[
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: 'BUG: <description>'\nlabels: ''\nassignees: ''\n\n---\n\nPlease search for existing issues and check for potential duplicates before filing yours.\n\n**ezQuake version:**\n(Specify the Git commit hash if using a non-stable build - this is available through the `/version` command)\n\n**OS/device including version:**\nSpecify GPU model, drivers, and the renderer (classic or modern) if graphics-related.\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\nAlso please attach your config (.cfg) file\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: 'REQ: <description>'\nlabels: ''\nassignees: ''\n\n---\n\nPlease ensure that your request is predominantly client-based, and not better suited to being logged with the server or mod aspects of QuakeWorld (e.g. mvdsv or ktx)\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n\n**Impact on server or mods**\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: main\n\non:\n  push:\n    # Only build branches on push, tags will be handled by 'release' job.\n    branches:\n      - '**'\n  pull_request:\n  workflow_dispatch:\n  release:\n    types: [published]\n\njobs:\n  build:\n    strategy:\n      matrix:\n        include:\n          - os: windows-latest\n            configurePreset: msbuild-x64\n            buildPreset: msbuild-x64-release\n            name: ezQuake-windows-x64\n            artifact: \"ezquake.exe\"\n            container: null\n          - os: macos-latest\n            configurePreset: macos-arm64\n            buildPreset: macos-arm64-release\n            name: ezQuake-macOS-arm64\n            artifact: \"ezQuake.zip\"\n            packages: [\"autoconf\", \"automake\", \"libtool\"]\n            container: null\n          - os: macos-latest\n            configurePreset: macos-x64\n            buildPreset: macos-x64-release\n            name: ezQuake-macOS-x64\n            artifact: \"ezQuake.zip\"\n            packages: [\"autoconf\", \"automake\", \"libtool\"]\n            container: null\n          - os: ubuntu-latest\n            configurePreset: dynamic\n            buildPreset: dynamic-release\n            name: ezQuake-linux-x86_64\n            artifact: \"ezQuake-x86_64.AppImage\"\n            cflags: \"-march=nehalem\"\n            packages: [\"build-essential\", \"ca-certificates\", \"cmake\", \"curl\", \"file\", \"git\", \"libcurl4-openssl-dev\", \"libexpat1-dev\", \"libfreetype-dev\", \"libfuse3-dev\", \"libjansson-dev\", \"libjpeg-dev\", \"libminizip-dev\", \"libpcre2-dev\", \"libpng-dev\", \"libsdl2-2.0-0\", \"libsdl2-dev\", \"libsndfile1-dev\", \"libspeex-dev\", \"libspeexdsp-dev\", \"ninja-build\", \"unzip\"]\n            container:\n              image: debian:testing\n              options: --privileged\n\n    name: ${{ matrix.name }}\n    runs-on: ${{ matrix.os }}\n    container: ${{ matrix.container }}\n\n    steps:\n    - name: Prepare Linux environment\n      run: |\n        apt-get -qq update && apt-get -qq install --no-install-recommends ${{ join(matrix.packages, ' ') }}\n        git config --global --add safe.directory $PWD\n      if: matrix.os == 'ubuntu-latest'\n\n    - name: Prepare macOS environment\n      run: brew install -q ${{ join(matrix.packages, ' ') }}\n      if: matrix.os == 'macos-latest'\n\n    - name: Check out code\n      uses: actions/checkout@v4\n      with:\n        submodules: true\n        # To get correct revision which is needed by server to detect certain client bugs\n        fetch-depth: 0\n\n    - name: Fetch upstream tags for version metadata\n      run: |\n          git remote add upstream https://github.com/QW-Group/ezquake-source.git\n          git fetch --tags --no-recurse-submodules upstream\n      if: github.repository != 'QW-Group/ezquake-source'\n\n    - name: Set library build type to release\n      shell: bash\n      run: |\n        # Hopefully overrideable via environment in the future. Done here as\n        # vcpkg is chainloaded before cmake, and doesn't seem to apply args.\n        # Avoids building both -dbg and -rel versions of static libraries.\n        for x in {cmake/triplets/*,vcpkg/triplets/{*,community/*}}; do\n          if [[ -f $x ]]; then\n            echo >> $x\n            echo 'set(VCPKG_BUILD_TYPE release)' >> $x\n          fi\n        done\n      if: matrix.os != 'ubuntu-latest'\n\n    - uses: lukka/get-cmake@latest\n      with:\n        cmakeVersion: \"~3.28.0\"\n      if: matrix.os != 'ubuntu-latest'\n\n    - name: Setup vcpkg\n      uses: lukka/run-vcpkg@v11\n      if: matrix.os != 'ubuntu-latest'\n\n    - name: Run CMake\n      uses: lukka/run-cmake@v10\n      with:\n        configurePreset: ${{ matrix.configurePreset }}\n        buildPreset: ${{ matrix.buildPreset }}\n      env:\n        CFLAGS: ${{ matrix.cflags }}\n\n    - name: Generate Linux AppImage\n      run: |\n        export EXECUTABLE=$(echo build-${{ matrix.configurePreset }}/Release/ezquake*)\n        ./misc/appimage/appimage-manual_creation.sh\n        mv ezQuake-x86_64.AppImage build-${{ matrix.configurePreset }}/Release/\n      if: matrix.os == 'ubuntu-latest'\n\n    - name: Preserve macOS executable bit\n      run: |\n        zip -r -9 ezQuake.zip ezQuake.app\n      working-directory: build-${{ matrix.configurePreset }}/Release\n      if: matrix.os == 'macos-latest'\n\n    - name: Archive client\n      uses: actions/upload-artifact@v4\n      with:\n        name: ${{ matrix.name }}\n        path: |\n          build-${{ matrix.configurePreset }}/Release/${{ matrix.artifact }}\n        compression-level: 9\n\n  macos-universal:\n    needs: build\n\n    name: \"ezQuake-macOS-universal\"\n    runs-on: macos-latest\n\n    steps:\n    - name: Check out code\n      uses: actions/checkout@v4\n\n    - name: Download ARM64 Build\n      uses: actions/download-artifact@v4\n      with:\n        name: ezQuake-macOS-arm64\n        path: artifacts/arm64\n\n    - name: Download Intel Build\n      uses: actions/download-artifact@v4\n      with:\n        name: ezQuake-macOS-x64\n        path: artifacts/x64\n\n    - name: Create Universal Binary\n      run: |\n        (cd artifacts/x64 && unzip -qq ezQuake.zip) && (cd artifacts/arm64 && unzip -qq ezQuake.zip)\n        cp -r artifacts/arm64/ezQuake.app .\n        lipo -create -output ezQuake.app/Contents/MacOS/ezQuake \\\n            artifacts/x64/ezQuake.app/Contents/MacOS/ezQuake \\\n            artifacts/arm64/ezQuake.app/Contents/MacOS/ezQuake\n        codesign --force --sign - --entitlements dist/macOS/ezquake.entitlements.plist --options runtime --timestamp ezQuake.app\n        zip -r ezQuake-macOS-universal.zip ezQuake.app\n\n    - name: Delete macOS arch specific builds\n      uses: geekyeggo/delete-artifact@v5\n      with:\n        name: |\n          ezQuake-macOS-arm64\n          ezQuake-macOS-x64\n\n    - name: Upload Build Artifact\n      uses: actions/upload-artifact@v4\n      with:\n        name: ezQuake-macOS-universal\n        path: ezQuake-macOS-universal.zip\n        compression-level: 9\n\n  upload:\n    if: github.repository == 'QW-group/ezquake-source' && ((github.event_name == 'push' && github.ref == 'refs/heads/master') || github.event_name == 'release')\n    needs: [macos-universal]\n    runs-on: ubuntu-latest\n    steps:\n      - name: Download Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: artifacts\n\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          path: ezquake\n          submodules: true\n          # To get correct revision when generating version.json for source release tarball\n          fetch-depth: 0\n        if: github.event_name == 'release'\n\n      - name: Get release date\n        run: |\n          REF_NAME=\"${{ github.ref_name || github.event.release.tag_name }}\"\n          RELEASE_DATE=$(git log -1 --format=%cI \"$REF_NAME\")\n          echo \"RELEASE_DATE=$RELEASE_DATE\" >> $GITHUB_ENV\n        working-directory: ezquake\n        if: github.event_name == 'release'\n\n      - name: Collect GitHub release artifacts\n        run: |\n          set -e\n\n          dist_dir=\"${GITHUB_WORKSPACE}/dist\"\n          mkdir \"${dist_dir}\"\n\n          # Reset timestamp to time of commit before compression\n          find \"artifacts\" -type f -exec touch --date=\"${RELEASE_DATE}\" {} +\n\n          # Set executable bit for all files\n          find \"artifacts\" -type f -exec chmod 755 {} +\n\n          # Recompress before attaching to release, avoiding double-zip of macOS\n          (\n            cd artifacts\n            for target in *; do\n              if ls \"${target}\"/*.zip &> /dev/null; then\n                cp \"${target}\"/*.zip \"${dist_dir}/${target}.zip\"\n              else\n                (cd \"${target}\"; zip -o -9 \"${dist_dir}/${target}.zip\" *)\n              fi\n            done;\n          )\n\n          # Generate source release with submodule, version.json etc\n          (\n            cd \"${GITHUB_WORKSPACE}/ezquake\"\n            dist/gen-release.sh\n            mv *.tar.gz \"${dist_dir}/\"\n          )\n\n          find \"${dist_dir}\" -type f -execdir sha256sum {} \\; > \"checksums.txt\"\n          mv \"checksums.txt\" \"${dist_dir}/\"\n\n          # Reset timestamp to time of commit for all release artifacts\n          find \"${dist_dir}\" -type f -exec touch --date=\"${RELEASE_DATE}\" {} +\n\n          echo \"Release artifacts:\"\n          ls -lR \"${dist_dir}\"\n\n          echo \"Checksums:\"\n          cat \"${dist_dir}/checksums.txt\"\n\n          echo \"GITHUB_ARTIFACTS=$dist_dir\" >> $GITHUB_ENV\n        if: github.event_name == 'release'\n\n      - name: Attach artifacts to GitHub release\n        uses: softprops/action-gh-release@v2\n        with:\n          files: ${{ env.GITHUB_ARTIFACTS }}/*\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        if: github.event_name == 'release'\n\n      - name: Prepare Upload Environemnt\n        run: |\n          sudo apt-get -qq update\n          sudo apt-get -qq --no-install-recommends install openssh-client\n\n      - name: Setup SSH\n        run: |\n          ssh-agent -a $SSH_AUTH_SOCK > /dev/null\n          ssh-add - <<< \"${{ secrets.SSH_PRIVATE_KEY }}\"\n        env:\n          SSH_AUTH_SOCK: /tmp/ssh_agent.sock\n\n      - name: Prepare upload to builds.quakeworld.nu\n        run: |\n          # Build file structure for uploading\n          # snapshots:\n          #  upload/snapshots/latest/$os/$arch/$filename\n          #  upload/snapshots/$os/$arch/$prefix_$filename\n          # releases:\n          #  upload/releases/latest/$os/$arch/$filename\n          #  upload/releases/$tag/$os/$arch/$filename\n\n          set -e\n\n          upload_dir=\"${GITHUB_WORKSPACE}/upload\"\n          if [[ $GITHUB_REF == refs/tags/* ]]; then\n            main_dir=\"${upload_dir}/releases/${{ github.ref_name }}\"\n            latest_dir=\"${upload_dir}/releases/latest\"\n            prefix=\"\"\n            upload_prefix=\"releases\"\n          else\n            main_dir=\"${upload_dir}/snapshots\"\n            latest_dir=\"${upload_dir}/snapshots/latest\"\n            date=$(TZ=\"Europe/Amsterdam\" date \"+%Y%m%d-%H%M%S\")\n            prefix=\"${date}_${GITHUB_SHA::7}_\"\n            upload_prefix=\"snapshots\"\n          fi\n\n          # Collect upload structure\n          for artifact in artifacts/*/*; do\n            artifact_file=$(basename \"${artifact}\")\n            artifact_dir=$(dirname \"${artifact}\")\n\n            IFS='-' read -r ezq os arch <<< \"${artifact_dir}\"\n\n            mkdir -p \"${main_dir}/${os}/${arch}\" \"${latest_dir}/${os}/${arch}\"\n\n            cp \"${artifact}\" \"${main_dir}/${os}/${arch}/${prefix}${artifact_file}\"\n            cp \"${artifact}\" \"${latest_dir}/${os}/${arch}/${artifact_file}\"\n          done\n\n          # Set executable bit for all files\n          find \"${upload_dir}\" -type f -exec chmod 755 {} +\n\n          # Generate checksums\n          for artifact in $(find \"${upload_dir}\" -type f); do\n            artifact_file=$(basename \"${artifact}\")\n            artifact_dir=$(dirname \"${artifact}\")\n            (cd \"${artifact_dir}\" && md5sum \"${artifact_file}\" > \"${artifact_file}.md5\")\n          done\n\n          # Reset timestamp to time of commit\n          find \"${upload_dir}\" -type f -exec touch --date=\"${RELEASE_DATE}\" {} +\n\n          echo \"Upload artifacts:\"\n          ls -lR \"${upload_dir}\"\n\n          echo \"UPLOAD_PREFIX=${upload_prefix}\" >> $GITHUB_ENV\n\n      - name: Upload to builds.quakeworld.nu\n        env:\n          SSH_AUTH_SOCK: /tmp/ssh_agent.sock\n        run: |\n          sftp -rp -o 'StrictHostKeyChecking no' -o 'UserKnownHostsFile /dev/null' -P ${{ secrets.SFTP_PORT }} ${{ secrets.SFTP_USERNAME }}@${{ secrets.SFTP_HOST }}:/${{ env.UPLOAD_PREFIX }} <<< $'put -rp upload/${{ env.UPLOAD_PREFIX }}/*'\n"
  },
  {
    "path": ".gitignore",
    "content": "ezquake\r\nezquake.exe\r\nezquake-*.exe\r\nezquake-*-*\r\nezQuake.aps\r\n*.vcxproj*.user\r\n*.vcxproj*.cache\r\nSysPrintf.log\r\n.ezquake/\r\n.vs\r\n/.vscode\r\n*.json.c\r\n*.glsl.c\r\nvcpkg_installed/\r\nsrc/.msversion.h\r\n/build*/\r\n\r\n!.gitignore\r\n\r\n# Local documentation files (not for repository)\r\nCR*.md\r\nEZQUAKE_HUD_*.md\r\nTRACKER_*.md\r\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"src/qwprot\"]\n\tpath = src/qwprot\n\turl = https://github.com/QW-Group/qwprot.git\n\tbranch = master\n[submodule \"vcpkg\"]\n\tpath = vcpkg\n\turl = https://github.com/Microsoft/vcpkg\n"
  },
  {
    "path": "BUILD.md",
    "content": "# Compiling ezQuake\n\n## Introduction\n\nTo provide a consistent build on Windows, Linux, macOS and BSD across various compilers,\ndynamic and static linking, all described in one single file, [CMake][cmake] is used, with\nthe help of [vcpkg][vcpkg] to support static linking.\n\nIn its most basic form on Linux/BSD, with dynamic linking, a build can be invoked as follows:\n\n```shell\nmkdir build && cd build\ncmake .. -DCMAKE_BUILD_TYPE=Release # Configuration phase\nmake -j $(nproc)                    # Build phase\n```\n\nThis will locate all system-wide dependencies and show a clear message if a mandatory\ndependency is not installed. Check the output to see if it matches your expectations\nin regard to optional dependencies.\n\nWhen configure phase passes, the build phase will produce a dynamically linked binary.\nThe resulting binary can be found in the build directory at `build/ezquake-linux-x86_64`.\n\nThe default mode of compilation is to not show the full command line, only errors and\nwarnings. To enable verbose mode, set `CMAKE_VERBOSE_MAKEFILE` to `ON`.\n\nWhile CMake can be configured via `CMAKE_C_CFLAGS`, it also supports picking up `CFLAGS`\nfrom the aptly named environment variable `CFLAGS`.\n\nThe ezQuake build also declares a few options on its own to customize the resulting executable.\nUnfortunately CMake CLI has no built-in command to list them, so check `CMakeLists.txt`:\n\n```shell\ngrep -E ^option CMakeLists.txt\n```\n\nPutting the above customizations to work may look like this:\n```shell\nexport CFLAGS=-march=native\nmkdir build && cd build\ncmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_VERBOSE_MAKEFILE=ON -DRENDERER_CLASSIC_OPENGL=OFF\nmake -j $(nproc)\n```\n\nCMake caches as much as possible when the build is initialized to allow for fast iteration\nwhen developing. Should an option not activate as expected, removing the build directory\nand starting over is always a failsafe. Static dependencies are [cached elsewhere](#Caches)\nand will not have to be rebuilt if only the build directory is removed.\n\n## CMake Presets\n\n[CMake presets][cmake-presets] can be seen as aliases that associates a number of build\nsettings to a name. While this may not add much value for the trivial dynamically linked\nLinux/BSD build, the presets do add some desirable conveniences for other targets as is \nseen in later sections.\n\nThere are two types of presets relevant to the ezQuake build:\n\n* Configuration Presets\n* Build Presets\n\nThe available presets can be listed via:\n\n```shell\n$ cmake --list-presets\nAvailable configure presets:\n\n  \"mingw64-x64-cross\"\n  \"mingw64-i686-cross\"\n  ...\n\n$ cmake --build --list-presets\nAvailable build presets:\n\n  \"msbuild-x64-debug\"                 - Build msbuild-x64 debug\n  \"msbuild-x64-release\"               - Build msbuild-x64 release\n  \"msbuild-x64-relwithdebinfo\"        - Build msbuild-x64 release with debug info\n  ...\n```\n\nThese can be invoked, regardless of underlying build system, via:\n\n```shell\n# Creates build-dynamic directory, and initializes the build\ncmake --preset dynamic \n\n# Builds executable in build-dynamic/Release/ezquake\ncmake --build --preset dynamic-release\n\n# ...or if skipping the build preset, pass path to build directory instead\ncamke --build build-dynamic --config Release\n```\n\nThe build directory is by convention always called `build-${presetName}` when using presets.\n\nAll declared presets are of type _Multi-Config_ so they're able to produce _Debug_,\n_Release_, and _RelWithDebInfo_ variants from the same configuration.\n\nWhile build presets are somewhat optional when building from the terminal, they add\nconvenience when working from within an editor with CMake support.\n\n## Static linking\n\nStatic builds are supported on Windows, Linux and macOS and offloads building of\ndependencies to [vcpkg][vcpkg]. The CMake configuration is agnostic to dynamic or\nstatic linking and enabling of static linking happens via parametrizing the CMake\ninvocation with the vcpkg toolchain. This is done automatically by using any of the\nWindows, macOS, or the static presets.\n\nBefore starting a static build, vcpkg needs to be initialized. This is done by invoking\n`bootstrap.sh` on *nix-like platforms. Depending on which [type of build](#Visual-Studio)\nis used on Windows, the `bootstrap.ps1` PowerShell script does the equivalent.\n\nThe invocation of a Linux static build is near identical to a dynamic build:\n```shell\n./bootstrap.sh\ncmake --preset static\ncmake --build build-static --config Release\n```\n\nIf you haven't compiled a static version before, or if the project has updated the vcpkg\nrepository version since last build, this will take a few minutes.\n\nNote that for Linux/BSD builds, you have to install the appropriate X11/Wayland development\nheaders for the static build to work with each of those targets.\n\n## Cross-Compilation to Windows\n\nAny *nix environment can produce a Windows build via:\n```shell\n./bootstrap.sh\ncmake --preset mingw64-x64-cross\ncmake --build build-mingw64-x64-cross --config Release\n```\n\nBe sure to install mingw-w64 before running the above commands. A native compiler is also needed as parts of the vcpkg\nbuild need to execute on the host.\n\n## Visual Studio\n\n### CMake Mode\n\nMicrosoft is highly invested in both CMake and vcpkg, so native support in Visual Studio has\nexisted for a number of years by now. Importing the project in Visual Studio 17 detects the\nCMake and vcpkg combination and builds the dependencies.\n\nOnce done, the build presets will be listed in the _Configurations_ drop down. The `msbuild-*`\nor `ninja-msvc-*` related build presets are typically a good fit.\n\nTo run ezQuake against a specific game directory, go via menu to _Debug_ > _Debug and Launch\nSettings for ezquake_ which will open up the `launch_schema.json` file where you introduce\n`currentDir` similar to the following:\n\n```json\n{\n  \"version\": \"...\",\n  \"defaults\": {},\n  \"configurations\": [\n    {\n      \"type\": \"...\",\n      \"currentDir\": \"C:\\\\Quake\"\n    }\n  ]\n}\n```\n\n### Visual Studio Solution Mode\n\nAs the Visual Studio Solution is generated first after CMake configuration phase has finished,\nthe `./bootstrap.ps1` PowerShell script must be invoked the first time to initialize vcpkg.\n\n```shell\npowershell -File bootstrap.ps1\ncmake --preset msbuild-x64\n```\n\nOnce done you will have a Visual Studio Solution in `build-msbuild-x64/ezquake.sln`.\nAny compilation changes that should be upstreamed must be updated in `CMakeLists.txt`.\n\nDuring the configuration phase, if a `ezquake.vcxproj.user` file exists in the top directory,\nthis will be copied to the build directory next to the Visual Studio Solution to allow for\npersisting custom settings as the solution is generated.\n\n## Xcode / macOS\n\nTo simplify bundling static linking is used to build ezQuake on macOS, so start off by\ninvoking `bootstrap.sh` first. When initializing for example the `macos-arm64` preset\nan Xcode project will be produced at `build-macos-arm64/ezquake.xcodeproj` with similar\nstructure to that of Visual Studio.\n\nThe same preset is also used when building via the terminal:\n```\n./bootstrap.sh\ncmake --preset macos-arm64\ncmake --build build-macos-arm64 --config Release\n```\nThis will produce `ezQuake.app` under `build-macos-arm64/Release/ezQuake.app` by invoking\n`xcodebuild` behind the scenes to do the actual building and bundling.\n\n## Caches\n\nIf you don't intend to build ezQuake again and want to reclaim some space, you can find the shared vcpkg cache at:\n\n* All platforms\n  * `./vcpkg/buildtrees`\n* *nix\n  * `~/.cache/vcpkg/`\n* Windows\n  * `c:\\Users\\$UserName\\AppData\\Local\\vcpkg`\n\n## Developer Tidbits\n\n### Adding new files to the project\n\nIn [CMakeLists.txt](CMakeLists.txt) source files are categorized into their approximate use\ncases (client, server, common, sys, etc), and this also applies to header files. The reason\nfor this is to serve editors with the correct context, and if generating either a Xcode or\nVisual Studio project this categorization is also visualized in the project tree view.\n\nLooking forward, this will allow adding a `ezquake-sv` target by just reusing the relevant\nsubset of source files.\n\n### Managing static dependencies\n\nVcpkg is used in [manifest mode][vcpkg-manifest], with the dependencies declared in `vcpkg.json`.\nWhile it's possible to lock dependencies at a specific version, this is not used today. Instead\nthe vcpkg submodule dictates the set of dependency versions the project relies on.\n\nIf adding a new mandatory dependency that static versions of ezQuake should use, first find it\nat [vcpkg.io][vcpkg-list], and if missing, read up on [overlay ports][vcpkg-overlay].\n\n### Target platforms\n\nIf a specific platform requires customizations to how the static dependencies are built a [_triplet_][vcpkg-triplets]\nfor this platform can be introduced. An example of such an override is the [x64 MinGW](cmake/triplets/x64-mingw-static.cmake)\ntriplet that adds `-march=nehalem` when building dependencies.\n\n [cmake]: https://cmake.org/\n [cmake-presets]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html\n [cmake-cflags]: https://cmake.org/cmake/help/latest/envvar/CFLAGS.html\n [vcpkg]: https://learn.microsoft.com/en-us/vcpkg/\n [vcpkg-manifest]: https://learn.microsoft.com/en-us/vcpkg/concepts/manifest-mode\n [vcpkg-list]: https://vcpkg.io/en/\n [vcpkg-overlay]: https://learn.microsoft.com/en-us/vcpkg/concepts/overlay-ports\n [vcpkg-triplets]: https://learn.microsoft.com/en-us/vcpkg/users/triplets\n [vscmake]: https://learn.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.22)\n\nproject(ezquake C)\n\nif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n    enable_language(OBJC)\nendif()\n\noption(USE_SYSTEM_LIBS          \"Use system libraries instead of VCPKG\"       ON)\noption(RENDERER_MODERN_OPENGL   \"Enable modern OpenGL renderer\"               ON)\noption(RENDERER_CLASSIC_OPENGL  \"Enable classic OpenGL renderer\"              ON)\noption(DEBUG_MEMORY_ALLOCATIONS \"Enable debug prints for memory allocations\" OFF)\noption(ENABLE_SANDBOX           \"Enables application sandboxing (macOS)\"      ON)\noption(ENABLE_LTO               \"Enable Link Time Optimization\"               ON)\n\nif(NOT RENDERER_CLASSIC_OPENGL AND NOT RENDERER_MODERN_OPENGL)\n    message(FATAL_ERROR \"At least one of RENDERER_CLASSIC_OPENGL or RENDERER_MODERN_OPENGL must be enabled.\")\nendif()\n\nset(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)\n\ninclude(GitUtils)\ninclude(AddResources)\ninclude(CheckIPOSupported)\ninclude(CheckCCompilerFlag)\ninclude(CheckDependency)\n\ngit_refresh_submodules()\ngit_extract_version(git_version)\n\n# Xcode has its own LTO features.\nif(ENABLE_LTO AND NOT CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n    check_ipo_supported(RESULT USE_LTO)\n    if(USE_LTO)\n        set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)\n        set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE)\n    else()\n        message(FATAL_ERROR \"Link Time Optimization requested, but not available.\")\n    endif()\nendif()\n\nif(ENABLE_LTO)\n    message(\"-- Link Time Optimization: Enabled\")\nendif()\n\nif(CMAKE_C_COMPILER_ID STREQUAL \"MSVC\")\n    add_compile_options(\n            /nologo\n            /W3\n            /WX-\n            /diagnostics:column\n    )\n\n    set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$<CONFIG:Debug>:Debug>)\n    set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT $<$<CONFIG:Debug>:ProgramDatabase>)\n\n    # Enable automatic parallelization of MSBuild\n    set(CMAKE_VS_GLOBALS\n            \"UseMultiToolTask=true\"\n            \"EnforceProcessCountAcrossBuilds=true\"\n    )\n\n    if(EXISTS \"${PROJECT_SOURCE_DIR}/ezquake.vcxproj.user\")\n        configure_file(\"${PROJECT_SOURCE_DIR}/ezquake.vcxproj.user\" \"${CMAKE_BINARY_DIR}/ezquake.vcxproj.user\" COPYONLY)\n    endif()\nelse()\n    add_compile_options(\n            -std=gnu89\n\n            -Wall\n            -Wno-pointer-to-int-cast\n            -Wno-int-to-pointer-cast\n            -Wno-strict-aliasing\n            -Wno-deprecated-declarations\n\n            -fvisibility=hidden\n\n            -Werror=format\n            -Werror=strict-prototypes\n            -Werror=old-style-definition\n\n            $<$<CONFIG:Release>:-Werror=unused-function>\n            $<$<CONFIG:Release>:-Werror=unused-variable>\n    )\n\n    check_c_compiler_flag(\"-Wstrlcpy-strlcat-size\" HAS_STRLCPY_STRLCAT_SIZE)\n    if(HAS_STRLCPY_STRLCAT_SIZE)\n        add_compile_options(\"-Werror=strlcpy-strlcat-size\")\n    endif()\n    check_c_compiler_flag(\"-Wformat-truncation\" HAS_FORMAT_TRUNCATION)\n    if(HAS_FORMAT_TRUNCATION)\n        add_compile_options(\"-Wno-error=format-truncation\" \"-Wno-format-truncation\")\n    endif()\n    check_c_compiler_flag(\"-Wparentheses\" HAS_PARENTHESIS)\n    if(HAS_PARENTHESIS)\n        add_compile_options(\"-Wno-parentheses\")\n    endif()\n    check_c_compiler_flag(\"-Wmisleading-indentation\" HAS_MISLEADING_INDENTATION)\n    if(HAS_MISLEADING_INDENTATION)\n        add_compile_options(\"-Wno-misleading-indentation\")\n    endif()\n    # While not gnu89, it's in practice allowed by all supported compilers\n    check_c_compiler_flag(\"-Wtypedef-redefinition\" HAS_TYPE_REDEFINITION)\n    if(HAS_TYPE_REDEFINITION)\n        add_compile_options(\"-Wno-typedef-redefinition\")\n    endif()\n    # Should mostly be fixed in mvdsv, and next sync can remove this\n    check_c_compiler_flag(\"-Wshorten-64-to-32\" HAS_SHORTEN_64_TO_32)\n    if(HAS_TYPE_REDEFINITION)\n        add_compile_options(\"-Wno-shorten-64-to-32\")\n    endif()\nendif()\n\nfind_library(MATH m)\n\n# Override by configuring with -DOpenGL_GL_PREFERENCE=GLVND\nif(NOT OpenGL_GL_PREFERENCE)\n    set(OpenGL_GL_PREFERENCE LEGACY)\nendif()\nfind_package(OpenGL REQUIRED)\nfind_package(Threads REQUIRED)\n\n# Args: target name, pkg-config name, vcpkg package name, vcpkg target name, extra args\ncheck_dependency(Expat    \"expat\"      \"EXPAT\"              \"expat::expat\"                 REQUIRED CONFIG)\ncheck_dependency(FreeType \"freetype2\"  \"Freetype\"           \"Freetype::Freetype\"           OPTIONAL)\ncheck_dependency(JPEG     \"libjpeg\"    \"JPEG\"               \"JPEG::JPEG\"                   REQUIRED)\ncheck_dependency(Jansson  \"jansson\"    \"jansson\"            \"jansson::jansson\"             REQUIRED)\ncheck_dependency(MiniZip  \"minizip\"    \"unofficial-minizip\" \"unofficial::minizip::minizip\" REQUIRED)\ncheck_dependency(PCRE2    \"libpcre2-8\" \"PCRE2\"              \"pcre2::pcre2-8-static\"        REQUIRED)\ncheck_dependency(PNG      \"libpng\"     \"PNG\"                \"PNG::PNG\"                     REQUIRED)\ncheck_dependency(SDL2     \"sdl2\"       \"SDL2\"               \"SDL2::SDL2-static\"            REQUIRED)\ncheck_dependency(SndFile  \"sndfile\"    \"SndFile\"            \"SndFile::sndfile\"             REQUIRED)\ncheck_dependency(Speex    \"speex\"      \"Speex\"              \"SPEEX::SPEEX\"                 OPTIONAL)\ncheck_dependency(SpeexDSP \"speexdsp\"   \"SpeexDSP\"           \"SPEEX::SPEEXDSP\"              OPTIONAL)\ncheck_dependency(cURL     \"libcurl\"    \"CURL\"               \"CURL::libcurl\"                REQUIRED CONFIG)\ncheck_dependency(zlib     \"zlib\"       \"ZLIB\"               \"ZLIB::ZLIB\"                   REQUIRED)\n\nif(USE_SYSTEM_LIBS AND HAVE_MINIZIP)\n    find_path(EZQUAKE_MINIZIP_INCLUDE_DIR\n            NAMES unzip.h\n            HINTS ${MiniZip_INCLUDE_DIRS}\n            PATH_SUFFIXES minizip\n            REQUIRED\n    )\nendif()\n\nif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n    find_library(FRAMEWORK_APPKIT AppKit REQUIRED)\n    find_library(FRAMEWORK_FOUNDATION Foundation REQUIRED)\n    find_library(FRAMEWORK_CORESERVICES CoreServices REQUIRED)\n    find_library(FRAMEWORK_GAMECONTROLLER GameController REQUIRED)\nendif()\n\n# Place special CMake targets in separate VS/Xcode folder\nset_property(GLOBAL PROPERTY USE_FOLDERS ON)\nset_property(GLOBAL PROPERTY VS_STARTUP_PROJECT \"ezquake\")\n\nset(SOURCE_DIR ${PROJECT_SOURCE_DIR}/src)\n\nset(common_headers\n        ${SOURCE_DIR}/bspfile.h\n        ${SOURCE_DIR}/cmd.h\n        ${SOURCE_DIR}/cmdline_params.h\n        ${SOURCE_DIR}/cmdline_params_ids.h\n        ${SOURCE_DIR}/cmodel.h\n        ${SOURCE_DIR}/common.h\n        ${SOURCE_DIR}/crc.h\n        ${SOURCE_DIR}/cvar.h\n        ${SOURCE_DIR}/cvar_groups.h\n        ${SOURCE_DIR}/fs.h\n        ${SOURCE_DIR}/hash.h\n        ${SOURCE_DIR}/macro_definitions.h\n        ${SOURCE_DIR}/macro_ids.h\n        ${SOURCE_DIR}/mathlib.h\n        ${SOURCE_DIR}/net.h\n        ${SOURCE_DIR}/parser.h\n        ${SOURCE_DIR}/pmove.h\n        ${SOURCE_DIR}/q_platform.h\n        ${SOURCE_DIR}/q_shared.h\n        ${SOURCE_DIR}/sha1.h\n        ${SOURCE_DIR}/sha3.h\n        ${SOURCE_DIR}/version.h\n        ${SOURCE_DIR}/vfs.h\n        ${SOURCE_DIR}/vfs_tar.h\n        ${SOURCE_DIR}/zone.h\n)\nset(common\n        ${SOURCE_DIR}/cmd.c\n        ${SOURCE_DIR}/cmodel.c\n        ${SOURCE_DIR}/com_msg.c\n        ${SOURCE_DIR}/common.c\n        ${SOURCE_DIR}/crc.c\n        ${SOURCE_DIR}/cvar.c\n        ${SOURCE_DIR}/fs.c\n        ${SOURCE_DIR}/hash.c\n        ${SOURCE_DIR}/mathlib.c\n        ${SOURCE_DIR}/md4.c\n        ${SOURCE_DIR}/net.c\n        ${SOURCE_DIR}/net_chan.c\n        ${SOURCE_DIR}/parser.c\n        ${SOURCE_DIR}/pmove.c\n        ${SOURCE_DIR}/pmovetst.c\n        ${SOURCE_DIR}/q_shared.c\n        ${SOURCE_DIR}/sha1.c\n        ${SOURCE_DIR}/sha3.c\n        ${SOURCE_DIR}/version.c\n        ${SOURCE_DIR}/vfs_doomwad.c\n        ${SOURCE_DIR}/vfs_gzip.c\n        ${SOURCE_DIR}/vfs_mmap.c\n        ${SOURCE_DIR}/vfs_os.c\n        ${SOURCE_DIR}/vfs_pak.c\n        ${SOURCE_DIR}/vfs_tar.c\n        ${SOURCE_DIR}/vfs_tcp.c\n        ${SOURCE_DIR}/vfs_zip.c\n        ${SOURCE_DIR}/zone.c\n        ${common_headers}\n)\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Source Files/common\" FILES ${common})\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Header Files/common\" FILES ${common_headers})\n\nset(QWPROT_SOURCE_DIR ${SOURCE_DIR}/qwprot/src)\nset(qwprot_headers\n        ${QWPROT_SOURCE_DIR}/protocol.h\n)\nsource_group(TREE ${QWPROT_SOURCE_DIR} PREFIX \"Header Files/qwprot\" FILES ${qwprot_headers})\n\nset(server_headers\n        ${SOURCE_DIR}/g_public.h\n        ${SOURCE_DIR}/pr2.h\n        ${SOURCE_DIR}/pr_comp.h\n        ${SOURCE_DIR}/progdefs.h\n        ${SOURCE_DIR}/progs.h\n        ${SOURCE_DIR}/qwsvdef.h\n        ${SOURCE_DIR}/server.h\n        ${SOURCE_DIR}/sv_log.h\n        ${SOURCE_DIR}/sv_mod_frags.h\n        ${SOURCE_DIR}/sv_world.h\n        ${SOURCE_DIR}/vm.h\n        ${SOURCE_DIR}/vm_local.h\n)\nset(server\n        ${SOURCE_DIR}/pr2_cmds.c\n        ${SOURCE_DIR}/pr2_edict.c\n        ${SOURCE_DIR}/pr2_exec.c\n        ${SOURCE_DIR}/pr_cmds.c\n        ${SOURCE_DIR}/pr_edict.c\n        ${SOURCE_DIR}/pr_exec.c\n        ${SOURCE_DIR}/sv_ccmds.c\n        ${SOURCE_DIR}/sv_demo.c\n        ${SOURCE_DIR}/sv_demo_misc.c\n        ${SOURCE_DIR}/sv_demo_qtv.c\n        ${SOURCE_DIR}/sv_ents.c\n        ${SOURCE_DIR}/sv_init.c\n        ${SOURCE_DIR}/sv_login.c\n        ${SOURCE_DIR}/sv_main.c\n        ${SOURCE_DIR}/sv_master.c\n        ${SOURCE_DIR}/sv_mod_frags.c\n        ${SOURCE_DIR}/sv_move.c\n        ${SOURCE_DIR}/sv_nchan.c\n        ${SOURCE_DIR}/sv_phys.c\n        ${SOURCE_DIR}/sv_save.c\n        ${SOURCE_DIR}/sv_send.c\n        ${SOURCE_DIR}/sv_user.c\n        ${SOURCE_DIR}/sv_world.c\n        ${SOURCE_DIR}/vm.c\n        ${SOURCE_DIR}/vm_interpreted.c\n        ${SOURCE_DIR}/vm_x86.c\n        ${server_headers}\n)\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Source Files/server\" FILES ${server})\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Header Files/server\" FILES ${server_headers})\n\nif(RENDERER_MODERN_OPENGL OR RENDERER_CLASSIC_OPENGL)\n    set(SHARED_GLSL_DIR ${SOURCE_DIR}/glsl/shared)\n    add_resources(shaders_shared\n            ${SHARED_GLSL_DIR}/fxaa.h.glsl\n    )\n    source_group(TREE ${SOURCE_DIR} PREFIX \"Source Files/shared_opengl\" FILES ${shared_opengl})\n    source_group(TREE ${SOURCE_DIR} PREFIX \"Header Files/shared_opengl\" FILES ${shared_opengl_headers})\nendif()\n\nif(RENDERER_MODERN_OPENGL)\n    set(MODERN_GLSL_DIR ${SOURCE_DIR}/glsl)\n    add_resources(shaders_modern\n            ${MODERN_GLSL_DIR}/common.glsl\n            ${MODERN_GLSL_DIR}/constants.glsl\n            ${MODERN_GLSL_DIR}/draw_aliasmodel.fragment.glsl\n            ${MODERN_GLSL_DIR}/draw_aliasmodel.vertex.glsl\n            ${MODERN_GLSL_DIR}/draw_sprites.fragment.glsl\n            ${MODERN_GLSL_DIR}/draw_sprites.vertex.glsl\n            ${MODERN_GLSL_DIR}/draw_world.fragment.glsl\n            ${MODERN_GLSL_DIR}/draw_world.vertex.glsl\n            ${MODERN_GLSL_DIR}/fx_world_geometry.fragment.glsl\n            ${MODERN_GLSL_DIR}/fx_world_geometry.vertex.glsl\n            ${MODERN_GLSL_DIR}/hud_draw_circle.fragment.glsl\n            ${MODERN_GLSL_DIR}/hud_draw_circle.vertex.glsl\n            ${MODERN_GLSL_DIR}/hud_draw_image.fragment.glsl\n            ${MODERN_GLSL_DIR}/hud_draw_image.geometry.glsl\n            ${MODERN_GLSL_DIR}/hud_draw_image.vertex.glsl\n            ${MODERN_GLSL_DIR}/hud_draw_line.fragment.glsl\n            ${MODERN_GLSL_DIR}/hud_draw_line.vertex.glsl\n            ${MODERN_GLSL_DIR}/hud_draw_polygon.fragment.glsl\n            ${MODERN_GLSL_DIR}/hud_draw_polygon.vertex.glsl\n            ${MODERN_GLSL_DIR}/lighting.compute.glsl\n            ${MODERN_GLSL_DIR}/post_process_screen.fragment.glsl\n            ${MODERN_GLSL_DIR}/post_process_screen.vertex.glsl\n            ${MODERN_GLSL_DIR}/simple.fragment.glsl\n            ${MODERN_GLSL_DIR}/simple.vertex.glsl\n            ${MODERN_GLSL_DIR}/simple3d.fragment.glsl\n            ${MODERN_GLSL_DIR}/simple3d.vertex.glsl\n    )\n\n    set(modern_opengl_headers\n            ${SOURCE_DIR}/glm_brushmodel.h\n            ${SOURCE_DIR}/glm_draw.h\n            ${SOURCE_DIR}/glm_local.h\n            ${SOURCE_DIR}/glm_particles.h\n            ${SOURCE_DIR}/glm_texture_arrays.h\n            ${SOURCE_DIR}/glm_vao.h\n    )\n    set(modern_opengl\n            ${SOURCE_DIR}/glm_aliasmodel.c\n            ${SOURCE_DIR}/glm_brushmodel.c\n            ${SOURCE_DIR}/glm_draw.c\n            ${SOURCE_DIR}/glm_framebuffer.c\n            ${SOURCE_DIR}/glm_lightmaps.c\n            ${SOURCE_DIR}/glm_main.c\n            ${SOURCE_DIR}/glm_md3.c\n            ${SOURCE_DIR}/glm_misc.c\n            ${SOURCE_DIR}/glm_particles.c\n            ${SOURCE_DIR}/glm_performance.c\n            ${SOURCE_DIR}/glm_rmain.c\n            ${SOURCE_DIR}/glm_rsurf.c\n            ${SOURCE_DIR}/glm_sdl.c\n            ${SOURCE_DIR}/glm_sprite.c\n            ${SOURCE_DIR}/glm_sprite3d.c\n            ${SOURCE_DIR}/glm_state.c\n            ${SOURCE_DIR}/glm_texture_arrays.c\n            ${SOURCE_DIR}/glm_vao.c\n            ${modern_opengl_headers}\n    )\n    source_group(TREE ${SOURCE_DIR} PREFIX \"Source Files/modern_opengl\" FILES ${modern_opengl})\n    source_group(TREE ${SOURCE_DIR} PREFIX \"Header Files/modern_opengl\" FILES ${modern_opengl_headers})\nendif()\n\nif(RENDERER_CLASSIC_OPENGL)\n    set(CLASSIC_GLSL_DIR ${SOURCE_DIR}/glsl/glc)\n    add_resources(shaders_classic\n            ${CLASSIC_GLSL_DIR}/glc_aliasmodel_shadow.fragment.glsl\n            ${CLASSIC_GLSL_DIR}/glc_aliasmodel_shadow.vertex.glsl\n            ${CLASSIC_GLSL_DIR}/glc_aliasmodel_shell.fragment.glsl\n            ${CLASSIC_GLSL_DIR}/glc_aliasmodel_shell.vertex.glsl\n            ${CLASSIC_GLSL_DIR}/glc_aliasmodel_std.fragment.glsl\n            ${CLASSIC_GLSL_DIR}/glc_aliasmodel_std.vertex.glsl\n            ${CLASSIC_GLSL_DIR}/glc_caustics.fragment.glsl\n            ${CLASSIC_GLSL_DIR}/glc_caustics.vertex.glsl\n            ${CLASSIC_GLSL_DIR}/glc_draw_sprites.fragment.glsl\n            ${CLASSIC_GLSL_DIR}/glc_draw_sprites.vertex.glsl\n            ${CLASSIC_GLSL_DIR}/glc_hud_images.fragment.glsl\n            ${CLASSIC_GLSL_DIR}/glc_hud_images.vertex.glsl\n            ${CLASSIC_GLSL_DIR}/glc_post_process_screen.fragment.glsl\n            ${CLASSIC_GLSL_DIR}/glc_post_process_screen.vertex.glsl\n            ${CLASSIC_GLSL_DIR}/glc_sky.fragment.glsl\n            ${CLASSIC_GLSL_DIR}/glc_sky.vertex.glsl\n            ${CLASSIC_GLSL_DIR}/glc_turbsurface.fragment.glsl\n            ${CLASSIC_GLSL_DIR}/glc_turbsurface.vertex.glsl\n            ${CLASSIC_GLSL_DIR}/glc_world_drawflat.fragment.glsl\n            ${CLASSIC_GLSL_DIR}/glc_world_drawflat.vertex.glsl\n            ${CLASSIC_GLSL_DIR}/glc_world_secondpass.fragment.glsl\n            ${CLASSIC_GLSL_DIR}/glc_world_secondpass.vertex.glsl\n            ${CLASSIC_GLSL_DIR}/glc_world_textured.fragment.glsl\n            ${CLASSIC_GLSL_DIR}/glc_world_textured.vertex.glsl\n    )\n\n    set(classic_opengl_headers\n            ${SOURCE_DIR}/glc_local.h\n            ${SOURCE_DIR}/glc_matrix.h\n            ${SOURCE_DIR}/glc_state.h\n            ${SOURCE_DIR}/glc_vao.h\n    )\n    set(classic_opengl\n            ${SOURCE_DIR}/glc_aliasmodel.c\n            ${SOURCE_DIR}/glc_aliasmodel_mesh.c\n            ${SOURCE_DIR}/glc_bloom.c\n            ${SOURCE_DIR}/glc_brushmodel.c\n            ${SOURCE_DIR}/glc_draw.c\n            ${SOURCE_DIR}/glc_framebuffer.c\n            ${SOURCE_DIR}/glc_lightmaps.c\n            ${SOURCE_DIR}/glc_main.c\n            ${SOURCE_DIR}/glc_matrix.c\n            ${SOURCE_DIR}/glc_md3.c\n            ${SOURCE_DIR}/glc_misc.c\n            ${SOURCE_DIR}/glc_particles.c\n            ${SOURCE_DIR}/glc_performance.c\n            ${SOURCE_DIR}/glc_sdl.c\n            ${SOURCE_DIR}/glc_sky.c\n            ${SOURCE_DIR}/glc_sprite3d.c\n            ${SOURCE_DIR}/glc_state.c\n            ${SOURCE_DIR}/glc_surf.c\n            ${SOURCE_DIR}/glc_turb_surface.c\n            ${SOURCE_DIR}/glc_vao.c\n            ${SOURCE_DIR}/glc_warp.c\n            ${classic_opengl_headers}\n    )\n    source_group(TREE ${SOURCE_DIR} PREFIX \"Source Files/classic_opengl\" FILES ${classic_opengl})\n    source_group(TREE ${SOURCE_DIR} PREFIX \"Header Files/classic_opengl\" FILES ${classic_opengl_headers})\nendif()\n\nset(common_opengl_headers\n        ${SOURCE_DIR}/gl_framebuffer.h\n        ${SOURCE_DIR}/gl_local.h\n        ${SOURCE_DIR}/gl_sprite3d.h\n        ${SOURCE_DIR}/gl_texture.h\n        ${SOURCE_DIR}/gl_texture_internal.h\n)\nset(common_opengl\n        ${SOURCE_DIR}/gl_aliasmodel.c\n        ${SOURCE_DIR}/gl_aliasmodel_md3.c\n        ${SOURCE_DIR}/gl_buffers.c\n        ${SOURCE_DIR}/gl_debug.c\n        ${SOURCE_DIR}/gl_drawcall_wrappers.c\n        ${SOURCE_DIR}/gl_framebuffer.c\n        ${SOURCE_DIR}/gl_misc.c\n        ${SOURCE_DIR}/gl_program.c\n        ${SOURCE_DIR}/gl_sdl.c\n        ${SOURCE_DIR}/gl_sprite3d.c\n        ${SOURCE_DIR}/gl_state.c\n        ${SOURCE_DIR}/gl_texture.c\n        ${SOURCE_DIR}/gl_texture_functions.c\n        ${SOURCE_DIR}/vid_common_gl.c\n        ${SOURCE_DIR}/r_vao.h\n        ${common_opengl_headers}\n)\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Source Files/common_opengl\" FILES ${common_opengl})\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Header Files/common_opengl\" FILES ${common_opengl_headers})\n\nset(common_renderer_headers\n        ${SOURCE_DIR}/anorm_dots.h\n        ${SOURCE_DIR}/anorms.h\n        ${SOURCE_DIR}/draw.h\n        ${SOURCE_DIR}/gl_model.h\n        ${SOURCE_DIR}/modelgen.h\n        ${SOURCE_DIR}/particles_classic.h\n        ${SOURCE_DIR}/qmb_particles.h\n        ${SOURCE_DIR}/quakedef.h\n        ${SOURCE_DIR}/r_aliasmodel.h\n        ${SOURCE_DIR}/r_aliasmodel_md3.h\n        ${SOURCE_DIR}/r_brushmodel.h\n        ${SOURCE_DIR}/r_brushmodel_sky.h\n        ${SOURCE_DIR}/r_brushmodel_warpsurfaces_sin.h\n        ${SOURCE_DIR}/r_buffers.h\n        ${SOURCE_DIR}/r_chaticons.h\n        ${SOURCE_DIR}/r_draw.h\n        ${SOURCE_DIR}/r_framestats.h\n        ${SOURCE_DIR}/r_lighting.h\n        ${SOURCE_DIR}/r_lightmaps.h\n        ${SOURCE_DIR}/r_lightmaps_internal.h\n        ${SOURCE_DIR}/r_local.h\n        ${SOURCE_DIR}/r_matrix.h\n        ${SOURCE_DIR}/r_particles_qmb.h\n        ${SOURCE_DIR}/r_performance.h\n        ${SOURCE_DIR}/r_program.h\n        ${SOURCE_DIR}/r_renderer.h\n        ${SOURCE_DIR}/r_renderer_structure.h\n        ${SOURCE_DIR}/r_shared.h\n        ${SOURCE_DIR}/r_sprite3d.h\n        ${SOURCE_DIR}/r_sprite3d_internal.h\n        ${SOURCE_DIR}/r_state.h\n        ${SOURCE_DIR}/r_texture.h\n        ${SOURCE_DIR}/r_texture_internal.h\n        ${SOURCE_DIR}/r_trace.h\n        ${SOURCE_DIR}/render.h\n        ${SOURCE_DIR}/spritegn.h\n        ${SOURCE_DIR}/vx_camera.h\n        ${SOURCE_DIR}/vx_stuff.h\n        ${SOURCE_DIR}/vx_tracker.h\n        ${SOURCE_DIR}/vx_vertexlights.h\n)\nset(common_renderer\n        ${SOURCE_DIR}/r_aliasmodel.c\n        ${SOURCE_DIR}/r_aliasmodel_md3.c\n        ${SOURCE_DIR}/r_aliasmodel_mesh.c\n        ${SOURCE_DIR}/r_aliasmodel_skins.c\n        ${SOURCE_DIR}/r_atlas.c\n        ${SOURCE_DIR}/r_bloom.c\n        ${SOURCE_DIR}/r_brushmodel.c\n        ${SOURCE_DIR}/r_brushmodel_bspx.c\n        ${SOURCE_DIR}/r_brushmodel_load.c\n        ${SOURCE_DIR}/r_brushmodel_sky.c\n        ${SOURCE_DIR}/r_brushmodel_surfaces.c\n        ${SOURCE_DIR}/r_brushmodel_textures.c\n        ${SOURCE_DIR}/r_brushmodel_warpsurfaces.c\n        ${SOURCE_DIR}/r_buffers.c\n        ${SOURCE_DIR}/r_chaticons.c\n        ${SOURCE_DIR}/r_draw.c\n        ${SOURCE_DIR}/r_draw_charset.c\n        ${SOURCE_DIR}/r_draw_circle.c\n        ${SOURCE_DIR}/r_draw_image.c\n        ${SOURCE_DIR}/r_draw_line.c\n        ${SOURCE_DIR}/r_draw_polygon.c\n        ${SOURCE_DIR}/r_hud.c\n        ${SOURCE_DIR}/r_lightmaps.c\n        ${SOURCE_DIR}/r_main.c\n        ${SOURCE_DIR}/r_matrix.c\n        ${SOURCE_DIR}/r_misc.c\n        ${SOURCE_DIR}/r_model.c\n        ${SOURCE_DIR}/r_netgraph.c\n        ${SOURCE_DIR}/r_palette.c\n        ${SOURCE_DIR}/r_part.c\n        ${SOURCE_DIR}/r_part_trails.c\n        ${SOURCE_DIR}/r_particles_qmb.c\n        ${SOURCE_DIR}/r_particles_qmb_spawn.c\n        ${SOURCE_DIR}/r_particles_qmb_trails.c\n        ${SOURCE_DIR}/r_performance.c\n        ${SOURCE_DIR}/r_refrag.c\n        ${SOURCE_DIR}/r_rlight.c\n        ${SOURCE_DIR}/r_rmain.c\n        ${SOURCE_DIR}/r_rmisc.c\n        ${SOURCE_DIR}/r_sprite3d.c\n        ${SOURCE_DIR}/r_sprites.c\n        ${SOURCE_DIR}/r_states.c\n        ${SOURCE_DIR}/r_texture.c\n        ${SOURCE_DIR}/r_texture_cvars.c\n        ${SOURCE_DIR}/r_texture_load.c\n        ${SOURCE_DIR}/r_texture_util.c\n        ${SOURCE_DIR}/vx_camera.c\n        ${SOURCE_DIR}/vx_coronas.c\n        ${SOURCE_DIR}/vx_stuff.c\n        ${SOURCE_DIR}/vx_vertexlights.c\n        ${common_renderer_headers}\n)\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Source Files/common_renderer\" FILES ${common_renderer})\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Header Files/common_renderer\" FILES ${common_renderer_headers})\n\nset(common_hud_headers\n        ${SOURCE_DIR}/common_draw.h\n        ${SOURCE_DIR}/hud.h\n        ${SOURCE_DIR}/hud_common.h\n        ${SOURCE_DIR}/hud_editor.h\n)\nset(common_hud\n        ${SOURCE_DIR}/hud.c\n        ${SOURCE_DIR}/hud_262.c\n        ${SOURCE_DIR}/hud_ammo.c\n        ${SOURCE_DIR}/hud_armor.c\n        ${SOURCE_DIR}/hud_autoid.c\n        ${SOURCE_DIR}/hud_centerprint.c\n        ${SOURCE_DIR}/hud_clock.c\n        ${SOURCE_DIR}/hud_common.c\n        ${SOURCE_DIR}/hud_editor.c\n        ${SOURCE_DIR}/hud_face.c\n        ${SOURCE_DIR}/hud_frags.c\n        ${SOURCE_DIR}/hud_gamesummary.c\n        ${SOURCE_DIR}/hud_groups.c\n        ${SOURCE_DIR}/hud_guns.c\n        ${SOURCE_DIR}/hud_health.c\n        ${SOURCE_DIR}/hud_items.c\n        ${SOURCE_DIR}/hud_net.c\n        ${SOURCE_DIR}/hud_performance.c\n        ${SOURCE_DIR}/hud_qtv.c\n        ${SOURCE_DIR}/hud_radar.c\n        ${SOURCE_DIR}/hud_scores.c\n        ${SOURCE_DIR}/hud_speed.c\n        ${SOURCE_DIR}/hud_teaminfo.c\n        ${SOURCE_DIR}/hud_tracking.c\n        ${SOURCE_DIR}/hud_weapon_stats.c\n        ${common_hud_headers}\n)\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Source Files/common_hud\" FILES ${common_hud})\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Header Files/common_hud\" FILES ${common_hud_headers})\n\nset(DOCUMENTATION_DIR ${PROJECT_SOURCE_DIR})\nadd_resources(documentation\n        ${DOCUMENTATION_DIR}/help_cmdline_params.json\n        ${DOCUMENTATION_DIR}/help_commands.json\n        ${DOCUMENTATION_DIR}/help_macros.json\n        ${DOCUMENTATION_DIR}/help_variables.json\n)\n\nset(client_headers\n        ${SOURCE_DIR}/Ctrl.h\n        ${SOURCE_DIR}/Ctrl_EditBox.h\n        ${SOURCE_DIR}/Ctrl_PageViewer.h\n        ${SOURCE_DIR}/Ctrl_Tab.h\n        ${SOURCE_DIR}/EX_FileList.h\n        ${SOURCE_DIR}/EX_browser.h\n        ${SOURCE_DIR}/EX_qtvlist.h\n        ${SOURCE_DIR}/cdaudio.h\n        ${SOURCE_DIR}/cl_slist.h\n        ${SOURCE_DIR}/cl_view.h\n        ${SOURCE_DIR}/client.h\n        ${SOURCE_DIR}/config_manager.h\n        ${SOURCE_DIR}/console.h\n        ${SOURCE_DIR}/demo_controls.h\n        ${SOURCE_DIR}/document_rendering.h\n        ${SOURCE_DIR}/ez_button.h\n        ${SOURCE_DIR}/ez_controls.h\n        ${SOURCE_DIR}/ez_label.h\n        ${SOURCE_DIR}/ez_listview.h\n        ${SOURCE_DIR}/ez_listviewitem.h\n        ${SOURCE_DIR}/ez_scrollbar.h\n        ${SOURCE_DIR}/ez_scrollpane.h\n        ${SOURCE_DIR}/ez_slider.h\n        ${SOURCE_DIR}/ez_window.h\n        ${SOURCE_DIR}/fchecks.h\n        ${SOURCE_DIR}/fmod.h\n        ${SOURCE_DIR}/fonts.h\n        ${SOURCE_DIR}/help.h\n        ${SOURCE_DIR}/ignore.h\n        ${SOURCE_DIR}/image.h\n        ${SOURCE_DIR}/input.h\n        ${SOURCE_DIR}/keys.h\n        ${SOURCE_DIR}/logging.h\n        ${SOURCE_DIR}/menu.h\n        ${SOURCE_DIR}/menu_demo.h\n        ${SOURCE_DIR}/menu_ingame.h\n        ${SOURCE_DIR}/menu_multiplayer.h\n        ${SOURCE_DIR}/menu_options.h\n        ${SOURCE_DIR}/menu_proxy.h\n        ${SOURCE_DIR}/movie.h\n        ${SOURCE_DIR}/movie_avi.h\n        ${SOURCE_DIR}/mvd_utils.h\n        ${SOURCE_DIR}/mvd_utils_common.h\n        ${SOURCE_DIR}/qsound.h\n        ${SOURCE_DIR}/qtv.h\n        ${SOURCE_DIR}/rulesets.h\n        ${SOURCE_DIR}/sbar.h\n        ${SOURCE_DIR}/screen.h\n        ${SOURCE_DIR}/settings.h\n        ${SOURCE_DIR}/settings_page.h\n        ${SOURCE_DIR}/demo_spawnwarn.h\n        ${SOURCE_DIR}/stats_grid.h\n        ${SOURCE_DIR}/sys.h\n        ${SOURCE_DIR}/teamplay.h\n        ${SOURCE_DIR}/textencoding.h\n        ${SOURCE_DIR}/tp_msgs.h\n        ${SOURCE_DIR}/tp_triggers.h\n        ${SOURCE_DIR}/tr_types.h\n        ${SOURCE_DIR}/utils.h\n        ${SOURCE_DIR}/vid.h\n        ${SOURCE_DIR}/wad.h\n        ${SOURCE_DIR}/xsd.h\n        ${SOURCE_DIR}/xsd_document.h\n)\nif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n    list(APPEND client_headers ${SOURCE_DIR}/movie_avi.h)\nendif()\nset(client\n        ${SOURCE_DIR}/Ctrl.c\n        ${SOURCE_DIR}/Ctrl_EditBox.c\n        ${SOURCE_DIR}/Ctrl_PageViewer.c\n        ${SOURCE_DIR}/Ctrl_ScrollBar.c\n        ${SOURCE_DIR}/Ctrl_Tab.c\n        ${SOURCE_DIR}/EX_FileList.c\n        ${SOURCE_DIR}/EX_browser.c\n        ${SOURCE_DIR}/EX_browser_net.c\n        ${SOURCE_DIR}/EX_browser_pathfind.c\n        ${SOURCE_DIR}/EX_browser_ping.c\n        ${SOURCE_DIR}/EX_browser_qtvlist.c\n        ${SOURCE_DIR}/EX_browser_sources.c\n        ${SOURCE_DIR}/EX_qtvlist.c\n        ${SOURCE_DIR}/cd_null.c\n        ${SOURCE_DIR}/cl_cam.c\n        ${SOURCE_DIR}/cl_cmd.c\n        ${SOURCE_DIR}/cl_demo.c\n        ${SOURCE_DIR}/cl_ents.c\n        ${SOURCE_DIR}/cl_input.c\n        ${SOURCE_DIR}/cl_main.c\n        ${SOURCE_DIR}/cl_multiview.c\n        ${SOURCE_DIR}/cl_nqdemo.c\n        ${SOURCE_DIR}/cl_parse.c\n        ${SOURCE_DIR}/cl_pred.c\n        ${SOURCE_DIR}/cl_screen.c\n        ${SOURCE_DIR}/cl_screenshot.c\n        ${SOURCE_DIR}/cl_skygroups.c\n        ${SOURCE_DIR}/cl_slist.c\n        ${SOURCE_DIR}/cl_tent.c\n        ${SOURCE_DIR}/cl_view.c\n        ${SOURCE_DIR}/collision.c\n        ${SOURCE_DIR}/common_draw.c\n        ${SOURCE_DIR}/config_manager.c\n        ${SOURCE_DIR}/console.c\n        ${SOURCE_DIR}/demo_controls.c\n        ${SOURCE_DIR}/document_rendering.c\n        ${SOURCE_DIR}/ez_button.c\n        ${SOURCE_DIR}/ez_controls.c\n        ${SOURCE_DIR}/ez_label.c\n        ${SOURCE_DIR}/ez_scrollbar.c\n        ${SOURCE_DIR}/ez_scrollpane.c\n        ${SOURCE_DIR}/ez_slider.c\n        ${SOURCE_DIR}/ez_window.c\n        ${SOURCE_DIR}/fchecks.c\n        ${SOURCE_DIR}/fmod.c\n        ${SOURCE_DIR}/fonts.c\n        ${SOURCE_DIR}/fragstats.c\n        ${SOURCE_DIR}/help.c\n        ${SOURCE_DIR}/help_files.c\n        ${SOURCE_DIR}/host.c\n        ${SOURCE_DIR}/ignore.c\n        ${SOURCE_DIR}/image.c\n        ${SOURCE_DIR}/in_sdl2.c\n        ${SOURCE_DIR}/irc.c\n        ${SOURCE_DIR}/irc_filter.c\n        ${SOURCE_DIR}/keys.c\n        ${SOURCE_DIR}/logging.c\n        ${SOURCE_DIR}/match_tools.c\n        ${SOURCE_DIR}/match_tools_challenge.c\n        ${SOURCE_DIR}/menu.c\n        ${SOURCE_DIR}/menu_demo.c\n        ${SOURCE_DIR}/menu_ingame.c\n        ${SOURCE_DIR}/menu_multiplayer.c\n        ${SOURCE_DIR}/menu_options.c\n        ${SOURCE_DIR}/menu_proxy.c\n        ${SOURCE_DIR}/movie.c\n        ${SOURCE_DIR}/mvd_autotrack.c\n        ${SOURCE_DIR}/mvd_utils.c\n        ${SOURCE_DIR}/mvd_xmlstats.c\n        ${SOURCE_DIR}/demo_spawnwarn.c\n        ${SOURCE_DIR}/qtv.c\n        ${SOURCE_DIR}/rulesets.c\n        ${SOURCE_DIR}/sbar.c\n        ${SOURCE_DIR}/settings_page.c\n        ${SOURCE_DIR}/skin.c\n        ${SOURCE_DIR}/snd_main.c\n        ${SOURCE_DIR}/snd_mem.c\n        ${SOURCE_DIR}/snd_mix.c\n        ${SOURCE_DIR}/snd_qizmo.c\n        ${SOURCE_DIR}/snd_voip.c\n        ${SOURCE_DIR}/stats_grid.c\n        ${SOURCE_DIR}/sys_sdl2.c\n        ${SOURCE_DIR}/teamplay.c\n        ${SOURCE_DIR}/teamplay_locfiles.c\n        ${SOURCE_DIR}/textencoding.c\n        ${SOURCE_DIR}/tp_msgs.c\n        ${SOURCE_DIR}/tp_triggers.c\n        ${SOURCE_DIR}/utils.c\n        ${SOURCE_DIR}/vid_sdl2.c\n        ${SOURCE_DIR}/vid_vsync.c\n        ${SOURCE_DIR}/vx_tracker.c\n        ${SOURCE_DIR}/wad.c\n        ${SOURCE_DIR}/xsd.c\n        ${SOURCE_DIR}/xsd_document.c\n        ${client_headers}\n)\nif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n    list(APPEND client ${SOURCE_DIR}/movie_avi.c)\nendif()\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Source Files/client\" FILES ${client})\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Header Files/client\" FILES ${client_headers})\n\nset(sys_headers ${SOURCE_DIR}/localtime.h)\nif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n    set(sys ${SOURCE_DIR}/localtime_win.c)\nelse()\n    set(sys\n            ${SOURCE_DIR}/localtime_posix.c\n            ${SOURCE_DIR}/linux_signals.c\n    )\nendif()\nif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n    list(APPEND sys_headers\n            ${SOURCE_DIR}/in_osx.h\n    )\n    list(APPEND sys\n            ${SOURCE_DIR}/in_osx.m\n            ${SOURCE_DIR}/sys_osx.m\n    )\nendif()\nlist(APPEND sys ${sys_headers})\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Source Files/sys\" FILES ${sys})\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Header Files/sys\" FILES ${sys_headers})\n\nset(central_headers\n        ${SOURCE_DIR}/central.h\n)\nset(central\n        ${SOURCE_DIR}/central.c\n        ${central_headers}\n)\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Source Files/central\" FILES ${central})\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Header Files/central\" FILES ${central_headers})\n\nif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n    set(main ${SOURCE_DIR}/sys_win.c)\nelse()\n    set(main ${SOURCE_DIR}/sys_posix.c)\nendif()\nsource_group(TREE ${SOURCE_DIR} PREFIX \"Source Files/main\" FILES ${main})\n\nget_target_property(version_major git_version VERSION_MAJOR)\nget_target_property(version_minor git_version VERSION_MINOR)\nget_target_property(version_patch git_version VERSION_PATCH)\nget_target_property(version_build git_version REVISION)\nget_target_property(version_commit git_version COMMIT)\n\n# macOS icon\nset(macos_icon \"${PROJECT_SOURCE_DIR}/dist/macOS/ezquake.icns\")\nset_source_files_properties(${macos_icon} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)\n\n# Windows icon, and exe metadata\nset(windows_icon \"${CMAKE_CURRENT_BINARY_DIR}/ezQuake.rc\")\nset(EZQUAKE_RESOURCE_AUTHOR \"QW-Group\")\nset(EZQUAKE_RESOURCE_NAME \"ezQuake\")\nset(EZQUAKE_RESOURCE_DESCRIPTION \"ezQuake - a QuakeWorld client\")\nset(EZQUAKE_RESOURCE_ICON \"${PROJECT_SOURCE_DIR}/dist/windows/ezquake.ico\")\nset(EZQUAKE_RESOURCE_VERSION \"${version_major},${version_minor},${version_patch},${version_build}\")\nset(EZQUAKE_RESOURCE_COMMIT \"${version_commit}\")\nconfigure_file(\"${PROJECT_SOURCE_DIR}/dist/windows/ezQuake.rc.in\" ${windows_icon} @ONLY)\n\nif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n    # Mark the executable as a non-console application\n    set(TARGET_TYPE WIN32)\nelseif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n    # Mark the executable for bundling as .app\n    set(TARGET_TYPE MACOSX_BUNDLE)\nendif()\n\nadd_executable(ezquake ${TARGET_TYPE}\n        ${main}\n        ${sys}\n        ${central}\n        ${common}\n        ${common_hud}\n        ${server}\n        ${client}\n\n        ${common_opengl}\n        ${common_renderer}\n        $<$<BOOL:${RENDERER_MODERN_OPENGL}>:${modern_opengl}>\n        $<$<BOOL:${RENDERER_CLASSIC_OPENGL}>:${classic_opengl}>\n\n        ${qwprot_headers}\n\n        $<IF:$<PLATFORM_ID:Darwin>,${macos_icon},>\n        $<IF:$<PLATFORM_ID:Windows>,${windows_icon},>\n)\n\ntarget_include_directories(ezquake PRIVATE\n        ${SOURCE_DIR}/qwprot/src\n        ${EZQUAKE_MINIZIP_INCLUDE_DIR}\n)\n\ntarget_compile_definitions(ezquake PRIVATE\n        BUILDSTRING=\"${CMAKE_SYSTEM_NAME}\"\n        CPUSTRING=\"${CMAKE_SYSTEM_PROCESSOR}\"\n\n        JSS_CAM\n        USE_PR2\n        WITH_NQPROGS\n\n        $<$<BOOL:${DEBUG_MEMORY_ALLOCATIONS}>:DEBUG_MEMORY_ALLOCATIONS>\n        $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:WITH_RENDERING_TRACE>\n\n        $<$<PLATFORM_ID:Darwin>:GL_SILENCE_DEPRECATION>\n\n        $<$<BOOL:${RENDERER_MODERN_OPENGL}>:RENDERER_OPTION_MODERN_OPENGL>\n        $<$<BOOL:${RENDERER_CLASSIC_OPENGL}>:RENDERER_OPTION_CLASSIC_OPENGL>\n\n        WITH_PNG\n        WITH_JPEG\n        WITH_ZIP\n        WITH_ZLIB\n\n        $<$<BOOL:${HAVE_FREETYPE}>:EZ_FREETYPE_SUPPORT>\n        $<$<BOOL:${HAVE_FREETYPE}>:EZ_FREETYPE_SUPPORT_STATIC>\n\n        $<$<AND:$<BOOL:${HAVE_SPEEX}>,$<BOOL:${HAVE_SPEEXDSP}>>:WITH_SPEEX>\n\n        PCRE2_CODE_UNIT_WIDTH=8\n)\n\ntarget_link_libraries(ezquake PRIVATE\n        $<$<BOOL:${RENDERER_MODERN_OPENGL}>:shaders_modern>\n        $<$<BOOL:${RENDERER_CLASSIC_OPENGL}>:shaders_classic>\n        shaders_shared\n        documentation\n        git_version\n\n        ${CMAKE_DL_LIBS}\n\n        Dep::zlib\n        Dep::cURL\n        Dep::Expat\n        Dep::JPEG\n        Dep::PCRE2\n        Dep::SDL2\n        Dep::Jansson\n        Dep::SndFile\n        Dep::PNG\n        Dep::MiniZip\n\n        $<$<BOOL:${HAVE_FREETYPE}>:Dep::FreeType>\n\n        $<$<AND:$<BOOL:${HAVE_SPEEX}>,$<BOOL:${HAVE_SPEEXDSP}>>:Dep::Speex>\n        $<$<AND:$<BOOL:${HAVE_SPEEX}>,$<BOOL:${HAVE_SPEEXDSP}>>:Dep::SpeexDSP>\n\n        OpenGL::GL\n        Threads::Threads\n\n        ${FRAMEWORK_APPKIT}\n        ${FRAMEWORK_FOUNDATION}\n        ${FRAMEWORK_CORESERVICES}\n        ${FRAMEWORK_GAMECONTROLLER}\n)\n\nif(MATH)\n    target_link_libraries(ezquake PRIVATE ${MATH})\nendif()\n\nif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n    string(TOLOWER \"ezQuake\" EXECUTABLE_NAME)\nelseif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n    string(TOLOWER \"ezquake\" EXECUTABLE_NAME)\nelse()\n    string(TOLOWER \"ezquake-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}\" EXECUTABLE_NAME)\nendif()\n\nset_target_properties(ezquake PROPERTIES OUTPUT_NAME ${EXECUTABLE_NAME})\n\nif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n    get_target_property(version git_version GIT_DESCRIBE)\n\n    set_target_properties(ezquake PROPERTIES\n            XCODE_ATTRIBUTE_PRODUCT_NAME                            \"ezQuake\"\n            XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER               \"com.ezquake.ezQuake\"\n            XCODE_ATTRIBUTE_MARKETING_VERSION                       \"${version}\"\n            XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION                 \"${version}\"\n            XCODE_ATTRIBUTE_LLVM_LTO                                $<IF:$<OR:$<CONFIG:Debug>,$<NOT:$<BOOL:${ENABLE_LTO}>>>,NO,Monolithic>\n            XCODE_ATTRIBUTE_GCC_OPTIMIZATION_LEVEL                  $<IF:$<CONFIG:Debug>,0,3>\n            XCODE_ATTRIBUTE_DEAD_CODE_STRIPPING                     YES\n            XCODE_ATTRIBUTE_GENERATE_INFOPLIST_FILE                 YES\n            XCODE_ATTRIBUTE_INFOPLIST_KEY_CFBundleDisplayName       \"ezQuake\"\n            XCODE_ATTRIBUTE_INFOPLIST_KEY_LSApplicationCategoryType \"public.app-category.action-games\"\n            XCODE_ATTRIBUTE_INFOPLIST_KEY_NSHumanReadableCopyright  \"GNU General Public License, version 2\"\n            XCODE_ATTRIBUTE_INFOPLIST_FILE                          \"${PROJECT_SOURCE_DIR}/dist/macOS/MacOSXBundleInfo.plist.in\"\n    )\n\n    if(ENABLE_SANDBOX)\n        set_target_properties(ezquake PROPERTIES\n                XCODE_ATTRIBUTE_ENABLE_APP_SANDBOX                                         YES\n                XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME                                    YES\n                XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS[variant=Debug]          YES\n                XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS[variant=RelWithDebInfo] YES\n                XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS[variant=Release]        NO\n                XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS \"dist/macOS/ezquake.entitlements.plist\"\n        )\n    endif()\nendif()\n"
  },
  {
    "path": "CMakePresets.json",
    "content": "{\n    \"version\": 3,\n    \"cmakeMinimumRequired\": {\n        \"major\": 3,\n        \"minor\": 22,\n        \"patch\": 0\n    },\n    \"configurePresets\": [\n        {\n            \"name\": \"template-common\",\n            \"hidden\": true,\n            \"binaryDir\": \"${sourceDir}/build-${presetName}\",\n            \"cacheVariables\": {\n                \"CMAKE_VERBOSE_MAKEFILE\": \"OFF\",\n                \"CMAKE_COLOR_DIAGNOSTICS\": \"ON\",\n                \"CMAKE_CONFIGURATION_TYPES\": \"Debug;RelWithDebInfo;Release\"\n            }\n        },\n        {\n            \"name\": \"template-vcpkg\",\n            \"hidden\": true,\n            \"inherits\": \"template-common\",\n            \"cacheVariables\": {\n                \"CMAKE_TOOLCHAIN_FILE\": {\n                    \"type\": \"FILEPATH\",\n                    \"value\": \"vcpkg/scripts/buildsystems/vcpkg.cmake\"\n                },\n                \"VCPKG_OVERLAY_TRIPLETS\": \"${sourceDir}/cmake/triplets\",\n                \"VCPKG_LIBRARY_LINKAGE\": \"static\",\n                \"VCPKG_CRT_LINKAGE\": \"dynamic\",\n                \"VCPKG_INSTALL_OPTIONS\": \"--clean-after-build\",\n                \"VCPKG_ENABLE_METRICS\": \"0\",\n                \"VCPKG_APPLOCAL_DEPS\": \"OFF\",\n                \"USE_SYSTEM_LIBS\": \"OFF\"\n            }\n        },\n        {\n            \"name\": \"template-gcc-mingw-cross\",\n            \"hidden\": true,\n            \"cacheVariables\": {\n                \"CMAKE_SYSTEM_NAME\": \"Windows\"\n            }\n        },\n        {\n            \"name\": \"template-gcc-mingw-i686\",\n            \"hidden\": true,\n            \"inherits\": \"template-vcpkg\",\n            \"cacheVariables\": {\n                \"CMAKE_C_COMPILER\": \"i686-w64-mingw32-gcc\",\n                \"CMAKE_CXX_COMPILER\": \"i686-w64-mingw32-g++\",\n                \"CMAKE_RC_COMPILER\": \"i686-w64-mingw32-windres\",\n                \"CMAKE_C_FLAGS\": \"-msse2\"\n            }\n        },\n        {\n            \"name\": \"template-gcc-mingw-x64\",\n            \"hidden\": true,\n            \"inherits\": \"template-vcpkg\",\n            \"cacheVariables\": {\n                \"CMAKE_C_COMPILER\": \"x86_64-w64-mingw32-gcc\",\n                \"CMAKE_CXX_COMPILER\": \"x86_64-w64-mingw32-g++\",\n                \"CMAKE_RC_COMPILER\": \"x86_64-w64-mingw32-windres\",\n                \"CMAKE_C_FLAGS\": \"-march=nehalem\"\n            }\n        },\n        {\n            \"name\": \"msbuild-x64\",\n            \"description\": \"Configure as Visual Studio project\",\n            \"generator\": \"Visual Studio 17 2022\",\n            \"inherits\": \"template-vcpkg\",\n            \"cacheVariables\": {\n                \"VCPKG_TARGET_TRIPLET\": \"x64-windows-static\"\n            },\n            \"condition\": {\n                \"type\": \"equals\",\n                \"lhs\": \"${hostSystemName}\",\n                \"rhs\": \"Windows\"\n            }\n        },\n        {\n            \"name\": \"msbuild-arm64\",\n            \"description\": \"Configure as Visual Studio project\",\n            \"generator\": \"Visual Studio 17 2022\",\n            \"inherits\": \"template-vcpkg\",\n            \"cacheVariables\": {\n                \"VCPKG_TARGET_TRIPLET\": \"arm64-windows-static\"\n            },\n            \"condition\": {\n                \"type\": \"equals\",\n                \"lhs\": \"${hostSystemName}\",\n                \"rhs\": \"Windows\"\n            }\n        },\n        {\n            \"name\": \"msvc-x64\",\n            \"description\": \"Configure using Ninja to build with msvc\",\n            \"generator\": \"Ninja Multi-Config\",\n            \"inherits\": \"template-vcpkg\",\n            \"cacheVariables\": {\n                \"VCPKG_TARGET_TRIPLET\": \"x64-windows-static\"\n            },\n            \"condition\": {\n                \"type\": \"equals\",\n                \"lhs\": \"${hostSystemName}\",\n                \"rhs\": \"Windows\"\n            }\n        },\n        {\n            \"name\": \"mingw64-x64-shared\",\n            \"hidden\": true,\n            \"description\": \"Configure using Ninja to build with mingw64 for x64\",\n            \"generator\": \"Ninja Multi-Config\",\n            \"inherits\": \"template-gcc-mingw-x64\",\n            \"architecture\": {\n                \"value\": \"x64\",\n                \"strategy\": \"external\"\n            },\n            \"cacheVariables\": {\n                \"VCPKG_TARGET_TRIPLET\": \"x64-mingw-static\",\n                \"CMAKE_SYSTEM_PROCESSOR\": \"x86_64\"\n            }\n        },\n        {\n            \"name\": \"mingw64-x64\",\n            \"inherits\": [\"mingw64-x64-shared\"],\n            \"condition\": {\n                \"type\": \"equals\",\n                \"lhs\": \"${hostSystemName}\",\n                \"rhs\": \"Windows\"\n            }\n        },\n        {\n            \"name\": \"mingw64-x64-cross\",\n            \"inherits\": [\"mingw64-x64-shared\", \"template-gcc-mingw-cross\"],\n            \"condition\": {\n                \"type\": \"notEquals\",\n                \"lhs\": \"${hostSystemName}\",\n                \"rhs\": \"Windows\"\n            }\n        },\n        {\n            \"name\": \"mingw64-i686-shared\",\n            \"hidden\": true,\n            \"description\": \"Configure with Ninja to build with mingw64 for i686\",\n            \"generator\": \"Ninja Multi-Config\",\n            \"inherits\": [\"template-gcc-mingw-i686\"],\n            \"architecture\": {\n                \"value\": \"x86\",\n                \"strategy\": \"external\"\n            },\n            \"cacheVariables\": {\n                \"VCPKG_TARGET_TRIPLET\": \"x86-mingw-static\",\n                \"CMAKE_SYSTEM_PROCESSOR\": \"x86\"\n            }\n        },\n        {\n            \"name\": \"mingw64-i686\",\n            \"inherits\": [\"mingw64-i686-shared\"],\n            \"condition\": {\n                \"type\": \"equals\",\n                \"lhs\": \"${hostSystemName}\",\n                \"rhs\": \"Windows\"\n            }\n        },\n        {\n            \"name\": \"mingw64-i686-cross\",\n            \"inherits\": [\"mingw64-i686-shared\", \"template-gcc-mingw-cross\"],\n            \"cacheVariables\": {\n                \"CMAKE_SYSTEM_PROCESSOR\": \"i686\"\n            },\n            \"condition\": {\n                \"type\": \"notEquals\",\n                \"lhs\": \"${hostSystemName}\",\n                \"rhs\": \"Windows\"\n            }\n        },\n        {\n            \"name\": \"macos\",\n            \"hidden\": true,\n            \"description\": \"Configure XCode project file\",\n            \"generator\": \"Xcode\",\n            \"inherits\": \"template-vcpkg\",\n            \"condition\": {\n                \"type\": \"equals\",\n                \"lhs\": \"${hostSystemName}\",\n                \"rhs\": \"Darwin\"\n            }\n        },\n        {\n            \"name\": \"macos-arm64\",\n            \"displayName\": \"XCode (arm64)\",\n            \"inherits\": \"macos\",\n            \"cacheVariables\": {\n                \"VCPKG_TARGET_TRIPLET\": \"arm64-osx\",\n                \"CMAKE_OSX_ARCHITECTURES\": \"arm64\",\n                \"CMAKE_OSX_DEPLOYMENT_TARGET\": \"11.0\"\n            }\n        },\n        {\n            \"name\": \"macos-x64\",\n            \"displayName\": \"XCode (x64)\",\n            \"inherits\": \"macos\",\n            \"cacheVariables\": {\n                \"VCPKG_TARGET_TRIPLET\": \"x64-osx\",\n                \"CMAKE_OSX_ARCHITECTURES\": \"x86_64\",\n                \"CMAKE_OSX_DEPLOYMENT_TARGET\": \"11.0\"\n            }\n        },\n        {\n            \"name\": \"dynamic\",\n            \"description\": \"Configure XCode project file\",\n            \"generator\": \"Ninja Multi-Config\",\n            \"inherits\": \"template-common\",\n            \"cacheVariables\": {\n                \"USE_SYSTEM_LIBS\": \"ON\"\n            }\n        },\n        {\n            \"name\": \"static\",\n            \"description\": \"Configure XCode project file\",\n            \"generator\": \"Ninja Multi-Config\",\n            \"inherits\": \"template-vcpkg\"\n        }\n    ],\n    \"buildPresets\": [\n        {\n            \"name\": \"msbuild-x64-debug\",\n            \"configurePreset\": \"msbuild-x64\",\n            \"displayName\": \"Build msbuild-x64 debug\",\n            \"description\": \"Build Visual Studio Debug configuration\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"msbuild-x64-release\",\n            \"configurePreset\": \"msbuild-x64\",\n            \"displayName\": \"Build msbuild-x64 release\",\n            \"description\": \"Build Visual Studio Release configuration\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"msbuild-x64-relwithdebinfo\",\n            \"configurePreset\": \"msbuild-x64\",\n            \"displayName\": \"Build msbuild-x64 release with debug info\",\n            \"description\": \"Build Visual Studio Release configuration\",\n            \"configuration\": \"RelWithDebInfo\"\n        },\n        {\n            \"name\": \"msbuild-arm64-debug\",\n            \"configurePreset\": \"msbuild-arm64\",\n            \"displayName\": \"Build msbuild-arm64 debug\",\n            \"description\": \"Build Visual Studio Debug configuration\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"msbuild-arm64-release\",\n            \"configurePreset\": \"msbuild-arm64\",\n            \"displayName\": \"Build msbuild-arm64 release\",\n            \"description\": \"Build Visual Studio Release configuration\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"msbuild-arm64-relwithdebinfo\",\n            \"configurePreset\": \"msbuild-arm64\",\n            \"displayName\": \"Build msbuild-arm64 release with debug info\",\n            \"description\": \"Build Visual Studio Release configuration\",\n            \"configuration\": \"RelWithDebInfo\"\n        },\n        {\n            \"name\": \"msvc-x64-debug\",\n            \"configurePreset\": \"msvc-x64\",\n            \"displayName\": \"Build msvc-x64 debug\",\n            \"description\": \"Build MSVC debug configuration\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"msvc-x64-release\",\n            \"configurePreset\": \"msvc-x64\",\n            \"displayName\": \"Build ninja-msvc-x64 release\",\n            \"description\": \"Build MSVC release configuration\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"msvc-x64-relwithdebinfo\",\n            \"configurePreset\": \"msvc-x64\",\n            \"displayName\": \"Build MSVC release with debug symbols\",\n            \"description\": \"Build MSVC release with debug info configuration\",\n            \"configuration\": \"RelWithDebInfo\"\n        },\n        {\n            \"name\": \"mingw64-x64-debug\",\n            \"configurePreset\": \"mingw64-x64\",\n            \"displayName\": \"Build mingw64-x64 debug\",\n            \"description\": \"Build Windows Subsystem for Linux Debug configuration\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"mingw64-x64-cross-debug\",\n            \"configurePreset\": \"mingw64-x64-cross\",\n            \"displayName\": \"Build mingw64-x64 debug\",\n            \"description\": \"Cross-compile with mingw64-x64 Debug configuration\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"mingw64-x64-release\",\n            \"configurePreset\": \"mingw64-x64\",\n            \"displayName\": \"Build mingw64-x64 release\",\n            \"description\": \"Build Windows Subsystem for Linux Release configuration\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"mingw64-x64-cross-release\",\n            \"configurePreset\": \"mingw64-x64-cross\",\n            \"displayName\": \"Build mingw64-x64 release\",\n            \"description\": \"Cross-compile with mingw64-x64 Release configuration\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"mingw64-x64-relwithdebinfo\",\n            \"configurePreset\": \"mingw64-x64\",\n            \"displayName\": \"Build mingw64-x64 release with debug info\",\n            \"description\": \"Build Windows Subsystem for Linux Release with debug info configuration\",\n            \"configuration\": \"RelWithDebInfo\"\n        },\n        {\n            \"name\": \"mingw64-x64-cross-relwithdebinfo\",\n            \"configurePreset\": \"mingw64-x64-cross\",\n            \"displayName\": \"Build mingw64-x64 release with debug info\",\n            \"description\": \"Cross-compile with mingw64-x64 Release with debug info configuration\",\n            \"configuration\": \"RelWithDebInfo\"\n        },\n        {\n            \"name\": \"mingw64-i686-debug\",\n            \"configurePreset\": \"mingw64-i686\",\n            \"displayName\": \"Build mingw64-i686 debug\",\n            \"description\": \"Build Windows Subsystem for Linux Debug configuration\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"mingw64-i686-cross-debug\",\n            \"configurePreset\": \"mingw64-i686-cross\",\n            \"displayName\": \"Build mingw64-i686 debug\",\n            \"description\": \"Build Windows Subsystem for Linux Debug configuration\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"mingw64-i686-release\",\n            \"configurePreset\": \"mingw64-i686\",\n            \"displayName\": \"Build mingw64-i686 release\",\n            \"description\": \"Build Windows Subsystem for Linux Debug configuration\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"mingw64-i686-cross-release\",\n            \"configurePreset\": \"mingw64-i686-cross\",\n            \"displayName\": \"Build mingw64-i686 release\",\n            \"description\": \"Build Windows Subsystem for Linux Debug configuration\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"mingw64-i686-relwithdebinfo\",\n            \"configurePreset\": \"mingw64-i686\",\n            \"displayName\": \"Build mingw64-i686 release with debug info\",\n            \"description\": \"Build Windows Subsystem for Linux Debug configuration\",\n            \"configuration\": \"RelWithDebInfo\"\n        },\n        {\n            \"name\": \"mingw64-i686-cross-relwithdebinfo\",\n            \"configurePreset\": \"mingw64-i686-cross\",\n            \"displayName\": \"Build mingw64-i686 release with debug info\",\n            \"description\": \"Cross-compile with mingw64-i686 Release with debug info configuration\",\n            \"configuration\": \"RelWithDebInfo\"\n        },\n        {\n            \"name\": \"macos-arm64-debug\",\n            \"configurePreset\": \"macos-arm64\",\n            \"displayName\": \"Build Xcode debug\",\n            \"description\": \"Build Xcode Debug configuration\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"macos-arm64-release\",\n            \"configurePreset\": \"macos-arm64\",\n            \"displayName\": \"Build Xcode release\",\n            \"description\": \"Build Xcode Release configuration\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"macos-arm64-relwithdebinfo\",\n            \"configurePreset\": \"macos-arm64\",\n            \"displayName\": \"Build Xcode release with debug info\",\n            \"description\": \"Build Xcode Release with debug info configuration\",\n            \"configuration\": \"RelWithDebInfo\"\n        },\n        {\n            \"name\": \"macos-x64-debug\",\n            \"configurePreset\": \"macos-x64\",\n            \"displayName\": \"Build Xcode debug\",\n            \"description\": \"Build Xcode Debug configuration\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"macos-x64-release\",\n            \"configurePreset\": \"macos-x64\",\n            \"displayName\": \"Build Xcode release\",\n            \"description\": \"Build Xcode Release configuration\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"macos-x64-relwithdebinfo\",\n            \"configurePreset\": \"macos-x64\",\n            \"displayName\": \"Build Xcode release with debug info\",\n            \"description\": \"Build Xcode Release with debug info configuration\",\n            \"configuration\": \"RelWithDebInfo\"\n        },\n        {\n            \"name\": \"dynamic-debug\",\n            \"configurePreset\": \"dynamic\",\n            \"displayName\": \"Build native dynamically linked release\",\n            \"description\": \"Build with community release triplet\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"dynamic-release\",\n            \"configurePreset\": \"dynamic\",\n            \"displayName\": \"Build native dynamically linked release\",\n            \"description\": \"Build with community release triplet\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"dynamic-relwithdebinfo\",\n            \"configurePreset\": \"dynamic\",\n            \"displayName\": \"Build native dynamically linked release\",\n            \"description\": \"Build with community release triplet\",\n            \"configuration\": \"RelWithDebInfo\"\n        },\n        {\n            \"name\": \"static-debug\",\n            \"configurePreset\": \"static\",\n            \"displayName\": \"Build native statically linked debug\",\n            \"description\": \"Build with statically linked debug\",\n            \"configuration\": \"Debug\"\n        },\n        {\n            \"name\": \"static-release\",\n            \"configurePreset\": \"static\",\n            \"displayName\": \"Build native statically linked release\",\n            \"description\": \"Build with statically linked release\",\n            \"configuration\": \"Release\"\n        },\n        {\n            \"name\": \"static-relwithdebinfo\",\n            \"configurePreset\": \"static\",\n            \"displayName\": \"Build native statically linked release with debug info\",\n            \"description\": \"Build with statically linked release with debug info\",\n            \"configuration\": \"RelWithDebInfo\"\n        }\n    ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "GNU GENERAL PUBLIC LICENSE\nVersion 2, June 1991 \n\nCopyright (C) 1989, 1991 Free Software Foundation, Inc.  \n59 Temple Place - Suite 330, Boston, MA  02111-1307, USA\n\nEveryone is permitted to copy and distribute verbatim copies\nof this license document, but changing it is not allowed.\nPreamble\nThe licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. \n\nWhen we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. \n\nTo protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. \n\nFor example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. \n\nWe protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. \n\nAlso, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. \n\nFinally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. \n\nThe precise terms and conditions for copying, distribution and modification follow. \n\nTERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The \"Program\", below, refers to any such program or work, and a \"work based on the Program\" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term \"modification\".) Each licensee is addressed as \"you\". \n\nActivities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. \n\n1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. \n\nYou may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. \n\n2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: \n\n\na) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. \n\nb) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. \n\nc) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) \nThese requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. \nThus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. \n\nIn addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. \n\n3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: \n\na) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, \n\nb) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, \n\nc) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) \nThe source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. \nIf distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. \n\n4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. \n\n5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. \n\n6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. \n\n7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. \n\nIf any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. \n\nIt is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. \n\nThis section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. \n\n8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. \n\n9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. \n\nEach version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and \"any later version\", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. \n\n10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. \n\nNO WARRANTY\n\n11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. \n\n12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. \n\n\nEND OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "README.md",
    "content": "# ezQuake — Modern QuakeWorld Client\nHomepage: [https://ezquake.com][homepage]\n\nCommunity discord: [http://discord.quake.world][discord]\n\nThis is the right place to start playing QuakeWorld&reg; — the fastest first\nperson shooter action game ever.\n\nCombining the features of all modern QuakeWorld® clients, ezQuake makes\nQuakeWorld&reg; easier to start and play. The immortal first person shooter\nQuake&reg; in the brand new skin with superb graphics and extremely fast\ngameplay.\n\n## Features\n\n * Modern graphics\n * [QuakeTV][qtv] support\n * Rich menus\n * Multiview support\n * Tons of features to serve latest pro-gaming needs\n * Built in server browser & MP3 player control\n * Recorded games browser\n * Customization of all possible graphics elements of the game including Heads Up Display\n * All sorts of scripting possibilities\n * Windows, Linux, MacOSX and FreeBSD platforms supported (SDL2).\n\nOur client comes only with bare minimum of game media. If you want to\nexperience ezQuake with modern graphics and other additional media including\ncustom configurations, maps, textures and more, try using the [nQuake][nQuake]-installer.\n\n## Support\n\nNeed help with using ezQuake? Try #dev-corner on [discord][discord]\n\nOr (less populated these days) visit us on IRC at QuakeNet, channel #ezQuake: [webchat][webchat] or [IRC][IRC].\n\nSometimes help from other users of ezQuake might be more useful to you so you\ncan also try visiting the [quakeworld.nu Client Talk-forums][forum].\n\nIf you have found a bug, please report it [here][issues]\n\n## Installation guide\n\nTo play Quakeworld you need the files *pak0.pak* and *pak1.pak* from the original Quake-game.\n\n### Install ezQuake to an existing Quake-installation\nIf you have an existing Quake-installation simply extract the ezQuake executable into your Quake-directory.\n\nA typical error message when installing ezQuake into a pre-existing directory is about *glide2x.dll* missing.\nTo get rid of this error, remove the file *opengl32.dll* from your Quake directory.\n\n### Upgrade an nQuake-installation\nIf you have a version of [nQuake][nQuake] already installed you can upgrade ezQuake by extracting the new executable into the nQuake-directory.\n\n### Minimal clean installation\nIf you want to make a clean installation of ezQuake you can do this by following these steps:\n\n1. Create a new directory\n2. Extract the ezQuake-executable into this directory\n3. Create a subdirectory called *id1*\n4. Copy *pak0.pak* and *pak1.pak* into this subdirectory\n\n## Compiling\n\nOn Linux, `./build-linux.sh` produces an ezQuake binary in the top directory. \n\nFor a more in-depth description of how to build on all platforms, have a look at \n[BUILD.md](BUILD.md).\n\n## Nightly builds\n\nNightly builds can be found [here][nightly]\n\n [nQuake]: http://nquake.com/\n [webchat]: http://webchat.quakenet.org/?channels=#ezquake\n [IRC]: irc://irc.quakenet.org/#ezquake\n [forum]: http://www.quakeworld.nu/forum/8\n [qtv]: http://qtv.quakeworld.nu/\n [nightly]: https://builds.quakeworld.nu/ezquake/snapshots/\n [releases]: https://github.com/ezQuake/ezquake-source/releases\n [issues]: https://github.com/ezQuake/ezquake-source/issues\n [homepage]: https://ezquake.com\n [discord]: http://discord.quake.world/\n"
  },
  {
    "path": "bootstrap.ps1",
    "content": "function Show-MessageBox {\n    param ([string]$message)\n    Add-Type -AssemblyName PresentationFramework\n    [System.Windows.MessageBox]::Show($message, \"Bootstrap Error\", 'OK', 'Warning')\n}\n\nif (-not (Get-Command git -ErrorAction SilentlyContinue)) {\n    Show-MessageBox \"Git is needed to checkout vcpkg, but it's not installed or not available in PATH.\"\n    exit 1\n}\n\ngit submodule update --init --recursive\n\nif (-not (Test-Path \"vcpkg/.git\")) {\n    if (-not (Test-Path \"version.json\")) {\n        Show-MessageBox \"Unable to checkout correct version of vcpkg without 'version.json'.\"\n        exit 1\n    }\n\n    try {\n        $versionData = Get-Content -Raw -Path \"version.json\" | ConvertFrom-Json\n    } catch {\n        Show-MessageBox \"version.json is not valid JSON.\"\n        exit 1\n    }\n\n    $vcpkgVersion = $versionData.vcpkg\n\n    if (Test-Path \"vcpkg\") {\n        Remove-Item -Recurse -Force \"vcpkg\"\n    }\n\n    git clone --branch $vcpkgVersion --depth 1 https://github.com/microsoft/vcpkg.git \"vcpkg\"\n    $cloneExitCode = $LASTEXITCODE\n\n    if ($cloneExitCode -ne 0) {\n        Show-MessageBox \"Checkout of vcpkg failed.\"\n        exit 1\n    }\n}\n\n& \"vcpkg/bootstrap-vcpkg.bat\" -disableMetrics"
  },
  {
    "path": "bootstrap.sh",
    "content": "#!/bin/sh -e\n\nshow_error() {\n    printf \"\\e[31mError\\e[0m: $1\\n\"\n    exit 1\n}\n\nrequired_commands=\"cmake ninja git automake autoconf pkg-config curl zip unzip tar\"\n\nmissing_deps=\"\"\nfor cmd in $required_commands; do\n    if ! command -v \"$cmd\" >/dev/null 2>&1; then\n        missing_deps=\"$missing_deps $cmd\"\n    fi\ndone\n\n# naming differs on macOS/Linux\nif ! command -v \"libtoolize\" >/dev/null 2>&1 && ! command -v \"glibtoolize\" >/dev/null 2>&1; then\n    missing_deps=\"$missing_deps libtool\";\nfi\n\nif [ -n \"$missing_deps\" ]; then\n    show_error \"Install packages that provide support for:$missing_deps\"\nfi\n\nif [ -e \".git\" ]; then\n    echo \"Updating submodules...\"\n    git submodule update --init --recursive\nfi\n\nif [ ! -e \"vcpkg/.git\" ]; then\n    if [ ! -f \"version.json\" ]; then\n        show_error \"Unable to checkout vcpkg without 'version.json'.\"\n    fi\n\n    vcpkg_version=$(sed -n 's/.*\"vcpkg\": *\"\\(.*\\)\".*/\\1/p' version.json)\n    if [ -z \"$vcpkg_version\" ]; then\n        show_error \"Could not extract vcpkg version from version.json.\"\n    fi\n\n    if [ -d \"vcpkg\" ]; then\n        rm -rf \"vcpkg\"\n    fi\n\n    git clone --branch \"$vcpkg_version\" --depth 1 https://github.com/microsoft/vcpkg.git \"vcpkg\"\n    if [ $? -ne 0 ]; then\n        show_error \"Checkout of vcpkg failed.\"\n    fi\nfi\n\nvcpkg/bootstrap-vcpkg.sh -disableMetrics\nif [ $? -ne 0 ]; then\n    show_error \"vcpkg bootstrap failed.\"\nfi\n"
  },
  {
    "path": "build-linux.sh",
    "content": "#!/bin/sh -e\n# simple build script for linux\n\n# ANSI color codes\nRED='\\e[31m'\nGREEN='\\e[32m'\nYELLOW='\\e[33m'\nNC='\\e[0m'\n\nBUILD_LOG=/tmp/ezquake-build.log\n\nSKIP_DEPS=\"${SKIP_DEPS:-0}\"\n\nPKGS_DEB=\"git cmake build-essential libsdl2-2.0-0 libsdl2-dev libjansson-dev libexpat1-dev libcurl4-openssl-dev libpng-dev libjpeg-dev libspeex-dev libspeexdsp-dev libfreetype-dev libsndfile1-dev libpcre2-dev libminizip-dev\"\nPKGS_RPM=\"pcre2-devel cmake mesa-libGL-devel SDL2-devel make gcc jansson-devel expat-devel libcurl-devel libpng-devel libjpeg-turbo-devel speex-devel speexdsp-devel freetype-devel libsndfile-devel libXxf86vm-devel minizip-devel\"\nPKGS_ARCH=\"base-devel cmake libpng libjpeg-turbo sdl2 expat libcurl-compat freetype2 speex speexdsp jansson libsndfile minizip\"\nPKGS_VOID=\"base-devel cmake SDL2-devel pcre2-devel jansson-devel expat-devel libcurl-devel libpng-devel libjpeg-turbo-devel speex-devel speexdsp-devel freetype-devel libsndfile-devel libXxf86vm-devel minizip\"\n\nCPU=$(uname -m | sed -e s/i.86/i386/ -e s/amd64/x86_64/ -e s/sun4u/sparc64/ -e s/arm.*/arm/ -e s/sa110/arm/ -e s/alpha/axp/)\n\nerror() {\n\t[ ! -e $BUILD_LOG ] || cat $BUILD_LOG\n\tprintf \"${RED}[ERROR]${NC} %s\\n\" \"$*\"\n\texit 1\n}\n\nstep() {\n\tprintf \"${GREEN}[STEP ]${NC} %s\\n\" \"$*\"\n}\n\ninfo() {\n\tprintf \"[INFO ] %s\\n\" \"$*\"\n}\n\nwarn() {\n\tprintf \"${YELLOW}[WARN ]${NC} %s\\n\" \"$*\"\n}\n\ninstall_check_deb() {\n\tstep \"Install/check dependecies (packages)...\"\n\tinfo \"You might be prompted to input your password as superuser privileges are required.\"\n\n\tinfo \"Updating apt repo list... (running with sudo)\"\n\tsudo apt-get update -y -qq || error \"Failed to update package sources. Exiting.\"\n\tinfo \"Checking/installing required packages... (running with sudo)\"\n\tsudo apt-get install -y -q $PKGS_DEB >>$BUILD_LOG 2>&1 || error \"Failed to install required packages. Exiting.\"\n}\n\ninstall_check_rpm() {\n\tstep \"Install/check dependecies (packages)...\"\n\tinfo \"You might be prompted to input your password as superuser privileges are required.\"\n\tinfo \"Updating yum repo list... (running with sudo)\"\n\tsudo yum clean all -yqqq && sudo yum check-update -yqqq >>$BUILD_LOG 2>&1 || error \"Failed to update repo list. Exiting.\"\n\tinfo \"Checking/installing required packages... (running with sudo)\"\n\tsudo yum install -yq $PKGS_RPM >>$BUILD_LOG 2>&1 || error \"Failed to install required packages. Exiting.\"\n}\n\ninstall_check_arch() {\n\tstep \"Install/check dependecies (packages)...\"\n\tinfo \"You might be prompted to input your password as superuser privileges are required.\"\n\tsudo pacman -Sy >>$BUILD_LOG 2>&1 || error \"Failed to update repository cache. Exiting.\"\n\tsudo pacman -S --needed --noconfirm $PKGS_ARCH >>$BUILD_LOG 2>&1 || error \"Failed to install required packages. Exiting.\"\n}\n\ninstall_check_void() {\n    step \"Install/check dependencies (packages)...\"\n    info \"You might be prompted to input your password as superuser privileges are required.\"\n\n    info \"Updating xbps repo list... (running with sudo)\"\n    sudo xbps-install -Sy >>$BUILD_LOG 2>&1 || error \"Failed to update package sources. Exiting.\"\n    info \"Checking/installing required packages... (running with sudo)\"\n    sudo xbps-install -y $PKGS_VOID >>$BUILD_LOG 2>&1 || error \"Failed to install required packages. Exiting.\"\n}\n\nif [ -f $BUILD_LOG ];then\n\trm -f $BUILD_LOG ||:\nfi\n\n[ -e CMakeLists.txt ] || error \"Cannot find 'CMakeLists.txt', please run this script from the source code directory.\"\n\nif [ $SKIP_DEPS -eq 0 ];then\n\tcommand -v sudo &>/dev/null || error \"Could not find sudo, please install it. Exiting.\"\n\n\tif [ -f /etc/os-release ]; then\n\t\t. /etc/os-release || error \"Failed to source os-release file\"\n\telse\n\t\terror \"Your environment isn't supported by this script. Exiting.\"\n\tfi\n\n\t[ -n \"${ID}\" ] || error \"Your dist does not specify ID in /etc/os-release. Exiting.\"\n\t[ -n \"${VERSION_ID}\" ] || VERSION_ID=0\n\tcase $ID in\n\t\tarch)\n\t\t\tinstall_check_arch\n\t\t\t;;\n\t\tmanjaro)\n\t\t\tinstall_check_arch\n\t\t\t;;\n\t\tvoid)\n\t\t\tinstall_check_void\n\t\t\t;;\n\t\tlinuxmint)\n\t\t\t[ $VERSION_ID -ge 18 ] || error \"Your Linux Mint version '$VERSION_ID' is too old. Exiting.\"\n\t\t\tinstall_check_deb\n\t\t\t;;\n\t\tubuntu)\n\t\t\tVERSION_ID=${VERSION_ID%.*}\n\t\t\t[ $VERSION_ID -ge 16 ] || error \"Your Ubuntu version '$VERSION_ID' is too old. Exiting.\"\n\t\t\tinstall_check_deb\n\t\t\t;;\n\t\tdevuan)\n\t\t\tif [ $VERSION_ID -gt 0 ] && [ $VERSION_ID -lt 4 ] ; then\n\t\t\t\terror \"Your Devuan version '$VERSION_ID' is too old. Exiting.\"\n\t\t\tfi\n\t\t\t# Includes Devuan testing/unstable as they don't provide a VERSION_ID\n\t\t\tinstall_check_deb\n\t\t\t;;\n\t\tdebian)\n\t\t\tif [ $VERSION_ID -gt 0 ] && [ $VERSION_ID -lt 8 ]; then\n\t\t\t\terror \"Your Debian version '$VERSION_ID' is too old. Exiting.\"\n\t\t\tfi\n\t\t\t# Includes Debian testing/unstable as they don't provide a VERSION_ID\n\t\t\tinstall_check_deb\n\t\t\t;;\n\t\tpop)\n\t\t\tVERSION_ID=${VERSION_ID%.*}\n\t\t\t[ $VERSION_ID -ge 17 ] || error \"Your Pop!_OS version '$VERSION_ID' is too old. Exiting.\"\n\t\t\tinstall_check_deb\n\t\t\t;;\n\t\tcentos|rhel|fedora)\n\t\t\t# FIXME: Versions checks?\n\t\t\tinstall_check_rpm\n\t\t\t;;\n\t\t*)\n\t\t\terror \"Your dist '$ID' isn't supported by this script. Exiting.\"\n\t\t\t;;\n\tesac\nfi\n\nif [ -d \".git\" ]; then\n\tstep \"Checking out git submodules...\"\n\tgit submodule update --init --recursive >> $BUILD_LOG 2>&1 || error \"Failed to checkout git submodules. Exiting.\"\nfi\n\nif [ ! -f src/qwprot/src/protocol.h ]; then\n  error \"Bad source code directory, not a git repository, and lacks src/qwprot/src/protocol.h.\" \\\n        \"Download an official source release or checkout the official git repository.\"\nfi\n\nstep \"Configure build...\"\nif command -v ninja >/dev/null 2>&1; then\n  GENERATOR=\"-G Ninja\"\nfi\ncmake -B build \"${GENERATOR}\" -DCMAKE_BUILD_TYPE=Release\n\nstep \"Compiling sources (this might take a while, please wait)...\"\ncmake --build build --parallel\n\nprintf \"\\n${GREEN}Build completed successfully.${NC}\\n\"\nprintf \"Copy ${YELLOW}build/ezquake-linux-${CPU}${NC} into your quake directory.\\n\\n\"\n"
  },
  {
    "path": "cmake/AddResources.cmake",
    "content": "# Generate C source code that embeds arbitrary files via calling the\n# ResourceCompiler CMake script using CMake itself.\n#\n# Declared like this:\n# const unsigned char blabla[] = {\n#   0x23,0x76,0x65,...,\n# };\n# const unsigned int blabla_len = 581;\n\nfind_program(JQ_EXECUTABLE jq)\n\nmacro(add_resources target_var)\n    add_library(${target_var} OBJECT)\n    set(RESOURCE_COMPILER \"${PROJECT_SOURCE_DIR}/cmake/ResourceCompiler.cmake\")\n\n    set(generated_base_directory \"${CMAKE_CURRENT_BINARY_DIR}/${target_var}.dir/resources\")\n\n    foreach(source_file ${ARGN})\n        file(RELATIVE_PATH source_file_relative \"${CMAKE_SOURCE_DIR}\" \"${source_file}\")\n        get_filename_component(source_file_dir \"${source_file}\" DIRECTORY)\n        get_filename_component(source_file_dir_relative \"${source_file_relative}\" DIRECTORY)\n        set(generated_directory \"${generated_base_directory}/${source_file_dir_relative}\")\n\n        file(MAKE_DIRECTORY ${generated_directory})\n\n        get_filename_component(source_file_name \"${source_file}\" NAME)\n        set(generated_file_name \"${generated_directory}/${source_file_name}.c\")\n\n        get_filename_component(source_ext \"${source_file}\" EXT)\n        if (source_ext STREQUAL \".json\" AND JQ_EXECUTABLE)\n            set(validation_command ${JQ_EXECUTABLE} empty \"${source_file}\")\n        endif()\n\n        add_custom_command(\n                OUTPUT ${generated_file_name}\n                COMMAND ${validation_command}\n                COMMAND ${CMAKE_COMMAND} -P ${RESOURCE_COMPILER} \"${source_file}\" \"${generated_file_name}\"\n                WORKING_DIRECTORY \"${source_file_dir}\"\n                DEPENDS ${source_file} ${RESOURCE_COMPILER}\n                COMMENT \"Generating C file from ${source_file_relative}\"\n                VERBATIM\n        )\n        target_sources(${target_var} PRIVATE ${generated_file_name} ${source_file})\n        set_source_files_properties(\"${CMAKE_SOURCE_DIR}/${source_file}\" PROPERTIES HEADER_FILE_ONLY TRUE)\n        set_source_files_properties(\"${generated_file_name}\" PROPERTIES GENERATED TRUE)\n\n        source_group(TREE \"${CMAKE_SOURCE_DIR}\" PREFIX \"Source Files\" FILES ${source_file})\n        source_group(TREE \"${generated_base_directory}\" PREFIX \"Generated Sources\" FILES ${generated_file_name})\n    endforeach()\nendmacro()\n\n"
  },
  {
    "path": "cmake/CheckDependency.cmake",
    "content": "# Check if a dependency exists and declare a target named Dep::$name.\n# If USE_SYSTEM_LIBS variable is set, find dependency via pkg-config.\n\nif (USE_SYSTEM_LIBS)\n    find_package(PkgConfig REQUIRED)\nendif()\n\nmacro(check_dependency target_var pkg_config_name vcpkg_name vcpkg_target_name)\n    cmake_parse_arguments(_ARG \"REQUIRED;CONFIG\" \"\" \"\" ${ARGN})\n    set(_REQUIRED \"\")\n    if (_ARG_REQUIRED)\n        set(_REQUIRED \"REQUIRED\")\n    endif()\n    set(_CONFIG \"\")\n    if (_ARG_CONFIG)\n        set(_CONFIG \"CONFIG\")\n    endif()\n    string(TOUPPER ${target_var} _TARGET_VAR_UPPER)\n    set(HAVE_${_TARGET_VAR_UPPER} FALSE)\n    if (USE_SYSTEM_LIBS)\n        pkg_check_modules(${target_var} ${_REQUIRED} IMPORTED_TARGET ${pkg_config_name})\n        if (${target_var}_FOUND)\n            add_library(Dep::${target_var} ALIAS PkgConfig::${target_var})\n            set(HAVE_${_TARGET_VAR_UPPER} TRUE)\n        endif()\n    else()\n        find_package(${vcpkg_name} ${_CONFIG} ${_REQUIRED})\n        if (TARGET ${vcpkg_target_name})\n            set(actual_target ${vcpkg_target_name})\n            get_target_property(aliased_target ${actual_target} ALIASED_TARGET)\n            if (aliased_target AND NOT \"${aliased_target}\" MATCHES \"NOTFOUND\")\n                set(actual_target ${aliased_target})\n            endif()\n            add_library(Dep::${target_var} ALIAS ${actual_target})\n            set(HAVE_${_TARGET_VAR_UPPER} TRUE)\n        endif()\n    endif()\nendmacro()\n"
  },
  {
    "path": "cmake/FindSpeex.cmake",
    "content": "# Can be removed once vcpkg speexdsp package has gained a proper cmake-wrapper.\nset(_VCPKG_ARCH_DIR \"${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}\")\nfind_path(SPEEX_INCLUDE_DIR NAMES speex/speex.h PATHS \"${_VCPKG_ARCH_DIR}/include\" NO_DEFAULT_PATH REQUIRED)\nfind_library(SPEEX_LIB_RELEASE NAMES speex PATHS \"${_VCPKG_ARCH_DIR}/lib\" NO_DEFAULT_PATH)\nfind_library(SPEEX_LIB_DEBUG NAMES speex PATHS \"${_VCPKG_ARCH_DIR}/debug/lib\" NO_DEFAULT_PATH)\nif(NOT SPEEX_LIB_RELEASE AND NOT SPEEX_LIB_DEBUG)\n    message(FATAL_ERROR \"Speex library not found\")\nendif()\nadd_library(SPEEX::SPEEX STATIC IMPORTED)\nset_target_properties(SPEEX::SPEEX PROPERTIES\n        IMPORTED_CONFIGURATIONS \"Debug;Release\"\n        IMPORTED_LOCATION_RELEASE \"${SPEEX_LIB_RELEASE}\"\n        IMPORTED_LOCATION_DEBUG \"${SPEEX_LIB_DEBUG}\"\n        INTERFACE_INCLUDE_DIRECTORIES \"${SPEEX_INCLUDE_DIR}\"\n)"
  },
  {
    "path": "cmake/FindSpeexDSP.cmake",
    "content": "# Can be removed once vcpkg speexdsp package has gained a proper cmake-wrapper.\nset(_VCPKG_ARCH_DIR \"${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}\")\nfind_path(SPEEXDSP_INCLUDE_DIR NAMES speex/speexdsp_types.h PATHS \"${_VCPKG_ARCH_DIR}/include\" NO_DEFAULT_PATH REQUIRED)\nfind_library(SPEEXDSP_LIB_RELEASE NAMES speexdsp PATHS \"${_VCPKG_ARCH_DIR}/lib\" NO_DEFAULT_PATH)\nfind_library(SPEEXDSP_LIB_DEBUG NAMES speexdsp PATHS \"${_VCPKG_ARCH_DIR}/debug/lib\" NO_DEFAULT_PATH)\nif(NOT SPEEXDSP_LIB_RELEASE AND NOT SPEEXDSP_LIB_DEBUG)\n    message(FATAL_ERROR \"SpeexDSP library not found\")\nendif()\nadd_library(SPEEX::SPEEXDSP STATIC IMPORTED)\nset_target_properties(SPEEX::SPEEXDSP PROPERTIES\n        IMPORTED_CONFIGURATIONS \"Debug;Release\"\n        IMPORTED_LOCATION_RELEASE \"${SPEEXDSP_LIB_RELEASE}\"\n        IMPORTED_LOCATION_DEBUG \"${SPEEXDSP_LIB_DEBUG}\"\n        INTERFACE_INCLUDE_DIRECTORIES \"${SPEEXDSP_INCLUDE_DIR}\"\n)"
  },
  {
    "path": "cmake/GitUtils.cmake",
    "content": "find_package(Git QUIET)\n\nfunction(git_refresh_submodules)\n    if (GIT_FOUND AND EXISTS \"${PROJECT_SOURCE_DIR}/.git\")\n        option(GIT_SUBMODULE \"Check submodules during build\" ON)\n        if (GIT_SUBMODULE)\n            message(STATUS \"Submodule update\")\n            execute_process(\n                    COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive\n                    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n                    RESULT_VARIABLE GIT_SUBMOD_RESULT\n                    OUTPUT_QUIET\n            )\n            if (NOT GIT_SUBMOD_RESULT EQUAL \"0\")\n                message(FATAL_ERROR \"git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules\")\n            endif()\n        endif()\n    endif()\nendfunction()\n\n# Will load the version from 'version.json' file on configuration time if it exists.\n#\n# The content of this file looks like this:\n# {\n#   \"version\": \"3.6.5-92-g8e3875f40\",\n#   \"revision\": 7739,\n#   \"commit\": \"595806cd2449d4b17024b892c6e5b169512be5e0\",\n#   \"date\": \"2024-08-18T16:53:29+02:00\",\n#   \"vcpkg\": \"2024-02-14\"\n# }\nfunction(git_extract_version target_var)\n    if (GIT_FOUND AND EXISTS \"${PROJECT_SOURCE_DIR}/.git\")\n        execute_process(\n                COMMAND ${GIT_EXECUTABLE} rev-parse --is-shallow-repository\n                OUTPUT_VARIABLE GIT_IS_SHALLOW\n                OUTPUT_STRIP_TRAILING_WHITESPACE\n                WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n        )\n\n        if (GIT_IS_SHALLOW MATCHES \"true\")\n            message(WARNING \"Shallow repository detected, revision not available.\")\n            set(GIT_REVISION \"0\")\n        else()\n            execute_process(\n                    COMMAND ${GIT_EXECUTABLE} rev-list HEAD --count\n                    OUTPUT_VARIABLE GIT_REVISION\n                    OUTPUT_STRIP_TRAILING_WHITESPACE\n                    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n            )\n        endif()\n\n        execute_process(\n                COMMAND ${GIT_EXECUTABLE} rev-parse HEAD\n                OUTPUT_VARIABLE GIT_COMMIT_HASH\n                OUTPUT_STRIP_TRAILING_WHITESPACE\n                WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n        )\n\n        execute_process(\n                COMMAND ${GIT_EXECUTABLE} describe --tags --always\n                OUTPUT_VARIABLE GIT_DESCRIBE\n                RESULT_VARIABLE GIT_DESCRIBE_RESULT\n                OUTPUT_STRIP_TRAILING_WHITESPACE\n                WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n        )\n    elseif (EXISTS \"${PROJECT_SOURCE_DIR}/version.json\")\n        message(\"-- Loading version from 'version.json'\")\n        file(READ \"${CMAKE_CURRENT_SOURCE_DIR}/version.json\" VERSION_CONTENT)\n        string(JSON GIT_DESCRIBE GET \"${VERSION_CONTENT}\" \"version\")\n        string(JSON GIT_REVISION GET \"${VERSION_CONTENT}\" \"revision\")\n        string(JSON GIT_COMMIT_HASH GET \"${VERSION_CONTENT}\" \"commit\")\n    endif()\n\n    if (NOT GIT_DESCRIBE)\n        set(GIT_DESCRIBE \"0.0.0-0-g00000000\")\n    endif()\n\n    if (NOT GIT_REVISION)\n        set(GIT_REVISION \"0\")\n    endif()\n\n    if (NOT GIT_COMMIT_HASH)\n        set(GIT_COMMIT_HASH \"0000000000000000000000000000000000000000\")\n    endif()\n\n    string(SUBSTRING ${GIT_COMMIT_HASH} 0 9 GIT_COMMIT_SHORT_HASH)\n\n    add_library(${target_var} INTERFACE)\n    target_compile_definitions(${target_var} INTERFACE\n            REVISION=${GIT_REVISION}\n            VERSION=\"${GIT_REVISION}~${GIT_COMMIT_SHORT_HASH}\"\n    )\n\n    set(VERSION_MAJOR 0)\n    set(VERSION_MINOR 0)\n    set(VERSION_PATCH 0)\n\n    string(REGEX REPLACE \"^([0-9]+)\\\\.([0-9]+)\\\\.([0-9]+).*\" \"\\\\1;\\\\2;\\\\3\" SEMVER_MATCH \"${GIT_DESCRIBE}\")\n    list(LENGTH SEMVER_MATCH PARTS_SIZE)\n\n    if(SEMVER_MATCH)\n        if(PARTS_SIZE GREATER 0)\n            list(GET SEMVER_MATCH 0 VERSION_MAJOR)\n        endif()\n\n        if(PARTS_SIZE GREATER 1)\n            list(GET SEMVER_MATCH 1 VERSION_MINOR)\n        endif()\n\n        if(PARTS_SIZE GREATER 2)\n            list(GET SEMVER_MATCH 2 VERSION_PATCH)\n        endif()\n    else()\n        message(WARNING \"Upstream tags missing. Using default version 0.0.0\")\n    endif()\n\n    set_target_properties(${target_var} PROPERTIES\n            REVISION      \"${GIT_REVISION}\"\n            VERSION       \"${GIT_REVISION}~${GIT_COMMIT_SHORT_HASH}\"\n            COMMIT        \"${GIT_COMMIT_HASH}\"\n            GIT_DESCRIBE  \"${GIT_DESCRIBE}\"\n            VERSION_MAJOR \"${VERSION_MAJOR}\"\n            VERSION_MINOR \"${VERSION_MINOR}\"\n            VERSION_PATCH \"${VERSION_PATCH}\"\n    )\n\n    message(STATUS \"Version: ${GIT_DESCRIBE} (${GIT_REVISION}~${GIT_COMMIT_SHORT_HASH})\")\nendfunction()"
  },
  {
    "path": "cmake/ResourceCompiler.cmake",
    "content": "# Generate C code with some content encoded as an array of unsigned char.\n# See AddResources.cmake for more information.\n\nset(input_file \"${CMAKE_ARGV3}\")\nset(output_file \"${CMAKE_ARGV4}\")\n\nget_filename_component(base_name \"${input_file}\" NAME)\nstring(REGEX REPLACE \"[.]\" \"_\" variable_name \"${base_name}\")\n\nfile(READ \"${input_file}\" content HEX)\n\nstring(LENGTH \"${content}\" content_length)\nmath(EXPR data_length \"${content_length} / 2\")\n\nstring(REGEX REPLACE \"([0-9a-f][0-9a-f])\" \"0x\\\\1,\" data \"${content}\")\n\nstring(APPEND data \"0x00,\")\n\nfile(WRITE \"${output_file}\" \"const unsigned char ${variable_name}[] = {\\n${data}\\n};\\nconst unsigned int ${variable_name}_len = ${data_length};\\n\")\n"
  },
  {
    "path": "cmake/triplets/arm64-osx.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE arm64)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE static)\n\nset(VCPKG_CMAKE_SYSTEM_NAME Darwin)\nset(VCPKG_OSX_ARCHITECTURES arm64)\nset(VCPKG_OSX_DEPLOYMENT_TARGET \"11.0\")"
  },
  {
    "path": "cmake/triplets/x64-mingw-static.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE x64)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE static)\nset(VCPKG_ENV_PASSTHROUGH PATH)\n\nset(VCPKG_CMAKE_SYSTEM_NAME MinGW)\n\nset(VCPKG_C_FLAGS \"-march=nehalem \")\nset(VCPKG_CXX_FLAGS \"-march=nehalem \")"
  },
  {
    "path": "cmake/triplets/x64-osx.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE x64)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE static)\n\nset(VCPKG_CMAKE_SYSTEM_NAME Darwin)\nset(VCPKG_OSX_ARCHITECTURES x86_64)\nset(VCPKG_OSX_DEPLOYMENT_TARGET \"11.0\")"
  },
  {
    "path": "cmake/triplets/x86-mingw-static.cmake",
    "content": "set(VCPKG_TARGET_ARCHITECTURE x86)\nset(VCPKG_CRT_LINKAGE dynamic)\nset(VCPKG_LIBRARY_LINKAGE static)\nset(VCPKG_ENV_PASSTHROUGH PATH)\n\nset(VCPKG_CMAKE_SYSTEM_NAME MinGW)\n\nset(VCPKG_C_FLAGS \"-msse2 \")\nset(VCPKG_CXX_FLAGS \"-msse2 \")"
  },
  {
    "path": "dist/gen-release.sh",
    "content": "#!/bin/sh\n\nTOP_DIR=$(git rev-parse --show-toplevel)\n\ncd \"${TOP_DIR}\"\n\ngit submodule update --init --recursive\n\nrm -rf release\nmkdir release\ncd release\n\nEZQ_DIR=$(git rev-parse --absolute-git-dir)\nVCPKG_DIR=$(git -C \"${TOP_DIR}/vcpkg\" rev-parse --absolute-git-dir)\nQWPROT_DIR=$(git -C \"${TOP_DIR}/src/qwprot\" rev-parse --absolute-git-dir)\n\nGIT_REVISION=$(git --git-dir=\"${EZQ_DIR}\" rev-list HEAD --count)\nGIT_COMMIT_HASH=$(git --git-dir=\"${EZQ_DIR}\" rev-parse HEAD)\nGIT_DESCRIBE=$(git --git-dir=\"${EZQ_DIR}\" describe --tags)\nGIT_COMMIT_DATE=$(git --git-dir=\"${EZQ_DIR}\" log -1 --format=%cI)\nGIT_COMMIT_TIMESTAMP=$(git --git-dir=\"${EZQ_DIR}\" log -1 --format=%cd --date=format-local:%Y%m%d%H%M.%S)\n\nVCPKG_TAG=$(git --git-dir=\"${VCPKG_DIR}\" describe --tags)\n\nEZQ_NAME=\"ezquake-source-${GIT_DESCRIBE}\"\nEZQ_TAR=\"${EZQ_NAME}.tar\"\nEZQ_VERSION=\"${EZQ_NAME}/version.json\"\nEZQ_CHECKSUM=\"${EZQ_NAME}/checksum\"\n\nQWPROT_TAR=\"qwprot.tar\"\n\necho \"* Release: ${GIT_DESCRIBE} (rev: ${GIT_REVISION}, vcpkg: ${VCPKG_TAG})\"\n\necho \"* Creating ${EZQ_TAR}\"\ngit --git-dir=\"${EZQ_DIR}\" archive --format=tar --prefix=\"${EZQ_NAME}/\" HEAD > \"${EZQ_TAR}\"\n\necho \"* Creating ${QWPROT_TAR}\"\ngit --git-dir=\"${QWPROT_DIR}\" archive --format=tar --prefix=src/qwprot/ HEAD > \"${QWPROT_TAR}\"\n\necho \"* Prepare merging tarballs\"\ntar -xf \"${EZQ_TAR}\"\ntar -xf \"${QWPROT_TAR}\" -C \"${EZQ_NAME}/\"\n\necho \"* Generating ${EZQ_VERSION}\"\ncat > \"${EZQ_VERSION}\" <<EOF\n{\n  \"version\": \"${GIT_DESCRIBE}\",\n  \"revision\": ${GIT_REVISION},\n  \"commit\": \"${GIT_COMMIT_HASH}\",\n  \"date\": \"${GIT_COMMIT_DATE}\",\n  \"vcpkg\": \"${VCPKG_TAG}\"\n}\nEOF\n\necho \"* Generating ${EZQ_CHECKSUM}\"\necho \"${GIT_DESCRIBE}\" >> \"${EZQ_CHECKSUM}\"\nshasum \"${EZQ_VERSION}\" >> \"${EZQ_CHECKSUM}\"\ngit --git-dir=\"${EZQ_DIR}\" ls-tree -r HEAD >> \"${EZQ_CHECKSUM}\"\ngit --git-dir=\"${QWPROT_DIR}\" ls-tree -r HEAD >> \"${EZQ_CHECKSUM}\"\n\necho \"* Resetting timestamp of generated files\"\ntouch -t $GIT_COMMIT_TIMESTAMP \"${EZQ_VERSION}\"\ntouch -t $GIT_COMMIT_TIMESTAMP \"${EZQ_CHECKSUM}\"\n\necho \"* Assembling ${EZQ_TAR}.gz\"\ntar cfz \"${TOP_DIR}/${EZQ_TAR}.gz\" \"${EZQ_NAME}\"\n\ncd \"${TOP_DIR}\"\nrm -rf release\n"
  },
  {
    "path": "dist/linux/io.github.ezQuake.appdata.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright 2019 Kevin Degeling -->\n\n<component type=\"desktop\">\n  <id>io.github.ezQuake</id>\n  <name>ezQuake</name>\n  <summary>a modern QuakeWorld client focused on competitive online play</summary>\n  <metadata_license>CC0-1.0</metadata_license>\n  <project_license>GPL-2.0</project_license>\n\n  <developer_name>ezQuake team</developer_name>\n  <update_contact>ezquake @ github</update_contact>\n\n  <url type=\"homepage\">https://ezquake.github.io/</url>\n  <url type=\"bugtracker\">https://github.com/ezQuake/ezquake-source</url>\n  <url type=\"help\">https://ezquake.github.io/manual.html</url>\n  <url type=\"contact\">https://www.quakeworld.nu/forum</url>\n\n  <launchable type=\"desktop-id\">io.github.ezQuake.desktop</launchable>\n\n  <description>\n    <p>\n      Welcome to the home of ezQuake, a modern QuakeWorld client focused on competitive online play. Combining the features of modern QuakeWorld® clients, ezQuake makes QuakeWorld® easier to start and play. The immortal first person shooter Quake® in a brand new skin with superb graphics and extremely fast gameplay.\n    </p>\n    <ul>\n      <li>Modern Graphics: Particle explosions, shaft beam, gunshots, nails, rocket and grenade trails, blood, and others, MD3 models, fog, water effects, killing spree messages, rain</li>\n      <li>Modern competitive gaming features: Fullbright skins, forcing team/enemy colors, advanced weapon handling, teamplay messages, auto game recording, automated screenshots and console logging</li>\n      <li>Graphics customization: Customize your HUD, colors of walls and liquids, turn superfluous graphics effects off, change world textures, crosshair, sky picture, console background, game font</li>\n      <li>Independent Physics: Get the smoothest experience possible without being limited by server or network settings</li>\n      <li>Integrated Server Browser: Easy searching and filtering of online servers</li>\n      <li>Enhanced demo/QTV playback: View recorded games from multiple points of view, watch action on radar, all player stats in a handy table, autotrack the strongest player</li>\n    </ul>\n    <p>\n     Commercial data files are required to run the supported games. These can be aquired though a multitude of sources. See the manual for more info on this.\n    </p>\n  </description>\n\n  <screenshots>\n    <screenshot type=\"default\">\n      <image>https://ezquake.github.io/screenshots/armor_battle.png</image>\n      <caption>Battle for armor</caption>\n    </screenshot>\n    <screenshot>\n      <image>https://ezquake.github.io/screenshots/bloom.jpg</image>\n      <caption>Bloom effect</caption>\n    </screenshot>\n    <screenshot>\n      <image>https://ezquake.github.io/screenshots/shambler_cutf_bluefog.jpg</image>\n      <caption>Special effects</caption>\n    </screenshot>\n  </screenshots>\n\n  <releases>\n    <release version=\"3.2.3\" date=\"2021-02-10\">\n    </release>\n    <release version=\"3.2.2\" date=\"2020-09-23\">\n    </release>\n    <release version=\"3.2.1\" date=\"2020-06-24\">\n    </release>\n    <release version=\"3.2.0\" date=\"2020-03-19\">\n    </release>\n    <release version=\"3.1.0\" date=\"2019-09-24\">\n    </release>\n  </releases>\n\n  <content_rating type=\"oars-1.1\">\n    <content_attribute id=\"violence-realistic\">moderate</content_attribute>\n    <content_attribute id=\"violence-bloodshed\">moderate</content_attribute>\n    <content_attribute id=\"violence-desecration\">moderate</content_attribute>\n    <!-- Full multiplayer functionality with voicechat support -->\n    <content_attribute id=\"social-chat\">intense</content_attribute>\n    <content_attribute id=\"social-audio\">intense</content_attribute>\n  </content_rating>\n</component>\n\n"
  },
  {
    "path": "dist/linux/io.github.ezQuake.desktop",
    "content": "[Desktop Entry]\nComment=A modern QuakeWorld client focused on competitive online play\nCategories=Game;Shooter;\nExec=ezquake.sh\nIcon=io.github.ezQuake\nName=ezQuake\nStartupNotify=true\nPrefersNonDefaultGPU=true\nTerminal=false\nType=Application\nKeywords=quake;first;person;shooter;multiplayer;\n"
  },
  {
    "path": "dist/macOS/MacOSXBundleInfo.plist.in",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleIconFile</key>\n\t<string>ezquake.icns</string>\n\t<key>CFBundleDocumentTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n\t\t\t\t<string>mvd</string>\n\t\t\t\t<string>qwd</string>\n\t\t\t\t<string>dem</string>\n\t\t\t</array>\n\t\t\t<key>CFBundleTypeIconFile</key>\n\t\t\t<string>ezquake.icns</string>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>Quake demo</string>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Viewer</string>\n\t\t</dict>\n\t</array>\n\t<key>CFBundleURLTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleURLName</key>\n\t\t\t<string>QW</string>\n\t\t\t<key>CFBundleURLSchemes</key>\n\t\t\t<array>\n\t\t\t\t<string>qw</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>NSHighResolutionCapable</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "dist/macOS/ezquake.entitlements.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>com.apple.security.app-sandbox</key>\n\t<true/>\n\t<key>com.apple.security.device.audio-input</key>\n\t<true/>\n\t<key>com.apple.security.network.client</key>\n\t<true/>\n\t<key>com.apple.security.network.server</key>\n\t<true/>\n\t<key>com.apple.security.files.user-selected.read-write</key>\n\t<true/>\n\t<key>com.apple.security.files.bookmarks.app-scope</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "dist/windows/ezQuake.rc.in",
    "content": "#include <winres.h>\n\nIDI_ICON1 ICON \"@EZQUAKE_RESOURCE_ICON@\"\n\nVS_VERSION_INFO VERSIONINFO\n FILEVERSION @EZQUAKE_RESOURCE_VERSION@\n BEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"040904b0\"\n        BEGIN\n            VALUE \"FileDescription\", \"@EZQUAKE_RESOURCE_DESCRIPTION@\\0\"\n            VALUE \"ProductName\", \"@EZQUAKE_RESOURCE_NAME@\\0\"\n            VALUE \"ProductVersion\", \"@EZQUAKE_RESOURCE_COMMIT@\\0\"\n            VALUE \"LegalCopyright\", \"@EZQUAKE_RESOURCE_AUTHOR@\\0\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x409, 1200\n    END\n END"
  },
  {
    "path": "help_cmdline_params.json",
    "content": "{\n  \"-allowmultiple\": {\n    \"description\": \"On Windows, launch multiple copies of ezQuake rather than re-using the existing instance\",\n    \"systems\": [\n      \"windows\"\n    ]\n  },\n  \"-basedir\": {\n    \"arguments\": \"<path>\",\n    \"description\": \"The \\\"base\\\" directory is the path to the directory holding the quake.exe and all game directories.\\n\\nThis can be overridden with the \\\"-basedir\\\" command line parm to allow code debugging in a different directory.\"\n  },\n  \"-bpp\": {\n    \"arguments\": \"<integer>\",\n    \"description\": \"Allows setting of 'r_colorbits' cvar during start-up\"\n  },\n  \"-cdaudio\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-cddev\": {\n    \"arguments\": \"<path>\",\n    \"description\": \"On Linux, specifies the cd device to use.  Must specify -cdaudio for this to have effect\",\n    \"remarks\": \"(is this only used in meson build?)\",\n    \"systems\": [\n      \"linux\"\n    ]\n  },\n  \"-cheats\": {\n    \"description\": \"enable cheats on local server (/noclip etc)\"\n  },\n  \"-clientport\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-conbufsize\": {\n    \"arguments\": \"<size-in-kb>\",\n    \"description\": \"set size of console buffer\",\n    \"remarks\": \"between 32KB & 4MB, default is 64KB\"\n  },\n  \"-condebug\": {\n    \"description\": \"log all console output to qw/qconsole.log\"\n  },\n  \"-conheight\": {\n    \"description\": \"set vid_conheight during startup\"\n  },\n  \"-conwidth\": {\n    \"description\": \"set vid_conwidth during startup\"\n  },\n  \"-data\": {\n    \"system-generated\": true\n  },\n  \"-democache\": {\n    \"arguments\": \"<size-in-kb>\",\n    \"description\": \"create memory buffer during startup, used instead of writing directly to disk when recording demos\",\n    \"remarks\": \"Minimum value 2048KB\"\n  },\n  \"-detailtrails\": {\n    \"description\": \"sets /gl_particle_fulldetail 1 during startup\"\n  },\n  \"-dev\": {\n    \"system-generated\": true\n  },\n  \"-display\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-enablelocalcommand\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-forceTextureReload\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-forcetexturereload\": {\n    \"system-generated\": true\n  },\n  \"-freq\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-game\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-gamma\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-gl-debug\": {\n    \"description\": \"enables OpenGL debug output.  Must be used in conjunction with -dev\"\n  },\n  \"-gl-forward-only-profile\": {\n    \"system-generated\": true\n  },\n  \"-gl_ext\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-glsl-renderer\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-heapsize\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-height\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-ip\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-maxtmu2\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-mem\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-minmemory\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-no-accel-visuals\": {\n    \"description\": \"Requests un-accelerated graphics (used in debugging to create OpenGL 1.1 context)\"\n  },\n  \"-no-triple-gl-buffer\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-no24bit\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-noatlas\": {\n    \"system-generated\": true\n  },\n  \"-noconinput\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-nodesktopres\": {\n    \"system-generated\": true\n  },\n  \"-nohome\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-nohwgamma\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-nohwtimer\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-noindphys\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-noinvlmaps\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-nolibjpeg\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-nolibpng\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-nomouse\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-nomtex\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-nonpot\": {\n    \"system-generated\": true\n  },\n  \"-nopriority\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-norjscripts\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-noscripts\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-nosound\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-nostdout\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-oldgamma\": {\n    \"system-generated\": true\n  },\n  \"-particles\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-port\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-progtype\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-r-debug\": {\n    \"system-generated\": true\n  },\n  \"-r-no-amd-fix\": {\n    \"system-generated\": true\n  },\n  \"-r-nocallback\": {\n    \"system-generated\": true\n  },\n  \"-r-nomultibind\": {\n    \"system-generated\": true\n  },\n  \"-r-novao\": {\n    \"system-generated\": true\n  },\n  \"-r-trace\": {\n    \"system-generated\": true\n  },\n  \"-r-verify\": {\n    \"system-generated\": true\n  },\n  \"-r-dump-shaders\": {\n    \"description\": \"Write expanded shader source to qw/shaders/<name>.<type>\"\n  },\n  \"-ruleset\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-showliberrors\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-startwindowed\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-userdir\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-width\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  },\n  \"-window\": {\n    \"flags\": [\n      \"incomplete\"\n    ]\n  }\n}\n"
  },
  {
    "path": "help_commands.json",
    "content": "{\n  \"+attack\": {\n    \"description\": \"When active the player will fire the weapon he is currently holding.\\nThis is the primary command used to make the player fire the gun.\\nFor as long as the key that is bound to this command is held down and this command is active the player will keep on firing the gun.\"\n  },\n  \"+attack2\": {\n    \"description\": \"Secondary attack button.\"\n  },\n  \"+back\": {\n    \"description\": \"When active the player will move backwards.\"\n  },\n  \"+cl_wp_stats\": {\n    \"system-generated\": true\n  },\n  \"+fire\": {\n    \"system-generated\": true\n  },\n  \"+fire_ar\": {\n    \"system-generated\": true\n  },\n  \"+forward\": {\n    \"description\": \"When active the player will move forward.\"\n  },\n  \"+jump\": {\n    \"description\": \"When active the player will do a single jump. The next jump won't be performed until \\\"-jump\\\" has been issued.\"\n  },\n  \"+klook\": {\n    \"description\": \"When active, \\\"+forward\\\" and \\\"+back\\\" become \\\"+lookup\\\" and \\\"+lookdown\\\"  respectively.\\nThis command is useful if the player needs to look at objects which are above or below him.\"\n  },\n  \"+left\": {\n    \"description\": \"When active the player will turn left.\"\n  },\n  \"+lookdown\": {\n    \"description\": \"When active the player will look down.\"\n  },\n  \"+lookup\": {\n    \"description\": \"When active the player will look up.\"\n  },\n  \"+mlook\": {\n    \"description\": \"When active moving the mouse or joystick forwards and backwards performs \\\"+lookup\\\" and \\\"+lookdown\\\" respectively.\\nThis command is very useful if the player needs to look at objects which are above or below him.\\nMost players execute this command and never remove it. This way they can use the keyboard to move the player forward and back and strafe left and right, while using the mouse to turn the player left and right and to make him look up and down.\\nIn order to have this command set permanently you will have to create a file called autoexec.cfg in the qw/ directory and put in the line \\\"+mlook\\\" into that file.\\nBy doing this the game will automatically execute the autoexec.cfg file and it will also active that command.\\nAlmost every player uses this command nowadays, because the combination of using mouse and keyboard is widely considered the best.\"\n  },\n  \"+movedown\": {\n    \"description\": \"When active the player will swim down when in the water.\"\n  },\n  \"+moveleft\": {\n    \"description\": \"When active the player will strafe left.\"\n  },\n  \"+moveright\": {\n    \"description\": \"When active the player will strafe right.\"\n  },\n  \"+moveup\": {\n    \"description\": \"When active the player will swim up when in the water.\"\n  },\n  \"+qtv_delay\": {\n    \"system-generated\": true\n  },\n  \"+right\": {\n    \"description\": \"When active the player will turn right.\"\n  },\n  \"+showscores\": {\n    \"description\": \"Display scoreboard.\"\n  },\n  \"+showteamscores\": {\n    \"description\": \"Display team scoreboard.\"\n  },\n  \"+speed\": {\n    \"description\": \"When active the player will run.\"\n  },\n  \"+strafe\": {\n    \"description\": \"When active, \\\"+left\\\" and \\\"+right\\\" function like \\\"+moveleft\\\" and \\\"+moveright\\\", strafing in that direction.\"\n  },\n  \"+use\": {\n    \"description\": \"When used it will activate objects in the game that have been designed to react at \\\"+use\\\"\"\n  },\n  \"+voip\": {\n    \"system-generated\": true\n  },\n  \"+zoom\": {\n    \"system-generated\": true\n  },\n  \"-attack\": {\n    \"description\": \"When used the player will stop firing the gun if \\\"+attack\\\" is active.\"\n  },\n  \"-attack2\": {\n    \"description\": \"Secondary attack button.\"\n  },\n  \"-back\": {\n    \"description\": \"When used the player will stop moving back if \\\"+back\\\" is active.\"\n  },\n  \"-cl_wp_stats\": {\n    \"system-generated\": true\n  },\n  \"-fire\": {\n    \"system-generated\": true\n  },\n  \"-fire_ar\": {\n    \"system-generated\": true\n  },\n  \"-forward\": {\n    \"description\": \"When used the player will stop moving forward if \\\"+forward\\\" is active.\"\n  },\n  \"-jump\": {\n    \"description\": \"When used the player will stop jumping if \\\"+jump\\\" is active.\"\n  },\n  \"-klook\": {\n    \"description\": \"When used the forward and back keys will stop making the player look up and down if \\\"+klook\\\" is active.\"\n  },\n  \"-left\": {\n    \"description\": \"When used the player will stop turning left if \\\"+left\\\" is active.\"\n  },\n  \"-lookdown\": {\n    \"description\": \"When used the player will stop looking down if \\\"+lookdown\\\" is active.\"\n  },\n  \"-lookup\": {\n    \"description\": \"When used the player will stop looking up if \\\"+lookup\\\" is active.\"\n  },\n  \"-mlook\": {\n    \"description\": \"When used the mouse forward and back movement will stop making the player look up and down if \\\"+mlook\\\" is active.\"\n  },\n  \"-movedown\": {\n    \"description\": \"When used the player will stop moving down if \\\"+movedown\\\" is active.\"\n  },\n  \"-moveleft\": {\n    \"description\": \"When used the player will stop moving left if \\\"+moveleft\\\" is active.\"\n  },\n  \"-moveright\": {\n    \"description\": \"When used the player will stop moving right if \\\"+moveright\\\" is active.\"\n  },\n  \"-moveup\": {\n    \"description\": \"When used the player will stop moving up if \\\"+moveup\\\" is active.\"\n  },\n  \"-qtv_delay\": {\n    \"system-generated\": true\n  },\n  \"-right\": {\n    \"description\": \"When used the player will stop turning right if \\\"+right\\\" is active.\"\n  },\n  \"-showscores\": {\n    \"description\": \"When used the score screen will disappear if +showscores is active.\"\n  },\n  \"-showteamscores\": {\n    \"description\": \"When used the score screen will disappear if +showteamscores is active.\"\n  },\n  \"-speed\": {\n    \"description\": \"When used the player will walk.\"\n  },\n  \"-strafe\": {\n    \"description\": \"When used the turn left and turn right keys will once again perform their original functions.\"\n  },\n  \"-use\": {\n    \"description\": \"When used it will stop activating objects in the game that have been designed to react at \\\"+use\\\".\"\n  },\n  \"-voip\": {\n    \"system-generated\": true\n  },\n  \"-zoom\": {\n    \"system-generated\": true\n  },\n  \"acc_block\": {\n    \"system-generated\": true\n  },\n  \"acc_create\": {\n    \"system-generated\": true\n  },\n  \"acc_list\": {\n    \"system-generated\": true\n  },\n  \"acc_remove\": {\n    \"system-generated\": true\n  },\n  \"acc_unblock\": {\n    \"system-generated\": true\n  },\n  \"addip\": {\n    \"description\": \"Add a single IP or a domain of IPs to the IP list of the server.\\nVery useful for banning people or for specifying which IPs only have access to the server.\\n\\nExamples:\\naddip 123.123.123.123\\naddip 123.123.123\",\n    \"syntax\": \"<ip>\"\n  },\n  \"addloc\": {\n    \"arguments\": [\n      {\n        \"description\": \"The name of the loc.\",\n        \"name\": \"locname\"\n      }\n    ],\n    \"description\": \"Adds a new loc with the specified name at current location.\",\n    \"syntax\": \"\\\"locname\\\"\"\n  },\n  \"addserver\": {\n    \"description\": \"Server Browser: This allows you to add a server to the UNBOUND source.\\nThis can be used to quickly bookmark servers.\"\n  },\n  \"alias\": {\n    \"description\": \"Used to create a reference to a command or list of commands.\\nWhen used without parameters, displays all current aliases.\",\n    \"remarks\": \"Enclose multiple commands within quotes and separate each command with a semi-colon.\"\n  },\n  \"alias_in\": {\n    \"arguments\": [\n      {\n        \"description\": \"Alias to be changed\",\n        \"name\": \"alias\"\n      },\n      {\n        \"description\": \"Variable whose value is inserted into alias\",\n        \"name\": \"variable\"\n      },\n      {\n        \"description\": \"Bitmask:\\n0 - insert from left\\n1 - from right side\\n2 - check in advance whether a string being inserted already exists in alias\\n4 - print an error message if the inserted string is already present in the alias\\n8 - automatically create an alias if it doesn't exist yet\",\n        \"name\": \"options\"\n      }\n    ],\n    \"description\": \"Inserts contents of variable into alias.\",\n    \"syntax\": \"<alias> <variable> [<options>]\"\n  },\n  \"alias_out\": {\n    \"system-generated\": true\n  },\n  \"aliasedit\": {\n    \"description\": \"Allows you to edit your alias in console manually.\",\n    \"syntax\": \"<alias>\"\n  },\n  \"aliaslist\": {\n    \"arguments\": [\n      {\n        \"description\": \"Prints only [regexp] matching aliases\",\n        \"name\": \"[regexp]\"\n      }\n    ],\n    \"description\": \"Prints all aliases.\",\n    \"syntax\": \"[regexp]\"\n  },\n  \"align\": {\n    \"system-generated\": true\n  },\n  \"allskins\": {\n    \"description\": \"Downloads all skins that is currently in use.\\nUseful for refreshing skins without exiting the level.\"\n  },\n  \"authenticate\": {\n    \"system-generated\": true\n  },\n  \"autotrack\": {\n    \"description\": \"Toggles auto-tracking.\\nAuto-tracking switches views for you when you are a spectator or when you are watching a demo or a broadcasted QTV match.\\nIt chooses the best available autotrack - if you are spectator, looks for server-side command autotrack, if you are watching a demo or QTV stream, turns on both demo_autotrack and mvd_autotrack, mvd_autotrack will get turned off as soon as demo_autotrack data are found.\\nAs a last resort if all previous autotrack are not available, cl_hightrack will be used.\"\n  },\n  \"bar_armor\": {\n    \"description\": \"HUD element that displays a bar representing your amount of armor.\"\n  },\n  \"bar_health\": {\n    \"description\": \"HUD element that displays a bar representing your amount of health.\"\n  },\n  \"batteryinfo\": {\n    \"system-generated\": true\n  },\n  \"bf\": {\n    \"description\": \"This command shows a background screen flash that is the same one that is produced when the player picked up an item in the game.\\nThis command basically serves no useful function except when people want to use it in scripts to give the user some visual feedback when an aliases is used for example.\"\n  },\n  \"bind\": {\n    \"description\": \"This command binds one or several commands to a key.\\nTo bind multiple commands to a key, enclose the commands in double-quotes (\\\") and separate them with semicolons (;).\"\n  },\n  \"bindedit\": {\n    \"description\": \"Allows you to edit your bind in the console.\",\n    \"syntax\": \"<key>\"\n  },\n  \"bindlist\": {\n    \"description\": \"Prints all binds.\"\n  },\n  \"calc_fov\": {\n    \"arguments\": [\n      {\n        \"description\": \"The old wide aspect FOV used in v2.x\",\n        \"name\": \"old_fov\"\n      }\n    ],\n    \"description\": \"Converts (ezq2) wide aspect FOV to new FOV.\"\n  },\n  \"calendar\": {\n    \"description\": \"Same as \\\"date\\\" but also shows a small calendar of the month. Nice :)\"\n  },\n  \"cam_angles\": {\n    \"arguments\": [\n      {\n        \"description\": \"\",\n        \"name\": \"pitch\"\n      },\n      {\n        \"description\": \"\",\n        \"name\": \"yaw\"\n      }\n    ],\n    \"description\": \"Set new camera angles.\",\n    \"syntax\": \"<pitch> <yaw> or cam_angles \\\"pitch yaw\\\"\"\n  },\n  \"cam_pos\": {\n    \"arguments\": [\n      {\n        \"description\": \"X coordinate\",\n        \"name\": \"x\"\n      },\n      {\n        \"description\": \"Y coordinate\",\n        \"name\": \"y\"\n      },\n      {\n        \"description\": \"Z coordinate\",\n        \"name\": \"z\"\n      }\n    ],\n    \"description\": \"Set new camera position.\",\n    \"syntax\": \"<x> <y> <z> or cam_pos \\\"x y z\"\n  },\n  \"cancel\": {\n    \"system-generated\": true\n  },\n  \"cd\": {\n    \"description\": \"cd play 5 plays cd track #5\",\n    \"remarks\": \"You need -cdaudio to use this command.\"\n  },\n  \"centerview\": {\n    \"description\": \"Centers the player's view ahead after +lookup or +lookdown.\"\n  },\n  \"cfg_load\": {\n    \"description\": \"This will do a cfg_reset and then execute filename.cfg (ezquake/configs).\",\n    \"syntax\": \"<filename>\"\n  },\n  \"cfg_reset\": {\n    \"description\": \"This command will unbind all keys, delete all aliases, msg_triggers, reset all plus commands, teamplay settings and reset all variables.\\nUser made variables (created with set/seta) are deleted.\\nAfter resetting all the above, it executes default.cfg and then autoexec.cfg.\"\n  },\n  \"cfg_save\": {\n    \"description\": \"This command will dump all aliases, bindings, plus commands, msg_triggers, teamplay settings and variables to filename.cfg .\\nUser made variables (created with set/seta) are saved as well.\",\n    \"remarks\": \"Configs saved with cfg_save are saved in quake/ezquake/configs/*.cfg\",\n    \"syntax\": \"<filename>\"\n  },\n  \"check_maps\": {\n    \"system-generated\": true\n  },\n  \"cl_messages\": {\n    \"description\": \"Prints amount and size of messages sent from server to ezQuake client.\"\n  },\n  \"clear\": {\n    \"description\": \"This command clears the console screen of any text.\"\n  },\n  \"clearlocs\": {\n    \"description\": \"Clear all currently loaded locs.\"\n  },\n  \"clipboard\": {\n    \"description\": \"Copies all the following arguments to the system clipboard\"\n  },\n  \"cmd\": {\n    \"description\": \"Sends a command directly to the server.\"\n  },\n  \"cmdlist\": {\n    \"description\": \"Prints a list of all available commands into the console.\"\n  },\n  \"cmdlist_re\": {\n    \"description\": \"This command same as cmdlist, but supports (perl) regexp matching.\"\n  },\n  \"color\": {\n    \"description\": \"This command sets the color for the player's shirt and pants.\",\n    \"remarks\": \"If only the shirt color is given, the pant color will match.\"\n  },\n  \"connect\": {\n    \"arguments\": [\n      {\n        \"description\": \"IP address of a QuakeWorld server.\",\n        \"name\": \"address\"\n      }\n    ],\n    \"description\": \"Connects your client to a QuakeWorld server.\",\n    \"syntax\": \"<address>\"\n  },\n  \"connectbr\": {\n    \"description\": \"Connects to given server via fastest available path (ping-wise).\"\n  },\n  \"cuff\": {\n    \"system-generated\": true\n  },\n  \"cvar_in\": {\n    \"system-generated\": true\n  },\n  \"cvar_out\": {\n    \"system-generated\": true\n  },\n  \"cvar_reset\": {\n    \"description\": \"Resets the cvar to default.\\n\\nExample:\\ncvar_reset topcolor\\n - sets topcolor to default.\",\n    \"syntax\": \"<cvar>\"\n  },\n  \"cvar_reset_re\": {\n    \"description\": \"Resets cvar(s) matching the regex to default.\\n\\nExample:\\ncvar_reset ^gl_.*\\n- resets all gl_ settings to default values.\",\n    \"syntax\": \"[regex]\"\n  },\n  \"cvaredit\": {\n    \"system-generated\": true\n  },\n  \"cvarlist\": {\n    \"description\": \"Print cvars.\"\n  },\n  \"cvarlist_re\": {\n    \"description\": \"This command same as cvarlist, but supports (perl) regexp matching.\"\n  },\n  \"date\": {\n    \"description\": \"Shows current time, date, month and year.\"\n  },\n  \"demo_capture\": {\n    \"arguments\": [\n      {\n        \"description\": \"Tells the client to start capturing.\",\n        \"name\": \"start\"\n      },\n      {\n        \"description\": \"Duration of the capture, in seconds.\\nCan be float value. Must be positive. Required argument.\",\n        \"name\": \"time\"\n      },\n      {\n        \"description\": \"An .avi file is saved instead of screenshots. See demo_capture_codec.\",\n        \"name\": \"avifile\"\n      },\n      {\n        \"description\": \"Stop the capture manually before <time>.\\nExample: demo_capture stop\",\n        \"name\": \"stop\"\n      }\n    ],\n    \"description\": \"Captures a series of screenshots or an .avi file.\",\n    \"syntax\": \"<start> <time> [avifile] | <stop>\"\n  },\n  \"demo_controls\": {\n    \"system-generated\": true\n  },\n  \"demo_jump\": {\n    \"description\": \"Jumps playback to a point in time.\\n\\nExamples:\\ndemo_jump 120\\n - Jump playback to 120 seconds from the start of the demo.\\ndemo_jump 4:30\\n - Jump playback to 4 minutes and 30 seconds from the start of the demo.\",\n    \"syntax\": \"[+|-][m:]<s>\"\n  },\n  \"demo_jump_end\": {\n    \"system-generated\": true\n  },\n  \"demo_jump_mark\": {\n    \"system-generated\": true\n  },\n  \"demo_jump_status\": {\n    \"arguments\": [\n      {\n        \"description\": \"For example h<1 +rl or +lg\",\n        \"name\": \"condition\"\n      }\n    ],\n    \"description\": \"Fast-forward in the demo playback until certain condition holds.\",\n    \"syntax\": \"<condition>\"\n  },\n  \"demo_playlist_clear\": {\n    \"description\": \"Clears the demo playlist.\"\n  },\n  \"demo_playlist_next\": {\n    \"description\": \"Goes to the next demo in the playlist.\"\n  },\n  \"demo_playlist_prev\": {\n    \"description\": \"Goes to the previous demo in the playlist.\"\n  },\n  \"demo_playlist_stop\": {\n    \"description\": \"Stops the demo playlist playback.\"\n  },\n  \"demo_setspeed\": {\n    \"description\": \"You can vary the speed of demo playback with the 'demo_setspeed' command.\\n'demo_setspeed x' sets the playback speed to x% of normal speed so that 'demo_setspeed 50' is half speed and 'demo_setspeed 300' gives you triple speed.\",\n    \"syntax\": \"[default: 100]\"\n  },\n  \"describe\": {\n    \"description\": \"Prints manual info about given variable or command into the console.\",\n    \"syntax\": \"<variable or command name>\"\n  },\n  \"dev_cache_print\": {\n    \"system-generated\": true\n  },\n  \"dev_cache_report\": {\n    \"system-generated\": true\n  },\n  \"dev_dump_defaults\": {\n    \"system-generated\": true\n  },\n  \"dev_gfxbenchmarklightmaps\": {\n    \"system-generated\": true\n  },\n  \"dev_gfxtrace\": {\n    \"system-generated\": true\n  },\n  \"dev_help_issues\": {\n    \"system-generated\": true\n  },\n  \"dev_help_verify_config\": {\n    \"system-generated\": true\n  },\n  \"dev_hunk_print\": {\n    \"system-generated\": true\n  },\n  \"dev_physicsnormalsave\": {\n    \"system-generated\": true\n  },\n  \"dev_physicsnormalset\": {\n    \"system-generated\": true\n  },\n  \"dev_physicsnormalshow\": {\n    \"system-generated\": true\n  },\n  \"dev_pointfile\": {\n    \"description\": \"The pointfile command will load a .pts file and give a dotted line indicating where the leak(s) are on the level.\",\n    \"remarks\": \"If a leak exists in the level, qbsp generates a non-zero .pts file in the maps directory.\",\n    \"syntax\": \"<filename>\"\n  },\n  \"devmap\": {\n    \"description\": \"Try it in cheats mode, start a map (devmap dm6) and type fly.\"\n  },\n  \"dir\": {\n    \"system-generated\": true\n  },\n  \"disconnect\": {\n    \"description\": \"This command will disconnect you from the server/demo/proxy you are currently connected to.\"\n  },\n  \"dns\": {\n    \"description\": \"Performs DNS lookups and reverse lookups.\",\n    \"syntax\": \"<address>\"\n  },\n  \"download\": {\n    \"description\": \"Manually download a quake file from the server.\\n\\nExample:\\ndownload skins/foo.pcx\"\n  },\n  \"easyrecord\": {\n    \"description\": \"This start recording demo and rename it like you have put with match_* settings.\"\n  },\n  \"echo\": {\n    \"description\": \"This command will print text to your local console.\",\n    \"syntax\": \"<text>\"\n  },\n  \"edict\": {\n    \"description\": \"Reports information on a given edict in the game.\"\n  },\n  \"edictcount\": {\n    \"description\": \"Displays summary information on the edicts in the game.\"\n  },\n  \"edicts\": {\n    \"description\": \"Displays information on all edicts in the game.\"\n  },\n  \"enemycolor\": {\n    \"description\": \"This will override enemy color.\\n\\nExample:\\nenemycolor 12 13\",\n    \"remarks\": \"If only the shirt color is given, the pant color will match.\"\n  },\n  \"eval\": {\n    \"arguments\": [\n      {\n        \"description\": \"Arithmetic expression, can also contain strings\",\n        \"name\": \"expression\"\n      }\n    ],\n    \"description\": \"Evaluates given expression and prints the result into the console.\",\n    \"syntax\": \"<expression>\"\n  },\n  \"exec\": {\n    \"description\": \"Executes a config file from \\\\qw, \\\\id1 or \\\\ezquake.\"\n  },\n  \"f_modified\": {\n    \"description\": \"All the usual dm models, sounds, palettes etc are included in the check.\\nIn Team Fortress the TF flag, dispensers and sentry guns are also checked. There is also an f_modified command which will print your f_modified response.\",\n    \"remarks\": \"You need to install security files.\"\n  },\n  \"f_ruleset\": {\n    \"system-generated\": true\n  },\n  \"f_server\": {\n    \"description\": \"Prints proxies you are using.\"\n  },\n  \"filter\": {\n    \"description\": \"Message filtering system. Only team messages are filtered.\\nUse filter with no parameters to list current filters and filter clear to remove all filters.\\n\\nExample:\\nfilter #a  say_team i can see this message #a.\\nsay_team i can't see this message #d.\"\n  },\n  \"find\": {\n    \"description\": \"Lists all the players on all servers. Based on QTV.\"\n  },\n  \"find_and_follow\": {\n    \"description\": \"Find a player by name and connect to the same server. Based on QTV.\"\n  },\n  \"find_update\": {\n    \"description\": \"Update the database of the current players on all servers. Based on QTV.\"\n  },\n  \"floodprot\": {\n    \"arguments\": [\n      {\n        \"description\": \"Number of allowed messages per client in given time period.\",\n        \"name\": \"messages\"\n      },\n      {\n        \"description\": \"Time period in which more than <messages> cannot be sent by a client.\",\n        \"name\": \"seconds\"\n      },\n      {\n        \"description\": \"Time the flooding player will be muted for.\",\n        \"name\": \"silence\"\n      }\n    ],\n    \"description\": \"Sets flood protection parameters.\",\n    \"syntax\": \"<messages> <seconds> <silence>\"\n  },\n  \"floodprotmsg\": {\n    \"system-generated\": true\n  },\n  \"flush\": {\n    \"description\": \"This command will clear the current game cache.\\nIt is usually used by developers to flush the memory of all game information and objects to test if the mechanism which handles the loading of the necessary files into memory works properly.\\nSometimes the game cache can become filled with unnecessary information and may need to be flushed manually. This is usually not necessary since the game automatically flushes all the data between every map.\"\n  },\n  \"fly\": {\n    \"description\": \"You can fly around the map with flymode on.\",\n    \"remarks\": \"Needs cheat support by server.\"\n  },\n  \"fontlist\": {\n    \"description\": \"Lists TrueType fonts available on the system.\"\n  },\n  \"fontload\": {\n    \"description\": \"Loads specified TrueType font for use where _proportional is set.\"\n  },\n  \"force_centerview\": {\n    \"description\": \"This command centers the player's screen.\\nIt was created because the original \\\"centerview\\\" command did not work when \\\"+mlook\\\" was enabled.\\nThis command will center the screen in any mode no matter commands are active.\"\n  },\n  \"fraglogfile\": {\n    \"description\": \"Enables logging of kills to a file.\\nUseful for external frag polling programs.\\nThe file name is frag_##.log\"\n  },\n  \"fs_diff\": {\n    \"system-generated\": true\n  },\n  \"fs_dir\": {\n    \"system-generated\": true\n  },\n  \"fs_loadpak\": {\n    \"system-generated\": true\n  },\n  \"fs_locate\": {\n    \"system-generated\": true\n  },\n  \"fs_path\": {\n    \"system-generated\": true\n  },\n  \"fs_removepak\": {\n    \"system-generated\": true\n  },\n  \"fs_restart\": {\n    \"system-generated\": true\n  },\n  \"fs_search\": {\n    \"description\": \"Search the filesystem cache by suffix.\"\n  },\n  \"fullinfo\": {\n    \"description\": \"Used by QuakeSpy and Qlist to set setinfo variables.\\n\\nExample:\\nfullinfo \\\"\\\\quote\\\\I am the only Lamer!\\\\\\\"\",\n    \"remarks\": \"Use the setinfo command to see the output.\"\n  },\n  \"gamedir\": {\n    \"description\": \"Specifies the directory where the QWPROGS.DAT file is found and other additional files such as maps, models, sound, and skins for Quake modifications.\",\n    \"remarks\": \"This command can be specified while a game is in progress, after the current map ends this command will take effect.\"\n  },\n  \"gamma\": {\n    \"description\": \"You can edit brightness.\",\n    \"remarks\": \"This is a shortcut for sw_gamma in vga and gl_gamma in gl.\"\n  },\n  \"give\": {\n    \"description\": \"Give user a certain amount of an item.\\nItems:\\n1 - Axe\\n2 - Shotgun\\n3 - Double-Barrelled Shotgun\\n4 - Nailgun\\n5 - Super Nailgun\\n6 - Grenade Launcher\\n7 - Rocket Launcher\\n8 - ThunderBolt\\nC - Cells\\nH - Health\\nN - Nails\\nR - Rockets\\nS - Shells\\n\\nExamples:\\ngive 1234 R 99\\n - gives 99 rockets to user 1234\\ngive 1234 7\\n - gives the rocket launcher to user 1234\",\n    \"remarks\": \"The -cheats parameter must be used to launch the server to use the give command. Also the key and value *cheats ON will be displayed in the serverinfo information.\"\n  },\n  \"gl_checkmodels\": {\n    \"description\": \"Not well implemented yet. Quickly looks at the pmodel and emodel listed in every player's infokey and reports anything unusual it finds.\\nBasically it saves you having to type \\\"users. user x\\\" and then comparing the models for everyone.\"\n  },\n  \"gl_inferno\": {\n    \"description\": \"Clientside (no one else can see it) hard-striking rocket, serves well for your entertainment.\"\n  },\n  \"gl_setmode\": {\n    \"description\": \"Quickly sets many variables to fit pre-defined scheme.\\nTry using \\\"newtrails\\\" or \\\"vultwah\\\".\",\n    \"syntax\": \"<modename>\"\n  },\n  \"god\": {\n    \"description\": \"You are immortal with god mode on.\",\n    \"remarks\": \"Needs cheats support by server.\"\n  },\n  \"hash\": {\n    \"system-generated\": true\n  },\n  \"heartbeat\": {\n    \"description\": \"Forces a heartbeat to be sent to the master server.\\nA heartbeat informs the master server of the server's IP address thus making sure that the master server knows that the server is still alive.\"\n  },\n  \"help\": {\n    \"description\": \"Enters manual pages. Use arrows, [Page Down], [Page Up], [Tab] and [Enter] for navigation.\"\n  },\n  \"hide\": {\n    \"system-generated\": true\n  },\n  \"hud262_add\": {\n    \"arguments\": [\n      {\n        \"description\": \"Can be cvar or str\",\n        \"name\": \"type\"\n      },\n      {\n        \"description\": \"String or cvarname\",\n        \"name\": \"param\"\n      }\n    ],\n    \"description\": \"Creates or changes a HUD element with hud_name.\\nThe following types of HUD elements are available:\\ncvar - a value of a variable is displayed; in this case param must be a name of this variable.\\nstr - a string defined by param is displayed\\nCvar and %macros expansion is performed every time element is shown.\",\n    \"syntax\": \"<hud_name> <type> <param>\"\n  },\n  \"hud262_alpha\": {\n    \"arguments\": [\n      {\n        \"description\": \"HUD element's name\",\n        \"name\": \"name\"\n      },\n      {\n        \"description\": \"From 0 up to 1 (0 - invisible, 1 - opaque)\",\n        \"name\": \"value\"\n      }\n    ],\n    \"description\": \"Sets a transparency for strings-HUD element.\",\n    \"syntax\": \"<name> <value>\"\n  },\n  \"hud262_bg\": {\n    \"arguments\": [\n      {\n        \"description\": \"HUD element's name\",\n        \"name\": \"hud_name\"\n      },\n      {\n        \"description\": \"color 111 - yellow, 79 - red, etc\",\n        \"name\": \"bg_color\"\n      }\n    ],\n    \"description\": \"Defines a color of the HUD element background.\\n0 - transparent (default).\",\n    \"syntax\": \"<hud_name> <bg_color>\"\n  },\n  \"hud262_blink\": {\n    \"arguments\": [\n      {\n        \"description\": \"HUD element's name\",\n        \"name\": \"name\"\n      },\n      {\n        \"description\": \"Blinking period in milliseconds\",\n        \"name\": \"period\"\n      },\n      {\n        \"description\": \"Defines what exactly should blink:\\n0 - nothing\\n1 - text only\\n2 - background only\\n3 - text and background\",\n        \"name\": \"mask\"\n      }\n    ],\n    \"description\": \"Allows to make blinking strings-HUD elements.\",\n    \"syntax\": \"<name> <period> <mask>\"\n  },\n  \"hud262_bringtofront\": {\n    \"arguments\": [\n      {\n        \"description\": \"HUD element's name\",\n        \"name\": \"name\"\n      }\n    ],\n    \"description\": \"Transfers all HUD elements created after hud_name element (including) to the end of drawing list, that in short means that they will be displayed above all other elements which could be present on that place already.\",\n    \"syntax\": \"<name>\"\n  },\n  \"hud262_disable\": {\n    \"arguments\": [\n      {\n        \"description\": \"HUD element's name\",\n        \"name\": \"hud_name\"\n      }\n    ],\n    \"description\": \"Prohibits to display one or more HUD elements.\",\n    \"syntax\": \"<hud_name> [hud_name2...]\"\n  },\n  \"hud262_enable\": {\n    \"arguments\": [\n      {\n        \"description\": \"HUD element's name\",\n        \"name\": \"hud_name\"\n      }\n    ],\n    \"description\": \"Allows to display one or more HUD elements.\",\n    \"syntax\": \"<hud_name> [hud_name2...]\"\n  },\n  \"hud262_list\": {\n    \"arguments\": [\n      {\n        \"description\": \"Print only [regexp] matching HUDs\",\n        \"name\": \"regexp\"\n      }\n    ],\n    \"description\": \"Prints a list of strings-HUD elements.\",\n    \"syntax\": \"[regexp]\"\n  },\n  \"hud262_move\": {\n    \"arguments\": [\n      {\n        \"description\": \"HUD element's name\",\n        \"name\": \"hud_name\"\n      },\n      {\n        \"description\": \"Horizontal shift\",\n        \"name\": \"dx\"\n      },\n      {\n        \"description\": \"Vertical shift\",\n        \"name\": \"dy\"\n      }\n    ],\n    \"description\": \"Moves a HUD element.\\ndx and dy deviations are measured in symbols.\",\n    \"syntax\": \"<hud_name> <dx> <dy>\"\n  },\n  \"hud262_position\": {\n    \"arguments\": [\n      {\n        \"description\": \"Name of your HUD element\",\n        \"name\": \"hud_name\"\n      },\n      {\n        \"description\": \"Position:\\n1 - upper left corner\\n2 - upper right corner\\n3 - lower right corner\\n4 - lower left corner\\n5 - upper central position\\n6 - lower central position\",\n        \"name\": \"pos\"\n      },\n      {\n        \"description\": \"Horizontal offset\",\n        \"name\": \"x\"\n      },\n      {\n        \"description\": \"Vertical offset\",\n        \"name\": \"y\"\n      }\n    ],\n    \"description\": \"Indicates position of a HUD element on the screen.\",\n    \"syntax\": \"<hud_name> <pos> <x> <y>\"\n  },\n  \"hud262_remove\": {\n    \"arguments\": [\n      {\n        \"description\": \"HUD element's name\",\n        \"name\": \"hud_name\"\n      }\n    ],\n    \"description\": \"Kills a HUD element.\",\n    \"syntax\": \"<hud_name>\"\n  },\n  \"hud262_width\": {\n    \"arguments\": [\n      {\n        \"description\": \"HUD element's name\",\n        \"name\": \"hud_name\"\n      },\n      {\n        \"description\": \"The range of width is 0-128, the value 0 (default) cancels the width forcing.\",\n        \"name\": \"width\"\n      }\n    ],\n    \"description\": \"Forces a HUD element width and cuts undesired space or adds it when needed.\",\n    \"syntax\": \"<hud_name> <width>\"\n  },\n  \"hud_editor\": {\n    \"description\": \"Toggles the HUD editor on or off.\"\n  },\n  \"hud_export\": {\n    \"arguments\": [\n      {\n        \"description\": \"Name (with optional path) of saved file. If .cfg extension is not present it will be automatically added.\",\n        \"name\": \"filename\"\n      }\n    ],\n    \"description\": \"Saves setup of your HUD (scr_newHUD 1) into a separate .cfg file.\",\n    \"syntax\": \"<filename>\"\n  },\n  \"hud_fps_min_reset\": {\n    \"system-generated\": true\n  },\n  \"hud_recalculate\": {\n    \"description\": \"Refresh the positions of your HUD elements.\"\n  },\n  \"hunk_print\": {\n    \"system-generated\": true\n  },\n  \"if\": {\n    \"arguments\": [\n      {\n        \"description\": \"A string where you usually use your own text, macros, and values of variables (like $volume).\",\n        \"name\": \"expr1\"\n      },\n      {\n        \"description\": \"==, =, !=, <>, >, <, >=, <=, isin, !isin\\nWhere 'isin' means \\\"expr1 is a substring of expr2\\\" and '!isin' is a negation of this.\\nThe others are standard mathematical comparison.\",\n        \"name\": \"operator\"\n      },\n      {\n        \"description\": \"See expr1\",\n        \"name\": \"expr2\"\n      },\n      {\n        \"description\": \"Will get executed if the binary operation succeeds.\",\n        \"name\": \"cmd1\"\n      },\n      {\n        \"description\": \"Will get executed if the binary operation fails.\",\n        \"name\": \"cmd2\"\n      }\n    ],\n    \"description\": \"Condition clause.\",\n    \"syntax\": \"<expr1> <operator> <expr2> <cmd1> [else <cmd1>]\"\n  },\n  \"if_exists\": {\n    \"description\": \"If an object <name> of a type <type> exists, a command <cmd1> will be issued, or a command <cmd2> if such object could not be found.\\nThe type of the object can be either cvar, alias, trigger or HUD.\",\n    \"syntax\": \"<type> <name>  <cmd1> [<cmd2>]\"\n  },\n  \"ignore\": {\n    \"description\": \"You can give ignore either a player's name (name completion is useful for this) or a user ID (ignore <name|userid>).\\nIgnore without any command line parameters displays your ignore list.\",\n    \"syntax\": \"<name|userid>\"\n  },\n  \"ignore_id\": {\n    \"description\": \"Identical to ignore, except it only accepts user IDs (only useful if there is a player whose name is the user ID of someone you want to ignore).\",\n    \"syntax\": \"<userid>\"\n  },\n  \"ignore_team\": {\n    \"description\": \"Ignores a team instead of players.\\n\\nExample:\\nignore_team nine\\n - ignores whole clan nine.\"\n  },\n  \"ignore_voip\": {\n    \"system-generated\": true\n  },\n  \"ignorelist\": {\n    \"description\": \"Prints ignore list.\"\n  },\n  \"impulse\": {\n    \"description\": \"This command calls a game function or QuakeC function.\\nOften impulses are used by the mod by defining aliases for game functions like \\\"ready\\\" and \\\"break\\\" that call certain impulses.\"\n  },\n  \"in_evdevlist\": {\n    \"description\": \"Prints list of evdev devices.\\nIf the list is empty, you probably don't have access rights to /dev/input/eventX\\nsudo chmod 644 /dev/input/event* should help you.\"\n  },\n  \"in_restart\": {\n    \"system-generated\": true\n  },\n  \"inc\": {\n    \"description\": \"Increments a variable by one or adds to it the optional second argument.\\nThere are no 'add' or 'dec' commands because 'inc' can handle both addition and subtraction.\\n\\nExample:\\ninc sensitivity -2\\n - subtracts 2 from sensitivity.\"\n  },\n  \"itemsclock\": {\n    \"description\": \"HUD element displaying items that will spawn soon in the game. Works only in MVD and QTV playback.\"\n  },\n  \"join\": {\n    \"description\": \"Joins a specified server as player.\\nIf no address is specified, join will reconnect to the last visited server as a player.\\n\\nExample:\\njoin 123.124.125.126\",\n    \"syntax\": \"<address>\"\n  },\n  \"joyadvancedupdate\": {\n    \"description\": \"This command must be run to apply changes made to the joyadvaxis[xyzruv] or joyindex cvars.\"\n  },\n  \"kick\": {\n    \"description\": \"Removes a user from the server. Use the status command to receive the user's id.\\n\\nExample:\\nkick 1234\",\n    \"syntax\": \"<userid>\"\n  },\n  \"kill\": {\n    \"description\": \"Suicide. (-2 frags on ktpro/kteam servers)\"\n  },\n  \"legacyquake\": {\n    \"arguments\": [\n      {\n        \"description\": \"Optional argument, when specified, will disable only new features from specified client version\",\n        \"name\": \"ver\"\n      }\n    ],\n    \"description\": \"Command that turns off new features added to the client that may confuse experienced users who don't like new features :-)\",\n    \"syntax\": \"[ver]\"\n  },\n  \"listip\": {\n    \"description\": \"Prints out the current list of IPs on the server list. Not to be confused with the status command which prints out the list of the IPs of the connected players.\"\n  },\n  \"load\": {\n    \"description\": \"Load 123 loads saved game 123.\",\n    \"syntax\": \"<filename>\"\n  },\n  \"loadFragfile\": {\n    \"system-generated\": true\n  },\n  \"loadcharset\": {\n    \"description\": \"You can change your console font from within ezQuake. Put all your charset images in qw/textures/charsets/*.png (and *.tga) and use /loadcharset XXX to load XXX.png (or tga).\\n\\\"/loadcharset original\\\" will restore the 8bit font in your gfx.wad (this is default).\",\n    \"remarks\": \"This command is just a 'shortcut' for the new gl_consolefont variable.\"\n  },\n  \"loadfont\": {\n    \"system-generated\": true\n  },\n  \"loadfragfile\": {\n    \"description\": \"Loads the specified fragfile.\",\n    \"syntax\": \"<filename>\"\n  },\n  \"loadloc\": {\n    \"description\": \"Loads a loc file (must be located in id1/locs, qw/locs, or ezquake/locs.\\nThe \\\".loc\\\" extension is optional, for example, \\\"loadloc dm6\\\"; if the file name has no extension, use its explicit name (\\\"loadloc dm6.\\\").\",\n    \"syntax\": \"<filename>\"\n  },\n  \"loadpak\": {\n    \"system-generated\": true\n  },\n  \"loadsky\": {\n    \"description\": \"Loads your skybox (qw\\\\env).\\n\\nExample:\\nloadsky snow\",\n    \"syntax\": \"<filename>\"\n  },\n  \"localinfo\": {\n    \"description\": \"Shows or sets localinfo variables. Useful for mod programmers who need to allow the admin to change settings.\\nThis is an alternative storage space to the serverinfo space for mod variables. The variables stored in this space are not broadcast on the network.\\nThis space also has a 32-kilobyte limit which is much greater then the 512-byte limit on the serverinfo space.\\nSpecial Keys: (current map) (next map) - Using this combination will allow the creation of a custom map cycle without editing code.\\n\\nExamples:\\nlocalinfo dm2 dm4\\nlocalinfo dm4 dm6\\nlocalinfo dm6 dm2\"\n  },\n  \"locate\": {\n    \"system-generated\": true\n  },\n  \"locations_add\": {\n    \"system-generated\": true\n  },\n  \"locations_clearall\": {\n    \"system-generated\": true\n  },\n  \"locations_loadfile\": {\n    \"system-generated\": true\n  },\n  \"locations_remove\": {\n    \"system-generated\": true\n  },\n  \"locations_savefile\": {\n    \"system-generated\": true\n  },\n  \"locname\": {\n    \"description\": \"Create a new location in the current spot.\"\n  },\n  \"log\": {\n    \"description\": \"If you type \\\"log filename\\\" it will log console to filename.log in your gamedir.\\nIt overwrites logs without asking.\",\n    \"syntax\": \"<filename>\"\n  },\n  \"logerrors\": {\n    \"system-generated\": true\n  },\n  \"logfile\": {\n    \"system-generated\": true\n  },\n  \"logplayers\": {\n    \"system-generated\": true\n  },\n  \"logrcon\": {\n    \"system-generated\": true\n  },\n  \"logtelnet\": {\n    \"system-generated\": true\n  },\n  \"ls\": {\n    \"system-generated\": true\n  },\n  \"macrolist\": {\n    \"description\": \"Prints a list of all available macros.\"\n  },\n  \"map\": {\n    \"description\": \"Loads a map and starts a game.\\n\\nExample:\\nmap e1m1\"\n  },\n  \"mapgroup\": {\n    \"description\": \"mapgroup 2fort5r 2fort5: will make 2fort5r and 2fort5 use the 2fort5r textures, locs and etc...\",\n    \"syntax\": \"mapgroup [map1] [map2] ...\"\n  },\n  \"master_rcon_password\": {\n    \"system-generated\": true\n  },\n  \"match_forcestart\": {\n    \"description\": \"Simulates the start of a match (so that auto recording etc is triggered).\\nUseful if you join a ktpro server after countdown has started, or you are playing a mode that doesn't have a proper countdown (eg race mode).\\nMost importantly this is useful for TF servers, since you can use a msg_trigger to execute match_forcestart on \\\"MATCH BEGINS NOW\\\".\"\n  },\n  \"match_format_macrolist\": {\n    \"description\": \"Prints a list of the macros and their meaning for autorecording and autoscreenshots.\"\n  },\n  \"match_save\": {\n    \"description\": \"If you are using 'match_auto_record 1' then a temp demo will be recorded to c:\\\\quake\\\\ezquake\\\\temp\\\\_!_temp_!_.qwd each time a map starts.\\nThis temp demo will be overwritten when the next match starts.\\nIf you want to keep the temp demo, use the \\\"match_save\\\" command. This will move the demo to the same folder and filename that easyrecord would have used.\"\n  },\n  \"menu_demos\": {\n    \"description\": \"This command will display the demos menu.\"\n  },\n  \"menu_fps\": {\n    \"description\": \"This command will display the fps menu.\"\n  },\n  \"menu_help\": {\n    \"description\": \"This command will display the help menu.\"\n  },\n  \"menu_keys\": {\n    \"description\": \"This command will display the keys menu.\"\n  },\n  \"menu_load\": {\n    \"description\": \"This command will display the singleplayer load menu.\"\n  },\n  \"menu_main\": {\n    \"description\": \"This command will display the main menu.\"\n  },\n  \"menu_mp3_control\": {\n    \"description\": \"This command will display the mp3 menu.\"\n  },\n  \"menu_mp3_playlist\": {\n    \"description\": \"This command will display the mp3 playlist menu.\"\n  },\n  \"menu_multiplayer\": {\n    \"description\": \"This command will display the multiplayer menu.\"\n  },\n  \"menu_options\": {\n    \"description\": \"This command will display the options menu.\"\n  },\n  \"menu_quit\": {\n    \"description\": \"This command will display the quit menu.\"\n  },\n  \"menu_save\": {\n    \"description\": \"This command will display the singleplayer save menu.\"\n  },\n  \"menu_setup\": {\n    \"description\": \"This command will display the setup menu.\"\n  },\n  \"menu_singleplayer\": {\n    \"description\": \"This command will display the singleplayer menu.\"\n  },\n  \"menu_slist\": {\n    \"description\": \"This command will display the server browser.\"\n  },\n  \"menu_video\": {\n    \"description\": \"This command will display the video menu.\"\n  },\n  \"messagemode\": {\n    \"description\": \"Prompts for string to broadcast to all other players.\"\n  },\n  \"messagemode2\": {\n    \"description\": \"Prompts for string to broadcast to team members.\"\n  },\n  \"messagemodeqtvtogame\": {\n    \"description\": \"Go into message mode for chatting in the QTV chat.\"\n  },\n  \"mod\": {\n    \"system-generated\": true\n  },\n  \"modfraglogfile\": {\n    \"system-generated\": true\n  },\n  \"move\": {\n    \"system-generated\": true\n  },\n  \"mp3_fadeout\": {\n    \"description\": \"Like stop but fades out.\"\n  },\n  \"mp3_fforward\": {\n    \"description\": \"Fast forward 5seconds.\"\n  },\n  \"mp3_loadplaylist\": {\n    \"description\": \"Loads the playlist filename.m3u\\n\\nExample:\\nmp3_loadplaylist top10\\n - loads top10.m3u.\",\n    \"syntax\": \"<filename>\"\n  },\n  \"mp3_next\": {\n    \"description\": \"Next song.\"\n  },\n  \"mp3_pause\": {\n    \"description\": \"Pause mp3.\"\n  },\n  \"mp3_play\": {\n    \"description\": \"Play mp3.\"\n  },\n  \"mp3_playlist\": {\n    \"description\": \"Displays playlist. Currently playing track is highlighted.\"\n  },\n  \"mp3_playtrack\": {\n    \"description\": \"Play track number #num from playlist.\\n\\nExample:\\nmp3_playtrack 5\\n - plays track 5 from playlist.\",\n    \"syntax\": \"<track>\"\n  },\n  \"mp3_prev\": {\n    \"description\": \"Previous song.\"\n  },\n  \"mp3_repeat\": {\n    \"description\": \"Repeat playlist.\",\n    \"syntax\": \"<off|on>\"\n  },\n  \"mp3_rewind\": {\n    \"description\": \"Rewind 5seconds.\"\n  },\n  \"mp3_shuffle\": {\n    \"description\": \"Shuffle mp3s.\",\n    \"syntax\": \"<off|on>\"\n  },\n  \"mp3_songinfo\": {\n    \"description\": \"Displays song title and other info like time elapsed, total time, and whether paused, stopped or playing.\"\n  },\n  \"mp3_startwinamp\": {\n    \"description\": \"Starts winamp if it is not running.\"\n  },\n  \"mp3_stop\": {\n    \"description\": \"Stop mp3.\"\n  },\n  \"mp3_volume\": {\n    \"system-generated\": true\n  },\n  \"msg_trigger\": {\n    \"arguments\": [\n      {\n        \"description\": \"The name of the alias you want to be executed when trigger matches the string.\",\n        \"name\": \"alias\"\n      },\n      {\n        \"description\": \"Case-sensitive substring of the message you want to catch in this trigger.\",\n        \"name\": \"string\"\n      },\n      {\n        \"description\": \"Level of the message.\\n0 - pickup messages\\n1 - death messages\\n2 - critical messages\\n3 - chat messages\\nEverything else is level 4.\",\n        \"name\": \"level\"\n      }\n    ],\n    \"description\": \"Allows you to set rules that will automatically execute actions when a certain message is sent from a server.\\nThis is very useful in Team Fortress, in deathmatch it can be used for example for auto-reporting \\\"quad in 30\\\" when you receive \\\"Quad Damage is wearing out\\\" message.\",\n    \"remarks\": \"Only some server messages can be triggered. Usually no pickup messages nor your teammates text is triggered.\",\n    \"syntax\": \"<alias> <string> [-l] <level>\"\n  },\n  \"mute\": {\n    \"system-generated\": true\n  },\n  \"mutesound\": {\n    \"system-generated\": true\n  },\n  \"mvd_dumpstats\": {\n    \"description\": \"Dumps statistics gathered from a MVD demo into a \\\"stats.xml\\\" file in the current working directory.\"\n  },\n  \"mvd_list_items\": {\n    \"system-generated\": true\n  },\n  \"mvd_name_item\": {\n    \"system-generated\": true\n  },\n  \"mvd_remove_item\": {\n    \"system-generated\": true\n  },\n  \"mvdrecord\": {\n    \"system-generated\": true\n  },\n  \"mvdstop\": {\n    \"system-generated\": true\n  },\n  \"netproblem\": {\n    \"description\": \"HUD element - icon displayed when network traffic is experiencing problems like lost packets, large delay, etc.\"\n  },\n  \"noclip\": {\n    \"description\": \"You can fly and go thru objects free mode as spectator\",\n    \"remarks\": \"Needs cheats support by server.\"\n  },\n  \"nslookup\": {\n    \"system-generated\": true\n  },\n  \"observe\": {\n    \"description\": \"Connects you to a server as a spectator. If no address is specified, observe will reconnect to the last visited server as a spectator.\\n\\nExample:\\nobserve 123.124.125.126\",\n    \"syntax\": \"<address>\"\n  },\n  \"observebr\": {\n    \"description\": \"Connects to given server as spectator via fastest available path (ping-wise).\"\n  },\n  \"observeqtv\": {\n    \"system-generated\": true\n  },\n  \"order\": {\n    \"system-generated\": true\n  },\n  \"packet\": {\n    \"description\": \"Sends a packet with specified contents to the destination.\\n\\nExample:\\npacket 123.123.123.123:27500 \\\"status\\\"\",\n    \"syntax\": \"<address>\"\n  },\n  \"password\": {\n    \"description\": \"Set the password to enter a password protected server.\"\n  },\n  \"path\": {\n    \"description\": \"Shows what paths ezQuake is using.\"\n  },\n  \"pause\": {\n    \"description\": \"Pauses a game.\",\n    \"remarks\": \"Servers must support pausing.\"\n  },\n  \"penaltylist\": {\n    \"system-generated\": true\n  },\n  \"penaltyremove\": {\n    \"system-generated\": true\n  },\n  \"place\": {\n    \"system-generated\": true\n  },\n  \"play\": {\n    \"description\": \"Plays a sound effect.\\n\\nExample:\\nplay misc/runekey.wav\",\n    \"syntax\": \"<filename>\"\n  },\n  \"playdemo\": {\n    \"description\": \"Plays a recorded demo.\\n\\nExample:\\nplaydemo thresh.qwd\",\n    \"syntax\": \"<filename>\"\n  },\n  \"playvol\": {\n    \"description\": \"Plays a sound at a given volume.\\n\\nExamples:\\nplayvol items/protect.wav .5\\nplayvol items/protect.wav 2\",\n    \"syntax\": \"<filename> (volume)\"\n  },\n  \"profile\": {\n    \"description\": \"Reports information about QuakeC stuff.\"\n  },\n  \"qstat\": {\n    \"system-generated\": true\n  },\n  \"qtv\": {\n    \"arguments\": [\n      {\n        \"description\": \"IP address of the internet QTV broadcasting some QW action\",\n        \"name\": \"address\"\n      }\n    ],\n    \"description\": \"Connects you to internet QTV broadcasting some action, using your local proxy.\\nStarts local QTV proxy, tells it what broadcasting proxy it should connect to and by connecting to it you'll see the action broadcasted on the IP you've specified.\",\n    \"syntax\": \"<address>\"\n  },\n  \"qtv_close\": {\n    \"system-generated\": true\n  },\n  \"qtv_fixuser\": {\n    \"system-generated\": true\n  },\n  \"qtv_list\": {\n    \"system-generated\": true\n  },\n  \"qtv_query_demolist\": {\n    \"system-generated\": true\n  },\n  \"qtv_query_sourcelist\": {\n    \"system-generated\": true\n  },\n  \"qtv_reconnect\": {\n    \"description\": \"Reconnect to last QTV server the client was connected to.\"\n  },\n  \"qtv_status\": {\n    \"system-generated\": true\n  },\n  \"qtv_update\": {\n    \"system-generated\": true\n  },\n  \"qtvplay\": {\n    \"system-generated\": true\n  },\n  \"qtvreconnect\": {\n    \"system-generated\": true\n  },\n  \"qtvusers\": {\n    \"system-generated\": true\n  },\n  \"quit\": {\n    \"description\": \"Exit - disconnects from the server and closes the client.\"\n  },\n  \"qwurl\": {\n    \"system-generated\": true\n  },\n  \"radar\": {\n    \"description\": \"HUD element showing a map overview.\",\n    \"syntax\": \"<property> <value>\"\n  },\n  \"rcon\": {\n    \"description\": \"Issue the set of commands to the server you are currently connected to or have set in rcon_address.\\nYou must know the rcon password for that specific server.\"\n  },\n  \"re_trigger\": {\n    \"arguments\": [\n      {\n        \"description\": \"re_trigger name\",\n        \"name\": \"rt_name\"\n      },\n      {\n        \"description\": \"Regexp defines which messages (or whatever) will activate a trigger.\\nThe client uses the PCRE library (Perl-compatible regular expression library).\\nYou can find information how to write regexps in the PCRE's documentation.\",\n        \"name\": \"regexp\"\n      }\n    ],\n    \"description\": \"When used w/o parameter, prints a list of all triggers.\\nWhen regexp is not defined prints options set for rt_name trigger; otherwise creates or changes rt_name trigger.\",\n    \"syntax\": \"re_trigger [rt_name [regexp]]\"\n  },\n  \"re_trigger_delete\": {\n    \"arguments\": [\n      {\n        \"description\": \"re_trigger name\",\n        \"name\": \"rt_name\"\n      }\n    ],\n    \"description\": \"Deletes the corresponding trigger.\",\n    \"syntax\": \"re_trigger_delete rt_name\"\n  },\n  \"re_trigger_disable\": {\n    \"arguments\": [\n      {\n        \"description\": \"Re_trigger name. Can be REGEXP.\",\n        \"name\": \"rt_name\"\n      }\n    ],\n    \"description\": \"Disables activation of one or more triggers.\",\n    \"syntax\": \"re_trigger_disable rt_name1 [rt_name2...]\"\n  },\n  \"re_trigger_enable\": {\n    \"arguments\": [\n      {\n        \"description\": \"Re_trigger name. Can be REGEXP.\",\n        \"name\": \"rt_name\"\n      }\n    ],\n    \"description\": \"Enables activation of one or more triggers.\",\n    \"syntax\": \"re_trigger_enable rt_name1 [rt_name2...]\"\n  },\n  \"re_trigger_match\": {\n    \"arguments\": [\n      {\n        \"description\": \"re_trigger name\",\n        \"name\": \"trigger_name\"\n      },\n      {\n        \"description\": \"Your string\",\n        \"name\": \"string\"\n      }\n    ],\n    \"description\": \"Allows to direct a <string> to a trigger <trigger_name>. If this string match regexp, then a corresponding alias is activated.\",\n    \"syntax\": \"re_trigger_match <trigger_name> <string>\"\n  },\n  \"re_trigger_options\": {\n    \"arguments\": [\n      {\n        \"description\": \"Value represents a bit mask which determines which types of messages can cause activation of a trigger:\\n1 - pickup messages\\n2 - death messages\\n4 - critical messages (most of Team Fortress messages)\\n8 - chat\\n16 - centerprint\\n32 - echo command output\\n64 - other strings printed in the console\\nThe default mask for any trigger equals 31.\",\n        \"name\": \"mask\"\n      },\n      {\n        \"description\": \"Sets a minimal interval of trigger activation (in seconds). If a second activation happened earlier than the value time, it is ignored.\\nDefault is 0.\",\n        \"name\": \"interval\"\n      },\n      {\n        \"description\": \"If activation of this trigger happened, the remaining (in the list of triggers) triggers are not checked.\\nTriggers are checked in a reversed order of their definition.\",\n        \"name\": \"final\"\n      },\n      {\n        \"description\": \"Activation of such trigger doesn't stop the checking of other triggers (default).\",\n        \"name\": \"notfinal\"\n      },\n      {\n        \"description\": \"A string which caused activation of a trigger is not printed on the screen.\",\n        \"name\": \"remove\"\n      },\n      {\n        \"description\": \"A string which caused activation of a trigger is printed on the screen (default).\",\n        \"name\": \"noremove\"\n      },\n      {\n        \"description\": \"A string which caused activation of a trigger is not added to a log-file.\",\n        \"name\": \"log\"\n      },\n      {\n        \"description\": \"A string which caused activation of a trigger is added to a log-file (default).\",\n        \"name\": \"nolog\"\n      },\n      {\n        \"description\": \"A corresponding alias is not executed upon a trigger activation.\\nThere is no need to exec alias if all you need is to use remove option :)\",\n        \"name\": \"noaction\"\n      },\n      {\n        \"description\": \"A corresponding alias is executed upon a trigger activation (default).\",\n        \"name\": \"action\"\n      }\n    ],\n    \"description\": \"Changes options for a corresponding trigger.\",\n    \"syntax\": \"re_trigger_options rt_name option_list\"\n  },\n  \"reconnect\": {\n    \"description\": \"Reconnects to the last server/proxy.\"\n  },\n  \"record\": {\n    \"description\": \"Records a demo.\\n\\nExample:\\nrecord test\\n - records test.qwd to qw folder\",\n    \"syntax\": \"<filename>\"\n  },\n  \"recordqwd\": {\n    \"system-generated\": true\n  },\n  \"register_qwurl_protocol\": {\n    \"system-generated\": true\n  },\n  \"removeip\": {\n    \"description\": \"Removes an IP address from the server IP list.\\n\\nExamples:\\nremoveip 123.123.123.123\\nremoveip 123.123.123\",\n    \"syntax\": \"<ip>\"\n  },\n  \"removeloc\": {\n    \"description\": \"Remove the closest location (use teamsay to see which).\"\n  },\n  \"removepak\": {\n    \"system-generated\": true\n  },\n  \"reset\": {\n    \"system-generated\": true\n  },\n  \"rm\": {\n    \"system-generated\": true\n  },\n  \"rmdir\": {\n    \"system-generated\": true\n  },\n  \"rotate\": {\n    \"description\": \"Rotates the player by x degrees.\\n\\nExample: \\\"rotate 180\\\"\\n - rotates your POV by 180 degrees.\",\n    \"remarks\": \"Negative values can also be used for the desired angle.\"\n  },\n  \"s_audiodevicelist\": {\n    \"system-generated\": true\n  },\n  \"s_listdrivers\": {\n    \"system-generated\": true\n  },\n  \"s_restart\": {\n    \"system-generated\": true\n  },\n  \"save\": {\n    \"description\": \"Saves a game in singleplayer.\\n\\nExample:\\nsave 123\"\n  },\n  \"saveloc\": {\n    \"arguments\": [\n      {\n        \"description\": \"The .loc file the locs should be saved in.\",\n        \"name\": \"filename\"\n      }\n    ],\n    \"description\": \"Saves the current locs in memory to the specified file.\",\n    \"syntax\": \"<filename>\"\n  },\n  \"say\": {\n    \"description\": \"Broadcasts a string to all other players.\\n\\nExample:\\nsay ezQuake rules!\"\n  },\n  \"say_team\": {\n    \"description\": \"Broadcasts a string to teammates.\\n\\nExample:\\nsay_team stop boring!\"\n  },\n  \"sb_buildpingtree\": {\n    \"system-generated\": true\n  },\n  \"sb_pingsdump\": {\n    \"description\": \"Dumps a list of pairs (IP address, ping) into the console based on the current content of the Server Browser list.\"\n  },\n  \"sb_proxygetpings\": {\n    \"system-generated\": true\n  },\n  \"sb_refresh\": {\n    \"description\": \"Causes Server Browser refresh ping and status info for all servers.\"\n  },\n  \"sb_sourceadd\": {\n    \"arguments\": [\n      {\n        \"description\": \"Name of the source\",\n        \"name\": \"name\"\n      },\n      {\n        \"description\": \"(IP) address of master server or filename of the server list file\",\n        \"name\": \"source\"\n      },\n      {\n        \"description\": \"\\\"master\\\" for master server or \\\"file\\\" for server list file\",\n        \"name\": \"type\"\n      }\n    ],\n    \"description\": \"Adds new server list source.\",\n    \"syntax\": \"<name> <source> <type>\"\n  },\n  \"sb_sourcemark\": {\n    \"description\": \"Marks \\\"source-name\\\" as selected server source list.\",\n    \"syntax\": \"<source-name>\"\n  },\n  \"sb_sourcesupdate\": {\n    \"description\": \"Reload server lists from all marked server sources.\"\n  },\n  \"sb_sourceunmarkall\": {\n    \"description\": \"Unmarks all servers sources.\"\n  },\n  \"score_difference\": {\n    \"description\": \"HUD element which displays the frag difference between you (your team) and your enemy (enemy team).\"\n  },\n  \"score_enemy\": {\n    \"description\": \"HUD element which displays amount of frags made by all the enemies.\"\n  },\n  \"score_own\": {\n    \"description\": \"HUD element which display your (or the person's you are observing) amount of frags.\"\n  },\n  \"score_position\": {\n    \"description\": \"HUD element which displays your position on the frag leaders board.\"\n  },\n  \"screenshot\": {\n    \"arguments\": [\n      {\n        \"description\": \"Optional. When used tells the destination filename.\\nIf extension is not preset it will be added automatically according to sshot_format setting.\",\n        \"name\": \"name\"\n      }\n    ],\n    \"description\": \"Saves a still picture of current screen to your hard drive.\",\n    \"syntax\": \"[name]\"\n  },\n  \"script\": {\n    \"system-generated\": true\n  },\n  \"serverexec\": {\n    \"system-generated\": true\n  },\n  \"serverinfo\": {\n    \"description\": \"Reports the current server info.\"\n  },\n  \"serverstatus\": {\n    \"description\": \"Prints the status of the ezQuake server.\"\n  },\n  \"set\": {\n    \"arguments\": [\n      {\n        \"description\": \"Name of variable or custom variable.\",\n        \"name\": \"varname\"\n      },\n      {\n        \"description\": \"Value you want to store in the variable.\",\n        \"name\": \"value\"\n      }\n    ],\n    \"description\": \"Sets a variable to a given value.\",\n    \"syntax\": \"<varname> <value>\"\n  },\n  \"set_alias_str\": {\n    \"description\": \"Assigns variable a value which contains an indicated alias.\",\n    \"syntax\": \"<cvar> <alias>\"\n  },\n  \"set_bind_str\": {\n    \"description\": \"Assigns variable a value which contains anything bound to an indicated key.\",\n    \"syntax\": \"<cvar> <key>\"\n  },\n  \"set_calc\": {\n    \"arguments\": [\n      {\n        \"description\": \"Name of variable you want to save the result in.\",\n        \"name\": \"cvar\"\n      },\n      {\n        \"description\": \"Possible commands:\\nstrlen - gets length of given string\\nint - converts given float value to integer\\nsubstr - return substring of given string\\nset_substr - replaces given position in string with another string\\npos - gets position of substring in given string\",\n        \"name\": \"command\"\n      },\n      {\n        \"description\": \"Arguments of commands:\\nstrlen <string>\\nint <float>\\nsubstr <sourcestr> <position> [<length>]\\nset_substr <replacestr> <position>\\npos <haystack> <needle>\",\n        \"name\": \"cmdargs\"\n      },\n      {\n        \"description\": \"First argument\",\n        \"name\": \"arg1\"\n      },\n      {\n        \"description\": \"Possible operators: +, -, *, /, div, %%, and, or, xor.\",\n        \"name\": \"oper\"\n      },\n      {\n        \"description\": \"Second argument.\",\n        \"name\": \"arg2\"\n      }\n    ],\n    \"description\": \"Advanced variables customization.\",\n    \"syntax\": \"<cvar command cmdargs> | <cvar arg1 oper arg2>\"\n  },\n  \"set_eval\": {\n    \"system-generated\": true\n  },\n  \"set_ex\": {\n    \"description\": \"Assigns a new value to a variable, expansion of %macros and variables is performed even in case if a parameter is inside the dual quotation marks.\",\n    \"syntax\": \"<cvar> <value>\"\n  },\n  \"set_ex2\": {\n    \"system-generated\": true\n  },\n  \"set_tp\": {\n    \"system-generated\": true\n  },\n  \"setinfo\": {\n    \"description\": \"Sets information about your FuhWorld user.\\nUsed without a key it will list all of your current settings. Specifying a non-existent key and a value will create the new key.\"\n  },\n  \"setmaster\": {\n    \"description\": \"Lists the server with up to eight masters.\\nWhen a server is listed with a master, the master is aware of the server's IP address and port and it is added to the list of current server connected to a master. A heartbeat is sent to the master from the server to indicated that the server is still running and alive.\\n\\nExamples:\\nsetmaster 192.246.40.12:27002\\nsetmaster 192.246.40.12:27002 192.246.40.12:27004\"\n  },\n  \"show\": {\n    \"system-generated\": true\n  },\n  \"showskins\": {\n    \"system-generated\": true\n  },\n  \"sizedown\": {\n    \"description\": \"Reduces the screen size.\"\n  },\n  \"sizeup\": {\n    \"description\": \"Increases the screen size.\"\n  },\n  \"skins\": {\n    \"description\": \"Refreshes skin settings.\",\n    \"remarks\": \"If you have set noskins to 0 and do this it will download all skins that players are using on server.\"\n  },\n  \"skygroup\": {\n    \"description\": \"In basics works same as mapgroup.\",\n    \"syntax\": \"skyboxname map1 [map2] ...\"\n  },\n  \"skywind\": {\n    \"description\": \"Sets the animation parameters of the skybox. Requires a skybox with some level of transparency.\",\n    \"syntax\": \"skywind [distance] [yaw] [period] [pitch]\"\n  },\n  \"skywind_load\": {\n    \"description\": \"Loads the skywind config for the loaded skymap. The file is expected to contain: skywind [distance] [yaw] [period] [pitch].\"\n  },\n  \"skywind_lookdir\": {\n    \"description\": \"Updates the skywind direction to current point of view with optional overrides of period and distance.\",\n    \"syntax\": \"skywind_lookdir [period] [distance]\"\n  },\n  \"skywind_rotate\": {\n    \"description\": \"Updates the skywind direction with yaw (horizontal) and pitch (vertical) adjustment.\",\n    \"syntax\": \"skywind_rotate [yaw] [pitch]\"\n  },\n  \"skywind_save\": {\n    \"description\": \"Saves the current skywind configuration as gfx/env/$skybox_wind.cfg.\"\n  },\n  \"snap\": {\n    \"description\": \"Remote screenshot from a player.\\n\\nExample:\\nsnap 1234\\n - server requests remote screenshot from user 1234\",\n    \"syntax\": \"<userid>\"\n  },\n  \"snapall\": {\n    \"description\": \"Remote screenshots from all players.\"\n  },\n  \"soundinfo\": {\n    \"description\": \"Reports information on the sound system.\"\n  },\n  \"soundlist\": {\n    \"description\": \"Reports a list of sounds in the cache.\"\n  },\n  \"spectator_password\": {\n    \"description\": \"Sets spectator password to ezQuake local server.\",\n    \"remarks\": \"Spectators must use \\\"spectator <password>\\\" to connect to a server with a spectator password.\"\n  },\n  \"speed\": {\n    \"description\": \"Shows your current movement speed.\"\n  },\n  \"speed2\": {\n    \"description\": \"An alternative speed-o-meter that is drawn as a half-circle.\"\n  },\n  \"status\": {\n    \"description\": \"Reports information on the current connected clients and the server.\"\n  },\n  \"stop\": {\n    \"description\": \"Stops demo recording.\"\n  },\n  \"stopqwd\": {\n    \"system-generated\": true\n  },\n  \"stopsound\": {\n    \"description\": \"Stops all sounds currently being played.\"\n  },\n  \"stopsound_script\": {\n    \"system-generated\": true\n  },\n  \"sv_democancel\": {\n    \"system-generated\": true\n  },\n  \"sv_demoeasyrecord\": {\n    \"system-generated\": true\n  },\n  \"sv_demoembedinfo\": {\n    \"system-generated\": true\n  },\n  \"sv_demoinfo\": {\n    \"system-generated\": true\n  },\n  \"sv_demoinfoadd\": {\n    \"system-generated\": true\n  },\n  \"sv_demoinforemove\": {\n    \"system-generated\": true\n  },\n  \"sv_demolist\": {\n    \"system-generated\": true\n  },\n  \"sv_demolistr\": {\n    \"system-generated\": true\n  },\n  \"sv_demolistregex\": {\n    \"system-generated\": true\n  },\n  \"sv_demonumremove\": {\n    \"system-generated\": true\n  },\n  \"sv_demorecord\": {\n    \"system-generated\": true\n  },\n  \"sv_demoremove\": {\n    \"system-generated\": true\n  },\n  \"sv_demostop\": {\n    \"system-generated\": true\n  },\n  \"sv_gamedir\": {\n    \"description\": \"Displays or determines the value of the serverinfo *gamedir variable.\\nThis is the directory clients will use.\\n\\nExamples:\\ngamedir tf2_5; sv_gamedir fortress\\ngamedir ctf4_2; sv_gamedir ctf\\ngamedir ktffa; sv_gamedir qw  // FFA servers should use default *gamedir\",\n    \"remarks\": \"Useful when the physical gamedir directory has a different name than the widely accepted gamedir directory.\"\n  },\n  \"sv_lastscores\": {\n    \"system-generated\": true\n  },\n  \"sv_status\": {\n    \"system-generated\": true\n  },\n  \"sv_usercmdtrace\": {\n    \"system-generated\": true\n  },\n  \"svadmin\": {\n    \"system-generated\": true\n  },\n  \"sys_forget_sandbox\": {\n    \"description\": \"Clears the currently selected sandbox directory. On the next restart, ezQuake will prompt you to select a directory again.\",\n    \"remarks\": \"macOS only\"\n  },\n  \"tcl_eval\": {\n    \"description\": \"Execute <string> as Tcl code.\",\n    \"syntax\": \"<string>\"\n  },\n  \"tcl_exec\": {\n    \"description\": \"Execute a config as Tcl script.\",\n    \"syntax\": \"<filename>\"\n  },\n  \"tcl_proc\": {\n    \"description\": \"Execute a Tcl procedure <name> with parameters <parameters>. Procedure must be defined before thru tcl_eval or tcl_exec commands.\",\n    \"syntax\": \"<name> [parameters]\"\n  },\n  \"tcpconnect\": {\n    \"arguments\": [\n      {\n        \"description\": \"IP address of a QuakeWorld server.\",\n        \"name\": \"address\"\n      },\n      {\n        \"description\": \"TCP port of a QuakeWorld server.\",\n        \"name\": \"port\"\n      }\n    ],\n    \"description\": \"Connects your client to a QuakeWorld server via TCP.\",\n    \"syntax\": \"<address>:<port>\"\n  },\n  \"teamcolor\": {\n    \"description\": \"This will override team color.\\n\\nExample:\\nteamcolor 12 13\",\n    \"remarks\": \"If only the shirt color is given, the pant color will match.\"\n  },\n  \"teamholdbar\": {\n    \"description\": \"Displays overall map control per team when watching multiview demo.\"\n  },\n  \"teamholdinfo\": {\n    \"description\": \"Displays items possession stats per team when watching multiview demo.\"\n  },\n  \"tempalias\": {\n    \"description\": \"Sets a temporary alias.\\nTempaliases will not save to your config.\"\n  },\n  \"timedemo\": {\n    \"description\": \"This command will load and play a demo at full speed. It will then divide the total number of frames in the demo by the total time it took finish, and calculate the average frames-per-second rate.\\n\\nExample:\\ntimedemo demoname\",\n    \"syntax\": \"<filename>\"\n  },\n  \"timedemo2\": {\n    \"system-generated\": true\n  },\n  \"timerefresh\": {\n    \"description\": \"This command will perform a 360 degree turn and calculate the frames-per-second rate.\"\n  },\n  \"toggle\": {\n    \"description\": \"You can turn off/on cvars.\\n\\nExample:\\ntoggle sensitivity turns off sensitivity and toggle sensitivity again turns on.\"\n  },\n  \"toggle_re\": {\n    \"description\": \"You can turn variable values ON and OFF.\\n\\nExample:\\ntoggle ^gl_part.* - turns all particle effects on/off.\",\n    \"syntax\": \"[cvar_regexp1] [cvar_regexp2] ... [cvar_regexpN]\"\n  },\n  \"toggleconsole\": {\n    \"description\": \"Brings the console up and down.\"\n  },\n  \"togglehud\": {\n    \"system-generated\": true\n  },\n  \"togglemenu\": {\n    \"description\": \"Displays the menu screens.\"\n  },\n  \"toggleproxymenu\": {\n    \"system-generated\": true\n  },\n  \"togglespec\": {\n    \"system-generated\": true\n  },\n  \"tp_msgcoming\": {\n    \"description\": \"Will send a message to your teammates telling them where you are coming from, and what your status is.\\nDoubles as tp_lost when you are dead (because you can't be coming from somewhere if you're dead).\"\n  },\n  \"tp_msgenemypwr\": {\n    \"description\": \"This command could be used for all cases involving players with powerup.\\nIf you, a teammate, or enemy has powerup, this bind will report it.\\nIf eyes is in your point, we assume enemy only because this is tp_ENEMYpwr, otherwise there is no way to tell if a player with ring is enemy or teammate.\"\n  },\n  \"tp_msggetpent\": {\n    \"description\": \"Informs your teammates to get the pent.\\nWill print enemy/team pent if you, teammate, or enemy has pent and they're in your point.\\nWill print nothing if eyes is in point.\"\n  },\n  \"tp_msggetquad\": {\n    \"description\": \"Informs your teammates to get the quad.\\nReports team/enemy quad if you, a teammate, or an enemy has quad and they are in your point.\\nReports nothing if eyes is in your point.\"\n  },\n  \"tp_msghelp\": {\n    \"description\": \"Requests help at a location. Also gives your status.\"\n  },\n  \"tp_msgitemsoon\": {\n    \"system-generated\": true\n  },\n  \"tp_msgkillme\": {\n    \"system-generated\": true\n  },\n  \"tp_msglost\": {\n    \"description\": \"Will send a message to your teammates telling them you died at a location.\\nAlso tells them how many enemies are there, and what weapon (if any) you dropped.\"\n  },\n  \"tp_msgneed\": {\n    \"description\": \"Equivalent of %u (need macro).\\nWill display what you need (health/armor/ammo).\"\n  },\n  \"tp_msgnocancel\": {\n    \"system-generated\": true\n  },\n  \"tp_msgpoint\": {\n    \"description\": \"Will report to your team item you see in your point at its location.\\nWill report team/enemy powerup if you, a teammate, or an enemy has a powerup and are in your point.\",\n    \"remarks\": \"Note that tp_pointpriorities defaults to 1 and it is strongly suggested to keep this value. You can alter the priorities of this bind by altering tp_point.\"\n  },\n  \"tp_msgquaddead\": {\n    \"description\": \"Reports quad is dead. Will print enemy/team powerup if you, a teammate, or an enemy have quad and are in your point.\"\n  },\n  \"tp_msgreplace\": {\n    \"description\": \"Requests a teammate to replace you at your location.\"\n  },\n  \"tp_msgreport\": {\n    \"description\": \"Will send a message to your teammates about your current status: health, armor, location, powerups, weapon.\\nDoubles as tp_lost when you are dead.\"\n  },\n  \"tp_msgsafe\": {\n    \"description\": \"Will send a message to your teammates informing them your current location is safe.\\nWill print nothing if there is enemy in your point.\\nAlso reports your status.\"\n  },\n  \"tp_msgslipped\": {\n    \"system-generated\": true\n  },\n  \"tp_msgtfconced\": {\n    \"system-generated\": true\n  },\n  \"tp_msgtook\": {\n    \"description\": \"Informs your team of the last item you took.\\nSaves each item in memory for 15 seconds.\"\n  },\n  \"tp_msgtrick\": {\n    \"description\": \"Requests a trick at a location.\\nBest for cases like dm2 when you need a teammate to help you get quad from stairs.\"\n  },\n  \"tp_msgutake\": {\n    \"system-generated\": true\n  },\n  \"tp_msgwaiting\": {\n    \"system-generated\": true\n  },\n  \"tp_msgyesok\": {\n    \"system-generated\": true\n  },\n  \"tp_pickup\": {\n    \"description\": \"Item can be: quad, pent, ring, suit, ra, ya, ga, mh, health, lg, rl, gl, sng, ng, ssg, pack, cells, rockets, nails, shells, flag, armor, weapons, powerups, ammo, all, default, none\"\n  },\n  \"tp_point\": {\n    \"description\": \"Specifies which items will be used in point (%x or $point) macro.\\nIf you point at an item and such item is not listed here, your message will be tp_name_nothing (default: \\\"nothing\\\").\\nSupported items: powerups, quad, pent, ring, armor, ra, ya, ga, weapons, lg, rl, gl, sng, ng, ssg, ammo, cells, rockets, nails, shells, players, eyes, teammate, enemy, mh, health, pack, flag, all, default, none, suit\\nItem names can be customized with tp_name_item.\",\n    \"syntax\": \"item1 item2 ... DO NOT USE QUOTES\"\n  },\n  \"tp_took\": {\n    \"description\": \"Specifies which items will be included in the took (%X or $took) macro.\\nIf you took an item and it's not listed in tp_took list, %X and $took macro will display tp_name_nothing (default: \\\"nothing\\\").\\nSupported items: quad, pent, ring, suit, ra, ya, ga, mh, health, lg, rl, gl, sng, ng, ssg, pack, cells, rockets, nails, shells, flag, armor, weapons, powerups, ammo, all, default, none\\nItem names can be customized with tp_name_item.\",\n    \"syntax\": \"item1 item2 ... DO NOT USE QUOTES\"\n  },\n  \"track\": {\n    \"system-generated\": true\n  },\n  \"track-\": {\n    \"description\": \"This means when you are spectating or watching an mvd, keys will automatically be assigned to track certain players.\\nSo in a 4v4 8 keys are needed, the first 4 make you track the 4 players in the first team and the last 4 make you track the second team.\"\n  },\n  \"track1\": {\n    \"system-generated\": true\n  },\n  \"track2\": {\n    \"system-generated\": true\n  },\n  \"track3\": {\n    \"system-generated\": true\n  },\n  \"track4\": {\n    \"system-generated\": true\n  },\n  \"trackkiller\": {\n    \"description\": \"Will switch view to the player who killed the player we are tracking at the moment.\"\n  },\n  \"trackteam\": {\n    \"arguments\": [\n      {\n        \"description\": \"Team number. In standard 4on4 use 1 or 2.\",\n        \"name\": \"teamnum\"\n      }\n    ],\n    \"description\": \"When using MultiView, allows you to assign all available points of view to a desired team.\",\n    \"syntax\": \"<teamnum>\"\n  },\n  \"troubleshoot\": {\n    \"description\": \"Performs a check on client settings and displays possible sources of issues.\"\n  },\n  \"unalias\": {\n    \"description\": \"Example:\\nunalias myreport\\n - removes myreport alias.\"\n  },\n  \"unalias_re\": {\n    \"description\": \"Deletes given alias(es).\\n\\nExample:\\nunalias ^_zoom.\\n - removes all aliases beginning with '_zoom', e.g. _zoom_in, _zoom_out\",\n    \"syntax\": \"<cvar1> [cvar2] ...\"\n  },\n  \"unaliasall\": {\n    \"description\": \"Removes all aliases.\"\n  },\n  \"unbind\": {\n    \"description\": \"Example: unbind x delete strings on x button.\"\n  },\n  \"unbindall\": {\n    \"description\": \"Removes all keyboard bindings.\"\n  },\n  \"unignore\": {\n    \"description\": \"Removes name or user ID number from ignore list.\",\n    \"syntax\": \"<name|userid>\"\n  },\n  \"unignoreAll\": {\n    \"description\": \"Removes all users from ignore list.\"\n  },\n  \"unignoreAll_team\": {\n    \"description\": \"Removes all ignored teams from ignore list.\"\n  },\n  \"unignore_id\": {\n    \"description\": \"Removes userid from ignore list.\",\n    \"syntax\": \"<userid>\"\n  },\n  \"unignore_team\": {\n    \"description\": \"Removes team from ignore list.\"\n  },\n  \"unignore_voip\": {\n    \"system-generated\": true\n  },\n  \"unset\": {\n    \"description\": \"Removes user-created variable.\",\n    \"syntax\": \"<cvar1> [cvar2] ...\"\n  },\n  \"unset_re\": {\n    \"description\": \"Removes user-created variable.\",\n    \"syntax\": \"<cvar1|regex1> [cvar2|regex2] ...\"\n  },\n  \"user\": {\n    \"description\": \"This command queries the user for his setinfo information.\",\n    \"syntax\": \"<userid>\"\n  },\n  \"userdir\": {\n    \"arguments\": [\n      {\n        \"description\": \"String used as the <dir> part in the final path (see 'type' argument below).\",\n        \"name\": \"dir\"\n      },\n      {\n        \"description\": \"Optional number. Default is 0.\\n0 - <quake_dir>/<gamedir>/<dir>\\n1 - <quake_dir>/<dir>/<gamedir>\\n2 - <quake_dir>/qw/<dir>/<gamedir>\\n3 - <quake_dir>/qw/<dir>\\n4 - <quake_dir>/<dir>\\n5 - $HOME/qw/<dir>\",\n        \"name\": \"type\"\n      }\n    ],\n    \"description\": \"A userdir command can be used for addition of directories to the list searched for files by the client (configs, sounds, models, etc.).\\nReal handy when several players with their own configs files play Quake on a single computer.\\nWhen issuing this command after reconnection to a server or parameter is not defined, a current userdir is printed in the console.\",\n    \"syntax\": \"[dir [type]]\"\n  },\n  \"userinfo\": {\n    \"description\": \"Prints your user settings.\"\n  },\n  \"users\": {\n    \"description\": \"Reports information on connected players and retrieve user IDs.\"\n  },\n  \"v_cshift\": {\n    \"description\": \"This adjusts all of the colors currently being displayed.\\nUsed when you are underwater, hit, have the Ring of Shadows, or Quad Damage.\\n\\nExample:\\nv_cshift 16 32 64\\n\",\n    \"syntax\": \"<red> <green> <blue> <intensity>\"\n  },\n  \"validate_clients\": {\n    \"description\": \"This shows authed ezQuake users, non-authed and non-ezQuake users.\"\n  },\n  \"version\": {\n    \"description\": \"Prints client version number and date into the console.\"\n  },\n  \"vid_displaylist\": {\n    \"system-generated\": true\n  },\n  \"vid_forcemode\": {\n    \"description\": \"This command will force ezQuake to use a certain video mode.\"\n  },\n  \"vid_fullscreen\": {\n    \"description\": \"This command will switch to a fullscreen video mode specified in the \\\"vid_fullscreen_mode\\\" variable.\"\n  },\n  \"vid_gfxinfo\": {\n    \"description\": \"This command will print out useful information about your video card, GL version, and refresh rate, video mode (width/height resolution and color depth) to console.\\nUseful to make sure everything is right, and also to screenshot to show other people.\"\n  },\n  \"vid_minimize\": {\n    \"description\": \"This command will minimize the windowed game screen.\\nIt was made available because the game takes control over the mouse when it is moved onto the game window thus prohibiting the normal minimization of the game window.\"\n  },\n  \"vid_modelist\": {\n    \"description\": \"Prints all supported video modes.\"\n  },\n  \"vid_reload\": {\n    \"system-generated\": true\n  },\n  \"vid_restart\": {\n    \"description\": \"Will restart your video renderer. Needed for some changes to take affect.\"\n  },\n  \"vid_testmode\": {\n    \"description\": \"This command will switch to the specified video mode for 5-seconds in order to test it.\"\n  },\n  \"vid_windowed\": {\n    \"description\": \"This command will switch the a windowed video mode specified in the \\\"vid_windowed_mode\\\" variable.\"\n  },\n  \"viewalias\": {\n    \"description\": \"Example:\\nviewalias mystatus\\n - prints mystatus alias.\"\n  },\n  \"vip_addip\": {\n    \"system-generated\": true\n  },\n  \"vip_listip\": {\n    \"system-generated\": true\n  },\n  \"vip_removeip\": {\n    \"system-generated\": true\n  },\n  \"vip_writeip\": {\n    \"system-generated\": true\n  },\n  \"vminfo\": {\n    \"system-generated\": true\n  },\n  \"wait\": {\n    \"description\": \"Adds one wait frame.\"\n  },\n  \"weapon\": {\n    \"description\": \"Weapon selection command.\\nWill select the best available weapon according to the sequence you choose.\",\n    \"syntax\": \"<w1> [<w2> [...]]\"\n  },\n  \"windows\": {\n    \"description\": \"Switches away from client and back to the Windows OS.\"\n  },\n  \"writeip\": {\n    \"description\": \"Records all IP addresses on the server IP list. The file name is listip.cfg.\"\n  },\n  \"z_ext_list\": {\n    \"system-generated\": true\n  }\n}\n"
  },
  {
    "path": "help_macros.json",
    "content": "{\n  \"ammo\": {\n    \"description\": \"returns amount of ammo held for currently selected weapon\",\n    \"teamplay-restricted\": false,\n    \"type\": \"integer\"\n  },\n  \"armor\": {\n    \"description\": \"returns current armor value\",\n    \"teamplay-restricted\": false,\n    \"type\": \"integer\"\n  },\n  \"armortype\": {\n    \"description\": \"returns current armor type\",\n    \"related-cvars\": [\n      \"tp_name_armortype_ga\",\n      \"tp_name_armortype_ya\",\n      \"tp_name_armortype_ra\",\n      \"tp_name_armortype_none\"\n    ],\n    \"teamplay-restricted\": false,\n    \"type\": \"string\"\n  },\n  \"bestammo\": {\n    \"description\": \"returns ammo held for the 'best' weapon\",\n    \"related-cvars\": [\n      \"tp_weapon_order\"\n    ],\n    \"teamplay-restricted\": false,\n    \"type\": \"integer\"\n  },\n  \"bestweapon\": {\n    \"description\": \"returns name of best weapon in inventory\",\n    \"related-cvars\": [\n      \"tp_weapon_order\",\n      \"tp_name_sg\",\n      \"tp_name_ssg\",\n      \"tp_name_ng\",\n      \"tp_name_sng\",\n      \"tp_name_gl\",\n      \"tp_name_rl\",\n      \"tp_name_lg\"\n    ],\n    \"teamplay-restricted\": false,\n    \"type\": \"string\"\n  },\n  \"cam_angles\": {\n    \"description\": \"returns current camera angles, in format that can be passed to /cam_angles command.\",\n    \"flags\": [\n      \"spectator-only\"\n    ],\n    \"teamplay-restricted\": true,\n    \"type\": \"string\"\n  },\n  \"cam_angles_pitch\": {\n    \"description\": \"returns current camera pitch (vertical angle).\",\n    \"flags\": [\n      \"spectator-only\"\n    ],\n    \"teamplay-restricted\": true,\n    \"type\": \"float\"\n  },\n  \"cam_angles_roll\": {\n    \"description\": \"returns current camera roll (not used).\",\n    \"flags\": [\n      \"spectator-only\"\n    ],\n    \"teamplay-restricted\": true,\n    \"type\": \"float\"\n  },\n  \"cam_angles_yaw\": {\n    \"description\": \"returns current camera yaw (horizontal angle).\",\n    \"flags\": [\n      \"spectator-only\"\n    ],\n    \"teamplay-restricted\": true,\n    \"type\": \"float\"\n  },\n  \"cam_pos\": {\n    \"description\": \"returns current camera position, in format that can be passed to /cam_pos command.\",\n    \"flags\": [\n      \"spectator-only\"\n    ],\n    \"teamplay-restricted\": true,\n    \"type\": \"string\"\n  },\n  \"cam_pos_x\": {\n    \"description\": \"returns one component of current camera position.\",\n    \"flags\": [\n      \"spectator-only\"\n    ],\n    \"teamplay-restricted\": true,\n    \"type\": \"float\"\n  },\n  \"cam_pos_y\": {\n    \"description\": \"returns one component of current camera position.\",\n    \"flags\": [\n      \"spectator-only\"\n    ],\n    \"teamplay-restricted\": true,\n    \"type\": \"float\"\n  },\n  \"cam_pos_z\": {\n    \"description\": \"returns one component of current camera position.\",\n    \"flags\": [\n      \"spectator-only\"\n    ],\n    \"teamplay-restricted\": true,\n    \"type\": \"float\"\n  },\n  \"cells\": {\n    \"description\": \"returns number of cells held, regardless of weapon selected\",\n    \"teamplay-restricted\": false,\n    \"type\": \"integer\"\n  },\n  \"colored_armor\": {\n    \"description\": \"returns $armor, surrounded by color codes so it is displayed in &c0b0red&r, &cff0yellow&r, &e00green&r or &cfffwhite&r\",\n    \"teamplay-restricted\": false,\n    \"type\": \"string\"\n  },\n  \"colored_powerups\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"colored_short_powerups\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"conheight\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"connectiontype\": {\n    \"description\": \"returns the current type of the connection\",\n    \"enum\": [\n      {\n        \"description\": \"client is disconnected from the server\",\n        \"value\": \"disconnected\"\n      },\n      {\n        \"description\": \"client is connected as a spectator\",\n        \"value\": \"spectator\"\n      },\n      {\n        \"description\": \"client is connected as a player\",\n        \"value\": \"player\"\n      }\n    ],\n    \"teamplay-restricted\": true,\n    \"type\": \"string\"\n  },\n  \"conwidth\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"date\": {\n    \"description\": \"returns the current date in the format <day>.<month>.<year>\",\n    \"teamplay-restricted\": true,\n    \"type\": \"string\"\n  },\n  \"dateiso\": {\n    \"system-generated\": true,\n    \"teamplay-restricted\": true\n  },\n  \"deathloc\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"demolength\": {\n    \"description\": \"returns the expected length of the current demo, in seconds\",\n    \"teamplay-restricted\": true,\n    \"type\": \"integer\"\n  },\n  \"demoname\": {\n    \"description\": \"returns the name of the current demo, minus extension\",\n    \"teamplay-restricted\": true,\n    \"type\": \"string\"\n  },\n  \"demoplayback\": {\n    \"description\": \"returns whether or not the client is current watching a demo\",\n    \"teamplay-restricted\": true,\n    \"type\": \"boolean\"\n  },\n  \"demotime\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"droploc\": {\n    \"description\": \"returns the name of the location where the last backpack was dropped by the current player.\",\n    \"teamplay-restricted\": false,\n    \"type\": \"string\"\n  },\n  \"droptime\": {\n    \"description\": \"returns the number of seconds since the last backpack was dropped by the current player.\",\n    \"teamplay-restricted\": false,\n    \"type\": \"integer\"\n  },\n  \"gamedir\": {\n    \"description\": \"returns the current gamedir, which is often set when a server is running a mod.\\ne.g: 'fortress' for Team Fortress mods\",\n    \"teamplay-restricted\": true,\n    \"type\": \"string\"\n  },\n  \"health\": {\n    \"description\": \"the current player's health (0 - 250)\",\n    \"teamplay-restricted\": false\n  },\n  \"lastip\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"lastloc\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"lastpowerup\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"latency\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"ledpoint\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"ledstatus\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"location\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"matchname\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"matchstatus\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"matchtype\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"mp3_volume\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"mp3info\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"nails\": {\n    \"description\": \"returns number of nails held, regardless of weapon selected\",\n    \"teamplay-restricted\": false,\n    \"type\": \"integer\"\n  },\n  \"need\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"ping\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"point\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"pointatloc\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"pointloc\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"powerups\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"qt\": {\n    \"description\": \"returns \\\" - useful to clear cvars in scripts\",\n    \"teamplay-restricted\": true\n  },\n  \"rand\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"rockets\": {\n    \"description\": \"returns number of rockets held, regardless of weapon selected\",\n    \"teamplay-restricted\": false,\n    \"type\": \"integer\"\n  },\n  \"serverip\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"shells\": {\n    \"description\": \"returns number of shells held, regardless of weapon selected\",\n    \"teamplay-restricted\": false,\n    \"type\": \"integer\"\n  },\n  \"team1\": {\n    \"system-generated\": true,\n    \"teamplay-restricted\": true\n  },\n  \"team2\": {\n    \"system-generated\": true,\n    \"teamplay-restricted\": true\n  },\n  \"tf_skin\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"time\": {\n    \"description\": \"returns the local time in the format <hours>:<minutes>\",\n    \"teamplay-restricted\": true,\n    \"type\": \"string\"\n  },\n  \"timestamp\": {\n    \"system-generated\": true,\n    \"teamplay-restricted\": true\n  },\n  \"took\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"tookatloc\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"tookloc\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  },\n  \"tp_powerups\": {\n    \"description\": \"$colored_powerups or $colored_short_powerups, depending on value of tp_poweruptextstyle\",\n    \"related-cvars\": [\n      \"tp_poweruptextstyle\"\n    ],\n    \"teamplay-restricted\": false,\n    \"type\": \"boolean\"\n  },\n  \"triggermatch\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"weapon\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"weaponnum\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": true\n  },\n  \"weapons\": {\n    \"flags\": [\n      \"incomplete\"\n    ],\n    \"teamplay-restricted\": false\n  }\n}"
  },
  {
    "path": "help_variables.json",
    "content": "{\n  \"groups\": [\n    {\n      \"id\": \"0\",\n      \"major-group\": \"Miscellaneous\",\n      \"name\": \"(added by system)\"\n    },\n    {\n      \"id\": \"2\",\n      \"major-group\": \"Miscellaneous\",\n      \"name\": \"Other\"\n    },\n    {\n      \"id\": \"3\",\n      \"major-group\": \"HUD\",\n      \"name\": \"Chat Settings\"\n    },\n    {\n      \"id\": \"4\",\n      \"major-group\": \"Miscellaneous\",\n      \"name\": \"Config Management\"\n    },\n    {\n      \"id\": \"5\",\n      \"major-group\": \"HUD\",\n      \"name\": \"Console Settings\"\n    },\n    {\n      \"id\": \"6\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"Crosshair Settings\"\n    },\n    {\n      \"id\": \"7\",\n      \"major-group\": \"Demos\",\n      \"name\": \"Demo Handling\"\n    },\n    {\n      \"id\": \"8\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"FPS and EyeCandy Settings\"\n    },\n    {\n      \"id\": \"9\",\n      \"major-group\": \"Input\",\n      \"name\": \"Input - Keyboard\"\n    },\n    {\n      \"id\": \"10\",\n      \"major-group\": \"Input\",\n      \"name\": \"Input - Misc\"\n    },\n    {\n      \"id\": \"11\",\n      \"major-group\": \"Input\",\n      \"name\": \"Input - Mouse\"\n    },\n    {\n      \"id\": \"12\",\n      \"major-group\": \"Miscellaneous\",\n      \"name\": \"IRC Client\"\n    },\n    {\n      \"id\": \"13\",\n      \"major-group\": \"Teamplay\",\n      \"name\": \"Item Names\"\n    },\n    {\n      \"id\": \"14\",\n      \"major-group\": \"Teamplay\",\n      \"name\": \"Item Need Amounts\"\n    },\n    {\n      \"id\": \"15\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"Lighting\"\n    },\n    {\n      \"id\": \"16\",\n      \"major-group\": \"Demos\",\n      \"name\": \"Match Tools\"\n    },\n    {\n      \"id\": \"17\",\n      \"major-group\": \"Miscellaneous\",\n      \"name\": \"Menu\"\n    },\n    {\n      \"id\": \"18\",\n      \"major-group\": \"Sound\",\n      \"name\": \"MP3 Settings\"\n    },\n    {\n      \"id\": \"19\",\n      \"major-group\": \"HUD\",\n      \"name\": \"MQWCL HUD\"\n    },\n    {\n      \"id\": \"20\",\n      \"major-group\": \"Demos\",\n      \"name\": \"MultiView Demos\"\n    },\n    {\n      \"id\": \"21\",\n      \"major-group\": \"Multiplayer\",\n      \"name\": \"Network Settings\"\n    },\n    {\n      \"id\": \"22\",\n      \"major-group\": \"Miscellaneous\",\n      \"name\": \"Not settable\"\n    },\n    {\n      \"id\": \"23\",\n      \"major-group\": \"Obsolete\",\n      \"name\": \"Obsolete\"\n    },\n    {\n      \"id\": \"24\",\n      \"major-group\": \"Obsolete\",\n      \"name\": \"Obsolete (2.2)\"\n    },\n    {\n      \"id\": \"25\",\n      \"major-group\": \"Obsolete\",\n      \"name\": \"Obsolete (2.2) - Custom Browser Fields\"\n    },\n    {\n      \"id\": \"26\",\n      \"major-group\": \"Obsolete\",\n      \"name\": \"Obsolete (3.0) - Audio\"\n    },\n    {\n      \"id\": \"27\",\n      \"major-group\": \"Obsolete\",\n      \"name\": \"Obsolete (3.0) - Authentication\"\n    },\n    {\n      \"id\": \"28\",\n      \"major-group\": \"Obsolete\",\n      \"name\": \"Obsolete (3.0) - Input Systems\"\n    },\n    {\n      \"id\": \"29\",\n      \"major-group\": \"Obsolete\",\n      \"name\": \"Obsolete (3.0) - Invalid\"\n    },\n    {\n      \"id\": \"30\",\n      \"major-group\": \"Obsolete\",\n      \"name\": \"Obsolete (3.0) - Software Renderer\"\n    },\n    {\n      \"id\": \"31\",\n      \"major-group\": \"Obsolete\",\n      \"name\": \"Obsolete (3.0) - Video\"\n    },\n    {\n      \"id\": \"32\",\n      \"major-group\": \"Sound\",\n      \"name\": \"VOIP\"\n    },\n    {\n      \"id\": \"33\",\n      \"major-group\": \"Obsolete\",\n      \"name\": \"Obsolete (Commands)\"\n    },\n    {\n      \"id\": \"34\",\n      \"major-group\": \"Obsolete\",\n      \"name\": \"Obsolete (server infokeys)\"\n    },\n    {\n      \"id\": \"35\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"OpenGL Rendering\"\n    },\n    {\n      \"id\": \"36\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"Particle Effects\"\n    },\n    {\n      \"id\": \"37\",\n      \"major-group\": \"Multiplayer\",\n      \"name\": \"Player Settings\"\n    },\n    {\n      \"id\": \"38\",\n      \"major-group\": \"Multiplayer\",\n      \"name\": \"QTV Settings\"\n    },\n    {\n      \"id\": \"39\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"Screen & Powerup Blends\"\n    },\n    {\n      \"id\": \"40\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"Screen Settings\"\n    },\n    {\n      \"id\": \"41\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"Screenshot Settings\"\n    },\n    {\n      \"id\": \"42\",\n      \"major-group\": \"Multiplayer\",\n      \"name\": \"Server Browser\"\n    },\n    {\n      \"id\": \"43\",\n      \"major-group\": \"Server\",\n      \"name\": \"Server Settings\"\n    },\n    {\n      \"id\": \"44\",\n      \"major-group\": \"Multiplayer\",\n      \"name\": \"Skin Settings\"\n    },\n    {\n      \"id\": \"45\",\n      \"major-group\": \"Sound\",\n      \"name\": \"Sound Settings\"\n    },\n    {\n      \"id\": \"46\",\n      \"major-group\": \"Multiplayer\",\n      \"name\": \"Spectator Tracking\"\n    },\n    {\n      \"id\": \"47\",\n      \"major-group\": \"HUD\",\n      \"name\": \"Status Bar and Scoreboard\"\n    },\n    {\n      \"id\": \"48\",\n      \"major-group\": \"Miscellaneous\",\n      \"name\": \"System Settings\"\n    },\n    {\n      \"id\": \"49\",\n      \"major-group\": \"Teamplay\",\n      \"name\": \"Teamplay Communications\"\n    },\n    {\n      \"id\": \"50\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"Texture Settings\"\n    },\n    {\n      \"id\": \"51\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"Turbulency and Sky Settings\"\n    },\n    {\n      \"id\": \"52\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"Video Settings\"\n    },\n    {\n      \"id\": \"53\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"View Settings\"\n    },\n    {\n      \"id\": \"54\",\n      \"major-group\": \"Graphics\",\n      \"name\": \"Weapon View Model Settings\"\n    }\n  ],\n  \"vars\": {\n    \"_vid_default_mode\": {\n      \"desc\": \"This variable sets the default video mode that the client uses.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"_vid_default_mode_win\": {\n      \"desc\": \"This variable sets the default video mode that the client uses when windowed.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"_windowed_mouse\": {\n      \"group-id\": \"28\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable windowed mouse support so you can use the mouse in other applications.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enabled windowed mouse support so you can use the mouse in the client.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"allow_download\": {\n      \"default\": \"1\",\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Clients can download game data from the server.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"allow_download_demos\": {\n      \"default\": \"1\",\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Clients can download demo files from the server.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"allow_download_gfx\": {\n      \"desc\": \"Enables downloading files from the server from the gfx directory.\",\n      \"group-id\": \"43\",\n      \"remarks\": \"Server-side.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"allow_download_maps\": {\n      \"default\": \"1\",\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Clients can download .bsp map files from the server.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"allow_download_models\": {\n      \"default\": \"1\",\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Clients can download .mdl files from the server.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"allow_download_other\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables downloading files from the server which are not in \\\"skins\\\", \\\"progs\\\", \\\"sound\\\", \\\"maps\\\" nor \\\"gfx\\\" directories.\",\n      \"group-id\": \"43\",\n      \"remarks\": \"Server-side.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"allow_download_pakmaps\": {\n      \"default\": \"1\",\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Clients can download map files in paks from the server.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"allow_download_skins\": {\n      \"default\": \"1\",\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Clients can download .pcx skin files from the server.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"allow_download_sounds\": {\n      \"default\": \"1\",\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Clients can download .wav sound files from the server.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"allow_f_cmdline\": {\n      \"default\": \"1\",\n      \"group-id\": \"3\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Your client will not respond to f_cmdline checks.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Your client will respond to f_cmdline checks with your current command line used to run current ezQuake instance.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"allow_f_system\": {\n      \"default\": \"1\",\n      \"group-id\": \"3\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Your client will not respond to f_system checks.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Your client will respond to f_system checks displaying your PC configuration.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"allow_scripts\": {\n      \"default\": \"2\",\n      \"desc\": \"Controls the complexity of movement scripts allowed in your client (e.g. rocket jump scripts). Other players can check your setting with 'f_scripts'.\",\n      \"group-id\": \"9\",\n      \"remarks\": \"If anyone said 'f_scripts' since the last map change, your client will also auto-report any change of allow_scripts.\\nCommand line switches -norjscripts or -noscripts set allow_scripts to 0.\\nMay not be changed during a match in some rulesets.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Same as 1, but also block fast +lookdown/+lookup, so even simple rjump will be impossible.\\nBlocks KTPro /kfjump and /krjump aliases.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Allow only simple scripts - it will block left/right turning, so fjump (for example) will be impossible, but normal rjump (+lookdown;+attack;+jump) will be possible.\\nBlocks KTPro alias /kfjump too.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Allow scripts (default).\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"auth_timeout\": {\n      \"desc\": \"Not currently used.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"auth_validate\": {\n      \"group-id\": \"27\",\n      \"remarks\": \"If you have 'auth_warninvalid 1' and someone gives a dirty hash in a version response (because they are using a hacked client that can't work out the right response), the client will print something like\\n'Warning Invalid Client: playername (userid)' in your console ('auth_warninvalid 0' is default).\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"If you don't want your client to validate other ezQuake clients.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Performs the validation (but you need to use 'validate_clients')\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"auth_viewcrc\": {\n      \"group-id\": \"27\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"the client hides the authentication CRC in f_version responses.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"the client shows you the authentication CRC in f_version responses.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"auth_warninvalid\": {\n      \"desc\": \"Check auth_validate.\",\n      \"group-id\": \"27\",\n      \"type\": \"float\"\n    },\n    \"b_switch\": {\n      \"default\": \"\",\n      \"desc\": \"This variable allows you to define the highest weapon that the client should switch to upon a backpack pickup.\\nThe possible arguments of \\\"b_switch\\\" refer to the impulse that is used to switch to a certain weapon.\\nNote that a setting of 1 will effectively disable backpack weapon switching.\",\n      \"group-id\": \"37\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Axe\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Shotgun\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Double-Barreled Shotgun\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Nailgun\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Super Nailgun\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Grenade Launcher\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Rocket Launcher\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"ThunderBolt\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"baseskin\": {\n      \"default\": \"base\",\n      \"desc\": \"Defines what skin you see other people using if you don't have their skin and don't have skin forcing on.\",\n      \"group-id\": \"44\",\n      \"type\": \"string\"\n    },\n    \"bgmvolume\": {\n      \"default\": \"1\",\n      \"desc\": \"This variable sets the volume of the CD music.\",\n      \"group-id\": \"45\",\n      \"type\": \"float\"\n    },\n    \"block_switch\": {\n      \"group-id\": \"31\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not block task-switching\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Block task-switching\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"bottomcolor\": {\n      \"default\": \"\",\n      \"desc\": \"Sets the pants color.\",\n      \"group-id\": \"37\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"White\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Brown\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Lavender\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Khaki\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Red\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Lt Brown\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Peach\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Lt Peach\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Purple\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Dk Purple\",\n          \"name\": \"9\"\n        },\n        {\n          \"description\": \"Tan\",\n          \"name\": \"10\"\n        },\n        {\n          \"description\": \"Green\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"Yellow\",\n          \"name\": \"12\"\n        },\n        {\n          \"description\": \"Blue\",\n          \"name\": \"13\"\n        },\n        {\n          \"description\": \"Orange.\",\n          \"name\": \"14\"\n        },\n        {\n          \"description\": \"Bright red.\",\n          \"name\": \"15\"\n        },\n        {\n          \"description\": \"Black.\",\n          \"name\": \"16\"\n        }\n      ]\n    },\n    \"cam_dist\": {\n      \"default\": \"100\",\n      \"desc\": \"Distance from player. Use +forward/+back to adjust it smoothly.\",\n      \"group-id\": \"46\",\n      \"remarks\": \"For use with cam_thirdperson.\",\n      \"type\": \"float\"\n    },\n    \"cam_lockdir\": {\n      \"default\": \"0\",\n      \"desc\": \"Force camera to locked direction mode.\",\n      \"group-id\": \"46\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't force\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Force\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cam_lockpos\": {\n      \"default\": \"0\",\n      \"desc\": \"Force camera to locked position mode.\",\n      \"group-id\": \"46\",\n      \"remarks\": \"EXPERIMENTAL.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't force\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Force\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cam_thirdperson\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables third person view in demo playback and spectator mode.\\nIn track mode, we look at the person being tracked rather than through his eyes.\\nUnlike cl_camera_tpp, you can use the mouse to look around.\",\n      \"group-id\": \"46\",\n      \"remarks\": \"in track mode, we look at the person being tracked rather than through his eyes.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cam_zoomaccel\": {\n      \"default\": \"2000\",\n      \"desc\": \"To control how fast you zoom in onto the target with +forward/+back in cam_thirdperson mode.\",\n      \"group-id\": \"46\",\n      \"type\": \"float\"\n    },\n    \"cam_zoomspeed\": {\n      \"default\": \"300\",\n      \"desc\": \"To control how fast you zoom in onto the target with +forward/+back in cam_thirdperson mode.\",\n      \"group-id\": \"46\",\n      \"type\": \"float\"\n    },\n    \"cfg_backup\": {\n      \"default\": \"0\",\n      \"group-id\": \"4\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"No backup of your config.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Backs up your old config before overwriting it with cfg_save.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_browser_democolor\": {\n      \"desc\": \"Color of the demo entries in the cfg browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"cfg_browser_dircolor\": {\n      \"desc\": \"Color of the dir entries in the cfg browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"cfg_browser_interline\": {\n      \"desc\": \"Size of the space between entries in the cfg browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"integer\"\n    },\n    \"cfg_browser_scrollnames\": {\n      \"desc\": \"Toggle scrolling of the filenames in the cfg browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_browser_selectedcolor\": {\n      \"desc\": \"Color of the selected entries in the cfg browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"cfg_browser_showdate\": {\n      \"desc\": \"Toggle the date column in the cfg browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_browser_showsize\": {\n      \"desc\": \"Toggle the file size column in the cfg browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_browser_showstatus\": {\n      \"desc\": \"Toggle the display of the status bar in the cfg browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_browser_showtime\": {\n      \"desc\": \"Toggle the time column in the cfg browse.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_browser_sortmode\": {\n      \"desc\": \"Sorting mode in the cfg browser. Each number represents one column. Their order represents the priority of the sorting.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"cfg_browser_stripnames\": {\n      \"desc\": \"Toggle stripping of the filenames in the cfg browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_browser_zipcolor\": {\n      \"desc\": \"Color of the zip entries in the cfg browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"cfg_legacy_exec\": {\n      \"default\": \"1\",\n      \"group-id\": \"4\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not execute config.cfg and frontend.cfg in gamedir ever.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Execute config.cfg and frontend.cfg in gamedir (unless gamedir = qw)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Execute config.cfg and frontend.cfg even if gamedir = qw.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Execute config.cfg and frontend.cfg even if gamedir = qw. If not found in current gamedir, execute from qw dir.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"cfg_save_aliases\": {\n      \"default\": \"1\",\n      \"group-id\": \"4\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Won't save aliases.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"cfg_save saves aliases.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_save_binds\": {\n      \"default\": \"1\",\n      \"group-id\": \"4\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Won't save binds.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Toggles whether cfg_save saves binds.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_save_cmdline\": {\n      \"default\": \"1\",\n      \"group-id\": \"4\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Won't save command line.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Writes your command line in configs made with cfg_save (commented out of course)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_save_cmds\": {\n      \"default\": \"1\",\n      \"group-id\": \"4\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Won't save commands.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Saves commands.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_save_cvars\": {\n      \"default\": \"1\",\n      \"group-id\": \"4\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Won't save variables.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Saves variables.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_save_onquit\": {\n      \"default\": \"0\",\n      \"desc\": \"When enabled, your main configuration (config.cfg) will be saved when you exit the application.\",\n      \"group-id\": \"4\",\n      \"remarks\": \"Configuration will be saved in the main user configuration file, that is config.cfg which can either be placed in the quake/ezquake/configs in your quakedir, or in your home dir in /ezquake, depending on cfg_use_home setting.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not save configuration on exit\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Automatically save the configuration on exit\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_save_sysinfo\": {\n      \"default\": \"0\",\n      \"desc\": \"Not implemented yet.\",\n      \"group-id\": \"4\",\n      \"type\": \"boolean\"\n    },\n    \"cfg_save_unchanged\": {\n      \"default\": \"0\",\n      \"group-id\": \"4\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Makes cfg_save only write variables that are not default valued to the config file.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Makes cfg_save write all variables to the config file.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cfg_save_userinfo\": {\n      \"default\": \"2\",\n      \"group-id\": \"4\",\n      \"remarks\": \"Note: 'cfg_save_userinfo 1' is best for teamfortress so you don't get kicked for changing bottom color.\\ncfg_save will never save the password variable, even though technically it is a userinfo variable.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not save userinfo variables.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Save all userinfo variables except spectator, topcolor, bottomcolor, teamcolor, skin, team, rate, msg, w and b_switch.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Save all userinfo variables\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cfg_use_gamedir\": {\n      \"default\": \"0\",\n      \"desc\": \"If set & gamedir is not 'qw', configurations will be saved to subdirectory based on gamedir.\\nUseful if configurations differ depending on gametype (e.g. team fortress)\",\n      \"group-id\": \"4\",\n      \"type\": \"boolean\"\n    },\n    \"cfg_use_home\": {\n      \"default\": \"0\",\n      \"desc\": \"When turned on, configuration will be saved into user's profile (home) directory.\",\n      \"group-id\": \"4\",\n      \"remarks\": \"This affects only cfg_save and cfg_load commands.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Save the configuration into <quake>/ezquake/configs\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Save the configuration into user's home dir\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_allow_downloads\": {\n      \"default\": \"bsp,lmp,loc,mdl,mvd,pcx,spr,wad,wav\",\n      \"desc\": \"This variable controls which file extensions a client/server can download/upload from/to the server/client.\",\n      \"group-id\": \"9\",\n      \"type\": \"string\"\n    },\n    \"cl_allow_uploads\": {\n      \"default\": \"0\",\n      \"group-id\": \"9\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't allow uploads.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Allow uploads.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"cl_anglespeedkey\": {\n      \"default\": \"1.5\",\n      \"desc\": \"This variable sets multiplier by which your \\\"cl_yawspeed\\\" (how fast you turn) is multiplied when running (+speed).\",\n      \"group-id\": \"9\",\n      \"type\": \"float\"\n    },\n    \"cl_backpackfilter\": {\n      \"default\": \"0\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't filter backpacks.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Filter backpacks.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"cl_backspeed\": {\n      \"default\": \"400\",\n      \"desc\": \"This allows you to set your backward speed.\\nObviously this is also limited by the server, usually to \\\"320\\\".\",\n      \"group-id\": \"9\",\n      \"remarks\": \"Sign of the value is ignored.\",\n      \"type\": \"float\"\n    },\n    \"cl_bob\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable controls how much your weapon moves up and down when walking.\",\n      \"group-id\": \"54\",\n      \"type\": \"float\"\n    },\n    \"cl_bobcycle\": {\n      \"default\": \"0.0\",\n      \"desc\": \"This variable determines how quickly your weapon moves up and down when walking.\",\n      \"group-id\": \"54\",\n      \"type\": \"float\"\n    },\n    \"cl_bobhead\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_bobup\": {\n      \"default\": \"0.0\",\n      \"desc\": \"This variable controls how long your weapon stays up before cycling when walking.\",\n      \"group-id\": \"54\",\n      \"type\": \"float\"\n    },\n    \"cl_bonusflash\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls weapon and item pickup flash.\",\n      \"group-id\": \"39\",\n      \"type\": \"boolean\"\n    },\n    \"cl_c2sImpulseBackup\": {\n      \"default\": \"3\",\n      \"desc\": \"Used with cl_c2spps, it controls how many backup copies of packets with non-zero impulses are to be sent to the server.\\nThe recommended value is 3, but you can try 2 or even 1 to reduce traffic if you don't have any packet loss.\",\n      \"group-id\": \"21\",\n      \"type\": \"integer\"\n    },\n    \"cl_c2sdupe\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls whether to send duplicate packets to the server.\",\n      \"group-id\": \"21\",\n      \"remarks\": \"Requires server support.\",\n      \"type\": \"integer\"\n    },\n    \"cl_c2spps\": {\n      \"default\": \"0\",\n      \"desc\": \"Packet filtering (a la Qizmo's .c2spps command).\\nUse this to reduce network traffic if you're playing on a 28800 (or worse) connection and can't set cl_maxfps 72 because it causes lag.\\nAlso consider use of FPS independent physics in conjunction with cl_physfps.\",\n      \"group-id\": \"21\",\n      \"type\": \"integer\"\n    },\n    \"cl_camera_death\": {\n      \"default\": \"0\",\n      \"desc\": \"Camera view above your body after death.\",\n      \"group-id\": \"46\",\n      \"remarks\": \"Enabled only for viewing demos and observing games.\",\n      \"type\": \"boolean\"\n    },\n    \"cl_camera_tpp\": {\n      \"default\": \"0\",\n      \"group-id\": \"46\",\n      \"remarks\": \"Enabled only for viewing demos and observing games.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"1st person view\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"3rd person view ala Tomb Raider (such a fun!)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"3rd person view ala cl_chasecam 0 (buggy)\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_camera_tpp_distance\": {\n      \"default\": \"-56\",\n      \"desc\": \"Sets distance of 3rd person camera from tracked player.\\nYou can use negative values too.\",\n      \"group-id\": \"46\",\n      \"remarks\": \"For use with cl_camera_tpp. See cl_camera_tpp_height too.\",\n      \"type\": \"float\"\n    },\n    \"cl_camera_tpp_height\": {\n      \"default\": \"24\",\n      \"desc\": \"Sets vertical position of 3rd person camera.\\nYou can use negative values too.\",\n      \"group-id\": \"46\",\n      \"remarks\": \"Set cl_camera_tpp 1 first.\",\n      \"type\": \"float\"\n    },\n    \"cl_chasecam\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggle between 3rd-person view and 1st-person view while observing or during demo playback.\",\n      \"group-id\": \"46\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Third-person view.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"First-person (\\\"through-eyes\\\") view.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_chatmode\": {\n      \"default\": \"2\",\n      \"desc\": \"Console chat mode.\",\n      \"group-id\": \"5\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Text in the console is always treated as a command and in order to chat you have to use messagemode/messagemode2 or use the say/say_team commands.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Everything typed in the console goes into chat. In order to issue a command, prefix it with a slash (/).\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"If the first word in the line is a command, it is executed. Otherwise, the line is sent as chat.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_chunksperframe\": {\n      \"default\": \"30\",\n      \"desc\": \"Affects the download speed when using chunked downloads, more chunks per frame results in higher download speed.\",\n      \"group-id\": \"21\",\n      \"remarks\": \"Servers can limit the amount of chunks sent per frame.\",\n      \"type\": \"integer\"\n    },\n    \"cl_clock\": {\n      \"default\": \"0\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Off.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Shows the time you spent on server.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Shows the time of day.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_clock_format\": {\n      \"default\": \"0\",\n      \"desc\": \"Changes the format of the clock which is displayed if cl_clock is non-zero.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"This was changed to an enumerated type in ezQuake 3.5, to match hud_clock_format.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"24hr time with seconds\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"12hr time (AM/PM) without seconds\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"12hr time (AM/PM) with seconds\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"24hr time without seconds\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"cl_clock_x\": {\n      \"default\": \"0\",\n      \"desc\": \"Horizontal coordinates of the clock.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"cl_clock_y\": {\n      \"default\": \"-1\",\n      \"desc\": \"Vertical coordinates of the clock.\\nIf < 0, the coordinates are calculated from bottom up, e.g. -1 means the screen line just above the scoreboard.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"cl_cmdline\": {\n      \"default\": \"\",\n      \"desc\": \"Read-only variable showing you what were the commandline options used to launch the client.\",\n      \"group-id\": \"2\",\n      \"type\": \"string\"\n    },\n    \"cl_confirmquit\": {\n      \"default\": \"0\",\n      \"desc\": \"This sets whether to confirm on quit or quit with no confirmation.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not ask for confirmation on quit.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Ask for confirmation on quit. This will display the 'About' message box where you have to press [Y] to quit.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_crossx\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable allows you to move the position of the crosshair on the X-axis by the specified amount of pixels.\",\n      \"group-id\": \"6\",\n      \"type\": \"float\"\n    },\n    \"cl_crossy\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable allows you to move the position of the crosshair on the Y-axis by the specified amount of pixels.\",\n      \"group-id\": \"6\",\n      \"type\": \"float\"\n    },\n    \"cl_crypt_rcon\": {\n      \"default\": \"1\",\n      \"desc\": \"Encrypts rcon messages sent to server.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\"\n    },\n    \"cl_curlybraces\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables new syntax to be used for Quake scripting allowing you to enclose commands into curly braces.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"See scripting manual for further info.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_deadbodyFilter\": {\n      \"default\": \"0\",\n      \"group-id\": \"8\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Don't filter dead bodies.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Filter dead bodies after they fall on the ground.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Instantly filter dead bodies.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Filter dead bodies (instantly) unless you are playing a TeamFortress game.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"cl_debug_antilag_ghost\": {\n      \"default\": \"0\",\n      \"desc\": \"Allows rendering a translucent copy of player position on supported .mvd/qtv streams.\",\n      \"group-id\": \"21\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Normal\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Antilag-rewind\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Client position\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_debug_antilag_lines\": {\n      \"default\": \"0\",\n      \"desc\": \"Chooses if lines are drawn between the different player positions on supported .mvd/qtv streams.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\"\n    },\n    \"cl_debug_antilag_self\": {\n      \"default\": \"0\",\n      \"desc\": \"Chooses whether to include the currently tracked player in antilag debugging.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\"\n    },\n    \"cl_debug_antilag_send\": {\n      \"default\": \"0\",\n      \"desc\": \"Sends location of opponents, which is stored in .mvd on supported servers.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\"\n    },\n    \"cl_debug_antilag_view\": {\n      \"default\": \"0\",\n      \"desc\": \"Chooses which location is used when rendering players on supported .mvd/qtv streams.\",\n      \"group-id\": \"21\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Normal\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Antilag-rewind\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Client position\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_debug_weapon_send\": {\n      \"default\": \"0\",\n      \"desc\": \"Sends information about client-side weapon selection, which is stored in .mvd on supported servers.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\"\n    },\n    \"cl_debug_weapon_view\": {\n      \"default\": \"0\",\n      \"desc\": \"Views the client-side weapon debugging messages, if stored in .mvd.\",\n      \"group-id\": \"0\",\n      \"type\": \"boolean\"\n    },\n    \"cl_delay_packet\": {\n      \"default\": \"0\",\n      \"desc\": \"Client will delay incoming and outgoing packets, allowing user to increase his ping in the game.\",\n      \"group-id\": \"21\",\n      \"remarks\": \"Specified in ms. If delay is 20ms, incoming packets will be delayed by 10ms, outgoing packets too. It's preferred to use the least value that gives the desired ping.\",\n      \"type\": \"integer\"\n    },\n    \"cl_delay_packet_deviation\": {\n      \"default\": \"0\",\n      \"desc\": \"Allows random deviation to be added to /cl_delay_packet delay.\",\n      \"group-id\": \"21\",\n      \"remarks\": \"Specified in ms.  Makes /cl_delay_packet better simulate high-ping connections.\",\n      \"type\": \"integer\"\n    },\n    \"cl_delay_packet_target\": {\n      \"default\": \"0\",\n      \"desc\": \"Targets a particular ping, rather than specifying the additional delay.\\nAdds half the ping to the outgoing packet and the remainder to incoming.\",\n      \"group-id\": \"21\",\n      \"type\": \"integer\"\n    },\n    \"cl_demoPingInterval\": {\n      \"default\": \"5\",\n      \"desc\": \"How often to request ping updates when recording demos.\\nThis variable doesn't affect ping updates when the scoreboard is shown (they are always one update per 2 seconds).\",\n      \"group-id\": \"7\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Disable automatic ping requests.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Request every 5 seconds (default).\",\n          \"name\": \"5\"\n        }\n      ]\n    },\n    \"cl_demo_qwd_delta\": {\n      \"default\": \"1\",\n      \"desc\": \"Whether or not to write delta packets for entities into .qwd files.\\nOlder clients may not be able to play these back.\",\n      \"group-id\": \"7\",\n      \"type\": \"boolean\"\n    },\n    \"cl_democlock\": {\n      \"default\": \"0\",\n      \"desc\": \"A clock showing how much time has elapsed since the start of the demo.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\"\n    },\n    \"cl_democlock_x\": {\n      \"default\": \"0\",\n      \"desc\": \"Determine where the democlock is positioned on your screen on the X co-ordinate.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"cl_democlock_y\": {\n      \"default\": \"-2\",\n      \"desc\": \"Determine where the democlock is positioned on your screen on the Y co-ordinate.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"cl_demoplay_flash\": {\n      \"default\": \"0.33\",\n      \"desc\": \"Reduces flash grenade effect when watching demos.\",\n      \"group-id\": \"39\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Flash effects disabled\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Adjust the intensity of the flash.\",\n          \"name\": \"0.x\"\n        },\n        {\n          \"description\": \"Original\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"cl_demospeed\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls the speed of demo playback in percentage (can be changed during demo playback if you wish).\",\n      \"group-id\": \"7\",\n      \"type\": \"float\"\n    },\n    \"cl_demoteamplay\": {\n      \"default\": \"0\",\n      \"desc\": \"If set, teamplay settings enabled during .dem playback.\",\n      \"group-id\": \"7\",\n      \"type\": \"boolean\"\n    },\n    \"cl_earlypackets\": {\n      \"default\": \"1\",\n      \"desc\": \"Read network data independently on physical frames.\\nWhen using independent physics, network data will be read as early as possible, compared to the old way when it was read only on every physframe (77 times per second).\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_fakename\": {\n      \"default\": \"\",\n      \"desc\": \"Automatically prefixes all team messages with a shorter version of your nick unless the message has a \\\"fake\\\" part already (so no configs are broken).\",\n      \"group-id\": \"3\",\n      \"remarks\": \"Applies for say_team and messagemode2.\",\n      \"type\": \"string\"\n    },\n    \"cl_fakename_suffix\": {\n      \"default\": \": \",\n      \"desc\": \"Suffix for cl_fakename.\",\n      \"group-id\": \"3\",\n      \"type\": \"string\"\n    },\n    \"cl_fakeshaft\": {\n      \"default\": \"0\",\n      \"desc\": \"Smoothes out shaft movement.\\n0 = no smoothing at all\\n1 = 100% smoothing\\nA value of about 0.5 is recommended for modem players.\\n\\nNote that this only affects the visual display of the shaft beam; the 'true' beam remains in the original location.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"cl_fakeshaft_extra_updates\": {\n      \"default\": \"1\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Update shaft position only when network packet received from server\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Update shaft position every frame\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"cl_filterdrawviewmodel\": {\n      \"default\": \"0\",\n      \"desc\": \"Prevents r_drawviewmodel from being changed by servers like Rocket Arena.\",\n      \"group-id\": \"54\",\n      \"type\": \"boolean\"\n    },\n    \"cl_fix_mvd\": {\n      \"default\": \"0\",\n      \"desc\": \"A fix for buggy MVD demos, making the client to parse them properly\",\n      \"group-id\": \"21\",\n      \"remarks\": \"Was caused by a flaw in MVDSV server\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_floodprot\": {\n      \"default\": \"0\",\n      \"group-id\": \"3\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable floodprot.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable floodprot.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_forwardspeed\": {\n      \"default\": \"400\",\n      \"desc\": \"This allows you to set your forward speed.\\nObviously this is also limited by the server, usually to \\\"320\\\".\",\n      \"group-id\": \"9\",\n      \"remarks\": \"Sign of the value is ignored.\",\n      \"type\": \"float\"\n    },\n    \"cl_fp_messages\": {\n      \"default\": \"4\",\n      \"desc\": \"This variable is used in conjunction with the variable \\\"cl_fp_persecond\\\" to define when the floodprot protection should be triggered (if \\\"cl_floodprot\\\" is set to \\\"1\\\").\",\n      \"group-id\": \"3\",\n      \"type\": \"float\"\n    },\n    \"cl_fp_persecond\": {\n      \"default\": \"4\",\n      \"desc\": \"This variable is used in conjunction with the variable \\\"cl_fp_messages\\\" to define when the floodprot protection should be triggered (if \\\"cl_floodprot\\\" is set to \\\"1\\\").\",\n      \"group-id\": \"3\",\n      \"type\": \"float\"\n    },\n    \"cl_gameclock\": {\n      \"default\": \"0\",\n      \"desc\": \"Displays clock with seconds on the screen.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"Use cl_gameclock_x and cl_gameclock_y to place it anywhere on the screen.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Turned OFF\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Counts up from the start of the match.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Counts down from the start of the match.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Same as 1 but only minutes:seconds (hours are not being displayed).\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Same as 2 but only minutes:seconds (hours are not being displayed).\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"cl_gameclock_offset\": {\n      \"default\": \"0\",\n      \"desc\": \"Allows using gameclock in custom mods that don't support standard KT-like clock synchronization.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"Some Capture The Flag or Team Fortress mods can take a use of this.\",\n      \"type\": \"integer\"\n    },\n    \"cl_gameclock_x\": {\n      \"default\": \"0\",\n      \"desc\": \"Adjusts horizontal placement of the clock with seconds.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"See cl_gameclock for detailed info about the clock.\",\n      \"type\": \"float\"\n    },\n    \"cl_gameclock_y\": {\n      \"default\": \"-3\",\n      \"desc\": \"Adjusts vertical placement of the clock with seconds.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"See cl_gameclock for detailed info about the clock.\",\n      \"type\": \"float\"\n    },\n    \"cl_gameclock_style\": {\n      \"default\": \"0\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"White numbers\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Red numbers\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Green numbers (high bit)\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Green numbers (low bit)\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"cl_gibFilter\": {\n      \"default\": \"0\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Gibs are displayed.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Gibs are filtered.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_hidenails\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles nails visibility.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"cl_hiderockets\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles rockets visibility. Variable cl_r2g must be 0 for this to work.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"cl_hightrack\": {\n      \"default\": \"0\",\n      \"desc\": \"Turns auto-tracking player with most frags ON when spectating or watching a demo.\",\n      \"group-id\": \"46\",\n      \"type\": \"float\"\n    },\n    \"cl_hud\": {\n      \"default\": \"1\",\n      \"desc\": \"Enables/Disables strings-hud.\\nStrings hud is not mqwcl hud. It gives you ability put any string (or value of some variable) on your hud.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"Strings hud banned for ruleset smackdown and smackdrive.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Strings hud disabled\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Strings hud enabled\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_hudswap\": {\n      \"default\": \"0\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"The inventory is drawn on the right side of the screen.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"The inventory is drawn on the left side of the screen.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_iDrive\": {\n      \"default\": \"0\",\n      \"desc\": \"Emulates \\\"strafe script\\\". When turned on, you will always change direction instead of stopping when holding opposing movement keys +moveleft and +moveright, or +forward and +back.\",\n      \"group-id\": \"9\",\n      \"remarks\": \"Reported in your f_ruleset reply with the '+i' flag.\\nCan not be changed during a match.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Pressing keys for both directions will stop movement on that axis.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Auto-releases one of the keys when both directions are pressed.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_independentPhysics\": {\n      \"default\": \"1\",\n      \"desc\": \"This enables independent physics. This means that you can achieve more FPS in the game than allowed by the server.\\nThe amount of FPS used to communicate with the server is set by /cl_physfps and the amount of graphics FPS is set by /cl_maxfps.\\nNote that you must have vid_vsync 0.\",\n      \"group-id\": \"8\",\n      \"remarks\": \"This variable can only be switched from 0<->1 when disconnected from a server (Qizmo/eztv are also servers).\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable independent physics.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable independent physics.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_keypad\": {\n      \"default\": \"1\",\n      \"group-id\": \"9\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"QW compatibility: all keypad keys treated as though equivalent button pushed, eg KP_8 acts like uparrow would.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"You will be able to bind all numpad keys independently of standard keys.\\nUse kp_enter, kp_8 etc to differentiate from standard keys.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"As 'cl_keypad 0' in game, 'cl_keypad 1' when in menus/console etc.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_lerp_monsters\": {\n      \"default\": \"1\",\n      \"desc\": \"Enables linear interpolation on Quake monsters and creatures.\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_loadFragfiles\": {\n      \"default\": \"1\",\n      \"group-id\": \"47\",\n      \"remarks\": \"Also needed to parse stats for extended scoreboard and frags tracker.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable displaying team fortress related statistics in the scoreboard (flag touches, steals, caps, etc)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_maxfps\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable sets the maximum limit for frames-per-second while in-game. Please see cl_maxfps_menu, vid_vsync, cl_independentphysics, and cl_physfps.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"cl_maxfps_menu\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable sets the maximum limit for frames-per-second while disconnected, has a minimum of 30, set lower to use display frequency.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"cl_mediaroot\": {\n      \"default\": \"0\",\n      \"desc\": \"Changes where demos, screenshots and logs are saved, how variables demo_dir, sshot_dir and log_dir are treated.\",\n      \"group-id\": \"48\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Treat paths as relative to the Quake dir\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Treat paths as relative to user home dir\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Treat paths as system absolute paths\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_model_bobbing\": {\n      \"default\": \"1\",\n      \"desc\": \"Rotating models bob up and down like in Quake3.\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\"\n    },\n    \"cl_movespeedkey\": {\n      \"default\": \"2.0\",\n      \"desc\": \"This variable is the multiplier for how fast you move when running (+speed) in relation to when walking (-speed).\",\n      \"group-id\": \"9\",\n      \"remarks\": \"Sign of the value is ignored.\",\n      \"type\": \"float\"\n    },\n    \"cl_multiview\": {\n      \"default\": \"0\",\n      \"desc\": \"This client adds a multiview component to mvd playback. Up to four views can be displayed at once. Use this variable to turn multiview on.\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Multiview disabled\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Still no multiview but you can use cl_mvdisplayhud to show small complex hud.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Two screens.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Three screens.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Four screens.\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"cl_muzzleflash\": {\n      \"default\": \"1\",\n      \"group-id\": \"8\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"All turned off.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"All turned on.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Own flash off, all other on.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_mvdisplayhud\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggle drawing of small compact HUD in each screen of Multiview control.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"Also see cl_mvhudpos and cl_mvhudvertical when this is set to a value greater than 1.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Don't draw mini-HUDs when in multiview.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Draw original mini-HUDs when in multiview. (Only strings)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Draw names only.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Draw graphics. Icons for weapons, healthbar, armorbar.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Same as 3 but with values showing the health/armor on the bars.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Same as 4 but with a semitransparent black background.\",\n          \"name\": \"5\"\n        }\n      ]\n    },\n    \"cl_mvhudflip\": {\n      \"default\": \"0\",\n      \"desc\": \"Flips the mini-HUD in multiview mode.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"This only works when cl_mvdisplayhud is greater than 1. See also cl_mvhudvertical and cl_mvhudpos.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't flip the mini-HUD drawing.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Flip the mini-HUD drawing.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_mvhudpos\": {\n      \"default\": \"bottom center\",\n      \"desc\": \"Sets the position of the multiview mini-HUDs.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"This only applies to the mini-HUD when cl_mvdisplayhud has a value greater than 1.\\nOtherwise old mini-HUD will be used (just strings).\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"bottom, centered horizontally\",\n          \"name\": \"bottom center\"\n        },\n        {\n          \"description\": \"bottom, left hand side\",\n          \"name\": \"bottom left\"\n        },\n        {\n          \"description\": \"bottom, right hand side\",\n          \"name\": \"bottom right\"\n        },\n        {\n          \"description\": \"The mini-HUDs are all \\\"gathered\\\" towards the center of the screen. Top left view has the hud in the lower right corner; bottom left view has it in the upper left corner and so on.\",\n          \"name\": \"gather\"\n        },\n        {\n          \"description\": \"top, centered horizontally\",\n          \"name\": \"top center\"\n        },\n        {\n          \"description\": \"top, left hand side\",\n          \"name\": \"top left\"\n        },\n        {\n          \"description\": \"top, right hand side\",\n          \"name\": \"top right\"\n        },\n        {\n          \"description\": \"bottom, centered horizontally\",\n          \"name\": \"bc\"\n        },\n        {\n          \"description\": \"bottom, left hand side\",\n          \"name\": \"bl\"\n        },\n        {\n          \"description\": \"bottom, right hand side\",\n          \"name\": \"br\"\n        },\n        {\n          \"description\": \"The mini-HUDs are all \\\"gathered\\\" towards the center of the screen. Top left view has the hud in the lower right corner; bottom left view has it in the upper left corner and so on.\",\n          \"name\": \"g\"\n        },\n        {\n          \"description\": \"top, centered horizontally\",\n          \"name\": \"tc\"\n        },\n        {\n          \"description\": \"top, left hand side\",\n          \"name\": \"tl\"\n        },\n        {\n          \"description\": \"top, right hand side\",\n          \"name\": \"tr\"\n        }\n      ]\n    },\n    \"cl_mvhudvertical\": {\n      \"default\": \"0\",\n      \"desc\": \"Set whetever the mini-HUDs in multiview mode will be drawn vertically or not.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"This only applies if cl_mvdisplayhud has a value greater than 1, otherwise the old style is used for the mini-HUDs (only strings).\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Draw mini-HUDs horizontally\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw mini-HUDs vertically\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_mvinset\": {\n      \"default\": \"0\",\n      \"desc\": \"Turns inset screen with multitrack on/off.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Divide screen in 2/3/4 areas according to cl_multiview settings.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display 2nd POV inside main screen in the right top corner.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_mvinset_offset_x\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_mvinset_offset_y\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_mvinset_right\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_mvinset_size_x\": {\n      \"default\": \"0.333\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_mvinset_size_y\": {\n      \"default\": \"0.333\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_mvinset_top\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_mvinsetcrosshair\": {\n      \"default\": \"1\",\n      \"desc\": \"Turn crosshair in inset POV in multiview on/off.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not display crosshair in inset POV.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display crosshair in inset POV.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_mvinsethud\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns inset HUD for inset POV with multiview on/off.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not display inset HUD.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display inset HUD (usually nick of tracked player).\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_name_as_skin\": {\n      \"default\": \"0\",\n      \"desc\": \"Allows you to override user skin settings and use player's name or ID as a his (her) skin.\",\n      \"group-id\": \"44\",\n      \"remarks\": \"There are many other skin settings that can override, e.g. all enemy*skin, team*skin settings override this setting.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Use player's skin setting\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Use player's name as his skin\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Use player's ID as his skin\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_net_clientport\": {\n      \"default\": \"27001\",\n      \"desc\": \"The UDP port opened by the client for communication with the server.\",\n      \"group-id\": \"21\",\n      \"remarks\": \"Can set to 0 to use a dynamically allocated port\",\n      \"type\": \"integer\"\n    },\n    \"cl_newlerp\": {\n      \"default\": \"0\",\n      \"desc\": \"Experimental rockets/grenades/spikes smoothing code.\\nFor example, a value of 0.1 means use 90% of our 'vision' and 10% of server 'vision'. Should be OK for most cases (see remarks).\",\n      \"group-id\": \"8\",\n      \"remarks\": \"Range between 0 and 1.\\nThis smoothing algorithm only works well with stable ping.\",\n      \"type\": \"float\"\n    },\n    \"cl_nodelta\": {\n      \"default\": \"0\",\n      \"desc\": \"Control the network packet delta compression.\\nWhen you get blue lines in your netgraph, you should set 'cl_nodelta 1'.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Enable delta compression.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Disable delta compression.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_nofake\": {\n      \"default\": \"2\",\n      \"desc\": \"This command effects name faking using $/ or cl_fakename used by players.\",\n      \"group-id\": \"3\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No unfaking.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Unfake all messages.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Unfake messages from enemies only.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_nolerp\": {\n      \"default\": \"0\",\n      \"desc\": \"Allows you to disable the linear interpolation (lerp) of objects in the game.\\nInformation about objects' states arrive periodically via the net connection. By default between these moments the client tries to interpolate where the objects are. After the new packet arrives client corrects the position of the object.\\nIf the interpolation is disabled, client will leave object in the state described in the last received packet until a new one arrives.\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Interpolate objects\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Disable interpolation\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_nolerp_on_entity\": {\n      \"default\": \"0\",\n      \"desc\": \"Disables linear interpolation only when player is standing on an entity.\\nIt is a workaround to remove jittering of floating platforms under feets.\",\n      \"group-id\": \"8\",\n      \"remarks\": \"No effect if fps independent physics is turned off. See cl_nolerp.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_nopred\": {\n      \"default\": \"0\",\n      \"desc\": \"For debugging, disables movement prediction for your character; other players are still predicted.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Predict movement\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Don't predict movement\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_novweps\": {\n      \"default\": \"0\",\n      \"desc\": \"If set, disables support for zquake vwep extension.\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\"\n    },\n    \"cl_onload\": {\n      \"default\": \"menu\",\n      \"desc\": \"Tells what will be the start-up screen of the client.\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Server browser will be your start-up screen\",\n          \"name\": \"browser\"\n        },\n        {\n          \"description\": \"After start-up you can immediately type into the console\",\n          \"name\": \"console\"\n        },\n        {\n          \"description\": \"You'll see the main menu after the start-up\",\n          \"name\": \"menu\"\n        },\n        {\n          \"description\": \"A command/script to be executed on start-up\",\n          \"name\": \"<other>\"\n        }\n      ]\n    },\n    \"cl_parseFrags\": {\n      \"default\": \"1\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable displaying team fortress related statistics in the scoreboard (flag touches, steals, caps, etc).\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_parseFunChars\": {\n      \"default\": \"1\",\n      \"group-id\": \"3\",\n      \"remarks\": \"Full list:\\n$R - red lamp\\n$G - green lamp\\n$B - blue lamp\\n$Y - yellow lamp\\n$\\\\ - carriage return\\n$( - big left bracket\\n$= - big equal sign\\n$) - big right bracket\\n$. - red middle dot\\n$, - white dot (names only)\\n$< - small left bracket\\n$- - small equal sign\\n$> - small right bracket\\n$a - big grey block\\n$: - line feed\\n$b - filled red block\\n$d - right pointing red arrow\\n$[ - gold left square bracket\\n$] - gold right square bracket\\n$^ - white ^ (names only)\\n^x - red x (names only)\\n$0-9 - yellow number\\n$xyy - char with hex code yy\\n(In order to use the lamps, you'll need the Ocrana pak).\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Off.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"You can use Ocrana LED's by hand right out of the console and chat anonymously through \\\"$\\\\\\\" without showing your name (chat messages that are seen without your name at the beginning).\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_parseSay\": {\n      \"default\": \"1\",\n      \"group-id\": \"3\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable %-macros (%a %h %b etc...)\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"You can use macros such as %a %h %l (like in Qizmo).\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_parseWhiteText\": {\n      \"default\": \"1\",\n      \"desc\": \"Convert text between { and } to white or not in chat/team chat.\",\n      \"group-id\": \"3\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Convert none.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Convert all (chat and team chat).\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Convert team chat only.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_pext\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, enable support for FTE's protocol extensions.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\"\n    },\n    \"cl_pext_256packetentities\": {\n      \"default\": \"1\",\n      \"desc\": \"Allow protocol extension for allowing more packet entities.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_pext_alpha\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, enable support for FTE's alpha attribute protocol extension.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\"\n    },\n    \"cl_pext_chunkeddownloads\": {\n      \"default\": \"1\",\n      \"desc\": \"Enables protocol extension called \\\"Chunked downloads\\\". Allows you to download maps and demos from servers faster.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_pext_colourmod\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, enable support for FTE's colour attribute extension.\\n Allows the server to color models, etc.'\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\"\n    },\n    \"cl_pext_floatcoords\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, enable support for FTE's floating point coordinate protocol extension.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\"\n    },\n    \"cl_pext_lagteleport\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable support for MVDSV to correct movement commands when travelling through teleports.\\nSee https://www.quakeworld.nu/forum/topic/7239 for more details.\",\n      \"group-id\": \"21\",\n      \"remarks\": \"You must reconnect to the server for changes to take effect.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"No adjustment made server-side\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Server will adjust movement in the delay between entering & exiting teleports\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_pext_limits\": {\n      \"default\": \"1\",\n      \"desc\": \"Enable support for FTE's extensions to support enhanced number of entities and models.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"FTE extensions disabled.  Some enemies/projectiles may be invisible.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"FTE extensions enabled.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_pext_other\": {\n      \"default\": \"0\",\n      \"desc\": \"Allows other protocol extensions other than Chunked downloads.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_pext_serversideweapon\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_pext_warndemos\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, warning will be displayed when recording a demo that is not backwards compatible with old clients.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\"\n    },\n    \"cl_physfps\": {\n      \"default\": \"0\",\n      \"desc\": \"Sets the amount of FPS used to communicate with the server.\\nThis variable is used when cl_independentphysics 1 is set, and controls the FPS sent/received to/from the server.\\nThe graphical FPS is limited by cl_maxfps (vid_vsync must be 0).\\n\\nWhen set to 0, the current cl_maxfps value is used.\\nWhen cl_maxfps is set to 0 too, client displays as much FPS as server and data rate allows.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"cl_physfps_spectator\": {\n      \"default\": \"77\",\n      \"desc\": \"Amount of updates the client will send/receive while being spectator.\\nLower values make the client smooth-out the field of view movement greatly.\",\n      \"group-id\": \"8\",\n      \"remarks\": \"This can increase your ping (only) when you are a spectator.\",\n      \"type\": \"integer\"\n    },\n    \"cl_pitchspeed\": {\n      \"default\": \"150\",\n      \"desc\": \"This variable determines how fast you you turn up/down when using \\\"+lookup\\\" and \\\"+lookdown\\\".\",\n      \"group-id\": \"9\",\n      \"type\": \"float\"\n    },\n    \"cl_portpingprobe_delay\": {\n      \"default\": \"0\",\n      \"desc\": \"Specifies the number of milliseconds to sleep between each probe. Defaults to no delay\",\n      \"group-id\": \"21\",\n      \"type\": \"integer\"\n    },\n    \"cl_portpingprobe_enable\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggle whether a port ping probe should be performed when connecting to a server.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not perform a port ping probe before connecting to a server.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Perform a port ping probe before connecting to a server.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_portpingprobe_port_probes\": {\n      \"default\": \"1\",\n      \"desc\": \"Lets you set how many times each port should be probed when connecting to a server.\",\n      \"group-id\": \"21\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Range 1 to 5.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"cl_portpingprobe_probes\": {\n      \"default\": \"500\",\n      \"desc\": \"Lets you set how many port ping probes that should be performed when connecting to a server.\",\n      \"group-id\": \"21\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Range 1 to 1000.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"cl_predict_half\": {\n      \"default\": \"0\",\n      \"group-id\": \"21\",\n      \"remarks\": \"The new default eliminates player models' jittering when independent physics is enabled; a possible downside is larger prediction errors of modem players' movement, hence the option to revert to old behavior.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Full prediction of players' movement (ezQuake default)\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Predicting half the move (QW 2.30 default)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_predict_players\": {\n      \"default\": \"1\",\n      \"desc\": \"This toggles the prediction for other players' movement. Unless you are having problems this variable should be left at \\\"1\\\".\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_proxyaddr\": {\n      \"default\": \"\",\n      \"desc\": \"IP address of the proxy server to use while connecting to servers.\",\n      \"group-id\": \"21\",\n      \"remarks\": \"This will override the /connect and /reconnect commands so that the connection is established via given proxy server.\",\n      \"type\": \"string\"\n    },\n    \"cl_r2g\": {\n      \"default\": \"0\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Normal rockets models.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Converts all rockets to grenades.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_remote_capabilities\": {\n      \"default\": \"+attack,-attack,alias,bf,changing,cmd,color,download,exec,fullserverinfo,impulse,infoset,ktx_infoset,ktx_sinfoset,nextul,on_admin,on_connect,on_connect_ctf,on_connect_ffa,on_enter,on_enter_ctf,on_enter_ffa,on_matchend,on_matchstart,on_observe,on_observe_ctf,on_observe_ffa,on_spec_enter,on_spec_enter_ctf,on_spec_enter_ffa,on_spec_matchend,on_spec_matchstart,on_unadmin,packet,play,rate,reconnect,say,sinfoset,skin,skins,team,tempalias,track,wait\",\n      \"desc\": \"This variable controls which commands and variables a server is allowed to execute or set on the client. Input a comma-separated list of commands and variables to toggle access. The default values are adapted for KTX use.\",\n      \"group-id\": \"9\",\n      \"type\": \"string\"\n    },\n    \"cl_restrictions\": {\n      \"default\": \"0\",\n      \"desc\": \"Triggers and re/msg trigger restrictions for spectator and demoplay modes.\",\n      \"group-id\": \"3\",\n      \"remarks\": \"FuhQuake always behaves as cl_restrictions 1. QW262 has cl_restrictions 1 by default.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Allow triggers and huds\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Disallow triggers and huds\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_rollalpha\": {\n      \"default\": \"20\",\n      \"desc\": \"You can turn off the 'dodging' or 'rolling' effect for your own point of view while still see other player models affected by cl_rollangle.\",\n      \"group-id\": \"53\",\n      \"remarks\": \"0 disables, 1 is standard. Disabled by ruleset smackdown and smackdrive.\",\n      \"type\": \"integer\"\n    },\n    \"cl_rollangle\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable controls how much your screen tilts when strafing.\",\n      \"group-id\": \"53\",\n      \"type\": \"float\"\n    },\n    \"cl_rollspeed\": {\n      \"default\": \"200\",\n      \"desc\": \"This variable controls how quickly you and other players straighten out after strafing.\",\n      \"group-id\": \"53\",\n      \"type\": \"float\"\n    },\n    \"cl_safestrafe\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables safestrafe mode in client regardless of server enforcement.\",\n      \"group-id\": \"9\",\n      \"remarks\": \"Safestrafe is a serverside SOCD fairness feature that requires one or more stop frames between direction changes.\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Disabled\",\n          \"name\": \"0 \"\n        },\n        {\n          \"description\": \"Enabled (requires one stop frame between direction changes)\",\n          \"name\": \"1 \"\n        },\n        {\n          \"description\": \"Sets the number of required stop frames between direction changes.\",\n          \"name\": \"2+\"\n        }\n      ]\n    },\n    \"cl_savehistory\": {\n      \"default\": \"1\",\n      \"desc\": \"Save console commands history to .ezquake_history. Loads history from this file while starting ezquake.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"don't save\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"save\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_sayfilter_coloredtext\": {\n      \"default\": \"0\",\n      \"desc\": \"Allows you to filter your own outgoing messages out of markup that is used in this client to send colored text.\",\n      \"group-id\": \"49\",\n      \"remarks\": \"Colored text is not supported by some other clients. See cl_sayfilter_sendboth for compatibility mode with other clients.\\nIf you want to filter incoming messages out of colors, use scr_coloredtext.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Send messages as they are\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Filter out colored markup\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Filter out colored and white text markup\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_sayfilter_sendboth\": {\n      \"default\": \"0\",\n      \"desc\": \"When used with cl_sayfilter_coloredtext, sends two versions of the teamplay colored messages: colored one with \\\"#c\\\" at the end and uncolored version of the message with \\\"#u\\\" at the end of the message.\",\n      \"group-id\": \"49\",\n      \"remarks\": \"FuhQuake users then can use \\\"filter #u\\\" to see only uncolored messages, ezQuake users can set \\\"filter #c\\\" to see only colored messages.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Send only one version of the message\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Send two versions of the message\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_sbar\": {\n      \"default\": \"0\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Use the new transparent HUD.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Use the old status bar.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_shownet\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable toggles the display of current net info.\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not display any information.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Print current incoming packet size in byte.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Print information about the current in coming packet and it's size.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_sidespeed\": {\n      \"default\": \"400\",\n      \"desc\": \"This allows you to set your strafe speed.\\nObviously this is also limited by the server, usually to \\\"320\\\".\",\n      \"group-id\": \"9\",\n      \"remarks\": \"Sign of the value is ignored.\",\n      \"type\": \"float\"\n    },\n    \"cl_smartjump\": {\n      \"default\": \"1\",\n      \"desc\": \"Will convert +jump commands to +moveup commands when in liquid (water, lava, slime) and in spectator mode.\",\n      \"group-id\": \"9\",\n      \"type\": \"boolean\"\n    },\n    \"cl_solid_players\": {\n      \"default\": \"1\",\n      \"desc\": \"If not set then client will not perform collision detection against other players.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\"\n    },\n    \"cl_startupdemo\": {\n      \"default\": \"\",\n      \"desc\": \"Demo that should be played on client's startup.\",\n      \"group-id\": \"7\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"File name of the demo.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"cl_staticSounds\": {\n      \"default\": \"1\",\n      \"group-id\": \"45\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable static sounds.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable static sounds.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_sv_packetsync\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines if internal server should process packets as soon as received from client.\",\n      \"group-id\": \"43\",\n      \"remarks\": \"Should only affect playing on local server, when cl_delay_packet is enabled.\\nRecommend to disable for older mods which fake players (frogbots) and leave enabled for KTX.\",\n      \"type\": \"boolean\"\n    },\n    \"cl_textencoding\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines method used to encoding non-standard characters in outgoing messages.\",\n      \"group-id\": \"48\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Use KOI8-R encoding (ezQuake 2.2 compatible)\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Use UTF8 encoding (larger range of characters)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Use FTE's ^Uxxxx encoding\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_timeout\": {\n      \"default\": \"60\",\n      \"desc\": \"This variable defines the timeout value in seconds until the client considers himself to be disconnected from the server.\",\n      \"group-id\": \"21\",\n      \"type\": \"float\"\n    },\n    \"cl_upspeed\": {\n      \"default\": \"400\",\n      \"desc\": \"This allows you to set the speed with which you move up and down in liquids or in spectator mode.\\nObviously this is also limited by the server, usually to 320*0.7 = 224 when being in liquid and to 500 in spectator mode.\",\n      \"group-id\": \"9\",\n      \"remarks\": \"Sign of the value is ignored.\",\n      \"type\": \"float\"\n    },\n    \"cl_useimagesinfraglog\": {\n      \"default\": \"0\",\n      \"desc\": \"Turns on using images in the frags tracker window to show which weapon did take the role in the frag.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"See Tracer Stats manual page for further info.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_useproxy\": {\n      \"default\": \"0\",\n      \"desc\": \"This toggles whether Qizmo should be used (if detected) to a server. When enabled the server browser will use an existing connection to a Qizmo when connecting another server by using the ezQuake Server Browser.\",\n      \"group-id\": \"21\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"do not use Qizmo to connect a server chosen in the Server Browser\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"use Qizmo to connect a server chosen in the Server Browser\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_username\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_verify_qwprotocol\": {\n      \"default\": \"1\",\n      \"desc\": \"Enables the check on client startup for handling URLs starting with qw://\",\n      \"group-id\": \"40\",\n      \"remarks\": \"When enabled and the client is not associated with handling of qw:// URLs, user will be prompted if he wishes to associate the client with the protocol.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not check if client is associated with qw:// URLs\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Check if client is associated, prompt if not\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Always associate client with qw:// URLs\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_voip_capturingvol\": {\n      \"default\": \"0.5\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_voip_capturingvol \": {\n      \"desc\": \"Volume multiplier applied while capturing, to avoid your audio from being heard by others.\",\n      \"group-id\": \"32\",\n      \"type\": \"integer\"\n    },\n    \"cl_voip_demorecord\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_voip_micamp\": {\n      \"default\": \"2\",\n      \"desc\": \"Amplifies your microphone when using voip.\",\n      \"group-id\": \"32\",\n      \"type\": \"integer\"\n    },\n    \"cl_voip_play\": {\n      \"default\": \"1\",\n      \"desc\": \"Enables voip (voice chat) playback.\",\n      \"group-id\": \"32\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_voip_send\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable sending of voice (voip) to the server.\",\n      \"group-id\": \"32\",\n      \"remarks\": \"Commands +void / -void emulate toggling this value to 2.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"disabled\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"voice-activated\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"always activated\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_voip_showmeter\": {\n      \"default\": \"1\",\n      \"desc\": \"Shows your speech volume above the at the bottom-left of the screen.\",\n      \"group-id\": \"32\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Hide\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Show when transmitting\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Always show (ignore voice-activation)\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_voip_showmeter_x\": {\n      \"default\": \"0\",\n      \"desc\": \"Adjust horizontal position of the voice volume meter.\",\n      \"group-id\": \"32\",\n      \"remarks\": \"See cl_voip_showmeter.\",\n      \"type\": \"integer\"\n    },\n    \"cl_voip_showmeter_y\": {\n      \"default\": \"0\",\n      \"desc\": \"Adjust vertical position of the voice volume meter.\",\n      \"group-id\": \"32\",\n      \"remarks\": \"See cl_voip_showmeter.\",\n      \"type\": \"integer\"\n    },\n    \"cl_voip_vad_delay\": {\n      \"default\": \"0.3\",\n      \"desc\": \"Keeps sending voice data for this many seconds after voice activation would normally stop.\",\n      \"group-id\": \"32\",\n      \"type\": \"float\"\n    },\n    \"cl_voip_vad_threshhold\": {\n      \"default\": \"15\",\n      \"desc\": \"This is the threshhold for voice-activation-detection when sending voip data.\",\n      \"group-id\": \"32\",\n      \"type\": \"float\"\n    },\n    \"cl_warncmd\": {\n      \"default\": \"1\",\n      \"group-id\": \"2\",\n      \"remarks\": \"Note: Not saved to config with cfg_save command.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable the unknown command messages.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable the unknown command messages.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_warnexec\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, the names of script files will be printed to console as they are executed.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\"\n    },\n    \"cl_weaponforgetondeath\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_weaponforgetorder\": {\n      \"default\": \"0\",\n      \"group-id\": \"9\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"weapon/impulse command sets best weapon order, will select best weapon as ammo or weapon obtained\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"weapon/impulse command picks best weapon at the time of command, with fallback to axe or sg (see /cl_weaponhide_axe)\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"cl_weaponhide\": {\n      \"default\": \"0\",\n      \"desc\": \"On -attack will cause your weapon to be automatically switched back to the shotgun or axe.\",\n      \"group-id\": \"9\",\n      \"remarks\": \"This is used in majority of teamplay games. If you don't have ammo for shotgun the axe gets selected.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Weapon will not be automatically hidden\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"After you fire your weapon, it will get switched to shotgun or axe\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Like option 1, but only for deathmatch mode 1.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"cl_weaponhide_axe\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines if axe should be used as \\\"dummy\\\" weapon when using automated weapon hiding (cl_weaponhide).\",\n      \"group-id\": \"9\",\n      \"remarks\": \"See cl_weaponhide and cl_weaponpreselect.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Use shotgun as dummy weapon\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Use axe as dummy weapon\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cl_weaponpreselect\": {\n      \"default\": \"0\",\n      \"desc\": \"When using the weapon command, this variable allows weapon preselection instead of standard immediate weapon selection.\\nPreselection means that the preselected weapon will be switch right before +attack command, that is right before you shoot from your weapon.\",\n      \"group-id\": \"9\",\n      \"remarks\": \"Usefull in most teamplay games where you don't want to carry your best weapon in your hands but want to be ready to instantly shoot from it.\\nNote: Doesn't work for impulse command, you have to use new weapon command.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Immediate weapon selection\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Weapon preselection\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Weapon preselection when not holding +attack. When holding +attack, select weapon immediately.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Like option 1, but only for deathmatch mode 1, otherwise like 0.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Like option 2, but only for deathmatch mode 1, otherwise like 0.\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"cl_window_caption\": {\n      \"default\": \"1\",\n      \"desc\": \"Choose different window caption formats for your taskbar when you play in windowed mode or when the client is minimized in the taskbar.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"The caption doesn't refresh regularly on some configurations. We recommend to use vid_flashonactivity 1 as well.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Format: ezQuake: address\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Format: <server state> - pl: <number of players> - <map>\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Fixed: ezQuake\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Format: ezQuake <version> or <number of players>/<max players> <delim> <map>\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"cl_window_caption_delimiter\": {\n      \"default\": \" | \",\n      \"desc\": \"Delimiter between <number of players>/<max players> and <map> when using cl_window_caption 3.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"cl_www_address\": {\n      \"default\": \"https://badplace.eu/\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"cl_yawspeed\": {\n      \"default\": \"140\",\n      \"desc\": \"This variable defines how quickly you turn left (+left) or right (+right).\",\n      \"group-id\": \"9\",\n      \"type\": \"float\"\n    },\n    \"con_bindphysical\": {\n      \"default\": \"0\",\n      \"desc\": \"Affects behaviour of bind command.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"con_bindphysical will always be set to 1 at start of executing a script, and be reset after.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"'bind x y' will bind y to the key used to produce character x at the console (layout-aware)\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"'bind x y' will bind y to physical key x, as if using an American keyboard layout (layout independent)\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"con_clearnotify\": {\n      \"default\": \"1\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Messages stay even when you toggle console.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Messages won't say when you toggle console.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"con_completion_changed_mark\": {\n      \"default\": \"1\",\n      \"desc\": \"Whether add or not asterisk before variables which values were changed.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Works only with con_completion_format > 0.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"con_completion_color_changed_mark\": {\n      \"default\": \"f30\",\n      \"desc\": \"Color of changed mark used in modern completion formatting.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Example: 39f\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Use following color format: RGB\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"con_completion_color_colon\": {\n      \"default\": \"fff\",\n      \"desc\": \"Color of colon used in modern completion formatting.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Example: 39f\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Use following color format: RGB\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"con_completion_color_name\": {\n      \"default\": \"8ff\",\n      \"desc\": \"Color of variable name in used in modern completion formatting.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Example: 39f\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Use following color format: RGB\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"con_completion_color_quotes_current\": {\n      \"default\": \"ff8\",\n      \"desc\": \"Color of quotes of current variable value used in modern completion formatting.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Example: 39f\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Use following color format: RGB\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"con_completion_color_quotes_default\": {\n      \"default\": \"ff8\",\n      \"desc\": \"Color of quotes of default variable value used in modern completion formatting.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Example: 39f\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Use following color format: RGB\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"con_completion_color_title\": {\n      \"default\": \"ff3\",\n      \"desc\": \"Color of completion type title (variables, aliases or commands) used in modern completion formatting.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Example: 39f\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Use following color format: RGB\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"con_completion_color_value_current\": {\n      \"default\": \"fff\",\n      \"desc\": \"Color of current variable value used in modern completion formatting.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Example: 39f\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Use following color format: RGB\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"con_completion_color_value_default\": {\n      \"default\": \"fff\",\n      \"desc\": \"Color of default variable value used in modern completion formatting.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Example: 39f\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Use following color format: RGB\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"con_completion_format\": {\n      \"default\": \"2\",\n      \"desc\": \"Console completion variants format.\",\n      \"group-id\": \"5\",\n      \"remarks\": \" - Modern: plain list with colorization.\\n - Old: somehow grouped list without colorization.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Old completion format\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Modern one, shows current and default values\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Modern one, shows current values only\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Modern one, shows default values only\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Modern one, shows current and default values if it differs\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Modern one, without any values of cvars\",\n          \"name\": \"5\"\n        }\n      ]\n    },\n    \"con_completion_padding\": {\n      \"default\": \"2\",\n      \"desc\": \"Number of spaces to pad command completion variants.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"con_completion_format must be > 1.\",\n      \"type\": \"integer\"\n    },\n    \"con_deadkey\": {\n      \"default\": \"1\",\n      \"desc\": \"Set this if the console toggle button is also a deadkey. The operating system will be sent a backspace character as the console is toggled.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Windows only.\",\n      \"type\": \"boolean\"\n    },\n    \"con_fragmessages\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls whether frag messages should be printed into console and notification area.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"Requires cl_loadfragfiles and cl_parsefrags variables to be set to 1.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not print frag messages\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Print frag messages to console and notification area (QW default).\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Print frag messages to console only\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"con_funchars_mode\": {\n      \"default\": \"0\",\n      \"desc\": \"Orange text, LEDs and special chars with [Ctrl] key - kind of MQWCL behaviour when set to 1.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"FuhQuake behaviour: [Ctrl] + key for yellow numbers and LEDs\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"MQWCL behaviour: [Ctrl] + [Y] turns on yellow number input\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"con_hide_chat_input\": {\n      \"default\": \"1\",\n      \"desc\": \"Hides the input of own chat text in the console.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"cl_chatmode must be 1 or 2.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"con_highlight\": {\n      \"default\": \"0\",\n      \"desc\": \"Console highlighting mode. Will highlight a line in the console which contains your nickname.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"See con_highlight_mark.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No highlighting\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Highlighted line will be all white.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Highlighted line will start with the text given in con_highlight_mark\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Both 1 and 2: highlighted line will be white and start with special text\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"con_highlight_mark\": {\n      \"default\": \"\",\n      \"desc\": \"Specifies the text that will be used to highlight lines with con_highlight 2 and 3.\",\n      \"group-id\": \"5\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This text will be used on the beginning of the highlighted line\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"con_margin_left\": {\n      \"default\": \"0\",\n      \"desc\": \"If set, messages are padded on the left by the number of spaces defined in the variable\",\n      \"group-id\": \"47\",\n      \"type\": \"integer\"\n    },\n    \"con_mm2_only\": {\n      \"default\": \"0\",\n      \"desc\": \"If set, notification area is limited to mm2 (/say_team) messages.\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"All messages\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"MM2 messages only\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"con_notify\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles the display of the notification area.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Notification area is the place where chat and game messages are displayed.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"hide notification area\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"show notification area\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"con_notifylines\": {\n      \"default\": \"4\",\n      \"desc\": \"This variable sets the maximum number of notify lines (default 4, max 15) to be used at the top of the screen.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Only affects con_notify.\",\n      \"type\": \"integer\"\n    },\n    \"con_notifytime\": {\n      \"default\": \"3\",\n      \"desc\": \"How long console messages stay on screen.\",\n      \"group-id\": \"5\",\n      \"type\": \"float\"\n    },\n    \"con_prompt_charcode\": {\n      \"default\": \"93\",\n      \"desc\": \"ASCII code of prompt character.\",\n      \"group-id\": \"5\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Range 32 to 255.\",\n          \"name\": \"false\"\n        }\n      ]\n    },\n    \"con_proportional\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable TrueType fonts on console text.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\"\n    },\n    \"con_shift\": {\n      \"default\": \"-10\",\n      \"desc\": \"Adjusts vertical offset of background of the console.\",\n      \"group-id\": \"5\",\n      \"type\": \"float\"\n    },\n    \"con_tilde_mode\": {\n      \"default\": \"0\",\n      \"desc\": \"When enabled, allows you to use the tilde key also in the console and when typing messages.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"To exit the console with con_tilde_mode 1, use Escape.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Always use tilde to toggle the console\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Treat tilde as a key leading to typing the character associated with it (tilde does not close the console)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Same as 1, but tilde does close the console if the console is empty\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"con_timestamps\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles time stamps before mm1 or spectator messages. Does not apply to messages with cl_fakename.\",\n      \"group-id\": \"5\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not display timestamps.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Display [HH:mm]\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Display [HH:mm:ss]\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"con_wordwrap\": {\n      \"default\": \"1\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable word wrapping.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable word wrapping.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"coop\": {\n      \"default\": \"0\",\n      \"desc\": \"Whether the next \\\"map\\\" command should start a coop (cooperative) game.\\nOnly works when deathmatch var is 0, otherwise coop will be forced off and a deathmatch game will start.\\n\\nYou need a file, spprogs.dat, in your quake/qw/ folder for coop games to work.\",\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable cooperative.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable cooperative.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"crosshair\": {\n      \"default\": \"4\",\n      \"group-id\": \"6\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Crosshair off.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Conchar crosshair (messagemode1 +).\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Quakeworld crosshair.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"A small cross crosshair.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"A dot crosshair.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Star wars crosshair.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"F22 simulator crosshair.\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"(same as 6)\",\n          \"name\": \"7\"\n        }\n      ]\n    },\n    \"crosshairalpha\": {\n      \"default\": \"1\",\n      \"desc\": \"This command regulates crosshair transparency, you can use any value from \\\"0\\\" to \\\"1\\\".\",\n      \"group-id\": \"6\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Transparent crosshair.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Opaque crosshair.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"crosshaircolor\": {\n      \"default\": \"255 0 0\",\n      \"desc\": \"This variable defines the color of the crosshair.\",\n      \"group-id\": \"6\",\n      \"type\": \"string\"\n    },\n    \"crosshairimage\": {\n      \"default\": \"\",\n      \"desc\": \"The filename of the crosshair image.\",\n      \"group-id\": \"6\",\n      \"remarks\": \"Crosshair images should be placed in ./ezquake/crosshairs or ./qw/crosshairs directory.\",\n      \"type\": \"string\"\n    },\n    \"crosshairscale\": {\n      \"default\": \"0\",\n      \"desc\": \"When using built-in crosshairs, increases the resolution of the crosshair, giving a crisper image on screen.\",\n      \"group-id\": \"6\",\n      \"remarks\": \"Max value 5. Increasing this value will require lowering of /crosshairsize to give same size image on screen.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"8x8\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"16x16\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"32x32\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"64x64\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"128x128\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"256x256\",\n          \"name\": \"5\"\n        }\n      ]\n    },\n    \"crosshairscalemethod\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines how crosshair images are scaled.\",\n      \"group-id\": \"6\",\n      \"remarks\": \"When changing this value, /crosshairscale & /crosshairsize should be adjusted accordingly.\",\n      \"type\": \"boolean\",\n      \"value\": [\n        {\n          \"description\": \"Quake standard scaling: resolution width / 320\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"No scaling, sizes are in pixels\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"crosshairsize\": {\n      \"default\": \"1\",\n      \"desc\": \"Performs basic scaling of the crosshair.\",\n      \"group-id\": \"6\",\n      \"remarks\": \"Maximum value 20. For example, /crosshairsize 0.5 halves the size, and /crosshair 2 doubles it.\",\n      \"type\": \"float\"\n    },\n    \"cvar_viewdefault\": {\n      \"default\": \"1\",\n      \"desc\": \"When you type a cvar name into console (like 'gl_gamma' or 'r_rocketlight'), the client will tell you the default value of the cvar.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\"\n    },\n    \"cvar_viewhelp\": {\n      \"default\": \"1\",\n      \"desc\": \"Automatically prints manual page when a variable name is typed in the console.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not print help\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Print help automatically\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"cvar_viewlatched\": {\n      \"default\": \"1\",\n      \"desc\": \"When you type a variable name in the console, you'll be able to see it's latched value if this is turned on.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Latched values are used for example for renderer settings. Variable value is latched until the renderer is restarted, in that moment the latched value becomes the actuall value of the variable.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't display latched value\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display latched value (if any)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"d_mipcap\": {\n      \"group-id\": \"30\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"High texture detail.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Medium texture detail.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Low texture detail.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Minimum texture detail.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"d_mipscale\": {\n      \"group-id\": \"30\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Full object detail\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Some object detail\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Medium object detail\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Low object detail\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"d_subdiv16\": {\n      \"group-id\": \"30\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Precise texture mapping (slower, more accurate).\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Perspective correction only every 16 pixels (faster, less accurate).\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"deathmatch\": {\n      \"default\": \"3\",\n      \"desc\": \"Chooses between basic multiplayer gameplay modes.\",\n      \"group-id\": \"43\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Weapons disappear after pickup (used in 4on4)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Weapons stay after pickup, ammo/armor does not (not used)\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Weapons stay after pickup (used in 1on1)\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Arena mode: players have all weapons and full health/armor\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"default_fov\": {\n      \"default\": \"90\",\n      \"desc\": \"Very useful in TeamFortress, and other mods that reset your fov when you spawn to 90.\\nBasically, when the server sends you \\\"fov 90\\\", your fov is set to default_fov.\",\n      \"group-id\": \"53\",\n      \"remarks\": \"-\",\n      \"type\": \"float\"\n    },\n    \"demo_autotrack\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables server-side autotrack to be accepted.\",\n      \"group-id\": \"7\",\n      \"remarks\": \"Server-side autotrack is recorded in MVD demos and in QTV stream allowing all the observers of the match observe the same player at the same time.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_benchmarkdumps\": {\n      \"default\": \"1\",\n      \"desc\": \"Allows you to automatically dump timedemo benchmark results into $log_dir/timedemo.log file.\\nThe output is in XML markup format and contains info about your operating system, hardware configuration, client version, rendering, screen resolution and the result FPS.\",\n      \"group-id\": \"7\",\n      \"remarks\": \"See timedemo command description for more info.\\nNote: The result file is not a well-formed XML file.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable timedemo result dumping\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Dump results of timedemo benchmark\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_browser_democolor\": {\n      \"desc\": \"Color of the demo entries in the demo browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"demo_browser_dircolor\": {\n      \"desc\": \"Color of the dir entries in the demo browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"demo_browser_interline\": {\n      \"desc\": \"Size of the space between entries in the demo browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"integer\"\n    },\n    \"demo_browser_scrollnames\": {\n      \"desc\": \"Toggle scrolling of the filenames in the demo browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_browser_selectedcolor\": {\n      \"desc\": \"Color of the selected entries in the demo browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"demo_browser_showdate\": {\n      \"desc\": \"Toggle the date column in the demo browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_browser_showsize\": {\n      \"desc\": \"Toggle the file size column in the demo browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_browser_showstatus\": {\n      \"desc\": \"Toggle the display of the status bar in the demo browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_browser_showtime\": {\n      \"desc\": \"Toggle the time column in the demo browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_browser_sortmode\": {\n      \"desc\": \"Sorting mode in the demo browser. Each number represents one column. Their order represents the priority of the sorting.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"demo_browser_stripnames\": {\n      \"desc\": \"Toggle stripping of the filenames in the demo browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_browser_zipcolor\": {\n      \"desc\": \"Color of the zip entries in the demo browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"demo_capture_background_threads\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines how many background threads should be used writing images to disk.\",\n      \"group-id\": \"7\",\n      \"type\": \"integer\"\n    },\n    \"demo_capture_codec\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines what codes should be used for captured video stream compression. E.g: xvid, divx, x264, ...\",\n      \"group-id\": \"7\",\n      \"type\": \"float\"\n    },\n    \"demo_capture_dir\": {\n      \"default\": \"capture\",\n      \"desc\": \"Change the default capture directory.\",\n      \"group-id\": \"7\",\n      \"type\": \"string\"\n    },\n    \"demo_capture_fps\": {\n      \"default\": \"30.0\",\n      \"desc\": \"Change the default capture fps.\",\n      \"group-id\": \"7\",\n      \"type\": \"float\"\n    },\n    \"demo_capture_mp3\": {\n      \"default\": \"0\",\n      \"desc\": \"When set to 1 .avi capturing captures sound compressed in MP3 format.\",\n      \"group-id\": \"7\",\n      \"remarks\": \"See demo_capture_mp3_kbps too.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Captures uncompressed sound\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Captures sound in MP3 format\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_capture_mp3_kbps\": {\n      \"default\": \"128\",\n      \"desc\": \"Sets bitrate for captured sound stream when demo_capture_mp3 is set to 1.\",\n      \"group-id\": \"7\",\n      \"type\": \"float\"\n    },\n    \"demo_capture_quiet\": {\n      \"desc\": \"Stops sound being played during demo capture.\",\n      \"group-id\": \"7\",\n      \"type\": \"boolean\"\n    },\n    \"demo_capture_steadycam\": {\n      \"default\": \"0\",\n      \"desc\": \"Changes behaviour of keyboard/mouse input when capturing.\",\n      \"group-id\": \"7\",\n      \"type\": \"float\"\n    },\n    \"demo_capture_vid_maxlen\": {\n      \"default\": \"0\",\n      \"desc\": \"If set, multiple files will be created. This variable determines length of each file in MB.\",\n      \"group-id\": \"7\",\n      \"remarks\": \"The .avi format has issues with files over 2GB in size.\",\n      \"type\": \"integer\"\n    },\n    \"demo_dir\": {\n      \"default\": \"\",\n      \"desc\": \"Change the demos and autorecord directory.\",\n      \"group-id\": \"7\",\n      \"type\": \"string\"\n    },\n    \"demo_format\": {\n      \"default\": \"qwz\",\n      \"desc\": \"Specifies the demo file format used when recording demo with Match tools.\",\n      \"group-id\": \"7\",\n      \"remarks\": \"See qwdtools_dir, qizmo_dir, match_auto_record.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"MultiView Demo, usually contains less frames per second.\",\n          \"name\": \"mvd\"\n        },\n        {\n          \"description\": \"Original QuakeWorld demo format.\",\n          \"name\": \"qwd\"\n        },\n        {\n          \"description\": \"Qizmo compressed demo.\",\n          \"name\": \"qwz\"\n        }\n      ]\n    },\n    \"demo_getpings\": {\n      \"default\": \"1\",\n      \"desc\": \"This toggles whether the client should always record pings into the demo or only when the player died and show(team)scores are being shown (QWCL default).\",\n      \"group-id\": \"7\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"only update pings in the demo when the player died and show(team)scores are being shown\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"always update pings\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_jump_rewind\": {\n      \"default\": \"-10\",\n      \"desc\": \"Specifies the automatic rewind time before /demo_jump_mark or /demo_jump_status triggers normal playback.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"Time in seconds, must be negative.\",\n      \"type\": \"float\"\n    },\n    \"demo_jump_skip_messages\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines if messages should be printed to console during demo jumps.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Display all messages during demo jump.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Skip messages during demo jump.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_playlist_loop\": {\n      \"default\": \"0\",\n      \"desc\": \"will toggle playlist looping.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"will not loop the demo playlist\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"will loop the demo playlist\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_playlist_track_name\": {\n      \"default\": \"\",\n      \"desc\": \"will set the default track name in the demo playlist for mvd demos.\\nExample: jogi, will always track the player jogi or versions of it (jogihoogi, angryjogi, etc).\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"demo_spawnwarn\": {\n      \"default\": \"0\",\n      \"desc\": \"Warns during demo playback when the current POV crosses a deathmatch spawn or teleporter exit.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable spawn crossing warnings in demos.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable spawn crossing warnings in demos.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"demo_spawnwarn_text\": {\n      \"default\": \"you crossed a spawn\",\n      \"desc\": \"Sets the centerprint text shown when demo_spawnwarn detects a spawn crossing.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"developer\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables debug mode which prints more messages into the console than for normal user.\",\n      \"group-id\": \"2\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"download_map_url\": {\n      \"default\": \"\",\n      \"desc\": \"MVDSV : URL announced to clients for faster map downloads over HTTP.\",\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"enemybothskin\": {\n      \"default\": \"\",\n      \"desc\": \"Overrides the enemy quad pent skin so you can define the skin which applies to enemys with both the quad and pent powerups.\",\n      \"group-id\": \"44\",\n      \"type\": \"string\"\n    },\n    \"enemybottomcolor\": {\n      \"default\": \"-1\",\n      \"desc\": \"Determines the color of the pants of the enemies you see. Overrides player's skin settings.\",\n      \"group-id\": \"44\",\n      \"remarks\": \"To be able to use RGB 24bit colors, look for r_enemyskincolor variable.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disabled\",\n          \"name\": \"-1\"\n        },\n        {\n          \"description\": \"White\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Brown\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Lavender\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Khaki\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Red\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Light Brown\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Peach\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Light Peach\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Purple\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Dark Purple\",\n          \"name\": \"9\"\n        },\n        {\n          \"description\": \"Tan\",\n          \"name\": \"10\"\n        },\n        {\n          \"description\": \"Green\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"Yellow\",\n          \"name\": \"12\"\n        },\n        {\n          \"description\": \"Blue\",\n          \"name\": \"13\"\n        },\n        {\n          \"description\": \"Orange.\",\n          \"name\": \"14\"\n        },\n        {\n          \"description\": \"Bright red.\",\n          \"name\": \"15\"\n        },\n        {\n          \"description\": \"Black.\",\n          \"name\": \"16\"\n        }\n      ]\n    },\n    \"enemyforceskins\": {\n      \"default\": \"0\",\n      \"desc\": \"Allows you to set different skin for every enemy player even if they all have same skin names set or use names of skins that you do not have in your Quake dir.\",\n      \"group-id\": \"44\",\n      \"remarks\": \"Read \\\"Player skins\\\" manual page for more info on skin rules.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disabled\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Use player's name as the name of the skin to be used\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Use player's userid number (only useful for video capturing)\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Assign skins \\\"e1\\\", \\\"e2\\\", \\\"e3\\\", etc. to the enemies.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"enemypentskin\": {\n      \"default\": \"\",\n      \"desc\": \"Overrides the enemy pent skin so you can define the skin which applies to enemy pents.\",\n      \"group-id\": \"44\",\n      \"type\": \"string\"\n    },\n    \"enemyquadskin\": {\n      \"default\": \"\",\n      \"desc\": \"Overrides the enemy quad skin so you can define the skin which applies to enemy quads.\",\n      \"group-id\": \"44\",\n      \"type\": \"string\"\n    },\n    \"enemyskin\": {\n      \"default\": \"\",\n      \"desc\": \"Overrides the enemies skin so you can define the skin which applies to enemies.\",\n      \"group-id\": \"44\",\n      \"type\": \"string\"\n    },\n    \"enemytopcolor\": {\n      \"default\": \"-1\",\n      \"desc\": \"Determines the color of the shirt of the enemies you see. Overrides player's skin settings.\",\n      \"group-id\": \"44\",\n      \"remarks\": \"To be able to use RGB 24bit colors, look for r_enemyskincolor variable.\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Disabled\",\n          \"name\": \"-1\"\n        },\n        {\n          \"description\": \"White\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Brown\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Lavender\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Khaki\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Red\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Light Brown\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Peach\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Light Peach\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Purple\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Dark Purple\",\n          \"name\": \"9\"\n        },\n        {\n          \"description\": \"Tan\",\n          \"name\": \"10\"\n        },\n        {\n          \"description\": \"Green\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"Yellow\",\n          \"name\": \"12\"\n        },\n        {\n          \"description\": \"Blue\",\n          \"name\": \"13\"\n        },\n        {\n          \"description\": \"Orange.\",\n          \"name\": \"14\"\n        },\n        {\n          \"description\": \"Bright red.\",\n          \"name\": \"15\"\n        },\n        {\n          \"description\": \"Black.\",\n          \"name\": \"16\"\n        }\n      ]\n    },\n    \"extralogname\": {\n      \"default\": \"unset\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"file_browser_archive_color\": {\n      \"default\": \"255 170 0 255\",\n      \"desc\": \"Color of archive files in the file browser.\",\n      \"group-id\": \"17\",\n      \"type\": \"string\"\n    },\n    \"file_browser_dir_color\": {\n      \"default\": \"170 80 0 255\",\n      \"desc\": \"Color of directories in the file browser.\",\n      \"group-id\": \"17\",\n      \"type\": \"string\"\n    },\n    \"file_browser_file_color\": {\n      \"default\": \"255 255 255 255\",\n      \"desc\": \"Color of files in the file browser.\",\n      \"group-id\": \"17\",\n      \"type\": \"string\"\n    },\n    \"file_browser_interline\": {\n      \"default\": \"0\",\n      \"desc\": \"Spacing between each row in the file browser.\",\n      \"group-id\": \"17\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Range 0 to 6.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"file_browser_scrollnames\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, the currently selected file will scroll to let you see the full name.\",\n      \"group-id\": \"17\",\n      \"type\": \"boolean\"\n    },\n    \"file_browser_selected_color\": {\n      \"default\": \"0 150 235 255\",\n      \"desc\": \"Color of the current selection in the file browser.\",\n      \"group-id\": \"17\",\n      \"type\": \"string\"\n    },\n    \"file_browser_show_date\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles whether the file modified date is shown in the file browser.\",\n      \"group-id\": \"17\",\n      \"type\": \"boolean\"\n    },\n    \"file_browser_show_size\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles whether the filesize is shown in the file browser.\",\n      \"group-id\": \"17\",\n      \"type\": \"boolean\"\n    },\n    \"file_browser_show_status\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles whether a statusbar is shown in the file browser.\",\n      \"group-id\": \"17\",\n      \"type\": \"boolean\"\n    },\n    \"file_browser_show_time\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles whether the file modified time is shown in the file browser.\",\n      \"group-id\": \"17\",\n      \"type\": \"boolean\"\n    },\n    \"file_browser_sort_archives\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"file_browser_sort_mode\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines sort order of files in the file browser.\",\n      \"group-id\": \"17\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"sort by name\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"sort by size\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"sort by date/time\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"sort by type\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"sort by name, then by type\",\n          \"name\": \"14\"\n        }\n      ]\n    },\n    \"file_browser_strip_names\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, strips extra spaces from filenames in the file browser.\",\n      \"group-id\": \"17\",\n      \"type\": \"boolean\"\n    },\n    \"filterban\": {\n      \"default\": \"1\",\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Only IP addresses on the Ban list will be allowed onto the server.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Only IP addresses NOT on the Ban list will be allowed onto the server.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"floodprotmsg\": {\n      \"desc\": \"Sets the message displayed after flood protection is invoked.\\nExample: floodprotmsg \\\"Shut up whiner!\\\"\",\n      \"group-id\": \"33\",\n      \"type\": \"float\"\n    },\n    \"font_capitalize\": {\n      \"default\": \"0\",\n      \"desc\": \"Draw all TrueType text in capital letters.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\"\n    },\n    \"font_facepath\": {\n      \"default\": \"\",\n      \"desc\": \"Current TrueType font file.\",\n      \"group-id\": \"5\",\n      \"type\": \"string\"\n    },\n    \"font_gradient_alternate_color1\": {\n      \"default\": \"175 120 52\",\n      \"desc\": \"Top color in Alternate TrueType text.\",\n      \"group-id\": \"5\",\n      \"type\": \"string\"\n    },\n    \"font_gradient_alternate_color2\": {\n      \"default\": \"75 52 22\",\n      \"desc\": \"Bottom color in Alternate TrueType text.\",\n      \"group-id\": \"5\",\n      \"type\": \"string\"\n    },\n    \"font_gradient_alternate_percent\": {\n      \"default\": \"0.4\",\n      \"desc\": \"Slope of the gradient for Alternate TrueType text.\",\n      \"group-id\": \"5\",\n      \"type\": \"float\"\n    },\n    \"font_gradient_normal_color1\": {\n      \"default\": \"255 255 255\",\n      \"desc\": \"Top color in Normal TrueType text.\",\n      \"group-id\": \"5\",\n      \"type\": \"string\"\n    },\n    \"font_gradient_normal_color2\": {\n      \"default\": \"107 98 86\",\n      \"desc\": \"Bottom color in Normal TrueType text.\",\n      \"group-id\": \"5\",\n      \"type\": \"string\"\n    },\n    \"font_gradient_normal_percent\": {\n      \"default\": \"0.2\",\n      \"desc\": \"Slope of the gradient for Normal TrueType text.\",\n      \"group-id\": \"5\",\n      \"type\": \"float\"\n    },\n    \"font_gradient_number_color1\": {\n      \"default\": \"255 255 150\",\n      \"desc\": \"Top color in Number TrueType text.\",\n      \"group-id\": \"5\",\n      \"type\": \"string\"\n    },\n    \"font_gradient_number_color2\": {\n      \"default\": \"218 132 7\",\n      \"desc\": \"Bottom color in Number TrueType text.\",\n      \"group-id\": \"5\",\n      \"type\": \"string\"\n    },\n    \"font_gradient_number_percent\": {\n      \"default\": \"0.2\",\n      \"desc\": \"Slope of the gradient for Number TrueType text.\",\n      \"group-id\": \"5\",\n      \"type\": \"float\"\n    },\n    \"font_outline_width\": {\n      \"default\": \"2\",\n      \"desc\": \"Scale of the outline around TrueType text.\",\n      \"group-id\": \"5\",\n      \"type\": \"integer\"\n    },\n    \"fov\": {\n      \"default\": \"90\",\n      \"desc\": \"This variable defines your field of vision, which determines how close your vision field is to the objects.\",\n      \"group-id\": \"53\",\n      \"remarks\": \"While the default is 90, many players prefer using a higher FOV.\\nNote that this would typically be scaled up when using a monitor with widescreen aspect ratio.\",\n      \"type\": \"float\"\n    },\n    \"frag_log_type\": {\n      \"default\": \"0\",\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"fraglimit\": {\n      \"default\": \"0\",\n      \"desc\": \"Amount of frags any player has to reach before the match is over.\",\n      \"group-id\": \"43\",\n      \"remarks\": \"When set to 0, there won't be any limit.\",\n      \"type\": \"integer\"\n    },\n    \"freelook\": {\n      \"default\": \"1\",\n      \"group-id\": \"10\",\n      \"remarks\": \"direction up or down.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Both -mlook and freelook 0 must be specified to disable free look mode.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enables free look mode, that is, moving the mouse forward and back moves your view\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"fs_cache\": {\n      \"default\": \"1\",\n      \"desc\": \"Enables Quake File System caching of files lists.\",\n      \"group-id\": \"48\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"fs_savegame_home\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"gender\": {\n      \"default\": \"\",\n      \"desc\": \"Indicates the gender of the player.\",\n      \"group-id\": \"37\",\n      \"remarks\": \"This relies on server/mod support to function correctly.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Not specified (use gender-neutral language)\",\n          \"name\": \"\"\n        },\n        {\n          \"description\": \"Male (he, his)\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Female (she, her)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"None (it, its)\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"gl_alphafont\": {\n      \"default\": \"1\",\n      \"desc\": \"Allows alpha transparency layer support for the console font.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Use only 2 levels of transparency for console font\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Use full 256 levels of transparency for console font\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"gl_anisotropy\": {\n      \"default\": \"16\",\n      \"desc\": \"Anisotropic filtering. Improves texture detail on angled surfaces.\",\n      \"group-id\": \"50\",\n      \"remarks\": \"Depends on your Graphics card capabilities and settings. Make sure you have set your graphic card's Anisotropic Filtering setting to \\\"Application controlled\\\".\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"0 and 1 means turned off, then usually up to 16 for the highest quality.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_bounceparticles\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls whether sparks bounce off walls.\",\n      \"group-id\": \"36\",\n      \"remarks\": \"Bouncing particles look nicer, but may eat up CPU.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_brush_polygonoffset\": {\n      \"default\": \"2.0\",\n      \"desc\": \"Offset drawing of entity models to stop z-fighting.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Disabled for qcon (can cause flickering areas, most often on Intel cards).\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range 0 to 2.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_brush_polygonoffset_factor\": {\n      \"default\": \"0.05\",\n      \"desc\": \"Set the factor for gl_brush_polygonoffset. Negative values offset the brushes towards the player.\",\n      \"group-id\": \"35\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range -1 to 1.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_buildingsparks\": {\n      \"default\": \"0\",\n      \"desc\": \"Buildings that are destroyed in TF will continue to throw sparks until they disappear.\",\n      \"group-id\": \"35\",\n      \"type\": \"float\"\n    },\n    \"gl_caustics\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable reflections in submerged areas (under water, lava, slime).\",\n      \"group-id\": \"51\",\n      \"remarks\": \"Multi-texturing is required for this setting.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_charsets_min\": {\n      \"default\": \"1\",\n      \"desc\": \"Speeds up start-up time by only searching for minimum number of charsets during start-up.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Search for full range of unicode charsets\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Search for default and -cyr only (as per ezQuake 2.2)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"gl_clear\": {\n      \"default\": \"0\",\n      \"desc\": \"Clears the screen with gl_clearcolor between each frame.\\nPrevents hall-of-mirrors glitches, e.g. when flying out of the map.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"This can cause problems when going through the surface of a liquid, as you may see a colored flash where the liquid texture would normally glitch.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_clearColor\": {\n      \"default\": \"0 0 0\",\n      \"desc\": \"Sets clear color (gl_clear 1).\",\n      \"group-id\": \"35\",\n      \"remarks\": \"When using vid_framebuffer, the GPU starts from a clean, black buffer and this value is effectively ignored.\",\n      \"type\": \"string\"\n    },\n    \"gl_clipparticles\": {\n      \"default\": \"1\",\n      \"desc\": \"Limits the number of blended particles close to you. This can give a big FPS increase when blended particles take up a big proportion of your screen and challenge your video card's fillrate.\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_colorlights\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles color on lighting from glowing items (quad, pent, flags in TF, etc).\",\n      \"group-id\": \"15\",\n      \"remarks\": \"This implementation does not give a speed increase when gl_colorlights is set to 0, but it does not require a map restart for changes to take effect.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_consolefont\": {\n      \"default\": \"povo5\",\n      \"desc\": \"Changes your console font.\\nPut all your charset images in qw/textures/charsets/*.png (and *.tga).\\n\\\"/gl_consolefont original\\\" will restore the 8bit font in your gfx.wad.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"loadcharset is an alias for gl_consolefont.\",\n      \"type\": \"string\"\n    },\n    \"gl_contrast\": {\n      \"default\": \"1.0\",\n      \"desc\": \"Change contrast.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range 1 to 3.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_coronas\": {\n      \"default\": \"0\",\n      \"desc\": \"Adds coronas to some effects:\\n\\n - Explosions: Basically just a bright flash since the default explosions didn't feel powerful enough.\\n - Weapon impacts (optional, see gl_particle_*).\\n - Muzzleflashes.\\n - Lightning bolts.\\n - Fire: Torches have glows.\\n - Rocket Lights.\",\n      \"group-id\": \"15\",\n      \"type\": \"float\"\n    },\n    \"gl_coronas_tele\": {\n      \"default\": \"0\",\n      \"desc\": \"Adds a blue glow to spawn/teleport effects.\",\n      \"group-id\": \"15\",\n      \"remarks\": \"Requires gl_coronas 1.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_cshiftpercent\": {\n      \"default\": \"100\",\n      \"desc\": \"This variable sets the percentage value for palette shifting effects (damage flash, powerup blend). (needs gl_polyblend 1).\",\n      \"group-id\": \"39\",\n      \"type\": \"float\"\n    },\n    \"gl_custom_grenade_color\": {\n      \"default\": \"\",\n      \"desc\": \"Allows color of grenades to be set without requiring a texture change.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Leave blank to disable.\\nSee gl_custom_grenade_fullbright.\",\n      \"type\": \"string\"\n    },\n    \"gl_custom_grenade_fullbright\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines if gl_custom_grenade_color refers to a fullbright color or standard.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Has no effect if gl_custom_grenade_color is blank.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_custom_grenade_tf\": {\n      \"default\": \"1\",\n      \"desc\": \"Allows gl_custom_grenade_* variables to be ignored when playing Team Fortress.\",\n      \"group-id\": \"35\",\n      \"type\": \"boolean\"\n    },\n    \"gl_custom_lg_color\": {\n      \"default\": \"\",\n      \"desc\": \"Allows color of lightning shaft to be set without requiring a texture change.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Leave blank to disable.\\nHas no effect if particle shaft is enabled.\",\n      \"type\": \"string\"\n    },\n    \"gl_custom_lg_fullbright\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines if gl_custom_lg_color refers to a fullbright color or standard.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Has no effect if gl_custom_lg_color is blank.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_custom_lgpack_color\": {\n      \"default\": \"64 64 255\",\n      \"desc\": \"Color lightning gun backpacks with this value.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Leave blank to disable. QTV/MVD only, KTX 1.38+ only.\",\n      \"type\": \"string\"\n    },\n    \"gl_custom_rlpack_color\": {\n      \"default\": \"255 64 64\",\n      \"desc\": \"Tints rocket launcher backpacks with this value.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Leave blank to disable. QTV/MVD only, KTX 1.38+ only.\",\n      \"type\": \"string\"\n    },\n    \"gl_custom_rocket_color\": {\n      \"default\": \"\",\n      \"desc\": \"Allows color of rockets to be set without requiring a texture change.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Leave blank to disable.\\nSee gl_custom_rocket_fullbright.\",\n      \"type\": \"string\"\n    },\n    \"gl_custom_rocket_fullbright\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines if gl_custom_rocket_color refers to a fullbright color or standard.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Has no effect if gl_custom_rocket_color is blank.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_custom_spike_color\": {\n      \"default\": \"\",\n      \"desc\": \"Allows color of spikes/nails to be set without requiring a texture change.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Leave blank to disable.\\nSee gl_custom_spike_fullbright.\",\n      \"type\": \"string\"\n    },\n    \"gl_custom_spike_fullbright\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines if gl_custom_spike_color refers to a fullbright color or standard.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Has no effect if gl_custom_spike_color is blank.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_cutf_tesla_effect\": {\n      \"default\": \"0\",\n      \"desc\": \"When a shambler is preparing to throw lightning, or a tesla coil (TF) is charging to fire, a particle effect is generated.\",\n      \"group-id\": \"35\",\n      \"type\": \"float\"\n    },\n    \"gl_detail\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles an extra layer of fine detail over world textures.\",\n      \"group-id\": \"8\",\n      \"remarks\": \"Can impact performance on weak GPUs.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_detpacklights\": {\n      \"default\": \"0\",\n      \"desc\": \"A little green light appears on the detpack, and when the timer reaches 5, the light changes to red.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"gl_coronas must be turned on for this to work.\",\n      \"type\": \"float\"\n    },\n    \"gl_ext_texture_compression\": {\n      \"default\": \"0\",\n      \"desc\": \"Compress textures on GPUs that support it to save VRAM, at the cost of longer load times and degraded quality.\",\n      \"group-id\": \"50\",\n      \"remarks\": \"No longer needed when you have many hundreds of Mb of VRAM.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_externalTextures_bmodels\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles loading external 24-bit textures for non-world bsp models, i.e. health and ammo boxes.\",\n      \"group-id\": \"50\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable (classic look).\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"gl_externalTextures_world\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles loading external 24-bit textures for the world, i.e. the actual map.\",\n      \"group-id\": \"50\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable (classic look).\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"gl_extratrails\": {\n      \"default\": \"0\",\n      \"desc\": \"Misc trails that appear on objects that frequently move, such as TF grenades, caltrops, etc.\\nRailguns in TF also leave behind unique trails.\",\n      \"group-id\": \"35\",\n      \"type\": \"float\"\n    },\n    \"gl_fb_bmodels\": {\n      \"default\": \"1\",\n      \"desc\": \"Enable \\\"fullbright\\\" colors on bsp models (World, health boxes, etc). Might give you a couple more fps.\",\n      \"group-id\": \"15\",\n      \"type\": \"boolean\"\n    },\n    \"gl_fb_models\": {\n      \"default\": \"1\",\n      \"desc\": \"Enable \\\"fullbright\\\" colors on alias models (grenades, player models, etc). Might give you a couple more fps.\",\n      \"group-id\": \"15\",\n      \"type\": \"boolean\"\n    },\n    \"gl_finish\": {\n      \"default\": \"0\",\n      \"desc\": \"Wait until OpenGL has finished working to start rendering the next frame.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"In the past, this setting might have helped (or worsened) some types of graphics glitches and timing issues.\\nIt is mostly there for compatibility reasons and best left disabled unless you have bugs, performance problems, an unusual setup (in any combination).\",\n      \"type\": \"boolean\"\n    },\n    \"gl_flashblend\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable affects when glow bubbles are displayed in the client. You can change the color of the rocket glow by r_rocketLightColor, the color of explosions by r_explosionLightColor, and the color of flag carriers by r_flagColor.\",\n      \"group-id\": \"15\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No glow bubbles.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"A glow bubble will appear on explosions and flag/quad/pent carrier.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Same as (1), but also around rockets in the air.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"gl_gamma\": {\n      \"default\": \"1.0\",\n      \"desc\": \"Change gamma.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range 0.3 to 3.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_hwblend\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles between OpenGL and Hardware palette changing.\",\n      \"group-id\": \"39\",\n      \"remarks\": \"1 can be slow on WinNT/2K.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_inferno_speed\": {\n      \"default\": \"1000\",\n      \"desc\": \"Changes speed of gl_inferno missile trail.\",\n      \"group-id\": \"35\",\n      \"type\": \"float\"\n    },\n    \"gl_inferno_trail\": {\n      \"default\": \"2\",\n      \"desc\": \"Changes type of gl_inferno missile trail.\",\n      \"group-id\": \"35\",\n      \"type\": \"float\"\n    },\n    \"gl_lerpimages\": {\n      \"default\": \"1\",\n      \"desc\": \"Improves quality of non-power of two textures, at a slight load time cost. No impact on framerates.\",\n      \"group-id\": \"50\",\n      \"remarks\": \"Only applies to GPUs which do not support non-power of two textures.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Worse texture quality, faster loading maps.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Better texture quality, slower loading maps.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"gl_lighting_color\": {\n      \"default\": \"0\",\n      \"desc\": \"Alias models are effected by colored lights around them.\",\n      \"group-id\": \"15\",\n      \"type\": \"float\"\n    },\n    \"gl_lighting_vertex\": {\n      \"default\": \"0\",\n      \"desc\": \"Alias models no longer have the same level of light on all sides.\\nThis may not work correctly if colored lighting is disabled.\",\n      \"group-id\": \"15\",\n      \"type\": \"float\"\n    },\n    \"gl_lightmode\": {\n      \"default\": \"1\",\n      \"desc\": \"Enables overbright lightmaps.\\nBrings more contrast, making world lighting look more like software Quake, and less like GLQuake.\",\n      \"group-id\": \"15\",\n      \"remarks\": \"Takes effect when the map reloads.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Makes the map lighter, but fullbrights don't look as good. (0% overbrights)\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Makes the map darker, but makes fullbrights stand out more (looks better). (33% overbrights)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"gl_lightning\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles particle lightning beams. Glow is controlled by gl_coronas.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Restricted in rulesets qcon and smackdown/drive (glow effect remains).\",\n      \"type\": \"boolean\"\n    },\n    \"gl_lightning_color\": {\n      \"default\": \"120 140 255\",\n      \"desc\": \"The RGB color of particle lightning beam and sparks.\",\n      \"group-id\": \"35\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"R G B alpha, each 0-255\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_lightning_size\": {\n      \"default\": \"3\",\n      \"desc\": \"Adjusts size of lightning particle beam.\",\n      \"group-id\": \"35\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range 3 to 30.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_lightning_sparks\": {\n      \"default\": \"0\",\n      \"desc\": \"Sparks fly from walls when hit by lightning gun.\",\n      \"group-id\": \"35\",\n      \"type\": \"float\"\n    },\n    \"gl_lightning_sparks_size\": {\n      \"default\": \"300\",\n      \"desc\": \"Size of lightning sparks.\",\n      \"group-id\": \"35\",\n      \"type\": \"integer\"\n    },\n    \"gl_loadlitfiles\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls when external 24-bit lighting will be used.\\n24-bit/colored lighting is either embedded in newer .bsp files, or loaded from external .lit files.\",\n      \"group-id\": \"15\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disable 24-bit lighting.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Load lit files & use inline colored lighting.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Load lit files only.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Use inline colored lighting only.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"gl_luma_level\": {\n      \"default\": \"1\",\n      \"desc\": \"Some luma textures have black non-transparent pixels. This can make them transparent.\",\n      \"group-id\": \"50\",\n      \"remarks\": \"Setting this to 0 will disable the functionality. For some commonly used texture packs value 1 is necessary.\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"e.g. value 10 makes particular pixels transparent if each color component is LESS than 10.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_lumatextures\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns using luma textures (named *_luma) ON when set to 1 and allowed by server.\",\n      \"group-id\": \"50\",\n      \"type\": \"float\"\n    },\n    \"gl_max_size\": {\n      \"default\": \"32768\",\n      \"desc\": \"This variable determines the detail level for loaded textures.\\nWhen set to \\\"1\\\", the objects and walls will be textured with 1x1 pixel textures.\",\n      \"group-id\": \"50\",\n      \"type\": \"float\"\n    },\n    \"gl_miptexLevel\": {\n      \"default\": \"0\",\n      \"desc\": \"Downscale textures in a way that looks like the 'd_mipcap' setting from classic software-rendered QW.\\nHas no effect on external 24-bit textures.\",\n      \"group-id\": \"50\",\n      \"type\": \"float\"\n    },\n    \"gl_modulate\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls how bright the lightmap gets.\",\n      \"group-id\": \"35\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range 0.5 to 3.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_motion_blur\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables motion blur effect (set gl_motion_blur_* variables to configure)\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\"\n    },\n    \"gl_motion_blur_dead\": {\n      \"default\": \"0.5\",\n      \"desc\": \"Level of blending of current scene with previous, when the player is dead.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"gl_motion_blur_fps\": {\n      \"default\": \"77\",\n      \"desc\": \"How often to copy the current frame to apply motion blur effect. Defaults to 77.\",\n      \"group-id\": \"8\",\n      \"type\": \"integer\"\n    },\n    \"gl_motion_blur_hurt\": {\n      \"default\": \"0.5\",\n      \"desc\": \"Level of blending of current scene with previous, when the player has recently been hurt.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"gl_motion_blur_norm\": {\n      \"default\": \"0.5\",\n      \"desc\": \"Level of blending of current scene with previous, when the player is alive and not recently hurt.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"gl_multisamples\": {\n      \"default\": \"0\",\n      \"desc\": \"The number of samples used around each pixel for anti-aliasing.\",\n      \"group-id\": \"52\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Range 0 to 16.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_nailtrail\": {\n      \"default\": \"0\",\n      \"desc\": \"Adds a white trail onto nails as they fly around.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"This feature won't work on servers not running with sv_nailhack set to 1.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_nailtrail_plasma\": {\n      \"default\": \"0\",\n      \"desc\": \"Adds a blue plasma trail to nail trails.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Requires gl_nailtrail 1.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_nailtrail_turb\": {\n      \"default\": \"0\",\n      \"desc\": \"Switches between two type of bubbles nails leave behind in water.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"gl_turb_trails 1 needs to be set.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Smaller bubbles\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Larger bubbles\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"gl_no24bit\": {\n      \"default\": \"0\",\n      \"desc\": \"Disables support of alternate 24-bit textures.\",\n      \"group-id\": \"50\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Allow 24-bit textures\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Disable 24-bit textures\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"gl_nocolors\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable top/bottom colors on 8-bit .pcx skins.\",\n      \"group-id\": \"35\",\n      \"type\": \"boolean\"\n    },\n    \"gl_oldlitscaling\": {\n      \"default\": \"0\",\n      \"desc\": \"Scales lit files the 'old' way ('makes colored areas too dark').\",\n      \"group-id\": \"15\",\n      \"type\": \"boolean\"\n    },\n    \"gl_outline\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls outlining of models and map. Disallowed in qcon ruleset.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"World outlines require vid_framebuffer 1|2 and Modern OpenGL (vid_renderer 1). With vid_renderer 0, ruleset default and sv_cheats 1, gl_outline 2 draws outlines of every surface that is rendered.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Outlining disabled\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Outline models\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Outline world\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Outline models and world\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"gl_outline_color_enemy\": {\n      \"default\": \"\",\n      \"desc\": \"Determines the outline color of enemy players. Set to \\\"\\\" to use the value of gl_outline_color_model.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Requires vid_renderer 1\",\n      \"type\": \"string\"\n    },\n    \"gl_outline_color_model\": {\n      \"default\": \"0 0 0\",\n      \"desc\": \"Determines the color of model outlines.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Requires vid_renderer 1\",\n      \"type\": \"string\"\n    },\n    \"gl_outline_color_team\": {\n      \"default\": \"\",\n      \"desc\": \"Determines the outline color of friendly players. Set to \\\"\\\" to use the value of gl_outline_color_model.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Requires vid_renderer 1\",\n      \"type\": \"string\"\n    },\n    \"gl_outline_color_world\": {\n      \"default\": \"0 0 0\",\n      \"desc\": \"Determines the color of world outlines.\",\n      \"group-id\": \"35\",\n      \"type\": \"string\"\n    },\n    \"gl_outline_scale_model\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines the scale of model outlines. Allows values 0 to 1 for rulesets smackdown, smackdrive and qcon, and 0 to 5 for other rulesets.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Requires vid_renderer 1\",\n      \"type\": \"float\"\n    },\n    \"gl_outline_use_player_color\": {\n      \"default\": \"0\",\n      \"desc\": \"Use the top and bottom color for drawing player outlines\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Requires vid_renderer 1\",\n      \"type\": \"boolean\"\n    },\n    \"gl_outline_world_depth_threshold\": {\n      \"default\": \"4\",\n      \"desc\": \"Threshold for finding edges with depth values. Higher value means vanishing lines at close range. Lower value means false positives at longer range.\",\n      \"group-id\": \"35\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range 1 to 16.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_outline_world_normal_threshold\": {\n      \"default\": \"0.997\",\n      \"desc\": \"Threshold for finding edges face orientation. Higher value is more edges.\",\n      \"group-id\": \"35\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range 0 to 0.999\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_part_blobs\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines particles used for blob explosions (EMPs).\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_part_blood\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines particles used for blood effects.\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_part_bloodtrails\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines particles used for blood trails (gibs).\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_part_bubble\": {\n      \"default\": \"1\",\n      \"desc\": \"Advanced particles for bubbles (drowning effect).\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_part_cache\": {\n      \"default\": \"1\",\n      \"description\": \"Reduce CPU overhead when newer particle effects are used.\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not cache hull-traces\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Reduce hull-traces as particles move\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"gl_part_detpackexplosion_fire_color\": {\n      \"default\": \"\",\n      \"desc\": \"Change color of the detpack explosion fire (TF).\",\n      \"group-id\": \"36\",\n      \"type\": \"string\"\n    },\n    \"gl_part_detpackexplosion_ray_color\": {\n      \"default\": \"\",\n      \"desc\": \"Change the color of the detpack explosion rays (Team Fortress).\",\n      \"group-id\": \"36\",\n      \"type\": \"string\"\n    },\n    \"gl_part_explosions\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines particles used for explosions.\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_part_gunshots\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines particles used for gunshots.\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_part_inferno\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines particles used for pyro flames in TF.\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_part_lavasplash\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines particles used for lava splashes (Spy Gren).\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_part_spikes\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines particles used for spikes (nailgun etc).\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_part_telesplash\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines particles used for teleport effects.\",\n      \"group-id\": \"36\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Use classic quake teleport particles.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Use 'new/eye-candy' teleport particles: 'dots' and 'rays'\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Use 'new/eye-candy' teleport particles: 'dots' only\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"gl_part_tracer1_color\": {\n      \"default\": \"0 124 0\",\n      \"desc\": \"Changes the color of the tracer1 trail, which allows you to customize the rockettrail color (see remarks).\",\n      \"group-id\": \"36\",\n      \"remarks\": \"If you set r_rockettrail 6 and gl_part_trails 1, this variable will change your rockettrail color.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"\\\"R G B\\\" format accepted, e.g. \\\"255 255 0\\\" for yellow\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_part_tracer1_size\": {\n      \"default\": \"3.75\",\n      \"desc\": \"Changes the size of the tracer1 trail, which allows you to customize the rockettrail size (see remarks).\",\n      \"group-id\": \"36\",\n      \"remarks\": \"If you set r_rockettrail 6 and gl_part_trails 1, this variable will change your rockettrail size.\",\n      \"type\": \"float\"\n    },\n    \"gl_part_tracer1_time\": {\n      \"default\": \"0.5\",\n      \"desc\": \"Changes the duration of the tracer1 trail, which allows you to customize the rockettrail (see remarks).\",\n      \"group-id\": \"36\",\n      \"remarks\": \"If you set r_rockettrail 6 and gl_part_trails 1, this variable will change your rockettrail duration.\",\n      \"type\": \"float\"\n    },\n    \"gl_part_tracer2_color\": {\n      \"default\": \"255 77 0\",\n      \"desc\": \"Changes the color of the tracer2 trail, which allows you to customize the rockettrail color (see remarks).\",\n      \"group-id\": \"36\",\n      \"remarks\": \"If you set r_rockettrail 7 and gl_part_trails 1, this variable will change your rockettrail color.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"\\\"R G B\\\" format accepted, e.g. \\\"255 255 0\\\" for yellow\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"gl_part_tracer2_size\": {\n      \"default\": \"3.75\",\n      \"desc\": \"Changes the size of the tracer2 trail, which allows you to customize the rockettrail size (see remarks).\",\n      \"group-id\": \"36\",\n      \"remarks\": \"If you set r_rockettrail 7 and gl_part_trails 1, this variable will change your rockettrail size.\",\n      \"type\": \"float\"\n    },\n    \"gl_part_tracer2_time\": {\n      \"default\": \"0.5\",\n      \"desc\": \"Changes the duration of the tracer2 trail, which allows you to customize the rockettrail (see remarks).\",\n      \"group-id\": \"36\",\n      \"remarks\": \"If you set r_rockettrail 7 and gl_part_trails 1, this variable will change your rockettrail duration.\",\n      \"type\": \"float\"\n    },\n    \"gl_part_trails\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines particles used for (rocket etc) trails.\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_particle_blobs\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines size of blob explosions.\\nIf set to 0, standard non-particle explosions will be used instead.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_blood\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines size of blood effect.\\nIf set to 0, standard blood effect will be used instead.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_blood_color\": {\n      \"default\": \"1\",\n      \"desc\": \"Changes color of blood particles.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_blood_type\": {\n      \"default\": \"1\",\n      \"desc\": \"Chooses among types of blood particles.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_deatheffect\": {\n      \"default\": \"0\",\n      \"desc\": \"Adds extra blood effects to gibs.\",\n      \"group-id\": \"36\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No extra effects\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Death effect enabled for all corpses\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Death effect for gibbed players only\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"gl_particle_explosions\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines alternate particles for explosions.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_fasttrails\": {\n      \"default\": \"0\",\n      \"desc\": \"Changes particle trails indicating motion.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_fire\": {\n      \"default\": \"0\",\n      \"desc\": \"Replaces torches with a particle flame effect.\\nIncludes people being burnt by the pyro in TF.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_firecolor\": {\n      \"default\": \"\",\n      \"desc\": \"Color of the fire particles.\",\n      \"group-id\": \"36\",\n      \"type\": \"string\"\n    },\n    \"gl_particle_fulldetail\": {\n      \"default\": \"0\",\n      \"desc\": \"Allows full detail depth for gl_particle_* effects.\",\n      \"group-id\": \"36\",\n      \"remarks\": \"Requires vid_restart.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_particle_gibtrails\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable alternate gib trails with bright blood.\",\n      \"group-id\": \"36\",\n      \"remarks\": \"Requires gl_part_trails 1.\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_gunshots\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable alternate gunshot impact effects.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_gunshots_type\": {\n      \"default\": \"1\",\n      \"desc\": \"Selects alternate gunshot impact effects.\",\n      \"group-id\": \"36\",\n      \"remarks\": \"Light flashes also appear with gl_coronas 1.\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Long, thin, curving orange sparks.\",\n          \"name\": \"0 and 1\"\n        },\n        {\n          \"description\": \"Large, bright yellowish sparks.\",\n          \"name\": \"2 and 3\"\n        },\n        {\n          \"description\": \"Short, straight orange sparks.\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"gl_particle_muzzleflash\": {\n      \"default\": \"0\",\n      \"desc\": \"Adds a particle effect when monsters and other players fire a weapon.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_shockwaves\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls the volumetric shockwave for the alternate blob explosion effects (r_explosiontype 6).\",\n      \"group-id\": \"36\",\n      \"remarks\": \"Requires gl_particle_explosions 1.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_particle_shockwaves_flat\": {\n      \"default\": \"0\",\n      \"desc\": \"Adds a flat, orange shockwave to alternate explosion effects.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_sparks\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls particle sparks.\\nTrails appear more beam-like and don't look stupid when they bounce.\\nThis applies for wizards and knights only.\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_particle_spikes\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable alternate spike impact effects.\",\n      \"group-id\": \"36\",\n      \"remarks\": \"Light flashes also appear with gl_coronas 1.\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_spikes_type\": {\n      \"default\": \"1\",\n      \"desc\": \"Selects alternate spike impact effects.\",\n      \"group-id\": \"36\",\n      \"remarks\": \"Light flashes also appear with gl_coronas 1.\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Long, thin, curving orange sparks.\",\n          \"name\": \"0 and 1\"\n        },\n        {\n          \"description\": \"Large, bright yellowish sparks.\",\n          \"name\": \"2 and 3\"\n        },\n        {\n          \"description\": \"Short, straight orange sparks.\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"gl_particle_telesplash\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable alternative teleport effect with extra sparks.\",\n      \"group-id\": \"36\"\n    },\n    \"gl_particle_trail_detail\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls the level of detail on the particle lightning beams, and some types of sparks.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_trail_length\": {\n      \"default\": \"1\",\n      \"desc\": \"X - Multiplies the length of the trail on particle trails.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_trail_time\": {\n      \"default\": \"1\",\n      \"desc\": \"X - Multiplies the length of time the particle bounces around for.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_particle_trail_type\": {\n      \"default\": \"1\",\n      \"desc\": \"Changes the type of particle on alt. gunshots, fuel rod explosions...\",\n      \"group-id\": \"36\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Long, thin, curving orange sparks.\",\n          \"name\": \"0 and 1\"\n        },\n        {\n          \"description\": \"A mist of small white pebbles.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"gl_particle_trail_width\": {\n      \"default\": \"3\",\n      \"desc\": \"Changes width of particle trails, e.g. wall hit by nail, explosion particles trail, etc.\",\n      \"group-id\": \"36\",\n      \"type\": \"float\"\n    },\n    \"gl_picmip\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines the level of detail for world textures.\",\n      \"group-id\": \"50\",\n      \"remarks\": \"Does not apply to models, liquids, simple icons, but you can also picmip these with the gl_playermip and gl_scale* cvars.\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Original size.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Half the dimensions.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"One-fourth the dimensions.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Etc...\",\n          \"name\": \"x\"\n        }\n      ]\n    },\n    \"gl_playermip\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines the level of detail of player model textures.\",\n      \"group-id\": \"50\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Full detail.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Medium detail.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Low detail.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Etc...\",\n          \"name\": \"x\"\n        }\n      ]\n    },\n    \"gl_polyblend\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls a short burst of screen tinting when submerged, taking a powerup, or taking damage.\\nVariables to look at are: gl_cshiftpercent (controls overall palette shifting) v_damagechisft v_quadcshift v_pentcshift v_ringcshift v_suitcshift.\",\n      \"group-id\": \"39\",\n      \"type\": \"boolean\"\n    },\n    \"gl_powerupshells\": {\n      \"default\": \"1\",\n      \"desc\": \"Enables a flashing layer over players carrying powerups.\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\"\n    },\n    \"gl_powerupshells_base1level\": {\n      \"default\": \"0.05\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"gl_powerupshells_base2level\": {\n      \"default\": \"0.1\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"gl_powerupshells_effect1level\": {\n      \"default\": \"0.75\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"gl_powerupshells_effect2level\": {\n      \"default\": \"0.4\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"gl_powerupshells_size\": {\n      \"default\": \"5\",\n      \"desc\": \"Size of the powerupshells effect layer.\",\n      \"group-id\": \"8\",\n      \"type\": \"integer\"\n    },\n    \"gl_program_aliasmodels\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines if GLSL will be used to render aliasmodels.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Applies to classic OpenGL renderer only.\",\n      \"renderers\": [\n        \"classic\"\n      ],\n      \"type\": \"integer\"\n    },\n    \"gl_program_hud\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines if GLSL will be used to render the HUD.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Applies to classic OpenGL renderer only.\",\n      \"renderers\": [\n        \"classic\"\n      ],\n      \"type\": \"integer\"\n    },\n    \"gl_program_sky\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines if GLSL will be used to render skybox/skydome surfaces.\\nr_fastsky does not use this setting.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Applies to classic OpenGL renderer only.\",\n      \"renderers\": [\n        \"classic\"\n      ],\n      \"type\": \"integer\"\n    },\n    \"gl_program_sprites\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines if GLSL will be used to render sprites.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Applies to classic OpenGL renderer only.\",\n      \"renderers\": [\n        \"classic\"\n      ],\n      \"type\": \"integer\"\n    },\n    \"gl_program_turbsurfaces\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines if GLSL will be used to render 'liquid' surfaces.\\nr_fastturb does not use this setting.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Applies to classic OpenGL renderer only.\",\n      \"renderers\": [\n        \"classic\"\n      ],\n      \"type\": \"integer\"\n    },\n    \"gl_program_world\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines if GLSL will be used to render world surfaces (walls, floors..).\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Applies to classic OpenGL renderer only.\",\n      \"renderers\": [\n        \"classic\"\n      ],\n      \"type\": \"integer\"\n    },\n    \"gl_rl_globe\": {\n      \"default\": \"0\",\n      \"desc\": \"Helps customize rocket light independent of gl_flashblend.\",\n      \"group-id\": \"15\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Rocket light controlled by gl_flashblend\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Rocket light looks like with gl_flashblend 2, but no dynamic lighting\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Rocket light looks like with gl_flashblend 0 or 1\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Rocket light looks like with gl_flashblend 2, with dynamic lighting\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"gl_scaleAlphaTextures\": {\n      \"default\": \"0\",\n      \"desc\": \"Applies picmip/max_size/miptexlevel to alpha masked textures (fence).\",\n      \"group-id\": \"50\",\n      \"type\": \"boolean\"\n    },\n    \"gl_scaleModelSimpleTextures\": {\n      \"default\": \"0\",\n      \"desc\": \"Applies picmip/max_size/miptexlevel to gl_simpleitem icons.\",\n      \"group-id\": \"50\",\n      \"type\": \"boolean\"\n    },\n    \"gl_scaleModelTextures\": {\n      \"default\": \"0\",\n      \"desc\": \"Applies picmip/max_size/miptexlevel to model textures.\",\n      \"group-id\": \"50\",\n      \"type\": \"boolean\"\n    },\n    \"gl_scaleTurbTextures\": {\n      \"default\": \"1\",\n      \"desc\": \"Applies picmip/max_size/miptexlevel to turb textures (lava, water, slime, teleports).\",\n      \"group-id\": \"50\",\n      \"type\": \"boolean\"\n    },\n    \"gl_scaleskytextures\": {\n      \"default\": \"0\",\n      \"desc\": \"Applies picmip/max_size/miptexlevel to sky textures (skydome and skybox).\",\n      \"group-id\": \"50\",\n      \"type\": \"boolean\"\n    },\n    \"gl_shaftlight\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles between darker/fullbright shaft beams.\",\n      \"group-id\": \"15\",\n      \"type\": \"boolean\"\n    },\n    \"gl_simpleitems\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles drawing simple icons instead of 3D item models.\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\"\n    },\n    \"gl_simpleitems_orientation\": {\n      \"default\": \"2\",\n      \"group-id\": \"8\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Orientation based on player's view\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Facing the player, horizontally only\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Directly facing the player\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Items rotate\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"gl_simpleitems_size\": {\n      \"default\": \"16\",\n      \"desc\": \"Size of simple items. Max size 16.\",\n      \"group-id\": \"8\",\n      \"type\": \"integer\"\n    },\n      \"gl_smoothmodels\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles between flat-shaded and smooth shaded models.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Applies to GLSL OpenGL renderer only.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_spec_xray\": {\n      \"default\": \"0\",\n      \"desc\": \"See players through walls (demo/qtv only). Is affected by gl_outline_* values like color, scale, etc.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"Requires vid_renderer 1\",\n      \"type\": \"boolean\"\n    },\n    \"gl_spec_xray_distance\": {\n      \"default\": \"1500\",\n      \"desc\": \"Distance from which you can see the players through walls, see gl_spec_xray\",\n      \"group-id\": \"35\",\n      \"type\": \"float\"\n    },\n    \"gl_squareparticles\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles between circle and square particles.\",\n      \"group-id\": \"36\",\n      \"type\": \"boolean\"\n    },\n    \"gl_subdivide_size\": {\n      \"default\": \"64\",\n      \"desc\": \"This variable sets the division value for the sky brushes.\\nThe higher the value the better the performance, but the smoothness of the sky suffers.\",\n      \"group-id\": \"50\",\n      \"type\": \"float\"\n    },\n    \"gl_surface_lava\": {\n      \"default\": \"0\",\n      \"desc\": \"Adds boiling bubbles over the lava.\",\n      \"group-id\": \"51\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disabled\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Small fumes\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Large fumes\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Like 1 but adjusted by HyperNewbie\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Like 2 but adjusted by HyperNewbie\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"gl_surface_slime\": {\n      \"default\": \"0\",\n      \"desc\": \"Adds bubbles and fumes of slime over the slime.\",\n      \"group-id\": \"51\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disabled\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Small fumes\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Large fumes\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Like 1 but adjusted by HyperNewbie\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Like 2 but adjusted by HyperNewbie\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"gl_textureless\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles between textures and flat colors based on the textures (looks like gl_max_size 1).\\nFor custom colors, look for r_drawflat.\",\n      \"group-id\": \"50\",\n      \"remarks\": \"The effect varies with picmip/max_size.\\nCan not be changed during a match.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Use world textures.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Use flat colors based on the world textures.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"gl_texturemode\": {\n      \"default\": \"GL_LINEAR_MIPMAP_LINEAR\",\n      \"group-id\": \"50\",\n      \"remarks\": \"GL_NEAREST* modes are often used with gl_miptexlevel 3.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Linear filtering, no blending.\",\n          \"name\": \"GL_LINEAR\"\n        },\n        {\n          \"description\": \"Trilinear interpolation. Highest quality, lowest performance.\",\n          \"name\": \"GL_LINEAR_MIPMAP_LINEAR\"\n        },\n        {\n          \"description\": \"Bilinear interpolation.\",\n          \"name\": \"GL_LINEAR_MIPMAP_NEAREST\"\n        },\n        {\n          \"description\": \"Point sampled (software-like). Lowest quality, highest performance.\",\n          \"name\": \"GL_NEAREST\"\n        },\n        {\n          \"description\": \"GL_NEAREST but with even more quality for far objects.\",\n          \"name\": \"GL_NEAREST_MIPMAP_LINEAR\"\n        },\n        {\n          \"description\": \"GL_NEAREST but with a bit more quality for far objects.\",\n          \"name\": \"GL_NEAREST_MIPMAP_NEAREST\"\n        }\n      ]\n    },\n    \"gl_texturemode_viewmodels\": {\n      \"default\": \"GL_LINEAR\",\n      \"group-id\": \"50\",\n      \"remarks\": \"GL_NEAREST* modes are often used with gl_miptexlevel 3.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Linear filtering, no blending.\",\n          \"name\": \"GL_LINEAR\"\n        },\n        {\n          \"description\": \"Trilinear interpolation. Highest quality, lowest performance.\",\n          \"name\": \"GL_LINEAR_MIPMAP_LINEAR\"\n        },\n        {\n          \"description\": \"Bilinear interpolation.\",\n          \"name\": \"GL_LINEAR_MIPMAP_NEAREST\"\n        },\n        {\n          \"description\": \"Point sampled (software-like). Lowest quality, highest performance.\",\n          \"name\": \"GL_NEAREST\"\n        },\n        {\n          \"description\": \"GL_NEAREST but with even more quality for far objects.\",\n          \"name\": \"GL_NEAREST_MIPMAP_LINEAR\"\n        },\n        {\n          \"description\": \"GL_NEAREST but with a bit more quality for far objects.\",\n          \"name\": \"GL_NEAREST_MIPMAP_NEAREST\"\n        }\n      ]\n    },\n    \"gl_triplebuffer\": {\n      \"default\": \"1\",\n      \"desc\": \"Triple buffering of HUD graphics. If you have problems with screen graphics, turn this on.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"This has nothing to do with 3D rendering, it will not increase graphics \\\"lag\\\" or anything like that. It only affects 2D HUD graphics.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_turb_effects\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls if shotgun/nailgun impact points will spawn bubbles underwater.\",\n      \"group-id\": \"51\",\n      \"remarks\": \"Requires QMB particles to be enabled (corresponding gl_particle_xxx option enabled).\",\n      \"type\": \"boolean\"\n    },\n    \"gl_turb_fire\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls if explosions/fire will spawn bubbles underwater.\",\n      \"group-id\": \"51\",\n      \"remarks\": \"Requires QMB particles to be enabled (corresponding gl_particle_xxx option enabled).\",\n      \"type\": \"boolean\"\n    },\n    \"gl_turb_trails\": {\n      \"default\": \"0\",\n      \"desc\": \"This gives rockets/grenades/nails and other things an alternate underwater trail.\\nVery nice to have nailgun fights underwater...\",\n      \"group-id\": \"51\",\n      \"type\": \"float\"\n    },\n    \"gl_turbalpha\": {\n      \"default\": \"1\",\n      \"desc\": \"This variable determines the level of opacity for liquids, which requires the use of maps that were VISed with transparent water.\\nWhen using maps that have not been VISed in such a way, you have to use \\\"r_novis 1\\\".\\nIf \\\"r_novis\\\" is set to \\\"1\\\" or when using a map VISed with transparent water and using setting \\\"gl_turbalpha\\\" to a value lower than \\\"1\\\", the player will be able to see objects and walls through liquids if the server allows that.\\nThis is a very nice feature to have in the game, for more information refer to the \\\"r_novis\\\" command.\\n\\nYou can use any value between \\\"0\\\" and \\\"1\\\" for \\\"gl_turbalpha\\\", here are some examples:\",\n      \"group-id\": \"51\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Transparent.\",\n          \"name\": \"0.00\"\n        },\n        {\n          \"description\": \"Low opacity.\",\n          \"name\": \"0.25\"\n        },\n        {\n          \"description\": \"Medium opacity.\",\n          \"name\": \"0.50\"\n        },\n        {\n          \"description\": \"High opacity.\",\n          \"name\": \"0.75\"\n        },\n        {\n          \"description\": \"Solid.\",\n          \"name\": \"1.00\"\n        }\n      ]\n    },\n    \"gl_vbo_clientmemory\": {\n      \"default\": \"0\",\n      \"desc\": \"Use client memory when rendering dynamic geometry.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"No effect unless using compatibility mode.\",\n      \"type\": \"boolean\"\n    },\n    \"gl_weather_rain\": {\n      \"default\": \"0\",\n      \"desc\": \"Turns on rain outdoors.\\nRain density is equal to whatever gl_weather_rain is set to.\",\n      \"group-id\": \"51\",\n      \"remarks\": \"Works on all custom maps except death32c, dakyne and some others.\",\n      \"type\": \"float\"\n    },\n    \"gl_weather_rain_fast\": {\n      \"default\": \"0\",\n      \"desc\": \"Adjusts rain effects performance.\",\n      \"group-id\": \"51\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Default\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Turn off all splashes\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Turn off only water splashes\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"halflifebsp\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"help_files_dircolor\": {\n      \"desc\": \"The RGB color of directories in the help browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Example: \\\"255 255 255\\\"\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"help_files_filecolor\": {\n      \"desc\": \"The RGB color for files in the help browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Example: \\\"255 255 255\\\"\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"help_files_interline\": {\n      \"desc\": \"Adjust interlining of file list in /help filebrowser.\",\n      \"group-id\": \"25\",\n      \"type\": \"float\"\n    },\n    \"help_files_scrollnames\": {\n      \"desc\": \"Should the filenames in the help browser get scrolled if they don't fit when the file is selected?\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't scroll names\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Scroll names\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"help_files_selectedcolor\": {\n      \"desc\": \"The RGB color of a selected file in the help browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Example: \\\"255 255 255\\\"\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"help_files_showdate\": {\n      \"desc\": \"Shows column displaying file last modification date in /help file browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"float\"\n    },\n    \"help_files_showsize\": {\n      \"desc\": \"Shows column displaying file size in /help file browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"float\"\n    },\n    \"help_files_showstatus\": {\n      \"desc\": \"Show quick info about selected help file in /help file browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"float\"\n    },\n    \"help_files_showtime\": {\n      \"desc\": \"Shows column displaying file last modification time in /help file browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"float\"\n    },\n    \"help_files_sortmode\": {\n      \"desc\": \"Switches sorting methods of files /help file browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"float\"\n    },\n    \"help_files_stripnames\": {\n      \"desc\": \"Strips filenames of help files.\",\n      \"group-id\": \"25\",\n      \"type\": \"float\"\n    },\n    \"hostname\": {\n      \"default\": \"unnamed\",\n      \"desc\": \"Server variable, changes the name of the server displayed in server browsers and server lists.\",\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo1_align\": {\n      \"desc\": \"Sets align of number of shells for both horizontal and vertical direction.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo1_align_x\": {\n      \"desc\": \"Sets horizontal align of number of shells.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo1_align_y\": {\n      \"desc\": \"Sets vertical align of number of shells.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo1_digits\": {\n      \"desc\": \"Sets number of digits for number of shells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo1_frame\": {\n      \"desc\": \"Sets frame visibility and style for number of shells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo1_frame_color\": {\n      \"desc\": \"Defines the color of the background of the ammo1 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo1_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo1_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_ammo1_place\": {\n      \"desc\": \"Sets relative positioning for number of shells.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo1_pos_x\": {\n      \"desc\": \"Sets horizontal position of number of shells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo1_pos_y\": {\n      \"desc\": \"Sets vertical position of number of shells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo1_scale\": {\n      \"desc\": \"Sets size of number of shells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo1_show\": {\n      \"desc\": \"Switches showing of number of shells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo1_style\": {\n      \"desc\": \"Switches graphical style of number of shells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo1_text_color_low\": {\n      \"desc\": \"Text color when low on shells (only used with style 1 or 3, format: 0-255 0-255 0-255)\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo1_text_color_normal\": {\n      \"desc\": \"Text color when shell count is normal (only used with style 1 or 3, format: 0-255 0-255 0-255)\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo2_align\": {\n      \"desc\": \"Sets align of number of nails.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo2_align_x\": {\n      \"desc\": \"Sets horizontal align of number of nails.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo2_align_y\": {\n      \"desc\": \"Sets vertical align of number of nails.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo2_digits\": {\n      \"desc\": \"Sets number of digits for number of nails.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo2_frame\": {\n      \"desc\": \"Sets frame visibility and style for number of nails.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo2_frame_color\": {\n      \"desc\": \"Defines the color of the background of the ammo2 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo2_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo2_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_ammo2_place\": {\n      \"desc\": \"Sets relative positioning for number of nails.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo2_pos_x\": {\n      \"desc\": \"Sets horizontal position of number of nails.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo2_pos_y\": {\n      \"desc\": \"Sets vertical position of number of nails.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo2_scale\": {\n      \"desc\": \"Sets size of number of nails.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo2_show\": {\n      \"desc\": \"Switches showing of number of nails.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo2_style\": {\n      \"desc\": \"Switches graphical style of number of nails.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo2_text_color_low\": {\n      \"desc\": \"Text color when low on nails (only used with style 1 or 3, format: 0-255 0-255 0-255)\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo2_text_color_normal\": {\n      \"desc\": \"Text color when nail count is normal (only used with style 1 or 3, format: 0-255 0-255 0-255)\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo3_align\": {\n      \"desc\": \"Sets align of number of rockets.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo3_align_x\": {\n      \"desc\": \"Sets horizontal align of number of rockets.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo3_align_y\": {\n      \"desc\": \"Sets vertical align of number of rockets.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo3_digits\": {\n      \"desc\": \"Sets number of digits for number of rockets.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo3_frame\": {\n      \"desc\": \"Sets frame visibility and style for number of rockets.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo3_frame_color\": {\n      \"desc\": \"Defines the color of the background of the ammo3 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo3_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo3_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_ammo3_place\": {\n      \"desc\": \"Sets relative positioning for number of rockets.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo3_pos_x\": {\n      \"desc\": \"Sets horizontal position of number of rockets.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo3_pos_y\": {\n      \"desc\": \"Sets vertical position of number of rockets.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo3_scale\": {\n      \"desc\": \"Sets size of number of rockets.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo3_show\": {\n      \"desc\": \"Switches showing of number of rockets.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo3_style\": {\n      \"desc\": \"Switches graphical style of number of rockets.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo3_text_color_low\": {\n      \"desc\": \"Text color when low on rockets (only used with style 1 or 3, format: 0-255 0-255 0-255)\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo3_text_color_normal\": {\n      \"desc\": \"Text color when rocket count is normal (only used with style 1 or 3, format: 0-255 0-255 0-255)\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo4_align\": {\n      \"desc\": \"Sets align of number of cells.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo4_align_x\": {\n      \"desc\": \"Sets horizontal align of number of cells.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo4_align_y\": {\n      \"desc\": \"Sets vertical align of number of cells.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo4_digits\": {\n      \"desc\": \"Sets number of digits for number of cells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo4_frame\": {\n      \"desc\": \"Sets frame visibility and style for number of cells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo4_frame_color\": {\n      \"desc\": \"Defines the color of the background of the ammo4 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo4_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo4_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_ammo4_place\": {\n      \"desc\": \"Sets relative positioning for number of cells.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo4_pos_x\": {\n      \"desc\": \"Sets horizontal position of number of cells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo4_pos_y\": {\n      \"desc\": \"Sets vertical position of number of cells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo4_scale\": {\n      \"desc\": \"Sets size of number of cells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo4_show\": {\n      \"desc\": \"Switches showing of number of cells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo4_style\": {\n      \"desc\": \"Switches graphical style of number of cells.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo4_text_color_low\": {\n      \"desc\": \"Text color when low on cells (only used with style 1 or 3, format: 0-255 0-255 0-255)\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo4_text_color_normal\": {\n      \"desc\": \"Text color when cell count is normal (only used with style 1 or 3, format: 0-255 0-255 0-255)\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo_align\": {\n      \"desc\": \"Sets align of current ammo value.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo_align_x\": {\n      \"desc\": \"Sets horizontal align of current ammo value.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo_align_y\": {\n      \"desc\": \"Sets vertical align of current ammo value.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo_digits\": {\n      \"desc\": \"Sets number of digits for current ammo value.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo_frame\": {\n      \"desc\": \"Sets frame visibility and style for current ammo value.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo_frame_color\": {\n      \"desc\": \"Defines the color of the background of the ammo HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_ammo_place\": {\n      \"desc\": \"Sets relative positioning for current ammo value.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ammo_pos_x\": {\n      \"desc\": \"Sets horizontal position of current ammo value.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo_pos_y\": {\n      \"desc\": \"Sets vertical position of current ammo value.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo_scale\": {\n      \"desc\": \"Sets size of current ammo value.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo_show\": {\n      \"desc\": \"Switches showing of current ammo value.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ammo_style\": {\n      \"desc\": \"Switches graphical style of current ammo value.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armor_align\": {\n      \"desc\": \"Sets align of armor level.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_armor_align_x\": {\n      \"desc\": \"Sets horizontal align of armor level.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_armor_align_y\": {\n      \"desc\": \"Sets vertical align of armor level.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_armor_digits\": {\n      \"desc\": \"Sets number of digits for armor level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armor_frame\": {\n      \"desc\": \"Sets frame visibility and style for armor level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armor_frame_color\": {\n      \"desc\": \"Defines the color of the background of the armor HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_armor_hidezero\": {\n      \"desc\": \"Hide armor number if 0.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armor_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armor_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_armor_pent_666\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_armor_place\": {\n      \"desc\": \"Sets relative positioning for armor level.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_armor_pos_x\": {\n      \"desc\": \"Sets horizontal position of armor level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armor_pos_y\": {\n      \"desc\": \"Sets vertical position of armor level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armor_scale\": {\n      \"desc\": \"Sets size of armor level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armor_show\": {\n      \"desc\": \"Switches showing of armor level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armor_style\": {\n      \"desc\": \"Switches graphical style of armor level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armordamage_align\": {\n      \"desc\": \"Sets armordamage HUD element alignment.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"horizontal: left, center, right, before, after\\nvertical: top, center, bottom, console, before, after\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_armordamage_align_x\": {\n      \"desc\": \"Sets armordamage HUD element horizontal alignment.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"left, center, right, before, after\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_armordamage_align_y\": {\n      \"desc\": \"Sets armordamage HUD element vertical alignment.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"top, center, bottom, console, before, after\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_armordamage_digits\": {\n      \"desc\": \"Sets highest possible number of digits displayed in HUD element armordamage.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armordamage_duration\": {\n      \"desc\": \"Sets how long armordamage should be visible after last damage to armor has been done.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Float number\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_armordamage_frame\": {\n      \"desc\": \"Sets frame visibility and style for this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Adjust background opacity\",\n          \"name\": \"0.x to 1\"\n        },\n        {\n          \"description\": \"Bevelled frame.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"hud_armordamage_frame_color\": {\n      \"desc\": \"Defines the color of the background of the armordamage HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_armordamage_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armordamage_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_armordamage_place\": {\n      \"desc\": \"Sets placement for this HUD element. You can align some elements relative to other elements.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"First you have to decide, if the element that you are locating now (element B) is to be positioned inside another element (element A) or outside it. See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"to place this element inside element B set this variable to \\\"@B\\\", to place it outside B element set this variable to \\\"B\\\"\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_armordamage_pos_x\": {\n      \"desc\": \"Sets horizontal position of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armordamage_pos_y\": {\n      \"desc\": \"Sets vertical position of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_armordamage_scale\": {\n      \"desc\": \"Sets overall size of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Use positive floating point numbers.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_armordamage_show\": {\n      \"desc\": \"Toggles visibility of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw this element\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw this element\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_armordamage_style\": {\n      \"desc\": \"Toggles between different numbers styles.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"large font, texture\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"small font, gfxwad\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_bar_armor_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_bar_armor_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_bar_armor_color_ga\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_color_ga_over\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_color_noarmor\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_color_ra\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_color_unnatural\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_color_ya\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_direction\": {\n      \"desc\": \"Direction of colored part inside HUD element that designates amount of armor.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"left->right\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"right->left\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"down -> up\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"up -> down\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"hud_bar_armor_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_height\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_bar_armor_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_bar_armor_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_armor_width\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_bar_health_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_bar_health_color_mega\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_color_nohealth\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_color_normal\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_color_twomega\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_color_unnatural\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_direction\": {\n      \"desc\": \"Direction of colored part inside HUD element that designates amount of health.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"left->right\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"right->left\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"down -> up\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"up -> down\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"hud_bar_health_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_height\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_bar_health_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_bar_health_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_bar_health_width\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_clock_align_x\": {\n      \"desc\": \"Sets horizontal align of clock.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_clock_align_y\": {\n      \"desc\": \"Sets vertical align of clock.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_clock_big\": {\n      \"desc\": \"Switches unsing larger version of clock.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_clock_blink\": {\n      \"desc\": \"Switches blinking colon of clock.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_clock_content\": {\n      \"desc\": \"Controls what time to display.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Local system time\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Client uptime\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Time spent on server\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"hud_clock_format\": {\n      \"desc\": \"Controls in what format the clock is displayed.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"24hr time with seconds\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"12hr time (AM/PM) without seconds\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"12hr time (AM/PM) with seconds\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"24hr time without seconds\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"hud_clock_frame\": {\n      \"desc\": \"Sets frame visibility and style for clock.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_clock_frame_color\": {\n      \"desc\": \"Defines the color of the background of the clock HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_clock_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_clock_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_clock_place\": {\n      \"desc\": \"Sets relative positioning for clock.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_clock_pos_x\": {\n      \"desc\": \"Sets horizontal position of clock.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_clock_pos_y\": {\n      \"desc\": \"Sets vertical position of clock.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_clock_scale\": {\n      \"desc\": \"Size of the clock HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_clock_show\": {\n      \"desc\": \"Switches showing of clock.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_clock_style\": {\n      \"desc\": \"Switches graphical style of clock.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_democlock_align_x\": {\n      \"desc\": \"Vertical alignment of the democlock HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_democlock_align_y\": {\n      \"desc\": \"Vertical alignment of the democlock HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_democlock_big\": {\n      \"desc\": \"Enables larger version of the democlock.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_democlock_blink\": {\n      \"desc\": \"Enables democlock colon blinking.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_democlock_frame\": {\n      \"desc\": \"Opacity of the background of the democlock HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_democlock_frame_color\": {\n      \"desc\": \"Defines the color of the background of the democlock HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_democlock_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_democlock_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_democlock_place\": {\n      \"desc\": \"Placement of the democlock HUD element. HUD elements can be place into various screen areas or other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_democlock_pos_x\": {\n      \"desc\": \"Horizontal relative position of the democlock HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_democlock_pos_y\": {\n      \"desc\": \"Vertical relative position of the democlock HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_democlock_scale\": {\n      \"desc\": \"Size of the democlock HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_democlock_show\": {\n      \"desc\": \"Visibility of the democlock HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_democlock_style\": {\n      \"desc\": \"Toggles democlock render styles.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_digits_trim\": {\n      \"desc\": \"Changes how large numbers are treated in Head Up Display.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Applies to all HUD elements with 'digits' property.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"10030 will be displayed as \\\"999\\\" with hud_*_digits 3\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"10030 will be displayed as \\\"030\\\" with hud_*_digits 3\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"10030 will be displayed as \\\"100\\\" with hud_*_digits 3\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"hud_editor_allowalign\": {\n      \"desc\": \"Should aligning be allowed when using the HUD editor?\",\n      \"group-id\": \"19\",\n      \"remarks\": \"This can also be toggled when in the HUD editor by using the F3 button.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Dissallow aligning in the HUD editor\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Allow aligning in the HUD editor (alt + mouse 1)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_editor_allowmove\": {\n      \"desc\": \"Allow moving HUD elements when in HUD editor mode.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"This can also be toggled when in the HUD editor by using the F1 button.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Dissalow moving objects in the HUD editor\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Allow moving objects in the HUD editor.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_editor_allowplace\": {\n      \"desc\": \"Allow placing HUD elements in HUD editor mode.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"This can also be toggled when in the HUD editor by using the F4 button.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Dissallow placing HUD elements in the HUD editor\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Allow placing HUD elements in the HUD editor (ctrl + mouse 1)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_editor_allowresize\": {\n      \"desc\": \"Allow resizing HUD elements in HUD editor mode.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"This can also be toggled when in the HUD editor by using the F2 button.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Dissalow resizing HUD elements in the HUD editor\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Allow resizing HUD elements in the HUD editor\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_face_align_x\": {\n      \"desc\": \"Sets horizontal align of player face.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_face_align_y\": {\n      \"desc\": \"Sets vertical align of player face.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_face_frame\": {\n      \"desc\": \"Sets frame visibility and style for player face.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_face_frame_color\": {\n      \"desc\": \"Defines the color of the background of the face HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_face_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_face_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_face_place\": {\n      \"desc\": \"Sets relative positioning for player face.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_face_pos_x\": {\n      \"desc\": \"Sets horizontal position of player face.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_face_pos_y\": {\n      \"desc\": \"Sets vertical position of player face.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_face_scale\": {\n      \"desc\": \"Sets size of player face.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_face_show\": {\n      \"desc\": \"Switches showing of player face.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_fps_align_x\": {\n      \"desc\": \"Sets horizontal align of fps counter.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_fps_align_y\": {\n      \"desc\": \"Sets vertical align of fps counter.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_fps_decimals\": {\n      \"desc\": \"How many decimal number should the FPS HUD element contain.\",\n      \"group-id\": \"29\",\n      \"remarks\": \"Code says always three decimals.\",\n      \"type\": \"integer\"\n    },\n    \"hud_fps_drop\": {\n      \"desc\": \"Sets the value which will trigger displaying the fps (requires hud_fps_style 2 or 3). For example, with value hud_fps_drop 70, the fps will only be displayed if it drops to 70 or below. For fps values greater than 70, the fps will not be displayed.\\nCan also be set to a negative value, interpretted as relative to cl_maxfps\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_fps_frame\": {\n      \"desc\": \"Sets frame visibility and style for fps counter.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_fps_frame_color\": {\n      \"desc\": \"Defines the color of the background of the fps HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_fps_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_fps_min_reset_interval\": {\n      \"group-id\": \"8\",\n      \"type\": \"\"\n    },\n    \"hud_fps_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_fps_place\": {\n      \"desc\": \"Sets relative positioning for fps counter.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_fps_pos_x\": {\n      \"desc\": \"Sets horizontal position of fps counter.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_fps_pos_y\": {\n      \"desc\": \"Sets vertical position of fps counter.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_fps_show\": {\n      \"desc\": \"Switches showing of fps counter.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_fps_show_min\": {\n      \"desc\": \"Switches showing of fps counter.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_fps_style\": {\n      \"desc\": \"Alters how and when the fps is drawn. Please see hud_fps_drop for styles 2 and 3.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"white text\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"brown text\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"white text, fps will show only if the client's fps is less than or equal to hud_fps_drop value\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"brown text, same as 2\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"hud_fps_title\": {\n      \"desc\": \"Switches displaying the text \\\"fps\\\" of fps counter.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_align_x\": {\n      \"desc\": \"Sets horizontal align of frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_frags_align_y\": {\n      \"desc\": \"Sets vertical align of frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_frags_bignum\": {\n      \"desc\": \"Changes the scale of the fragcount number.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"If this is 0 the fragcount will use the normal charset. If it's above 0 it will scale a big number character instead.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_frags_cell_height\": {\n      \"desc\": \"Sets cell height of frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_cell_width\": {\n      \"desc\": \"Sets cell width of frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_colors_alpha\": {\n      \"desc\": \"Sets the opacity of the players colors for the frags hud element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_cols\": {\n      \"desc\": \"Sets number of columns of frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_extra_spec_info\": {\n      \"desc\": \"Enables to see when people have rocket launchers, lightning guns, powerups and how much health and armor they have using icons next to the frags. Works while watching MVD demo.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"No extra information is shown.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Show RL, LG, powerups armor and health.\",\n          \"name\": \"ALL\"\n        },\n        {\n          \"description\": \"Show powerups, armor and health. (No RLs/LGs)\",\n          \"name\": \"POWERUPARMORHEALTH\"\n        },\n        {\n          \"description\": \"Show RL, LG, powerups and health. (No armor)\",\n          \"name\": \"RLLGPOWERUPHEALTH\"\n        },\n        {\n          \"description\": \"Show RL, LG, powerups and armor. (No health)\",\n          \"name\": \"RLLGPOWERUPARMOR\"\n        },\n        {\n          \"description\": \"Show RL, LG, armor and health. (No powerups)\",\n          \"name\": \"RLLGARMORHEALTH\"\n        },\n        {\n          \"description\": \"Show only powerups.\",\n          \"name\": \"POWERUP\"\n        },\n        {\n          \"description\": \"Show only health.\",\n          \"name\": \"HEALTH\"\n        },\n        {\n          \"description\": \"Show only armor.\",\n          \"name\": \"ARMOR\"\n        },\n        {\n          \"description\": \"Show horizontal health meter.\",\n          \"name\": \"HMETER\"\n        },\n        {\n          \"description\": \"Show horizontal power meter.\",\n          \"name\": \"PMETER\"\n        }\n      ]\n    },\n    \"hud_frags_fliptext\": {\n      \"desc\": \"Toggles alignment of players nick and team name in frags HUD element.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Use 'frags shownames 1' and/or 'frags showteams 1' to show names and team names of players.\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Rows aligned to the left side, names and team tags are on the right side of frag counts.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Rows aligned to the right side, names and team tags are on the left side of frag counts.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Rows aligned toward the inside of each column.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Rows aligned toward the outside of each column.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"hud_frags_frame\": {\n      \"desc\": \"Sets frame visibility and style for frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_frame_color\": {\n      \"desc\": \"Defines the color of the background of the frags HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_frags_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_maxname\": {\n      \"desc\": \"The max number of characters to use for displaying the names in the frags element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_frags_notintp\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_frags_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_frags_padtext\": {\n      \"desc\": \"Toggles text padding in 'frags' HUD element.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Use 'frags shownames 1' and/or 'frags showteams 1'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"No spaces between text fields.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Adds padding so frags, names, team tags aligned into columns.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_frags_place\": {\n      \"desc\": \"Sets relative positioning for frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_frags_pos_x\": {\n      \"desc\": \"Sets horizontal position of frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_pos_y\": {\n      \"desc\": \"Sets vertical position of frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_rows\": {\n      \"desc\": \"Sets number of rows in frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_show\": {\n      \"desc\": \"Switches showing of frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_shownames\": {\n      \"desc\": \"Draws players names next to frag counts in 'frags' HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't display players' names.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display players' names.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_frags_showself_always\": {\n      \"desc\": \"Forces the client to show the part of frags table where you are.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Display highest frags only.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display the part of table where you are.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_frags_showteams\": {\n      \"desc\": \"Draws players' team tags next to frag counts in 'frags' HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not display players' team tags.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display players' team tags.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_frags_space_x\": {\n      \"desc\": \"Sets vertical spacing of frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_space_y\": {\n      \"desc\": \"Sets horizontal spacing of frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_strip\": {\n      \"desc\": \"Switches stripped version of frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_style\": {\n      \"desc\": \"Sets drawing style of 'frags' HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Golden brackets around your own frags field.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Arrow pointing to your own frags field.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Red rectangle around your own frags fiels.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"No pointer or indicator.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Sets background color for your own field to 'teamcolor'.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Sets background color for all field to teamcolors enemycolors, all fields 50% transparent, your own field not transparent. Red rectangle around your own field including name and team tag.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Red rectangle around your own field including name and team tag. Background color only for your own field and set to 'teamcolor'.\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Background color for whole table 50% transparent red, your own field not transparent.\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Red background color only for your own field including name and team tag.\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"hud_frags_teamsort\": {\n      \"desc\": \"Switches sorting by teams in frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_frags_vertical\": {\n      \"desc\": \"Switches vertical rendering of frags bar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"No vertical rendering.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Legacy vertical rendering.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"New vertical rendering: split columns evenly based on number of players.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"hud_gameclock_align_x\": {\n      \"desc\": \"Vertical alignment of the gameclock HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gameclock_align_y\": {\n      \"desc\": \"Vertical alignment of the gameclock HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gameclock_big\": {\n      \"desc\": \"Draw the text of the gameclock using big numbers.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Draw the gameclock using the normal charset\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw the gameclock with big numbers.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_gameclock_blink\": {\n      \"desc\": \"Blink the colon on the gameclock hud element every second.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Blink the colon.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Don't blink the colon.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_gameclock_countdown\": {\n      \"desc\": \"Changes the direction of the game clock (gameclock HUD element)\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Count from 0:00 upwards\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Countdown from match time to zero\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_gameclock_frame\": {\n      \"desc\": \"Opacity of the background of the gameclock HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gameclock_frame_color\": {\n      \"desc\": \"Defines the color of the background of the gameclock HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gameclock_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gameclock_offset\": {\n      \"desc\": \"Allows using gameclock in custom mods that don't support standard KT-like clock synchronization.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Some Capture The Flag or Team Fortress mods can take a use of this.\",\n      \"type\": \"integer\"\n    },\n    \"hud_gameclock_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gameclock_place\": {\n      \"desc\": \"Placement of the gameclock HUD element. HUD elements can be place into various screen areas or other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gameclock_pos_x\": {\n      \"desc\": \"Horizontal relative position of the gameclock HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gameclock_pos_y\": {\n      \"desc\": \"Vertical relative position of the gameclock HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gameclock_scale\": {\n      \"desc\": \"Size of the gameclock HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gameclock_show\": {\n      \"desc\": \"Visibility of the gameclock HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_gameclock_style\": {\n      \"desc\": \"Sets the style of the gameclock hud element.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Gameclock is white.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Gameclock is red.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"hud_gamesummary_format\": {\n      \"default\": \"RYM myr\",\n      \"desc\": \"Sets the content of the gamesummary hud element.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"MVD playback only.  if the letter is in upper-case, it refers to the first team, otherwise the second.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"number of megahealths taken\",\n          \"name\": \"m\"\n        },\n        {\n          \"description\": \"number of red armors taken\",\n          \"name\": \"r\"\n        },\n        {\n          \"description\": \"number of yellow armors taken\",\n          \"name\": \"y\"\n        },\n        {\n          \"description\": \"number of green armors taken\",\n          \"name\": \"g\"\n        },\n        {\n          \"description\": \"number of players with rocket launchers on current team\",\n          \"name\": \"o\"\n        },\n        {\n          \"description\": \"number of players with lightning gun on current team\",\n          \"name\": \"l\"\n        },\n        {\n          \"description\": \"number of players with rocket launcher or lightning gun on current team\",\n          \"name\": \"w\"\n        },\n        {\n          \"description\": \"number of quads taken\",\n          \"name\": \"q\"\n        },\n        {\n          \"description\": \"number of pents taken\",\n          \"name\": \"p\"\n        },\n        {\n          \"description\": \"number of rings taken\",\n          \"name\": \"e\"\n        }\n      ]\n    },\n    \"hud_group1_align_x\": {\n      \"desc\": \"Sets horizontal align of grouping object 1.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group1_align_y\": {\n      \"desc\": \"Sets vertical align of grouping object 1.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group1_frame\": {\n      \"desc\": \"Sets frame visibility and style for grouping object 1.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group1_frame_color\": {\n      \"desc\": \"Defines the color of the background of the group1 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group1_height\": {\n      \"desc\": \"Sets vertical size of grouping object 1.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group1_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group1_name\": {\n      \"desc\": \"Sets name of grouping object 1.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group1_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group1_pic_alpha\": {\n      \"desc\": \"Transparency level of the background image of the group1 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group1_pic_scalemode\": {\n      \"desc\": \"Changes the style how the background picture is aligned and stretched for the group1 HUD element. Values from 0 to 5 are supported. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group1_picture\": {\n      \"desc\": \"Sets background picture of grouping object 1.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group1_place\": {\n      \"desc\": \"Sets relative positioning for grouping object 1.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group1_pos_x\": {\n      \"desc\": \"Sets horizontal position of grouping object 1.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group1_pos_y\": {\n      \"desc\": \"Sets vertical position of grouping object 1.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group1_show\": {\n      \"desc\": \"Switches showing of grouping object 1.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group1_width\": {\n      \"desc\": \"Sets horizontal size of grouping object 1.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group2_align_x\": {\n      \"desc\": \"Sets horizontal align of grouping object 2.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group2_align_y\": {\n      \"desc\": \"Sets vertical align of grouping object 2.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group2_frame\": {\n      \"desc\": \"Sets frame visibility and style for grouping object 2.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group2_frame_color\": {\n      \"desc\": \"Defines the color of the background of the group2 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group2_height\": {\n      \"desc\": \"Sets vertical size of grouping object 2.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group2_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group2_name\": {\n      \"desc\": \"Sets name of grouping object 2.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group2_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group2_pic_alpha\": {\n      \"desc\": \"Transparency level of the background image of the group2 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group2_pic_scalemode\": {\n      \"desc\": \"Changes the style how the background picture is aligned and stretched for the group2 HUD element. Values from 0 to 5 are supported. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group2_picture\": {\n      \"desc\": \"Sets background picture of grouping object 2.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group2_place\": {\n      \"desc\": \"Sets relative positioning for grouping object 2.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group2_pos_x\": {\n      \"desc\": \"Sets horizontal position of grouping object 2.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group2_pos_y\": {\n      \"desc\": \"Sets vertical position of grouping object 2.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group2_show\": {\n      \"desc\": \"Switches showing of grouping object 2.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group2_width\": {\n      \"desc\": \"Sets horizontal size of grouping object 2.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group3_align_x\": {\n      \"desc\": \"Sets horizontal align of grouping object 3.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group3_align_y\": {\n      \"desc\": \"Sets vertical align of grouping object 3.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group3_frame\": {\n      \"desc\": \"Sets frame visibility and style for grouping object 3.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group3_frame_color\": {\n      \"desc\": \"Defines the color of the background of the group3 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group3_height\": {\n      \"desc\": \"Sets vertical size of grouping object 3.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group3_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group3_name\": {\n      \"desc\": \"Sets name of grouping object 3.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group3_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group3_pic_alpha\": {\n      \"desc\": \"Transparency level of the background image of the group3 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group3_pic_scalemode\": {\n      \"desc\": \"Changes the style how the background picture is aligned and stretched for the group3 HUD element. Values from 0 to 5 are supported. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group3_picture\": {\n      \"desc\": \"Sets background picture of grouping object 3.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group3_place\": {\n      \"desc\": \"Sets relative positioning for grouping object 3.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group3_pos_x\": {\n      \"desc\": \"Sets horizontal position of grouping object 3.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group3_pos_y\": {\n      \"desc\": \"Sets vertical position of grouping object 3.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group3_show\": {\n      \"desc\": \"Switches showing of grouping object 3.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group3_width\": {\n      \"desc\": \"Sets horizontal size of grouping object 3.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group4_align_x\": {\n      \"desc\": \"Sets horizontal align of grouping object 4.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group4_align_y\": {\n      \"desc\": \"Sets vertical align of grouping object 4.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group4_frame\": {\n      \"desc\": \"Sets frame visibility and style for grouping object 4.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group4_frame_color\": {\n      \"desc\": \"Defines the color of the background of the group4 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group4_height\": {\n      \"desc\": \"Sets vertical size of grouping object 4.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group4_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group4_name\": {\n      \"desc\": \"Sets name of grouping object 4.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group4_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group4_pic_alpha\": {\n      \"desc\": \"Transparency level of the background image of the group4 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group4_pic_scalemode\": {\n      \"desc\": \"Changes the style how the background picture is aligned and stretched for the group4 HUD element. Values from 0 to 5 are supported. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group4_picture\": {\n      \"desc\": \"Sets background picture of grouping object 4.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group4_place\": {\n      \"desc\": \"Sets relative positioning for grouping object 4.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group4_pos_x\": {\n      \"desc\": \"Sets horizontal position of grouping object 4.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group4_pos_y\": {\n      \"desc\": \"Sets vertical position of grouping object 4.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group4_show\": {\n      \"desc\": \"Switches showing of grouping object 4.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group4_width\": {\n      \"desc\": \"Sets horizontal size of grouping object 4.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group5_align_x\": {\n      \"desc\": \"Sets horizontal align of grouping object 5.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group5_align_y\": {\n      \"desc\": \"Sets vertical align of grouping object 5.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group5_frame\": {\n      \"desc\": \"Sets frame visibility and style for grouping object 5.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group5_frame_color\": {\n      \"desc\": \"Defines the color of the background of the group5 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group5_height\": {\n      \"desc\": \"Sets vertical size of grouping object 5.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group5_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group5_name\": {\n      \"desc\": \"Sets name of grouping object 5.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group5_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group5_pic_alpha\": {\n      \"desc\": \"Transparency level of the background image of the group5 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group5_pic_scalemode\": {\n      \"desc\": \"Changes the style how the background picture is aligned and stretched for the group5 HUD element. Values from 0 to 5 are supported. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group5_picture\": {\n      \"desc\": \"Sets background picture of grouping object 5.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group5_place\": {\n      \"desc\": \"Sets relative positioning for grouping object 5.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group5_pos_x\": {\n      \"desc\": \"Sets horizontal position of grouping object 5.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group5_pos_y\": {\n      \"desc\": \"Sets vertical position of grouping object 5.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group5_show\": {\n      \"desc\": \"Switches showing of grouping object 5.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group5_width\": {\n      \"desc\": \"Sets horizontal size of grouping object 5.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group6_align_x\": {\n      \"desc\": \"Sets horizontal align of grouping object 6.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group6_align_y\": {\n      \"desc\": \"Sets vertical align of grouping object 6.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group6_frame\": {\n      \"desc\": \"Sets frame visibility and style for grouping object 6.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group6_frame_color\": {\n      \"desc\": \"Defines the color of the background of the group6 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group6_height\": {\n      \"desc\": \"Sets vertical size of grouping object 6.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group6_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group6_name\": {\n      \"desc\": \"Sets name of grouping object 6.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group6_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group6_pic_alpha\": {\n      \"desc\": \"Transparency level of the background image of the group6 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group6_pic_scalemode\": {\n      \"desc\": \"Changes the style how the background picture is aligned and stretched for the group6 HUD element. Values from 0 to 5 are supported. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group6_picture\": {\n      \"desc\": \"Sets background picture of grouping object 6.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group6_place\": {\n      \"desc\": \"Sets relative positioning for grouping object 6.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group6_pos_x\": {\n      \"desc\": \"Sets horizontal position of grouping object 6.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group6_pos_y\": {\n      \"desc\": \"Sets vertical position of grouping object 6.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group6_show\": {\n      \"desc\": \"Switches showing of grouping object 6.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group6_width\": {\n      \"desc\": \"Sets horizontal size of grouping object 6.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group7_align_x\": {\n      \"desc\": \"Sets horizontal align of grouping object 7.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group7_align_y\": {\n      \"desc\": \"Sets vertical align of grouping object 7.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group7_frame\": {\n      \"desc\": \"Sets frame visibility and style for grouping object 7.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group7_frame_color\": {\n      \"desc\": \"Defines the color of the background of the group7 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group7_height\": {\n      \"desc\": \"Sets vertical size of grouping object 7.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group7_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group7_name\": {\n      \"desc\": \"Sets name of grouping object 7.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group7_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group7_pic_alpha\": {\n      \"desc\": \"Transparency level of the background image of the group7 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group7_pic_scalemode\": {\n      \"desc\": \"Changes the style how the background picture is aligned and stretched for the group7 HUD element. Values from 0 to 5 are supported. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group7_picture\": {\n      \"desc\": \"Sets background picture of grouping object 7.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group7_place\": {\n      \"desc\": \"Sets relative positioning for grouping object 7.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group7_pos_x\": {\n      \"desc\": \"Sets horizontal position of grouping object 7.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group7_pos_y\": {\n      \"desc\": \"Sets vertical position of grouping object 7.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group7_show\": {\n      \"desc\": \"Switches showing of grouping object 7.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group7_width\": {\n      \"desc\": \"Sets horizontal size of grouping object 7.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group8_align_x\": {\n      \"desc\": \"Sets horizontal align of grouping object 8.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group8_align_y\": {\n      \"desc\": \"Sets vertical align of grouping object 8.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group8_frame\": {\n      \"desc\": \"Sets frame visibility and style for grouping object 8.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group8_frame_color\": {\n      \"desc\": \"Defines the color of the background of the group8 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group8_height\": {\n      \"desc\": \"Sets vertical size of grouping object 8.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group8_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group8_name\": {\n      \"desc\": \"Sets name of grouping object 8.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group8_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group8_pic_alpha\": {\n      \"desc\": \"Transparency level of the background image of the group8 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group8_pic_scalemode\": {\n      \"desc\": \"Changes the style how the background picture is aligned and stretched for the group8 HUD element. Values from 0 to 5 are supported. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group8_picture\": {\n      \"desc\": \"Sets background picture of grouping object 8.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group8_place\": {\n      \"desc\": \"Sets relative positioning for grouping object 8.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group8_pos_x\": {\n      \"desc\": \"Sets horizontal position of grouping object 8.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group8_pos_y\": {\n      \"desc\": \"Sets vertical position of grouping object 8.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group8_show\": {\n      \"desc\": \"Switches showing of grouping object 8.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group8_width\": {\n      \"desc\": \"Sets horizontal size of grouping object 8.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group9_align_x\": {\n      \"desc\": \"Sets horizontal align of grouping object 9.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group9_align_y\": {\n      \"desc\": \"Sets vertical align of grouping object 9.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group9_frame\": {\n      \"desc\": \"Sets frame visibility and style for grouping object 9.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group9_frame_color\": {\n      \"desc\": \"Defines the color of the background of the group9 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group9_height\": {\n      \"desc\": \"Sets vertical size of grouping object 9.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group9_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group9_name\": {\n      \"desc\": \"Sets name of grouping object 9.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group9_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group9_pic_alpha\": {\n      \"desc\": \"Transparency level of the background image of the group9 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group9_pic_scalemode\": {\n      \"desc\": \"Changes the style how the background picture is aligned and stretched for the group9 HUD element. Values from 0 to 5 are supported. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_group9_picture\": {\n      \"desc\": \"Sets background picture of grouping object 9.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group9_place\": {\n      \"desc\": \"Sets relative positioning for grouping object 9.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_group9_pos_x\": {\n      \"desc\": \"Sets horizontal position of grouping object 9.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group9_pos_y\": {\n      \"desc\": \"Sets vertical position of grouping object 9.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group9_show\": {\n      \"desc\": \"Switches showing of grouping object 9.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_group9_width\": {\n      \"desc\": \"Sets horizontal size of grouping object 9.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun2_align_x\": {\n      \"desc\": \"Sets horizontal align of shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun2_align_y\": {\n      \"desc\": \"Sets vertical align of shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun2_frame\": {\n      \"desc\": \"Sets frame visibility and style for shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun2_frame_color\": {\n      \"desc\": \"Defines the color of the background of the gun2 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun2_frame_hide\": {\n      \"desc\": \"Hide the frame unless you have the weapon.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun2_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun2_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun2_place\": {\n      \"desc\": \"Sets relative positioning for shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun2_pos_x\": {\n      \"desc\": \"Sets horizontal position of shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun2_pos_y\": {\n      \"desc\": \"Sets vertical position of shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun2_scale\": {\n      \"desc\": \"Sets size of shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun2_show\": {\n      \"desc\": \"Switches showing of shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun2_style\": {\n      \"desc\": \"Switches the graphical style of the shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Picture\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Text: gold inactive, white active\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Numbers: gold inactive, white active\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Text: white inactive, gold active\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Numbers: white inactive, gold active\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_sg active, gold inactive\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Text: white active, value of tp_name_sg inactive\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_sg active, white inactive\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Text: gold active, value of tp_name_sg inactive\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"hud_gun3_align_x\": {\n      \"desc\": \"Sets horizontal align of super shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun3_align_y\": {\n      \"desc\": \"Sets vertical align of super shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun3_frame\": {\n      \"desc\": \"Sets frame visibility and style for super shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun3_frame_color\": {\n      \"desc\": \"Defines the color of the background of the gun3 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun3_frame_hide\": {\n      \"desc\": \"Hide the frame unless you have the weapon.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun3_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun3_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun3_place\": {\n      \"desc\": \"Sets relative positioning for super shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun3_pos_x\": {\n      \"desc\": \"Sets horizontal position of super shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun3_pos_y\": {\n      \"desc\": \"Sets vertical position of super shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun3_scale\": {\n      \"desc\": \"Sets size of super shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun3_show\": {\n      \"desc\": \"Switches showing of super shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun3_style\": {\n      \"desc\": \"Switches the graphical style of the super shotgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Picture\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Text: gold inactive, white active\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Numbers: gold inactive, white active\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Text: white inactive, gold active\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Numbers: white inactive, gold active\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_ssg active, gold inactive\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Text: white active, value of tp_name_ssg inactive\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_ssg active, white inactive\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Text: gold active, value of tp_name_ssg inactive\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"hud_gun4_align_x\": {\n      \"desc\": \"Sets horizontal align of nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun4_align_y\": {\n      \"desc\": \"Sets vertical align of nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun4_frame\": {\n      \"desc\": \"Sets frame visibility and style for nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun4_frame_color\": {\n      \"desc\": \"Defines the color of the background of the gun4 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun4_frame_hide\": {\n      \"desc\": \"Hide the frame unless you have the weapon.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun4_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun4_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun4_place\": {\n      \"desc\": \"Sets relative positioning for nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun4_pos_x\": {\n      \"desc\": \"Sets horizontal position of nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun4_pos_y\": {\n      \"desc\": \"Sets vertical position of nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun4_scale\": {\n      \"desc\": \"Sets size of nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun4_show\": {\n      \"desc\": \"Switches showing of nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun4_style\": {\n      \"desc\": \"Switches the graphical style of the nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Picture\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Text: gold inactive, white active\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Numbers: gold inactive, white active\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Text: white inactive, gold active\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Numbers: white inactive, gold active\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_ng active, gold inactive\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Text: white active, value of tp_name_ng inactive\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_ng active, white inactive\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Text: gold active, value of tp_name_ng inactive\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"hud_gun5_align_x\": {\n      \"desc\": \"Sets horizontal align of super nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun5_align_y\": {\n      \"desc\": \"Sets vertical align of super nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun5_frame\": {\n      \"desc\": \"Sets frame visibility and style for super nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun5_frame_color\": {\n      \"desc\": \"Defines the color of the background of the gun5 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun5_frame_hide\": {\n      \"desc\": \"Hide the frame unless you have the weapon.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun5_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun5_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun5_place\": {\n      \"desc\": \"Sets relative positioning for super nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun5_pos_x\": {\n      \"desc\": \"Sets horizontal position of super nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun5_pos_y\": {\n      \"desc\": \"Sets vertical position of super nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun5_scale\": {\n      \"desc\": \"Sets size of super nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun5_show\": {\n      \"desc\": \"Switches showing of super nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun5_style\": {\n      \"desc\": \"Switches the graphical style of the super nailgun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Picture\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Text: gold inactive, white active\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Numbers: gold inactive, white active\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Text: white inactive, gold active\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Numbers: white inactive, gold active\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_sng active, gold inactive\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Text: white active, value of tp_name_sng inactive\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_sng active, white inactive\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Text: gold active, value of tp_name_sng inactive\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"hud_gun6_align_x\": {\n      \"desc\": \"Sets horizontal align of grenade launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun6_align_y\": {\n      \"desc\": \"Sets vertical align of grenade launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun6_frame\": {\n      \"desc\": \"Sets frame visibility and style for grenade launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun6_frame_color\": {\n      \"desc\": \"Defines the color of the background of the gun6 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun6_frame_hide\": {\n      \"desc\": \"Hide the frame unless you have the weapon.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun6_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun6_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun6_place\": {\n      \"desc\": \"Sets relative positioning for grenade launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun6_pos_x\": {\n      \"desc\": \"Sets horizontal position of grenade launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun6_pos_y\": {\n      \"desc\": \"Sets vertical position of grenade launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun6_scale\": {\n      \"desc\": \"Sets size of grenade launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun6_show\": {\n      \"desc\": \"Switches showing of grenade launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun6_style\": {\n      \"desc\": \"Switches the graphical style of the grenade launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Picture\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Text: gold inactive, white active\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Numbers: gold inactive, white active\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Text: white inactive, gold active\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Numbers: white inactive, gold active\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_gl active, gold inactive\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Text: white active, value of tp_name_gl inactive\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_gl active, white inactive\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Text: gold active, value of tp_name_gl inactive\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"hud_gun7_align_x\": {\n      \"desc\": \"Sets horizontal align of rocket launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun7_align_y\": {\n      \"desc\": \"Sets vertical align of rocket launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun7_frame\": {\n      \"desc\": \"Sets frame visibility and style for rocket launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun7_frame_color\": {\n      \"desc\": \"Defines the color of the background of the gun7 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun7_frame_hide\": {\n      \"desc\": \"Hide the frame unless you have the weapon.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun7_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun7_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun7_place\": {\n      \"desc\": \"Sets relative positioning for rocket launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun7_pos_x\": {\n      \"desc\": \"Sets horizontal position of rocket launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun7_pos_y\": {\n      \"desc\": \"Sets vertical position of rocket launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun7_scale\": {\n      \"desc\": \"Sets size of rocket launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun7_show\": {\n      \"desc\": \"Switches showing of rocket launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun7_style\": {\n      \"desc\": \"Switches the graphical style of the rocket launcher icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Picture\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Text: gold inactive, white active\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Numbers: gold inactive, white active\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Text: white inactive, gold active\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Numbers: white inactive, gold active\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_rl active, gold inactive\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Text: white active, value of tp_name_rl inactive\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_rl active, white inactive\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Text: gold active, value of tp_name_rl inactive\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"hud_gun8_align_x\": {\n      \"desc\": \"Sets horizontal align of lightning gun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun8_align_y\": {\n      \"desc\": \"Sets vertical align of lightning gun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun8_frame\": {\n      \"desc\": \"Sets frame visibility and style for lightning gun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun8_frame_color\": {\n      \"desc\": \"Defines the color of the background of the gun8 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun8_frame_hide\": {\n      \"desc\": \"Hide the frame unless you have the weapon.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun8_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun8_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun8_place\": {\n      \"desc\": \"Sets relative positioning for lightning gun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun8_pos_x\": {\n      \"desc\": \"Sets horizontal position of lightning gun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun8_pos_y\": {\n      \"desc\": \"Sets vertical position of lightning gun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun8_scale\": {\n      \"desc\": \"Sets size of lightning gun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun8_show\": {\n      \"desc\": \"Switches showing of lightning gun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun8_style\": {\n      \"desc\": \"Switches the graphical style of the lightning gun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Picture\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Text: gold inactive, white active\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Numbers: gold inactive, white active\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Text: white inactive, gold active\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Numbers: white inactive, gold active\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_lg active, gold inactive\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Text: white active, value of tp_name_lg inactive\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_lg active, white inactive\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Text: gold active, value of tp_name_lg inactive\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"hud_gun8_wide\": {\n      \"desc\": \"Switches wide and short of version of lightning gun icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun_align_x\": {\n      \"desc\": \"Sets horizontal align of current weapon icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun_align_y\": {\n      \"desc\": \"Sets vertical align of current weapon icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun_frame\": {\n      \"desc\": \"Sets frame visibility and style for current weapon icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun_frame_color\": {\n      \"desc\": \"Defines the color of the background of the gun HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_gun_place\": {\n      \"desc\": \"Sets relative positioning for current weapon icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_gun_pos_x\": {\n      \"desc\": \"Sets horizontal position of current weapon icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun_pos_y\": {\n      \"desc\": \"Sets vertical position of current weapon icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun_scale\": {\n      \"desc\": \"Sets size of current weapon icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun_show\": {\n      \"desc\": \"Switches showing of current weapon icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_gun_style\": {\n      \"desc\": \"Switches graphical style of the current weapon's icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Picture\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Text, white\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Number, white\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Text, gold\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Number, gold\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Text: value of tp_name_X, where X is the name of the current gun held.\",\n          \"name\": \"5\"\n        }\n      ]\n    },\n    \"hud_gun_wide\": {\n      \"desc\": \"Switches between wide and short version of current weapon icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_health_align\": {\n      \"desc\": \"Sets align of health level.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_health_align_x\": {\n      \"desc\": \"Sets horizontal align of health level.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_health_align_y\": {\n      \"desc\": \"Sets vertical align of health level.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_health_digits\": {\n      \"desc\": \"Sets number of digits for health level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_health_frame\": {\n      \"desc\": \"Sets frame visibility and style for health level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_health_frame_color\": {\n      \"desc\": \"Defines the color of the background of the health HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_health_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_health_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_health_place\": {\n      \"desc\": \"Sets relative positioning for health level.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_health_pos_x\": {\n      \"desc\": \"Sets horizontal position of health level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_health_pos_y\": {\n      \"desc\": \"Sets vertical position of health level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_health_scale\": {\n      \"desc\": \"Sets size of health level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_health_show\": {\n      \"desc\": \"Switches showing of health level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_health_style\": {\n      \"desc\": \"Switches graphical style of health level.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_healthdamage_align\": {\n      \"desc\": \"Sets healthdamage HUD element alignment.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"horizontal: left, center, right, before, after\\nvertical: top, center, bottom, console, before, after\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_healthdamage_align_x\": {\n      \"desc\": \"Sets healthdamage HUD element horizontal alignment.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"left, center, right, before, after\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_healthdamage_align_y\": {\n      \"desc\": \"Sets healthdamage HUD element vertical alignment.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"top, center, bottom, console, before, after\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_healthdamage_digits\": {\n      \"desc\": \"Sets highest possible number of digits displayed in HUD element healthdamage.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_healthdamage_duration\": {\n      \"desc\": \"Sets how long healthdamage should be visible after last damage to health has been done.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Float number\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_healthdamage_frame\": {\n      \"desc\": \"Sets frame visibility and style for this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Adjust background opacity\",\n          \"name\": \"0.x to 1\"\n        },\n        {\n          \"description\": \"Bevelled frame.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"hud_healthdamage_frame_color\": {\n      \"desc\": \"Defines the color of the background of the healthdamage HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_healthdamage_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_healthdamage_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_healthdamage_place\": {\n      \"desc\": \"Sets placement for this HUD element. You can align some elements relative to other elements.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"First you have to decide, if the element that you are locating now (element B) is to be positioned inside another element (element A) or outside it. See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"to place this element inside element B set this variable to \\\"@B\\\", to place it outside B element set this variable to \\\"B\\\"\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_healthdamage_pos_x\": {\n      \"desc\": \"Sets horizontal position of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_healthdamage_pos_y\": {\n      \"desc\": \"Sets vertical position of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_healthdamage_scale\": {\n      \"desc\": \"Sets overall size of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Use positive floating point numbers.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_healthdamage_show\": {\n      \"desc\": \"Toggles visibility of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw this element\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw this element\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_healthdamage_style\": {\n      \"desc\": \"Toggles between different numbers styles.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"large font, texture\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"small font, gfxwad\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_iammo1_align_x\": {\n      \"desc\": \"Sets horizontal align of shells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo1_align_y\": {\n      \"desc\": \"Sets vertical align of shells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo1_frame\": {\n      \"desc\": \"Sets frame visibility and style for shells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo1_frame_color\": {\n      \"desc\": \"Defines the color of the background of the iammo1 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo1_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo1_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_iammo1_place\": {\n      \"desc\": \"Sets relative positioning for shells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo1_pos_x\": {\n      \"desc\": \"Sets horizontal position of shells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo1_pos_y\": {\n      \"desc\": \"Sets vertical position of shells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo1_scale\": {\n      \"desc\": \"Sets size of shells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo1_show\": {\n      \"desc\": \"Switches showing of shells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo1_style\": {\n      \"desc\": \"Switches graphical style of shells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo2_align_x\": {\n      \"desc\": \"Sets horizontal align of nails icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo2_align_y\": {\n      \"desc\": \"Sets vertical align of nails icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo2_frame\": {\n      \"desc\": \"Sets frame visibility and style for nails icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo2_frame_color\": {\n      \"desc\": \"Defines the color of the background of the iammo2 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo2_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo2_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_iammo2_place\": {\n      \"desc\": \"Sets relative positioning for nails icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo2_pos_x\": {\n      \"desc\": \"Sets horizontal position of nails icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo2_pos_y\": {\n      \"desc\": \"Sets vertical position of nails icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo2_scale\": {\n      \"desc\": \"Sets size of nails icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo2_show\": {\n      \"desc\": \"Switches showing of nails icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo2_style\": {\n      \"desc\": \"Switches graphical style of nails icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo3_align_x\": {\n      \"desc\": \"Sets horizontal align of rockets icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo3_align_y\": {\n      \"desc\": \"Sets vertical align of rockets icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo3_frame\": {\n      \"desc\": \"Sets frame visibility and style for rockets icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo3_frame_color\": {\n      \"desc\": \"Defines the color of the background of the iammo3 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo3_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo3_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_iammo3_place\": {\n      \"desc\": \"Sets relative positioning for rockets icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo3_pos_x\": {\n      \"desc\": \"Sets horizontal position of rockets icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo3_pos_y\": {\n      \"desc\": \"Sets vertical position of rockets icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo3_scale\": {\n      \"desc\": \"Sets size of rockets icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo3_show\": {\n      \"desc\": \"Switches showing of rockets icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo3_style\": {\n      \"desc\": \"Switches graphical style of rockets icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo4_align_x\": {\n      \"desc\": \"Sets horizontal align of cells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo4_align_y\": {\n      \"desc\": \"Sets vertical align of cells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo4_frame\": {\n      \"desc\": \"Sets frame visibility and style for cells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo4_frame_color\": {\n      \"desc\": \"Defines the color of the background of the iammo4 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo4_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo4_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_iammo4_place\": {\n      \"desc\": \"Sets relative positioning for cells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo4_pos_x\": {\n      \"desc\": \"Sets horizontal position of cells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo4_pos_y\": {\n      \"desc\": \"Sets vertical position of cells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo4_scale\": {\n      \"desc\": \"Sets size of cells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo4_show\": {\n      \"desc\": \"Switches showing of cells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo4_style\": {\n      \"desc\": \"Switches graphical style of cells icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo_align_x\": {\n      \"desc\": \"Sets horizontal align of current ammo icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo_align_y\": {\n      \"desc\": \"Sets vertical align of current ammo icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo_frame\": {\n      \"desc\": \"Sets frame visibility and style for current ammo icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo_frame_color\": {\n      \"desc\": \"Defines the color of the background of the iammo HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_iammo_place\": {\n      \"desc\": \"Sets relative positioning for current ammo icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iammo_pos_x\": {\n      \"desc\": \"Sets horizontal position of current ammo icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo_pos_y\": {\n      \"desc\": \"Sets vertical position of current ammo icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo_scale\": {\n      \"desc\": \"Sets size of current ammo icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo_show\": {\n      \"desc\": \"Switches showing of current ammo icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iammo_style\": {\n      \"desc\": \"Switches graphical style of current ammo icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iarmor_align_x\": {\n      \"desc\": \"Sets horizontal align of armor icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iarmor_align_y\": {\n      \"desc\": \"Sets vertical align of armor icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iarmor_frame\": {\n      \"desc\": \"Sets frame visibility and style for armor icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iarmor_frame_color\": {\n      \"desc\": \"Defines the color of the background of the iarmor HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iarmor_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iarmor_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_iarmor_place\": {\n      \"desc\": \"Sets relative positioning for armor icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_iarmor_pos_x\": {\n      \"desc\": \"Sets horizontal position of armor icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iarmor_pos_y\": {\n      \"desc\": \"Sets vertical position of armor icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iarmor_scale\": {\n      \"desc\": \"Sets size of armor icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iarmor_show\": {\n      \"desc\": \"Switches showing of armor icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_iarmor_style\": {\n      \"desc\": \"Switches graphical style of armor icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_itemsclock_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_itemsclock_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_itemsclock_filter\": {\n      \"desc\": \"Space-separated list of items to NOT be shown.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Options are: gl, rl, lg, quad, pent, ring, suit, mh, ga, ya, ra.\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"hide grenade launcher, green armor & suit from items list\",\n          \"name\": \"gl ga suit\"\n        }\n      ]\n    },\n    \"hud_itemsclock_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_itemsclock_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_itemsclock_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_itemsclock_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_itemsclock_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_itemsclock_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_itemsclock_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_itemsclock_scale\": {\n      \"desc\": \"Sets size of items clock items relative to standard font size.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_itemsclock_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_itemsclock_style\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_itemsclock_timelimit\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_key1_align_x\": {\n      \"desc\": \"Sets horizontal align of silver key.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_key1_align_y\": {\n      \"desc\": \"Sets vertical align of silver key.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_key1_frame\": {\n      \"desc\": \"Sets frame visibility and style for silver key.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key1_frame_color\": {\n      \"desc\": \"Defines the color of the background of the key1 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_key1_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key1_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_key1_place\": {\n      \"desc\": \"Sets relative positioning for silver key.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_key1_pos_x\": {\n      \"desc\": \"Sets horizontal position of silver key.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key1_pos_y\": {\n      \"desc\": \"Sets vertical position of silver key.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key1_scale\": {\n      \"desc\": \"Sets size of silver key.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key1_show\": {\n      \"desc\": \"Switches showing of silver key.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key1_style\": {\n      \"desc\": \"Switches graphical style of silver key.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key2_align_x\": {\n      \"desc\": \"Sets horizontal align of gold key.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_key2_align_y\": {\n      \"desc\": \"Sets vertical align of gold key.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_key2_frame\": {\n      \"desc\": \"Sets frame visibility and style for gold key.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key2_frame_color\": {\n      \"desc\": \"Defines the color of the background of the key2 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_key2_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key2_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_key2_place\": {\n      \"desc\": \"Sets relative positioning for gold key.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_key2_pos_x\": {\n      \"desc\": \"Sets horizontal position of gold key.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key2_pos_y\": {\n      \"desc\": \"Sets vertical position of gold key.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key2_scale\": {\n      \"desc\": \"Sets size of gold key.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key2_show\": {\n      \"desc\": \"Switches showing of gold key.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_key2_style\": {\n      \"desc\": \"Switches graphical style of gold key.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_keys_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_keys_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_keys_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_keys_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_keys_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_keys_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_keys_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_keys_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_keys_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_keys_scale\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_keys_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_mouserate_align_x\": {\n      \"desc\": \"Vertical alignment of the mouserate HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"28\",\n      \"type\": \"string\"\n    },\n    \"hud_mouserate_align_y\": {\n      \"desc\": \"Vertical alignment of the mouserate HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"28\",\n      \"type\": \"string\"\n    },\n    \"hud_mouserate_frame\": {\n      \"desc\": \"Opacity of the background of the mouserate HUD element.\",\n      \"group-id\": \"28\",\n      \"type\": \"float\"\n    },\n    \"hud_mouserate_frame_color\": {\n      \"desc\": \"Defines the color of the background of the mouserate HUD element. See HUD manual for more info.\",\n      \"group-id\": \"28\",\n      \"type\": \"string\"\n    },\n    \"hud_mouserate_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"28\",\n      \"type\": \"integer\"\n    },\n    \"hud_mouserate_place\": {\n      \"desc\": \"Placement of the mouserate HUD element. HUD elements can be place into various screen areas or other elements. See HUD manual for more info.\",\n      \"group-id\": \"28\",\n      \"type\": \"string\"\n    },\n    \"hud_mouserate_pos_x\": {\n      \"desc\": \"Horizontal relative position of the mouserate HUD element.\",\n      \"group-id\": \"28\",\n      \"type\": \"integer\"\n    },\n    \"hud_mouserate_pos_y\": {\n      \"desc\": \"Vertical relative position of the mouserate HUD element.\",\n      \"group-id\": \"28\",\n      \"type\": \"integer\"\n    },\n    \"hud_mouserate_show\": {\n      \"desc\": \"Visibility of the mouserate HUD element.\",\n      \"group-id\": \"28\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_name_remove_prefixes\": {\n      \"default\": \"\",\n      \"desc\": \"Space-separated list of prefixes to remove from player names before displaying in the tracker & teaminfo.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"Useful if one team has clan prefixes that stop the names appearing fully.\",\n      \"type\": \"string\"\n    },\n    \"hud_net_align_x\": {\n      \"desc\": \"Sets horizontal align of net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_net_align_y\": {\n      \"desc\": \"Sets vertical align of net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_net_frame\": {\n      \"desc\": \"Sets frame visibility and style for net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_net_frame_color\": {\n      \"desc\": \"Defines the color of the background of the net HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_net_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_net_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_net_period\": {\n      \"desc\": \"Sets period for updating net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_net_place\": {\n      \"desc\": \"Sets relative positioning for net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_net_pos_x\": {\n      \"desc\": \"Sets horizontal position of net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_net_pos_y\": {\n      \"desc\": \"Sets vertical position of net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_net_show\": {\n      \"desc\": \"Switches showing of net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_align_x\": {\n      \"desc\": \"Sets horizontal align of everything about net.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_netgraph_align_y\": {\n      \"desc\": \"Sets vertical align of everything about net.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_netgraph_alpha\": {\n      \"desc\": \"Sets transparency level of everything about net.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_frame\": {\n      \"desc\": \"Sets frame visibility and style for everything about net.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_frame_color\": {\n      \"desc\": \"Defines the color of the background of the netgraph HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_netgraph_height\": {\n      \"desc\": \"Sets vertical size of everything about net.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_inframes\": {\n      \"desc\": \"setting this to \\\"1\\\" lets you measure your latency in an alternate way every level of netgraph will mean one frame of delay, between sending it to server and getting answer. On local/lan server you'll always get one frame of delay, even with low FPS.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_lostscale\": {\n      \"desc\": \"Lets you cut down those red, yellow, blue and gray bars, which are always full-height.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range 0 to 1.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_netgraph_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_netgraph_place\": {\n      \"desc\": \"Sets relative positioning for everything about net.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_netgraph_ploss\": {\n      \"desc\": \"print packet loss or not everything about net.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_pos_x\": {\n      \"desc\": \"Sets horizontal position of everything about net.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_pos_y\": {\n      \"desc\": \"Sets vertical position of everything about net.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_scale\": {\n      \"desc\": \"Sets size of everything about net.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_show\": {\n      \"desc\": \"Switches showing of everything about net.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_swap_x\": {\n      \"desc\": \"reverse horizontally, like for placing at left edge of the screen.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_swap_y\": {\n      \"desc\": \"reverse vertically, like for top edge.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netgraph_width\": {\n      \"desc\": \"Sets horizontal size of everything about net.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netproblem_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_netproblem_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_netproblem_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_netproblem_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_netproblem_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_netproblem_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_netproblem_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_netproblem_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_netproblem_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_netproblem_scale\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_netproblem_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_notify_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_notify_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_notify_cols\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_notify_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_notify_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_notify_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_notify_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_notify_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_notify_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_notify_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_notify_rows\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_notify_scale\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_notify_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_notify_time\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_ownfrags_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ownfrags_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ownfrags_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ownfrags_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_ownfrags_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ownfrags_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_ownfrags_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ownfrags_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_ownfrags_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_ownfrags_scale\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ownfrags_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_ownfrags_timeout\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_pent_align_x\": {\n      \"desc\": \"Sets horizontal align of pentagram icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_pent_align_y\": {\n      \"desc\": \"Sets vertical align of pentagram icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_pent_frame\": {\n      \"desc\": \"Sets frame visibility and style for pentagram icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_pent_frame_color\": {\n      \"desc\": \"Defines the color of the background of the pent HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_pent_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_pent_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_pent_place\": {\n      \"desc\": \"Sets relative positioning for pentagram icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_pent_pos_x\": {\n      \"desc\": \"Sets horizontal position of pentagram icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_pent_pos_y\": {\n      \"desc\": \"Sets vertical position of pentagram icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_pent_scale\": {\n      \"desc\": \"Sets size of pentagram icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_pent_show\": {\n      \"desc\": \"Switches showing of pentagram icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_pent_style\": {\n      \"desc\": \"Switches graphical style of pentagram icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ping_align_x\": {\n      \"desc\": \"Sets horizontal align of small net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ping_align_y\": {\n      \"desc\": \"Sets vertical align of small net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ping_blink\": {\n      \"desc\": \"Enable yellow blinking dot, which shows when your ping is recalculated.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ping_frame\": {\n      \"desc\": \"Sets frame visibility and style for small net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ping_frame_color\": {\n      \"desc\": \"Defines the color of the background of the ping HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ping_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ping_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_ping_period\": {\n      \"desc\": \"Period of time between updates (minimum value is your frame time)\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ping_place\": {\n      \"desc\": \"Sets relative positioning for small net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ping_pos_x\": {\n      \"desc\": \"Sets horizontal position of small net statistics;\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ping_pos_y\": {\n      \"desc\": \"Sets vertical position of small net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ping_show\": {\n      \"desc\": \"Switches showing of small net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ping_show_dev\": {\n      \"desc\": \"Switches showing of small net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ping_show_max\": {\n      \"desc\": \"Switches showing of small net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ping_show_min\": {\n      \"desc\": \"Switches showing of small net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ping_show_pl\": {\n      \"desc\": \"Switches showing of small net statistics.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ping_style\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_planmode\": {\n      \"desc\": \"Toggles special hud-editing mode where all hud elements are being displayed.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Plan-mode off.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Plan-mode on.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_quad_align_x\": {\n      \"desc\": \"Sets horizontal align of quad icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_quad_align_y\": {\n      \"desc\": \"Sets vertical align of quad icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_quad_frame\": {\n      \"desc\": \"Sets frame visibility and style for quad icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_quad_frame_color\": {\n      \"desc\": \"Defines the color of the background of the quad HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_quad_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_quad_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_quad_place\": {\n      \"desc\": \"Sets relative positioning for quad icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_quad_pos_x\": {\n      \"desc\": \"Sets horizontal position of quad icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_quad_pos_y\": {\n      \"desc\": \"Sets vertical position of quad icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_quad_scale\": {\n      \"desc\": \"Sets size of quad icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_quad_show\": {\n      \"desc\": \"Switches showing of quad icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_quad_style\": {\n      \"desc\": \"Switches graphical style of quad icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_radar_align_x\": {\n      \"desc\": \"Vertical alignment of the radar HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_radar_align_y\": {\n      \"desc\": \"Vertical alignment of the radar HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_radar_autosize\": {\n      \"desc\": \"Automatically size the Radar hud item to show the radar picture at the resolution it was created for.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"The width and height values decides how big the radar picture is.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"The hud item is sized after the size of the radar picture.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_radar_fade_players\": {\n      \"desc\": \"Fade players (make them more transparent) as they lose health/armor.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Players stay a solid color no matter what health/armor.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Players color fades as they get weaker.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_radar_frame\": {\n      \"desc\": \"Opacity of the background of the radar HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_radar_frame_color\": {\n      \"desc\": \"Defines the color of the background of the radar HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_radar_height\": {\n      \"desc\": \"Sets the height of the radar hud item.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Note that if hud_radar_autosize is set, this value will be ignored.\",\n      \"type\": \"integer\"\n    },\n    \"hud_radar_highlight\": {\n      \"desc\": \"Show a highlight around the currently tracked player on the radar or not.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Change the color of the higlight using the hud_radar_highlight_color variable.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Don't highlight the tracked player.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Only highlight the name of the player (show_names needs to be on)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Draw a variably sized circle outline around the tracked player.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Draw a fixed sized circle outline around the tracked player.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Draw a variably sized circle around the tracked player.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Draw a variably sized circle around the tracked player.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Draw a line from the bottom center of the radar to the tracked player.\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Draw a line from the center of the radar to the tracked player.\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Draw a line from the top center of the radar to the tracked player.\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Draw a line from each corner of the radar to the tracked player.\",\n          \"name\": \"9\"\n        }\n      ]\n    },\n    \"hud_radar_highlight_color\": {\n      \"desc\": \"Sets the RGBA color of the highlight of the tracked player for the radar HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Example: \\\"255 255 255 150\\\"\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_radar_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_radar_itemfilter\": {\n      \"desc\": \"Decides what items should be shown on the radar. Such as ammo, health packs, backpacks and powerups.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Any character/whitespace can be used as a delimiter. Make sure you enter the value between quotes if you use whitespaces.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Valid values: backpack, health, armor, rockets, nails, cells, shells, quad, pent, ring, suit, mega\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_radar_onlytp\": {\n      \"desc\": \"Decides whetever the radar hud item should be shown only when in teamplay mode, or demo playback.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"The radar will NOT be visible when playing as a normal player no matter what you set this to, this only applies to spectators/during demo playback.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Always show.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Only show when teamplay is on.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Only show when in demo playback.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Only show when teamplay is on and in demo playback.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"hud_radar_opacity\": {\n      \"desc\": \"The opacity of the radar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"0 is fully transparent and 1 is opague.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_radar_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_radar_otherfilter\": {\n      \"desc\": \"Decides what \\\"other\\\" things, such as projectiles (rockets, nails, shaft beam), gibs, and explosions, that should be shown on the radar.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Any character/whitespace can be used as a delimiter. Make sure you enter the value between quotes if you use whitespaces.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Valid values: projectiles, nails, rockets, shaft, gibs, explosions\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_radar_place\": {\n      \"desc\": \"Placement of the radar HUD element. HUD elements can be place into various screen areas or other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_radar_player_size\": {\n      \"desc\": \"The radius in of the players on the radar.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"If show_height is turned on, then this ofcourse depends on what height the player is located at.\",\n      \"type\": \"float\"\n    },\n    \"hud_radar_pos_x\": {\n      \"desc\": \"Horizontal relative position of the radar HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_radar_pos_y\": {\n      \"desc\": \"Vertical relative position of the radar HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_radar_show\": {\n      \"desc\": \"Visibility of the radar HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_radar_show_height\": {\n      \"desc\": \"Should the players become bigger as they move to higher points on the map or not?\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Players always stay the same size on the radar.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Players change size depending on their altitude on the level.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_radar_show_hold\": {\n      \"desc\": \"This will show the name of all the important items on the map (RL, LG, GL, SNG, Quad, Pent, Ring, Suit, Mega, Armors). A circle is drawn around the items, the team who has the highest weight inside this area is considered to be holding that particular item. See the teamholdinfo hud item.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"It is not recommended to have this visible at all times. Instead it's meant for a quick glance to know which item is named what when using the Teamholdbarinfo hud item. For instance if the map has two YA's, one is named YA and the other YA2. Use this feature to see which is which.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't show the items and circles on the radar.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Show the item names and circle on the radar.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_radar_show_names\": {\n      \"desc\": \"Show the names of the players on the radar.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_radar_show_powerups\": {\n      \"desc\": \"Highlight players with powerups on the radar hud item with a colored circle around them, depending on what type of powerup.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't highlight players with powerups.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Highlight players with powerups.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_radar_show_stats\": {\n      \"desc\": \"Decides what type of stats should be shown on the radar.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"The team stats are calculated depending on how strong a player is. A strong player with a good weapon raises the weight for a certain area more than a weak one. The team with the highest weight for a certain area is considered to hold that area.\",\n      \"type\": \"enum\"\n    },\n    \"hud_radar_weaponfilter\": {\n      \"desc\": \"Decides which weapons that should be shown on the radar.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Any character/whitespace can be used as a delimiter. Make sure you enter the value between quotes if you use whitespaces.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Valid values: ssg, ng, sng, rl, gl, lg\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_radar_width\": {\n      \"desc\": \"The width of the radar hud item.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Note that if hud_radar_autosize is set, this value will be ignored.\",\n      \"type\": \"integer\"\n    },\n    \"hud_ring_align_x\": {\n      \"desc\": \"Sets horizontal align of ring icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ring_align_y\": {\n      \"desc\": \"Sets vertical align of ring icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ring_frame\": {\n      \"desc\": \"Sets frame visibility and style for ring icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ring_frame_color\": {\n      \"desc\": \"Defines the color of the background of the ring HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ring_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ring_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_ring_place\": {\n      \"desc\": \"Sets relative positioning for ring icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_ring_pos_x\": {\n      \"desc\": \"Sets horizontal position of ring icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ring_pos_y\": {\n      \"desc\": \"Sets vertical position of ring icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ring_scale\": {\n      \"desc\": \"Sets size of ring icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ring_show\": {\n      \"desc\": \"Switches showing of ring icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_ring_style\": {\n      \"desc\": \"Switches graphical style of ring icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_score_bar_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_bar_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_bar_fixed_order\": {\n      \"desc\": \"If set, order of player names is fixed, irrespective of who is currently being tracked.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Current player is always %t\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Fixed order of player names\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"hud_score_bar_format_big\": {\n      \"desc\": \"Format string for score_bar HUD element.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"%d - score difference between you and enemy.\\n%D - score difference between you and enemy, red chars.\\n%t - team score.\\n%T - your (team) score, red chars.\\n%e - enemy (team) score.\\n%E - enemy (team) score, red chars.\\n%d - score (team) difference.\\n%D - score (team) difference, red chars.\\n%p - your (team) position on scoreboard.\\n%z - score difference between you and enemy, unsigned. Red chars used for negative difference.\\n%z - score difference between you and enemy, unsigned. Red chars used for positive difference.\",\n      \"type\": \"string\"\n    },\n    \"hud_score_bar_format_reversed_big\": {\n      \"desc\": \"Format string for score_bar HUD element, used when hud_score_bar_fixed_order has taken effect.\\nIf not set, hud_score_bar_format_big will be used instead.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"See hud_score_bar_format_big for format codes.\",\n      \"type\": \"string\"\n    },\n    \"hud_score_bar_format_reversed_small\": {\n      \"desc\": \"Format string for score_bar HUD element, used when hud_score_bar_fixed_order has taken effect.\\nIf not set, hud_score_bar_format_reversed_small will be used instead.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"See hud_score_bar_format_reversed_small for format codes.\",\n      \"type\": \"string\"\n    },\n    \"hud_score_bar_format_small\": {\n      \"desc\": \"Format string for score_bar HUD element.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"%d - score difference between you and enemy.\\n%D - score difference between you and enemy, always signed.\\n%t - your (team) score.\\n%T - your (team) name.\\n%e - enemy (team) score.\\n%E - enemy (team) name.\\n%d - score difference.\\n%D - score difference, always signed.\\n%p - Your (team) position on scoreboard.\\n\\nAlso, you could use color codes.\",\n      \"type\": \"string\"\n    },\n    \"hud_score_bar_frag_length\": {\n      \"desc\": \"Minimum number of characters to use when displaying frags.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_score_bar_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_score_bar_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_bar_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_score_bar_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_bar_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_bar_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_bar_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_bar_scale\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_bar_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_bar_style\": {\n      \"desc\": \"Style of score_bar HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Use small characterss (conchars)\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Use big characters (num* and anum* images)\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"hud_score_difference_align\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_difference_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_difference_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_difference_colorize\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_difference_digits\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_difference_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_score_difference_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_difference_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_score_difference_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_difference_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_difference_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_difference_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_difference_scale\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_difference_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_difference_style\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_enemy_align\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_enemy_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_enemy_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_enemy_colorize\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_enemy_digits\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_enemy_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_score_enemy_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_enemy_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_score_enemy_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_enemy_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_enemy_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_enemy_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_enemy_scale\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_enemy_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_enemy_style\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_position_align\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_position_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_position_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_position_colorize\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_position_digits\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_position_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_score_position_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_position_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_score_position_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_position_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_position_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_position_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_position_scale\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_position_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_position_style\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_team_align\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_team_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_team_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_team_colorize\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_team_digits\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_team_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_score_team_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_team_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_score_team_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_team_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_score_team_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_team_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_team_scale\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_team_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_score_team_style\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_scoreclock_align_x\": {\n      \"desc\": \"Sets horizontal align of scoreclock.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_scoreclock_align_y\": {\n      \"desc\": \"Sets vertical align of scoreclock.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_scoreclock_format\": {\n      \"desc\": \"Controls in what format the scoreclock is displayed. Check the link below for available options. You can also add text, e.g. \\\"time: %H:%M:%S\\\"\",\n      \"group-id\": \"19\",\n      \"remarks\": \"https://www.man7.org/linux/man-pages/man3/strftime.3.html\",\n      \"type\": \"string\"\n    },\n    \"hud_scoreclock_frame\": {\n      \"desc\": \"Sets frame visibility and style for scoreclock.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_scoreclock_frame_color\": {\n      \"desc\": \"Defines the color of the background of the scoreclock HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_scoreclock_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_scoreclock_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_scoreclock_place\": {\n      \"desc\": \"Sets relative positioning for scoreclock.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_scoreclock_pos_x\": {\n      \"desc\": \"Sets horizontal position of scoreclock.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_scoreclock_pos_y\": {\n      \"desc\": \"Sets vertical position of scoreclock.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_scoreclock_proportional\": {\n      \"desc\": \"Toggles whether the scoreclock uses charset chars or TTF chars\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\"\n    },\n    \"hud_scoreclock_scale\": {\n      \"desc\": \"Scale of the scoreclock HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_scoreclock_show\": {\n      \"desc\": \"Toggles whether the scoreclock is displayed. It is only displayed when the scoreboard is open.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\"\n    },\n    \"hud_scoremapname_align_x\": {\n      \"desc\": \"Sets horizontal align of scoremapname.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_scoremapname_align_y\": {\n      \"desc\": \"Sets vertical align of scoremapname.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_scoremapname_frame\": {\n      \"desc\": \"Sets frame visibility and style for scoremapname.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_scoremapname_frame_color\": {\n      \"desc\": \"Defines the color of the background of the scoremapname HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_scoremapname_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_scoremapname_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_scoremapname_place\": {\n      \"desc\": \"Sets relative positioning for scoremapname.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_scoremapname_pos_x\": {\n      \"desc\": \"Sets horizontal position of scoremapname.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_scoremapname_pos_y\": {\n      \"desc\": \"Sets vertical position of scoremapname.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_scoremapname_proportional\": {\n      \"desc\": \"Toggles whether the scoremapname uses charset chars or TTF chars\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\"\n    },\n    \"hud_scoremapname_scale\": {\n      \"desc\": \"Scale of the scoremapname HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_scoremapname_show\": {\n      \"desc\": \"Toggles whether the scoremapname is displayed. It is only displayed when the scoreboard is open.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\"\n    },\n    \"hud_scoremapname_style\": {\n      \"desc\": \"Style of the scoremapname HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Full name, example: The Abandoned Base (dm3)\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Level name, example: The Abandoned Base\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Map name, example: dm3\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"hud_sigil1_align_x\": {\n      \"desc\": \"Sets horizontal align of sigil 1 icon (rune)\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil1_align_y\": {\n      \"desc\": \"Sets vertical align of sigil 1 icon (rune)\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil1_frame\": {\n      \"desc\": \"Sets frame visibility and style for sigil 1 icon (rune)\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil1_frame_color\": {\n      \"desc\": \"Defines the color of the background of the sigil1 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil1_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil1_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_sigil1_place\": {\n      \"desc\": \"Sets relative positioning for sigil 1 icon (rune)\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil1_pos_x\": {\n      \"desc\": \"Sets horizontal position of sigil 1 icon (rune)\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil1_pos_y\": {\n      \"desc\": \"Sets vertical position of sigil 1 icon (rune)\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil1_scale\": {\n      \"desc\": \"Sets size of sigil 1 icon (rune)\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil1_show\": {\n      \"desc\": \"Switches showing of sigil 1 icon (rune)\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil1_style\": {\n      \"desc\": \"Switches graphical style of sigil 1 icon (rune)\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil2_align_x\": {\n      \"desc\": \"Sets horizontal align of sigil 2 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil2_align_y\": {\n      \"desc\": \"Sets vertical align of sigil 2 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil2_frame\": {\n      \"desc\": \"Sets frame visibility and style for sigil 2 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil2_frame_color\": {\n      \"desc\": \"Defines the color of the background of the sigil2 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil2_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil2_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_sigil2_place\": {\n      \"desc\": \"Sets relative positioning for sigil 2 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil2_pos_x\": {\n      \"desc\": \"Sets horizontal position of sigil 2 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil2_pos_y\": {\n      \"desc\": \"Sets vertical position of sigil 2 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil2_scale\": {\n      \"desc\": \"Sets size of sigil 2 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil2_show\": {\n      \"desc\": \"Switches showing of sigil 2 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil2_style\": {\n      \"desc\": \"Switches graphical style of sigil 2 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil3_align_x\": {\n      \"desc\": \"Sets horizontal align of sigil 3 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil3_align_y\": {\n      \"desc\": \"Sets vertical align of sigil 3 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil3_frame\": {\n      \"desc\": \"Sets frame visibility and style for sigil 3 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil3_frame_color\": {\n      \"desc\": \"Defines the color of the background of the sigil3 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil3_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil3_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_sigil3_place\": {\n      \"desc\": \"Sets relative positioning for sigil 3 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil3_pos_x\": {\n      \"desc\": \"Sets horizontal position of sigil 3 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil3_pos_y\": {\n      \"desc\": \"Sets vertical position of sigil 3 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil3_scale\": {\n      \"desc\": \"Sets size of sigil 3 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil3_show\": {\n      \"desc\": \"Switches showing of sigil 3 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil3_style\": {\n      \"desc\": \"Switches graphical style of sigil 3 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil4_align_x\": {\n      \"desc\": \"Sets horizontal align of sigil 4 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil4_align_y\": {\n      \"desc\": \"Sets vertical align of sigil 4 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil4_frame\": {\n      \"desc\": \"Sets frame visibility and style for sigil 4 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil4_frame_color\": {\n      \"desc\": \"Defines the color of the background of the sigil4 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil4_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil4_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_sigil4_place\": {\n      \"desc\": \"Sets relative positioning for sigil 4 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_sigil4_pos_x\": {\n      \"desc\": \"Sets horizontal position of sigil 4 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil4_pos_y\": {\n      \"desc\": \"Sets vertical position of sigil 4 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil4_scale\": {\n      \"desc\": \"Sets size of sigil 4 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil4_show\": {\n      \"desc\": \"Switches showing of sigil 4 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sigil4_style\": {\n      \"desc\": \"Switches graphical style of sigil 4 icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_sortrules_includeself\": {\n      \"desc\": \"Determines how the current player is sorted.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No special treatment, sort as normal.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"The current player always appears in first place.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"The current player be in first or second place.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"hud_sortrules_playersort\": {\n      \"desc\": \"Determines how players are sorted.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Sort by name.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Sort by frags, then by name.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Sort by team, then name.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Sort by team, frags, then name.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"hud_sortrules_teamsort\": {\n      \"desc\": \"Determines how teams are sorted.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Sort by name.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Sort by frags, then by name.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"hud_speed2_align_x\": {\n      \"desc\": \"Vertical alignment of the speed2 HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_speed2_align_y\": {\n      \"desc\": \"Vertical alignment of the speed2 HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_speed2_color_fast\": {\n      \"desc\": \"Sets the color of the speed2 hud item when the speed is above the wrap speed.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Uses quake palette colors (0-255).\\nSee hud_radar2_wrapspeed also.\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed2_color_fastest\": {\n      \"desc\": \"Sets the color of the speed2 hud item when the speed is above 2x wrap speed.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Uses quake palette colors (0-255).\\nSee hud_radar2_wrapspeed also.\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed2_color_insane\": {\n      \"desc\": \"Sets the color of the speed2 hud item when the speed is above 3x wrap speed.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Uses quake palette colors (0-255).\\nSee hud_radar2_wrapspeed also.\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed2_color_normal\": {\n      \"desc\": \"Sets the color of the speed2 hud item when the speed is between 1 and the wrap speed.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Uses quake palette colors (0-255).\\nSee hud_radar2_wrapspeed also.\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed2_color_stopped\": {\n      \"desc\": \"Sets the color of the speed2 hud item when the speed is 0. Default is green.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Uses quake palette colors (0-255).\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed2_frame\": {\n      \"desc\": \"Opacity of the background of the speed2 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_speed2_frame_color\": {\n      \"desc\": \"Defines the color of the background of the speed2 HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_speed2_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_speed2_opacity\": {\n      \"desc\": \"Sets the opacity of the speed2 hud item.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"0 is fully transparent, and 1 is opague.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_speed2_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed2_orientation\": {\n      \"desc\": \"The orientation of the speed2 hud item. This can be set to, up, down, left and right. That is, the direction that the hud item will be pointing in.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Point upwards.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Point downwards.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Point to the right.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Point to the left.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"hud_speed2_place\": {\n      \"desc\": \"Placement of the speed2 HUD element. HUD elements can be place into various screen areas or other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_speed2_pos_x\": {\n      \"desc\": \"Horizontal relative position of the speed2 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed2_pos_y\": {\n      \"desc\": \"Vertical relative position of the speed2 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed2_radius\": {\n      \"desc\": \"Sets the radius of the half circle. The width and height is based on this.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"The radius of the half circle.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_speed2_show\": {\n      \"desc\": \"Visibility of the speed2 HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_speed2_wrapspeed\": {\n      \"desc\": \"Sets the speed when the speed needle will reset to the original position, and the next color is shown (to indicate faster speed).\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"The wrap speed.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_speed2_xyz\": {\n      \"desc\": \"Base the speed calculation on up/down movement also.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Only use the X/Y movement to calculate the speed.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Use X/Y/Z movement to calculate the speed. (movement up/down included).\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_speed_align_x\": {\n      \"desc\": \"Sets horizontal align of your current speed.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_speed_align_y\": {\n      \"desc\": \"Sets vertical align of your current speed.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_speed_color_fast\": {\n      \"desc\": \"Sets the color of the speed hud item when the player is moving at a \\\"fast\\\" speed (above 500).\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Uses quake palette colors (0-255).\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed_color_fastest\": {\n      \"desc\": \"Sets the color of the speed hud item when the player is moving at a really \\\"fast\\\" speed (above 1000).\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Uses quake palette colors (0-255).\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed_color_insane\": {\n      \"desc\": \"Sets the color of the speed hud item when the player is moving at a crazy speed (above 1500... I think).\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Uses quake palette colors (0-255).\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed_color_normal\": {\n      \"desc\": \"Sets the color of the speed hud item when the player is moving at a \\\"normal\\\" speed (below 500).\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Uses quake palette colors (0-255).\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed_color_stopped\": {\n      \"desc\": \"The color that the fill part of the speed hud item has when the player isn't moving. Default is green.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Uses quake palette colors (0-255).\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed_frame\": {\n      \"desc\": \"Sets frame visibility and style for your current speed.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_speed_frame_color\": {\n      \"desc\": \"Defines the color of the background of the speed HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_speed_height\": {\n      \"desc\": \"Sets the height of the speed hud item.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_speed_opacity\": {\n      \"desc\": \"Sets the opacity of the speed hud item.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"A value of 0 gives full transparency, 1 is opague.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_speed_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed_place\": {\n      \"desc\": \"Sets relative positioning for your current speed.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_speed_pos_x\": {\n      \"desc\": \"Sets horizontal position of your current speed.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_speed_pos_y\": {\n      \"desc\": \"Sets vertical position of your current speed.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_speed_show\": {\n      \"desc\": \"Switches showing of your current speed.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_speed_style\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_speed_text_align\": {\n      \"desc\": \"Sets how the text on the speed hud item should be aligned.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Don't draw the text at all.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Draw the text left aligned (or at the top when in vertical mode).\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Draw the text center aligned.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Draw the text left aligned (or at the bottom when in vertical mode).\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"hud_speed_tick_spacing\": {\n      \"desc\": \"Sets the \\\"tick spacing\\\" for the speed hud item.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_speed_vertical\": {\n      \"desc\": \"Sets whetever the speed hud item should be drawn vertically or horizontally.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Also see hud_speed_vertical_text to choose if the text should be drawn vertically also.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Draw speed horizontally.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw speed vertically.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_speed_vertical_text\": {\n      \"desc\": \"Sets whetever the text on the speed hud item should be drawn vertically when the hud item itself is being drawn vertically.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"This has no effect if the hud item isn't drawn vertically.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Draw the text horizontally.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw the text vertically when the speed hud item is being drawn vertically.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_speed_width\": {\n      \"desc\": \"Sets the width of the speed hud item.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_speed_xyz\": {\n      \"desc\": \"Sets This toggles whether the speed is measured over the XY axis (xyz 0) or the XYZ axis (xyz 1)\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_suit_align_x\": {\n      \"desc\": \"Sets horizontal align of suit icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_suit_align_y\": {\n      \"desc\": \"Sets vertical align of suit icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_suit_frame\": {\n      \"desc\": \"Sets frame visibility and style for suit icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_suit_frame_color\": {\n      \"desc\": \"Defines the color of the background of the suit HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_suit_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_suit_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_suit_place\": {\n      \"desc\": \"Sets relative positioning for suit icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_suit_pos_x\": {\n      \"desc\": \"Sets horizontal position of suit icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_suit_pos_y\": {\n      \"desc\": \"Sets vertical position of suit icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_suit_scale\": {\n      \"desc\": \"Sets size of suit icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_suit_show\": {\n      \"desc\": \"Switches showing of suit icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_suit_style\": {\n      \"desc\": \"Switches graphical style of suit icon.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teamfrags_align_x\": {\n      \"desc\": \"Sets teamfrags HUD element horizontal alignment.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"left, center, right, before, after\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_teamfrags_align_y\": {\n      \"desc\": \"Sets teamfrags HUD element vertical alignment.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"top, center, bottom, console, before, after\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_teamfrags_bignum\": {\n      \"desc\": \"Changes the scale of the fragcount number.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"If this is 0 the fragcount will use the normal charset. If it's above 0 it will scale a big number character instead.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_teamfrags_cell_height\": {\n      \"desc\": \"Sets cell height for cells in 'teamfrags' HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Use positive values.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_teamfrags_cell_width\": {\n      \"desc\": \"Sets cell width for cells in 'teamfrags' HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Use positive values.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_teamfrags_colors_alpha\": {\n      \"desc\": \"Sets the opacity of the teams colors for the teamfrags hud element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teamfrags_cols\": {\n      \"desc\": \"Sets number of columns used to draw table in 'teamfrags' HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Use positive values.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_teamfrags_extra_spec_info\": {\n      \"desc\": \"Enables to see what people have rocket launchers, powerups and how much health and armor they have using icons next to the frags. Works while watching MVD demo.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No extra information is shown.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Show RL, powerups armor and health.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Show only powerups.\",\n          \"name\": \"10\"\n        },\n        {\n          \"description\": \"Show only health.\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"Show only armor.\",\n          \"name\": \"12\"\n        },\n        {\n          \"description\": \"Show only RL.\",\n          \"name\": \"13\"\n        },\n        {\n          \"description\": \"Show only RL.  Show weapons as text instead of a picture.\",\n          \"name\": \"14\"\n        },\n        {\n          \"description\": \"Show RL, powerups armor and health.  Show weapons as text instead of a picture.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Show powerups, armor and health. (No RL's)\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Show RL, powerups and health. (No armor)\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Show RL, powerups and health. (No armor)  Show weapons as text instead of a picture.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Show RL, powerups and armor. (No health)\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Show RL, powerups and armor. (No health)  Show weapons as text instead of a picture.\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Show RL, armor and health. (No powerups)\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Show RL, armor and health. (No powerups)  Show weapons as text instead of a picture.\",\n          \"name\": \"9\"\n        }\n      ]\n    },\n    \"hud_teamfrags_fliptext\": {\n      \"desc\": \"Toggles alignment of team names in 'teamfrags' HUD element.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Use 'teamfrags shownames 1' to show names of teams.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Rows aligned to the left side, team tags are on the right side of frag counts.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Rows aligned to the right side, team tags are on the left side of frag counts.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teamfrags_frame\": {\n      \"desc\": \"Sets frame visibility and style for this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Adjust background opacity\",\n          \"name\": \"0.x to 1\"\n        },\n        {\n          \"description\": \"Bevelled frame.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"hud_teamfrags_frame_color\": {\n      \"desc\": \"Defines the color of the background of the teamfrags HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teamfrags_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teamfrags_maxname\": {\n      \"desc\": \"The max number of characters to use for displaying the teamnames in the teamfrags element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teamfrags_onlytp\": {\n      \"desc\": \"Decides whetever the teamfrags hud item should be shown only when in teamplay mode.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Show always.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Only show during teamplay.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teamfrags_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teamfrags_padtext\": {\n      \"desc\": \"Toggles text padding in 'frags' HUD element.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Use 'teamfrags shownames 1'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"No spaces between text fields.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Adds padding so frags and team tags are aligned into columns.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teamfrags_place\": {\n      \"desc\": \"Sets placement for this HUD element. You can align some elements relative to other elements.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"First you have to decide, if the element that you are locating now (element B) is to be positioned inside another element (element A) or outside it. See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"to place this element inside element B set this variable to \\\"@B\\\", to place it outside B element set this variable to \\\"B\\\"\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_teamfrags_pos_x\": {\n      \"desc\": \"Sets horizontal position of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teamfrags_pos_y\": {\n      \"desc\": \"Sets vertical position of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teamfrags_rows\": {\n      \"desc\": \"Sets number of rows used to draw table in 'teamfrags' HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Use positive values.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_teamfrags_show\": {\n      \"desc\": \"Toggles visibility of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw this element\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw this element\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teamfrags_shownames\": {\n      \"desc\": \"Draws players' team tags next to frag counts in 'teamfrags' HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not display team tags.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display team tags.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teamfrags_space_x\": {\n      \"desc\": \"Sets horizontal spacing for teamfrags fields.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Use zero or positive values.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_teamfrags_space_y\": {\n      \"desc\": \"Sets vertical spacing for teamfrags fields.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Use zero or positive values.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_teamfrags_strip\": {\n      \"desc\": \"Toggles stripped formatting of teamfrags fields.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Standard display.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Stripped format.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teamfrags_style\": {\n      \"desc\": \"Sets drawing style of 'teamfrags' HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Golden brackets around field with your team frags.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Arrow pointing to your own team frags field.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Red rectangle around your own team frags field.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Similar to 0.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Sets background color for your own team field to 'teamcolor'.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Sets background color for fields to teamcolors and enemycolors. Red rectangle around your own field including name and team tag.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Red rectangle around your own team field including team tag. Background color only for your own team field and set to 'teamcolor'.\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Background color for whole table 50% transparent red, your own team field not transparent.\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Red background color only for your own team field including team tag.\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"hud_teamfrags_vertical\": {\n      \"desc\": \"Toggles vertical ordering of teamfrags fields.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Order cells horizontally first.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Order cells vertically first.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teamholdbar_align_x\": {\n      \"desc\": \"Vertical alignment of the teamholdbar HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teamholdbar_align_y\": {\n      \"desc\": \"Vertical alignment of the teamholdbar HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teamholdbar_frame\": {\n      \"desc\": \"Opacity of the background of the teamholdbar HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teamholdbar_frame_color\": {\n      \"desc\": \"Defines the color of the background of the teamholdbar HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teamholdbar_height\": {\n      \"desc\": \"Height of the teamholdbar HUD element in pixels. Some elements support relative heights, e.g. 25%\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teamholdbar_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teamholdbar_onlytp\": {\n      \"desc\": \"Decides whetever the teamholdbar hud item should be shown only when in teamplay mode, or demo playback.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Always show.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Only show when teamplay is on.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Only show when in demo playback.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Only show when teamplay is on and in demo playback.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"hud_teamholdbar_opacity\": {\n      \"desc\": \"Sets the opacity of the background for the teamholdbar.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teamholdbar_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teamholdbar_place\": {\n      \"desc\": \"Placement of the teamholdbar HUD element. HUD elements can be place into various screen areas or other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teamholdbar_pos_x\": {\n      \"desc\": \"Horizontal relative position of the teamholdbar HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teamholdbar_pos_y\": {\n      \"desc\": \"Vertical relative position of the teamholdbar HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teamholdbar_show\": {\n      \"desc\": \"Visibility of the teamholdbar HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teamholdbar_show_text\": {\n      \"desc\": \"Shows the percent for each team on the teamholdbar.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"vertical_text can be used when vertical mode is set to draw the text vertically also.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't show the percentages.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Show the percentages for the teams.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teamholdbar_vertical\": {\n      \"desc\": \"Draw the teamholdbar vertically.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Use vertical_text to make the text vertical also.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Draw horizontally.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw vertically.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teamholdbar_vertical_text\": {\n      \"desc\": \"Draw the text vertically for the teamholdbar.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Draw the text horizontally.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw the text vertically.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teamholdbar_width\": {\n      \"desc\": \"Width of the teamholdbar HUD element in pixels. Some elements support relative width, e.g. 25%\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teamholdinfo_align_x\": {\n      \"desc\": \"Vertical alignment of the teamholdinfo HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teamholdinfo_align_y\": {\n      \"desc\": \"Vertical alignment of the teamholdinfo HUD element. See the HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teamholdinfo_frame\": {\n      \"desc\": \"Opacity of the background of the teamholdinfo HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teamholdinfo_frame_color\": {\n      \"desc\": \"Defines the color of the background of the teamholdinfo HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teamholdinfo_height\": {\n      \"desc\": \"Height of the teamholdinfo HUD element in pixels. Some elements support relative heights, e.g. 25%\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teamholdinfo_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teamholdinfo_itemfilter\": {\n      \"desc\": \"Decides what items should be shown in the list.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Any character/whitespace can be used as a delimiter. Make sure you enter the value between quotes if you use whitespaces.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Values: RL, LG, GL, SNG, Quad, Pent, Ring, Suit, Mega, RA, YA, GA\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_teamholdinfo_onlytp\": {\n      \"desc\": \"Decides whetever the teamholdinfo hud item should be shown only when in teamplay mode, or demo playback.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Always show.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Only show when teamplay is on.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Only show when in demo playback.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Only show when teamplay is on and in demo playback.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"hud_teamholdinfo_opacity\": {\n      \"desc\": \"Sets the background opacity for the teamholdinfo hud element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teamholdinfo_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teamholdinfo_place\": {\n      \"desc\": \"Placement of the teamholdinfo HUD element. HUD elements can be place into various screen areas or other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teamholdinfo_pos_x\": {\n      \"desc\": \"Horizontal relative position of the teamholdinfo HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teamholdinfo_pos_y\": {\n      \"desc\": \"Vertical relative position of the teamholdinfo HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teamholdinfo_show\": {\n      \"desc\": \"Visibility of the teamholdinfo HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teamholdinfo_style\": {\n      \"desc\": \"The style of the teamholdinfo hud item.\",\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Show the name of the team holding the items next to it.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Show a teamholdbar for each item showing how much of the item each team is holding.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"hud_teamholdinfo_width\": {\n      \"desc\": \"Width of the teamholdinfo HUD element in pixels. Some elements support relative width, e.g. 25%\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teaminfo_align_right\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_teaminfo_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teaminfo_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teaminfo_armor_style\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_teaminfo_flag_style\": {\n      \"group-id\": \"19\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Flag icons.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Colored text.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"hud_teaminfo_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teaminfo_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_teaminfo_header_spacing\": {\n      \"desc\": \"Lines spacing between teams when showing headers\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teaminfo_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_teaminfo_layout\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teaminfo_loc_width\": {\n      \"desc\": \"Number of character spaces used to display the location information in hud_teaminfo.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teaminfo_low_health\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_teaminfo_name_width\": {\n      \"desc\": \"Number of character spaces used to display the player name in hud_teaminfo.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_teaminfo_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_teaminfo_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_teaminfo_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_teaminfo_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_teaminfo_powerup_style\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_teaminfo_scale\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_teaminfo_show\": {\n      \"desc\": \"Display information about your team and optionally enemies.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"Enemy information can be enabled through hud_teaminfo_show_enemies 1.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teaminfo_show_ammo\": {\n      \"desc\": \"Display ammo count next to best weapon in the teaminfo.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"If disabled, ammo can still be displayed by adding %c to /hud_teaminfo_layout\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not show ammo next to best weapon\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Show ammo next to best weapon\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"hud_teaminfo_show_countdown\": {\n      \"desc\": \"Shows respawn time instead of powerups during wipeout mode.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"If disabled, countdown can still be displayed by adding %r to /hud_teaminfo_layout\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not show respawn time instead of powerups in wipeout mode\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Show respawn time instead of powerups in wipeout mode\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"hud_teaminfo_show_enemies\": {\n      \"desc\": \"Show information about the enemy team(s) in the teaminfo window. Displays a header for each team consisting of the team name and the current team score.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Hides information about enemy team(s)\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Shows information about enemy team(s)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teaminfo_show_self\": {\n      \"desc\": \"Display your self (or player spectated) in the teaminfo list.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_teaminfo_weapon_style\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_tp_need\": {\n      \"desc\": \"Toggles connection of tp_need_* variables with hud elements.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"E.g.: Use with 'tp_need_health 40' and your health indicator will display red numbers if your health is 40 or lower.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Numbers in HUD elements get red when below standard values.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Numbers in HUD get red when they are lower than appropriate tp_need_* values.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_tracking_align_x\": {\n      \"desc\": \"Sets tracking HUD element horizontal alignment.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"left, center, right, before, after\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_tracking_align_y\": {\n      \"desc\": \"Sets tracking HUD element vertical alignment.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"top, center, bottom, console, before, after\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_tracking_format\": {\n      \"desc\": \"Changes the format of descriptive text displayed when you are tracking someone as spectator or watching a demo where you can see player's team and name.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Pattern %n will be replaced with tracked player's name, %t will be replaced with player's team.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_tracking_frame\": {\n      \"desc\": \"Sets frame visibility and style for this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Adjust background opacity\",\n          \"name\": \"0.x to 1\"\n        },\n        {\n          \"description\": \"Bevelled frame.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"hud_tracking_frame_color\": {\n      \"desc\": \"Defines the color of the background of the tracking HUD element. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_tracking_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_tracking_order\": {\n      \"desc\": \"This defines the order of drawing the HUD elements. That means you can change will be drawn on top of other elements. See HUD manual for more info.\",\n      \"group-id\": \"19\",\n      \"type\": \"integer\"\n    },\n    \"hud_tracking_place\": {\n      \"desc\": \"Sets placement for this HUD element. You can align some elements relative to other elements.\",\n      \"group-id\": \"19\",\n      \"remarks\": \"First you have to decide, if the element that you are locating now (element B) is to be positioned inside another element (element A) or outside it. See HUD manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"to place this element inside element B set this variable to \\\"@B\\\", to place it outside B element set this variable to \\\"B\\\"\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"hud_tracking_pos_x\": {\n      \"desc\": \"Sets horizontal position of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_tracking_pos_y\": {\n      \"desc\": \"Sets vertical position of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_tracking_scale\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_tracking_show\": {\n      \"desc\": \"Toggles visibility of this HUD element.\",\n      \"group-id\": \"19\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw this element\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw this element\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"hud_vidlag_align_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_vidlag_align_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_vidlag_frame\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_vidlag_frame_color\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_vidlag_item_opacity\": {\n      \"group-id\": \"19\",\n      \"type\": \"float\"\n    },\n    \"hud_vidlag_order\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_vidlag_place\": {\n      \"group-id\": \"19\",\n      \"type\": \"string\"\n    },\n    \"hud_vidlag_pos_x\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_vidlag_pos_y\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_vidlag_show\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"hud_vidlag_style\": {\n      \"group-id\": \"19\",\n      \"type\": \"\"\n    },\n    \"ignore_flood\": {\n      \"default\": \"0\",\n      \"group-id\": \"3\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Off.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Ignores repeated 'say' and spectator messages.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Ignores repeated 'say', 'say_team' and spectator messages.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"ignore_flood_duration\": {\n      \"default\": \"4\",\n      \"desc\": \"You can change the 4 second cooldown with the 'ignore_flood_duration' variable.\",\n      \"group-id\": \"3\",\n      \"type\": \"float\"\n    },\n    \"ignore_mode\": {\n      \"default\": \"0\",\n      \"desc\": \"Someone is on your ignore list, you won't see any messagemode (/say hello) messages from them (even if they are a spec).\",\n      \"group-id\": \"3\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Ignore say only.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Ignore say_team too.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"ignore_no_weapon\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggle showing the \\\"no weapon.\\\" message emitted by KTX when attempting to fire a weapon you don't have.\",\n      \"group-id\": \"3\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"ignore_not_enough_ammo\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggle showing the \\\"not enough ammo.\\\" message emitted by KTX when attempting to fire a weapon without enough ammo.\",\n      \"group-id\": \"3\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"ignore_opponents\": {\n      \"default\": \"0\",\n      \"group-id\": \"3\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not ignore opponent team.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Alway ignore opponent team.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Only ignore opponent team during the match.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"ignore_qizmo_spec\": {\n      \"default\": \"0\",\n      \"desc\": \"Ignores all Qizmo spectators (= observers). Very useful on big matches with 100 and more spectators.\",\n      \"group-id\": \"3\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Ignoring OFF.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Ignoring ON.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"ignore_qtv\": {\n      \"default\": \"0\",\n      \"desc\": \"Ignore chat messages from the Quake-TV broadcast.\",\n      \"group-id\": \"3\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"ignore_spec\": {\n      \"default\": \"0\",\n      \"group-id\": \"3\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Off\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Ignore spec chat unless you are a spectator.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Ignore spec chat even if you are a spectator.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"image_jpeg_quality_level\": {\n      \"default\": \"75\",\n      \"desc\": \"You can set quality of jpeg screenshots with 'image_jpeg_quality_level x' where x is an integer from 0 to 100 inclusive.\\nThe larger x is, the better the quality of a jpeg screenshot, but also the bigger the size.\",\n      \"group-id\": \"41\",\n      \"type\": \"float\"\n    },\n    \"image_png_compression_level\": {\n      \"default\": \"1\",\n      \"desc\": \"You can set the amount of png compression with 'image_png_compression_level x' where x is an integer from 0 to 9 inclusive.\\n0 gives no compression and 9 gives maximum compression (and slowest writing time).\",\n      \"group-id\": \"41\",\n      \"type\": \"float\"\n    },\n    \"in_builtinkeymap\": {\n      \"default\": \"0\",\n      \"desc\": \"Allows you to use old Quake keyboard mapping.\",\n      \"group-id\": \"9\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Use your operating system keyboard layout\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Use Quake keyboard layout\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"in_di_buffered\": {\n      \"desc\": \"Direct Input: Use Immedia data instead of buffered mode of reading.\",\n      \"group-id\": \"28\",\n      \"remarks\": \"This setting has been added only because Direct Input allows such mode of use. It is not going to decrease input lag, make the mouse responsiveness better, or anything like that. Read the official documentation on Direct Input to know the difference between buffered and immediate mode of use.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Use immediate data\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Use events buffer to process the input data\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"in_di_bufsize\": {\n      \"desc\": \"Size of the direct input buffer.\",\n      \"group-id\": \"28\",\n      \"remarks\": \"On some circumstances the default size does not have to be sufficient.\",\n      \"type\": \"integer\"\n    },\n    \"in_evdevice\": {\n      \"desc\": \"Specify device for evdev mouse.\\nShould be absolute path like /dev/input/event0.\\nUse in_evdevlist command to get lost of devices.\",\n      \"group-id\": \"28\",\n      \"remarks\": \"You should have read access to your device.\\nsudo chmod 644 /dev/input/event* should help you if you have trouble.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"absolute path to mouse event device\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"in_grab_windowed_mouse\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, will grab mouse input when not fullscreen.\",\n      \"group-id\": \"11\",\n      \"type\": \"boolean\"\n    },\n    \"in_ignore_deadkeys\": {\n      \"default\": \"1\",\n      \"desc\": \"Ignores operating-system interpretation of keypresses while deadkey is held down\",\n      \"group-id\": \"11\",\n      \"systems\": [\n        \"macos\"\n      ],\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disabled\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Enabled when option keys are held down\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Enabled when alt keys are held down (for standard keyboard on MacOS)\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"in_ignore_touch_events\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, will ignore events from touch inputs (events from a real mouse should be unaffected)\",\n      \"group-id\": \"11\",\n      \"type\": \"boolean\"\n    },\n    \"in_ignore_unfocused_keyb\": {\n      \"default\": \"0\",\n      \"desc\": \"If set, will ignore keyboard events received immediately after regaining focus.  Should stop shortcut keys from firing in-game.\",\n      \"group-id\": \"11\",\n      \"remarks\": \"X11 systems only.\",\n      \"systems\": [\n        \"linux\"\n      ],\n      \"type\": \"boolean\"\n    },\n    \"in_m_os_parameters\": {\n      \"desc\": \"Allows you to use your system mouse settings in the client.\",\n      \"group-id\": \"28\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not keep any of the system mouse settings\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Keep acceleration settings\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Keep speed settings\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Keep both acceleration and speed settings\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"in_m_smooth\": {\n      \"desc\": \"Enables advanced mouse smoothing.\\nYou have to be using Direct Input for this to work.\",\n      \"group-id\": \"28\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"in_mmt\": {\n      \"desc\": \"Multithreaded mouse.\\nFor most users in_mmt 1 + evdev gives the most smooth input.\",\n      \"group-id\": \"28\",\n      \"remarks\": \"Linux only and evdev (in_mouse 3) only.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Read mouse data in main thread\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Use separate thread for reading mouse data\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"in_mouse\": {\n      \"desc\": \"Different types of mouse input\\n[OBSOLETE in ezQuake 3.0]\",\n      \"group-id\": \"28\",\n      \"remarks\": \"Linux: You have to set in_evdevice to proper value (/dev/input/eventX). Use command in_evdevlist to get the lost of proper values. Also in_mmt makes your mouse more smooth.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Mouse off\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Windows: standard mouse; Linux: DGA mouse\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Windows: Direct Input; Linux: X Mouse\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Windows: Raw Input; Linux: EVDEV mouse\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"in_raw\": {\n      \"default\": \"1\",\n      \"desc\": \"Whether or not ezQuake will use raw input to sample mouse events.\",\n      \"group-id\": \"11\",\n      \"remarks\": \"It is recommended to leave this set.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disabled, correlates to /in_mouse 1 in ezQuake 2.2\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enabled, correlates to /in_mouse 3 in ezQuake 2.2\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"in_raw_allbuttons\": {\n      \"desc\": \"Forces RAW Input handler to treat more incoming signals as mouse buttons.\\nHelps if you cannot get some mouse buttons working in the game.\",\n      \"group-id\": \"28\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Only standard set of events is interpreted as mouse buttons\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"More events are interpreted as mouse buttons\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"in_release_mouse_modes\": {\n      \"default\": \"2\",\n      \"desc\": \"Controls under which mode(s) the cursor will be released back to the operating system when not fullscreen.\",\n      \"group-id\": \"11\",\n      \"remarks\": \"To specify more than one mode, add the values together.\\n\\nTo always release the cursor, set in_grab_windowed_mouse 0.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Never release mouse cursor\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Release when playing (demo playback only)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Release when console is down\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Release when using /messagemode\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Release when menu on-screen\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Release when using hud editor\",\n          \"name\": \"16\"\n        },\n        {\n          \"description\": \"Release when using demo controls\",\n          \"name\": \"32\"\n        }\n      ]\n    },\n    \"irc_server_address\": {\n      \"desc\": \"IP address of the IRC server to connect to on irc connect command.\",\n      \"group-id\": \"12\",\n      \"type\": \"string\"\n    },\n    \"irc_server_password\": {\n      \"desc\": \"Password for IRC server connection.\",\n      \"group-id\": \"12\",\n      \"type\": \"string\"\n    },\n    \"irc_server_port\": {\n      \"desc\": \"Port number for IRC server connection.\",\n      \"group-id\": \"12\",\n      \"type\": \"integer\"\n    },\n    \"irc_user_nick\": {\n      \"desc\": \"Your nickname used to log-in to the IRC server.\",\n      \"group-id\": \"12\",\n      \"type\": \"string\"\n    },\n    \"irc_user_realname\": {\n      \"desc\": \"Real name field used to log-in to the IRC server.\",\n      \"group-id\": \"12\",\n      \"type\": \"string\"\n    },\n    \"irc_user_username\": {\n      \"desc\": \"Username used to log-in to the IRC server.\",\n      \"group-id\": \"12\",\n      \"type\": \"string\"\n    },\n    \"joyadvanced\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables advanced joystick features.\",\n      \"group-id\": \"28\",\n      \"type\": \"boolean\"\n    },\n    \"joyadvaxisr\": {\n      \"default\": \"0\",\n      \"desc\": \"Mapping for joystick's R axis.  joyadvanced must be enabled.  See joyadvaxisx for arguments.\",\n      \"group-id\": \"28\",\n      \"type\": \"integer\"\n    },\n    \"joyadvaxisu\": {\n      \"default\": \"0\",\n      \"desc\": \"Mapping for joystick's U axis.  joyadvanced must be enabled.  See joyadvaxisx for arguments.\",\n      \"group-id\": \"28\",\n      \"type\": \"integer\"\n    },\n    \"joyadvaxisv\": {\n      \"default\": \"0\",\n      \"desc\": \"Mapping for joystick's V axis.  joyadvanced must be enabled.  See joyadvaxisx for arguments.\",\n      \"group-id\": \"28\",\n      \"type\": \"integer\"\n    },\n    \"joyadvaxisx\": {\n      \"default\": \"0\",\n      \"desc\": \"Mapping for joystick's X axis.  joyadvanced must be enabled.\",\n      \"group-id\": \"28\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Unmapped (axis input ignored).\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Map to forward/backward motion.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Map to pitch up/down.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Map to strafing motion.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Map to turning left/right.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Map to vertical motion.\",\n          \"name\": \"5\"\n        }\n      ]\n    },\n    \"joyadvaxisy\": {\n      \"default\": \"0\",\n      \"desc\": \"Mapping for joystick's Y axis.  joyadvanced must be enabled.  See joyadvaxisx for arguments.\",\n      \"group-id\": \"28\",\n      \"type\": \"integer\"\n    },\n    \"joyadvaxisz\": {\n      \"default\": \"0\",\n      \"desc\": \"Mapping for joystick's Z axis.  joyadvanced must be enabled.  See joyadvaxisx for arguments.\",\n      \"group-id\": \"28\",\n      \"type\": \"integer\"\n    },\n    \"joyflysensitivity\": {\n      \"default\": \"-1.0\",\n      \"desc\": \"Scale multiplier for vertical movement.\",\n      \"group-id\": \"28\",\n      \"type\": \"float\"\n    },\n    \"joyflythreshold\": {\n      \"default\": \"0.15\",\n      \"desc\": \"Minimum deflection required for vertical movement.\\nRange [0.0,1.0]\",\n      \"group-id\": \"28\",\n      \"type\": \"float\"\n    },\n    \"joyforwardsensitivity\": {\n      \"default\": \"-1.0\",\n      \"desc\": \"Scale multiplier for forward movement.\",\n      \"group-id\": \"28\",\n      \"type\": \"float\"\n    },\n    \"joyforwardthreshold\": {\n      \"default\": \"0.15\",\n      \"desc\": \"Minimum deflection required for forward movement.\\nRange [0.0,1.0]\",\n      \"group-id\": \"28\",\n      \"type\": \"float\"\n    },\n    \"joyindex\": {\n      \"default\": \"0\",\n      \"desc\": \"Index number of joystick device to use.\\nDevices typically start numbering from 0.\",\n      \"group-id\": \"28\",\n      \"type\": \"integer\"\n    },\n    \"joyname\": {\n      \"default\": \"joystick\",\n      \"desc\": \"User-assigned name of the joystick.\",\n      \"group-id\": \"28\",\n      \"type\": \"string\"\n    },\n    \"joypitchsensitivity\": {\n      \"default\": \"1.0\",\n      \"desc\": \"Scale multiplier for pitching up/down.\",\n      \"group-id\": \"28\",\n      \"type\": \"float\"\n    },\n    \"joypitchthreshold\": {\n      \"default\": \"0.15\",\n      \"desc\": \"Minimum deflection required for pitching up/down.\\nRange [0.0,1.0]\",\n      \"group-id\": \"28\",\n      \"type\": \"float\"\n    },\n    \"joysidesensitivity\": {\n      \"default\": \"-1.0\",\n      \"desc\": \"Scale multiplier for strafing movement.\",\n      \"group-id\": \"28\",\n      \"type\": \"float\"\n    },\n    \"joysidethreshold\": {\n      \"default\": \"0.15\",\n      \"desc\": \"Minimum deflection required for strafing movement.\\nRange [0.0,1.0]\",\n      \"group-id\": \"28\",\n      \"type\": \"float\"\n    },\n    \"joystick\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables usage of joystick device.\",\n      \"group-id\": \"28\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not use joystick.\",\n          \"name\": \"false\"\n    },\n        {\n          \"description\": \"Use joystick input.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"joyyawsensitivity\": {\n      \"default\": \"-1.0\",\n      \"desc\": \"Scale multiplier for turning left/right.\",\n      \"group-id\": \"28\",\n      \"type\": \"float\"\n    },\n    \"joyyawthreshold\": {\n      \"default\": \"0.15\",\n      \"desc\": \"Minimum deflection required for turning left/right.\\nRange [0.0,1.0]\",\n      \"group-id\": \"28\",\n      \"type\": \"float\"\n    },\n    \"localid\": {\n      \"default\": \"\",\n      \"group-id\": \"2\",\n      \"type\": \"string\"\n    },\n    \"log_dir\": {\n      \"default\": \"\",\n      \"desc\": \"The logging dir.\",\n      \"group-id\": \"5\",\n      \"type\": \"string\"\n    },\n    \"log_readable\": {\n      \"default\": \"1\",\n      \"description\": \"Converts all non-printable characters to printable characters in your log (ie colored text, Ocrana LEDs) to make it more readable.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\"\n    },\n    \"lookspring\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable toggles the centering of the screen after the -klook command.\",\n      \"group-id\": \"10\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable automatic force_centerview.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable automatic force_centerview.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"lookstrafe\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable toggles the automatic strafing when the +klook command is used.\\nWhen set to \\\"1\\\" and the player used the +klook command, the keys that are bound to the +left and +right commands will now act as if they were bound to +moveleft and +moveright.\\nThis command was put it in order to allow keyboard player to combine the +strafe and +klook commands into one.\\nThis command also has effect on the mouse controls. When set to \\\"1\\\" moving the mouse left and right will make the player move left and right instead of making him turn left and right.\",\n      \"group-id\": \"10\",\n      \"type\": \"float\"\n    },\n    \"m_accel\": {\n      \"default\": \"0\",\n      \"desc\": \"Values >0 will amplify mouse movement proportional to velocity.\\nSmall values have great effect.\",\n      \"group-id\": \"11\",\n      \"remarks\": \"A lot of good Quake Live players use around the 0.1-0.2 mark, but this depends on your mouse CPI and polling rate.\",\n      \"type\": \"float\"\n    },\n    \"m_accel_offset\": {\n      \"default\": \"0\",\n      \"desc\": \"Acceleration will not be active until the mouse movement exceeds this speed (counts per millisecond).\\nNegative values are supported, which has the effect of causing higher rates of acceleration to happen at lower velocities.\",\n      \"group-id\": \"11\",\n      \"type\": \"float\"\n    },\n    \"m_accel_power\": {\n      \"default\": \"2\",\n      \"desc\": \"Values 1 or below are dumb.\\n2 is linear and the default.\\nAbove 2 begins to amplify exponentially and you will get more acceleration at higher velocities. Great if you want low accel for slow movements, and high accel for fast movements. Good in combination with a sensitivity cap (m_accel_senscap)\",\n      \"group-id\": \"11\",\n      \"type\": \"float\"\n    },\n    \"m_accel_senscap\": {\n      \"default\": \"0\",\n      \"desc\": \"Sets an upper limit on the amplified mouse movement. Great for tuning acceleration around lower velocities while still remaining in control of fast motion such as flicking.\",\n      \"group-id\": \"11\",\n      \"type\": \"float\"\n    },\n    \"m_filter\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable toggles mouse input filtering.\\nWhen set to \\\"1\\\", the values which are received from the mouse's input will first be averaged together and then that value will be used in the game.\\nThe reason for this command is that some mice had problems with sending sporadic coordinates which make the input from the mouse jerky, also when using a serial or PS/2 mouse, the Windows operating system will only sample mouse input every 25ms, that is 40 times a second (for USB mice the sample rate is 125 Hz, that is every 8 ms).\\nWhen set to \\\"1\\\" this variable will smooth out the input but it will cause latency between the movement of the mouse and the actual response in the game.\\nWhen using a PS/2 mouse it is thus first recommended to try to increase the sampling rate either by changing it via your mouse driver or by using the ps2rate program which can be downloaded at ps2rate homepage.\\nIf you are playing the game at frame rates above 40 FPS and if you can't increase the sampling rate of your PS/2 rate or if you are playing with a serial mouse it is recommended that you enable this toggle.\",\n      \"group-id\": \"11\",\n      \"type\": \"float\"\n    },\n    \"m_forcewheel\": {\n      \"group-id\": \"28\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"If you have problems to get MWHEELUP and MWHEELDOWN working set this to 1.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"m_forward\": {\n      \"default\": \"1\",\n      \"desc\": \"This variable controls how fast the player should move forward and back when the mouse is moved forward and back.\\nThis command has no effect if the +mlook command is in effect because when the mouse is moved forward and back the player looks up and down instead of moving forward and back.\\nSome players might want to set this variable to \\\"0\\\" if they happen not to use +mlook constantly and they only want to use the mouse to turn the player.\\nSetting this variable to \\\"0\\\" will prevent the inadvertent movement of the player forward and back while trying to make precise turns with the mouse.\",\n      \"group-id\": \"11\",\n      \"type\": \"float\"\n    },\n    \"m_pitch\": {\n      \"default\": \"0.022\",\n      \"desc\": \"This variable sets the level of precision when the mouse is used to make the player look up and down while the +mlook command is in effect.\\nBy default this variable is set in such a way that moving the mouse forward makes the player look up and moving the mouse backward makes the player look down.\\nSome people prefer to have this movement inverted just like it is inverted for airplane controls.\\nIf you wish to use this inverted mouse movement then you should set this variable to a negative value (for example \\\"-0.022\\\"). It is a matter of preference which movement method is used by players.\\nAlso lowering the value for this variable will increase the level of precision when the mouse is used to make the player look up and down.\\nThis variable can be used separately from the sensitivity variable to provide greater control over the mouse sensitivity for movement along the pitch. It is advisable to keep the value for this variable constant at 0.022 or -0.022 and instead use the sensitivity variable to change the overall sensitivity of the mouse.\\nAlso, some script writers lower the value for this variable along with a lowered value for the fov variable in order to provide more precision when the fov variable is used to zoom.\",\n      \"group-id\": \"11\",\n      \"type\": \"float\"\n    },\n    \"m_rate\": {\n      \"desc\": \"This variable should be set to your mouse rate (in Hz).\\nNote: need -m_smooth and -dinput to commandline.\",\n      \"group-id\": \"28\",\n      \"type\": \"float\"\n    },\n    \"m_showrate\": {\n      \"group-id\": \"28\",\n      \"remarks\": \"Note: need -m_smooth and -dinput to commandline.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Print current mouse rate\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"m_side\": {\n      \"default\": \"0.8\",\n      \"desc\": \"When the +strafe command is active or when \\\"lookspring\\\" is set to \\\"1\\\" this variable is used to control the sensitivity when the mouse is moved left and right to make the player move left and right.\",\n      \"group-id\": \"11\",\n      \"type\": \"float\"\n    },\n    \"m_yaw\": {\n      \"default\": \"0.022\",\n      \"desc\": \"This variable controls how fast the player turns left and right when the mouse is moved left and right.\\nIt is recommended that this variable be left alone and instead the \\\"sensitivity\\\" variable is used to change the level of precision.\\nIf you set this variable to a negative value you will reverse the mouse movement.\\nSome script writers use this variable to increase the level of precision when the \\\"fov\\\" variable is used to zoom the screen.\",\n      \"group-id\": \"11\",\n      \"type\": \"float\"\n    },\n    \"mapname\": {\n      \"default\": \"\",\n      \"desc\": \"Contains the name of the current map.\",\n      \"group-id\": \"2\",\n      \"type\": \"string\"\n    },\n    \"match_auto_logconsole\": {\n      \"default\": \"1\",\n      \"desc\": \"When set to 1 or 2, a temp console log will automatically be created when a match starts (usually when the countdown starts).\",\n      \"group-id\": \"16\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not auto-log console.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Auto record console but requires manuall saving. See 'match_save'.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Automatically saves the console log after the match is completed.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"match_auto_logupload\": {\n      \"default\": \"0\",\n      \"desc\": \"Automatically upload match console log to remote server specified in match_auto_logurl.\",\n      \"group-id\": \"16\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"match_auto_logupload_token\": {\n      \"default\": \"\",\n      \"group-id\": \"16\",\n      \"type\": \"string\"\n    },\n    \"match_auto_logurl\": {\n      \"default\": \"http://stats.quakeworld.nu/logupload\",\n      \"group-id\": \"16\",\n      \"type\": \"string\"\n    },\n    \"match_auto_minlength\": {\n      \"default\": \"30\",\n      \"desc\": \"When using 'match_auto_record 2', temp demo's auto recorded won't be saved automatically if they are shorter than the number of seconds match_auto_minlength' is set to.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"If a temp demo is too short to autosave, you can still save it manually with \\\"match_save\\\".\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"number of seconds\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_auto_record\": {\n      \"default\": \"0\",\n      \"desc\": \"When set to 1 or 2, a temp demo will automatically be recorded when a match starts (usually when the countdown starts).\",\n      \"group-id\": \"16\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No auto recording.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Demo will be recorded but requires manual saving with match_save\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Automatically saves the demo after the match is completed.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"match_auto_spectating\": {\n      \"default\": \"0\",\n      \"desc\": \"When set to 1, auto recording will also occur when in spectator mode.\",\n      \"group-id\": \"16\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't auto record demos in spectator mode.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Auto record demos even in spectator mode.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"match_auto_sshot\": {\n      \"default\": \"0\",\n      \"desc\": \"Automatically take a screenshot at the end of a game.\\nIf your console is down or you are in the menus, the client will remove the console/menu for a split second so it can take a screenshot of the scoreboard without any interference.\",\n      \"group-id\": \"16\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not take auto screenshot.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Take auto screenshot on match end.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"match_auto_unminimize\": {\n      \"default\": \"1\",\n      \"desc\": \"Bring client from minimized state on countdown start.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"Only works on KT* servers.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"match_challenge\": {\n      \"default\": \"0\",\n      \"desc\": \"When enabled, sends announcements to duel ladder server about match start and match end.\\nCan be activated by special startup *.qw file or by opening special qw: URL.\",\n      \"group-id\": \"16\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"match_challenge_url\": {\n      \"default\": \"http://stats.quakeworld.nu/post-challenge\",\n      \"desc\": \"Web address where POST requests announcing challenge match start and end will be sent.\",\n      \"group-id\": \"16\",\n      \"type\": \"string\"\n    },\n    \"match_format_2on2\": {\n      \"default\": \"2on2/%n - [%k%v%l] - [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (gamedir = \\\"qw\\\", 2 teams with at least 1 team having 2 people)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_format_3on3\": {\n      \"default\": \"tdm/%n - [%Oon%E_%t%v%e] - [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (gamedir = \\\"qw\\\", 2 teams with at least 1 team having 3 people)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_format_4on4\": {\n      \"default\": \"tdm/%n - [%Oon%E_%t%v%e] - [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (gamedir = \\\"qw\\\", 2 teams with at least 1 team having 4 people)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_format_arena\": {\n      \"default\": \"arena/%n - %p%v%e - [%F_frags] - [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (gamedir = \\\"arena\\\")\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_format_coop\": {\n      \"default\": \"coop/%n - [%C_player_coop] - [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (gamedir = \\\"qw\\\", at least 2 people, deathmatch is off)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_format_duel\": {\n      \"default\": \"duel/%n - %p%v%e - [dmm%D] - [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (gamedir = \\\"qw\\\", 2 players)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_format_ffa\": {\n      \"default\": \"ffa/%n - [%C_player_ffa] - [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (gamedir = \\\"qw\\\", more than 2 players, teamplay off)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_format_multiteam\": {\n      \"default\": \"tdm/%n - [%a_%b] - [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (gamedir = \\\"qw\\\", 3 or more teams)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_format_race\": {\n      \"default\": \"race/%n - [race] - [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (gamedir = qw, serverinfo \\\"race\\\" key present and equal to mapname)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_format_solo\": {\n      \"default\": \"solo/%n - [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (any gamedir, 1 player on a server, not in race mode)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_format_tdm\": {\n      \"default\": \"tdm/%n - [%Oon%E_%t%v%e] - [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (gamedir = \\\"qw\\\", 2 teams with at least 1 team having 5 people)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_format_tf_clanwar\": {\n      \"default\": \"tfwar/%n - [%Oon%E_%t%v%e] - [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (gamedir = \\\"fortress\\\", at least 2 teams)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_format_tf_duel\": {\n      \"default\": \"tfduel/%n - %p%v%e [%M]\",\n      \"desc\": \"Each match category has a name format variable associated with it. This variable is called match_format_<category>. For example, there is match_format_duel, match_format_2on2, etc, etc. All these variables can contain macro's that are expanded according to the macro list given below.  You can also use the \\\"match_format_macrolist\\\" command inside the client to display a list of the macros and their meaning.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See 'match_format_macrolist' command and Match tools manual for more info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"This pattern will be used in following situation: (gamedir = \\\"fortress\\\", 2 players)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"match_ladder_id\": {\n      \"default\": \"1\",\n      \"desc\": \"Identification of the current duel ladder. Not used right now, always 1.\",\n      \"group-id\": \"16\",\n      \"type\": \"string\"\n    },\n    \"match_name_and\": {\n      \"default\": \"_&_\",\n      \"desc\": \"Used for separating names in %k and %l.\",\n      \"group-id\": \"16\",\n      \"type\": \"string\"\n    },\n    \"match_name_nick\": {\n      \"default\": \"\",\n      \"desc\": \"%n uses this if its not \\\"\\\", otherwise it uses your in game name.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See match_format_macrolist.\",\n      \"type\": \"string\"\n    },\n    \"match_name_on\": {\n      \"default\": \"on\",\n      \"desc\": \"Used for separating numbers in %a.\",\n      \"group-id\": \"16\",\n      \"type\": \"string\"\n    },\n    \"match_name_spec\": {\n      \"default\": \"(SPEC)\",\n      \"desc\": \"This is placed after your nick when using %n and are in spec mode. Eg. if you use \\\"match_name_nick foo\\\" and leave match_name_spec default, then %n will be \\\"foo(SPEC)\\\" in spec mode and \\\"foo\\\" when not in spec mode.\",\n      \"group-id\": \"16\",\n      \"remarks\": \"See match_format_macrolist.\",\n      \"type\": \"string\"\n    },\n    \"match_name_versus\": {\n      \"default\": \"_vs_\",\n      \"desc\": \"Used for separating names in %b.\",\n      \"group-id\": \"16\",\n      \"type\": \"string\"\n    },\n    \"maxclients\": {\n      \"default\": \"24\",\n      \"desc\": \"Highest number of players allowed on the server.\",\n      \"group-id\": \"43\",\n      \"type\": \"integer\"\n    },\n    \"maxfps\": {\n      \"default\": \"77\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"maxspectators\": {\n      \"default\": \"8\",\n      \"desc\": \"Highest number of spectators allowed to connect to the server.\",\n      \"group-id\": \"43\",\n      \"type\": \"integer\"\n    },\n    \"maxvip_spectators\": {\n      \"default\": \"0\",\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"menu_advanced\": {\n      \"default\": \"0\",\n      \"desc\": \"Shows/hides advanced options entries in the Options menu.\",\n      \"group-id\": \"17\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Hide advanced options\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Show advanced options\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"menu_botmatch_gamedir\": {\n      \"default\": \"fbca\",\n      \"desc\": \"The gamedir that ezQuake will switch to when starting a botmatch through the multiplayer menu.\",\n      \"group-id\": \"17\",\n      \"type\": \"string\"\n    },\n    \"menu_botmatch_mod_old\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, starting a botmatch through multiplayer menu will disable features that cause problems for older mods that spawn bots as non-clients.\",\n      \"group-id\": \"17\",\n      \"type\": \"boolean\"\n    },\n    \"menu_ingame\": {\n      \"default\": \"1\",\n      \"desc\": \"Enables/disables ingame menu - menu that typically appears if you press Esc key during the game.\",\n      \"group-id\": \"17\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"in-game menu disabled\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"in-game menu enabled\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"menu_marked_bgcolor\": {\n      \"default\": \"20 20 20 128\",\n      \"desc\": \"The color of the selected entry in options menu.\",\n      \"group-id\": \"17\",\n      \"type\": \"string\"\n    },\n    \"menu_marked_fade\": {\n      \"default\": \"4\",\n      \"desc\": \"Speed of menu entry highlight flashing. Zero disables the flashing effect.\",\n      \"group-id\": \"17\",\n      \"type\": \"float\"\n    },\n    \"msg\": {\n      \"default\": \"1\",\n      \"group-id\": \"37\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Maximum reporting of messages.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Parsed reporting of messages.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Limited reporting of messages.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Minimal reporting of messages.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"No reporting of messages.\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"msg_filter\": {\n      \"default\": \"0\",\n      \"group-id\": \"3\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Filters nothing (default)\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Filters mm1 and spec messages\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Filters mm2 messages\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Filters mm1, mm2, and spec messages\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"mtu\": {\n      \"default\": \"\",\n      \"desc\": \"Maximum transmission unit size.\\nSuggests maximum packet size to server, if supported.\",\n      \"group-id\": \"37\",\n      \"type\": \"integer\"\n    },\n    \"mumble_enabled\": {\n      \"desc\": \"Turn on mumble positional audio support.\",\n      \"group-id\": \"32\",\n      \"remarks\": \"Requires mumble to be loaded before toggling this on.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"mvd_autoadd_items\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"mvd_autohud\": {\n      \"default\": \"0\",\n      \"desc\": \"Will load different Head Up Display settings when watching MultiView Demos.\",\n      \"group-id\": \"20\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Won't do anything\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Will load appropriate config according to current match type. Possible types: 1on1, 4on4, custom. Config is like cfg/mvdhud_4on4.cfg\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Will always load cfg/mvdhud_custom.cfg\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"mvd_autotrack\": {\n      \"default\": \"0\",\n      \"desc\": \"Turns auto-tracking function ON / OFF.\\nThis feature can be used while watching MultiView Demo. The client will choose and switch to the best player point of view using appropriate algorithm.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"You can change the switching algorithm properties using mvd_autotrack_1on1, mvd_autotrack_2on2, etc. variables. The setting no. 4. cannot be customized and is independent on other autotrack client settings, except of mvd_autotrack_lockteam.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Off\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Autodetect current gametype and use the appropriate xonx settings\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Force the use of mvd_autotrack_custom* settings\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Enables the use of mvd_multitrack_1 - 4 allowing to set separate algorithms for every window with cl_multiview 2-4\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Simple algorithm which prefers pent+rl over quad+rl, quad+rl over pent, quad over noquad, weapon over no weapon player\\nCannot be customized\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"mvd_autotrack_1on1\": {\n      \"default\": \"%a * %A + 50 * %W + %p + %f\",\n      \"desc\": \"Will be used if mvd_autotrack = 1 and a 1on1 game is played. Its an algorithm for selecting the best player.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"For a full description read the manual.\",\n      \"type\": \"string\"\n    },\n    \"mvd_autotrack_1on1_values\": {\n      \"default\": \"1 2 3 2 3 5 8 8 1 2 3 0 0 0\",\n      \"desc\": \"Allows you to customize autotrack algorithm. See autotrack manual page for more info.\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_autotrack_2on2\": {\n      \"default\": \"%a * %A + 50 * %W + %p + %f\",\n      \"desc\": \"Will be used if mvd_autotrack = 1 and a 2on2 game is played. Its an algorithm for selecting the best player.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"For a full description read the manual.\",\n      \"type\": \"string\"\n    },\n    \"mvd_autotrack_2on2_values\": {\n      \"default\": \"1 2 3 2 3 5 8 8 1 2 3 500 900 1000\",\n      \"desc\": \"Allows you to customize autotrack algorithm. See autotrack manual page for more info.\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_autotrack_4on4\": {\n      \"default\": \"%a * %A + 50 * %W + %p + %f\",\n      \"desc\": \"Will be used if mvd_autotrack = 1 and a 4on4 game is played. Its an algorithm for selecting the best player.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"For a full description read the manual.\",\n      \"type\": \"string\"\n    },\n    \"mvd_autotrack_4on4_values\": {\n      \"default\": \"1 2 4 2 4 6 10 10 1 2 3 500 900 1000\",\n      \"desc\": \"Allows you to customize autotrack algorithm. See autotrack manual page for more info.\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_autotrack_custom\": {\n      \"default\": \"%a * %A + 50 * %W + %p + %f\",\n      \"desc\": \"Will be used if mvd_autotrack = 2.\\n Its an algorithm for selecting the best player.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"For a full description read the manual.\",\n      \"type\": \"string\"\n    },\n    \"mvd_autotrack_custom_values\": {\n      \"default\": \"1 2 3 2 3 4 6 6 1 2 3 500 900 1000\",\n      \"desc\": \"Allows you to customize autotrack algorithm. See autotrack manual page for more info.\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_autotrack_instant\": {\n      \"default\": \"0\",\n      \"desc\": \"Makes mvd_autotrack always find the best player and switch to him instantly.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"In KTPro autotrack tracked player is changed only when some events happen. In instant mvd_autotrack tracked player is changed everytime a better player is found.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"mvd_autotrack will work more like ktpro autotrack\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"player change will happen more often\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"mvd_autotrack_lockteam\": {\n      \"default\": \"0\",\n      \"desc\": \"If set to 1, autotrack will keep switching POVs only from players within the same team.\",\n      \"group-id\": \"20\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Autotrack will switch between all players.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Autotrack will stay within one team.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"mvd_info\": {\n      \"default\": \"0\",\n      \"desc\": \"When watching Multi View Demo (.mvd) you can show a table on the screen with full info about players' status.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"See mvd_info_setup for setting up displayed informations.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Turned OFF\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Turned ON\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"mvd_info_setup\": {\n      \"default\": \"%p%n \\u0010%l\\u0011 %h/%a %w\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_info_show_header\": {\n      \"default\": \"0\",\n      \"desc\": \"Will show a line above the mvd_info table telling you wich column is armor/health/location etc.\",\n      \"group-id\": \"20\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Turn Off\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Turned ON\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"mvd_info_x\": {\n      \"default\": \"0\",\n      \"desc\": \"You can adjust horizontal placement of Players' info table.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"See scr_mvdinfo and scr_mvdinfo_setup description for more details.\",\n      \"type\": \"float\"\n    },\n    \"mvd_info_y\": {\n      \"default\": \"0\",\n      \"desc\": \"You can adjust vertical placement of Players' info table.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"See scr_mvdinfo and scr_mvdinfo_setup description for more details.\",\n      \"type\": \"float\"\n    },\n    \"mvd_moreinfo\": {\n      \"default\": \"0\",\n      \"desc\": \"When playing MultiView Demo (MVD), you can turn on more messages printed in the console, like those you might know from moreinfo command in ktpro.\\nE.g.: Nabbe picked up quad.\",\n      \"group-id\": \"20\",\n      \"type\": \"boolean\"\n    },\n    \"mvd_multitrack_1\": {\n      \"default\": \"%f\",\n      \"desc\": \"Will be used if mvd_autotrack = 3 and cl_multiview is enabled.\\nIts an algorithm for selecting the best player on viewport 1.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"For a full description read the manual.\",\n      \"type\": \"string\"\n    },\n    \"mvd_multitrack_1_values\": {\n      \"default\": \"1 2 3 2 3 5 8 8 1 2 3 0 0 0\",\n      \"desc\": \"Allows you to customize autotrack algorithm. See autotrack manual page for more info.\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_multitrack_2\": {\n      \"default\": \"%W\",\n      \"desc\": \"Will be used if mvd_autotrack = 3 and cl_multiview is enabled.\\nIts an algorithm for selecting the best player on viewport 2.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"For a full description read the manual.\",\n      \"type\": \"string\"\n    },\n    \"mvd_multitrack_2_values\": {\n      \"default\": \"1 2 3 2 3 5 8 8 1 2 3 0 0 0\",\n      \"desc\": \"Allows you to customize autotrack algorithm. See autotrack manual page for more info.\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_multitrack_3\": {\n      \"default\": \"%h\",\n      \"desc\": \"Will be used if mvd_autotrack = 3 and cl_multiview is enabled.\\nIts an algorithm for selecting the best player on viewport 3.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"For a full description read the manual.\",\n      \"type\": \"string\"\n    },\n    \"mvd_multitrack_3_values\": {\n      \"default\": \"1 2 3 2 3 5 8 8 1 2 3 0 0 0\",\n      \"desc\": \"Allows you to customize autotrack algorithm. See autotrack manual page for more info.\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_multitrack_4\": {\n      \"default\": \"%A\",\n      \"desc\": \"Will be used if mvd_autotrack = 3 and cl_multiview is enabled.\\nIts an algorithm for selecting the best player on viewport 4.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"For a full description read the manual.\",\n      \"type\": \"string\"\n    },\n    \"mvd_multitrack_4_values\": {\n      \"default\": \"1 2 3 2 3 5 8 8 1 2 3 0 0 0\",\n      \"desc\": \"Allows you to customize autotrack algorithm. See autotrack manual page for more info.\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_pc_pent_1\": {\n      \"default\": \"\",\n      \"desc\": \"Describes the position and viewing angles of the p1 cam.\\nx y z rot_x rot_y\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_pc_pent_2\": {\n      \"default\": \"\",\n      \"desc\": \"Describes the position and viewing angles of the p2 cam.\\nx y z rot_x rot_y\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_pc_pent_3\": {\n      \"default\": \"\",\n      \"desc\": \"Describes the position and viewing angles of the p3 cam.\\nx y z rot_x rot_y\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_pc_quad_1\": {\n      \"default\": \"\",\n      \"desc\": \"Describes the position and viewing angles of the q1 cam.\\nx y z rot_x rot_y\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_pc_quad_2\": {\n      \"default\": \"\",\n      \"desc\": \"Describes the position and viewing angles of the q2 cam.\\nx y z rot_x rot_y\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_pc_quad_3\": {\n      \"default\": \"\",\n      \"desc\": \"Describes the position and viewing angles of the q3 cam.\\nx y z rot_x rot_y\",\n      \"group-id\": \"20\",\n      \"type\": \"string\"\n    },\n    \"mvd_pc_view_1\": {\n      \"default\": \"\",\n      \"desc\": \"Sets the powerup camera for viewport 1.\",\n      \"group-id\": \"20\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"(disabled)\",\n          \"name\": \"\"\n        },\n        {\n          \"description\": \"mvd_pc_pent_1\",\n          \"name\": \"p1\"\n        },\n        {\n          \"description\": \"mvd_pc_pent_2\",\n          \"name\": \"p2\"\n        },\n        {\n          \"description\": \"mvd_pc_pent_3\",\n          \"name\": \"p3\"\n        },\n        {\n          \"description\": \"mvd_pc_quad_1\",\n          \"name\": \"q1\"\n        },\n        {\n          \"description\": \"mvd_pc_quad_2\",\n          \"name\": \"q2\"\n        },\n        {\n          \"description\": \"mvd_pc_quad_3\",\n          \"name\": \"q3\"\n        }\n      ]\n    },\n    \"mvd_pc_view_2\": {\n      \"default\": \"\",\n      \"desc\": \"Sets the powerup camera for viewport 2.\",\n      \"group-id\": \"20\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"(disabled)\",\n          \"name\": \"\"\n        },\n        {\n          \"description\": \"mvd_pc_pent_1\",\n          \"name\": \"p1\"\n        },\n        {\n          \"description\": \"mvd_pc_pent_2\",\n          \"name\": \"p2\"\n        },\n        {\n          \"description\": \"mvd_pc_pent_3\",\n          \"name\": \"p3\"\n        },\n        {\n          \"description\": \"mvd_pc_quad_1\",\n          \"name\": \"q1\"\n        },\n        {\n          \"description\": \"mvd_pc_quad_2\",\n          \"name\": \"q2\"\n        },\n        {\n          \"description\": \"mvd_pc_quad_3\",\n          \"name\": \"q3\"\n        }\n      ]\n    },\n    \"mvd_pc_view_3\": {\n      \"default\": \"\",\n      \"desc\": \"Sets the powerup camera for viewport 3.\",\n      \"group-id\": \"20\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"(disabled)\",\n          \"name\": \"\"\n        },\n        {\n          \"description\": \"mvd_pc_pent_1\",\n          \"name\": \"p1\"\n        },\n        {\n          \"description\": \"mvd_pc_pent_2\",\n          \"name\": \"p2\"\n        },\n        {\n          \"description\": \"mvd_pc_pent_3\",\n          \"name\": \"p3\"\n        },\n        {\n          \"description\": \"mvd_pc_quad_1\",\n          \"name\": \"q1\"\n        },\n        {\n          \"description\": \"mvd_pc_quad_2\",\n          \"name\": \"q2\"\n        },\n        {\n          \"description\": \"mvd_pc_quad_3\",\n          \"name\": \"q3\"\n        }\n      ]\n    },\n    \"mvd_pc_view_4\": {\n      \"default\": \"\",\n      \"desc\": \"Sets the powerup camera for viewport 4.\",\n      \"group-id\": \"20\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"(disabled)\",\n          \"name\": \"\"\n        },\n        {\n          \"description\": \"mvd_pc_pent_1\",\n          \"name\": \"p1\"\n        },\n        {\n          \"description\": \"mvd_pc_pent_2\",\n          \"name\": \"p2\"\n        },\n        {\n          \"description\": \"mvd_pc_pent_3\",\n          \"name\": \"p3\"\n        },\n        {\n          \"description\": \"mvd_pc_quad_1\",\n          \"name\": \"q1\"\n        },\n        {\n          \"description\": \"mvd_pc_quad_2\",\n          \"name\": \"q2\"\n        },\n        {\n          \"description\": \"mvd_pc_quad_3\",\n          \"name\": \"q3\"\n        }\n      ]\n    },\n    \"mvd_powerup_cam\": {\n      \"default\": \"0\",\n      \"desc\": \"Will enable powerupcams, cams will be enabled on every viewport 5 seconds before the powerup spawns.\",\n      \"group-id\": \"20\",\n      \"remarks\": \"For setting up the viewports cams look at mvd_pc_view_1-4.\\nFor setting the locations of the cams look at\\nmvd_pc_quad_1-3 and mvd_pc_pent_1-3.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Turn OFF\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Turned ON\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"mvd_sortitems\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"mvd_status\": {\n      \"default\": \"0\",\n      \"desc\": \"Shows information of the player you are currently tracking.\\n\\nInformation includes:\\n - Taken/dropped items and powerups.\\n - Last 3 runs (including the current one)\\nRun is from powerup-took/spawn to powerup-end/death.\",\n      \"group-id\": \"20\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Turned OFF\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Turned ON\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"mvd_status_x\": {\n      \"default\": \"0\",\n      \"desc\": \"Adjusts the horizontal placement of mvd_status table.\",\n      \"group-id\": \"20\",\n      \"type\": \"float\"\n    },\n    \"mvd_status_y\": {\n      \"default\": \"0\",\n      \"desc\": \"Adjusts the vertical placement of mvd_status table.\",\n      \"group-id\": \"20\",\n      \"type\": \"float\"\n    },\n    \"mvd_write_xml\": {\n      \"desc\": \"Enables dumping of XML stats from a MVD.\",\n      \"group-id\": \"24\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"name\": {\n      \"default\": \"player\",\n      \"desc\": \"Player's name.\",\n      \"group-id\": \"37\",\n      \"type\": \"string\"\n    },\n    \"net_tcp_timeout\": {\n      \"default\": \"2000\",\n      \"desc\": \"Timeout (in ms) when making TCP connections (such as connecting to QTV)\",\n      \"group-id\": \"21\",\n      \"remarks\": \"Range 500 to 5000.\",\n      \"type\": \"integer\"\n    },\n    \"noaim\": {\n      \"default\": \"1\",\n      \"desc\": \"This variable toggles whether a server-sided aiming-help should be used when shooting rockets (not possible when the server variable \\\"sv_aim\\\" is set to \\\"0\\\").\",\n      \"group-id\": \"37\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Enable server-side aiming help for rockets (if allowed by the server).\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Disable server-side aiming help for rockets.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"noskins\": {\n      \"default\": \"0\",\n      \"group-id\": \"44\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Enable skins.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Disable skins.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Enable skins but do not download new skins.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"not_auth_timeout\": {\n      \"desc\": \"Not currently used.\",\n      \"group-id\": \"43\",\n      \"type\": \"integer\"\n    },\n    \"password\": {\n      \"default\": \"\",\n      \"desc\": \"Password players have to use to connect to local server.\",\n      \"group-id\": \"43\",\n      \"remarks\": \"Server-side.\",\n      \"type\": \"string\"\n    },\n    \"pausable\": {\n      \"default\": \"0\",\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable pause.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable pause.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"pext_ezquake_verfortrans\": {\n      \"default\": \"7814\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"pm_airstep\": {\n      \"default\": \"\",\n      \"desc\": \"Airstep player-move-extension.\\nChanges the physics of the game and allows to do jumps on stairs.\",\n      \"group-id\": \"43\",\n      \"remarks\": \"Server-side.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"pm_bunnyspeedcap\": {\n      \"default\": \"\",\n      \"desc\": \"This is experimental code intended to restore class balance in TF.\\nIf you set this cvar to something like 1.5 or 2, a solider will not be able to bunnyhop as fast as a scout does :)\\nNote that this cvar is optional and disabled by default, so it will in NO WAY affect gameplay unless you set it explicitly to anything but 0.\",\n      \"group-id\": \"34\",\n      \"type\": \"float\"\n    },\n    \"pm_ktjump\": {\n      \"default\": \"1\",\n      \"desc\": \"If enabled (the standard), when the player jumps, velocity will always be at least 270 (standard jump).\\nWithout this set, jumping when moving down a slope would result in lower velocity.\",\n      \"group-id\": \"34\",\n      \"type\": \"float\"\n    },\n    \"pm_pground\": {\n      \"default\": \"\",\n      \"desc\": \"Enables 'persistent onground flag' physics tweak.\\nIts only use is that it makes pm_airstep work better.\",\n      \"group-id\": \"34\",\n      \"remarks\": \"This extension makes use of Z_EXT_PF_ONGROUND to ensure correct movement prediction by clients. Prediction errors shouldn't be very noticeable anyway even with old clients.\",\n      \"type\": \"integer\"\n    },\n    \"pm_rampjump\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"pm_slidefix\": {\n      \"default\": \"\",\n      \"desc\": \"When set to 1, Upon decsension from a sloped surface, the player doesn't \\\"drop\\\" (which is a bug in QW), but smoothly slides, as it was in NQ.\",\n      \"group-id\": \"34\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"qconsole_log_say\": {\n      \"default\": \"0\",\n      \"desc\": \"Log chat messages into the main server console log.\",\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"qizmo_dir\": {\n      \"default\": \"qizmo\",\n      \"desc\": \"Change the default qizmo directory.\",\n      \"group-id\": \"7\",\n      \"type\": \"string\"\n    },\n    \"qport\": {\n      \"default\": \"0\",\n      \"desc\": \"Local port the client uses to connect to servers.\",\n      \"group-id\": \"2\",\n      \"type\": \"integer\"\n    },\n    \"qtv_adjustbuffer\": {\n      \"default\": \"1\",\n      \"desc\": \"Enables balancing of the buffer length of the QTV stream.\\nWhen turned on, the size of the stream buffer (the delay from the actual action) will be auto-adjusted (by changing the playback speed when necessary) so that it stays on the same level most of the time.\",\n      \"group-id\": \"38\",\n      \"remarks\": \"When turned on, the speed of the playback may change sometimes - that's how the buffer length is balanced. But usually you want to have this turned on when you are watching a shoutcast-commentated game because you want to stay synchronized with the commentary.\\nSee qtv_buffer hud element for monitoring.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not adjust buffer size, always play at 100% speed\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Auto-adjust buffer size, to stay within % of total buffer length (see /qtv_buffertime, /qtv_adjustlowstart, /qtv_adjusthighstart)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Auto-adjust buffer size, to stay close to total buffer length (see /qtv_buffertime)\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"qtv_adjusthighstart\": {\n      \"default\": \"1\",\n      \"desc\": \"The level on which QTV buffer auto-adjusting will start.\\nE.g. when set to 1.5, (and qtv_adjustbuffer is 1), QTV buffer auto-adjusting will start when buffer length reached 150% of it's normal length (determined by qtv_buffertime setting)\",\n      \"group-id\": \"38\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Only values higher than 1.0 make sense\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"qtv_adjustlowstart\": {\n      \"default\": \"0.3\",\n      \"desc\": \"The bottom level on which QTV buffer auto-adjusting will start.\\nE.g. when set to 0.5, (and qtv_adjustbuffer is 1), QTV buffer auto-adjusting will start when buffer length reached 50% of it's normal length (determined by qtv_buffertime setting)\",\n      \"group-id\": \"38\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Only values lower than 1.0 make sense\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"qtv_adjustmaxspeed\": {\n      \"default\": \"999\",\n      \"desc\": \"The fastest possible playback speed when QTV buffer becomes excessive and auto-adjusting starts.\\nThe higher the speed is, the faster the QTV buffer length will go down to normal levels.\",\n      \"group-id\": \"38\",\n      \"remarks\": \"Works in conjunction with qtv_adjustbuffer 1.\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Only values above 1.0 make sense\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"qtv_adjustminspeed\": {\n      \"default\": \"0\",\n      \"desc\": \"The slowest possible playback speed when QTV buffer adjusting takes action.\\nThe slower the speed is, the faster the QTV buffer will fill up.\",\n      \"group-id\": \"38\",\n      \"remarks\": \"Not allowing to slow down enough, buffer level might still drop down to zero if there were longer network lag. This causes pausing during playback. Allowing to slow down too much is noticeable.\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"0.5 means 50% of the normal playback speed\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"qtv_allow_pause\": {\n      \"default\": \"0\",\n      \"desc\": \"Allows QTV streams to be affected by cl_demospeed.\",\n      \"group-id\": \"38\",\n      \"type\": \"boolean\"\n    },\n    \"qtv_api_url\": {\n      \"default\": \"http://qtvapi.quakeworld.nu/api/v1/servers\",\n      \"desc\": \"URL used by /qtv command to resolve servers to QTV addresses and vice versa.\",\n      \"group-id\": \"38\",\n      \"type\": \"string\"\n    },\n    \"qtv_buffertime\": {\n      \"default\": \"0.5\",\n      \"desc\": \"Defines how much the client buffers from the QTV stream (in seconds).\",\n      \"group-id\": \"38\",\n      \"remarks\": \"This determines the \\\"delay\\\" you will get from what is actually happening. For example if you want to synchronize shoutcast commentary with the QTV stream, this is the variable you need to change.\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range 0.1 to 30.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"qtv_chatprefix\": {\n      \"default\": \"$[{QTV}$] \",\n      \"desc\": \"String that will be added at the beginning of all messages send to a QTV chat.\",\n      \"group-id\": \"38\",\n      \"type\": \"string\"\n    },\n    \"qtv_event_changename\": {\n      \"default\": \" &cFF0changed name to&r \",\n      \"desc\": \"Text pattern used when an user changes his name on a QTV stream.\",\n      \"group-id\": \"38\",\n      \"type\": \"string\"\n    },\n    \"qtv_event_join\": {\n      \"default\": \" &c2F2joined&r\",\n      \"desc\": \"Text pattern used when an user joins a QTV stream.\",\n      \"group-id\": \"38\",\n      \"type\": \"string\"\n    },\n    \"qtv_event_leave\": {\n      \"default\": \" &cF22left&r\",\n      \"desc\": \"Text pattern used when an user leaves a QTV stream.\",\n      \"group-id\": \"38\",\n      \"type\": \"string\"\n    },\n    \"qtv_event_msglevel\": {\n      \"default\": \"2\",\n      \"desc\": \"Controls the visibility of QTV event messages, allowing them to be enabled, disabled, or hidden during an active match.\",\n      \"group-id\": \"38\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disables QTV event messages.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Enables QTV event messages.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Hides QTV event messages when a match has started.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"qtv_gamechatprefix\": {\n      \"default\": \"$[{QTV>game}$] \",\n      \"desc\": \"String that will be added at the beginning of all messages send from QTV to the server where the broadcasted game is being played.\",\n      \"group-id\": \"38\",\n      \"type\": \"string\"\n    },\n    \"qtv_maxstreams\": {\n      \"default\": \"1\",\n      \"desc\": \"Maximum number of simultaneous QTV connections.\",\n      \"group-id\": \"43\",\n      \"type\": \"integer\"\n    },\n    \"qtv_password\": {\n      \"default\": \"\",\n      \"desc\": \"Password required for QTV to connect to the streamport.\",\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"qtv_pendingtimeout\": {\n      \"default\": \"5\",\n      \"desc\": \"Number of seconds to wait before timing out a pending QTV connection.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"qtv_prebuffertime\": {\n      \"default\": \"0\",\n      \"desc\": \"Number of seconds to buffer before starting playback from QTV.\",\n      \"group-id\": \"38\",\n      \"remarks\": \"See /qtv_buffertime.\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Use qtv_buffertime for prebuffer.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Range 0.1 to 10.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"qtv_say_team\": {\n      \"default\": \"0\",\n      \"group-id\": \"38\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"say_team will be blocked when sending to QTV.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"say_team allowed as normal on QTV.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"qtv_sayenabled\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qtv_skipchained\": {\n      \"default\": \"1\",\n      \"group-id\": \"38\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"QTV chat messages will have prefix detailing userIDs and proxy addresses\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"QTV chat messages will be in simpler form <name>: <message>\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"qtv_streamport\": {\n      \"default\": \"0\",\n      \"desc\": \"Server variable, TCP port on which the server will listen for QTV connections.\",\n      \"group-id\": \"43\",\n      \"type\": \"integer\"\n    },\n    \"qtv_streamtimeout\": {\n      \"default\": \"45\",\n      \"desc\": \"Number of seconds to wait before timing out a QTV stream.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"qwdtools_dir\": {\n      \"default\": \"qwdtools\",\n      \"desc\": \"Specifies the qwdtools utility placement.\",\n      \"group-id\": \"7\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Use path relative to your quakedir\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"qwm_builddate\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qwm_buildnum\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qwm_fullname\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qwm_homepage\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qwm_name\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qwm_platform\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qwm_version\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qws_builddate\": {\n      \"default\": \"Jun 10 2025, 22:10:07\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qws_buildnum\": {\n      \"default\": \"unknown\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qws_fullname\": {\n      \"default\": \"MVDSV: MultiView Demo SerVer\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qws_homepage\": {\n      \"default\": \"https://mvdsv.deurk.net\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qws_name\": {\n      \"default\": \"EZQUAKE\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qws_platform\": {\n      \"default\": \"w\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"qws_version\": {\n      \"default\": \"0.34-beta\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"r_aliastransadj\": {\n      \"desc\": \"Not much is known about this variable except for that increasing the value will cause the player models to be displayed smaller.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"r_aliastransbase\": {\n      \"desc\": \"Not much is known about this variable except for that increasing the value will cause the player models to be displayed smaller.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"r_ambient\": {\n      \"desc\": \"This variable controls the amount of ambient lighting on the map.\\nWhen this variable is increased the map will become brighter and all walls and objects will be rendered in a lighter color.\\nIt was removed in QWCL and has been re-introduced in this client, however it can only be used when the server is running with cheats enabled (for example \\\"qwsv -cheats\\\"), because it could be used as a cheat by making it easier to see players in dark corners when no fullbright skins are used.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"r_bloom\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables lights blooming.\",\n      \"group-id\": \"8\",\n      \"remarks\": \"Lights will have a \\\"diamond\\\" effect near them.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_bloom_alpha\": {\n      \"default\": \"0.5\",\n      \"desc\": \"Transparency level of the blooming effect.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"r_bloom_darken\": {\n      \"default\": \"3\",\n      \"desc\": \"Level of \\\"darkness\\\" of the blooming effect.\",\n      \"group-id\": \"8\",\n      \"type\": \"integer\"\n    },\n    \"r_bloom_diamond_size\": {\n      \"default\": \"8\",\n      \"desc\": \"Size of the diamond particle used in the blooming effect.\",\n      \"group-id\": \"8\",\n      \"type\": \"integer\"\n    },\n    \"r_bloom_fast_sample\": {\n      \"default\": \"0\",\n      \"desc\": \"Faster variant of the blooming effect.\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_bloom_intensity\": {\n      \"default\": \"1\",\n      \"desc\": \"Intensity of the blooming effect.\",\n      \"group-id\": \"8\",\n      \"type\": \"integer\"\n    },\n    \"r_bloom_sample_size\": {\n      \"default\": \"256\",\n      \"desc\": \"Size of the particle used in the blooming effect.\",\n      \"group-id\": \"8\",\n      \"type\": \"integer\"\n    },\n    \"r_chaticons_alpha\": {\n      \"default\": \"0.8\",\n      \"desc\": \"Opacity of chaticons.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"0 mean no icons at all, 0.5 mean half opacity and 1 is opaque.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_clearcolor\": {\n      \"desc\": \"This variable changes the color of the areas outside the map, commonly seen when free floating as a spectator.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"r_damagestats\": {\n      \"default\": \"0\",\n      \"desc\": \"Displays the amount of damage taken recently on the screen above your armour and health.\",\n      \"group-id\": \"47\",\n      \"type\": \"float\"\n    },\n    \"r_drawdisc\": {\n      \"default\": \"1\",\n      \"desc\": \"Displays an indicator in top-right of screen when disk IO is in progress.\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\"\n    },\n    \"r_drawentities\": {\n      \"default\": \"1\",\n      \"desc\": \"This variable can be used to disable that any object entities are drawn.\\nThis command is useful to map authors who wish to take a look at their map without drawing any objects such as lifts, buttons, platforms or items.\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Display no entities.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display all entities.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_drawflame\": {\n      \"default\": \"1\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Display no flames.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display all flames.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_drawflat\": {\n      \"default\": \"0\",\n      \"desc\": \"Replaces wall/floor textures with a solid color (/r_wallcolor or /r_floorcolor, depending on the angle of the surface).\",\n      \"group-id\": \"50\",\n      \"remarks\": \"Usually used for performance improvements at the expense of eyecandy. See /r_wallcolor, /r_floorcolor & /r_drawflat_mode.\\nCan not be changed during a match.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Use textures for walls and floors.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Use custom colored walls and floors.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Use colored floors and textured walls.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Use colored walls and textured floors.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"r_drawflat_mode\": {\n      \"default\": \"0\",\n      \"desc\": \"Sets the method used to adjust wall & floor textures (/r_drawflat) by the corresponding color (/r_wallcolor & /r_floorcolor)\",\n      \"group-id\": \"50\",\n      \"remarks\": \"Works in GLSL modes only (see /gl_program_world).\\nMode 0 will give best performance, mode 2 is the recommended setting for different colors.\\nCan not be changed during a match.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Walls/floors will be solid color, as per /r_drawflat setting.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Walls/floors will be textured and blended with custom colors.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Walls/floors will be monochrome textures using custom colors.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"r_drawhud\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"r_drawparticles\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles drawing particle effects.\\nIntended for helping isolate rendering performance issues.\",\n      \"group-id\": \"8\",\n      \"remarks\": \"Limited by particle ruleset.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Hide particles\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Draw particles\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"r_drawviewmodel\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls transparency level of the currently-held weapon.\",\n      \"group-id\": \"54\",\n      \"remarks\": \"Example: 0.5 = 50% transparency.\\nr_drawviewmodel 2 will cause weapon to be hidden when fov > 90.\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Fully transparent.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Fully opaque.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"r_drawviewmodel_invisible\": {\n      \"default\": \"0\",\n      \"desc\": \"This command controls whether or not to draw the weapon when invisible. It needs to be used in conjunction with r_drawviewmodel > 0.\",\n      \"group-id\": \"54\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't display the weapon when invisible.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Display the weapon when invisible.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"r_drawvweps\": {\n      \"default\": \"1\",\n      \"desc\": \"Whether to draw vweps (other players' weapon models).\",\n      \"group-id\": \"8\",\n      \"remarks\": \"In order for vweps to work, you must have the appropriate models (vwplayer.mdl and w_*.mdl), and the server you're playing on must have vwep support enabled.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_drawworld\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"r_dspeeds\": {\n      \"group-id\": \"31\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not display renderer speed statistics.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display renderer speed statistics.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_dynamic\": {\n      \"default\": \"2\",\n      \"desc\": \"Controls dynamic lighting (muzzle-flash, quad & rocket glow, etc) on world surfaces.\",\n      \"group-id\": \"15\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not display dynamic lighting.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Display dynamic lighting (calculated on CPU)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Display dynamic lighting (calculated on GPU - GLSL only)\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"r_enemyskincolor\": {\n      \"default\": \"\",\n      \"desc\": \"Allows you to set color for enemies you see in RGB format.\",\n      \"group-id\": \"44\",\n      \"remarks\": \"See r_skincolormode for more info.\",\n      \"type\": \"string\"\n    },\n    \"r_explosionLight\": {\n      \"default\": \"1\",\n      \"desc\": \"Size of the light effect around explosions.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"No explosion light.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"50% explosion light.\",\n          \"name\": \"0.5\"\n        },\n        {\n          \"description\": \"100% explosion light.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"r_explosionLightColor\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines the color of the explosion light.\",\n      \"group-id\": \"8\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Normal (light yellow)\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Red\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Blue\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Purple\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Green\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Yellow\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Cyan\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"White\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Random\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"r_explosionType\": {\n      \"default\": \"1\",\n      \"desc\": \"Sets the type of effect used for explosions.\",\n      \"group-id\": \"8\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Fire + sparks\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Fire only\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Teleport\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Blood\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Big blood\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Double gunshot\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Blob effect\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Big explosion\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Fuel Rod Gun explosion\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Sparks\",\n          \"name\": \"9\"\n        },\n        {\n          \"description\": \"None\",\n          \"name\": \"10\"\n        }\n      ]\n    },\n    \"r_farclip\": {\n      \"default\": \"8192\",\n      \"desc\": \"Can be used to overcome vision being limited to 4096 units in GL clients (good for xpress2 etc).\",\n      \"group-id\": \"35\",\n      \"remarks\": \"See also: r_nearclip.\",\n      \"type\": \"float\"\n    },\n    \"r_fastsky\": {\n      \"default\": \"0\",\n      \"group-id\": \"51\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable fastsky.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable fastsky, helps fps a bit on maps with large sky areas.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_fastturb\": {\n      \"default\": \"0\",\n      \"group-id\": \"51\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"All turbulences will be displayed normally.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"All turbulences will be replaced by single-colored surfaces.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_flagColor\": {\n      \"default\": \"0\",\n      \"group-id\": \"8\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Normal\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Red\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Blue\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Purple\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Green\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"White\",\n          \"name\": \"5\"\n        }\n      ]\n    },\n    \"r_floorcolor\": {\n      \"default\": \"50 100 150\",\n      \"desc\": \"Changes color of floors and ceilings when r_drawflat is set to 1.\\nEnter RGB value here, e.g. r_floorcolor \\\"128 128 128\\\" goes for gray floor.\",\n      \"group-id\": \"50\",\n      \"remarks\": \"Can not be changed during a match.\",\n      \"type\": \"string\"\n    },\n    \"r_fullbright\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable toggles the use of lights at full brightness on the map.\\nThis allows map authors to remove all of the lighting effects from the map, effectively removing all shadows.\",\n      \"group-id\": \"15\",\n      \"remarks\": \"This variable can only be used when the server is running with cheats enabled (for example \\\"mvdsv -cheats\\\" or sv_cheats 1), because it could be used as a cheat by making it easier to see players in shadows.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not fullbright the map.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Fullbrights the map (need -cheats or sv_cheats 1 enabled in server)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_fullbrightSkins\": {\n      \"default\": \"1\",\n      \"desc\": \"Determines the fullbright percentage of skins.\\nFullbright skins can always be used during demo playback.\\nThe f_skins response will indicate the brightness level being used as a percentage.\",\n      \"group-id\": \"44\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Being 0% fullbright.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Being 50% fullbright.\",\n          \"name\": \"0.5\"\n        },\n        {\n          \"description\": \"Being 100% fullbright.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"r_fx_fog\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables fog.\",\n      \"group-id\": \"51\",\n      \"remarks\": \"See also other r_fx_fog cvars for fine-tuning.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Off\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"On\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"In water/slime/lava only\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"r_fx_fog_color_air\": {\n      \"default\": \"153 128 103\",\n      \"desc\": \"Color of fog when not underwater.\",\n      \"group-id\": \"51\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"R G B, each 0-255\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_fx_fog_color_lava\": {\n      \"default\": \"255 64 0\",\n      \"desc\": \"Color of fog in lava.\",\n      \"group-id\": \"51\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"R G B, each 0-255\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_fx_fog_color_slime\": {\n      \"default\": \"128 255 0\",\n      \"desc\": \"Color of fog in slime.\",\n      \"group-id\": \"51\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"R G B, each 0-255\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_fx_fog_color_water\": {\n      \"default\": \"32 64 128\",\n      \"desc\": \"Color of fog in water.\",\n      \"group-id\": \"51\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"R G B, each 0-255\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_fx_fog_density\": {\n      \"default\": \"0.125\",\n      \"desc\": \"Sets the density of the fog when r_fx_fog_model is an exponential model (1 or 2)\",\n      \"group-id\": \"51\",\n      \"type\": \"float\"\n    },\n    \"r_fx_fog_end\": {\n      \"default\": \"800.0\",\n      \"desc\": \"Sets ending distance for rendering the fog when r_fx_fog_model is 0 (linear).\",\n      \"group-id\": \"51\",\n      \"type\": \"float\"\n    },\n    \"r_fx_fog_model\": {\n      \"default\": \"2\",\n      \"desc\": \"Sets the fog rendering model.\",\n      \"group-id\": \"51\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Linear - range is r_fx_fog_start/r_fx_fog_end\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Exponential - density r_fx_fog_density\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Exponential squared - density r_fx_fog_density.  Used when map fog is enabled\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"r_fx_fog_sky\": {\n      \"default\": \"0.3\",\n      \"desc\": \"Percentage of shading effect to apply to sky textures\",\n      \"group-id\": \"51\",\n      \"type\": \"float\"\n    },\n    \"r_fx_fog_start\": {\n      \"default\": \"50.0\",\n      \"desc\": \"Sets starting distance for rendering the fog when r_fx_fog_model is 0 (linear).\\nThe greater the number is, the better visibility in game is and the fog is \\\"thinner\\\".\",\n      \"group-id\": \"51\",\n      \"type\": \"float\"\n    },\n    \"r_fx_fog_usemap\": {\n      \"default\": \"2\",\n      \"desc\": \"When to use fog settings from the map, rather than config.\",\n      \"group-id\": \"51\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Never use map-specific settings\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Always use map-specific settings, if specified\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Only use map-specific settings in local single-player mode\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"r_glstats\": {\n      \"default\": \"0\",\n      \"desc\": \"When enabled, it creates a window in the top right of the screen showing the number of particles and etc. in use.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"r_graphheight\": {\n      \"desc\": \"This variable used to set the number of lines displayed in the \\\"r_timegraph\\\" command.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"r_grenadeTrail\": {\n      \"default\": \"1\",\n      \"desc\": \"Customizable grenade trails.\",\n      \"group-id\": \"8\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No grenade trail.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Smoke (grenade) trail.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Normal (rocket) trail.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Alternative normal trail.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Slight blood trail.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Big blood trail.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Green Tracer trail.\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Orange/Yellow Tracer trail.\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Blue/White Plasma trail.\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Lavaball trail.\",\n          \"name\": \"9\"\n        },\n        {\n          \"description\": \"Green Fuel Rod Gun trail.\",\n          \"name\": \"10\"\n        },\n        {\n          \"description\": \"Orange/Yellow Plasma Rocket Mk II trail.\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"Similar to 3, but with a white line and less smoke.\",\n          \"name\": \"12\"\n        }\n      ]\n    },\n    \"r_instagibTrail\": {\n      \"default\": \"1\",\n      \"desc\": \"Type of trail to use when viewing trails left in /instagib mode.\",\n      \"group-id\": \"8\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No railtrail.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Normal grenade trail.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Normal rocket trail.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Alternative rocket trail.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Slight blood trail.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Big blood trail.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Green Tracer trail.\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Orange/Yellow Tracer trail.\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Vore trail.\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Classic railtrail.\",\n          \"name\": \"9\"\n        },\n        {\n          \"description\": \"New railtrail.\",\n          \"name\": \"10\"\n        },\n        {\n          \"description\": \"Particle railtrail.\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"Lava trail\",\n          \"name\": \"12\"\n        },\n        {\n          \"description\": \"Particle rocket trail\",\n          \"name\": \"13\"\n        }\n      ]\n    },\n    \"r_lavacolor\": {\n      \"default\": \"80 0 0\",\n      \"desc\": \"Changes color of lava when r_fastturb set to 1.\",\n      \"group-id\": \"51\",\n      \"type\": \"string\"\n    },\n    \"r_lerpframes\": {\n      \"default\": \"1\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable smoothing.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable smoothing (watch people moving in mvd demos with cl_demospeed 0.1).\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_lerpmuzzlehack\": {\n      \"default\": \"1\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"No fix for interpolated muzzle flashes.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Fix for interpolated muzzle flashes.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_lgbloodColor\": {\n      \"default\": \"225\",\n      \"desc\": \"Determines the color of the blood particles emitted when hitting entities with the lightning gun.\",\n      \"group-id\": \"8\",\n      \"type\": \"integer\"\n    },\n    \"r_lightdecayrate\": {\n      \"default\": \"2\",\n      \"desc\": \"Determines how fast lights decay (radius decreases).\\nMinimum value 1 (QWCL)\",\n      \"group-id\": \"8\",\n      \"type\": \"float\"\n    },\n    \"r_lightflicker\": {\n      \"default\": \"1\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable rocket/explosion/flag/powerup light flickering.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable rocket/explosion/flag/powerup light flickering.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_lightmap_lateupload\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"r_lightmap_packbytexture\": {\n      \"default\": \"2\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"r_max_size_1\": {\n      \"group-id\": \"31\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Off.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Blend map textures.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Blend map textures and bsp entities (ammo, health etc.)\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"r_maxedges\": {\n      \"desc\": \"This variable sets the maximum number of plane surface edges to be rendered.\\nActual default (and minimal) value is 2000.\\nOn some new maps you may need to increase this value.\\nSee also r_maxsurfs, r_reportedgeout, r_reportsurfout.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"r_maxsurfs\": {\n      \"desc\": \"This variable sets the maximum number of plane surfaces to be rendered.\\nActual default (and minimal) value is 2000.\\nOn some new maps you may need to increase this value.\\nSee also r_maxedges, r_reportedgeout, r_reportsurfout.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"r_nearclip\": {\n      \"default\": \"2\",\n      \"desc\": \"Distance of clipping nearest objects (v_models).\",\n      \"group-id\": \"53\",\n      \"remarks\": \"See also: r_farclip.\",\n      \"type\": \"float\"\n    },\n    \"r_netgraph\": {\n      \"default\": \"0\",\n      \"desc\": \"Netgraph is a diagnostic tool to help you tweak your rate.\\nIf you find that you suffer from short pauses in the game and you see red spikes in your netgraph, you should try setting the rate down a bit.\\nThe height of the pink lines towards the bottom is your latency on received packets.\\nRed lines are lost packets. (bad)\\nYellow lines are from the rate command kicking in, it's data that wasn't sent to you because you didn't have the bandwidth for it. (OK)\\nBlue lines are very bad, they invalid delta's, and are related to the U_REMOVE warnings.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"If during your play, you frequently see a string of messages with the term \\\"U_REMOVE\\\" in them, and your play seems to freeze for serveral seconds, use the console command cl_nodelta 1. This is a slightly less efficient way for QuakeWorld to work, but if your ISP is overloaded or has some configuration problems, it may not pass packets to QuakeWorld properly and cause difficulty.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not display the netgraph.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display the netgraph.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_netstats\": {\n      \"default\": \"0\",\n      \"desc\": \"Shows information about ping, packetloss, average packet size and incoming/outgoing traffic.\\nEquivalent with 'net' HUD element.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw network statistics.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw network statistics.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_novis\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable toggles the use of VIS information from the map data.\\nWhen this variable is set to \\\"1\\\" the game will calculate it's own VIS information on the fly instead of using the information stored in the map data, this will cause a severe performance penalty in the game.\\nWhen \\\"r_novis\\\" is set to \\\"1\\\", the variable \\\"gl_turbalpha\\\" has a value lower than \\\"1\\\" and the server allows the client to display liquid transparently the player will be able to see through liquids.\\nThis is a nice effect to see but it is not recommended to keep this variable set to \\\"1\\\" because of the huge performance penalty.\\nIt is possible to enable transparent liquids and still keep this variable set to \\\"0\\\" (and thus not experience such a severe performance drop) by using maps that are VISed accordingly.\\nYou can visit the official Water VIS site at \\\"http://www.sod.net/\\\" and download the vispatch program along with the patch data files which you would use to update the VIS data for all of your game maps.\\nThere are also map packs available that contain VISed versions of the original ID maps.\",\n      \"group-id\": \"51\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not calculate VIS information for unVISed maps.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Calculate VIS information for unVISed maps.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_numedges\": {\n      \"group-id\": \"31\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not print information about the number of edges.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Print information about the number of edges.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_numsurfs\": {\n      \"group-id\": \"31\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not print information about the number of surfaces.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Print information about the number of surfaces.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_particles_count\": {\n      \"default\": \"2048\",\n      \"desc\": \"Maximum ammount of particles displayed.\",\n      \"group-id\": \"36\",\n      \"remarks\": \"You can adjust graphics performance with this.\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"1024 or 2048 is quite enough\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_polymodelstats\": {\n      \"group-id\": \"31\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not print polygon model statistics.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Print polygon model statistics.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_powerupGlow\": {\n      \"default\": \"1\",\n      \"group-id\": \"8\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Powerup glow is turned off on all players.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Powerup glow on.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Disables the powerup glow from yourself only.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"r_railTrail\": {\n      \"default\": \"1\",\n      \"desc\": \"Customizable rail trails.\",\n      \"group-id\": \"8\",\n      \"remarks\": \"See /r_instagibTrail for customising /instagib mode in KTX.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No rail trail.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Smoke trail.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Normal (rocket) trail.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Alternative normal trail.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Slight blood trail.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Big blood trail.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Green Tracer trail.\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Orange/Yellow Tracer trail.\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Blue/White Plasma trail.\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Lavaball trail.\",\n          \"name\": \"9\"\n        },\n        {\n          \"description\": \"Green Fuel Rod Gun trail.\",\n          \"name\": \"10\"\n        },\n        {\n          \"description\": \"Orange/Yellow Plasma Rocket Mk II trail.\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"Similar to 3, but with a white line and less smoke.\",\n          \"name\": \"12\"\n        }\n      ]\n    },\n    \"r_remove_collinear_vertices\": {\n      \"default\": \"0\",\n      \"desc\": \"Filter out vertices causing triangles to have zero area. These types of triangles are a sore spot in some GPUs that risk introducing glitches. An expected side effect is a risk of seeing a sparkling pixel.\",\n      \"group-id\": \"35\",\n      \"remarks\": \"For macOS arm64 this is force-enabled to prevent cheating. This filtering is a temporary workaround before adding code to avoid generating such triangles in the first place.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable filtering of collinear vertices.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable filtering of collinear vertices.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_reportedgeout\": {\n      \"group-id\": \"31\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not report when running out of edges.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Report when running out of edges.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_reportsurfout\": {\n      \"desc\": \"Toggle the display of how many surfaces where not displayed.\\nThis shouldn't happen during normal game play, only when in noclip mode.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"r_rlbloodColor_big\": {\n      \"default\": \"73\",\n      \"desc\": \"Determines the color of the blood particles emitted when hitting entities with the rocket launcher. Requires the rocket explosion to be big blood (cl_explosiontype 4).\",\n      \"group-id\": \"8\",\n      \"type\": \"integer\"\n    },\n    \"r_rlbloodColor_small\": {\n      \"default\": \"225\",\n      \"desc\": \"Determines the color of the blood particles emitted when hitting entities with the rocket launcher. Requires the rocket explosion to be small blood (cl_explosiontype 3).\",\n      \"group-id\": \"8\",\n      \"type\": \"integer\"\n    },\n    \"r_rocketLight\": {\n      \"default\": \"1\",\n      \"desc\": \"Enables rocket lights.\\nValues from 0-1 can be used to scale the light.\",\n      \"group-id\": \"8\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Disable dynamic rocket lights.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Enable dynamic rocket lights, maximum scale.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"r_rocketLightColor\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines the color of the rocket lights.\",\n      \"group-id\": \"8\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Normal\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Red\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Blue\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Purple\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Green\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Yellow\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Cyan\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"White\",\n          \"name\": \"7\"\n        }\n      ]\n    },\n    \"r_rocketTrail\": {\n      \"default\": \"1\",\n      \"desc\": \"Customizable rocket trails.\",\n      \"group-id\": \"8\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No rocket trail.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Normal (rocket) trail.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Smoke (grenade) trail.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Alternative normal trail.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Slight blood trail.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Big blood trail.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Green Tracer trail.\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Orange/Yellow Tracer trail.\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Blue/White Plasma trail.\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Lavaball trail.\",\n          \"name\": \"9\"\n        },\n        {\n          \"description\": \"Green Fuel Rod Gun trail.\",\n          \"name\": \"10\"\n        },\n        {\n          \"description\": \"Orange/Yellow Plasma Rocket Mk II trail.\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"Similar to 3, but with a white line and less smoke.\",\n          \"name\": \"12\"\n        }\n      ]\n    },\n    \"r_sgbloodColor\": {\n      \"default\": \"73\",\n      \"desc\": \"Determines the color of the blood particles emitted when hitting entities with weapons other than the lightning gun.\",\n      \"group-id\": \"8\",\n      \"type\": \"integer\"\n    },\n    \"r_shadows\": {\n      \"default\": \"0\",\n      \"group-id\": \"15\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not display shadows for entities.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display shadows for entities.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_shaftalpha\": {\n      \"default\": \"1\",\n      \"desc\": \"Adds transparency to the lightning gun beam (shaft).\",\n      \"group-id\": \"8\",\n      \"remarks\": \"Disabled in ruleset smackdown and smackdrive.\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Values from 0.0 (transparent) to 1.0 (opaque) possible\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_shiftbeam\": {\n      \"default\": \"0\",\n      \"desc\": \"Shift start of the shaft lights.\",\n      \"group-id\": \"8\",\n      \"remarks\": \"Doesn't work with ruleset smackdown and smackdrive. Intended for movies.\",\n      \"type\": \"float\"\n    },\n    \"r_skincolormode\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles different modes of how colors from r_enemyskincolor and r_teamskincolor are applied to players.\",\n      \"group-id\": \"44\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Solid color\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Color doesn't affect skin\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Blends skin texture and color\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Effectively the same as 1\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Adds color to skin\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Hues skin by color\",\n          \"name\": \"5\"\n        }\n      ]\n    },\n    \"r_skincolormodedead\": {\n      \"default\": \"-1\",\n      \"desc\": \"Sets r_skincolormode for players upon death.\",\n      \"group-id\": \"44\",\n      \"remarks\": \"See r_skincolormode for valid values.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not change r_skincolormode when player dies.\",\n          \"name\": \"-1\"\n        }\n      ]\n    },\n    \"r_skycolor\": {\n      \"default\": \"40 80 150\",\n      \"desc\": \"Changes color of sky when r_fastsky set to 1.\",\n      \"group-id\": \"51\",\n      \"type\": \"string\"\n    },\n    \"r_skyname\": {\n      \"default\": \"\",\n      \"desc\": \"Allows the sky to be rendered using an external skybox, rather than using textures from the map\",\n      \"group-id\": \"51\",\n      \"remarks\": \"Specifying a value will disable loading of skyboxes specified by the map.\\nSkyboxes should be placed in 'env' or 'gfx/env' folders\",\n      \"type\": \"string\"\n    },\n    \"r_skywind\": {\n      \"default\": \"1\",\n      \"desc\": \"Sets the scale factor of skybox animation if above 0, and skybox has an animation configuration.\",\n      \"group-id\": \"51\",\n      \"remarks\": \"Specifying a value above 1.0 will increase the pace of the configured animation.\",\n      \"type\": \"float\"\n    },\n    \"r_slimecolor\": {\n      \"default\": \"10 60 10\",\n      \"desc\": \"Changes color of slime when r_fastturb set to 1.\",\n      \"group-id\": \"51\",\n      \"type\": \"string\"\n    },\n    \"r_smoothalphahack\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls the alpha blending style for HUD text.\",\n      \"group-id\": \"50\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Corrected HUD text alpha blending (clean edges).\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Pre-ezQuake 3.6 HUD text alpha blending (creats faint outlines around the edges).\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_smoothcrosshair\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls texture filtering on the crosshair.\",\n      \"group-id\": \"6\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Nearest filtering - sharp, pixelated crosshair.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Linear filtering - smooth crosshair.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_smoothimages\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls texture filtering on HUD graphics : menu, console background, big numbers, icons, radar...\",\n      \"group-id\": \"50\",\n      \"remarks\": \"Classic graphics usually look better with 1.\\nHigher res content will work better with 1.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Nearest filtering - sharp, pixelated images.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Linear filtering - smooth images.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_smoothtext\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls texture filtering on HUD text.\",\n      \"group-id\": \"50\",\n      \"remarks\": \"Classic fonts usually look better with 0.\\nHigher res fonts will work better with 1.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Nearest filtering - sharp, pixelated text.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Linear filtering - smooth text.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_speeds\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles the displaying of drawing time and stats of what is currently being viewed.\\nExample:\\n32.7 ms 267/196/ 74 poly   3 surf\\n38.6 ms 267/196/ 74 poly  20 surf\\n60.4 ms 267/196/ 74 poly  18 surf\\n38.2 ms 267/196/ 73 poly  18 surf\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"r_teamskincolor\": {\n      \"default\": \"\",\n      \"desc\": \"Allows you to set color for teammates you see in RGB format.\",\n      \"group-id\": \"44\",\n      \"remarks\": \"See r_skincolormode for more info.\",\n      \"type\": \"string\"\n    },\n    \"r_telecolor\": {\n      \"default\": \"255 60 60\",\n      \"desc\": \"Changes color of teleport when r_fastturb set to 1.\",\n      \"group-id\": \"51\",\n      \"type\": \"string\"\n    },\n    \"r_telesplash\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles teleport splash effect.\",\n      \"group-id\": \"8\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable teleport splashes.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable teleport splashes.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_timegraph\": {\n      \"group-id\": \"31\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not display the timegraph.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display the timegraph.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_tracker\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"r_tracker_align_right\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls the alignment of the extra frag messages window.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Extra frag messages window will be shown on the left side of the screen.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Extra frag messages window will be shown on the right side of the screen.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_tracker_color_bad\": {\n      \"default\": \"900\",\n      \"desc\": \"Color to use when reporting a frag by opponent's team.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_color_fragonme\": {\n      \"default\": \"900\",\n      \"desc\": \"Color to use when reporting an opponent killed the current player.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_color_good\": {\n      \"default\": \"090\",\n      \"desc\": \"Color to use when reporting a kill by current player's team.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_color_myfrag\": {\n      \"default\": \"090\",\n      \"desc\": \"Color to use when reporting a frag by current player.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_color_suicide\": {\n      \"default\": \"900\",\n      \"desc\": \"Color to use when reporting a suicide.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_color_tkbad\": {\n      \"default\": \"009\",\n      \"desc\": \"Color to use when reporting a teamkill by current player's team.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_color_tkgood\": {\n      \"default\": \"990\",\n      \"desc\": \"Color to use when reporting an opponent's teamkill.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_colorfix\": {\n      \"default\": \"0\",\n      \"desc\": \"Both players in fragline will be colored according to good/bad/myfrag/etc values.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"r_tracker_flags\": {\n      \"default\": \"0\",\n      \"desc\": \"Everytime you take, capture or drop a flag, the number of times is displayed on the screen.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"See r_tracker_*, cl_parseFrags and cl_loadFragFiles variables description for further info.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_tracker_frags\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls which frag messages will be shown in extra window, beside the standard onscreen chat (notify area).\",\n      \"group-id\": \"40\",\n      \"remarks\": \"Values 1 and 2 requires cl_loadfragfiles and cl_parsefrags to be set to 1.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No extra frag messages shown.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Frag messages related to yourself will be shown in extra window.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"All frag messages will be shown in extra window.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"r_tracker_frame_color\": {\n      \"default\": \"0 0 0 0\",\n      \"desc\": \"Controls the opacity and color of the background of the extra frag messages window.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"See other r_tracker_* variables description for further info.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"<r g b><a> = red, green, blue and alpha values 0..255.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_tracker_images_scale\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls the size of images when using the tracker.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range 0.1 to 10.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_tracker_inconsole\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls how tracker messages will appear in the console/notify area.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"To see full death messages in the console, use con_fragmessages 2.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Tracker messages will not be shown in the console.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Tracker messages will be shown in console and notify area (disables regular tracker messages)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Tracker messages will be shown in console and notify area (regular tracker messages enabled)\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Tracker messages will be shown in console and regular tracker area but not in notify area.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"r_tracker_inconsole_colored_weapon\": {\n      \"default\": \"0\",\n      \"desc\": \"Use colored weapon string in death messages\",\n      \"group-id\": \"40\",\n      \"remarks\": \"The colors will be selected from r_tracker_color_good, r_tracker_color_bad, r_tracker_color_myfrag and r_tracker_color_fragonme\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Weapon text will not be colored\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Weapon text will be colored\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"r_tracker_messages\": {\n      \"default\": \"20\",\n      \"desc\": \"Maximum number of custom frag messages to be displayed in extra frag messages window.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"See other r_tracker_*, cl_parseFrags and cl_loadFragFiles variables description for further info.\",\n      \"type\": \"integer\"\n    },\n    \"r_tracker_name_width\": {\n      \"default\": \"0\",\n      \"desc\": \"Maximum length of player names in the tracker.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"r_tracker_own_frag_prefix\": {\n      \"default\": \"You fragged \",\n      \"desc\": \"Text to show before frag messages relating to the current player.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_pickups\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"r_tracker_positive_enemy_suicide\": {\n      \"default\": \"0\",\n      \"desc\": \"If enabled frag tracker draws enemy suicides using color value of r_tracker_color_good variable.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"r_tracker_positive_enemy_vs_enemy\": {\n      \"default\": \"0\",\n      \"desc\": \"If enabled frag tracker draws enemy vs enemy kills using color value of r_tracker_color_good variable.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"r_tracker_row_spacing\": {\n      \"default\": \"0\",\n      \"desc\": \"Additional pixels to add between tracker message rows. Positive values increase spacing, negative values decrease it.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"r_tracker_proportional\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable TrueType fonts on tracker text.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\"\n    },\n    \"r_tracker_scale\": {\n      \"default\": \"1\",\n      \"desc\": \"Allows you to change the font size in the extra frag messages.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"See other r_tracker_* variables.\",\n      \"type\": \"float\"\n    },\n    \"r_tracker_streaks\": {\n      \"default\": \"0\",\n      \"desc\": \"Everytime a player makes a set number of consecutive kills, it will display a message showing they are on a streak.\\nWhen the player is killed, it will display the name of the person who ended that streak.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"r_tracker_string_died\": {\n      \"default\": \" (died)\",\n      \"desc\": \"Text appearing in the tracker when a player dies (typically lava, slime, drowning etc)\",\n      \"group-id\": \"40\",\n      \"remarks\": \"Relates to PLAYER_DEATH rules in fragfile.dat.\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_string_enemy\": {\n      \"default\": \"enemy\",\n      \"desc\": \"Text appearing in the tracker when a player earns a frag.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"Relates to X_FRAGS_UNKNOWN rules in fragfile.dat.\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_string_inconsole_prefix\": {\n      \"default\": \"\",\n      \"desc\": \"Prefix to prepend before tracker messages in the console\",\n      \"group-id\": \"40\",\n      \"remarks\": \"The prefix is only visible when r_tracker_inconsole has a value greater than 0\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_string_suicides\": {\n      \"default\": \" (suicides)\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_string_teammate\": {\n      \"default\": \"teammate\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"r_tracker_time\": {\n      \"default\": \"4\",\n      \"desc\": \"Number of seconds the tracker information is drawn on the screen.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"See other 'r_tracker_*' variables too.\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Use positive numbers.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_tracker_weapon_first\": {\n      \"default\": \"0\",\n      \"desc\": \"Print the weapon icon or name before the player name.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\"\n    },\n    \"r_tracker_x\": {\n      \"default\": \"0\",\n      \"desc\": \"Adjusts the position of extra frag messages window.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"See other r_tracker_* variables description for more info.\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"horizontal position.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_tracker_y\": {\n      \"default\": \"0\",\n      \"desc\": \"Adjusts the position of extra frag messages window.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"See other r_tracker_* variables description for more info.\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"vertical position.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_turbwarp\": {\n      \"group-id\": \"31\",\n      \"remarks\": \"SW only!\\nThis variable has the same effect as r_waterwarp in other quake clients.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable warping of surfaces.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable warping of surfaces.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_viewmodelSize\": {\n      \"default\": \"1\",\n      \"desc\": \"This allows you to change the size of the viewmodel (the model of your active weapon displayed on the center of your screen) a little.\\nUseful if you prefer to use \\\"r_drawviewmodel 1\\\" and higher fov values (for demos for example).\\nFor \\\"fov 110\\\" you could try \\\"r_viewmodelSize 0.8\\\" or 0.85 for example.\",\n      \"group-id\": \"54\",\n      \"type\": \"float\"\n    },\n    \"r_viewmodellastfired\": {\n      \"default\": \"0\",\n      \"desc\": \"Display last fired weapon instead of currently held one on MVD / QTV playback.\",\n      \"group-id\": \"54\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_viewmodeloffset\": {\n      \"default\": \"\",\n      \"desc\": \"Moves the gun model to the right (positive value) or to the left (negative value).\\nGood for people who like to see right/left-handed looking weapons.\",\n      \"group-id\": \"54\",\n      \"remarks\": \"You can also change Y and Z coordinates, just provide parameters in \\\"X Y Z\\\" format.\\nSee also r_shiftbeam variable.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Range -10 to 10.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"r_viewpreselgun\": {\n      \"default\": \"0\",\n      \"desc\": \"Will show a gun that was picked as the best by weapon pre-selection.\",\n      \"group-id\": \"54\",\n      \"remarks\": \"See cl_weaponpreselect and cl_weaponhide.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Display the weapon you really hold.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Disaply the current pre-selected weapon.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"r_wallcolor\": {\n      \"default\": \"255 255 255\",\n      \"desc\": \"Changes color of walls when r_drawflat is set to 1.\\nEnter RGB value here, e.g. r_wallcolor \\\"128 128 128\\\" goes for gray walls.\",\n      \"group-id\": \"50\",\n      \"remarks\": \"Can not be changed during a match.\",\n      \"type\": \"string\"\n    },\n    \"r_watercolor\": {\n      \"default\": \"10 50 80\",\n      \"desc\": \"Changes color of water when r_fastturb set to 1.\",\n      \"group-id\": \"51\",\n      \"type\": \"string\"\n    },\n    \"r_zgraph\": {\n      \"group-id\": \"31\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not display the z-graph.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display the z-graph.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"railcolor\": {\n      \"default\": \"\",\n      \"desc\": \"Player's rail color.\",\n      \"group-id\": \"37\",\n      \"remarks\": \"Sent to server, requires mod support.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Server decides color.\",\n          \"name\": \"\"\n        },\n        {\n          \"description\": \"White.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Blue.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Green.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Light Blue.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Red.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Magenta.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Yellow.\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"White.\",\n          \"name\": \"7\"\n        }\n      ]\n    },\n    \"rate\": {\n      \"default\": \"25000\",\n      \"desc\": \"Sets the maximum amount of bytes per second that the server should send to the client.\",\n      \"group-id\": \"37\",\n      \"type\": \"float\"\n    },\n    \"rcon_address\": {\n      \"default\": \"\",\n      \"desc\": \"Address of the server to query with remote console commands.\",\n      \"group-id\": \"2\",\n      \"type\": \"string\"\n    },\n    \"rcon_password\": {\n      \"default\": \"\",\n      \"desc\": \"Password used for remote console communication.\",\n      \"group-id\": \"2\",\n      \"type\": \"string\"\n    },\n    \"re_trigger_match_0\": {\n      \"default\": \"\",\n      \"desc\": \"Whole matched pattern of the regular expression match.\",\n      \"group-id\": \"22\",\n      \"type\": \"string\"\n    },\n    \"re_trigger_match_1\": {\n      \"default\": \"\",\n      \"desc\": \"First matched subpattern of the regexp match.\",\n      \"group-id\": \"22\",\n      \"type\": \"string\"\n    },\n    \"re_trigger_match_2\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"re_trigger_match_3\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"re_trigger_match_4\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"re_trigger_match_5\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"re_trigger_match_6\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"re_trigger_match_7\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"re_trigger_match_8\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"re_trigger_match_9\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"registered\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"ruleset\": {\n      \"default\": \"default\",\n      \"desc\": \"Enforces a set of restrictions on the client features.\",\n      \"group-id\": \"37\",\n      \"remarks\": \"Most commonly used is \\\"smackdown\\\"\",\n      \"type\": \"string\"\n    },\n    \"s_alsa_device\": {\n      \"default\": \"default\",\n      \"desc\": \"Sets which device ALSA will be trying to open (when s_driver is set to alsa).\\nIf \\\"default\\\" fails it will try \\\"hw\\\" and if that fails too, \\\"plug:hw\\\" will be tried.\",\n      \"group-id\": \"26\",\n      \"type\": \"string\"\n    },\n    \"s_alsa_latency\": {\n      \"desc\": \"Specifies latency for ALSA.\\nIf you got distortion in sound, upper the value a bit.\\nIf you experience delays, try lowering this value.\",\n      \"group-id\": \"26\",\n      \"type\": \"float\"\n    },\n    \"s_alsa_noworkaround\": {\n      \"desc\": \"If you are having problems with ALSA you can try to set this var to 1 before you try legacy drivers.\",\n      \"group-id\": \"26\",\n      \"remarks\": \"Linux/FreeBSD only.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Workaround enabled.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Workaround disabled. Try this if you are having problems with new ALSA driver.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"s_ambientfade\": {\n      \"default\": \"100\",\n      \"desc\": \"How fast the volume of ambient sounds changes as you move around the map.\",\n      \"group-id\": \"45\",\n      \"type\": \"float\"\n    },\n    \"s_ambientlevel\": {\n      \"default\": \"0.3\",\n      \"desc\": \"Volume level of ambient sounds (produced by liquid and sky brushes).\",\n      \"group-id\": \"45\",\n      \"type\": \"float\"\n    },\n    \"s_audiodevice\": {\n      \"default\": \"0\",\n      \"desc\": \"Audio device to use.\",\n      \"group-id\": \"45\",\n      \"type\": \"integer\"\n    },\n    \"s_bits\": {\n      \"desc\": \"This defines how many sampling bits should be used, when using 16 bits the interpolation quality will be better.\",\n      \"group-id\": \"26\",\n      \"remarks\": \"Linux only.\\nUse s_restart after you change it.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"16 bit sound.\",\n          \"name\": \"16\"\n        },\n        {\n          \"description\": \"8 bit sound.\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"s_chat_custom\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls usage of s_mm*, s_chat_*, s_otherchat_* and s_spec_* variables.\",\n      \"group-id\": \"3\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No chat sounds played.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Chat sounds played for mm1 & mm2.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Chat sounds played for mm2 only.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"s_desiredsamples\": {\n      \"default\": \"0\",\n      \"desc\": \"Indicates how large the audio buffer should be, in samples.\\nIf 0, ezQuake will choose a sensible default, based on value of s_khz.\",\n      \"group-id\": \"45\",\n      \"remarks\": \"This is a suggestion only, the driver is free to choose a different value.\\nSmaller buffer sizes generally result in lower latency, but higher CPU usage.\",\n      \"type\": \"integer\"\n    },\n    \"s_device\": {\n      \"desc\": \"Allows you to choose from multiple sound devices.\",\n      \"group-id\": \"26\",\n      \"remarks\": \"Linux only.\\nUse s_restart after you change it.\\n\\nFor OSS you should use \\\"/dev/dsp\\\", \\\"/dev/dsp0\\\", \\\"/dev/dsp1\\\", etc.\\nFor ALSA use \\\"default\\\", \\\"dmix\\\", etc.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"device name.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"s_driver\": {\n      \"desc\": \"Specifices which sounddriver to use: ALSA/OSS or experimental Pulseaudio.\",\n      \"group-id\": \"26\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Will use ALSA drivers.\",\n          \"name\": \"alsa\"\n        },\n        {\n          \"description\": \"Will use OSS drivers.\",\n          \"name\": \"oss\"\n        },\n        {\n          \"description\": \"Will use experimental Pulseaudio drivers (note: have to be set in all loading configs before starting ezQuake to work!)\",\n          \"name\": \"pulse\"\n        }\n      ]\n    },\n    \"s_khz\": {\n      \"default\": \"11\",\n      \"desc\": \"Sets the audio sampling rate.\\nIn ezQuake, this introduces artifacts which give a \\\"crisper\\\"/\\\"grainy\\\" feel to some sounds, as the interpolation is not perfect. All the original sounds are 11k, except for shaft which is 22k and will sound different when setting s_khz over 11.\",\n      \"group-id\": \"45\",\n      \"remarks\": \"If you dislike the changes in sound, you can i.e. convert the shaft sounds down to 11k (and fail \\\"f_modified\\\" checks), go back to s_khz 11, or use s_linearresample for perfect resampling.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"11kHz sound (default).\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"22kHz sound.\",\n          \"name\": \"22\"\n        },\n        {\n          \"description\": \"44kHz sound.\",\n          \"name\": \"44\"\n        },\n        {\n          \"description\": \"48kHz sound.\",\n          \"name\": \"48\"\n        }\n      ]\n    },\n    \"s_linearresample\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls whether to resample sounds to s_khz with perfect quality. This cleans up any artifacts resulting from the default interpolation.\",\n      \"group-id\": \"45\",\n      \"remarks\": \"Resampling takes place as the sounds are loaded.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"No linear resampling.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Linear resampling when upsampling only.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Linear resampling when downsampling and upsampling.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"s_linearresample_stream\": {\n      \"default\": \"0\",\n      \"desc\": \"Same as s_linearresample, but specifically for VOIP audio streams.\",\n      \"group-id\": \"45\",\n      \"type\": \"integer\"\n    },\n    \"s_loadas8bit\": {\n      \"default\": \"0\",\n      \"desc\": \"Reduce sound samples down to 8-bit at load time, trading quality for lower memory usage.\",\n      \"group-id\": \"45\",\n      \"remarks\": \"This used to help with performance on systems with low memory.\",\n      \"type\": \"boolean\"\n    },\n    \"s_mixahead\": {\n      \"desc\": \"Only affects OSS and legacy ALSA:\\n\\nThis variable defines the delay time for sounds. How low you can set your sound mixahead depends on your FPS, when you set it too low, your sound will start crackling.\\nGenerally, with 72 FPS you should be able to use a delay of 0.06 seconds.\",\n      \"group-id\": \"26\",\n      \"type\": \"float\"\n    },\n    \"s_mm1_file\": {\n      \"default\": \"misc/talk.wav\",\n      \"desc\": \"You can specify notification sound for messagemode1 (/messagemode or /say foo) messages.\",\n      \"group-id\": \"45\",\n      \"type\": \"string\"\n    },\n    \"s_mm1_volume\": {\n      \"default\": \"1\",\n      \"desc\": \"You can specify volume of notification sound for messagemode1 (/messagemode or /say foo) messages.\",\n      \"group-id\": \"45\",\n      \"type\": \"float\"\n    },\n    \"s_mm2_file\": {\n      \"default\": \"misc/talk.wav\",\n      \"desc\": \"You can specify notification sound for messagemode2 (/messagemode2 or /say_team foo) messages.\",\n      \"group-id\": \"45\",\n      \"type\": \"string\"\n    },\n    \"s_mm2_volume\": {\n      \"default\": \"1\",\n      \"desc\": \"You can specify volume of notification sound for messagemode2 (/messagemode2 or /say_team foo) messages.\",\n      \"group-id\": \"45\",\n      \"type\": \"float\"\n    },\n    \"s_noalsa\": {\n      \"desc\": \"Determinates sound output for linux clients: ALSA or OSS.\",\n      \"group-id\": \"26\",\n      \"remarks\": \"If you don't know what is ALSA and OSS - leave it at the default value.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Use ALSA sound output.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Use OSS sound output.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"s_noextraupdate\": {\n      \"group-id\": \"45\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Makes Quake update its sound buffers more often to avoid choppy sound when your fps is really low (20 or worse)\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Don't use extra sound updates.\\nGives a slight fps boost, but may lead to choppy sound on low-end machines.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"s_nosound\": {\n      \"default\": \"0\",\n      \"group-id\": \"45\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Enable the playback of sounds.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Disable the playback of sounds.\\nNote that it effectively just resets sound volume to zero, but the sound engine still runs.\\nIf you want disable sound in order to get more fps, use the -nosound command line option instead.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"s_oss_device\": {\n      \"default\": \"/dev/dsp\",\n      \"desc\": \"Specifies which device OSS will try to open.\",\n      \"group-id\": \"26\",\n      \"type\": \"string\"\n    },\n    \"s_otherchat_file\": {\n      \"default\": \"misc/talk.wav\",\n      \"desc\": \"You can specify notification sound for other messages (than messagemode, messagemode2 and from spectators).\",\n      \"group-id\": \"45\",\n      \"type\": \"string\"\n    },\n    \"s_otherchat_volume\": {\n      \"default\": \"1\",\n      \"desc\": \"You can specify volume of notification sound for other messages (than messagemode, messagemode2 and from spectators).\",\n      \"group-id\": \"45\",\n      \"type\": \"float\"\n    },\n    \"s_precache\": {\n      \"default\": \"1\",\n      \"group-id\": \"45\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable automatic sound caching.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable automatic sound caching.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"s_pulseaudio_latency\": {\n      \"desc\": \"Specifies latency for Pulseaudio.\\nIf you got distortion in sound, upper the value a bit.\\nIf you experience delays, try lowering this value.\",\n      \"group-id\": \"26\",\n      \"type\": \"float\"\n    },\n    \"s_raw_volume\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls volume of voice (voip) playback.\",\n      \"group-id\": \"45\",\n      \"remarks\": \"See cl_voip_play.\",\n      \"type\": \"float\"\n    },\n    \"s_show\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles displaying how many and/or which sound files are currently being played.\",\n      \"group-id\": \"45\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not show anything.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Print the number of sounds playing.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Print the volume in left/right speakers, name of sound file playing, and number of sounds playing.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"s_silent_racing\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles whether or not you can hear other players when racing.\",\n      \"group-id\": \"45\",\n      \"type\": \"boolean\"\n    },\n    \"s_spec_file\": {\n      \"default\": \"misc/talk.wav\",\n      \"desc\": \"You can specify notification sound for spectator messages.\",\n      \"group-id\": \"45\",\n      \"type\": \"string\"\n    },\n    \"s_spec_volume\": {\n      \"default\": \"1\",\n      \"desc\": \"You can specify volume of notification sound for spectator messages.\",\n      \"group-id\": \"45\",\n      \"type\": \"float\"\n    },\n    \"s_stereo\": {\n      \"desc\": \"Use stereo or mono sound.\",\n      \"group-id\": \"26\",\n      \"remarks\": \"Linux only.\\nUse s_restart after you change it.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Mono.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Stereo.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"s_swapstereo\": {\n      \"default\": \"0\",\n      \"description\": \"Swaps left and right sound channels.\",\n      \"group-id\": \"45\",\n      \"type\": \"boolean\"\n    },\n    \"s_uselegacydrivers\": {\n      \"desc\": \"If you are having problems with the new ALSA/OSS drivers you can enable this to be able to use legacy sound drivers, as in the ones used before v2.1.\",\n      \"group-id\": \"26\",\n      \"remarks\": \"Linux/FreeBSD only.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Will use new sound drivers for ALSA/OSS.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Will use legacy drivers for ALSA/OSS (as used in versions before 2.1)\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"samelevel\": {\n      \"default\": \"1\",\n      \"desc\": \"When enabled, the same level will be played once the match is over.\\nOther mods use this value to store options.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Advance to next map after intermission.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Play same map again.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"sb_autohide\": {\n      \"default\": \"1\",\n      \"desc\": \"This toggles in which cases the server browser should automatically hide itself when connecting to a server.\",\n      \"group-id\": \"42\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Never hide server browser.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"This will cause the server browser to always hide after connecting.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"This will cause the server browser to hide after connecting only if the connected server is not a qizmo proxy.\\nUseful if you often connect to Qizmos only for rerouting features (with \\\"useproxy 1\\\")\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"sb_autoupdate\": {\n      \"default\": \"1\",\n      \"desc\": \"Enabled auto-updates (pings & refreshes status) servers from actually marked server sources in Server Browser.\",\n      \"group-id\": \"42\",\n      \"remarks\": \"Usefull with sb_starttab 1 and '+cfg_load myconfig +menu_slist' in command-line.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"do not refresh server list automatically.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"auto-refresh server list when first entering Server Browser menu.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_findroutes\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables automatic lookup of lowest ping path via proxies for connection to each server in the server browser.\",\n      \"group-id\": \"42\",\n      \"remarks\": \"Use with connectbr command.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Lookup disabled.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Lookup will start after the \\\"getting infos\\\" phase.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_hidedead\": {\n      \"default\": \"1\",\n      \"desc\": \"This toggles whether ezQuake should hide dead servers.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"don't hide dead servers.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"hide dead servers.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_hideempty\": {\n      \"default\": \"1\",\n      \"desc\": \"This toggles whether ezQuake should hide empty servers.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"don't hide empty servers.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"hide empty servers.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_hidefull\": {\n      \"default\": \"0\",\n      \"desc\": \"This toggles whether ezQuake should hide full servers.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"don't hide full servers.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"hide full servers.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_hidehighping\": {\n      \"default\": \"0\",\n      \"desc\": \"Remove servers with high ping from the server list and also exclude them from querying servers for details.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_hidenotempty\": {\n      \"default\": \"0\",\n      \"desc\": \"This toggles whether ezQuake should hide empty servers.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"don't hide empty servers.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"hide empty servers.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_ignore_proxy\": {\n      \"default\": \"\",\n      \"group-id\": \"42\",\n      \"type\": \"string\"\n    },\n    \"sb_info_filter\": {\n      \"default\": \"\",\n      \"desc\": \"Allows custom filters to be written based on the serverinfo strings\\nFormat is [+|-]infokey=value with +/- being include/exclude.\\nCan also specify +*/-* to include/exclude all, and +key/-key to include/exclude servers with any value for that key\\nValues are regular expressions, if the server doesn't have that key specified then the rule is skipped\",\n      \"group-id\": \"42\",\n      \"remarks\": \"Default (if no rules match) is opposite of the last valid rule specified.\\nRules should be separated by spaces\\nExamples\\n\\n+ktxver               // pass any ktx server, hide others\\n+*gamedir=fortress    // pass Team Fortress servers, hide others\\n-*version=QTV*        // remove QTV servers\\n-map=dm4              // fourier woz ere\\n\",\n      \"type\": \"string\"\n    },\n    \"sb_inforetries\": {\n      \"default\": \"3\",\n      \"desc\": \"This determines how often ezQuake should try to retrieve information from a server until it is considered to be not responding.\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_infospersec\": {\n      \"default\": \"100\",\n      \"desc\": \"This determines how many serverinfos per second ezQuake should retrieve when scanning servers.\\nWhen setting this value too high you will flood your line, causing you to not receive information from servers or lagging your connection to the server you are currently connected to.\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_infotimeout\": {\n      \"default\": \"1000\",\n      \"desc\": \"This determines how long ezQuake will wait for a reply when trying to retrieve information from a server until the attempt times out.\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_listcache\": {\n      \"default\": \"0\",\n      \"desc\": \"Cache the list of alive servers and load it on next startup of the client.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_liveupdate\": {\n      \"default\": \"2\",\n      \"desc\": \"This will determine how often ezQuake should refresh the serverinfo window, the specified value sets the delay in seconds.\\nSetting it to \\\"0\\\" will disable automatic refreshing.\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_mastercache\": {\n      \"default\": \"1\",\n      \"desc\": \"This toggles whether ezQuake should cache the results of queries to master server (in directory qw/sb/cache).\\nIf you restart ezQuake and don't update sources or if a master server is down, ezQuake will use the cache.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"don't use cache for master servers.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"use the cache for master servers when they are down or haven't been refreshed.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_masterretries\": {\n      \"default\": \"3\",\n      \"desc\": \"This determines how often ezQuake should try to retrieve information from a master server until it is considered to be not responding.\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_mastertimeout\": {\n      \"default\": \"1000\",\n      \"desc\": \"This determines how long ezQuake will wait for a reply when trying to retrieve information from a master server until the attempt times out.\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_nosockraw\": {\n      \"default\": \"0\",\n      \"desc\": \"Disable ICMP pinging in server browser and use UDP QW Ping packet to query servers for their ping (multithreaded).\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_pinglimit\": {\n      \"default\": \"80\",\n      \"desc\": \"Limit ping for servers to be visible when sb_hidehighping is enabled.\",\n      \"group-id\": \"42\",\n      \"type\": \"integer\"\n    },\n    \"sb_pings\": {\n      \"default\": \"3\",\n      \"desc\": \"This determines how many pings ezQuake will send to a server when trying to determine your ping to it.\\nA higher value will cause scanning servers to take longer, but the result may be more exact.\\nA lower value obviously makes scanning faster, but pings may be inaccurate.\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_pingspersec\": {\n      \"default\": \"150\",\n      \"desc\": \"This determines how many pings per second ezQuake should sent out when scanning servers.\\nIf you set this value too high the result will be that the pings will not be accurate because you overloaded your line.\\nIf you set it too low scanning servers will take very long especially when you have a large server list.\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_pingtimeout\": {\n      \"default\": \"1000\",\n      \"desc\": \"This determines how long ezQuake will wait for a reply when trying to ping a server until the attempt times out.\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_proxinfopersec\": {\n      \"default\": \"10\",\n      \"desc\": \"Number of proxies to contact per second.\\nUsed when finding fastest path to a server.\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_proxretries\": {\n      \"default\": \"3\",\n      \"desc\": \"Number of times to attempt contact with a proxy before giving up.\",\n      \"group-id\": \"42\",\n      \"type\": \"integer\"\n    },\n    \"sb_proxtimeout\": {\n      \"default\": \"1000\",\n      \"desc\": \"Timeout period (in ms) before a proxy connection times out.\",\n      \"group-id\": \"42\",\n      \"type\": \"integer\"\n    },\n    \"sb_showaddress\": {\n      \"default\": \"0\",\n      \"desc\": \"This toggles whether ezQuake should display the server IP column in the server list.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"do not show the server IP column.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"show the server IP column.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_showcounters\": {\n      \"desc\": \"This toggles whether ezQuake should display the number of servers or players in the status line.\",\n      \"group-id\": \"31\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"do not show the server/player counter in the status line.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"show the server/player counter in the status line.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_showfraglimit\": {\n      \"default\": \"0\",\n      \"desc\": \"This toggles whether ezQuake should display the fraglimit column in the server list.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"do not show the fraglimit column.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"show the fraglimit column.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_showgamedir\": {\n      \"default\": \"0\",\n      \"desc\": \"This toggles whether ezQuake should display the gamedir column in the server list, for example to see which mod is being played.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"do not show the gamedir column.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"show the gamedir column.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_showmap\": {\n      \"default\": \"1\",\n      \"desc\": \"This toggles whether ezQuake should display the map column in the server list.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"do not show the map column.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"show the map column.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_showping\": {\n      \"default\": \"1\",\n      \"desc\": \"This toggles whether ezQuake should display the ping column in the server list.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"do not show the ping column.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"show the ping column.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_showplayers\": {\n      \"default\": \"1\",\n      \"desc\": \"This toggles whether ezQuake should display the players column in the server list that shows how many players are on the server as well as how many players are allowed.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"do not show the players column.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"show the players column.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_showproxies\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggle display of proxy servers (QWFwd and Qizmo) in the Server Browser.\",\n      \"group-id\": \"42\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Hide proxies in the server browser.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Show proxies in the server browser.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Only show proxies in the server browser.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"sb_showtimelimit\": {\n      \"default\": \"0\",\n      \"desc\": \"This toggles whether ezQuake should display the timelimit column in the server list.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"do not show the timelimit column.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"show the timelimit column.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sb_sortplayers\": {\n      \"default\": \"92\",\n      \"desc\": \"This determines sort order in the players list. This uses the numbers from the description of the columns.\\nCheck \\\"Columns available in servers/players list\\\" above for an explanation of each number.\\nA \\\"-\\\" in front of the value reverses the sort order for that value from ascending to descending.\\n\\naddress (ip:port).\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_sortservers\": {\n      \"default\": \"32\",\n      \"desc\": \"This determines sort order in the servers list. This uses the numbers from the description of the columns.\\nCheck \\\"Columns available in servers/players list\\\"  above for an explanation of each number.\\nA \\\"-\\\" in front of the value reverses the sort order for that value from ascending to descending.\\n\\nserver address (ip:port).\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_sortsources\": {\n      \"default\": \"3\",\n      \"desc\": \"Ordering instructions for server browser sources list are stored in this variable.\\nYou can order source server in Server browser by pressing Ctrl+1, Ctrl+2 and Ctrl+3.\\nSequence of your desired combination will be stored in this variable, newest keys first.\",\n      \"group-id\": \"42\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"E.g.: 3-21 means you've pressed Ctrl+1, Ctrl+2, Ctrl+2 and Ctrl+3 so your sources are now ordered by server count, last update time and source name.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"sb_sourcevalidity\": {\n      \"default\": \"30\",\n      \"desc\": \"This sets the time of master servers validity in minutes.\\nMaster servers that were updated within the specified time will not be refreshed when updating sources with [SPACE].\",\n      \"group-id\": \"42\",\n      \"type\": \"float\"\n    },\n    \"sb_status\": {\n      \"default\": \"1\",\n      \"desc\": \"This toggles whether the server status should be displayed in the two bottom lines of the server browser.\",\n      \"group-id\": \"42\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"do not display the status.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"display the status.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_allowsnap\": {\n      \"default\": \"1\",\n      \"group-id\": \"41\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Allow screenshot uploads to be requested by server.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_autoid\": {\n      \"default\": \"5\",\n      \"desc\": \"Controls how names and other info are displayed above players, when spectating.\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not show any player info.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Show name.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Name + health and armor bars.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"2 + Show armor name/icon.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"3 + Show if the user's best weapon is RL.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"3 + Show the user's best weapon name/icon. Minimum best weapon set with scr_autoid_weapons.\",\n          \"name\": \"5\"\n        }\n      ]\n    },\n    \"scr_autoid_armorbar_green_armor\": {\n      \"default\": \"25 170 0 255\",\n      \"desc\": \"Color of the autoID armor bar when a player has the Green armor.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"scr_autoid_armorbar_red_armor\": {\n      \"default\": \"255 0 0 255\",\n      \"desc\": \"Color of the autoID armor bar when a player has the Red armor.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"scr_autoid_armorbar_yellow_armor\": {\n      \"default\": \"255 220 0 255\",\n      \"desc\": \"Color of the autoID armor bar when a player has the Yellow armor.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"scr_autoid_barlength\": {\n      \"default\": \"16\",\n      \"desc\": \"Sets a fixed length for health/armor bars in autoID.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"If 0, length will be based on scr_autoid_namelength and/or length of individual player's name.\",\n      \"type\": \"integer\"\n    },\n    \"scr_autoid_drawname\": {\n      \"desc\": \"Shows player names when using autoID.\",\n      \"group-id\": \"31\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not show player names.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Show player names.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_autoid_healthbar_bg_color\": {\n      \"default\": \"180 115 115 100\",\n      \"desc\": \"Background color of the autoID health bar.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"scr_autoid_healthbar_mega_color\": {\n      \"default\": \"255 0 0 255\",\n      \"desc\": \"Color of the autoID health bar when a player has a Megahealth.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"scr_autoid_healthbar_normal_color\": {\n      \"default\": \"80 0 0 255\",\n      \"desc\": \"Default color of the autoID health bar.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"scr_autoid_healthbar_two_mega_color\": {\n      \"default\": \"255 100 0 255\",\n      \"desc\": \"Color of the autoID health bar when a player has multiple Megahealths.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"scr_autoid_healthbar_unnatural_color\": {\n      \"default\": \"255 255 255 255\",\n      \"desc\": \"Color of the autoID health bar for any unusual situation (i.e invincibility during warmup).\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"scr_autoid_namelength\": {\n      \"default\": \"0\",\n      \"desc\": \"Maximum number of characters for autoID player names.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"scr_autoid_proportional\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable TrueType fonts on autoID text.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\"\n    },\n    \"scr_autoid_scale\": {\n      \"default\": \"1\",\n      \"desc\": \"Sets relative size of scr_autoid display.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"scr_autoid_weaponicon\": {\n      \"default\": \"1\",\n      \"desc\": \"Sets the style of player's weapons in autoID.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Player's weapon is indicated by text (RL, etc).\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Player's weapon is indicated by an icon.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_autoid_weapons\": {\n      \"default\": \"2\",\n      \"desc\": \"Determines the minimum best weapon to show for a player when scr_autoid > 4.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"The order of 'best' weapon can be controlled via the 'tp_weapon_order' variable.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Shotgun or better.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Super shotgun or better.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Nailgun or better.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Super nailgun or better.\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Grenade launcher or better.\",\n          \"name\": \"16\"\n        },\n        {\n          \"description\": \"Rocket launcher or better.\",\n          \"name\": \"32\"\n        },\n        {\n          \"description\": \"Lightning gun or better.\",\n          \"name\": \"64\"\n        }\n      ]\n    },\n    \"scr_centerMenu\": {\n      \"default\": \"1\",\n      \"desc\": \"Centers the menu vertically (has no effect if you're playing in 320x200 mode, same applies to scr_centersbar).\",\n      \"group-id\": \"17\",\n      \"type\": \"boolean\"\n    },\n    \"scr_centerSbar\": {\n      \"default\": \"1\",\n      \"description\": \"Centers the status bar (classic HUD).\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\"\n    },\n    \"scr_centershift\": {\n      \"default\": \"0\",\n      \"desc\": \"Shifts all centerprints.\\nIf you are playing in 1280x1024 and want to watch some demo recorded in 320x200 with +wp_stats (ktpro) or sbar_on (teamfortress) you can shift that text down.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Range -999 to 999.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_centerspeed\": {\n      \"default\": \"8\",\n      \"desc\": \"This variable controls how fast the text is displayed at the end of the single player episodes.\",\n      \"group-id\": \"5\",\n      \"type\": \"float\"\n    },\n    \"scr_centertime\": {\n      \"default\": \"2\",\n      \"desc\": \"This variable sets the amount of time in seconds that centerprinted messages stay on the screen.\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not display any centerprints.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Display centerprints for 1 second.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Display centerprints for 2 seconds.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"scr_coloredText\": {\n      \"default\": \"1\",\n      \"desc\": \"Allows colored text in scoreboard, console and notify area.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"See Colored text manual page for more details.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disable rainbow spectator text on the scoreboard.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Enable rainbow spectator text on the scoreboard.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Enable rainbow spectator text on the scoreboard plus parse colored console messages.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"scr_coloredfrags\": {\n      \"default\": \"0\",\n      \"desc\": \"Frag messages will be colored according to your teamcolor/enemycolor settings.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"Needs scr_coloredText 1, cl_parsefrags 1, cl_loadfragfiles 1.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Coloring off.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Coloring on.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_compactHud\": {\n      \"default\": \"0\",\n      \"group-id\": \"47\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Compact huds off.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Will display armour/health/ammo/weapons *very* compactly.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Will display armour/health/ammo *very* compactly.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Displays only health/armour.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Will display armour/health/ammo/weapons compactly with icons.\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"scr_compactHudAlign\": {\n      \"default\": \"0\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"determine ammo amounts are aligned to the left.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"determine ammo amounts are aligned to the right.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_conalpha\": {\n      \"default\": \"0.8\",\n      \"desc\": \"Opacity of the console background.\",\n      \"group-id\": \"5\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Transparent.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Opaque.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"scr_conback\": {\n      \"default\": \"1\",\n      \"desc\": \"Allows display of map preview as a console background.\",\n      \"group-id\": \"5\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Never display map preview.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Display map preview on level loading process.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Always display map preview as a console background.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"scr_conpicture\": {\n      \"default\": \"conback\",\n      \"desc\": \"Console background image filename.\",\n      \"group-id\": \"5\",\n      \"remarks\": \"Put console images into id1/gfx or qw/gfx or ezquake/gfx.\",\n      \"type\": \"string\"\n    },\n    \"scr_consize\": {\n      \"default\": \"0.5\",\n      \"desc\": \"Sets the height of the console during a game.\",\n      \"group-id\": \"5\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range 0 to 1.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_conspeed\": {\n      \"default\": \"9999\",\n      \"desc\": \"This variable controls the speed at which the console screen scrolls up and down.\",\n      \"group-id\": \"5\",\n      \"type\": \"float\"\n    },\n    \"scr_cursor_alpha\": {\n      \"default\": \"1\",\n      \"desc\": \"Level of transparency of the cursor used in menus and HUD editor.\",\n      \"group-id\": \"17\",\n      \"type\": \"float\"\n    },\n    \"scr_cursor_iconoffset_x\": {\n      \"default\": \"0\",\n      \"desc\": \"Offset of the cursor image used in menus and HUD editor.\",\n      \"group-id\": \"17\",\n      \"type\": \"integer\"\n    },\n    \"scr_cursor_iconoffset_y\": {\n      \"default\": \"0\",\n      \"desc\": \"Offset of the cursor image used in menus and HUD editor.\",\n      \"group-id\": \"17\",\n      \"type\": \"integer\"\n    },\n    \"scr_cursor_scale\": {\n      \"default\": \"0.2\",\n      \"desc\": \"Size of the cursor image used in menus and HUD editor.\",\n      \"group-id\": \"17\",\n      \"type\": \"float\"\n    },\n    \"scr_cursor_sensitivity\": {\n      \"default\": \"1\",\n      \"desc\": \"Mouse sensitivity for the cursor used in menus and HUD editor.\",\n      \"group-id\": \"17\",\n      \"type\": \"float\"\n    },\n    \"scr_damage_floating\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"scr_damage_hitbeep\": {\n      \"default\": \"0\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"scr_damage_offset_ingame\": {\n      \"default\": \"14\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"scr_damage_offset_spectator\": {\n      \"default\": \"28\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"scr_damage_proportional\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable TrueType fonts on damage text.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\"\n    },\n    \"scr_damage_scale\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"scr_drawHFrags\": {\n      \"default\": \"1\",\n      \"desc\": \"Displays horizontal bar with frag stats (4 cells) in the old hud.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"Required settings for this to work:\\nscr_newhud 0 or 2, viewsize below 110, cl_sbar 1 or cl_sbar 0 but vid_conwidth below 512.\\nif you migrated from FuhQuake and can't get this to work, check these settings, the feature should work the same as in FuhQuake.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Turns frag display off.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Individual frags.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Teamfrags in teamplay mode.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Teamfrags in teamplay mode, individual frags hidden.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"scr_drawVFrags\": {\n      \"default\": \"1\",\n      \"desc\": \"Shows vertical row with frag stats on the right side of the old hud.\\nWhen teamplay mode is on, will display also frags per team.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"Note: vid_conwidth must be at least 512, scr_centerSbar must be disabled.\\nWorks only in deathmatch, not in cooperative.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Turns frag display off.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Individual frags.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Teamfrags in teamplay mode.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"scr_fovmode\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines how to keep aspect ratio correct when viewsize is reduced.\",\n      \"group-id\": \"53\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Standard QWCL behaviour: reduce vertical fov, cropping image.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Reduce horizontal size, keeping aspect ratio correct.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"scr_menualpha\": {\n      \"default\": \"0.7\",\n      \"desc\": \"Opacity of the main menu's background.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Transparent mainmenu.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Black mainmenu.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"scr_menudrawhud\": {\n      \"default\": \"0\",\n      \"desc\": \"Draw HUD elements/crosshair/etc in background of main menu.\\nUseful when HUD distracting you in server browser.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Don't draw HUD elements when in menus.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw HUD elements when in menus.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_newhud\": {\n      \"default\": \"0\",\n      \"group-id\": \"47\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Old standard status bar.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"New customizable status bar.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"First draws old status bar and then renders new status bar on the top of it.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"scr_notifyalways\": {\n      \"default\": \"0\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"The notify area is hidden during intermission (chat messages go to console but not to screen)\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"The notify area is always shown.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_qtvbuffer\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables display of the QTV buffer status.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"Makes it able to check how much of the stream is buffered on the client side.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_qtvbuffer_x\": {\n      \"default\": \"0\",\n      \"desc\": \"Horizontal position of the qtvbuffer counter.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"scr_qtvbuffer_y\": {\n      \"default\": \"-10\",\n      \"desc\": \"Vertical position of the qtvbuffer counter.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"scr_sbar_drawammo\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns drawing amount of ammo on/off.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"This variable applies for old HUD <= 'scr_newhud 0'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw ammo.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw ammo.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_sbar_drawammocounts\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns drawing amounts of ammo on/off.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"This variable applies for old HUD <= 'scr_newhud 0'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw countrs of ammo.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw countrs of ammo.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_sbar_drawammoicon\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns drawing of ammo icon on/off.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"This variable applies for old HUD <= 'scr_newhud 0'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw ammo icon.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw ammo icon.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_sbar_drawarmor\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns drawing of armor count on/off.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"This variable applies for old HUD <= 'scr_newhud 0'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw armor count.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw armor count.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_sbar_drawarmor666\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns on/off drawing of armor as 666 when holding pent.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"This variable applies for old HUD <= 'scr_newhud 0'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw armor as 666 and instead draw current armor value.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw armor as 666.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_sbar_drawarmoricon\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns drawing of armor icon on/off.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"This variable applies for old HUD <= 'scr_newhud 0'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw armor icon.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw armor icon.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_sbar_drawfaceicon\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns drawing of face icon on/off.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"This variable applies for old HUD <= 'scr_newhud 0'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw face icon.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw face icon.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_sbar_drawguns\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns drawing of available guns on/off.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"This variable applies for old HUD <= 'scr_newhud 0'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw available guns.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw avialable guns.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_sbar_drawhealth\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns drawing of health amount on/off.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"This variable applies for old HUD <= 'scr_newhud 0'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw health amount.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw health amount.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_sbar_drawitems\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns drawing of items (powerups and keys) in status bar on/off.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"This variable applies for old HUD <= 'scr_newhud 0'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw items in status bar.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw items in status bar.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_sbar_drawsigils\": {\n      \"default\": \"1\",\n      \"desc\": \"Turns drawing of sigils on/off.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"This variable applies for old HUD <= 'scr_newhud 0'.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not draw sigils.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Draw sigils.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_sbar_lowammo\": {\n      \"default\": \"5\",\n      \"desc\": \"The ammo limit at which ammo numbers will start using the alternate text.\",\n      \"group-id\": \"47\",\n      \"remarks\": \"This variable applies for old HUD <= 'scr_newhud 0'.\",\n      \"type\": \"integer\"\n    },\n    \"scr_scaleMenu\": {\n      \"default\": \"2\",\n      \"desc\": \"Scales the frontend menu.\",\n      \"group-id\": \"17\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Disable scaling.\",\n          \"name\": \"0\"\n        }\n      ]\n    },\n    \"scr_scoreboard_afk\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, indicate players who are AFK (don't have Quake active)\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\"\n    },\n    \"scr_scoreboard_afk_style\": {\n      \"default\": \"1\",\n      \"desc\": \"Affects how a player being away from the game is reflected in scoreboard.\",\n      \"group-id\": \"47\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"If user is AFK, time replaced with AFK.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"If user is AFK, time value shown in red.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"scr_scoreboard_borderless\": {\n      \"default\": \"1\",\n      \"desc\": \"Disables the border around the scoreboard.\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Scoreboard will have a border.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Scoreboard will not have a border.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_scoreboard_centered\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls X-position of scoreboard.\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Scoreboard will not be centered.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Scoreboard will be centered.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_scoreboard_classic\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggle between the classic and modern look in the scoreboard.\",\n      \"group-id\": \"47\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disable the classic look.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Enable the classic look.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Enable the true classic look, which was available in qwcl 1.50 and 1.55\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"scr_scoreboard_drawtitle\": {\n      \"default\": \"1\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable scoreboard title.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable scoreboard title.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_scoreboard_fadescreen\": {\n      \"default\": \"0\",\n      \"desc\": \"shadowing level, when scoreboard is displayed.\",\n      \"group-id\": \"47\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"[0...1] 0=transparent scoreboard 1=black scoreboard.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_scoreboard_fillalpha\": {\n      \"default\": \"0.7\",\n      \"desc\": \"Change scoreboard fillalpha.\",\n      \"group-id\": \"47\",\n      \"type\": \"float\"\n    },\n    \"scr_scoreboard_fillcolored\": {\n      \"default\": \"2\",\n      \"desc\": \"Modify scoreboard fillcolor.\",\n      \"group-id\": \"47\",\n      \"type\": \"float\"\n    },\n    \"scr_scoreboard_forcecolors\": {\n      \"default\": \"1\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"When you overwrite team/enemy color it will use only those colors for skins.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"When you overwrite team/enemy color it will use only those colors for skins and scoreboard.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_scoreboard_highlightself\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggle the highlighting of your own scores in the scoreboard.\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable highlight of your own scores in the scoreboard.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable highlight of your own scores in the scoreboard.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_scoreboard_login_color\": {\n      \"default\": \"255 255 192\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"scr_scoreboard_login_flagfile\": {\n      \"default\": \"flags\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"scr_scoreboard_login_indicator\": {\n      \"default\": \"&cffc*&r\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"scr_scoreboard_login_names\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"scr_scoreboard_posx\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls the X-offset of the scoreboard.\",\n      \"group-id\": \"47\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"If 0, scoreboard will be shown at the left of the screen (scr_scoreboard_centered 0) or middle (scr_scoreboard_centered 1).\\nIf nonzero, the scoreboard will be shifted left (negative values) or right (positive values).\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_scoreboard_posy\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls the Y-offset of the scoreboard.\",\n      \"group-id\": \"47\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"If 0, scoreboard will be shown at the top of the screen.\\nIf nonzero, the scoreboard will be shifted down.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_scoreboard_proportional\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable TrueType fonts on the scoreboard.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\"\n    },\n    \"scr_scoreboard_qtv_name\": {\n      \"desc\": \"This variable will change what qtv users are called in the scoreboard.\\nWhen teamplay is not on, this variable is cut to 4 characters.\\n&cRGB values are not accepted.\",\n      \"group-id\": \"47\",\n      \"type\": \"string\"\n    },\n    \"scr_scoreboard_showclock\": {\n      \"default\": \"0\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Hide the clock on the scoreboard.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Show the clock on the scoreboard.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_scoreboard_showflagstats\": {\n      \"default\": \"0\",\n      \"desc\": \"This setting has no effect on TeamFortress which automatically enables flag stats\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable flag stats on the scoreboard.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable flag stats on the scoreboard.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_scoreboard_showfrags\": {\n      \"default\": \"1\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable frags on the scoreboard.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable frags on the scoreboard.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_scoreboard_showmapname\": {\n      \"default\": \"0\",\n      \"group-id\": \"47\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Don't display the level name\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Full name, example: The Abandoned Base (dm3)\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Level name, example: The Abandoned Base\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Map name, example: dm3\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"scr_scoreboard_showqtvusers\": {\n      \"default\": \"1\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable QTV users in the scoreboard.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable QTV users in the scoreboard.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_scoreboard_spectator_name\": {\n      \"desc\": \"This variable will change what spectators are called in the scoreboard.\\nWhen teamplay is not on, this variable is cut to 4 characters.\\n&cRGB values are not accepted.\",\n      \"group-id\": \"47\",\n      \"type\": \"string\"\n    },\n    \"scr_scoreboard_teamsort\": {\n      \"default\": \"1\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Frag sort.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Team sort.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_scoreboard_wipeout\": {\n      \"default\": \"1\",\n      \"group-id\": \"47\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Display normal scoreboard during clan arena or wipeout modes.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display game-specific scoreboard during clan arena or wipeout modes.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_showcrosshair\": {\n      \"default\": \"1\",\n      \"desc\": \"Allows you to force the display of the crosshair when in menus or in scoreboard.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_shownick_frame_color\": {\n      \"default\": \"10 0 0 120\",\n      \"desc\": \"If using '/shownick 1' in KTX, this determines the color of the frame around the on-screen information.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"scr_shownick_name_width\": {\n      \"default\": \"6\",\n      \"desc\": \"Maximum length of player's name when using '/shownick 1' in KTX.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"scr_shownick_order\": {\n      \"default\": \"%p%n %a/%H %w\",\n      \"desc\": \"Format string for results of '/shownick 1' in KTX.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"scr_shownick_proportional\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable TrueType fonts on shownick text.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\"\n    },\n    \"scr_shownick_scale\": {\n      \"default\": \"1\",\n      \"desc\": \"Adjusts relative size of text when showing results of '/shownick 1' in KTX.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"scr_shownick_show_ammo\": {\n      \"default\": \"0\",\n      \"desc\": \"Displays ammo count next to best weapon.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\"\n    },\n    \"scr_shownick_time\": {\n      \"default\": \"0.8\",\n      \"desc\": \"Number of seconds to show results of '/shownick 1' command in KTX.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"scr_shownick_x\": {\n      \"default\": \"0\",\n      \"desc\": \"Horizontal position of the shownick label.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"scr_shownick_y\": {\n      \"default\": \"0\",\n      \"desc\": \"Vertical position of the shownick label.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"scr_spectatorMessage\": {\n      \"default\": \"1\",\n      \"desc\": \"Switch on/off the text at the bottom of the screen when spectating in free mode:\\n\\\"SPECTATOR MODE | PRESS [ATTACK] for AutoCamera\\\"\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\"\n    },\n    \"scr_teaminfo\": {\n      \"default\": \"1\",\n      \"desc\": \"Displays team info when you are spectating or watching mvd demo.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"see scr_teaminfo_* for other options.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"No team info.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display team info.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_teaminfo_align_right\": {\n      \"default\": \"1\",\n      \"desc\": \"Aligns scr_teaminfo left or right.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Align scr_teaminfo left.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Align scr_teaminfo right.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_teaminfo_armor_style\": {\n      \"default\": \"3\",\n      \"desc\": \"Changes %a to different styles.\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Does not display teammates' armor.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Displays teammates' armor in white, prefixed by the icon of the armor they have.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Displays teammates' armor in white.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Displays teammates' armor in the color of the armor they have.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Displays teammates' armor in white, prefixed by g|y|r depending on type.\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"scr_teaminfo_flag_style\": {\n      \"default\": \"1\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Flag icons.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Colored text.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"scr_teaminfo_frame_color\": {\n      \"default\": \"10 0 0 120\",\n      \"desc\": \"Changes the color and transparency of scr_teaminfo's frame.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"R G B alpha, each 0-255.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_teaminfo_loc_width\": {\n      \"default\": \"5\",\n      \"desc\": \"Sets the width for %l.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"From 0 (no loc display) to 20.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_teaminfo_low_health\": {\n      \"default\": \"25\",\n      \"desc\": \"Sets a minimum health value to display red.\\nE.g. if scr_teaminfo_low_health is 20, and a teammate has 20 health or less, then their health will be displayed red.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"From 1 to 99.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_teaminfo_name_width\": {\n      \"default\": \"6\",\n      \"desc\": \"Sets name width in scr_teaminfo.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"From 0 (don't display name) to 20.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_teaminfo_order\": {\n      \"default\": \"%p%n $x10%l$x11 %a/%H %w\",\n      \"desc\": \"Changes the order of displayed items in scr_teaminfo.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"default: \\\"%p%n [%l] %h/%a %w\\\"\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Options: %p (powerup) %n (name) %h (health) %a (armor) %l (location) %w (weapon)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_teaminfo_powerup_style\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls how powerup indicators are displayed in scr_teaminfo.\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Powerup icons.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Player face.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Colored text.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"scr_teaminfo_proportional\": {\n      \"default\": \"0\",\n      \"desc\": \"Enable TrueType fonts on teaminfo text.\",\n      \"group-id\": \"5\",\n      \"type\": \"boolean\"\n    },\n    \"scr_teaminfo_scale\": {\n      \"default\": \"1\",\n      \"desc\": \"Scales scr_teaminfo.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"From 0 (very small) to 10 (very big)\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_teaminfo_show_ammo\": {\n      \"default\": \"0\",\n      \"desc\": \"Will show ammo count next to best weapon in teaminfo table.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"If disabled, ammo can still be displayed by adding %c to /scr_teaminfo_order\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not show ammo next to best weapon.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Show ammo next to best weapon.\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"scr_teaminfo_show_countdown\": {\n      \"default\": \"1\",\n      \"desc\": \"Shows respawn time instead of powerups during wipeout mode.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"If disabled, countdown can still be displayed by adding %r to /scr_teaminfo_order\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not show respawn time instead of powerups in wipeout mode\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Show respawn time instead of powerups in wipeout mode\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"scr_teaminfo_show_enemies\": {\n      \"default\": \"0\",\n      \"desc\": \"Will show enemy players in the teaminfo table.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"Works only for MVD playback (servers do not send info about enemies to you anyway).\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not show enemy players status.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Show enemy players status in teaminfo table.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_teaminfo_show_self\": {\n      \"default\": \"2\",\n      \"desc\": \"Will show row with the status of the current player in the teaminfo table.\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not show tracked player status in the teaminfo table.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Show tracked player in the teaminfo table.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Show tracked player when watching demos/QTV only.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"scr_teaminfo_weapon_style\": {\n      \"default\": \"1\",\n      \"desc\": \"Changes %w to different styles.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Displays teammates' weapon as an icon.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Displays teammates' weapon as a word (rl, lg, etc..)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_teaminfo_x\": {\n      \"default\": \"0\",\n      \"desc\": \"Moves scr_teaminfo in the x-direction.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"From -255 to 255.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_teaminfo_y\": {\n      \"default\": \"0\",\n      \"desc\": \"Moves scr_teaminfo in the y-direction.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"From -255 to 255.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_tracking\": {\n      \"desc\": \"Changes the format of descriptive text displayed when you are tracking someone as spectator or watching a demo where you can see player's team and name.\",\n      \"group-id\": \"40\",\n      \"remarks\": \"This variable affects scr_newhud 0. To change same text in new HUD (scr_newhud 1) use /tracking format (hud_tracking_format) settings.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Pattern %n will be replaced with tracked player's name, %t will be replaced with player's team.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"scr_weaponstats\": {\n      \"desc\": \"Displays weapons stats on server that support it.\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_weaponstats_align_right\": {\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Displays weapon stats on the left hand side of the screen.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Displays weapon stats on the right hand side of the screen.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"scr_weaponstats_frame_color\": {\n      \"desc\": \"Sets the color of the frame around scr_weaponstats display.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"scr_weaponstats_order\": {\n      \"desc\": \"Accept next tokens %1 %2 %3 ... %8 which expands in to weapons accuracy, %1 - axe, %2 - sg ... %8 - lg.\\n\\nBut, for axe accuracy is useless, so better use direct hits instead, for that you must use it like #1.\\n\\nUnrecognized tokens printed as-is, so for axe order will looks like \\\"axe: #1\\\" - that means print string \\\"axe: \\\" and axe hits.\\n\\nAlso variable supports ezquake color sequences.\",\n      \"group-id\": \"40\",\n      \"type\": \"string\"\n    },\n    \"scr_weaponstats_scale\": {\n      \"desc\": \"Controls size of scr_weaponstats display.\\n1 - Normal text size.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"scr_weaponstats_x\": {\n      \"desc\": \"Horizontal placement of the weapon stats table.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"scr_weaponstats_y\": {\n      \"desc\": \"Vertical placement of the weapon stats table.\",\n      \"group-id\": \"40\",\n      \"type\": \"integer\"\n    },\n    \"sensitivity\": {\n      \"default\": \"12\",\n      \"desc\": \"This variable sets the sensitivity of the mouse, it is one of the most important variables in the whole game.\",\n      \"group-id\": \"10\",\n      \"type\": \"float\"\n    },\n    \"serverdemo\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"show_fps\": {\n      \"default\": \"0\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disables the display of the frames-per-second value.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enables the display of the frames-per-second value.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"show_fps_x\": {\n      \"default\": \"-5\",\n      \"desc\": \"Determine where the show_fps is positioned on your screen on the X co-ordinate.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"show_fps_y\": {\n      \"default\": \"-1\",\n      \"desc\": \"Determine where the show_fps is positioned on your screen on the Y co-ordinate.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"show_speed\": {\n      \"default\": \"0\",\n      \"desc\": \"Displays a speed counter in the top right corner in the client's units (Horizontal velocity).\\n\\n320 - Normal walking speed.\\n440 - Accel jump.\\n450 - Bunnyhopping.\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Off.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"More accurate counter (Best used when LPB).\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Predicted speed (Less laggy when HPB).\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"show_speed_x\": {\n      \"default\": \"-1\",\n      \"desc\": \"Determine where the show_speed is positioned on your screen on the X co-ordinate.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"show_speed_y\": {\n      \"default\": \"1\",\n      \"desc\": \"Determine where the show_speed is positioned on your screen on the Y co-ordinate.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"show_velocity_3d\": {\n      \"default\": \"0\",\n      \"desc\": \"Shows your velocity as 3d vector and its projections (*GL ONLY*).\",\n      \"group-id\": \"40\",\n      \"remarks\": \"See also show_velocity_3d_offset_forward, show_velocity_3d_offset_down.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"off (default)\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"velocity and projections.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"only horizontal velocity and projections.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"show_velocity_3d_offset_down\": {\n      \"default\": \"5\",\n      \"group-id\": \"40\",\n      \"type\": \"\"\n    },\n    \"show_velocity_3d_offset_forward\": {\n      \"default\": \"2.5\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"showdrop\": {\n      \"default\": \"0\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable text dump printing dropped packets.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable text dump printing dropped packets.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"showpackets\": {\n      \"default\": \"0\",\n      \"description\": \"Dumps log of network activity to the console (from client's point of view)\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not display information about all network packets.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Display information about all network packets.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Display information about incoming network packets only.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Display information about outgoing network packets only.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"showpause\": {\n      \"default\": \"1\",\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Never display the pause icon.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display that the pause icon is displayed when the game is paused.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"showram\": {\n      \"group-id\": \"40\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Never display the ram icon.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Display the ram icon when running out of memory.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"showturtle\": {\n      \"default\": \"0\",\n      \"desc\": \"Sets the value at which the turtle icon will be displayed. For example, if showturtle is set to 70, the turtle will appear when the FPS drops to 70 or below.\",\n      \"group-id\": \"40\",\n      \"type\": \"float\"\n    },\n    \"skill\": {\n      \"default\": \"1\",\n      \"group-id\": \"43\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"easy level for singleplaying.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Normal level for singleplaying.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Hard level for singleplaying.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Nightmare level for singleplaying.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"skin\": {\n      \"default\": \"\",\n      \"desc\": \"Sets the skin name for the player.\",\n      \"group-id\": \"37\",\n      \"type\": \"string\"\n    },\n    \"skin_browser_democolor\": {\n      \"desc\": \"Color of the demo entries in the skin browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"skin_browser_dircolor\": {\n      \"desc\": \"Color of the dir entries in the skin browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"skin_browser_interline\": {\n      \"desc\": \"Size of the space between entries in the skin browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"integer\"\n    },\n    \"skin_browser_scrollnames\": {\n      \"desc\": \"Toggle scrolling of the filenames in the skin browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"skin_browser_selectedcolor\": {\n      \"desc\": \"Color of the selected entries in the skin browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"skin_browser_showdate\": {\n      \"desc\": \"Toggle the date column in the skin browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"skin_browser_showsize\": {\n      \"desc\": \"Toggle the file size column in the skin browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"skin_browser_showstatus\": {\n      \"desc\": \"Toggle the display of the status bar in the skin browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"skin_browser_showtime\": {\n      \"desc\": \"Toggle the time column in the skin browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"skin_browser_sortmode\": {\n      \"desc\": \"Sorting mode in the skin browser.\\nEach number represents one column. Their order represents the priority of the sorting.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"skin_browser_stripnames\": {\n      \"desc\": \"Toggle stripping of the filenames in the skin browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"skin_browser_zipcolor\": {\n      \"desc\": \"Color of the zip entries in the skin browser.\",\n      \"group-id\": \"25\",\n      \"type\": \"string\"\n    },\n    \"spectator\": {\n      \"default\": \"\",\n      \"group-id\": \"37\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Join to server as player.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Join to server as spectator.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"spectator_password\": {\n      \"default\": \"\",\n      \"desc\": \"A password spectators must use to connect to local server.\",\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sshot_autoname\": {\n      \"default\": \"0\",\n      \"desc\": \"Add the map name as screenshot filename prefix.\",\n      \"group-id\": \"41\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sshot_dir\": {\n      \"default\": \"\",\n      \"desc\": \"Change the screenshot directory.\",\n      \"group-id\": \"41\",\n      \"type\": \"string\"\n    },\n    \"sshot_format\": {\n      \"default\": \"png\",\n      \"desc\": \"File format used when saving screenshots.\",\n      \"group-id\": \"41\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"TGA screenshots (gl only)\",\n          \"name\": \"tga\"\n        },\n        {\n          \"description\": \"PNG screenshots\",\n          \"name\": \"png\"\n        },\n        {\n          \"description\": \"JPEG screenshots (gl only)\",\n          \"name\": \"jpg\"\n        },\n        {\n          \"description\": \"Pcx screenshots (software only)\",\n          \"name\": \"pcx\"\n        }\n      ]\n    },\n    \"sv_accelerate\": {\n      \"desc\": \"Sets the acceleration value for the player.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_admininfo\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_aim\": {\n      \"desc\": \"Sets the value for auto-aiming leniency.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_airaccelerate\": {\n      \"desc\": \"Sets how quickly the player accelerates in air.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_allowlastscores\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_bigcoords\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_cheats\": {\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable cheats.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable cheats. (need map realoading)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sv_cpserver\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_crypt_rcon\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_cullentities\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_default_name\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_demoClearOld\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_demoDir\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_demoExtraNames\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_demoIdlefps\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_demoMaxDirSize\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_demoMaxSize\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_demoPrefix\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_demoRegexp\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_demoSuffix\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_demoUseCache\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_demofps\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_demonovis\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_demopings\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_demotxt\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_downloadchunksperframe\": {\n      \"desc\": \"Limits the speed of the chunked downloads.\",\n      \"group-id\": \"43\",\n      \"remarks\": \"Server-side.\\nClients can set high amount of chunks per frame allowed and make your data eat connection traffic rapidly. Use this variable to prevent this.\",\n      \"type\": \"\"\n    },\n    \"sv_enable_cmd_minping\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_enableprofile\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_fastconnect\": {\n      \"desc\": \"actually no help.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_forcenick\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_forcenqprogs\": {\n      \"desc\": \"Force loading of NetQuake progs - if progs.dat (typically from Quake 1 sigle player / mods) is present in the gamedir, it will be preferred over qwprogs.dat and spprogs.dat (QW game mods).\",\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sv_forcespec_onfull\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_friction\": {\n      \"desc\": \"Sets the friction value for the player.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_getrealip\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_gravity\": {\n      \"desc\": \"Sets the global value for the amount of gravity.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_hashpasswords\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_highchars\": {\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable high character color names for players.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sv_kicktop\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_kickuserinfospamcount\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_kickuserinfospamtime\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_ktpro_mode\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_loadentfiles\": {\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable custom map entity file support.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sv_logdir\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_login\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_mapcheck\": {\n      \"group-id\": \"43\",\n      \"remarks\": \"Note: A player who has edited his map files to cheat by removing textures from walls will not be able to join the server and play.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable map checksumming to check for players who edit maps to cheat.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sv_maxdownloadrate\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_maxlogsize\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_maxpitch\": {\n      \"desc\": \"server-side variable for setting maximum of view angles.\",\n      \"group-id\": \"43\",\n      \"remarks\": \"EZQuake and ZQuake (may be some other) clients understand this physics change.\\nOld clients will be clamped at [-70~80] (quakeworld default) view angles.\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"By default, in quakeworld maximum viewangle is '80'. You can set for example '90' then you will be able look directly to sky. q3 players should like it.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"sv_maxrate\": {\n      \"desc\": \"Maximum rate for clients.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_maxspeed\": {\n      \"desc\": \"Sets the maximum speed a player can move.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_maxtic\": {\n      \"desc\": \"The maximum amount of time in seconds before a client a receives an update from the server.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_maxuploadsize\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_maxvelocity\": {\n      \"desc\": \"Sets the maximum velocity an object can travel.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_minping\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_minpitch\": {\n      \"desc\": \"server-side variable for setting minimum of view angles.\",\n      \"group-id\": \"43\",\n      \"remarks\": \"EZQuake and ZQuake (may be some other) clients understand this physics change.\\nOld clients will be clamped at [-70~80] (quakeworld default) view angles.\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"By default, in quakeworld minimum viewangle is '-70'. You can set for example '-90' then you will be able look directly to floor. q3 players should like it.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"sv_mintic\": {\n      \"desc\": \"The minimum amount of time the server will wait before sending packets to a client.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_mod_msg_file\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_nailhack\": {\n      \"group-id\": \"43\",\n      \"remarks\": \"smoother and point in the right direction.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Allows nails in most cases to uses less bandwidth and to fly around.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sv_onDemoRemove\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_onRecordFinish\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_paused\": {\n      \"desc\": \"read-only variable that gives you current pause state (condition).\",\n      \"group-id\": \"43\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"pause is off.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"normal pause that can be set by 'pause' command.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"auto pause (single player only) when going into menus.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"both (normal pause + auto pause)\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"sv_phs\": {\n      \"group-id\": \"43\",\n      \"remarks\": \"of the map.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Has something to do with the table which is build at the loading time.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sv_progsname\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_progtype\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_qwfwd_port\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_rconlim\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_reconnectlimit\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_registrationinfo\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_sayteam_to_spec\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_serverip\": {\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"sv_showpackets\": {\n      \"description\": \"Dumps log of network activity to the console (from internal server's point of view)\",\n      \"group-id\": \"40\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not display information about all network packets.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Display information about all network packets.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Display information about incoming network packets only.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Display information about outgoing network packets only.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"sv_specprint\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_spectalk\": {\n      \"group-id\": \"43\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Players can't hear spectators.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Players can hear spectators.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sv_spectatormaxspeed\": {\n      \"desc\": \"Sets the maximum speed a spectator can move.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_speedcheck\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_stopspeed\": {\n      \"desc\": \"Sets the value that determines how fast the player should come to a complete stop.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_timeout\": {\n      \"desc\": \"Sets the amount of time in seconds before a client is considered disconnected if the server does not receive a packet.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_timestamplen\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_unfake\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_use_dns\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_use_internal_cmd_dl\": {\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"sv_wateraccelerate\": {\n      \"desc\": \"Sets the water acceleration value.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sv_waterfriction\": {\n      \"desc\": \"Sets the water friction value.\",\n      \"group-id\": \"43\",\n      \"type\": \"float\"\n    },\n    \"sw_contrast\": {\n      \"desc\": \"Change contrast.\",\n      \"group-id\": \"30\",\n      \"type\": \"float\"\n    },\n    \"sw_gamma\": {\n      \"desc\": \"Change gamma.\",\n      \"group-id\": \"30\",\n      \"type\": \"float\"\n    },\n    \"sys_command_line\": {\n      \"default\": \"\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"sys_disableWinKeys\": {\n      \"default\": \"0\",\n      \"group-id\": \"48\",\n      \"remarks\": \"Note: Windows keys are now bindable (LWINKEY, RWINKEY, WINKEY, POPUPMENU).\\nObviously only useful on NT/2K/XP with sys_disableWinKeys 1 (and possibly useful in Linux too!).\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Winkeys are processed by operating system.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Disable winkeys from alt-tabbing you (only works in NT/2K/XP, and only when the client is in focus).\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Disable winkeys from alt-tabbing you, when fullscreen.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"sys_disable_alt_enter\": {\n      \"default\": \"0\",\n      \"group-id\": \"48\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"alt-enter will toggle full-screen mode.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"alt-enter ignored by ezquake.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sys_highpriority\": {\n      \"default\": \"0\",\n      \"group-id\": \"48\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Sets process to normal priority.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Sets process to high priority.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Sets process to low priority.\",\n          \"name\": \"-1\"\n        }\n      ]\n    },\n    \"sys_inactivesleep\": {\n      \"default\": \"1\",\n      \"group-id\": \"48\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Prevent freeing of CPU when the client is minimized or not in focus.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable inactive sleeping.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sys_inactivesound\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables sounds when the application is not focused.\",\n      \"group-id\": \"48\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sys_restart_on_error\": {\n      \"default\": \"0\",\n      \"group-id\": \"23\",\n      \"type\": \"boolean\"\n    },\n    \"sys_select_timeout\": {\n      \"default\": \"10000\",\n      \"group-id\": \"23\",\n      \"type\": \"\"\n    },\n    \"sys_update_check\": {\n      \"default\": \"1\",\n      \"desc\": \"Checks if the the client is outdated. If a new version is found, this will be presented above the in-game menu. The version check performs a HTTP request to GitHub once per launch without introducing any application start delay. Any error querying the latest version assumes the current client is the latest.\",\n      \"group-id\": \"48\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Deny update check.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Allow update check.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"sys_yieldcpu\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls CPU sharing when client is waiting to draw a frame.\\nWhen disabled, client will run a loop, actively waiting for the time to draw a new frame.\\nWhen enabled, client will call system sleep function during the waiting, which will cause the thread to be deactivated for a while - leading to significantly lower CPU usage in most cases.\",\n      \"group-id\": \"48\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Do not sleep between frames, use CPU as much as possible.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Sleep between frames, make CPU available for other processes.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"team\": {\n      \"default\": \"\",\n      \"desc\": \"Set the team name.\",\n      \"group-id\": \"37\",\n      \"type\": \"string\"\n    },\n    \"teambothskin\": {\n      \"default\": \"\",\n      \"desc\": \"Overrides the team quad pent skin so you can define the skin which applies to team quads pents.\",\n      \"group-id\": \"44\",\n      \"type\": \"string\"\n    },\n    \"teambottomcolor\": {\n      \"default\": \"-1\",\n      \"desc\": \"Determines the color of the pants of the teammates you see.\\nOverrides player's skin settings.\",\n      \"group-id\": \"44\",\n      \"remarks\": \"To be able to use RGB 24bit colors, look for r_teamskincolor variable.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disabled\",\n          \"name\": \"-1\"\n        },\n        {\n          \"description\": \"White\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Brown\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Lavender\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Khaki\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Red\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Light Brown\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Peach\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Light Peach\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Purple\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Dark Purple\",\n          \"name\": \"9\"\n        },\n        {\n          \"description\": \"Tan\",\n          \"name\": \"10\"\n        },\n        {\n          \"description\": \"Green\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"Yellow\",\n          \"name\": \"12\"\n        },\n        {\n          \"description\": \"Blue\",\n          \"name\": \"13\"\n        },\n        {\n          \"description\": \"Orange.\",\n          \"name\": \"14\"\n        },\n        {\n          \"description\": \"Bright red.\",\n          \"name\": \"15\"\n        },\n        {\n          \"description\": \"Black.\",\n          \"name\": \"16\"\n        }\n      ]\n    },\n    \"teamforceskins\": {\n      \"default\": \"0\",\n      \"desc\": \"Allows you to set different skin for every team player even if they all have same skin names set or use names of skins that you do not have in your Quake dir.\",\n      \"group-id\": \"44\",\n      \"remarks\": \"Read \\\"Player skins\\\" manual page for more info on skin rules.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disabled.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Use player's name as the name of the skin to be used.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Use player's userid number (only useful for video capturing)\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Assign skins \\\"t1\\\", \\\"t2\\\", \\\"t3\\\", etc. to your teammates.\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"teamlock\": {\n      \"default\": \"0\",\n      \"desc\": \"Observing feature. Do not toggle enemy recognition when you switch players.\\nOne team will always remain marked as enemy even if you spec their players.\",\n      \"group-id\": \"44\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"Do not lock team/enemy\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Lock team/enemy\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Lock the specified team name as \\\"team\\\"\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"teampentskin\": {\n      \"default\": \"\",\n      \"desc\": \"Overrides the team pent skin so you can define the skin which applies to team pents.\",\n      \"group-id\": \"44\",\n      \"type\": \"string\"\n    },\n    \"teamplay\": {\n      \"default\": \"0\",\n      \"desc\": \"Teamplay mode.\",\n      \"group-id\": \"43\",\n      \"remarks\": \"Nowadays only value 2 is used.\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"No teamplay\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Teamplay with no suicides or teamkills\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Teamplay with teamkills\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"teamquadskin\": {\n      \"default\": \"\",\n      \"desc\": \"Overrides the team quad skin so you can define the skin which applies to team quads.\",\n      \"group-id\": \"44\",\n      \"type\": \"string\"\n    },\n    \"teamskin\": {\n      \"default\": \"\",\n      \"desc\": \"Overrides the team skin so you can define the skin which applies to team mates.\",\n      \"group-id\": \"44\",\n      \"type\": \"string\"\n    },\n    \"teamtopcolor\": {\n      \"default\": \"-1\",\n      \"desc\": \"Determines the color of the shirt of the teammates you see.\\nOverrides player's skin settings.\",\n      \"group-id\": \"44\",\n      \"remarks\": \"To be able to use RGB 24bit colors, look for r_teamskincolor variable.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disabled\",\n          \"name\": \"-1\"\n        },\n        {\n          \"description\": \"White\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Brown\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Lavender\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Khaki\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Red\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Light Brown\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Peach\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Light Peach\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Purple\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Dark Purple\",\n          \"name\": \"9\"\n        },\n        {\n          \"description\": \"Tan\",\n          \"name\": \"10\"\n        },\n        {\n          \"description\": \"Green\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"Yellow\",\n          \"name\": \"12\"\n        },\n        {\n          \"description\": \"Blue\",\n          \"name\": \"13\"\n        },\n        {\n          \"description\": \"Orange.\",\n          \"name\": \"14\"\n        },\n        {\n          \"description\": \"Bright red.\",\n          \"name\": \"15\"\n        },\n        {\n          \"description\": \"Black.\",\n          \"name\": \"16\"\n        }\n      ]\n    },\n    \"telnet_log_level\": {\n      \"default\": \"0\",\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"telnet_password\": {\n      \"desc\": \"Password for login via telnet.\\nNot currently used.\",\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"timelimit\": {\n      \"default\": \"0\",\n      \"desc\": \"Number of minutes the match will take.\",\n      \"group-id\": \"43\",\n      \"type\": \"integer\"\n    },\n    \"timeout\": {\n      \"default\": \"65\",\n      \"group-id\": \"43\",\n      \"type\": \"\"\n    },\n    \"topcolor\": {\n      \"default\": \"\",\n      \"desc\": \"Sets the shirt color.\",\n      \"group-id\": \"37\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"White.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Brown.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Lavender.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Khaki.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Red.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Lt Brown.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Peach.\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Lt Peach.\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Purple.\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Dk Purple.\",\n          \"name\": \"9\"\n        },\n        {\n          \"description\": \"Tan.\",\n          \"name\": \"10\"\n        },\n        {\n          \"description\": \"Green.\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"Yellow.\",\n          \"name\": \"12\"\n        },\n        {\n          \"description\": \"Blue.\",\n          \"name\": \"13\"\n        },\n        {\n          \"description\": \"Orange.\",\n          \"name\": \"14\"\n        },\n        {\n          \"description\": \"Bright red.\",\n          \"name\": \"15\"\n        },\n        {\n          \"description\": \"Black.\",\n          \"name\": \"16\"\n        }\n      ]\n    },\n    \"tp_forceTriggers\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls whether f_took, f_death etc are forced to execute even if the game isn't a team game.\",\n      \"group-id\": \"49\",\n      \"type\": \"boolean\"\n    },\n    \"tp_loadlocs\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls whether to load locs for teamplay reporting.\\nNote: The locs should be in your \\\"/id1/locs/\\\",\\n\\\"/ezquake/locs\\\" or \\\"/qw/locs\\\" folder.\",\n      \"group-id\": \"49\",\n      \"type\": \"boolean\"\n    },\n    \"tp_msgtriggers\": {\n      \"default\": \"1\",\n      \"desc\": \"Switches on/off message triggers.\",\n      \"group-id\": \"49\",\n      \"remarks\": \"For ruleset 'Smackdown' message triggers always are off.\",\n      \"type\": \"boolean\"\n    },\n    \"tp_name_armor\": {\n      \"default\": \"armor\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_armortype_ga\": {\n      \"default\": \"{&c0b0g&cfff}\",\n      \"desc\": \"Customizes output of %A macro for green armor.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_armortype_ra\": {\n      \"default\": \"{&cf00r&cfff}\",\n      \"desc\": \"Customizes output of %A macro for red armor.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_armortype_ya\": {\n      \"default\": \"{&cff0y&cfff}\",\n      \"desc\": \"Customizes output of %A macro for yellow armor.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_at\": {\n      \"default\": \"at\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_axe\": {\n      \"default\": \"axe\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_backpack\": {\n      \"default\": \"{&cf2apack&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_cells\": {\n      \"default\": \"cells\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_disp\": {\n      \"default\": \"dispenser\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_enemy\": {\n      \"default\": \"{&cf00enemy&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_eyes\": {\n      \"default\": \"{&cff0eyes&cfff}\",\n      \"desc\": \"Sets the name for ring (invisibility) powerup for teamplay messages.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"common values are 'eyes' or 'ring'\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"tp_name_filter\": {\n      \"default\": \"\",\n      \"desc\": \"TF only - added to end of tp_msgtfconced message/\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_flag\": {\n      \"default\": \"flag\",\n      \"desc\": \"Customizes item/\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_ga\": {\n      \"default\": \"{&c0b0ga&cfff}\",\n      \"desc\": \"Customizes item/\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_gl\": {\n      \"default\": \"gl\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_health\": {\n      \"default\": \"health\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_lg\": {\n      \"default\": \"{&c2aalg&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_mh\": {\n      \"default\": \"{&c0a0mega&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_nails\": {\n      \"default\": \"nails\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_ng\": {\n      \"default\": \"ng\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_none\": {\n      \"default\": \"\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_nothing\": {\n      \"default\": \"nothing\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_pent\": {\n      \"default\": \"{&cf00pent&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_pented\": {\n      \"default\": \"{&cf00pented&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_quad\": {\n      \"default\": \"{&c05fquad&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_quaded\": {\n      \"default\": \"{&c05fquaded&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_ra\": {\n      \"default\": \"{&cf00ra&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_ring\": {\n      \"default\": \"{&cff0ring&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_rl\": {\n      \"default\": \"{&cf13rl&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_rlg\": {\n      \"default\": \"{&cf13rl&cfff}{&c2aag&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_rockets\": {\n      \"default\": \"rox\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_rune1\": {\n      \"default\": \"{&c0f0resistance&cfff}\",\n      \"desc\": \"Sets name of the first rune (Resistance Rune) used for teamplay messages.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_rune2\": {\n      \"default\": \"{&cf00strength&cfff}\",\n      \"desc\": \"Sets name of the second rune (Strength Rune) used for teamplay messages.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_rune3\": {\n      \"default\": \"{&cff0haste&cfff}\",\n      \"desc\": \"Sets name of the third rune (Haste Rune) used for teamplay messages.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_rune4\": {\n      \"default\": \"{&c0ffregeneration&cfff}\",\n      \"desc\": \"Sets name of the fourth rune (Regeneration Rune) used for teamplay messages.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_sentry\": {\n      \"default\": \"sentry gun\",\n      \"desc\": \"Sets the name for sentry tower in teamplay messages.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"common values are 'sentry' or 'sentry gun'.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"tp_name_separator\": {\n      \"default\": \"/\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_sg\": {\n      \"default\": \"sg\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_shells\": {\n      \"default\": \"shells\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_sng\": {\n      \"default\": \"sng\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_someplace\": {\n      \"default\": \"someplace\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_ssg\": {\n      \"default\": \"ssg\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_status_blue\": {\n      \"default\": \"$B\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_status_green\": {\n      \"default\": \"$G\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_status_red\": {\n      \"default\": \"$R\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_status_white\": {\n      \"default\": \"$W\",\n      \"desc\": \"Currently unused.\",\n      \"group-id\": \"13\",\n      \"remarks\": \"tp_name_status_ cvars are used by the client when generating teamplay messages.\",\n      \"type\": \"string\"\n    },\n    \"tp_name_status_yellow\": {\n      \"default\": \"$Y\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_suit\": {\n      \"default\": \"suit\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_teammate\": {\n      \"default\": \"\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_weapon\": {\n      \"default\": \"weapon\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_name_ya\": {\n      \"default\": \"{&cff0ya&cfff}\",\n      \"desc\": \"Customizes item.\",\n      \"group-id\": \"13\",\n      \"type\": \"string\"\n    },\n    \"tp_need_cells\": {\n      \"default\": \"13\",\n      \"desc\": \"Customizes the %u macro.\",\n      \"group-id\": \"14\",\n      \"type\": \"float\"\n    },\n    \"tp_need_ga\": {\n      \"default\": \"60\",\n      \"desc\": \"Customizes the %u macro.\",\n      \"group-id\": \"14\",\n      \"type\": \"float\"\n    },\n    \"tp_need_health\": {\n      \"default\": \"50\",\n      \"desc\": \"Customizes the %u macro.\",\n      \"group-id\": \"14\",\n      \"type\": \"float\"\n    },\n    \"tp_need_nails\": {\n      \"default\": \"0\",\n      \"desc\": \"Customizes the %u macro.\",\n      \"group-id\": \"14\",\n      \"type\": \"float\"\n    },\n    \"tp_need_ra\": {\n      \"default\": \"120\",\n      \"desc\": \"Customizes the %u macro.\",\n      \"group-id\": \"14\",\n      \"type\": \"float\"\n    },\n    \"tp_need_rl\": {\n      \"default\": \"1\",\n      \"desc\": \"Customizes the %u macro.\",\n      \"group-id\": \"14\",\n      \"type\": \"float\"\n    },\n    \"tp_need_rockets\": {\n      \"default\": \"5\",\n      \"desc\": \"Customizes the %u macro.\",\n      \"group-id\": \"14\",\n      \"type\": \"float\"\n    },\n    \"tp_need_shells\": {\n      \"default\": \"0\",\n      \"desc\": \"Customizes the %u macro.\",\n      \"group-id\": \"14\",\n      \"type\": \"float\"\n    },\n    \"tp_need_weapon\": {\n      \"default\": \"87\",\n      \"desc\": \"Customizes the %u macro.\",\n      \"group-id\": \"14\",\n      \"type\": \"float\"\n    },\n    \"tp_need_ya\": {\n      \"default\": \"80\",\n      \"desc\": \"Customizes the %u macro.\",\n      \"group-id\": \"14\",\n      \"type\": \"float\"\n    },\n    \"tp_pointpriorities\": {\n      \"default\": \"0\",\n      \"desc\": \"You can choose the way the client decides what object you want to point with $point macro.\",\n      \"group-id\": \"49\",\n      \"remarks\": \"Priorities of object are given in tp_point command. You have to type all objects manually in tp_point command.\\n\\nExample: tp_point quad ra mh health\\nIn this example, quad has the highest priority and $point will always return quad if quad ra mh are all in your view.\\n\\nExample: tp_point all\\nYou never know what object will be pointed because all objects get the same priority.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Original, default behavior. The closest object will be chosen.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Object with highest priority will be chosen. First items in tp_point have highest priorities.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"tp_tookpriorities\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls whether the last item taken macro (%X or $took) uses the order of tp_took as item pickup priority.\",\n      \"group-id\": \"49\",\n      \"remarks\": \"When enabled, earlier items in tp_took have higher priority and can prevent lower priority pickups from replacing $took within tp_tooktimeout.\\n\\nExample:\\ntp_took powerups rl lg ra ya ga mega pack gl sng ng ssg rockets cells nails shells health\\ntp_tookpriorities 1\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Original, default behavior. The latest pickup replaces the previous one.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Use tp_took order as pickup priority.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"tp_pointtimeout\": {\n      \"default\": \"15\",\n      \"desc\": \"Time (in seconds) before the last pointed item (%j) is cleared.\",\n      \"group-id\": \"49\",\n      \"type\": \"float\"\n    },\n    \"tp_poweruptextstyle\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls whether $tp_powerups (used in built-in binds) uses long or short-form powerup names.\",\n      \"group-id\": \"14\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Long-form names (quad, pent, ring)\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Short-form names (q, p, r)\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"tp_soundtrigger\": {\n      \"default\": \"~\",\n      \"desc\": \"SoundTrigger like proxys.\\nA SoundTrigger must be terminated by either a CR, LF or filter. The .wav extension is not required at the end.\\nTrigger is a character before the sound file.\\n\\nExample:\\nfilter #a\\nsay_team I'm at %l ~location.wav\\\"\\nsay_team ~location.wav$\\\\me: I'm at %l #a\\\"\\nsay_team ~location$\\\\me: I'm at %l #a\\\"\\nsay_team I'm at %l ~location #a\\\"\",\n      \"group-id\": \"49\",\n      \"type\": \"string\"\n    },\n    \"tp_tooktimeout\": {\n      \"default\": \"15\",\n      \"desc\": \"Time (in seconds) before the last item taken (%X) is cleared.\",\n      \"group-id\": \"49\",\n      \"type\": \"float\"\n    },\n    \"tp_triggers\": {\n      \"default\": \"1\",\n      \"group-id\": \"49\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable tp triggers.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable tp triggers.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"tp_weapon_order\": {\n      \"default\": \"78654321\",\n      \"desc\": \"This allows you to define the order from best to worst weapon.\\nThe default value, \\\"78564321\\\", means that the Rocket Launcher is the best weapon (impulse 7), then Lightning Gun (impulse 8), Super Nailgun (impulse 5), Grenade Launcher (impulse 6), Nailgun (impulse 4), Super Shotgun (impulse 3), Shotgun (impulse 2), Axe (impulse 1).\",\n      \"group-id\": \"49\",\n      \"type\": \"string\"\n    },\n    \"userdir\": {\n      \"desc\": \"userdir [dir [type]]\\nAddition of the given directory to the list searched files.\",\n      \"group-id\": \"33\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"<quake_dir>/<gamedir>/<dir>\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"<quake_dir>/<dir>/<gamedir>\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"<quake_dir>/qw/<dir>/<gamedir>\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"<quake_dir>/qw/<dir>\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"<quake_dir>/<dir>\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"$HOME/qw/<dir>\",\n          \"name\": \"5\"\n        }\n      ]\n    },\n    \"v_centermove\": {\n      \"default\": \"0.15\",\n      \"desc\": \"This variable sets the distance that the player must move before the screen automatically centers itself when \\\"lookspring\\\" is set to \\\"1\\\".\\nThis variable is only useful to people who only play with the keyboard, have \\\"lookspring\\\" set to \\\"1\\\" and do not have \\\"+klook\\\" or \\\"+mlook\\\" enabled at that time.\\nWhen the player nmoves the distance specified in this variable the screen will pop back to the center position.\",\n      \"group-id\": \"53\",\n      \"type\": \"float\"\n    },\n    \"v_centerspeed\": {\n      \"default\": \"500\",\n      \"desc\": \"This variable sets how quickly your view returns to the center after looking up or down and moving after that and \\\"lookspring\\\" is set to \\\"1\\\".\\nThis variable is only useful to people who only play with the keyboard, have \\\"lookspring\\\" set to \\\"1\\\" and do not have \\\"+klook\\\" or \\\"+mlook\\\" enabled at that time.\\nWhen the player moves the distance specified in this variable the screen will pop back to the center position.\",\n      \"group-id\": \"53\",\n      \"type\": \"float\"\n    },\n    \"v_contentblend\": {\n      \"default\": \"0.2\",\n      \"desc\": \"Turning this variable on will temporarily change the hue of your screen when inside liquid (water/slime/lava) (requires gl_polyblend 1)\",\n      \"group-id\": \"39\",\n      \"remarks\": \"Specify a percentage value, between 0 and 1.  0 turns the effect off.\",\n      \"type\": \"float\"\n    },\n    \"v_damagecshift\": {\n      \"default\": \"0.2\",\n      \"desc\": \"This variable (0..1) will temporarily add a red hue to your screen when you've taken damage. Needs gl_polyblend 1.\",\n      \"group-id\": \"39\",\n      \"type\": \"float\"\n    },\n    \"v_dlightcolor\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls if the color of a light affects your view\\nRequires gl_polyblend & gl_flashblend set\",\n      \"group-id\": \"39\",\n      \"type\": \"boolean\"\n    },\n    \"v_dlightcshift\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls when palette will change when inside a light bubble.\\nAlso need to set gl_flashblend 1.\",\n      \"group-id\": \"39\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Off.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"On.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"On, but excludes lights around players.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"v_dlightcshiftpercent\": {\n      \"default\": \"0.5\",\n      \"desc\": \"Strength of effect of v_dlightcshift, as a percentage\",\n      \"group-id\": \"39\",\n      \"type\": \"float\"\n    },\n    \"v_gunkick\": {\n      \"default\": \"0\",\n      \"desc\": \"Kickback effect when the weapon is fired.\",\n      \"group-id\": \"53\",\n      \"type\": \"float\"\n    },\n    \"v_idlescale\": {\n      \"default\": \"0\",\n      \"desc\": \"This variable controls the amount that the screen should sway automatically.\\nWhen this variable is set to a value above \\\"0\\\" the screen will start swaying in all directions, similar as if the player was drunk of had a concussion.\\nIf you want to have some real fun, set this variable to a value of \\\"100\\\" and run around some maps.\\nThe larger the value for this variable the more the screen will sway from side to side.\",\n      \"group-id\": \"53\",\n      \"type\": \"integer\"\n    },\n    \"v_ipitch_cycle\": {\n      \"default\": \"1\",\n      \"desc\": \"This variable controls the speed at which the screen should sway up and down when \\\"v_idlescale\\\" is set to a value above \\\"0\\\".\",\n      \"group-id\": \"53\",\n      \"type\": \"float\"\n    },\n    \"v_ipitch_level\": {\n      \"default\": \"0.3\",\n      \"desc\": \"This variable controls the distance that the screen should sway up and down when \\\"v_idlescale\\\" is set to a value above \\\"0\\\".\",\n      \"group-id\": \"53\",\n      \"type\": \"float\"\n    },\n    \"v_iroll_cycle\": {\n      \"default\": \"0.5\",\n      \"desc\": \"This variable controls the speed at which the screen should roll clockwise and counter clockwise when \\\"v_idlescale\\\" is set to a value above \\\"0\\\".\",\n      \"group-id\": \"53\",\n      \"type\": \"float\"\n    },\n    \"v_iroll_level\": {\n      \"default\": \"0.1\",\n      \"desc\": \"This variable controls the distance the screen should roll clockwise and counter clockwise when \\\"v_idlescale\\\" is set to a value above \\\"0\\\".\",\n      \"group-id\": \"53\",\n      \"type\": \"float\"\n    },\n    \"v_iyaw_cycle\": {\n      \"default\": \"2\",\n      \"desc\": \"Sets how quickly you look left and right when v_idlescale is active.\",\n      \"group-id\": \"53\",\n      \"type\": \"float\"\n    },\n    \"v_iyaw_level\": {\n      \"default\": \"0.3\",\n      \"desc\": \"Sets how far you look left and right when v_idlescale is active.\",\n      \"group-id\": \"53\",\n      \"type\": \"float\"\n    },\n    \"v_kickpitch\": {\n      \"default\": \"0.0\",\n      \"desc\": \"This variable controls the distance that the screen should move up or down when the player is shot.\\nMost players set this variable to \\\"0\\\" in order to remove the screen tilting effect when they are injured by a shot.\\nThis allows them to keep a perfect aim while being fired upon.\",\n      \"group-id\": \"53\",\n      \"remarks\": \"v_kicktime must be >0 for this to take effect.\",\n      \"type\": \"float\"\n    },\n    \"v_kickroll\": {\n      \"default\": \"0.0\",\n      \"desc\": \"This variable controls the distance that the screen should roll clockwise or counter clockwise when the player is shot.\\nMost players set this variable to \\\"0\\\" in order to remove the screen tilting effect when they are injured by a shot.\\nThis allows them to keep a perfect aim while being fired upon.\",\n      \"group-id\": \"53\",\n      \"remarks\": \"v_kicktime must be >0 for this to take effect.\",\n      \"type\": \"float\"\n    },\n    \"v_kicktime\": {\n      \"default\": \"0.0\",\n      \"desc\": \"This variable controls the amount of time v_kickpitch and v_kickroll take effect.\",\n      \"group-id\": \"53\",\n      \"type\": \"float\"\n    },\n    \"v_pentcshift\": {\n      \"default\": \"0.5\",\n      \"desc\": \"This variable (0..1) will add a temporary red hue to your screen if you are carry the Pent powerup. Requires gl_polyblend 1.\",\n      \"group-id\": \"39\",\n      \"type\": \"float\"\n    },\n    \"v_quadcshift\": {\n      \"default\": \"0.5\",\n      \"desc\": \"This variable (0..1) will add a temporary blue hue to your screen if you are carry the Quad powerup. Requires gl_polyblend 1.\",\n      \"group-id\": \"39\",\n      \"type\": \"float\"\n    },\n    \"v_ringcshift\": {\n      \"default\": \"0.5\",\n      \"desc\": \"This variable (0..1) will add a temporary yellow hue to your screen if you are carry the Ring powerup. Requires gl_polyblend 1.\",\n      \"group-id\": \"39\",\n      \"type\": \"float\"\n    },\n    \"v_suitcshift\": {\n      \"default\": \"0.5\",\n      \"desc\": \"This variable (0..1) will add a temporary green hue to your screen if you are carry the Suit powerup. Requires gl_polyblend 1.\",\n      \"group-id\": \"39\",\n      \"type\": \"float\"\n    },\n    \"v_viewheight\": {\n      \"default\": \"0\",\n      \"desc\": \"New variable v_viewheight can be used to get the effect of negative/zero values \\nof cl_bobcycle etc in qw 2.33.\\nNegative values of v_viewheight = lower viewheight, \\npositive = higher.\\nCan't go below -7 or above 4 because that's the range you can get with qw 2.33 by exploiting the bob cycle bug.\\n\\nUsing v_viewheight automatically turns off weapon model bobbing (cl_bob*).\",\n      \"group-id\": \"53\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Range -7 to 4.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"vid_24bit_depth\": {\n      \"default\": \"1\",\n      \"desc\": \"Requests a 24-bit depth-buffer if possible.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Visual artifacts may be visible if using a 16-bit depth-buffer (e.g. entities disappearing at a distance).\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Size of depth-buffer will be system default.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Request 24-bit depth-buffer.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"vid_colorbits\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines the color mode.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Highest quality is \\\"24\\\"\",\n      \"type\": \"integer\"\n    },\n    \"vid_conaspect\": {\n      \"desc\": \"Keep the screen proportions to given aspect ratio.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Always changes vid_conheight when you change vid_conwidth.\",\n      \"type\": \"string\",\n      \"values\": [\n        {\n          \"description\": \"4:3 or 16:9 or 16:10 etc.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"vid_config_x\": {\n      \"desc\": \"This variable sets the width of the vid_mode 2 window, the minimum possible value is \\\"640\\\".\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"vid_config_y\": {\n      \"desc\": \"This variable sets the width of the vid_mode 2 window, the minimum possible value is \\\"400\\\"\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"vid_conheight\": {\n      \"default\": \"0\",\n      \"desc\": \"Height of the text layer.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Cannot be higher than the height of the current game resolution.\",\n      \"type\": \"integer\"\n    },\n    \"vid_conscale\": {\n      \"default\": \"2.0\",\n      \"desc\": \"If vid_conwidth & vid_conheight are set to 0, vid_conheight will be scaled by the value of this variable.\",\n      \"group-id\": \"52\",\n      \"type\": \"float\"\n    },\n    \"vid_conwidth\": {\n      \"default\": \"0\",\n      \"desc\": \"Height of the text layer.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Cannot be higher than the height of the current game resolution.\",\n      \"type\": \"integer\"\n    },\n    \"vid_customheight\": {\n      \"desc\": \"Screen resolution width for custom screen mode.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"vid_mode must be -1.\",\n      \"type\": \"integer\"\n    },\n    \"vid_customwidth\": {\n      \"desc\": \"Screen resolution height for custom screen mode.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"vid_mode must be -1.\",\n      \"type\": \"integer\"\n    },\n    \"vid_depthbits\": {\n      \"group-id\": \"31\",\n      \"type\": \"\"\n    },\n    \"vid_displayfrequency\": {\n      \"default\": \"0\",\n      \"desc\": \"Sets horizontal frequency for your monitor (in hertz - Hz).\",\n      \"group-id\": \"52\",\n      \"type\": \"integer\"\n    },\n    \"vid_displaynumber\": {\n      \"default\": \"0\",\n      \"desc\": \"Display/screen to use when in fullscreen mode.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Use vid_displaylist command to enumerate displays.\",\n      \"type\": \"integer\"\n    },\n    \"vid_flashonactivity\": {\n      \"default\": \"1\",\n      \"desc\": \"When there is activity in the server, ezQuake's taskbar window will flash.\\nActivity includes chat, high priority messages, disconnection from server.\",\n      \"group-id\": \"52\",\n      \"type\": \"float\"\n    },\n    \"vid_forcerestoregamma\": {\n      \"desc\": \"Force restore gamma after returning to minimized application.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Might be usefull if you have ATI card.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Old way of restoring gamma.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Force restore in 1 sec.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"vid_framebuffer\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables rendering to custom Framebuffer Objects. ezQuake uses these FBOs for improved color, \\\"render scale\\\" control, and other post-processing effects.\",\n      \"group-id\": \"52\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Only uses the default framebuffer.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Render everything to a FBO.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Use separate FBOs for the 3D scene and HUD (allows scaling the two separately).\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"vid_framebuffer_blit\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles blitting from the FBO to the front buffer instead of flipping buffers.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Only available with \\\"vid_framebuffer 1\\\" + \\\"vid_software_palette 0\\\" and a supported GPU.\",\n      \"type\": \"boolean\"\n    },\n    \"vid_framebuffer_depthformat\": {\n      \"default\": \"0\",\n      \"desc\": \"Configures the depth-buffer precision for the framebuffer.\",\n      \"group-id\": \"52\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Use the best precision available (up to 32-bit float).\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Use 16-bit float precision.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Use 24-bit float precision.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Use 32-bit precision.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Use 32-bit float precision.\",\n          \"name\": \"4\"\n        }\n      ]\n    },\n    \"vid_framebuffer_fxaa\": {\n      \"default\": \"0\",\n      \"desc\": \"Fast Approximate Anti-Aliasing. Enable vid_framebuffer 2 to avoid filtering HUD. Typically between 1-6.\",\n      \"group-id\": \"52\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Disabled.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Medium dither, quality 1 (fastest).\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Medium dither, quality 2.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Medium dither, quality 3.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Medium dither, quality 4.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Medium dither, quality 5.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Medium dither, quality 6 (highest quality).\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Less dither, quality 1 (fastest).\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"Less dither, quality 2.\",\n          \"name\": \"8\"\n        },\n        {\n          \"description\": \"Less dither, quality 3.\",\n          \"name\": \"9\"\n        },\n        {\n          \"description\": \"Less dither, quality 4.\",\n          \"name\": \"10\"\n        },\n        {\n          \"description\": \"Less dither, quality 5.\",\n          \"name\": \"11\"\n        },\n        {\n          \"description\": \"Less dither, quality 6.\",\n          \"name\": \"12\"\n        },\n        {\n          \"description\": \"Less dither, quality 7.\",\n          \"name\": \"13\"\n        },\n        {\n          \"description\": \"Less dither, quality 8.\",\n          \"name\": \"14\"\n        },\n        {\n          \"description\": \"Less dither, quality 9.\",\n          \"name\": \"15\"\n        },\n        {\n          \"description\": \"Less dither, quality 10 (highest quality).\",\n          \"name\": \"16\"\n        },\n        {\n          \"description\": \"No dither (expensive).\",\n          \"name\": \"17\"\n        }\n      ]\n    },\n    \"vid_framebuffer_hdr\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables increased, 16-bit float HDR color precision for the framebuffer. This reduces color banding, and allows e.g. tone mapping in post-processing.\",\n      \"group-id\": \"52\",\n      \"type\": \"boolean\"\n    },\n    \"vid_framebuffer_hdr_tonemap\": {\n      \"default\": \"0\",\n      \"desc\": \"Toggles tone mapping in HDR mode.\",\n      \"group-id\": \"52\",\n      \"type\": \"boolean\"\n    },\n    \"vid_framebuffer_height\": {\n      \"default\": \"0\",\n      \"desc\": \"Sets a custom height for the FBO (200 pixels minimum).\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Setting vid_framebuffer_scale overrides custom FBO width/height.\",\n      \"type\": \"integer\"\n    },\n    \"vid_framebuffer_multisample\": {\n      \"default\": \"0\",\n      \"desc\": \"The number of samples used around each pixel for anti-aliasing when using custom FBOs.\",\n      \"group-id\": \"52\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Range 0 to 16.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"vid_framebuffer_scale\": {\n      \"default\": \"1\",\n      \"desc\": \"Scales the FBOs down/up to the screen resolution. Similar to \\\"render scale\\\" in other games if combined with vid_framebuffer 2.\",\n      \"group-id\": \"52\",\n      \"type\": \"float\",\n      \"values\": [\n        {\n          \"description\": \"Custom scale with vid_framebuffer_scale_width/height.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Disable scaling.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Render to a larger FBO, then scale down.\",\n          \"name\": \"0.25 to 1\"\n        },\n        {\n          \"description\": \"Render to a smaller FBO, then scale up.\",\n          \"name\": \"1.x+\"\n        }\n      ]\n    },\n    \"vid_framebuffer_smooth\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles filtering when scaling the framebuffer up/down to the screen resolution (with either the vid_framebuffer_scale or width+height properties).\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Disable smoothing if you want to go for a chunky pixel look with FBO upscaling.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Nearest filtering - blocky scaling.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Linear filtering - smooth scaling.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"vid_framebuffer_sshotmode\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls screenshot resolution when using scaled framebuffers.\",\n      \"group-id\": \"41\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Fullscreen/windowed resolution.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Framebuffer resolution.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"vid_framebuffer_width\": {\n      \"default\": \"0\",\n      \"desc\": \"Sets a custom width for the FBO (320 pixels minimum).\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Setting vid_framebuffer_scale overrides custom FBO width/height.\",\n      \"type\": \"integer\"\n    },\n    \"vid_fullscreen\": {\n      \"default\": \"1\",\n      \"desc\": \"Toggles between fullscreen and windowed mode.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"alt + enter toggles the setting and applies the change without a vid_restart.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Windowed mode.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Fullscreen mode.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"vid_fullscreen_mode\": {\n      \"desc\": \"This variable sets the full screen video mode that the game will switch to when the \\\"vid_fullscreen\\\" command is executed.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"vid_gammacorrection\": {\n      \"default\": \"0\",\n      \"group-id\": \"35\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Do not use sRGB rendering context.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Request sRGB rendering context, use if possible.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Request sRGB rendering context, reject non-sRGB contexts.\",\n          \"name\": \"2\"\n        }\n      ]\n    },\n    \"vid_gl_core_profile\": {\n      \"default\": \"0\",\n      \"desc\": \"Controls the level of backwards compatibility of the Classic Renderer with legacy OpenGL features.\",\n      \"group-id\": \"52\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"More backwards compatibility with older OpenGL.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Use the OpenGL Core profile, which drops older features. Disables immediate mode.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"vid_grab_keyboard\": {\n      \"default\": \"0\",\n      \"desc\": \"If set, grabs keyboard hotkeys.\\nMay need to be enabled/disabled depending on your window manager.\",\n      \"group-id\": \"9\",\n      \"type\": \"boolean\"\n    },\n    \"vid_height\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines width of screen resolution when in fullscreen mode.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"This value is ignored if vid_usedesktopres is set.\",\n      \"type\": \"integer\"\n    },\n    \"vid_hwgamma_fps\": {\n      \"default\": \"60\",\n      \"desc\": \"Controls the frequency of hardware gamma updates per second. These updates have a performance cost, so it is preferred to not update every frame when playing at thousands of FPS.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"This can really hurt performance on screen flashing effects, i.e when taking damage - the worst moment to suffer framerate dips!\",\n      \"type\": \"integer\",\n      \"values\": [\n        {\n          \"description\": \"Update hardware gamma every frame.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Update hardware gamma at the specified frame rate.\",\n          \"name\": \"*\"\n        }\n      ]\n    },\n    \"vid_hwgammacontrol\": {\n      \"default\": \"0\",\n      \"group-id\": \"52\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Disables hwgamma control.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"Enables hwgamma control for fullscreen mode.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Enables hwgamma control for fullscreen and windowed mode.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Enables hwgamma even when window focus is lost but not minimized (Xorg only).\",\n          \"name\": \"3\"\n        }\n      ]\n    },\n    \"vid_minimize_on_focus_loss\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, ezQuake will minimise when running in fullscreen mode and it loses focus (e.g. alt-tab).\",\n      \"group-id\": \"52\",\n      \"type\": \"boolean\"\n    },\n    \"vid_mode\": {\n      \"desc\": \"This variables sets the next video mode to be used.\\nAny change of this variable will cause the video mode to be changed immediately.\",\n      \"group-id\": \"23\",\n      \"type\": \"float\"\n    },\n    \"vid_nopageflip\": {\n      \"desc\": \"This variable toggles the use of page-flipping during supported video modes.\\nBy default the game will allow for page-flipping for video modes that support this feature.\\nFor example if a given VESA mode can support page flipping, then it defaults to page-flipped operation.\\nA VESA mode can be forced to non-page-flipped operation by setting the \\\"vid_nopageflip\\\" variable to \\\"1\\\", then setting the mode (note that \\\"vid_nopageflip\\\" takes operation on the next, not the current video mode, and note that it then stays in effect permanently, even when the client is exited and restarted, unless it is manually set back to \\\"0\\\").\\nIf there is not enough memory for two pages in a VESA mode, or if the adapter doesn't support page flipping, then the mode will automatically be nnon-page-flipped.\\n\\nPage-flipping works by drawing multiple virtual game screens at the same time in the video memory and then switching among them to display the current gamescreen. Page-flipped modes thus use less system memory than non-page-flipped modes, but require more video memory.\\nWhen using page-flipping, visual quality may be higher, but performance of the game can change to better or worse, depending on the graphics adapter and other hardware (see the discussion of the Pentium Pro below, for a discussion of why page flipping can be faster but is sometimes much slower on that processor). However in most cases the performance (and thus frame rate) will be increased, but for people with hardware that has problems with page-flipping \\\"vid_nopageflip 1\\\" may help.\\nThe Pentium Pro is an example for such a hardware: it is an okay client platform (sniff!), but it has one weak spot, it is by default very slow on writes to video memory.\\nThis means that in default hardware configurations, you are usually much better off setting \\\"vid_nopageflip\\\" to \\\"1\\\" if you use VESA modes, so drawing is done to system memory instead of to video memory.\\nRemember that you must set the mode after setting \\\"vid_nopageflip\\\" to \\\"1\\\" in order to get \\\"vid_nopageflip\\\" to take effect. \\\"vid_nopageflip 1\\\" can sometimes be faster on a Pentium too, but not by nearly as much in general, and it's often slower.\",\n      \"group-id\": \"23\",\n      \"type\": \"float\"\n    },\n    \"vid_reload_auto\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"vid_renderer\": {\n      \"default\": \"1\",\n      \"desc\": \"Selects which rendering mode to use.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"(variable only available if multiple renderers available)\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"OpenGL, classic.\",\n          \"name\": \"0\"\n        },\n        {\n          \"description\": \"OpenGL, GLSL-only (requires OpenGL 4.3 driver)\",\n          \"name\": \"1\"\n        }\n      ]\n    },\n    \"vid_resetonswitch\": {\n      \"group-id\": \"23\",\n      \"remarks\": \"This is a workaround for the bug in some NVidia drivers, when you alt-tab out of ezQuake and the screen gets garbaged.\\nWhen enabled, the client will reset your Windows video mode every time you press alt-tab. Note that some NVidia drivers lock up your system completely when you alt-tab: vid_resetonswitch will not help in this case (try starting ezQuake with -dibonly).\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"vid_showextensions\": {\n      \"default\": \"0\",\n      \"desc\": \"If set, GL_EXTENSIONS string will be displayed when printing graphics info.\",\n      \"group-id\": \"52\",\n      \"type\": \"boolean\"\n    },\n    \"vid_software_palette\": {\n      \"default\": \"1\",\n      \"desc\": \"Controls whether to adjust gamma at the display level, or as a color correction rendering effect.\",\n      \"group-id\": \"52\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Use hardware gamma (classic style).\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Adjust gamma in post-processing.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"vid_stencilbits\": {\n      \"group-id\": \"31\",\n      \"type\": \"\"\n    },\n    \"vid_stretch_by_2\": {\n      \"group-id\": \"23\",\n      \"remarks\": \"This variable toggles whether in \\\"vid_mode 2\\\" the picture should be rendered at half-resolution in each direction and stretched up to the specified size.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"vid_usedesktopres\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, vid_width/vid_height are ignored, and desktop resolution is used instead.\",\n      \"group-id\": \"52\",\n      \"type\": \"boolean\"\n    },\n    \"vid_verbose\": {\n      \"default\": \"0\",\n      \"desc\": \"If set, display graphics info on every vid_restart.\",\n      \"group-id\": \"52\",\n      \"type\": \"boolean\"\n    },\n    \"vid_vsync\": {\n      \"default\": \"0\",\n      \"desc\": \"Syncs your FPS with your monitor's vertical refresh rate. This will override cl_maxfps.\",\n      \"group-id\": \"52\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Disable vsync.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Enable vsync.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"vid_vsync_lag_fix\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables experimental fix for a delay vertical synchronization mode.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"When vid_vsync is set to 1, an artificial delay in the input can appear, enabling this should eliminate the delay.\",\n      \"type\": \"boolean\"\n    },\n    \"vid_vsync_lag_tweak\": {\n      \"default\": \"1.0\",\n      \"desc\": \"Custom value for experimental vertical synchronization delay reduction.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Too low values (0.2 or lower) may significantly decrease FPS and make it not synced with display frequency.\\nToo high values (10 and above) may turn off the effect of video lag fix completely.\\nUse the lowest value that keeps stable FPS for you.\",\n      \"type\": \"float\"\n    },\n    \"vid_wideaspect\": {\n      \"desc\": \"Recalculates screen proportions so that they suit wide-screen displays.\\nIf you toggle this variable from 0 to 1, it will recalculate your field of view (fov) and screen proportions (conwidth/conheight ratio) so that the resulting image looks similar on a 16:10 screen as it did on 4:3 screen.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"The effect of the variable is only changing values of \\\"fov\\\" and \\\"vid_conheight\\\" variables when you toggle the value of this variable. The algorithm used to get resulting values is described in the QuakeWorld wiki.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Keep the settings suitable for 4:3 screen.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Recalculate proportions to match 16:10 screens.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"vid_width\": {\n      \"default\": \"0\",\n      \"desc\": \"Determines width of screen resolution when in fullscreen mode.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"This value is ignored if vid_usedesktopres is set.\",\n      \"type\": \"integer\"\n    },\n    \"vid_win_borderless\": {\n      \"default\": \"0\",\n      \"desc\": \"Enables borderless windowed mode (no window title, frame...). Useful for multi-monitor setups, where it can provide a fullscreen window that does not pause when out of focus.\",\n      \"group-id\": \"52\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"Standard windowed mode.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"Borderless windowed mode.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"vid_win_displaynumber\": {\n      \"default\": \"0\",\n      \"desc\": \"Display/screen to use when in windowed mode.\",\n      \"group-id\": \"52\",\n      \"remarks\": \"Use vid_displaylist command to enumerate displays.\",\n      \"type\": \"integer\"\n    },\n    \"vid_win_height\": {\n      \"default\": \"480\",\n      \"desc\": \"Height of display when in windowed mode.\",\n      \"group-id\": \"52\",\n      \"type\": \"integer\"\n    },\n    \"vid_win_save_pos\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, window position variables will be set as window moved around screen.\",\n      \"group-id\": \"52\",\n      \"type\": \"boolean\"\n    },\n    \"vid_win_save_size\": {\n      \"default\": \"1\",\n      \"desc\": \"If set, window size variables will be set as window resized.\",\n      \"group-id\": \"52\",\n      \"type\": \"boolean\"\n    },\n    \"vid_win_width\": {\n      \"default\": \"640\",\n      \"desc\": \"Width of display when in windowed mode.\",\n      \"group-id\": \"52\",\n      \"type\": \"integer\"\n    },\n    \"vid_window_x\": {\n      \"desc\": \"This variable sets the x-axis location of the top left corner of the game window on the desktop.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"vid_window_y\": {\n      \"desc\": \"This variable sets the y-axis location of the top left corner of the game window on the desktop.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"vid_windowed_mode\": {\n      \"desc\": \"This variable sets the windowed video mode that the game will switch to when the \\\"vid_windowed\\\" command is executed.\",\n      \"group-id\": \"31\",\n      \"type\": \"float\"\n    },\n    \"vid_xpos\": {\n      \"default\": \"3\",\n      \"desc\": \"Horizontal position of the client window.\",\n      \"group-id\": \"52\",\n      \"type\": \"integer\"\n    },\n    \"vid_ypos\": {\n      \"default\": \"39\",\n      \"desc\": \"Vertical position of the client window.\",\n      \"group-id\": \"52\",\n      \"type\": \"integer\"\n    },\n    \"viewsize\": {\n      \"default\": \"100\",\n      \"desc\": \"This variable determines how large the viewable screen size is.\",\n      \"group-id\": \"53\",\n      \"remarks\": \"Range is 30-120.\\nValues 100 & 110 are affected by cl_sbar.\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Minimum screen size.\",\n          \"name\": \"30\"\n        },\n        {\n          \"description\": \"Normal screen size.\",\n          \"name\": \"100\"\n        },\n        {\n          \"description\": \"Large screen size, without the weapons status bar.\",\n          \"name\": \"110\"\n        },\n        {\n          \"description\": \"Maximum screen size, without any status bars.\",\n          \"name\": \"120\"\n        }\n      ]\n    },\n    \"vip_password\": {\n      \"default\": \"\",\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"vip_values\": {\n      \"default\": \"\",\n      \"group-id\": \"43\",\n      \"type\": \"string\"\n    },\n    \"vm_rtChecks\": {\n      \"default\": \"1\",\n      \"group-id\": \"0\",\n      \"system-generated\": true\n    },\n    \"volume\": {\n      \"default\": \"0.7\",\n      \"desc\": \"Sets sound volume.\",\n      \"group-id\": \"45\",\n      \"remarks\": \"This can go above 1, but sound will get more and more distorted.\",\n      \"type\": \"float\"\n    },\n    \"w_switch\": {\n      \"default\": \"\",\n      \"desc\": \"This variable allows you to define the highest weapon that the client should switch to when picking it up.\\nThe possible arguments of \\\"w_switch\\\" refer to the impulse that is used to switch to a certain weapon:\",\n      \"group-id\": \"37\",\n      \"type\": \"enum\",\n      \"values\": [\n        {\n          \"description\": \"Axe.\",\n          \"name\": \"1\"\n        },\n        {\n          \"description\": \"Shotgun.\",\n          \"name\": \"2\"\n        },\n        {\n          \"description\": \"Double-Barreled Shotgun.\",\n          \"name\": \"3\"\n        },\n        {\n          \"description\": \"Nailgun.\",\n          \"name\": \"4\"\n        },\n        {\n          \"description\": \"Super Nailgun.\",\n          \"name\": \"5\"\n        },\n        {\n          \"description\": \"Grenade Launcher.\",\n          \"name\": \"6\"\n        },\n        {\n          \"description\": \"Rocket Launcher.\",\n          \"name\": \"7\"\n        },\n        {\n          \"description\": \"ThunderBolt.\",\n          \"name\": \"8\"\n        }\n      ]\n    },\n    \"watervis\": {\n      \"default\": \"0\",\n      \"desc\": \"This server-side cvar is mirrored to serverinfo as a hint to clients on whether the server is using the so-called watervised maps.\",\n      \"group-id\": \"34\",\n      \"remarks\": \"It's a bad idea to use watervis 1 if the maps the server is using are not watervised.\\nLiquids will appear 'transparent' but you won't be able to see any items/players/etc through it.\\nIt is a bad idea anyway because Quake maps were not designed for transparent water (or other liquids); enabling it changes gameplay, decreases fps and produces more network traffic and puts non-GL players at a disadvantage because the software renderer doesn't support transparent water (yet).\\nNot saved to config with cfg_save command.\",\n      \"type\": \"boolean\",\n      \"values\": [\n        {\n          \"description\": \"No watervis.\",\n          \"name\": \"false\"\n        },\n        {\n          \"description\": \"GLQuake users can see through liquids if they set gl_turbalpha to a value < 1.\",\n          \"name\": \"true\"\n        }\n      ]\n    },\n    \"zombietime\": {\n      \"default\": \"2\",\n      \"desc\": \"The number of minutes that the server will keep the character of a player on the map who seems to have disconnected.\",\n      \"group-id\": \"43\",\n      \"type\": \"integer\"\n    }\n  }\n}\n"
  },
  {
    "path": "misc/appimage/appimage-manual_creation.sh",
    "content": "#!/bin/bash\n\nset -e\n\nSKIP_DEPS=\"${SKIP_DEPS:-0}\"\n\nARCH=$(uname -m)\nARCHDASH=$(echo \"$ARCH\"|tr '_' '-')\nAPPIMAGETOOL=\"https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${ARCH}.AppImage\"\n\ncurl -Ls \"$APPIMAGETOOL\" > appimagetool\nif [ $? -ne 0 ];then\n\techo \"failed to retrieve appimagetool.  bailing out.\"\n\texit 1\nfi\nchmod -f +x appimagetool\n\n#unused but must exist\nDESKTOP_ENTRY='[Desktop Entry]\nComment=A modern QuakeWorld client focused on competitive online play\nName=ezQuake\nExec=ezquake-linux-'$ARCH'\nIcon=io.github.ezQuake\nType=Application\nStartupNotify=true\nTerminal=false\nCategories=Game;\nKeywords=quake;first;person;shooter;multiplayer;'\n\nTESTPROGRAM='\n#include \"curl/curl.h\"\nint main(){\n\tcurl_easy_init();\n\treturn 0;\n}\n'\n\nQUAKE_SCRIPT='#!/usr/bin/env bash\nexport LD_LIBRARY_PATH=\"${APPIMAGE_LIBRARY_PATH}:${APPDIR}/usr/lib:${LD_LIBRARY_PATH}\"\nif [ ! -e \"$OWD/id1\" ];then\n\tcd \"$(dirname \"$APPIMAGE\")\"\nelse\n\tcd \"$OWD\"\nfi\n\"${APPDIR}/usr/bin/test\"  >/dev/null 2>&1 |:\nFAIL=${PIPESTATUS[0]}\nif [ $FAIL -eq 0 ];then\n\techo \"executing with native libc\"\n\t\"${APPDIR}/usr/bin/ezquake-linux-'$ARCH'\" $*\nelse\n\techo \"executing with appimage libc\"\n\texport LD_LIBRARY_PATH=\"${LD_LIBRARY_PATH}:${APPDIR}/usr/lib-override\"\n\t\"${APPDIR}/usr/lib-override/ld-linux-'$ARCHDASH'.so.2\" \"${APPDIR}/usr/bin/ezquake-linux-'$ARCH'\" $*\nfi\nexitstatus=$?\nif [ $exitstatus -eq 0 ];then\n\t#fix qwurl association if set for appimage\n\tgrep -q \"^Exec=/tmp/.mount_\" \"${HOME}/.local/share/applications/qw-url-handler.desktop\" >/dev/null 2>&1 && \\\n\t\tsed -i \"s|^Exec=.*|Exec=${APPIMAGE} +qwurl %u|g\" \"${HOME}/.local/share/applications/qw-url-handler.desktop\"\nfi\nexit $exitstatus\n'\n\nunset CC\nif [ \"$ARCH\" == \"x86_64\" ];then\n\tmarch=\"-march=nehalem\"\nfi\nexport CFLAGS=\"$march -O3 -pipe -flto=$(nproc)\"\nexport LDFLAGS=\"$CFLAGS\"\n\nDIR=\"$(pwd)\"\n\nif [ -d AppDir ];then\n\trm -rf AppDir\nfi\nmkdir -p \"$DIR/AppDir/usr/bin\" || exit 1\nmkdir -p \"$DIR/AppDir/usr/lib\" || exit 1\nmkdir -p \"$DIR/AppDir/usr/lib-override\" || exit 1\n\nVERSION=$(sed -n 's/.*VERSION_NUMBER.*\"\\(.*\\)\".*/\\1/p' src/version.h)\nREVISION=$(git log -n 1|head -1|awk '{print $2}'|cut -c1-6)\n\n#build ezquake unless executable set\nif ! [[ -x \"$EXECUTABLE\" ]]; then\n\texport SKIP_DEPS\n\tchmod +x ./build-linux.sh && nice ./build-linux.sh || exit 3\n\tEXECUTABLE=\"build/ezquake-linux-$ARCH\"\nfi\n\n#build test program\necho \"$TESTPROGRAM\" > \"$DIR/test.c\" || exit 2\ngcc \"$DIR/test.c\" -o \"$DIR/AppDir/usr/bin/test\" -lcurl || exit 2\nrm -f \"$DIR/AppDir/test.c\" || exit 2\n\ncp -f $EXECUTABLE \"$DIR/AppDir/usr/bin/.\" || exit 4\nrm -f \"$DIR/AppDir/AppRun\"\necho \"$QUAKE_SCRIPT\" > \"$DIR/AppDir/AppRun\" || exit 4\nchmod +x \"$DIR/AppDir/AppRun\" || exit 4\necho \"$DESKTOP_ENTRY\" > \"$DIR/AppDir/io.github.ezQuake.desktop\" || exit 4\ncp \"$DIR/dist/linux/io.github.ezQuake.128.png\" \"$DIR/AppDir/io.github.ezQuake.png\"||true #copy over quake png if it exists\nmkdir -p \"$DIR/AppDir/usr/share/metainfo\"\nsed 's,EZQUAKE_VERSION,'$VERSION-$REVISION',g;s,EZQUAKE_DATE,'$(date +%F)',g' \"$DIR/misc/appimage/ezquake.appdata.xml.template\" > \"$DIR/AppDir/usr/share/metainfo/ezquake.appdata.xml\"\nldd \"$DIR/AppDir/usr/bin/ezquake-linux-$ARCH\" | \\\n\tgrep --color=never -v libGL| \\\n\tgrep --color=never -v libdrm.so | \\\n\tgrep --color=never -v libgbm.so | \\\n\tawk '{print $3}'| \\\n\txargs -I% cp -Lf \"%\" \"$DIR/AppDir/usr/lib/.\" || exit 5\nstrip -s \"$DIR/AppDir/usr/lib/\"* || exit 5\nstrip -s \"$DIR/AppDir/usr/bin/\"* || exit 5\nmv -f \"$DIR/AppDir/usr/lib/libc.so.6\" \"$DIR/AppDir/usr/lib-override/.\"\nmv -f \"$DIR/AppDir/usr/lib/libm.so.6\" \"$DIR/AppDir/usr/lib-override/.\"\ncp -f \"$(ldconfig -Np|grep --color=never libpthread.so.0$|grep --color=never $(uname -m)|awk '{print $NF}')\" \"$DIR/AppDir/usr/lib-override/.\"\ncp -Lf \"/lib64/ld-linux-${ARCHDASH}.so.2\" \"$DIR/AppDir/usr/lib-override/.\" || exit 6\n\ncd \"$DIR\" || exit 5\n./appimagetool AppDir ezQuake-$ARCH.AppImage || exit 7\n"
  },
  {
    "path": "misc/appimage/ezquake.appdata.xml.template",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<component type=\"desktop-application\">\n  <id>io.github.ezQuake</id>\n  <name>ezQuake</name>\n  <summary>a modern QuakeWorld client focused on competitive online play</summary>\n  <metadata_license>CC0-1.0</metadata_license>\n  <project_license>GPL-2.0</project_license>\n\n  <developer_name>ezQuake team</developer_name>\n  <update_contact>ezquake @ github</update_contact>\n\n  <url type=\"homepage\">https://ezquake.github.io/</url>\n  <url type=\"bugtracker\">https://github.com/ezQuake/ezquake-source</url>\n  <url type=\"help\">https://ezquake.github.io/manual.html</url>\n  <url type=\"contact\">https://www.quakeworld.nu/forum</url>\n\n  <launchable type=\"desktop-id\">io.github.ezQuake.desktop</launchable>\n\n  <description>\n    <p>\n      Welcome to the home of ezQuake, a modern QuakeWorld client focused on competitive online play. Combining the features of modern QuakeWorld® clients, ezQuake makes QuakeWorld® easier to start and play. The immortal first person shooter Quake® in a brand new skin with superb graphics and extremely fast gameplay.                                                                                                                                                                                                                                     \n    </p>\n    <ul>\n      <li>Modern Graphics: Particle explosions, shaft beam, gunshots, nails, rocket and grenade trails, blood, and others, MD3 models, fog, water effects, killing spree messages, rain</li>\n      <li>Modern competitive gaming features: Fullbright skins, forcing team/enemy colors, advanced weapon handling, teamplay messages, auto game recording, automated screenshots and console logging</li>\n      <li>Graphics customization: Customize your HUD, colors of walls and liquids, turn superfluous graphics effects off, change world textures, crosshair, sky picture, console background, game font</li>\n      <li>Independent Physics: Get the smoothest experience possible without being limited by server or network settings</li>\n      <li>Integrated Server Browser: Easy searching and filtering of online servers</li>\n      <li>Enhanced demo/QTV playback: View recorded games from multiple points of view, watch action on radar, all player stats in a handy table, autotrack the strongest player</li>\n    </ul>\n    <p>\n      Commercial data files are required to run the supported games. These can be aquired though a multitude of sources. See the manual for more info on this.\n    </p>\n  </description>\n\n  <screenshots>\n    <screenshot type=\"default\">\n      <image>https://ezquake.github.io/screenshots/armor_battle.png</image>\n      <caption>Battle for armor</caption>\n    </screenshot>\n    <screenshot>\n      <image>https://ezquake.github.io/screenshots/bloom.jpg</image>\n      <caption>Bloom effect</caption>\n    </screenshot>\n    <screenshot>\n      <image>https://ezquake.github.io/screenshots/shambler_cutf_bluefog.jpg</image>\n      <caption>Special effects</caption>\n    </screenshot>\n  </screenshots>\n\n  <releases>\n    <release version=\"EZQUAKE_VERSION\" date=\"EZQUAKE_DATE\" />\n  </releases>\n\n  <content_rating type=\"oars-1.1\">\n    <content_attribute id=\"violence-realistic\">moderate</content_attribute>\n    <content_attribute id=\"violence-bloodshed\">moderate</content_attribute>\n    <content_attribute id=\"violence-desecration\">moderate</content_attribute>\n    <!-- Full multiplayer functionality with voicechat support -->\n    <content_attribute id=\"social-chat\">intense</content_attribute>\n    <content_attribute id=\"social-audio\">intense</content_attribute>\n  </content_rating>\n</component>\n\n"
  },
  {
    "path": "misc/cfg/cams.cfg",
    "content": "//\n// Camera positions bookmarks 1.2\n//\n// Use [Ctrl]+[0], [1], ..., [9] to store cam positions\n// Use [0], [1], ..., [9] to load stored camera positions\n// Use [Shift]+[0], [1], ..., [9] to move smoothly to cam position\n//   (will happen only during capturing)\n//\n\n// -- settings --\nset cams_moveframes 50 // duration - how many frames will the movement take\n// note: you set time here, not the distance nor the speed. distance is given by the two points of the movement, speed is given as distance/time (frames)\n\nbind ctrl +cams_save_mode\nbind shift +cams_move_mode\n\n\n// -- print info --\necho \"$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-\"\necho \"Camera bookmarks config\"\necho \"$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-$-\"\necho \"Use $[ctrl$]+$[num$] to save camera position, $[num$] to load it\"\necho \"Use $[shift$]+$[num$] to start camera movement to camera positions 1, 2, ..., num\"\n\n// -- code -- (don't touch this)\n\nset cams_bindn 0\n// in other words: for (i=0; i<10; i++) { bind i cams_go i }\nalias cams_bindnext \"cams_bindc; inc cams_bindn 1; if $cams_bindn < 10 cams_bindnext\" \nalias cams_bindc \"bind $cams_bindn cams_go $cams_bindn\"\ncams_bindnext\n\nset cams_bindmode 0\n\nalias +cams_save_mode \"set cams_bindmode 1\"\nalias -cams_save_mode \"set cams_bindmode 0\"\nalias +cams_move_mode \"set cams_bindmode 2\"\nalias -cams_move_mode \"set cams_bindmode 0\"\n \nalias cams_go \"if $cams_bindmode == 0 cams_load %1 else if $cams_bindmode == 1 cams_save %1 else cams_move %1\"\nalias cams_load \"cam_angles $cam_angles_saved%1; cam_pos $cam_pos_saved%1\"\nalias cams_save \"set cam_angles_saved%1 $cam_angles; set cam_pos_saved%1 $cam_pos; cams_save_detailed %1\"\n\n// this saves each value to separated variable. it's being used in the move\nalias cams_save_detailed \"cams_save_detang %1; cams_save_detpos %1\"\nalias cams_save_detang \"set cams_angles_saved_pitch%1 $cam_angles_pitch; set cams_angles_saved_yaw%1 $cam_angles_yaw; set cams_angles_saved_roll%1 $cam_angles_roll\"\nalias cams_save_detpos \"set cams_pos_saved_x%1 $cam_pos_x; set cams_pos_saved_y%1 $cam_pos_y; set cams_pos_saved_z%1 $cam_pos_z\"\n\n// here we do the calculations for the smooth camera movement\n// note: I really should have written this in TCL :)\n\nalias cams_move \"set cams_part_moves %1; set cams_part_curm 1; if $cams_part_moves >= 1 cams_move_partial $cams_part_curm\"\nalias cams_move_partial \"cams_calc_steps %1; cams_store_alias; cams_replace_alias; set cams_cappedframes 0\"\nalias cams_store_alias \"set_alias_str cams_stored_alias f_captureframe\"\n// alias cams_restore_alias \"alias f_captureframe $cams_stored_alias\"\nalias cams_finish_move \"inc cams_part_curm; if $cams_part_curm <= $cams_part_moves cams_move_partial $cams_part_curm else cams_restore_alias\"\nalias cams_restore_alias \"alias f_captureframe\" // yes, this will clear f_captureframe alias, not \"clean\" but better behavior than something else\nalias cams_replace_alias \"alias f_captureframe cams_moveframe\"\nalias cams_calc_steps \"cams_calc_diffs %1; cams_calc_divs\"\nalias cams_calc_diffs \"set_calc cams_step_x $cams_pos_saved_x%1 - $cam_pos_x; set_calc cams_step_y $cams_pos_saved_y%1 - $cam_pos_y; set_calc cams_step_z $cams_pos_saved_z%1 - $cam_pos_z; cams_calc_yaw_diff %1; set_calc cams_step_pitch $cams_angles_saved_pitch%1 - $cam_angles_pitch\"\n\n// will calculate the horizontal turn and check if it's bigger than 180 degrees\nalias cams_calc_yaw_diff \"set_calc cams_step_yaw $cams_angles_saved_yaw%1 - $cam_angles_yaw; if $cams_step_yaw > 180 cams_calc_yaw_complement; if $cams_step_yaw < -180 cams_calc_yaw_comp_neg\" // get the shortest of the two possible ways: CW/CCW\n// if the turn was bigger than 180 degrees, it will use 360 degrees complement\nalias cams_calc_yaw_complement \"set_calc cams_step_yaw $cams_step_yaw - 360\"\nalias cams_calc_yaw_comp_neg \"set_calc cams_step_yaw $cams_step_yaw + 360\"\nalias cams_calc_divs \"set_calc cams_step_x $cams_step_x / $cams_moveframes; set_calc cams_step_y $cams_step_y / $cams_moveframes; set_calc cams_step_z $cams_step_z / $cams_moveframes; set_calc cams_step_yaw $cams_step_yaw / $cams_moveframes; set_calc cams_step_pitch $cams_step_pitch / $cams_moveframes\"\nalias cams_moveframe \"cams_move_pos; cams_move_ang; inc cams_cappedframes; if $cams_cappedframes >= $cams_moveframes cams_finish_move\"\nalias cams_move_pos \"set_calc cams_tx $cam_pos_x + $cams_step_x; set_calc cams_ty $cam_pos_y + $cams_step_y; set_calc cams_tz $cam_pos_z + $cams_step_z; cam_pos $cams_tx $cams_ty $cams_tz\"\nalias cams_move_ang \"set_calc cams_tw $cam_angles_yaw + $cams_step_yaw; set_calc cams_tp $cam_angles_pitch + $cams_step_pitch; cam_angles $cams_tp $cams_tw 0\"\n\n\n// 1) calculate 6 steps\n// 2) save current f_captureframe alias\n// 3) set new f_captureframe alias\n\nalias cams_clear \"unset_re cam_angles.\"\n"
  },
  {
    "path": "misc/cfg/cams.tcl",
    "content": "#\n# Camera positions bookmarks 1.1\n#\n# Use [Ctrl]+[0], [1], ..., [9] to store cam positions\n# Use [0], [1], ..., [9] to load stored camera positions\n# Use [Shift]+[0], [1], ..., [9] to move smoothly to cam position\n#   (will happen only during capturing)\n#\n\n# -- settings --\nset cams_moveframes 50; # duration - how many frames will the movement take \n\ncmd bind ctrl +cams_save_mode\ncmd bind shift +cams_move_mode\n\n\n# -- code -- (don't touch this)\n\nset cams_bindn 0\n\nfor {set i 0} {$i < 10} {incr i} {\n    cmd bind $i cams_go $i\n}\n\nset cams_bindmode 0\nset cams_totalmoves 0\n\nalias +cams_save_mode {} {set ::cams_bindmode 1}\nalias -cams_save_mode {} {set ::cams_bindmode 0}\nalias +cams_move_mode {} {set ::cams_bindmode 2}\nalias -cams_move_mode {} {set ::cams_bindmode 0}\n\n#using globals sux in TCL (as everywhere), i'm new to TCL and perhaps i could use some namespaces here and there\n\n# this one is bound to 0,1,2,...9 keys\nalias cams_go {slot} {\n    global cams_bindmode\n    if {$cams_bindmode == 0} {\n        cams_load $slot\n    } elseif { $cams_bindmode == 1 } {\n        cams_save $slot\n    } else {\n        cams_move $slot\n    }\n}\n\nproc cams_load {slot} {\n    global cam_angles_saved\n    global cam_pos_saved\n    cmd cam_angles $cam_angles_saved($slot)\n    cmd cam_pos $cam_pos_saved($slot)\n}\n\nproc cams_save {slot} {\n    global cam_angles_saved\n    global cam_pos_saved\n    global cam_angles\n    set cam_angles_saved($slot) $::cam_angles\n    set cam_pos_saved($slot) $::cam_pos\n    cams_save_detailed $slot\n}\n\n# this saves each value to separated field. it's being used in the move\nproc cams_save_detailed {slot} {\n    global cams_saved_det\n\n    set cams_saved_det($slot,pitch) $::cam_angles_pitch\n    set cams_saved_det($slot,yaw) $::cam_angles_yaw\n    set cams_saved_det($slot,roll) $::cam_angles_roll\n\n    set cams_saved_det($slot,x) $::cam_pos_x\n    set cams_saved_det($slot,y) $::cam_pos_y\n    set cams_saved_det($slot,z) $::cam_pos_z\n}\n\nproc cams_move {part} {\n    global cams_totalmoves\n    set cams_totalmoves $part\n    cams_move_partial 0\n}\n\n# calculate the differences between current and future position\nproc cams_calc_diffs {slot} {\n    global cams_step\n    global cams_saved_det\n    set cams_step(x) [expr {$cams_saved_det($slot,x) - $::cam_pos_x}]\n    set cams_step(y) [expr {$cams_saved_det($slot,y) - $::cam_pos_y}]\n    set cams_step(z) [expr {$cams_saved_det($slot,z) - $::cam_pos_z}]\n\n    # get the shortest of the two possible ways: CW/CCW\n    set cams_step(yaw) [expr {$cams_saved_det($slot,yaw) - $::cam_angles_yaw}]\n    if {$cams_step(yaw) > 180} {\n        set cams_step(yaw) [expr { $cams_step(yaw) - 360}]\n    } elseif {$cams_step(yaw) < -180} {\n        set cams_step(yaw) [expr { $cams_step(yaw) + 360}]\n    }\n    set cams_step(pitch) [expr {$cams_saved_det($slot,pitch) - $::cam_angles_pitch}]\n    set cams_step(roll) [expr {$cams_saved_det($slot,roll) - $::cam_angles_roll}]\n}\n\n# cams_setp\nproc cams_calc_divs {moveframes} {\n    global cams_step\n    set cams_step(x) [expr {$cams_step(x) / $moveframes}]\n    set cams_step(y) [expr {$cams_step(y) / $moveframes}]\n    set cams_step(z) [expr {$cams_step(z) / $moveframes}]\n    set cams_step(yaw) [expr {$cams_step(yaw) / $moveframes}]\n    set cams_step(pitch) [expr {$cams_step(pitch) / $moveframes}]\n}\n\nproc cams_move_partial {part} { ;# input: part = previous move number\n    global cams_totalmoves   ;# how many moves we will take\n    global cams_moveframes   ;# how many frames will the move take (todo)\n    global cams_cappedframes ;# how many frames we have already passed\n    incr part                ;# go to next move (this proc is first called with part = 0)\n    \n    if {$part > $cams_totalmoves} {\n        cmd alias f_captureframe \"\"\n    } else {\n        cams_calc_diffs $part\n        cams_calc_divs $cams_moveframes\n        set cams_cappedframes 0\n        cmd alias f_captureframe cams_moveframe $part\n    }\n}\n\n# happens in each frame\nalias cams_moveframe {part} {\n    global cams_cappedframes ;# how many frames we have already captured on this move\n    global cams_moveframes   ;# how many frames we should capture on this move\n    global cams_step         ;# contains step info (x,y,z,pitch,yaw,roll)\n    \n    set cams_tx [expr {$::cam_pos_x + $cams_step(x)}]\n    set cams_ty [expr {$::cam_pos_y + $cams_step(y)}]\n    set cams_tz [expr {$::cam_pos_z + $cams_step(z)}]\n    cmd cam_pos $cams_tx $cams_ty $cams_tz\n    set cams_tw [expr {$::cam_angles_yaw + $cams_step(yaw)}]\n    set cams_tp [expr {$::cam_angles_pitch + $cams_step(pitch)}]\n    cmd cam_angles $cams_tp $cams_tw 0\n    incr cams_cappedframes\n    if {$cams_cappedframes > $cams_moveframes} {\n        cams_move_partial $part\n    }\n}\n\n\nproc cams_clear {} {\n    cmd unset_re cam_angles.\n}\n"
  },
  {
    "path": "misc/cfg/eq260.cfg",
    "content": "\n//---CREDITS-------------------------------------------------------------------------------------------------------------//\n// THIS CONFIG FILE WAS MADE BY STRIFE WITH HELP FROM JEZAJA AND XANTOM. LAST UPDATED 05-03-28.\n// WORKS ONLY WITH FUHQUAKE OR EZQUAKE, FOUND AT http://www.fuhquake.net AND http://ezquake.sourceforge.net/\n// WORKS BEST WITH FLINTHEARTS FUHQUAKE LOC FILES, GET THEM AT http://maps.quakeworld.nu/locs\n// PUT THEM IN QUAKE/QW/LOCS FOLDER.\n\n//---HOW-TO-INSTALL------------------------------------------------------------------------------------------------------//\n// THE ONLY TWO THINGS YOU HAVE TO CHANGE IN THIS CONFIG IS THE SET NICK ??? TO AN ABBREVATION OF YOUR NICK NAME.\n// SECOND THING TO EDIT ARE ALL THE ? KEYS, WHICH BUTTON GOES TO WHAT TEAMSAYMESSAGE.\n// WHEN THIS FILE IS EDITED AND SAVED PUT IT IN THE QUAKE/QW FOLDER, TYPE \"exec eq260.cfg\"\n// AND THEN \"cfg_save nameofyourcfg\" WITHOUT THE \"\" IN THE CONSOLE.\n\n\n\n//---EDIT-THIS-----------------------------------------------------------------------------------------------------------//\n\nset nick ???          //Change \"???\" to the 3 first letters in your nickname. Example: set nick str\n\nbind  a  \"_report\"    //Example: bind R \"_report\"\n                      //Report that will show your powerups, health, armor, location and weapons.\n                      //Weapon = RLG means you have the RocketLauncher AND the Lightning-Gun with at least 15 cells.\n\nbind  shift  \"_coming\"    //Example: bind C \"_coming\"\n                      //Simple \"coming\" report, will also show powerup if you have that. If you lack rocketlauncher and\n                      //have more than 99 armor, it will show your armor amount aswell.\n\nbind  z  \"_took\"      //Example: bind T \"_took\"\n                      //This is to report \"team powerup\" and \"took item [location]\".\n                      //If you have RL after taking a backpack, it will display \"took rl-pack [location]\"\n\nbind  4  \"_need\"      //Example: bind H \"_help\"\n                      //Reports \"help [location]\" everywhere except dm2-quad-low where it will report \"help/trick\".\n                      //Help should also be used as swap, switch or whatever people like to have.\n\nbind  x  \"_safe\"      //Example: bind F \"_safe\"\n                      //Will show \"safe [location]\" with 2 green leds. This means that area is safe.\n                      //If your best weapon is shotgun or nailgun, it will just do a regular _report.\n\nbind  g  \"_help\"      //Example: bind E \"_need\"\n                      //\"need items [location]\" amr = armor, hth = health, rox = rockets, wpn = weapon.\n                      //If health is above 50, armor above 70, RL with 5 or more rockets it will display _safe instead.\n\nbind  r  \"_point\"     //Example: bind Q \"_point\"\n                      //Use to point items like ra/ya/ga/pack, also point players with powerups. It will also show what you \n                      //need if you are pointing powerup-item, like \"str >> quad [quad] amr/rox\" (needs armor and rockets)\n\nbind  c  \"_attack\"    //Example: bind G \"_attack\"\n                      //Use when you died vs weakened enemy-rl and need teammates to spam that area.\n                      //Displays \"sync\" instead if you have weapon, this is good for coordinating attacks with teammates.\n\nbind  v  \"_lost\"      //Example: bind Z \"_lost\"\n                      //Will show \"lost [location]\" you died at with 2 red leds, use when you feel it is important.\n\nbind  ctrl  \"_rlpack\"    //Example: bind 1 \"_rlpack\"\n                      //Works like lost, but use when you think you dropped a rocketlauncher.\n\nbind  3  \"_qout\"      //Example: bind 2 \"_qout\"\n                      //\"quad killed\" if you dont have quad. \"quad 30\" if you do.\n\nbind  1  \"_getquad\"   //Example: bind 3 \"_getquad\"\n                      //Standard \"get quad\" report, use about 10 seconds before it spawns.\n\nbind  2  \"_getpent\"   //Example: bind 4 \"_getpent\"\n                      //Simply reports \"get pent/ring\", use about 20 seconds before pent spawns.\n\nbind  w  \"_enemyp\"    //Example: bind V \"_enemyp\"\n                      //Will report \"enemy quad\" by default, but if you have seen enemy pent, it will report that instead.\n                      //Use this before you start shooting at someone with ring to see if it is enemy or teammate.\n                      //If you are dead, [location] will also show. You can point enemy quad with it while dead.\n\nbind  t  \"_take\"      //Example: bind 5 \"_take\"\n                      //Will report \"soon [location]\" if you already have whatever item spawns in the area you are.\n                      //If you need the item yourself, it will report \"awaits [location]\".\n                      //If you point teammate when using this it will report \"teammate take item [location]\".\n\n//-----------------------------------------------------------------------------------------------------------------------//\n// NOW SAVE THE FILE TO YOUR /QUAKE/QW DIRECTORY, START QUAKE AND TYPE exec eq260.cfg\n// DONE! DONT FORGET TO CFG_SAVE YOUR CONFIG IF IT WORKS LIKE YOU WANT IT TO.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n//----------------------------------------DO-NOT-EDIT-ANYTHING-BELOW-------------------------------------------------------//\n\nalias .attack                  \"say_team $\\$nick $eq_red $tp_name_attack $dl %E\"\nalias .attacksync              \"say_team $\\$nick $eq_yellow $tp_name_sync $l %b\"\nalias .attacksync2             \"say_team $\\$nick $eq_yellow $tp_name_sync $l rlg:$rockets\"\nalias .attacksyncpowq             \"say_team $\\$nick $eq_yellow {q} $tp_name_sync $l\"\nalias .attacksyncpowp             \"say_team $\\$nick $eq_yellow {p} $tp_name_sync $l\"\nalias .attacksyncpowr             \"say_team $\\$nick $eq_yellow {r} $tp_name_sync $l\"\nalias .attacksyncpowqp            \"say_team $\\$nick $eq_yellow {qp} $tp_name_sync $l\"\nalias .attacksyncpowqr            \"say_team $\\$nick $eq_yellow {qr} $tp_name_sync $l\"\nalias .attacksyncpowpr            \"say_team $\\$nick $eq_yellow {pr} $tp_name_sync $l\"\nalias .attacksyncpowqpr           \"say_team $\\$nick $eq_yellow {qpr} $tp_name_sync $l\"\nalias .attacksyncpowwpnq             \"say_team $\\$nick $eq_yellow {q} $tp_name_sync $l %b\"\nalias .attacksyncpowwpnp             \"say_team $\\$nick $eq_yellow {p} $tp_name_sync $l %b\"\nalias .attacksyncpowwpnr             \"say_team $\\$nick $eq_yellow {r} $tp_name_sync $l %b\"\nalias .attacksyncpowwpnqp            \"say_team $\\$nick $eq_yellow {qp} $tp_name_sync $l %b\"\nalias .attacksyncpowwpnqr            \"say_team $\\$nick $eq_yellow {qr} $tp_name_sync $l %b\"\nalias .attacksyncpowwpnpr            \"say_team $\\$nick $eq_yellow {pr} $tp_name_sync $l %b\"\nalias .attacksyncpowwpnqpr           \"say_team $\\$nick $eq_yellow {qpr} $tp_name_sync $l %b\"\nalias .attacksyncpowrlgq             \"say_team $\\$nick $eq_yellow {q} $tp_name_sync $l rlg:$rockets\"\nalias .attacksyncpowrlgp             \"say_team $\\$nick $eq_yellow {p} $tp_name_sync $l rlg:$rockets\"\nalias .attacksyncpowrlgr             \"say_team $\\$nick $eq_yellow {r} $tp_name_sync $l rlg:$rockets\"\nalias .attacksyncpowrlgqp            \"say_team $\\$nick $eq_yellow {qp} $tp_name_sync $l rlg:$rockets\"\nalias .attacksyncpowrlgqr            \"say_team $\\$nick $eq_yellow {qr} $tp_name_sync $l rlg:$rockets\"\nalias .attacksyncpowrlgpr            \"say_team $\\$nick $eq_yellow {pr} $tp_name_sync $l rlg:$rockets\"\nalias .attacksyncpowrlgqpr           \"say_team $\\$nick $eq_yellow {qpr} $tp_name_sync $l rlg:$rockets\"\n\nalias .coming                  \"say_team $\\$nick $eq_green $tp_name_coming $l\"\nalias .coming2                 \"say_team $\\$nick $eq_green $tp_name_coming $l %A%a\"\nalias .comingpowq             \"say_team $\\$nick $eq_green {q} coming $l\"\nalias .comingpowp             \"say_team $\\$nick $eq_green {p} coming $l\"\nalias .comingpowr             \"say_team $\\$nick $eq_green {r} coming $l\"\nalias .comingpowqp            \"say_team $\\$nick $eq_green {qp} coming $l\"\nalias .comingpowqr            \"say_team $\\$nick $eq_green {qr} coming $l\"\nalias .comingpowpr            \"say_team $\\$nick $eq_green {pr} coming $l\"\nalias .comingpowqpr           \"say_team $\\$nick $eq_green {qpr} coming $l\"\nalias .comingamrpowq             \"say_team $\\$nick $eq_green {q} coming $l %A%a\"\nalias .comingamrpowp             \"say_team $\\$nick $eq_green {p} coming $l %A%a\"\nalias .comingamrpowr             \"say_team $\\$nick $eq_green {r} coming $l %A%a\"\nalias .comingamrpowqp            \"say_team $\\$nick $eq_green {qp} coming $l %A%a\"\nalias .comingamrpowqr            \"say_team $\\$nick $eq_green {qr} coming $l %A%a\"\nalias .comingamrpowpr            \"say_team $\\$nick $eq_green {pr} coming $l %A%a\"\nalias .comingamrpowqpr           \"say_team $\\$nick $eq_green {qpr} coming $l %A%a\"\n\nalias .enemyp                  \"say_team $\\$nick $eq_red enemy %q $eq_red\"\nalias .enemyp2                 \"say_team $\\$nick $eq_red enemy %q $l\"\nalias .enemyp3                 \"say_team $\\$nick $eq_red enemy %q $eq_left%y$eq_right\"\n\nalias .getpent                 \"say_team $\\$nick $eq_purple get $tp_name_pent/$tp_name_ring $eq_purple\"\nalias .getquad                 \"say_team $\\$nick $eq_purple get $tp_name_quad $eq_purple\"\nalias .getring                 \"say_team $\\$nick $eq_purple get $tp_name_ring $eq_purple\"\n\nalias .help                    \"say_team $\\$nick $eq_yellow help $l %E\"\n\nalias .lost                    \"say_team $\\$nick $eq_red lost $dl %E\"\n\nalias .need                    \"say_team $\\$nick $eq_white $tp_name_need %u $l\"\nalias .needq                   \"say_team $\\$nick $eq_white {q} $tp_name_need %u $l\"\nalias .needp                   \"say_team $\\$nick $eq_white {p} $tp_name_need %u $l\"\nalias .needr                   \"say_team $\\$nick $eq_white {r} $tp_name_need %u $l\"\nalias .needqp                  \"say_team $\\$nick $eq_white {qp} $tp_name_need %u $l\"\nalias .needqr                  \"say_team $\\$nick $eq_white {qr} $tp_name_need %u $l\"\nalias .needpr                  \"say_team $\\$nick $eq_white {pr} $tp_name_need %u $l\"\nalias .needqpr                 \"say_team $\\$nick $eq_white {qpr} $tp_name_need %u $l\"\n\nalias .point                   \"say_team $\\$nick $eq_arrows %x $eq_left%y$eq_right\"\nalias .point2                  \"say_team $\\$nick $eq_arrows %x $eq_left%y$eq_right need %u\"\nalias .point3                  \"say_team $\\$nick $eq_arrows %x $eq_left%y$eq_right\"\n\nalias .awaits                  \"say_team $\\$nick $eq_yellow awaits $l\"\nalias .soon                    \"say_team $\\$nick $eq_green $tp_name_soon $l\"\nalias .take_mate               \"say_team $\\$nick $eq_yellow %x take $l\"\n\nalias .qout                    \"say_team $\\$nick $eq_yellow $tp_name_quad killed $eq_yellow\"\nalias .qout2                   \"say_team $\\$nick $eq_yellow $tp_name_quad 30 $eq_yellow\"\n\nalias .report                  \"say_team $\\$nick $eq_blue %A%a/%h $l rlg:$rockets\"\nalias .report2                 \"say_team $\\$nick $eq_blue %A%a/%h $l %b\"\nalias .report3                 \"say_team $\\$nick $eq_blue %A%a/%h $l\"\nalias .reportpowq             \"say_team $\\$nick $eq_blue {q} %A%a/%h $l\"\nalias .reportpowp             \"say_team $\\$nick $eq_blue {p} %A%a/%h $l\"\nalias .reportpowr             \"say_team $\\$nick $eq_blue {r} %A%a/%h $l\"\nalias .reportpowqp            \"say_team $\\$nick $eq_blue {qp} %A%a/%h $l\"\nalias .reportpowqr            \"say_team $\\$nick $eq_blue {qr} %A%a/%h $l\"\nalias .reportpowpr            \"say_team $\\$nick $eq_blue {pr} %A%a/%h $l\"\nalias .reportpowqpr           \"say_team $\\$nick $eq_blue {qpr} %A%a/%h $l\"\nalias .reportpowwpnq             \"say_team $\\$nick $eq_blue {q} %A%a/%h $l %b\"\nalias .reportpowwpnp             \"say_team $\\$nick $eq_blue {p} %A%a/%h $l %b\"\nalias .reportpowwpnr             \"say_team $\\$nick $eq_blue {r} %A%a/%h $l %b\"\nalias .reportpowwpnqp            \"say_team $\\$nick $eq_blue {qp} %A%a/%h $l %b\"\nalias .reportpowwpnqr            \"say_team $\\$nick $eq_blue {qr} %A%a/%h $l %b\"\nalias .reportpowwpnpr            \"say_team $\\$nick $eq_blue {pr} %A%a/%h $l %b\"\nalias .reportpowwpnqpr           \"say_team $\\$nick $eq_blue {qpr} %A%a/%h $l %b\"\nalias .reportpowrlgq             \"say_team $\\$nick $eq_blue {q} %A%a/%h $l rlg:$rockets\"\nalias .reportpowrlgp             \"say_team $\\$nick $eq_blue {p} %A%a/%h $l rlg:$rockets\"\nalias .reportpowrlgr             \"say_team $\\$nick $eq_blue {r} %A%a/%h $l rlg:$rockets\"\nalias .reportpowrlgqp            \"say_team $\\$nick $eq_blue {qp} %A%a/%h $l rlg:$rockets\"\nalias .reportpowrlgqr            \"say_team $\\$nick $eq_blue {qr} %A%a/%h $l rlg:$rockets\"\nalias .reportpowrlgpr            \"say_team $\\$nick $eq_blue {pr} %A%a/%h $l rlg:$rockets\"\nalias .reportpowrlgqpr           \"say_team $\\$nick $eq_blue {qpr} %A%a/%h $l rlg:$rockets\"\n\nalias .rlpack                \"say_team $\\$nick $eq_red rl-pack $dl %E\"\n\nalias .safe                    \"say_team $\\$nick $eq_green safe $l rlg:$rockets\"\nalias .safe2                   \"say_team $\\$nick $eq_green safe $l %b\"\nalias .safe3                   \"say_team $\\$nick $eq_green safe $l\"\nalias .safepowwpnq             \"say_team $\\$nick $eq_green {q} safe $l %b\"\nalias .safepowwpnp             \"say_team $\\$nick $eq_green {p} safe $l %b\"\nalias .safepowwpnr             \"say_team $\\$nick $eq_green {r} safe $l %b\"\nalias .safepowwpnqp            \"say_team $\\$nick $eq_green {qp} safe $l %b\"\nalias .safepowwpnqr            \"say_team $\\$nick $eq_green {qr} safe $l %b\"\nalias .safepowwpnpr            \"say_team $\\$nick $eq_green {pr} safe $l %b\"\nalias .safepowwpnqpr           \"say_team $\\$nick $eq_green {qpr} safe $l %b\"\nalias .safepowrlgq             \"say_team $\\$nick $eq_green {q} safe $l rlg:$rockets\"\nalias .safepowrlgp             \"say_team $\\$nick $eq_green {p} safe $l rlg:$rockets\"\nalias .safepowrlgr             \"say_team $\\$nick $eq_green {r} safe $l rlg:$rockets\"\nalias .safepowrlgqp            \"say_team $\\$nick $eq_green {qp} safe $l rlg:$rockets\"\nalias .safepowrlgqr            \"say_team $\\$nick $eq_green {qr} safe $l rlg:$rockets\"\nalias .safepowrlgpr            \"say_team $\\$nick $eq_green {pr} safe $l rlg:$rockets\"\nalias .safepowrlgqpr           \"say_team $\\$nick $eq_green {qpr} safe $l rlg:$rockets\"\n\nalias .teamp                   \"say_team $\\$nick $eq_green $tp_name_teamp %p $eq_green need %u\"\nalias .teamp2                  \"say_team $\\$nick $eq_green $tp_name_teamp %p $eq_green\"\n\nalias .took                    \"say_team $\\$nick $eq_blue took %X $eq_left%Y$eq_right\"\nalias .tookrlpack              \"say_team $\\$nick $eq_blue took rl-pack $eq_left%Y$eq_right\"\n\nalias .trick                   \"say_team $\\$nick $eq_yellow $tp_name_help/$tp_name_trick $l\"\n\n//Teamplay Communications\n\ntp_weapon_order              \"78653241\"\n\n//Item Names\ntp_name_axe                  \"axe\"\ntp_name_sg                   \"sg\"\ntp_name_ssg                  \"ssg\"\ntp_name_ng                   \"ng\"\ntp_name_sng                  \"sng\"\ntp_name_gl                   \"gl\"\ntp_name_rl                   \"rl\"\ntp_name_lg                   \"lg\"\ntp_name_ga                   \"ga\"\ntp_name_ya                   \"ya\"\ntp_name_ra                   \"ra\"\ntp_name_armortype_ga         \"g\"\ntp_name_armortype_ya         \"y\"\ntp_name_armortype_ra         \"r\"\ntp_name_shells               \"shells\"\ntp_name_nails                \"nails\"\ntp_name_rockets              \"rox\"\ntp_name_cells                \"cel\"\ntp_name_quad                 \"quad\"\ntp_name_pent                 \"pent\"\ntp_name_ring                 \"ring\"\ntp_name_suit                 \"suit\"\ntp_name_mh                   \"mh\"\ntp_name_health               \"hth\"\ntp_name_armor                \"amr\"\ntp_name_weapon               \"wpn\"\ntp_name_backpack             \"pack\"\ntp_name_flag                 \"flag\"\ntp_name_sentry               \"sent\"\ntp_name_disp                 \"disp\"\ntp_name_teammate             \"\"\ntp_name_enemy                \"nme\"\ntp_name_eyes                 \"{r}\"\ntp_name_quaded               \"{q}\"\ntp_name_pented               \"{p}\"\ntp_name_status_green         \"$G\"\ntp_name_status_yellow        \"$Y\"\ntp_name_status_red           \"$R\"\ntp_name_status_blue          \"$B\"\ntp_name_someplace            \"someplace\"\ntp_name_at                   \" $eq_arrow \"\ntp_name_nothing              \"--\"\ntp_name_none                 \"\"\ntp_name_separator            \"/\"\n\n//Item Need Amounts\ntp_need_rl                   \"1\"\ntp_need_weapon               \"87\"\ntp_need_ga                   \"70\"\ntp_need_ya                   \"70\"\ntp_need_ra                   \"70\"\ntp_need_shells               \"0\"\ntp_need_nails                \"0\"\ntp_need_rockets              \"5\"\ntp_need_cells                \"0\"\ntp_need_health               \"50\"\n\n//User Created Variables\nset l                       \"\u0010{%l}\u0011\"\nset dl                      \"\u0010{%d}\u0011\"\nset loc                     \"\u0010{%l}\u0011\"\nset eq_arrow                \"\"\nset eq_arrows               \"\"\nset eq_awaitspoint          \"powerups armor players mh lg rl gl sng ssg pack rockets cells\"\nset eq_blue                 \"\"\nset eq_lightblue            \"\"\nset eq_green                \"\"\nset eq_green3               \"\"\nset eq_left                 \"\u0010{\"\nset eq_normalpoint          \"powerups armor players mh lg rl gl sng ssg pack rockets\"\nset eq_purple               \"\"\nset eq_red                  \"\"\nset eq_red3                 \"\"\nset eq_right                \"}\u0011\"\nset eq_white                \"\"\nset eq_white3               \"\"\nset eq_yellow               \"\"\nset loc_name_ga             \"ga\"\nset loc_name_gl             \"gl\"\nset loc_name_mh             \"mega\"\nset loc_name_ng             \"ng\"\nset loc_name_ra             \"ra\"\nset loc_name_rl             \"rl\"\nset loc_name_separator      \"-\"\nset loc_name_sng            \"sng\"\nset loc_name_ssg            \"ssg\"\nset loc_name_ya             \"ya\"\nset tp_name_attack          \"attack\"\nset tp_name_coming          \"coming\"\nset tp_name_dm2button       \"low-rl/button\"\nset tp_name_dm2quadlow      \"quad-low\"\nset tp_name_help            \"help\"\nset tp_name_lgpack          \"lg-pack\"\nset tp_name_need            \"need\"\nset tp_name_powdead         \"dead\"\nset tp_name_ratunnel        \"ra-tunnel\"\nset tp_name_rlpack          \"rl-pack\"\nset tp_name_soon            \"soon\"\nset tp_name_sync            \"sync\"\nset tp_name_teamp           \"team\"\nset tp_name_trick           \"trick\"\n\n// warn if prefix is default\nif $qt$nick$qt = $qt???$qt then echo $eq_red Change your reports prefix using /set nick pfx\n\ntp_pickup    powerups armor mh lg rl gl pack rockets\ntp_took      powerups armor mh lg rl gl pack rockets\ntp_point     powerups armor players mh lg rl gl sng ssg pack rockets\n\nalias +aback                   \"+back\"\nalias -aback                   \"-back\"\nalias +aforward                \"+forward\"\nalias -aforward                \"-forward\"\nalias +ajump                   \"+jump\"\nalias -ajump                   \"-jump\"\nalias +amoveleft               \"+moveleft\"\nalias -amoveleft               \"-moveleft\"\nalias +amoveright              \"+moveright\"\nalias -amoveright              \"-moveright\"\n\nalias __awaitspoint            \"tp_point $eq_awaitspoint\"\nalias __normalpoint            \"tp_point $eq_normalpoint\"\n\nalias _attack                  \"if $health < 1 then .attack else _attack2\"\nalias _attack2                 \"if $qt$tp_name_quad$qt isin $qt$powerups$qt then _attacksyncp else _attack3\"\nalias _attack3                 \"if $bestweapon isin $tp_name_rl|$tp_name_lg then _attacksync else .attack\"\nalias _attacksync              \"if $tp_name_lg isin $qt$weapons$qt then _attacksync2 else _attacksync4\"\nalias _attacksync2             \"if $cells > 14 then _attacksync3 else _attacksync4\"\nalias _attacksync3             \"if $bestweapon = $qt$tp_name_lg$qt then _attacksync4 else _attacksync5\"\nalias _attacksync4             \"if $qt$powerups$qt == $qt$tp_name_none$qt then .attacksync else _attacksyncpowwpn\"\nalias _attacksync5             \"if $qt$powerups$qt == $qt$tp_name_none$qt then .attacksync2 else _attacksyncpowrlg\"\nalias _attacksyncp             \"if $bestweapon isin $tp_name_sg|$tp_name_ng then _attacksyncpow else _attacksyncp1\"\nalias _attacksyncp1            \"if $tp_name_lg isin $qt$weapons$qt then _attacksyncp2 else _attacksyncpowwpn\"\nalias _attacksyncp2            \"if $cells > 14 then _attacksyncp3 else _attacksyncpowwpn\"\nalias _attacksyncp3            \"if $bestweapon = $qt$tp_name_lg$qt then _attacksyncpowwpn else _attacksyncpowrlg\"\nalias _attacksyncpow                \"if $qt$powerups$qt == $qt$tp_name_quad$qt then .attacksyncpowq else _attacksyncpow2\"\nalias _attacksyncpow2               \"if $qt$powerups$qt == $qt$tp_name_pent$qt then .attacksyncpowp else _attacksyncpow3\"\nalias _attacksyncpow3               \"if $qt$powerups$qt == $qt$tp_name_ring$qt then .attacksyncpowr else _attacksyncpow4\"\nalias _attacksyncpow4               \"if $qt$tp_name_quad$qt !isin $qt$powerups$qt then .attacksyncpowpr else _attacksyncpow5\"\nalias _attacksyncpow5               \"if $qt$tp_name_ring$qt !isin $qt$powerups$qt then .attacksyncpowqp else _attacksyncpow6\"\nalias _attacksyncpow6               \"if $qt$tp_name_pent$qt !isin $qt$powerups$qt then .attacksyncpowqr else .attacksyncpowqpr\"\nalias _attacksyncpowwpn                   \"if $qt$powerups$qt == $qt$tp_name_quad$qt then .attacksyncpowwpnq else _attacksyncpowwpn2\"\nalias _attacksyncpowwpn2                  \"if $qt$powerups$qt == $qt$tp_name_pent$qt then .attacksyncpowwpnp else _attacksyncpowwpn3\"\nalias _attacksyncpowwpn3                  \"if $qt$powerups$qt == $qt$tp_name_ring$qt then .attacksyncpowwpnr else _attacksyncpowwpn4\"\nalias _attacksyncpowwpn4                  \"if $qt$tp_name_quad$qt !isin $qt$powerups$qt then .attacksyncpowwpnpr else _attacksyncpowwpn5\"\nalias _attacksyncpowwpn5                  \"if $qt$tp_name_ring$qt !isin $qt$powerups$qt then .attacksyncpowwpnqp else _attacksyncpowwpn6\"\nalias _attacksyncpowwpn6                  \"if $qt$tp_name_pent$qt !isin $qt$powerups$qt then .attacksyncpowwpnqr else .attacksyncpowwpnqpr\"\nalias _attacksyncpowrlg                   \"if $qt$powerups$qt == $qt$tp_name_quad$qt then .attacksyncpowrlgq else _attacksyncpowrlg2\"\nalias _attacksyncpowrlg2                  \"if $qt$powerups$qt == $qt$tp_name_pent$qt then .attacksyncpowrlgp else _attacksyncpowrlg3\"\nalias _attacksyncpowrlg3                  \"if $qt$powerups$qt == $qt$tp_name_ring$qt then .attacksyncpowrlgr else _attacksyncpowrlg4\"\nalias _attacksyncpowrlg4                  \"if $qt$tp_name_quad$qt !isin $qt$powerups$qt then .attacksyncpowrlgpr else _attacksyncpowrlg5\"\nalias _attacksyncpowrlg5                  \"if $qt$tp_name_ring$qt !isin $qt$powerups$qt then .attacksyncpowrlgqp else _attacksyncpowrlg6\"\nalias _attacksyncpowrlg6                  \"if $qt$tp_name_pent$qt !isin $qt$powerups$qt then .attacksyncpowrlgqr else .attacksyncpowrlgqpr\"\n\nalias _rlpack                \".rlpack\"\n\nalias _report   \"if $health < 1 then _lost else _report1\"\nalias _report1  \"if $qt$powerups$qt = $qt$tp_name_none$qt then _report2 else _reportp2\"\nalias _report2  \"if $bestweapon isin $tp_name_sg|$tp_name_ng then .report3 else if $tp_name_lg isin $qt$weapons$qt then _report4 else .report2\"\nalias _report4  \"if $cells > 14 then _report5 else .report2\"\nalias _report5  \"if $bestweapon = $qt$tp_name_lg$qt then .report2 else .report\"\nalias _reportp2 \"if $bestweapon isin $tp_name_sg|$tp_name_ng then _reportpow else if $tp_name_lg isin $qt$weapons$qt then _reportp4 else _reportpowwpn\"\nalias _reportp4 \"if $cells > 14 then _reportp5 else _reportpowwpn\"\nalias _reportp5 \"if $bestweapon = $qt$tp_name_lg$qt then _reportpowwpn else _reportpowrlg\"\nalias _reportpow                   \"if $qt$powerups$qt == $qt$tp_name_quad$qt then .reportpowq else _reportpow2\"\nalias _reportpow2                  \"if $qt$powerups$qt == $qt$tp_name_pent$qt then .reportpowp else _reportpow3\"\nalias _reportpow3                  \"if $qt$powerups$qt == $qt$tp_name_ring$qt then .reportpowr else _reportpow4\"\nalias _reportpow4                  \"if $qt$tp_name_quad$qt !isin $qt$powerups$qt then .reportpowpr else _reportpow5\"\nalias _reportpow5                  \"if $qt$tp_name_ring$qt !isin $qt$powerups$qt then .reportpowqp else _reportpow6\"\nalias _reportpow6                  \"if $qt$tp_name_pent$qt !isin $qt$powerups$qt then .reportpowqr else .reportpowqpr\"\nalias _reportpowwpn                   \"if $qt$powerups$qt == $qt$tp_name_quad$qt then .reportpowwpnq else _reportpowwpn2\"\nalias _reportpowwpn2                  \"if $qt$powerups$qt == $qt$tp_name_pent$qt then .reportpowwpnp else _reportpowwpn3\"\nalias _reportpowwpn3                  \"if $qt$powerups$qt == $qt$tp_name_ring$qt then .reportpowwpnr else _reportpowwpn4\"\nalias _reportpowwpn4                  \"if $qt$tp_name_quad$qt !isin $qt$powerups$qt then .reportpowwpnpr else _reportpowwpn5\"\nalias _reportpowwpn5                  \"if $qt$tp_name_ring$qt !isin $qt$powerups$qt then .reportpowwpnqp else _reportpowwpn6\"\nalias _reportpowwpn6                  \"if $qt$tp_name_pent$qt !isin $qt$powerups$qt then .reportpowwpnqr else .reportpowwpnqpr\"\nalias _reportpowrlg                   \"if $qt$powerups$qt == $qt$tp_name_quad$qt then .reportpowrlgq else _reportpowrlg2\"\nalias _reportpowrlg2                  \"if $qt$powerups$qt == $qt$tp_name_pent$qt then .reportpowrlgp else _reportpowrlg3\"\nalias _reportpowrlg3                  \"if $qt$powerups$qt == $qt$tp_name_ring$qt then .reportpowrlgr else _reportpowrlg4\"\nalias _reportpowrlg4                  \"if $qt$tp_name_quad$qt !isin $qt$powerups$qt then .reportpowrlgpr else _reportpowrlg5\"\nalias _reportpowrlg5                  \"if $qt$tp_name_ring$qt !isin $qt$powerups$qt then .reportpowrlgqp else _reportpowrlg6\"\nalias _reportpowrlg6                  \"if $qt$tp_name_pent$qt !isin $qt$powerups$qt then .reportpowrlgqr else .reportpowrlgqpr\"\n\nalias _coming                  \"if $health < 1 then _lost else _coming1\"\nalias _coming1                 \"if $qt$powerups$qt = $qt$tp_name_none$qt then _coming2 else _comingp\"\nalias _coming2                 \"if $bestweapon != $qt$tp_name_rl$qt then _coming3 else .coming\"\nalias _coming3                 \"if $armor > 99 then .coming2 else .coming\"\nalias _comingp                 \"_comingp1\"\nalias _comingp1                \"if $bestweapon != $qt$tp_name_rl$qt then _comingp2 else _comingpow\"\nalias _comingp2                \"if $armor > 99 then _comingamrpow else _comingpow\"\nalias _comingamrpow          \"if $qt$powerups$qt == $qt$tp_name_quad$qt then .comingamrpowq else _comingamrpow2\"\nalias _comingamrpow2         \"if $qt$powerups$qt == $qt$tp_name_pent$qt then .comingamrpowp else _comingamrpow3\"\nalias _comingamrpow3         \"if $qt$powerups$qt == $qt$tp_name_ring$qt then .comingamrpowr else _comingamrpow4\"\nalias _comingamrpow4         \"if $qt$tp_name_quad$qt !isin $qt$powerups$qt then .comingamrpowpr else _comingamrpow5\"\nalias _comingamrpow5         \"if $qt$tp_name_ring$qt !isin $qt$powerups$qt then .comingamrpowqp else _comingamrpow6\"\nalias _comingamrpow6         \"if $qt$tp_name_pent$qt !isin $qt$powerups$qt then .comingamrpowqr else .comingamrpowqpr\"\nalias _comingpow            \"if $qt$powerups$qt == $qt$tp_name_quad$qt then .comingpowq else _comingpow2\"\nalias _comingpow2           \"if $qt$powerups$qt == $qt$tp_name_pent$qt then .comingpowp else _comingpow3\"\nalias _comingpow3           \"if $qt$powerups$qt == $qt$tp_name_ring$qt then .comingpowr else _comingpow4\"\nalias _comingpow4           \"if $qt$tp_name_quad$qt !isin $qt$powerups$qt then .comingpowpr else _comingpow5\"\nalias _comingpow5           \"if $qt$tp_name_ring$qt !isin $qt$powerups$qt then .comingpowqp else _comingpow6\"\nalias _comingpow6           \"if $qt$tp_name_pent$qt !isin $qt$powerups$qt then .comingpowqr else .comingpowqpr\"\n\nalias _enemyp                  \"if $health < 1 then _enemyp2 else .enemyp\"\nalias _enemyp2                 \"if $ledpoint = $tp_name_status_red then .enemyp3 else .enemyp2\"\n\nalias _getpent                 \"if $qt$mapname$qt = $qtdm2$qt then else if $qt$mapname$qt = $qte1m2$qt then else _getpent1\"\nalias _getpent1                \"if $qt$tp_name_pent$qt isin $qt$powerups$qt then _getring else .getpent\"\nalias _getquad                 \"if $qt$tp_name_quad$qt isin $qt$powerups$qt then else .getquad\"\nalias _getring                 \"if $qt$tp_name_ring$qt isin $qt$powerups$qt then else if $qt$tp_name_pent$qt isin $qt$powerups$qt then else .getring\"\n\nalias _help                    \"if $health < 1 then _lost else _help_trick_maps\"\nalias _help_trick_dm2          \"if $qt$tp_name_quad-low$qt isin $location then .trick else if $qtquad-button$qt isin $location then .trick else .help\"\nalias _help_trick_maps         \"if $mapname = $qtdm2$qt then _help_trick_dm2 else .help\"\n\nalias _lost                    \"if $health >= 1000 then _lost2 else _lost1\"\nalias _lost1                   \"if $qtsomeplace$qt isin $deathloc then else _lost2\"\nalias _lost2                   \"if $tp_name_rl = $droppedweapon then .rlpack else if $tp_name_lg = $droppedweapon then _lost3 else .lost\"\nalias _lost3                   \"if $droppedammo > 13 then .lgpack else .lost\"\n\nalias _need                    \"if $health < 1 then _lost else _need1\"\nalias _need1                   \"if $qt$powerups$qt = $qt$tp_name_none$qt then _need2 else _needp\"\nalias _need2                   \"if $need = $qt$tp_name_nothing$qt then _safe else .need\"\nalias _needp                   \"if $need = $qt$tp_name_nothing$qt then _safe else _needp2\"\nalias _needp2                  \"if $qt$powerups$qt == $qt$tp_name_quad$qt then .needq else _needp3\"\nalias _needp3                  \"if $qt$powerups$qt == $qt$tp_name_pent$qt then .needp else _needp4\"\nalias _needp4                  \"if $qt$powerups$qt == $qt$tp_name_ring$qt then .needr else _needp5\"\nalias _needp5                  \"if $qt$tp_name_quad$qt !isin $qt$powerups$qt then .needpr else _needp6\"\nalias _needp6                  \"if $qt$tp_name_ring$qt !isin $qt$powerups$qt then .needqp else _needp7\"\nalias _needp7                  \"if $qt$tp_name_pent$qt !isin $qt$powerups$qt then .needqr else .needqpr\"\n\nalias _point                   \"if $ledpoint = $tp_name_status_green then else _pointx\"\nalias _pointx                  \"if $health > 1 then _point1 else if $qt$tp_name_backpack$qt isin $qt$point$qt then else _point1\"\nalias _point1                  \"if $qt$point$qt = $qt$tp_name_nothing$qt then else _point2x\"\nalias _point2x                 \"if $ledpoint = $tp_name_status_red then .point3 else _point5\"\nalias _point5                  \"if $ledpoint = $tp_name_status_yellow then _point6 else .point\"\nalias _point6                  \"if $need = $qt$tp_name_nothing$qt then .point else .point2\"\n\nalias _qout                    \"if $qt$tp_name_quad$qt isin $qt$powerups$qt then .qout2 else .qout\"\n\nalias _safe                    \"if $health < 1 then _lost else _safe1\"\nalias _safe1                   \"if $qt$powerups$qt = $qt$tp_name_none$qt then _safe2 else _safep\"\nalias _safe2                   \"if $bestweapon isin $tp_name_sg|$tp_name_ng then _report else _safe3\"\nalias _safe3                   \"if $tp_name_lg isin $qt$weapons$qt then _safe4 else .safe2\"\nalias _safe4                   \"if $cells > 14 then _safe5 else .safe2\"\nalias _safe5                   \"if $bestweapon = $qt$tp_name_lg$qt then .safe2 else .safe\"\nalias _safep                   \"if $health < 1 then _lost else _safep2\"\nalias _safep2                  \"if $bestweapon isin $tp_name_sg|$tp_name_ng then _reportp2 else _safep3\"\nalias _safep3                  \"if $tp_name_lg isin $qt$weapons$qt then _safep4 else _safepowwpn\"\nalias _safep4                  \"if $cells > 14 then _safep5 else _safepowwpn\"\nalias _safep5                  \"if $bestweapon = $qt$tp_name_lg$qt then _safepowwpn else _safepowrlg\"\nalias _safepowwpn                   \"if $qt$powerups$qt == $qt$tp_name_quad$qt then .safepowwpnq else _safepowwpn2\"\nalias _safepowwpn2                  \"if $qt$powerups$qt == $qt$tp_name_pent$qt then .safepowwpnp else _safepowwpn3\"\nalias _safepowwpn3                  \"if $qt$powerups$qt == $qt$tp_name_ring$qt then .safepowwpnr else _safepowwpn4\"\nalias _safepowwpn4                  \"if $qt$tp_name_quad$qt !isin $qt$powerups$qt then .safepowwpnpr else _safepowwpn5\"\nalias _safepowwpn5                  \"if $qt$tp_name_ring$qt !isin $qt$powerups$qt then .safepowwpnqp else _safepowwpn6\"\nalias _safepowwpn6                  \"if $qt$tp_name_pent$qt !isin $qt$powerups$qt then .safepowwpnqr else .safepowwpnqpr\"\nalias _safepowrlg                   \"if $qt$powerups$qt == $qt$tp_name_quad$qt then .safepowrlgq else _safepowrlg2\"\nalias _safepowrlg2                  \"if $qt$powerups$qt == $qt$tp_name_pent$qt then .safepowrlgp else _safepowrlg3\"\nalias _safepowrlg3                  \"if $qt$powerups$qt == $qt$tp_name_ring$qt then .safepowrlgr else _safepowrlg4\"\nalias _safepowrlg4                  \"if $qt$tp_name_quad$qt !isin $qt$powerups$qt then .safepowrlgpr else _safepowrlg5\"\nalias _safepowrlg5                  \"if $qt$tp_name_ring$qt !isin $qt$powerups$qt then .safepowrlgqp else _safepowrlg6\"\nalias _safepowrlg6                  \"if $qt$tp_name_pent$qt !isin $qt$powerups$qt then .safepowrlgqr else .safepowrlgqpr\"\n\nalias _take                    \"if $health < 1 then _lost else _take_try\"\nalias _take_ga                 \"if $armor < $tp_need_ga then .awaits else .soon\"\nalias _take_lg                 \"if $bestweapon isin $tp_name_lg then .soon else _take_point_it\"\nalias _take_point \"if $ledpoint = $tp_name_status_green then .take_mate else _take_point_1\"\nalias _take_point_1     \"__normalpoint; if $qt$point$qt != $qtrl$qt then _take_point_2 else if $qtrl$qt isin $location then _point else _take_point_2\"\nalias _take_point_2     \"if $qt$point$qt != $qtlg$qt then _take_point_3 else if $qtlg$qt isin $location then _point else _take_point_3\"\nalias _take_point_3     \"if $qt$point$qt != $qtra$qt then _take_point_4 else if $qtra$qt isin $location then _point else _take_point_4\"\nalias _take_point_4     \"if $qt$point$qt != $qtya$qt then _take_point_5 else if $qtya$qt isin $location then _point else _take_point_5\"\nalias _take_point_5     \"if $qt$point$qt != $qtga$qt then _take_try_ra else if $qtga$gt isin $location then _point else _take_try_ra\"\nalias _take_point_c     \"if $qt$point$qt != $qt$tp_name_cells$qt then .awaits else _point\"\nalias _take_point_it    \"if $ledpoint = $tp_name_status_blue then _take_point_c else .awaits\"\nalias _take_ra                 \"if $armor < $tp_need_ra then .awaits else .soon\"\nalias _take_rl                 \"if $bestweapon isin $tp_name_rl then .soon else .awaits\"\nalias _take_try                \"__awaitspoint;if $qt$point$qt != $tp_name_nothing then _take_point else _take_try_ra\"\nalias _take_try_dm2            \"if $mapname != $qtdm2$qt then _take_try_ya else if $qtsecret$qt = $qt$location$qt then _take_ra else _take_try_ya\"\nalias _take_try_ga             \"if $qtga$qt isin $location then _take_ga else _take_try_rl\"\nalias _take_try_lg             \"if $qtlg$qt isin $location then _take_lg else _take_point_it\"\nalias _take_try_mega           \"if $qtmega$qt isin $location then .awaits else _take_try_ga\"\nalias _take_try_ra             \"__normalpoint; if $qtra$qt isin $location then _take_ra else _take_try_dm2\"\nalias _take_try_rl             \"if $qtrl$qt isin $location then _take_rl else _take_try_lg\"\nalias _take_try_ya             \"if $qtya$qt isin $location then _take_ya else _take_try_mega\"\nalias _take_ya                 \"if $armor < $tp_need_ra then .awaits else .soon\"\n\nalias _teamp                   \"if $need = $qt$tp_name_nothing$qt then _teamp2 else _teamp1\"\nalias _teamp1                  \".teamp;.teamp\"\nalias _teamp2                  \".teamp2;.teamp2\"\n\nalias _took                    \"if $qt$took$qt isin $qt$tp_name_quad/$tp_name_ring/$tp_name_pent$qt then _teamp else _took_nopow\"\nalias _took_nopow              \"if $qt$took$qt == $qt$tp_name_nothing$qt then else _took_rlpack\"\nalias _took_rlpack             \"if $qt$took$qt == $qt$tp_name_backpack$qt then _took_rlpack2 else .took\"\nalias _took_rlpack2            \"if $bestweapon isin $tp_name_rl then .tookrlpack else .took\"\n"
  },
  {
    "path": "misc/cfg/ezhud.cfg",
    "content": "//\n// ------------------- ezHUD --------------------\n// Head Up Display configuration script\n// Made by johnnycz 2006\n//\n// About:\n// This script helps you customize your HUD.\n// You select, move, resize, change the frame,\n// show, hide and align the HUD elements using\n// your keyboard.\n// \n// Requirements:\n// ezQuake 1517 or newer \n// [ http://ezquake.sourceforge.net/ ]\n//\n// Usage:\n// 1) Place the script into <quake>/ezquake/cfg\n// 2) Run ezQuake, go into the console and type: \n//    /exec cfg/ezhud\n// 3) Follow the instructions on the screen\n//\n// ----------------------------------------------\n//\n\n// init\nunset_re ezhud_.\nunalias_re .ezhud_.\nhud_planmode 1\nif $scr_newhud == 0 scr_newhud 1\nhud_recalculate\nset ezhud_verbose 1\n\n// store current binds\nset ezhud_cfg_save_cmds $cfg_save_cmds\nset ezhud_cfg_save_cmdline $cfg_save_cmdline\nset ezhud_cfg_save_sysinfo $cfg_save_sysinfo\nset ezhud_cfg_save_aliases $cfg_save_aliases\nset ezhud_cfg_save_userinfo $cfg_save_userinfo\nset ezhud_cfg_save_cvars $cfg_save_cvars\nset ezhud_cfg_save_binds $cfg_save_binds\ncfg_save_cmds 0\ncfg_save_cmdline 0\ncfg_save_sysinfo 0\ncfg_save_aliases 0\ncfg_save_cvars 0\ncfg_save_binds 1\nwait\necho dumping current bindings...\ncfg_save _temp_ezhud_binds\n\n// set binds\nbind [ ezhud_sel_prev\nbind ] ezhud_sel_next\nbind pgdn ezhud_sel_nextmore\nbind pgup ezhud_sel_prevmore\nbind end ezhud_sel_last\nbind home ezhud_sel_first\nbind del ezhud_hide\nbind ins ezhud_show\n\nbind uparrow ezhud_move_up\nbind downarrow ezhud_movedown\nbind leftarrow ezhud_moveleft\nbind rightarrow ezhud_moveright\nbind space +ezhud_mark\nbind enter ezhud_toggle\nbind h ezhud_help\n\nbind kp_1 \"ezhud_align left bottom\"\nbind kp_2 \"ezhud_align center bottom\"\nbind kp_3 \"ezhud_align right bottom\"\nbind kp_4 \"ezhud_align left center\"\nbind kp_5 \"ezhud_align center center\"\nbind kp_6 \"ezhud_align right center\"\nbind kp_7 \"ezhud_align left top\"\nbind kp_8 \"ezhud_align center top\"\nbind kp_9 \"ezhud_align right top\"\n\nbind w \"ezhud_align_rel_y before\"\nbind s \"ezhud_align_rel_y after\"\nbind a \"ezhud_align_rel_x before\"\nbind d \"ezhud_align_rel_x after\"\n\nbind z ezhud_widthdown\nbind x ezhud_widthup\nbind c ezhud_heightdown\nbind v ezhud_heightup\n\nbind q \"ezhud_showmore\"\nbind r \"ezhud_resetpos\"\n\nbind shift +ezhud_bigchange\nbind kp_plus ezhud_scaleup\nbind kp_minus ezhud_scaledown\nbind kp_slash ezhud_frameup\nbind kp_star ezhud_framedown\nbind , ezhud_styleup\nbind . ezhud_styledown\nbind kp_ins ezhud_place_sel\nbind kp_enter ezhud_place_conf\n\nbind 1 \"ezhud_selplace 1\"\nbind 2 \"ezhud_selplace 2\"\nbind 3 \"ezhud_selplace 3\"\nbind 4 \"ezhud_selplace 4\"\nbind 5 \"ezhud_selplace 5\"\nbind 6 \"ezhud_selplace 6\"\nbind 7 \"ezhud_selplace 7\"\nbind 8 \"ezhud_selplace 8\"\nbind 9 \"ezhud_selplace 9\"\n\nbind u ezhud_finish\n\n// instructions\necho \"ezHud config loaded\"\necho \"press $[h$] for help\"\n\n// yeah, this sucks, TCL please! (this config is not made in TCL because I think most people would be too lazy to turn off smackdown ruleset to try it)\nalias ezhud_help \"ezhud_help1; ezhud_help2; ezhud_help3; ezhud_help4; ezhud_help4b; ezhud_help5; ezhud_help6; ezhud_help7; ezhud_help8; ezhud_help9; ezhud_help10; ezhud_help11\"\nalias ezhud_help1 \"echo $[[$], $[]$], $[page up$], $[page down$], $[home$], $[end$] to select elements\"\nalias ezhud_help2 \"echo $[Enter$], $[Del$], $[Ins$] to show/hide elements\"\nalias ezhud_help3 \"echo $[Space$] to mark mark selected elements\"\nalias ezhud_help4 \"echo arrows to move, keypad and w,a,s,d keys to align elements\"\nalias ezhud_help4b \"echo $[r$] to reset element position\"\nalias ezhud_help5 \"echo hold $[shift$] to move in larger steps\"\nalias ezhud_help6 \"echo $[z$], $[x$], $[c$], $[v$] to change width and height\"\nalias ezhud_help7 \"echo keypad plus/minus to scale up/down, slash/star to adjust frame\"\nalias ezhud_help8 \"echo use $[,$] and $[.$] to change style of an element\"\nalias ezhud_help9 \"echo use $[keypad 0$] to align elements to each other\"\nalias ezhud_help10 \"echo press $[q$] to see all element properties\"\nalias ezhud_help11 \"echo press $[u$] when you are done\"\n\n// code, etc...\nset ezhud_elem_1 ammo\nset ezhud_elem_2 ammo1\nset ezhud_elem_3 ammo2\nset ezhud_elem_4 ammo3\nset ezhud_elem_5 ammo4\nset ezhud_elem_6 armor\nset ezhud_elem_7 armordamage\nset ezhud_elem_8 clock\nset ezhud_elem_9 cross\nset ezhud_elem_10 democlock\nset ezhud_elem_11 face\nset ezhud_elem_12 fps\nset ezhud_elem_13 frags\nset ezhud_elem_14 gameclock\nset ezhud_elem_15 group1\nset ezhud_elem_16 group2\nset ezhud_elem_17 group3\nset ezhud_elem_18 group4\nset ezhud_elem_19 group5\nset ezhud_elem_20 group6\nset ezhud_elem_21 group7\nset ezhud_elem_22 group8\nset ezhud_elem_23 group9\nset ezhud_elem_24 gun\nset ezhud_elem_25 gun2\nset ezhud_elem_26 gun3\nset ezhud_elem_27 gun4\nset ezhud_elem_28 gun5\nset ezhud_elem_29 gun6\nset ezhud_elem_30 gun7\nset ezhud_elem_31 gun8\nset ezhud_elem_32 health\nset ezhud_elem_33 healthdamage\nset ezhud_elem_34 iammo\nset ezhud_elem_35 iammo1\nset ezhud_elem_36 iammo2\nset ezhud_elem_37 iammo3\nset ezhud_elem_38 iammo4\nset ezhud_elem_39 iarmor\nset ezhud_elem_40 key1\nset ezhud_elem_41 key2\nset ezhud_elem_42 mp3_time\nset ezhud_elem_43 mp3_title\nset ezhud_elem_44 net\nset ezhud_elem_45 netgraph\nset ezhud_elem_46 pent\nset ezhud_elem_47 ping\nset ezhud_elem_48 quad\nset ezhud_elem_49 radar\nset ezhud_elem_50 ring\nset ezhud_elem_51 sigil1\nset ezhud_elem_52 sigil2\nset ezhud_elem_53 sigil3\nset ezhud_elem_54 sigil4\nset ezhud_elem_55 speed\nset ezhud_elem_56 suit\nset ezhud_elem_57 teamfrags\nset ezhud_elem_58 teamholdbar\nset ezhud_elem_59 tracking\nset ezhud_max_elems 59\nset ezhud_curnum 1\nalias ezhud_curcheck \"if $ezhud_curnum > $ezhud_max_elems set ezhud_curnum $ezhud_max_elems else if $ezhud_curnum < 1 set ezhud_curnum 1\"\nalias ezhud_setcur \"ezhud_curcheck; set_ex ezhud_curelem $ezhud_elem_$ezhud_curnum\"\nalias ezhud_echocur \"echo ($ezhud_curnum/$ezhud_max_elems) $ezhud_curelem selected\"\nezhud_setcur\n\nset ezhud_place_1 screen\nset ezhud_place_2 view\nset ezhud_place_3 top\nset ezhud_place_4 sbar\nset ezhud_place_5 ibar\nset ezhud_place_6 hbar\nset ezhud_place_7 sfree\nset ezhud_place_8 ifree\nset ezhud_place_9 hfree\n\nalias ezhud_sel_next \"inc ezhud_curnum 1; ezhud_setcur; ezhud_echocur\"\nalias ezhud_sel_prev \"inc ezhud_curnum -1; ezhud_setcur; ezhud_echocur\"\nalias ezhud_sel_nextmore \"inc ezhud_curnum $ezhud_bigchange; ezhud_setcur; ezhud_echocur\"\nalias ezhud_sel_prevmore \"inc ezhud_curnum -$ezhud_bigchange; ezhud_setcur; ezhud_echocur\"\nalias ezhud_sel_first \"set ezhud_curnum 1; ezhud_setcur; ezhud_echocur\"\nalias ezhud_sel_last \"set ezhud_curnum $ezhud_max_elems; ezhud_setcur; ezhud_echocur\"\n\nalias ezhud_refresh \"hud_recalculate\"\n\nset ezhud_smallchange 1\nset ezhud_bigchange 8\nalias +ezhud_bigchange \"set ezhud_curchange $ezhud_bigchange\"\nalias -ezhud_bigchange \"set ezhud_curchange $ezhud_smallchange\"\n-ezhud_bigchange\n\nalias ezhud_move_up \"inc hud_$ezhud_curelem_pos_y -$ezhud_curchange; ezhud_refresh\"\nalias ezhud_movedown \"inc hud_$ezhud_curelem_pos_y $ezhud_curchange; ezhud_refresh\"\nalias ezhud_moveleft \"inc hud_$ezhud_curelem_pos_x -$ezhud_curchange; ezhud_refresh\"\nalias ezhud_moveright \"inc hud_$ezhud_curelem_pos_x $ezhud_curchange; ezhud_refresh\"\n\nalias ezhud_resetpos \"move $ezhud_curelem 0 0\"\n\nalias +ezhud_mark \"set_ex ezhud_tempframe $hud_$ezhud_curelem_frame; set_ex ezhud_tempshow $hud_$ezhud_curelem_show;set hud_$ezhud_curelem_frame 2; set hud_$ezhud_curelem_show 1\"\nalias -ezhud_mark \"set hud_$ezhud_curelem_frame $ezhud_tempframe; set hud_$ezhud_curelem_show $ezhud_tempshow; ezhud_refresh\"\n\nalias ezhud_toggle \"toggle hud_$ezhud_curelem_show\"\nalias ezhud_hide \"hide $ezhud_curelem\"\nalias ezhud_show \"show $ezhud_curelem\"\n\nalias ezhud_align \"align $ezhud_curelem %1 %2; ezhud_refresh\"\n\nalias ezhud_align_rel_y \"set hud_$ezhud_curelem_align_y %1; ezhud_refresh\"\nalias ezhud_align_rel_x \"set hud_$ezhud_curelem_align_x %1; ezhud_refresh\"\n\nalias ezhud_echoscale \"set_ex ezhud_curscale $hud_$ezhud_curelem_scale; set_calc ezhud_curscale $ezhud_curscale * 100; echo $ezhud_curelem size: $ezhud_curscale%\"\nalias ezhud_echoframe \"set_ex ezhud_curframe $hud_$ezhud_curelem_frame; set_calc ezhud_curpframe $ezhud_curframe * 100; if $ezhud_curpframe <= 0 ezhud_echonoframe else if $ezhud_curpframe == 200 echo $ezhud_curelem bordered frame else echo $ezhud_curelem frame: $ezhud_curpframe%%\"\nalias ezhud_echonoframe \"echo $ezhud_curelem frame removed\"\nalias ezhud_echostyle \"set_ex ezhud_curstyle $hud_$ezhud_curelem_style; echo $ezhud_curelem style: $ezhud_curstyle\"\n\nalias ezhud_scalecheck \"set_ex ezhud_curscale $hud_$ezhud_curelem_scale; if $ezhud_curscale < 0 set hud_$ezhud_curelem_scale 1\"\nalias ezhud_framecheck \"set_ex ezhud_curframe $hud_$ezhud_curelem_frame; if $ezhud_curframe < 0 set hud_$ezhud_curelem_frame 0; if $ezhud_curframe > 1 ezhud_framecheck2\"\nalias ezhud_framecheck2 \"if $ezhud_curframe < 1.5 set hud_$ezhud_curelem_frame 2 else set hud_$ezhud_curelem_frame 1\"\nalias ezhud_stylecheck \"set_ex ezhud_curstyle $hud_$ezhud_curelem_style; if $ezhud_curstyle < 0 set hud_$ezhud_curelem_style 0\"\n\nalias ezhud_widthcheck \"set_ex ezhud_curwidth $hud_$ezhud_curelem_width; if $ezhud_curwidth < 0 set hud_$ezhud_curelem_width 0\"\nalias ezhud_heightcheck \"set_ex ezhud_curheight $hud_$ezhud_curelem_height; if $ezhud_curheight < 0 set hud_$ezhud_curelem_height 0\"\n\nalias ezhud_scaleup \"inc hud_$ezhud_curelem_scale 0.1; ezhud_scalecheck; if $ezhud_verbose == 1 ezhud_echoscale\"\nalias ezhud_scaledown \"inc hud_$ezhud_curelem_scale -0.1; ezhud_scalecheck; if $ezhud_verbose == 1 ezhud_echoscale\"\nalias ezhud_frameup \"inc hud_$ezhud_curelem_frame 0.1; ezhud_framecheck; if $ezhud_verbose == 1 ezhud_echoframe\"\nalias ezhud_framedown \"inc hud_$ezhud_curelem_frame -0.1; ezhud_framecheck; if $ezhud_verbose == 1 ezhud_echoframe\"\nalias ezhud_styleup \"inc hud_$ezhud_curelem_style -1; ezhud_stylecheck; if $ezhud_verbose == 1 ezhud_echostyle\"\nalias ezhud_styledown \"inc hud_$ezhud_curelem_style 1; ezhud_stylecheck; if $ezhud_verbose == 1 ezhud_echostyle\"\n\nalias ezhud_widthup \"inc hud_$ezhud_curelem_width $ezhud_curchange; ezhud_widthcheck\"\nalias ezhud_widthdown \"inc hud_$ezhud_curelem_width -$ezhud_curchange; ezhud_widthcheck\"\nalias ezhud_heightup \"inc hud_$ezhud_curelem_height $ezhud_curchange; ezhud_heightcheck\"\nalias ezhud_heightdown \"inc hud_$ezhud_curelem_height -$ezhud_curchange; ezhud_heightcheck\"\n\nalias ezhud_selplace \"set ezhud_curelem $ezhud_place_%1; echo $ezhud_curelem selected as a target for alignment\"\n\nalias ezhud_place_sel \"echo select which element '$ezhud_curelem' should be aligned to with 1-9 or [ & ] keys and press keypad Enter to confirm; set ezhud_align_what $ezhud_curelem\"\nalias ezhud_place_conf \"place $ezhud_align_what $ezhud_curelem; set ezhud_curelem $ezhud_align_what\"\n\nalias ezhud_showmore \"$ezhud_curelem\"\n\n// we will load bindings back and put back all cfg_save_* values\nalias ezhud_clearbinds \"echo unloading keyboard bindings...; exec configs/_temp_ezhud_binds; wait; set cfg_save_cmds $ezhud_cfg_save_cmds; set cfg_save_cmdline $ezhud_cfg_save_cmdline; set cfg_save_sysinfo $ezhud_cfg_save_sysinfo; set cfg_save_aliases $ezhud_cfg_save_aliases; set cfg_save_userinfo $ezhud_cfg_save_userinfo; set cfg_save_cvars $ezhud_cfg_save_cvars; set cfg_save_binds $ezhud_cfg_save_binds\"\n\nalias ezhud_finish \"ezhud_clearbinds; unset_re ezhud_.; unalias_re ezhud_.; hud_planmode 0; hud_recalculate; hud_export ezhud_$name; echo your HUD configuration has been saved into ezquake/configs/ezhud_$name.cfg\" \n"
  },
  {
    "path": "misc/cfg/gfx_gl_eyecandy.cfg",
    "content": "//FPS and EyeCandy Settings\ncl_deadbodyFilter            \"1\"\ncl_gibFilter                 \"1\"\ncl_hidenails                 \"0\"\ncl_hiderockets               \"0\"\ncl_model_bobbing             \"0\"\ncl_muzzleflash               \"0\"\ncl_r2g                       \"0\"\ngl_detail                    \"0\"\ngl_simpleitems               \"0\"\nr_drawentities               \"1\"\nr_drawflame                  \"1\"\nr_explosionLight             \"0\"\nr_explosionLightColor        \"0\"\nr_explosionType              \"1\"\nr_flagColor                  \"0\"\nr_grenadeTrail               \"12\"\nr_lerpframes                 \"1\"\nr_lerpmuzzlehack             \"1\"\nr_lgbloodColor               \"225\"\nr_lightflicker               \"1\"\nr_powerupGlow                \"1\"\nr_rocketLight                \"0\"\nr_rocketLightColor           \"0\"\nr_rocketTrail                \"3\"\n\n//Particle Effects\ngl_bounceparticles           \"1\"\ngl_clipparticles             \"1\"\ngl_part_blobs                \"0\"\ngl_part_blood                \"1\"\ngl_part_explosions           \"1\"\ngl_part_gunshots             \"1\"\ngl_part_inferno              \"0\"\ngl_part_lavasplash           \"0\"\ngl_part_spikes               \"0\"\ngl_part_telesplash           \"1\"\ngl_part_trails               \"1\"\ngl_particle_blobs            \"0\"\ngl_particle_blood            \"1\"\ngl_particle_blood_color      \"0.001\"\ngl_particle_blood_type       \"0\"\ngl_particle_deatheffect      \"0\"\ngl_particle_explosions       \"0\"\ngl_particle_fire             \"1\"\ngl_particle_gibtrails        \"1\"\ngl_particle_gunshots         \"0\"\ngl_particle_gunshots_type    \"0\"\ngl_particle_muzzleflash      \"0\"\ngl_particle_shockwaves       \"1\"\ngl_particle_shockwaves_flat  \"0\"\ngl_particle_sparks           \"1\"\ngl_particle_spikes           \"0\"\ngl_particle_spikes_type      \"2\"\ngl_particle_trail_detail     \"75\"\ngl_particle_trail_length     \"1\"\ngl_particle_trail_time       \"1\"\ngl_particle_trail_type       \"1\"\ngl_particle_trail_width      \"3\"\ngl_squareparticles           \"0\"\n\n//Lighting\ngl_coronas                   \"0\"\ngl_coronas_tele              \"0\"\ngl_fb_bmodels                \"1\"\ngl_fb_models                 \"1\"\ngl_flashblend                \"1\"\ngl_lighting_colour           \"0\"\ngl_lighting_vertex           \"0\"\ngl_lightmode                 \"2\"\ngl_loadlitfiles              \"1\"\ngl_shaftlight                \"1\"\nr_dynamic                    \"1\"\nr_fullbright                 \"0\"\nr_shadows                    \"0\"\n\n//Turbulency and Sky Settings\ngl_caustics                  \"0\"\nr_fx_fog                     \"0\"\nr_fx_fog_end                 \"800.0\"\nr_fx_fog_sky                 \"1\"\nr_fx_fog_start               \"50.0\"\nr_fx_fog_density             \"1\"\ngl_surface_lava              \"0\"\ngl_surface_slime             \"0\"\ngl_turb_trails               \"0\"\ngl_turbalpha                 \"1\"\ngl_weather_rain              \"0\"\ngl_weather_rain_fast         \"0\"\nr_fastsky                    \"0\"\nr_fastturb                   \"0\"\nr_lavacolor                  \"80 0 0\"\nr_novis                      \"0\"\nr_skycolor                   \"40 80 150\"\nr_skyname                    \"\"\nr_slimecolor                 \"10 60 10\"\nr_telecolor                  \"255 60 60\"\nr_watercolor                 \"10 50 80\"\n\n//Weapon View Model Settings\ncl_bob                       \"0\"\ncl_bobcycle                  \"0.0\"\ncl_bobup                     \"0\"\ncl_filterdrawviewmodel       \"0\"\nr_drawviewmodel              \"1\"\nr_viewmodelSize              \"1\"\n\n//Texture Settings\ngl_ext_texture_compression   \"0\"\ngl_externalTextures_bmodels  \"1\"\ngl_externalTextures_world    \"1\"\ngl_lerpimages                \"1\"\ngl_lumaTextures              \"1\"\ngl_max_size                  \"2048\"\ngl_miptexLevel               \"0\"\ngl_picmip                    \"0\"\ngl_playermip                 \"0\"\ngl_scaleModelTextures        \"0\"\ngl_scaleTurbTextures         \"0\"\ngl_subdivide_size            \"64\"\ngl_textureless               \"0\"\ngl_texturemode               \"gl_linear_mipmap_linear\"\nr_drawflat                   \"0\"\nr_floorcolor                 \"50 100 150\"\nr_wallcolor                  \"255 255 255\"\n\n//OpenGL Rendering\ngl_buildingsparks            \"0\"\ngl_clear                     \"0\"\ngl_clearColor                \"0 0 0\"\ngl_cutf_tesla_effect         \"0\"\ngl_detpacklights             \"0\"\ngl_extratrails               \"0\"\ngl_finish                    \"0\"\ngl_inferno_speed             \"1000\"\ngl_inferno_trail             \"2\"\ngl_lightning                 \"1\"\ngl_lightning_size            \"0.1\"\ngl_lightning_sparks          \"0\"\ngl_nailtrail                 \"1\"\ngl_nailtrail_plasma          \"0\"\ngl_nailtrail_turb            \"0\"\ngl_smoothmodels              \"1\"\ngl_triplebuffer              \"1\"\nr_farclip                    \"8192\"\ngl_colorlights               \"1\"\n\n//Screen & Powerup Blends\ncl_bonusflash                \"0\"\ngl_cshiftpercent             \"0\"\ngl_polyblend                 \"1\"\ngl_powerupshells             \"1\"\nv_contentblend               \"0\"\nv_damagecshift               \"1\"\nv_dlightcshift               \"0\"\nv_pentcshift                 \"0\"\nv_quadcshift                 \"0\"\nv_ringcshift                 \"0\"\nv_suitcshift                 \"0\"\n\n//View Settings\ncl_rollangle                 \"1.0\"\ncl_rollspeed                 \"250\"\nv_centermove                 \"0.15\"\nv_centerspeed                \"500\"\nv_gunkick                    \"0\"\nv_idlescale                  \"0\"\nv_ipitch_cycle               \"1\"\nv_ipitch_level               \"0.3\"\nv_iroll_cycle                \"0.5\"\nv_iroll_level                \"0.1\"\nv_iyaw_cycle                 \"2\"\nv_iyaw_level                 \"0.3\"\nv_kickpitch                  \"0.01\"\nv_kickroll                   \"0.01\"\nv_kicktime                   \"0.05\"\nv_viewheight                 \"0\"\n"
  },
  {
    "path": "misc/cfg/gfx_gl_faithful.cfg",
    "content": "//FPS and EyeCandy Settings\ncl_deadbodyFilter            \"1\"\ncl_gibFilter                 \"1\"\ncl_hidenails                 \"0\"\ncl_hiderockets               \"0\"\ncl_model_bobbing             \"0\"\ncl_muzzleflash               \"1\"\ncl_r2g                       \"0\"\ngl_detail                    \"0\"\ngl_simpleitems               \"0\"\nr_drawentities               \"1\"\nr_drawflame                  \"1\"\nr_explosionLight             \"1\"\nr_explosionLightColor        \"0\"\nr_explosionType              \"1\"\nr_flagColor                  \"0\"\nr_grenadeTrail               \"1\"\nr_lerpframes                 \"1\"\nr_lerpmuzzlehack             \"1\"\nr_lgbloodColor               \"225\"\nr_lightflicker               \"1\"\nr_powerupGlow                \"1\"\nr_rocketLight                \"1\"\nr_rocketLightColor           \"0\"\nr_rocketTrail                \"1\"\n\n//Particle Effects\ngl_bounceparticles           \"0\"\ngl_clipparticles             \"0\"\ngl_part_blobs                \"0\"\ngl_part_blood                \"0\"\ngl_part_explosions           \"0\"\ngl_part_gunshots             \"0\"\ngl_part_inferno              \"0\"\ngl_part_lavasplash           \"0\"\ngl_part_spikes               \"0\"\ngl_part_telesplash           \"0\"\ngl_part_trails               \"0\"\ngl_particle_blobs            \"0\"\ngl_particle_blood            \"0\"\ngl_particle_blood_color      \"0\"\ngl_particle_blood_type       \"0\"\ngl_particle_deatheffect      \"0\"\ngl_particle_explosions       \"0\"\ngl_particle_fire             \"0\"\ngl_particle_gibtrails        \"0\"\ngl_particle_gunshots         \"0\"\ngl_particle_gunshots_type    \"0\"\ngl_particle_muzzleflash      \"0\"\ngl_particle_shockwaves       \"0\"\ngl_particle_shockwaves_flat  \"0\"\ngl_particle_sparks           \"0\"\ngl_particle_spikes           \"0\"\ngl_particle_spikes_type      \"0\"\ngl_particle_trail_detail     \"0\"\ngl_particle_trail_length     \"0\"\ngl_particle_trail_time       \"0\"\ngl_particle_trail_type       \"0\"\ngl_particle_trail_width      \"0\"\ngl_squareparticles           \"1\"\n\n//Lighting\ngl_coronas                   \"0\"\ngl_coronas_tele              \"0\"\ngl_fb_bmodels                \"1\"\ngl_fb_models                 \"1\"\ngl_flashblend                \"0\"\ngl_lighting_colour           \"1\"\ngl_lighting_vertex           \"0\"\ngl_lightmode                 \"2\"\ngl_loadlitfiles              \"0\"\ngl_shaftlight                \"1\"\nr_dynamic                    \"1\"\nr_fullbright                 \"0\"\nr_shadows                    \"0\"\n\n//Turbulency and Sky Settings\ngl_caustics                  \"1\"\nr_fx_fog                     \"0\"\nr_fx_fog_end                 \"0\"\nr_fx_fog_sky                 \"1\"\nr_fx_fog_start               \"0\"\nr_fx_fog_density             \"0\"\ngl_surface_lava              \"0\"\ngl_surface_slime             \"0\"\ngl_turb_trails               \"0\"\ngl_turbalpha                 \"1\"\ngl_weather_rain              \"0\"\ngl_weather_rain_fast         \"0\"\nr_fastsky                    \"0\"\nr_fastturb                   \"0\"\nr_lavacolor                  \"80 0 0\"\nr_novis                      \"0\"\nr_skycolor                   \"40 80 150\"\nr_skyname                    \"\"\nr_slimecolor                 \"10 60 10\"\nr_telecolor                  \"255 60 60\"\nr_watercolor                 \"50 80 120\"\n\n//Weapon View Model Settings\ncl_bob                       \"0\"\ncl_bobcycle                  \"0.0\"\ncl_bobup                     \"0\"\ncl_filterdrawviewmodel       \"0\"\nr_drawviewmodel              \"1\"\nr_viewmodelSize              \"1\"\n\n//Texture Settings\ngl_ext_texture_compression   \"0\"\ngl_externalTextures_bmodels  \"1\"\ngl_externalTextures_world    \"1\"\ngl_lerpimages                \"1\"\ngl_lumaTextures              \"1\"\ngl_max_size                  \"2048\"\ngl_miptexLevel               \"0\"\ngl_picmip                    \"0\"\ngl_playermip                 \"0\"\ngl_scaleModelTextures        \"0\"\ngl_scaleTurbTextures         \"1\"\ngl_subdivide_size            \"64\"\ngl_textureless               \"0\"\ngl_texturemode               \"gl_linear_mipmap_nearest\"\nr_drawflat                   \"0\"\nr_floorcolor                 \"100 20 30\"\nr_wallcolor                  \"192 100 20\"\n\n//OpenGL Rendering\ngl_buildingsparks            \"0\"\ngl_clear                     \"0\"\ngl_clearColor                \"0 0 0\"\ngl_cutf_tesla_effect         \"0\"\ngl_detpacklights             \"0\"\ngl_extratrails               \"0\"\ngl_finish                    \"0\"\ngl_inferno_speed             \"0\"\ngl_inferno_trail             \"0\"\ngl_lightning                 \"0\"\ngl_lightning_size            \"0\"\ngl_lightning_sparks          \"0\"\ngl_nailtrail                 \"0\"\ngl_nailtrail_plasma          \"0\"\ngl_nailtrail_turb            \"0\"\ngl_smoothmodels              \"1\"\ngl_triplebuffer              \"1\"\nr_farclip                    \"4096\"\ngl_colorlights               \"0\"\n\n//Screen & Powerup Blends\ncl_bonusflash                \"0\"\ngl_cshiftpercent             \"100\"\ngl_polyblend                 \"0\"\ngl_powerupshells             \"0\"\nv_contentblend               \"0.33\"\nv_damagecshift               \"1\"\nv_dlightcshift               \"1\"\nv_pentcshift                 \"1\"\nv_quadcshift                 \"1\"\nv_ringcshift                 \"1\"\nv_suitcshift                 \"1\"\n\n//View Settings\ncl_rollangle                 \"2.0\"\ncl_rollspeed                 \"200\"\nv_centermove                 \"0.15\"\nv_centerspeed                \"500\"\nv_gunkick                    \"0\"\nv_idlescale                  \"0\"\nv_ipitch_cycle               \"1\"\nv_ipitch_level               \"0.3\"\nv_iroll_cycle                \"0.5\"\nv_iroll_level                \"0.1\"\nv_iyaw_cycle                 \"2\"\nv_iyaw_level                 \"0.3\"\nv_kickpitch                  \"0.0\"\nv_kickroll                   \"0.0\"\nv_kicktime                   \"0.0\"\nv_viewheight                 \"0\"\n"
  },
  {
    "path": "misc/cfg/gfx_gl_fast.cfg",
    "content": "//FPS and EyeCandy Settings\ncl_deadbodyFilter            \"2\"\ncl_gibFilter                 \"1\"\ncl_hidenails                 \"0\"\ncl_hiderockets               \"0\"\ncl_model_bobbing             \"0\"\ncl_muzzleflash               \"0\"\ncl_r2g                       \"1\"\ngl_detail                    \"0\"\ngl_simpleitems               \"1\"\nr_drawentities               \"1\"\nr_drawflame                  \"0\"\nr_explosionLight             \"0\"\nr_explosionLightColor        \"0\"\nr_explosionType              \"4\"\nr_flagColor                  \"0\"\nr_grenadeTrail               \"0\"\nr_lerpframes                 \"1\"\nr_lerpmuzzlehack             \"0\"\nr_lgbloodColor               \"225\"\nr_lightflicker               \"0\"\nr_powerupGlow                \"0\"\nr_rocketLight                \"0\"\nr_rocketLightColor           \"0\"\nr_rocketTrail                \"0\"\n\n//Particle Effects\ngl_bounceparticles           \"0\"\ngl_clipparticles             \"0\"\ngl_part_blobs                \"0\"\ngl_part_blood                \"0\"\ngl_part_explosions           \"0\"\ngl_part_gunshots             \"0\"\ngl_part_inferno              \"0\"\ngl_part_lavasplash           \"0\"\ngl_part_spikes               \"0\"\ngl_part_telesplash           \"0\"\ngl_part_trails               \"0\"\ngl_particle_blobs            \"0\"\ngl_particle_blood            \"0\"\ngl_particle_blood_color      \"0\"\ngl_particle_blood_type       \"0\"\ngl_particle_deatheffect      \"0\"\ngl_particle_explosions       \"0\"\ngl_particle_fire             \"0\"\ngl_particle_gibtrails        \"0\"\ngl_particle_gunshots         \"0\"\ngl_particle_gunshots_type    \"0\"\ngl_particle_muzzleflash      \"0\"\ngl_particle_shockwaves       \"0\"\ngl_particle_shockwaves_flat  \"0\"\ngl_particle_sparks           \"0\"\ngl_particle_spikes           \"0\"\ngl_particle_spikes_type      \"0\"\ngl_particle_trail_detail     \"0\"\ngl_particle_trail_length     \"0\"\ngl_particle_trail_time       \"0\"\ngl_particle_trail_type       \"0\"\ngl_particle_trail_width      \"0\"\ngl_squareparticles           \"1\"\n\n//Lighting\ngl_coronas                   \"0\"\ngl_coronas_tele              \"0\"\ngl_fb_bmodels                \"1\"\ngl_fb_models                 \"1\"\ngl_flashblend                \"0\"\ngl_lighting_colour           \"0\"\ngl_lighting_vertex           \"0\"\ngl_lightmode                 \"2\"\ngl_loadlitfiles              \"0\"\ngl_shaftlight                \"0\"\nr_dynamic                    \"0\"\nr_fullbright                 \"0\"\nr_shadows                    \"0\"\n\n//Turbulency and Sky Settings\ngl_caustics                  \"0\"\nr_fx_fog                     \"0\"\nr_fx_fog_end                 \"0\"\nr_fx_fog_sky                 \"1\"\nr_fx_fog_start               \"0\"\nr_fx_fog_density             \"0\"\ngl_surface_lava              \"0\"\ngl_surface_slime             \"0\"\ngl_turb_trails               \"0\"\ngl_turbalpha                 \"1\"\ngl_weather_rain              \"0\"\ngl_weather_rain_fast         \"0\"\nr_fastsky                    \"1\"\nr_fastturb                   \"1\"\nr_lavacolor                  \"80 0 0\"\nr_novis                      \"0\"\nr_skycolor                   \"40 80 150\"\nr_skyname                    \"\"\nr_slimecolor                 \"10 60 10\"\nr_telecolor                  \"255 60 60\"\nr_watercolor                 \"20 50 120\"\n\n//Weapon View Model Settings\ncl_bob                       \"0.0\"\ncl_bobcycle                  \"0.0\"\ncl_bobup                     \"0.0\"\ncl_filterdrawviewmodel       \"0\"\nr_drawviewmodel              \"0\"\nr_viewmodelSize              \"0.3\"\n\n//Texture Settings\ngl_ext_texture_compression   \"0\"\ngl_externaltextures_bmodels  \"0\"\ngl_externaltextures_world    \"0\"\ngl_lerpimages                \"0\"\ngl_lumaTextures              \"0\"\ngl_max_size                  \"2048\"\ngl_miptexLevel               \"3\"\ngl_picmip                    \"5\"\ngl_playermip                 \"5\"\ngl_scaleModelTextures        \"0\"\ngl_scaleTurbTextures         \"1\"\ngl_subdivide_size            \"512\"\ngl_textureless               \"0\"\ngl_texturemode               \"gl_nearest\"\nr_drawflat                   \"1\"\nr_floorcolor                 \"100 20 30\"\nr_wallcolor                  \"192 100 20\"\n\n//OpenGL Rendering\ngl_buildingsparks            \"0\"\ngl_clear                     \"0\"\ngl_clearColor                \"0 0 0\"\ngl_cutf_tesla_effect         \"0\"\ngl_detpacklights             \"0\"\ngl_extratrails               \"0\"\ngl_finish                    \"0\"\ngl_inferno_speed             \"0\"\ngl_inferno_trail             \"0\"\ngl_lightning                 \"0\"\ngl_lightning_size            \"0\"\ngl_lightning_sparks          \"0\"\ngl_nailtrail                 \"0\"\ngl_nailtrail_plasma          \"0\"\ngl_nailtrail_turb            \"0\"\ngl_smoothmodels              \"0\"\ngl_triplebuffer              \"0\"\nr_farclip                    \"4096\"\ngl_colorlights               \"0\"\n\n//Screen & Powerup Blends\ncl_bonusflash                \"0\"\ngl_cshiftpercent             \"100\"\ngl_polyblend                 \"0\"\ngl_powerupshells             \"0\"\nv_contentblend               \"0\"\nv_damagecshift               \"0\"\nv_dlightcshift               \"1\"\nv_pentcshift                 \"1\"\nv_quadcshift                 \"1\"\nv_ringcshift                 \"1\"\nv_suitcshift                 \"1\"\n\n//View Settings\ncl_rollangle                 \"0\"\ncl_rollspeed                 \"200\"\nv_centermove                 \"0.15\"\nv_centerspeed                \"500\"\nv_gunkick                    \"0\"\nv_idlescale                  \"0\"\nv_ipitch_cycle               \"1\"\nv_ipitch_level               \"0.3\"\nv_iroll_cycle                \"0.5\"\nv_iroll_level                \"0.1\"\nv_iyaw_cycle                 \"2\"\nv_iyaw_level                 \"0.3\"\nv_kickpitch                  \"0\"\nv_kickroll                   \"0\"\nv_kicktime                   \"0\"\nv_viewheight                 \"0\"\n"
  },
  {
    "path": "misc/cfg/gfx_gl_higheyecandy.cfg",
    "content": "//FPS and EyeCandy Settings\r\ncl_deadbodyFilter            \"0\"\r\ncl_gibFilter                 \"0\"\r\ncl_hidenails                 \"0\"\r\ncl_hiderockets               \"0\"\r\ncl_model_bobbing             \"1\"\r\ncl_muzzleflash               \"1\"\r\ncl_r2g                       \"0\"\r\ngl_detail                    \"0\"\r\ngl_simpleitems               \"0\"\r\nr_drawentities               \"1\"\r\nr_drawflame                  \"1\"\r\nr_explosionLight             \"1\"\r\nr_explosionLightColor        \"0\"\r\nr_explosionType              \"7\"\r\nr_flagColor                  \"0\"\r\nr_grenadeTrail               \"3\"\r\nr_lerpframes                 \"1\"\r\nr_lerpmuzzlehack             \"1\"\r\nr_lgbloodColor               \"225\"\r\nr_lightflicker               \"1\"\r\nr_powerupGlow                \"1\"\r\nr_rocketLight                \"1\"\r\nr_rocketLightColor           \"0\"\r\nr_rocketTrail                \"1\"\r\n\r\n//Particle Effects\r\ngl_bounceparticles           \"1\"\r\ngl_clipparticles             \"1\"\r\ngl_part_blobs                \"1\"\r\ngl_part_blood                \"1\"\r\ngl_part_explosions           \"1\"\r\ngl_part_gunshots             \"1\"\r\ngl_part_inferno              \"1\"\r\ngl_part_lavasplash           \"1\"\r\ngl_part_spikes               \"1\"\r\ngl_part_telesplash           \"1\"\r\ngl_part_trails               \"1\"\r\ngl_particle_blobs            \"0.1\"\r\ngl_particle_blood            \"1\"\r\ngl_particle_blood_color      \"1\"\r\ngl_particle_blood_type       \"1\"\r\ngl_particle_deatheffect      \"1\"\r\ngl_particle_explosions       \"0\"\r\ngl_particle_fire             \"1\"\r\ngl_particle_gibtrails        \"1\"\r\ngl_particle_gunshots         \"0\"\r\ngl_particle_gunshots_type    \"1\"\r\ngl_particle_muzzleflash      \"0\"\r\ngl_particle_shockwaves       \"1\"\r\ngl_particle_shockwaves_flat  \"0\"\r\ngl_particle_sparks           \"1\"\r\ngl_particle_spikes           \"0.1\"\r\ngl_particle_spikes_type      \"1\"\r\ngl_particle_trail_detail     \"100\"\r\ngl_particle_trail_length     \"1\"\r\ngl_particle_trail_time       \"1\"\r\ngl_particle_trail_type       \"1\"\r\ngl_particle_trail_width      \"3\"\r\ngl_squareparticles           \"0\"\r\n\r\n//Lighting\r\ngl_coronas                   \"1\"\r\ngl_coronas_tele              \"0\"\r\ngl_fb_bmodels                \"1\"\r\ngl_fb_models                 \"1\"\r\ngl_flashblend                \"0\"\r\ngl_lighting_colour           \"1\"\r\ngl_lighting_vertex           \"1\"\r\ngl_lightmode                 \"2\"\r\ngl_loadlitfiles              \"1\"\r\ngl_shaftlight                \"1\"\r\nr_dynamic                    \"1\"\r\nr_fullbright                 \"0\"\r\nr_shadows                    \"0\"\r\n\r\n//Turbulency and Sky Settings\r\ngl_caustics                  \"1\"\r\nr_fx_fog                     \"0\"\r\nr_fx_fog_end                 \"800.0\"\r\nr_fx_fog_sky                 \"1\"\r\nr_fx_fog_start               \"50.0\"\r\nr_fx_fog_density             \"1\"\r\ngl_surface_lava              \"0\"\r\ngl_surface_slime             \"0\"\r\ngl_turb_trails               \"1\"\r\ngl_turbalpha                 \"1\"\r\ngl_weather_rain              \"0\"\r\ngl_weather_rain_fast         \"0\"\r\nr_fastsky                    \"0\"\r\nr_fastturb                   \"0\"\r\nr_lavacolor                  \"80 0 0\"\r\nr_novis                      \"0\"\r\nr_skycolor                   \"40 80 150\"\r\nr_skyname                    \"\"\r\nr_slimecolor                 \"10 60 10\"\r\nr_telecolor                  \"255 60 60\"\r\nr_watercolor                 \"50 80 120\"\r\n\r\n//Weapon View Model Settings\r\ncl_bob                       \"0\"\r\ncl_bobcycle                  \"0.0\"\r\ncl_bobup                     \"0\"\r\ncl_filterdrawviewmodel       \"0\"\r\nr_drawviewmodel              \"1\"\r\nr_viewmodelSize              \"1\"\r\n\r\n//Texture Settings\r\ngl_ext_texture_compression   \"0\"\r\ngl_externalTextures_bmodels  \"1\"\r\ngl_externalTextures_world    \"1\"\r\ngl_lerpimages                \"1\"\r\ngl_lumaTextures              \"1\"\r\ngl_max_size                  \"2048\"\r\ngl_miptexLevel               \"0\"\r\ngl_picmip                    \"0\"\r\ngl_playermip                 \"0\"\r\ngl_scaleModelTextures        \"0\"\r\ngl_scaleTurbTextures         \"1\"\r\ngl_subdivide_size            \"64\"\r\ngl_textureless               \"0\"\r\ngl_texturemode               \"gl_linear_mipmap_nearest\"\r\nr_drawflat                   \"0\"\r\nr_floorcolor                 \"100 20 30\"\r\nr_wallcolor                  \"192 100 20\"\r\n\r\n//OpenGL Rendering\r\ngl_buildingsparks            \"1\"\r\ngl_clear                     \"0\"\r\ngl_clearColor                \"0 0 0\"\r\ngl_cutf_tesla_effect         \"1\"\r\ngl_detpacklights             \"1\"\r\ngl_extratrails               \"1\"\r\ngl_finish                    \"0\"\r\ngl_inferno_speed             \"1000\"\r\ngl_inferno_trail             \"2\"\r\ngl_lightning                 \"1\"\r\ngl_lightning_size            \"3\"\r\ngl_lightning_sparks          \"0.4\"\r\ngl_nailtrail                 \"1\"\r\ngl_nailtrail_plasma          \"0\"\r\ngl_nailtrail_turb            \"0\"\r\ngl_smoothmodels              \"1\"\r\ngl_triplebuffer              \"1\"\r\nr_farclip                    \"8192\"\r\ngl_colorlights               \"1\"\r\n\r\n//Screen & Powerup Blends\r\ncl_bonusflash                \"0\"\r\ngl_cshiftpercent             \"100\"\r\ngl_polyblend                 \"0\"\r\ngl_powerupshells             \"1\"\r\nv_contentblend               \"1\"\r\nv_damagecshift               \"1\"\r\nv_dlightcshift               \"1\"\r\nv_pentcshift                 \"1\"\r\nv_quadcshift                 \"1\"\r\nv_ringcshift                 \"1\"\r\nv_suitcshift                 \"1\"\r\n\r\n//View Settings\r\ncl_rollangle                 \"2.0\"\r\ncl_rollspeed                 \"200\"\r\nv_centermove                 \"0.15\"\r\nv_centerspeed                \"500\"\r\nv_gunkick                    \"0\"\r\nv_idlescale                  \"0\"\r\nv_ipitch_cycle               \"1\"\r\nv_ipitch_level               \"0.3\"\r\nv_iroll_cycle                \"0.5\"\r\nv_iroll_level                \"0.1\"\r\nv_iyaw_cycle                 \"2\"\r\nv_iyaw_level                 \"0.3\"\r\nv_kickpitch                  \"0\"\r\nv_kickroll                   \"0\"\r\nv_kicktime                   \"0\"\r\nv_viewheight                 \"0\"\r\n"
  },
  {
    "path": "misc/cfg/gfx_sw_default.cfg",
    "content": "//FPS and EyeCandy Settings\ncl_deadbodyFilter            \"0\"\ncl_gibFilter                 \"0\"\ncl_maxfps                    \"0\"\ncl_model_bobbing             \"1\"\ncl_muzzleflash               \"1\"\ncl_nolerp                    \"0\"\ncl_physfps                   \"0\"\ncl_r2g                       \"0\"\ncl_trueLightning             \"0\"\nr_drawentities               \"1\"\nr_drawflame                  \"1\"\nr_explosionLight             \"1\"\nr_explosionLightColor        \"0\"\nr_explosionType              \"7\"\nr_flagColor                  \"0\"\nr_grenadeTrail               \"3\"\nr_lerpframes                 \"1\"\nr_lerpmuzzlehack             \"1\"\nr_lgbloodColor               \"225\"\nr_lightflicker               \"1\"\nr_powerupGlow                \"1\"\nr_rocketLight                \"1\"\nr_rocketLightColor           \"0\"\nr_rocketTrail                \"9\"\n\n//Lighting\nr_ambient                    \"0\"\nr_fullbright                 \"0\"\n\n//Turbulency and Sky Settings\nr_fastsky                    \"0\"\nr_fastturb                   \"0\"\nr_lavacolor                  \"73\"\nr_skycolor                   \"172\"\nr_slimecolor                 \"53\"\nr_telecolor                  \"26\"\nr_turbwarp                   \"1\"\nr_watercolor                 \"36\"\n\n//Weapon View Model Settings\ncl_bob                       \"0.02\"\ncl_bobcycle                  \"0.6\"\ncl_bobup                     \"0.5\"\ncl_filterdrawviewmodel       \"0\"\nr_drawviewmodel              \"1\"\nr_viewmodelSize              \"1\"\n\n//Texture Settings\nd_mipcap                     \"0\"\nd_mipscale                   \"1\"\nd_subdiv16                   \"1\"\nr_drawflat                   \"0\"\nr_floorcolor                 \"100 20 30\"\nr_max_size_1                 \"0\"\nr_wallcolor                  \"192 100 20\"\n\n//Software Rendering\ncl_multiview                 \"0\"\ncl_mvdisplayhud              \"1\"\ncl_mvinset                   \"0\"\ncl_mvinsetcrosshair          \"1\"\ncl_mvinsethud                \"1\"\nr_aliastransadj              \"100\"\nr_aliastransbase             \"200\"\nr_clearcolor                 \"2\"\nr_draworder                  \"0\"\nr_dspeeds                    \"0\"\nr_graphheight                \"15\"\nr_maxedges                   \"0\"\nr_maxsurfs                   \"0\"\nr_numedges                   \"0\"\nr_numsurfs                   \"0\"\nr_polymodelstats             \"0\"\nr_reportedgeout              \"0\"\nr_reportsurfout              \"0\"\nr_speeds                     \"0\"\nr_timegraph                  \"0\"\nr_zgraph                     \"0\"\n\n//Screen & Powerup Blends\ncl_bonusflash                \"0\"\nv_contentblend               \"1\"\nv_damagecshift               \"1\"\nv_pentcshift                 \"1\"\nv_quadcshift                 \"1\"\nv_ringcshift                 \"1\"\nv_suitcshift                 \"1\"\n\n//View Settings\ncl_rollangle                 \"2.0\"\ncl_rollspeed                 \"200\"\ndefault_fov                  \"0\"\nfov                          \"90\"\nv_centermove                 \"0.15\"\nv_centerspeed                \"500\"\nv_gunkick                    \"0\"\nv_idlescale                  \"0\"\nv_ipitch_cycle               \"1\"\nv_ipitch_level               \"0.3\"\nv_iroll_cycle                \"0.5\"\nv_iroll_level                \"0.1\"\nv_iyaw_cycle                 \"2\"\nv_iyaw_level                 \"0.3\"\nv_kickpitch                  \"0.6\"\nv_kickroll                   \"0.6\"\nv_kicktime                   \"0.5\"\nv_viewheight                 \"0\"\nviewsize                     \"100\"\n"
  },
  {
    "path": "misc/cfg/gfx_sw_fast.cfg",
    "content": "//FPS and EyeCandy Settings\ncl_deadbodyFilter            \"2\"\ncl_gibFilter                 \"1\"\ncl_maxfps                    \"0\"\ncl_model_bobbing             \"0\"\ncl_muzzleflash               \"0\"\ncl_nolerp                    \"0\"\ncl_physfps                   \"0\"\ncl_r2g                       \"1\"\ncl_trueLightning             \"0\"\nr_drawentities               \"1\"\nr_drawflame                  \"0\"\nr_explosionLight             \"0\"\nr_explosionLightColor        \"0\"\nr_explosionType              \"4\"\nr_flagColor                  \"0\"\nr_grenadeTrail               \"0\"\nr_lerpframes                 \"1\"\nr_lerpmuzzlehack             \"0\"\nr_lightflicker               \"0\"\nr_lgbloodColor               \"225\"\nr_powerupGlow                \"0\"\nr_rocketLight                \"0\"\nr_rocketLightColor           \"0\"\nr_rocketTrail                \"0\"\n\n//Lighting\nr_ambient                    \"0\"\nr_fullbright                 \"0\"\n\n//Turbulency and Sky Settings\nr_fastsky                    \"1\"\nr_fastturb                   \"1\"\nr_lavacolor                  \"73\"\nr_skycolor                   \"172\"\nr_slimecolor                 \"53\"\nr_telecolor                  \"26\"\nr_turbwarp                   \"0\"\nr_watercolor                 \"36\"\n\n//Weapon View Model Settings\ncl_bob                       \"0\"\ncl_bobcycle                  \"0\"\ncl_bobup                     \"0\"\ncl_filterdrawviewmodel       \"0\"\nr_drawviewmodel              \"0\"\nr_viewmodelSize              \"0.3\"\n\n//Texture Settings\nd_mipcap                     \"5\"\nd_mipscale                   \"5\"\nd_subdiv16                   \"1\"\nr_drawflat                   \"1\"\nr_floorcolor                 \"100 20 30\"\nr_max_size_1                 \"1\"\nr_wallcolor                  \"192 100 20\"\n\n//Software Rendering\nr_aliastransadj              \"100\"\nr_aliastransbase             \"200\"\nr_clearcolor                 \"2\"\nr_draworder                  \"0\"\nr_dspeeds                    \"0\"\nr_graphheight                \"15\"\nr_maxedges                   \"0\"\nr_maxsurfs                   \"0\"\nr_numedges                   \"0\"\nr_numsurfs                   \"0\"\nr_polymodelstats             \"0\"\nr_reportedgeout              \"0\"\nr_reportsurfout              \"0\"\nr_speeds                     \"0\"\nr_timegraph                  \"0\"\nr_zgraph                     \"0\"\n\n//Screen Settings\nsw_contrast                  \"1\"\nsw_gamma                     \"1\"\n\n//Screen & Powerup Blends\ncl_bonusflash                \"0\"\nv_contentblend               \"1\"\nv_damagecshift               \"1\"\nv_pentcshift                 \"1\"\nv_quadcshift                 \"1\"\nv_ringcshift                 \"1\"\nv_suitcshift                 \"1\"\n\n//View Settings\ncl_rollangle                 \"0\"\ncl_rollspeed                 \"0\"\nv_centermove                 \"0.15\"\nv_centerspeed                \"500\"\nv_gunkick                    \"0\"\nv_idlescale                  \"0\"\nv_ipitch_cycle               \"1\"\nv_ipitch_level               \"0.3\"\nv_iroll_cycle                \"0.5\"\nv_iroll_level                \"0.1\"\nv_iyaw_cycle                 \"2\"\nv_iyaw_level                 \"0.3\"\nv_kickpitch                  \"0\"\nv_kickroll                   \"0\"\nv_kicktime                   \"0\"\nv_viewheight                 \"0\"\nviewsize                     \"110\"\n"
  },
  {
    "path": "misc/cfg/how_to_use_these_files.txt",
    "content": "Configuration files (configs) stored in this folder contain variety\nof scripts and settings of features.\n\nYou can execute these configs by typing e.g. \"/exec cfg/movement.cfg\"\ninto the console.\n\nConfiguration file movement.cfg contains some basic movement and weapons\nkeyboard binds, basic commands like ready/break and player tracking on numpad,\nvery basic screen setup and Qizmo proxy navigation keyboard binds. \nConfiguration files teamplay.cfg and eq260.cfg contain keyboard binds for\nteamplay communication messages.\n\nConfigs with the name starting with 'hud_*' are Head Up Display configuration\nfiles. Some of them require specific 'conwidth' and 'conheight' settings (see\nezStart utility).\n\n\nAdvanced\n---\nConfiguration file teamtime.cfg contains script that helps you time items\nthrough teamplay messages with your teammates.\n\nConfigs with the name starting with 'gfx_*' are used by the game engine when\nyou choose different values for \"Graphics settings\" option in the Options menu.\nUsually you don't care much what is in those files and you don't need to\ncustomize them but you might wish to change something in them so the menu option\nsuits your needs.\n"
  },
  {
    "path": "misc/cfg/hud_aas.cfg",
    "content": "//\r\n// Head Up Display Configuration Dump\r\n//\r\n\r\nscr_newhud                    \"1\"\r\nhud_ammo1_align               \"right\"\r\nhud_ammo1_align_x             \"center\"\r\nhud_ammo1_align_y             \"bottom\"\r\nhud_ammo1_digits              \"3\"\r\nhud_ammo1_frame               \"0\"\r\nhud_ammo1_frame_color         \"0 0 0\"\r\nhud_ammo1_item_opacity        \"0.99\"\r\nhud_ammo1_order               \"5\"\r\nhud_ammo1_place               \"group2\"\r\nhud_ammo1_pos_x               \"0\"\r\nhud_ammo1_pos_y               \"-2\"\r\nhud_ammo1_scale               \"0.7\"\r\nhud_ammo1_show                \"1\"\r\nhud_ammo1_style               \"0\"\r\nhud_ammo2_align               \"right\"\r\nhud_ammo2_align_x             \"center\"\r\nhud_ammo2_align_y             \"bottom\"\r\nhud_ammo2_digits              \"3\"\r\nhud_ammo2_frame               \"0\"\r\nhud_ammo2_frame_color         \"0 0 0\"\r\nhud_ammo2_item_opacity        \"0.99\"\r\nhud_ammo2_order               \"6\"\r\nhud_ammo2_place               \"group3\"\r\nhud_ammo2_pos_x               \"0\"\r\nhud_ammo2_pos_y               \"-2\"\r\nhud_ammo2_scale               \"0.7\"\r\nhud_ammo2_show                \"1\"\r\nhud_ammo2_style               \"0\"\r\nhud_ammo3_align               \"right\"\r\nhud_ammo3_align_x             \"center\"\r\nhud_ammo3_align_y             \"bottom\"\r\nhud_ammo3_digits              \"3\"\r\nhud_ammo3_frame               \"0\"\r\nhud_ammo3_frame_color         \"0 0 0\"\r\nhud_ammo3_item_opacity        \"0.99\"\r\nhud_ammo3_order               \"7\"\r\nhud_ammo3_place               \"group4\"\r\nhud_ammo3_pos_x               \"0\"\r\nhud_ammo3_pos_y               \"-2\"\r\nhud_ammo3_scale               \"0.7\"\r\nhud_ammo3_show                \"1\"\r\nhud_ammo3_style               \"0\"\r\nhud_ammo4_align               \"right\"\r\nhud_ammo4_align_x             \"center\"\r\nhud_ammo4_align_y             \"bottom\"\r\nhud_ammo4_digits              \"3\"\r\nhud_ammo4_frame               \"0\"\r\nhud_ammo4_frame_color         \"0 0 0\"\r\nhud_ammo4_item_opacity        \"0.99\"\r\nhud_ammo4_order               \"8\"\r\nhud_ammo4_place               \"group5\"\r\nhud_ammo4_pos_x               \"0\"\r\nhud_ammo4_pos_y               \"-2\"\r\nhud_ammo4_scale               \"0.7\"\r\nhud_ammo4_show                \"1\"\r\nhud_ammo4_style               \"0\"\r\nhud_ammo_align                \"right\"\r\nhud_ammo_align_x              \"after\"\r\nhud_ammo_align_y              \"center\"\r\nhud_ammo_digits               \"3\"\r\nhud_ammo_frame                \"0\"\r\nhud_ammo_frame_color          \"0 0 0\"\r\nhud_ammo_item_opacity         \"1.0\"\r\nhud_ammo_order                \"1313\"\r\nhud_ammo_place                \"health\"\r\nhud_ammo_pos_x                \"32\"\r\nhud_ammo_pos_y                \"0\"\r\nhud_ammo_scale                \"1\"\r\nhud_ammo_show                 \"0\"\r\nhud_ammo_style                \"0\"\r\nhud_armor_align               \"right\"\r\nhud_armor_align_x             \"after\"\r\nhud_armor_align_y             \"center\"\r\nhud_armor_digits              \"3\"\r\nhud_armor_frame               \"0\"\r\nhud_armor_frame_color         \"0 0 0\"\r\nhud_armor_item_opacity        \"0.99\"\r\nhud_armor_order               \"2282\"\r\nhud_armor_pent_666            \"0\"\r\nhud_armor_place               \"iarmor\"\r\nhud_armor_pos_x               \"0\"\r\nhud_armor_pos_y               \"0\"\r\nhud_armor_scale               \"1\"\r\nhud_armor_show                \"1\"\r\nhud_armor_style               \"0\"\r\nhud_armordamage_align         \"left\"\r\nhud_armordamage_align_x       \"center\"\r\nhud_armordamage_align_y       \"after\"\r\nhud_armordamage_digits        \"3\"\r\nhud_armordamage_duration      \"1.2\"\r\nhud_armordamage_frame         \"0.2\"\r\nhud_armordamage_frame_color   \"0 0 50\"\r\nhud_armordamage_item_opacity  \"0.5\"\r\nhud_armordamage_order         \"2223\"\r\nhud_armordamage_place         \"healthdamage\"\r\nhud_armordamage_pos_x         \"0\"\r\nhud_armordamage_pos_y         \"0\"\r\nhud_armordamage_scale         \"2\"\r\nhud_armordamage_show          \"1\"\r\nhud_armordamage_style         \"1\"\r\nhud_clock_align_x             \"right\"\r\nhud_clock_align_y             \"after\"\r\nhud_clock_big                 \"0\"\r\nhud_clock_blink               \"0\"\r\nhud_clock_format              \"0\"\r\nhud_clock_frame               \"0\"\r\nhud_clock_frame_color         \"0 0 0\"\r\nhud_clock_item_opacity        \"1.0\"\r\nhud_clock_order               \"12\"\r\nhud_clock_place               \"fps\"\r\nhud_clock_pos_x               \"0\"\r\nhud_clock_pos_y               \"2\"\r\nhud_clock_scale               \"1\"\r\nhud_clock_show                \"1\"\r\nhud_clock_style               \"1\"\r\nhud_democlock_align_x         \"right\"\r\nhud_democlock_align_y         \"after\"\r\nhud_democlock_big             \"0\"\r\nhud_democlock_blink           \"0\"\r\nhud_democlock_frame           \"0\"\r\nhud_democlock_frame_color     \"0 0 0\"\r\nhud_democlock_item_opacity    \"1.0\"\r\nhud_democlock_order           \"1315\"\r\nhud_democlock_place           \"tracking\"\r\nhud_democlock_pos_x           \"0\"\r\nhud_democlock_pos_y           \"0\"\r\nhud_democlock_scale           \"1\"\r\nhud_democlock_show            \"1\"\r\nhud_democlock_style           \"3\"\r\nhud_digits_trim               \"1\"\r\nhud_editor_allowalign         \"0\"\r\nhud_editor_allowmove          \"0\"\r\nhud_editor_allowplace         \"0\"\r\nhud_editor_allowresize        \"0\"\r\nhud_face_align_x              \"before\"\r\nhud_face_align_y              \"center\"\r\nhud_face_frame                \"0\"\r\nhud_face_frame_color          \"0 0 0\"\r\nhud_face_item_opacity         \"1.0\"\r\nhud_face_order                \"1313\"\r\nhud_face_place                \"health\"\r\nhud_face_pos_x                \"0\"\r\nhud_face_pos_y                \"0\"\r\nhud_face_scale                \"1\"\r\nhud_face_show                 \"1\"\r\nhud_fps_align_x               \"right\"\r\nhud_fps_align_y               \"after\"\r\nhud_fps_decimals              \"0\"\r\nhud_fps_frame                 \"0\"\r\nhud_fps_frame_color           \"0 0 0\"\r\nhud_fps_item_opacity          \"1.0\"\r\nhud_fps_order                 \"11\"\r\nhud_fps_place                 \"ping\"\r\nhud_fps_pos_x                 \"0\"\r\nhud_fps_pos_y                 \"2\"\r\nhud_fps_show                  \"1\"\r\nhud_fps_show_min              \"0\"\r\nhud_fps_style                 \"1\"\r\nhud_fps_title                 \"1\"\r\nhud_frags_align_x             \"right\"\r\nhud_frags_align_y             \"bottom\"\r\nhud_frags_bignum              \"0\"\r\nhud_frags_cell_height         \"8\"\r\nhud_frags_cell_width          \"32\"\r\nhud_frags_colors_alpha        \"1.0\"\r\nhud_frags_cols                \"4\"\r\nhud_frags_extra_spec_info     \"ALL\"\r\nhud_frags_fliptext            \"0\"\r\nhud_frags_frame               \"0\"\r\nhud_frags_frame_color         \"0 0 0\"\r\nhud_frags_item_opacity        \"1.0\"\r\nhud_frags_maxname             \"16\"\r\nhud_frags_order               \"0\"\r\nhud_frags_padtext             \"1\"\r\nhud_frags_place               \"top\"\r\nhud_frags_pos_x               \"0\"\r\nhud_frags_pos_y               \"0\"\r\nhud_frags_rows                \"1\"\r\nhud_frags_show                \"0\"\r\nhud_frags_shownames           \"0\"\r\nhud_frags_showself_always     \"1\"\r\nhud_frags_showteams           \"0\"\r\nhud_frags_space_x             \"1\"\r\nhud_frags_space_y             \"1\"\r\nhud_frags_strip               \"1\"\r\nhud_frags_style               \"0\"\r\nhud_frags_teamsort            \"0\"\r\nhud_frags_vertical            \"0\"\r\nhud_gameclock_align_x         \"center\"\r\nhud_gameclock_align_y         \"before\"\r\nhud_gameclock_big             \"1\"\r\nhud_gameclock_blink           \"0\"\r\nhud_gameclock_countdown       \"0\"\r\nhud_gameclock_frame           \"0.1\"\r\nhud_gameclock_frame_color     \"0 0 0\"\r\nhud_gameclock_item_opacity    \"0.99\"\r\nhud_gameclock_offset          \"0\"\r\nhud_gameclock_order           \"10\"\r\nhud_gameclock_place           \"teamfrags\"\r\nhud_gameclock_pos_x           \"0\"\r\nhud_gameclock_pos_y           \"-2\"\r\nhud_gameclock_scale           \"1\"\r\nhud_gameclock_show            \"1\"\r\nhud_gameclock_style           \"0\"\r\nhud_group1_align_x            \"center\"\r\nhud_group1_align_y            \"bottom\"\r\nhud_group1_frame              \"0.25\"\r\nhud_group1_frame_color        \"0 0 0\"\r\nhud_group1_height             \"32\"\r\nhud_group1_item_opacity       \"1.0\"\r\nhud_group1_name               \"group1\"\r\nhud_group1_order              \"0\"\r\nhud_group1_pic_alpha          \"0.5\"\r\nhud_group1_pic_scalemode      \"2\"\r\nhud_group1_picture            \"\"\r\nhud_group1_place              \"screen\"\r\nhud_group1_pos_x              \"0\"\r\nhud_group1_pos_y              \"2\"\r\nhud_group1_show               \"1\"\r\nhud_group1_width              \"220\"\r\nhud_group2_align_x            \"before\"\r\nhud_group2_align_y            \"center\"\r\nhud_group2_frame              \"0.25\"\r\nhud_group2_frame_color        \"0 0 0\"\r\nhud_group2_height             \"32\"\r\nhud_group2_item_opacity       \"1.0\"\r\nhud_group2_name               \"group2\"\r\nhud_group2_order              \"2\"\r\nhud_group2_pic_alpha          \"0.5\"\r\nhud_group2_pic_scalemode      \"2\"\r\nhud_group2_picture            \"\"\r\nhud_group2_place              \"group3\"\r\nhud_group2_pos_x              \"-5\"\r\nhud_group2_pos_y              \"0\"\r\nhud_group2_show               \"1\"\r\nhud_group2_width              \"50\"\r\nhud_group3_align_x            \"center\"\r\nhud_group3_align_y            \"before\"\r\nhud_group3_frame              \"0.25\"\r\nhud_group3_frame_color        \"0 0 0\"\r\nhud_group3_height             \"32\"\r\nhud_group3_item_opacity       \"1\"\r\nhud_group3_name               \"group3\"\r\nhud_group3_order              \"1\"\r\nhud_group3_pic_alpha          \"0.5\"\r\nhud_group3_pic_scalemode      \"2\"\r\nhud_group3_picture            \"\"\r\nhud_group3_place              \"group1\"\r\nhud_group3_pos_x              \"-30\"\r\nhud_group3_pos_y              \"-4\"\r\nhud_group3_show               \"1\"\r\nhud_group3_width              \"50\"\r\nhud_group4_align_x            \"center\"\r\nhud_group4_align_y            \"before\"\r\nhud_group4_frame              \"0.25\"\r\nhud_group4_frame_color        \"0 0 0\"\r\nhud_group4_height             \"32\"\r\nhud_group4_item_opacity       \"1.0\"\r\nhud_group4_name               \"group4\"\r\nhud_group4_order              \"2\"\r\nhud_group4_pic_alpha          \"0.5\"\r\nhud_group4_pic_scalemode      \"2\"\r\nhud_group4_picture            \"\"\r\nhud_group4_place              \"group1\"\r\nhud_group4_pos_x              \"30\"\r\nhud_group4_pos_y              \"-4\"\r\nhud_group4_show               \"1\"\r\nhud_group4_width              \"50\"\r\nhud_group5_align_x            \"after\"\r\nhud_group5_align_y            \"center\"\r\nhud_group5_frame              \"0.25\"\r\nhud_group5_frame_color        \"0 0 0\"\r\nhud_group5_height             \"32\"\r\nhud_group5_item_opacity       \"1.0\"\r\nhud_group5_name               \"group5\"\r\nhud_group5_order              \"3\"\r\nhud_group5_pic_alpha          \"0.5\"\r\nhud_group5_pic_scalemode      \"2\"\r\nhud_group5_picture            \"\"\r\nhud_group5_place              \"group4\"\r\nhud_group5_pos_x              \"5\"\r\nhud_group5_pos_y              \"0\"\r\nhud_group5_show               \"1\"\r\nhud_group5_width              \"50\"\r\nhud_group6_align_x            \"right\"\r\nhud_group6_align_y            \"bottom\"\r\nhud_group6_frame              \"0\"\r\nhud_group6_frame_color        \"0 0 0\"\r\nhud_group6_height             \"32\"\r\nhud_group6_item_opacity       \"1.0\"\r\nhud_group6_name               \"group6\"\r\nhud_group6_order              \"1\"\r\nhud_group6_pic_alpha          \"0.5\"\r\nhud_group6_pic_scalemode      \"2\"\r\nhud_group6_picture            \"\"\r\nhud_group6_place              \"screen\"\r\nhud_group6_pos_x              \"0\"\r\nhud_group6_pos_y              \"0\"\r\nhud_group6_show               \"1\"\r\nhud_group6_width              \"88\"\r\nhud_group7_align_x            \"center\"\r\nhud_group7_align_y            \"center\"\r\nhud_group7_frame              \"0.5\"\r\nhud_group7_frame_color        \"255 0 0\"\r\nhud_group7_height             \"384\"\r\nhud_group7_item_opacity       \"1.0\"\r\nhud_group7_name               \"group7\"\r\nhud_group7_order              \"0\"\r\nhud_group7_pic_alpha          \"1.0\"\r\nhud_group7_pic_scalemode      \"0\"\r\nhud_group7_picture            \"\"\r\nhud_group7_place              \"screen\"\r\nhud_group7_pos_x              \"1\"\r\nhud_group7_pos_y              \"0\"\r\nhud_group7_show               \"0\"\r\nhud_group7_width              \"1\"\r\nhud_group8_align_x            \"center\"\r\nhud_group8_align_y            \"bottom\"\r\nhud_group8_frame              \".5\"\r\nhud_group8_frame_color        \"0 0 0\"\r\nhud_group8_height             \"64\"\r\nhud_group8_item_opacity       \"1.0\"\r\nhud_group8_name               \"group8\"\r\nhud_group8_order              \"0\"\r\nhud_group8_pic_alpha          \"1.0\"\r\nhud_group8_pic_scalemode      \"0\"\r\nhud_group8_picture            \"\"\r\nhud_group8_place              \"screen\"\r\nhud_group8_pos_x              \"0\"\r\nhud_group8_pos_y              \"0\"\r\nhud_group8_show               \"0\"\r\nhud_group8_width              \"64\"\r\nhud_group9_align_x            \"right\"\r\nhud_group9_align_y            \"bottom\"\r\nhud_group9_frame              \".5\"\r\nhud_group9_frame_color        \"0 0 0\"\r\nhud_group9_height             \"64\"\r\nhud_group9_item_opacity       \"1.0\"\r\nhud_group9_name               \"group9\"\r\nhud_group9_order              \"0\"\r\nhud_group9_pic_alpha          \"1.0\"\r\nhud_group9_pic_scalemode      \"0\"\r\nhud_group9_picture            \"\"\r\nhud_group9_place              \"screen\"\r\nhud_group9_pos_x              \"0\"\r\nhud_group9_pos_y              \"0\"\r\nhud_group9_show               \"0\"\r\nhud_group9_width              \"64\"\r\nhud_gun2_align_x              \"left\"\r\nhud_gun2_align_y              \"top\"\r\nhud_gun2_frame                \"0\"\r\nhud_gun2_frame_color          \"0 0 0\"\r\nhud_gun2_item_opacity         \"1\"\r\nhud_gun2_order                \"5\"\r\nhud_gun2_place                \"group2\"\r\nhud_gun2_pos_x                \"2\"\r\nhud_gun2_pos_y                \"0\"\r\nhud_gun2_scale                \"1\"\r\nhud_gun2_show                 \"0\"\r\nhud_gun2_style                \"0\"\r\nhud_gun3_align_x              \"right\"\r\nhud_gun3_align_y              \"top\"\r\nhud_gun3_frame                \"0\"\r\nhud_gun3_frame_color          \"0 0 0\"\r\nhud_gun3_item_opacity         \"1.0\"\r\nhud_gun3_order                \"6\"\r\nhud_gun3_place                \"group2\"\r\nhud_gun3_pos_x                \"-2\"\r\nhud_gun3_pos_y                \"0\"\r\nhud_gun3_scale                \"1\"\r\nhud_gun3_show                 \"1\"\r\nhud_gun3_style                \"0\"\r\nhud_gun4_align_x              \"left\"\r\nhud_gun4_align_y              \"top\"\r\nhud_gun4_frame                \"0\"\r\nhud_gun4_frame_color          \"0 0 0\"\r\nhud_gun4_item_opacity         \"1.0\"\r\nhud_gun4_order                \"7\"\r\nhud_gun4_place                \"group3\"\r\nhud_gun4_pos_x                \"0\"\r\nhud_gun4_pos_y                \"0\"\r\nhud_gun4_scale                \"1\"\r\nhud_gun4_show                 \"1\"\r\nhud_gun4_style                \"0\"\r\nhud_gun5_align_x              \"right\"\r\nhud_gun5_align_y              \"top\"\r\nhud_gun5_frame                \"0\"\r\nhud_gun5_frame_color          \"0 0 0\"\r\nhud_gun5_item_opacity         \"1.0\"\r\nhud_gun5_order                \"8\"\r\nhud_gun5_place                \"group3\"\r\nhud_gun5_pos_x                \"-2\"\r\nhud_gun5_pos_y                \"0\"\r\nhud_gun5_scale                \"1\"\r\nhud_gun5_show                 \"1\"\r\nhud_gun5_style                \"0\"\r\nhud_gun6_align_x              \"left\"\r\nhud_gun6_align_y              \"top\"\r\nhud_gun6_frame                \"0\"\r\nhud_gun6_frame_color          \"0 0 0\"\r\nhud_gun6_item_opacity         \"1.0\"\r\nhud_gun6_order                \"9\"\r\nhud_gun6_place                \"group4\"\r\nhud_gun6_pos_x                \"0\"\r\nhud_gun6_pos_y                \"0\"\r\nhud_gun6_scale                \"1\"\r\nhud_gun6_show                 \"1\"\r\nhud_gun6_style                \"0\"\r\nhud_gun7_align_x              \"right\"\r\nhud_gun7_align_y              \"top\"\r\nhud_gun7_frame                \"0\"\r\nhud_gun7_frame_color          \"0\"\r\nhud_gun7_item_opacity         \"1.0\"\r\nhud_gun7_order                \"10\"\r\nhud_gun7_place                \"group4\"\r\nhud_gun7_pos_x                \"-2\"\r\nhud_gun7_pos_y                \"0\"\r\nhud_gun7_scale                \"1\"\r\nhud_gun7_show                 \"1\"\r\nhud_gun7_style                \"0\"\r\nhud_gun8_align_x              \"left\"\r\nhud_gun8_align_y              \"top\"\r\nhud_gun8_frame                \"0\"\r\nhud_gun8_frame_color          \"0 0 0\"\r\nhud_gun8_item_opacity         \"1.0\"\r\nhud_gun8_order                \"11\"\r\nhud_gun8_place                \"group5\"\r\nhud_gun8_pos_x                \"2\"\r\nhud_gun8_pos_y                \"0\"\r\nhud_gun8_scale                \"1\"\r\nhud_gun8_show                 \"1\"\r\nhud_gun8_style                \"0\"\r\nhud_gun8_wide                 \"0\"\r\nhud_gun_align_x               \"center\"\r\nhud_gun_align_y               \"bottom\"\r\nhud_gun_frame                 \"0\"\r\nhud_gun_frame_color           \"0 0 0\"\r\nhud_gun_item_opacity          \"1.0\"\r\nhud_gun_order                 \"0\"\r\nhud_gun_place                 \"ibar\"\r\nhud_gun_pos_x                 \"0\"\r\nhud_gun_pos_y                 \"0\"\r\nhud_gun_scale                 \"1\"\r\nhud_gun_show                  \"0\"\r\nhud_gun_style                 \"0\"\r\nhud_gun_wide                  \"0\"\r\nhud_health_align              \"right\"\r\nhud_health_align_x            \"right\"\r\nhud_health_align_y            \"center\"\r\nhud_health_digits             \"3\"\r\nhud_health_frame              \"0\"\r\nhud_health_frame_color        \"0 0 0\"\r\nhud_health_item_opacity       \"0.99\"\r\nhud_health_order              \"1312\"\r\nhud_health_place              \"group1\"\r\nhud_health_pos_x              \"-5\"\r\nhud_health_pos_y              \"0\"\r\nhud_health_scale              \"1\"\r\nhud_health_show               \"1\"\r\nhud_health_style              \"0\"\r\nhud_healthdamage_align        \"left\"\r\nhud_healthdamage_align_x      \"left\"\r\nhud_healthdamage_align_y      \"center\"\r\nhud_healthdamage_digits       \"3\"\r\nhud_healthdamage_duration     \"1.2\"\r\nhud_healthdamage_frame        \"0.2\"\r\nhud_healthdamage_frame_color  \"50 0 0\"\r\nhud_healthdamage_item_opacity \"0.5\"\r\nhud_healthdamage_order        \"1313\"\r\nhud_healthdamage_place        \"screen\"\r\nhud_healthdamage_pos_x        \"0\"\r\nhud_healthdamage_pos_y        \"0\"\r\nhud_healthdamage_scale        \"2\"\r\nhud_healthdamage_show         \"1\"\r\nhud_healthdamage_style        \"1\"\r\nhud_iammo1_align_x            \"before\"\r\nhud_iammo1_align_y            \"center\"\r\nhud_iammo1_frame              \"0\"\r\nhud_iammo1_frame_color        \"0 0 0\"\r\nhud_iammo1_item_opacity       \"1\"\r\nhud_iammo1_order              \"5\"\r\nhud_iammo1_place              \"group2\"\r\nhud_iammo1_pos_x              \"-2\"\r\nhud_iammo1_pos_y              \"0\"\r\nhud_iammo1_scale              \"1\"\r\nhud_iammo1_show               \"0\"\r\nhud_iammo1_style              \"0\"\r\nhud_iammo2_align_x            \"before\"\r\nhud_iammo2_align_y            \"center\"\r\nhud_iammo2_frame              \"0\"\r\nhud_iammo2_frame_color        \"0 0 0\"\r\nhud_iammo2_item_opacity       \"1.0\"\r\nhud_iammo2_order              \"6\"\r\nhud_iammo2_place              \"group3\"\r\nhud_iammo2_pos_x              \"-2\"\r\nhud_iammo2_pos_y              \"0\"\r\nhud_iammo2_scale              \"1\"\r\nhud_iammo2_show               \"0\"\r\nhud_iammo2_style              \"0\"\r\nhud_iammo3_align_x            \"after\"\r\nhud_iammo3_align_y            \"center\"\r\nhud_iammo3_frame              \"0\"\r\nhud_iammo3_frame_color        \"0 0 0\"\r\nhud_iammo3_item_opacity       \"1.0\"\r\nhud_iammo3_order              \"7\"\r\nhud_iammo3_place              \"group4\"\r\nhud_iammo3_pos_x              \"2\"\r\nhud_iammo3_pos_y              \"0\"\r\nhud_iammo3_scale              \"1\"\r\nhud_iammo3_show               \"0\"\r\nhud_iammo3_style              \"0\"\r\nhud_iammo4_align_x            \"after\"\r\nhud_iammo4_align_y            \"center\"\r\nhud_iammo4_frame              \"0\"\r\nhud_iammo4_frame_color        \"0 0 0\"\r\nhud_iammo4_item_opacity       \"1.0\"\r\nhud_iammo4_order              \"8\"\r\nhud_iammo4_place              \"group5\"\r\nhud_iammo4_pos_x              \"2\"\r\nhud_iammo4_pos_y              \"0\"\r\nhud_iammo4_scale              \"1\"\r\nhud_iammo4_show               \"0\"\r\nhud_iammo4_style              \"0\"\r\nhud_iammo_align_x             \"before\"\r\nhud_iammo_align_y             \"center\"\r\nhud_iammo_frame               \"0\"\r\nhud_iammo_frame_color         \"0 0 0\"\r\nhud_iammo_item_opacity        \"1.0\"\r\nhud_iammo_order               \"1314\"\r\nhud_iammo_place               \"ammo\"\r\nhud_iammo_pos_x               \"0\"\r\nhud_iammo_pos_y               \"0\"\r\nhud_iammo_scale               \"1\"\r\nhud_iammo_show                \"0\"\r\nhud_iammo_style               \"0\"\r\nhud_iarmor_align_x            \"left\"\r\nhud_iarmor_align_y            \"center\"\r\nhud_iarmor_frame              \"0\"\r\nhud_iarmor_frame_color        \"0 0 0\"\r\nhud_iarmor_item_opacity       \"1.0\"\r\nhud_iarmor_order              \"2281\"\r\nhud_iarmor_place              \"group1\"\r\nhud_iarmor_pos_x              \"5\"\r\nhud_iarmor_pos_y              \"0\"\r\nhud_iarmor_scale              \"1\"\r\nhud_iarmor_show               \"1\"\r\nhud_iarmor_style              \"0\"\r\nhud_inputlag_align_x          \"center\"\r\nhud_inputlag_align_y          \"center\"\r\nhud_inputlag_frame            \"0\"\r\nhud_inputlag_frame_color      \"0 0 0\"\r\nhud_inputlag_height           \"60\"\r\nhud_inputlag_item_opacity     \"1.0\"\r\nhud_inputlag_order            \"1\"\r\nhud_inputlag_place            \"screen\"\r\nhud_inputlag_pos_x            \"0\"\r\nhud_inputlag_pos_y            \"0\"\r\nhud_inputlag_show             \"0\"\r\nhud_inputlag_style            \"0\"\r\nhud_inputlag_width            \"200\"\r\nhud_itemsclock_align_x        \"left\"\r\nhud_itemsclock_align_y        \"before\"\r\nhud_itemsclock_frame          \"0\"\r\nhud_itemsclock_frame_color    \"0 0 0\"\r\nhud_itemsclock_item_opacity   \"1.0\"\r\nhud_itemsclock_order          \"11\"\r\nhud_itemsclock_place          \"gameclock\"\r\nhud_itemsclock_pos_x          \"0\"\r\nhud_itemsclock_pos_y          \"-4\"\r\nhud_itemsclock_show           \"1\"\r\nhud_itemsclock_style          \"1\"\r\nhud_itemsclock_timelimit      \"20\"\r\nhud_key1_align_x              \"before\"\r\nhud_key1_align_y              \"bottom\"\r\nhud_key1_frame                \"0\"\r\nhud_key1_frame_color          \"0 0 0\"\r\nhud_key1_item_opacity         \"1.0\"\r\nhud_key1_order                \"93\"\r\nhud_key1_place                \"key2\"\r\nhud_key1_pos_x                \"0\"\r\nhud_key1_pos_y                \"0\"\r\nhud_key1_scale                \"1.5\"\r\nhud_key1_show                 \"1\"\r\nhud_key1_style                \"0\"\r\nhud_key2_align_x              \"before\"\r\nhud_key2_align_y              \"top\"\r\nhud_key2_frame                \"0\"\r\nhud_key2_frame_color          \"0 0 0\"\r\nhud_key2_item_opacity         \"1.0\"\r\nhud_key2_order                \"92\"\r\nhud_key2_place                \"iammo1\"\r\nhud_key2_pos_x                \"-4\"\r\nhud_key2_pos_y                \"0\"\r\nhud_key2_scale                \"1.5\"\r\nhud_key2_show                 \"1\"\r\nhud_key2_style                \"0\"\r\nhud_keys_align_x              \"right\"\r\nhud_keys_align_y              \"center\"\r\nhud_keys_frame                \"0.5\"\r\nhud_keys_frame_color          \"20 20 20\"\r\nhud_keys_item_opacity         \"1.0\"\r\nhud_keys_order                \"1\"\r\nhud_keys_place                \"screen\"\r\nhud_keys_pos_x                \"0\"\r\nhud_keys_pos_y                \"0\"\r\nhud_keys_scale                \"3\"\r\nhud_keys_show                 \"0\"\r\nhud_mouserate_align_x         \"after\"\r\nhud_mouserate_align_y         \"center\"\r\nhud_mouserate_frame           \"0.1\"\r\nhud_mouserate_frame_color     \"0 0 0\"\r\nhud_mouserate_interval        \"1\"\r\nhud_mouserate_item_opacity    \"1.0\"\r\nhud_mouserate_order           \"13\"\r\nhud_mouserate_place           \"vidlag\"\r\nhud_mouserate_pos_x           \"0\"\r\nhud_mouserate_pos_y           \"0\"\r\nhud_mouserate_show            \"1\"\r\nhud_mouserate_style           \"1\"\r\nhud_mouserate_title           \"1\"\r\nhud_mp3_time_align_x          \"center\"\r\nhud_mp3_time_align_y          \"before\"\r\nhud_mp3_time_frame            \"0.7\"\r\nhud_mp3_time_frame_color      \"50 0 0\"\r\nhud_mp3_time_item_opacity     \"1.0\"\r\nhud_mp3_time_on_scoreboard    \"1\"\r\nhud_mp3_time_order            \"2\"\r\nhud_mp3_time_place            \"mp3_title\"\r\nhud_mp3_time_pos_x            \"0\"\r\nhud_mp3_time_pos_y            \"0\"\r\nhud_mp3_time_show             \"1\"\r\nhud_mp3_time_style            \"0\"\r\nhud_mp3_title_align_x         \"center\"\r\nhud_mp3_title_align_y         \"bottom\"\r\nhud_mp3_title_frame           \"0.5\"\r\nhud_mp3_title_frame_color     \"0 0 0\"\r\nhud_mp3_title_height          \"8\"\r\nhud_mp3_title_item_opacity    \"1.0\"\r\nhud_mp3_title_on_scoreboard   \"1\"\r\nhud_mp3_title_order           \"1\"\r\nhud_mp3_title_place           \"screen\"\r\nhud_mp3_title_pos_x           \"0\"\r\nhud_mp3_title_pos_y           \"32\"\r\nhud_mp3_title_scroll          \"1\"\r\nhud_mp3_title_scroll_delay    \"0.5\"\r\nhud_mp3_title_show            \"1\"\r\nhud_mp3_title_style           \"2\"\r\nhud_mp3_title_width           \"640\"\r\nhud_mp3_title_wordwrap        \"0\"\r\nhud_net_align_x               \"left\"\r\nhud_net_align_y               \"center\"\r\nhud_net_frame                 \"0.2\"\r\nhud_net_frame_color           \"0 0 0\"\r\nhud_net_item_opacity          \"1.0\"\r\nhud_net_order                 \"7\"\r\nhud_net_period                \"1\"\r\nhud_net_place                 \"top\"\r\nhud_net_pos_x                 \"0\"\r\nhud_net_pos_y                 \"0\"\r\nhud_net_show                  \"0\"\r\nhud_netgraph_align_x          \"after\"\r\nhud_netgraph_align_y          \"bottom\"\r\nhud_netgraph_alpha            \"1\"\r\nhud_netgraph_frame            \"0\"\r\nhud_netgraph_frame_color      \"0 0 0\"\r\nhud_netgraph_full             \"0\"\r\nhud_netgraph_height           \"32\"\r\nhud_netgraph_inframes         \"0\"\r\nhud_netgraph_item_opacity     \"1.0\"\r\nhud_netgraph_lostscale        \"1\"\r\nhud_netgraph_order            \"9\"\r\nhud_netgraph_place            \"net\"\r\nhud_netgraph_ploss            \"1\"\r\nhud_netgraph_pos_x            \"0\"\r\nhud_netgraph_pos_y            \"0\"\r\nhud_netgraph_scale            \"256\"\r\nhud_netgraph_show             \"0\"\r\nhud_netgraph_swap_x           \"0\"\r\nhud_netgraph_swap_y           \"0\"\r\nhud_netgraph_width            \"256\"\r\nhud_netproblem_align_x        \"left\"\r\nhud_netproblem_align_y        \"top\"\r\nhud_netproblem_frame          \"0\"\r\nhud_netproblem_frame_color    \"0 0 0\"\r\nhud_netproblem_item_opacity   \"1.0\"\r\nhud_netproblem_order          \"0\"\r\nhud_netproblem_place          \"top\"\r\nhud_netproblem_pos_x          \"0\"\r\nhud_netproblem_pos_y          \"0\"\r\nhud_netproblem_scale          \"1\"\r\nhud_netproblem_show           \"1\"\r\nhud_notify_align_x            \"left\"\r\nhud_notify_align_y            \"center\"\r\nhud_notify_cols               \"50\"\r\nhud_notify_frame              \"0\"\r\nhud_notify_frame_color        \"0 0 0\"\r\nhud_notify_item_opacity       \"0.99\"\r\nhud_notify_order              \"8\"\r\nhud_notify_place              \"screen\"\r\nhud_notify_pos_x              \"0\"\r\nhud_notify_pos_y              \"80\"\r\nhud_notify_rows               \"10\"\r\nhud_notify_scale              \"1.2\"\r\nhud_notify_show               \"1\"\r\nhud_notify_time               \"10\"\r\nhud_ownfrags_align_x          \"center\"\r\nhud_ownfrags_align_y          \"top\"\r\nhud_ownfrags_frame            \"0.3\"\r\nhud_ownfrags_frame_color      \"0 0 80\"\r\nhud_ownfrags_item_opacity     \"1.0\"\r\nhud_ownfrags_order            \"1\"\r\nhud_ownfrags_place            \"screen\"\r\nhud_ownfrags_pos_x            \"0\"\r\nhud_ownfrags_pos_y            \"50\"\r\nhud_ownfrags_scale            \"2\"\r\nhud_ownfrags_show             \"1\"\r\nhud_ownfrags_timeout          \"3\"\r\nhud_pent_align_x              \"before\"\r\nhud_pent_align_y              \"center\"\r\nhud_pent_frame                \"0\"\r\nhud_pent_frame_color          \"0 0 0\"\r\nhud_pent_item_opacity         \"1.0\"\r\nhud_pent_order                \"140\"\r\nhud_pent_place                \"quad\"\r\nhud_pent_pos_x                \"0\"\r\nhud_pent_pos_y                \"0\"\r\nhud_pent_scale                \"1\"\r\nhud_pent_show                 \"1\"\r\nhud_pent_style                \"0\"\r\nhud_ping_align_x              \"right\"\r\nhud_ping_align_y              \"top\"\r\nhud_ping_blink                \"0\"\r\nhud_ping_frame                \"0\"\r\nhud_ping_frame_color          \"0 0 0\"\r\nhud_ping_item_opacity         \"1.0\"\r\nhud_ping_order                \"9\"\r\nhud_ping_period               \"1\"\r\nhud_ping_place                \"group6\"\r\nhud_ping_pos_x                \"0\"\r\nhud_ping_pos_y                \"0\"\r\nhud_ping_show                 \"1\"\r\nhud_ping_show_dev             \"0\"\r\nhud_ping_show_max             \"0\"\r\nhud_ping_show_min             \"0\"\r\nhud_ping_show_pl              \"1\"\r\nhud_ping_style                \"1\"\r\nhud_planmode                  \"0\"\r\nhud_quad_align_x              \"before\"\r\nhud_quad_align_y              \"top\"\r\nhud_quad_frame                \"0\"\r\nhud_quad_frame_color          \"0 0 0\"\r\nhud_quad_item_opacity         \"0.99\"\r\nhud_quad_order                \"139\"\r\nhud_quad_place                \"group1\"\r\nhud_quad_pos_x                \"0\"\r\nhud_quad_pos_y                \"0\"\r\nhud_quad_scale                \"1\"\r\nhud_quad_show                 \"0\"\r\nhud_quad_style                \"0\"\r\nhud_radar_align_x             \"left\"\r\nhud_radar_align_y             \"top\"\r\nhud_radar_autosize            \"0\"\r\nhud_radar_fade_players        \"1\"\r\nhud_radar_frame               \"0\"\r\nhud_radar_frame_color         \"0 0 0\"\r\nhud_radar_height              \"35%\"\r\nhud_radar_highlight           \"0\"\r\nhud_radar_highlight_color     \"yellow\"\r\nhud_radar_item_opacity        \"1\"\r\nhud_radar_itemfilter          \"backpack quad pent armor mega ring suit\"\r\nhud_radar_onlytp              \"3\"\r\nhud_radar_opacity             \"0.5\"\r\nhud_radar_order               \"0\"\r\nhud_radar_otherfilter         \"projectiles gibs explosions shotgun\"\r\nhud_radar_place               \"screen\"\r\nhud_radar_player_size         \"10\"\r\nhud_radar_pos_x               \"0\"\r\nhud_radar_pos_y               \"0\"\r\nhud_radar_show                \"0\"\r\nhud_radar_show_height         \"1\"\r\nhud_radar_show_hold           \"0\"\r\nhud_radar_show_names          \"0\"\r\nhud_radar_show_powerups       \"1\"\r\nhud_radar_show_stats          \"1\"\r\nhud_radar_weaponfilter        \"gl rl lg\"\r\nhud_radar_width               \"35%\"\r\nhud_ring_align_x              \"before\"\r\nhud_ring_align_y              \"yop\"\r\nhud_ring_frame                \"0\"\r\nhud_ring_frame_color          \"0 0 0\"\r\nhud_ring_item_opacity         \"0.99\"\r\nhud_ring_order                \"141\"\r\nhud_ring_place                \"pent\"\r\nhud_ring_pos_x                \"0\"\r\nhud_ring_pos_y                \"0\"\r\nhud_ring_scale                \"1\"\r\nhud_ring_show                 \"1\"\r\nhud_ring_style                \"0\"\r\nhud_sigil1_align_x            \"left\"\r\nhud_sigil1_align_y            \"bottom\"\r\nhud_sigil1_frame              \"0\"\r\nhud_sigil1_frame_color        \"0 0 0\"\r\nhud_sigil1_item_opacity       \"0.5\"\r\nhud_sigil1_order              \"13\"\r\nhud_sigil1_place              \"screen\"\r\nhud_sigil1_pos_x              \"0\"\r\nhud_sigil1_pos_y              \"0\"\r\nhud_sigil1_scale              \"2\"\r\nhud_sigil1_show               \"0\"\r\nhud_sigil1_style              \"0\"\r\nhud_sigil2_align_x            \"after\"\r\nhud_sigil2_align_y            \"top\"\r\nhud_sigil2_frame              \"0\"\r\nhud_sigil2_frame_color        \"0 0 0\"\r\nhud_sigil2_item_opacity       \"0.5\"\r\nhud_sigil2_order              \"14\"\r\nhud_sigil2_place              \"sigil1\"\r\nhud_sigil2_pos_x              \"0\"\r\nhud_sigil2_pos_y              \"0\"\r\nhud_sigil2_scale              \"2\"\r\nhud_sigil2_show               \"1\"\r\nhud_sigil2_style              \"0\"\r\nhud_sigil3_align_x            \"after\"\r\nhud_sigil3_align_y            \"top\"\r\nhud_sigil3_frame              \"0\"\r\nhud_sigil3_frame_color        \"0 0 0\"\r\nhud_sigil3_item_opacity       \"0.5\"\r\nhud_sigil3_order              \"15\"\r\nhud_sigil3_place              \"sigil2\"\r\nhud_sigil3_pos_x              \"0\"\r\nhud_sigil3_pos_y              \"0\"\r\nhud_sigil3_scale              \"2\"\r\nhud_sigil3_show               \"1\"\r\nhud_sigil3_style              \"0\"\r\nhud_sigil4_align_x            \"after\"\r\nhud_sigil4_align_y            \"top\"\r\nhud_sigil4_frame              \"0\"\r\nhud_sigil4_frame_color        \"0 0 0\"\r\nhud_sigil4_item_opacity       \"0.5\"\r\nhud_sigil4_order              \"16\"\r\nhud_sigil4_place              \"sigil3\"\r\nhud_sigil4_pos_x              \"0\"\r\nhud_sigil4_pos_y              \"0\"\r\nhud_sigil4_scale              \"2\"\r\nhud_sigil4_show               \"1\"\r\nhud_sigil4_style              \"0\"\r\nhud_speed2_align_x            \"center\"\r\nhud_speed2_align_y            \"center\"\r\nhud_speed2_color_fast         \"72\"\r\nhud_speed2_color_fastest      \"216\"\r\nhud_speed2_color_insane       \"229\"\r\nhud_speed2_color_normal       \"100\"\r\nhud_speed2_color_stopped      \"52\"\r\nhud_speed2_frame              \"0\"\r\nhud_speed2_frame_color        \"0 0 0\"\r\nhud_speed2_item_opacity       \"1.0\"\r\nhud_speed2_opacity            \"1.0\"\r\nhud_speed2_order              \"7\"\r\nhud_speed2_orientation        \"0\"\r\nhud_speed2_place              \"screen\"\r\nhud_speed2_pos_x              \"0\"\r\nhud_speed2_pos_y              \"0\"\r\nhud_speed2_radius             \"50.0\"\r\nhud_speed2_show               \"0\"\r\nhud_speed2_wrapspeed          \"500\"\r\nhud_speed2_xyz                \"0\"\r\nhud_speed_align_x             \"center\"\r\nhud_speed_align_y             \"center\"\r\nhud_speed_color_fast          \"72\"\r\nhud_speed_color_fastest       \"216\"\r\nhud_speed_color_insane        \"229\"\r\nhud_speed_color_normal        \"5\"\r\nhud_speed_color_stopped       \"1\"\r\nhud_speed_frame               \"0.2\"\r\nhud_speed_frame_color         \"0 0 0\"\r\nhud_speed_height              \"15\"\r\nhud_speed_item_opacity        \"1.0\"\r\nhud_speed_opacity             \"1.0\"\r\nhud_speed_order               \"7\"\r\nhud_speed_place               \"screen\"\r\nhud_speed_pos_x               \"0\"\r\nhud_speed_pos_y               \"20\"\r\nhud_speed_show                \"0\"\r\nhud_speed_style               \"0\"\r\nhud_speed_text_align          \"3\"\r\nhud_speed_tick_spacing        \"0.2\"\r\nhud_speed_vertical            \"0\"\r\nhud_speed_vertical_text       \"1\"\r\nhud_speed_width               \"100\"\r\nhud_speed_xyz                 \"0\"\r\nhud_suit_align_x              \"before\"\r\nhud_suit_align_y              \"center\"\r\nhud_suit_frame                \"0\"\r\nhud_suit_frame_color          \"0 0 0\"\r\nhud_suit_item_opacity         \"1.0\"\r\nhud_suit_order                \"142\"\r\nhud_suit_place                \"ring\"\r\nhud_suit_pos_x                \"0\"\r\nhud_suit_pos_y                \"0\"\r\nhud_suit_scale                \"1\"\r\nhud_suit_show                 \"1\"\r\nhud_suit_style                \"0\"\r\nhud_teamfrags_align_x         \"center\"\r\nhud_teamfrags_align_y         \"before\"\r\nhud_teamfrags_bignum          \"0\"\r\nhud_teamfrags_cell_height     \"8\"\r\nhud_teamfrags_cell_width      \"38\"\r\nhud_teamfrags_colors_alpha    \"1.0\"\r\nhud_teamfrags_cols            \"2\"\r\nhud_teamfrags_extra_spec_info \"0\"\r\nhud_teamfrags_fliptext        \"1\"\r\nhud_teamfrags_frame           \"0\"\r\nhud_teamfrags_frame_color     \"0 0 0\"\r\nhud_teamfrags_item_opacity    \"1.0\"\r\nhud_teamfrags_maxname         \"16\"\r\nhud_teamfrags_onlytp          \"0\"\r\nhud_teamfrags_order           \"9\"\r\nhud_teamfrags_padtext         \"1\"\r\nhud_teamfrags_place           \"group1\"\r\nhud_teamfrags_pos_x           \"0\"\r\nhud_teamfrags_pos_y           \"-40\"\r\nhud_teamfrags_rows            \"1\"\r\nhud_teamfrags_show            \"1\"\r\nhud_teamfrags_shownames       \"0\"\r\nhud_teamfrags_space_x         \"2\"\r\nhud_teamfrags_space_y         \"0\"\r\nhud_teamfrags_strip           \"1\"\r\nhud_teamfrags_style           \"0\"\r\nhud_teamfrags_vertical        \"0\"\r\nhud_teamholdbar_align_x       \"left\"\r\nhud_teamholdbar_align_y       \"bottom\"\r\nhud_teamholdbar_frame         \"0\"\r\nhud_teamholdbar_frame_color   \"0 0 0\"\r\nhud_teamholdbar_height        \"8\"\r\nhud_teamholdbar_item_opacity  \"1.0\"\r\nhud_teamholdbar_onlytp        \"0\"\r\nhud_teamholdbar_opacity       \"0.8\"\r\nhud_teamholdbar_order         \"0\"\r\nhud_teamholdbar_place         \"top\"\r\nhud_teamholdbar_pos_x         \"0\"\r\nhud_teamholdbar_pos_y         \"0\"\r\nhud_teamholdbar_show          \"0\"\r\nhud_teamholdbar_show_text     \"1\"\r\nhud_teamholdbar_vertical      \"0\"\r\nhud_teamholdbar_vertical_text \"0\"\r\nhud_teamholdbar_width         \"200\"\r\nhud_teamholdinfo_align_x      \"left\"\r\nhud_teamholdinfo_align_y      \"bottom\"\r\nhud_teamholdinfo_frame        \"0\"\r\nhud_teamholdinfo_frame_color  \"0 0 0\"\r\nhud_teamholdinfo_height       \"8\"\r\nhud_teamholdinfo_item_opacity \"1.0\"\r\nhud_teamholdinfo_itemfilter   \"quad ra ya ga mega pent rl quad\"\r\nhud_teamholdinfo_onlytp       \"0\"\r\nhud_teamholdinfo_opacity      \"0.8\"\r\nhud_teamholdinfo_order        \"0\"\r\nhud_teamholdinfo_place        \"top\"\r\nhud_teamholdinfo_pos_x        \"0\"\r\nhud_teamholdinfo_pos_y        \"0\"\r\nhud_teamholdinfo_show         \"0\"\r\nhud_teamholdinfo_style        \"1\"\r\nhud_teamholdinfo_width        \"200\"\r\nhud_teaminfo_align_right      \"0\"\r\nhud_teaminfo_align_x          \"right\"\r\nhud_teaminfo_align_y          \"center\"\r\nhud_teaminfo_armor_style      \"3\"\r\nhud_teaminfo_frame            \"0.2\"\r\nhud_teaminfo_frame_color      \"0 0 0\"\r\nhud_teaminfo_item_opacity     \"1.0\"\r\nhud_teaminfo_layout           \"%p%n $x10%l$x11 %a/%H %w\"\r\nhud_teaminfo_loc_width        \"5\"\r\nhud_teaminfo_low_health       \"25\"\r\nhud_teaminfo_name_width       \"6\"\r\nhud_teaminfo_order            \"0\"\r\nhud_teaminfo_place            \"screen\"\r\nhud_teaminfo_pos_x            \"0\"\r\nhud_teaminfo_pos_y            \"0\"\r\nhud_teaminfo_scale            \"1\"\r\nhud_teaminfo_show             \"1\"\r\nhud_teaminfo_show_enemies     \"0\"\r\nhud_teaminfo_show_self        \"1\"\r\nhud_teaminfo_weapon_style     \"1\"\r\nhud_tp_need                   \"1\"\r\nhud_tracking_align_x          \"after\"\r\nhud_tracking_align_y          \"top\"\r\nhud_tracking_format           \"%t  %n\"\r\nhud_tracking_frame            \"0\"\r\nhud_tracking_frame_color      \"0 0 0\"\r\nhud_tracking_item_opacity     \"1.0\"\r\nhud_tracking_order            \"1314\"\r\nhud_tracking_place            \"gameclock\"\r\nhud_tracking_pos_x            \"0\"\r\nhud_tracking_pos_y            \"0\"\r\nhud_tracking_scale            \"1\"\r\nhud_tracking_show             \"1\"\r\nhud_vidlag_align_x            \"after\"\r\nhud_vidlag_align_y            \"center\"\r\nhud_vidlag_frame              \"0.2\"\r\nhud_vidlag_frame_color        \"0 0 0\"\r\nhud_vidlag_item_opacity       \"1.0\"\r\nhud_vidlag_order              \"12\"\r\nhud_vidlag_place              \"fps\"\r\nhud_vidlag_pos_x              \"0\"\r\nhud_vidlag_pos_y              \"0\"\r\nhud_vidlag_show               \"0\"\r\nhud_vidlag_style              \"1\"\r\nhud_recalculate\r\n"
  },
  {
    "path": "misc/cfg/hud_berzerk.cfg",
    "content": "echo Berzerk's Head Up Display Configuration\n\nscr_newhud                  \"1\"\nhud_ammo1_align             \"right\"\nhud_ammo1_align_x           \"left\"\nhud_ammo1_align_y           \"center\"\nhud_ammo1_digits            \"3\"\nhud_ammo1_frame             \"0\"\nhud_ammo1_place             \"group4\"\nhud_ammo1_pos_x             \"50\"\nhud_ammo1_pos_y             \"0\"\nhud_ammo1_scale             \"1\"\nhud_ammo1_show              \"2\"\nhud_ammo1_style             \"1\"\nhud_ammo2_align             \"left\"\nhud_ammo2_align_x           \"left\"\nhud_ammo2_align_y           \"center\"\nhud_ammo2_digits            \"3\"\nhud_ammo2_frame             \"0\"\nhud_ammo2_place             \"group4\"\nhud_ammo2_pos_x             \"130\"\nhud_ammo2_pos_y             \"0\"\nhud_ammo2_scale             \"1\"\nhud_ammo2_show              \"1\"\nhud_ammo2_style             \"1\"\nhud_ammo3_align             \"left\"\nhud_ammo3_align_x           \"left\"\nhud_ammo3_align_y           \"center\"\nhud_ammo3_digits            \"3\"\nhud_ammo3_frame             \"0\"\nhud_ammo3_place             \"group4\"\nhud_ammo3_pos_x             \"250\"\nhud_ammo3_pos_y             \"0\"\nhud_ammo3_scale             \"1\"\nhud_ammo3_show              \"1\"\nhud_ammo3_style             \"1\"\nhud_ammo4_align             \"left\"\nhud_ammo4_align_x           \"left\"\nhud_ammo4_align_y           \"center\"\nhud_ammo4_digits            \"3\"\nhud_ammo4_frame             \"0\"\nhud_ammo4_place             \"group4\"\nhud_ammo4_pos_x             \"333\"\nhud_ammo4_pos_y             \"0\"\nhud_ammo4_scale             \"1\"\nhud_ammo4_show              \"1\"\nhud_ammo4_style             \"1\"\nhud_ammo_align              \"center\"\nhud_ammo_align_x            \"center\"\nhud_ammo_align_y            \"center\"\nhud_ammo_digits             \"2\"\nhud_ammo_frame              \"0\"\nhud_ammo_place              \"screen\"\nhud_ammo_pos_x              \"0\"\nhud_ammo_pos_y              \"11\"\nhud_ammo_scale              \"1\"\nhud_ammo_show               \"1\"\nhud_ammo_style              \"1\"\nhud_armor_align             \"right\"\nhud_armor_align_x           \"center\"\nhud_armor_align_y           \"center\"\nhud_armor_digits            \"3\"\nhud_armor_frame             \"0\"\nhud_armor_place             \"screen\"\nhud_armor_pos_x             \"-50\"\nhud_armor_pos_y             \"0\"\nhud_armor_scale             \"1\"\nhud_armor_show              \"1\"\nhud_armor_style             \"1\"\nhud_armordamage_align       \"right\"\nhud_armordamage_align_x     \"left\"\nhud_armordamage_align_y     \"before\"\nhud_armordamage_digits      \"3\"\nhud_armordamage_duration    \"0.8\"\nhud_armordamage_frame       \"0\"\nhud_armordamage_place       \"armor\"\nhud_armordamage_pos_x       \"0\"\nhud_armordamage_pos_y       \"0\"\nhud_armordamage_scale       \"1\"\nhud_armordamage_show        \"0\"\nhud_armordamage_style       \"1\"\nhud_clock_align_x           \"right\"\nhud_clock_align_y           \"center\"\nhud_clock_big               \"0\"\nhud_clock_blink             \"0\"\nhud_clock_frame             \"0\"\nhud_clock_place             \"@group3\"\nhud_clock_pos_x             \"-5\"\nhud_clock_pos_y             \"0\"\nhud_clock_scale             \"1\"\nhud_clock_show              \"1\"\nhud_clock_style             \"2\"\nhud_face_align_x            \"after\"\nhud_face_align_y            \"center\"\nhud_face_frame              \"0\"\nhud_face_place              \"armor\"\nhud_face_pos_x              \"10\"\nhud_face_pos_y              \"0\"\nhud_face_scale              \"1\"\nhud_face_show               \"0\"\nhud_fps_align_x             \"center\"\nhud_fps_align_y             \"center\"\nhud_fps_decimals            \"0\"\nhud_fps_frame               \"0\"\nhud_fps_place               \"@group3\"\nhud_fps_pos_x               \"15\"\nhud_fps_pos_y               \"0\"\nhud_fps_show                \"1\"\nhud_fps_show_min            \"0\"\nhud_fps_title               \"1\"\nhud_frags_align_x           \"left\"\nhud_frags_align_y           \"bottom\"\nhud_frags_cell_height       \"7\"\nhud_frags_cell_width        \"28\"\nhud_frags_cols              \"8\"\nhud_frags_fliptext          \"0\"\nhud_frags_frame             \"0\"\nhud_frags_padtext           \"1\"\nhud_frags_place             \"screen\"\nhud_frags_pos_x             \"3\"\nhud_frags_pos_y             \"-40\"\nhud_frags_rows              \"8\"\nhud_frags_show              \"0\"\nhud_frags_shownames         \"0\"\nhud_frags_showself_always   \"1\"\nhud_frags_showteams         \"0\"\nhud_frags_space_x           \"2\"\nhud_frags_space_y           \"2\"\nhud_frags_strip             \"1\"\nhud_frags_style             \"0\"\nhud_frags_teamsort          \"0\"\nhud_frags_vertical          \"1\"\nhud_gameclock_align_x       \"right\"\nhud_gameclock_align_y       \"console\"\nhud_gameclock_big           \"1\"\nhud_gameclock_blink         \"1\"\nhud_gameclock_countdown     \"0\"\nhud_gameclock_frame         \"0\"\nhud_gameclock_place         \"top\"\nhud_gameclock_pos_x         \"0\"\nhud_gameclock_pos_y         \"0\"\nhud_gameclock_scale         \"1\"\nhud_gameclock_show          \"0\"\nhud_gameclock_style         \"0\"\nhud_group1_align_x          \"center\"\nhud_group1_align_y          \"center\"\nhud_group1_alpha            \"1\"\nhud_group1_frame            \".5\"\nhud_group1_height           \"480\"\nhud_group1_name             \"group1\"\nhud_group1_picture          \"\"\nhud_group1_place            \"screen\"\nhud_group1_pos_x            \"1\"\nhud_group1_pos_y            \"0\"\nhud_group1_show             \"1\"\nhud_group1_tile             \"0\"\nhud_group1_width            \"2\"\nhud_group2_align_x          \"center\"\nhud_group2_align_y          \"center\"\nhud_group2_alpha            \"1\"\nhud_group2_frame            \".5\"\nhud_group2_height           \"2\"\nhud_group2_name             \"group2\"\nhud_group2_picture          \"\"\nhud_group2_place            \"screen\"\nhud_group2_pos_x            \"0\"\nhud_group2_pos_y            \"1\"\nhud_group2_show             \"0\"\nhud_group2_tile             \"0\"\nhud_group2_width            \"640\"\nhud_group3_align_x          \"right\"\nhud_group3_align_y          \"bottom\"\nhud_group3_alpha            \"1\"\nhud_group3_frame            \".25\"\nhud_group3_height           \"10\"\nhud_group3_name             \"group3\"\nhud_group3_picture          \"\"\nhud_group3_place            \"screen\"\nhud_group3_pos_x            \"-25\"\nhud_group3_pos_y            \"-5\"\nhud_group3_show             \"1\"\nhud_group3_tile             \"0\"\nhud_group3_width            \"355\"\nhud_group4_align_x          \"left\"\nhud_group4_align_y          \"bottom\"\nhud_group4_alpha            \"1\"\nhud_group4_frame            \".25\"\nhud_group4_height           \"10\"\nhud_group4_name             \"group4\"\nhud_group4_picture          \"\"\nhud_group4_place            \"screen\"\nhud_group4_pos_x            \"20\"\nhud_group4_pos_y            \"-20\"\nhud_group4_show             \"1\"\nhud_group4_tile             \"0\"\nhud_group4_width            \"355\"\nhud_group5_align_x          \"after\"\nhud_group5_align_y          \"center\"\nhud_group5_alpha            \"1\"\nhud_group5_frame            \"0.25\"\nhud_group5_height           \"10\"\nhud_group5_name             \"group5\"\nhud_group5_picture          \"\"\nhud_group5_place            \"group4\"\nhud_group5_pos_x            \"5\"\nhud_group5_pos_y            \"0\"\nhud_group5_show             \"0\"\nhud_group5_tile             \"0\"\nhud_group5_width            \"75\"\nhud_group6_align_x          \"left\"\nhud_group6_align_y          \"top\"\nhud_group6_alpha            \"1\"\nhud_group6_frame            \".5\"\nhud_group6_height           \"64\"\nhud_group6_name             \"group6\"\nhud_group6_picture          \"\"\nhud_group6_place            \"screen\"\nhud_group6_pos_x            \"0\"\nhud_group6_pos_y            \"0\"\nhud_group6_show             \"0\"\nhud_group6_tile             \"0\"\nhud_group6_width            \"64\"\nhud_group7_align_x          \"left\"\nhud_group7_align_y          \"top\"\nhud_group7_alpha            \"1\"\nhud_group7_frame            \".5\"\nhud_group7_height           \"64\"\nhud_group7_name             \"group7\"\nhud_group7_picture          \"\"\nhud_group7_place            \"screen\"\nhud_group7_pos_x            \"0\"\nhud_group7_pos_y            \"0\"\nhud_group7_show             \"0\"\nhud_group7_tile             \"0\"\nhud_group7_width            \"64\"\nhud_group8_align_x          \"left\"\nhud_group8_align_y          \"top\"\nhud_group8_alpha            \"1\"\nhud_group8_frame            \".5\"\nhud_group8_height           \"64\"\nhud_group8_name             \"group8\"\nhud_group8_picture          \"\"\nhud_group8_place            \"screen\"\nhud_group8_pos_x            \"0\"\nhud_group8_pos_y            \"0\"\nhud_group8_show             \"0\"\nhud_group8_tile             \"0\"\nhud_group8_width            \"64\"\nhud_group9_align_x          \"left\"\nhud_group9_align_y          \"top\"\nhud_group9_alpha            \"1\"\nhud_group9_frame            \".5\"\nhud_group9_height           \"64\"\nhud_group9_name             \"group9\"\nhud_group9_picture          \"\"\nhud_group9_place            \"screen\"\nhud_group9_pos_x            \"0\"\nhud_group9_pos_y            \"0\"\nhud_group9_show             \"0\"\nhud_group9_tile             \"0\"\nhud_group9_width            \"64\"\nhud_gun2_align_x            \"center\"\nhud_gun2_align_y            \"center\"\nhud_gun2_frame              \"0\"\nhud_gun2_place              \"screen\"\nhud_gun2_pos_x              \"-60\"\nhud_gun2_pos_y              \"110\"\nhud_gun2_scale              \"1\"\nhud_gun2_show               \"1\"\nhud_gun2_style              \"1\"\nhud_gun3_align_x            \"center\"\nhud_gun3_align_y            \"center\"\nhud_gun3_frame              \"0\"\nhud_gun3_place              \"screen\"\nhud_gun3_pos_x              \"-40\"\nhud_gun3_pos_y              \"110\"\nhud_gun3_scale              \"1\"\nhud_gun3_show               \"1\"\nhud_gun3_style              \"1\"\nhud_gun4_align_x            \"center\"\nhud_gun4_align_y            \"center\"\nhud_gun4_frame              \"0\"\nhud_gun4_place              \"screen\"\nhud_gun4_pos_x              \"-20\"\nhud_gun4_pos_y              \"110\"\nhud_gun4_scale              \"1\"\nhud_gun4_show               \"1\"\nhud_gun4_style              \"1\"\nhud_gun5_align_x            \"center\"\nhud_gun5_align_y            \"center\"\nhud_gun5_frame              \"0\"\nhud_gun5_place              \"screen\"\nhud_gun5_pos_x              \"0\"\nhud_gun5_pos_y              \"110\"\nhud_gun5_scale              \"1\"\nhud_gun5_show               \"1\"\nhud_gun5_style              \"1\"\nhud_gun6_align_x            \"center\"\nhud_gun6_align_y            \"center\"\nhud_gun6_frame              \"0\"\nhud_gun6_place              \"screen\"\nhud_gun6_pos_x              \"20\"\nhud_gun6_pos_y              \"110\"\nhud_gun6_scale              \"1\"\nhud_gun6_show               \"1\"\nhud_gun6_style              \"1\"\nhud_gun7_align_x            \"center\"\nhud_gun7_align_y            \"center\"\nhud_gun7_frame              \"0\"\nhud_gun7_place              \"screen\"\nhud_gun7_pos_x              \"40\"\nhud_gun7_pos_y              \"110\"\nhud_gun7_scale              \"1\"\nhud_gun7_show               \"1\"\nhud_gun7_style              \"1\"\nhud_gun8_align_x            \"center\"\nhud_gun8_align_y            \"center\"\nhud_gun8_frame              \"0\"\nhud_gun8_place              \"screen\"\nhud_gun8_pos_x              \"60\"\nhud_gun8_pos_y              \"110\"\nhud_gun8_scale              \"1\"\nhud_gun8_show               \"1\"\nhud_gun8_style              \"1\"\nhud_gun8_wide               \"0\"\nhud_gun_align_x             \"center\"\nhud_gun_align_y             \"bottom\"\nhud_gun_frame               \"0\"\nhud_gun_place               \"top\"\nhud_gun_pos_x               \"0\"\nhud_gun_pos_y               \"0\"\nhud_gun_scale               \"1\"\nhud_gun_show                \"0\"\nhud_gun_style               \"0\"\nhud_gun_wide                \"0\"\nhud_health_align            \"left\"\nhud_health_align_x          \"center\"\nhud_health_align_y          \"center\"\nhud_health_digits           \"3\"\nhud_health_frame            \"0\"\nhud_health_place            \"screen\"\nhud_health_pos_x            \"50\"\nhud_health_pos_y            \"0\"\nhud_health_scale            \"1\"\nhud_health_show             \"1\"\nhud_health_style            \"1\"\nhud_healthdamage_align      \"right\"\nhud_healthdamage_align_x    \"left\"\nhud_healthdamage_align_y    \"before\"\nhud_healthdamage_digits     \"3\"\nhud_healthdamage_duration   \"0.8\"\nhud_healthdamage_frame      \"0\"\nhud_healthdamage_place      \"health\"\nhud_healthdamage_pos_x      \"0\"\nhud_healthdamage_pos_y      \"0\"\nhud_healthdamage_scale      \"1\"\nhud_healthdamage_show       \"0\"\nhud_healthdamage_style      \"1\"\nhud_iammo1_align_x          \"center\"\nhud_iammo1_align_y          \"center\"\nhud_iammo1_frame            \"0\"\nhud_iammo1_place            \"ammo1\"\nhud_iammo1_pos_x            \"-35\"\nhud_iammo1_pos_y            \"0\"\nhud_iammo1_scale            \"0.75\"\nhud_iammo1_show             \"1\"\nhud_iammo1_style            \"0\"\nhud_iammo2_align_x          \"center\"\nhud_iammo2_align_y          \"center\"\nhud_iammo2_frame            \"0\"\nhud_iammo2_place            \"ammo2\"\nhud_iammo2_pos_x            \"-35\"\nhud_iammo2_pos_y            \"0\"\nhud_iammo2_scale            \"0.75\"\nhud_iammo2_show             \"1\"\nhud_iammo2_style            \"0\"\nhud_iammo3_align_x          \"center\"\nhud_iammo3_align_y          \"center\"\nhud_iammo3_frame            \"0\"\nhud_iammo3_place            \"ammo3\"\nhud_iammo3_pos_x            \"-35\"\nhud_iammo3_pos_y            \"0\"\nhud_iammo3_scale            \"0.75\"\nhud_iammo3_show             \"1\"\nhud_iammo3_style            \"0\"\nhud_iammo4_align_x          \"center\"\nhud_iammo4_align_y          \"center\"\nhud_iammo4_frame            \"0\"\nhud_iammo4_place            \"ammo4\"\nhud_iammo4_pos_x            \"-30\"\nhud_iammo4_pos_y            \"0\"\nhud_iammo4_scale            \"0.75\"\nhud_iammo4_show             \"1\"\nhud_iammo4_style            \"0\"\nhud_iammo_align_x           \"center\"\nhud_iammo_align_y           \"top\"\nhud_iammo_frame             \"0\"\nhud_iammo_place             \"screen\"\nhud_iammo_pos_x             \"0\"\nhud_iammo_pos_y             \"0\"\nhud_iammo_scale             \"1\"\nhud_iammo_show              \"0\"\nhud_iammo_style             \"1\"\nhud_iarmor_align_x          \"center\"\nhud_iarmor_align_y          \"center\"\nhud_iarmor_frame            \"0\"\nhud_iarmor_place            \"armor\"\nhud_iarmor_pos_x            \"-16\"\nhud_iarmor_pos_y            \"0\"\nhud_iarmor_scale            \"1\"\nhud_iarmor_show             \"1\"\nhud_iarmor_style            \"1\"\nhud_key1_align_x            \"top\"\nhud_key1_align_y            \"left\"\nhud_key1_frame              \"0\"\nhud_key1_place              \"top\"\nhud_key1_pos_x              \"0\"\nhud_key1_pos_y              \"64\"\nhud_key1_scale              \"1\"\nhud_key1_show               \"0\"\nhud_key1_style              \"0\"\nhud_key2_align_x            \"left\"\nhud_key2_align_y            \"after\"\nhud_key2_frame              \"0\"\nhud_key2_place              \"key1\"\nhud_key2_pos_x              \"0\"\nhud_key2_pos_y              \"0\"\nhud_key2_scale              \"1\"\nhud_key2_show               \"0\"\nhud_key2_style              \"0\"\nhud_mp3_time_align_x        \"left\"\nhud_mp3_time_align_y        \"bottom\"\nhud_mp3_time_frame          \"0\"\nhud_mp3_time_on_scoreboard  \"0\"\nhud_mp3_time_place          \"top\"\nhud_mp3_time_pos_x          \"0\"\nhud_mp3_time_pos_y          \"0\"\nhud_mp3_time_show           \"0\"\nhud_mp3_time_style          \"0\"\nhud_mp3_title_align_x       \"right\"\nhud_mp3_title_align_y       \"bottom\"\nhud_mp3_title_frame         \"0\"\nhud_mp3_title_height        \"8\"\nhud_mp3_title_on_scoreboard \"0\"\nhud_mp3_title_place         \"top\"\nhud_mp3_title_pos_x         \"0\"\nhud_mp3_title_pos_y         \"-10\"\nhud_mp3_title_scroll        \"1\"\nhud_mp3_title_scroll_delay  \"0.5\"\nhud_mp3_title_show          \"0\"\nhud_mp3_title_style         \"0\"\nhud_mp3_title_width         \"512\"\nhud_mp3_title_wordwrap      \"0\"\nhud_net_align_x             \"left\"\nhud_net_align_y             \"before\"\nhud_net_frame               \"0.25\"\nhud_net_period              \"1\"\nhud_net_place               \"group2\"\nhud_net_pos_x               \"0\"\nhud_net_pos_y               \"-5\"\nhud_net_show                \"0\"\nhud_netgraph_align_x        \"after\"\nhud_netgraph_align_y        \"bottom\"\nhud_netgraph_alpha          \"0.1\"\nhud_netgraph_frame          \"0.25\"\nhud_netgraph_full           \"1\"\nhud_netgraph_height         \"100\"\nhud_netgraph_inframes       \"0\"\nhud_netgraph_lostscale      \"0\"\nhud_netgraph_place          \"group3\"\nhud_netgraph_ploss          \"0\"\nhud_netgraph_pos_x          \"5\"\nhud_netgraph_pos_y          \"0\"\nhud_netgraph_scale          \"160\"\nhud_netgraph_show           \"0\"\nhud_netgraph_swap_x         \"0\"\nhud_netgraph_swap_y         \"0\"\nhud_netgraph_width          \"45\"\nhud_pent_align_x            \"left\"\nhud_pent_align_y            \"center\"\nhud_pent_frame              \"0\"\nhud_pent_place              \"@group5\"\nhud_pent_pos_x              \"20\"\nhud_pent_pos_y              \"0\"\nhud_pent_scale              \"1\"\nhud_pent_show               \"1\"\nhud_pent_style              \"1\"\nhud_ping_align_x            \"left\"\nhud_ping_align_y            \"center\"\nhud_ping_blink              \"0\"\nhud_ping_frame              \"0\"\nhud_ping_period             \"1\"\nhud_ping_place              \"@group3\"\nhud_ping_pos_x              \"5\"\nhud_ping_pos_y              \"0\"\nhud_ping_show               \"1\"\nhud_ping_show_dev           \"0\"\nhud_ping_show_max           \"0\"\nhud_ping_show_min           \"0\"\nhud_ping_show_pl            \"1\"\nhud_quad_align_x            \"left\"\nhud_quad_align_y            \"center\"\nhud_quad_frame              \"0\"\nhud_quad_place              \"@group5\"\nhud_quad_pos_x              \"5\"\nhud_quad_pos_y              \"0\"\nhud_quad_scale              \"1\"\nhud_quad_show               \"1\"\nhud_quad_style              \"1\"\nhud_ring_align_x            \"right\"\nhud_ring_align_y            \"center\"\nhud_ring_frame              \"0\"\nhud_ring_place              \"@group5\"\nhud_ring_pos_x              \"-20\"\nhud_ring_pos_y              \"0\"\nhud_ring_scale              \"1\"\nhud_ring_show               \"1\"\nhud_ring_style              \"1\"\nhud_sigil1_align_x          \"left\"\nhud_sigil1_align_y          \"top\"\nhud_sigil1_frame            \"0\"\nhud_sigil1_place            \"screen\"\nhud_sigil1_pos_x            \"0\"\nhud_sigil1_pos_y            \"0\"\nhud_sigil1_scale            \"1\"\nhud_sigil1_show             \"0\"\nhud_sigil1_style            \"0\"\nhud_sigil2_align_x          \"after\"\nhud_sigil2_align_y          \"top\"\nhud_sigil2_frame            \"0\"\nhud_sigil2_place            \"sigil1\"\nhud_sigil2_pos_x            \"0\"\nhud_sigil2_pos_y            \"0\"\nhud_sigil2_scale            \"1\"\nhud_sigil2_show             \"0\"\nhud_sigil2_style            \"0\"\nhud_sigil3_align_x          \"after\"\nhud_sigil3_align_y          \"top\"\nhud_sigil3_frame            \"0\"\nhud_sigil3_place            \"sigil2\"\nhud_sigil3_pos_x            \"0\"\nhud_sigil3_pos_y            \"0\"\nhud_sigil3_scale            \"1\"\nhud_sigil3_show             \"0\"\nhud_sigil3_style            \"0\"\nhud_sigil4_align_x          \"after\"\nhud_sigil4_align_y          \"top\"\nhud_sigil4_frame            \"0\"\nhud_sigil4_place            \"sigil3\"\nhud_sigil4_pos_x            \"0\"\nhud_sigil4_pos_y            \"0\"\nhud_sigil4_scale            \"1\"\nhud_sigil4_show             \"0\"\nhud_sigil4_style            \"0\"\nhud_speed_align_x           \"center\"\nhud_speed_align_y           \"before\"\nhud_speed_frame             \"0.25\"\nhud_speed_place             \"group3\"\nhud_speed_pos_x             \"0\"\nhud_speed_pos_y             \"-5\"\nhud_speed_show              \"0\"\nhud_speed_xyz               \"0\"\nhud_suit_align_x            \"right\"\nhud_suit_align_y            \"center\"\nhud_suit_frame              \"0\"\nhud_suit_place              \"@group5\"\nhud_suit_pos_x              \"-5\"\nhud_suit_pos_y              \"0\"\nhud_suit_scale              \"1\"\nhud_suit_show               \"1\"\nhud_suit_style              \"1\"\nhud_teamfrags_align_x       \"left\"\nhud_teamfrags_align_y       \"bottom\"\nhud_teamfrags_cell_height   \"8\"\nhud_teamfrags_cell_width    \"32\"\nhud_teamfrags_cols          \"2\"\nhud_teamfrags_fliptext      \"1\"\nhud_teamfrags_frame         \"0\"\nhud_teamfrags_padtext       \"1\"\nhud_teamfrags_place         \"screen\"\nhud_teamfrags_pos_x         \"320\"\nhud_teamfrags_pos_y         \"-40\"\nhud_teamfrags_rows          \"1\"\nhud_teamfrags_show          \"1\"\nhud_teamfrags_shownames     \"0\"\nhud_teamfrags_space_x       \"1\"\nhud_teamfrags_space_y       \"1\"\nhud_teamfrags_strip         \"1\"\nhud_teamfrags_style         \"0\"\nhud_teamfrags_vertical      \"0\"\nhud_tracking_align_x        \"left\"\nhud_tracking_align_y        \"bottom\"\nhud_tracking_format         \"Tracking %t %n, [JUMP] for next\"\nhud_tracking_frame          \"0\"\nhud_tracking_place          \"top\"\nhud_tracking_pos_x          \"0\"\nhud_tracking_pos_y          \"0\"\nhud_tracking_show           \"0\"\nhud_recalculate\n"
  },
  {
    "path": "misc/cfg/hud_corner.cfg",
    "content": "echo ezQuake Cornered Head Up Configuration\necho Use with conwidth 512\n\n//\n// Head Up Display Configuration Dump\n//\n\nscr_newhud                  \"1\"\nhud_ammo1_align             \"right\"\nhud_ammo1_align_x           \"left\"\nhud_ammo1_align_y           \"top\"\nhud_ammo1_digits            \"3\"\nhud_ammo1_frame             \"0\"\nhud_ammo1_place             \"iammo1\"\nhud_ammo1_pos_x             \"28\"\nhud_ammo1_pos_y             \"0\"\nhud_ammo1_scale             \"1\"\nhud_ammo1_show              \"1\"\nhud_ammo1_style             \"0\"\nhud_ammo2_align             \"right\"\nhud_ammo2_align_x           \"left\"\nhud_ammo2_align_y           \"top\"\nhud_ammo2_digits            \"3\"\nhud_ammo2_frame             \"0\"\nhud_ammo2_place             \"iammo2\"\nhud_ammo2_pos_x             \"28\"\nhud_ammo2_pos_y             \"0\"\nhud_ammo2_scale             \"1\"\nhud_ammo2_show              \"1\"\nhud_ammo2_style             \"0\"\nhud_ammo3_align             \"right\"\nhud_ammo3_align_x           \"left\"\nhud_ammo3_align_y           \"top\"\nhud_ammo3_digits            \"3\"\nhud_ammo3_frame             \"0\"\nhud_ammo3_place             \"iammo3\"\nhud_ammo3_pos_x             \"28\"\nhud_ammo3_pos_y             \"0\"\nhud_ammo3_scale             \"1\"\nhud_ammo3_show              \"1\"\nhud_ammo3_style             \"0\"\nhud_ammo4_align             \"right\"\nhud_ammo4_align_x           \"left\"\nhud_ammo4_align_y           \"top\"\nhud_ammo4_digits            \"3\"\nhud_ammo4_frame             \"0\"\nhud_ammo4_place             \"iammo4\"\nhud_ammo4_pos_x             \"28\"\nhud_ammo4_pos_y             \"0\"\nhud_ammo4_scale             \"1\"\nhud_ammo4_show              \"1\"\nhud_ammo4_style             \"0\"\nhud_ammo_align              \"right\"\nhud_ammo_align_x            \"left\"\nhud_ammo_align_y            \"top\"\nhud_ammo_digits             \"3\"\nhud_ammo_frame              \"0\"\nhud_ammo_place              \"screen\"\nhud_ammo_pos_x              \"0\"\nhud_ammo_pos_y              \"0\"\nhud_ammo_scale              \"1\"\nhud_ammo_show               \"0\"\nhud_ammo_style              \"0\"\nhud_armor_align             \"right\"\nhud_armor_align_x           \"left\"\nhud_armor_align_y           \"top\"\nhud_armor_digits            \"3\"\nhud_armor_frame             \"0\"\nhud_armor_place             \"iarmor\"\nhud_armor_pos_x             \"27\"\nhud_armor_pos_y             \"0\"\nhud_armor_scale             \"1\"\nhud_armor_show              \"1\"\nhud_armor_style             \"0\"\nhud_armordamage_align       \"right\"\nhud_armordamage_align_x     \"left\"\nhud_armordamage_align_y     \"before\"\nhud_armordamage_digits      \"3\"\nhud_armordamage_duration    \"0.8\"\nhud_armordamage_frame       \"0\"\nhud_armordamage_place       \"armor\"\nhud_armordamage_pos_x       \"0\"\nhud_armordamage_pos_y       \"0\"\nhud_armordamage_scale       \"1\"\nhud_armordamage_show        \"0\"\nhud_armordamage_style       \"0\"\nhud_clock_align_x           \"right\"\nhud_clock_align_y           \"before\"\nhud_clock_big               \"0\"\nhud_clock_blink             \"0\"\nhud_clock_frame             \"0.5\"\nhud_clock_place             \"group2\"\nhud_clock_pos_x             \"-2\"\nhud_clock_pos_y             \"-2\"\nhud_clock_scale             \"1\"\nhud_clock_show              \"0\"\nhud_clock_style             \"0\"\nhud_face_align_x            \"left\"\nhud_face_align_y            \"bottom\"\nhud_face_frame              \"0\"\nhud_face_place              \"group1\"\nhud_face_pos_x              \"112\"\nhud_face_pos_y              \"-4\"\nhud_face_scale              \"1\"\nhud_face_show               \"1\"\nhud_fps_align_x             \"right\"\nhud_fps_align_y             \"center\"\nhud_fps_decimals            \"0\"\nhud_fps_frame               \"0\"\nhud_fps_place               \"group4\"\nhud_fps_pos_x               \"-8\"\nhud_fps_pos_y               \"0\"\nhud_fps_show                \"1\"\nhud_fps_show_min            \"0\"\nhud_fps_title               \"1\"\nhud_frags_align_x           \"right\"\nhud_frags_align_y           \"before\"\nhud_frags_cell_height       \"8\"\nhud_frags_cell_width        \"32\"\nhud_frags_cols              \"4\"\nhud_frags_fliptext          \"0\"\nhud_frags_frame             \"0.5\"\nhud_frags_padtext           \"1\"\nhud_frags_place             \"group3\"\nhud_frags_pos_x             \"0\"\nhud_frags_pos_y             \"-2\"\nhud_frags_rows              \"2\"\nhud_frags_show              \"1\"\nhud_frags_shownames         \"0\"\nhud_frags_showself_always   \"1\"\nhud_frags_showteams         \"0\"\nhud_frags_space_x           \"1\"\nhud_frags_space_y           \"1\"\nhud_frags_strip             \"1\"\nhud_frags_style             \"0\"\nhud_frags_teamsort          \"0\"\nhud_frags_vertical          \"0\"\nhud_gameclock_align_x       \"left\"\nhud_gameclock_align_y       \"before\"\nhud_gameclock_big           \"1\"\nhud_gameclock_blink         \"1\"\nhud_gameclock_countdown     \"0\"\nhud_gameclock_frame         \"0.5\"\nhud_gameclock_place         \"group3\"\nhud_gameclock_pos_x         \"0\"\nhud_gameclock_pos_y         \"0\"\nhud_gameclock_scale         \"0.5\"\nhud_gameclock_show          \"1\"\nhud_gameclock_style         \"0\"\nhud_group1_align_x          \"left\"\nhud_group1_align_y          \"bottom\"\nhud_group1_alpha            \"1\"\nhud_group1_frame            \"0.5\"\nhud_group1_height           \"32\"\nhud_group1_name             \"group1\"\nhud_group1_picture          \"\"\nhud_group1_place            \"screen\"\nhud_group1_pos_x            \"0\"\nhud_group1_pos_y            \"0\"\nhud_group1_show             \"1\"\nhud_group1_tile             \"0\"\nhud_group1_width            \"216\"\nhud_group2_align_x          \"right\"\nhud_group2_align_y          \"bottom\"\nhud_group2_alpha            \"1\"\nhud_group2_frame            \".5\"\nhud_group2_height           \"116\"\nhud_group2_name             \"group2\"\nhud_group2_picture          \"\"\nhud_group2_place            \"screen\"\nhud_group2_pos_x            \"0\"\nhud_group2_pos_y            \"0\"\nhud_group2_show             \"1\"\nhud_group2_tile             \"0\"\nhud_group2_width            \"110\"\nhud_group3_align_x          \"center\"\nhud_group3_align_y          \"before\"\nhud_group3_alpha            \"1\"\nhud_group3_frame            \".5\"\nhud_group3_height           \"23\"\nhud_group3_name             \"group3\"\nhud_group3_picture          \"\"\nhud_group3_place            \"group1\"\nhud_group3_pos_x            \"0\"\nhud_group3_pos_y            \"-2\"\nhud_group3_show             \"1\"\nhud_group3_tile             \"0\"\nhud_group3_width            \"208\"\nhud_group4_align_x          \"center\"\nhud_group4_align_y          \"bottom\"\nhud_group4_alpha            \"1\"\nhud_group4_frame            \".5\"\nhud_group4_height           \"16\"\nhud_group4_name             \"group4\"\nhud_group4_picture          \"\"\nhud_group4_place            \"screen\"\nhud_group4_pos_x            \"53\"\nhud_group4_pos_y            \"-3\"\nhud_group4_show             \"1\"\nhud_group4_tile             \"0\"\nhud_group4_width            \"178\"\nhud_group5_align_x          \"left\"\nhud_group5_align_y          \"top\"\nhud_group5_alpha            \"1\"\nhud_group5_frame            \".5\"\nhud_group5_height           \"64\"\nhud_group5_name             \"group5\"\nhud_group5_picture          \"\"\nhud_group5_place            \"screen\"\nhud_group5_pos_x            \"0\"\nhud_group5_pos_y            \"0\"\nhud_group5_show             \"0\"\nhud_group5_tile             \"0\"\nhud_group5_width            \"64\"\nhud_group6_align_x          \"left\"\nhud_group6_align_y          \"top\"\nhud_group6_alpha            \"1\"\nhud_group6_frame            \".5\"\nhud_group6_height           \"64\"\nhud_group6_name             \"group6\"\nhud_group6_picture          \"\"\nhud_group6_place            \"screen\"\nhud_group6_pos_x            \"0\"\nhud_group6_pos_y            \"0\"\nhud_group6_show             \"0\"\nhud_group6_tile             \"0\"\nhud_group6_width            \"64\"\nhud_group7_align_x          \"left\"\nhud_group7_align_y          \"top\"\nhud_group7_alpha            \"1\"\nhud_group7_frame            \".5\"\nhud_group7_height           \"64\"\nhud_group7_name             \"group7\"\nhud_group7_picture          \"\"\nhud_group7_place            \"screen\"\nhud_group7_pos_x            \"0\"\nhud_group7_pos_y            \"0\"\nhud_group7_show             \"0\"\nhud_group7_tile             \"0\"\nhud_group7_width            \"64\"\nhud_group8_align_x          \"left\"\nhud_group8_align_y          \"top\"\nhud_group8_alpha            \"1\"\nhud_group8_frame            \".5\"\nhud_group8_height           \"64\"\nhud_group8_name             \"group8\"\nhud_group8_picture          \"\"\nhud_group8_place            \"screen\"\nhud_group8_pos_x            \"0\"\nhud_group8_pos_y            \"0\"\nhud_group8_show             \"0\"\nhud_group8_tile             \"0\"\nhud_group8_width            \"64\"\nhud_group9_align_x          \"left\"\nhud_group9_align_y          \"top\"\nhud_group9_alpha            \"1\"\nhud_group9_frame            \".5\"\nhud_group9_height           \"64\"\nhud_group9_name             \"group9\"\nhud_group9_picture          \"\"\nhud_group9_place            \"screen\"\nhud_group9_pos_x            \"0\"\nhud_group9_pos_y            \"0\"\nhud_group9_show             \"0\"\nhud_group9_tile             \"0\"\nhud_group9_width            \"64\"\nhud_gun2_align_x            \"after\"\nhud_gun2_align_y            \"top\"\nhud_gun2_frame              \"0\"\nhud_gun2_place              \"quad\"\nhud_gun2_pos_x              \"4\"\nhud_gun2_pos_y              \"0\"\nhud_gun2_scale              \"1\"\nhud_gun2_show               \"0\"\nhud_gun2_style              \"0\"\nhud_gun3_align_x            \"after\"\nhud_gun3_align_y            \"center\"\nhud_gun3_frame              \"0\"\nhud_gun3_place              \"quad\"\nhud_gun3_pos_x              \"2\"\nhud_gun3_pos_y              \"0\"\nhud_gun3_scale              \"1\"\nhud_gun3_show               \"1\"\nhud_gun3_style              \"0\"\nhud_gun4_align_x            \"after\"\nhud_gun4_align_y            \"center\"\nhud_gun4_frame              \"0\"\nhud_gun4_place              \"gun3\"\nhud_gun4_pos_x              \"0\"\nhud_gun4_pos_y              \"0\"\nhud_gun4_scale              \"1\"\nhud_gun4_show               \"0\"\nhud_gun4_style              \"0\"\nhud_gun5_align_x            \"after\"\nhud_gun5_align_y            \"center\"\nhud_gun5_frame              \"0\"\nhud_gun5_place              \"gun3\"\nhud_gun5_pos_x              \"-1\"\nhud_gun5_pos_y              \"0\"\nhud_gun5_scale              \"1\"\nhud_gun5_show               \"1\"\nhud_gun5_style              \"0\"\nhud_gun6_align_x            \"after\"\nhud_gun6_align_y            \"center\"\nhud_gun6_frame              \"0\"\nhud_gun6_place              \"gun5\"\nhud_gun6_pos_x              \"-1\"\nhud_gun6_pos_y              \"0\"\nhud_gun6_scale              \"1\"\nhud_gun6_show               \"1\"\nhud_gun6_style              \"0\"\nhud_gun7_align_x            \"after\"\nhud_gun7_align_y            \"center\"\nhud_gun7_frame              \"0\"\nhud_gun7_place              \"gun6\"\nhud_gun7_pos_x              \"-1\"\nhud_gun7_pos_y              \"0\"\nhud_gun7_scale              \"1\"\nhud_gun7_show               \"1\"\nhud_gun7_style              \"0\"\nhud_gun8_align_x            \"after\"\nhud_gun8_align_y            \"center\"\nhud_gun8_frame              \"0\"\nhud_gun8_place              \"gun7\"\nhud_gun8_pos_x              \"-1\"\nhud_gun8_pos_y              \"0\"\nhud_gun8_scale              \"1\"\nhud_gun8_show               \"1\"\nhud_gun8_style              \"0\"\nhud_gun8_wide               \"0\"\nhud_gun_align_x             \"center\"\nhud_gun_align_y             \"bottom\"\nhud_gun_frame               \"0\"\nhud_gun_place               \"top\"\nhud_gun_pos_x               \"0\"\nhud_gun_pos_y               \"0\"\nhud_gun_scale               \"1\"\nhud_gun_show                \"0\"\nhud_gun_style               \"0\"\nhud_gun_wide                \"0\"\nhud_health_align            \"right\"\nhud_health_align_x          \"left\"\nhud_health_align_y          \"top\"\nhud_health_digits           \"3\"\nhud_health_frame            \"0\"\nhud_health_place            \"face\"\nhud_health_pos_x            \"26\"\nhud_health_pos_y            \"0\"\nhud_health_scale            \"1\"\nhud_health_show             \"1\"\nhud_health_style            \"0\"\nhud_healthdamage_align      \"right\"\nhud_healthdamage_align_x    \"left\"\nhud_healthdamage_align_y    \"before\"\nhud_healthdamage_digits     \"3\"\nhud_healthdamage_duration   \"0.8\"\nhud_healthdamage_frame      \"0\"\nhud_healthdamage_place      \"health\"\nhud_healthdamage_pos_x      \"0\"\nhud_healthdamage_pos_y      \"0\"\nhud_healthdamage_scale      \"1\"\nhud_healthdamage_show       \"0\"\nhud_healthdamage_style      \"0\"\nhud_iammo1_align_x          \"left\"\nhud_iammo1_align_y          \"top\"\nhud_iammo1_frame            \"0\"\nhud_iammo1_place            \"group2\"\nhud_iammo1_pos_x            \"4\"\nhud_iammo1_pos_y            \"4\"\nhud_iammo1_scale            \"1\"\nhud_iammo1_show             \"1\"\nhud_iammo1_style            \"0\"\nhud_iammo2_align_x          \"left\"\nhud_iammo2_align_y          \"top\"\nhud_iammo2_frame            \"0\"\nhud_iammo2_place            \"iammo1\"\nhud_iammo2_pos_x            \"0\"\nhud_iammo2_pos_y            \"28\"\nhud_iammo2_scale            \"1\"\nhud_iammo2_show             \"1\"\nhud_iammo2_style            \"0\"\nhud_iammo3_align_x          \"left\"\nhud_iammo3_align_y          \"top\"\nhud_iammo3_frame            \"0\"\nhud_iammo3_place            \"iammo2\"\nhud_iammo3_pos_x            \"0\"\nhud_iammo3_pos_y            \"28\"\nhud_iammo3_scale            \"1\"\nhud_iammo3_show             \"1\"\nhud_iammo3_style            \"0\"\nhud_iammo4_align_x          \"left\"\nhud_iammo4_align_y          \"top\"\nhud_iammo4_frame            \"0\"\nhud_iammo4_place            \"iammo3\"\nhud_iammo4_pos_x            \"0\"\nhud_iammo4_pos_y            \"28\"\nhud_iammo4_scale            \"1\"\nhud_iammo4_show             \"1\"\nhud_iammo4_style            \"0\"\nhud_iammo_align_x           \"left\"\nhud_iammo_align_y           \"top\"\nhud_iammo_frame             \"0\"\nhud_iammo_place             \"screen\"\nhud_iammo_pos_x             \"0\"\nhud_iammo_pos_y             \"0\"\nhud_iammo_scale             \"1\"\nhud_iammo_show              \"0\"\nhud_iammo_style             \"0\"\nhud_iarmor_align_x          \"left\"\nhud_iarmor_align_y          \"top\"\nhud_iarmor_frame            \"0\"\nhud_iarmor_place            \"group1\"\nhud_iarmor_pos_x            \"4\"\nhud_iarmor_pos_y            \"4\"\nhud_iarmor_scale            \"1\"\nhud_iarmor_show             \"1\"\nhud_iarmor_style            \"0\"\nhud_key1_align_x            \"top\"\nhud_key1_align_y            \"left\"\nhud_key1_frame              \"0\"\nhud_key1_place              \"group3\"\nhud_key1_pos_x              \"4\"\nhud_key1_pos_y              \"4\"\nhud_key1_scale              \"1\"\nhud_key1_show               \"1\"\nhud_key1_style              \"0\"\nhud_key2_align_x            \"after\"\nhud_key2_align_y            \"top\"\nhud_key2_frame              \"0\"\nhud_key2_place              \"key1\"\nhud_key2_pos_x              \"0\"\nhud_key2_pos_y              \"0\"\nhud_key2_scale              \"1\"\nhud_key2_show               \"1\"\nhud_key2_style              \"0\"\nhud_mp3_time_align_x        \"left\"\nhud_mp3_time_align_y        \"bottom\"\nhud_mp3_time_frame          \"0\"\nhud_mp3_time_on_scoreboard  \"0\"\nhud_mp3_time_place          \"top\"\nhud_mp3_time_pos_x          \"0\"\nhud_mp3_time_pos_y          \"0\"\nhud_mp3_time_show           \"0\"\nhud_mp3_time_style          \"0\"\nhud_mp3_title_align_x       \"center\"\nhud_mp3_title_align_y       \"bottom\"\nhud_mp3_title_frame         \"0\"\nhud_mp3_title_height        \"8\"\nhud_mp3_title_on_scoreboard \"0\"\nhud_mp3_title_place         \"screen\"\nhud_mp3_title_pos_x         \"0\"\nhud_mp3_title_pos_y         \"0\"\nhud_mp3_title_scroll        \"0\"\nhud_mp3_title_scroll_delay  \"0.2\"\nhud_mp3_title_show          \"0\"\nhud_mp3_title_style         \"0\"\nhud_mp3_title_width         \"256\"\nhud_mp3_title_wordwrap      \"0\"\nhud_net_align_x             \"left\"\nhud_net_align_y             \"center\"\nhud_net_frame               \"0.2\"\nhud_net_period              \"1\"\nhud_net_place               \"top\"\nhud_net_pos_x               \"10\"\nhud_net_pos_y               \"0\"\nhud_net_show                \"0\"\nhud_netgraph_align_x        \"center\"\nhud_netgraph_align_y        \"before\"\nhud_netgraph_alpha          \"1\"\nhud_netgraph_frame          \"0.5\"\nhud_netgraph_full           \"1\"\nhud_netgraph_height         \"28\"\nhud_netgraph_inframes       \"0\"\nhud_netgraph_lostscale      \"1\"\nhud_netgraph_place          \"group4\"\nhud_netgraph_ploss          \"0\"\nhud_netgraph_pos_x          \"0\"\nhud_netgraph_pos_y          \"-2\"\nhud_netgraph_scale          \"256\"\nhud_netgraph_show           \"0\"\nhud_netgraph_swap_x         \"0\"\nhud_netgraph_swap_y         \"0\"\nhud_netgraph_width          \"170\"\nhud_pent_align_x            \"after\"\nhud_pent_align_y            \"top\"\nhud_pent_frame              \"0\"\nhud_pent_place              \"ring\"\nhud_pent_pos_x              \"0\"\nhud_pent_pos_y              \"0\"\nhud_pent_scale              \"1\"\nhud_pent_show               \"1\"\nhud_pent_style              \"0\"\nhud_ping_align_x            \"left\"\nhud_ping_align_y            \"center\"\nhud_ping_blink              \"0\"\nhud_ping_frame              \"0\"\nhud_ping_period             \"1\"\nhud_ping_place              \"group4\"\nhud_ping_pos_x              \"4\"\nhud_ping_pos_y              \"0\"\nhud_ping_show               \"1\"\nhud_ping_show_dev           \"0\"\nhud_ping_show_max           \"0\"\nhud_ping_show_min           \"0\"\nhud_ping_show_pl            \"1\"\nhud_quad_align_x            \"after\"\nhud_quad_align_y            \"top\"\nhud_quad_frame              \"0\"\nhud_quad_place              \"pent\"\nhud_quad_pos_x              \"0\"\nhud_quad_pos_y              \"0\"\nhud_quad_scale              \"1\"\nhud_quad_show               \"1\"\nhud_quad_style              \"0\"\nhud_ring_align_x            \"after\"\nhud_ring_align_y            \"top\"\nhud_ring_frame              \"0\"\nhud_ring_place              \"key2\"\nhud_ring_pos_x              \"2\"\nhud_ring_pos_y              \"0\"\nhud_ring_scale              \"1\"\nhud_ring_show               \"1\"\nhud_ring_style              \"0\"\nhud_sigil1_align_x          \"left\"\nhud_sigil1_align_y          \"top\"\nhud_sigil1_frame            \"0\"\nhud_sigil1_place            \"screen\"\nhud_sigil1_pos_x            \"0\"\nhud_sigil1_pos_y            \"0\"\nhud_sigil1_scale            \"1\"\nhud_sigil1_show             \"0\"\nhud_sigil1_style            \"0\"\nhud_sigil2_align_x          \"after\"\nhud_sigil2_align_y          \"top\"\nhud_sigil2_frame            \"0\"\nhud_sigil2_place            \"sigil1\"\nhud_sigil2_pos_x            \"0\"\nhud_sigil2_pos_y            \"0\"\nhud_sigil2_scale            \"1\"\nhud_sigil2_show             \"0\"\nhud_sigil2_style            \"0\"\nhud_sigil3_align_x          \"after\"\nhud_sigil3_align_y          \"top\"\nhud_sigil3_frame            \"0\"\nhud_sigil3_place            \"sigil2\"\nhud_sigil3_pos_x            \"0\"\nhud_sigil3_pos_y            \"0\"\nhud_sigil3_scale            \"1\"\nhud_sigil3_show             \"0\"\nhud_sigil3_style            \"0\"\nhud_sigil4_align_x          \"after\"\nhud_sigil4_align_y          \"top\"\nhud_sigil4_frame            \"0\"\nhud_sigil4_place            \"sigil3\"\nhud_sigil4_pos_x            \"0\"\nhud_sigil4_pos_y            \"0\"\nhud_sigil4_scale            \"1\"\nhud_sigil4_show             \"0\"\nhud_sigil4_style            \"0\"\nhud_speed_align_x           \"center\"\nhud_speed_align_y           \"after\"\nhud_speed_frame             \"0.5\"\nhud_speed_place             \"group4\"\nhud_speed_pos_x             \"0\"\nhud_speed_pos_y             \"-79\"\nhud_speed_show              \"0\"\nhud_speed_xyz               \"1\"\nhud_suit_align_x            \"after\"\nhud_suit_align_y            \"top\"\nhud_suit_frame              \"0\"\nhud_suit_place              \"pent\"\nhud_suit_pos_x              \"0\"\nhud_suit_pos_y              \"0\"\nhud_suit_scale              \"1\"\nhud_suit_show               \"0\"\nhud_suit_style              \"0\"\nhud_teamfrags_align_x       \"after\"\nhud_teamfrags_align_y       \"center\"\nhud_teamfrags_cell_height   \"8\"\nhud_teamfrags_cell_width    \"44\"\nhud_teamfrags_cols          \"1\"\nhud_teamfrags_fliptext      \"1\"\nhud_teamfrags_frame         \"0\"\nhud_teamfrags_padtext       \"1\"\nhud_teamfrags_place         \"group2\"\nhud_teamfrags_pos_x         \"4\"\nhud_teamfrags_pos_y         \"0\"\nhud_teamfrags_rows          \"2\"\nhud_teamfrags_show          \"1\"\nhud_teamfrags_shownames     \"1\"\nhud_teamfrags_space_x       \"0\"\nhud_teamfrags_space_y       \"1\"\nhud_teamfrags_strip         \"1\"\nhud_teamfrags_style         \"0\"\nhud_teamfrags_vertical      \"1\"\nhud_tracking_align_x        \"center\"\nhud_tracking_align_y        \"before\"\nhud_tracking_format         \"pov: %t %n\"\nhud_tracking_frame          \"0\"\nhud_tracking_place          \"frags\"\nhud_tracking_pos_x          \"0\"\nhud_tracking_pos_y          \"0\"\nhud_tracking_show           \"1\"\nhud_recalculate\n"
  },
  {
    "path": "misc/cfg/hud_dobbz.cfg",
    "content": "echo Head Up Configuration by Dobbz\necho Use with conwidth 400 and higher\n\n//\n// Head Up Display Configuration Dump\n//\n\nscr_newhud                  \"1\"\nhud_ammo1_align             \"right\"\nhud_ammo1_align_x           \"before\"\nhud_ammo1_align_y           \"center\"\nhud_ammo1_digits            \"3\"\nhud_ammo1_frame             \"0\"\nhud_ammo1_place             \"iammo1\"\nhud_ammo1_pos_x             \"-2\"\nhud_ammo1_pos_y             \"0\"\nhud_ammo1_scale             \".65\"\nhud_ammo1_show              \"1\"\nhud_ammo1_style             \"0\"\nhud_ammo2_align             \"right\"\nhud_ammo2_align_x           \"before\"\nhud_ammo2_align_y           \"center\"\nhud_ammo2_digits            \"3\"\nhud_ammo2_frame             \"0\"\nhud_ammo2_place             \"iammo2\"\nhud_ammo2_pos_x             \"-2\"\nhud_ammo2_pos_y             \"0\"\nhud_ammo2_scale             \".65\"\nhud_ammo2_show              \"1\"\nhud_ammo2_style             \"0\"\nhud_ammo3_align             \"right\"\nhud_ammo3_align_x           \"before\"\nhud_ammo3_align_y           \"center\"\nhud_ammo3_digits            \"3\"\nhud_ammo3_frame             \"0\"\nhud_ammo3_place             \"iammo3\"\nhud_ammo3_pos_x             \"-2\"\nhud_ammo3_pos_y             \"0\"\nhud_ammo3_scale             \".65\"\nhud_ammo3_show              \"1\"\nhud_ammo3_style             \"0\"\nhud_ammo4_align             \"right\"\nhud_ammo4_align_x           \"before\"\nhud_ammo4_align_y           \"center\"\nhud_ammo4_digits            \"3\"\nhud_ammo4_frame             \"0\"\nhud_ammo4_place             \"iammo4\"\nhud_ammo4_pos_x             \"-2\"\nhud_ammo4_pos_y             \"0\"\nhud_ammo4_scale             \".65\"\nhud_ammo4_show              \"1\"\nhud_ammo4_style             \"0\"\nhud_ammo_align              \"right\"\nhud_ammo_align_x            \"left\"\nhud_ammo_align_y            \"top\"\nhud_ammo_digits             \"3\"\nhud_ammo_frame              \"0\"\nhud_ammo_place              \"screen\"\nhud_ammo_pos_x              \"0\"\nhud_ammo_pos_y              \"0\"\nhud_ammo_scale              \"1\"\nhud_ammo_show               \"0\"\nhud_ammo_style              \"0\"\nhud_armor_align             \"left\"\nhud_armor_align_x           \"after\"\nhud_armor_align_y           \"center\"\nhud_armor_digits            \"3\"\nhud_armor_frame             \"0\"\nhud_armor_place             \"iarmor\"\nhud_armor_pos_x             \"3\"\nhud_armor_pos_y             \"0\"\nhud_armor_scale             \"1.25\"\nhud_armor_show              \"1\"\nhud_armor_style             \"0\"\nhud_armordamage_align       \"right\"\nhud_armordamage_align_x     \"left\"\nhud_armordamage_align_y     \"before\"\nhud_armordamage_digits      \"3\"\nhud_armordamage_duration    \"0.8\"\nhud_armordamage_frame       \"0\"\nhud_armordamage_place       \"armor\"\nhud_armordamage_pos_x       \"0\"\nhud_armordamage_pos_y       \"0\"\nhud_armordamage_scale       \"1\"\nhud_armordamage_show        \"0\"\nhud_armordamage_style       \"0\"\nhud_clock_align_x           \"right\"\nhud_clock_align_y           \"console\"\nhud_clock_big               \"1\"\nhud_clock_blink             \"1\"\nhud_clock_frame             \"0\"\nhud_clock_place             \"top\"\nhud_clock_pos_x             \"0\"\nhud_clock_pos_y             \"0\"\nhud_clock_scale             \"1\"\nhud_clock_show              \"0\"\nhud_clock_style             \"0\"\nhud_face_align_x            \"left\"\nhud_face_align_y            \"bottom\"\nhud_face_frame              \"0\"\nhud_face_place              \"screen\"\nhud_face_pos_x              \"2\"\nhud_face_pos_y              \"-2\"\nhud_face_scale              \"1.25\"\nhud_face_show               \"1\"\nhud_fps_align_x             \"right\"\nhud_fps_align_y             \"bottom\"\nhud_fps_decimals            \"0\"\nhud_fps_frame               \"0\"\nhud_fps_place               \"top\"\nhud_fps_pos_x               \"0\"\nhud_fps_pos_y               \"0\"\nhud_fps_show                \"0\"\nhud_fps_show_min            \"0\"\nhud_fps_title               \"1\"\nhud_frags_align_x           \"left\"\nhud_frags_align_y           \"top\"\nhud_frags_cell_height       \"8\"\nhud_frags_cell_width        \"32\"\nhud_frags_cols              \"4\"\nhud_frags_fliptext          \"0\"\nhud_frags_frame             \"0\"\nhud_frags_padtext           \"1\"\nhud_frags_place             \"screen\"\nhud_frags_pos_x             \"0\"\nhud_frags_pos_y             \"0\"\nhud_frags_rows              \"1\"\nhud_frags_show              \"0\"\nhud_frags_shownames         \"0\"\nhud_frags_showself_always   \"1\"\nhud_frags_showteams         \"0\"\nhud_frags_space_x           \"1\"\nhud_frags_space_y           \"1\"\nhud_frags_strip             \"1\"\nhud_frags_style             \"0\"\nhud_frags_teamsort          \"0\"\nhud_frags_vertical          \"0\"\nhud_gameclock_align_x       \"right\"\nhud_gameclock_align_y       \"before\"\nhud_gameclock_big           \"1\"\nhud_gameclock_blink         \"1\"\nhud_gameclock_countdown     \"0\"\nhud_gameclock_frame         \"0\"\nhud_gameclock_place         \"iammo1\"\nhud_gameclock_pos_x         \"0\"\nhud_gameclock_pos_y         \"0\"\nhud_gameclock_scale         \"0.65\"\nhud_gameclock_show          \"1\"\nhud_gameclock_style         \"0\"\nhud_group1_align_x          \"left\"\nhud_group1_align_y          \"bottom\"\nhud_group1_alpha            \".2\"\nhud_group1_frame            \".5\"\nhud_group1_height           \"64\"\nhud_group1_name             \"group1\"\nhud_group1_picture          \"\"\nhud_group1_place            \"screen\"\nhud_group1_pos_x            \"0\"\nhud_group1_pos_y            \"0\"\nhud_group1_show             \"0\"\nhud_group1_tile             \"0\"\nhud_group1_width            \"110\"\nhud_group2_align_x          \"left\"\nhud_group2_align_y          \"top\"\nhud_group2_alpha            \"1\"\nhud_group2_frame            \".5\"\nhud_group2_height           \"64\"\nhud_group2_name             \"group2\"\nhud_group2_picture          \"\"\nhud_group2_place            \"screen\"\nhud_group2_pos_x            \"0\"\nhud_group2_pos_y            \"0\"\nhud_group2_show             \"0\"\nhud_group2_tile             \"0\"\nhud_group2_width            \"64\"\nhud_group3_align_x          \"left\"\nhud_group3_align_y          \"top\"\nhud_group3_alpha            \"1\"\nhud_group3_frame            \".5\"\nhud_group3_height           \"64\"\nhud_group3_name             \"group3\"\nhud_group3_picture          \"\"\nhud_group3_place            \"screen\"\nhud_group3_pos_x            \"0\"\nhud_group3_pos_y            \"0\"\nhud_group3_show             \"0\"\nhud_group3_tile             \"0\"\nhud_group3_width            \"64\"\nhud_group4_align_x          \"left\"\nhud_group4_align_y          \"top\"\nhud_group4_alpha            \"1\"\nhud_group4_frame            \".5\"\nhud_group4_height           \"64\"\nhud_group4_name             \"group4\"\nhud_group4_picture          \"\"\nhud_group4_place            \"screen\"\nhud_group4_pos_x            \"0\"\nhud_group4_pos_y            \"0\"\nhud_group4_show             \"0\"\nhud_group4_tile             \"0\"\nhud_group4_width            \"64\"\nhud_group5_align_x          \"left\"\nhud_group5_align_y          \"top\"\nhud_group5_alpha            \"1\"\nhud_group5_frame            \".5\"\nhud_group5_height           \"64\"\nhud_group5_name             \"group5\"\nhud_group5_picture          \"\"\nhud_group5_place            \"screen\"\nhud_group5_pos_x            \"0\"\nhud_group5_pos_y            \"0\"\nhud_group5_show             \"0\"\nhud_group5_tile             \"0\"\nhud_group5_width            \"64\"\nhud_group6_align_x          \"left\"\nhud_group6_align_y          \"top\"\nhud_group6_alpha            \"1\"\nhud_group6_frame            \".5\"\nhud_group6_height           \"64\"\nhud_group6_name             \"group6\"\nhud_group6_picture          \"\"\nhud_group6_place            \"screen\"\nhud_group6_pos_x            \"0\"\nhud_group6_pos_y            \"0\"\nhud_group6_show             \"0\"\nhud_group6_tile             \"0\"\nhud_group6_width            \"64\"\nhud_group7_align_x          \"left\"\nhud_group7_align_y          \"top\"\nhud_group7_alpha            \"1\"\nhud_group7_frame            \".5\"\nhud_group7_height           \"64\"\nhud_group7_name             \"group7\"\nhud_group7_picture          \"\"\nhud_group7_place            \"screen\"\nhud_group7_pos_x            \"0\"\nhud_group7_pos_y            \"0\"\nhud_group7_show             \"0\"\nhud_group7_tile             \"0\"\nhud_group7_width            \"64\"\nhud_group8_align_x          \"left\"\nhud_group8_align_y          \"top\"\nhud_group8_alpha            \"1\"\nhud_group8_frame            \".5\"\nhud_group8_height           \"64\"\nhud_group8_name             \"group8\"\nhud_group8_picture          \"\"\nhud_group8_place            \"screen\"\nhud_group8_pos_x            \"0\"\nhud_group8_pos_y            \"0\"\nhud_group8_show             \"0\"\nhud_group8_tile             \"0\"\nhud_group8_width            \"64\"\nhud_group9_align_x          \"left\"\nhud_group9_align_y          \"top\"\nhud_group9_alpha            \"1\"\nhud_group9_frame            \".5\"\nhud_group9_height           \"64\"\nhud_group9_name             \"group9\"\nhud_group9_picture          \"\"\nhud_group9_place            \"screen\"\nhud_group9_pos_x            \"0\"\nhud_group9_pos_y            \"0\"\nhud_group9_show             \"0\"\nhud_group9_tile             \"0\"\nhud_group9_width            \"64\"\nhud_gun2_align_x            \"left\"\nhud_gun2_align_y            \"bottom\"\nhud_gun2_frame              \"0\"\nhud_gun2_place              \"top\"\nhud_gun2_pos_x              \"0\"\nhud_gun2_pos_y              \"0\"\nhud_gun2_scale              \"1\"\nhud_gun2_show               \"0\"\nhud_gun2_style              \"0\"\nhud_gun3_align_x            \"center\"\nhud_gun3_align_y            \"bottom\"\nhud_gun3_frame              \"0\"\nhud_gun3_place              \"screen\"\nhud_gun3_pos_x              \"41\"\nhud_gun3_pos_y              \"0\"\nhud_gun3_scale              \"1\"\nhud_gun3_show               \"0\"\nhud_gun3_style              \"0\"\nhud_gun4_align_x            \"after\"\nhud_gun4_align_y            \"center\"\nhud_gun4_frame              \"0\"\nhud_gun4_place              \"gun3\"\nhud_gun4_pos_x              \"-14\"\nhud_gun4_pos_y              \"0\"\nhud_gun4_scale              \"1\"\nhud_gun4_show               \"1\"\nhud_gun4_style              \"0\"\nhud_gun5_align_x            \"after\"\nhud_gun5_align_y            \"center\"\nhud_gun5_frame              \"0\"\nhud_gun5_place              \"gun4\"\nhud_gun5_pos_x              \"-14\"\nhud_gun5_pos_y              \"0\"\nhud_gun5_scale              \"1\"\nhud_gun5_show               \"1\"\nhud_gun5_style              \"0\"\nhud_gun6_align_x            \"after\"\nhud_gun6_align_y            \"center\"\nhud_gun6_frame              \"0\"\nhud_gun6_place              \"gun5\"\nhud_gun6_pos_x              \"-14\"\nhud_gun6_pos_y              \"0\"\nhud_gun6_scale              \"1\"\nhud_gun6_show               \"1\"\nhud_gun6_style              \"0\"\nhud_gun7_align_x            \"after\"\nhud_gun7_align_y            \"center\"\nhud_gun7_frame              \"0\"\nhud_gun7_place              \"gun6\"\nhud_gun7_pos_x              \"-14\"\nhud_gun7_pos_y              \"0\"\nhud_gun7_scale              \"1\"\nhud_gun7_show               \"1\"\nhud_gun7_style              \"0\"\nhud_gun8_align_x            \"after\"\nhud_gun8_align_y            \"center\"\nhud_gun8_frame              \"0\"\nhud_gun8_place              \"gun7\"\nhud_gun8_pos_x              \"-14\"\nhud_gun8_pos_y              \"0\"\nhud_gun8_scale              \"1\"\nhud_gun8_show               \"1\"\nhud_gun8_style              \"0\"\nhud_gun8_wide               \"0\"\nhud_gun_align_x             \"center\"\nhud_gun_align_y             \"bottom\"\nhud_gun_frame               \"0\"\nhud_gun_place               \"top\"\nhud_gun_pos_x               \"0\"\nhud_gun_pos_y               \"0\"\nhud_gun_scale               \"1\"\nhud_gun_show                \"0\"\nhud_gun_style               \"0\"\nhud_gun_wide                \"0\"\nhud_health_align            \"bottom left\"\nhud_health_align_x          \"after\"\nhud_health_align_y          \"center\"\nhud_health_digits           \"3\"\nhud_health_frame            \"0\"\nhud_health_place            \"face\"\nhud_health_pos_x            \"3\"\nhud_health_pos_y            \"0\"\nhud_health_scale            \"1.25\"\nhud_health_show             \"1\"\nhud_health_style            \"0\"\nhud_healthdamage_align      \"right\"\nhud_healthdamage_align_x    \"left\"\nhud_healthdamage_align_y    \"before\"\nhud_healthdamage_digits     \"3\"\nhud_healthdamage_duration   \"0.8\"\nhud_healthdamage_frame      \"0\"\nhud_healthdamage_place      \"health\"\nhud_healthdamage_pos_x      \"0\"\nhud_healthdamage_pos_y      \"0\"\nhud_healthdamage_scale      \"1\"\nhud_healthdamage_show       \"0\"\nhud_healthdamage_style      \"0\"\nhud_iammo1_align_x          \"center\"\nhud_iammo1_align_y          \"before\"\nhud_iammo1_frame            \"0\"\nhud_iammo1_place            \"iammo2\"\nhud_iammo1_pos_x            \"0\"\nhud_iammo1_pos_y            \"-2\"\nhud_iammo1_scale            \".65\"\nhud_iammo1_show             \"1\"\nhud_iammo1_style            \"0\"\nhud_iammo2_align_x          \"center\"\nhud_iammo2_align_y          \"before\"\nhud_iammo2_frame            \"0\"\nhud_iammo2_place            \"iammo3\"\nhud_iammo2_pos_x            \"0\"\nhud_iammo2_pos_y            \"-2\"\nhud_iammo2_scale            \".65\"\nhud_iammo2_show             \"1\"\nhud_iammo2_style            \"0\"\nhud_iammo3_align_x          \"center\"\nhud_iammo3_align_y          \"before\"\nhud_iammo3_frame            \"0\"\nhud_iammo3_place            \"iammo4\"\nhud_iammo3_pos_x            \"0\"\nhud_iammo3_pos_y            \"-2\"\nhud_iammo3_scale            \".65\"\nhud_iammo3_show             \"1\"\nhud_iammo3_style            \"0\"\nhud_iammo4_align_x          \"right\"\nhud_iammo4_align_y          \"bottom\"\nhud_iammo4_frame            \"0\"\nhud_iammo4_place            \"screen\"\nhud_iammo4_pos_x            \"-2\"\nhud_iammo4_pos_y            \"-2\"\nhud_iammo4_scale            \".65\"\nhud_iammo4_show             \"1\"\nhud_iammo4_style            \"0\"\nhud_iammo_align_x           \"left\"\nhud_iammo_align_y           \"top\"\nhud_iammo_frame             \"0\"\nhud_iammo_place             \"screen\"\nhud_iammo_pos_x             \"0\"\nhud_iammo_pos_y             \"0\"\nhud_iammo_scale             \"1\"\nhud_iammo_show              \"0\"\nhud_iammo_style             \"0\"\nhud_iarmor_align_x          \"left\"\nhud_iarmor_align_y          \"before\"\nhud_iarmor_frame            \"0\"\nhud_iarmor_place            \"face\"\nhud_iarmor_pos_x            \"0\"\nhud_iarmor_pos_y            \"-2\"\nhud_iarmor_scale            \"1.25\"\nhud_iarmor_show             \"1\"\nhud_iarmor_style            \"0\"\nhud_key1_align_x            \"top\"\nhud_key1_align_y            \"left\"\nhud_key1_frame              \"0\"\nhud_key1_place              \"top\"\nhud_key1_pos_x              \"0\"\nhud_key1_pos_y              \"64\"\nhud_key1_scale              \"1\"\nhud_key1_show               \"0\"\nhud_key1_style              \"0\"\nhud_key2_align_x            \"left\"\nhud_key2_align_y            \"after\"\nhud_key2_frame              \"0\"\nhud_key2_place              \"key1\"\nhud_key2_pos_x              \"0\"\nhud_key2_pos_y              \"0\"\nhud_key2_scale              \"1\"\nhud_key2_show               \"0\"\nhud_key2_style              \"0\"\nhud_mp3_time_align_x        \"left\"\nhud_mp3_time_align_y        \"bottom\"\nhud_mp3_time_frame          \"0\"\nhud_mp3_time_on_scoreboard  \"0\"\nhud_mp3_time_place          \"top\"\nhud_mp3_time_pos_x          \"0\"\nhud_mp3_time_pos_y          \"0\"\nhud_mp3_time_show           \"0\"\nhud_mp3_time_style          \"0\"\nhud_mp3_title_align_x       \"center\"\nhud_mp3_title_align_y       \"bottom\"\nhud_mp3_title_frame         \"0\"\nhud_mp3_title_height        \"8\"\nhud_mp3_title_on_scoreboard \"0\"\nhud_mp3_title_place         \"screen\"\nhud_mp3_title_pos_x         \"0\"\nhud_mp3_title_pos_y         \"0\"\nhud_mp3_title_scroll        \"0\"\nhud_mp3_title_scroll_delay  \"0.2\"\nhud_mp3_title_show          \"0\"\nhud_mp3_title_style         \"0\"\nhud_mp3_title_width         \"256\"\nhud_mp3_title_wordwrap      \"0\"\nhud_net_align_x             \"left\"\nhud_net_align_y             \"center\"\nhud_net_frame               \"0.2\"\nhud_net_period              \"1\"\nhud_net_place               \"top\"\nhud_net_pos_x               \"0\"\nhud_net_pos_y               \"0\"\nhud_net_show                \"0\"\nhud_netgraph_align_x        \"left\"\nhud_netgraph_align_y        \"bottom\"\nhud_netgraph_alpha          \"1\"\nhud_netgraph_frame          \"0\"\nhud_netgraph_full           \"0\"\nhud_netgraph_height         \"32\"\nhud_netgraph_inframes       \"0\"\nhud_netgraph_lostscale      \"1\"\nhud_netgraph_place          \"top\"\nhud_netgraph_ploss          \"1\"\nhud_netgraph_pos_x          \"0\"\nhud_netgraph_pos_y          \"0\"\nhud_netgraph_scale          \"256\"\nhud_netgraph_show           \"0\"\nhud_netgraph_swap_x         \"0\"\nhud_netgraph_swap_y         \"0\"\nhud_netgraph_width          \"256\"\nhud_pent_align_x            \"left\"\nhud_pent_align_y            \"after\"\nhud_pent_frame              \"0\"\nhud_pent_place              \"ring\"\nhud_pent_pos_x              \"0\"\nhud_pent_pos_y              \"0\"\nhud_pent_scale              \"1\"\nhud_pent_show               \"0\"\nhud_pent_style              \"0\"\nhud_ping_align_x            \"center\"\nhud_ping_align_y            \"bottom\"\nhud_ping_blink              \"0\"\nhud_ping_frame              \"0\"\nhud_ping_period             \"1\"\nhud_ping_place              \"screen\"\nhud_ping_pos_x              \"0\"\nhud_ping_pos_y              \"0\"\nhud_ping_show               \"0\"\nhud_ping_show_dev           \"0\"\nhud_ping_show_max           \"0\"\nhud_ping_show_min           \"0\"\nhud_ping_show_pl            \"0\"\nhud_quad_align_x            \"left\"\nhud_quad_align_y            \"after\"\nhud_quad_frame              \"0\"\nhud_quad_place              \"suit\"\nhud_quad_pos_x              \"0\"\nhud_quad_pos_y              \"0\"\nhud_quad_scale              \"1\"\nhud_quad_show               \"0\"\nhud_quad_style              \"0\"\nhud_ring_align_x            \"left\"\nhud_ring_align_y            \"after\"\nhud_ring_frame              \"0\"\nhud_ring_place              \"key2\"\nhud_ring_pos_x              \"0\"\nhud_ring_pos_y              \"0\"\nhud_ring_scale              \"1\"\nhud_ring_show               \"0\"\nhud_ring_style              \"0\"\nhud_sigil1_align_x          \"left\"\nhud_sigil1_align_y          \"top\"\nhud_sigil1_frame            \"0\"\nhud_sigil1_place            \"screen\"\nhud_sigil1_pos_x            \"0\"\nhud_sigil1_pos_y            \"0\"\nhud_sigil1_scale            \"1\"\nhud_sigil1_show             \"0\"\nhud_sigil1_style            \"0\"\nhud_sigil2_align_x          \"after\"\nhud_sigil2_align_y          \"top\"\nhud_sigil2_frame            \"0\"\nhud_sigil2_place            \"sigil1\"\nhud_sigil2_pos_x            \"0\"\nhud_sigil2_pos_y            \"0\"\nhud_sigil2_scale            \"1\"\nhud_sigil2_show             \"0\"\nhud_sigil2_style            \"0\"\nhud_sigil3_align_x          \"after\"\nhud_sigil3_align_y          \"top\"\nhud_sigil3_frame            \"0\"\nhud_sigil3_place            \"sigil2\"\nhud_sigil3_pos_x            \"0\"\nhud_sigil3_pos_y            \"0\"\nhud_sigil3_scale            \"1\"\nhud_sigil3_show             \"0\"\nhud_sigil3_style            \"0\"\nhud_sigil4_align_x          \"after\"\nhud_sigil4_align_y          \"top\"\nhud_sigil4_frame            \"0\"\nhud_sigil4_place            \"sigil3\"\nhud_sigil4_pos_x            \"0\"\nhud_sigil4_pos_y            \"0\"\nhud_sigil4_scale            \"1\"\nhud_sigil4_show             \"0\"\nhud_sigil4_style            \"0\"\nhud_speed_align_x           \"center\"\nhud_speed_align_y           \"bottom\"\nhud_speed_frame             \"0\"\nhud_speed_place             \"top\"\nhud_speed_pos_x             \"0\"\nhud_speed_pos_y             \"-5\"\nhud_speed_show              \"0\"\nhud_speed_xyz               \"0\"\nhud_suit_align_x            \"left\"\nhud_suit_align_y            \"after\"\nhud_suit_frame              \"0\"\nhud_suit_place              \"pent\"\nhud_suit_pos_x              \"0\"\nhud_suit_pos_y              \"0\"\nhud_suit_scale              \"1\"\nhud_suit_show               \"0\"\nhud_suit_style              \"0\"\nhud_teamfrags_align_x       \"after\"\nhud_teamfrags_align_y       \"center\"\nhud_teamfrags_cell_height   \"8\"\nhud_teamfrags_cell_width    \"44\"\nhud_teamfrags_cols          \"1\"\nhud_teamfrags_fliptext      \"1\"\nhud_teamfrags_frame         \"0\"\nhud_teamfrags_padtext       \"1\"\nhud_teamfrags_place         \"group2\"\nhud_teamfrags_pos_x         \"4\"\nhud_teamfrags_pos_y         \"0\"\nhud_teamfrags_rows          \"2\"\nhud_teamfrags_show          \"1\"\nhud_teamfrags_shownames     \"1\"\nhud_teamfrags_space_x       \"0\"\nhud_teamfrags_space_y       \"1\"\nhud_teamfrags_strip         \"1\"\nhud_teamfrags_style         \"0\"\nhud_teamfrags_vertical      \"1\"\nhud_tracking_align_x        \"center\"\nhud_tracking_align_y        \"bottom\"\nhud_tracking_format         \"pov: %t %n\"\nhud_tracking_frame          \"0\"\nhud_tracking_place          \"screen\"\nhud_tracking_pos_x          \"0\"\nhud_tracking_pos_y          \"0\"\nhud_tracking_show           \"1\"\nhud_recalculate\n"
  },
  {
    "path": "misc/cfg/hud_empezar.cfg",
    "content": "echo Empezar's Head Up Display \n\nscr_newhud 1\ncvar_reset_re hud_.\nhide gameclock\nhide mp3_title\nshow tracking\nplace tracking screen\nalign tracking center top\nmove tracking 0 0\n\n//MQWCL HUD\nhud_ammo1_align_x            \"center\"\nhud_ammo1_align_y            \"bottom\"\nhud_ammo1_pos_x              \"-61\"\nhud_ammo1_pos_y              \"-17\"\nhud_ammo1_show               \"1\"\nhud_ammo1_style              \"1\"\nhud_ammo2_align_x            \"center\"\nhud_ammo2_align_y            \"bottom\"\nhud_ammo2_pos_x              \"-33\"\nhud_ammo2_pos_y              \"-17\"\nhud_ammo2_show               \"1\"\nhud_ammo2_style              \"1\"\nhud_ammo3_align_x            \"center\"\nhud_ammo3_align_y            \"bottom\"\nhud_ammo3_pos_x              \"32\"\nhud_ammo3_pos_y              \"-17\"\nhud_ammo3_show               \"1\"\nhud_ammo3_style              \"1\"\nhud_ammo4_align_x            \"center\"\nhud_ammo4_align_y            \"bottom\"\nhud_ammo4_pos_x              \"60\"\nhud_ammo4_pos_y              \"-17\"\nhud_ammo4_show               \"1\"\nhud_ammo4_style              \"1\"\nhud_ammo_align               \"center\"\nhud_ammo_align_x             \"center\"\nhud_ammo_align_y             \"bottom\"\nhud_ammo_pos_y               \"-17\"\nhud_ammo_scale               \"0.5\"\nhud_ammo_show                \"1\"\nhud_armor_align              \"left\"\nhud_armor_align_x            \"center\"\nhud_armor_align_y            \"bottom\"\nhud_armor_pos_x              \"-56\"\nhud_armor_pos_y              \"-25\"\nhud_armor_scale              \"0.5\"\nhud_armor_show               \"1\"\nhud_face_align_x             \"center\"\nhud_face_align_y             \"bottom\"\nhud_face_pos_x               \"83\"\nhud_face_pos_y               \"-25\"\nhud_face_scale               \"0.5\"\nhud_face_show                \"1\"\nhud_frags_align_x            \"right\"\nhud_frags_align_y            \"bottom\"\nhud_frags_cell_width         \"28\"\nhud_frags_cols               \"1\"\nhud_frags_rows               \"8\"\nhud_frags_show               \"1\"\nhud_frags_style              \"2\"\nhud_gun2_align_x             \"center\"\nhud_gun2_align_y             \"bottom\"\nhud_gun2_place               \"screen\"\nhud_gun2_pos_x               \"-58\"\nhud_gun2_pos_y               \"-7\"\nhud_gun2_scale               \"0.7\"\nhud_gun2_show                \"1\"\nhud_gun3_align_x             \"center\"\nhud_gun3_align_y             \"bottom\"\nhud_gun3_place               \"screen\"\nhud_gun3_pos_x               \"-57\"\nhud_gun3_scale               \"0.7\"\nhud_gun3_show                \"1\"\nhud_gun4_align_x             \"center\"\nhud_gun4_align_y             \"bottom\"\nhud_gun4_place               \"screen\"\nhud_gun4_pos_x               \"-29\"\nhud_gun4_pos_y               \"-7\"\nhud_gun4_scale               \"0.7\"\nhud_gun4_show                \"1\"\nhud_gun5_align_x             \"center\"\nhud_gun5_align_y             \"bottom\"\nhud_gun5_place               \"screen\"\nhud_gun5_pos_x               \"-30\"\nhud_gun5_scale               \"0.7\"\nhud_gun5_show                \"1\"\nhud_gun6_align_x             \"center\"\nhud_gun6_align_y             \"bottom\"\nhud_gun6_place               \"screen\"\nhud_gun6_pos_x               \"35\"\nhud_gun6_pos_y               \"-7\"\nhud_gun6_scale               \"0.7\"\nhud_gun6_show                \"1\"\nhud_gun7_align_x             \"center\"\nhud_gun7_align_y             \"bottom\"\nhud_gun7_place               \"screen\"\nhud_gun7_pos_x               \"35\"\nhud_gun7_scale               \"0.7\"\nhud_gun7_show                \"1\"\nhud_gun8_align_x             \"center\"\nhud_gun8_align_y             \"bottom\"\nhud_gun8_place               \"screen\"\nhud_gun8_pos_x               \"64\"\nhud_gun8_pos_y               \"-6\"\nhud_gun8_scale               \"0.7\"\nhud_gun8_show                \"1\"\nhud_gun_place                \"screen\"\nhud_gun_pos_y                \"-26\"\nhud_gun_show                 \"1\"\nhud_health_align_x           \"center\"\nhud_health_align_y           \"bottom\"\nhud_health_pos_x             \"55\"\nhud_health_pos_y             \"-25\"\nhud_health_scale             \"0.5\"\nhud_health_show              \"1\"\nhud_iarmor_align_x           \"center\"\nhud_iarmor_align_y           \"bottom\"\nhud_iarmor_pos_x             \"-83\"\nhud_iarmor_pos_y             \"-25\"\nhud_iarmor_scale             \"0.5\"\nhud_iarmor_show              \"1\"\nhud_pent_align_x             \"center\"\nhud_pent_align_y             \"bottom\"\nhud_pent_place               \"screen\"\nhud_pent_pos_x               \"33.7\"\nhud_pent_pos_y               \"-42\"\nhud_pent_scale               \"0.5\"\nhud_pent_show                \"1\"\nhud_quad_align_x             \"center\"\nhud_quad_align_y             \"bottom\"\nhud_quad_place               \"screen\"\nhud_quad_pos_x               \"25\"\nhud_quad_pos_y               \"-42\"\nhud_quad_scale               \"0.5\"\nhud_quad_show                \"1\"\nhud_ring_align_x             \"center\"\nhud_ring_align_y             \"bottom\"\nhud_ring_place               \"screen\"\nhud_ring_pos_x               \"41\"\nhud_ring_pos_y               \"-42\"\nhud_ring_scale               \"0.5\"\nhud_ring_show                \"1\"\nhud_teamfrags_align_x        \"right\"\nhud_teamfrags_align_y        \"bottom\"\nhud_teamfrags_cols           \"1\"\nhud_teamfrags_pos_x          \"-29\"\nhud_teamfrags_rows           \"2\"\nhud_teamfrags_show           \"1\"\nhud_teamfrags_style          \"2\"\n\n//Screen Settings\ncl_gameclock                 \"4\"\ncl_gameclock_x               \"22.4\"\ncl_gameclock_y               \"31.3\"\n\nhud_recalculate\n"
  },
  {
    "path": "misc/cfg/hud_murdoc.cfg",
    "content": "echo Murdoc's Head Up Display Configuration File\necho made for conwidth 512 conheigh 412\n\nscr_newhud 1\ncvar_reset_re hud_.\n\nhud_ammo1_pos_x              \"270\"\nhud_ammo1_pos_y              \"395\"\nhud_ammo1_scale              \"0.5\"\nhud_ammo1_show               \"1\"\nhud_ammo2_pos_x              \"330\"\nhud_ammo2_pos_y              \"395\"\nhud_ammo2_scale              \"0.5\"\nhud_ammo2_show               \"1\"\nhud_ammo3_pos_x              \"390\"\nhud_ammo3_pos_y              \"395\"\nhud_ammo3_scale              \"0.5\"\nhud_ammo3_show               \"1\"\nhud_ammo4_pos_x              \"450\"\nhud_ammo4_pos_y              \"395\"\nhud_ammo4_scale              \"0.5\"\nhud_ammo4_show               \"1\"\nhud_ammo_pos_x               \"430\"\nhud_ammo_pos_y               \"385\"\nhud_armor_pos_x              \"40\"\nhud_armor_pos_y              \"385\"\nhud_armor_show               \"1\"\nhud_face_pos_x               \"130\"\nhud_face_pos_y               \"386\"\nhud_face_show                \"1\"\nhud_fps_pos_x                \"-24\"\nhud_fps_pos_y                \"-42\"\nhud_fps_show                 \"1\"\nhud_fps_title                \"0\"\nhud_gun2_pos_x               \"279\"\nhud_gun2_pos_y               \"-40\"\nhud_gun2_show                \"1\"\nhud_gun3_pos_x               \"-25\"\nhud_gun3_pos_y               \"20\"\nhud_gun3_show                \"1\"\nhud_gun4_pos_x               \"35\"\nhud_gun4_pos_y               \"-20\"\nhud_gun4_show                \"1\"\nhud_gun5_pos_x               \"-24\"\nhud_gun5_pos_y               \"20\"\nhud_gun5_show                \"1\"\nhud_gun6_pos_x               \"36\"\nhud_gun6_pos_y               \"-20\"\nhud_gun6_show                \"1\"\nhud_gun7_pos_x               \"-24\"\nhud_gun7_pos_y               \"20\"\nhud_gun7_show                \"1\"\nhud_gun8_pos_x               \"35\"\nhud_gun8_show                \"1\"\nhud_health_pos_x             \"160\"\nhud_health_pos_y             \"385\"\nhud_health_show              \"1\"\nhud_iarmor_pos_x             \"10\"\nhud_iarmor_pos_y             \"387\"\nhud_iarmor_show              \"1\"\n\nhud_recalculate\n"
  },
  {
    "path": "misc/cfg/hud_senft.cfg",
    "content": "scr_newhud                    \"1\"\nhud_ammo1_align               \"right\"\nhud_ammo1_align_x             \"center\"\nhud_ammo1_align_y             \"center\"\nhud_ammo1_digits              \"3\"\nhud_ammo1_frame               \"0\"\nhud_ammo1_frame_color         \"0 0 0\"\nhud_ammo1_order               \"2\"\nhud_ammo1_place               \"group2\"\nhud_ammo1_pos_x               \"0\"\nhud_ammo1_pos_y               \"0\"\nhud_ammo1_scale               \"0.65\"\nhud_ammo1_show                \"1\"\nhud_ammo1_style               \"0\"\nhud_ammo2_align               \"right\"\nhud_ammo2_align_x             \"center\"\nhud_ammo2_align_y             \"center\"\nhud_ammo2_digits              \"3\"\nhud_ammo2_frame               \"0\"\nhud_ammo2_frame_color         \"0 0 0\"\nhud_ammo2_order               \"5\"\nhud_ammo2_place               \"group4\"\nhud_ammo2_pos_x               \"0\"\nhud_ammo2_pos_y               \"0\"\nhud_ammo2_scale               \"0.65\"\nhud_ammo2_show                \"1\"\nhud_ammo2_style               \"0\"\nhud_ammo3_align               \"right\"\nhud_ammo3_align_x             \"center\"\nhud_ammo3_align_y             \"center\"\nhud_ammo3_digits              \"3\"\nhud_ammo3_frame               \"0\"\nhud_ammo3_frame_color         \"0 0 0\"\nhud_ammo3_order               \"4\"\nhud_ammo3_place               \"group5\"\nhud_ammo3_pos_x               \"0\"\nhud_ammo3_pos_y               \"0\"\nhud_ammo3_scale               \"0.65\"\nhud_ammo3_show                \"1\"\nhud_ammo3_style               \"0\"\nhud_ammo4_align               \"right\"\nhud_ammo4_align_x             \"center\"\nhud_ammo4_align_y             \"bottom\"\nhud_ammo4_digits              \"3\"\nhud_ammo4_frame               \"0\"\nhud_ammo4_frame_color         \"0 0 0\"\nhud_ammo4_order               \"5\"\nhud_ammo4_place               \"group6\"\nhud_ammo4_pos_x               \"0\"\nhud_ammo4_pos_y               \"-4\"\nhud_ammo4_scale               \"0.65\"\nhud_ammo4_show                \"1\"\nhud_ammo4_style               \"0\"\nhud_ammo_align                \"right\"\nhud_ammo_align_x              \"center\"\nhud_ammo_align_y              \"center\"\nhud_ammo_digits               \"3\"\nhud_ammo_frame                \"0\"\nhud_ammo_frame_color          \"0 0 0\"\nhud_ammo_order                \"2\"\nhud_ammo_place                \"iammo\"\nhud_ammo_pos_x                \"62\"\nhud_ammo_pos_y                \"1\"\nhud_ammo_scale                \"1.2\"\nhud_ammo_show                 \"1\"\nhud_ammo_style                \"0\"\nhud_armor_align               \"right\"\nhud_armor_align_x             \"right\"\nhud_armor_align_y             \"top\"\nhud_armor_digits              \"3\"\nhud_armor_frame               \"0\"\nhud_armor_frame_color         \"0 0 0\"\nhud_armor_order               \"2\"\nhud_armor_place               \"face\"\nhud_armor_pos_x               \"88\"\nhud_armor_pos_y               \"-1\"\nhud_armor_scale               \"1.2\"\nhud_armor_show                \"1\"\nhud_armor_style               \"0\"\nhud_armordamage_align         \"right\"\nhud_armordamage_align_x       \"left\"\nhud_armordamage_align_y       \"before\"\nhud_armordamage_digits        \"3\"\nhud_armordamage_duration      \"0.8\"\nhud_armordamage_frame         \"0\"\nhud_armordamage_frame_color   \"0 0 0\"\nhud_armordamage_order         \"3\"\nhud_armordamage_place         \"armor\"\nhud_armordamage_pos_x         \"0\"\nhud_armordamage_pos_y         \"0\"\nhud_armordamage_scale         \"1\"\nhud_armordamage_show          \"0\"\nhud_armordamage_style         \"0\"\nhud_clock_align_x             \"right\"\nhud_clock_align_y             \"before\"\nhud_clock_big                 \"0\"\nhud_clock_blink               \"0\"\nhud_clock_frame               \"0.5\"\nhud_clock_frame_color         \"0 0 0\"\nhud_clock_order               \"8\"\nhud_clock_place               \"group2\"\nhud_clock_pos_x               \"-2\"\nhud_clock_pos_y               \"-2\"\nhud_clock_scale               \"1\"\nhud_clock_show                \"0\"\nhud_clock_style               \"0\"\nhud_democlock_align_x         \"right\"\nhud_democlock_align_y         \"console\"\nhud_democlock_big             \"0\"\nhud_democlock_blink           \"0\"\nhud_democlock_frame           \"0\"\nhud_democlock_frame_color     \"0 0 0\"\nhud_democlock_order           \"11\"\nhud_democlock_place           \"tracking\"\nhud_democlock_pos_x           \"-7\"\nhud_democlock_pos_y           \"15\"\nhud_democlock_scale           \"0.59\"\nhud_democlock_show            \"0\"\nhud_democlock_style           \"0\"\nhud_digits_trim               \"1\"\nhud_editor_allowalign         \"1\"\nhud_editor_allowmove          \"1\"\nhud_editor_allowplace         \"1\"\nhud_editor_allowresize        \"1\"\nhud_face_align_x              \"left\"\nhud_face_align_y              \"bottom\"\nhud_face_frame                \"0\"\nhud_face_frame_color          \"0 0 0\"\nhud_face_order                \"1\"\nhud_face_place                \"screen\"\nhud_face_pos_x                \"0\"\nhud_face_pos_y                \"0\"\nhud_face_scale                \"2.9\"\nhud_face_show                 \"1\"\nhud_fps_align_x               \"right\"\nhud_fps_align_y               \"console\"\nhud_fps_decimals              \"0\"\nhud_fps_frame                 \"0\"\nhud_fps_frame_color           \"0 0 0\"\nhud_fps_order                 \"9\"\nhud_fps_place                 \"screen\"\nhud_fps_pos_x                 \"-2\"\nhud_fps_pos_y                 \"2\"\nhud_fps_show                  \"1\"\nhud_fps_show_min              \"0\"\nhud_fps_title                 \"1\"\nhud_frags_align_x             \"right\"\nhud_frags_align_y             \"bottom\"\nhud_frags_bignum              \"0\"\nhud_frags_cell_height         \"10\"\nhud_frags_cell_width          \"26\"\nhud_frags_colors_alpha        \"0.2\"\nhud_frags_cols                \"1\"\nhud_frags_extra_spec_info     \"0\"\nhud_frags_fliptext            \"0\"\nhud_frags_frame               \"0\"\nhud_frags_frame_color         \"0 0 0\"\nhud_frags_maxname             \"10\"\nhud_frags_order               \"1\"\nhud_frags_padtext             \"1\"\nhud_frags_place               \"screen\"\nhud_frags_pos_x               \"0\"\nhud_frags_pos_y               \"0\"\nhud_frags_rows                \"8\"\nhud_frags_show                \"1\"\nhud_frags_shownames           \"1\"\nhud_frags_showself_always     \"1\"\nhud_frags_showteams           \"1\"\nhud_frags_space_x             \"0\"\nhud_frags_space_y             \"2\"\nhud_frags_strip               \"1\"\nhud_frags_style               \"8\"\nhud_frags_teamsort            \"1\"\nhud_frags_vertical            \"0\"\nhud_gameclock_align_x         \"center\"\nhud_gameclock_align_y         \"center\"\nhud_gameclock_big             \"1\"\nhud_gameclock_blink           \"0\"\nhud_gameclock_countdown       \"0\"\nhud_gameclock_frame           \"0\"\nhud_gameclock_frame_color     \"0 0 0\"\nhud_gameclock_order           \"1\"\nhud_gameclock_place           \"top\"\nhud_gameclock_pos_x           \"0\"\nhud_gameclock_pos_y           \"-67\"\nhud_gameclock_scale           \"0.6\"\nhud_gameclock_show            \"1\"\nhud_gameclock_style           \"0\"\nhud_group1_align_x            \"center\"\nhud_group1_align_y            \"bottom\"\nhud_group1_frame              \"0\"\nhud_group1_frame_color        \"0 0 0\"\nhud_group1_height             \"32\"\nhud_group1_name               \"group1\"\nhud_group1_order              \"0\"\nhud_group1_pic_alpha          \"1.0\"\nhud_group1_pic_scalemode      \"0\"\nhud_group1_picture            \"\"\nhud_group1_place              \"screen\"\nhud_group1_pos_x              \"-22\"\nhud_group1_pos_y              \"-11\"\nhud_group1_show               \"0\"\nhud_group1_width              \"172\"\nhud_group2_align_x            \"left\"\nhud_group2_align_y            \"center\"\nhud_group2_frame              \".5\"\nhud_group2_frame_color        \"0 0 0\"\nhud_group2_height             \"76\"\nhud_group2_name               \"group2\"\nhud_group2_order              \"0\"\nhud_group2_pic_alpha          \"1.0\"\nhud_group2_pic_scalemode      \"0\"\nhud_group2_picture            \"\"\nhud_group2_place              \"screen\"\nhud_group2_pos_x              \"3\"\nhud_group2_pos_y              \"-130\"\nhud_group2_show               \"1\"\nhud_group2_width              \"51\"\nhud_group3_align_x            \"left\"\nhud_group3_align_y            \"top\"\nhud_group3_frame              \".5\"\nhud_group3_frame_color        \"0 0 0\"\nhud_group3_height             \"23\"\nhud_group3_name               \"group3\"\nhud_group3_order              \"1\"\nhud_group3_pic_alpha          \"1.0\"\nhud_group3_pic_scalemode      \"0\"\nhud_group3_picture            \"\"\nhud_group3_place              \"group1\"\nhud_group3_pos_x              \"0\"\nhud_group3_pos_y              \"0\"\nhud_group3_show               \"0\"\nhud_group3_width              \"168\"\nhud_group4_align_x            \"center\"\nhud_group4_align_y            \"center\"\nhud_group4_frame              \".5\"\nhud_group4_frame_color        \"0 0 0\"\nhud_group4_height             \"76\"\nhud_group4_name               \"group4\"\nhud_group4_order              \"1\"\nhud_group4_pic_alpha          \"1.0\"\nhud_group4_pic_scalemode      \"0\"\nhud_group4_picture            \"\"\nhud_group4_place              \"group2\"\nhud_group4_pos_x              \"0\"\nhud_group4_pos_y              \"91\"\nhud_group4_show               \"1\"\nhud_group4_width              \"51\"\nhud_group5_align_x            \"center\"\nhud_group5_align_y            \"center\"\nhud_group5_frame              \".5\"\nhud_group5_frame_color        \"0 0 0\"\nhud_group5_height             \"76\"\nhud_group5_name               \"group5\"\nhud_group5_order              \"2\"\nhud_group5_pic_alpha          \"1.0\"\nhud_group5_pic_scalemode      \"0\"\nhud_group5_picture            \"\"\nhud_group5_place              \"group4\"\nhud_group5_pos_x              \"0\"\nhud_group5_pos_y              \"88\"\nhud_group5_show               \"1\"\nhud_group5_width              \"51\"\nhud_group6_align_x            \"center\"\nhud_group6_align_y            \"center\"\nhud_group6_frame              \".5\"\nhud_group6_frame_color        \"0 0 0\"\nhud_group6_height             \"48\"\nhud_group6_name               \"group6\"\nhud_group6_order              \"3\"\nhud_group6_pic_alpha          \"1.0\"\nhud_group6_pic_scalemode      \"0\"\nhud_group6_picture            \"\"\nhud_group6_place              \"group5\"\nhud_group6_pos_x              \"0\"\nhud_group6_pos_y              \"72\"\nhud_group6_show               \"1\"\nhud_group6_width              \"51\"\nhud_group7_align_x            \"left\"\nhud_group7_align_y            \"top\"\nhud_group7_frame              \".5\"\nhud_group7_frame_color        \"0 0 0\"\nhud_group7_height             \"64\"\nhud_group7_name               \"group7\"\nhud_group7_order              \"0\"\nhud_group7_pic_alpha          \"1.0\"\nhud_group7_pic_scalemode      \"0\"\nhud_group7_picture            \"\"\nhud_group7_place              \"screen\"\nhud_group7_pos_x              \"0\"\nhud_group7_pos_y              \"0\"\nhud_group7_show               \"0\"\nhud_group7_width              \"64\"\nhud_group8_align_x            \"left\"\nhud_group8_align_y            \"top\"\nhud_group8_frame              \".5\"\nhud_group8_frame_color        \"0 0 0\"\nhud_group8_height             \"64\"\nhud_group8_name               \"group8\"\nhud_group8_order              \"0\"\nhud_group8_pic_alpha          \"1.0\"\nhud_group8_pic_scalemode      \"0\"\nhud_group8_picture            \"\"\nhud_group8_place              \"screen\"\nhud_group8_pos_x              \"0\"\nhud_group8_pos_y              \"0\"\nhud_group8_show               \"0\"\nhud_group8_width              \"64\"\nhud_group9_align_x            \"left\"\nhud_group9_align_y            \"top\"\nhud_group9_frame              \".5\"\nhud_group9_frame_color        \"0 0 0\"\nhud_group9_height             \"64\"\nhud_group9_name               \"group9\"\nhud_group9_order              \"0\"\nhud_group9_pic_alpha          \"1.0\"\nhud_group9_pic_scalemode      \"0\"\nhud_group9_picture            \"\"\nhud_group9_place              \"screen\"\nhud_group9_pos_x              \"0\"\nhud_group9_pos_y              \"0\"\nhud_group9_show               \"0\"\nhud_group9_width              \"64\"\nhud_gun2_align_x              \"center\"\nhud_gun2_align_y              \"top\"\nhud_gun2_frame                \"0\"\nhud_gun2_frame_color          \"100 40 0\"\nhud_gun2_order                \"2\"\nhud_gun2_place                \"group2\"\nhud_gun2_pos_x                \"13\"\nhud_gun2_pos_y                \"5\"\nhud_gun2_scale                \"1.7\"\nhud_gun2_show                 \"1\"\nhud_gun2_style                \"0\"\nhud_gun3_align_x              \"center\"\nhud_gun3_align_y              \"bottom\"\nhud_gun3_frame                \"0\"\nhud_gun3_frame_color          \"100 80 0\"\nhud_gun3_order                \"3\"\nhud_gun3_place                \"group2\"\nhud_gun3_pos_x                \"9\"\nhud_gun3_pos_y                \"4\"\nhud_gun3_scale                \"1.7\"\nhud_gun3_show                 \"1\"\nhud_gun3_style                \"0\"\nhud_gun4_align_x              \"center\"\nhud_gun4_align_y              \"top\"\nhud_gun4_frame                \"0\"\nhud_gun4_frame_color          \"0 100 200\"\nhud_gun4_order                \"3\"\nhud_gun4_place                \"group4\"\nhud_gun4_pos_x                \"12\"\nhud_gun4_pos_y                \"11\"\nhud_gun4_scale                \"1.7\"\nhud_gun4_show                 \"1\"\nhud_gun4_style                \"0\"\nhud_gun5_align_x              \"center\"\nhud_gun5_align_y              \"bottom\"\nhud_gun5_frame                \"0\"\nhud_gun5_frame_color          \"0 0 255\"\nhud_gun5_order                \"4\"\nhud_gun5_place                \"group4\"\nhud_gun5_pos_x                \"11\"\nhud_gun5_pos_y                \"4\"\nhud_gun5_scale                \"1.7\"\nhud_gun5_show                 \"1\"\nhud_gun5_style                \"0\"\nhud_gun6_align_x              \"center\"\nhud_gun6_align_y              \"bottom\"\nhud_gun6_frame                \"0\"\nhud_gun6_frame_color          \"0 100 0\"\nhud_gun6_order                \"4\"\nhud_gun6_place                \"group5\"\nhud_gun6_pos_x                \"13\"\nhud_gun6_pos_y                \"4\"\nhud_gun6_scale                \"1.7\"\nhud_gun6_show                 \"1\"\nhud_gun6_style                \"0\"\nhud_gun7_align_x              \"center\"\nhud_gun7_align_y              \"top\"\nhud_gun7_frame                \"0\"\nhud_gun7_frame_color          \"255 0 0\"\nhud_gun7_order                \"3\"\nhud_gun7_place                \"group5\"\nhud_gun7_pos_x                \"8\"\nhud_gun7_pos_y                \"2\"\nhud_gun7_scale                \"1.7\"\nhud_gun7_show                 \"1\"\nhud_gun7_style                \"0\"\nhud_gun8_align_x              \"center\"\nhud_gun8_align_y              \"top\"\nhud_gun8_frame                \"0\"\nhud_gun8_frame_color          \"160 160 160\"\nhud_gun8_order                \"4\"\nhud_gun8_place                \"group6\"\nhud_gun8_pos_x                \"13\"\nhud_gun8_pos_y                \"5\"\nhud_gun8_scale                \"1.7\"\nhud_gun8_show                 \"1\"\nhud_gun8_style                \"0\"\nhud_gun8_wide                 \"0\"\nhud_gun_align_x               \"center\"\nhud_gun_align_y               \"bottom\"\nhud_gun_frame                 \"0\"\nhud_gun_frame_color           \"0 0 0\"\nhud_gun_order                 \"0\"\nhud_gun_place                 \"top\"\nhud_gun_pos_x                 \"0\"\nhud_gun_pos_y                 \"21\"\nhud_gun_scale                 \"1\"\nhud_gun_show                  \"0\"\nhud_gun_style                 \"0\"\nhud_gun_wide                  \"0\"\nhud_health_align              \"right\"\nhud_health_align_x            \"right\"\nhud_health_align_y            \"bottom\"\nhud_health_digits             \"3\"\nhud_health_frame              \"0\"\nhud_health_frame_color        \"0 0 0\"\nhud_health_order              \"2\"\nhud_health_place              \"face\"\nhud_health_pos_x              \"116\"\nhud_health_pos_y              \"-3\"\nhud_health_scale              \"1.6\"\nhud_health_show               \"1\"\nhud_health_style              \"0\"\nhud_healthdamage_align        \"right\"\nhud_healthdamage_align_x      \"left\"\nhud_healthdamage_align_y      \"before\"\nhud_healthdamage_digits       \"3\"\nhud_healthdamage_duration     \"0.8\"\nhud_healthdamage_frame        \"0\"\nhud_healthdamage_frame_color  \"0 0 0\"\nhud_healthdamage_order        \"3\"\nhud_healthdamage_place        \"health\"\nhud_healthdamage_pos_x        \"0\"\nhud_healthdamage_pos_y        \"0\"\nhud_healthdamage_scale        \"1\"\nhud_healthdamage_show         \"0\"\nhud_healthdamage_style        \"0\"\nhud_iammo1_align_x            \"center\"\nhud_iammo1_align_y            \"center\"\nhud_iammo1_frame              \"0\"\nhud_iammo1_frame_color        \"0 0 0\"\nhud_iammo1_order              \"3\"\nhud_iammo1_place              \"ammo1\"\nhud_iammo1_pos_x              \"30\"\nhud_iammo1_pos_y              \"0\"\nhud_iammo1_scale              \"0.55\"\nhud_iammo1_show               \"0\"\nhud_iammo1_style              \"0\"\nhud_iammo2_align_x            \"center\"\nhud_iammo2_align_y            \"center\"\nhud_iammo2_frame              \"0\"\nhud_iammo2_frame_color        \"0 0 0\"\nhud_iammo2_order              \"6\"\nhud_iammo2_place              \"ammo2\"\nhud_iammo2_pos_x              \"30\"\nhud_iammo2_pos_y              \"0\"\nhud_iammo2_scale              \"0.55\"\nhud_iammo2_show               \"0\"\nhud_iammo2_style              \"0\"\nhud_iammo3_align_x            \"center\"\nhud_iammo3_align_y            \"center\"\nhud_iammo3_frame              \"0\"\nhud_iammo3_frame_color        \"0 0 0\"\nhud_iammo3_order              \"5\"\nhud_iammo3_place              \"ammo3\"\nhud_iammo3_pos_x              \"30\"\nhud_iammo3_pos_y              \"0\"\nhud_iammo3_scale              \"0.55\"\nhud_iammo3_show               \"0\"\nhud_iammo3_style              \"0\"\nhud_iammo4_align_x            \"center\"\nhud_iammo4_align_y            \"center\"\nhud_iammo4_frame              \"0\"\nhud_iammo4_frame_color        \"0 0 0\"\nhud_iammo4_order              \"6\"\nhud_iammo4_place              \"ammo4\"\nhud_iammo4_pos_x              \"30\"\nhud_iammo4_pos_y              \"0\"\nhud_iammo4_scale              \"0.55\"\nhud_iammo4_show               \"0\"\nhud_iammo4_style              \"0\"\nhud_iammo_align_x             \"center\"\nhud_iammo_align_y             \"center\"\nhud_iammo_frame               \"0\"\nhud_iammo_frame_color         \"0 0 0\"\nhud_iammo_order               \"1\"\nhud_iammo_place               \"screen\"\nhud_iammo_pos_x               \"-124\"\nhud_iammo_pos_y               \"223\"\nhud_iammo_scale               \"1.44\"\nhud_iammo_show                \"0\"\nhud_iammo_style               \"0\"\nhud_iarmor_align_x            \"center\"\nhud_iarmor_align_y            \"center\"\nhud_iarmor_frame              \"0\"\nhud_iarmor_frame_color        \"0 0 0\"\nhud_iarmor_order              \"3\"\nhud_iarmor_place              \"armor\"\nhud_iarmor_pos_x              \"54\"\nhud_iarmor_pos_y              \"0\"\nhud_iarmor_scale              \"1\"\nhud_iarmor_show               \"1\"\nhud_iarmor_style              \"0\"\nhud_key1_align_x              \"left\"\nhud_key1_align_y              \"top\"\nhud_key1_frame                \"0\"\nhud_key1_frame_color          \"0 0 0\"\nhud_key1_order                \"4\"\nhud_key1_place                \"frags\"\nhud_key1_pos_x                \"0\"\nhud_key1_pos_y                \"-25\"\nhud_key1_scale                \"1.6\"\nhud_key1_show                 \"1\"\nhud_key1_style                \"0\"\nhud_key2_align_x              \"after\"\nhud_key2_align_y              \"top\"\nhud_key2_frame                \"0\"\nhud_key2_frame_color          \"0 0 0\"\nhud_key2_order                \"5\"\nhud_key2_place                \"key1\"\nhud_key2_pos_x                \"0\"\nhud_key2_pos_y                \"0\"\nhud_key2_scale                \"1.6\"\nhud_key2_show                 \"1\"\nhud_key2_style                \"0\"\nhud_keys_align_x              \"right\"\nhud_keys_align_y              \"center\"\nhud_keys_frame                \"0.5\"\nhud_keys_frame_color          \"20 20 20\"\nhud_keys_order                \"1\"\nhud_keys_place                \"screen\"\nhud_keys_pos_x                \"0\"\nhud_keys_pos_y                \"0\"\nhud_keys_scale                \"2\"\nhud_keys_show                 \"0\"\nhud_mouserate_align_x         \"left\"\nhud_mouserate_align_y         \"bottom\"\nhud_mouserate_frame           \"0\"\nhud_mouserate_frame_color     \"0 0 0\"\nhud_mouserate_interval        \"1\"\nhud_mouserate_order           \"9\"\nhud_mouserate_place           \"screen\"\nhud_mouserate_pos_x           \"0\"\nhud_mouserate_pos_y           \"0\"\nhud_mouserate_show            \"0\"\nhud_mouserate_title           \"1\"\nhud_mp3_time_align_x          \"left\"\nhud_mp3_time_align_y          \"bottom\"\nhud_mp3_time_frame            \"0\"\nhud_mp3_time_frame_color      \"0 0 0\"\nhud_mp3_time_on_scoreboard    \"0\"\nhud_mp3_time_order            \"0\"\nhud_mp3_time_place            \"top\"\nhud_mp3_time_pos_x            \"0\"\nhud_mp3_time_pos_y            \"0\"\nhud_mp3_time_show             \"0\"\nhud_mp3_time_style            \"0\"\nhud_mp3_title_align_x         \"center\"\nhud_mp3_title_align_y         \"bottom\"\nhud_mp3_title_frame           \"0\"\nhud_mp3_title_frame_color     \"0 0 0\"\nhud_mp3_title_height          \"8\"\nhud_mp3_title_on_scoreboard   \"0\"\nhud_mp3_title_order           \"0\"\nhud_mp3_title_place           \"screen\"\nhud_mp3_title_pos_x           \"0\"\nhud_mp3_title_pos_y           \"0\"\nhud_mp3_title_scroll          \"0\"\nhud_mp3_title_scroll_delay    \"0.2\"\nhud_mp3_title_show            \"0\"\nhud_mp3_title_style           \"0\"\nhud_mp3_title_width           \"256\"\nhud_mp3_title_wordwrap        \"0\"\nhud_net_align_x               \"left\"\nhud_net_align_y               \"center\"\nhud_net_frame                 \"0.2\"\nhud_net_frame_color           \"0 0 0\"\nhud_net_order                 \"7\"\nhud_net_period                \"1\"\nhud_net_place                 \"top\"\nhud_net_pos_x                 \"10\"\nhud_net_pos_y                 \"0\"\nhud_net_show                  \"0\"\nhud_netgraph_align_x          \"center\"\nhud_netgraph_align_y          \"before\"\nhud_netgraph_alpha            \"1\"\nhud_netgraph_frame            \"0.5\"\nhud_netgraph_frame_color      \"0 0 0\"\nhud_netgraph_full             \"1\"\nhud_netgraph_height           \"28\"\nhud_netgraph_inframes         \"0\"\nhud_netgraph_lostscale        \"1\"\nhud_netgraph_order            \"2\"\nhud_netgraph_place            \"group4\"\nhud_netgraph_ploss            \"0\"\nhud_netgraph_pos_x            \"0\"\nhud_netgraph_pos_y            \"-2\"\nhud_netgraph_scale            \"256\"\nhud_netgraph_show             \"0\"\nhud_netgraph_swap_x           \"0\"\nhud_netgraph_swap_y           \"0\"\nhud_netgraph_width            \"170\"\nhud_notify_align_x            \"left\"\nhud_notify_align_y            \"top\"\nhud_notify_cols               \"30\"\nhud_notify_frame              \"0\"\nhud_notify_frame_color        \"0 0 0\"\nhud_notify_order              \"8\"\nhud_notify_place              \"top\"\nhud_notify_pos_x              \"0\"\nhud_notify_pos_y              \"0\"\nhud_notify_rows               \"4\"\nhud_notify_scale              \"1\"\nhud_notify_show               \"0\"\nhud_notify_time               \"4\"\nhud_offscreen                 \"1\"\nhud_ownfrags_align_x          \"center\"\nhud_ownfrags_align_y          \"top\"\nhud_ownfrags_frame            \"0.2\"\nhud_ownfrags_frame_color      \"0 0 100\"\nhud_ownfrags_order            \"1\"\nhud_ownfrags_place            \"screen\"\nhud_ownfrags_pos_x            \"0\"\nhud_ownfrags_pos_y            \"50\"\nhud_ownfrags_scale            \"1.5\"\nhud_ownfrags_show             \"0\"\nhud_ownfrags_timeout          \"3\"\nhud_pent_align_x              \"after\"\nhud_pent_align_y              \"top\"\nhud_pent_frame                \"0\"\nhud_pent_frame_color          \"0 0 0\"\nhud_pent_order                \"2\"\nhud_pent_place                \"ring\"\nhud_pent_pos_x                \"-1\"\nhud_pent_pos_y                \"0\"\nhud_pent_scale                \"2\"\nhud_pent_show                 \"0\"\nhud_pent_style                \"0\"\nhud_ping_align_x              \"left\"\nhud_ping_align_y              \"center\"\nhud_ping_blink                \"0\"\nhud_ping_frame                \"0\"\nhud_ping_frame_color          \"0 0 0\"\nhud_ping_order                \"9\"\nhud_ping_period               \"1\"\nhud_ping_place                \"group4\"\nhud_ping_pos_x                \"4\"\nhud_ping_pos_y                \"0\"\nhud_ping_show                 \"0\"\nhud_ping_show_dev             \"0\"\nhud_ping_show_max             \"0\"\nhud_ping_show_min             \"0\"\nhud_ping_show_pl              \"1\"\nhud_planmode                  \"0\"\nhud_quad_align_x              \"after\"\nhud_quad_align_y              \"top\"\nhud_quad_frame                \"0\"\nhud_quad_frame_color          \"0 0 0\"\nhud_quad_order                \"3\"\nhud_quad_place                \"pent\"\nhud_quad_pos_x                \"0\"\nhud_quad_pos_y                \"0\"\nhud_quad_scale                \"2\"\nhud_quad_show                 \"0\"\nhud_quad_style                \"0\"\nhud_radar_align_x             \"left\"\nhud_radar_align_y             \"bottom\"\nhud_radar_autosize            \"0\"\nhud_radar_fade_players        \"1\"\nhud_radar_frame               \"0\"\nhud_radar_frame_color         \"0 0 0\"\nhud_radar_height              \"25%\"\nhud_radar_highlight           \"0\"\nhud_radar_highlight_color     \"255 255 0\"\nhud_radar_itemfilter          \"backpack quad pent armor mega\"\nhud_radar_onlytp              \"0\"\nhud_radar_opacity             \"0.5\"\nhud_radar_order               \"0\"\nhud_radar_otherfilter         \"projectiles gibs explosions shotgun\"\nhud_radar_place               \"top\"\nhud_radar_player_size         \"10\"\nhud_radar_pos_x               \"0\"\nhud_radar_pos_y               \"0\"\nhud_radar_show                \"0\"\nhud_radar_show_height         \"1\"\nhud_radar_show_hold           \"0\"\nhud_radar_show_names          \"0\"\nhud_radar_show_powerups       \"1\"\nhud_radar_show_stats          \"1\"\nhud_radar_weaponfilter        \"gl rl lg\"\nhud_radar_width               \"30%\"\nhud_ring_align_x              \"center\"\nhud_ring_align_y              \"center\"\nhud_ring_frame                \"0\"\nhud_ring_frame_color          \"0 0 0\"\nhud_ring_order                \"1\"\nhud_ring_place                \"group1\"\nhud_ring_pos_x                \"-61\"\nhud_ring_pos_y                \"-39\"\nhud_ring_scale                \"2\"\nhud_ring_show                 \"0\"\nhud_ring_style                \"0\"\nhud_sigil1_align_x            \"left\"\nhud_sigil1_align_y            \"top\"\nhud_sigil1_frame              \"0\"\nhud_sigil1_frame_color        \"0 0 0\"\nhud_sigil1_order              \"0\"\nhud_sigil1_place              \"screen\"\nhud_sigil1_pos_x              \"0\"\nhud_sigil1_pos_y              \"0\"\nhud_sigil1_scale              \"1\"\nhud_sigil1_show               \"0\"\nhud_sigil1_style              \"0\"\nhud_sigil2_align_x            \"after\"\nhud_sigil2_align_y            \"top\"\nhud_sigil2_frame              \"0\"\nhud_sigil2_frame_color        \"0 0 0\"\nhud_sigil2_order              \"1\"\nhud_sigil2_place              \"sigil1\"\nhud_sigil2_pos_x              \"0\"\nhud_sigil2_pos_y              \"0\"\nhud_sigil2_scale              \"1\"\nhud_sigil2_show               \"0\"\nhud_sigil2_style              \"0\"\nhud_sigil3_align_x            \"after\"\nhud_sigil3_align_y            \"top\"\nhud_sigil3_frame              \"0\"\nhud_sigil3_frame_color        \"0 0 0\"\nhud_sigil3_order              \"2\"\nhud_sigil3_place              \"sigil2\"\nhud_sigil3_pos_x              \"0\"\nhud_sigil3_pos_y              \"0\"\nhud_sigil3_scale              \"1\"\nhud_sigil3_show               \"0\"\nhud_sigil3_style              \"0\"\nhud_sigil4_align_x            \"after\"\nhud_sigil4_align_y            \"top\"\nhud_sigil4_frame              \"0\"\nhud_sigil4_frame_color        \"0 0 0\"\nhud_sigil4_order              \"3\"\nhud_sigil4_place              \"sigil3\"\nhud_sigil4_pos_x              \"0\"\nhud_sigil4_pos_y              \"0\"\nhud_sigil4_scale              \"1\"\nhud_sigil4_show               \"0\"\nhud_sigil4_style              \"0\"\nhud_speed2_align_x            \"center\"\nhud_speed2_align_y            \"bottom\"\nhud_speed2_color_fast         \"72\"\nhud_speed2_color_fastest      \"216\"\nhud_speed2_color_insane       \"229\"\nhud_speed2_color_normal       \"100\"\nhud_speed2_color_stopped      \"52\"\nhud_speed2_frame              \"0\"\nhud_speed2_frame_color        \"0 0 0\"\nhud_speed2_opacity            \"1.0\"\nhud_speed2_order              \"7\"\nhud_speed2_orientation        \"0\"\nhud_speed2_place              \"top\"\nhud_speed2_pos_x              \"0\"\nhud_speed2_pos_y              \"0\"\nhud_speed2_radius             \"50.0\"\nhud_speed2_show               \"0\"\nhud_speed2_wrapspeed          \"500\"\nhud_speed2_xyz                \"0\"\nhud_speed_align_x             \"center\"\nhud_speed_align_y             \"after\"\nhud_speed_color_fast          \"72\"\nhud_speed_color_fastest       \"216\"\nhud_speed_color_insane        \"229\"\nhud_speed_color_normal        \"100\"\nhud_speed_color_stopped       \"52\"\nhud_speed_frame               \"0.5\"\nhud_speed_frame_color         \"0 0 0\"\nhud_speed_height              \"15\"\nhud_speed_opacity             \"1.0\"\nhud_speed_order               \"7\"\nhud_speed_place               \"group4\"\nhud_speed_pos_x               \"0\"\nhud_speed_pos_y               \"-79\"\nhud_speed_show                \"0\"\nhud_speed_text_align          \"1\"\nhud_speed_tick_spacing        \"0.2\"\nhud_speed_vertical            \"0\"\nhud_speed_vertical_text       \"1\"\nhud_speed_width               \"160\"\nhud_speed_xyz                 \"1\"\nhud_suit_align_x              \"after\"\nhud_suit_align_y              \"top\"\nhud_suit_frame                \"0\"\nhud_suit_frame_color          \"0 0 0\"\nhud_suit_order                \"3\"\nhud_suit_place                \"pent\"\nhud_suit_pos_x                \"0\"\nhud_suit_pos_y                \"0\"\nhud_suit_scale                \"1\"\nhud_suit_show                 \"0\"\nhud_suit_style                \"0\"\nhud_teamfrags_align_x         \"right\"\nhud_teamfrags_align_y         \"top\"\nhud_teamfrags_bignum          \"1\"\nhud_teamfrags_cell_height     \"24\"\nhud_teamfrags_cell_width      \"86\"\nhud_teamfrags_colors_alpha    \"0.3\"\nhud_teamfrags_cols            \"2\"\nhud_teamfrags_extra_spec_info \"0\"\nhud_teamfrags_fliptext        \"1\"\nhud_teamfrags_frame           \"0\"\nhud_teamfrags_frame_color     \"0 0 0\"\nhud_teamfrags_maxname         \"16\"\nhud_teamfrags_onlytp          \"1\"\nhud_teamfrags_order           \"2\"\nhud_teamfrags_padtext         \"0\"\nhud_teamfrags_place           \"frags\"\nhud_teamfrags_pos_x           \"0\"\nhud_teamfrags_pos_y           \"-24\"\nhud_teamfrags_rows            \"1\"\nhud_teamfrags_show            \"1\"\nhud_teamfrags_shownames       \"0\"\nhud_teamfrags_space_x         \"0\"\nhud_teamfrags_space_y         \"1\"\nhud_teamfrags_strip           \"1\"\nhud_teamfrags_style           \"8\"\nhud_teamfrags_vertical        \"0\"\nhud_teamholdbar_align_x       \"left\"\nhud_teamholdbar_align_y       \"bottom\"\nhud_teamholdbar_frame         \"0\"\nhud_teamholdbar_frame_color   \"0 0 0\"\nhud_teamholdbar_height        \"8\"\nhud_teamholdbar_onlytp        \"0\"\nhud_teamholdbar_opacity       \"0.8\"\nhud_teamholdbar_order         \"0\"\nhud_teamholdbar_place         \"top\"\nhud_teamholdbar_pos_x         \"0\"\nhud_teamholdbar_pos_y         \"0\"\nhud_teamholdbar_show          \"0\"\nhud_teamholdbar_show_text     \"1\"\nhud_teamholdbar_vertical      \"0\"\nhud_teamholdbar_vertical_text \"0\"\nhud_teamholdbar_width         \"200\"\nhud_teamholdinfo_align_x      \"left\"\nhud_teamholdinfo_align_y      \"bottom\"\nhud_teamholdinfo_frame        \"0\"\nhud_teamholdinfo_frame_color  \"0 0 0\"\nhud_teamholdinfo_height       \"8\"\nhud_teamholdinfo_itemfilter   \"quad ra ya ga mega pent rl quad\"\nhud_teamholdinfo_onlytp       \"0\"\nhud_teamholdinfo_opacity      \"0.8\"\nhud_teamholdinfo_order        \"0\"\nhud_teamholdinfo_place        \"top\"\nhud_teamholdinfo_pos_x        \"0\"\nhud_teamholdinfo_pos_y        \"0\"\nhud_teamholdinfo_show         \"0\"\nhud_teamholdinfo_style        \"1\"\nhud_teamholdinfo_width        \"200\"\nhud_tp_need                   \"0\"\nhud_tracking_align_x          \"center\"\nhud_tracking_align_y          \"bottom\"\nhud_tracking_format           \"pov: %t %n\"\nhud_tracking_frame            \"0\"\nhud_tracking_frame_color      \"0 0 0\"\nhud_tracking_order            \"10\"\nhud_tracking_place            \"screen\"\nhud_tracking_pos_x            \"0\"\nhud_tracking_pos_y            \"0\"\nhud_tracking_scale            \"1\"\nhud_tracking_show             \"1\"\nhud_recalculate\n"
  },
  {
    "path": "misc/cfg/hud_vleesh.cfg",
    "content": "echo Vleesh's Head Up Display Configuration\necho made for all console resolutions\n\nscr_newhud                  \"1\"\nhud_ammo1_align             \"right\"\nhud_ammo1_align_x           \"left\"\nhud_ammo1_align_y           \"top\"\nhud_ammo1_digits            \"3\"\nhud_ammo1_frame             \"0\"\nhud_ammo1_place             \"group2\"\nhud_ammo1_pos_x             \"5\"\nhud_ammo1_pos_y             \"45\"\nhud_ammo1_scale             \"1\"\nhud_ammo1_show              \"1\"\nhud_ammo1_style             \"1\"\nhud_ammo2_align             \"right\"\nhud_ammo2_align_x           \"left\"\nhud_ammo2_align_y           \"top\"\nhud_ammo2_digits            \"3\"\nhud_ammo2_frame             \"0\"\nhud_ammo2_place             \"group2\"\nhud_ammo2_pos_x             \"35\"\nhud_ammo2_pos_y             \"45\"\nhud_ammo2_scale             \"1\"\nhud_ammo2_show              \"1\"\nhud_ammo2_style             \"1\"\nhud_ammo3_align             \"right\"\nhud_ammo3_align_x           \"left\"\nhud_ammo3_align_y           \"top\"\nhud_ammo3_digits            \"3\"\nhud_ammo3_frame             \"0\"\nhud_ammo3_place             \"group2\"\nhud_ammo3_pos_x             \"65\"\nhud_ammo3_pos_y             \"45\"\nhud_ammo3_scale             \"1\"\nhud_ammo3_show              \"1\"\nhud_ammo3_style             \"1\"\nhud_ammo4_align             \"right\"\nhud_ammo4_align_x           \"left\"\nhud_ammo4_align_y           \"top\"\nhud_ammo4_digits            \"3\"\nhud_ammo4_frame             \"0\"\nhud_ammo4_place             \"group2\"\nhud_ammo4_pos_x             \"95\"\nhud_ammo4_pos_y             \"45\"\nhud_ammo4_scale             \"1\"\nhud_ammo4_show              \"1\"\nhud_ammo4_style             \"1\"\nhud_ammo_align              \"right\"\nhud_ammo_align_x            \"left\"\nhud_ammo_align_y            \"top\"\nhud_ammo_digits             \"3\"\nhud_ammo_frame              \"0\"\nhud_ammo_place              \"screen\"\nhud_ammo_pos_x              \"0\"\nhud_ammo_pos_y              \"0\"\nhud_ammo_scale              \"1\"\nhud_ammo_show               \"0\"\nhud_ammo_style              \"0\"\nhud_armor_align             \"right\"\nhud_armor_align_x           \"left\"\nhud_armor_align_y           \"top\"\nhud_armor_digits            \"3\"\nhud_armor_frame             \"0\"\nhud_armor_place             \"group1\"\nhud_armor_pos_x             \"18\"\nhud_armor_pos_y             \"9\"\nhud_armor_scale             \".6\"\nhud_armor_show              \"1\"\nhud_armor_style             \"0\"\nhud_armordamage_align       \"right\"\nhud_armordamage_align_x     \"left\"\nhud_armordamage_align_y     \"before\"\nhud_armordamage_digits      \"3\"\nhud_armordamage_duration    \"0.8\"\nhud_armordamage_frame       \"0\"\nhud_armordamage_place       \"armor\"\nhud_armordamage_pos_x       \"0\"\nhud_armordamage_pos_y       \"0\"\nhud_armordamage_scale       \"1\"\nhud_armordamage_show        \"0\"\nhud_armordamage_style       \"0\"\nhud_clock_align_x           \"right\"\nhud_clock_align_y           \"console\"\nhud_clock_big               \"1\"\nhud_clock_blink             \"1\"\nhud_clock_frame             \"0\"\nhud_clock_place             \"top\"\nhud_clock_pos_x             \"0\"\nhud_clock_pos_y             \"0\"\nhud_clock_show              \"0\"\nhud_clock_style             \"0\"\nhud_face_align_x            \"left\"\nhud_face_align_y            \"top\"\nhud_face_frame              \"0\"\nhud_face_place              \"group1\"\nhud_face_pos_x              \"65\"\nhud_face_pos_y              \"2\"\nhud_face_scale              \"1.2\"\nhud_face_show               \"1\"\nhud_fps_align_x             \"right\"\nhud_fps_align_y             \"top\"\nhud_fps_frame               \"0\"\nhud_fps_place               \"screen\"\nhud_fps_pos_x               \"0\"\nhud_fps_pos_y               \"0\"\nhud_fps_show                \"0\"\nhud_fps_show_min            \"0\"\nhud_fps_title               \"1\"\nhud_frags_align_x           \"left\"\nhud_frags_align_y           \"top\"\nhud_frags_cell_height       \"8\"\nhud_frags_cell_width        \"32\"\nhud_frags_cols              \"4\"\nhud_frags_fliptext          \"0\"\nhud_frags_frame             \"0\"\nhud_frags_padtext           \"1\"\nhud_frags_place             \"screen\"\nhud_frags_pos_x             \"0\"\nhud_frags_pos_y             \"0\"\nhud_frags_rows              \"1\"\nhud_frags_show              \"0\"\nhud_frags_shownames         \"0\"\nhud_frags_showself_always   \"1\"\nhud_frags_showteams         \"0\"\nhud_frags_space_x           \"1\"\nhud_frags_space_y           \"1\"\nhud_frags_strip             \"1\"\nhud_frags_style             \"0\"\nhud_frags_teamsort          \"0\"\nhud_frags_vertical          \"0\"\nhud_group1_align_x          \"left\"\nhud_group1_align_y          \"bottom\"\nhud_group1_alpha            \"1\"\nhud_group1_frame            \".5\"\nhud_group1_height           \"58\"\nhud_group1_name             \"group1\"\nhud_group1_picture          \"\"\nhud_group1_place            \"screen\"\nhud_group1_pos_x            \"0\"\nhud_group1_pos_y            \"-8\"\nhud_group1_show             \"1\"\nhud_group1_tile             \"0\"\nhud_group1_width            \"128\"\nhud_group2_align_x          \"right\"\nhud_group2_align_y          \"bottom\"\nhud_group2_alpha            \"1\"\nhud_group2_frame            \".5\"\nhud_group2_height           \"58\"\nhud_group2_name             \"group2\"\nhud_group2_picture          \"\"\nhud_group2_place            \"screen\"\nhud_group2_pos_x            \"0\"\nhud_group2_pos_y            \"-8\"\nhud_group2_show             \"1\"\nhud_group2_tile             \"0\"\nhud_group2_width            \"128\"\nhud_group3_align_x          \"right\"\nhud_group3_align_y          \"center\"\nhud_group3_alpha            \"1\"\nhud_group3_frame            \"1\"\nhud_group3_height           \"32\"\nhud_group3_name             \"group3\"\nhud_group3_picture          \"\"\nhud_group3_place            \"group1\"\nhud_group3_pos_x            \"0\"\nhud_group3_pos_y            \"0\"\nhud_group3_show             \"1\"\nhud_group3_tile             \"0\"\nhud_group3_width            \"2\"\nhud_group4_align_x          \"left\"\nhud_group4_align_y          \"top\"\nhud_group4_alpha            \"1\"\nhud_group4_frame            \"1\"\nhud_group4_height           \"2\"\nhud_group4_name             \"group4\"\nhud_group4_picture          \"\"\nhud_group4_place            \"group1\"\nhud_group4_pos_x            \"0\"\nhud_group4_pos_y            \"0\"\nhud_group4_show             \"1\"\nhud_group4_tile             \"0\"\nhud_group4_width            \"128\"\nhud_group5_align_x          \"left\"\nhud_group5_align_y          \"bottom\"\nhud_group5_alpha            \"1\"\nhud_group5_frame            \"1\"\nhud_group5_height           \"2\"\nhud_group5_name             \"group5\"\nhud_group5_picture          \"\"\nhud_group5_place            \"group1\"\nhud_group5_pos_x            \"0\"\nhud_group5_pos_y            \"0\"\nhud_group5_show             \"1\"\nhud_group5_tile             \"0\"\nhud_group5_width            \"128\"\nhud_group6_align_x          \"right\"\nhud_group6_align_y          \"center\"\nhud_group6_alpha            \"1\"\nhud_group6_frame            \"1\"\nhud_group6_height           \"2\"\nhud_group6_name             \"group6\"\nhud_group6_picture          \"\"\nhud_group6_place            \"screen\"\nhud_group6_pos_x            \"0\"\nhud_group6_pos_y            \"0\"\nhud_group6_show             \"0\"\nhud_group6_tile             \"0\"\nhud_group6_width            \"128\"\nhud_group7_align_x          \"left\"\nhud_group7_align_y          \"center\"\nhud_group7_alpha            \"1\"\nhud_group7_frame            \"1\"\nhud_group7_height           \"58\"\nhud_group7_name             \"group7\"\nhud_group7_picture          \"\"\nhud_group7_place            \"group2\"\nhud_group7_pos_x            \"0\"\nhud_group7_pos_y            \"0\"\nhud_group7_show             \"1\"\nhud_group7_tile             \"0\"\nhud_group7_width            \"2\"\nhud_group8_align_x          \"left\"\nhud_group8_align_y          \"bottom\"\nhud_group8_alpha            \"1\"\nhud_group8_frame            \"1\"\nhud_group8_height           \"2\"\nhud_group8_name             \"group8\"\nhud_group8_picture          \"\"\nhud_group8_place            \"group2\"\nhud_group8_pos_x            \"0\"\nhud_group8_pos_y            \"0\"\nhud_group8_show             \"1\"\nhud_group8_tile             \"0\"\nhud_group8_width            \"128\"\nhud_group9_align_x          \"left\"\nhud_group9_align_y          \"top\"\nhud_group9_alpha            \"1\"\nhud_group9_frame            \"1\"\nhud_group9_height           \"2\"\nhud_group9_name             \"group9\"\nhud_group9_picture          \"\"\nhud_group9_place            \"group2\"\nhud_group9_pos_x            \"0\"\nhud_group9_pos_y            \"0\"\nhud_group9_show             \"1\"\nhud_group9_tile             \"0\"\nhud_group9_width            \"128\"\nhud_gun2_align_x            \"left\"\nhud_gun2_align_y            \"center\"\nhud_gun2_frame              \"0\"\nhud_gun2_place              \"group2\"\nhud_gun2_pos_x              \"5\"\nhud_gun2_pos_y              \"5\"\nhud_gun2_scale              \"1\"\nhud_gun2_show               \"1\"\nhud_gun2_style              \"0\"\nhud_gun3_align_x            \"left\"\nhud_gun3_align_y            \"center\"\nhud_gun3_frame              \"0\"\nhud_gun3_place              \"group2\"\nhud_gun3_pos_x              \"5\"\nhud_gun3_pos_y              \"-15\"\nhud_gun3_scale              \"1\"\nhud_gun3_show               \"1\"\nhud_gun3_style              \"0\"\nhud_gun4_align_x            \"left\"\nhud_gun4_align_y            \"center\"\nhud_gun4_frame              \"0\"\nhud_gun4_place              \"group2\"\nhud_gun4_pos_x              \"35\"\nhud_gun4_pos_y              \"5\"\nhud_gun4_scale              \"1\"\nhud_gun4_show               \"1\"\nhud_gun4_style              \"0\"\nhud_gun5_align_x            \"left\"\nhud_gun5_align_y            \"center\"\nhud_gun5_frame              \"0\"\nhud_gun5_place              \"group2\"\nhud_gun5_pos_x              \"35\"\nhud_gun5_pos_y              \"-15\"\nhud_gun5_scale              \"1\"\nhud_gun5_show               \"1\"\nhud_gun5_style              \"0\"\nhud_gun6_align_x            \"left\"\nhud_gun6_align_y            \"center\"\nhud_gun6_frame              \"0\"\nhud_gun6_place              \"group2\"\nhud_gun6_pos_x              \"65\"\nhud_gun6_pos_y              \"5\"\nhud_gun6_scale              \"1\"\nhud_gun6_show               \"1\"\nhud_gun6_style              \"0\"\nhud_gun7_align_x            \"left\"\nhud_gun7_align_y            \"center\"\nhud_gun7_frame              \"0\"\nhud_gun7_place              \"group2\"\nhud_gun7_pos_x              \"65\"\nhud_gun7_pos_y              \"-15\"\nhud_gun7_scale              \"1\"\nhud_gun7_show               \"1\"\nhud_gun7_style              \"0\"\nhud_gun8_align_x            \"left\"\nhud_gun8_align_y            \"center\"\nhud_gun8_frame              \"0\"\nhud_gun8_place              \"group2\"\nhud_gun8_pos_x              \"95\"\nhud_gun8_pos_y              \"5\"\nhud_gun8_scale              \"1\"\nhud_gun8_show               \"1\"\nhud_gun8_style              \"0\"\nhud_gun8_wide               \"0\"\nhud_gun_align_x             \"center\"\nhud_gun_align_y             \"bottom\"\nhud_gun_frame               \"0\"\nhud_gun_place               \"top\"\nhud_gun_pos_x               \"0\"\nhud_gun_pos_y               \"0\"\nhud_gun_scale               \"1\"\nhud_gun_show                \"0\"\nhud_gun_style               \"0\"\nhud_gun_wide                \"0\"\nhud_health_align            \"right\"\nhud_health_align_x          \"left\"\nhud_health_align_y          \"top\"\nhud_health_digits           \"3\"\nhud_health_frame            \"0\"\nhud_health_place            \"group1\"\nhud_health_pos_x            \"80\"\nhud_health_pos_y            \"9\"\nhud_health_scale            \".6\"\nhud_health_show             \"1\"\nhud_health_style            \"0\"\nhud_healthdamage_align      \"right\"\nhud_healthdamage_align_x    \"left\"\nhud_healthdamage_align_y    \"before\"\nhud_healthdamage_digits     \"3\"\nhud_healthdamage_duration   \"0.8\"\nhud_healthdamage_frame      \"0\"\nhud_healthdamage_place      \"health\"\nhud_healthdamage_pos_x      \"0\"\nhud_healthdamage_pos_y      \"0\"\nhud_healthdamage_scale      \"1\"\nhud_healthdamage_show       \"0\"\nhud_healthdamage_style      \"0\"\nhud_iammo1_align_x          \"left\"\nhud_iammo1_align_y          \"top\"\nhud_iammo1_frame            \"0\"\nhud_iammo1_place            \"group2\"\nhud_iammo1_pos_x            \"0\"\nhud_iammo1_pos_y            \"0\"\nhud_iammo1_scale            \".5\"\nhud_iammo1_show             \"0\"\nhud_iammo1_style            \"0\"\nhud_iammo2_align_x          \"left\"\nhud_iammo2_align_y          \"top\"\nhud_iammo2_frame            \"0\"\nhud_iammo2_place            \"group2\"\nhud_iammo2_pos_x            \"0\"\nhud_iammo2_pos_y            \"0\"\nhud_iammo2_scale            \".5\"\nhud_iammo2_show             \"0\"\nhud_iammo2_style            \"0\"\nhud_iammo3_align_x          \"left\"\nhud_iammo3_align_y          \"top\"\nhud_iammo3_frame            \"0\"\nhud_iammo3_place            \"group2\"\nhud_iammo3_pos_x            \"0\"\nhud_iammo3_pos_y            \"0\"\nhud_iammo3_scale            \".5\"\nhud_iammo3_show             \"0\"\nhud_iammo3_style            \"0\"\nhud_iammo4_align_x          \"left\"\nhud_iammo4_align_y          \"top\"\nhud_iammo4_frame            \"0\"\nhud_iammo4_place            \"group2\"\nhud_iammo4_pos_x            \"0\"\nhud_iammo4_pos_y            \"0\"\nhud_iammo4_scale            \".5\"\nhud_iammo4_show             \"0\"\nhud_iammo4_style            \"0\"\nhud_iammo_align_x           \"left\"\nhud_iammo_align_y           \"top\"\nhud_iammo_frame             \"0\"\nhud_iammo_place             \"screen\"\nhud_iammo_pos_x             \"0\"\nhud_iammo_pos_y             \"0\"\nhud_iammo_scale             \"1\"\nhud_iammo_show              \"0\"\nhud_iammo_style             \"0\"\nhud_iarmor_align_x          \"left\"\nhud_iarmor_align_y          \"top\"\nhud_iarmor_frame            \"0\"\nhud_iarmor_place            \"group1\"\nhud_iarmor_pos_x            \"3\"\nhud_iarmor_pos_y            \"1\"\nhud_iarmor_scale            \"1.2\"\nhud_iarmor_show             \"1\"\nhud_iarmor_style            \"0\"\nhud_key1_align_x            \"top\"\nhud_key1_align_y            \"left\"\nhud_key1_frame              \"0\"\nhud_key1_place              \"top\"\nhud_key1_pos_x              \"0\"\nhud_key1_pos_y              \"64\"\nhud_key1_scale              \"1\"\nhud_key1_show               \"0\"\nhud_key1_style              \"0\"\nhud_key2_align_x            \"left\"\nhud_key2_align_y            \"after\"\nhud_key2_frame              \"0\"\nhud_key2_place              \"key1\"\nhud_key2_pos_x              \"0\"\nhud_key2_pos_y              \"0\"\nhud_key2_scale              \"1\"\nhud_key2_show               \"0\"\nhud_key2_style              \"0\"\nhud_mp3_time_align_x        \"left\"\nhud_mp3_time_align_y        \"bottom\"\nhud_mp3_time_frame          \"0\"\nhud_mp3_time_on_scoreboard  \"0\"\nhud_mp3_time_place          \"top\"\nhud_mp3_time_pos_x          \"0\"\nhud_mp3_time_pos_y          \"0\"\nhud_mp3_time_show           \"0\"\nhud_mp3_time_style          \"0\"\nhud_mp3_title_align_x       \"right\"\nhud_mp3_title_align_y       \"bottom\"\nhud_mp3_title_frame         \"0\"\nhud_mp3_title_height        \"8\"\nhud_mp3_title_on_scoreboard \"0\"\nhud_mp3_title_place         \"top\"\nhud_mp3_title_pos_x         \"0\"\nhud_mp3_title_pos_y         \"0\"\nhud_mp3_title_scroll        \"1\"\nhud_mp3_title_scroll_delay  \"0.5\"\nhud_mp3_title_show          \"0\"\nhud_mp3_title_style         \"2\"\nhud_mp3_title_width         \"512\"\nhud_mp3_title_wordwrap      \"0\"\nhud_net_align_x             \"left\"\nhud_net_align_y             \"center\"\nhud_net_frame               \"0.2\"\nhud_net_period              \"1\"\nhud_net_place               \"top\"\nhud_net_pos_x               \"0\"\nhud_net_pos_y               \"0\"\nhud_net_show                \"0\"\nhud_netgraph_align_x        \"left\"\nhud_netgraph_align_y        \"bottom\"\nhud_netgraph_alpha          \"1\"\nhud_netgraph_frame          \"0\"\nhud_netgraph_full           \"0\"\nhud_netgraph_height         \"32\"\nhud_netgraph_inframes       \"0\"\nhud_netgraph_lostscale      \"1\"\nhud_netgraph_place          \"top\"\nhud_netgraph_ploss          \"1\"\nhud_netgraph_pos_x          \"0\"\nhud_netgraph_pos_y          \"0\"\nhud_netgraph_scale          \"256\"\nhud_netgraph_show           \"0\"\nhud_netgraph_swap_x         \"0\"\nhud_netgraph_swap_y         \"0\"\nhud_netgraph_width          \"256\"\nhud_pent_align_x            \"left\"\nhud_pent_align_y            \"after\"\nhud_pent_frame              \"0\"\nhud_pent_place              \"ring\"\nhud_pent_pos_x              \"0\"\nhud_pent_pos_y              \"0\"\nhud_pent_scale              \"1\"\nhud_pent_show               \"0\"\nhud_pent_style              \"0\"\nhud_ping_align_x            \"left\"\nhud_ping_align_y            \"bottom\"\nhud_ping_blink              \"0\"\nhud_ping_frame              \"0\"\nhud_ping_period             \"1\"\nhud_ping_place              \"group1\"\nhud_ping_pos_x              \"0\"\nhud_ping_pos_y              \"-6\"\nhud_ping_show               \"1\"\nhud_ping_show_dev           \"0\"\nhud_ping_show_max           \"0\"\nhud_ping_show_min           \"0\"\nhud_ping_show_pl            \"1\"\nhud_quad_align_x            \"left\"\nhud_quad_align_y            \"after\"\nhud_quad_frame              \"0\"\nhud_quad_place              \"suit\"\nhud_quad_pos_x              \"0\"\nhud_quad_pos_y              \"0\"\nhud_quad_scale              \"1\"\nhud_quad_show               \"0\"\nhud_quad_style              \"0\"\nhud_ring_align_x            \"left\"\nhud_ring_align_y            \"after\"\nhud_ring_frame              \"0\"\nhud_ring_place              \"key2\"\nhud_ring_pos_x              \"0\"\nhud_ring_pos_y              \"0\"\nhud_ring_scale              \"1\"\nhud_ring_show               \"0\"\nhud_ring_style              \"0\"\nhud_sigil1_align_x          \"left\"\nhud_sigil1_align_y          \"top\"\nhud_sigil1_frame            \"0\"\nhud_sigil1_place            \"screen\"\nhud_sigil1_pos_x            \"0\"\nhud_sigil1_pos_y            \"0\"\nhud_sigil1_scale            \"1\"\nhud_sigil1_show             \"0\"\nhud_sigil1_style            \"0\"\nhud_sigil2_align_x          \"after\"\nhud_sigil2_align_y          \"top\"\nhud_sigil2_frame            \"0\"\nhud_sigil2_place            \"sigil1\"\nhud_sigil2_pos_x            \"0\"\nhud_sigil2_pos_y            \"0\"\nhud_sigil2_scale            \"1\"\nhud_sigil2_show             \"0\"\nhud_sigil2_style            \"0\"\nhud_sigil3_align_x          \"after\"\nhud_sigil3_align_y          \"top\"\nhud_sigil3_frame            \"0\"\nhud_sigil3_place            \"sigil2\"\nhud_sigil3_pos_x            \"0\"\nhud_sigil3_pos_y            \"0\"\nhud_sigil3_scale            \"1\"\nhud_sigil3_show             \"0\"\nhud_sigil3_style            \"0\"\nhud_sigil4_align_x          \"after\"\nhud_sigil4_align_y          \"top\"\nhud_sigil4_frame            \"0\"\nhud_sigil4_place            \"sigil3\"\nhud_sigil4_pos_x            \"0\"\nhud_sigil4_pos_y            \"0\"\nhud_sigil4_scale            \"1\"\nhud_sigil4_show             \"0\"\nhud_sigil4_style            \"0\"\nhud_speed_align_x           \"center\"\nhud_speed_align_y           \"bottom\"\nhud_speed_frame             \"0\"\nhud_speed_place             \"top\"\nhud_speed_pos_x             \"0\"\nhud_speed_pos_y             \"-5\"\nhud_speed_show              \"0\"\nhud_speed_xyz               \"0\"\nhud_suit_align_x            \"left\"\nhud_suit_align_y            \"after\"\nhud_suit_frame              \"0\"\nhud_suit_place              \"pent\"\nhud_suit_pos_x              \"0\"\nhud_suit_pos_y              \"0\"\nhud_suit_scale              \"1\"\nhud_suit_show               \"0\"\nhud_suit_style              \"0\"\nhud_teamfrags_align_x       \"right\"\nhud_teamfrags_align_y       \"bottom\"\nhud_teamfrags_cell_height   \"8\"\nhud_teamfrags_cell_width    \"32\"\nhud_teamfrags_cols          \"1\"\nhud_teamfrags_fliptext      \"1\"\nhud_teamfrags_frame         \"0\"\nhud_teamfrags_padtext       \"1\"\nhud_teamfrags_place         \"group1\"\nhud_teamfrags_pos_x         \"-5\"\nhud_teamfrags_pos_y         \"-5\"\nhud_teamfrags_rows          \"2\"\nhud_teamfrags_show          \"1\"\nhud_teamfrags_shownames     \"0\"\nhud_teamfrags_space_x       \"1\"\nhud_teamfrags_space_y       \"1\"\nhud_teamfrags_strip         \"1\"\nhud_teamfrags_style         \"0\"\nhud_teamfrags_vertical      \"1\"\nhud_tracking_align_x        \"center\"\nhud_tracking_align_y        \"bottom\"\nhud_tracking_format         \"Tracking %t %n\"\nhud_tracking_frame          \"0\"\nhud_tracking_place          \"screen\"\nhud_tracking_pos_x          \"0\"\nhud_tracking_pos_y          \"-8\"\nhud_tracking_show           \"1\"\nhud_recalculate\n"
  },
  {
    "path": "misc/cfg/movement.cfg",
    "content": "//\n// --- basic settings ---\n//\n\nbind  `             \"toggleconsole\"\nbind  ~             \"toggleconsole\"\nbind  TAB           \"+showteamscores\"\nbind  SPACE         \"+jump\"\nbind  ENTER         \"messagemode\"\nbind  \\             \"messagemode2\"\nbind  d             \"+moveleft\"\nbind  f             \"+back\"\nbind  g             \"+moveright\"\nbind  r             \"+forward\"\nbind  MOUSE1        \"+imp.7\"\nbind  MOUSE2        \"+imp.6\"\nbind  MOUSE3        \"+imp.8\"\nbind  MOUSE4        \"+imp.5\"\nbind  MOUSE5        \"+imp.2\"\nbind  MWHEELUP      \"\"\nbind  MWHEELDOWN    \"\"\n\nbind  KP_SLASH      \"agree\"\nbind  KP_STAR       \"addserver $lastip\"\nbind  KP_DEL        \"toggle cl_useproxy; echo proxy usage: $cl_useproxy\"\nbind  KP_MINUS      \"break\"\nbind  KP_PLUS       \"ready\"\nbind  KP_HOME       \"track #7\"\nbind  KP_UPARROW    \"track #8\"\nbind  KP_LEFTARROW  \"track #4\"\nbind  KP_5          \"track #5\"\nbind  KP_RIGHTARROW \"track #6\"\nbind  KP_END        \"track #1\"\nbind  KP_DOWNARROW  \"track #2\"\nbind  KP_PGDN       \"track #3\"\n\nset viewmodelalpha           \"0.3\"\ncl_maxfps                    \"999\"\nfov                          \"115\"\nm_forcewheel                 \"1\"\n\n//\n// -- advanced settings --\n//\n\nserverinfo maxfps 999\nserverinfo watervis 1\n\n\n//Input - Misc\nfreelook                     \"1\"\nlookspring                   \"0\"\nlookstrafe                   \"0\"\n\n//Plus commands\n-moveup\n-movedown\n-left\n-right\n-forward\n-back\n-lookup\n-lookdown\n-strafe\n-moveleft\n-moveright\n+speed\n-attack\n-use\n-jump\n-klook\n+mlook\n-showscores\n-showteamscores\n\n//Aliases\nalias  +imp.1              \"impulse 1;wait;fire\"\nalias  -imp.1              \"-attack;impulse 1;r_drawviewmodel 0\"\nalias  +imp.2              \"impulse 2 1;fire\"\nalias  -imp.2              \"-attack;impulse 1;r_drawviewmodel 0\"\nalias  +imp.3              \"impulse 3 4 2 1;fire\"\nalias  -imp.3              \"-attack;impulse 1;r_drawviewmodel 0\"\nalias  +imp.4              \"impulse 4 2 1;fire\"\nalias  -imp.4              \"-attack;impulse 1;r_drawviewmodel 0\"\nalias  +imp.5              \"impulse 5 3 4 2 1;fire\"\nalias  -imp.5              \"-attack;impulse 1;r_drawviewmodel 0\"\nalias  +imp.6              \"impulse 6 2 1;fire\"\nalias  -imp.6              \"-attack;impulse 1;r_drawviewmodel 0\"\nalias  +imp.7              \"impulse 7 6 2 1;fire\"\nalias  -imp.7              \"-attack;impulse 1;r_drawviewmodel 0\"\nalias  +imp.8              \"impulse 8 5 3 4 2 1;fire\"\nalias  -imp.8              \"-attack;impulse 1;r_drawviewmodel 0\"\nalias  +imp.9              \"impulse 9;fire\"\nalias  -imp.9              \"-attack;r_drawviewmodel 0\"\nalias  fire                \"r_drawviewmodel $viewmodelalpha;+attack\"\nalias  respawner           \"-attack;-jump;wait;+jump;wait;-jump;impulse 1\"\n\n\nbind  KP_HOME       \"say proxy:menu back\"\nbind  KP_UPARROW    \"say proxy:menu up\"\nbind  KP_PGUP       \"say proxy:menu select\"\nbind  KP_LEFTARROW  \"say proxy:menu left\"\nbind  KP_5          \"say proxy:menu down\"\nbind  KP_RIGHTARROW \"say proxy:menu right\"\nbind  KP_PLUS       \"say proxy:menu pgdn\"\nbind  KP_DOWNARROW  \"say proxy:menu down\"\nbind  KP_INS        \"say proxy:menu\"\nbind  KP_MINUS      \"say proxy:menu pgup\"\n\nbind  PGDN          \"screenshot\"\nbind  PGUP          \"menu_slist\"\n"
  },
  {
    "path": "misc/cfg/mvdhud_1on1.cfg",
    "content": "// backup keyboard bindings\nset_bind_str mvdhuddel del\n\necho \"press $[Delete$] to remove 1on1 hud\"\n\nbind del \"bind del $mvdhuddel; mvd_autohud 0; echo hud removed - use mvd_autohud 1 to turn it back\"\n\ncl_multiview 2\ncl_mvinset 1\nscr_autoid 1\ncl_mvinsethud 1\n\nhide group1\n"
  },
  {
    "path": "misc/cfg/mvdhud_2on2.cfg",
    "content": "exec cfg/mvdhud_base_del.cfg\n\ncl_multiview 4\n\n// Default radar group\nset radar_group\t2\n\nexec cfg/mvdhud_base.cfg\nexec cfg/mvdhud_base_4.cfg\nexec cfg/mvdhud_base_other.cfg\n\nfrags cols 2\n\n//\n// ######################################## BINDS ##########################################\n//\n\nbind enter move_radar"
  },
  {
    "path": "misc/cfg/mvdhud_3on3.cfg",
    "content": "exec cfg/mvdhud_base_del.cfg\n\ncl_multiview 3\n\n// Default radar group\nset radar_group\t3\n\nexec cfg/mvdhud_base.cfg\nexec cfg/mvdhud_base_3.cfg\nexec cfg/mvdhud_base_other.cfg\n\nfrags cols 3\n\n//\n// ######################################## BINDS ##########################################\n//\n\nbind enter move_radar"
  },
  {
    "path": "misc/cfg/mvdhud_4on4.cfg",
    "content": "exec cfg/mvdhud_base_del.cfg\n\ncl_multiview 4\n\n// Default radar group is #2 (top right corner)\nset radar_group\t2\n\nexec cfg/mvdhud_base.cfg\nexec cfg/mvdhud_base_4.cfg\nexec cfg/mvdhud_base_other.cfg\n\nfrags cols 4\n\n//\n// ######################################## BINDS ##########################################\n//\n\nbind enter move_radar"
  },
  {
    "path": "misc/cfg/mvdhud_base.cfg",
    "content": "//\n// ######################################## BASIC ALIASES #########################################\n//\n\nif $cl_multiview > 0 set max_group 5 else set_calc max_group $cl_multiview + 1\n\n// Let the player move the radar around in the views.\nalias next_radar_group\t\"if $radar_group == $max_group set radar_group 1 else set_calc radar_group $radar_group + 1\"\nalias move_radar\t\"group$radar_group frame 0;next_radar_group;place radar group$radar_group\"\n\n// Remove the frame of the group the radar is in when going into free fly mode.\nalias f_freeflyspectate \"group$radar_group frame 0\"\nalias f_trackspectate\t\"if $cl_multiview > 0 group$radar_group frame 1 else group$radar_group frame 0\"\n\n//\n// ######################################## BASIC CONFIG ##########################################\n//\n\n// Track the first team to start with\ntrackteam\t\t1\n\n// Display the mini-huds gathered in the center of the screen.\ncl_mvdisplayhud\t\t4\ncl_mvhudpos\t\tgather \ncl_mvhudvertical\t1\n\n// Someone might be using scr_newhud > 1 so don't screw with their settings\nif $scr_newhud == 0 scr_newhud 1\n\n// Allow elements to be offscreen (only in GL).\nhud_offscreen\t\t0\n\n// Make sure we're not in planmode.\nhud_planmode\t\t0\n\n// Show RL's/armor/health name.\nscr_autoid\t\t4\n\n// Get rid of all hud elements.\nhide all\n"
  },
  {
    "path": "misc/cfg/mvdhud_base_2.cfg",
    "content": "//\n// ########################################### VIEW GROUPS ########################################\n//\ncl_multiview 2\n\n// Calculate the size of a view.\nset_calc halfconh $conheight / 2\n\n//\n// One HUD group for each view.\n//\n\n// Top\nshow\tgroup1\nplace\tgroup1\t\tscreen\nalign\tgroup1\t\tleft top\nmove\tgroup1\t\t0 0\ngroup1\tname\t\t\"Top\"\ngroup1\twidth\t\t$conwidth\ngroup1\theight\t\t$halfconh\ngroup1\tframe\t\t0\ngroup1\tframe_color \tblack\n\n// Bottom \nshow\tgroup2\nplace\tgroup2\t\tscreen\nalign\tgroup2\t\tleft bottom\nmove\tgroup2\t\t0 0\ngroup2\tname\t\t\"Bottom\"\ngroup2\twidth\t\t$conwidth\ngroup2\theight\t\t$halfconh\ngroup2\tframe\t\t0\ngroup2\tframe_color\tblack\n\nhide group3\nhide group4\nhide group5"
  },
  {
    "path": "misc/cfg/mvdhud_base_3.cfg",
    "content": "//\n// ########################################### VIEW GROUPS ########################################\n//\ncl_multiview 3\n\n// Calculate the size of a view.\nset_calc halfconw $conwidth / 2\nset_calc halfconh $conheight / 2\n\n//\n// One HUD group for each view.\n//\n\n// Top\nshow\tgroup1\nplace\tgroup1\t\tscreen\nalign\tgroup1\t\tleft top\nmove\tgroup1\t\t0 0\ngroup1\tname\t\t\"Top\"\ngroup1\twidth\t\t$conwidth\ngroup1\theight\t\t$halfconh\ngroup1\tframe\t\t0\ngroup1\tframe_color \tblack\n\n// Bottom left\nshow\tgroup2\nplace\tgroup2\t\tscreen\nalign\tgroup2\t\tleft bottom\nmove\tgroup2\t\t0 0\ngroup2\tname\t\t\"Bottom left\"\ngroup2\twidth\t\t$halfconw\ngroup2\theight\t\t$halfconh\ngroup2\tframe\t\t0\ngroup2\tframe_color\tblack\n\n// Bottom right\nshow\tgroup3\nplace\tgroup3\t\tscreen\nalign\tgroup3\t\tright bottom\nmove\tgroup3\t\t0 0\ngroup3\tname\t\t\"Bottom right\"\ngroup3\twidth\t\t$halfconw\ngroup3\theight\t\t$halfconh\ngroup3\tframe\t\t0\ngroup3\tframe_color\tblack\n\nhide group4\nhide group5"
  },
  {
    "path": "misc/cfg/mvdhud_base_4.cfg",
    "content": "//\n// ########################################### VIEW GROUPS ########################################\n//\n//cl_multiview 4\n\n// Calculate the size of a view.\nset_calc halfconw $conwidth / 2\nset_calc halfconh $conheight / 2\n\n//\n// One HUD group for each view.\n//\n\n// Top left\nshow group1\nplace\tgroup1\t\tscreen\nalign\tgroup1\t\tleft top\nmove\tgroup1\t\t0 0\ngroup1\tname\t\t\"Top left\"\ngroup1\twidth\t\t$halfconw\ngroup1\theight\t\t$halfconh\ngroup1\tframe\t\t0\n\n// Top right - Radar group\nshow\tgroup2\nplace\tgroup2\t\tscreen\nalign\tgroup2\t\tright top\nmove\tgroup2\t\t0 0\ngroup2\tname\t\t\"Top right radar\"\ngroup2\twidth\t\t$halfconw\ngroup2\theight\t\t$halfconh\ngroup2\tframe\t\t0\ngroup2\tframe_color \tblack\n\n// Bottom left\nshow\tgroup3\nplace\tgroup3\t\tscreen\nalign\tgroup3\t\tleft bottom\nmove\tgroup3\t\t0 0\ngroup3\tname\t\t\"Bottom left\"\ngroup3\twidth\t\t$halfconw\ngroup3\theight\t\t$halfconh\ngroup3\tframe\t\t0\ngroup3\tframe_color\tblack\n\n// Bottom right\nshow\tgroup4\nplace\tgroup4\t\tscreen\nalign\tgroup4\t\tright bottom\nmove\tgroup4\t\t0 0\ngroup4\tname\t\t\"Bottom right\"\ngroup4\twidth\t\t$halfconw\ngroup4\theight\t\t$halfconh\ngroup4\tframe\t\t0\ngroup4\tframe_color\tblack\n\nhide group5"
  },
  {
    "path": "misc/cfg/mvdhud_base_del.cfg",
    "content": "// backup keyboard bindings\nset_bind_str mvdhuddel del\n\necho \"press $[Delete$] to remove auto mvdhud\"\n\nbind del \"bind del $mvdhuddel; mvd_autohud 0; echo hud removed - use mvd_autohud 1 or 2 to turn it back on\""
  },
  {
    "path": "misc/cfg/mvdhud_base_other.cfg",
    "content": "//\n// ########################################### OTHER HUD ELEMENTS ########################################\n//\n\n//\n// Radar\n//\nset_calc radarw $halfconw - 15\nset_calc radarh $halfconh - 15\n\nshow\t\tradar\nplace\t\tradar\t\tgroup2\nalign\t\tradar\t\tcenter center\nmove\t\tradar\t\t0 0\nradar\t\tframe\t\t0\nradar\t\twidth\t\t$radarw\nradar\t\theight\t\t$radarh\nradar\t\topacity\t\t0.5\nradar\t\titemfilter\t\"quad pent armor ring backpack\"\nradar\t\tautosize\t0\nradar\t\tshow_height\t1\nradar\t\tshow_powerups\t1\nradar\t\tshow_stats\t1\nradar\t\tweaponfilter\t\"rl lg gl\"\n\n//\n// Frags\n//\nshow\t\tfrags\nplace\t\tfrags\t\tscreen\nalign\t\tfrags\t\tcenter bottom\nmove\t\tfrags\t\t0 -8\nfrags\t\tframe\t\t0.02\nfrags\t\tframe_color\twhite\nfrags\t\trows\t\t8\nfrags\t\tcols\t\t$cl_multiview\nfrags\t\tvertical\t0\nfrags\t\textra_spec_info 1\nfrags\t\tteamsort\t1\nfrags\t\tcell_width\t16\nfrags\t\tcell_height\t8\nfrags\t\tpadtext\t\t1\nfrags\t\tshownames\t1\nfrags\t\tshowteams\t0\nfrags\t\tspace_x\t\t1\nfrags\t\tspace_y\t\t1\nfrags\t\tteamsort\t1\n\n//\n// Teamfrags\n//\nshow\t\tteamfrags\nplace\t\tteamfrags\tscreen\nalign\t\tteamfrags\tcenter bottom\nmove\t\tteamfrags\t0 -35\nteamfrags\tframe\t\t0.02\nteamfrags\tframe_color\twhite\nteamfrags\trows\t\t2\nteamfrags\tcols\t\t1\nteamfrags\tfliptext\t1\nteamfrags\tcell_width\t32\nteamfrags\tcell_height\t8\nteamfrags\tpadtext\t\t1\nteamfrags\tshownames\t1\nteamfrags\textra_spec_info\t1\nteamfrags\tvertical\t0\n\n//\n// Team hold bar\n//\nshow\t\tteamholdbar\nplace\t\tteamholdbar\tscreen\nalign\t\tteamholdbar\tleft bottom\nmove\t\tteamholdbar\t0 0\nteamholdbar\twidth\t\t$conwidth\nteamholdbar\theight\t\t8\nteamholdbar\tframe\t\t0\nteamholdbar\tvertical\t0\nteamholdbar\tvertical_text\t0\nteamholdbar\tshow_text\t1\n\n//\n// Team hold info\n//\nshow\t\tteamholdinfo\nplace\t\tteamholdinfo\tgroup4\nalign\t\tteamholdinfo\tright top\nmove\t\tteamholdinfo\t0 0\nteamholdinfo\titemfilter\t\"quad rl ra ya pent\"\nteamholdinfo\theight\t\t48\nteamholdinfo\twidth\t\t100\nteamholdinfo\tstyle\t\t0\nteamholdinfo\topacity\t\t0.8\n"
  },
  {
    "path": "misc/cfg/mvdhud_custom.cfg",
    "content": "exec cfg/mvdhud_base_del.cfg\nexec cfg/mvdhud_4on4.cfg\n\n//\n// ######################################## ALIASES ##############################################\n//\n\nalias clear_groups\t\"group1 frame 0;group2 frame 0;group3 frame 0;group4 frame 0\"\nalias mv_1\t\t\"cl_multiview 0;exec cfg/mvdhud_base_4.cfg;clear_groups\"\nalias mv_2\t\t\"cl_multiview 2;exec cfg/mvdhud_base_2.cfg;clear_groups\"\nalias mv_3\t\t\"cl_multiview 3;exec cfg/mvdhud_base_3.cfg;clear_groups\"\nalias mv_4\t\t\"cl_multiview 4;exec cfg/mvdhud_base_4.cfg;clear_groups\"\nclear_groups\n//\n// ######################################## BINDS ##########################################\n//\n\nbind\tenter\tmove_radar\nbind\t1\tmv_1\nbind\t2\tmv_2\nbind\t3\tmv_3\nbind\t4\tmv_4"
  },
  {
    "path": "misc/cfg/pingdump.cfg",
    "content": "echo \"ping dump script by JohnNy_cz\"\r\n\r\nalias f_sbrefreshdone \"log pingdump;sb_pingsdump;log stop;echo ping dumping to $log_dir/pingdump.log done\"\r\nalias f_sbupdatesourcesdone \"sb_sourceunmarkall; sb_sourcemark quakeservers.net;sb_refresh\"\r\ncvar_reset_re sb_ping.*\r\n\r\necho \"Refreshing servers list, this may take up to 30 seconds...\"\r\n\r\nsb_sourceadd quakeservers.net master.quakeservers.net master\r\nsb_sourcesupdate\r\n"
  },
  {
    "path": "misc/cfg/teamplay.cfg",
    "content": "// shaman's ezQuake teamplay configuration file\n// e-mail: post at o2 dot pl\n// (C) Hugo Dworak 2000-2004\n\n// LEDs:\n// $x86 = green   - teammates, safe, u take, has, our, took, coming, camping, ok, don't kill, i take, my pack, soon, unlocked, powerup over, rl died \n// $x87 = red     - enemies, enemy powerups, death messages, negative, nmy coming from, nmy holds, breached, enemy rl, discharge warning, nmy took secret ra, lava opened, lag report\n// $x89 = blue    - pointed items that are not: powerups (except the biosuit), eyes, enemies or teammates \n// $x8b = brown   - chat, questions, pointed eyes, orders without specified location\n// $x88 = yellow  - ...everything else\n\n// Magic LEDs:\n// $ledpoint      - the type of object you point\n// $ledstatus     - related to how much do you need\n\n\n\n\n// prefix for teamplay reports\nset nick                    \"noob\"\n\n// bindings\n\nbind mwheelup       \"x_get\"\nbind mwheeldown     \"x_need\"\n\nbind 1              \"x_ringover\"\nbind 2              \"x_trick\"\nbind 3              \"x_statusra\"\nbind 4              \"x_statusquad\"\n\nbind 5              \"x_getquad\"\nbind 6              \"x_quadover\"\nbind 7              \"x_pentover\"\nbind 8              \"x_nosorry\"\n\nbind q              \"x_statusya\"\nbind w              \"x_tele\"\nbind e              \"x_coming\"\nbind i              \"x_choose_best\"\n\nbind y              \"x_report\"\nbind u              \"x_attacklost\"\nbind 9              \"x_iquad\"\nbind a              \"x_lostpack\"\n\nbind s              \"x_lost\"\nbind h              \"x_took\"\nbind j              \"x_call\"\nbind k              \"x_already\"\n\nbind z              \"x_rasecure\"\nbind x              \"x_swap\"\nbind c              \"x_safe\"\nbind v              \"x_wait\"\n\nbind b              \"x_enemy\"\nbind n              \"x_call\"\nbind capslock       \"x_cancel\"\nbind lwinkey        \"x_yasecure\"\n\nbind lalt           \"x_itemsoon\"\nbind lctrl          \"x_cantyoutake\"\nbind lshift         \"x_help\"\n\nbind rightarrow     \"x_msg1\"\nbind leftarrow      \"x_msg2\"\nbind uparrow        \"x_msg3\"\nbind downarrow      \"x_msg4\"\n\nbind f1             \"x_msg5\"\nbind f2             \"x_msg6\"\nbind f3             \"x_pent30\"\nbind f4             \"x_getpent\"\n\nbind f5             \"x_scores\"\nbind f6             \"x_itemsoon\"\nbind f7             \"x_iquad\"\nbind f8             \"x_hide\"\n\nbind f9             \"x_msg7\"\nbind f10            \"x_msg8\"\nbind f11            \"x_msg9\"\nbind f12            \"x_msg10\"\n\n// end of bindings\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n// DON'T CHANGE ANYTHING BELOW\n\n// basic variables\nset loc_name_ga             \"GA\"\nset loc_name_mh             \"MEGA\"\nset loc_name_ra             \"RA\"\nset loc_name_separator      \"-\"\nset loc_name_ya             \"YA\"\nset tp_name_ask_status      \"status?\"\nset tp_name_attack          \"attack\"\nset tp_name_get             \"get\"\nset tp_name_go_secure       \"go secure!\"\nset tp_name_now             \"NOW!\"\nset tp_name_over            \"over\"\nset tp_name_rush            \"rush - attack NOW!\"\nset tp_name_seconds         \"s\"\nset tp_need_ping            \"89\"\nset tp_waymaps              \"?dm2?dm3?dm4?dm5?dm6?e1m2?e1m6?e3m2?e3m3?e3m4?ukooldm4?\"\nset x_last_mega_location    \"\"\nset x_mega_active           \"0\"\nset x_mega_timer            \"0\"\nset x_parsing               \"1\"\nset x_funparsing            \"1\"\n\n// those variables must not be empty\nif $qt$tp_name_none$qt = $qt$qt then $qtset tp_name_none -$qt\nif $qt$tp_name_separator$qt = $qt$qt then $qtset tp_name_separator $x0f$qt\n\n// warn if prefix is default\nif $qt$nick$qt = $qtnoob$qt then echo Change your reports prefix using /set nick pfx\n\n// load location file\nloadloc $mapname\n\n// resets mega timer on map change\nalias  on_enter                          \"set x_last_mega_location $qt$qt;set x_mega_active 0\"\n\n// produces $x_lag\nalias  x_recalculate_lag                 \"set x_lag $qt$qt;if $latency > $tp_need_ping then $qtset x_lag $x20$x87$x87$x20{PING $latency}$qt\"\n\n// produces $x_bestweapon\nalias  x_recalculate_weapons_cells       \"set x_rl_lg_cells 0;if $qt$bestweapon$qt = $qt$tp_name_rl$qt then if $qt$tp_name_lg$qt isin $qt$weapons$qt then if $cells >= $tp_need_cells then set x_rl_lg_cells 1\"\nalias  x_recalculate_weapons_rl_lg       \"set x_rl_lg 0;if $qt$bestweapon$qt = $qt$tp_name_rl$qt then if $qt$tp_name_lg$qt isin $qt$weapons$qt then set x_rl_lg 1\"\nalias  x_recalculate_weapons_sg          \"set x_sg 0;if $qt$bestweapon$qt isin $qt$tp_name_none$tp_name_separator$tp_name_axe$tp_name_separator$tp_name_sg$tp_name_separator$tp_name_ng$qt then set x_sg 1\"\nalias  x_recalculate_weapons_rl          \"if $rockets = 0 then $qtset x_rl $tp_name_rl$qt else set x_rl $tp_name_rl:$rockets\"\nalias  x_recalculate_weapons_ammo        \"if $bestammo = 0 then $qtset x_bestweapon $bestweapon$qt else set x_bestweapon $bestweapon:$bestammo\"\nalias  x_recalculate_weapons_water       \"if $qt$tp_name_sng$qt isin $qt$weapons$qt then if $qt$tp_name_sng$qt != $qt$bestweapon$qt $qtset x_bestweapon $x_bestweapon$tp_name_separator$tp_name_sng:$nails;if $qt$tp_name_sng$qt !isin $qt$weapons$qt then if $qt$tp_name_ssg$qt != $qt$bestweapon$qt then if $qt$tp_name_ssg$qt isin $qt$weapons$qt then set x_bestweapon $x_bestweapon$tp_name_separator$tp_name_ssg:$shells\"\nalias  x_recalculate_weapons             \"x_recalculate_weapons_rl;x_recalculate_weapons_cells;x_recalculate_weapons_rl_lg;x_recalculate_weapons_sg;set x_bestweapon $qt$qt;if $x_rl_lg_cells = 1 then $qtset x_bestweapon $x_rl$tp_name_separator$tp_name_lg:$cells$qt else if $x_rl_lg = 1 then $qtset x_bestweapon $x_rl$tp_name_separator$tp_name_lg$qt else if $x_sg = 0 then x_recalculate_weapons_ammo;if $mapname != dm2 then if $qtwater$qt isin $qt$location$qt then x_recalculate_weapons_water;if $qt$x_bestweapon$qt != $qt$qt then set x_bestweapon $qt$x_bestweapon$x20$qt\"\n\n// produces $x_armor\nalias  x_recalculate_armor               \"set x_armor $qt$qt;if $qt$armortype$qt != $qt$tp_name_none$qt then if $armor > 0 then $qtset x_armor $armortype$armor$tp_name_separator$qt;if $qt$tp_name_pent$qt isin $qt$powerups$qt then set x_armor $qt$[{$tp_name_pent}$]$x20$qt\"\n\n// produces $x_health (white text might cover 201+ health)\nalias  x_recalculate_health              \"set x_health $qt$qt;if $health < 501 then if $qt$tp_name_pent$qt !isin $qt$powerups$qt then $qtset x_health $health$x20$qt;if $qt$armortype$qt = $qt$tp_name_none$qt then if $health < 501 then if $qt$tp_name_pent$qt !isin $qt$powerups$qt then $qtset x_health h$x_health$qt\"\n\n// produces $x_powerups (this is only for the report bind; the pent and the biosuit are not reported)\nalias  x_recalculate_powerups_ring       \"if $qt$tp_name_quad$qt isin $qt$powerups$qt then $qtset x_powerups QR$qt else $qtset x_powerups $tp_name_ring$qt\"\nalias  x_recalculate_powerups_quad       \"if $qt$tp_name_quad$qt isin $qt$powerups$qt then $qtset x_powerups $tp_name_quad$qt\"\nalias  x_recalculate_powerups            \"set x_powerups $qt$qt;if $qt$tp_name_ring$qt isin $qt$powerups$qt then x_recalculate_powerups_ring else x_recalculate_powerups_quad;if $qt$x_powerups$qt != $qt$qt then set x_powerups $qt$[{$x_powerups}$]$qt\"\n\n// produces $x_pows (the biosuit is not reported)\nalias  x_recalculate_pows_two            \"if $qt$tp_name_quad$qt isin $qt$powerups$qt then $qtset x_pows $x_powsQ$qt;if $qt$tp_name_pent$qt isin $qt$powerups$qt then $qtset x_pows $x_powsP$qt;if $qt$tp_name_ring$qt isin $qt$powerups$qt then $qtset x_pows $x_powsR$qt\"\nalias  x_recalculate_pows_short          \"if $qt$tp_name_quad$tp_name_separator$tp_name_pent$tp_name_separator$tp_name_ring$qt isin $qt$powerups$qt then $qtset x_pows QPR$qt else x_recalculate_pows_two\"\nalias  x_recalculate_pows_single         \"if $qt$tp_name_none$qt != $qt$powerups$qt then $qtset x_pows $powerups$qt\"\nalias  x_recalculate_pows                \"set x_pows $qt$qt;if $qt$tp_name_separator$qt !isin $qt$powerups$qt then x_recalculate_pows_single else x_recalculate_pows_short;if $qt$x_pows$qt != $qt$qt then set x_pows $qt$[{$x_pows}$]$qt\"\n\n// should cover more than one megahealth taken\nalias  x_mega_notify                     \"playvol boss2/idle 5;if $qt$x_last_mega_location$qt != $qt$qt then say_team $\\$nick$x89 $tp_name_mh soon {$x_last_mega_location}\"\nalias  x_mega_check                      \"if $qt$x_mega_timer$qt = $qt1$qt then x_mega_notify\"\nalias  x_mega                            \"x_mega_check;set x_mega_active 0\"\nalias  x_recalculate_mega                \"if $x_mega_active = 1 then if $health < 101 then x_mega\"\n\n// report recalculation\nalias  x_recalculate                     \"x_recalculate_mega;x_recalculate_lag;x_recalculate_weapons;x_recalculate_armor;x_recalculate_health;x_recalculate_powerups;x_recalculate_pows\"\n\n// status\nalias  x_status                          \"x_recalculate;say_team $\\$nick$ledstatus $x_armor$x_health$x_bestweapon{$location} $x_powerups$x_lag\"\n\n// lost\nalias  x_lost_empty                      \"if $qt$deathloc$qt != $qt$tp_name_someplace$qt then say_team $\\$nick$x87 {$deathloc} lost %EE\"\nalias  x_lost_backpack                   \"if $qt$deathloc$qt != $qt$tp_name_someplace$qt then say_team $\\$nick$x87 died $tp_name_at {$deathloc} with $weapon:$ammo %EE\"\nalias  x_lost                            \"x_recalculate_mega;if $qt$weapon$qt = $qt$tp_name_rl$qt then x_lost_backpack else if $qt$weapon$qt = $qt$tp_name_lg$qt then x_lost_backpack else x_lost_empty\"\n\n// status or lost\nalias  x_report                          \"if $health < 1 then x_lost else x_status\"\n\n// coming\nalias  x_comingshort                     \"say_team $\\$nick$x86 coming $x_bestweapon{$location} $x_pows\"\nalias  x_comingway                       \"say_team $\\$nick$x86 coming $x_bestweapon{$location} to {%Z} $x_pows$x20\"\nalias  x_comingwaypoints                 \"x_recalculate;if ?$mapname? !isin $qt$tp_waymaps$qt then x_comingshort else x_comingway\"\nalias  x_coming                          \"x_recalculate;x_comingshort\"\n\n// need, has rl, has powerup, where help, what need\nalias  x_needbind                        \"say_team $\\$nick$ledstatus need $need {$location}\"\nalias  x_hasweapon                       \"if $tp_name_ring isin $qt$powerups$qt then $qtsay_team $\\$nick$x86 has $[{$tp_name_ring}$] $ledstatus need $need$qt else $qtsay_team $\\$nick$x86 has $bestweapon $ledstatus need $need$qt\"\nalias  x_haspowerup1                     \"say_team $\\$nick$x86 has $x_pows $ledstatus need $tp_name_weapon\"\nalias  x_haspowerup2                     \"say_team $\\$nick$x86 has $x_pows $ledstatus need $need\"\n\nalias  x_teamneed                        \"say_team $\\$nick$x8b what do u need? REPORT!\"\nalias  x_wherehelp                       \"say_team $\\$nick$x8b where is $x_pows help needed?\"\n\nalias  x_needpent                        \"if $tp_name_weapon isin $qt$need$qt then x_haspowerup1 else x_wherehelp\"\nalias  x_needself                        \"if $tp_name_quad isin $qt$powerups$qt then x_haspowerup2 else if $tp_name_pent isin $qt$powerups$qt then x_needpent else if $tp_name_weapon !isin $qt$need$qt x_hasweapon else x_needbind\"\nalias  x_needmates                       \"if $tp_name_quad isin $qt$powerups$qt then x_wherehelp else if $tp_name_pent isin $qt$powerups$qt then x_wherehelp else x_teamneed\"\nalias  x_need_check_armor                \"if $tp_name_pent isin $qt$powerups$qt then $qtif $need = $tp_name_armor then x_needmates$qt else x_needself\"\nalias  x_need                            \"x_recalculate;if $ledstatus = $tp_name_status_green then x_needmates else x_need_check_armor\"\n\n// took, team powerup\nalias  x_team                            \"say_team $\\$nick$x86 our $powerups $ledstatus need $need\"\nalias  x_took_set                        \"set x_last_mega_location $tookloc;set x_mega_active 1\"\nalias  x_took_check                      \"if $qt$tookloc$qt != $qt$tp_name_someplace$qt then x_took_set\"\nalias  x_took_item                       \"if $qt$took$qt = $qt$tp_name_mh$qt then x_took_check;if $qt$cl_parseSay$qt = 0 then say_team $\\$nick$x86 took %i else say_team $\\$nick$x86 took $took $tp_name_at {$tookloc}\"\nalias  x_took                            \"if $qt$took$qt = $qt$tp_name_nothing$qt then else if $qt$took$qt = $qt$tp_name_quad$qt then x_team else if $qt$took$qt = $qt$tp_name_pent$qt then x_team else if $qt$took$qt = $qt$tp_name_ring$qt then x_team else x_took_item\"\n\n// KTpro's shownick\nalias  x_shownick                        \"if $ledpoint isin $qt$tp_name_status_green$tp_name_status_red$qt then shownick\"\n\n// get, get pack\nalias  x_parsing_save                    \"set x_funparsing $qt$cl_parseFunChars$qt;set x_parsing $qt$cl_parseSay$qt\"\nalias  x_parsing_restore                 \"cl_parseFunChars $qt$x_funparsing$qt;cl_parseSay $qt$x_parsing$qt\"\n\nalias  x_geteyesway                      \"say_team $\\$nick$x8b %x $tp_name_at {%y}! REPORT!\"\nalias  x_getenemybway                    \"say_team $\\$nick$x87 %x $tp_name_at {%y} %e\"\n\nalias  x_getpack                         \"say_team $\\$nick$x89 $tp_name_backpack {$location}\"\nalias  x_geteyes                         \"shownick;if ?$mapname? !isin $qt$tp_waymaps$qt then say_team $\\$nick$x8b %x $tp_name_at {%y}! REPORT!$x20 else x_getaskway\"\nalias  x_getenemyb                       \"shownick;if ?$mapname? !isin $qt$tp_waymaps$qt then say_team $\\$nick$x87 %x $tp_name_at {%y} %eE$x20 else x_getenemybway\"\nalias  x_getenemy                        \"if $tp_name_eyes isin $qt$point$qt then x_geteyes else x_getenemyb\"\n\nalias  x_powerups_red                    \"tp_name_eyes eyes;tp_name_quaded Quaded;tp_name_pented Penta\"\nalias  x_powerups_white                  \"x_powerups_red\" // \"tp_name_eyes {eyes};tp_name_quaded {Quaded};tp_name_pented {Penta}\"\nalias  x_point_live                      \"tp_point powerups armor players suit mh lg rl gl sng ssg pack rockets\"\nalias  x_point_dead                      \"tp_point powerups armor players suit mh lg rl gl sng ssg rockets\"\n\nalias  x_getteammatep                    \"wait;x_powerups_red;x_getask;x_powerups_white;shownick\"\nalias  x_getteammateb                    \"shownick;x_getask\"\nalias  x_getteammate                     \"if $qt $qt isin $qt$point$qt then x_getteammatep else x_getteammateb\"\nalias  x_getpower                        \"say_team $\\$nick$ledpoint $tp_name_get %x $tp_name_now\"\n\nalias  x_getb                            \"say_team $\\$nick$ledpoint %x $tp_name_at {%y}\"\nalias  x_getelse                         \"if $qt$point$qt = $qt$tp_name_nothing$qt then else if $qtrune$qt isin $qt$point$qt then x_getpower else x_getb\"\nalias  x_getalive                        \"if $ledpoint = $tp_name_status_yellow then x_getpower else if $ledpoint = $tp_name_status_red then x_getenemy else if $ledpoint = $tp_name_status_green then x_getteammate else x_getelse\"\nalias  x_getdeath                        \"x_point_dead;if $ledpoint = $tp_name_status_green then else x_getalive;x_point_live\"\nalias  x_get                             \"if $health > 0 then x_getalive else x_getdeath\"\n\nalias  x_getask                          \"if ?$mapname? !isin $qt$tp_waymaps$qt then say_team $\\$nick$x8b status %x $tp_name_at {%y}? else x_getaskway\"\nalias  x_getaskway                       \"say_team $\\$nick$x8b status %x $tp_name_at {%y}?\"\n\nx_powerups_white\nx_point_live\n\n// u take\nalias  x_cantyoutakeb                    \"say_team $\\$nick$x8b can't/u take {$location}\"\nalias  x_cantyoutaken                    \"say_team $\\$nick$x86 %x take {$location};shownick\"\nalias  x_cantyoutakep                    \"wait;x_powerups_red;say_team $\\$nick$x86 %x take {$location};x_powerups_white;shownick\"\nalias  x_cantyoutaket                    \"if $qt $qt isin $qt$point$qt then x_cantyoutakep else x_cantyoutaken\"\n\nalias  x_cantyoutake                     \"if $ledpoint = $tp_name_status_green then x_cantyoutaket else x_cantyoutakeb\"\n\n// enemy powerup\nalias  x_enemyl                          \"say_team $\\$nick$x87 $tp_name_enemy %q\"\nalias  x_enemyd                          \"if $qt$deathloc$qt != $qt$tp_name_someplace$qt then say_team $\\$nick$x87 killed by %q {$deathloc}\"\nalias  x_enemy                           \"if $health < 1 then x_enemyd else x_enemyl\"\n\n// go secure\nalias  x_gasecure                        \"say_team $\\$nick$x88 {$loc_name_ga} $tp_name_go_secure\"\nalias  x_megasecure                      \"say_team $\\$nick$x88 {$loc_name_mh} $tp_name_go_secure\"\nalias  x_200secure                       \"say_team $\\$nick$x88 {$loc_name_ra} $tp_name_go_secure\"\nalias  x_150secure                       \"say_team $\\$nick$x88 {$loc_name_ya} $tp_name_go_secure\"\nalias  x_telesecure                      \"say_team $\\$nick$x88 {tele} $tp_name_go_secure\"\nalias  x_yasecure                        \"if $mapname = dm2 then x_telesecure else x_150secure\"\nalias  x_rasecure                        \"if $mapname = e1m2 then x_megasecure else x_200secure\"\n\n// area status?\nalias  x_statusarmor                     \"say_team $\\$nick$x8b $tp_name_armor $tp_name_ask_status\"\nalias  x_statusra                        \"say_team $\\$nick$x8b {$loc_name_ra} $tp_name_ask_status\"\nalias  x_status150                       \"say_team $\\$nick$x8b {$loc_name_ya} $tp_name_ask_status\"\nalias  x_statustele                      \"say_team $\\$nick$x8b {tele} $tp_name_ask_status\"\nalias  x_statusya                        \"if $mapname = dm2 then x_statustele else x_status150\"\n\n// Euthanasia map binds\n\n// - dm2 enemy rl binds\nalias  x_dm2rlhigh                       \"say_team $\\$nick$x87 {high} $tp_name_enemy $tp_name_rl\"\nalias  x_dm2rlya-tele                    \"say_team $\\$nick$x87 {tele} $tp_name_enemy $tp_name_rl\"\nalias  x_dm2rllarge                      \"say_team $\\$nick$x87 {big} $tp_name_enemy $tp_name_rl\"\nalias  x_dm2rlra-mega                    \"say_team $\\$nick$x87 {$loc_name_ra$loc_name_separator$loc_name_mh} $tp_name_enemy $tp_name_rl\"\n\n// - dm3 enemy rl binds\nalias  x_dm3rlsng                        \"say_team $\\$nick$x87 {$tp_name_sng} $tp_name_enemy $tp_name_rl\"\nalias  x_dm3rlpent                       \"say_team $\\$nick$x87 {lifts/$tp_name_pent} $tp_name_enemy $tp_name_rl\"\nalias  x_dm3rlya                         \"say_team $\\$nick$x87 {$loc_name_ya} $tp_name_enemy $tp_name_rl\"\nalias  x_dm3rlra                         \"say_team $\\$nick$x87 {$loc_name_ra} $tp_name_enemy $tp_name_rl\"\n\n// - e1m2 enemy rl binds\nalias  x_e1m2rlya                        \"say_team $\\$nick$x87 {$loc_name_ya} $tp_name_enemy $tp_name_rl\"\nalias  x_e1m2rl+100                      \"say_team $\\$nick$x87 {$loc_name_mh} $tp_name_enemy $tp_name_rl\"\nalias  x_e1m2rlquad                      \"say_team $\\$nick$x87 {$tp_name_quad area} $tp_name_enemy $tp_name_rl\"\nalias  x_e1m2rlstart                     \"say_team $\\$nick$x87 {start} $tp_name_enemy $tp_name_rl\"\n\n// - dm2 attack orders\nalias  x_dm2atthigh                      \"say_team $\\$nick$x88 $tp_name_attack {high}\"\nalias  x_dm2attya-tele                   \"say_team $\\$nick$x88 $tp_name_attack {tele}\"\nalias  x_dm2attlarge                     \"say_team $\\$nick$x88 $tp_name_attack {big}\"\nalias  x_dm2attra-mega                   \"say_team $\\$nick$x88 $tp_name_attack {$loc_name_ra$loc_name_separator$loc_name_mh}\"\n\n// - dm3 attack orders\nalias  x_dm3attsng                       \"say_team $\\$nick$x88 $tp_name_attack {$tp_name_sng}\"\nalias  x_dm3attpent                      \"say_team $\\$nick$x88 $tp_name_attack {lifts/$tp_name_pent}\"\nalias  x_dm3attya                        \"say_team $\\$nick$x88 $tp_name_attack {$loc_name_ya}\"\nalias  x_dm3attra                        \"say_team $\\$nick$x88 $tp_name_attack {$loc_name_ra}\"\n\n// - e1m2 attack orders\nalias  x_e1m2attya                       \"say_team $\\$nick$x88 $tp_name_attack {$loc_name_ya}\"\nalias  x_e1m2att+100                     \"say_team $\\$nick$x88 $tp_name_attack {$loc_name_mh}\"\nalias  x_e1m2attquad                     \"say_team $\\$nick$x88 $tp_name_attack {$tp_name_quad}\"\nalias  x_e1m2attstart                    \"say_team $\\$nick$x88 $tp_name_attack {start}\"\n\n// - ready to bind aliases\nalias  x_msg1                            \"if $mapname = dm2 then x_dm2rlra-mega else if $mapname = e1m2 then x_e1m2rlstart else x_dm3rlsng\"\nalias  x_msg2                            \"if $mapname = dm2 then x_dm2rlya-tele else if $mapname = e1m2 then x_e1m2rlya else x_dm3rlpent\"\nalias  x_msg3                            \"if $mapname = dm2 then x_dm2rlhigh else if $mapname = e1m2 then x_e1m2rlquad else x_dm3rlra\"\nalias  x_msg4                            \"if $mapname = dm2 then x_dm2rllarge else if $mapname = e1m2 then x_e1m2rl+100 else x_dm3rlya\"\n\nalias  x_msg5                            \"if $mapname = dm2 then x_dm2secret else x_delayedpent\"\nalias  x_msg6                            \"if $mapname = dm2 then x_dm2lava else x_pent60\"\n\nalias  x_msg7                            \"if $mapname = dm2 then x_dm2attra-mega else if $mapname = e1m2 then x_e1m2attstart else x_dm3attsng\"\nalias  x_msg8                            \"if $mapname = dm2 then x_dm2attya-tele else if $mapname = e1m2 then x_e1m2attya else x_dm3attpent\"\nalias  x_msg9                            \"if $mapname = dm2 then x_dm2atthigh else if $mapname = e1m2 then x_e1m2attquad else x_dm3attra\"\nalias  x_msg10                           \"if $mapname = dm2 then x_dm2attlarge else if $mapname = e1m2 then x_e1m2att+100 else x_dm3attya\"\n\n// breached by cb2\nalias  x_tele                            \"if $qt$mapname$qt = $qtdm2$qt then x_brslipdm2 else if $qt$mapname$qt = $qtdm3$qt then x_brslipdm3 else if $qt$mapname$qt = $qte1m2$qt then x_brslipe1m2\"\nalias  x_brslipdm2                       \"if $qtbig$qt isin $qt$location$qt then x_brerlhigh else if $qt$location$qt = $qtlow$loc_name_separatorrl/stairs$qt then x_brerlbig else if $qt$location$qt = $qt$loc_name_ra$loc_name_separator$loc_name_mh$qt then x_brerlbig else if $qt$location$qt = $qtlow$loc_name_separatorrl$qt then x_brerlsecret else if $qt$location$qt = $qtlow$loc_name_separatorrl/button$qt then x_brerlbig else if $qt$location$qt = $qttele$loc_name_separatorhigh$qt then x_brerlbig else if $qt$location$qt = $qthigh$loc_name_separatorrl$qt then x_brerlbig else if $qt$location$qt = $qtfloating$qt then x_brerlbig else if $qt$location$qt = $qtgl$qt then x_brerlbig else x_breslipped\"\nalias  x_brslipdm3                       \"if $qt$loc_name_ya$qt isin $qt$location$qt then x_brerlwindow else if $qtpent$qt isin $qt$location$qt then x_brerlwindow else if $qtsng$qt isin $qt$location$qt then x_brerlra else if $qt$loc_name_ra$qt isin $qt$location$qt then x_brerlsng else if $qtwater$qt isin $qt$location$qt then x_brerlpent else if $qt$location$qt = $qtwindow$qt then x_brerlpent else if $qtrl$qt isin $qt$location$qt then x_brerlya else if $qt$location$qt = $qtring$qt then x_brerlra else if $qt$location$qt = $qtquad$qt then x_brerllifts else x_brerlsng\"\nalias  x_brslipe1m2                      \"if $qt$location$qt = $qtssg$qt then x_brerlstart else if $qtspikes$qt isin $qt$location$qt then x_brerlya else if $loc_name_ga isin $qt$location$qt then x_brerlstart else if $qtgl$qt isin $qt$location$qt then x_brerlya else if $qt$location$qt = $qt$loc_name_ya$qt then x_brerlstart else if $qt$location$qt = $qt$loc_name_ya-top$qt then x_brerlstart else if $qtrl$qt isin $qt$location$qt then x_brerlstart else if $qt$location$qt = $qtbridge$qt then x_brerlstart else x_brerlmega\"\n\nalias  x_brerlbig                        \"say_team $\\$nick$x87 {big} $tp_name_enemy rl\"\nalias  x_brerlgl                         \"say_team $\\$nick$x87 {$tp_name_gl} $tp_name_enemy rl\"\nalias  x_brerlhigh                       \"say_team $\\$nick$x87 {high} $tp_name_enemy rl\"\nalias  x_brerllifts                      \"say_team $\\$nick$x87 {lifts} $tp_name_enemy rl\"\n\nalias  x_brerlmega                       \"say_team $\\$nick$x87 {$loc_name_mh} $tp_name_enemy rl\"\nalias  x_brerlpent                       \"say_team $\\$nick$x87 {$tp_name_pent} $tp_name_enemy rl\"\nalias  x_brerlra                         \"say_team $\\$nick$x87 {$loc_name_ra} $tp_name_enemy rl\"\nalias  x_brerlratnl                      \"say_team $\\$nick$x87 {$loc_name_ra-tunnel} $tp_name_enemy rl\"\n\nalias  x_brerlsecret                     \"say_team $\\$nick$x87 {secret} $tp_name_enemy rl\"\nalias  x_brerlsng                        \"say_team $\\$nick$x87 {$tp_name_sng} $tp_name_enemy rl\"\nalias  x_brerlstart                      \"say_team $\\$nick$x87 {start/$loc_name_ga} $tp_name_enemy rl\"\nalias  x_brerlwindow                     \"say_team $\\$nick$x87 {window} $tp_name_enemy rl\"\n\nalias  x_brerlya                         \"say_team $\\$nick$x87 {$loc_name_ya} $tp_name_enemy rl\"\nalias  x_breslipped                      \"say_team $\\$nick$x87 breached {$location}\"\n\n// trick by def\nalias  x_dischargewb                     \"say_team $\\$nick$x87 {water} discharge warning! $tp_name_lg:$cells\"\nalias  x_dischargepb                     \"say_team $\\$nick$x87 {$tp_name_pent} discharge warning! $tp_name_lg:$cells\"\nalias  x_dischargew                      \"if water isin $qt$location$qt then if $cells > 6 then if $tp_name_lg isin $weapons then x_dischargewb\"\nalias  x_dischargep                      \"if water isin $qt$location$qt then if $cells > 6 then if $tp_name_lg isin $weapons then x_dischargepb\"\nalias  x_dischargeb                      \"if bridge$loc_name_separatorlow isin $qt$location$qt then if $cells > 6 then if $tp_name_lg isin $weapons then x_dischargewb\"\n\nalias  x_shortcut                        \"say_team $\\$nick$ledstatus {$tp_name_quad} shortcut\"\nalias  x_opensecret                      \"say_team $\\$nick$ledstatus open {secret}\"\nalias  x_openramega                      \"say_team $\\$nick$ledstatus open {$loc_name_ra$loc_name_separator$loc_name_mh}\"\nalias  x_opentele                        \"say_team $\\$nick$ledstatus open {tele$loc_name_separator$loc_name_ya}\"\n\nalias  x_trickdm3w                       \"if $qt$location$qt = $qtwater$qt then x_dischargew else if $qt$location$qt = $qtwater$loc_name_separatorammo$qt then x_dischargew else if $qt$location$qt = $qtwater$loc_name_separatorgl$qt then x_dischargew else if $qt$location$qt = $qtwater$loc_name_separatorlg$qt then x_dischargew else if $qt$location$qt = $qtwater$loc_name_separatortunnel$qt then x_dischargep else if $qt$location$qt = $qtpent$loc_name_separatorwater$qt then x_dischargep\"\nalias  x_trickdm2                        \"if quad isin $qt$location$qt then x_shortcut else if $qt$location$qt = $qtwater$loc_name_separatorstairs$qt then x_opensecret else if $qt$location$qt = $qtwater$loc_name_separatorng$qt then x_opensecret else if $qt$location$qt = $qtwater$loc_name_separatormid$qt then x_opensecret else if $qt$location$qt = $qtsecret$qt then x_opensecret else if $qt$location$qt = $qtwater$qt then x_opensecret else if $qt$location$qt = $qtlow$loc_name_separatorrl/button$qt then x_openramega else if $qt$location$qt = $qttele$loc_name_separator$loc_name_ya$qt then x_opentele else if $qt$location$qt = $qttele$loc_name_separatorstairs$qt then x_opentele else if $qt$location$qt = $qttele$loc_name_separatorrox$qt then x_opentele else if $qt$location$qt = $qttele$qt then x_opentele else x_trickb\"\nalias  x_trickdm3                        \"if water isin $qt$location$qt then x_trickdm3w else if $qt$location$qt = bridge$loc_name_separatorlow then x_dischargeb else x_trickb\"\nalias  x_trickb                          \"if $qt$mapname$qt != $qtdm2$qt then x_tricka else if ?path? = $qt?$location?$qt then x_opensecret else x_tricka\"\nalias  x_tricka                          \"say_team $\\$nick$ledstatus {$location} trick\"\n\nalias  x_trick                           \"if $qt$mapname$qt = $qtdm2$qt then x_trickdm2 else if $qt$mapname$qt = $qtdm3$qt then x_trickdm3 else x_trickb\"\n\n// powerups\n\n// - powerup over\nalias  x_quadover                        \"say_team $\\$nick$x86 $tp_name_quad $tp_name_over\"\nalias  x_pentover                        \"say_team $\\$nick$x86 $tp_name_pent $tp_name_over\"\nalias  x_ringover                        \"say_team $\\$nick$x86 $tp_name_ring $tp_name_over\"\nalias  x_suitover                        \"say_team $\\$nick$x86 $tp_name_suit $tp_name_over\"\n\n// - powerup status?\nalias  x_statusquad                      \"say_team $\\$nick$x8b $tp_name_quad $tp_name_ask_status\"\nalias  x_statuspent                      \"say_team $\\$nick$x8b $tp_name_pent $tp_name_ask_status\"\nalias  x_statusring                      \"say_team $\\$nick$x8b $tp_name_ring $tp_name_ask_status\"\nalias  x_statussuit                      \"say_team $\\$nick$x8b $tp_name_suit $tp_name_ask_status\"\n\n// - get a powerup\nalias  x_getquad                         \"say_team $\\$nick$x88 $tp_name_get $tp_name_quad\"\nalias  x_getpentb                        \"say_team $\\$nick$x88 $tp_name_get $tp_name_pent\"\nalias  x_getring                         \"say_team $\\$nick$x88 $tp_name_get $tp_name_ring\"\nalias  x_getsuit                         \"say_team $\\$nick$x88 $tp_name_get $tp_name_suit\"\n\nalias  x_getpent                         \"if $tp_name_pent isin $qt$powerups$qt x_getring else x_getpentb\"\n\n// - quad/pent timing\nalias  x_quad60                          \"say_team $\\$nick$x88 $tp_name_quad 60$tp_name_seconds\"\nalias  x_quad50                          \"say_team $\\$nick$x88 $tp_name_quad 50$tp_name_seconds\"\nalias  x_quad40                          \"say_team $\\$nick$x88 $tp_name_quad 40$tp_name_seconds\"\nalias  x_quad30                          \"say_team $\\$nick$x88 $tp_name_quad 30$tp_name_seconds\"\n\nalias  x_quad20                          \"say_team $\\$nick$x88 $tp_name_quad 20$tp_name_seconds\"\nalias  x_quad10                          \"say_team $\\$nick$x88 $tp_name_quad 10$tp_name_seconds\"\nalias  x_quad5                           \"say_team $\\$nick$x88 $tp_name_quad 5$tp_name_seconds\"\nalias  x_quadnow                         \"say_team $\\$nick$x88 $tp_name_get $tp_name_quad $tp_name_now\"\n\nalias  x_pent60                          \"say_team $\\$nick$x88 $tp_name_pent 60$tp_name_seconds\"\nalias  x_pent50                          \"say_team $\\$nick$x88 $tp_name_pent 50$tp_name_seconds\"\nalias  x_pent40                          \"say_team $\\$nick$x88 $tp_name_pent 40$tp_name_seconds\"\nalias  x_pent30                          \"say_team $\\$nick$x88 $tp_name_pent 30$tp_name_seconds\"\n\nalias  x_pent20                          \"say_team $\\$nick$x88 $tp_name_pent 20$tp_name_seconds\"\nalias  x_pent10                          \"say_team $\\$nick$x88 $tp_name_pent 10$tp_name_seconds\"\nalias  x_pent5                           \"say_team $\\$nick$x88 $tp_name_pent 5$tp_name_seconds\"\nalias  x_pentnow                         \"say_team $\\$nick$x88 $tp_name_get $tp_name_pent $tp_name_now\"\n\n// help, group\nalias  x_helpb                           \"say_team $\\$nick$x88 help {$location} %eE\"\nalias  x_group                           \"say_team $\\$nick$x88 group {$location}\"\nalias  x_help                            \"if $ledstatus != $tp_name_status_green then x_helpb else x_group\"\n\n// lost backpack, attack lost, nmy holds\nalias  x_lostpack                        \"if $qt$deathloc$qt != $qt$tp_name_someplace$qt then say_team $\\$nick$x87 $tp_name_backpack lost $tp_name_at {$deathloc} %EE\"\nalias  x_attacklost                      \"if $qt$deathloc$qt != $qt$tp_name_someplace$qt then say_team $\\$nick$x87 attack {$deathloc}\"\nalias  x_holds                           \"say_team $\\$nick$x87 nmy holds {$location} %eE\"\n\n// group at pent, don't kill eyes, delayed pent, delayed ring\nalias  x_grouppent                       \"say_team $\\$nick$x88 group at {$tp_name_pent}\"\nalias  x_dontkill                        \"say_team $\\$nick$x86 DON'T KILL $tp_name_eyes\"\nalias  x_delayedpent                     \"say_team $\\$nick$x88 $tp_name_pent delayed\"\nalias  x_delayedring                     \"say_team $\\$nick$x88 $tp_name_ring delayed\"\n\n// chat\nalias  x_false                           \"say_team $\\$nick$x8b PUBLIC/VOICE MSG is FALSE!\"\nalias  x_cancel                          \"say_team $\\$nick$x8b cancel that\"\nalias  x_nosorry                         \"say_team $\\$nick$x8b no/sorry\"\nalias  x_yesok                           \"say_team $\\$nick$x86 yes/ok\"\n\n// self actions: i take, coming, enemy coming, attacking, awaiting, safe, camping\nalias  x_iquad                           \"x_recalculate;say_team $\\$nick$x86 i take $tp_name_quad $x_bestweapon{$location} $x_pows\"\nalias  x_itakemypack                     \"x_recalculate;say_team $\\$nick$x86 i take/my $tp_name_backpack $x_bestweapon{$location} $x_pows\"\nalias  x_already                         \"say_team $\\$nick$x87 {$location} already guarded, bugger off!\"\n\nalias  x_nmycoming                       \"if ?$mapname? isin $qt$tp_waymaps$qt then say_team $\\$nick$x87 $tp_name_enemy coming from {%z}$x20\"\nalias  x_attacking                       \"if ?$mapname? !isin $qt$tp_waymaps$qt then say_team $\\$nick$x88 attacking from {$location} else say_team $\\$nick$x87 attacking {%Z} from {$location}$x20\"\n\nalias  x_wait                            \"x_recalculate;say_team $\\$nick$x88 awaiting $x_bestweapon{$location} $x_pows\"\nalias  x_safe                            \"x_recalculate;say_team $\\$nick$x86 safe $x_bestweapon{$location} $x_pows\"\nalias  x_camping                         \"x_recalculate;say_team $\\$nick$x86 camping $x_bestweapon{$location} $x_pows\"\n\n// orders: call positions, hide, attack lost, swap, kill\nalias  x_reportdeath                     \"say_team $\\$nick$x8b report death location!\"\nalias  x_call                            \"say_team $\\$nick$x8b call your positions!\"\nalias  x_hide                            \"say_team $\\$nick$x88 hide/spawn slowly!\"\n\nalias  x_swap                            \"say_team $\\$nick$x88 swap {$location} %eE\"\n\nalias  x_choose_best                     \"impulse 7 8 5 6 4 3 2\"\nalias  x_killme                          \"x_choose_best;say_team $\\$nick$x86 kill me! $weapon {$location}\"\nalias  x_killyourself                    \"say_team $\\$nick$x88 kill yourself! {$location}\"\n\n// item soon, unlocked, KT' scores\nalias  x_itemsoon                        \"say_team $\\$nick$x86 soon {$location}\"\nalias  x_unlocked                        \"say_team $\\$nick$x86 unlocked {$location}\"\nalias  x_scores                          \"scores\"\n\n// dm2 specific\nalias  x_dm2lava                         \"if $mapname = dm2 then say_team $\\$nick$x87 lava in {bigroom}\"\nalias  x_dm2secret                       \"if $mapname = dm2 then say_team $\\$nick$x87 $tp_name_enemy took {secret $loc_name_ra}\"\n\n// cooperative bindings\nalias  x_tooksilver                      \"say_team $\\$nick$x86 took silver key $tp_name_at {$location}\"\nalias  x_tookgolden                      \"say_team $\\$nick$x86 took gold key $tp_name_at {$location}\" \nalias  x_openedsilver                    \"say_team $\\$nick$x86 opened silver door $tp_name_at {$location}\"\nalias  x_openedgolden                    \"say_team $\\$nick$x86 opened golden door $tp_name_at {$location}\" \n\nalias  x_axetrick                        \"say_team $\\$nick$x86 {$location} $tp_name_axe trick\"\nalias  x_secret                          \"say_team $\\$nick$x86 {$location} secret triggered\"\nalias  x_tookrune                        \"say_team $\\$nick$x86 our rune\"\nalias  x_checkplayername2                \"if $qt?$pointloc?$qt != $qt?$tp_name_someplace?$qt then echo $point at $pointloc else echo $point\"\nalias  x_checkplayername                 \"if $qt$ledpoint$qt = $qt$tp_name_status_green$qt then x_checkplayername2\n\n// console aliases; not bound\nalias  sync                              \"say_team $\\$nick$x88 sync attack from {$location}\"\n\nalias  yar                               \"say_team $\\$nick$x88 {$loc_name_ya} $tp_name_rush\"\nalias  rar                               \"say_team $\\$nick$x88 {$loc_name_ra} $tp_name_rush\"\nalias  qr                                \"say_team $\\$nick$x88 {$tp_name_quad} $tp_name_rush\"\nalias  pentr                             \"say_team $\\$nick$x88 {$tp_name_pent} $tp_name_rush\"\n\nalias  erl                               \"say_team $\\$nick$x87 $tp_name_enemy $tp_name_rl!\"\nalias  edied                             \"say_team $\\$nick$x86 $tp_name_rl died\"\nalias  gr                                \"x_getring\"\nalias  ge                                \"gr\"\n\nalias  yarush                            \"yar\"\nalias  rarush                            \"rar\"\nalias  quadrush                          \"qr\"\nalias  pr                                \"pentr\"\n\nalias  prush                             \"pentr\"\nalias  pentrush                          \"pentr\"\nalias  666rush                           \"pentr\"\nalias  666r                              \"pentr\"\n\nalias  66r                               \"pentr\"\nalias  6r                                \"pentr\"\nalias  yr                                \"yar\"\nalias  rr                                \"rar\"\n\nalias  km                                \"x_killme;x_killme\"\nalias  ky                                \"x_need;x_killyourself;x_killyourself\"\nalias  fal                               \"x_false\"\n\n// end of file"
  },
  {
    "path": "misc/cfg/teamtime.cfg",
    "content": "// ***** BEGIN LICENSE BLOCK ***** \n// Version: MPL 1.1/GPL 2.0/LGPL 2.1 \n// \n// The contents of this file are subject to the Mozilla Public License Version \n// 1.1 (the \"License\"); you may not use this file except in compliance with \n// the License. You may obtain a copy of the License at \n// http://www.mozilla.org/MPL/ \n// \n// Software distributed under the License is distributed on an \"AS IS\" basis, \n// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License \n// for the specific language governing rights and limitations under the \n// License. \n// \n// The Original Code is the toekk.org QuakeWorld timer script. \n// \n// The Initial Developer of the Original Code is \n// Alexander Ahern - toekk at toekk dot org. \n// Portions created by the Initial Developer are Copyright (C) 2005 \n// the Initial Developer. All Rights Reserved. \n// \n// Contributor(s): \n// \n// Alternatively, the contents of this file may be used under the terms of \n// either the GNU General Public License Version 2 or later (the \"GPL\"), or \n// the GNU Lesser General Public License Version 2.1 or later (the \"LGPL\"), \n// in which case the provisions of the GPL or the LGPL are applicable instead \n// of those above. If you wish to allow use of your version of this file only \n// under the terms of either the GPL or the LGPL, and not to allow others to \n// use your version of this file under the terms of the MPL, indicate your \n// decision by deleting the provisions above and replace them with the notice \n// and other provisions required by the GPL or the LGPL. If you do not delete \n// the provisions above, a recipient may use your version of this file under \n// the terms of any one of the MPL, the GPL or the LGPL. \n// \n// ***** END LICENSE BLOCK ***** \n\n// $Id: teamtime.cfg,v 1.1 2006-04-20 18:49:00 johnnycz Exp $ \n\n// Bindings: \n// The variables \"__kt_X\" indicate the keys you should bind. \n// __kt_reset - resets the timer, if you make a mistake entering a time. \n// __kt_spam - tell your teammates the time on the currently selected timer. \n// __kt_nudgeup   - increment current item's time by 1. \n// __kt_nudgedown - you can guess! \n// __kt_zero/one... - typically these are the the numeric keypad. \n\n// Installation: \n// Put the timer.cfg in your quakeworld/id1 directory, and then place \n// \"exec timer.cfg\" in your config somewhere. This will bind all the \n// keys and set your timers to zero. The script will not be much use unless \n// you also set \"cl_gameclock 1\" in your config! \n\n// Usage: \n// At the moment, the timer only records quad, rl, pent, ra and ya times. \n// To enter a time, first select the item you wish to record: \n// 1: yellow armour \n// 2: red armour \n// 4: quad \n// 6: pent \n// 7: rocket launcher \n// Then type the time you took the item. As an example, suppose we collect rl \n// on 47 seconds. Type \"7\", \"4\", \"7\" and the script will report that the next \n// rl is on 17. \n// \n// (these instructions assume you have the standard numeric keypad bindings) \n\n// Customisation: \n// To change your message, the alias __kt_spam can be redefined. \n\n// Contact: \n// The author, toekk, can usually be found on QuakeNet IRC, \n// channel #knockback. \n// JohnNy_cz, 1.13->1.13j updater - #qw.cz\n\nset __kt_reset      \"kp_enter\" \nset __kt_spam      \"kp_star\" \nset __kt_nudgeup    \"kp_plus\" \nset __kt_nudgedown    \"kp_minus\" \nset __kt_zero       \"kp_0\" \nset __kt_one       \"kp_1\" \nset __kt_two       \"kp_2\" \nset __kt_three       \"kp_3\" \nset __kt_four       \"kp_4\" \nset __kt_five       \"kp_5\" \nset __kt_six       \"kp_6\" \nset __kt_seven       \"kp_7\" \nset __kt_eight       \"kp_8\" \nset __kt_nine       \"kp_9\" \n\nset __t_quad       \"00\" \nset __t_rl       \"00\" \nset __t_pent       \"00\" \nset __t_ra      \"00\" \nset __t_ya      \"00\" \n\nset __t_tens      0 \nset __t_units      0 \nset __t_item      0 \nset __t_tmp      0 \n\n// Save the temporary time __t_tmp into the correct item's time \nalias _t_save      \"if $qt,$__t_item,$qt isin $qt,quad,rl,pent,ya,ra,$qt then set __t_$__t_item $__t_tmp\" \n\n// Load the selected item's time into the temporary time __t_tmp \nalias _t_load      \"if $qt,$__t_item,$qt isin $qt,quad,rl,pent,ya,ra,$qt then _t_load$__t_item\" \nalias _t_loadquad   \"set __t_tmp $__t_quad\" \nalias _t_loadrl      \"set __t_tmp $__t_rl\" \nalias _t_loadpent   \"set __t_tmp $__t_pent\" \nalias _t_loadra      \"set __t_tmp $__t_ra\" \nalias _t_loadya      \"set __t_tmp $__t_ya\" \n\n// Show the current time as an echo \nalias _t_echo      \"echo $__t_item on $__t_tmp\" \n\n// Spam the current time \nalias _t_spam      \"say_team $\\$nick $__t_item $__t_tmp; _t_bindselect\" \n\n// Panic button to stop setting a time if you make a mistake \nalias _t_reset      \"bf; echo reset - selection mode; _t_bindselect\" \n\n// Select item to set/report it's time \nalias _ts_ya      \"echo yellow selected; _t_save; set __t_item ya; _t_load; _t_bindtens;\" \nalias _ts_ra      \"echo red selected; _t_save; set __t_item ra; _t_load; _t_bindtens;\" \nalias _ts_quad      \"echo quad selected; _t_save; set __t_item quad; _t_load; _t_bindtens\" \nalias _ts_rl      \"echo rl selected; _t_save; set __t_item rl; _t_load; _t_bindtens\" \nalias _ts_pent      \"echo pent selected; _t_save; set __t_item pent; _t_load; _t_bindtens\" \n\n// Bind all the keys to item selection \nalias _t_bindselect   \"unbind $__kt_zero; bind $__kt_one _ts_ya; bind $__kt_two _ts_ra; unbind $__kt_three; bind $__kt_four _ts_quad; unbind $__kt_five; bind $__kt_six _ts_pent; bind $__kt_seven _ts_rl; unbind $__kt_eight; unbind $__kt_nine;\" \n\n// Bind all the keys to unit selection \nalias _t_bindunits   \"bind $__kt_zero _tu_zero; bind $__kt_one _tu_one; bind $__kt_two _tu_two; bind $__kt_three _tu_three; bind $__kt_four _tu_four; bind $__kt_five _tu_five; bind $__kt_six _tu_six; bind $__kt_seven _tu_seven; bind $__kt_eight _tu_eight; bind $__kt_nine _tu_nine\" \n\n// Bind keys to tens selection \nalias _t_bindtens   \"bind $__kt_zero _tt_zero; bind $__kt_one _tt_one; bind $__kt_two _tt_two; bind $__kt_three _tt_three; bind $__kt_four _tt_four; bind $__kt_five _tt_five; unbind $__kt_six; unbind $__kt_seven; unbind $__kt_eight; unbind $__kt_nine\" \n\n// Take a weapon, this adds 30 seconds \n// this works for any gameclock settings the same - JohnNy_cz\nalias _t_weapon    \"inc __t_tmp 30; if $__t_tmp >= 60 then inc __t_tmp -60; _t_pad\" \n\n// Take an armour, this adds 20 seconds \n// <JohnNy_cz>\nalias _t_armour      \"if $cl_gameclock == 1 _t_armour_cw else if $cl_gameclock == 3 _t_armour_cw else _t_armour_ccw\"\nalias _t_armour_cw      \"inc __t_tmp 20; if $__t_tmp >= 60 then inc __t_tmp -60; _t_pad\" \nalias _t_armour_ccw      \"inc __t_tmp -20; if $__t_tmp < 0 then inc __t_tmp 60; _t_pad\"\n// </JohnNy_cz>\n\n// Pad with leading zeros \nalias _t_pad      \"if $__t_tmp < 10 then set __t_tmp 0$__t_tmp\" \n\n// Set the time \nalias _t_settime   \"set __t_tmp $qt$__t_tens$__t_units$qt; if $__t_item == rl then _t_weapon else if $qt,$__t_item,$qt isin $qt,ra,ya,$qt then _t_armour\" \nalias _t_nudgeup   \"inc __t_tmp; if $__t_tmp >= 60 then inc __t_tmp -60; _t_pad; _t_echo\" \nalias _t_nudgedown   \"inc __t_tmp -1; if $__t_tmp < 0 then inc __t_tmp 60; _t_pad; _t_echo\" \n\n// Units \nalias _tu_zero      \"set __t_units 0; _t_settime; _t_spam; _t_bindselect\" \nalias _tu_one      \"set __t_units 1; _t_settime; _t_spam; _t_bindselect\" \nalias _tu_two      \"set __t_units 2; _t_settime; _t_spam; _t_bindselect\" \nalias _tu_three      \"set __t_units 3; _t_settime; _t_spam; _t_bindselect\" \nalias _tu_four      \"set __t_units 4; _t_settime; _t_spam; _t_bindselect\" \nalias _tu_five      \"set __t_units 5; _t_settime; _t_spam; _t_bindselect\" \nalias _tu_six      \"set __t_units 6; _t_settime; _t_spam; _t_bindselect\" \nalias _tu_seven      \"set __t_units 7; _t_settime; _t_spam; _t_bindselect\" \nalias _tu_eight      \"set __t_units 8; _t_settime; _t_spam; _t_bindselect\" \nalias _tu_nine      \"set __t_units 9; _t_settime; _t_spam; _t_bindselect\" \n\n// Tens \nalias _tt_zero      \"echo 0...; set __t_tens 0; _t_bindunits\" \nalias _tt_one      \"echo 1...; set __t_tens 1; _t_bindunits\" \nalias _tt_two      \"echo 2...; set __t_tens 2; _t_bindunits\" \nalias _tt_three      \"echo 3...; set __t_tens 3; _t_bindunits\" \nalias _tt_four      \"echo 4...; set __t_tens 4; _t_bindunits\" \nalias _tt_five      \"echo 5...; set __t_tens 5; _t_bindunits\" \n\n// Setup \n\n_t_bindselect \nbind $__kt_reset    \"_t_reset\" \nbind $__kt_spam      \"_t_spam\" \nbind $__kt_nudgeup   \"_t_nudgeup\" \nbind $__kt_nudgedown   \"_t_nudgedown\" \n\necho \"timer.cfg $Revision: 1.1 $\""
  },
  {
    "path": "misc/pak.lst",
    "content": "base.pk3\r\nhelp.pk3\r\nhud.pk3\r\nprogs.pk3\r\nlocs.pk3\r\nlevelshots.pk3\r\n"
  },
  {
    "path": "misc/sb/au-sv.txt",
    "content": "159.196.248.221:27600\n103.195.53.179:28501\n103.195.53.179:28502\n103.195.53.179:28503\n103.25.59.27:28000\n103.25.59.27:28501\n103.25.59.27:28502\n103.25.59.27:28504\n182.239.198.9:27500\n103.25.59.27:30000\n103.25.59.27:27501\n103.25.59.27:27504\n103.25.59.27:27505\n103.25.59.27:27503\n103.25.59.27:27502\n103.151.65.19:27500\n103.151.65.19:30000\n16.50.174.37:27510\n3.25.174.195:27501\n3.25.174.195:27500\n3.25.174.195:27502\n3.25.174.195:30000\n3.25.174.195:27510\n3.25.174.195:27503\n159.196.248.221:27601\n210.54.39.19:28501\n115.188.3.198:27500\n103.151.65.11:28000\n103.151.65.11:30000\n103.151.65.11:28501\n103.151.65.11:28502\n163.47.21.45:28000\n103.151.65.11:28503\n103.151.65.11:28504\n103.195.53.179:28000\n223.165.64.141:27500\n172.105.184.44:27501\n203.29.243.177:28000\n203.29.243.177:30000\n203.29.243.177:28501\n124.189.251.43:27666\n124.189.251.43:28001\n124.189.251.43:28002\n124.189.251.43:28003\n124.189.251.43:28000\n124.189.251.43:30000\n163.47.21.45:30000\n103.25.59.27:28503\n"
  },
  {
    "path": "misc/sb/cache/empty",
    "content": ""
  },
  {
    "path": "misc/sb/check_sources.sh",
    "content": "for source in *.txt; do\n  cat $source | grep -v -E -e \"^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:[0-9]{5}$\" \ndone\n\n"
  },
  {
    "path": "misc/sb/ctf.txt",
    "content": "72.83.103.37:28505\n54.94.165.196:30000\n159.196.248.221:27601\n98.144.80.46:27500\n149.28.240.2:28507\n45.76.23.209:28507\n159.203.25.195:28507\n"
  },
  {
    "path": "misc/sb/eu-4on4.txt",
    "content": "144.24.188.96:28501\n89.66.241.225:28501\n209.38.242.238:28501\n128.199.52.100:28502\n128.199.52.100:28503\n128.199.52.100:28504\n194.38.96.52:28501\n194.38.96.52:28502\n194.38.96.52:28503\n194.38.96.52:28504\n80.241.209.47:28501\n185.11.166.90:28501\n185.11.166.90:28502\n185.11.166.90:28503\n139.162.245.4:28501\n217.197.83.154:28501\n217.197.83.154:28502\n217.197.83.154:28503\n217.197.83.154:28504\n108.61.178.207:27501\n108.61.178.207:27502\n108.61.178.207:27503\n108.61.178.207:27504\n108.61.178.207:27505\n108.61.178.207:27506\n108.61.178.207:27507\n108.61.178.207:28501\n108.61.178.207:28502\n108.61.178.207:28503\n108.61.178.207:29501\n82.102.20.226:27501\n82.102.20.226:27502\n82.102.20.226:27503\n82.102.20.226:27504\n82.102.20.226:27505\n82.102.20.226:28501\n82.102.20.226:28502\n82.102.20.226:28503\n82.102.20.226:28504\n82.102.20.226:28505\n185.73.44.100:28501\n185.73.44.100:28502\n185.73.44.100:28503\n185.73.44.100:28504\n185.73.44.100:28505\n185.73.44.100:28506\n163.172.90.127:28501\n163.172.90.127:28502\n163.172.90.127:28503\n163.172.90.127:28504\n195.201.133.22:27600\n195.201.133.22:27700\n195.201.133.22:27800\n145.239.54.184:28501\n35.158.239.193:28501\n35.158.239.193:28502\n35.158.239.193:28503\n35.158.239.193:28504\n91.206.14.17:27500\n91.206.14.17:27501\n91.206.14.17:27502\n91.206.14.17:27503\n91.206.14.17:27504\n91.206.14.17:27505\n91.206.14.17:27506\n91.206.14.17:27507\n91.206.14.17:27508\n91.206.14.17:27509\n20.13.149.88:28501\n20.13.149.88:28502\n136.243.174.101:27166\n51.38.225.118:27500\n193.108.113.153:28501\n193.108.113.153:28502\n193.108.113.153:28503\n193.108.113.153:28504\n5.9.82.79:27500\n130.185.249.174:28501\n130.185.249.174:28502\n46.101.78.83:28502\n46.101.78.83:28501\n46.101.78.83:28502\n46.101.78.83:28503\n46.101.78.83:28504\n188.166.0.137:28501\n188.166.0.137:28502\n188.166.0.137:28503\n188.166.0.137:28504\n108.61.117.210:27502\n108.61.117.210:27503\n108.61.117.210:28501\n108.61.117.210:28502\n108.61.117.210:28503\n82.196.2.206:28502\n82.196.2.206:28502\n82.196.2.206:28503\n82.196.2.206:28504\n82.196.2.206:28504\n82.196.2.206:28501\n82.196.2.206:28502\n82.196.2.206:28503\n82.196.2.206:28504\n82.196.2.206:28505\n82.196.2.206:28506\n80.211.201.216:28501\n80.211.201.216:28502\n80.211.201.216:28503\n80.211.201.216:28504\n37.221.215.47:28501\n37.221.215.47:28502\n193.42.36.145:28501\n193.42.36.145:28502\n137.74.7.185:27501\n212.42.38.88:27501\n212.42.38.88:27502\n85.242.107.149:28501\n188.40.66.105:28501\n188.40.66.105:28503\n46.227.68.148:28501\n46.227.68.148:28502\n46.227.68.148:28503\n46.227.68.148:28504\n46.227.68.148:28505\n46.227.68.148:28506\n46.227.68.148:28507\n46.227.68.148:28508\n46.227.68.148:28509\n46.227.68.148:28510\n95.216.202.86:28501\n130.242.114.28:28501\n130.242.114.28:28502\n130.242.114.28:28503\n130.242.114.28:28504\n130.242.114.28:28505\n130.242.114.28:28506\n130.242.114.28:28507\n130.242.114.28:28508\n130.242.114.28:28509\n130.242.114.28:28510\n78.45.89.65:28501\n78.45.89.65:28502\n95.165.130.151:27501\n95.165.130.151:27502\n95.165.130.151:27503\n51.210.10.181:28501\n141.105.67.104:28000\n141.105.67.104:28501\n141.105.67.104:28502\n176.210.100.6:28501\n176.210.100.6:28502\n168.119.172.182:28501\n168.119.172.182:28502\n168.119.172.182:28503\n168.119.172.182:28504\n13.49.243.223:28501\n13.49.243.223:28502\n13.49.243.223:28503\n13.49.243.223:28504\n185.189.48.45:28501\n185.189.48.45:28502\n185.189.48.45:28503\n185.189.48.45:28504\n185.189.48.45:28505\n185.189.48.45:28506\n78.45.93.104:27500\n79.139.57.116:27500\n213.164.209.174:28501\n95.216.18.118:28001\n95.216.18.118:28002\n95.216.18.118:28003\n217.215.138.225:27500\n145.239.94.156:27500\n145.239.94.156:27501\n145.239.94.156:27502\n145.239.94.156:27503\n145.239.94.156:27504\n45.147.98.159:28505\n45.147.98.159:28506\n45.147.98.159:28501\n45.147.98.159:28501\n45.147.98.159:28502\n45.147.98.159:28502\n45.147.98.159:28503\n45.147.98.159:28503\n45.147.98.159:28504\n45.147.98.159:28504\n"
  },
  {
    "path": "misc/sb/eu-sv.txt",
    "content": "144.24.188.96:28501\n89.66.241.225:30000\n89.66.241.225:28501\n185.73.44.100:27500\n193.227.134.114:27500\n193.227.134.114:27501\n193.227.134.114:27502\n193.227.134.114:27503\n209.38.242.238:28501\n128.199.52.100:28501\n128.199.52.100:28502\n128.199.52.100:28503\n128.199.52.100:28504\n194.38.96.52:27500\n194.38.96.52:28000\n194.38.96.52:30000\n194.38.96.52:28501\n194.38.96.52:28502\n194.38.96.52:28503\n194.38.96.52:28504\n80.241.209.47:28501\n95.94.59.73:27500\n185.11.166.90:28501\n185.11.166.90:28502\n185.11.166.90:28503\n139.162.245.4:28501\n217.197.83.154:28000\n217.197.83.154:30000\n217.197.83.154:28501\n217.197.83.154:28502\n217.197.83.154:28503\n217.197.83.154:28504\n108.61.178.207:28000\n108.61.178.207:30000\n108.61.178.207:27501\n108.61.178.207:27502\n108.61.178.207:27503\n108.61.178.207:27504\n108.61.178.207:27505\n108.61.178.207:27506\n108.61.178.207:27507\n108.61.178.207:28501\n108.61.178.207:28502\n108.61.178.207:28503\n108.61.178.207:29501\n108.61.178.207:29502\n85.242.107.149:28000\n85.242.107.149:30000\n82.102.20.226:30000\n82.102.20.226:27501\n82.102.20.226:27502\n82.102.20.226:27503\n82.102.20.226:27504\n82.102.20.226:27505\n82.102.20.226:28501\n82.102.20.226:28502\n82.102.20.226:28503\n82.102.20.226:28504\n82.102.20.226:28505\n185.73.44.100:28000\n185.73.44.100:30000\n185.73.44.100:28501\n185.73.44.100:28502\n185.73.44.100:28503\n185.73.44.100:28504\n185.73.44.100:28505\n185.73.44.100:28506\n163.172.90.127:28000\n163.172.90.127:30000\n163.172.90.127:28501\n163.172.90.127:28502\n163.172.90.127:28503\n163.172.90.127:28504\n195.201.133.22:27600\n195.201.133.22:27700\n195.201.133.22:27800\n195.201.133.22:28000\n195.201.133.22:30000\n213.239.216.253:27500\n141.105.67.104:27500\n34.244.130.97:30000\n34.244.130.97:27510\n13.40.65.97:30000\n13.40.65.97:27510\n16.170.162.186:27501\n16.170.162.186:27500\n16.170.162.186:27502\n16.170.162.186:30000\n16.170.162.186:27510\n16.170.162.186:27503\n145.239.54.184:28000\n145.239.54.184:30000\n145.239.54.184:28501\n35.158.239.193:28000\n35.158.239.193:30000\n35.158.239.193:28501\n35.158.239.193:28502\n35.158.239.193:28503\n35.158.239.193:28504\n91.206.14.17:27500\n91.206.14.17:27501\n91.206.14.17:27502\n91.206.14.17:27503\n91.206.14.17:27504\n91.206.14.17:27505\n91.206.14.17:27506\n91.206.14.17:27507\n91.206.14.17:27508\n91.206.14.17:27509\n91.206.14.17:28000\n91.206.14.17:30000\n20.13.149.88:28000\n20.13.149.88:30000\n20.13.149.88:28501\n20.13.149.88:28502\n136.243.174.101:27166\n51.38.225.118:27500\n193.108.113.153:28000\n193.108.113.153:30000\n193.108.113.153:28501\n193.108.113.153:28502\n193.108.113.153:28503\n193.108.113.153:28504\n78.45.89.65:30000\n5.9.82.79:27500\n130.185.249.174:28000\n130.185.249.174:30000\n130.185.249.174:28501\n130.185.249.174:28502\n46.101.78.83:28502\n46.101.78.83:30000\n46.101.78.83:28501\n46.101.78.83:28502\n46.101.78.83:28503\n46.101.78.83:28504\n141.105.67.104:27000\n188.166.0.137:28000\n188.166.0.137:28501\n188.166.0.137:28502\n188.166.0.137:28503\n188.166.0.137:28504\n108.61.117.210:28000\n108.61.117.210:30000\n108.61.117.210:27501\n108.61.117.210:27502\n108.61.117.210:27503\n108.61.117.210:28501\n108.61.117.210:28502\n108.61.117.210:28503\n82.196.2.206:28502\n82.196.2.206:28502\n82.196.2.206:28503\n82.196.2.206:28504\n82.196.2.206:28504\n82.196.2.206:30000\n82.196.2.206:28501\n82.196.2.206:28502\n82.196.2.206:28503\n82.196.2.206:28504\n82.196.2.206:28505\n82.196.2.206:28506\n146.90.114.169:27500\n80.211.201.216:28501\n80.211.201.216:28502\n80.211.201.216:28503\n80.211.201.216:28504\n37.221.215.47:28501\n37.221.215.47:28502\n193.42.36.145:28000\n193.42.36.145:30000\n193.42.36.145:28501\n193.42.36.145:28502\n137.74.7.185:27520\n137.74.7.185:27500\n137.74.7.185:27501\n137.74.7.185:27510\n137.74.7.185:30000\n212.42.38.88:30000\n212.42.38.88:27500\n212.42.38.88:27501\n212.42.38.88:27502\n85.242.107.149:28501\n95.216.202.86:27666\n188.40.66.105:28501\n188.40.66.105:28503\n91.66.2.91:27500\n46.227.68.148:28000\n46.227.68.148:30000\n46.227.68.148:28501\n46.227.68.148:28502\n46.227.68.148:28503\n46.227.68.148:28504\n46.227.68.148:28505\n46.227.68.148:28506\n46.227.68.148:28507\n46.227.68.148:28508\n46.227.68.148:28509\n46.227.68.148:28510\n95.216.202.86:27500\n95.216.202.86:28502\n95.216.202.86:28501\n95.216.202.86:28000\n95.216.202.86:30000\n95.216.202.86:28504\n95.216.202.86:28503\n130.242.114.28:28000\n130.242.114.28:30000\n130.242.114.28:28501\n130.242.114.28:28502\n130.242.114.28:28503\n130.242.114.28:28504\n130.242.114.28:28505\n130.242.114.28:28506\n130.242.114.28:28507\n130.242.114.28:28508\n130.242.114.28:28509\n130.242.114.28:28510\n78.45.89.65:28501\n78.45.89.65:28502\n78.45.89.65:28503\n95.165.130.151:27500\n95.165.130.151:27501\n95.165.130.151:27502\n95.165.130.151:27503\n188.166.161.185:27501\n188.166.161.185:30000\n188.166.161.185:27502\n188.166.161.185:27500\n185.11.166.90:28000\n185.11.166.90:30000\n51.210.10.181:28000\n51.210.10.181:28501\n37.201.131.70:28000\n37.201.131.70:30000\n45.142.244.163:28501\n141.105.67.104:28000\n141.105.67.104:28501\n141.105.67.104:28502\n176.210.100.6:28501\n176.210.100.6:28502\n176.210.100.6:30000\n13.53.75.0:27500\n168.119.172.182:28000\n168.119.172.182:30000\n168.119.172.182:28501\n168.119.172.182:28502\n168.119.172.182:28503\n168.119.172.182:28504\n13.49.243.223:28000\n13.49.243.223:30000\n13.49.243.223:28501\n13.49.243.223:28502\n13.49.243.223:28503\n13.49.243.223:28504\n185.189.48.45:28501\n185.189.48.45:28502\n185.189.48.45:28503\n185.189.48.45:28504\n185.189.48.45:28505\n185.189.48.45:28506\n78.45.93.104:27500\n79.139.57.116:27500\n213.164.209.174:28501\n37.201.131.70:28501\n95.216.18.118:30000\n95.216.18.118:28001\n95.216.18.118:28002\n95.216.18.118:28003\n217.215.138.225:27500\n82.196.2.206:30000\n185.189.48.45:30000\n139.162.245.4:30000\n188.166.0.137:30000\n145.239.94.156:28000\n145.239.94.156:30000\n145.239.94.156:27500\n145.239.94.156:27501\n145.239.94.156:27502\n145.239.94.156:27503\n145.239.94.156:27504\n188.40.103.81:27600\n45.147.98.159:28000\n45.147.98.159:28505\n45.147.98.159:28506\n45.147.98.159:28501\n45.147.98.159:28501\n45.147.98.159:28502\n45.147.98.159:28502\n45.147.98.159:28503\n45.147.98.159:28503\n45.147.98.159:28504\n45.147.98.159:28504\n"
  },
  {
    "path": "misc/sb/global.txt",
    "content": "177.71.158.196:27500\n177.71.158.196:27502\n144.22.145.206:27504\n144.22.145.206:27506\n144.22.145.206:27502\n144.22.145.206:27508\n144.22.145.206:30000\n144.22.145.206:27510\n130.162.236.198:28500\n34.148.103.12:27500\n71.69.230.224:27500\n159.196.248.221:27600\n103.195.53.179:28501\n103.195.53.179:28502\n103.195.53.179:28503\n192.227.172.145:28000\n192.227.172.145:30000\n192.227.172.145:28501\n192.227.172.145:28502\n3.76.38.12:28501\n144.24.188.96:28501\n54.232.250.209:28000\n54.232.250.209:30000\n54.232.250.209:28501\n54.232.250.209:28502\n54.232.250.209:28503\n54.232.250.209:28504\n129.148.54.225:26000\n129.148.54.225:27500\n23.227.170.222:26666\n103.25.59.27:28000\n103.25.59.27:28501\n103.25.59.27:28502\n103.25.59.27:28504\n89.66.241.225:30000\n89.66.241.225:28501\n185.73.44.100:27500\n193.227.134.114:27500\n193.227.134.114:27501\n193.227.134.114:27502\n193.227.134.114:27503\n209.38.242.238:28501\n182.239.198.9:27500\n128.199.52.100:28501\n128.199.52.100:28502\n128.199.52.100:28503\n128.199.52.100:28504\n194.38.96.52:27500\n194.38.96.52:28000\n194.38.96.52:30000\n194.38.96.52:28501\n194.38.96.52:28502\n194.38.96.52:28503\n194.38.96.52:28504\n80.241.209.47:28501\n174.138.43.249:28502\n174.138.43.249:28501\n99.72.255.45:28501\n99.72.255.45:28504\n99.72.255.45:28507\n114.35.54.116:30000\n114.35.54.116:28501\n95.94.59.73:27500\n185.11.166.90:28501\n185.11.166.90:28502\n185.11.166.90:28503\n162.248.94.117:30000\n72.83.103.37:27504\n20.221.237.152:26000\n72.18.132.133:26000\n72.83.103.37:28505\n139.162.245.4:28501\n155.138.247.213:28000\n155.138.247.213:30000\n155.138.247.213:28501\n155.138.247.213:28502\n155.138.247.213:28503\n155.138.247.213:28504\n217.197.83.154:28000\n217.197.83.154:30000\n217.197.83.154:28501\n217.197.83.154:28502\n217.197.83.154:28503\n217.197.83.154:28504\n108.61.242.130:27500\n108.61.178.207:28000\n108.61.178.207:30000\n108.61.178.207:27501\n108.61.178.207:27502\n108.61.178.207:27503\n108.61.178.207:27504\n108.61.178.207:27505\n108.61.178.207:27506\n108.61.178.207:27507\n108.61.178.207:28501\n108.61.178.207:28502\n108.61.178.207:28503\n108.61.178.207:29501\n108.61.178.207:29502\n64.227.18.147:28501\n85.242.107.149:28000\n85.242.107.149:30000\n82.102.20.226:30000\n82.102.20.226:27501\n82.102.20.226:27502\n82.102.20.226:27503\n82.102.20.226:27504\n82.102.20.226:27505\n82.102.20.226:28501\n82.102.20.226:28502\n82.102.20.226:28503\n82.102.20.226:28504\n82.102.20.226:28505\n185.73.44.100:28000\n185.73.44.100:30000\n185.73.44.100:28501\n185.73.44.100:28502\n185.73.44.100:28503\n185.73.44.100:28504\n185.73.44.100:28505\n185.73.44.100:28506\n103.25.59.27:30000\n103.25.59.27:27501\n103.25.59.27:27504\n103.25.59.27:27505\n103.25.59.27:27503\n103.25.59.27:27502\n163.172.90.127:28000\n163.172.90.127:30000\n163.172.90.127:28501\n163.172.90.127:28502\n163.172.90.127:28503\n163.172.90.127:28504\n195.201.133.22:27600\n195.201.133.22:27700\n195.201.133.22:27800\n195.201.133.22:28000\n195.201.133.22:30000\n18.144.19.16:27500\n103.72.77.220:30000\n103.72.77.220:28501\n103.72.77.220:28502\n103.72.77.220:28503\n103.72.77.220:28504\n72.83.103.37:27500\n213.239.216.253:27500\n141.105.67.104:27500\n104.207.135.230:28008\n104.207.135.230:28000\n104.207.135.230:30000\n104.207.135.230:28001\n104.207.135.230:28002\n104.207.135.230:28003\n104.207.135.230:28004\n104.207.135.230:28005\n74.91.120.206:28000\n74.91.120.206:30000\n74.91.120.206:28501\n74.91.120.206:28502\n162.248.93.195:28000\n162.248.93.195:30000\n162.248.93.195:28501\n162.248.93.195:28502\n162.248.93.195:28503\n162.248.93.195:28504\n20.125.113.83:30000\n20.245.213.126:30000\n54.177.92.188:27501\n54.177.92.188:30000\n54.177.92.188:27510\n54.177.92.188:27503\n50.116.27.42:30000\n103.151.65.19:27500\n103.151.65.19:30000\n34.244.130.97:30000\n34.244.130.97:27510\n13.40.65.97:30000\n13.40.65.97:27510\n16.50.174.37:30000\n16.50.174.37:27510\n144.202.33.70:30000\n144.202.33.70:27510\n3.108.227.32:27501\n3.108.227.32:27500\n3.108.227.32:27502\n3.108.227.32:30000\n3.108.227.32:27510\n3.108.227.32:27503\n18.228.39.185:27501\n18.228.39.185:27502\n18.228.39.185:30000\n18.228.39.185:27510\n18.228.39.185:27503\n16.170.162.186:27501\n16.170.162.186:27500\n16.170.162.186:27502\n16.170.162.186:30000\n16.170.162.186:27510\n16.170.162.186:27503\n3.25.174.195:27501\n3.25.174.195:27500\n3.25.174.195:27502\n3.25.174.195:30000\n3.25.174.195:27510\n3.25.174.195:27503\n18.177.140.83:30000\n18.208.156.115:27501\n18.208.156.115:27502\n18.208.156.115:30000\n18.208.156.115:27510\n18.208.156.115:27503\n144.202.59.41:28501\n145.239.54.184:28000\n145.239.54.184:30000\n145.239.54.184:28501\n35.158.239.193:28000\n35.158.239.193:30000\n35.158.239.193:28501\n35.158.239.193:28502\n35.158.239.193:28503\n35.158.239.193:28504\n66.85.80.160:28501\n66.85.80.160:28000\n152.67.110.136:28501\n152.67.110.136:28502\n152.67.110.136:28503\n152.67.110.136:28000\n152.67.110.136:30000\n54.94.165.196:28501\n54.94.165.196:28502\n54.94.165.196:30000\n54.94.165.196:27700\n54.94.165.196:27500\n54.94.165.196:27600\n91.206.14.17:27500\n91.206.14.17:27501\n91.206.14.17:27502\n91.206.14.17:27503\n91.206.14.17:27504\n91.206.14.17:27505\n91.206.14.17:27506\n91.206.14.17:27507\n91.206.14.17:27508\n91.206.14.17:27509\n91.206.14.17:28000\n91.206.14.17:30000\n162.248.92.183:30000\n162.248.92.183:28501\n167.71.142.56:28501\n167.71.142.56:28502\n167.71.142.56:28503\n167.71.142.56:28504\n191.223.251.197:27500\n156.247.189.2:27502\n44.210.138.174:26000\n174.164.173.71:28000\n174.164.173.71:28501\n174.164.173.71:28502\n174.164.173.71:28503\n174.164.173.71:28504\n45.32.196.50:28501\n45.32.196.50:27500\n20.13.149.88:28000\n20.13.149.88:30000\n20.13.149.88:28501\n20.13.149.88:28502\n20.241.104.145:28000\n20.241.104.145:30000\n20.241.104.145:28501\n139.84.135.106:28000\n139.84.135.106:30000\n139.84.135.106:28501\n139.84.135.106:28502\n20.193.138.18:28000\n20.193.138.18:30000\n20.193.138.18:28501\n20.193.138.18:28502\n20.106.48.249:28000\n20.106.48.249:30000\n20.106.48.249:28501\n20.106.48.249:28502\n136.243.174.101:27166\n20.243.122.16:28000\n20.243.122.16:30000\n20.243.122.16:28501\n51.38.225.118:27500\n148.135.104.18:27020\n72.83.103.37:27501\n72.83.103.37:27502\n72.83.103.37:27503\n193.108.113.153:28000\n193.108.113.153:30000\n193.108.113.153:28501\n193.108.113.153:28502\n193.108.113.153:28503\n193.108.113.153:28504\n72.83.103.37:666\n45.32.75.169:30000\n45.32.75.169:28501\n45.32.75.169:28502\n45.32.75.169:28503\n45.32.75.169:28504\n78.45.89.65:30000\n69.247.38.137:27500\n135.181.96.161:27500\n5.9.82.79:27500\n130.185.249.174:28000\n130.185.249.174:30000\n130.185.249.174:28501\n130.185.249.174:28502\n46.101.78.83:28502\n46.101.78.83:30000\n46.101.78.83:28501\n46.101.78.83:28502\n46.101.78.83:28503\n46.101.78.83:28504\n159.196.248.221:27601\n141.105.67.104:27000\n52.67.74.216:27500\n72.83.103.37:28000\n188.166.0.137:28000\n188.166.0.137:28501\n188.166.0.137:28502\n188.166.0.137:28503\n188.166.0.137:28504\n108.61.117.210:28000\n108.61.117.210:30000\n108.61.117.210:27501\n108.61.117.210:27502\n108.61.117.210:27503\n108.61.117.210:28501\n108.61.117.210:28502\n108.61.117.210:28503\n82.196.2.206:28502\n82.196.2.206:28502\n82.196.2.206:28503\n82.196.2.206:28504\n82.196.2.206:28504\n82.196.2.206:30000\n82.196.2.206:28501\n82.196.2.206:28502\n82.196.2.206:28503\n82.196.2.206:28504\n82.196.2.206:28505\n82.196.2.206:28506\n146.90.114.169:27500\n80.211.201.216:28501\n80.211.201.216:28502\n80.211.201.216:28503\n80.211.201.216:28504\n149.28.62.121:30000\n149.28.62.121:28501\n149.28.62.121:28502\n149.28.62.121:28504\n210.54.39.19:28501\n115.188.3.198:27500\n164.152.62.160:28000\n164.152.62.160:30000\n18.228.199.156:28500\n18.228.199.156:28501\n18.228.199.156:28502\n18.228.199.156:28503\n18.228.199.156:28504\n103.151.65.11:28000\n103.151.65.11:30000\n103.151.65.11:28501\n103.151.65.11:28502\n37.221.215.47:28501\n37.221.215.47:28502\n23.22.56.152:28501\n23.22.56.152:28502\n23.22.56.152:28503\n23.22.56.152:28504\n193.42.36.145:28000\n193.42.36.145:30000\n193.42.36.145:28501\n193.42.36.145:28502\n137.74.7.185:27520\n137.74.7.185:27500\n137.74.7.185:27501\n137.74.7.185:27510\n137.74.7.185:30000\n212.42.38.88:30000\n212.42.38.88:27500\n212.42.38.88:27501\n212.42.38.88:27502\n150.136.70.158:27500\n139.162.45.192:30000\n85.242.107.149:28501\n64.227.18.147:28502\n64.227.18.147:28503\n64.227.18.147:28504\n24.87.1.123:26000\n95.216.202.86:27666\n163.47.21.45:28000\n188.40.66.105:28501\n188.40.66.105:28503\n91.66.2.91:27500\n46.227.68.148:28000\n46.227.68.148:30000\n46.227.68.148:28501\n46.227.68.148:28502\n46.227.68.148:28503\n46.227.68.148:28504\n46.227.68.148:28505\n46.227.68.148:28506\n46.227.68.148:28507\n46.227.68.148:28508\n46.227.68.148:28509\n46.227.68.148:28510\n45.56.70.87:28501\n45.56.70.87:28502\n45.56.70.87:28503\n45.56.70.87:28504\n170.239.85.110:28000\n170.239.85.110:30000\n170.239.85.110:28501\n170.239.85.110:28502\n170.239.85.110:28503\n170.239.85.110:28504\n170.239.85.110:28505\n103.151.65.11:28503\n103.151.65.11:28504\n103.195.53.179:28000\n95.216.202.86:27500\n95.216.202.86:28502\n95.216.202.86:28501\n95.216.202.86:28000\n95.216.202.86:30000\n95.216.202.86:28504\n95.216.202.86:28503\n130.242.114.28:28000\n130.242.114.28:30000\n130.242.114.28:28501\n130.242.114.28:28502\n130.242.114.28:28503\n130.242.114.28:28504\n130.242.114.28:28505\n130.242.114.28:28506\n130.242.114.28:28507\n130.242.114.28:28508\n130.242.114.28:28509\n130.242.114.28:28510\n78.45.89.65:28501\n78.45.89.65:28502\n78.45.89.65:28503\n164.152.62.160:28501\n164.152.62.160:28502\n164.152.62.160:28503\n164.152.62.160:28504\n95.165.130.151:27500\n95.165.130.151:27501\n95.165.130.151:27502\n95.165.130.151:27503\n160.16.101.168:30000\n72.83.103.37:30000\n138.197.111.29:28501\n188.166.161.185:27501\n188.166.161.185:30000\n188.166.161.185:27502\n188.166.161.185:27500\n156.247.189.2:27506\n223.165.64.141:27500\n72.94.86.54:27500\n52.89.197.170:26000\n52.89.197.170:26001\n185.11.166.90:28000\n185.11.166.90:30000\n206.51.245.84:27500\n45.76.56.39:28000\n45.76.56.39:30000\n45.76.56.39:28001\n45.76.56.39:28002\n45.76.56.39:28003\n45.76.56.39:28004\n45.76.56.39:28005\n70.34.202.147:30000\n70.34.202.147:28501\n70.34.202.147:28502\n70.34.202.147:28503\n70.34.202.147:28504\n70.34.202.147:29501\n172.105.184.44:27501\n20.245.6.70:28000\n20.245.6.70:30000\n35.185.44.174:27502\n75.102.17.203:28501\n75.102.17.203:28502\n75.102.17.203:28503\n75.102.17.203:28504\n75.102.17.203:28000\n75.102.17.203:30000\n51.210.10.181:28000\n51.210.10.181:28501\n37.201.131.70:28000\n37.201.131.70:30000\n170.39.226.130:28501\n45.142.244.163:28501\n141.105.67.104:28000\n141.105.67.104:28501\n141.105.67.104:28502\n176.210.100.6:28501\n176.210.100.6:28502\n176.210.100.6:30000\n13.53.75.0:27500\n168.119.172.182:28000\n168.119.172.182:30000\n168.119.172.182:28501\n168.119.172.182:28502\n168.119.172.182:28503\n168.119.172.182:28504\n13.49.243.223:28000\n13.49.243.223:30000\n13.49.243.223:28501\n13.49.243.223:28502\n13.49.243.223:28503\n13.49.243.223:28504\n185.189.48.45:28501\n185.189.48.45:28502\n185.189.48.45:28503\n185.189.48.45:28504\n185.189.48.45:28505\n185.189.48.45:28506\n98.144.80.46:27500\n78.45.93.104:27500\n79.139.57.116:27500\n177.76.50.14:27501\n177.76.50.14:27502\n177.76.50.14:27503\n177.76.50.14:27504\n177.76.50.14:28000\n177.76.50.14:27506\n177.76.50.14:27505\n23.227.170.216:27502\n23.227.170.216:27500\n23.227.170.216:27501\n23.227.170.216:27507\n23.227.170.216:27505\n23.227.170.216:27506\n213.164.209.174:28501\n37.201.131.70:28501\n203.29.243.177:28000\n203.29.243.177:30000\n203.29.243.177:28501\n124.189.251.43:27666\n124.189.251.43:28001\n124.189.251.43:28002\n124.189.251.43:28003\n124.189.251.43:28000\n124.189.251.43:30000\n95.216.18.118:30000\n95.216.18.118:28001\n95.216.18.118:28002\n95.216.18.118:28003\n217.215.138.225:27500\n72.83.103.37:27505\n144.22.145.206:27500\n102.37.98.187:27500\n50.116.17.172:28501\n163.47.21.45:30000\n82.196.2.206:30000\n185.189.48.45:30000\n66.85.80.160:30000\n139.162.245.4:30000\n188.166.0.137:30000\n149.28.240.2:28000\n149.28.240.2:30000\n45.76.23.209:28000\n45.76.23.209:30000\n159.203.25.195:28000\n159.203.25.195:30000\n207.148.5.167:28502\n207.148.5.167:28501\n207.148.5.167:28000\n207.148.5.167:30000\n149.28.240.2:28507\n149.28.240.2:28505\n149.28.240.2:28503\n149.28.240.2:28501\n149.28.240.2:28506\n149.28.240.2:28502\n149.28.240.2:28504\n45.76.23.209:28507\n45.76.23.209:28505\n45.76.23.209:28503\n45.76.23.209:28501\n45.76.23.209:28506\n45.76.23.209:28502\n45.76.23.209:28504\n159.65.230.186:27505\n159.65.230.186:27502\n159.65.230.186:27500\n159.65.230.186:27504\n159.65.230.186:28000\n159.65.230.186:30000\n159.65.230.186:27501\n159.65.230.186:27503\n159.203.25.195:28507\n159.203.25.195:28505\n159.203.25.195:28503\n159.203.25.195:28501\n159.203.25.195:28506\n159.203.25.195:28502\n159.203.25.195:28504\n5.161.189.133:27600\n138.68.21.116:30000\n138.68.21.116:28000\n138.68.21.116:28501\n138.68.21.116:28502\n20.51.107.74:28000\n20.51.107.74:30000\n20.51.107.74:28501\n20.51.107.74:28502\n20.69.57.161:28000\n20.69.57.161:30000\n20.69.57.161:28501\n20.69.57.161:28502\n13.245.119.116:28000\n13.245.119.116:27500\n13.245.119.116:28501\n13.245.119.116:28502\n145.239.94.156:28000\n145.239.94.156:30000\n145.239.94.156:27500\n145.239.94.156:27501\n145.239.94.156:27502\n145.239.94.156:27503\n145.239.94.156:27504\n188.40.103.81:27600\n103.25.59.27:28503\n35.185.44.174:27500\n45.147.98.159:28000\n45.147.98.159:28505\n45.147.98.159:28506\n45.147.98.159:28501\n45.147.98.159:28501\n45.147.98.159:28502\n45.147.98.159:28502\n45.147.98.159:28503\n45.147.98.159:28503\n45.147.98.159:28504\n45.147.98.159:28504\n52.240.58.168:27503\n52.240.58.168:27515\n208.77.22.94:27501\n"
  },
  {
    "path": "misc/sb/na-sv.txt",
    "content": "130.162.236.198:28500\n34.148.103.12:27500\n71.69.230.224:27500\n192.227.172.145:28000\n192.227.172.145:30000\n192.227.172.145:28501\n192.227.172.145:28502\n3.76.38.12:28501\n23.227.170.222:26666\n174.138.43.249:28502\n174.138.43.249:28501\n99.72.255.45:28501\n99.72.255.45:28504\n99.72.255.45:28507\n162.248.94.117:30000\n72.83.103.37:27504\n20.221.237.152:26000\n72.18.132.133:26000\n72.83.103.37:28505\n155.138.247.213:28000\n155.138.247.213:30000\n155.138.247.213:28501\n155.138.247.213:28502\n155.138.247.213:28503\n155.138.247.213:28504\n108.61.242.130:27500\n64.227.18.147:28501\n18.144.19.16:27500\n103.72.77.220:30000\n103.72.77.220:28501\n103.72.77.220:28502\n103.72.77.220:28503\n103.72.77.220:28504\n72.83.103.37:27500\n104.207.135.230:28008\n104.207.135.230:28000\n104.207.135.230:30000\n104.207.135.230:28001\n104.207.135.230:28002\n104.207.135.230:28003\n104.207.135.230:28004\n104.207.135.230:28005\n74.91.120.206:28000\n74.91.120.206:30000\n74.91.120.206:28501\n74.91.120.206:28502\n162.248.93.195:28000\n162.248.93.195:30000\n162.248.93.195:28501\n162.248.93.195:28502\n162.248.93.195:28503\n162.248.93.195:28504\n20.125.113.83:30000\n20.245.213.126:30000\n54.177.92.188:27501\n54.177.92.188:30000\n54.177.92.188:27510\n54.177.92.188:27503\n50.116.27.42:30000\n16.50.174.37:30000\n144.202.33.70:30000\n144.202.33.70:27510\n18.208.156.115:27501\n18.208.156.115:27502\n18.208.156.115:30000\n18.208.156.115:27510\n18.208.156.115:27503\n144.202.59.41:28501\n66.85.80.160:28501\n66.85.80.160:28000\n152.67.110.136:28501\n152.67.110.136:28502\n152.67.110.136:28503\n152.67.110.136:28000\n152.67.110.136:30000\n162.248.92.183:30000\n162.248.92.183:28501\n167.71.142.56:28501\n167.71.142.56:28502\n167.71.142.56:28503\n167.71.142.56:28504\n44.210.138.174:26000\n174.164.173.71:28000\n174.164.173.71:28501\n174.164.173.71:28502\n174.164.173.71:28503\n174.164.173.71:28504\n45.32.196.50:28501\n45.32.196.50:27500\n20.241.104.145:28000\n20.241.104.145:30000\n20.241.104.145:28501\n20.106.48.249:28000\n20.106.48.249:30000\n20.106.48.249:28501\n20.106.48.249:28502\n148.135.104.18:27020\n72.83.103.37:27501\n72.83.103.37:27502\n72.83.103.37:27503\n72.83.103.37:666\n45.32.75.169:30000\n45.32.75.169:28501\n45.32.75.169:28502\n45.32.75.169:28503\n45.32.75.169:28504\n69.247.38.137:27500\n135.181.96.161:27500\n52.67.74.216:27500\n72.83.103.37:28000\n149.28.62.121:30000\n149.28.62.121:28501\n149.28.62.121:28502\n149.28.62.121:28504\n23.22.56.152:28501\n23.22.56.152:28502\n23.22.56.152:28503\n23.22.56.152:28504\n150.136.70.158:27500\n64.227.18.147:28502\n64.227.18.147:28503\n64.227.18.147:28504\n24.87.1.123:26000\n45.56.70.87:28501\n45.56.70.87:28502\n45.56.70.87:28503\n45.56.70.87:28504\n72.83.103.37:30000\n138.197.111.29:28501\n72.94.86.54:27500\n52.89.197.170:26000\n52.89.197.170:26001\n206.51.245.84:27500\n45.76.56.39:28000\n45.76.56.39:30000\n45.76.56.39:28001\n45.76.56.39:28002\n45.76.56.39:28003\n45.76.56.39:28004\n45.76.56.39:28005\n70.34.202.147:30000\n70.34.202.147:28501\n70.34.202.147:28502\n70.34.202.147:28503\n70.34.202.147:28504\n70.34.202.147:29501\n20.245.6.70:28000\n20.245.6.70:30000\n35.185.44.174:27502\n75.102.17.203:28501\n75.102.17.203:28502\n75.102.17.203:28503\n75.102.17.203:28504\n75.102.17.203:28000\n75.102.17.203:30000\n170.39.226.130:28501\n98.144.80.46:27500\n23.227.170.216:27502\n23.227.170.216:27500\n23.227.170.216:27501\n23.227.170.216:27507\n23.227.170.216:27505\n23.227.170.216:27506\n72.83.103.37:27505\n50.116.17.172:28501\n66.85.80.160:30000\n149.28.240.2:28000\n149.28.240.2:30000\n45.76.23.209:28000\n45.76.23.209:30000\n159.203.25.195:28000\n159.203.25.195:30000\n207.148.5.167:28502\n207.148.5.167:28501\n207.148.5.167:28000\n207.148.5.167:30000\n149.28.240.2:28507\n149.28.240.2:28505\n149.28.240.2:28503\n149.28.240.2:28501\n149.28.240.2:28506\n149.28.240.2:28502\n149.28.240.2:28504\n45.76.23.209:28507\n45.76.23.209:28505\n45.76.23.209:28503\n45.76.23.209:28501\n45.76.23.209:28506\n45.76.23.209:28502\n45.76.23.209:28504\n159.65.230.186:27505\n159.65.230.186:27502\n159.65.230.186:27500\n159.65.230.186:27504\n159.65.230.186:28000\n159.65.230.186:30000\n159.65.230.186:27501\n159.65.230.186:27503\n159.203.25.195:28507\n159.203.25.195:28505\n159.203.25.195:28503\n159.203.25.195:28501\n159.203.25.195:28506\n159.203.25.195:28502\n159.203.25.195:28504\n5.161.189.133:27600\n138.68.21.116:30000\n138.68.21.116:28000\n138.68.21.116:28501\n138.68.21.116:28502\n20.51.107.74:28000\n20.51.107.74:30000\n20.51.107.74:28501\n20.51.107.74:28502\n20.69.57.161:28000\n20.69.57.161:30000\n20.69.57.161:28501\n20.69.57.161:28502\n35.185.44.174:27500\n52.240.58.168:27503\n52.240.58.168:27515\n208.77.22.94:27501\n"
  },
  {
    "path": "misc/sb/qizmo.txt",
    "content": "95.216.202.86:27666\n124.189.251.43:27666\n"
  },
  {
    "path": "misc/sb/sa-sv.txt",
    "content": "177.71.158.196:27500\n177.71.158.196:27502\n144.22.145.206:27504\n144.22.145.206:27506\n144.22.145.206:27502\n144.22.145.206:27508\n144.22.145.206:30000\n144.22.145.206:27510\n54.232.250.209:28000\n54.232.250.209:30000\n54.232.250.209:28501\n54.232.250.209:28502\n54.232.250.209:28503\n54.232.250.209:28504\n129.148.54.225:26000\n129.148.54.225:27500\n18.228.39.185:27501\n18.228.39.185:27502\n18.228.39.185:30000\n18.228.39.185:27510\n18.228.39.185:27503\n54.94.165.196:28501\n54.94.165.196:28502\n54.94.165.196:30000\n54.94.165.196:27700\n54.94.165.196:27500\n54.94.165.196:27600\n191.223.251.197:27500\n164.152.62.160:28000\n164.152.62.160:30000\n18.228.199.156:28500\n18.228.199.156:28501\n18.228.199.156:28502\n18.228.199.156:28503\n18.228.199.156:28504\n170.239.85.110:28000\n170.239.85.110:30000\n170.239.85.110:28501\n170.239.85.110:28502\n170.239.85.110:28503\n170.239.85.110:28504\n170.239.85.110:28505\n164.152.62.160:28501\n164.152.62.160:28502\n164.152.62.160:28503\n164.152.62.160:28504\n177.76.50.14:27501\n177.76.50.14:27502\n177.76.50.14:27503\n177.76.50.14:27504\n177.76.50.14:28000\n177.76.50.14:27506\n177.76.50.14:27505\n144.22.145.206:27500\n"
  },
  {
    "path": "misc/sb/sources.txt",
    "content": "url \"QuakeServers URL\" http://www.quakeservers.net/lists/servers/global.txt\nmaster \"QuakeServers.net\" master.quakeservers.net:27000\nmaster \"Asgaard\" qwmaster.fodquake.net:27000\nfile \"Global\" global.txt\nmaster \"Poland\" 217.153.59.106:27000\nfile \"European Servers\" eu-sv.txt\nfile \"Europe 4on4\" eu-4on4.txt\nfile \"Australian Servers\" au-sv.txt\nfile \"Capture The Flag\" ctf.txt\nfile \"North America Servers\" na-sv.txt\nfile \"South America Servers\" sa-sv.txt\nfile \"Team Fortress\" tf.txt\nfile \"World Qizmos\" qizmo.txt\n"
  },
  {
    "path": "misc/sb/tf.txt",
    "content": "177.71.158.196:27500\n177.71.158.196:27502\n144.22.145.206:27504\n144.22.145.206:27506\n144.22.145.206:27502\n144.22.145.206:27508\n144.22.145.206:27510\n71.69.230.224:27500\n23.227.170.222:26666\n193.227.134.114:27500\n193.227.134.114:27501\n193.227.134.114:27502\n193.227.134.114:27503\n103.25.59.27:27501\n103.25.59.27:27504\n103.25.59.27:27505\n103.25.59.27:27503\n103.25.59.27:27502\n54.177.92.188:27501\n54.177.92.188:27510\n54.177.92.188:27503\n103.151.65.19:27500\n34.244.130.97:27510\n13.40.65.97:27510\n16.50.174.37:27510\n144.202.33.70:27510\n3.108.227.32:27501\n3.108.227.32:27500\n3.108.227.32:27502\n3.108.227.32:27510\n3.108.227.32:27503\n18.228.39.185:27501\n18.228.39.185:27502\n18.228.39.185:27510\n18.228.39.185:27503\n16.170.162.186:27501\n16.170.162.186:27500\n16.170.162.186:27502\n16.170.162.186:27510\n16.170.162.186:27503\n3.25.174.195:27501\n3.25.174.195:27500\n3.25.174.195:27502\n3.25.174.195:27510\n3.25.174.195:27503\n18.208.156.115:27501\n18.208.156.115:27502\n18.208.156.115:27510\n18.208.156.115:27503\n95.216.202.86:28504\n95.216.202.86:28503\n35.185.44.174:27502\n23.227.170.216:27502\n23.227.170.216:27500\n23.227.170.216:27501\n23.227.170.216:27507\n23.227.170.216:27505\n23.227.170.216:27506\n144.22.145.206:27500\n188.40.103.81:27600\n35.185.44.174:27500\n"
  },
  {
    "path": "misc/sb/update_sources.bat",
    "content": "wget -m -nd --no-if-modified-since https://www.quakeservers.net/lists/australia/au-sv.txt\nwget -m -nd --no-if-modified-since https://www.quakeservers.net/lists/ctf/ctf.txt\nwget -m -nd --no-if-modified-since https://www.quakeservers.net/lists/europe/eu-sv.txt\nwget -m -nd --no-if-modified-since https://www.quakeservers.net/lists/servers/global.txt\nwget -m -nd --no-if-modified-since https://www.quakeservers.net/lists/north_america/na-sv.txt\nwget -m -nd --no-if-modified-since https://www.quakeservers.net/lists/qizmo/qizmo.txt\nwget -m -nd --no-if-modified-since https://www.quakeservers.net/lists/south_america/sa-sv.txt\nwget -m -nd --no-if-modified-since https://www.quakeservers.net/lists/tf/tf.txt\nwget -m -nd --no-if-modified-since https://www.quakeservers.net/lists/4on4_eu/eu-4on4.txt\n"
  },
  {
    "path": "src/Ctrl.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"utils.h\"\n#include \"Ctrl.h\"\n\ncvar_t     menu_marked_bgcolor = {\"menu_marked_bgcolor\", \"20 20 20 128\", CVAR_COLOR};\ncvar_t     menu_marked_fade = {\"menu_marked_fade\", \"4\"};\n\n#define SLIDER_RANGE 12\n\nvoid UI_DrawCharacter (int cx, int line, int num)\n{\n\tDraw_Character ( cx, line, num);\n}\n\nvoid UI_Print (int cx, int cy, const char *str, int red)\n{\n\t// We want to draw the charset red, if colored text is turned on\n\t// Draw_ColoredString will only draw white chars and color them.\n\tif (red)\n\t{\n\t\twhile (*str) \n\t\t{\n\t\t\tUI_DrawCharacter (cx, cy, (*str) | (red ? 128 : 0));\n\t\t\tstr++;\n\t\t\tcx += 8;\n\t\t}\n\t}\n\telse\n\t{\n\t\tDraw_ColoredString (cx, cy, str, red, false);\n\t}\n}\n\nint UI_DrawSlider (int x, int y, float range) {\n\tint    i;\n\n\trange = bound(0, range, 1);\n\tUI_DrawCharacter (x-8, y, 128);\n\tfor (i=0 ; i<SLIDER_RANGE ; i++)\n\t\tUI_DrawCharacter (x + i*8, y, 129);\n\tUI_DrawCharacter (x+i*8, y, 130);\n\tUI_DrawCharacter (x + (SLIDER_RANGE-1)*8 * range, y, 131);\n\treturn x+(i+1)*8;\n}\n\nint UI_SliderWidth(void) { return (SLIDER_RANGE+1)*LETTERWIDTH; }\n\nvoid UI_Print3 (int cx, int cy, const char *str, clrinfo_t *clr, int clr_cnt, int red)\n{\n\tif (red)\n\t{\n\t\twhile (*str) \n\t\t{\n\t\t\tUI_DrawCharacter (cx, cy, (*str) | (red ? 128 : 0));\n\t\t\tstr++;\n\t\t\tcx += 8;\n\t\t}\n\t}\n\telse\n\t{\n\t\tDraw_ColoredString3 (cx, cy, str, clr, clr_cnt, red);\n\t}\n}\n\nvoid UI_Print_Center (int cx, int cy, int w, const char *str, int red)\n{\n\t// UH!!! wtf.... if I do:\n\t// UI_Print(cx + (w - 8 * strlen(str)) / 2, cy, str, red);\n\t// The call to UI_Print will get the first argument all fucked up (-217309285) instead of 192 in one instance.\n\t// making the line that should be drawn to dissapear?!?!? ...... If I moved the strlen(str) outside of the\n\t// function call like below it works fine.\n\tint text_length = strlen(str);\n\tUI_Print(cx + (w - 8 * text_length) / 2, cy, str, red);\n}\n\nvoid UI_Print_Center3 (int cx, int cy, int w, char *str, clrinfo_t *clr, int clr_cnt, int red)\n{\n\tint text_length = strlen(str);\n\tUI_Print3(cx + (w - 8 * text_length) / 2, cy, str, clr, clr_cnt, red);\n}\n\nvoid UI_MakeLine(char *buf, int w)\n{\n\tbuf[0] = '\\x1d';\n\tmemset(buf+1, '\\x1e', w-2);\n\tbuf[w-1] = '\\x1f';\n\tbuf[w] = 0;\n}\n\nvoid UI_MakeLine2(char *buf, int w)\n{\n\tbuf[0] = '\\x80';\n\tmemset(buf+1, '\\x81', w-2);\n\tbuf[w-1] = '\\x82';\n\tbuf[w] = 0;\n}\n\nvoid UI_DrawColoredAlphaBox(int x, int y, int w, int h, color_t color)\n{\n\tDraw_AlphaFillRGB(x, y, w, h, color);\n}\n\nvoid UI_DrawGrayBox(int x, int y, int w, int h)\n{\t// ridiculous function, eh?\n\tbyte c[4];\n\tfloat fade = 1;\n\n\tmemcpy(c, menu_marked_bgcolor.color, sizeof(byte) * 4);\n\tif (menu_marked_fade.value) \n\t{\n\t\tfade = 0.5 * (1.0 + sin(menu_marked_fade.value * Sys_DoubleTime())); // this give us sinusoid from 0 to 1\n\t\tfade = 0.5 + (1.0 - 0.5) * fade ; // this give us sinusoid from 0.5 to 1, so no zero alpha\n\t\tfade = bound(0, fade, 1); // guarantee sanity if we mess somewhere\n\t}\n\n\tUI_DrawColoredAlphaBox(x, y, w, h, RGBA_TO_COLOR((fade * c[0]), (fade * c[1]), (fade * c[2]), c[3]));\n}\n\nvoid UI_DrawBox(int x, int y, int w, int h)\n{\n\tDraw_TextBox(x, y, w / 8 - 1, h / 8 - 1);\n}\n\nqbool UI_PrintTextBlock(int x, int y, int w, int h, const char* text, qbool red)\n{\n\tint letters_per_line = w / 8;\t// Letters per line.\n\tint lines = h / 8;\t\t\t\t// Lines in the text block.\n\tint linelen = 0;\t\t\t\t// Current line length.\n\tint linecount = 0;\t\t\t\t// Current line.\n\tchar buf[1024];\n\n\tconst char *start = text, *end, *wordend;\n\n\twhile (*start && linecount < lines) \n\t{\n\t\twordend = start;\n\t\tend = wordend;\n\t\tlinelen = 0;\n\n\t\t// Read the next line.\n\t\twhile (linelen < letters_per_line && *wordend) \n\t\t{\n\t\t\tend = wordend;\n\n\t\t\t// Read the next word.\n\t\t\tdo \n\t\t\t{\n\t\t\t\twordend++;\n\t\t\t\tlinelen++;\n\t\t\t}\n\t\t\twhile (*wordend && *wordend != ' ' && *wordend != '\\n');\n\t\t\t\n\t\t\t// Make sure we didn't find a word that was too long and\n\t\t\t// caused us to go outside of the line boundary.\n\t\t\tif(linelen >= letters_per_line)\n\t\t\t{\n\t\t\t\t// Try to find the last word boundary (whis\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\twordend--;\n\t\t\t\t\tlinelen--;\n\t\t\t\t}\n\t\t\t\twhile(*wordend && *wordend != ' ' && linelen > 0);\n\n\t\t\t\t// Check if failed to find a word boundary. \n\t\t\t\t// If that's the case just break the line at the max letters allowed for a line.\n\t\t\t\t// Otherwise break at the last word boundary.\n\t\t\t\tend = (linelen == 0) ? (end + letters_per_line - 1) : wordend;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Check if it's time for a new line.\n\t\t\tif (*wordend == '\\n') \n\t\t\t{\n\t\t\t\tend = wordend; \n\t\t\t\tbreak; \n\t\t\t}\n\t\t}\n\t\t\n\t\t// End of string found.\n\t\tif (!*wordend && linelen < letters_per_line)\n\t\t{\n\t\t\tend = wordend;\n\t\t}\n\n\t\t// Break the line after the word, not after the next whitespace\n\t\t// so the next line won't start with that whitespace.\n\t\twhile (*end && *end == ' ')\n\t\t{\n\t\t\tend++;\n\t\t}\n\n\t\t// Make sure we consume any newline character before printing.\n\t\tif(*start && *start == '\\n')\n\t\t{\n\t\t\tstart++;\n\t\t}\n\n\t\tstrlcpy(buf, start, end - start + 1);\n\t\tUI_Print(x, y + (linecount * 8), buf, red);\n\t\tlinecount++;\n\t\tstart = end;\n\t}\n\n\t// If the text didn't fit in there, *start won't be zero.\n\treturn !(*start);\n}\n"
  },
  {
    "path": "src/Ctrl.h",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n//    $Id: Ctrl.h,v 1.14 2007-07-10 21:20:11 cokeman1982 Exp $\n\n#ifndef __CTRL_H__\n#define __CTRL_H__\n\n#include \"keys.h\"\n\n// this can be used to determine text block proportions\n#define LETTERWIDTH 8\n#define LETTERHEIGHT 8\n\n//\n// common GUI drawing methods\n//\nvoid UI_DrawCharacter (int cx, int line, int num);\nvoid UI_Print (int cx, int cy, const char *str, int red);\nint UI_DrawSlider (int x, int y, float range);\nint UI_SliderWidth(void);\nvoid UI_Print3 (int cx, int cy, const char *str, clrinfo_t *clr, int clr_cnt, int red);\nvoid UI_Print_Center (int cx, int cy, int w, const char *str, int red);\nvoid UI_Print_Center3 (int cx, int cy, int w, char *str, clrinfo_t *clr, int clr_cnt, int red);\nvoid UI_DrawTextBox (int x, int y, int width, int lines);\nvoid UI_MakeLine(char *buf, int w);\nvoid UI_MakeLine2(char *buf, int w);\nvoid UI_DrawColoredAlphaBox(int x, int y, int w, int h, color_t color);\nvoid UI_DrawGrayBox(int x, int y, int w, int h);\nvoid UI_DrawBox(int x, int y, int w, int h);\n\n// prints the text into a box given by the area definitions (x,y, width, height)\n// returned value determines, if the text did fit in the box\nqbool UI_PrintTextBlock(int x, int y, int w, int h, const char* text, qbool red);\n\n//\n// <scrollbar>\n// if this part gets too big, it might be moved to own header (Ctrl_*.h)\n//\n\nvoid ScrollBars_Init(void);\n\ntypedef void (*ScrollPos_setter) (double);\n\ntypedef struct ScrollBar_s {\n    // current scrolling position in percentage 0-100%\n    double curpos;                      \n\n    // a function to which the scrollbar should report new scroll position\n    ScrollPos_setter    scroll_fnc;     \n\n    // should the scrollbar grab the mouse?\n    // used to be able to scroll even if the mouse is a bit off\n    qbool mouselocked;\n\n    // scrollbar width and height\n    int width, height;\n} ScrollBar, *PScrollBar;\n\n// scrollbar constructor\n// 1st parameter: a function it should call, can be NULL\nPScrollBar ScrollBar_Create(ScrollPos_setter, const char* name);\n\n// scrollbar destructor\nvoid ScrollBar_Delete(PScrollBar);\n\n// mouse event handler\n// returns true if scrollbar changed it's position\nqbool ScrollBar_MouseEvent(PScrollBar, const mouse_state_t*);\n\n// scrollbar draw - starting point (x,y) and scrollbar's height (width is given internally)\nvoid ScrollBar_Draw(PScrollBar, int x, int y, int h);\n\n//\n// </scrollbar>\n//\n\n\n#endif // __CTRL_H__\n"
  },
  {
    "path": "src/Ctrl_EditBox.c",
    "content": "/*\nCopyright (C) 2011 azazello\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"utils.h\"\n#include \"Ctrl_EditBox.h\"\n#include \"keys.h\"\n\n\nchar buf[MAX_EDITTEXT+1];\n\nvoid CEditBox_Init(CEditBox *e, int width, int max)\n{\n\te->width = width;\n\te->max = max;\n\te->pos = 0;\n\te->disp = 0;\n\te->text[0] = 0;\n}\n\nvoid CEditBox_Draw(CEditBox *e, int x, int y, qbool active)\n{\n\twhile (e->pos > e->disp + e->width - 1)\n\t\te->disp ++;\n\tif (e->disp  >  e->pos)\n\t\te->disp = e->pos;\n\tif (e->disp  >  strlen(e->text) - e->width + 1)\n\t\te->disp = strlen(e->text) - e->width + 1;\n//\tif (e->disp  <  0)\n//\t\te->disp = 0;\n\n\tsnprintf(buf, sizeof (buf), \"%-*.*s\", e->width, e->width, active ? e->text+e->disp : e->text);\n\tDraw_String(x, y, buf);\n\n\tif (active)\n\t\tDraw_Character(x+8*(e->pos-e->disp), y, 10+((int)(cls.realtime*4)&1));\n}\n\nvoid CEditBox_Key(CEditBox *e, int key, wchar unichar)\n{\n\n\tswitch (key) {\n\t\t\tcase K_LEFTARROW:\n\t\t\t\tif (e->pos != 0) {\n\t\t\t\t\te->pos--;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase K_RIGHTARROW:\n\t\t\t\te->pos++;\n\t\t\t\tbreak;\n\t\t\tcase K_HOME:\n\t\t\t\te->pos = 0;\n\t\t\t\tbreak;\n\t\t\tcase K_END:\n\t\t\t\te->pos = strlen(e->text);\n\t\t\t\tbreak;\n\t\t\tcase K_DEL:\n\t\t\t\tif (e->pos < strlen(e->text))\n\t\t\t\t\tmemmove(e->text + e->pos,\n\t\t\t\t\t\te->text + e->pos + 1,\n\t\t\t\t\t\tstrlen(e->text + e->pos + 1) + 1);\n\t\t\t\tbreak;\n\t\t\tcase K_BACKSPACE:\n\t\t\t\tif (e->pos > 0) {\n\t\t\t\t\tmemmove(e->text + e->pos - 1,\n\t\t\t\t\t\te->text + e->pos,\n\t\t\t\t\t\tstrlen(e->text + e->pos) + 1);\n\t\t\t\t\te->pos --;\n\t\t\t\t}\n\t\t\tbreak;\n\t\t\tcase 'v':\n\t\t\tcase 'V':\n\t\t\t\tif (keydown[K_CTRL]) {\n\t\t\t\t\tint len;\n\t\t\t\t\tchar *clip = ReadFromClipboard();\n\t\t\t\t\tlen = min(strlen(clip), e->max - strlen(e->text));\n\t\n\t\t\t\t\tif (len > 0) {\n\t\t\t\t\t\tmemmove(e->text + e->pos + len,\n\t\t\t\t\t\t\te->text + e->pos,\n\t\t\t\t\t\t\tstrlen(e->text + e->pos) + 1);\n\t\t\t\t\t\tmemcpy(e->text + e->pos, clip, len);\n\t\t\t\t\t\te->pos += len;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tbreak;\n\t}\n\n\te->pos = max(e->pos, 0);\n\te->pos = min(e->pos, strlen(e->text));\n\n\tif (!keydown[K_CTRL] &&\n\t\tkey >= ' '  &&  key <= '}' &&\n\t\tstrlen(e->text) < e->max)\n\t{\n\t\tmemmove(e->text + e->pos + 1,\n\t\t\te->text + e->pos,\n\t\t\tstrlen(e->text + e->pos) + 1);\n\n\t\te->text[e->pos] = unichar;\n\n\t\te->pos++;\n\t}\n}\n"
  },
  {
    "path": "src/Ctrl_EditBox.h",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/*\n * EditBox functions\n *\n *    $Id: Ctrl_EditBox.h,v 1.4 2006-05-13 07:31:57 disconn3ct Exp $\n */\n\n#ifndef __CTRL_EDITBOX_H__\n#define __CTRL_EDITBOX_H__\n\n#define MAX_EDITTEXT 255\n\ntypedef struct CEditBox_s\n{\n\tchar text[MAX_EDITTEXT+1];\n\tunsigned int width;\n\tunsigned int max;\n\tunsigned int pos;\n\tunsigned int disp;\n} CEditBox;\n\nvoid CEditBox_Init(CEditBox *e, int width, int max);\nvoid CEditBox_Draw(CEditBox *e, int x, int y, qbool active);\nvoid CEditBox_Key(CEditBox *e, int key, wchar unichar);\n\n#endif // __CTRL_EDITBOX_H__\n"
  },
  {
    "path": "src/Ctrl_PageViewer.c",
    "content": "/*\nCopyright (C) 2011 azazello\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"Ctrl.h\"\n#include <expat.h>\n#include \"xsd.h\"\n#include \"document_rendering.h\"\n#include \"Ctrl_PageViewer.h\"\n#include \"keys.h\"\n\n\nstatic void LeaveNavigationMode(CPageViewer_t *viewer)\n{\n    if (viewer->navigation_mode)\n    {\n        Q_free(viewer->current_links);\n        viewer->current_link_index = 0;\n        viewer->current_links_total = 0;\n        viewer->navigation_mode = false;\n    }\n}\n\nstatic void EnterNavigationMode(CPageViewer_t *viewer)\n{\n    int i;\n    document_rendered_link_t *from, *to;\n    int screen_start, screen_end;\n\n    if (viewer->navigation_mode)\n        LeaveNavigationMode(viewer);\n\n    screen_start = viewer->page->current_line * viewer->page->width;\n    screen_end = (viewer->page->current_line + viewer->page->last_height) * viewer->page->width;\n\n    from = viewer->page->rendered.links;\n    while (from)\n    {\n        if (from->start >= screen_start  ||  from->end >= screen_start)\n            break;\n        from = from->next;\n    }\n\n    if (from == NULL)\n        return; // no navigation\n\n    viewer->current_links_total = 0;\n    to = from;\n    while (to)\n    {\n        if (to->start >= screen_end)\n            break;\n\n        viewer->current_links_total++;\n        to = to->next;\n    }\n\n    if (viewer->current_links_total == 0)\n        return;\n\n    // make links table\n    viewer->current_links = (document_rendered_link_t **)\n        Q_malloc(viewer->current_links_total*sizeof(document_rendered_link_t *));\n    i = 0;\n    while (from != to)\n    {\n        viewer->current_links[i++] = from;\n        from = from->next;\n    }\n    viewer->navigation_mode = true;\n    if (keydown[K_SHIFT])\n        viewer->current_link_index = viewer->current_links_total - 1;\n    else\n        viewer->current_link_index = 0;\n}\n\nstatic void MakeString(char *buf, byte *mem, int len)\n{\n    int i;\n    for (i=0; i < len; i++)\n        buf[i] = mem[i] == 0 ? ' ' : mem[i];\n    buf[len] = 0;\n}\n\nstatic void FreePageRendered(CPageViewer_page_t *page)\n{\n    XSD_RenderClear(&page->rendered);\n}\n\nstatic void FreePage(CPageViewer_page_t *page)\n{\n    FreePageRendered(page);\n    if (page->url)\n        Q_free(page->url);\n    if (page->doc)\n        XSD_FreeDocument((xml_t *)page->doc);\n    Q_free(page);\n    return;\n}\n\nstatic void RenderDocument(CPageViewer_t *viewer, int width)\n{\n    // int lines;\n    xml_document_t *doc;\n\n    LeaveNavigationMode(viewer);\n    FreePageRendered(viewer->page);\n\n    viewer->page->current_line = 0;\n    viewer->page->width = width;\n    viewer->page->should_render = false;\n\n    // load document\n    if (!viewer->page->doc)\n        viewer->page->doc = XSD_LoadDocumentWithXsl(viewer->page->url);\n\n    if (!viewer->page->doc)\n        goto error;\n\n    doc = viewer->page->doc;\n\n    if (!XSD_RenderDocument(&viewer->page->rendered, doc, width))\n        goto error;\n\n    return;\n\nerror:\n\n    viewer->page->rendered.title = (char *) Q_malloc(viewer->page->width);\n    memset(viewer->page->rendered.title, 0, width);\n    memcpy(viewer->page->rendered.title, \"Document loading error!\", strlen(\"Document loading error!\"));\n    viewer->page->rendered.title_lines = 1;\n    return;\n}\n\nstatic void AddPage(CPageViewer_t *viewer)\n{\n    CPageViewer_page_t *page = (CPageViewer_page_t *) Q_malloc(sizeof(CPageViewer_page_t));\n    memset(page, 0, sizeof(CPageViewer_page_t));\n\n    page->next = viewer->page;\n    viewer->page = page;\n}\n\nvoid CPageViewer_Init(CPageViewer_t *viewer)\n{\n    memset(viewer, 0, sizeof(CPageViewer_t));\n\n    viewer->show_status = true;\n    viewer->show_title = true;\n}\n\nCPageViewer_t * CPageViewer_New(void)\n{\n    CPageViewer_t *viewer = (CPageViewer_t *) Q_malloc(sizeof(CPageViewer_t));\n\n    CPageViewer_Init(viewer);\n    return viewer;\n}\n\n// load document by url\nvoid CPageViewer_GoUrl(CPageViewer_t *viewer, char *url)\n{\n    AddPage(viewer);\n\n    viewer->page->url = Q_strdup(url);\n    viewer->page->should_render = true;\n}\n\n// load document by xml_document_t\nvoid CPageViewer_Go(CPageViewer_t *viewer, char *url, xml_document_t *doc)\n{\n    AddPage(viewer);\n\n    viewer->page->url = Q_strdup(url);\n    viewer->page->doc = doc;\n    viewer->page->should_render = true;\n}\n\nvoid CPageViewer_Draw(CPageViewer_t *viewer, int x, int y, int w, int h)\n{\n    int i;\n    document_rendered_link_t *link;\n    int line;\n    char buf[512];\n    int sx, sy, sh;\n\n    // change x, y, w, h\n    x = x + (w - 8*(w/8)) /2;\n    w = w/8;\n    y = y + (h - 8*(h/8)) /2;\n    h = h/8;\n\n    if (w < 20  ||  h < 10)\n        return;\n\n    if (viewer->page == NULL)\n        return;\n\n    sx = x;\n    sy = y;\n    sh = h;\n\n    if (viewer->page->width != w)\n        viewer->page->should_render = true;\n\n    if (viewer->page->should_render)\n        RenderDocument(viewer, w);\n\n    // current link, if in navigation mode\n    link = viewer->navigation_mode ? viewer->current_links[viewer->current_link_index] : NULL;\n\n    // draw title bar\n    /*\n    if (viewer->page->rendered.title  &&  viewer->show_title)\n    {\n        for (line = 0; line < viewer->page->rendered.title_lines; line++)\n        {\n            MakeString(buf, viewer->page->rendered.title + line*viewer->page->width, viewer->page->width);\n            UI_Print(x, y + line*8, buf, false);\n        }\n\n        UI_MakeLine(buf, viewer->page->width/2);\n        UI_Print_Center(x, y + line*8, viewer->page->width*8, buf, false);\n\n        sh -= line + 1;\n        sy += (line + 1) * 8;\n    }\n    */\n\n    // calculate scrollable area yet\n    if (viewer->show_status)\n        sh -= 2;\n    if (viewer->page->rendered.text_lines - viewer->page->current_line < sh)\n        viewer->page->current_line -= sh - (viewer->page->rendered.text_lines - viewer->page->current_line);\n    if (viewer->page->current_line < 0)\n        viewer->page->current_line = 0;\n\n    // draw status bar\n    if (viewer->show_status)\n    {\n        if (viewer->navigation_mode)\n        {\n            if (strlen (link->tag->href) > w)\n            {\n                strlcpy (buf, link->tag->href, min (sizeof (buf), w-3));\n                strlcat (buf, \"...\", sizeof (buf));\n            }\n            else\n            {\n                int offset = (w - strlen(link->tag->href)) / 2;\n                memset(buf, ' ', offset);\n                strlcpy (buf + offset, link->tag->href, sizeof (buf) - offset);\n            }\n            UI_Print(x, y + (h-1)*8, buf, false);\n        }\n        else\n        {\n            snprintf(buf, sizeof (buf), \"%d lines  \", viewer->page->rendered.text_lines);\n            if (sh >= viewer->page->rendered.text_lines)\n                strlcat(buf, \"[full]\", sizeof (buf));\n            else if (viewer->page->current_line == 0)\n                strlcat(buf, \"[top]\", sizeof (buf));\n            else if (viewer->page->current_line + sh == viewer->page->rendered.text_lines)\n                strlcat(buf, \"[bottom]\", sizeof (buf));\n            else\n            {\n                int percent = (100*(viewer->page->current_line + sh)) / viewer->page->rendered.text_lines;\n                strlcat(buf, va(\"[%d%%]\", percent), sizeof (buf));\n            }\n\n            UI_Print(x+8*(w-strlen(buf)-1), y + (h-1)*8, buf, false);\n\n            if (viewer->page->doc  &&  viewer->page->doc->title)\n            {\n                int l = w - strlen(buf) - 3;\n                if (strlen(viewer->page->doc->title) <= l)\n                {\n                    UI_Print(x+8, y + (h-1)*8, viewer->page->doc->title, false);\n                }\n                else\n                {\n                    strlcpy(buf, viewer->page->doc->title, min (sizeof (buf), l-3));\n                    strlcat(buf, \"...\", sizeof (buf));\n                    UI_Print(x+8, y + (h-1)*8, buf, false);\n                }\n            }\n        }\n        UI_MakeLine(buf, w);\n        UI_Print_Center(x, y + (h-2)*8, w*8, buf, false);\n    }\n\n    // process with scrollable area\n    for (line = 0;  line < min(sh, viewer->page->rendered.text_lines - viewer->page->current_line); line++)\n    {\n        int start, end;\n\tMakeString(buf, (byte *) viewer->page->rendered.text + (line+viewer->page->current_line)*viewer->page->width, viewer->page->width);\n\n        if (link)\n        {\n            start = max(link->start, (viewer->page->current_line + line) * viewer->page->width);\n            end = min(link->end, (viewer->page->current_line + line + 1) * viewer->page->width);\n            if (end > start)\n            {\n                start -= (viewer->page->current_line + line) * viewer->page->width;\n                end -= (viewer->page->current_line + line) * viewer->page->width;\n                for (i=start; i < end; i++)\n                    buf[i] ^= 128;\n            }\n        }\n\n        UI_Print(sx, sy + line*8, buf, false);\n    }\n    viewer->page->last_height = sh;\n}\n\nqbool CPageViewer_Key(CPageViewer_t *viewer, int key, wchar unichar)\n{\n    qbool ret = false;\n\n    if (viewer->page == NULL)\n        return 0;\n\n    if (viewer->navigation_mode)\n    {\n        char *href;\n        switch (key)\n        {\n        case K_TAB:\n            viewer->current_link_index += keydown[K_SHIFT] ? -1 : 1;\n            viewer->current_link_index = (viewer->current_link_index +\n                viewer->current_links_total) % viewer->current_links_total;\n            ret = true;\n            break;\n        case K_ENTER:\n            href = viewer->current_links[viewer->current_link_index]->tag->href;            if (href)\n            {\n                LeaveNavigationMode(viewer);\n                CPageViewer_GoUrl(viewer, href);\n            }            ret = true;\n            break;\n        case K_SHIFT:\n            break;\n        default:\n            LeaveNavigationMode(viewer);\n            ret = true;\n            break;\n        }\n    }\n\n    if (!viewer->navigation_mode)\n    {\n        switch (key)\n        {\n        case K_UPARROW:\n\t\tcase K_MWHEELUP:\n            viewer->page->current_line--;\n            ret = true;\n            break;\n        case K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n            viewer->page->current_line++;\n            ret = true;\n            break;\n        case K_PGUP:\n            viewer->page->current_line -= viewer->page->last_height-1;\n            ret = true;\n            break;\n        case K_PGDN:\n            viewer->page->current_line += viewer->page->last_height-1;\n            ret = true;\n            break;\n        case K_HOME:\n            viewer->page->current_line = 0;\n            ret = true;\n            break;\n        case K_END:\n            viewer->page->current_line = viewer->page->rendered.text_lines;\n            ret = true;\n            break;\n        case K_BACKSPACE:\n            CPageViewer_Back(viewer, 1);\n            ret = true;\n            break;\n        case K_TAB:\n            EnterNavigationMode(viewer);\n            ret = true;\n            break;\n        }\n    }\n    return ret;\n}\n\n// go back\nvoid CPageViewer_Back(CPageViewer_t *viewer, int level)\n{\n    if (viewer->page == NULL)\n        return;\n\n    while (viewer->page->next && level--)\n    {\n        CPageViewer_page_t *page = viewer->page->next;\n\n        FreePage(viewer->page);\n        viewer->page = page;\n    }\n}\n\nvoid CPageViewer_Clear(CPageViewer_t *viewer)\n{\n    CPageViewer_page_t *next;\n\n    LeaveNavigationMode(viewer);\n\n    // free all pages\n    while (viewer->page)\n    {\n        next = viewer->page->next;\n\n        FreePage(viewer->page);\n        viewer->page = next;\n    }\n\n    // clear\n    CPageViewer_Init(viewer);\n}\n\nvoid CPageViewer_Free(CPageViewer_t *viewer)\n{\n    // clear all data\n    CPageViewer_Clear(viewer);\n}\n"
  },
  {
    "path": "src/Ctrl_PageViewer.h",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/*\n * EditBox functions\n *\n */\n\n#ifndef __CTRL_PAGEVIEWER_H__\n#define __CTRL_PAGEVIEWER_H__\n\n\n\ntypedef struct CPageViewer_page_s\n{\n    char *url;\n    xml_document_t *doc;\n\n    document_rendered_t rendered;\n\n    int current_line;\n    int width;\n    qbool should_render;\n\n    // current settings\n    int last_height;\n\n    // previous page in history\n    struct CPageViewer_page_s *next;\n}\nCPageViewer_page_t;\n\ntypedef struct CPageViewer_s\n{\n    CPageViewer_page_t *page;\n\n    // options\n    qbool show_title;\n    qbool show_status;\n\n    // navigation mode\n    qbool navigation_mode;\n    document_rendered_link_t **current_links;\n    int current_link_index;\n    int current_links_total;\n}\nCPageViewer_t;\n\n// initialize CPageViewer control\nvoid CPageViewer_Init(CPageViewer_t *);\n\n// create new viewer\nCPageViewer_t * CPageViewer_New(void);\n\n// load document by url\nvoid CPageViewer_GoUrl(CPageViewer_t *viewer, char *url);\n\n// load document by xml_document_t\nvoid CPageViewer_Go(CPageViewer_t *viewer, char *url, xml_document_t *doc);\n\n// go back\nvoid CPageViewer_Back(CPageViewer_t *viewer, int level);\n\n// draw control\nvoid CPageViewer_Draw(CPageViewer_t *viewer, int x, int y, int w, int h);\n\n// handle keyboard\nqbool CPageViewer_Key(CPageViewer_t *viewer, int key, wchar unichar);\n\n// clear control\nvoid CPageViewer_Clear(CPageViewer_t *viewer);\n\n// delete control\nvoid CPageViewer_Free(CPageViewer_t *viewer);\n\n#endif // __CTRL_PAGEVIEWER_H__\n"
  },
  {
    "path": "src/Ctrl_ScrollBar.c",
    "content": "/*\nCopyright (C) 2011 johnnycz\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/**\n    GUI Control: ScrollBar\n\n    Include and read Ctrl.h to use scrollbars\n\n    made by:\n        johnnycz, Mar 2007\n    last edit:\n        $Id: Ctrl_ScrollBar.c,v 1.7 2007-07-19 19:12:00 cokeman1982 Exp $\n\n*/\n\n#include \"quakedef.h\"\n#include \"keys.h\"\n#include \"Ctrl.h\"\n\n#define SCRBARSCALE 0.33\n\n// PNG scrollbar images\nstatic mpic_t *scrbar_up, *scrbar_down, *scrbar_bg, *scrbar_slider;\nint scrollbar_width;\nint slider_height;\n\nvoid ScrollBars_Init(void)\n{\n\tscrbar_bg = Draw_CachePicSafe(\"textures/scrollbars/slidebg\", false, true);\n\tscrbar_up = Draw_CachePicSafe(\"textures/scrollbars/arrow_up\", false, true);\n\tscrbar_down = Draw_CachePicSafe(\"textures/scrollbars/arrow_down\", false, true);\n    scrbar_slider = Draw_CachePicSafe(\"textures/scrollbars/slider\", false, true);\n    if (scrbar_slider) \n\t{\n        scrollbar_width = scrbar_slider->width * SCRBARSCALE;\n        slider_height = scrbar_slider->height * SCRBARSCALE;\n    } \n\telse\n    {\n        scrollbar_width = 8;\n        slider_height = 8;\n    }\n}\n\nPScrollBar ScrollBar_Create(ScrollPos_setter pos_setter, const char* name)\n{\n    PScrollBar scb_new = Q_malloc_named(sizeof(ScrollBar), name);\n\n    if (!scb_new) {\n        return NULL;\n    }\n\n    scb_new->curpos = 0;\n    scb_new->mouselocked = false;\n    scb_new->scroll_fnc = pos_setter;\n    scb_new->width = scrollbar_width;\n    return scb_new;\n}\n\n// scrollbar destructor\nvoid ScrollBar_Delete(PScrollBar scrbar)\n{\n    Q_free (scrbar);\n}\n\n// mouse event handler\nqbool ScrollBar_MouseEvent(PScrollBar scrbar, const mouse_state_t *ms)\n{\n    // check if there is some reason to react on this event\n    if (!ms->button_down && !ms->button_up && !ms->buttons[1])\n        return false;   \n\n    if (ms->button_up)\n    {\n        scrbar->mouselocked = false;\n    }\n    else // button_down or mousemove\n    {\n        double y = ms->y - scrollbar_width - slider_height/2;\n        double ah = scrbar->height - scrollbar_width*2 - slider_height; \n\n        scrbar->mouselocked = true;\n        y = bound(0, y, ah);\n        scrbar->curpos = y / ah;\n        scrbar->curpos = bound(0, scrbar->curpos, 1);\n    }\n\n    if (scrbar->scroll_fnc)\n        scrbar->scroll_fnc(scrbar->curpos);\n\n    return true;\n}\n\nstatic void SCRB_DrawPics(PScrollBar scrbar, int x, int y, int h)\n{\n    int w = scrollbar_width;\n\n    // Height of one background image.\n    int sh = max(1, (scrbar_bg->height) * SCRBARSCALE);\n   \n\t// How many complete background images fit in here\n    int compl_bgs = h / sh;    \n\n    // Height of the part of the last background image.\n    int rest_bgh = (h - compl_bgs * sh) / SCRBARSCALE;\n    int i;\n    \n    for (i = 0; i < compl_bgs; i++)\n        Draw_SPic(x, y + i * sh, scrbar_bg, SCRBARSCALE);\n\n    // Add the last part to fill the whole background.\n    Draw_SSubPic(x, y + i * sh, scrbar_bg, 0, 0, scrbar_bg->width, rest_bgh, SCRBARSCALE);\n    \n    // Draw the remaining parts of the scrollbar.\n    Draw_SPic(x, y, scrbar_up, SCRBARSCALE);\n    Draw_SPic(x, y + h - w, scrbar_down, SCRBARSCALE);\n    Draw_SPic(x, y + w + (h - 2 * w - slider_height) * scrbar->curpos, scrbar_slider, SCRBARSCALE);\n}\n\n\nstatic void SCRB_DrawNoPics(PScrollBar scrbar, int x, int y, int h)\n{\n\t#define SCROLLBAR_QCOLOR\t\t4\n\t#define SCROLLBAR_BUTTON_QCOLOR\t72\n\t#define SCROLLBAR_SLIDER_QCOLOR\t40\n    int w = scrollbar_width;\n\n    Draw_Fill(x, y, w, h, SCROLLBAR_QCOLOR);\n    Draw_Fill(x, y, w, w, SCROLLBAR_BUTTON_QCOLOR);\n    Draw_Fill(x, y + h - w, w, w, SCROLLBAR_BUTTON_QCOLOR);\n    Draw_Fill(x, y + w + (h - 3 * w) * scrbar->curpos, w, w, SCROLLBAR_SLIDER_QCOLOR);\n}\n\nvoid ScrollBar_Draw(PScrollBar scrbar, int x, int y, int h)\n{\n    scrbar->height = h;\n\n\tif (scrbar_up && scrbar_bg && scrbar_down && scrbar_slider)\n\t\tSCRB_DrawPics(scrbar, x, y, h);\n\telse\n\t\tSCRB_DrawNoPics(scrbar, x, y, h);\n}\n"
  },
  {
    "path": "src/Ctrl_Tab.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"Ctrl.h\"\n#include \"Ctrl_Tab.h\"\n#include \"keys.h\"\n#include \"qsound.h\"\n\n// initialize control\nvoid CTab_Init(CTab_t *tab)\n{\n\tmemset(tab, 0, sizeof(CTab_t));\n\ttab->lastViewedPage = -1;\n\ttab->hoveredPage = -1;\n}\n\n\n// clear control\nvoid CTab_Clear(CTab_t *tab)\n{\n\t// no dynamic data, just clear it\n\tCTab_Init(tab);\n}\n\n\n// create new control\nCTab_t * CTab_New(void)\n{\n\tCTab_t *tab = (CTab_t *) Q_malloc(sizeof(CTab_t));\n\tCTab_Init(tab);\n\treturn tab;\n}\n\n\n// free control\nvoid CTab_Free(CTab_t *tab)\n{\n\tCTab_Clear(tab);\n\tQ_free(tab);\n}\n\n\n// add tab\nvoid CTab_AddPage(CTab_t *tab, const char *name, int id, const CTabPage_Handlers_t *handlers)\n{\n\tint i;\n\tCTabPage_t *page;\n\n\tif (tab->nPages >= TAB_MAX_TABS)\n\t{\n\t\tSys_Error(\"CTab_AddPage: Maximum page number reached \"\n\t\t\t\t\"while adding %s\", name);\n\t\treturn;\n\t}\n\n\tfor (i=0; i < tab->nPages; i++)\n\t{\n\t\tif (tab->pages[i].id == id)\n\t\t\tSys_Error(\"CTab_AddPage: such id already exist\");\n\t\tif (!strcmp(tab->pages[i].name, name))\n\t\t\tSys_Error(\"CTab_AddPage: such name already exist\");\n\t}\n\n\tpage = &tab->pages[tab->nPages++];\n\n\t// set name\n\tstrlcpy (page->name, name, sizeof (page->name));\n\n\t// set id\n\tpage->id = id;\n\n\t// set handlers\n\tmemcpy(&page->handlers, handlers, sizeof(CTabPage_Handlers_t));\n}\n\n// will draw tab navigation bar, remember labels positions\n// and will return the width of the navigation bar\nstatic int CTab_Draw_PageLinks(CTab_t *tab, int x, int y, int w, int h)\n{\n\tchar buf[TAB_MAX_NAME_LENGTH+3];\n\tint i, cx, cy, ww, wh;          // current x/y, word width/height\n\tqbool ap, hp;                   // active page / hovered page\n\n\tcx = 0; cy = 0;\n\tfor (i = 0; i < tab->nPages; i++)\n\t{\n\t\t*buf = 0;\n\t\tap = tab->activePage == i;\n\t\thp = tab->hoveredPage == i;\n\n\t\t// add leading space/brace\n\t\tstrlcat (buf, ap ? \"\\x10\" : \" \", sizeof (buf));\n\n\t\t// adds white or red variant of the page name to the buf string\n\t\tstrlcat (buf, tab->pages[i].name, sizeof (buf));\n\n\t\t// add closing space/brace\n\t\tstrlcat (buf, ap ? \"\\x11\" : \" \", sizeof (buf));\n\n\t\tww = strlen(buf) * LETTERWIDTH;\n\t\twh = LETTERHEIGHT;\n\n\t\t// this is not the first word we are printing and we don't fit on the line anymore\n\t\tif (cx && (cx + ww > w)) {\n\t\t\t// so wrap the line already\n\t\t\tcx = 0;\n\t\t\tcy += LETTERHEIGHT;\n\t\t}\n\n\t\tUI_Print(x + cx, y + cy, buf, ap || hp);\n\n\t\t// remember where the text was so we can point to it with the mouse later\n\t\ttab->navi_boxes[i].x = cx;\n\t\ttab->navi_boxes[i].y = cy;\n\t\ttab->navi_boxes[i].x2 = cx+ww-LETTERWIDTH;\n\t\ttab->navi_boxes[i].y2 = cy+wh;\n\n\t\t// we substract 1 letter here and above because we want only one (common) space\n\t\t// between the labels, not two (one for each)\n\t\tcx += ww - LETTERWIDTH;\n\t}\n\n\treturn cy + LETTERHEIGHT;\n}\n\n// draw control\nvoid CTab_Draw(CTab_t *tab, int x, int y, int w, int h)\n{\n\tchar line[1024];\n\tint nav_height;\n\n\tif (tab->activePage != tab->lastViewedPage && tab->activePage < tab->nPages)\n\t{\n\t\tCTabPage_OnShowType onshowFnc = tab->pages[tab->activePage].handlers.onshow;\n\t\tif (onshowFnc != NULL) onshowFnc();\n\t\ttab->lastViewedPage = tab->activePage;\n\t}\n\n\ttab->width = w;\n\ttab->height = h;\n\n\tnav_height = CTab_Draw_PageLinks(tab, x, y, w, h);\n\n\t// draw separator\n\tmemset(line, '\\x1E', w/8);\n\tline[w/8] = 0;\n\tline[w/8-1] = '\\x1F';\n\tline[0] = '\\x1D';\n\t// memcpy(line + 2, \" \\x10shift\\x11+\\x10tab\\x11 \", min((w/8)-3, 15));\n\t// memcpy(line + w/8 - 2 - 7, \" \\x10tab\\x11 \", 7);\n\tUI_Print_Center(x, y + nav_height, w, line, false);\n\tnav_height += 8;\n\n\t// draw page\n\tif (tab->pages[tab->activePage].handlers.draw != NULL)\n\t\ttab->pages[tab->activePage].handlers.draw(x, y + nav_height, w, h- nav_height, tab, &tab->pages[tab->activePage]);\n}\n\n\n// process key\nint CTab_Key(CTab_t *tab, int key, wchar unichar)\n{\n\tint handled;\n\n\tif (tab->hoveredPage >= 0 && key == K_MOUSE1)\n\t{\n\t\ttab->activePage = tab->hoveredPage;\n\t\treturn true;\n\t}\n\n\t// we first call tabs handlers, because they might override\n\t// default tab keys for modal dialogs\n\t// tabs are responsible for not using \"our\" keys for other\n\t// purposes\n\thandled = false;\n\tif (tab->pages[tab->activePage].handlers.key != NULL)\n\t\thandled = tab->pages[tab->activePage].handlers.key(key, unichar, tab, &tab->pages[tab->activePage]);\n\n\t// then try our handlers\n\tif (!handled)\n\t{\n\t\tswitch (key)\n\t\t{\n\t\t\tcase K_PGUP:\n\t\t\tcase K_MOUSE4:\n\t\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\t\ttab->activePage--;\n\t\t\t\thandled = true;\n\t\t\t\tbreak;\n\n\t\t\tcase K_PGDN:\n\t\t\tcase K_MOUSE5:\n\t\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\t\ttab->activePage++;\n\t\t\t\thandled = true;\n\t\t\t\tbreak;\n\n\t\t\tcase K_LEFTARROW:\n\t\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\t\ttab->activePage--;\n\t\t\t\thandled = true;\n\t\t\t\tbreak;\n\n\t\t\tcase K_RIGHTARROW:\n\t\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\t\ttab->activePage++;\n\t\t\t\thandled = true;\n\t\t\t\tbreak;\n\n\t\t\tcase K_TAB:\n\t\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\t\tif (keydown[K_SHIFT]) tab->activePage--; else tab->activePage++;\n\t\t\t\thandled = true;\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (handled)\n\t\t\ttab->activePage = (tab->activePage + tab->nPages) % tab->nPages;\n\t}\n\n\t// return handled status\n\treturn handled;\n}\n\nstatic qbool CTab_Navi_Mouse_Event (CTab_t *tab, const mouse_state_t *ms) \n{\n\tint i;\n\tif (!tab->width) return false;\n\n\tif (ms->button_up == 1) {\n\t\tCTab_Key(tab, K_MOUSE1, 0);\n\t\treturn true;\n\t} else if (ms->button_up == 2) {\n\t\tCTab_Key(tab, K_MOUSE2, 0);\n\t\treturn true;\n\t}\n\n\tfor (i = 0; i < tab->nPages; i++)\n\t{\n\t\tif (!(tab->navi_boxes[i].x > ms->x || tab->navi_boxes[i].y > ms->y || tab->navi_boxes[i].x2 < ms->x || tab->navi_boxes[i].y2 < ms->y))\n\t\t{   // pointer is within the bounds\n\t\t\ttab->hoveredPage = i;\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nqbool CTab_Mouse_Event(CTab_t *tab, const mouse_state_t *ms)\n{\n\tint nav_height = tab->navi_boxes[tab->nPages-1].y2;\n\n\tif (ms->x < 0 || ms->x > tab->width || ms->y < 0 || ms->y > tab->height)\n\t\treturn false;\n\n\tif (ms->y <= nav_height)\n\t{   // pointer is in the navigation area\n\t\treturn CTab_Navi_Mouse_Event(tab, ms);\n\t}\n\telse if (ms->y >= nav_height + LETTERHEIGHT) \n\t{   // pointer is in the main area\n\t\tCTabPage_MouseMoveType mmf;\n\t\tmouse_state_t lms = *ms;\n\n\t\tlms.y -= nav_height + LETTERHEIGHT;\n\t\ttab->hoveredPage = -1;\n\n\t\tmmf = tab->pages[tab->activePage].handlers.mousemove;\n\t\tif (mmf)\n\t\t\treturn mmf(&lms);\n\t}\n\n\treturn false;\n}\n\n// get current page\nCTabPage_t * CTab_GetCurrent(CTab_t *tab)\n{\n\treturn &tab->pages[tab->activePage];\n}\n\n\n// get current page id\nint CTab_GetCurrentId(CTab_t *tab)\n{\n\treturn tab->pages[tab->activePage].id;\n}\n\n\n// get current page name\nchar * CTab_GetCurrentPage(CTab_t *tab)\n{\n\treturn tab->pages[tab->activePage].name;\n}\n\n\n// set current page by ID\nvoid CTab_SetCurrentId(CTab_t *tab, int id)\n{\n\tint i;\n\tfor (i=0; i < tab->nPages; i++)\n\t{\n\t\tif (tab->pages[i].id == id)\n\t\t{\n\t\t\ttab->activePage = i;\n\t\t\treturn;\n\t\t}\n\t}\n\tSys_Error(\"CTab_SetCurrentId: id not found\");\n}\n\n\n// set current page by string\nvoid SetCurrentPage(CTab_t *tab, char *name)\n{\n\tint i;\n\tfor (i=0; i < tab->nPages; i++)\n\t{\n\t\tif (!strcmp(tab->pages[i].name, name))\n\t\t{\n\t\t\ttab->activePage = i;\n\t\t\treturn;\n\t\t}\n\t}\n\tSys_Error(\"CTab_SetCurrentName: name not found\");\n}\n\n"
  },
  {
    "path": "src/Ctrl_Tab.h",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n// Tab Control\n\n#ifndef __CTRL_TAB_H__\n#define __CTRL_TAB_H__\n\n#include \"keys.h\"\n\n#define TAB_MAX_NAME_LENGTH     15\n#define TAB_MAX_TABS            16\n\n\nstruct CTab_s;\nstruct CTabPage_s;\n\ntypedef void (*CTabPage_OnShowType) (void);\ntypedef void (*CTabPage_DrawType)(int x, int y, int w, int h, struct CTab_s *, struct CTabPage_s *);\ntypedef int  (*CTabPage_KeyType)(int key, wchar unichar, struct CTab_s *, struct CTabPage_s *);\ntypedef qbool (*CTabPage_MouseMoveType) (const mouse_state_t * ms);\n\ntypedef struct CTabPage_Handlers_s\n{\n    CTabPage_DrawType   draw;\n    CTabPage_KeyType    key;\n    CTabPage_OnShowType onshow;\n\tCTabPage_MouseMoveType mousemove;\n}\nCTabPage_Handlers_t;\n\ntypedef struct CTabPage_s\n{\n    char    name[TAB_MAX_NAME_LENGTH+1];\n    int     id;\n\tCTabPage_Handlers_t handlers;\n}\nCTabPage_t;\n\ntypedef struct CTab_s\n{\n    CTabPage_t  pages[TAB_MAX_TABS];\n    int         nPages;\n    int         activePage;\n\tint\t\t\tlastViewedPage;\n\tint\t\t\thoveredPage;\n\tint\t\t\twidth;\n\tint\t\t\theight;\n    struct { int x, y, x2, y2; } navi_boxes[TAB_MAX_TABS];\n    void *tag;\n}\nCTab_t;\n\n\n// initialize control\nvoid CTab_Init(CTab_t *);\n\n// clear control\nvoid CTab_Clear(CTab_t *);\n\n// create new control\nCTab_t * CTab_New(void);\n\n// free control\nvoid CTab_Free(CTab_t *);\n\n// add tab\nvoid CTab_AddPage(CTab_t *, const char *name, int id, const CTabPage_Handlers_t *handlers);\n// draw control\nvoid CTab_Draw(CTab_t *, int x, int y, int w, int h);\n\n// process key\nint CTab_Key(CTab_t *, int key, wchar unichar);\n\n// process mouse move event\nqbool CTab_Mouse_Event(CTab_t *, const mouse_state_t *ms);\n\n// get current page\nCTabPage_t * CTab_GetCurrent(CTab_t *);\n\n// get current page id\nint CTab_GetCurrentId(CTab_t *);\n\n// get current page name\nchar * CTab_GetCurrentPage(CTab_t *);\n\n// set current page by ID\nvoid CTab_SetCurrentId(CTab_t *, int);\n\n// set current page by string\nvoid CTab_SetCurrentPage(CTab_t *, char *);\n\n\n\n#endif // __CTRL_TAB_H__\n"
  },
  {
    "path": "src/EX_FileList.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/*\n * File List\n *\n * display directory contents, allows for changing dir,\n * drive etc..\n *\n * meant for use by MP3 player, mayby demo player too\n *\n * FileList have four colums:\n *  - file name (with ext)\n *  - size\n *  - date (last modification)\n *  - time (last modification)\n *\n * and allows to display different sets of columns\n * also do sorting\n *\n */\n\n#include \"quakedef.h\"\n#include \"localtime.h\"\n#include \"Ctrl.h\"\n#include \"EX_FileList.h\"\n#include \"utils.h\"\n#include \"keys.h\"\n#include \"hash.h\"\n#include \"fs.h\"\n#include \"vfs.h\"\n\n\n// column width\n#define COL_SIZE        4\n#define COL_DATE        8\n#define COL_TIME        5\n\n#define FL_SEARCH_TIMEOUT\t2.0\nstatic double last_search_enter_time = 0.0;\n\n\nstatic void OnChange_file_browser_sort_mode(cvar_t *var, char *string, qbool *cancel);\n\nstatic cvar_t  file_browser_show_size       = {\"file_browser_show_size\",      \"1\"};\nstatic cvar_t  file_browser_show_date       = {\"file_browser_show_date\",      \"1\"};\nstatic cvar_t  file_browser_show_time       = {\"file_browser_show_time\",      \"0\"};\nstatic cvar_t  file_browser_sort_mode       = {\"file_browser_sort_mode\",      \"1\",\tCVAR_NONE, OnChange_file_browser_sort_mode};\nstatic cvar_t  file_browser_show_status     = {\"file_browser_show_status\",    \"1\"};\nstatic cvar_t  file_browser_strip_names     = {\"file_browser_strip_names\",    \"1\"};\nstatic cvar_t  file_browser_interline       = {\"file_browser_interline\",      \"0\"};\nstatic cvar_t  file_browser_scrollnames     = {\"file_browser_scrollnames\" ,   \"1\"};\nstatic cvar_t  file_browser_selected_color  = {\"file_browser_selected_color\", \"0 150 235 255\", CVAR_COLOR};\nstatic cvar_t  file_browser_file_color      = {\"file_browser_file_color\",     \"255 255 255 255\", CVAR_COLOR};\nstatic cvar_t  file_browser_dir_color       = {\"file_browser_dir_color\",      \"170 80 0 255\", CVAR_COLOR};\nstatic cvar_t  file_browser_archive_color   = {\"file_browser_archive_color\",  \"255 170 0 255\", CVAR_COLOR};\nstatic cvar_t  file_browser_sort_archives   = {\"file_browser_sort_archives\",  \"0\",\tCVAR_NONE, OnChange_file_browser_sort_mode };\n\nvoid EX_FileList_Init(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_MENU);\n\tCvar_Register(&file_browser_show_size);\n\tCvar_Register(&file_browser_show_date);\n\tCvar_Register(&file_browser_show_time);\n\tCvar_Register(&file_browser_sort_mode);\n\tCvar_Register(&file_browser_show_status);\n\tCvar_Register(&file_browser_strip_names);\n\tCvar_Register(&file_browser_interline);\n\tCvar_Register(&file_browser_scrollnames);\n\tCvar_Register(&file_browser_file_color);\n\tCvar_Register(&file_browser_selected_color);\n\tCvar_Register(&file_browser_dir_color);\n\tCvar_Register(&file_browser_archive_color);\n\tCvar_Register(&file_browser_sort_archives);\n\tCvar_ResetCurrentGroup();\n}\n\n//\n// Create list\n//\nvoid FL_Init(filelist_t\t*fl, char *initdir)\n{\n\tSys_getcwd(fl->current_dir, MAX_PATH);\n\tFL_SetCurrentDir(fl, initdir);\n\tfl->error = false;\n\tfl->need_refresh = true;\n\tfl->num_entries = 0;\n\tfl->current_entry = 0;\n\tfl->num_filetypes = 0;\n\n\tfl->search_string[0] = 0;\n\tfl->last_page_size = 0;\n\n\tfl->search_valid = false;\n\tfl->cdup_find = false;\n\n\tfl->show_dirup = true;\n\tfl->show_dirs = true;\n\n\tfl->scrollbar = ScrollBar_Create(NULL, fl->current_dir);\n\n#ifdef WITH_ZIP\n\tfl->current_archive[0] = 0;\n\tfl->in_archive = false;\n#endif // WITH_ZIP\n}\n\nvoid FL_Shutdown(filelist_t* fl)\n{\n\tQ_free(fl->scrollbar);\n}\n\n//\n// Set directory\n//\nvoid FL_SetCurrentDir(filelist_t *fl, const char *dir)\n{\n\tchar buf[MAX_PATH+1];\n\n\tif (Sys_fullpath(buf, dir, sizeof(buf)) == NULL) {\n\t\treturn;\n\t}\n\n\tif (strlen(buf) > MAX_PATH) {\n\t\t// Should never fail in this\n\t\treturn;\n\t}\n\n\t// Try changing directory, block if this fails\n\t{\n\t\tchar olddir[MAX_PATH + 1];\n\n\t\t// Save the current directory. (we want to restore this later)\n\t\tif (Sys_getcwd(olddir, MAX_PATH + 1) == NULL) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Change to the new dir.\n\t\tif (!Sys_chdir(buf)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// restore\n\t\tSys_chdir(olddir);\n\t}\n\n\tstrlcpy(fl->current_dir, buf, sizeof(fl->current_dir));\n\tfl->need_refresh = true;\n}\n\n//\n// Get current directory\n//\nchar *FL_GetCurrentDir(filelist_t *fl)\n{\n\treturn fl->current_dir;\n}\n\n//\n// add new file type (.qwd, .qwz, .mp3)\n//\nvoid FL_AddFileType(filelist_t *fl, int id, char *ext)\n{\n\tint num = fl->num_filetypes;\n\n\tif (num >= MAX_FILELIST_TYPES)\n\t\treturn;\n\n\tif (strlen(ext) > MAX_EXTENSION_LENGTH)\n\t\treturn;\n\n\tstrlcpy (fl->filetypes[num].extension, ext, sizeof (fl->filetypes[num].extension));\n\tfl->filetypes[num].id = id;\n\n\tfl->num_filetypes ++;\n}\n\n//\n// hides the \"..\" option to traverse in the dirs hierarchy\n//\nvoid FL_SetDirUpOption(filelist_t *fl, qbool show)\n{\n\tfl->show_dirup = show;\n}\n\n\n//\n// hides the \"..\" option to traverse in the dirs hierarchy\n//\nvoid FL_SetDirsOption(filelist_t *fl, qbool show)\n{\n\tfl->show_dirs = show;\n}\n\n\n//\n// get current entry\n//\nfiledesc_t * FL_GetCurrentEntry(filelist_t *fl)\n{\n\tif (fl->num_entries <= 0)\n\t\treturn NULL;\n\n\treturn &fl->entries[fl->current_entry];\n}\n\n//\n// get current path\n//\nchar *FL_GetCurrentPath(filelist_t *fl)\n{\n\tif (fl->num_entries <= 0)\n\t\treturn NULL;\n\n\treturn fl->entries[fl->current_entry].name;\n}\n\n//\n// get current display\n//\nchar *FL_GetCurrentDisplay(filelist_t *fl)\n{\n\tif (fl->num_entries <= 0)\n\t\treturn NULL;\n\n\treturn fl->entries[fl->current_entry].display;\n}\n\n//\n// get current entry type\n//\nint FL_GetCurrentEntryType(filelist_t *fl)\n{\n\tif (fl->num_entries <= 0)\n\t\treturn -1;\n\n\treturn fl->filetypes[fl->entries[fl->current_entry].type_index].id;\n}\n\n//\n// is current entry a dir ?\n//\nqbool FL_IsCurrentDir(filelist_t *fl)\n{\n\tif (fl->num_entries <= 0)\n\t\treturn true;    // we can handle that\n\n\treturn fl->entries[fl->current_entry].is_directory;\n}\n\n#ifdef WITH_ZIP\n//\n// Is current entry a zip file?\n//\nqbool FL_IsCurrentArchive(filelist_t *fl)\n{\n\tif (fl->num_entries <= 0)\n\t{\n\t\treturn true;\n\t}\n\n\treturn fl->entries[fl->current_entry].is_archive;\n}\n#endif // WITH_ZIP\n\nvoid FL_StripFileName(filelist_t *fl, filedesc_t *f)\n{\n\tint i;\n\tchar namebuf[_MAX_FNAME] = {0};\n\tchar extbuf[_MAX_EXT] = {0};\n\tchar *t;\n\n\t// Don't try to strip this.\n\tif (!strcmp(f->name, \"..\"))\n\t{\n\t\tstrlcpy (f->display, \"/..\", sizeof(f->display));\n\t\treturn;\n\t}\n\n\t// Common for dir/file, get name without path but with ext\n\tstrlcpy (namebuf, COM_SkipPath (f->name), sizeof (namebuf));\n\n\t// File specific.\n\tif (!f->is_directory)\n\t{\n\t\t// Get extension.\n\t\tsnprintf (extbuf, sizeof(extbuf), \".%s\", COM_FileExtension (namebuf));\n\n\t\t// If the extension is only a . it means we have no extension.\n\t\tif (strlen (extbuf) == 1)\n\t\t{\n\t\t\textbuf[0] = '\\0';\n\t\t}\n\n\t\t// Remove extension from the name.\n\t\tCOM_StripExtension(namebuf, namebuf, sizeof(namebuf));\n\t}\n\n\tif (file_browser_strip_names.value && !f->is_directory)\n\t{\n\t\tchar *s;\n\n\t\tfor (i=0; i < strlen(namebuf); i++)\n\t\t\tif (namebuf[i] == '_')\n\t\t\t\tnamebuf[i] = ' ';\n\n\t\t// Remove spaces from the beginning\n\t\ts = namebuf;\n\t\twhile (*s == ' ')\n\t\t\ts++;\n\t\tmemmove(namebuf, s, strlen(s) + 1);\n\n\t\t// Remove spaces from the end\n\t\ts = namebuf + strlen(namebuf);\n\t\twhile (s > namebuf  &&  *(s-1) == ' ')\n\t\t\ts--;\n\t\t*s = 0;\n\n\t\t// Remove few spaces in a row\n\t\ts = namebuf;\n\t\twhile (s < namebuf + strlen(namebuf))\n\t\t{\n\t\t\tif (*s == ' ')\n\t\t\t{\n\t\t\t\tchar *q = s+1;\n\t\t\t\twhile (*q == ' ')\n\t\t\t\t\tq++;\n\n\t\t\t\tif (q > s+1)\n\t\t\t\t\tmemmove(s+1, q, strlen(q)+1);\n\t\t\t}\n\t\t\ts++;\n\t\t}\n\n\t\tif (namebuf[0] == 0)\n\t\t\tstrlcpy (namebuf, \"_\", sizeof (namebuf));\n\t}\n\n\t// Replace all non-standard characters with '_'\n\tfor (i=0; i < strlen(namebuf); i++)\n\t{\n\t\tif (namebuf[i] < ' '  ||  namebuf[i] > '~')\n\t\t\tnamebuf[i] = '_';\n\t}\n\n\tf->display[0] = 0;\n\tt = f->display;\n\t// Add a slash before a dir.\n\tif (f->is_directory)\n\t\t*t++ = '/';\n\n\t// Add the name and extension to the final buffer.\n\tfor (i=0; i < strlen(namebuf)  &&  (t - f->display) < MAX_PATH; i++)\n\t\t*t++ = namebuf[i];\n\tfor (i=0; i < strlen(extbuf)  &&  (t - f->display) < MAX_PATH; i++)\n\t\t*t++ = extbuf[i];\n\t*t = 0;\n}\n\n//\n// Goto specific file by its name\n//\nvoid FL_GotoFile(filelist_t *fl, char *name)\n{\n\tint i;\n\n\tfl->current_entry = 0;\n\tif (!name[0])\n\t\treturn;\n\n\tfor (i=0; i < fl->num_entries; i++)\n\t{\n\t\tif (!strcmp(name, fl->entries[i].name))\n\t\t{\n\t\t\tfl->current_entry = i;\n\t\t\treturn;\n\t\t}\n\t}\n\n\treturn;\n}\n\n//\n// file compare func\n// set FL_CompareFunc_FileList before calling..\n//\nfilelist_t * FL_CompareFunc_FileList;    // global\nint FL_CompareFunc(const void * p_d1, const void * p_d2)\n{\n\textern int  SYSTEMTIMEcmp(const SYSTEMTIME *, const SYSTEMTIME *);\n\tint reverse = 0;\n\tfilelist_t *fl = FL_CompareFunc_FileList;\n\tchar *sort_string = file_browser_sort_mode.string;\n\tconst filedesc_t *d1 = (filedesc_t *)p_d1;\n\tconst filedesc_t *d2 = (filedesc_t *)p_d2;\n\n\t// Directories always first.\n\tif (d1->is_directory &&  !d2->is_directory)\n\t\treturn -1;\n\tif (d2->is_directory  && !d1->is_directory)\n\t\treturn 1;\n\n\t// Directories sorted always by name, ascending\n\tif (d1->is_directory && d2->is_directory)\n\t\treturn strcasecmp(d1->name, d2->name);\n\n#ifdef WITH_ZIP\n\t// Zips after directories.\n\tif (d1->is_archive && !d2->is_archive)\n\t\treturn -1;\n\tif (d2->is_archive  && !d1->is_archive)\n\t\treturn 1;\n\tif (!file_browser_sort_archives.integer && d1->is_archive && d2->is_archive)\n\t\treturn strcasecmp(d1->name, d2->name);\n#endif // WITH_ZIP\n\n\twhile (true)\n\t{\n\t\tlong int d;  // difference\n\t\tchar c = *sort_string++;\n\n\t\tif (c & 128)\n\t\t{\n\t\t\treverse = 1;\n\t\t\tc -= 128;\n\t\t}\n\n\t\tswitch (c)\n\t\t{\n\t\t\tcase '1':   // name\n\t\t\t\td = strcasecmp(d1->name, d2->name); break;\n\t\t\tcase '2':   // size\n\t\t\t\td = d1->size - d2->size;\n\t\t\t\tbreak;\n\t\t\tcase '3':   // date / time\n\t\t\t\td = SYSTEMTIMEcmp(&(d1->time), &(d2->time));\n\t\t\t\tbreak;\n\t\t\tcase '4':   // type\n\t\t\t\t{\n\t\t\t\t\tchar *ext1 = fl->filetypes[d1->type_index].extension;\n\t\t\t\t\tchar *ext2 = fl->filetypes[d2->type_index].extension;\n\t\t\t\t\td = strcasecmp(ext1, ext2);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\td = d1 - d2;\n\t\t}\n\n\t\tif (d)\n\t\t\treturn reverse ? -d : d;\n\t}\n}\n\n//\n// sort directory\n//\nvoid FL_SortDir (filelist_t *fl)\n{\n\tchar name[MAX_PATH+1] = \"\";\n\n\tif (fl->num_entries <= 0  ||\n\t\t\tfile_browser_sort_mode.string == NULL  ||\n\t\t\tfile_browser_sort_mode.string[0] == 0)\n\t\treturn;\n\n\tFL_CompareFunc_FileList = fl;\n\tif (fl->current_entry >= 0 && fl->current_entry < fl->num_entries)\n\t\tstrlcpy (name, fl->entries[fl->current_entry].name, sizeof (name));\n\n\tqsort (fl->entries, fl->num_entries, sizeof(filedesc_t), FL_CompareFunc);\n\n\tFL_GotoFile (fl, name);\n\n\tfl->need_resort = false;\n}\n\n//\n// Find out if a given directory entry is a registered file type and\n// if so, returns the index of the file type. Otherwise -1 is returned.\n//\nstatic int FL_FindRegisteredType(filelist_t *fl, sys_dirent *ent)\n{\n\tint i = 0;\n\tint result = -1;\n\n\tif (!ent->directory)\n\t{\n\t\tfor (i=0; i < fl->num_filetypes; i++)\n\t\t{\n\t\t\tchar ext[_MAX_EXT];\n\n\t\t\tsnprintf (ext, sizeof(ext), \".%s\", COM_FileExtension (ent->fname));\n\n\t\t\tif (!strcasecmp(fl->filetypes[i].extension, ext))\n\t\t\t{\n\t\t\t\tresult = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n\n//\n// Finds which entry to highlight after doing \"cd up\".\n//\nstatic void FL_FindHighlightEntry (filelist_t *fl)\n{\n\tint i = 0;\n\n\t// Look for what we have to highlight when listing the files in the\n\t// parent directory. If we're in c:\\quake\\qw\\some_directory\\ and do a cdup \"..\"\n\t// we want to highlight (move the cursor to) \"some_directory\" in the file list\n\t// of c:\\quake\\qw\\ so that it's obvious which directory we just left.\n\tif (fl->cdup_find)\n\t{\n\t\tint uplen = strlen (fl->cdup_name);\n\t\tfl->cdup_find = false;\n\t\tfor (i=0; i < fl->num_entries; i++)\n\t\t{\n\t\t\tint clen = strlen (fl->entries[i].name);\n\n\t\t\tif (uplen > clen)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (strcmp(fl->cdup_name, fl->entries[i].name + clen - uplen))\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfl->current_entry = i;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n#ifdef WITH_ZIP\n//\n// Read the ZIP file.\n//\nvoid FL_ReadArchive (filelist_t *fl)\n{\n\tint temp = 0;\n\tunzFile zip_file;\n\tsys_dirent ent;\n\tSYSTEMTIME archive_time;\n\n\t// Save the file modification time of the current archive\n\tarchive_time = fl->entries[fl->current_entry].time;\n\n\tfl->error = true;\n\tfl->need_refresh = false;\n\tfl->display_entry = 0;\n\tfl->num_entries = 0;\n\tfl->current_entry = 0;\n\n\t// Open the zip file.\n\tif (fl->current_archive[0])\n\t{\n\t\tzip_file = FS_ZipUnpackOpenFile (fl->current_archive);\n\t}\n\telse\n\t{\n\t\treturn;\n\t}\n\n\tif (zip_file == NULL)\n\t{\n\t\treturn;\n\t}\n\n\t// Get the first file from the zip file.\n\tif (!FS_ZipGetFirst (zip_file, &ent))\n\t{\n\t\tgoto finish;\n\t}\n\n\tfl->error = false;\n\n\t// Make sure we don't have some garbage left from the previous dir.\n\t// This caused a bug where some files inside of a zip would be\n\t// regarded as zip files, since the files with the same index\n\t// in the parent directory were zip files.\n\tmemset(fl->entries, 0, sizeof(fl->entries));\n\n\t// Add \"..\" entry to allow navigating out of archive if setting says so.\n\tif (fl->show_dirup)\n\t{\n\t\t// Pointer to the current file entry.\n\t\tfiledesc_t *f = &fl->entries[fl->num_entries];\n\n\t\t// Populate the file descriptor\n\t\tf->type_index = -1;\n\t\tf->is_directory = true;\n\t\tsnprintf(f->name, sizeof(f->name), \"..\");\n\t\tf->size = 0;\n\t\tf->time = archive_time;\n\n\t\t// Find friendly name.\n\t\tFL_StripFileName(fl, f);\n\n\t\t// Increase counter of how many files have been found.\n\t\tfl->num_entries++;\n\t\tif (fl->num_entries >= MAX_FILELIST_ENTRIES)\n\t\t{\n\t\t\tgoto finish;\n\t\t}\n\t}\n\n\tdo\n\t{\n\t\t// Pointer to the current file entry.\n\t\tfiledesc_t *f = &fl->entries[fl->num_entries];\n\t\tf->type_index = -1;\n\n\t\t// Skip current/above dir and hidden files.\n\t\tif (!strcmp(ent.fname, \".\") || !strcmp(ent.fname, \"..\") || ent.hidden)\n\t\t{\n\t\t\tgoto skip;\n\t\t}\n\n\t\t// Find registered type if it's not a directory.\n\t\tf->type_index = FL_FindRegisteredType (fl, &ent);\n\n\t\t// We're not interested in this file type since it wasn't registered.\n\t\tif (!ent.directory && f->type_index < 0)\n\t\t{\n\t\t\tgoto skip;\n\t\t}\n\n\t\t// We found a file that we're interested in so save the info about it\n\t\t// in the file description structure.\n\t\tf->is_directory = ent.directory;\n\n\t\t// Get the full path for the file.\n\t\tsnprintf (f->name, sizeof(f->name), \"%s%s%s\", fl->current_archive, PATH_SEPARATOR, ent.fname);\n\n\t\tf->size = ent.size;\n\t\tmemcpy(&f->time, &ent.time, sizeof(f->time));\n\n\t\t// Find friendly name.\n\t\tFL_StripFileName(fl, f);\n\n\t\t// Increase counter of how many files have been found.\n\t\tfl->num_entries++;\n\t\tif (fl->num_entries >= MAX_FILELIST_ENTRIES)\n\t\t{\n\t\t\tbreak;\n\t\t}\nskip:\n\t\t// Get next filesystem entry\n\t\ttemp = FS_ZipGetNextFile (zip_file, &ent);\n\t}\n\twhile (temp > 0);\n\n\tfl->need_resort = true;\n\nfinish:\n\t// Close the zip file.\n\tFS_ZipUnpackCloseFile (zip_file);\n\n\t// Re-sort the file list if needed.\n\tif (fl->need_resort)\n\t{\n\t\tFL_SortDir (fl);\n\t}\n\n\t// Resort might have changed this.\n\tfl->current_entry = 0;\n\n\t// Find which item to highlight in the list.\n\tFL_FindHighlightEntry(fl);\n}\n#endif // WITH_ZIP\n\n//\n// read directory\n//\nvoid FL_ReadDir(filelist_t *fl)\n{\n\tsys_dirent ent;\n\tSysDirEnumHandle search;\n\tint temp;\n\tchar  olddir[MAX_PATH+1];\n\n\tfl->error = true;\n\tfl->need_refresh = false;\n\tfl->display_entry = 0;\n\n\t// Save the current directory. (we want to restore this later)\n\tif (Sys_getcwd(olddir, MAX_PATH+1) == NULL)\n\t{\n\t\treturn;\n\t}\n\n\t// Change to the new dir.\n\tif (!Sys_chdir(fl->current_dir))\n\t{\n\t\t// Set the current dir.\n\t\treturn;\n\t}\n\n\tfl->num_entries = 0;\n\tfl->current_entry = 0;\n\tfl->error = false;\n\n\t// Get the first entry in the dir.\n\tsearch = Sys_ReadDirFirst(&ent);\n\tif (!search)\n\t{\n\t\tgoto finish;\n\t}\n\n\tdo\n\t{\n\t\t// Pointer to the current file entry.\n\t\tfiledesc_t *f = &fl->entries[fl->num_entries];\n\t\tmemset (f, 0, sizeof(filedesc_t));\n\t\tf->type_index = -1;\n\n\t\t// Skip current/above dir and hidden files.\n\t\tif (!strcmp(ent.fname, \".\") || ent.hidden)\n\t\t{\n\t\t\tgoto skip;\n\t\t}\n\n\t\t// Skip the \"up one level\" option if setting says so\n\t\tif (!strcmp(ent.fname, \"..\") && !fl->show_dirup)\n\t\t{\n\t\t\tgoto skip;\n\t\t}\n\n\t\t// Skip directories, if settings say so\n\t\tif (ent.directory && !fl->show_dirs)\n\t\t{\n\t\t\tgoto skip;\n\t\t}\n\n\t\t// Find registered type if it's not a directory.\n\t\tf->type_index = FL_FindRegisteredType (fl, &ent);\n\n#ifdef WITH_ZIP\n\t\tif (FS_IsArchive (ent.fname))\n\t\t{\n\t\t\tf->is_archive = true;\n\t\t}\n#endif\n\n\t\t// We're not interested in this file type since it wasn't registered.\n\t\tif (!ent.directory && f->type_index < 0\n#ifdef WITH_ZIP\n\t\t\t\t// Or isn't a zip.\n\t\t\t\t&& !f->is_archive\n#endif\n\t\t   )\n\t\t{\n\t\t\tgoto skip;\n\t\t}\n\n\t\t// We found a file that we're interested in so save the info about it\n\t\t// in the file description structure.\n\t\t{\n\t\t\tf->is_directory = ent.directory;\n\n\t\t\tif (!strcmp(ent.fname, \"..\"))\n\t\t\t{\n\t\t\t\t// Don't get the full path for the \"..\" (parent) dir, that's just confusing.\n\t\t\t\tstrlcpy (f->name, ent.fname, sizeof (f->name));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Get full path for normal files/dirs.\n\t\t\t\tSys_fullpath(f->name, ent.fname, sizeof(f->name));\n\t\t\t}\n\n\t\t\tf->size = ent.size;\n\t\t\tmemcpy(&f->time, &ent.time, sizeof(f->time));\n\t\t}\n\n\t\t// Find friendly name.\n\t\tFL_StripFileName(fl, f);\n\n\t\t// Increase counter of how many files have been found.\n\t\tfl->num_entries++;\n\t\tif (fl->num_entries >= MAX_FILELIST_ENTRIES)\n\t\t{\n\t\t\tbreak;\n\t\t}\nskip:\n\t\tmemset (&ent, 0, sizeof(ent));\n\n\t\t// Get next filesystem entry\n\t\ttemp = Sys_ReadDirNext(search, &ent);\n\t}\n\twhile (temp > 0);\n\n\t// Close the handle for the directory.\n\tSys_ReadDirClose(search);\n\n\tfl->need_resort = true;\n\nfinish:\n\t// Change the current dir back to what it was.\n\tSys_chdir (olddir);\n\n\t// Re-sort the file list if needed.\n\tif (fl->need_resort)\n\t{\n\t\tFL_SortDir (fl);\n\t}\n\n\t// Resort might have changed this.\n\tfl->current_entry = 0;\n\n\t// Find which item to highlight in the list.\n\tFL_FindHighlightEntry(fl);\n\n\treturn;\n}\n\n//\n// Search by name for next item.\n//\nqbool FL_Search(filelist_t *fl)\n{\n\tint start = fl->current_entry;\n\tint stop = fl->num_entries;\n\tint i = 0;\n\tchar *s = NULL;\n\tchar tmp[512];\n\n\tfl->search_dirty = false;\n\nsearch :\n\n\t// First search at the beginning of the string (like when you type in a window in explorer).\n\tfor (i = start; i < stop; i++)\n\t{\n\t\tstrlcpy (tmp, fl->entries[i].display, sizeof (tmp));\n\t\ts = tmp;\n\n\t\t// Get rid of slashes for dirs.\n\t\twhile (s && (*s) == '/')\n\t\t{\n\t\t\ts++;\n\t\t}\n\n\t\tif (!strncasecmp (s, fl->search_string, strlen (fl->search_string)))\n\t\t{\n\t\t\tfl->current_entry = i;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Nothing found, so search in any part of the string.\n\tfor (i = start; i < stop; i++)\n\t{\n\t\tstrlcpy (tmp, fl->entries[i].display, sizeof (tmp));\n\t\tFunToSort (tmp);\n\n\t\tif (strstr(tmp, fl->search_string))\n\t\t{\n\t\t\tfl->current_entry = i;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Try to search from the start if we didn't find it below the\n\t// current position of the cursor.\n\tif (start > 0)\n\t{\n\t\tstart = 0;\n\t\tstop = fl->current_entry; // No idea searching anything below this again (we just did that).\n\t\tgoto search;\n\t}\n\n\t// We couldn't find anything.\n\tfl->search_error = true;\n\treturn false;\n}\n\n//\n// FL_ChangeZip - enter a zip file\n//\nvoid FL_ChangeArchive(filelist_t *fl, char *archive)\n{\n\tstrlcpy (fl->current_archive, archive, sizeof(fl->current_archive));\n\tfl->in_archive = true;\n\n\tfl->need_refresh = true;\n}\n\n//\n// FL_ChangeDirUp - cd ..\n//\nvoid FL_ChangeDirUp(filelist_t *fl)\n{\n\t// No point doing anything.\n\tif (strlen(fl->current_dir) < 2)\n\t{\n\t\treturn;\n\t}\n\n\t// Get the name of the directory we're leaving, so that we can highlight it\n\t// in the file list of it's parent. (makes it easier keep track of where you are)\n\t{\n\t\tfl->cdup_find = true;\n\n#ifdef WITH_ZIP\n\t\tif (fl->in_archive)\n\t\t{\n\t\t\tstrlcpy (fl->cdup_name, COM_SkipPath(fl->current_archive), sizeof(fl->cdup_name));\n\t\t}\n\t\telse\n#endif // WITH_ZIP\n\t\t{\n\t\t\tstrlcpy (fl->cdup_name, COM_SkipPath(fl->current_dir), sizeof(fl->cdup_name));\n\t\t}\n\n\t\t// fl->cdup_name will be:\n\t\t// the_directory_were_leaving\n\t\t// If the full path was:\n\t\t// c:\\quake\\qw\\the_directory_were_leaving\n\t}\n}\n\n//\n// FL_ChangeDir - changes directory\n//\nvoid FL_ChangeDir(filelist_t *fl, char *newdir)\n{\n\tchar olddir[MAX_PATH+1];\n\n\t// Get the current dir from the OS and save it.\n\tif (Sys_getcwd(olddir, MAX_PATH+1) == NULL)\n\t{\n\t\treturn;\n\t}\n\n\t// Check if changing to parent directory or leaving archive\n\tif (newdir != NULL && strcmp(newdir, \"..\") == 0)\n\t{\n\t\tFL_ChangeDirUp(fl);\n\n#ifdef WITH_ZIP\n\t\tif (fl->in_archive)\n\t\t{\n\t\t\t// Leaving a zip file.\n\t\t\tfl->current_archive[0] = 0;\n\t\t\tfl->in_archive = false;\n\t\t\tfl->need_refresh = true;\n\n\t\t\treturn;\n\t\t}\n#endif // WITH_ZIP\n\t}\n\n\t// Change to the current dir that we're in (might be different from the OS's).\n\t// If we're changing dirs in a relative fashion \"..\" for instance we need to\n\t// be in this dir, and not the dir that the OS is in.\n\tif (Sys_chdir(fl->current_dir) == 0)\n\t{\n\t\t// Normal directory.\n\t\treturn;\n\t}\n\n\t// Change to the new dir requested.\n\tSys_chdir (newdir);\n\n\t// Save the current dir we just changed to.\n\tSys_getcwd (fl->current_dir, MAX_PATH+1);\n\n\t// Go back to where the OS wants to be.\n\tSys_chdir (olddir);\n\n\tfl->need_refresh = true;\n\n#ifdef WITH_ZIP\n\t// Since we just changed to a new directory we can't be in a zip file.\n\tfl->current_archive[0] = 0;\n\tfl->in_archive = false;\n#endif // WITH_ZIP\n}\n\n//\n// check current position\n//\nvoid FL_CheckPosition(filelist_t *fl)\n{\n\tif (fl->current_entry < 0)\n\t\tfl->current_entry = 0;\n\tif (fl->current_entry >= fl->num_entries)\n\t\tfl->current_entry = fl->num_entries - 1;\n\tif (fl->scrollbar && fl->num_entries > fl->displayed_entries_count) {\n\t\tfl->scrollbar->curpos = fl->display_entry * 1.0f / (fl->num_entries - fl->displayed_entries_count);\n\t}\n}\n\n#ifdef WITH_ZIP\nvoid FL_CompressFile (filelist_t *fl)\n{\n\tchar *file_path = FL_GetCurrentPath(fl);\n\n\t// Remember where we were.\n\tint ce = fl->current_entry;\t \n\n\t// Compress the file\n\tint ret = FS_GZipPack(file_path, va(\"%s.gz\", file_path), false); \n\n\tif (ret)\n\t{\n\t\tunlink(file_path);\n\t}\n\telse\n\t{\n\t\tCom_Printf(\"Failed compressing file to GZip.\");\n\t}\n\n\tFL_ReadDir(fl);\t\t\t\t// Reload dir.\n\tfl->current_entry = ce;\t\t// Set previous position.\n\tFL_CheckPosition(fl);\n}\n\nvoid FL_DecompressFile (filelist_t *fl)\n{\n\tint ret = 0;\n\n\t// Remember where we were.\n\tint ce = fl->current_entry;\t \n\tchar *gzip_path = FL_GetCurrentPath(fl);\n\tchar file_path[MAX_PATH];\n\n\tCOM_StripExtension(gzip_path, file_path, sizeof(file_path));\n\n\tret = FS_GZipUnpack(gzip_path, file_path, false); \n\n\tif (ret)\n\t{\n\t\tunlink(gzip_path);\n\t}\n\telse\n\t{\n\t\tCom_Printf(\"Failed decompressing file from GZip.\");\n\t}\n\n\tFL_ReadDir(fl);\t\t\t\t// Reload dir.\n\tfl->current_entry = ce;\t\t// Set previous position.\n\tFL_CheckPosition(fl);\n}\n#endif // WITH_ZIP\n\n//\n// Delete the current file.\n//\nvoid FL_DeleteFile(filelist_t *fl)\n{\n\tint ce = fl->current_entry;\t\t// Remember where we were.\n\tunlink(FL_GetCurrentPath(fl));\t// Delete.\n\tFL_ReadDir(fl);\t\t\t\t\t// Reload dir.\n\tfl->current_entry = ce;\n\tFL_CheckPosition(fl);\n}\n\n//\n// Check display position\n//\nvoid FL_CheckDisplayPosition(filelist_t *fl)\n{\n\tint lines = fl->displayed_entries_count;\n\n\tif (fl->current_entry < 0) fl->current_entry = 0;\n\n\t// FIXME: move list earlier..\n\tif (fl->current_entry > fl->display_entry + lines - 1)\n\t\tfl->display_entry = fl->current_entry - lines + 1;\n\n\tif (fl->display_entry > fl->num_entries - lines)\n\t\tfl->display_entry = max(fl->num_entries - lines, 0);\n\n\tif (fl->current_entry < fl->display_entry)\n\t\tfl->display_entry = fl->current_entry;\n\n\tFL_CheckPosition(fl);\n}\n\n//\n// keys handling\n// returns: true if processed, false if ignored\n//\nqbool FL_Key(filelist_t *fl, int key)\n{\n\tif (fl->mode != FL_MODE_NORMAL)\n\t{\n\t\tswitch(key) \n\t\t{\n\t\t\tcase 'y':\n\t\t\tcase 'Y':\n\t\t\tcase K_ENTER:\n\t\t\t\tif (!FL_IsCurrentDir(fl))\n\t\t\t\t{\n\t\t\t\t\tif (fl->mode == FL_MODE_DELETE)\n\t\t\t\t\t{\n\t\t\t\t\t\tFL_DeleteFile(fl);\n\t\t\t\t\t}\n#ifdef WITH_ZIP\n\t\t\t\t\telse if (fl->mode == FL_MODE_COMPRESS)\n\t\t\t\t\t{\n\t\t\t\t\t\tFL_CompressFile(fl); // Compress file.\n\t\t\t\t\t}\n\t\t\t\t\telse if (fl->mode == FL_MODE_DECOMPRESS)\n\t\t\t\t\t{\n\t\t\t\t\t\tFL_DecompressFile(fl); // Decompress file.\n\t\t\t\t\t}\n#endif // WITH_ZIP\n\t\t\t\t}\n\n\t\t\t\tfl->mode = FL_MODE_NORMAL;\n\t\t\t\treturn true;\n\t\t\tcase 'n':\n\t\t\tcase 'N':\n\t\t\tcase K_ESCAPE:\n\t\t\t\tfl->mode = FL_MODE_NORMAL;\n\t\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t// Check for search\n\tif ((key >= ' ' && key <= '~') && (fl->search_valid || (!keydown[K_ALT] && !keydown[K_CTRL] && !keydown[K_SHIFT])))\n\t{\n\t\tint len;\n\n\t\tif (!fl->search_valid)\n\t\t{\n\t\t\t// Start searching\n\t\t\tfl->search_valid = true;\n\t\t\tfl->search_error = false;\n\t\t\tfl->search_string[0] = 0;\n\t\t}\n\n\t\tlen = strlen(fl->search_string);\n\n\t\tif (len < MAX_SEARCH_STRING && !fl->search_error)\n\t\t{\n\t\t\tfl->search_string[len] = key;\n\t\t\tfl->search_string[len+1] = 0;\n\t\t\tfl->search_dirty = true;\n\n\t\t\t// Save the last time the user entered a char in the\n\t\t\t// search term, so that we know when to timeout the search prompt.\n\t\t\t// (See beginning of FL_Draw)\n\t\t\tlast_search_enter_time = Sys_DoubleTime();\n\t\t}\n\n\t\treturn true;    // handled\n\t}\n\telse\n\t{\n\t\tfl->search_valid = false;   // finish search mode\n\t}\n\n\t// sorting mode / displaying columns\n\tif (key >= '1' && key <= '4') {\n\t\tif (keydown[K_CTRL] && !keydown[K_ALT] && !keydown[K_SHIFT])\n\t\t{\n\t\t\tswitch (key)\n\t\t\t{\n\t\t\t\tcase '2':\n\t\t\t\t\tCvar_Toggle(&file_browser_show_size); break;\n\t\t\t\tcase '3':\n\t\t\t\t\tCvar_Toggle(&file_browser_show_date); break;\n\t\t\t\tcase '4':\n\t\t\t\t\tCvar_Toggle(&file_browser_show_time); break;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\telse if (!keydown[K_CTRL] && keydown[K_ALT] && !keydown[K_SHIFT])\n\t\t{\n\t\t\tchar buf[128];\n\n\t\t\tstrlcpy(buf, file_browser_sort_mode.string, 32); // WTF?\n\t\t\tif (key  ==  buf[0])\n\t\t\t{\n\t\t\t\t// reverse order\n\t\t\t\tbuf[0] ^= 128;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// add new\n\t\t\t\tmemmove(buf+1, buf, strlen(buf)+1);\n\t\t\t\tbuf[0] = key;\n\t\t\t}\n\t\t\tbuf[8] = 0;\n\t\t\tCvar_Set(&file_browser_sort_mode, buf);\n\n\t\t\tfl->need_resort = true;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// change drive\n#ifdef _WIN32\n\tif (keydown[K_ALT]  &&  keydown[K_CTRL]  &&\n\t\t\ttolower(key) >= 'a'  &&  tolower(key) <= 'z')\n\t{\n\t\tchar newdir[MAX_PATH+1];\n\t\tchar olddir[MAX_PATH+1];\n\n\t\tsnprintf(newdir, sizeof (newdir), \"%c:\\\\\", tolower(key));\n\n\t\t// validate\n\t\tif (Sys_getcwd(olddir, MAX_PATH+1) == 0)\n\t\t\treturn true;\n\t\tif (Sys_chdir(newdir) == 0)\n\t\t\treturn true;\n\t\tSys_chdir(olddir);\n\n\t\t// and set\n\t\tFL_SetCurrentDir(fl, newdir);\n\t\tfl->need_refresh = true;\n\n\t\treturn true;\n\t}\n#endif\n\n\tif (key == '\\\\'  ||  key == '/')\n\t{\n\t\tFL_ChangeDir(fl, PATH_SEPARATOR);\n\t\treturn true;\n\t}\n\n\tif (key == K_ENTER || key == K_MOUSE1)\n\t{\n\t\tif (FL_IsCurrentDir(fl))\n\t\t{\n\t\t\tFL_ChangeDir(fl, FL_GetCurrentPath(fl));\n\t\t\treturn true;\n\t\t}\n#ifdef WITH_ZIP\n\t\telse if (FL_IsCurrentArchive(fl))\n\t\t{\n\t\t\tFL_ChangeArchive(fl, FL_GetCurrentPath(fl));\n\t\t\treturn true;\n\t\t}\n#endif //WITH_ZIP\n\t\telse\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (key == K_BACKSPACE)\n\t{\n\t\tif (fl->show_dirup)\n\t\t\tFL_ChangeDir(fl, \"..\");\n\t\treturn true;\n\t}\n\n\tif (key == K_UPARROW || key == K_MWHEELUP)\n\t{\n\t\tfl->current_entry--;\n\t\tFL_CheckDisplayPosition(fl);\n\t\treturn true;\n\t}\n\n\tif (key == K_DOWNARROW || key == K_MWHEELDOWN)\n\t{\n\t\tfl->current_entry++;\n\t\tFL_CheckDisplayPosition(fl);\n\t\treturn true;\n\t}\n\n\tif (key == K_PGUP)\n\t{\n\t\tfl->current_entry -= fl->last_page_size;\n\t\tFL_CheckDisplayPosition(fl);\n\t\treturn true;\n\t}\n\n\tif (key == K_PGDN)\n\t{\n\t\tfl->current_entry += fl->last_page_size;\n\t\tFL_CheckDisplayPosition(fl);\n\t\treturn true;\n\t}\n\n\tif (key == K_HOME)\n\t{\n\t\tfl->current_entry = 0;\n\t\tFL_CheckDisplayPosition(fl);\n\t\treturn true;\n\t}\n\n\tif (key == K_END)\n\t{\n\t\tfl->current_entry = fl->num_entries - 1;\n\t\tFL_CheckDisplayPosition(fl);\n\t\treturn true;\n\t}\n\n#ifdef WITH_ZIP\n\t//\n\t// Compress the current file.\n\t//\n\tif ((key == 'c' || key == 'C') && keydown[K_ALT])\n\t{\n\t\tif (!FL_IsCurrentDir(fl))\n\t\t{\n\t\t\tif (keydown[K_SHIFT])\n\t\t\t{\n\t\t\t\t// Alt + shift + c == Compress without confirming.\n\t\t\t\tFL_CompressFile(fl);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Alt + c == Confirm before compressing.\n\t\t\t\tfl->mode = FL_MODE_COMPRESS;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\t//\n\t// Decompress the current file.\n\t//\n\tif ((key == 'd' || key == 'D') && keydown[K_ALT])\n\t{\n\t\tif (!strcmp(COM_FileExtension(FL_GetCurrentPath(fl)), \"gz\"))\n\t\t{\n\t\t\tFL_DecompressFile(fl);\n\t\t}\n\t}\n#endif // WITH_ZIP\n\n\t//\n\t// Delete the current file.\n\t//\n\tif (key == K_DEL)\n\t{\n\t\tif (!FL_IsCurrentDir(fl)) \n\t\t{\n\t\t\tif (keydown[K_SHIFT])\n\t\t\t{\n\t\t\t\t// Shift + del == Delete without confirming.\n\t\t\t\tFL_DeleteFile(fl);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Del == Confirm before deleting.\n\t\t\t\tfl->mode = FL_MODE_DELETE;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nqbool FL_Mouse_Event(filelist_t *fl, const mouse_state_t *ms)\n{\n\tint entry;\n\n\tif (fl->scrollbar->mouselocked || \n\t\t\t((ms->x > fl->list_width) && (ms->x <= (fl->list_width + fl->scrollbar->width))))\n\t{   // catch the scrollbar mouse event\n\n\t\tif (ScrollBar_MouseEvent(fl->scrollbar, ms))\n\t\t{\n\t\t\tif (fl->num_entries > fl->displayed_entries_count) {\n\t\t\t\tfl->display_entry = (fl->num_entries - fl->displayed_entries_count) * fl->scrollbar->curpos;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t// we don't handle mouse clicks, that's up to the module above us\n\tif (ms->button_down || ms->button_up) return false;\n\n\t// no other area then the list interests us\n\tif (ms->x > fl->list_width || ms->y >= (fl->list_y_offset + fl->list_height) || ms->y <= fl->list_y_offset)\n\t\treturn false;\n\n\t// we presume that each line is 8 px high\n\n\tentry = (int) ms->y / 8 - 2;\n\tfl->current_entry = fl->display_entry + entry;\n\tFL_CheckPosition(fl);\n\n\treturn true;\n}\n\n//\n// This is used only in FL_Draw below, EX_browser.c has Add_Column2\n//\nstatic void Add_Column(char *line, int *pos, char *t, int w)\n{\n\t// Adds columns starting from the right.\n\n\t// If we're too far to the left we can't fit a column\n\t// of this width.\n\tif ((*pos) - w - 1  <=  1)\n\t{\n\t\treturn;\n\t}\n\n\t// Move the position to the left by the width of the column.\n\t(*pos) -= w;\n\n\t// Copy the contents into the column.\n\tmemcpy(line + (*pos), t, min(w, strlen(t)));\n\n\t// Create a space for the next column.\n\t(*pos)--;\n\tline[*pos] = ' ';\n}\n\n//\n// FileList drawing func\n//\nvoid FL_Draw(filelist_t *fl, int x, int y, int w, int h)\n{\n\tint i;\n\tint listsize, pos, interline, inter_up, inter_dn, rowh;\n\tchar line[1024];\n\tchar sname[MAX_PATH] = {0}, ssize[COL_SIZE+1] = {0}, sdate[COL_DATE+1] = {0}, stime[COL_TIME+1] = {0};\n\n\t// Check if it's time for us to reset the search.\n\t// (FL_SEARCH_TIMEOUT seconds after the user entered the last char in the search term)\n\tif (Sys_DoubleTime() - last_search_enter_time >= FL_SEARCH_TIMEOUT)\n\t{\n\t\tfl->search_valid = false;\n\t\tfl->search_string[0] = 0;\n\t}\n\n\tif (fl->mode == FL_MODE_DELETE)\n\t{\n\t\tUI_Print_Center(x, y + 8,  w, \"Are you sure you want to delete this file?\", true);\n\t\tUI_Print_Center(x, y + 24, w, FL_GetCurrentDisplay(fl), false);\n\t\tUI_Print_Center(x, y + 40, w, \"(Y/N)\", true);\n\t\treturn;\n\t}\n#ifdef WITH_ZIP\n\telse if (fl->mode == FL_MODE_COMPRESS)\n\t{\n\t\tUI_Print_Center(x, y + 8,  w, \"Are you sure you want to compress this file?\", true);\n\t\tUI_Print_Center(x, y + 24, w, FL_GetCurrentDisplay(fl), false);\n\t\tUI_Print_Center(x, y + 40, w, \"(Y/N)\", true);\n\t\treturn;\n\t}\n\telse if (fl->mode == FL_MODE_DECOMPRESS)\n\t{\n\t\tUI_Print_Center(x, y + 8,  w, \"Are you sure you want to decompress this file?\", true);\n\t\tUI_Print_Center(x, y + 24, w, FL_GetCurrentDisplay(fl), false);\n\t\tUI_Print_Center(x, y + 40, w, \"(Y/N)\", true);\n\t\treturn;\n\t}\n#endif // WITH_ZIP\n\n\tfl->last_page_size = 0;\n\n\tw -= fl->scrollbar->width;\n\n\t// Calculate interline (The space between each row)\n\tinterline = file_browser_interline.value;\n\tinterline = max(interline, 0);\n\tinterline = min(interline, 6);\n\tinter_up = interline / 2;\n\tinter_dn = interline - inter_up;\n\trowh = 8 + inter_up + inter_dn;\n\tlistsize = h / rowh;\n\n\t// Check screen boundaries and mimnimum size\n\tif (w < 160 || h < 80)\n\t\treturn;\n\tif (x < 0 || y < 0 || x + w > vid.width || y + h > vid.height)\n\t\treturn;\n\n\tif (fl->need_refresh)\n\t{\n#ifdef WITH_ZIP\n\t\tif (fl->in_archive)\n\t\t{\n\t\t\tFL_ReadArchive (fl);\n\t\t}\n\t\telse\n#endif // WITH_ZIP\n\t\t{\n\t\t\tFL_ReadDir(fl);\n\t\t}\n\t}\n\n\tif (fl->need_resort)\n\t{\n\t\tFL_SortDir(fl);\n\t}\n\n\tif (fl->search_dirty)\n\t{\n\t\tFL_Search(fl);\n\t}\n\n\t// Print the current path.\n\t{\n\t\tchar *curr_path = NULL;\n\n#ifdef WITH_ZIP\n\t\tif (fl->in_archive)\n\t\t{\n\t\t\tcurr_path = fl->current_archive;\n\t\t}\n\t\telse\n#endif // WITH_ZIP\n\t\t{\n\t\t\tcurr_path = fl->current_dir;\n\t\t}\n\n\t\t// Make the path fit on screen \"c:\\quake\\bla\\bla\\bla\" => \"c:\\quake...la\\bla\".\n\t\tCOM_FitPath (line, sizeof(line), curr_path, w/8);\n\t}\n\n\tline[w/8] = 0;\n\tUI_Print(x, y + inter_up, line, true);\n\tlistsize--;\n\n\t// Draw column titles.\n\tpos = w/8;\n\tmemset(line, ' ', pos);\n\tline[pos] = 0;\n\n\tif (file_browser_show_time.value)\n\t\tAdd_Column(line, &pos, \"time\", COL_TIME);\n\tif (file_browser_show_date.value)\n\t\tAdd_Column(line, &pos, \"date\", COL_DATE);\n\tif (file_browser_show_size.value)\n\t\tAdd_Column(line, &pos, \"  kb\", COL_SIZE);\n\n\tmemcpy(line, \"name\", min(pos, 4));\n\tline[w/8] = 0;\n\tUI_Print_Center(x, y + rowh + inter_dn, w, line, true);\n\tlistsize--;\n\n\t// Nothing to show.\n\tif (fl->num_entries <= 0)\n\t{\n\t\tUI_Print_Center(x, y + 2 * rowh + inter_dn + 4, w, \"directory empty\", false);\n\t\treturn;\n\t}\n\n\t// Something went wrong when processing the directory.\n\tif (fl->error)\n\t{\n\t\tUI_Print_Center(x, y + 2 * rowh + inter_dn + 4, w, \"error reading directory\", false);\n\t\treturn;\n\t}\n\n\t// If we're showing the status bar we have less room for the file list.\n\tif (file_browser_show_status.value)\n\t{\n\t\tlistsize -= 3;\n\t}\n\n\tfl->list_width = w;\n\tfl->list_height = listsize*rowh;\n\tfl->list_y_offset = 2 * rowh;\n\tfl->last_page_size = listsize;  // Remember for PGUP/PGDN\n\tfl->displayed_entries_count = listsize;\n\tFL_CheckPosition(fl);\n\n\tif (fl->displayed_entries_count < fl->num_entries) {\n\t\tScrollBar_Draw(fl->scrollbar, x + fl->list_width, y + fl->list_y_offset, fl->list_height);\n\t}\n\n\tfor (i = 0; i < listsize; i++)\n\t{\n\t\tfiledesc_t *entry;\n\t\tunsigned long dwsize;\n\t\tchar size[COL_SIZE+1], date[COL_DATE+1], time[COL_TIME+1];\n\t\tchar name[MAX_PATH];\n\t\tint filenum = fl->display_entry + i;\n\t\tclrinfo_t clr[2]; // here we use _one_ color, at begining of the string\n\t\tstatic char\tlast_name[sizeof(name)]\t= {0}; // this is for scroll, can't put it inside scroll code\n\n\t\tclr[0].c = COLOR_WHITE;\n\t\tclr[0].i = 0; // begin of the string\n\n\t\tif (filenum >= fl->num_entries)\n\t\t\tbreak;\n\n\t\tentry = &fl->entries[filenum];\n\n\t\t// Extract date & time.\n\t\tsnprintf(date, sizeof(date), \"%02d-%02d-%02d\", entry->time.wYear % 100, entry->time.wMonth, entry->time.wDay);\n\t\tsnprintf(time, sizeof(time), \"%2d:%02d\", entry->time.wHour, entry->time.wMinute);\n\n\t\t// Extract size.\n\t\tif (entry->is_directory)\n\t\t{\n\t\t\tstrlcpy(size, \"<-->\", sizeof(size));\n\t\t\tif (filenum == fl->current_entry)\n\t\t\t\tstrlcpy(ssize, \"dir\", sizeof(ssize));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdwsize = entry->size / 1024;\n\t\t\tif (dwsize > 9999)\n\t\t\t{\n\t\t\t\tdwsize /= 1024;\n\t\t\t\tdwsize = min(dwsize, 999);\n\t\t\t\tsnprintf(size, sizeof(size), \"%3lum\", dwsize);\n\t\t\t\tif (filenum == fl->current_entry) {\n\t\t\t\t\tsnprintf(ssize, sizeof(ssize), \"%lu mb\", dwsize);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tsnprintf(size, sizeof(size), \"%4lu\", dwsize);\n\t\t\t\tif (filenum == fl->current_entry) {\n\t\t\t\t\tsnprintf(ssize, sizeof(ssize), \"%lu kb\", dwsize);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add the columns to the current row (starting from the right).\n\t\tpos = w/8;\n\n\t\t// Clear the line.\n\t\tmemset(line, ' ', pos);\n\t\tline[pos] = 0;\n\n\t\t// Add columns.\n\t\tif (file_browser_show_time.value)\n\t\t\tAdd_Column(line, &pos, time, COL_TIME);\n\t\tif (file_browser_show_date.value)\n\t\t\tAdd_Column(line, &pos, date, COL_DATE);\n\t\tif (file_browser_show_size.value)\n\t\t\tAdd_Column(line, &pos, size, COL_SIZE);\n\n\t\t// End of name, switch to white.\n\t\tclr[1].c = COLOR_WHITE;\n\t\tclr[1].i = pos;\n\n\t\t// Set the name.\n\t\t// (Add a space infront so that the cursor for the currently\n\t\t// selected file can fit infront)\n\t\tsnprintf (name, sizeof(name) - 1, \" %s\", entry->display);\n\n\t\t//\n\t\t// Copy the display name of the entry into the space that's left on the row.\n\t\t//\n\t\tif (filenum == fl->current_entry && file_browser_scrollnames.value && strlen(name) > pos)\n\t\t{\n\t\t\t// We need to scroll the text since it doesn't fit.\n#define SCROLL_RIGHT\t1\n#define SCROLL_LEFT\t\t0\n\n\t\t\tdouble\t\t\tt\t\t\t\t\t= 0;\n\t\t\tstatic double\tt_last_scroll\t\t= 0;\n\t\t\tstatic qbool\twait\t\t\t\t= false;\n\t\t\tstatic int\t\tscroll_position\t\t= 0;\n\t\t\tstatic int\t\tscroll_direction\t= SCROLL_RIGHT;\n\t\t\tstatic int\t\tlast_text_length\t= 0;\n\t\t\tint\t\t\t\ttext_length\t\t\t= strlen(name);\n\t\t\tfloat\t\t\tscroll_delay\t\t= 0.1;\n\n\t\t\t// If the text has changed since last time we scrolled\n\t\t\t// the scroll data will be invalid so reset it.\n\t\t\tif (last_text_length != text_length || !last_name[0] || strncmp(name, last_name, sizeof(last_name)))\n\t\t\t{\n\t\t\t\tstrlcpy(last_name, name, sizeof(last_name));\n\t\t\t\tscroll_position = 0;\n\t\t\t\tscroll_direction = SCROLL_RIGHT;\n\t\t\t\tlast_text_length = text_length;\n\n\t\t\t\t// Wait a second before start scroll plz.\n\t\t\t\twait = true;\n\t\t\t\tt_last_scroll = Sys_DoubleTime();\n\t\t\t}\n\n\t\t\t// Get the current time.\n\t\t\tt = Sys_DoubleTime();\n\n\t\t\t// Time to scroll.\n\t\t\tif (!wait && (t - t_last_scroll) > scroll_delay)\n\t\t\t{\n\t\t\t\t// Save the current time as the last time we scrolled\n\t\t\t\t// and change the scroll position depending on what direction we're scrolling.\n\t\t\t\tt_last_scroll = t;\n\t\t\t\tscroll_position = (scroll_direction == SCROLL_RIGHT) ? scroll_position + 1 : scroll_position - 1;\n\t\t\t}\n\n\t\t\t// Set the scroll direction.\n\t\t\tif (text_length - scroll_position == pos)\n\t\t\t{\n\t\t\t\t// We've reached the end, go back.\n\t\t\t\tscroll_direction = SCROLL_LEFT;\n\t\t\t\twait = true;\n\t\t\t}\n\t\t\telse if (scroll_position == 0)\n\t\t\t{\n\t\t\t\t// At the beginning, start over.\n\t\t\t\tscroll_direction = SCROLL_RIGHT;\n\t\t\t\twait = true;\n\t\t\t}\n\n\t\t\t// Wait a second when changing direction.\n\t\t\tif (wait && (t - t_last_scroll) > 1.0)\n\t\t\t{\n\t\t\t\twait = false;\n\t\t\t}\n\n\t\t\tmemcpy(line, name + scroll_position, min(pos, text_length + scroll_position));\n\t\t}\n\t\telse\n\t\t{\n\t\t\t//\n\t\t\t// Fits in the name column, no need to scroll (or the user doesn't want us to scroll :~<)\n\t\t\t//\n\n\t\t\t// Did we just select a new entry? In that case reset the scrolling.\n\t\t\tif (filenum == fl->current_entry)\n\t\t\t{\n\t\t\t\tlast_name[0] = 0;\n\t\t\t}\n\n\t\t\t// If it's not the selected directory/zip color it so that it stands out.\n\t\t\tif (filenum != fl->current_entry)\n\t\t\t{\n\t\t\t\tif (entry->is_directory)\n\t\t\t\t{\n\t\t\t\t\t// Set directory color.\n\t\t\t\t\tclr[0].c = RGBAVECT_TO_COLOR(file_browser_dir_color.color);\n\t\t\t\t}\n#ifdef WITH_ZIP\n\t\t\t\telse if (entry->is_archive)\n\t\t\t\t{\n\t\t\t\t\t// Set zip color.\n\t\t\t\t\tclr[0].c = RGBAVECT_TO_COLOR(file_browser_archive_color.color);\n\t\t\t\t}\n#endif // WITH_ZIP\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tclr[0].c = RGBAVECT_TO_COLOR(file_browser_file_color.color);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmemcpy(line, name, min(pos, strlen(name)));\n\t\t}\n\n\t\t// Draw a cursor character at the start of the line.\n\t\tif (filenum == fl->current_entry)\n\t\t{\n\t\t\tline[0] = (char)141;\n\t\t\tUI_DrawGrayBox(x, y + rowh * (i + 2) + inter_dn, w, rowh);\n\t\t}\n\n\t\t// Max amount of characters that fits on a line.\n\t\tline[w/8] = 0;\n\n\t\t// Color the selected entry.\n\t\tif (filenum == fl->current_entry)\n\t\t{\n\t\t\tclr[0].c = RGBAVECT_TO_COLOR(file_browser_selected_color.color);\n\t\t\tclr[0].i = 1; // Don't color the cursor (index 0).\n\t\t}\n\n\t\t// Print the line for the directory entry.\n\t\tUI_Print_Center3(x, y + rowh * (i + 2) + inter_dn, w, line, clr, 2, 0);\n\n\t\t// Remember the currently selected file for dispalying in the status bar\n\t\tif (filenum == fl->current_entry)\n\t\t{\n\t\t\tstrlcpy (sname, line + 1, min(pos, sizeof(sname)));\n\t\t\tstrlcpy (stime, time, sizeof(stime));\n\t\t\tsnprintf (sdate, sizeof(sdate), \"%02d-%02d-%02d\", entry->time.wYear % 100, entry->time.wMonth, entry->time.wDay);\n\t\t}\n\t}\n\n\t// Show a statusbar displaying the currently selected entry.\n\tif (file_browser_show_status.value)\n\t{\n\t\t// Print a line to part the status bar from the file list.\n\t\tmemset(line, '\\x1E', w/8);\n\t\tline[0] = '\\x1D';\n\t\tline[w/8 - 1] = '\\x1F';\n\t\tline[w/8] = 0;\n\t\tUI_Print(x, y + h - 3 * rowh - inter_up, line, false);\n\n\t\t// Print the name.\n\t\tUI_Print_Center(x, y + h - 2 * rowh - inter_up, w, sname, false);\n\n\t\tif (fl->search_valid)\n\t\t{\n\t\t\t// Some weird but nice-looking string in Quake font perhaps\n\t\t\tstrlcpy(line, \"search for: \", sizeof(line));   // seach for:\n\t\t\tif (fl->search_error)\n\t\t\t{\n\t\t\t\tstrlcat(line, \"not found\", sizeof(line));\n\t\t\t\tUI_Print_Center(x, y + h - rowh - inter_up, w, line, false);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tstrlcat(line, fl->search_string, sizeof(line));\n\t\t\t\tUI_Print_Center(x, y + h - rowh - inter_up, w, line, true);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsnprintf(line, sizeof(line), \"%s \\x8f modified: %s %s\", ssize, sdate, stime);\n\t\t\tUI_Print_Center(x, y + h - rowh - inter_up, w, line, false);\n\t\t}\n\t}\n}\n\n// make ../backspace work fine (ala demoplayer)\n// do not show hidden files\n\nstatic void OnChange_file_browser_sort_mode(cvar_t *var, char *string, qbool *cancel)\n{\n\textern qbool host_everything_loaded;\n\textern filelist_t demo_filelist;\n\textern filelist_t help_index_fl;\n\textern filelist_t help_tutorials_fl;\n\textern filelist_t configs_filelist;\n\textern filelist_t skins_filelist;\n\n\tif (host_everything_loaded) {\n\t\tdemo_filelist.need_resort\t\t= true;\n\t\thelp_index_fl.need_resort\t\t= true;\n\t\thelp_tutorials_fl.need_resort\t= true;\n\t\tconfigs_filelist.need_resort\t= true;\n\t\tskins_filelist.need_resort\t\t= true;\n\t}\n}\n\n"
  },
  {
    "path": "src/EX_FileList.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/*\n * File List\n *\n * display directory contents, allows for changing dir,\n * drive etc..\n *\n * meant for use by MP3 player, mayby demo player too\n *\n * FileList have four colums:\n *  - file name (with ext)\n *  - size\n *  - date (last modification)\n *  - time (last modification)\n *\n * and allows to display different sets of columns\n * also do sorting\n *\n */\n\n#ifndef __EX_FILELIST_H__\n#define __EX_FILELIST_H__\n\n#include \"keys.h\"\n#include \"Ctrl.h\"\n\n//\n// maximum-s\n//\n#define  MAX_FILELIST_ENTRIES  1024\n#define  MAX_FILELIST_TYPES    16\n#define  MAX_FILELIST_DISPLAY  80\n#define  MAX_EXTENSION_LENGTH  15\n#define  MAX_SEARCH_STRING     64\n\n//\n// entry (file) type strucy\n//\ntypedef struct filetype_s\n{\n    int         id;\n    char        extension [MAX_EXTENSION_LENGTH+1];\n}\nfiletype_t;\n\n\n//\n// file entry structure\n//\ntypedef struct filedesc_s\n{\n    qbool\t\t\tis_directory;\n\t\n\t#ifdef WITH_ZIP\n\tqbool\t\t\tis_archive;\n\t#endif\n    \n\tchar            name[MAX_PATH+1];\n    char            display[MAX_PATH+1];\n    unsigned long   size;\n    SYSTEMTIME      time;\n    int             type_index;\n}\nfiledesc_t;\n\n#define FL_MODE_NORMAL\t\t0\n#define FL_MODE_DELETE\t\t1\n#define FL_MODE_COMPRESS\t2\n#define FL_MODE_DECOMPRESS\t3\n\n//\n// Filelist structure\n//\ntypedef struct filelist_s\n{\n    char\t\t\tcurrent_dir[MAX_PATH+1];\n\tchar\t\t\tcurrent_archive[MAX_PATH+1];\n\tqbool\t\t\tin_archive;\n    qbool\t\t\terror;          // Error reading dir\n    qbool\t\t\tneed_refresh;   // Dir is reread in draw func\n    qbool\t\t\tneed_resort;    // Dir is sorted in draw func\n\n    filedesc_t\t\tentries[MAX_FILELIST_ENTRIES];\n    int\t\t\t\tnum_entries;\n    int\t\t\t\tcurrent_entry;\n    int\t\t\t\tdisplay_entry;  // First item displayed\n    int             displayed_entries_count;    // ammount of entries that fit on the screen\n\n    filetype_t\t\tfiletypes[MAX_FILELIST_TYPES];\n    int\t\t\t\tnum_filetypes;\n\n\tqbool\t\t\tshow_dirup;\n\tqbool\t\t\tshow_dirs;\n\n    // For PGUP/PGDN, filled by drawing func\n    int\t\t\t\tlast_page_size;\n\n    // For searching\n    char\t\t\tsearch_string[MAX_SEARCH_STRING+1];\n    qbool\t\t\tsearch_valid;   // If is in search mode\n    qbool\t\t\tsearch_dirty;   // If should research\n    qbool\t\t\tsearch_error;   // Not found\n\tint\t\t\t\tmode;\t\t\t// Are we deleting/compressing a file?\n\n    // For cd ..\n    char            cdup_name[MAX_PATH+1];\n    qbool\t\t\tcdup_find;\n\n\t// for mouse navigation\n    int             list_y_offset;\n\tint\t\t\t\tlist_width;\n\tint\t\t\t\tlist_height;\n\n    // associated scrollbar GUI element\n    PScrollBar      scrollbar;\n}\nfilelist_t;\n\n\n//\n// Drawing routine\n//\nvoid FL_Draw(filelist_t *, int x, int y, int w, int h);\n\n\n//\n// Send key to list\n// returns: true if processed, false if ignored\n//\nqbool FL_Key(filelist_t *, int key);\n\n\n//\n// Send Mouse Move event to the list\n// returns: true if processed, false if mouse pointed somewhere else\n//\nqbool FL_Mouse_Event(filelist_t *, const mouse_state_t *ms);\n\n//\n// Create file list\n//\nvoid FL_Init(filelist_t\t*fl, char *initdir);\n\nvoid FL_Shutdown(filelist_t* fl);\n\n//\n// Add new file type (.qwd, .qwz, .mp3).\n//\nvoid FL_AddFileType(filelist_t *, int id, char *ext);\n\n\n//\n// Set current directory (and drive).\n//\nvoid FL_SetCurrentDir(filelist_t *, const char *dir);\n\n\n//\n// hides the \"..\" option to traverse in the dirs hierarchy\n//\nvoid FL_SetDirUpOption(filelist_t *fl, qbool show);\n\n\n//\n// hides the \"..\" option to traverse in the dirs hierarchy\n//\nvoid FL_SetDirsOption(filelist_t *fl, qbool show);\n\n\n//\n// Get current directory.\n//\nchar *FL_GetCurrentDir(filelist_t *);\n\n\n//\n// Get current entry.\n//\nfiledesc_t * FL_GetCurrentEntry(filelist_t *);\n\n\n//\n// Get current path.\n//\nchar *FL_GetCurrentPath(filelist_t *);\n\n\n//\n// Get current display.\n//\nchar *FL_GetCurrentDisplay(filelist_t *fl);\n\n\n//\n// Get current entry type.\n//\nint FL_GetCurrentEntryType(filelist_t *);\n\n\n//\n// Is current entry a dir ?\n//\nqbool FL_IsCurrentDir(filelist_t *);\n\n#endif // __EX_FILELIST_H__\n"
  },
  {
    "path": "src/EX_browser.c",
    "content": "/*\nCopyright (C) 2011-2015 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"fs.h\"\n#include \"gl_model.h\"\n#ifndef _WIN32\n#include <netinet/in.h>\n#include <unistd.h>\n#endif\n\n#include \"keys.h\"\n#include \"EX_browser.h\"\n#include \"EX_qtvlist.h\"\n#include \"Ctrl_EditBox.h\"\n#include \"settings.h\"\n#include \"settings_page.h\"\n#include \"Ctrl.h\"\n#include \"Ctrl_Tab.h\"\n#include \"menu.h\"\n#include \"utils.h\"\n#include \"menu_multiplayer.h\"\n#include \"qsound.h\"\n#include \"teamplay.h\"\n\nint source_unique = 0;\n\ntypedef struct info_filter_s {\n\tchar name[MAX_INFO_KEY];\n\tpcre2_code* regex;\n\tqbool pass;\n\tqbool exec;\n} info_filter_t;\n\nstatic info_filter_t* SB_InfoFilter_Parse(int* count);\nstatic qbool SB_InfoFilter_Exec(info_filter_t* info_filters, int info_filter_count, server_data* s);\nstatic void SB_InfoFilter_Free(info_filter_t* info_filters, int info_filter_count);\n\n// searching\n#define MAX_SEARCH 20\n#define SEARCH_TIME 3\ndouble searchtime = -10;\n\n#define MWHEEL_SCROLL_STEP 4\n\nenum {\n\tsearch_none = 0,\n\tsearch_server,\n\tsearch_player\n} searchtype = search_none;\n\nchar searchstring[MAX_SEARCH+1];\n\n// add source\nCEditBox edit1, edit2;\nint adding_source = 0;\nsb_source_type_t newsource_type; // 0 = file\nint newsource_pos;\n\n// add server\nint adding_server = 0;\nint newserver_pos;\n\nserverbrowser_window_t Browser_window;\n\n// must correspond to server_occupancy enum values\nstatic const char* occupancy_colors[] = { \"00f\", \"0f0\", \"f00\" };\n\ncvar_t  sb_status        =  {\"sb_status\", \t\t\t\"1\"}; // Shows Server status at the bottom\n\n// columns\ncvar_t  sb_showping      = {\"sb_showping\",         \"1\"};\ncvar_t  sb_showaddress   = {\"sb_showaddress\",      \"0\"};\ncvar_t  sb_showmap       = {\"sb_showmap\",          \"1\"};\ncvar_t  sb_showgamedir   = {\"sb_showgamedir\",      \"0\"};\ncvar_t  sb_showplayers   = {\"sb_showplayers\",      \"1\"};\ncvar_t  sb_showfraglimit = {\"sb_showfraglimit\",    \"0\"};\ncvar_t  sb_showtimelimit = {\"sb_showtimelimit\",    \"0\"};\n\ncvar_t  sb_pingtimeout   = {\"sb_pingtimeout\",   \"1000\"};\ncvar_t  sb_infotimeout   = {\"sb_infotimeout\",   \"1000\"};\ncvar_t  sb_pingspersec   = {\"sb_pingspersec\",    \"150\"}; // Pings per second\ncvar_t  sb_pings         = {\"sb_pings\",            \"3\"}; // Number of times to ping a server\ncvar_t  sb_inforetries   = {\"sb_inforetries\",      \"3\"};\ncvar_t  sb_infospersec   = {\"sb_infospersec\",    \"100\"};\ncvar_t  sb_proxinfopersec= {\"sb_proxinfopersec\",  \"10\"};\ncvar_t  sb_proxretries   = {\"sb_proxretries\",      \"3\"};\ncvar_t  sb_proxtimeout   = {\"sb_proxtimeout\",   \"1000\"};\ncvar_t  sb_mastertimeout = {\"sb_mastertimeout\", \"1000\"};\ncvar_t  sb_masterretries = {\"sb_masterretries\",    \"3\"};\ncvar_t  sb_nosockraw     = {\"sb_nosockraw\",        \"0\"}; // when enabled, forces \"new ping\" (udp qw query packet, multithreaded) to be used\n\ncvar_t  sb_liveupdate    = {\"sb_liveupdate\",       \"2\"}; // not in menu\n\ncvar_t  sb_sortservers   = {\"sb_sortservers\",     \"32\"}; // not in new menu\ncvar_t  sb_sortplayers   = {\"sb_sortplayers\",     \"92\"}; // not in new menu\ncvar_t  sb_sortsources   = {\"sb_sortsources\",      \"3\"}; // not in new menu\n\ncvar_t  sb_autohide      = {\"sb_autohide\",         \"1\"}; // not in menu\ncvar_t  sb_findroutes    = {\"sb_findroutes\",       \"0\"};\ncvar_t  sb_ignore_proxy  = {\"sb_ignore_proxy\",     \"\"};\n\n// filters\nstatic void sb_trigger_resort(cvar_t* var, char* value, qbool* cancel)\n{\n\tresort_servers = 1;\n}\n\ncvar_t  sb_hideempty     = {\"sb_hideempty\",        \"1\", 0, sb_trigger_resort };\ncvar_t  sb_hidenotempty  = {\"sb_hidenotempty\",     \"0\", 0, sb_trigger_resort };\ncvar_t  sb_hidefull      = {\"sb_hidefull\",         \"0\", 0, sb_trigger_resort };\ncvar_t  sb_hidedead      = {\"sb_hidedead\",         \"1\", 0, sb_trigger_resort };\ncvar_t  sb_hidehighping  = {\"sb_hidehighping\",     \"0\", 0, sb_trigger_resort };\ncvar_t  sb_pinglimit     = {\"sb_pinglimit\",       \"80\", 0, sb_trigger_resort };\ncvar_t  sb_showproxies   = {\"sb_showproxies\",      \"0\", 0, sb_trigger_resort };\ncvar_t  sb_info_filter   = {\"sb_info_filter\",       \"\", 0, sb_trigger_resort };\n\ncvar_t  sb_sourcevalidity  = {\"sb_sourcevalidity\", \"30\"}; // not in menu\ncvar_t  sb_mastercache     = {\"sb_mastercache\",    \"1\"};  // not in menu\ncvar_t  sb_autoupdate      = {\"sb_autoupdate\",     \"1\"};  // not in menu\ncvar_t  sb_listcache       = {\"sb_listcache\",      \"0\"};\n\n// servers table\nserver_data *servers[MAX_SERVERS];\nint serversn;\nint serversn_passed;\n\n// all players table\nplayer_host ** all_players = NULL;\nint all_players_n = 0;\n\n/// when 1, in the next frame in which the list is drawn also sorting will be done\nint resort_servers = 1; \nint resort_sources = 1;\nvoid Sort_Servers (void);\n\nint testing_connection = 0;\nint ping_phase = 0;\ndouble ping_pos;\nint abort_ping;\n\n// allow background threads to fire triggers\nint sb_queuedtriggers = 0;\n\n// mouse and server list columns\nstatic qbool mouse_in_header_row = false;\nstatic int mouse_header_pos_y = 0;\nstatic unsigned int mouse_hovered_column;\n\nextern cvar_t cl_proxyaddr;\n\nstatic SDL_mutex* serverlist_mutex = NULL;\nsem_t serverinfo_semaphore;\n\ntypedef struct sb_column_t {\n\tconst char *name;\n\tunsigned int width;\n\tcvar_t *showvar;\n} sb_column_t;\n\nsb_column_t sb_columns[] = {\n\t{ \"name\", COL_NAME, NULL },\n\t{ \"address\", COL_IP, &sb_showaddress },\n\t{ \"png\", COL_PING, &sb_showping },\n\t{ \"gamedir\", COL_GAMEDIR, &sb_showgamedir },\n\t{ \"map\", COL_MAP, &sb_showmap },\n\t{ \"plyrs\", COL_PLAYERS, &sb_showplayers },\n\t{ \"fl\", COL_FRAGLIMIT, &sb_showfraglimit },\n\t{ \"tl\", COL_TIMELIMIT, &sb_showtimelimit }\n};\n\nstatic const unsigned int sb_columns_size = sizeof(sb_columns) / sizeof(sb_column_t);\n\nvoid Serverinfo_Stop(void);\n\nvoid SB_ServerList_Lock(void)\n{\n\tSDL_LockMutex(serverlist_mutex);\n}\n\nvoid SB_ServerList_Unlock(void)\n{\n\tSDL_UnlockMutex(serverlist_mutex);\n}\n\nstatic qbool SB_Is_Selected_Proxy(const server_data *s)\n{\n\treturn (strstr(cl_proxyaddr.string, s->display.ip) != NULL);\n}\n\n// removes given proxy from the proxyaddr list\n// if cl_proxyaddr \"a:b:c\" and s->ip = \"b\" then it changes cl_proxyaddr to \"a:c\"\nstatic void SB_Proxy_Unselect(const server_data *s)\n{\n\tconst char *start = strstr(cl_proxyaddr.string, s->display.ip);\n\tconst char *end;\n\tchar *buf = (char *) Q_malloc(strlen(cl_proxyaddr.string) + 1);\n\tchar *bufstart = buf;\n\tconst char *cur;\n\n\tif (start == NULL) {\n\t\tQ_free(buf);\n\t\treturn; // invalid call, proxy is not selected\n\t}\n\n\tfor (cur = cl_proxyaddr.string; cur < start - 1;) {\n\t\t*buf++ = *cur++;\n\t}\n\t*buf = '\\0';\n\n\tend = strchr(start, '@');\n\tif (end != NULL) {\n\t\tcur = end;\n\t\tif (*bufstart == '\\0') cur++; // don't copy the '@' sign\n\t\twhile (*cur) *buf++ = *cur++;\n\t\t*buf++ = '\\0';\n\t}\n\n\tCvar_Set(&cl_proxyaddr, bufstart);\n\n\tQ_free(bufstart);\n}\n\n// adds selected proxy to the end of the proxies list\nstatic void SB_Proxy_Select(const server_data *s)\n{\n\tsize_t len = strlen(s->display.ip) + strlen(cl_proxyaddr.string) + 2;\n\tchar *buf = (char *) Q_malloc(len);\n\n\t*buf = '\\0';\n\tstrlcat(buf, cl_proxyaddr.string, len);\n\tif (*buf) {\n\t\tstrlcat(buf, \"@\", len);\n\t}\n\tstrlcat(buf, s->display.ip, len);\n\n\tCvar_Set(&cl_proxyaddr, buf);\n\n\tQ_free(buf);\n}\n\nstatic void SB_Select_QWfwd(server_data *s)\n{\n\tif (SB_Is_Selected_Proxy(s)) {\n\t\tSB_Proxy_Unselect(s);\n\t}\n\telse {\n\t\tSB_Proxy_Select(s);\n\t}\n\tS_LocalSound (\"misc/menu2.wav\");\n\tServerinfo_Stop();\n}\n\nstatic const char* SB_Source_Type_Name(sb_source_type_t type)\n{\n\tswitch (type) {\n\t\tcase type_master: return \"master\";\n\t\tcase type_file: return \"file  \";\n\t\tcase type_url: return \"url   \";\n\t\tcase type_dummy: return \"dummy \";\n\t\tdefault:\n\t\t\t\t Sys_Error(\"SB_Source_Type_Name(): Invalid sb_source_type_t type\");\n\t\t\t\t return \"ERROR\";\n\t}\n}\n\nstatic void SB_Browser_Hide(const server_data *s)\n{\n\tif (sb_autohide.value)\n\t{\n\t\tif (sb_autohide.value > 1  ||  strcmp(s->display.gamedir, \"qizmo\")) {\n\t\t\tkey_dest = key_game;\n\t\t\tm_state = m_none;\n\t\t\tM_Draw();\n\t\t}\n\t}\n}\n\nstatic void Join_Server (server_data *s)\n{\n\tif (sb_findroutes.integer) {\n\t\tCbuf_AddText(\"spectator 0\\n\");\n\t\tSB_PingTree_ConnectBestPath(&s->address);\n\t} else {\n\t\tCbuf_AddText (\"join \");\n\t\tCbuf_AddText (s->display.ip);\n\t\tCbuf_AddText (\"\\n\");\n\t}\n\n\tSB_Browser_Hide(s);\n}\n\nstatic void Join_Server_Direct(server_data *s)\n{\n\tCvar_Set(&cl_proxyaddr, \"\");\n\tCbuf_AddText (\"join \");\n\tCbuf_AddText (s->display.ip);\n\tCbuf_AddText (\"\\n\");\n\tSB_Browser_Hide(s);\n}\n\nstatic void Observe_Server (server_data *s)\n{\n\tif (sb_findroutes.integer) {\n\t\tCbuf_AddText(\"spectator 1\\n\");\n\t\tSB_PingTree_ConnectBestPath(&s->address);\n\t} else {\n\t\tCbuf_AddText (\"observe \");\n\t\tCbuf_AddText (s->display.ip);\n\t\tCbuf_AddText (\"\\n\");\n\t}\n\tSB_Browser_Hide(s);\n}\n\nstatic void CopyServerToClipboard (server_data *s)\n{\n\tchar buf[2048];\n\n\tif (keydown[K_CTRL] || s->display.name[0] == 0)\n\t\tstrlcpy (buf, s->display.ip, sizeof(buf));\n\telse\n\t\tsnprintf (buf, sizeof (buf), \"%s (%s)\",\n\t\t\t\ts->display.name,\n\t\t\t\ts->display.ip);\n\n\tCopyToClipboard(buf);\n}\n\nstatic void PasteServerToConsole (server_data *s)\n{\n\tchar buf[2048];\n\n\tsnprintf(buf, sizeof (buf), \"%s (%s)\",\n\t\t\ts->display.name,\n\t\t\ts->display.ip);\n\n\tCbuf_AddText (keydown[K_CTRL] ? \"say_team \" :  \"say \");\n\tCbuf_AddText (buf);\n\tCbuf_AddText (\"\\n\");\n}\n\n\n//\n// browser routines\n//\n\n\nserver_data * Create_Server (char *ip)\n{\n\tserver_data *s;\n\n\ts = (server_data *) Q_malloc (sizeof(server_data));\n\tmemset (s, 0, sizeof(server_data));\n\n\ts->ping = -1;\n\n\tif (!NET_StringToAdr (ip, &(s->address)))\n\t{\n\t\tQ_free(s);\n\t\treturn NULL;\n\t}\n\n\tif (!strchr(ip, ':'))\n\t\ts->address.port = htons(27500);\n\n\tsnprintf (s->display.ip, sizeof (s->display.ip), \"%d.%d.%d.%d:%d\",\n\t\t\ts->address.ip[0], s->address.ip[1], s->address.ip[2], s->address.ip[3],\n\t\t\tntohs(s->address.port));\n\n\treturn s;\n}\n\nserver_data* Clone_Server(server_data* source)\n{\n\tint i = 0;\n\n\tserver_data* new_server = Create_Server2(source->address);\n\n\tnew_server->bestping = source->bestping;\n\tmemcpy(&new_server->display, &source->display, sizeof(source->display));\n\tnew_server->keysn = source->keysn;\n\tfor (i = 0; i < new_server->keysn; ++i) {\n\t\tnew_server->keys[i] = Q_strdup(source->keys[i]);\n\t\tnew_server->values[i] = Q_strdup(source->values[i]);\n\t}\n\tnew_server->occupancy = source->occupancy;\n\tnew_server->passed_filters = source->passed_filters;\n\tnew_server->ping = source->ping;\n\n\tnew_server->playersn = source->playersn;\n\tfor (i = 0; i < sizeof(source->players) / sizeof(source->players[0]); ++i) {\n\t\tif (source->players[i]) {\n\t\t\tnew_server->players[i] = Q_malloc(sizeof(playerinfo));\n\n\t\t\tmemcpy(new_server->players[i], source->players[i], sizeof(playerinfo));\n\t\t}\n\t}\n\n\tnew_server->qizmo = source->qizmo;\n\tnew_server->qwfwd = source->qwfwd;\n\tnew_server->spectatorsn = source->spectatorsn;\n\tnew_server->support_teams = source->support_teams;\n\n\treturn new_server;\n}\n\nserver_data * Create_Server2 (netadr_t n)\n{\n\tserver_data *s;\n\n\ts = (server_data *) Q_malloc (sizeof (server_data));\n\tmemset (s, 0, sizeof(server_data));\n\n\tmemcpy (&(s->address), &n, sizeof (netadr_t));\n\tstrlcpy (s->display.ip, NET_AdrToString(n), sizeof (s->display.ip));\n\n\ts->ping = -1;\n\n\treturn s;\n}\n\nvoid Reset_Server (server_data *s)\n{\n\tint i;\n\n\tfor (i = 0; i < s->keysn; i++)\n\t{\n\t\tQ_free(s->keys[i]);\n\t\tif (s->values[i]) {\n\t\t\t// fixme: this was causing a crash so a check for not-null-ness was added\n\t\t\tQ_free(s->values[i]);\n\t\t}\n\t}\n\n\ts->keysn = 0;\n\n\tfor (i = 0; i < s->playersn + s->spectatorsn; i++)\n\t\tQ_free(s->players[i]);\n\n\ts->playersn = 0;\n\ts->spectatorsn = 0;\n\ts->support_teams = false;\n\n\trebuild_all_players = 1; // rebuild all-players list\n}\n\nvoid Delete_Server (server_data *s)\n{\n\tReset_Server(s);\n\tQ_free(s);\n}\n\n\nchar confirm_text[64];\nqbool confirmation;\nvoid (*confirm_func)(void);\n\nvoid SB_Confirmation (const char *text, void (*func)(void))\n{\n\tstrlcpy (confirm_text, text, sizeof (confirm_text));\n\tconfirm_func = func;\n\tconfirmation = 1;\n}\n\nvoid SB_Confirmation_Draw (void)\n{\n\tint x, y, w, h;\n\n#define CONFIRM_TEXT_DEFAULT \"Are you sure? <y/n>\"\n\n\tw = 32 + 8 * max (strlen (confirm_text), strlen (CONFIRM_TEXT_DEFAULT));\n\th = 24;\n\n\tx = (vid.width - w) / 2;\n\ty = (vid.height - h) / 2;\n\tx = (x / 8) * 8;\n\ty = (y / 8) * 8;\n\n\tDraw_TextBox (x - 16, y - 16, w / 8 + 1, h / 8 + 2);\n\n\tUI_Print_Center (x, y, w, confirm_text, false);\n\tUI_Print_Center (x, y+16, w, CONFIRM_TEXT_DEFAULT, true);\n}\n\nvoid SB_Confirmation_Key (int key)\n{\n\tswitch (key)\n\t{\n\t\tcase K_BACKSPACE:\n\t\tcase K_ESCAPE:\n\t\tcase 'n':\n\t\tcase 'N':\n\t\t\tconfirmation = 0;\n\t\t\tbreak;\n\t\tcase 'y':\n\t\tcase 'Y':\n\t\t\tconfirmation = 0;\n\t\t\tconfirm_func();\n\t}\n}\n\n/* Menu drawing */\n\nint Servers_pos;\nint Sources_pos;\nint Players_pos;\nint Options_pos;\n\nserver_data * show_serverinfo;\nint serverinfo_pos;\n\nint Servers_disp;   // server# at the top of the list\nint Sources_disp;   // source# at the top of the list\nint Players_disp;   // player# at the top of the list\n\nvoid Serverinfo_Draw (void);\nvoid Serverinfo_Players_Draw(int x, int y, int w, int h);\nvoid Serverinfo_Rules_Draw(int x, int y, int w, int h);\nvoid Serverinfo_Sources_Draw(int x, int y, int w, int h);\nvoid Serverinfo_Key (int key);\nvoid Serverinfo_Players_Key(int key);\nvoid Serverinfo_Rules_Key(int key);\nvoid Serverinfo_Sources_Key(int key);\n\n//\n// serverinfo\n//\n\nint sourcesn_updated = 0;\n\nint Sources_Compare_Func (const void * p_s1, const void * p_s2)\n{\n\tint reverse = 0;\n\tchar *sort_string = sb_sortsources.string;\n\tconst source_data *s1 = *((source_data **)p_s1);\n\tconst source_data *s2 = *((source_data **)p_s2);\n\n\tif (show_serverinfo)\n\t{\n\t\tif (!(s1->last_update.wYear)  &&  s2->last_update.wYear)\n\t\t\treturn 1;\n\t\tif (!s2->last_update.wYear  &&  s1->last_update.wYear)\n\t\t\treturn -1;\n\t\tif (!s1->last_update.wYear  &&  !s2->last_update.wYear)\n\t\t\treturn s1 - s2;\n\t}\n\n\twhile (true)\n\t{\n\t\tint d;  // difference\n\n\t\tif (*sort_string == '-')\n\t\t{\n\t\t\treverse = 1;\n\t\t\tsort_string++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch (*sort_string++)\n\t\t{\n\t\t\tcase '1':\n\t\t\t\td = s1->type - s2->type; break;\n\t\t\tcase '2':\n\t\t\t\td = funcmp(s1->name, s2->name); break;\n\t\t\tcase '3':\n\t\t\t\td = s1->unique - s2->unique; break;\n\t\t\tdefault:\n\t\t\t\td = s1 - s2;\n\t\t}\n\n\t\tif (d)\n\t\t\treturn reverse ? -d : d;\n\t}\n}\nvoid Sort_Sources (void)\n{\n\tint i;\n\n\tqsort (sources+1, sourcesn-1, sizeof(sources[0]), Sources_Compare_Func);\n\n\tsourcesn_updated = 1;\n\n\tfor (i = 1; i < sourcesn; i++)\n\t\tif (sources[i]->last_update.wYear)\n\t\t\tsourcesn_updated ++;\n}\n\n\nint serverinfo_players_pos;\nint serverinfo_sources_pos;\nint serverinfo_sources_disp;\nextern int autoupdate_serverinfo; // declared in EX_browser_net.c\n\nvoid Serverinfo_Stop(void)\n{\n\tshow_serverinfo = NULL;\n\tautoupdate_serverinfo = 0;\n\tSys_SemDestroy(&serverinfo_semaphore);\n}\n\nvoid Serverinfo_Start (server_data *s)\n{\n\tif (show_serverinfo)\n\t\tServerinfo_Stop();\n\n\tserverinfo_players_pos = 0;\n\tserverinfo_sources_pos = 0;\n\n\tautoupdate_serverinfo = 1;\n\tshow_serverinfo = s;\n\n\tSys_SemInit(&serverinfo_semaphore, 1, 1);\n\n\t// sort for eliminating ot-updated\n\tSort_Sources();\n\tresort_sources = 1; // and mark for resort on next sources draw\n\n\tStart_Autoupdate(s);\n\n\t// testing connection\n\tif (testing_connection)\n\t{\n\t\tchar buf[256];\n\t\tsnprintf(buf, sizeof (buf), \"%d.%d.%d.%d\",\n\t\t\t\tshow_serverinfo->address.ip[0],\n\t\t\t\tshow_serverinfo->address.ip[1],\n\t\t\t\tshow_serverinfo->address.ip[2],\n\t\t\t\tshow_serverinfo->address.ip[3]);\n\t\tSB_Test_Init(buf);\n\t\ttesting_connection = 1;\n\t}\n}\n\nvoid Serverinfo_Change (server_data *s)\n{\n\tAlter_Autoupdate(s);\n\tshow_serverinfo = s;\n\n\t// testing connection\n\tif (testing_connection)\n\t{\n\t\tchar buf[256];\n\t\tsnprintf (buf, sizeof (buf), \"%d.%d.%d.%d\",\n\t\t\t\tshow_serverinfo->address.ip[0],\n\t\t\t\tshow_serverinfo->address.ip[1],\n\t\t\t\tshow_serverinfo->address.ip[2],\n\t\t\t\tshow_serverinfo->address.ip[3]);\n\t\tSB_Test_Change(buf);\n\t\ttesting_connection = 1;\n\t}\n}\n\n// --\n\nqbool AddUnboundServer(char *addr)\n{\n\tint i;\n\tserver_data *s;\n\tqbool existed = false;\n\n\tif (sources[0]->serversn >= MAX_UNBOUND)\n\t\treturn false;\n\ts = Create_Server(addr);\n\tif (s == NULL)\n\t\treturn false;\n\n\tfor (i=0; i < sources[0]->serversn; i++)\n\t\tif (!memcmp(&s->address, &sources[0]->servers[i]->address, sizeof(netadr_t)))\n\t\t{\n\t\t\tQ_free(s);\n\t\t\ts = sources[0]->servers[i];\n\t\t\texisted = true;\n\t\t\tbreak;\n\t\t}\n\tif (!existed)  // not found\n\t{\n\t\tsources[0]->servers[sources[0]->serversn] = s;\n\t\t(sources[0]->serversn) ++;\n\t\trebuild_servers_list = true;\n\t}\n\n\t// start menu\n\tkey_dest = key_menu;\n\tMark_Source(sources[0]);\n\tMenu_MultiPlayer_SwitchToServersTab();\n\tGetServerPing(s);\n\tGetServerInfo(s);\n\tServerinfo_Start(s);\n\t// M_Menu_ServerList_f();\n\treturn true;\n}\n\nvoid AddServer_f(void)\n{\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCom_Printf(\"Usage: addserver <addr>\\n\");\n\t\treturn;\n\t}\n\n\tif (!AddUnboundServer(Cmd_Argv(1)))\n\t{\n\t\tif (sources[0]->serversn >= MAX_UNBOUND)\n\t\t\tCom_Printf(\"Error: maximum unbound servers number reached\\n\");\n\t\telse\n\t\t\tCom_Printf(\"Error: couldn't resolve\\n\");\n\t}\n}\n\nvoid SB_PingsDump_f(void)\n{\n\textern qbool useNewPing;\n\tint i;\n\n\tCom_Printf(\"// server ping dump\\n// format: ip ping\\n// protocol: %s\\n\", useNewPing ? \"UDP\" : \"ICMP\");\n\tfor (i = 0; i < serversn; i++) {\n\t\tint ping = servers[i]->ping;\n\n\t\tif (ping >= 0 && ping < 999) {\n\t\t\tCom_Printf(\"%s %d\\n\", NET_AdrToString(servers[i]->address), ping);\n\t\t}\n\t}\n}\n\n//\n// drawing routines\n//\n\nvoid Add_ColumnColored(int x, int y, int *pos, const char *t, int w, const char* color)\n{\n\tchar buf[128];\n\tif ((*pos) - w - 1  <=  5)\n\t\treturn;\n\n\tsnprintf (buf, sizeof(buf), \"&c%s%s\", color, t);\n\n\t(*pos) -= w;\n\tUI_Print_Center(x + (*pos)*8, y, 8*(w+5), buf, false);\n\t(*pos)--;\n}\n\nvoid Add_Column2(int x, int y, int *pos, const char *t, int w, int red)\n{\n\tif ((*pos) - w - 1  <=  5)\n\t\treturn;\n\n\t(*pos) -= w;\n\tUI_Print_Center(x + (*pos)*8, y, 8*w, t, red);\n\t(*pos)--;\n}\n\nvoid Draw_Server_Statusbar(int x, int y, int w, int h, server_data *s, int count, int total)\n{\n\tint i;\n\tint d_gamedir, d_map;\n\tchar buf[1024], line[1024];\n\n\tmemset(line, '\\x1E', w/8);\n\tline[w/8] = 0;\n\tline[w/8-1] = '\\x1F';\n\tline[0] = '\\x1D';\n\tif (total > 0)\n\t{\n\t\tsnprintf(buf, sizeof (buf), \"%d/%d\", count+1, total);\n\t\tmemset(line+w/8-3-strlen(buf), ' ', strlen(buf)+1);\n\t}\n\tUI_Print(x, y+h-24, line, false);\n\tif (total > 0)\n\t\tUI_Print(x+w-8*(3+strlen(buf))+4, y+h-24, buf, true);\n\n\t// line 1\n\tstrlcpy (line, s->display.name[1] ? s->display.name : s->display.ip, sizeof (line));\n\tline[w/8] = 0;\n\tUI_Print_Center(x, y+h-16, w, line, false);\n\n\t// line 2\n\tif (searchtype)\n\t{\n\t\tint i;\n\t\tsnprintf(line, sizeof (line), \"search for: %-7s\", searchstring);\n\t\tline[w/8] = 0;\n\t\tfor (i=0; i < strlen(line); i++)\n\t\t\tline[i] ^= 128;\n\t\tUI_Print_Center(x, y+h-8, w, line, false);\n\t}\n\telse\n\t{\n\t\td_gamedir = d_map = 1;\n\t\tif (ValueForKey(s, \"map\") == NULL)\n\t\t\td_map = 0;\n\t\tif (ValueForKey(s, \"*gamedir\") == NULL)\n\t\t\td_gamedir = 0;\n\n\t\tline[0] = 0;\n\n\t\tif (d_gamedir)\n\t\t{\n\t\t\tmemset(buf, 0, 10);\n\t\t\tstrlcpy(buf, ValueForKey(s, \"*gamedir\"), sizeof(buf));\n\t\t\tbuf[8] = 0;\n\t\t\tstrlcat (line, buf, sizeof (line));\n\t\t\tstrlcat (line, \"\\xa0 \", sizeof (line));\n\t\t}\n\n\t\tif (d_map)\n\t\t{\n\t\t\tmemset(buf, 0, 10);\n\t\t\tstrlcpy(buf, ValueForKey(s, \"map\"), sizeof(buf));\n\t\t\tbuf[8] = 0;\n\t\t\tstrlcat (line, buf, sizeof (line));\n\t\t\tstrlcat (line, \"\\xa0 \", sizeof (line));\n\t\t}\n\n\t\t//if (d_players)\n\t\t{\n\t\t\tchar buf[10], *max;\n\t\t\tmax =  ValueForKey(s, \"maxclients\");\n\t\t\tsnprintf(buf, sizeof(buf), \"%d/%s\", s->playersn, max==NULL ? \"??\" : max);\n\t\t\tstrlcat (line, buf, sizeof (line));\n\t\t\tmax =  ValueForKey(s, \"maxspectators\");\n\t\t\tsnprintf(buf, sizeof(buf), \"-%d/%s\", s->spectatorsn, max==NULL ? \"??\" : max);\n\t\t\tstrlcat (line, buf, sizeof(line));\n\t\t}\n\n\t\tif (ValueForKey(s, \"status\") == NULL)\n\t\t{\n\t\t\tchar *dm, *fl, *tl;\n\t\t\tchar buf[200];\n\t\t\tdm = ValueForKey(s, \"deathmatch\");\n\t\t\tfl = ValueForKey(s, \"fraglimit\");\n\t\t\ttl = ValueForKey(s, \"timelimit\");\n\n\t\t\tif (dm  &&  strlen(line) + 7 <= w/8)\n\t\t\t{\n\t\t\t\tsnprintf(buf, sizeof(buf), \"\\xa0 dmm%s\", dm);\n\t\t\t\tstrlcat(line, buf, sizeof(line));\n\t\t\t}\n\n\t\t\tif (fl  &&  strlen(line) + 8 <= w/8)\n\t\t\t{\n\t\t\t\tsnprintf(buf, sizeof(buf), \"\\xa0 fl:%s\", fl);\n\t\t\t\tstrlcat(line, buf, sizeof(line));\n\t\t\t}\n\n\t\t\tif (tl  &&  strlen(line) + 7 <= w/8)\n\t\t\t{\n\t\t\t\tsnprintf(buf, sizeof(buf), \"\\xa0 tl:%s\", tl);\n\t\t\t\tstrlcat(line, buf, sizeof(line));\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tchar buf[200];\n\t\t\tsnprintf(buf, sizeof(buf), \"\\xa0 %s\", ValueForKey(s, \"status\"));\n\t\t\tstrlcat(line, buf, sizeof(line));\n\t\t}\n\n\t\t// draw line\n\t\tline[w/8] = 0;\n\t\tUI_Print_Center(x, y+h-8, w, line, false);\n\t\t// and dots  --  shifted by 4 pixels\n\t\tfor (i=0; i < strlen(line); i++)\n\t\t\tline[i] = (line[i]=='\\xa0' ? '\\x85' : ' ');\n\t\tUI_Print_Center(x+4, y+h-8, w, line, false);\n\t}\n}\n\nvoid Add_Server_Draw(void)\n{\n\tint x, y, w, h;\n\tw = 176;\n\th = 56;\n\tx = (vid.width - w)/2;\n\ty = (vid.height - h)/2;\n\tx = (x/8) * 8;\n\ty = (y/8) * 8;\n\n\tDraw_TextBox (x-16, y-16, w/8+1, h/8+2);\n\n\tx-=4;\n\ty-=4;\n\tw+=8;\n\th+=8;\n\n\tUI_Print_Center(x, y+4, w, \"Create new server\", true);\n\n\tUI_Print(x+4, y + 20, \"Address:\", newserver_pos==0);\n\tCEditBox_Draw(&edit1, x+70, y+20, newserver_pos==0);\n\n\tUI_Print_Center(x+4, y+40, w, \"accept\", newserver_pos==1);\n\tif (newserver_pos == 1)\n\t\tDraw_Character (x+59, y+40, 13);\n\n\tUI_Print_Center(x+4, y+50, w, \"cancel\", newserver_pos==2);\n\tif (newserver_pos == 2)\n\t\tDraw_Character (x+59, y+50, 13);\n}\n\nvoid SB_Servers_OnShow (void)\n{\n\tstatic qbool updated = false;\n\n\tif (sb_autoupdate.value && !updated) {\n\n\t\tGetServerPingsAndInfos(true);\n\t\tupdated = true;\n\t}\n\n\tresort_servers = 1;\n}\n\nstatic const char *SB_Ping_Color(int ping)\n{\n\tdouble frame_duration = 1000.0/77.0;\n\tint frames = ping / frame_duration;\n\n\tswitch (frames) {\n\t\tcase 0: return \"1f0\"; // 13\n\t\tcase 1: return \"3d0\"; // 26\n\t\tcase 2: return \"790\"; // 39\n\t\tcase 3: return \"880\"; // 51\n\t\tcase 4: return \"a60\"; // 65\n\t\tcase 5: \n\t\tcase 6: \n\t\tcase 7: \n\t\tcase 8:\n\t\tcase 9: return \"d30\"; // 70-116\n\t\tdefault: return \"f00\"; // 116+\n\t}\n}\n\nstatic unsigned int SB_Servers_Hovered_Column(int w)\n{\n\tint w_from = w, w_to = w;\n\tint col;\n\n\tfor (col = sb_columns_size - 1; col > 0; col--) {\n\t\tif (sb_columns[col].showvar->integer) {\n\t\t\tw_to = w_from;\n\t\t\tw_from -= sb_columns[col].width * LETTERWIDTH + LETTERWIDTH;\n\t\t\tif (w_from <= mouse_header_pos_y && mouse_header_pos_y < w_to) {\n\t\t\t\treturn col;\n\t\t\t}\n\t\t}\n\t}\n\treturn 0;\n}\n\n// returns the horizontal offset of the second shown column in characters\nint SB_Servers_Draw_ColumnHeaders(int x, int y, int w)\n{\n\tint pos = w/8;\n\tchar line[1024];\n\tunsigned int colidx;\n\tqbool hovered;\n\n\tmouse_hovered_column = SB_Servers_Hovered_Column(w);\n\tmemset(line, ' ', pos);\n\tline[pos] = 0;\n\n\tUI_DrawColoredAlphaBox(x, y, w, 8, RGBA_TO_COLOR(10, 10, 10, 200));\n\n\tfor (colidx = sb_columns_size - 1; colidx > 0; colidx--) {\n\t\tsb_column_t *col = &sb_columns[colidx];\n\t\thovered = mouse_in_header_row && mouse_hovered_column == colidx;\n\n\t\tif (col->showvar->integer) {\n\t\t\tif (mouse_in_header_row && hovered) {\n\t\t\t\tUI_DrawColoredAlphaBox(x + pos * LETTERWIDTH - col->width * LETTERWIDTH - LETTERWIDTH,\n\t\t\t\t\t\ty, col->width * LETTERWIDTH + LETTERWIDTH, 8, RGBA_TO_COLOR(30, 30, 30, 200));\n\t\t\t}\n\t\t\tAdd_Column2(x, y, &pos, col->name, col->width, !hovered);\n\t\t}\n\t}\n\t// name is always displayed\n\thovered = mouse_in_header_row && mouse_hovered_column == colidx;\n\tif (hovered) {\n\t\tUI_DrawColoredAlphaBox(x, y, pos * LETTERWIDTH + LETTERWIDTH, 8, RGBA_TO_COLOR(30, 30, 30, 200));\n\t}\n\tUI_Print(x, y, \"name\", !(mouse_in_header_row && mouse_hovered_column == 0));\n\n\treturn pos;\n}\n\nvoid SB_Servers_Draw (int x, int y, int w, int h)\n{\n\tchar line[1024];\n\tint i, pos, listsize;\n\n\tif (updating_sources) {\n\t\tUI_Print_Center(x, y + 8, w, \"Updating, please wait\", false);\n\t\treturn;\n\t}\n\n\tif (rebuild_servers_list)\n\t\tRebuild_Servers_List();\n\n\tif (searchtype != search_server  ||  searchtime + SEARCH_TIME < cls.realtime)\n\t\tsearchtype = search_none;\n\n\tif (resort_servers)\n\t{\n\t\tSort_Servers();\n\t\tresort_servers = 0;\n\t}\n\n\tif (serversn_passed > 0)\n\t{\n\t\tSB_ServerList_Lock();\n\n\t\tServers_pos = max(Servers_pos, 0);\n\t\tServers_pos = min(Servers_pos, serversn_passed-1);\n\n\t\tlistsize = (int)(h/8) - (sb_status.value ? 3 : 0);\n\n\t\tlistsize--;     // column titles\n\n\t\tpos = SB_Servers_Draw_ColumnHeaders(x, y, w);\n\n\t\tif (Servers_disp < 0)\n\t\t\tServers_disp = 0;\n\t\tif (Servers_pos > Servers_disp + listsize - 1)\n\t\t\tServers_disp = Servers_pos - listsize + 1;\n\t\tif (Servers_disp > serversn_passed - listsize)\n\t\t\tServers_disp = max(serversn_passed - listsize, 0);\n\t\tif (Servers_pos < Servers_disp)\n\t\t\tServers_disp = Servers_pos;\n\n\t\tif (updating_sources) {\n\t\t\tSB_ServerList_Unlock();\n\t\t\treturn;\n\t\t}\n\n\t\tfor (i = 0; i < listsize; i++)\n\t\t{\n\t\t\tint servnum = Servers_disp + i;\n\t\t\tif (servnum >= serversn_passed)\n\t\t\t\tbreak;\n\n\t\t\tif (servnum==Servers_pos) {\n\t\t\t\tUI_DrawGrayBox(x, y+8*(i+1), w, 8);\n\t\t\t\tUI_DrawCharacter(x + 8*pos, y+8*(i+1), FLASHINGARROW());\n\t\t\t} else if (servers[servnum]->qizmo) {\n\t\t\t\tUI_DrawColoredAlphaBox(x, y+8*(i+1), w, 8, RGBA_TO_COLOR(25, 25, 75, 255));\n\t\t\t} else if (servers[servnum]->qwfwd) {\n\t\t\t\tif (SB_Is_Selected_Proxy(servers[servnum])) {\n\t\t\t\t\tUI_DrawColoredAlphaBox(x, y+8*(i+1), w, 8, RGBA_TO_COLOR(25, 120, 40, 255));\n\t\t\t\t} else {\n\t\t\t\t\tUI_DrawColoredAlphaBox(x, y+8*(i+1), w, 8, RGBA_TO_COLOR(25, 50, 25, 255));\n\t\t\t\t}\n\t\t\t} else if (servnum % 2) {\n\t\t\t\tUI_DrawColoredAlphaBox(x, y+8*(i+1), w, 8, RGBA_TO_COLOR(25, 25, 25, 125));\n\t\t\t} else {\n\t\t\t\tUI_DrawColoredAlphaBox(x, y+8*(i+1), w, 8, RGBA_TO_COLOR(50, 50, 50, 125));\n\t\t\t}\n\n\t\t\t// Display server\n\t\t\tpos = w/8;\n\t\t\tmemset(line, ' ', 1000);\n\n\t\t\tif (sb_showtimelimit.value)\n\t\t\t\tAdd_Column2(x, y+8*(i+1), &pos, servers[servnum]->display.timelimit, COL_TIMELIMIT, servnum==Servers_pos);\n\t\t\tif (sb_showfraglimit.value)\n\t\t\t\tAdd_Column2(x, y+8*(i+1), &pos, servers[servnum]->display.fraglimit, COL_FRAGLIMIT, servnum==Servers_pos);\n\t\t\tif (sb_showplayers.value)\n\t\t\t\tAdd_ColumnColored(x, y+8*(i+1), &pos, servers[servnum]->display.players, COL_PLAYERS, occupancy_colors[servers[servnum]->occupancy]);\n\n\t\t\tif (sb_showmap.value)\n\t\t\t\tAdd_Column2(x, y+8*(i+1), &pos, servers[servnum]->display.map, COL_MAP, servnum==Servers_pos);\n\t\t\tif (sb_showgamedir.value)\n\t\t\t\tAdd_Column2(x, y+8*(i+1), &pos, servers[servnum]->display.gamedir, COL_GAMEDIR, servnum==Servers_pos);\n\t\t\tif (sb_showping.value) {\n\t\t\t\tconst char *ping = (servers[servnum]->bestping >= 0) ? servers[servnum]->display.bestping : servers[servnum]->display.ping;\n\t\t\t\tconst char *color = SB_Ping_Color((servers[servnum]->bestping >= 0) ? servers[servnum]->bestping : servers[servnum]->ping);\n\t\t\t\tAdd_ColumnColored(x, y+8*(i+1), &pos, ping, COL_PING, color);\n\t\t\t}\n\t\t\tif (sb_showaddress.value)\n\t\t\t\tAdd_Column2(x, y+8*(i+1), &pos, servers[servnum]->display.ip, COL_IP, servnum==Servers_pos);\n\n\t\t\t// 'name' column\n\t\t\tif (servers[servnum]->qwfwd) {\n\t\t\t\tif (servers[servnum]->display.name[0]) {\n\t\t\t\t\tsnprintf(line, sizeof(line), \"proxy %s\", servers[servnum]->display.name);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tsnprintf(line, sizeof(line), \"proxy %s\", servers[servnum]->display.ip);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (servers[servnum]->display.name[0]) {\n\t\t\t\tsnprintf(line, sizeof(line), \"%s\", servers[servnum]->display.name);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tsnprintf(line, sizeof(line), \"%s\", servers[servnum]->display.ip);\n\t\t\t}\n\n\t\t\t// display only as much as fits into the column\n\t\t\tline[min(pos, sizeof(line)-1)] = '\\0';\n\n\t\t\tUI_Print(x, y+8*(i+1), line, servnum==Servers_pos);\n\t\t}\n\n\n\t\t//\n\t\t// status line\n\t\t//\n\t\tif (sb_status.value  &&  serversn_passed > 0)\n\t\t{\n\t\t\t// Semaphore lock\n\t\t\tif(show_serverinfo)\n\t\t\t{\n\t\t\t\tSys_SemWait(&serverinfo_semaphore);\n\t\t\t\tDraw_Server_Statusbar(x, y, w, h, servers[Servers_pos], Servers_pos, serversn_passed);\n\t\t\t\tSys_SemPost(&serverinfo_semaphore);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDraw_Server_Statusbar(x, y, w, h, servers[Servers_pos], Servers_pos, serversn_passed);\n\t\t\t}\n\t\t}\n\t\tSB_ServerList_Unlock();\n\t} else if (!adding_server) {\n\t\tUI_Print_Center(x, y+8, w, \"No servers filtered\", false);\n\t\tUI_Print_Center(x, y+24, w, \"Press [space] to refresh the list\", true);\n\t\tUI_Print_Center(x, y+40, w, \"Mark some sources on the next tab\", false);\n\t\tUI_Print_Center(x, y+48, w, \"or press [Insert] to add a server\", false);\n\n\t\tif (strlen(searchstring)) {\n\t\t\tchar line[1024];\n\t\t\tsnprintf(line, sizeof (line), \"search for: %-7s\", searchstring);\n\t\t\tUI_Print_Center(x, y+h-8, w, line, true);\n\t\t}\n\t}\n\n\t// adding server\n\tif (adding_server)\n\t\tAdd_Server_Draw();\n}\n\nvoid PingPhase_Draw(void)\n{\n\tint x, y, w, h;\n\tw = 144;\n\th = 24;\n\tx = (vid.width - w)/2;\n\ty = (vid.height - h)/2;\n\tx = (x/8) * 8;\n\ty = (y/8) * 8;\n\n\tDraw_TextBox (x-16, y-16, w/8+1, h/8+2);\n\n\tUI_Print_Center(x, y, w,\n\t\t\tping_phase==1 ? \"Pinging Servers\" : \"Getting Infos\",\n\t\t\tfalse);\n\tif (abort_ping)\n\t\tUI_Print_Center(x, y+16, w, \"cancelled\", true);\n\telse\n\t{\n\t\tUI_Print(x, y+16,\n\t\t\t\t\"\\x80\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x82\",\n\t\t\t\tfalse);\n\t\tUI_DrawCharacter(x+8+(int)(ping_pos*15*8), y+16, '\\x83');\n\t}\n}\n\nvoid UpdatingSources_Draw(void)\n{\n\tint x, y, w, h;\n\tw = 144;\n\th = 24;\n\tx = (vid.width - w)/2;\n\ty = (vid.height - h)/2;\n\tx = (x/8) * 8;\n\ty = (y/8) * 8;\n\n\tDraw_TextBox (x-16, y-16, w/8+1, h/8+2);\n\n\tUI_Print_Center(x, y, w, \"Updating Sources\", false);\n\n\tif (abort_ping)\n\t\tUI_Print_Center(x, y+16, w, \"cancelled\", true);\n\telse\n\t{\n\t\tUI_Print(x, y+16,\n\t\t\t\t\"\\x80\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x81\\x82\",\n\t\t\t\tfalse);\n\t\tUI_DrawCharacter(x+8+(int)(ping_pos*15*8), y+16, '\\x83');\n\t}\n}\n\nvoid Serverinfo_Help_Draw(int x, int y, int wPixels)\n{\n\tint wLetters = wPixels / 8;\n\n\tif (vid.conheight < 216 || vid.conwidth < 384) {\n\t\treturn;\n\t}\n\n\t// make it wider\n\tx -= LETTERWIDTH * 4;\n\twLetters += 8;\n\n\tDraw_TextBox (x, y, wLetters, sb_findroutes.integer > 0 ? 4 : 3);\n\tx += LETTERWIDTH * 2;\n\ty += LETTERWIDTH;\n\tUI_Print(x, y,  \"\\xDBj\\xDD join \\xDBo\\xDD observe \\xDBq\\xDD QuakeTV obs.\", false);\n\ty += LETTERWIDTH;\n\tUI_Print(x, y, \"\\xDB\" \"c\\xDD copy to clipboard \\xDBv\\xDD say in chat\", false);\n\ty += LETTERWIDTH;\n\tUI_Print(x, y, \"\\xDB\" \"ctrl+c\\xDD copy addr. \\xDB\" \"ctrl+v\\xDD say team\", false);\n\n\tif (sb_findroutes.integer > 0) {\n\t\tint pathlen = SB_PingTree_GetPathLen(&show_serverinfo->address);\n\t\ty += LETTERWIDTH;\n\t\tif (pathlen < 0) {\n\t\t\tUI_Print(x, y, \"Route: No route found\", false);\n\t\t}\n\t\telse if (pathlen == 0) {\n\t\t\tUI_Print(x, y, \"Route: Direct route is best\", false);\n\t\t}\n\t\telse if (pathlen == 1) {\n\t\t\tUI_Print(x, y, \"Route: 1 hop \" \"\\xDB\" \"n\\xDD direct connect\", false);\n\t\t}\n\t\telse if (pathlen > 1) {\n\t\t\tchar tmp[64];\n\t\t\tsnprintf(&tmp[0], sizeof(tmp), \"Route: %d hops, \" \"\\xDB\" \"n\\xDD direct connect\", pathlen);\n\t\t\tUI_Print(x, y, tmp, false);\n\t\t}\n\t}\n}\n\nvoid Serverinfo_Draw (void)\n{\n\textern int server_during_update;\n\tchar buf[512];\n\n\tint x, y, w, h;\n\tw = 200 + 40;\n\th = 112;\n\n\tif (vid.height >= 200 + 40)\n\t\th += 8*5;\n\n\tx = (vid.width - w)/2;\n\ty = (vid.height - h)/2;\n\n\tx = (x/8) * 8;\n\ty = (y/8) * 8;\n\n\tDraw_TextBox (x-16, y-16, w/8+1, h/8+2);\n\n\tx-=4;\n\ty-=4;\n\tw+=8;\n\th+=8;\n\n\twhile (server_during_update)\n\t\tSys_MSleep(5);\n\n\tstrlcpy(buf, show_serverinfo->display.name, sizeof(buf));\n\tbuf[w/8] = 0;\n\tUI_Print_Center(x, y, w, buf, false);\n\tstrlcpy(buf, show_serverinfo->display.ip, sizeof(buf));\n\tbuf[w/8] = 0;\n\tUI_Print_Center(x, y+10, w, buf, false);\n\n\tstrlcpy(buf, \" players serverinfo sources \", sizeof(buf));\n\tif (serverinfo_pos == 0)\n\t\tmemcpy (buf, \"\\x10\\xF0\\xEC\\xE1\\xF9\\xE5\\xF2\\xF3\\x11\", 9);\n\tif (serverinfo_pos == 1)\n\t\tmemcpy (buf + 8, \"\\x10\\xF3\\xE5\\xF2\\xF6\\xE5\\xF2\\xE9\\xEE\\xE6\\xEF\\x11\", 12); // FIXME: non-ascii chars\n\tif (serverinfo_pos == 2)\n\t\tmemcpy (buf + 19, \"\\x10\\xF3\\xEF\\xF5\\xF2\\xE3\\xE5\\xF3\\x11\", 9); // FIXME: non-ascii chars\n\n\tUI_Print_Center(x, y+24, w, buf, false);\n\n\tmemset(buf, '\\x1E', w/8);\n\tbuf[w/8] = 0;\n\tbuf[w/8-1] = '\\x1F';\n\tbuf[0] = '\\x1D';\n\tUI_Print(x, y+32, buf, false);\n\n\tif (testing_connection)\n\t\th -= 5*8;\n\n\tSys_SemWait(&serverinfo_semaphore);\n\tswitch (serverinfo_pos)\n\t{\n\t\tcase 0: // players\n\t\t\tServerinfo_Players_Draw(x, y+40, w, h-40); break;\n\t\tcase 1: // serverinfo / rules\n\t\t\tServerinfo_Rules_Draw(x, y+40, w, h-40); break;\n\t\tcase 2: // sources\n\t\t\tServerinfo_Sources_Draw(x, y+40, w, h-40); break;\n\t\tdefault:\n\t\t\t;\n\t}\n\tSys_SemPost(&serverinfo_semaphore);\n\n\tServerinfo_Help_Draw(x - 16, y + h + LETTERWIDTH, w);\n\n\tif (testing_connection)\n\t\tSB_Test_Frame();\n}\n\nvoid Serverinfo_Players_Draw(int x, int y, int w, int h)\n{\n\tserver_data *s = show_serverinfo; // shortcut\n\tchar *tp = (ValueForKey(s, \"teamplay\") ? ValueForKey(s, \"teamplay\") : \"\"); // tp at least \"\" not NULL\n\tqbool support_tp = s->support_teams && strcmp(tp, \"0\"); // is server support team info per player and teamplay != 0\n\n\tint i;\n\tint listsize;\n\tint match = 0;\n\tint top1=0, bottom1=0, frags1=0;\n\tint top2=0, bottom2=0, frags2=0;\n\tchar *t1 = \"\", *t2 = \"\";\n\tint qizmo = 0;\n\n\tif (s == NULL  ||  s->playersn + s->spectatorsn <= 0)\n\t\treturn;\n\n\tif (!strcmp(s->display.gamedir, \"qizmo\"))\n\t\tqizmo = 1;\n\n\t// check if this is a teamplay (2 teams) match\n\tif (!qizmo && s->playersn > 2 && strcmp(tp, \"0\")) // at least 3 players\n\t{\n\t\ttop1 = bottom1 = top2 = bottom2 = -1;\n\t\tfrags1 = frags2 = 0;\n\t\tmatch = 1;\n\n\t\tfor (i = 0; i < s->playersn + s->spectatorsn; i++)\n\t\t{\n\t\t\tif (s->players[i]->spec)\n\t\t\t\tcontinue; // ignore spec\n\n\t\t\tif (top1 < 0) // ok, we found member from first team, save colors/team\n\t\t\t{\n\t\t\t\ttop1\t= s->players[i]->top;\n\t\t\t\tbottom1\t= s->players[i]->bottom;\n\t\t\t\tt1\t\t= s->players[i]->team;\n\t\t\t}\n\n\t\t\t// we now can sort teams by two way: colors, and actual teams\n\n\t\t\tif (    ( support_tp && !strncmp(t1, s->players[i]->team, sizeof(s->players[0]->team) - 1)) // teams matched\n\t\t\t\t\t|| (!support_tp && s->players[i]->top == top1 && s->players[i]->bottom == bottom1) // colors matched\n\t\t\t   )\n\t\t\t{\n\t\t\t\tfrags1 += s->players[i]->frags;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (top2 < 0) // ok, we found member from second team, save colors/team\n\t\t\t{\n\t\t\t\ttop2\t= s->players[i]->top;\n\t\t\t\tbottom2\t= s->players[i]->bottom;\n\t\t\t\tt2\t\t= s->players[i]->team;\n\t\t\t}\n\n\t\t\tif (    ( support_tp && !strncmp(t2, s->players[i]->team, sizeof(s->players[0]->team) - 1)) // teams matched\n\t\t\t\t\t|| (!support_tp && s->players[i]->top == top2 && s->players[i]->bottom == bottom2) // colors matched\n\t\t\t   )\n\t\t\t{\n\t\t\t\tfrags2 += s->players[i]->frags;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// found member from third team, we does't support such case\n\t\t\tmatch = 0;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (top1 < 0 || top2 < 0)\n\t\t\tmatch = 0; // we does't have two teams\n\n\t\tif (match  &&  frags2 > frags1)\n\t\t{\n\t\t\tint swap;\n\t\t\tswap = frags2;   frags2  = frags1;   frags1  = swap;\n\t\t\tswap = top2;     top2    = top1;     top1    = swap;\n\t\t\tswap = bottom2;  bottom2 = bottom1;  bottom1 = swap;\n\t\t}\n\t}\n\n\tlistsize = h/8-1 - match;\n\n\tif (serverinfo_players_pos > s->playersn + s->spectatorsn - listsize)\n\t\tserverinfo_players_pos = s->playersn + s->spectatorsn - listsize;\n\tif (serverinfo_players_pos < 0)\n\t\tserverinfo_players_pos = 0;\n\n\tUI_Print(x, y, support_tp ? \"png tm frgs team name\" : \"png tm frgs name\", true);\n\tfor (i=0; i < listsize; i++)\n\t{\n\t\tchar buf[100], fragsbuf[100] = {0};\n\t\tint top, bottom;\n\n\t\tif (serverinfo_players_pos + i >= s->playersn + s->spectatorsn)\n\t\t\tbreak;\n\n\t\tif (!s->players[serverinfo_players_pos+i]->spec) {\n\t\t\tint frags_tmp = bound(-99, s->players[serverinfo_players_pos+i]->frags, 9999);\n\t\t\tsnprintf(fragsbuf, sizeof(fragsbuf), \"%3d%s\", frags_tmp, frags_tmp < 1000 ? \" \" : \"\"); // \"centering\" frags as much as possible\n\t\t}\n\n\t\tif (support_tp)\n\t\t\tsnprintf(buf, sizeof(buf), \"%3d %2d %4.4s %4.4s %s\", // frags column fixed to 4 symbols\n\t\t\t\t\tmax(min(s->players[serverinfo_players_pos+i]->ping, 999), 0),\n\t\t\t\t\tmax(min(s->players[serverinfo_players_pos+i]->time, 99), 0),\n\t\t\t\t\ts->players[serverinfo_players_pos+i]->spec ? \"spec\" : fragsbuf,\n\t\t\t\t\ts->players[serverinfo_players_pos+i]->team,\n\t\t\t\t\ts->players[serverinfo_players_pos+i]->name);\n\t\telse\n\t\t\tsnprintf(buf, sizeof(buf), \"%3d %2d %4.4s %s\", // frags column fixed to 4 symbols\n\t\t\t\t\tmax(min(s->players[serverinfo_players_pos+i]->ping, 999), 0),\n\t\t\t\t\tmax(min(s->players[serverinfo_players_pos+i]->time, 99), 0),\n\t\t\t\t\ts->players[serverinfo_players_pos+i]->spec ? \"spec\" : fragsbuf,\n\t\t\t\t\ts->players[serverinfo_players_pos+i]->name);\n\n\t\tbuf[w/8] = 0;\n\n\t\tif (!s->players[serverinfo_players_pos+i]->spec) {\n\t\t\ttop\t\t= s->players[serverinfo_players_pos+i]->top;\n\t\t\tbottom\t= s->players[serverinfo_players_pos+i]->bottom;\n\n\t\t\tif (support_tp && match) // force players have same colors in same team, in such case\n\t\t\t{\n\t\t\t\tif (!strncmp(t1, s->players[serverinfo_players_pos+i]->team, sizeof(s->players[0]->team) - 1))\n\t\t\t\t{\n\t\t\t\t\ttop\t\t= top1;\n\t\t\t\t\tbottom\t= bottom1;\n\t\t\t\t}\n\t\t\t\telse if (!strncmp(t2, s->players[serverinfo_players_pos+i]->team, sizeof(s->players[0]->team) - 1))\n\t\t\t\t{\n\t\t\t\t\ttop\t\t= top2;\n\t\t\t\t\tbottom\t= bottom2;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tDraw_Fill (x+7*8-2, y+i*8+8   +1, 34, 4, top);\n\t\t\tDraw_Fill (x+7*8-2, y+i*8+8+4 +1, 34, 3, bottom);\n\t\t}\n\n\t\tUI_Print(x, y+i*8+8, buf, false);\n\t}\n\n\tif (match)\n\t{\n\t\tchar buf[100];\n\t\tDraw_Fill (x+13*8, y+listsize*8+8   +1, 40, 4, top1);\n\t\tDraw_Fill (x+13*8, y+listsize*8+8+4 +1, 40, 4, bottom1);\n\t\tDraw_Fill (x+21*8, y+listsize*8+8   +1, 40, 4, top2);\n\t\tDraw_Fill (x+21*8, y+listsize*8+8+4 +1, 40, 4, bottom2);\n\n\t\tsnprintf(buf, sizeof (buf), \"      score:  %3d  -  %3d\", frags1, frags2);\n\t\tUI_Print(x, y+listsize*8+8, buf, false);\n\t}\n}\n\nint serverinfo_rules_pos = 0;\n\nvoid Serverinfo_Rules_Draw(int x, int y, int w, int h)\n{\n\tint i;\n\tint listsize = h/8;\n\tserver_data *s = show_serverinfo;\n\n\tif (serverinfo_rules_pos > s->keysn - listsize)\n\t\tserverinfo_rules_pos = s->keysn - listsize;\n\tif (serverinfo_rules_pos < 0)\n\t\tserverinfo_rules_pos = 0;\n\n\tfor (i=0; i < listsize; i++)\n\t{\n\t\tchar buf[128];\n\n\t\tif (serverinfo_rules_pos + i >= s->keysn)\n\t\t\tbreak;\n\t\tsnprintf(buf, sizeof (buf), \"%-13.13s %-*s\",\n\t\t\t\ts->keys[serverinfo_rules_pos+i],\n\t\t\t\tw/8-1-13,\n\t\t\t\ts->values[serverinfo_rules_pos+i]);\n\n\t\tbuf[w/8] = 0;\n\n\t\tUI_Print(x, y+i*8, buf, false);\n\t}\n}\n\nint IsInSource(source_data *source, server_data *serv)\n{\n\tint i;\n\tfor (i=0; i < source->serversn; i++)\n\t\tif (!memcmp(&source->servers[i]->address, &serv->address, sizeof(netadr_t)))\n\t\t\treturn i+1;\n\treturn false;\n}\n\nvoid Serverinfo_Sources_Draw(int x, int y, int w, int h)\n{\n\tint i;\n\tint listsize;\n\n\tserver_data *s = show_serverinfo;\n\n\tif (s == NULL  ||  sourcesn_updated <= 0)\n\t\treturn;\n\n\tlistsize = h/8-1;\n\n\tif (serverinfo_sources_pos > serverinfo_sources_disp + listsize - 1)\n\t\tserverinfo_sources_disp = serverinfo_sources_pos - listsize + 1;\n\tif (serverinfo_sources_disp > sourcesn_updated - listsize)\n\t\tserverinfo_sources_disp = max(sourcesn_updated - listsize, 0);\n\tif (serverinfo_sources_pos < serverinfo_sources_disp)\n\t\tserverinfo_sources_disp = serverinfo_sources_pos;\n\n\tUI_Print(x, y, \" type    name\", true);\n\tfor (i=0; i < listsize; i++)\n\t{\n\t\tchar buf[128], buf2[16];\n\n\t\tif (serverinfo_sources_disp + i >= sourcesn_updated)\n\t\t\tbreak;\n\n\t\tstrlcpy(buf2, SB_Source_Type_Name(sources[serverinfo_sources_disp+i]->type), sizeof (buf2));\n\n\t\tsnprintf(buf, sizeof (buf), \"%s   %s\", buf2, sources[serverinfo_sources_disp+i]->name);\n\t\tbuf[w/8] = 0;\n\n\t\tUI_Print(x, y+i*8+8, buf,\n\t\t\t\tIsInSource(sources[serverinfo_sources_disp+i], s));\n\n\t\tif (serverinfo_sources_pos == serverinfo_sources_disp+i)\n\t\t\tUI_DrawCharacter(x+8*7, y+i*8+8, '\\x8D');\n\t}\n}\n\nconst char *SB_Source_Type_Location_Name(sb_source_type_t type)\n{\n\tswitch (type) {\n\t\tcase type_master: return \"addr\";\n\t\tcase type_file: return \"file\";\n\t\tcase type_url: return \"url\";\n\t\tcase type_dummy: return \"dummy\";\n\t\tdefault:\n\t\t\t\t Sys_Error(\"SB_Source_Type_Location_Name(): Invalid sb_source_type_t type\");\n\t\t\t\t return \"ERROR\";\n\t}\n}\n\nvoid Add_Source_Draw(void)\n{\n\tint x, y, w, h;\n\tw = 176;\n\th = 72;\n\tx = (vid.width - w)/2;\n\ty = (vid.height - h)/2;\n\tx = (x/8) * 8;\n\ty = (y/8) * 8;\n\n\tDraw_TextBox (x-16, y-16, w/8+1, h/8+2);\n\n\tx-=4;\n\ty-=4;\n\tw+=8;\n\th+=8;\n\n\tUI_Print_Center(x, y+4, w, \"Create new source\", true);\n\n\tUI_Print(x+4, y + 20, \"type\", newsource_pos==0);\n\tUI_Print(x+54, y + 20, SB_Source_Type_Name(newsource_type), newsource_pos==0);\n\tif (newsource_pos == 0)\n\t\tDraw_Character (x+42, y+20, 13);\n\n\tUI_Print(x+4, y + 30, \"name\", newsource_pos==1);\n\tCEditBox_Draw(&edit1, x+54, y+30, newsource_pos==1);\n\tif (newsource_pos == 1)\n\t\tDraw_Character (x+42, y+30, 13);\n\n\tUI_Print(x+4, y + 40, SB_Source_Type_Location_Name(newsource_type), newsource_pos==2);\n\tCEditBox_Draw(&edit2, x+54, y+40, newsource_pos==2);\n\tif (newsource_pos == 2)\n\t\tDraw_Character (x+42, y+40, 13);\n\n\tUI_Print_Center(x+4, y+60, w, \"accept\", newsource_pos==3);\n\tif (newsource_pos == 3)\n\t\tDraw_Character (x+59, y+60, 13);\n\n\tUI_Print_Center(x+4, y+70, w, \"cancel\", newsource_pos==4);\n\tif (newsource_pos == 4)\n\t\tDraw_Character (x+59, y+70, 13);\n}\n\n\nvoid SB_Sources_Draw (int x, int y, int w, int h)\n{\n\tint i, listsize;\n\tchar line[1024];\n\tSYSTEMTIME curtime;\n\n\tif (sourcesn <= 0)\n\t\treturn;\n\n\tGetLocalTime(&curtime);\n\n\tif (resort_sources)\n\t{\n\t\tSort_Sources();\n\t\tresort_sources = 0;\n\t}\n\n\tlistsize = (int)(h/8) - (sb_status.value ? 3 : 0);\n\n\tUI_Print_Center(x, y, w, \" type   name             servs updated\", true);\n\n\tlistsize--;     // subtract one line (column titles)\n\n\tif (Sources_pos > Sources_disp + listsize - 1)\n\t\tSources_disp = Sources_pos - listsize + 1;\n\tif (Sources_disp > sourcesn - listsize)\n\t\tSources_disp = max(sourcesn - listsize, 0);\n\tif (Sources_pos < Sources_disp)\n\t\tSources_disp = Sources_pos;\n\n\tfor (i = 0; i < listsize; i++)\n\t{\n\t\tchar type[10], time[10];\n\t\tsource_data *s = sources[Sources_disp + i];\n\n\t\tint sourcenum = Sources_disp + i;\n\t\tif (sourcenum >= sourcesn)\n\t\t\tbreak;\n\n\t\tstrlcpy(type, SB_Source_Type_Name(s->type), sizeof (type));\n\n\t\tif (s->type == type_dummy)\n\t\t\tstrlcpy (time, \" n/a \", sizeof (time));\n\t\telse\n\t\t\tif (s->last_update.wYear)\n\t\t\t{\n\t\t\t\tif (s->last_update.wYear != curtime.wYear)\n\t\t\t\t\tsnprintf(time, sizeof (time), \"%4dy\", s->last_update.wYear);\n\t\t\t\telse if (s->last_update.wMonth != curtime.wMonth ||\n\t\t\t\t\t\ts->last_update.wDay != curtime.wDay)\n\t\t\t\t\tsnprintf(time, sizeof (time),  \"%02d-%02d\", s->last_update.wMonth, s->last_update.wDay);\n\t\t\t\telse\n\t\t\t\t\tsnprintf(time, sizeof (time),  \"%2d:%02d\",\n\t\t\t\t\t\t\ts->last_update.wHour,\n\t\t\t\t\t\t\ts->last_update.wMinute);\n\t\t\t}\n\t\t\telse\n\t\t\t\tstrlcpy (time, \"never\", sizeof (time));\n\n\t\tsnprintf(line, sizeof (line), \"%s %c%-17.17s %4d  %s \", type,\n\t\t\t\tsourcenum==Sources_pos ? 141 : ' ',\n\t\t\t\ts->name, s->serversn, time);\n\n\t\tUI_Print_Center(x, y+8*(i+1), w, line, s->checked);\n\t}\n\n\t//\n\t// status line\n\t//\n\tif (sb_status.value)\n\t{\n\t\tint total_servers = 0;\n\t\tint sel_servers = 0, sel_sources = 0;\n\n\t\tmemset(line, '\\x1E', w/8);\n\t\tline[w/8] = 0;\n\t\tline[w/8-1] = '\\x1F';\n\t\tline[0] = '\\x1D';\n\t\tUI_Print(x, y+h-24, line, false);\n\n\t\t// get stats\n\t\tfor (i=0; i < sourcesn; i++)\n\t\t{\n\t\t\ttotal_servers += sources[i]->serversn;\n\t\t\tif (sources[i]->checked)\n\t\t\t{\n\t\t\t\tsel_servers += sources[i]->serversn;\n\t\t\t\tsel_sources ++;\n\t\t\t}\n\t\t}\n\n\t\tsnprintf(line, sizeof (line), \"%d sources selected (%d servers)\", sel_sources, sel_servers);\n\t\tUI_Print_Center(x, y+h-16, w, line, false);\n\n\t\tsnprintf(line, sizeof (line), \"of %d total (%d servers)\", sourcesn, total_servers);\n\t\tUI_Print_Center(x, y+h-8, w, line, false);\n\t}\n\n\t// adding source\n\tif (adding_source)\n\t\tAdd_Source_Draw();\n}\n\n\nvoid SB_Players_Draw (int x, int y, int w, int h)\n{\n\tint i, listsize;\n\tchar line[2048];\n\tint hw = min(w/8 - 16, 60) -4; // hostname width (in chars)\n\n\tif (ping_phase)\n\t\treturn;\n\n\tif (searchtype != search_player  ||  searchtime + SEARCH_TIME < cls.realtime)\n\t\tsearchtype = search_none;\n\n\tif (rebuild_servers_list)\n\t\tRebuild_Servers_List();\n\n\tif (rebuild_all_players  &&  show_serverinfo == NULL)\n\t\tRebuild_All_Players();\n\n\tif (resort_all_players)\n\t{\n\t\tSort_All_Players();\n\t\tresort_all_players = 0;\n\t}\n\n\tif (all_players_n <= 0)\n\t\treturn;\n\n\tPlayers_pos = max(Players_pos, 0);\n\tPlayers_pos = min(Players_pos, all_players_n-1);\n\n\tlistsize = (int)(h/8) - (sb_status.value ? 3 : 0);\n\n\t//UI_Print_Center(x, y, w, \"name            server              \", true);\n\tsnprintf(line, sizeof (line), \"name            %-*s png\", hw, \"server\");\n\tUI_Print_Center(x, y, w, line, true);\n\n\tlistsize--;     // subtract one line (column titles)\n\n\tif (Players_pos > Players_disp + listsize - 1)\n\t\tPlayers_disp = Players_pos - listsize + 1;\n\tif (Players_disp > all_players_n - listsize)\n\t\tPlayers_disp = max(all_players_n - listsize, 0);\n\tif (Players_pos < Players_disp)\n\t\tPlayers_disp = Players_pos;\n\n\tfor (i = 0; i < listsize; i++)\n\t{\n\t\tplayer_host *s = all_players[Players_disp + i];\n\n\t\tint num = Players_disp + i;\n\t\tif (num >= all_players_n)\n\t\t\tbreak;\n\n\t\tsnprintf(line, sizeof (line), \"%-15s%c%-*.*s %3s\",\n\t\t\t\ts->name, num==Players_pos ? 141 : ' ',\n\t\t\t\thw, hw, strlen(s->serv->display.name) > 0 ? s->serv->display.name : s->serv->display.ip, s->serv->display.ping);\n\n\t\tUI_Print_Center(x, y+8*(i+1), w, line, num == Players_pos);\n\t}\n\n\t//\n\t// status line\n\t//\n\tif (sb_status.value  &&  all_players_n > 0)\n\t\tDraw_Server_Statusbar(x, y, w, h, all_players[Players_pos]->serv, Players_pos, all_players_n);\n}\n\nvoid SB_SourceUnmarkAll(void)\n{\n\tint i;\n\tfor (i=0; i < sourcesn; i++)\n\t\tUnmark_Source(sources[i]);\n}\n\nvoid SB_SourceMark(void)\n{\n\tint i;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCom_Printf(\"usage:  sb_sourcemark <source-name>\\n\");\n\t\treturn;\n\t}\n\n\tfor (i=0; i < sourcesn; i++)\n\t\tif (!strcmp(sources[i]->name, Cmd_Argv(1)))\n\t\t{\n\t\t\tMark_Source(sources[i]);\n\t\t\tbreak;\n\t\t}\n}\n\nvoid MarkDefaultSources(void) {\n\tint i;\n\tfor (i=0; i < sourcesn; i++)\n\t{\n\t\tif (!strcmp(sources[i]->name, \"id limbo\") || \n\t\t\t\t!strcmp(sources[i]->name, \"Global\") ||\n\t\t\t\t!strcmp(sources[i]->name, \"QuakeServers.net\") ||\n\t\t\t\t!strcmp(sources[i]->name, \"Asgaard\")\n\t\t   )\n\t\t\tsources[i]->checked = 1;\n\t}\n}\n\n\nvoid WriteSourcesConfiguration(FILE *f)\n{\n\tint i;\n\tfprintf(f, \"sb_sourceunmarkall\\n\");\n\tfor (i=0; i < sourcesn; i++)\n\t\tif (sources[i]->checked)\n\t\t\tfprintf(f, \"sb_sourcemark \\\"%s\\\"\\n\", sources[i]->name);\n}\n\nvoid SB_Source_Add_f(void)\n{\n\tsb_source_type_t type = type_dummy;\n\n\tif (Cmd_Argc() != 4) {\n\t\tCom_Printf(\"Usage: %s <name> <address/filename> <master|file>\\n\", Cmd_Argv(0));\n\t\tif (Cmd_Argc() != 1) {\n\t\t\tCom_Printf(\"You supplied incorrect amount of arguments\\n\");\n\t\t}\n\t\treturn;\n\t}\n\n\tif (strcmp(Cmd_Argv(3), \"master\") == 0) {\n\t\ttype = type_master;\n\t}\n\telse if (strcmp(Cmd_Argv(3), \"file\") == 0) {\n\t\ttype = type_file;\n\t}\n\telse if (strcmp(Cmd_Argv(3), \"url\") == 0) {\n\t\ttype = type_url;\n\t}\n\telse {\n\t\tCom_Printf(\"Usage: %s <name> <address/filename> <master|file>\\n\", Cmd_Argv(0));\n\t\tCom_Printf(\"Last argument must be 'master' or 'file'\\n\");\n\t\treturn;\n\t}\n\n\tSB_Source_Add(Cmd_Argv(1), Cmd_Argv(2), type);\n}\n\nstatic void SB_NewSource_Shift(void)\n{\n\t// excluding dummy, presuming dummy is last\n\tnewsource_type = (newsource_type + 1) % (type_dummy);\n}\n\nvoid Add_Source_Key(int key, wchar unichar)\n{\n\tswitch (key)\n\t{\n\t\tcase K_HOME:\n\t\t\tif (keydown[K_CTRL])\n\t\t\t\tnewsource_pos = 0;\n\t\t\tbreak;\n\t\tcase K_END:\n\t\t\tif (keydown[K_CTRL])\n\t\t\t\tnewsource_pos = 4;\n\t\t\tbreak;\n\t\tcase K_UPARROW:\n\t\tcase K_MWHEELUP:\n\t\t\tnewsource_pos--;\n\t\t\tbreak;\n\t\tcase K_TAB:\n\t\t\tkeydown[K_SHIFT]?newsource_pos--:newsource_pos++;\n\t\t\tbreak;\n\t\tcase K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n\t\t\tnewsource_pos++;\n\t\t\tbreak;\n\t\tcase K_ENTER:\n\t\tcase '+':\n\t\tcase '=':\n\t\tcase '-':\n\t\t\tswitch (newsource_pos)\n\t\t\t{\n\t\t\t\tcase 0:\n\t\t\t\t\tSB_NewSource_Shift();\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\tcase 3:\n\t\t\t\t\tif (key == K_ENTER)\n\t\t\t\t\t{\n\t\t\t\t\t\tint newpos;\n\n\t\t\t\t\t\tnewpos = SB_Source_Add(edit1.text, edit2.text, newsource_type);\n\t\t\t\t\t\tif (newpos >= 0) {\n\t\t\t\t\t\t\tSources_pos = newpos;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tadding_source = 0;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4: // cancel\n\t\t\t\t\tif (key == K_ENTER)\n\t\t\t\t\t\tadding_source = 0;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase K_ESCAPE:\n\t\t\tadding_source = 0;\n\t\t\tbreak;\n\t\tcase K_BACKSPACE:\n\t\t\tif (newsource_pos != 1  &&  newsource_pos != 2)\n\t\t\t\tadding_source = 0;\n\t\t\tbreak;\n\t}\n\n\tif ((!keydown[K_CTRL] || tolower(key)=='v') && !keydown[K_ALT])\n\t{\n\t\tif (newsource_pos == 1)\n\t\t\tCEditBox_Key(&edit1, key, unichar);\n\t\tif (newsource_pos == 2)\n\t\t\tCEditBox_Key(&edit2, key, unichar);\n\t}\n\n\t// Make sure value stays within limits and enable field wrapping 0..4->0\n\tnewsource_pos = (newsource_pos + 5) % 5;\n}\n\nvoid Add_Server_Key(int key, wchar unichar)\n{\n\tswitch (key)\n\t{\n\t\tcase K_HOME:\n\t\t\tif (keydown[K_CTRL])\n\t\t\t\tnewserver_pos = 0;\n\t\t\tbreak;\n\t\tcase K_END:\n\t\t\tif (keydown[K_CTRL])\n\t\t\t\tnewserver_pos = 4;\n\t\t\tbreak;\n\t\tcase K_UPARROW:\n\t\tcase K_MWHEELUP:\n\t\t\tnewserver_pos--;\n\t\t\tbreak;\n\t\tcase K_TAB:\n\t\t\tkeydown[K_SHIFT] ? newserver_pos-- : newserver_pos++;\n\t\t\tbreak;\n\t\tcase K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n\t\t\tnewserver_pos++;\n\t\t\tbreak;\n\t\tcase K_ENTER:\n\t\tcase '+':\n\t\tcase '=':\n\t\tcase '-':\n\t\t\tswitch (newserver_pos)\n\t\t\t{\n\t\t\t\tcase 0:\n\t\t\t\tcase 1: // accept\n\t\t\t\t\tif (key == K_ENTER)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (strlen(edit1.text) > 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAddUnboundServer(edit1.text);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tadding_server = 0;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2: // cancel\n\t\t\t\t\tif (key == K_ENTER)\n\t\t\t\t\t\tadding_server = 0;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase K_ESCAPE:\n\t\t\tadding_server = 0;\n\t\t\tbreak;\n\t\tcase K_BACKSPACE:\n\t\t\tif (newserver_pos != 0)\n\t\t\t\tadding_server = 0;\n\t\t\tbreak;\n\t}\n\n\tif ((!keydown[K_CTRL] || tolower(key)=='v') && !keydown[K_ALT])\n\t{\n\t\tif (newserver_pos == 0)\n\t\t\tCEditBox_Key(&edit1, key, unichar);\n\t}\n\n\t// Make sure value stays within limits and enable field wrapping 0..2->0\n\tnewserver_pos = (newserver_pos + 3) % 3;\n}\n\nqbool SearchNextServer (int pos)\n{\n\tint i;\n\tchar tmp[1024];\n\n\tfor (i = pos; i < serversn_passed; i++) {\n\t\tstrlcpy (tmp, servers[i]->display.name, sizeof (tmp));\n\t\tFunToSort (tmp);\n\n\t\tif (strstr (tmp, searchstring)) {\n\t\t\tServers_pos = i;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nstatic void SB_Servers_Toggle_Column_Show(int colidx)\n{\n\tif (colidx > 0 && colidx < sb_columns_size) {\n\t\tCvar_Toggle(sb_columns[colidx].showvar);\n\t}\n}\n\nstatic void SB_Servers_Toggle_Column_Sort(char key)\n{\n\tchar buf[32];\n\tif ((sb_sortservers.string[0] == '-' && sb_sortservers.string[1] == key)\n\t\t\t|| sb_sortservers.string[0] == key)\n\t{\n\t\tif (sb_sortservers.string[0] == '-')\n\t\t{\n\t\t\tstrlcpy (buf, sb_sortservers.string + 1, sizeof (buf));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tbuf[0] = '-';\n\t\t\tstrlcpy (buf + 1, sb_sortservers.string, sizeof (buf) - 1);\n\t\t}\n\t} else {\n\t\tbuf[0] = key;\n\t\tstrlcpy (buf + 1, sb_sortservers.string, sizeof (buf) - 1);\n\t}\n\n\tCvar_Set(&sb_sortservers, buf);\n\tresort_servers = 1;\n}\n\nint SB_Servers_Key(int key)\n{\n\tif (serversn_passed <= 0 && key == K_BACKSPACE) {\n\t\tsearchstring[0] = '\\0';\n\t\tsearchtype = search_none;\n\t\tresort_servers = 1;\n\t}\n\n\tif (serversn_passed <= 0  &&  (key != K_SPACE || keydown[K_ALT])\n\t\t\t&& tolower(key) != 'n'  && key != K_INS)\n\t\treturn false;\n\n\tif (key == K_SPACE) {\n\t\tGetServerPingsAndInfos(keydown[K_CTRL]);\n\t\treturn true;\n\t}\n\n\tif (!keydown[K_CTRL] && !keydown[K_ALT] && key > ' ' && key <= '}')  // search\n\t{\n\t\tint len;\n\t\tchar c = tolower(key);\n\t\tif (searchtype != search_server) {\n\t\t\tsearchtype = search_server;\n\t\t\tsearchstring[0] = 0;\n\t\t}\n\t\tsearchtime = cls.realtime;\n\n\t\tlen = strlen(searchstring);\n\t\tif (len < MAX_SEARCH) {\n\t\t\tsearchstring[len] = c;\n\t\t\tsearchstring[len+1] = 0;\n\t\t\tresort_servers = 1;\n\t\t}\n\n\t\treturn true;\n\t} else {\n\t\tsearchtype = search_none;\n\t\tswitch (key)\n\t\t{\n\t\t\tcase K_BACKSPACE:\n\t\t\t\tsearchstring[0] = '\\0';\n\t\t\t\tresort_servers = 1;\n\t\t\t\tbreak;\n\t\t\tcase K_INS:\n\t\t\tcase 'n':\t// new server\n\t\t\t\tnewserver_pos = 0;\n\t\t\t\tCEditBox_Init(&edit1, 14, 64);\n\t\t\t\tadding_server = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'j':\n\t\t\tcase 'p':\n\t\t\t\tJoin_Server(servers[Servers_pos]);\n\t\t\t\tbreak;\n\t\t\tcase 'o':\n\t\t\tcase 's':\n\t\t\t\tObserve_Server(servers[Servers_pos]);\n\t\t\t\tbreak;\n\t\t\tcase 'r':\n\t\t\t\tGetServerInfo(servers[Servers_pos]);\n\t\t\t\tbreak;\n\t\t\tcase K_UPARROW:\n\t\t\t\tServers_pos--;\n\t\t\t\tbreak;\n\t\t\tcase K_MWHEELUP:\n\t\t\t\tServers_disp -= MWHEEL_SCROLL_STEP;\n\t\t\t\tServers_pos -= MWHEEL_SCROLL_STEP;\n\t\t\t\tbreak;\n\n\t\t\tcase K_DOWNARROW:\n\t\t\t\tServers_pos++;\n\t\t\t\tbreak;\n\t\t\tcase K_MWHEELDOWN:\n\t\t\t\tServers_disp += MWHEEL_SCROLL_STEP;\n\t\t\t\tServers_pos += MWHEEL_SCROLL_STEP;\n\t\t\t\tbreak;\n\n\t\t\tcase K_HOME:\n\t\t\t\tServers_pos = 0;\n\t\t\t\tbreak;\n\t\t\tcase K_END:\n\t\t\t\tServers_pos = serversn_passed-1;\n\t\t\t\tbreak;\n\t\t\tcase K_PGUP:\n\t\t\t\tServers_pos -= 10;\n\t\t\t\tbreak;\n\t\t\tcase K_PGDN:\n\t\t\t\tServers_pos += 10;\n\t\t\t\tbreak;\n\t\t\tcase K_MOUSE1:\n\t\t\tcase K_ENTER:\n\t\t\t\tServerinfo_Start(servers[Servers_pos]);\n\t\t\t\tbreak;\n\t\t\tcase K_SPACE:\n\t\t\t\tGetServerPingsAndInfos(keydown[K_CTRL]);\n\t\t\t\tbreak;\n\t\t\tcase '1':\n\t\t\tcase '2':\n\t\t\tcase '3':\n\t\t\tcase '4':\n\t\t\tcase '5':\n\t\t\tcase '6':\n\t\t\tcase '7':\n\t\t\tcase '8':   // sorting mode\n\t\t\t\tif (keydown[K_ALT]) // fixme\n\t\t\t\t{\n\t\t\t\t\tSB_Servers_Toggle_Column_Sort(key);\n\t\t\t\t} else if (keydown[K_CTRL]) {\n\t\t\t\t\tSB_Servers_Toggle_Column_Show(key - '1');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'c':\t// copy server to clipboard\n\t\t\t\tCopyServerToClipboard(servers[Servers_pos]);\n\t\t\t\tbreak;\n\t\t\tcase 'v':\t// past server into console\n\t\t\t\tPasteServerToConsole(servers[Servers_pos]);\n\t\t\t\tbreak;\n\t\t\tdefault: return false;\n\t\t}\n\t}\n\n\tServers_pos = max(Servers_pos, 0);\n\tServers_pos = min(Servers_pos, serversn-1);\n\treturn true;\n}\n\nvoid Serverinfo_Key(int key)\n{\n\tswitch (key)\n\t{\n\t\tcase K_MOUSE1:\n\t\tcase 'x': // x was once used for best route connection\n\t\tcase K_ENTER:\n\t\t\tif (serverinfo_pos != 2)\n\t\t\t{\n\t\t\t\tif (show_serverinfo->qwfwd) {\n\t\t\t\t\tSB_Select_QWfwd(show_serverinfo);\n\t\t\t\t} else if (keydown[K_CTRL]) {\n\t\t\t\t\tObserve_Server(show_serverinfo);\n\t\t\t\t} else {\n\t\t\t\t\tJoin_Server(show_serverinfo);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t\tServerinfo_Sources_Key(key);\n\t\t\tbreak;\n\t\tcase K_MOUSE2:\n\t\tcase K_ESCAPE:\n\t\tcase K_BACKSPACE:\n\t\t\tServerinfo_Stop();\n\t\t\tbreak;\n\t\tcase K_TAB:\n\t\t\tif (keydown[K_SHIFT]) {\n\t\t\t\tserverinfo_pos--;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tserverinfo_pos++;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase K_LEFTARROW:\n\t\t\tserverinfo_pos--;\n\t\t\tbreak;\n\t\tcase K_RIGHTARROW:\n\t\t\tserverinfo_pos++;\n\t\t\tbreak;\n\t\tcase 'q':\n\t\t\tCbuf_AddText(\"observeqtv \");\n\t\t\tCbuf_AddText(NET_AdrToString(show_serverinfo->address));\n\t\t\tCbuf_AddText(\"\\n\");\n\t\t\tbreak;\n\t\tcase 'j':\n\t\tcase 'p':\n\t\t\tJoin_Server(show_serverinfo);\n\t\t\tbreak;\n\t\tcase 'n':\n\t\t\tJoin_Server_Direct(show_serverinfo);\n\t\t\tbreak;\n\t\tcase 'o':\n\t\tcase 's':\n\t\t\tObserve_Server(show_serverinfo);\n\t\t\tbreak;\n\t\tcase 't':\n\t\t\t{\n\t\t\t\tif (!testing_connection)\n\t\t\t\t{\n\t\t\t\t\tchar buf[256];\n\t\t\t\t\tsnprintf(buf, sizeof (buf), \"%d.%d.%d.%d\",\n\t\t\t\t\t\t\tshow_serverinfo->address.ip[0],\n\t\t\t\t\t\t\tshow_serverinfo->address.ip[1],\n\t\t\t\t\t\t\tshow_serverinfo->address.ip[2],\n\t\t\t\t\t\t\tshow_serverinfo->address.ip[3]);\n\t\t\t\t\tSB_Test_Init(buf);\n\t\t\t\t\ttesting_connection = 1;\n\t\t\t\t} else {\n\t\t\t\t\ttesting_connection = 0;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\tcase 'c':   // copy server to clipboard\n\t\t\tCopyServerToClipboard(show_serverinfo);\n\t\t\tbreak;\n\t\tcase 'v':   // past server into console\n\t\t\tPasteServerToConsole(show_serverinfo);\n\t\t\tbreak;\n\t\tcase 'i':\n\t\t\tSB_PingTree_DumpPath(&show_serverinfo->address);\n\t\t\tbreak;\n\t\tcase K_MWHEELUP:\n\t        case K_UPARROW:\n\t        case K_PGUP:\n\t          if (!keydown[K_CTRL]) {\n\t\t    Servers_pos--;\n\t\t    Servers_pos = max(0, Servers_pos);\n\t\t    Serverinfo_Change(servers[Servers_pos]);\n\t\t    break;\n\t          }\n\t        case K_MWHEELDOWN:\n\t        case K_DOWNARROW:\n\t        case K_PGDN:\n\t          if (!keydown[K_CTRL]) {\n\t\t    Servers_pos++;\n\t\t    Servers_pos = min(serversn_passed - 1, Servers_pos);\n\t\t    Serverinfo_Change(servers[Servers_pos]);\n\t          }\n\t\tdefault:\n\t\t\tswitch (serverinfo_pos)\n\t\t\t{\n\t\t\t\tcase 0: // players list\n\t\t\t\t\tServerinfo_Players_Key(key);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 1: // serverinfo / rules\n\t\t\t\t\tServerinfo_Rules_Key(key);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2: // sources\n\t\t\t\t\tServerinfo_Sources_Key(key);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\t;\n\t\t\t}\n\t}\n\n\tserverinfo_pos = (serverinfo_pos + 3) % 3;\n}\n\nvoid Serverinfo_Players_Key(int key)\n{\n\tswitch (key)\n\t{\n\t\tcase K_UPARROW:\n\t\tcase K_MWHEELUP:\n\t\t\tserverinfo_players_pos--;\n\t\t\tbreak;\n\t\tcase K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n\t\t\tserverinfo_players_pos++;\n\t\t\tbreak;\n\t\tcase K_HOME:\n\t\t\tserverinfo_players_pos = 0;\n\t\t\tbreak;\n\t\tcase K_END:\n\t\t\tserverinfo_players_pos = 999;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t;\n\t}\n}\n\nvoid Serverinfo_Rules_Key(int key)\n{\n\tswitch (key)\n\t{\n\t\tcase K_UPARROW:\n\t\tcase K_MWHEELUP:\n\t\t\tserverinfo_rules_pos--;\n\t\t\tbreak;\n\t\tcase K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n\t\t\tserverinfo_rules_pos++;\n\t\t\tbreak;\n\t\tcase K_HOME:\n\t\t\tserverinfo_rules_pos = 0;\n\t\t\tbreak;\n\t\tcase K_END:\n\t\t\tserverinfo_rules_pos = 999;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t;\n\t}\n}\n\nvoid Serverinfo_Sources_Key(int key)\n{\n\tswitch (key)\n\t{\n\t\tcase K_UPARROW:\n\t\tcase K_MWHEELUP:\n\t\t\tserverinfo_sources_pos--;\n\t\t\tbreak;\n\t\tcase K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n\t\t\tserverinfo_sources_pos++;\n\t\t\tbreak;\n\t\tcase K_HOME:\n\t\t\tserverinfo_sources_pos = 0;\n\t\t\tbreak;\n\t\tcase K_END:\n\t\t\tserverinfo_sources_pos = 999;\n\t\t\tbreak;\n\t\tcase K_INS:\n\t\t\tif (!IsInSource(sources[serverinfo_sources_pos], show_serverinfo))\n\t\t\t\tAddToFileSource(sources[serverinfo_sources_pos], show_serverinfo);\n\t\t\tbreak;\n\t\tcase K_DEL:\n\t\t\tif (IsInSource(sources[serverinfo_sources_pos], show_serverinfo))\n\t\t\t\tRemoveFromFileSource(sources[serverinfo_sources_pos], show_serverinfo);\n\t\t\tbreak;\n\t\tcase K_ENTER:\n\t\t\tif (IsInSource(sources[serverinfo_sources_pos], show_serverinfo))\n\t\t\t\tRemoveFromFileSource(sources[serverinfo_sources_pos], show_serverinfo);\n\t\t\telse\n\t\t\t\tAddToFileSource(sources[serverinfo_sources_pos], show_serverinfo);\n\t\tdefault:\n\t\t\t;\n\t}\n\n\tserverinfo_sources_pos = max(serverinfo_sources_pos, 0);\n\tserverinfo_sources_pos = min(serverinfo_sources_pos, sourcesn_updated-1);\n}\n\nvoid SB_RemoveSourceProc(void)\n{\n\tSB_Source_Remove(Sources_pos);\n\n\tif (Sources_pos >= sourcesn) {\n\t\tSources_pos = sourcesn - 1;\n\t}\n}\n\nint SB_Sources_Key(int key)\n{\n\tint i;\n\n\tif (sourcesn <= 0)\n\t\treturn false;\n\n\tswitch (key)\n\t{\n\t\tcase K_INS:\n\t\tcase 'n':       // new source\n\t\t\tnewsource_pos = 0;\n\t\t\tnewsource_type = type_file;\n\t\t\tCEditBox_Init(&edit1, 16, 25);\n\t\t\tCEditBox_Init(&edit2, 16, 100);\n\t\t\tadding_source = 1;\n\t\t\tbreak;\n\t\tcase K_DEL:\n\t\tcase 'd':       // remove source\n\t\t\tif (sources[Sources_pos]->type != type_dummy) {\n\t\t\t\tchar tmp[64];\n\t\t\t\tsnprintf(&tmp[0], sizeof(tmp), \"Remove %-.20s\", sources[Sources_pos]->name);\n\t\t\t\tSB_Confirmation(tmp, SB_RemoveSourceProc);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'u':\n\t\t\tUpdate_Source(sources[Sources_pos]); \n\t\t\tbreak;\n\t\tcase K_UPARROW:\n\t\tcase K_MWHEELUP:\n\t\t\tSources_pos--;\n\t\t\tbreak;\n\t\tcase K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n\t\t\tSources_pos++;\n\t\t\tbreak;\n\t\tcase K_HOME:\n\t\t\tSources_pos = 0;\n\t\t\tbreak;\n\t\tcase K_END:\n\t\t\tSources_pos = sourcesn-1;\n\t\t\tbreak;\n\t\tcase K_PGUP:\n\t\t\tSources_pos -= 10;\n\t\t\tbreak;\n\t\tcase K_PGDN:\n\t\t\tSources_pos += 10;\n\t\t\tbreak;\n\t\tcase K_MOUSE1:\n\t\tcase K_ENTER:\n\t\t\tToggle_Source(sources[Sources_pos++]);\n\t\t\tbreak;\n\t\tcase ']':\n\t\t\tToggle_Source(sources[Sources_pos]);\n\t\t\tbreak;\n\t\tcase K_SPACE:\n\t\t\tSB_Sources_Update_Begin(keydown[K_CTRL]);\n\t\t\tbreak;\n\t\tcase '=':\n\t\tcase '+':   // select all sources\n\t\t\tfor (i=0; i < sourcesn; i++)\n\t\t\t\tMark_Source(sources[i]);\n\t\t\tbreak;\n\t\tcase '-':   // select none\n\t\t\tfor (i=0; i < sourcesn; i++)\n\t\t\t\tUnmark_Source(sources[i]);\n\t\t\tbreak;\n\t\tcase '*':   // invert selection\n\t\t\tfor (i=0; i < sourcesn; i++)\n\t\t\t\tToggle_Source(sources[i]);\n\t\t\tbreak;\n\t\tcase '1':\n\t\tcase '2':\n\t\tcase '3':   // sorting mode\n\t\t\tif ((sb_sortsources.string[0] == '-' && sb_sortsources.string[1] == key)\n\t\t\t\t\t|| sb_sortsources.string[0] == key)\n\t\t\t{\n\t\t\t\tchar buf[32];\n\t\t\t\tif (sb_sortsources.string[0] == '-')\n\t\t\t\t\tstrlcpy(buf, sb_sortsources.string+1, sizeof (buf));\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tbuf[0] = '-';\n\t\t\t\t\tstrlcpy(buf+1, sb_sortsources.string, sizeof (buf)-1);\n\t\t\t\t}\n\t\t\t\tCvar_Set(&sb_sortsources, buf);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tchar buf[32];\n\t\t\t\tbuf[0] = key;\n\t\t\t\tstrlcpy(buf+1, sb_sortsources.string, sizeof (buf)-1);\n\t\t\t\tCvar_Set(&sb_sortsources, buf);\n\t\t\t}\n\t\t\tresort_sources = 1;\n\t\t\tbreak;\n\t\tdefault: return false;\n\t}\n\n\tSources_pos = max(Sources_pos, 0);\n\tSources_pos = min(Sources_pos, sourcesn-1);\n\treturn true;\n}\n\nqbool SearchNextPlayer(int pos)\n{\n\tint i;\n\tchar tmp[1024];\n\n\tfor (i = pos; i < all_players_n; i++) {\n\t\tstrlcpy (tmp, all_players[i]->name, sizeof (tmp));\n\t\tFunToSort (tmp);\n\n\t\tif (strstr (tmp, searchstring)) {\n\t\t\tPlayers_pos = i;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nint SB_Players_Key(int key)\n{\n\tint i;\n\n\tif (all_players_n <= 0  &&  key != K_SPACE)\n\t\treturn false;\n\n\tif ((keydown[K_ALT]  ||  searchtype == search_player)  &&\n\t\t\tkey >= ' '  &&  key <= '}')  // search\n\t{\n\t\tint len;\n\t\tchar c = tolower(key);\n\t\tif (searchtype != search_player)\n\t\t{\n\t\t\tsearchtype = search_player;\n\t\t\tsearchstring[0] = 0;\n\t\t}\n\t\tsearchtime = cls.realtime;\n\n\t\tlen = strlen(searchstring);\n\t\tif (len < MAX_SEARCH)\n\t\t{\n\t\t\tsearchstring[len] = c;\n\t\t\tsearchstring[len+1] = 0;\n\n\t\t\tif (!SearchNextPlayer(Players_pos))\n\t\t\t\tif (!SearchNextPlayer(0))\n\t\t\t\t\tstrlcpy (searchstring, \"not found\", sizeof (searchstring));  // not found\n\t\t}\n\t\treturn true;\n\t}\n\telse\n\t{\n\t\tsearchtype = search_none;\n\t\tswitch (key)\n\t\t{\n\t\t\tcase 'j':\n\t\t\tcase 'p':\n\t\t\t\tJoin_Server(all_players[Players_pos]->serv);\n\t\t\t\tbreak;\n\t\t\tcase 'o':\n\t\t\tcase 's':\n\t\t\t\tObserve_Server(all_players[Players_pos]->serv);\n\t\t\t\tbreak;\n\t\t\tcase K_SPACE:\n\t\t\t\tGetServerPingsAndInfos(keydown[K_CTRL]);\n\t\t\t\tbreak;\n\n\t\t\tcase K_INS: // go to servers -- locate\n\t\t\t\tServers_pos = 0;\n\t\t\t\tfor (i=0; i < serversn_passed; i++)\n\t\t\t\t{\n\t\t\t\t\tif (servers[i] == all_players[Players_pos]->serv)\n\t\t\t\t\t{\n\t\t\t\t\t\tServers_pos = i;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tCTab_SetCurrentId(&sb_tab, SBPG_SERVERS);\n\t\t\t\tbreak;\n\n\t\t\tcase K_UPARROW:\n\t\t\tcase K_MWHEELUP:\n\t\t\t\tPlayers_pos--;\n\t\t\t\tbreak;\n\t\t\tcase K_DOWNARROW:\n\t\t\tcase K_MWHEELDOWN:\n\t\t\t\tPlayers_pos++;\n\t\t\t\tbreak;\n\t\t\tcase K_HOME:\n\t\t\t\tPlayers_pos = 0;\n\t\t\t\tbreak;\n\t\t\tcase K_END:\n\t\t\t\tPlayers_pos = all_players_n-1;\n\t\t\t\tbreak;\n\t\t\tcase K_PGUP:\n\t\t\t\tPlayers_pos -= 10;\n\t\t\t\tbreak;\n\t\t\tcase K_PGDN:\n\t\t\t\tPlayers_pos += 10;\n\t\t\t\tbreak;\n\t\t\tcase K_MOUSE1:\n\t\t\tcase K_ENTER:\n\t\t\t\tServerinfo_Start(all_players[Players_pos]->serv);\n\t\t\t\tbreak;\n\t\t\tcase '1':\n\t\t\tcase '2':\n\t\t\tcase '3':\n\t\t\tcase '4':\n\t\t\tcase '5':\n\t\t\tcase '6':\n\t\t\tcase '7':\n\t\t\tcase '8':\n\t\t\tcase '9':   // sorting mode\n\t\t\t\tif ((sb_sortplayers.string[0] == '-' && sb_sortplayers.string[1] == key)\n\t\t\t\t\t\t|| sb_sortplayers.string[0] == key)\n\t\t\t\t{\n\t\t\t\t\tchar buf[32];\n\t\t\t\t\tif (sb_sortplayers.string[0] == '-')\n\t\t\t\t\t\tstrlcpy(buf, sb_sortplayers.string+1, sizeof (buf));\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tbuf[0] = '-';\n\t\t\t\t\t\tstrlcpy(buf+1, sb_sortplayers.string, sizeof (buf)-1);\n\t\t\t\t\t}\n\t\t\t\t\tCvar_Set(&sb_sortplayers, buf);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tchar buf[32];\n\t\t\t\t\tbuf[0] = key;\n\t\t\t\t\tstrlcpy(buf+1, sb_sortplayers.string, sizeof (buf)-1);\n\t\t\t\t\tCvar_Set(&sb_sortplayers, buf);\n\t\t\t\t}\n\t\t\t\tresort_all_players = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'c':\n\t\t\t\tCopyServerToClipboard(all_players[Players_pos]->serv);\n\t\t\t\tbreak;\n\t\t\tcase 'v':   // past server into console\n\t\t\t\tPasteServerToConsole(all_players[Players_pos]->serv);\n\t\t\t\tbreak;\n\t\t\tdefault: return false;\n\t\t}\n\t}\n\n\tPlayers_pos = max(Players_pos, 0);\n\tPlayers_pos = min(Players_pos, all_players_n-1);\n\treturn true;\n}\n\nqbool SB_Servers_Mouse_Event(const mouse_state_t *ms)\n{\n\tif (show_serverinfo) {\n\t}\n\telse if (ms->button_up == 1) {\n\t\tif (mouse_in_header_row) {\n\t\t\tSB_Servers_Toggle_Column_Sort('0' + mouse_hovered_column + 1);\n\t\t}\n\t\telse {\n\t\t\tSB_Servers_Key(K_MOUSE1);\n\t\t}\n\t}\n\telse {\n\t\tdouble mouse_row = ms->y / 8.0 - 1.0;\n\t\tif (mouse_row < 0.0) {\n\t\t\tmouse_in_header_row = true;\n\t\t\tmouse_header_pos_y = ms->x;\n\t\t}\n\t\telse {\n\t\t\tmouse_in_header_row = false;\n\t\t\tServers_pos = Servers_disp + (int) mouse_row;\n\t\t\tServers_pos = bound(0, Servers_pos, serversn - 1);\n\t\t}\n\t}\n\treturn true;\n}\n\nqbool SB_Sources_Mouse_Event(const mouse_state_t *ms)\n{\n\tif (show_serverinfo) return false;\n\n\tif (ms->button_up == 1)\n\t{\n\t\tSB_Sources_Key(K_MOUSE1);\n\t\treturn true;\n\t}\n\tSources_pos = Sources_disp + ms->y / 8 - 1;\n\tSources_pos = bound(0, Sources_pos, sourcesn - 1);\n\treturn true;\n}\n\nqbool SB_Players_Mouse_Event(const mouse_state_t *ms)\n{\n\tif (show_serverinfo) return false;\n\n\tPlayers_pos = Players_disp + ms->y / 8 - 1;\n\tPlayers_pos = bound(0, Players_pos, all_players_n - 1);\n\treturn true;\n}\n\nvoid SB_Specials_Draw(void)\n{\n\tif (show_serverinfo) Serverinfo_Draw();\n\tif (ping_phase) PingPhase_Draw();\n\tif (updating_sources) UpdatingSources_Draw();\n\tif (confirmation) SB_Confirmation_Draw();\n}\n\nqbool SB_Specials_Key(int key, wchar unichar)\n{\n\tif (confirmation)\n\t{\n\t\tSB_Confirmation_Key(key);\n\t\treturn true;\n\t}\n\n\tif ((ping_phase || updating_sources) && (key == '`' || key == '~' || key == 'm')) {\n\t\tM_LeaveMenus();\n\t\t// Con_ToggleConsole_f ();\n\t\treturn true;\n\t}\n\n\tif ((key == K_ESCAPE || key == K_MOUSE2)  &&\n\t\t\tshow_serverinfo == NULL  &&\n\t\t\t!adding_source  &&\n\t\t\t!adding_server  &&\n\t\t\tping_phase == 0  &&\n\t\t\t!updating_sources)  // exit from browser to main menu\n\t{\n\t\tM_Menu_Main_f();\n\t\treturn true;\n\t}\n\n\tif (show_serverinfo)\n\t{\n\t\tServerinfo_Key(key);\n\t\treturn true;\n\t}\n\n\tif (adding_source)\n\t{\n\t\tAdd_Source_Key(key, unichar);\n\t\treturn true;\n\t}\n\n\tif (adding_server)\n\t{\n\t\tAdd_Server_Key(key, unichar);\n\t\treturn true;\n\t}\n\n\tif (ping_phase || updating_sources)  // no keys when pinging\n\t{\n\t\tif (!abort_ping  &&  (key == K_ESCAPE || key == K_BACKSPACE))\n\t\t\tabort_ping = 1;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n//\n// sorting routines\n//\n\nstatic int Servers_Compare_Ping_Func(const server_data *s1, const server_data *s2)\n{\n\tint p1 = (sb_findroutes.integer && s1->bestping > 0) ? s1->bestping : s1->ping;\n\tint p2 = (sb_findroutes.integer && s2->bestping > 0) ? s2->bestping : s2->ping;\n\tint diff = p1 - p2;\n\n\treturn diff;\n}\n\nstatic int Servers_Occupancy_Sort_Rating(server_occupancy oc) {\n\tif (oc == SERVER_EMPTY) {\n\t\treturn 0;\n\t}\n\telse if (oc == SERVER_FULL) {\n\t\treturn 1;\n\t}\n\telse if (oc == SERVER_NONEMPTY) {\n\t\treturn 2;\n\t}\n\telse {\n\t\tCom_DPrintf(\"Illegal server occupancy %d\\n\", oc);\n\t\treturn -1;\n\t}\n}\n\nstatic int Servers_Compare_Natural(const server_data *s1, const server_data *s2) {\n\tif (s1->occupancy != s2->occupancy) {\n\t\tint o1 = Servers_Occupancy_Sort_Rating(s1->occupancy);\n\t\tint o2 = Servers_Occupancy_Sort_Rating(s2->occupancy);\n\t\treturn o2 - o1;\n\t}\n\telse {\n\t\tserver_occupancy oc = s1->occupancy;\n\t\tswitch (oc) {\n\t\t\tcase SERVER_NONEMPTY:\n\t\t\t\treturn Servers_Compare_Ping_Func(s1, s2);\n\t\t\tcase SERVER_FULL:\n\t\t\t\treturn s2->playersn - s1->playersn;\n\t\t\tcase SERVER_EMPTY:\n\t\t\t\treturn Servers_Compare_Ping_Func(s1, s2);\n\t\t\tdefault:\n\t\t\t\treturn 0;\n\t\t}\n\t}\n}\n\nint Servers_Compare_Func(const void * p_s1, const void * p_s2)\n{\n\tint reverse = 0;\n\tchar *sort_string = sb_sortservers.string;\n\tconst server_data *s1 = *((server_data **)p_s1);\n\tconst server_data *s2 = *((server_data **)p_s2);\n\n\tif (s1->ping < 0  &&  s2->ping >= 0)\n\t\treturn 1;\n\tif (s2->ping < 0  &&  s1->ping >= 0)\n\t\treturn -1;\n\n\tif (!s1->passed_filters  &&  s2->passed_filters)\n\t\treturn 1;\n\tif (!s2->passed_filters  &&  s1->passed_filters)\n\t\treturn -1;\n\tif (!s1->passed_filters  &&  !s2->passed_filters)\n\t\treturn s1 - s2;\n\n\tif (sort_string[0] == '\\0') {\n\t\treturn Servers_Compare_Natural(s1, s2);\n\t}\n\n\twhile (true)\n\t{\n\t\tint d;  // difference\n\n\t\tif (*sort_string == '-')\n\t\t{\n\t\t\treverse = 1;\n\t\t\tsort_string++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch (*sort_string++)\n\t\t{\n\t\t\tcase '1':\n\t\t\t\td = funcmp(s1->display.name, s2->display.name);\n\t\t\t\tbreak;\n\t\t\tcase '2':\n\t\t\t\td = memcmp(&(s1->address.ip), &(s2->address.ip), 4);\n\t\t\t\tif (!d)\n\t\t\t\t\td = ntohs(s1->address.port) - ntohs(s2->address.port);\n\t\t\t\tbreak;\n\t\t\tcase '3':\n\t\t\t\td = Servers_Compare_Ping_Func(s1, s2);\n\t\t\t\tbreak;\n\t\t\tcase '4':\n\t\t\t\td = Q_strcmp2(s1->display.gamedir, s2->display.gamedir);\n\t\t\t\tbreak;\n\t\t\tcase '5':\n\t\t\t\td = Q_strcmp2(s1->display.map, s2->display.map);\n\t\t\t\tbreak;\n\t\t\tcase '6':\n\t\t\t\td = s1->playersn - s2->playersn;\n\t\t\t\tbreak;\n\t\t\tcase '7':\n\t\t\t\td = Q_strcmp2(s1->display.fraglimit, s2->display.fraglimit);\n\t\t\t\tbreak;\n\t\t\tcase '8':\n\t\t\t\td = Q_strcmp2(s1->display.timelimit, s2->display.timelimit);\n\t\t\t\t break;\n\t\t\tdefault:\n\t\t\t\td = s1 - s2;\n\t\t}\n\n\t\tif (d)\n\t\t\treturn reverse ? -d : d;\n\t}\n}\n\nvoid Filter_Servers(void)\n{\n\tint i, info_filter_count;\n\tinfo_filter_t* info_filters = SB_InfoFilter_Parse(&info_filter_count);\n\n\tserversn_passed = 0;\n\tfor (i=0; i < serversn; i++)\n\t{\n\t\tchar *tmp;\n\t\tserver_data *s = servers[i];\n\t\ts->passed_filters = 0;\n\n\t\tif (searchstring[0] && !strstri(s->display.name, searchstring)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (sb_showproxies.integer == 0 && (s->qwfwd || s->qizmo))\n\t\t\tcontinue; // hide\n\n\t\tif (sb_showproxies.integer == 2 && !(s->qwfwd || s->qizmo))\n\t\t\tcontinue; // exclusive\n\n\t\tif (sb_hidedead.value  &&  s->ping < 0)\n\t\t\tcontinue;\n\n\t\tif (!s->qizmo && !s->qwfwd) {\n\t\t\tif (sb_hideempty.value  &&  s->playersn + s->spectatorsn <= 0)\n\t\t\t\tcontinue;\n\n\t\t\tif (sb_hidenotempty.value  &&  s->playersn + s->spectatorsn > 0)\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tif (sb_hidehighping.integer && s->ping > sb_pinglimit.integer)\n\t\t\tcontinue;\n\n\t\ttmp = ValueForKey(s, \"maxclients\");\n\t\tif (sb_hidefull.value  &&  s->playersn >= (tmp ? atoi(tmp) : 255))\n\t\t\tcontinue;\n\n\t\tif (!SB_InfoFilter_Exec(info_filters, info_filter_count, s)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ts->passed_filters = 1;  // passed\n\t\tserversn_passed++;\n\t}\n\n\tSB_InfoFilter_Free(info_filters, info_filter_count);\n\n\treturn;\n}\n\nvoid Sort_Servers (void)\n{\n\tSB_ServerList_Lock();\n\tFilter_Servers();\n\tqsort(servers, serversn, sizeof(servers[0]), Servers_Compare_Func);\n\tSB_ServerList_Unlock();\n}\n\n\n//\n// build all players list\n//\n\nint rebuild_all_players = 0;\nint resort_all_players = 0;\n\nint All_Players_Compare_Func(const void * p_p1, const void * p_p2)\n{\n\tint reverse = 0;\n\tchar *sort_string = sb_sortplayers.string;\n\tconst player_host *p1 = *((player_host **)p_p1);\n\tconst player_host *p2 = *((player_host **)p_p2);\n\tserver_data *s1 = p1->serv;\n\tserver_data *s2 = p2->serv;\n\n\tif (s1->ping < 0  &&  s2->ping >= 0)\n\t\treturn 1;\n\tif (s2->ping < 0  &&  s1->ping >= 0)\n\t\treturn -1;\n\n\twhile (true)\n\t{\n\t\tint d;  // difference\n\n\t\tif (*sort_string == '-')\n\t\t{\n\t\t\treverse = 1;\n\t\t\tsort_string++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch (*sort_string++)\n\t\t{\n\t\t\tcase '1':\n\t\t\t\td = funcmp(s1->display.name, s2->display.name);\n\t\t\t\tbreak;\n\t\t\tcase '2':\n\t\t\t\td = memcmp(&(s1->address.ip), &(s2->address.ip), 4);\n\t\t\t\tif (!d)\n\t\t\t\t\td = ntohs(s1->address.port) - ntohs(s2->address.port);\n\t\t\t\tbreak;\n\t\t\tcase '3':\n\t\t\t\td = s1->ping - s2->ping;\n\t\t\t\tbreak;\n\t\t\tcase '4':\n\t\t\t\td = Q_strcmp2(s1->display.gamedir, s2->display.gamedir);\n\t\t\t\tbreak;\n\t\t\tcase '5':\n\t\t\t\td = Q_strcmp2(s1->display.map, s2->display.map);\n\t\t\t\tbreak;\n\t\t\tcase '6':\n\t\t\t\td = s1->playersn - s2->playersn;\n\t\t\t\tbreak;\n\t\t\tcase '7':\n\t\t\t\td = Q_strcmp2(s1->display.fraglimit, s2->display.fraglimit);\n\t\t\t\tbreak;\n\t\t\tcase '8':\n\t\t\t\td = Q_strcmp2(s1->display.timelimit, s2->display.timelimit);\n\t\t\t\tbreak;\n\t\t\tcase '9':\n\t\t\t\td = funcmp(p1->name, p2->name);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\td = p1 - p2;\n\t\t}\n\n\t\treverse = 0;\n\n\t\tif (d)\n\t\t\treturn reverse ? -d : d;\n\t}\n}\n\nvoid Sort_All_Players(void)\n{\n\tqsort(all_players, all_players_n, sizeof(all_players[0]), All_Players_Compare_Func);\n}\n\nvoid Rebuild_All_Players(void)\n{\n\tint i, j, players = 0;\n\n\t// clear\n\tif (all_players != NULL)\n\t{\n\t\tfor (i = 0; i < all_players_n; i++)\n\t\t\tQ_free(all_players[i]);\n\n\t\tQ_free(all_players);\n\t}\n\n\t// count players\n\tfor (i = 0; i < serversn; i++)\n\t\tplayers += servers[i]->playersn + servers[i]->spectatorsn;\n\n\t// alloc memory\n\tall_players = (player_host **) Q_malloc (players * sizeof (player_host *));\n\n\t// make players\n\tall_players_n = 0;\n\tfor (i = 0; i < serversn; i++)\n\t{\n\t\tfor (j = 0; j < servers[i]->playersn + servers[i]->spectatorsn; j++)\n\t\t{\n\t\t\tall_players[all_players_n] = (player_host *) Q_malloc (sizeof (player_host));\n\t\t\tstrlcpy (all_players[all_players_n]->name,\n\t\t\t\t\tservers[i]->players[j]->name,\n\t\t\t\t\tsizeof (all_players[all_players_n]->name));\n\t\t\tall_players[all_players_n]->serv = servers[i];\n\t\t\tall_players_n++;\n\t\t}\n\t}\n\n\tresort_all_players = 1;\n\trebuild_all_players = 0;\n\t//Players_pos = 0;\n}\n\nvoid SB_Sources_Update_f(void)\n{\n\tSB_Sources_Update_Begin(true);\n}\n\n// Server list serialization.\n// When user set sb_listcache 1, the server list will be saved to his home dir after each update\n// so that next time the client is started the list is immediately full of servers\n// and it's not necessary to do full refresh, just get infos from alive servers is enough\n// (which is significantly faster than full refresh).\n// Of course users should do full-update of their list after some time so that \n// new servers have a chance to appear.\n#define SERIALIZE_FILE_VERSION 1003\nvoid SB_Serverlist_Serialize(FILE *f)\n{\n\tint version = SERIALIZE_FILE_VERSION;\n\tsize_t server_data_size = sizeof(server_data);\n\tint i;\n\n\t// header\n\t// - version\n\tfwrite(&version, sizeof(int), 1, f);\n\t// - server_data struct size\n\tfwrite(&server_data_size, sizeof(size_t), 1, f);\n\t// - number of servers\n\tfwrite(&serversn, sizeof(int), 1, f);\n\t// - number of filtered servers\n\tfwrite(&serversn_passed, sizeof(int), 1, f);\n\n\t// body\n\tfor (i = 0; i < serversn; i++) {\n\t\tserver_data t = *servers[i];\n\t\tt.keysn = 0; // we don't store the keys\n\t\tfwrite(&t, sizeof(server_data), 1, f);\n\t}\n}\n\nint SB_Serverlist_Unserialize(FILE *f)\n{\n\tsize_t server_data_size;\n\tint i;\n\tint version;\n\tint serversn_buffer, serversn_passed_buffer;\n\n\tif (fread(&version, sizeof(int), 1, f) != 1)\n\t\treturn -3;\n\tif (version != SERIALIZE_FILE_VERSION)\n\t\treturn -1;\n\n\tif (fread(&server_data_size, sizeof(size_t), 1, f) != 1)\n\t\treturn -3;\n\tif (server_data_size != sizeof(server_data))\n\t\treturn -1;\n\n\tif (fread(&serversn_buffer, sizeof(int), 1, f) != 1)\n\t\treturn -3;\n\tif (serversn_buffer > MAX_SERVERS)\n\t\treturn -2;\n\n\tif (fread(&serversn_passed_buffer, sizeof(int), 1, f) != 1)\n\t\treturn -3;\n\tif (serversn_passed_buffer > MAX_SERVERS)\n\t\treturn -2;\n\n\tserversn = serversn_buffer;\n\tserversn_passed = serversn_passed_buffer;\n\n\tfor (i = 0; i < serversn; i++) {\n\t\tserver_data *s = (server_data *) Q_malloc(sizeof(server_data));\n\t\tint j;\n\n\t\tif (fread(s, sizeof(server_data), 1, f) != 1 ||\n\t\t\t\ts->playersn < 0 || s->playersn > MAX_CLIENTS || s->spectatorsn < 0 || s->spectatorsn > MAX_CLIENTS) {\n\t\t\t// naive check for corrupted data\n\t\t\tserversn = 0;\n\t\t\tserversn_passed = 0;\n\t\t\tQ_free(s);\n\t\t\tfor (j = 0; j < i; j++) {\n\t\t\t\tQ_free(servers[j]);\n\t\t\t}\n\t\t\treturn -3;\n\t\t}\n\n\t\tservers[i] = s;\n\n\t\t// Create empty entries because\n\t\t// serialized playersn and spectatorsn is not zero, that's needed\n\t\t// to not get empty list on \"hide empty\". Empty list leads to full refresh\n\t\t// which makes the un/serialization stuff useless.\n\t\tfor (j = 0; j < s->playersn + s->spectatorsn; j++) {\n\t\t\tservers[i]->players[j] = Q_calloc(1, sizeof(playerinfo));\n\t\t}\n\t}\n\n\trebuild_servers_list = 0;\n\n\treturn serversn;\n}\n\nvoid SB_Serverlist_Serialize_f(void)\n{\n\tFILE *f;\n\tchar filename[MAX_OSPATH] = {0};\n\n\tsnprintf(&filename[0], sizeof(filename), \"%s/%s\", com_homedir, \"servers_data\");\n\n\tif (!(f\t= fopen\t(filename, \"wb\"))) {\n\t\tFS_CreatePath(filename);\n\t\tif (!(f\t= fopen\t(filename, \"wb\"))) {\n\t\t\tCom_Printf (\"Couldn't write\t%s.\\n\",\tfilename);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tSB_Serverlist_Serialize(f);\n\tCom_Printf(\"Wrote server list contents to disk\\n\");\n\tfclose(f);\n\tfilesystemchanged = true;\n}\n\nvoid SB_Serverlist_Unserialize_f(void)\n{\n\tFILE *f;\n\tint err;\n\tchar filename[MAX_OSPATH] = {0};\n\n\tsnprintf(&filename[0], sizeof(filename), \"%s/%s\", com_homedir, \"servers_data\");\n\n\tif (!(f\t= fopen\t(filename, \"rb\"))) {\n\t\tCom_Printf (\"Couldn't read %s.\\n\", filename);\n\t\treturn;\n\t}\n\n\terr = SB_Serverlist_Unserialize(f);\n\tif (err > 0) {\n\t\tCom_Printf(\"Successfully read %d servers\\n\", err);\n\t}\n\telse if (err == -1) {\n\t\tCom_Printf(\"Format didn't match\\n\");\n\t}\n\telse if (err == -2) {\n\t\tCom_Printf(\"Format error (servers number too big)\\n\");\n\t}\n\telse if (err == -3) {\n\t\tCom_Printf(\"Corrupted data\\n\");\n\t}\n\telse { // err == 0\n\t\tCom_Printf(\"No servers read\\n\");\n\t}\n\n\tfclose(f);\n}\n\nvoid SB_ProxyDumpPing(netadr_t adr, short dist)\n{\n\tCom_Printf(\"%3d.%3d.%3d.%3d:%5d   %d\\n\",\n\t\t\tadr.ip[0], adr.ip[1], adr.ip[2], adr.ip[3], (int) adr.port, dist);\n}\n\nvoid SB_ProxyGetPings_f(void)\n{\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"Usage: %s <ip>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\telse {\n\t\tnetadr_t adr;\n\n\t\tif (!NET_StringToAdr(Cmd_Argv(1), &adr)) {\n\t\t\tCom_Printf(\"Invalid address\\n\");\n\t\t\treturn;\n\t\t}\n\t\tCom_Printf(\"List:\\n\");\n\t\tSB_Proxy_QueryForPingList(&adr, SB_ProxyDumpPing);\n\t\tCom_Printf(\"End of list.\\n\");\n\t}\n}\n\nvoid SB_Shutdown(void)\n{\n\tint i;\n\n\tSB_PingTree_Shutdown();\n\n\t// FIXME - this probably never worked\n\t// Serverinfo_Stop();\n\t// Sys_MSleep(150);     // wait for thread to terminate\n\n\tfor (i = 0; i < sourcesn; ++i) {\n\t\tReset_Source(sources[i]);\n\t\tQ_free(sources[i]);\n\t}\n\n\t// delete servers[0...serversn) ?\n}\n\nvoid Browser_Init (void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SERVER_BROWSER);\n\tCvar_Register(&sb_status);\n\tCvar_Register(&sb_showping);\n\tCvar_Register(&sb_showaddress);\n\tCvar_Register(&sb_showmap);\n\tCvar_Register(&sb_showgamedir);\n\tCvar_Register(&sb_showplayers);\n\tCvar_Register(&sb_showfraglimit);\n\tCvar_Register(&sb_showtimelimit);\n\tCvar_Register(&sb_pingtimeout);\n\tCvar_Register(&sb_infotimeout);\n\tCvar_Register(&sb_pings);\n\tCvar_Register(&sb_pingspersec);\n\tCvar_Register(&sb_inforetries);\n\tCvar_Register(&sb_infospersec);\n\tCvar_Register(&sb_proxinfopersec);\n\tCvar_Register(&sb_proxretries);\n\tCvar_Register(&sb_proxtimeout);\n\tCvar_Register(&sb_liveupdate);\n\tCvar_Register(&sb_mastertimeout);\n\tCvar_Register(&sb_masterretries);\n\tCvar_Register(&sb_nosockraw);\n\tCvar_Register(&sb_sortservers);\n\tCvar_Register(&sb_sortplayers);\n\tCvar_Register(&sb_sortsources);\n\tCvar_Register(&sb_autohide);\n\tCvar_Register(&sb_hideempty);\n\tCvar_Register(&sb_hidenotempty);\n\tCvar_Register(&sb_hidefull);\n\tCvar_Register(&sb_hidedead);\n\tCvar_Register(&sb_hidehighping);\n\tCvar_Register(&sb_pinglimit);\n\tCvar_Register(&sb_showproxies);\n\tCvar_Register(&sb_sourcevalidity);\n\tCvar_Register(&sb_mastercache);\n\tCvar_Register(&sb_autoupdate);\n\tCvar_Register(&sb_listcache);\n\tCvar_Register(&sb_findroutes);\n\tCvar_Register(&sb_ignore_proxy);\n\tCvar_Register(&sb_info_filter);\n\tCvar_ResetCurrentGroup();\n\n\tCmd_AddCommand(\"addserver\", AddServer_f);\n\tCmd_AddCommand(\"sb_refresh\", GetServerPingsAndInfos_f);\n\tCmd_AddCommand(\"sb_pingsdump\", SB_PingsDump_f);\n\tCmd_AddCommand(\"sb_sourceadd\", SB_Source_Add_f);\n\tCmd_AddCommand(\"sb_sourcesupdate\", SB_Sources_Update_f);\n\tCmd_AddCommand(\"sb_buildpingtree\", SB_PingTree_Build);\n\tCmd_AddCommand(\"sb_proxygetpings\", SB_ProxyGetPings_f);\n\n\tif (sb_listcache.integer) {\n\t\tSB_Serverlist_Unserialize_f();\n\t\tSB_Proxylist_Unserialize_f();\n\t}\n\n\tqtvlist_init();\n}\n\nvoid Browser_Init2 (void)\n{\n\tint i;\n\n\tServers_pos = 0;\n\tSources_pos = 0;\n\tServers_disp = 0;\n\tshow_serverinfo = NULL;\n\tserverinfo_pos = 0;\n\n\tfor (i=0; i < MAX_SERVERS; i++)\n\t\tservers[i] = NULL;\n\n\tserversn = serversn_passed = 0;\n\tsourcesn = 0;\n\n\tserverlist_mutex = SDL_CreateMutex();\n\tSB_PingTree_Init();\n\n\t// read sources from SOURCES_PATH\n\tReload_Sources();\n\tMarkDefaultSources();\n}\n\nvoid SB_ExecuteQueuedTriggers(void) {\n\tif (sb_queuedtriggers & SB_TRIGGER_REFRESHDONE) {\n\t\tTP_ExecTrigger(\"f_sbrefreshdone\");\n\t\tsb_queuedtriggers &= ~SB_TRIGGER_REFRESHDONE;\n\t}\n\n\tif (sb_queuedtriggers & SB_TRIGGER_SOURCESUPDATED) {\n\t\tTP_ExecTrigger(\"f_sbupdatesourcesdone\");\n\t\tsb_queuedtriggers &= ~SB_TRIGGER_SOURCESUPDATED;\n\t}\n\n\tif (sb_queuedtriggers & SB_TRIGGER_NOTIFY_PINGTREE) {\n\t\tCom_Printf(\"Ping tree has been created\\n\");\n\t\tsb_queuedtriggers &= ~SB_TRIGGER_NOTIFY_PINGTREE;\n\t}\n}\n\nstatic qbool SB_InfoFilter_Exec(info_filter_t* info_filters, int info_filter_count, server_data* s)\n{\n\tint j;\n\tqbool default_pass = true;\n\n\tfor (j = 0; j < info_filter_count; ++j) {\n\t\tinfo_filter_t* filter = &info_filters[j];\n\t\tconst char* value;\n\n\t\tif (!filter->exec) {\n\t\t\t// Invalid definition\n\t\t\tcontinue;\n\t\t}\n\n\t\t// if any filters were valid then block the remaining\n\t\tdefault_pass = !filter->pass;\n\n\t\t// shorthand for \"rest\"\n\t\tif (filter->name[0] == '*' && filter->name[1] == '\\0') {\n\t\t\treturn filter->pass;\n\t\t}\n\n\t\tvalue = ValueForKey(s, filter->name);\n\t\tif (!value) {\n\t\t\t// if server doesn't specify key then we move on to next (hmm)\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!filter->regex) {\n\t\t\t// Any value will do\n\t\t\tif (value != NULL && value[0]) {\n\t\t\t\treturn filter->pass;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t// Rule specified, check it matches the regex\n\t\t\tpcre2_match_data *match_data = pcre2_match_data_create_from_pattern(filter->regex, NULL);\n\t\t\tif (pcre2_match(filter->regex, (PCRE2_SPTR)value, strlen(value), 0, 0, match_data, NULL) >= 0) {\n\t\t\t\tpcre2_match_data_free (match_data);\n\t\t\t\treturn filter->pass;\n\t\t\t}\n\t\t\tpcre2_match_data_free (match_data);\n\t\t}\n\t}\n\n\t// run out of rules - default \n\treturn default_pass;\n}\n\nstatic info_filter_t* SB_InfoFilter_Parse(int* count)\n{\n\tint i;\n\ttokenizecontext_t info_filter_ctx;\n\tint info_filter_count;\n\tinfo_filter_t* info_filters;\n\tchar definition[MAX_MACRO_STRING];\n\n\tCmd_TokenizeStringEx(&info_filter_ctx, sb_info_filter.string);\n\t*count = info_filter_count = Cmd_ArgcEx(&info_filter_ctx);\n\n\tinfo_filters = Q_malloc(info_filter_count * sizeof(info_filter_t));\n\tfor (i = 0; i < info_filter_count; ++i) {\n\t\tchar* split;\n\t\tint error;\n\t\tPCRE2_SIZE error_offset;\n\t\tinfo_filter_t* filter = &info_filters[i];\n\n\t\t// filters must be +x=y or -x=y\n\t\tstrlcpy(definition, Cmd_ArgvEx(&info_filter_ctx, i), sizeof(definition));\n\t\tif (definition[0] != '-' && definition[0] != '+') {\n\t\t\tCon_Printf(\"Invalid info-filter: bad syntax (%s)\\n\", definition);\n\t\t\tcontinue;\n\t\t}\n\t\tfilter->pass = (definition[0] == '+');\n\n\t\t// Copy the serverinfo key name\n\t\tsplit = strchr(definition, '=');\n\t\tif (split != NULL) {\n\t\t\tsplit[0] = '\\0';\n\t\t}\n\t\tstrlcpy(filter->name, definition + 1, sizeof(filter->name));\n\t\tif (!filter->name[0]) {\n\t\t\t// empty key name is invalid...\n\t\t\tCon_Printf(\"Invalid info-filter: bad syntax (%s)\\n\", definition);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (split != NULL && split[1] && split[1] != '*') {\n\t\t\t// no regex is fine\n\t\t\tfilter->regex = pcre2_compile((PCRE2_SPTR)(split + 1), PCRE2_ZERO_TERMINATED, PCRE2_CASELESS, &error, &error_offset, NULL);\n\t\t\tif (filter->regex == NULL) {\n\t\t\t\tPCRE2_UCHAR error_str[256];\n\t\t\t\tpcre2_get_error_message(error, error_str, sizeof(error_str));\n\t\t\t\tCon_Printf(\"Invalid rule definition: %s\\n\", error_str);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tfilter->exec = true;\n\t}\n\n\treturn info_filters;\n}\n\nstatic void SB_InfoFilter_Free(info_filter_t* info_filters, int info_filter_count)\n{\n\tint i;\n\n\tfor (i = 0; i < info_filter_count; ++i) {\n\t\tif (info_filters[i].regex) {\n\t\t\tpcre2_code_free(info_filters[i].regex);\n\t\t}\n\t}\n\n\tQ_free(info_filters);\n}\n"
  },
  {
    "path": "src/EX_browser.h",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n#ifndef __EX_BROWSER__H__\n#define __EX_BROWSER__H__\n\n#include \"localtime.h\"\n#include \"keys.h\"\n\n#define MAX_UNBOUND 200\n\n#define MAX_SOURCES 200\n#define MAX_SERVERS 1000\n\n#define MAX_KEYS    100\n#define MAX_PLAYERS 128\n\n// column width\n#define COL_PING        3\n#define COL_IP          21\n#define COL_NAME        60  // max\n#define COL_MAP         8\n#define COL_GAMEDIR     8\n#define COL_PLAYERS     5\n#define COL_FRAGLIMIT   2\n#define COL_TIMELIMIT   2\n\n\nextern cvar_t  sb_status;           // show status-bar\n\nextern cvar_t  sb_showping;         // show ping column\nextern cvar_t  sb_showaddress;      // show address colums\nextern cvar_t  sb_showmap;          // show map column\nextern cvar_t  sb_showgamedir;      // show gamedir column\nextern cvar_t  sb_showplayers;      // show players column\nextern cvar_t  sb_showfraglimit;    // show fraglimit column\nextern cvar_t  sb_showtimelimit;    // show timelimit column\n\nextern cvar_t  sb_pingtimeout;      // ping server timeout\nextern cvar_t  sb_pings;            // number of pings\nextern cvar_t  sb_pingspersec;      // pings per second\n\nextern cvar_t  sb_infotimeout;      // get serverinfo timeout\nextern cvar_t  sb_inforetries;      // max serverinfo retries\nextern cvar_t  sb_infospersec;      // serverinfos per second\n\nextern cvar_t  sb_mastertimeout;    // master server server timeout\nextern cvar_t  sb_masterretries;    // max master-server retries\nextern cvar_t  sb_nosockraw;        // when enabled, forces \"new ping\" (udp qw query packet, multithreaded) to be used\n\nextern cvar_t  sb_liveupdate;       // serveinfo window update interval (0 = off)\n\nextern cvar_t  sb_sortplayers;      // sorting mode in players list\nextern cvar_t  sb_sortservers;      // sorting mode in servers list\n\nextern cvar_t  sb_autohide;         // hide browser after connect\n\nextern cvar_t  sb_findroutes;       // look for best available route automatically\nextern cvar_t  sb_ignore_proxy;     // list of proxies excluded from the best route lookup\n\nextern cvar_t  sb_sourcevalidity;   // source validity in minutes\nextern cvar_t  sb_mastercache;      // if cache master query results\n\nextern cvar_t  sb_hideempty;\nextern cvar_t  sb_hidenotempty;\nextern cvar_t  sb_hidefull;\nextern cvar_t  sb_hidedead;\nextern cvar_t  sb_hidehighping;\nextern cvar_t  sb_pinglimit;\nextern cvar_t  sb_showproxies;\n\ntypedef struct column_s\n{\n    char ping [COL_PING + 1];\n\tchar bestping [COL_PING + 1];\n    char ip [COL_IP + 1];\n    char name [COL_NAME + 1];\n    char map [COL_MAP + 1];\n    char gamedir [COL_GAMEDIR + 1];\n    char players [COL_PLAYERS + 1];\n    char fraglimit [COL_FRAGLIMIT + 1];\n    char timelimit [COL_TIMELIMIT + 1];\n} columns;\n\n\ntypedef struct playerinfo_s\n{\n    int id;\n    int frags;\n    int time;\n    int ping;\n    char name[21];\n    char skin[21];\n    char team[21];\n    int top;\n    int bottom;\n\tqbool spec; // flag: is spectator or player\n} playerinfo;\n\ntypedef enum {\n\tSERVER_EMPTY = 0, SERVER_NONEMPTY = 1, SERVER_FULL = 2\n} server_occupancy;\n\ntypedef struct server_data_s\n{\n    int passed_filters;\n    int ping;\n\tint bestping;\n    netadr_t    address;\n    columns     display;\n    char *keys[MAX_KEYS], *values[MAX_KEYS];\n    int keysn;\n\n    playerinfo *players[MAX_PLAYERS];\n    int playersn;\n\tserver_occupancy\toccupancy;\n    int spectatorsn;\n\tqbool qizmo;\n\tqbool qwfwd;\n\tqbool support_teams; // is server support team per player\n} server_data;\n\n\n#define MAX_SOURCE_NAME 25\n\ntypedef enum sb_source_type_e {\n\ttype_master,\n\ttype_file,\n\ttype_url,\n\ttype_dummy\n} sb_source_type_t;\n\ntypedef struct source_data_s\n{\n\tsb_source_type_t type;           // source type\n\tunion\n\t{\n\t\tnetadr_t    address;            // IP for master type\n\t\tchar        filename[200];      // filename for file type\n\t\tchar        url[512];           // URL with the list of servers\n\t} address;\n\n\tchar name[MAX_SOURCE_NAME+1];       // source name\n\tSYSTEMTIME last_update;             // last update time\n\n\tserver_data **servers;              // servers list\n\tint serversn;                       // servers no\n\tint servers_allocated;              // allocated size of servers array\n\n\tint checked;                        // 1 if use this source\n\tint unique;                         // order in file (for sorting)\n} source_data;\n\ntypedef struct player_host_s\n{\n    char name[21];\n    server_data *serv;\n} player_host;\n\ntypedef struct serverbrowser_window_s {\n\tint x, y, w, h;\n} serverbrowser_window_t;\nextern serverbrowser_window_t Browser_window;\n\nextern server_data * show_serverinfo;\n\nextern int resort_servers;\nextern int rebuild_servers_list;\nextern int resort_all_players;\nextern int rebuild_all_players;\nextern int ping_phase;\nextern double ping_pos;\nextern int updating_sources;\nextern int abort_ping;\nextern int source_full_update;\n\n// sources table\nextern source_data *sources[MAX_SOURCES];\nextern int sourcesn;\n\n// servers table\nextern server_data *servers[MAX_SERVERS];\nextern int serversn;\nextern int serversn_passed;\nextern int Servers_pos;\nextern int resort_sources;\nextern int source_unique;\n\n\n// servers\nserver_data * Create_Server(char *ip);\nserver_data * Clone_Server(server_data* source);\nserver_data * Create_Server2(netadr_t n);\nvoid Reset_Server(server_data *s);\nvoid Delete_Server(server_data *s);\nsource_data * Create_Source(void);\nvoid Reset_Source(source_data *s);\nvoid Delete_Source(source_data *s);\nvoid Update_Source(source_data *s);\nvoid Reload_Sources(void);\nvoid Rebuild_Servers_List(void);\nvoid Toggle_Source(source_data *);\nvoid Mark_Source(source_data *s);\nvoid Unmark_Source(source_data *s);\nvoid MarkDefaultSources(void);\nvoid WriteSourcesConfiguration(FILE *f);\nvoid Rebuild_All_Players(void);\nvoid Sort_All_Players(void);\nvoid DumpSource(source_data *s);\nvoid RemoveFromFileSource(source_data *source, server_data *serv);\nvoid AddToFileSource(source_data *source, server_data *serv);\nint IsInSource(source_data *source, server_data *serv);\n\nchar * next_space(char *s);\nchar * next_nonspace(char *s);\nchar * next_quote(char *s);\n\nvoid SB_ServerList_Lock(void);\nvoid SB_ServerList_Unlock(void);\n\n// sources\nqbool SB_Sources_Dump(void);\nint SB_Source_Add(const char* name, const char* address, sb_source_type_t type);\n// asynchronous sources update (in new thread)\nvoid SB_Sources_Update_Begin(qbool full);\n// synchronous sources update\nvoid SB_Sources_Update(qbool full);\nvoid SB_Source_Remove(int i);\n\n\n// net\nvoid GetServerInfo(server_data *serv);\nvoid GetServerPing(server_data *serv);\nvoid GetServerPingsAndInfos(int full);\nvoid GetServerPingsAndInfos_f(void);\nvoid Start_Autoupdate(server_data *s);\nvoid Alter_Autoupdate(server_data *s);\n\nchar *ValueForKey(server_data *s, char *k);\n\nvoid SetPing999(server_data *s);\nvoid SetPing(server_data *s, int ping);\nvoid SB_Server_SetBestPing(server_data *s, int bestping);\n\nvoid SB_Shutdown(void);\nvoid SB_RootInit(void);    // must be called as root\n\n// connection tester\nvoid SB_Test_Init(char *address);\nvoid SB_Test_Change(char *address);\nvoid SB_Test_Frame(void);\n\nvoid Browser_Init(void);\nvoid Browser_Init2(void);\n\nvoid SB_Servers_Draw (int x, int y, int w, int h);\nint SB_Servers_Key(int key);\nqbool SB_Servers_Mouse_Event(const mouse_state_t *ms);\nvoid SB_Servers_OnShow (void);\n\nvoid SB_Sources_Draw (int x, int y, int w, int h);\nint SB_Sources_Key(int key);\nqbool SB_Sources_Mouse_Event(const mouse_state_t *ms);\n\nvoid SB_Players_Draw (int x, int y, int w, int h);\nint SB_Players_Key(int key);\nqbool SB_Players_Mouse_Event(const mouse_state_t *ms);\n\nvoid SB_Specials_Draw(void);\nqbool SB_Specials_Key(int key, wchar unichar);\n\n// EX_browser_pathfind\ntypedef void (* proxy_ping_report_callback) (netadr_t adr, short dist);\n\nvoid SB_PingTree_Init(void);\nvoid SB_PingTree_Shutdown(void);\n\nqbool SB_PingTree_Built(void);\nvoid SB_PingTree_Build(void);\nqbool SB_PingTree_IsBuilding(void);\nvoid SB_PingTree_DumpPath(const netadr_t *addr);\nvoid SB_Proxy_QueryForPingList(const netadr_t *address, proxy_ping_report_callback callback);\nvoid SB_PingTree_ConnectBestPath(const netadr_t *addr);\nint SB_PingTree_GetPathLen(const netadr_t *addr);\nvoid SB_Proxylist_Unserialize_f(void);\n\n#define SB_TRIGGER_REFRESHDONE        1\n#define SB_TRIGGER_SOURCESUPDATED     2\n#define SB_TRIGGER_NOTIFY_PINGTREE    4\nextern int sb_queuedtriggers;\n\n#endif  // __EX_BROWSER__H__\n"
  },
  {
    "path": "src/EX_browser_net.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#ifndef _WIN32\n#include <sys/time.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#include <sys/param.h>\n#include <sys/ioctl.h>\n#include <sys/uio.h>\n#include <arpa/inet.h>\n#include <errno.h>\n#include <unistd.h>\n#endif\n#include \"menu.h\"\n#include \"EX_browser.h\"\n#include \"sbar.h\"\n#include \"keys.h\"\n\nextern qbool useNewPing;\nint oldPingHost(char *host_to_ping, int count);\nint oldPingHosts(server_data *servs[], int servsn, int count);\nint PingHost(char *host_to_ping, unsigned short port, int count, int time_out);\nint PingHosts(server_data *servs[], int servsn, int count);\n\nextern sem_t serverinfo_semaphore;\n// To prevent several Serverinfo threads to be started at the same time\nstatic int serverinfo_lock;\n\ntypedef struct infohost_s\n{\n    double lastsenttime;\n    int phase;\n} infohost;\n\nint autoupdate_serverinfo = 0;\n\nserver_data *autoupdate_server;\n\nstatic const char senddata[] = {255, 255, 255, 255, 's','t','a','t','u','s',' ','2','3','\\n'};\n\nint server_during_update = 0;\n\nint ReadInt (char *playerinfo, int *i)\n{\n    int s = 0, d = 0;\n    char buf[100];\n    while (playerinfo[s] == ' ')\n        s++;\n    while (playerinfo[s] != ' '  &&  playerinfo[s] != '\\n'  &&  s < 99)\n        buf[d++] = playerinfo[s++];\n\n    buf[d] = 0;\n    *i = atoi(buf);\n    return s;\n}\n\n\nint ReadString (char *playerinfo, char *str)\n{\n    int s = 0, d = 0;\n    while (playerinfo[s] == ' ')\n        s++;\n    if (playerinfo[s] == '\\\"') {\n        s++;\n        while (playerinfo[s] != '\\\"'  &&  playerinfo[s] != '\\n'  &&   s < 99)\n            str[d++] = playerinfo[s++];\n        if (playerinfo[s] == '\\\"')\n            s++;\n    }\n\n    str[d] = 0;\n    return s;\n}\n\nchar *ValueForKey(server_data *s, char *k)\n{\n    int i;\n    for (i=0; i < s->keysn; i++)\n        if (s->keys[i] && !strcmp(k, s->keys[i]))\n            return s->values[i];\n\n    return NULL;\n}\n\nvoid SetPing(server_data *s, int ping)\n{\n    if (ping < 0)\n        strlcpy (s->display.ping, \"n/a\", sizeof (s->display.ping));\n    else\n        snprintf (s->display.ping, sizeof (s->display.ping), \"%3d\", ping > 999 ? 999 : ping);\n\n    s->ping = ping;\n\t\n\tSB_Server_SetBestPing(s, -1);\n}\n\nvoid SB_Server_SetBestPing(server_data *s, int bestping)\n{\n    if (bestping < 0)\n        strlcpy (s->display.bestping, \"n/a\", sizeof (s->display.bestping));\n    else\n        snprintf (s->display.bestping, sizeof (s->display.bestping), \"%3d\", bestping > 999 ? 999 : bestping);\n\n    s->bestping = bestping;\n}\n\nqbool SB_AllServersDead(void)\n{\n\tint i;\n\t\n\tfor (i = 0; i < serversn; i++) {\n\t\tif (servers[i]->ping != -1) {\n\t\t\treturn false;\n\t\t}\n\t}\n\t\n\treturn true;\n}\n\nqbool SB_IsServerQWfwd(server_data *s)\n{\n\tchar *ident_match = \"qwfwd\";\n\tsize_t match_len = strlen(ident_match);\n\tchar *ident_value = ValueForKey(s, \"*version\");\n\t\n\tif (ident_value) {\n\t\treturn strncmp(ident_value, ident_match, match_len) == 0;\n\t}\n\telse\n\t{\n\t\treturn false;\n\t}\n}\n\nvoid Parse_Serverinfo(server_data *s, char *info)\n{\n    int i, j;\n    char *pinfo;\n    char *tmp;\n\n    s->passed_filters = 1;\n    s->support_teams = false; // by default server does't support team info per player\n\n    if (strncmp(info, \"\\xFF\\xFF\\xFF\\xFFn\", 5))\n    {\n        SetPing(s, -1);\n        return;\n    }\n\n    pinfo = strchr(info, '\\n');\n    if (pinfo != NULL)\n        *(pinfo++) = 0;\n\n    info += 5;\n\n    while (*info == '\\\\'  &&  s->keysn < MAX_KEYS)\n    {\n        char *i2, *i3;\n        i2 = strchr(info+1, '\\\\');\n        if (i2 == NULL)\n            break;\n        i3 = strchr(i2+1, '\\\\');\n        if (i3 == NULL)\n            i3 = info + strlen(info);\n\n        s->keys[s->keysn] = (char *) Q_malloc(i2-info);\n        strlcpy(s->keys[s->keysn], info+1, i2-info);\n\n        s->values[s->keysn] = (char *) Q_malloc(i3-i2);\n        strlcpy(s->values[s->keysn], i2+1, i3-i2);\n\n        s->keysn++;\n\n        info = i3;\n    }\n\n    // read players\n\n    for (i = s->spectatorsn = s->playersn = 0; pinfo  &&  strchr(pinfo, '\\n'); i++)\n    {\n        qbool spec;\n        int id, frags, time, ping, slen;\n        char name[100], skin[100], team[100];\n        char *nameptr = name;\n        int top, bottom;\n        int pos;\n\n        if (s->playersn + s->spectatorsn >= MAX_PLAYERS)\n            break;  // man\n\n        pos = 0;\n        pos += ReadInt(pinfo+pos, &id);\n        pos += ReadInt(pinfo+pos, &frags);\n        pos += ReadInt(pinfo+pos, &time);\n        pos += ReadInt(pinfo+pos, &ping);\n        pos += ReadString(pinfo+pos, name);\n        pos += ReadString(pinfo+pos, skin);\n        pos += ReadInt(pinfo+pos, &top);\n        pos += ReadInt(pinfo+pos, &bottom);\n        pos += ReadString(pinfo+pos, team);\n\n        if (team[0])\n            s->support_teams = true; // seems server support team info per player\n\n        if (ping > 0) { // seems player if relay on ping\n            spec = false;\n            s->playersn++;\n        }\n        else // spec\n        {\n            spec = true;\n            slen = strlen(name);\n            s->spectatorsn++;\n            ping = -ping;\n\n            if (name[0] == '\\\\' && name[1] == 's' && name[2] == '\\\\')\n                nameptr = name+3; // strip \\s\\<name>\n            else if (slen > 3 && name[slen-3] == '(' && name[slen-2] == 's' && name[slen-1] == ')')\n                name[slen-3] = 0; // strip <name>(s) for old servers\n        }\n\n        s->players[i] = (playerinfo *)Q_malloc(sizeof(playerinfo));\n        s->players[i]->id = id;\n        s->players[i]->frags = frags;\n        s->players[i]->time = time;\n        s->players[i]->ping = ping;\n        s->players[i]->spec = spec;\n\n        s->players[i]->top = Sbar_ColorForMap(top);\n        s->players[i]->bottom = Sbar_ColorForMap(bottom);\n\n        strlcpy(s->players[i]->name, nameptr, sizeof(s->players[0]->name));\n        strlcpy(s->players[i]->skin, skin, sizeof(s->players[0]->skin));\n        strlcpy(s->players[i]->team, team, sizeof(s->players[0]->team));\n\n        pinfo = strchr(pinfo, '\\n') + 1;\n    }\n\n    {\n    void *swap;\n    int n;\n    // sort players by frags\n    n = s->playersn + s->spectatorsn - 2;\n    for (i = 0; i <= n; i++)\n        for (j = n; j >= i; j--)\n            if (s->players[j] && s->players[j+1] && s->players[j]->frags < s->players[j+1]->frags)\n            {\n                swap = (void*)s->players[j];\n                s->players[j] = s->players[j+1];\n                s->players[j+1] = (playerinfo*)swap;\n            }\n    // sort keys\n    n = s->keysn - 2;\n    for (i = 0; i <= n; i++)\n        for (j = n; j >= i; j--)\n            if (strcasecmp(s->keys[j], s->keys[j+1]) > 0)\n            {\n                swap = (void*)s->keys[j];\n                s->keys[j] = s->keys[j+1];\n                s->keys[j+1] = (char*)swap;\n                swap = (void*)s->values[j];\n                s->values[j] = s->values[j+1];\n                s->values[j+1] = (char*)swap;\n            }\n    }\n\n    // fill-in display\n\ts->qwfwd = SB_IsServerQWfwd(s);\n\n    tmp = ValueForKey(s, \"hostname\");\n    if (tmp != NULL)\n        snprintf (s->display.name, sizeof (s->display.name),\"%-.*s\", COL_NAME, tmp);\n    else\n        return;\n\n    tmp = ValueForKey(s, \"fraglimit\");\n    if (tmp != NULL)\n        snprintf(s->display.fraglimit, sizeof (s->display.fraglimit), \"%*.*s\", COL_FRAGLIMIT, COL_FRAGLIMIT, strlen(tmp) > COL_FRAGLIMIT ? \"999\" : tmp);\n\n    tmp = ValueForKey(s, \"timelimit\");\n    if (tmp != NULL)\n        snprintf(s->display.timelimit, sizeof (s->display.timelimit), \"%*.*s\", COL_TIMELIMIT, COL_TIMELIMIT, strlen(tmp) > COL_TIMELIMIT ? \"99\" : tmp);\n\n    tmp = ValueForKey(s, \"*gamedir\");\n    s->qizmo = false;\n    if (tmp != NULL)\n        snprintf(s->display.gamedir, sizeof (s->display.gamedir) ,\"%.*s\", COL_GAMEDIR, tmp==NULL ? \"\" : tmp);\n    else\n    {\n        tmp = ValueForKey(s, \"*progs\");\n        if (tmp != NULL  &&  !strcmp(tmp, \"666\"))\n        {\n            snprintf(s->display.gamedir, sizeof (s->display.gamedir), \"qizmo\");\n            s->qizmo = true;\n        }\n    }\n\n    tmp = ValueForKey(s, \"map\");\n    if (tmp != NULL)\n        snprintf(s->display.map, sizeof (s->display.map), \"%-.*s\", COL_MAP, tmp==NULL ? \"\" : tmp);\n\n    tmp = ValueForKey(s, \"maxclients\");\n    if (!tmp || strlen(tmp) > 2)\n        tmp = \"99\";\n    i = s->playersn > 99 ? 99 : s->playersn;\n    if (i < 1) { s->occupancy = SERVER_EMPTY; }\n    else if (i > 0 && i < atoi(tmp)) { s->occupancy = SERVER_NONEMPTY; }\n    else { s->occupancy = SERVER_FULL; }\n    if (tmp != NULL)\n        snprintf(s->display.players, sizeof (s->display.players), \"%2d/%-2s\", i, tmp==NULL ? \"\" : tmp);\n}\n\nvoid GetServerInfo(server_data *serv)\n{\n    socket_t newsocket;\n    struct sockaddr_storage server;\n    int ret;\n    char answer[5000];\n    fd_set fd;\n    struct timeval tv;\n\n    // so we have a socket\n    newsocket = UDP_OpenSocket(PORT_ANY);\n    NetadrToSockadr (&(serv->address), &server);\n\n    // send status request\n    ret = sendto (newsocket, senddata, sizeof(senddata), 0,\n                  (struct sockaddr *)&server, sizeof(server) );\n\n    if (ret == -1)\n        return;\n\n    //fd.fd_count = 1;\n    //fd.fd_array[0] = newsocket;\n    FD_ZERO(&fd);\n    FD_SET(newsocket, &fd);\n    tv.tv_sec = 0;\n    tv.tv_usec = 1000 * 1.5 * sb_infotimeout.value; // multiply timeout by 1.5\n    ret = select(newsocket+1, &fd, NULL, NULL, &tv);\n\n    // get answer\n    if (ret > 0)\n        ret = recvfrom (newsocket, answer, 5000, 0, NULL, NULL);\n\n    if (ret > 0  &&  ret < 5000)\n    {\n        answer[ret] = 0;\n        server_during_update = 1;\n        Reset_Server(serv);\n        Parse_Serverinfo(serv, answer);\n        server_during_update = 0;\n    }\n    closesocket(newsocket);\n}\n\n//\n// Gets multiple server info simultaneously\n//\n\nint GetServerInfosProc(void * lpParameter)\n{\n    infohost *hosts;   // 0 if not sent yet, -1 if data read\n    double interval, lastsenttime;\n\n    socket_t newsocket;\n    struct sockaddr_storage dest;\n    int ret, i;\n    fd_set fd;\n    struct timeval timeout;\n\n    if (abort_ping)\n        return 0;\n\n    // so we have a socket\n    newsocket = UDP_OpenSocket(PORT_ANY);\n\n    hosts = (infohost *) Q_malloc (serversn * sizeof(infohost));\n    for (i=0; i < serversn; i++)\n    {\n        hosts[i].phase = 0;\n        hosts[i].lastsenttime = -1000;\n        Reset_Server(servers[i]);\n\n        // do not update dead servers\n\t\tif (servers[i]->ping < 0) {\n            hosts[i].phase = -1;//(int)sb_inforetries.value;\n\t\t}\n\t\t// do not update too distant servers\n\t\telse if (sb_hidehighping.integer && servers[i]->ping > sb_pinglimit.integer) {\n\t\t\thosts[i].phase = -1;\n\t\t}\n    }\n\n    interval = (1000.0 / sb_infospersec.value) / 1000;\n    lastsenttime = Sys_DoubleTime() - interval;\n    timeout.tv_sec = 0;\n    timeout.tv_usec = (long)(interval * 1000.0 * 1000.0 / 2);\n\n    ping_pos = 0;\n\n    while (1  &&  !abort_ping)\n    {\n        int index = -1;\n        double time = Sys_DoubleTime();\n\n        // if it is time to send next request\n        if (time > lastsenttime + interval)\n        {\n            int finished = 0;\n            int to_ask = 0;\n            int phase = (int)(sb_inforetries.value);\n\n            // find next server to ask\n            for (i=0; i < serversn; i++)\n            {\n                if (hosts[i].phase < phase  &&  hosts[i].phase >= 0  &&\n                    time > hosts[i].lastsenttime + (sb_infotimeout.value / 1000))\n                {\n                    index = i;\n                    phase = hosts[i].phase;\n                }\n\n                if (hosts[i].phase >= (int)sb_inforetries.value)\n                    finished++;\n                else\n                    if (hosts[i].phase >= 0)\n                        to_ask++;\n            }\n            //ping_pos = finished / (double)serversn;\n            ping_pos = (finished+to_ask <= 0) ? 0 :\n            finished / (double)(finished+to_ask);\n        }\n\n        // check if we should finish\n        if (index < 0)\n            if (time > lastsenttime + 1.2 * (sb_infotimeout.value / 1000))\n                break;\n\n        // send status request\n        if (index >= 0)\n        {\n            hosts[index].phase ++;\n            hosts[index].lastsenttime = time;\n            lastsenttime = time;\n\n            NetadrToSockadr (&(servers[index]->address), &dest);\n\n            ret = sendto (newsocket, senddata, sizeof(senddata), 0,\n                          (struct sockaddr *)&dest, sizeof(*(struct sockaddr *)&dest));\n            if(ret < 0)\n            {\n                Com_DPrintf(\"sendto() gave errno = %d : %s\\n\", errno, strerror(errno));\n            }\n            if (ret == -1)\n                ;//return;\n\n            // requests_sent++;\n            // ping_pos = requests_total <= 0 ? 0 : requests_sent / (double)requests_total;\n        }\n\n        // check if answer arrived and decode it\n        //fd.fd_count = 1;\n        //fd.fd_array[0] = newsocket;\n        FD_ZERO(&fd);\n        FD_SET(newsocket, &fd);\n\n        ret = select(newsocket+1, &fd, NULL, NULL, &timeout);\n        if (ret < 1)\n        {\n            Com_DPrintf(\"select() gave errno = %d : %s\\n\", errno, strerror(errno));\n        }\n\n        if (FD_ISSET(newsocket, &fd))\n        {\n            struct sockaddr_storage hostaddr;\n            netadr_t from;\n            int i;\n            char answer[5000];\n            answer[0] = 0;\n\n            i = sizeof(hostaddr);\n            ret = recvfrom (newsocket, answer, 5000, 0, (struct sockaddr *)&hostaddr, (socklen_t *)&i);\n            answer[max(0, min(ret, 4999))] = 0;\n\n            if (ret > 0)\n            {\n                SockadrToNetadr (&hostaddr, &from);\n\n                for (i=0; i < serversn; i++)\n                    if (from.ip[0] == servers[i]->address.ip[0] &&\n                        from.ip[1] == servers[i]->address.ip[1] &&\n                        from.ip[2] == servers[i]->address.ip[2] &&\n                        from.ip[3] == servers[i]->address.ip[3] &&\n                        from.port == servers[i]->address.port)\n                    {\n                        hosts[i].phase = (int)sb_inforetries.value;\n                        Parse_Serverinfo(servers[i], answer);\n                        break;\n                    }\n            }\n        }\n    }\n\n    // reset pings to 999 if server didn't answer\n    for (i=0; i < serversn; i++)\n        if (servers[i]->keysn <= 0)\n            SetPing(servers[i], -1);\n\n    closesocket(newsocket);\n    Q_free(hosts);\n\n    return 0;\n}\n\nvoid GetServerPing(server_data *serv)\n{\n    int p;\n    char buf[32];\n    snprintf (buf, sizeof (buf), \"%d.%d.%d.%d\",\n        serv->address.ip[0],\n        serv->address.ip[1],\n        serv->address.ip[2],\n        serv->address.ip[3]);\n\n    p = useNewPing\n\t\t\t// new ping = UPD QW Packet\n\t\t\t? PingHost(buf, serv->address.port, (int)max(1, min(sb_pings.value, 10)), sb_pingtimeout.value)\n\t\t\t// old ping = ICMP PING Packet\n\t\t\t: oldPingHost(buf, (int)max(1, min(sb_pings.value, 10)));\n    \n\tif (p)\n        SetPing(serv, p-1);\n    else\n        SetPing(serv, p-1);\n}\n\nint GetServerPingsAndInfosProc(void * lpParameter)\n{\n\tunsigned int SB_Sources_Marked_Count(void);\n\textern cvar_t sb_listcache;\n\textern void SB_Serverlist_Serialize_f(void);\n\n\tint full = (int)(uintptr_t)lpParameter;\n    abort_ping = 0;\n\n\tif (full || serversn_passed == 0) {\n\t\tif (SB_Sources_Marked_Count() == 0) {\n\t\t\t// ensure some sources are marked, otherwise the refresh makes no sense\n\t\t\tMarkDefaultSources();\n\t\t}\n\n\t\tSB_Sources_Update(true);\n\t\tif (useNewPing) {\n\t\t\t// New Ping = UPD QW Packet ping using 2 threads (sender and receiver)\n\t\t\tPingHosts(servers, serversn, sb_pings.integer);\n\t\t}\n\t\telse {\n\t\t\t// Old Ping = ICMP PING Packet using single thread\n\t\t\toldPingHosts(servers, serversn, sb_pings.integer );\t\n\t\t}\n\t}\n\n    if (!abort_ping)\n    {\n        ping_phase = 2;\n        GetServerInfosProc(NULL);\n    }\n\n    /*\n\tif (abort_ping)\n        Sys_MSleep(500);    // let the packets end the road\n\t*/\n\n    resort_servers = 1;\n    rebuild_all_players = 1;\n    ping_phase = 0;\n\n\tsb_queuedtriggers |= SB_TRIGGER_REFRESHDONE;\n\n\tif (sb_listcache.integer) {\n\t\tSB_Serverlist_Serialize_f();\n\t}\n\n\tif (sb_findroutes.integer && (full || !SB_PingTree_Built())) {\n\t\tSB_PingTree_Build();\n\t}\n\n\tserverinfo_lock = 0;\n    return 0;\n}\n\nvoid GetServerPingsAndInfos(int full)\n{\n\tif (serverinfo_lock || SB_PingTree_IsBuilding()) {\n\t\tCom_Printf(\"Server list refresh is still pending\\n\");\n\t\treturn;\n\t}\n\n\tserverinfo_lock = 1;\n\n\tif (rebuild_servers_list)\n\t\tRebuild_Servers_List();\n\n\tif (rebuild_all_players  &&  show_serverinfo == NULL)\n\t\tRebuild_All_Players();\n\n\tif (serversn <= 0 || (sb_hidedead.integer == 0 && SB_AllServersDead())) {\n\t\t// there's a possibility that sources hasn't been queried yet\n\t\t// so let's do a full update, that ensures sources are updated\n\t\tfull = true;\n\t}\n\n\tping_phase = 1;\n\tping_pos = 0;\n\n\tif (Sys_CreateDetachedThread (GetServerPingsAndInfosProc, (void *)(uintptr_t)full) < 0) {\n\t\tCom_Printf(\"Failed to create GetServerPingsAndInfosProc thread\\n\");\n\t}\n}\n\nvoid GetServerPingsAndInfos_f(void)\n{\n\tqbool full = true;\n\tif (Cmd_Argc() > 0 && strcmp(Cmd_Argv(1), \"info\") == 0) {\n\t\tfull = false;\n\t}\n\tGetServerPingsAndInfos(full);\n}\n\n//\n// autoupdate serverinfo\n//\n\nint AutoupdateProc(void * lpParameter)\n{\n    double lastupdatetime = -1;\n\n    while (autoupdate_serverinfo)\n    {\n        double time = Sys_DoubleTime();\n\n        if ((sb_liveupdate.integer > 0)  &&\n            time >= lastupdatetime + sb_liveupdate.integer  &&\n            key_dest == key_menu /* todo: add \"on server list tab\" condition here */)\n        {\n            server_data *serv;\n\t\t\t\n\t\t\tSys_SemWait(&serverinfo_semaphore);\n\t\t\tserv = autoupdate_server;\n\t\t\tif (serv != NULL)\n            {\n                GetServerInfo(serv);\n                lastupdatetime = time;\n            }\n\t\t\tSys_SemPost(&serverinfo_semaphore);\n\t\t}\n\t\t\n\t\tSys_MSleep(1000); // we don't need nor allow updates faster than 1 second anyway\n    }\n    return 0;\n}\n\nvoid Start_Autoupdate(server_data *s)\n{\n    autoupdate_server = s;\n    if (Sys_CreateDetachedThread(AutoupdateProc, (void *) s) < 0) {\n\t    Com_Printf(\"Failed to create AutoupdateProc thread\\n\");\n    }\n}\n\nvoid Alter_Autoupdate(server_data *s)\n{\n    autoupdate_server = s;\n}\n"
  },
  {
    "path": "src/EX_browser_pathfind.c",
    "content": "/*\nCopyright (C) 2011 johnnycz\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/**\n\t\\file\n\n\t\\brief\n\tModule for finding best connection to a server, ping-wise.\n\n\t\\author johnnycz\n**/\n\n#include \"quakedef.h\"\n#include <limits.h>\n#ifndef _WIN32\n#include <sys/time.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#include <sys/param.h>\n#include <sys/ioctl.h>\n#include <sys/uio.h>\n#include <arpa/inet.h>\n#include <errno.h>\n#include <unistd.h>\n#endif\n#include \"EX_browser.h\"\n\n// declared in EX_browser.c\nextern cvar_t sb_proxinfopersec;\nextern cvar_t sb_proxretries;\nextern cvar_t sb_proxtimeout;\nextern cvar_t sb_listcache;\n\n// non-leaf = proxy (or users computer) = has more than 1 neighbour\n// at the time of writing this code there were 10 active proxies around the world\n#define MAX_NONLEAVES 40\n\n#define PROXY_PINGLIST_QUERY \"\\xff\\xff\\xff\\xffpingstatus\"\n#define PROXY_PINGLIST_QUERY_LEN (sizeof(PROXY_PINGLIST_QUERY)-1)\n#define PROXY_REPLY_ENTRY_LEN 8\n#define PROXY_REPLY_BUFFER_SIZE (PROXY_REPLY_ENTRY_LEN*MAX_SERVERS)\n\n// current amount of qw servers ~ 300... but neighbour count means MAX_SERVERS*MAX_NONLEAVES\n#define INVALID_NODE (-1)\ntypedef int nodeid_t;\n\n// only pings 0-999 are in our interest; and -1 for invalid ping\n#define DIST_INFINITY SHRT_MAX\ntypedef short dist_t;\n\n// trick around language limits - allows to copy 4 bytes with \"=\"\ntypedef struct ipaddr_t {\n\tbyte data[4];\n} ipaddr_t;\n\ntypedef struct ping_node_t {\n\tipaddr_t ipaddr;\n\tnodeid_t prev;           // previous node on the shortest path\n\tnodeid_t nlist_start;    // index of the first neighbour\n\tnodeid_t nlist_end;      // index of the last neighbour + 1\n\tdist_t dist;              // distance (= ping)\n\tqbool visited;\n\tunsigned short proxport; // if there's a proxy on this address,\n\t                         // this is the port it's running on\n\t                         // and it is already in the network format\n} ping_node_t;\n\ntypedef struct ping_neighbour_t {\n\tnodeid_t id;\n\tdist_t dist;\n} ping_neighbour_t;\n\ntypedef struct proxy_query_request_t {\n\tsocket_t sock;\n\tnodeid_t nodeid;\n\tqbool done;\n} proxy_query_request_t;\n\ntypedef struct proxy_request_queue_t {\n\tproxy_query_request_t *data;\n\tsize_t items;\n\tqbool sending_done; // sending thread ended\n\tqbool allrecved;\t// all proxies have been successfully scanned\n} proxy_request_queue;\n\nstatic ping_node_t ping_nodes[MAX_SERVERS];\nstatic nodeid_t ping_nodes_count = 0;\n#define MAX_SERVERS_BLOCKSIZE (MAX_SERVERS*MAX_NONLEAVES)\nstatic ping_neighbour_t* ping_neighbours;\nstatic unsigned long ping_neighbours_max;\nstatic nodeid_t ping_neighbours_count = 0;\n\nstatic nodeid_t startnode_id = 0;\n\nstatic qbool building_pingtree = false; // when true, the pingtree build thread is still working\nstatic qbool pingtree_built = false;\n\nstatic sem_t phase2thread_lock;\n\nstatic void SB_PingTree_Assertions(void)\n{\n\tif (ping_nodes_count >= MAX_SERVERS) {\n\t\tSys_Error(\"EX_Browser_pathfind: max nodes count reached\");\n\t}\n\n\tif (ping_neighbours_count >= ping_neighbours_max) {\n\t\twhile (ping_neighbours_count >= ping_neighbours_max)\n\t\t\tping_neighbours_max += MAX_SERVERS_BLOCKSIZE;\n\t\tping_neighbours = Q_realloc(ping_neighbours, ping_neighbours_max * sizeof(ping_neighbour_t));\n\t\tif (!ping_neighbours) {\n\t\t\tSys_Error(\"EX_Browser_pathfind: max neighbours count reached\");\n\t\t}\n\t}\n\n\tif (startnode_id != 0) {\n\t\t// a bit paranoid check, startnode is always the first node\n\t\tSys_Error(\"EX_browser_pathfind: startnode_id != 0\");\n\t}\n}\n\nstatic int SB_PingTree_FindIp(ipaddr_t ipaddr)\n{\n\tint i;\n\n\t// xxx make the lookup faster than linear\n\tfor (i = 0; i < ping_nodes_count; i++) {\n\t\tif (memcmp(&ping_nodes[i].ipaddr, &ipaddr, sizeof(ipaddr_t)) == 0) {\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn INVALID_NODE;\n}\n\nstatic int SB_PingTree_AddNode(ipaddr_t ipaddr, unsigned short proxport)\n{\n\tint id = SB_PingTree_FindIp(ipaddr);\n\n\tif (id != INVALID_NODE) {\n\t\tif (proxport && !ping_nodes[id].proxport) {\n\t\t\tping_nodes[id].proxport = proxport;\n\t\t}\n\t\treturn id;\n\t}\n\n\tid = ping_nodes_count++;\n\n\tSB_PingTree_Assertions();\n\tping_nodes[id].ipaddr = ipaddr;\n\tping_nodes[id].prev = INVALID_NODE;\n\tping_nodes[id].nlist_start = INVALID_NODE;\n\tping_nodes[id].nlist_end = INVALID_NODE;\n\tping_nodes[id].dist = DIST_INFINITY;\n\tping_nodes[id].proxport = proxport;\n\tping_nodes[id].visited = false;\n\tSB_PingTree_Assertions();\n\n\treturn id;\n}\n\nstatic int SB_PingTree_AddNeighbour(nodeid_t neighbour_id, dist_t dist)\n{\n\tint id = ping_neighbours_count++;\n\n\tSB_PingTree_Assertions();\n\tping_neighbours[id].id = neighbour_id;\n\tping_neighbours[id].dist = dist;\n\tSB_PingTree_Assertions();\n\n\treturn id;\n}\n\nstatic ipaddr_t SB_DummyIpAddr(void)\n{\n\tipaddr_t dummy = {{0, 0, 0, 0}};\n\treturn dummy;\n}\n\nstatic void SB_PingTree_AddSelf(void)\n{\n\tstartnode_id = SB_PingTree_AddNode(SB_DummyIpAddr(), 0);\n}\n\nstatic void SB_PingTree_Clear(void)\n{\n\tping_nodes_count = 0;\n\tping_neighbours_count = 0;\n\tSB_PingTree_AddSelf();\n}\n\nstatic ipaddr_t SB_Netaddr2Ipaddr(const netadr_t *netadr)\n{\n\tipaddr_t retval;\n\tmemcpy(retval.data, &netadr->ip, 4);\n\treturn retval;\n}\n\nstatic qbool SB_PingTree_IsServerDead(const server_data *data)\n{\n\treturn data->ping < 0;\n}\n\nstatic qbool SB_PingTree_IsProxyFiltered(const server_data *data)\n{\n\tif (!data->qwfwd) {\n\t\treturn false;\n\t}\n\telse if (sb_ignore_proxy.string[0] == '\\0') {\n\t\treturn false;\n\t}\n\telse {\n\t\tchar ip_str[32];\n\t\tconst byte *ip = data->address.ip;\n\t\tint port = (int) ntohs(data->address.port);\n\t\t\n\t\tsnprintf(&ip_str[0], sizeof(ip_str), \"%d.%d.%d.%d:%d\", ip[0], ip[1], ip[2], ip[3], port);\n\n\t\treturn strstr(sb_ignore_proxy.string, ip_str) != NULL;\n\t}\n}\n\nstatic nodeid_t SB_PingTree_AddServer(const server_data *data)\n{\n\tnodeid_t node_id = INVALID_NODE;\n\n\tif (!SB_PingTree_IsServerDead(data) && !SB_PingTree_IsProxyFiltered(data)) {\n\t\t\tnode_id = SB_PingTree_AddNode(SB_Netaddr2Ipaddr(&data->address),\n\t\t\t\tdata->qwfwd ? data->address.port : 0);\n\n\t\tSB_PingTree_AddNeighbour(node_id, data->ping);\n\t}\n\t\n\treturn node_id;\n}\n\nstatic void SB_PingTree_AddProxyPing(netadr_t adr, dist_t dist)\n{\n\tnodeid_t id_neighbour;\n\tipaddr_t ip = SB_Netaddr2Ipaddr(&adr);\n\n\tid_neighbour = SB_PingTree_FindIp(ip); // most of the servers should be found\n\tif (id_neighbour == INVALID_NODE) {\n\t\t// strange - there is no direct route to this server, but a proxy can reach it (!)\n\t\tid_neighbour = SB_PingTree_AddNode(ip, 0);\n\t}\n\t\n\tSB_PingTree_AddNeighbour(id_neighbour, dist);\n}\n\nstatic void SB_Proxy_ParseReply(const byte *buf, size_t buflen, proxy_ping_report_callback callback)\n{\n\tsize_t entries = buflen / PROXY_REPLY_ENTRY_LEN;\n\tint i;\n\n\tCom_DPrintf(\"Reading %d entries from a proxy reply\\n\", entries);\n\n\tfor (i = 0; i < entries; i++) {\n\t\tnetadr_t adr;\n\t\tdist_t dist = 0;\n\n\t\tadr.type = NA_IP;\n\t\tmemcpy(adr.ip, buf, 4);\n\t\tbuf += 4;\n\t\t\n\t\tadr.port = 0;\n\t\tadr.port |= 0x00FF & *buf++;\n\t\tadr.port |= 0xFF00 & (*buf++ << 8);\n\n\t\tdist |= 0x00FF & *buf++;\n\t\tdist |= 0xFF00 & (*buf++ << 8);\n\n\t\tif (dist >= 0) {\n\t\t\t// \"server not reachable\" is reported as 65536, in our case -1\n\t\t\tcallback(adr, dist);\n\t\t}\n\t}\n}\n\nvoid SB_Proxy_QueryForPingList(const netadr_t *address, proxy_ping_report_callback callback)\n{\n\tbyte buf[PROXY_REPLY_BUFFER_SIZE];\n\tchar packet[] = PROXY_PINGLIST_QUERY;\n\tchar adrstr[32];\n\tstruct sockaddr_in addr_to, addr_from;\n\tstruct timeval timeout;\n\tfd_set fd;\n\tsocket_t sock;\n\tint i, ret;\n\tsocklen_t inaddrlen;\n\n\tsnprintf(&adrstr[0], sizeof(adrstr), \"%d.%d.%d.%d\", (int) address->ip[0], (int) address->ip[1], (int) address->ip[2], (int) address->ip[3]);\n\n\taddr_to.sin_addr.s_addr = inet_addr((const char *)adrstr);\n\tif (addr_to.sin_addr.s_addr == INADDR_NONE) {\n\t\treturn;\n\t}\n\taddr_to.sin_family = AF_INET;\n\taddr_to.sin_port = address->port;\n\n\tsock = UDP_OpenSocket(PORT_ANY);\n\tfor (i = 0; i < sb_proxretries.integer; i++) {\n\t\tret = sendto(sock, packet, strlen(packet), 0, (struct sockaddr *)&addr_to, sizeof(struct sockaddr));\n\t\tif (ret == -1) // failure, try again\n\t\t\tcontinue;\n\n_select:\n\t\tFD_ZERO(&fd);\n\t\tFD_SET(sock, &fd);\n\t\ttimeout.tv_sec = 0;\n\t\ttimeout.tv_usec = sb_proxtimeout.integer * 1000;\n\t\tret = select(sock+1, &fd, NULL, NULL, &timeout);\n\t\tif (ret <= 0) { // timed out or error\n\t\t\tCom_DPrintf(\"select() gave errno = %d : %s\\n\", errno, strerror(errno));\n\t\t\tcontinue;\n\t\t}\n\n\t\tinaddrlen = sizeof(struct sockaddr_in);\n\t\tret = recvfrom(sock, (char *) buf, sizeof(buf), 0, (struct sockaddr *)&addr_from, &inaddrlen);\n\n\t\tif (ret == -1) // failure, try again\n\t\t\tcontinue;\n\t\tif (addr_from.sin_addr.s_addr != addr_to.sin_addr.s_addr) // martian, discard and see if a valid response came in after it\n\t\t\tgoto _select;\n\t\tif (strncmp(\"\\xff\\xff\\xff\\xffn\", (char *) buf, 5) == 0)\n\t\t\tSB_Proxy_ParseReply(buf+5, ret-5, callback);\n\n\t\tbreak;\n\t}\n\tclosesocket(sock);\n}\n\nstatic void SB_PingTree_AddNodes(void)\n{\n\tint i;\n\t\n\t// add our neighbours - servers we directly ping\n\tSB_ServerList_Lock();\n\tping_nodes[startnode_id].nlist_start = ping_neighbours_count;\n\tfor (i = 0; i < serversn; i++) {\n\t\tSB_PingTree_AddServer(servers[i]);\n\t}\n\tping_nodes[startnode_id].nlist_end = ping_neighbours_count;\n\tSB_ServerList_Unlock();\n}\n\nstatic netadr_t SB_NodeNetadr_Get(nodeid_t id)\n{\n\tnetadr_t ret;\n\tret.type = NA_IP;\n\tret.port = ping_nodes[id].proxport;\n\tmemcpy(&ret.ip, ping_nodes[id].ipaddr.data, 4);\n\treturn ret;\n}\n\nint SB_PingTree_SendQueryThread(void *thread_arg)\n{\n\tproxy_request_queue *queue = (proxy_request_queue *) thread_arg;\n\tint i, ret;\n\tdouble interval_ms;\n#ifdef _WIN32\n\ttimerresolution_session_t timersession = {0, 0};\n#endif\n\n\t// Null check for queue\n\tif (!queue) {\n\t\tCom_DPrintf(\"SB_PingTree_SendQueryThread: queue is NULL\\n\");\n\t\treturn -1;\n\t}\n\n\t// Null check for queue data\n\tif (!queue->data) {\n\t\tCom_DPrintf(\"SB_PingTree_SendQueryThread: queue->data is NULL\\n\");\n\t\treturn -1;\n\t}\n\n\tinterval_ms = (1.0 / sb_proxinfopersec.value) * 1000.0;\n\n\tSys_TimerResolution_InitSession(&timersession);\n\tSys_TimerResolution_RequestMinimum(&timersession);\n\n\tfor (i = 0; i < queue->items; i++) {\n\t\tif (!queue->data[i].done) {\n\t\t\tstruct sockaddr_storage addr_to;\n\t\t\tnetadr_t netadr;\n\n\t\t\t// Validate nodeid\n\t\t\tif (queue->data[i].nodeid < 0 || queue->data[i].nodeid >= ping_nodes_count) {\n\t\t\t\tCom_DPrintf(\"SB_PingTree_SendQueryThread: invalid nodeid %d\\n\", queue->data[i].nodeid);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tnetadr = SB_NodeNetadr_Get(queue->data[i].nodeid);\n\n\t\t\tNetadrToSockadr(&netadr, &addr_to);\n\t\t\tret = sendto(queue->data[i].sock,\n\t\t\t\tPROXY_PINGLIST_QUERY, PROXY_PINGLIST_QUERY_LEN, 0,\n\t\t\t\t(struct sockaddr *) &addr_to, sizeof (struct sockaddr));\n\t\t\tif (ret < 0) {\n\t\t\t\tCom_DPrintf(\"SB_PingTree_SendQueryThread sendto returned %d\\n\", ret);\n\t\t\t}\n\t\t\tSys_MSleep(interval_ms);\n\t\t}\n\n\t\tif (queue->allrecved) break;\n\t}\n\n\tSys_TimerResolution_Clear(&timersession);\n\n\tqueue->sending_done = true;\n\n\treturn 0;\n}\n\n#define PROXY_SERIALIZE_FILE_VERSION 1\nvoid SB_Proxylist_Serialize_Start(FILE *f)\n{\n\tint version = PROXY_SERIALIZE_FILE_VERSION;\n\n\t// header\n\t// - version\n\tfwrite(&version, sizeof(int), 1, f);\n}\n\nvoid SB_Proxylist_Serialize_Reply(FILE *f, netadr_t proxy, void *buf, size_t buflen)\n{\n\tfwrite(&proxy, sizeof(netadr_t), 1, f);\n\tfwrite(&buflen, sizeof(size_t), 1, f);\n\tfwrite(buf, buflen, 1, f);\n}\n\nvoid SB_Proxylist_Serialize_End(FILE *f)\n{\n\tnetadr_t invalid;\n\n\tinvalid.type = NA_INVALID;\n\tfwrite(&invalid, sizeof(netadr_t), 1, f);\n}\n\nstatic qbool SB_PingTree_RecvQuery(proxy_request_queue *queue, FILE *f)\n{\n\tqbool last_cycle = false;\n\tfd_set recvset;\n\tint maxsock = 0;\n\tint i, ret;\n\tstruct timeval timeout;\n\tqbool allrecved = false;\n\n\ttimeout.tv_sec = 0;\n\ttimeout.tv_usec = sb_proxtimeout.integer * 1000;\n\n\tfor (;;) {\n\t\tif (queue->sending_done) {\n\t\t\tlast_cycle = true;\n\t\t}\n\t\t\n\t\tallrecved = true;\n\t\tFD_ZERO(&recvset);\n\t\tfor (i = 0; i < queue->items; i++) {\n\t\t\tif (!queue->data[i].done) {\n\t\t\t\tsocket_t sock = queue->data[i].sock;\n\t\t\t\tFD_SET(sock, &recvset);\n\t\t\t\tif ((int) sock > maxsock) {\n\t\t\t\t\tmaxsock = (int) sock;\n\t\t\t\t}\n\t\t\t\tallrecved = false;\n\t\t\t}\n\t\t}\n\t\tif (allrecved) {\n\t\t\tqueue->allrecved = true;\n\t\t\tbreak;\n\t\t}\n\n\t\tret = select((maxsock + 1), &recvset, NULL, NULL, &timeout);\n\n\t\tif (ret == 0 && last_cycle == true) {\n\t\t\tbreak; // ret = 0 means we got timeout\n\t\t}\n\n\t\tif (ret == 0) {\n\t\t\tcontinue; // not all proxies were queried yet\n\t\t}\n\n\t\tif (ret < 0) {\n\t\t\tCom_DPrintf(\"select returned %d\\n\", ret);\n\t\t\tbreak;\n\t\t}\n\n\t\tfor (i = 0; i < queue->items; i++) {\n\t\t\tif (!queue->data[i].done && FD_ISSET(queue->data[i].sock, &recvset)) {\n\t\t\t\tbyte buf[PROXY_REPLY_BUFFER_SIZE];\n\t\t\t\tstruct sockaddr_storage addr_from;\n\t\t\t\tsocklen_t addr_from_len = sizeof(struct sockaddr_in);\n\n\t\t\t\tret = recvfrom(queue->data[i].sock, (char *) buf, PROXY_REPLY_BUFFER_SIZE, 0, (struct sockaddr *) &addr_from, &addr_from_len);\n\t\t\t\tif (ret == -1) {\n\t\t\t\t\tCom_DPrintf(\"SB_PingTree_RecvQuery recvfrom failed\\n\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (strncmp(\"\\xff\\xff\\xff\\xffn\", (char *) buf, 5) == 0) {\n\t\t\t\t\tnodeid_t id = queue->data[i].nodeid;\n\t\t\t\t\tqueue->data[i].done = true;\n\t\t\t\t\tping_nodes[id].nlist_start = ping_neighbours_count;\n\t\t\t\t\tif (f && ret > 5)\n\t\t\t\t\t\t\tSB_Proxylist_Serialize_Reply(f, SB_NodeNetadr_Get(id), buf+5, ret-5);\n\t\t\t\t\tSB_Proxy_ParseReply(buf+5, ret-5, SB_PingTree_AddProxyPing);\n\t\t\t\t\tping_nodes[id].nlist_end = ping_neighbours_count;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tCom_DPrintf(\"Invalid reply received\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn allrecved;\n}\n\nstatic void SB_PingTree_ScanProxies(void)\n{\n\tint i;\n\tproxy_request_queue *queue = NULL;\n\tsize_t request = 0;\n\tFILE *f = NULL;\n\n\t// Allocate queue on heap\n\tqueue = (proxy_request_queue *) Q_malloc(sizeof(proxy_request_queue));\n\tif (!queue) {\n\t\tCom_Printf(\"Failed to allocate memory for proxy request queue\\n\");\n\t\treturn;\n\t}\n\n\t// Initialize queue fields\n\tqueue->data = NULL;\n\tqueue->items = 0;\n\tqueue->sending_done = false;\n\tqueue->allrecved = false;\n\n\tfor (i = 0; i < ping_nodes_count; i++) {\n\t\tif (ping_nodes[i].proxport) {\n\t\t\tqueue->items++;\n\t\t}\n\t}\n\n\tif (!queue->items) {\n\t\tQ_free(queue);\n\t\treturn;\n\t}\n\n\tqueue->data = (proxy_query_request_t *) Q_malloc(sizeof(proxy_query_request_t) * queue->items);\n\tif (!queue->data) {\n\t\tCom_Printf(\"Failed to allocate memory for proxy request data\\n\");\n\t\tQ_free(queue);\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < ping_nodes_count; i++) {\n\t\tif (ping_nodes[i].proxport) {\n\t\t\tqueue->data[request].done = false;\n\t\t\tqueue->data[request].nodeid = i;\n\t\t\tqueue->data[request].sock = UDP_OpenSocket(PORT_ANY);\n\t\t\trequest++;\n\t\t}\n\t}\n\n\tif (sb_listcache.value) {\n\t\tchar prx_data_path[MAX_OSPATH] = {0};\n\n\t\tsnprintf(&prx_data_path[0], sizeof(prx_data_path), \"%s/%s\", com_homedir, \"proxies_data\");\n\t\tf = fopen(prx_data_path, \"wb\");\n\t\tif (f)\n\t\t\tSB_Proxylist_Serialize_Start(f);\n\t}\n\n\tfor (i = 0; i < sb_proxretries.integer; i++) {\n\t\tqueue->sending_done = false;\n\t\tif (Sys_CreateDetachedThread(SB_PingTree_SendQueryThread, (void *) queue) < 0) {\n\t\t\tCom_Printf(\"Failed to create SB_PingTree_SendQueryThread thread\\n\");\n\t\t\t// If thread creation fails, don't continue with this retry\n\t\t\tcontinue;\n\t\t}\n\t\tSB_PingTree_RecvQuery(queue, f);\n\t\t\n\t\t// Wait for the sending thread to complete before next retry\n\t\twhile (!queue->sending_done) {\n\t\t\tSys_MSleep(10);\n\t\t}\n\t\t\n\t\tif (queue->allrecved) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (f) {\n\t\tSB_Proxylist_Serialize_End(f);\n\t\tfclose(f);\n\t}\n\n\tfor (i = 0; i < queue->items; i++) {\n\t\tclosesocket(queue->data[i].sock);\n\t}\n\n\tQ_free(queue->data);\n\tQ_free(queue);\n}\n\nstatic nodeid_t SB_PingTree_NearestNodeGet(void)\n{\n\t// XXX: implement using binary/fibonacci heap...\n\tint i;\n\tnodeid_t ret = INVALID_NODE;\n\tdist_t minimum = DIST_INFINITY;\n\n\tfor (i = 0; i < ping_nodes_count; i++) {\n\t\tif (!ping_nodes[i].visited && ping_nodes[i].dist < minimum) {\n\t\t\tret = i;\n\t\t\tminimum = ping_nodes[i].dist;\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nstatic void SB_PingTree_Dijkstra(void)\n{\n\tint i;\n\n\tping_nodes[startnode_id].dist = 0;\n\n\tfor (;;) {\n\t\tnodeid_t cur = SB_PingTree_NearestNodeGet();\n\t\tif (cur == INVALID_NODE) break;\n\n\t\tping_nodes[cur].visited = true;\n\t\tfor (i = ping_nodes[cur].nlist_start; i < ping_nodes[cur].nlist_end; i++) {\n\t\t\tdist_t altdist = ping_nodes[cur].dist + ping_neighbours[i].dist;\n\t\t\tif (altdist < ping_nodes[ping_neighbours[i].id].dist) {\n\t\t\t\t// so-called Relax()\n\t\t\t\tping_nodes[ping_neighbours[i].id].dist = altdist;\n\t\t\t\tping_nodes[ping_neighbours[i].id].prev = cur;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void SB_PingTree_Phase1(void)\n{\n\tSB_PingTree_Clear();\n\tSB_PingTree_AddNodes();\n}\n\nstatic void SB_PingTree_UpdateServerList(void)\n{\n\tint i;\n\n\tSB_ServerList_Lock();\n\n\tfor (i = 0; i < serversn; i++) {\n\t\tnodeid_t id = SB_PingTree_FindIp(SB_Netaddr2Ipaddr(&servers[i]->address));\n\t\tif (id == INVALID_NODE || ping_nodes[id].prev == INVALID_NODE || ping_nodes[id].prev == startnode_id) continue;\n\n\t\tSB_Server_SetBestPing(servers[i], ping_nodes[id].dist);\n\t}\n\n\tSB_ServerList_Unlock();\n}\n\nint SB_PingTree_Phase2(void *ignored_arg)\n{\n\tSB_PingTree_ScanProxies();\n\tSB_PingTree_Dijkstra();\n\tSB_PingTree_UpdateServerList();\n\n\tsb_queuedtriggers |= SB_TRIGGER_NOTIFY_PINGTREE;\n\tSys_SemPost(&phase2thread_lock);\n\tbuilding_pingtree = false;\n\tpingtree_built = true;\n\treturn 0;\n}\n\n/// Has the Ping Tree been already built?\nqbool SB_PingTree_Built(void)\n{\n\treturn ping_nodes_count > 0;\n}\n\n/// Creates whole graph structure for looking up shortest paths to servers (ping-wise).\n///\n/// Grabs data from the server browser and then from the proxies.\nvoid SB_PingTree_Build(void)\n{\n\tif (building_pingtree) {\n\t\tCom_Printf(\"Ping Tree is still being built...\\n\");\n\t\treturn;\n\t}\n\t// no race condition here, as this must always get executed by the main thread\n\tbuilding_pingtree = true;\n\tCom_Printf(\"Building the Ping Tree...\\n\");\n\n\t// first quick phase is initialization + quick read of data from the server browser\n\tSB_PingTree_Phase1();\n\t// second longer phase is querying the proxies for their ping data + dijkstra algo\n\tSys_SemWait(&phase2thread_lock);\n\tif (Sys_CreateDetachedThread(SB_PingTree_Phase2, NULL) < 0) {\n\t\tCom_Printf(\"Failed to create SB_PingTree_Phase2 thread\\n\");\n\t}\n}\n\n/// Prints the shortest path to given IP address\nvoid SB_PingTree_DumpPath(const netadr_t *addr)\n{\n\tnodeid_t target = SB_PingTree_FindIp(SB_Netaddr2Ipaddr(addr));\n\n\tif (target == INVALID_NODE) {\n\t\tCom_Printf(\"No route found to given host\\n\");\n\t}\n\telse {\n\t\tnodeid_t current = target;\n\n\t\tCom_Printf(\"Shortest path length: %d ms\\nRoute: \\n\", ping_nodes[current].dist);\n\t\twhile (current != startnode_id && current != INVALID_NODE) {\n\t\t\tbyte *ip = ping_nodes[current].ipaddr.data;\n\t\t\tCom_Printf(\"%4d ms  %d.%d.%d.%d:%d\\n\", ping_nodes[current].dist, \n\t\t\t\tip[0], ip[1], ip[2], ip[3],\tntohs(ping_nodes[current].proxport));\n\t\t\tcurrent = ping_nodes[current].prev;\n\t\t}\n\t\tCom_Printf(\"%4d ms  localhost (your machine)\\n\", 0);\n\t}\n}\n\nint SB_PingTree_GetPathLen(const netadr_t *addr)\n{\n\tnodeid_t target = SB_PingTree_FindIp(SB_Netaddr2Ipaddr(addr));\n\n\tif (target == INVALID_NODE || ping_nodes[target].prev == INVALID_NODE) {\n\t\treturn -1;\n\t}\n\telse if (ping_nodes[target].prev == startnode_id) {\n\t\treturn 0;\n\t}\n\telse {\n\t\tnodeid_t current = ping_nodes[target].prev;\n\t\tint proxies = 0;\n\n\t\twhile (current != startnode_id && current != INVALID_NODE) {\n\t\t\tproxies++;\n\t\t\tcurrent = ping_nodes[current].prev;\n\t\t}\n\n\t\treturn proxies;\n\t}\n}\n\n/// Connects to given QW server using the best available route\nvoid SB_PingTree_ConnectBestPath(const netadr_t *addr)\n{\n\textern cvar_t cl_proxyaddr;\n\tnodeid_t target = SB_PingTree_FindIp(SB_Netaddr2Ipaddr(addr));\n\n\tif (target == INVALID_NODE || ping_nodes[target].prev == INVALID_NODE) {\n\t\tCom_Printf(\"No route found, trying to connect directly...\\n\");\n\t\tCvar_Set(&cl_proxyaddr, \"\");\n\t}\n\telse if (ping_nodes[target].prev == startnode_id) {\n\t\tCom_Printf(\"Direct route is the best route, connecting directly...\\n\");\n\t\tCvar_Set(&cl_proxyaddr, \"\");\n\t}\n\telse {\n\t\tchar proxylist_buf[32*MAX_NONLEAVES] = \"\";\n\t\tnodeid_t current = ping_nodes[target].prev;\n\t\tint proxies = 0;\n\n\t\twhile (current != startnode_id && current != INVALID_NODE) {\n\t\t\tbyte *ip = ping_nodes[current].ipaddr.data;\n\t\t\tchar newval[2048]; /* va() used 2048b buffer..*/\n\n\t\t\tsnprintf(&newval[0], sizeof(newval), \"%d.%d.%d.%d:%d%s%s\", (int) ip[0], (int) ip[1], (int) ip[2],\n\t\t\t\t(int) ip[3], (int) ntohs(ping_nodes[current].proxport), *proxylist_buf ? \"@\" : \"\", proxylist_buf);\n\t\t\tstrlcpy(proxylist_buf, newval, 32*MAX_NONLEAVES);\n\n\t\t\tproxies++;\n\t\t\tcurrent = ping_nodes[current].prev;\n\t\t}\n\t\t\n\t\tCom_Printf(\"Connecting using %d %s with best ping %d ms\\n\",\n\t\t\tproxies, ((proxies == 1) ? \"proxy\" : \"proxies\"), ping_nodes[target].dist);\n\t\tCvar_Set(&cl_proxyaddr, proxylist_buf);\n\t}\n\n\t/* FIXME: Create a Cbuf_AddTextFmt? */\n\tCbuf_AddText(\"connect \");\n\tCbuf_AddText(NET_AdrToString(*addr));\n\tCbuf_AddText(\"\\n\");\n}\n\nint SB_Proxylist_Unserialize(FILE *f)\n{\n\tint version, count = 0;\n\n\tif (fread(&version, sizeof(int), 1, f) != 1)\n\t\treturn -1;\n\tif (version != PROXY_SERIALIZE_FILE_VERSION)\n\t\treturn -1;\n\n\twhile (!ferror(f) && !feof(f)) {\n\t\tnetadr_t proxy;\n\t\tsize_t buflen;\n\t\tbyte buf[PROXY_REPLY_BUFFER_SIZE];\n\t\tnodeid_t id;\n\n\t\tif (fread(&proxy, sizeof(netadr_t), 1, f) != 1)\n\t\t\treturn -3;\n\n\t\tif (proxy.type == NA_INVALID)\n\t\t\tbreak;\n\n\t\tif (fread(&buflen, sizeof(size_t), 1, f) != 1)\n\t\t\treturn -3;\n\t\tif (buflen > PROXY_REPLY_BUFFER_SIZE)\n\t\t\treturn -3;\n\t\tif (fread(buf, buflen, 1, f) != 1)\n\t\t\treturn -3;\n\n\t\tid = SB_PingTree_FindIp(SB_Netaddr2Ipaddr(&proxy));\n\t\tif (id == INVALID_NODE)\n\t\t\treturn -3;\n\n\t\tping_nodes[id].nlist_start = ping_neighbours_count;\n\t\tSB_Proxy_ParseReply(buf, buflen, SB_PingTree_AddProxyPing);\n\t\tping_nodes[id].nlist_end = ping_neighbours_count;\n\n\t\tcount++;\n\t}\n\n\treturn count;\n}\n\nvoid SB_Proxylist_Unserialize_f(void)\n{\n\tchar filename[MAX_OSPATH] = {0};\n\tFILE *f;\n\tint err;\n\t\n\tsnprintf(&filename[0], sizeof(filename), \"%s/%s\", com_homedir, \"proxies_data\");\n\n\tif (!(f\t= fopen(filename, \"rb\"))) {\n\t\tCom_Printf(\"Couldn't read %s.\\n\", filename);\n\t\treturn;\n\t}\n\n\tbuilding_pingtree = true;\n\tSB_PingTree_Phase1();\n\terr = SB_Proxylist_Unserialize(f);\n\tif (err > 0) {\n\t\tCom_Printf(\"Successfully read %d proxies\\n\", err);\n\t\tSB_PingTree_Dijkstra();\n\t\tSB_PingTree_UpdateServerList();\n\t\tpingtree_built = true;\n\t}\n\telse if (err == -1) {\n\t\tCom_Printf(\"Format didn't match\\n\");\n\t}\n\telse if (err == -3) {\n\t\tCom_Printf(\"Corrupted data\\n\");\n\t}\n\telse { // err == 0\n\t\tCom_Printf(\"No proxies read\\n\");\n\t}\n\tbuilding_pingtree = false;\n\n\tfclose(f);\n}\n\nqbool SB_PingTree_IsBuilding(void)\n{\n\treturn building_pingtree;\n}\n\nvoid SB_PingTree_Init(void)\n{\n\tSys_SemInit(&phase2thread_lock, 1, 1);\n}\n\nvoid SB_PingTree_Shutdown(void)\n{\n\tSys_SemWait(&phase2thread_lock);\n\tSys_SemDestroy(&phase2thread_lock);\n}\n"
  },
  {
    "path": "src/EX_browser_ping.c",
    "content": "/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: EX_browser_ping.c,v 1.40 2007-10-27 08:51:12 dkure Exp $\n*/\n\n#ifdef _WIN32\n\n#include <winsock2.h>\n#include \"quakedef.h\"\n\n#else\n\n#include \"quakedef.h\"\n#include <sys/time.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#include <sys/param.h>\n#include <sys/ioctl.h>\n#include <sys/uio.h>\n#include <arpa/inet.h>\n#include <errno.h>\n#include <unistd.h>\n#include <semaphore.h>\n\n#define SO_DONTLINGER 0 \t \n#define GetCurrentProcessId getpid\n\n#define ioctlsocket ioctl\n\n#endif // _WIN32\n\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"EX_browser.h\"\n\n// =============================================================================\n//  Constants\n// =============================================================================\n\n#define ICMP_ECHO       8\n#define ICMP_ECHOREPLY  0 \t \n  \t \n#define ICMP_MIN 8 // minimum 8 byte icmp packet (just header) \t \n\n#define STATUS_FAILED    0xFFFF\n#define DEF_PACKET_SIZE  0\n#define MAX_PACKET       1024\n\n#define PING_PACKET_DATA \"\\xff\\xff\\xff\\xffk\\n\"\n\n// =============================================================================\n//  Local Types\n// =============================================================================\n\n/* The IP header */\ntypedef struct IP_header_s\n{\n    unsigned int   h_len:4;        // length of the header\n    unsigned int   version:4;      // Version of IP\n    unsigned char  tos;            // Type of service\n    unsigned short total_len;      // total length of the packet\n    unsigned short ident;          // unique identifier\n    unsigned short frag_and_flags; // flags\n    unsigned char  ttl; \n    unsigned char  proto;          // protocol (TCP, UDP etc)\n    unsigned short checksum;       // IP checksum\n\n    unsigned int sourceIP;\n    unsigned int destIP;\n} IP_header_t;\n \ntypedef struct ICMP_header_s\n{\n    byte i_type;\n    byte i_code; /* type sub code */\n    unsigned short i_cksum;\n    unsigned short i_id;\n    unsigned short i_seq;\n    /* This is not the std header, but we reserve space for our needs */\n    double timestamp;\n    u_int id;   // ip\n    int index;  // server num\n    int phase;  // ping phase\n    int randomizer;\n} ICMP_header_t; \n\n/* Packets */\ntypedef union IP_Packet_u {\n\tIP_header_t hdr;\n\tbyte data[MAX_PACKET];\n} IP_packet_t;\n\ntypedef union ICMP_Packet_u {\n\tICMP_header_t hdr;\n\tbyte data[MAX_PACKET];\n} ICMP_packet_t;\n\n/* Struct for storing ping reponses */\ntypedef struct pinghost_s\n{\n    int ip;\n    unsigned short port;\n    int recv, send;\n    int phase;\n\tdouble ping;\n    double stime[6];\n} pinghost_t;\n\ntypedef struct {\n\tpinghost_t *hosts;\n\tint nelms;\n} pinghost_list_t;\n\n// =============================================================================\n//  Function Prototypes\n// =============================================================================\n\n// =============================================================================\n//  Global variables\n// =============================================================================\nqbool useNewPing = false; // New Ping = UDP QW Packet multithreaded ping\n\nsocket_t sock;\nsocket_t ping_sock;\n\n/* Used for thread syncronisation */\nstatic qbool ping_finished = false;\nstatic sem_t ping_semaphore;\n\n// =============================================================================\n//  Local Functions\n// =============================================================================\n/**\n * Given a string of the form \"ip:port\", gets the addr, port in integer format\n */\nstatic int ParseServerIp(char *server_port, int *addr, unsigned short *port) \n{\n\tchar server_ip[50];\n\tchar *port_divide;\n\n\tstrlcpy (server_ip, server_port, sizeof(server_ip));\n\n\t/* Break the ip at the port */\n\tif ((port_divide = strchr(server_ip, ':')))\n\t\t*port_divide = 0;\n\n\tif (addr) {\n\t\t*addr = inet_addr(server_ip);\n\t\tif (*addr == INADDR_NONE)\n\t\t\treturn 1;\n\t}\n\n\tif (port) \n\t{\n\t\tif (port_divide != NULL)\n\t\t\t*port = (unsigned short) Q_atoi(port_divide+1);\n\t\telse\n\t\t\t*port = 27500;\n\t}\n\n\treturn 0;\n}\n\n/**\n * Given a servs list, parse and create a pinghost_t list from it\n */\nstatic pinghost_t *ParseServerList(server_data *servs[], int servsn, int *host_nelms) \n{\n\tpinghost_t *phosts;\n\tint nelms;\n\tint i, j;\n\n\tphosts = (pinghost_t *)Q_malloc(sizeof(pinghost_t) * servsn);\n\tnelms = 0;\n\tfor (i=0; i < servsn; i++) \n\t{\n\t\tint addr;\n\t\tunsigned short port;\n\n\t\tif (ParseServerIp(servs[i]->display.ip, &addr, &port)) \n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Make sure it is unique */\n\t\tfor (j = 0; j < nelms; j++) \n\t\t{\n\t\t\tif (phosts[j].ip == addr && phosts[j].port == port) \n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (j == nelms) \n\t\t{\n\t\t\tphosts[nelms].recv = 0;\n\t\t\tphosts[nelms].send = 0;\n\t\t\tphosts[nelms].ping = 0;\n\t\t\tphosts[nelms].ip = addr;\n\t\t\tphosts[nelms].port = port;\n\t\t\tnelms++;\n\t\t}\n\t}\n\n\t*host_nelms = nelms;\n\treturn phosts;\n}\n\n/**\n * Given a ping host array takes the average ping and fills the display for\n * the servs array\n */\nstatic void FillServerListPings(server_data *servs[], int servsn, \n\t\t\t\t\t\t\tpinghost_t *phosts, int host_nelms) {\n\tint i, j;\n\n\tfor (i = 0; i < host_nelms; i++) {\n\t\tint ping;\n\t\tint addr;\n\t\tunsigned short port;\n\n\t\t/* Take the average of the recieved pings */\n\t\tif (phosts[i].recv > 0)\n\t\t\tping = (int)((phosts[i].ping / phosts[i].recv) * 1000);\n\t\telse\n\t\t\tping = -1;\n\n\t\t/* Find the server from the host */\n\t\tfor (j = 0; j < servsn; j++) \n\t\t{\n\t\t\tif (ParseServerIp(servs[j]->display.ip, &addr, &port)) \n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (addr == phosts[i].ip && port == phosts[i].port) \n\t\t\t{\n\t\t\t\tSetPing(servs[j], ping); // 10\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Decode an IP packet and return the underlying ICMP header\n */\nstatic ICMP_header_t *IP_DecodePacket(IP_packet_t *ip_packet, int bytes,\n\t\t\t\t\t\t\t\tstruct sockaddr_in *from, u_int ip)\n{\n    ICMP_packet_t *icmp_packet;\n    unsigned short iphdrlen;\n\n    iphdrlen = ip_packet->hdr.h_len * 4 ; // number of 32-bit words *4 = bytes\n\n    if (bytes  < iphdrlen + ICMP_MIN)\n    {\n        return NULL;\n    }\n\n    icmp_packet = (ICMP_packet_t *)(ip_packet->data + iphdrlen);\n\n    if (icmp_packet->hdr.i_type != ICMP_ECHOREPLY)\n\t{\n        return NULL;\n    }\n    if (icmp_packet->hdr.i_id != (unsigned short)GetCurrentProcessId())\n\t{\n        return NULL;\n    }\n\n    return &icmp_packet->hdr;\n} \n\n/** \n * Decode an ICMP packet and calculate the total return trip time (RTT) is ms\n */\nstatic int ICMP_GetEchoResponseTime(ICMP_header_t *icmp_hdr)\n{\n    return 1 + (int)((Sys_DoubleTime() - icmp_hdr->timestamp)*1000);\n}\n\n/**\n * Calculate the ICMP checksum which includes the header and data\n */\nstatic unsigned short ICMP_Checksum(ICMP_packet_t *packet, int size)\n{\n  unsigned int cksum=0;\n  unsigned short *buffer = (unsigned short *)packet->data;\n\n\twhile(size >1)\n\t{\n    cksum+=*buffer++;\n    size -=sizeof(unsigned short);\n  }\n  \n\tif (size)\n\t{\n    cksum += *(byte *)buffer;\n  }\n\n  cksum = (cksum >> 16) + (cksum & 0xffff);\n  cksum += (cksum >>16);\n  return (unsigned short)(~cksum);\n}\n\n\n/**\n * Helper function to fill in various stuff in our ICMP request.\n */\nstatic void ICMP_FillData(ICMP_packet_t *packet, int datasize)\n{\n  packet->hdr.i_type = ICMP_ECHO;\n  packet->hdr.i_code = 0;\n  packet->hdr.i_id = (unsigned short)GetCurrentProcessId();\n  packet->hdr.i_cksum = 0;\n  packet->hdr.i_seq = 0;\n  \n  //\n  // Place some junk in the buffer.\n  //\n  memset(packet->data + sizeof(packet->hdr), 'E', datasize);\n}\n\n// =============================================================================\n//  Global Functions\n// =============================================================================\nvoid SB_RootInit(void)\n{\n    useNewPing = true;\n} \n\n/**\n * Pings a single host up to count attempts, each attemp we send the request\n * and wait for the reply. The total return trip time (RTT) in ms is returned.\n */\nint oldPingHost(char *host_to_ping, int count)\n{\n    struct sockaddr_in dest, from;\n    int fromlen = sizeof(from);\n    int bread, datasize;\n    ICMP_packet_t icmp_packet;\n\n\tIP_packet_t   ip_recv_packet;\n\tICMP_header_t *icmp_recv_header;\n\n    int attempts, rtt;\n    struct timeval timeout;\n    fd_set fd_set_struct;\n\n    unsigned int addr=0;\n    unsigned short seq_no = 0;\n\n    if (sock < 0)\n        return 0;\n\n    addr = inet_addr(host_to_ping);\n    if (addr == INADDR_NONE)\n        return 0;\n\n    memset(&dest, 0, sizeof(dest));\n    dest.sin_addr.s_addr = addr;\n    dest.sin_family = AF_INET;\n\n    memset(icmp_packet.data, 0, sizeof(icmp_packet.data));\n    datasize = DEF_PACKET_SIZE;\n    datasize += sizeof(icmp_packet.hdr);  \n    ICMP_FillData(&icmp_packet, datasize);\n\n    attempts = 0;\n\trtt = 0;\n    while (attempts < count)\n    {\n        int bwrote;\n\t\tint ret;\n\n        attempts++;\n    \n\t\t/* Prepare the packet */\n        icmp_packet.hdr.i_cksum = 0;\n        icmp_packet.hdr.timestamp = Sys_DoubleTime();\n        icmp_packet.hdr.id = addr;\n\n        icmp_packet.hdr.i_seq = seq_no++;\n        icmp_packet.hdr.i_cksum = ICMP_Checksum(&icmp_packet, datasize);\n\n\t\t/* Prepare for response */\n        FD_ZERO(&fd_set_struct);\n        FD_SET(sock, &fd_set_struct);\n        timeout.tv_sec = 0;\n        timeout.tv_usec = sb_pingtimeout.value * 1000;\n\n        ret = select(sock+1, NULL, (fd_set *) &fd_set_struct, NULL, &timeout);\n        if (ret <= 0)\n            continue;\n\n\t\t/* Send ping request */\n        bwrote = sendto(sock, (char *) icmp_packet.data,datasize,0,\n\t\t\t\t(struct sockaddr*)&dest, sizeof(dest));\n        if (bwrote <= 0 || bwrote < datasize)\n            continue;\n\n\t\t/* Wait for response */\n        FD_ZERO(&fd_set_struct);\n        FD_SET(sock, &fd_set_struct);\n\n        ret = select(sock+1, (fd_set *) &fd_set_struct, NULL, NULL, &timeout);\n\n        if (ret <= 0)\n            continue;\n\n        bread = recvfrom(sock, (char *) ip_recv_packet.data, MAX_PACKET, 0,\n\t\t\t\t(struct sockaddr*)&from, (socklen_t *)&fromlen);\n        if (bread <= 0)\n            continue;\n\n\t\t/* Look for the RTT */\n\t\ticmp_recv_header = IP_DecodePacket(&ip_recv_packet, bread, &from, addr);\n\t\tif (icmp_recv_header) {\n\t\t\tif (!(rtt = ICMP_GetEchoResponseTime(icmp_recv_header))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n        break;\n    }\n\n    return rtt;\n} \n\n/**\n * Ping multiple hosts listed by servs, ping each host count times\n */\nint oldPingHosts(server_data *servs[], int servsn, int count)\n{\n    double interval;\n    double lastsenttime;\n    int ping_number;\n    int randomizer;\n\n\tpinghost_list_t host_list;\n    struct sockaddr_in dest,from;\n    int bread,datasize;\n    int fromlen = sizeof(from);\n    ICMP_packet_t icmp_packet;\n\tIP_packet_t   ip_packet;\n\n    int success;\n    struct timeval timeout;\n    fd_set fd_set_struct;\n\n    unsigned int addr=0;\n    unsigned short seq_no = 0;\n\n    if (sock < 0)\n        return 0;\n\t\n    datasize = DEF_PACKET_SIZE;\n    datasize += sizeof(icmp_packet.hdr);\n\n\t// FIXME: The datasize only sizeof(IcmpHeader) here so fill_icmp_data won't set any of the data \"to junk\", it will all be 0.\n    memset(icmp_packet.data, 0, MAX_PACKET);\n    ICMP_FillData(&icmp_packet, datasize);\n\n    success = 0;\n\thost_list.hosts = ParseServerList(servs, servsn, &host_list.nelms);\n\n    interval = (1000.0 / sb_pingspersec.value) / 1000;\n    lastsenttime = Sys_DoubleTime() - interval;\n    timeout.tv_sec = 0;\n    timeout.tv_usec = (long)(interval * 1000.0 * 1000.0);\n    ping_number = 0;\n    randomizer = (int)(Sys_DoubleTime() * 10);\n\n\t// Ping all addresses in the host list we just created.\n    while (!abort_ping)\n    {\n        int bwrote;\n        double time = Sys_DoubleTime();\n\n        if (time > (lastsenttime + 1.2 * (sb_pingtimeout.value / 1000)))\n\t\t{\n            // No answers, no pings - finish.\n            break;\n\t\t}\n\n\t\t// Send a ping request to the current host.\n        if ((time > (lastsenttime + interval)) \n\t\t\t\t&& (ping_number < (host_list.nelms * sb_pings.value)))\n        {\n            // Send next ping.\n            pinghost_t * host = &host_list.hosts[ping_number % host_list.nelms];\n\n            if (host->ping >= 0)\n            {\n                icmp_packet.hdr.i_cksum = 0;\n                icmp_packet.hdr.timestamp = time;\n\n                icmp_packet.hdr.id = host->ip;\n                icmp_packet.hdr.index = ping_number % host_list.nelms;\n                icmp_packet.hdr.phase = ping_number;\n                icmp_packet.hdr.randomizer = randomizer;\n\n                icmp_packet.hdr.i_seq = seq_no++;\n                icmp_packet.hdr.i_cksum = ICMP_Checksum(&icmp_packet, datasize);\n\n                memset(&dest, 0, sizeof(dest));\n                dest.sin_addr.s_addr = host->ip;\n                dest.sin_family = AF_INET;\n\n                bwrote = sendto(sock, (char *) icmp_packet.data, datasize, 0,\n\t\t\t\t\t\t\t\t(struct sockaddr*)&dest, sizeof(dest));\n\n                if (bwrote <= 0 || bwrote < datasize)\n                    host->ping = -1;\n\n                lastsenttime = time;\n            }\n\n            ping_number++;\n            ping_pos = min(1, ping_number \n\t\t\t\t\t/ (double)(host_list.nelms * sb_pings.value));\n        }\n\n\t\t// Wait for an answer.\n        while (!abort_ping)\n        {\n\t\t\tint myvar;\n            ICMP_header_t *icmp_answer;\n\n            FD_ZERO(&fd_set_struct);\n            FD_SET(sock, &fd_set_struct);\n\n\t\t\tmyvar = select(sock+1, (fd_set *) &fd_set_struct, NULL, \n\t\t\t\t\t\t\tNULL, &timeout);\n\n\t\t\tif (myvar <= 0)\n                break;\n\n            // there's an answer - get it!\n            bread = recvfrom(sock, (char *) ip_packet.data, sizeof(ip_packet.data), 0,\n\t\t\t\t\t\t\t(struct sockaddr *)&from, (socklen_t *)&fromlen);\n           \n\t\t\tif (bread <= 0)\n                continue;\n\n            icmp_answer = IP_DecodePacket(&ip_packet, bread, &from, addr);\n\n\t\t\t// Make sure the reply is ok.\n            if (icmp_answer && (randomizer == icmp_answer->randomizer))\n            {\n                int index;\n                int fromhost;\n\n                fromhost = icmp_answer->id;\n                index    = icmp_answer->index;\n                if ((host_list.hosts[index].ip == fromhost)\n\t\t\t\t\t\t&& (host_list.hosts[index].ping >= 0))\n                {\n                    host_list.hosts[index].ping \n\t\t\t\t\t\t\t\t= (host_list.hosts[index].ping \n\t\t\t\t\t\t\t\t\t* host_list.hosts[index].phase\n                                    + (Sys_DoubleTime()-icmp_answer->timestamp))\n                                    / (host_list.hosts[index].phase + 1);\n                    host_list.hosts[index].phase++;\n\n\t\t\t\t\t// Remove averaging that would occur in FillServerListPings\n\t\t\t\t\thost_list.hosts[index].recv = 1;\n                }\n            }\n        }\n    }\n\n    // update pings in our servz\n\tif (!abort_ping) {\n\t\tFillServerListPings(servs, servsn, host_list.hosts, host_list.nelms);\n\t}\n\n    Q_free(host_list.hosts);\n\n    return success;\n}\n\n/**\n * Pings multiple hosts in parrallel, each host is pingged count times\n */\nvoid PingSendParrallelMultiHosts(pinghost_t *phosts, int nelms, int count) {\n\tstruct sockaddr_in to;\n\tint interval; \n\tint i, j;\n\tint ret;\n\ttimerresolution_session_t timer_session;\n\t\n\tmemset(&timer_session, 0, sizeof(timer_session));\n\n\tcount = bound(1, count, 6);\n\tinterval = 1000.0 / sb_pingspersec.integer;\n\n\tSys_TimerResolution_InitSession(&timer_session);\n\tSys_TimerResolution_RequestMinimum(&timer_session);\n\n\tfor (i = 0; i < count && !abort_ping; i++) {\n\t\tfor (j = 0; j < nelms && !abort_ping; j++) {\n\t\t\tchar *packet = PING_PACKET_DATA;\n\n\t\t\tping_pos = min(1, (j / (double)(nelms * count))) + (i / (double)count);\n\n\t\t\tto.sin_family = AF_INET;\n\t\t\tto.sin_port = htons(phosts[j].port);\n\t\t\tto.sin_addr.s_addr = phosts[j].ip;\n\n\t\t\tret = sendto(ping_sock, packet, strlen(packet), 0, (struct sockaddr *)&to, sizeof(struct sockaddr));\n\n\t\t\tif (ret == -1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tphosts[j].stime[phosts[j].send] = Sys_DoubleTime();\n\t\t\tphosts[j].send++;\n\n\t\t\tSys_MSleep(interval);\n\n\t\t}\n\t}\n\n\tSys_TimerResolution_Clear(&timer_session);\n}\n\n/**\n * Thread entry point for parrallel recving of ping responses\n */\nint PingRecvProc(void *lpParameter)\n{\n\tsocklen_t inaddrlen;\n\tstruct sockaddr_in from;\n\tfd_set fd;\n\tint k, ret;\n\tstruct timeval timeout;\n\tpinghost_list_t *host_list = (pinghost_list_t *)lpParameter;\n\n\twhile (!ping_finished && !abort_ping) {\n\t\tFD_ZERO(&fd);\n\t\tFD_SET(ping_sock, &fd);\n\t\ttimeout.tv_sec = 0;\n\t\ttimeout.tv_usec = 100 * 1000;\n\n\t\tret = select(ping_sock+1, &fd, NULL, NULL, &timeout);\n\t\tif (ret <= 0) // error or timeout\n\t\t\tcontinue;\n\n\t\twhile (1) {\n\t\t\tchar buf[16];\n\n\t\t\tinaddrlen = sizeof(struct sockaddr_in);\n\t\t\tret = recvfrom(ping_sock, buf, sizeof(buf), 0, (struct sockaddr *)&from, &inaddrlen);\n\t\t\tif (ret <= 0) // socket is asynch, so most likely this means there is no data to read\n\t\t\t\tbreak;\n\t\t\tif (buf[0] != 'l') // not A2A_ACK\n\t\t\t\tcontinue;\n\t\n\t\t\tfor (k = 0; k < host_list->nelms; k++) {\n\t\t\t\tif (host_list->hosts[k].ip == (int) from.sin_addr.s_addr &&\n\t\t\t\t    host_list->hosts[k].port == ntohs(from.sin_port)) {\n\t\t\t\t\thost_list->hosts[k].ping += Sys_DoubleTime() \n\t\t\t\t\t\t\t\t\t- host_list->hosts[k]\n\t\t\t\t\t\t\t\t\t\t\t.stime[host_list->hosts[k].recv];\n\t\t\t\t\thost_list->hosts[k].recv++;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tSys_SemPost(&ping_semaphore);\n\n\treturn 0;\n}\n\n/**\n * Ping a single host count times, returns the average of the responses\n */\nint PingHost(char *host_to_ping, unsigned short port, int count, int time_out)\n{\n\tsocket_t sock;\n\tint i, ret, pings;\n\tstruct sockaddr_in addr_to, addr_from;\n\tstruct timeval timeout;\n\tfd_set fd;\n\tchar *packet = \"\\xff\\xff\\xff\\xffk\\n\", buf[16]; // A2A_PING, should return A2A_ACK\n\tsocklen_t inaddrlen;\n\tdouble ping, t1;\n\n\taddr_to.sin_addr.s_addr = inet_addr(host_to_ping);\n\tif (addr_to.sin_addr.s_addr == INADDR_NONE)\n\t\treturn 0;\n\taddr_to.sin_family = AF_INET;\n\taddr_to.sin_port = htons(port);\n\n\tsock = UDP_OpenSocket(PORT_ANY);\n\tpings = ping = 0;\n\tfor (i = 0; i < count; i++) {\n\t\tret = sendto(sock, packet, strlen(packet), 0, (struct sockaddr *)&addr_to, sizeof(struct sockaddr));\n\t\tif (ret == -1) // failure, try again\n\t\t\tcontinue;\n\t\tt1 = Sys_DoubleTime();\n\n_select:\n\t\tFD_ZERO(&fd);\n\t\tFD_SET(sock, &fd);\n\t\ttimeout.tv_sec = 0;\n\t\ttimeout.tv_usec = 1000 * time_out;\n\t\tret = select(sock+1, &fd, NULL, NULL, &timeout);\n\t\tif (ret <= 0) // timed out or error\n\t\t\tcontinue;\n\n\t\tinaddrlen = sizeof(struct sockaddr_in);\n\t\tret = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&addr_from, &inaddrlen);\n\t\tif (ret == -1) // failure, try again\n\t\t\tcontinue;\n\t\tif (addr_from.sin_addr.s_addr != addr_to.sin_addr.s_addr) // martian, discard and see if a valid response came in after it\n\t\t\tgoto _select;\n\t\tif (buf[0] != 'l') // not A2A_ACK, discard and see if a valid response came in after it\n\t\t\tgoto _select;\n\n\t\tping += Sys_DoubleTime() - t1;\n\t\tpings++;\n\t}\n\n\tclosesocket(sock);\n\treturn pings ? (int)((ping * 1000) / pings) : 0;\n}\n\n/**\n * Send several ping requests at once, start another thread to recieve\n * the responses.\n * FIXME: error handling is in rly bad shape\n */\nint PingHosts(server_data *servs[], int servsn, int count)\n{\n\tu_int arg;\n\tpinghost_list_t host_list;\n\n\thost_list.hosts = ParseServerList(servs, servsn, &host_list.nelms);\n\tping_finished = false;\n\n\tping_sock = UDP_OpenSocket(PORT_ANY);\n\n#ifndef _WIN32\n\tif ((fcntl (ping_sock, F_SETFL, O_NONBLOCK)) == -1) { // O'Rly?! @@@\n\t\tCom_Printf (\"TCP_OpenStream: fcntl: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\t//closesocket(ping_sock);\n\t}\n#endif\n\n\targ = 1;\n\tif (ioctlsocket (ping_sock, FIONBIO, (u_long *)&arg) == -1) { // make asynchronous\n\t\tCom_Printf (\"PingHosts: ioctl: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\t//closesocket(newsocket);\n\t}\n\n\tSys_SemInit(&ping_semaphore, 0, 1);\n\n\tif (Sys_CreateDetachedThread(PingRecvProc, (void *)&host_list) < 0) {\n\t\tCom_Printf(\"Failed to create Ping receiver thread\\n\");\n\t}\n\n\tPingSendParrallelMultiHosts(host_list.hosts, host_list.nelms, count);\n\n\tSys_MSleep(500); // Wait for slow pings\n\n\tping_finished = true; // let thread know we are done\n\tSys_SemWait(&ping_semaphore);\n\tSys_SemDestroy(&ping_semaphore);\n\tclosesocket(ping_sock);\n\n\tif (!abort_ping) {\n\t\tFillServerListPings(servs, servsn, host_list.hosts, host_list.nelms);\n\t}\n\n\tQ_free(host_list.hosts);\n\n\treturn 1;\n}\n\n//\n// ----------------------------------------------\n//  connection test\n//  netgraph + ping stats\n//\n//\n\nint sb_test_starttime;\nint sb_test_incoming_sequence, sb_test_outgoing_sequence;\nint sb_test_packet_latency[NET_TIMINGS];\ndouble sent_time[NET_TIMINGS];\ndouble received_time[NET_TIMINGS];\nint    sequence_latency[NET_TIMINGS];\nunsigned int sb_test_addr;\nint sb_test_pl;\nint sb_test_min, sb_test_max, sb_test_avg, sb_test_dev;\ndouble last_stats_time;\n\nserver_data *sb_test_server = NULL;\n\nvoid SB_Test_GetPackets(void)\n{\n    struct sockaddr_in dest,from;\n    int bread;\n    int fromlen = sizeof(from);\n    IP_packet_t ip_packet;\n//    int bwrote;\n\n//    u_long arg;\n//    int arg2, success;\n//    struct timeval timeout;\n//    fd_set fd_set_struct;\n\n    unsigned int addr=0;\n//    unsigned short seq_no = 0;\n\n    if (sock < 0)\n        return;\n\n    memset(&dest, 0, sizeof(dest));\n\n    addr = sb_test_addr;\n\n    if (addr == INADDR_NONE)\n        return;\n\n    dest.sin_addr.s_addr = addr;\n    dest.sin_family = AF_INET;\n\n    while (1)\n    {\n//        int success;\n        ICMP_header_t *icmp_answer;\n        int seq;\n\n        bread = recvfrom(sock, (char *) ip_packet.data, sizeof(ip_packet.data), 0,\n\t\t\t\t\t\t(struct sockaddr*)&from, (socklen_t *) &fromlen);\n        if (bread <= 0)\n            break;\n\n        icmp_answer = IP_DecodePacket(&ip_packet, bread, &from, addr);\n\n        if (!icmp_answer || icmp_answer->phase != sb_test_starttime)\n            continue;\n\n        seq = icmp_answer->i_seq;\n        if (seq > sb_test_incoming_sequence)\n        {\n            received_time[seq%NET_TIMINGS] = cls.realtime;\n            sequence_latency[seq%NET_TIMINGS] = sb_test_outgoing_sequence - seq;\n            sb_test_incoming_sequence = seq;\n        }\n    }\n\n}\n\nvoid SB_Test_SendPacket(void)\n{\n    struct sockaddr_in dest/*,from*/;\n//    int bread,\n    int datasize;\n//   int fromlen = sizeof(from);\n\tICMP_packet_t icmp_packet;\n//    int ret;\n\n    //u_long arg;\n    //int arg2, success;\n    //struct timeval timeout;\n    //fd_set fd_set_struct;\n\n    unsigned int addr=0;\n//    unsigned short seq_no = 0;\n\n    sent_time[sb_test_outgoing_sequence%NET_TIMINGS] = cls.realtime;\n    received_time[sb_test_outgoing_sequence%NET_TIMINGS] = -1;  // no answer yet\n\n    if (sock < 0)\n        return ;\n\n    memset(&dest, 0, sizeof(dest));\n\n    addr = sb_test_addr;\n\n    if (addr == INADDR_NONE)\n        return;\n\n    dest.sin_addr.s_addr = addr;\n    dest.sin_family = AF_INET;\n    datasize = DEF_PACKET_SIZE;\n    datasize += sizeof(icmp_packet.hdr);  \n\n    memset(icmp_packet.data, 0, MAX_PACKET);\n    ICMP_FillData(&icmp_packet, datasize);\n\n    icmp_packet.hdr.phase = sb_test_starttime;\n\n    icmp_packet.hdr.i_cksum = 0;\n    icmp_packet.hdr.timestamp = cls.realtime;\n    icmp_packet.hdr.id = addr;\n\n    icmp_packet.hdr.i_seq = sb_test_outgoing_sequence++;\n    icmp_packet.hdr.i_cksum = ICMP_Checksum(&icmp_packet, datasize);\n\n    sendto(sock, (char *) icmp_packet.data,datasize,0,(struct sockaddr*)&dest,\n                    sizeof(dest));\n}\n\nvoid SB_Test_CalcNet(void)\n{\n    int i, a;\n\n    extern hud_t * hud_netgraph;\n    cvar_t *netgraph_inframes = HUD_FindVar(hud_netgraph, \"inframes\");\n\n    for (i=0; i < NET_TIMINGS; i++)\n    {\n        if (received_time[i] == -1)\n            sb_test_packet_latency[i] = 9999;   // dropped\n        else\n        {\n            double l;\n            if (netgraph_inframes->value)      // [frames]\n                l = 2*(sequence_latency[i]);\n            else                                // [miliseconds]\n                l = min((received_time[i] - sent_time[i])*1000, 1000);\n\n            sb_test_packet_latency[i] = (int)l;\n        }\n    }\n\n    if (last_stats_time +1 < cls.realtime)\n    {\n        int count;\n        double sum;\n        double avg;\n        int i;\n\n        last_stats_time = cls.realtime;\n\n        // recalculate statistics\n        sb_test_min = 9999999;\n        sb_test_max = 0;\n        sum = 0;\n        count = 0;\n\n        for (i=sb_test_outgoing_sequence-NET_TIMINGS; i < sb_test_incoming_sequence; i++)\n        {\n            int a = i % NET_TIMINGS;\n            double ping;\n\n            if (i < 0)\n                continue;\n\n            if (received_time[a] == -1)\n                continue;\n\n            ping = (received_time[a] - sent_time[a]) * 1000;\n\n            if (ping < sb_test_min)\n                sb_test_min = ping;\n\n            if (ping > sb_test_max)\n                sb_test_max = ping;\n\n            sum += ping;\n            count++;\n        }\n\n        if (count > 0)\n        {\n            avg = (sum / count);\n            sb_test_avg = (int)(avg + 0.5);\n\n            // standard deviation\n            sum = 0;\n            for (i=sb_test_outgoing_sequence-NET_TIMINGS; i < sb_test_incoming_sequence; i++)\n            {\n                int a = i % NET_TIMINGS;\n                double ping;\n\n                if (i < 0)\n                    continue;\n\n                if (received_time[a] == -1)\n                    continue;\n\n                ping = (received_time[a] - sent_time[a]) * 1000;\n\n                sum += (ping - avg) * (ping - avg);\n            }\n            sb_test_dev = (int)(sqrt(sum / count) + 0.5);\n        }\n        else\n            sb_test_min = sb_test_max = sb_test_avg = sb_test_dev = 0;\n\n        // pl\n        sb_test_pl = 0;\n        for (a=0 ; a<NET_TIMINGS ; a++)\n        {\n            i = (sb_test_outgoing_sequence-a) & NET_TIMINGSMASK;\n            if (sb_test_packet_latency[i] == 9999)\n                sb_test_pl++;\n        }\n        sb_test_pl =  sb_test_pl * 100 / NET_TIMINGS;\n    }\n\n    sb_test_min = min(sb_test_min, 999);\n    sb_test_max = min(sb_test_max, 999);\n    sb_test_avg = min(sb_test_avg, 999);\n    sb_test_dev = min(sb_test_dev, 99);\n}\n\nvoid SB_Test_Draw(void)\n{\n    int x, y, w, h;\n\n\textern void R_MQW_NetGraph(int outgoing_sequence, int incoming_sequence, int *packet_latency, int lost, int minping, int avgping, int maxping, int devping, int posx, int posy, int width, int height, int revx, int revy);\n\n    w = 240;\n    h = 112;\n\n    x = (vid.width - w)/2;\n    y = (vid.height - h)/2;\n    x -= 8;\n    y += 76;\n\n    if (vid.height >= 240)\n        y += 20;\n\n    R_MQW_NetGraph(sb_test_outgoing_sequence, sb_test_incoming_sequence,\n               sb_test_packet_latency, sb_test_pl,\n               sb_test_min, sb_test_avg, sb_test_max, sb_test_dev,\n               x, y, -1, -1, 0, 0);\n}\n\nvoid SB_Test_Init(char *address)\n{\n    int i;\n\n    sb_test_incoming_sequence = 0;\n    sb_test_outgoing_sequence = 0;\n\n    sb_test_addr = inet_addr(address);\n\n    for (i=0; i < NET_TIMINGS; i++)\n    {\n        sb_test_packet_latency[i] = 0;\n        received_time[i] = 0;\n        sent_time[i] = 0;\n        sequence_latency[i] = 0;\n    }\n\n    last_stats_time = -999;\n    sb_test_starttime = cls.realtime*1000;\n}\n\nvoid SB_Test_Change(char *address)\n{\n    unsigned int addr = inet_addr(address);\n\n    if (addr != sb_test_addr)\n    {\n        sb_test_incoming_sequence = sb_test_outgoing_sequence;\n        sb_test_addr = inet_addr(address);\n\n        last_stats_time = -999;\n        sb_test_starttime = cls.realtime*1000;\n    }\n}\n\nvoid SB_Test_Frame(void)\n{\n    // receive answers\n    SB_Test_GetPackets();\n\n    // send command\n    SB_Test_SendPacket();\n\n    // update stats\n    SB_Test_CalcNet();\n\n    // draw statistics\n    SB_Test_Draw();\n}\n"
  },
  {
    "path": "src/EX_browser_qtvlist.c",
    "content": "/*\nCopyright (C) 2010       ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"utils.h\"\n#include \"expat.h\"\n#include <curl/curl.h>\n\n#define QTVLIST_CACHE_FILE_DIR \"ezquake/sb/cache\"\n#define QTVLIST_CACHE_FILE \"qtvcache\"\n#define CHARACTERDATA_BUFFERSIZE 256\n#define MAX_QTV_ENTRY_TEXTLEN CHARACTERDATA_BUFFERSIZE\n\ncvar_t sb_qtvlist_url = { \"sb_qtvlist_url\", \"http://qtv.quakeworld.nu/?rss\" };\n\ntypedef struct sb_qtvplayer_s {\n\tstruct sb_qtvplayer_s *next; // next item in the linked list of players\n\n\tchar name[MAX_SCOREBOARDNAME];\n\tchar team[MAX_SCOREBOARDNAME];\n\tint frags;\n\tint ping;\n\tint pl;\n\tint topcolor;\n\tint bottomcolor;\n} sb_qtvplayer_t;\n\ntypedef struct sb_qtventry_s {\n\tstruct sb_qtventry_s *next; // next item in the linked list of entries\n\n\tchar title[MAX_QTV_ENTRY_TEXTLEN];\n\tchar link[MAX_QTV_ENTRY_TEXTLEN];\n\tchar hostname[MAX_QTV_ENTRY_TEXTLEN];\n\tint port;\n\tnetadr_t addr;\n\tint observercount;\n\tsb_qtvplayer_t *players; // first item of a linked list\n} sb_qtventry_t;\n\ntypedef enum {\n\tQTVLIST_INITIAL,\n\tQTVLIST_ITEM,\n\tQTVLIST_PLAYER,\n} sb_qtvlist_parse_position_t;\n\ntypedef enum {\n\tQTVLIST_INIT,\n\tQTVLIST_PROCESSING,\n\tQTVLIST_DOWNLOADING,\n\tQTVLIST_PARSING,\n\tQTVLIST_RESOLVING,\n\tQTVLIST_PRINTING,\n\tQTVLIST_READY\n} sb_qtvlist_status_t;\n\ntypedef struct sb_qtvlist_parse_state_s {\n\tchar chardata_buffer[CHARACTERDATA_BUFFERSIZE];\n\tsb_qtventry_t *list;\n\tsb_qtvlist_parse_position_t position;\n} sb_qtvlist_parse_state_t;\n\ntypedef struct sb_qtvlist_cache_s {\n\tsb_qtventry_t *sb_qtventries;\n\tsb_qtvlist_status_t status;\n} sb_qtvlist_cache_t;\n\nstatic sb_qtvlist_cache_t sb_qtvlist_cache = { NULL, QTVLIST_INIT };\n\nstatic sb_qtventry_t *QTVList_New_Entry(void)\n{\n\tsb_qtventry_t *ret;\n\n\tret = Q_calloc(sizeof (sb_qtventry_t), 1);\n\n\treturn ret;\n}\n\nstatic sb_qtvplayer_t *QTVList_New_Player(void)\n{\n\tsb_qtvplayer_t *ret;\n\n\tret = Q_calloc(sizeof (sb_qtvplayer_t), 1);\n\n\treturn ret;\n}\n\nstatic void QTVList_Parse_StartElement(void *userData, const XML_Char *name,\n\tconst XML_Char **atts)\n{\n\tsb_qtvlist_parse_state_t *sb_qtvparse = (sb_qtvlist_parse_state_t *) userData;\n\n\tif (sb_qtvparse->position == QTVLIST_INITIAL) {\n\t\tif (strcmp(name, \"item\") == 0) {\n\t\t\tsb_qtventry_t *current = sb_qtvparse->list;\n\t\t\tsb_qtventry_t *newentry = QTVList_New_Entry();\n\n\t\t\tnewentry->next = current;\n\t\t\tsb_qtvparse->list = newentry;\n\t\t\tsb_qtvparse->position = QTVLIST_ITEM;\n\t\t}\n\t\telse {\n\t\t}\n\t}\n\telse if (sb_qtvparse->position == QTVLIST_ITEM) {\n\t\tif (strcmp(name, \"player\") == 0) {\n\t\t\tsb_qtvplayer_t *current = sb_qtvparse->list->players;\n\t\t\tsb_qtvplayer_t *newplayer = QTVList_New_Player();\n\n\t\t\tnewplayer->next = current;\n\t\t\tsb_qtvparse->list->players = newplayer;\n\n\t\t\tsb_qtvparse->position = QTVLIST_PLAYER;\n\t\t}\n\t}\n\telse if (sb_qtvparse->position == QTVLIST_PLAYER) {\n\t}\n\telse {\n\t\tSys_Error(\"QTVList_Parse_StartElement(): invalid position\");\n\t}\n}\n\nvoid QTVList_Parse_CharacterData(void *userData, const XML_Char *s, int len)\n{\n\tsb_qtvlist_parse_state_t *sb_qtvparse = (sb_qtvlist_parse_state_t *) userData;\n\n\tsize_t copied;\n\tsize_t n;\n\n\tcopied = strlen(sb_qtvparse->chardata_buffer);\n\tn = 0;\n\n\twhile (copied + 1 < CHARACTERDATA_BUFFERSIZE && n < len) {\n\t\tsb_qtvparse->chardata_buffer[copied++] = s[n++];\n\t}\n\n\tsb_qtvparse->chardata_buffer[copied] = '\\0';\n}\n\nvoid QTVList_Parse_EndElement(void *userData, const XML_Char *name)\n{\n\tsb_qtvlist_parse_state_t *sb_qtvparse = (sb_qtvlist_parse_state_t *) userData;\n\tchar *buf = str_trim(sb_qtvparse->chardata_buffer);\n\n\tif (sb_qtvparse->position == QTVLIST_INITIAL) {\n\t}\n\telse if (sb_qtvparse->position == QTVLIST_ITEM) {\n\t\tif (strcmp(name, \"hostname\") == 0) {\n\t\t\tstrlcpy(sb_qtvparse->list->hostname, buf, MAX_QTV_ENTRY_TEXTLEN);\n\t\t}\n\t\telse if (strcmp(name, \"port\") == 0) {\n\t\t\tsb_qtvparse->list->port = Q_atoi(buf);\n\t\t}\n\t\telse if (strcmp(name, \"observercount\") == 0) {\n\t\t\tsb_qtvparse->list->observercount = Q_atoi(buf);\n\t\t}\n\t\telse if (strcmp(name, \"title\") == 0) {\n\t\t\tstrlcpy(sb_qtvparse->list->title, buf, MAX_QTV_ENTRY_TEXTLEN);\n\t\t}\n\t\telse if (strcmp(name, \"link\") == 0) {\n\t\t\tstrlcpy(sb_qtvparse->list->link, buf, MAX_QTV_ENTRY_TEXTLEN);\n\t\t}\n\t\telse if (strcmp(name, \"item\") == 0) {\n\t\t\tsb_qtvparse->position = QTVLIST_INITIAL; // go up\n\t\t}\n\t\telse if (strcmp(name, \"player\") == 0) {\n\t\t\tsb_qtvparse->position = QTVLIST_PLAYER; // go down\n\t\t}\n\t}\n\telse if (sb_qtvparse->position == QTVLIST_PLAYER) {\n\t\tif (strcmp(name, \"name\") == 0) {\n\t\t\tstrlcpy(sb_qtvparse->list->players->name, buf, MAX_SCOREBOARDNAME);\n\t\t}\n\t\telse if (strcmp(name, \"team\") == 0) {\n\t\t\tstrlcpy(sb_qtvparse->list->players->team, buf, MAX_SCOREBOARDNAME);\n\t\t}\n\t\telse if (strcmp(name, \"frags\") == 0) {\n\t\t\tsb_qtvparse->list->players->frags = Q_atoi(buf);\n\t\t}\n\t\telse if (strcmp(name, \"ping\") == 0) {\n\t\t\tsb_qtvparse->list->players->ping = Q_atoi(buf);\n\t\t}\n\t\telse if (strcmp(name, \"pl\") == 0) {\n\t\t\tsb_qtvparse->list->players->pl = Q_atoi(buf);\n\t\t}\n\t\telse if (strcmp(name, \"topcolor\") == 0) {\n\t\t\tsb_qtvparse->list->players->topcolor = Q_atoi(buf);\n\t\t}\n\t\telse if (strcmp(name, \"bottomcolor\") == 0) {\n\t\t\tsb_qtvparse->list->players->bottomcolor = Q_atoi(buf);\n\t\t}\n\t\telse if (strcmp(name, \"player\") == 0) {\n\t\t\tsb_qtvparse->position = QTVLIST_ITEM;\n\t\t}\n\t}\n\n\tbuf[0] = '\\0';\n}\n\nsb_qtvlist_parse_state_t *QTVList_Parse_Init(void)\n{\n\tsb_qtvlist_parse_state_t *ret = Q_malloc(sizeof (sb_qtvlist_parse_state_t));\n\tret->chardata_buffer[0] = '\\0';\n\tret->list = NULL;\n\tret->position = QTVLIST_INITIAL;\n\n\treturn ret;\n}\n\nvoid QTVList_Entry_Destroy(sb_qtventry_t *entry)\n{\n\twhile (entry->players) {\n\t\tsb_qtvplayer_t *next = entry->players->next;\n\t\tQ_free(entry->players);\n\t\tentry->players = next;\n\t}\n\n\tQ_free(entry);\n}\n\nvoid QTVList_Parse_Destroy(sb_qtvlist_parse_state_t *parse)\n{\n\twhile (parse->list) {\n\t\tsb_qtventry_t *next = parse->list->next;\n\n\t\tQTVList_Entry_Destroy(parse->list);\n\t\tparse->list = next;\n\t}\n}\n\nvoid QTVList_Process_Full_List(vfsfile_t *f, sb_qtvlist_parse_state_t *sb_qtvparse)\n{\n\tXML_Parser parser = NULL;\n\tint len;\n\tenum XML_Status status;\n\tchar buf[4096];\n\tvfserrno_t err;\n\n    // initialize XML parser\n    parser = XML_ParserCreate(NULL);\n\tif (parser == NULL) {\n\t\tCom_Printf(\"Couldn't initialize XML parser\\n\");\n        return;\n\t}\n    XML_SetStartElementHandler(parser, QTVList_Parse_StartElement);\n\tXML_SetCharacterDataHandler(parser, QTVList_Parse_CharacterData);\n\tXML_SetEndElementHandler(parser, QTVList_Parse_EndElement);\n    XML_SetUserData(parser, (void *) sb_qtvparse);\n\n    while ((len = VFS_READ(f, buf, 4096, &err)) > 0)\n    {\n\t\tif ((status = XML_Parse(parser, buf, len, 0)) != XML_STATUS_OK) {\n\t\t\tenum XML_Error err = XML_GetErrorCode(parser);\n\t\t\tCom_Printf(\"XML parser error.\\n%s\\n\", status, XML_ErrorString(err));\n\t\t\tbreak;\n\t\t}\n    }\n\n    XML_ParserFree(parser);\n}\n\nvoid QTVList_Cache_File_Download(void)\n{\n\tCURL *curl;\n\tCURLcode res;\n\tFILE *f;\n\n\tcurl = curl_easy_init();\n\tif (curl) {\n\t\tcurl_easy_setopt(curl, CURLOPT_URL, sb_qtvlist_url.string);\n\t}\n\telse {\n\t\tCom_Printf_State(PRINT_FAIL, \"QTVList_List_f() Can't init cURL\\n\");\n\t\treturn;\n\t}\n\n\tif (!FS_FCreateFile(QTVLIST_CACHE_FILE, &f, QTVLIST_CACHE_FILE_DIR, \"wb+\")) {\n\t\tCom_Printf_State(PRINT_FAIL, \"Can't create QTVList cache file\\n\");\n        return;\n\t}\n\n\tcurl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);\n\tcurl_easy_setopt(curl, CURLOPT_WRITEDATA, f);\n\n\tres = curl_easy_perform(curl);\n    curl_easy_cleanup(curl);\n\tfclose(f);\n\n\tif (res != CURLE_OK) {\n\t\tCom_Printf_State(PRINT_FAIL, \"Couldn't download QTV list\");\n\t\treturn;\n\t}\n}\n\nstatic vfsfile_t *QTVList_Cache_File_Open(char *mode)\n{\n\treturn FS_OpenVFS(QTVLIST_CACHE_FILE_DIR \"/\" QTVLIST_CACHE_FILE, mode, FS_BASE);\n}\n\nstatic void QTVList_Resolve_Hostnames(void)\n{\n\tsb_qtventry_t *cur = sb_qtvlist_cache.sb_qtventries;\n\n\twhile (cur) {\n\t\tchar fulladdr[MAX_QTV_ENTRY_TEXTLEN+6]; /* :xxxxx */\n\n\t\tsnprintf(&fulladdr[0], sizeof(fulladdr), \"%s:%d\", cur->hostname, cur->port);\n\t\tNET_StringToAdr(fulladdr, &cur->addr);\n\t\tcur->addr.type = NA_IP;\n\t\tcur = cur->next;\n\t}\n}\n\nstatic qbool QTVList_Cache_File_Is_Old(void)\n{\n\tchar cache_path[MAX_OSPATH] = {0};\n\tSYSTEMTIME cache_time;\n\tSYSTEMTIME min_time; // minimum required time\n\tint diff;\n\n\tsnprintf(&cache_path[0], sizeof(cache_path), \"%s/%s/%s\", com_basedir, QTVLIST_CACHE_FILE_DIR, QTVLIST_CACHE_FILE);\n\tGetFileLocalTime(cache_path, &cache_time);\n\n\tGetLocalTime(&min_time);\n\n\t// subtract 7 days\n\tif (min_time.wDay > 7) {\n\t\tmin_time.wDay -= 7;\n\t}\n\telse {\n\t\t// approximately 7 days in this case\n\t\t// let's not bother if a month has 28, 29, 30 or 31 days\n\t\tif (min_time.wMonth > 1) {\n\t\t\tmin_time.wMonth -= 1;\n\t\t\tmin_time.wDay += 22;\n\t\t}\n\t\telse {\n\t\t\tmin_time.wYear -= 1;\n\t\t\tmin_time.wMonth = 12;\n\t\t\tmin_time.wDay += 22;\n\t\t}\n\t}\n\n\tdiff = SYSTEMTIMEcmp(&cache_time, &min_time);\n\treturn diff < 0;\n}\n\nvoid QTVList_Refresh_Cache(qbool force_redownload)\n{\n\tchar cache_dir[MAX_OSPATH] = {0};\n\tvfsfile_t *cache_file;\n\tsb_qtvlist_parse_state_t *sb_qtvparse;\n\n\tsnprintf(&cache_dir[0], sizeof(cache_dir), \"%s/\" QTVLIST_CACHE_FILE_DIR, com_basedir);\n\tSys_mkdir(cache_dir);\n\n\tif (force_redownload\n\t\t|| !(cache_file = QTVList_Cache_File_Open(\"rb\"))\n\t\t|| QTVList_Cache_File_Is_Old())\n\t{\n\t\tsb_qtvlist_cache.status = QTVLIST_DOWNLOADING;\n\t\tQTVList_Cache_File_Download();\n\n\t\t\tcache_file = QTVList_Cache_File_Open(\"rb\");\n\t\tif (!cache_file) {\n\t\t\tCom_Printf(\"Can't open QTV cache file\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\tsb_qtvlist_cache.status = QTVLIST_PARSING;\n\t\n\tsb_qtvparse = QTVList_Parse_Init();\n\tQTVList_Process_Full_List(cache_file, sb_qtvparse);\n\tVFS_CLOSE(cache_file);\n\tsb_qtvlist_cache.sb_qtventries = sb_qtvparse->list;\n\tQ_free(sb_qtvparse);\n\t\n\tsb_qtvlist_cache.status = QTVLIST_RESOLVING;\n\tQTVList_Resolve_Hostnames();\n}\n\nstatic size_t QTVList_Length(const sb_qtventry_t *first)\n{\n\tsize_t ret = 0;\n\tconst sb_qtventry_t *cur = first;\n\n\twhile (cur) {\n\t\tret++;\n\t\tcur = cur->next;\n\t}\n\n\treturn ret;\n}\n\nstatic size_t QTVList_Player_Count(const sb_qtventry_t *entry)\n{\n\tconst sb_qtvplayer_t *cur = entry->players;\n\tsize_t ret = 0;\n\n\twhile (cur) {\n\t\tret++;\n\t\tcur = cur->next;\n\t}\n\n\treturn ret;\n}\n\nint QTVList_Entry_Cmp(const void *a_v, const void *b_v)\n{\n\tsb_qtventry_t *entry_a = (sb_qtventry_t *) a_v;\n\tsb_qtventry_t *entry_b = (sb_qtventry_t *) b_v;\n\n\tif (entry_a->observercount > entry_b->observercount) {\n\t\treturn -1;\n\t}\n\telse if (entry_a->observercount == entry_b->observercount) {\n\t\treturn 0;\n\t}\n\telse {\n\t\treturn 1;\n\t}\n}\n\nstatic void QTVList_Print(void)\n{\n\tsize_t len = QTVList_Length(sb_qtvlist_cache.sb_qtventries);\n\tsb_qtventry_t *entries_array;\n\tsb_qtventry_t *cur;\n\tint i;\n\tsize_t players;\n\tsb_qtvplayer_t *player;\n\n\tentries_array = Q_malloc(len * sizeof (sb_qtventry_t));\n\n\tcur = sb_qtvlist_cache.sb_qtventries;\n\n\ti = 0;\n\twhile (cur) {\n\t\tmemcpy(&entries_array[i++], cur, sizeof (sb_qtventry_t));\n\t\tcur = cur->next;\n\t}\n\n\tqsort(entries_array, len, sizeof (sb_qtventry_t), QTVList_Entry_Cmp);\n\n\tfor (i = 0; i < len; i++) {\n\t\tcur = &entries_array[i];\n\t\tplayers = QTVList_Player_Count(cur);\n\n\t\tif (players == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tCom_Printf(\"--- %3d %3u %30s ---\\n\",\n\t\t\tcur->observercount, players, cur->title);\n\n\t\tplayer = cur->players;\n\t\twhile (player) {\n\t\t\tCom_Printf(\"      %4d [%4s] %s\\n\", player->frags, player->team, player->name);\n\t\t\tplayer = player->next;\n\t\t}\n\t}\n}\n\nint QTVList_Refresh_Cache_Thread(void *userData)\n{\n\tQTVList_Refresh_Cache((qbool) userData);\n\tsb_qtvlist_cache.status = QTVLIST_READY;\n\treturn 0;\n}\n\nint QTVList_Download_And_Print_Thread(void *userData)\n{\n\tCom_Printf(\"QuakeTV list downloading...\\n\");\n\tQTVList_Refresh_Cache(true);\n\tsb_qtvlist_cache.status = QTVLIST_PRINTING;\n\tQTVList_Print();\n\tsb_qtvlist_cache.status = QTVLIST_READY;\n\n\treturn 0;\n}\n\nvoid QTVList_Print_Global(void)\n{\n\tif (sb_qtvlist_cache.status != QTVLIST_READY && sb_qtvlist_cache.status != QTVLIST_INIT) {\n\t\tCom_Printf(\"QTV cache is still being rebuilt\\n\");\n\t\treturn;\n\t}\n\n\tsb_qtvlist_cache.status = QTVLIST_PROCESSING;\n\tif (Sys_CreateDetachedThread(QTVList_Download_And_Print_Thread, NULL) < 0) {\n\t\tCom_Printf(\"Failed to create QTVList Download_And_Print thread\\n\");\n\t}\n}\n\nvoid QTVList_Initialize_Streammap(void)\n{\n\tif (sb_qtvlist_cache.status != QTVLIST_READY && sb_qtvlist_cache.status != QTVLIST_INIT) {\n\t\tCom_Printf(\"QTV cache is still being rebuilt\\n\");\n\t\treturn;\n\t}\n\n\tsb_qtvlist_cache.status = QTVLIST_PROCESSING;\n\tif (Sys_CreateDetachedThread(QTVList_Refresh_Cache_Thread, NULL) < 0) {\n\t\tCom_Printf(\"Failed to create QTVList Refresh_Cache thread\\n\");\n\t}\n}\n\nstatic netadr_t QTVList_Current_IP(void)\n{\n\tnetadr_t adr;\n\tchar *prx = Info_ValueForKey(cls.userinfo, \"prx\");\n\n\tif (prx && *prx) {\n\t\tchar *lastsep = strrchr(prx, '@');\n\t\tlastsep = lastsep ? lastsep + 1 : prx;\n\t\t\n\t\tNET_StringToAdr(lastsep, &adr);\n\t\treturn adr;\n\t}\n\telse {\n\t\treturn cls.server_adr;\n\t}\n}\n\nvoid QTVList_Observeqtv_f(void)\n{\n\tnetadr_t addr;\n\tsb_qtventry_t *cur;\n\n\tif (sb_qtvlist_cache.status != QTVLIST_READY) {\n\t\tCom_Printf(\"QTV cache is still being rebuilt\\n\");\n\t\treturn;\n\t}\n\t\n\tif (Cmd_Argc() < 2) {\n\t\taddr = QTVList_Current_IP();\n\t}\n\telse {\n\t\tNET_StringToAdr(Cmd_Argv(1), &addr);\n\t}\n\n\tif (addr.type == NA_IP) {\n\t\tcur = sb_qtvlist_cache.sb_qtventries;\n\n\t\twhile (cur) {\n\t\t\tif (cur->addr.port == addr.port && memcmp(cur->addr.ip, addr.ip, sizeof (addr.ip)) == 0) {\n\t\t\t\tCbuf_AddText(\"qtvplay \");\n\t\t\t\tCbuf_AddText(cur->link);\n\t\t\t\tCbuf_AddText(\"\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcur = cur->next;\n\t\t}\n\n\t\tCom_Printf(\"Cannot find current server on any QTV\\n\");\n\t}\n\telse {\n\t\tCom_Printf(\"Can't observe current server via QTV\\n\");\n\t}\n}\n\nvoid QTVList_Init(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SERVER_BROWSER);\n\n\tCvar_Register(&sb_qtvlist_url);\n\n\tCvar_ResetCurrentGroup();\n\n\tCmd_AddCommand(\"observeqtv\", QTVList_Observeqtv_f);\n\n\tQTVList_Initialize_Streammap();\n}\n"
  },
  {
    "path": "src/EX_browser_sources.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"fs.h\"\n\n#ifndef _WIN32\n#include <sys/time.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#include <sys/param.h>\n#include <sys/ioctl.h>\n#include <sys/uio.h>\n#include <arpa/inet.h>\n#include <errno.h>\n#include <unistd.h>\n#endif\n#include <curl/curl.h>\n\n#include \"EX_browser.h\"\n\n#define SOURCES_LIST_FILENAME \"sb/sources.txt\"\n\n// Used by curl to read server lists from the web\nstruct curl_buf\n{\n\tchar *ptr;\n\tsize_t len;\n};\n\n// sources table\nsource_data *sources[MAX_SOURCES];\nint sourcesn;\n\nsource_data * Create_Source(void)\n{\n\tsource_data *s;\n\ts = (source_data *) Q_malloc(sizeof(source_data));\n\ts->serversn = 0;\n\ts->servers_allocated = 0;\n\ts->last_update.wYear = 0;\n\ts->name[0] = 0;\n\ts->checked = 0;\n\ts->servers = NULL;\n\ts->unique = source_unique++;\n\treturn s;\n}\n\nvoid Reset_Source(source_data *s)\n{\n    int i;\n\tif (s->servers != NULL)\n    {\n        for (i=0; i < s->serversn; i++)\n            Q_free(s->servers[i]);\n        Q_free(s->servers);\n    }\n\ts->serversn = 0;\n\ts->servers_allocated = 0;\n\ts->last_update.wYear = 0;\n    //s->name[0] = 0;\n}\n\n// used only for list re-loading\nvoid Delete_Source(source_data *s)\n{\n    Reset_Source(s);\n    Q_free(s);\n}\n\n// returns true, if there were some problems (like domain-name addresses)\n// which require the source to be dumped to file in corrected form\nqbool Update_Source_From_File(source_data *s, char *fname, server_data **servers, int *pserversn)\n{\n\tvfsfile_t *f;\n\tchar line[2048];\n    qbool should_dump = false;\n\n    //length = COM_FileOpenRead (fname, &f);\n\tf = FS_OpenVFS(fname, \"rb\", FS_ANY);\n\n    if (f) {\n\t\twhile (VFS_GETS(f, line, sizeof(line)))\n\t\t{\n\t\t\tnetadr_t addr;\n\n\t\t\tif (!strchr(line, ':'))\n\t\t\t\tstrlcat (line, \":27000\", sizeof (line));\n\t\t\tif (!NET_StringToAdr(line, &addr))\n\t\t\t\tcontinue;\n\n\t\t\tservers[(*pserversn)++] = Create_Server2(addr);\n\t\t\tif (line[0] <= '0'  ||  line[0] >= '9')\n\t\t\t\tshould_dump = true;\n\t\t}\n\t\tVFS_CLOSE(f);\n    } else {\n        //Com_Printf (\"Updating %15.15s failed: file not found\\n\", s->name);\n        // ?????? should_dump = true;\n\t}\n\n    return should_dump;\n}\n\nstatic size_t SB_URL_to_FileName(const char *str, char *dest, size_t size)\n{\n\tsize_t written = 0;\n\tchar *hexa = \"0123456789abcdef\";\n\t\n\twhile (*str != '\\0' && written + 1 < size) {\n\t\tif (isalnum(*str) || *str == '.') {\n\t\t\t*dest++ = *str++;\n\t\t\twritten++;\n\t\t}\n\t\telse {\n\t\t\tif (written + 1 < size) {\n\t\t\t\tint curchar = *str++;\n\t\t\t\twritten += 3;\n\n\t\t\t\t*dest++ = '_';\n\t\t\t\t*dest++ = hexa[curchar / 16];\n\t\t\t\t*dest++ = hexa[curchar % 16];\n\t\t\t}\n\t\t\telse {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\t*dest = '\\0';\n\n\treturn written;\n}\n\nstatic size_t SB_URL_To_Filename_Length(const char *s)\n{\n\treturn strlen(s)*3+1;\n}\n\nstatic void Precache_Source(source_data *s)\n{\n    int i;\n    char name[1024];\n    server_data *servers[MAX_SERVERS];\n    int serversn = 0;\n\n\tif (s->type == type_url) {\n\t\tchar *filename;\n\t\tsize_t filename_size = SB_URL_To_Filename_Length(s->address.url);\n\n\t\tfilename = Q_malloc(filename_size);\n\t\tSB_URL_to_FileName(s->address.url, filename, filename_size);\n\t\tsnprintf(name, sizeof (name), \"sb/cache/%s\", filename);\n\t\tQ_free(filename);\n\t}\n\telse if (s->type == type_master) {\n\t\tsnprintf(name, sizeof (name), \"sb/cache/%d_%d_%d_%d_[%d].txt\",\n\t\t\t\ts->address.address.ip[0], s->address.address.ip[1],\n\t\t\t\ts->address.address.ip[2], s->address.address.ip[3],\n\t\t\t\tntohs(s->address.address.port));\n\t}\n\telse {\n\t\treturn;\n\t}\n\n\tUpdate_Source_From_File(s, name, servers, &serversn);\n\n\tif (serversn > 0)\n\t{\n\t\tSYSTEMTIME tm;\n\t\tchar tmp_path[MAX_OSPATH] = {0};\n\n\t\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s/ezquake/%s\", com_basedir, name);\n\t\tif (GetFileLocalTime(tmp_path, &tm))\n\t\t{\n\t\t\tReset_Source(s);\n\t\t\ts->servers = (server_data **) Q_malloc(serversn * sizeof(server_data *));\n\t\t\tfor (i=0; i < serversn; i++)\n\t\t\t\ts->servers[i] = servers[i];\n\t\t\ts->serversn = serversn;\n\t\t\ts->servers_allocated = serversn;\n\n\t\t\tif (s->checked)\n\t\t\t\trebuild_servers_list = 1;\n\n\t\t\tmemcpy(&s->last_update, &tm, sizeof(SYSTEMTIME));\n\t\t}\n\t}\n}\n\nstatic void SB_Process_URL_Buffer(const struct curl_buf *curl_buf, server_data *servers[], int *serversn)\n{\n\tnetadr_t addr;\n\tchar *buf, *p0, *p1;\n\n\t// Don't modify curl_buf as it might be used to create cache file\n\tbuf = Q_malloc(sizeof(char) * curl_buf->len);\n\tmemcpy(buf, curl_buf->ptr, sizeof(char) * curl_buf->len);\n\n\t// Not using strtok as it's not thread safe\n\tfor (p0 = buf, p1 = buf; p1 < buf + curl_buf->len; p1++) {\n\t\tif (*p1 == '\\n') {\n\t\t\t*p1 = '\\0';\n\t\t\tNET_StringToAdr(p0, &addr);\n\t\t\tservers[(*serversn)++] = Create_Server2(addr);\n\t\t\tp0 = p1 + 1;\n\t\t}\n\t}\n\n\tQ_free(buf);\n}\n\nstatic struct curl_buf *curl_buf_init(void)\n{\n\t// Q_malloc handles errors and exits on failure\n\tstruct curl_buf *curl_buf = Q_malloc(sizeof(struct curl_buf));\n\tcurl_buf->len = 0;\n\tcurl_buf->ptr = Q_malloc(curl_buf->len + 1);\n\treturn curl_buf;\n}\n\nstatic void curl_buf_deinit(struct curl_buf *curl_buf)\n{\n\tQ_free(curl_buf->ptr);\n\tQ_free(curl_buf);\n}\n\nstatic size_t curl_write_func( void *ptr, size_t size, size_t nmemb, void* buf_)\n{\n\tstruct curl_buf * buf = (struct curl_buf *)buf_;\n\tsize_t new_len = buf->len + size * nmemb;\n\n\t// not checking for realloc errors since Q_realloc will exit on failure\n\tbuf->ptr = Q_realloc(buf->ptr, new_len + 1);\n\n\tmemcpy(buf->ptr + buf->len, ptr, size * nmemb);\n\tbuf->ptr[new_len] = '\\0';\n\tbuf->len = new_len;\n\n\treturn size * nmemb;\n}\n\nint SB_Cache_Source(const source_data *s, const struct curl_buf *curl_buf)\n{\n\tsize_t filename_buf_len;\n\tchar *filename;\n\tFILE *f;\n\n\tfilename_buf_len = SB_URL_To_Filename_Length(s->address.url);\n\tfilename = Q_malloc(filename_buf_len);\n\tSB_URL_to_FileName(s->address.url, filename, filename_buf_len);\n\tif (!FS_FCreateFile(filename, &f, \"ezquake/sb/cache\", \"wb+\")) {\n\t\tCom_Printf_State(PRINT_FAIL, \"SB_Cache_Source() Can't create cache file\");\n\t\tQ_free(filename);\n\t\treturn 0;\n\t}\n\n\tfwrite(curl_buf->ptr, sizeof(char), curl_buf->len, f);\n\n\tfclose(f);\n\tQ_free(filename);\n\treturn 1;\n}\n\nstruct curl_buf *SB_Retrieve_Data(const source_data *s)\n{\n\tCURL *curl;\n\tCURLcode res;\n\tstruct curl_buf *curl_buf;\n\n\tcurl = curl_easy_init();\n\tif (curl) {\n\t\tcurl_easy_setopt(curl, CURLOPT_URL, s->address.url);\n\t\tcurl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);\n\t}\n\telse {\n\t\tCom_Printf_State(PRINT_FAIL, \"SB_Retrieve_Data() Can't init cURL\\n\");\n\t\treturn NULL;\n\t}\n\n\tcurl_buf = curl_buf_init();\n\tcurl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_func);\n\tcurl_easy_setopt(curl, CURLOPT_WRITEDATA, curl_buf);\n\n\tres = curl_easy_perform(curl);\n\tif (res != CURLE_OK) {\n\t\tCom_Printf(\"SB_Retrieve_Data(): Could not read URL %s\\n\", s->address.url);\n\t\tcurl_easy_cleanup(curl);\n\t\tcurl_buf_deinit(curl_buf);\n\t\treturn NULL;\n\t}\n\n\tcurl_easy_cleanup(curl);\n\treturn curl_buf;\n}\n\nstatic void SB_Update_Source_From_URL(const source_data *s, server_data *servers[],\n\tint *serversn)\n{\n\tstruct curl_buf *curl_buf;\n\n\tif (s->type != type_url) {\n\t\tCom_Printf_State(PRINT_FAIL, \"SB_Update_Source_From_URL() Invalid argument\\n\");\n\t\treturn;\n\t}\n\n\t// Retrieve servers\n\tcurl_buf = SB_Retrieve_Data(s);\n\tif (curl_buf == NULL) {\n\t\t// SB_Retrieve_Data will print meaningful error message\n\t\treturn;\n\t}\n\n\t// Update servers variable\n\tSB_Process_URL_Buffer(curl_buf, servers, serversn);\n\n\t// Cache servers (file)\n\t// No need to check for errors since we cleanup anyways\n\tSB_Cache_Source(s, curl_buf);\n\n\tcurl_buf_deinit(curl_buf);\n}\n\nvoid Update_Source(source_data *s)\n{\n\tint i;\n\tqbool should_dump = false;\n\tserver_data *servers[MAX_SERVERS];\n\tint serversn = 0;\n\n\tif (s->type == type_dummy)\n\t\treturn;\n\n\tif (s->type == type_file)\n\t{\n\t\t// read servers from file\n\t\tchar name[1024];\n\t\tsnprintf(name, sizeof (name), \"sb/%s\", s->address.filename);\n\t\tshould_dump = Update_Source_From_File(s, name, servers, &serversn);\n\t\tGetLocalTime(&(s->last_update));\n\t}\n\n\tif (s->type == type_url)\n\t{\t\n\t\tSB_Update_Source_From_URL(s, servers, &serversn);\n\t}\n\n\tif (s->type == type_master)\n\t{\n\t\t// get servers from master server\n\t\tchar request[] = {'c', '\\n', '\\0'};\n\n\t\tsocket_t newsocket;\n\t\tstruct sockaddr_storage server;\n\t\tint ret = 0, i;\n\t\tunsigned char answer[10000];\n\t\tfd_set fd;\n\t\tstruct timeval tv;\n\t\tint trynum;\n\t\tint timeout;\n\n\t\tnewsocket = UDP_OpenSocket(PORT_ANY);\n\t\t// so we have a socket\n\n\t\t// send status request\n\n\t\tfor (trynum=0; trynum < sb_masterretries.value; trynum++) {\n\t\t\tNetadrToSockadr (&(s->address.address), &server);\n\t\t\tret = sendto (newsocket, request, sizeof(request), 0,\n\t\t\t\t\t(struct sockaddr *)&server, sizeof(server) );\n\t\t}\n\n\t\tif (ret < 0)\n\t\t\treturn;\n\n\t\ttimeout = Sys_DoubleTime() + (sb_mastertimeout.value / 1000.0);\n\t\twhile (Sys_DoubleTime() < timeout) {\n\t\t\t//fd.fd_count = 1;\n\t\t\t//fd.fd_array[0] = newsocket;\n\t\t\tFD_ZERO(&fd);\n\t\t\tFD_SET(newsocket, &fd);\n\t\t\ttv.tv_sec = 0;\n\t\t\ttv.tv_usec = 1000 * 1.5 * sb_mastertimeout.value; // multiply timeout by 1.5\n\t\t\tret = select(newsocket+1, &fd, NULL, NULL, &tv);\n\n\t\t\t// get answer\n\t\t\tif (ret > 0)\n\t\t\t\tret = recvfrom (newsocket, (char *) answer, 10000, 0, NULL, NULL);\n\n\t\t\tif (ret > 0  &&  ret < 10000)\n\t\t\t{\n\t\t\t\tanswer[ret] = 0;\n\n\t\t\t\tif (memcmp(answer, \"\\xff\\xff\\xff\\xff\\x64\\x0a\", 6))\n\t\t\t\t{\n\t\t\t\t\tclosesocket(newsocket);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// create servers avoiding duplicates\n\t\t\t\tfor (i=6; i+5 < ret; i+=6)\n\t\t\t\t{\n\t\t\t\t\tchar buf[32];\n\t\t\t\t\tserver_data* server;\n\t\t\t\t\tqbool exists = false;\n\t\t\t\t\tint j;\n\n\t\t\t\t\tsnprintf(buf, sizeof (buf), \"%u.%u.%u.%u:%u\",\n\t\t\t\t\t\t\t(int)answer[i+0], (int)answer[i+1],\n\t\t\t\t\t\t\t(int)answer[i+2], (int)answer[i+3],\n\t\t\t\t\t\t\t256 * (int)answer[i+4] + (int)answer[i+5]);\n\n\t\t\t\t\tserver = Create_Server(buf);\n\t\t\t\t\tfor (j=0; j<serversn; j++) {\n\t\t\t\t\t\tif (NET_CompareAdr(servers[j]->address, server->address)) {\n\t\t\t\t\t\t\texists = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!exists)\n\t\t\t\t\t\tservers[serversn++] = server;\n\t\t\t\t\telse\n\t\t\t\t\t\tDelete_Server(server);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tclosesocket(newsocket);\n\n\t}\n\n\tSB_ServerList_Lock();\n\t// copy all servers to source list\n\tif (serversn > 0)\n\t{\n\t\tReset_Source(s);\n\t\ts->servers = Q_malloc((serversn + (s->type==type_file ? MAX_UNBOUND : 0)) * sizeof(server_data *));\n\t\tfor (i=0; i < serversn; i++)\n\t\t\ts->servers[i] = servers[i];\n\t\ts->serversn = serversn;\n\t\ts->servers_allocated = serversn + (s->type == type_file ? MAX_UNBOUND : 0);\n\n\t\tif (s->checked)\n\t\t\trebuild_servers_list = 1;\n\n\t\tif (sb_mastercache.value)\n\t\t{\n\t\t\tDumpSource(s);\n\t\t\tshould_dump = false;\n\t\t}\n\t\tGetLocalTime(&(s->last_update));\n\t}\n\telse {\n\t\tif (s->type == type_file)\n\t\t{\n\t\t\tReset_Source(s);\n\t\t\ts->servers = Q_malloc(MAX_UNBOUND * sizeof(server_data *));\n\t\t}\n\t}\n\n\tSB_ServerList_Unlock();\n\tif (should_dump)\n\t\tDumpSource(s);\n\t//Com_Printf (\"Updating %15.15s: %d servers\\n\", s->name, serversn);\n}\n\nint updating_sources = 0;\nint source_full_update = 0;\n\ntypedef struct infohost_s\n{\n    double lastsenttime;\n    int phase;\n} infohost;\n\nsource_data **psources;\nint psourcesn;\n\nint Update_Multiple_Sources_Proc(void * lpParameter)\n{\n    // get servers from master server\n    SYSTEMTIME lt;\n    char request[] = {'c', '\\n', '\\0'};\n\n    socket_t newsocket;\n\tstruct sockaddr_storage server;\n    int ret = 0, i, sourcenum;\n    unsigned char answer[10000];\n    fd_set fd;\n    struct timeval tv;\n    int total_masters = 0;\n    int updated = 0;\n    int d1, d2;\n\n    GetLocalTime(&lt);\n    d1 = lt.wSecond + 60*(lt.wMinute + 60*(lt.wHour + 24*(lt.wDay)));\n    // update file sources - this should be a flash\n    for (sourcenum = 0; sourcenum < psourcesn; sourcenum++)\n        if (psources[sourcenum]->checked)\n        {\n            if (psources[sourcenum]->type == type_file)\n                Update_Source(psources[sourcenum]);\n\t\t\tif (psources[sourcenum]->type == type_url)\n\t\t\t\tUpdate_Source(psources[sourcenum]); // todo cache this too\n            else if (psources[sourcenum]->type == type_master)\n            {\n                source_data *s = psources[sourcenum];\n                if (s->last_update.wYear != 0  &&  !source_full_update)\n                {\n                    d2 = s->last_update.wSecond + 60*(s->last_update.wMinute + 60*(s->last_update.wHour + 24*(s->last_update.wDay)));\n\n                    if (d1 > d2  &&  d1 < d2 + sb_sourcevalidity.value*60)\n                    continue;\n                }\n                total_masters++;\n            }\n        }\n\t\n    // update master sources\n    newsocket = UDP_OpenSocket(PORT_ANY);\n\n    for (sourcenum = 0; sourcenum < psourcesn  &&  !abort_ping; sourcenum++)\n    {\n        server_data *servers[MAX_SERVERS];\n        int serversn = 0;\n        int trynum = 0;\n        source_data *s = psources[sourcenum];\n\t\tdouble timeout;\n\n        if (psources[sourcenum]->type != type_master  ||  !psources[sourcenum]->checked)\n            continue;\n\n        if (s->last_update.wYear != 0  &&  !source_full_update)\n        {\n            d2 = s->last_update.wSecond + 60*(s->last_update.wMinute + 60*(s->last_update.wHour + 24*(s->last_update.wDay)));\n\n            if (d1 > d2  &&  d1 < d2 + sb_sourcevalidity.value*60)\n                continue;\n        }\n\n\t\t// send trynum queries to master server\n        for (trynum=0; trynum < sb_masterretries.value; trynum++)\n        {\n\t\t\tNetadrToSockadr (&(s->address.address), &server);\n            ret = sendto (newsocket, request, sizeof(request), 0,\n                          (struct sockaddr *)&server, sizeof(server) );\n\t\t}\n\n\t\tif (ret <= 0)\n\t\t\tcontinue;\n\n\t\ttimeout = Sys_DoubleTime() + (sb_mastertimeout.value / 1000.0);\n\t\twhile (Sys_DoubleTime() < timeout) {\n\t\t\tstruct sockaddr_storage hostaddr;\n            netadr_t from;\n\n            //fd.fd_count = 1;\n            //fd.fd_array[0] = newsocket;\n\t\t\tFD_ZERO(&fd);\n\t\t\tFD_SET(newsocket, &fd);\n            tv.tv_sec = 0;\n            tv.tv_usec = 1000 * sb_mastertimeout.value;\n            ret = select(newsocket+1, &fd, NULL, NULL, &tv);\n\n            // get answer\n            i = sizeof(hostaddr);\n            if (ret > 0)\n                ret = recvfrom (newsocket, (char *) answer, 10000, 0,\n\t\t\t\t(struct sockaddr *)&hostaddr, (socklen_t *)&i);\n\n            if (ret > 0  &&  ret < 10000)\n            {\n                SockadrToNetadr (&hostaddr, &from);\n\n                if (from.ip[0] == s->address.address.ip[0] &&\n                    from.ip[1] == s->address.address.ip[1] &&\n                    from.ip[2] == s->address.address.ip[2] &&\n                    from.ip[3] == s->address.address.ip[3] &&\n                    from.port == s->address.address.port)\n                {\n                    answer[ret] = 0;\n\n                    if (memcmp(answer, \"\\xff\\xff\\xff\\xff\\x64\\x0a\", 6))\n                    {\n                        continue;\n                    }\n\n                    // create servers avoiding duplicates\n\t\t\t\t\tfor (i=6; i+5 < ret; i+=6)\n\t\t\t\t\t{\n\t\t\t\t\t\tchar buf[32];\n\t\t\t\t\t\tserver_data* server;\n\t\t\t\t\t\tqbool exists = false;\n\t\t\t\t\t\tint j;\n\n\t\t\t\t\t\tsnprintf(buf, sizeof (buf), \"%u.%u.%u.%u:%u\",\n\t\t\t\t\t\t\t(int)answer[i+0], (int)answer[i+1],\n\t\t\t\t\t\t\t(int)answer[i+2], (int)answer[i+3],\n\t\t\t\t\t\t\t256 * (int)answer[i+4] + (int)answer[i+5]);\n\n\t\t\t\t\t\tserver = Create_Server(buf);\n\t\t\t\t\t\tfor (j=0; j<serversn; j++) {\n\t\t\t\t\t\t\tif (NET_CompareAdr(servers[j]->address, server->address)) {\n\t\t\t\t\t\t\t\texists = true;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\tif (!exists)\n\t\t\t\t\t\t\tservers[serversn++] = server;\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tDelete_Server(server);\n\t\t\t\t\t}\n                }\n            }\n\t\t}\n\n        // copy all servers to source list\n        if (serversn > 0)\n        {\n\t\t\tupdated++;\n\n\t\t\tSB_ServerList_Lock();\n\n            Reset_Source(s);\n            s->servers = (server_data **) Q_malloc(serversn * sizeof(server_data *));\n            for (i=0; i < serversn; i++)\n                s->servers[i] = servers[i];\n            s->serversn = serversn;\n\t\t\ts->servers_allocated = serversn;\n            if (s->checked)\n                rebuild_servers_list = 1;\n            GetLocalTime(&(s->last_update));\n\n\t\t\tSB_ServerList_Unlock();\n\n            if (sb_mastercache.value)\n                DumpSource(s);\n        }\n\n        ping_pos = updated / (double)total_masters;\n    }\n\n    closesocket(newsocket);\n\n\t// Not having this here leads to crash almost always when some\n\t// other action with servers list happens right after this function.\n\t// Even 1 ms delay was enough during the tests, previously 500 ms was used.\n    //Sys_MSleep(100);\n\n    updating_sources = 0;\n\tsb_queuedtriggers |= SB_TRIGGER_SOURCESUPDATED;\n    return 0;\n}\n\nvoid Update_Init(source_data *s[], int sn)\n{\n    psources = s;\n    psourcesn = sn;\n\n    abort_ping = 0;\n    updating_sources = 1;\n    ping_pos = 0;\n}\n\n// starts asynchronous sources update\nvoid Update_Multiple_Sources_Begin(source_data *s[], int sn)\n{\n\tUpdate_Init(s, sn);\n\tif (Sys_CreateDetachedThread(Update_Multiple_Sources_Proc, NULL) < 0) {\n\t\tCom_Printf(\"Failed to create Update_Multiple_Sources_Proc thread\\n\");\n\t}\n}\n\n// starts synchronous sources update\nvoid Update_Multiple_Sources(source_data *s[], int sn)\n{\n\tUpdate_Init(s, sn);\n    Update_Multiple_Sources_Proc(NULL);\n}\n\nvoid SB_Sources_Update(qbool full)\n{\n\tsource_full_update = full;\n\tUpdate_Multiple_Sources(sources, sourcesn);\n    if (rebuild_servers_list)\n        Rebuild_Servers_List();\n}\n\nvoid SB_Sources_Update_Begin(qbool full)\n{\n\tsource_full_update = full;\n\tUpdate_Multiple_Sources_Begin(sources, sourcesn);\n}\n\nunsigned int SB_Sources_Marked_Count(void)\n{\n\tint i;\n\tunsigned int ret = 0;\n\n\tfor (i = 0; i < sourcesn; i++) {\n\t\tif (sources[i]->checked == 1) {\n\t\t\tret++;\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nvoid Toggle_Source(source_data *s)\n{\n    s->checked = !(s->checked);\n    rebuild_servers_list = 1;\n}\n\nvoid Mark_Source(source_data *s)\n{\n    if (!s->checked)\n    {\n        s->checked = 1;\n        rebuild_servers_list = 1;\n    }\n}\n\nvoid Unmark_Source(source_data *s)\n{\n    if (s->checked)\n    {\n        s->checked = 0;\n        rebuild_servers_list = 1;\n    }\n}\n\nchar * next_space(char *s)\n{\n    char *ret = s;\n    while (*ret  &&  !isspace2(*ret))\n        ret++;\n    return ret;\n}\n\nchar * next_nonspace(char *s)\n{\n    char *ret = s;\n    while (*ret  &&  isspace2(*ret))\n        ret++;\n    return ret;\n}\n\nchar * next_quote(char *s)\n{\n    char *ret = s;\n    while (*ret  &&  *ret != '\\\"')\n        ret++;\n    return ret;\n}\n\nqbool SB_Sources_Dump(void)\n{\n\tFILE *f;\n\tint i;\n\n\tif (!FS_FCreateFile(SOURCES_LIST_FILENAME, &f, \"ezquake\", \"wt\")) {\n\t\treturn false;\n\t}\n \n\tfor (i = 0; i < sourcesn; i++) {\n\t\tsb_source_type_t type = sources[i]->type;\n\n\t\tif (type == type_master || type == type_file || type == type_url) {\n\t\t\tconst char *typestr;\n\t\t\tconst char *name = sources[i]->name;\n\t\t\tconst char *loc;\n\n\t\t\tif (type == type_master) {\n\t\t\t\ttypestr = \"master\";\n\t\t\t\tloc = NET_AdrToString(sources[i]->address.address);\n\t\t\t}\n\t\t\telse if (type == type_url) {\n\t\t\t\ttypestr = \"url\";\n\t\t\t\tloc = sources[i]->address.url;\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttypestr = \"file\";\n\t\t\t\tloc = sources[i]->address.filename;\n\t\t\t}\n\t\t\t\n\t\t\tfprintf(f, \"%s \\\"%s\\\" %s\\n\", typestr, name, loc);\n\t\t}\n\t}\n\n\tfclose(f);\n\n\treturn true;\n}\n\nint SB_Source_Add(const char* name, const char* address, sb_source_type_t type)\n{\n\tsource_data *s;\n\tchar addr[512];\n\tint pos;\n\n\tif (strlen(name) <= 0  ||  strlen(address) <= 0)\n\t\treturn -1;\n\n\t// create new source\n\ts = Create_Source();\n\ts->type = type;\n\tstrlcpy (s->name, name, sizeof (s->name));\n\tstrlcpy (addr, address, sizeof (addr));\n\n\tif (s->type == type_file) {\n\t\tstrlcpy (s->address.filename, address, sizeof (s->address.filename));\n\t}\n\telse if (s->type == type_url) {\n\t\tstrlcpy(s->address.url, address, sizeof(s->address.url));\n\t}\n\telse {\n\t\tif (!strchr(addr, ':')) {\n\t\t\tstrlcat (addr, \":27000\", sizeof (addr));\n\t\t}\n\t\tif (!NET_StringToAdr(addr, &(s->address.address))) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tpos = sourcesn++;\n\tsources[pos] = s;\n\tMark_Source(sources[pos]);\n\tUpdate_Source(sources[pos]);\n\n\tSB_Sources_Dump();\n\n\treturn pos;\n}\n\nvoid SB_Source_Remove(int i)\n{\n    source_data *s;\n\n\tif (i < 0 || i >= MAX_SOURCES) {\n\t\treturn;\n\t}\n\n\ts = sources[i];\n    if (s->type == type_dummy)\n        return;\n\n\tQ_free(sources[i]);\n\n    // remove from SB\n    if (i < sourcesn - 1)\n    {\n\t\tmemmove(sources+i,\n                sources+i + 1,\n                (sourcesn-i-1)*sizeof(*sources));\n    }\n    sourcesn--;\n\n\tSB_Sources_Dump();\n}\n\nvoid Reload_Sources(void)\n{\n    int i;\n\tvfsfile_t *f;\n\tchar ln[2048];\n    source_data *s;\n\n\tSB_ServerList_Lock();\n    for (i=0; i < sourcesn; i++)\n        Delete_Source(sources[i]);\n    sourcesn = 0;\n\n    // create dummy unbound source\n    sources[0] = Create_Source();\n    sources[0]->type = type_dummy;\n    strlcpy (sources[0]->name, \"Unbound\", sizeof (sources[0]->name));\n    sources[0]->servers = (server_data **) Q_malloc(MAX_UNBOUND*sizeof(server_data *));\n\tsources[0]->serversn = 0;\n\tsources[0]->servers_allocated = MAX_UNBOUND;\n\n\tsourcesn = 1;\n\n\tf = FS_OpenVFS(SOURCES_LIST_FILENAME, \"rb\", FS_ANY);\n\tif (!f) \n\t{\n        //Com_Printf (\"sources file not found: %s\\n\", SOURCES_PATH);\n\t\tSB_ServerList_Unlock();\n\t\treturn;\n\t}\n\n    s = Create_Source();\n    while (VFS_GETS(f, ln, sizeof(ln)))\n    {\n\t\tchar line[2048];\n        char *p, *q;\n\n        if (sscanf(ln, \"%[ -~\t]s\", line) != 1) {\n\t\t\tcontinue;\n\t\t}\n\n        p = next_nonspace(line);\n        if (*p == '/')\n            continue;   // comment\n        q = next_space(p);\n\n\t\tif (!strncmp(p, \"master\", q-p)) {\n            s->type = type_master;\n\t\t}\n\t\telse if (!strncmp(p, \"file\", q-p)) {\n\t\t\ts->type = type_file;\n\t\t}\n\t\telse if (!strncmp(p, \"url\", q-p)) {\n\t\t\ts->type = type_url;\n\t\t}\n\t\telse {\n\t\t\tcontinue;\n\t\t}\n\n        p = next_nonspace(q);\n        q = (*p == '\\\"') ? next_quote(++p) : next_space(p);\n\n        if (q-p <= 0)\n            continue;\n\n        strlcpy (s->name, p, min(q-p+1, MAX_SOURCE_NAME+1));\n\n        p = next_nonspace(q+1);\n        q = next_space(p);\n        *q = 0;\n\n        if (q-p <= 0)\n            continue;\n\n        if (s->type == type_file)\n            strlcpy (s->address.filename, p, sizeof (s->address.filename));\n\t\telse if (s->type == type_url)\n\t\t\tstrlcpy (s->address.url, p, sizeof (s->address.url));\n        else\n            if (!NET_StringToAdr(p, &(s->address.address)))\n                continue;\n\n        sources[sourcesn] = Create_Source();\n        i = sources[sourcesn]->unique;\n        memcpy(sources[sourcesn], s, sizeof(source_data));\n        sources[sourcesn]->unique = i;\n        sourcesn++;\n    }\n\n    Delete_Source(s);\n\tVFS_CLOSE(f);\n\n    //Com_Printf(\"Read %d sources for Server Browser\\n\", sourcesn);\n\n    // update all file sources\n    for (i=0; i < sourcesn; i++)\n        if (sources[i]->type == type_file)\n            Update_Source(sources[i]);\n        else if (sources[i]->type == type_master || sources[i]->type == type_url)\n            Precache_Source(sources[i]);\n\n    rebuild_servers_list = 1;\n    resort_sources = 1;\n\tSB_ServerList_Unlock();\n}\n\nint rebuild_servers_list = 0;\n\nvoid Rebuild_Servers_List(void)\n{\n    int i;\n    int server_limit = sizeof(servers) / sizeof(servers[0]);\n    serversn = 0;\n\t\n    rebuild_servers_list = 0;\n\tSB_ServerList_Lock();\n\n    for (i=0; i < sourcesn; i++)\n    {\n        if (sources[i]->checked)\n        {\n            int j;\n            for (j=0; j < sources[i]->serversn; j++)\n            {\n                int k;\n                qbool found_duplicate = false;\n\n\t\t\t\tif (sources[i]->servers[j] == NULL)\n\t\t\t\t\tcontinue;\n\n                // Try and find a matching address\n                for (k = 0; k < serversn && k < server_limit; k++) {\n                    if (!memcmp(&(servers[k]->address), &(sources[i]->servers[j]->address), sizeof(netadr_t))) {\n                        found_duplicate = true;\n                        break;\n                    }\n                }\n\n                if (! found_duplicate) {\n                    // if not on list yet\n                    if (serversn < server_limit) {\n                        servers[serversn++] = sources[i]->servers[j];\n                    }\n                }\n            }\n        }\n    }\n\n    resort_servers = 1;\n    rebuild_all_players = 1;\n    Servers_pos = 0;\n    serversn_passed = serversn;\n\n\tSB_ServerList_Unlock();\n}\n\nvoid DumpSource(source_data *s)\n{\n    FILE *f;\n    int i;\n    char buf[1024];\n\n    if (s->type == type_file)\n        snprintf(buf, sizeof (buf), \"sb/%s\", s->address.filename);\n    else if (s->type == type_master)\n    {\n        Sys_mkdir(\"sb/cache\");\n        snprintf(buf, sizeof (buf), \"sb/cache/%d_%d_%d_%d_[%d].txt\",\n                s->address.address.ip[0], s->address.address.ip[1],\n                s->address.address.ip[2], s->address.address.ip[3],\n                ntohs(s->address.address.port));\n    }\n    else\n        return;\n\n//    f = fopen(buf, \"wt\");\n//    if (f == NULL)\n//        return;\n    if (!FS_FCreateFile(buf, &f, \"ezquake\", \"wt\"))\n        return;\n\n    for (i=0; i < s->serversn; i++)\n        fprintf(f, \"%d.%d.%d.%d:%d\\n\",\n        s->servers[i]->address.ip[0], s->servers[i]->address.ip[1],\n        s->servers[i]->address.ip[2], s->servers[i]->address.ip[3],\n        ntohs(s->servers[i]->address.port));\n\n    fclose(f);\n}\n\nstatic void AddUnbound(server_data *s)\n{\n    if (sources[0]->serversn >= MAX_UNBOUND)\n        return;\n\n    if (IsInSource(sources[0], s))\n        return;\n\n    sources[0]->servers[sources[0]->serversn] = s;\n    (sources[0]->serversn) ++;\n    rebuild_servers_list = true;\n}\n\nvoid RemoveFromFileSource(source_data *source, server_data *serv)\n{\n    int i;\n    \n    if (source->serversn <= 0)\n        return;\n\n    for (i=0; i < source->serversn; i++)\n        if (!memcmp(&source->servers[i]->address, &serv->address, 6))\n        {\n\t\t\t// Only add to unbound if not in any other sources...\n\t\t\tint j = 0;\n\t\t\tqbool in_other_source = false;\n\t\t\tfor (j = 0; j < sourcesn; ++j) {\n\t\t\t\tif (source != sources[j]) {\n\t\t\t\t\tin_other_source |= IsInSource(sources[j], serv);\n\t\t\t\t}\n\t\t\t}\n\n            // remove from source\n            if (i != source->serversn - 1)\n            {\n                memmove(source->servers+i,\n                        source->servers+i+1,\n                        (source->serversn - i - 1) * sizeof(source_data *));\n            }\n            --source->serversn;\n            DumpSource(source);\n\t\t\tif (!in_other_source) {\n\t\t\t\t// add to unbound\n\t\t\t\tAddUnbound(serv);\n\t\t\t\tMark_Source(sources[0]);\n\t\t\t}\n            return;\n        }\n}\n\nvoid AddToFileSource(source_data *source, server_data *serv)\n{\n\tif (IsInSource(source, serv))\n\t\treturn;\n\n\tSB_ServerList_Lock();\n\n    // reallocate buffer if we've run out of space\n\tif (source->serversn >= source->servers_allocated) {\n\t\tint new_size = source->servers_allocated + 4;\n\t\tserver_data** newlist = Q_malloc(new_size * sizeof(server_data*));\n\n\t\tmemcpy(newlist, source->servers, sizeof(server_data*) * source->servers_allocated);\n\t\tQ_free(source->servers);\n\t\tsource->servers = newlist;\n\t\tsource->servers_allocated = new_size;\n\t}\n\n\tsource->servers[source->serversn++] = Clone_Server(serv);\n\trebuild_servers_list = true;\n\n\tSB_ServerList_Unlock();\n\n\tDumpSource(source);\n\tMark_Source(sources[0]);\n}\n"
  },
  {
    "path": "src/EX_qtvlist.c",
    "content": "/*\nCopyright (C) 2015 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#include <curl/curl.h>\n#include <jansson.h>\n#include <SDL_thread.h>\n#include \"quakedef.h\"\n#include \"EX_qtvlist.h\"\n\ncvar_t qtv_api_url = {\"qtv_api_url\", \"http://qtvapi.quakeworld.nu/api/v1/servers\"};\n\nstatic json_t *root;\nstatic SDL_mutex *qtvlist_mutex;\n\nextern char *CL_QTV_GetCurrentStream(void);\n\nstatic size_t qtvlist_curl_callback(char *content, size_t size, size_t nmemb, void *userp)\n{\n\tsize_t realsize = nmemb * size;\n\tstruct str_buf *buf = (struct str_buf *)userp;\n\tchar *tmpstr = NULL;\n\n\tif (buf->len > (4*1024*1024)) { /* Some sort of sanity check */\n\t\tCom_Printf(\"error: file too big\\n\");\n\t\treturn -1;\n\t}\n\n\ttmpstr = Q_realloc(buf->str, buf->len + realsize + 1);\n\tif (tmpstr == NULL) {\n\t\tCom_Printf(\"error: Out of memory (realloc returned NULL)\\n\");\n\t\treturn 0;\n\t}\n\telse {\n\t\tbuf->str = tmpstr;\n\t}\n\n\tmemcpy(&(buf->str[buf->len]), content, realsize);\n\tbuf->len += realsize;\n\tbuf->str[buf->len] = 0;\n\n\treturn realsize;\n}\n\n/* Caller must free */\nstatic char* qtvlist_get_jsondata(void)\n{\n\tCURL *handle;\n\tstruct str_buf buf;\n\tint res = 0;\n\n\tmemset(&buf, 0, sizeof(buf));\n\n\tif ((handle = curl_easy_init()) == NULL) {\n\t\tCom_Printf(\"error: failed to init curl\\n\");\n\t\treturn NULL;\n\t}\n\n\tbuf.str = Q_calloc(1, 128); /*  Initially set to 128 bytes, will grow if necessary */\n\n\tres += curl_easy_setopt(handle, CURLOPT_URL, qtv_api_url.string);\n\tres += curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void*)&buf);\n\tres += curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, qtvlist_curl_callback);\n\n\tres += curl_easy_perform(handle);\n\n\tif (res != CURLE_OK) {\n\t\tCom_Printf(\"error: Failed to fetch qtv list JSON data\\n\");\n\t\tQ_free(buf.str); /* Will set to NULL */\n\t}\n\t\n\tcurl_easy_cleanup(handle);\n\treturn buf.str;\n}\n\nstatic void qtvlist_json_load_and_verify_string(const char *input)\n{\n\tjson_t *server_array = NULL;\n\tjson_t *server_count = NULL;\n\tjson_t *player_count = NULL;\n\tjson_t *observ_count = NULL;\n\tjson_error_t error;\n\n\tif (root != NULL) {\n\t\tjson_decref(root);\n\t\troot = NULL;\n\t}\n\n\troot = json_loads(input, 0, &error);\n\tif (root == NULL) {\n\t\tCom_Printf(\"error: JSON error on line %d: %s\\n\", error.line, error.text);\n\t\tgoto err;\n\t}\n\n\tif (!json_is_object(root)) {\n\t\tCom_Printf(\"error: invalid JSON, root is not an object\\n\");\n\t\tgoto err;\n\t}\n\n\tserver_array = json_object_get(root, \"Servers\");\n\tserver_count = json_object_get(root, \"ServerCount\");\n\tplayer_count = json_object_get(root, \"PlayerCount\");\n\tobserv_count = json_object_get(root, \"ObserverCount\");\n\n\tif (!json_is_array(server_array)  || !json_is_number(server_count) ||\n\t    !json_is_number(player_count) || !json_is_number(observ_count)) {\n\t\tCom_Printf(\"error: invalid JSON, unsupported format\\n\");\n\t\tgoto err;\n\t}\n\n\treturn;\nerr:\n\tif (root != NULL) {\n\t\tjson_decref(root);\n\t\troot = NULL;\n\t}\n}\n\n#if 0\n/* FIXME Verify JSON entries before printing + mutex lock it */\nstatic void qtvlist_print_server_and_qtvaddress_list(void)\n{\n\tint i, j;\n\tjson_t *server_array, *server_entry, *gs_array, *gs_entry;\n\n\tserver_array = json_object_get(root, \"Servers\");\n\t\n\tfor (i = 0; i < json_array_size(server_array); i++) {\n\t\tserver_entry = json_array_get(server_array, i);\n\t\tgs_array = json_object_get(server_entry, \"GameStates\");\n\n\t\tfor (j = 0; j < json_array_size(gs_array); j++) {\n\t\t\tgs_entry = json_array_get(gs_array, j);\n\t\t\tCom_Printf(\"%s:%\" JSON_INTEGER_FORMAT \" %s\\n\", json_string_value(json_object_get(gs_entry, \"Hostname\")),\n\t\t\t\t\t\tjson_integer_value(json_object_get(gs_entry, \"Port\")),\n\t\t\t\t\t\tjson_string_value(json_object_get(gs_entry, \"Link\")));\n\t\t}\n\t}\n}\n#endif\n\nstatic const char *qtvlist_get_qtvaddress(const char *qwserver, short port)\n{\n\tint i, j;\n\tjson_t *server_array, *server_entry, *gs_array, *gs_entry;\n\tconst char *hostname, *ipaddress;\n\n\tif (qwserver == NULL) {\n\t\treturn NULL;\n\t}\n\n\tif (root == NULL) {\n\t\tCom_Printf(\"error: qtv list data not initialized\\n\");\n\t\treturn NULL;\n\t}\n\n\tserver_array = json_object_get(root, \"Servers\");\n\t\n\tfor (i = 0; i < json_array_size(server_array); i++) {\n\t\tserver_entry = json_array_get(server_array, i);\n\t\tgs_array = json_object_get(server_entry, \"GameStates\");\n\n\t\tfor (j = 0; j < json_array_size(gs_array); j++) {\n\t\t\tgs_entry = json_array_get(gs_array, j);\n\t\t\tif (gs_entry == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\thostname = json_string_value(json_object_get(gs_entry, \"Hostname\"));\n\t\t\tipaddress = json_string_value(json_object_get(gs_entry, \"IpAddress\"));\n\t\t\tif (hostname == NULL || ipaddress == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (strcmp(qwserver, hostname) == 0 || strcmp(qwserver, ipaddress) == 0) {\n\t\t\t\tif ((short)json_integer_value(json_object_get(gs_entry, \"Port\")) == port) {\n\t\t\t\t\treturn json_string_value(json_object_get(gs_entry, \"Link\"));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nstatic void qtvlist_find_player(const char *name, qbool list_all)\n{\n\tjson_t *server_array, *server_entry, *gs_array, *gs_entry;\n\tjson_t *players_array, *player_entry;\n\tconst char *player_name;\n\tint i,j,k;\n\tunsigned short found = 0;\n\n\t\n\tif (name == NULL) {\n\t\treturn;\n\t}\n\n\tif (root == NULL) {\n\t\tCom_Printf(\"error: qtvlist data not initialized\\n\");\n\t\treturn;\n\t}\n\n\tserver_array = json_object_get(root, \"Servers\");\n\n\tif (server_array == NULL) {\n\t\tCom_Printf(\"error: invalid qtvlist json data\\n\");\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < json_array_size(server_array); i++) {\n\t\tserver_entry = json_array_get(server_array, i);\n\t\tif (server_entry == NULL) {\n\t\t\tCom_Printf(\"error: invalid qtvlist json data\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tgs_array = json_object_get(server_entry, \"GameStates\");\n\t\tif (gs_array == NULL || !json_is_array(gs_array)) {\n\t\t\tCom_Printf(\"error: malformed qtvlist json data\\n\"); /* FIXME: Make better error prints */\n\t\t\treturn;\n\t\t}\n\n\t\tfor (j = 0; j < json_array_size(gs_array); j++) {\n\t\t\tgs_entry = json_array_get(gs_array, j);\n\t\t\tif (gs_entry == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tplayers_array = json_object_get(gs_entry, \"Players\");\n\t\t\tif (players_array == NULL || !json_is_array(players_array)) {\n\t\t\t\tcontinue;\n\t\t\t\t/* FIXME: Print some debug stuff atleast ?? */\n\t\t\t}\n\n\t\t\tfor (k = 0; k < json_array_size(players_array); k++) {\n\t\t\t\tplayer_entry = json_array_get(players_array, k);\n\t\t\t\tif (player_entry == NULL) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tplayer_name = json_string_value(json_object_get(player_entry, \"Name\"));\n\t\t\t\tif (player_name) {\n\t\t\t\t\tif (list_all || strstri(player_name, name) != NULL) {\n\t\t\t\t\t\tfound++;\n\t\t\t\t\t\tCom_Printf(\"&cff4%15s&r - %s:%\" JSON_INTEGER_FORMAT \"\\n\", player_name, json_string_value(json_object_get(gs_entry, \"Hostname\")), json_integer_value(json_object_get(gs_entry, \"Port\")));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (list_all) {\n\t\tCom_Printf(\"Listing all players\\n\");\n\t} else if (found == 0) {\n\t\tCom_Printf(\"Found no players matching: \\\"%s\\\"\\n\", name);\n\t}\n}\n\nstatic void qtvlist_find_and_follow_player(const char *name)\n{\n\tjson_t *server_array, *server_entry, *gs_array, *gs_entry;\n\tjson_t *players_array, *player_entry;\n\tconst char *player_name, *hostname;\n\tint i,j,k, port;\n\tunsigned short found = 0;\n\n\n\tif (name == NULL) {\n\t\treturn;\n\t}\n\n\tif (root == NULL) {\n\t\tCom_Printf(\"error: qtvlist data not initialized\\n\");\n\t\treturn;\n\t}\n\n\tserver_array = json_object_get(root, \"Servers\");\n\n\tif (server_array == NULL) {\n\t\tCom_Printf(\"error: invalid qtvlist json data\\n\");\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < json_array_size(server_array); i++) {\n\t\tserver_entry = json_array_get(server_array, i);\n\t\tif (server_entry == NULL) {\n\t\t\tCom_Printf(\"error: invalid qtvlist json data\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tgs_array = json_object_get(server_entry, \"GameStates\");\n\t\tif (gs_array == NULL || !json_is_array(gs_array)) {\n\t\t\tCom_Printf(\"error: malformed qtvlist json data\\n\"); /* FIXME: Make better error prints */\n\t\t\treturn;\n\t\t}\n\n\t\tfor (j = 0; j < json_array_size(gs_array); j++) {\n\t\t\tgs_entry = json_array_get(gs_array, j);\n\t\t\tif (gs_entry == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tplayers_array = json_object_get(gs_entry, \"Players\");\n\t\t\tif (players_array == NULL || !json_is_array(players_array)) {\n\t\t\t\tcontinue;\n\t\t\t\t/* FIXME: Print some debug stuff atleast ?? */\n\t\t\t}\n\n\t\t\tfor (k = 0; k < json_array_size(players_array); k++) {\n\t\t\t\tplayer_entry = json_array_get(players_array, k);\n\t\t\t\tif (player_entry == NULL) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tplayer_name = json_string_value(json_object_get(player_entry, \"Name\"));\n\t\t\t\tif (strcmp(player_name, name) == 0) {\n\t\t\t\t\tfound++;\n\t\t\t\t\thostname = json_string_value(json_object_get(gs_entry, \"Hostname\"));\n\t\t\t\t\tport = json_integer_value(json_object_get(gs_entry, \"Port\"));\n\t\t\t\t\tCbuf_AddText(va(\"connect %s:%d\\n\", hostname, port));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif (found == 0) {\n\t\tCom_Printf(\"Found no players matching: \\\"%s\\\"\\n\", name);\n\t}\n\n}\n\nstatic void qtvlist_get_gameaddress(const char *qtvaddress, char *out_addr, size_t out_addr_len)\n{\n\tint i, j;\n\tjson_t *server_array, *server_entry, *gs_array, *gs_entry;\n\tconst char *ipaddress, *link;\n\tjson_int_t port;\n\n\tif (qtvaddress == NULL) {\n\t\tgoto err;\n\t}\n\n\tif (root == NULL) {\n\t\tCom_Printf(\"error: qtv list data not initialized\\n\");\n\t\tgoto err;\n\t}\n\n\tserver_array = json_object_get(root, \"Servers\");\n\n\tif (server_array == NULL) {\n\t\tCom_Printf(\"error: invalid qtvlist json data\\n\");\n\t\tgoto err;\n\t}\n\t\n\tfor (i = 0; i < json_array_size(server_array); i++) {\n\t\tserver_entry = json_array_get(server_array, i);\n\t\tgs_array = json_object_get(server_entry, \"GameStates\");\n\n\t\tfor (j = 0; j < json_array_size(gs_array); j++) {\n\t\t\tgs_entry = json_array_get(gs_array, j);\n\t\t\tif (gs_entry == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlink = json_string_value(json_object_get(gs_entry, \"Link\"));\n\t\t\tif (link == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (strcmp(link, qtvaddress) == 0) {\n\t\t\t\tipaddress = json_string_value(json_object_get(gs_entry, \"IpAddress\"));\n\t\t\t\tif (ipaddress == NULL) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tport = json_integer_value(json_object_get(gs_entry, \"Port\"));\n\n\t\t\t\tsnprintf(out_addr, out_addr_len, \"%s:%\" JSON_INTEGER_FORMAT, ipaddress, port);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\nerr:\n\t*out_addr = 0;\n}\n\nstatic void qtvlist_qtv_cmd(void)\n{\n\tchar tmp[256] = {0};\n\tchar *port;\n\tconst char *qtvaddress;\n\textern qbool connected_via_proxy;\n\n\tif (qtvlist_mutex == NULL) {\n\t\tCom_Printf(\"error: cannot read QTV list, mutex not initialized\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() < 2) {\n\t\t/* No argument, use current connected server ip:port */\n\t\tif (cls.state < ca_connected) {\n\t\t\tCom_Printf(\"error: not connected to a server\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\t/* FIXME: It's pretty ugly, refactor all this so that we can for sure\n\t\t * keep track of where we are connected, what kind of endpoint it is and\n\t\t * also which server we asked the proxy to connect to...\n\t\t */\n\t\tif (connected_via_proxy) {\n\t\t\t/* If connected through proxy, the target server is \n\t\t\t * hopefully still in userinfo/prx\n\t\t\t */\n\t\t\tchar *prx = Info_ValueForKey(cls.userinfo, \"prx\");\n\t\t\tchar *srv = strrchr(prx, '@');\n\t\t\tif (srv) {\n\t\t\t\tsrv++;\n\t\t\t} else {\n\t\t\t\tsrv = prx;\n\t\t\t}\n\n\t\t\tif (!prx[0]) {\n\t\t\t\tCom_Printf(\"error: connected through proxy but missing 'prx' userinfo, can't find QTV stream\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstrlcpy(&tmp[0], srv, sizeof(tmp));\n\t\t} else {\n\t\t\tstrlcpy(&tmp[0], NET_AdrToString(cls.server_adr), sizeof(tmp));\n\t\t}\n\t} else if (Cmd_Argc() == 2) {\n\t\t/* User provided which qwserver to find a QTV stream address for */\n\t\tstrlcpy(&tmp[0], Cmd_Argv(1), sizeof(tmp));\n\t} else {\n\t\tCom_Printf(\"usage: %s [qwserver:port]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tport = strchr(&tmp[0], ':');\n\tif (port == NULL) {\n\t\tport = \"27500\";\n\t} else {\n\t\t*port = 0;\n\t\tport++;\n\t}\n\n\tif (SDL_TryLockMutex(qtvlist_mutex) != 0) {\n\t\tCom_Printf(\"QTV list is being updated, please try again soon\\n\");\n\t\treturn;\n\t}\n\n\tqtvaddress = qtvlist_get_qtvaddress((const char*)&tmp[0], Q_atoi(port));\n\n\tif (qtvaddress != NULL) {\n\t\tCbuf_AddText(\"qtvplay \");\n\t\tCbuf_AddText(qtvaddress);\n\t\tCbuf_AddText(\"\\n\");\n\t} else {\n\t\tCom_Printf(\"No QTV stream address found for '%s:%s'\\n\", &tmp[0], port);\n\t}\n\n\tSDL_UnlockMutex(qtvlist_mutex);\n}\n\nstatic int qtvlist_update(void *unused)\n{\n\tchar *jsondata = NULL;\n\tint ret = -1;\n\tint res;\n\t(void)unused;\n\n\tres = SDL_TryLockMutex(qtvlist_mutex);\n\n\tif (res == SDL_MUTEX_TIMEDOUT) {\n\t\tCom_Printf(\"The qtvlist is already in the process of being updated\\n\");\n\t\tgoto out;\n\t} else if (res < 0) {\n\t\tCom_Printf(\"error: mutex lock failed (SDL2): %s\\n\", SDL_GetError());\n\t\tgoto out;\n\t}\n\n\tjsondata = qtvlist_get_jsondata();\n\tif (jsondata == NULL) {\n\t\tgoto out;\n\t}\n\n\tqtvlist_json_load_and_verify_string(jsondata);\n\tQ_free(jsondata);\n\n\tif (root == NULL) {\n\t\tgoto out;\n\t}\n\n\tret = 0;\nout:\n\tif (res == 0) {\n\t\tSDL_UnlockMutex(qtvlist_mutex);\n\t}\n\treturn ret;\n}\n\nstatic void qtvlist_find_player_cmd(void)\n{\n\tqbool list_all = false;\n\n\tif (qtvlist_mutex == NULL) {\n\t\tCom_Printf(\"error: cannot read QTV list, mutex not initialized\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() == 1) {\n\t\tlist_all = true;\n\t} else if (Cmd_Argc() > 2) {\n\t\tCom_Printf(\"usage: find [nickname] (empty arg lists all)\\n\");\n\t\treturn;\n\t}\n\n\tif (SDL_TryLockMutex(qtvlist_mutex) != 0) {\n\t\tCom_Printf(\"Player list is being updated, please try again soon\\n\");\n\t\treturn;\n\t}\n\n\tif (list_all) {\n\t\tqtvlist_find_player(\"\", true);\n\t} else {\n\t\tqtvlist_find_player(Cmd_Argv(1), false);\n\t}\n\n\tSDL_UnlockMutex(qtvlist_mutex);\n}\n\nstatic void qtvlist_find_and_follow_player_cmd(void)\n{\n\tif (qtvlist_mutex == NULL) {\n\t\tCom_Printf(\"error: cannot read QTV list, mutex not initialized\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() == 1) {\n\t\tCom_Printf(\"usage: find_and_follow [nickname]\\n\");\n\t\treturn;\n\t}\n\n\tif (SDL_TryLockMutex(qtvlist_mutex) != 0) {\n\t\tCom_Printf(\"Player list is being updated, please try again soon\\n\");\n\t\treturn;\n\t}\n\n\tqtvlist_find_and_follow_player(Cmd_Argv(1));\n\n\tSDL_UnlockMutex(qtvlist_mutex);\n\n}\n\nstatic void qtvlist_spawn_updater(void)\n{\n\tSDL_Thread *qtvlist_thread;\n\n\tif (qtvlist_mutex == NULL) {\n\t\tCom_Printf(\"error: cannot update QTV list, mutex not initialized\\n\");\n\t\treturn;\n\t}\n\n\tqtvlist_thread = SDL_CreateThread(qtvlist_update, \"qtvupdater\", (void*)NULL);\n\tif (qtvlist_thread == NULL) {\n\t\tCom_Printf(\"error: failed to initialize qtvlist thread\\n\");\n\t\tCom_Printf(\"error: qtv/observeqtv commands may not work...\\n\");\n\t\treturn;\n\t}\n\n\tSDL_DetachThread(qtvlist_thread);\n}\n\nvoid qtvlist_joinfromqtv_cmd(void)\n{\n\t/* FIXME: Make this prettier */\n\tchar addr[512];\n\tchar httpaddr[512];\n\tchar gameaddress[512];\n\tchar *currstream, *server;\n\n\tcurrstream = CL_QTV_GetCurrentStream();\n\tif (currstream == NULL) {\n\t\tCom_Printf(\"Not connected to a QTV, can't join\\n\");\n\t\treturn;\n\t}\n\n\tstrlcpy(&addr[0], currstream, sizeof(addr));\n\t/* Bleh, transformation of id@server:port to http://server:port/watch.qtv?sid=id */\n\tserver = strchr(&addr[0], '@');\n\tif (server == NULL) {\n\t\tCom_Printf(\"error: wrong format on input\\n\");\n\t\treturn;\n\t}\n\t*server++ = 0;\n\t\n\tsnprintf(&httpaddr[0], sizeof(httpaddr), \"http://%s/watch.qtv?sid=%s\", server, &addr[0]);\n\n\tif (SDL_TryLockMutex(qtvlist_mutex) != 0) {\n\t\tCom_Printf(\"qtvlist is being updated, please try again soon\\n\");\n\t\treturn;\n\t}\n\tqtvlist_get_gameaddress((const char*)&httpaddr[0], &gameaddress[0], sizeof(gameaddress));\n\tSDL_UnlockMutex(qtvlist_mutex);\n\n\tif (gameaddress[0] != 0) {\n\t\tCbuf_AddText(va(\"connect %s\\n\", &gameaddress[0]));\n\t} else {\n\t\tCom_Printf(\"No game address found for this QTV stream\\n\");\n\t}\n}\n\nvoid qtvlist_init(void)\n{\n\tCmd_AddCommand(\"qtv\", qtvlist_qtv_cmd);\n\tCmd_AddCommand(\"find\", qtvlist_find_player_cmd);\n\tCmd_AddCommand(\"find_and_follow\", qtvlist_find_and_follow_player_cmd);\n\tCmd_AddCommand(\"find_update\", qtvlist_spawn_updater);\n\tCmd_AddCommand(\"observeqtv\", qtvlist_qtv_cmd); /* For backwards compat */\n\tCmd_AddCommand(\"qtv_update\", qtvlist_spawn_updater);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_QTV);\n\tCvar_Register(&qtv_api_url);\n\tCvar_ResetCurrentGroup();\n\n\tqtvlist_mutex = SDL_CreateMutex();\n\tif (qtvlist_mutex == NULL) {\n\t\tCom_Printf(\"error: failed to initialize qtvlist mutex\\n\");\n\t\tCom_Printf(\"error: qtv/observeqtv commands won't work...\\n\");\n\t\treturn;\n\t}\n\n\t/* Initialize by running the updater at startup */\n\tqtvlist_spawn_updater();\n}\n\nvoid qtvlist_deinit(void)\n{\n\tif (root != NULL) {\n\t\tjson_decref(root);\n\t\troot = NULL;\n\t}\n\n\tif (qtvlist_mutex != NULL) {\n\t\tSDL_UnlockMutex(qtvlist_mutex);\n\t\tSDL_DestroyMutex(qtvlist_mutex);\n\t}\n}\n\n\n"
  },
  {
    "path": "src/EX_qtvlist.h",
    "content": "/*\nCopyright (C) 2015 dimman\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#include <stddef.h>\n\nstruct str_buf {\n\tsize_t len; /* len not size, basically strlen(str) so 1 byte less than actual size */\n\tchar *str; /* NULL terminated */\n};\n\nvoid qtvlist_init(void);\nvoid qtvlist_deinit(void);\nvoid qtvlist_joinfromqtv_cmd(void);\n\n"
  },
  {
    "path": "src/anorm_dots.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n{\n{156,164,186,171,197,216,174,174,201,203,227,250,239,244,226,130,99,136,50,70,75,103,109,145,141,49,54,79,79,65,100,88,127,153,117,188,165,199,108,136,144,203,204,178,174,217,226,226,244,252,241,212,216,236,203,212,226,236,244,252,250,183,154,188,97,123,123,156,154,183,188,79,82,112,89,117,150,52,26,50,3,9,14,27,37,67,56,9,26,27,79,52,79,50,82,89,56,108,144,112,178,150,117,100,127,136,174,153,188,165,199,204,174,65,54,88,50,49,75,79,109,145,117,70,99,103,130,164,136,171,174,141,197,244,226,227,201,203,217,241,226,41,65,37,97,41,65,27,17,9,27,17,9,1,1,3,27,12,36,36,12,27},\n{156,162,186,167,195,216,166,168,195,198,222,250,239,242,224,129,96,132,46,67,70,98,102,137,135,43,47,72,72,58,92,80,118,145,109,180,156,192,100,127,136,198,198,171,166,211,221,221,241,250,237,209,216,234,207,214,229,237,246,253,250,178,151,186,97,122,124,155,157,186,189,72,77,106,86,113,145,46,22,45,3,7,14,24,37,67,53,11,31,29,87,58,85,55,86,91,58,116,151,118,183,155,121,108,135,144,181,161,195,173,206,210,181,73,61,97,55,55,82,87,117,153,126,75,102,108,131,167,140,176,181,147,200,246,229,231,207,208,223,245,231,39,64,37,98,44,67,24,16,7,32,19,12,0,3,3,22,8,30,42,16,32},\n{157,161,186,163,193,216,159,162,188,194,218,249,238,239,222,128,95,129,42,64,65,94,96,130,130,38,41,65,64,51,83,72,109,136,101,172,148,184,93,118,129,192,191,164,158,205,216,215,237,248,233,205,215,231,211,216,232,237,248,253,249,173,147,184,96,120,125,154,158,189,190,66,72,99,84,109,139,40,18,41,4,6,15,22,37,67,51,14,35,31,94,65,91,59,90,92,60,123,157,123,188,159,124,117,144,152,189,170,202,181,212,215,188,81,69,105,61,62,89,95,124,160,135,80,106,114,133,169,144,181,187,154,202,247,231,235,213,212,229,248,236,37,63,38,99,48,69,21,16,5,37,22,16,0,5,4,17,5,24,48,20,38},\n{158,160,187,160,191,216,152,156,182,189,213,248,238,236,219,128,94,127,40,62,60,90,90,123,125,34,35,59,57,44,75,65,100,128,94,164,139,176,85,110,121,186,184,157,150,198,211,209,233,246,228,201,212,228,213,216,234,236,249,253,247,168,143,180,95,117,125,152,159,191,189,59,67,92,81,104,132,34,14,36,5,5,15,20,37,66,48,17,40,34,101,71,97,64,93,93,62,130,163,128,193,163,126,125,153,159,196,178,209,188,218,219,194,89,77,114,67,69,96,103,132,168,143,85,110,121,136,172,149,186,194,161,205,248,233,239,219,217,234,250,239,37,64,41,101,52,73,19,17,4,42,25,20,0,7,6,14,3,19,55,25,44},\n{159,159,188,156,189,216,144,151,175,185,208,247,237,233,216,129,94,125,38,61,57,87,85,117,120,30,29,54,50,37,67,57,92,120,87,156,130,168,77,101,112,179,176,149,141,191,204,202,228,242,222,196,209,223,215,216,235,235,249,252,244,162,138,176,94,114,124,150,159,192,187,53,62,85,77,99,125,28,11,32,6,4,16,19,37,65,46,20,45,37,109,78,102,68,97,94,64,136,168,133,196,166,128,133,161,166,203,186,216,196,224,223,199,97,85,123,74,77,104,112,141,176,152,91,115,128,139,176,154,191,200,168,207,249,234,242,225,221,238,252,242,37,66,44,103,57,77,18,18,4,49,30,25,1,11,9,11,1,15,62,31,51},\n{161,159,189,154,188,216,138,146,169,181,202,245,236,229,214,131,94,124,37,61,54,85,80,111,116,28,25,49,43,31,60,51,84,113,80,149,122,160,69,92,104,172,168,141,132,184,198,194,222,239,217,190,206,219,216,215,235,232,249,251,241,155,133,171,92,110,122,147,159,192,185,47,57,78,74,93,118,23,8,28,8,4,17,17,37,64,43,24,51,39,115,84,107,72,99,94,65,142,173,137,199,168,129,140,169,173,210,193,222,202,228,225,204,104,93,131,81,85,112,121,149,184,161,98,120,135,143,179,160,196,206,175,210,249,236,245,230,225,242,253,244,38,68,47,106,63,82,18,21,4,55,34,31,2,14,12,9,0,11,69,36,59},\n{164,159,190,151,187,216,131,141,163,177,197,242,234,225,211,133,96,123,37,61,52,84,77,105,113,26,21,45,37,26,52,45,77,105,74,141,113,151,61,84,96,164,159,133,123,177,190,187,216,234,211,183,201,213,216,213,235,229,247,249,237,147,127,166,89,106,120,143,157,192,182,41,52,71,70,87,110,19,6,24,11,4,19,16,37,63,40,28,56,42,122,90,112,76,102,94,66,148,176,140,201,169,130,148,176,179,216,201,227,208,232,227,208,112,102,140,89,94,120,130,157,192,169,106,126,143,147,183,166,201,212,182,213,249,237,247,234,229,245,253,245,40,71,52,110,70,87,18,24,6,63,40,37,4,19,16,8,0,8,76,42,66},\n{166,160,192,149,186,216,125,137,157,173,192,239,233,221,208,135,98,123,37,63,51,83,73,100,111,25,18,42,31,21,46,39,70,99,69,134,105,143,54,76,87,156,151,124,115,170,183,179,210,230,205,176,196,207,216,210,233,225,245,247,233,140,120,160,87,101,118,138,155,190,178,35,47,64,66,81,103,14,4,20,14,5,20,15,37,61,37,32,61,45,128,96,116,80,104,93,67,153,180,142,202,170,130,154,183,184,222,207,232,214,235,228,211,119,110,148,97,102,129,138,166,199,177,113,133,150,152,187,172,206,218,189,216,248,238,249,239,233,248,253,246,43,75,57,115,77,93,20,28,8,70,46,43,6,23,20,7,0,5,83,48,74},\n{170,161,194,148,185,216,120,133,152,170,187,236,231,217,206,138,100,124,39,65,51,84,71,96,109,24,16,39,25,17,39,35,63,92,64,127,97,135,47,68,79,147,142,115,106,163,175,171,203,225,199,168,190,200,214,206,231,221,242,244,228,132,113,153,83,96,115,133,153,188,174,29,42,57,62,75,95,10,2,17,17,6,22,14,37,59,35,36,66,47,133,101,120,83,105,92,68,157,182,144,202,169,129,161,190,189,228,214,236,218,237,229,214,126,118,156,106,111,138,147,174,206,185,121,140,158,157,191,178,211,224,196,218,247,239,251,243,236,250,253,246,47,79,63,120,85,100,22,32,11,78,53,50,9,28,25,7,0,3,90,54,82},\n{173,162,195,147,184,216,115,130,146,166,182,233,229,213,203,142,104,125,42,68,52,85,70,93,107,25,14,37,20,13,34,31,57,87,60,120,90,127,40,61,71,138,133,107,97,156,166,163,196,219,192,160,183,193,211,201,228,216,239,240,222,124,106,146,80,91,111,128,149,185,168,24,37,50,58,69,87,7,2,14,20,7,24,14,37,58,32,40,71,50,138,107,123,87,106,91,69,160,183,146,201,168,128,166,196,193,233,219,240,222,239,228,216,133,126,163,115,120,146,156,182,213,192,129,147,166,162,195,184,216,229,203,221,246,239,251,246,239,251,251,245,52,85,70,125,93,107,25,37,14,87,60,57,13,34,31,8,2,2,97,61,90},\n{177,164,198,146,184,216,110,127,142,164,177,229,227,208,200,146,108,127,45,72,53,86,69,90,107,27,13,37,16,10,29,28,52,82,57,114,83,119,34,54,63,130,125,98,89,149,158,155,189,214,186,152,176,185,208,196,224,210,235,236,216,115,99,138,76,85,107,122,145,181,162,19,32,44,53,62,79,4,1,11,24,9,26,13,37,55,30,45,76,53,143,111,126,89,107,89,69,163,184,146,200,167,126,171,201,196,237,224,243,225,240,226,216,139,134,170,123,128,155,164,190,219,199,138,154,174,168,200,191,221,234,209,223,244,240,252,249,242,252,249,243,57,91,77,131,101,115,29,43,18,95,68,64,17,39,37,10,4,1,104,67,98},\n{181,166,200,146,184,216,106,125,137,161,173,225,225,204,198,150,113,130,49,76,55,89,69,88,107,29,14,37,12,8,24,25,47,77,55,108,77,111,28,48,56,121,116,90,81,143,149,147,181,208,179,144,169,178,204,190,220,203,230,231,210,107,91,130,72,79,103,115,140,177,156,15,27,37,49,56,71,2,1,8,28,11,28,13,37,53,27,49,80,55,147,116,128,92,107,87,69,165,184,146,198,164,123,176,206,198,241,229,245,228,239,224,216,145,142,176,132,137,163,172,197,225,205,146,162,182,174,204,197,226,238,216,226,242,240,252,251,245,252,247,240,63,97,84,138,109,123,33,50,23,104,75,72,22,45,43,13,6,1,110,74,106},\n{185,169,202,147,185,216,103,123,133,159,168,221,222,200,196,155,118,133,54,82,59,92,70,86,108,32,14,38,9,7,21,24,44,74,53,103,71,104,23,42,49,112,108,81,74,137,141,140,174,202,173,135,161,169,199,184,214,196,225,227,203,98,84,122,68,73,98,109,135,171,149,11,23,31,44,50,63,1,2,6,32,14,31,14,37,51,25,53,85,57,150,120,130,94,106,84,68,167,183,145,194,161,120,179,209,200,244,232,246,229,239,221,215,150,149,182,141,145,172,179,204,230,211,155,169,190,180,209,203,230,242,222,228,239,239,251,252,247,251,244,237,69,104,92,144,118,131,39,57,28,112,84,79,26,51,50,16,9,2,116,80,113},\n{189,172,205,148,185,216,101,122,130,158,164,217,220,195,194,160,124,136,59,87,62,96,72,86,109,36,16,40,6,6,18,23,41,71,53,98,66,98,19,37,42,103,100,73,67,131,132,132,166,196,167,126,153,161,194,177,209,189,219,221,196,90,76,114,64,67,93,101,129,166,142,8,19,26,40,43,56,0,3,5,36,17,33,14,37,48,23,58,89,59,152,123,131,95,105,81,68,167,181,144,191,157,117,182,212,200,247,235,247,230,237,217,213,155,155,187,150,153,180,186,211,234,216,163,177,197,186,213,210,234,245,227,230,236,239,250,253,248,250,240,233,76,111,100,152,127,139,44,64,34,121,92,87,32,57,57,20,13,3,122,86,121},\n{194,175,207,150,186,216,99,122,127,157,161,212,217,191,192,166,130,141,65,94,67,101,74,86,111,41,19,42,4,6,16,24,38,69,53,94,62,92,15,33,36,95,92,66,61,126,124,125,158,190,161,117,144,152,188,169,202,181,213,216,189,81,69,105,59,61,87,94,123,159,134,5,15,21,36,38,49,0,4,4,41,20,36,15,37,46,21,62,92,61,154,126,131,96,103,78,67,167,179,142,186,152,112,184,215,200,249,237,247,229,234,212,211,159,161,191,158,161,187,192,217,238,220,172,184,204,192,217,215,238,248,232,232,233,238,249,253,249,247,236,229,84,119,109,159,136,148,51,72,40,129,101,95,37,63,64,24,17,6,127,92,128},\n{198,179,210,152,187,216,98,122,125,156,157,208,215,187,190,172,137,145,72,101,73,106,78,87,114,46,22,46,3,7,15,25,37,68,54,91,58,86,12,29,31,87,85,59,55,122,116,118,151,184,155,109,135,144,181,161,195,173,206,210,181,73,61,97,55,55,81,87,116,152,126,3,12,16,31,32,42,0,6,3,45,24,38,16,37,43,19,66,96,63,155,128,131,97,101,74,66,166,175,139,180,147,108,185,216,199,250,238,246,228,231,207,207,162,167,195,166,168,194,198,222,241,224,180,192,211,198,222,221,241,250,237,234,229,237,247,253,250,245,231,223,92,127,118,166,144,156,58,80,47,137,109,102,43,69,72,30,22,8,131,98,135},\n{203,183,212,154,188,216,97,123,123,156,154,203,212,183,188,178,144,150,79,108,79,112,82,89,117,52,26,50,3,9,14,27,37,67,56,89,56,82,9,27,26,79,79,52,50,117,108,112,144,178,150,100,126,136,174,153,188,165,199,204,174,65,54,88,50,49,75,79,109,145,117,1,9,12,27,27,36,1,9,3,50,27,41,17,37,41,17,70,99,65,156,130,130,97,99,70,65,164,171,136,174,141,103,186,216,197,250,239,244,226,227,201,203,164,171,197,174,174,201,203,227,244,226,188,199,217,204,226,226,244,252,241,236,226,236,244,252,250,241,226,217,100,136,127,174,153,165,65,88,54,145,117,109,49,75,79,36,27,12,136,103,141},\n{208,187,215,157,190,216,98,125,122,156,152,198,210,179,187,184,151,155,87,116,85,118,86,91,122,59,31,55,3,12,15,29,37,68,58,87,54,78,7,25,22,72,73,46,46,114,101,106,137,172,145,92,118,127,166,144,180,156,192,198,166,58,47,80,45,43,69,72,102,137,109,0,6,8,24,22,30,3,12,3,55,31,43,19,37,38,16,74,101,66,155,131,128,97,96,66,63,162,167,131,168,135,98,185,216,195,250,238,241,224,222,194,198,166,175,199,181,180,207,207,231,246,228,195,206,223,210,229,231,247,253,245,237,222,234,241,250,250,237,221,211,109,144,135,181,161,173,73,97,61,152,126,116,55,81,87,42,32,16,139,108,147},\n{212,191,217,161,192,216,99,127,122,157,150,194,207,175,186,190,158,161,95,124,92,125,92,94,126,66,36,61,4,15,16,33,38,69,62,86,53,74,6,24,19,65,67,41,42,111,94,101,130,166,141,84,109,119,158,136,172,148,184,192,159,51,40,72,41,37,63,64,95,129,101,0,4,6,20,17,24,5,15,4,59,36,46,21,37,36,15,78,103,67,154,131,126,96,92,62,61,159,161,127,161,128,92,184,215,191,249,237,238,220,217,187,192,167,179,200,188,186,212,211,234,247,229,202,213,229,216,233,236,249,253,247,238,217,232,238,248,249,232,215,204,117,152,144,189,169,181,81,105,69,159,134,123,61,87,94,49,38,21,142,112,152},\n{217,195,220,164,194,216,101,130,122,158,148,189,205,172,185,196,166,167,103,132,100,132,98,98,131,73,42,67,6,19,18,37,41,71,66,86,53,72,6,23,16,59,62,36,40,109,87,96,124,160,136,76,100,111,150,127,163,139,177,186,152,44,34,64,36,32,57,57,87,121,92,0,3,3,17,13,20,8,19,5,64,40,48,23,37,33,14,81,105,68,152,131,123,95,89,58,59,155,155,122,153,121,86,182,212,187,247,235,234,216,211,180,186,167,181,200,194,191,217,213,237,247,230,209,219,233,221,236,240,250,253,250,239,213,230,234,245,248,227,210,197,126,161,153,196,177,189,90,114,76,166,142,129,67,93,101,56,43,26,144,117,157},\n{221,200,222,168,196,216,103,133,123,159,147,185,202,169,185,202,174,173,112,141,108,140,104,103,137,81,49,74,9,23,21,42,44,74,71,86,53,70,7,24,14,54,59,32,38,108,82,92,118,155,133,69,92,104,141,118,155,131,169,180,144,39,28,57,32,26,51,50,79,112,84,1,2,2,14,9,16,11,23,6,68,44,51,25,37,31,14,84,106,68,150,130,120,94,85,53,57,150,149,116,145,113,80,179,209,182,244,232,230,211,204,172,179,167,183,200,199,194,221,215,239,246,229,214,225,237,227,239,244,251,252,251,239,209,228,230,242,247,222,203,190,135,169,161,203,184,196,98,122,84,171,149,135,73,98,109,63,50,31,145,120,161},\n{225,204,225,173,198,216,106,137,125,161,146,181,200,166,184,208,181,179,121,149,116,147,111,108,143,90,56,81,12,28,24,48,47,77,77,88,55,69,8,25,14,49,55,29,37,107,76,89,113,150,130,63,84,97,132,109,146,123,162,174,138,33,23,50,28,22,45,43,72,104,75,2,1,1,11,6,13,15,27,8,72,49,53,27,37,28,13,87,107,69,147,128,116,92,80,49,55,145,142,110,137,106,74,176,206,176,241,229,225,205,197,163,172,165,184,198,204,198,224,216,239,245,228,220,230,240,231,242,247,252,251,252,240,204,226,226,238,245,216,197,182,144,178,169,210,190,203,107,130,91,177,156,140,79,103,115,71,56,37,146,123,164},\n{229,208,227,177,200,216,110,142,127,164,146,177,198,164,184,214,189,186,130,158,125,155,119,114,149,98,63,89,16,34,29,54,52,82,83,90,57,69,10,28,13,45,53,27,37,107,72,86,108,146,127,57,77,91,123,101,138,115,154,168,131,29,18,43,24,17,39,37,64,95,68,4,1,1,9,4,10,19,32,11,76,53,55,30,37,26,13,89,107,69,143,126,111,89,76,45,53,139,134,104,128,98,67,171,201,170,237,224,219,199,190,155,164,163,184,196,208,200,226,216,240,243,225,224,235,243,236,244,249,252,249,252,240,200,223,221,234,242,209,191,174,152,185,176,216,196,210,115,138,99,181,162,145,85,107,122,79,62,44,146,126,167},\n{233,213,229,182,203,216,115,146,130,166,147,173,195,162,184,219,196,192,138,166,133,163,127,120,156,107,71,97,20,40,34,61,57,87,90,93,60,70,13,31,14,42,52,25,37,107,68,85,104,142,125,52,70,85,115,93,129,107,147,162,125,25,14,37,20,13,34,31,57,87,60,7,2,2,7,2,8,24,37,14,80,58,58,32,37,24,14,91,106,69,138,123,107,87,71,40,50,133,126,97,120,90,61,166,196,163,233,219,213,192,182,146,156,160,183,193,211,201,228,216,239,240,222,228,239,245,240,246,251,251,246,251,239,195,221,216,229,239,203,184,166,160,193,183,222,201,216,124,146,106,185,168,149,91,111,128,87,69,50,146,128,168},\n{236,217,231,187,206,216,120,152,133,170,148,170,194,161,185,225,203,199,147,175,142,171,135,127,163,115,79,106,25,47,39,68,63,92,97,96,64,71,17,35,16,39,51,24,39,109,65,84,100,138,124,47,63,79,106,85,121,100,140,157,120,22,11,32,17,9,28,25,50,78,53,10,2,3,6,0,7,29,42,17,83,62,59,35,37,22,14,92,105,68,133,120,101,83,66,36,47,126,118,90,111,82,54,161,190,156,228,214,206,185,174,138,147,157,182,189,214,202,229,214,237,236,218,231,242,246,244,247,253,251,243,250,239,191,218,211,224,236,196,178,158,168,200,190,228,206,221,132,153,113,188,174,153,96,115,133,95,75,57,144,129,169},\n{239,221,233,192,208,216,125,157,137,173,149,166,192,160,186,230,210,205,156,183,151,179,143,134,170,124,87,115,31,54,46,76,70,99,105,100,69,73,21,39,18,37,51,25,42,111,63,83,98,135,123,43,57,75,97,77,113,93,133,152,115,20,8,28,14,6,23,20,43,70,46,14,4,5,5,0,7,35,47,20,87,66,61,37,37,20,15,93,104,67,128,116,96,80,61,32,45,119,110,83,102,74,48,154,183,148,222,207,199,177,166,129,138,153,180,184,216,202,228,211,235,232,214,233,245,246,247,248,253,249,239,248,238,187,216,206,218,233,189,172,150,176,207,196,233,210,225,140,160,120,190,178,155,101,118,138,103,81,64,142,130,170},\n{242,225,234,197,211,216,131,163,141,177,151,164,190,159,187,234,216,211,164,190,159,187,151,141,177,133,96,123,37,61,52,84,77,105,113,105,74,77,26,45,21,37,52,26,45,113,61,84,96,133,123,40,52,71,89,70,106,87,126,147,110,18,6,24,11,4,19,16,37,63,40,19,6,8,4,0,8,41,52,24,89,70,63,40,37,19,16,94,102,66,122,112,90,76,56,28,42,112,102,76,94,66,42,148,176,140,216,201,192,169,157,120,130,148,176,179,216,201,227,208,232,227,208,235,247,245,249,249,253,247,234,245,237,183,213,201,212,229,182,166,143,183,213,201,237,213,229,147,166,127,192,182,157,106,120,143,110,87,71,140,130,169},\n{245,229,236,202,214,216,138,169,146,181,154,161,189,159,188,239,222,217,172,198,168,194,160,149,184,141,104,132,43,69,60,92,84,113,122,111,80,80,31,51,25,37,54,28,49,116,61,85,94,131,124,38,47,68,81,63,98,82,120,143,106,18,4,21,8,2,14,12,31,55,34,23,8,11,4,0,9,47,57,28,92,74,64,43,37,17,17,94,99,65,115,107,84,72,51,24,39,104,93,69,85,59,36,140,169,131,210,193,184,161,149,112,121,142,173,173,216,199,225,204,228,222,202,235,249,244,251,249,253,245,230,242,236,179,210,196,206,225,175,160,135,190,219,206,241,215,232,155,171,133,192,185,159,110,122,147,118,93,78,137,129,168},\n{247,233,237,208,216,216,144,175,151,185,156,159,188,159,189,242,228,222,179,204,176,202,168,156,191,149,112,141,50,77,67,101,92,120,130,117,87,85,37,57,29,38,57,30,54,120,61,87,94,129,125,37,44,66,74,57,91,77,115,139,103,18,4,18,6,1,11,9,25,49,30,28,11,15,4,1,11,53,62,32,94,77,65,46,37,16,19,94,97,64,109,102,78,68,45,20,37,97,85,62,77,51,31,133,161,123,203,186,176,152,141,104,112,136,168,166,215,196,223,199,224,216,196,235,249,242,252,249,252,242,225,238,234,176,207,191,200,221,168,154,128,196,223,209,244,216,235,162,176,138,192,187,159,114,124,150,125,99,85,133,128,166},\n{248,236,238,213,219,216,152,182,156,189,160,158,187,160,191,246,233,228,186,211,184,209,176,164,198,157,121,150,57,85,75,110,100,128,139,123,94,90,44,65,35,40,60,34,59,125,62,90,94,128,127,37,41,64,67,52,85,73,110,136,101,19,4,17,5,0,7,6,20,42,25,34,14,19,5,3,14,59,67,36,95,81,66,48,37,15,20,93,93,62,101,97,71,64,40,17,34,89,77,55,69,44,25,125,153,114,196,178,168,143,132,96,103,130,163,159,213,193,219,194,218,209,188,234,249,239,253,248,250,239,219,234,233,172,205,186,194,217,161,149,121,201,228,212,247,216,236,168,180,143,191,189,159,117,125,152,132,104,92,128,126,163},\n{249,239,238,218,222,216,159,188,162,194,163,157,186,161,193,248,237,233,192,216,191,215,184,172,205,164,129,158,64,93,83,118,109,136,148,130,101,96,51,72,41,42,65,38,65,130,64,94,95,128,129,37,38,63,61,48,80,69,106,133,99,21,5,16,4,0,5,4,16,37,22,40,18,24,6,5,17,66,72,41,96,84,67,51,37,15,22,92,90,60,94,91,65,59,35,14,31,81,69,48,62,38,20,117,144,105,189,170,160,135,124,89,95,123,157,152,211,188,215,188,212,202,181,232,248,236,253,247,248,235,213,229,231,169,202,181,187,212,154,144,114,205,231,215,249,216,237,173,184,147,189,190,158,120,125,154,139,109,99,123,124,159},\n{250,242,239,222,224,216,166,195,168,198,167,156,186,162,195,250,241,237,198,221,198,221,192,180,211,171,136,166,72,100,92,127,118,145,156,137,109,102,58,80,47,46,70,43,72,135,67,98,96,129,132,39,37,64,55,44,75,67,102,131,98,24,7,16,3,0,3,3,12,32,19,46,22,30,7,8,22,72,77,45,97,86,67,53,37,14,24,91,86,58,87,85,58,55,31,11,29,73,61,42,55,32,16,108,135,97,181,161,153,126,117,82,87,116,151,144,207,183,210,181,206,195,173,229,246,231,253,246,245,231,207,223,229,167,200,176,181,208,147,140,108,209,234,216,250,214,237,178,186,151,186,189,157,122,124,155,145,113,106,118,121,155},\n{250,244,239,227,226,216,174,201,174,203,171,156,186,164,197,252,244,241,203,226,204,226,199,188,217,178,144,174,79,108,100,136,127,153,165,145,117,109,65,88,54,50,75,49,79,141,70,103,99,130,136,41,37,65,50,41,70,65,99,130,97,27,9,17,3,1,1,3,9,27,17,52,26,36,9,12,27,79,82,50,97,89,67,56,37,14,27,89,82,56,79,79,52,50,26,9,27,65,54,36,49,27,12,100,126,88,174,153,145,117,109,75,79,108,144,136,203,178,204,174,199,188,165,226,244,226,252,244,241,227,201,217,226,164,197,171,174,203,141,136,103,212,236,216,250,212,236,183,188,154,183,188,154,123,123,156,150,117,112,112,117,150},\n{250,246,239,231,229,216,181,207,181,208,176,156,186,167,200,253,246,245,207,229,210,231,206,195,223,183,151,181,87,116,108,144,135,161,173,153,126,117,73,97,61,55,82,55,87,147,75,108,102,131,140,44,37,67,46,39,67,64,96,129,98,32,12,19,3,3,0,3,7,24,16,58,31,42,11,16,32,85,86,55,97,91,67,58,37,14,29,86,77,53,72,72,46,45,22,7,24,58,47,30,43,22,8,92,118,80,166,145,137,109,102,70,72,100,136,127,198,171,198,166,192,180,156,221,241,221,250,242,237,222,195,211,224,162,195,167,168,198,135,132,98,214,237,216,250,209,234,186,189,157,178,186,151,124,122,155,155,121,118,106,113,145},\n{249,247,238,235,231,216,189,213,187,212,181,157,186,169,202,253,248,248,211,232,215,236,212,202,229,188,157,188,94,123,117,152,144,170,181,160,135,124,81,105,69,61,89,62,95,154,80,114,106,133,144,48,38,69,42,37,64,63,95,128,99,37,16,22,4,5,0,4,5,21,16,65,35,48,14,20,38,91,90,59,96,92,67,60,37,15,31,84,72,51,64,66,40,41,18,6,22,51,41,24,38,17,5,83,109,72,159,136,130,101,96,65,65,93,129,118,192,164,191,158,184,172,148,216,237,215,248,239,233,218,188,205,222,161,193,163,162,194,130,129,94,216,237,215,249,205,231,189,190,158,173,184,147,125,120,154,159,124,123,99,109,139},\n{248,248,238,239,233,216,196,219,194,217,186,158,187,172,205,253,249,250,213,234,219,239,218,209,234,193,163,194,101,130,125,159,153,178,188,168,143,132,89,114,77,67,96,69,103,161,85,121,110,136,149,52,41,73,40,37,62,64,94,128,101,42,20,25,5,7,0,6,4,19,17,71,40,55,17,25,44,97,93,64,95,93,66,62,37,15,34,81,67,48,57,59,34,36,14,5,20,44,35,19,34,14,3,75,100,65,152,128,123,94,90,60,59,85,121,110,186,157,184,150,176,164,139,211,233,209,246,236,228,213,182,198,219,160,191,160,156,189,125,127,90,216,236,212,247,201,228,191,189,159,168,180,143,125,117,152,163,126,128,92,104,132},\n{247,249,237,242,234,216,203,225,200,221,191,159,188,176,207,252,249,252,215,235,223,242,224,216,238,196,168,199,109,136,133,166,161,186,196,176,152,141,97,123,85,74,104,77,112,168,91,128,115,139,154,57,44,77,38,37,61,66,94,129,103,49,25,30,6,11,1,9,4,18,18,78,45,62,20,31,51,102,97,68,94,94,65,64,37,16,37,77,62,46,50,53,28,32,11,4,19,37,29,15,30,11,1,67,92,57,144,120,117,87,85,57,54,77,112,101,179,149,176,141,168,156,130,204,228,202,242,233,222,208,175,191,216,159,189,156,151,185,120,125,87,216,235,209,244,196,223,192,187,159,162,176,138,124,114,150,166,128,133,85,99,125},\n{245,249,236,245,236,216,210,230,206,225,196,161,189,179,210,251,249,253,216,235,225,244,228,222,242,199,173,204,115,142,140,173,169,193,202,184,161,149,104,131,93,81,112,85,121,175,98,135,120,143,160,63,47,82,37,38,61,68,94,131,106,55,31,34,8,14,2,12,4,18,21,84,51,69,24,36,59,107,99,72,92,94,64,65,37,17,39,74,57,43,43,47,23,28,8,4,17,31,25,11,28,9,0,60,84,51,138,113,111,80,80,54,49,69,104,92,172,141,168,132,160,149,122,198,222,194,239,229,217,202,169,184,214,159,188,154,146,181,116,124,85,215,232,206,241,190,219,192,185,159,155,171,133,122,110,147,168,129,137,78,93,118},\n{242,249,234,247,237,216,216,234,212,229,201,164,190,183,213,249,247,253,216,235,227,245,232,227,245,201,176,208,122,148,148,179,176,201,208,192,169,157,112,140,102,89,120,94,130,182,106,143,126,147,166,70,52,87,37,40,61,71,96,133,110,63,37,40,11,19,4,16,6,18,24,90,56,76,28,42,66,112,102,76,89,94,63,66,37,19,42,70,52,40,37,41,19,24,6,4,16,26,21,8,26,8,0,52,77,45,131,105,105,74,77,52,45,61,96,84,164,133,159,123,151,141,113,190,216,187,234,225,211,197,163,177,211,159,187,151,141,177,113,123,84,213,229,201,237,183,213,192,182,157,147,166,127,120,106,143,169,130,140,71,87,110},\n{239,248,233,249,238,216,222,239,218,233,206,166,192,187,216,247,245,253,216,233,228,246,235,232,248,202,180,211,128,153,154,184,183,207,214,199,177,166,119,148,110,97,129,102,138,189,113,150,133,152,172,77,57,93,37,43,63,75,98,135,115,70,43,46,14,23,6,20,8,20,28,96,61,83,32,48,74,116,104,80,87,93,61,67,37,20,45,66,47,37,31,35,14,20,4,5,15,21,18,5,25,7,0,46,70,39,125,99,100,69,73,51,42,54,87,76,156,124,151,115,143,134,105,183,210,179,230,221,205,192,157,170,208,160,186,149,137,173,111,123,83,210,225,196,233,176,207,190,178,155,140,160,120,118,101,138,170,130,142,64,81,103},\n{236,247,231,251,239,216,228,243,224,236,211,170,194,191,218,244,242,253,214,231,229,246,237,236,250,202,182,214,133,157,161,189,190,214,218,206,185,174,126,156,118,106,138,111,147,196,121,158,140,157,178,85,63,100,39,47,65,79,100,138,120,78,50,53,17,28,9,25,11,22,32,101,66,90,36,54,82,120,105,83,83,92,59,68,37,22,47,62,42,35,25,29,10,17,2,6,14,17,16,3,24,7,0,39,63,35,120,92,96,64,71,51,39,47,79,68,147,115,142,106,135,127,97,175,203,171,225,217,199,187,152,163,206,161,185,148,133,170,109,124,84,206,221,190,228,168,200,188,174,153,132,153,113,115,96,133,169,129,144,57,75,95},\n{233,246,229,251,239,216,233,246,229,239,216,173,195,195,221,240,239,251,211,228,228,245,239,240,251,201,183,216,138,160,166,193,196,219,222,213,192,182,133,163,126,115,146,120,156,203,129,166,147,162,184,93,70,107,42,52,68,85,104,142,125,87,57,60,20,34,13,31,14,25,37,107,71,97,40,61,90,123,106,87,80,91,58,69,37,24,50,58,37,32,20,24,7,14,2,7,14,13,14,2,25,8,2,34,57,31,115,87,93,60,70,52,37,40,71,61,138,107,133,97,127,120,90,166,196,163,219,213,192,182,146,156,203,162,184,147,130,166,107,125,85,201,216,183,222,160,193,185,168,149,124,146,106,111,91,128,168,128,146,50,69,87},\n{229,244,227,252,240,216,237,249,234,242,221,177,198,200,223,236,235,249,208,224,226,243,240,243,252,200,184,216,143,163,171,196,201,224,225,219,199,190,139,170,134,123,155,128,164,209,138,174,154,168,191,101,77,115,45,57,72,91,108,146,131,95,64,68,24,39,17,37,18,29,43,111,76,104,45,67,98,126,107,89,76,89,55,69,37,26,53,53,32,30,16,19,4,11,1,9,13,10,13,1,27,10,4,29,52,28,110,82,90,57,69,53,37,34,63,54,130,98,125,89,119,114,83,158,189,155,214,208,186,177,142,149,200,164,184,146,127,164,107,127,86,196,210,176,216,152,185,181,162,145,115,138,99,107,85,122,167,126,146,44,62,79},\n{225,242,225,252,240,216,241,251,238,245,226,181,200,204,226,231,230,247,204,220,224,240,239,245,252,198,184,216,147,165,176,198,206,229,228,225,205,197,145,176,142,132,163,137,172,216,146,182,162,174,197,109,84,123,49,63,76,97,113,150,138,104,72,75,28,45,22,43,23,33,50,116,80,110,49,74,106,128,107,92,72,87,53,69,37,28,55,49,27,27,12,15,2,8,1,11,13,8,14,1,29,13,6,24,47,25,106,77,88,55,69,55,37,28,56,48,121,90,116,81,111,108,77,149,181,147,208,204,179,173,137,143,198,166,184,146,125,161,107,130,89,190,203,169,210,144,178,177,156,140,107,130,91,103,79,115,164,123,146,37,56,71},\n{221,239,222,251,239,216,244,252,242,247,230,185,202,209,228,227,225,244,199,214,221,237,239,246,251,194,183,215,150,167,179,200,209,232,229,230,211,204,150,182,149,141,172,145,179,222,155,190,169,180,203,118,92,131,54,69,82,104,118,155,144,112,79,84,32,51,26,50,28,39,57,120,85,116,53,80,113,130,106,94,68,84,51,68,37,31,57,44,23,25,9,11,1,6,2,14,14,7,14,2,32,16,9,21,44,24,103,74,86,53,70,59,38,23,49,42,112,81,108,74,104,103,71,141,174,140,202,200,173,168,133,137,196,169,185,147,123,159,108,133,92,184,196,161,203,135,169,171,149,135,98,122,84,98,73,109,161,120,145,31,50,63},\n{217,236,220,250,239,216,247,253,245,248,234,189,205,213,230,221,219,240,194,209,217,233,237,247,250,191,181,213,152,167,182,200,212,235,230,234,216,211,155,187,155,150,180,153,186,227,163,197,177,186,210,127,100,139,59,76,87,111,124,160,152,121,87,92,36,57,32,57,34,44,64,123,89,122,58,86,121,131,105,95,64,81,48,68,37,33,59,40,19,23,6,8,0,5,3,17,14,6,16,3,36,20,13,18,41,23,101,71,86,53,72,62,40,19,42,37,103,73,100,67,98,98,66,132,166,132,196,195,167,164,130,131,194,172,185,148,122,158,109,136,96,177,189,153,196,126,161,166,142,129,90,114,76,93,67,101,157,117,144,26,43,56},\n{212,233,217,249,238,216,249,253,248,249,238,194,207,217,232,216,213,236,188,202,212,229,234,247,247,186,179,211,154,167,184,200,215,237,229,238,220,217,159,191,161,158,187,161,192,232,172,204,184,192,215,136,109,148,65,84,94,119,130,166,159,129,95,101,41,63,37,64,40,51,72,126,92,127,62,92,128,131,103,96,59,78,46,67,37,36,61,36,15,21,4,5,0,4,4,20,15,6,19,6,41,24,17,16,38,24,99,69,86,53,74,67,42,15,36,33,95,66,92,61,92,94,62,124,158,125,190,191,161,161,127,126,192,175,186,150,122,157,111,141,101,169,181,144,189,117,152,159,134,123,81,105,69,87,61,94,152,112,142,21,38,49},\n{208,229,215,247,237,216,250,253,250,250,241,198,210,222,234,210,206,231,181,195,207,223,231,246,245,180,175,207,155,166,185,199,216,238,228,241,224,222,162,195,167,166,194,168,198,237,180,211,192,198,221,144,118,156,72,92,101,127,137,172,166,137,102,109,45,69,43,72,47,58,80,128,96,131,66,98,135,131,101,97,55,74,43,66,37,38,63,31,12,19,3,3,0,3,6,24,16,7,22,8,46,30,22,15,37,25,98,68,87,54,78,73,46,12,31,29,87,59,85,55,86,91,58,116,151,118,184,187,155,157,125,122,190,179,187,152,122,156,114,145,106,161,173,135,181,109,144,152,126,116,73,97,61,81,55,87,147,108,139,16,32,42},\n{203,226,212,244,236,216,250,252,252,250,244,203,212,226,236,204,199,226,174,188,201,217,227,244,241,174,171,203,156,164,186,197,216,239,226,244,226,227,164,197,171,174,201,174,203,241,188,217,199,204,226,153,127,165,79,100,108,136,144,178,174,145,109,117,50,75,49,79,54,65,88,130,99,136,70,103,141,130,99,97,50,70,41,65,37,41,65,27,9,17,3,1,1,3,9,27,17,9,26,12,52,36,27,14,37,27,97,67,89,56,82,79,50,9,26,27,79,52,79,50,82,89,56,108,144,112,178,183,150,154,123,117,188,183,188,154,123,156,117,150,112,153,165,127,174,100,136,145,117,109,65,88,54,75,49,79,141,103,136,12,27,36},\n{198,222,210,241,234,216,250,250,253,250,247,208,215,229,237,198,192,221,166,180,194,211,222,241,237,168,167,198,155,162,185,195,216,238,224,246,228,231,166,199,175,181,207,180,207,245,195,223,206,210,231,161,135,173,87,109,116,144,151,184,181,152,116,126,55,81,55,87,61,73,97,131,101,139,74,108,147,128,96,97,45,66,38,63,37,43,66,24,6,16,3,0,3,3,12,31,19,12,31,16,59,42,32,15,37,29,98,68,91,58,86,85,55,7,22,25,72,46,73,46,78,87,54,101,137,106,172,179,145,152,122,114,187,187,190,157,125,156,122,155,118,144,156,118,166,92,127,137,109,102,58,80,47,69,43,72,135,98,131,8,22,30},\n{194,217,207,238,232,216,249,248,253,249,249,212,217,233,238,192,184,215,158,172,187,204,217,238,232,161,161,192,154,159,184,191,215,237,220,247,229,234,167,200,179,188,212,186,211,247,202,229,213,216,236,169,144,181,95,117,124,152,158,190,189,159,123,134,59,87,61,94,69,81,105,131,103,142,78,112,152,126,92,96,41,62,36,61,37,46,67,20,4,15,4,0,5,4,15,36,21,15,36,21,66,49,38,16,38,33,99,69,94,62,92,92,61,6,19,24,65,41,67,42,74,86,53,94,130,101,166,175,141,150,122,111,186,191,192,161,127,157,126,161,125,136,148,109,159,84,119,129,101,95,51,72,40,63,37,64,128,92,127,6,17,24},\n{189,213,205,234,230,216,247,245,253,248,250,217,220,236,239,186,177,210,150,163,180,197,211,234,227,153,155,186,152,155,182,187,212,235,216,247,230,237,167,200,181,194,217,191,213,250,209,233,219,221,240,177,153,189,103,126,132,161,166,196,196,166,129,142,64,93,67,101,76,90,114,131,105,144,81,117,157,123,89,95,36,58,33,59,37,48,68,17,3,14,6,0,8,5,19,40,23,19,42,26,73,56,43,18,41,37,101,71,98,66,98,100,67,6,16,23,59,36,62,40,72,86,53,87,124,96,160,172,136,148,122,109,185,195,194,164,130,158,131,167,132,127,139,100,152,76,111,121,92,87,44,64,34,57,32,57,121,86,122,3,13,20},\n{185,209,202,230,228,216,244,242,252,247,251,221,222,239,239,180,169,203,141,155,172,190,204,230,222,145,149,179,150,150,179,182,209,232,211,246,229,239,167,200,183,199,221,194,215,251,214,237,225,227,244,184,161,196,112,135,141,169,174,202,203,171,135,149,68,98,73,109,84,98,122,130,106,145,84,120,161,120,85,94,32,53,31,57,37,51,68,14,2,14,9,1,11,6,23,44,25,23,49,31,81,63,50,21,44,42,103,74,103,71,104,108,74,7,14,24,54,32,59,38,70,86,53,82,118,92,155,169,133,147,123,108,185,200,196,168,133,159,137,173,140,118,131,92,144,69,104,112,84,79,39,57,28,51,26,50,113,80,116,2,9,16},\n{181,204,200,226,226,216,241,238,251,245,252,225,225,242,240,174,162,197,132,146,163,182,197,225,216,137,142,172,147,145,176,176,206,229,205,245,228,239,165,198,184,204,224,198,216,252,220,240,230,231,247,190,169,203,121,144,149,178,181,208,210,177,140,156,72,103,79,115,91,107,130,128,107,146,87,123,164,116,80,92,28,49,28,55,37,53,69,11,1,13,12,2,15,8,27,49,27,28,56,37,90,71,56,24,47,48,106,77,108,77,111,116,81,8,14,25,49,29,55,37,69,88,55,76,113,89,150,166,130,146,125,107,184,204,198,173,137,161,143,179,147,109,123,84,138,63,97,104,75,72,33,50,23,45,22,43,106,74,110,1,6,13},\n{177,200,198,221,223,216,237,234,249,242,252,229,227,244,240,168,154,191,123,138,155,174,190,219,209,128,134,164,143,139,171,170,201,224,199,243,225,240,163,196,184,208,226,200,216,252,224,243,235,236,249,196,176,210,130,152,158,185,189,214,216,181,145,162,76,107,85,122,99,115,138,126,107,146,89,126,167,111,76,89,24,45,26,53,37,55,69,9,1,13,16,4,19,11,32,53,30,34,63,44,98,79,62,29,52,54,110,82,114,83,119,125,89,10,13,28,45,27,53,37,69,90,57,72,108,86,146,164,127,146,127,107,184,208,200,177,142,164,149,186,155,101,115,77,131,57,91,95,68,64,29,43,18,39,17,37,98,67,104,1,4,10},\n{173,195,195,216,221,216,233,229,246,239,251,233,229,246,239,162,147,184,115,129,146,166,182,213,203,120,126,156,138,133,166,163,196,219,192,240,222,239,160,193,183,211,228,201,216,251,228,245,239,240,251,201,183,216,138,160,166,193,196,219,222,185,149,168,80,111,91,128,106,124,146,123,106,146,91,128,168,107,71,87,20,40,24,50,37,58,69,7,2,14,20,7,24,14,37,58,32,40,71,50,107,87,69,34,57,61,115,87,120,90,127,133,97,13,14,31,42,25,52,37,70,93,60,68,104,85,142,162,125,147,130,107,184,213,203,182,146,166,156,192,163,93,107,70,125,52,85,87,60,57,25,37,14,34,13,31,90,61,97,2,2,8},\n{170,191,194,211,218,216,228,224,243,236,251,236,231,247,239,157,140,178,106,121,138,158,174,206,196,111,118,147,133,126,161,156,190,214,185,236,218,237,157,189,182,214,229,202,214,250,231,246,242,244,253,206,190,221,147,168,175,200,203,225,228,188,153,174,83,115,96,133,113,132,153,120,105,144,92,129,169,101,66,83,17,36,22,47,37,59,68,6,2,14,25,10,29,17,42,62,35,47,79,57,115,95,75,39,63,68,120,92,127,97,135,142,106,17,16,35,39,24,51,39,71,96,64,65,100,84,138,161,124,148,133,109,185,217,206,187,152,170,163,199,171,85,100,63,120,47,79,78,53,50,22,32,11,28,9,25,82,54,90,3,0,7},\n{166,187,192,206,216,216,222,218,239,233,249,239,233,248,238,152,133,172,97,113,129,150,166,199,189,102,110,138,128,119,154,148,183,207,177,232,214,235,153,184,180,216,228,202,211,248,233,246,245,247,253,210,196,225,156,176,183,207,210,230,233,190,155,178,87,118,101,138,120,140,160,116,104,142,93,130,170,96,61,80,14,32,20,45,37,61,67,5,4,15,31,14,35,20,47,66,37,54,87,64,124,103,81,46,70,76,125,99,134,105,143,151,115,21,18,39,37,25,51,42,73,100,69,63,98,83,135,160,123,149,137,111,186,221,208,192,157,173,170,205,179,77,93,57,115,43,75,70,46,43,20,28,8,23,6,20,74,48,83,5,0,7},\n{164,183,190,201,213,216,216,212,234,229,247,242,234,249,237,147,126,166,89,106,120,143,157,192,182,94,102,130,122,112,148,140,176,201,169,227,208,232,148,179,176,216,227,201,208,245,235,245,247,249,253,213,201,229,164,183,190,213,216,234,237,192,157,182,89,120,106,143,127,147,166,112,102,140,94,130,169,90,56,76,11,28,19,42,37,63,66,4,6,16,37,19,41,24,52,70,40,61,96,71,133,110,87,52,77,84,131,105,141,113,151,159,123,26,21,45,37,26,52,45,77,105,74,61,96,84,133,159,123,151,141,113,187,225,211,197,163,177,177,211,187,70,87,52,110,40,71,63,40,37,18,24,6,19,4,16,66,42,76,8,0,8},\n{161,179,189,196,210,216,210,206,230,225,245,245,236,249,236,143,120,160,81,98,112,135,149,184,175,85,93,121,115,104,140,131,169,193,161,222,202,228,142,173,173,216,225,199,204,242,235,244,249,251,253,215,206,232,172,190,198,219,222,239,241,192,159,185,92,122,110,147,133,155,171,107,99,137,94,129,168,84,51,72,8,24,17,39,37,64,65,4,8,17,43,23,47,28,57,74,43,69,104,78,141,118,93,60,84,92,138,113,149,122,160,168,132,31,25,51,37,28,54,49,80,111,80,61,94,85,131,159,124,154,146,116,188,229,214,202,169,181,184,217,194,63,82,47,106,38,68,55,34,31,18,21,4,14,2,12,59,36,69,11,0,9},\n{159,176,188,191,207,216,203,200,225,221,242,247,237,249,234,139,115,154,74,91,104,128,141,176,168,77,85,112,109,97,133,123,161,186,152,216,196,224,136,166,168,215,223,196,199,238,235,242,249,252,252,216,209,235,179,196,204,223,228,242,244,192,159,187,94,124,114,150,138,162,176,102,97,133,94,128,166,78,45,68,6,20,16,37,37,65,64,4,11,19,50,28,53,32,62,77,46,77,112,85,149,125,99,67,92,101,144,120,156,130,168,176,141,37,29,57,38,30,57,54,85,117,87,61,94,87,129,159,125,156,151,120,189,233,216,208,175,185,191,222,202,57,77,44,103,37,66,49,30,25,18,18,4,11,1,9,51,31,62,15,1,11},\n{158,172,187,186,205,216,196,194,219,217,239,248,238,248,233,136,110,149,67,85,96,121,132,168,161,69,77,103,101,89,125,114,153,178,143,209,188,218,130,159,163,213,219,193,194,234,234,239,249,253,250,216,212,236,186,201,211,228,233,246,247,191,159,189,95,125,117,152,143,168,180,97,93,128,93,126,163,71,40,64,5,17,15,34,37,66,62,5,14,20,57,34,59,36,67,81,48,85,121,92,157,132,104,75,100,110,152,128,164,139,176,184,150,44,35,65,40,34,60,59,90,123,94,62,94,90,128,160,127,160,156,125,191,236,219,213,182,189,198,228,209,52,73,41,101,37,64,42,25,20,19,17,4,7,0,6,44,25,55,19,3,14},\n{157,169,186,181,202,216,189,187,213,212,235,249,238,247,231,133,106,144,61,80,89,114,124,160,154,62,69,95,94,81,117,105,144,170,135,202,181,212,123,152,157,211,215,188,188,229,232,236,248,253,248,216,215,237,192,205,216,231,237,248,249,189,158,190,96,125,120,154,147,173,184,91,90,123,92,124,159,65,35,59,4,14,15,31,37,67,60,6,18,22,64,40,66,41,72,84,51,93,129,99,164,139,109,83,109,118,159,136,172,148,184,191,158,51,41,72,42,38,65,65,96,130,101,64,95,94,128,161,129,163,162,130,193,239,222,218,188,194,205,233,215,48,69,38,99,37,63,37,22,16,21,16,5,5,0,4,38,20,48,24,5,17},\n{156,167,186,176,200,216,181,181,207,208,231,250,239,246,229,131,102,140,55,75,82,108,117,153,147,55,61,87,87,73,108,97,135,161,126,195,173,206,116,144,151,207,210,183,181,223,229,231,246,253,245,214,216,237,198,209,221,234,241,250,250,186,157,189,97,124,122,155,151,178,186,85,86,118,91,121,155,58,31,55,3,11,14,29,37,67,58,7,22,24,72,46,72,45,77,86,53,100,136,106,171,145,113,92,118,127,166,145,180,156,192,198,166,58,47,80,46,43,70,72,102,137,109,67,96,98,129,162,132,167,168,135,195,242,224,222,195,198,211,237,221,44,67,37,98,39,64,32,19,12,24,16,7,3,0,3,32,16,42,30,8,22}\n}\n"
  },
  {
    "path": "src/anorms.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n{-0.525731, 0.000000, 0.850651}, \n{-0.442863, 0.238856, 0.864188}, \n{-0.295242, 0.000000, 0.955423}, \n{-0.309017, 0.500000, 0.809017}, \n{-0.162460, 0.262866, 0.951056}, \n{0.000000, 0.000000, 1.000000}, \n{0.000000, 0.850651, 0.525731}, \n{-0.147621, 0.716567, 0.681718}, \n{0.147621, 0.716567, 0.681718}, \n{0.000000, 0.525731, 0.850651}, \n{0.309017, 0.500000, 0.809017}, \n{0.525731, 0.000000, 0.850651}, \n{0.295242, 0.000000, 0.955423}, \n{0.442863, 0.238856, 0.864188}, \n{0.162460, 0.262866, 0.951056}, \n{-0.681718, 0.147621, 0.716567}, \n{-0.809017, 0.309017, 0.500000}, \n{-0.587785, 0.425325, 0.688191}, \n{-0.850651, 0.525731, 0.000000}, \n{-0.864188, 0.442863, 0.238856}, \n{-0.716567, 0.681718, 0.147621}, \n{-0.688191, 0.587785, 0.425325}, \n{-0.500000, 0.809017, 0.309017}, \n{-0.238856, 0.864188, 0.442863}, \n{-0.425325, 0.688191, 0.587785}, \n{-0.716567, 0.681718, -0.147621}, \n{-0.500000, 0.809017, -0.309017}, \n{-0.525731, 0.850651, 0.000000}, \n{0.000000, 0.850651, -0.525731}, \n{-0.238856, 0.864188, -0.442863}, \n{0.000000, 0.955423, -0.295242}, \n{-0.262866, 0.951056, -0.162460}, \n{0.000000, 1.000000, 0.000000}, \n{0.000000, 0.955423, 0.295242}, \n{-0.262866, 0.951056, 0.162460}, \n{0.238856, 0.864188, 0.442863}, \n{0.262866, 0.951056, 0.162460}, \n{0.500000, 0.809017, 0.309017}, \n{0.238856, 0.864188, -0.442863}, \n{0.262866, 0.951056, -0.162460}, \n{0.500000, 0.809017, -0.309017}, \n{0.850651, 0.525731, 0.000000}, \n{0.716567, 0.681718, 0.147621}, \n{0.716567, 0.681718, -0.147621}, \n{0.525731, 0.850651, 0.000000}, \n{0.425325, 0.688191, 0.587785}, \n{0.864188, 0.442863, 0.238856}, \n{0.688191, 0.587785, 0.425325}, \n{0.809017, 0.309017, 0.500000}, \n{0.681718, 0.147621, 0.716567}, \n{0.587785, 0.425325, 0.688191}, \n{0.955423, 0.295242, 0.000000}, \n{1.000000, 0.000000, 0.000000}, \n{0.951056, 0.162460, 0.262866}, \n{0.850651, -0.525731, 0.000000}, \n{0.955423, -0.295242, 0.000000}, \n{0.864188, -0.442863, 0.238856}, \n{0.951056, -0.162460, 0.262866}, \n{0.809017, -0.309017, 0.500000}, \n{0.681718, -0.147621, 0.716567}, \n{0.850651, 0.000000, 0.525731}, \n{0.864188, 0.442863, -0.238856}, \n{0.809017, 0.309017, -0.500000}, \n{0.951056, 0.162460, -0.262866}, \n{0.525731, 0.000000, -0.850651}, \n{0.681718, 0.147621, -0.716567}, \n{0.681718, -0.147621, -0.716567}, \n{0.850651, 0.000000, -0.525731}, \n{0.809017, -0.309017, -0.500000}, \n{0.864188, -0.442863, -0.238856}, \n{0.951056, -0.162460, -0.262866}, \n{0.147621, 0.716567, -0.681718}, \n{0.309017, 0.500000, -0.809017}, \n{0.425325, 0.688191, -0.587785}, \n{0.442863, 0.238856, -0.864188}, \n{0.587785, 0.425325, -0.688191}, \n{0.688191, 0.587785, -0.425325}, \n{-0.147621, 0.716567, -0.681718}, \n{-0.309017, 0.500000, -0.809017}, \n{0.000000, 0.525731, -0.850651}, \n{-0.525731, 0.000000, -0.850651}, \n{-0.442863, 0.238856, -0.864188}, \n{-0.295242, 0.000000, -0.955423}, \n{-0.162460, 0.262866, -0.951056}, \n{0.000000, 0.000000, -1.000000}, \n{0.295242, 0.000000, -0.955423}, \n{0.162460, 0.262866, -0.951056}, \n{-0.442863, -0.238856, -0.864188}, \n{-0.309017, -0.500000, -0.809017}, \n{-0.162460, -0.262866, -0.951056}, \n{0.000000, -0.850651, -0.525731}, \n{-0.147621, -0.716567, -0.681718}, \n{0.147621, -0.716567, -0.681718}, \n{0.000000, -0.525731, -0.850651}, \n{0.309017, -0.500000, -0.809017}, \n{0.442863, -0.238856, -0.864188}, \n{0.162460, -0.262866, -0.951056}, \n{0.238856, -0.864188, -0.442863}, \n{0.500000, -0.809017, -0.309017}, \n{0.425325, -0.688191, -0.587785}, \n{0.716567, -0.681718, -0.147621}, \n{0.688191, -0.587785, -0.425325}, \n{0.587785, -0.425325, -0.688191}, \n{0.000000, -0.955423, -0.295242}, \n{0.000000, -1.000000, 0.000000}, \n{0.262866, -0.951056, -0.162460}, \n{0.000000, -0.850651, 0.525731}, \n{0.000000, -0.955423, 0.295242}, \n{0.238856, -0.864188, 0.442863}, \n{0.262866, -0.951056, 0.162460}, \n{0.500000, -0.809017, 0.309017}, \n{0.716567, -0.681718, 0.147621}, \n{0.525731, -0.850651, 0.000000}, \n{-0.238856, -0.864188, -0.442863}, \n{-0.500000, -0.809017, -0.309017}, \n{-0.262866, -0.951056, -0.162460}, \n{-0.850651, -0.525731, 0.000000}, \n{-0.716567, -0.681718, -0.147621}, \n{-0.716567, -0.681718, 0.147621}, \n{-0.525731, -0.850651, 0.000000}, \n{-0.500000, -0.809017, 0.309017}, \n{-0.238856, -0.864188, 0.442863}, \n{-0.262866, -0.951056, 0.162460}, \n{-0.864188, -0.442863, 0.238856}, \n{-0.809017, -0.309017, 0.500000}, \n{-0.688191, -0.587785, 0.425325}, \n{-0.681718, -0.147621, 0.716567}, \n{-0.442863, -0.238856, 0.864188}, \n{-0.587785, -0.425325, 0.688191}, \n{-0.309017, -0.500000, 0.809017}, \n{-0.147621, -0.716567, 0.681718}, \n{-0.425325, -0.688191, 0.587785}, \n{-0.162460, -0.262866, 0.951056}, \n{0.442863, -0.238856, 0.864188}, \n{0.162460, -0.262866, 0.951056}, \n{0.309017, -0.500000, 0.809017}, \n{0.147621, -0.716567, 0.681718}, \n{0.000000, -0.525731, 0.850651}, \n{0.425325, -0.688191, 0.587785}, \n{0.587785, -0.425325, 0.688191}, \n{0.688191, -0.587785, 0.425325}, \n{-0.955423, 0.295242, 0.000000}, \n{-0.951056, 0.162460, 0.262866}, \n{-1.000000, 0.000000, 0.000000}, \n{-0.850651, 0.000000, 0.525731}, \n{-0.955423, -0.295242, 0.000000}, \n{-0.951056, -0.162460, 0.262866}, \n{-0.864188, 0.442863, -0.238856}, \n{-0.951056, 0.162460, -0.262866}, \n{-0.809017, 0.309017, -0.500000}, \n{-0.864188, -0.442863, -0.238856}, \n{-0.951056, -0.162460, -0.262866}, \n{-0.809017, -0.309017, -0.500000}, \n{-0.681718, 0.147621, -0.716567}, \n{-0.681718, -0.147621, -0.716567}, \n{-0.850651, 0.000000, -0.525731}, \n{-0.688191, 0.587785, -0.425325}, \n{-0.587785, 0.425325, -0.688191}, \n{-0.425325, 0.688191, -0.587785}, \n{-0.425325, -0.688191, -0.587785}, \n{-0.587785, -0.425325, -0.688191}, \n{-0.688191, -0.587785, -0.425325}, \n"
  },
  {
    "path": "src/bspfile.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifndef __BSPFILE_H__\n#define __BSPFILE_H__\n\n// upper design bounds\n\n#define\tMAX_MAP_HULLS\t\t4\n\n#define\tMAX_MAP_MODELS\t\t4096\n#define\tMAX_MAP_BRUSHES\t\t4096\n#define\tMAX_MAP_ENTITIES\t1024\n#define\tMAX_MAP_ENTSTRING\t65536\n\n#define\tMAX_MAP_PLANES\t\t8192\n#define\tMAX_MAP_NODES\t\t32767\t\t// because negative shorts are contents\n#define\tMAX_MAP_CLIPNODES\t32767\t\t//\n#define\tMAX_MAP_LEAFS\t\t32767\t\t// \n#define\tMAX_MAP_VERTS\t\t65535\n#define\tMAX_MAP_FACES\t\t65535\n#define\tMAX_MAP_MARKSURFACES 65535\n#define\tMAX_MAP_TEXINFO\t\t4096\n#define\tMAX_MAP_EDGES\t\t256000\n#define\tMAX_MAP_SURFEDGES\t512000\n#define\tMAX_MAP_MIPTEX\t\t0x200000\n#define\tMAX_MAP_LIGHTING\t0x100000\n#define\tMAX_MAP_VISIBILITY\t0x100000\n\n// key / value pair sizes\n\n#define\tMAX_KEY\t\t32\n#define\tMAX_VALUE\t1024\n\n\n//=============================================================================\n\n\n#define Q1_BSPVERSION     29\n#define HL_BSPVERSION     30\n#define Q1_BSPVERSION29a  (('2') + ('P' << 8) + ('S' << 16) + ('B' << 24))\n#define Q1_BSPVERSION2    (('B') + ('S' << 8) + ('P' << 16) + ('2' << 24))\n\ntypedef struct\n{\n\tint\t\tfileofs, filelen;\n} lump_t;\n\n#define\tLUMP_ENTITIES\t0\n#define\tLUMP_PLANES\t\t1\n#define\tLUMP_TEXTURES\t2\n#define\tLUMP_VERTEXES\t3\n#define\tLUMP_VISIBILITY\t4\n#define\tLUMP_NODES\t\t5\n#define\tLUMP_TEXINFO\t6\n#define\tLUMP_FACES\t\t7\n#define\tLUMP_LIGHTING\t8\n#define\tLUMP_CLIPNODES\t9\n#define\tLUMP_LEAFS\t\t10\n#define\tLUMP_MARKSURFACES 11\n#define\tLUMP_EDGES\t\t12\n#define\tLUMP_SURFEDGES\t13\n#define\tLUMP_MODELS\t\t14\n\n#define\tHEADER_LUMPS\t15\n\n#if defined(_MSC_VER) && !defined(__attribute__)\n#define __attribute__(A) /**/\n#endif\n\ntypedef struct\n{\n\tfloat       mins[3], maxs[3];\n\tfloat       origin[3];\n\tint\t        headnode[MAX_MAP_HULLS];\n\tint         visleafs;                    // not including the solid leaf 0\n\tint         firstface, numfaces;\n} dmodel_t __attribute__((aligned(1)));\n\n\ntypedef struct\n{\n\tint         version;\n\tlump_t      lumps[HEADER_LUMPS];\n} dheader_t;\n\ntypedef struct\n{\n\tint         nummiptex;\n\tint         dataofs[4];                  // [nummiptex]\n} dmiptexlump_t;\n\n#define\tMIPLEVELS\t4\ntypedef struct miptex_s\n{\n\tchar        name[16];\n\tunsigned    width, height;\n\tunsigned    offsets[MIPLEVELS];\t\t// four mip maps stored\n} miptex_t;\n\n\ntypedef struct\n{\n\tfloat       point[3];\n} dvertex_t;\n\n\n// 0-2 are axial planes\n#define\tPLANE_X\t\t\t0\n#define\tPLANE_Y\t\t\t1\n#define\tPLANE_Z\t\t\t2\n\n// 3-5 are non-axial planes snapped to the nearest\n#define\tPLANE_ANYX\t\t3\n#define\tPLANE_ANYY\t\t4\n#define\tPLANE_ANYZ\t\t5\n\ntypedef struct\n{\n\tfloat       normal[3];\n\tfloat       dist;\n\tint         type;             // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate\n} dplane_t;\n\n\n\n#define\tCONTENTS_EMPTY\t\t-1\n#define\tCONTENTS_SOLID\t\t-2\n#define\tCONTENTS_WATER\t\t-3\n#define\tCONTENTS_SLIME\t\t-4\n#define\tCONTENTS_LAVA\t\t-5\n#define\tCONTENTS_SKY\t\t-6\n\ntypedef struct\n{\n\tint             planenum;\n\tshort           children[2];\t// negative numbers are -(leafs+1), not nodes\n\tshort           mins[3];        // for sphere culling\n\tshort           maxs[3];\n\tunsigned short  firstface;\n\tunsigned short  numfaces;       // counting both sides\n} dnode_t;\n\ntypedef struct\n{\n\tint             planenum;\n\tint             children[2];    // negative numbers are -(leafs+1), not nodes\n\tshort           mins[3];        // for sphere culling\n\tshort           maxs[3];\n\tunsigned int    firstface;\n\tunsigned int    numfaces;       // counting both sides\n} dnode29a_t;\n\ntypedef struct\n{\n\tint             planenum;\n\tint             children[2];    // negative numbers are -(leafs+1), not nodes\n\tfloat           mins[3];        // for sphere culling\n\tfloat           maxs[3];\n\tunsigned int    firstface;\n\tunsigned int    numfaces;       // counting both sides\n} dnode_bsp2_t;\n\ntypedef struct\n{\n\tint         planenum;\n\tshort       children[2];        // negative numbers are contents\n} dclipnode_t;\n\ntypedef struct\n{\n\tint         planenum;\n\tint         children[2];        // negative numbers are contents\n} dclipnode29a_t;\n\ntypedef struct\n{\n\tint         planenum;\n\tint         children[2];        // negative numbers are contents\n} mclipnode_t;\n\ntypedef struct texinfo_s\n{\n\tfloat\t\tvecs[2][4];         // [s/t][xyz offset]\n\tint\t\t\tmiptex;\n\tint\t\t\tflags;\n} texinfo_t;\n#define\tTEX_SPECIAL\t\t1           // sky or slime, no lightmap or 256 subdivision\n\n// note that edge 0 is never used, because negative edge nums are used for\n// counterclockwise use of the edge in a face\ntypedef struct {\n\tunsigned short\tv[2];           // vertex numbers\n} dedge_t;\n\ntypedef struct {\n\tunsigned int\tv[2];           // vertex numbers\n} dedge29a_t;\n\n#define\tMAXLIGHTMAPS\t4\ntypedef struct {\n\tshort       planenum;\n\tshort       side;\n\n\tint         firstedge;      // we must support > 64k edges\n\tshort       numedges;\t\n\tshort       texinfo;\n\n\t// lighting info\n\tbyte        styles[MAXLIGHTMAPS];\n\tint         lightofs;       // start of [numstyles*surfsize] samples\n} dface_t;\n\ntypedef struct {\n\tint         planenum;\n\tint         side;\n\n\tint         firstedge;      // we must support > 64k edges\n\tint         numedges;\n\tint         texinfo;\n\n\t// lighting info\n\tbyte        styles[MAXLIGHTMAPS];\n\tint         lightofs;       // start of [numstyles*surfsize] samples\n} dface29a_t;\n\n#define\tAMBIENT_WATER   0\n#define\tAMBIENT_SKY     1\n#define\tAMBIENT_SLIME   2\n#define\tAMBIENT_LAVA    3\n\n#define\tNUM_AMBIENTS    4       // automatic ambient sounds\n\n// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas\n// all other leafs need visibility info\ntypedef struct {\n\tint            contents;\n\tint            visofs;          // -1 = no visibility info\n\n\tshort          mins[3];         // for frustum culling\n\tshort          maxs[3];\n\n\tunsigned short firstmarksurface;\n\tunsigned short nummarksurfaces;\n\n\tbyte           ambient_level[NUM_AMBIENTS];\n} dleaf_t;\n\ntypedef struct {\n\tint            contents;\n\tint            visofs;          // -1 = no visibility info\n\n\tshort          mins[3];         // for frustum culling\n\tshort          maxs[3];\n\n\tunsigned int   firstmarksurface;\n\tunsigned int   nummarksurfaces;\n\n\tbyte           ambient_level[NUM_AMBIENTS];\n} dleaf29a_t;\n\ntypedef struct {\n\tint            contents;\n\tint            visofs;          // -1 = no visibility info\n\n\tfloat          mins[3];         // for frustum culling\n\tfloat          maxs[3];\n\n\tunsigned int   firstmarksurface;\n\tunsigned int   nummarksurfaces;\n\n\tbyte           ambient_level[NUM_AMBIENTS];\n} dleaf_bsp2_t;\n\ntypedef struct {\n\tunsigned short lmwidth;\n\tunsigned short lmheight;\n\tint            lightofs;\n\tfloat          vecs[2][4];\n} dlminfo_t;\n\n#endif /* !__BSPFILE_H__ */\n"
  },
  {
    "path": "src/cd_linux.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: cd_linux.c,v 1.9 2007-10-04 13:48:11 dkure Exp $\n*/\n// cd_linux.c\n\n#include <stdio.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <sys/ioctl.h>\n#include <sys/file.h>\n#include <sys/types.h>\n#include <fcntl.h>\n#include <string.h>\n#include <time.h>\n#include <errno.h>\n#ifdef __FreeBSD__\n#include <sys/cdio.h>\n#else\n#include <linux/cdrom.h>\n#endif\n\n#include \"quakedef.h\"\n#include \"cdaudio.h\"\n#include \"qsound.h\"\n\nstatic qbool cdValid = false;\nstatic qbool\tplaying = false;\nstatic qbool\twasPlaying = false;\nstatic qbool\tinitialized = false;\nstatic qbool\tenabled = true;\nstatic qbool playLooping = false;\nstatic float\tcdvolume;\nstatic byte \tremap[100];\nstatic byte\t\tplayTrack;\nstatic byte\t\tmaxTrack;\n\nstatic int cdfile = -1;\nstatic char cd_dev[64] = \"/dev/cdrom\";\n\nstatic void CDAudio_Eject(void)\n{\n\tif (cdfile == -1 || !enabled)\n\t\treturn; // no cd init'd\n\n#ifdef __FreeBSD__\n\tif (ioctl(cdfile, CDIOCEJECT) == -1)\n\t\tCom_DPrintf (\"ioctl cdioceject failed\\n\");\n#else\n\tif ( ioctl(cdfile, CDROMEJECT) == -1 )\n\t\tCom_DPrintf (\"ioctl cdromeject failed\\n\");\n#endif\n}\n\n\nstatic void CDAudio_CloseDoor(void)\n{\n\tif (cdfile == -1 || !enabled)\n\t\treturn; // no cd init'd\n\n#ifdef __FreeBSD__\n\tif (ioctl(cdfile, CDIOCCLOSE) == -1)\n\t\tCom_DPrintf (\"ioctl cdiocclose failed\\n\");\n#else\n\tif ( ioctl(cdfile, CDROMCLOSETRAY) == -1 )\n\t\tCom_DPrintf (\"ioctl cdromclosetray failed\\n\");\n#endif\n}\n\nstatic int CDAudio_GetAudioDiskInfo(void)\n{\n#ifdef __FreeBSD__\n\tstruct ioc_toc_header tochdr;\n#else\n\tstruct cdrom_tochdr tochdr;\n#endif\n\n\tcdValid = false;\n\n#ifdef __FreeBSD__\n\tif (ioctl(cdfile, CDIOREADTOCHEADER, &tochdr) == -1)\n\t{\n\t\tCom_DPrintf (\"ioctl cdioreadtocheader failed\\n\");\n#else\n\tif ( ioctl(cdfile, CDROMREADTOCHDR, &tochdr) == -1 )\n    {\n      Com_DPrintf (\"ioctl cdromreadtochdr failed\\n\");\n#endif\n\t  return -1;\n    }\n\n#ifdef __FreeBSD__\n\tif (tochdr.starting_track < 1)\n#else\n\tif (tochdr.cdth_trk0 < 1)\n#endif\n\t{\n\t\tCom_DPrintf (\"CDAudio: no music tracks\\n\");\n\t\treturn -1;\n\t}\n\n\tcdValid = true;\n#ifdef __FreeBSD__\n\tmaxTrack = tochdr.ending_track;\n#else\n\tmaxTrack = tochdr.cdth_trk1;\n#endif\n\n\treturn 0;\n}\n\n\nvoid CDAudio_Play(byte track, qbool looping)\n{\n#ifdef __FreeBSD__\n\tstruct ioc_read_toc_entry entry;\n\tstruct cd_toc_entry toc_buffer;\n\tstruct ioc_play_track ti;\n#else\n\tstruct cdrom_tocentry entry;\n\tstruct cdrom_ti ti;\n#endif\n\n\tif (cdfile == -1 || !enabled)\n\t\treturn;\n\n\tif (!cdValid)\n\t{\n\t\tCDAudio_GetAudioDiskInfo();\n\t\tif (!cdValid)\n\t\t\treturn;\n\t}\n\n\ttrack = remap[track];\n\n\tif (track < 1 || track > maxTrack)\n\t{\n\t\tCom_DPrintf (\"CDAudio: Bad track number %u.\\n\", track);\n\t\treturn;\n\t}\n\n#ifdef __FreeBSD__\n\t#define CDROM_DATA_TRACK 4\n\tbzero((char *)&toc_buffer, sizeof(toc_buffer));\n\tentry.data_len = sizeof(toc_buffer);\n\tentry.data = &toc_buffer;\n\t// don't try to play a non-audio track\n\tentry.starting_track = track;\n\tentry.address_format = CD_MSF_FORMAT;\n    if ( ioctl(cdfile, CDIOREADTOCENTRYS, &entry) == -1 )\n\t{\n\t\tCom_DPrintf(\"ioctl cdromreadtocentry failed\\n\");\n\t\treturn;\n\t}\n\tif (toc_buffer.control == CDROM_DATA_TRACK)\n#else\n\t// don't try to play a non-audio track\n\tentry.cdte_track = track;\n\tentry.cdte_format = CDROM_MSF;\n    if ( ioctl(cdfile, CDROMREADTOCENTRY, &entry) == -1 )\n\t{\n\t\tCom_DPrintf (\"ioctl cdromreadtocentry failed\\n\");\n\t\treturn;\n\t}\n\tif (entry.cdte_ctrl == CDROM_DATA_TRACK)\n#endif\n\t{\n\t\tCom_Printf (\"CDAudio: track %i is not audio\\n\", track);\n\t\treturn;\n\t}\n\n\tif (playing)\n\t{\n\t\tif (playTrack == track)\n\t\t\treturn;\n\t\tCDAudio_Stop();\n\t}\n\n#ifdef __FreeBSD__\n\tti.start_track = track;\n\tti.end_track = track;\n\tti.start_index = 1;\n\tti.end_index = 99;\n#else\n\tti.cdti_trk0 = track;\n\tti.cdti_trk1 = track;\n\tti.cdti_ind0 = 1;\n\tti.cdti_ind1 = 99;\n#endif\n\n#ifdef __FreeBSD__\n\tif (ioctl(cdfile, CDIOCPLAYTRACKS, &ti) == -1)\n\t{\n\t\tCom_DPrintf (\"ioctl cdiocplaytracks failed\\n\");\n#else\n\tif ( ioctl(cdfile, CDROMPLAYTRKIND, &ti) == -1 )\n    {\n\t\tCom_DPrintf (\"ioctl cdromplaytrkind failed\\n\");\n#endif\n\t\treturn;\n    }\n\n#ifdef __FreeBSD__\n\tif (ioctl(cdfile, CDIOCRESUME) == -1)\n\t\tCom_DPrintf (\"ioctl cdiocresume failed\\n\");\n#else\n\tif ( ioctl(cdfile, CDROMRESUME) == -1 )\n\t\tCom_DPrintf (\"ioctl cdromresume failed\\n\");\n#endif\n\n\tplayLooping = looping;\n\tplayTrack = track;\n\tplaying = true;\n\n\tif (cdvolume == 0.0)\n\t\tCDAudio_Pause ();\n}\n\n\nvoid CDAudio_Stop(void)\n{\n\tif (cdfile == -1 || !enabled)\n\t\treturn;\n\n\tif (!playing)\n\t\treturn;\n\n#ifdef __FreeBSD__\n\tif (ioctl(cdfile, CDIOCSTOP) == -1)\n\t\tCom_DPrintf (\"ioctl cdiocstop failed (%d)\\n\", errno);\n#else\n\tif ( ioctl(cdfile, CDROMSTOP) == -1 )\n\t\tCom_DPrintf (\"ioctl cdromstop failed (%d)\\n\", errno);\n#endif\n\n\twasPlaying = false;\n\tplaying = false;\n}\n\nvoid CDAudio_Pause(void)\n{\n\tif (cdfile == -1 || !enabled)\n\t\treturn;\n\n\tif (!playing)\n\t\treturn;\n\n#ifdef __FreeBSD__\n\tif (ioctl(cdfile, CDIOCPAUSE) == -1)\n\t\tCom_DPrintf (\"ioctl cdiocpause failed\\n\");\n#else\n\tif ( ioctl(cdfile, CDROMPAUSE) == -1 )\n\t\tCom_DPrintf (\"ioctl cdrompause failed\\n\");\n#endif\n\n\twasPlaying = playing;\n\tplaying = false;\n}\n\n\nvoid CDAudio_Resume(void)\n{\n\tif (cdfile == -1 || !enabled)\n\t\treturn;\n\n\tif (!cdValid)\n\t\treturn;\n\n\tif (!wasPlaying)\n\t\treturn;\n\n#ifdef __FreeBSD__\n\tif (ioctl(cdfile, CDIOCRESUME) == -1)\n\t\tCom_DPrintf (\"ioctl cdiocresume failed\\n\");\n#else\n\tif ( ioctl(cdfile, CDROMRESUME) == -1 )\n\t\tCom_DPrintf (\"ioctl cdromresume failed\\n\");\n#endif\n\tplaying = true;\n}\n\n#define CHECK_CD_ARGS(x)\t\t\\\n\tif (Cmd_Argc() != x) {\t\t\\\n\tCom_Printf(\"Usage: %s <on|off|reset|remap|close|play|loop|stop|pause|resume|eject|info>\\n\", Cmd_Argv(0));\t\\\n\treturn;\t\t\t\t\t\\\n\t}\n\nvoid CD_f (void) {\n\tchar *command;\n\tint ret, n;\n\n\tif (Cmd_Argc() < 2) {\n\t\tCom_Printf(\"Usage: %s <on|off|reset|remap|close|play|loop|stop|pause|resume|eject|info>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tcommand = Cmd_Argv (1);\n\n\tif (!strcasecmp(command, \"on\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tenabled = true;\n\t\treturn;\n\t} else if (!strcasecmp(command, \"off\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tCDAudio_Stop();\n\t\tenabled = false;\n\t\treturn;\n\t} else if (!strcasecmp(command, \"reset\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tenabled = true;\n\t\tCDAudio_Stop();\n\t\tfor (n = 0; n < 100; n++)\n\t\t\tremap[n] = n;\n\t\tCDAudio_GetAudioDiskInfo();\n\t\treturn;\n\t} else if (!strcasecmp(command, \"remap\")) {\n\t\tret = Cmd_Argc() - 2;\n\t\tif (!ret) {\n\t\t\tfor (n = 1; n < 100; n++)\n\t\t\t\tif (remap[n] != n)\n\t\t\t\t\tCom_Printf (\"  %u -> %u\\n\", n, remap[n]);\n\t\t} else {\n\t\t\tfor (n = 1; n <= ret; n++)\n\t\t\t\tremap[n] = Q_atoi(Cmd_Argv (n + 1));\n\t\t}\n\t\treturn;\n\t} else if (!strcasecmp(command, \"close\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tCDAudio_CloseDoor();\n\t\treturn;\n\t}\n\n\tif (!cdValid) {\n\t\tCDAudio_GetAudioDiskInfo();\n\t\tif (!cdValid) {\n\t\t\tCom_Printf (\"No CD in player.\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (!strcasecmp(command, \"play\"))\t{\n\t\tCHECK_CD_ARGS(3);\n\t\tCDAudio_Play((byte) Q_atoi(Cmd_Argv(2)), false);\n\t} else if (!strcasecmp(command, \"loop\"))\t{\n\t\tCHECK_CD_ARGS(3);\n\t\tCDAudio_Play((byte) Q_atoi(Cmd_Argv(2)), true);\n\t} else if (!strcasecmp(command, \"stop\"))\t{\n\t\tCHECK_CD_ARGS(2);\n\t\tCDAudio_Stop();\n\t} else if (!strcasecmp(command, \"pause\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tCDAudio_Pause();\n\t} else if (!strcasecmp(command, \"resume\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tCDAudio_Resume();\n\t} else if (!strcasecmp(command, \"eject\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tCDAudio_Stop();\n\t\tCDAudio_Eject();\n\t\tcdValid = false;\n\t} else if (!strcasecmp(command, \"info\"))\t{\n\t\tCHECK_CD_ARGS(2);\n\t\tCom_Printf (\"%u tracks\\n\", maxTrack);\n\t\tif (playing)\n\t\t\tCom_Printf (\"Currently %s track %u\\n\", playLooping ? \"looping\" : \"playing\", playTrack);\n\t\telse if (wasPlaying)\n\t\t\tCom_Printf (\"Paused %s track %u\\n\", playLooping ? \"looping\" : \"playing\", playTrack);\n\t\tCom_Printf (\"Volume is %f\\n\", cdvolume);\n\t}\n}\n\nvoid CDAudio_Update(void)\n{\n#ifdef __FreeBSD__\n\tstruct ioc_read_subchannel subchnl;\n\tstruct cd_sub_channel_info data;\n#else\n\tstruct cdrom_subchnl subchnl;\n#endif\n\tstatic time_t lastchk;\n\n\tif (!enabled)\n\t\treturn;\n\n\tif (bgmvolume.value != cdvolume)\n\t{\n\t\tif (cdvolume)\n\t\t{\n\t\t\tCvar_SetValue (&bgmvolume, 0.0);\n\t\t\tcdvolume = bgmvolume.value;\n\t\t\tCDAudio_Pause ();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCvar_SetValue (&bgmvolume, 1.0);\n\t\t\tcdvolume = bgmvolume.value;\n\t\t\tCDAudio_Resume ();\n\t\t}\n\t}\n\n\tif (playing && lastchk < time(NULL)) {\n\t\tlastchk = time(NULL) + 2; //two seconds between chks\n#if defined(__FreeBSD__)\n\t\tsubchnl.address_format = CD_MSF_FORMAT;\n\t\tsubchnl.data_format = CD_CURRENT_POSITION;\n\t\tsubchnl.data_len = sizeof(data);\n\t\tsubchnl.track = playTrack;\n\t\tsubchnl.data = &data;\n\t\tif (ioctl(cdfile, CDIOCREADSUBCHANNEL, &subchnl) == -1 ) {\n\t\t\tCom_DPrintf(\"ioctl cdiocreadsubchannel failed\\n\");\n\t\t\tplaying = false;\n\t\t\treturn;\n\t\t}\n\t\tif (subchnl.data->header.audio_status != CD_AS_PLAY_IN_PROGRESS &&\n\t\t\tsubchnl.data->header.audio_status != CD_AS_PLAY_PAUSED) {\n\t\t\tplaying = false;\n\t\t\tif (playLooping)\n\t\t\t\tCDAudio_Play(playTrack, true);\n\t\t}\n#else\n\t\tsubchnl.cdsc_format = CDROM_MSF;\n\t\tif (ioctl(cdfile, CDROMSUBCHNL, &subchnl) == -1 ) {\n\t\t\tCom_DPrintf (\"ioctl cdromsubchnl failed\\n\");\n\t\t\tplaying = false;\n\t\t\treturn;\n\t\t}\n\t\tif (subchnl.cdsc_audiostatus != CDROM_AUDIO_PLAY &&\n\t\t\tsubchnl.cdsc_audiostatus != CDROM_AUDIO_PAUSED) {\n\t\t\tplaying = false;\n\t\t\tif (playLooping)\n\t\t\t\tCDAudio_Play(playTrack, true);\n\t\t}\n#endif\n\t}\n}\n\nint CDAudio_Init(void)\n{\n\tint i;\n\n\tif (!COM_CheckParm(cmdline_param_client_cd_audio))\n\t\treturn -1;\n\n\tif ((i = COM_CheckParm(cmdline_param_client_cd_device)) != 0 && i < COM_Argc() - 1)\n\t\tstrlcpy (cd_dev, COM_Argv(i + 1), sizeof(cd_dev));\n\n\tif ((cdfile = open(cd_dev, O_RDONLY)) == -1) {\n\t\tCom_Printf (\"CDAudio_Init: open of \\\"%s\\\" failed (%i)\\n\", cd_dev, errno);\n\t\tcdfile = -1;\n\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < 100; i++)\n\t\tremap[i] = i;\n\tinitialized = true;\n\tenabled = true;\n\n\tif (CDAudio_GetAudioDiskInfo())\n\t{\n\t\tCom_Printf (\"CDAudio_Init: No CD in player.\\n\");\n\t\tcdValid = false;\n\t}\n\n\tCmd_AddCommand (\"cd\", CD_f);\n\n\tCom_Printf (\"CD Audio Initialized\\n\");\n\n\treturn 0;\n}\n\n\nvoid CDAudio_Shutdown(void)\n{\n\tif (!initialized)\n\t\treturn;\n\tCDAudio_Stop();\n\tclose(cdfile);\n\tcdfile = -1;\n}\n"
  },
  {
    "path": "src/cd_null.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n#include \"quakedef.h\"\n\nvoid CDAudio_Play(byte track, qbool looping)\n{\n}\n\nvoid CDAudio_Stop(void)\n{\n}\n\nvoid CDAudio_Pause(void)\n{\n}\n\nvoid CDAudio_Resume(void)\n{\n}\n\nvoid CD_f (void) {\n}\n\nvoid CDAudio_Update(void)\n{\n}\n\nint CDAudio_Init(void)\n{\n\treturn 0;\n}\n\nvoid CDAudio_Shutdown(void)\n{\n}"
  },
  {
    "path": "src/cd_win.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All\n// rights reserved.\n\n#include \"quakedef.h\"\n#include \"cdaudio.h\"\n\nextern\tHWND\tmainwindow;\nextern\tcvar_t\tbgmvolume;\n\nstatic qbool cdValid = false;\nstatic qbool\tplaying = false;\nstatic qbool\twasPlaying = false;\nstatic qbool\tinitialized = false;\nstatic qbool\tenabled = false;\nstatic qbool playLooping = false;\nstatic float\tcdvolume;\nstatic byte \tremap[100];\nstatic byte\t\tplayTrack;\nstatic byte\t\tmaxTrack;\n\nUINT\twDeviceID;\n\n\nstatic void CDAudio_Eject(void)\n{\n\tDWORD\tdwReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_DOOR_OPEN, (DWORD)NULL);\n\n    if (dwReturn)\n\t\tCom_DPrintf (\"MCI_SET_DOOR_OPEN failed (%i)\\n\", dwReturn);\n}\n\n\nstatic void CDAudio_CloseDoor(void)\n{\n\tDWORD\tdwReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_DOOR_CLOSED, (DWORD)NULL);\n\n    if (dwReturn)\n\t\tCom_DPrintf (\"MCI_SET_DOOR_CLOSED failed (%i)\\n\", dwReturn);\n}\n\n\nstatic int CDAudio_GetAudioDiskInfo(void)\n{\n\tDWORD\t\t\t\tdwReturn;\n\tMCI_STATUS_PARMS\tmciStatusParms;\n\n\n\tcdValid = false;\n\n\tmciStatusParms.dwItem = MCI_STATUS_READY;\n    dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD) (LPVOID) &mciStatusParms);\n\tif (dwReturn)\n\t{\n\t\tCom_DPrintf (\"CDAudio: drive ready test - get status failed\\n\");\n\t\treturn -1;\n\t}\n\tif (!mciStatusParms.dwReturn)\n\t{\n\t\tCom_DPrintf (\"CDAudio: drive not ready\\n\");\n\t\treturn -1;\n\t}\n\n\tmciStatusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;\n    dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD) (LPVOID) &mciStatusParms);\n\tif (dwReturn)\n\t{\n\t\tCom_DPrintf (\"CDAudio: get tracks - status failed\\n\");\n\t\treturn -1;\n\t}\n\tif (mciStatusParms.dwReturn < 1)\n\t{\n\t\tCom_DPrintf (\"CDAudio: no music tracks\\n\");\n\t\treturn -1;\n\t}\n\n\tcdValid = true;\n\tmaxTrack = mciStatusParms.dwReturn;\n\n\treturn 0;\n}\n\n\nvoid CDAudio_Play(byte track, qbool looping)\n{\n\tDWORD\t\t\t\tdwReturn;\n    MCI_PLAY_PARMS\t\tmciPlayParms;\n\tMCI_STATUS_PARMS\tmciStatusParms;\n\n\tif (!enabled)\n\t\treturn;\n\t\n\tif (!cdValid)\n\t{\n\t\tCDAudio_GetAudioDiskInfo();\n\t\tif (!cdValid)\n\t\t\treturn;\n\t}\n\n\ttrack = remap[track];\n\n\tif (track < 1 || track > maxTrack)\n\t{\n\t\tCom_DPrintf (\"CDAudio: Bad track number %u.\\n\", track);\n\t\treturn;\n\t}\n\n\t// don't try to play a non-audio track\n\tmciStatusParms.dwItem = MCI_CDA_STATUS_TYPE_TRACK;\n\tmciStatusParms.dwTrack = track;\n    dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD) (LPVOID) &mciStatusParms);\n\tif (dwReturn)\n\t{\n\t\tCom_DPrintf (\"MCI_STATUS failed (%i)\\n\", dwReturn);\n\t\treturn;\n\t}\n\tif (mciStatusParms.dwReturn != MCI_CDA_TRACK_AUDIO)\n\t{\n\t\tCom_Printf (\"CDAudio: track %i is not audio\\n\", track);\n\t\treturn;\n\t}\n\n\t// get the length of the track to be played\n\tmciStatusParms.dwItem = MCI_STATUS_LENGTH;\n\tmciStatusParms.dwTrack = track;\n    dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD) (LPVOID) &mciStatusParms);\n\tif (dwReturn)\n\t{\n\t\tCom_DPrintf (\"MCI_STATUS failed (%i)\\n\", dwReturn);\n\t\treturn;\n\t}\n\n\tif (playing)\n\t{\n\t\tif (playTrack == track)\n\t\t\treturn;\n\t\tCDAudio_Stop();\n\t}\n\n    mciPlayParms.dwFrom = MCI_MAKE_TMSF(track, 0, 0, 0);\n\tmciPlayParms.dwTo = (mciStatusParms.dwReturn << 8) | track;\n    mciPlayParms.dwCallback = (DWORD)mainwindow;\n    dwReturn = mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY | MCI_FROM | MCI_TO, (DWORD)(LPVOID) &mciPlayParms);\n\tif (dwReturn)\n\t{\n\t\tCom_DPrintf (\"CDAudio: MCI_PLAY failed (%i)\\n\", dwReturn);\n\t\treturn;\n\t}\n\n\tplayLooping = looping;\n\tplayTrack = track;\n\tplaying = true;\n\n\tif (cdvolume == 0.0)\n\t\tCDAudio_Pause ();\n}\n\n\nvoid CDAudio_Stop(void)\n{\n\tDWORD\tdwReturn;\n\n\tif (!enabled)\n\t\treturn;\n\t\n\tif (!playing)\n\t\treturn;\n\n\tdwReturn = mciSendCommand(wDeviceID, MCI_STOP, 0, (DWORD)NULL);\n    if (dwReturn)\n\t\tCom_DPrintf (\"MCI_STOP failed (%i)\", dwReturn);\n\n\twasPlaying = false;\n\tplaying = false;\n}\n\n\nvoid CDAudio_Pause(void)\n{\n\tDWORD\t\t\t\tdwReturn;\n\tMCI_GENERIC_PARMS\tmciGenericParms;\n\n\tif (!enabled)\n\t\treturn;\n\n\tif (!playing)\n\t\treturn;\n\n\tmciGenericParms.dwCallback = (DWORD)mainwindow;\n    if ((dwReturn = mciSendCommand(wDeviceID, MCI_PAUSE, 0, (DWORD)(LPVOID) &mciGenericParms)))\n\t\tCom_DPrintf (\"MCI_PAUSE failed (%i)\", dwReturn);\n\n\twasPlaying = playing;\n\tplaying = false;\n}\n\n\nvoid CDAudio_Resume(void)\n{\n\tDWORD\t\t\tdwReturn;\n    MCI_PLAY_PARMS\tmciPlayParms;\n\n\tif (!enabled)\n\t\treturn;\n\t\n\tif (!cdValid)\n\t\treturn;\n\n\tif (!wasPlaying)\n\t\treturn;\n\t\n    mciPlayParms.dwFrom = MCI_MAKE_TMSF(playTrack, 0, 0, 0);\n    mciPlayParms.dwTo = MCI_MAKE_TMSF(playTrack + 1, 0, 0, 0);\n    mciPlayParms.dwCallback = (DWORD)mainwindow;\n    dwReturn = mciSendCommand(wDeviceID, MCI_PLAY, MCI_TO | MCI_NOTIFY, (DWORD)(LPVOID) &mciPlayParms);\n\tif (dwReturn)\n\t{\n\t\tCom_DPrintf (\"CDAudio: MCI_PLAY failed (%i)\\n\", dwReturn);\n\t\treturn;\n\t}\n\tplaying = true;\n}\n\n#define CHECK_CD_ARGS(x)\t\t\\\n\tif (Cmd_Argc() != x) {\t\t\\\n\t\tCom_Printf(\"Usage: %s <on|off|reset|remap|close|play|loop|stop|pause|resume|eject|info>\\n\", Cmd_Argv(0));\t\\\n\t\treturn;\t\t\t\t\t\\\n\t}\n\n\nvoid CD_f (void) {\n\tchar *command;\n\tint ret, n;\n\n\tif (Cmd_Argc() < 2) {\n\t\tCom_Printf(\"Usage: %s <on|off|reset|remap|close|play|loop|stop|pause|resume|eject|info>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tcommand = Cmd_Argv (1);\n\n\tif (!strcasecmp(command, \"on\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tenabled = true;\n\t\treturn;\n\t} else if (!strcasecmp(command, \"off\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tCDAudio_Stop();\n\t\tenabled = false;\n\t\treturn;\n\t} else if (!strcasecmp(command, \"reset\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tenabled = true;\n\t\tCDAudio_Stop();\n\t\tfor (n = 0; n < 100; n++)\n\t\t\tremap[n] = n;\n\t\tCDAudio_GetAudioDiskInfo();\n\t\treturn;\n\t} else if (!strcasecmp(command, \"remap\")) {\n\t\tret = Cmd_Argc() - 2;\n\t\tif (!ret) {\n\t\t\tfor (n = 1; n < 100; n++)\n\t\t\t\tif (remap[n] != n)\n\t\t\t\t\tCom_Printf (\"  %u -> %u\\n\", n, remap[n]);\n\t\t} else {\n\t\t\tfor (n = 1; n <= ret; n++)\n\t\t\t\tremap[n] = Q_atoi(Cmd_Argv (n + 1));\n\t\t}\n\t\treturn;\n\t} else if (!strcasecmp(command, \"close\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tCDAudio_CloseDoor();\n\t\treturn;\n\t}\n\n\tif (!cdValid) {\n\t\tCDAudio_GetAudioDiskInfo();\n\t\tif (!cdValid) {\n\t\t\tCom_Printf (\"No CD in player.\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (!strcasecmp(command, \"play\"))\t{\n\t\tCHECK_CD_ARGS(3);\n\t\tCDAudio_Play((byte) Q_atoi(Cmd_Argv(2)), false);\n\t} else if (!strcasecmp(command, \"loop\"))\t{\n\t\tCHECK_CD_ARGS(3);\n\t\tCDAudio_Play((byte) Q_atoi(Cmd_Argv(2)), true);\n\t} else if (!strcasecmp(command, \"stop\"))\t{\n\t\tCHECK_CD_ARGS(2);\n\t\tCDAudio_Stop();\n\t} else if (!strcasecmp(command, \"pause\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tCDAudio_Pause();\n\t} else if (!strcasecmp(command, \"resume\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tCDAudio_Resume();\n\t} else if (!strcasecmp(command, \"eject\")) {\n\t\tCHECK_CD_ARGS(2);\n\t\tCDAudio_Stop();\n\t\tCDAudio_Eject();\n\t\tcdValid = false;\n\t} else if (!strcasecmp(command, \"info\"))\t{\n\t\tCHECK_CD_ARGS(2);\n\t\tCom_Printf (\"%u tracks\\n\", maxTrack);\n\t\tif (playing)\n\t\t\tCom_Printf (\"Currently %s track %u\\n\", playLooping ? \"looping\" : \"playing\", playTrack);\n\t\telse if (wasPlaying)\n\t\t\tCom_Printf (\"Paused %s track %u\\n\", playLooping ? \"looping\" : \"playing\", playTrack);\n\t\tCom_Printf (\"Volume is %f\\n\", cdvolume);\n\t}\n}\n\n\nLONG CDAudio_MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n\tif ((UINT) lParam != wDeviceID)\n\t\treturn 1;\n\n\tswitch (wParam)\n\t{\n\t\tcase MCI_NOTIFY_SUCCESSFUL:\n\t\t\tif (playing)\n\t\t\t{\n\t\t\t\tplaying = false;\n\t\t\t\tif (playLooping)\n\t\t\t\t\tCDAudio_Play(playTrack, true);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase MCI_NOTIFY_ABORTED:\n\t\tcase MCI_NOTIFY_SUPERSEDED:\n\t\t\tbreak;\n\n\t\tcase MCI_NOTIFY_FAILURE:\n\t\t\tCom_DPrintf (\"MCI_NOTIFY_FAILURE\\n\");\n\t\t\tCDAudio_Stop ();\n\t\t\tcdValid = false;\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tCom_DPrintf (\"Unexpected MM_MCINOTIFY type (%i)\\n\", wParam);\n\t\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\n\nvoid CDAudio_Update(void)\n{\n\tif (!enabled)\n\t\treturn;\n\n\tif (bgmvolume.value != cdvolume)\n\t{\n\t\tif (cdvolume)\n\t\t{\n\t\t\tCvar_SetValue (&bgmvolume, 0.0);\n\t\t\tcdvolume = bgmvolume.value;\n\t\t\tCDAudio_Pause ();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCvar_SetValue (&bgmvolume, 1.0);\n\t\t\tcdvolume = bgmvolume.value;\n\t\t\tCDAudio_Resume ();\n\t\t}\n\t}\n}\n\n\nint CDAudio_Init(void)\n{\n\tDWORD\tdwReturn;\n\tMCI_OPEN_PARMS\tmciOpenParms;\n    MCI_SET_PARMS\tmciSetParms;\n\tint\t\t\t\tn;\n\n\tif (!COM_CheckParm(\"-cdaudio\"))\n\t\treturn -1;\n\n\tmciOpenParms.lpstrDeviceType = \"cdaudio\";\n\tdwReturn = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_SHAREABLE, (DWORD) (LPVOID) &mciOpenParms);\n\tif (dwReturn)\n\t{\n\t\tCom_Printf (\"CDAudio_Init: MCI_OPEN failed (%i)\\n\", dwReturn);\n\t\treturn -1;\n\t}\n\twDeviceID = mciOpenParms.wDeviceID;\n\n    // Set the time format to track/minute/second/frame (TMSF).\n    mciSetParms.dwTimeFormat = MCI_FORMAT_TMSF;\n    dwReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD)(LPVOID) &mciSetParms);\n    if (dwReturn)\n    {\n\t\tCom_Printf (\"MCI_SET_TIME_FORMAT failed (%i)\\n\", dwReturn);\n        mciSendCommand(wDeviceID, MCI_CLOSE, 0, (DWORD)NULL);\n\t\treturn -1;\n    }\n\n\tfor (n = 0; n < 100; n++)\n\t\tremap[n] = n;\n\tinitialized = true;\n\tenabled = true;\n\n\tif (CDAudio_GetAudioDiskInfo())\n\t{\n\t\tCom_Printf (\"CDAudio_Init: No CD in player.\\n\");\n\t\tcdValid = false;\n\t\tenabled = false;\n\t}\n\n\tCmd_AddCommand (\"cd\", CD_f);\n\n//\tCom_Printf (\"CD Audio Initialized\\n\");\n\n\treturn 0;\n}\n\n\nvoid CDAudio_Shutdown(void)\n{\n\tif (!initialized)\n\t\treturn;\n\tCDAudio_Stop();\n\tif (mciSendCommand(wDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD)NULL))\n\t\tCom_DPrintf (\"CDAudio_Shutdown: MCI_CLOSE failed\\n\");\n}\n"
  },
  {
    "path": "src/cdaudio.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\nint CDAudio_Init(void);\nvoid CDAudio_Play(byte track, qbool looping);\nvoid CDAudio_Stop(void);\nvoid CDAudio_Pause(void);\nvoid CDAudio_Resume(void);\nvoid CDAudio_Shutdown(void);\nvoid CDAudio_Update(void);\n"
  },
  {
    "path": "src/central.c",
    "content": "\n// central.c - communication with central server\n\n#include \"qwsvdef.h\"\n#include <curl/curl.h>\n#ifndef SERVER_ONLY\n#include \"quakedef.h\"\n#endif\n\n#define GENERATE_CHALLENGE_PATH     \"Authentication/GenerateChallenge\"\n#define CHALLENGETEXT_RESPONSE_PATH \"Authentication/GenerateResponse\"\n#define VERIFY_RESPONSE_PATH        \"Authentication/VerifyResponse\"\n#define CHECKIN_PATH                \"ServerApi/Checkin\"\n#define MIN_CHECKIN_PERIOD          60\n\n#ifdef _WIN32\n#ifdef SERVER_ONLY\n#pragma comment(lib, \"libcurld.lib\")\n#pragma comment(lib, \"ws2_32.lib\")\n#pragma comment(lib, \"wldap32.lib\")\n#endif\n#endif\n\n#ifndef SERVER_ONLY\n// Have set this to read-only to stop this accidentally on-purpose being set to another site by someone else's config\nstatic cvar_t cl_www_address = { \"cl_www_address\", \"https://badplace.eu/\", CVAR_ROM };\n#else\nstatic cvar_t sv_www_address = { \"sv_www_address\", \"\" };\nstatic cvar_t sv_www_authkey = { \"sv_www_authkey\", \"\" };\nstatic cvar_t sv_www_checkin_period = { \"sv_www_checkin_period\", \"60\" };\n\nstatic void Web_ConstructServerURL(char* url, const char* path, int sizeof_url);\nstatic void Web_SubmitRequestFormServer(const char* url, struct curl_httppost* first_form_ptr, struct curl_httppost* last_form_ptr, web_response_func_t callback, const char* requestId, void* internal_data);\n#endif\n\nstatic CURLM* curl_handle = NULL;\n\nstatic double last_checkin_time;\n\n#define MAX_RESPONSE_LENGTH    (4096*2)\n\nstruct web_request_data_s;\n\ntypedef void(*web_response_func_t)(struct web_request_data_s* req, qbool valid);\n\ntypedef struct web_request_data_s {\n\tCURL*               handle;\n\tdouble              time_sent;\n\n\t// response from server\n\tchar                response[MAX_RESPONSE_LENGTH];\n\tsize_t              response_length;\n\n\t// form data\n\tstruct curl_httppost *first_form_ptr;\n\tstruct curl_httppost *last_form_ptr;\n\n\t// called when response complete\n\tweb_response_func_t onCompleteCallback;\n\tvoid*               internal_data;\n\tchar*               request_id;                 // if set, content will be passed to game-mod. will be Q_free()'d\n\n\tstruct web_request_data_s* next;\n} web_request_data_t;\n\nstatic web_request_data_t* web_requests;\n\nstatic size_t Web_StandardTokenWrite(void* buffer, size_t size, size_t nmemb, void* userp)\n{\n\tweb_request_data_t* data = (web_request_data_t*)userp;\n\tsize_t available = sizeof(data->response) - data->response_length;\n\tif (size * nmemb > available) {\n\t\tCon_DPrintf(\"WWW: Response too large, size*nmemb = %d, available %d\\n\", size * nmemb, available);\n\t\treturn 0;\n\t}\n\telse if (size * nmemb > 0) {\n\t\tCon_DPrintf(\"WWW: response received, writing %d bytes\\n\", size * nmemb);\n\t\tmemcpy(data->response + data->response_length, buffer, size * nmemb);\n\t\tdata->response_length += size * nmemb;\n\t\tCon_DPrintf(\"WWW: response_length now %d bytes\\n\", data->response_length);\n\t}\n\treturn size * nmemb;\n}\n\nstatic void Web_SubmitRequestFormGeneric(const char* url, struct curl_httppost* first_form_ptr, struct curl_httppost* last_form_ptr, web_response_func_t callback, const char* requestId, void* internal_data)\n{\n\tCURL* req = curl_easy_init();\n\tweb_request_data_t* data = (web_request_data_t*)Q_malloc(sizeof(web_request_data_t));\n\n\tdata->onCompleteCallback = callback;\n\tdata->time_sent = curtime;\n\tdata->internal_data = internal_data;\n\tdata->handle = req;\n\tdata->request_id = requestId && requestId[0] ? Q_strdup(requestId) : NULL;\n\tdata->first_form_ptr = first_form_ptr;\n\n\tcurl_easy_setopt(req, CURLOPT_URL, url);\n\tcurl_easy_setopt(req, CURLOPT_WRITEDATA, data);\n\tcurl_easy_setopt(req, CURLOPT_WRITEFUNCTION, Web_StandardTokenWrite);\n\tif (first_form_ptr) {\n\t\tcurl_easy_setopt(req, CURLOPT_POST, 1);\n\t\tcurl_easy_setopt(req, CURLOPT_HTTPPOST, first_form_ptr);\n\t}\n\n\tcurl_multi_add_handle(curl_handle, req);\n\tdata->next = web_requests;\n\tweb_requests = data;\n}\n\n\ntypedef struct response_field_s {\n\tconst char* name;\n\tconst char** value;\n} response_field_t;\n\nstatic int ProcessWebResponse(web_request_data_t* req, response_field_t* fields, int field_count)\n{\n\tchar* colon, *newline;\n\tchar* start = req->response;\n\tint i;\n\tint total_fields = 0;\n\n\t// Response should be multiple lines, <Key>:<Value>\\n\n\t// For multi-line values, prefix end of line with $\n\treq->response[req->response_length] = '\\0';\n\twhile ((colon = strchr(start, ':'))) {\n\t\tnewline = strchr(colon, '\\n');\n\t\tif (newline) {\n\t\t\twhile (newline && newline != colon + 1 && *(newline - 1) == '$') {\n\t\t\t\t*(newline - 1) = ' ';\n\t\t\t\tnewline = strchr(newline + 1, '\\n');\n\t\t\t}\n\t\t\tif (newline) {\n\t\t\t\t*newline = '\\0';\n\t\t\t}\n\t\t}\n\n\t\t*colon = '\\0';\n\t\tfor (i = 0; i < field_count; ++i) {\n\t\t\tif (!strcmp(start, fields[i].name)) {\n\t\t\t\t*fields[i].value = colon + 1;\n\t\t\t}\n\t\t}\n\n\t\tif (newline == NULL) {\n\t\t\tbreak;\n\t\t}\n\n\t\tstart = newline + 1;\n\t\twhile (*start && (*start == 10 || *start == 13)) {\n\t\t\t++start;\n\t\t}\n\t\t++total_fields;\n\t}\n\n\treturn total_fields;\n}\n\n#ifdef SERVER_ONLY\nstatic int utf8Encode(char in, char* out1, char* out2) {\n\tif (in <= 0x7F) {\n\t\t// <= 127 is encoded as single byte, no translation\n\t\t*out1 = in;\n\t\treturn 1;\n\t}\n\telse {\n\t\t// Two byte characters... 5 bits then 6\n\t\t*out1 = 0xC0 | ((in >> 6) & 0x1F);\n\t\t*out2 = 0x80 | (in & 0x3F);\n\t\treturn 2;\n\t}\n}\n\nstatic void Auth_GenerateChallengeResponse(web_request_data_t* req, qbool valid)\n{\n\tclient_t* client = (client_t*) req->internal_data;\n\tconst char* response = NULL;\n\tconst char* challenge = NULL;\n\tconst char* message = NULL;\n\tresponse_field_t fields[] = {\n\t\t{ \"Result\", &response },\n\t\t{ \"Challenge\", &challenge },\n\t\t{ \"Message\", &message }\n\t};\n\n\tif (client->login_request_time != req->time_sent) {\n\t\t// Ignore result, subsequent request sent\n\t\treturn;\n\t}\n\n\treq->internal_data = NULL;\n\tProcessWebResponse(req, fields, sizeof(fields) / sizeof(fields[0]));\n\n\tif (response && !strcmp(response, \"Success\") && challenge) {\n\t\tchar buffer[128];\n\t\tstrlcpy(buffer, \"//challenge \", sizeof(buffer));\n\t\tstrlcat(buffer, challenge, sizeof(buffer));\n\t\tstrlcat(buffer, \"\\n\", sizeof(buffer));\n\n\t\tstrlcpy(client->challenge, challenge, sizeof(client->challenge));\n\t\tSV_ClientPrintf2(client, PRINT_HIGH, \"Challenge stored...\\n\", message);\n\n\t\tClientReliableWrite_Begin (client, svc_stufftext, 2+strlen(buffer));\n\t\tClientReliableWrite_String (client, buffer);\n\t}\n\telse if (message) {\n\t\tSV_ClientPrintf2(client, PRINT_HIGH, \"Error: %s\\n\", message);\n\t}\n\telse {\n\t\t// Maybe add CURLOPT_ERRORBUFFER?\n\t\tSV_ClientPrintf2(client, PRINT_HIGH, \"Error: unknown error\\n\");\n\t}\n}\n\n// Initial stage of login: ask server to create challenge/response pair\n// Connected client to supply the response in order to prove identity\nvoid Central_GenerateChallenge(client_t* client, const char* username, qbool during_login)\n{\n\tchar url[512];\n\tstruct curl_httppost* first_form_ptr = NULL;\n\tstruct curl_httppost* last_form_ptr = NULL;\n\tCURLFORMcode code;\n\n\tif (!sv_www_address.string[0]) {\n\t\tSV_ClientPrintf2(client, PRINT_HIGH, \"Remote logins not supported on this server\\n\");\n\t\treturn;\n\t}\n\n\tWeb_ConstructServerURL(url, GENERATE_CHALLENGE_PATH, sizeof(url));\n\n\tcode = curl_formadd(&first_form_ptr, &last_form_ptr,\n\t\tCURLFORM_PTRNAME, \"userName\",\n\t\tCURLFORM_COPYCONTENTS, username,\n\t\tCURLFORM_END\n\t);\n\n\tif (code != CURL_FORMADD_OK) {\n\t\tSV_ClientPrintf2(client, PRINT_HIGH, \"Failed to generate challenge (0: %u)\\n\", code);\n\t\treturn;\n\t}\n\n\tcode = curl_formadd(&first_form_ptr, &last_form_ptr,\n\t\tCURLFORM_PTRNAME, \"status\",\n\t\tCURLFORM_COPYCONTENTS, during_login ? 0 : 1,\n\t\tCURLFORM_END\n\t);\n\n\tif (code != CURL_FORMADD_OK) {\n\t\tSV_ClientPrintf2(client, PRINT_HIGH, \"Failed to generate challenge (1: %u)\\n\", code);\n\t\treturn;\n\t}\n\n\tclient->login_request_time = curtime;\n\tWeb_SubmitRequestFormServer(url, first_form_ptr, last_form_ptr, Auth_GenerateChallengeResponse, NULL, client);\n}\n\n// Final stage of login: verify challenge/response with central server\nvoid Auth_ProcessLoginAttempt(web_request_data_t* req, qbool valid)\n{\n\tclient_t* client = (client_t*) req->internal_data;\n\tconst char* response = NULL;\n\tconst char* login = NULL;\n\tconst char* preferred_alias = NULL;\n\tconst char* message = NULL;\n\tconst char* flag = NULL;\n\tresponse_field_t fields[] = {\n\t\t{ \"Result\", &response },\n\t\t{ \"Alias\", &preferred_alias },\n\t\t{ \"Login\", &login },\n\t\t{ \"Message\", &message },\n\t\t{ \"Flag\", &flag }\n\t};\n\n\treq->internal_data = NULL;\n\tif (client->login_request_time != req->time_sent) {\n\t\t// Ignore result, subsequent request sent\n\t\treturn;\n\t}\n\n\tProcessWebResponse(req, fields, sizeof(fields) / sizeof(fields[0]));\n\n\tif (response && !strcmp(response, \"Success\")) {\n\t\tif (login) {\n\t\t\tchar oldval[MAX_EXT_INFO_STRING];\n\n\t\t\tstrlcpy(oldval, Info_Get(&client->_userinfo_ctx_, \"*auth\"), sizeof(oldval));\n\t\t\tstrlcpy(client->login, login, sizeof(client->login));\n\n\t\t\tInfo_SetStar(&client->_userinfo_ctx_, \"*auth\", login);\n\t\t\tProcessUserInfoChange(client, \"*auth\", oldval);\n\t\t}\n\n\t\tflag = (flag ? flag : \"\");\n\t\tstrlcpy(client->login_flag, flag, sizeof(client->login_flag));\n\t\tInfo_SetStar(&client->_userinfo_ctx_, \"*flag\", flag);\n\t\tProcessUserInfoChange(client, \"*flag\", flag);\n\n\t\tpreferred_alias = preferred_alias ? preferred_alias : login;\n\t\tif (preferred_alias) {\n\t\t\tstrlcpy(client->login_alias, preferred_alias, sizeof(client->login_alias));\n\t\t}\n\t\tclient->logged_in_via_web = true;\n\n\t\tSV_LoginWebCheck(client);\n\t}\n\telse if (message) {\n\t\tSV_ClientPrintf2(client, PRINT_HIGH, \"Error: %s\\n\", message);\n\t\tSV_LoginWebFailed(client);\n\t}\n\telse {\n\t\t// Maybe add CURLOPT_ERRORBUFFER?\n\t\tSV_ClientPrintf2(client, PRINT_HIGH, \"Error: unknown error (invalid response from server)\\n\");\n\t\tSV_LoginWebFailed(client);\n\t}\n}\n\n// Final stage of login: client has supplied response to challenge, now check with central server\nvoid Central_VerifyChallengeResponse(client_t* client, const char* challenge, const char* response)\n{\n\tchar url[512];\n\tstruct curl_httppost* first_form_ptr = NULL;\n\tstruct curl_httppost* last_form_ptr = NULL;\n\tCURLFORMcode code;\n\n\tif (!sv_www_address.string[0]) {\n\t\tSV_ClientPrintf2(client, PRINT_HIGH, \"Remote logins not supported on this server\\n\");\n\t\treturn;\n\t}\n\n\tcode = curl_formadd(&first_form_ptr, &last_form_ptr,\n\t\tCURLFORM_PTRNAME, \"challenge\",\n\t\tCURLFORM_COPYCONTENTS, challenge,\n\t\tCURLFORM_END\n\t);\n\n\tif (code != CURL_FORMADD_OK) {\n\t\tSV_ClientPrintf2(client, PRINT_HIGH, \"Failed to generate request (1: %u)\\n\", code);\n\t\treturn;\n\t}\n\n\tcode = curl_formadd(&first_form_ptr, &last_form_ptr,\n\t\tCURLFORM_PTRNAME, \"response\",\n\t\tCURLFORM_COPYCONTENTS, response,\n\t\tCURLFORM_END\n\t);\n\n\tif (code != CURL_FORMADD_OK) {\n\t\tSV_ClientPrintf2(client, PRINT_HIGH, \"Failed to generate request (2: %u)\\n\", code);\n\t\treturn;\n\t}\n\n\tWeb_ConstructServerURL(url, VERIFY_RESPONSE_PATH, sizeof(url));\n\n\tclient->login_request_time = curtime;\n\tWeb_SubmitRequestFormServer(url, first_form_ptr, last_form_ptr, Auth_ProcessLoginAttempt, NULL, client);\n}\n\nstatic qbool CheckFileExists(const char* path)\n{\n\tFILE* f;\n\tif (!(f = fopen(path, \"rb\"))) {\n\t\treturn false;\n\t}\n\tfclose(f);\n\treturn true;\n}\n\nvoid Web_PostResponse(web_request_data_t* req, qbool valid)\n{\n\tif (valid) {\n\t\tconst char* broadcast = NULL;\n\t\tconst char* upload = NULL;\n\t\tconst char* uploadPath = NULL;\n\n\t\tresponse_field_t fields[] = {\n\t\t\t{ \"Broadcast\", &broadcast },\n\t\t\t{ \"UploadPath\", &uploadPath },\n\t\t\t{ \"Upload\", &upload }\n\t\t};\n\n\t\treq->response[req->response_length] = '\\0';\n\n\t\tCon_DPrintf(\"Response from web server:\\n\");\n\t\tCon_DPrintf(req->response);\n\n\t\tProcessWebResponse(req, fields, sizeof(fields) / sizeof(fields[0]));\n\n\t\tif (broadcast) {\n\t\t\t// Server is making announcement\n\t\t\tSV_BroadcastPrintfEx(PRINT_HIGH, 0, \"%s\\n\", broadcast);\n\t\t}\n\t\tif (upload && uploadPath) {\n\t\t\tif (strstr(uploadPath, \"//\") || FS_UnsafeFilename(upload)) {\n\t\t\t\tCon_Printf(\"Upload request deemed unsafe, ignoring...\\n\");\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (!strncmp(upload, \"demos/\", 6)) {\n\t\t\t\t\t// Ok - could be demo_dir, or full path\n\t\t\t\t\tchar demoName[MAX_OSPATH];\n\n\t\t\t\t\tif (sv_demoDir.string[0]) {\n\t\t\t\t\t\tchar url[512];\n\t\t\t\t\t\tstruct curl_httppost *first_form_ptr = NULL;\n\t\t\t\t\t\tstruct curl_httppost *last_form_ptr = NULL;\n\n\t\t\t\t\t\tsnprintf(demoName, sizeof(demoName), \"%s/%s/%s\", fs_gamedir, sv_demoDir.string, upload + 6);\n\t\t\t\t\t\tif (!CheckFileExists(demoName) && sv_demoDirAlt.string[0]) {\n\t\t\t\t\t\t\tsnprintf(demoName, sizeof(demoName), \"%s/%s/%s\", fs_gamedir, sv_demoDirAlt.string, upload + 6);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (CheckFileExists(demoName)) {\n\t\t\t\t\t\t\tWeb_ConstructServerURL(url, uploadPath, sizeof(url));\n\n\t\t\t\t\t\t\tcurl_formadd(&first_form_ptr, &last_form_ptr,\n\t\t\t\t\t\t\t\tCURLFORM_PTRNAME, \"file\",\n\t\t\t\t\t\t\t\tCURLFORM_FILE, demoName,\n\t\t\t\t\t\t\t\tCURLFORM_END\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\tcurl_formadd(&first_form_ptr, &last_form_ptr,\n\t\t\t\t\t\t\t\tCURLFORM_COPYNAME, \"localPath\",\n\t\t\t\t\t\t\t\tCURLFORM_COPYCONTENTS, upload,\n\t\t\t\t\t\t\t\tCURLFORM_END\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\tWeb_SubmitRequestFormServer(url, first_form_ptr, last_form_ptr, Web_PostResponse, \"upload\", NULL);\n\t\t\t\t\t\t\tCon_Printf(\"Uploading %s...\\n\", demoName);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tCon_Printf(\"Couldn't find file %s, ignoring...\\n\", demoName);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tCon_Printf(\"Upload request folder not authorised, ignoring...\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\tCon_Printf(\"Failure contacting central server.\\n\");\n\t}\n\n\tif (req->internal_data) {\n\t\tQ_free(req->internal_data);\n\t}\n}\n\nstatic void Web_SubmitRequestFormServer(const char* url, struct curl_httppost *first_form_ptr, struct curl_httppost *last_form_ptr, web_response_func_t callback, const char* requestId, void* internal_data)\n{\n\tcurl_formadd(&first_form_ptr, &last_form_ptr,\n\t\tCURLFORM_PTRNAME, \"authKey\",\n\t\tCURLFORM_COPYCONTENTS, sv_www_authkey.string,\n\t\tCURLFORM_END\n\t);\n\n\tWeb_SubmitRequestFormGeneric(url, first_form_ptr, last_form_ptr, callback, requestId, internal_data);\n}\n\nstatic void Web_ConstructServerURL(char* url, const char* path, int sizeof_url)\n{\n\tWeb_ConstructGenericURL(sv_www_address.string, url, path, sizeof_url);\n}\n\nstatic void Web_SendRequest(qbool post)\n{\n\tstruct curl_httppost* first_form_ptr = NULL;\n\tstruct curl_httppost* last_form_ptr = NULL;\n\tchar url[512];\n\tchar* requestId = NULL;\n\tint i;\n\n\tif (!sv_www_address.string[0]) {\n\t\tCon_Printf(\"Address not set - functionality disabled\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() < 3) {\n\t\tCon_Printf(\"Usage: %s <url> <request-id> (<key> <value>)*\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tWeb_ConstructServerURL(url, Cmd_Argv(1), sizeof(url));\n\n\trequestId = Cmd_Argv(2);\n\n\tfor (i = 3; i < Cmd_Argc() - 1; i += 2) {\n\t\tchar encoded_value[128];\n\t\tint encoded_length = 0;\n\t\tint j;\n\t\tchar* name;\n\t\tchar* value;\n\t\tCURLFORMcode code;\n\n\t\tname = Cmd_Argv(i);\n\t\tvalue = Cmd_Argv(i + 1);\n\t\tfor (j = 0; j < strlen(value) && encoded_length < sizeof(encoded_value) - 2; ++j) {\n\t\t\tencoded_length += utf8Encode(value[j], &encoded_value[encoded_length], &encoded_value[encoded_length + 1]);\n\t\t}\n\t\tencoded_value[encoded_length] = '\\0';\n\n\t\tcode = curl_formadd(&first_form_ptr, &last_form_ptr,\n\t\t\tCURLFORM_COPYNAME, name,\n\t\t\tCURLFORM_COPYCONTENTS, encoded_value,\n\t\t\tCURLFORM_END\n\t\t);\n\n\t\tif (code != CURL_FORMADD_OK) {\n\t\t\tcurl_formfree(first_form_ptr);\n\t\t\tCon_Printf(\"Request failed\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\tWeb_SubmitRequestFormServer(url, first_form_ptr, last_form_ptr, Web_PostResponse, requestId, NULL);\n}\n\nstatic void Web_GetRequest_f(void)\n{\n\tWeb_SendRequest(false);\n}\n\nstatic void Web_PostRequest_f(void)\n{\n\tWeb_SendRequest(true);\n}\n\nstatic void Web_PostFileRequest_f(void)\n{\n\tstruct curl_httppost* first_form_ptr = NULL;\n\tstruct curl_httppost* last_form_ptr = NULL;\n\tchar* requestId = NULL;\n\tchar url[512];\n\tchar path[MAX_OSPATH];\n\tCURLFORMcode code = CURL_FORMADD_OK;\n\tconst char* specified = Cmd_Argv(3);\n\n\tif (!sv_www_address.string[0]) {\n\t\tCon_Printf(\"Address not set - functionality disabled\\n\");\n\t\treturn;\n\t}\n\n\tif (specified[0] == '*' && specified[1] == '\\0') {\n\t\tconst char* demoname = SV_MVDDemoName();\n\n\t\tif (!sv.mvdrecording || !demoname) {\n\t\t\tCon_Printf(\"Not recording demo!\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(demoname));\n\t}\n\telse {\n\t\tif (strstr(specified, \".cfg\") || !strchr(specified, '/')) {\n\t\t\tCon_Printf(\"Filename invalid\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s\", fs_gamedir, specified);\n\t}\n\n\tif (Cmd_Argc() < 4) {\n\t\tCon_Printf(\"Usage: %s <url> <request-id> <file> (<key> <value>)*\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif (FS_UnsafeFilename(path)) {\n\t\tCon_Printf(\"Filename invalid\\n\");\n\t\treturn;\n\t}\n\n\tWeb_ConstructServerURL(url, Cmd_Argv(1), sizeof(url));\n\n\trequestId = Cmd_Argv(2);\n\n\tif (!CheckFileExists(path)) {\n\t\tCon_Printf(\"Failed to open file\\n\");\n\t\treturn;\n\t}\n\n\tcode = curl_formadd(&first_form_ptr, &last_form_ptr,\n\t\tCURLFORM_PTRNAME, \"file\",\n\t\tCURLFORM_FILE, path,\n\t\tCURLFORM_END\n\t);\n\n\tif (code != CURL_FORMADD_OK) {\n\t\tCon_Printf(\"Failed to generate form (0: %u)\\n\", code);\n\t\treturn;\n\t}\n\n\tWeb_SubmitRequestFormServer(url, first_form_ptr, last_form_ptr, Web_PostResponse, requestId, NULL);\n}\n\nstatic void Web_PeriodicCheck(qbool server_busy)\n{\n\tif (sv_www_address.string[0] && !server_busy && curtime - last_checkin_time > max(MIN_CHECKIN_PERIOD, sv_www_checkin_period.value)) {\n\t\tchar url[512];\n\n\t\tWeb_ConstructServerURL(url, CHECKIN_PATH, sizeof(url));\n\n\t\tWeb_SubmitRequestFormServer(url, NULL, NULL, Web_PostResponse, NULL, NULL);\n\n\t\tlast_checkin_time = curtime;\n\t}\n\telse if (server_busy) {\n\t\tlast_checkin_time = curtime;\n\t}\n}\n#else\n#define Web_PeriodicCheck(...)\n#endif // SERVER_ONLY\n\nstatic void Web_ConstructGenericURL(const char* base_url, char* url, const char* path, int sizeof_url)\n{\n\tstrlcpy(url, base_url, sizeof_url);\n\tif (url[strlen(url) - 1] != '/') {\n\t\tstrlcat(url, \"/\", sizeof_url);\n\t}\n\twhile (*path == '/') {\n\t\t++path;\n\t}\n\tstrlcat(url, path, sizeof_url);\n}\n\nvoid Central_ProcessResponses(void)\n{\n\tCURLMsg* msg;\n\tint running_handles = 0;\n\tint messages_in_queue = 0;\n\n\tif (!last_checkin_time) {\n\t\tlast_checkin_time = curtime;\n\t\treturn;\n\t}\n\n\tcurl_multi_perform(curl_handle, &running_handles);\n\n\twhile ((msg = curl_multi_info_read(curl_handle, &messages_in_queue))) {\n\t\tif (msg->msg == CURLMSG_DONE) {\n\t\t\tCURL* handle = msg->easy_handle;\n\t\t\tCURLcode result = msg->data.result;\n\t\t\tweb_request_data_t** list_pointer = &web_requests;\n\n\t\t\tcurl_multi_remove_handle(curl_handle, handle);\n\n\t\t\twhile (*list_pointer) {\n\t\t\t\tweb_request_data_t* this = *list_pointer;\n\n\t\t\t\tif (this->handle == handle) {\n\t\t\t\t\t// Remove from queue immediately\n\t\t\t\t\t*list_pointer = this->next;\n\n\t\t\t\t\tif (this->onCompleteCallback) {\n\t\t\t\t\t\tif (result == CURLE_OK) {\n\t\t\t\t\t\t\tthis->onCompleteCallback(this, true);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tCon_DPrintf(\"ERROR: %s\\n\", curl_easy_strerror(result));\n\t\t\t\t\t\t\tthis->onCompleteCallback(this, false);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tif (result != CURLE_OK) {\n\t\t\t\t\t\t\tCon_DPrintf(\"ERROR: %s\\n\", curl_easy_strerror(result));\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tCon_DPrintf(\"WEB OK, no callback\\n\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// free memory\n\t\t\t\t\tcurl_formfree(this->first_form_ptr);\n\t\t\t\t\tQ_free(this->request_id);\n\t\t\t\t\tQ_free(this);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tlist_pointer = &this->next;\n\t\t\t}\n\n\t\t\tcurl_easy_cleanup(handle);\n\t\t}\n\t}\n\n\t// Periodically check in when idle\n\tWeb_PeriodicCheck(running_handles || GameStarted());\n}\n\nvoid Central_Shutdown(void)\n{\n\tif (curl_handle) {\n\t\tcurl_multi_cleanup(curl_handle);\n\t\tcurl_handle = NULL;\n\t}\n}\n\nvoid Central_Init(void)\n{\n// TODO: client-only version\n#ifdef SERVER_ONLY\n\tCvar_Register(&sv_www_address);\n\tCvar_Register(&sv_www_authkey);\n\tCvar_Register(&sv_www_checkin_period);\n#endif\n#ifndef SERVER_ONLY\n\tCvar_Register(&cl_www_address);\n#endif\n\n\tcurl_handle = curl_multi_init();\n\n#ifdef SERVER_ONLY\n\tif (curl_handle) {\n\t\tCmd_AddCommand(\"sv_web_get\", Web_GetRequest_f);\n\t\tCmd_AddCommand(\"sv_web_post\", Web_PostRequest_f);\n\t\tCmd_AddCommand(\"sv_web_postfile\", Web_PostFileRequest_f);\n\t}\n#endif\n}\n\n#ifndef SERVER_ONLY\nstatic void Web_ConstructClientURL(char* url, const char* path, int sizeof_url)\n{\n\tWeb_ConstructGenericURL(cl_www_address.string, url, path, sizeof_url);\n}\n\n// Web response to client asking for challenge response text\nvoid Auth_FindChallengeResponse(web_request_data_t* req, qbool valid)\n{\n\tconst char* challengeText = req->request_id;\n\tconst char* result = NULL;\n\tconst char* responseText = NULL;\n\tconst char* message = NULL;\n\tresponse_field_t fields[] = {\n\t\t{ \"Result\", &result },\n\t\t{ \"Response\", &responseText },\n\t\t{ \"Message\", &message }\n\t};\n\n\tif (strcmp(cl.auth_challenge, challengeText)) {\n\t\t// Ignore result, subsequent request sent\n\t\treturn;\n\t}\n\n\treq->internal_data = NULL;\n\tProcessWebResponse(req, fields, sizeof(fields) / sizeof(fields[0]));\n\n\tif (result && !strcmp(result, \"Success\") && responseText) {\n\t\tchar buffer[256];\n\n\t\tstrlcpy(buffer, \"cmd login-response \", sizeof(buffer));\n\t\tstrlcat(buffer, responseText, sizeof(buffer));\n\t\tstrlcat(buffer, \"\\n\", sizeof(buffer));\n\n\t\tCom_Printf(\"Responding to challenge...\\n\");\n\n\t\tCbuf_AddTextEx(&cbuf_main, buffer);\n\t}\n\telse if (message) {\n\t\tCom_Printf(\"Error receiving challenge response: %s\\n\", message);\n\t}\n\telse {\n\t\t// Maybe add CURLOPT_ERRORBUFFER?\n\t\tCom_Printf(\"Error receiving challenge response: unknown error\\n\");\n\t}\n}\n\n// Called when //challenge received from server\nvoid Central_FindChallengeResponse(const char* token, const char* challengeText)\n{\n\tchar url[512];\n\tstruct curl_httppost* first_form_ptr = NULL;\n\tstruct curl_httppost* last_form_ptr = NULL;\n\tCURLFORMcode code;\n\tSHA1_CTX context;\n\tunsigned char hash[DIGEST_SIZE];\n\tchar hash_buffer[128] = { 0 };\n\n\tif (!cl_www_address.string[0]) {\n\t\tCom_Printf(\"cl_www_address not configured: please authenticate manually\\n\");\n\t\treturn;\n\t}\n\n\t// FIXME: something stronger\n\tSHA1Init(&context);\n\tSHA1Update(&context, (unsigned char*)challengeText, strlen(challengeText));\n\tSHA1Update(&context, (unsigned char*)\"^\", 1);\n\tSHA1Update(&context, (unsigned char*)token, strlen(token));\n\tSHA1Final(hash, &context);\n\n\tstrlcpy(hash_buffer, bin2hex(hash), sizeof(hash_buffer));\n\n\tcode = curl_formadd(&first_form_ptr, &last_form_ptr,\n\t\tCURLFORM_PTRNAME, \"hashalg\",\n\t\tCURLFORM_COPYCONTENTS, \"sha1\",\n\t\tCURLFORM_END\n\t);\n\tif (code != CURL_FORMADD_OK) {\n\t\tcurl_formfree(first_form_ptr);\n\t\tCon_Printf(\"Failed to generate form (0: %u)\\n\", code);\n\t\treturn;\n\t}\n\n\tcode = curl_formadd(&first_form_ptr, &last_form_ptr,\n\t\tCURLFORM_PTRNAME, \"authKey\",\n\t\tCURLFORM_COPYCONTENTS, hash_buffer,\n\t\tCURLFORM_END\n\t);\n\tif (code != CURL_FORMADD_OK) {\n\t\tcurl_formfree(first_form_ptr);\n\t\tCon_Printf(\"Failed to generate form (0: %u)\\n\", code);\n\t\treturn;\n\t}\n\n\tcode = curl_formadd(&first_form_ptr, &last_form_ptr,\n\t\tCURLFORM_PTRNAME, \"challenge\",\n\t\tCURLFORM_COPYCONTENTS, challengeText,\n\t\tCURLFORM_END\n\t);\n\tif (code != CURL_FORMADD_OK) {\n\t\tcurl_formfree(first_form_ptr);\n\t\tCon_Printf(\"Failed to generate form (0: %u)\\n\", code);\n\t\treturn;\n\t}\n\n\tWeb_ConstructClientURL(url, CHALLENGETEXT_RESPONSE_PATH, sizeof(url));\n\n\tWeb_SubmitRequestFormGeneric(url, first_form_ptr, last_form_ptr, Auth_FindChallengeResponse, challengeText, NULL);\n}\n#endif\n"
  },
  {
    "path": "src/central.h",
    "content": "\n#ifndef CENTRAL_H\n#define CENTRAL_H\n\n#include \"server.h\"\n\nvoid Central_Init(void);\nvoid Central_Shutdown(void);\nvoid Central_ProcessResponses(void);\n// void Central_SubmitGame(const char* path);\n\n// Creates a challenge/response on the web server after user claims to be 'username'\nvoid Central_GenerateChallenge(client_t* client, const char* username, qbool during_login);\n\n// Checks with the server if a client's response to a challenge is correct\nvoid Central_VerifyChallengeResponse(client_t* client, const char* challenge, const char* response);\n\n#ifndef SERVER_ONLY\n// \nvoid Central_FindChallengeResponse(const char* token, const char* challengeText);\n#endif\n\n#endif // !CENTRAL_H\n"
  },
  {
    "path": "src/cl_cam.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n/* ZOID\n *\n * Player camera tracking in Spectator mode\n *\n * This takes over player controls for spectator automatic camera.\n * Player moves as a spectator, but the camera tracks and enemy player\n */\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"pmove.h\"\n#include \"utils.h\"\n#include \"sbar.h\"\n#include \"qtv.h\"\n\nstatic int Cam_MainTrackNum(void);\n\nstatic vec3_t desired_position; // where the camera wants to be.\nstatic int oldbuttons;\nstatic qbool cmddown, olddown;\nextern cvar_t cam_thirdperson, cl_camera_tpp;\n\ncvar_t cl_hightrack = {\"cl_hightrack\", \"0\" };\t// track high fragger \ncvar_t cl_chasecam = {\"cl_chasecam\", \"1\"};\t\t// \"through the eyes\" view\n\nvec3_t cam_viewangles;\ndouble cam_lastviewtime;\n\nvoid CL_TrackMV1_f(void);\nvoid CL_TrackMV2_f(void);\nvoid CL_TrackMV3_f(void);\nvoid CL_TrackMV4_f(void);\nvoid CL_TrackTeam_f(void); \n\nvoid CL_Track_f(void);\nvoid CL_Trackkiller_f(void);\nvoid CL_Autotrack_f(void);\n\ndouble last_lock = 0;\t\t\t// Last time Cam_Lock() successful\n\nstatic int\tkiller = -1;\t\t// id of the player who killed the player we are now tracking\n\nvoid CL_Cam_SetKiller(int killernum, int victimnum) {\n\tif (victimnum != cl.viewplayernum) return;\n\tif (killernum < 0 || killernum >= MAX_CLIENTS) return;\n\n\tkiller = killernum;\n}\n\nvoid vectoangles(vec3_t vec, vec3_t ang) {\n\tfloat forward, yaw, pitch;\n\t\n\tif (vec[1] == 0 && vec[0] == 0) {\n\t\tyaw = 0;\n\t\tpitch = (vec[2] > 0) ? 90 : 270;\n\t} else {\n\t\tyaw = /*(int)*/ (atan2(vec[1], vec[0]) * 180 / M_PI);\n\t\tif (yaw < 0)\n\t\t\tyaw += 360;\n\n\t\tforward = sqrt (vec[0]*vec[0] + vec[1]*vec[1]);\n\t\tpitch = /*(int)*/ (atan2(vec[2], forward) * 180 / M_PI);\n\t\tif (pitch < 0)\n\t\t\tpitch += 360;\n\t}\n\n\tang[0] = pitch;\n\tang[1] = yaw;\n\tang[2] = 0;\n}\n\nvoid Cam_SetViewPlayer (void)\n{\n\tint new_track;\n\n\tif (cl.spectator && cl.autocam && cl.spec_locked && cl_chasecam.value) {\n\t\tnew_track = cl.spec_track;\n\t}\n\telse {\n\t\tnew_track = cl.playernum;\n\t}\n\n\tif (new_track != cl.viewplayernum) {\n\t\tmemset(cl.antilag_positions, 0, sizeof(cl.antilag_positions));\n\t}\n\tcl.viewplayernum = new_track;\n}\n\n// returns true if weapon model should be drawn in camera mode\nqbool Cam_DrawViewModel(void) {\n\tif (!cl.spectator)\n\t\treturn true;\n\n\tif (cl.autocam && cl.spec_locked && cl_chasecam.value)\n\t\treturn true;\n\treturn false;\n}\n\nstatic qbool Cam_FirstPersonMode(void)\n{\n\treturn cl_chasecam.value && !cam_thirdperson.integer && !cl_camera_tpp.integer;\n}\n\n// returns true if we should draw this player, we don't if we are chase camming\nqbool Cam_DrawPlayer(int playernum)\n{\n\tif (cl.spectator && cl.autocam && cl.spec_locked && cl.spec_track == playernum && Cam_FirstPersonMode())\n\t\treturn false;\n\treturn true;\n}\n\nvoid Cam_Unlock(void) \n{\n\tif (Cmd_FindAlias(\"f_freeflyspectate\"))\n\t{\n\t\tCbuf_AddTextEx (&cbuf_main, \"f_freeflyspectate\\n\");\n\t}\n\n\tif (!cl.autocam) {\n\t\treturn;\n\t}\n\n\tif (cls.mvdplayback == QTV_PLAYBACK)\n\t{\n\t\t// its not setinfo extension, but adding new extension just for this is stupid IMO\n\t\tQTV_Cmd_Printf(QTV_EZQUAKE_EXT_SETINFO, \"ptrack\");\n\t}\n\telse\n\t{\n\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\tMSG_WriteString (&cls.netchan.message, \"ptrack\");\n\t}\n\n\tcl.autocam = CAM_NONE;\n\tcl.spec_locked = false;\n\tSbar_Changed();\n\n\tif (cls.mvdplayback && cl.teamfortress) \n\t{\n\t\tV_TF_ClearGrenadeEffects ();\n\t}\n\n\tif (TP_NeedRefreshSkins())\n\t{\n\t\tTP_RefreshSkins();\n\t}\n}\n\nvoid Cam_Lock(int playernum) \n{\n\tchar st[32];\n\n\tif (Cmd_FindAlias(\"f_trackspectate\")) {\n\t\tCbuf_AddTextEx (&cbuf_main, \"f_trackspectate\\n\");\n\t}\n\n\tsnprintf(st, sizeof (st), \"ptrack %i\", playernum);\n\tif (cls.mvdplayback == QTV_PLAYBACK) {\n\t\t// its not setinfo extension, but adding new extension just for this is stupid IMO\n\t\tQTV_Cmd_Printf(QTV_EZQUAKE_EXT_SETINFO, st);\n\t}\n\telse {\n\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\tMSG_WriteString (&cls.netchan.message, st);\n\t}\n\n\tif (CL_MultiviewEnabled ()) {\n\t\tCL_MultiviewSetTrackSlot (-1, playernum);\n\t\tif (!cls.findtrack) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (cls.mvdplayback) {\n\t\tmemcpy(cl.stats, cl.players[playernum].stats, sizeof(cl.stats));\n\t\tcl.ideal_track = playernum;\n\t\tcl.mvd_time_offset = 0;\n\t}\n\tlast_lock = cls.realtime;\n\n\tcl.spec_track = playernum;\n\tcl.spec_locked = false;\n\tSbar_Changed();\n\n\tif (TP_NeedRefreshSkins())\n\t\tTP_RefreshSkins();\n}\n\ntrace_t Cam_DoTrace(vec3_t vec1, vec3_t vec2) {\n\tVectorCopy (vec1, pmove.origin);\n\treturn PM_PlayerTrace(pmove.origin, vec2);\n}\n\t\n// Returns distance or 9999 if invalid for some reason\nstatic float Cam_TryFlyby(player_state_t *self, player_state_t *player, vec3_t vec, qbool checkvis) {\n\tvec3_t v;\n\ttrace_t trace;\n\tfloat len;\n\n\tvectoangles(vec, v);\n\tVectorCopy (v, pmove.angles);\n\tVectorNormalizeFast(vec);\n\tVectorMA(player->origin, 800, vec, v);\n\t// v is endpos\n\t// fake a player move\n\ttrace = Cam_DoTrace(player->origin, v);\n\tif (trace.inwater)\n\t\treturn 9999;\n\tVectorCopy(trace.endpos, vec);\n\tVectorSubtract(trace.endpos, player->origin, v);\n\tlen = VectorLength(v);\n\tif (len < 32 || len > 800)\n\t\treturn 9999;\n\tif (checkvis) {\n\t\tVectorSubtract(trace.endpos, self->origin, v);\n\t\tlen = VectorLength(v);\n\n\t\ttrace = Cam_DoTrace(self->origin, vec);\n\t\tif (trace.fraction != 1 || trace.inwater)\n\t\t\treturn 9999;\n\t}\n\treturn len;\n}\n\n// Is player visible?\nstatic qbool Cam_IsVisible(player_state_t *player, vec3_t vec) {\n\ttrace_t trace;\n\tvec3_t v;\n\n\ttrace = Cam_DoTrace(player->origin, vec);\n\tif (trace.fraction != 1 || /*trace.inopen ||*/ trace.inwater)\n\t\treturn false;\n\t// check distance, don't let the player get too far away or too close\n\tVectorSubtract(player->origin, vec, v);\n\n\treturn ((v[0]*v[0]+v[1]*v[1]+v[2]*v[2]) >= 256);\n}\n\nstatic qbool InitFlyby(player_state_t *self, player_state_t *player, int checkvis) {\n    float f, max;\n    vec3_t vec, vec2;\n\tvec3_t forward, right, up;\n\n\tVectorCopy(player->viewangles, vec);\n    vec[0] = 0;\n\tAngleVectors (vec, forward, right, up);\n\n    max = 1000;\n\tVectorAdd(forward, up, vec2);\n\tVectorAdd(vec2, right, vec2);\n    if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {\n        max = f;\n\t\tVectorCopy(vec2, vec);\n    }\n\tVectorAdd(forward, up, vec2);\n\tVectorSubtract(vec2, right, vec2);\n    if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {\n        max = f;\n\t\tVectorCopy(vec2, vec);\n    }\n\tVectorAdd(forward, right, vec2);\n    if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {\n        max = f;\n\t\tVectorCopy(vec2, vec);\n    }\n\tVectorSubtract(forward, right, vec2);\n    if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {\n        max = f;\n\t\tVectorCopy(vec2, vec);\n    }\n\tVectorAdd(forward, up, vec2);\n    if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {\n        max = f;\n\t\tVectorCopy(vec2, vec);\n    }\n\tVectorSubtract(forward, up, vec2);\n    if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {\n        max = f;\n\t\tVectorCopy(vec2, vec);\n    }\n\tVectorAdd(up, right, vec2);\n\tVectorSubtract(vec2, forward, vec2);\n    if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {\n        max = f;\n\t\tVectorCopy(vec2, vec);\n    }\n\tVectorSubtract(up, right, vec2);\n\tVectorSubtract(vec2, forward, vec2);\n    if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {\n        max = f;\n\t\tVectorCopy(vec2, vec);\n    }\n\t// invert\n\tVectorSubtract(vec3_origin, forward, vec2);\n    if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {\n        max = f;\n\t\tVectorCopy(vec2, vec);\n    }\n\tVectorCopy(forward, vec2);\n    if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {\n        max = f;\n\t\tVectorCopy(vec2, vec);\n    }\n\t// invert\n\tVectorSubtract(vec3_origin, right, vec2);\n    if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {\n        max = f;\n\t\tVectorCopy(vec2, vec);\n    }\n\tVectorCopy(right, vec2);\n    if ((f = Cam_TryFlyby(self, player, vec2, checkvis)) < max) {\n        max = f;\n\t\tVectorCopy(vec2, vec);\n    }\n\n\t// ack, can't find him\n    if (max >= 1000)\n\t\treturn false;\n\tcl.spec_locked = true;\n\tVectorCopy(vec, desired_position); \n\treturn true;\n}\n\n// cl_hightrack \nstatic void Cam_CheckHighTarget(void)\n{\n\tint i, j, max;\n\tplayer_info_t\t*s;\n\n\tj = -1;\n\tfor (i = 0, max = -9999; i < MAX_CLIENTS; i++) {\n\t\ts = &cl.players[i];\n\t\tif (s->name[0] && !s->spectator && s->frags > max) {\n\t\t\tmax = s->frags;\n\t\t\tj = i;\n\t\t}\n\t}\n\tif (j >= 0) {\n\t\tif (!cl.spec_locked || cl.players[j].frags > cl.players[cl.spec_track].frags) {\n\t\t\tCam_Lock(j);\n\t\t\tcl.ideal_track = cl.spec_track;\n\t\t}\n\t} else\n\t\tCam_Unlock();\n} \n\n// Take over the user controls and track a player.\n// We find a nice position to watch the player and move there\nvoid Cam_Track(usercmd_t *cmd) \n{\n\tplayer_state_t *player, *self;\n\tframe_t *frame;\n\tvec3_t vec;\n\n\tif (!cl.spectator) {\n\t\treturn;\n\t}\n\n\t// hack: save +movedown command\n\tcmddown = cmd->upmove < 0;\n\n\t// cl_hightrack \n\tif (cl_hightrack.value && !cl.spec_locked)\n\t{\n\t\tCam_CheckHighTarget(); \n\t}\n\t\n\tif (!cl.autocam || cls.state != ca_active)\n\t{\n\t\treturn;\n\t}\n\n\tif (cl.spec_locked && (!cl.players[cl.spec_track].name[0] || cl.players[cl.spec_track].spectator))\n\t{\n\t\tcl.spec_locked = false;\n\n\t\t// cl_hightrack \n\t\tif (cl_hightrack.value)\n\t\t{\n\t\t\tCam_CheckHighTarget();\n\t\t}\n\t\telse \n\t\t{\n\t\t\tCam_Unlock();\n\t\t}\n\t\treturn;\n\t}\n\n\tframe = &cl.frames[cl.validsequence & UPDATE_MASK];\n\n\tif (cl.autocam && cls.mvdplayback) {\n\t\tif (cl.ideal_track != cl.spec_track && cls.realtime - last_lock > 0.1 && frame->playerstate[cl.ideal_track].messagenum == cl.parsecount) {\n\t\t\tCam_Lock(cl.ideal_track);\n\t\t}\n\n\t\tif ((frame->playerstate[cl.spec_track].messagenum != cl.parsecount && frame->playerstate[cl.spec_track].messagenum != cl.oldparsecount) || Cam_MainTrackNum() != cl.ideal_track) {\n\t\t\tint i;\n\n\t\t\tfor (i = 0; i < MAX_CLIENTS - 1; i++) {\n\t\t\t\tif ((frame->playerstate[i].messagenum == cl.parsecount || frame->playerstate[i].messagenum == cl.oldparsecount)) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tCam_Lock(i);\n\t\t\tcls.findtrack = (i >= MAX_CLIENTS - 1);\n\t\t}\n\t}\n\n\tplayer = frame->playerstate + cl.spec_track;\n\tself = frame->playerstate + cl.playernum;\n\n\tif (!cl.spec_locked || !Cam_IsVisible(player, desired_position))\n\t{\n\t\tif (!cl.spec_locked || cls.realtime - cam_lastviewtime > 0.1)\n\t\t{\n\t\t\tif (!InitFlyby(self, player, true))\n\t\t\t\tInitFlyby(self, player, false);\n\t\t\tcam_lastviewtime = cls.realtime;\n\t\t}\n\t} \n\telse \n\t{\n\t\tcam_lastviewtime = cls.realtime;\n\t}\n\t\n\t// couldn't track for some reason\n\tif (!cl.spec_locked || !cl.autocam)\n\t\treturn;\n\n\tif (cl_chasecam.value) \n\t{\n\t\tcmd->forwardmove = cmd->sidemove = cmd->upmove = 0;\n\n\t\t#ifdef JSS_CAM\n\t\tif (!cam_thirdperson.integer)\n\t\t#endif\n\t\t{\n\t\t\tVectorCopy(player->viewangles, cl.viewangles);\n\t\t}\n\t\tVectorCopy(player->origin, desired_position);\n\t\tif (memcmp(&desired_position, &self->origin, sizeof(desired_position)) != 0) {\n\t\t\tMSG_WriteByte (&cls.netchan.message, clc_tmove);\n\t\t\tMSG_WriteCoord (&cls.netchan.message, desired_position[0]);\n\t\t\tMSG_WriteCoord (&cls.netchan.message, desired_position[1]);\n\t\t\tMSG_WriteCoord (&cls.netchan.message, desired_position[2]);\n\t\t\t// move there locally immediately\n\t\t\tVectorCopy(desired_position, self->origin);\n\t\t}\n\t} \n\telse \n\t{\n\t\t// Ok, move to our desired position and set our angles to view\n\t\t// the player\n\t\tVectorSubtract(desired_position, self->origin, vec);\n\t\tcmd->forwardmove = cmd->sidemove = cmd->upmove = 0;\n\t\tif (VectorLength(vec) > 16) \n\t\t{ \n\t\t\t// close enough?\n\t\t\tMSG_WriteByte (&cls.netchan.message, clc_tmove);\n\t\t\tMSG_WriteCoord (&cls.netchan.message, desired_position[0]);\n\t\t\tMSG_WriteCoord (&cls.netchan.message, desired_position[1]);\n\t\t\tMSG_WriteCoord (&cls.netchan.message, desired_position[2]);\n\t\t}\n\n\t\t// move there locally immediately\n\t\tVectorCopy(desired_position, self->origin);\n\t\t\t\t\t\t\t\t\t\t \n\t\tVectorSubtract(player->origin, desired_position, vec);\n\t\tvectoangles(vec, cl.viewangles);\n\t\tcl.viewangles[0] = -cl.viewangles[0];\n\t}\n}\n\n// Returns true if last new button command was jump\nqbool Cam_JumpCheck(usercmd_t *cmd)\n{\n\tif ((cmd->buttons & BUTTON_JUMP) && (oldbuttons & BUTTON_JUMP))\n\t\treturn false;\t\t// don't pogo stick\n\n\tif (!(cmd->buttons & BUTTON_JUMP))\n\t{\n\t\toldbuttons &= ~BUTTON_JUMP;\n\t\treturn false;\n\t}\n\toldbuttons |= BUTTON_JUMP;\t// don't jump again until released\n\n\treturn true;\n}\n\n// Returns true if last new button command was jump\nqbool Cam_MoveDownCheck(usercmd_t *cmd)\n{\n\tif (cmddown && olddown)\n\t\treturn false;\n\n\tif (!cmddown)\n\t{\n\t\tolddown = false;\n\t\treturn false;\n\t}\n\tolddown = true;\n\n\treturn true;\n}\n\nvoid Cam_FinishMove(usercmd_t *cmd) \n{\n\tint i, end, inc;\n\tplayer_info_t *s;\n\n\tif (cls.state != ca_active)\n\t\treturn;\n\n\tif (!cl.spectator) // only in spectator mode\n\t\treturn;\n\n\tif (cmd->buttons & BUTTON_ATTACK) {\n\t\tif (!(oldbuttons & BUTTON_ATTACK)) {\n\n\t\t\toldbuttons |= BUTTON_ATTACK;\n\t\t\tcl.autocam++;\n\n\t\t\tif (cl.autocam > CAM_TRACK) {\n\t\t\t\tCam_Unlock();\n\t\t\t\tVectorCopy(cl.viewangles, cmd->angles);\n\t\t\t\treturn;\n\t\t\t}\n\t\t} else\n\t\t\treturn;\n\t} else {\n\t\toldbuttons &= ~BUTTON_ATTACK;\n\t\tif (!cl.autocam)\n\t\t\treturn;\n\t}\n\n\t// cl_hightrack \n\tif (cl.autocam && cl_hightrack.value)\n\t{\n\t\tCam_CheckHighTarget();\n\t\tif (Cam_JumpCheck(cmd))\n\t\t{\n\t\t\tCom_Printf_State(PRINT_FAIL,\"cl_hightrack enabled. Unable to switch POV.\\n\");\n\t\t}\n\t\treturn;\n\t}\n\n\tif (Cam_MoveDownCheck(cmd)) {\n\t\tinc = -1;\n\t} else {\n\t\tinc = 1;\n\t}\n\n\tif (cl.spec_locked) {\n\t\tif (!Cam_JumpCheck(cmd) && inc == 1) {\n\t\t\treturn;\n\t\t}\n\t\t// Swap the Multiview mvinset/main view pov when jump button is pressed.\n\t\tCL_MultiviewTrackingAdjustment (inc);\n\t}\n\n\t\n\tif (cl.spec_locked && cl.autocam) {\n\t\tend = (cl.ideal_track + MAX_CLIENTS + inc) % MAX_CLIENTS;\n\t}\n\telse {\n\t\tend = cl.ideal_track;\n\t}\n\n\ti = end;\n\tdo {\n\t\ts = &cl.players[i];\n\t\tif (s->name[0] && !s->spectator && (!cls.mvdplayback || cl.frames[cl.parsecount & UPDATE_MASK].playerstate[i].messagenum == cl.parsecount)) {\n\t\t\tif (cls.mvdplayback && cl.teamfortress) {\n\t\t\t\tV_TF_ClearGrenadeEffects(); // BorisU\n\t\t\t}\n\t\t\tCam_Lock(i);\n\t\t\tcl.ideal_track = i;\n\t\t\treturn;\n\t\t}\n\t\ti = (i + MAX_CLIENTS + inc) % MAX_CLIENTS;\n\t} while (i != end);\n\n\t// stay on same guy?\n\ti = cl.ideal_track;\n\ts = &cl.players[i];\n\tif (s->name[0] && !s->spectator) {\n\t\tCam_Lock(i);\n\t\treturn;\n\t}\n\tCom_Printf (\"No target found ...\\n\");\n\tcl.autocam = cl.spec_locked = false;\n}\n\nvoid Cam_Reset(void)\n{\n\tcl.autocam = CAM_NONE;\n\tcl.spec_track = 0;\n\tcl.ideal_track = 0;\n\tcl.spec_locked = false;\n}\n\n//Fixes spectator chasecam demos\nvoid Cam_TryLock (void) {\n\tint i, j, old_autocam, old_spec_track;\n\tplayer_state_t *state;\n\tstatic float cam_lastlocktime;\n\n\tif (!cl.validsequence)\n\t\treturn;\n\n\tif (!cl.autocam)\n\t\tcam_lastlocktime = 0;\n\n\told_autocam = cl.autocam;\n\told_spec_track = cl.spec_track;\n\n\tstate = cl.frames[cl.validsequence & UPDATE_MASK].playerstate;\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!cl.players[i].name[0] || cl.players[i].spectator ||\n\t\t\tstate[i].messagenum != cl.parsecount)\n\t\t\tcontinue;\n\t\tif (fabs(state[i].command.angles[0] - cl.viewangles[0]) < 2 && fabs(state[i].command.angles[1] - cl.viewangles[1]) < 2) {\n\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\tif (fabs(state[i].origin[j] - state[cl.playernum].origin[j]) > 200)\n\t\t\t\t\tbreak;\t// too far\n\t\t\tif (j < 3)\n\t\t\t\tcontinue;\n\t\t\tcl.autocam = CAM_TRACK;\n\t\t\tcl.spec_track = i;\n\t\t\tcl.spec_locked = true;\n\t\t\tcam_lastlocktime = cls.realtime;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (cls.realtime - cam_lastlocktime > 0.3) {\n\t\t// Couldn't lock to any player for 0.3 seconds, so assume\n\t\t// the spectator switched to free spectator mode\n\t\tcl.autocam = CAM_NONE;\n\t\tcl.spec_track = 0;\n\t\tcl.spec_locked = false;\n\t}\n\n\tif (cl.autocam != old_autocam || cl.spec_track != old_spec_track) {\n\t\tSbar_Changed ();\n\n\t\tif (TP_NeedRefreshSkins())\n\t\t\tTP_RefreshSkins();\n\t}\n}\n\n\n#ifdef JSS_CAM\nstatic char *myftos (float f)\n{\n#define MAX_VAL 128\n\tstatic char buf[4][MAX_VAL];\n\tstatic char idx = 0;\n\tchar *val;\n\tint\ti;\n\n\tif (!cls.demoplayback && !cl.spectator)\n\t\treturn \"?\";\n\n\tval = buf[(idx++) & 3];\n\n\tsnprintf (val, MAX_VAL, \"%f\", f);\n\n\t// strip trailing zeroes\n\tfor (i = strlen(val) - 1; i > 0 && val[i] == '0'; i--)\n\t\tval[i] = 0;\n\tif (val[i] == '.')\n\t\tval[i] = 0;\n\t\t\n\treturn val;\n}\n\nvoid Cam_Pos_Set(float x, float y, float z)\n{\n\textern qbool clpred_newpos;\n\n\tcl.simorg[0] = x;\n\tcl.simorg[1] = y;\n\tcl.simorg[2] = z;\n\tclpred_newpos = true;\n\t\n\tVectorCopy (cl.simorg, cl.frames[cl.validsequence & UPDATE_MASK].playerstate[cl.playernum].origin);\n\t\n\tif (cls.state >= ca_active && !cls.demoplayback) {\n\t\tMSG_WriteByte (&cls.netchan.message, clc_tmove);\n\t\tMSG_WriteCoord (&cls.netchan.message, cl.simorg[0]);\n\t\tMSG_WriteCoord (&cls.netchan.message, cl.simorg[1]);\n\t\tMSG_WriteCoord (&cls.netchan.message, cl.simorg[2]);\n\t}\n}\n\nstatic void Cam_Pos_f (void)\n{\n\tif (Cmd_Argc() == 1)\n\t{\n\t\tCom_Printf (\"\\\"%s %s %s\\\"\\n\", myftos(cl.simorg[0]), myftos(cl.simorg[1]), myftos(cl.simorg[2]));\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() == 2) {\n\t\t// cam_pos \"x y z\"  -->  cam_pos x y z\n\t\tCmd_TokenizeString (va(\"cam_pos %s\", Cmd_Argv(1)));\n\t}\n\n\tif (Cmd_Argc() != 4) {\n\t\tCom_Printf(\"usage:\\n\"\n\t\t\t\"cam_pos - show current coordinates\\n\"\n\t\t\t\"cam pos x y z - set new coordinates\\n\");\n\t\treturn;\n\t}\n\n\tif (!cls.demoplayback && !cl.spectator)\n\t\treturn;\n\n\tCam_Reset();\n\tCam_Pos_Set(Q_atof(Cmd_Argv(1)), Q_atof(Cmd_Argv(2)), Q_atof(Cmd_Argv(3)));\n}\n\nvoid Cam_Angles_Set(float pitch, float yaw, float roll)\n{\n\tcl.simangles[0] = pitch;\n\tcl.simangles[1] = yaw;\n\tcl.simangles[2] = roll;\n\tVectorCopy (cl.simangles, cl.viewangles);\n}\n\nstatic void Cam_Angles_f (void)\n{\n\tif (Cmd_Argc() == 1)\n\t{\n\t\tCom_Printf (\"\\\"%s %s %s\\\"\\n\", myftos(cl.viewangles[0]), myftos(cl.viewangles[1]), myftos(cl.viewangles[2]));\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() == 2) {\n\t\t// cam_angles \"pitch yaw roll\"  -->  cam_pos pitch yaw roll\n\t\tCmd_TokenizeString (va(\"cam_angles %s\", Cmd_Argv(1)));\n\t}\n\n\tif (Cmd_Argc() != 4 && Cmd_Argc() != 3) {\n\t\tCom_Printf(\"usage:\\n\"\n\t\t\t\"cam_pos - show current angles\\n\"\n\t\t\t\"cam pos pitch yaw [roll] - set new angles\\n\");\n\t\treturn;\n\t}\n\t\n\tif (!cls.demoplayback && !cl.spectator)\n\t\treturn;\n\n\tCam_Angles_Set(Q_atof(Cmd_Argv(1)), Q_atof(Cmd_Argv(2)), Q_atof(Cmd_Argv(3)));\n}\n\nstatic char *Macro_Cam_Pos_X (void) { return myftos(cl.simorg[0]); }\nstatic char *Macro_Cam_Pos_Y (void) { return myftos(cl.simorg[1]); }\nstatic char *Macro_Cam_Pos_Z (void) { return myftos(cl.simorg[2]); }\nstatic char *Macro_Cam_Pos (void) {\treturn va(\"\\\"%s %s %s\\\"\", myftos(cl.simorg[0]), myftos(cl.simorg[1]), myftos(cl.simorg[2])); }\n\nstatic char *Macro_Cam_Angles_Pitch (void) { return myftos(cl.viewangles[0]); }\nstatic char *Macro_Cam_Angles_Yaw (void) { return myftos(cl.viewangles[1]); }\nstatic char *Macro_Cam_Angles_Roll (void) { return myftos(cl.viewangles[2]); }\nstatic char *Macro_Cam_Angles (void) { return va(\"\\\"%s %s %s\\\"\", myftos(cl.viewangles[0]), myftos(cl.viewangles[1]), myftos(cl.viewangles[2])); }\n#endif // JSS_CAM\n\n\nvoid CL_InitCam(void) \n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SPECTATOR);\n\n\t// cl_hightrack \n\tCvar_Register (&cl_hightrack); \n\n\tCvar_Register (&cl_chasecam);\n\n\tCvar_ResetCurrentGroup();\n\tCmd_AddCommand (\"track\", CL_Track_f);\n\tCmd_AddCommand (\"autotrack\", CL_Autotrack_f);\n\tCmd_AddCommand (\"trackkiller\", CL_Trackkiller_f);\n\n\t// Multivew tracking.\n\tCmd_AddCommand (\"track1\", CL_TrackMV1_f);\t\n\tCmd_AddCommand (\"track2\", CL_TrackMV2_f);\t\n\tCmd_AddCommand (\"track3\", CL_TrackMV3_f);\t\n\tCmd_AddCommand (\"track4\", CL_TrackMV4_f);\n\tCmd_AddCommand (\"trackteam\", CL_TrackTeam_f);\t\n \n #ifdef JSS_CAM\n\tCmd_AddCommand (\"cam_pos\", Cam_Pos_f);\n\tCmd_AddCommand (\"cam_angles\", Cam_Angles_f);\n\tCmd_AddMacro (macro_cam_pos_x, Macro_Cam_Pos_X);\n\tCmd_AddMacro (macro_cam_pos_y, Macro_Cam_Pos_Y);\n\tCmd_AddMacro (macro_cam_pos_z, Macro_Cam_Pos_Z);\n\tCmd_AddMacro (macro_cam_pos, Macro_Cam_Pos);\n\tCmd_AddMacro (macro_cam_angles_pitch, Macro_Cam_Angles_Pitch);\n\tCmd_AddMacro (macro_cam_angles_yaw, Macro_Cam_Angles_Yaw);\n\tCmd_AddMacro (macro_cam_angles_roll, Macro_Cam_Angles_Roll);\n\tCmd_AddMacro (macro_cam_angles, Macro_Cam_Angles);\n #endif\n \n\t// Multiview tracking.\t\n\tCL_MultiviewInitialise ();\n}\n\nstatic int Cam_MainTrackNum(void)\n{\n\tif (CL_MultiviewInsetEnabled()) {\n\t\treturn CL_MultiviewMainView();\n\t}\n\treturn Cam_TrackNum();\n}\n\n//\n// Change what player we are tracking.\n//\n// trackview:\n// - Should be < 0 if we're in normal mode.\n// - Between 0-3 if we're in multiview.\n//\nvoid CL_Track (int trackview)\n{\n\tint slot;\n\tchar *arg;\n\n\tif (cls.state < ca_connected) \n\t{\n\t\tCom_Printf(\"You must be connected to track\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif (!cl.spectator) \n\t{\n\t\tCom_Printf(\"You can only track in spectator mode\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\t// Don't go outside of the mv_trackslots array bounds.\n\ttrackview = min(trackview, 3);\n\n\t// Allow resetting to default tracking for multiview.\n\tif (trackview >= 0 && !strcmp(Cmd_Args(), \"off\")) \n\t{\n\t\tCom_Printf(\"Track %d resetting to default\\n\", trackview);\n\t\tCL_MultiviewSetTrackSlot (trackview, -1);\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() != 2) \n\t{\n\t\tif (trackview < 0)\n\t\t{\n\t\t\t// Normal track.\n\t\t\tCom_Printf(\"Usage: %s <userid> | <name>\\n\", Cmd_Argv(0));\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Multiview track.\n\t\t\tCom_Printf(\"Usage: %s <userid> | <name> | <off>\\n\", Cmd_Argv(0));\n\t\t}\n\t\treturn;\n\t}\n\n\tslot = Player_GetSlot(arg = Cmd_Argv(1), true);\n\n\t//\n\t// The specified player wasn't found.\n\t//\n\tif (slot == PLAYER_NAME_NOMATCH) \n\t{\n\t\tCom_Printf(\"%s : no such player %s\\n\", Cmd_Argv(0), arg);\n\t\treturn;\n\t} \n\telse if (slot == PLAYER_ID_NOMATCH) \n\t{\n\t\tCom_Printf(\"%s : no player with userid %d\\n\", Cmd_Argv(0), Q_atoi(arg));\n\t\treturn;\n\t} \n\telse if (slot < 0 || slot >= MAX_CLIENTS) \n\t{\t\n\t\t// PLAYER_NUM_MISMATCH covered by this\n\t\tCom_Printf(\"%s : no such player\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\t// A player has been found that we want to track.\n\tif (cl.players[slot].spectator) \n\t{\n\t\tCom_Printf(\"You cannot track a spectator\\n\", Cmd_Argv(0));\n\t} \n\telse if (Cam_MainTrackNum() != slot || trackview >= 0)\n\t{\n\t\t// If we're not already tracking the found slot\n\t\t// set the camera to track mode and lock it to the selected slot.\n\t\t// (Locked as in \"not free flying\", not \"cannot change tracked player\")\n\t\tcl.autocam = CAM_TRACK;\n\t\tCam_Lock(slot);\n\t\tcl.ideal_track = slot;\n\t\t\n\t\t// Multiview tracking:\n\t\t// Set the specified track view to track the specified player.\n\t\tCL_MultiviewSetTrackSlot(trackview, slot);\n\n\t\tcl.spec_locked = true;\n\n\t\tif (cls.mvdplayback && cl.teamfortress)\n\t\t{\n\t\t\tV_TF_ClearGrenadeEffects();\n\t\t}\n\t}\n}\n\nvoid CL_Track_f(void) \n{\n\tCL_Track(-1);\t\n}\n\nvoid CL_Trackkiller_f(void)\n{\n\tif (killer >= 0 && killer < MAX_CLIENTS) {\n\t\tchar buf[16];\n\t\tsnprintf(buf, sizeof(buf), \"track %d\\n\", cl.players[killer].userid);\n\t\tCbuf_AddText(buf);\n\t}\n}\n\n// auto-tracking is a feature implemented in three different places in QW\n// - server side, client side (for a demo), recorded in a demo\n// this command will choose which feature is available\n// at the moment and will toggle it (on/off)\nvoid CL_Autotrack_f(void)\n{\n\tcmd_alias_t* at;\n\textern cvar_t mvd_autotrack;\n\textern cvar_t demo_autotrack;\n\tqbool mvda = mvd_autotrack.integer ? true : false;\n\tqbool demoa = demo_autotrack.integer ? true : false;\n\n\tif (cls.demoplayback) {\n\t\tif (cls.mvdplayback) {\n\t\t\tif (cl_hightrack.integer) {\n\t\t\t\tCvar_SetValue(&cl_hightrack, 0);\n\t\t\t}\n\n\t\t\tif (!mvda && !demoa) {\n\t\t\t\t// we will turn on both features but if demo_autotrack info is found\n\t\t\t\t// it will turn off mvd_autotrack\n\t\t\t\tCvar_SetValue(&mvd_autotrack, 4);\n\t\t\t\tCvar_SetValue(&demo_autotrack, 1);\n\t\t\t\tCom_Printf(\"MVD Autotracking on\\n\");\n\t\t\t} else if (mvda && !demoa) {\n\t\t\t\tCom_Printf(\"MVD Autotracking off\\n\");\n\t\t\t\tCvar_SetValue(&mvd_autotrack, 0);\n\t\t\t} else if (!mvda && demoa) {\n\t\t\t\tCom_Printf(\"Demo Autotracking off\\n\");\n\t\t\t\tCvar_SetValue(&demo_autotrack, 0);\n\t\t\t} else { // mvda && demoa\n\t\t\t\tCom_Printf(\"Autotracking off\\n\");\n\t\t\t\tCvar_SetValue(&mvd_autotrack, 0);\n\t\t\t\tCvar_SetValue(&demo_autotrack, 0);\n\t\t\t}\n\t\t} else {\n\t\t\tCom_Printf(\"Only one point of view is recorded in this demo\\n\");\n\t\t}\n\t}\n\telse { // not playing a demo\n\t\tif (cl.spectator) {\n\t\t\tif ((at = Cmd_FindAlias(\"autotrack\")) != NULL) {\n\t\t\t\t// not very \"clean\" way to execute an alias, but sufficient for this purpose\n\t\t\t\tCbuf_AddText(va(\"%s\\n\", at->value)); // note KTX this is cmd 154, but we want to be compatible with other mods/versions\n\n\t\t\t\t/* Bugfix: When setting autotrack ON, make sure to set cl_hightrack 0.\n\t\t\t\tIf player hits autotrack bind before KTX had a chance to stuff the impulse, then ezQuake would set cl_hightrack to 1.\n\t\t\t\tThen, if player hits autotrack again after KTX has finished stuffing, both autotrack and cl_hightrack would be on, creating chaos.\n\t\t\t\t\n\t\t\t\tHOWEVER, this creates a different, albeit less frustrating bug: if you have autotrack on first, then set cl_hightrack 1, then turn off autotrack, cl_hightrack gets set to 0.\n\t\t\t\tCurrently there is no better way to solve this as autotrack is simply a command sent to the server.*/\t\t\t\t\n\t\t\t\tif (cl_hightrack.integer) {\n\t\t\t\t\tCvar_SetValue(&cl_hightrack, 0);\n\t\t\t\t\tCom_Printf(\"Hightrack off\\n\");\n\t\t\t\t}\t\t\n\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (!cl_hightrack.integer) {\n\t\t\t\t\tCom_Printf(\"Autotrack not supported here, tracking top fragger (Hightrack on)\\n\");\n\t\t\t\t\tCvar_SetValue(&cl_hightrack, 1);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tCom_Printf(\"Hightrack off\\n\");\n\t\t\t\t\tCvar_SetValue(&cl_hightrack, 0);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n//\n// Returns the player id of the currently tracked player (not free flying).\n//\nint Cam_TrackNum(void) \n{\n\tstatic int mvlatch;\n\n\t// If we're free-flying, temporarily turn cl_multiview off\n\tif (cl_multiview.value && !cl.spec_locked)\n\t{\n\t\tmvlatch = cl_multiview.value;\n\t\tcl_multiview.value = 0;\n\t}\n\telse if (!cl_multiview.value && mvlatch && cl.spec_locked)\n\t{\n\t\tcl_multiview.value = mvlatch;\n\t\tmvlatch = 0;\n\t}\n\n\tif (!cl.autocam) {\n\t\treturn -1;\n\t}\n\t\n\treturn cl.spec_track;\n}\n\nint WhoIsSpectated (void)\n{\n\tif (cl.spectator && cl.autocam == CAM_TRACK && cl.players[cl.spec_track].name[0]) {\n\t\treturn cl.spec_track;\n\t}\n\n    return -1;\n}\n"
  },
  {
    "path": "src/cl_cmd.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: cl_cmd.c,v 1.58 2007-10-11 05:55:47 dkure Exp $\n*/\n\n#include <time.h>\n#include \"quakedef.h\"\n#include \"sha1.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"config_manager.h\"\n#include \"rulesets.h\"\n#include \"version.h\"\n#include \"utils.h\"\n#include \"menu.h\"\n#include \"qtv.h\"\n#include \"fs.h\"\n\n/*time type to be used for rcon encryption*/\n#define __qtime_t uint64_t\n\nvoid SCR_RSShot_f (void);\nvoid CL_ProcessServerInfo(void);\nvoid SV_Serverinfo_f(void);\nvoid S_StopAllSounds(void);\n\n\ncvar_t cl_sayfilter_coloredtext = {\"cl_sayfilter_coloredtext\", \"0\"};\ntypedef enum coloredtextfilterlevel_e\n{\n\tcoltextfilter_none = 0,\n\tcoltextfilter_color = 1,\n\tcoltextfilter_colorwhite = 2\n} coloredtextfilterlevel_e;\n#define SAYSTRING_UNCOLORED_FILTERMARK\t\"#u\"\n#define SAYSTRING_COLORED_FILTERMARK\t\"#c\"\n\ncvar_t cl_sayfilter_sendboth = {\"cl_sayfilter_sendboth\", \"0\"};\n\n//adds the current command line as a clc_stringcmd to the client message.\n//things like kill, say, etc, are commands directed to the server,\n//so when they are typed in at the console, they will need to be forwarded.\nvoid Cmd_ForwardToServer (void) {\n\tchar *s;\n\n\tif (cls.mvdplayback == QTV_PLAYBACK) {\n\t\tQTV_Cmd_ForwardToServer();\n\t\treturn;\n\t}\n\n\tif (cls.state == ca_disconnected) {\n\t\tCom_Printf (\"Can't \\\"%s\\\", not connected\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t// lowercase command\n\tfor (s = Cmd_Argv(0); *s; s++)\n\t\t*s = (char) tolower(*s);\n\tSZ_Print (&cls.netchan.message, Cmd_Argv(0));\n\tif (Cmd_Argc() > 1) {\n\t\tSZ_Print (&cls.netchan.message, \" \");\n\t\tSZ_Print (&cls.netchan.message, Cmd_Args());\n\t}\n}\n\n// don't forward the first argument\nvoid CL_ForwardToServer_f (void) {\n// Added by VVD {\n\tchar* server_string;\n\tchar client_time_str[sizeof(__qtime_t) * 2 + 1] = { 0 };\n\tint i, server_string_len;\n\textern cvar_t cl_crypt_rcon;\n\t__qtime_t client_time = 0;\n// Added by VVD }\n\n\tif (cls.mvdplayback == QTV_PLAYBACK) {\n\t\tQTV_Cl_ForwardToServer_f();\n\t\treturn;\n\t}\n\n\tif (cls.state == ca_disconnected) {\n\t\tCom_Printf (\"Can't \\\"%s\\\", not connected\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif (cls.demoplayback)\n\t\treturn;\t\t// not really connected\n\n\tif (Cmd_Argc() > 1) {\n\t\tif (strcasecmp(Cmd_Argv(1), \"snap\") == 0) {\n\t\t\tSCR_RSShot_f ();\n\t\t\treturn;\n\t\t}\n\n//bliP ->\n\t\tif (strcasecmp(Cmd_Argv(1), \"fileul\") == 0) {\n\t\t\tCL_StartFileUpload ();\n\t\t\treturn;\n\t\t}\n//<-\n\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n/* johnnycz: disabled due to security reasons -- fixme\n\t\tif (strcasecmp(Cmd_Argv(1), \"download\") == 0 && Cmd_Argc() > 2)\n\t\t{\n\t\t\tstrlcpy(cls.downloadname, Cmd_Argv(2), sizeof(cls.downloadname));\n\t\t\tCOM_StripExtension(cls.downloadname, cls.downloadtempname);\n\t\t\tstrlcat(cls.downloadtempname, \".tmp\", sizeof(cls.downloadtempname));\n\t\t\tcls.downloadtype = dl_single;\n\t\t\t//snprintf (cls.downloadname, sizeof(cls.downloadname), \"%s\", Cmd_Argv(2));\n\t\t\t//strlcpy (cls.downloadtempname, cls.downloadname, sizeof(cls.downloadtempname));\n\t\t}\n*/\n// Added by VVD {\n\t\tif (cl_crypt_rcon.value && strcasecmp(Cmd_Argv(1), \"techlogin\") == 0 && Cmd_Argc() > 2)\n\t\t{\n\t\t\ttime((time_t *)&client_time);\n\t\t\tfor (client_time_str[0] = i = 0; i < sizeof(client_time); i++) {\n\t\t\t\tchar tmp[3];\n\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%02X\", (unsigned int)((client_time >> (i * 8)) & 0xFF));\n\t\t\t\tstrlcat(client_time_str, tmp, sizeof(client_time_str));\n\t\t\t}\n\n\t\t\tserver_string_len = Cmd_Argc() + strlen(Cmd_Argv(1)) + DIGEST_SIZE * 2 + 16;\n\t\t\tfor (i = 3; i < Cmd_Argc(); ++i)\n\t\t\t\tserver_string_len += strlen(Cmd_Argv(i));\n\t\t\tserver_string = (char *) Q_malloc(server_string_len);\n\n\t\t\tSHA1_Init();\n\t\t\tSHA1_Update((unsigned char *)Cmd_Argv(1));\n\t\t\tSHA1_Update((unsigned char *)\" \");\n\t\t\tSHA1_Update((unsigned char *)Cmd_Argv(2));\n\t\t\tSHA1_Update((unsigned char *)client_time_str);\n\t\t\tSHA1_Update((unsigned char *)\" \");\n\t\t\tfor (i = 3; i < Cmd_Argc(); ++i)\n\t\t\t{\n\t\t\t\tSHA1_Update((unsigned char *)Cmd_Argv(i));\n\t\t\t\tSHA1_Update((unsigned char *)\" \");\n\t\t\t}\n\n\t\t\tsnprintf(server_string, server_string_len, \"%s %s%s \",\n\t\t\t\tCmd_Argv(1), SHA1_Final(), client_time_str);\n\t\t\tfor (i = 3; i < Cmd_Argc(); ++i)\n\t\t\t{\n\t\t\t\tstrlcat(server_string, Cmd_Argv(i), server_string_len);\n\t\t\t\tstrlcat(server_string, \" \", server_string_len);\n\t\t\t}\n\t\t\tSZ_Print (&cls.netchan.message, server_string);\n\t\t\tQ_free(server_string);\n\t\t}\n\t\telse\n// Added by VVD }\n\t\t\tSZ_Print (&cls.netchan.message, Cmd_Args());\n\t}\n}\n\n/// filters white markup chars from the string\nstatic void CL_Cmd_SayString_FilterWhite(char *s)\n{\n\tchar *rp = s;\t// read pointer\n\tchar *wp = s;\t// write pointer\n\tchar c;\t\t\t// current char\n\n\twhile ((c = *rp++)) {\n\t\tif (c == '{' || c == '}') continue;\n\t\t*wp++ = c;\n\t}\n\n\t*wp = '\\0';\n}\n\n/// filters '&cfa5'-like colored markup from the string\nstatic void CL_Cmd_SayString_FilterColoredText(char *s)\n{\n\tchar *rp = s;\t// read pointer\n\tchar *wp = s;\t// write pointer\n\tchar c;\t\t\t// current char\n\n\twhile ((c = *rp++)) {\n\t\tif (c == '&' && *rp == 'c') {\n\t\t\trp++;\t// skip 'c'\n\t\t\tif (*rp) rp++; else break;\t// skip red value\n\t\t\tif (*rp) rp++; else break;\t// skip green value\n\t\t\tif (*rp) rp++; else break;\t// skip blue value\n\t\t\tcontinue;\n\t\t}\n\t\t*wp++ = c;\n\t}\n\n\t*wp = '\\0';\n}\n\n// applies colored text filters on the string\nstatic void CL_Cmd_SayString_ApplyFilters(char *s)\n{\n\tif (cl_sayfilter_coloredtext.integer != coltextfilter_none) {\n\t\tCL_Cmd_SayString_FilterColoredText(s);\n\t}\n\tif (cl_sayfilter_coloredtext.integer == coltextfilter_colorwhite) {\n\t\tCL_Cmd_SayString_FilterWhite(s);\n\t}\n}\n\n// inserts an appendix to the string\n// if there already is an appendix (starting with #), it will be overwritten\n// if the string is enclosed in quotes (\"), they will be kept\nstatic void CL_Cmd_SayString_InsertAppendix(char *s, char *appendix)\n{\n\tchar *p = strchr(s, '#');\n\tsize_t l = strlen(s);\n\tqbool endquote = s[l-1] == '\\\"';\n\n\tif (!p) p = s + l - 1;\n\n\twhile ((*p++ = *appendix++)) {}\n\tp--;\n\tif (endquote)\n\t\t*p++ = '\\\"';\n\t*p = '\\0';\n}\n\n// sends saystring with optional appendix\nstatic void CL_Cmd_SayString_SendBase(char *s, char *appendix)\n{\n\tif (*s && *s < 32) {\n\t\tSZ_Print (&cls.netchan.message, \"\\\"\");\n\t\tif (appendix) {\n\t\t\tCL_Cmd_SayString_InsertAppendix(s, appendix);\n\t\t}\n\t\tSZ_Print (&cls.netchan.message, s);\n\t\tSZ_Print (&cls.netchan.message, \"\\\"\");\n\t} else {\n\t\tif (appendix) {\n\t\t\tCL_Cmd_SayString_InsertAppendix(s, appendix);\n\t\t}\n\t\tSZ_Print (&cls.netchan.message, s);\n\t}\n}\n\n/// returns true if there is a color markup in the string\nstatic qbool CL_Cmd_SayString_IsColored(char *s)\n{\n\twhile(*s) {\n\t\tif (*s == '&' && s[1] == 'c') return true;\n\t\ts++;\n\t}\n\treturn false;\n}\n\n// this function presumes there's at least two more bytes available memory\n// after the end of the string\nstatic void CL_Cmd_SayString_Send(char *s)\n{\n\tif (cl_sayfilter_sendboth.integer && cl_sayfilter_coloredtext.integer\n\t\t&& !strcmp(\"say_team\", Cmd_Argv(0)) && CL_Cmd_SayString_IsColored(s)) {\n\t\tCL_Cmd_SayString_SendBase(s, SAYSTRING_COLORED_FILTERMARK);\n\t\tCL_Cmd_SayString_ApplyFilters(s);\n\t\t\n\t\t// send header again\n\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\tSZ_Print (&cls.netchan.message, Cmd_Argv(0));\n\t\tSZ_Print (&cls.netchan.message, \" \");\n\t\t\n\t\tCL_Cmd_SayString_SendBase(s, SAYSTRING_UNCOLORED_FILTERMARK);\n\t} else {\n\t\tCL_Cmd_SayString_ApplyFilters(s);\n\t\tCL_Cmd_SayString_SendBase(s, NULL);\n\t}\n}\n\n//Handles both say and say_team\nvoid CL_Say_f (void) {\n\tchar *s, msg[1024], qmsg[1024];\n\tint tmp;\n\tqbool qizmo = false;\n\textern cvar_t cl_fakename;\n\textern cvar_t cl_fakename_suffix;\n\n\tif (cls.mvdplayback == QTV_PLAYBACK) {\n\t\tQTV_Say_f();\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() < 2)\n\t\treturn;\n\n\tif (cls.state == ca_disconnected) {\n\t\tCom_Printf (\"Can't \\\"%s\\\", not connected\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t// lowercase command\n\tfor (s = Cmd_Argv(0); *s; s++)\n\t\t*s = (char) tolower(*s);\n\tSZ_Print (&cls.netchan.message, Cmd_Argv(0));\n\n\tif (CL_ConnectedToProxy()) {\t\n\t\tfor (s = Cmd_Argv(1); *s == ' '; s++)\n\t\t\t;\n\t\tif (!strncmp(s, \".stuff\", 6) || !strncmp(s, \",stuff\", 6) || strstr(s, \":stuff\"))\n\t\t\treturn;\t\t\n\n\t\tqizmo = (!strncmp(s, \"proxy:\", 6) || s[0] == ',' || s[0] == '.');\n\t}\n\n\t\n\tif (!qizmo && cl_floodprot.value && cl_fp_messages.value > 0 && cl_fp_persecond.value > 0) {\n\t\ttmp = cl.whensaidhead - min(cl_fp_messages.value, 10) + 1;\n\t\tif (tmp < 0)\n\t\t\ttmp += 10;\n\t\tif (cl.whensaid[tmp] && (cls.realtime - cl.whensaid[tmp]) < (1.02 * cl_fp_persecond.value)) {\n\t\t\tCom_Printf(\"Flood Protection\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\t\n\n\tSZ_Print (&cls.netchan.message, \" \");\n\n\ts = TP_ParseMacroString (Cmd_Args());\n\ts = TP_ParseFunChars (s, true);\n\n    /* The string in 's' can be both wrapped in quotes or without wrapper quotes,\n       but to make cl_fakename works properly, we need it to be always wrapped in quotes.\n       messagemode/messagemode2 commands send text in quotes,\n       say/say_team typed in the console sends text without quotes\n       So if quotes are missing, we add them.\n    */\n    // first char isn't quote, last char isn't quote or string is shorter then 2 chars\n    if (s[0] != '\\\"' || s[strlen(s)-1] != '\\\"' || s[1] == '\\0')\n    {\n        snprintf(qmsg, sizeof(qmsg), \"\\\"%s\\\"\", s);\n        s = qmsg;\n    }\n\n    // if team message mode and teamname is set and message is not custom mm2 message...\n\tif (!strcasecmp(Cmd_Argv(0), \"say_team\") && !cl.spectator && cl_fakename.string[0] && !strchr(s, '\\x0d'))\n\t{\n\t\tchar c_fn[1024], c_fna[1024], c_msg[1024];\n        size_t len = strlen(s) - 1; // cut the trailing quote (\")\n\n        len = bound(0, len, sizeof(c_fn));\n\n\t\t// TP_ParseFunChars wants a string < 1024 chars (fix it?)\n        strlcpy (c_fn, cl_fakename.string, sizeof(c_fn));\n        strlcpy (c_fna, cl_fakename_suffix.string, sizeof(c_fna));\n\t\t\n        // 1) save the message text, because TP_ParseFunChars will overwrite the temp memory\n        // 2) cut the leading quote (+1) and also the trailing quote (len is 1 char shorter)\n        strlcpy (c_msg, s+1, len);\n\n\t\tsnprintf (msg, sizeof(msg), \"\\x0d%s%s\", TP_ParseFunChars(strcat(c_fn, c_fna), true), c_msg);\n\n\t\ts = msg;\n\t}\n\n\tCL_Cmd_SayString_Send(s);\n\t\n\tif (!qizmo) {\n\t\tcl.whensaidhead++;\n\t\tif (cl.whensaidhead > 9)\n\t\t\tcl.whensaidhead = 0;\n\t\tcl.whensaid[cl.whensaidhead] = cls.realtime;\n\t}\n\t\n}\n\nvoid CL_Pause_f (void) {\n\tif (cls.demoplayback)\n\t\tcl.paused ^= PAUSED_DEMO;\n\telse\n\t\tCmd_ForwardToServer();\n}\n\n//packet <destination> <contents>\n//Contents allows \\n escape character\nvoid CL_Packet_f(void) {\n// TCPCONNECT -->\n\tint tcpsock;\n// <--TCPCONNECT\n\tnetadr_t adr;\n\tchar send[2048], *in, *out;\n\n\tif (Cmd_Argc() != 3) {\n\t\tCom_Printf(\"packet <destination> <contents>\\n\");\n\t\treturn;\n\t}\n\n\tif (cbuf_current && cbuf_current != &cbuf_svc && Rulesets_RestrictPacket()) {\n\t\tCom_Printf(\"Packet command is disabled during match\\n\");\n\t\treturn;\n\t}\n\n\tif (!NET_StringToAdr(Cmd_Argv(1), &adr)) {\n\t\tCom_Printf(\"Bad address\\n\");\n\t\treturn;\n\t}\n\n\tif (adr.port == 0)\n\t\tadr.port = BigShort(PORT_SERVER);\n\n\tsend[0] = send[1] = send[2] = send[3] = 0xFF;\n\n\tin = Cmd_Argv(2);\n\tout = send + 4;\n\n\twhile (*in && out - send < sizeof(send) - 2) {\n\t\tif (in[0] == '\\\\' && in[1]) {\n\t\t\tswitch(in[1]) {\n\t\t\t\tcase 'n' : *out++ = '\\n'; break;\n\t\t\t\tcase 't' : *out++ = '\\t'; break;\n\t\t\t\tcase '\\\\' : *out++ = '\\\\'; break;\n\t\t\t\tdefault : *out++ = in[0]; *out++ = in[1]; break;\n\t\t\t}\n\t\t\tin += 2;\n\t\t} else {\n\t\t\t*out++ = *in++;\n\t\t}\n\t}\n\t*out = 0;\n\n// TCPCONNECT -->\n\t//extra code to stop the packet command from sending to the server via tcp\n\ttcpsock = cls.sockettcp;\n\tcls.sockettcp = -1;\n\tNET_SendPacket (NS_CLIENT, out-send, send, adr);\n\tcls.sockettcp = tcpsock;\n// <--TCPCONNECT\n}\n\n\nvoid CL_PrintQStatReply (char *s) {\n\tchar *p;\n\tint n, numplayers;\n\tint userid, frags, time, ping, topcolor, bottomcolor;\n\tchar name[33], skin[17];\n\n\tCom_Printf (\"\\n\");\n\tCom_Printf (\"-------------------------------------\\n\");\n\n\tcon_ormask = 128;\n\tCom_Printf (\"qstat %s:\\n\", NET_AdrToString(net_from));\n\tcon_ormask = 0;\n\n\t// count players\n\tnumplayers = -1;\n\tp = s;\n\twhile (*p) if (*p++ == '\\n') numplayers++;\n\n\t// extract serverinfo string\n\ts = strtok (s, \"\\n\");\n\n\tCom_Printf (\"hostname   %s\\n\", Info_ValueForKey(s, \"hostname\"));\n\tif (*(p = Info_ValueForKey(s, \"*gamedir\")) && strcmp(p, \"qw\"))\n\t\tCom_Printf (\"gamedir    %s\\n\", p);\n\tCom_Printf (\"map        %s\\n\", Info_ValueForKey(s, \"map\"));\n\tif (*(p = Info_ValueForKey(s, \"status\")))\n\t\tCom_Printf (\"status     %s\\n\", p);\n\tCom_Printf (\"deathmatch %s\\n\", Info_ValueForKey(s, \"deathmatch\"));\n\tCom_Printf (\"teamplay   %s\\n\", Info_ValueForKey(s, \"teamplay\"));\n\tCom_Printf (\"timelimit  %s\\n\", Info_ValueForKey(s, \"timelimit\"));\n\tCom_Printf (\"fraglimit  %s\\n\", Info_ValueForKey(s, \"fraglimit\"));\n\tif ((n = Q_atoi(Info_ValueForKey(s, \"needpass\")) & 3) != 0)\n\t\tCom_Printf (\"needpass   %s%s%s\\n\", n & 1 ? \"player\" : \"\",\n\t\t\tn == 3 ? \", \" : \"\", n & 2 ? \"spectator\" : \"\");\n\tif (Q_atoi(Info_ValueForKey(s, \"needpass\")) & 1)\n\t\tCom_Printf (\"player password required\\n\");\n\tif (Q_atoi(Info_ValueForKey(s, \"needpass\")) & 2)\n\t\tCom_Printf (\"spectator password required\\n\");\n\n\tCom_Printf (\"players    %i/%s\\n\", numplayers, Info_ValueForKey(s, \"maxclients\"));\n\n\tp = strtok (NULL, \"\\n\");\n\n\tif (p)\n\t{\n\t\tcon_ormask = 128;\n\t\tCom_Printf (\"\\nping time frags name\\n\");\n\t\tcon_ormask = 0;\n\t\tCom_Printf (\"-------------------------------------\\n\");\n\n\t\twhile (p)\n\t\t{\n\t\t\tsscanf (p, \"%d %d %d %d \\\"%32[^\\\"]\\\" \\\"%16[^\\\"]\\\" %d %d\",\n\t\t\t\t&userid, &frags, &time, &ping, (char *)&name, (char *)&skin, &topcolor, &bottomcolor);\n\t\t\tCom_Printf(\"%4d %4d %4d  %-16.16s\\n\", ping, time, frags, name);\n\t\t\tp = strtok (NULL, \"\\n\");\n\t\t}\n\t\tCom_Printf (\"-------------------------------------\\n\");\n\t}\n\n\tCom_Printf (\"\\n\");\n}\n\n/*\n====================\nCL_QStat_f\n\nqstat <destination>\n====================\n*/\ndouble\tqstat_senttime = 0;\n\nvoid CL_QStat_f (void)\n{\n\tchar\tsend[10] = {0xff, 0xff, 0xff, 0xff, 's', 't', 'a', 't', 'u', 's'};\n\tnetadr_t\tadr;\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tCom_Printf (\"usage: qstat <server>\\n\");\n\t\treturn;\n\t}\n\n\tif (!NET_StringToAdr (Cmd_Argv(1), &adr))\n\t{\n\t\tCom_Printf (\"Bad address\\n\");\n\t\treturn;\n\t}\n\n\tif (adr.port == 0)\n\t\tadr.port = BigShort (PORT_SERVER);\n\n\tNET_SendPacket (NS_CLIENT, 10, send, adr);\n\n\tqstat_senttime = curtime;\n}\n\n\n//Send the rest of the command line over as an unconnected command.\nvoid CL_Rcon_f (void) {\n\n\tchar message[1024];\n\tchar client_time_str[sizeof(__qtime_t) * 2 + 1];\n\tint i, i_from;\n\tnetadr_t to;\n\textern cvar_t rcon_password, rcon_address, cl_crypt_rcon;\n\t__qtime_t client_time = 0;\n\n\tmessage[0] = (char)255;\n\tmessage[1] = (char)255;\n\tmessage[2] = (char)255;\n\tmessage[3] = (char)255;\n\tmessage[4] = 0;\n\tstrlcat (message, \"rcon \", sizeof(message));\n\n// Added by VVD {\n\tif (cl_crypt_rcon.value)\n\t{\n\t\ttime((time_t *)&client_time);\n\t\tfor (client_time_str[0] = i = 0; i < sizeof(client_time); i++) {\n\t\t\tchar tmp[3];\n\t\t\tsnprintf(tmp, sizeof(tmp), \"%02X\", (unsigned int)((client_time >> (i * 8)) & 0xFF));\n\t\t\tstrlcat(client_time_str, tmp, sizeof(client_time_str));\n\t\t}\n\t\t\n\t\tSHA1_Init();\n\t\tSHA1_Update((unsigned char *)\"rcon \");\n\t\tif (rcon_password.string[0])\n\t\t{\n\t\t\tSHA1_Update((unsigned char *)rcon_password.string);\n\t\t\tSHA1_Update((unsigned char *)client_time_str);\n\t\t\ti_from = 1;\n\t\t}\n\t\telse // first arg must be pass in such case, so handle this\n\t\t{\n\t\t\tSHA1_Update((unsigned char *)Cmd_Argv(1));\n\t\t\tSHA1_Update((unsigned char *)client_time_str);\n\t\t\ti_from = 2;\n\t\t}\n\t\tSHA1_Update((unsigned char *)\" \");\n\t\tfor (i = i_from; i < Cmd_Argc(); i++)\n\t\t{\n\t\t\tSHA1_Update((unsigned char *)Cmd_Argv(i));\n\t\t\tSHA1_Update((unsigned char *)\" \");\n\t\t}\n\t\tstrlcat (message, SHA1_Final(), sizeof(message));\n\t\tstrlcat (message, client_time_str, sizeof(message));\n\t\tstrlcat (message, \" \", sizeof(message));\n\t}\n\telse {\n\t\ti_from = 1;\n \t\tif (rcon_password.string[0]) {\n\t\t\tstrlcat (message, rcon_password.string, sizeof(message));\n\t\t\tstrlcat (message, \" \", sizeof(message));\n\t\t}\n\t}\n\tfor (i = i_from; i < Cmd_Argc(); i++)\n\t{\n\t\tstrlcat (message, Cmd_Argv(i), sizeof(message));\n\t\tstrlcat (message, \" \", sizeof(message));\n\t}\n// } Added by VVD\n\n\tif (cls.state >= ca_connected) {\n\t\tto = cls.netchan.remote_address;\n\t} else {\n\t\tif (!strlen(rcon_address.string)) {\n\t\t\tCom_Printf (\"You must either be connected or set 'rcon_address' to issue rcon commands\\n\");\n\t\t\treturn;\n\t\t}\n\t\tNET_StringToAdr (rcon_address.string, &to);\n\t\tif (to.port == 0)\n\t\t\tto.port = BigShort (PORT_SERVER);\n\t}\n\n\tNET_SendPacket (NS_CLIENT, strlen(message)+1, message, to);\n}\n\nqbool CL_Download_Accept(const char *filename)\n{\n\tif (strstr(filename, \"..\") || !strcmp(filename, \"\") || filename[0] == '/' || strchr(filename, '\\\\') || strchr(filename, ':') || strstr(filename, \"//\")) {\n\t\tCom_Printf(\"Warning: Invalid characters in filename \\\"%s\\\"\\n\", filename);\n\t\treturn false;\n\t}\n\n\tif (!CL_IsDownloadableFileExtension(filename)) {\n\t\tCom_Printf(\"Warning: Non-allowed file \\\"%s\\\" skipped. Add \\\"%s\\\" to cl_allow_downloads to allow the file to be downloaded\\n\", filename, COM_FileExtension(filename));\n\t\treturn false;\n\t}\n\n\tvfsfile_t *f = FS_OpenVFS(filename, \"rb\", FS_ANY);\n\tif (f) {\n\t\tVFS_CLOSE(f);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid CL_Download_f (void){\n\tchar *dir; // we save to demo_dir or gamedir\n\tchar *filename; // which file to dl, will be sent to server\n\tchar ondiskname[sizeof(cls.downloadname)]; // hack, save file to \"right\" place\n\textern char *CL_DemoDirectory(void);\n\n\tif (cls.state == ca_disconnected) {\n\t\tCom_Printf (\"Must be connected.\\n\");\n\t\treturn;\n\t}\n\n\tfilename = Cmd_Argv(1);\n\tstrlcpy(ondiskname, filename, sizeof(ondiskname)); // in most cases this is same as filename\n\n\tif (Cmd_Argc() != 2 || !filename[0] || !CL_Download_Accept(filename)) {\n\t\tCom_Printf (\"Usage: %s <datafile>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\t// hack: save demos in demo_dir\n\tif (\n\t\t\t\t    Utils_RegExpMatch(\"\\\\.mvd(\\\\.(gz|bz2|rar|zip))?$\", filename)\n\t\t\t\t || Utils_RegExpMatch(\"\\\\.(qwd|qwz|dem)$\", filename)\n\t\t\t) {\n\t\tdir = CL_DemoDirectory(); // seems filename is a demo\n\t\t// so, we save file in /dir/<demo_dir> instead of /dir/<demo_dir>/demos\n\t\tstrlcpy(ondiskname, COM_SkipPath(filename), sizeof(ondiskname));\n\t}\n\telse\n\t\tdir = cls.gamedir; // not a demo\n\n\t// download to a temp name, and only rename\n\t// to the real name when done, so if interrupted\n\t// a runt file wont be left\n\n\tcls.downloadtype      = dl_single;\n\tcls.downloadmethod    = DL_QW; // by default its DL_QW, if server support DL_QWCHUNKED it will be changed.\n\tcls.downloadstarttime = Sys_DoubleTime();\n\n\tsnprintf(cls.downloadname, sizeof(cls.downloadname), \"%s/%s\", dir, ondiskname);\n\tCOM_StripExtension(cls.downloadname, cls.downloadtempname, sizeof(cls.downloadtempname));\n\tstrlcat(cls.downloadtempname, \".tmp\", sizeof(cls.downloadtempname));\n\n\tif (cls.mvdplayback == QTV_PLAYBACK)\n\t{\n\t\tQTV_Cmd_Printf(QTV_EZQUAKE_EXT_DOWNLOAD, \"download \\\"%s\\\"\", filename);\n\t}\n\telse\n\t{\n\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\tSZ_Print (&cls.netchan.message, va(\"download \\\"%s\\\"\", filename));\n\t}\n}\n\nvoid CL_User_f (void) {\n\tint uid, i;\n\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf (\"Usage: %s <username / userid>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tuid = atoi(Cmd_Argv(1));\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!cl.players[i].name[0])\n\t\t\tcontinue;\n\t\tif (cl.players[i].userid == uid\t|| !strcmp(cl.players[i].name, Cmd_Argv(1)) ) {\n\t\t\tInfo_Print (cl.players[i].userinfo);\n\t\t\treturn;\n\t\t}\n\t}\n\tCom_Printf (\"User not in server.\\n\");\n}\n\nvoid CL_Users_f (void) {\n\tint i, c;\n\n\tc = 0;\n\tCom_Printf (\"userid frags name\\n\");\n\tCom_Printf (\"------ ----- ----\\n\");\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (cl.players[i].name[0]) {\n\t\t\tCom_Printf (\"%6i %4i %s\\n\", cl.players[i].userid, cl.players[i].frags, cl.players[i].name);\n\t\t\tc++;\n\t\t}\n\t}\n\n\tCom_Printf (\"%i total users\\n\", c);\n}\n\nvoid CL_Color_f (void) {\n\textern cvar_t topcolor, bottomcolor;\n\tint top, bottom;\n\n\tswitch (Cmd_Argc())\n\t{\n\t\tcase 1:\n\t\t\tCom_Printf (\"\\\"color\\\" is \\\"%s %s\\\"\\n\",\n\t\t\t\tInfo_ValueForKey (cls.userinfo, \"topcolor\"),\n\t\t\t\tInfo_ValueForKey (cls.userinfo, \"bottomcolor\") );\n\t\t\tCom_Printf (\"color <0-16> [0-16]\\n\");\n\t\t\treturn;\n\t\tcase 2:\n\t\t\ttop = bottom = Q_atoi(Cmd_Argv(1));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\ttop = Q_atoi(Cmd_Argv(1));\n\t\t\tbottom = Q_atoi(Cmd_Argv(2));\n\t}\n\n\tCvar_SetValue (&topcolor, bound(0, top, 16));\n\tCvar_SetValue (&bottomcolor, bound(0, bottom, 16));\n}\n\n//usage: fullinfo \\name\\unnamed\\topcolor\\0\\bottomcolor\\1, etc\nvoid CL_FullInfo_f (void) {\n\tchar key[512], value[512], *o, *s;\n\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf (\"fullinfo <complete info string>\\n\");\n\t\treturn;\n\t}\n\n\ts = Cmd_Argv(1);\n\tif (*s == '\\\\')\n\t\ts++;\n\twhile (*s) {\n\t\to = key;\n\t\twhile (*s && *s != '\\\\')\n\t\t\t*o++ = *s++;\n\t\t*o = 0;\n\n\t\tif (!*s) {\n\t\t\tCom_Printf (\"MISSING VALUE\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\to = value;\n\t\ts++;\n\t\twhile (*s && *s != '\\\\')\n\t\t\t*o++ = *s++;\n\t\t*o = 0;\n\n\t\tif (*s)\n\t\t\ts++;\n\n\t\tif (!strcasecmp(key, pmodel_name) || !strcasecmp(key, emodel_name))\n\t\t\tcontinue;\n\n\t\tInfo_SetValueForKey (cls.userinfo, key, value, MAX_INFO_STRING);\n\t}\n}\n\n//Allow clients to change userinfo\nvoid CL_SetInfo_f (void) {\n\tif (Cmd_Argc() == 1) {\n\t\tInfo_Print (cls.userinfo);\n\t\tCom_Printf (\"[%i/196]\\n\", strlen(cls.userinfo));\n\t\treturn;\n\t}\n\tif (Cmd_Argc() != 3) {\n\t\tCom_Printf (\"Usage: %s [ <key> <value> ]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\tif (!strcasecmp(Cmd_Argv(1), pmodel_name) || !strcmp(Cmd_Argv(1), emodel_name))\n\t\treturn;\n\n\tInfo_SetValueForKey (cls.userinfo, Cmd_Argv(1), Cmd_Argv(2), MAX_INFO_STRING);\n\tif (cls.state >= ca_connected)\n\t\tCmd_ForwardToServer ();\n}\n\n\nvoid CL_UserInfo_f (void) {\n\tif (Cmd_Argc() != 1) {\n\t\tCom_Printf(\"%s : no arguments expected\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\tInfo_Print (cls.userinfo);\n}\n\nvoid CL_Quit_f (void) {\n\textern cvar_t cl_confirmquit;\n\n\tif (cl_confirmquit.value)\n\t\tM_Menu_Quit_f ();\n\telse\n\t\tHost_Quit ();\n}\n\n// QW262 -->\n/*\n============\nCL_Userdir_f\n\n============\n*/\nvoid CL_Userdir_f (void)\n{\n\tif (cls.state > ca_disconnected || Cmd_Argc() == 1) {\n\t\tCom_Printf(\"Current userdir: %s\\n\", userdirfile);\n\t} else {\n\t\tint u = Q_atoi(Cmd_Argv(2));\n\t\tif (u < 0 || u > 5)\n\t\t\tCom_Printf(\"Invalid userdir type\\n\");\n\t\telse\n\t\t\tFS_SetUserDirectory (Cmd_Argv(1), Cmd_Argv(2));\n\t}\n}\n// <-- QW262\n\nvoid CL_Serverinfo_f (void) \n{\n\t#ifndef CLIENTONLY\n\tif (cls.state < ca_connected || com_serveractive) \n\t{\n\t\tSV_Serverinfo_f();\n\t\treturn;\n\t}\n\t#endif // CLIENTONLY\n\n\tif (cls.state >= ca_onserver && cl.serverinfo[0])\n\t\tInfo_Print (cl.serverinfo);\n\telse\t\t\n\t\tCom_Printf (\"Can't \\\"%s\\\", not connected\\n\", Cmd_Argv(0));\n}\n\n//============================================================================\n\ntypedef struct\n{\n\tconst char\t*name;\n\tint\t\t\tbit;\n} z_ext_map_t;\n\nstatic z_ext_map_t z_map[] =\n{\n\t{ \"PM_TYPE\",\t\tZ_EXT_PM_TYPE },\n\t{ \"PM_TYPE_NEW\",\tZ_EXT_PM_TYPE_NEW },\n\t{ \"VIEWHEIGHT\",\t\tZ_EXT_VIEWHEIGHT },\n\t{ \"SERVERTIME\",\t\tZ_EXT_SERVERTIME },\n\t{ \"PITCHLIMITS\",\tZ_EXT_PITCHLIMITS },\n\t{ \"JOIN_OBSERVE\",\tZ_EXT_JOIN_OBSERVE },\n\t{ \"PF_ONGROUND\",\tZ_EXT_PF_ONGROUND },\n\t{ \"VWEP\",\t\t\tZ_EXT_VWEP },\n\t{ \"PF_SOLID\",\t\tZ_EXT_PF_SOLID },\n};\n\nstatic int z_map_cnt = sizeof(z_map)/sizeof(z_map[0]);\n\nint get_z_ext_list(int bits, char *buf, int bufsize)\n{\n\tint i, cnt;\n\n\tbuf[0] = 0; // hope buf size at least one byte\n\n\tfor (i = cnt = 0; i < z_map_cnt; i++)\n\t{\n\t\tif (!z_map[i].bit || z_map[i].bit != (z_map[i].bit & bits))\n\t\t\tcontinue; // not match\n\n\t\tif (cnt)\n\t\t\tstrlcat(buf, \" \", bufsize);\n\t\tstrlcat(buf, z_map[i].name, bufsize);\n\t\tcnt++;\n\t}\n\n\treturn cnt;\n}\n\nvoid CL_Z_Ext_List_f (void)\n{\n\tchar buf[1024] = {0};\n\n\tCom_Printf(\"ZQuake protocol extensions:\\n\");\n\tCom_Printf(\"%s\\n\", get_z_ext_list(cl.z_ext, buf, sizeof(buf)) ? buf : \"NONE\");\n}\n\n//============================================================================\n\nvoid CL_InitCommands (void) {\n\t// general commands\n\tCmd_AddCommand (\"cmd\", CL_ForwardToServer_f);\n\tCmd_AddCommand (\"download\", CL_Download_f);\n\tCmd_AddCommand (\"qstat\", CL_QStat_f);\n\tCmd_AddCommand (\"packet\", CL_Packet_f);\n\tCmd_AddCommand (\"rcon\", CL_Rcon_f);\n\tCmd_AddCommand (\"pause\", CL_Pause_f);\n\tCmd_AddCommand (\"quit\", CL_Quit_f);\n\tCmd_AddCommand (\"say\", CL_Say_f);\n\tCmd_AddCommand (\"say_team\", CL_Say_f);\n\tCmd_AddCommand (\"serverinfo\", CL_Serverinfo_f);\n\tCmd_AddCommand (\"skins\", Skin_Skins_f);\n\tCmd_AddCommand (\"showskins\", Skin_ShowSkins_f);\n\tCmd_AddCommand (\"user\", CL_User_f);\n\tCmd_AddCommand (\"users\", CL_Users_f);\n\tCmd_AddCommand (\"version\", CL_Version_f);\n\n\t// client info setting\n\tCmd_AddCommand (\"color\", CL_Color_f);\n\tCmd_AddCommand (\"fullinfo\", CL_FullInfo_f);\n\tCmd_AddCommand (\"setinfo\", CL_SetInfo_f);\n\tCmd_AddCommand (\"userinfo\", CL_UserInfo_f);\n// QW262 -->\n\tCmd_AddCommand (\"userdir\", CL_Userdir_f);\n// <-- QW262\n\t// forward to server commands\n\tCmd_AddCommand (\"kill\", NULL);\n\tCmd_AddCommand (\"god\", NULL);\n\tCmd_AddCommand (\"give\", NULL);\n\tCmd_AddCommand (\"noclip\", NULL);\n\tCmd_AddCommand (\"fly\", NULL);\n\n\t//  Windows commands\n\tCmd_AddCommand (\"windows\", VID_Minimize);\n\n\tCmd_AddCommand (\"z_ext_list\", CL_Z_Ext_List_f);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_COMMUNICATION);\n\tCvar_Register(&cl_sayfilter_coloredtext);\n\tCvar_Register(&cl_sayfilter_sendboth);\n\tCvar_ResetCurrentGroup();\n}\n\n/*\n==============================================================================\nSERVER COMMANDS\n\nServer commands are commands stuffed by server into client's cbuf\nWe use a separate command buffer for them -- there are several\nreasons for that:\n1. So that partially stuffed commands are always executed properly\n2. Not to let players cheat in TF (v_cshift etc don't work in console)\n3. To hide some commands the user doesn't need to know about, like\nchanging, fullserverinfo, nextul, stopul\n==============================================================================\n*/\n\n//Just sent as a hint to the client that they should drop to full console\nvoid CL_Changing_f (void) {\n\tcl.intermission = 0;\n\n\t// don't change when downloading\n\tif (cls.download) {\n\t\t// we were on server\n\t\tif (cls.state == ca_active) {\n\t\t\t// drop to full console\n\t\t\t// not active anymore, but not disconnected\n\t\t\tcls.state = ca_connected;\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\t\t\tSys_Printf(\"\\nevent,active(changing)\\n\");\n#endif\n\n\t\t\tif (!com_serveractive) {\n\t\t\t\t// notice mapname not valid yet\n\t\t\t\tCvar_ForceSet(&host_mapname, \"\");\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\tS_StopAllSounds();\n\n\t// MVDs starting during map change can have /changing from the previous map\n\tif (!(cls.mvdplayback && cls.state == ca_onserver)) {\n\t\t// not active anymore, but not disconnected\n\t\tcls.state = ca_connected;\n\t}\n\n\tif (!com_serveractive) {\n\t\tCvar_ForceSet(&host_mapname, \"\"); // notice mapname not valid yet\n\t}\n\n\tCom_Printf (\"\\nChanging map...\\n\");\n}\n\n//Sent by server when serverinfo changes\nvoid CL_FullServerinfo_f (void) {\n\tchar *p;\n\n\tif (Cmd_Argc() != 2)\n\t\treturn;\n\n\tstrlcpy (cl.serverinfo, Cmd_Argv(1), sizeof(cl.serverinfo));\n\n\tp = Info_ValueForKey (cl.serverinfo, \"*cheats\");\n\tif (*p)\n\t\tCom_Printf (\"== Cheats are enabled ==\\n\");\n\n\tCL_ProcessServerInfo ();\n}\n\nvoid CL_R_DrawViewModel_f (void) {\n\textern cvar_t cl_filterdrawviewmodel;\n\n\tif (cl_filterdrawviewmodel.value)\n\t\treturn;\n\tCvar_Command ();\n}\n\ntypedef struct {\n\tchar\t*name;\n\tvoid\t(*func) (void);\n} svcmd_t;\n\nsvcmd_t svcmds[] = {\n\t{\"changing\", CL_Changing_f},\n\t{\"fullserverinfo\", CL_FullServerinfo_f},\n\t{\"nextul\", CL_NextUpload},\n\t{\"stopul\", CL_StopUpload},\n//\t{\"fov\", CL_Fov_f},\n\t{\"r_drawviewmodel\", CL_R_DrawViewModel_f},\n\t{\"fileul\", CL_StartFileUpload}, //bliP\n\t{NULL, NULL}\n};\n\n//Called by Cmd_ExecuteString if cbuf_current == &cbuf_svc\nqbool CL_CheckServerCommand (void) {\n\tsvcmd_t\t*cmd;\n\tchar *s;\n\n\ts = Cmd_Argv (0);\n\tfor (cmd = svcmds; cmd->name; cmd++) {\n\t\tif (!strcmp (s, cmd->name) ) {\n\t\t\tcmd->func();\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nusermainbuttons_t CL_GetLastCmd(int player_slot)\n{\n\tusercmd_t cmd = { 0 };\n\tusermainbuttons_t ret;\n\tstatic int last_impulse;\n\tstatic double impulse_time;\n\n\textern void MVD_FlushUserCommands (void);\n\n\tif (cls.mvdplayback && cl.mvd_time_offset) {\n\t\tmemset (&ret, 0, sizeof (ret));\n\n\t\t// Check for latest\n\t\tMVD_FlushUserCommands ();\n\n\t\tret.attack = (cl.mvd_user_cmd[0] & 1);\n\t\tret.jump = (cl.mvd_user_cmd[0] & 2);\n\t\tret.forward = (cl.mvd_user_cmd[0] & 4);\n\t\tret.back = (cl.mvd_user_cmd[0] & 8);\n\t\tret.right = (cl.mvd_user_cmd[0] & 16);\n\t\tret.left = (cl.mvd_user_cmd[0] & 32);\n\t\tret.up = (cl.mvd_user_cmd[0] & 64);\n\t\tret.down = (cl.mvd_user_cmd[0] & 128);\n\t\treturn ret;\n\t}\n\n\tif (player_slot >= 0 && player_slot < MAX_CLIENTS) {\n\t\tframe_t* frame = &cl.frames[cl.validsequence & UPDATE_MASK];\n\t\tif (frame->playerstate[player_slot].messagenum == cl.parsecount) {\n\t\t\tcmd = frame->playerstate[player_slot].command;\n\t\t}\n\t}\n\telse {\n\t\tcmd = cl.frames[(cls.netchan.outgoing_sequence - 1) & UPDATE_MASK].cmd;\n\t}\n\n\tret.attack = cmd.buttons & BUTTON_ATTACK;\n\tret.jump = cmd.buttons & BUTTON_JUMP;\n\tret.up = cmd.upmove > 0;\n\tret.down = cmd.upmove < 0;\n\tret.forward = cmd.forwardmove > 0;\n\tret.back = cmd.forwardmove < 0;\n\tret.left = cmd.sidemove < 0;\n\tret.right = cmd.sidemove > 0;\n\n\tif (cmd.impulse) {\n\t\tlast_impulse = cmd.impulse;\n\t\timpulse_time = cls.realtime;\n\t}\n\tif (!(last_impulse && cls.realtime >= impulse_time &&\n\t\tcls.realtime <= impulse_time + 0.2))\n\t\tlast_impulse = 0;\n\treturn ret;\n}\n"
  },
  {
    "path": "src/cl_demo.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\nCopyright (C) 2007-2015 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#include <time.h>\n#include \"quakedef.h\"\n#include \"movie.h\"\n#include \"menu_demo.h\"\n#include \"qtv.h\"\n#include \"gl_model.h\"\n#include \"tr_types.h\"\n#include \"teamplay.h\"\n#include \"pmove.h\"\n#include \"fs.h\"\n#include \"hash.h\"\n#include \"vfs.h\"\n#include \"utils.h\"\n#include \"crc.h\"\n#include \"logging.h\"\n#include \"version.h\"\n#include \"demo_controls.h\"\n#include \"mvd_utils.h\"\n#include \"r_trace.h\"\n#include \"sha3.h\"\n#ifndef CLIENTONLY\n#include \"server.h\"\n#endif\n#ifndef _WIN32\n#include <sys/types.h>\n#include <sys/wait.h>\n#endif\n\n/* FIXME Move these to a proper header file and included that */\nvoid Cam_Unlock(void);\n\n// TODO: Create states for demo_recording, demo_playback, and so on and put all related vars into these.\n// Right now with global vars for everything is a mess. Also renaming some of the time vars to be less\n// confusing is probably good. demotime, olddemotime, nextdemotime, prevtime...\n/*\ntypedef struct demo_state_s\n{\n\tfloat\t\tolddemotime;\n\tfloat\t\tnextdemotime;\n\n\tdouble\t\tbufferingtime;\n\n\tsizebuf_t\tdemocache;\n\tqbool\t\tdemocache_available;\n\n\tqbool\t\tqwz_unpacking;\n\tqbool\t\tqwz_playback;\n\tqbool\t\tqwz_packing;\n\tchar\t\ttempqwd_name[256];\n} demo_state_t;\n*/\ndouble olddemotime, nextdemotime; // TODO: Put in a demo struct.\n\ndouble bufferingtime; // if we stream from QTV, this is non zero when we trying fill our buffer\n\n// playback buffer\nvfsfile_t* playbackfile = NULL;           // The demo file used for playback.\nfloat demo_time_length = 0;               // The length of the demo.\n\nunsigned char stream_buffer[1024 * 256];  // Playback buffer (qtv only).\nint     stream_buffer_cnt = 0;            // How many bytes we've have in playback buffer.\nqbool   stream_buffer_eof = false;        // Have we reached the end of the playback buffer?\n\n//\n// Vars related to QIZMO compressed demos.\n// (Only available in Win32 since we need to use an external app)\n//\ntypedef enum {\n\tqizmo_not_running,\n\tqizmo_still_active,\n\tqizmo_terminated_ok,\n\tqizmo_terminated_unexpected,\n\tqizmo_terminated_failure\n} qizmo_status_t;\n\nstatic void CL_Demo_RemoveQWD(void);\nstatic void CL_Demo_GetCompressedName(char* cdemo_name);\nstatic void CL_Demo_RemoveCompressed(void);\nstatic void StopQWZPlayback(void);\nstatic void PlayQWZDemo(const char* name);\nstatic qbool CL_Demo_Compress(char* qwdname);\nstatic void CL_DemoStartPlayback(const char* name);\n\nstatic qbool\tqwz_unpacking = false;\nstatic qbool\tqwz_playback = false;\nstatic qbool\tqwz_packing = false;\n\n#define QWZ_DECOMPRESSION_TIMEOUT_MS\t10000\n\nstatic void\t\tOnChange_demo_format(cvar_t*, char*, qbool*);\ncvar_t\t\t\tdemo_format = {\"demo_format\", \"qwz\", 0, OnChange_demo_format};\n\nstatic char tempqwd_name[MAX_PATH] = { 0 }; // This file must be deleted after playback is finished.\nstatic char tempqwz_name[MAX_PATH] = { 0 };\n\nstatic vfsfile_t *CL_Open_Demo_File(const char *name, qbool searchpaks, char **fullpath);\nstatic void OnChange_demo_dir(cvar_t *var, char *string, qbool *cancel);\ncvar_t demo_dir = {\"demo_dir\", \"\", 0, OnChange_demo_dir};\ncvar_t demo_benchmarkdumps = {\"demo_benchmarkdumps\", \"1\"};\ncvar_t cl_startupdemo = {\"cl_startupdemo\", \"\"};\ncvar_t demo_jump_rewind = { \"demo_jump_rewind\", \"-10\" };\ncvar_t cl_demo_qwd_delta = { \"cl_demo_qwd_delta\", \"1\" };\ncvar_t demo_jump_skip_messages = { \"demo_jump_skip_messages\", \"1\" };\n\n// Used to save track status when rewinding.\nstatic vec3_t rewind_angle;\nstatic vec3_t rewind_pos;\nstatic double qtv_demospeed = 1;\nstatic int rewind_spec_track = 0;\n\nchar Demos_Get_Trackname(void);\nstatic void CL_DemoPlaybackInit(void);\nvoid CL_ProcessUserInfo(int slot, player_info_t *player, char *key);\n\nchar *CL_DemoDirectory(void);\nvoid CL_Demo_Jump_Status_Check (void);\n\n//=============================================================================\n//\t\t\t\t\t\t\t\tDEMO WRITING\n//=============================================================================\n\nstatic FILE *recordfile = NULL;\t\t// File used for recording demos. // TODO: Put in a demo struct.\nstatic float playback_recordtime;\t// Time when in demo playback and recording. // TODO: Put in a demo struct.\n\n#define DEMORECORDTIME\t((float) (cls.demoplayback ? playback_recordtime : cls.realtime))\n#define DEMOCACHE_MINSIZE\t(2 * 1024 * 1024)\n#define DEMOCACHE_FLUSHSIZE\t(1024 * 1024)\n\nstatic sizebuf_t democache; // TODO: Put in a demo struct.\nstatic qbool democache_available = false;\t// Has the user opted to use a demo cache? // TODO: Put in a demo struct.\n\n//\n// Opens a demo for writing.\n//\nstatic qbool CL_Demo_Open(char *name)\n{\n\t// Clear the demo cache and open the demo file for writing.\n\tif (democache_available)\n\t\tSZ_Clear(&democache);\n\trecordfile = fopen (name, \"wb\");\n\treturn recordfile ? true : false;\n}\n\n//\n// Closes a demo.\n//\nstatic void CL_Demo_Close(void)\n{\n\t// Flush the demo cache and close the demo file.\n\tif (democache_available)\n\t\tfwrite(democache.data, democache.cursize, 1, recordfile);\n\tfclose(recordfile);\n\trecordfile = NULL;\n}\n\n//\n// Writes a chunk of data to the currently opened demo record file.\n//\nstatic void CL_Demo_Write(const void *data, int size)\n{\n\tif (democache_available)\n\t{\n\t\t//\n\t\t// Write to the demo cache.\n\t\t//\n\t\tif (size > democache.maxsize)\n\t\t{\n\t\t\t// The size of the data to be written is bigger than the demo cache, fatal error.\n\t\t\tSys_Error(\"CL_Demo_Write: size > democache.maxsize\");\n\t\t}\n\t\telse if (size > democache.maxsize - democache.cursize)\n\t\t{\n\t\t\t//\n\t\t\t// Flushes part of the demo cache (enough to fit the new data) if it has been overflowed.\n\t\t\t//\n\t\t\tint overflow_size = size - (democache.maxsize - democache.cursize);\n\n\t\t\tCom_Printf(\"Warning: democache overflow...flushing\\n\");\n\n\t\t\tif (overflow_size <= DEMOCACHE_FLUSHSIZE)\n\t\t\t\toverflow_size = min(DEMOCACHE_FLUSHSIZE, democache.cursize);\n\n\t\t\t// Write as much data as overflowed from the current\n\t\t\t// contents of the demo cache to the demo file.\n\t\t\tfwrite(democache.data, overflow_size, 1, recordfile);\n\n\t\t\t// Shift the cache contents (remove what was just written).\n\t\t\tmemmove(democache.data, democache.data + overflow_size, democache.cursize - overflow_size);\n\t\t\tdemocache.cursize -= overflow_size;\n\n\t\t\t// Write the new data to the demo cache.\n\t\t\tSZ_Write(&democache, data, size);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Write to the demo cache.\n\t\t\tSZ_Write(&democache, data, size);\n\t\t}\n\t}\n\telse\n\t{\n\t\t//\n\t\t// Write directly to the file.\n\t\t//\n\t\tfwrite (data, size, 1, recordfile);\n\t}\n}\n\n//\n// Writes a user cmd to the open demo file.\n//\nvoid CL_WriteDemoCmd (usercmd_t *pcmd)\n{\n\tint i;\n\tfloat fl, t[3];\n\tbyte c;\n\tusercmd_t cmd;\n\n\t// Write the current time.\n\tfl = LittleFloat(DEMORECORDTIME);\n\tCL_Demo_Write(&fl, sizeof(fl));\n\n\t// Write the message type. A user cmd (movement and such).\n\tc = dem_cmd;\n\tCL_Demo_Write(&c, sizeof(c));\n\n\t// Correct for byte order, bytes don't matter.\n\tcmd = *pcmd;\n\n\t// Convert the movement angles / vectors to the correct byte order.\n\tfor (i = 0; i < 3; i++)\n\t\tcmd.angles[i] = LittleFloat(cmd.angles[i]);\n\tcmd.forwardmove = LittleShort(cmd.forwardmove);\n\tcmd.sidemove    = LittleShort(cmd.sidemove);\n\tcmd.upmove      = LittleShort(cmd.upmove);\n\n\t// Write the actual user command to the demo.\n\tCL_Demo_Write(&cmd, sizeof(cmd));\n\n\t// Write the view angles with the correct byte order.\n\tt[0] = LittleFloat (cl.viewangles[0]);\n\tt[1] = LittleFloat (cl.viewangles[1]);\n\tt[2] = LittleFloat (cl.viewangles[2]);\n\tCL_Demo_Write(t, sizeof(t));\n}\n\n//\n// Dumps the current net message, prefixed by the length and view angles\n//\nvoid CL_WriteDemoMessage (sizebuf_t *msg)\n{\n\tint len;\n\tfloat fl;\n\tbyte c;\n\n\t// Write time of cmd.\n\tfl = LittleFloat(DEMORECORDTIME);\n\tCL_Demo_Write(&fl, sizeof(fl));\n\n\t// Write the message type. Net message.\n\tc = dem_read;\n\tCL_Demo_Write(&c, sizeof(c));\n\n\t// Write the length of the data.\n\tlen = LittleLong (msg->cursize);\n\tCL_Demo_Write(&len, 4);\n\n\t// Write the data.\n\tCL_Demo_Write(msg->data, msg->cursize);\n\n\t// Request pings from the net chan if the user has set it.\n    {\n        extern void Request_Pings(void);\n        extern cvar_t demo_getpings;\n\n        if (demo_getpings.value)\n            Request_Pings();\n    }\n}\n\n\n//\n// Writes the entities list to a demo.\n//\nvoid CL_WriteDemoEntities (void)\n{\n\tint ent_index, ent_total;\n\tentity_state_t *ent;\n\t\n\t// Write the ID byte for a delta entity operation to the demo.\n\tMSG_WriteByte (&cls.demomessage, svc_packetentities);\n\n\t// Get the entities list from the frame.\n\tent = cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK].packet_entities.entities;\n\tent_total = cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK].packet_entities.num_entities;\n\n\t// Write all the entity changes since last packet entity message.\n\tfor (ent_index = 0; ent_index < ent_total; ent_index++, ent++) {\n\t\tMSG_WriteDeltaEntity(&cl_entities[ent->number].baseline, ent, &cls.demomessage, true, cls.fteprotocolextensions, cls.mvdprotocolextensions1);\n\t}\n\n\t// End of packetentities.\n\tMSG_WriteShort (&cls.demomessage, 0);\n}\n\n//\n// Writes a startup demo message to the demo being recorded.\n//\nstatic void CL_WriteStartupDemoMessage (sizebuf_t *msg, int seq)\n{\n\tint len, i;\n\tfloat fl;\n\tbyte c;\n\n\t// Don't write, we're not recording.\n\tif (!cls.demorecording)\n\t\treturn;\n\n\t// Write the time.\n\tfl = LittleFloat(DEMORECORDTIME);\n\tCL_Demo_Write(&fl, sizeof(fl));\n\n\t// Message type = net message.\n\tc = dem_read;\n\tCL_Demo_Write(&c, sizeof(c));\n\n\t// Write the length of the message.\n\tlen = LittleLong (msg->cursize + 8);\n\tCL_Demo_Write(&len, 4);\n\n\t// Write the sequence number twice. Why?\n\ti = LittleLong(seq);\n\tCL_Demo_Write(&i, 4);\n\tCL_Demo_Write(&i, 4);\n\n\t// Write the actual message data to the demo.\n\tCL_Demo_Write(msg->data, msg->cursize);\n}\n\n//\n// Writes a set demo message. The outgoing / incoming sequence at the start of the demo.\n// This is only written once at startup.\n//\nstatic void CL_WriteSetDemoMessage (void)\n{\n\tint len;\n\tfloat fl;\n\tbyte c;\n\n\t// We're not recording.\n\tif (!cls.demorecording)\n\t\treturn;\n\n\t// Write the demo time.\n\tfl = LittleFloat(DEMORECORDTIME);\n\tCL_Demo_Write(&fl, sizeof(fl));\n\n\t// Write the message type.\n\tc = dem_set;\n\tCL_Demo_Write(&c, sizeof(c));\n\n\t// Write the outgoing / incoming sequence.\n\tlen = LittleLong(cls.netchan.outgoing_sequence);\n\tCL_Demo_Write(&len, 4);\n\tlen = LittleLong(cls.netchan.incoming_sequence);\n\tCL_Demo_Write(&len, 4);\n}\n\n// FIXME: same as in sv_user.c. Move to common.c?\nstatic char *TrimModelName (const char *full)\n{\n\tstatic char shortn[MAX_QPATH];\n\tint len;\n\n\tif (!strncmp(full, \"progs/\", 6) && !strchr(full + 6, '/'))\n\t\tstrlcpy (shortn, full + 6, sizeof(shortn));\t\t// strip progs/\n\telse\n\t\tstrlcpy (shortn, full, sizeof(shortn));\n\n\tlen = strlen(shortn);\n\tif (len > 4 && !strcmp(shortn + len - 4, \".mdl\")\n\t\t&& strchr(shortn, '.') == shortn + len - 4)\n\t{\t// strip .mdl\n\t\tshortn[len - 4] = '\\0';\n\t}\n\n\treturn shortn;\n}\n\nvoid CL_WriteServerdata (sizebuf_t *msg)\n{\n\tint ignore_extensions;\n\n\tMSG_WriteByte (msg, svc_serverdata);\n\n\t// Maintain demo compatibility.\n\tignore_extensions = 0;\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\t// this is OK, since download data skipped anyway\n\tignore_extensions |= FTE_PEXT_CHUNKEDDOWNLOADS;\n#endif\n#ifdef FTE_PEXT_256PACKETENTITIES\n\t// this is probably OK, since engine should ignore more than 64 entities if not supported.\n\tignore_extensions |= FTE_PEXT_256PACKETENTITIES;\n#endif\n\n\t#ifdef PROTOCOL_VERSION_FTE\n\tif (cls.fteprotocolextensions &~ ignore_extensions)\n\t{\n\t\tMSG_WriteLong (msg, PROTOCOL_VERSION_FTE);\n\t\tMSG_WriteLong (msg, cls.fteprotocolextensions);\n\t}\n\t#endif // PROTOCOL_VERSION_FTE\n\n\t// Maintain demo pseudo-compatibility,\n\tignore_extensions = 0;\n\n\t#ifdef PROTOCOL_VERSION_FTE2\n\tif (cls.fteprotocolextensions2 & ~ignore_extensions)\n\t{\n\t\tMSG_WriteLong (msg, PROTOCOL_VERSION_FTE2);\n\t\tMSG_WriteLong (msg, cls.fteprotocolextensions2);\n\t}\n\t#endif // PROTOCOL_VERSION_FTE2\n\n\t// Maintain demo pseudo-compatibility,\n\tignore_extensions = 0;\n\n#ifdef PROTOCOL_VERSION_MVD1\n\tif (cls.mvdprotocolextensions1 & ~ignore_extensions)\n\t{\n\t\tMSG_WriteLong (msg, PROTOCOL_VERSION_MVD1);\n\t\tMSG_WriteLong (msg, cls.mvdprotocolextensions1);\n\t}\n#endif // PROTOCOL_VERSION_MVD1\n\n\tMSG_WriteLong (msg, PROTOCOL_VERSION);\n\tMSG_WriteLong (msg, cl.servercount);\n\tMSG_WriteString (msg, cls.gamedirfile);\n\n\t// Write if we're a spectator or not.\n\tif (cl.spectator)\n\t\tMSG_WriteByte (msg, cl.playernum | 128);\n\telse\n\t\tMSG_WriteByte (msg, cl.playernum);\n\n\t// Send full levelname.\n\tMSG_WriteString (msg, cl.levelname);\n\n\t// Send the movevars.\n\tMSG_WriteFloat(msg, movevars.gravity);\n\tMSG_WriteFloat(msg, movevars.stopspeed);\n\tMSG_WriteFloat(msg, cl.maxspeed);\n\tMSG_WriteFloat(msg, movevars.spectatormaxspeed);\n\tMSG_WriteFloat(msg, movevars.accelerate);\n\tMSG_WriteFloat(msg, movevars.airaccelerate);\n\tMSG_WriteFloat(msg, movevars.wateraccelerate);\n\tMSG_WriteFloat(msg, movevars.friction);\n\tMSG_WriteFloat(msg, movevars.waterfriction);\n\tMSG_WriteFloat(msg, cl.entgravity);\n}\n\n//\n// Write startup data to demo (called when demo started and cls.state == ca_active)\n//\nstatic void CL_WriteStartupData (void)\n{\n\tsizebuf_t buf;\n\tchar buf_data[MAX_MSGLEN * 2], *s;\n\tint n, i, j, seq = 1;\n\tentity_t *ent;\n\tentity_state_t *es, blankes;\n\tplayer_info_t *player;\n\n\t//\n\t// Serverdata\n\t// Send the info about the new client to all connected clients.\n\t//\n\n\t// Init a buffer that we write to before writing to the file.\n\tSZ_Init (&buf, (byte *) buf_data, sizeof(buf_data));\n\n\t//\n\t// Send the serverdata.\n\t//\n\tCL_WriteServerdata (&buf);\n\n\t// Send music.\n\tMSG_WriteByte (&buf, svc_cdtrack);\n\tMSG_WriteByte (&buf, 0); // None in demos\n\n\t// Send server info string.\n\tMSG_WriteByte (&buf, svc_stufftext);\n\tMSG_WriteString (&buf, va(\"fullserverinfo \\\"%s\\\"\\n\", cl.serverinfo));\n\n\t// Flush the buffer to the demo file and then clear it.\n\tCL_WriteStartupDemoMessage (&buf, seq++);\n\tSZ_Clear (&buf);\n\n\t//\n\t// Write the soundlist.\n\t//\n\tMSG_WriteByte (&buf, svc_soundlist);\n\tMSG_WriteByte (&buf, 0);\n\n\t// Loop through all sounds and write them to the demo.\n\tn = 0;\n\ts = cl.sound_name[n + 1];\n\twhile (*s)\n\t{\n\t\t// Write the sound name to the buffer.\n\t\tMSG_WriteString (&buf, s);\n\n\t\t// If the buffer is half full, flush it.\n\t\tif (buf.cursize > MAX_MSGLEN / 2)\n\t\t{\n\t\t\t// End of the partial sound list.\n\t\t\tMSG_WriteByte (&buf, 0);\n\n\t\t\t// Write how many sounds we've listed so far.\n\t\t\tMSG_WriteByte (&buf, n);\n\n\t\t\t// Flush the buffer to the demo file and clear the buffer.\n\t\t\tCL_WriteStartupDemoMessage (&buf, seq++);\n\t\t\tSZ_Clear (&buf);\n\n\t\t\t// Start on a new sound list and continue writing the\n\t\t\t// remaining sounds.\n\t\t\tMSG_WriteByte (&buf, svc_soundlist);\n\t\t\tMSG_WriteByte (&buf, n + 1);\n\t\t}\n\n\t\tn++;\n\t\ts = cl.sound_name[n+1];\n\t}\n\n\t// If the buffer isn't empty flush and clear it.\n\tif (buf.cursize)\n\t{\n\t\tMSG_WriteByte (&buf, 0);\n\t\tMSG_WriteByte (&buf, 0);\n\t\tCL_WriteStartupDemoMessage (&buf, seq++);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t// Vwep modellist\n\tif (cl.vwep_enabled && cl.vw_model_name[0][0]) \n\t{\n\t\t// Send VWep precaches\n\t\t// pray we don't overflow\n\t\tchar ss[1024] = \"//vwep \";\n\t\tfor (i = 0; i < MAX_VWEP_MODELS; i++) {\n\t\t\ts = cl.vw_model_name[i];\n\t\t\tif (!*s)\n\t\t\t\tbreak;\n\t\t\tif (i > 0)\n\t\t\t\tstrlcat (ss, \" \", sizeof(ss));\n\t\t\tstrlcat (ss, TrimModelName(s), sizeof(ss));\n\t\t}\n\t\tstrlcat (ss, \"\\n\", sizeof(ss));\n\t\tif (strlen(ss) < sizeof(ss)-1)\t\t// Didn't overflow?\n\t\t{\n\t\t\tMSG_WriteByte (&buf, svc_stufftext);\n\t\t\tMSG_WriteString (&buf, ss);\n\t\t}\n\t}\n\t// Don't bother flushing, the vwep list is not that large (I hope).\n\n\t//\n\t// Modellist.\n\t//\n\tMSG_WriteByte (&buf, svc_modellist);\n\tMSG_WriteByte (&buf, 0);\n\n\t// Loop through the models\n\tn = 0;\n\ts = cl.model_name[n + 1];\n\twhile (*s)\n\t{\n\t\t// Write the model name to the buffer.\n\t\tMSG_WriteString (&buf, s);\n\n\t\t// If the buffer is half full, flush it.\n\t\tif (buf.cursize\t> MAX_MSGLEN / 2)\n\t\t{\n\t\t\t// End of partial model list.\n\t\t\tMSG_WriteByte (&buf, 0);\n\n\t\t\t// Write how many models we've written so far.\n\t\t\tMSG_WriteByte (&buf, n);\n\n\t\t\t// Flush the model list to the demo file and clear the buffer.\n\t\t\tCL_WriteStartupDemoMessage (&buf, seq++);\n\t\t\tSZ_Clear (&buf);\n\n\t\t\t// Start on a new partial model list and continue.\n\t\t\tMSG_WriteByte (&buf, svc_modellist);\n\t\t\tMSG_WriteByte (&buf, n + 1);\n\t\t}\n\t\tn++;\n\t\ts = cl.model_name[n + 1];\n\t}\n\n\t// Flush the buffer if it's not empty.\n\tif (buf.cursize)\n\t{\n\t\tMSG_WriteByte (&buf, 0);\n\t\tMSG_WriteByte (&buf, 0);\n\t\tCL_WriteStartupDemoMessage (&buf, seq++);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t//\n\t// Write static entities.\n\t//\n\tfor (i = 0; i < cl.num_statics; i++)\n\t{\n\t\t// Get the next static entity.\n\t\tent = cl_static_entities + i;\n\n\t\t// Write ID for static entities.\n\t\tMSG_WriteByte (&buf, svc_spawnstatic);\n\n\t\t// Find if the model is precached or not.\n\t\tfor (j = 1; j < MAX_MODELS; j++)\n\t\t{\n\t\t\tif (ent->model == cl.model_precache[j])\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// Write if the model is precached.\n\t\tif (j == MAX_MODELS)\n\t\t\tMSG_WriteByte (&buf, 0);\n\t\telse\n\t\t\tMSG_WriteByte (&buf, j);\n\n\t\t// Write the entities frame and skin number.\n\t\tMSG_WriteByte (&buf, ent->frame);\n\t\tMSG_WriteByte (&buf, 0);\n\t\tMSG_WriteByte (&buf, ent->skinnum);\n\n\t\t// Write the coordinate and angles.\n\t\tfor (j = 0; j < 3; j++)\n\t\t{\n\t\t\tMSG_WriteCoord (&buf, ent->origin[j]);\n\t\t\tMSG_WriteAngle (&buf, ent->angles[j]);\n\t\t}\n\n\t\t// Flush the buffer if it's half full.\n\t\tif (buf.cursize > MAX_MSGLEN / 2)\n\t\t{\n\t\t\tCL_WriteStartupDemoMessage (&buf, seq++);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\t// spawnstaticsound\n\tfor (i = 0; i < cl.num_static_sounds; i++) \n\t{\n\t\tstatic_sound_t *ss = &cl.static_sounds[i];\n\t\tMSG_WriteByte (&buf, svc_spawnstaticsound);\n\t\tfor (j = 0; j < 3; j++)\n\t\t\tMSG_WriteCoord (&buf, ss->org[j]);\n\t\tMSG_WriteByte (&buf, ss->sound_num);\n\t\tMSG_WriteByte (&buf, ss->vol);\n\t\tMSG_WriteByte (&buf, ss->atten);\n\n\t\tif (buf.cursize > MAX_MSGLEN/2) \n\t\t{\n\t\t\tCL_WriteStartupDemoMessage (&buf, seq++);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\t//\n\t// Write entity baselines.\n\t//\n\tmemset(&blankes, 0, sizeof(blankes));\n\tfor (i = 0; i < CL_MAX_EDICTS; i++)\n\t{\n\t\tes = &cl_entities[i].baseline;\n\n\t\t//\n\t\t// If the entity state isn't blank write it to the buffer.\n\t\t//\n\t\tif (memcmp(es, &blankes, sizeof(blankes)))\n\t\t{\n\t\t\t// Write ID.\n\t\t\tMSG_WriteByte (&buf, svc_spawnbaseline);\n\t\t\tMSG_WriteShort (&buf, i);\n\n\t\t\t// Write model info.\n\t\t\tMSG_WriteByte (&buf, es->modelindex);\n\t\t\tMSG_WriteByte (&buf, es->frame);\n\t\t\tMSG_WriteByte (&buf, es->colormap);\n\t\t\tMSG_WriteByte (&buf, es->skinnum);\n\n\t\t\t// Write coordinates and angles.\n\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t{\n\t\t\t\tMSG_WriteCoord(&buf, es->origin[j]);\n\t\t\t\tMSG_WriteAngle(&buf, es->angles[j]);\n\t\t\t}\n\n\t\t\t// Flush to demo file if buffer is half full.\n\t\t\tif (buf.cursize > MAX_MSGLEN / 2)\n\t\t\t{\n\t\t\t\tCL_WriteStartupDemoMessage (&buf, seq++);\n\t\t\t\tSZ_Clear (&buf);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Write spawn information into the clients console buffer.\n\tMSG_WriteByte (&buf, svc_stufftext);\n\tMSG_WriteString (&buf, va(\"cmd spawn %i 0\\n\", cl.servercount));\n\n\t// Flush buffer to demo file.\n\tif (buf.cursize)\n\t{\n\t\tCL_WriteStartupDemoMessage (&buf, seq++);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t//\n\t// Send current status of all other players.\n\t//\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tplayer = cl.players + i;\n\n\t\t// Frags.\n\t\tMSG_WriteByte (&buf, svc_updatefrags);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteShort (&buf, player->frags);\n\n\t\t// Ping.\n\t\tMSG_WriteByte (&buf, svc_updateping);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteShort (&buf, player->ping);\n\n\t\t// Packet loss.\n\t\tMSG_WriteByte (&buf, svc_updatepl);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteByte (&buf, player->pl);\n\n\t\t// Entertime.\n\t\tMSG_WriteByte (&buf, svc_updateentertime);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteFloat (&buf, cls.realtime - player->entertime);\n\n\t\t// User ID and user info.\n\t\tMSG_WriteByte (&buf, svc_updateuserinfo);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteLong (&buf, player->userid);\n\t\tMSG_WriteString (&buf, player->userinfo);\n\n\t\t// Flush buffer to demo file.\n\t\tif (buf.cursize > MAX_MSGLEN / 2)\n\t\t{\n\t\t\tCL_WriteStartupDemoMessage (&buf, seq++);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\t// Send all current light styles.\n\tfor (i = 0; i < MAX_LIGHTSTYLES; i++)\n\t{\n\t\t// Don't send empty lightstyle strings.\n\t\tif (!cl_lightstyle[i].length)\n\t\t\tcontinue;\n\n\t\tMSG_WriteByte (&buf, svc_lightstyle);\n\t\tMSG_WriteByte (&buf, (char)i);\n\t\tMSG_WriteString (&buf, cl_lightstyle[i].map);\n\t}\n\n\tfor (i = 0; i < MAX_CL_STATS; i++)\n\t{\n\t\t// No need to send zero values.\n\t\tif (!cl.stats[i])\n\t\t\tcontinue;\n\n\t\t// Write the current players user stats.\n\t\tif (cl.stats[i] >= 0 && cl.stats[i] <= 255)\n\t\t{\n\t\t\tMSG_WriteByte (&buf, svc_updatestat);\n\t\t\tMSG_WriteByte (&buf, i);\n\t\t\tMSG_WriteByte (&buf, cl.stats[i]);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMSG_WriteByte (&buf, svc_updatestatlong);\n\t\t\tMSG_WriteByte (&buf, i);\n\t\t\tMSG_WriteLong (&buf, cl.stats[i]);\n\t\t}\n\n\t\t// Flush buffer to demo file.\n\t\tif (buf.cursize > MAX_MSGLEN / 2)\n\t\t{\n\t\t\tCL_WriteStartupDemoMessage (&buf, seq++);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\t// Get the client to check and download skins\n\t// when that is completed, a begin command will be issued.\n\tMSG_WriteByte (&buf, svc_stufftext);\n\tMSG_WriteString (&buf, va(\"skins\\n\"));\n\n\tCL_WriteStartupDemoMessage (&buf, seq++);\n\n\tCL_WriteSetDemoMessage();\n\n\tfor (i = 0; i < sizeof(cl.frames) / sizeof(cl.frames[0]); ++i) {\n\t\tcl.frames[i].in_qwd = false;\n\t}\n}\n\n//=========================================================\n// MVD demo writing\n//=========================================================\n\nstatic FILE *mvdrecordfile = NULL;\nstatic char mvddemoname[2 * MAX_OSPATH] = {0};\n\nstatic void CL_MVD_DemoWrite (void *data, int len)\n{\n\tif (!mvdrecordfile)\n\t\treturn;\n\n\tfwrite(data, len, 1, mvdrecordfile);\n}\n\n// ====================\n// CL_WriteRecordMVDMessage\n// ====================\nstatic void CL_WriteRecordMVDMessage (sizebuf_t *msg)\n{\n\tint len;\n\tbyte c;\n\n\tif (!cls.mvdrecording)\n\t\treturn;\n\n\tif (!msg->cursize)\n\t\treturn;\n\n\tc = 0;\n\tCL_MVD_DemoWrite (&c, sizeof(c));\n\n\tc = dem_all;\n\tCL_MVD_DemoWrite (&c, sizeof(c));\n\n\tlen = LittleLong (msg->cursize);\n\tCL_MVD_DemoWrite (&len, 4);\n\n\tCL_MVD_DemoWrite (msg->data, msg->cursize);\n}\n\n// ====================\n// CL_WriteRecordMVDStatsMessage\n// ====================\nstatic void CL_WriteRecordMVDStatsMessage (sizebuf_t *msg, int client)\n{\n\tint len;\n\tbyte c;\n\n\tif (!cls.mvdrecording)\n\t\treturn;\n\n\tif (!msg->cursize)\n\t\treturn;\n\n\tif (client < 0 || client >= MAX_CLIENTS)\n\t\treturn;\n\n\tc = 0;\n\tCL_MVD_DemoWrite (&c, sizeof(c));\n\n\tc = dem_stats | (client << 3) ; // msg \"type\" and \"to\" incapsulated in one byte\n\tCL_MVD_DemoWrite (&c, sizeof(c));\n\n\tlen = LittleLong (msg->cursize);\n\tCL_MVD_DemoWrite (&len, 4);\n\n\tCL_MVD_DemoWrite (msg->data, msg->cursize);\n}\n\n// TODO: Split this up?\nvoid CL_WriteMVDStartupData(void)\n{\n\tsizebuf_t\tbuf;\n\tunsigned char buf_data[MAX_MSGLEN * 4];\n\tplayer_info_t *player;\n\tentity_state_t *es, blankes;\n\tentity_t *ent;\n\tint i, j, n;\n\tchar *s;\n\n\t// Serverdata\n\t// send the info about the new client to all connected clients\n\tSZ_Init (&buf, buf_data, sizeof(buf_data));\n\n\t// send the serverdata\n\n\tMSG_WriteByte (&buf, svc_serverdata);\n\n\t#ifdef PROTOCOL_VERSION_FTE\n\tif (cls.fteprotocolextensions)\n\t{\n\t\tMSG_WriteLong (&buf, PROTOCOL_VERSION_FTE);\n\t\tMSG_WriteLong (&buf, cls.fteprotocolextensions);\n\t}\n\t#endif // PROTOCOL_VERSION_FTE\n\n\t#ifdef PROTOCOL_VERSION_FTE2\n\tif (cls.fteprotocolextensions2)\n\t{\n\t\tMSG_WriteLong (&buf, PROTOCOL_VERSION_FTE2);\n\t\tMSG_WriteLong (&buf, cls.fteprotocolextensions2);\n\t}\n\t#endif // PROTOCOL_VERSION_FTE2\n\n\tMSG_WriteLong (&buf, PROTOCOL_VERSION);\n\tMSG_WriteLong (&buf, cl.servercount);\n\n\t//\n\t// Gamedir.\n\t//\n\n\ts = cls.gamedirfile; // FIXME: or do we need to take a look in serverinfo?\n\tif (!s[0])\n\t\ts = \"qw\"; // If empty use \"qw\" gamedir\n\n\tMSG_WriteString (&buf, s);\n\n\tMSG_WriteFloat (&buf, cls.demotime); // FIXME: not sure\n\n\t// Send full levelname.\n\tMSG_WriteString (&buf, cl.levelname);\n\n\t// Send the movevars.\n\tMSG_WriteFloat(&buf, movevars.gravity);\n\tMSG_WriteFloat(&buf, movevars.stopspeed);\n\tMSG_WriteFloat(&buf, movevars.maxspeed);\n\tMSG_WriteFloat(&buf, movevars.spectatormaxspeed);\n\tMSG_WriteFloat(&buf, movevars.accelerate);\n\tMSG_WriteFloat(&buf, movevars.airaccelerate);\n\tMSG_WriteFloat(&buf, movevars.wateraccelerate);\n\tMSG_WriteFloat(&buf, movevars.friction);\n\tMSG_WriteFloat(&buf, movevars.waterfriction);\n\tMSG_WriteFloat(&buf, movevars.entgravity);\n\n\t// Send music.\n\tMSG_WriteByte (&buf, svc_cdtrack);\n\tMSG_WriteByte (&buf, 0); // None in demos.\n\n\t// Send server info string.\n\tMSG_WriteByte (&buf, svc_stufftext);\n\tMSG_WriteString (&buf, va(\"fullserverinfo \\\"%s\\\"\\n\", cl.serverinfo));\n\n\t// Flush packet.\n\tCL_WriteRecordMVDMessage (&buf);\n\tSZ_Clear (&buf);\n\n\t//\n\t// Soundlist.\n\t//\n\tMSG_WriteByte (&buf, svc_soundlist);\n\tMSG_WriteByte (&buf, 0);\n\n\tn = 0;\n\ts = cl.sound_name[n + 1];\n\twhile (*s)\n\t{\n\t\tMSG_WriteString (&buf, s);\n\t\tif (buf.cursize > MAX_MSGLEN/2)\n\t\t{\n\t\t\tMSG_WriteByte (&buf, 0);\n\t\t\tMSG_WriteByte (&buf, n);\n\t\t\tCL_WriteRecordMVDMessage (&buf);\n\t\t\tSZ_Clear (&buf);\n\t\t\tMSG_WriteByte (&buf, svc_soundlist);\n\t\t\tMSG_WriteByte (&buf, n + 1);\n\t\t}\n\t\tn++;\n\t\ts = cl.sound_name[n + 1];\n\t}\n\n\tif (buf.cursize)\n\t{\n\t\tMSG_WriteByte (&buf, 0);\n\t\tMSG_WriteByte (&buf, 0);\n\t\tCL_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t//\n\t// Modellist.\n\t//\n\tMSG_WriteByte (&buf, svc_modellist);\n\tMSG_WriteByte (&buf, 0);\n\n\tn = 0;\n\ts = cl.model_name[n+1];\n\twhile (*s)\n\t{\n\t\tMSG_WriteString (&buf, s);\n\t\tif (buf.cursize > MAX_MSGLEN/2)\n\t\t{\n\t\t\tMSG_WriteByte (&buf, 0);\n\t\t\tMSG_WriteByte (&buf, n);\n\t\t\tCL_WriteRecordMVDMessage (&buf);\n\t\t\tSZ_Clear (&buf);\n\t\t\tMSG_WriteByte (&buf, svc_modellist);\n\t\t\tMSG_WriteByte (&buf, n + 1);\n\t\t}\n\t\tn++;\n\t\ts = cl.model_name[n+1];\n\t}\n\n\tif (buf.cursize)\n\t{\n\t\tMSG_WriteByte (&buf, 0);\n\t\tMSG_WriteByte (&buf, 0);\n\t\tCL_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t//\n\t// Write static entities.\n\t//\n\tfor (i = 0; i < cl.num_statics; i++)\n\t{\n\t\t// Get the next static entity.\n\t\tent = cl_static_entities + i;\n\n\t\t// Write ID for static entities.\n\t\tMSG_WriteByte (&buf, svc_spawnstatic);\n\n\t\t// Find if the model is precached or not.\n\t\tfor (j = 1; j < MAX_MODELS; j++)\n\t\t{\n\t\t\tif (ent->model == cl.model_precache[j])\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// Write if the model is precached.\n\t\tif (j == MAX_MODELS)\n\t\t\tMSG_WriteByte (&buf, 0);\n\t\telse\n\t\t\tMSG_WriteByte (&buf, j);\n\n\t\t// Write the entities frame and skin number.\n\t\tMSG_WriteByte (&buf, ent->frame);\n\t\tMSG_WriteByte (&buf, 0);\n\t\tMSG_WriteByte (&buf, ent->skinnum);\n\n\t\t// Write the coordinate and angles.\n\t\tfor (j = 0; j < 3; j++)\n\t\t{\n\t\t\tMSG_WriteCoord (&buf, ent->origin[j]);\n\t\t\tMSG_WriteAngle (&buf, ent->angles[j]);\n\t\t}\n\n\t\t// Flush the buffer if it's half full.\n\t\tif (buf.cursize > MAX_MSGLEN / 2)\n\t\t{\n\t\t\tCL_WriteRecordMVDMessage (&buf);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\tif (buf.cursize)\n\t{\n\t\tCL_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t//\n\t// Write static sounds\n\t//\n\tfor (i = 0; i < cl.num_static_sounds; i++)\n\t{\n\t\tstatic_sound_t *ss = &cl.static_sounds[i];\n\n\t\tMSG_WriteByte (&buf, svc_spawnstaticsound);\n\n\t\tfor (j = 0; j < 3; j++)\n\t\t\tMSG_WriteCoord (&buf, ss->org[j]);\n\n\t\tMSG_WriteByte (&buf, ss->sound_num);\n\t\tMSG_WriteByte (&buf, ss->vol);\n\t\tMSG_WriteByte (&buf, ss->atten);\n\n\t\tif (buf.cursize > MAX_MSGLEN/2)\n\t\t{\n\t\t\tCL_WriteRecordMVDMessage (&buf);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\tif (buf.cursize)\n\t{\n\t\tCL_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t//\n\t// Write entity baselines.\n\t//\n\tmemset(&blankes, 0, sizeof(blankes));\n\n\tfor (i = 0; i < CL_MAX_EDICTS; i++)\n\t{\n\t\tes = &cl_entities[i].baseline;\n\n\t\t//\n\t\t// If the entity state isn't blank write it to the buffer.\n\t\t//\n\t\tif (memcmp(es, &blankes, sizeof(blankes)))\n\t\t{\n\t\t\t// Write ID.\n\t\t\tMSG_WriteByte (&buf, svc_spawnbaseline);\n\t\t\tMSG_WriteShort (&buf, i);\n\n\t\t\t// Write model info.\n\t\t\tMSG_WriteByte (&buf, es->modelindex);\n\t\t\tMSG_WriteByte (&buf, es->frame);\n\t\t\tMSG_WriteByte (&buf, es->colormap);\n\t\t\tMSG_WriteByte (&buf, es->skinnum);\n\n\t\t\t// Write coordinates and angles.\n\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t{\n\t\t\t\tMSG_WriteCoord(&buf, es->origin[j]);\n\t\t\t\tMSG_WriteAngle(&buf, es->angles[j]);\n\t\t\t}\n\n\t\t\t// Flush to demo file if buffer is half full.\n\t\t\tif (buf.cursize > MAX_MSGLEN / 2)\n\t\t\t{\n\t\t\t\tCL_WriteRecordMVDMessage (&buf);\n\t\t\t\tSZ_Clear (&buf);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (buf.cursize)\n\t{\n\t\tCL_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t//\n\t// Send all current light styles.\n\t//\n\tfor (i = 0; i < MAX_LIGHTSTYLES; i++)\n\t{\n\t\t// Don't send empty lightstyle strings.\n\t\tif (!cl_lightstyle[i].length)\n\t\t\tcontinue;\n\n\t\tMSG_WriteByte (&buf, svc_lightstyle);\n\t\tMSG_WriteByte (&buf, (char)i);\n\t\tMSG_WriteString (&buf, cl_lightstyle[i].map);\n\n\t\t// Flush to demo file if buffer is half full.\n\t\tif (buf.cursize > MAX_MSGLEN / 2)\n\t\t{\n\t\t\tCL_WriteRecordMVDMessage (&buf);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\tif (buf.cursize)\n\t{\n\t\tCL_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\tMSG_WriteByte (&buf, svc_stufftext);\n\tMSG_WriteString (&buf, va(\"cmd spawn %i 0\\n\", cl.servercount));\n\n\tif (buf.cursize)\n\t{\n\t\tCL_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t//\n\t// Send current status of all other players: frags, ping, pl, enter time, userinfo, player id.\n\t//\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tplayer = cl.players + i;\n\n\t\t// Do NOT ignore spectators here, since we need at least userinfo.\n\n\t\t// Frags.\n\t\tMSG_WriteByte (&buf, svc_updatefrags);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteShort (&buf, player->frags);\n\n\t\t// Ping.\n\t\tMSG_WriteByte (&buf, svc_updateping);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteShort (&buf, player->ping);\n\n\t\t// Packet loss.\n\t\tMSG_WriteByte (&buf, svc_updatepl);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteByte (&buf, player->pl);\n\n\t\t// Entertime.\n\t\tMSG_WriteByte (&buf, svc_updateentertime);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteFloat (&buf, cls.realtime - player->entertime);\n\n\t\t// User ID and user info.\n\t\tMSG_WriteByte (&buf, svc_updateuserinfo);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteLong (&buf, player->userid);\n\t\tMSG_WriteString (&buf, player->userinfo);\n\n\t\t// Flush buffer to demo file.\n\t\tif (buf.cursize > MAX_MSGLEN / 2)\n\t\t{\n\t\t\tCL_WriteRecordMVDMessage (&buf);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\tif (buf.cursize)\n\t{\n\t\tCL_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t//\n\t// This set proper model, origin, angles etc for players.\n\t//\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tvec3_t origin, angles;\n\t\tint j, flags;\n\t\tplayer_state_t *state;\n\n\t\tplayer = cl.players + i;\n\n\t\tstate = cl.frames[cl.validsequence & UPDATE_MASK].playerstate + i;\n\n\t\tif (!player->name[0])\n\t\t\tcontinue;\n\n\t\tif (player->spectator)\n\t\t\tcontinue; // Ignore spectators.\n\n\t\tflags =   (DF_ORIGIN << 0) | (DF_ORIGIN << 1) | (DF_ORIGIN << 2)\n\t\t\t\t| (DF_ANGLES << 0) | (DF_ANGLES << 1) | (DF_ANGLES << 2)\n\t\t\t\t| DF_EFFECTS | DF_SKINNUM \n\t\t\t\t| ((state->flags & PF_DEAD) ? DF_DEAD : 0)\n\t\t\t\t| ((state->flags & PF_GIB)  ? DF_GIB  : 0)\n\t\t\t\t| DF_WEAPONFRAME | DF_MODEL;\n\n\t\tVectorCopy(state->origin, origin);\n\t\tVectorCopy(state->viewangles, angles);\n\n\t\tMSG_WriteByte (&buf, svc_playerinfo);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteShort (&buf, flags);\n\n\t\tMSG_WriteByte (&buf, state->frame);\n\n\t\tfor (j = 0 ; j < 3 ; j++)\n\t\t\tif (flags & (DF_ORIGIN << j))\n\t\t\t\tMSG_WriteCoord (&buf, origin[j]);\n\n\t\tfor (j = 0 ; j < 3 ; j++)\n\t\t\tif (flags & (DF_ANGLES << j))\n\t\t\t\tMSG_WriteAngle16 (&buf, angles[j]);\n\n\t\tif (flags & DF_MODEL)\n\t\t\tMSG_WriteByte (&buf, state->modelindex);\n\n\t\tif (flags & DF_SKINNUM)\n\t\t\tMSG_WriteByte (&buf, state->skinnum);\n\n\t\tif (flags & DF_EFFECTS)\n\t\t\tMSG_WriteByte (&buf, state->effects);\n\n\t\tif (flags & DF_WEAPONFRAME)\n\t\t\tMSG_WriteByte (&buf, state->weaponframe);\n\n\t\tif (buf.cursize > MAX_MSGLEN/2)\n\t\t{\n\t\t\tCL_WriteRecordMVDMessage (&buf);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\t// we really need clear buffer before sending stats\n\tif (buf.cursize)\n\t{\n\t\tCL_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t// send stats\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tint\t\t*stats;\n\t\tint\t\tj;\n\n\t\tplayer = cl.players + i;\n\n\t\tif (!player->name[0])\n\t\t\tcontinue;\n\n\t\tif (player->spectator)\n\t\t\tcontinue; // Ignore spectators.\n\n\t\tstats = cl.players[i].stats;\n\n\t\tfor (j = 0; j < MAX_CL_STATS; j++)\n\t\t{\n\t\t\tif (stats[j] >= 0 && stats[j] <= 255)\n\t\t\t{\n\t\t\t\tMSG_WriteByte(&buf, svc_updatestat);\n\t\t\t\tMSG_WriteByte(&buf, j);\n\t\t\t\tMSG_WriteByte(&buf, stats[j]);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMSG_WriteByte(&buf, svc_updatestatlong);\n\t\t\t\tMSG_WriteByte(&buf, j);\n\t\t\t\tMSG_WriteLong(&buf, stats[j]);\n\t\t\t}\n\t\t}\n\n\t\tif (buf.cursize)\n\t\t{\n\t\t\tCL_WriteRecordMVDStatsMessage(&buf, i);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\t// Above stats writing must clear buffer.\n\tif (buf.cursize)\n\t{\n\t\tSys_Error(\"CL_WriteMVDStartupData: buf.cursize %d\", buf.cursize);\n\t}\n\n\t// \n\t// Send packetentities.\n\t//\n\t{\n\t\tint ent_index, ent_total;\n\t\tentity_state_t *ent_state;\n\n\t\t// Write the ID byte for a delta entity operation to the demo.\n\t\tMSG_WriteByte (&buf, svc_packetentities);\n\n\t\t// Get the entities list from the frame.\n\t\tent_state = cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK].packet_entities.entities;\n\t\tent_total = cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK].packet_entities.num_entities;\n\n\t\t// Write all the entity changes since last packet entity message.\n\t\tfor (ent_index = 0; ent_index < ent_total; ent_index++, ent_state++) {\n\t\t\tMSG_WriteDeltaEntity(&cl_entities[ent_state->number].baseline, ent_state, &buf, true, cls.fteprotocolextensions, cls.mvdprotocolextensions1);\n\t\t}\n\n\t\t// End of packetentities.\n\t\tMSG_WriteShort (&buf, 0);\n\t}\n\n\tif (buf.cursize)\n\t{\n\t\tCL_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t// Get the client to check and download skins\n\t// when that is completed, a begin command will be issued.\n\tMSG_WriteByte (&buf, svc_stufftext);\n\tMSG_WriteString (&buf, \"skins\\n\");\n\n\tCL_WriteRecordMVDMessage (&buf);\n}\n\n// ==============\n// Commands\n// ==============\n\nvoid CL_StopMvd_f(void)\n{\n\tif (!cls.mvdrecording && !mvdrecordfile)\n\t{\n\t\tCom_Printf (\"Not recording a demo\\n\");\n\t\treturn;\n\t}\n\n\tif (mvdrecordfile)\n\t{\n\t\tsizebuf_t\tbuf;\n\t\tunsigned char buf_data[MAX_MSGLEN];\n\n\t\tSZ_Init (&buf, buf_data, sizeof(buf_data));\n\n\t\t// Print offensive message.\n#ifdef EZ_MVD_SIGNOFF\n\t\tMSG_WriteByte(&buf, svc_print);\n\t\tMSG_WriteByte(&buf, 2);\n\t\tMSG_WriteString(&buf, EZ_MVD_SIGNOFF);\n#endif\n\n\t\t// Add disconnect.\n\t\tMSG_WriteByte (&buf, svc_disconnect);\n\t\tMSG_WriteString (&buf, \"EndOfDemo\");\n\n\t\tCL_WriteRecordMVDMessage (&buf);\n\n\t\tfclose(mvdrecordfile);\n\t\tmvdrecordfile = NULL;\n\n\t\tCom_Printf (\"Completed demo\\n\");\n\t}\n\telse\n\t{\n\t\tCom_Printf (\"BUG: Demo alredy completed or something\\n\");\n\t}\n\n\tcls.mvdrecording = false;\n}\n\nvoid CL_RecordMvd_f(void)\n{\n\tchar nameext[MAX_OSPATH * 2], name[MAX_OSPATH * 2];\n\n\tswitch(Cmd_Argc())\n\t{\n\t\tcase 1:\n\t\t//\n\t\t// Just show if anything is being recorded.\n\t\t//\n\t\t{\n\t\t\tif (cls.mvdrecording)\n\t\t\t\tCom_Printf(\"Recording to %s\\n\", mvddemoname);\n\t\t\telse\n\t\t\t\tCom_Printf(\"Not recording\\n\");\n\t\t\tbreak;\n\t\t}\n\t\tcase 2:\n\t\t//\n\t\t// Start recording to the specified demo name.\n\t\t//\n\t\t{\n\t\t\tif (cls.state != ca_active && cls.state != ca_disconnected)\n\t\t\t{\n\t\t\t\tCom_Printf (\"Cannot record whilst connecting\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Stop any recording in progress.\n\t\t\tif (cls.mvdrecording)\n\t\t\t\tCL_StopMvd_f();\n\n\t\t\t// Make sure the filename doesn't contain any invalid characters.\n\t\t\tif (!Util_Is_Valid_Filename(Cmd_Argv(1)))\n\t\t\t{\n\t\t\t\tCom_Printf(Util_Invalid_Filename_Msg(Cmd_Argv(1)));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Open the demo file for writing.\n\t\t\tstrlcpy(nameext, Cmd_Argv(1), sizeof(nameext));\n\t\t\tCOM_ForceExtensionEx (nameext, \".mvd\", sizeof (nameext));\n\n\t\t\t// Get the path for the demo and try opening the file for writing.\n\t\t\tsnprintf (name, sizeof(name), \"%s/%s\", CL_DemoDirectory(), nameext);\n\n\t\t\tmvdrecordfile = fopen(name, \"wb\");\n\n\t\t\tif (!mvdrecordfile)\n\t\t\t{\n\t\t\t\tCom_Printf (\"Error: Couldn't record to %s. Make sure path exists.\\n\", name);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Demo starting has begun.\n\t\t\tcls.mvdrecording = true;\n\n\t\t\t// Save the demoname for later use.\n\t\t\tstrlcpy(mvddemoname, name, sizeof(mvddemoname));\n\n\t\t\t// If we're active, write startup data right away.\n\t\t\tif (cls.state == ca_active)\n\t\t\t\tCL_WriteMVDStartupData();\n\n\t\t\tCom_Printf (\"Recording to %s\\n\", nameext);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t{\n\t\t\tCom_Printf(\"Usage: %s [demoname]\\n\", Cmd_Argv(0));\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n//=============================================================================\n//\t\t\t\t\t\t\t\tDEMO READING\n//=============================================================================\n\n//\n// Inits the demo playback buffer.\n// If we do QTV demo playback, we read data ahead while parsing QTV connection headers,\n// so we may have demo data in qtvrequestbuffer[], so we move data from qtvrequestbuffer[] to pb_buf[] with this function.\n//\nvoid CL_Demo_PB_Init(void *buf, int buflen)\n{\n\t// The length of the buffer is out of bounds.\n\tif (FSMMAP_IsMemoryMapped(playbackfile)) {\n\t\tstream_buffer_cnt = 0;\n\t\tstream_buffer_eof = false;\n\t}\n\telse {\n\t\tif (buflen < 0 || (size_t)buflen > sizeof(stream_buffer)) {\n\t\t\tSys_Error(\"CL_Demo_PB_Init: buflen out of bounds.\");\n\t\t}\n\n\t\t// Copy the specified init data into the playback buffer.\n\t\tmemcpy(stream_buffer, buf, buflen);\n\n\t\t// Reset any associated playback buffers.\n\t\tstream_buffer_cnt = buflen;\n\t\tstream_buffer_eof = false;\n\t}\n}\n\nstatic int CL_StreamRead(void* buf, int size, qbool peek)\n{\n\tint need = max(0, min(stream_buffer_cnt, size));\n\tmemcpy(buf, stream_buffer, need);\n\n\tif (!peek) {\n\t\t// We are not peeking, so move along buffer.\n\t\tstream_buffer_cnt -= need;\n\t\tmemmove(stream_buffer, stream_buffer + need, stream_buffer_cnt);\n\n\t\t// We get some data from playback file or qtv stream, dump it to file right now.\n\t\tif (need > 0 && cls.mvdplayback && cls.mvdrecording) {\n\t\t\tCL_MVD_DemoWrite(buf, need);\n\t\t}\n\t}\n\n\treturn need;\n}\n\n//\n// This is memory reading(not from file or socket), we just copy data from stream buffer to caller buffer,\n// if we're not peeking we decrease move data along buffer so next packet is always at position 0\n// If using a memory mapped file this is avoided and we just use standard READ/SEEK etc\n//\nint CL_Demo_Read(void *buf, int size, qbool peek)\n{\n\tint need;\n\n\t// Size can't be negative.\n\tif (size < 0) {\n\t\tHost_Error(\"pb_read: size < 0\");\n\t}\n\n\tif (FSMMAP_IsMemoryMapped(playbackfile)) {\n\t\tvfserrno_t err;\n\t\tunsigned long pos = VFS_TELL(playbackfile);\n\n\t\tneed = VFS_READ(playbackfile, buf, size, &err);\n\n\t\tif (peek) {\n\t\t\tVFS_SEEK(playbackfile, pos, SEEK_SET);\n\t\t}\n\t}\n\telse {\n\t\tneed = CL_StreamRead(buf, size, peek);\n\t}\n\n\tif (need != size) {\n\t\tHost_Error(\"Unexpected end of demo\");\n\t}\n\n\treturn need;\n}\n\n//\n// Reads a chunk of data from the playback file and returns the number of bytes read.\n//\nstatic int pb_raw_read(void *buf, int size)\n{\n\tif (playbackfile && FSMMAP_IsMemoryMapped(playbackfile)) {\n\t\treturn VFS_GETLEN(playbackfile) - (int)VFS_TELL(playbackfile); // FIXME: _TELL is unsigned long but _GETLEN is int?\n\t}\n\telse {\n\t\tvfserrno_t err;\n\t\tint r = VFS_READ(playbackfile, buf, size, &err);\n\n\t\t// Size > 0 mean detect EOF only if we actually trying read some data.\n\t\tstream_buffer_eof |= (size > 0 && !r && err == VFSERR_EOF);\n\n\t\treturn r;\n\t}\n}\n\n//\n// Ensure we have enough data to parse, it not then return false.\n// Function was introduced with QTV. If you read a demo from file and you run out of data\n// that's critical, but when streaming an MVD using QTV and don't receive enough data from \n// the network it isn't critical, we just freeze the client for some time while filling\n// the playback buffer.\n//\nqbool pb_ensure(void)\n{\n\t// Try to fill the entire buffer with demo data.\n\tif (FSMMAP_IsMemoryMapped(playbackfile)) {\n\t\treturn VFS_TELL(playbackfile) < VFS_GETLEN(playbackfile);\n\t}\n\telse {\n\t\t// Increase internal TCP buffer by faking a read to it.\n\t\tpb_raw_read(NULL, 0);\n\n\t\t// Show how much we've read.\n\t\tif (cl_shownet.value == 3) {\n\t\t\tCom_Printf(\" %d\", stream_buffer_cnt);\n\t\t}\n\n\t\tstream_buffer_cnt += pb_raw_read(stream_buffer + stream_buffer_cnt, max(0, (int)sizeof(stream_buffer) - stream_buffer_cnt));\n\n\t\tif (stream_buffer_cnt == (int)sizeof(stream_buffer) || stream_buffer_eof) {\n\t\t\treturn true; // Return true if we have full buffer or get EOF.\n\t\t}\n\n\t\t// Probably not enough data in buffer, check do we have at least one message in buffer.\n\t\tif (cls.mvdplayback && stream_buffer_cnt) {\n\t\t\t// Only care if there's a single packet...\n\t\t\tif (ConsistantMVDData((unsigned char*)stream_buffer, stream_buffer_cnt, 1)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set the buffering time if it hasn't been set already.\n\tif (cls.mvdplayback == QTV_PLAYBACK && !bufferingtime && !cls.qtv_donotbuffer)\n\t{\n\t\tdouble prebufferseconds = QTVPREBUFFERTIME;\n\n\t\tbufferingtime = Sys_DoubleTime() + prebufferseconds;\n\n\t\tif (developer.integer >= 2) {\n\t\t\tCom_DPrintf(\"&cF00\" \"qtv: not enough buffered, buffering for %.1fs\\n\" \"&r\", prebufferseconds); // print some annoying message\n\t\t}\n\t}\n\n\treturn false;\n}\n\n//\n// Peeks the demo time.\n//\nstatic double CL_PeekDemoTime(void)\n{\n\tdouble demotime = 0.0;\n\n\tif (cls.mvdplayback)\n\t{\n\t\tbyte mvd_time = 0; // Number of miliseconds since last frame. Can be between 0-255. MVD Only.\n\n\t\t// Peek inside, but don't read.\n\t\t// (Since it might not be time to continue reading in the demo\n\t\t// we want to be able to check this again later if that's the case).\n\t\tCL_Demo_Read(&mvd_time, sizeof(mvd_time), true);\n\n\t\t// Calculate the demo time.\n\t\t// (The time in an MVD is saved as a byte with number of miliseconds since the last cmd\n\t\t// so we need to multiply it by 0.001 to get it in seconds like normal quake time).\n\t\tdemotime = cls.demopackettime + (mvd_time * 0.001);\n\n\t\tif ((cls.demotime - nextdemotime) > 0.001 && (nextdemotime != demotime))\n\t\t{\n\t\t\tolddemotime = nextdemotime;\n\t\t\tcls.netchan.incoming_sequence++;\n\t\t\tcls.netchan.incoming_acknowledged++;\n\t\t\tcls.netchan.frame_latency = 0;\n\t\t\tcls.netchan.last_received = cls.demotime; // Make timeout check happy.\n\t\t\tnextdemotime = demotime;\n\t\t}\n\t}\n\telse \n\t{\n\t\tfloat floatTime;\n\n\t\t// Peek inside, but don't read.\n\t\t// (Since it might not be time to continue reading in the demo\n\t\t// we want to be able to check this again later if that's the case).\n\t\tCL_Demo_Read(&floatTime, sizeof(floatTime), true);\n\t\tdemotime = LittleFloat(floatTime);\n\n\t\tif (demotime < cls.demotime)\n\t\t{\n\t\t\tolddemotime = nextdemotime;\n\n\t\t\tnextdemotime = demotime;\n\t\t}\n\t}\n\n\treturn demotime;\n}\n\n//\n// Consume the demo time.\n//\nstatic void CL_ConsumeDemoTime(void)\n{\n\tif (cls.mvdplayback)\n\t{\n\t\tbyte dummy_newtime;\n\t\tCL_Demo_Read(&dummy_newtime, sizeof(dummy_newtime), false);\n\t}\n\telse\n\t{\n\t\tfloat dummy_demotime;\n\t\tCL_Demo_Read(&dummy_demotime, sizeof(dummy_demotime), false);\n\t}\n}\n\n//\n// Reads a dem_cmd message from an demo.\n//\nstatic void CL_DemoReadDemCmd(void)\n{\t\t\t\n\t// User cmd read.\n\textern int cmdtime_msec;\n\n\t// Get which frame we should read the cmd into from the demo.\n\tint i = cls.netchan.outgoing_sequence & UPDATE_MASK;\n\tint s = cls.netchan.outgoing_sequence & NETWORK_STATS_MASK;\n\tint j;\n\n\t// Read the user cmd from the demo.\n\tusercmd_t *pcmd = &cl.frames[i].cmd;\n\tCL_Demo_Read(pcmd, sizeof(*pcmd), false);\n\n\t// Convert the angles/movement vectors into the correct byte order.\n\tfor (j = 0; j < 3; j++)\n\t\tpcmd->angles[j] = LittleFloat(pcmd->angles[j]);\n\tpcmd->forwardmove = LittleShort(pcmd->forwardmove);\n\tpcmd->sidemove = LittleShort(pcmd->sidemove);\n\tpcmd->upmove = LittleShort(pcmd->upmove);\n\tcmdtime_msec += pcmd->msec;\n\n\t// Set the time time this cmd was sent and increase\n\t// how many net messages have been sent.\n\tcl.frames[i].senttime = cls.demopackettime;\n\tcl.frames[i].receivedtime = -1;\t\t// We haven't gotten a reply yet.\n\tcl.frames[i].delta_sequence = cl.delta_sequence;\n\tnetwork_stats[s].senttime = cls.realtime;\n\tnetwork_stats[s].sentsize = sizeof(*pcmd) + 12; // complete lie, compared to the original\n\tcls.netchan.outgoing_sequence++;\n\n\t// Read the viewangles from the demo and convert them to correct byte order.\n\tCL_Demo_Read(cl.viewangles, 12, false);\n\tfor (j = 0; j < 3; j++)\n\t\tcl.viewangles[j] = LittleFloat (cl.viewangles[j]);\n\n\t// Calculate the player fps based on the cmd.\n\tCL_CalcPlayerFPS(&cl.players[cl.playernum], pcmd->msec);\n\n\t// Try locking on to a player.\n\tif (cl.spectator)\n\t\tCam_TryLock();\n\n\t// Write the demo to the record file if we're recording.\n\tif (cls.demorecording)\n\t\tCL_WriteDemoCmd(pcmd);\n}\n\n//\n// Reads a dem_read message from a demo. Returns true if we should continue reading messages.\n//\nstatic qbool CL_DemoReadDemRead(void)\n{\n\t// Read the size of next net message in the demo file\n\t// and convert it into the correct byte order.\n\tCL_Demo_Read(&net_message.cursize, 4, false);\n\tnet_message.cursize = LittleLong(net_message.cursize);\n\n\t// The message was too big, stop playback.\n\tif (net_message.cursize > net_message.maxsize)\n\t{\n\t\tCom_DPrintf(\"CL_GetDemoMessage: net_message.cursize > net_message.maxsize\");\n\t\tHost_EndGame();\n\t\tHost_Abort();\n\t}\n\n\t// Read the net message from the demo.\n\tCL_Demo_Read(net_message.data, net_message.cursize, false);\n\n\t// Skip over any dem_multiple packets sent to no-one\n\tif (cls.mvdplayback && cls.lasttype == dem_multiple && cls.lastto == 0) {\n#ifdef MVD_PEXT1_HIDDEN_MESSAGES\n\t\t// Don't skip these if they're in parseable format\n\t\tif (cls.mvdprotocolextensions1 & MVD_PEXT1_HIDDEN_MESSAGES) {\n\t\t\treturn false;\n\t\t}\n#endif\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n//\n// Reads a dem_set message from a demo.\n//\nstatic void CL_DemoReadDemSet(void)\n{\n\tint i;\n\n\tCL_Demo_Read(&i, sizeof(i), false);\n\tcls.netchan.outgoing_sequence = LittleLong(i);\n\n\tCL_Demo_Read(&i, sizeof(i), false);\n\tcls.netchan.incoming_sequence = LittleLong(i);\n\n\tif (cls.mvdplayback)\n\t\tcls.netchan.incoming_acknowledged = cls.netchan.incoming_sequence;\n}\n\n//\n// Returns true if it's time to read the next message, false otherwise.\n//\nstatic qbool CL_DemoShouldWeReadNextMessage(double demotime)\n{\n\tif (cls.timedemo)\n\t{\n\t\t// Timedemo playback, grab the next message as quickly as possible.\n\n\t\tif (cls.td_lastframe < 0)\n\t\t{\n\t\t\t// This is the first frame of the timedemo.\n\t\t\tcls.td_lastframe = demotime;\n\t\t}\n\t\telse if (demotime > cls.td_lastframe)\n\t\t{\n\t\t\t// We've already read this frame's message so skip it.\n\t\t\tcls.td_lastframe = demotime;\n\t\t\treturn false;\n\t\t}\n\n\t\t// Did we just start the time demo?\n\t\tif (!cls.td_starttime && (cls.state == ca_active))\n\t\t{\n\t\t\t// Save the start time (real world time) and current frame number\n\t\t\t// so that we will know how long it took to go through it all\n\t\t\t// and calculate the framerate when it's done.\n\t\t\tcls.td_starttime = Sys_DoubleTime();\n\t\t\tcls.td_startframe = cls.framecount;\n\t\t\tR_TraceAPI(\"[timedemo] first-frame\");\n\t\t\tkey_dest = key_game;\n\t\t}\n\n\t\tif (cls.timedemo == TIMEDEMO_CLASSIC || (cls.state != ca_active)) {\n\t\t\t// Warp - will render as many frames as there are in the demo\n\t\t\tcl.gametime += (demotime - cls.demotime);\n\t\t\tcls.demotime = demotime;\n\t\t}\n\t\telse if (nextdemotime >= demotime) {\n\t\t\t// No warping, fixed fps\n\t\t\treturn true;\n\t\t}\n\t\telse {\n\t\t\treturn false;\n\t\t}\n\t}\n\telse if (!(cl.paused & PAUSED_SERVER) && (cls.state == ca_active)) // Always grab until fully connected.\n\t{\n\t\t// Not paused and active.\n\n\t\tif (cls.mvdplayback)\n\t\t{\n\t\t\tif (nextdemotime < demotime)\n\t\t\t{\n\t\t\t\treturn false; // Don't need another message yet.\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (cls.demotime < demotime)\n\t\t\t{\n\t\t\t\t// Don't need another message yet.\n\n\t\t\t\t// Adjust the demotime to match what's read from file.\n\t\t\t\tif (cls.demotime + 1.0 < demotime)\n\t\t\t\t\tcls.demotime = demotime - 1.0;\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tcls.demotime = demotime; // We're warping.\n\t}\n\n\treturn true;\n}\n\n//\n// When a demo is playing back, all NET_SendMessages are skipped, and NET_GetMessages are read from the demo file.\n// Whenever cl.time gets past the last received message, another message is read from the demo file.\n//\n// Summary:\n//\n// 1. MVD - Make sure we're not more than 1 second behind the next demo time.\n// 2. Get the time of the next demo message by peeking at it.\n// 3. Is it time to get the next demo message yet? (Always for timedemo) Return false if not.\n// 4. Read the time of the next message from the demo (consume it).\n// 5. MVD - Save the current time so we have it for the next frame\n//    (we need it to calculate the demo time. Time in MVDs is saved\n//     as the number of miliseconds since last frame).\n// 6. Read the message type from the demo, only the first 3 bits are significant.\n//    There are 3 basic message types used by all demos:\n//    dem_set = Only appears once at start of demo. Contains sequence numbers for the netchan.\n//    dem_cmd = A user movement cmd.\n//    dem_Read = An arbitrary network message.\n//\n//    MVDs also use:\n//    dem_multiple, dem_single, dem_stats and dem_all to direct certain messages to\n//    specific clients only, and to update stats.\n//\n// 7. Parse the specific demo messages.\n//\nqbool CL_GetDemoMessage (void)\n{\n\tdouble demotime;\n\tbyte c;\n\tbyte message_type;\n\n\t// Don't try to play while QWZ is being unpacked.\n\tif (qwz_unpacking) {\n\t\treturn false;\n\t}\n\n\t// Demo paused, don't read anything.\n\tif (cl.paused & PAUSED_DEMO) {\n\t\tpb_ensure();\t// Make sure we keep the QTV connection alive by reading from the socket.\n\t\treturn false;\n\t}\n\n\t// We're not ready to parse since we're buffering for QTV.\n\tif (bufferingtime && (bufferingtime > Sys_DoubleTime())) {\n\t\textern qbool\thost_skipframe;\n\n\t\tpb_ensure();\t\t\t// Fill the buffer for QTV.\n\t\thost_skipframe = true;\t// This will force not update cls.demotime.\n\n\t\treturn false;\n\t}\n\n\tbufferingtime = 0;\n\n\t// DEMO REWIND.\n\tif (!cls.mvdplayback || cls.mvdplayback != QTV_PLAYBACK) {\n\t\tCL_Demo_Check_For_Rewind(nextdemotime);\n\t}\n\n\t// Adjust the time for MVD playback.\n\tif (cls.mvdplayback)\n\t{\n\t\t// Reset the previous time.\n\t\tif (cls.demopackettime < nextdemotime) {\n\t\t\tcls.demopackettime = nextdemotime;\n\t\t}\n\n\t\t// Always be within one second from the next demo time.\n\t\tif (cls.demotime + 1.0 < nextdemotime) {\n\t\t\tcls.demotime = nextdemotime - 1.0;\n\t\t}\n\t}\n\n\t// Check if we need to get more messages for now and if so read it\n\t// from the demo file and pass it on to the net channel.\n\twhile (true)\n\t{\n\t\t// Make sure we have enough data in the buffer.\n\t\tif (!pb_ensure()) {\n \t\t\treturn false;\n\t\t}\n\n\t\t// Read the time of the next message in the demo.\n\t\tdemotime = CL_PeekDemoTime();\n\n\t\t// Keep gameclock up-to-date if we are seeking\n\t\tif (cls.demoseeking && demotime > cls.demopackettime) {\n\t\t\tcl.gametime += demotime - cls.demopackettime;\n\t\t}\n\n\t\t// Keep MVD features such as itemsclock up-to-date during seeking\n\t\tif (cls.demoseeking && cls.mvdplayback) {\n\t\t\tdouble tmp = cls.demotime;\n\t\t\tcls.demotime = demotime;\n\t\t\tMVD_Interpolate();\n\t\t\tMVD_Mainhook();\n\t\t\tcls.demotime = tmp;\n\t\t}\n\n\t\tif (cls.demoseeking == DST_SEEKING_STATUS) {\n\t\t\tCL_Demo_Jump_Status_Check();\n\t\t}\n\n\t\t// If we found demomark, we should stop seeking, so reset time to the proper value.\n\t\tif (cls.demoseeking == DST_SEEKING_FOUND || cls.demoseeking == DST_SEEKING_FOUND_NOREWIND) {\n\t\t\tcls.demotime = demotime; // this will trigger seeking stop\n\n\t\t\tif (cls.demoseeking == DST_SEEKING_FOUND_NOREWIND) {\n\t\t\t\t// Pause instead of rewinding\n\t\t\t\tCvar_SetValue(&cl_demospeed, 0.0f);\n\t\t\t}\n\t\t\telse if (demo_jump_rewind.value < 0) {\n\t\t\t\tCL_Demo_Jump(-demo_jump_rewind.value, -1, DST_SEEKING_NORMAL);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// If we've reached our seek goal, stop seeking.\n\t\tif (cls.demoseeking && cls.demotime <= demotime && cls.state >= ca_active)\n\t\t{\n\t\t\tcls.demoseeking = DST_SEEKING_NONE;\n\n\t\t\tif (cls.demorewinding)\n\t\t\t{\n\t\t\t\tCL_Demo_Stop_Rewinding();\n\t\t\t}\n\t\t}\n\n\t\tplayback_recordtime = demotime;\n\n\t\t// Decide if it is time to grab the next message from the demo yet.\n\t\tif (!CL_DemoShouldWeReadNextMessage(demotime)) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\t// Read the time from the packet (we peaked at it earlier),\n\t\t// we're ready to get the next message.\n\t\tCL_ConsumeDemoTime();\n\n\t\t// Save the previous time for MVD playback (for the next message),\n\t\t// it is needed to calculate the demotime since in mvd's the time is\n\t\t// saved as the number of miliseconds since last frame message.\n\t\t// This is also used when seeking in qwds to keep the gameclock in time.\n\t\tcls.demopackettime = demotime;\n\n\t\t// Get the msg type.\n\t\tCL_Demo_Read(&c, sizeof(c), false);\n\t\tmessage_type = (c & 7);\n\n\t\t// QWD Only.\n\t\tif (message_type == dem_cmd)\n\t\t{\n\t\t\tCL_DemoReadDemCmd();\n\t\t\t\n\t\t\tcontinue; // Get next message.\n\t\t}\n\n\t\t// MVD Only. These message types tells to which players the message is directed to.\n\t\tif ((message_type >= dem_multiple) && (message_type <= dem_all))\n\t\t{\n\t\t\tswitch (message_type)\n\t\t\t{\n\t\t\t\tcase dem_multiple:\n\t\t\t\t//\n\t\t\t\t// This message should be sent to more than one player, get which players.\n\t\t\t\t//\n\t\t\t\t{\n\t\t\t\t\t// Read the player bit mask from the demo and convert to the correct byte order.\n\t\t\t\t\t// Each bit in this number represents a player, 32-bits, 32 players.\n\t\t\t\t\tint i;\n\t\t\t\t\tCL_Demo_Read(&i, 4, false);\n\t\t\t\t\tcls.lastto = LittleLong(i);\n\t\t\t\t\tcls.lasttype = dem_multiple;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase dem_stats:\n\t\t\t\t//\n\t\t\t\t// The stats for a player has changed. Get the player number of that player.\n\t\t\t\t//\n\t\t\t\tcase dem_single:\n\t\t\t\t//\n\t\t\t\t// Only a single player should receive this message. Get the player number of that player.\n\t\t\t\t//\n\t\t\t\t{\n\t\t\t\t\t// The first 3 bits contain the message type (so remove that part), the rest contains\n\t\t\t\t\t// a 5-bit number which contains the player number of the affected player.\n\t\t\t\t\tcls.lastto = (c >> 3);\n\t\t\t\t\tcls.lasttype = message_type;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase dem_all:\n\t\t\t\t//\n\t\t\t\t// This message is directed to all players.\n\t\t\t\t//\n\t\t\t\t{\n\t\t\t\t\tcls.lastto = 0;\n\t\t\t\t\tcls.lasttype = dem_all;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tHost_Error(\"This can't happen (unknown command type) %c.\\n\", message_type);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fall through to dem_read after we've gotten the affected players.\n\t\t\tmessage_type = dem_read;\n\t\t}\n\n\t\t// Get the next net message from the demo file.\n\t\tif (message_type == dem_read)\n\t\t{\n\t\t\tif (CL_DemoReadDemRead())\n\t\t\t{\n\t\t\t\tcontinue; // Continue reading messages.\n\t\t\t}\n\t\t\t\n\t\t\treturn true; // We just read something.\n\t\t}\n\n\t\t// Gets the sequence numbers for the netchan at the start of the demo.\n\t\tif (message_type == dem_set)\n\t\t{\n\t\t\tCL_DemoReadDemSet();\n\t\t\t\n\t\t\tcontinue;\n\t\t}\n\n\t\t// We should never get this far if the demo is ok.\n\t\tHost_Error(\"Corrupted demo\");\n\t\treturn false;\n\t}\n}\n\n//=============================================================================\n//\t\t\t\t\t\t\t\tDEMO RECORDING\n//=============================================================================\n\nstatic char demoname[2 * MAX_OSPATH];\nstatic char fulldemoname[MAX_PATH];\nstatic qbool autorecording = false;\nstatic qbool easyrecording = false;\n\nvoid CL_AutoRecord_StopMatch(void);\nvoid CL_AutoRecord_CancelMatch(void);\n\nstatic void OnChange_demo_dir(cvar_t *var, char *string, qbool *cancel)\n{\n\tif (!string[0])\n\t\treturn;\n\n\t// Replace \\ with /.\n\tUtil_Process_FilenameEx(string, cl_mediaroot.integer == 2);\n\n\t// Make sure the filename doesn't have any invalid chars in it.\n\tif (!Util_Is_Valid_FilenameEx(string, cl_mediaroot.integer == 2))\n\t{\n\t\tCom_Printf(Util_Invalid_Filename_Msg(var->name));\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\t// Change to the new folder in the demo browser.\n\tif (cl_mediaroot.integer == 2) {\n\t\tMenu_Demo_NewHome(string);\n\t}\n\telse {\n\t\tchar buf[MAX_OSPATH];\n\n\t\tstrlcpy(buf, com_basedir, sizeof(buf));\n\t\tstrlcat(buf, \"/\", sizeof(buf));\n\t\tstrlcat(buf, string, sizeof(buf));\n\n\t\tMenu_Demo_NewHome(buf);\n\t}\n}\n\nstatic void OnChange_demo_format(cvar_t *var, char *string, qbool *cancel)\n{\n\tchar* allowed_formats[5] = { \"qwd\", \"qwz\", \"mvd\", \"mvd.gz\", \"qwd.gz\" };\n\tint i;\n\n\tfor (i = 0; i < 5; i++)\n\t\tif (!strcmp(allowed_formats[i], string))\n\t\t\treturn;\n\n\tCom_Printf(\"Not valid demo format. Allowed values are: \");\n\tfor (i = 0; i < 5; i++)\n\t{\n\t\tif (i)\n\t\t\tCom_Printf(\", \");\n\t\tCom_Printf(allowed_formats[i]);\n\t}\n\tCom_Printf(\".\\n\");\n\n\t*cancel = true;\n}\n\n//\n// Writes a \"pimp message\" for ezQuake at the end of a demo.\n//\nstatic void CL_WriteDemoPimpMessage(void)\n{\n\tif (cls.demoplayback) {\n\t\treturn;\n\t}\n\n\tSZ_Clear(&net_message);\n\tMSG_WriteLong(&net_message, cls.netchan.incoming_sequence + 1);\n\tMSG_WriteLong(&net_message, cls.netchan.incoming_acknowledged | (cls.netchan.incoming_reliable_acknowledged << 31));\n\tMSG_WriteByte(&net_message, svc_print);\n\tMSG_WriteByte(&net_message, PRINT_HIGH);\n\tMSG_WriteString(&net_message, EZ_QWD_SIGNOFF);\n\tCL_WriteDemoMessage(&net_message);\n}\n\n//\n// Stop recording a demo.\n//\nstatic void CL_StopRecording (void)\n{\n\t// Nothing to stop.\n\tif (!cls.demorecording)\n\t\treturn;\n\n\t// Write a pimp message to the demo.\n\tCL_WriteDemoPimpMessage();\n\n\t// Write a disconnect message to the demo file.\n\tSZ_Clear (&net_message);\n\tMSG_WriteLong (&net_message, -1);\t// -1 sequence means out of band\n\tMSG_WriteByte (&net_message, svc_disconnect);\n\tMSG_WriteString (&net_message, \"EndOfDemo\");\n\tCL_WriteDemoMessage (&net_message);\n\n\t// Finish up by closing the demo file.\n\tCL_Demo_Close();\n\tcls.demorecording = false;\n}\n\n//\n// Stop recording a demo.\n//\nvoid CL_Stop_f (void)\n{\n#ifndef CLIENTONLY\n\tif (com_serveractive && strcmp(Cmd_Argv(0), \"stop\") == 0)\n\t{\n\t\tSV_MVDStop_f();\n\t\treturn;\n\t}\n#endif\n\n\tif (cls.mvdplayback && cls.mvdrecording) {\n\t\tCL_StopMvd_f();\n\t\treturn;\n\t}\n\n\tif (!cls.demorecording)\n\t{\n\t\tCom_Printf (\"Not recording a demo\\n\");\n\t\treturn;\n\t}\n\tif (autorecording)\n\t{\n\t\tCL_AutoRecord_StopMatch();\n\t}\n\telse if (easyrecording)\n\t{\n\t\tCL_StopRecording();\n\t\tCL_Demo_Compress(fulldemoname);\n\t\teasyrecording = false;\n\t}\n\telse\n\t{\n\t\tCL_StopRecording();\n\t\tCom_Printf (\"Completed demo\\n\");\n\t}\n}\n\n//\n// Returns the Demo directory. If the user hasn't set the demo_dir var, the gamedir is returned.\n//\nextern char *CL_DemoDirectory(void)\n{\n\tstatic char dir[MAX_PATH];\n\n\tstrlcpy(dir, FS_LegacyDir(demo_dir.string), sizeof(dir));\n\treturn dir;\n}\n\n//\n// Start recording a demo.\n//\nvoid CL_Record_f (void)\n{\n\tchar nameext[MAX_OSPATH * 2], name[MAX_OSPATH * 2];\n\n#ifndef CLIENTONLY\n\tif (com_serveractive && strcmp(Cmd_Argv(0), \"record\") == 0)\n\t{\n\t\tSV_MVD_Record_f();\n\t\treturn;\n\t}\n#endif\n\n\tif (cls.mvdplayback) {\n\t\tCL_RecordMvd_f();\n\t\treturn;\n\t}\n\n#if defined(PROTOCOL_VERSION_FTE) || defined(PROTOCOL_VERSION_FTE2)\n\tif (cls.fteprotocolextensions &~ (FTE_PEXT_CHUNKEDDOWNLOADS|FTE_PEXT_256PACKETENTITIES))\n\t{\n\t\textern cvar_t cl_pext_warndemos;\n\n\t\tif (cl_pext_warndemos.value) {\n\t\t\tCom_Printf(\"WARNING: FTE protocol extensions enabled; this demo most likely will be unplayable in older clients. \"\n\t\t\t\t\"Use cl_pext 0 for 100%% compatible demos. But do NOT forget set it to 1 later or you will lack useful features!\\n\");\n\t\t}\n\t}\n#endif\n\n\tswitch(Cmd_Argc())\n\t{\n\t\tcase 1:\n\t\t//\n\t\t// Just show if anything is being recorded.\n\t\t//\n\t\t{\n\t\t\tif (autorecording)\n\t\t\t\tCom_Printf(\"Auto demo recording is in progress\\n\");\n\t\t\telse if (cls.demorecording)\n\t\t\t\tCom_Printf(\"Recording to %s\\n\", demoname);\n\t\t\telse\n\t\t\t\tCom_Printf(\"Not recording\\n\");\n\t\t\tbreak;\n\t\t}\n\t\tcase 2:\n\t\t//\n\t\t// Start recording to the specified demo name.\n\t\t//\n\t\t{\n\t\t\tif (cls.state != ca_active && cls.state != ca_disconnected)\n\t\t\t{\n\t\t\t\tCom_Printf (\"Cannot record whilst connecting\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (autorecording)\n\t\t\t{\n\t\t\t\tCom_Printf(\"Auto demo recording must be stopped first!\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Stop any recording in progress.\n\t\t\tif (cls.demorecording) {\n\t\t\t\tCL_Stop_f();\n\t\t\t}\n\n\t\t\t// Make sure the filename doesn't contain any invalid characters.\n\t\t\tif (!Util_Is_Valid_Filename(Cmd_Argv(1)))\n\t\t\t{\n\t\t\t\tCom_Printf(Util_Invalid_Filename_Msg(Cmd_Argv(1)));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Open the demo file for writing.\n\t\t\tstrlcpy(nameext, Cmd_Argv(1), sizeof(nameext));\n\t\t\tCOM_ForceExtensionEx (nameext, \".qwd\", sizeof (nameext));\n\n\t\t\t// Get the path for the demo and try opening the file for writing.\n\t\t\tsnprintf (name, sizeof(name), \"%s/%s\", CL_DemoDirectory(), nameext);\n\t\t\tif (!CL_Demo_Open(name))\n\t\t\t{\n\t\t\t\tCom_Printf (\"Error: Couldn't record to %s. Make sure path exists.\\n\", name);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Demo starting has begun.\n\t\t\tcls.demorecording = true;\n\n\t\t\t// If we're active, write startup data right away.\n\t\t\tif (cls.state == ca_active) {\n\t\t\t\tCL_WriteStartupData();\n\t\t\t}\n\n\t\t\t// Save the demoname for later use.\n\t\t\tstrlcpy(demoname, nameext, sizeof(demoname));\n\n\t\t\tCom_Printf (\"Recording to %s\\n\", nameext);\n\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t{\n\t\t\tCom_Printf(\"Usage: %s [demoname]\\n\", Cmd_Argv(0));\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n//\n// Starts recording a demo using autorecord or easyrecord.\n//\nstatic qbool CL_MatchRecordDemo(char *dir, char *name, qbool autorecord)\n{\n\tchar extendedname[MAX_PATH];\n\tchar strippedname[MAX_PATH];\n\tchar *fullname;\n\tchar *exts[] = {\"qwd\", \"qwz\", \"mvd\", NULL};\n\tint num;\n\n\tif (cls.state != ca_active)\n\t{\n\t\tCom_Printf (\"You must be connected before using easyrecord\\n\");\n\t\treturn false;\n\t}\n\n\tif (cls.mvdplayback)\n\t{\n\t\tCom_Printf (\"Cannot record during mvd playback\\n\");\n\t\treturn false;\n\t}\n\n\tif (autorecording)\n\t{\n\t\tCom_Printf(\"Auto demo recording must be stopped first!\\n\");\n\t\treturn false;\n\t}\n\n\t// Stop any old recordings.\n\tif (cls.demorecording)\n\t\tCL_Stop_f();\n\n\t// Make sure we don't have any invalid chars in the demo name.\n\tif (!Util_Is_Valid_Filename(name))\n\t{\n\t\tCom_Printf(Util_Invalid_Filename_Msg(name));\n\t\treturn false;\n\t}\n\n\t// We always record to qwd. If the user has set some other demo format\n\t// we convert to that later on.\n\tCOM_ForceExtension(name, \".qwd\");\n\n\tif (autorecord)\n\t{\n\t\t// Save the final demo name.\n\t\tstrlcpy (extendedname, name, sizeof(extendedname));\n\t}\n\telse\n\t{\n\t\t//\n\t\t// Easy recording, file is saved using match_* settings.\n\t\t//\n\n\t\t// Get rid of the extension again.\n\t\tCOM_StripExtension(name, strippedname, sizeof(strippedname));\n\t\tfullname = va(\"%s/%s\", dir, strippedname);\n\n\t\t// Find a unique filename in the specified dir.\n\t\tif ((num = Util_Extend_Filename(fullname, exts)) == -1)\n\t\t{\n\t\t\tCom_Printf(\"Error: no available filenames\\n\");\n\t\t\treturn false;\n\t\t}\n\n\t\t// Save the demo name..\n\t\tsnprintf (extendedname, sizeof(extendedname), \"%s_%03i.qwd\", strippedname, num);\n\t}\n\n\t// Get dir + final demo name.\n\tfullname = va(\"%s/%s\", dir, extendedname);\n\n\t// Open the demo file for writing.\n\tif (!CL_Demo_Open(fullname))\n\t{\n\t\t// Failed to open the file, make sure it exists and try again.\n\t\tFS_CreatePath(fullname);\n\t\tif (!CL_Demo_Open(fullname))\n\t\t{\n\t\t\tCom_Printf(\"Error: Couldn't open %s\\n\", fullname);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// Write the demo startup stuff.\n\tcls.demorecording = true;\n\tCL_WriteStartupData ();\n\n\t// Echo the name of the demo if we're easy recording\n\t// and save the demo name for later use.\n\tif (!autorecord)\n\t{\n\t\tCom_Printf (\"Recording to %s\\n\", extendedname);\n\t\tstrlcpy(demoname, extendedname, sizeof(demoname));\t\t// Just demo name.\n\t\tstrlcpy(fulldemoname, fullname, sizeof(fulldemoname));  // Demo name including path.\n\t}\n\n\treturn true;\n}\n\n//\n// Starts recording a demo and names it according to your match_ settings.\n//\nvoid CL_EasyRecord_f (void)\n{\n\tchar *name;\n\n#ifndef CLIENTONLY\n\tif ( com_serveractive )\n\t{\n\t\tSV_MVDEasyRecord_f();\n\t\treturn;\n\t}\n#endif\n\n\tif (cls.state != ca_active)\n\t{\n\t\tCom_Printf(\"You must be connected to easyrecord\\n\");\n\t\treturn;\n\t}\n\n\tswitch(Cmd_Argc())\n\t{\n\t\tcase 1:\n\t\t{\n\t\t\t// No name specified by the user, get it from match tools instead.\n\t\t\tname = MT_MatchName();\n\t\t\tbreak;\n\t\t}\n\t\tcase 2:\n\t\t{\n\t\t\t// User specified a demo name, use it.\n\t\t\tname = Cmd_Argv(1);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t{\n\t\t\tCom_Printf(\"Usage: %s [demoname]\\n\", Cmd_Argv(0));\n\t\t\treturn;\n\t\t}\n\t}\n\n\teasyrecording = CL_MatchRecordDemo(CL_DemoDirectory(), name, false);\n}\n\n//=============================================================================\n//\t\t\t\t\t\t\tDEMO AUTO RECORDING\n//=============================================================================\n\nstatic char\tauto_matchname[MAX_PATH];\t// Demoname when recording auto match demo.\nstatic qbool temp_demo_ready = false;\t// Indicates if the autorecorded match demo is done recording.\nstatic float auto_starttime;\n\nchar *MT_TempDemoDirectory(void);\n\nextern cvar_t match_auto_record, match_auto_minlength;\n\n#define TEMP_DEMO_NAME \"_!_temp_!_.qwd\"\n\n#define DEMO_MATCH_NORECORD\t\t0 // No autorecord.\n#define DEMO_MATCH_MANUALSAVE\t1 // Demo will be recorded but requires manuall saving.\n#define DEMO_MATCH_AUTOSAVE\t\t2 // Automatically saves the demo after the match is completed.\n\n//\n// Stops auto recording of a match.\n//\nvoid CL_AutoRecord_StopMatch(void)\n{\n\t// Not doing anything.\n\tif (!autorecording)\n\t\treturn;\n\n\t// Stop the recording and write end of demo stuff.\n\tautorecording = false;\n\tCL_StopRecording();\n\ttemp_demo_ready = true;\n\n\t// Automatically save the demo after the match is completed.\n\tif (match_auto_record.value == DEMO_MATCH_AUTOSAVE)\n\t{\n\t\tCL_AutoRecord_SaveMatch();\n\t\tCom_Printf (\"Auto record ok\\n\");\n\t}\n\telse\n\t{\n\t\tCom_Printf (\"Auto demo recording completed\\n\");\n\t}\n}\n\n//\n// Cancels the match.\n//\nvoid CL_AutoRecord_CancelMatch(void)\n{\n\t// Not recording.\n\tif (!autorecording)\n\t\treturn;\n\n\t// Stop the recording and write end of demo stuff.\n\tautorecording = false;\n\tCL_StopRecording();\n\ttemp_demo_ready = true;\n\n\tif (match_auto_record.value == DEMO_MATCH_AUTOSAVE)\n\t{\n\t\t// Only save the demo if it's longer than the specified minimum length\n\t\tif (cls.realtime - auto_starttime > match_auto_minlength.value)\n\t\t\tCL_AutoRecord_SaveMatch();\n\t\telse\n\t\t\tCom_Printf(\"Auto demo recording cancelled\\n\");\n\t}\n\telse\n\t{\n\t\tCom_Printf (\"Auto demo recording completed\\n\");\n\t}\n}\n\n//\n// Starts autorecording a match.\n//\nvoid CL_AutoRecord_StartMatch(char *demoname)\n{\n\ttemp_demo_ready = false;\n\n\t// No autorecording is set.\n\tif (!match_auto_record.value)\n\t\treturn;\n\n\t// Don't start autorecording if the\n\t// user already is recording a demo.\n\tif (cls.demorecording)\n\t{\n\t\t// We're autorecording since before, it's\n\t\t// ok to restart the recording then.\n\t\tif (autorecording)\n\t\t{\n\t\t\tautorecording = false;\n\t\t\tCL_StopRecording();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCom_Printf(\"Auto demo recording skipped (already recording)\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// Save the name of the auto recorded demo for later.\n\tstrlcpy(auto_matchname, demoname, sizeof(auto_matchname));\n\n\t// Try starting to record the demo.\n\tif (!CL_MatchRecordDemo(MT_TempDemoDirectory(), TEMP_DEMO_NAME, true))\n\t{\n\t\tCom_Printf (\"Auto demo recording failed to start!\\n\");\n\t\treturn;\n\t}\n\n\t// We're now in business.\n\tautorecording = true;\n\tauto_starttime = cls.realtime;\n\tCom_Printf (\"Auto demo recording commenced\\n\");\n}\n\n//\n//\n//\nqbool CL_AutoRecord_Status(void)\n{\n\treturn temp_demo_ready ? 2 : autorecording ? 1 : 0;\n}\n\n//\n// Saves an autorecorded demo.\n//\nvoid CL_AutoRecord_SaveMatch(void)\n{\n\t//\n\t// All demos are first recorded in .qwd, and will then be converted to .mvd/.qwz afterwards\n\t// if those formats are chosen.\n\t//\n\tint error, num;\n\tFILE *f;\n\tchar *dir, *tempname, savedname[MAX_PATH], *fullsavedname, *exts[] = {\"qwd\", \"qwz\", \"mvd\", NULL};\n\n\t// The auto recorded demo hasn't finished recording, can't do this yet.\n\tif (!temp_demo_ready)\n\t\treturn;\n\n\t// Don't try to save it again.\n\ttemp_demo_ready = false;\n\n\t// Get the demo dir.\n\tdir = CL_DemoDirectory();\n\n\t// Get the temp name of the file we've recorded.\n\ttempname = va(\"%s/%s\", MT_TempDemoDirectory(), TEMP_DEMO_NAME);\n\n\t// Get the final name where we'll save the final product.\n\tfullsavedname = va(\"%s/%s\", dir, auto_matchname);\n\n\t// Find a unique filename in the final location.\n\tif ((num = Util_Extend_Filename(fullsavedname, exts)) == -1)\n\t{\n\t\tCom_Printf(\"Error: no available filenames\\n\");\n\t\treturn;\n\t}\n\n\t// Get the final full path where we'll save the demo. (This is the final name for real now)\n\tsnprintf (savedname, sizeof(savedname), \"%s_%03i.qwd\", auto_matchname, num);\n\tfullsavedname = va(\"%s/%s\", dir, savedname);\n\n\t// Try opening the temp file to make sure we can read it.\n\tif (!(f = fopen(tempname, \"rb\")))\n\t\treturn;\n\tfclose(f);\n\n\t// Move the temp file to the final location.\n\tif ((error = rename(tempname, fullsavedname)))\n\t{\n\t\t// Failed to move, make sure the path exists and try again.\n\t\tFS_CreatePath(fullsavedname);\n\t\terror = rename(tempname, fullsavedname);\n\t}\n\n\t// If the file type is not QWD we need to conver it using external apps.\n\tif (!strcmp(demo_format.string, \"qwz\") || !strcmp(demo_format.string, \"mvd\")) {\n\t\tCom_Printf(\"Converting QWD to %s format.\\n\", demo_format.string);\n\n\t\t// Convert the file to either MVD or QWZ.\n\t\tif (CL_Demo_Compress(fullsavedname)) {\n\t\t\treturn;\n\t\t}\n\n\t\tqwz_packing = false;\n\t}\n\n\tif (!error) {\n\t\tCom_Printf(\"Match demo saved to %s\\n\", savedname);\n\t}\n}\n\n//=============================================================================\n//\t\t\t\t\t\t\tQIZMO COMPRESSION\n//=============================================================================\n\n#ifdef _WIN32\n#define QIZMO_EXECUTABLE_NAME       \"qizmo.exe\"\n#define QWDTOOLS_EXECUTABLE_NAME    \"qwdtools.exe\"\nstatic HANDLE hQizmoProcess = NULL;\nstatic HANDLE hQizmoThread = NULL;\n\nqbool Sys_QizmoRunning(void)\n{\n\treturn hQizmoProcess != NULL;\n}\n\nqbool Sys_LaunchExternalDemoProcess(const char* cmdline, const char* path)\n{\n\tSTARTUPINFO si;\n\tPROCESS_INFORMATION\tpi;\n\tchar cmdline_[MAX_OSPATH];\n\tDWORD dwCreationFlags = GetPriorityClass(GetCurrentProcess());\n\n\tstrlcpy(cmdline_, cmdline, sizeof(cmdline_));\n\n\tmemset(&si, 0, sizeof(si));\n\tsi.cb = sizeof(si);\n\tsi.wShowWindow = SW_SHOWMINNOACTIVE;\n\tsi.dwFlags = STARTF_USESHOWWINDOW;\n\n\tif (!CreateProcess(NULL, cmdline_, NULL, NULL, FALSE, dwCreationFlags, NULL, path, &si, &pi)) {\n\t\treturn false;\n\t}\n\n\t// Might not actually be Qizmo (could be demotools)...\n\thQizmoProcess = pi.hProcess;\n\thQizmoThread = pi.hThread;\n\n\treturn true;\n}\n\nqizmo_status_t Sys_QizmoStatus(void)\n{\n\tDWORD ExitCode;\n\tif (hQizmoProcess == NULL) {\n\t\treturn qizmo_not_running;\n\t}\n\n\tif (!GetExitCodeProcess(hQizmoProcess, &ExitCode)) {\n\t\thQizmoProcess = NULL;\n\t\treturn qizmo_terminated_unexpected;\n\t}\n\n\tif (ExitCode == STILL_ACTIVE) {\n\t\treturn qizmo_still_active;\n\t}\n\n\tCloseHandle(hQizmoThread);\n\tCloseHandle(hQizmoProcess);\n\thQizmoThread = NULL;\n\thQizmoProcess = NULL;\n\n\tif (ExitCode == 0) {\n\t\treturn qizmo_terminated_ok;\n\t}\n\n\treturn qizmo_terminated_failure;\n}\n#elif defined(__linux__)\n#define QIZMO_EXECUTABLE_NAME       \"qizmo\"\n#define QWDTOOLS_EXECUTABLE_NAME    \"qwdtools\"\n\nstatic pid_t qizmo_process_id = 0;\n\nqbool Sys_QizmoRunning(void)\n{\n\treturn qizmo_process_id != 0;\n}\n\nstatic qbool Sys_LaunchExternalDemoProcess(const char* cmdline, const char* path)\n{\n\tint child;\n\n\tchild = fork();\n\tif (child == -1) {\n\t\tCon_Printf(\"Failed to create sub-process\\n\");\n\t\treturn false;\n\t}\n\n\tif (child == 0) {\n\t\t// TODO: can we redirect stdout for the system() call?\n\n\t\t// child process: change to qizmo dir so it can find compress.dat\n\t\tif (chdir(path) != 0) {\n\t\t\t_exit(EXIT_FAILURE);\n\t\t}\n\n\t\t// execute & terminate with success/failure\n\t\tif (system(cmdline) == 0) {\n\t\t\t_exit(EXIT_SUCCESS);\n\t\t}\n\t\t_exit(EXIT_FAILURE);\n\t}\n\n\t// parent process, store and check back later\n\tqizmo_process_id = child;\n\treturn true;\n}\n\nqizmo_status_t Sys_QizmoStatus(void)\n{\n\tint wait_result;\n\tint status = 0;\n\n\tif (qizmo_process_id == 0) {\n\t\treturn qizmo_not_running;\n\t}\n\n\twait_result = waitpid(qizmo_process_id, &status, WNOHANG);\n\tif (wait_result == 0) {\n\t\t// child exists but hasn't changed state\n\t\treturn qizmo_still_active;\n\t}\n\telse if (wait_result < 0) {\n\t\t// child no longer exists...\n\t\tqizmo_process_id = 0;\n\t\treturn qizmo_terminated_unexpected;\n\t}\n\telse {\n\t\t// status has changed\n\t\tif (WIFEXITED(status)) {\n\t\t\tqizmo_process_id = 0;\n\t\t\treturn WEXITSTATUS(status) == 0 ? qizmo_terminated_ok : qizmo_terminated_failure;\n\t\t}\n\t\telse {\n\t\t\tCon_Printf(\"Unknown status: %d\\n\", status);\n\t\t\treturn qizmo_still_active;\n\t\t}\n\t}\n}\n#else\n// Other operating systems - not supported yet\n#define QIZMO_EXECUTABLE_NAME       \"qizmo\"\n#define QWDTOOLS_EXECUTABLE_NAME    \"qwdtools\"\n\nqbool Sys_QizmoRunning(void)\n{\n\treturn false;\n}\n\nstatic qbool Sys_LaunchExternalDemoProcess(const char* cmdline, const char* path)\n{\n\tCon_Printf(\"Not supported on this system.\");\n\treturn false;\n}\n\nqizmo_status_t Sys_QizmoStatus(void)\n{\n\treturn qizmo_not_running;\n}\n#endif\n\nstatic qbool CL_CompressExternally(const char* qwdname)\n{\n\tchar cmdline[MAX_OSPATH];\n\tchar workingDirectory[MAX_OSPATH];\n\textern cvar_t qizmo_dir, qwdtools_dir;\n\tconst char* appname = NULL;\n\tconst char* shortname = NULL;\n\tconst char* parameters = NULL;\n\tconst char* path = NULL;\n\tchar outputpath[MAX_OSPATH];\n\n\tif (!strcmp(demo_format.string, \"qwz\")) {\n\t\tappname = QIZMO_EXECUTABLE_NAME;\n\t\tshortname = \"qizmo\";\n\t\tparameters = \"-q -C\";\n\t\tpath = qizmo_dir.string;\n\t\toutputpath[0] = 0;\n\t}\n\telse if (!strcmp(demo_format.string, \"mvd\")) {\n\t\tappname = QWDTOOLS_EXECUTABLE_NAME;\n\t\tshortname = \"qwdtools\";\n\t\tparameters = \"-c -o * -od\";\n\t\tpath = qwdtools_dir.string;\n\t\tstrlcpy(outputpath, qwdname, COM_SkipPath(qwdname) - qwdname);\n\t}\n\telse {\n\t\tCom_Printf(\"%s demo format not yet supported.\\n\", demo_format.string);\n\t\treturn false;\n\t}\n\n\tstrlcpy(workingDirectory, va(\"%s/%s\", com_basedir, path), sizeof(workingDirectory));\n\tstrlcpy(cmdline, va(\"\\\"%s/%s/%s\\\" %s \\\"%s\\\" \\\"%s\\\"\", com_basedir, path, appname, parameters, outputpath, qwdname), sizeof(cmdline));\n\tCom_Printf(\"&cf00%s&r: %s\\n\", shortname, cmdline);\n\n\treturn Sys_LaunchExternalDemoProcess(cmdline, workingDirectory);\n}\n\n// Qizmo only\nstatic qbool CL_DecompressExternally(const char* qwz_name)\n{\n\tchar cmdline[MAX_OSPATH];\n\tchar workingDirectory[MAX_OSPATH];\n\textern cvar_t qizmo_dir;\n\n\tstrlcpy(workingDirectory, va(\"%s/%s\", com_basedir, qizmo_dir.string), sizeof(workingDirectory));\n\tstrlcpy(cmdline, va(\"%s/%s/%s -q -u -3 -D \\\"%s\\\"\", com_basedir, qizmo_dir.string, QIZMO_EXECUTABLE_NAME, qwz_name), sizeof(cmdline));\n\n\tCom_Printf(\"&cf00qizmo&r: %s\\n\", cmdline);\n\n\treturn Sys_LaunchExternalDemoProcess(cmdline, workingDirectory);\n}\n\n//\n//\n//\nstatic void CL_Demo_RemoveQWD(void)\n{\n\tSys_remove(tempqwd_name);\n}\n\n//\n// cdemo_name is assumed to be 255 chars long\n//\nstatic void CL_Demo_GetCompressedName(char* cdemo_name)\n{\n\tsize_t namelen = strlen(tempqwd_name);\n\n\tif (strlen(demo_format.string) && namelen) {\n\t\tstrlcpy(cdemo_name, tempqwd_name, 255);\n\t\tstrlcpy(cdemo_name + namelen - 3, demo_format.string, 255 - namelen + 3);\n\t}\n}\n\n//\n//\n//\nstatic void CL_Demo_RemoveCompressed(void)\n{\n\tchar cdemo_name[255];\n\tCL_Demo_GetCompressedName(cdemo_name);\n\tSys_remove(cdemo_name);\n}\n\n//\n//\n//\nstatic void StopQWZPlayback (void)\n{\n\tif (!Sys_QizmoRunning() && tempqwd_name[0]) {\n\t\tif (Sys_remove(tempqwd_name) != 0) {\n\t\t\tCom_Printf(\"Error: Couldn't delete %s\\n\", tempqwd_name);\n\t\t}\n\t\ttempqwd_name[0] = 0;\n\t}\n\tqwz_playback = false;\n\tqwz_unpacking = false;\n}\n\n//\n//\n//\nvoid CL_CheckQizmoCompletion(void)\n{\n\tqizmo_status_t state = Sys_QizmoStatus();\n\n\tif (state == qizmo_not_running) {\n\t\treturn;\n\t}\n\n\tif (state == qizmo_terminated_unexpected) {\n\t\tCom_Printf (\"WARNING: CL_CheckQizmoCompletion: unexpected termination\\n\");\n\t\tif (qwz_unpacking) {\n\t\t\tqwz_unpacking = false;\n\t\t\tqwz_playback = false;\n\t\t\tcls.demoplayback = cls.timedemo = false;\n\t\t\tStopQWZPlayback();\n\t\t}\n\t\telse if (qwz_packing) {\n\t\t\tqwz_packing = false;\n\t\t\tCL_Demo_RemoveCompressed();\n\t\t}\n\t\treturn;\n\t}\n\n\tif (state == qizmo_still_active) {\n\t\treturn;\n\t}\n\n\tif (!qwz_packing && !qwz_unpacking) {\n\t\tStopQWZPlayback();\n\t\treturn;\n\t}\n\n\tif (qwz_unpacking) {\n\t\tbyte* data = NULL;\n\t\tint length = 0;\n\n\t\tqwz_unpacking = false;\n\n\t\tSys_remove(tempqwz_name);\n\t\ttempqwz_name[0] = 0;\n\n\t\tdata = FS_LoadHeapFile(tempqwd_name, &length);\n\t\tif (data == NULL) {\n\t\t\tCom_Printf(\"Error: Couldn't open %s\\n\", tempqwd_name);\n\t\t\tqwz_playback = false;\n\t\t\tcls.demoplayback = cls.timedemo = false;\n\t\t\ttempqwd_name[0] = 0;\n\t\t\treturn;\n\t\t}\n\n\t\tplaybackfile = FSMMAP_OpenVFS(data, length);\n\t\tCom_Printf(\"Decompression complete...\\n\");\n\n\t\tCL_DemoStartPlayback(tempqwd_name);\n\n\t\tSys_remove(tempqwd_name);\n\t\ttempqwd_name[0] = 0;\n\t\tqwz_playback = false;\n\t}\n\telse if (qwz_packing) {\n\t\tFILE* tempfile;\n\t\tchar newname[255];\n\n\t\tCL_Demo_GetCompressedName(newname);\n\t\tqwz_packing = false;\n\n\t\tif ((tempfile = fopen(newname, \"rb\")) && (FS_FileLength(tempfile) > 0) && fclose(tempfile) != EOF) {\n\t\t\tCom_Printf(\"Demo saved to %s\\n\", newname + strlen(com_basedir));\n\t\t\tCL_Demo_RemoveQWD();\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"Compression failed, demo saved as QWD.\\n\");\n\t\t}\n\t}\n}\n\n//\n//\n//\nstatic void PlayQWZDemo(const char* name)\n{\n\tchar qwz_name[MAX_PATH], *p;\n\n\tif (Sys_QizmoRunning()) {\n\t\tCom_Printf (\"Cannot unpack -- Qizmo still running!\\n\");\n\t\treturn;\n\t}\n\n\t//\n\tchar* initialName = NULL;\n\tmemset(tempqwz_name, 0, sizeof(tempqwz_name));\n\tif (playbackfile) {\n\t\tCOM_WriteToUniqueTempFileVFS(qwz_name, sizeof(qwz_name), \".qwz\", playbackfile);\n\n\t\t// Store the temporary path here so we can delete it when decompression is finished\n\t\tstrlcpy(tempqwz_name, qwz_name, sizeof(tempqwz_name));\n\t}\n\telse {\n\t\t// Try opening direct from disk\n\t\tif (!(playbackfile = CL_Open_Demo_File(name, false, &initialName))) {\n\t\t\tCom_Printf(\"Error: Couldn't open %s\\n\", name);\n\t\t\treturn;\n\t\t}\n\n\t\t// Convert to system path\n\t\tSys_fullpath(qwz_name, initialName, sizeof(qwz_name));\n\t}\n\n\tVFS_CLOSE(playbackfile);\n\tplaybackfile = NULL;\n\n\tstrlcpy(tempqwd_name, qwz_name, sizeof(tempqwd_name) - 4);\n\n\t// the way Qizmo does it, sigh\n\tif (!(p = strstr(tempqwd_name, \".qwz\"))) {\n\t\tp = strstr(tempqwd_name, \".QWZ\");\n\t}\n\tif (!p) {\n\t\tp = tempqwd_name + strlen(tempqwd_name);\n\t}\n\tstrcpy(p, \".qwd\");\n\n\t// If .qwd already exists, just play it.\n\tif ((playbackfile = FS_OpenVFS(tempqwd_name, \"rb\", FS_NONE_OS))) {\n\t\treturn;\n\t}\n\n\tCom_Printf(\"Unpacking %s...\\n\", COM_SkipPath(name));\n\n\t// Start Qizmo to unpack the demo.\n\tif (!CL_DecompressExternally(qwz_name)) {\n\t\treturn;\n\t}\n\n\tqwz_unpacking = true;\n\tqwz_playback = true;\n}\n\n//\n//\n//\nstatic qbool CL_Demo_Compress(char* qwdname)\n{\n\tif (Sys_QizmoRunning()) {\n\t\tCom_Printf(\"Cannot compress -- Qizmo still running!\\n\");\n\t\treturn false;\n\t}\n\n\tif (!CL_CompressExternally(qwdname)) {\n\t\tCom_Printf(\"Failed to compress %s\\n\", qwdname);\n\t\treturn false;\n\t}\n\n\tstrlcpy(tempqwd_name, qwdname, sizeof(tempqwd_name));\n\tqwz_packing = true;\n\treturn true;\n}\n\n//=============================================================================\n//\t\t\t\t\t\t\tDEMO PLAYBACK\n//=============================================================================\n\ndouble\t\tdemostarttime;\n\n#ifdef WITH_ZIP\n//\n// [IN]\t\tplay_path = The compressed demo file that needs to be extracted to play it.\n// [RETURN]\tThe open file handle, or NULL on failure.\n//\nstatic qbool CL_UnpackAndOpenDemo(char *play_path, char* unpacked_path, int unpacked_path_length)\n{\n\t//\n\t// Check if the demo is in a zip file and if so, try to extract it before playing.\n\t//\n\tchar archive_path[MAX_PATH];\n\tchar inzip_path[MAX_PATH];\n\n\t//\n\t// Is it a GZip file?\n\t//\n\tif (!strcmp(COM_FileExtension(play_path), \"gz\"))\n\t{\n\t\t// Unpack to memory and open file\n\t\tsize_t unpacked_length;\n\t\tvoid* unpacked_bytes = FS_GZipUnpackToMemory(play_path, &unpacked_length);\n\n\t\tif (unpacked_bytes) {\n\t\t\tplaybackfile = FSMMAP_OpenVFS(unpacked_bytes, unpacked_length);\n\t\t\tCOM_StripExtension(play_path, unpacked_path, unpacked_path_length);\n\t\t\t// If it doesn't have an extension, default to .mvd\n\t\t\tif (!COM_FileExtension(unpacked_path)[0]) {\n\t\t\t\tCOM_ForceExtensionEx(unpacked_path, \".mvd\", unpacked_path_length);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t//\n\t// Check if the path is in the format \"c:\\quake\\bla\\demo.zip\\some_demo.mvd\" and split it up.\n\t//\n\tif (FS_ZipBreakupArchivePath(\"zip\", play_path, archive_path, sizeof(archive_path), inzip_path, sizeof(inzip_path)) < 0) {\n\t\treturn false;\n\t}\n\n\t//\n\t// Extract the ZIP file.\n\t//\n\t{\n\t\tvoid* unpacked_bytes = NULL;\n\t\tsize_t unpacked_length = 0;\n\t\t// Open the zip file.\n\t\tunzFile zip_file = FS_ZipUnpackOpenFile(archive_path);\n\n\t\t// Try extracting the zip file.\n\t\tunpacked_bytes = FS_ZipUnpackOneFileToMemory(zip_file, inzip_path, false, false, NULL, &unpacked_length);\n\n\t\tstrlcpy(unpacked_path, COM_SkipPath(inzip_path), unpacked_path_length);\n\n\t\t// Close the zip file.\n\t\tFS_ZipUnpackCloseFile(zip_file);\n\n\t\tif (unpacked_bytes != NULL) {\n\t\t\tplaybackfile = FSMMAP_OpenVFS(unpacked_bytes, unpacked_length);\n\n\t\t\t// Successfully unpacked the demo.\n\t\t\treturn true;\n\t\t}\n\n\t\tCom_Printf(\"Failed to unpack the demo file \\\"%s\\\" to memory\\n\", inzip_path);\n\t\treturn false;\n\t}\n}\n#endif // WITH_ZIP\n\nvoid CL_Demo_DumpBenchmarkResult(int frames, float timet)\n{\n\tchar logfile[MAX_PATH];\n\tchar datebuf[32];\n\tFILE* f;\n\ttime_t t = time(&t);\n\tstruct tm *ptm = localtime(&t);\n\tint width = 0, height = 0; \n\n\tsnprintf(logfile, sizeof(logfile), \"%s/timedemo.log\", FS_LegacyDir(log_dir.string));\n\tf = fopen(logfile, \"a\");\n\tif (!f) {\n\t\tCom_Printf(\"Can't open %s to dump timedemo result\\n\", logfile);\n\t\treturn;\n\t}\n\n\tfputs(\"<timedemo date=\\\"\", f);\n\tif (ptm)\n\t\tstrftime (datebuf, sizeof(datebuf) - 1, \"%Y-%m-%dT%H:%M:%S\", ptm);\n\telse\n\t\t*datebuf = '\\0';\n\tfputs(datebuf, f); fputs(\"\\\">\\n\", f);\n\n\tfputs(va(\"\\t<system>\\n\\t\\t<os>%s</os>\\n\\t\\t<hardware>%s</hardware>\\n\\t</system>\\n\", QW_PLATFORM, SYSINFO_GetString()), f);\n\n\tfputs(va(\"\\t<client>\\n\\t\\t<name>ezQuake</name><version>%s</version>\\n\"\n\t\t\"\\t\\t<configuration>%s</configuration><rendering>%s</rendering>\\n\\t</client>\\n\",\n\t\tVersionString(), QW_CONFIGURATION, QW_RENDERER), f);\n//FIXME width/height doesnt get set, remove vid_mode/r_mode... Is this function used??\n\tif (width)\n\t\tfputs(va(\"\\t<screen width=\\\"%d\\\" height=\\\"%d\\\"/>\\n\", width, height), f);\n\n\tfputs(va(\"\\t<demo><name>%s</name></demo>\\n\", cls.demoname), f);\n\n\tfputs(va(\"\\t<result frames=\\\"%i\\\" time=\\\"PT%fS\\\" fps=\\\"%f\\\"/>\\n\", frames, timet, frames/timet), f);\n\t\n\tfputs(\"</timedemo>\\n\", f);\n\n\tfclose(f);\n}\n\n//\n// Stops demo playback.\n//\nvoid CL_StopPlayback(void)\n{\n\t// Nothing to stop.\n\tif (!cls.demoplayback) {\n\t\treturn;\n\t}\n\n\t// Capturing to avi/images, stop that.\n\tif (Movie_IsCapturing()) {\n\t\tMovie_Stop(false);\n\t}\n\n\t// Close the playback file.\n\tif (playbackfile) {\n\t\tVFS_CLOSE(playbackfile);\n\t}\n\n\t// Reset demo playback vars.\n\tplaybackfile = NULL;\n\tcls.mvdplayback = cls.demoplayback = cls.nqdemoplayback = false;\n\tcl.paused &= ~PAUSED_DEMO;\n\n\tcls.qtv_svversion = 0;\n\tcls.qtv_ezquake_ext = 0;\n\tcls.qtv_donotbuffer = false;\n\n\t// Stop Qizmo demo playback.\n\t#ifdef WIN32\n\tif (qwz_playback) {\n\t\tStopQWZPlayback();\n\t}\n\t#endif\n\n\t//\n\t// Stop timedemo and show the result.\n\t//\n\tif (cls.timedemo)\n\t{\n\t\tint frames, i, frames2, worst_ms_count = 0;\n\t\tdouble stddev = 0;\n\t\tdouble renderTime = 0;\n\t\tfloat time, worst_ms = 0;\n\t\tdouble avg_ms = 0;\n\n\t\t//\n\t\t// Calculate the time it took to render the frames.\n\t\t//\n\t\tR_TraceAPI(\"[timedemo] finished\");\n\t\tframes = cls.framecount - cls.td_startframe - 1;\n\t\ttime = Sys_DoubleTime() - cls.td_starttime;\n\t\tif (time <= 0) {\n\t\t\ttime = 1;\n\t\t}\n\t\tif (frames <= 0) {\n\t\t\tframes = 1;\n\t\t}\n\t\tavg_ms = (time * 1000.0) / frames;\n\t\tCom_Printf(\"%i frames %5.1f seconds %5.1f fps\\n\", frames, time, frames / time);\n\t\tif (cls.timedemo == TIMEDEMO_FIXEDFPS && cls.td_frametime > 0) {\n\t\t\tCom_Printf(\"  simulated @ %5.1f fps\\n\", 1.0 / cls.td_frametime);\n\t\t}\n\t\tif (cls.td_frametime_max) {\n\t\t\tCom_Printf(\"Worst frametime %dms @ frame %d\\n\", cls.td_frametime_max, cls.td_frametime_max_frame);\n\t\t}\n\n\t\tframes2 = 0;\n\t\tfor (i = 0; i < sizeof(cls.td_frametime_stats) / sizeof(cls.td_frametime_stats[0]); ++i) {\n\t\t\tif (cls.td_frametime_stats[i]) {\n\t\t\t\tstddev += cls.td_frametime_stats[i] * (i / 10.0 - avg_ms) * (i / 10.0 - avg_ms);\n\t\t\t\tworst_ms = i / 10.0;\n\t\t\t\tworst_ms_count = cls.td_frametime_stats[i];\n\t\t\t\tframes2 += cls.td_frametime_stats[i];\n\t\t\t\trenderTime += cls.td_frametime_stats[i] * (i / 10000.0);\n\t\t\t}\n\t\t}\n\t\tstddev = sqrt(stddev / frames2);\n\t\tCom_Printf(\"... avg frametime %0.3fms, std dev %0.3fms\\n\", avg_ms, stddev);\n\t\tCom_Printf(\"... %d frames vs %d\\n\", frames2, frames);\n\t\tCom_Printf(\"... %5.1fs vs %5.1fs\\n\", renderTime, time);\n\t\tCom_Printf(\"... non-rendering time %5.1fs\\n\", cls.td_nonrendering);\n\t\tif (worst_ms) {\n\t\t\tCom_Printf(\"... worst frametime %0.3fms, %0.1ffps (%d frames)\\n\", worst_ms, 1 / (worst_ms / 1000.0f), worst_ms_count);\n\t\t}\n\t\t// Create bins\n\t\t{\n#define TIMEDEMO_BIN_COUNT 10\n#define TIMEDEMO_BIN_OFFSET (TIMEDEMO_BIN_COUNT - 4)\n\t\t\tdouble bin_percentages[TIMEDEMO_BIN_COUNT] = { 0.0 };\n\t\t\tunsigned long bin_thresholds[TIMEDEMO_BIN_COUNT] = { 0 };\n\t\t\tdouble total_samples = 0;\n\t\t\tunsigned long bin_ms[TIMEDEMO_BIN_COUNT] = { 0 };\n\t\t\tint j;\n\n\t\t\tfor (i = 0; i < TIMEDEMO_BIN_COUNT; ++i) {\n\t\t\t\tbin_percentages[i] = 1.0 - pow(2, TIMEDEMO_BIN_OFFSET - i) * 0.01;\n\t\t\t\tbin_thresholds[i] = bin_percentages[i] * frames2;\n\t\t\t}\n\t\t\tbin_percentages[TIMEDEMO_BIN_COUNT - 1] = 1.0;\n\n\t\t\tfor (i = 0; i < sizeof(cls.td_frametime_stats) / sizeof(cls.td_frametime_stats[0]); ++i) {\n\t\t\t\ttotal_samples += cls.td_frametime_stats[i];\n\n\t\t\t\tfor (j = 0; j < sizeof(bin_percentages) / sizeof(bin_percentages[0]); ++j) {\n\t\t\t\t\tif (total_samples <= bin_thresholds[j]) {\n\t\t\t\t\t\tbin_ms[j] = i;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbin_ms[TIMEDEMO_BIN_COUNT - 1] = worst_ms * 10.0;\n\n\t\t\tfor (j = 0; j < TIMEDEMO_BIN_COUNT; ++j) {\n\t\t\t\tCon_Printf(\"  %6.3f%: %4.1fms\\n\", bin_percentages[j], (bin_ms[j] / 10.0));\n\t\t\t}\n\t\t}\n\t\tcls.timedemo = false;\n\t\tif (demo_benchmarkdumps.integer) {\n\t\t\tCL_Demo_DumpBenchmarkResult(frames, time);\n\t\t}\n\t}\n\n\t// Go to the next demo in the demo playlist.\n\tCL_Demo_NextInPlaylist();\n\n\t// Reset demoseeking and such.\n\tcls.demoseeking = DST_SEEKING_NONE;\n\tcls.demorewinding = false;\n\n\tTP_ExecTrigger(\"f_demoend\");\n}\n\n//\n// Returns the demo length.\n//\nfloat CL_GetDemoLength(void)\n{\n\treturn demo_time_length;\n}\n\ntypedef enum demoprobe_parse_type_e\n{\n\tREAD_MVD_TIME\t= 1,\n\tREAD_QWD_TIME\t= 2,\n\tTRY_READ_MVD\t= 3\n} demoprobe_parse_type_t;\n\n//\n// Validates a demo probe read.\n//\n#define DEMOPROBE_VALIDATE_READ(bytes_expected, bytes_read, message)\t\t\\\n\tif (bytes_expected != bytes_read)\t\t\t\t\t\t\t\t\t\t\\\n\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tCom_DPrintf(\"CL_ProbeDemo: Unexpected end of demo. \"message\"\\n\");\t\\\n\t\tabort = true;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tbreak;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t}\n\n//\n// Seek the specified length and fail/abort if it fails.\n//\n#define DEMOPROBE_SEEK(file, length, message)\t\t\t\t\t\t\t\t\\\n\tif (VFS_SEEK(file, length, SEEK_CUR) == -1)\t\t\t\t\t\t\t\t\\\n\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tCom_DPrintf(\"CL_ProbeDemo: Unexpected end of demo. \"message\"\\n\");\t\\\n\t\tabort = true;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tbreak;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t}\n\n//\n// Probe a demo in different ways.\n//\nqbool CL_ProbeDemo(vfsfile_t *demfile, demoprobe_parse_type_t probetype, float *demotime)\n{\n\t#define PARSE_AS_MVD()\t\t\t((probetype == READ_MVD_TIME) || (probetype == TRY_READ_MVD))\n\t#define REGARD_AS_MVD_COUNT\t\t4\t\t// Regard this to be an MVD when this count has been reached.\n\n\tvfserrno_t err;\n\t\n\tfloat qwd_time\t\t\t\t= 0.0;\t\t// QWD Time = Time since start of demo in seconds.\n\tbyte mvd_time\t\t\t\t= 0;\t\t// MVD Time = Time in miliseconds since last time stamp.\n\tunsigned int total_mvd_time = 0;\t\t// Total MVD time in miliseconds.\n\t\n\tbyte command\t\t\t\t= 0;\t\t// Holds the command code.\n\tunsigned int multiple\t\t= 0;\t\t// Read dem_multiple data into this.\n\tunsigned int size\t\t\t= 0;\t\t// The size of the demo packet (follows dem_multiple, _single, _stats, _all and _read).\n\n\tint message_count\t\t\t= 0;\t\t// The total amount of demo messages.\n\tint len\t\t\t\t\t\t= 0;\t\t// Length of what's been read\n\tqbool is_mvd\t\t\t\t= false;\t// Is this an MVD. (Used when guessing if this is an MVD).\n\tint mvd_only_count\t\t\t= 0;\t\t// Try to figure out if this is a MVD by looking for commands only present in MVDs.\n\tqbool abort\t\t\t\t\t= false;\t// Something bad happened when reading the demo.\n\n\twhile (!abort)\n\t{\n\t\t// Read the time.\n\t\tif (PARSE_AS_MVD())\n\t\t{\n\t\t\t// MVD time.\n\t\t\tlen = VFS_READ(demfile, &mvd_time, 1, &err);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// QWD time.\n\t\t\tlen = VFS_READ(demfile, &qwd_time, 4, &err);\n\t\t\tqwd_time = LittleFloat(qwd_time);\n\t\t}\n\t\t\n\t\t// Any well formed demo should only end at an expected time stamp.\n\t\tif ((len == 0) || (err == VFSERR_EOF))\n\t\t{\n\t\t\tCom_DPrintf(\"CL_ProbeDemo: End of file. All good!\\n\");\n\t\t\tbreak;\n\t\t}\n\n\t\t// Read the command.\n\t\tlen = VFS_READ(demfile, &command, 1, &err);\n\t\tDEMOPROBE_VALIDATE_READ(1, len, \"when reading command\");\n\n\t\tsize = 0;\n\n\t\t// The first 3 bits contains the command code.\n\t\t// The 5 other bits are:\n\t\t// dem_stats, \n\t\t// dem_single\t= Player number for the player affected.\n\t\t// dem_all\t\t= Nothing, directed to all players.\n\t\t// dem_multiple = Nothing.\n\t\tswitch (command & 0x7)\n\t\t{\n\t\t\tcase dem_multiple :\n\t\t\t{\n\t\t\t\t// Only MVD.\n\n\t\t\t\t// Read a 32-bit number containing a bitmask for which the affected players are.\n\t\t\t\t// 32-bits, 32 players.\n\t\t\t\tlen = VFS_READ(demfile, &multiple, 4, &err);\n\n\t\t\t\tif (err == VFSERR_EOF)\n\t\t\t\t{\n\t\t\t\t\tCom_Printf(\"Unexpected end of demo when reading multiple.\\n\");\n\t\t\t\t\tabort = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase dem_single :\t\t// Only MVD.\n\t\t\tcase dem_all :\t\t\t// Only MVD.\n\t\t\t\tmvd_only_count++;\t// Count the number of MVD-only commands we find to determine if this is an MVD or not.\n\t\t\tcase dem_stats : \n\t\t\tcase dem_read :\n\t\t\t{\n\t\t\t\t// Read the size of the packet, we'll need it to seek past it.\n\t\t\t\tlen = VFS_READ(demfile, &size, 4, &err);\n\t\t\t\tDEMOPROBE_VALIDATE_READ(4, len, \"when reading size\");\n\t\t\t\tsize = LittleLong(size);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase dem_set :\n\t\t\t{\n\t\t\t\tDEMOPROBE_SEEK(demfile, 4, \"when reading incoming sequence number\"); // 32-bit int.\n\t\t\t\tDEMOPROBE_SEEK(demfile, 4, \"when reading outgoing sequence number\"); // 32-bit int.\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase dem_cmd :\n\t\t\t{\n\t\t\t\t// Only QWD.\n\t\t\t\tDEMOPROBE_SEEK(demfile, sizeof(usercmd_t), \"when reading user movement cmd.\");\n\t\t\t\tDEMOPROBE_SEEK(demfile, 12, \"when reading user viewangles cmd.\"); // 3 * 32-bit floats.\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault :\n\t\t\t{\n\t\t\t\tCom_DPrintf(\"CL_ProbeDemo: Unsupported command type %d!\\n\", (command & 0x7));\n\t\t\t\tabort = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// We're just trying to find out if this is an MVD so break if it is.\n\t\tif ((probetype == TRY_READ_MVD) && (mvd_only_count >= REGARD_AS_MVD_COUNT))\n\t\t{\n\t\t\tis_mvd = true;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (abort)\n\t\t\tbreak;\n\n\t\t// Read any specified data if needed.\n\t\tDEMOPROBE_SEEK(demfile, size, \"when reading size bytes of message\");\n\n\t\t// MVD time is saved as the time since last frame,\n\t\t// so we need to keep track of the total seperatly.\n\t\tif (probetype == READ_MVD_TIME)\n\t\t{\n\t\t\ttotal_mvd_time += mvd_time;\n\t\t}\n\n\t\tmessage_count++;\n\t}\n\n\t// Return to the start of the file.\n\tVFS_SEEK(demfile, 0, SEEK_SET);\n\n\tif (demotime)\n\t{\n\t\tif (probetype == READ_MVD_TIME)\n\t\t{\n\t\t\t// Convert mvd time to seconds.\n\t\t\t*demotime = total_mvd_time * 0.001;\n\t\t}\n\t\telse if (probetype == READ_QWD_TIME)\n\t\t{\n\t\t\t*demotime = qwd_time;\n\t\t}\n\n\t\tCom_DPrintf(\"CL_DemoProbe: Time: %f\\n\", *demotime);\n\t}\n\n\t// Is this a really short MVD, that doesn't contain our threshold of MVD only messages\n\t// but still enough for it to likely be one.\n\tif (!is_mvd \n\t\t&& (probetype == TRY_READ_MVD)\n\t\t&& (message_count > 0) \n\t\t&& (((float)mvd_only_count / message_count) >= 0.1))\n\t{\n\t\tis_mvd = true;\n\t}\n\n\treturn is_mvd;\n}\n\n//\n// Try to guess if this is an MVD by trying to parse it as one.\n//\nqbool CL_GetIsMVD(vfsfile_t *demfile)\n{\n\treturn CL_ProbeDemo(demfile, TRY_READ_MVD, NULL);\n}\n\n//\n// Try to calculate the time of a demo.\n//\nfloat CL_CalculateDemoTime(vfsfile_t *demfile)\n{\n\tfloat demotime = 0.0;\n\n\tCL_ProbeDemo(demfile, (cls.mvdplayback ? READ_MVD_TIME : READ_QWD_TIME), &demotime);\n\n\treturn demotime;\n}\n\n//\n// Returns true if the specified filename has a demo extension.\n//\nqbool CL_IsDemoExtension(const char *filename)\n{\n\tchar *ext = COM_FileExtension(filename);\n\n\treturn (!strncasecmp(ext, \"mvd\", sizeof(\"mvd\"))\n\t\t || !strncasecmp(ext, \"qwd\", sizeof(\"qwd\"))\n\t\t || !strncasecmp(ext, \"dem\", sizeof(\"dem\"))\n\t\t || !strncasecmp(ext, \"qwz\", sizeof(\"qwz\")));\n}\n\n//\n// Resets various states for beginning playback.\n//\nstatic void CL_DemoPlaybackInit(void)\n{\t\n\tcls.demoplayback\t= true;\t\n\n\t// Set demoplayback vars depending on the demo type.\n\t// CL_GetIsMVD(playbackfile); \n\t// TODO : Add a similar check for QWD also (or DEM), so that we can distinguish if it's a DEM or not playing also\n\t// TODO : Make a working check if a demo really is an mvd by its contents that also works on short demos.\n\tcls.mvdplayback\t\t= !strcasecmp(COM_FileExtension(cls.demoname), \"mvd\"); \n\tcls.nqdemoplayback\t= !strcasecmp(COM_FileExtension(cls.demoname), \"dem\");\n\n\t // Init some buffers for reading.\n\tCL_Demo_PB_Init(NULL, 0);\n\n\t// NetQuake demo support.\n\tif (cls.nqdemoplayback)\n\t{\n\t\tNQD_StartPlayback();\n\t\tdemo_time_length = -1.0;\n\t}\n\telse\n\t{\n\t\t// Calculate the demo time.\n\t\tdouble start = Sys_DoubleTime();\n\t\tdemo_time_length = CL_CalculateDemoTime(playbackfile);\n\t\tCom_DPrintf(\"Demo probe took %f seconds.\\n\", Sys_DoubleTime() - start);\n\t}\n\n\t// Setup the netchan and state.\n\tNetchan_Setup(NS_CLIENT, &cls.netchan, net_from, 0, 0);\n\tcls.state\t\t= ca_demostart;\n\tcls.demotime\t= 0;\n\tdemostarttime\t= -1.0;\n\tolddemotime\t\t= 0;\n\tnextdemotime\t= 0;\n\tcls.findtrack\t= true;\n\tbufferingtime\t= 0;\n\n\t// Used for knowing who messages is directed to in MVD's.\n\tcls.lastto\t\t= 0;\n\tcls.lasttype\t= 0;\n\n\tCL_ClearPredict();\n\n\t// Recording not allowed during mvdplayback.\n\tif (cls.mvdplayback && cls.demorecording) {\n\t\tCL_Stop_f();\n\t}\n\tMVD_Initialise();\n}\n\nchar *CL_Macro_DemoName_f (void)\n{\n\tstatic char macrobuf[128];\n\n\tif (cls.demoplayback) {\n\t\tCOM_StripExtension (COM_SkipPath (cls.demoname), macrobuf, sizeof (macrobuf));\n\t}\n\telse {\n\t\tmacrobuf[0] = '\\0';\n\t}\n\treturn macrobuf;\n}\n\nchar *CL_Macro_DemoLength_f (void)\n{\n\tstatic char macrobuf[64];\n\n\tsnprintf (macrobuf, sizeof (macrobuf), \"%d\", (cls.demoplayback ? (int) ceil(CL_GetDemoLength()) : 0));\n\n\treturn macrobuf;\n}\n\nstatic void CL_StartDemoCommand(void)\n{\n\tkeydest_t failure_dest = KeyDestStartupDemo(key_dest) ? key_console : key_dest;\n\n\tchar *real_name;\n\tchar name[MAX_OSPATH];\n\n\tif (Cmd_Argc() < 2) {\n\t\treturn; // internal error\n\t}\n\n\t// Save the name the user specified.\n\treal_name = Cmd_Argv(1);\n\n\t// Quick check for buffer overrun on COM_StripExtension below...\n\tif (strlen (real_name) > MAX_OSPATH - 4) {\n\t\tCom_Printf (\"Path is too long (%d characters, max is %d)\\n\", strlen (real_name), MAX_OSPATH - 4);\n\t\tkey_dest = failure_dest;\n\t\treturn;\n\t}\n\n\t// Disconnect any current game.\n\tHost_EndGame();\n\n\t// VFS-FIXME: This will affect playing qwz inside a zip\n\t#ifdef WITH_ZIP\n\t{\n\t\t//\n\t\t// Unpack the demo if it's zipped or gzipped. And get the path to the unpacked demo file.\n\t\t//\n\t\tchar unpacked_path[MAX_OSPATH];\n\t\tif (CL_UnpackAndOpenDemo(Cmd_Argv(1), unpacked_path, sizeof(unpacked_path))) {\n\t\t\treal_name = unpacked_path;\n\t\t}\n\t}\n\t#endif // WITH_ZIP\n\n\tstrlcpy(name, real_name, sizeof(name));\n\n\t//\n\t// Decompress QWZ demos to QWD before playing it (using an external app).\n\t//\n\tif (strlen(name) > 4 && !strcasecmp(COM_FileExtension(name), \"qwz\"))\n\t{\n\t\tint length = 0;\n\t\tbyte* data;\n\n\t\tPlayQWZDemo(Cmd_Argv(1));\n\n\t\t// We failed to extract the QWZ demo.\n\t\tif (!playbackfile && !qwz_playback) {\n\t\t\tkey_dest = failure_dest;\n\t\t\treturn;\n\t\t}\n\n\t\tdata = FS_LoadHeapFile(tempqwd_name, &length);\n\t\tif (data) {\n\t\t\tplaybackfile = FSMMAP_OpenVFS(data, length);\n\t\t}\n\t\telse if (qwz_playback) {\n\t\t\tCom_Printf(\"Decompression in progress...\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\telse if (!playbackfile) {\n\t\tint i;\n\t\tstatic char* demo_file_extensions[] = { \"qwd\", \"mvd\", \"dem\" };\n\n\t\t//\n\t\t// Find the demo path, trying different extensions if needed.\n\t\t//\n\n\t\t// If they specified a valid extension, try that first\n\t\tfor (i = 0; playbackfile == NULL && i < sizeof(demo_file_extensions) / sizeof(demo_file_extensions[0]); ++i) {\n\t\t\tif (!strcasecmp(COM_FileExtension(name), demo_file_extensions[i])) {\n\t\t\t\tplaybackfile = CL_Open_Demo_File(name, true, NULL);\n\t\t\t}\n\t\t}\n\n\t\tfor (i = 0; playbackfile == NULL && i < sizeof(demo_file_extensions) / sizeof(demo_file_extensions[0]); ++i) {\n\t\t\t// Strip the extension from the specified filename and append\n\t\t\t// the one we're currently checking for.\n\t\t\tCOM_StripExtension(name, name, sizeof(name));\n\t\t\tstrlcat(name, \".\", sizeof(name));\n\t\t\tstrlcat(name, demo_file_extensions[i], sizeof(name));\n\n\t\t\tplaybackfile = CL_Open_Demo_File(name, true, NULL);\n\t\t}\n\t}\n\n\t// Read the file completely into memory\n\tif (playbackfile && !FSMMAP_IsMemoryMapped(playbackfile)) {\n\t\tunsigned long len;\n\t\tvoid *buf;\n\t\tvfsfile_t *mmap_file;\n\n\t\tlen = VFS_GETLEN(playbackfile);\n\t\tbuf = Q_malloc(len);\n\n\t\tVFS_READ(playbackfile, buf, len, NULL);\n\t\tif (!(mmap_file = FSMMAP_OpenVFS(buf, len))) \n\t\t{\n\t\t\t// Couldn't create the memory file, just remove the buffer\n\t\t\tQ_free(buf);\n\t\t}\n\t\telse \n\t\t{\n\t\t\t// Close the file on disk now that we have read the file into memory\n\t\t\tVFS_CLOSE(playbackfile);\n\t\t\tplaybackfile = mmap_file;\n\t\t}\n\t}\n\n\t// Failed to open the demo from any path :(\n\tif (!playbackfile) {\n\t\tCom_Printf (\"Error: Couldn't open %s\\n\", Cmd_Argv(1));\n\t\tkey_dest = failure_dest;\n\t\treturn;\n\t}\n\n\tCL_DemoStartPlayback(name);\n}\n\nstatic void CL_DemoStartPlayback(const char* name)\n{\n\tstrlcpy(cls.demoname, name, sizeof(cls.demoname));\n\n\t// Reset multiview track slots.\n\tCL_MultiviewDemoStart ();\n\n\t// Reset stuff so demo rewinding works.\n\tcls.demoseeking\t\t= DST_SEEKING_NONE;\n\tcls.demorewinding\t= false;\n\tcls.demo_rewindtime = 0;\n\n\tCL_DemoPlaybackInit();\n\tTP_ExecTrigger(\"f_demostart\");\n\n\tCom_Printf(\"Playing demo from %s\\n\", COM_SkipPath(name));\n}\n\nqbool CL_DemoExtensionMatch(const char* path)\n{\n\tconst char* file_ext = COM_FileExtension(path);\n\tconst char* demo_file_extensions[] = {\n\t\t\"qwd\", \"mvd\", \"dem\", \"qwz\"\n\t};\n\tint i;\n\n\tfor (i = 0; i < sizeof(demo_file_extensions) / sizeof(demo_file_extensions[0]); ++i) {\n\t\tif (!strcasecmp(file_ext, demo_file_extensions[i])) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n//\n// Starts playback of a demo.\n//\nstatic void CL_Play_f(void)\n{\n\t// Show usage.\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tkeydest_t failure_dest = KeyDestStartupDemo(key_dest) ? key_console : key_dest;\n\n\t\tCom_Printf(\"Usage: %s <demoname>\\n\", Cmd_Argv(0));\n\t\tkey_dest = failure_dest;\n\t\treturn;\n\t}\n\n\tCL_StartDemoCommand();\n}\n\nstatic vfsfile_t* CL_Open_Demo_File(const char* name, qbool searchpaks, char** fullPath)\n{\n\tstatic char fullname[MAX_OSPATH];\n\tvfsfile_t *file = NULL;\n\n\tmemset(fullname, 0, sizeof(fullname));\n\tif (fullPath != NULL)\n\t\t*fullPath = fullname;\n\n\t// Look for the file in the above directory if it has ../ prepended to the filename.\n\tif (!strncmp(name, \"../\", 3) || !strncmp(name, \"..\\\\\", 3))\n\t{\n\t\tsnprintf(fullname, MAX_OSPATH, \"%s/%s\", com_basedir, name + 3);\n\t\tfile = FS_OpenVFS(fullname, \"rb\", FS_NONE_OS);\n\t}\n\telse if (searchpaks)\n\t{\n\t\t// Search demo on quake file system, even in paks.\n\t\tstrlcpy(fullname, name, sizeof(fullname));\n\t\tfile = FS_OpenVFS(name, \"rb\", FS_ANY);\n\t}\n\n\t// Look in the demo dir (user specified).\n\tif (!file)\n\t{\n\t\tsnprintf(fullname, MAX_OSPATH, \"%s/%s\", CL_DemoDirectory(), name);\n\t\tfile = FS_OpenVFS(fullname, \"rb\", FS_NONE_OS);\n\t}\n\n\t// Check the full system path (Run a demo anywhere on the file system).\n\tif (!file)\n\t{\n\t\tstrlcpy(fullname, name, sizeof(fullname));\n\t\tfile = FS_OpenVFS(name, \"rb\", FS_NONE_OS);\n\t}\n\n\treturn file;\n}\n\n//\n// Renders a demo as quickly as possible.\n//\nvoid CL_TimeDemo_f(void)\n{\n\tconst char* command = Cmd_Argv(0);\n\tqbool classic = !strcasecmp(command, \"timedemo\");\n\n\tif (classic && Cmd_Argc() != 2)\n\t{\n\t\tCom_Printf(\"%s <demoname> : gets demo speeds\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\telse if (!classic)\n\t{\n\t\tint desired_fps = TIMEDEMO_FIXEDFPS_DEFAULT;\n\n\t\tif (Cmd_Argc() < 2) {\n\t\t\tCom_Printf(\"%s <demoname> [<fps>]: gets demo speeds (default fps %d) \\n\", Cmd_Argv(0), desired_fps);\n\t\t\treturn;\n\t\t}\n\n\t\tif (Cmd_Argc() >= 3) {\n\t\t\tdesired_fps = atoi(Cmd_Argv(2));\n\t\t\tif (desired_fps < TIMEDEMO_FIXEDFPS_MINIMUM || desired_fps > TIMEDEMO_FIXEDFPS_MAXIMUM) {\n\t\t\t\tCom_Printf(\"Desired FPS must be between %d and %d\\n\", TIMEDEMO_FIXEDFPS_MINIMUM, TIMEDEMO_FIXEDFPS_MAXIMUM);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tcls.td_frametime = 1.0 / desired_fps;\n\t}\n\n\tCL_StartDemoCommand();\n\n\t// We failed to start demoplayback.\n\tif (cls.state != ca_demostart) {\n\t\treturn;\n\t}\n\n\t// cls.td_starttime will be grabbed at the second frame of the demo,\n\t// so all the loading time doesn't get counted.\n\n\tcls.timedemo = (classic ? TIMEDEMO_CLASSIC : TIMEDEMO_FIXEDFPS);\n\tcls.td_starttime = 0;\n\tcls.td_startframe = cls.framecount;\n\tcls.td_lastframe = -1;\t\t// Get a new message this frame.\n\tcls.td_nonrendering = 0;\n\tcls.td_frametime_max_frame = cls.td_frametime_max = 0;\n\tmemset(cls.td_frametime_stats, 0, sizeof(cls.td_frametime_stats));\n}\n\nvoid CL_QTVPlay (vfsfile_t *newf, void *buf, int buflen);\n\nchar qtvrequestbuffer[512 * 1024] = {0}; // mmm, demo list may be pretty long\nint  qtvrequestsize = 0;\n\nchar qtvpassword[128] = {0};\n\nvfsfile_t *qtvrequest = NULL;\n\n//\n// Closes a QTV request.\n//\nvoid QTV_CloseRequest(qbool warn)\n{\n\tif (qtvrequest)\n\t{\n\t\tif (warn)\n\t\t\tCom_Printf(\"Closing qtv request file\\n\");\n\n\t\tVFS_CLOSE(qtvrequest);\n\t\tqtvrequest = NULL;\n\t}\n\n\tqtvrequestsize = 0;\n}\n\n//\n// Polls a QTV proxy. (Called on each frame to see if we have a new QTV request available)\n//\nvoid CL_QTVPoll (void)\n{\n\tchar QTVSV[] = \"QTVSV \";\n\tint\t QTVSVLEN = sizeof(QTVSV)-1;\n\n\tchar hash[512] = {0};\n\tchar challenge[128] = {0};\n\tchar authmethod[128] = {0};\n\tvfserrno_t err;\n\tchar *start, *end, *colon;\n\tint len, need, chunk_size;\n\tqbool streamavailable = false;\n\tqbool saidheader = false;\n\tfloat svversion = 0;\n\tint qtv_ezquake_ext = 0;\n\n\t// We're not playing any QTV stream.\n\tif (!qtvrequest)\n\t\treturn;\n\n\t//\n\t// Calculate how much we need to read from the buffer.\n\t//\n\tneed = sizeof(qtvrequestbuffer); // Read the entire buffer\n\tneed -= 1;\t\t\t\t\t\t// For null terminator.\n\tneed -= qtvrequestsize;\t\t\t// We probably already have something, so don't re-read something we already read.\n\tneed = max(0, need);\t\t\t// Don't cause a crash by trying to read a negative value.\n\n\tlen = VFS_READ(qtvrequest, qtvrequestbuffer + qtvrequestsize, need, &err);\n\n\t// EOF, end of polling.\n\tif (!len && err == VFSERR_EOF)\n\t{\n\t\tQTV_CloseRequest(true);\n\t\treturn;\n\t}\n\n\t// Increase how much we've read.\n\tqtvrequestsize += len;\n\tqtvrequestbuffer[qtvrequestsize] = '\\0';\n\n\t// QTVSV not seen yet, abort.\n\tif (qtvrequestsize < QTVSVLEN)\n\t\treturn;\n\n\tif (strncmp(qtvrequestbuffer, QTVSV, QTVSVLEN))\n\t{\n\t\tCom_Printf(\"Server is not a QTV server (or is incompatible)\\n\");\n\t\tQTV_CloseRequest(true);\n\t\treturn;\n\t}\n\n\t// Make sure it's a complete chunk. \"\\n\\n\" specifies the end of a message.\n\tfor (start = qtvrequestbuffer; *start; start++)\n\t{\n\t\tif (start[0] == '\\n' && start[1] == '\\n')\n\t\t\tbreak;\n\t}\n\n\t// We've reached the end and didn't find \"\\n\\n\" so the chunk is incomplete.\n\tif (!*start)\n\t\treturn;\n\n\tsvversion = atof(qtvrequestbuffer + QTVSVLEN);\n\n\t// server sent float version, but we compare only major version number here\n\tif ((int)svversion != (int)QTV_VERSION)\n\t{\n\t\tCom_Printf(\"QTV server doesn't support a compatible protocol version, returned %.2f, need %.2f\\n\", svversion, QTV_VERSION);\n\t\tQTV_CloseRequest(true);\n\t\treturn;\n\t}\n\n\tstart = qtvrequestbuffer;\n\n\t//\n\t// Loop through the request buffer line by line.\n\t//\n\tfor (end = start; *end; )\n\t{\n\t\t// Found a new line.\n\t\tif (*end == '\\n')\n\t\t{\n\t\t\t// Don't parse it again.\n\t\t\t*end = '\\0';\n\n\t\t\t// Find the first colon in the string.\n\t\t\tcolon = strchr(start, ':');\n\n\t\t\t// We found a request.\n\t\t\tif (colon)\n\t\t\t{\n\t\t\t\t// Remove the colon.\n\t\t\t\t*colon++ = '\\0';\n\n\t\t\t\twhile (*colon == ' ')\n\t\t\t\t\tcolon++;\n\n\t\t\t\t//\n\t\t\t\t// Check the request type. Might be an error.\n\t\t\t\t//\n\t\t\t\tif (!strcmp(start, \"PERROR\"))\n\t\t\t\t{\n\t\t\t\t\tCom_Printf(\"QTV Error:\\n%s\\n\", colon);\n\t\t\t\t}\n\t\t\t\telse if (!strcmp(start, \"PRINT\"))\n\t\t\t\t{\n\t\t\t\t\tCom_Printf(\"QTV:\\n%s\\n\", colon);\n\t\t\t\t}\n\t\t\t\telse if (!strcmp(start, \"TERROR\"))\n\t\t\t\t{\n\t\t\t\t\tCom_Printf(\"QTV Error:\\n%s\\n\", colon);\n\t\t\t\t}\n\t\t\t\telse if (!strcmp(start, \"ADEMO\"))\n\t\t\t\t{\n\t\t\t\t\t//\n\t\t\t\t\t// Print demos.\n\t\t\t\t\t//\n\n\t\t\t\t\tif (!saidheader)\n\t\t\t\t\t{\n\t\t\t\t\t\tsaidheader = true;\n\t\t\t\t\t\tCom_Printf(\"Available Demos:\\n\");\n\t\t\t\t\t}\n\n\t\t\t\t\tCom_Printf(\"%s\\n\", colon);\n\t\t\t\t}\n\t\t\t\telse if (!strcmp(start, \"ASOURCE\"))\n\t\t\t\t{\n\t\t\t\t\t//\n\t\t\t\t\t// Print sources.\n\t\t\t\t\t//\n\n\t\t\t\t\tif (!saidheader)\n\t\t\t\t\t{\n\t\t\t\t\t\tsaidheader = true;\n\t\t\t\t\t\tCom_Printf(\"Available Sources:\\n\");\n\t\t\t\t\t}\n\n\t\t\t\t\tCom_Printf(\"%s\\n\", colon);\n\t\t\t\t}\n\t\t\t\telse if (!strcmp(start, \"AUTH\"))\n\t\t\t\t{\n\t\t\t\t\tstrlcpy(authmethod, colon, sizeof(authmethod));\n\t\t\t\t}\n\t\t\t\telse if (!strcmp(start, \"CHALLENGE\"))\n\t\t\t\t{\n\t\t\t\t\tstrlcpy(challenge, colon, sizeof(challenge));\n\t\t\t\t}\n\t\t\t\telse if (!strcmp(start, \"BEGIN\"))\n\t\t\t\t{\n\t\t\t\t\tstreamavailable = true;\n\t\t\t\t}\n\t\t\t\telse if (!strcmp(start, QTV_EZQUAKE_EXT))\n\t\t\t\t{\n\t\t\t\t\tqtv_ezquake_ext = atoi(colon);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// What follows this is a stream.\n\t\t\t\tif (!strcmp(start, \"BEGIN\"))\n\t\t\t\t{\n\t\t\t\t\tstreamavailable = true;\n\t\t\t\t}\n\t\t\t\telse if (!strncmp(start, \"QTVSV \", 6))\n\t\t\t\t{\n//\t\t\t\t\tCom_Printf(\"QTVSV HEADER: %s\\n\", start);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// From start to end, we have a line.\n\t\t\tstart = end + 1;\n\t\t}\n\n\t\tend++;\n\t}\n\n\t// Get the size of the stream chunk.\n\tchunk_size = qtvrequestsize - (end - qtvrequestbuffer);\n\n\tif (chunk_size < 0)\n\t{\n\t\tCom_Printf(\"Error while parsing qtv request\\n\");\n\t\tQTV_CloseRequest(true);\n\t\treturn;\n\t}\n\n\t// drop header\n\tqtvrequestsize = chunk_size;\n\tmemmove(qtvrequestbuffer, end, qtvrequestsize);\n\n//\tCom_Printf(\"memmove: %d\\n\", qtvrequestsize);\n\n\t// We found a stream.\n\tif (streamavailable)\n\t{\n\t\t// Start playing the QTV stream.\n\t\tCL_QTVPlay(qtvrequest, qtvrequestbuffer, qtvrequestsize);\n\t\tcls.qtv_svversion = svversion;\n\t\tcls.qtv_ezquake_ext = qtv_ezquake_ext;\n\t\tqtvrequest = NULL;\n\n\t\treturn;\n\t}\n\n\t// we need send auth now\n\tif (authmethod[0])\n\t{\n\t\tchar connrequest[2048];\n\n\t\tif (!strcmp(authmethod, \"PLAIN\"))\n\t\t{\n\t\t\tstrlcpy(connrequest, QTV_CL_HEADER(QTV_VERSION, QTV_EZQUAKE_EXT_NUM), sizeof(connrequest));\n\t\t\tstrlcat(connrequest, \"AUTH: PLAIN\\nPASSWORD: \\\"\", sizeof(connrequest));\n\t\t\tstrlcat(connrequest, qtvpassword, sizeof(connrequest));\n\t\t\tstrlcat(connrequest, \"\\\"\\n\", sizeof(connrequest));\n\t\t\tstrlcat(connrequest, cls.qtv_source, sizeof(connrequest));\n\t\t\tstrlcat(connrequest, \"\\n\", sizeof(connrequest));\n\n\t\t\tVFS_WRITE(qtvrequest, connrequest, strlen(connrequest));\n\n\t\t\treturn;\n\t\t}\n\t\telse if (!strcmp(authmethod, \"SHA3_512\"))\n\t\t{\n\t\t\tif (strlen(challenge)>=63)\n\t\t\t{\n\t\t\t\tsha3_context c;\n\t\t\t\tconst uint8_t *byte_hash;\n\n\t\t\t\tsha3_Init512(&c);\n\t\t\t\tsha3_Update(&c, challenge, strlen(challenge));\n\t\t\t\tsha3_Update(&c, qtvpassword, strlen(qtvpassword));\n\t\t\t\tbyte_hash = sha3_Finalize(&c);\n\t\t\t\tsha3_512_ByteToHex(hash, byte_hash);\n\t\t\t\tsnprintf(connrequest, sizeof(connrequest),\n\t\t\t\t\t\"%s\" \"AUTH: SHA3_512\\nPASSWORD: \\\"%s\\\"\\n\\n\", QTV_CL_HEADER(QTV_VERSION, QTV_EZQUAKE_EXT_NUM), hash);\n\n\t\t\t\tVFS_WRITE(qtvrequest, connrequest, strlen(connrequest));\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tCom_Printf(\"Wrong challenge for AUTH: %s\\n\", authmethod);\n\t\t}\n\t\telse if (!strcmp(authmethod, \"CCITT\"))\n\t\t{\n\t\t\tif (strlen(challenge)>=32)\n\t\t\t{\n\t\t\t\tunsigned short crcvalue;\n\n\t\t\t\tsnprintf(hash, sizeof(hash), \"%s%s\", challenge, qtvpassword);\n\t\t\t\tcrcvalue = CRC_Block((byte *)hash, strlen(hash));\n\t\t\t\tsnprintf(hash, sizeof(hash), \"0x%X\", (unsigned int)CRC_Value(crcvalue));\n\t\t\t\tsnprintf(connrequest, sizeof(connrequest), \n\t\t\t\t\t\"%s\" \"AUTH: CCITT\\nPASSWORD: \\\"%s\\\"\\n\\n\", QTV_CL_HEADER(QTV_VERSION, QTV_EZQUAKE_EXT_NUM), hash);\n\n\t\t\t\tVFS_WRITE(qtvrequest, connrequest, strlen(connrequest));\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tCom_Printf(\"Wrong challenge for AUTH: %s\\n\", authmethod);\n\t\t}\n\t\telse if (!strcmp(authmethod, \"MD4\"))\n\t\t{\n\t\t\tif (strlen(challenge)>=8)\n\t\t\t{\n\t\t\t\tunsigned int md4sum[4];\n\n\t\t\t\tsnprintf(hash, sizeof(hash), \"%s%s\", challenge, qtvpassword);\n\t\t\t\tCom_BlockFullChecksum (hash, strlen(hash), (unsigned char*)md4sum);\n\t\t\t\tsnprintf(hash, sizeof(hash), \"%X%X%X%X\", md4sum[0], md4sum[1], md4sum[2], md4sum[3]);\n\t\t\t\tsnprintf(connrequest, sizeof(connrequest), \n\t\t\t\t\t\"%s\" \"AUTH: MD4\\nPASSWORD: \\\"%s\\\"\\n\\n\", QTV_CL_HEADER(QTV_VERSION, QTV_EZQUAKE_EXT_NUM), hash);\n\n\t\t\t\tVFS_WRITE(qtvrequest, connrequest, strlen(connrequest));\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tCom_Printf(\"Wrong challenge for AUTH: %s\\n\", authmethod);\n\t\t}\n\t\telse if (!strcmp(authmethod, \"NONE\"))\n\t\t{\n\t\t\tsnprintf(connrequest, sizeof(connrequest),\n\t\t\t\t\t\"%s\" \"AUTH: NONE\\nPASSWORD: \\n\\n\", QTV_CL_HEADER(QTV_VERSION, QTV_EZQUAKE_EXT_NUM));\n\n\t\t\tVFS_WRITE(qtvrequest, connrequest, strlen(connrequest));\n\n\t\t\treturn;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCom_Printf(\"Unknown auth method %s\\n\", authmethod);\n\t\t}\n\t}\n\n\tQTV_CloseRequest(true);\n}\n\n/*\n * Queries a QTV proxy for a list of available stream sources or demos\n * This function is used for both for qtvlist and qtvdemolist\n */\nvoid CL_QTVList_f (void)\n{\n\tchar *connrequest;\n\tvfsfile_t *newf;\n\tqbool qtvlist = !strcmp(\"qtv_query_sourcelist\", Cmd_Argv(0));\n\n\tif (Cmd_Argc() < 2) {\n\t\tCom_Printf(\"Usage: %s hostname[:port] [password]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\t// Open the TCP connection to the QTV proxy.\n\tnewf = FS_OpenTCP(Cmd_Argv(1));\n\tif (newf == NULL) {\n\t\tCom_Printf(\"Couldn't connect to proxy\\n\");\n\t\treturn;\n\t}\n\n\tstrlcpy(qtvpassword, Cmd_Argv(2), sizeof(qtvpassword));\n\n\t// Send the version of QTV the client supports.\n\tconnrequest = QTV_CL_HEADER(QTV_VERSION, QTV_EZQUAKE_EXT_NUM);\n\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\n\t// Get a source list from the server.\n\tconnrequest = qtvlist ? \"SOURCELIST\\n\" : \"DEMOLIST\\n\";\n\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\n\t// Send our userinfo\n\tconnrequest = \"USERINFO: \";\n\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\n\tconnrequest = cls.userinfo;\n\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\n\tconnrequest =\t\"\\n\";\n\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\n\t/* if we use pass, then send our supported auth methods */\n\tif (qtvpassword[0]) {\n\t\tconnrequest = \"AUTH: SHA3_512\\n\" \"AUTH: MD4\\n\" \"AUTH: CCITT\\n\" \"AUTH: PLAIN\\n\" \"AUTH: NONE\\n\";\n\t\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\t}\n\n\t/* \"\\n\\n\" will end the session */\n\tconnrequest = \"\\n\";\n\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\n\t/* Close any old request that might still be open */\n\tQTV_CloseRequest(true);\n\n\t/* Set the current connection to be the QTVRequest to be used later */\n\tqtvrequest = newf;\n}\n\n//\n// Plays a QTV stream.\n//\nvoid CL_QTVPlay (vfsfile_t *newf, void *buf, int buflen)\n{\n\t// End any current game.\n\tHost_EndGame();\n\n\t// Close the old playback file just in case, and\n\t// open the \"network file\" for QTV playback.\n\tif (playbackfile)\n\t\tVFS_CLOSE(playbackfile);\n\tplaybackfile = newf;\n\n\t// Reset multiview track slots\n\tCL_MultiviewDemoStart ();\n\n\t// We're now playing a demo.\n\tcls.demoplayback\t= true;\n\tcls.mvdplayback\t\t= QTV_PLAYBACK;\n\tcls.nqdemoplayback\t= false;\n\n\t// Init playback buffers.\n\tCL_Demo_PB_Init(buf, buflen);\n\n\t// NetQuake demo support.\n\tif (cls.nqdemoplayback)\n\t{\n\t\tNQD_StartPlayback (); // Maybe some day QTV will stream NQ demos too...\n\t}\n\n\t// Setup demo playback for the netchan.\n\tcls.state = ca_demostart;\n\tNetchan_Setup (NS_CLIENT, &cls.netchan, net_from, 0, 0);\n\tcls.demotime = 0;\n\tdemostarttime = -1.0;\n\tolddemotime = nextdemotime = 0;\n\tcls.findtrack = true;\n\n\tcls.qtv_donotbuffer = true; // do not try buffering before \"skins\" not received\n\n\tbufferingtime = 0; // with eztv it correct, since eztv do not send data before we complete connection, so prebuffering is pointless\n\n\t// Used for knowing who messages is directed to in MVD's.\n\tcls.lastto = cls.lasttype = 0;\n\n\tCL_ClearPredict();\n\n\t// Recording not allowed during mvdplayback.\n\tif (cls.mvdplayback && cls.demorecording) {\n\t\tCL_Stop_f();\n\t}\n\n\tMVD_Initialise();\n\tTP_ExecTrigger (\"f_demostart\");\n\n\tCom_Printf(\"Attempting to stream QTV data, buffer is %.1fs\\n\", (double)(QTVBUFFERTIME));\n}\n\nstatic char prev_qtv_connrequest[512]; /* FIXME: Stupid name, it might as well be ACTUAL streaming address */\nstatic char prev_qtv_password[128];\n\nchar *CL_QTV_GetCurrentStream(void)\n{\n\tif (cls.mvdplayback == QTV_PLAYBACK) {\n\t\tif (prev_qtv_connrequest[0]) {\n\t\t\treturn &prev_qtv_connrequest[0];\n\t\t}\n\t}\n\t/* Not connected to QTV, no address then */\n\treturn NULL;\n}\n\n//\n// Reconnects to the previous QTV.\n//\nvoid CL_QTVReconnect_f (void)\n{\n\tif (!prev_qtv_connrequest[0])\n\t{\n\t\tCom_Printf(\"No previous QTV proxy available to connect to.\\n\");\n\t\treturn;\n\t}\n\n\tCbuf_AddText(va(\"qtvplay %s %s\\n\", prev_qtv_connrequest, prev_qtv_password));\n}\n\n// checks if the argument is of the form http://quakeworld.fi:28000/watch.qtv?sid=8\n// if so, issue a new qtvplay command with stream@hostname:port format\nstatic qbool CL_QTVPlay_URL_format(void)\n{\n\tconst char *prefix = strstr(Cmd_Argv(1), \"http://\");\n\tconst char *docpart = strstr(Cmd_Argv(1), \"/watch.qtv?sid=\");\n\n\tif (prefix && docpart && prefix == Cmd_Argv(1) && prefix < docpart) {\n\t\tint streamid = Q_atoi(docpart + strlen(\"/watch.qtv?sid=\"));\n\t\tint hostnamelen = docpart - prefix - strlen(\"http://\");\n\t\tCbuf_AddText(va(\"qtvplay %d@%.*s\\n\", streamid, hostnamelen, Cmd_Argv(1) + strlen(\"http://\")));\n\t\treturn true;\n\t}\n\telse {\n\t\treturn false;\n\t}\n}\n\n//\n// Start playback of a QTV stream.\n//\nvoid CL_QTVPlay_f (void)\n{\n\tchar *connrequest;\n\tvfsfile_t *newf;\n\tchar stream_host[1024] = {0}, *stream, *host;\n\n\t// Show usage.\n\tif (Cmd_Argc() < 2) {\n\t\tCom_Printf(\"Usage: qtvplay [stream@]hostname[:port] [password]\\n\");\n\t\treturn;\n\t}\n\n\tif (CL_QTVPlay_URL_format()) {\n\t\treturn;\n\t}\n\n\tstrlcpy(qtvpassword, Cmd_Argv(2), sizeof(qtvpassword));\n\n\t// The stream address.\n\tconnrequest = Cmd_Argv(1);\n\n\t// We've succesfully connected to a QTV proxy, save the connrequest string so we can use it to reconnect.\n\tstrlcpy(prev_qtv_connrequest, connrequest, sizeof(prev_qtv_connrequest));\n\tstrlcpy(prev_qtv_password, qtvpassword, sizeof(prev_qtv_password));\n\n\t//\n\t// If a \"#\" is at the beginning of the given address it refers to a .qtv file.\n\t//\n\tif (*connrequest == '#')\n\t{\n\t\t#define QTV_FILE_STREAM     1\n\t\t#define QTV_FILE_JOIN       2\n\t\t#define QTV_FILE_OBSERVE    3\n\t\t#define QTV_FILE_CHALLENGE  4\n\t\tint match = 0;\n\n\t\tchar buffer[1024];\n\t\tchar *s;\n\t\tFILE *f;\n\n\t\t// Try to open the .qtv file.\n\t\tf = fopen(connrequest + 1, \"rt\");\n\t\tif (!f)\n\t\t{\n\t\t\tCom_Printf(\"qtvplay: can't open file %s\\n\", connrequest + 1);\n\t\t\treturn;\n\t\t}\n\n\t\t//\n\t\t// Read the entire .qtv file and execute the approriate commands\n\t\t// Stream, join or observe.\n\t\t//\n\t\twhile (!feof(f))\n\t\t{\n\t\t\tif (fgets(buffer, sizeof(buffer) - 1, f) == NULL) {\n\t\t\t\tCom_Printf(\"Error reading the QTV file.\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (!strncmp(buffer, \"Stream=\", 7) || !strncmp(buffer, \"Stream:\", 7))\n\t\t\t{\n\t\t\t\tmatch = QTV_FILE_STREAM;\n\t\t\t}\n\n\t\t\tif (!strncmp(buffer, \"Join=\", 5) || !strncmp(buffer, \"Join:\", 5))\n\t\t\t{\n\t\t\t\tmatch = QTV_FILE_JOIN;\n\t\t\t}\n\n\t\t\tif (!strncmp(buffer, \"Observe=\", 8) || !strncmp(buffer, \"Observe:\", 8))\n\t\t\t{\n\t\t\t\tmatch = QTV_FILE_OBSERVE;\n\t\t\t}\n\n\t\t\tif (!strncmp(buffer, \"Challenge=\", 10) || !strncmp(buffer, \"Challenge:\", 10))\n\t\t\t{\n\t\t\t\tmatch = QTV_FILE_CHALLENGE;\n\t\t\t}\n\n\t\t\t// We found a match in the .qtv file.\n\t\t\tif (match)\n\t\t\t{\n\t\t\t\t// Strip new line chars.\n\t\t\t\tfor (s = buffer + strlen(buffer)-1; s >= buffer; s--)\n\t\t\t\t{\n\t\t\t\t\tif (*s == '\\r' || *s == '\\n')\n\t\t\t\t\t{\n\t\t\t\t\t\t*s = 0;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip the title part.\n\t\t\t\ts = buffer;\n\t\t\t\twhile(*s && *s != ':' && *s != '=') {\n\t\t\t\t\ts++;\n\t\t\t\t}\n\t\t\t\tif (*s) {\n\t\t\t\t\ts++;\n\t\t\t\t}\n\n\t\t\t\t// Remove any leading white spaces.\n\t\t\t\twhile(*s && *s <= ' ')\n\t\t\t\t{\n\t\t\t\t\ts++;\n\t\t\t\t}\n\n\t\t\t\t// Call the appropriate commands based on the match.\n\t\t\t\tswitch (match)\n\t\t\t\t{\n\t\t\t\t\tcase QTV_FILE_STREAM :\n\t\t\t\t\t\tCbuf_AddText(va(\"qtvplay \\\"%s\\\"\\n\", s));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase QTV_FILE_JOIN :\n\t\t\t\t\t\tCbuf_AddText(va(\"join \\\"%s\\\"\\n\", s));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase QTV_FILE_OBSERVE :\n\t\t\t\t\t\tCbuf_AddText(va(\"observe \\\"%s\\\"\\n\", s));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase QTV_FILE_CHALLENGE:\n\t\t\t\t\t\t// URL is passed in, it needs to be further processed by the qwurl command\n\t\t\t\t\t\tCbuf_AddText(va(\"qwurl \\\"%s\\\"\\n\", s));\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Break the while-loop we found what we want.\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Close the file.\n\t\tfclose(f);\n\t\treturn;\n\t}\n\n\t// The argument is the host address for the QTV server.\n\n\t// Find the position of the last @ char in the connection string,\n\t// this is when the user specifies a specific stream # on the QTV proxy\n\t// that he wants to stream. Default is to stream the first one.\n\t// [stream@]hostname[:port]\n\t// (QTV proxies can be chained, so we must get the last @)\n\t// In other words split stream and host part\n\n\tstrlcpy(stream_host, connrequest, sizeof(stream_host));\n\n\tconnrequest = strchrrev(stream_host, '@');\n\tif (connrequest)\n\t{\n\t\tstream = stream_host;\t\t// Stream part.\n\t\tconnrequest[0] = 0;\t\t\t// Truncate.\n\t\thost   = connrequest + 1;\t// Host part.\n\t}\n\telse\n\t{\n\t\tstream = \"\";\t\t\t\t// Use default stream, user not specifie stream part.\n\t\thost   = stream_host;\t\t// Arg is just host.\n\t}\n\n\t// Open a TCP socket to the specified host.\n\tCL_StopPlayback();\n\tnewf = FS_OpenTCP(host);\n\n\t// Failed to open the connection.\n\tif (!newf)\n\t{\n\t\tCom_Printf(\"Couldn't connect to proxy %s\\n\", host);\n\t\treturn;\n\t}\n\n\t// Send a QTV request to the proxy.\n\tconnrequest = QTV_CL_HEADER(QTV_VERSION, QTV_EZQUAKE_EXT_NUM);\n\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\n\t// If the user specified a specific stream such as \"5@hostname:port\"\n\t// we need to send a SOURCE request.\n\tif (stream[0]) {\n\t\tstrlcpy(cls.qtv_source, \"SOURCE: \", sizeof(cls.qtv_source));\n\t\tstrlcat(cls.qtv_source, stream, sizeof(cls.qtv_source));\n\t\tstrlcat(cls.qtv_source, \"\\n\", sizeof(cls.qtv_source));\n\n\t\tVFS_WRITE(newf, cls.qtv_source, strlen(cls.qtv_source));\n\t}\n\telse {\n\t\tmemset(cls.qtv_source, 0, sizeof(cls.qtv_source));\n\t}\n\n\t// Send our userinfo\n\tconnrequest = \"USERINFO: \";\n\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\tconnrequest = cls.userinfo;\n\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\tconnrequest =\t\"\\n\";\n\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\n\t// if we use pass, then send our supported auth methods\n\tif (qtvpassword[0])\n\t{\n\t\tconnrequest =\n\t\t\t\t\t\t\"AUTH: SHA3_512\\n\"\n\t\t\t\t\t\t\"AUTH: MD4\\n\"\n\t\t\t\t\t\t\"AUTH: CCITT\\n\"\n\t\t\t\t\t\t\"AUTH: PLAIN\\n\"\n\t\t\t\t\t\t\"AUTH: NONE\\n\";\n\n\t\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\t}\n\n\t// Two \\n\\n tells the server we're done.\n\tconnrequest =\t\"\\n\";\n\tVFS_WRITE(newf, connrequest, strlen(connrequest));\n\n\t// We're finished requesting, but not done yet so save the\n\t// socket for the actual streaming :)\n\tQTV_CloseRequest(false);\n\tqtvrequest = newf;\n}\n\n\n//=============================================================================\n//\t\t\t\t\t\t\t\tDEMO TOOLS\n//=============================================================================\n\n//\n// Sets the playback speed of a demo.\n//\nvoid CL_Demo_SetSpeed_f (void)\n{\n\textern cvar_t cl_demospeed;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCom_Printf(\"Usage: %s [speed %%]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tCvar_SetValue(&cl_demospeed, atof(Cmd_Argv(1)) / 100.0);\n}\n\n//\n// Cleans up after demo has been rewound to the correct point\n//\nvoid CL_Demo_Stop_Rewinding(void) \n{\n\t// Make sure we keep our tracked players after rewinding.\n\tCL_MultiviewDemoStopRewind ();\n\n\tif (rewind_spec_track >= 0) \n\t{\n\t\tCam_Lock(rewind_spec_track);\n\t}\n\telse \n\t{\n\t\t// Switch to free-floating camera and restore view\n\t\tCam_Unlock();\n\t\tCam_Pos_Set(rewind_pos[0], rewind_pos[1], rewind_pos[2]);\n\t\tCam_Angles_Set(rewind_angle[0], rewind_angle[1], rewind_angle[2]);\n\t}\n\tcls.findtrack\t\t= false;\n\n\tR_ClearParticles();\n\tcls.demorewinding   = false;\n}\n\n// \n// Checks if demo needs to be rewound to previous point in time\n//\nvoid CL_Demo_Check_For_Rewind(float nextdemotime)\n{\n\t// If we're seeking and our seek destination is in the past we need to rewind.\n\tif (cls.demoseeking && !cls.demorewinding && (cls.demotime < nextdemotime))\n\t{\n\t\t// Restart playback from the start of the file and then demo seek to the rewind spot.\n\t\tVFS_SEEK(playbackfile, 0, SEEK_SET);\n\n\t\t// We need to save track information.\n\t\tCL_MultiviewDemoStartRewind ();\n\t\trewind_spec_track = WhoIsSpectated(); //spec_track;\n\n\t\tcls.findtrack = false;\n\t\tVectorCopy(cl.viewangles, rewind_angle);\n\t\tVectorCopy(cl.simorg, rewind_pos);\n\n\t\t// Restart the demo from scratch.\n\t\tCL_DemoPlaybackInit();\n\n\t\tcls.demopackettime  = 0.0;\n\t\tcls.demorewinding   = true;\n\t}\n\t\n\tif (cls.demorewinding)\n\t{\n\t\t// If we've reached active state, we can set the new demotime\n\t\t// to trigger the demo seek to the desired location.\n\t\t// Before we're active, cl.demotime will just get overwritten.\n\t\tif (cls.state >= ca_active)\n\t\t{\n\t\t\tcls.demotime = demostarttime + cls.demo_rewindtime;\n\t\t\tcls.demoseeking = DST_SEEKING_NORMAL;\n\t\t\t//cls.demorewinding = false;\n\n\t\t\t// We have now finished restarting the demo and will now seek\n\t\t\t// to the new demotime just like we do when seeking forward.\n\t\t}\n\t}\n}\n\n//\n// Jumps to a specified time in a demo.\n//\nvoid CL_Demo_Jump_f (void)\n{\n    int seconds = 0, seen_col, relative = 0;\n    char *text, *s;\n\tstatic char *usage_message = \"Usage: %s [+|-][m:]<s> (seconds)\\n\";\n\n\t// Cannot jump without playing demo.\n\tif (!cls.demoplayback)\n\t{\n\t\tCom_Printf(\"Error: not playing a demo\\n\");\n        return;\n\t}\n\n\tif (cls.mvdplayback == QTV_PLAYBACK)\n\t{\n\t\tCom_Printf(\"Error: cannot jump during QTV playback.\\n\");\n\t\treturn;\n\t}\n\n\t// Must be active to jump.\n\tif (cls.state < ca_active)\n\t{\n\t\tCom_Printf(\"Error: demo must be active first\\n\");\n\t\treturn;\n\t}\n\n\t// Show usage.\n    if (Cmd_Argc() != 2)\n\t{\n        Com_Printf(usage_message, Cmd_Argv(0));\n        return;\n    }\n\n\t// Get the jump string.\n    text = Cmd_Argv(1);\n\n\t//\n\t// Parse which direction we're jumping in if\n\t// we're jumping relativly based on the current time.\n\t//\n\tif (text[0] == '-')\n\t{\n\t\t// Jumping backwards.\n\t\ttext++;\n\t\trelative = -1;\n\t}\n\telse if (text[0] == '+')\n\t{\n\t\t// Jumping forward.\n\t\ttext++;\n\t\trelative = 1;\n\t}\n\telse if (!isdigit(text[0]))\n\t{\n\t\t// Incorrect input, show usage message.\n        Com_Printf(usage_message, Cmd_Argv(0));\n        return;\n\t}\n\n\t// Find the number of colons (max 2 allowed) and make sure\n\t// we only have digits in the string.\n\tfor (seen_col = 0, s = text; *s; s++)\n\t{\n\t\tif (*s == ':')\n\t\t{\n\t\t\tseen_col++;\n\t\t}\n\t\telse if (!isdigit(*s))\n\t\t{\n\t\t\t// Not a digit, show usage message.\n\t\t\tCom_Printf(usage_message, Cmd_Argv(0));\n\t\t\treturn;\n\t\t}\n\n\t\tif (seen_col >= 2)\n\t\t{\n\t\t\t// More than two colons found, show usage message.\n\t\t\tCom_Printf(usage_message, Cmd_Argv(0));\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// If there's at least 1 colon we know everything\n\t// before it is minutes, so add it them to our jump time.\n    if (strchr(text, ':'))\n\t{\n        seconds += 60 * atoi(text);\n        text = strchr(text, ':') + 1;\n    }\n\n\t// The numbers after the first colon will be seconds.\n    seconds += atoi(text);\n\n\tCL_Demo_Jump(seconds, relative, DST_SEEKING_NORMAL);\n}\n\n//\n// Jumps to a specified time in a demo.\n//\nvoid CL_Demo_Jump_Mark_f (void)\n{\n\tint seconds = 99999; // as far as possible, we have NO idea about time, we search MARK\n\n\t// Cannot jump without playing demo.\n\tif (!cls.demoplayback)\n\t{\n\t\tCom_Printf(\"Error: not playing a demo\\n\");\n        return;\n\t}\n\n\t// Must be active to jump.\n\tif (cls.state < ca_active)\n\t{\n\t\tCom_Printf(\"Error: demo must be active first\\n\");\n\t\treturn;\n\t}\n\n\tCL_Demo_Jump(seconds, 0, DST_SEEKING_DEMOMARK);\n}\n\nvoid CL_Demo_Jump_End_f(void)\n{\n\tint target_time;\n\n\t// Cannot jump without playing demo.\n\tif (!cls.demoplayback) {\n\t\tCom_Printf(\"Error: not playing a demo\\n\");\n\t\treturn;\n\t}\n\n\t// Must be active to jump.\n\tif (cls.state < ca_active) {\n\t\tCom_Printf(\"Error: demo must be active first\\n\");\n\t\treturn;\n\t}\n\n\ttarget_time = (cls.demoplayback ? (int)ceil(CL_GetDemoLength()) - 2 : 0);\n\tif (target_time - (cls.demotime - demostarttime) < (cl.intermission ? 10 : 2)) {\n\t\tCom_Printf(\"Error: too close to end of demo\\n\");\n\t\treturn;\n\t}\n\n\tCL_Demo_Jump(target_time, 0, DST_SEEKING_END);\n}\n\nstatic void CL_Demo_Jump_Status_Free (demoseekingstatus_condition_t *condition)\n{\n\tif (condition == NULL)\n\t\treturn;\n\n\tCL_Demo_Jump_Status_Free(condition->or);\n\tCL_Demo_Jump_Status_Free(condition->and);\n\n\tQ_free(condition);\n}\n\nstatic demoseekingstatus_condition_t *CL_Demo_Jump_Status_Condition_New (demoseekingstatus_matchtype_t type, int stat, int value)\n{\n\tdemoseekingstatus_condition_t *condition = Q_malloc(sizeof(demoseekingstatus_condition_t));\n\n\tcondition->type = type;\n\tcondition->stat = stat;\n\tcondition->value = value;\n\tcondition->or = NULL;\n\tcondition->and = NULL;\n\n\treturn condition;\n}\n\nstatic void CL_Demo_Jump_Status_Condition_Negate (demoseekingstatus_condition_t *condition)\n{\n\tswitch (condition->type) {\n\t\tcase DEMOSEEKINGSTATUS_MATCH_EQUAL:\n\t\t\tcondition->type = DEMOSEEKINGSTATUS_MATCH_NOT_EQUAL;\n\t\t\tbreak;\n\t\tcase DEMOSEEKINGSTATUS_MATCH_NOT_EQUAL:\n\t\t\tcondition->type = DEMOSEEKINGSTATUS_MATCH_EQUAL;\n\t\t\tbreak;\n\t\tcase DEMOSEEKINGSTATUS_MATCH_LESS_THAN:\n\t\t\tcondition->type = DEMOSEEKINGSTATUS_MATCH_GREATER_THAN;\n\t\t\tcondition->value -= 1;\n\t\t\tbreak;\n\t\tcase DEMOSEEKINGSTATUS_MATCH_GREATER_THAN:\n\t\t\tcondition->type = DEMOSEEKINGSTATUS_MATCH_LESS_THAN;\n\t\t\tcondition->value += 1;\n\t\t\tbreak;\n\t\tcase DEMOSEEKINGSTATUS_MATCH_BIT_ON:\n\t\t\tcondition->type = DEMOSEEKINGSTATUS_MATCH_BIT_OFF;\n\t\t\tbreak;\n\t\tcase DEMOSEEKINGSTATUS_MATCH_BIT_OFF:\n\t\t\tcondition->type = DEMOSEEKINGSTATUS_MATCH_BIT_ON;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tassert(false);\n\t\t\tbreak;\n\t}\n}\n\nstatic qbool CL_Demo_Jump_Status_Match (demoseekingstatus_condition_t *condition)\n{\n\tif (condition->or && CL_Demo_Jump_Status_Match(condition->or))\n\t\treturn true;\n\n\tswitch (condition->type) {\n\t\tcase DEMOSEEKINGSTATUS_MATCH_EQUAL:\n\t\t\tif (cl.stats[condition->stat] != condition->value)\n\t\t\t\treturn false;\n\t\t\tbreak;\n\t\tcase DEMOSEEKINGSTATUS_MATCH_NOT_EQUAL:\n\t\t\tif (cl.stats[condition->stat] == condition->value)\n\t\t\t\treturn false;\n\t\t\tbreak;\n\t\tcase DEMOSEEKINGSTATUS_MATCH_LESS_THAN:\n\t\t\tif (cl.stats[condition->stat] >= condition->value)\n\t\t\t\treturn false;\n\t\t\tbreak;\n\t\tcase DEMOSEEKINGSTATUS_MATCH_GREATER_THAN:\n\t\t\tif (cl.stats[condition->stat] <= condition->value)\n\t\t\t\treturn false;\n\t\t\tbreak;\n\t\tcase DEMOSEEKINGSTATUS_MATCH_BIT_ON:\n\t\t\tif (!(cl.stats[condition->stat] & condition->value))\n\t\t\t\treturn false;\n\t\t\tbreak;\n\t\tcase DEMOSEEKINGSTATUS_MATCH_BIT_OFF:\n\t\t\tif (cl.stats[condition->stat] & condition->value)\n\t\t\t\treturn false;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tassert(false);\n\t\t\treturn false;\n\t}\n\n\tif (condition->and != NULL) {\n\t\treturn CL_Demo_Jump_Status_Match(condition->and);\n\t} else {\n\t\treturn true;\n\t}\n}\n\nvoid CL_Demo_Jump_Status_Check (void)\n{\n\tif (CL_Demo_Jump_Status_Match(cls.demoseekingstatus.conditions)) {\n\t\tif (cls.demoseekingstatus.non_matching_found) {\n\t\t\tCL_Demo_Jump_Status_Free(cls.demoseekingstatus.conditions);\n\t\t\tcls.demoseekingstatus.conditions = NULL;\n\t\t\tcls.demoseeking = DST_SEEKING_FOUND;\n\t\t}\n\t} else if (!cls.demoseekingstatus.non_matching_found) {\n\t\tcls.demoseekingstatus.non_matching_found = true;\n\t}\n}\n\nstatic int CL_Demo_Jump_Status_Parse_Weapon (const char *arg)\n{\n\tif (!strcasecmp(\"axe\", arg)) {\n\t\treturn IT_AXE;\n\t} else if (!strcasecmp(\"sg\", arg)) {\n\t\treturn IT_SHOTGUN;\n\t} else if (!strcasecmp(\"ssg\", arg)) {\n\t\treturn IT_SUPER_SHOTGUN;\n\t} else if (!strcasecmp(\"ng\", arg)) {\n\t\treturn IT_NAILGUN;\n\t} else if (!strcasecmp(\"sng\", arg)) {\n\t\treturn IT_SUPER_NAILGUN;\n\t} else if (!strcasecmp(\"gl\", arg)) {\n\t\treturn IT_GRENADE_LAUNCHER;\n\t} else if (!strcasecmp(\"rl\", arg)) {\n\t\treturn IT_ROCKET_LAUNCHER;\n\t} else if (!strcasecmp(\"lg\", arg)) {\n\t\treturn IT_LIGHTNING;\n\t} else {\n\t\treturn 0;\n\t}\n}\n\nstatic int CL_Demo_Jump_Status_Parse_Constraint (const char *arg, int *value)\n{\n\tif (strlen(arg) < 2)\n\t\treturn -1;\n\n\t*value = Q_atoi(arg+1);\n\n\tswitch (arg[0]) {\n\t\tcase '=':\n\t\t\treturn DEMOSEEKINGSTATUS_MATCH_EQUAL;\n\t\tcase '<':\n\t\t\treturn DEMOSEEKINGSTATUS_MATCH_LESS_THAN;\n\t\tcase '>':\n\t\t\treturn DEMOSEEKINGSTATUS_MATCH_GREATER_THAN;\n\t\tdefault:\n\t\t\treturn -1;\n\t}\n}\n\n//\n// Jumps to a point in demo based on the status of player in POV\n//\nstatic void CL_Demo_Jump_Status_f (void)\n{\n\tint i;\n\tqbool or = false;\n\tdemoseekingstatus_condition_t *parent = NULL;\n\n\tif (Cmd_Argc() < 2) {\n\t\tCom_Printf(\"Usage: %s <conditions>\\n\", Cmd_Argv(0));\n\t\tCom_Printf(\"\\n\");\n\t\tCom_Printf(\"Skip forward in the demo until the conditions based on the player in POV are met.\\n\");\n\t\tCom_Printf(\"\\n\");\n\t\tCom_Printf(\"Valid conditions are:\\n\");\n\t\tCom_Printf(\"  Weapon, armor and powerup names (rl, ya, quad etc.), for items held\\n\");\n\t\tCom_Printf(\"  Weapon names with + in front (+rl), for the weapon in hand\\n\");\n\t\tCom_Printf(\"  Constraints on health, armor and ammo held with syntax id=value, id<value or id>value\\n\");\n\t\tCom_Printf(\"    The id can be h for health, a for armor, s for shells, n for nails, r for rockets or c for cells\\n\");\n\t\tCom_Printf(\"  All the conditions can be negated by inserting a ! character in front\\n\");\n\t\tCom_Printf(\"  Special value \\\"or\\\" can be used to connect two conditions requiring only one of them to be true\\n\");\n\t\tCom_Printf(\"\\n\");\n\t\tCom_Printf(\"Example: %s h<1 +rl or +lg\\n\", Cmd_Argv(0));\n\t\tCom_Printf(\"  Skip to the next position where player in the POV drops a RL or LG pack\\n\");\n\t\treturn;\n\t}\n\n\t// Cannot jump without playing demo.\n\tif (!cls.demoplayback) {\n\t\tCom_Printf(\"Error: not playing a demo\\n\");\n\t\treturn;\n\t}\n\n\t// Must be active to jump.\n\tif (cls.state < ca_active) {\n\t\tCom_Printf(\"Error: demo must be active first\\n\");\n\t\treturn;\n\t}\n\n\tcls.demoseekingstatus.non_matching_found = false;\n\tCL_Demo_Jump_Status_Free(cls.demoseekingstatus.conditions);\n\tcls.demoseekingstatus.conditions = NULL;\n\n\tfor (i = 1; i < Cmd_Argc(); i++) {\n\t\tdemoseekingstatus_condition_t *condition = NULL;\n\t\tqbool neg = false;\n\t\tchar *arg = Cmd_Argv(i);\n\t\tint weapon, value;\n\t\tint type;\n\n\t\tif (!strcasecmp(\"or\", arg)) {\n\t\t\tif (cls.demoseekingstatus.conditions == NULL) {\n\t\t\t\tCom_Printf(\"Error: or can't be the first argument\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tor = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (arg[0] == '!') {\n\t\t\tneg = true;\n\t\t\targ++;\n\t\t}\n\n\t\tif ((weapon = CL_Demo_Jump_Status_Parse_Weapon(arg)) != 0) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(DEMOSEEKINGSTATUS_MATCH_BIT_ON, STAT_ITEMS, weapon);\n\t\t} else if (arg[0] == '+' && (weapon = CL_Demo_Jump_Status_Parse_Weapon(arg+1)) != 0) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(DEMOSEEKINGSTATUS_MATCH_EQUAL, STAT_ACTIVEWEAPON, weapon);\n\t\t} else if (arg[0] == 'h' && (type = CL_Demo_Jump_Status_Parse_Constraint(arg+1, &value)) >= 0) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(type, STAT_HEALTH, value);\n\t\t} else if (arg[0] == 'a' && (type = CL_Demo_Jump_Status_Parse_Constraint(arg+1, &value)) >= 0) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(type, STAT_ARMOR, value);\n\t\t} else if (arg[0] == 's' && (type = CL_Demo_Jump_Status_Parse_Constraint(arg+1, &value)) >= 0) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(type, STAT_SHELLS, value);\n\t\t} else if (arg[0] == 'n' && (type = CL_Demo_Jump_Status_Parse_Constraint(arg+1, &value)) >= 0) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(type, STAT_NAILS, value);\n\t\t} else if (arg[0] == 'r' && (type = CL_Demo_Jump_Status_Parse_Constraint(arg+1, &value)) >= 0) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(type, STAT_ROCKETS, value);\n\t\t} else if (arg[0] == 'c' && (type = CL_Demo_Jump_Status_Parse_Constraint(arg+1, &value)) >= 0) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(type, STAT_CELLS, value);\n\t\t} else if (!strcasecmp(\"ga\", arg)) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(DEMOSEEKINGSTATUS_MATCH_BIT_ON, STAT_ITEMS, IT_ARMOR1);\n\t\t} else if (!strcasecmp(\"ya\", arg)) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(DEMOSEEKINGSTATUS_MATCH_BIT_ON, STAT_ITEMS, IT_ARMOR2);\n\t\t} else if (!strcasecmp(\"ra\", arg)) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(DEMOSEEKINGSTATUS_MATCH_BIT_ON, STAT_ITEMS, IT_ARMOR3);\n\t\t} else if (!strcasecmp(\"quad\", arg)) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(DEMOSEEKINGSTATUS_MATCH_BIT_ON, STAT_ITEMS, IT_QUAD);\n\t\t} else if (!strcasecmp(\"ring\", arg)) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(DEMOSEEKINGSTATUS_MATCH_BIT_ON, STAT_ITEMS, IT_INVISIBILITY);\n\t\t} else if (!strcasecmp(\"pent\", arg)) {\n\t\t\tcondition = CL_Demo_Jump_Status_Condition_New(DEMOSEEKINGSTATUS_MATCH_BIT_ON, STAT_ITEMS, IT_INVULNERABILITY);\n\t\t} else {\n\t\t\tCom_Printf(\"Error: unknown condition: %s\\n\", Cmd_Argv(i));\n\t\t\tCL_Demo_Jump_Status_Free(cls.demoseekingstatus.conditions);\n\t\t\tcls.demoseekingstatus.conditions = NULL;\n\t\t\treturn;\n\t\t}\n\n\t\tif (neg)\n\t\t\tCL_Demo_Jump_Status_Condition_Negate(condition);\n\n\t\tif (parent != NULL) {\n\t\t\tif (or) {\n\t\t\t\tparent->or = condition;\n\t\t\t} else {\n\t\t\t\tparent->and = condition;\n\t\t\t}\n\t\t} else {\n\t\t\tcls.demoseekingstatus.conditions = condition;\n\t\t}\n\t\tparent = condition;\n\t\tor = false;\n\t}\n\n\tCL_Demo_Jump(99999, 0, DST_SEEKING_STATUS);\n}\n\n\n//\n// Jumps to a specified time in a demo. Time specified in seconds.\n//\nvoid CL_Demo_Jump(double seconds, int relative, demoseekingtype_t seeking)\n{\n\t// Calculate the new demo time we want to jump to.\n\tdouble initialtime = (cls.nqdemoplayback ? cl.servertime : cls.demotime);\n\tdouble newdemotime = relative ? (initialtime + (relative * seconds)) : (demostarttime + seconds);\n\n\t// We need to rewind.\n\tif (newdemotime < initialtime)\n\t{\n\t\tcls.demo_rewindtime = newdemotime - demostarttime;\n\t}\n\n\t// Set the new demotime.\t\n\tcls.demotime = newdemotime;\n\n\tcls.demoseeking = seeking;\n\tCon_ClearNotify ();\n}\n\ndouble Demo_GetSpeed(void)\n{\n\tif (cls.mvdplayback == QTV_PLAYBACK) {\n\t\treturn qtv_demospeed;\n\t}\n\n\treturn bound(0, cl_demospeed.value, 20);\n}\n\nqbool qtv_playback_paused;\n\nvoid Demo_AdjustSpeed(void)\n{\n\tif (cls.mvdplayback == QTV_PLAYBACK) {\n\t\tif (qtv_playback_paused) {\n\t\t\tqtv_demospeed = 0;\n\t\t\treturn;\n\t\t}\n\n\t\tif (qtv_adjustbuffer.integer) {\n\t\t\tint\t\t\t\tms;\n\t\t\tdouble\t\t\tdemospeed, desired, current;\n\n\t\t\tDemo_BufferSize(&ms);\n\n\t\t\tdesired = max(0.5, QTVBUFFERTIME); // well, we need some reserve for adjusting\n\t\t\tcurrent = 0.001 * ms;\n\n\t\t\t// adjustbuffer 1 (original): adjustments are made based on % of buffer filled\n\t\t\tif (qtv_adjustbuffer.integer != 2) {\n\t\t\t\t// qqshka: this is linear version\n\t\t\t\tdemospeed = current / desired;\n\n\t\t\t\t// if you unwilling constant speed change, then you can set range with qtv_adjustlowstart and qtv_adjusthighstart\n\t\t\t\tif (demospeed > bound(0, qtv_adjustlowstart.value, 1) && demospeed < max(1, qtv_adjusthighstart.value)) {\n\t\t\t\t\tdemospeed = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// adjustments made to keep within 0.5 seconds of target buffertime\n\t\t\t\tfloat minimum = max(0.5, desired - 0.5);\n\t\t\t\tfloat maximum = minimum + 1.0;\n\t\t\t\tfloat diff;\n\n\t\t\t\tcurrent = bound(minimum, current, maximum);\n\n\t\t\t\t// Smootherstep\n\t\t\t\tif (current < desired) {\n\t\t\t\t\t// Slow down\n\t\t\t\t\tdiff = desired - current;\n\t\t\t\t\tdiff = diff * diff * diff * (diff * (diff * 6 - 15) + 10);\n\t\t\t\t\tdemospeed = 1 - diff;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Speed up\n\t\t\t\t\tdiff = current - desired;\n\t\t\t\t\tdiff = diff * diff * diff * (diff * (diff * 6 - 15) + 10);\n\t\t\t\t\tdemospeed = 1 + diff;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// bound demospeed\n\t\t\tdemospeed = bound(qtv_adjustminspeed.value, demospeed, qtv_adjustmaxspeed.value);\n\n\t\t\tqtv_demospeed = demospeed;\n\t\t\treturn;\n\t\t}\n\n\t\tif (!qtv_allow_pause.value) {\n\t\t\tqtv_demospeed = 1;\n\t\t\treturn;\n\t\t}\n\t}\n\t\n\tqtv_demospeed = bound(0, cl_demospeed.value, 20);\n}\n\n// \nvoid CL_QTVFixUser_f(void) {\n\tint uid, i;\n\tchar newuserinfo[MAX_INFO_STRING] = { 0 };\n\tchar* newName = NULL;\n\tchar* newTeam = NULL;\n\tint topcolor = 0;\n\tint bottomcolor = 0;\n\tqbool isSpectator = (Cmd_Argc() >= 3 && !strcmp(Cmd_Argv(2), \"spectator\"));\n\tqbool isPlayer = (Cmd_Argc() >= 3 && !strcmp(Cmd_Argv(2), \"player\"));\n\n\tif (!cls.demoplayback || cls.mvdplayback != QTV_PLAYBACK) {\n\t\tCom_Printf(\"Only valid when viewing QTV streams.\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() <= 3 || !(isSpectator || isPlayer)) {\n\t\tCom_Printf(\"Usage: %s <userid> <spectator | player> <name> [team] [topcolor] [bottomcolor]\\n\", Cmd_Argv(0));\n\t\tCom_Printf(\"This allows you to directly set user info fields on bugged QTV streams.\\n\");\n\t\tCom_Printf(\"------ ----- ----\\n\");\n\t\tCom_Printf(\"userid frags name\\n\");\n\t\tCom_Printf(\"------ ----- ----\\n\");\n\t\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\t\tif (cl.players[i].name[0] || cl.players[i].userinfo[0] || cl.players[i].frags) {\n\t\t\t\tCom_Printf(\"%6i %4i %s\\n\", cl.players[i].userid, cl.players[i].frags, cl.players[i].name);\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\tuid = atoi(Cmd_Argv(1));\n\tnewName = Cmd_Argv(3);\n\tnewTeam = Cmd_Argc() <= 4 ? \"\" : Cmd_Argv(4);\n\ttopcolor = Cmd_Argc() <= 5 ? 0 : atoi(Cmd_Argv(5));\n\tbottomcolor = Cmd_Argc() <= 6 ? topcolor : atoi(Cmd_Argv(6));\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!cl.players[i].name[0] && !cl.players[i].userinfo[0] && !cl.players[i].frags)\n\t\t\tcontinue;\n\n\t\tif (cl.players[i].userid == uid) {\n\t\t\tplayer_info_t* player = &cl.players[i];\n\n\t\t\tstrcpy(newuserinfo, \"\\\\*client\\\\QTVBug\");\n\t\t\tstrlcat(newuserinfo, \"\\\\name\\\\\", MAX_INFO_STRING);\n\t\t\tstrlcat(newuserinfo, newName, MAX_INFO_STRING);\n\n\t\t\tif (isSpectator) {\n\t\t\t\tstrlcat(newuserinfo, \"\\\\*spectator\\\\1\", MAX_INFO_STRING);\n\t\t\t}\n\n\t\t\tif (*newTeam) {\n\t\t\t\tstrlcat(newuserinfo, \"\\\\team\\\\\", MAX_INFO_STRING);\n\t\t\t\tstrlcat(newuserinfo, newTeam, MAX_INFO_STRING);\n\t\t\t}\n\n\t\t\tstrlcat(newuserinfo, \"\\\\topcolor\\\\\", MAX_INFO_STRING);\n\t\t\tstrlcat(newuserinfo, va(\"%d\", topcolor), MAX_INFO_STRING);\n\t\t\tstrlcat(newuserinfo, \"\\\\bottomcolor\\\\\", MAX_INFO_STRING);\n\t\t\tstrlcat(newuserinfo, va(\"%d\", bottomcolor), MAX_INFO_STRING);\n\n\t\t\t// Our scoreboard will set spectators to -999 frags for sorting, so reset and wait for server update\n\t\t\tif (player->spectator && player->frags == -999 && isPlayer)\n\t\t\t\tplayer->frags = 0;\n\n\t\t\tmemset(player->userinfo, 0, sizeof(player->userinfo));\n\t\t\tstrlcpy(player->userinfo, newuserinfo, MAX_INFO_STRING);\n\t\t\tCL_ProcessUserInfo(i, player, NULL);\n\t\t\treturn;\n\t\t}\n\t}\n\tCom_Printf(\"User not in server.\\n\");\n}\n\n//\n// Inits the demo cache and adds demo commands.\n//\nvoid CL_Demo_Init(void)\n{\n\tint parm, democache_size;\n\tbyte *democache_buffer;\n\n\t//\n\t// Init the demo cache if the user specified to use one.\n\t//\n\tdemocache_available = false;\n\tif ((parm = COM_CheckParm(cmdline_param_client_democache)) && parm + 1 < COM_Argc())\n\t{\n\t\tdemocache_size = Q_atoi(COM_Argv(parm + 1)) * 1024;\n\t\tdemocache_size = max(democache_size, DEMOCACHE_MINSIZE);\n\t\tif ((democache_buffer = (byte *) malloc (democache_size)))\n\t\t{\n\t\t\tCom_Printf_State (PRINT_OK, \"Democache initialized (%.1f MB)\\n\", (float) (democache_size) / (1024 * 1024));\n\t\t\tSZ_Init(&democache, democache_buffer, democache_size);\n\t\t\tdemocache_available = true;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCom_Printf_State (PRINT_FAIL, \"Democache allocation failed\\n\");\n\t\t}\n\t}\n\n\t//\n\t// Add demo commands.\n\t//\n\tCmd_AddCommand (\"record\", CL_Record_f);\n\tCmd_AddCommand (\"recordqwd\", CL_Record_f);\n\tCmd_AddCommand (\"stop\", CL_Stop_f);\n\tCmd_AddCommand (\"stopqwd\", CL_Stop_f);\n\tCmd_AddCommand (\"playdemo\", CL_Play_f);\n\tCmd_AddCommand (\"timedemo\", CL_TimeDemo_f);\n\tCmd_AddCommand (\"timedemo2\", CL_TimeDemo_f);\n\tCmd_AddCommand (\"easyrecord\", CL_EasyRecord_f);\n\n\tCmd_AddCommand(\"demo_setspeed\", CL_Demo_SetSpeed_f);\n\tCmd_AddCommand(\"demo_jump\", CL_Demo_Jump_f);\n\tCmd_AddCommand(\"demo_jump_mark\", CL_Demo_Jump_Mark_f);\n\tCmd_AddCommand(\"demo_jump_status\", CL_Demo_Jump_Status_f);\n\tCmd_AddCommand(\"demo_jump_end\", CL_Demo_Jump_End_f);\n\tCmd_AddCommand(\"demo_controls\", DemoControls_f);\n\n\t//\n\t// mvd \"recording\"\n\t//\n\tCmd_AddCommand(\"mvdrecord\", CL_RecordMvd_f);\n\tCmd_AddCommand(\"mvdstop\", CL_StopMvd_f);\n\n\t//\n\t// QTV commands.\n\t//\n\tCmd_AddCommand (\"qtvplay\", CL_QTVPlay_f);\n\tCmd_AddCommand (\"qtv_query_sourcelist\", CL_QTVList_f);\n\tCmd_AddCommand (\"qtv_query_demolist\", CL_QTVList_f);\n\tCmd_AddCommand (\"qtvreconnect\", CL_QTVReconnect_f);\n\tCmd_AddCommand (\"qtv_fixuser\", CL_QTVFixUser_f);\n\n\t// Macros\n\tCmd_AddMacro (macro_demoname, CL_Macro_DemoName_f);\n\tCmd_AddMacro (macro_demolength, CL_Macro_DemoLength_f);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_DEMO);\n\tCvar_Register(&demo_format);\n\tCvar_Register(&demo_dir);\n\tCvar_Register(&demo_benchmarkdumps);\n\tCvar_Register(&cl_startupdemo);\n\tCvar_Register(&demo_jump_rewind);\n\tCvar_Register(&cl_demo_qwd_delta);\n\tCvar_Register(&demo_jump_skip_messages);\n\n\tCvar_ResetCurrentGroup();\n}\n\nqbool CL_Demo_NotForTrackedPlayer(void)\n{\n\tint tracknum = Cam_TrackNum();\n\n\tif (cls.lasttype == dem_multiple && ((tracknum == -1) || !(cls.lastto & (1 << tracknum))))\n\t\treturn true;\n\tif (cls.lasttype == dem_single && ((tracknum == -1) || (cls.lastto != cl.spec_track)))\n\t\treturn true;\n\n\treturn false;\n}\n\nqbool CL_Demo_SkipMessage (qbool skip_if_seeking)\n{\n\tif (!cls.demoplayback)\n\t\treturn false;\n\tif (skip_if_seeking && cls.demoseeking)\n\t\treturn true;\n\tif (!cls.mvdplayback)\n\t\treturn false;\n\n\treturn CL_Demo_NotForTrackedPlayer();\n}\n\nqbool SCR_QTVBufferToBeDrawn(int options)\n{\n\treturn (options == 1 && cls.mvdplayback == QTV_PLAYBACK) || (options > 1 && cls.mvdplayback && !FSMMAP_IsMemoryMapped(playbackfile));\n}\n\nint Demo_BufferSize(int* ms)\n{\n\treturn ConsistantMVDDataEx(stream_buffer, stream_buffer_cnt, ms, 0);\n}\n"
  },
  {
    "path": "src/cl_ents.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"vx_stuff.h\"\n#include \"pmove.h\"\n#include \"utils.h\"\n#include \"qmb_particles.h\"\n#include \"rulesets.h\"\n#include \"teamplay.h\"\n\nstatic int MVD_TranslateFlags(int src);\nvoid TP_ParsePlayerInfo(player_state_t *, player_state_t *, player_info_t *info);\t\n\nextern cvar_t cl_predict_players, cl_solid_players, cl_rocket2grenade;\nextern cvar_t cl_predict_half;\nextern cvar_t cl_model_bobbing;\t\t\nextern cvar_t cl_nolerp, cl_lerp_monsters, cl_newlerp;\nextern cvar_t r_drawvweps;\t\t\nextern  unsigned int     cl_dlight_active[MAX_DLIGHTS/32];       \n\nstatic struct predicted_player {\n\tint flags;\n\tqbool active;\n\tvec3_t origin;\t// predicted origin\n\n\tqbool predict;\n\tvec3_t\toldo;\n\tvec3_t\tolda;\n\tvec3_t\toldv;\n\tplayer_state_t *oldstate;\n\n\tqbool drawn;\n\tvec3_t drawn_origin;\n\tint msec;\n\tfloat paused_sec;\n} predicted_players[MAX_CLIENTS];\n\nchar *cl_modelnames[cl_num_modelindices];\nint cl_modelindices[cl_num_modelindices];\n\nvoid CL_InitEnts(void) {\n\tint i;\n\n\tmemset(cl_modelnames, 0, sizeof(cl_modelnames));\n\n\tcl_modelnames[mi_spike] = \"progs/spike.mdl\";\n\tcl_modelnames[mi_player] = \"progs/player.mdl\";\n\tcl_modelnames[mi_eyes] = \"progs/eyes.mdl\";\n\tcl_modelnames[mi_flag] = \"progs/flag.mdl\";\n\tcl_modelnames[mi_tf_flag] = \"progs/tf_flag.mdl\";\n\tcl_modelnames[mi_tf_stan] = \"progs/tf_stan.mdl\";\n\tcl_modelnames[mi_stag] = \"progs/stag.mdl\";\n\tcl_modelnames[mi_basrkey] = \"progs/basrkey.bsp\";\n\tcl_modelnames[mi_basbkey] = \"progs/basbkey.bsp\";\n\tcl_modelnames[mi_w_s_key] = \"progs/w_s_key.mdl\";\n\tcl_modelnames[mi_w_g_key] = \"progs/w_g_key.mdl\";\n\tcl_modelnames[mi_b_g_key] = \"progs/b_g_key.mdl\";\n\tcl_modelnames[mi_b_s_key] = \"progs/b_s_key.mdl\";\n\tcl_modelnames[mi_ff_flag] = \"progs/ff_flag.mdl\";\n\tcl_modelnames[mi_harbflag] = \"progs/harbflag.mdl\";\n\tcl_modelnames[mi_princess] = \"progs/princess.mdl\";\n\tcl_modelnames[mi_explod1] = \"progs/s_explod.spr\";\n\tcl_modelnames[mi_explod2] = \"progs/s_expl.spr\";\n\tcl_modelnames[mi_h_player] = \"progs/h_player.mdl\";\n\tcl_modelnames[mi_gib1] = \"progs/gib1.mdl\";\n\tcl_modelnames[mi_gib2] = \"progs/gib2.mdl\";\n\tcl_modelnames[mi_gib3] = \"progs/gib3.mdl\";\n\tcl_modelnames[mi_rocket] = \"progs/missile.mdl\";\n\tcl_modelnames[mi_grenade] = \"progs/grenade.mdl\";\n\tcl_modelnames[mi_bubble] = \"progs/s_bubble.spr\";\n\tcl_modelnames[mi_flame] = \"progs/flame.mdl\";\t//joe\n\n\t// carried weapon models (drawviewmodel)\n\tcl_modelnames[mi_weapon1] = \"progs/v_axe.mdl\";\n\tcl_modelnames[mi_weapon2] = \"progs/v_shot.mdl\";\n\tcl_modelnames[mi_weapon3] = \"progs/v_shot2.mdl\";\n\tcl_modelnames[mi_weapon4] = \"progs/v_nail.mdl\";\n\tcl_modelnames[mi_weapon5] = \"progs/v_nail2.mdl\";\n\tcl_modelnames[mi_weapon6] = \"progs/v_rock.mdl\";\n\tcl_modelnames[mi_weapon7] = \"progs/v_rock2.mdl\";\n\tcl_modelnames[mi_weapon8] = \"progs/v_light.mdl\";\n\n\tcl_modelnames[mi_vaxe] = \"progs/v_axe.mdl\";\n\tcl_modelnames[mi_vbio] = \"progs/v_bio.mdl\";\n\tcl_modelnames[mi_vgrap] = \"progs/v_grap.mdl\";\n\tcl_modelnames[mi_vknife] = \"progs/v_knife.mdl\";\n\tcl_modelnames[mi_vknife2] = \"progs/v_knife2.mdl\";\n\tcl_modelnames[mi_vmedi] = \"progs/v_medi.mdl\";\n\tcl_modelnames[mi_vspan] = \"progs/v_span.mdl\";\n\t// monsters\n\tcl_modelnames[mi_monster1] = \"progs/soldier.mdl\";\n\tcl_modelnames[mi_m2] = \"progs/dog.mdl\";\n\tcl_modelnames[mi_m3] = \"progs/demon.mdl\";\n\tcl_modelnames[mi_m4] = \"progs/ogre.mdl\";\n\tcl_modelnames[mi_m5] = \"progs/shambler.mdl\";\n\tcl_modelnames[mi_m6] = \"progs/knight.mdl\";\n\tcl_modelnames[mi_m7] = \"progs/zombie.mdl\";\n\tcl_modelnames[mi_m8] = \"progs/wizard.mdl\";\n\tcl_modelnames[mi_m9] = \"progs/enforcer.mdl\";\n\tcl_modelnames[mi_m10] = \"progs/fish.mdl\";\n\tcl_modelnames[mi_m11] = \"progs/hknight.mdl\";\n\tcl_modelnames[mi_m12] = \"progs/shalrath.mdl\";\n\tcl_modelnames[mi_m13] = \"progs/tarbaby\";\n\t// hipnotic monsters\n\tcl_modelnames[mi_m14] = \"progs/armabody.mdl\";\n\tcl_modelnames[mi_m15] = \"progs/armalegs.mdl\";\n\tcl_modelnames[mi_m16] = \"progs/grem.mdl\";\n\tcl_modelnames[mi_m17] = \"progs/scor.mdl\";\n\n\t// Item sprites.\n\t// FIXME : 32-bit sprites not working properly.\n\t#if 0\n\tcl_modelnames[mi_2dshells]\t\t= \"sprites/s_shells.spr\";\n\tcl_modelnames[mi_2dcells]\t\t= \"sprites/s_cells.spr\";\n\tcl_modelnames[mi_2drockets]\t\t= \"sprites/s_rockets.spr\";\n\tcl_modelnames[mi_2dnails]\t\t= \"sprites/s_nails.spr\";\n\tcl_modelnames[mi_2dmega]\t\t= \"sprites/s_mega.spr\";\n\tcl_modelnames[mi_2dpent]\t\t= \"sprites/s_invuln.spr\";\n\tcl_modelnames[mi_2dquad]\t\t= \"sprites/s_quad.spr\";\n\tcl_modelnames[mi_2dring]\t\t= \"sprites/s_invis.spr\";\n\tcl_modelnames[mi_2dsuit]\t\t= \"sprites/s_suit.spr\";\n\tcl_modelnames[mi_2darmor1]\t\t= \"sprites/s_armor1.spr\";\n\tcl_modelnames[mi_2darmor2]\t\t= \"sprites/s_armor2.spr\";\n\tcl_modelnames[mi_2darmor3]\t\t= \"sprites/s_armor3.spr\";\n\tcl_modelnames[mi_2dbackpack]\t= \"sprites/s_backpack.spr\";\n\tcl_modelnames[mi_2dhealth10]\t= \"sprites/s_health10.spr\";\n\tcl_modelnames[mi_2dhealth25]\t= \"sprites/s_health25.spr\";\n\t#endif\n\n\tfor (i = 0; i < cl_num_modelindices; i++) {\n\t\tif (!cl_modelnames[i]) {\n\t\t\tSys_Error(\"cl_modelnames[%d] not initialized\", i);\n\t\t}\n\t}\n\n\tCL_ClearScene();\n}\n\nstatic qbool is_monster (int modelindex)\n{\n\tint i;\n\tif (!cl_lerp_monsters.value)\n\t\treturn false;\n\tfor (i = 1; i < 17; i++)\n\t\tif (modelindex == cl_modelindices[mi_monster1 + i - 1])\n\t\t\treturn true;\n\treturn false;\n}\n\nvoid CL_ClearScene(void) {\n\tif (cl_visents.count > 0)\n\t{\n\t\tmemset(cl_visents.list, 0, sizeof(cl_visents.list[0]) * cl_visents.count);\n\t}\n\tmemset(cl_visents.typecount, 0, sizeof(cl_visents.typecount));\n\tcl_visents.count = 0;\n}\n\nvoid CL_AddEntityToList(visentlist_t* list, visentlist_entrytype_t vistype, entity_t* ent, modtype_t type, qbool shell)\n{\n\textern cvar_t gl_outline;\n\n\tif (list->count < sizeof(list->list) / sizeof(list->list[0])) {\n\t\tlist->list[cl_visents.count].ent = *ent;\n\n\t\tent = &list->list[cl_visents.count].ent;\n\t\tlist->list[cl_visents.count].type = type;\n\t\tif (vistype == visent_alpha) {\n\t\t\t// Sort transparent entities based on closest point for stable back-to-front rendering.\n\t\t\tvec3_t distance;\n\t\t\tint i;\n\t\t\tfor (i = 0; i < 3; i++) {\n\t\t\t\tdistance[i] = r_refdef.vieworg[i] - ent->origin[i];\n\t\t\t\tdistance[i] -= bound(ent->model->mins[i], distance[i], ent->model->maxs[i]);\n\t\t\t}\n\t\t\tlist->list[cl_visents.count].distance = DotProduct(distance, distance);\n\t\t} else {\n\t\t\tlist->list[cl_visents.count].distance = VectorDistanceQuick(cl.simorg, ent->origin);\n\t\t}\n\t\tlist->list[cl_visents.count].draw[vistype] = true;\n\n\t\tent->outlineScale = 0.5f * (r_refdef2.outlineBase + DotProduct(ent->origin, r_refdef2.outline_vpn));\n\t\tent->outlineScale = bound(ent->outlineScale, 0, 2);\n\n\t\t++list->typecount[vistype];\n\t\tif (shell) {\n\t\t\tlist->list[cl_visents.count].draw[visent_shells] = true;\n\t\t\t++list->typecount[visent_shells];\n\t\t}\n\t\t// Check for outline on models.\n\t\t// We don't support outline for transparent models,\n\t\t// and we also check for ruleset, since we don't want outline on eyes.\n\t\tif (((gl_outline.integer & 1) && !RuleSets_DisallowModelOutline(ent->model))) {\n\t\t\tlist->list[cl_visents.count].draw[visent_outlines] = true;\n\t\t\t++list->typecount[visent_outlines];\n\t\t}\n\n\t\t++list->count;\n\t}\n}\n\nvoid CL_AddEntity(entity_t *ent)\n{\n\textern qbool R_CanDrawSimpleItem(entity_t* ent);\n\tvisentlist_entrytype_t vistype;\n\tmodtype_t type = ent->model->type;\n\tqbool shell = false;\n\textern cvar_t gl_powerupshells;\n\n\tif ((ent->effects & (EF_BLUE | EF_RED | EF_GREEN)) && bound(0, gl_powerupshells.value, 1)) {\n\t\tif (R_CanDrawSimpleItem(ent)) {\n\t\t\tvistype = visent_alpha;\n\t\t\ttype = mod_sprite;\n\t\t}\n\t\telse {\n\t\t\tvistype = visent_normal;\n\t\t\tshell = true;\n\t\t}\n\t}\n\telse if (ent->renderfx & RF_NORMALENT) {\n\t\tvistype = visent_normal;\n\t}\n\telse if (ent->model->type == mod_sprite || R_CanDrawSimpleItem(ent)) {\n\t\tvistype = visent_alpha;\n\t\ttype = mod_sprite;\n\t}\n\telse if (ent->model->modhint == MOD_PLAYER || ent->model->modhint == MOD_EYES || ent->renderfx & RF_PLAYERMODEL) {\n\t\tvistype = visent_firstpass;\n\t\tent->renderfx |= RF_NOSHADOW;\n\t}\n\telse if (ent->alpha > 0.0f && ent->alpha < 1.0f) {\n\t\tvistype = visent_alpha;\n\t}\n\telse {\n\t\tvistype = visent_normal;\n\t}\n\n\tCL_AddEntityToList(&cl_visents, vistype, ent, type, shell);\n}\n\n// NUM_DLIGHTTYPES - this constant not used here, but help u find dynamic light related code if u change something\nstatic dlighttype_t dl_colors[] = {lt_red, lt_blue, lt_redblue, lt_green, lt_redgreen, lt_bluegreen, lt_white};\nstatic int dl_colors_cnt = sizeof(dl_colors) / sizeof(dl_colors[0]);\n\ndlighttype_t dlightColor(float f, dlighttype_t def, qbool random) {\n\n\tif ((int) f >= 1 && (int) f <= dl_colors_cnt)\n\t\treturn dl_colors[(int) f - 1];\n\telse if (((int) f == dl_colors_cnt + 1) && random)\n\t\treturn dl_colors[rand() % dl_colors_cnt];\n\telse\n\t\treturn def;\n}\n\n// if we have color in \"r g b\" format use it overwise use pre defined colors\ncustomlight_t *dlightColorEx(float f, char *str, dlighttype_t def, qbool random, customlight_t *l) \n{\n\t// TODO : Ok to use this in software also?\n\tbyte color[4];\n\tint i;\n\n\ti = StringToRGB_W(str, color);\n\n\tif (i > 1)\n\t\tl->type = lt_custom; // we got at least two params so treat this as custom color\n\telse\n\t\tl->type = dlightColor(f, def, random); // this may set lt_custom too\n\n\tif (l->type == lt_custom)\n\t\tfor (i = 0; i < 3; i++)\n\t\t\tl->color[i] = min(128, color[i]); // i've seen color set in float form to 0.5 maximum and to 128 in byte form, so keep this tradition even i'm do not understand why they do so\n\tl->alpha = color[3];\n\n\treturn l;\n}\n\ndlight_t *CL_AllocDlight(int key)\n{\n\tunsigned int i;\n\tunsigned int j;\n\tdlight_t *dl;\n\n\t// first look for an exact key match\n\tif (key) {\n\t\tdl = cl_dlights;\n\t\tfor (i = 0; i < MAX_DLIGHTS; i++, dl++) {\n\t\t\tif (dl->key == key) {\n\t\t\t\tmemset (dl, 0, sizeof(*dl));\n\t\t\t\tdl->key = key;\n\t\t\t\tcl_dlight_active[i/32] |= (1<<(i%32));\n\t\t\t\treturn dl;\n\t\t\t}\n\t\t}\n\t}\n\n\t// then look for anything else\n\tfor (i = 0; i < MAX_DLIGHTS/32; i++) {\n\t\tif (cl_dlight_active[i] != 0xffffffff) {\n\t\t\tfor (j = 0; j < 32; j++) {\n\t\t\t\tif (!(cl_dlight_active[i] & (1<<j)) && i*32+j < MAX_DLIGHTS) {\n\t\t\t\t\tdl = cl_dlights + i * 32 + j;\n\t\t\t\t\tmemset(dl, 0, sizeof(*dl));\n\t\t\t\t\tdl->key = key;\n\t\t\t\t\tcl_dlight_active[i] |= 1<<j;\n\t\t\t\t\treturn dl;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tdl = &cl_dlights[0];\n\tmemset (dl, 0, sizeof(*dl));\n\tdl->key = key;\n\tcl_dlight_active[0] |= 1;\n\n\treturn dl;\n}\n\n\nvoid CL_NewDlight (int key, vec3_t origin, float radius, float time, dlighttype_t type, int bubble) {\n\tdlight_t *dl;\n\n\tdl = CL_AllocDlight (key);\n\tVectorCopy (origin, dl->origin);\n\tdl->radius = radius;\n\tdl->die = cl.time + time;\n\tdl->type = type;\n\tdl->bubble = bubble;\t\t\n}\n\nvoid CL_NewDlightEx (int key, vec3_t origin, float radius, float time, customlight_t *l, int bubble) {\n// same as CL_NewDlight {\n\tdlight_t *dl;\n\n\tdl = CL_AllocDlight (key);\n\tVectorCopy (origin, dl->origin);\n\tdl->radius = radius;\n\tdl->die = cl.time + time;\n\tdl->type = l->type; // <= only here difference\n\tdl->bubble = bubble;\n// }\n\n\t// and GL addon for custom color\n\tif (dl->type == lt_custom)\n\t\tVectorCopy(l->color, dl->color);\n}\n\nvoid CL_DecayLights(void)\n{\n\tunsigned int i;\n\tunsigned int j;\n\tdlight_t *dl;\n\textern cvar_t r_lightdecayrate;\n\n\tif (cls.state < ca_active) {\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < MAX_DLIGHTS/32; i++) {\n\t\tif (cl_dlight_active[i]) {\n\t\t\tfor (j = 0; j < 32; j++) {\n\t\t\t\tif ((cl_dlight_active[i]&(1<<j)) && i*32+j < MAX_DLIGHTS) {\n\t\t\t\t\tdl = cl_dlights + i*32 + j;\n\n\t\t\t\t\tif (dl->die < cl.time) {\n\t\t\t\t\t\tcl_dlight_active[i] &= ~(1 << j);\n\t\t\t\t\t\tdl->radius = 0;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tdl->radius -= max(r_lightdecayrate.value, 1) * cls.frametime * dl->decay;\n\n\t\t\t\t\tif (dl->radius <= 0) {\n\t\t\t\t\t\tcl_dlight_active[i] &= ~(1 << j);\n\t\t\t\t\t\tdl->radius = 0;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nqbool NewLerp_AbleModel(int idx)\n{\n\treturn !cls.demoplayback && cl_newlerp.value && \n\t\t\t(idx == cl_modelindices[mi_rocket] || idx == cl_modelindices[mi_grenade] || idx == cl_modelindices[mi_spike]);\n}\n\nvoid CL_SetupPacketEntity (int number, entity_state_t *state, qbool changed) {\n\tcentity_t *cent;\n\tint t;\n\n\tcent = &cl_entities[number];\n\n\tif (!cl.oldvalidsequence || cl.oldvalidsequence != cent->sequence ||\n\t\tstate->modelindex != cent->current.modelindex ||\n\t\t!VectorL2Compare(state->origin, cent->current.origin, 200)\n\t) {\n\t\tcent->startlerp = cl.time;\n\t\tcent->deltalerp = -1;\n\t\tcent->frametime = -1;\n\t\tcent->oldsequence = 0;\n\t\tcent->trail_number = (cent->trail_number + 1) % 1000;\n\t\tfor (t = 0; t < sizeof(cent->trails) / sizeof(cent->trails[0]); ++t) {\n\t\t\tcent->trails[t].lasttime = 0;\n\t\t\tVectorCopy(state->origin, cent->trails[t].stop);\n\t\t}\n\t}\n\telse {\n\t\tcent->oldsequence = cent->sequence;\n\t\t\n\t\tif (state->frame != cent->current.frame) {\n\t\t\tcent->frametime = cl.time;\n\t\t\tcent->oldframe = cent->current.frame;\n\t\t}\n\t\t\n\t\tif (changed) {\n\t\t\tif (!VectorCompare(state->origin, cent->current.origin) || !VectorCompare(state->angles, cent->current.angles)) {\n\t\t\t    if (NewLerp_AbleModel(state->modelindex)) {\n\t\t\t    \tfloat s  = (cls.mvdplayback ? nextdemotime - olddemotime : cl.time - cent->startlerp); // time delta\n\t\t\t    \tfloat s2 = 1.0 / (s ? s : 1); // here no function which divide vector by X value, so we multiplay vector by 1/X\n\t\t\t    \tvec3_t tmp, tmp2;\n\t\t\t\t\tVectorSubtract(state->origin, cent->current.origin, tmp); // origin delta, also move direction\n\t\t\t\t\tVectorScale(tmp, s2, tmp2); // we divide origin delta by time delta, that velocity in other words\n\n\t\t\t\t\tif (cent->deltalerp == -1) {\n\t\t\t\t\t\tVectorCopy(tmp2, cent->velocity); // we got first velocity for our enitity, just save it\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tVectorAdd(tmp2, cent->velocity, cent->velocity);\n\t\t\t\t\t\tVectorScale(cent->velocity, 0.5, cent->velocity); // (previous velocity + current) / 2, we got average velocity\n\t\t\t\t\t}\n\n//\t\t\t\t\tCom_Printf(\"v: %6.1f %6.1f %6.1f %f %f\\n\", VectorLength(tmp), VectorLength(tmp2), VectorLength(cent->velocity), s2, s);\n\n\t\t\t\t\tif (cent->deltalerp == -1) { // seems we get second update for enitity from server, just save some vectors\n\t\t\t\t\t\tVectorCopy(cent->current.origin, cent->old_origin);\n\t\t\t\t\t\tVectorCopy(cent->current.origin, cent->lerp_origin);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\t// its correction due to round/interploation, we have our own vision where object, and server vision,\n\t\t\t\t\t\t// use 90% of our vision and 10% of server, that make things smooth and somehow accurate\n\t\t\t\t\t\tVectorInterpolate(cent->lerp_origin, cl_newlerp.value, cent->current.origin, cent->old_origin);\n\t\t\t\t\t}\n\t\t\t    }\n\t\t\t\telse \n\t\t\t\t{\n\t\t\t\t\tVectorCopy(cent->current.origin, cent->old_origin);\n\t\t\t\t}\n\n\t\t\t\tVectorCopy(cent->current.angles, cent->old_angles);\n\t\t\t\tif (cls.mvdplayback) {\n\t\t\t\t\tcent->deltalerp = nextdemotime - olddemotime;\n\t\t\t\t\tcent->startlerp = cls.demotime;\n\t\t\t\t} else {\n\t\t\t\t\tcent->deltalerp = cl.time - cent->startlerp;\n\t\t\t\t\tcent->startlerp = cl.time;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n \n\tcent->current = *state;\n\tcent->sequence = cl.validsequence;\n}\n\n//Can go from either a baseline or a previous packet_entity\nvoid CL_ParseDelta (entity_state_t *from, entity_state_t *to, int bits) {\n\tint i;\n#ifdef PROTOCOL_VERSION_FTE\n\tint morebits;\n#endif\n\n\t// set everything to the state we are delta'ing from\n\t*to = *from;\n\n\tto->number = bits & 511;\n\tbits &= ~511;\n\n\tif (bits & U_MOREBITS) {\t// read in the low order bits\n\t\ti = MSG_ReadByte ();\n\t\tbits |= i;\n\t}\n\n#ifdef PROTOCOL_VERSION_FTE\n\tif (bits & U_FTE_EVENMORE && cls.fteprotocolextensions) {\n\t\tmorebits = MSG_ReadByte ();\n\t\tif (morebits & U_FTE_YETMORE)\n\t\t\tmorebits |= MSG_ReadByte()<<8;\n\t} else {\n\t\tmorebits = 0;\n\t}\n#endif\n\n\tto->flags = bits;\n\tif (bits & U_MODEL) {\n\t\tto->modelindex = MSG_ReadByte();\n#ifdef FTE_PEXT_MODELDBL\n\t\tif (morebits & U_FTE_MODELDBL) {\n\t\t\tto->modelindex += 256;\n\t\t}\n\t} else if (morebits & U_FTE_MODELDBL) {\n\t\tto->modelindex = MSG_ReadShort();\n#endif\n\t}\n\n\tif (bits & U_FRAME)\n\t\tto->frame = MSG_ReadByte ();\n\n\tif (bits & U_COLORMAP)\n\t\tto->colormap = MSG_ReadByte();\n\n\tif (bits & U_SKIN)\n\t\tto->skinnum = MSG_ReadByte();\n\n\tif (bits & U_EFFECTS)\n\t\tto->effects = MSG_ReadByte();\n\n\tif (bits & U_ORIGIN1) {\n\t\tif (cls.mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) {\n\t\t\tto->origin[0] = MSG_ReadFloatCoord();\n\t\t}\n\t\telse {\n\t\t\tto->origin[0] = MSG_ReadCoord();\n\t\t}\n\t}\n\n\tif (bits & U_ANGLE1)\n\t\tto->angles[0] = MSG_ReadAngle();\n\n\tif (bits & U_ORIGIN2) {\n\t\tif (cls.mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) {\n\t\t\tto->origin[1] = MSG_ReadFloatCoord();\n\t\t}\n\t\telse {\n\t\t\tto->origin[1] = MSG_ReadCoord();\n\t\t}\n\t}\n\n\tif (bits & U_ANGLE2)\n\t\tto->angles[1] = MSG_ReadAngle();\n\n\tif (bits & U_ORIGIN3) {\n\t\tif (cls.mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) {\n\t\t\tto->origin[2] = MSG_ReadFloatCoord();\n\t\t}\n\t\telse {\n\t\t\tto->origin[2] = MSG_ReadCoord();\n\t\t}\n\t}\n\n\tif (bits & U_ANGLE3)\n\t\tto->angles[2] = MSG_ReadAngle();\n\n\tif (bits & U_SOLID) {\n\t\t// FIXME\n\t}\n\n#ifdef PROTOCOL_VERSION_FTE\n#ifdef FTE_PEXT_TRANS\n\tif (morebits & U_FTE_TRANS && cls.fteprotocolextensions & FTE_PEXT_TRANS) {\n\t\tto->trans = MSG_ReadByte();\n\t}\n#endif\n\n#ifdef FTE_PEXT_COLOURMOD\n\tif ((morebits & U_FTE_COLOURMOD) && (cls.fteprotocolextensions & FTE_PEXT_COLOURMOD))\n\t{\n\t\tto->colourmod[0] = MSG_ReadByte();\n\t\tto->colourmod[1] = MSG_ReadByte();\n\t\tto->colourmod[2] = MSG_ReadByte();\n\t}\n#endif\n\n#ifdef FTE_PEXT_ENTITYDBL\n\tif (morebits & U_FTE_ENTITYDBL) {\n\t\tto->number += 512;\n\t}\n#endif\n#ifdef FTE_PEXT_ENTITYDBL2\n\tif (morebits & U_FTE_ENTITYDBL2) {\n\t\tto->number += 1024;\n\t}\n#endif\n#endif\n}\n\nvoid FlushEntityPacket (void) {\n\tint word;\n\tentity_state_t olde, newe;\n\n\tCom_DPrintf (\"FlushEntityPacket\\n\");\n\n\tmemset (&olde, 0, sizeof(olde));\n\n\tcl.delta_sequence = 0;\n\tcl.frames[cls.netchan.incoming_sequence & UPDATE_MASK].invalid = true;\n\n\t// read it all, but ignore it\n\twhile (1) {\n\t\tword = (unsigned short) MSG_ReadShort ();\n\t\tif (msg_badread) {\t// something didn't parse right...\n\t\t\tHost_Error (\"msg_badread in packetentities\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (!word)\n\t\t\tbreak;\t// done\n\n\t\tCL_ParseDelta (&olde, &newe, word);\n\t}\n}\n\n// An svc_packetentities has just been parsed, deal with the rest of the data stream.\nvoid CL_ParsePacketEntities (qbool delta) \n{\n\tint oldpacket, newpacket, oldindex, newindex, word, newnum, oldnum;\n\tpacket_entities_t *oldp, *newp, dummy;\n\tqbool full;\n\tbyte from;\n\tint maxentities = MAX_MVD_PACKET_ENTITIES; // allow as many as we can handle\n\tqbool copy = (cls.netchan.incoming_sequence == 0 && cls.mvdplayback);\n\n\tnewpacket = cls.netchan.incoming_sequence & UPDATE_MASK;\n\tnewp = &cl.frames[newpacket].packet_entities;\n\tcl.frames[newpacket].invalid = false;\n\tcl.frames[newpacket].in_qwd = false;\n\n\tif (delta) \n\t{\n\t\tfrom = MSG_ReadByte ();\n\n\t\toldpacket = cl.frames[newpacket].delta_sequence;\n\t\tif (cls.mvdplayback) {\n\t\t\tfrom = oldpacket = cls.netchan.incoming_sequence - 1;\n\t\t}\n\n\t\tif (cls.netchan.outgoing_sequence - cls.netchan.incoming_sequence >= UPDATE_BACKUP - 1) \n\t\t{\n\t\t\t// There are no valid frames left, so drop it.\n\t\t\tFlushEntityPacket();\n\t\t\tcl.validsequence = 0;\n\t\t\treturn;\n\t\t}\n\n\t\tif ((from & UPDATE_MASK) != (oldpacket & UPDATE_MASK)) \n\t\t{\n\t\t\tCom_DPrintf (\"WARNING: from mismatch (%d vs %d, %d vs %d)\\n\", (from & UPDATE_MASK), (oldpacket & UPDATE_MASK), from, oldpacket);\n\t\t\tFlushEntityPacket();\n\t\t\tcl.validsequence = 0;\n\t\t\treturn;\n\t\t}\n\n\t\tif (cls.netchan.outgoing_sequence - oldpacket >= UPDATE_BACKUP - 1) \n\t\t{\n\t\t\t// We can't use this, it is too old.\n\t\t\tFlushEntityPacket ();\n\t\t\t// Don't clear cl.validsequence, so that frames can still be rendered;\n\t\t\t// it is possible that a fresh packet will be received before\n\t\t\t// (outgoing_sequence - incoming_sequence) exceeds UPDATE_BACKUP - 1\n\t\t\treturn;\n\t\t}\n\n\t\toldp = &cl.frames[oldpacket & UPDATE_MASK].packet_entities;\n\t\tfull = false;\n\t} \n\telse \n\t{\n\t\t// This is a full update that we can start delta compressing from now.\n\t\toldp = &dummy;\n\t\tdummy.num_entities = 0;\n\t\tfull = true;\n\t}\n\n\tcl.oldvalidsequence = cl.validsequence;\n\tcl.validsequence = cls.netchan.incoming_sequence;\n\tcl.delta_sequence = cl.validsequence;\n\n\toldindex = 0;\n\tnewindex = 0;\n\tnewp->num_entities = 0;\n\n\twhile (1) \n\t{\n\t\tword = (unsigned short) MSG_ReadShort ();\n\t\tif (msg_badread)\n\t\t{\n\t\t\t// something didn't parse right...\n\t\t\tHost_Error (\"msg_badread in packetentities\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (!word) \n\t\t{\n\t\t\twhile (oldindex < oldp->num_entities) \n\t\t\t{\n\t\t\t\t// Copy all the rest of the entities from the old packet\n\t\t\t\t\n\t\t\t\tif (newindex >= maxentities)\n\t\t\t\t\tHost_Error (\"CL_ParsePacketEntities: newindex == MAX_PACKET_ENTITIES\");\n\n\t\t\t\tnewp->entities[newindex] = oldp->entities[oldindex];\n\t\t\t\tCL_SetupPacketEntity(newp->entities[newindex].number, &newp->entities[newindex], false);\n\t\t\t\tnewindex++;\n\t\t\t\toldindex++;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tnewnum = word & 511;\n\t\n\t\t#ifdef PROTOCOL_VERSION_FTE\n\t\tif (word & U_MOREBITS && (cls.fteprotocolextensions & FTE_PEXT_ENTITYDBL))\n\t\t{\n\t\t\t// Fte extensions for huge entity counts\n\t\t\tint oldpos = msg_readcount;\n\t\t\tint excessive;\n\t\t\texcessive = MSG_ReadByte();\n\t\t\t\n\t\t\tif (excessive & U_FTE_EVENMORE) \n\t\t\t{\n\t\t\t\texcessive = MSG_ReadByte();\n\t\t\t\t#ifdef FTE_PEXT_ENTITYDBL\n\t\t\t\tif (excessive & U_FTE_ENTITYDBL)\n\t\t\t\t\tnewnum += 512;\n\t\t\t\t#endif // FTE_PEXT_ENTITYDBL\n\t\t\t\t\n\t\t\t\t#ifdef FTE_PEXT_ENTITYDBL2\n\t\t\t\tif (excessive & U_FTE_ENTITYDBL2)\n\t\t\t\t\tnewnum += 1024;\n\t\t\t\t#endif // FTE_PEXT_ENTITYDBL2\n\t\t\t}\n\n\t\t\tmsg_readcount = oldpos; // undo the read...\n\t\t}\n\t\t#endif // PROTOCOL_VERSION_FTE\n\n\t\toldnum = oldindex >= oldp->num_entities ? 9999 : oldp->entities[oldindex].number;\n\n\t\twhile (newnum > oldnum)\n\t\t{\n\t\t\tif (full) \n\t\t\t{\n\t\t\t\tCom_Printf (\"WARNING: oldcopy on full update\");\n\t\t\t\tFlushEntityPacket ();\n\t\t\t\tcl.validsequence = 0;\t// can't render a frame\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Copy one of the old entities over to the new packet unchanged\n\t\t\tif (newindex >= maxentities)\n\t\t\t\tHost_Error (\"CL_ParsePacketEntities: newindex == MAX_PACKET_ENTITIES\");\n\n\t\t\tnewp->entities[newindex] = oldp->entities[oldindex];\n\t\t\tCL_SetupPacketEntity(oldnum, &newp->entities[newindex], word > 511);\n\t\t\tnewindex++;\n\t\t\toldindex++;\n\t\t\toldnum = oldindex >= oldp->num_entities ? 9999 : oldp->entities[oldindex].number;\n\t\t}\n\n\t\tif (newnum < oldnum) \n\t\t{\n\t\t\t// New from baseline\n\n\t\t\tif (word & U_REMOVE) \n\t\t\t{\n\t\t\t\t#ifdef PROTOCOL_VERSION_FTE\n\t\t\t\tif (word & U_MOREBITS && (cls.fteprotocolextensions & FTE_PEXT_ENTITYDBL))\n\t\t\t\t{\n\t\t\t\t\tif (MSG_ReadByte() & U_FTE_EVENMORE)\n\t\t\t\t\t\tMSG_ReadByte();\n\t\t\t\t}\n\t\t\t\t#endif // PROTOCOL_VERSION_FTE\n\n\t\t\t\tif (full) \n\t\t\t\t{\n\t\t\t\t\tCom_Printf (\"WARNING: U_REMOVE on full update\\n\");\n\t\t\t\t\tFlushEntityPacket ();\n\t\t\t\t\tcl.validsequence = 0;\t// can't render a frame\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (newindex >= maxentities)\n\t\t\t\tHost_Error (\"CL_ParsePacketEntities: newindex == MAX_PACKET_ENTITIES\");\n\n\t\t\tCL_ParseDelta (&cl_entities[newnum].baseline, &newp->entities[newindex], word);\n\t\t\tCL_SetupPacketEntity (newnum, &newp->entities[newindex], word > 511); \n\t\t\tnewindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (newnum == oldnum) \n\t\t{\n\t\t\t// Delta from previous\n\t\t\tif (full) \n\t\t\t{\n\t\t\t\tcl.validsequence = 0;\n\t\t\t\tcl.delta_sequence = 0;\n\t\t\t\tCom_Printf (\"WARNING: delta on full update\");\n\t\t\t}\n\t\t\t\n\t\t\tif (word & U_REMOVE) \n\t\t\t{\n\t\t\t\t#ifdef PROTOCOL_VERSION_FTE\n\t\t\t\tif (word & U_MOREBITS && (cls.fteprotocolextensions & FTE_PEXT_ENTITYDBL))\n\t\t\t\t{\n\t\t\t\t\tif (MSG_ReadByte() & U_FTE_EVENMORE)\n\t\t\t\t\t\tMSG_ReadByte();\n\t\t\t\t}\n\t\t\t\t#endif // PROTOCOL_VERSION_FTE\n\t\t\t\toldindex++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tCL_ParseDelta (&oldp->entities[oldindex], &newp->entities[newindex], word);\n\t\t\tCL_SetupPacketEntity (newnum, &newp->entities[newindex], word > 511);\n\t\t\tnewindex++;\n\t\t\toldindex++;\n\t\t}\n\t}\n\n\tnewp->num_entities = newindex;\n\tif (copy) {\n\t\t// do this incase it's FTE demo...\n\t\tmemcpy(&cl.frames[1], &cl.frames[0], sizeof(cl.frames[1]));\n\t}\n\n\tif (cls.state == ca_onserver) {\n\t\t// we can now render a frame\n\t\tCL_MakeActive();\n\t}\n}\n\nstatic qbool CL_SetAlphaByDistance(entity_t* ent)\n{\n\tvec3_t diff;\n\tfloat distance;\n\n\tVectorSubtract(ent->origin, cl.simorg, diff);\n\tdistance = VectorLength(diff);\n\n\t// If too close to player, just hide entirely\n\tif (distance < KTX_RACING_PLAYER_MIN_DISTANCE)\n\t\treturn false;\n\n\tent->alpha = min(distance, KTX_RACING_PLAYER_MAX_DISTANCE) / KTX_RACING_PLAYER_ALPHA_SCALE;\n\tent->renderfx |= RF_NORMALENT;\n\treturn true;\n}\n\n// TODO: OMG SPLIT THIS UP!\nvoid CL_LinkPacketEntities(void) \n{\n\tentity_t ent;\n\tcentity_t *cent;\n\tpacket_entities_t *pack;\n\tentity_state_t *state;\n\tmodel_t *model;\n\tdouble time;\n\tfloat autorotate, lerp;\n\tint i, pnum, flicker;\n\tcustomlight_t cst_lt = {0};\n\textern qbool cl_nolerp_on_entity_flag;\n\n\tpack = &cl.frames[cl.validsequence & UPDATE_MASK].packet_entities;\n\n\tautorotate = anglemod(100 * cl.time);\n\n\tmemset(&ent, 0, sizeof(ent));\n\n\tfor (pnum = 0; pnum < pack->num_entities; pnum++) \n\t{\n\t\tstate = &pack->entities[pnum];\n\t\tcent = &cl_entities[state->number];\n\n\t\t// Control powerup glow for bots.\n\t\tif (state->modelindex != cl_modelindices[mi_player] || r_powerupglow.value) \n\t\t{\n\t\t\tflicker = r_lightflicker.value ? (rand() & 31) : 0;\n\t\t\t\n\t\t\t// Spawn light flashes, even ones coming from invisible objects.\n\t\t\tif ((state->effects & (EF_BLUE | EF_RED | EF_GREEN)) == (EF_BLUE | EF_RED | EF_GREEN)) \n\t\t\t{\n\t\t\t\tCL_NewDlight (state->number, state->origin, 200 + flicker, 0.1, lt_white, 0);\n\t\t\t} \n\t\t\telse if ((state->effects & (EF_BLUE | EF_RED)) == (EF_BLUE | EF_RED)) \n\t\t\t{\n\t\t\t\tCL_NewDlight (state->number, state->origin, 200 + flicker, 0.1, lt_redblue, 0);\n\t\t\t} \n\t\t\telse if ((state->effects & (EF_BLUE | EF_GREEN)) == (EF_BLUE | EF_GREEN)) \n\t\t\t{\n\t\t\t\tCL_NewDlight (state->number, state->origin, 200 + flicker, 0.1, lt_bluegreen, 0);\n\t\t\t}\n\t\t\telse if ((state->effects & (EF_RED | EF_GREEN)) == (EF_RED | EF_GREEN)) \n\t\t\t{\n\t\t\t\tCL_NewDlight (state->number, state->origin, 200 + flicker, 0.1, lt_redgreen, 0);\n\t\t\t} \n\t\t\telse if (state->effects & EF_BLUE)\n\t\t\t{\n\t\t\t\tCL_NewDlight (state->number, state->origin, 200 + flicker, 0.1, lt_blue, 0);\n\t\t\t} \n\t\t\telse if (state->effects & EF_RED) \n\t\t\t{\n\t\t\t\tCL_NewDlight (state->number, state->origin, 200 + flicker, 0.1, lt_red, 0);\n\t\t\t}\n\t\t\telse if (state->effects & EF_GREEN) \n\t\t\t{\n\t\t\t\tCL_NewDlight (state->number, state->origin, 200 + flicker, 0.1, lt_green, 0);\n\t\t\t}\n\t\t\telse if (state->effects & EF_BRIGHTLIGHT) \n\t\t\t{\n\t\t\t\tvec3_t\ttmp;\n\t\t\t\tVectorCopy (state->origin, tmp);\n\t\t\t\ttmp[2] += 16;\n\t\t\t\tCL_NewDlight (state->number, tmp, 400 + flicker, 0.1, lt_default, 0);\n\t\t\t}\n\t\t\telse if (state->effects & EF_DIMLIGHT)\n\t\t\t{\n\t\t\t\tqbool flagcolor = false;\n\n\t\t\t\tif (cl.teamfortress && (state->modelindex == cl_modelindices[mi_tf_flag] || \n\t\t\t\t\t\t\t\t\t\tstate->modelindex == cl_modelindices[mi_tf_stan] || \n\t\t\t\t\t\t\t\t\t\tstate->modelindex == cl_modelindices[mi_stag] || \n\t\t\t\t\t\t\t\t\t\tstate->modelindex == cl_modelindices[mi_basrkey] || \n\t\t\t\t\t\t\t\t\t\tstate->modelindex == cl_modelindices[mi_basbkey] || \n\t\t\t\t\t\t\t\t\t\tstate->modelindex == cl_modelindices[mi_w_s_key] ||\n\t\t\t\t\t\t\t\t\t\tstate->modelindex == cl_modelindices[mi_w_g_key] ||\n\t\t\t\t\t\t\t\t\t\tstate->modelindex == cl_modelindices[mi_b_g_key] ||\n\t\t\t\t\t\t\t\t\t\tstate->modelindex == cl_modelindices[mi_b_s_key] ||\n\t\t\t\t\t\t\t\t\t\tstate->modelindex == cl_modelindices[mi_ff_flag] ||\n\t\t\t\t\t\t\t\t\t\tstate->modelindex == cl_modelindices[mi_harbflag] ||\n\t\t\t\t\t\t\t\t\t\tstate->modelindex == cl_modelindices[mi_princess]))\n\t\t\t\t\tflagcolor = true;\n\t\t\t\telse if (state->modelindex == cl_modelindices[mi_flag])\n\t\t\t\t\tflagcolor = true;\n\n\t\t\t\tif (flagcolor)\n\t\t\t\t{\n\t\t\t\t\tdlightColorEx(r_flagcolor.value, r_flagcolor.string, lt_default, false, &cst_lt);\n\t\t\t\t\tCL_NewDlightEx(state->number, state->origin, 200 + flicker, 0.1, &cst_lt, 0);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tCL_NewDlight(state->number, state->origin, 200 + flicker, 0.1, lt_default, 0);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!state->modelindex)\t\t// If set to invisible, skip\n\t\t\tcontinue;\n\n\t\tent.effects = state->effects; // Electro - added for shells\n\n\t\tif (state->modelindex == cl_modelindices[mi_player]) \n\t\t{\n\t\t\ti = state->frame;\n\n\t\t\tif ((cl_deadbodyfilter.value == 3) & !cl.teamfortress)\n\t\t\t{ // will instantly filter dead bodies unless gamedir is fortress (because in TF you probably want to see dead bodies as they may be feigned spies)\n\t\t\t\t\tif (ISDEAD(i))\n\t\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (cl_deadbodyfilter.value == 2)\n\t\t\t{\n\t\t\t\tif (ISDEAD(i))\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse if (cl_deadbodyfilter.value == 1) \n\t\t\t{\n\t\t\t\t// These indices are the last animation of a death sequence in the player.mdl\n\t\t\t\t//49=axdeth9, 60=deatha11, 69=deathb9, 84=deathc15, 93=deathd9, 102=deathe9\n\t\t\t\tif (i == 49 || i == 60 || i == 69 || i == 84 || i == 93 || i == 102)\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (cl_gibfilter.value &&\n\t\t\t(state->modelindex == cl_modelindices[mi_h_player]\n\t\t\t|| state->modelindex == cl_modelindices[mi_gib1]\n\t\t\t|| state->modelindex == cl_modelindices[mi_gib2]\n\t\t\t|| state->modelindex == cl_modelindices[mi_gib3]))\n\t\t\tcontinue;\n\n\t\tif (!(model = cl.model_precache[state->modelindex]))\n\t\t{\n\t\t\tHost_Error (\"CL_LinkPacketEntities: bad modelindex\");\n\t\t}\n\n\t\tent.model = model;\n\n\t\tif (state->modelindex == cl_modelindices[mi_rocket]) \n\t\t{\n\t\t\tif (cl_rocket2grenade.value && cl_modelindices[mi_grenade] != -1)\n\t\t\t\tent.model = cl.model_precache[cl_modelindices[mi_grenade]];\n\t\t}\n\n\t\tent.skinnum = state->skinnum;\n\n\t\tif (ent.model->modhint == MOD_BACKPACK && cl_backpackfilter.value) \n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tent.frame = state->frame;\n\t\tif (cent->frametime >= 0 && cent->frametime <= cl.time)\n\t\t{\n\t\t\tent.oldframe = cent->oldframe;\n\t\t\tent.framelerp = (cl.time - cent->frametime) * 10;\n\t\t} \n\t\telse \n\t\t{\n\t\t\tent.oldframe = ent.frame;\n\t\t\tent.framelerp = -1;\n\t\t}\n\n\t\tif (state->colormap >=1 && state->colormap <= MAX_CLIENTS\n\t\t&& (ent.model->modhint == MOD_PLAYER || (ent.renderfx & RF_PLAYERMODEL)))\n\t\t{\n\t\t\tent.colormap = cl.players[state->colormap - 1].translations;\n\t\t\tent.scoreboard = &cl.players[state->colormap - 1];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent.colormap = vid.colormap;\n\t\t\tent.scoreboard = NULL;\n\t\t}\n\t\n\t\tif (((cl_nolerp.value || cl_nolerp_on_entity_flag) && !cls.mvdplayback && !is_monster(state->modelindex))\n\t\t\t|| cent->deltalerp <= 0)\n\t\t{\n\t\t\tlerp = -1;\n\t\t\tVectorCopy(cent->current.origin, ent.origin);\n\t\t} \n\t\telse \n\t\t{\n\t\t\ttime = cls.mvdplayback ? cls.demotime : cl.time;\n\t\t\tlerp = (time - cent->startlerp) / (cent->deltalerp);\n\t\t\tlerp = min(lerp, 1);\n\n\t\t\tif (NewLerp_AbleModel(cent->current.modelindex))\n\t\t\t{\n\t\t\t\tfloat d = time - cent->startlerp;\n\n\t\t\t\tif (d >= 2 * cent->deltalerp) // Seems enitity stopped move.\n\t\t\t\t\tVectorCopy(cent->lerp_origin, ent.origin);\n\t\t\t\telse // interpolate\n\t\t\t\t\tVectorMA(cent->old_origin, d, cent->velocity, ent.origin);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorInterpolate(cent->old_origin, lerp, cent->current.origin, ent.origin);\n\t\t\t}\n\t\t}\n#if defined(FTE_PEXT_TRANS)\n\t\t// set trans, 0 and 255 are both opaque, represented by alpha 0.\n\t\tent.alpha = state->trans == 255 ? 0.0f : (float)state->trans / 254.0f;\n#endif\n#if defined(FTE_PEXT_COLOURMOD)\n\t\t// colourmod 0 is unset, 32 (1.0) incurs no color change\n\t\tif ((state->colourmod[0] > 0 || state->colourmod[1] > 0 || state->colourmod[2] > 0) &&\n\t\t\t!(state->colourmod[0] == 32 && state->colourmod[1] == 32 && state->colourmod[2] == 32))\n\t\t{\n\t\t\tent.r_modelcolor[0] = ((float) state->colourmod[0] * 8.0f) / 256.0f;\n\t\t\tent.r_modelcolor[1] = ((float) state->colourmod[1] * 8.0f) / 256.0f;\n\t\t\tent.r_modelcolor[2] = ((float) state->colourmod[2] * 8.0f) / 256.0f;\n\t\t\tent.renderfx |= RF_FORCECOLOURMOD;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent.renderfx &= ~RF_FORCECOLOURMOD;\n\t\t}\n#endif\n\n\t\tif (ent.model->flags & EF_ROTATE)\n\t\t{\n\t\t\tent.angles[0] = ent.angles[2] = 0;\n\t\t\tent.angles[1] = autorotate;\n\t\t\tif (cl_model_bobbing.value)\n\t\t\t\tent.origin[2] += sin(autorotate / 90 * M_PI) * 5 + 5;\n\t\t} \n\t\telse \n\t\t{\n\t\t\tif (lerp != -1) \n\t\t\t{\n\t\t\t\tAngleInterpolate(cent->old_angles, lerp, cent->current.angles, ent.angles);\n\t\t\t}\n\t\t\telse \n\t\t\t{\n\t\t\t\tVectorCopy(cent->current.angles, ent.angles);\n\t\t\t}\n\t\t}\n\n\t\tif (qmb_initialized) \n\t\t{\n\t\t\tif (state->modelindex == cl_modelindices[mi_explod1] || state->modelindex == cl_modelindices[mi_explod2]) \n\t\t\t{\n\t\t\t\textern cvar_t gl_part_inferno;\n\n\t\t\t\tif (gl_part_inferno.value) \n\t\t\t\t{\n\t\t\t\t\tQMB_InfernoFlame (ent.origin);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (state->modelindex == cl_modelindices[mi_bubble]) \n\t\t\t{\n\t\t\t\textern cvar_t gl_part_bubble;\n\n\t\t\t\tif (gl_part_bubble.value) {\n\t\t\t\t\tQMB_StaticBubble(&ent);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add trails\n\t\tVectorCopy(ent.origin, cent->lerp_origin);\n\t\tif ((model->flags & ~EF_ROTATE) || model->modhint) {\n\t\t\tCL_AddParticleTrail(&ent, cent, &cst_lt, state);\n\t\t}\n\n\t\tif ((!cls.mvdplayback || Cam_TrackNum() >= 0) && cl.racing && cl.race_pacemaker_ent == state->number && !CL_SetAlphaByDistance(&ent)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tent.renderfx &= ~(RF_BACKPACK_FLAGS);\n\t\tif (cls.mvdplayback && model->modhint == MOD_BACKPACK) {\n\t\t\tif (cent->contents == IT_ROCKET_LAUNCHER) {\n\t\t\t\tent.renderfx |= RF_ROCKETPACK;\n\t\t\t}\n\t\t\telse if (cent->contents == IT_LIGHTNING) {\n\t\t\t\tent.renderfx |= RF_LGPACK;\n\t\t\t}\n\t\t}\n\n\t\tCL_AddEntity (&ent);\n\t}\n}\n\n\n\ntypedef struct \n{\n\tint\t\tmodelindex;\n\tvec3_t\torigin;\n\tvec3_t\tangles;\n\tint\t\tnum;\t\n} projectile_t;\n\nprojectile_t\tcl_projectiles[MAX_PROJECTILES];\nint\t\t\t\tcl_num_projectiles;\n\nprojectile_t\tcl_oldprojectiles[MAX_PROJECTILES];\t\t\nint\t\t\t\tcl_num_oldprojectiles;\t\t\t\t\t\n\nvoid CL_ClearProjectiles (void) \n{\n\tif (cls.mvdplayback) \n\t{\n\t\tstatic int parsecount = 0;\n\n\t\tif (parsecount == cl.parsecount)\n\t\t\treturn;\n\n\t\tparsecount = cl.parsecount;\n\t\tmemset(cl.int_projectiles, 0, sizeof(cl.int_projectiles));\n\t\tcl_num_oldprojectiles = cl_num_projectiles;\n\t\tmemcpy(cl_oldprojectiles, cl_projectiles, sizeof(cl_projectiles));\n\t}\n\n\tcl_num_projectiles = 0;\n}\n\n// Nails are passed as efficient temporary entities\nvoid CL_ParseProjectiles (qbool indexed) \n{\n\tint i, c, j, num;\n\tbyte bits[6];\n\tprojectile_t *pr;\n\tinterpolate_t *int_projectile;\t\n\n\tc = MSG_ReadByte ();\n\tfor (i = 0; i < c; i++) \n\t{\n\t\tnum = indexed ? MSG_ReadByte() : 0;\t\n\n\t\tfor (j = 0 ; j < 6 ; j++)\n\t\t\tbits[j] = MSG_ReadByte ();\n\n\t\tif (cl_num_projectiles == MAX_PROJECTILES)\n\t\t\tcontinue;\n\n\t\tpr = &cl_projectiles[cl_num_projectiles];\n\t\tint_projectile = &cl.int_projectiles[cl_num_projectiles];\t\n\t\tcl_num_projectiles++;\n\n\t\tpr->modelindex = cl_modelindices[mi_spike];\n\t\tpr->origin[0] = (( bits[0] + ((bits[1] & 15) << 8)) << 1) - 4096;\n\t\tpr->origin[1] = (((bits[1] >> 4) + (bits[2] << 4)) << 1) - 4096;\n\t\tpr->origin[2] = ((bits[3] + ((bits[4] & 15) << 8)) << 1) - 4096;\n\t\tpr->angles[0] = 360 * (bits[4] >> 4) / 16;\n\t\tpr->angles[1] = 360 * bits[5] / 256;\n\n\t\tif (!(pr->num = num))\n\t\t\tcontinue;\n\n\t\tfor (j = 0; j < cl_num_oldprojectiles; j++) \n\t\t{\n\t\t\tif (cl_oldprojectiles[j].num == num)\n\t\t\t{\n\t\t\t\tint_projectile->interpolate = true;\n\t\t\t\tint_projectile->oldindex = j;\n\t\t\t\tVectorCopy(pr->origin, int_projectile->origin);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\t\t\n\t}\t\n}\n\nvoid CL_LinkProjectiles (void) \n{\n\tint i;\n\tprojectile_t *pr;\n\tentity_t ent;\n\n\tmemset(&ent, 0, sizeof(entity_t));\n\tent.colormap = vid.colormap;\n\n\tfor (i = 0, pr = cl_projectiles; i < cl_num_projectiles; i++, pr++)\t\n\t{\n\t\tif (pr->modelindex < 1)\n\t\t\tcontinue;\n\t\tent.model = cl.model_precache[pr->modelindex];\n\t\tVectorCopy(pr->origin, ent.origin);\n\t\tVectorCopy(pr->angles, ent.angles);\n\t\tCL_AddEntity(&ent);\n\t}\n}\n\nvoid SetupPlayerEntity(int num, player_state_t *state) \n{\n\tcentity_t *cent;\n\n\tcent = &cl_entities[num];\n\n\tif (!cl.oldparsecount || cl.oldparsecount != cent->sequence ||\n\t\tstate->modelindex != cent->current.modelindex ||\n\t\t!VectorL2Compare(state->origin, cent->current.origin, 200)) \n\t{\n\t\tcent->startlerp = cl.time;\n\t\tcent->deltalerp = -1;\n\t\tcent->frametime = -1;\n\t} \n\telse \n\t{\n\t\t\n\t\tif (state->frame != cent->current.frame) \n\t\t{\n\t\t\tcent->frametime = cl.time;\n\t\t\tcent->oldframe = cent->current.frame;\n\t\t\tif (state->vw_index == cent->old_vw_index)\n\t\t\t\tcent->old_vw_frame = cent->oldframe;\n\t\t\telse\n\t\t\t\tcent->old_vw_frame = state->frame;\t// no lerping if vwep model changed\n\t\t}\n\n\t\tif (!VectorCompare(state->origin, cent->current.origin) || !VectorCompare(state->viewangles, cent->current.angles)) {\n\t\t\tVectorCopy(cent->current.origin, cent->old_origin);\n\t\t\tVectorCopy(cent->current.angles, cent->old_angles);\n\t\t\tif (cls.mvdplayback) \n\t\t\t{\n\t\t\t\tcent->deltalerp = nextdemotime - olddemotime;\n\t\t\t\tcent->startlerp = cls.demotime;\n\t\t\t} \n\t\t\telse\n\t\t\t{\n\t\t\t\tcent->deltalerp = cl.time - cent->startlerp;\n\t\t\t\tcent->startlerp = cl.time;\n\t\t\t}\n\t\t}\n\t}\n\n\tVectorCopy(state->origin, cent->current.origin);\n\tVectorCopy(state->viewangles, cent->current.angles);\n\tcent->current.frame = state->frame;\n\tcent->sequence = state->messagenum;\t\n\tcent->current.modelindex = state->modelindex;\n\tcent->old_vw_index = state->vw_index;\n}\n\nplayer_state_t oldplayerstates[MAX_CLIENTS];\t\n\nstatic int MVD_WeaponModelNumber (int cweapon)\n{\n\tint i;\n\n\tfor (i = 0; i < 8; i++)\n\t{\n\t\tif (cweapon == cl_modelindices[mi_weapon1 + i])\n\t\t\treturn i + 1;\n\t}\n\n\treturn 0;\n}\n\nvoid CL_ParsePlayerinfo (void) \n{\n\textern cvar_t cl_fix_mvd;\n\tint\tmsec=0, flags, pm_code;\n\tcentity_t *cent;\n\tplayer_info_t *info;\n\tplayer_state_t *state;\n\n\tplayer_state_t *prevstate, dummy;\n\tint num, i;\n\n\textern void TP_ParsePlayerInfo(player_state_t *, player_state_t *, player_info_t *info);\n\n\tnum = MSG_ReadByte ();\n\tif (num >= MAX_CLIENTS)\n\t\tHost_Error (\"CL_ParsePlayerinfo: num >= MAX_CLIENTS\");\n\n\tinfo = &cl.players[num];\n\tstate = &cl.frames[cl.parsecountmod].playerstate[num];\n\tcent = &cl_entities[num + 1];\n\n\tif (cls.mvdplayback)\n\t{\n\t\tif (!cl.parsecount || cent->sequence > cl.parsecount || cl.parsecount - cent->sequence >= UPDATE_BACKUP - 1)\n\t \t{\n\t\t\tmemset(&dummy, 0, sizeof(dummy));\n\t\t\tdummy.pm_type = PM_SPECTATOR; // so camera able to fly\n\t\t\tprevstate = &dummy;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tprevstate = &cl.frames[cent->sequence & UPDATE_MASK].playerstate[num];\n\t\t}\n\n\t\tmemcpy(state, prevstate, sizeof(player_state_t));\n\n\t\tif (cls.findtrack && info->name[0] && !info->spectator)\n\t\t{\n\t\t\tcl.autocam = CAM_TRACK;\n\t\t\tCam_Lock(num);\n\t\t\tcl.ideal_track = num;\n\t\t\tcls.findtrack = false;\n\t\t}\n\n\t\tflags = MSG_ReadShort ();\n\t\tstate->flags = MVD_TranslateFlags(flags);\n\n\t\tstate->messagenum = cl.parsecount;\n\n\t\tstate->frame = MSG_ReadByte ();\n\n\t\tstate->state_time = cl.parsecounttime;\n\t\tstate->command.msec = 0;\n\n\t\tfor (i = 0; i < 3; i++) \n\t\t{\n\t\t\tif (flags & (DF_ORIGIN << i)) {\n\t\t\t\tstate->origin[i] = MSG_ReadCoord();\n\t\t\t}\n\t\t}\n\n\t\tfor (i = 0; i < 3; i++) \n\t\t{\n\t\t\tif (flags & (DF_ANGLES << i))\n\t\t\t\tstate->command.angles[i] = MSG_ReadAngle16 ();\n\t\t}\n\n\t\tif (flags & DF_MODEL) {\n\t\t\tstate->modelindex = MSG_ReadByte();\n\t\t}\n\t\telse if (cl_fix_mvd.integer && !state->modelindex && !cl.players[num].spectator && cl_modelindices[mi_player] != -1) {\n\t\t\t// old bug in mvd/qtv\n\t\t\tstate->modelindex = cl_modelindices[mi_player];\n\t\t}\n\n\t\tif (flags & DF_SKINNUM) {\n\t\t\tstate->skinnum = MSG_ReadByte();\n\n\t\t\tif (cls.fteprotocolextensions & FTE_PEXT_MODELDBL) {\n\t\t\t\tif ((state->skinnum & (1 << 7)) && (flags & DF_MODEL)) {\n\t\t\t\t\tstate->skinnum &= ~(1 << 7);\n\t\t\t\t\tstate->modelindex += 256;\n\t\t\t\t}\n\t\t\t\telse if (cl.model_precache[state->modelindex] != NULL && cl.model_precache[state->modelindex]->type == mod_brush && state->modelindex + 256 == cl_modelindices[mi_player]) {\n\t\t\t\t\t// Temporary hack to detect demos where the modelindex needs to be +256, but not encoded in the skin field\n\t\t\t\t\tstate->modelindex = cl_modelindices[mi_player];\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tif (flags & DF_EFFECTS)\n\t\t\tstate->effects = MSG_ReadByte ();\n\n\t\tif (flags & DF_WEAPONFRAME)\n\t\t\tstate->weaponframe = MSG_ReadByte ();\n\n\t\tif (cl.vwep_enabled && !(state->flags & PF_GIB) && state->modelindex == cl_modelindices[mi_player] /* no vweps for ring! */) {\n\t\t\tstate->vw_index = MVD_WeaponModelNumber(info->stats[STAT_WEAPON]);\n\t\t}\n\t\telse {\n\t\t\tstate->vw_index = 0;\n\t\t}\n\t} \n\telse \n\t{\n#ifdef FTE_PEXT_TRANS\n\t\tflags = (unsigned short) MSG_ReadShort ();\n\t\tif (cls.fteprotocolextensions & FTE_PEXT_TRANS)\n\t\t{\n\t\t\tif (flags & PF_EXTRA_PFS)\n\t\t\t\tflags |= MSG_ReadByte() << 16;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Without PEXT_TRANS there's no PF_EXTRA_PFS, move\n\t\t\t// PF_ONGROUND and PF_SOLID to their expected offsets.\n\t\t\tflags = (flags & 0x3fff) | (flags & 0xc000) << 8;\n\t\t}\n\t\tstate->flags = flags;\n#else\n\t\tstate->flags = flags = MSG_ReadShort ();\n#endif\n\n\t\tstate->messagenum = cl.parsecount;\n\t\tif (cls.mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) {\n\t\t\tstate->origin[0] = MSG_ReadFloatCoord();\n\t\t\tstate->origin[1] = MSG_ReadFloatCoord();\n\t\t\tstate->origin[2] = MSG_ReadFloatCoord();\n\t\t}\n\t\telse {\n\t\t\tstate->origin[0] = MSG_ReadCoord();\n\t\t\tstate->origin[1] = MSG_ReadCoord();\n\t\t\tstate->origin[2] = MSG_ReadCoord();\n\t\t}\n\t\tstate->frame = MSG_ReadByte ();\n\n\t\t// the other player's last move was likely some time before the packet was sent out,\n\t\t// so accurately track the exact time it was valid at\n\t\tif (flags & PF_MSEC) \n\t\t{\n\t\t\tmsec = MSG_ReadByte ();\n\t\t\tstate->state_time = cl.parsecounttime - msec * 0.001;\n\t\t} \n\t\telse\n\t\t{\n\t\t\tstate->state_time = cl.parsecounttime;\n\t\t}\n\n\t\tif (flags & PF_COMMAND) \n\t\t{\n\t\t\tMSG_ReadDeltaUsercmd (&nullcmd, &state->command, cl.protoversion);\n\t\t\tCL_CalcPlayerFPS(info, state->command.msec);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (num != cl.playernum)\t\n\t\t\t\tinfo->fps = -1;\n\t\t}\n\n\t\tif (cl.z_ext & Z_EXT_VWEP && !(state->flags & PF_GIB))\n\t\t\tstate->vw_index = state->command.impulse;\n\t\telse\n\t\t\tstate->vw_index = 0;\n\n\t\tfor (i = 0; i < 3; i++)\n\t\t{\n\t\t\tif (flags & (PF_VELOCITY1 << i) )\n\t\t\t\tstate->velocity[i] = MSG_ReadShort();\n\t\t\telse\n\t\t\t\tstate->velocity[i] = 0;\n\t\t}\n\t\tif (flags & PF_MODEL) {\n\t\t\tstate->modelindex = MSG_ReadByte();\n\t\t}\n\t\telse {\n\t\t\tstate->modelindex = cl_modelindices[mi_player];\n\t\t}\n\n\t\tif (flags & PF_SKINNUM) {\n\t\t\tstate->skinnum = MSG_ReadByte();\n\t\t\tif (state->skinnum & (1<<7) && (flags & PF_MODEL)) {\n\t\t\t\tstate->modelindex += 256;\n\t\t\t\tstate->skinnum -= (1<<7);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tstate->skinnum = 0;\n\t\t}\n\n\t\tif (flags & PF_EFFECTS)\n\t\t\tstate->effects = MSG_ReadByte ();\n\t\telse\n\t\t\tstate->effects = 0;\n\n\t\tif (flags & PF_WEAPONFRAME)\n\t\t\tstate->weaponframe = MSG_ReadByte ();\n\t\telse\n\t\t\tstate->weaponframe = 0;\n\n\n#ifdef FTE_PEXT_TRANS\n\t\tif (flags & PF_TRANS_Z && cls.fteprotocolextensions & FTE_PEXT_TRANS)\n\t\t\tstate->alpha = MSG_ReadByte();\n#endif\n\n\t\tif (cl.z_ext & Z_EXT_PM_TYPE)\n\t\t{\n\t\t\tpm_code = (flags >> PF_PMC_SHIFT) & PF_PMC_MASK;\n\t\t\tif (pm_code == PMC_NORMAL || pm_code == PMC_NORMAL_JUMP_HELD)\n\t\t\t{\n\t\t\t\tif (flags & PF_DEAD)\n\t\t\t\t{\n\t\t\t\t\tstate->pm_type = PM_DEAD;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tstate->pm_type = PM_NORMAL;\n\t\t\t\t\tstate->jump_held = (pm_code == PMC_NORMAL_JUMP_HELD);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t\tif (pm_code == PMC_OLD_SPECTATOR)\n\t\t\t\t\tstate->pm_type = PM_OLD_SPECTATOR;\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif (cl.z_ext & Z_EXT_PM_TYPE_NEW)\n\t\t\t\t\t{\n\t\t\t\t\t\tswitch (pm_code)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcase PMC_SPECTATOR:\n\t\t\t\t\t\t\t\tstate->pm_type = PM_SPECTATOR;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase PMC_FLY:\n\t\t\t\t\t\t\t\tstate->pm_type = PM_FLY;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase PMC_NONE:\n\t\t\t\t\t\t\t\tstate->pm_type = PM_NONE;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase PMC_LOCK:\n\t\t\t\t\t\t\t\tstate->pm_type = PM_LOCK;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t// future extension?\n\t\t\t\t\t\t\t\tgoto guess_pm_type;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// future extension?\n\t\t\t\t\t\tgoto guess_pm_type;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\telse\n\t\t{\nguess_pm_type:\n\t\t\tif (cl.players[num].spectator)\n\t\t\t\tstate->pm_type = PM_OLD_SPECTATOR;\n\t\t\telse if (flags & PF_DEAD)\n\t\t\t\tstate->pm_type = PM_DEAD;\n\t\t\telse\n\t\t\t\tstate->pm_type = PM_NORMAL;\n\t\t}\n\n\t\tif (cls.demorecording)\n\t\t{\n\t\t\t// Write out here - generally packets are copied, but flags for userdelta were changed\n\t\t\t//   in protocol 27 - we always write out in new format\n\t\t\tMSG_WriteByte(&cls.demomessage, num);\n#if defined(FTE_PEXT_TRANS)\n\t\t\tif (cls.fteprotocolextensions & FTE_PEXT_TRANS)\n\t\t\t{\n\t\t\t\tif (flags & 0xff0000)\n\t\t\t\t{\n\t\t\t\t\tflags |= PF_EXTRA_PFS;\n\t\t\t\t}\n\t\t\t\tMSG_WriteShort (&cls.demomessage, flags & 0xffff);\n\t\t\t\tif (flags & PF_EXTRA_PFS)\n\t\t\t\t{\n\t\t\t\t\tMSG_WriteByte(&cls.demomessage, (flags & 0xff0000) >> 16);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Without PEXT_TRANS there's no PF_EXTRA_PFS, move\n\t\t\t\t// PF_ONGROUND and PF_SOLID to their expected offsets.\n\t\t\t\tMSG_WriteShort (&cls.demomessage, flags & 0x3fff | (flags & 0xc00000) >> 8);\n\t\t\t}\n#else\n\t\t\tMSG_WriteShort(&cls.demomessage, flags);\n#endif\n\n\t\t\tif (cls.mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) {\n\t\t\t\tMSG_WriteLongCoord(&cls.demomessage, state->origin[0]);\n\t\t\t\tMSG_WriteLongCoord(&cls.demomessage, state->origin[1]);\n\t\t\t\tMSG_WriteLongCoord(&cls.demomessage, state->origin[2]);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tMSG_WriteCoord(&cls.demomessage, state->origin[0]);\n\t\t\t\tMSG_WriteCoord(&cls.demomessage, state->origin[1]);\n\t\t\t\tMSG_WriteCoord(&cls.demomessage, state->origin[2]);\n\t\t\t}\n\t\t\tMSG_WriteByte(&cls.demomessage, state->frame);\n\t\t\tif (flags & PF_MSEC)\n\t\t\t\tMSG_WriteByte(&cls.demomessage, msec);\n\t\t\tif (flags & PF_COMMAND)\n\t\t\t\tMSG_WriteDeltaUsercmd(&cls.demomessage, &nullcmd, &state->command, 0);\n\t\t\tfor (i = 0; i < 3; i++)\n\t\t\t\tif (flags & (PF_VELOCITY1 << i) )\n\t\t\t\t\tMSG_WriteShort(&cls.demomessage, state->velocity[i]);\n\t\t\tif (flags & PF_MODEL)\n\t\t\t\tMSG_WriteByte(&cls.demomessage, state->modelindex);\n\t\t\tif (flags & PF_SKINNUM)\n\t\t\t\tMSG_WriteByte(&cls.demomessage, state->skinnum);\n\t\t\tif (flags & PF_EFFECTS)\n\t\t\t\tMSG_WriteByte(&cls.demomessage, state->effects);\n\t\t\tif (flags & PF_WEAPONFRAME)\n\t\t\t\tMSG_WriteByte(&cls.demomessage, state->weaponframe);\n#ifdef FTE_PEXT_TRANS\n\t\t\tif (flags & PF_TRANS_Z && cls.fteprotocolextensions & FTE_PEXT_TRANS)\n\t\t\t\tMSG_WriteByte(&cls.demomessage, state->alpha);\t\t\n#endif\n\t\t}\n\t}\n\n\tif (cl.z_ext & Z_EXT_PF_ONGROUND)\n\t\tstate->onground = (flags & PF_ONGROUND) != 0;\n\telse\n\t\tstate->onground = false;\n\n\tif (!(cl.z_ext & Z_EXT_PF_SOLID))\n\t{\t// the PF_SOLID bit is unsupported, use our best guess\n\t\tif (cl.players[num].spectator || (state->flags & PF_DEAD))\n\t\t\tstate->flags &= ~PF_SOLID;\n\t\telse\n\t\t\tstate->flags |= PF_SOLID;\n\t}\n\n\tVectorCopy (state->command.angles, state->viewangles);\n\t\n\tTP_ParsePlayerInfo(&oldplayerstates[num], state, info);\n\toldplayerstates[num] = *state;\n\t\n\tSetupPlayerEntity(num + 1, state);\n}\n\n// Called when the CTF flags are set\nvoid CL_AddFlagModels (entity_t *ent, int team) \n{\n\tint i;\n\tfloat f;\n\tvec3_t v_forward, v_right;\n\tentity_t newent;\n\n\tif (cl_modelindices[mi_flag] == -1)\n\t\treturn;\n\n\tf = 14;\n\tif (ent->frame >= 29 && ent->frame <= 40) {\n\t\tif (ent->frame >= 29 && ent->frame <= 34) { //axpain\n\t\t\tif      (ent->frame == 29) f = f + 2; \n\t\t\telse if (ent->frame == 30) f = f + 8;\n\t\t\telse if (ent->frame == 31) f = f + 12;\n\t\t\telse if (ent->frame == 32) f = f + 11;\n\t\t\telse if (ent->frame == 33) f = f + 10;\n\t\t\telse if (ent->frame == 34) f = f + 4;\n\t\t} else if (ent->frame >= 35 && ent->frame <= 40) { // pain\n\t\t\tif      (ent->frame == 35) f = f + 2; \n\t\t\telse if (ent->frame == 36) f = f + 10;\n\t\t\telse if (ent->frame == 37) f = f + 10;\n\t\t\telse if (ent->frame == 38) f = f + 8;\n\t\t\telse if (ent->frame == 39) f = f + 4;\n\t\t\telse if (ent->frame == 40) f = f + 2;\n\t\t}\n\t} else if (ent->frame >= 103 && ent->frame <= 118) {\n\t\tif      (ent->frame >= 103 && ent->frame <= 104) f = f + 6;  //nailattack\n\t\telse if (ent->frame >= 105 && ent->frame <= 106) f = f + 6;  //light \n\t\telse if (ent->frame >= 107 && ent->frame <= 112) f = f + 7;  //rocketattack\n\t\telse if (ent->frame >= 112 && ent->frame <= 118) f = f + 7;  //shotattack\n\t}\n\n\tmemset (&newent, 0, sizeof(entity_t));\n\tnewent.model = cl.model_precache[cl_modelindices[mi_flag]];\n\tnewent.skinnum = team;\n\tnewent.colormap = vid.colormap;\n\n\tAngleVectors (ent->angles, v_forward, v_right, NULL);\n\tv_forward[2] = -v_forward[2]; // reverse z component\n\tfor (i = 0; i < 3; i++)\n\t\tnewent.origin[i] = ent->origin[i] - f * v_forward[i] + 22 * v_right[i];\n\tnewent.origin[2] -= 16;\n\n\tVectorCopy (ent->angles, newent.angles);\n\tnewent.angles[2] -= 45;\n\n\tCL_AddEntity (&newent);\n}\n\n/*\n================\nCL_AddVWepModel\n================\n*/\nstatic qbool CL_AddVWepModel (entity_t *ent, int vw_index, int old_vw_frame)\n{\n\tentity_t\tnewent;\n\n\tif ((unsigned)vw_index >= MAX_VWEP_MODELS)\n\t\treturn false;\n\n\tif (cl.vw_model_name[vw_index][0] == '-')\n\t\treturn true;\t// empty vwep model\n\n\tif (!cl.vw_model_precache[vw_index])\n\t\treturn false;\t// vwep model not present - draw default player.mdl\n\n\t// build the weapon entity\n\tmemset (&newent, 0, sizeof(entity_t));\n\tVectorCopy (ent->origin, newent.origin);\n\tVectorCopy (ent->angles, newent.angles);\n\tnewent.model = cl.vw_model_precache[vw_index];\n\tnewent.frame = ent->frame;\n\tnewent.oldframe = old_vw_frame;\n\tnewent.framelerp = ent->framelerp;\n\tnewent.skinnum = 0;\n\tnewent.colormap = vid.colormap;\n\tnewent.renderfx |= RF_PLAYERMODEL;\t// not really, but use same lighting rules\n\tnewent.renderfx |= RF_VWEPMODEL;\n\tif(ent->renderfx & RF_BEHINDWALL)\n\t\tnewent.renderfx |= RF_BEHINDWALL;\n\tnewent.effects = ent->effects; // Electro - added for shells\n\tnewent.scoreboard = ent->scoreboard; // for team color in gl_outline\n\n\tif ((!cls.mvdplayback || Cam_TrackNum() >= 0) && cl.racing && !CL_SetAlphaByDistance(&newent)) {\n\t\treturn false;\n\t}\n\n\tCL_AddEntity (&newent);\n\treturn true;\n}\n\nstatic double CL_PlayerTime (void)\n{\n\tdouble current_time = (cls.demoplayback && !cls.mvdplayback) ? cls.demotime : cls.realtime;\n\tdouble playertime = current_time - cls.latency + 0.02;\n\n\treturn min(playertime, current_time);\n}\n\nvoid CL_StorePausePredictionLocations(void)\n{\n\tint i;\n\tframe_t* frame = &cl.frames[cl.parsecount & UPDATE_MASK];\n\tdouble playertime = CL_PlayerTime();\n\n\tfor (i = 0; i < MAX_CLIENTS; ++i) {\n\t\tpredicted_players[i].paused_sec = playertime - frame->playerstate[i].state_time;\n\t}\n}\n\n// Create visible entities in the correct position for all current players\nstatic void CL_LinkPlayers(void)\n{\n\tint j, msec, i, flicker, oldphysent;\n\tfloat *org, distance;\n\tvec3_t tmp, end, diff;\n\tdouble playertime = CL_PlayerTime();\n\tplayer_info_t *info;\n\tplayer_state_t *state, exact;\n\tentity_t ent;\n\tcentity_t *cent;\n\tframe_t *frame;\n\ttrace_t trace;\n\tcustomlight_t cst_lt = {0};\n\textern cvar_t cl_debug_antilag_ghost, cl_debug_antilag_view;\n\textern cvar_t gl_spec_xray, gl_spec_xray_distance;\n\n\tframe = &cl.frames[cl.parsecount & UPDATE_MASK];\n\tmemset (&ent, 0, sizeof(entity_t));\n\n\tfor (j = 0; j < MAX_CLIENTS; j++, info++, state++) \n\t{\n\t\tinfo = &cl.players[j];\n\t\tstate = &frame->playerstate[j];\n\t\tcent = &cl_entities[j + 1];\n\n\t\tpredicted_players[j].drawn = false;\n\n\t\tif (state->messagenum != cl.parsecount)\n\t\t\tcontinue;\t// not present this frame\n\n\t\t// spawn light flashes, even ones coming from invisible objects\n\t\tif (r_powerupglow.value && !(r_powerupglow.value == 2 && j == cl.viewplayernum)) \n\t\t{\n\t\t\torg = (j == cl.playernum) ? cl.simorg : state->origin;\n\t\t\tflicker = r_lightflicker.value ? (rand() & 31) : 0;\n\n\t\t\tif ((state->effects & (EF_BLUE | EF_RED | EF_GREEN)) == (EF_BLUE | EF_RED | EF_GREEN)) \n\t\t\t{\n\t\t\t\tCL_NewDlight (j + 1, org, 200 + flicker, 0.1, lt_white, 0);\n\t\t\t} \n\t\t\telse if ((state->effects & (EF_BLUE | EF_RED)) == (EF_BLUE | EF_RED)) \n\t\t\t{\n\t\t\t\tCL_NewDlight (j + 1, org, 200 + flicker, 0.1, lt_redblue, 0);\n\t\t\t}\n\t\t\telse if ((state->effects & (EF_BLUE | EF_GREEN)) == (EF_BLUE | EF_GREEN)) \n\t\t\t{\n\t\t\t\tCL_NewDlight (j + 1, org, 200 + flicker, 0.1, lt_bluegreen, 0);\n\t\t\t} \n\t\t\telse if ((state->effects & (EF_RED | EF_GREEN)) == (EF_RED | EF_GREEN)) \n\t\t\t{\n\t\t\t\tCL_NewDlight (j + 1, org, 200 + flicker, 0.1, lt_redgreen, 0);\n\t\t\t} \n\t\t\telse if (state->effects & EF_BLUE) \n\t\t\t{\n\t\t\t\tCL_NewDlight (j + 1, org, 200 + flicker, 0.1, lt_blue, 0);\n\t\t\t}\n\t\t\telse if (state->effects & EF_RED) \n\t\t\t{\n\t\t\t\tCL_NewDlight (j + 1, org, 200 + flicker, 0.1, lt_red, 0);\n\t\t\t}\n\t\t\telse if (state->effects & EF_GREEN) \n\t\t\t{\n\t\t\t\tCL_NewDlight (j + 1, org, 200 + flicker, 0.1, lt_green, 0);\n\t\t\t} \n\t\t\telse if (state->effects & EF_BRIGHTLIGHT) \n\t\t\t{\n\t\t\t\tVectorCopy (org, tmp);\n\t\t\t\ttmp[2] += 16;\n\t\t\t\tCL_NewDlight (j + 1, tmp, 400 + flicker, 0.1, lt_default, 0);\n\t\t\t}\n\t\t\telse if (state->effects & EF_DIMLIGHT)\n\t\t\t{\n\t\t\t\tqbool flagcolor = false;\n\t\t\t\tif (cl.teamfortress)\t\n\t\t\t\t\tflagcolor = true;\n\t\t\t\telse if (state->effects & (EF_FLAG1|EF_FLAG2))\n\t\t\t\t\tflagcolor = true;\n\n\t\t\t\tif (flagcolor) {\n\t\t\t\t\tdlightColorEx(r_flagcolor.value, r_flagcolor.string, lt_default, false, &cst_lt);\n\t\t\t\t\tCL_NewDlightEx(j + 1, org, 200 + flicker, 0.1, &cst_lt, 0);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tCL_NewDlight(j + 1, org, 200 + flicker, 0.1, lt_default, 0);\n\t\t\t}\n\t\t}\n\n\t\t// VULT MOTION TRAILS\n\t\tent.alpha = 0;\n\t\t// The player object never gets added.\n\t\tif (j == cl.playernum) {\n\t\t\t// VULT CAMERAS\n\t\t\tif (cameratype != C_NORMAL && !cl.spectator) {\n\t\t\t\tent.alpha = -1;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (!Cam_DrawPlayer(j)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!state->modelindex)\n\t\t\tcontinue;\n\n\t\tif (cl_gibfilter.value && state->modelindex == cl_modelindices[mi_h_player]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (state->modelindex == cl_modelindices[mi_player])\n\t\t{\n\t\t\ti = state->frame;\n\n\t\t\tif (cl_deadbodyfilter.value == 3 && !cl.teamfortress) {\n\t\t\t\t// will instantly filter dead bodies unless gamedir is fortress (in TF they may be feigned spies)\n\t\t\t\tif (ISDEAD(i))\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse if (cl_deadbodyfilter.value == 2)  {\n\t\t\t\tif (ISDEAD(i))\n\t\t\t\t\tcontinue;\n\t\t\t} \n\t\t\telse if (cl_deadbodyfilter.value == 1) \n\t\t\t{\n\t\t\t\t// These indices are the last animation of a death sequence in the player.mdl\n\t\t\t\t//49=axdeth9, 60=deatha11, 69=deathb9, 84=deathc15, 93=deathd9, 102=deathe9\n\t\t\t\tif (i == 49 || i == 60 || i == 69 || i == 84 || i == 93 || i == 102)\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (!(ent.model = cl.model_precache[state->modelindex]))\n\t\t{\n\t\t\tHost_Error (\"CL_LinkPlayers: bad modelindex\");\n\t\t}\n\t\telse if (state->modelindex == cl_modelindices[mi_player] && ISDEAD(state->frame)\n\t\t\t\t&& cl.vw_model_precache[0] && r_drawvweps.value\n\t\t\t\t&& cls.mvdplayback) // For non-mvd, let the server decide \n\t\t{\n\t\t\tent.model = cl.vw_model_precache[0];\n\t\t\tent.renderfx |= RF_PLAYERMODEL;\n\t\t}\n\n\t\tent.skinnum = state->skinnum;\n\t\tent.colormap = info->translations;\n\t\tent.scoreboard = (state->modelindex == cl_modelindices[mi_player]) ? info : NULL;\n\t\tent.frame = state->frame;\n\t\tent.effects = state->effects; // Electro - added for shells\n\n\t\tif (cent->frametime >= 0 && cent->frametime <= cl.time) \n\t\t{\n\t\t\tent.oldframe = cent->oldframe;\n\t\t\tent.framelerp = (cl.time - cent->frametime) * 10;\n\t\t} \n\t\telse \n\t\t{\n\t\t\tent.oldframe = ent.frame;\n\t\t\tent.framelerp = -1;\n\t\t}\n\n\t\t// angles\n\t\tent.angles[PITCH] = -state->viewangles[PITCH] / 3;\n\t\tent.angles[YAW] = state->viewangles[YAW];\n\t\tent.angles[ROLL] = 0;\n\t\tent.angles[ROLL] = 4 * V_CalcRoll (ent.angles, state->velocity);\n\n\t\t// only predict half the move to minimize overruns\n\t\tmsec = (cl_predict_half.value ? 500 : 1000) * (cl.paused ? predicted_players[j].paused_sec : (playertime - state->state_time));\n\t\tif (msec <= 0 || !cl_predict_players.value || cls.mvdplayback) {\n\t\t\tVectorCopy (state->origin, ent.origin);\n\t\t\tVectorCopy(ent.origin, predicted_players[j].drawn_origin);\n\t\t\tpredicted_players[j].msec = 0;\n\t\t\tpredicted_players[j].drawn = true;\n\t\t}\n\t\telse {\n\t\t\t// predict players movement\n\t\t\tmsec = min(msec, 255);\n\t\t\tstate->command.msec = msec;\n\n\t\t\toldphysent = pmove.numphysent;\n\t\t\tCL_SetSolidPlayers(j);\n\t\t\tCL_PredictUsercmd(state, &exact, &state->command);\n\t\t\tpmove.numphysent = oldphysent;\n\t\t\tVectorCopy(exact.origin, ent.origin);\n\t\t\tVectorCopy(exact.origin, predicted_players[j].drawn_origin);\n\t\t\tpredicted_players[j].msec = msec;\n\t\t\tpredicted_players[j].drawn = true;\n\t\t}\n\n\t\tif (state->effects & (EF_FLAG1|EF_FLAG2))\n\t\t\tCL_AddFlagModels (&ent, !!(state->effects & EF_FLAG2));\n\n\t\t// VULT CAMERAS\n\t\tif (j == cl.playernum)\n\t\t{\n\t\t\tif (cl.stats[STAT_HEALTH] <= 0)\n\t\t\t{\n\t\t\t\tVectorCopy(state->viewangles, ent.angles);\n\t\t\t\t//This doesn't work... But no one has noticed yet :D\n\t\t\t}\n\t\t\telse\n\t\t\t\tVectorCopy(cl.simangles, ent.angles);\n\t\t\tent.angles[0] = state->viewangles[0];\n\t\t\tVectorCopy (cl.simorg, ent.origin);\n\t\t}\n\n\t\t// Set alpha after origin determined\n\t\tif ((!cls.mvdplayback || Cam_TrackNum() >= 0) && cl.racing && !CL_SetAlphaByDistance(&ent)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Add mvd ghost\n\t\tif (cls.mvdplayback && cl_debug_antilag_ghost.integer != cl_debug_antilag_view.integer) {\n\t\t\tvec3_t temp;\n\t\t\tfloat old_alpha;\n\n\t\t\tVectorCopy(ent.origin, temp);\n\t\t\told_alpha = ent.alpha;\n\n\t\t\tent.alpha = 0.2f;\n\t\t\tswitch (cl_debug_antilag_ghost.integer) {\n\t\t\tcase 0:\n\t\t\t\tif (state->antilag_flags & dbg_antilag_position_set) {\n\t\t\t\t\tVectorCopy(state->current_origin, ent.origin);\n\t\t\t\t\tCL_AddEntity(&ent);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 1:\n\t\t\t\tif (state->antilag_flags & dbg_antilag_rewind_present) {\n\t\t\t\t\tVectorCopy(state->rewind_origin, ent.origin);\n\t\t\t\t\tCL_AddEntity(&ent);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tif (state->antilag_flags & dbg_antilag_client_present) {\n\t\t\t\t\tVectorCopy(state->client_origin, ent.origin);\n\t\t\t\t\tCL_AddEntity(&ent);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tVectorCopy(temp, ent.origin);\n\t\t\tent.alpha = old_alpha;\n\t\t}\n\n\t\tVectorCopy (ent.origin, cent->lerp_origin);\n\n\t\t// VULT DEATH EFFECT\n\t\tif (amf_part_deatheffect.value)\n\t\t{\n\t\t\tif (ISDEAD(state->frame) && amf_part_deatheffect.value == 2)\n\t\t\t{\n\t\t\t\tif (info->dead == false) //dead effect not played yet\n\t\t\t\t{\n\t\t\t\t\t//deatheffect\n\t\t\t\t\tVX_DeathEffect (state->origin);\n\t\t\t\t\tinfo->dead = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (state->modelindex == cl_modelindices[mi_h_player])\n\t\t\t{\n\t\t\t\tif (info->dead == false)\n\t\t\t\t{\n\t\t\t\t\t//gibeffect\n\t\t\t\t\tVX_GibEffect (state->origin);\n\t\t\t\t\tinfo->dead = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tinfo->dead = false;\n\t\t\t}\n\t\t}\n\n\t\tif (gl_spec_xray.value && (cls.demoplayback || cls.mvdplayback))\n\t\t{\n\t\t\tVectorCopy(cent->lerp_origin, end);\n\t\t\tend[2] += 12;\n\t\t\ttrace = PM_TraceLine(r_refdef.vieworg, end);\n\n\t\t\tif (trace.fraction != 1) {\n\t\t\t\tVectorSubtract(cent->lerp_origin, r_refdef.vieworg, diff);\n\t\t\t\tdistance = VectorLength(diff);\n\n\t\t\t\tif(distance > gl_spec_xray_distance.value)\n\t\t\t\t\tcontinue;\n\t\t\t\telse\n\t\t\t\t\tent.renderfx |= RF_BEHINDWALL;\n\t\t\t} else\n\t\t\t\tent.renderfx &= ~RF_BEHINDWALL;\n\t\t}\n\n\t\tent.renderfx |= RF_PLAYERMODEL;\n\n\t\tif ((cl.vwep_enabled && r_drawvweps.value && state->vw_index) && (state->modelindex != cl_modelindices[mi_eyes]))\n\t\t{\n\t\t\tqbool vwep;\n\t\t\tvwep = CL_AddVWepModel (&ent, state->vw_index, cent->old_vw_frame);\n\t\t\tif (vwep)\n\t\t\t{\n\t\t\t\tif (cl.vw_model_name[0][0] != '-') \n\t\t\t\t{\n\t\t\t\t\tent.model = cl.vw_model_precache[0];\n\t\t\t\t\tif (Cam_TrackNum() >= 0 && cl.racing) {\n\t\t\t\t\t\tCL_SetAlphaByDistance(&ent);\n\t\t\t\t\t}\n\t\t\t\t\tCL_AddEntity (&ent);\n\t\t\t\t}\n\t\t\t\telse \n\t\t\t\t{\n\t\t\t\t\t// server said don't add vwep player model\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCL_AddEntity(&ent);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tCL_AddEntity(&ent);\n\t\t}\n\t}\n}\n\n// Builds all the pmove physents for the current frame\nvoid CL_SetSolidEntities (void)\n{\n\tint i;\n\tframe_t\t*frame;\n\tpacket_entities_t *pak;\n\tentity_state_t *state;\n\n\tpmove.physents[0].model = cl.clipmodels[1];\n\tVectorClear (pmove.physents[0].origin);\n\tpmove.physents[0].info = 0;\n\tpmove.numphysent = 1;\n\n\tframe = &cl.frames[cl.parsecountmod];\n\tpak = &frame->packet_entities;\n\n\tfor (i = 0; i < pak->num_entities; i++) \n\t{\n\t\tstate = &pak->entities[i];\n\n\t\tif (!state->modelindex)\n\t\t\tcontinue;\n\n\t\tif (cl.clipmodels[state->modelindex])\n\t\t{\n\t\t\tif (pmove.numphysent == MAX_PHYSENTS)\n\t\t\t\tbreak;\n\n\t\t\tpmove.physents[pmove.numphysent].model = cl.clipmodels[state->modelindex];\n#if defined(FTE_PEXT_TRANS)\n\t\t\tpmove.physents[pmove.numphysent].is_transparent =\n\t\t\t\tstate->trans > 0 && state->trans < 255 ? true : false;\n#endif\n\t\t\tVectorCopy (state->origin, pmove.physents[pmove.numphysent].origin);\n\t\t\tpmove.numphysent++;\n\t\t}\n\t}\n}\n\n// Calculate the new position of players, without other player clipping\n//\n// We do this to set up real player prediction.\n// Players are predicted twice, first without clipping other players,\n// then with clipping against them.\n// This sets up the first phase.\nvoid CL_SetUpPlayerPrediction(qbool dopred)\n{\n\tint j, msec;\n\tplayer_state_t *state, exact;\n\tdouble playertime = CL_PlayerTime();\n\tframe_t *frame;\n\tstruct predicted_player *pplayer;\n\n#ifdef EXPERIMENTAL_SHOW_ACCELERATION\n\textern qbool flag_player_pmove;\n#endif\n\n\tframe = &cl.frames[cl.parsecount & UPDATE_MASK];\n\n\tfor (j = 0; j < MAX_CLIENTS; j++) \n\t{\n\t\tpplayer = &predicted_players[j];\n\t\tstate = &frame->playerstate[j];\n\n\t\tpplayer->active = false;\n\n\t\tif (state->messagenum != cl.parsecount)\n\t\t\tcontinue;\t// not present this frame\n\n\t\tif (!state->modelindex)\n\t\t\tcontinue;\n\n\t\tpplayer->active = true;\n\t\tpplayer->flags = state->flags;\n\n#ifdef EXPERIMENTAL_SHOW_ACCELERATION\n\t\tflag_player_pmove = (j == cl.playernum);\n#endif\n\t\t// note that the local player is special, since he moves locally we use his last predicted postition\n\t\tif (j == cl.playernum) \n\t\t{\n\t\t\tVectorCopy(cl.frames[cls.netchan.outgoing_sequence & UPDATE_MASK].playerstate[cl.playernum].origin, pplayer->origin);\n\t\t} \n\t\telse \n\t\t{\n\t\t\tmsec = (cl_predict_half.value ? 500 : 1000) * (playertime - state->state_time);\n\t\t\tif (msec <= 0 || !cl_predict_players.value || !dopred || cls.mvdplayback) \n\t\t\t{ \n\t\t\t\tVectorCopy (state->origin, pplayer->origin);\n\t\t\t} \n\t\t\telse \n\t\t\t{\n\t\t\t\t// Predict players movement\n\t\t\t\tif (msec > 255)\n\t\t\t\t\tmsec = 255;\n\t\t\t\tstate->command.msec = msec;\n\n\t\t\t\tCL_PredictUsercmd (state, &exact, &state->command);\n\t\t\t\tVectorCopy (exact.origin, pplayer->origin);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Builds all the pmove physents for the current frame.\n// Note that CL_SetUpPlayerPrediction() must be called first!\n// pmove must be setup with world and solid entity hulls before calling (via CL_PredictMove)\nvoid CL_SetSolidPlayers (int playernum)\n{\n\tint j;\n\tstruct predicted_player *pplayer;\n\tphysent_t *pent;\n\textern vec3_t player_mins, player_maxs;\n\n\tif (!cl_solid_players.value)\n\t\treturn;\n\n\tpent = pmove.physents + pmove.numphysent;\n\n\tfor (j = 0; j < MAX_CLIENTS; j++) \n\t{\n\t\tpplayer = &predicted_players[j];\n\t\n\t\tif (pmove.numphysent == MAX_PHYSENTS)\n\t\t\tbreak;\n\n\t\tif (!pplayer->active)\n\t\t\tcontinue;\t// not present this frame\n\n\t\t// the player object never gets added\n\t\tif (j == playernum)\n\t\t\tcontinue;\n\n\t\tif (!(pplayer->flags & PF_SOLID))\n\t\t\tcontinue;\n\n\t\tpent->model = 0;\n\t\tVectorCopy(pplayer->origin, pent->origin);\n\t\tVectorCopy(player_mins, pent->mins);\n\t\tVectorCopy(player_maxs, pent->maxs);\n\t\tpmove.numphysent++;\n\t\tpent++;\n\t}\n}\n\n// Builds the visedicts array for cl.time\n// Made up of: clients, packet_entities, nails, and tents\nvoid CL_EmitEntities (void) \n{\n\tif (cls.state != ca_active)\n\t\treturn;\n\n\tif (cls.demoseeking)\n\t\treturn;\n\n\tif (!cl.validsequence && !cls.nqdemoplayback)\n\t\treturn;\n\n\tCL_ClearScene ();\n\n\tif (cls.nqdemoplayback) {\n\t\tNQD_LinkEntities();\n\t}\n\telse {\n\t\tCL_LinkPlayers();\n\t\tCL_LinkPacketEntities();\n\t\tCL_LinkProjectiles();\n\t}\n\n\tCL_UpdateTEnts();\n}\n\nint\tmvd_fixangle;\n\nstatic int MVD_TranslateFlags(int src) \n{\n\tint dst = 0;\n\n\tif (src & DF_EFFECTS)\n\t\tdst |= PF_EFFECTS;\n\tif (src & DF_SKINNUM)\n\t\tdst |= PF_SKINNUM;\n\tif (src & DF_DEAD)\n\t\tdst |= PF_DEAD;\n\tif (src & DF_GIB)\n\t\tdst |= PF_GIB;\n\tif (src & DF_WEAPONFRAME)\n\t\tdst |= PF_WEAPONFRAME;\n\tif (src & DF_MODEL)\n\t\tdst |= PF_MODEL;\n\n\treturn dst;\n}\n\nstatic float MVD_AdjustAngle(float current, float ideal, float fraction) {\n\tfloat move;\n\n\tmove = ideal - current;\n\tif (move >= 180)\n\t\tmove -= 360;\n\telse if (move <= -180)\n\t\tmove += 360;\n\n\treturn current + fraction * move;\n}\n\nstatic void MVD_InitInterpolation(void) {\n\tplayer_state_t *state, *oldstate;\n\tqbool dead_body, was_dead_body;\n\tint i, tracknum;\n\tframe_t\t*frame, *oldframe;\n\tvec3_t dist;\n\tstruct predicted_player *pplayer;\n\n\tif (!cl.validsequence)\n\t\t return;\n\n\tif (nextdemotime <= olddemotime)\n\t\treturn;\n\n\tframe = &cl.frames[cl.parsecount & UPDATE_MASK];\n\toldframe = &cl.frames[cl.oldparsecount & UPDATE_MASK];\n\n\t// clients\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tpplayer = &predicted_players[i];\n\t\tstate = &frame->playerstate[i];\n\t\toldstate = &oldframe->playerstate[i];\n\n\t\tif (pplayer->predict) {\n\t\t\tVectorCopy(pplayer->oldo, oldstate->origin);\n\t\t\tVectorCopy(pplayer->olda, oldstate->command.angles);\n\t\t\tVectorCopy(pplayer->oldv, oldstate->velocity);\n\t\t}\n\n\t\tpplayer->predict = false;\n\n\t\ttracknum = Cam_TrackNum();\n\t\tif (mvd_fixangle & (1 << i)) {\n\t\t\tif (i == tracknum) {\n\t\t\t\tVectorCopy(cl.viewangles, state->command.angles);\n\t\t\t\tVectorCopy(cl.viewangles, state->viewangles);\n\t\t\t}\n\n\t\t\t// no angle interpolation\n\t\t\tVectorCopy(state->command.angles, oldstate->command.angles);\n\n\t\t\tmvd_fixangle &= ~(1 << i);\n\t\t}\n\n\t\t// we dont interpolate ourself if we are spectating\n\t\tif (i == cl.playernum && cl.spectator)\n\t\t\tcontinue;\n\n\t\tmemset(state->velocity, 0, sizeof(state->velocity));\n\n\t\tif (state->messagenum != cl.parsecount)\n\t\t\tcontinue;\t// not present this frame\n\n\t\tif (oldstate->messagenum != cl.oldparsecount || !oldstate->messagenum)\n\t\t\tcontinue;\t// not present last frame\n\n\t\t// Identify dead bodies\n\t\tdead_body = (state->modelindex == cl_modelindices[mi_player] && ISDEAD(state->frame)) || state->modelindex == cl_modelindices[mi_h_player];\n\t\twas_dead_body = (oldstate->modelindex == cl_modelindices[mi_player] && ISDEAD(oldstate->frame)) || oldstate->modelindex == cl_modelindices[mi_h_player];\n\n\t\t// Don't lerp if respawning\n\t\tif (!dead_body && was_dead_body) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Don't lerp if first frame being gibbed\n\t\tif (state->modelindex == cl_modelindices[mi_h_player] && !was_dead_body) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tVectorSubtract(state->origin, oldstate->origin, dist);\n\t\tif (DotProduct(dist, dist) > 22500)\n\t\t\tcontinue;\n\n\t\tVectorScale(dist, 1 / (nextdemotime - olddemotime), pplayer->oldv);\n\n\t\tVectorCopy(state->origin, pplayer->oldo);\n\t\tVectorCopy(state->command.angles, pplayer->olda);\n\n\t\tpplayer->oldstate = oldstate;\n\t\tpplayer->predict = true;\n\t}\n\n\t// nails\n\tfor (i = 0; i < cl_num_projectiles; i++) {\n\t\tif (!cl.int_projectiles[i].interpolate)\n\t\t\tcontinue;\n\n\t\tVectorCopy(cl.int_projectiles[i].origin, cl_projectiles[i].origin);\n\t}\n}\n\nvoid MVD_Interpolate(void)\n{\n\tint i, j;\n\tfloat f;\n\tframe_t\t*frame, *oldframe;\n\tplayer_state_t *state, *oldstate, *self, *oldself;\n\tstruct predicted_player *pplayer;\n\tstatic double old;\n\n\tself = &cl.frames[cl.parsecount & UPDATE_MASK].playerstate[cl.playernum];\n\toldself = &cl.frames[(cls.netchan.outgoing_sequence - 1) & UPDATE_MASK].playerstate[cl.playernum];\n\n\tself->messagenum = cl.parsecount;\n\n\tVectorCopy(oldself->origin, self->origin);\n\tVectorCopy(oldself->velocity, self->velocity);\n\tVectorCopy(oldself->viewangles, self->viewangles);\n\n\tif (old != nextdemotime) {\n\t\told = nextdemotime;\n\t\tMVD_InitInterpolation();\n\t}\n\n\tCL_ParseClientdata();\n\n\tcls.netchan.outgoing_sequence = cl.parsecount + 1;\n\n\tif (!cl.validsequence)\n\t\treturn;\n\n\tif (nextdemotime <= olddemotime)\n\t\treturn;\n\n\tframe = &cl.frames[cl.parsecount & UPDATE_MASK];\n\toldframe = &cl.frames[cl.oldparsecount & UPDATE_MASK];\n\n\tf = bound(0, (cls.demotime - olddemotime) / (nextdemotime - olddemotime), 1);\n\n\t// interpolate nails\n\tfor (i = 0; i < cl_num_projectiles; i++)\t{\n\t\tif (!cl.int_projectiles[i].interpolate)\n\t\t\tcontinue;\n\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tcl_projectiles[i].origin[j] = cl_oldprojectiles[cl.int_projectiles[i].oldindex].origin[j] +\n\t\t\t\tf * (cl.int_projectiles[i].origin[j] - cl_oldprojectiles[cl.int_projectiles[i].oldindex].origin[j]);\n\t\t}\n\t}\n\n\t// interpolate clients\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\textern cvar_t cl_debug_antilag_view;\n\t\textern cvar_t cl_debug_antilag_ghost;\n\t\textern cvar_t cl_debug_antilag_self;\n\n\t\tpplayer = &predicted_players[i];\n\t\tstate = &frame->playerstate[i];\n\t\toldstate = &oldframe->playerstate[i];\n\n\t\tif (pplayer->predict) {\n\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\tstate->viewangles[j] = MVD_AdjustAngle(oldstate->command.angles[j], pplayer->olda[j], f);\n\t\t\t\tstate->origin[j] = oldstate->origin[j] + f * (pplayer->oldo[j] - oldstate->origin[j]);\n\t\t\t\tstate->velocity[j] = oldstate->velocity[j] + f * (pplayer->oldv[j] - oldstate->velocity[j]);\n\t\t\t}\n\t\t}\n\n\t\t// copy the antilag values for currently viewed client\n\t\tVectorCopy(state->origin, state->current_origin);\n\t\tVectorCopy(cl.antilag_positions[i].pos, state->rewind_origin);\n\t\tVectorCopy(cl.antilag_positions[i].clientpos, state->client_origin);\n\t\tstate->antilag_flags = (cl.antilag_positions[i].present ? dbg_antilag_rewind_present : 0) | (cl.antilag_positions[i].clientpresent ? dbg_antilag_client_present : 0);\n\n\t\tif (i == cl.viewplayernum && !cl_debug_antilag_self.integer) {\n\t\t\t// only move the other players, not the current view\n\t\t\tcontinue;\n\t\t}\n\n\t\t// show the antilagged positions from the current player's point of view\n\t\tif (cl_debug_antilag_view.integer && cl.spectator == cl.autocam) {\n\t\t\tif (cl_debug_antilag_view.integer == 1) {\n\t\t\t\tif (cl.antilag_positions[i].present) {\n\t\t\t\t\tVectorCopy(cl.antilag_positions[i].pos, state->origin);\n\t\t\t\t\tstate->antilag_flags |= dbg_antilag_position_set;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (cl_debug_antilag_view.integer == 2) {\n\t\t\t\tif (cl.antilag_positions[i].clientpresent) {\n\t\t\t\t\tVectorCopy(cl.antilag_positions[i].clientpos, state->origin);\n\t\t\t\t\tstate->antilag_flags |= dbg_antilag_position_set;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid CL_ClearPredict(void) {\n\tmemset(predicted_players, 0, sizeof(predicted_players));\n\tmvd_fixangle = 0;\n}\n\nvoid CL_CalcPlayerFPS(player_info_t *info, int msec)\n{\n    info->fps_msec += msec;\n    info->fps_frames++;\n    if (info->fps < 0  ||  cls.realtime - info->fps_measure_time > 2  ||  cls.realtime < info->fps_measure_time)\n    {\n        info->fps_measure_time = cls.realtime;\n        info->fps_frames = 0;\n        info->fps_msec = 0;\n        info->fps = 0;\n    }\n\n    if (cls.realtime - info->fps_measure_time >= 1)\n    {\n        if (info->fps_msec  &&  info->fps_frames)\n        {\n            info->fps = (int)(1000.0/(info->fps_msec/(double)info->fps_frames));\n            info->last_fps = info->fps;\n        }\n        else\n\t\t\tinfo->fps = 0;\n\t\tinfo->fps_msec = 0;\n\t\tinfo->fps_frames = 0;\n\t\tinfo->fps_measure_time += 1.0;\n    }\n}\n\nqbool CL_DrawnPlayerPosition(int player_num, float* pos, int* msec)\n{\n\tif (pos) {\n\t\tVectorCopy(predicted_players[player_num].drawn_origin, pos);\n\t}\n\tif (msec) {\n\t\t*msec = predicted_players[player_num].msec;\n\t}\n\treturn predicted_players[player_num].drawn;\n}\n"
  },
  {
    "path": "src/cl_input.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\nSee the GNU General Public License for more details.\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n// cl.input.c  -- builds an intended movement command to send to the server\n\n#include \"quakedef.h\"\n#include \"movie.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"input.h\"\n#include \"pmove.h\"\t\t// PM_FLY etc\n#include \"rulesets.h\"\n\nstatic void IN_AttackUp_CommonHide(void);\n\ncvar_t cl_anglespeedkey = { \"cl_anglespeedkey\",\"1.5\" };\ncvar_t cl_backspeed = { \"cl_backspeed\",\"400\" };\ncvar_t cl_c2spps = { \"cl_c2spps\",\"0\" };\ncvar_t cl_c2sImpulseBackup = { \"cl_c2sImpulseBackup\",\"3\" };\ncvar_t cl_c2sdupe = { \"cl_c2sdupe\", \"0\" };\ncvar_t cl_forwardspeed = { \"cl_forwardspeed\",\"400\" };\ncvar_t cl_smartjump = { \"cl_smartjump\", \"1\" };\ncvar_t cl_iDrive = { \"cl_iDrive\", \"0\", 0, Rulesets_OnChange_cl_iDrive };\ncvar_t cl_movespeedkey = { \"cl_movespeedkey\",\"2.0\" };\ncvar_t cl_nodelta = { \"cl_nodelta\",\"0\" };\ncvar_t cl_pitchspeed = { \"cl_pitchspeed\",\"150\" };\ncvar_t cl_upspeed = { \"cl_upspeed\",\"400\" };\ncvar_t cl_safestrafe = {\"cl_safestrafe\", \"0\"};\ncvar_t cl_sidespeed = { \"cl_sidespeed\",\"400\" };\ncvar_t cl_yawspeed = { \"cl_yawspeed\",\"140\" };\ncvar_t cl_weaponhide = { \"cl_weaponhide\", \"0\" };\ncvar_t cl_weaponpreselect = { \"cl_weaponpreselect\", \"0\" };\ncvar_t cl_weaponforgetorder = { \"cl_weaponforgetorder\", \"0\" };\ncvar_t cl_weaponhide_axe = { \"cl_weaponhide_axe\", \"0\" };\ncvar_t freelook = { \"freelook\",\"1\" };\ncvar_t lookspring = { \"lookspring\",\"0\" };\ncvar_t lookstrafe = { \"lookstrafe\",\"0\" };\n\ncvar_t sensitivity = { \"sensitivity\",\"12\" };\ncvar_t cursor_sensitivity = { \"scr_cursor_sensitivity\", \"1\" };\ncvar_t m_pitch = { \"m_pitch\",\"0.022\" };\ncvar_t m_yaw = { \"m_yaw\",\"0.022\" };\ncvar_t m_forward = { \"m_forward\",\"1\" };\ncvar_t m_side = { \"m_side\",\"0.8\" };\ncvar_t m_accel = { \"m_accel\", \"0\" };\ncvar_t m_accel_offset = { \"m_accel_offset\", \"0\" };\ncvar_t m_accel_power = { \"m_accel_power\", \"2\" };\ncvar_t m_accel_senscap = { \"m_accel_senscap\", \"0\" };\n\n#ifdef JSS_CAM\ncvar_t cam_zoomspeed = { \"cam_zoomspeed\", \"300\" };\ncvar_t cam_zoomaccel = { \"cam_zoomaccel\", \"2000\" };\n#endif\n\nextern cvar_t cl_independentPhysics;\nextern qbool physframe;\nextern double physframetime;\n\n#define CL_INPUT_WEAPONHIDE() ((cl_weaponhide.integer == 1) \\\n\t|| ((cl_weaponhide.integer == 2) && (cl.deathmatch == 1)))\n\n/*\n===============================================================================\nKEY BUTTONS\nContinuous button event tracking is complicated by the fact that two different\ninput sources (say, mouse button 1 and the control key) can both press the\nsame button, but the button should only be released when both of the\npressing key have been released.\nWhen a key event issues a button command (+forward, +attack, etc), it appends\nits key number as a parameter to the command so it can be matched up with\nthe release.\nstate bit 0 is the current state of the key\nstate bit 1 is edge triggered on the up to down transition\nstate bit 2 is edge triggered on the down to up transition\n===============================================================================\n*/\n\nkbutton_t in_mlook, in_klook;\nkbutton_t in_left, in_right, in_forward, in_back;\nkbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright;\nkbutton_t in_strafe, in_speed, in_use, in_jump, in_attack, in_attack2;\nkbutton_t in_up, in_down;\n\nint in_impulse;\n\n#define VOID_KEY (-1)\n#define NULL_KEY (-2)\n\nvoid KeyDown_common(kbutton_t* b, int k)\n{\n\tif (k != NULL_KEY) {\n\t\tif (k == b->down[0] || k == b->down[1])\n\t\t\treturn;\t\t// repeating key\n\n\t\tif (!b->down[0]) {\n\t\t\tb->down[0] = k;\n\t\t}\n\t\telse if (!b->down[1]) {\n\t\t\tb->down[1] = k;\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"Three keys down for a button!\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (b->state & 1)\n\t\treturn;\t\t// still down\n\tb->state |= 1 + 2;\t// down + impulse down\n\tb->downtime = curtime;\n}\n\nqbool KeyUp_common(kbutton_t* b, int k)\n{\n\tif (k == VOID_KEY || k == NULL_KEY) { // typed manually at the console, assume for unsticking, so clear all\n\t\tb->down[0] = b->down[1] = 0;\n\t\tb->state &= ~1;\t\t// now up\n\t\tb->state |= 4; \t\t// impulse up\n\t\tb->uptime = curtime;\n\t\treturn true;\n\t}\n\n\tif (b->down[0] == k)\n\t\tb->down[0] = 0;\n\telse if (b->down[1] == k)\n\t\tb->down[1] = 0;\n\telse\n\t\treturn true;\t\t// key up without coresponding down (menu pass through)\n\n\tif (b->down[0] || b->down[1])\n\t\treturn false;\t\t// some other key is still holding it down\n\n\tif (!(b->state & 1))\n\t\treturn true;\t\t// still up (this should not happen)\n\tb->state &= ~1;\t\t// now up\n\tb->state |= 4; \t\t// impulse up\n\tb->uptime = curtime;\n\treturn true;\n}\n\nvoid KeyDown(kbutton_t* b)\n{\n\tint k = VOID_KEY;\n\tchar* c = Cmd_Argv(1);\n\tif (*c) {\n\t\tk = atoi(c);\n\t}\n\n\tKeyDown_common(b, k);\n}\n\n// returns whether the button is now up, will not be if other key is holding it down\nqbool KeyUp(kbutton_t* b)\n{\n\tint k = VOID_KEY;\n\tchar* c = Cmd_Argv(1);\n\tif (*c) {\n\t\tk = atoi(c);\n\t}\n\n\treturn KeyUp_common(b, k);\n}\n\n\nvoid IN_KLookDown(void)\n{\n\tKeyDown(&in_klook);\n}\nvoid IN_KLookUp(void)\n{\n\tKeyUp(&in_klook);\n}\n\nvoid IN_MLookDown(void)\n{\n\tKeyDown(&in_mlook);\n}\nvoid IN_MLookUp(void)\n{\n\tif (concussioned) {\n\t\treturn;\n\t}\n\n\tKeyUp(&in_mlook);\n\n\tif (!mlook_active && lookspring.value) {\n\t\tV_StartPitchDrift();\n\t}\n}\n\n#define PROTECTEDKEY()                                 \\\n\tif (allow_scripts.integer == 0) {                  \\\n\t\tCom_Printf(\"Movement scripts are disabled\\n\"); \\\n\t\treturn;                                        \\\n\t}\n\nvoid IN_UpDown(void) { PROTECTEDKEY(); KeyDown(&in_up); }\nvoid IN_UpUp(void) { PROTECTEDKEY(); KeyUp(&in_up); }\nvoid IN_DownDown(void) { PROTECTEDKEY(); KeyDown(&in_down); }\nvoid IN_DownUp(void) { PROTECTEDKEY(); KeyUp(&in_down); }\nvoid IN_LeftDown(void) { PROTECTEDKEY(); KeyDown(&in_left); }\nvoid IN_LeftUp(void) { PROTECTEDKEY(); KeyUp(&in_left); }\nvoid IN_RightDown(void) { PROTECTEDKEY(); KeyDown(&in_right); }\nvoid IN_RightUp(void) { PROTECTEDKEY(); KeyUp(&in_right); }\nvoid IN_ForwardDown(void) { PROTECTEDKEY(); KeyDown(&in_forward); }\nvoid IN_ForwardUp(void) { PROTECTEDKEY(); KeyUp(&in_forward); }\nvoid IN_BackDown(void) { PROTECTEDKEY(); KeyDown(&in_back); }\nvoid IN_BackUp(void) { PROTECTEDKEY(); KeyUp(&in_back); }\nvoid IN_LookupDown(void) { PROTECTEDKEY(); KeyDown(&in_lookup); }\nvoid IN_LookupUp(void) { PROTECTEDKEY(); KeyUp(&in_lookup); }\nvoid IN_LookdownDown(void) { PROTECTEDKEY(); KeyDown(&in_lookdown); }\nvoid IN_LookdownUp(void) { PROTECTEDKEY(); KeyUp(&in_lookdown); }\nvoid IN_MoveleftDown(void) { PROTECTEDKEY(); KeyDown(&in_moveleft); }\nvoid IN_MoveleftUp(void) { PROTECTEDKEY(); KeyUp(&in_moveleft); }\nvoid IN_MoverightDown(void) { PROTECTEDKEY(); KeyDown(&in_moveright); }\nvoid IN_MoverightUp(void) { PROTECTEDKEY(); KeyUp(&in_moveright); }\n\nvoid IN_SpeedDown(void) { KeyDown(&in_speed); }\nvoid IN_SpeedUp(void) { KeyUp(&in_speed); }\nvoid IN_StrafeDown(void) { KeyDown(&in_strafe); }\nvoid IN_StrafeUp(void) { KeyUp(&in_strafe); }\n\n// Returns true if given command is a protected movement command and was executed successfully\nqbool Key_TryMovementProtected(const char* cmd, qbool down, int key)\n{\n\ttypedef void (*KeyPress_fnc) (kbutton_t* b, int key);\n\tKeyPress_fnc f = down ? KeyDown_common : (KeyPress_fnc)KeyUp_common;\n\tkbutton_t* b = NULL;\n\n\tif (strcmp(cmd, \"+forward\") == 0) b = &in_forward;\n\telse if (strcmp(cmd, \"+back\") == 0) b = &in_back;\n\telse if (strcmp(cmd, \"+moveleft\") == 0) b = &in_moveleft;\n\telse if (strcmp(cmd, \"+moveright\") == 0) b = &in_moveright;\n\telse if (strcmp(cmd, \"+left\") == 0) b = &in_left;\n\telse if (strcmp(cmd, \"+right\") == 0) b = &in_right;\n\telse if (strcmp(cmd, \"+lookup\") == 0) b = &in_lookup;\n\telse if (strcmp(cmd, \"+lookdown\") == 0) b = &in_lookdown;\n\telse if (strcmp(cmd, \"+moveup\") == 0) b = &in_up;\n\telse if (strcmp(cmd, \"+movedown\") == 0) b = &in_down;\n\n\tif (b) {\n\t\tf(b, key);\n\t\treturn true;\n\t}\n\telse {\n\t\treturn false;\n\t}\n}\n\nvoid IN_AttackDown(void)\n{\n\tint best;\n\tif (cl_weaponpreselect.value && (best = IN_BestWeapon(false)))\n\t\tin_impulse = best;\n\n\tKeyDown(&in_attack);\n}\n\n// Checks if we have a keycode at the end, e.g. +fire 8 5 3 120\n// if it's >= 32, treat is as keycodes, otherwise an impulse\nstatic qbool IN_IsLastArgKeyCode(void)\n{\n\treturn atoi(Cmd_Argv(Cmd_Argc() - 1)) >= 32;\n}\n\nstatic void IN_AntiRolloverFireKeyDown(int key_code)\n{\n\t// Actual firing has already happened in +fire_ar handler, so just store here...\n\n\tif (key_code) {\n\t\tint i;\n\n\t\t// shouldn't happen, but prevent duplicates\n\t\tfor (i = 0; i < cl.ar_count; ++i) {\n\t\t\tif (cl.ar_keycodes[i] == key_code) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// add to the stack\n\t\tif (cl.ar_count < sizeof(cl.ar_keycodes) / sizeof(cl.ar_keycodes[0])) {\n\t\t\tcl.ar_keycodes[cl.ar_count] = key_code;\n\t\t\tmemcpy(cl.ar_weapon_orders[cl.ar_count], cl.weapon_order, sizeof(cl.ar_weapon_orders[cl.ar_count]));\n\t\t\t++cl.ar_count;\n\t\t}\n\t}\n}\n\nstatic void IN_AntiRolloverFireKeyUp(int key_code)\n{\n\tint i;\n\n\tif (cl.ar_count > 0 && cl.ar_keycodes[cl.ar_count - 1] == key_code) {\n\t\t// Found in most recent position: use weaponlist from previously pressed button\n\t\t--cl.ar_count;\n\n\t\tif (cl.ar_count > 0) {\n\t\t\tint prev = cl.ar_count - 1;\n\n\t\t\tmemcpy(cl.weapon_order, cl.ar_weapon_orders[prev], sizeof(cl.weapon_order));\n\t\t\tin_impulse = IN_BestWeapon(false);\n\t\t\tKeyDown_common(&in_attack, NULL_KEY);\n\t\t}\n\t\telse {\n\t\t\tKeyUp_common(&in_attack, NULL_KEY);\n\t\t\tIN_AttackUp_CommonHide();\n\t\t}\n\t}\n\telse {\n\t\t// Not the most recent, so just remove from the list silently\n\t\tfor (i = 0; i < cl.ar_count - 1; ++i) {\n\t\t\tif (cl.ar_keycodes[i] == key_code) {\n\t\t\t\tmemcpy(&cl.ar_keycodes[i], &cl.ar_keycodes[i + 1], sizeof(cl.ar_keycodes[0]) * (cl.ar_count - 1 - i));\n\t\t\t\tmemcpy(&cl.ar_weapon_orders[i], &cl.ar_weapon_orders[i + 1], sizeof(cl.ar_weapon_orders[0]) * (cl.ar_count - 1 - i));\n\t\t\t\t--i;\n\t\t\t\t--cl.ar_count;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid IN_FireDown(void)\n{\n\tint key_code = VOID_KEY;\n\tint last_arg_idx = Cmd_Argc() - 1;\n\tint i;\n\tqbool anti_rollover = !strcasecmp(Cmd_Argv(0), \"+fire_ar\");\n\n\tif (Cmd_Argc() < 2) {\n\t\tCom_Printf(\"Usage: %s <weapon number>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif (IN_IsLastArgKeyCode()) {\n\t\tkey_code = Q_atoi(Cmd_Argv(last_arg_idx));\n\t\tlast_arg_idx--;\n\t}\n\n\tfor (i = 1; i <= last_arg_idx && i <= MAXWEAPONS; i++) {\n\t\tint desired_impulse = Q_atoi(Cmd_Argv(i));\n\t\tcl.weapon_order[i - 1] = desired_impulse;\n\t}\n\n\tfor (; i <= MAXWEAPONS; i++) {\n\t\tcl.weapon_order[i - 1] = 0;\n\t}\n\n\tin_impulse = IN_BestWeapon(false);\n\n\tif (anti_rollover && key_code) {\n\t\tKeyDown_common(&in_attack, NULL_KEY);\n\t\tIN_AntiRolloverFireKeyDown(key_code);\n\t}\n\telse {\n\t\tKeyDown_common(&in_attack, key_code);\n\t}\n}\n\nstatic void IN_AttackUp_CommonHide(void)\n{\n\tif (CL_INPUT_WEAPONHIDE()) \t{\n\t\tif (cl_weaponhide_axe.integer) \t\t{\n\t\t\t// always switch to axe because user wants to\n\t\t\tin_impulse = 1;\n\t\t}\n\t\telse \t\t{\n\t\t\t// performs \"weapon 2 1\"\n\t\t\t// that means: if player has shotgun and shells, select shotgun, otherwise select axe\n\t\t\tin_impulse = ((cl.stats[STAT_ITEMS] & IT_SHOTGUN) && cl.stats[STAT_SHELLS] >= 1) ? 2 : 1;\n\t\t}\n\t}\n}\n\nvoid IN_FireUp(void)\n{\n\tint key_code = VOID_KEY;\n\tqbool anti_rollover = !strcasecmp(Cmd_Argv(0), \"-fire_ar\");\n\n\tif (IN_IsLastArgKeyCode()) {\n\t\tkey_code = Q_atoi(Cmd_Argv(Cmd_Argc() - 1));\n\t}\n\n\tif (key_code && anti_rollover) {\n\t\tIN_AntiRolloverFireKeyUp(key_code);\n\t}\n\telse if (KeyUp_common(&in_attack, key_code)) {\n\t\tIN_AttackUp_CommonHide();\n\t}\n}\n\n\nvoid IN_AttackUp(void)\n{\n\tqbool up = KeyUp(&in_attack);\n\tif (up) {\n\t\tIN_AttackUp_CommonHide();\n\t}\n}\n\nvoid IN_UseDown(void) { KeyDown(&in_use); }\nvoid IN_UseUp(void) { KeyUp(&in_use); }\nvoid IN_Attack2Down(void) { KeyDown(&in_attack2); }\nvoid IN_Attack2Up(void) { KeyUp(&in_attack2); }\n\n\nvoid IN_JumpDown(void)\n{\n\tqbool up;\n\tint pmt;\n\n\tif (cls.state != ca_active || !cl_smartjump.value)\n\t\tup = false;\n\telse if (cls.demoplayback && !cls.mvdplayback)\n\t\tup = false; // use jump instead of up in demos unless its MVD and I have no idea why QWD have this restriction.\n\telse if (cl.spectator)\n\t\tup = (Cam_TrackNum() == -1); // NOTE: cl.spectator is non false during MVD playback, so this code executed.\n\telse if (cl.stats[STAT_HEALTH] <= 0)\n\t\tup = false;\n\telse if (cl.validsequence && (\n\t\t((pmt = cl.frames[cl.validsequence & UPDATE_MASK].playerstate[cl.playernum].pm_type) == PM_FLY)\n\t\t|| pmt == PM_SPECTATOR || pmt == PM_OLD_SPECTATOR))\n\t\tup = true;\n\telse if (cl.waterlevel >= 2 && !(cl.teamfortress && (in_forward.state & 1)))\n\t\tup = true;\n\telse\n\t\tup = false;\n\n\tKeyDown(up ? &in_up : &in_jump);\n}\nvoid IN_JumpUp(void)\n{\n\tif (cl_smartjump.value)\n\t\tKeyUp(&in_up);\n\tKeyUp(&in_jump);\n}\n\n// called within 'impulse' or 'weapon' commands, remembers it's first 10 (MAXWEAPONS) arguments\nvoid IN_RememberWpOrder(void)\n{\n\tint i, c;\n\tc = Cmd_Argc() - 1;\n\n\tfor (i = 0; i < MAXWEAPONS; i++)\n\t\tcl.weapon_order[i] = (i < c) ? Q_atoi(Cmd_Argv(i + 1)) : 0;\n}\n\nstatic int IN_BestWeapon_Common(int implicit, int* weapon_order, qbool persist, qbool rendering_only);\n\n// picks the best available (carried & having some ammunition) weapon according to users current preference\n// or if the intersection (whished * carried) is empty\n// select the top wished weapon\nint IN_BestWeapon(qbool rendering_only)\n{\n\treturn IN_BestWeapon_Common(cl.weapon_order[0], cl.weapon_order, cl_weaponforgetorder.integer != 1, rendering_only);\n}\n\n// picks the best available (carried & having some ammunition) weapon according to users current preference\n// or if the intersection (whished * carried) is empty\n// select the current weapon\nint IN_BestWeaponReal(qbool rendering_only)\n{\n\treturn IN_BestWeapon_Common(in_impulse, cl.weapon_order, cl_weaponforgetorder.integer != 1, rendering_only);\n}\n\n// finds the best weapon from the carried weapons; if none is found, returns implicit\nstatic int IN_BestWeapon_Common(int implicit, int* weapon_order, qbool persist, qbool rendering_only)\n{\n\tint i, imp, items;\n\tint best = implicit;\n\n\titems = cl.stats[STAT_ITEMS];\n\n\tfor (i = MAXWEAPONS - 1; i >= 0; i--) \t{\n\t\timp = weapon_order[i];\n\t\tif (imp < 1 || imp > 8)\n\t\t\tcontinue;\n\n\t\tswitch (imp) \t\t{\n\t\tcase 1:\n\t\t\tif (items & IT_AXE)\n\t\t\t\tbest = 1;\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tif (items & IT_SHOTGUN && cl.stats[STAT_SHELLS] >= 1)\n\t\t\t\tbest = 2;\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\tif (items & IT_SUPER_SHOTGUN && cl.stats[STAT_SHELLS] >= 2)\n\t\t\t\tbest = 3;\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\tif (items & IT_NAILGUN && cl.stats[STAT_NAILS] >= 1)\n\t\t\t\tbest = 4;\n\t\t\tbreak;\n\t\tcase 5:\n\t\t\tif (items & IT_SUPER_NAILGUN && cl.stats[STAT_NAILS] >= 2)\n\t\t\t\tbest = 5;\n\t\t\tbreak;\n\t\tcase 6:\n\t\t\tif (items & IT_GRENADE_LAUNCHER && cl.stats[STAT_ROCKETS] >= 1)\n\t\t\t\tbest = 6;\n\t\t\tbreak;\n\t\tcase 7:\n\t\t\tif (items & IT_ROCKET_LAUNCHER && cl.stats[STAT_ROCKETS] >= 1)\n\t\t\t\tbest = 7;\n\t\t\tbreak;\n\t\tcase 8:\n\t\t\tif (items & IT_LIGHTNING && cl.stats[STAT_CELLS] >= 1)\n\t\t\t\tbest = 8;\n\t\t}\n\t}\n\n\tif (!rendering_only) {\n\t\t/* If weapon order shouldn't persist, set the first element\n\t\t * of the order to the most recently selected weapon\n\t\t */\n\t\tif (!persist) {\n\t\t\tweapon_order[0] = best;\n\t\t\tweapon_order[1] = (cl_weaponhide_axe.integer || best == 2 ? 1 : 2);\n\t\t\tweapon_order[2] = (weapon_order[1] == 1 ? 0 : 1);\n\t\t\tweapon_order[3] = 0;\n\t\t}\n\t}\n\n\treturn best;\n}\n\nvoid IN_Impulse(void)\n{\n\tint best;\n\n\tin_impulse = Q_atoi(Cmd_Argv(1));\n\n\tif (Cmd_Argc() <= 2)\n\t\treturn;\n\n\t// If more than one argument, select immediately the best weapon.\n\tIN_RememberWpOrder();\n\tif ((best = IN_BestWeapon(false))) {\n\t\tin_impulse = best;\n\t}\n}\n\n// This is the same command as impulse but cl_weaponpreselect can be used in here, while for impulses cannot be used.\nvoid IN_Weapon(void)\n{\n\tint c, best, mode, first;\n\n\tif ((c = Cmd_Argc() - 1) < 1) {\n\t\tCom_Printf(\"Usage: %s w1 [w2 [w3..]]\\nWill pre-select best available weapon from given sequence.\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tfirst = Q_atoi(Cmd_Argv(1));\n\tif (first == 10) {\n\t\tint best_temp = IN_BestWeapon(false);\n\t\tint temp_order[10] = {\n\t\t\tbest_temp % 8 + 1,\n\t\t\t(best_temp + 1) % 8 + 1,\n\t\t\t(best_temp + 2) % 8 + 1,\n\t\t\t(best_temp + 3) % 8 + 1,\n\t\t\t(best_temp + 4) % 8 + 1,\n\t\t\t(best_temp + 5) % 8 + 1,\n\t\t\t(best_temp + 6) % 8 + 1,\n\t\t\t(best_temp + 7) % 8 + 1,\n\t\t\t0,\n\t\t\t0\n\t\t};\n\n\t\tcl.weapon_order[0] = best = IN_BestWeapon_Common(1, temp_order, false, false);\n\t}\n\telse if (first == 12) {\n\t\tint best_temp = IN_BestWeapon(false);\n\t\tint temp_order[10] = {\n\t\t\t8 - ((8 - best_temp + 1) % 8),\n\t\t\t8 - ((8 - best_temp + 2) % 8),\n\t\t\t8 - ((8 - best_temp + 3) % 8),\n\t\t\t8 - ((8 - best_temp + 4) % 8),\n\t\t\t8 - ((8 - best_temp + 5) % 8),\n\t\t\t8 - ((8 - best_temp + 6) % 8),\n\t\t\t8 - ((8 - best_temp + 7) % 8),\n\t\t\t8 - ((8 - best_temp + 8) % 8),\n\t\t\t0,\n\t\t\t0\n\t\t};\n\n\t\tcl.weapon_order[0] = best = IN_BestWeapon_Common(1, temp_order, false, false);\n\t}\n\telse {\n\t\t// read user input\n\t\tIN_RememberWpOrder();\n\n\t\tbest = IN_BestWeapon(false);\n\t}\n\n\tmode = (int)cl_weaponpreselect.value;\n\n\t// cl_weaponpreselect behaviour:\n\t// 0: select best weapon right now\n\t// 1: always only pre-select; switch to it on +attack\n\t// 2: user is holding +attack -> select, otherwise just pre-select\n\t// 3: same like 1, but only in deathmatch 1\n\t// 4: same like 2, but only in deathmatch 1\n\tif (mode == 3) {\n\t\tmode = (cl.deathmatch == 1) ? 1 : 0;\n\t}\n\telse if (mode == 4) {\n\t\tmode = (cl.deathmatch == 1) ? 2 : 0;\n\t}\n\n\tswitch (mode) \t{\n\tcase 2:\n\t\tif ((in_attack.state & 3) && best) // user is holding +attack and there is some weapon available\n\t\t\tin_impulse = best;\n\t\tbreak;\n\tcase 1: break;\t// don't select weapon immediately\n\tdefault: case 0:\t// no pre-selection\n\t\tif (best)\n\t\t\tin_impulse = best;\n\n\t\tbreak;\n\t}\n}\n\n/*\nReturns 0.25 if a key was pressed and released during the frame,\n0.5 if it was pressed and held\n0 if held then released, and\n1.0 if held for the entire time\n*/\nfloat CL_KeyState(kbutton_t* key, qbool lookbutton)\n{\n\tfloat val;\n\tqbool impulsedown, impulseup, down;\n\n\timpulsedown = key->state & 2;\n\timpulseup = key->state & 4;\n\tdown = key->state & 1;\n\tval = 0.0;\n\n\tif (impulsedown && !impulseup) \t{\n\t\tif (down)\n\t\t\tval = lookbutton ? 0.5 : 1.0;\t// pressed and held this frame\n\t\telse\n\t\t\tval = 0;\t// I_Error ();\n\t}\n\tif (impulseup && !impulsedown) \t{\n\t\tif (down)\n\t\t\tval = 0.0;\t// I_Error ();\n\t\telse\n\t\t\tval = 0.0;\t// released this frame\n\t}\n\tif (!impulsedown && !impulseup) \t{\n\t\tif (down)\n\t\t\tval = 1.0;\t// held the entire frame\n\t\telse\n\t\t\tval = 0.0;\t// up the entire frame\n\t}\n\tif (impulsedown && impulseup) \t{\n\t\tif (down)\n\t\t\tval = 0.75;\t// released and re-pressed this frame\n\t\telse\n\t\t\tval = 0.25;\t// pressed and released this frame\n\t}\n\n\tkey->state &= 1;\t// clear impulses\n\n\treturn val;\n}\n\n//==========================================================================\n\nvoid CL_Rotate_f(void)\n{\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"Usage: %s <degrees>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif ((cl.fpd & FPD_LIMIT_YAW) || allow_scripts.value < 2) {\n\t\treturn;\n\t}\n\n\tcl.viewangles[YAW] += atof(Cmd_Argv(1));\n\tcl.viewangles[YAW] = anglemod(cl.viewangles[YAW]);\n}\n\n// Moves the local angle positions.\nvoid CL_AdjustAngles(void)\n{\n\tfloat basespeed, speed, up, down, frametime;\n\n\tframetime = (cl_independentPhysics.value == 0 ? cls.trueframetime : physframetime);\n\tif (Movie_IsCapturing()) {\n\t\tframetime = Movie_InputFrametime();\n\t}\n\n\tbasespeed = ((in_speed.state & 1) ? cl_anglespeedkey.value : 1);\n\n\tif (!(in_strafe.state & 1)) {\n\t\tspeed = basespeed * cl_yawspeed.value;\n\t\tif ((cl.fpd & FPD_LIMIT_YAW) || allow_scripts.value < 2)\n\t\t\tspeed = bound(-900, speed, 900);\n\t\tspeed *= frametime;\n\t\tcl.viewangles[YAW] -= speed * CL_KeyState(&in_right, true);\n\t\tcl.viewangles[YAW] += speed * CL_KeyState(&in_left, true);\n\t\tif (cl.viewangles[YAW] < 0)\n\t\t\tcl.viewangles[YAW] += 360.0;\n\t\telse if (cl.viewangles[YAW] > 360)\n\t\t\tcl.viewangles[YAW] -= 360.0;\n\t}\n\n\tspeed = basespeed * cl_pitchspeed.value;\n\tif ((cl.fpd & FPD_LIMIT_PITCH) || allow_scripts.value == 0)\n\t\tspeed = bound(-700, speed, 700);\n\tspeed *= frametime;\n\tif (in_klook.state & 1) \t{\n\t\tV_StopPitchDrift();\n\t\tcl.viewangles[PITCH] -= speed * CL_KeyState(&in_forward, true);\n\t\tcl.viewangles[PITCH] += speed * CL_KeyState(&in_back, true);\n\t}\n\n\n\tup = CL_KeyState(&in_lookup, true);\n\tdown = CL_KeyState(&in_lookdown, true);\n\tcl.viewangles[PITCH] -= speed * up;\n\tcl.viewangles[PITCH] += speed * down;\n\tif (up || down)\n\t\tV_StopPitchDrift();\n\n\tif (cl.viewangles[PITCH] > cl.maxpitch)\n\t\tcl.viewangles[PITCH] = cl.maxpitch;\n\tif (cl.viewangles[PITCH] < cl.minpitch)\n\t\tcl.viewangles[PITCH] = cl.minpitch;\n\n\t//cl.viewangles[PITCH] = bound(cl.min!pitch, cl.viewangles[PITCH], cl.ma!xpitch);\n\tcl.viewangles[ROLL] = bound(-50, cl.viewangles[ROLL], 50);\n}\n\n// Send the intended movement message to the server.\nvoid CL_BaseMove(usercmd_t* cmd)\n{\n\tfloat sidespeed = (float)fabs(cl_sidespeed.value);\n\tfloat upspeed = (float)fabs(cl_upspeed.value);\n\tfloat forwardspeed = (float)fabs(cl_forwardspeed.value);\n\tfloat backspeed = (float)fabs(cl_backspeed.value);\n\tfloat speedmodifier = (float)fabs(cl_movespeedkey.value);\n\n\tCL_AdjustAngles();\n\n\tmemset(cmd, 0, sizeof(*cmd));\n\n\tVectorCopy(cl.viewangles, cmd->angles);\n\n\tif (cl_iDrive.integer) {\n\t\tfloat s1, s2;\n\n\t\tif (in_strafe.state & 1) {\n\t\t\ts1 = CL_KeyState(&in_right, false);\n\t\t\ts2 = CL_KeyState(&in_left, false);\n\n\t\t\tif (s1 && s2) {\n\t\t\t\tif (in_right.downtime > in_left.downtime)\n\t\t\t\t\ts2 = 0;\n\t\t\t\tif (in_right.downtime < in_left.downtime)\n\t\t\t\t\ts1 = 0;\n\t\t\t}\n\n\t\t\tcmd->sidemove += sidespeed * s1;\n\t\t\tcmd->sidemove -= sidespeed * s2;\n\t\t}\n\n\t\ts1 = CL_KeyState(&in_moveright, false);\n\t\ts2 = CL_KeyState(&in_moveleft, false);\n\n\t\tif (s1 && s2) {\n\t\t\tif (in_moveright.downtime > in_moveleft.downtime)\n\t\t\t\ts2 = 0;\n\t\t\tif (in_moveright.downtime < in_moveleft.downtime)\n\t\t\t\ts1 = 0;\n\t\t}\n\n\t\tcmd->sidemove += sidespeed * s1;\n\t\tcmd->sidemove -= sidespeed * s2;\n\n\t\ts1 = CL_KeyState(&in_up, false);\n\t\ts2 = CL_KeyState(&in_down, false);\n\n\t\tif (s1 && s2) {\n\t\t\tif (in_up.downtime > in_down.downtime)\n\t\t\t\ts2 = 0;\n\t\t\tif (in_up.downtime < in_down.downtime)\n\t\t\t\ts1 = 0;\n\t\t}\n\n\t\tcmd->upmove += upspeed * s1;\n\t\tcmd->upmove -= upspeed * s2;\n\n\t\tif (!(in_klook.state & 1)) {\n\t\t\ts1 = CL_KeyState(&in_forward, false);\n\t\t\ts2 = CL_KeyState(&in_back, false);\n\n\t\t\tif (s1 && s2) \t\t\t{\n\t\t\t\tif (in_forward.downtime > in_back.downtime)\n\t\t\t\t\ts2 = 0;\n\t\t\t\tif (in_forward.downtime < in_back.downtime)\n\t\t\t\t\ts1 = 0;\n\t\t\t}\n\n\t\t\tcmd->forwardmove += forwardspeed * s1;\n\t\t\tcmd->forwardmove -= backspeed * s2;\n\t\t}\n\t}\n\telse {\n\t\tif (in_strafe.state & 1) {\n\t\t\tcmd->sidemove += sidespeed * CL_KeyState(&in_right, false);\n\t\t\tcmd->sidemove -= sidespeed * CL_KeyState(&in_left, false);\n\t\t}\n\n\t\tcmd->sidemove += sidespeed * CL_KeyState(&in_moveright, false);\n\t\tcmd->sidemove -= sidespeed * CL_KeyState(&in_moveleft, false);\n\n\t\tcmd->upmove += upspeed * CL_KeyState(&in_up, false);\n\t\tcmd->upmove -= upspeed * CL_KeyState(&in_down, false);\n\n\t\tif (!(in_klook.state & 1)) {\n\t\t\tcmd->forwardmove += forwardspeed * CL_KeyState(&in_forward, false);\n\t\t\tcmd->forwardmove -= backspeed * CL_KeyState(&in_back, false);\n\t\t}\n\t}\n\n\t// adjust for speed key\n\tif (in_speed.state & 1) {\n\t\tcmd->forwardmove *= speedmodifier;\n\t\tcmd->sidemove *= speedmodifier;\n\t\tcmd->upmove *= speedmodifier;\n\t}\n\n#ifdef JSS_CAM\n\t{\n\t\tstatic float zoomspeed = 0;\n\t\textern cvar_t cam_thirdperson, cam_lockpos;\n\n\t\tif ((cls.demoplayback || cl.spectator) && cam_thirdperson.integer && !cam_lockpos.integer) {\n\t\t\tzoomspeed -= CL_KeyState(&in_forward, false) * cls.trueframetime * cam_zoomaccel.value;\n\t\t\tzoomspeed += CL_KeyState(&in_back, false) * cls.trueframetime * cam_zoomaccel.value;\n\t\t\tif (!CL_KeyState(&in_forward, false) && !CL_KeyState(&in_back, false)) {\n\t\t\t\tif (zoomspeed > 0) {\n\t\t\t\t\tzoomspeed -= cls.trueframetime * cam_zoomaccel.value;\n\t\t\t\t\tif (zoomspeed < 0)\n\t\t\t\t\t\tzoomspeed = 0;\n\t\t\t\t}\n\t\t\t\telse if (zoomspeed < 0) {\n\t\t\t\t\tzoomspeed += cls.trueframetime * cam_zoomaccel.value;\n\t\t\t\t\tif (zoomspeed > 0)\n\t\t\t\t\t\tzoomspeed = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tzoomspeed = bound(-cam_zoomspeed.value, zoomspeed, cam_zoomspeed.value);\n\n\t\t\tif (zoomspeed) {\n\t\t\t\textern cvar_t cam_dist;\n\t\t\t\tfloat dist = cam_dist.value;\n\n\t\t\t\tdist += cls.trueframetime * zoomspeed;\n\t\t\t\tif (dist < 0) {\n\t\t\t\t\tdist = 0;\n\t\t\t\t}\n\t\t\t\tCvar_SetValue(&cam_dist, dist);\n\t\t\t}\n\t\t}\n\t}\n#endif // JSS_CAM\n}\n\nint MakeChar(int i)\n{\n\ti &= ~3;\n\tif (i < -127 * 4)\n\t\ti = -127 * 4;\n\tif (i > 127 * 4)\n\t\ti = 127 * 4;\n\treturn i;\n}\n\nvoid CL_FinishMove(usercmd_t* cmd)\n{\n\tint i, ms;\n\tfloat frametime;\n\tstatic double extramsec = 0;\n\textern cvar_t allow_scripts;\n\n\tframetime = cls.trueframetime;\n\tif (Movie_IsCapturing()) {\n\t\tframetime = Movie_InputFrametime();\n\t}\n\n\t// figure button bits\n\tif (in_attack.state & 3)\n\t\tcmd->buttons |= 1;\n\tin_attack.state &= ~2;\n\n\tif (in_jump.state & 3)\n\t\tcmd->buttons |= 2;\n\tin_jump.state &= ~2;\n\n\tif (in_use.state & 3)\n\t\tcmd->buttons |= 4;\n\tin_use.state &= ~2;\n\n\tif (in_attack2.state & 3)\n\t\tcmd->buttons |= 8;\n\tin_attack2.state &= ~2;\n\n\t// send milliseconds of time to apply the move\n\t//#fps\textramsec += cls.frametime * 1000;\n\textramsec += (cl_independentPhysics.value == 0 ? frametime : physframetime) * 1000;\t//#fps\n\tms = extramsec;\n\textramsec -= ms;\n\n\tif (ms > 250) {\n\t\tms = 100;\t\t// time was unreasonable\n\t}\n\n\tcmd->msec = ms;\n\n\tVectorCopy(cl.viewangles, cmd->angles);\n\n\t// shaman RFE 1030281 {\n\t// KTPro's KFJump == impulse 156\n\t// KTPro's KRJump == impulse 164\n\tif (*Info_ValueForKey(cl.serverinfo, \"kmod\") && (\n\t\t((in_impulse == 156) && (cl.fpd & FPD_LIMIT_YAW || allow_scripts.value < 2)) ||\n\t\t((in_impulse == 164) && (cl.fpd & FPD_LIMIT_PITCH || allow_scripts.value == 0))\n\t\t)\n\t\t) {\n\t\tcmd->impulse = 0;\n\t}\n\telse {\n\t\tcmd->impulse = in_impulse;\n\t}\n\t// } shaman RFE 1030281\n\tin_impulse = 0;\n\n\t// chop down so no extra bits are kept that the server wouldn't get\n\tcmd->forwardmove = MakeChar(cmd->forwardmove);\n\tcmd->sidemove = MakeChar(cmd->sidemove);\n\tcmd->upmove = MakeChar(cmd->upmove);\n\n\tfor (i = 0; i < 3; i++) {\n\t\tcmd->angles[i] = (Q_rint(cmd->angles[i] * 65536.0 / 360.0) & 65535) * (360.0 / 65536.0);\n\t}\n}\n\nvoid CL_SendClientCommand(qbool reliable, char* format, ...)\n{\n\tva_list\t\targptr;\n\tchar\t\tstring[2048];\n\n\tif (cls.demoplayback || cls.state == ca_disconnected) {\n\t\treturn;\t// no point.\n\t}\n\n\tva_start(argptr, format);\n\tvsnprintf(string, sizeof(string), format, argptr);\n\tva_end(argptr);\n\n\tif (reliable) {\n\t\tMSG_WriteByte(&cls.netchan.message, clc_stringcmd);\n\t\tMSG_WriteString(&cls.netchan.message, string);\n\t}\n\telse {\n\t\tMSG_WriteByte(&cls.cmdmsg, clc_stringcmd);\n\t\tMSG_WriteString(&cls.cmdmsg, string);\n\t}\n}\n\nint cmdtime_msec = 0;\n\nvoid CL_ApplySafestrafe(usercmd_t *cmd)\n{\n\tint required_frames = max(movevars.safestrafe, cl_safestrafe.value);\n\n\tif (required_frames <= 0)\n\t\treturn;\n\t\n\tfloat current_move = cmd->sidemove;\n\t\n\t// Handle pending stop frames\n\tif (cl.safestrafe.pending_frames > 0) {\n\t\tcmd->sidemove = 0;\n\t\tcl.safestrafe.pending_frames--;\n\t\tcl.safestrafe.stop_frames++;\n\t\tcl.safestrafe.last_sidemove = 0;\n\t\treturn;\n\t}\n\t\n\t// Determine directions\n\tint current_dir = (current_move > 0) - (current_move < 0);\n\tint previous_dir = (cl.safestrafe.last_sidemove > 0) - \n\t                   (cl.safestrafe.last_sidemove < 0);\n\t\n\t// Check for direction change\n\tif (current_dir != 0 && previous_dir != 0 && \n\t    current_dir != previous_dir) {\n\t\t// Direct direction change - enforce stop frames\n\t\tcl.safestrafe.pending_frames = required_frames;\n\t\tcl.safestrafe.pending_direction = current_move;\n\t\tcl.safestrafe.stop_frames = 1;\n\t\tcmd->sidemove = 0;\n\t}\n\telse if (current_dir != 0 && previous_dir == 0) {\n\t\t// Starting movement after stop\n\t\tif (cl.safestrafe.stop_frames < required_frames) {\n\t\t\t// Not enough stop frames\n\t\t\tcl.safestrafe.pending_frames = \n\t\t\t\trequired_frames - cl.safestrafe.stop_frames;\n\t\t\tcl.safestrafe.pending_direction = current_move;\n\t\t\tcl.safestrafe.stop_frames++;\n\t\t\tcmd->sidemove = 0;\n\t\t}\n\t\telse {\n\t\t\t// Enough stop frames - allow movement\n\t\t\tcl.safestrafe.stop_frames = 0;\n\t\t}\n\t}\n\telse if (current_dir == 0) {\n\t\t// Currently stopped\n\t\tcl.safestrafe.stop_frames++;\n\t}\n\t\n\t// Update last move\n\tcl.safestrafe.last_sidemove = cmd->sidemove;\n}\n\nvoid CL_SendCmd(void)\n{\n\tsizebuf_t buf;\n\tbyte data[1024];\n\tusercmd_t* cmd, * oldcmd;\n\tint i, checksumIndex, lost;\n\tqbool dontdrop;\n\tstatic float pps_balance = 0;\n\tstatic int dropcount = 0;\n\n\tif (cls.demoplayback && !cls.mvdplayback) {\n\t\tCL_CalcNet();\n\t\treturn; // sendcmds come from the demo\n\t}\n\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tCL_SendChunkDownloadReq();\n#endif\n\n\t// save this command off for prediction\n\ti = cls.netchan.outgoing_sequence & UPDATE_MASK;\n\tcmd = &cl.frames[i].cmd;\n\tcl.frames[i].senttime = cls.realtime;\n\tcl.frames[i].receivedtime = -1;\t\t// we haven't gotten a reply yet\n\n\t// update network stats table\n\ti = cls.netchan.outgoing_sequence & NETWORK_STATS_MASK;\n\tnetwork_stats[i].delta = 0;     // filled-in later\n\tnetwork_stats[i].sentsize = 0;  // filled-in later\n\tnetwork_stats[i].senttime = cls.realtime;\n\tnetwork_stats[i].receivedtime = -1;\n\n\t// get basic movement from keyboard\n\tCL_BaseMove(cmd);\n\n\t// allow mice or other external controllers to add to the move\n\n\tif (cl_independentPhysics.value == 0 || (physframe && cl_independentPhysics.value != 0)) {\n\t\tIN_Move(cmd);\n\t}\n\n\t// if we are spectator, try autocam\n\tif (cl.spectator) {\n\t\tCam_Track(cmd);\n\t}\n\n\t// Apply safestrafe if enabled on server\n\tif ((movevars.safestrafe > 0 || cl_safestrafe.value > 0) && !cl.spectator) {\n\t\tCL_ApplySafestrafe(cmd);\n\t}\n\n\tCL_FinishMove(cmd);\n\tcmdtime_msec += cmd->msec;\n\n\tCam_FinishMove(cmd);\n\n\tif (cls.mvdplayback) {\n\t\tCL_CalcPlayerFPS(&cl.players[cl.playernum], cmd->msec);\n\t\tcls.netchan.outgoing_sequence++;\n\t\treturn;\n\t}\n\n\tSZ_Init(&buf, data, sizeof(data));\n\n\tSZ_Write(&buf, cls.cmdmsg.data, cls.cmdmsg.cursize);\n\n\tif (cls.cmdmsg.overflowed) {\n\t\tCom_DPrintf(\"cls.cmdmsg overflowed\\n\");\n\t}\n\n\tSZ_Clear(&cls.cmdmsg);\n\n\t// begin a client move command\n\tMSG_WriteByte(&buf, clc_move);\n\n\t// save the position for a checksum byte\n\tchecksumIndex = buf.cursize;\n\tMSG_WriteByte(&buf, 0);\n\n\t// write our lossage percentage\n\tlost = CL_CalcNet();\n\tMSG_WriteByte(&buf, (byte)lost);\n\n\t// send this and the previous two cmds in the message, so if the last packet was dropped, it can be recovered\n\tdontdrop = false;\n\n\ti = (cls.netchan.outgoing_sequence - 2) & UPDATE_MASK;\n\tcmd = &cl.frames[i].cmd;\n\n\tif (cl_c2sImpulseBackup.value >= 2) {\n\t\tdontdrop = dontdrop || cmd->impulse;\n\t}\n\n\tMSG_WriteDeltaUsercmd(&buf, &nullcmd, cmd, cls.mvdprotocolextensions1);\n\toldcmd = cmd;\n\n\ti = (cls.netchan.outgoing_sequence - 1) & UPDATE_MASK;\n\tcmd = &cl.frames[i].cmd;\n\n\tif (cl_c2sImpulseBackup.value >= 3) {\n\t\tdontdrop = dontdrop || cmd->impulse;\n\t}\n\n\tMSG_WriteDeltaUsercmd(&buf, oldcmd, cmd, cls.mvdprotocolextensions1);\n\toldcmd = cmd;\n\n\ti = (cls.netchan.outgoing_sequence) & UPDATE_MASK;\n\tcmd = &cl.frames[i].cmd;\n\tif (cl_c2sImpulseBackup.value >= 1)\n\t\tdontdrop = dontdrop || cmd->impulse;\n\tMSG_WriteDeltaUsercmd(&buf, oldcmd, cmd, cls.mvdprotocolextensions1);\n\n\t// calculate a checksum over the move commands\n\tbuf.data[checksumIndex] = COM_BlockSequenceCRCByte(\n\t\tbuf.data + checksumIndex + 1, buf.cursize - checksumIndex - 1,\n\t\tcls.netchan.outgoing_sequence);\n\n\t// request delta compression of entities\n\tif (cls.netchan.outgoing_sequence - cl.validsequence >= UPDATE_BACKUP - 1) {\n\t\tcl.validsequence = 0;\n\t\tcl.delta_sequence = 0;\n\t}\n\n\tif (cl.delta_sequence && !cl_nodelta.value && cls.state == ca_active) {\n\t\tcl.frames[cls.netchan.outgoing_sequence & UPDATE_MASK].delta_sequence = cl.delta_sequence;\n\t\tMSG_WriteByte(&buf, clc_delta);\n\t\tMSG_WriteByte(&buf, cl.delta_sequence & 255);\n\n\t\t// network stats table\n\t\tnetwork_stats[cls.netchan.outgoing_sequence & NETWORK_STATS_MASK].delta = 1;\n\t}\n\telse {\n\t\tcl.frames[cls.netchan.outgoing_sequence & UPDATE_MASK].delta_sequence = -1;\n\t}\n\n\tif (cls.demorecording) {\n\t\tCL_WriteDemoCmd(cmd);\n\t}\n\n\tif (cl_c2spps.value) {\n\t\tpps_balance += cls.frametime;\n\t\t// never drop more than 2 messages in a row -- that'll cause PL\n\t\t// and don't drop if one of the last two movemessages have an impulse\n\t\tif (pps_balance > 0 || dropcount >= 2 || dontdrop) {\n\t\t\tfloat\tpps;\n\t\t\tpps = cl_c2spps.value;\n\t\t\tif (pps < 10) pps = 10;\n\t\t\tif (pps > 72) pps = 72;\n\t\t\tpps_balance -= 1 / pps;\n\t\t\t// bound pps_balance. FIXME: is there a better way?\n\t\t\tif (pps_balance > 0.1) pps_balance = 0.1;\n\t\t\tif (pps_balance < -0.1) pps_balance = -0.1;\n\t\t\tdropcount = 0;\n\t\t}\n\t\telse {\n\t\t\t// don't count this message when calculating PL\n\t\t\tcl.frames[i].receivedtime = -3;\n\t\t\t// drop this message\n\t\t\tcls.netchan.outgoing_sequence++;\n\t\t\tdropcount++;\n\t\t\treturn;\n\t\t}\n\t}\n\telse {\n\t\tpps_balance = 0;\n\t\tdropcount = 0;\n\t}\n\n#ifdef FTE_PEXT2_VOICECHAT\n\tS_Voip_Transmit(clc_voicechat, &buf);\n#endif\n\n\tcl.frames[cls.netchan.outgoing_sequence & UPDATE_MASK].sentsize = buf.cursize + 8;    // 8 = PACKET_HEADER\n\t// network stats table\n\tnetwork_stats[cls.netchan.outgoing_sequence & NETWORK_STATS_MASK].sentsize = buf.cursize + 8;\n\n\t//send duplicated packets, if set\n\tcls.netchan.dupe = bound(0, cl_c2sdupe.value, MAX_DUPLICATE_PACKETS);\n\n\t// deliver the message\n\tNetchan_Transmit(&cls.netchan, buf.cursize, buf.data);\n}\n\nvoid CL_InitInput(void)\n{\n\tCmd_AddCommand(\"+moveup\", IN_UpDown);\n\tCmd_AddCommand(\"-moveup\", IN_UpUp);\n\tCmd_AddCommand(\"+movedown\", IN_DownDown);\n\tCmd_AddCommand(\"-movedown\", IN_DownUp);\n\tCmd_AddCommand(\"+left\", IN_LeftDown);\n\tCmd_AddCommand(\"-left\", IN_LeftUp);\n\tCmd_AddCommand(\"+right\", IN_RightDown);\n\tCmd_AddCommand(\"-right\", IN_RightUp);\n\tCmd_AddCommand(\"+forward\", IN_ForwardDown);\n\tCmd_AddCommand(\"-forward\", IN_ForwardUp);\n\tCmd_AddCommand(\"+back\", IN_BackDown);\n\tCmd_AddCommand(\"-back\", IN_BackUp);\n\tCmd_AddCommand(\"+lookup\", IN_LookupDown);\n\tCmd_AddCommand(\"-lookup\", IN_LookupUp);\n\tCmd_AddCommand(\"+lookdown\", IN_LookdownDown);\n\tCmd_AddCommand(\"-lookdown\", IN_LookdownUp);\n\tCmd_AddCommand(\"+strafe\", IN_StrafeDown);\n\tCmd_AddCommand(\"-strafe\", IN_StrafeUp);\n\tCmd_AddCommand(\"+moveleft\", IN_MoveleftDown);\n\tCmd_AddCommand(\"-moveleft\", IN_MoveleftUp);\n\tCmd_AddCommand(\"+moveright\", IN_MoverightDown);\n\tCmd_AddCommand(\"-moveright\", IN_MoverightUp);\n\tCmd_AddCommand(\"+speed\", IN_SpeedDown);\n\tCmd_AddCommand(\"-speed\", IN_SpeedUp);\n\tCmd_AddCommand(\"+attack\", IN_AttackDown);\n\tCmd_AddCommand(\"-attack\", IN_AttackUp);\n\tCmd_AddCommand(\"+fire\", IN_FireDown);\n\tCmd_AddCommand(\"-fire\", IN_FireUp);\n\tCmd_AddCommand(\"+fire_ar\", IN_FireDown);\n\tCmd_AddCommand(\"-fire_ar\", IN_FireUp);\n\tCmd_AddCommand(\"+attack2\", IN_Attack2Down);\n\tCmd_AddCommand(\"-attack2\", IN_Attack2Up);\n\tCmd_AddCommand(\"+use\", IN_UseDown);\n\tCmd_AddCommand(\"-use\", IN_UseUp);\n\tCmd_AddCommand(\"+jump\", IN_JumpDown);\n\tCmd_AddCommand(\"-jump\", IN_JumpUp);\n\tCmd_AddCommand(\"impulse\", IN_Impulse);\n\tCmd_AddCommand(\"weapon\", IN_Weapon);\n\tCmd_AddCommand(\"+klook\", IN_KLookDown);\n\tCmd_AddCommand(\"-klook\", IN_KLookUp);\n\tCmd_AddCommand(\"+mlook\", IN_MLookDown);\n\tCmd_AddCommand(\"-mlook\", IN_MLookUp);\n\tCmd_AddCommand(\"rotate\", CL_Rotate_f);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_INPUT_KEYBOARD);\n\n\tCvar_Register(&cl_smartjump);\n\tCvar_Register(&cl_weaponhide);\n\tCvar_Register(&cl_weaponpreselect);\n\tCvar_Register(&cl_weaponforgetorder);\n\tCvar_Register(&cl_weaponhide_axe);\n\tCvar_Register(&cl_upspeed);\n\tCvar_Register(&cl_forwardspeed);\n\tCvar_Register(&cl_backspeed);\n\tCvar_Register(&cl_safestrafe);\n\tCvar_Register(&cl_sidespeed);\n\tCvar_Register(&cl_movespeedkey);\n\tCvar_Register(&cl_yawspeed);\n\tCvar_Register(&cl_pitchspeed);\n\tCvar_Register(&cl_anglespeedkey);\n\tCvar_Register(&cl_iDrive);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_INPUT_MISC);\n\tCvar_Register(&lookspring);\n\tCvar_Register(&lookstrafe);\n\tCvar_Register(&sensitivity);\n\tCvar_Register(&freelook);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_MENU);\n\tCvar_Register(&cursor_sensitivity);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_INPUT_MOUSE);\n\tCvar_Register(&m_pitch);\n\tCvar_Register(&m_yaw);\n\tCvar_Register(&m_forward);\n\tCvar_Register(&m_side);\n\tCvar_Register(&m_accel);\n\tCvar_Register(&m_accel_power);\n\tCvar_Register(&m_accel_offset);\n\tCvar_Register(&m_accel_senscap);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_NETWORK);\n\tCvar_Register(&cl_nodelta);\n\tCvar_Register(&cl_c2sImpulseBackup);\n\tCvar_Register(&cl_c2spps);\n\tCvar_Register(&cl_c2sdupe);\n\tCvar_ResetCurrentGroup();\n\n#ifdef JSS_CAM\n\tCvar_SetCurrentGroup(CVAR_GROUP_SPECTATOR);\n\tCvar_Register(&cam_zoomspeed);\n\tCvar_Register(&cam_zoomaccel);\n\tCvar_ResetCurrentGroup();\n#endif // JSS_CAM\n}\n\nvoid IN_ClearProtectedKeys(void)\n{\n\t// Pretend the user entered -moveleft etc at the console\n\tKeyUp_common(&in_up, VOID_KEY);\n\tKeyUp_common(&in_down, VOID_KEY);\n\tKeyUp_common(&in_left, VOID_KEY);\n\tKeyUp_common(&in_right, VOID_KEY);\n\tKeyUp_common(&in_forward, VOID_KEY);\n\tKeyUp_common(&in_back, VOID_KEY);\n\tKeyUp_common(&in_lookup, VOID_KEY);\n\tKeyUp_common(&in_lookdown, VOID_KEY);\n\tKeyUp_common(&in_moveleft, VOID_KEY);\n\tKeyUp_common(&in_moveright, VOID_KEY);\n}\n"
  },
  {
    "path": "src/cl_main.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n// cl_main.c  -- client main loop\n\n#include \"quakedef.h\"\n#include \"cdaudio.h\"\n#include \"cl_slist.h\"\n#include \"movie.h\"\n#include \"logging.h\"\n#include \"ignore.h\"\n#include \"fchecks.h\"\n#include \"config_manager.h\"\n#include \"mvd_utils.h\"\n#include \"EX_browser.h\"\n#include \"EX_qtvlist.h\"\n#include \"qtv.h\"\n#include \"keys.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"hud_editor.h\"\n#include \"input.h\"\n#include \"gl_model.h\"\n#include \"tr_types.h\"\n#include \"teamplay.h\"\n#include \"tp_triggers.h\"\n#include \"rulesets.h\"\n#include \"version.h\"\n#include \"stats_grid.h\"\n#include \"fmod.h\"\n#include \"sbar.h\"\n#include \"utils.h\"\n#include \"qsound.h\"\n#include \"menu.h\"\n#include \"image.h\"\n#ifndef _WIN32\n#include <netdb.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <arpa/inet.h>\n#endif\n#ifndef CLIENTONLY\n#include \"server.h\"\n#endif\n#include \"fs.h\"\n#include \"help.h\"\n#include \"irc.h\"\n#ifdef _DEBUG\n#include \"parser.h\"\n#endif\n#include \"pmove.h\"\n#include \"vx_tracker.h\"\n#include \"menu_demo.h\"\n#include \"r_local.h\"\n#include \"r_renderer.h\"\n#include \"r_performance.h\"\n#include \"r_program.h\"\n#include \"demo_spawnwarn.h\"\n\nextern qbool ActiveApp, Minimized;\n\n#ifndef CLIENTONLY\nstatic void Dev_PhysicsNormalSet(void);\nstatic void Dev_PhysicsNormalSave(void);\nstatic void Dev_PhysicsNormalShow(void);\n#endif\n\nstatic void Cl_Reset_Min_fps_f(void);\nvoid CL_QWURL_ProcessChallenge(const char *parameters);\n\n// cl_input.c\nvoid onchange_pext_serversideweapon(cvar_t* var, char* value, qbool* cancel);\nvoid onchange_hud_performance_average(cvar_t* var, char* value, qbool* cancel);\n\n#ifdef MVD_PEXT1_HIDDEN_MESSAGES\n// cl_parse.c\nvoid CL_ParseHiddenDataMessage(void);\n#endif\n\nstatic void AuthUsernameChanged(cvar_t* var, char* value, qbool* cancel);\n\ncvar_t\tallow_scripts = {\"allow_scripts\", \"2\", 0, Rulesets_OnChange_allow_scripts};\ncvar_t\trcon_password = {\"rcon_password\", \"\"};\ncvar_t\trcon_address = {\"rcon_address\", \"\"};\ncvar_t\tcl_crypt_rcon = {\"cl_crypt_rcon\", \"1\"};\n\ncvar_t\tcl_timeout = {\"cl_timeout\", \"60\"};\n\ncvar_t\tcl_delay_packet = {\"cl_delay_packet\", \"0\", 0, Rulesets_OnChange_cl_delay_packet};\ncvar_t  cl_delay_packet_target = { \"cl_delay_packet_target\", \"0\", 0, Rulesets_OnChange_cl_delay_packet };\ncvar_t  cl_delay_packet_dev = { \"cl_delay_packet_deviation\", \"0\", 0, Rulesets_OnChange_cl_delay_packet };\n\ncvar_t\tcl_shownet = {\"cl_shownet\", \"0\"};\t// can be 0, 1, or 2\n#if defined(PROTOCOL_VERSION_FTE) || defined(PROTOCOL_VERSION_FTE2) || defined(PROTOCOL_VERSION_MVD1)\ncvar_t  cl_pext = {\"cl_pext\", \"1\"};\t\t\t\t\t// allow/disallow protocol extensions at all.\n\t\t\t\t\t\t\t\t\t\t\t\t\t// some extensions can be explicitly controlled.\ncvar_t  cl_pext_limits = { \"cl_pext_limits\", \"1\" }; // enhanced protocol limits\ncvar_t  cl_pext_other = {\"cl_pext_other\", \"0\"};\t\t// extensions which does not have own variables should be controlled by this variable.\ncvar_t  cl_pext_warndemos = { \"cl_pext_warndemos\", \"1\" }; // if set, user will be warned when saving demos that are not backwards compatible\ncvar_t  cl_pext_lagteleport = { \"cl_pext_lagteleport\", \"1\" }; // server-side adjustment of yaw angle through teleports\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\ncvar_t  cl_pext_serversideweapon = { \"cl_pext_serversideweapon\", \"0\", 0, onchange_pext_serversideweapon }; // server-side weapon selection\n#endif\n#endif\n#ifdef FTE_PEXT_256PACKETENTITIES\ncvar_t\tcl_pext_256packetentities = {\"cl_pext_256packetentities\", \"1\"};\n#endif\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\ncvar_t  cl_pext_chunkeddownloads  = {\"cl_pext_chunkeddownloads\", \"1\"};\ncvar_t  cl_chunksperframe  = {\"cl_chunksperframe\", \"30\"};\n#endif\n\n#ifdef FTE_PEXT_FLOATCOORDS\ncvar_t  cl_pext_floatcoords  = {\"cl_pext_floatcoords\", \"1\"};\n#endif\n\n#ifdef FTE_PEXT_TRANS\ncvar_t cl_pext_alpha = {\"cl_pext_alpha\", \"1\"};\n#endif\n\n#ifdef FTE_PEXT_COLOURMOD\ncvar_t cl_pext_colourmod = {\"cl_pext_colourmod\", \"1\"};\n#endif\n\n#ifdef CLIENTONLY\n#define PROCESS_SERVERPACKETS_IMMEDIATELY (0)\n#else\nstatic cvar_t cl_sv_packetsync = { \"cl_sv_packetsync\", \"1\" };\n#define PROCESS_SERVERPACKETS_IMMEDIATELY (cl_sv_packetsync.integer)\n#endif\n\n\ncvar_t\tcl_sbar\t\t= {\"cl_sbar\", \"0\"};\ncvar_t\tcl_hudswap\t= {\"cl_hudswap\", \"0\"};\ncvar_t\tcl_maxfps\t= {\"cl_maxfps\", \"0\"};\ncvar_t\tcl_maxfps_menu\t= {\"cl_maxfps_menu\", \"0\"};\ncvar_t\tcl_physfps\t= {\"cl_physfps\", \"0\"};\t//#fps\ncvar_t\tcl_physfps_spectator = {\"cl_physfps_spectator\", \"77\"};\ncvar_t  cl_independentPhysics = {\"cl_independentPhysics\", \"1\", 0, Rulesets_OnChange_indphys};\n\ncvar_t\tcl_predict_players = {\"cl_predict_players\", \"1\"};\ncvar_t\tcl_solid_players = {\"cl_solid_players\", \"1\"};\ncvar_t\tcl_predict_half = {\"cl_predict_half\", \"0\"};\n\ncvar_t\thud_fps_min_reset_interval = {\"hud_fps_min_reset_interval\", \"30\"};\ncvar_t  hud_frametime_max_reset_interval = { \"hud_frametime_max_reset_interval\", \"30\" };\ncvar_t  hud_performance_average = { \"hud_performance_average\", \"1\", 0, onchange_hud_performance_average };\n\ncvar_t  localid = {\"localid\", \"\"};\n\nstatic qbool allowremotecmd = true;\n\ncvar_t\tcl_deadbodyfilter = {\"cl_deadbodyFilter\", \"0\"};\ncvar_t\tcl_gibfilter = {\"cl_gibFilter\", \"0\"};\ncvar_t\tcl_backpackfilter = {\"cl_backpackfilter\", \"0\"};\ncvar_t\tcl_muzzleflash = {\"cl_muzzleflash\", \"1\"};\ncvar_t\tcl_rocket2grenade = {\"cl_r2g\", \"0\"};\ncvar_t\tcl_demospeed = {\"cl_demospeed\", \"1\"};\ncvar_t\tcl_staticsounds = {\"cl_staticSounds\", \"1\"};\ncvar_t\tcl_fakeshaft = {\"cl_fakeshaft\", \"1\", 0, Rulesets_OnChange_cl_fakeshaft};\ncvar_t\tcl_fakeshaft_extra_updates = {\"cl_fakeshaft_extra_updates\", \"1\"};\ncvar_t\tcl_parseWhiteText = {\"cl_parseWhiteText\", \"1\"};\ncvar_t\tcl_filterdrawviewmodel = {\"cl_filterdrawviewmodel\", \"0\"};\ncvar_t\tcl_demoPingInterval = {\"cl_demoPingInterval\", \"5\"};\ncvar_t  demo_getpings      = {\"demo_getpings\",    \"1\"};\ncvar_t\tcl_chatsound = {\"s_chat_custom\", \"1\"};\ncvar_t\tcl_confirmquit = {\"cl_confirmquit\", \"0\"}; // , CVAR_INIT\ncvar_t\tcl_fakename = {\"cl_fakename\", \"\"};\ncvar_t\tcl_fakename_suffix = {\"cl_fakename_suffix\", \": \"};\ncvar_t\tqizmo_dir = {\"qizmo_dir\", \"qizmo\"};\ncvar_t\tqwdtools_dir = {\"qwdtools_dir\", \"qwdtools\"};\nvoid OnChangeColorForcing (cvar_t *var, char *value, qbool *cancel);\nvoid OnChangeDemoTeamplay (cvar_t *var, char *value, qbool *cancel);\ncvar_t  cl_demoteamplay = {\"cl_demoteamplay\", \"0\", 0, OnChangeDemoTeamplay};\t// for NQ demos where we need to say it is teamplay rather than FFA\n\ncvar_t\tcl_earlypackets = {\"cl_earlypackets\", \"1\"};\n\ncvar_t\tcl_restrictions = {\"cl_restrictions\", \"0\"}; // 1 is FuhQuake and QW262 defaults\n\ncvar_t cl_floodprot\t\t\t= {\"cl_floodprot\", \"0\"};\ncvar_t cl_fp_messages\t\t= {\"cl_fp_messages\", \"4\"};\ncvar_t cl_fp_persecond\t\t= {\"cl_fp_persecond\", \"4\"};\ncvar_t cl_cmdline\t\t\t= {\"cl_cmdline\", \"\", CVAR_ROM};\ncvar_t cl_useproxy\t\t\t= {\"cl_useproxy\", \"0\"};\ncvar_t cl_proxyaddr         = {\"cl_proxyaddr\", \"\"};\ncvar_t cl_window_caption\t= {\"cl_window_caption\", \"1\"};\ncvar_t cl_window_caption_delimiter = {\"cl_window_caption_delimiter\", \" | \"};\n\ncvar_t cl_model_bobbing\t\t= {\"cl_model_bobbing\", \"1\"};\ncvar_t cl_nolerp\t\t\t= {\"cl_nolerp\", \"0\"}; // 0 is good for indep-phys, 1 is good for old-phys\n\n//this var has effect only if cl_nolerp is 1 and indep-phys enabled\n//setting it to 0 removes jerking when standing on platforms\ncvar_t cl_nolerp_on_entity\t= {\"cl_nolerp_on_entity\", \"0\"};\n\ncvar_t cl_newlerp\t\t\t\t= {\"cl_newlerp\", \"0\"};\ncvar_t cl_lerp_monsters\t\t\t= {\"cl_lerp_monsters\", \"1\"};\ncvar_t cl_fix_mvd\t\t\t\t= {\"cl_fix_mvd\", \"0\"};\n\ncvar_t r_rocketlight            = {\"r_rocketLight\", \"1\"};\ncvar_t r_rocketlightcolor       = {\"r_rocketLightColor\", \"0\"};\ncvar_t r_explosionlightcolor    = {\"r_explosionLightColor\", \"0\"};\ncvar_t r_explosionlight         = {\"r_explosionLight\", \"1\"};\ncvar_t r_flagcolor              = {\"r_flagColor\", \"0\"};\ncvar_t r_lightflicker           = {\"r_lightflicker\", \"1\"};\ncvar_t r_powerupglow            = {\"r_powerupGlow\", \"1\"};\ncvar_t cl_novweps               = {\"cl_novweps\", \"0\"};\ncvar_t r_drawvweps              = {\"r_drawvweps\", \"1\"};\ncvar_t r_rockettrail            = {\"r_rocketTrail\", \"1\"}; // 9\ncvar_t r_grenadetrail           = {\"r_grenadeTrail\", \"1\"}; // 3\ncvar_t r_railtrail              = {\"r_railTrail\", \"1\"};\ncvar_t r_instagibtrail          = {\"r_instagibTrail\", \"1\"};\ncvar_t r_explosiontype          = {\"r_explosionType\", \"1\"}; // 7\ncvar_t r_telesplash             = {\"r_telesplash\", \"1\"}; // disconnect\ncvar_t r_shaftalpha             = {\"r_shaftalpha\", \"1\"};\ncvar_t r_lightdecayrate         = {\"r_lightdecayrate\", \"2\"}; // default 2, as CL_DecayLights() used to get called twice per frame\ncvar_t r_lightmap_lateupload    = {\"r_lightmap_lateupload\", \"0\"};\ncvar_t r_lightmap_packbytexture = {\"r_lightmap_packbytexture\", \"2\"};\n\n// info mirrors\ncvar_t  password                = {\"password\", \"\", CVAR_USERINFO};\ncvar_t  spectator               = {\"spectator\", \"\", CVAR_USERINFO_NO_CFG_RESET };\nvoid CL_OnChange_name_validate(cvar_t *var, char *val, qbool *cancel);\ncvar_t  name                    = {\"name\", \"player\", CVAR_USERINFO, CL_OnChange_name_validate};\ncvar_t  team                    = {\"team\", \"\", CVAR_USERINFO_NO_CFG_RESET };\ncvar_t  topcolor                = {\"topcolor\",\"\", CVAR_USERINFO_NO_CFG_RESET };\ncvar_t  bottomcolor             = {\"bottomcolor\",\"\", CVAR_USERINFO_NO_CFG_RESET };\ncvar_t  skin                    = {\"skin\", \"\", CVAR_USERINFO_NO_CFG_RESET };\ncvar_t  rate                    = {\"rate\", \"25000\", CVAR_USERINFO};\nvoid OnChange_AppliedAfterReconnect (cvar_t *var, char *value, qbool *cancel);\ncvar_t  mtu                     = {\"mtu\", \"\", CVAR_USERINFO, OnChange_AppliedAfterReconnect};\ncvar_t  msg                     = {\"msg\", \"1\", CVAR_USERINFO};\ncvar_t  noaim                   = {\"noaim\", \"1\", CVAR_USERINFO};\ncvar_t  w_switch                = {\"w_switch\", \"\", CVAR_USERINFO};\ncvar_t  b_switch                = {\"b_switch\", \"\", CVAR_USERINFO};\ncvar_t  railcolor               = {\"railcolor\", \"\", CVAR_USERINFO};\ncvar_t  gender                  = {\"gender\", \"\", CVAR_USERINFO};\n\ncvar_t  cl_mediaroot            = {\"cl_mediaroot\", \"0\"};\n\ncvar_t  msg_filter              = {\"msg_filter\", \"0\"};\n\ncvar_t  cl_onload               = {\"cl_onload\", \"menu\"};\n\n#ifdef WIN32\ncvar_t cl_verify_qwprotocol     = {\"cl_verify_qwprotocol\", \"1\"};\n#endif // WIN32\n\ncvar_t demo_autotrack           = {\"demo_autotrack\", \"0\"}; // use or not autotrack info from mvd demos\n\n// Authentication\ncvar_t cl_username              = {\"cl_username\", \"\", CVAR_QUEUED_TRIGGER, AuthUsernameChanged};\nstatic void CL_Authenticate_f(void);\n\n// antilag debugging\ncvar_t cl_debug_antilag_view    = { \"cl_debug_antilag_view\", \"0\" };\ncvar_t cl_debug_antilag_ghost   = { \"cl_debug_antilag_ghost\", \"0\" };\ncvar_t cl_debug_antilag_self    = { \"cl_debug_antilag_self\", \"0\" };\ncvar_t cl_debug_antilag_lines   = { \"cl_debug_antilag_lines\", \"0\" };\ncvar_t cl_debug_antilag_send    = { \"cl_debug_antilag_send\", \"0\" };\n\n// weapon-switching debugging\ncvar_t cl_debug_weapon_view     = { \"cl_debug_weapon_view\", \"0\" };\n\n/// persistent client state\nclientPersistent_t\tcls;\n\n/// client state\nclientState_t\t\tcl;\n\ncentity_t       cl_entities[CL_MAX_EDICTS];\nentity_t\t\tcl_static_entities[MAX_STATIC_ENTITIES];\nlightstyle_t\tcl_lightstyle[MAX_LIGHTSTYLES];\ndlight_t\t\tcl_dlights[MAX_DLIGHTS];\nunsigned int cl_dlight_active[MAX_DLIGHTS/32];\n\n// refresh list\nvisentlist_t    cl_visents;\n\ndouble\t\tconnect_time = 0;\t\t// for connection retransmits\nqbool\t\tconnected_via_proxy = false;\n\nqbool\thost_skipframe;\t\t\t// used in demo playback\n\nbyte\t\t*host_basepal = NULL;\nbyte\t\t*host_colormap = NULL;\n\nqbool physframe;\ndouble physframetime;\n\n// emodel and pmodel are encrypted to prevent llamas from easily hacking them\nchar emodel_name[] = { 'e'^0xe5, 'm'^0xe5, 'o'^0xe5, 'd'^0xe5, 'e'^0xe5, 'l'^0xe5, 0 };\nchar pmodel_name[] = { 'p'^0xe5, 'm'^0xe5, 'o'^0xe5, 'd'^0xe5, 'e'^0xe5, 'l'^0xe5, 0 };\n\nstatic void simple_crypt (char *buf, int len) {\n\twhile (len--)\n\t\t*buf++ ^= 0xe5;\n}\n\nstatic void CL_FixupModelNames (void) {\n\tsimple_crypt (emodel_name, sizeof(emodel_name) - 1);\n\tsimple_crypt (pmodel_name, sizeof(pmodel_name) - 1);\n}\n\nvoid OnChange_AppliedAfterReconnect (cvar_t *var, char *value, qbool *cancel)\n{\n\tif (cls.state != ca_disconnected)\n\t{\n\t\tCom_Printf (\"%s change will be applied after reconnect!\\n\", var->name);\n\t}\n}\n\nchar *CL_Macro_ConnectionType(void) \n{\n\tchar *s;\n\tstatic char macrobuf[16];\n\n\ts = (cls.state < ca_connected) ? \"disconnected\" : cl.spectator ? \"spectator\" : \"player\";\n\tstrlcpy(macrobuf, s, sizeof(macrobuf));\n\treturn macrobuf;\n}\n\nchar *CL_Macro_Demoplayback(void) \n{\n\tchar *s;\n\tstatic char macrobuf[16];\n\n\ts = cls.mvdplayback ? \"mvdplayback\" : cls.demoplayback ? \"qwdplayback\" : \"0\";\n\tstrlcpy(macrobuf, s, sizeof(macrobuf));\n\treturn macrobuf;\n}\n\nchar *CL_Macro_Demotime(void)\n{   \n\t// Intended for scripted & timed camera movement\n\tstatic char macrobuf[16];\n\n\tsnprintf(macrobuf, sizeof(macrobuf), \"%f\", (float) cls.demotime);\n\treturn macrobuf;\n}\n\nchar *CL_Macro_Rand(void)\n{\n\t// Returns a number in range <0..1)\n\tstatic char macrobuf[16];\n\n\tsnprintf(macrobuf, sizeof(macrobuf), \"%f\", (double) rand() / RAND_MAX);\n\treturn macrobuf;\n}\n\nchar *CL_Macro_Serverstatus(void)\n{\n\tchar *s;\n\tstatic char macrobuf[16];\n\n\ts = (cls.state < ca_connected) ? \"disconnected\" : cl.standby ? \"standby\" : \"normal\";\n\tstrlcpy(macrobuf, s, sizeof(macrobuf));\n\treturn macrobuf;\n}\n\nchar *CL_Macro_ServerIp(void) \n{\n\treturn NET_AdrToString(cls.server_adr);\n}\n\nchar *CL_Macro_Conwidth(void) \n{\n\tstatic char macrobuf[16];\n\tsnprintf(macrobuf, sizeof(macrobuf), \"%i\", vid.conwidth);\n\treturn macrobuf;\n}\n\nchar *CL_Macro_Conheight(void)\n{\n\tstatic char macrobuf[16];\n\tsnprintf(macrobuf, sizeof(macrobuf), \"%i\", vid.conheight);\n\treturn macrobuf;\n}\n\nint CL_ClientState (void)\n{\n\treturn cls.state;\n}\n\nvoid CL_MakeActive(void) \n{\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tSys_Printf(\"\\nevent,active (map=%s)\\n\", host_mapname.string);\n#endif\n\t// last chance\n\tCachePics_AtlasFrame();\n\t// compile all programs\n\tif (GL_Supported(R_SUPPORT_RENDERING_SHADERS)) {\n\t\tR_ProgramCompileAll();\n\t}\n\n\tcls.state = ca_active;\n\tif (cls.demoplayback) \n\t{\n\t\thost_skipframe = true;\n\t\tdemostarttime = cls.demotime;\n\t}\n\n\tif (key_dest == key_startupdemo_game) {\n\t\tkey_dest = key_game;\n\t}\n\n\tif (!cls.demoseeking) {\n\t\tCon_ClearNotify ();\n\t}\n\t\n\t// Reset safestrafe state on spawn\n\tmemset(&cl.safestrafe, 0, sizeof(cl.safestrafe));\n\t\n\tTP_ExecTrigger(\"f_spawn\");\n}\n\n// Cvar system calls this when a CVAR_USERINFO cvar changes\nvoid CL_UserinfoChanged (char *key, char *string) \n{\n\tchar *s;\n\n\ts = TP_ParseFunChars (string, false);\n\n\tif (strcmp(s, Info_ValueForKey (cls.userinfo, key))) \n\t{\n\t\tInfo_SetValueForKey (cls.userinfo, key, s, MAX_INFO_STRING);\n\n\t\tif (cls.state >= ca_connected)\n\t\t{\n\t\t\tif (cls.mvdplayback == QTV_PLAYBACK)\n\t\t\t{\n\t\t\t\tQTV_Cmd_Printf(QTV_EZQUAKE_EXT_SETINFO, \"setinfo \\\"%s\\\" \\\"%s\\\"\", key, s);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\t\t\tSZ_Print (&cls.netchan.message, va(\"setinfo \\\"%s\\\" \\\"%s\\\"\", key, s));\n\t\t\t}\n\t\t}\n\t}\n}\n\n#ifdef PROTOCOL_VERSION_FTE\nunsigned int CL_SupportedFTEExtensions (void)\n{\n\tunsigned int fteprotextsupported = 0;\n\n\tif (!cl_pext.value)\n\t\treturn 0;\n\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tif (cl_pext_chunkeddownloads.value)\n\t\tfteprotextsupported |= FTE_PEXT_CHUNKEDDOWNLOADS;\n#endif\n\n#ifdef FTE_PEXT_256PACKETENTITIES\n\tif (cl_pext_256packetentities.value)\n\t\tfteprotextsupported |= FTE_PEXT_256PACKETENTITIES;\n#endif\n\n#ifdef FTE_PEXT_FLOATCOORDS\n\tif (cl_pext_floatcoords.value)\n\t\tfteprotextsupported |= FTE_PEXT_FLOATCOORDS;\n#endif\n\n#ifdef FTE_PEXT_TRANS\n\tif (cl_pext_alpha.value)\n\t\tfteprotextsupported |= FTE_PEXT_TRANS;\n#endif\n#ifdef FTE_PEXT_COLOURMOD\n\tif (cl_pext_colourmod.value)\n\t\tfteprotextsupported |= FTE_PEXT_COLOURMOD;\n#endif\n\n\tif (cl_pext_limits.value) {\n#ifdef FTE_PEXT_MODELDBL\n\t\tfteprotextsupported |= FTE_PEXT_MODELDBL;\n#endif\n#ifdef FTE_PEXT_ENTITYDBL\n\t\tfteprotextsupported |= FTE_PEXT_ENTITYDBL;\n#endif\n#ifdef FTE_PEXT_ENTITYDBL2\n\t\tfteprotextsupported |= FTE_PEXT_ENTITYDBL2;\n#endif\n#ifdef FTE_PEXT_SPAWNSTATIC2\n\t\tfteprotextsupported |= FTE_PEXT_SPAWNSTATIC2;\n#endif\n\t}\n\n\tif (cl_pext_other.value)\n\t{\n#ifdef FTE_PEXT_ACCURATETIMINGS\n\t\tfteprotextsupported |= FTE_PEXT_ACCURATETIMINGS;\n#endif\n#ifdef FTE_PEXT_HLBSP\n\t\tfteprotextsupported |= FTE_PEXT_HLBSP;\n#endif\n\t}\n\n\treturn fteprotextsupported;\n}\n#endif // PROTOCOL_VERSION_FTE\n\n#ifdef PROTOCOL_VERSION_FTE2\nunsigned int CL_SupportedFTEExtensions2 (void)\n{\n\tunsigned int fteprotextsupported2 = 0\n#ifdef FTE_PEXT2_VOICECHAT\n\t\t| FTE_PEXT2_VOICECHAT\n#endif\n\t\t;\n\n\tif (!cl_pext.value)\n\t\treturn 0;\n\n\treturn fteprotextsupported2;\n}\n#endif // PROTOCOL_VERSION_FTE2\n\n#ifdef PROTOCOL_VERSION_MVD1\nunsigned int CL_SupportedMVDExtensions1(void)\n{\n\tunsigned int extensions_supported = 0;\n\n\tif (!cl_pext.value) {\n\t\treturn 0;\n\t}\n\n#ifdef MVD_PEXT1_FLOATCOORDS\n\tif (cl_pext_floatcoords.value) {\n\t\textensions_supported |= MVD_PEXT1_FLOATCOORDS;\n\t}\n#endif\n\n#ifdef MVD_PEXT1_HIGHLAGTELEPORT\n\tif (cl_pext_lagteleport.integer & 1) {\n\t\textensions_supported |= MVD_PEXT1_HIGHLAGTELEPORT;\n\t}\n#endif\n\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\tif (cl_pext_serversideweapon.integer) {\n\t\textensions_supported |= MVD_PEXT1_SERVERSIDEWEAPON;\n\t}\n#endif\n\n#ifdef MVD_PEXT1_DEBUG_ANTILAG\n\tif (cl_debug_antilag_send.integer) {\n\t\textensions_supported |= MVD_PEXT1_DEBUG_ANTILAG;\n\t}\n#endif\n\n\treturn extensions_supported;\n}\n#endif\n\n// Called by CL_Connect_f and CL_CheckResend\nstatic void CL_SendConnectPacket(\n#ifdef PROTOCOL_VERSION_FTE\n\tunsigned int ftepext\n#ifdef PROTOCOL_VERSION_FTE2\n\t,\n#endif // PROTOCOL_VERSION_FTE2\n#endif // PROTOCOL_VERSION_FTE\n#ifdef PROTOCOL_VERSION_FTE2\n\tunsigned int ftepext2\n#ifdef PROTOCOL_VERSION_MVD1\n\t,\n#endif\n#endif // PROTOCOL_VERSION_FTE2\n#ifdef PROTOCOL_VERSION_MVD1\n\tunsigned int mvdpext1\n#endif\n\t\t\t\t\t\t\t\t) \n{\n\tchar data[2048];\n\tchar biguserinfo[MAX_INFO_STRING + 32];\n\tint extensions;\n\textern cvar_t cl_novweps;\n\n\tif (cls.state != ca_disconnected)\n\t\treturn;\n\n#ifdef PROTOCOL_VERSION_FTE\n\tcls.fteprotocolextensions  = (ftepext & CL_SupportedFTEExtensions());\n#endif // PROTOCOL_VERSION_FTE\n#ifdef PROTOCOL_VERSION_FTE2\n\tcls.fteprotocolextensions2  = (ftepext2 & CL_SupportedFTEExtensions2());\n#endif // PROTOCOL_VERSION_FTE\n#ifdef PROTOCOL_VERSION_MVD1\n\tcls.mvdprotocolextensions1 = (mvdpext1 & CL_SupportedMVDExtensions1());\n#endif\n\n\tconnect_time = cls.realtime; // For retransmit requests\n\tcls.qport = Cvar_Value(\"qport\");\n\n\t// Let the server know what extensions we support.\n\tstrlcpy (biguserinfo, cls.userinfo, sizeof (biguserinfo));\n\textensions = CLIENT_EXTENSIONS &~ (cl_novweps.value ? Z_EXT_VWEP : 0);\n\tInfo_SetValueForStarKey (biguserinfo, \"*z_ext\", va(\"%i\", extensions), sizeof(biguserinfo));\n\n\tsnprintf(data, sizeof(data), \"\\xff\\xff\\xff\\xff\" \"connect %i %i %i \\\"%s\\\"\\n\", PROTOCOL_VERSION, cls.qport, cls.challenge, biguserinfo);\n\n#ifdef PROTOCOL_VERSION_FTE\n\tif (cls.fteprotocolextensions) \n\t{\n\t\tchar tmp[128];\n\t\tsnprintf(tmp, sizeof(tmp), \"0x%x 0x%x\\n\", PROTOCOL_VERSION_FTE, cls.fteprotocolextensions);\n\t\tCom_Printf_State(PRINT_DBG, \"0x%x is fte protocol ver and 0x%x is fteprotocolextensions\\n\", PROTOCOL_VERSION_FTE, cls.fteprotocolextensions);\n\t\tstrlcat(data, tmp, sizeof(data));\n\t}\n#endif // PROTOCOL_VERSION_FTE\n\n#ifdef PROTOCOL_VERSION_FTE2\n\tif (cls.fteprotocolextensions2) \n\t{\n\t\tchar tmp[128];\n\t\tsnprintf(tmp, sizeof(tmp), \"0x%x 0x%x\\n\", PROTOCOL_VERSION_FTE2, cls.fteprotocolextensions2);\n\t\tCom_Printf_State(PRINT_DBG, \"0x%x is fte protocol ver and 0x%x is fteprotocolextensions2\\n\", PROTOCOL_VERSION_FTE2, cls.fteprotocolextensions2);\n\t\tstrlcat(data, tmp, sizeof(data));\n\t}\n#endif // PROTOCOL_VERSION_FTE2\n\n#ifdef PROTOCOL_VERSION_MVD1\n\tif (cls.mvdprotocolextensions1) {\n\t\tchar tmp[128];\n\t\tsnprintf(tmp, sizeof(tmp), \"0x%x 0x%x\\n\", PROTOCOL_VERSION_MVD1, cls.mvdprotocolextensions1);\n\t\tCom_Printf_State(PRINT_DBG, \"0x%x is mvd protocol ver and 0x%x is mvdprotocolextensions1\\n\", PROTOCOL_VERSION_MVD1, cls.mvdprotocolextensions1);\n\t\tstrlcat(data, tmp, sizeof(data));\n\t}\n#endif\n\n\tNET_SendPacket(NS_CLIENT, strlen(data), data, cls.server_adr);\n}\n\n// Resend a connect message if the last one has timed out\nvoid CL_CheckForResend (void) \n{\n\tchar data[2048];\n\tdouble t1, t2;\n\n#ifndef CLIENTONLY\n\tif (cls.state == ca_disconnected && com_serveractive) \n\t{\n\t\t// if the local server is running and we are not, then connect\n\t\tstrlcpy (cls.servername, \"local\", sizeof(cls.servername));\n\t\tNET_StringToAdr(\"local\", &cls.server_adr);\n\n\t\t// We don't need a challenge on the local server.\n\t\tCL_SendConnectPacket(\n#ifdef PROTOCOL_VERSION_FTE\n\t\t\t\tsvs.fteprotocolextensions\n\t#ifdef PROTOCOL_VERSION_FTE2\n\t\t\t\t,\n\t#endif // PROTOCOL_VERSION_FTE2\n#endif // PROTOCOL_VERSION_FTE\n#ifdef PROTOCOL_VERSION_FTE2\n\t\t\t\tsvs.fteprotocolextensions2\n\t#ifdef PROTOCOL_VERSION_MVD1\n                ,\n\t#endif\n#endif // PROTOCOL_VERSION_FTE\n#ifdef PROTOCOL_VERSION_MVD1\n                svs.mvdprotocolextension1\n#endif\n\t\t);\n\t\t\n\t\t// FIXME: cls.state = ca_connecting so that we don't send the packet twice?\n\t\treturn;\n\t}\n#endif\n\n\tif (cls.state != ca_disconnected || !connect_time)\n\t\treturn;\n\tif (cls.realtime - connect_time < 5.0)\n\t\treturn;\n\n\tt1 = Sys_DoubleTime();\n\tif (!NET_StringToAdr(cls.servername, &cls.server_adr)) \n\t{\n\t\tCom_Printf(\"Bad server address\\n\");\n\t\tconnect_time = 0;\n\t\treturn;\n\t}\n\n\tt2 = Sys_DoubleTime();\n\tconnect_time = cls.realtime + t2 - t1;\t// for retransmit requests\n\n\tif (cls.server_adr.port == 0)\n\t\tcls.server_adr.port = BigShort(PORT_SERVER);\n\n\tCom_Printf(\"&cf11connect:&r %s...\\n\", cls.servername);\n\tsnprintf(data, sizeof(data), \"\\xff\\xff\\xff\\xff\" \"getchallenge\\n\");\n\tNET_SendPacket(NS_CLIENT, strlen(data), data, cls.server_adr);\n}\n\nvoid CL_BeginServerConnect(void) \n{\n\tconnect_time = -999;\t// CL_CheckForResend() will fire immediately\n\tCL_CheckForResend();\n}\n\n//\n// Parses a QW-URL of the following format \n// (this can be associated with ezquake in windows by setting some reg info):\n// qw://server:port/command\n//\n// Supported commands:\n// - join/connect\n// - spectate/observe\n// - qtv\n//\nvoid CL_QWURL_f (void)\n{\n\tchar *connection_str = NULL;\n\tchar *command = NULL;\n\n\tif (Cmd_Argc() != 2) \n\t{\n\t\tCom_Printf (\"Usage: %s <qw-url>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\t// Ensure someone isn't trying to concatenate commands.\n\tif (strchr(Cmd_Argv(1), ';') != NULL)\n\t{\n\t\tCom_Printf(\"%s: The QW-URL \\\"%s\\\" contains illegal characters\\n\",\n\t\t\tCmd_Argv(0), Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\t// Strip the leading qw:// first.\n\t{\n\t\tchar qws_str[] = \"qw://\";\n\t\tint qws_len\t= sizeof(qws_str) - 1;\n\n\t\tconnection_str = Cmd_Argv(1);\n\n\t\tif (!strncasecmp(qws_str, connection_str, qws_len))\n\t\t{\n\t\t\tconnection_str += qws_len;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCom_Printf(\"%s: The QW-URL must start with qw://\\n\", Cmd_Argv(0));\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// Find the first \"/\" and treat what's after it as the command.\t\n\tif ((command = strchr(connection_str, '/')))\n\t{\n\t\t// Null terminate the server name string.\n\t\t*command = 0;\n\t\tcommand++;\n\t}\n\telse\n\t{\n\t\t// No command given.\n\t\tcommand = \"\";\n\t}\n\n\t// Default to connecting.\n\tif (!strcmp(command, \"\") || !strncasecmp(command, \"join\", 4) || !strncasecmp(command, \"connect\", 7))\n\t{\n\t\tCbuf_AddText(va(\"join %s\\n\", connection_str));\n\t}\n\telse if (!strncmp(command, \"challenge?\", 10))\n\t{\n\t\tCL_QWURL_ProcessChallenge(command + 9);\n\t\tCbuf_AddText(va(\"connect %s\\n\", connection_str));\n\t}\n\telse if (!strncasecmp(command, \"spectate\", 8) || !strncasecmp(command, \"observe\", 7))\n\t{\n\t\tCbuf_AddText(va(\"observe %s\\n\", connection_str));\n\t}\n\telse if (!strncasecmp(command, \"qtv\", 3))\n\t{\n\t\tchar *password = command + 3;\n\n\t\tif (*password == '/') {\n\t\t\t*password = ' ';\n\t\t}\n\t\telse {\n\t\t\t*password = '\\0';\n\t\t}\n\n\t\tCbuf_AddText(va(\"qtvplay %s%s\\n\", connection_str, password));\n\t}\n\telse\n\t{\n\t\tCom_Printf(\"%s: Illegal command %s\\n\", Cmd_Argv(0), command);\n\t}\n}\n\nvoid CL_Connect_f (void) \n{\n\tqbool proxy;\n\tchar *connect_addr = NULL;\n\tchar *server_buf = NULL;\n\tportpingprobe_status_t status;\n\n\tif (Cmd_Argc() != 2) \n\t{\n\t\tCom_Printf (\"Usage: %s <server>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\t// It's not possible to change the source port when connected to a\n\t// server. Terminate the current connection before proceeding.\n\tif (IsPortPingProbeEnabled() && cls.state != ca_disconnected)\n\t{\n\t\tHost_EndGame();\n\t}\n\n\t// The user has already initiated a port ping probe, so we'll abort that\n\t// and wait for the worker thread to stop before we proceed.\n\tif (IsPortPingProbeEnabled() && (status = NET_GetPortPingProbeStatus()) == PORTPINGPROBE_PROBING)\n\t{\n\t\tNET_SetPortPingProbeStatus(PORTPINGPROBE_ABORT);\n\t\twhile ((status = NET_GetPortPingProbeStatus()) != PORTPINGPROBE_READY)\n\t\t{\n\t\t\tSys_MSleep(10);\n\t\t}\n\t}\n\n\t// in this part proxy means QWFWD proxy\n\tif (cl_proxyaddr.string[0]) {\n\t\tchar *secondproxy;\n\t\tif ((secondproxy = strchr(cl_proxyaddr.string, '@'))) {\n\t\t\tsize_t prx_buf_len = strlen(cl_proxyaddr.string) + strlen(Cmd_Argv(1)) + 2;\n\t\t\tchar *prx_buf = (char *) Q_malloc(prx_buf_len);\n\t\t\tserver_buf = (char *) Q_malloc(strlen(cl_proxyaddr.string) + 1); // much more than needed\n\n\t\t\tstrlcpy(server_buf, cl_proxyaddr.string, secondproxy - cl_proxyaddr.string + 1);\n\t\t\tconnect_addr = server_buf;\n\t\t\t\n\t\t\tstrlcpy(prx_buf, secondproxy + 1, prx_buf_len);\n\t\t\tstrlcat(prx_buf, \"@\", prx_buf_len);\n\t\t\tstrlcat(prx_buf, Cmd_Argv(1), prx_buf_len);\n\t\t\tInfo_SetValueForKeyEx(cls.userinfo, \"prx\", prx_buf, MAX_INFO_STRING, false);\n\t\t\tQ_free(prx_buf);\n\t\t}\n\t\telse {\n\t\t\tInfo_SetValueForKey (cls.userinfo, \"prx\", Cmd_Argv(1), MAX_INFO_STRING);\n#if 0 // FIXME: qqshka: disabled untill one explain that it does and why.\n\t\t\tif (cls.state >= ca_connected) {\n\t\t\t\tCmd_ForwardToServer ();\n\t\t\t}\n#endif\n\t\t\tconnect_addr = cl_proxyaddr.string;\n\t\t}\n\t\tconnected_via_proxy = true;\n\t}\n\telse\n\t{\n\t\tconnect_addr = Cmd_Argv(1);\n\t\tconnected_via_proxy = false;\n\t}\n\n\tif (IsPortPingProbeEnabled())\n\t{\n\t\tif (status == PORTPINGPROBE_READY)\n\t\t{\n\t\t\tNET_PortPingProbe(connect_addr, Cmd_Argv(1));\n\t\t\treturn;\n\t\t}\n\t\telse if (status == PORTPINGPROBE_COMPLETED)\n\t\t{\n\t\t\tNET_SetPortPingProbeStatus(PORTPINGPROBE_READY);\n\t\t}\n\t}\n\n\t// in this part proxy means Qizmo proxy\n\tproxy = cl_useproxy.value && CL_ConnectedToProxy();\n\n\tif (proxy)\n\t{\n\t\tCbuf_AddText(va(\"say ,connect %s\\n\", connect_addr));\n\t} \n\telse\n\t{\n\t\tHost_EndGame();\n\t\tstrlcpy(cls.servername, connect_addr, sizeof(cls.servername));\n\t\tCL_BeginServerConnect();\n\t}\n\n\tif (server_buf) Q_free(server_buf);\n}\n\nvoid CL_Connect_BestRoute_f(void)\n{\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"Usage: %s <address>\\nConnects to given server via fastest available path (ping-wise).\\n\", Cmd_Argv(0));\n\t\tCom_Printf(\"Requires Server Browser refreshed with sb_findroutes 1\\n\");\n\t\treturn;\n\t}\n\telse {\n\t\tnetadr_t adr;\n\t\tif (!NET_StringToAdr(Cmd_Argv(1), &adr)) {\n\t\t\tCom_Printf(\"Invalid address\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (adr.port == 0)\n\t\t\tadr.port = htons(27500);\n\n\t\tSB_PingTree_DumpPath(&adr);\n\t\tSB_PingTree_ConnectBestPath(&adr);\n\t}\n}\n\nvoid CL_TCPConnect_f (void)\n{\n\tchar buffer[6] = {'q', 'i', 'z', 'm', 'o', '\\n'};\n\tint newsocket;\n\tint _true = true;\n\n\tfloat giveuptime;\n\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf (\"Usage: %s <server>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tHost_EndGame (); // CL_Disconnect_f();\n\n\tstrlcpy(cls.servername, Cmd_Argv (1), sizeof(cls.servername));\n\n\tNET_StringToAdr(cls.servername, &cls.sockettcpdest);\n\n\tif (cls.sockettcp != INVALID_SOCKET)\n\t\tclosesocket(cls.sockettcp);\n\n\tcls.sockettcp = INVALID_SOCKET;\n\tcls.tcpinlen = 0;\n\n\tnewsocket = TCP_OpenStream(cls.sockettcpdest);\n\tif (newsocket == INVALID_SOCKET)\n\t{\n\t\t// Failed\n\t\tCom_Printf(\"Failed to connect, server is either down, firewalled, or on a different port\\n\");\n\t\treturn;\n\t}\n\n\tCom_Printf(\"Waiting for confirmation of server (10 secs)\\n\");\n\n\tgiveuptime = Sys_DoubleTime() + 10;\n\n#if 1 // qqshka: qizmo sends \"qizmo\\n\" then expects reply, unfortunatelly that does not work for mvdsv\n\t// that how MVDSV expects, should work with qizmo too\n\tsend(newsocket, buffer, sizeof(buffer), 0);\n\tmemset(buffer, 0, sizeof(buffer));\n#endif\n\n\twhile(giveuptime > Sys_DoubleTime())\n\t{\n\t\trecv(newsocket, buffer, sizeof(buffer), 0);\n\t\tif (!strncmp(buffer, \"qizmo\\n\", 6))\n\t\t{\n\t\t\tcls.sockettcp = newsocket;\n\t\t\tbreak;\n\t\t}\n\t\tSCR_UpdateScreen();\n\t}\n\n\tif (cls.sockettcp == INVALID_SOCKET)\n\t{\n\t\tCom_Printf(\"Timeout - wrong server type\\n\");\n\t\tclosesocket(newsocket);\n\t\treturn;\n\t}\n\n\tCom_Printf(\"Confirmed\\n\");\n\n#if 0 // qqshka: qizmo sends \"qizmo\\n\" then expects reply, unfortunatelly that does not work for mvdsv\n\t// that how qizmo expects, does not work with MVDSV\n\tsend(cls.sockettcp, buffer, sizeof(buffer), 0);\n#endif\n\n\tif (setsockopt(cls.sockettcp, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)) == -1) {\n\t\tCom_Printf (\"CL_TCPConnect_f: setsockopt: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t}\n\n\tCL_BeginServerConnect();\n}\n\nqbool CL_ConnectedToProxy(void)\n{\n\tcmd_alias_t *alias = NULL;\n\tqbool found = true;\n\tchar **s;\n\tchar *qizmo_aliases[] = {\t\"ezcomp\", \"ezcomp2\", \"ezcomp3\",\n\t\t\t\t\t\t\t\t\t\"f_sens\", \"f_fps\", \"f_tj\", \"f_ta\", NULL};\n\tchar *fteqtv_aliases[] = { \"+proxleft\", \"+proxright\", NULL }; // who would need more?\n\n\tif (cls.state < ca_active)\n\t\treturn false;\n\n\tfor (s = qizmo_aliases; *s; s++) \n\t{\n\t\tif (!(alias = Cmd_FindAlias(*s)) || !(alias->flags & ALIAS_SERVER)) \n\t\t{\n\t\t\tfound = false; \n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (found)\n\t\treturn true;\n\n\tfound = true;\n\tfor (s = fteqtv_aliases; *s; s++) \n\t{\n\t\tif (!(alias = Cmd_FindAlias(*s)) || !(alias->flags & ALIAS_SERVER)) \n\t\t{\n\t\t\tfound = false; break;\n\t\t}\n\t}\n\n\treturn found;\n}\n\nvoid CL_Join_f (void) \n{\n\tqbool proxy;\n\n\tproxy = cl_useproxy.value && CL_ConnectedToProxy();\n\n\tif (Cmd_Argc() > 2) \n\t{\n\t\tCom_Printf (\"Usage: %s [server]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tCvar_Set(&spectator, \"\");\n\n\tif (Cmd_Argc() == 2) \n\t{\n\t\t// A server name was given, connect directly or through Qizmo\n\t\tCvar_Set(&spectator, \"\");\n\t\tCbuf_AddText(va(\"%s %s\\n\", proxy ? \"say ,connect\" : \"connect\", Cmd_Argv(1)));\n\t\treturn;\n\t}\n\n\tif (cls.mvdplayback == QTV_PLAYBACK) {\n\t\tqtvlist_joinfromqtv_cmd();\n\t\treturn;\n\t}\n\n\tif (!cls.demoplayback && (cl.z_ext & Z_EXT_JOIN_OBSERVE)) \n\t{\n\t\t// Server supports the 'join' command, good\n\t\tCmd_ExecuteString(\"cmd join\");\n\t\treturn;\n\t}\n\n\tCbuf_AddText(va(\"%s\\n\", proxy ? \"say ,reconnect\" : \"reconnect\"));\n}\n\nvoid CL_Observe_f (void) \n{\n\tqbool proxy;\n\n\tproxy = cl_useproxy.value && CL_ConnectedToProxy();\n\n\tif (Cmd_Argc() > 2) \n\t{\n\t\tCom_Printf (\"Usage: %s [server]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tCvar_SetValue(&spectator, 1);\n\n\tif (Cmd_Argc() == 2) \n\t{\n\t\t// A server name was given, connect directly or through Qizmo\n\t\tCbuf_AddText(va(\"%s %s\\n\", proxy ? \"say ,connect\" : \"connect\", Cmd_Argv(1)));\n\t\treturn;\n\t}\n\n\tif (!cls.demoplayback && (cl.z_ext & Z_EXT_JOIN_OBSERVE))\n\t{\n\t\t// Server supports the 'join' command, good\n\t\tCmd_ExecuteString(\"cmd observe\");\n\t\treturn;\n\t}\n\n\tCbuf_AddText(va(\"%s\\n\", proxy ? \"say ,reconnect\" : \"reconnect\"));\n}\n\n\nvoid CL_Observe_BestRoute_f(void)\n{\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"Usage: %s <address>\\nConnects to given server via fastest available path (ping-wise).\\n\", Cmd_Argv(0));\n\t\tCom_Printf(\"Requires Server Browser refreshed with sb_findroutes 1\\n\");\n\t\treturn;\n\t}\n\telse {\n\t\tnetadr_t adr;\n\t\tif (!NET_StringToAdr(Cmd_Argv(1), &adr)) {\n\t\t\tCom_Printf(\"Invalid address\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (adr.port == 0)\n\t\t\tadr.port = htons(27500);\n\n\t\tCvar_SetValue(&spectator, 1);\n\n\n\t\tSB_PingTree_DumpPath(&adr);\n\t\tSB_PingTree_ConnectBestPath(&adr);\n\t}\n}\n\n// Just toggle mode between spec and player.\nvoid Cl_ToggleSpec_f (void)\n{\n\tif (spectator.string[0])\n\t\tCL_Join_f();\n\telse\n\t\tCL_Observe_f();\n}\n\nvoid CL_Hash_f(void)\n{\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCom_Printf(\"Usage: %s <string to hash>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tCom_Printf(\"%u\\n\", Com_HashKey(Cmd_Argv(1)));\n}\n\nvoid CL_DNS_f(void) \n{\n\tchar address[128], *s;\n\tstruct hostent *h;\n\tstruct in_addr addr;\n\n\tif (Cmd_Argc() != 2) \n\t{\n\t\tCom_Printf(\"Usage: %s <address>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\t\n\tstrlcpy(address, Cmd_Argv(1), sizeof(address));\n\tif ((s = strchr(address, ':')))\n\t\t*s = 0;\n\taddr.s_addr = inet_addr(address);\n\t\n\tif (inet_addr(address) == INADDR_NONE) \n\t{\n\t\t// Forward lookup\n\t\tif (!(h = gethostbyname(address))) \n\t\t{\n\t\t\tCom_Printf(\"Couldn't resolve %s\\n\", address);\n\t\t} \n\t\telse \n\t\t{\n\t\t\taddr.s_addr = *(int *) h->h_addr_list[0];\n\t\t\tCom_Printf(\"Resolved %s to %s\\n\", address, inet_ntoa(addr));\n\t\t}\n\t\treturn;\n\t}\n\n\t// Reverse lookup ip address\n\tif (!(h = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET)))\n\t\tCom_Printf(\"Couldn't resolve %s\\n\", address);\n\telse\n\t\tCom_Printf(\"Resolved %s to %s\\n\", address, h->h_name);\n}\n\nvoid SCR_ClearShownick(void);\nvoid SCR_ClearTeamInfo(void);\nvoid SCR_ClearWeaponStats(void);\n\nvoid CL_ClearState (void) \n{\n\tint i;\n\textern cshift_t\tcshift_empty;\n\textern void CL_ProcessServerInfo (void);\n\n\tS_StopAllSounds();\n\n\tCom_DPrintf (\"Clearing memory\\n\");\n\n\tif (!com_serveractive) {\n\t\tHost_ClearMemory();\n\t}\n\n\tCL_ClearTEnts ();\n\tCL_ClearScene ();\n\n\tCL_ClearPredict();\n\n\tif (cls.state == ca_active) {\n\t\tint ideal_track = cl.ideal_track;\n\t\tint autocam = cl.autocam;\n\n\t\t// Wipe the entire cl structure.\n\t\tmemset(&cl, 0, sizeof(cl));\n\n\t\tcl.ideal_track = ideal_track;\n\t\tcl.autocam = autocam;\n\t}\n\telse {\n\t\t// Wipe the entire cl structure.\n\t\tmemset(&cl, 0, sizeof(cl));\n\t}\n\n\t// default weapon selection if nothing else replaces it\n\tcl.weapon_order[0] = 2;\n\tcl.weapon_order[1] = 1;\n\tcl.weapon_order[2] = 0;\n\n\tSZ_Clear (&cls.netchan.message);\n\n\t// Clear other arrays.\n\tmemset(cl_dlight_active, 0, sizeof(cl_dlight_active));\n\tmemset(cl_lightstyle, 0, sizeof(cl_lightstyle));\n\tmemset(cl_entities, 0, sizeof(cl_entities));\n\tmemset(cl_static_entities, 0, sizeof(cl_static_entities));\n\tCL_SpawnWarn_ClearPoints();\n\n\t// Set entnum for all entity baselines\n\tfor (i = 0; i < sizeof(cl_entities) / sizeof(cl_entities[0]); ++i) {\n\t\tcl_entities[i].baseline.number = i;\n\t}\n\n\t// Set default viewheight for mvd, we copy cl.players[].stats[] to cl_stats[] in Cam_Lock() when pov changes.\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t\tcl.players[i].stats[STAT_VIEWHEIGHT] = DEFAULT_VIEWHEIGHT;\n\t// Set default viewheight for normal game/current pov.\n\tcl.stats[STAT_VIEWHEIGHT] = DEFAULT_VIEWHEIGHT;\n\n\tR_Init_EFrags();\n\n\tmemset(&cshift_empty, 0, sizeof(cshift_empty));\n\n\t// Clear shownick structs\n\tSCR_ClearShownick();\n\n\t// Clear teaminfo structs\n\tSCR_ClearTeamInfo();\n\n\t// Clear weapon stats structs\n\tSCR_ClearWeaponStats();\n\n\tSCR_CenterPrint_Clear();\n\n\tif (!com_serveractive)\n\t\tCvar_ForceSet (&host_mapname, \"\"); // Notice mapname not valid yet.\n\n\tcl.fakeshaft_policy = 1;\n\n\t// Default teamnames for TF\n\tstrlcpy(cl.fixed_team_names[0], \"blue\", sizeof(cl.fixed_team_names[0]));\n\tstrlcpy(cl.fixed_team_names[1], \"red\", sizeof(cl.fixed_team_names[1]));\n\tstrlcpy(cl.fixed_team_names[2], \"yell\", sizeof(cl.fixed_team_names[2]));\n\tstrlcpy(cl.fixed_team_names[3], \"gren\", sizeof(cl.fixed_team_names[3]));\n\n\tCL_ProcessServerInfo(); // Force set some default variables, because server may not sent fullserverinfo.\n}\n\n// Sends a disconnect message to the server\n// This is also called on Host_Error, so it shouldn't cause any errors\nvoid CL_Disconnect (void) \n{\n\tbyte final[10];\n\n\tconnect_time = 0;\n\tcon_addtimestamp = true;\n\n\tif (cl.teamfortress)\n\t\tV_TF_ClearGrenadeEffects();\n\tcl.teamfortress = false;\n\n\t// Reset values changed by Multiview.\n\tCL_MultiviewResetCvars ();\n\n\t// Stop sounds (especially looping!)\n\tS_StopAllSounds();\n\n\tMT_Disconnect();\n\n\t//\n\tR_OnDisconnect();\n\n\tif (cls.demorecording && cls.state != ca_disconnected) {\n\t\tCL_Stop_f();\n\t}\n\n\tif (cls.mvdrecording && cls.state != ca_disconnected) {\n\t\textern void CL_StopMvd_f(void);\n\n\t\tCL_StopMvd_f();\n\t}\n\n\tif (cls.demoplayback) {\n\t\tCL_StopPlayback();\n\t} \n\telse if (cls.state != ca_disconnected) {\n\t\tfinal[0] = clc_stringcmd;\n\t\tstrlcpy ((char *)(final + 1), \"drop\", sizeof (final) - 1);\n\t\tNetchan_Transmit (&cls.netchan, 6, final);\n\t\tNetchan_Transmit (&cls.netchan, 6, final);\n\t\tNetchan_Transmit (&cls.netchan, 6, final);\n\n\t\tCL_UnqueOutputPacket(true);\n\n\t\t// TCP connect, that gives TCP a chance to transfer data to the server...\n\t\tif (cls.sockettcp != INVALID_SOCKET) {\n\t\t\tSys_MSleep(1000);\n\t\t}\n\t}\n\n\tmemset(&cls.netchan, 0, sizeof(cls.netchan));\n\tmemset(&cls.server_adr, 0, sizeof(cls.server_adr));\n\tcls.state = ca_disconnected;\n\tcls.fteprotocolextensions = cls.fteprotocolextensions2 = cls.mvdprotocolextensions1 = 0;\n\tconnect_time = 0;\n\n\tCam_Reset();\n\n\tif (cls.download) {\n\t\tCL_FinishDownload();\n\t}\n\telse {\n\t\t/* Just to make sure it's not in an ambigious state */\n\t\tcls.downloadmethod = DL_NONE;\n\t\tcls.downloadnumber = 0;\n\t\tcls.downloadpercent = 0;\n\t\tcls.downloadtype = dl_none;\n\t}\n\n\tCL_StopUpload();\n\tDeleteServerAliases();\n\tCL_RE_Trigger_ResetLasttime();\n\n\t// TCP connect.\n\tif (cls.sockettcp != INVALID_SOCKET) {\n\t\tclosesocket(cls.sockettcp);\n\t\tcls.sockettcp = INVALID_SOCKET;\n\t}\n\n\tcls.qport++; // A hack I picked up from qizmo.\n\n\tSZ_Clear(&cls.cmdmsg);\n\n\t// So join/observe not confused\n\tInfo_SetValueForStarKey(cl.serverinfo, \"*z_ext\", \"\", sizeof(cl.serverinfo));\n\tcl.z_ext = 0;\n\n\t// well, we need free qtv users before new connection\n\tQTV_FreeUserList();\n\n\tCvar_ForceSet(&host_mapname, \"\"); // Notice mapname not valid yet\n}\n\nvoid CL_Disconnect_f (void) \n{\n\tportpingprobe_status_t status;\n\n\tcl.intermission = 0;\n\tCL_Demo_Disconnected();\n\n\tif (IsPortPingProbeEnabled() && (status = NET_GetPortPingProbeStatus()) == PORTPINGPROBE_PROBING)\n\t{\n\t\tNET_SetPortPingProbeStatus(PORTPINGPROBE_ABORT);\n\t\twhile ((status = NET_GetPortPingProbeStatus()) != PORTPINGPROBE_READY)\n\t\t{\n\t\t\tSys_MSleep(10);\n\t\t}\n\t}\n\n\tHost_EndGame();\n}\n\n// The server is changing levels.\nvoid CL_Reconnect_f (void) \n{\n\tif (cls.download)\n\t\treturn; // Don't change when downloading.\n\n\tS_StopAllSounds();\n\n\tif (cls.mvdplayback) {\n\t\treturn; // Change map during qtv playback.\n\t}\n\n\tif (cls.state == ca_connected) \n\t{\n\t\tCom_Printf (\"reconnecting...\\n\");\n\t\tMSG_WriteChar (&cls.netchan.message, clc_stringcmd);\n\t\tMSG_WriteString (&cls.netchan.message, \"new\");\n\t\treturn;\n\t}\n\n\tif (!*cls.servername) \n\t{\n\t\tCom_Printf (\"No server to reconnect to.\\n\");\n\t\treturn;\n\t}\n\n\tif (connected_via_proxy) {\n\t\t// we were connected via a proxy\n\t\t// the target server is (hopefully still) in userinfo/prx\n\t\tchar *prx = Info_ValueForKey(cls.userinfo, \"prx\");\n\t\tchar *lastnode = strrchr(prx, '@');\n\t\tif (lastnode) {\n\t\t\tlastnode++;\n\t\t}\n\t\telse {\n\t\t\tlastnode = prx;\n\t\t}\n\t\t\n\t\tCbuf_AddText(va(\"connect %s\\n\", lastnode));\n\t}\n\telse if (!connected_via_proxy && cl_proxyaddr.string[0]) {\n\t\t// switch the stuff\n\t\tCbuf_AddText(va(\"connect %s\\n\", cls.servername));\n\t}\n\telse if (IsPortPingProbeEnabled()) {\n\t\tCbuf_AddText(va(\"connect %s\\n\", cls.servername));\n\t}\n\telse {\n\t\t// good old reconnect, no need to do anything special\n\t\tHost_EndGame();\n\t\tCL_BeginServerConnect();\n\t}\n\n\t// remember what's the type of this new connection\n\tconnected_via_proxy = cl_proxyaddr.string[0] ? true : false;\n}\n\nextern double qstat_senttime;\nextern void CL_PrintQStatReply (char *s);\n\n// Responses to broadcasts, etc\nvoid CL_ConnectionlessPacket (void) \n{\n\tint c;\n\tchar *s, cmdtext[2048];\n\t\n\t#ifdef PROTOCOL_VERSION_FTE\n\tunsigned int pext = 0;\n\t#endif // PROTOCOL_VERSION_FTE\n\t#ifdef PROTOCOL_VERSION_FTE2\n\tunsigned int pext2 = 0;\n\t#endif // PROTOCOL_VERSION_FTE2\n\t#ifdef PROTOCOL_VERSION_MVD1\n\tunsigned int pext_mvd1 = 0;\n\t#endif\n\n    MSG_BeginReading();\n    MSG_ReadLong();\t// Skip the -1\n\n\tc = MSG_ReadByte();\n\n\tif (msg_badread)\n\t\treturn;\t// Runt packet\n\n\tswitch(c) \n\t{\n\t\tcase S2C_CHALLENGE :\n\t\t{\n\t\t\tif (!NET_CompareAdr(net_from, cls.server_adr))\n\t\t\t{\n\t\t\t\tCom_DPrintf(\"S2C_CHALLENGE rejected\\n\");\n\t\t\t\tCom_DPrintf(\"net_from       %s\\n\", NET_AdrToString(net_from));\n\t\t\t\tCom_DPrintf(\"cls.server_adr %s\\n\", NET_AdrToString(cls.server_adr));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tCom_Printf(\"&cf55challenge:&r %s\\n\", NET_AdrToString(net_from));\n\t\t\tcls.challenge = atoi(MSG_ReadString());\n\n\t\t\tfor(;;)\n\t\t\t{\n\t\t\t\tc = MSG_ReadLong();\n\t\t\t\tif (msg_badread)\n\t\t\t\t\tbreak;\n\n#ifdef PROTOCOL_VERSION_FTE\n\t\t\t\tif (c == PROTOCOL_VERSION_FTE)\n\t\t\t\t\tpext = MSG_ReadLong();\n\t\t\t\telse\n#endif // PROTOCOL_VERSION_FTE\n#ifdef PROTOCOL_VERSION_FTE2\n\t\t\t\tif (c == PROTOCOL_VERSION_FTE2)\n\t\t\t\t\tpext2 = MSG_ReadLong();\n\t\t\t\telse\n#endif // PROTOCOL_VERSION_FTE2\n#ifdef PROTOCOL_VERSION_MVD1\n\t\t\t\tif (c == PROTOCOL_VERSION_MVD1)\n\t\t\t\t\tpext_mvd1 = MSG_ReadLong();\n\t\t\t\telse\n#endif\n\t\t\t\t\tMSG_ReadLong();\n\t\t\t}\n\n\t\t\tCL_SendConnectPacket(\n#ifdef PROTOCOL_VERSION_FTE\n\t\t\t\tpext\n\t#ifdef PROTOCOL_VERSION_FTE2\n\t\t\t\t,\n\t#endif // PROTOCOL_VERSION_FTE2\n#endif // PROTOCOL_VERSION_FTE\n#ifdef PROTOCOL_VERSION_FTE2\n\t\t\t\tpext2\n#ifdef PROTOCOL_VERSION_MVD1\n\t\t\t\t,\n#endif\n#endif // PROTOCOL_VERSION_FTE\n#ifdef PROTOCOL_VERSION_MVD1\n\t\t\t\tpext_mvd1\n#endif\n\t\t\t\t);\n\n\t\t\tbreak;\n\t\t}\n\t\tcase S2C_CONNECTION :\n\t\t{\n\t\t\tif (!NET_CompareAdr(net_from, cls.server_adr))\n\t\t\t\treturn;\n\t\t\tif ((!com_serveractive || developer.value) && !cls.demoplayback)\n\t\t\t\tCom_Printf(\"&cff5connection:&r %s\\n\", NET_AdrToString(net_from));\n\n\t\t\tif (cls.state >= ca_connected) \n\t\t\t{\n\t\t\t\tif (!cls.demoplayback)\n\t\t\t\t\tCom_Printf(\"Dup connect received.  Ignored.\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tNetchan_Setup(NS_CLIENT, &cls.netchan, net_from, cls.qport, 0);\n\t\t\tMSG_WriteChar (&cls.netchan.message, clc_stringcmd);\n\t\t\tMSG_WriteString (&cls.netchan.message, \"new\");\n\t\t\tcls.state = ca_connected;\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\t\t\tSys_Printf(\"\\nevent,connected...\\n\");\n#endif\n\t\t\tif ((!com_serveractive || developer.value) && !cls.demoplayback)\n\t\t\t\tCom_Printf(\"&c1f1connected!&r\\n\");\n\t\t\tallowremotecmd = false; // localid required now for remote cmds\n\t\t\tbreak;\n\t\t}\n\t\tcase A2C_CLIENT_COMMAND : \n\t\t{\n\t\t\t// Remote command from gui front end\n\t\t\tCom_Printf (\"%s: client command\\n\", NET_AdrToString (net_from));\n\n\t\t\tif (net_from.type != net_local_cl_ipadr.type\n\t\t\t\t|| ((*(unsigned *)net_from.ip != *(unsigned *)net_local_cl_ipadr.ip)\n\t\t\t\t&& (*(unsigned *)net_from.ip != htonl(INADDR_LOOPBACK))))\n\t\t\t{\n\t\t\t\tCom_Printf (\"Command packet from remote host.  Ignored.\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\n                        VID_Restore();\n\t\t\t\n\t\t\ts = MSG_ReadString ();\n\t\t\tstrlcpy (cmdtext, s, sizeof(cmdtext));\n\t\t\ts = MSG_ReadString ();\n\n\t\t\twhile (*s && isspace(*s))\n\t\t\t\ts++;\n\t\t\twhile (*s && isspace(s[strlen(s) - 1]))\n\t\t\t\ts[strlen(s) - 1] = 0;\n\n\t\t\tif (!allowremotecmd && (!*localid.string || strcmp(localid.string, s))) \n\t\t\t{\n\t\t\t\tif (!*localid.string) \n\t\t\t\t{\n\t\t\t\t\tCom_Printf (\"===========================\\n\");\n\t\t\t\t\tCom_Printf (\"Command packet received from local host, but no \"\n\t\t\t\t\t\t\"localid has been set.  You may need to upgrade your server \"\n\t\t\t\t\t\t\"browser.\\n\");\n\t\t\t\t\tCom_Printf (\"===========================\\n\");\n\t\t\t\t}\n\t\t\t\telse \n\t\t\t\t{\n\t\t\t\t\tCom_Printf (\"===========================\\n\");\n\t\t\t\t\tCom_Printf (\"Invalid localid on command packet received from local host. \"\n\t\t\t\t\t\t\"\\n|%s| != |%s|\\n\"\n\t\t\t\t\t\t\"You may need to reload your server browser and ezQuake.\\n\",\n\t\t\t\t\t\ts, localid.string);\n\t\t\t\t\tCom_Printf (\"===========================\\n\");\n\t\t\t\t\tCvar_Set(&localid, \"\");\n\t\t\t\t}\n\t\t\t} \n\t\t\telse \n\t\t\t{\n\t\t\t\tif (KeyDestStartupDemo(key_dest)) {\n\t\t\t\t\tkey_dest = key_console;\n\t\t\t\t}\n\n\t\t\t\tCbuf_AddText (cmdtext);\n\t\t\t\tCbuf_AddText (\"\\n\");\n\t\t\t\tallowremotecmd = false;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase A2C_PRINT:\t\t\n\t\t{\n\t\t\t// Print command from somewhere.\n\t\t\t\n\t\t\t#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\t\t\tif (net_message.cursize > 100 && !strncmp((char *)net_message.data + 5, \"\\\\chunk\", sizeof(\"\\\\chunk\")-1)) \n\t\t\t{\n\t\t\t\tCL_Parse_OOB_ChunkedDownload();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t#endif // FTE_PEXT_CHUNKEDDOWNLOADS\n\n\t\t\tif (net_message.data[msg_readcount] == '\\\\') \n\t\t\t{\n\t\t\t\tif (qstat_senttime && curtime - qstat_senttime < 10)\n\t\t\t\t{\n\t\t\t\t\tCL_PrintQStatReply (MSG_ReadString());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tCom_Printf(\"%s: print\\n\", NET_AdrToString(net_from));\n\t\t\tCom_Printf(\"%s\", MSG_ReadString());\n\t\t\tbreak;\n\t\t}\n\t\tcase svc_disconnect :\n\t\t{\n\t\t\tif (cls.demoplayback)\n\t\t\t{\n\t\t\t\tCom_Printf(\"\\n======== End of demo ========\\n\\n\");\n\t\t\t\tHost_EndGame();\n\t\t\t\tHost_Abort();\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n// Handles playback of demos, on top of NET_ code\nqbool CL_GetMessage (void) \n{\n\tCL_CheckQizmoCompletion ();\n\n\tif (cls.demoplayback)\n\t\treturn CL_GetDemoMessage();\n\n\tif (!NET_GetPacket(NS_CLIENT))\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic void CL_ReadPackets(void)\n{\n\tif (cls.nqdemoplayback) \n\t{\n\t\tNQD_ReadPackets();\n\t\treturn;\n\t}\n\n\twhile (CL_GetMessage()) \n\t{\n\t\t// Remote command packet.\n\t\tif (*(int *)net_message.data == -1)\t\n\t\t{\n\t\t\tCL_ConnectionlessPacket();\n\t\t\tCom_DPrintf(\"Connectionless packet\\n\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!cls.mvdplayback && (net_message.cursize < 8)) \n\t\t{\n\t\t\tCom_DPrintf(\"%s: Runt packet\\n\", NET_AdrToString(net_from));\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Packet from server.\n\t\tif (!cls.demoplayback && !NET_CompareAdr(net_from, cls.netchan.remote_address)) \n\t\t{\n\t\t\tCom_DPrintf(\"%s: sequenced packet without connection\\n\", NET_AdrToString(net_from));\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (cls.mvdplayback) \n\t\t{\n\t\t\tMSG_BeginReading();\n\t\t}\n\t\telse \n\t\t{\n\t\t\tif (!Netchan_Process(&cls.netchan))\n\t\t\t\tcontinue; // Wasn't accepted for some reason.\n\t\t}\n\n\t\tif (cls.lastto == 0 && cls.lasttype == dem_multiple) {\n#ifdef MVD_PEXT1_HIDDEN_MESSAGES\n\t\t\tif (cls.mvdprotocolextensions1 & MVD_PEXT1_HIDDEN_MESSAGES) {\n\t\t\t\tCL_ParseHiddenDataMessage();\n\t\t\t}\n#endif\n\t\t\tcontinue;\n\t\t}\n\n\t\tCL_ParseServerMessage();\n\t}\n\n\t// Check timeout.\n\tif (!cls.demoplayback && cls.state >= ca_connected ) \n\t{\n\t\tif (curtime - cls.netchan.last_received > (cl_timeout.value > 0 ? cl_timeout.value : 60)) \n\t\t{\n\t\t\tCom_Printf(\"\\nServer connection timed out.\\n\");\n\t\t\tHost_EndGame();\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nvoid CL_SendToServer (void) \n{\n\t// When recording demos, request new ping times every cl_demoPingInterval.value seconds.\n\tif (cls.demorecording && !cls.demoplayback && cls.state == ca_active && cl_demoPingInterval.value > 0) \n\t{\n\t\tif (cls.realtime - cl.last_ping_request > cl_demoPingInterval.value) \n\t\t{\n\t\t\tcl.last_ping_request = cls.realtime;\n\t\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\t\tSZ_Print (&cls.netchan.message, \"pings\");\n\t\t}\n\t}\n\n\t// Send intentions now. Resend a connection request if necessary.\n\tif (cls.state == ca_disconnected)\n\t\tCL_CheckForResend ();\n\telse\n\t\tCL_SendCmd ();\n}\n\nvoid CL_OnChange_name_validate(cvar_t *var, char *val, qbool *cancel)\n{\n\tchar *clrpart;\n\n\t// check for &r\n\tif (strstr(val, \"&r\")) {\n\t\t*cancel = true;\n\t\tCom_Printf(\"Using color codes in your name is not allowed\\n\");\n\t\treturn;\n\t}\n\t\n\t// check for &cRGB\n\t// RGB has to be a valid hexadecimal number, otherwise it's ok\n\tclrpart = val;\n\tdo {\n\t\tclrpart = strstr(clrpart, \"&c\");\n\t\tif (clrpart) {\n\t\t\tclrpart += 2;\n\t\t\tif (clrpart[0] && clrpart[1] && clrpart[2]) {\n\t\t\t\tif (HexToInt(clrpart[0]) >= 0 && HexToInt(clrpart[1]) >= 0 && HexToInt(clrpart[2]) >= 0) {\n\t\t\t\t\t*cancel = true;\n\t\t\t\t\tCom_Printf(\"Using color codes in your name is not allowed\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} while (clrpart);\n}\n//=============================================================================\n\nvoid CL_InitCommands (void);\n\nstatic void CL_InitLocal(void)\n{\n\tchar st[256];\n\n\textern void Cl_Messages_Init(void);\n\n\tCl_Messages_Init();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_CHAT);\n\tCvar_Register(&cl_parseWhiteText);\n\tCvar_Register(&cl_chatsound);\n\tCvar_Register(&cl_fakename);\n\tCvar_Register(&cl_fakename_suffix);\n\n\tCvar_Register(&cl_restrictions);\n\n\tCvar_Register(&cl_floodprot);\n\tCvar_Register(&cl_fp_messages);\n\tCvar_Register(&cl_fp_persecond);\n\n\tCvar_Register(&msg_filter);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\tCvar_Register(&cl_shownet);\n\tCvar_Register(&cl_confirmquit);\n\tCvar_Register(&cl_window_caption);\n\tCvar_Register(&cl_window_caption_delimiter);\n\tCvar_Register(&cl_onload);\n\n#ifdef WIN32\n\tCvar_Register(&cl_verify_qwprotocol);\n#endif // WIN32\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SBAR);\n\tCvar_Register(&cl_sbar);\n\tCvar_Register(&cl_hudswap);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_VIEWMODEL);\n\tCvar_Register(&cl_filterdrawviewmodel);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_EYECANDY);\n\tCvar_Register(&cl_model_bobbing);\n\tCvar_Register(&cl_nolerp);\n\tCvar_Register(&cl_nolerp_on_entity);\n\tCvar_Register(&cl_newlerp);\n\tCvar_Register(&cl_lerp_monsters);\n\tCvar_Register(&demo_spawnwarn);\n\tCvar_Register(&demo_spawnwarn_text);\n\tCvar_Register(&cl_maxfps);\n\tCvar_Register(&cl_maxfps_menu);\n\tCvar_Register(&cl_physfps);\n\tCvar_Register(&hud_fps_min_reset_interval);\n\tCvar_Register(&hud_frametime_max_reset_interval);\n\tCvar_Register(&hud_performance_average);\n\tCvar_Register(&cl_physfps_spectator);\n\tCvar_Register(&cl_independentPhysics);\n\tCvar_Register(&cl_deadbodyfilter);\n\tCvar_Register(&cl_gibfilter);\n\tCvar_Register(&cl_backpackfilter);\n\tCvar_Register(&cl_muzzleflash);\n\tCvar_Register(&cl_rocket2grenade);\n\tCvar_Register(&r_explosiontype);\n\tCvar_Register(&r_lightflicker);\n\tCvar_Register(&r_lightmap_lateupload);\n\tCvar_Register(&r_lightmap_packbytexture);\n\tCvar_Register(&r_rockettrail);\n\tCvar_Register(&r_grenadetrail);\n\tCvar_Register(&r_railtrail);\n\tCvar_Register(&r_instagibtrail);\n\tCvar_Register(&r_powerupglow);\n\tCvar_Register(&cl_novweps);\n\tCvar_Register(&r_drawvweps);\n\tCvar_Register(&r_rocketlight);\n\tCvar_Register(&r_explosionlight);\n\tCvar_Register(&r_rocketlightcolor);\n\tCvar_Register(&r_explosionlightcolor);\n\tCvar_Register(&r_flagcolor);\n\tCvar_Register(&cl_fakeshaft);\n\tCvar_Register(&cl_fakeshaft_extra_updates);\n\tCvar_Register(&r_telesplash);\n\tCvar_Register(&r_shaftalpha);\n\tCvar_Register(&r_lightdecayrate);\n\n\tSkin_RegisterCvars();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_DEMO);\n\tCvar_Register(&cl_demospeed);\n\tCvar_Register(&cl_demoPingInterval);\n\tCvar_Register(&qizmo_dir);\n\tCvar_Register(&qwdtools_dir);\n\tCvar_Register(&demo_getpings);\n\tCvar_Register(&demo_autotrack);\n\tCvar_Register(&cl_demoteamplay);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SOUND);\n\tCvar_Register(&cl_staticsounds);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_USERINFO);\n\tCvar_Register(&team);\n\tCvar_Register(&spectator);\n\tCvar_Register(&skin);\n\tCvar_Register(&rate);\n\tCvar_Register(&mtu);\n\tCvar_Register(&name);\n\tCvar_Register(&msg);\n\tCvar_Register(&noaim);\n\tCvar_Register(&topcolor);\n\tCvar_Register(&bottomcolor);\n\tCvar_Register(&w_switch);\n\tCvar_Register(&b_switch);\n\tCvar_Register(&railcolor);\n\tCvar_Register(&gender);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_NETWORK);\n\tCvar_Register(&cl_predict_players);\n\tCvar_Register(&cl_solid_players);\n\tCvar_Register(&cl_predict_half);\n\tCvar_Register(&cl_timeout);\n\tCvar_Register(&cl_useproxy);\n\tCvar_Register(&cl_proxyaddr);\n\tCvar_Register(&cl_crypt_rcon);\n\tCvar_Register(&cl_fix_mvd);\n\n\tCvar_Register(&cl_delay_packet);\n\tCvar_Register(&cl_delay_packet_target);\n\tCvar_Register(&cl_delay_packet_dev);\n\tCvar_Register(&cl_earlypackets);\n\n#if defined(PROTOCOL_VERSION_FTE) || defined(PROTOCOL_VERSION_FTE2) || defined(PROTOCOL_VERSION_MVD1)\n\tCvar_Register(&cl_pext);\n\tCvar_Register(&cl_pext_limits);\n\tCvar_Register(&cl_pext_other);\n\tCvar_Register(&cl_pext_warndemos);\n#ifdef MVD_PEXT1_HIGHLAGTELEPORT\n\tCvar_Register(&cl_pext_lagteleport);\n#endif\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\tCvar_Register(&cl_pext_serversideweapon);\n#endif\n#endif // PROTOCOL_VERSION_FTE\n#ifdef FTE_PEXT_256PACKETENTITIES\n\tCvar_Register(&cl_pext_256packetentities);\n#endif\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tCvar_Register(&cl_pext_chunkeddownloads);\n\tCvar_Register(&cl_chunksperframe);\n#endif\n\n#ifdef FTE_PEXT_FLOATCOORDS\n\tCvar_Register(&cl_pext_floatcoords);\n#endif\n\n#ifdef FTE_PEXT_TRANS\n\tCvar_Register(&cl_pext_alpha);\n#endif\n#ifdef FTE_PEXT_COLOURMOD\n\tCvar_Register(&cl_pext_colourmod);\n#endif\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_INPUT_KEYBOARD);\n\tCvar_Register(&allow_scripts);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SYSTEM_SETTINGS);\n\tCvar_Register(&cl_mediaroot);\n\n#ifndef CLIENTONLY\n\tCvar_SetCurrentGroup(CVAR_GROUP_COMMUNICATION);\n\tCvar_Register(&cl_sv_packetsync);\n#endif\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_NO_GROUP);\n\tCvar_Register (&password);\n\tCvar_Register (&rcon_password);\n\tCvar_Register (&rcon_address);\n\tCvar_Register (&localid);\n\tCvar_Register (&cl_warncmd);\n\tCvar_Register (&cl_cmdline);\n\tCvar_ForceSet (&cl_cmdline, com_args_original);\n\tCvar_ResetCurrentGroup();\n\n\tCvar_Register (&cl_username);\n\tCmd_AddCommand(\"authenticate\", CL_Authenticate_f);\n\n\t// debugging antilag\n\tCvar_Register(&cl_debug_antilag_view);\n\tCvar_Register(&cl_debug_antilag_ghost);\n\tCvar_Register(&cl_debug_antilag_self);\n\tCvar_Register(&cl_debug_antilag_lines);\n\tCvar_Register(&cl_debug_antilag_send);\n\n\t// debugging weapons\n\tCvar_Register(&cl_debug_weapon_view);\n\n\tsnprintf(st, sizeof(st), \"ezQuake %i\", REVISION);\n\n\tif (COM_CheckParm (cmdline_param_client_norjscripts) || COM_CheckParm (cmdline_param_client_noscripts))\n\t\tCvar_SetValue (&allow_scripts, 0);\n\n \tInfo_SetValueForStarKey (cls.userinfo, \"*client\", st, MAX_INFO_STRING);\n\n\tsnprintf(st, sizeof(st), \"ezQuake v%s %s\", VERSION_NUMBER, VERSION);\n\tInfo_SetValueForStarKey(cls.userinfo, \"*ver\", st, MAX_INFO_STRING);\n\n\tif (COM_CheckParm(cmdline_param_client_noindphys))\n\t{\n\t\tCvar_SetValue(&cl_independentPhysics, 0);\n\t\tCvar_SetValue(&cl_nolerp, 1);\n\t}\n\n\tCL_InitCommands ();\n\n\tCmd_AddCommand (\"disconnect\", CL_Disconnect_f);\n\tCmd_AddCommand (\"connect\", CL_Connect_f);\n\tCmd_AddCommand (\"connectbr\", CL_Connect_BestRoute_f);\n\n\tCmd_AddCommand (\"qwurl\", CL_QWURL_f);\n\n\tCmd_AddCommand (\"tcpconnect\", CL_TCPConnect_f);\n\n\tCmd_AddCommand (\"join\", CL_Join_f);\n\tCmd_AddCommand (\"observe\", CL_Observe_f);\n\tCmd_AddCommand (\"observebr\", CL_Observe_BestRoute_f);\n\tCmd_AddCommand (\"togglespec\", Cl_ToggleSpec_f);\n\n\tCmd_AddCommand (\"hud_fps_min_reset\", Cl_Reset_Min_fps_f);\n\n\t#if defined WIN32 || defined __linux__\n\tCmd_AddCommand (\"register_qwurl_protocol\", Sys_RegisterQWURLProtocol_f);\n\t#endif // WIN32 or linux\n\n\tCmd_AddCommand (\"dns\", CL_DNS_f);\n\tCmd_AddCommand (\"hash\", CL_Hash_f);\n\tCmd_AddCommand (\"reconnect\", CL_Reconnect_f);\n\n\tCmd_AddMacro(macro_connectiontype, CL_Macro_ConnectionType);\n\tCmd_AddMacro(macro_demoplayback, CL_Macro_Demoplayback);\n\tCmd_AddMacro(macro_demotime, CL_Macro_Demotime);\n\tCmd_AddMacro(macro_rand, CL_Macro_Rand);\n\tCmd_AddMacro(macro_matchstatus, CL_Macro_Serverstatus);\n\tCmd_AddMacro(macro_serverip, CL_Macro_ServerIp);\n\tCmd_AddMacro(macro_conwidth, CL_Macro_Conwidth);\n\tCmd_AddMacro(macro_conheight, CL_Macro_Conheight);\n\n#ifdef WITH_RENDERING_TRACE\n\tif (R_DebugProfileContext()) {\n\t\tCmd_AddCommand(\"dev_gfxtrace\", Dev_VidFrameTrace);\n\t\tCmd_AddCommand(\"dev_gfxtexturedump\", Dev_VidTextureDump);\n\t\tCmd_AddCommand(\"dev_gfxtexturelist\", Dev_TextureList);\n\t}\n#endif\n\n#ifndef CLIENTONLY\n\tif (IsDeveloperMode()) {\n\t\tCmd_AddCommand(\"dev_physicsnormalset\", Dev_PhysicsNormalSet);\n\t\tCmd_AddCommand(\"dev_physicsnormalshow\", Dev_PhysicsNormalShow);\n\t\tCmd_AddCommand(\"dev_physicsnormalsave\", Dev_PhysicsNormalSave);\n\t}\n#endif\n\n\t{\n\t\textern void GL_BenchmarkLightmapFormats(void);\n\n\t\tCmd_AddCommand(\"dev_gfxbenchmarklightmaps\", GL_BenchmarkLightmapFormats);\n\t}\n}\n\nvoid GFX_Init (void) \n{\n\tDraw_Init();\n\tSCR_Init();\n\tR_Init();\n\tSbar_Init();\n\tHUD_Editor_Init();\t// Need to reload some textures.\n\tVX_TrackerInit();\n\n\tCachePics_CreateAtlas();\n}\n\nvoid ReloadPaletteAndColormap(void)\n{\n\tint filesize;\n\n\t// free\n\n\tQ_free(host_basepal);\n\tQ_free(host_colormap);\n\n\t// load\n\n\thost_basepal = (byte *) FS_LoadHeapFile (\"gfx/palette.lmp\", &filesize);\n\tif (!host_basepal)\n\t\tSys_Error(\"Couldn't load gfx/palette.lmp\\n\\nThis is typically caused by being unable to locate pak0.pak.\\nCopy pak0.pak into 'id1' folder, in same directory as executable.\");\n\tFMod_CheckModel(\"gfx/palette.lmp\", host_basepal, filesize);\n\n\thost_colormap = (byte *) FS_LoadHeapFile (\"gfx/colormap.lmp\", &filesize);\n\tif (!host_colormap)\n\t\tSys_Error (\"Couldn't load gfx/colormap.lmp\");\n\tFMod_CheckModel(\"gfx/colormap.lmp\", host_colormap, filesize);\n}\n\nvoid EX_FileList_Init(void);\n\nvoid CL_Init (void) \n{\n\t// When ezquake was launched via a webpage (qtv) the working directory wasn't properly\n\t// set. Changing the directory makes sure it starts out in the directory where ezquake \n\t// is located.\n\tSys_chdir(com_basedir);\n\n\tcls.state = ca_disconnected;\n\tcls.min_fps = 999999;\n\tcls.max_frametime = 1;\n\n\tSZ_Init(&cls.cmdmsg, cls.cmdmsg_data, sizeof(cls.cmdmsg_data));\n\tcls.cmdmsg.allowoverflow = true;\n\n\tstrlcpy (cls.gamedirfile, com_gamedirfile, sizeof (cls.gamedirfile));\n\tstrlcpy (cls.gamedir, com_gamedir, sizeof (cls.gamedir));\n\n\tFChecks_Init();\n\n\tReloadPaletteAndColormap();\n\n\tSys_mkdir(va(\"%s/qw\", com_basedir));\n\tSys_mkdir(va(\"%s/ezquake\", com_basedir));\n\n\tSDL_SetHint(SDL_HINT_APP_NAME, \"ezQuake\");\n\n\tHistory_Init();\n\tV_Init ();\n\tMVD_Utils_Init ();\n\n\tVID_Init(host_basepal);\n\tIN_Init();\n\n\tImage_Init();\n\n\tGFX_Init ();\n\n\tS_Init ();\n\n\tCDAudio_Init ();\n\n\tCL_InitLocal ();\n\tCL_FixupModelNames ();\n\tCL_InitInput ();\n\tCL_InitEnts ();\n\tCL_InitTEnts ();\n\tCL_InitTEntsCvar();\n\tCL_InitPrediction ();\n\tCL_InitCam ();\n\tTP_Init ();\n\tHud_262Init();\n\tHUD_Init();\n\tHelp_Init();\n\tM_Init ();\n\tEX_FileList_Init();\n\n\tSList_Init ();\n\tSList_Load ();\n\n\tMT_Init();\n\tCL_Demo_Init();\n\tIgnore_Init();\n\tLog_Init();\n\tMovie_Init();\n\n#ifdef _DEBUG\n\tif (Expr_Run_Unit_Tests() != 0) {\n\t\tSys_Error(\"One of the expression parser unit tests failed\");\n\t}\n#endif\n\n\t// moved to host.c:Host_Init()\n\t//ConfigManager_Init();\n\tStats_Init();\n\tSB_RootInit();\n#ifdef WITH_IRC\t\n\tIRC_Init();\n#endif\n\n\tQTV_Init();\n\n\tSys_InitIPC();\n\n\tRulesets_Init();\n}\n\n//============================================================================\n\nvoid CL_BeginLocalConnection (void) \n{\n\tS_StopAllSounds();\n\n\t// make sure we're not connected to an external server,\n\t// and demo playback is stopped\n\tif (!com_serveractive)\n\t\tCL_Disconnect ();\n\n\tcl.worldmodel = NULL;\n\n\tif (cls.state == ca_active)\n\t\tcls.state = ca_connected;\n}\n\n// automatically pause the game when going into the menus in single player\nstatic void CL_CheckAutoPause (void) \n{\n\t#ifndef CLIENTONLY\n\n\textern cvar_t maxclients;\n\n\tif (com_serveractive && cls.state == ca_active && !cl.deathmatch && maxclients.value == 1\n\t\t&& (key_dest == key_menu /*|| key_dest == key_console*/))\n\t{\n\t\tif (!(sv.paused & 2))\n\t\t\tSV_TogglePause (NULL, 2);\n\t}\n\telse \n\t{\n\t\tif (sv.paused & 2)\n\t\t\tSV_TogglePause (NULL, 2);\n\t}\n\t#endif // CLIENTONLY\n}\n\n\nstatic double CL_MinFrameTime (void) \n{\n\tdouble fps, fpscap;\n\n\tif (cls.timedemo || Movie_IsCapturing())\n\t\treturn 0;\n\n\tif ((cls.state == ca_disconnected) || (Minimized && !cls.download))\n\t\tif (cl_maxfps_menu.value >= 30)\n\t\t\treturn 1 / cl_maxfps_menu.value;\n\t\telse\n\t\t\treturn 1 / (r_displayRefresh.autoString ? atoi(r_displayRefresh.autoString) : 30.0);\n\n\tif (cls.demoplayback)\n\t{\n\t\tif (!cl_maxfps.value)\n\t\t\treturn 0;\n\n\t\t// Multiview.\n\t\tfps = max(30.0, cl_maxfps.value);\n\t}\n\telse\n\t{\n\t\tif (cl_independentPhysics.value == 0)\n\t\t{\n\t\t\tfpscap = cl.maxfps ? max (30.0, cl.maxfps) : Rulesets_MaxFPS();\n\t\t\tfps = cl_maxfps.value ? bound (30.0, cl_maxfps.value, fpscap) : com_serveractive ? fpscap : bound (30.0, rate.value / 80.0, fpscap);\n\t\t}\n\t\telse\n\t\t\tfps = cl_maxfps.value ? max(cl_maxfps.value, 30) : 99999; //#fps:\n\t}\n\n\treturn 1 / fps;\n}\n\nstatic double MinPhysFrameTime (void)\n{\n\t// Server policy\n\tfloat fpscap = (cl.maxfps ? cl.maxfps : 72.0);\n\t// Use either cl_physfps_spectator or cl_physfps\n\tfloat physfps = ((cl.spectator && !cls.demoplayback) ? cl_physfps_spectator.value : cl_physfps.value);\n\n\t// this makes things smooth in mvd demo play back, since mvd interpolation applied each frame\n\tif (cls.demoplayback)\n\t\treturn 0;\n\n\t// the user can lower it for testing (or really shit connection)\n\tif (physfps)\n\t\tfpscap = min(fpscap, physfps);\n\n\t// not less than this no matter what\n\tfpscap = max(fpscap, 10);\n\n\treturn 1 / fpscap;\n}\n\nvoid onchange_hud_performance_average(cvar_t* var, char* value, qbool* cancel)\n{\n\t// Reset on change\n\tif (strcmp(var->string, value)) {\n\t\tCl_Reset_Min_fps_f();\n\t}\n}\n\nvoid CL_CalcFPS(void)\n{\n\tdouble t = Sys_DoubleTime();\n\tperfinfo_t* stats = &cls.fps_stats;\n\tdouble frametime = stats->last_run_time == 0 ? 0 : t - stats->last_run_time;\n\tdouble time_since_snapshot = (t - stats->last_snapshot_time);\n\tstats->last_run_time = t;\n\n\t// Average over previous second\n\tif (time_since_snapshot >= 1.0)\n\t{\n\t\tstats->lastfps_value = (double)stats->fps_count / time_since_snapshot;\n\t\tstats->lastframetime_value = time_since_snapshot / max(stats->fps_count, 1);\n\t\tstats->fps_count = 0;\n\t\tstats->last_snapshot_time = t;\n\t}\n\n\tcls.fps = stats->lastfps_value;\n\tcls.avg_frametime = stats->lastframetime_value;\n\n\tif (hud_performance_average.integer) {\n\t\t// update min_fps if last fps is less than our lowest accepted minfps (10.0) or greater than min_reset_interval\n\t\tif ((stats->lastfps_value > 10.0 && stats->lastfps_value < cls.min_fps) || ((t - stats->time_of_last_minfps_update) > hud_fps_min_reset_interval.value)) {\n\t\t\tcls.min_fps = stats->lastfps_value;\n\t\t\tstats->time_of_last_minfps_update = t;\n\t\t}\n\t\tif ((stats->lastframetime_value < 2.0f && stats->lastframetime_value > cls.max_frametime) || ((t - stats->time_of_last_maxframetime_update) > hud_frametime_max_reset_interval.value)) {\n\t\t\tcls.max_frametime = stats->lastframetime_value;\n\t\t\tstats->time_of_last_maxframetime_update = t;\n\t\t}\n\t}\n\telse if (frametime > 0) {\n\t\t// update min_fps if last fps is less than our lowest accepted minfps (10.0) or greater than min_reset_interval\n\t\tif (stats->lastfps_value < cls.min_fps || ((t - stats->time_of_last_minfps_update) > hud_fps_min_reset_interval.value)) {\n\t\t\tcls.min_fps = stats->lastfps_value;\n\t\t\tstats->time_of_last_minfps_update = t;\n\t\t}\n\t\tif (frametime > cls.max_frametime || ((t - stats->time_of_last_maxframetime_update) > hud_frametime_max_reset_interval.value)) {\n\t\t\tcls.max_frametime = frametime;\n\t\t\tstats->time_of_last_maxframetime_update = t;\n\t\t}\n\t}\n}\n\nvoid Cl_Reset_Min_fps_f(void)\n{\n\tcls.min_fps = 9999.0f;\n\tcls.max_frametime = 0.0f;\n\n\tCL_CalcFPS();\n}\n\nvoid CL_QTVPoll (void);\nvoid SB_ExecuteQueuedTriggers (void);\n\nvoid CL_LinkEntities (void)\n{\n\tif (cls.state >= ca_onserver)\n\t{\n\t\t// actually it should be curtime == cls.netchan.last_received but that did not work on float values...\n\t\tqbool recent_packet = (curtime - cls.netchan.last_received < 0.00001);\n\t\tqbool setup_player_prediction = ((physframe && cl_independentPhysics.value != 0) || cl_independentPhysics.value == 0);\n\t\tsetup_player_prediction |= recent_packet && !cls.demoplayback && cl_earlypackets.integer;\n\n\t\tCam_SetViewPlayer();\n\n\t\tif (setup_player_prediction) {\n\t\t\t// Set up prediction for other players\n\t\t\tCL_SetUpPlayerPrediction(false);\n\t\t\tCL_PredictMove(true);\n\t\t\tCL_SetUpPlayerPrediction(true);\n\t\t}\n\t\telse {\n\t\t\t// Do client side motion prediction\n\t\t\tCL_PredictMove(false);\n\t\t}\n\n\t\t// build a refresh entity list\n\t\tCL_EmitEntities();\n\t}\n}\n\nvoid CL_SoundFrame (void)\n{\n\tif (cls.state == ca_active)\n\t{\n\t\tif (!ISPAUSED) {\n\t\t\tS_Update (r_origin, vpn, vright, vup);\n\t\t}\n\t\telse {\n\t\t\t// do not play loop sounds (lifts etc.) when paused\n#ifndef INFINITY\n\t\t\tfloat temp = 1.0;\n\t\t\tfloat infinity = temp / (temp - 1.0);\n\t\t\tvec3_t hax = { infinity, infinity, infinity };\n#else\n\t\t\tvec3_t hax = { INFINITY, INFINITY, INFINITY };\n#endif\n\t\t\tS_Update(hax, vec3_origin, vec3_origin, vec3_origin);\n\t\t}\n\t}\n\telse\n\t{\n\t\tS_Update (vec3_origin, vec3_origin, vec3_origin, vec3_origin);\n\t}\n}\n\nstatic void CL_ServerFrame(double frametime)\n{\n#ifndef CLIENTONLY\n\tif (com_serveractive) {\n\t\tplayermove_t oldmove;\n\n\t\tmemcpy(&oldmove, &pmove, sizeof(playermove_t));\n\n\t\tSV_Frame(frametime);\n\n\t\tmemcpy(&pmove, &oldmove, sizeof(playermove_t));\n\t}\n#endif\n}\n\nvoid CL_Frame(double time)\n{\n\tstatic double extratime = 0.001;\n\tdouble minframetime;\n\tstatic double\textraphysframetime;\t//#fps\n\tqbool need_server_frame = false;\n\n\textratime += time;\n\tminframetime = CL_MinFrameTime();\n\tCL_MultiviewFrameStart ();\n\n\tif (extratime < minframetime) {\n\t\textern cvar_t sys_yieldcpu;\n\t\tif (sys_yieldcpu.integer || Minimized) {\n\t\t\t#ifdef _WIN32\n\t\t\tSys_MSleep(0);\n\t\t\t#else\n\t\t\tusleep( (minframetime - extratime) * 1000 * 1000 );\n\t\t\t#endif\n\t\t}\n\n\t\tif (cl_delay_packet.integer || cl_delay_packet_target.integer) {\n\t\t\tCL_QueInputPacket();\n\t\t\tneed_server_frame = CL_UnqueOutputPacket(false);\n\t\t}\n\n\t\tif (need_server_frame && PROCESS_SERVERPACKETS_IMMEDIATELY) {\n\t\t\tCL_ServerFrame(0);\n\t\t}\n\n\t\treturn;\n\t}\n\n\tif (cl_delay_packet.integer || cl_delay_packet_target.integer) {\n\t\tCL_QueInputPacket();\n\t\tneed_server_frame = CL_UnqueOutputPacket(false);\n\t}\n\n\tif (VID_VSyncLagFix()) {\n\t\tif (need_server_frame && PROCESS_SERVERPACKETS_IMMEDIATELY) {\n\t\t\tCL_ServerFrame(0);\n\t\t}\n\t\treturn;\n\t}\n\n\tcls.trueframetime = extratime - 0.001;\n\tcls.trueframetime = max(cls.trueframetime, minframetime);\n\textratime -= cls.trueframetime;\n\n\tif (Movie_IsCapturing()) {\n\t\tMovie_StartFrame();\n\t\tcls.frametime = Movie_Frametime();\n\t}\n\telse {\n\t\tcls.frametime = min(0.2, cls.trueframetime);\n\t}\n\t\n\tif (cl_independentPhysics.value != 0)\n\t{\n\t\tdouble minphysframetime = MinPhysFrameTime();\n\n\t\textraphysframetime += cls.frametime;\n\t\tif (extraphysframetime < minphysframetime) {\n\t\t\tphysframe = false;\n\t\t}\n\t\telse {\n\t\t\tphysframe = true;\n\n\t\t\t// FIXME: this is for the case when actual fps is too low.  Dunno how to do it right\n\t\t\tif (extraphysframetime > minphysframetime * 2) {\n\t\t\t\tphysframetime = extraphysframetime;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tphysframetime = minphysframetime;\n\t\t\t}\n\t\t\textraphysframetime -= physframetime;\n\t\t}\n\t} \n\telse {\n\t\t// this vars SHOULD NOT be used in case of cl_independentPhysics == 0, so we just reset it for sanity\n\t\tphysframetime = extraphysframetime = 0;\n\t\t// this var actually used\n\t\tphysframe = true;\n\t}\n\n\tif (cls.demoplayback) {\n\t\tDemo_AdjustSpeed();\n\n\t\tif (cl.paused & PAUSED_DEMO) {\n\t\t\tcls.frametime = 0;\n\t\t}\n\t\telse if (!cls.timedemo) {\n\t\t\tcls.frametime *= Demo_GetSpeed();\n\t\t}\n\t\telse if (cls.timedemo == TIMEDEMO_FIXEDFPS) {\n\t\t\tcls.frametime = cls.td_frametime;\n\t\t}\n\n\t\tif (!host_skipframe) {\n\t\t\tcls.demotime += cls.frametime;\n\t\t}\n\t\thost_skipframe = false;\n\t}\n\n\tcls.realtime += cls.frametime;\n\n\tif (!ISPAUSED) \n\t{\n\t\tcl.time += cls.frametime;\n\t\tcl.servertime += cls.frametime;\n\t\tcl.stats[STAT_TIME] = (int) (cl.servertime * 1000);\n\t\tif (cls.demoplayback)\n\t\t\tcl.gametime += cls.frametime;\n\t\telse\n\t\t\tcl.gametime = Sys_DoubleTime() - cl.gamestarttime - cl.gamepausetime;\n\t}\n\telse\n\t{\n\t\t// We hope here, that pause doesn't take long so we don't get too much de-synced\n\t\t// if pause takes too much, we can get into usual clock sync-problems as we did before\n\t\tcl.gamepausetime += cls.frametime;\n\t}\n\n\tr_refdef2.time = cl.time;\n\n\t// get new key events\n\tif (cl_independentPhysics.value == 0)\n\t{\n\t\tSys_SendKeyEvents();\n\n\t\t// allow mice or other external controllers to add commands\n\t\tIN_Commands();\n\n\t\t// process console commands\n\t\tCbuf_Execute();\n\t\tCL_CheckAutoPause();\n\n#ifndef CLIENTONLY\n\t\tCL_ServerFrame(cls.frametime);\n#endif\n\n\t\t// fetch results from server\n\t\tCL_ReadPackets();\n\n\t\tTP_UpdateSkins();\n\n\t\tif (cls.mvdplayback && !cls.demoseeking)\n\t\t{\n\t\t\tMVD_Interpolate();\n\t\t\tMVD_Mainhook();\n\n\t\t\tif (!cl.standby && physframe) {\n\t\t\t\tStatsGrid_Gather();\n\t\t\t}\n\t\t}\n\n\t\t// process stuffed commands\n\t\tCbuf_ExecuteEx(&cbuf_svc);\n\n\t\tCL_SendToServer();\n\n\t\t// We need to move the mouse also when disconnected\n\t\t// to get the cursor working properly.\n\t\tif (cls.state == ca_disconnected) {\n\t\t\tusercmd_t dummy;\n\t\t\tIN_Move(&dummy);\n\t\t}\n\n\t\tSys_SendDeferredKeyEvents();\n\t}\n\telse \n\t{\n\t\tif (physframe) {\n\t\t\tSys_SendKeyEvents();\n\n\t\t\t// allow mice or other external controllers to add commands\n\t\t\tIN_Commands();\n\n\t\t\t// process console commands\n\t\t\tCbuf_Execute();\n\t\t\tCL_CheckAutoPause ();\n\n#ifndef CLIENTONLY\n\t\t\tCL_ServerFrame(physframetime);\n#endif\n\n\t\t\t// Fetch results from server\n\t\t\tCL_ReadPackets();\n\n\t\t\tTP_UpdateSkins();\n\n\t\t\t// Gather MVD stats and interpolate.\n\t\t\tif (cls.mvdplayback && !cls.demoseeking)\n\t\t\t{\n\t\t\t\tMVD_Interpolate();\n\t\t\t\tMVD_Mainhook();\n\n\t\t\t\tif (!cl.standby && physframe) {\n\t\t\t\t\tStatsGrid_Gather();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// process stuffed commands\n\t\t\tCbuf_ExecuteEx(&cbuf_svc);\n\n\t\t\tCL_SendToServer();\n\n\t\t\t// We need to move the mouse also when disconnected\n\t\t\tif (cls.state == ca_disconnected) {\n\t\t\t\tusercmd_t dummy;\n\t\t\t\tIN_Move(&dummy);\n\t\t\t}\n\n\t\t\tSys_SendDeferredKeyEvents();\n\t\t}\n\t\telse {\n\t\t\tif (need_server_frame && PROCESS_SERVERPACKETS_IMMEDIATELY) {\n\t\t\t\tCL_ServerFrame(0);\n\t\t\t}\n\n\t\t\tif (!cls.demoplayback && cl_earlypackets.integer) {\n\t\t\t\tCL_ReadPackets(); // read packets ASAP\n\t\t\t}\n\n\t\t\tif (   (!cls.demoplayback && !cl.spectator) // not demo playback and not a spec\n\t\t\t\t|| (!cls.demoplayback && cl.spectator && Cam_TrackNum() == -1) // not demo, spec free fly\n\t\t\t\t|| ( cls.demoplayback && cls.mvdplayback && Cam_TrackNum() == -1) // mvd demo and free fly\n\t\t\t\t|| cls.state == ca_disconnected // We need to move the mouse also when disconnected\n\t\t\t\t) \n\t\t\t{\n\t\t\t\tusercmd_t dummy;\n\t\t\t\tSys_SendKeyEvents();\n\t\t\t\tIN_Move(&dummy);\n\t\t\t}\n\t\t}\n\t}\n\n\t{\n\t\t// chat icons\n\t\tint cif_flags = 0;\n\n\t\t// add chat flag if in console, menus, mm1, mm2 etc...\n\t\tcif_flags |= (key_dest != key_game ? CIF_CHAT : 0);\n\n\t\t// add AFK flag if app minimized, or not the focus\n\t\t// TODO: may be add afk flag on idle? if no user input in 45 seconds for example?\n\t\tcif_flags |= (!ActiveApp || Minimized ? CIF_AFK : 0);\n\n\t\tif (cif_flags != cl.cif_flags) {\n\t\t\tchar char_flags[64] = {0};\n\n\t\t\tif (cif_flags && cls.state >= ca_connected) {\n\t\t\t\t// put key in userinfo only then we are connected, remove key if we not connected yet\n\t\t\t\tsnprintf(char_flags, sizeof(char_flags), \"%d\", cif_flags);\n\t\t\t}\n\n\t\t\tCL_UserinfoChanged(\"chat\", char_flags);\n\t\t\tcl.cif_flags = cif_flags;\n\t\t}\n\t}\n\n\tVID_ReloadCheck();\n\n\tR_ParticleFrame();\n\n\tbuffers.StartFrame();\n\n\tCachePics_AtlasFrame();\n\n\tCL_MultiviewPreUpdateScreen();\n\n\t// update video\n\tif (CL_MultiviewEnabled()) {\n\t\tqbool draw_next_view = true;\n\t\tqbool first_view = true;\n\n\t\tR_PerformanceBeginFrame();\n\t\tif (SCR_UpdateScreenPrePlayerView()) {\n\t\t\tqbool two_pass_rendering = GL_FramebufferEnabled2D();\n\t\t\trenderer.ScreenDrawStart();\n\n\t\t\twhile (draw_next_view) {\n\t\t\t\tdraw_next_view = CL_MultiviewAdvanceView();\n\t\t\t\tif (!first_view) {\n\t\t\t\t\tbuffers.EndFrame();\n\t\t\t\t\tbuffers.StartFrame();\n\t\t\t\t}\n\t\t\t\tfirst_view = false;\n\n\t\t\t\tCL_LinkEntities();\n\t\t\t\tCL_SpawnWarn_UpdateWarning();\n\n\t\t\t\tSCR_CalcRefdef();\n\n\t\t\t\tSCR_UpdateScreenPlayerView((draw_next_view ? 0 : UPDATESCREEN_POSTPROCESS) | (two_pass_rendering ? UPDATESCREEN_3D_ONLY : 0));\n\n\t\t\t\tif (!two_pass_rendering) {\n\t\t\t\t\tSCR_DrawMultiviewIndividualElements();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tSCR_SaveAutoID();\n\t\t\t\t}\n\n\t\t\t\tif (CL_MultiviewCurrentView() == 2 || (CL_MultiviewCurrentView() == 1 && CL_MultiviewActiveViews() == 1)) {\n\t\t\t\t\tCL_SoundFrame();\n\t\t\t\t}\n\n\t\t\t\t// Multiview: advance to next player\n\t\t\t\tCL_MultiviewFrameFinish();\n\t\t\t}\n\n\t\t\tif (two_pass_rendering) {\n\t\t\t\tbuffers.EndFrame();\n\n\t\t\t\tdraw_next_view = true;\n\t\t\t\twhile (draw_next_view) {\n\t\t\t\t\tdraw_next_view = CL_MultiviewAdvanceView();\n\n\t\t\t\t\t// Need to call this again to keep autoid correct\n\t\t\t\t\tSCR_RestoreAutoID();\n\n\t\t\t\t\tSCR_UpdateScreenPlayerView(UPDATESCREEN_2D_ONLY);\n\t\t\t\t\tSCR_DrawMultiviewIndividualElements();\n\n\t\t\t\t\t// Multiview: advance to next player\n\t\t\t\t\tCL_MultiviewFrameFinish();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tSCR_UpdateScreenPostPlayerView();\n\t\t}\n\t\telse {\n\t\t\tVID_RenderFrameEnd();\n\t\t}\n\t\tR_PerformanceEndFrame();\n\t}\n\telse {\n\t\tCL_LinkEntities();\n\t\tCL_SpawnWarn_UpdateWarning();\n\n\t\tR_PerformanceBeginFrame();\n\t\tSCR_UpdateScreen();\n\t\tR_PerformanceEndFrame();\n\n\t\tCL_SoundFrame();\n\t}\n\n\tCL_DecayLights();\n\n\tCDAudio_Update();\n\n\tMT_Frame();\n\n\tif (Movie_IsCapturing()) {\n\t\tMovie_FinishFrame();\n\t}\n\n\tcls.framecount++;\n\tcls.fps_stats.fps_count++;\n\tCL_CalcFPS();\n\n\tVFS_TICK(); // VFS hook for updating some systems\n\n\tSys_ReadIPC();\n\n\tCL_QTVPoll();\n#ifdef WITH_IRC\n\tIRC_Update();\n#endif\n\n\tSB_ExecuteQueuedTriggers();\n\n\tR_ParticleEndFrame();\n\n\tCL_UpdateCaption(false);\n}\n\n//============================================================================\n\nvoid CL_Shutdown (void) \n{\n\tCL_Disconnect();\n\tSList_Shutdown();\n\tCDAudio_Shutdown();\n\tS_Shutdown();\n\tIN_Shutdown ();\n\tLog_Shutdown();\n\tif (host_basepal) {\n\t\tVID_Shutdown(false);\n\t}\n\tHistory_Shutdown();\n\tSys_CloseIPC();\n\tSB_Shutdown();\n\tMT_Shutdown();\n\tHelp_Shutdown();\n\tHUD_Shutdown();\n\tCmd_Shutdown();\n\tKey_Shutdown();\n\tTP_Shutdown();\n\tM_Shutdown();\n\tStats_Shutdown();\n\tDraw_Shutdown();\n\tCache_Flush();\n\tQ_free(host_basepal);\n\tQ_free(host_colormap);\n}\n\nvoid CL_UpdateCaption(qbool force)\n{\n\tstatic char caption[512] = { 0 };\n\tchar str[512] = { 0 };\n\n\tif (!cl_window_caption.value) {\n\t\tif (!cls.demoplayback && (cls.state == ca_active)) {\n\t\t\tsnprintf(str, sizeof(str), \"ezQuake: %s\", cls.servername);\n\t\t}\n\t\telse {\n\t\t\tsnprintf(str, sizeof(str), \"ezQuake\");\n\t\t}\n\t}\n\telse if (cl_window_caption.integer == 1) {\n\t\tsnprintf(str, sizeof(str), \"%s - %s\", CL_Macro_Serverstatus(), MT_ShortStatus());\n\t}\n\telse if (cl_window_caption.integer == 2) {\n\t\tsnprintf(str, sizeof(str), \"ezQuake\");\n\t}\n\telse if (cl_window_caption.integer == 3) {\n\t\tif (cls.state < ca_connected) {\n\t\t\tsnprintf(str, sizeof(str), \"ezQuake v%s\", VERSION_NUMBER);\n\t\t}\n\t\telse {\n\t\t\tsnprintf(str, sizeof(str), \"%d/%d%s%s\",\n\t\t\t\tTP_CountPlayers(),\n\t\t\t\tQ_atoi(Info_ValueForKey(cl.serverinfo, \"maxclients\")),\n\t\t\t\tcl_window_caption_delimiter.string,\n\t\t\t\tTP_MapName());\n\t\t}\n\t}\n\n\tif (force || strcmp(str, caption)) {\n\t\tVID_SetCaption(str);\n\t\tstrlcpy(caption, str, sizeof(caption));\n\t}\n}\n\nvoid OnChangeDemoTeamplay (cvar_t *var, char *value, qbool *cancel)\n{\n\tif (cls.nqdemoplayback) \n\t{\n\t\tcl.teamplay = (*value != '0');\n\n\t\tNQD_SetSpectatorFlags();\n\n\t\tOnChangeColorForcing(var, value, cancel);\n\t}\n}\n\n\n\n#ifndef CLIENTONLY\n\nvoid Dev_PhysicsNormalShow(void)\n{\n\tvec3_t point = { cl.simorg[0], cl.simorg[1], cl.simorg[2] - 1 };\n\ttrace_t trace;\n\tmphysicsnormal_t physicsnormal;\n\n\tif (cls.demoplayback || cls.state != ca_active || !r_refdef2.allow_cheats) {\n\t\t// it's not actually a cheat, but using these functions would screw with prediction\n\t\tCon_Printf(\"Not available outwith /devmap\\n\");\n\t\treturn;\n\t}\n\n\ttrace = PM_PlayerTrace(pmove.origin, point);\n\tif (trace.fraction == 1 || trace.plane.normal[2] < MIN_STEP_NORMAL) {\n\t\tCon_Printf(\"Not on ground\\n\");\n\t}\n\telse {\n\t\tphysicsnormal = CM_PhysicsNormal(trace.physicsnormal);\n\n\t\tCon_Printf(\"Plane normal  : %+f %+f %+f\\n\", trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2]);\n\t\tif (!(physicsnormal.flags & PHYSICSNORMAL_SET)) {\n\t\t\tCon_Printf(\"No custom physics plane found\\n\");\n\t\t}\n\t\telse {\n\t\t\tconst char* flipx = physicsnormal.flags & PHYSICSNORMAL_FLIPX ? \"&cff0\" : \"&r\";\n\t\t\tconst char* flipy = physicsnormal.flags & PHYSICSNORMAL_FLIPY ? \"&cff0\" : \"&r\";\n\t\t\tconst char* flipz = physicsnormal.flags & PHYSICSNORMAL_FLIPZ ? \"&cff0\" : \"&r\";\n\n\t\t\tCon_Printf(\"Physics normal: %s%+f %s%+f %s%+f&r\\n\", flipx, physicsnormal.normal[0], flipy, physicsnormal.normal[1], flipz, physicsnormal.normal[2]);\n\t\t}\n\t}\n}\n\nvoid Dev_PhysicsNormalSet(void)\n{\n\tvec3_t point = { cl.simorg[0], cl.simorg[1], cl.simorg[2] - 1 };\n\ttrace_t trace;\n\tmphysicsnormal_t physicsnormal;\n\tvec3_t newnormal;\n\tint newflags = PHYSICSNORMAL_SET;\n\n\tif (cls.demoplayback || cls.state != ca_active || !r_refdef2.allow_cheats) {\n\t\t// it's not actually a cheat, but using these functions would screw with prediction\n\t\tCon_Printf(\"Not available outwith /devmap\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() < 5) {\n\t\tCom_Printf(\"Usage: %s <x> <y> <z> <flags(xyzn)>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\t{\n\t\tint i;\n\t\tconst char* flags = Cmd_Argv(4);\n\n\t\tnewnormal[0] = atof(Cmd_Argv(1));\n\t\tnewnormal[1] = atof(Cmd_Argv(2));\n\t\tnewnormal[2] = atof(Cmd_Argv(3));\n\t\tfor (i = 0; i < strlen(flags); ++i) {\n\t\t\tif (flags[i] == 'x') {\n\t\t\t\tnewflags |= PHYSICSNORMAL_FLIPX;\n\t\t\t}\n\t\t\telse if (flags[i] == 'y') {\n\t\t\t\tnewflags |= PHYSICSNORMAL_FLIPY;\n\t\t\t}\n\t\t\telse if (flags[i] == 'z') {\n\t\t\t\tnewflags |= PHYSICSNORMAL_FLIPZ;\n\t\t\t}\n\t\t\telse if (flags[i] != 'n') {\n\t\t\t\tCom_Printf(\"Unknown flag %c\\n\", flags[i]);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (newnormal[0] || newnormal[1] || newnormal[2]) {\n\t\tVectorNormalize(newnormal);\n\t\tif (VectorLength(newnormal) != 1) {\n\t\t\tCom_Printf(\"Error: failed to normalize, found %f %f %f\\n\", newnormal[0], newnormal[1], newnormal[2]);\n\t\t\treturn;\n\t\t}\n\t}\n\n\ttrace = PM_PlayerTrace(pmove.origin, point);\n\tif (trace.fraction == 1 || trace.plane.normal[2] < MIN_STEP_NORMAL) {\n\t\tCon_Printf(\"Not on ground\\n\");\n\t}\n\telse {\n\t\tphysicsnormal = CM_PhysicsNormal(trace.physicsnormal);\n\t\tif (!(physicsnormal.flags & PHYSICSNORMAL_SET)) {\n\t\t\tCon_Printf(\"Unable to determine ground normal\\n\");\n\t\t}\n\t\telse {\n\t\t\tCM_PhysicsNormalSet(trace.physicsnormal, newnormal[0], newnormal[1], newnormal[2], newflags);\n\t\t\tDev_PhysicsNormalShow();\n\t\t}\n\t}\n}\n\nvoid Dev_PhysicsNormalSave(void)\n{\n\tchar filename[MAX_OSPATH];\n\tFILE* out;\n\textern cvar_t pm_rampjump;\n\n\tif (cls.demoplayback || cls.state != ca_active || !r_refdef2.allow_cheats) {\n\t\t// it's not actually a cheat, but using these functions would screw with prediction\n\t\tCon_Printf(\"Not available outwith /devmap\\n\");\n\t\treturn;\n\t}\n\n\tsnprintf(filename, sizeof(filename), \"qw/%s.qpn\", host_mapname.string);\n\tif (!(out = fopen(filename, \"wb\"))) {\n\t\tCon_Printf(\"Failed: unable to open %s\\n\", filename);\n\t\treturn;\n\t}\n\n\tCM_PhysicsNormalDump(out, pm_rampjump.value, 0);\n\tfclose(out);\n\n\tCon_Printf(\"Wrote %s\\n\", filename);\n}\n\n#endif // !CLIENTONLY\n\nvoid Cache_Flush(void)\n{\n\tSkin_Clear(false);\n\tMod_FreeAllCachedData();\n}\n\nstatic void AuthUsernameChanged(cvar_t* var, char* value, qbool* cancel)\n{\n\tchar path[MAX_PATH];\n\tchar filename[MAX_PATH];\n\tbyte* auth_token;\n\tint auth_token_length;\n\tint len = strlen(value);\n\tint i;\n\n\t// Don't validate if no changes\n\tif (!strcmp(var->string, value)) {\n\t\treturn;\n\t}\n\n\t// Validate new name\n\tfor (i = 0; i < len; ++i) {\n\t\tif (isalpha(value[i]) || isdigit(value[i]))\n\t\t\tcontinue;\n\t\tif (value[i] == '_' || value[i] == '[' || value[i] == ']' || value[i] == '(' || value[i] == ')' || value[i] == '.' || value[i] == '-')\n\t\t\tcontinue;\n\t\t// TODO: others\n\n\t\t// Illegal\n\t\tCom_Printf(\"Illegal character %c in username\\n\", value[i]);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tif (!value[0]) {\n\t\t// Logging out...\n\t\tif (cls.state == ca_active && cl.players[cl.playernum].loginname[0]) {\n\t\t\tCbuf_AddTextEx(&cbuf_main, \"cmd logout\\n\");\n\t\t}\n\t\tmemset(cls.auth_logintoken, 0, sizeof(cls.auth_logintoken));\n\t\treturn;\n\t}\n\t\n\t// FIXME: server-side, treat new login request as logout?\n\tif (cls.state == ca_active && cl.players[cl.playernum].loginname[0]) {\n\t\tCom_Printf(\"You are logged in to the server & must logout first\\n\");\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\t// Try and load login token\n\tstrlcpy(filename, value, sizeof(filename));\n\tCOM_ForceExtensionEx(filename, \".apikey\", sizeof(filename));\n\n\tCfg_GetConfigPath(path, sizeof(path), filename);\n\n\tauth_token = FS_LoadTempFile(path, &auth_token_length);\n\tif (auth_token == 0) {\n\t\tCom_Printf(\"Unable to load authentication token for '%s'\\n\", value);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\telse {\n\t\tstrlcpy(cls.auth_logintoken, (const char*)auth_token, sizeof(cls.auth_logintoken));\n\t\tmemset(cl.auth_challenge, 0, sizeof(cl.auth_challenge));\n\t\tif (cls.state == ca_active) {\n\t\t\tCbuf_AddTextEx(&cbuf_main, va(\"cmd login %s\\n\", value));\n\t\t}\n\t}\n}\n\nstatic void CL_Authenticate_f(void)\n{\n\tif (!cls.auth_logintoken[0] || !cl_username.string[0]) {\n\t\tCom_Printf(\"You must load a login token by setting cl_username first.\\n\");\n\t\tCom_Printf(\"Login tokens must be located in your configuration directory\\n\");\n\t\treturn;\n\t}\n\n\tif (cls.state != ca_active) {\n\t\tCom_Printf(\"Cannot authenticate, not connected\\n\");\n\t\treturn;\n\t}\n\n\tif (cl.players[cl.playernum].loginname[0]) {\n\t\tCom_Printf(\"Logging out...\\n\");\n\t\tCbuf_AddTextEx(&cbuf_main, \"cmd logout\\n\");\n\t\treturn;\n\t}\n\n\tCom_Printf(\"Starting authentication process...\\n\");\n\tCbuf_AddTextEx(&cbuf_main, va(\"cmd login \\\"%s\\\"\\n\", cl_username.string));\n}\n"
  },
  {
    "path": "src/cl_multiview.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// Multiview code\n#include \"quakedef.h\"\n#include \"utils.h\"          // Reg expressions\n#include \"draw.h\"           // RGBA macros\n#include \"teamplay.h\"       // FPD_NO_FORCE_COLOR\n\n// meag: for switching back to 3d resolution to draw the multiview outlines\n#include \"r_matrix.h\"\n#include \"mvd_utils.h\"\n\nextern int glx, gly, glwidth, glheight;\n\n#define\tMV_VIEW1 0\n#define\tMV_VIEW2 1\n#define\tMV_VIEW3 2\n#define\tMV_VIEW4 3\n\nstatic int      CURRVIEW;\t\t\t\t\t// The current view being drawn in multiview mode.\nstatic qbool    bExitmultiview;\t\t\t\t// Used when saving effect values on each frame.\nstatic int      nNumViews;\t\t\t\t\t// The number of views in multiview mode.\nstatic int      nPlayernum;\nstatic int      mv_trackslots[MV_VIEWS];    // The different track slots for each view.\nstatic char     currteam[MAX_INFO_STRING];\t// The name of the current team being tracked in multiview mode.\nstatic int      nSwapPov;\t\t\t\t\t// Change in POV positive for next, negative for previous.\nstatic int      nTrack1duel;\t\t\t\t// When cl_multiview = 2 and mvinset is on this is the tracking slot for the main view.\nstatic int      nTrack2duel;\t\t\t\t// When cl_multiview = 2 and mvinset is on this is the tracking slot for the mvinset view.\n\n// temporary storage (essentially caching OpenGL viewport state, can get rid of this in 3.5)\nstatic int inset_x;\nstatic int inset_y;\nstatic int inset_width;\nstatic int inset_height;\n\n// Temporary storage so we keep same settings during demo rewind\nstatic int rewind_trackslots[4];\nstatic int rewind_duel_track1 = 0;\nstatic int rewind_duel_track2 = 0;\n\nstatic int next_spec_track = -1;\nextern cvar_t gl_polyblend;\nextern cvar_t gl_clear;\n\nextern cvar_t scr_autoid_healthbar_bg_color;\nextern cvar_t scr_autoid_healthbar_normal_color;\nextern cvar_t scr_autoid_healthbar_mega_color;\nextern cvar_t scr_autoid_healthbar_two_mega_color;\nextern cvar_t scr_autoid_healthbar_unnatural_color;\n\nextern cvar_t scr_autoid_armorbar_green_armor;\nextern cvar_t scr_autoid_armorbar_yellow_armor;\nextern cvar_t scr_autoid_armorbar_red_armor;\n\nextern cvar_t cl_multiview_inset_offset_x, cl_multiview_inset_offset_y;\nextern cvar_t cl_multiview_inset_size_x, cl_multiview_inset_size_y;\nextern cvar_t cl_multiview_inset_top, cl_multiview_inset_right;\n\n#define ALPHA_COLOR(x, y) RGBA_TO_COLOR((x)[0],(x)[1],(x)[2],(y))\n\ntypedef struct mv_viewrect_s\n{\n\tint x;\n\tint y;\n\tint width;\n\tint height;\n} mv_viewrect_t;\n\n#define MV_HUD_POS_BOTTOM_CENTER\t1\n#define MV_HUD_POS_BOTTOM_LEFT\t\t2\n#define MV_HUD_POS_BOTTOM_RIGHT\t\t3\n#define MV_HUD_POS_TOP_CENTER\t\t4\n#define MV_HUD_POS_TOP_LEFT\t\t\t5\n#define MV_HUD_POS_TOP_RIGHT\t\t6\n#define MV_HUD_POS_GATHERED_CENTER\t7\n\n#define MV_HUDPOS(regexp, input, position, cvar) if(Utils_RegExpMatch(regexp, input)) { mv_hudpos = position; found = true; }\n\n// true if the player in given slot is an existing player\n#define VALID_PLAYER(i) (cl.players[i].name[0] && !cl.players[i].spectator)\n\nint mv_hudpos = MV_HUD_POS_BOTTOM_CENTER;\n\nmpic_t* SCR_GetWeaponIconByFlag (int flag);\nvoid R_TranslatePlayerSkin (int playernum);\nvoid CL_Track (int trackview);\nstatic void CL_MultiviewOverrideValues (void);\n\nstatic int CL_IncrLoop(int cview, int max)\n{\n\treturn (cview >= max) ? 1 : ++cview;\n}\n\nint CL_NextPlayer(int plr)\n{\n\tdo\n\t{\n\t\tplr++;\n\t} while ((plr < MAX_CLIENTS) && (cl.players[plr].spectator || !cl.players[plr].name[0]));\n\n\tif (plr >= MAX_CLIENTS)\n\t{\n\t\tplr = -1;\n\n\t\tdo\n\t\t{\n\t\t\tplr++;\n\t\t} while ((plr < MAX_CLIENTS) && (cl.players[plr].spectator || !cl.players[plr].name[0]));\n\t}\n\n\tif (plr >= MAX_CLIENTS)\n\t{\n\t\tplr = 0;\n\t}\n\n\treturn plr;\n}\n\nint CL_PrevPlayer(int plr)\n{\n\tdo\n\t{\n\t\tplr--;\n\t} while ((plr >= 0) && (cl.players[plr].spectator || !cl.players[plr].name[0]));\n\n\tif (plr < 0)\n\t{\n\t\tplr = MAX_CLIENTS;\n\n\t\tdo\n\t\t{\n\t\t\tplr--;\n\t\t} while ((plr >= 0) && (cl.players[plr].spectator || !cl.players[plr].name[0]));\n\t}\n\n\tif (plr < 0)\n\t{\n\t\tplr = 0;\n\t}\n\n\treturn plr;\n}\n\nvoid SCR_OnChangeMVHudPos (cvar_t *var, char *newval, qbool *cancel)\n{\n\tqbool found = false;\n\n\tMV_HUDPOS (\"(top\\\\s*left|tl)\", newval, MV_HUD_POS_TOP_LEFT, var);\n\tMV_HUDPOS (\"(top\\\\s*right|tr)\", newval, MV_HUD_POS_TOP_RIGHT, var);\n\tMV_HUDPOS (\"(top\\\\s*center|tc)\", newval, MV_HUD_POS_TOP_CENTER, var);\n\tMV_HUDPOS (\"(bottom\\\\s*left|bl)\", newval, MV_HUD_POS_BOTTOM_LEFT, var);\n\tMV_HUDPOS (\"(bottom\\\\s*right|br)\", newval, MV_HUD_POS_BOTTOM_RIGHT, var);\n\tMV_HUDPOS (\"(bottom\\\\s*center|bc)\", newval, MV_HUD_POS_BOTTOM_CENTER, var);\n\tMV_HUDPOS (\"(gather|g)\", newval, MV_HUD_POS_GATHERED_CENTER, var);\n\n\tif (found) {\n\t\tCvar_Set (var, newval);\n\t}\n\n\t*cancel = found;\n}\n\n// SCR_SetMVStatusPosition calls SCR_SetMVStatusGatheredPosition and vice versa.\nvoid SCR_SetMVStatusGatheredPosition (mv_viewrect_t *view, int hud_width, int hud_height, int *x, int *y);\n\nvoid SCR_SetMVStatusPosition (int position, mv_viewrect_t *view, int hud_width, int hud_height, int *x, int *y)\n{\n\tswitch (position) {\n\tcase MV_HUD_POS_BOTTOM_LEFT:\n\t{\n\t\t(*x) = 0;\n\t\t(*y) = max (0, (view->height - hud_height));\n\t\tbreak;\n\t}\n\tcase MV_HUD_POS_BOTTOM_RIGHT:\n\t{\n\t\t(*x) = max (0, (view->width - hud_width));\n\t\t(*y) = max (0, (view->height - hud_height));\n\t\tbreak;\n\t}\n\tcase MV_HUD_POS_TOP_CENTER:\n\t{\n\t\t(*x) = max (0, (view->width - hud_width) / 2);\n\t\t(*y) = 0;\n\t\tbreak;\n\t}\n\tcase MV_HUD_POS_TOP_LEFT:\n\t{\n\t\t(*x) = 0;\n\t\t(*y) = 0;\n\t\tbreak;\n\t}\n\tcase MV_HUD_POS_TOP_RIGHT:\n\t{\n\t\t(*x) = max (0, view->width - hud_width);\n\t\t(*y) = 0;\n\t\tbreak;\n\t}\n\tcase MV_HUD_POS_GATHERED_CENTER:\n\t{\n\t\tSCR_SetMVStatusGatheredPosition (view, hud_width, hud_height, &(*x), &(*y));\n\t\tbreak;\n\t}\n\tcase MV_HUD_POS_BOTTOM_CENTER:\n\tdefault:\n\t{\n\t\t(*x) = max (0, (view->width - hud_width) / 2);\n\t\t(*y) = max (0, (view->height - hud_height));\n\t\tbreak;\n\t}\n\t}\n}\n\nvoid SCR_SetMVStatusGatheredPosition (mv_viewrect_t *view, int hud_width, int hud_height, int *x, int *y)\n{\n\t//\n\t// Position the huds towards the center of the screen for the different views\n\t// so that all the HUD's are close to each other on the screen.\n\t//\n\n\tif (cl_multiview.value == 2) {\n\t\tif (!cl_mvinset.value) {\n\t\t\tif (CURRVIEW == 2) {\n\t\t\t\t// Top view. (Put the hud at the bottom)\n\t\t\t\tSCR_SetMVStatusPosition (MV_HUD_POS_BOTTOM_CENTER, view, hud_width, hud_height, x, y);\n\t\t\t}\n\t\t\telse if (CURRVIEW == 1) {\n\t\t\t\t// Bottom view. (Put the hud at the top)\n\t\t\t\tSCR_SetMVStatusPosition (MV_HUD_POS_TOP_CENTER, view, hud_width, hud_height, x, y);\n\t\t\t}\n\t\t}\n\t}\n\telse if (cl_multiview.value == 3) {\n\t\tif (CURRVIEW == 2) {\n\t\t\t// Top view. (Put the hud at the bottom)\n\t\t\tSCR_SetMVStatusPosition (MV_HUD_POS_BOTTOM_CENTER, view, hud_width, hud_height, x, y);\n\t\t}\n\t\telse if (CURRVIEW == 3) {\n\t\t\t// Bottom left view. (Put the hud at the top right)\n\t\t\tSCR_SetMVStatusPosition (MV_HUD_POS_TOP_RIGHT, view, hud_width, hud_height, x, y);\n\t\t}\n\t\telse if (CURRVIEW == 1) {\n\t\t\t// Bottom right view. (Put the hud at the top left)\n\t\t\tSCR_SetMVStatusPosition (MV_HUD_POS_TOP_LEFT, view, hud_width, hud_height, x, y);\n\t\t}\n\t}\n\telse if (cl_multiview.value == 4) {\n\t\tif (CURRVIEW == 2) {\n\t\t\t// Top left view. (Put the hud at the bottom right)\n\t\t\tSCR_SetMVStatusPosition (MV_HUD_POS_BOTTOM_RIGHT, view, hud_width, hud_height, x, y);\n\t\t}\n\t\telse if (CURRVIEW == 3) {\n\t\t\t// Top right view. (Put the hud at the bottom left)\n\t\t\tSCR_SetMVStatusPosition (MV_HUD_POS_BOTTOM_LEFT, view, hud_width, hud_height, x, y);\n\t\t}\n\t\telse if (CURRVIEW == 4) {\n\t\t\t// Bottom left view. (Put the hud at the top right)\n\t\t\tSCR_SetMVStatusPosition (MV_HUD_POS_TOP_RIGHT, view, hud_width, hud_height, x, y);\n\t\t}\n\t\telse if (CURRVIEW == 1) {\n\t\t\t// Bottom right view. (Put the hud at the top left)\n\t\t\tSCR_SetMVStatusPosition (MV_HUD_POS_TOP_LEFT, view, hud_width, hud_height, x, y);\n\t\t}\n\t}\n}\n\n#define MV_HUD_ARMOR_WIDTH\t\t(5*8)\n#define MV_HUD_HEALTH_WIDTH\t\t(5*8)\n#define MV_HUD_CURRAMMO_WIDTH\t(5*8)\n#define MV_HUD_CURRWEAP_WIDTH\t(3*8)\n#define MV_HUD_POWERUPS_WIDTH\t(2*8)\n#define MV_HUD_POWERUPS_HEIGHT\t(2*8)\n\n#define MV_HUD_STYLE_ONLY_NAME\t2\n#define MV_HUD_STYLE_ALL\t\t3\n#define MV_HUD_STYLE_ALL_TEXT\t4\n#define MV_HUD_STYLE_ALL_FILL\t5\n\nvoid SCR_MV_SetBoundValue (int *var, int val)\n{\n\tif (var != NULL) {\n\t\t(*var) = val;\n\t}\n}\n\nvoid SCR_MV_DrawName (int x, int y, int *width, int *height)\n{\n\tchar *name = cl.players[nPlayernum].name;\n\tSCR_MV_SetBoundValue (width, 8 * (strlen (name) + 1));\n\tSCR_MV_SetBoundValue (height, 8);\n\n\tDraw_String (x, y, name);\n}\n\nvoid SCR_MV_DrawArmor (int x, int y, int *width, int *height, int style)\n{\n\tchar armor_color_code[6] = \"\";\n\tint armor_amount_width = 0;\n\n\tbyte armor_color[4] = { 0, 0, 0, 75 };\t// RGBA.\n\n\t\t\t\t\t\t\t\t\t\t\t//\n\t\t\t\t\t\t\t\t\t\t\t// Set the armor text color based on what armor the player has.\n\t\t\t\t\t\t\t\t\t\t\t//\n\tif (cl.stats[STAT_ITEMS] & IT_ARMOR1) {\n\t\t// Green armor.\n\t\tstrlcpy (armor_color_code, \"&c0f0\", sizeof (armor_color_code));\n\t\tarmor_amount_width = Q_rint (MV_HUD_ARMOR_WIDTH * cl.stats[STAT_ARMOR] / 100.0);\n\n\t\tarmor_color[0] = 80;\n\t\tarmor_color[1] = 190;\n\t\tarmor_color[2] = 80;\n\t}\n\telse if (cl.stats[STAT_ITEMS] & IT_ARMOR2) {\n\t\t// Yellow armor.\n\t\tstrlcpy (armor_color_code, \"&cff0\", sizeof (armor_color_code));\n\t\tarmor_amount_width = Q_rint (MV_HUD_ARMOR_WIDTH * cl.stats[STAT_ARMOR] / 150.0);\n\n\t\tarmor_color[0] = 250;\n\t\tarmor_color[1] = 230;\n\t\tarmor_color[2] = 0;\n\t}\n\telse if (cl.stats[STAT_ITEMS] & IT_ARMOR3) {\n\t\t// Red armor.\n\t\tstrlcpy (armor_color_code, \"&cf00\", sizeof (armor_color_code));\n\t\tarmor_amount_width = Q_rint (MV_HUD_ARMOR_WIDTH * cl.stats[STAT_ARMOR] / 200.0);\n\n\t\tarmor_color[0] = 190;\n\t\tarmor_color[1] = 50;\n\t\tarmor_color[2] = 0;\n\t}\n\n\tif (cl.stats[STAT_ARMOR] > 0) {\n\t\t//\n\t\t// Background fill for armor.\n\t\t//\n\t\tDraw_AlphaFillRGB (x, y, armor_amount_width, 8, RGBA_TO_COLOR (armor_color[0], armor_color[1], armor_color[2], armor_color[3]));\n\n\t\t// Armor value.\n\t\tif (style >= MV_HUD_STYLE_ALL_TEXT) {\n\t\t\tDraw_ColoredString (x, y, va (\"%s%4d\", armor_color_code, cl.stats[STAT_ARMOR]), 0, false);\n\t\t}\n\t}\n\n\tSCR_MV_SetBoundValue (width, MV_HUD_ARMOR_WIDTH);\n\tSCR_MV_SetBoundValue (height, 8);\n}\n\nvoid SCR_MV_DrawHealth (int x, int y, int *width, int *height, int style)\n{\n#define MV_HEALTH_OPACITY 75\n\tint health = cl.stats[STAT_HEALTH];\n\n\tint health_amount_width = 0;\n\n\thealth = min (100, health);\n\thealth_amount_width = Q_rint (fabs((MV_HUD_HEALTH_WIDTH * health) / 100.0f));\n\n\tif (health > 0) {\n\t\tDraw_AlphaFillRGB (x, y, health_amount_width, 8, ALPHA_COLOR (scr_autoid_healthbar_normal_color.color, 2 * MV_HEALTH_OPACITY));\n\t}\n\n\thealth = cl.stats[STAT_HEALTH];\n\n\tif (health > 100 && health <= 200) {\n\t\t// Mega health.\n\t\thealth_amount_width = Q_rint ((MV_HUD_HEALTH_WIDTH / 100.0f) * (health - 100));\n\n\t\tDraw_AlphaFillRGB (x, y, health_amount_width, 8, ALPHA_COLOR (scr_autoid_healthbar_mega_color.color, MV_HEALTH_OPACITY));\n\t}\n\telse if (health > 200 && health <= 250) {\n\t\t// Super health.\n\t\thealth_amount_width = Q_rint ((MV_HUD_HEALTH_WIDTH / 100.0f) * (health - 200));\n\n\t\tDraw_AlphaFillRGB (x, y, MV_HUD_HEALTH_WIDTH, 8, ALPHA_COLOR (scr_autoid_healthbar_mega_color.color, MV_HEALTH_OPACITY));\n\t\tDraw_AlphaFillRGB (x, y, health_amount_width, 8, ALPHA_COLOR (scr_autoid_healthbar_two_mega_color.color, MV_HEALTH_OPACITY));\n\t}\n\telse if (health > 250) {\n\t\t// Crazy health.\n\t\tDraw_AlphaFillRGB (x, y, MV_HUD_HEALTH_WIDTH, 8, ALPHA_COLOR (scr_autoid_healthbar_unnatural_color.color, MV_HEALTH_OPACITY));\n\n\t}\n\n\t// No powerup.\n\tif (style >= MV_HUD_STYLE_ALL_TEXT && !(cl.stats[STAT_ITEMS] & IT_INVULNERABILITY)) {\n\t\tDraw_String (x, y, va (\"%4d\", health));\n\t}\n\n\tSCR_MV_SetBoundValue (width, MV_HUD_HEALTH_WIDTH);\n\tSCR_MV_SetBoundValue (height, 8);\n}\n\nvoid SCR_MV_DrawPowerups (int x, int y)\n{\n\textern mpic_t  *sb_face_invis;\n\textern mpic_t  *sb_face_quad;\n\textern mpic_t  *sb_face_invuln;\n\textern mpic_t  *sb_face_invis_invuln;\n\n\tif (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY\n\t\t&& cl.stats[STAT_ITEMS] & IT_INVISIBILITY) {\n\t\t// Pentagram + Ring.\n\t\tDraw_AlphaPic (x + (MV_HUD_HEALTH_WIDTH - sb_face_invis_invuln->width) / 2,\n\t\t\ty - sb_face_invis_invuln->height / 2,\n\t\t\tsb_face_invis_invuln, 0.4);\n\t}\n\telse if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) {\n\t\t// Pentagram.\n\t\tDraw_AlphaPic (x + (MV_HUD_HEALTH_WIDTH - sb_face_invuln->width) / 2,\n\t\t\ty - sb_face_invuln->height / 2,\n\t\t\tsb_face_invuln, 0.4);\n\t}\n\telse if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) {\n\t\t// Ring.\n\t\tDraw_AlphaPic (x + (MV_HUD_HEALTH_WIDTH - sb_face_invis->width) / 2,\n\t\t\ty - sb_face_invis->height / 2,\n\t\t\tsb_face_invis, 0.4);\n\t}\n\n\tif (cl.stats[STAT_ITEMS] & IT_QUAD) {\n\t\t// Ring.\n\t\tDraw_AlphaPic (x + (MV_HUD_HEALTH_WIDTH - sb_face_quad->width) / 2,\n\t\t\ty - sb_face_quad->height / 2,\n\t\t\tsb_face_quad, 0.4);\n\t}\n}\n\nstatic mpic_t *SCR_GetActiveWeaponIcon (void)\n{\n\treturn SCR_GetWeaponIconByFlag (cl.stats[STAT_ACTIVEWEAPON]);\n}\n\nstatic void SCR_MV_DrawCurrentWeapon (int x, int y, int *width, int *height)\n{\n\tmpic_t *current_weapon = NULL;\n\tcurrent_weapon = SCR_GetActiveWeaponIcon ();\n\n\tif (current_weapon) {\n\t\tDraw_Pic (x,\n\t\t\ty - (current_weapon->height / 4),\n\t\t\tcurrent_weapon);\n\t}\n\n\tSCR_MV_SetBoundValue (width, MV_HUD_CURRWEAP_WIDTH);\n\tSCR_MV_SetBoundValue (height, 8);\n}\n\nvoid SCR_MV_DrawCurrentAmmo (int x, int y, int *width, int *height)\n{\n\t// Draw the ammo count in blue/greyish color.\n\tDraw_ColoredString (x, y, va (\"&c5CE%4d\", cl.stats[STAT_AMMO]), 0, false);\n\tSCR_MV_SetBoundValue (width, MV_HUD_CURRAMMO_WIDTH);\n\tSCR_MV_SetBoundValue (height, 8);\n}\n\nmpic_t *SCR_GetWeaponIconByWeaponNumber (int num)\n{\n\textern mpic_t *sb_weapons[7][8];  // sbar.c Weapon pictures.\n\n\tnum -= 2;\n\n\tif (num >= 0 && num < 8) {\n\t\treturn sb_weapons[0][num];\n\t}\n\n\treturn NULL;\n}\n\nvoid SCR_MV_DrawWeapons (int x, int y, int *width, int *height, int hud_width, int hud_height, qbool vertical)\n{\n#define WEAPON_COUNT 8\n\tmpic_t *weapon_pic = NULL;\n\tint weapon = 0;\n\tint weapon_flag = 0;\n\tint weapon_x = 0;\n\tint weapon_y = 0;\n\n\t// Draw the weapons the user has.\n\tfor (weapon = 0; weapon < WEAPON_COUNT; weapon++) {\n\t\tweapon_flag = IT_SHOTGUN << weapon;\n\n\t\tif (cl.stats[STAT_ITEMS] & weapon_flag) {\n\t\t\t// Get the weapon picture and draw it.\n\t\t\tweapon_pic = SCR_GetWeaponIconByWeaponNumber (weapon + 2);\n\t\t\tDraw_Pic (x + weapon_x, y + weapon_y, weapon_pic);\n\t\t}\n\n\t\t// Evenly distribute the weapons.\n\t\tif (!vertical) {\n\t\t\tweapon_x += Q_rint ((float)hud_width / WEAPON_COUNT);\n\t\t}\n\t\telse {\n\t\t\tweapon_y += Q_rint ((float)hud_height / WEAPON_COUNT);\n\t\t}\n\t}\n}\n\nvoid SCR_DrawMVStatusView (mv_viewrect_t *view, int style, int position, qbool flip, qbool vertical)\n{\n\tint hud_x = 0;\n\tint hud_y = 0;\n\tint hud_width = 0;\n\tint hud_height = 0;\n\n\tchar *name = cl.players[nPlayernum].name;\n\n\tif (style == MV_HUD_STYLE_ONLY_NAME) {\n\t\t// Only draw the players name.\n\n\t\thud_height = 2 * 8;\n\t\thud_width = 8 * (strlen (name) + 1);\n\n\t\t//\n\t\t// Get the position we should draw the hud at.\n\t\t//\n\t\tSCR_SetMVStatusPosition (position, view, hud_width, hud_height, &hud_x, &hud_y);\n\n\t\tDraw_String (view->x + hud_x, view->y + hud_y, name);\n\t}\n\telse if (style >= MV_HUD_STYLE_ALL) {\n#define MV_HUD_VERTICAL_GAP\t4\n\n\t\tint name_width = 0;\n\t\tint name_height = 0;\n\t\tint armor_width = 0;\n\t\tint armor_height = 0;\n\t\tint health_width = 0;\n\t\tint health_height = 0;\n\t\tint currweap_width = 0;\n\t\tint currweap_height = 0;\n\t\tint currammo_width = 0;\n\t\tint currammo_height = 0;\n\n\t\t//\n\t\t// Calculate the total width and height of the hud.\n\t\t//\n\t\tif (!vertical) {\n\t\t\thud_height = 3 * 8;\n\t\t\thud_width =\n\t\t\t\t8 * (strlen (name)) +\t\t\t\t// Name.\n\t\t\t\tMV_HUD_ARMOR_WIDTH + \t\t\t\t// Armor + space.\n\t\t\t\tMV_HUD_HEALTH_WIDTH + \t\t\t\t// Health.\n\t\t\t\tMV_HUD_CURRWEAP_WIDTH +\t\t\t\t// Current weapon.\n\t\t\t\tMV_HUD_CURRAMMO_WIDTH;\t\t\t\t// Current weapon ammo count.\n\n\t\t}\n\t\telse {\n\t\t\t// Vertical.\n\t\t\thud_height = 5 * (8 + MV_HUD_VERTICAL_GAP);\n\t\t\thud_width = max (8 * strlen (name), MV_HUD_ARMOR_WIDTH) + MV_HUD_ARMOR_WIDTH;\n\t\t}\n\n\t\t//\n\t\t// Get the position we should draw the hud at.\n\t\t//\n\t\tSCR_SetMVStatusPosition (position, view, hud_width, hud_height, &hud_x, &hud_y);\n\n\t\t//\n\t\t// Draw a fill background.\n\t\t//\n\t\tif (style >= MV_HUD_STYLE_ALL_FILL) {\n\t\t\tDraw_AlphaFill (view->x + hud_x, view->y + hud_y, hud_width, hud_height, 0, 0.5);\n\t\t}\n\n\t\t// Draw powerups in the middle background of the hud.\n\t\tSCR_MV_DrawPowerups (view->x + hud_x + (hud_width / 2), view->y + hud_y + (hud_height / 2));\n\n\t\t// Draw the elements vertically? (Add a small gap between the items when\n\t\t// drawing them vertically, otherwise they're too close together).\n#define MV_FLIP(W,H) if(vertical) { hud_y += (H) + MV_HUD_VERTICAL_GAP; } else { hud_x += (W); }\n\n\t\tif (!flip) {\n\t\t\t// Name.\n\t\t\tSCR_MV_DrawName (view->x + hud_x, view->y + hud_y, &name_width, &name_height);\n\t\t\tMV_FLIP (name_width, name_height);\n\n\t\t\t// Armor.\n\t\t\tSCR_MV_DrawArmor (view->x + hud_x, view->y + hud_y, &armor_width, &armor_height, style);\n\t\t\tMV_FLIP (armor_width, armor_height);\n\n\t\t\t// Health.\n\t\t\tSCR_MV_DrawHealth (view->x + hud_x, view->y + hud_y, &health_width, &health_height, style);\n\t\t\tMV_FLIP (health_width, health_height);\n\n\t\t\t// Current weapon.\n\t\t\tSCR_MV_DrawCurrentWeapon (view->x + hud_x, view->y + hud_y, &currweap_width, &currweap_height);\n\t\t\tMV_FLIP (currweap_width, currweap_height);\n\n\t\t\t// Ammo for current weapon.\n\t\t\tSCR_MV_DrawCurrentAmmo (view->x + hud_x, view->y + hud_y, &currammo_width, &currammo_height);\n\t\t\tMV_FLIP (currammo_width, currammo_height);\n\t\t}\n\t\telse {\n\t\t\t//\n\t\t\t// Flipped horizontally.\n\t\t\t//\n\n\t\t\t// Ammo for current weapon.\n\t\t\tSCR_MV_DrawCurrentAmmo (view->x + hud_x, view->y + hud_y, &currammo_width, &currammo_height);\n\t\t\tMV_FLIP (currammo_width, currammo_height);\n\n\t\t\t// Current weapon.\n\t\t\tSCR_MV_DrawCurrentWeapon (view->x + hud_x, view->y + hud_y, &currweap_width, &currweap_height);\n\t\t\tMV_FLIP (currweap_width, currweap_height);\n\n\t\t\t// Health.\n\t\t\tSCR_MV_DrawHealth (view->x + hud_x, view->y + hud_y, &health_width, &health_height, style);\n\t\t\tMV_FLIP (health_width, health_height);\n\n\t\t\t// Armor.\n\t\t\tSCR_MV_DrawArmor (view->x + hud_x, view->y + hud_y, &armor_width, &armor_height, style);\n\t\t\tMV_FLIP (armor_width, armor_height);\n\n\t\t\t// Name.\n\t\t\tSCR_MV_DrawName (view->x + hud_x, view->y + hud_y, &name_width, &name_height);\n\t\t\tMV_FLIP (name_width, name_height);\n\t\t}\n\n\t\tif (vertical) {\n\t\t\t// Start in the next column.\n\t\t\thud_x += max (8 * strlen (name), MV_HUD_ARMOR_WIDTH);\n\t\t\thud_y -= hud_height;\n\t\t}\n\t\telse {\n\t\t\t// Start on the next row.\n\t\t\thud_x -= hud_width;\n\t\t\thud_y += hud_height / 2;\n\t\t}\n\n\t\t// Weapons.\n\t\tSCR_MV_DrawWeapons (view->x + hud_x, view->y + hud_y, NULL, NULL, hud_width, hud_height, vertical);\n\t}\n}\n\nvoid SCR_SetMVStatusTwoViewRect (mv_viewrect_t *view)\n{\n\tif (CURRVIEW == 2) {\n\t\t// Top.\n\t\tview->x = 0;\n\t\tview->y = 0;\n\t\tview->width = vid.width;\n\t\tview->height = vid.height / 2;\n\t}\n\telse if (CURRVIEW == 1) {\n\t\t// Bottom.\n\t\tview->x = 0;\n\t\tview->y = vid.height / 2;\n\t\tview->width = vid.width;\n\t\tview->height = vid.height / 2;\n\t}\n}\n\nvoid SCR_SetMVStatusTwoInsetViewRect(mv_viewrect_t *view)\n{\n\tif (CURRVIEW == 2) {\n\t\t// Main.\n\t\tview->x = 0;\n\t\tview->y = 0;\n\t\tview->width = vid.width;\n\t\tview->height = vid.height;\n\t}\n\telse if (CURRVIEW == 1) {\n\t\tfloat ratio_x = (vid.width * 1.0f) / glwidth;\n\t\tfloat ratio_y = (vid.height * 1.0f) / glheight;\n\n\t\t// inset window\n\t\tview->x = inset_x * ratio_x;\n\t\tview->y = (glheight - (inset_y + inset_height)) * ratio_y; // reversed for 2d/3d rendering\n\t\tview->width = ceil(inset_width * ratio_x);\n\t\tview->height = ceil(inset_height * ratio_y);\n\t}\n}\n\nvoid SCR_SetMVStatusThreeViewRect (mv_viewrect_t *view)\n{\n\tif (CURRVIEW == 2) {\n\t\t// Top.\n\t\tview->x = 0;\n\t\tview->y = 0;\n\t\tview->width = vid.width;\n\t\tview->height = vid.height / 2;\n\t}\n\telse if (CURRVIEW == 3) {\n\t\t// Bottom left.\n\t\tview->x = 0;\n\t\tview->y = vid.height / 2;\n\t\tview->width = vid.width / 2;\n\t\tview->height = vid.height / 2;\n\t}\n\telse if (CURRVIEW == 1) {\n\t\t// Bottom right.\n\t\tview->x = vid.width / 2;\n\t\tview->y = vid.height / 2;\n\t\tview->width = vid.width / 2;\n\t\tview->height = vid.height / 2;\n\t}\n}\n\nvoid SCR_SetMVStatusFourViewRect (mv_viewrect_t *view)\n{\n\tif (CURRVIEW == 2) {\n\t\t// Top left.\n\t\tview->x = 0;\n\t\tview->y = 0;\n\t\tview->width = vid.width / 2;\n\t\tview->height = vid.height / 2;\n\t}\n\telse if (CURRVIEW == 3) {\n\t\t// Top right.\n\t\tview->x = vid.width / 2;\n\t\tview->y = 0;\n\t\tview->width = vid.width / 2;\n\t\tview->height = vid.height / 2;\n\t}\n\telse if (CURRVIEW == 4) {\n\t\t// Bottom left.\n\t\tview->x = 0;\n\t\tview->y = vid.height / 2;\n\t\tview->width = vid.width / 2;\n\t\tview->height = vid.height / 2;\n\t}\n\telse if (CURRVIEW == 1) {\n\t\t// Bottom right.\n\t\tview->x = vid.width / 2;\n\t\tview->y = vid.height / 2;\n\t\tview->width = vid.width / 2;\n\t\tview->height = vid.height / 2;\n\t}\n}\n\nvoid SCR_DrawMVStatus (void)\n{\n\tmv_viewrect_t view;\n\n\t// Only draw mini hud when there are more than 1 views.\n\tif (cl_multiview.value <= 1 || !cls.mvdplayback) {\n\t\treturn;\n\t}\n\n\t// Reset the view.\n\tmemset (&view, -1, sizeof (view));\n\n\t//\n\t// Get the view rect to draw the hud within based on the current\n\t// multiview mode, and what view that is being drawn.\n\t//\n\tif (cl_multiview.value == 2) {\n\t\tif (cl_mvinset.value) {\n\t\t\t// Only draw the mini hud for the inset, \n\t\t\t// since we probably want the full size hud \n\t\t\t// for the main view.\n\t\t\tif (CURRVIEW == 2) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tSCR_SetMVStatusTwoInsetViewRect(&view);\n\t\t}\n\t\telse {\n\t\t\tSCR_SetMVStatusTwoViewRect (&view);\n\t\t}\n\t}\n\telse if (cl_multiview.value == 3) {\n\t\tSCR_SetMVStatusThreeViewRect (&view);\n\t}\n\telse if (cl_multiview.value == 4) {\n\t\tSCR_SetMVStatusFourViewRect (&view);\n\t}\n\n\t// Only draw if we the view rect was set properly.\n\tif (view.x != -1) {\n\t\tSCR_DrawMVStatusView (&view,\n\t\t\tcl_mvdisplayhud.integer,\n\t\t\tmv_hudpos,\n\t\t\t(qbool)cl_mvhudflip.value,\n\t\t\t(qbool)cl_mvhudvertical.value);\n\t}\n}\n\nvoid SCR_DrawMVStatusStrings (void)\n{\n\tint xb = 0, yb = 0, xd = 0, yd = 0;\n\tchar strng[80];\n\tchar weapons[40];\n\tchar weapon[3];\n\tchar sAmmo[3];\n\tchar pups[4];\n\tchar armor;\n\tchar name[16];\n\n\tint i;\n\n\t// Only in MVD.\n\tif (!cl_multiview.value || !cls.mvdplayback) {\n\t\treturn;\n\t}\n\n\t//\n\t// Get the current weapon.\n\t//\n\tif (cl.stats[STAT_ACTIVEWEAPON] & IT_LIGHTNING || cl.stats[STAT_ACTIVEWEAPON] & IT_SUPER_LIGHTNING) {\n\t\tstrlcpy (weapon, \"lg\", sizeof (weapon));\n\t}\n\telse if (cl.stats[STAT_ACTIVEWEAPON] & IT_ROCKET_LAUNCHER) {\n\t\tstrlcpy (weapon, \"rl\", sizeof (weapon));\n\t}\n\telse if (cl.stats[STAT_ACTIVEWEAPON] & IT_GRENADE_LAUNCHER) {\n\t\tstrlcpy (weapon, \"gl\", sizeof (weapon));\n\t}\n\telse if (cl.stats[STAT_ACTIVEWEAPON] & IT_SUPER_NAILGUN) {\n\t\tstrlcpy (weapon, \"sn\", sizeof (weapon));\n\t}\n\telse if (cl.stats[STAT_ACTIVEWEAPON] & IT_NAILGUN) {\n\t\tstrlcpy (weapon, \"ng\", sizeof (weapon));\n\t}\n\telse if (cl.stats[STAT_ACTIVEWEAPON] & IT_SUPER_SHOTGUN) {\n\t\tstrlcpy (weapon, \"ss\", sizeof (weapon));\n\t}\n\telse if (cl.stats[STAT_ACTIVEWEAPON] & IT_SHOTGUN) {\n\t\tstrlcpy (weapon, \"sg\", sizeof (weapon));\n\t}\n\telse if (cl.stats[STAT_ACTIVEWEAPON] & IT_AXE) {\n\t\tstrlcpy (weapon, \"ax\", sizeof (weapon));\n\t}\n\telse {\n\t\tstrlcpy (weapon, \"??\", sizeof (weapon));\n\t}\n\tweapon[0] |= 128;\n\tweapon[1] |= 128;\n\n\t//\n\t// Get current powerups.\n\t//\n\tpups[0] = pups[1] = pups[2] = ' ';\n\tpups[3] = '\\0';\n\n\tif (cl.stats[STAT_ITEMS] & IT_QUAD) {\n\t\tpups[0] = 'Q';\n\t\tpups[0] |= 128;\n\t}\n\n\tif (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) {\n\t\tpups[1] = 'R';\n\t\tpups[1] |= 128;\n\t}\n\n\tif (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) {\n\t\tpups[2] = 'P';\n\t\tpups[2] |= 128;\n\t}\n\n\tstrng[0] = '\\0';\n\n\tfor (i = 0; i < 8; i++) {\n\t\tweapons[i] = ' ';\n\t\tweapons[8] = '\\0';\n\t}\n\n\tif (cl.stats[STAT_ITEMS] & IT_AXE) {\n\t\tweapons[0] = '1' - '0' + 0x12;\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_SHOTGUN) {\n\t\tweapons[1] = '2' - '0' + 0x12;\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_SUPER_SHOTGUN) {\n\t\tweapons[2] = '3' - '0' + 0x12;\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_NAILGUN) {\n\t\tweapons[3] = '4' - '0' + 0x12;\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_SUPER_NAILGUN) {\n\t\tweapons[4] = '5' - '0' + 0x12;\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_GRENADE_LAUNCHER) {\n\t\tweapons[5] = '6' - '0' + 0x12;\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER) {\n\t\tweapons[6] = '7' - '0' + 0x12;\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_SUPER_LIGHTNING || cl.stats[STAT_ITEMS] & IT_LIGHTNING) {\n\t\tweapons[7] = '8' - '0' + 0x12;\n\t}\n\n\tarmor = ' ';\n\tif (cl.stats[STAT_ITEMS] & IT_ARMOR1) {\n\t\tarmor = 'g';\n\t\tarmor |= 128;\n\t}\n\telse if (cl.stats[STAT_ITEMS] & IT_ARMOR2) {\n\t\tarmor = 'y';\n\t\tarmor |= 128;\n\t}\n\telse if (cl.stats[STAT_ITEMS] & IT_ARMOR3) {\n\t\tarmor = 'r';\n\t\tarmor |= 128;\n\t}\n\n\t//\n\t// Get the player's name.\n\t//\n\tstrlcpy (name, cl.players[nPlayernum].name, sizeof (name));\n\n\tif (strcmp (cl.players[nPlayernum].name, \"\") && !cl.players[nPlayernum].spectator) {\n\t\tif ((cl.players[nPlayernum].stats[STAT_HEALTH] <= 0) && (cl_multiview.value == 2) && cl_mvinset.integer) {\n\t\t\t// mvinset and dead\n\t\t\tsnprintf (sAmmo, sizeof (sAmmo), \"%02d\", cl.players[nPlayernum].stats[STAT_AMMO]);\n\t\t\tsnprintf (strng, sizeof (strng), \"%.5s   %s %s:%-3s\", name,\n\t\t\t\t\"dead   \",\n\t\t\t\tweapon,\n\t\t\t\tsAmmo);\n\t\t}\n\t\telse if ((cl.players[nPlayernum].stats[STAT_HEALTH] <= 0) && (vid.width <= 400)) {\n\t\t\t// Resolution width <= 400 and dead\n\t\t\tsnprintf (sAmmo, sizeof (sAmmo), \"%02d\", cl.players[nPlayernum].stats[STAT_AMMO]);\n\t\t\tsnprintf (strng, sizeof (strng), \"%.4s  %s %s:%-3s\", name,\n\t\t\t\t\"dead   \",\n\t\t\t\tweapon,\n\t\t\t\tsAmmo);\n\t\t}\n\t\telse if (cl.players[nPlayernum].stats[STAT_HEALTH] <= 0) {\n\t\t\t// > 512 and dead\n\t\t\tsnprintf (sAmmo, sizeof (sAmmo), \"%02d\", cl.players[nPlayernum].stats[STAT_AMMO]);\n\t\t\tsnprintf (strng, sizeof (strng), \"%s   %s %s:%-3s\", name,\n\t\t\t\t\"dead   \",\n\t\t\t\tweapon,\n\t\t\t\tsAmmo);\n\t\t}\n\n\t\telse if ((cl_multiview.integer == 2) && cl_mvinset.integer && (CURRVIEW == 1)) {\n\t\t\t// mvinset\n\t\t\tsnprintf (sAmmo, sizeof (sAmmo), \"%02d\", cl.players[nPlayernum].stats[STAT_AMMO]);\n\t\t\tsnprintf (strng, sizeof (strng), \"%s %.5s  %c%03d %03d %s:%-3s\", pups,\n\t\t\t\tname,\n\t\t\t\tarmor,\n\t\t\t\tcl.players[nPlayernum].stats[STAT_ARMOR],\n\t\t\t\tcl.players[nPlayernum].stats[STAT_HEALTH],\n\t\t\t\tweapon,\n\t\t\t\tsAmmo);\n\t\t}\n\t\telse if (cl_multiview.value && vid.width <= 400) {\n\t\t\t// <= 400 and alive\n\t\t\tsnprintf (sAmmo, sizeof (sAmmo), \"%02d\", cl.players[nPlayernum].stats[STAT_AMMO]);\n\t\t\tsnprintf (strng, sizeof (strng), \"%s %.4s %c%03d %03d %s:%-3s\", pups,\n\t\t\t\tname,\n\t\t\t\tarmor,\n\t\t\t\tcl.players[nPlayernum].stats[STAT_ARMOR],\n\t\t\t\tcl.players[nPlayernum].stats[STAT_HEALTH],\n\t\t\t\tweapon,\n\t\t\t\tsAmmo);\n\t\t}\n\t\telse {\n\t\t\tsnprintf (sAmmo, sizeof (sAmmo), \"%02d\", cl.players[nPlayernum].stats[STAT_AMMO]); // > 512 and alive\n\t\t\tsnprintf (strng, sizeof (strng), \"%s %s  %c%03d %03d %s:%-3s\", pups,\n\t\t\t\tname,\n\t\t\t\tarmor,\n\t\t\t\tcl.players[nPlayernum].stats[STAT_ARMOR],\n\t\t\t\tcl.players[nPlayernum].stats[STAT_HEALTH],\n\t\t\t\tweapon,\n\t\t\t\tsAmmo);\n\t\t}\n\t}\n\n\t//\n\t// Powerup cam stuff: (don't display links if we're using fixed camera position)\n\t//\n\tif (MVD_PowerupCam_Enabled()) {\n\t\tsAmmo[0] = strng[0] = weapons[0] = '\\0';\n\t}\n\n\t//\n\t// Placement.\n\t//\n\tif (cl_multiview.value == 1) {\n\t\txb = vid.width - strlen (strng) * 8 - 12;\n\t\tyb = vid.height - sb_lines - 16;\n\t\txd = vid.width - strlen (weapons) * 8 - 84;\n\t\tyd = vid.height - sb_lines - 8;\n\t}\n\telse if (cl_multiview.value == 2) {\n\t\tif (!cl_mvinset.value) {\n\t\t\tif (CURRVIEW == 2) {\n\t\t\t\t// Top\n\t\t\t\txb = vid.width - strlen (strng) * 8 - 12;\n\t\t\t\tyb = vid.height / 2 - sb_lines - 16;\n\t\t\t\txd = vid.width - strlen (weapons) * 8 - 84;\n\t\t\t\tyd = vid.height / 2 - sb_lines - 8;\n\t\t\t}\n\t\t\telse if (CURRVIEW == 1) {\n\t\t\t\t// Bottom\n\t\t\t\txb = vid.width - strlen (strng) * 8 - 12;\n\t\t\t\tyb = vid.height - sb_lines - 16;\n\t\t\t\txd = vid.width - strlen (weapons) * 8 - 84;\n\t\t\t\tyd = vid.height - sb_lines - 8;\n\t\t\t}\n\t\t}\n\t\telse if (cl_mvinset.value) {\n\t\t\tif (CURRVIEW == 2) {\n\t\t\t\t// Main\n\t\t\t\txb = vid.width - strlen (strng) * 8 - 12;\n\t\t\t\tyb = vid.height / 2 - sb_lines - 16;\n\t\t\t\txd = vid.width - strlen (weapons) * 8 - 84;\n\t\t\t\tyd = vid.height / 2 - sb_lines - 8;\n\t\t\t}\n\t\t\telse if (CURRVIEW == 1) {\n\t\t\t\tmv_viewrect_t view;\n\n\t\t\t\tSCR_SetMVStatusTwoInsetViewRect(&view);\n\n\t\t\t\txb = (view.x + view.width) - strlen (strng) * 8; // hud\n\t\t\t\tyb = (view.y + view.height) - 16;\n\t\t\t\txd = (view.x + view.width) - strlen (weapons) * 8 - 70; // weapons\n\t\t\t\tyd = (view.y + view.height) - 8;\n\t\t\t}\n\t\t}\n\t}\n\telse if (cl_multiview.value == 3) {\n\t\tif (CURRVIEW == 2) {\n\t\t\t// top\n\t\t\txb = vid.width - strlen (strng) * 8 - 12;\n\t\t\tyb = vid.height / 2 - sb_lines - 16;\n\t\t\txd = vid.width - strlen (weapons) * 8 - 84;\n\t\t\tyd = vid.height / 2 - sb_lines - 8;\n\t\t}\n\t\telse if (CURRVIEW == 3) {\n\t\t\t// Bottom left\n\t\t\txb = vid.width - (vid.width / 2) - strlen (strng) * 8 - 12;\n\t\t\tyb = vid.height - sb_lines - 16;\n\t\t\txd = vid.width - (vid.width / 2) - strlen (weapons) * 8 - 84;\n\t\t\tyd = vid.height - sb_lines - 8;\n\t\t}\n\t\telse if (CURRVIEW == 1) {\n\t\t\t// Bottom right\n\t\t\txb = vid.width - strlen (strng) * 8 - 12;\n\t\t\tyb = vid.height - sb_lines - 16;\n\t\t\txd = vid.width - strlen (weapons) * 8 - 84;\n\t\t\tyd = vid.height - sb_lines - 8;\n\t\t}\n\t}\n\telse if (cl_multiview.value == 4) {\n\t\tif (CURRVIEW == 2) {\n\t\t\t// Top left\n\t\t\txb = vid.width - (vid.width / 2) - strlen (strng) * 8 - 12;\n\t\t\tyb = vid.height / 2 - sb_lines - 16;\n\t\t\txd = vid.width - (vid.width / 2) - strlen (weapons) * 8 - 84;\n\t\t\tyd = vid.height / 2 - sb_lines - 8;\n\t\t}\n\t\telse if (CURRVIEW == 3) {\n\t\t\t// Top right\n\t\t\txb = vid.width - strlen (strng) * 8 - 12;\n\t\t\tyb = vid.height / 2 - sb_lines - 16;\n\t\t\txd = vid.width - strlen (weapons) * 8 - 84;\n\t\t\tyd = vid.height / 2 - sb_lines - 8;\n\t\t}\n\t\telse if (CURRVIEW == 4) {\n\t\t\t// Bottom left\n\t\t\txb = vid.width - (vid.width / 2) - strlen (strng) * 8 - 12;\n\t\t\tyb = vid.height - sb_lines - 16;\n\t\t\txd = vid.width - (vid.width / 2) - strlen (weapons) * 8 - 84;\n\t\t\tyd = vid.height - sb_lines - 8;\n\t\t}\n\t\telse {\n\t\t\t// Bottom right\n\t\t\txb = vid.width - strlen (strng) * 8 - 12;\n\t\t\tyb = vid.height - sb_lines - 16;\n\t\t\txd = vid.width - strlen (weapons) * 8 - 84;\n\t\t\tyd = vid.height - sb_lines - 8;\n\t\t}\n\t}\n\n\t// Hud info\n\tif ((cl_mvdisplayhud.value && !cl_mvinset.value && cl_multiview.value == 2)\n\t\t|| (cl_mvdisplayhud.value && cl_multiview.value != 2)) {\n\t\tDraw_String (xb, yb, strng);\n\t}\n\telse if (cl_multiview.value == 2 && cl_mvdisplayhud.value && CURRVIEW == 1 && cl_mvinsethud.value) {\n\t\tif (vid.width > 512) {\n\t\t\tDraw_String (xb, yb, strng);\n\t\t}\n\t\telse {\n\t\t\t// <= 512 mvinset, just draw the name\n\t\t\tint var, limit;\n\t\t\tchar namestr[16];\n\t\t\tmv_viewrect_t view;\n\n\t\t\tSCR_SetMVStatusTwoInsetViewRect(&view);\n\n\t\t\tvar = ((view.x + view.width) - 320) * 0.05;\n\t\t\tvar--;\n\t\t\tvar |= (var >> 1);\n\t\t\tvar |= (var >> 2);\n\t\t\tvar |= (var >> 4);\n\t\t\tvar |= (var >> 8);\n\t\t\tvar |= (var >> 16);\n\t\t\tvar++;\n\n\t\t\tlimit = bound(0, view.width / 8.0f, sizeof(namestr));\n\t\t\tstrlcpy(namestr, name, limit);\n\n\t\t\tDraw_String((view.x + view.width) - strlen(namestr) * 8 - var - 2, yb + (cl_sbar.integer ? 1 : 4), namestr);\n\t\t}\n\t}\n\n\t// Weapons\n\tif ((cl_mvdisplayhud.value && !cl_mvinset.value && cl_multiview.value == 2)\n\t\t|| (cl_mvdisplayhud.value && cl_multiview.value != 2)) {\n\t\tDraw_String (xd, yd, weapons);\n\t}\n\telse if (cl_multiview.value == 2 && cl_mvdisplayhud.value && CURRVIEW == 1 && vid.width > 512 && cl_mvinsethud.value) {\n\t\tDraw_String (xd, yd, weapons);\n\t}\n}\n\n\n\n\n\n\n// cl_main.c\nstatic void CL_Multiview (void)\n{\n\tstatic int playernum = 0;\n\n\tif (!cls.mvdplayback) {\n\t\treturn;\n\t}\n\n\tnNumViews = cl_multiview.value;\n\n\t/*\n\t// Only refresh skins once (to start with), I think this is the best solution for multiview\n\t// eg when viewing 4 players in a 2v2.\n\t// Also refresh them when the player changes what team to track.\n\tif ((!CURRVIEW && cls.state >= ca_connected) || nSwapPov) {\n\t\tTP_RefreshSkins ();\n\t}*/\n\n\tCL_MultiviewOverrideValues ();\n\n\tnPlayernum = playernum;\n\n\t// Copy the stats for the player we're about to draw in the next\n\t// view to the client state, so that the correct stats are drawn\n\t// in the multiview mini-HUD.\n\tmemcpy (cl.stats, cl.players[playernum].stats, sizeof (cl.stats));\n\n\t//\n\t// Increase the current view being rendered.\n\t//\n\tCURRVIEW = CL_IncrLoop (CURRVIEW, cl_multiview.integer);\n\n\tif (cl_mvinset.value && cl_multiview.value == 2) {\n\t\t//\n\t\t// Special case for mvinset and tracking 2 people\n\t\t// this is meant for spectating duels primarily.\n\t\t// Lets the user swap which player is shown in the\n\t\t// main view and the mvinset by pressing jump.\n\t\t//\n\n\t\t// If both the mvinset and main view is set to show\n\t\t// the same player, pick the first player for the main view\n\t\t// and the next after that for the mvinset.\n\t\tif (nTrack1duel == nTrack2duel) {\n\t\t\tnTrack1duel = CL_NextPlayer (-1);\n\t\t\tnTrack2duel = CL_NextPlayer (nTrack1duel);\n\t\t}\n\n\t\t// The user pressed jump so we need to swap the pov.\n\t\tif (nSwapPov) {\n\t\t\tif (nSwapPov > 0) {\n\t\t\t\tnTrack1duel = CL_NextPlayer (nTrack1duel);\n\t\t\t\tnTrack2duel = CL_NextPlayer (nTrack2duel);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tnTrack1duel = CL_PrevPlayer (nTrack1duel);\n\t\t\t\tnTrack2duel = CL_PrevPlayer (nTrack2duel);\n\t\t\t}\n\t\t\tnSwapPov = false;\n\t\t}\n\t\telse {\n\t\t\t// Set the playernum based on if we're drawing the mvinset\n\t\t\t// or the main view\n\t\t\t// (nTrack1duel = main view)\n\t\t\t// (nTrack2duel = mvinset)\n\t\t\tplayernum = (CURRVIEW == 1) ? nTrack1duel : nTrack2duel;\n\t\t}\n\t}\n\telse {\n\t\t//\n\t\t// Normal multiview.\n\t\t//\n\n\t\t// Start from the first player on each new frame.\n\t\tplayernum = ((CURRVIEW == 1) ? 0 : playernum);\n\n\t\t//\n\t\t// The player pressed jump and wants to change what team is spectated.\n\t\t//\n\t\tif (nSwapPov && cl_multiview.value >= 2 && cl.teamplay) {\n\t\t\tint j;\n\t\t\tint team_slot_count = 0;\n\t\t\tint last_mv_trackslots[4];\n\n\t\t\t// Save the old track values and reset them.\n\t\t\tmemcpy (last_mv_trackslots, mv_trackslots, sizeof (last_mv_trackslots));\n\t\t\tmemset (mv_trackslots, -1, sizeof (mv_trackslots));\n\n\t\t\t// Find the new team.\n\t\t\tfor (j = 0; j < MAX_CLIENTS; j++) {\n\t\t\t\tif (!cl.players[j].spectator && cl.players[j].name[0]) {\n\t\t\t\t\t// Find the opposite team from the one we are tracking now.\n\t\t\t\t\tif (!currteam[0] || strcmp (currteam, cl.players[j].team)) {\n\t\t\t\t\t\tstrlcpy (currteam, cl.players[j].team, sizeof (currteam));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Find the team members.\n\t\t\tfor (j = 0; j < MAX_CLIENTS; j++) {\n\t\t\t\tif (!cl.players[j].spectator\n\t\t\t\t\t&& cl.players[j].name[0]\n\t\t\t\t\t&& !strcmp (currteam, cl.players[j].team)) {\n\t\t\t\t\t// Find the player slot to track.\n\t\t\t\t\tmv_trackslots[team_slot_count] = Player_StringtoSlot (cl.players[j].name, false, false);\n\t\t\t\t\tteam_slot_count++;\n\t\t\t\t}\n\n\t\t\t\tCom_DPrintf (\"New trackslots: %i %i %i %i\\n\", mv_trackslots[0], mv_trackslots[1], mv_trackslots[2], mv_trackslots[3]);\n\n\t\t\t\t// Don't go out of bounds in the mv_trackslots array.\n\t\t\t\tif (team_slot_count == 4) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (cl_multiview.value == 2 && team_slot_count == 2) {\n\t\t\t\t// Switch between 2on2 teams.\n\t\t\t}\n\t\t\telse if (cl_multiview.value < team_slot_count || team_slot_count >= 3) {\n\t\t\t\t// We don't want to show all from one team and then one of the enemies...\n\t\t\t\tcl_multiview.value = team_slot_count;\n\t\t\t}\n\t\t\telse if (team_slot_count == 2) {\n\t\t\t\t// 2on2... one team on top, on on the bottom\n\t\t\t\t// Swap the teams between the top and bottom in a 4 view setup.\n\t\t\t\tcl_multiview.value = 4;\n\t\t\t\tmv_trackslots[MV_VIEW3] = last_mv_trackslots[MV_VIEW1];\n\t\t\t\tmv_trackslots[MV_VIEW4] = last_mv_trackslots[MV_VIEW2];\n\n\t\t\t\tCom_DPrintf (\"Team on top/bottom trackslots: %i %i %i %i\\n\",\n\t\t\t\t\tmv_trackslots[0], mv_trackslots[1], mv_trackslots[2], mv_trackslots[3]);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t// Check if the track* values have been set by the user,\n\t\t\t// otherwise show the first 4 players.\n\n\t\t\tif (CURRVIEW >= 1 && CURRVIEW <= 4) {\n\t\t\t\t// If the value of mv_trackslots[i] is negative, it means that view\n\t\t\t\t// doesn't have any track value set so we need to find someone to track using CL_NextPlayer().\n\t\t\t\tplayernum = ((mv_trackslots[CURRVIEW - 1] < 0) ? CL_NextPlayer (playernum) : mv_trackslots[CURRVIEW - 1]);\n\t\t\t}\n\t\t}\n\t}\n\n\t// BUGFIX - Make sure the player we're tracking is still left, might have disconnected since\n\t// we picked him for tracking (This would result in being thrown into freefly mode and not being able\n\t// to go back into tracking mode when a player disconnected).\n\tif (!nSwapPov && (cl.players[playernum].spectator || !cl.players[playernum].name[0])) {\n\t\tmv_trackslots[CURRVIEW - 1] = -1;\n\t}\n\n\tif (nSwapPov) {\n\t\tCom_DPrintf (\"Final trackslots: %i %i %i %i\\n\", mv_trackslots[0], mv_trackslots[1], mv_trackslots[2], mv_trackslots[3]);\n\t}\n\n\tnSwapPov = false;\n\n\t// Set the current player we're tracking for the next view to be drawn.\n\t// BUGFIX: Only change the spec_track if the new track target is a player.\n\tif (!cl.players[playernum].spectator && cl.players[playernum].name[0]) {\n\t\tnext_spec_track = playernum;\n\t}\n\n\t// Make sure we reset variables we suppressed during multiview drawing.\n\tbExitmultiview = true;\n}\n\ntypedef struct mv_temp_cvar_s {\n\tcvar_t* cvar;\n\tfloat   value;\n} mv_temp_cvar_t;\n\nstatic mv_temp_cvar_t multiviewCvars[] = {\n\t{ &scr_viewsize,      100.0f },\n\t{ &cl_fakeshaft,        0.0f },\n\t{ &gl_polyblend,        1.0f },\n\t{ &gl_clear,            0.0f },\n};\n#define MV_CVAR_VIEWSIZE 0                 // we reference saved value when cl_mvinset specified\n\nvoid CL_MultiviewSaveValues (void)\n{\n\tint i = 0;\n\n\tfor (i = 0; i < sizeof (multiviewCvars) / sizeof (multiviewCvars[0]); ++i) {\n\t\tmultiviewCvars[i].value = multiviewCvars[i].cvar->value;\n\t}\n}\n\nvoid CL_MultiviewRestoreValues (void)\n{\n\tint i = 0;\n\n\tfor (i = 0; i < sizeof (multiviewCvars) / sizeof (multiviewCvars[0]); ++i) {\n\t\tCvar_SetValue(multiviewCvars[i].cvar, multiviewCvars[i].value);\n\t}\n\n\tbExitmultiview = false;\n}\n\nstatic void CL_MultiviewOverrideValues (void)\n{\n\t// contrast was disabled for OpenGL build with the note \"blanks all but 1 view\"\n\t// this was due to gl_ztrick in R_Clear(void) that would clear these. FIXED\n\t// v_contrast.value = 1;\n\n\t// stop fakeshaft as it lerps with the other views\n\tif (cl_fakeshaft.value < 1 && cl_fakeshaft.value > 0) {\n\t\tCvar_SetValue(&cl_fakeshaft, 0);\n\t}\n\n\t// allow mvinset 1 to use viewsize value\n\tscr_viewsize.value = multiviewCvars[MV_CVAR_VIEWSIZE].value;\n\tif ((!cl_mvinset.value && cl_multiview.value == 2) || cl_multiview.value != 2) {\n\t\tCvar_SetValue(&scr_viewsize, 120);\n\t}\n\n\t// stop small screens\n\tif (cl_mvinset.value && cl_multiview.value == 2 && scr_viewsize.value < 100) {\n\t\tCvar_SetValue(&scr_viewsize, 100);\n\t}\n\n\tCvar_SetValue(&gl_polyblend, 0);\n\tCvar_SetValue(&gl_clear, 0);\n}\n\nstatic qbool CL_MultiviewCvarResetRequired (void)\n{\n\treturn bExitmultiview;\n}\n\n// Can be used during rendering to find out what the next spec track will be\n//   (especially useful when CURRVIEW == 1 after CL_Multiview() to report who tracked in main view)\nint CL_MultiviewNextPlayer (void)\n{\n\tif (next_spec_track >= 0) {\n\t\treturn next_spec_track;\n\t}\n\treturn cl.spec_track;\n}\n\nint CL_MultiviewCurrentView (void)\n{\n\treturn CURRVIEW;\n}\n\nint CL_MultiviewNumberViews (void)\n{\n\treturn nNumViews;\n}\n\nvoid CL_MultiviewResetCvars (void)\n{\n\tif (CL_MultiviewCvarResetRequired ()) {\n\t\tCL_MultiviewRestoreValues ();\n\t\tnTrack1duel = nTrack2duel = 0;\n\t\tCURRVIEW = 0;\n\t}\n}\n\nqbool CL_MultiviewEnabled (void)\n{\n\tif (cl_multiview.integer > 0 && cls.mvdplayback && !cl.intermission) {\n\t\tif (!MVD_PowerupCams_Enabled()) {\n\t\t\tint first_player = CL_NextPlayer(-1);\n\t\t\tint next_player = CL_NextPlayer(first_player);\n\n\t\t\tif (first_player == next_player) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nqbool CL_MultiviewInsetEnabled (void)\n{\n\treturn cl_multiview.value == 2 && cls.mvdplayback && cl_mvinset.value;\n}\n\nqbool CL_MultiviewInsetView (void)\n{\n\treturn CL_MultiviewInsetEnabled() && CURRVIEW == 1;\n}\n\nint CL_MultiviewActiveViews (void)\n{\n\tif (cls.mvdplayback) {\n\t\treturn bound(1, cl_multiview.integer, 4);\n\t}\n\treturn 1;\n}\n\n\n\n\n\n// Events called during game loop\n\nvoid CL_MultiviewFrameStart (void)\n{\n\tnext_spec_track = -1;\n}\n\nvoid CL_MultiviewFrameFinish (void)\n{\n\tif (next_spec_track >= 0) {\n\t\tcl.spec_track = next_spec_track;\n\t}\n}\n\nvoid CL_MultiviewDemoStart (void)\n{\n\tmemset (mv_trackslots, -1, sizeof (mv_trackslots));\n\tnTrack1duel = 0;\n\tnTrack2duel = 0;\n}\n\nvoid CL_MultiviewDemoFinish (void)\n{\n}\n\nvoid CL_MultiviewDemoStopRewind (void)\n{\n\tmemcpy (mv_trackslots, rewind_trackslots, sizeof (mv_trackslots));\n\tnTrack1duel = rewind_duel_track1;\n\tnTrack2duel = rewind_duel_track2;\n}\n\nvoid CL_MultiviewDemoStartRewind (void)\n{\n\tmemcpy (rewind_trackslots, mv_trackslots, sizeof (rewind_trackslots));\n\trewind_duel_track1 = nTrack1duel;\n\trewind_duel_track2 = nTrack2duel;\n}\n\nvoid CL_MultiviewTrackingAdjustment (int adjustment)\n{\n\tnSwapPov = adjustment;\n}\n\nvoid CL_MultiviewInitialise (void)\n{\n\tmemset (mv_trackslots, -1, sizeof (mv_trackslots));\n}\n\nvoid CL_TrackMV1_f (void)\n{\n\tCL_Track (MV_VIEW1);\n}\n\nvoid CL_TrackMV2_f (void)\n{\n\tCL_Track (MV_VIEW2);\n}\nvoid CL_TrackMV3_f (void)\n{\n\tCL_Track (MV_VIEW3);\n}\nvoid CL_TrackMV4_f (void)\n{\n\tCL_Track (MV_VIEW4);\n}\n\nvoid CL_TrackTeam_f (void)\n{\n\tint i, teamchoice, team_slot_count = 0;\n\n\tif (cls.state < ca_connected) {\n\t\tCom_Printf (\"You must be connected to track\\n\", Cmd_Argv (0));\n\t\treturn;\n\t}\n\n\tif (!cl.spectator) {\n\t\tCom_Printf (\"You can only track in spectator mode\\n\", Cmd_Argv (0));\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc () != 2) {\n\t\tCom_Printf (\"Usage: %s < 1 | 2 >\\n\", Cmd_Argv (0));\n\t\treturn;\n\t}\n\n\t// Get the team.\n\tteamchoice = atoi (Cmd_Args ());\n\n\tif (!currteam[0]) {\n\t\t// Find the the first team.\n\t\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\t\tif (VALID_PLAYER (i) && strcmp (currteam, cl.players[i].team) != 0) {\n\t\t\t\tstrlcpy (currteam, cl.players[i].team, sizeof (currteam));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Find the team members.\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\t// Find the player slot to track.\n\t\tif (!cl.players[i].spectator && strcmp (cl.players[i].name, \"\")\n\t\t\t&& teamchoice == 1 && !strcmp (currteam, cl.players[i].team)) {\n\t\t\tmv_trackslots[team_slot_count] = Player_StringtoSlot (cl.players[i].name, false, false);\n\t\t\tteam_slot_count++;\n\t\t}\n\t\telse if (!cl.players[i].spectator && strcmp (cl.players[i].name, \"\")\n\t\t\t&& teamchoice == 2 && strcmp (currteam, cl.players[i].team)) {\n\t\t\tmv_trackslots[team_slot_count] = Player_StringtoSlot (cl.players[i].name, false, false);\n\t\t\tteam_slot_count++;\n\t\t}\n\n\t\t// Don't go out of bounds in the mv_trackslots array.\n\t\tif (team_slot_count == 4) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tcl_multiview.value = team_slot_count;\n}\n\nvoid CL_MultiviewSetTrackSlot (int trackSlot, int player)\n{\n\tif (trackSlot >= 0 && trackSlot < sizeof (mv_trackslots) / sizeof (mv_trackslots[0])) {\n\t\tmv_trackslots[trackSlot] = player;\n\t}\n\telse {\n\t\tnTrack1duel = player;\n\t\tnTrack2duel = CL_NextPlayer (player);\n\t}\n}\n\nvoid CL_MultiviewPreUpdateScreen (void)\n{\n\tif (!CL_MultiviewCvarResetRequired ()) {\n\t\tCL_MultiviewSaveValues ();\n\t}\n\tif (CL_MultiviewCvarResetRequired () && !cl_multiview.value) {\n\t\tCL_MultiviewRestoreValues ();\n\t}\n}\n\nqbool CL_MultiviewAdvanceView (void)\n{\n\tCL_Multiview ();\n\n\treturn CURRVIEW > 1;\n}\n\nint CL_MultiviewMainView (void)\n{\n\treturn nTrack1duel;\n}\n\nint CL_MultiviewAutotrackSlot (void)\n{\n\treturn CL_MultiviewInsetEnabled () ? nTrack1duel : cl.viewplayernum;\n}\n\nqbool CL_MultiviewGetCrosshairCoordinates(qbool use_screen_coords, float* cross_x, float* cross_y, qbool* half_size)\n{\n\textern vrect_t scr_vrect;\n\n\tfloat x, y;\n\tfloat min_x = scr_vrect.x;\n\tfloat width = scr_vrect.width;\n\tfloat min_y = scr_vrect.y;\n\tfloat height = scr_vrect.height;\n\n\tif (use_screen_coords) {\n\t\tmin_x = min_y = 0;\n\t\twidth = VID_RenderWidth2D();\n\t\theight = VID_RenderHeight2D();\n\n\t\tmin_y += scr_vrect.y * ((float)height / vid.height);\n\t\theight -= (vid.height - scr_vrect.height) * ((float)height / vid.height);\n\t}\n\n\tx = min_x + 0.5 * width;\n\ty = min_y + 0.5 * height;\n\t*half_size = false;\n\n\tif (CL_MultiviewEnabled()) {\n\t\tint active_views = CL_MultiviewActiveViews();\n\n\t\tif (active_views == 2) {\n\t\t\tif (CL_MultiviewInsetEnabled()) {\n\t\t\t\t// inset\n\t\t\t\tif (CL_MultiviewInsetView()) {\n\t\t\t\t\tif (!cl_mvinsetcrosshair.value) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tx = inset_x + inset_width / 2.0f;\n\t\t\t\t\ty = inset_y + inset_height / 2.0f;\n\n\t\t\t\t\t// y is flipped in 2d world...\n\t\t\t\t\ty = VID_RenderHeight2D() - y;\n\n\t\t\t\t\t*half_size = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (CL_MultiviewCurrentView() == 1)\n\t\t\t\t{\n\t\t\t\t\tx = min_x + width / 2;\n\t\t\t\t\ty = min_y + height * 3.0f / 4.0f;\n\t\t\t\t}\n\t\t\t\telse if (CL_MultiviewCurrentView() == 2)\n\t\t\t\t{\n\t\t\t\t\t// top cv2\n\t\t\t\t\tx = min_x + width / 2;\n\t\t\t\t\ty = min_y + height * 1.0f / 4.0f;\n\t\t\t\t}\n\t\t\t\t*half_size = true;\n\t\t\t}\n\t\t}\n\t\telse if (active_views == 3) {\n\t\t\tif (CL_MultiviewCurrentView() == 2) {\n\t\t\t\t// top\n\t\t\t\tx = min_x + width / 2;\n\t\t\t\ty = min_y + height / 4.0f;\n\t\t\t}\n\t\t\telse if (CL_MultiviewCurrentView() == 3) {\n\t\t\t\t// bl\n\t\t\t\tx = min_x + width / 4.0f;\n\t\t\t\ty = min_y + height * 3.0f / 4.0f;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// br\n\t\t\t\tx = min_x + width * 3.0f / 4.0f;\n\t\t\t\ty = min_y + height * 3.0f / 4.0f;\n\t\t\t}\n\t\t\t*half_size = true;\n\t\t}\n\t\telse if (active_views >= 4) {\n\t\t\tif (CL_MultiviewCurrentView() == 2) {\n\t\t\t\t// tl\n\t\t\t\tx = min_x + width / 4.0f;\n\t\t\t\ty = min_y + height / 4.0f;\n\t\t\t}\n\t\t\telse if (CL_MultiviewCurrentView() == 3) {\n\t\t\t\t// tr\n\t\t\t\tx = min_x + width * 3.0f / 4.0f;\n\t\t\t\ty = min_y + height / 4.0f;\n\t\t\t}\n\t\t\telse if (CL_MultiviewCurrentView() == 4) {\n\t\t\t\t// bl\n\t\t\t\tx = min_x + width / 4.0f;\n\t\t\t\ty = min_y + height * 3.0f / 4.0f;\n\t\t\t}\n\t\t\telse if (CL_MultiviewCurrentView() == 1) {\n\t\t\t\t// br\n\t\t\t\tx = min_x + width * 3.0f / 4.0f;\n\t\t\t\ty = min_y + height * 3.0f / 4.0f;\n\t\t\t}\n\t\t\t*half_size = true;\n\t\t}\n\t}\n\n\t*cross_x = x;\n\t*cross_y = y;\n\treturn true;\n}\n\nvoid SCR_DrawMultiviewBorders(void)\n{\n\t//\n\t// Draw black borders around the views.\n\t//\n\tif (cl_multiview.integer == 2 && !cl_mvinset.integer) {\n\t\tDraw_Fill(0, vid.height / 2, vid.width - 1, 1, 0);\n\t}\n\telse if (cl_multiview.integer == 2 && cl_mvinset.integer) {\n\t\textern byte color_black[4];\n\n\t\tR_OrthographicProjection(0, glwidth, 0, glheight, -99999, 99999);\n\t\tDraw_AlphaRectangleRGB(inset_x, inset_y, inset_width, inset_height, 1.0f, false, RGBAVECT_TO_COLOR(color_black));\n\t\tR_OrthographicProjection(0, vid.width, vid.height, 0, -99999, 99999);\n\t}\n\telse if (cl_multiview.integer == 3) {\n\t\tDraw_Fill(vid.width / 2, vid.height / 2, 1, vid.height / 2, 0);\n\t\tDraw_Fill(0, vid.height / 2, vid.width, 1, 0);\n\t}\n\telse if (cl_multiview.integer == 4) {\n\t\tDraw_Fill(vid.width / 2, 0, 1, vid.height, 0);\n\t\tDraw_Fill(0, vid.height / 2, vid.width, 1, 0);\n\t}\n}\n\nvoid CL_MultiviewInsetSetScreenCoordinates(int x, int y, int width, int height)\n{\n\tinset_x = x;\n\tinset_y = y;\n\tinset_width = width;\n\tinset_height = height;\n}\n\ncentity_t* CL_WeaponModelForView(void)\n{\n\tint view = bound(0, CURRVIEW - 1, MV_VIEWS - 1);\n\n\treturn &cl.viewent[view];\n}\n\nvoid CL_MultiviewInsetRestoreStats(void)\n{\n\tif (cl_multiview.value == 2 && cl_mvinset.integer) {\n\t\tmemcpy(cl.stats, cl.players[nTrack1duel].stats, sizeof(cl.stats));\n\t}\n}\n"
  },
  {
    "path": "src/cl_nqdemo.c",
    "content": "/*\nCopyright (C) 2011 tonik\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"cdaudio.h\"\n#include \"stats_grid.h\"\n#include \"sbar.h\"\n#include \"qsound.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"vx_stuff.h\"\n#include \"settings.h\"\n#include \"teamplay.h\"\n#ifdef _WIN32\n#include \"movie.h\"\n#endif\n\n#define MAX_BIG_MSGLEN 8000\nint CL_Demo_Read(void *buf, int size, qbool peek);\n#define SCR_EndLoadingPlaque()\nint cl_entframecount;\n#define CL_EntityParticles(a)\nextern cvar_t cl_rocket2grenade;\n#define R_ModelFlags(model) model->flags\n\nvoid CL_FindModelNumbers (void);\nvoid TP_NewMap (void);\nvoid CL_ParseBaseline (entity_state_t *es);\nvoid CL_ParseStatic (qbool extended);\nvoid CL_ParseStaticSound (void);\n\n#define\tNQ_PROTOCOL_VERSION\t15\n\n#define\tNQ_MAX_CLIENTS\t16\n#define NQ_SIGNONS\t\t4\n\n#define\tNQ_MAX_EDICTS\t600\t\t\t// Must be <= MAX_CL_EDICTS!\n\n// if the high bit of the servercmd is set, the low bits are fast update flags:\n#define\tNQ_U_MOREBITS\t(1<<0)\n#define\tNQ_U_ORIGIN1\t(1<<1)\n#define\tNQ_U_ORIGIN2\t(1<<2)\n#define\tNQ_U_ORIGIN3\t(1<<3)\n#define\tNQ_U_ANGLE2\t\t(1<<4)\n#define\tNQ_U_NOLERP\t\t(1<<5)\t\t// don't interpolate movement\n#define\tNQ_U_FRAME\t\t(1<<6)\n#define NQ_U_SIGNAL\t\t(1<<7)\t\t// just differentiates from other updates\n#define\tNQ_U_ANGLE1\t\t(1<<8)\n#define\tNQ_U_ANGLE3\t\t(1<<9)\n#define\tNQ_U_MODEL\t\t(1<<10)\n#define\tNQ_U_COLORMAP\t(1<<11)\n#define\tNQ_U_SKIN\t\t(1<<12)\n#define\tNQ_U_EFFECTS\t(1<<13)\n#define\tNQ_U_LONGENTITY\t(1<<14)\n\n#define\tSU_VIEWHEIGHT\t(1<<0)\n#define\tSU_IDEALPITCH\t(1<<1)\n#define\tSU_PUNCH1\t\t(1<<2)\n#define\tSU_PUNCH2\t\t(1<<3)\n#define\tSU_PUNCH3\t\t(1<<4)\n#define\tSU_VELOCITY1\t(1<<5)\n#define\tSU_VELOCITY2\t(1<<6)\n#define\tSU_VELOCITY3\t(1<<7)\n//define\tSU_AIMENT\t\t(1<<8)  AVAILABLE BIT\n#define\tSU_ITEMS\t\t(1<<9)\n#define\tSU_ONGROUND\t\t(1<<10)\t\t// no data follows, the bit is it\n#define\tSU_INWATER\t\t(1<<11)\t\t// no data follows, the bit is it\n#define\tSU_WEAPONFRAME\t(1<<12)\n#define\tSU_ARMOR\t\t(1<<13)\n#define\tSU_WEAPON\t\t(1<<14)\n\n// a sound with no channel is a local only sound\n#define\tNQ_SND_VOLUME\t\t(1<<0)\t\t// a byte\n#define\tNQ_SND_ATTENUATION\t(1<<1)\t\t// a byte\n#define\tNQ_SND_LOOPING\t\t(1<<2)\t\t// a long\n\n\n//=========================================================================================\n\nqbool\tnq_drawpings;\t// for sbar code\n\nstatic qbool\tnq_player_teleported;\t// hacky\n\nstatic vec3_t\tnq_last_fixangle;\n\nstatic int\t\tnq_num_entities;\nstatic int\t\tnq_viewentity;\nstatic int\t\tnq_forcecdtrack;\nstatic int\t\tnq_signon;\nstatic int\t\tnq_maxclients;\nstatic float\tnq_mtime[2];\nstatic vec3_t\tnq_mvelocity[2];\nstatic vec3_t\tnq_mviewangles[2];\nstatic vec3_t\tnq_mviewangles_temp;\nstatic qbool\tstandard_quake = true;\nstatic demoseekingtype_t nq_lastseektype = DST_SEEKING_NONE;\n\nstatic void CL_CheckForNQDSeekPointFound(void) \n{\n\tfloat demotime = nq_mtime[0];\n\n\t// Check for rewind only the first time we want to seek through the demo \n\tif (cls.demoseeking != nq_lastseektype)\n\t\tCL_Demo_Check_For_Rewind(nq_mtime[0]);\n\tnq_lastseektype = cls.demoseeking;\n\t\t\n\tif (cls.demoseeking == DST_SEEKING_STATUS)\n\t\tCL_Demo_Jump_Status_Check();\n\n\t// If we found demomark, we should stop seeking, so reset time to the proper value.\n\tif (cls.demoseeking == DST_SEEKING_FOUND) \n\t{\n\t\textern cvar_t demo_jump_rewind;\n\n\t\tcls.demotime = demotime; // this will trigger seeking stop\n\n\t\tif (demo_jump_rewind.value < 0) {\n\t\t\tCL_Demo_Jump (-demo_jump_rewind.value, -1, DST_SEEKING_NORMAL);\n\t\t}\n\t}\n\n\t// If we've reached our seek goal, stop seeking.\n\tif (cls.demoseeking && cls.demotime <= demotime && cls.state >= ca_active)\n\t{\n\t\tcls.demoseeking = DST_SEEKING_NONE;\n\t\tcls.demotime = demotime; \n\n\t\tif (cls.demorewinding)\n\t\t{\n\t\t\tCL_Demo_Stop_Rewinding();\n\t\t}\n\t}\n}\n\nstatic qbool CL_GetNQDemoMessage (void)\n{\n\tint i;\n\tfloat f;\n\textern qbool pb_ensure(void);\n\n\tif(!pb_ensure())\n\t\treturn false;\n\n\t// decide if it is time to grab the next message\t\t\n\tif (cls.state == ca_active\t\t\t\t// always grab until fully connected\n\t\t&& !(cl.paused & PAUSED_SERVER))\t// or if the game was paused by server\n\t{\n\t\tif (cls.timedemo)\n\t\t{\n\t\t\tif (cls.framecount == cls.td_lastframe)\n\t\t\t\treturn false;\t\t// already read this frame's message\n\t\t\tcls.td_lastframe = cls.framecount;\n\t\t\t// if this is the second frame, grab the real td_starttime\n\t\t\t// so the bogus time on the first frame doesn't count\n\t\t\tif (cls.framecount == cls.td_startframe + 1) {\n\t\t\t\tcls.td_starttime = Sys_DoubleTime();\n\t\t\t\tkey_dest = key_game;\n\t\t\t}\n\t\t}\n\t\telse if (cl.time <= nq_mtime[0] && ! cls.demoseeking)\n\t\t\treturn false;\t\t// don't need another message yet\n\t}\n\n\t// get the next message\n\tCL_Demo_Read(&net_message.cursize, 4, false);\n\tfor (i=0 ; i<3 ; i++) {\n\t\tCL_Demo_Read(&f, 4, false);\n\t\tnq_mviewangles_temp[i] = LittleFloat (f);\n\t}\n\n\tnet_message.cursize = LittleLong (net_message.cursize);\n\tif (net_message.cursize > MAX_BIG_MSGLEN)\n\t\tHost_Error (\"Demo message > MAX_BIG_MSGLEN\");\n\n\tCL_Demo_Read(net_message.data, net_message.cursize, false);\n\treturn true;\n}\n\n\nstatic void NQD_BumpEntityCount (int num)\n{\n\tif (num >= nq_num_entities)\n\t\tnq_num_entities = num + 1;\n}\n\n\nstatic void NQD_ParseClientdata (int bits)\n{\n\tint\t\ti, j;\n\textern player_state_t view_message;\n\n\tif (bits & SU_VIEWHEIGHT)\n\t\tcl.stats[STAT_VIEWHEIGHT] = MSG_ReadChar ();\n\telse\n\t\tcl.stats[STAT_VIEWHEIGHT] = DEFAULT_VIEWHEIGHT;\n\n\tif (bits & SU_IDEALPITCH)\n\t\tMSG_ReadChar ();\t\t// ignore\n\t\n\tVectorCopy (nq_mvelocity[0], nq_mvelocity[1]);\n\tfor (i=0 ; i<3 ; i++)\n\t{\n\t\tif (bits & (SU_PUNCH1<<i) ) {\n\t\t\tif (i == 0)\n\t\t\t\tcl.punchangle = MSG_ReadChar ();\n\t\t\telse\n\t\t\t\tMSG_ReadChar();\t\t\t// ignore\n\t\t}\n\t\tif (bits & (SU_VELOCITY1<<i) )\n\t\t\tnq_mvelocity[0][i] = MSG_ReadChar()*16;\n\t\telse\n\t\t\tnq_mvelocity[0][i] = 0;\n\t}\n\n// [always sent]\tif (bits & SU_ITEMS)\n\ti = MSG_ReadLong ();\t\t\t// FIXME, check SU_ITEMS anyway? -- Tonik\n\n\tif (cl.stats[STAT_ITEMS] != i)\n\t{\t// set flash times\n\t\tSbar_Changed ();\n\t\tfor (j=0 ; j<32 ; j++)\n\t\t\tif ( (i & (1<<j)) && !(cl.stats[STAT_ITEMS] & (1<<j)))\n\t\t\t\tcl.item_gettime[j] = cl.time;\n\t\tcl.stats[STAT_ITEMS] = i;\n\t}\n\t\t\n\tcl.onground = (bits & SU_ONGROUND) != 0;\n//\tcl.inwater = (bits & SU_INWATER) != 0;\n\n\tif (bits & SU_WEAPONFRAME)\n\t\tview_message.weaponframe = MSG_ReadByte ();\n\telse\n\t\tview_message.weaponframe = 0;\n\n\tif (bits & SU_ARMOR)\n\t\ti = MSG_ReadByte ();\n\telse\n\t\ti = 0;\n\tif (cl.stats[STAT_ARMOR] != i)\n\t{\n\t\tcl.stats[STAT_ARMOR] = i;\n\t\tSbar_Changed ();\n\t}\n\n\tif (bits & SU_WEAPON)\n\t\ti = MSG_ReadByte ();\n\telse\n\t\ti = 0;\n\tif (cl.stats[STAT_WEAPON] != i)\n\t{\n\t\tcl.stats[STAT_WEAPON] = i;\n\t\tSbar_Changed ();\n\t}\n\t\n\ti = MSG_ReadShort ();\n\tif (cl.stats[STAT_HEALTH] != i)\n\t{\n\t\tcl.stats[STAT_HEALTH] = i;\n\t\tSbar_Changed ();\n\t}\n\n\ti = MSG_ReadByte ();\n\tif (cl.stats[STAT_AMMO] != i)\n\t{\n\t\tcl.stats[STAT_AMMO] = i;\n\t\tSbar_Changed ();\n\t}\n\n\tfor (i=0 ; i<4 ; i++)\n\t{\n\t\tj = MSG_ReadByte ();\n\t\tif (cl.stats[STAT_SHELLS+i] != j)\n\t\t{\n\t\t\tcl.stats[STAT_SHELLS+i] = j;\n\t\t\tSbar_Changed ();\n\t\t}\n\t}\n\n\ti = MSG_ReadByte ();\n\n\tif (standard_quake)\n\t{\n\t\tif (cl.stats[STAT_ACTIVEWEAPON] != i)\n\t\t{\n\t\t\tcl.stats[STAT_ACTIVEWEAPON] = i;\n\t\t\tSbar_Changed ();\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (cl.stats[STAT_ACTIVEWEAPON] != (1<<i))\n\t\t{\n\t\t\tcl.stats[STAT_ACTIVEWEAPON] = (1<<i);\n\t\t\tSbar_Changed ();\n\t\t}\n\t}\n}\n\nstatic void NQD_SetSpectatorFlag(player_info_t* player)\n{\n\tplayer->spectator = (cl.teamplay && player->name[0] && player->real_topcolor == 0 && player->real_bottomcolor == 0 && player->frags <= -99);\n}\n\nvoid NQD_SetSpectatorFlags(void)\n{\n\tint i;\n\n\tfor (i = 0; i < sizeof(cl.players) / sizeof(cl.players[0]); ++i)\n\t\tNQD_SetSpectatorFlag(&cl.players[i]);\n}\n\n/*\n==================\nNQD_ParseUpdatecolors\n==================\n*/\nstatic void NQD_ParseUpdatecolors (void)\n{\n\tint\ti, colors;\n\tint top, bottom;\n\tqbool client_team_changed = false;\n\tqbool player_skin_changed = false;\n\n\ti = MSG_ReadByte ();\n\tif (i >= nq_maxclients)\n\t\tHost_Error (\"CL_ParseServerMessage: svc_updatecolors > NQ_MAX_CLIENTS\");\n\tcolors = MSG_ReadByte ();\n\n\t// fill in userinfo values\n\ttop = min(colors & 15, 13);\n\tbottom = min((colors >> 4) & 15, 13);\n\n\tclient_team_changed = (cl.playernum == i && bottom != cl.players[i].real_bottomcolor);\n\tplayer_skin_changed = (top != cl.players[i].real_topcolor || bottom != cl.players[i].real_bottomcolor);\n\n\tInfo_SetValueForKey (cl.players[i].userinfo, \"topcolor\", va(\"%i\", top), MAX_INFO_KEY);\n\tInfo_SetValueForKey (cl.players[i].userinfo, \"bottomcolor\", va(\"%i\", bottom), MAX_INFO_KEY);\n\n\tcl.players[i].real_topcolor = top;\n\tcl.players[i].real_bottomcolor = bottom;\n\n\tNQD_SetSpectatorFlag(&cl.players[i]);\n\n\t// Update team (based on bottom colour)\n\tif (cl.players[i].spectator)\n\t{\n\t\tInfo_SetValueForKey (cl.players[i].userinfo, \"team\", \"\", MAX_INFO_KEY);\n\t\tstrlcpy(cl.players[i].team, \"\", sizeof(cl.players[i].team));\n\t}\n\telse \n\t{\n\t\tchar* bottom_as_string = SettingColorName(bottom); \n\t\tInfo_SetValueForKey (cl.players[i].userinfo, \"team\", bottom_as_string, MAX_INFO_KEY);\n\t\tstrlcpy(cl.players[i].team, bottom_as_string, sizeof(cl.players[i].team));\n\t}\n\n\t// Update skins\n\tif (TP_NeedRefreshSkins() && client_team_changed)\n\t\tTP_RefreshSkins();\n\telse if (player_skin_changed)\n\t\tTP_RefreshSkin(i);\n\tSbar_Changed ();\n}\n\n\t\t\t\n/*\n==================\nNQD_ParsePrint\n==================\n*/\nstatic void NQD_ParsePrint (void)\n{\n\textern cvar_t\tcl_chatsound;\n\n\tchar *s = MSG_ReadString();\n\tif (s[0] == 1) {\t// chat\n\t\tif (cl_chatsound.value)\n\t\t\tS_LocalSound (\"misc/talk.wav\");\n\t}\n\tCom_Printf (\"%s\", s);\n}\n\n\n// JPG's ProQuake hacks\nstatic int ReadPQByte (void) {\n\tint word;\n\tword = MSG_ReadByte() * 16;\n\tword += MSG_ReadByte() - 272;\n\treturn word;\n}\nstatic int ReadPQShort (void) {\n\tint word;\n\tword = ReadPQByte() * 256;\n\tword += ReadPQByte();\n\treturn word;\n}\n\n/*\n==================\nNQD_ParseStufftext\n==================\n*/\nstatic void NQD_ParseStufftext (void)\n{\n\tchar\t*s;\n\tbyte\t*p;\n\n\tif (msg_readcount + 7 <= net_message.cursize &&\n\t\tnet_message.data[msg_readcount] == 1 && net_message.data[msg_readcount + 1] == 7)\n\t{\n\t\tint num, ping;\n\t\tMSG_ReadByte();\n\t\tMSG_ReadByte();\n\t\twhile ((ping = ReadPQShort()) != 0)\n\t\t{\n\t\t\tnum = ping / 4096;\n\t\t\tif ((unsigned int)num >= nq_maxclients)\n\t\t\t\tHost_Error (\"Bad ProQuake message\");\n\t\t\tcl.players[num].ping = ping & 4095;\n\t\t\tnq_drawpings = true;\n\t\t}\n\t\t// fall through to stufftext parsing (yes that's how it's intended by JPG)\n\t}\n\n\ts = MSG_ReadString ();\n\tif (developer.integer > 1) {\n\t\tCom_DPrintf(\"stufftext: %s\\n\", s);\n\t}\n\n\tfor (p = (byte *)s; *p; p++) {\n\t\tif (*p > 32 && *p < 128)\n\t\t\tgoto ok;\n\t}\n\t// ignore weird ProQuake stuff\n\treturn;\n\nok:\n\tCbuf_AddTextEx (&cbuf_svc, s);\n}\n\n\n/*\n==================\nNQD_ParseServerData\n==================\n*/\nstatic void NQD_ParseServerData(void)\n{\n\tchar\t*str;\n\tint\t\ti;\n\tint\t\tnummodels, numsounds;\n\tchar\tmapname[MAX_QPATH];\n\n\tCom_DPrintf(\"Serverdata packet received.\\n\");\n\n\t// wipe the client_state_t struct\n\tCL_ClearState();\n\n#ifdef PROTOCOL_VERSION_FTE\n\tcls.fteprotocolextensions = 0;\n#endif // PROTOCOL_VERSION_FTE\n\n#ifdef PROTOCOL_VERSION_FTE2\n\tcls.fteprotocolextensions2 = 0;\n#endif // PROTOCOL_VERSION_FTE2\n\n#ifdef PROTOCOL_VERSION_MVD1\n\tcls.mvdprotocolextensions1 = 0;\n#endif\n\n#ifdef FTE_PEXT_FLOATCOORDS\n\tmsg_coordsize = 2;\n\tmsg_anglesize = 1;\n#endif\n\n\t// parse protocol version number\n\ti = MSG_ReadLong();\n\tif (i != NQ_PROTOCOL_VERSION)\n\t\tHost_Error(\"Server returned version %i, not %i\", i, NQ_PROTOCOL_VERSION);\n\n\t// parse maxclients\n\tnq_maxclients = MSG_ReadByte();\n\tif (nq_maxclients < 1 || nq_maxclients > NQ_MAX_CLIENTS)\n\t\tHost_Error(\"Bad maxclients (%u) from server\", nq_maxclients);\n\n\t// parse gametype\n\tcl.gametype = MSG_ReadByte() ? GAME_DEATHMATCH : GAME_COOP;\n\n\t// parse signon message\n\tstr = MSG_ReadString();\n\tstrlcpy(cl.levelname, str, sizeof(cl.levelname));\n\n\t// separate the printfs so the server message can have a color\n\tCom_Printf(\"\\n\\n\\35\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\37\\n\\n\");\n\tCom_Printf(\"%c%s\\n\", 2, str);\n\n\t//\n\t// first we go through and touch all of the precache data that still\n\t// happens to be in the cache, so precaching something else doesn't\n\t// needlessly purge it\n\t//\n\n\t// precache models\n\t*mapname = 0;\n\tfor (nummodels = 1; ; nummodels++) {\n\t\tstr = MSG_ReadString();\n\t\tif (!str[0]) {\n\t\t\tbreak;\n\t\t}\n\t\tif (nummodels == MAX_MODELS) {\n\t\t\tHost_Error(\"Server sent too many model precaches\");\n\t\t}\n\t\tstrlcpy(cl.model_name[nummodels], str, sizeof(cl.model_name[0]));\n\t\tMod_TouchModel(str);\n\n\t\tif (nummodels == 1) {\n\t\t\tCOM_StripExtension(COM_SkipPath(cl.model_name[1]), mapname, sizeof(mapname));\n\t\t}\n\t}\n\n\t// precache sounds\n\tfor (numsounds = 1; ; numsounds++) {\n\t\tstr = MSG_ReadString();\n\t\tif (!str[0]) {\n\t\t\tbreak;\n\t\t}\n\t\tif (numsounds == MAX_SOUNDS) {\n\t\t\tHost_Error(\"Server sent too many sound precaches\");\n\t\t}\n\t\tstrlcpy(cl.sound_name[numsounds], str, sizeof(cl.sound_name[0]));\n\t\t// S_TouchSound (str); @ZQ@\n\t}\n\n\t// now we try to load everything else until a cache allocation fails\n\tcl.clipmodels[1] = CM_LoadMap(cl.model_name[1], true, NULL, &cl.map_checksum2);\n\tif (!com_serveractive) {\n\t\tCvar_ForceSet(&host_mapname, mapname);\n\t}\n\tCOM_StripExtension(COM_SkipPath(cl.model_name[1]), mapname, sizeof(mapname));\n\tcl.map_checksum2 = Com_TranslateMapChecksum(mapname, cl.map_checksum2);\n\n\tfor (i = 1; i < nummodels; i++) {\n\t\tcl.model_precache[i] = Mod_ForName(cl.model_name[i], false);\n\t\tif (cl.model_precache[i] == NULL) {\n\t\t\tHost_Error(\"Model %s not found\", cl.model_name[i]);\n\t\t}\n\n\t\tif (cl.model_name[i][0] == '*') {\n\t\t\tcl.clipmodels[i] = CM_InlineModel(cl.model_name[i]);\n\t\t}\n\t}\n\n\tfor (i = 1; i < numsounds; i++) {\n\t\tcl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i]);\n\t}\n\n\t// local state\n\tcl.worldmodel = cl.model_precache[1];\n\tif (!cl.model_precache[1]) {\n\t\tHost_Error(\"NQD_ParseServerData: NULL worldmodel\");\n\t}\n\n\tClassic_InitParticles();\n\tCL_FindModelNumbers();\n\tR_NewMap(false);\n\tTP_NewMap();\n\tMT_NewMap();\n\tStats_NewMap();\n\n\t// Reset the status grid.\n\tStatsGrid_Remove(&stats_grid);\n\tStatsGrid_ResetHoldItems();\n\tHUD_NewMap();\n\n\tHunk_Check(); // make sure nothing is hurt\n\n\tnq_signon = 0;\n\tnq_num_entities = 0;\n\tnq_drawpings = false; // unless we have the ProQuake extension\n\tcl.servertime_works = true;\n\t//\tcl.allow_fbskins = true; @ZQ@\n\tcls.state = ca_onserver;\n\tCL_Demo_Check_For_Rewind(nq_mtime[0]);\n\n\t// Demos don't store this so we set from user-defined option\n\tcl.teamplay = cl_demoteamplay.integer;\n\n\tnq_lastseektype = DST_SEEKING_NONE;\n}\n\n/*\n==================\nNQD_ParseStartSoundPacket\n==================\n*/\nstatic void NQD_ParseStartSoundPacket(void)\n{\n    vec3_t  pos;\n    int \tchannel, ent;\n    int \tsound_num;\n    int \tvolume;\n    int \tfield_mask;\n    float \tattenuation;  \n \tint\t\ti;\n\t           \n    field_mask = MSG_ReadByte(); \n\n    if (field_mask & NQ_SND_VOLUME)\n\t\tvolume = MSG_ReadByte ();\n\telse\n\t\tvolume = DEFAULT_SOUND_PACKET_VOLUME;\n\t\n    if (field_mask & NQ_SND_ATTENUATION)\n\t\tattenuation = MSG_ReadByte () / 64.0;\n\telse\n\t\tattenuation = DEFAULT_SOUND_PACKET_ATTENUATION;\n\t\n\tchannel = MSG_ReadShort ();\n\tsound_num = MSG_ReadByte ();\n\n\tent = channel >> 3;\n\tchannel &= 7;\n\n\tif (ent > NQ_MAX_EDICTS)\n\t\tHost_Error (\"NQD_ParseStartSoundPacket: ent = %i\", ent);\n\t\n\tfor (i=0 ; i<3 ; i++)\n\t\tpos[i] = MSG_ReadCoord ();\n \n    S_StartSound (ent, channel, cl.sound_precache[sound_num], pos, volume/255.0, attenuation);\n}       \n\n\n/*\n===============\nCL_ParseParticleEffect\n\nBack from NetQuake\n===============\n*/\nstatic void CL_ParseParticleEffect (void)\n{\n\tvec3_t\t\torg, dir;\n\tint\t\t\ti, count, color;\n\n\tfor (i = 0; i < 3; i++)\n\t\torg[i] = MSG_ReadCoord ();\n\tfor (i = 0; i < 3; i++)\n\t\tdir[i] = MSG_ReadChar () * (1.0/16);\n\tcount = MSG_ReadByte ();\n\tcolor = MSG_ReadByte ();\n\n\t// now run the effect\n\tif (count == 255)\n\t\tClassic_ParticleExplosion (org);\n\telse \n\t\tR_RunParticleEffect (org, dir, color, count);\n}\n\n/*\n==================\nNQD_ParseUpdate\n\nParse an entity update message from the server\nIf an entities model or origin changes from frame to frame, it must be\nrelinked.  Other attributes can change without relinking.\n==================\n*/\nstatic void NQD_ParseUpdate (int bits)\n{\n\tint\t\t\ti;\n//\tmodel_t\t\t*model;\n\tint\t\t\tmodnum;\n\tqbool\t\tforcelink;\n\tcentity_t\t*ent;\n\tentity_state_t\t*state;\n\tint\t\t\tnum;\n\tint prev_frame;\n\n\tif (nq_signon == NQ_SIGNONS - 1)\n\t{\t// first update is the final signon stage\n\t\tnq_signon = NQ_SIGNONS;\n\t\tCon_ClearNotify ();\n\t\t//TP_ExecTrigger (\"f_spawn\");\n\t\tSCR_EndLoadingPlaque ();\n\t\tcls.state = ca_active;\n\t\tCL_Demo_Check_For_Rewind(nq_mtime[0]);\n\t}\n\n\tif (bits & NQ_U_MOREBITS)\n\t{\n\t\ti = MSG_ReadByte ();\n\t\tbits |= (i<<8);\n\t}\n\n\tif (bits & NQ_U_LONGENTITY)\t\n\t\tnum = MSG_ReadShort ();\n\telse\n\t\tnum = MSG_ReadByte ();\n\n\tif (num >= NQ_MAX_EDICTS)\n\t\tHost_Error (\"NQD_ParseUpdate: ent > MAX_EDICTS\");\n\n\tNQD_BumpEntityCount (num);\n\n\tent = &cl_entities[num];\n//##\tent->previous = ent->current;\n\tVectorCopy (ent->current.origin, ent->old_origin);\t//##\n\tVectorCopy (ent->current.angles, ent->old_angles);\t//##\n\tprev_frame = ent->current.frame;\n\tent->current = ent->baseline;\n\tstate = &ent->current;\n\tstate->number = num;\n\n\tif (ent->sequence != cl_entframecount - 1)\n\t\tforcelink = true;\t// no previous frame to lerp from\n\telse\n\t\tforcelink = false;\n\tent->oldsequence = ent->sequence;\n\tent->sequence = cl_entframecount;\n\t\n\tif (bits & NQ_U_MODEL)\n\t{\n\t\tmodnum = MSG_ReadByte ();\n\t\tif (modnum >= MAX_MODELS)\n\t\t\tHost_Error (\"CL_ParseModel: bad modnum\");\n\t}\n\telse\n\t\tmodnum = ent->baseline.modelindex;\n\t\t\n//\tmodel = cl.model_precache[modnum];\n\tif (modnum != state->modelindex)\n\t{\n\t\tstate->modelindex = modnum;\n\t\t// automatic animation (torches, etc) can be either all together\n\t\t// or randomized\n\t\tif (modnum)\n\t\t{\n\t\t\t/*if (model->synctype == ST_RAND)\n\t\t\t\tstate->syncbase = (float)(rand()&0x7fff) / 0x7fff;\n\t\t\telse\n\t\t\t\tstate->syncbase = 0.0;\n\t\t\t\t*/\n\t\t}\n\t\telse\n\t\t\tforcelink = true;\t// hack to make null model players work\n\t}\n\t\n\tif (bits & NQ_U_FRAME)\n\t\tstate->frame = MSG_ReadByte ();\n\telse\n\t\tstate->frame = ent->baseline.frame;\n\n\tif (bits & NQ_U_COLORMAP)\n\t\ti = MSG_ReadByte();\n\telse\n\t\ti = ent->baseline.colormap;\n\n\tstate->colormap = i;\n\n\tif (bits & NQ_U_SKIN)\n\t\tstate->skinnum = MSG_ReadByte();\n\telse\n\t\tstate->skinnum = ent->baseline.skinnum;\n\n\tif (bits & NQ_U_EFFECTS)\n\t\tstate->effects = MSG_ReadByte();\n\telse\n\t\tstate->effects = 0;\n\n\tif (bits & NQ_U_ORIGIN1)\n\t\tstate->origin[0] = MSG_ReadCoord ();\n\telse\n\t\tstate->origin[0] = ent->baseline.origin[0];\n\tif (bits & NQ_U_ANGLE1)\n\t\tstate->angles[0] = MSG_ReadAngle ();\n\telse\n\t\tstate->angles[0] = ent->baseline.angles[0];\n\n\tif (bits & NQ_U_ORIGIN2)\n\t\tstate->origin[1] = MSG_ReadCoord ();\n\telse\n\t\tstate->origin[1] = ent->baseline.origin[1];\n\tif (bits & NQ_U_ANGLE2)\n\t\tstate->angles[1] = MSG_ReadAngle ();\n\telse\n\t\tstate->angles[1] = ent->baseline.angles[1];\n\n\tif (bits & NQ_U_ORIGIN3)\n\t\tstate->origin[2] = MSG_ReadCoord ();\n\telse\n\t\tstate->origin[2] = ent->baseline.origin[2];\n\tif (bits & NQ_U_ANGLE3)\n\t\tstate->angles[2] = MSG_ReadAngle ();\n\telse\n\t\tstate->angles[2] = ent->baseline.angles[2];\n\n\tif ( bits & NQ_U_NOLERP )\n\t\tforcelink = true;\n\n\tif (state->frame != prev_frame) {\n\t\tent->frametime = cl.time;\n\t\tent->oldframe = prev_frame;\n\t}\n\t\n\tif ( forcelink )\n\t{\t// didn't have an update last message\n\t\tVectorCopy (state->origin, ent->old_origin);\n\t\tVectorCopy (state->origin, ent->lerp_origin);\n\t\tVectorCopy (state->angles, ent->old_angles);\n//we get U_NOLERP for monsters, but we want to lerp them\tent->frametime = -1;\n\t\t//ent->forcelink = true;\n\t}\n}\n\n\n/*\n===============\nNQD_LerpPoint\n\nDetermines the fraction between the last two messages that the objects\nshould be put at.\n===============\n*/\nstatic float NQD_LerpPoint (void)\n{\n\tfloat\tf, frac;\n\n\tf = nq_mtime[0] - nq_mtime[1];\n\t\n\tif (!f || /* cl_nolerp.value || */ cls.timedemo) {\n\t\tcl.time = nq_mtime[0];\n\t\treturn 1;\n\t}\n\t\t\n\tif (f > 0.1)\n\t{\t// dropped packet, or start of demo\n\t\tnq_mtime[1] = nq_mtime[0] - 0.1;\n\t\tf = 0.1;\n\t}\n\tfrac = (cl.time - nq_mtime[1]) / f;\n\tif (frac < 0)\n\t{\n\t\tif (frac < -0.01)\n\t\t\tcl.time = nq_mtime[1];\n\t\tfrac = 0;\n\t}\n\telse if (frac > 1)\n\t{\n\t\tif (frac > 1.01)\n\t\t\tcl.time = nq_mtime[0];\n\t\tfrac = 1;\n\t}\n\t\t\n\treturn frac;\n}\n\nstatic void NQD_LerpPlayerinfo (float f)\n{\n\tif (cl.intermission) {\n\t\t// just stay there\n\t\treturn;\n\t}\n\n\tif (nq_player_teleported) {\n\t\tVectorCopy (nq_mvelocity[0], cl.simvel);\n\t\tVectorCopy (nq_mviewangles[0], cl.viewangles);\n\t\tVectorCopy (nq_mviewangles[0], cl.simangles);\n\t\treturn;\n\t}\n\n\tVectorInterpolate (nq_mvelocity[1], f, nq_mvelocity[0], cl.simvel);\n\tAngleInterpolate (nq_mviewangles[1], f, nq_mviewangles[0], cl.simangles);\n\tVectorCopy (cl.simangles, cl.viewangles);\n}\n\nstatic int NQD_FirstPersonCamera(void)\n{\n\textern cvar_t cam_thirdperson, cl_camera_tpp;\n\n\treturn !cam_thirdperson.integer && !cl_camera_tpp.integer;\n}\n\nvoid NQD_LinkEntities (void)\n{\n\tentity_t            ent;\n\tcentity_t*          cent;\n\tentity_state_t*     state;\n\tfloat               f;\n\tstruct model_s*     model;\n\tint                 modelflags;\n\tvec3_t              cur_origin;\n\tfloat               autorotate;\n\tint                 i, num;\n\tcustomlight_t       cst_lt = { 0 };\n\tfloat               flicker = (r_lightflicker.integer ? rand() & 31 : 0);\n\n\tf = NQD_LerpPoint ();\n\n\tNQD_LerpPlayerinfo (f);\n\n\tautorotate = anglemod (100*cl.time);\n\n\tmemset (&ent, 0, sizeof(ent));\n\n\tfor (num = 1; num < nq_num_entities; num++)\n\t{\n\t\tcent = &cl_entities[num];\n\t\tstate = &cent->current;\n\n\t\tif (cent->sequence != cl_entframecount)\n\t\t\tcontinue;\t\t// not present in this frame\n\n\t\tVectorCopy (state->origin, cur_origin);\n\t\tif (state->effects & EF_BRIGHTFIELD) {\n\t\t\tCL_EntityParticles(cur_origin);\n\t\t}\n\n\t\t// spawn light flashes, even ones coming from invisible objects\n\t\tif (state->effects & EF_MUZZLEFLASH) {\n\t\t\tvec3_t\t\tangles, forward;\n\t\t\tdlight_t\t*dl;\n\n\t\t\tdl = CL_AllocDlight (-num);\n\t\t\tVectorCopy (state->angles, angles);\n\t\t\tAngleVectors (angles, forward, NULL, NULL);\n\t\t\tVectorMA (cur_origin, 18, forward, dl->origin);\n\t\t\tdl->origin[2] += 16;\n\t\t\tdl->radius = 200 + flicker;\n\t\t\tdl->minlight = 32;\n\t\t\tdl->die = cl.time + 0.1;\n\t\t\tdl->type = lt_muzzleflash;\n\t\t}\n\t\tif (state->effects & EF_BRIGHTLIGHT) {\n\t\t\tif (state->modelindex != cl_modelindices[mi_player] || r_powerupglow.integer) {\n\t\t\t\tvec3_t\ttmp;\n\t\t\t\tVectorCopy (cur_origin, tmp);\n\t\t\t\ttmp[2] += 16;\n\t\t\t\tCL_NewDlight(state->number, cur_origin, 400 + flicker, 0.1, lt_default, 0);\n\t\t\t}\n\t\t}\n\t\tif (state->effects & EF_DIMLIGHT) {\n\t\t\tif (state->modelindex != cl_modelindices[mi_player] || r_powerupglow.integer) {\n\t\t\t\tCL_NewDlight(state->number, cur_origin, 200 + flicker, 0.1, lt_default, 0);\n\t\t\t}\n\t\t}\n\n\t\t// if set to invisible, skip\n\t\tif (!state->modelindex) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tcent->current = *state;\n\n\t\tent.model = model = cl.model_precache[state->modelindex];\n\t\tif (!model) {\n\t\t\tHost_Error(\"CL_LinkPacketEntities: bad modelindex\");\n\t\t}\n\n\t\tif (cl_rocket2grenade.value && cl_modelindices[mi_grenade] != -1) {\n\t\t\tif (state->modelindex == cl_modelindices[mi_rocket]) {\n\t\t\t\tent.model = cl.model_precache[cl_modelindices[mi_grenade]];\n\t\t\t}\n\t\t}\n\n\t\tmodelflags = R_ModelFlags (model);\n\n\t\t// rotate binary objects locally\n\t\tif (modelflags & EF_ROTATE) {\n\t\t\tent.angles[0] = 0;\n\t\t\tent.angles[1] = autorotate;\n\t\t\tent.angles[2] = 0;\n\t\t}\n\t\telse {\n\t\t\tAngleInterpolate(cent->old_angles, f, cent->current.angles, ent.angles);\n\t\t}\n\n\t\t// calculate origin\n\t\tfor (i = 0; i < 3; i++) {\n\t\t\tif (fabs(cent->current.origin[i] - cent->old_origin[i]) > 128.0f) {\n\t\t\t\t// teleport or something, don't lerp\n\t\t\t\tVectorCopy(cur_origin, ent.origin);\n\t\t\t\tif (num == nq_viewentity) {\n\t\t\t\t\tnq_player_teleported = true;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tent.origin[i] = cent->old_origin[i] + f * (cur_origin[i] - cent->old_origin[i]);\n\t\t}\n\n\t\tif (num == nq_viewentity) {\n\t\t\tVectorCopy (ent.origin, cent->lerp_origin);\t// FIXME?\n\n\t\t\tif (!cls.nqdemoplayback || NQD_FirstPersonCamera()) {\n\t\t\t\tcontinue;\t\t\t// player entity\n\t\t\t}\n\t\t}\n\n\t\tif (cl_deadbodyfilter.value && state->modelindex == cl_modelindices[mi_player]\n\t\t\t&& ( (i=state->frame)==49 || i==60 || i==69 || i==84 || i==93 || i==102) )\n\t\t\tcontinue;\n\n\t\tif (cl_gibfilter.value &&\n\t\t\t(state->modelindex == cl_modelindices[mi_h_player] || state->modelindex == cl_modelindices[mi_gib1]\n\t\t\t|| state->modelindex == cl_modelindices[mi_gib2] || state->modelindex == cl_modelindices[mi_gib3]))\n\t\t\tcontinue;\n\n\t\t// set colormap\n\t\tif (state->colormap && state->colormap <= MAX_CLIENTS\n\t\t\t&& state->modelindex == cl_modelindices[mi_player]\n\t\t) \n\t\t{\n\t\t\tent.colormap = cl.players[state->colormap-1].translations;\n\t\t\tent.scoreboard = &cl.players[state->colormap-1];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent.colormap = vid.colormap;\n\t\t\tent.scoreboard = NULL;\n\t\t}\n\n\t\t// set skin\n\t\tent.skinnum = state->skinnum;\n\n\t\t// set frame\n\t\tent.frame = state->frame;\n\t\tif (cent->frametime >= 0 && cent->frametime <= cl.time) {\n\t\t\tent.oldframe = cent->oldframe;\n\t\t\tent.framelerp = (cl.time - cent->frametime) * 10;\n\t\t}\n\t\telse {\n\t\t\tent.oldframe = ent.frame;\n\t\t\tent.framelerp = -1;\n\t\t}\n\t\t\n\t\t// add automatic particle trails\n\t\tif ((modelflags & ~EF_ROTATE)) {\n\t\t\tCL_AddParticleTrail(&ent, cent, &cst_lt, state);\n\t\t}\n\n\t\tVectorCopy(ent.origin, cent->lerp_origin);\n\t\tcent->sequence = cl_entframecount;\n\t\tCL_AddEntity(&ent);\n\t}\n\n\tif (nq_viewentity == 0) {\n\t\tHost_Error(\"viewentity == 0\");\n\t}\n\tVectorCopy(cl_entities[nq_viewentity].lerp_origin, cl.simorg);\n}\n\n\n\n\n\nextern char *svc_strings[];\nextern const int num_svc_strings;\n\n#define SHOWNET(x) {if(cl_shownet.value==2)Com_Printf (\"%3i:%s\\n\", msg_readcount-1, x);}\n\nstatic void NQD_ParseServerMessage (void)\n{\n\tint\t\tcmd;\n\tint\t\ti;\n\tqbool\tmessage_with_datagram;\t\t// hack to fix glitches when receiving a packet\n\t\t\t\t\t\t\t\t\t\t\t// without a datagram\n\n\tnq_player_teleported = false;\t\t// OMG, it's a hack!\n\tmessage_with_datagram = false;\n\tcl_entframecount++;\n\n\tif (cl_shownet.value == 1)\n\t\tCom_Printf (\"%i \", net_message.cursize);\n\telse if (cl_shownet.value == 2)\n\t\tCom_Printf (\"------------------\\n\");\n\t\n\tcl.onground = false;\t// unless the server says otherwise\t\n\n//\n// parse the message\n//\n\tMSG_BeginReading ();\n\twhile (1)\n\t{\n\t\tif (msg_badread)\n\t\t\tHost_Error (\"CL_ParseServerMessage: Bad server message\");\n\n\t\tcmd = MSG_ReadByte ();\n\n\t\tif (cmd == -1)\n\t\t{\n\t\t\tSHOWNET(\"END OF MESSAGE\");\n\t\t\tif (!message_with_datagram) {\n\t\t\t\tcl_entframecount--;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorCopy (nq_mviewangles[0], nq_mviewangles[1]);\n\t\t\t\tVectorCopy (nq_mviewangles_temp, nq_mviewangles[0]);\n\t\t\t}\n\t\t\treturn;\t\t// end of message\n\t\t}\n\n\t// if the high bit of the command byte is set, it is a fast update\n\t\tif (cmd & 128)\n\t\t{\n\t\t\tSHOWNET(\"fast update\");\n\t\t\tNQD_ParseUpdate (cmd&127);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (cmd < num_svc_strings)\n\t\t\tSHOWNET(svc_strings[cmd]);\n\t\n\t// other commands\n\t\tswitch (cmd)\n\t\t{\n\t\tdefault:\n\t\t\tHost_Error (\"CL_ParseServerMessage: Illegible server message (cmd %i)\\n\", cmd);\n\t\t\tbreak;\n\n\t\tcase svc_nop:\n\t\t\tbreak;\n\n\t\tcase nq_svc_time:\n\t\t\tnq_mtime[1] = nq_mtime[0];\n\t\t\tnq_mtime[0] = MSG_ReadFloat ();\n\t\t\tcl.servertime = nq_mtime[0];\n\t\t\tCL_CheckForNQDSeekPointFound();\n\t\t\tif (demostarttime <= 0) \n\t\t\t\tdemostarttime = nq_mtime[0];\n\t\t\tif (cls.demotime < demostarttime) \n\t\t\t\tcls.demotime = demostarttime;\n\t\t\tmessage_with_datagram = true;\n\t\t\tbreak;\n\n\t\tcase nq_svc_clientdata:\n\t\t\ti = MSG_ReadShort ();\n\t\t\tNQD_ParseClientdata (i);\n\t\t\tbreak;\n\n\t\tcase nq_svc_version:\n\t\t\ti = MSG_ReadLong ();\n\t\t\tif (i != NQ_PROTOCOL_VERSION)\n\t\t\t\tHost_Error (\"CL_ParseServerMessage: Server is protocol %i instead of %i\\n\", i, NQ_PROTOCOL_VERSION);\n\t\t\tbreak;\n\n\t\tcase svc_disconnect:\n\t\t\tCom_Printf (\"\\n======== End of demo ========\\n\\n\");\n//##\t\t\tCL_NextDemo ();\n\t\t\tHost_EndGame ();\n\t\t\tHost_Abort ();\n\t\t\tbreak;\n\n\t\tcase svc_print:\n\t\t\tNQD_ParsePrint ();\n\t\t\tbreak;\n\t\t\t\n\t\tcase svc_centerprint:\n\t\t\tSCR_CenterPrint (MSG_ReadString ());\n\t\t\tbreak;\n\n\t\tcase svc_stufftext:\n\t\t\tNQD_ParseStufftext ();\n\t\t\tbreak;\n\n\t\tcase svc_damage:\n\t\t\tV_ParseDamage ();\n\t\t\tbreak;\n\n\t\tcase svc_serverdata:\n\t\t\tNQD_ParseServerData ();\n\t\t\tbreak;\n\n\t\tcase svc_setangle:\n\t\t\tfor (i=0 ; i<3 ; i++)\n\t\t\t\tnq_last_fixangle[i] = cl.simangles[i] = cl.viewangles[i] = MSG_ReadAngle ();\n\t\t\tbreak;\n\n\t\tcase nq_svc_setview:\n\t\t\tnq_viewentity = MSG_ReadShort ();\n\t\t\tif (nq_viewentity <= nq_maxclients)\n\t\t\t\tcl.playernum = nq_viewentity - 1;\n\t\t\telse\t{\n\t\t\t\t// just let cl.playernum stay where it was\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase svc_lightstyle:\n\t\t\ti = MSG_ReadByte ();\n\t\t\tif (i >= MAX_LIGHTSTYLES)\n\t\t\t\tSys_Error (\"svc_lightstyle > MAX_LIGHTSTYLES\");\n\t\t\tstrlcpy (cl_lightstyle[i].map,  MSG_ReadString(), sizeof(cl_lightstyle[0].map));\n\t\t\tcl_lightstyle[i].length = strlen(cl_lightstyle[i].map);\n\t\t\tbreak;\n\n\t\tcase svc_sound:\n\t\t\tNQD_ParseStartSoundPacket();\n\t\t\tbreak;\n\n\t\tcase svc_stopsound:\n\t\t\ti = MSG_ReadShort();\n\t\t\tS_StopSound(i>>3, i&7);\n\t\t\tbreak;\n\n\t\tcase nq_svc_updatename:\n\t\t\tSbar_Changed ();\n\t\t\ti = MSG_ReadByte ();\n\t\t\tif (i >= nq_maxclients)\n\t\t\t\tHost_Error (\"CL_ParseServerMessage: svc_updatename > NQ_MAX_CLIENTS\");\n\t\t\tstrlcpy (cl.players[i].name, MSG_ReadString(), sizeof(cl.players[i].name));\n\t\t\tbreak;\n\n\t\tcase svc_updatefrags:\n\t\t\tSbar_Changed ();\n\t\t\ti = MSG_ReadByte ();\n\t\t\tif (i >= nq_maxclients)\n\t\t\t\tHost_Error (\"CL_ParseServerMessage: svc_updatefrags > NQ_MAX_CLIENTS\");\n\t\t\tcl.players[i].frags = MSG_ReadShort();\n\t\t\tbreak;\n\n\t\tcase nq_svc_updatecolors:\n\t\t\tNQD_ParseUpdatecolors ();\n\t\t\tbreak;\n\t\t\t\n\t\tcase nq_svc_particle:\n\t\t\tCL_ParseParticleEffect ();\n\t\t\tbreak;\n\n\t\tcase svc_spawnbaseline:\n\t\t\ti = MSG_ReadShort ();\n\t\t\tif (i >= NQ_MAX_EDICTS)\n\t\t\t\tHost_Error (\"svc_spawnbaseline: ent > MAX_EDICTS\");\n\t\t\tNQD_BumpEntityCount (i);\n\t\t\tCL_ParseBaseline (&cl_entities[i].baseline);\n\t\t\tbreak;\n\t\tcase svc_spawnstatic:\n\t\t\tCL_ParseStatic (false);\n\t\t\tbreak;\n\t\tcase svc_temp_entity:\n\t\t\tCL_ParseTEnt ();\n\t\t\tbreak;\n\n\t\tcase svc_setpause:\n\t\t\tif (MSG_ReadByte() != 0)\n\t\t\t\tcl.paused |= PAUSED_SERVER;\n\t\t\telse\n\t\t\t\tcl.paused &= ~PAUSED_SERVER;\n\n\t\t\tif (ISPAUSED)\n\t\t\t\tCDAudio_Pause ();\n\t\t\telse\n\t\t\t\tCDAudio_Resume ();\n\t\t\tbreak;\n\n\t\tcase nq_svc_signonnum:\n\t\t\ti = MSG_ReadByte ();\n\t\t\tif (i <= nq_signon)\n\t\t\t\tHost_Error (\"Received signon %i when at %i\", i, nq_signon);\n\t\t\tnq_signon = i;\n\t\t\tbreak;\n\n\t\tcase svc_killedmonster:\n\t\t\tcl.stats[STAT_MONSTERS]++;\n\t\t\tbreak;\n\n\t\tcase svc_foundsecret:\n\t\t\tcl.stats[STAT_SECRETS]++;\n\t\t\tbreak;\n\n\t\tcase svc_updatestat:\n\t\t\ti = MSG_ReadByte ();\n\t\t\tif (i < 0 || i >= MAX_CL_STATS)\n\t\t\t\tSys_Error (\"svc_updatestat: %i is invalid\", i);\n\t\t\tcl.stats[i] = MSG_ReadLong ();;\n\t\t\tbreak;\n\n\t\tcase svc_spawnstaticsound:\n\t\t\tCL_ParseStaticSound ();\n\t\t\tbreak;\n\n\t\tcase svc_cdtrack:\n\t\t\tcl.cdtrack = MSG_ReadByte ();\n\t\t\tMSG_ReadByte();\t\t// loop track (unused)\n\t\t\tif (nq_forcecdtrack != -1)\n\t\t\t\tCDAudio_Play ((byte)nq_forcecdtrack, true);\n\t\t\telse\n\t\t\t\tCDAudio_Play ((byte)cl.cdtrack, true);\n\t\t\tbreak;\n\n\t\tcase svc_intermission:\n\t\t\tcl.intermission = 1;\n\t\t\tcl.completed_time = cl.time;\n\t\t\tcl.solo_completed_time = cl.servertime;\n\t\t\tVectorCopy (nq_last_fixangle, cl.simangles);\n\t\t\tbreak;\n\n\t\tcase svc_finale:\n\t\t\tcl.intermission = 2;\n\t\t\tcl.completed_time = cl.time;\n\t\t\tcl.solo_completed_time = cl.servertime;\n\t\t\tSCR_CenterPrint (MSG_ReadString ());\n\t\t\tVectorCopy (nq_last_fixangle, cl.simangles);\n\t\t\tbreak;\n\n\t\tcase nq_svc_cutscene:\n\t\t\tcl.intermission = 3;\n\t\t\tcl.completed_time = cl.time;\n\t\t\tcl.solo_completed_time = cl.servertime;\n\t\t\tSCR_CenterPrint (MSG_ReadString ());\n\t\t\tVectorCopy (nq_last_fixangle, cl.simangles);\n\t\t\tbreak;\n\n\t\tcase svc_sellscreen:\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n\nvoid NQD_ReadPackets (void)\n{\n\textern qbool\thost_skipframe;\n\n\twhile (CL_GetNQDemoMessage()) {\n\t\tNQD_ParseServerMessage();\n\t}\n\t\n\tCL_SetSolidEntities();\n\n\t// If we're seeking, demotime is set to the target time: stop demotime from being advanced as normal\n\tif (cls.demoseeking)\n\t\thost_skipframe = true;\n}\n\n\nvoid NQD_StartPlayback (void)\n{\n\tbyte\tc;\n\tqbool\tneg = false;\n\n\textern qbool pb_ensure(void);\n\n\tpb_ensure(); // FIXME: zzzz, is it possible to put \"track parsing\" somewhere in CL_GetNQDemoMessage() ????\n\n\t// parse forced cd track\n\tfor (c = 0; c != '\\n'; ) {\n\t\tCL_Demo_Read(&c, 1, false);\n\n\t\tif (c == '-')\n\t\t\tneg = true;\n\t\telse\n\t\t\tnq_forcecdtrack = nq_forcecdtrack * 10 + (c - '0');\n\t}\n\tif (neg)\n\t\tnq_forcecdtrack = -nq_forcecdtrack;\n\n\tcl.spectator = false;\n\tnq_signon = 0;\n\tnq_mtime[0] = 0;\n\tnq_maxclients = 0;\n\tcl_entframecount = 0;\n}\n"
  },
  {
    "path": "src/cl_parse.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: cl_parse.c,v 1.135 2007-10-28 19:56:44 qqshka Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"cdaudio.h\"\n#include \"ignore.h\"\n#include \"fchecks.h\"\n#include \"config_manager.h\"\n#include \"utils.h\"\n#include \"localtime.h\"\n#include \"sbar.h\"\n#include \"textencoding.h\"\n#include \"vx_stuff.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"tp_triggers.h\"\n#include \"pmove.h\"\n#include \"stats_grid.h\"\n#include \"qsound.h\"\n#include \"menu.h\"\n#include \"keys.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"mvd_utils.h\"\n#include \"input.h\"\n#include \"qtv.h\"\n#include \"r_brushmodel_sky.h\"\n#include \"demo_spawnwarn.h\"\n#include \"central.h\"\n\nint CL_LoginImageId(const char* name);\n\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\nvoid IN_ServerSideWeaponSelectionResponse(const char* s);\n#endif\n\n#ifdef MVD_PEXT1_HIDDEN_MESSAGES\nstatic void CL_ParseAntilagPosition(int size);\nstatic void CL_ParseDemoInfo(int size);\nstatic void CL_ParseDemoWeapon(int size, qbool server_side);\nstatic void CL_ParseDamageDone(int size);\nstatic void CL_ParseDemoWeaponInstruction(int size);\nstatic void CL_ParseUserCommand(int size);\n#endif // MVD_PEXT1_HIDDEN_MESSAGES\n\nvoid R_TranslatePlayerSkin (int playernum);\n\nchar *svc_strings[] = {\n\t\"svc_bad\",\n\t\"svc_nop\",\n\t\"svc_disconnect\",\n\t\"svc_updatestat\",\n\t\"svc_version\",\t\t// [long] server version\n\t\"svc_setview\",\t\t// [short] entity number\n\t\"svc_sound\",\t\t// <see code>\n\t\"svc_time\",\t\t\t// [float] server time\n\t\"svc_print\",\t\t// [string] null terminated string\n\t\"svc_stufftext\",\t// [string] stuffed into client's console buffer\n\t\t\t\t\t\t// the string should be \\n terminated\n\t\"svc_setangle\",\t\t// [vec3] set the view angle to this absolute value\n\n\t\"svc_serverdata\",\t// [long] version ...\n\t\"svc_lightstyle\",\t// [byte] [string]\n\t\"svc_updatename\",\t// [byte] [string]\n\t\"svc_updatefrags\",\t// [byte] [short]\n\t\"svc_clientdata\",\t// <shortbits + data>\n\t\"svc_stopsound\",\t// <see code>\n\t\"svc_updatecolors\",\t// [byte] [byte]\n\t\"svc_particle\",\t\t// [vec3] <variable>\n\t\"svc_damage\",\t\t// [byte] impact [byte] blood [vec3] from\n\n\t\"svc_spawnstatic\",\n\t\"OBSOLETE svc_spawnbinary\",\n\t\"svc_spawnbaseline\",\n\n\t\"svc_temp_entity\",\t// <variable>\n\t\"svc_setpause\",\n\t\"svc_signonnum\",\n\t\"svc_centerprint\",\n\t\"svc_killedmonster\",\n\t\"svc_foundsecret\",\n\t\"svc_spawnstaticsound\",\n\t\"svc_intermission\",\n\t\"svc_finale\",\n\n\t\"svc_cdtrack\",\n\t\"svc_sellscreen\",\n\n\t\"svc_smallkick\",\n\t\"svc_bigkick\",\n\n\t\"svc_updateping\",\n\t\"svc_updateentertime\",\n\n\t\"svc_updatestatlong\",\n\t\"svc_muzzleflash\",\n\t\"svc_updateuserinfo\",\n\t\"svc_download\",\n\t\"svc_playerinfo\",\n\t\"svc_nails\",\n\t\"svc_choke\",\n\t\"svc_modellist\",\n\t\"svc_soundlist\",\n\t\"svc_packetentities\",\n \t\"svc_deltapacketentities\",\n\t\"svc_maxspeed\",\n\t\"svc_entgravity\",\n\n\t\"svc_setinfo\",\n\t\"svc_serverinfo\",\n\t\"svc_updatepl\",\n\t\"svc_nails2\",\n\t\"NEW PROTOCOL\",\n\t\"NEW PROTOCOL\",\n\t\"NEW PROTOCOL\",\n\t\"NEW PROTOCOL\",\n\t\"NEW PROTOCOL\",\n\t\"NEW PROTOCOL\",\n\t\"NEW PROTOCOL\",\n\t\"NEW PROTOCOL\",\n\t\"NEW PROTOCOL\",\n\t\"NEW PROTOCOL\",\n\t\"NEW PROTOCOL\",\n\t\"NEW PROTOCOL\"\n};\n\nconst int num_svc_strings = sizeof(svc_strings)/sizeof(svc_strings[0]);\n\n//=========================================================\n// Cl_Messages, just some simple network statistics/profiling\n//=========================================================\n\n#define NUMMSG 256 // for svc_xxx used one byte, so its in range...\n\ntypedef struct cl_message_s\n{\n\tint msgs;\n\tint size;\n\n\tint svc; // well, its helpful after qsort\n} cl_message_t;\n\nstatic cl_message_t cl_messages[NUMMSG];\n\nstatic void CL_Messages_f(void);\nstatic void CL_InitialiseDemoMessageIfRequired(void);\n\nvoid Cl_Messages_Init(void)\n{\n\tint i;\n\n\tmemset(cl_messages, 0, sizeof(cl_messages));\n\n\tfor (i = 0; i < NUMMSG; i++)\n\t\tcl_messages[i].svc = i; // well, its helpful after qsort\n\n\tCmd_AddCommand (\"cl_messages\", CL_Messages_f);\n}\n\nstatic int CL_Messages_qsort(const void *a, const void *b)\n{\n\tcl_message_t *msg1 = (cl_message_t*)a;\n\tcl_message_t *msg2 = (cl_message_t*)b;\n\n\tif ( msg1->size < msg2->size )\n\t\treturn -1;\n\n\tif ( msg1->size > msg2->size )\n\t\treturn 1;\n\n   return 0;\n}\n\nstatic void CL_Messages_f(void)\n{\n\tcl_message_t messages[NUMMSG]; // local copy of cl_messages[] for qsorting\n\n\tint i, svc, total;\n\tchar *svc_name;\n\n\tmemcpy(messages, cl_messages, sizeof(messages)); // copy it\n\tqsort(messages, NUMMSG, sizeof(messages[0]), CL_Messages_qsort); // qsort it\n\n\tCom_Printf(\"Received msgs:\\n\");\n\n\tfor (i = 0, total = 0; i < NUMMSG; i++)\n\t{\n\t\tif (messages[i].msgs < 1)\n\t\t\tcontinue;\n\n\t\tsvc = messages[i].svc;\n\n\t\tif (svc < 0 || svc >= NUMMSG) {\n\t\t\tSys_Error(\"CL_Messages_f: svc < 0 || svc >= NUMMSG\");\n\t\t\treturn;\n\t\t}\n\n\t\tsvc_name = ( svc < num_svc_strings ? svc_strings[svc] : \"unknown\" );\n\n\t\tCom_Printf(\"%2d:%s: %d msgs: %0.2fk\\n\", svc, svc_name, messages[i].msgs, (float)(messages[i].size)/1024);\n\n\t\ttotal += messages[i].size;\n\t}\n\n\tCom_Printf(\"Total size: %d\\n\", total);\n}\n\n//=============================================================================\n\npacket_info_t network_stats[NETWORK_STATS_SIZE];\n\nint packet_latency[NET_TIMINGS];\n\nint CL_CalcNet (void) \n{\n\tint a, i, j, lost, packetcount;\n\tframe_t\t*frame;\n\n\tstatic cvar_t * netgraph_inframes = NULL;\n\textern hud_t * hud_netgraph;\n\n\tstatic int last_calculated_outgoing;\n\tstatic int last_calculated_incoming;\n\tstatic int last_lost;\n\n\tif (last_calculated_incoming == cls.netchan.incoming_sequence  &&\n\t    last_calculated_outgoing == cls.netchan.outgoing_sequence)\n\t\treturn last_lost;\n\n\tlast_calculated_outgoing = cls.netchan.outgoing_sequence;\n\tlast_calculated_incoming = cls.netchan.incoming_sequence;\n\n\tif (netgraph_inframes == NULL)\n\t    netgraph_inframes = HUD_FindVar(hud_netgraph, \"inframes\");\n\n\tfor (i = cls.netchan.outgoing_sequence-UPDATE_BACKUP + 1; i <= cls.netchan.outgoing_sequence; i++) \n\t{\n\t\tframe = &cl.frames[i & UPDATE_MASK];\n\t        j = i & NETWORK_STATS_MASK;\n\t\tif (frame->receivedtime == -1)\n\t\t{\n\t\t\tpacket_latency[i & NET_TIMINGSMASK] = 9999;\t// dropped\n\t\t\tnetwork_stats[j].status = packet_dropped;\n\t\t}\n\t\telse if (frame->receivedtime == -2) \n\t\t{\n\t\t\tpacket_latency[i & NET_TIMINGSMASK] = 10000;\t// choked\n\t\t\tnetwork_stats[j].status = packet_choked;\n\t\t}\n\t\telse if (frame->receivedtime == -3)\n\t\t{\n\t\t\tpacket_latency[i & NET_TIMINGSMASK] = -1;\t// choked by c2spps\n\t\t\tnetwork_stats[j].status = packet_netlimit;\n\t\t}\n\t\telse if (frame->invalid) \n\t\t{\n\t\t\tpacket_latency[i & NET_TIMINGSMASK] = 9998;\t// invalid delta\n\t\t\tnetwork_stats[j].status = packet_delta;\n\t\t}\n\t\telse \n\t\t{\n\t\t\tdouble l;\n\t\t\tif (netgraph_inframes->value)      // [frames]\n\t\t\t\tl = 2*(frame->seq_when_received-i);\n\t\t\telse                                // [miliseconds]\n\t\t\t\tl = min((frame->receivedtime - frame->senttime)*1000, 1000);\n\n\t\t\tpacket_latency[i&NET_TIMINGSMASK] = (int)l;\n\t\t\tnetwork_stats[j].status = packet_ok;\n\t\t}\n\t}\n\n\tlost = packetcount = 0;\n\tfor (a = 0; a < NET_TIMINGS; a++)\t\n\t{\n\t\t// fix for packetloss on high ping\n\t\tif (a < UPDATE_BACKUP && (cls.realtime -\n\t\t\tcl.frames[(cls.netchan.outgoing_sequence-a)&UPDATE_MASK].senttime) < cls.latency)\n\t\t\tcontinue;\n\n\t\ti = (cls.netchan.outgoing_sequence-a) & NET_TIMINGSMASK;\n\t\tif (packet_latency[i] == 9999)\n\t\t\tlost++;\n\t\tif (packet_latency[i] != -1)\t// don't count packets choked by c2spps\n\t\t\tpacketcount++;\n\t}\n\tlast_lost = packetcount ? lost * 100 / packetcount : 100;\n\treturn last_lost;\n}\n\n// More network statistics\nint CL_CalcNetStatistics(\n            /* in */\n            float period,           // period of time\n            packet_info_t *samples, // statistics table\n            int num_samples,        // table size\n            /* out */\n            net_stat_result_t *res)\n{\n    int i, p, q;   // calc fom p to q\n\n    float ping_min, ping_max, ping_avg, ping_dev, ping_sum, ping_dev_sum;\n    float f_min, f_max, f_avg, f_dev, f_sum, f_dev_sum;\n    int lost_lost, lost_rate, lost_delta, lost_netlimit;\n    int size_sent, size_received;\n    int samples_received, samples_sent, samples_delta;\n\n    if (cls.netchan.outgoing_sequence - cls.netchan.incoming_sequence  >  NETWORK_STATS_SIZE/2)\n        return (res->samples = 0);\n\n    // find boundaries\n    q = cls.netchan.incoming_sequence - 1;\n    p = q - 1;\n    while (p > cls.netchan.outgoing_sequence - NETWORK_STATS_SIZE + 1  &&\n           samples[q&NETWORK_STATS_MASK].senttime - samples[p&NETWORK_STATS_MASK].senttime < period)\n\t{\n        p--;\n\t}\n\n    // init values\n    samples_sent = 0;\n    samples_received = 0;\n    samples_delta = 0;  // packets with delta compression applied\n\n    ping_sum = 0;\n    ping_min =  99999999;\n    ping_max = -99999999;\n    ping_dev_sum = 0;\n\n    f_sum = 0;\n    f_min =  99999999;\n    f_max = -99999999;\n    f_dev_sum = 0;\n\n    lost_lost = 0;\n    lost_rate = 0;\n    lost_netlimit = 0;\n    lost_delta = 0;\n\n    size_sent = 0;\n    size_received = 0;\n\n    for (i=p; i < q; i++)\n    {\n        int a = i & NETWORK_STATS_MASK;\n\n        if (samples[a].status == packet_netlimit)\n        {\n            // not even sent\n            lost_netlimit++;\n            continue;\n        }\n\n        // packet was sent\n        samples_sent++;\n\n        size_sent += samples[a].sentsize;\n\n        switch (samples[a].status)\n        {\n\t\t\tcase packet_delta:\n\t\t\t\tlost_delta++;\n\t\t\t\tsamples_delta++;\n\t\t\t\tbreak;\n\t\t\tcase packet_choked:\n\t\t\t\tlost_rate++;\n\t\t\t\tbreak;\n\t\t\tcase packet_dropped:\n\t\t\t\tlost_lost++;\n\t\t\t\tbreak;\n\t\t\tcase packet_ok:\n\t\t\t\t// packet received\n\t\t\t\t{\n\t\t\t\t\tfloat ping;\n\t\t\t\t\tint frames;\n\n\t\t\t\t\tsamples_received++;\n\t\t\t\t\tframes = samples[a].seq_diff;\n\t\t\t\t\tping = 1000*(samples[a].receivedtime - samples[a].senttime);\n\n\t\t\t\t\tif (ping < ping_min)    ping_min = ping;\n\t\t\t\t\tif (ping > ping_max)    ping_max = ping;\n\t\t\t\t\tif (frames < f_min)     f_min = frames;\n\t\t\t\t\tif (frames > f_max)     f_max = frames;\n\n\t\t\t\t\tping_sum += ping;\n\t\t\t\t\tf_sum += frames;\n\n\t\t\t\t\tsize_received += samples[a].receivedsize;\n\n\t\t\t\t\tif (samples[a].delta)\n\t\t\t\t\t\tsamples_delta++;\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tbreak;\n        }\n\n        // end of loop\n    }\n\n    if (samples_sent <= 0  ||  samples_received <= 0)\n        return (res->samples = 0);\n\n    ping_avg = ping_sum / samples_received;\n    f_avg = f_sum / samples_received;\n\n    // loop again to calc standard deviation\n    for (i=p; i < q; i++)\n    {\n        int a = i & NETWORK_STATS_MASK;\n\n        if (samples[a].status == packet_ok)\n        {\n            float ping;\n            int frames;\n\n            frames = samples[a].seq_diff;\n            ping = 1000*(samples[a].receivedtime - samples[a].senttime);\n\n            ping_dev_sum += (ping - ping_avg) * (ping - ping_avg);\n            f_dev_sum += (frames - f_avg) * (frames - f_avg);\n        }\n    }\n\n    ping_dev = sqrt(ping_dev_sum / samples_received);\n    f_dev = sqrt(f_dev_sum / samples_received);\n\n    // fill-in result struct\n    res->ping_min = ping_min;\n    res->ping_max = ping_max;\n    res->ping_avg = ping_avg;\n    res->ping_dev = ping_dev;\n\n    res->ping_f_min = f_min;\n    res->ping_f_max = f_max;\n    res->ping_f_avg = f_avg;\n    res->ping_f_dev = f_dev;\n\n    res->lost_netlimit = lost_netlimit * 100.0 / (q-p);\n    res->lost_lost     = lost_lost     * 100.0 / samples_sent;\n    res->lost_rate     = lost_rate     * 100.0 / samples_sent;\n    res->bandwidth_in  = lost_delta    * 100.0 / samples_sent;\n\n    res->size_out = size_sent      / (float)samples_sent;\n    res->size_in  = size_received  / (float)samples_received;\n\n    res->bandwidth_out = size_sent      / period;\n    res->bandwidth_in  = size_received  / period;\n\n    res->delta = (samples_delta > 0) ? 1 : 0;\n    res->samples = q-p;\n    return res->samples;\n}\n\n//=============================================================================\n\nqbool CL_Download_Accept(const char *filename);\n\n// Returns true if the file exists, otherwise it attempts to start a download from the server.\nqbool CL_CheckOrDownloadFile(char *filename)\n{\n\tif (!CL_Download_Accept(filename))\n\t{\n\t\treturn true;\n\t}\n\n\t// Can't download when playback, except qtv which support download\n\tif (cls.demoplayback)\n\t{\n\t\tif (cls.mvdplayback == QTV_PLAYBACK)\n\t\t{\n\t\t\tif (!(cls.qtv_ezquake_ext & QTV_EZQUAKE_EXT_DOWNLOAD)) \n\t\t\t{\n\t\t\t\tCom_Printf (\"Unable to download %s, this QTV does't support download\\n\", filename);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\treturn true;\n\t}\n\n\tif (cls.state < ca_connected) \n\t{\n\t\tCom_DPrintf (\"Unable to download %s, not connected\\n\", filename);\n\t\treturn true;\n\t}\n\n\tsnprintf (cls.downloadname, sizeof(cls.downloadname), \"%s/%s\", cls.gamedir, filename);\n\tCom_Printf (\"Downloading %s...\\n\", filename);\n\n\t// download to a temp name, and only rename\n\t// to the real name when done, so if interrupted\n\t// a runt file wont be left\n\n\tcls.downloadmethod    = DL_QW; // by default its DL_QW, if server support DL_QWCHUNKED it will be changed.\n\tcls.downloadstarttime = Sys_DoubleTime();\n\n\tCOM_StripExtension (cls.downloadname, cls.downloadtempname, sizeof(cls.downloadtempname));\n\tstrlcat (cls.downloadtempname, \".tmp\", sizeof(cls.downloadtempname));\n\n\tif (cls.mvdplayback == QTV_PLAYBACK) \n\t{\n\t\tQTV_Cmd_Printf(QTV_EZQUAKE_EXT_DOWNLOAD, \"download \\\"%s\\\"\", filename);\n\t}\n\telse \n\t{\n\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\tMSG_WriteString (&cls.netchan.message, va(\"download \\\"%s\\\"\", filename));\n\t}\n\n\tcls.downloadnumber++;\n\n\treturn false;\n}\n\nvoid CL_FindModelNumbers (void) \n{\n\tint i, j;\n\n\tfor (i = 0; i < cl_num_modelindices; i++)\n\t\tcl_modelindices[i] = -1;\n\n\tfor (i = 0; i < MAX_MODELS; i++) \n\t{\n\t\tfor (j = 0; j < cl_num_modelindices; j++) \n\t\t{\n\t\t\tif (!strcmp(cl_modelnames[j], cl.model_name[i]))\n\t\t\t{\n\t\t\t\tcl_modelindices[j] = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid CL_ProxyEnter (void) \n{\n\tif (!strcmp(cl.levelname, \"Qizmo menu\") ||\t// qizmo detection\n\t\tstrstr(cl.serverinfo, \"*QTV\")) \t\t\t// fteqtv detection\n\t{\n\t\t// If we are connected only to the proxy frontend\n\t\t// we presume that the menu is on.\n\t\tM_EnterProxyMenu();\n\n\t} \n\telse if (key_dest == key_menu && m_state == m_proxy) \n\t{\n\t\t// This happens when we connect to a server using a proxy.\n\t\tM_LeaveMenus();\n\t}\n\n\t// If we used console to connect to a server via proxy,\n\t// cancel return to the proxy menu, because it's not there.\n\tif (key_dest_beforecon == key_menu)\n\t\tkey_dest_beforecon = key_game;\n}\n\nstatic void CL_TransmitModelCrc (int index, char *info_key)\n{\n\tif (index != -1) \n\t{\n\t\tstruct model_s *model = cl.model_precache[index];\n\t\tunsigned short crc = model->crc;\n\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\tMSG_WriteString (&cls.netchan.message, va(\"setinfo %s %d\", info_key, (int) crc));\n\t\tInfo_SetValueForKey(cls.userinfo, info_key, va(\"%d\", (int) crc), MAX_INFO_STRING);\n\t}\n}\n\nvoid CL_Prespawn (void)\n{\n\tcl.worldmodel = cl.model_precache[1];\n\tif (!cl.worldmodel)\n\t\tHost_Error (\"Model_NextDownload: NULL worldmodel\");\n\n\tCL_FindModelNumbers ();\n\tR_NewMap (false);\n\tTP_NewMap();\n\tMT_NewMap();\n\tStats_NewMap();\n\tCL_ProxyEnter();\n\tMVD_Stats_Cleanup();\n\n\t// Reset the status grid.\n\tStatsGrid_Remove(&stats_grid);\n\tStatsGrid_ResetHoldItems();\n\tHUD_NewMap();\n\tHunk_Check(); // make sure nothing is hurt\n\n\tCL_TransmitModelCrc (cl_modelindices[mi_player], \"pmodel\");\n\tCL_TransmitModelCrc (cl_modelindices[mi_eyes], \"emodel\");\n\n\tCL_SpawnWarn_LoadPoints();\n\n#if 0\n//TEI: loading entitys from map, at clientside,\n// will be usefull to locate more eyecandy and cameras\n\tif (cl.worldmodel->entities)\n\t{\n\t\tCL_PR_LoadProgs();\n\t\tCL_ED_LoadFromFile (cl.worldmodel->entities);\n\t}\n#endif\n\n\t// done with modellist, request first of static signon messages, in case of qtv it different\n\tif (cls.mvdplayback == QTV_PLAYBACK)\n\t{\n\t\tQTV_Cmd_Printf(QTV_EZQUAKE_EXT_DOWNLOAD, \"qtvspawn %i\", cl.servercount);\n\t}\n\telse \n\t{\n\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\tMSG_WriteString (&cls.netchan.message, va(\"prespawn %i 0 %i\", cl.servercount, cl.map_checksum2));\n\t}\n}\n\n/*\n=================\nVWepModel_NextDownload\n=================\n*/\nvoid CL_ParseVWepPrecache (char *str);\nvoid VWepModel_NextDownload (void)\n{\n\tint\t\ti;\n\textern cvar_t cl_novweps;\n\n\tif (((!(cl.z_ext & Z_EXT_VWEP) || !cl.vw_model_name[0][0]) && !cls.mvdplayback)\n\t|| cl_novweps.value) \n\t{\n\t\t// no vwep support, go straight to prespawn\n\t\tCL_Prespawn ();\n\t\treturn;\n\t}\n\n\tif (cls.mvdplayback && cls.downloadnumber == 0)\n\t{\n\t\tCL_ParseVWepPrecache (\"//vwep vwplayer w_axe w_shot w_shot2 w_nail w_nail2 w_rock w_rock2 w_light\");\n\t}\n\n\tif (cls.downloadnumber == 0)\n\t{\n\t\tif (!com_serveractive || developer.value)\n\t\t\tCom_DPrintf (\"Checking vwep models...\\n\");\n\t\t//cls.downloadnumber = 0;\n\t}\n\n\tcls.downloadtype = dl_vwep_model;\n\tfor ( ; cls.downloadnumber < MAX_VWEP_MODELS; cls.downloadnumber++)\n\t{\n\t\tif (!cl.vw_model_name[cls.downloadnumber][0] ||\n\t\t\tcl.vw_model_name[cls.downloadnumber][0] == '-')\n\t\t\tcontinue;\n\t\tif (!CL_CheckOrDownloadFile(cl.vw_model_name[cls.downloadnumber]))\n\t\t\treturn;\t\t// started a download\n\t}\n\n\tfor (i = 0; i < MAX_VWEP_MODELS; i++)\n\t{\n\t\tif (!cl.vw_model_name[i][0])\n\t\t\tcontinue;\n\n\t\tif (strcmp(cl.vw_model_name[i], \"-\"))\n\t\t\tcl.vw_model_precache[i] = Mod_ForName (cl.vw_model_name[i], false);\n\n\t\tif (!cl.vw_model_precache[i])\n\t\t{\n\t\t\t// never mind\n\t\t\t// Com_Printf (\"Warning: model %s could not be found\\n\", cl.vw_model_name[i]);\n\t\t}\n\t}\n\n\tif (!strcmp(cl.vw_model_name[0], \"-\") || cl.vw_model_precache[0])\n\t\tcl.vwep_enabled = true;\n\telse \n\t{\n\t\t// if the vwep player model is required but not present,\n\t\t// don't enable vwep support\n\t}\n\n\t// all done\n\tCL_Prespawn ();\n}\n\nvoid Model_NextDownload (void) \n{\n\tint\ti;\n\tchar *s;\n\tchar mapname[MAX_QPATH];\n\n\tif (cls.downloadnumber == 0) \n\t{\n\t\tif (!com_serveractive || developer.value)\n\t\t\tCom_DPrintf (\"Checking models...\\n\");\n\t\tcls.downloadnumber = 1;\n\t}\n\n\tcls.downloadtype = dl_model;\n\tfor ( ; cl.model_name[cls.downloadnumber][0]; cls.downloadnumber++)\t\n\t{\n\t\ts = cl.model_name[cls.downloadnumber];\n\t\tif (s[0] == '*')\n\t\t\tcontinue; // inline brush model\n\n\t\tif (!CL_CheckOrDownloadFile(s))\n\t\t\treturn;\t// started a download\n\t}\n\n\tcl.clipmodels[1] = CM_LoadMap (cl.model_name[1], true, NULL, &cl.map_checksum2);\n\tCOM_StripExtension (COM_SkipPath(cl.model_name[1]), mapname, sizeof(mapname));\n\tcl.map_checksum2 = Com_TranslateMapChecksum (mapname, cl.map_checksum2);\n\tR_NewMapPreLoad();\n\n\tfor (i = 1; i < MAX_MODELS; i++) {\n\t\tif (!cl.model_name[i][0]) {\n\t\t\tbreak;\n\t\t}\n\n\t\tcl.model_precache[i] = Mod_ForName(cl.model_name[i], false);\n\n\t\tif (!cl.model_precache[i]) {\n\t\t\tCom_Printf(\"\\n&cf22Couldn't load model:&r %s\\n\", cl.model_name[i]);\n\t\t\tHost_EndGame();\n\t\t\treturn;\n\t\t}\n\n\t\tif (cl.model_name[i][0] == '*') {\n\t\t\tcl.clipmodels[i] = CM_InlineModel(cl.model_name[i]);\n\t\t}\n\t}\n\n\t// Done with normal models, request vwep models if necessary\n\tcls.downloadtype = dl_vwep_model;\n\tcls.downloadnumber = 0;\n\tVWepModel_NextDownload ();\n}\n\nvoid Sound_NextDownload (void) \n{\n\tchar *s;\n\tint i;\n\n\tif (cls.downloadnumber == 0)\n\t{\n\t\tif (!com_serveractive || developer.value)\n\t\t\tCom_DPrintf (\"Checking sounds...\\n\");\n\t\tcls.downloadnumber = 1;\n\t}\n\n\tcls.downloadtype = dl_sound;\n\tfor ( ; cl.sound_name[cls.downloadnumber][0]; cls.downloadnumber++)\t\n\t{\n\t\ts = cl.sound_name[cls.downloadnumber];\n\t\tif (!CL_CheckOrDownloadFile(va(\"sound/%s\",s)))\n\t\t\treturn;\t\t// started a download\n\t}\n\n\tfor (i = 1; i < MAX_SOUNDS; i++) \n\t{\n\t\tif (!cl.sound_name[i][0])\n\t\t\tbreak;\n\t\tcl.sound_precache[i] = S_PrecacheSound (cl.sound_name[i]);\n\t}\n\n\t// Done with sound downloads, go for models\n\tcls.downloadnumber = 0;\n\tcls.downloadtype = dl_model;\n\tModel_NextDownload ();\n\n}\n\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\n//\n// FTE's chunked download\n//\n\nextern void CL_RequestNextDownload (void);\n\n#define MAXBLOCKS 1024\t// Must be power of 2\n#define DLBLOCKSIZE 1024\n\nint chunked_download_number = 0; // Never reset, bumped up.\n\nint downloadsize;\nint receivedbytes;\nint recievedblock[MAXBLOCKS];\nint firstblock;\nint blockcycle;\n\nint CL_RequestADownloadChunk(void)\n{\n\tint i;\n\tint b;\n\n\tif (cls.downloadmethod != DL_QWCHUNKS) // Paranoia!\n\t\tHost_Error(\"download not initiated\\n\");\n\n\tfor (i = 0; i < MAXBLOCKS; i++)\n\t{\n\t\tblockcycle++;\n\n\t\tb = ((blockcycle) & (MAXBLOCKS-1)) + firstblock;\n\n\t\tif (!recievedblock[b&(MAXBLOCKS-1)]) // Don't ask for ones we've already got.\n\t\t{\n\t\t\tif (b >= (downloadsize+DLBLOCKSIZE-1)/DLBLOCKSIZE)\t// Don't ask for blocks that are over the size of the file.\n\t\t\t\tcontinue;\n\t\t\treturn b;\n\t\t}\n\t}\n\n\treturn -1;\n}\n\nvoid CL_SendChunkDownloadReq(void)\n{\n\textern cvar_t cl_chunksperframe;\n\tint i, j, chunks;\n\t\n\tchunks = bound(1, cl_chunksperframe.integer, 30);\n\n\tfor (j = 0; j < chunks; j++)\n\t{\n\t\tif (cls.downloadmethod != DL_QWCHUNKS)\n\t\t\treturn;\n\n\t\ti = CL_RequestADownloadChunk();\n\t\t// i < 0 mean client complete download, let server know\n\t\t// qqshka: download percent optional, server does't really require it, that my extension, hope does't fuck up something\n\n\t\tif (i < 0)\n\t\t{\n\t\t\tif (strstr(Info_ValueForKey(cl.serverinfo, \"*version\"), \"MVDSV\"))\n\t\t\t\tCL_SendClientCommand(true, \"nextdl %d %d %d\", i, cls.downloadpercent, chunked_download_number);\n\t\t\telse\n\t\t\t\tCL_SendClientCommand(true, \"stopdownload\");\n\n\t\t\tcls.downloadpercent = 100;\n\t\t\tCL_FinishDownload(); // this also request next dl\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCL_SendClientCommand(false, \"nextdl %d %d %d\", i, cls.downloadpercent, chunked_download_number);\n\t\t}\n\t}\n}\n\nvoid CL_ParseDownload (void);\n\nvoid CL_Parse_OOB_ChunkedDownload(void)\n{\n\tint j;\n\n\tfor ( j = 0; j < sizeof(\"\\\\chunk\")-1; j++ )\n\t\tMSG_ReadByte ();\n\n\t//\n\t// qqshka: well, this is evil.\n\t// In case of when one file completed download and next started\n\t// here may be some packets which travel via network,\n\t// so we got packets from different file, that mean we may assemble wrong data,\n\t// need somehow discard such packets, i have no idea how, so adding at least this check.\n\t//\n\n\tif (chunked_download_number != MSG_ReadLong ())\n\t{\n\t\tCom_DPrintf(\"Dropping OOB chunked message, out of sequence\\n\");\n\t\treturn;\n\t}\n\n\tif (MSG_ReadByte() != svc_download)\n\t{\n\t\tCom_DPrintf(\"Something wrong in OOB message and chunked download\\n\");\n\t\treturn;\n\t}\n\n\tCL_ParseDownload ();\n}\n\nvoid CL_ParseChunkedDownload(void)\n{\n\tchar *svname;\n\tint totalsize;\n\tint chunknum;\n\tchar data[DLBLOCKSIZE];\n\tdouble tm;\n\n\tchunknum = MSG_ReadLong();\n\tif (chunknum < 0)\n\t{\n\t\ttotalsize = MSG_ReadLong();\n\t\tsvname    = MSG_ReadString();\n\n\t\tif (cls.download) \n\t\t{ \n\t\t\t// Ensure FILE is closed\n\t\t\tif (totalsize != -3) // -3 = dl stopped, so this known issue, do not warn\n\t\t\t\tCom_Printf (\"cls.download shouldn't have been set\\n\");\n\n\t\t\tfclose (cls.download);\n\t\t\tcls.download = NULL;\n\t\t\tcls.downloadpercent = 0;\n\t\t}\n\n\t\tif (cls.demoplayback)\n\t\t\treturn;\n\n\t\tif (totalsize < 0)\n\t\t{\n\t\t\tswitch (totalsize)\n\t\t\t{\n\t\t\t\tcase -3: Com_DPrintf(\"Server cancel downloading file %s\\n\", svname);\t\t\tbreak;\n\t\t\t\tcase -2: Com_Printf(\"Server permissions deny downloading file %s\\n\", svname);\tbreak;\n\t\t\t\tdefault: Com_Printf(\"Couldn't find file %s on the server\\n\", svname);\t\t\tbreak;\n\t\t\t}\n\n\t\t\tCL_FinishDownload(); // this also request next dl\n\t\t\treturn;\n\t\t}\n\n\t\tif (cls.downloadmethod == DL_QWCHUNKS)\n\t\t\tHost_Error(\"Received second download - \\\"%s\\\"\\n\", svname);\n\n// FIXME: damn, fixme!!!!!\n//\t\tif (strcasecmp(cls.downloadname, svname))\n//\t\t\tHost_Error(\"Server sent the wrong download - \\\"%s\\\" instead of \\\"%s\\\"\\n\", svname, cls.downloadname);\n\n\t\t// Start the new download\n\t\tFS_CreatePath (cls.downloadtempname);\n\n\t\tif ( !(cls.download = fopen (cls.downloadtempname, \"wb\")) ) \n\t\t{\n\t\t\tCom_Printf (\"Failed to open %s\\n\", cls.downloadtempname);\n\t\t\tCL_FinishDownload(); // This also requests next dl.\n\t\t\treturn;\n\t\t}\n\n\t\tcls.downloadmethod  = DL_QWCHUNKS;\n\t\tcls.downloadpercent = 0;\n\n\t\tchunked_download_number++;\n\n\t\tdownloadsize        = totalsize;\n\n\t\tfirstblock    = 0;\n\t\treceivedbytes = 0;\n\t\tblockcycle    = -1;\t//so it requests 0 first. :)\n\t\tmemset(recievedblock, 0, sizeof(recievedblock));\n\t\treturn;\n\t}\n\n\tMSG_ReadData(data, DLBLOCKSIZE);\n\n\tif (!cls.download) \n\t{ \n\t\treturn;\n\t}\n\n\tif (cls.downloadmethod != DL_QWCHUNKS)\n\t\tHost_Error(\"cls.downloadmethod != DL_QWCHUNKS\\n\");\n\n\tif (cls.demoplayback)\n\t{\t\n\t\t// Err, yeah, when playing demos we don't actually pay any attention to this.\n\t\treturn;\n\t}\n\n\tif (chunknum < firstblock)\n\t{\n\t\treturn;\n\t}\n\n\tif (chunknum - firstblock >= MAXBLOCKS)\n\t{\n\t\treturn;\n\t}\n\n\tif (recievedblock[chunknum&(MAXBLOCKS-1)])\n\t{\n\t\treturn;\n\t}\n\n\treceivedbytes += DLBLOCKSIZE;\n\trecievedblock[chunknum&(MAXBLOCKS-1)] = true;\n\n\twhile(recievedblock[firstblock&(MAXBLOCKS-1)])\n\t{\n\t\trecievedblock[firstblock&(MAXBLOCKS-1)] = false;\n\t\tfirstblock++;\n\t}\n\n\tfseek(cls.download, chunknum * DLBLOCKSIZE, SEEK_SET);\n\tif (downloadsize - chunknum * DLBLOCKSIZE < DLBLOCKSIZE)\t//final block is actually meant to be smaller than we recieve.\n\t\tfwrite(data, 1, downloadsize - chunknum * DLBLOCKSIZE, cls.download);\n\telse\n\t\tfwrite(data, 1, DLBLOCKSIZE, cls.download);\n\n\tcls.downloadpercent = receivedbytes/(float)downloadsize*100;\n\n\ttm = Sys_DoubleTime() - cls.downloadstarttime; // how long we dl-ing\n\tcls.downloadrate = (tm ? receivedbytes / 1024 / tm : 0); // some average dl speed in KB/s\n}\n\n#endif // FTE_PEXT_CHUNKEDDOWNLOADS\n\nvoid CL_RequestNextDownload (void) \n{\n\tswitch (cls.downloadtype)\n\t{\n\t\tcase dl_single:\n\t\t\tbreak;\n\t\tcase dl_skin:\n\t\t\tSkin_NextDownload ();\n\t\t\tbreak;\n\t\tcase dl_model:\n\t\t\tModel_NextDownload ();\n\t\t\tbreak;\n\t\tcase dl_vwep_model:\n\t\t\tVWepModel_NextDownload ();\n\t\t\tbreak;\n\t\tcase dl_sound:\n\t\t\tSound_NextDownload ();\n\t\t\tbreak;\n\t\tcase dl_none:\n\t\tdefault:\n\t\t\tCom_DPrintf (\"Unknown download type.\\n\");\n\t}\n}\n\nvoid CL_FinishDownload(void)\n{\n\tif (cls.download) {\n\t\tfclose (cls.download);\n\n\t\tif (cls.downloadpercent == 100) {\n\t\t\tCom_DPrintf(\"Download took %.1f seconds\\n\", Sys_DoubleTime() - cls.downloadstarttime);\n\n\t\t\t// rename the temp file to its final name\n\t\t\tif (strcmp(cls.downloadtempname, cls.downloadname))\n\t\t\t\tif (rename(cls.downloadtempname, cls.downloadname))\n\t\t\t\t\tCom_Printf (\"Failed to rename %s to %s.\\n\",\tcls.downloadtempname, cls.downloadname);\n\t\t} else {\n\t\t\t/* If download didn't complete, remove the unfinished leftover .tmp file ... */\n\t\t\tunlink(cls.downloadtempname);\n\t\t}\n\t}\n\n\tcls.download = NULL;\n\tcls.downloadpercent = 0;\n\tcls.downloadmethod = DL_NONE;\n\n\t// VFS-FIXME: D-Kure: Surely there is somewhere better for this in fs.c\n\tfilesystemchanged = true;\n\n\t// get another file if needed\n\n\tif (cls.state != ca_disconnected)\n\t\tCL_RequestNextDownload ();\n}\n\n// A download message has been received from the server\nvoid CL_ParseDownload (void)\n{\n\tint size, percent;\n\n\tdouble current = Sys_DoubleTime();\n\tstatic double time = 0;\n\tstatic int s = 0;\n\n\t#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tif (cls.fteprotocolextensions & FTE_PEXT_CHUNKEDDOWNLOADS)\n\t{\n\t\tCL_ParseChunkedDownload();\n\t\treturn;\n\t}\n\t#endif // PFTE_PEXT_CHUNKEDDOWNLOADS\n\n\tif (cls.downloadmethod != DL_QW)\n\t\tHost_Error(\"cls.downloadmethod != DL_QW\\n\");\n\n\t// Read the data\n\tsize = MSG_ReadShort ();\n\tpercent = MSG_ReadByte ();\n\n\ts += size;\n\tif (current - time > 1) \n\t{\n\t\tcls.downloadrate = s / (1024 * (current - time));\n\t\ttime = current;\n\t\ts = 0;\n\t}\n\n\tif (cls.demoplayback) \n\t{\n\t\tqbool skip_download = true;\n\n\t\t// Skip download data in demo playback, except during qtving which support download.\n\n\t\tif (cls.mvdplayback == QTV_PLAYBACK)\n\t\t{\n\t\t\tif (cls.qtv_ezquake_ext & QTV_EZQUAKE_EXT_DOWNLOAD)\n\t\t\t\tskip_download = false;\n\t\t}\n\n\t\tif (skip_download)\n\t\t{\n\t\t\tif (size > 0)\n\t\t\t\tmsg_readcount += size;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (size == -1)\t\n\t{\n\t\tCom_Printf (\"File not found.\\n\");\n\t\tif (cls.download) {\n\t\t\tCom_Printf (\"cls.download shouldn't have been set\\n\");\n\t\t\tfclose (cls.download);\n\t\t\tcls.download = NULL;\n\t\t}\n\t\tCL_FinishDownload(); // this also request next dl\n\t\treturn;\n\t}\n\n\t// Open the file if not opened yet\n\tif (!cls.download) \n\t{\n\t\tFS_CreatePath (cls.downloadtempname);\n\n\t\tif ( !(cls.download = fopen (cls.downloadtempname, \"wb\")) )\n\t\t{\n\t\t\tmsg_readcount += size;\n\t\t\tCom_Printf (\"Failed to open %s\\n\", cls.downloadtempname);\n\t\t\tCL_FinishDownload(); // this also request next dl\n\t\t\treturn;\n\t\t}\n\t}\n\n\tfwrite (net_message.data + msg_readcount, 1, size, cls.download);\n\tmsg_readcount += size;\n\n\tif (percent != 100) \n\t{\n\t\t// Change display routines by zoid.\n\t\t// Request next block.\n\t\tcls.downloadpercent = percent;\n\n\t\tif (cls.mvdplayback == QTV_PLAYBACK) \n\t\t{\n\t\t\t// nothing\n\t\t}\n\t\telse \n\t\t{\n\t\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\t\tSZ_Print (&cls.netchan.message, \"nextdl\");\n\t\t}\n\t} \n\telse\n\t{\n\t\tcls.downloadpercent = 100;\n\t\tCL_FinishDownload(); // this also request next dl\n\t}\n}\n\n/*\n=====================================================================\n\n  UPLOAD FILE FUNCTIONS\n\n=====================================================================\n*/\nvoid CL_NextUpload(void)\n{\n\tstatic byte\tbuffer[FILE_TRANSFER_BUF_SIZE];\n\tint\t\tr;\n\tint\t\tpercent;\n\tint\t\tsize;\n\tstatic int s = 0;\n\tdouble current = Sys_DoubleTime();\n\tstatic double\ttime;\n\n\tif ((!cls.is_file && !cls.mem_upload) || (cls.is_file && !cls.upload))\n\t\treturn;\n\n\tr = min(cls.upload_size - cls.upload_pos, (int)sizeof(buffer));\n\tMSG_WriteByte (&cls.netchan.message, clc_upload);\n\tMSG_WriteShort (&cls.netchan.message, r);\n\n\tif (cls.is_file)\n\t{\n\t\tif ((int)fread(buffer, 1, r, cls.upload) != r)\n\t\t{\n\t\t\tCom_Printf(\"Error reading the upload file\\n\");\n\t\t\tCL_StopUpload();\n\t\t\treturn;\n\t\t}\n\t} \n\telse \n\t{\n\t\tmemcpy(buffer, cls.mem_upload + cls.upload_pos, r);\n\t}\n\tcls.upload_pos += r;\n\tsize = cls.upload_size ? cls.upload_size : 1;\n\tpercent = cls.upload_pos * 100 / size;\n\tcls.uploadpercent = percent;\n\tMSG_WriteByte (&cls.netchan.message, percent);\n\tSZ_Write (&cls.netchan.message, buffer, r);\n\n\tCom_DPrintf (\"UPLOAD: %6d: %d written\\n\", cls.upload_pos - r, r);\n\n\ts += r;\n\tif (current - time > 1) \n\t{\n\t\tcls.uploadrate = s/(1024*(current - time));\n\t\ttime = current;\n\t\ts = 0;\n\t}\n\t\n\tif (cls.upload_pos != cls.upload_size)\n\t\treturn;\n\n\tCom_Printf (\"Upload completed\\n\");\n\n\tif (cls.is_file) \n\t{\n\t\tfclose (cls.upload);\n\t\tcls.upload = NULL;\n\t} \n\telse \n\t{\n\t\tQ_free(cls.mem_upload);\n\t\tcls.mem_upload = 0;\n\t}\n\tcls.upload_pos = 0;\n\tcls.upload_size = 0;\n}\n\nvoid CL_StartUpload (byte *data, int size)\n{\n\tif (cls.state < ca_onserver)\n\t\treturn; // gotta be connected\n\n\tcls.is_file = false;\n\n\t// override\n\tif (cls.mem_upload)\n\t\tQ_free(cls.mem_upload);\n\tcls.mem_upload = (byte *) Q_malloc (size);\n\tmemcpy(cls.mem_upload, data, size);\n\tcls.upload_size = size;\n\tcls.upload_pos = 0;\n\tCom_Printf (\"Upload starting of %d...\\n\", cls.upload_size);\n\n\tCL_NextUpload();\n}\n\nstatic void ReplaceChar(char *s, char from, char to)\n{\n\tif (s)\n\t{\n\t\tfor ( ;*s ; ++s)\n\t\t{\n\t\t\tif (*s == from)\n\t\t\t\t*s = to;\n\t\t}\n\t}\n}\n\nvoid CL_StartFileUpload (void)\n{\n\tchar *name;\n\tint i;\n\textern cvar_t cl_allow_uploads;\n\n\tif (!cl_allow_uploads.integer)\n\t{\n\t\tCom_Printf (\"This command has been disabled for security reasons. Set cl_allow_uploads to 1 if you want to enable uploads.\\n\");\n\t\treturn;\n\t}\n\n\tif (cls.state < ca_onserver) \n\t{\n\t\tCom_Printf(\"must be connected\\n\");\n\t\treturn;\n\t}\n\n\tname = Cmd_Argv (2);\n\n\tReplaceChar(name, '\\\\', '/');\n\n\tif (\n//\t\tTODO: split name to pathname and filename\n//\t\tand check for 'bad symbols' only in pathname\n\t\t*name == '/' //no absolute\n\t\t|| !strncmp(name, \"../\", 3) // no leading ../\n\t\t|| strstr (name, \"/../\") // no /../\n\t\t|| ((i = strlen(name)) < 3 ? 0 : !strncmp(name + i - 3, \"/..\", 4)) // no /.. at end\n\t\t|| *name == '.' //relative is pointless\n\t\t|| ((i = strlen(name)) < 4 ? 0 : !strncasecmp(name+i-4,\".log\",4)) // no logs\n#ifdef _WIN32\n\t\t// no leading X:\n\t\t|| ( name[1] == ':' && ((*name >= 'a' && *name <= 'z') ||\n\t\t\t(*name >= 'A' && *name <= 'Z')) )\n#endif //_WIN32\n\t) \n\t{\n\t\tCom_Printf (\"File upload: bad path/name \\\"%s\\\"\", name);\n\t\treturn;\n\t}\n\n\tcls.is_file = true;\n\n\t// override\n\tif (cls.upload) \n\t{\n\t\tfclose(cls.upload);\n\t\tcls.upload = NULL;\n\t}\n\n\tstrlcpy(cls.uploadname, Cmd_Argv(2), sizeof(cls.uploadname));\n\tcls.upload = fopen(cls.uploadname, \"rb\"); // BINARY\n\n\tif (!cls.upload)\n\t{\n\t\tCom_Printf (\"Bad file \\\"%s\\\"\\n\", cls.uploadname);\n\t\treturn;\n\t}\n\n\tcls.upload_size = FS_FileLength(cls.upload);\n\tcls.upload_pos = 0;\n\n\tCom_Printf (\"Upload starting: %s (%d bytes)...\\n\", cls.uploadname, cls.upload_size);\n\n\tCL_NextUpload();\n}\n\nqbool CL_IsUploading(void)\n{\n\tif ((!cls.is_file && cls.mem_upload) || (cls.is_file && cls.upload))\n\t\treturn true;\n\treturn false;\n}\n\nvoid CL_StopUpload(void)\n{\n\tif (cls.is_file) \n\t{\n\t\tif (cls.upload) \n\t\t{\n\t\t\tfclose(cls.upload);\n\t\t\tcls.upload = NULL;\n\t\t}\n\t} \n\telse \n\t{\n\t\tif (cls.mem_upload) \n\t\t{\n\t\t\tQ_free(cls.mem_upload);\n\t\t\tcls.mem_upload = NULL;\n\t\t}\n\t}\n}\n\n/*\n=====================================================================\n  SERVER CONNECTING MESSAGES\n=====================================================================\n*/\n\nvoid CL_ParseServerData (void) \n{\n\tchar *str, fn[MAX_OSPATH];\n\tFILE *f;\n\tqbool cflag = false;\n\tint i, protover;\n\n\tCom_DPrintf(\"Serverdata packet received.\\n\");\n\n\t// wipe the clientState_t struct\n\tCL_ClearState();\n\n\t// parse protocol version number\n\t// allow 2.2 and 2.29 demos to play\n#ifdef PROTOCOL_VERSION_FTE\n\tcls.fteprotocolextensions = 0;\n#endif // PROTOCOL_VERSION_FTE\n\n#ifdef PROTOCOL_VERSION_FTE2\n\tcls.fteprotocolextensions2 = 0;\n#endif // PROTOCOL_VERSION_FTE2\n\n#ifdef PROTOCOL_VERSION_MVD1\n\tcls.mvdprotocolextensions1 = 0;\n#endif\n\n\tfor(;;)\n\t{\n\t\tprotover = MSG_ReadLong ();\n#ifdef PROTOCOL_VERSION_FTE\n\t\tif (protover == PROTOCOL_VERSION_FTE)\n\t\t{\n\t\t\tcls.fteprotocolextensions = MSG_ReadLong();\n\t\t\tCom_DPrintf (\"Using FTE extensions 0x%x\\n\", cls.fteprotocolextensions);\n\t\t\tcontinue;\n\t\t}\n#endif\n\n#ifdef PROTOCOL_VERSION_FTE2\n\t\tif (protover == PROTOCOL_VERSION_FTE2)\n\t\t{\n\t\t\tcls.fteprotocolextensions2 = MSG_ReadLong();\n\t\t\tCom_DPrintf (\"Using FTE extensions2 0x%x\\n\", cls.fteprotocolextensions2);\n\t\t\tcontinue;\n\t\t}\n#endif\n\n#ifdef PROTOCOL_VERSION_MVD1\n\t\tif (protover == PROTOCOL_VERSION_MVD1) {\n\t\t\textern cvar_t cl_pext_lagteleport;\n\t\t\textern cvar_t cl_debug_antilag_send;\n\n\t\t\tcls.mvdprotocolextensions1 = MSG_ReadLong();\n\t\t\tCom_DPrintf(\"Using MVDSV extensions 0x%x\\n\", cls.mvdprotocolextensions1);\n\t\t\tif (!cls.demoplayback) {\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\t\t\t\textern cvar_t cl_pext_serversideweapon;\n\n\t\t\t\tif (cl_pext_serversideweapon.integer && !(cls.mvdprotocolextensions1 & MVD_PEXT1_SERVERSIDEWEAPON)) {\n\t\t\t\t\tCon_Printf(\"&cf00Warning&r: weapon scripts will be executed client-side\\n\");\n\t\t\t\t}\n#endif\n#ifdef MVD_PEXT1_HIGHLAGTELEPORT\n\t\t\t\tif (cl_pext_lagteleport.integer && !(cls.mvdprotocolextensions1 & MVD_PEXT1_HIGHLAGTELEPORT)) {\n\t\t\t\t\tCon_Printf(\"&cf00Warning&r: high-lag teleport fix not available\\n\");\n\t\t\t\t}\n#endif\n#ifdef MVD_PEXT1_DEBUG_ANTILAG\n\t\t\t\tif (cl_debug_antilag_send.integer && !(cls.mvdprotocolextensions1 & MVD_PEXT1_DEBUG_ANTILAG)) {\n\t\t\t\t\tCon_Printf(\"&cf00Warning&r: server doesn't support antilag debugging - will not send\\n\");\n\t\t\t\t}\n#endif\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n#endif\n\n\t\tif (protover == PROTOCOL_VERSION) //this ends the version info\n\t\t\tbreak;\n\t\tif (cls.demoplayback && protover >= 24 && protover <= 28)\t//older versions, maintain demo compatability.\n\t\t\tbreak;\n\t\tHost_Error (\"Server returned version %i, not %i\\nYou probably need to upgrade.\\nCheck http://www.quakeworld.net/\", protover, PROTOCOL_VERSION);\n\t}\n\n#ifdef FTE_PEXT_FLOATCOORDS\n\tif (cls.fteprotocolextensions & FTE_PEXT_FLOATCOORDS)\n\t{\n\t\tif (!com_serveractive)\n\t\t{\n\t\t\tmsg_coordsize = 4;\n\t\t\tmsg_anglesize = 2;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (!com_serveractive)\n\t\t{\n\t\t\tmsg_coordsize = 2;\n\t\t\tmsg_anglesize = 1;\n\t\t}\n\t}\n#endif\n\n\tcl.protoversion = protover;\n\tcl.servercount = MSG_ReadLong ();\n\n\t// game directory\n\tstr = MSG_ReadString ();\n\n\tif (!strcmp(str, \"\")  || !strcmp(str, \".\") || strstr(str, \"..\") || strstr(str, \"//\") || strchr(str, '\\\\') || strchr(str, ':')  || str[0] == '/') {\n\t\tHost_Error(\"Server reported invalid gamedir!\\n\");\n\t}\n\n\tcl.teamfortress = !strcasecmp(str, \"fortress\");\n\tif (cl.teamfortress) \n\t{\n\t\textern cvar_t\tv_iyaw_cycle, v_iroll_cycle, v_ipitch_cycle,\n\t\t\tv_iyaw_level, v_iroll_level, v_ipitch_level, v_idlescale;\n\t\tcbuf_current = &cbuf_svc;\t// hack\n\t\tCvar_SetValue (&v_iyaw_cycle, 2);\n\t\tCvar_SetValue (&v_iroll_cycle, 0.5);\n\t\tCvar_SetValue (&v_ipitch_cycle, 1);\n\t\tCvar_SetValue (&v_iyaw_level, 0.3);\n\t\tCvar_SetValue (&v_iroll_level, 0.1);\n\t\tCvar_SetValue (&v_ipitch_level, 0.3);\n\t\tCvar_SetValue (&v_idlescale, 0);\n\t}\n\n\tif (strcasecmp(cls.gamedirfile, str)) \n\t{\n\t\tstrlcpy (cls.gamedirfile, str, sizeof(cls.gamedirfile));\n\t\tsnprintf (cls.gamedir, sizeof(cls.gamedir),\n\t\t\t\"%s/%s\", com_basedir, cls.gamedirfile);\n\t\tcflag = true;\n\t}\n\n\tif (!com_serveractive)\n\t\tFS_SetGamedir (str, false);\n\n\tif (cfg_legacy_exec.value && (cflag || cfg_legacy_exec.value >= 2)) \n\t{\n\t\tsnprintf (fn, sizeof(fn), \"%s/%s\", cls.gamedir, \"config.cfg\");\n\t\tCbuf_AddText (\"cl_warncmd 0\\n\");\n\t\tif ((f = fopen(fn, \"r\")) != NULL) \n\t\t{\n\t\t\tfclose(f);\n\t\t\tif (!strcmp(cls.gamedirfile, com_gamedirfile))\n\t\t\t\tCbuf_AddText (\"exec config.cfg\\n\");\n\t\t\telse\n\t\t\t\tCbuf_AddText (va(\"exec ../%s/config.cfg\\n\", cls.gamedirfile));\n\t\t} \n\t\telse if (cfg_legacy_exec.value == 3 && strcmp(cls.gamedir, \"qw\"))\n\t\t{\n\t\t\tsnprintf (fn, sizeof(fn), \"qw/%s\", \"config.cfg\");\n\t\t\tif ((f = fopen(fn, \"r\")) != NULL) \n\t\t\t{\n\t\t\t\tfclose(f);\n\t\t\t\tCbuf_AddText (\"exec config.cfg\\n\");\n\t\t\t}\n\t\t}\n\t\tsnprintf (fn, sizeof(fn), \"%s/%s\", cls.gamedir, \"frontend.cfg\");\n\t\tif ((f = fopen(fn, \"r\")) != NULL) \n\t\t{\n\t\t\tfclose(f);\n\t\t\tif (!strcmp(cls.gamedirfile, com_gamedirfile))\n\t\t\t\tCbuf_AddText (\"exec frontend.cfg\\n\");\n\t\t\telse\n\t\t\t\tCbuf_AddText (va(\"exec ../%s/frontend.cfg\\n\", cls.gamedirfile));\n\t\t} \n\t\telse if (cfg_legacy_exec.value == 3 && strcmp(cls.gamedir, \"qw\"))\n\t\t{\n\t\t\tsnprintf (fn, sizeof(fn), \"qw/%s\", \"frontend.cfg\");\n\t\t\tif ((f = fopen(fn, \"r\")) != NULL) \n\t\t\t{\n\t\t\t\tfclose(f);\n\t\t\t\tCbuf_AddText (\"exec frontend.cfg\\n\");\n\t\t\t}\n\t\t}\n\t\tCbuf_AddText (\"cl_warncmd 1\\n\");\n\t}\n\n\n\t// parse player slot, high bit means spectator\n\tif (cls.mvdplayback)\n\t{\n\t\tcls.netchan.last_received = nextdemotime = olddemotime = MSG_ReadFloat();\n\t\tcl.playernum = MAX_CLIENTS - 1;\n\t\tcl.spectator = true;\n\t\tfor (i = 0; i < UPDATE_BACKUP; i++)\n\t\t\tcl.frames[i].playerstate[cl.playernum].pm_type = PM_SPECTATOR;\n\t}\n\telse \n\t{\n\t\tcl.playernum = MSG_ReadByte ();\n\t\tif (cl.playernum & 128) \n\t\t{\n\t\t\tcl.spectator = true;\n\t\t\tcl.playernum &= ~128;\n\t\t}\n\t}\n\n\t// get the full level name\n\tstr = MSG_ReadString ();\n\tstrlcpy (cl.levelname, str, sizeof(cl.levelname));\n\n\t// get the movevars\n\tif (cl.protoversion >= 25) \n\t{\n\t\tmovevars.gravity\t\t\t= MSG_ReadFloat();\n\t\tmovevars.stopspeed          = MSG_ReadFloat();\n\t\tcl.maxspeed                 = MSG_ReadFloat();\n\t\tmovevars.spectatormaxspeed  = MSG_ReadFloat();\n\t\tmovevars.accelerate         = MSG_ReadFloat();\n\t\tmovevars.airaccelerate      = MSG_ReadFloat();\n\t\tmovevars.wateraccelerate    = MSG_ReadFloat();\n\t\tmovevars.friction           = MSG_ReadFloat();\n\t\tmovevars.waterfriction      = MSG_ReadFloat();\n\t\tcl.entgravity               = MSG_ReadFloat();\n\t}\n\telse\n\t{\n\t\t// These seem to be the defaults for standard QW... \n\t\t//   if there are mods with other defaults, we could \n\t\t//   set accordingly to give best experience?\n\t\tmovevars.gravity = 800.0f;\n\t\tmovevars.stopspeed = 100.0f;\n\t\tcl.maxspeed = 320.0f;\n\t\tmovevars.spectatormaxspeed = 500.0f;\n\t\tmovevars.accelerate = 10.0f;\n\t\tmovevars.airaccelerate = 0.7f;\n\t\tmovevars.wateraccelerate = 10.0f;\n\t\tmovevars.friction = 6.0f;\n\t\tmovevars.waterfriction = 1.0f;\n\t\tcl.entgravity = 1.0f;\n\t}\n\n\t// separate the printfs so the server message can have a color\n\tif (!cls.demoseeking) {\n\t\tCom_Printf (\"\\n\\n\\35\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\37\\n\\n\");\n\t\tCom_Printf (\"%c%s\\n\", 2, str);\n\t}\n\n\t// ask for the sound list next\n\tmemset(cl.sound_name, 0, sizeof(cl.sound_name));\n\n\tif (cls.mvdplayback == QTV_PLAYBACK) \n\t{\n\t\tcls.qtv_donotbuffer = true; // do not try buffering before \"skins\" not received\n\t\tQTV_Cmd_Printf(QTV_EZQUAKE_EXT_DOWNLOAD, \"qtvsoundlist %i %i\", cl.servercount, 0);\n\t}\n\telse \n\t{\n\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\tMSG_WriteString (&cls.netchan.message, va(\"soundlist %i %i\", cl.servercount, 0));\n\t}\n\n\t// now waiting for downloads, etc\n\tcls.state = ca_onserver;\n\n#ifdef FTE_PEXT2_VOICECHAT\n\tS_Voip_MapChange();\n#endif\n}\n\nvoid CL_ParseSoundlist (void)\n{\n\tint\tnumsounds, n;\n\tchar *str;\n\n\t// precache sounds\n\t// memset (cl.sound_precache, 0, sizeof(cl.sound_precache));\n\n\tif (cl.protoversion >= 26) \n\t{\n\t\tnumsounds = MSG_ReadByte();\n\n\t\twhile (1) \n\t\t{\n\t\t\tstr = MSG_ReadString ();\n\t\t\tif (!str[0])\n\t\t\t\tbreak;\n\t\t\tnumsounds++;\n\t\t\tif (numsounds >= MAX_SOUNDS) {\n\t\t\t\tHost_Error(\"Server sent too many sound_precache\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (str[0] == '/')\n\t\t\t\tstr++; // hexum -> fixup server error (submitted by empezar bug #1026106)\n\t\t\tstrlcpy (cl.sound_name[numsounds], str, sizeof(cl.sound_name[numsounds]));\n\t\t}\n\n\t\tn = MSG_ReadByte();\n\n\t\tif (n) \n\t\t{\n\t\t\tif (cls.mvdplayback == QTV_PLAYBACK) \n\t\t\t{\n\t\t\t\t// none\n\t\t\t}\n\t\t\telse \n\t\t\t{\n\t\t\t\tMSG_WriteByte(&cls.netchan.message, clc_stringcmd);\n\t\t\t\tMSG_WriteString(&cls.netchan.message, va(\"soundlist %i %i\", cl.servercount, n));\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\t}\n\telse \n\t{\n\t\t// Taken from http://www.quakewiki.net/archives/demospecs/qwd/qwd.html#AEN562\n\t\tnumsounds = 0;\n\t\tdo {\n\t\t\tif (++numsounds > 255)\n\t\t\t\tHost_Error (\"Server sent too many sound_precache\");\n\t\t\tstr = MSG_ReadString ();\n\t\t\tstrlcpy (cl.sound_name[numsounds], str, sizeof(cl.sound_name[numsounds]));\n\t\t} while (*str);\n\t}\n\n\t// AFTER we got the soundlist, request the modellist (older FTE servers will send it right away anyway)\n\t// done with sounds, request models now\n\tmemset (cl.model_precache, 0, sizeof(cl.model_precache));\n\n\tif (cls.mvdplayback == QTV_PLAYBACK)\n\t{\n\t\tQTV_Cmd_Printf(QTV_EZQUAKE_EXT_DOWNLOAD, \"qtvmodellist %i %i\", cl.servercount, 0);\n\t}\n\telse \n\t{\n\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\tMSG_WriteString (&cls.netchan.message, va(\"modellist %i %i\", cl.servercount, 0));\n\t}\n}\n\nvoid CL_ParseModellist (qbool extended)\n{\n\tint\tnummodels, n;\n\tchar *str;\n\n\tif (cl.protoversion >= 26)\n\t{\n\t\t// Precache models and note certain default indexes\n\t\tnummodels = (extended) ? (unsigned) MSG_ReadShort () : MSG_ReadByte ();\n\n\t\twhile (1) \n\t\t{\n\t\t\tstr = MSG_ReadString ();\n\t\t\tif (!str[0])\n\t\t\t\tbreak;\n\n\t\t\t#if defined (PROTOCOL_VERSION_FTE) && defined (FTE_PEXT_MODELDBL)\n\t\t\tnummodels++;\n\t\t\tif (nummodels >= MAX_MODELS) {\n\t\t\t\t// Spike: tweeked this, we still complain if the server exceeds the standard limit without using extensions.\n\t\t\t\tHost_Error(\"Server sent too many model_precache\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\n\t\t\tif (nummodels >= 256 && !(cls.fteprotocolextensions & FTE_PEXT_MODELDBL))\n\t\t\t#else\n\t\t\tif (++nummodels >= MAX_MODELS)\n\t\t\t#endif // PROTOCOL_VERSION_FTE\n\t\t\t{\n\t\t\t\tHost_Error (\"Server sent too many model_precache\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (str[0] == '/')\n\t\t\t\tstr++; // hexum -> fixup server error (submitted by empezar bug #1026106)\n\t\t\tstrlcpy (cl.model_name[nummodels], str, sizeof(cl.model_name[nummodels]));\n\n\t\t\tif (nummodels == 1)\n\t\t\t\tif (!com_serveractive) \n\t\t\t\t{\n\t\t\t\t\tchar mapname[MAX_QPATH];\n\t\t\t\t\tCOM_StripExtension (COM_SkipPath(cl.model_name[1]), mapname, sizeof(mapname));\n\t\t\t\t\tCvar_ForceSet (&host_mapname, mapname);\n\t\t\t\t}\n\t\t}\n\n\t\tif ((n = MSG_ReadByte())) \n\t\t{\n\t\t\tif (cls.mvdplayback == QTV_PLAYBACK) \n\t\t\t{\n\t\t\t\t// none\n\t\t\t}\n\t\t\telse \n\t\t\t{\n\t\t\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\t\t\n\t\t\t\t#if defined (PROTOCOL_VERSION_FTE) && defined (FTE_PEXT_MODELDBL)\n\t\t\t\tMSG_WriteString (&cls.netchan.message, va(\"modellist %i %i\", cl.servercount, (nummodels&0xff00)+n));\n\t\t\t\t#else\n\t\t\t\tMSG_WriteString (&cls.netchan.message, va(\"modellist %i %i\", cl.servercount, n));\n\t\t\t\t#endif // PROTOCOL_VERSION_FTE\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\t}\n\telse \n\t{\n\t\t// Up to 2.1 (taken from http://www.quakewiki.net/archives/demospecs/qwd/qwd.html#AEN562)\n\t\tnummodels = 0;\n\t\tdo \n\t\t{\n\t\t\tif (++nummodels > 255)\n\t\t\t\tHost_Error (\"Server sent too many model_precache\\n\");\n\t\t\tstr = MSG_ReadString ();\n\t\t\tstrlcpy (cl.model_name[nummodels], str, sizeof(cl.model_name[nummodels]));\n\n\t\t\tif (nummodels == 1)\n\t\t\t\tif (!com_serveractive) \n\t\t\t\t{\n\t\t\t\t\tchar mapname[MAX_QPATH];\n\t\t\t\t\tCOM_StripExtension (COM_SkipPath(cl.model_name[1]), mapname, sizeof(mapname));\n\t\t\t\t\tCvar_ForceSet (&host_mapname, mapname);\n\t\t\t\t}\n\t\t} while (*str);\n\t}\n\t// We now get here after having received both soundlist and modellist, but no\n\t// sounds have been downloaded yet, we must do that first, when that is finished\n\t// it will call for model download \n\t// FIXME Implement a download queue later on and thus break this long chain of events\n\tcls.downloadnumber = 0;\n\tcls.downloadtype = dl_sound;\n\tSound_NextDownload();\n}\n\nvoid CL_ParseBaseline (entity_state_t *es)\n{\n\tint\ti;\n\n\tes->modelindex = MSG_ReadByte ();\n\tes->frame = MSG_ReadByte ();\n\tes->colormap = MSG_ReadByte();\n\tes->skinnum = MSG_ReadByte();\n\n\tfor (i = 0; i < 3; i++) \n\t{\n\t\tes->origin[i] = MSG_ReadCoord ();\n\t\tes->angles[i] = MSG_ReadAngle ();\n\t}\n\n#ifdef FTE_PEXT_TRANS\n\tes->trans = 0;\n#endif\n#ifdef FTE_PEXT_COLOURMOD\n\tes->colourmod[0] = 0;\n\tes->colourmod[1] = 0;\n\tes->colourmod[2] = 0;\n#endif\n}\n\n// An easy way to keep compatability with other entity extensions\n#if defined (PROTOCOL_VERSION_FTE) && defined (FTE_PEXT_SPAWNSTATIC2)\nextern void CL_ParseDelta (entity_state_t *from, entity_state_t *to, int bits);\n\nstatic void CL_ParseSpawnBaseline2 (void)\n{\n\tentity_state_t nullst, es;\n\n\tif (!(cls.fteprotocolextensions & FTE_PEXT_SPAWNSTATIC2)) \n\t{\n\t\tCom_Printf (\"illegible server message\\nsvc_fte_spawnbaseline2 (%i) without FTE_PEXT_SPAWNSTATIC2\\n\", svc_fte_spawnbaseline2);\n\t\tHost_EndGame();\n\t}\n\n\tmemset(&nullst, 0, sizeof (entity_state_t));\n\tmemset(&es, 0, sizeof (entity_state_t));\n\n\tCL_ParseDelta(&nullst, &es, MSG_ReadShort());\n\tmemcpy(&cl_entities[es.number].baseline, &es, sizeof(es));\n}\n#endif\n\n// Static entities are non-interactive world objects like torches\nvoid CL_ParseStatic (qbool extended)\n{\n\tentity_t *ent;\n\tentity_state_t es;\n\n\tif (extended)\n\t{\n\t\tentity_state_t nullst;\n\t\tmemset (&nullst, 0, sizeof(entity_state_t));\n\n\t\tCL_ParseDelta (&nullst, &es, MSG_ReadShort());\n\t} \n\telse\n\t{\n\t\tCL_ParseBaseline (&es);\n\t}\n\n\tif (cl.num_statics >= MAX_STATIC_ENTITIES) {\n\t\tHost_Error(\"Too many static entities\");\n\t}\n\tent = &cl_static_entities[cl.num_statics++];\n\tent->entity_id = cl.num_statics;\n\n\t// Copy it to the current state\n\tent->model = cl.model_precache[es.modelindex];\n\tent->frame = es.frame;\n\tent->colormap = vid.colormap;\n\tent->skinnum = es.skinnum;\n#ifdef FTE_PEXT_TRANS\n\t// set trans, 0 and 255 are both opaque, represented by alpha 0.\n\tent->alpha = es.trans == 255 ? 0.0f : (float)es.trans / 254.0f;\n#endif\n#ifdef FTE_PEXT_COLOURMOD\n\t// Skip colourmod if unset, or identity\n\tif ((es.colourmod[0] > 0 || es.colourmod[1] > 0 || es.colourmod[2] > 0) &&\n\t    !(es.colourmod[0] == 32 && es.colourmod[1] == 32 && es.colourmod[2] == 32))\n\t{\n\t\tent->r_modelcolor[0] = (float)es.colourmod[0] * 8.0f / 256.0f;\n\t\tent->r_modelcolor[1] = (float)es.colourmod[1] * 8.0f / 256.0f;\n\t\tent->r_modelcolor[2] = (float)es.colourmod[2] * 8.0f / 256.0f;\n\t\tent->renderfx |= RF_FORCECOLOURMOD;\n\t}\n#endif\n\n\tVectorCopy(es.origin, ent->origin);\n\tVectorCopy(es.angles, ent->angles);\n\n\tR_AddEfrags(ent);\n}\n\nvoid CL_ParseStaticSound (void)\n{\n\textern cvar_t cl_staticsounds;\n\tstatic_sound_t ss;\n\tint i;\n\n\tfor (i = 0; i < 3; i++) {\n\t\tss.org[i] = MSG_ReadCoord();\n\t}\n\n\tss.sound_num = MSG_ReadByte();\n\tss.vol = MSG_ReadByte();\n\tss.atten = MSG_ReadByte();\n\n\tif (cl.num_static_sounds < MAX_STATIC_SOUNDS) \n\t{\n\t\tcl.static_sounds[cl.num_static_sounds] = ss;\n\t\tcl.num_static_sounds++;\n\t}\n\n\tif ((int) cl_staticsounds.value)\n\t\tS_StaticSound(cl.sound_precache[ss.sound_num], ss.org, ss.vol, ss.atten);\n}\n\n/*\n=====================================================================\nACTION MESSAGES\n=====================================================================\n*/\n\nvoid CL_ParseStartSoundPacket(void)\n{\n    vec3_t pos;\n    int channel, ent, sound_num, volume, i;\n\tint tracknum;\n    float attenuation;\n\n    channel = MSG_ReadShort();\n\n\tvolume = (channel & SND_VOLUME) ? MSG_ReadByte() : DEFAULT_SOUND_PACKET_VOLUME;\n\n\tattenuation = (channel & SND_ATTENUATION) ? MSG_ReadByte() / 64.0 : DEFAULT_SOUND_PACKET_ATTENUATION;\n\n\tsound_num = MSG_ReadByte();\n\n\tfor (i = 0; i < 3; i++)\n\t\tpos[i] = MSG_ReadCoord();\n\n\tent = (channel >> 3) & 1023;\n\tchannel &= 7;\n\n\tif (ent > CL_MAX_EDICTS)\n\t\tHost_Error (\"CL_ParseStartSoundPacket: ent = %i\", ent);\n\n\t// MVD Playback\n    if (cls.mvdplayback)\n\t{\n\t    tracknum = Cam_TrackNum();\n\n\t    if (cl.spectator && tracknum != -1 && ent == tracknum + 1 && cl_multiview.value < 2)\n\t\t{\n\t\t    ent = cl.playernum + 1;\n\t\t}\n    }\n\n\tif (CL_Demo_SkipMessage(true))\n\t\treturn;\n\n    S_StartSound (ent, channel, cl.sound_precache[sound_num], pos, volume/255.0, attenuation);\n\n\tif (ent == cl.playernum+1)\n\t\tTP_CheckPickupSound (cl.sound_name[sound_num], pos);\n}\n\n// Server information pertaining to this client only, sent every frame\nvoid CL_ParseClientdata (void) \n{\n\tint newparsecount, i;\n\tfloat latency;\n\tframe_t *frame;\n\n\t// calculate simulated time of message\n\tnewparsecount = cls.netchan.incoming_acknowledged;\n\n\tcl.oldparsecount = (cls.mvdplayback) ? newparsecount - 1 : cl.parsecount;\n\tcl.parsecount = newparsecount;\n\tcl.parsecountmod = (cl.parsecount & UPDATE_MASK);\n\tframe = &cl.frames[cl.parsecountmod];\n\n\tframe->receivedtime = cls.realtime;\n\tif (cls.mvdplayback) {\n\t\tframe->senttime = cls.realtime - cls.frametime;\n\t}\n\telse if (cls.demoplayback) {\n\t\tframe->receivedtime = cls.demopackettime;\n\t}\n\tcl.parsecounttime = cl.frames[cl.parsecountmod].senttime;\n\n\tframe->seq_when_received = cls.netchan.outgoing_sequence;\n\n\tframe->receivedsize = net_message.cursize;\n\n\t// update network stats table\n\ti = cls.netchan.incoming_sequence & NETWORK_STATS_MASK;\n\tnetwork_stats[i].receivedtime = cls.realtime;\n\tnetwork_stats[i].receivedsize = net_message.cursize;\n\tnetwork_stats[i].seq_diff = cls.netchan.outgoing_sequence - cls.netchan.incoming_sequence;\n\n\t// calculate latency\n\tlatency = frame->receivedtime - frame->senttime;\n\n\tif (latency >= 0 && latency <= 1) \n\t{\n\t\t// drift the average latency towards the observed latency\n\t\tif (latency <= cls.latency)\n\t\t\tcls.latency = latency;\n\t\telse\n\t\t\tcls.latency += min(0.001, latency - cls.latency);\t// drift up, so correction is needed\n\t}\n}\n\nvoid CL_NewTranslation (int slot)\n{\n\tplayer_info_t *player;\n\n\tif (cls.state < ca_connected)\n\t{\n\t\treturn;\n\t}\n\n\tif (slot >= MAX_CLIENTS)\n\t{\n\t\tSys_Error (\"CL_NewTranslation: slot >= MAX_CLIENTS\");\n\t}\n\n\tplayer = &cl.players[slot];\n\tif (!player->name[0] || player->spectator) {\n\t\treturn;\n\t}\n\n\tplayer->topcolor = player->real_topcolor;\n\tplayer->bottomcolor = player->real_bottomcolor;\n\tplayer->teammate = false;\n\n\tskinforcing_team = TP_SkinForcingTeam();\n\n\tif (!cl.teamfortress && !(cl.fpd & FPD_NO_FORCE_COLOR)) {\n\t\tqbool lockedTeams = TP_TeamLockSpecified();\n\t\tqbool teammate = false;\n\n\t\t// it's me or it's teamplay and he's my teammate\n\t\tif (cl.spectator && slot == cl.spec_track && !lockedTeams) {\n\t\t\tteammate = true;\n\t\t}\n\t\telse if (!cl.spectator && slot == cl.playernum) {\n\t\t\tteammate = true;\n\t\t}\n\t\telse if (cl.teamplay && !strcmp(player->team, skinforcing_team)) {\n\t\t\tteammate = true;\n\t\t}\n\n\t\tplayer->teammate = teammate;\n\n\t\tif (teammate) {\n\t\t\tif (cl_teamtopcolor.integer != -1) {\n\t\t\t\tplayer->topcolor = cl_teamtopcolor.value;\n\t\t\t}\n\t\t\tif (cl_teambottomcolor.integer != -1) {\n\t\t\t\tplayer->bottomcolor = cl_teambottomcolor.value;\n\t\t\t}\n\t\t}\n\t\telse if (slot != cl.playernum) {\n\t\t\tif (cl_enemytopcolor.integer != -1) {\n\t\t\t\tplayer->topcolor = cl_enemytopcolor.value;\n\t\t\t}\n\t\t\tif (cl_enemybottomcolor.integer != -1) {\n\t\t\t\tplayer->bottomcolor = cl_enemybottomcolor.value;\n\t\t\t}\n\t\t}\n\t}\n\n\tR_TranslatePlayerSkin(slot);\n}\n\nvoid CL_ProcessUserInfo(int slot, player_info_t *player, char *key)\n{\n\tstrlcpy(player->name, Info_ValueForKey(player->userinfo, \"name\"), sizeof(player->name));\n\n\tif (!player->name[0] && player->userid && strlen(player->userinfo) >= MAX_INFO_STRING - 17) {\n\t\t// Somebody's trying to hide himself by overloading userinfo.\n\t\tstrlcpy(player->name, \" \", sizeof(player->name));\n\t}\n\n\tCL_RemovePrefixFromName(slot);\n\n\tplayer->real_topcolor = atoi(Info_ValueForKey(player->userinfo, \"topcolor\"));\n\tplayer->real_bottomcolor = atoi(Info_ValueForKey(player->userinfo, \"bottomcolor\"));\n\n\tstrlcpy(player->team, Info_ValueForKey(player->userinfo, \"team\"), sizeof(player->team));\n\n\tif (atoi(Info_ValueForKey(player->userinfo, \"*spectator\"))) {\n\t\tplayer->spectator = true;\n\t}\n\telse {\n\t\tplayer->spectator = false;\n\t}\n\n\tif (slot == cl.playernum && player->name[0]) {\n\t\tif (cl.spectator != player->spectator) {\n\t\t\tCam_Reset();\n\t\t\tcl.spectator = player->spectator;\n\t\t\tTP_RefreshSkins();\n\t\t}\n\t}\n\n\tSbar_Changed();\n\n\tSkin_UserInfoChange(slot, player, key);\n\n\tstrlcpy(player->_team, player->team, sizeof (player->_team));\n\n\t// Fix the team color in scoreboard when using TF\n\tplayer->known_team_color = 0;\n\tif (!strcasecmp(player->team, cl.fixed_team_names[0])) {\n\t\tplayer->known_team_color = 13;\n\t}\n\telse if (!strcasecmp(player->team, cl.fixed_team_names[1])) {\n\t\tplayer->known_team_color = 4;\n\t}\n\telse if (!strcasecmp(player->team, cl.fixed_team_names[2])) {\n\t\tplayer->known_team_color = 12;\n\t}\n\telse if (!strcasecmp(player->team, cl.fixed_team_names[3])) {\n\t\tplayer->known_team_color = 11;\n\t}\n\n\t// login info\n\tstrlcpy(player->loginname, Info_ValueForKey(player->userinfo, \"*auth\"), sizeof(player->loginname));\n\tstrlcpy(player->loginflag, Info_ValueForKey(player->userinfo, \"*flag\"), sizeof(player->loginflag));\n\tplayer->loginflag_id = CL_LoginImageId(player->loginflag);\n\n\t// gender\n\t{\n\t\tchar* userinfo_gender = Info_ValueForKey(player->userinfo, \"gender\");\n\t\tif (!*userinfo_gender) {\n\t\t\tuserinfo_gender = Info_ValueForKey(player->userinfo, \"g\");\n\t\t}\n\n\t\tplayer->gender = gender_unknown;\n\t\tif (userinfo_gender && userinfo_gender[0]) {\n\t\t\tchar gender = userinfo_gender[0];\n\t\t\tif (gender == '0' || gender == 'M') {\n\t\t\t\tplayer->gender = gender_male;\n\t\t\t}\n\t\t\telse if (gender == '1' || gender == 'F') {\n\t\t\t\tplayer->gender = gender_female;\n\t\t\t}\n\t\t\telse if (gender == '2' || gender == 'N') {\n\t\t\t\tplayer->gender = gender_neutral;\n\t\t\t}\n\t\t}\n\t}\n\n\t// chat status\n\t{\n\t\tchar* s = Info_ValueForKey(player->userinfo, \"chat\");\n\n\t\tplayer->chatflag = 0;\n\t\tif (s && s[0]) {\n\t\t\tplayer->chatflag = Q_atoi(s);\n\t\t}\n\t}\n}\n\nvoid CL_NotifyOnFull(void)\n{\n\tif (!cl.spectator && !cls.demoplayback) {\n\t\tint limit = Q_atoi(Info_ValueForKey(cl.serverinfo, \"maxclients\"));\n\t\tint players = 0;\n\t\tint i;\n\n\t\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\t\tif (*cl.players[i].name && !cl.players[i].spectator) {\n\t\t\t\tplayers++;\n\t\t\t}\n\t\t}\n\n\t\tif (players >= limit) {\n\t\t\tVID_NotifyActivity();\n\t\t}\n\t}\n}\n\nvoid CL_PlayerEnterSlot(player_info_t *player) \n{\n\textern player_state_t oldplayerstates[MAX_CLIENTS];\n\n\tplayer->ignored = false;\n\tmemset(&oldplayerstates[player - cl.players], 0, sizeof(player_state_t));\n\tStats_EnterSlot(player - cl.players);\n\tCL_NotifyOnFull();\n}\n\nvoid CL_PlayerLeaveSlot(player_info_t *player) \n{\n\treturn;\n}\n\nvoid CL_UpdateUserinfo (void) \n{\n\tqbool was_empty_slot;\n\tint slot;\n\tplayer_info_t *player;\n\n\tif (CL_Demo_SkipMessage(false)) {\n\t\t// Older demos (kteams?) can send blank userinfo strings to specs, then re-send the old strings again in next packet\n\t\t//   Not sure why this happened, but it breaks our scoreboard, autotrack etc, so ignore\n\t\tMSG_ReadByte();\n\t\tMSG_ReadLong();\n\t\tMSG_ReadString();\n\t\treturn;\n\t}\n\n\tslot = MSG_ReadByte();\n\tif (slot >= MAX_CLIENTS)\n\t\tHost_Error (\"CL_ParseServerMessage: svc_updateuserinfo > MAX_CLIENTS\");\n\n\tplayer = &cl.players[slot];\n\n\twas_empty_slot = player->name[0] ? false : true;\n\n\tplayer->userid = MSG_ReadLong();\n\tstrlcpy (player->userinfo, MSG_ReadString(), sizeof(player->userinfo));\n\n\tCL_ProcessUserInfo(slot, player, NULL);\n\n\tif (player->name[0] && was_empty_slot) {\n\t\tCL_PlayerEnterSlot(player);\n\t\tMVD_Init_Info(slot);\n\t}\n\telse if (!player->name[0] && !was_empty_slot) {\n\t\tCL_PlayerLeaveSlot(player);\n\t\tMVD_Init_Info(slot);\n\t}\n}\n\nvoid CL_SetInfo (void) \n{\n\tint\tslot;\n\tplayer_info_t *player;\n\tchar key[MAX_INFO_STRING], value[MAX_INFO_STRING];\n\n\tslot = MSG_ReadByte ();\n\tif (slot >= MAX_CLIENTS)\n\t\tHost_Error (\"CL_ParseServerMessage: svc_setinfo > MAX_CLIENTS\");\n\n\tplayer = &cl.players[slot];\n\n\tstrlcpy(key, MSG_ReadString(), sizeof(key));\n\tstrlcpy(value, MSG_ReadString(), sizeof(value));\n\n\tif (!cl.teamfortress)\t// don't allow cheating in TF\n\t\tCom_DPrintf (\"SETINFO %s: %s=%s\\n\", player->name, key, value);\n\n\tInfo_SetValueForStarKey (player->userinfo, key, value, MAX_INFO_STRING);\n\n\tCL_ProcessUserInfo (slot, player, key);\n}\n\n/*\n * qqshka: Well, \"cmd pext\" slightly broken in MVDSV 0.30. I have to fix it somehow.\n */\nstatic void CL_PEXT_Fix(void)\n{\n\tchar version_bugged[] = \"MVDSV 0.30\";\n\tchar *version = Info_ValueForKey(cl.serverinfo, \"*version\");\n\n\tif (!strncmp(version, version_bugged, sizeof(version_bugged)-1))\n\t{\n\t\t// MVDSV 0.30 support maximum this FTE extensions.\n#ifdef PROTOCOL_VERSION_FTE\n\t\t{\n\t\t\tunsigned int ext = 0;\n#ifdef FTE_PEXT_ACCURATETIMINGS\n\t\t\text |= FTE_PEXT_ACCURATETIMINGS;\n#endif\n#ifdef FTE_PEXT_256PACKETENTITIES\n\t\t\text |= FTE_PEXT_256PACKETENTITIES;\n#endif\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\t\t\text |= FTE_PEXT_CHUNKEDDOWNLOADS;\n#endif\n\t\t\tcls.fteprotocolextensions &= ext;\n\t\t}\n#endif // PROTOCOL_VERSION_FTE\n\n\t\t// MVDSV 0.30 support maximum this FTE extensions2.\n#ifdef PROTOCOL_VERSION_FTE2\n\t\t{\n\t\t\tunsigned int ext = 0;\n\t\t\tcls.fteprotocolextensions2 &= ext;\n\t\t}\n#endif // PROTOCOL_VERSION_FTE2\n\n#ifdef FTE_PEXT_FLOATCOORDS\n\t\tif (!com_serveractive)\n\t\t{\n\t\t\tif (msg_coordsize != 2 || msg_anglesize != 1)\n\t\t\t{\n\t\t\t\tCom_DPrintf(\"Fixing FTE_PEXT_FLOATCOORDS!\\n\");\n\n\t\t\t\tmsg_coordsize = 2;\n\t\t\t\tmsg_anglesize = 1;\n\t\t\t}\n\t\t}\n#endif\n\t}\n}\n\n// Called by CL_FullServerinfo_f and CL_ParseServerInfoChange\nvoid CL_ProcessServerInfo (void) \n{\n\tchar *p, *minlight;\n\tint new_teamplay, newfpd;\n\tqbool skin_refresh, standby, countdown;\n\n\t// fix broken PEXT.\n\tCL_PEXT_Fix(); // must be called once from CL_FullServerinfo_f() but should be ok here too.\n\n\t// game type (sbar code checks it) (GAME_DEATHMATCH default)\n\tcl.gametype = *(p = Info_ValueForKey(cl.serverinfo, \"deathmatch\")) ? (atoi(p) ? GAME_DEATHMATCH : GAME_COOP) : GAME_DEATHMATCH;\n\n\t// server side fps restriction\n\tcl.maxfps = Q_atof(Info_ValueForKey(cl.serverinfo, \"maxfps\"));\n\n\tnewfpd = cls.demoplayback ? 0 : atoi(Info_ValueForKey(cl.serverinfo, \"fpd\"));\n\n\tp = Info_ValueForKey(cl.serverinfo, \"status\");\n\tstandby = !strcasecmp(p, \"standby\");\n\tcountdown = !strcasecmp(p, \"countdown\");\n\n\tif ((cl.standby || cl.countdown) && !(standby || countdown)) \n\t{\n\t\tcl.gametime = 0;\n\t\tcl.gamestarttime = Sys_DoubleTime();\n\n\t\tif (cls.mvdplayback) {\n\t\t\tMVD_GameStart();\n\t\t}\n\t}\n\n\tif (countdown && cl.countdown)\n\t{\n\t\tTP_ExecTrigger(\"f_countdownstart\");\n\t}\n\telse if (standby && !cl.standby && !countdown && cl.countdown)\n\t{\n\t\tTP_ExecTrigger(\"f_countdownbreak\");\n\t}\n\n\tcl.standby = standby;\n\tcl.countdown = countdown;\n\n\tcl.minlight = (strlen(minlight = Info_ValueForKey(cl.serverinfo, \"minlight\")) ? bound(0, Q_atoi(minlight), 255) : 4);\n\n\t// Get the server's ZQuake extension bits\n\tcl.z_ext = atoi(Info_ValueForKey(cl.serverinfo, \"*z_ext\"));\n\n\t// Initialize cl.maxpitch & cl.minpitch\n\tp = (cl.z_ext & Z_EXT_PITCHLIMITS) ? Info_ValueForKey (cl.serverinfo, \"maxpitch\") : \"\";\n\tcl.maxpitch = *p ? Q_atof(p) : 80.0f;\n\tp = (cl.z_ext & Z_EXT_PITCHLIMITS) ? Info_ValueForKey (cl.serverinfo, \"minpitch\") : \"\";\n\tcl.minpitch = *p ? Q_atof(p) : -70.0f;\n\n\t// movement vars for prediction\n\tcl.bunnyspeedcap = Q_atof(Info_ValueForKey(cl.serverinfo, \"pm_bunnyspeedcap\"));\n\tmovevars.slidefix = (Q_atof(Info_ValueForKey(cl.serverinfo, \"pm_slidefix\")) != 0);\n\tmovevars.airstep = (Q_atof(Info_ValueForKey(cl.serverinfo, \"pm_airstep\")) != 0);\n\tmovevars.pground = (Q_atof(Info_ValueForKey(cl.serverinfo, \"pm_pground\")) != 0)\n\t\t&& (cl.z_ext & Z_EXT_PF_ONGROUND) /* pground doesn't make sense without this */;\n\tmovevars.ktjump = *(p = Info_ValueForKey(cl.serverinfo, \"pm_ktjump\")) ? Q_atof(p) : cl.teamfortress ? 0 : 1;\n\tmovevars.rampjump = (Q_atof(Info_ValueForKey(cl.serverinfo, \"pm_rampjump\")) != 0);\n\tmovevars.safestrafe = Q_atoi(Info_ValueForKey(cl.serverinfo, \"sv_safestrafe\"));\n\n\t// Deathmatch and teamplay.\n\tcl.deathmatch = atoi(Info_ValueForKey(cl.serverinfo, \"deathmatch\"));\n\tnew_teamplay = atoi(Info_ValueForKey(cl.serverinfo, \"teamplay\"));\n\n\t// Timelimit and fraglimit.\n\tcl.timelimit = atoi(Info_ValueForKey(cl.serverinfo, \"timelimit\"));\n\tcl.fraglimit = atoi(Info_ValueForKey(cl.serverinfo, \"fraglimit\"));\n\n\tcl.racing = !strcmp(Info_ValueForKey(cl.serverinfo, \"ktxmode\"), \"race\");\n\tcl.scoring_system = atoi(Info_ValueForKey(cl.serverinfo, \"scoring\"));\n\n\t// Update fakeshaft limits\n\t{\n\t\tchar* p = Info_ValueForKey(cl.serverinfo, \"fakeshaft\");\n\t\tif (!p[0]) {\n\t\t\tp = Info_ValueForKey(cl.serverinfo, \"truelightning\");\n\t\t}\n\n\t\tif (p[0]) {\n\t\t\tcl.fakeshaft_policy = bound(0, Q_atof(p), 1);\n\t\t}\n\t\telse {\n\t\t\tcl.fakeshaft_policy = 1;\n\t\t}\n\t}\n\n\t// Update skins if needed.\n\tskin_refresh = ( !new_teamplay != !cl.teamplay || ( (newfpd ^ cl.fpd) & (FPD_NO_FORCE_COLOR|FPD_NO_FORCE_SKIN) ) );\n\tcl.teamplay = new_teamplay;\n\tcl.fpd = newfpd;\n\tif (skin_refresh)\n\t\tTP_RefreshSkins();\n\n\t// Set fixed teamnames if broadcast by server\n\t{\n\t\tconst char* s = NULL;\n\n\t\tif (*(s = Info_ValueForKey(cl.serverinfo, \"team1\")) || *(s = Info_ValueForKey(cl.serverinfo, \"t1\"))) {\n\t\t\tstrlcpy(cl.fixed_team_names[0], s, sizeof(cl.fixed_team_names[0]));\n\t\t}\n\t\tif (*(s = Info_ValueForKey(cl.serverinfo, \"team2\")) || *(s = Info_ValueForKey(cl.serverinfo, \"t2\"))) {\n\t\t\tstrlcpy(cl.fixed_team_names[1], s, sizeof(cl.fixed_team_names[1]));\n\t\t}\n\t\tif (*(s = Info_ValueForKey(cl.serverinfo, \"team3\")) || *(s = Info_ValueForKey(cl.serverinfo, \"t3\"))) {\n\t\t\tstrlcpy(cl.fixed_team_names[2], s, sizeof(cl.fixed_team_names[2]));\n\t\t}\n\t\tif (*(s = Info_ValueForKey(cl.serverinfo, \"team4\")) || *(s = Info_ValueForKey(cl.serverinfo, \"t4\"))) {\n\t\t\tstrlcpy(cl.fixed_team_names[3], s, sizeof(cl.fixed_team_names[3]));\n\t\t}\n\t}\n}\n\n// Parse a string looking like this: //vwep vwplayer w_axe w_shot w_shot2\nvoid CL_ParseVWepPrecache (char *str)\n{\n\tint i, num;\n\tconst char *p;\n\n\tCmd_TokenizeString (str + 2 /* skip the // */);\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tcl.vwep_enabled = 0;\n\t\treturn;\n\t}\n\n\tif (cls.state == ca_active)\n\t{\n\t\t// vweps can be turned off on the fly, but not back on\n\t\tCom_DPrintf (\"CL_ParseVWepPrecache: ca_active, ignoring\\n\");\n\t\treturn;\n\t}\n\n\tnum = min(Cmd_Argc()-1, MAX_VWEP_MODELS);\n\n\tfor (i = 0; i < num; i++)\n\t{\n\t\tp = Cmd_Argv(i+1);\n\n\t\tif (!strcmp(p, \"-\")) \n\t\t{\n\t\t\t// empty model\n\t\t\tstrlcpy (cl.vw_model_name[i], \"-\", MAX_QPATH);\n\t\t}\n\t\telse \n\t\t{\n\t\t\tif (strstr(p, \"..\") || p[0] == '/' || p[0] == '\\\\')\n\t\t\t\tHost_Error(\"CL_ParseVWepPrecache: illegal model name '%s'\", p);\n\n\t\t\tif (strchr(p, '/'))\n\t\t\t{\n\t\t\t\t// A full path was specified.\n\t\t\t\tstrlcpy(cl.vw_model_name[i], p, sizeof(cl.vw_model_name[0]));\n\t\t\t}\n\t\t\telse \n\t\t\t{\n\t\t\t\t// Use default path.\n\t\t\t\tstrlcpy(cl.vw_model_name[i], \"progs/\", sizeof(cl.vw_model_name[0]));\n\t\t\t\tstrlcat(cl.vw_model_name[i], p, sizeof(cl.vw_model_name[0]));\n\t\t\t}\n\n\t\t\t// Use default extension if not specified.\n\t\t\tif (!strchr(p, '.'))\n\t\t\t\tstrlcat(cl.vw_model_name[i], \".mdl\", sizeof(cl.vw_model_name[0]));\n\t\t}\n\t}\n\n\tCom_DPrintf (\"VWEP precache: %i models\\n\", num);\n}\n\nvoid CL_ParseServerInfoChange (void) \n{\n\tchar key[MAX_INFO_STRING], value[MAX_INFO_STRING];\n\n\tstrlcpy (key, MSG_ReadString(), sizeof(key));\n\tstrlcpy (value, MSG_ReadString(), sizeof(value));\n\n\tCom_DPrintf (\"SERVERINFO: %s=%s\\n\", key, value);\n\tif (!cl.standby && !cl.countdown && !strncmp(key, \"status\", 6)) \n\t{\n\t\tint timeleft = atoi(value) * 60;\n\t\tif (timeleft) \n\t\t{\n\t\t\tif (cls.demoplayback) \n\t\t\t{\n\t\t\t\tcl.gametime = (cl.timelimit * 60) - timeleft;\n\t\t\t}\n\t\t\telse \n\t\t\t{\n\t\t\t\tcl.gamestarttime = Sys_DoubleTime() - (cl.timelimit * 60) + timeleft - cl.gamepausetime;\n\t\t\t\tCom_DPrintf(\"Clock sync, match started %i seconds ago\\n\", (cl.timelimit*60) + atoi(value)*60);\n\t\t\t}\n\t\t}\n\t}\n\n\tInfo_SetValueForKey (cl.serverinfo, key, value, MAX_SERVERINFO_STRING);\n\n\tCL_ProcessServerInfo ();\n}\n\n// Converts quake color 4 to \"&cf00\", 13 to \"&c00f\", 12 to \"&cff0\" and so on\n// those are marks used to make colored text in console\nchar *CL_Color2ConColor(int color)\n{\n\tstatic char buf[6] = \"&c000\";\n\tconst char hexc[] = \"0123456789abcdefffffffffffffffff\";\n\t// For some reason my palette doesn't give higher number then 127, those Fs are there for palettes that give higher than that\n\tint x = Sbar_ColorForMap(color);\n\tbuf[2] = hexc[host_basepal[x * 3 + 0] / 8];\n\tbuf[3] = hexc[host_basepal[x * 3 + 1] / 8];\n\tbuf[4] = hexc[host_basepal[x * 3 + 2] / 8];\n\treturn buf;\n}\n\n// Will add colors to nicks in \"ParadokS rides JohnNy_cz's rocket\"\n// source - source frag message, dest - destination buffer, destlen - length of buffer\n// cff - see the cfrags_format definition \nstatic wchar* CL_ColorizeFragMessage (const wchar *source, cfrags_format *cff)\n{\n\tstatic wchar dest_buf[2048] = {0};\n\twchar col1[6+1] = {0}, col2[6+1] = {0}, *dest = dest_buf, *col_off;\n\tint destlen = sizeof(dest_buf)/sizeof(dest_buf[0]), len;\n\n\tdest[0] = 0; // new string\n\n\tqwcslcpy(col1, str2wcs(CL_Color2ConColor(cff->p1col)), sizeof(col1)/sizeof(wchar));\n\tqwcslcpy(col2, str2wcs(CL_Color2ConColor(cff->p2col)), sizeof(col2)/sizeof(wchar));\n\n\t// before 1st nick\n\tqwcslcpy(dest, source, bound(0, cff->p1pos + 1, destlen));\n\tdestlen -= (len = qwcslen(dest));\n\tdest += len;\n\t\n\t// color1\n\tlen = qwcslen(col1) + 1;\n\tqwcslcpy(dest, col1, bound(0, len, destlen));\n\tdestlen -= (len = qwcslen(dest));\n\tdest += len;\n\t// 1st nick\n\tqwcslcpy(dest, source + cff->p1pos, bound(0, cff->p1len + 1, destlen));\n\tdestlen -= (len = qwcslen(dest));\n\tdest += len;\n\t// color off\n\tlen = qwcslen(col_off = str2wcs(\"&cfff\")) + 1;\n\tqwcslcpy(dest, col_off, bound(0, len, destlen));\n\tdestlen -= (len = qwcslen(dest));\n\tdest += len;\n\n\tif (cff->p2len)\n\t{\n\t\t// middle part\n\t\tqwcslcpy(dest, source + cff->p1pos + cff->p1len, bound(0, cff->p2pos - cff->p1len - cff->p1pos + 1, destlen));\n\t\tdestlen -= (len = qwcslen(dest));\n\t\tdest += len;\n\t\t// color2\n\t\tlen = qwcslen(col2) + 1;\n\t\tqwcslcpy(dest, col2, bound(0, len, destlen));\n\t\tdestlen -= (len = qwcslen(dest));\n\t\tdest += len;\n\t\t// 2nd nick\n\t\tqwcslcpy(dest, source + cff->p2pos, bound(0, cff->p2len + 1, destlen));\n\t\tdestlen -= (len = qwcslen(dest));\n\t\tdest += len;\n\t\t// color off\n\t\tlen = qwcslen(col_off = str2wcs(\"&cfff\")) + 1;\n\t\tqwcslcpy(dest, col_off, bound(0, len, destlen));\n\t\tdestlen -= (len = qwcslen(dest));\n\t\tdest += len;\n\t\t// the rest\n\t\tqwcslcpy(dest, source + cff->p2pos + cff->p2len, destlen);\n\t}\n\telse \n\t{\n\t\t// the rest\n\t\tqwcslcpy(dest, source + cff->p1pos + cff->p1len, destlen);\n\t}\n\n\treturn dest_buf;\n}\n\nextern cvar_t con_highlight, con_highlight_mark, name;\nextern cvar_t cl_showFragsMessages;\nextern cvar_t scr_coloredfrags;\n\n// For CL_ParsePrint\nstatic void FlushString (const wchar *s, int level, qbool team, int offset) \n{\n\textern cvar_t demo_jump_skip_messages;\n\n\tchar *s0; // C-char copy of s\n\n\tchar *mark;\n\tconst wchar *text; // needed for con_highlight work\n\tchar *f; \n\n\twchar zomfg[4096]; // FIXME\n\n\tcfrags_format cff = {0, 0, 0, 0, 0, 0, false}; // Stats_ParsePrint stuff\n\n\t// During gametime, use notify for team messages only\n\tif (con_mm2_only.integer && !team && !(cl.standby || cl.countdown))\n\t\tPrint_flags[Print_current] |= PR_NONOTIFY;\n\n\ts0 = wcs2str (s);\n\tf = strstr (s0, name.string);\n\tCL_SearchForReTriggers (s0, 1<<level); // re_triggers, s0 not modified\n\n\t// Highlighting on && nickname found && it's not our own text (nickname not close to the beginning)\n\tif (con_highlight.value && f && ((f - s0) > 1)) \n\t{\n\t\tswitch ((int)(con_highlight.value)) \n\t\t{\n\t\t\tcase 1:\n\t\t\t\tmark = \"\";\n\t\t\t\ttext = s;\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tmark = con_highlight_mark.string;\n\t\t\t\ttext = (level == PRINT_CHAT) ? (const wchar *) TP_ParseWhiteText (s, team, offset) : s;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\tcase 3:\n\t\t\t\tmark = con_highlight_mark.string;\n\t\t\t\ttext = s;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\telse \n\t{\n\t\tmark = \"\";\n\t\ttext = (level == PRINT_CHAT) ? (const wchar *) TP_ParseWhiteText(s, team, offset) : s;\n\t}\n\n\t// s0 is same after Stats_ParsePrint like before, but Stats_ParsePrint modify it during it's work\n\t// we can change this function a bit, so s0 can be const char*\n\tStats_ParsePrint(s0, level, &cff);\n\n\tif (CL_Demo_SkipMessage(demo_jump_skip_messages.integer >= 1)) {\n\t\treturn;\n\t}\n\n\t// Colorize player names here\n\tif (scr_coloredfrags.value && cff.isFragMsg && cff.p1len) {\n\t\ttext = CL_ColorizeFragMessage(text, &cff);\n\t}\n\n\t/* FIXME\n\t * disconnect: There should be something like Com_PrintfW...\n\t * we have to *Print* message with one Con_Printf/Con_PrintfW call\n\t * else re_triggers with remove option will be fucked\n\t * so we have to use additional buffer\n\t */\n\tmemset (zomfg, 0, sizeof (zomfg));\n\tqwcslcpy (zomfg, str2wcs(mark), sizeof (zomfg) / sizeof (wchar));\n\tqwcslcpy (zomfg + qwcslen (zomfg), text, sizeof (zomfg) / sizeof (wchar) - qwcslen (zomfg));\n\tif (cl_showFragsMessages.value || !cff.isFragMsg) {\n\t\tif (cl_showFragsMessages.integer == 2 && cff.isFragMsg) {\n\t\t\tPrint_flags[Print_current] |= PR_NONOTIFY;\n\t\t}\n\t\tCon_PrintW(zomfg); // FIXME logging\n\t}\n\n\tif (level >= 4)\n\t\treturn;\n\n\tif (team)\n\t\tlevel = 4;\n\n\tTP_SearchForMsgTriggers ((const char *)wcs2str(s + offset), level);\n}\n\n#define  CHAT_MM1   1\n#define  CHAT_MM2   2\n#define  CHAT_SPEC  3\nint SeparateChat(char *chat, int *out_type, char **out_msg)\n{\n    int server_cut = 31;    // maximum characters sent in nick by server\n\n    int i;\n    int classified = -1;\n    int type = 0;\n    char *msg=NULL;\n\n\tfor (i=0; i <= MAX_CLIENTS; i++)\n    {\n        int client = -1;\n        char buf[512];\n\n        if (i == MAX_CLIENTS)\n        {\n            strlcpy (buf, \"console: \", sizeof (buf));\n\n\t\t\tif (!strncmp(chat, buf, strlen(buf)))\n            {\n                client = i;\n                type = CHAT_MM1;\n                msg = chat + strlen(buf);\n            }\n        }\n        else\n        {\n            if (!cl.players[i].name[0])\n                continue;\n\n            snprintf(buf,  sizeof (buf), \"%.*s: \", server_cut, Info_ValueForKey(cl.players[i].userinfo, \"name\"));\n\n\t\t\tif (!strncmp(chat, buf, strlen(buf)))\n            {\n                client = i;\n                type = CHAT_MM1;\n                msg = chat + strlen(buf);\n            }\n\n            snprintf(buf,  sizeof (buf), \"(%.*s): \", server_cut, Info_ValueForKey(cl.players[i].userinfo, \"name\"));\n\n\t\t\tif (!strncmp(chat, buf, strlen(buf)))\n            {\n                client = i;\n                type = CHAT_MM2;\n                msg = chat + strlen(buf);\n            }\n\n            snprintf(buf,  sizeof (buf), \"[SPEC] %.*s: \", server_cut, Info_ValueForKey(cl.players[i].userinfo, \"name\"));\n\n\t\t\tif (!strncmp(chat, buf, strlen(buf)))\n            {\n                client = i;\n                type = CHAT_SPEC;\n                msg = chat + strlen(buf);\n            }\n        }\n\n        if (client >= 0)\n        {\n            if (classified < 0)\n                classified = client;\n            else\n                return -1;\n        }\n    }\n    \n    if (classified < 0)\n        return -1;\n\n    if (out_msg)\n        *out_msg = msg;\n    if (out_type)\n        *out_type = type;\n\n    return classified;\n}\n\n// QTV chat formed like #qtv2_id:qtv2_name: #qtv1_id:qtv1_name: #qtvClient_id:qtvClient_name: chat text\n// above example was for case qtvClient -> qtv1 -> qtv2 -> server\n\n// So, this fucntion intended for skipping leading proxies names and ids, and just return \"qtvClient_name: chat text\"\n\nstatic char *SkipQTVLeadingProxies(char *s)\n{\n\tchar *last = NULL; // pointer to \"qtvClient_name: chat text\" at the end of parsing or NULL if we fail of some kind\n\n\tfor ( ; s[0]; )\n\t{\n\t\tif (s[0] == '#' && isdigit(s[1])) // check do we have # and at least one digit\n\t\t{\n\t\t\ts++; // skip #\n    \n\t\t\twhile(isdigit(s[0])) // skip digits\n\t\t\t\ts++;\n    \n\t\t\tif (s[0] == ':') // ok, seems it was qtv chat\n\t\t\t{\n\t\t\t\tchar *name;\n\n\t\t\t\ts++; // skip :\n\n\t\t\t\tname = s; // remember name start\n\n\t\t\t\t// skip name\n\t\t\t\t// well, this code allow empty names... like \"#id:: chat text\"\n\t\t\t\tif (s[0] != ' ') // name must NOT start from space, unless someone do \"crime\"\n\t\t\t\t{\n\t\t\t\t\twhile(s[0] && s[0] != ':')\n\t\t\t\t\t\ts++;\n\n\t\t\t\t\tif (s[0] == ':')\n\t\t\t\t\t{\n\t\t\t\t\t\tlast = name; // yeah, its well formed qtv chat string\n\n\t\t\t\t\t\tif (!qtv_skipchained.integer)\n\t\t\t\t\t\t\tbreak; // user select to not skip chained proxies\n\n\t\t\t\t\t\ts++; // skip :\n\n\t\t\t\t\t\tif (s[0] == ' ')\n\t\t\t\t\t\t\ts++; // skip space\n\n\t\t\t\t\t\tcontinue; // probably we have chained qtvs, lets check it\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tbreak; // seems it wasn't chat\n\t\t\t}\n\t\t}\n\n\t\tbreak;\n\t}\n\n\treturn last;\n}\n\nextern qbool TP_SuppressMessage (wchar *);\nextern cvar_t cl_chatsound, msg_filter;\nextern cvar_t ignore_qizmo_spec;\n\nvoid CL_ProcessPrint (int level, char* s0)\n{\n\tqbool suppress_talksound;\n\twchar *s, str[2048], *p, check_flood;\n\tchar *qtvtmp, qtvstr[2048];\n\tint flags = 0, offset = 0;\n\tsize_t len;\n\n\tint client;\n\tint type;\n\tchar *msg;\n\n\tchar *chat_sound_file;\n\tfloat chat_sound_vol = 0.0;\n\n\textern cvar_t ignore_no_weapon;\n\textern cvar_t ignore_not_enough_ammo;\n\n\t// { QTV: check do this string is QTV chat\n\tqtvtmp = SkipQTVLeadingProxies(s0);\n\n\tif (qtvtmp)\n\t{\n\t\tchar name[1024] = {0}, *column;\n\n\t\tcolumn = strchr(qtvtmp, ':');\n\n\t\tif (!column)\n\t\t{\n\t\t\t// this must not be the case, but...\n\t\t\tcolumn = qtvtmp;\n\t\t\tname[0] = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstrlcpy(name, qtvtmp, bound(1, column - qtvtmp + 1, (int)sizeof(name)));\n\t\t}\n\n\t\tif      (!strncmp(s0, \"#0:qtv_say_game:\",      sizeof(\"#0:qtv_say_game:\")-1))\n\t\t\tsnprintf(qtvstr, sizeof(qtvstr), \"%s%s%s\\n\", TP_ParseFunChars(qtv_gamechatprefix.string, false), name, column);\n\t\telse if (!strncmp(s0, \"#0:qtv_say_team_game:\", sizeof(\"#0:qtv_say_team_game:\")-1))\n\t\t\tsnprintf(qtvstr, sizeof(qtvstr), \"%s(%s)%s\\n\", TP_ParseFunChars(qtv_gamechatprefix.string, false), name, column);\n\t\telse\n\t\t\tsnprintf(qtvstr, sizeof(qtvstr), \"%s%s%s\\n\", TP_ParseFunChars(qtv_chatprefix.string, false), name, column);\n\n\t\ts0 = qtvstr;\n\t}\n\t// }\n\n\ts = decode_string (s0);\n\n\tqwcslcpy (str, s, sizeof(str)/sizeof(str[0]));\n\ts = str; // so no memmory overwrite, because decode_string() uses static buffers\n\n\tif (level == PRINT_CHAT) \n\t{\n\t\tif (TP_SuppressMessage (s)) {\n\t\t\tCom_DPrintf(\"Suppressed TP message: %s\\n\", s);\n\t\t\treturn;\n\t\t}\n\n\t\ts0 = wcs2str (s); // TP_SuppressMessage may modify the source string, so s0 should be updated\n\n\t\tflags = TP_CategorizeMessage (s0, &offset);\n\t\tif (qtvtmp) {\n\t\t\tflags |= msgtype_qtv;\n\t\t}\n\n\t\tFChecks_CheckRequest (s0);\n\n\t\ts0 = wcs2str (s);\n\n\t\tif (Ignore_Message(s0, flags, offset)) {\n\t\t\tCom_DPrintf(\"Ignoring message: %s\\n\", s0);\n\t\t\treturn;\n\t\t}\n\n\t\tif (flags == 0 && ignore_qizmo_spec.value && strlen (s0) > 0 && s0[0] == '[' && strstr(s0, \"]: \") ) {\n\t\t\tCom_DPrintf(\"Ignoring qizmo message: %s\\n\", s0);\n\t\t\treturn;\n\t\t}\n\n\t\tif (flags == 2 && strstr(s0, \"#inlay#\") ) {\n\t\t\tCom_DPrintf(\"Ignoring unezQuake inlay message: %s\\n\", s0);\n\t\t\treturn;\n\t\t}\n\n\t\tif (flags == 2 && !TP_FilterMessage (s + offset)) {\n\t\t\tCom_DPrintf(\"Filtered message: %s\\n\", s0);\n\t\t\treturn;\n\t\t}\n\n\t\tcheck_flood = Ignore_Check_Flood(s0, flags, offset); // @CHECKME@\n\t\tif (check_flood == IGNORE_NO_ADD) {\n\t\t\tCom_DPrintf(\"Ignoring flood message: %s\\n\", s0);\n\t\t\treturn;\n\t\t}\n\t\telse if (check_flood == NO_IGNORE_ADD)\n\t\t\tIgnore_Flood_Add(s0); // @CHECKME@\n\n\t\tsuppress_talksound = false;\n\n\t\tif (flags == 2) \n\t\t{\n\t\t\tsuppress_talksound = TP_CheckSoundTrigger (s + offset);\n\t\t\ts0 = wcs2str (s); // TP_CheckSoundTrigger may modify the source string, so s0 should be updated\n\t\t}\n\n\t\tif (!cl_chatsound.value ||\t\t\t\t\t\t// no sound at all\n\t\t\t\t(cl_chatsound.value == 2 && flags != 2))\t// only play sound in mm2\n\t\t\tsuppress_talksound = true;\n\n\t\tchat_sound_file = NULL;\n\n\t\tclient = SeparateChat(s0, &type, &msg); // @CHECKME@\n\n\t\tif (client >= 0 && !suppress_talksound)\n\t\t{\n\t\t\tif (\n\t\t\t\t\t(\n\t\t\t\t\t (type == CHAT_MM1 || type == CHAT_SPEC)\n\t\t\t\t\t && (msg_filter.value == 1 || msg_filter.value == 3)\n\t\t\t\t\t)\n\t\t\t\t\t|| (\n\t\t\t\t\t\t(type == CHAT_MM2)\n\t\t\t\t\t\t&& (msg_filter.value == 2 || msg_filter.value == 3)\n\t\t\t\t\t   )\n\t\t\t   ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Allow customisation of sounds...\n\t\t\tif (cl.players[client].spectator)\n\t\t\t{\n\t\t\t\tchat_sound_file = con_sound_spec_file.string;\n\t\t\t\tchat_sound_vol = con_sound_spec_volume.value;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tswitch (type)\n\t\t\t\t{\n\t\t\t\t\tcase CHAT_MM1:\n\t\t\t\t\t\tchat_sound_file = con_sound_mm1_file.string;\n\t\t\t\t\t\tchat_sound_vol = con_sound_mm1_volume.value;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase CHAT_MM2:\n\t\t\t\t\t\tchat_sound_file = con_sound_mm2_file.string;\n\t\t\t\t\t\tchat_sound_vol = con_sound_mm2_volume.value;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase CHAT_SPEC:\n\t\t\t\t\t\tchat_sound_file = con_sound_spec_file.string;\n\t\t\t\t\t\tchat_sound_vol = con_sound_spec_volume.value;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// ... but only when spec (players can still customise volume)\n\t\t\tif (!cls.demoplayback && !cl.spectator) {\n\t\t\t\tchat_sound_file = DEFAULT_CHAT_SOUND;\n\t\t\t}\n\t\t}\n\n\t\tif (chat_sound_file == NULL) {\n\t\t\t// assign \"other\" if not recognized\n\t\t\tchat_sound_file = con_sound_other_file.string;\n\t\t\tchat_sound_vol = con_sound_other_volume.value;\n\t\t}\n\n\t\tif (chat_sound_vol > 0 && !suppress_talksound) {\n\t\t\tS_LocalSoundWithVol(chat_sound_file, chat_sound_vol);\n\t\t}\n\n\t\tif (level >= PRINT_HIGH && s[0]) // actually there are no need for level >= PRINT_HIGH check\n\t\t\tVID_NotifyActivity();\n\n\t\tif (s[0])  // KT sometimes sends empty strings\n\t\t{\n\t\t\t// @CHECKME@\n\t\t\tif (con_timestamps.integer) {\n\t\t\t\tSYSTEMTIME lt;\n\t\t\t\tchar tmpbuf[16];\n\t\t\t\tGetLocalTime (&lt);\n\t\t\t\tif (con_timestamps.integer == 2 && (cl.spectator || cls.demoplayback || cl.standby)) {\n\t\t\t\t\tsnprintf(tmpbuf, sizeof(tmpbuf), \"%2d:%02d:%02d \", lt.wHour, lt.wMinute, lt.wSecond);\n\t\t\t\t\tCom_Printf(tmpbuf);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tsnprintf(tmpbuf, sizeof(tmpbuf), \"%2d:%02d \", lt.wHour, lt.wMinute);\n\t\t\t\t\tCom_Printf(tmpbuf);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (cl_nofake.value == 1 || (cl_nofake.value == 2 && flags != 2)) \n\t\t{\n\t\t\tfor (p = s; *p; p++)\n\t\t\t{\n\t\t\t\tif (*p == 0x0D || (*p == 0x0A && p[1]))\n\t\t\t\t\t*p = ' ';\n\t\t\t}\n\t\t}\n\t}\n\telse if (level == PRINT_HIGH)\n\t{\n\t\tif (ignore_no_weapon.integer > 0 && strncmp(s0, \"no weapon\", 9) == 0)\n\t\t{\n\t\t\treturn;\n\t\t}\n\t\telse if (ignore_not_enough_ammo.integer > 0 && strncmp(s0, \"not enough ammo\", 15) == 0)\n\t\t{\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (cl.sprint_buf[0] && (level != cl.sprint_level || s[0] == 1 || s[0] == 2)) \n\t{\n\t\tFlushString (cl.sprint_buf, cl.sprint_level, false, 0);\n\t\tcl.sprint_buf[0] = 0;\n\t}\n\n\tif (s[0] == 1 || s[0] == 2) \n\t{\n\t\tFlushString (s, level, (flags == 2), offset);\n\t\treturn;\n\t}\n\n\t// Emulate qwcslcat (which is not implemented)\n\tqwcslcpy (cl.sprint_buf + qwcslen(cl.sprint_buf), s, sizeof(cl.sprint_buf)/sizeof(wchar) - qwcslen(cl.sprint_buf));\n\tcl.sprint_level = level;\n\n\tif ((p = qwcsrchr(cl.sprint_buf, '\\n'))) \n\t{\n\t\tlen = p - cl.sprint_buf + 1;\n\t\tmemcpy(str, cl.sprint_buf, len * sizeof(wchar));\n\t\tstr[len] = '\\0';\n\t\tqwcslcpy (cl.sprint_buf, p + 1, sizeof(cl.sprint_buf)/sizeof(wchar));\n\t\tFlushString (str, level, (flags == 2), offset);\n\t}\n}\n\nvoid CL_ParsePrint (void)\n{\n\tint level = MSG_ReadByte ();\n\tchar* s0 = MSG_ReadString ();\n\n\tif (CL_Demo_NotForTrackedPlayer()) {\n\t\treturn;\n\t}\n\n\tCL_ProcessPrint (level, s0);\n}\n\nvoid CL_ParseStufftext (void) \n{\n\tchar *s = MSG_ReadString();\n\n\t// Always process demomarks, regardless of who inserted them\n\tif (!strcmp(s, \"//demomark\\n\"))\n\t{\n\t\t// demo mark\n\t\tif (cls.demoseeking == DST_SEEKING_DEMOMARK) {\n\t\t\tcls.demoseeking = DST_SEEKING_FOUND; // it will reset to the DST_SEEKING_NONE in the deep of the demo code\n\n\t\t\treturn;\n\t\t}\n\t}\n\telse if (!strncmp(s, \"//ktx \", sizeof(\"//ktx \") - 1)) {\n\t\tif (!strncmp(s, \"//ktx race \", sizeof(\"//ktx race \") - 1)) {\n\t\t\tif (!strncmp(s, \"//ktx race pm \", sizeof(\"//ktx race pm \") - 1)) {\n\t\t\t\tcl.race_pacemaker_ent = atoi(s + sizeof(\"//ktx race pm \") - 1);\n\t\t\t}\n\t\t}\n\t\telse if (!strcmp(s, \"//ktx matchstart\\n\")) {\n\t\t\tif (cls.mvdplayback) {\n\t\t\t\tMVDAnnouncer_MatchStart();\n\t\t\t}\n\t\t}\n\t\telse if (!strncmp(s, \"//ktx took \", sizeof(\"//ktx took \") - 1)) {\n\t\t\tif (cls.mvdplayback) {\n\t\t\t\tMVDAnnouncer_ItemTaken(s + 2);\n\t\t\t}\n\t\t}\n\t\telse if (!strncmp(s, \"//ktx timer \", sizeof(\"//ktx timer \") - 1)) {\n\t\t\tif (cls.mvdplayback) {\n\t\t\t\tMVDAnnouncer_StartTimer(s + 2);\n\t\t\t}\n\t\t}\n\t\telse if (!strncmp(s, \"//ktx drop \", sizeof(\"//ktx drop \") - 1)) {\n\t\t\tif (cls.mvdplayback) {\n\t\t\t\tMVDAnnouncer_PackDropped(s + 2);\n\t\t\t}\n\t\t}\n\t\telse if (!strncmp(s, \"//ktx expire \", sizeof(\"//ktx expire \") - 1)) {\n\t\t\tif (cls.mvdplayback) {\n\t\t\t\tMVDAnnouncer_Expired(s + 2);\n\t\t\t}\n\t\t}\n\t\telse if (!strncmp(s, \"//ktx bp \", sizeof(\"//ktx bp \") - 1)) {\n\t\t\tif (cls.mvdplayback) {\n\t\t\t\tMVDAnnouncer_BackpackPickup(s + 2);\n\t\t\t}\n\t\t}\n\t\telse if (!strncmp(s, \"//ktx di \", sizeof(\"//ktx di \") - 1)) {\n\t\t\tif (cl.standby && !CL_Demo_SkipMessage(true)) {\n\t\t\t\t// Ignore if not the tracked player\n\t\t\t\tCL_ReadKtxDamageIndicatorString(s + 2);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Any processing after this point will be ignored if not tracking the target player\n\tif (cls.state == ca_active && CL_Demo_SkipMessage(true))\n\t\treturn;\n\n\tif (!strncmp(s, \"//wps \", sizeof(\"//wps \") - 1) || !strncmp(s, \"alias \", 6) || !strncmp(s, \"cmd mapslist_dl \", sizeof(\"cmd mapslist_dl \") - 1) || !strncmp(s, \"cmd cmdslist_dl \", sizeof(\"cmd cmdslist_dl \") - 1)) {\n\t\tif (developer.integer > 1) {\n\t\t\tCom_DPrintf(\"stufftext: %s\\n\", s);\n\t\t}\n\t}\n\telse {\n\t\tCom_DPrintf (\"stufftext: %s\\n\", s);\t\n\t}\n\n\tif (!strncmp(s, \"alias _cs\", 9))\n\t\tCbuf_AddTextEx(&cbuf_svc, \"alias _cs \\\"wait;+attack;wait;wait;-attack;wait\\\"\\n\");\n\telse if (!strncmp(s, \"alias _y\", 8))\n\t\tCbuf_AddTextEx(&cbuf_svc, \"alias _y \\\"wait;-attack;wait;wait;+attack;wait;wait;-attack;wait\\\"\\n\");\n\telse if (!strcmp(s, \"cmd snap\") || (!strncmp (s, \"r_skyname \", 10) && !strchr (s, '\\n')))\n\t\tCbuf_AddTextEx(&cbuf_svc, va(\"%s\\n\", s));\n\telse if (!strncmp(s, \"//tinfo \", sizeof(\"//tinfo \") - 1))\n\t{\n\t\textern void Parse_TeamInfo(char *s);\n\n\t\tif (!cls.mvdplayback && !check_ktx_ca_wo())\n\t\t{\n\t\t\tParse_TeamInfo( s + 2 );\n\t\t}\n\t}\n\telse if (!strncmp(s, \"//cainfo \", sizeof(\"//cainfo \") - 1))\n\t{\n\t\textern void Parse_CAInfo(char *s);\n\n\t\tParse_CAInfo( s + 2 );\n\t}\n\telse if (!strncmp(s, \"//at \", sizeof(\"//at \") - 1))\n\t{\n\t\t// This is autotrack info from MVD demo/QTV stream, they are almost same.\n\t\textern cvar_t demo_autotrack;\n\t\textern cvar_t mvd_autotrack;\n\n\t\tif (demo_autotrack.integer)\n\t\t{\n\t\t\tCbuf_AddTextEx (&cbuf_svc, va(\"track %s\\n\", s + sizeof(\"//at \")-1));\n\t\t\tif (mvd_autotrack.integer) \n\t\t\t{\n\t\t\t\t// Do not let mvd_autotrack and demo_autotrack fight.\n\t\t\t\tCom_Printf(\"Server autotrack found, client autotrack turned off\\n\");\n\t\t\t\tCvar_SetValue(&mvd_autotrack, 0);\n\t\t\t}\n\t\t}\n\t}\n\telse if (!strncmp(s, \"//vwep \", 7) && s[strlen(s) - 1] == '\\n') \n\t{\n\t\tCL_ParseVWepPrecache(s);\n\t\treturn;\n\t}\n\telse if (!strncmp(s, \"//sn \", sizeof(\"//sn \") - 1))\n\t{\n\t\textern void Parse_Shownick(char *s)\t;\n\t\tParse_Shownick( s + 2 );\n\t}\n\telse if (!strncmp(s, \"//qul \", sizeof(\"//qul \") - 1))\n\t{\n\t\t// qtv user list\n\t\tParse_QtvUserList( s + 2 );\n\t}\n\telse if (!strncmp(s, \"//wps \", sizeof(\"//wps \") - 1))\n\t{\n\t\t// weapon stats\n\t\textern void Parse_WeaponStats(char *s);\n\t\tParse_WeaponStats( s + 2 );\n\t}\n\telse if (!strcmp(s, \"cmd pext\\n\"))\n\t{\n\t\t// If someone requested protocol extensions we support - reply.\n\n#ifdef PROTOCOL_VERSION_FTE\n\t\textern unsigned int CL_SupportedFTEExtensions (void);\n#endif // PROTOCOL_VERSION_FTE\n#ifdef PROTOCOL_VERSION_FTE2\n\t\textern unsigned int CL_SupportedFTEExtensions2 (void);\n#endif // PROTOCOL_VERSION_FTE2\n#ifdef PROTOCOL_VERSION_MVD1\n\t\textern unsigned int CL_SupportedMVDExtensions1(void);\n#endif\n\n\t\tchar tmp[128];\n\t\tchar data[1024] = \"cmd pext\";\n\t\tint ext;\n\n#ifdef PROTOCOL_VERSION_FTE\n\t\text = cls.fteprotocolextensions ? cls.fteprotocolextensions : CL_SupportedFTEExtensions();\n\t\tsnprintf(tmp, sizeof(tmp), \" 0x%x 0x%x\", PROTOCOL_VERSION_FTE, ext);\n\t\tCom_Printf_State(PRINT_DBG, \"PEXT: 0x%x is fte protocol ver and 0x%x is fteprotocolextensions\\n\", PROTOCOL_VERSION_FTE, ext);\n\t\tstrlcat(data, tmp, sizeof(data));\n#endif // PROTOCOL_VERSION_FTE \n\n#ifdef PROTOCOL_VERSION_FTE2\n\t\text = cls.fteprotocolextensions2 ? cls.fteprotocolextensions2 : CL_SupportedFTEExtensions2();\n\t\tsnprintf(tmp, sizeof(tmp), \" 0x%x 0x%x\", PROTOCOL_VERSION_FTE2, ext);\n\t\tCom_Printf_State(PRINT_DBG, \"PEXT: 0x%x is fte protocol ver and 0x%x is fteprotocolextensions2\\n\", PROTOCOL_VERSION_FTE2, ext);\n\t\tstrlcat(data, tmp, sizeof(data));\n#endif // PROTOCOL_VERSION_FTE2 \n\n#ifdef PROTOCOL_VERSION_MVD1\n\t\text = cls.mvdprotocolextensions1 ? cls.mvdprotocolextensions1 : CL_SupportedMVDExtensions1();\n\t\tsnprintf(tmp, sizeof(tmp), \" 0x%x 0x%x\", PROTOCOL_VERSION_MVD1, ext);\n\t\tCom_Printf_State(PRINT_DBG, \"PEXT: 0x%x is mvdsv protocol ver and 0x%x is mvdprotocolextensions1\\n\", PROTOCOL_VERSION_MVD1, ext);\n\t\tstrlcat(data, tmp, sizeof(data));\n#endif\n\n\t\tstrlcat(data, \"\\n\", sizeof(data));\n\t\tCbuf_AddTextEx(&cbuf_svc, data);\n\t}\n\telse if (!strncmp(s, \"//ucmd \", sizeof(\"//ucmd \") - 1))\n\t{\n\t\textern void MVD_ParseUserCommand(const char* s);\n\n\t\tMVD_ParseUserCommand(s + sizeof(\"//ucmd \") - 1);\n\t}\n\telse if (!strncmp(s, \"//finalscores \", sizeof(\"//finalscores \") - 1))\n\t{\n\t\tcmd_alias_t* alias = Cmd_FindAlias(\"f_qtvfinalscores\");\n\n\t\tif (alias) {\n\t\t\tCbuf_AddTextEx(&cbuf_svc, va(\"f_qtvfinalscores %s\\n\", s + sizeof(\"//finalscores \") - 1));\n\t\t}\n\t}\n\telse if (!strcmp(s, \"//authprompt\\n\")) {\n\t\tif (!cls.demoplayback) {\n\t\t\textern cvar_t cl_username;\n\n\t\t\tif (cls.auth_logintoken[0] && cl_username.string[0]) {\n\t\t\t\tCbuf_AddTextEx(&cbuf_main, va(\"cmd login %s\\n\", cl_username.string));\n\t\t\t}\n\t\t}\n\t}\n\telse if (!strncmp(s, \"//challenge \", sizeof(\"//challenge \") - 1)) {\n\t\tif (!cls.demoplayback) {\n\t\t\tCmd_TokenizeString(s + 2);\n\n\t\t\tif (Cmd_Argc() >= 2) {\n\t\t\t\tstrlcpy(cl.auth_challenge, Cmd_Argv(1), sizeof(cl.auth_challenge));\n\n\t\t\t\tif (cls.auth_logintoken[0]) {\n\t\t\t\t\tCentral_FindChallengeResponse(cls.auth_logintoken, cl.auth_challenge);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\telse if (!strncmp(s, \"//mvdsv_ssw \", sizeof(\"//mvdsv_ssw \") - 1)) {\n\t\tif (!cls.demoplayback && !cl.spectator) {\n\t\t\tIN_ServerSideWeaponSelectionResponse(s + sizeof(\"//mvdsv_ssw \") - 1);\n\t\t}\n\t}\n#endif\n\telse\n\t{\n\t\tCbuf_AddTextEx(&cbuf_svc, s);\n\t}\n\n\t// Execute stuffed commands immediately when starting a demo\n\tif (cls.demoplayback && cls.state != ca_active)\n\t\tCbuf_ExecuteEx (&cbuf_svc); // FIXME: execute cbuf_main too?\n}\n\nvoid CL_SetStat (int stat, int value)\n{\n\tint\tj;\n\textern cvar_t scr_gameclock;\n\n\tif (stat < 0 || stat >= MAX_CL_STATS) {\n\t\tHost_Error(\"CL_SetStat: %i is invalid\", stat);\n\t\treturn;\n\t}\n\n\t// Set the stat value for the current player we're parsing in the MVD.\n\tif (cls.mvdplayback)\n\t{\n\t\tint old_value = cl.players[cls.lastto].stats[stat];\n\n\t\tcl.players[cls.lastto].stats[stat] = value;\n\n\t\t// If we're not tracking the active player,\n\t\t// then don't update sbar and such.\n\t\tif (Cam_TrackNum() != cls.lastto) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (stat == STAT_ITEMS && (old_value & IT_ALL_WEAPONS) > (value & IT_ALL_WEAPONS)) {\n\t\t\t// Weapons removed, reset last fired\n\t\t\tcl.lastfired = 0;\n\t\t}\n\t}\n\n\tSbar_Changed();\n\n\tif (stat == STAT_ITEMS)\n\t{\n\t\t// Set flash times.\n\t\tfor (j = 0; j < 32; j++)\n\t\t\tif ( (value & (1 << j)) && !(cl.stats[stat] & (1 << j)) )\n\t\t\t\tcl.item_gettime[j] = cl.time;\n\t}\n\n\t// Reset safestrafe state when respawning (health goes from 0 or less to positive)\n\tif (stat == STAT_HEALTH && value > 0 && cl.stats[stat] <= 0 && !cl.spectator) {\n\t\tmemset(&cl.safestrafe, 0, sizeof(cl.safestrafe));\n\t\tCL_SpawnWarn_SuppressAfterRespawn();\n\t}\n\n\tcl.stats[stat] = value;\n\n#ifdef FTE_PEXT_ACCURATETIMINGS\n\tif (stat == STAT_TIME && (cls.fteprotocolextensions & FTE_PEXT_ACCURATETIMINGS))\n\t{\n\t\tcl.servertime_works = true;\n\t\tcl.servertime = cl.stats[STAT_TIME] * 0.001;\n\t}\n\telse\n#endif\n\t\tif (stat == STAT_TIME && cl.z_ext & Z_EXT_SERVERTIME)\n\t\t{\n\t\t\tcl.servertime_works = true;\n\t\t\tcl.servertime = cl.stats[STAT_TIME] * 0.001;\n\t\t}\n\n\t// Since the server doesn't include overtime data, we can't rely on\n\t// the STAT_MATCHSTARTTIME data when using a game clock that counts\n\t// down. Instead, we'll have to wait for a \"[X] minutes remaining\"\n\t// message to sync the game clock. Without this, during overtime, the\n\t// clock will start counting up instead of down.\n\tif (cl.stats[STAT_MATCHSTARTTIME] && scr_gameclock.value != 2 && scr_gameclock.value != 4)\n\t{\n\t\tcl.gamestarttime = Sys_DoubleTime() - cl.servertime + ((double)cl.stats[STAT_MATCHSTARTTIME])/1000 - cl.gamepausetime;\n\t\tif (cls.mvdplayback && cl.servertime_works && !cl.gametime) {\n\t\t\tcl.gametime = max(0, cl.servertime - cl.stats[STAT_MATCHSTARTTIME] * 0.001);\n\t\t}\n\t}\n\n\tTP_StatChanged(stat, value);\n}\n\nvoid CL_MuzzleFlash (void) \n{\n\tvec3_t forward, right, up, org, angles;\n\tmodel_t *mod;\n\n\tdlight_t *dl;\n\tint i, j, num_ent;\n\tentity_state_t *ent;\n\tplayer_state_t *state;\n\tvec3_t none = {0,0,0};\n\n\ti = MSG_ReadShort();\n\n\tif (CL_Demo_SkipMessage(true))\n\t\treturn;\n\n\tif (!cl_muzzleflash.value)\n\t\treturn;\n\n\tif (!cl.validsequence)\n\t\treturn;\n\n\tif ((unsigned) (i - 1) >= MAX_CLIENTS) \n\t{\n\t\t// A monster firing\n\t\tnum_ent = cl.frames[cl.validsequence & UPDATE_MASK].packet_entities.num_entities;\n\n\t\tfor (j = 0; j < num_ent; j++) {\n\t\t\tent = &cl.frames[cl.validsequence & UPDATE_MASK].packet_entities.entities[j];\n\n\t\t\tif (ent->number == i) {\n\t\t\t\tmod = cl.model_precache[ent->modelindex];\n\n\t\t\t\t// Special muzzleflashes for some enemies.\n\t\t\t\tif (mod->modhint == MOD_SOLDIER) {\n\t\t\t\t\tAngleVectors(ent->angles, forward, right, up);\n\t\t\t\t\tVectorMA(ent->origin, 22, forward, org);\n\t\t\t\t\tVectorMA(org, 10, right, org);\n\t\t\t\t\tVectorMA(org, 12, up, org);\n\n\t\t\t\t\tif (amf_part_muzzleflash.integer) {\n\t\t\t\t\t\tif (!ISPAUSED && amf_coronas.integer) {\n\t\t\t\t\t\t\tR_CoronasEntityNew(C_SMALLFLASH, &cl_entities[ent->number]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tDrawMuzzleflash(org, ent->angles, none);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (mod->modhint == MOD_ENFORCER) {\n\t\t\t\t\tAngleVectors (ent->angles, forward, right, up);\n\t\t\t\t\tVectorMA(ent->origin, 22, forward, org);\n\t\t\t\t\tVectorMA(org, 10, right, org);\n\t\t\t\t\tVectorMA(org, 12, up, org);\n\t\t\t\t\tif (amf_part_muzzleflash.integer) {\n\t\t\t\t\t\tif (amf_coronas.integer) {\n\t\t\t\t\t\t\tR_CoronasEntityNew(C_SMALLFLASH, &cl_entities[ent->number]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tDrawMuzzleflash(org, ent->angles, none);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (mod->modhint == MOD_OGRE) {\n\t\t\t\t\tAngleVectors(ent->angles, forward, right, up);\n\t\t\t\t\tVectorMA(ent->origin, 22, forward, org);\n\t\t\t\t\tVectorMA(org, -8, right, org);\n\t\t\t\t\tVectorMA(org, 14, up, org);\n\t\t\t\t\tif (amf_part_muzzleflash.integer) {\n\t\t\t\t\t\tif (amf_coronas.integer) {\n\t\t\t\t\t\t\tR_CoronasEntityNew(C_SMALLFLASH, &cl_entities[ent->number]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tDrawMuzzleflash(org, ent->angles, none);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tAngleVectors(ent->angles, forward, NULL, NULL);\n\t\t\t\t\tVectorMA(ent->origin, 18, forward, org);\n\t\t\t\t}\n\n\t\t\t\tdl = CL_AllocDlight(-i);\n\t\t\t\tdl->radius = 200 + (rand() & 31);\n\t\t\t\tdl->minlight = 32;\n\t\t\t\tdl->die = cl.time + 0.1;\n\n\t\t\t\t// Blue muzzleflashes for shamblers/teslas\n\t\t\t\tif (mod->modhint == MOD_SHAMBLER || mod->modhint == MOD_TESLA) {\n\t\t\t\t\tdl->type = lt_blue;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tdl->type = lt_muzzleflash;\n\t\t\t\t}\n\t\t\t\tVectorCopy(org, dl->origin);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\tif (cl_muzzleflash.value == 2 && i - 1 == cl.viewplayernum)\n\t\treturn;\n\n\tdl = CL_AllocDlight(-i);\n\tstate = &cl.frames[cls.mvdplayback ? (cl.oldparsecount & UPDATE_MASK) : cl.parsecountmod].playerstate[i - 1];\n\n\tif ((i - 1) == cl.viewplayernum)\n\t{\n\t\tVectorCopy(cl.simangles, angles);\n\t\tVectorCopy(cl.simorg, org);\n\t}\n\telse\n\t{\n\t\tVectorCopy(state->viewangles, angles);\n\t\tVectorCopy(state->origin, org);\n\t}\n\n\tAngleVectors(angles, forward, right, up);\n\tVectorMA(org, 22, forward, org);\n\tVectorMA(org, 10, right, org);\n\tVectorMA(org, 12, up, org);\n\tVectorCopy(org, dl->origin);\n\n\tdl->radius = 200 + (rand()&31);\n\tdl->minlight = 32;\n\tdl->die = cl.time + 0.1;\n\tdl->type = lt_muzzleflash;\n\n\tif (amf_part_muzzleflash.integer) {\n\t\tif (((i - 1) != cl.viewplayernum) || (cameratype != C_NORMAL)) {\n\t\t\tif (amf_coronas.integer) {\n\t\t\t\tR_CoronasNew(C_SMALLFLASH, dl->origin);\n\t\t\t}\n\t\t\tDrawMuzzleflash(org, angles, state->velocity);\n\t\t}\n\t}\n}\n\nvoid CL_ParseQizmoVoice (void) \n{\n\tbyte frame_data[33];\n\tbyte sequence_low;\n\tint sequence;\n\tint voice_id;\n\n\t// Qizmo prefixes each frame with one low sequence byte. The next byte is\n\t// mixed: bits 4-5 extend the sequence, bits 6-7 identify the voice burst,\n\t// and the low nibble remains GSM data restored before decoding.\n\tsequence_low = MSG_ReadByte();\n\n\tMSG_ReadData(frame_data, sizeof(frame_data));\n\tsequence = sequence_low | ((frame_data[0] & 0x30) << 4);\n\tvoice_id = frame_data[0] >> 6;\n\n\tif (!CL_Demo_SkipMessage(true)) {\n\t\tS_QizmoVoice_PlayFrame(sequence, voice_id, frame_data, sizeof(frame_data));\n\t}\n}\n\n#define SHOWNET(x) {if (cl_shownet.value == 2) Com_Printf (\"%3i:%s\\n\", msg_readcount - 1, x);}\n\n// SV_RotateCmd\n// Rotates client command so a high-ping player can better control direction as they exit teleporters on high-ping\nstatic void CL_RotateCmd(usercmd_t* cmd, float yaw_delta)\n{\n\tstatic vec3_t up = { 0, 0, 1 };\n\tvec3_t direction = { cmd->sidemove, cmd->forwardmove, 0 };\n\tvec3_t result;\n\n\tRotatePointAroundVector(result, up, direction, yaw_delta);\n\n\tcmd->sidemove = result[0];\n\tcmd->forwardmove = result[1];\n}\n\nvoid CL_ParseServerMessage (void) \n{\n\tint cmd, i, j = 0;\n\tchar *s;\n\textern int mvd_fixangle;\n\tvec3_t newangles;\n\tint msg_svc_start;\n\tint oldread = 0;\n\n\tif (cl_shownet.value == 1) \n\t{\n\t\tPrint_flags[Print_current] |= PR_TR_SKIP;\n\t\tCom_Printf (\"%i \", net_message.cursize);\n\t}\n\telse if (cl_shownet.value == 2) \n\t{\n\t\tPrint_flags[Print_current] |= PR_TR_SKIP;\n\t\tCom_Printf (\"------------------\\n\");\n\t}\n\n\tcls.demomessage.cursize = 0;\n\n\tCL_ParseClientdata();\n\tCL_ClearProjectiles();\n\n\t// Parse the message.\n\twhile (1) \n\t{\n\t\tif (msg_badread) \n\t\t{\n\t\t\tHost_Error (\"CL_ParseServerMessage: Bad server message\");\n\t\t\tbreak;\n\t\t}\n\n\t\tmsg_svc_start = msg_readcount;\n\t\tcmd = MSG_ReadByte ();\n\n\t\tif (cmd == -1) \n\t\t{\n\t\t\tmsg_readcount++;\t// so the EOM showner has the right value\n\t\t\tSHOWNET(\"END OF MESSAGE\");\n\t\t\tbreak;\n\t\t}\n\n\t\tif (cmd == svc_qizmovoice)\n\t\t\tSHOWNET(\"svc_qizmovoice\")\n\t\telse if (cmd < num_svc_strings)\n\t\t\tSHOWNET(svc_strings[cmd]);\n\n\t\t// Update msg no:\n\t\tif (cmd < NUMMSG)\n\t\t\tcl_messages[cmd].msgs++;\n\n\t\toldread = msg_readcount;\n\n\t\t// Other commands\n\t\tswitch (cmd) \n\t\t{\n\t\t\tdefault:\n\t\t\t\t{\t\n\t\t\t\t\tHost_Error (\"CL_ParseServerMessage: Illegible server message (%d)\\n\", cmd);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_nop:\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_disconnect:\n\t\t\t\t{\n\t\t\t\t\tVID_NotifyActivity();\n\n\t\t\t\t\tif (cls.mvdplayback == QTV_PLAYBACK)\n\t\t\t\t\t{ \n\t\t\t\t\t\t// Workaround, do not disconnect in case of QTV playback\n\t\t\t\t\t\tif (net_message.cursize > msg_readcount && strcmp(s = MSG_ReadString(), \"EndOfDemo\"))\n\t\t\t\t\t\t\tCom_Printf(\"WARNING: Non-standard disconnect message from QTV '%s'\\n\", s);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (cls.mvdplayback == MVD_FILE_PLAYBACK) {\n\t\t\t\t\t\t// MVD playback, but not QTV stream.\n\t\t\t\t\t\t// We still have some data, so lets try ignore disconnect since it probably multy map MVD.\n\t\t\t\t\t\tint ms;\n\n\t\t\t\t\t\tif (Demo_BufferSize(&ms)) {\n\t\t\t\t\t\t\tif (net_message.cursize > msg_readcount && strcmp(s = MSG_ReadString(), \"EndOfDemo\")) {\n\t\t\t\t\t\t\t\tCom_Printf(\"WARNING: Non-standard disconnect message in MVD '%s'\\n\", s);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tCom_DPrintf(\"Ignoring Server disconnect\\n\");\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (cls.state == ca_connected) \n\t\t\t\t\t{\n\t\t\t\t\t\tHost_Error( \"Server disconnected\\n\"\n\t\t\t\t\t\t\t\t\"Server version may not be compatible\");\n\t\t\t\t\t} \n\t\t\t\t\telse if (cls.demoplayback && cls.state == ca_demostart) \n\t\t\t\t\t{\n\t\t\t\t\t\tCom_DPrintf(\"Server disconnect found - hadn't started yet, ignoring.\\n\");\n\t\t\t\t\t}\n\t\t\t\t\telse \n\t\t\t\t\t{\n\t\t\t\t\t\tCom_DPrintf(\"Server disconnected\\n\");\n\t\t\t\t\t\tHost_EndGame();\t// The server will be killed if it tries to kick the local player.\n\t\t\t\t\t\tHost_Abort();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase nq_svc_time:\n\t\t\t\t{\n\t\t\t\t\tMSG_ReadFloat();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_print:\n\t\t\t\t{\n\t\t\t\t\tCL_ParsePrint();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_centerprint:\n\t\t\t\t{\n\t\t\t\t\t// SCR_CenterPrint (MSG_ReadString ());\n\t\t\t\t\t// Centerprint re-triggers\n\t\t\t\t\ts = MSG_ReadString();\n\n\t\t\t\t\tif (CL_Demo_SkipMessage (true))\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tif (!cls.demoseeking)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (!CL_SearchForReTriggers(s, RE_PRINT_CENTER))\n\t\t\t\t\t\t\tSCR_CenterPrint(s);\n\t\t\t\t\t\tPrint_flags[Print_current] = 0;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_stufftext:\n\t\t\t\t{\n\t\t\t\t\tCL_ParseStufftext();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_damage:\n\t\t\t\t{\n\t\t\t\t\tV_ParseDamage();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_serverdata:\n\t\t\t\t{\n\t\t\t\t\tCbuf_ExecuteEx(&cbuf_svc);\t\t// make sure any stuffed commands are done\n\t\t\t\t\tCL_ParseServerData();\n\t\t\t\t\tvid.recalc_refdef = true;\t\t// leave full screen intermission\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_setangle:\n\t\t\t\t{\n\t\t\t\t\tif (cls.mvdplayback || (cls.mvdprotocolextensions1 & MVD_PEXT1_HIGHLAGTELEPORT)) {\n\t\t\t\t\t\tj = MSG_ReadByte();\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (i = 0; i < 3; i++) {\n\t\t\t\t\t\tnewangles[i] = MSG_ReadAngle();\n\t\t\t\t\t}\n\n\t\t\t\t\tif (CL_Demo_SkipMessage(true)) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tCL_DisableLerpMove();\n\n\t\t\t\t\tif (cls.mvdplayback) \n\t\t\t\t\t{\n\t\t\t\t\t\tmvd_fixangle |= 1 << j;\n\n\t\t\t\t\t\tif (j == Cam_TrackNum()) {\n\t\t\t\t\t\t\tVectorCopy(newangles, cl.viewangles);\n\t\t\t\t\t\t}\n\t\t\t\t\t} \n\t\t\t\t\telse {\n\t\t\t\t\t\tVectorCopy (newangles, cl.viewangles);\n\n\t\t\t\t\t\tif ((cls.mvdprotocolextensions1 & MVD_PEXT1_HIGHLAGTELEPORT) && j) {\n\t\t\t\t\t\t\t// Update all subsequent packets with amended directions to try and keep prediction correct\n\t\t\t\t\t\t\tframe_t* this_frame = &cl.frames[cl.parsecount & UPDATE_MASK];\n\t\t\t\t\t\t\tfloat yaw_delta = newangles[YAW] - this_frame->cmd.angles[YAW];\n\n\t\t\t\t\t\t\tfor (i = 2; i < UPDATE_BACKUP - 1 && cl.validsequence + i < cls.netchan.outgoing_sequence; i++) {\n\t\t\t\t\t\t\t\tframe_t* f = &cl.frames[(cl.validsequence + i) & UPDATE_MASK];\n\n\t\t\t\t\t\t\t\tif (j == 1) {\n\t\t\t\t\t\t\t\t\t// Teleport\n\t\t\t\t\t\t\t\t\tCL_RotateCmd(&f->cmd, yaw_delta);\n\n\t\t\t\t\t\t\t\t\tcl.viewangles[YAW] = f->cmd.angles[YAW] + yaw_delta;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse if (j == 2) {\n\t\t\t\t\t\t\t\t\t// Respawn, angle sent from client was over-ruled\n\t\t\t\t\t\t\t\t\tf->cmd.angles[YAW] = newangles[YAW];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_lightstyle:\n\t\t\t\t{\n\t\t\t\t\ti = MSG_ReadByte ();\n\t\t\t\t\tif (i >= MAX_LIGHTSTYLES)\n\t\t\t\t\t\tHost_Error (\"svc_lightstyle > MAX_LIGHTSTYLES\");\n\t\t\t\t\tstrlcpy(cl_lightstyle[i].map, MSG_ReadString(), sizeof(cl_lightstyle[i].map));\n\t\t\t\t\tcl_lightstyle[i].length = strlen(cl_lightstyle[i].map);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_sound:\n\t\t\t\t{\n\t\t\t\t\tCL_ParseStartSoundPacket();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_stopsound:\n\t\t\t\t{\n\t\t\t\t\ti = MSG_ReadShort();\n\n\t\t\t\t\tif (CL_Demo_SkipMessage (true))\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tS_StopSound(i >> 3, i & 7);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n#ifdef FTE_PEXT2_VOICECHAT\n\t\t\tcase svc_fte_voicechat:\n\t\t\t{\n\t\t\t\tS_Voip_Parse();\n\t\t\t\tbreak;\n\t\t\t}\n#endif\n\n\t\t\tcase svc_updatefrags:\n\t\t\t\t{\n\t\t\t\t\tSbar_Changed();\n\t\t\t\t\ti = MSG_ReadByte();\n\t\t\t\t\tif (i >= MAX_CLIENTS)\n\t\t\t\t\t\tHost_Error(\"CL_ParseServerMessage: svc_updatefrags > MAX_CLIENTS\");\n\t\t\t\t\tcl.players[i].frags = MSG_ReadShort();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_updateping:\n\t\t\t\t{\n\t\t\t\t\ti = MSG_ReadByte();\n\t\t\t\t\tif (i >= MAX_CLIENTS)\n\t\t\t\t\t\tHost_Error(\"CL_ParseServerMessage: svc_updateping > MAX_CLIENTS\");\n\t\t\t\t\tcl.players[i].ping = MSG_ReadShort();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_updatepl:\n\t\t\t\t{\n\t\t\t\t\ti = MSG_ReadByte();\n\t\t\t\t\tif (i >= MAX_CLIENTS)\n\t\t\t\t\t\tHost_Error(\"CL_ParseServerMessage: svc_updatepl > MAX_CLIENTS\");\n\t\t\t\t\tcl.players[i].pl = MSG_ReadByte();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_updateentertime:\n\t\t\t\t{\n\t\t\t\t\t// Time is sent over as seconds ago.\n\t\t\t\t\ti = MSG_ReadByte();\n\t\t\t\t\tif (i >= MAX_CLIENTS)\n\t\t\t\t\t\tHost_Error (\"CL_ParseServerMessage: svc_updateentertime > MAX_CLIENTS\");\n\t\t\t\t\tcl.players[i].entertime = (cls.demoplayback ? cls.demotime : cls.realtime) - MSG_ReadFloat();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_spawnbaseline:\n\t\t\t\t{\n\t\t\t\t\ti = MSG_ReadShort();\n\t\t\t\t\tCL_ParseBaseline(&cl_entities[i].baseline);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n#if defined (PROTOCOL_VERSION_FTE) && defined (FTE_PEXT_SPAWNSTATIC2)\n\t\t\tcase svc_fte_spawnbaseline2:\n\t\t\t\t{\n\t\t\t\t\tCL_ParseSpawnBaseline2();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n#endif // PROTOCOL_VERSION_FTE\n\t\t\tcase svc_spawnstatic:\n\t\t\t\t{\n\t\t\t\t\tCL_ParseStatic(false);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n#if defined (PROTOCOL_VERSION_FTE) && defined (FTE_PEXT_SPAWNSTATIC2)\n\t\t\tcase svc_fte_spawnstatic2:\n\t\t\t\t{\n\t\t\t\t\tif (cls.fteprotocolextensions & FTE_PEXT_SPAWNSTATIC2)\n\t\t\t\t\t\tCL_ParseStatic(true);\n\t\t\t\t\telse\n\t\t\t\t\t\tHost_Error(\"CL_ParseServerMessage: svc_fte_spawnstatic2 without FTE_PEXT_SPAWNSTATIC2\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n#endif // PROTOCOL_VERSION_FTE\n\t\t\tcase svc_temp_entity:\n\t\t\t\t{\n\t\t\t\t\tCL_ParseTEnt();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_killedmonster:\n\t\t\t\t{\n\t\t\t\t\tcl.stats[STAT_MONSTERS]++;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_foundsecret:\n\t\t\t\t{\n\t\t\t\t\tcl.stats[STAT_SECRETS]++;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_updatestat:\n\t\t\t\t{\n\t\t\t\t\ti = MSG_ReadByte();\n\t\t\t\t\tj = MSG_ReadByte();\n\n\t\t\t\t\tCL_SetStat(i, j);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_updatestatlong:\n\t\t\t\t{\n\t\t\t\t\ti = MSG_ReadByte();\n\t\t\t\t\tj = MSG_ReadLong();\n\n\t\t\t\t\tCL_SetStat(i, j);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_spawnstaticsound:\n\t\t\t\t{\n\t\t\t\t\tCL_ParseStaticSound();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_cdtrack:\n\t\t\t\t{\n\t\t\t\t\tcl.cdtrack = MSG_ReadByte ();\n\t\t\t\t\tCDAudio_Play((byte)cl.cdtrack, true);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_intermission:\n\t\t\t\t{\n\t\t\t\t\tcl.intermission = 1;\n\t\t\t\t\tcl.completed_time = cls.demoplayback ? cls.demotime : cls.realtime;\n\t\t\t\t\tcl.solo_completed_time = cl.servertime;\n\t\t\t\t\tvid.recalc_refdef = true;\t// go to full screen\n\t\t\t\t\tfor (i = 0; i < 3; i++)\n\t\t\t\t\t\tcl.simorg[i] = MSG_ReadCoord();\n\t\t\t\t\tfor (i = 0; i < 3; i++)\n\t\t\t\t\t\tcl.simangles[i] = MSG_ReadAngle();\n\t\t\t\t\tVectorClear(cl.simvel);\n\t\t\t\t\tTP_ExecTrigger (\"f_mapend\");\n\n\t\t\t\t\tif (cls.demoseeking == DST_SEEKING_END) {\n\t\t\t\t\t\tcls.demoseeking = DST_SEEKING_FOUND_NOREWIND; // it will reset to the DST_SEEKING_NONE in the deep of the demo code\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_finale:\n\t\t\t\t{\n\t\t\t\t\tcl.intermission = 2;\n\t\t\t\t\tcl.completed_time = cls.demoplayback ? cls.demotime : cls.realtime;\n\t\t\t\t\tcl.solo_completed_time = cl.servertime;\n\t\t\t\t\tvid.recalc_refdef = true;\t// go to full screen\n\t\t\t\t\tSCR_CenterPrint(MSG_ReadString ());\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_sellscreen:\n\t\t\t\t{\n\t\t\t\t\tCmd_ExecuteString(\"help\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_smallkick:\n\t\t\t\t{\n\t\t\t\t\tif (CL_Demo_SkipMessage (true))\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcl.ideal_punchangle = -2;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_bigkick:\n\t\t\t\t{\n\t\t\t\t\tif (CL_Demo_SkipMessage (true))\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcl.ideal_punchangle = -4;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_muzzleflash:\n\t\t\t\t{\n\t\t\t\t\tCL_MuzzleFlash();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_updateuserinfo:\n\t\t\t\t{\n\t\t\t\t\tCL_UpdateUserinfo();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_setinfo:\n\t\t\t\t{\n\t\t\t\t\tCL_SetInfo();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_serverinfo:\n\t\t\t\t{\n\t\t\t\t\tCL_ParseServerInfoChange();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_download:\n\t\t\t\t{\n\t\t\t\t\tCL_ParseDownload();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_playerinfo:\n\t\t\t\t{\n\t\t\t\t\tif (cls.demorecording)\n\t\t\t\t\t{\n\t\t\t\t\t\tCL_InitialiseDemoMessageIfRequired();\n\t\t\t\t\t\tMSG_WriteByte(&cls.demomessage, svc_playerinfo);\n\t\t\t\t\t}\n\t\t\t\t\tCL_ParsePlayerinfo();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_nails:\n\t\t\t\t{\n\t\t\t\t\tCL_ParseProjectiles(false);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_nails2:\n\t\t\t\t{\n\t\t\t\t\tif (!cls.mvdplayback)\n\t\t\t\t\t\tHost_Error(\"CL_ParseServerMessage: svc_nails2 without cls.mvdplayback\");\n\t\t\t\t\tCL_ParseProjectiles(true);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_chokecount: // Some preceding packets were choked\n\t\t\t\t{\n\t\t\t\t\ti = MSG_ReadByte();\n\t\t\t\t\tfor (j = cls.netchan.incoming_acknowledged - 1; i > 0 && j > cls.netchan.outgoing_sequence - UPDATE_BACKUP; j--) \n\t\t\t\t\t{\n\t\t\t\t\t\tif (cl.frames[j & UPDATE_MASK].receivedtime != -3) \n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcl.frames[j & UPDATE_MASK].receivedtime = -2;\n\t\t\t\t\t\t\ti--;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_modellist:\n\t\t\t\t{\n\t\t\t\t\tCL_ParseModellist(false);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n#if defined (PROTOCOL_VERSION_FTE) && defined (FTE_PEXT_MODELDBL)\n\t\t\tcase svc_fte_modellistshort:\n\t\t\t\t{\n\t\t\t\t\tif (cls.fteprotocolextensions & FTE_PEXT_MODELDBL)\n\t\t\t\t\t\tCL_ParseModellist(true);\n\t\t\t\t\telse\n\t\t\t\t\t\tHost_Error(\"CL_ParseServerMessage: svc_fte_modellistshort without FTE_PEXT_MODELDBL\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n#endif  // PROTOCOL_VERSION_FTE\n\t\t\tcase svc_soundlist:\n\t\t\t\t{\n\t\t\t\t\tCL_ParseSoundlist();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_packetentities:\n\t\t\t\t{\n\t\t\t\t\tCL_ParsePacketEntities(false);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_deltapacketentities:\n\t\t\t\t{\n\t\t\t\t\tCL_ParsePacketEntities(true);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_maxspeed:\n\t\t\t\t{\n\t\t\t\t\tfloat newspeed = MSG_ReadFloat ();\n\n\t\t\t\t\tif (CL_Demo_SkipMessage (false))\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcl.maxspeed = newspeed;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_entgravity :\n\t\t\t\t{\n\t\t\t\t\tfloat newgravity = MSG_ReadFloat ();\n\n\t\t\t\t\tif (CL_Demo_SkipMessage (false))\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcl.entgravity = newgravity;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_setpause:\n\t\t\t\t{\n\t\t\t\t\tif (MSG_ReadByte())\n\t\t\t\t\t\tcl.paused |= PAUSED_SERVER;\n\t\t\t\t\telse\n\t\t\t\t\t\tcl.paused &= ~PAUSED_SERVER;\n\n\t\t\t\t\tif (ISPAUSED) {\n\t\t\t\t\t\tCDAudio_Pause();\n\t\t\t\t\t\tCL_StorePausePredictionLocations();\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tCDAudio_Resume();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tcase svc_qizmovoice:\n\t\t\t\t{\n\t\t\t\t\tCL_ParseQizmoVoice();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\n\t\t// cl_messages, update size\n\t\tif (cmd < NUMMSG)\n\t\t\tcl_messages[cmd].size += msg_readcount - oldread;\n\n\t\tif (cls.demorecording)\n\t\t{\n\t\t\t// Init the demo message buffer if it hasn't been done.\n\t\t\tCL_InitialiseDemoMessageIfRequired();\n\n\t\t\t// Write the change in entities to the demo being recorded\n\t\t\t// or the net message we just received.\n\t\t\tif (cmd == svc_deltapacketentities) {\n\t\t\t\textern cvar_t cl_demo_qwd_delta;\n\t\t\t\tint newpacket = cls.netchan.incoming_sequence & UPDATE_MASK;\n\t\t\t\tint deltaseq = cl.frames[newpacket].delta_sequence;\n\t\t\t\tqbool delta_valid = deltaseq > 0 && !cl.frames[deltaseq & UPDATE_MASK].invalid && cl.frames[deltaseq & UPDATE_MASK].in_qwd;\n\n\t\t\t\t// Only write if it was parsed correctly and we've written the original packet to qwd\n\t\t\t\tif (cl_demo_qwd_delta.integer && cl.validsequence == cls.netchan.incoming_sequence && delta_valid) {\n\t\t\t\t\tSZ_Write(&cls.demomessage, net_message.data + msg_svc_start, msg_readcount - msg_svc_start);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Write from baselines instead (old way)\n\t\t\t\t\tCL_WriteDemoEntities();\n\t\t\t\t}\n\t\t\t\tcl.frames[newpacket].in_qwd = true;\n\t\t\t}\n\t\t\telse if (cmd == svc_download) {\n\t\t\t\t// there's no point in writing it to the demo\n\t\t\t}\n\t\t\telse if (cmd == svc_serverdata) {\n\t\t\t\tCL_WriteServerdata(&cls.demomessage);\n\t\t\t}\n\t\t\telse if (cmd != svc_playerinfo) {\n\t\t\t\t// We write svc_playerinfo out as we read it in\n\t\t\t\tSZ_Write(&cls.demomessage, net_message.data + msg_svc_start, msg_readcount - msg_svc_start);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (cls.demorecording)\n\t{\n\t\t// Write the gathered changes to the demo file.\n\t\tif (cls.demomessage.cursize)\n\t\t\tCL_WriteDemoMessage (&cls.demomessage);\n\t}\n\n\tCL_SetSolidEntities ();\n}\n\nstatic void CL_DemoMessageBufferOverflow(struct sizebuf_s* buf, int length)\n{\n\tint newsize = buf->maxsize + MAX_MSGLEN * 2;\n\tbyte* mem = Q_realloc(buf->data, newsize);\n\n\tif (mem) {\n\t\tbuf->maxsize = newsize;\n\t\tbuf->data = mem;\n\t}\n}\n\nstatic void CL_InitialiseDemoMessageIfRequired(void)\n{\n\tif (!cls.demomessage.maxsize) {\n\t\tbyte* data = Q_malloc(MAX_MSGLEN * 2);\n\n\t\tSZ_InitEx2(&cls.demomessage, data, MAX_MSGLEN * 2, false, CL_DemoMessageBufferOverflow);\n\t}\n\tif (!cls.demomessage.cursize) {\n\t\tSZ_Write(&cls.demomessage, net_message.data, 8);\n\t}\n}\n\n#ifdef MVD_PEXT1_HIDDEN_MESSAGES\n// Hidden data packets (stuffed into mvd/qtv stream)\nvoid CL_ParseHiddenDataMessage(void)\n{\n\twhile (true) {\n\t\tint size = LittleLong(MSG_ReadLong());\n\t\tint protocol_version = 0, type;\n\n\t\tif (size == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\ttype = LittleLong(MSG_ReadShort());\n\t\twhile (type == 0xFFFF && size > 0) {\n\t\t\ttype = LittleLong(MSG_ReadShort());\n\t\t\t++protocol_version;\n\t\t}\n\n\t\tif (protocol_version != 0) {\n\t\t\tMSG_ReadSkip(size);\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch (type) {\n\t\tcase mvdhidden_antilag_position:\n\t\t\tCL_ParseAntilagPosition(size);\n\t\t\tbreak;\n\t\tcase mvdhidden_demoinfo:\n\t\t\tCL_ParseDemoInfo(size);\n\t\t\tbreak;\n\t\tcase mvdhidden_usercmd_weapons:\n\t\t\tCL_ParseDemoWeapon(size, false);\n\t\t\tbreak;\n\t\tcase mvdhidden_usercmd_weapons_ss:\n\t\t\tCL_ParseDemoWeapon(size, true);\n\t\t\tbreak;\n\t\tcase mvdhidden_usercmd:\n\t\t\tCL_ParseUserCommand(size);\n\t\t\tbreak;\n\t\tcase mvdhidden_dmgdone:\n\t\t\tCL_ParseDamageDone(size);\n\t\t\tbreak;\n\t\tcase mvdhidden_usercmd_weapon_instruction:\n\t\t\tCL_ParseDemoWeaponInstruction(size);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tMSG_ReadSkip(size);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n#define DEMOINFO_BLOCK_SIZE (16 * 1024)\nstatic byte* demoinfo_buffer;\nstatic size_t demoinfo_buffer_size;\n\nstatic void CL_ParseDemoInfo(int size)\n{\n\tint block_number = LittleShort(MSG_ReadShort());\n\tsize -= sizeof(short);\n\n\tif (cl.demoinfo_bytes + size >= demoinfo_buffer_size) {\n\t\tsize_t new_size = ((cl.demoinfo_bytes + size + DEMOINFO_BLOCK_SIZE) / DEMOINFO_BLOCK_SIZE) * DEMOINFO_BLOCK_SIZE;\n\t\tdemoinfo_buffer = Q_realloc_named(demoinfo_buffer, new_size, \"demoinfo_buffer\");\n\t\tdemoinfo_buffer_size = new_size;\n\t}\n\n\tif (block_number == cl.demoinfo_blocknumber + 1 || block_number == 0) {\n\t\tMSG_ReadData(demoinfo_buffer + cl.demoinfo_bytes, size);\n\t\tcl.demoinfo_bytes += size;\n\t\tcl.demoinfo_blocknumber = block_number;\n\t}\n\telse {\n\t\tMSG_ReadSkip(size);\n\t\tcl.demoinfo_bytes = 0;\n\t\tcl.demoinfo_blocknumber = 0;\n\t}\n\n\tif (block_number == 0 && cl.demoinfo_bytes > 0) {\n\t\t// finished, parse stats and do something useful here\n#if 0\n\t\tFILE* file = fopen(\"qw/demoinfo.txt\", \"wb\");\n\t\tif (file) {\n\t\t\tfwrite(demoinfo_buffer, 1, cl.demoinfo_bytes, file);\n\t\t\tfclose(file);\n\t\t}\n#endif\n\t\tQ_free(demoinfo_buffer);\n\t\tdemoinfo_buffer_size = 0;\n\t\tcl.demoinfo_bytes = 0;\n\t\tcl.demoinfo_blocknumber = 0;\n\t}\n}\n\nstatic void CL_ParseAntilagPosition(int size)\n{\n\tmvdhidden_antilag_position_header_t header;\n\tint old_readcount = msg_readcount;\n\n\theader.playernum = MSG_ReadByte();\n\theader.players = MSG_ReadByte();\n\theader.incoming_seq = LittleLong(MSG_ReadLong());\n\theader.server_time = LittleFloat(MSG_ReadFloat());\n\theader.target_time = LittleFloat(MSG_ReadFloat());\n\n\tsize -= (msg_readcount - old_readcount);\n\tif (size != header.players * sizeof_mvdhidden_antilag_position_t) {\n\t\tCon_DPrintf(\"unexpected size: %d vs %d (%d players)\\n\", size, header.players * sizeof_mvdhidden_antilag_position_t, header.players);\n\t\tMSG_ReadSkip(size);\n\t}\n\telse if (header.playernum != cl.viewplayernum) {\n\t\tMSG_ReadSkip(size);\n\t}\n\telse {\n\t\tmvdhidden_antilag_position_t position;\n\t\tint i;\n\n\t\tmemset(&cl.antilag_positions, 0, sizeof(cl.antilag_positions));\n\t\tfor (i = 0; i < header.players; ++i) {\n\t\t\tqbool clientpos_valid = false;\n\n\t\t\tposition.playernum = MSG_ReadByte();\n\t\t\tclientpos_valid = position.playernum & MVD_PEXT1_ANTILAG_CLIENTPOS;\n\t\t\tposition.playernum &= ~MVD_PEXT1_ANTILAG_CLIENTPOS;\n\n\t\t\tposition.pos[0] = LittleFloat(MSG_ReadFloat());\n\t\t\tposition.pos[1] = LittleFloat(MSG_ReadFloat());\n\t\t\tposition.pos[2] = LittleFloat(MSG_ReadFloat());\n\t\t\tposition.msec = MSG_ReadByte();\n\t\t\tposition.predmodel = MSG_ReadByte();\n\t\t\tposition.clientpos[0] = LittleFloat(MSG_ReadFloat());\n\t\t\tposition.clientpos[1] = LittleFloat(MSG_ReadFloat());\n\t\t\tposition.clientpos[2] = LittleFloat(MSG_ReadFloat());\n\n\t\t\tif (position.playernum >= 0 && position.playernum < MAX_CLIENTS) {\n\t\t\t\tVectorCopy(position.pos, cl.antilag_positions[position.playernum].pos);\n\t\t\t\tcl.antilag_positions[position.playernum].present = true;\n\t\t\t\tVectorCopy(position.clientpos, cl.antilag_positions[position.playernum].clientpos);\n\t\t\t\tcl.antilag_positions[position.playernum].clientpresent = clientpos_valid;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void CL_ParseDemoWeaponInstruction(int size)\n{\n\textern cvar_t cl_debug_weapon_view;\n\n\tbyte playernum;\n\tbyte flags;\n\tint mode;\n\tbyte weaponlist[10];\n\n\tplayernum = MSG_ReadByte();\n\tflags = MSG_ReadByte();\n\tLittleLong(MSG_ReadLong()); // sequence_set =\n\tmode = LittleLong(MSG_ReadLong());\n\n\tMSG_ReadData(weaponlist, sizeof(weaponlist));\n\n\tif (cl_debug_weapon_view.integer && playernum == cl.viewplayernum) {\n\t\tchar description[128] = { 0 };\n\t\tint i;\n\n\t\tstrlcat(description, \"WS(I) \", sizeof(description));\n\t\tif (flags & MVDHIDDEN_SSWEAPON_HIDE_AXE) {\n\t\t\tstrlcat(description, \"hide(1) \", sizeof(description));\n\t\t}\n\t\tif (flags & MVDHIDDEN_SSWEAPON_HIDE_SG) {\n\t\t\tstrlcat(description, \"hide(2) \", sizeof(description));\n\t\t}\n\t\tif (flags & MVDHIDDEN_SSWEAPON_HIDEONDEATH) {\n\t\t\tstrlcat(description, \"hod \", sizeof(description));\n\t\t}\n\t\tif (flags & MVDHIDDEN_SSWEAPON_ENABLED) {\n\t\t\tstrlcat(description, \"enabled \", sizeof(description));\n\t\t}\n\t\telse {\n\t\t\tstrlcat(description, \"disabled \", sizeof(description));\n\t\t}\n\t\tif (flags & MVDHIDDEN_SSWEAPON_FORGETORDER) {\n\t\t\tstrlcat(description, \"forget \", sizeof(description));\n\t\t}\n\t\tif (mode & clc_mvd_weapon_mode_presel) {\n\t\t\tstrlcat(description, \"presel \", sizeof(description));\n\t\t}\n\t\tif (mode & clc_mvd_weapon_mode_iffiring) {\n\t\t\tstrlcat(description, \"iffiring \", sizeof(description));\n\t\t}\n\n\t\tstrlcat(description, \"[\", sizeof(description));\n\t\tfor (i = 0; i < sizeof(weaponlist) / sizeof(weaponlist[0]); ++i) {\n\t\t\tif (!weaponlist[i]) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tstrlcat(description, va(\"%s%d\", (i ? \",\" : \"\"), weaponlist[i]), sizeof(description));\n\t\t}\n\t\tstrlcat(description, \"]\", sizeof(description));\n\n\t\tCon_Printf(\"%s\\n\", description);\n\t}\n}\n\nstatic void CL_ParseDemoWeapon(int size, qbool server_side)\n{\n\textern cvar_t cl_debug_weapon_view;\n\tbyte playernum = MSG_ReadByte();\n\tint items = LittleLong(MSG_ReadLong());\n\tbyte shells = MSG_ReadByte();\n\tbyte nails = MSG_ReadByte();\n\tbyte rockets = MSG_ReadByte();\n\tbyte cells = MSG_ReadByte();\n\tbyte choice = MSG_ReadByte();\n\tchar* str = MSG_ReadString();\n\tchar indicator = (server_side ? 'S' : 'C');\n\n\tif (cl_debug_weapon_view.integer && playernum == cl.viewplayernum) {\n\t\tchar script_options[128] = { 0 };\n\t\tconst char* has_weapon = \"&c0f0\";\n\t\tconst char* no_weapon = \"&cf00\";\n\t\tconst char* no_ammo = \"&cff0\";\n\t\tconst char* unknown_weapon = \"[\";\n\n\t\twhile (*str) {\n\t\t\tconst char* prefix = unknown_weapon;\n\t\t\tconst char* postfix = \"\";\n\t\t\tswitch (*str) {\n\t\t\tcase 1:\n\t\t\t\tprefix = (items & IT_AXE) ? has_weapon : no_weapon;\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tprefix = (items & IT_SHOTGUN) ? (shells > 0 ? has_weapon : no_ammo) : no_weapon;\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\tprefix = (items & IT_SUPER_SHOTGUN) ? (shells > 1 ? has_weapon : no_ammo) : no_weapon;\n\t\t\t\tbreak;\n\t\t\tcase 4:\n\t\t\t\tprefix = (items & IT_NAILGUN) ? (nails > 0 ? has_weapon : no_ammo) : no_weapon;\n\t\t\t\tbreak;\n\t\t\tcase 5:\n\t\t\t\tprefix = (items & IT_SUPER_NAILGUN) ? (nails > 1 ? has_weapon : no_ammo) : no_weapon;\n\t\t\t\tbreak;\n\t\t\tcase 6:\n\t\t\t\tprefix = (items & IT_GRENADE_LAUNCHER) ? (rockets > 0 ? has_weapon : no_ammo) : no_weapon;\n\t\t\t\tbreak;\n\t\t\tcase 7:\n\t\t\t\tprefix = (items & IT_ROCKET_LAUNCHER) ? (rockets > 0 ? has_weapon : no_ammo) : no_weapon;\n\t\t\t\tbreak;\n\t\t\tcase 8:\n\t\t\t\tprefix = (items & IT_LIGHTNING) ? (rockets > 0 ? has_weapon : no_ammo) : no_weapon;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tprefix = \"[\";\n\t\t\t\tpostfix = \"]\";\n\t\t\t}\n\t\t\tstrlcat(script_options, va(\"%s%d&r%s\", prefix, str[0], postfix), sizeof(script_options));\n\t\t\tstr++;\n\t\t}\n\n\t\tCon_Printf(\"WS(%c) %s, A %d,%d,%d,%d => %d\\n\", indicator, script_options, shells, nails, rockets, cells, choice);\n\t}\n}\n\nvoid CL_SpawnDamageIndicator(int deathtype, int targ_ent, int damage, qbool splash_damage, qbool team_damage);\n\nstatic void CL_ParseDamageDone(int size)\n{\n\tshort deathtype;\n\tshort attacker_ent;\n\tshort targ_ent;\n\tshort damage;\n\tqbool splash_damage;\n\tint player = Cam_TrackNum();\n\n\tif (size != 8) {\n\t\t// Unknown\n\t\tMSG_ReadSkip(size);\n\t\treturn;\n\t}\n\n\tdeathtype = MSG_ReadShort();\n\tattacker_ent = MSG_ReadShort();\n\ttarg_ent = MSG_ReadShort();\n\tdamage = MSG_ReadShort();\n\n\tsplash_damage = (deathtype & MVDHIDDEN_DMGDONE_SPLASHDAMAGE);\n\tdeathtype &= ~MVDHIDDEN_DMGDONE_SPLASHDAMAGE;\n\n\t// Don't trigger for self damage\n\tif (player + 1 == attacker_ent && player + 1 != targ_ent) {\n\t\tqbool team_damage = (targ_ent >= 1 && targ_ent <= MAX_CLIENTS && cl.players[targ_ent - 1].teammate);\n\n\t\tCL_SpawnDamageIndicator(deathtype, targ_ent, damage, splash_damage, team_damage);\n\t}\n}\n\nstatic void CL_ParseUserCommand(int size)\n{\n\tbyte playernum, dropnum, msec;\n\tvec3_t angles;\n\tshort forward, side, up;\n\tbyte buttons, impulse;\n\tframe_t* frame;\n\n\tif (size != sizeof_mvdhidden_block_header_t_usercmd) {\n\t\tMSG_ReadSkip(size);\n\t\treturn;\n\t}\n\n\tplayernum = MSG_ReadByte();\n\tdropnum = MSG_ReadByte();\n\tmsec = MSG_ReadByte();\n\tangles[0] = MSG_ReadFloat();\n\tangles[1] = MSG_ReadFloat();\n\tangles[2] = MSG_ReadFloat();\n\tforward = MSG_ReadShort();\n\tside = MSG_ReadShort();\n\tup = MSG_ReadShort();\n\tbuttons = MSG_ReadByte();\n\timpulse = MSG_ReadByte();\n\n\tif (playernum >= MAX_CLIENTS) {\n\t\treturn;\n\t}\n\n\tif (dropnum != 0) {\n\t\t// replaying an old packet due to loss in this frame\n\t\treturn;\n\t}\n\n\tframe = &cl.frames[cl.validsequence & UPDATE_MASK];\n\tif (frame->playerstate[playernum].messagenum == cl.parsecount || frame->playerstate[playernum].messagenum == cl.oldparsecount) {\n\t\tusercmd_t* cmd = &frame->playerstate[playernum].command;\n\n\t\tVectorCopy(angles, cmd->angles);\n\t\tcmd->buttons = buttons;\n\t\tcmd->forwardmove = forward;\n\t\tcmd->sidemove = side;\n\t\tcmd->upmove = up;\n\t\tcmd->impulse = impulse;\n\t\tcmd->msec = msec;\n\t}\n}\n\n#endif // #ifdef MVD_PEXT1_HIDDEN_MESSAGES\n"
  },
  {
    "path": "src/cl_pred.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n#include \"quakedef.h\"\n#include \"pmove.h\"\n\n\ncvar_t\tcl_nopred\t= {\"cl_nopred\", \"0\"};\n\nextern cvar_t cl_independentPhysics;\n\n#ifdef JSS_CAM\ncvar_t cam_thirdperson = {\"cam_thirdperson\", \"0\"};\ncvar_t cam_dist = {\"cam_dist\", \"100\"};\ncvar_t cam_lockdir = {\"cam_lockdir\", \"0\"};\ncvar_t cam_lockpos = {\"cam_lockpos\", \"0\"};\nstatic vec3_t saved_angles;\nqbool clpred_newpos = false;\n#endif\n\nstatic qbool nolerp[2];\nstatic qbool nolerp_nextpos;\n\nvoid CL_PredictUsercmd (player_state_t *from, player_state_t *to, usercmd_t *u) {\n\t// split up very long moves\n\tif (u->msec > 50) {\n\t\tplayer_state_t temp;\n\t\tusercmd_t split;\n\n\t\tsplit = *u;\n\t\tsplit.msec /= 2;\n\n\t\tCL_PredictUsercmd (from, &temp, &split);\n\t\tCL_PredictUsercmd (&temp, to, &split);\n\t\treturn;\n\t}\n\n\tVectorCopy (from->origin, pmove.origin);\n\tVectorCopy (u->angles, pmove.angles);\n\tVectorCopy (from->velocity, pmove.velocity);\n\n\tpmove.jump_msec = (cl.z_ext & Z_EXT_PM_TYPE) ? 0 : from->jump_msec;\n\tpmove.jump_held = from->jump_held;\n\tpmove.waterjumptime = from->waterjumptime;\n\tpmove.pm_type = from->pm_type;\n\tpmove.onground = from->onground;\n\tpmove.cmd = *u;\n\n#ifdef JSS_CAM\n\tif (cam_lockdir.value) {\n\t\tVectorCopy (saved_angles, pmove.cmd.angles);\n\t\tVectorCopy (saved_angles, pmove.angles);\n\t}\n\telse\n\t\tVectorCopy (pmove.cmd.angles, saved_angles);\n#endif\n\n\tmovevars.entgravity = cl.entgravity;\n\tmovevars.maxspeed = cl.maxspeed;\n\tmovevars.bunnyspeedcap = cl.bunnyspeedcap;\n\n\tPM_PlayerMove();\n\n\tto->waterjumptime = pmove.waterjumptime;\n\tto->pm_type = pmove.pm_type;\n\tto->jump_held = pmove.jump_held;\n\tto->jump_msec = pmove.jump_msec;\n\tpmove.jump_msec = 0;\n\n\tVectorCopy (pmove.origin, to->origin);\n\tVectorCopy (pmove.angles, to->viewangles);\n\tVectorCopy (pmove.velocity, to->velocity);\n\tto->onground = pmove.onground;\n\n\tto->weaponframe = from->weaponframe;\n}\n\n//Used when cl_nopred is 1 to determine whether we are on ground, otherwise stepup smoothing code produces ugly jump physics\nvoid CL_CategorizePosition (void) {\n\tif (cl.spectator && cl.playernum == cl.viewplayernum) {\n\t\tcl.onground = false;\t// in air\n\t\treturn;\n\t}\n\tVectorClear (pmove.velocity);\n\tVectorCopy (cl.simorg, pmove.origin);\n\tpmove.numtouch = 0;\n\tPM_CategorizePosition ();\n\tcl.onground = pmove.onground;\n}\n\n//Smooth out stair step ups.\n//Called before CL_EmitEntities so that the player's lightning model origin is updated properly\nvoid CL_CalcCrouch (void)\n{\n\tqbool teleported;\n\tstatic vec3_t oldorigin = {0, 0, 0};\n\tstatic float oldz = 0, extracrouch = 0, crouchspeed = 100;\n\n\tteleported = nolerp[0] || !VectorL2Compare(cl.simorg, oldorigin, 48);\n\tVectorCopy(cl.simorg, oldorigin);\n\n\tif (teleported) {\n\t\t// possibly teleported or respawned\n\t\toldz = cl.simorg[2];\n\t\textracrouch = 0;\n\t\tcrouchspeed = 100;\n\t\tcl.crouch = 0;\n\t\treturn;\n\t}\n\n\tif (cl.onground && cl.simorg[2] - oldz > 0) {\n\t\tif (cl.simorg[2] - oldz > 20) {\n\t\t\t// if on steep stairs, increase speed\n\t\t\tif (crouchspeed < 160) {\n\t\t\t\textracrouch = cl.simorg[2] - oldz - cls.frametime * 200 - 15;\n\t\t\t\textracrouch = min(extracrouch, 5);\n\t\t\t}\n\t\t\tcrouchspeed = 160;\n\t\t}\n\n\t\toldz += cls.frametime * crouchspeed;\n\t\tif (oldz > cl.simorg[2])\n\t\t\toldz = cl.simorg[2];\n\n\t\tif (cl.simorg[2] - oldz > 15 + extracrouch)\n\t\t\toldz = cl.simorg[2] - 15 - extracrouch;\n\t\textracrouch -= cls.frametime * 200;\n\t\textracrouch = max(extracrouch, 0);\n\n\t\tcl.crouch = oldz - cl.simorg[2];\n\t}\n\telse {\n\t\t// in air or moving down\n\t\toldz = cl.simorg[2];\n\t\tcl.crouch += cls.frametime * 150;\n\t\tif (cl.crouch > 0)\n\t\t\tcl.crouch = 0;\n\t\tcrouchspeed = 100;\n\t\textracrouch = 0;\n\t}\n}\n\nvoid CL_DisableLerpMove(void)\n{\n\tnolerp[0] = nolerp[1] = nolerp_nextpos = true;\n}\n\nstatic void CL_LerpMove (qbool angles_lerp)\n{\t\n\tstatic int\t\tlastsequence = 0;\n\tstatic vec3_t\tlerp_angles[3];\n\tstatic vec3_t\tlerp_origin[3];\n\tstatic double\tlerp_times[3];\n\tstatic double\tdemo_latency = 0.01;\n\tfloat\tfrac;\n\tdouble\tsimtime;\n\tint\t\ti;\n\tint\t\tfrom, to;\n\textern cvar_t cl_nolerp;\n\textern int cmdtime_msec;\n\textern double physframetime;\n\textern qbool cl_nolerp_on_entity_flag;\n\tdouble  current_time = cls.demoplayback ? cls.demotime : cls.realtime;\n\tdouble  current_lerp_time = cls.demoplayback ? cls.demopackettime : (cmdtime_msec * 0.001);\n\tqbool   physframe = cls.netchan.outgoing_sequence != lastsequence;\n\n\tif ((cl_nolerp.value || cl_nolerp_on_entity_flag)) {\n\t\t//reset\n\t\tnolerp[0] = nolerp[1] = nolerp_nextpos = false;\n\t\tlastsequence = ((unsigned)-1) >> 1;\n\t\treturn;\n\t}\n\n\tif (cls.netchan.outgoing_sequence < lastsequence) \n\t{\n\t\t// reset\n\t\tlastsequence = -1;\n\t\tlerp_times[0] = -1;\n\t\tdemo_latency = 0.01;\n\t}\n\n\t// Independent physics.\n\tif (physframe)\n\t{\n\t\tlastsequence = cls.netchan.outgoing_sequence;\n\n\t\t// move along\n\t\tlerp_times[2] = lerp_times[1];\n\t\tlerp_times[1] = lerp_times[0];\n\t\tlerp_times[0] = current_lerp_time;\n\n\t\tVectorCopy (lerp_origin[1], lerp_origin[2]);\n\t\tVectorCopy (lerp_origin[0], lerp_origin[1]);\n\t\tVectorCopy (cl.simorg, lerp_origin[0]);\n\n\t\tVectorCopy (lerp_angles[1], lerp_angles[2]);\n\t\tVectorCopy (lerp_angles[0], lerp_angles[1]);\n\t\tVectorCopy (cl.simangles, lerp_angles[0]);\n\n\t\tnolerp[1] = nolerp[0];\n\t\tnolerp[0] = nolerp_nextpos;\n\t\tnolerp_nextpos = false;\n\n\t\tfor (i = 0; i < 3; i++)\n\t\t{\n\t\t\tif (fabs(lerp_origin[0][i] - lerp_origin[1][i]) > 100)\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (i < 3)\n\t\t{\n\t\t\textern cvar_t cl_earlypackets;\n\n\t\t\t// a teleport or something\n\t\t\tnolerp[0] = true;\n\n\t\t\t// cl.simangles will already be set, don't lerp there either\n\t\t\t// (gives a flash of looking in wrong direction at teleport entrance)\n\t\t\tnolerp[1] |= (cl_earlypackets.integer);\n\t\t}\n\t}\n\n\tsimtime = current_time - demo_latency;\n\n\t// Adjust latency\n\tif (simtime > lerp_times[0]) \n\t{\n\t\t// High clamp\n\t\tdemo_latency = current_time - lerp_times[0];\n\t}\n\telse if (simtime < lerp_times[2]) \n\t{\n\t\t// Low clamp\n\t\tdemo_latency = current_time - lerp_times[2];\n\t} \n\telse\n\t{\n\t\t// slowly drift down till corrected\n\t\tif (physframe)\n\t\t\tdemo_latency -= min(physframetime * 0.005, 0.001);\n\t}\n\n\t// decide where to lerp from\n\tif (simtime > lerp_times[1]) {\n\t\tfrom = 1;\n\t\tto = 0;\n\t} \n\telse {\n\t\tfrom = 2;\n\t\tto = 1;\n\t}\n\n\tif (nolerp[to]) {\n\t\treturn;\n\t}\n\n    frac = (simtime - lerp_times[from]) / (lerp_times[to] - lerp_times[from]);\n    frac = bound (0, frac, 1);\n\n\tif ((cl.spectator && cl.viewplayernum != cl.playernum) || angles_lerp) {\n\t\t// we track someone, so lerp angles\n\t\tAngleInterpolate(lerp_angles[from], frac, lerp_angles[to], cl.simangles);\n\t}\n\n\tfor (i = 0; i < 3; i++)\t{\n\t\tcl.simorg[i] = lerp_origin[from][i] + (lerp_origin[to][i] - lerp_origin[from][i]) * frac;\n\t}\n}\n\nqbool cl_nolerp_on_entity_flag = false;\n// function check_standing_on_entity(void)\n// raises flag cl_nolerp_on_entity_flag if standing on entity\n// and cl_nolerp_on_entity.value is 1\nstatic void check_standing_on_entity(void)\n{\n  extern cvar_t cl_nolerp;\n  extern cvar_t cl_nolerp_on_entity;\n  extern cvar_t cl_independentPhysics;\n  cl_nolerp_on_entity_flag = \n       (pmove.onground && pmove.groundent > 0 &&\n        cl_nolerp_on_entity.value &&\n        cl_independentPhysics.value);\n}\n\nvoid CL_PredictMove (qbool physframe) {\n\tint i, oldphysent;\n\tframe_t *from = NULL, *to;\n\tqbool angles_lerp = false;\n\n\tif (cl.paused && !CL_MultiviewEnabled())\n\t\treturn;\n\n\tif (cl.intermission) {\n\t\tcl.crouch = 0;\n\t\treturn;\n\t}\n\n\tif (cls.nqdemoplayback)\n\t\treturn;\n\n\tif (!cl.validsequence)\n\t\treturn;\n\n\tif (cls.netchan.outgoing_sequence - cl.validsequence >= UPDATE_BACKUP - 1)\n\t\treturn;\n\n\tVectorCopy (cl.viewangles, cl.simangles);\n\n\t// this is the last valid frame received from the server\n\tto = &cl.frames[cl.validsequence & UPDATE_MASK];\n\n\n#ifdef JSS_CAM\n\tif (clpred_newpos && cls.demoplayback && cl.spectator)\n\t\tVectorCopy (cl.simorg, to->playerstate[cl.playernum].origin);\n\tclpred_newpos = false;\n#endif\n\n\t// FIXME...\n\tif (cls.demoplayback && cl.spectator && cl.viewplayernum != cl.playernum) {\n\t\tVectorCopy (to->playerstate[cl.viewplayernum].velocity, cl.simvel);\n#ifdef JSS_CAM\n\t\tif (!(cam_thirdperson.value && cam_lockpos.value) && !clpred_newpos)\n\t\t\tVectorCopy (to->playerstate[cl.viewplayernum].origin, cl.simorg);\n\t\tif (!cam_thirdperson.value)\n\t\t\tVectorCopy (to->playerstate[cl.viewplayernum].viewangles, cl.simangles);\n#endif\n\t\tCL_CategorizePosition ();\n\t}\n\telse if (to->playerstate[cl.playernum].pm_type == PM_LOCK)\n\t{\n\t\tangles_lerp = true;\n\n\t\tVectorCopy (to->playerstate[cl.playernum].velocity, cl.simvel);\n\t\tVectorCopy (to->playerstate[cl.playernum].origin, cl.simorg);\n\t\tVectorCopy (to->playerstate[cl.playernum].command.angles, cl.simangles);\n\t\tcl.onground = false;\n\t}\n\telse if ((cl_nopred.value && !cls.mvdplayback) || cl.validsequence + 1 >= cls.netchan.outgoing_sequence) {\n\t\tVectorCopy (to->playerstate[cl.playernum].velocity, cl.simvel);\n\t\tVectorCopy (to->playerstate[cl.playernum].origin, cl.simorg);\n\t\tCL_CategorizePosition ();\n\t}\n\telse if (physframe || !cl_independentPhysics.value)\n\t{\n\t\toldphysent = pmove.numphysent;\n\t\tCL_SetSolidPlayers (cl.playernum);\n\n\t\t// run frames\n\t\tfor (i = 1; i < UPDATE_BACKUP - 1 && cl.validsequence + i < cls.netchan.outgoing_sequence; i++) {\n\t\t\tfrom = to;\n\t\t\tto = &cl.frames[(cl.validsequence + i) & UPDATE_MASK];\n\t\t\tCL_PredictUsercmd (&from->playerstate[cl.playernum], &to->playerstate[cl.playernum], &to->cmd);\n\t\t}\n\n\t\tpmove.numphysent = oldphysent;\n\n\t\t// save results\n\t\tVectorCopy (to->playerstate[cl.playernum].velocity, cl.simvel);\n\t\tVectorCopy (to->playerstate[cl.playernum].origin, cl.simorg);\n\t\tcl.onground = pmove.onground;\n\t\tcl.waterlevel = pmove.waterlevel;\n\t\tcheck_standing_on_entity();\n\t}\n\n\tif (!cls.mvdplayback && cl_independentPhysics.value != 0) {\n\t\tCL_LerpMove (angles_lerp || cls.demoplayback);\n\t}\n    CL_CalcCrouch ();\n\n#ifdef JSS_CAM\n\tif (cam_thirdperson.value && cam_lockpos.value && cl.viewplayernum != cl.playernum) {\n\t\tvec3_t v;\n\t\tplayer_state_t *pstate;\n\t\tint i;\n\n\t\ti = cl.viewplayernum;\n\n\t\tpstate = &cl.frames[cl.parsecount & UPDATE_MASK].playerstate[i];\n\t\tVectorSubtract (pstate->origin, cl.simorg, v);\n\t\tvectoangles (v, cl.simangles);\n\t\tcl.simangles[PITCH] = -cl.simangles[PITCH];\n\t}\n\telse if (cam_thirdperson.value && cl.viewplayernum != cl.playernum) {\n\t\tint i;\n\t\tplayer_state_t *pstate;\n\t\tvec3_t fw, rt, up;\n\n\t\ti = cl.viewplayernum;\n\n\t\tpstate = &cl.frames[cl.parsecount & UPDATE_MASK].playerstate[i];\n\n\t\tAngleVectors (cl.simangles, fw, rt, up);\n\t\tVectorMA (pstate->origin, -cam_dist.value, fw, cl.simorg);\n\t}\n#endif\t// JSS_CAM\n\t\n}\n\nvoid CL_InitPrediction(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_NETWORK);\n\tCvar_Register(&cl_nopred);\n\tCvar_ResetCurrentGroup();\n\n#ifdef JSS_CAM\n\tCvar_SetCurrentGroup(CVAR_GROUP_SPECTATOR);\n\tCvar_Register(&cam_thirdperson);\n\tCvar_Register(&cam_dist);\n\tCvar_Register(&cam_lockdir);\n\tCvar_Register(&cam_lockpos);\n\tCvar_ResetCurrentGroup();\n#endif\n}\n \n"
  },
  {
    "path": "src/cl_screen.c",
    "content": "/*\nCopyright (C) 1996-2003 Id Software, Inc., A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: cl_screen.c,v 1.156 2007-10-29 00:56:47 qqshka Exp $\n*/\n\n/// declarations may be found in screen.h\n\n#include \"quakedef.h\"\n#include <time.h>\n#include \"vx_tracker.h\"\n#include \"gl_model.h\"\n#include \"mvd_utils.h\"\n#include \"keys.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"hud_editor.h\"\n#include \"utils.h\"\n#include \"vx_stuff.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"input.h\"\n#include \"utils.h\"\n#include \"sbar.h\"\n#include \"menu.h\"\n#include \"Ctrl.h\"\n#include \"qtv.h\"\n#include \"demo_controls.h\"\n#include \"r_trace.h\"\n#include \"r_lightmaps.h\"\n#include \"r_local.h\"\n#include \"r_chaticons.h\"\n#include \"r_renderer.h\"\n#include \"r_matrix.h\"\n#include \"qsound.h\"\n\n#ifndef CLIENTONLY\n#include \"server.h\"\n#endif\n\nvoid WeaponStats_CommandInit(void);\nvoid SCR_DrawHud(void);\nvoid SCR_DrawClocks(void);\nvoid R_SetupFrame(void);\nvoid SCR_Draw_TeamInfo(void);\nvoid SCR_Draw_ShowNick(void);\nvoid SCR_DrawQTVBuffer(void);\nvoid SCR_DrawFPS(void);\nvoid SCR_DrawSpeed(void);\nqbool V_PreRenderView(void);\n\nstatic void SCR_DrawDamageIndicators(void);\nvoid SCR_SetupDamageIndicators(void);\nstatic void SCR_RegisterDamageIndicatorCvars(void);\nvoid CL_SpawnDamageIndicator(int deathtype, int targ_ent, int damage, qbool splash_damage, qbool team_damage);\n\nint\tglx, gly, glwidth, glheight;\n\n#define ALPHA_COLOR(x, y) RGBA_TO_COLOR((x)[0],(x)[1],(x)[2],(y))\n\nextern byte\tcurrent_pal[768];\t// Tonik\n\nint\thost_screenupdatecount; // kazik - HUD -> hexum\n\n// only the refresh window will be updated unless these variables are flagged\nint\t\tscr_copytop;\nint\t\tscr_copyeverything;\n\nfloat\tscr_con_current;\nfloat\tscr_conlines;           // lines of console to display\n\nqbool\tscr_skipupdate;\nqbool\tblock_drawing;\n\nfloat\toldscreensize, oldfov, oldsbar;\n\n#define ZOOMEDFOV 35\nqbool\tzoomedin;\nfloat\tunzoomedfov;\nfloat\tunzoomedsensitivity;\n\nvoid OnFovChange (cvar_t *var, char *value, qbool *cancel);\nvoid OnDefaultFovChange (cvar_t *var, char *value, qbool *cancel);\ncvar_t\tscr_fov\t\t\t\t\t= {\"fov\", \"90\", CVAR_NONE, OnFovChange};\t// 10 - 140\ncvar_t\tdefault_fov\t\t\t\t= {\"default_fov\", \"90\", CVAR_NONE, OnDefaultFovChange};\ncvar_t\tscr_viewsize\t\t\t= {\"viewsize\", \"100\", CVAR_NONE};\ncvar_t\tscr_consize\t\t\t\t= {\"scr_consize\", \"0.5\"};\ncvar_t\tscr_conspeed\t\t\t= {\"scr_conspeed\", \"9999\"};\ncvar_t\tscr_showturtle\t\t\t= {\"showturtle\", \"0\"};\ncvar_t\tscr_showpause\t\t\t= {\"showpause\", \"1\"};\n\ncvar_t\tscr_newHud              = {\"scr_newhud\", \"0\"};\n\ncvar_t\tshow_velocity_3d\t\t= {\"show_velocity_3d\", \"0\"};\ncvar_t\tshow_velocity_3d_offset_forward\t= {\"show_velocity_3d_offset_forward\", \"2.5\"};\ncvar_t\tshow_velocity_3d_offset_down\t= {\"show_velocity_3d_offset_down\", \"5\"};\n\ncvar_t\tcl_hud\t\t\t\t\t= {\"cl_hud\", \"1\"};\t// QW262 HUD.\n\ncvar_t\tgl_triplebuffer\t\t\t= {\"gl_triplebuffer\", \"1\"};\ncvar_t  r_chaticons_alpha\t\t= {\"r_chaticons_alpha\", \"0.8\"};\ncvar_t\tscr_coloredfrags\t\t= {\"scr_coloredfrags\", \"0\"};\n\ncvar_t\tscr_cursor_scale\t\t= {\"scr_cursor_scale\", \"0.2\"};\t\t\t// The mouse cursor scale.\ncvar_t\tscr_cursor_iconoffset_x\t= {\"scr_cursor_iconoffset_x\", \"0\"};\t// How much the cursor icon should be offseted from the cursor.\ncvar_t\tscr_cursor_iconoffset_y\t= {\"scr_cursor_iconoffset_y\", \"0\"};\ncvar_t\tscr_cursor_alpha\t\t= {\"scr_cursor_alpha\", \"1\"};\n\ncvar_t  scr_showcrosshair       = {\"scr_showcrosshair\", \"1\"}; // so crosshair does't affected by +showscores, or vice versa\ncvar_t  scr_notifyalways        = {\"scr_notifyalways\", \"0\"}; // don't hide notification messages in intermission\n\ncvar_t  scr_fovmode             = {\"scr_fovmode\", \"0\"}; // When using reduced viewsize, reduce vertical fov or letterbox the screen\ncvar_t  r_drawhud               = {\"r_drawhud\", \"1\"};   // disables hud rendering\n\nqbool\tscr_initialized;\t// Ready to draw.\n\nmpic_t\t*scr_ram;\nmpic_t\t*scr_net;\nmpic_t\t*scr_turtle;\nmpic_t  *scr_cursor;\t\t// Cursor image.\nmpic_t\t*scr_cursor_icon;\t// Cursor icon image (load icon or similar).\n\nint\t\tscr_fullupdate;\n\nint\t\tclearconsole;\nint\t\tclearnotify;\n\nviddef_t vid; // global video state\n\nvrect_t\tscr_vrect;\n\nqbool\tscr_skipupdate;\n\nqbool\tscr_drawloading;\nqbool\tscr_disabled_for_loading;\nfloat\tscr_disabled_time;\n\nqbool\tblock_drawing;\n\ndouble cursor_x = 0, cursor_y = 0;\n\nvoid SCR_DrawMultiviewBorders(void);\nvoid SCR_DrawMVStatus(void);\nvoid SCR_DrawMVStatusStrings(void);\n\n/************************************ FOV ************************************/\n\nextern\tcvar_t\t\tv_idlescale;\nqbool\tconcussioned = false;\n\nvoid OnFovChange (cvar_t *var, char *value, qbool *cancel)\n{\n\tfloat newfov = Q_atof(value);\n\n\tif (newfov > 140)\n\t\tnewfov = 140;\n\telse if (newfov < 10)\n\t\tnewfov = 10;\n\n\tif (newfov == scr_fov.value && newfov == default_fov.value) {\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tif (cbuf_current != &cbuf_svc) {\n\t\tCvar_SetValue (&default_fov, newfov);\n\t\tif (concussioned && !cls.demoplayback) {\n\t\t\t*cancel = true;\n\t\t\treturn;\n\t\t}\n\t} else {\n\t\tif (newfov != 90 && cl.teamfortress && v_idlescale.value >= 20) {\n\t\t\tconcussioned = true;\n\t\t\tif (v_idlescale.value == 100)\n\t\t\t\tTP_ExecTrigger (\"f_conc\");\n\t\t} else if (newfov == 90 && cl.teamfortress) {\n\t\t\tconcussioned = false;\n\t\t}\n\t\tif (cls.demoplayback) { // && !cl_fovfromdemo.value)\n\t\t\t*cancel = true;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tvid.recalc_refdef = true;\n\tif (newfov == 90) {\n\t\tCvar_Set (&scr_fov,default_fov.string);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tCvar_SetValue (&scr_fov, newfov);\n\t*cancel = true;\n}\n\nvoid OnDefaultFovChange (cvar_t *var, char *value, qbool *cancel)\n{\n\tfloat newfov = Q_atof(value);\n\n\tif (newfov < 10.0 || newfov > 140.0){\n\t\tCom_Printf(\"Invalid default_fov\\n\");\n\t\t*cancel = true;\n\t}\n}\n\n// Much above this for fovx and we'd need to change the visibility calculations, getting hall of mirrors effect\n#define FOVX_SANITY_LIMIT 165\n#define FOVY_SANITY_LIMIT 140\n\nstatic void CalcFov(float fov, float *fov_x, float *fov_y, float width, float height, float view_width, float view_height, qbool reduce_vertfov)\n{\n\tfloat t;\n\tfloat fovx;\n\tfloat fovy;\n\n\tfov = bound(10, fov, 140);\n\n\tif (width / 4 < height / 3) {\n\t\tfovx = fov;\n\n\t\tif (reduce_vertfov) {\n\t\t\t// Crop vertically when viewsize decreased\n\t\t\t// hmx: Fixes \"legacy\" fov style causing some visual glitches since 3.0.1 in rare aspect ratios/viewsizes combos\n\t\t\t// *almost* matches 3.0 results *and* not visually broken\n\t\t\tt = view_width / tan(fovx / 360 * M_PI);\n\t\t\tfovy = atan(view_height / t) * 360 / M_PI;\n\t\t}\n\t\telse {\n\t\t\tt = width / tan(fovx / 360 * M_PI);\n\t\t\tfovy = atan(height / t) * 360 / M_PI;\n\t\t}\n\t}\n\telse {\n\t\tfovx = fov;\n\n\t\t// Work out what the vertical FOV would be on 4:3 display\n\t\tt = 4.0 / tan(fovx / 360 * M_PI);\n\t\tfovy = atan (3.0 / t) * 360 / M_PI;\n\n\t\t// Now work out what the correct FOV is\n\t\tt = height / tan(fovy / 360 * M_PI);\n\t\tfovx = atan (width / t) * 360 / M_PI;\n\n\t\tif (reduce_vertfov) {\n\t\t\t// Crop vertically when viewsize decreased\n\t\t\tt = view_width / tan (fovx / 360 * M_PI);\n\t\t\tfovy = atan (view_height / t) * 360 / M_PI;\n\t\t}\n\t}\n\n\tif ((fovx < 10 || fovx > FOVX_SANITY_LIMIT))\n\t{\n\t\tCon_DPrintf(\"Limiting fovx (was %f)\\n\", fovx);\n\t\tfovx = bound(10, fovx, FOVX_SANITY_LIMIT);\n\n\t\tt = width / tan(fovx / 360 * M_PI);\n\t\tfovy = atan (height / t) * 360 / M_PI;\n\t}\n\n\tif (fovy < 10 || fovy > FOVY_SANITY_LIMIT)\n\t{\n\t\tCon_DPrintf(\"Limiting FOVY (was %f)\\n\", fovy);\n\t\tfovy = bound(10, fovy, FOVY_SANITY_LIMIT);\n\n\t\tt = height / tan(fovy / 360 * M_PI);\n\t\tfovx = atan (width / t) * 360 / M_PI;\n\t}\n\n\tif (fovx < 1 || fovx > FOVX_SANITY_LIMIT || fovy < 1 || fovy > FOVY_SANITY_LIMIT) {\n\t\tSys_Error(\"CalcFov: Bad fov (%f, %f)\", fovx, fovy);\n\t}\n\telse {\n\t\tCon_DPrintf(\"fov: %f %f\\n\", fovx, fovy);\n\t}\n\n\t*fov_x = fovx;\n\t*fov_y = fovy;\n}\n\n//Must be called whenever vid changes\nvoid SCR_CalcRefdef(void)\n{\n\tfloat size = 0;\n\tqbool full = false;\n\tint h;\n\tfloat aspectratio = vid.height ? (float)vid.width / vid.height : 1;\n\tqbool letterbox = (scr_fovmode.integer == 1);\n\tqbool height_reduced = false;\n\n\tscr_fullupdate = 0;             // force a background redraw\n\tvid.recalc_refdef = 0;\n\n\t// force the status bar to redraw\n\tSbar_Changed ();\n\n\t// bound viewsize\n\tif (scr_viewsize.value < 30) {\n\t\tCvar_Set(&scr_viewsize, \"30\");\n\t}\n\tif (scr_viewsize.value > 120) {\n\t\tCvar_Set(&scr_viewsize, \"120\");\n\t}\n\n\t// intermission is always full screen\n\tsize = cl.intermission ? 120 : scr_viewsize.value;\n\n\tif (size >= 120) {\n\t\tsb_lines = 0;           // no status bar at all\n\t}\n\telse if (size >= 110) {\n\t\tsb_lines = 24;          // no inventory\n\t}\n\telse {\n\t\tsb_lines = 24 + 16 + 8;\n\t}\n\n\tif (scr_viewsize.value >= 100.0) {\n\t\tfull = true;\n\t\tsize = 100.0;\n\t}\n\telse {\n\t\tsize = scr_viewsize.value;\n\t}\n\tif (cl.intermission) {\n\t\tfull = true;\n\t\tsize = 100.0;\n\t\tsb_lines = 0;\n\t}\n\tsize /= 100.0;\n\n\tif (!cl_sbar.value && full)\n\t\th = vid.height;\n\telse\n\t\th = vid.height - sb_lines;\n\n\tr_refdef.vrect.width = vid.width * size;\n\tif (r_refdef.vrect.width < 96) {\n\t\tsize = 96.0 / r_refdef.vrect.width;\n\t\tr_refdef.vrect.width = 96;      // min for icons\n\t}\n\n\tr_refdef.vrect.height = vid.height * size;\n\tif (cl_sbar.value || !full) {\n\t\tif (r_refdef.vrect.height > vid.height - sb_lines) {\n\t\t\tr_refdef.vrect.height = vid.height - sb_lines;\n\t\t\theight_reduced = true;\n\t\t}\n\t} else if (r_refdef.vrect.height > vid.height) {\n\t\tr_refdef.vrect.height = vid.height;\n\t}\n\n\t// Reduce width to keep aspect ratio constant with monitor\n\tif (letterbox) {\n\t\tr_refdef.vrect.width = min (r_refdef.vrect.width, r_refdef.vrect.height * aspectratio);\n\t\theight_reduced = false;\n\t}\n\n\tr_refdef.vrect.x = (vid.width - r_refdef.vrect.width) / 2;\n\tif (full)\n\t\tr_refdef.vrect.y = 0;\n\telse\n\t\tr_refdef.vrect.y = (h - r_refdef.vrect.height) / 2;\n\n\tCalcFov (scr_fov.value, &r_refdef.fov_x, &r_refdef.fov_y, vid.width, vid.height, r_refdef.vrect.width, r_refdef.vrect.height, height_reduced);\n\n\tscr_vrect = r_refdef.vrect;\n}\n\n//Keybinding command\nvoid SCR_SizeUp_f(void)\n{\n\tCvar_SetValue(&scr_viewsize, scr_viewsize.value + 10);\n\tvid.recalc_refdef = 1;\n}\n\n//Keybinding command\nvoid SCR_SizeDown_f(void)\n{\n\tCvar_SetValue(&scr_viewsize, scr_viewsize.value - 10);\n\tvid.recalc_refdef = 1;\n}\n\nvoid SCR_ZoomIn_f(void)\n{\n\tif (zoomedin) {\n\t\treturn;\n\t}\n\tzoomedin = true;\n\tunzoomedfov = scr_fov.value;\n\tunzoomedsensitivity = sensitivity.value;\n\tCvar_SetValue(&scr_fov, ZOOMEDFOV);\n\tCvar_SetValue(&sensitivity, unzoomedsensitivity * ((double)ZOOMEDFOV / unzoomedfov));\n}\n\nvoid SCR_ZoomOut_f(void)\n{\n\tif (!zoomedin) {\n\t\treturn;\n\t}\n\tzoomedin = false;\n\tCvar_SetValue(&scr_fov, unzoomedfov);\n\tCvar_SetValue(&sensitivity, unzoomedsensitivity);\n}\n\n/********************************** ELEMENTS **********************************/\n\n#ifdef EXPERIMENTAL_SHOW_ACCELERATION\nvoid SCR_DrawAccel (void) {\n\textern qbool player_in_air;\n\textern float cosinus_val;\n\n\tint x, y, length, charsize, pos;\n\tconst float scale_factor = 10.f;\n\tchar cosinus_str[10];\n\tif(!player_in_air) return;\n\n\tcharsize = (int) (8.f * vid.height / vid.conheight);\n\tlength = vid.width / 3;\n\tx = (vid.width - length) / 2;\n\ty = vid.height - sb_lines - charsize - 1;\n\n\tpos = (int) ((cosinus_val + 1) * length / 2);\n\n\thud.draw_accel_bar(x, y, length, charsize, pos);\n\n\t//scale: show most interesting\n\tpos = (int) ((cosinus_val * scale_factor + 1) * length / 2);\n\n\thud.draw_accel_bar(x, y - 2 * charsize, length, charsize, pos);\n\n\tcosinus_str[0] = '\\0';\n\tsprintf(cosinus_str,\"%.3f\", cosinus_val);\n\tDraw_String(x, y - charsize, cosinus_str);\n}\n#endif\n\nvoid SCR_DrawTurtle(void)\n{\n\tif (!scr_showturtle.value || cls.fps >= scr_showturtle.value) {\n\t\treturn;\n\t}\n\n\tDraw_Pic(scr_vrect.x, scr_vrect.y, scr_turtle);\n}\n\nstatic void SCR_DrawNet(void)\n{\n\tif (cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged < UPDATE_BACKUP - 1) {\n\t\treturn;\n\t}\n\tif (cls.demoplayback || scr_newHud.value == 1) {\n\t\treturn;\n\t}\n\n\tDraw_Pic(scr_vrect.x + 64, scr_vrect.y, scr_net);\n}\n\nstatic void SCR_DrawPause (void) {\n\tmpic_t *pic;\n\n\tif (!scr_showpause.value)               // turn off for screenshots\n\t\treturn;\n\n\tif (!cl.paused)\n\t\treturn;\n\n#ifndef CLIENTONLY\n\tif (sv.paused == 2)\n\t\treturn; // auto-paused in single player\n#endif\n\n\tpic = Draw_CachePic (CACHEPIC_PAUSE);\n\tDraw_Pic ((vid.width - pic->width) / 2, (vid.height - 48 - pic->height) / 2, pic);\n}\n\nvoid SCR_DrawLoading (void) {\n\tmpic_t *pic;\n\n\tif (!scr_drawloading)\n\t\treturn;\n\n\tpic = Draw_CachePic (CACHEPIC_LOADING);\n\tDraw_Pic ( (vid.width - pic->width )/ 2, (vid.height - 48 - pic->height) / 2, pic);\n}\n\n\n\nvoid SCR_BeginLoadingPlaque (void) {\n\tif (cls.state != ca_active)\n\t\treturn;\n\n\tif (key_dest == key_console)\n\t\treturn;\n\n\t// redraw with no console and the loading plaque\n\tscr_fullupdate = 0;\n\tSbar_Changed ();\n\tscr_drawloading = true;\n\tSCR_UpdateScreen ();\n\tscr_drawloading = false;\n\n\tscr_disabled_for_loading = true;\n\tscr_disabled_time = cls.realtime;\n\tscr_fullupdate = 0;\n}\n\nvoid SCR_EndLoadingPlaque (void) {\n\tif (!scr_disabled_for_loading)\n\t\treturn;\n\tscr_disabled_for_loading = false;\n\tscr_fullupdate = 0;\n}\n\n/********************************** CONSOLE **********************************/\n\nvoid SCR_SetUpToDrawConsole (void) {\n\tCon_CheckResize ();\n\t\n\t// decide on the height of the console\n\tif (SCR_NEED_CONSOLE_BACKGROUND) {\n\t\tscr_conlines = vid.height;\t\t// full screen\n\t\tscr_con_current = scr_conlines;\n\t} else if (key_dest == key_console) {\n\t\tscr_conlines = vid.height * scr_consize.value;\n\t\tscr_conlines = bound(30, scr_conlines, vid.height);\n\t} else {\n\t\tscr_conlines = 0;\t\t\t\t// none visible\n\t}\n\n\tif (scr_conlines < scr_con_current)\t{\n\t\tscr_con_current -= scr_conspeed.value * cls.trueframetime * vid.height / 320;\n\t\tscr_con_current = max(scr_con_current, scr_conlines);\n\t} else if (scr_conlines > scr_con_current) {\n\t\tscr_con_current += scr_conspeed.value * cls.trueframetime * vid.height / 320;\n\t\tscr_con_current = min(scr_con_current, scr_conlines);\n\t}\n\n\tif (clearconsole++ < vid.numpages)\n\t{\n\t\tSbar_Changed ();\n\t}\n\telse\n\t{\n\t\tcon_notifylines = 0;\n\t}\n\n\tclearnotify++;\n}\n\nvoid SCR_DrawConsole (void) {\n\tif (scr_con_current) {\n\t\tscr_copyeverything = 1;\n\t\tCon_DrawConsole (scr_con_current);\n\t\tclearconsole = 0;\n\t} else {\n\t\tif (key_dest == key_game || key_dest == key_message || (key_dest == key_menu && m_state == m_proxy))\n\t\t\tCon_DrawNotify ();      // only draw notify in game\n\t}\n}\n\n/********************************* TILE CLEAR *********************************/\n\nvoid SCR_TileClear(void)\n{\n\tint sb_lines_cleared = scr_newHud.integer ? 0 : sb_lines; // newhud does not (typically) have solid status bar, so clear the bottom of the screen\n\n\tif (cls.state != ca_active && cl.intermission) {\n\t\tDraw_TileClear(0, 0, vid.width, vid.height);\n\t\treturn;\n\t}\n\n\tif (r_refdef.vrect.x > 0) {\n\t\t// left\n\t\tDraw_TileClear(0, 0, r_refdef.vrect.x, vid.height - sb_lines_cleared);\n\t\t// right\n\t\tDraw_TileClear(r_refdef.vrect.x + r_refdef.vrect.width, 0,\n\t\t\tvid.width - (r_refdef.vrect.x + r_refdef.vrect.width), vid.height - sb_lines_cleared);\n\t}\n\tif (r_refdef.vrect.y > 0) {\n\t\t// top\n\t\tDraw_TileClear(r_refdef.vrect.x, 0, r_refdef.vrect.width, r_refdef.vrect.y);\n\t}\n\tif (r_refdef.vrect.y + r_refdef.vrect.height < vid.height - sb_lines_cleared) {\n\t\t// bottom\n\t\tDraw_TileClear(r_refdef.vrect.x,\n\t\t\tr_refdef.vrect.y + r_refdef.vrect.height,\n\t\t\tr_refdef.vrect.width,\n\t\t\tvid.height - sb_lines_cleared - (r_refdef.vrect.height + r_refdef.vrect.y));\n\t}\n}\n\n//\n// Calculates the cursor scale based on the current screen/text size\n//\nstatic double SCR_GetCursorScale(void) \n{\n\treturn (double) scr_cursor_scale.value * ((double) vid.width / (double)vid.conwidth);\n}\n\nstatic void SCR_DrawCursor(void) \n{\n\tdouble scale = SCR_GetCursorScale();\n\n\t// Always draw the cursor if fullscreen\n\tif (IN_QuakeMouseCursorRequired()) {\n\t\tfloat x_coord = scr_pointer_state.x;\n\t\tfloat y_coord = scr_pointer_state.y;\n\n\t\tif (scr_cursor && R_TextureReferenceIsValid(scr_cursor->texnum)) {\n\t\t\tDraw_SAlphaPic(x_coord + scr_cursor_iconoffset_x.value, y_coord + scr_cursor_iconoffset_y.value, scr_cursor, scr_cursor_alpha.value, scale);\n\n\t\t\tif (scr_cursor_icon && R_TextureReferenceIsValid(scr_cursor_icon->texnum)) {\n\t\t\t\tDraw_SAlphaPic(x_coord + scr_cursor_iconoffset_x.value, y_coord + scr_cursor_iconoffset_y.value, scr_cursor_icon, scr_cursor_alpha.value, scale);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tcolor_t c = RGBA_TO_COLOR(0, 255, 0, 255);\n\n\t\t\tDraw_AlphaLineRGB(x_coord + (10 * scale), y_coord + (10 * scale), x_coord + (40 * scale), y_coord + (40 * scale), 10 * scale, c);\n\t\t\tDraw_AlphaLineRGB(x_coord, y_coord, x_coord + (20 * scale), y_coord, 10 * scale, c);\n\t\t\tDraw_AlphaLineRGB(x_coord, y_coord, x_coord, y_coord + 20 * scale, 10 * scale, c);\n\t\t\tDraw_AlphaLineRGB(x_coord + (20 * scale), y_coord, x_coord, y_coord + (20 * scale), 10 * scale, c);\n\t\t}\n\t}\n\n\t// remember the position for future\n\tscr_pointer_state.x_old = scr_pointer_state.x;\n\tscr_pointer_state.y_old = scr_pointer_state.y;\n}\n\nstatic void SCR_UpdateCursor(void)\n{\n\tint max_x = VID_RenderWidth2D();\n\tint max_y = VID_RenderHeight2D();\n\n\t// vid_sdl2 updates absolute cursor position when not locked\n\tscr_pointer_state.x = bound(0, (cursor_x * vid.conwidth) / max_x, max_x - 1);\n\tscr_pointer_state.y = bound(0, (cursor_y * vid.conheight) / max_y, max_y - 1);\n\n\tif (IN_MouseTrackingRequired() && (scr_pointer_state.x != scr_pointer_state.x_old || scr_pointer_state.y != scr_pointer_state.y_old)) {\n\t\tMouse_MoveEvent();\n\t}\n}\n\nstatic void SCR_VoiceMeter(void)\n{\n#ifdef FTE_PEXT2_VOICECHAT\n\n\tfloat\trange;\n\tint\t\tloudness;\n\tint\t\tw1, w2;\n\tint\t\tw = 100;\t\t\t\t\t\t\t\t\t\t\t\t// random.\n\tint     h = 8;\n\tint     x, y;\n\n\tif (!S_Voip_ShowMeter (&x, &y)) {\n\t\treturn;\n\t}\n\n\tloudness = S_Voip_Loudness();\n\n\tif (loudness < 0)\n\t\treturn;\n\n\trange = loudness / 100.0f;\n\trange = bound(0.0f, range, 1.0f);\n\tw1 = range * w;\n\tw2 = w - w1;\n\tDraw_String (x, y, \"mic \");\n\tx += 8 * (sizeof(\"mic \")-1);\n\tDraw_AlphaRectangleRGB(x, y, w1, h, 1, true, RGBA_TO_COLOR(255, 0, 0, 255));\n\tx += w1;\n\tDraw_AlphaRectangleRGB(x, y, w2, h, 1, true, RGBA_TO_COLOR(0, 255, 0, 255));\n\n#endif // FTE_PEXT2_VOICECHAT\n}\n\nvoid SCR_DrawMultiviewOverviewElements (void)\n{\n\tSCR_DrawMultiviewBorders ();\n}\n\n// Elements to be drawn on every view during multiview\nvoid SCR_DrawMultiviewIndividualElements(void)\n{\n\textern qbool  sb_showscores,  sb_showteamscores;\n\n\t// Show autoid in all views\n\tif (!sb_showscores && !sb_showteamscores) {\n\t\tSCR_DrawAutoID();\n\t\tSCR_DrawDamageIndicators();\n\t}\n\n\t// Crosshair\n\tif ((key_dest != key_menu) && (scr_showcrosshair.integer || (!sb_showscores && !sb_showteamscores))) {\n\t\tif (!CL_MultiviewInsetView() || cl_mvinsetcrosshair.integer) {\n\t\t\tDraw_Crosshair();\n\t\t}\n\t}\n\n\t// Draw multiview mini-HUD's.\n\tif (cl_mvdisplayhud.integer) {\n\t\tif (cl_mvdisplayhud.integer >= 2) {\n\t\t\t// Graphical with icons.\n\t\t\tSCR_DrawMVStatus();\n\t\t}\n\t\telse {\n\t\t\t// Only strings.\n\t\t\tSCR_DrawMVStatusStrings();\n\t\t}\n\t}\n\n\tCL_MultiviewInsetRestoreStats();\n}\n\nstatic void SCR_DrawElements(void) \n{\n\textern qbool  sb_showscores,  sb_showteamscores;\n\textern cvar_t\tscr_menudrawhud;\n\n\tif (scr_drawloading) \n\t{\n\t\tSCR_DrawLoading ();\n\t\tSbar_Draw ();\n\t\tHUD_Draw ();\t\t// HUD -> hexum\n\t}\n\telse \n\t{\n\t\tSCR_UpdateCursor();\n\n\t\tif( !(!scr_menudrawhud.integer && (m_state != m_none)) || (!scr_menudrawhud.integer && (m_state == m_proxy)) )\n\t\t{\n\t\t\tif (cl.intermission == 1) {\n\t\t\t\tSbar_IntermissionOverlay();\n\t\t\t\tif (!scr_notifyalways.integer) {\n\t\t\t\t\tCon_ClearNotify();\n\t\t\t\t}\n\t\t\t\tHUD_Draw();\n\t\t\t}\n\t\t\telse if (cl.intermission == 2) {\n\t\t\t\tSbar_FinaleOverlay();\n\t\t\t\tSCR_CenterString_Draw();\n\t\t\t\tif (!scr_notifyalways.integer) {\n\t\t\t\t\tCon_ClearNotify();\n\t\t\t\t}\n\t\t\t\tHUD_Draw();\n\t\t\t}\n\t\t\telse if (cls.state == ca_active) {\n\t\t\t\tSCR_DrawNet ();\n\t\t\t\tSCR_DrawTurtle ();\n#ifdef EXPERIMENTAL_SHOW_ACCELERATION\n\t\t\t\tSCR_DrawAccel();\n#endif\n\n\t\t\t\tif (!sb_showscores && !sb_showteamscores) \n\t\t\t\t{ \n\t\t\t\t\t// Do not show if +showscores\n\t\t\t\t\tSCR_DrawPause();\n\n\t\t\t\t\tSCR_DrawAutoID();\n\n\t\t\t\t\tSCR_DrawAntilagIndicators();\n\n\t\t\t\t\tSCR_DrawDamageIndicators();\n\n\t\t\t\t\tSCR_VoiceMeter();\n\t\t\t\t}\n\n\t\t\t\tif ((key_dest != key_menu) && (scr_showcrosshair.integer || (!sb_showscores && !sb_showteamscores)))\n\t\t\t\t{\n\t\t\t\t\tDraw_Crosshair ();\n\t\t\t\t}\n\n\t\t\t\t// Do not show if +showscores\n\t\t\t\tif (!sb_showscores && !sb_showteamscores)\n\t\t\t\t{\n\t\t\t\t\tSCR_Draw_TeamInfo();\n\n\t\t\t\t\tSCR_Draw_ShowNick();\n\n\t\t\t\t\tSCR_CenterString_Draw();\n\t\t\t\t\tSCR_DrawSpeed();\n\t\t\t\t\tSCR_DrawClocks();\n\t\t\t\t\tSCR_DrawQTVBuffer();\n\t\t\t\t\tSCR_DrawFPS();\n\t\t\t\t}\n\n\t\t\t\t// QW262\n\t\t\t\tSCR_DrawHud();\n\n\t\t\t\tif (cls.mvdplayback) {\n\t\t\t\t\tMVD_Screen();\n\t\t\t\t}\n\n\t\t\t\t// VULT DISPLAY KILLS\n\t\t\t\tVX_TrackerThink();\n\n\t\t\t\tif (CL_MultiviewEnabled())\n\t\t\t\t\tSCR_DrawMultiviewOverviewElements ();\n\n\t\t\t\tSbar_Draw();\n\t\t\t\tHUD_Draw();\n\t\t\t\tHUD_Editor_Draw();\n\n\t\t\t\tDemoControls_Draw();\n\t\t\t}\n\t\t}\n\n\t\tif (!SCR_TakingAutoScreenshot()) {\n\t\t\tSCR_DrawConsole();\n\n\t\t\tM_Draw();\n\t\t}\n\n\t\tSCR_DrawCursor();\n\t}\n}\n\n/******************************* UPDATE SCREEN *******************************/\n\nqbool SCR_UpdateScreenPrePlayerView (void)\n{\n\textern cvar_t gl_clear;\n\textern qbool Minimized;\n\tstatic int oldfovmode = 0;\n\n\tif (!scr_initialized) {\n\t\treturn false;\n\t}\n\n\tif (scr_skipupdate || block_drawing) {\n\t\treturn false;\n\t}\n\n\tif (scr_disabled_for_loading) {\n\t\tif (cls.realtime - scr_disabled_time > 20) {\n\t\t\tscr_disabled_for_loading = false;\n\t\t}\n\t\telse {\n\t\t\treturn false;\n\t\t}\n\t}\n\t// Don't suck up any cpu if minimized.\n\n\tif (Minimized) {\n\t\treturn false;\n\t}\n\n\tvid.numpages = 2 + gl_triplebuffer.value;\n\n\tscr_copytop = 0;\n\tscr_copyeverything = 0;\n\n\thost_screenupdatecount++;  // For HUD.\n\n\t// Check for vid changes.\n\tif (oldfov != scr_fov.value) \n\t{\n\t\toldfov = scr_fov.value;\n\t\tvid.recalc_refdef = true;\n\t}\n\n\tif (oldscreensize != scr_viewsize.value) \n\t{\n\t\toldscreensize = scr_viewsize.value;\n\t\tvid.recalc_refdef = true;\n\t}\n\n\tif (oldsbar != cl_sbar.value) \n\t{\n\t\toldsbar = cl_sbar.value;\n\t\tvid.recalc_refdef = true;\n\t}\n\n\tif (oldfovmode != scr_fovmode.integer) {\n\t\toldfovmode = scr_fovmode.integer;\n\t\tvid.recalc_refdef = true;\n\t}\n\n\tif (vid.recalc_refdef) {\n\t\tSCR_CalcRefdef();\n\t}\n\n\tif ((v_contrast.value > 1 && !vid_hwgamma_enabled) || gl_clear.value) {\n\t\tSbar_Changed();\n\t}\n\telse if (scr_newHud.integer == 2 && scr_viewsize.value < 120) {\n\t\tSbar_Changed();\n\t}\n\n\treturn true;\n}\n\nvoid SCR_UpdateScreenPlayerView(int flags)\n{\n\tqbool draw_2d = !(flags & UPDATESCREEN_3D_ONLY);\n\tqbool draw_3d = !(flags & UPDATESCREEN_2D_ONLY);\n\n\tif (draw_3d) {\n\t\tR_CheckReloadLightmaps();\n\n\t\t// preache skins if needed\n\t\tSkins_PreCache();\n\n\t\tSCR_SetUpToDrawConsole();\n\n\t\tR_BeginRendering(&glx, &gly, &glwidth, &glheight);\n\n\t\tif (V_PreRenderView()) {\n\t\t\tR_SetupFrame();\n\n\t\t\tR_RenderView();\n\n\t\t\tif (flags & UPDATESCREEN_POSTPROCESS) {\n\t\t\t\tR_PostProcessScene();\n\t\t\t}\n\t\t}\n\t}\n\n\tif (draw_2d) {\n\t\tR_SetupChatIcons();\n\n\t\tR_Set2D();\n\n\t\tR_PolyBlend();\n\n\t\t// draw any areas not covered by the refresh\n\t\tSCR_TileClear();\n\t}\n}\n\nvoid SCR_HUD_WeaponStats(hud_t* hud);\n\n// Drawing new HUD items in old HUD mode - eventually move everything across\nstatic void SCR_DrawNewHudElements(void)\n{\n\textern cvar_t r_netgraph, r_netstats;\n\tstatic hud_t *hud_netstats = NULL;\n\tstatic hud_t *hud_weaponstats = NULL;\n\tif (hud_netstats == NULL) // first time\n\t\thud_netstats = HUD_Find(\"net\");\n\tif (hud_weaponstats == NULL)\n\t\thud_weaponstats = HUD_Find(\"weaponstats\");\n\n\tif (r_netgraph.value)\n\t{\n\t\tfloat temp = hud_netgraph->show->value;\n\n\t\tCvar_SetValue(hud_netgraph->show, 1);\n\t\tSCR_HUD_Netgraph(hud_netgraph);\n\t\tCvar_SetValue(hud_netgraph->show, temp);\n\t}\n\n\tif (r_netstats.value)\n\t{\n\t\tfloat temp = hud_netstats->show->value;\n\n\t\tCvar_SetValue(hud_netstats->show, 1);\n\t\tSCR_HUD_DrawNetStats(hud_netstats);\n\t\tCvar_SetValue(hud_netstats->show, temp);\n\t}\n\n\tif (hud_weaponstats && hud_weaponstats->show && hud_weaponstats->show->value) {\n\t\tSCR_HUD_WeaponStats(hud_weaponstats);\n\t}\n}\n\nvoid SCR_UpdateScreenHudOnly(void)\n{\n\tif (r_drawhud.integer) {\n\t\tR_TraceEnterNamedRegion(\"HUD\");\n\t\tif (scr_newHud.value != 1) {\n\t\t\tSCR_DrawNewHudElements();\n\t\t}\n\n\t\tSCR_DrawElements();\n\n\t\t// Actual rendering...\n\t\tif (r_drawhud.integer != 2) {\n\t\t\tR_FlushImageDraw();\n\t\t}\n\t\tR_TraceLeaveNamedRegion();\n\t}\n}\n\nvoid SCR_UpdateScreenPostPlayerView(void)\n{\n\tSCR_UpdateScreenHudOnly();\n\n\trenderer.PostProcessScreen();\n\n\tSCR_CheckAutoScreenshot();\n\n\tVID_RenderFrameEnd();\n\n\tR_EndRendering();\n}\n\n// This is called every frame, and can also be called explicitly to flush text to the screen.\n// WARNING: be very careful calling this from elsewhere, because the refresh needs almost the entire 256k of stack space!\nqbool SCR_UpdateScreen(void)\n{\n\tif (!SCR_UpdateScreenPrePlayerView()) {\n\t\tVID_RenderFrameEnd();\n\t\treturn false;\n\t}\n\n\trenderer.ScreenDrawStart();\n\n\tSCR_UpdateScreenPlayerView(UPDATESCREEN_POSTPROCESS);\n\n\tSCR_UpdateScreenPostPlayerView();\n\n\treturn true;\n}\n\nvoid SCR_UpdateWholeScreen(void)\n{\n\tscr_fullupdate = 0;\n\tSCR_UpdateScreen();\n}\n\nmpic_t *SCR_LoadCursorImage(char *cursorimage)\n{\n\tmpic_t *image = NULL;\n\n\timage = Draw_CachePicSafe(va(\"%s.lmp\", cursorimage), false, false);\n\n\t// Failed to load anything, maybe missing .lmp-file, so just try\n\t// loading any 24-bit version that's available instead.\n\tif(!image)\n\t{\n\t\timage = Draw_CachePicSafe(cursorimage, false, true);\n\t}\n\n\treturn image;\n}\n\n/************************************ TEMPORARY *******************************/\n\n/* FIXME: Remove this when most people have upgraded to 3.0 */\nstatic void tmp_calc_fov(void)\n{\n\tfloat fov;\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"Usage: calc_fov <old_fov>\\n\");\n\t\treturn;\n\t}\n\n\tfov = Q_atof(Cmd_Argv(1));\n\tfov = atan(((tan((fov/2)*M_PI/180))/1.2))*360/M_PI;\n\n\tCom_Printf(\"Use fov %d (%f)\\n\", (int)(fov+0.5), fov);\n}\n\n\n/************************************ INIT ************************************/\n\nvoid SCR_Init (void)\n{\n\tscr_ram = Draw_CacheWadPic (\"ram\", WADPIC_RAM);\n\tscr_net = Draw_CacheWadPic (\"net\", WADPIC_NET);\n\tscr_turtle = Draw_CacheWadPic (\"turtle\", WADPIC_TURTLE);\n\tscr_cursor = SCR_LoadCursorImage(\"gfx/cursor\");\n\n\tif (!host_initialized) {\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_VIEW);\n\t\tCvar_Register(&scr_fov);\n\t\tCvar_Register(&default_fov);\n\t\tCvar_Register(&scr_viewsize);\n\t\tCvar_Register(&scr_fovmode);\n\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_SBAR);\n\t\tCvar_Register(&scr_newHud);\n\t\tCvar_ResetCurrentGroup();\n\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_CONSOLE);\n\t\tCvar_Register(&scr_consize);\n\t\tCvar_Register(&scr_conspeed);\n\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_OPENGL);\n\t\tCvar_Register(&gl_triplebuffer);\n\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_EYECANDY);\n\t\tCvar_Register(&r_chaticons_alpha);\n\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\t\tCvar_Register(&scr_showturtle);\n\t\tCvar_Register(&scr_showpause);\n\n\t\tCvar_Register(&show_velocity_3d);\n\t\tCvar_Register(&show_velocity_3d_offset_forward);\n\t\tCvar_Register(&show_velocity_3d_offset_down);\n\n\t\tCvar_Register(&scr_coloredfrags);\n\n\t\tCmd_AddCommand(\"calc_fov\", tmp_calc_fov);\n\n\t\t// QW 262 HUD\n\t\tCvar_Register(&cl_hud);\n\t\tCvar_Register(&r_drawhud);\n\n\t\tCvar_Register(&scr_showcrosshair);\n\t\tCvar_Register(&scr_notifyalways);\n\t\tCvar_ResetCurrentGroup();\n\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_MENU);\n\t\tCvar_Register(&scr_cursor_scale);\n\t\tCvar_Register(&scr_cursor_iconoffset_x);\n\t\tCvar_Register(&scr_cursor_iconoffset_y);\n\t\tCvar_Register(&scr_cursor_alpha);\n\t\tCvar_ResetCurrentGroup();\n\n\t\tCmd_AddCommand(\"sizeup\", SCR_SizeUp_f);\n\t\tCmd_AddCommand(\"sizedown\", SCR_SizeDown_f);\n\t\tCmd_AddCommand(\"+zoom\", SCR_ZoomIn_f);\n\t\tCmd_AddCommand(\"-zoom\", SCR_ZoomOut_f);\n\t}\n\n\tSCR_RegisterAutoIDCvars();\n\tSShot_RegisterCvars();\n\tWeaponStats_CommandInit();\n\tSCR_CenterPrint_Init();\n\tSCR_RegisterDamageIndicatorCvars();\n\n\tscr_initialized = true;\n\n\tScrollBars_Init();\n}\n\nmpic_t * SCR_GetWeaponIconByFlag (int flag)\n{\n\textern mpic_t *sb_weapons[7][8];  // sbar.c Weapon pictures.\n\n\tif (flag == IT_LIGHTNING || flag == IT_SUPER_LIGHTNING)\n\t{\n\t\treturn sb_weapons[0][6];\n\t}\n\telse if (flag == IT_ROCKET_LAUNCHER)\n\t{\n\t\treturn sb_weapons[0][5];\n\t}\n\telse if (flag == IT_GRENADE_LAUNCHER)\n\t{\n\t\treturn sb_weapons[0][4];\n\t}\n\telse if (flag == IT_SUPER_NAILGUN)\n\t{\n\t\treturn sb_weapons[0][3];\n\t}\n\telse if (flag == IT_NAILGUN)\n\t{\n\t\treturn sb_weapons[0][2];\n\t}\n\telse if (flag == IT_SUPER_SHOTGUN)\n\t{\n\t\treturn sb_weapons[0][1];\n\t}\n\telse if (flag == IT_SHOTGUN)\n\t{\n\t\treturn sb_weapons[0][0];\n\t}\n\n\treturn NULL;\n}\n\nstatic cvar_t  scr_damage_proportional     = { \"scr_damage_proportional\", \"0\" };\nstatic cvar_t  scr_damage_floating         = { \"scr_damage_floating\", \"0\" };\nstatic cvar_t  scr_damage_hitbeep          = { \"scr_damage_hitbeep\", \"0\" };\nstatic cvar_t  scr_damage_scale            = { \"scr_damage_scale\", \"1\" };\nstatic cvar_t  scr_damage_offset_spectator = { \"scr_damage_offset_spectator\", \"28\" };\nstatic cvar_t  scr_damage_offset_ingame    = { \"scr_damage_offset_ingame\", \"14\" };\n\nstatic float DAMAGE_INITIAL_VELOCITY[2] = { 250, 200 };\n#define DAMAGE_VERTICAL_OFFSET_INGAME 16\n#define DAMAGE_VERTICAL_OFFSET_SPECTATOR 32\n#define DAMAGE_GRAVITY 400\n\nstatic int direction_order = 0;\n\nvoid SCR_SetupDamageIndicators(void)\n{\n\tvec3_t r;\n\tfloat winz;\n\tint i;\n\n\tif (cl.intermission || !scr_damage_floating.integer) {\n\t\tmemset(cl.damage_notifications, 0, sizeof(cl.damage_notifications));\n\t\treturn;\n\t}\n\n\tif (cls.state != ca_active || !cl.validsequence) {\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < MAX_DAMAGE_NOTIFICATIONS; ++i) {\n\t\tscr_damage_t* dmg = &cl.damage_notifications[i];\n\n\t\tdmg->visible = false;\n\t\tif (!dmg->time || cls.realtime - dmg->time >= MAX_DAMAGE_NOTIFICATION_TIME) {\n\t\t\tdmg->time = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\tdmg->vel[1] -= cls.frametime * DAMAGE_GRAVITY;\n\n\t\tVectorScale(vright, dmg->vel[0], r);\n\t\tVectorMA(dmg->origin, cls.frametime, r, dmg->origin);\n\t\tdmg->origin[2] += dmg->vel[1] * cls.frametime;\n\n\t\tif (R_CullSphere(dmg->origin, 0)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tdmg->visible = R_Project3DCoordinates(dmg->origin[0], dmg->origin[1], dmg->origin[2] + 28, &dmg->x, &dmg->y, &winz);\n\t}\n}\n\nstatic void SCR_DrawDamageIndicators(void)\n{\n\tint i;\n\n\tif (cl.intermission || !scr_damage_floating.integer) {\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < MAX_DAMAGE_NOTIFICATIONS; ++i) {\n\t\tscr_damage_t * dmg = &cl.damage_notifications[i];\n\n\t\tif (dmg->visible) {\n\t\t\tfloat x = dmg->x * vid.width / glwidth;\n\t\t\tfloat y = (glheight - dmg->y) * vid.height / glheight;\n\t\t\tfloat scale = (scr_damage_scale.value > 0 ? scr_damage_scale.value : 1.0);\n\t\t\tfloat age = (cls.realtime - dmg->time);\n\t\t\tfloat alpha = 1.0f;\n\n\t\t\tif (age > MAX_DAMAGE_NOTIFICATION_TIME / 2) {\n\t\t\t\talpha = 1 - 2 * age / MAX_DAMAGE_NOTIFICATION_TIME;\n\t\t\t}\n\t\t\tDraw_SStringAligned(x - 5 * 8 * scale, y - 8 * scale, dmg->text, scale, alpha, scr_damage_proportional.integer, text_align_center, x + 5 * 8 * scale);\n\t\t}\n\t}\n}\n\nstatic void SCR_DamageInit(scr_damage_t * dmg, int damage, vec3_t origin, qbool team, qbool splash)\n{\n\tconst char* color = \"&c7f7\"; // direct/projectile\n\tfloat distance;\n\n\tif (team) {\n\t\tcolor = \"&cf55\";\n\t}\n\telse if (splash) {\n\t\tcolor = \"&cff5\";\n\t}\n\n\tdistance = VectorDistance(origin, cl.simorg) / 300.0f;\n\tdistance = bound(0.1f, distance, 1.0f);\n\n\tdmg->time = cls.realtime;\n\tsnprintf(dmg->text, sizeof(dmg->text), \"%s%d\", color, damage);\n\tVectorCopy(origin, dmg->origin);\n\tdmg->origin[2] += (cl.spectator ? scr_damage_offset_spectator.value : scr_damage_offset_ingame.value);\n\tswitch (direction_order % 4) {\n\tcase 0:\n\t\tdmg->vel[0] = DAMAGE_INITIAL_VELOCITY[0];\n\t\tbreak;\n\tcase 1:\n\t\tdmg->vel[0] = -DAMAGE_INITIAL_VELOCITY[0];\n\t\tbreak;\n\tcase 2:\n\t\tdmg->vel[0] = DAMAGE_INITIAL_VELOCITY[0] / 3;\n\t\tbreak;\n\tcase 3:\n\t\tdmg->vel[0] = -DAMAGE_INITIAL_VELOCITY[0] / 3;\n\t\tbreak;\n\t}\n\tdmg->vel[0] *= distance;\n\tdmg->vel[1] = DAMAGE_INITIAL_VELOCITY[1] * distance;\n\tVectorCopy(dmg->origin, origin);\n\t++direction_order;\n}\n\nstatic void CL_SpawnDamageIndicatorDirect(int deathtype, vec3_t origin, int damage, qbool splash_damage, qbool team_damage)\n{\n\tif (scr_damage_hitbeep.integer) {\n\t\tS_LocalSound(\"dmg-notification.wav\");\n\t}\n\n\tif (scr_damage_floating.integer) {\n\t\tint i;\n\t\tdouble earliest_time;\n\t\tint earliest_slot;\n\n\t\tearliest_time = cls.realtime;\n\t\tearliest_slot = -1;\n\t\tfor (i = 0; i < MAX_DAMAGE_NOTIFICATIONS; ++i) {\n\t\t\tscr_damage_t* dmg = &cl.damage_notifications[i];\n\n\t\t\tif (dmg->time == 0 || dmg->time < cls.realtime - MAX_DAMAGE_NOTIFICATION_TIME) {\n\t\t\t\tSCR_DamageInit(dmg, damage, origin, team_damage, splash_damage);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (earliest_time > dmg->time) {\n\t\t\t\tearliest_time = dmg->time;\n\t\t\t\tearliest_slot = i;\n\t\t\t}\n\t\t}\n\n\t\tif (earliest_slot >= 0) {\n\t\t\tSCR_DamageInit(&cl.damage_notifications[earliest_slot], damage, origin, team_damage, splash_damage);\n\t\t}\n\t}\n}\n\nvoid CL_SpawnDamageIndicator(int deathtype, int targ_ent, int damage, qbool splash_damage, qbool team_damage)\n{\n\tif (!(scr_damage_floating.integer || scr_damage_hitbeep.integer) || damage == 0) {\n\t\treturn;\n\t}\n\n\tif (targ_ent == 0 || targ_ent >= sizeof(cl_entities) / sizeof(cl_entities[0])) {\n\t\treturn;\n\t}\n\n\tCL_SpawnDamageIndicatorDirect(deathtype, cl_entities[targ_ent].current.origin, damage, splash_damage, team_damage);\n}\n\nvoid CL_ReadKtxDamageIndicatorString(const char* s)\n{\n\tvec3_t origin;\n\tint deathtype;\n\tint damage;\n\tqbool splash_damage;\n\tqbool team_damage;\n\n\tCmd_TokenizeString((char*)s);\n\n\tif (Cmd_Argc() == 9) {\n\t\torigin[0] = atoi(Cmd_Argv(2));\n\t\torigin[1] = atoi(Cmd_Argv(3));\n\t\torigin[2] = atoi(Cmd_Argv(4));\n\t\tdeathtype = atoi(Cmd_Argv(5));\n\t\tdamage = atoi(Cmd_Argv(6));\n\t\tsplash_damage = atoi(Cmd_Argv(7));\n\t\tteam_damage = atoi(Cmd_Argv(8));\n\n\t\tCL_SpawnDamageIndicatorDirect(deathtype, origin, damage, splash_damage, team_damage);\n\t}\n}\n\nstatic void SCR_RegisterDamageIndicatorCvars(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\tCvar_Register(&scr_damage_proportional);\n\tCvar_Register(&scr_damage_floating);\n\tCvar_Register(&scr_damage_hitbeep);\n\tCvar_Register(&scr_damage_scale);\n\tCvar_Register(&scr_damage_offset_spectator);\n\tCvar_Register(&scr_damage_offset_ingame);\n\tCvar_ResetCurrentGroup();\n}\n"
  },
  {
    "path": "src/cl_screenshot.c",
    "content": "/*\nCopyright (C) 1996-2003 Id Software, Inc., A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: cl_screen.c,v 1.156 2007-10-29 00:56:47 qqshka Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"image.h\"\n#ifdef _WIN32\n#include \"movie.h\"\t//joe: capturing to avi\n#include \"movie_avi.h\"\t//\n#endif\n#include \"utils.h\"\n#include \"r_local.h\"\n#include \"r_renderer.h\"\n#ifdef X11_GAMMA_WORKAROUND\n#include \"tr_types.h\"\n#endif\n\n#define\tDEFAULT_SSHOT_FORMAT \"png\"\n\nstatic void OnChange_scr_allowsnap(cvar_t *, char *, qbool *);\n\nstatic cvar_t scr_sshot_autoname = { \"sshot_autoname\", \"0\" };\ncvar_t scr_allowsnap      = { \"scr_allowsnap\", \"1\", 0, OnChange_scr_allowsnap };\ncvar_t scr_sshot_format = { \"sshot_format\", DEFAULT_SSHOT_FORMAT };\ncvar_t scr_sshot_dir = { \"sshot_dir\", \"\" };\n\nstatic int scr_autosshot_countdown = 0;\nstatic char auto_matchname[2 * MAX_OSPATH];\n\n/******************************** SCREENSHOTS ********************************/\n\n#define SSHOT_FAILED\t\t-1\n#define SSHOT_FAILED_QUIET\t-2\t\t//failed but don't print an error message\n#define SSHOT_SUCCESS\t\t0\n\nstatic char *SShot_ExtForFormat(int format)\n{\n\tswitch (format) {\n\t\tcase IMAGE_PCX:\t\treturn \".pcx\";\n\t\tcase IMAGE_TGA:\t\treturn \".tga\";\n\t\tcase IMAGE_JPEG:\treturn \".jpg\";\n\t\tcase IMAGE_PNG:\t\treturn \".png\";\n\t}\n\tassert(!\"SShot_ExtForFormat: unknown format\");\n\treturn \"err\";\n}\n\nstatic image_format_t SShot_FormatForName(char *name)\n{\n\tchar *ext;\n\n\text = COM_FileExtension(name);\n\n\tif (!strcasecmp(ext, \"tga\"))\n\t\treturn IMAGE_TGA;\n\n#ifdef WITH_PNG\n\telse if (!strcasecmp(ext, \"png\"))\n\t\treturn IMAGE_PNG;\n#endif\n\n#ifdef WITH_JPEG\n\telse if (!strcasecmp(ext, \"jpg\"))\n\t\treturn IMAGE_JPEG;\n#endif\n\n#ifdef WITH_PNG\n\telse if (!strcasecmp(scr_sshot_format.string, \"png\") || !strcasecmp(ext, \"apng\"))\n\t\treturn IMAGE_PNG;\n#endif\n\n#ifdef WITH_JPEG\n\telse if (!strcasecmp(scr_sshot_format.string, \"jpg\") || !strcasecmp(scr_sshot_format.string, \"jpeg\"))\n\t\treturn IMAGE_JPEG;\n#endif\n\n\telse\n\t\treturn IMAGE_TGA;\n}\n\nstatic char *Sshot_SshotDirectory(void)\n{\n\tstatic char dir[MAX_PATH];\n\n\tstrlcpy(dir, FS_LegacyDir(scr_sshot_dir.string), sizeof(dir));\n\n\treturn dir;\n}\n\n#ifdef X11_GAMMA_WORKAROUND\nextern unsigned short ramps[3][4096];\n#else\nextern unsigned short ramps[3][256];\n#endif\n\n//applies hwgamma to RGB data\nstatic void applyHWGamma(byte *buffer, size_t size)\n{\n\tint i;\n\n\tif (vid_hwgamma_enabled) {\n\t\tfor (i = 0; i < size; i += 3) {\n\t\t\tint r = buffer[i + 0];\n\t\t\tint g = buffer[i + 1];\n\t\t\tint b = buffer[i + 2];\n\n#ifdef X11_GAMMA_WORKAROUND\n\t\t\tif (glConfig.gammacrap.size >= 256 && glConfig.gammacrap.size <= 4096) {\n\t\t\t\tr = (int)((r * glConfig.gammacrap.size) / 256.0f);\n\t\t\t\tg = (int)((g * glConfig.gammacrap.size) / 256.0f);\n\t\t\t\tb = (int)((b * glConfig.gammacrap.size) / 256.0f);\n\t\t\t}\n#endif\n\t\t\tbuffer[i + 0] = ramps[0][r] >> 8;\n\t\t\tbuffer[i + 1] = ramps[1][g] >> 8;\n\t\t\tbuffer[i + 2] = ramps[2][b] >> 8;\n\t\t}\n\t}\n}\n\nint SCR_Screenshot(char *name, qbool movie_capture)\n{\n\tscr_sshot_target_t* target_params = Q_malloc(sizeof(scr_sshot_target_t));\n\tsize_t width = renderer.ScreenshotWidth();\n\tsize_t height = renderer.ScreenshotHeight();\n\tsize_t buffer_size = width * height * 3;\n\n\t// name is fullpath now\n\t//\tname = (*name == '/') ? name + 1 : name;\n\ttarget_params->format = SShot_FormatForName(name);\n\tstrlcpy(target_params->fileName, name, sizeof(target_params->fileName));\n\tCOM_ForceExtension(target_params->fileName, SShot_ExtForFormat(target_params->format));\n\ttarget_params->width = width;\n\ttarget_params->height = height;\n\n\ttarget_params->buffer = Movie_TempBuffer(width, height);\n\ttarget_params->movie_capture = movie_capture;\n\tif (!target_params->buffer) {\n\t\ttarget_params->buffer = Q_malloc(buffer_size);\n\t\ttarget_params->freeMemory = true;\n\t}\n\n\trenderer.Screenshot(target_params->buffer, buffer_size);\n\n\tif (movie_capture && Movie_BackgroundCapture(target_params)) {\n\t\treturn SSHOT_SUCCESS;\n\t}\n\n\treturn SCR_ScreenshotWrite(target_params);\n}\n\nint SCR_ScreenshotWrite(scr_sshot_target_t* target_params)\n{\n\tint i, temp;\n\tint success = SSHOT_FAILED;\n\tint format = target_params->format;\n\tbyte* buffer = target_params->buffer;\n\tchar* name = target_params->fileName;\n\tsize_t buffersize = target_params->width * target_params->height * 3;\n\n#ifdef WITH_PNG\n\tif (format == IMAGE_PNG) {\n\t\tapplyHWGamma(buffer, buffersize);\n\n\t\tif (target_params->movie_capture && Movie_AnimatedPNG()) {\n\t\t\textern cvar_t movie_fps;\n\n\t\t\tImage_WriteAPNGFrame(buffer, target_params->width, target_params->height, movie_fps.integer);\n\t\t}\n\t\telse {\n\t\t\tsuccess = Image_WritePNG(name, image_png_compression_level.value, buffer, target_params->width, target_params->height) ? SSHOT_SUCCESS : SSHOT_FAILED;\n\t\t}\n\t}\n#endif\n\n#ifdef WITH_JPEG\n\tif (format == IMAGE_JPEG) {\n\t\tapplyHWGamma(buffer, buffersize);\n\t\tsuccess = Image_WriteJPEG(\n\t\t\tname, image_jpeg_quality_level.value,\n\t\t\tbuffer + buffersize - 3 * target_params->width, -(int)target_params->width, (int)target_params->height\n\t\t) ? SSHOT_SUCCESS : SSHOT_FAILED;\n\t}\n#endif\n\n\tif (format == IMAGE_TGA) {\n\t\t// swap rgb to bgr\n\t\tfor (i = 0; i < buffersize; i += 3) {\n\t\t\ttemp = buffer[i];\n\t\t\tbuffer[i] = buffer[i + 2];\n\t\t\tbuffer[i + 2] = temp;\n\t\t}\n\t\tapplyHWGamma(buffer, buffersize);\n\t\tsuccess = Image_WriteTGA(name, buffer, target_params->width, target_params->height) ? SSHOT_SUCCESS : SSHOT_FAILED;\n\t}\n\n\tif (target_params->freeMemory) {\n\t\tQ_free(target_params->buffer);\n\t}\n\tQ_free(target_params);\n\treturn success;\n}\n\n#define MAX_SCREENSHOT_COUNT\t65535\n\nint SCR_GetScreenShotName(char *name, int name_size, char *sshot_dir)\n{\n\tint i = 0;\n\tchar ext[4], basename[64];\n\tFILE *f;\n\n\text[0] = 0;\n\n\t// Find a file name to save it to\n#ifdef WITH_PNG\n\tif (!strcasecmp(scr_sshot_format.string, \"png\")) {\n\t\tstrlcpy(ext, \"png\", 4);\n\t}\n#endif\n\n#ifdef WITH_JPEG\n\tif (!strcasecmp(scr_sshot_format.string, \"jpeg\") || !strcasecmp(scr_sshot_format.string, \"jpg\")) {\n\t\tstrlcpy(ext, \"jpg\", 4);\n\t}\n#endif\n\n\tif (!strcasecmp(scr_sshot_format.string, \"tga\")) {\n\t\tstrlcpy(ext, \"tga\", 4);\n\t}\n\n\tif (!strcasecmp(scr_sshot_format.string, \"pcx\")) {\n\t\tstrlcpy(ext, \"pcx\", 4);\n\t}\n\n\tif (!ext[0]) {\n\t\tstrlcpy(ext, DEFAULT_SSHOT_FORMAT, sizeof(ext));\n\t}\n\n\tif (fabsf(scr_sshot_autoname.value - 1.0f) < 0.0001f) {\n\t\t// if sshot_autoname is 1, prefix with map name.\n\t\tsnprintf(basename, sizeof(basename), \"%s_\", host_mapname.string);\n\t}\n\telse {\n\t\t// otherwise prefix with ezquake.\n\t\tstrcpy(basename, \"ezquake\");\n\t}\n\n\tfor (i = 0; i < MAX_SCREENSHOT_COUNT; i++) {\n\t\tsnprintf(name, name_size, \"%s%03i.%s\", basename, i, ext);\n\t\tif (!(f = fopen(va(\"%s/%s\", sshot_dir, name), \"rb\"))) {\n\t\t\tbreak;  // file doesn't exist\n\t\t}\n\t\tfclose(f);\n\t}\n\n\tif (i == MAX_SCREENSHOT_COUNT) {\n\t\tCom_Printf(\"Error: Cannot create more than %d screenshots\\n\", MAX_SCREENSHOT_COUNT);\n\t\treturn -1;\n\t}\n\n\treturn 1;\n}\n\nvoid SCR_ScreenShot_f(void)\n{\n\tchar name[MAX_OSPATH], *filename, *sshot_dir;\n\tint success;\n\n\tsshot_dir = Sshot_SshotDirectory();\n\n\tif (Cmd_Argc() == 2) {\n\t\tstrlcpy(name, Cmd_Argv(1), sizeof(name));\n\t}\n\telse if (Cmd_Argc() == 1) {\n\t\tif (SCR_GetScreenShotName(name, sizeof(name), sshot_dir) < 0) {\n\t\t\treturn;\n\t\t}\n\t}\n\telse {\n\t\tCom_Printf(\"Usage: %s [filename]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tfor (filename = name; *filename == '/' || *filename == '\\\\'; filename++)\n\t\t;\n\n\tsuccess = SCR_Screenshot(va(\"%s/%s\", sshot_dir, filename), false);\n\n\tif (success != SSHOT_FAILED_QUIET) {\n\t\tCom_Printf(\"%s %s\\n\", success == SSHOT_SUCCESS ? \"Wrote\" : \"Couldn't write\", name);\n\t}\n}\n\nvoid SCR_RSShot_f(void)\n{\n\tint success = SSHOT_FAILED;\n\tchar filename[MAX_PATH];\n\tint width, height;\n\tbyte *base, *pixels;\n\n\tif (CL_IsUploading())\n\t\treturn;\t\t// already one pending\n\n\tif (cls.state < ca_onserver)\n\t\treturn;\t\t// gotta be connected\n\n\tif (!scr_allowsnap.value) {\n\t\tMSG_WriteByte(&cls.netchan.message, clc_stringcmd);\n\t\tSZ_Print(&cls.netchan.message, \"snap\\n\");\n\t\treturn;\n\t}\n\t\n\tsnprintf(filename, sizeof(filename), \"%s/temp/__rsshot__\", Sshot_SshotDirectory());\n\n\twidth = 800; height = 600;\n\tbase = (byte *)Q_malloc((width * height + glwidth * glheight) * 3);\n\tpixels = base + glwidth * glheight * 3;\n\n\trenderer.Screenshot(base, glwidth * glheight * 3);\n\tImage_Resample(base, glwidth, glheight, pixels, width, height, 3, 0);\n#ifdef WITH_JPEG\n\tsuccess = Image_WriteJPEG(filename, 70, pixels + 3 * width * (height - 1), -width, height) ? SSHOT_SUCCESS : SSHOT_FAILED;\n#endif\n\tif (success == SSHOT_FAILED) {\n\t\tsuccess = Image_WriteTGA(filename, pixels, width, height) ? SSHOT_SUCCESS : SSHOT_FAILED;\n\t}\n\n\tQ_free(base);\n\n\tif (success == SSHOT_SUCCESS) {\n\t\tFILE\t*f;\n\t\tbyte\t*screen_shot;\n\t\tint\tsize;\n\t\tif ((size = FS_FileOpenRead(filename, &f)) != -1) {\n\t\t\tscreen_shot = (byte *)Q_malloc(size);\n\t\t\tif (fread(screen_shot, 1, (size_t)size, f) == (size_t)size) {\n\t\t\t\tCL_StartUpload(screen_shot, size);\n\t\t\t}\n\n\t\t\tfclose(f);\n\t\t\tQ_free(screen_shot);\n\t\t}\n\t}\n\n\tremove(filename);\n}\n\nqbool SCR_TakingAutoScreenshot(void)\n{\n\treturn scr_autosshot_countdown > 0;\n}\n\nvoid SCR_CheckAutoScreenshot(void)\n{\n\tchar *filename, savedname[MAX_PATH], *sshot_dir, *fullsavedname, *ext;\n\tchar *exts[5] = { \"pcx\", \"tga\", \"png\", \"jpg\", NULL };\n\tint num;\n\n\tif (scr_autosshot_countdown <= 0 || --scr_autosshot_countdown) {\n\t\treturn;\n\t}\n\n\tif (!cl.intermission) {\n\t\treturn;\n\t}\n\n\tsshot_dir = Sshot_SshotDirectory();\n\n\t// no, sorry\n\t//\twhile (*sshot_dir && (*sshot_dir == '/'))\n\t//\t\tsshot_dir++; // will skip all '/' chars at the beginning\n\n\tif (!sshot_dir[0])\n\t\tsshot_dir = cls.gamedir;\n\n\tfor (filename = auto_matchname; *filename == '/' || *filename == '\\\\'; filename++)\n\t\t;\n\n\text = SShot_ExtForFormat(SShot_FormatForName(filename));\n\n\tfullsavedname = va(\"%s/%s\", sshot_dir, filename);\n\tif ((num = Util_Extend_Filename(fullsavedname, exts)) == -1) {\n\t\tCom_Printf(\"Error: no available filenames\\n\");\n\t\treturn;\n\t}\n\n\tsnprintf(savedname, sizeof(savedname), \"%s_%03i%s\", filename, num, ext);\n\tfullsavedname = va(\"%s/%s\", sshot_dir, savedname);\n\n\trenderer.EnsureFinished();\n\n\tif ((SCR_Screenshot(fullsavedname, false)) == SSHOT_SUCCESS) {\n\t\tCom_Printf(\"Match scoreboard saved to %s\\n\", savedname);\n\t}\n}\n\nvoid SCR_AutoScreenshot(char *matchname)\n{\n\tif (cl.intermission == 1) {\n\t\tscr_autosshot_countdown = vid.numpages;\n\t\tstrlcpy(auto_matchname, matchname, sizeof(auto_matchname));\n\t}\n}\n\n// Capturing to avi.\nvoid SCR_Movieshot(char *name)\n{\n#ifdef _WIN32\n\tif (Movie_IsCapturingAVI()) {\n\t\tint size = 0;\n\t\t// Capturing a movie.\n\t\tint i;\n\t\tbyte *buffer, temp;\n\n\t\t// Set buffer size to fit RGB data for the image.\n\t\tsize = glwidth * glheight * 3;\n\n\t\t// Allocate the RGB buffer, get the pixels from GL and apply the gamma.\n\t\tbuffer = (byte *)Q_malloc(size);\n\t\trenderer.Screenshot(buffer, size);\n\t\tapplyHWGamma(buffer, size);\n\n\t\t// We now have a byte buffer with RGB values, but\n\t\t// before we write it to the file, we need to swap\n\t\t// them to GBR instead, which windows DIBs uses.\n\t\t// (There's a GL Extension that allows you to use\n\t\t// BGR_EXT instead of GL_RGB in the glReadPixels call\n\t\t// instead, but there is no real speed gain using it).\n\t\tfor (i = 0; i < size; i += 3) {\n\t\t\t// Swap RGB => GBR\n\t\t\ttemp = buffer[i];\n\t\t\tbuffer[i] = buffer[i + 2];\n\t\t\tbuffer[i + 2] = temp;\n\t\t}\n\n\t\t// Write the buffer to video.\n\t\tCapture_WriteVideo(buffer, size);\n\n\t\tQ_free(buffer);\n\t}\n\telse {\n\t\t// We're just capturing images.\n\t\tSCR_Screenshot(name, true);\n\t}\n\n#else // _WIN32\n\n\t// Capturing to avi only supported in windows yet.\n\tSCR_Screenshot(name, true);\n\n#endif // _WIN32\n}\n\nstatic void OnChange_scr_allowsnap(cvar_t *var, char *s, qbool *cancel)\n{\n\t*cancel = (cls.state >= ca_connected && cbuf_current == &cbuf_svc);\n}\n\nvoid SShot_RegisterCvars(void)\n{\n\tif (!host_initialized) {\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_SCREENSHOTS);\n\t\tCvar_Register(&scr_allowsnap);\n\t\tCvar_Register(&scr_sshot_autoname);\n\t\tCvar_Register(&scr_sshot_format);\n\t\tCvar_Register(&scr_sshot_dir);\n\t\tCvar_ResetCurrentGroup();\n\n\t\tCmd_AddCommand(\"screenshot\", SCR_ScreenShot_f);\n\t}\n}\n"
  },
  {
    "path": "src/cl_skygroups.c",
    "content": "/*\n\nCopyright (C) 2002-2003       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#include \"quakedef.h\"\n#include \"utils.h\"\n\n#define FIRSTUSERSKYGROUP (last_system_skygroup ? last_system_skygroup->next : skygroups)\n#define MAX_SKYGROUP_MEMBERS\t36\n\ntypedef struct skygroup_s {\n\tchar groupname[MAX_QPATH];\n\tchar members[MAX_SKYGROUP_MEMBERS][MAX_QPATH];\n\tstruct skygroup_s *next, *prev;\n\tqbool system;\n\tint nummembers;\n} skygroup_t;\n\nstatic skygroup_t *skygroups = NULL;\nstatic skygroup_t *last_system_skygroup = NULL;\nstatic qbool skygroups_init = false;\n\nstatic skygroup_t *GetSkyGroupWithName(char *groupname)\n{\n\tskygroup_t *node;\n\n\tfor (node = skygroups; node; node = node->next) {\n\t\tif (!strcasecmp(node->groupname, groupname))\n\t\t\treturn node;\n\t}\n\treturn NULL;\n}\n\nstatic skygroup_t *GetSkyGroupWithMember(char *member)\n{\n\tint j;\n\tskygroup_t *node;\n\n\tfor (node = skygroups; node; node = node->next) {\n\t\tfor (j = 0; j < node->nummembers; j++) {\n\t\t\tif (!strcasecmp(node->members[j], member))\n\t\t\t\treturn node;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nstatic void DeleteSkyGroup(skygroup_t *group)\n{\n\tif (!group)\n\t\treturn;\n\n\tif (group->prev)\n\t\tgroup->prev->next = group->next;\n\tif (group->next)\n\t\tgroup->next->prev = group->prev;\n\n\n\tif (group == skygroups) {\n\t\tskygroups = skygroups->next;\n\t\tif (group == last_system_skygroup)\n\t\t\tlast_system_skygroup = NULL;\n\t}\n\telse if (group == last_system_skygroup) {\n\t\tlast_system_skygroup = last_system_skygroup->prev;\n\t}\n\n\tQ_free(group);\n}\n\nstatic void ResetSkyGroupMembers(skygroup_t *group)\n{\n\tint i;\n\n\tif (!group)\n\t\treturn;\n\n\tfor (i = 0; i < group->nummembers; i++)\n\t\tgroup->members[i][0] = 0;\n\n\tgroup->nummembers = 0;\n}\n\nstatic void DeleteSkyGroupMember(skygroup_t *group, char *member)\n{\n\tint i;\n\n\tif (!group)\n\t\treturn;\n\n\tfor (i = 0; i < group->nummembers; i++) {\n\t\tif (!strcasecmp(member, group->members[i]))\n\t\t\tbreak;\n\t}\n\n\tif (i == group->nummembers)\n\t\treturn;\n\n\tif (i < group->nummembers - 1)\n\t\tmemmove(group->members[i], group->members[i + 1], (group->nummembers - 1 - i) * sizeof(group->members[0]));\n\n\tgroup->nummembers--;\n}\n\nstatic void AddSkyGroupMember(skygroup_t *group, char *member)\n{\n\tint i;\n\n\tif (!group || group->nummembers == MAX_SKYGROUP_MEMBERS)\n\t\treturn;\n\n\tfor (i = 0; i < group->nummembers; i++) {\n\t\tif (!strcasecmp(member, group->members[i]))\n\t\t\treturn;\n\t}\n\n\tstrlcpy(group->members[group->nummembers], member, sizeof(group->members[group->nummembers]));\n\tgroup->nummembers++;\n}\n\nvoid MT_SkyGroup_f(void)\n{\n\tint i, c, j;\n\tqbool removeflag = false;\n\tskygroup_t *node, *group, *tempnode;\n\textern cvar_t r_skyname;\n\tchar *groupname, *member;\n\n\textern int R_SetSky(char *skyname);\n\n\tif ((c = Cmd_Argc()) == 1) {\n\t\tif (!FIRSTUSERSKYGROUP) {\n\t\t\tCom_Printf(\"No sky groups defined\\n\");\n\t\t}\n\t\telse {\n\t\t\tfor (node = FIRSTUSERSKYGROUP; node; node = node->next) {\n\t\t\t\tCom_Printf(\"\\x02%s: \", node->groupname);\n\t\t\t\tfor (j = 0; j < node->nummembers; j++)\n\t\t\t\t\tCom_Printf(\"%s \", node->members[j]);\n\t\t\t\tCom_Printf(\"\\n\");\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\tgroupname = Cmd_Argv(1);\n\n\tif (c == 2 && !strcasecmp(groupname, \"clear\")) {\n\t\tfor (node = FIRSTUSERSKYGROUP; node; node = tempnode) {\n\t\t\ttempnode = node->next;\n\t\t\tDeleteSkyGroup(node);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (Util_Is_Valid_Filename(groupname) == false) {\n\t\tCom_Printf(\"Error: %s is not a valid sky group name\\n\", groupname);\n\t\treturn;\n\t}\n\n\tgroup = GetSkyGroupWithName(groupname);\n\n\tif (c == 2) {\n\t\tif (!group) {\n\t\t\tCom_Printf(\"No sky group named \\\"%s\\\"\\n\", groupname);\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"\\x02%s: \", groupname);\n\t\t\tfor (j = 0; j < group->nummembers; j++)\n\t\t\t\tCom_Printf(\"%s \", group->members[j]);\n\t\t\tCom_Printf(\"\\n\");\n\t\t}\n\t\treturn;\n\t}\n\n\tif (group && group->system) {\n\t\tCom_Printf(\"Cannot modify system group \\\"%s\\\"\\n\", groupname);\n\t\treturn;\n\t}\n\n\tif (c == 3 && !strcasecmp(Cmd_Argv(2), \"clear\")) {\n\t\tif (!group && !strcmp(\"exmx\", groupname))\n\t\t\tCom_Printf(\"\\\"%s\\\" is not a sky group name\\n\", groupname);\n\t\telse\n\t\t\tDeleteSkyGroup(group);\n\t\treturn;\n\t}\n\n\n\n\tif (!group) {\n\t\tgroup = (skygroup_t *)Q_calloc(1, sizeof(skygroup_t));\n\t\tstrlcpy(group->groupname, groupname, sizeof(group->groupname));\n\t\tgroup->system = !skygroups_init;\n\t\tif (skygroups) {\n\t\t\tfor (tempnode = skygroups; tempnode->next; tempnode = tempnode->next)\n\t\t\t\t;\n\t\t\ttempnode->next = group;\n\t\t\tgroup->prev = tempnode;\n\t\t}\n\t\telse {\n\t\t\tskygroups = group;\n\t\t}\n\t}\n\telse {\n\t\tmember = Cmd_Argv(2);\n\t\tif (member[0] != '+' && member[0] != '-')\n\t\t\tResetSkyGroupMembers(group);\n\t}\n\n\tfor (i = 2; i < c; i++) {\n\t\tmember = Cmd_Argv(i);\n\t\tif (member[0] == '+') {\n\t\t\tremoveflag = false;\n\t\t\tmember++;\n\t\t}\n\t\telse if (member[0] == '-') {\n\t\t\tremoveflag = true;\n\t\t\tmember++;\n\t\t}\n\n\t\tif (!removeflag && (tempnode = GetSkyGroupWithMember(member)) && tempnode != group) {\n\t\t\tif (cl_warncmd.integer || developer.integer)\n\t\t\t\tCom_Printf(\"Warning: \\\"%s\\\" is already a member of group \\\"%s\\\"...ignoring\\n\", member, tempnode->groupname);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (removeflag)\n\t\t\tDeleteSkyGroupMember(group, member);\n\t\telse\n\t\t\tAddSkyGroupMember(group, member);\n\t}\n\n\tif (!group->nummembers) {\n\t\tDeleteSkyGroup(group);\n\t}\n\n\tR_SetSky(r_skyname.string);\n}\n\nvoid MT_AddSkyGroups(void)\n{\n\tchar clear[256] = { 0 };\n\n\tstrlcat(clear, \"skygroup exmy clear\", sizeof(clear));\n\tCmd_TokenizeString(clear);\n\tMT_SkyGroup_f();\n\tskygroups_init = true;\n}\n\nchar *MT_GetSkyGroupName(char *mapname, qbool *system)\n{\n\tskygroup_t *group;\n\n\tgroup = GetSkyGroupWithMember(mapname);\n\tif (group && strcmp(mapname, group->groupname)) {\n\t\tif (system)\n\t\t\t*system = group->system;\n\t\treturn group->groupname;\n\t}\n\telse {\n\t\treturn NULL;\n\t}\n}\n\nvoid DumpSkyGroups(FILE *f)\n{\n\tskygroup_t *node;\n\tint j;\n\tif (!FIRSTUSERSKYGROUP) {\n\t\tfprintf(f, \"skygroup clear\\n\");\n\t\treturn;\n\t}\n\tfor (node = FIRSTUSERSKYGROUP; node; node = node->next) {\n\t\tfprintf(f, \"skygroup %s \", node->groupname);\n\t\tfor (j = 0; j < node->nummembers; j++)\n\t\t\tfprintf(f, \"%s \", node->members[j]);\n\t\tfprintf(f, \"\\n\");\n\t}\n}\n"
  },
  {
    "path": "src/cl_slist.c",
    "content": "/*\nCopyright (C) 1999,2000  contributors of the QuakeForge project\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: cl_slist.c,v 1.10 2007-03-11 06:01:36 disconn3ct Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"cl_slist.h\"\n#include \"utils.h\"\n\n\nstatic qbool slist_initialised = false;\n\nserver_entry_t\tslist[MAX_SERVER_LIST];\n\nvoid SList_Init(void) {\n\tmemset(&slist, 0, sizeof(slist));\n}\n\nvoid SList_Shutdown(void) {  \n\tchar filename[MAX_OSPATH] = {0};\n\tFILE *f;\n\n\tif (!slist_initialised)\n\t\treturn;\n\n\tsnprintf(&filename[0], sizeof(filename), \"%s/servers.txt\", com_basedir);\n\n\tif (!(f = fopen(filename, \"w\"))) {\n\t\tCom_DPrintf(\"Couldn't open %s\\n\", filename);\n\t\treturn;\n\t}\n\tSList_Save(f);\n\tfclose(f);\n}\n\nvoid SList_Set (int i, char *addr, char *desc) {\n\tif (i >= MAX_SERVER_LIST || i < 0)\n\t\tSys_Error(\"SList_Switch: Bad index %d\", i);\n\n\tif (slist[i].server)\n\t\tQ_free(slist[i].server);\n\tif (slist[i].description)\n\t\tQ_free(slist[i].description);\n\n\tslist[i].server = Q_strdup (addr);\n\tslist[i].description = Q_strdup (desc);\n}\n\nvoid SList_Reset_NoFree (int i) { \n\tif (i >= MAX_SERVER_LIST || i < 0)\n\t\tSys_Error(\"SList_Switch: Bad index %d\", i);\n\n\tslist[i].description = slist[i].server = NULL;\n}\n\nvoid SList_Reset (int i) {\n\tif (i >= MAX_SERVER_LIST || i < 0)\n\t\tSys_Error(\"SList_Switch: Bad index %d\", i);\n\n\tif (slist[i].server) {\n\t\tQ_free(slist[i].server);\n\t\tslist[i].server = NULL;\n\t}\n\n\tif (slist[i].description) {\n\t\tQ_free(slist[i].description);\n\t\tslist[i].description = NULL;\n\t}\n}\n\nvoid SList_Switch (int a, int b) {\n\tserver_entry_t temp;\n\n\tif (a >= MAX_SERVER_LIST || a < 0)\n\t\tSys_Error(\"SList_Switch: Bad index %d\", a);\n\tif (b >= MAX_SERVER_LIST || b < 0)\n\t\tSys_Error(\"SList_Switch: Bad index %d\", b);\n\n\tmemcpy(&temp, &slist[a], sizeof(temp));\n\tmemcpy(&slist[a], &slist[b], sizeof(temp));\n\tmemcpy(&slist[b], &temp, sizeof(temp));\n}\n\nint SList_Length (void) {\n\tint count;\n\n\tfor (count = 0; count < MAX_SERVER_LIST && slist[count].server; count++)\n\t\t;\n\treturn count;\n}\n\nvoid SList_Load (void) {\n\tchar line[128];\n\tchar filename[MAX_OSPATH] = {0};\n\tchar *desc, *addr;\n\tunsigned int len;\n\tint c, argc, count;\n\tFILE *f;\n\n\tsnprintf(&filename[0], sizeof(filename), \"%s/servers.txt\", com_basedir);\n\tif (!(f = fopen(filename, \"r\")))\n\t\treturn;\n\n\tcount = len = 0;\n\twhile ((c = getc(f))) {\n\t\tif (c == '\\n' || c == '\\r' || c == EOF) {\n\t\t\tif (c == '\\r' && (c = getc(f)) != '\\n' && c != EOF)\n\t\t\t\tungetc(c, f);\n\n\t\t\tline[len] = 0;\n\t\t\tlen = 0;\n\t\t\tCmd_TokenizeString(line);\n\n\t\t\tif ((argc = Cmd_Argc()) >= 1) {\n\t\t\t\taddr = Cmd_Argv(0);\n\t\t\t\tdesc = (argc >= 2) ? Cmd_MakeArgs(1) : \"Unknown\";\n\t\t\t\tSList_Set (count, addr, desc);\n\t\t\t\tif (++count == MAX_SERVER_LIST)\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (c == EOF)\n\t\t\t\tbreak;\t//just in case an EOF follows a '\\r'\n\t\t} else {\n\t\t\tif (len + 1 < sizeof(line))\n\t\t\t\tline[len++] = c;\n\t\t}\n\t}\n\n\tfclose (f);\n\n\tslist_initialised = true;\n}\n\nvoid SList_Save (FILE *f) {\n\tint i;\n\tchar *spaces;\n\n\tfor (i = 0; i < MAX_SERVER_LIST; i++) {\n\t\tif (!slist[i].server)\n\t\t\tbreak;\n\t\tspaces = CreateSpaces(32 - strlen(slist[i].server));\n\t\tfprintf(f, \"%s%s%s\\n\", slist[i].server, spaces, slist[i].description);\n\t}\n}\n"
  },
  {
    "path": "src/cl_slist.h",
    "content": "/*\nCopyright (C) 1999,2000  contributors of the QuakeForge project\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#define MAX_SERVER_LIST 512\n\ntypedef struct {\n\tchar *server;\n\tchar *description;\n} server_entry_t;\n\nextern server_entry_t slist[MAX_SERVER_LIST];\n\nvoid SList_Init(void);\nvoid SList_Shutdown(void);\nvoid SList_Set(int i,char *addr,char *desc);\nvoid SList_Reset_NoFree(int i);\nvoid SList_Reset(int i);\nvoid SList_Switch(int a,int b);\nint SList_Length(void);\nvoid SList_Load(void);\nvoid SList_Save(FILE *f);\n"
  },
  {
    "path": "src/cl_tent.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: cl_tent.c,v 1.31 2007-10-29 00:13:26 d3urk Exp $\n*/\n// cl_tent.c -- client side temporary entities\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"vx_stuff.h\"\n#include \"rulesets.h\"\n#include \"pmove.h\"\n#include \"utils.h\"\n#include \"qsound.h\"\n#include \"qmb_particles.h\"\n\nextern cvar_t gl_no24bit;\n\ntemp_entity_list_t temp_entities;\n\n#define\tMAX_BEAMS 32\ntypedef struct \n{\n\tint entity;\n\tmodel_t *model;\n\tfloat endtime;\n\tvec3_t start, end;\n} beam_t;\n\nbeam_t cl_beams[MAX_BEAMS];\n\nstatic vec3_t playerbeam_end;\nstatic qbool playerbeam_update;\n\n#define MAX_EXPLOSIONS 32\ntypedef struct explosion_s \n{\n\tstruct explosion_s *prev, *next;\n\tvec3_t origin;\n\tfloat start;\n\tmodel_t *model;\n} explosion_t;\n\n\n// cl_free_explosions = linear linked list of free explosions\nexplosion_t\tcl_explosions[MAX_EXPLOSIONS];\nexplosion_t cl_explosions_headnode, *cl_free_explosions;\n\nstatic model_t\t*cl_explo_mod, *cl_bolt1_mod, *cl_bolt2_mod, *cl_bolt3_mod, *cl_beam_mod;\n\nsfx_t\t*cl_sfx_wizhit, *cl_sfx_knighthit, *cl_sfx_tink1, *cl_sfx_ric1, *cl_sfx_ric2, *cl_sfx_ric3, *cl_sfx_r_exp3;\n\ncvar_t r_lgbloodColor = {\"r_lgbloodColor\", \"225\"};\ncvar_t r_rlbloodColor_small = {\"r_rlbloodColor_small\", \"225\"};\ncvar_t r_rlbloodColor_big = {\"r_rlbloodColor_big\", \"73\"};\ncvar_t r_sgbloodColor = {\"r_sgbloodColor\", \"73\"};\ncvar_t r_shiftbeam = {\"r_shiftbeam\", \"0\"};\n\nvoid CL_InitTEnts(void)\n{\n\tcl_sfx_wizhit = S_PrecacheSound (\"wizard/hit.wav\");\n\tcl_sfx_knighthit = S_PrecacheSound (\"hknight/hit.wav\");\n\tcl_sfx_tink1 = S_PrecacheSound (\"weapons/tink1.wav\");\n\tcl_sfx_ric1 = S_PrecacheSound (\"weapons/ric1.wav\");\n\tcl_sfx_ric2 = S_PrecacheSound (\"weapons/ric2.wav\");\n\tcl_sfx_ric3 = S_PrecacheSound (\"weapons/ric3.wav\");\n\tcl_sfx_r_exp3 = S_PrecacheSound (\"weapons/r_exp3.wav\");\n}\n\nvoid CL_InitTEntsCvar(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_EYECANDY);\n\tCvar_Register(&r_lgbloodColor);\n\tCvar_Register(&r_rlbloodColor_small);\n\tCvar_Register(&r_rlbloodColor_big);\n\tCvar_Register(&r_sgbloodColor);\n\tCvar_Register(&r_shiftbeam);\n\tCvar_ResetCurrentGroup();\n}\n\nvoid CL_ClearTEnts(void) \n{\n\tint i;\n\n\tcl_explo_mod = cl_bolt1_mod = cl_bolt2_mod = cl_bolt3_mod = cl_beam_mod = NULL;\n\n\tmemset (&cl_beams, 0, sizeof(cl_beams));\n\tmemset (&cl_explosions, 0, sizeof(cl_explosions));\n\n\t// link explosions \n\tcl_free_explosions = cl_explosions; \n\tcl_explosions_headnode.next = cl_explosions_headnode.prev = &cl_explosions_headnode; \n\n\tfor (i = 0; i < MAX_EXPLOSIONS - 1; i++) \n\t\tcl_explosions[i].next = &cl_explosions[i + 1]; \n}\n\nexplosion_t *CL_AllocExplosion(void) \n{\n\texplosion_t *ex;\n\n\tif (cl_free_explosions) \n\t{\t\n\t\t// Take a free explosion if possible.\n\t\tex = cl_free_explosions;\n\t\tcl_free_explosions = ex->next;\n\t} \n\telse\n\t{\n\t\t// Grab the oldest one otherwise.\n\t\tex = cl_explosions_headnode.prev;\n\t\tex->prev->next = ex->next;\n\t\tex->next->prev = ex->prev;\n\t}\n\n\t// Put the explosion at the start of the list.\n\tex->prev = &cl_explosions_headnode;\n\tex->next = cl_explosions_headnode.next;\n\tex->next->prev = ex;\n\tex->prev->next = ex;\n\n\treturn ex;\n}\n\nvoid CL_FreeExplosion(explosion_t *ex)\n{\n\t// Remove from linked active list.\n\tex->prev->next = ex->next;\n\tex->next->prev = ex->prev;\n\n\t// Insert into front linked free list.\n\tex->next = cl_free_explosions;\n\tcl_free_explosions = ex;\n}\n\nstatic void CL_ParseBeam(int type, vec3_t end)\n{\n\tint ent, i;\n\tvec3_t start;\n\tbeam_t *b;\n\tstruct model_s *m;\n\n\tent = MSG_ReadShort();\n\n\tstart[0] = MSG_ReadCoord();\n\tstart[1] = MSG_ReadCoord();\n\tstart[2] = MSG_ReadCoord();\n\n\tend[0] = MSG_ReadCoord();\n\tend[1] = MSG_ReadCoord();\n\tend[2] = MSG_ReadCoord();\n\n\tif (CL_Demo_SkipMessage(true))\n\t\treturn;\n\n\tif (cl.intermission && ent >= 1 && ent <= MAX_CLIENTS) {\n\t\treturn;\n\t}\n\n    // an experimental protocol extension:\n    // TE_LIGHTNING1 with entity num in -512..-1 range is a rail trail\n    if (type == 1 && (ent >= -512 && ent <= -1)) \n\t{\n        int colors[8] = { 6 /* white (change to something else?) */,\n                208 /* blue */,\n                180 /* green */, 35 /* light blue */, 224 /* red */,\n                133 /* magenta... kinda */, 192 /* yellow */, 6 /* white */};\n        int color;\n        int cnum;\n\n        // -512..-257 are colored trails assigned to a specific\n        // player, so overrides can be applied; encoded as follows:\n        // 7654321076543210\n        // 1111111nnnnnnccc  (n = player num, c = color code)\n        cnum = ent & 7;\n        //pnum = (ent >> 3) & 63;\n        color = colors[cnum];\n\n\t\t//color is ignored by most of trails\n\t\tswitch(r_instagibtrail.integer)\n\t\t{\n\t\t\tcase 1:\n\t\t\tR_ParticleTrail(start, end, GRENADE_TRAIL);\n\t\t\tbreak;\n\n\t\t\tcase 2:\n\t\t\tR_ParticleTrail(start, end, ROCKET_TRAIL);\n\t\t\tbreak;\n\n\t\t\tcase 3:\n\t\t\tR_ParticleTrail(start, end, ALT_ROCKET_TRAIL);\n\t\t\tbreak;\n\n\t\t\tcase 4:\n\t\t\tR_ParticleTrail(start, end, BLOOD_TRAIL);\n\t\t\tbreak;\n\n\t\t\tcase 5:\n\t\t\tR_ParticleTrail(start, end, BIG_BLOOD_TRAIL);\n\t\t\tbreak;\n\n\t\t\tcase 6:\n\t\t\tR_ParticleTrail(start, end, TRACER1_TRAIL);\n\t\t\tbreak;\n\n\t\t\tcase 7:\n\t\t\tR_ParticleTrail(start, end, TRACER2_TRAIL);\n\t\t\tbreak;\n\n\t\t\tcase 8:\n\t\t\tR_ParticleTrail(start, end, VOOR_TRAIL);\n\t\t\tbreak;\n\n\t\t\tcase 9:\n\t\t\tClassic_ParticleRailTrail(start, end, color);\n\t\t\tbreak;\n\n\t\t\tcase 10:\n\t\t\tR_ParticleTrail(start, end, RAIL_TRAIL);\n\t\t\tbreak;\n\n\t\t\tcase 11:\n\t\t\tQMB_ParticleRailTrail(start, end, color);\n\t\t\tbreak;\n\n\t\t\tcase 12:\n\t\t\tR_ParticleTrail(start, end, LAVA_TRAIL);\n\t\t\tbreak;\n\n\t\t\tcase 13:\n\t\t\tR_ParticleTrail(start, end, AMF_ROCKET_TRAIL);\n\t\t\tbreak;\n\n\t\t\tdefault: break;\n\t\t}\n\n        return;\n    }\n\n\tswitch (type) \n\t{\n\t\tcase 1:\n\t\t\tif (!cl_bolt1_mod)\n\t\t\t\tcl_bolt1_mod = Mod_CustomModel(custom_model_bolt, true);\n\t\t\tm = cl_bolt1_mod;\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tif (!cl_bolt2_mod)\n\t\t\t\tcl_bolt2_mod = Mod_CustomModel(custom_model_bolt2, true);\n\t\t\tm = cl_bolt2_mod;\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\tif (!cl_bolt3_mod)\n\t\t\t\tcl_bolt3_mod = Mod_CustomModel(custom_model_bolt3, true);\n\t\t\tm = cl_bolt3_mod;\n\t\t\tbreak;\n\t\tcase 4: default:\n\t\t\tif (!cl_beam_mod)\n\t\t\t\tcl_beam_mod = Mod_CustomModel(custom_model_beam, true);\n\t\t\tm = cl_beam_mod;\n\t\t\tbreak;\n\t}\n\t\n\tif (ent == cl.viewplayernum + 1) {\n\t\tVectorCopy(end, playerbeam_end); // for cl_fakeshaft\n\t\tplayerbeam_update = true;\n\t}\n\n\t// Override any beam with the same entity.\n\tfor (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) \n\t{\n\t\tif (b->entity == ent) \n\t\t{\n\t\t\tb->model = m;\n\t\t\tb->endtime = cl.time + 0.2;\n\t\t\tVectorCopy(start, b->start);\n\t\t\tVectorCopy(end, b->end);\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// Find a free beam.\n\tfor (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++)\n\t{\n\t\tif (!b->model || b->endtime < cl.time) \n\t\t{\n\t\t\tb->entity = ent;\n\t\t\tb->model = m;\n\t\t\tb->endtime = cl.time + 0.2;\n\t\t\tVectorCopy(start, b->start);\n\t\t\tVectorCopy(end, b->end);\n\t\t\treturn;\n\t\t}\n\t}\n\tCom_DPrintf (\"beam list overflow!\\n\");\n}\n\nvoid CL_ExplosionSprite(vec3_t pos) \n{\t\t\t\t\t\n\texplosion_t\t*ex;\n\n\tex = CL_AllocExplosion();\n\tVectorCopy(pos, ex->origin);\n\tex->start = cl.time;\n\tif (!cl_explo_mod)\n\t\tcl_explo_mod = Mod_CustomModel(custom_model_explosion, true);\n\tex->model = cl_explo_mod;\n}\n\nstatic void CL_Parse_TE_WIZSPIKE(vec3_t pos)\n{\n\tif (amf_part_sparks.value && !Rulesets_RestrictParticles())\n\t{\n\t\tbyte col[3] = {0, 124, 0};\n\t\tSparkGen(pos, col, 20, 90, 1);\n\t}\n\telse\n\t{\n\t\tR_RunParticleEffect(pos, vec3_origin, 20, 30);\n\t}\n\tS_StartSound(-1, 0, cl_sfx_wizhit, pos, 1, 1);\n}\n\n// FIXME: D-Kure: This seems to be unused\n#if 0\nstatic void CL_Parse_TE_KNIGHTSPIKE(vec3_t pos)\n{\n\tif (amf_part_sparks.value && !Rulesets_RestrictParticles())\n\t{\n\t\tbyte col[3] = {255, 77, 0};\n\t\tSparkGen(pos, col, 20, 150, 1);\n\t}\n\telse\n\t{\n\t\tR_RunParticleEffect(pos, vec3_origin, 226, 20);\n\t}\n\n\tS_StartSound(-1, 0, cl_sfx_knighthit, pos, 1, 1);\n}\n#endif\n\nstatic void CL_Parse_TE_SPIKE(vec3_t pos)\n{\n\tint rnd;\n\n\tif (amf_part_spikes.value && !Rulesets_RestrictParticles() && !gl_no24bit.integer)\n\t\tVXNailhit(pos, 10 * amf_part_spikes.value);\n\telse\n\t{\n\t\tR_RunParticleEffect(pos, vec3_origin, 0, 10);\n\t}\n\n\tif ( rand() % 5 ) \n\t{\n\t\tS_StartSound(-1, 0, cl_sfx_tink1, pos, 1, 1);\n\t}\n\telse\n\t{\n\t\trnd = rand() & 3;\n\t\tif (rnd == 1)\n\t\t\tS_StartSound(-1, 0, cl_sfx_ric1, pos, 1, 1);\n\t\telse if (rnd == 2)\n\t\t\tS_StartSound(-1, 0, cl_sfx_ric2, pos, 1, 1);\n\t\telse\n\t\t\tS_StartSound(-1, 0, cl_sfx_ric3, pos, 1, 1);\n\t}\n}\n\nstatic void CL_Parse_TE_SUPERSPIKE(vec3_t pos)\n{\n\tint rnd;\n\n\tif (amf_part_spikes.value && !Rulesets_RestrictParticles() && !gl_no24bit.integer)\n\t\tVXNailhit(pos, 20 * amf_part_spikes.value);\n\telse\n\t{\n\t\tR_RunParticleEffect(pos, vec3_origin, 0, 20);\n\t}\n\n\tif (rand() % 5) \n\t{\n\t\tS_StartSound(-1, 0, cl_sfx_tink1, pos, 1, 1);\n\t}\n\telse\n\t{\n\t\trnd = rand() & 3;\n\t\tif (rnd == 1)\n\t\t\tS_StartSound(-1, 0, cl_sfx_ric1, pos, 1, 1);\n\t\telse if (rnd == 2)\n\t\t\tS_StartSound(-1, 0, cl_sfx_ric2, pos, 1, 1);\n\t\telse\n\t\t\tS_StartSound(-1, 0, cl_sfx_ric3, pos, 1, 1);\n\t}\n}\n\nstatic void CL_Parse_TE_EXPLOSION(vec3_t pos)\n{\n\tdlight_t *dl;\n\textern cvar_t gl_part_explosions;\n\n\tif (cls.state != ca_active) {\n\t\treturn;\n\t}\n\n\tif (r_explosiontype.value == 2) {\n\t\tif (amf_part_teleport.value) {\n\t\t\tVXTeleport(pos);\n\t\t}\n\t\telse {\n\t\t\t// Teleport splash\n\t\t\tR_TeleportSplash(pos);\n\t\t}\n\n\t\tif (amf_coronas_tele.integer) {\n\t\t\tR_CoronasNew(C_BLUEFLASH, pos);\n\t\t}\n\t}\n\telse if (r_explosiontype.value == 3) {\n\t\t// lightning blood\n\t\tR_RunParticleEffect(pos, vec3_origin, r_rlbloodColor_small.value ? r_rlbloodColor_small.integer : 225, 50);\n\t}\n\telse if (r_explosiontype.value == 4) {\n\t\t// Big blood\n\t\tR_RunParticleEffect(pos, vec3_origin, r_rlbloodColor_big.value ? r_rlbloodColor_big.integer : 73, 20 * 32);\n\t}\n\telse if (r_explosiontype.value == 5) {\n\t\t// Double gunshot\n\t\tR_RunParticleEffect(pos, vec3_origin, 0, 20 * 14);\n\t}\n\telse if (r_explosiontype.value == 6) {\n\t\tif (amf_part_blobexplosion.value) {\n\t\t\tVXBlobExplosion(pos);\n\t\t}\n\t\telse {\n\t\t\tR_BlobExplosion(pos); // Blob explosion\n\t\t}\n\t\tif (amf_coronas.integer) {\n\t\t\tR_CoronasNew(C_BLUEFLASH, pos);\n\t\t}\n\t}\n\telse if (r_explosiontype.value == 7 && qmb_initialized && gl_part_explosions.value) {\n\t\tQMB_DetpackExplosion(pos);\t// Detpack explosion\n\t}\n\telse if (r_explosiontype.value == 8 && qmb_initialized) {\n\t\tFuelRodExplosion(pos);\n\t}\n\telse if (r_explosiontype.value == 10) {\n\t\t/* Explosions turned off */\n\t}\n\telse {\n\t\t// sprite and particles\n\t\tif (amf_part_explosion.value)\n\t\t\tVXExplosion(pos);\n\t\telse {\n\t\t\tR_ParticleExplosion(pos); // Normal explosion\n\t\t}\n\t}\n\n\tif (r_explosionlight.value) {\n\t\tdl = CL_AllocDlight(0);\n\t\tVectorCopy(pos, dl->origin);\n\t\tdl->radius = 150 + 200 * bound(0, r_explosionlight.value, 1);\n\t\tdl->die = cl.time + 0.5;\n\t\tdl->decay = 300;\n\t\tif (r_explosiontype.value == 8) {\n\t\t\tdl->type = lt_green;\n\t\t}\n\t\telse {\n\t\t\tcustomlight_t cst_lt = { 0 };\n\t\t\tdlightColorEx(r_explosionlightcolor.value, r_explosionlightcolor.string, lt_explosion, true, &cst_lt);\n\t\t\tdl->type = cst_lt.type;\n\n\t\t\tif (dl->type == lt_custom) {\n\t\t\t\tVectorCopy(cst_lt.color, dl->color);\n\t\t\t}\n\t\t}\n\t\tif (amf_coronas.integer && r_explosiontype.integer != 7 && r_explosiontype.integer != 2 && r_explosiontype.integer != 8) {\n\t\t\tR_CoronasNew(C_FLASH, pos);\n\t\t}\n\t}\n\n\tS_StartSound(-1, 0, cl_sfx_r_exp3, pos, 1, 1);\n}\n\nstatic void CL_Parse_TE_TAREXPLOSION(vec3_t pos)\n{\n\tif (amf_part_blobexplosion.value) {\n\t\tVXBlobExplosion(pos);\n\t}\n\telse {\n\t\tR_BlobExplosion(pos); // Blob explosion\n\t}\n\t\n\tif (amf_coronas.integer) {\n\t\tR_CoronasNew(C_BLUEFLASH, pos);\n\t}\n\n\tS_StartSound(-1, 0, cl_sfx_r_exp3, pos, 1, 1);\n}\n\nstatic void CL_Parse_TE_TELEPORT(vec3_t pos)\n{\n\tif (r_telesplash.value)\n\t{\n\t\tif (amf_part_teleport.value) {\n\t\t\tVXTeleport(pos);\n\t\t}\n\t\telse {\n\t\t\tR_TeleportSplash(pos); // Teleport splash\n\t\t}\n\t\tif (amf_coronas_tele.integer) {\n\t\t\tR_CoronasNew(C_BLUEFLASH, pos);\n\t\t}\n\t}\n}\n\nstatic void CL_Parse_TE_GUNSHOT(vec3_t pos)\n{\n\tint count;\n\n\tif (cls.nqdemoplayback) {\n\t\tcount = 1;\n\t}\n\telse {\n\t\tcount = MSG_ReadByte();\n\t}\n\n\tpos[0] = MSG_ReadCoord();\n\tpos[1] = MSG_ReadCoord();\n\tpos[2] = MSG_ReadCoord();\n\n\tif (CL_Demo_SkipMessage(true)) {\n\t\treturn;\n\t}\n\t\n\tif (amf_part_gunshot.value && !Rulesets_RestrictParticles()) {\n\t\tVXGunshot(pos, 5 * count * amf_part_gunshot.value);\n\t}\n\telse {\n\t\tR_RunParticleEffect(pos, vec3_origin, 256 /* magic! */, 20 * count);\n\t}\n}\n\nstatic void CL_Parse_TE_BLOOD(vec3_t pos)\n{\n\tint count;\n\tdlight_t *dl;\n\t\n\tif (cls.nqdemoplayback) {\n\t\t// NQ_TE_EXPLOSION2\n\t\tpos[0] = MSG_ReadCoord();\n\t\tpos[1] = MSG_ReadCoord();\n\t\tpos[2] = MSG_ReadCoord();\n\t\tMSG_ReadByte(); // colorStart\n\t\tMSG_ReadByte(); // colorLength\n\n\t\tif (CL_Demo_SkipMessage(true)) {\n\t\t\treturn;\n\t\t}\n\n\t\tdl = CL_AllocDlight(0);\n\t\tVectorCopy(pos, dl->origin);\n\t\tdl->radius = 350;\n\t\tdl->die = cl.time + 0.5;\n\t\tdl->decay = 300;\n\t\tS_StartSound(-1, 0, cl_sfx_r_exp3, pos, 1, 1);\n\t\treturn;\n\t}\n\n\tcount = MSG_ReadByte();\n\tpos[0] = MSG_ReadCoord();\n\tpos[1] = MSG_ReadCoord();\n\tpos[2] = MSG_ReadCoord();\n\n\tif (CL_Demo_SkipMessage(true)) {\n\t\treturn;\n\t}\n\n\tif (amf_part_blood.value) {\n\t\tVXBlood(pos, 5 * count * amf_part_blood.value);\n\t}\n\telse {\n        extern cvar_t gl_part_blood;\n\n\t\tR_RunParticleEffect(pos, vec3_origin, gl_part_blood.value ? 73 : r_sgbloodColor.integer, 20 * count);\n\t}\n}\n\nstatic void CL_Parse_TE_LIGHTNINGBLOOD(vec3_t pos)\n{\n\tif (cls.nqdemoplayback) {\n\t\t// NQ_TE_BEAM - grappling hook beam\n\t\tCL_ParseBeam(4, pos);\n\t}\n\telse {\n\t\textern cvar_t gl_part_blood;\n\n\t\tpos[0] = MSG_ReadCoord();\n\t\tpos[1] = MSG_ReadCoord();\n\t\tpos[2] = MSG_ReadCoord();\n\n\t\tif (CL_Demo_SkipMessage(true)) {\n\t\t\treturn;\n\t\t}\n\n\t\tR_RunParticleEffect(pos, vec3_origin, gl_part_blood.value ? 225 : r_lgbloodColor.integer, 50); // 225 default\n\t}\n}\n\nvoid CL_ParseTEnt (void) \n{\n\tint\ttype;\n\tvec3_t pos;\n\tqbool parsed = false;\n\t\n\tmemset(pos, 0, sizeof(pos));\n\t\n\ttype = MSG_ReadByte();\n\n\t// Handle special cases first (need to parse more than just position).\n\tswitch (type)\n\t{\n\t\t// Lightning bolts.\n\t\tcase TE_LIGHTNING1:\t\t\t\n\t\t\tCL_ParseBeam(1, pos);\n\t\t\tparsed = true;\n\t\t\tbreak;\n\n\t\tcase TE_LIGHTNING2:\n\t\t\tCL_ParseBeam(2, pos);\n\t\t\tparsed = true;\n\t\t\tbreak;\n\n\t\tcase TE_LIGHTNING3:\n\t\t\tCL_ParseBeam(3, pos);\n\t\t\tparsed = true;\n\t\t\tbreak;\n\n\t\t// Bullet hitting wall.\n\t\tcase TE_GUNSHOT:\n\t\t\tCL_Parse_TE_GUNSHOT(pos);\n\t\t\tparsed = true;\n\t\t\tbreak;\n\n\t\t// Bullets hitting body.\n\t\tcase TE_BLOOD: \n\t\t\tCL_Parse_TE_BLOOD(pos);\n\t\t\tparsed = true;\n\t\t\tbreak;\n\n\t\t// Lightning hitting body\n\t\tcase TE_LIGHTNINGBLOOD: \n\t\t\tCL_Parse_TE_LIGHTNINGBLOOD(pos);\n\t\t\tparsed = true;\n\t\t\tbreak;\n\t}\n\n\tif (!parsed)\n\t{\n\t\t// The rest only needs position.\n\t\tpos[0] = MSG_ReadCoord();\n\t\tpos[1] = MSG_ReadCoord();\n\t\tpos[2] = MSG_ReadCoord();\n\n\t\tif (CL_Demo_SkipMessage(true)) {\n\t\t\treturn;\n\t\t}\n\t\t\n\t\tswitch (type) \n\t\t{\n\t\t\t// Spike hitting wall.\n\t\t\tcase TE_WIZSPIKE: \n\t\t\t\tCL_Parse_TE_WIZSPIKE(pos);\n\t\t\t\tbreak;\n\n\t\t\t// Spike hitting wall.\n\t\t\tcase TE_KNIGHTSPIKE: \n\t\t\t\tCL_Parse_TE_WIZSPIKE(pos);\n\t\t\t\tbreak;\n\t\t\t\n\t\t\t// Spike hitting wall.\n\t\t\tcase TE_SPIKE: \n\t\t\t\tCL_Parse_TE_SPIKE(pos);\n\t\t\t\tbreak;\n\n\t\t\t// Super spike hitting wall.\n\t\t\tcase TE_SUPERSPIKE:\t\t\n\t\t\t\tCL_Parse_TE_SUPERSPIKE(pos);\n\t\t\t\tbreak;\n\n\t\t\t// Rocket explosion.\n\t\t\tcase TE_EXPLOSION:\t\n\t\t\t\tCL_Parse_TE_EXPLOSION(pos);\n\t\t\t\tbreak;\n\n\t\t\t// Tarbaby explosion\n\t\t\tcase TE_TAREXPLOSION:\n\t\t\t\tCL_Parse_TE_TAREXPLOSION(pos);\n\t\t\t\tbreak;\n\n\t\t\tcase TE_LAVASPLASH:\t\n\t\t\t\tR_LavaSplash(pos);\n\t\t\t\tbreak;\n\n\t\t\tcase TE_TELEPORT:\n\t\t\t\tCL_Parse_TE_TELEPORT(pos);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tHost_Error(\"CL_ParseTEnt: unknown type %d\", type);\n\t\t}\n\t}\n\telse if (CL_Demo_SkipMessage(true)) {\n\t\treturn;\n\t}\n\n\t// Save the temp entities.\n\tVectorCopy(pos, temp_entities.list[temp_entities.count].pos);\n\ttemp_entities.list[temp_entities.count].time = cls.demoplayback ? cls.demotime : cls.realtime; // FIXME: Use realtime here?\n\ttemp_entities.list[temp_entities.count].type = type;\n\ttemp_entities.count = (temp_entities.count + 1 >= MAX_TEMP_ENTITIES) ? 0 : temp_entities.count + 1;\n}\n\nvoid vectoangles(vec3_t vec, vec3_t ang);\n\n#define MAX_LIGHTNINGBEAMS 10\n\nstatic float fakeshaft_policy (void)\n{\n\tif (cls.demoplayback || cl.spectator) {\n\t\treturn 1;\n\t}\n\telse {\n\t\treturn cl.fakeshaft_policy;\n\t}\n}\n\nstatic void CL_UpdateBeams(void)\n{\n\tint i;\n\tbeam_t *b;\t\n\tvec3_t dist, org;\n\tentity_t ent;\n\tfloat d, yaw, pitch, forward, fakeshaft;\n\textern cvar_t v_viewheight;\n\n\tfloat lg_size = bound(3, amf_lightning_size.value, 30);\n\tint beamstodraw, j, k;\n\tqbool sparks = false;\n\tvec3_t beamstart[MAX_LIGHTNINGBEAMS], beamend[MAX_LIGHTNINGBEAMS];\n\tvec3_t furthest_sparks;\n\tbeamstodraw = bound(1, amf_lightning.value, MAX_LIGHTNINGBEAMS);\t\n\n\tmemset (&ent, 0, sizeof(entity_t));\n\tent.colormap = vid.colormap;\n\n\tfakeshaft = cl.intermission ? 0 : bound(0, cl_fakeshaft.value, fakeshaft_policy());\n\n\t// Update lightning.\n\tfor (i = 0, b = cl_beams; i < MAX_BEAMS; i++, b++) {\n\t\tsparks = false;\n\n\t\tif (!b->model || b->endtime < cl.time) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (b->entity >= 1 && b->entity <= MAX_CLIENTS && cl.intermission) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// if coming from the player, update the start position\n\t\tif (b->entity == cl.viewplayernum + 1) {\n\t\t\tVectorCopy (cl.simorg, b->start);\n\t\t\tb->start[2] += cl.crouch + bound(-7, v_viewheight.value, 4);\n\t\t\tVectorMA(b->start, r_shiftbeam.value, vright, b->start);\n\n\t\t\tif (fakeshaft && (cl_fakeshaft_extra_updates.value || playerbeam_update)) {\n\t\t\t\tvec3_t\tforward, v, org, ang, target_angles, target_origin;\n\t\t\t\tfloat\tdelta;\n\t\t\t\ttrace_t\ttrace;\n\n\t\t\t\tplayerbeam_update = false;\n\n\t\t\t\tif (cl_fakeshaft.value == 2 && !cl.spectator && !cls.nqdemoplayback) {\n\t\t\t\t\t// try to simulate 13 ms ping\n\t\t\t\t\tframe_t *frame = &cl.frames[(cls.netchan.outgoing_sequence - 2) & UPDATE_MASK];\n\t\t\t\t\tVectorCopy(frame->cmd.angles, target_angles);\n\t\t\t\t\tVectorCopy(frame->playerstate[cl.viewplayernum].origin, target_origin);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tVectorCopy(cl.simangles, target_angles);\n\t\t\t\t\tVectorCopy(cl.simorg, target_origin);\n\t\t\t\t}\n\n\t\t\t\tVectorSubtract (playerbeam_end, target_origin, v);\n\t\t\t\tv[2] -= 22;\t// Adjust for view height.\n\t\t\t\tvectoangles (v, ang);\n\n\t\t\t\t// Lerp pitch.\n\t\t\t\tang[0] = -ang[0];\n\t\t\t\tif (ang[0] < -180) {\n\t\t\t\t\tang[0] += 360;\n\t\t\t\t}\n\t\t\t\tang[0] += (target_angles[0] - ang[0]) * fakeshaft;\n\n\t\t\t\t// Lerp yaw.\n\t\t\t\tdelta = target_angles[1] - ang[1];\n\t\t\t\tif (delta > 180) {\n\t\t\t\t\tdelta -= 360;\n\t\t\t\t}\n\t\t\t\tif (delta < -180) {\n\t\t\t\t\tdelta += 360;\n\t\t\t\t}\n\t\t\t\tang[1] += delta * fakeshaft;\n\t\t\t\tang[2] = 0;\n\n\t\t\t\tAngleVectors (ang, forward, NULL, NULL);\n\t\t\t\tVectorScale (forward, 600, forward);\n\t\t\t\tVectorCopy (target_origin, org);\n\t\t\t\torg[2] += 16;\n\t\t\t\tVectorAdd (org, forward, b->end);\n\n\t\t\t\ttrace = PM_TraceLine (org, b->end);\n\t\t\t\tif (trace.fraction < 1) {\n\t\t\t\t\tVectorCopy(trace.endpos, b->end);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Calculate pitch and yaw.\n\t\tVectorSubtract (b->end, b->start, dist);\n\n\t\tif (dist[1] == 0 && dist[0] == 0) {\n\t\t\tyaw = 0;\n\t\t\tpitch = (dist[2] > 0) ? 90 : 270;\n\t\t}\n\t\telse {\n\t\t\tyaw = atan2(dist[1], dist[0]) * 180 / M_PI;\n\t\t\tif (yaw < 0) {\n\t\t\t\tyaw += 360;\n\t\t\t}\n\t\n\t\t\tforward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]);\n\t\t\tpitch = atan2(dist[2], forward) * 180 / M_PI;\n\t\t\tif (pitch < 0) {\n\t\t\t\tpitch += 360;\n\t\t\t}\n\t\t}\n\n\t\t// Add new entities for the lightning.\n\t\tVectorCopy (b->start, org);\n\t\t\n\t\tfor (k = 0; k < beamstodraw; k++) {\n\t\t\tVectorCopy(b->start, beamstart[k]);\n\t\t}\n\t\t\n\t\td = VectorNormalize(dist);\n\t\tVectorScale (dist, 30, dist);\n\n\t\tfor ( ; d > 0; d -= 30) {\n\t\t\tif (!amf_lightning.value || Rulesets_RestrictParticles()) {\n\t\t\t\tVectorCopy (org, ent.origin);\n\t\t\t\tent.model = b->model;\n\t\t\t\tent.angles[0] = pitch;\n\t\t\t\tent.angles[1] = yaw;\n\t\t\t\tent.angles[2] = (cl.racing ? 0 : rand() % 360);\n\t\t\t\tif ((cl.racing && b->entity >= MAX_CLIENTS) || !Rulesets_RestrictParticles()) {\n\t\t\t\t\tent.alpha = r_shaftalpha.value;\n\t\t\t\t}\n\n\t\t\t\tCL_AddEntity (&ent);\n\t\t\t}\n\t\t\telse if (!ISPAUSED) {\n\t\t\t\t//VULT - Some people might like their lightning beams thicker\n\t\t\t\tfor (k = 0; k < beamstodraw; k++) {\n\t\t\t\t\tVectorAdd(org, dist, beamend[k]);\n\t\t\t\t\tif (!cl.racing) {\n\t\t\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\t\t\tbeamend[k][j] += f_rnd(-lg_size, lg_size);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tVX_LightningBeam(beamstart[k], beamend[k]);\n\t\t\t\t\tVectorCopy(beamend[k], beamstart[k]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// The infamous d-light glow has been replaced with a simple corona so it doesn't light up the room anymore.\n\t\t\tif (amf_coronas.value && amf_lightning.value && !ISPAUSED) {\n\t\t\t\tif (b->entity == cl.viewplayernum + 1 && !((cls.demoplayback || cl.spectator) && cl_camera_tpp.integer)) {\n\t\t\t\t\tR_CoronasNew(C_SMALLLIGHTNING, org);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tR_CoronasNew(C_LIGHTNING, org);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tVectorAdd (org, dist, org);\n\n\t\t\t// LIGHTNING SPARKS\n\t\t\tif (amf_lightning_sparks.value && !Rulesets_RestrictParticles() && !ISPAUSED && !gl_no24bit.integer) {\n\t\t\t\ttrace_t\ttrace;\n\t\t\t\ttrace = PM_TraceLine (org, b->end);\n\t\t\t\tif (trace.fraction < 1) {\n\t\t\t\t\tVectorCopy(trace.endpos, furthest_sparks);\n\t\t\t\t\tsparks = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (sparks) {\n\t\t\tSparkGen(furthest_sparks, amf_lightning_color.color, (int)(3 * amf_lightning_sparks.value), amf_lightning_sparks_size.value, 0.25);\n\t\t}\n\t}\n}\n\nstatic void CL_UpdateExplosions(void)\n{\n\tint f;\n\texplosion_t\t*ex, *next, *hnode;\n\tentity_t ent;\n\n\tmemset (&ent, 0, sizeof(entity_t));\n\tent.colormap = vid.colormap;\n\n\thnode = &cl_explosions_headnode;\n\tfor (ex = hnode->next; ex != hnode; ex = next) {\n\t\tnext = ex->next;\n\t\tf = 10 * (cl.time - ex->start);\n\n\t\tif (f >= ex->model->numframes) {\n\t\t\tCL_FreeExplosion (ex);\n\t\t\tcontinue;\n\t\t}\n\n\t\tVectorCopy (ex->origin, ent.origin);\n\t\tent.model = ex->model;\n\t\tent.frame = f;\n\n\t\tCL_AddEntity (&ent);\n\t}\n}\n\nvoid CL_UpdateTEnts (void)\n{\n\tCL_UpdateBeams();\n\tCL_UpdateExplosions();\n}\n\n"
  },
  {
    "path": "src/cl_view.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#include \"quakedef.h\"\n#include \"vx_stuff.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"rulesets.h\"\n#include \"utils.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"mvd_utils.h\"\n#include \"r_matrix.h\"\n\n#ifdef X11_GAMMA_WORKAROUND\n#include \"tr_types.h\"\n#endif\n#include \"r_local.h\"\n#include \"r_renderer.h\"\n#include \"r_brushmodel.h\"\n\n/*\nThe view is allowed to move slightly from its true position for bobbing,\nbut if it exceeds 8 pixels linear distance (spherical, not box), the list of\nentities sent from the server may not include everything in the pvs, especially\nwhen crossing a water boudnary.\n*/\n\nstatic cvar_t cl_bob      = { \"cl_bob\",      \"0\"   };\nstatic cvar_t cl_bobcycle = { \"cl_bobcycle\", \"0.0\" };\nstatic cvar_t cl_bobup    = { \"cl_bobup\",    \"0.0\" };\nstatic cvar_t cl_bobhead  = { \"cl_bobhead\",  \"0\"   };\n\ncvar_t\tcl_rollspeed = {\"cl_rollspeed\", \"200\"};\ncvar_t\tcl_rollangle = {\"cl_rollangle\", \"0\"};\ncvar_t\tcl_rollalpha = {\"cl_rollalpha\", \"20\"};\ncvar_t\tv_kicktime = {\"v_kicktime\", \"0.0\"};\ncvar_t\tv_kickroll = {\"v_kickroll\", \"0.0\"};\ncvar_t\tv_kickpitch = {\"v_kickpitch\", \"0.0\"};\ncvar_t\tv_gunkick = {\"v_gunkick\", \"0\"};\ncvar_t\tv_viewheight = {\"v_viewheight\", \"0\"};\n\ncvar_t\tcl_drawgun = {\"r_drawviewmodel\", \"1\"};\ncvar_t\tcl_drawgun_invisible = {\"r_drawviewmodel_invisible\", \"0\"};\ncvar_t  r_nearclip = {\"r_nearclip\", \"2\", CVAR_RULESET_MAX | CVAR_RULESET_MIN, NULL, R_MINIMUM_NEARCLIP, R_MAXIMUM_FARCLIP, R_MINIMUM_NEARCLIP };\ncvar_t\tr_viewmodelsize = {\"r_viewmodelSize\", \"1\"};\ncvar_t\tr_viewmodeloffset = {\"r_viewmodeloffset\", \"\"};\ncvar_t  r_viewpreselgun = {\"r_viewpreselgun\", \"0\"};\ncvar_t\tr_viewlastfired = {\"r_viewmodellastfired\", \"0\"};\n\nvoid Change_v_idle (cvar_t *var, char *value, qbool *cancel);\ncvar_t\tv_iyaw_cycle = {\"v_iyaw_cycle\", \"2\", 0, Change_v_idle};\ncvar_t\tv_iroll_cycle = {\"v_iroll_cycle\", \"0.5\", 0, Change_v_idle};\ncvar_t\tv_ipitch_cycle = {\"v_ipitch_cycle\", \"1\", 0, Change_v_idle};\ncvar_t\tv_iyaw_level = {\"v_iyaw_level\", \"0.3\", 0, Change_v_idle};\ncvar_t\tv_iroll_level = {\"v_iroll_level\", \"0.1\", 0, Change_v_idle};\ncvar_t\tv_ipitch_level = {\"v_ipitch_level\", \"0.3\", 0, Change_v_idle};\ncvar_t\tv_idlescale = {\"v_idlescale\", \"0\", 0, Change_v_idle};\n\ncvar_t\tcrosshair = {\"crosshair\", \"4\",};\ncvar_t\tcrosshaircolor = {\"crosshaircolor\", \"255 0 0\", CVAR_COLOR};\ncvar_t\tcrosshairsize\t= {\"crosshairsize\", \"1\"};\ncvar_t  cl_crossx = {\"cl_crossx\", \"0\"};\ncvar_t  cl_crossy = {\"cl_crossy\", \"0\"};\n\n// gamma updates are expensive in hw: update at lower fps, otherwise drops are severe\nstatic cvar_t vid_hwgamma_fps = { \"vid_hwgamma_fps\", \"60\" };\n\n// QW262: less flash grenade effect in demos\ncvar_t cl_demoplay_flash       = { \"cl_demoplay_flash\",      \"0.33\" };\n\ncvar_t v_contentblend          = { \"v_contentblend\",         \"0.2\" };\ncvar_t v_damagecshift          = { \"v_damagecshift\",         \"0.2\" };\ncvar_t v_quadcshift            = { \"v_quadcshift\",           \"0.5\" };\ncvar_t v_suitcshift            = { \"v_suitcshift\",           \"0.5\" };\ncvar_t v_ringcshift            = { \"v_ringcshift\",           \"0.5\" };\ncvar_t v_pentcshift            = { \"v_pentcshift\",           \"0.5\" };\ncvar_t v_dlightcshift          = { \"v_dlightcshift\",         \"1\" };\ncvar_t v_dlightcolor           = { \"v_dlightcolor\",          \"1\" };\ncvar_t v_dlightcshiftpercent   = { \"v_dlightcshiftpercent\",  \"0.5\" };\n\ncvar_t v_bonusflash            = { \"cl_bonusflash\",          \"0\" };\n\nfloat\tv_dmg_time, v_dmg_roll, v_dmg_pitch;\n\nframe_t\t\t\t*view_frame;\nplayer_state_t\tview_message;\n\nvoid Change_v_idle (cvar_t *var, char *value, qbool *cancel) {\n\t// Don't allow cheating in TF\n\t*cancel = (cl.teamfortress && cls.state >= ca_connected && cbuf_current != &cbuf_svc);\n}\n\nfloat V_CalcRoll (vec3_t angles, vec3_t velocity) {\n\tvec3_t right;\n\tfloat sign, side;\n\n\tAngleVectors (angles, NULL, right, NULL);\n\tside = DotProduct (velocity, right);\n\tsign = side < 0 ? -1 : 1;\n\tside = fabs(side);\n\n\tside = (side < cl_rollspeed.value) ? side * Ruleset_RollAngle() / cl_rollspeed.value : Ruleset_RollAngle();\n\n\tif (side > 45)\n\t\tside = 45;\n\n\treturn side * sign;\n\t\n}\n\nfloat V_CalcBob (void) {\n\tstatic double bobtime;\n\tstatic float bob;\n\tfloat cycle;\n\t\n\tif (cl.spectator)\n\t\treturn 0;\n\n\tif (!cl.onground)\n\t\treturn bob;\t\t// just use old value\n\n\tif (cl_bobcycle.value <= 0)\t\n\t\treturn 0;\n\n\tbobtime += cls.frametime;\n\tcycle = bobtime - (int) (bobtime / cl_bobcycle.value) * cl_bobcycle.value;\n\tcycle /= cl_bobcycle.value;\n\tif (cycle < cl_bobup.value)\n\t\tcycle = M_PI * cycle / cl_bobup.value;\n\telse\n\t\tcycle = M_PI + M_PI * (cycle - cl_bobup.value) / (1.0 - cl_bobup.value);\n\n\t// bob is proportional to simulated velocity in the xy plane\n\t// (don't count Z, or jumping messes it up)\n\tbob = sqrt(cl.simvel[0] * cl.simvel[0] + cl.simvel[1] * cl.simvel[1]) * cl_bob.value;\n\tbob = bob * 0.3 + bob * 0.7 * sin(cycle);\n\tbob = bound (-7, bob, 4);\n\treturn bob;\t\n}\n\n//=============================================================================\n\ncvar_t\tv_centermove = {\"v_centermove\", \"0.15\"};\ncvar_t\tv_centerspeed = {\"v_centerspeed\",\"500\"};\n\nvoid Force_Centerview_f (void)\n{\n\tif (concussioned)\n\t\treturn;\n\n\tcl.viewangles[PITCH] = 0;\n}\n\nvoid V_StartPitchDrift (void) {\n\tif (cl.laststop == cl.time)\n\t\treturn;\t\t// something else is keeping it from drifting\n\n\tif (cl.nodrift || !cl.pitchvel) {\n\t\tcl.pitchvel = v_centerspeed.value;\n\t\tcl.nodrift = false;\n\t\tcl.driftmove = 0;\n\t}\n}\n\nvoid V_StopPitchDrift (void) {\n\tcl.laststop = cl.time;\n\tcl.nodrift = true;\n\tcl.pitchvel = 0;\n}\n\n/*\nMoves the client pitch angle towards cl.idealpitch sent by the server.\n\nIf the user is adjusting pitch manually, either with lookup/lookdown,\nmlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.\n\nDrifting is enabled when the center view key is hit, mlook is released and\nlookspring is non 0, or when \n*/\nvoid V_DriftPitch (void) {\n\tfloat delta, move;\n\n\tif (!cl.onground || cls.demoplayback ) {\n\t\tcl.driftmove = cl.pitchvel = 0;\n\t\treturn;\n\t}\n\n// don't count small mouse motion\n\tif (cl.nodrift) {\n\t\tif (abs(cl.frames[(cls.netchan.outgoing_sequence-1)&UPDATE_MASK].cmd.forwardmove) < 200)\n\t\t\tcl.driftmove = 0;\n\t\telse\n\t\t\tcl.driftmove += cls.frametime;\n\t\n\t\tif ( cl.driftmove > v_centermove.value)\n\t\t\tV_StartPitchDrift ();\n\n\t\treturn;\n\t}\n\t\n\tdelta = 0 - cl.viewangles[PITCH];\n\n\tif (!delta) {\n\t\tcl.pitchvel = 0;\n\t\treturn;\n\t}\n\n\tmove = cls.frametime * cl.pitchvel;\n\tcl.pitchvel += cls.frametime * v_centerspeed.value;\n\n\tif (delta > 0) {\n\t\tif (move > delta) {\n\t\t\tcl.pitchvel = 0;\n\t\t\tmove = delta;\n\t\t}\n\t\tcl.viewangles[PITCH] += move;\n\t} else if (delta < 0) {\n\t\tif (move > -delta) {\n\t\t\tcl.pitchvel = 0;\n\t\t\tmove = -delta;\n\t\t}\n\t\tcl.viewangles[PITCH] -= move;\n\t}\n}\n\n/*\n============================================================================== \n \t\t\t\t\t\tPALETTE FLASHES \n============================================================================== \n*/\n \ncshift_t\tcshift_empty = { {130,80,50}, 0 };\ncshift_t\tcshift_water = { {130,80,50}, 128 };\ncshift_t\tcshift_slime = { {0,25,5}, 150 };\ncshift_t\tcshift_lava = { {255,80,0}, 150 };\n\ncvar_t\t\tgl_cshiftpercent = {\"gl_cshiftpercent\", \"100\"};\ncvar_t\t\tgl_hwblend = {\"gl_hwblend\", \"0\"};\nfloat\t\tv_blend[4];\t\t// rgba 0.0 - 1.0\ncvar_t\t\tv_gamma = {\"gl_gamma\", \"1.0\"};\ncvar_t\t\tv_contrast = {\"gl_contrast\", \"1.0\"};\n\n#ifdef X11_GAMMA_WORKAROUND\nunsigned short ramps[3][4096];\n#else\nunsigned short\tramps[3][256];\n#endif\n\nvoid V_ParseDamage (void)\n{\n\tint armor, blood, i;\n\tvec3_t from, forward, right;\n\tfloat side, count, fraction;\n\n\tarmor = MSG_ReadByte ();\n\tblood = MSG_ReadByte ();\n\tfor (i = 0; i < 3; i++)\n\t\tfrom[i] = MSG_ReadCoord ();\n\n\tif (cls.mvdplayback && cls.lastto >= 0 && cls.lastto < MAX_CLIENTS) {\n\t\tcl.players[cls.lastto].max_health_last_set = cls.demotime;\n\t}\n\n\tif (CL_Demo_SkipMessage(true))\n\t\treturn;\n\n\tcount = blood * 0.5 + armor * 0.5;\n\tif (count < 10)\n\t\tcount = 10;\n\n\tcl.faceanimtime = cl.time + 0.2;\t\t// put sbar face into pain frame\n\n\tcl.hurtblur = cl.time + count / 24;\t\t// use hurt motion blur.\n\n\tcl.cshifts[CSHIFT_DAMAGE].percent += 3*count;\n\tif (cl.cshifts[CSHIFT_DAMAGE].percent < 0)\n\t\tcl.cshifts[CSHIFT_DAMAGE].percent = 0;\n\tif (cl.cshifts[CSHIFT_DAMAGE].percent > 150)\n\t\tcl.cshifts[CSHIFT_DAMAGE].percent = 150;\n\n\tfraction = bound(0, v_damagecshift.value, 1);\n\tcl.cshifts[CSHIFT_DAMAGE].percent *= fraction;\n\n\tif (armor > blood) {\n\t\tcl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;\n\t\tcl.cshifts[CSHIFT_DAMAGE].destcolor[1] = cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;\n\t} else if (armor) {\n\t\tcl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;\n\t\tcl.cshifts[CSHIFT_DAMAGE].destcolor[1] = cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;\n\t} else {\n\t\tcl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;\n\t\tcl.cshifts[CSHIFT_DAMAGE].destcolor[1] = cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;\n\t}\n\n\t// calculate view angle kicks\n\tVectorSubtract (from, cl.simorg, from);\n\tVectorNormalizeFast (from);\n\n\tAngleVectors (cl.simangles, forward, right, NULL);\n\n\tside = DotProduct (from, right);\n\tv_dmg_roll = count * side * v_kickroll.value;\n\n\tside = DotProduct (from, forward);\n\tv_dmg_pitch = count * side * v_kickpitch.value;\n\n\tv_dmg_time = v_kicktime.value;\n}\n\n// disconnect -->\nqbool flashed = false; // it should be used for f_flashout tirgger\nextern cvar_t v_gamma, v_contrast;\n\nvoid V_TF_FlashSettings(qbool flashed)\n{\n\tstatic float old_gamma, old_contrast;\n\n\t// remove read only flag if it was set\n\tif (Cvar_GetFlags(&v_gamma) & CVAR_ROM) {\n\t\tCvar_SetFlags(&v_gamma, Cvar_GetFlags(&v_gamma) & ~CVAR_ROM);\n\t}\n\tif (Cvar_GetFlags(&v_contrast) & CVAR_ROM) {\n\t\tCvar_SetFlags(&v_contrast, Cvar_GetFlags(&v_contrast) & ~CVAR_ROM);\n\t}\n\n\tif (flashed) {\n\t\t// store normal settings\n\t\told_gamma = v_gamma.value;\n\t\told_contrast = v_contrast.value;\n\n\t\t// set MTFL flash settings\t\n\t\tCvar_SetValue(&v_gamma, MTFL_FLASH_GAMMA);\n\t\tCvar_SetValue(&v_contrast, MTFL_FLASH_CONTRAST);\n\n\t\t// made gamma&contrast read only\n\t\tCvar_SetFlags(&v_gamma, Cvar_GetFlags(&v_gamma) | CVAR_ROM);\n\t\tCvar_SetFlags(&v_contrast, Cvar_GetFlags(&v_contrast) | CVAR_ROM);\n\t}\n\telse {\n\t\t// restore old settings\n\t\tCvar_SetValue(&v_gamma, old_gamma);\n\t\tCvar_SetValue(&v_contrast, old_contrast);\n\t}\n}\n\nvoid V_TF_FlashStuff (void)\n{\n\tstatic float last_own_flash_time;\n\tstatic float last_other_flash_time;\n\tfloat blocktime;\n\n\t// 240 = Normal TF || 255 = Angel TF\n\tif (cshift_empty.percent == 240 || cshift_empty.percent == 255 ) {\n\t\tTP_ExecTrigger (\"f_flash\");\n\t\tif (!flashed && Rulesets_ToggleWhenFlashed()) {\n\t\t\tV_TF_FlashSettings(true);\n\t\t}\n\n\t\tflashed = true;\n\t\tlast_other_flash_time = cls.realtime;\n\t}\n\n\tif (cshift_empty.percent == 160) {\n\t\t// flashed by your own flash\n\t\tif (!flashed && Rulesets_ToggleWhenFlashed()) {\n\t\t\tV_TF_FlashSettings(true);\n\t\t}\n\n\t\tflashed = true;\n\t\tlast_own_flash_time = cls.realtime;\n\t}\n\n\tblocktime = (last_other_flash_time > last_own_flash_time) ? 20.0 : 10.0;\n\n\t{\n\t\tqbool flashed_for_10seconds = (!(cls.realtime - max(last_own_flash_time, last_other_flash_time) < blocktime));\n\t\tqbool death_while_flashed = (cshift_empty.percent == 0 && cbuf_current == &cbuf_svc);\n\n\t\tif (flashed_for_10seconds || death_while_flashed) {\n\t\t\t// turn gamma and contrast back\n\t\t\tif (flashed && Rulesets_ToggleWhenFlashed()) {\n\t\t\t\tV_TF_FlashSettings(false);\n\t\t\t}\n\t\t\tflashed = false;\n\t\t}\n\t}\n\n\tif (cls.demoplayback && cshift_empty.destcolor[0] == cshift_empty.destcolor[1]) {\n\t\tcshift_empty.percent *= bound(0, cl_demoplay_flash.value, 1.0f);\n\t}\n}\n// <-- disconnect\n\nvoid V_cshift_f (void) {\n\t// don't allow cheating in TF\n\tif (cls.state >= ca_connected && cl.teamfortress && cbuf_current != &cbuf_svc)\n\t\treturn;\n\n\tcshift_empty.destcolor[0] = atoi(Cmd_Argv(1));\n\tcshift_empty.destcolor[1] = atoi(Cmd_Argv(2));\n\tcshift_empty.destcolor[2] = atoi(Cmd_Argv(3));\n\tcshift_empty.percent = atoi(Cmd_Argv(4));\n\n\t// TF flash grenades stuff\n\tif (cl.teamfortress)\n\t\tV_TF_FlashStuff ();\n}\n\n//When you run over an item, the server sends this command\nvoid V_BonusFlash_f (void) {\n        static double last_bonusflashtrigger = 0;\n\n\tif (cls.realtime != last_bonusflashtrigger) { // do not trigger twice a frame\n\t\tTP_ExecTrigger (\"f_bonusflash\");\n\t\tlast_bonusflashtrigger = cls.realtime;\n\t}\n\n\tif (!v_bonusflash.value && cbuf_current == &cbuf_svc)\n\t\treturn;\n\n\tcl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;\n\tcl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;\n\tcl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;\n\tcl.cshifts[CSHIFT_BONUS].percent = 50;\n}\n\n//Underwater, lava, etc each has a color shift\nvoid V_SetContentsColor (int contents)\n{\n\textern cvar_t gl_polyblend;\n\n\tif (!v_contentblend.value) {\n\t\tcl.cshifts[CSHIFT_CONTENTS] = cshift_empty;\n\t\tcl.cshifts[CSHIFT_CONTENTS].percent *= 100;\n\t\treturn;\n\t}\n\n\tswitch (contents) {\n\t\tcase CONTENTS_EMPTY:\n\t\t\tcl.cshifts[CSHIFT_CONTENTS] = cshift_empty;\n\t\t\tbreak;\n\t\tcase CONTENTS_LAVA:\n\t\t\tcl.cshifts[CSHIFT_CONTENTS] = cshift_lava;\n\t\t\tbreak;\n\t\tcase CONTENTS_SOLID:\n\t\tcase CONTENTS_SLIME:\n\t\t\tcl.cshifts[CSHIFT_CONTENTS] = cshift_slime;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tcl.cshifts[CSHIFT_CONTENTS] = cshift_water;\n\t\t\tbreak;\n\t}\n\n\tif (v_contentblend.value > 0 && v_contentblend.value < 1 && contents != CONTENTS_EMPTY) {\n\t\tcl.cshifts[CSHIFT_CONTENTS].percent *= v_contentblend.value;\n\t}\n\n\tif (!gl_polyblend.value && !cl.teamfortress) {\n\t\tcl.cshifts[CSHIFT_CONTENTS].percent = 0;\n\t}\n\telse {\n\t\t// ignore gl_cshiftpercent on custom cshifts (set with v_cshift\n\t\t// command) to avoid cheating in TF\n\t\tif (contents != CONTENTS_EMPTY) {\n\t\t\tif (!gl_polyblend.value) {\n\t\t\t\tcl.cshifts[CSHIFT_CONTENTS].percent = 0;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcl.cshifts[CSHIFT_CONTENTS].percent *= gl_cshiftpercent.value;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tcl.cshifts[CSHIFT_CONTENTS].percent *= 100;\n\t\t}\n\t}\n}\n\nvoid V_CalcPowerupCshift(void)\n{\n\tfloat fraction;\n\n\tif (cl.stats[STAT_ITEMS] & IT_QUAD)\t{\n\t\tcl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;\n\t\tcl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;\n\t\tcl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;\n\t\tfraction = bound (0, v_quadcshift.value, 1);\n\t\tcl.cshifts[CSHIFT_POWERUP].percent = 30 * fraction;\n\t} else if (cl.stats[STAT_ITEMS] & IT_SUIT) {\n\t\tcl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;\n\t\tcl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;\n\t\tcl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;\n\t\tfraction = bound (0, v_suitcshift.value, 1);\n\t\tcl.cshifts[CSHIFT_POWERUP].percent = 20 * fraction;\n\t} else if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) {\n\t\tcl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;\n\t\tcl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;\n\t\tcl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;\n\t\tfraction = bound (0, v_ringcshift.value, 1);\n\t\tcl.cshifts[CSHIFT_POWERUP].percent = 100 * fraction;\n\t} else if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) {\n\t\tcl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;\n\t\tcl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;\n\t\tcl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;\n\t\tfraction = bound (0, v_pentcshift.value, 1);\n\t\tcl.cshifts[CSHIFT_POWERUP].percent = 30 * fraction;\n\t} else {\n\t\tcl.cshifts[CSHIFT_POWERUP].percent = 0;\n\t}\n}\n\nvoid V_CalcBlend (void)\n{\n\tfloat r, g, b, a, a2, t;\n\tint j;\n\textern cvar_t gl_polyblend;\n\n\tr = g = b = a = 0;\n\n\tif (cls.state != ca_active) {\n\t\tcl.cshifts[CSHIFT_CONTENTS] = cshift_empty;\n\t\tcl.cshifts[CSHIFT_POWERUP].percent = 0;\n\t}\n\telse {\n\t\tV_CalcPowerupCshift ();\n\t}\n\n\t// drop the damage value\n\tt = cls.frametime * 150;\n\tcl.cshifts[CSHIFT_DAMAGE].percent -= t;\n\tif (cl.cshifts[CSHIFT_DAMAGE].percent <= 0) {\n\t\tcl.cshifts[CSHIFT_DAMAGE].percent = 0;\n\t}\n\n\t// drop the bonus value\n\tcl.cshifts[CSHIFT_BONUS].percent -= cls.frametime * 100;\n\tif (cl.cshifts[CSHIFT_BONUS].percent <= 0) {\n\t\tcl.cshifts[CSHIFT_BONUS].percent = 0;\n\t}\n\n\tfor (j = 0; j < NUM_CSHIFTS; j++)\t{\n\t\tif ((!gl_cshiftpercent.value || !gl_polyblend.value) && j != CSHIFT_CONTENTS) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (j == CSHIFT_CONTENTS) {\n\t\t\ta2 = cl.cshifts[j].percent / 100.0 / 255.0;\n\t\t}\n\t\telse {\n\t\t\ta2 = ((cl.cshifts[j].percent * gl_cshiftpercent.value) / 100.0) / 255.0;\n\t\t}\n\n\t\tif (!a2) {\n\t\t\tcontinue;\n\t\t}\n\t\ta = a + a2 * (1-a);\n\n\t\ta2 = a2 / a;\n\t\tr = r * (1 - a2) + cl.cshifts[j].destcolor[0] * a2;\n\t\tg = g * (1 - a2) + cl.cshifts[j].destcolor[1] * a2;\n\t\tb = b * (1 - a2) + cl.cshifts[j].destcolor[2 ]* a2;\n\t}\n\n\tv_blend[0] = r / 255.0;\n\tv_blend[1] = g / 255.0;\n\tv_blend[2] = b / 255.0;\n\tv_blend[3] = a;\n\tv_blend[3] = bound(0, v_blend[3], 1);\n}\n\nvoid V_AddLightBlend(float r, float g, float b, float a2, qbool suppress_polyblend)\n{\n\tfloat a;\n\textern cvar_t gl_polyblend;\n\tqbool shift_on_dlight = gl_polyblend.integer && (v_dlightcshift.integer == 1 || (v_dlightcshift.integer == 2 && !suppress_polyblend));\n\tfloat percentage = bound(0, v_dlightcshiftpercent.value, 1);\n\n\tif (percentage <= 0 || !shift_on_dlight) {\n\t\treturn;\n\t}\n\n\tif (!v_dlightcolor.integer) {\n\t\tr = 1.0f;\n\t\tg = 0.5f;\n\t\tb = 0.0f;\n\t}\n\telse {\n\t\t// some kind of scaling, the normal colors aren't full red/blue etc\n\t\tfloat max = max(r, max(g, b));\n\n\t\tif (max > 0) {\n\t\t\tr /= max;\n\t\t\tg /= max;\n\t\t\tb /= max;\n\t\t}\n\t}\n\n\ta2 = bound(0, a2, 1);\n\ta2 *= percentage;\n\n\tv_blend[3] = a = v_blend[3] + a2 * (1 - v_blend[3]);\n\n\tif (!a) {\n\t\treturn;\n\t}\n\n\ta2 = a2 / a;\n\n\tv_blend[0] = v_blend[0] * (1 - a2) + r * a2;\n\tv_blend[1] = v_blend[1] * (1 - a2) + g * a2;\n\tv_blend[2] = v_blend[2] * (1 - a2) + b * a2;\n}\n\nvoid V_UpdatePalette (void)\n{\n\tint i, j, c;\n\tfloat current_gamma, current_contrast, a, rgb[3];\n\tstatic float prev_blend[4] = { 0, 0, 0, 0 };\n\tstatic float old_gamma = 1, old_contrast = 1;\n\tstatic qbool old_change_palette = false;\n\tstatic double last_set = 0;\n\textern float vid_gamma;\n\tfloat hw_vblend[4];\n\t// whether or not to include content/damage palette shifts as part of the hw gamma\n\tqbool change_palette = (vid_hwgamma_enabled && gl_hwblend.value && !cl.teamfortress);\n\tint change_flags = 0;\n\n\tif (change_palette) {\n\t\tmemcpy(hw_vblend, v_blend, sizeof(hw_vblend));\n\t}\n\telse {\n\t\tmemset(hw_vblend, 0, sizeof(hw_vblend));\n\t}\n\tcurrent_gamma = (vid_hwgamma_enabled ? bound(0.3, v_gamma.value, 3) : 1);\n\tcurrent_contrast = (vid_hwgamma_enabled ? bound(1, v_contrast.value, 3) : 1);\n\n\tchange_flags |= (memcmp(hw_vblend, prev_blend, sizeof(hw_vblend)) ? 1 : 0);\n\tchange_flags |= (current_gamma != old_gamma || v_gamma.modified ? 2 : 0);\n\tchange_flags |= (current_contrast != old_contrast ? 4 : 0);\n\tchange_flags |= (change_palette != old_change_palette ? 8 : 0);\n\n\tif (!change_flags) {\n\t\treturn;\n\t}\n\n\t// don't update if not enough time has passed & only palette updating\n\tif (change_flags == 1 && vid_hwgamma_fps.integer && curtime < last_set + (1.0f / max(10, vid_hwgamma_fps.integer))) {\n\t\treturn;\n\t}\n\n\t// store flags\n\told_change_palette = change_palette;\n\tprev_blend[0] = hw_vblend[0];\n\tprev_blend[1] = hw_vblend[1];\n\tprev_blend[2] = hw_vblend[2];\n\tprev_blend[3] = hw_vblend[3];\n\told_gamma = current_gamma;\n\told_contrast = current_contrast;\n\tlast_set = curtime;\n\tv_gamma.modified = false;\n\tif (developer.integer == 3) {\n\t\tCon_DPrintf(\"palette: change_flags %d [%d %d %d %d] %.2f %.2f\\n\", change_flags, (int)(hw_vblend[0] * 255), (int)(hw_vblend[1] * 255), (int)(hw_vblend[2] * 255), (int)(hw_vblend[3] * 255), current_gamma, current_contrast);\n\t}\n\n\ta = hw_vblend[3];\n\tif (R_OldGammaBehaviour() && vid_gamma != 1.0) {\n\t\tcurrent_contrast = pow(current_contrast, vid_gamma);\n\t\tcurrent_gamma = current_gamma / vid_gamma;\n\t}\n\n\t// Have to do this in a loop these days as certain color ranges will be blocked by OS\n\tdo {\n\t\tfloat std_alpha;\n\n\t\trgb[0] = 255 * hw_vblend[0] * a;\n\t\trgb[1] = 255 * hw_vblend[1] * a;\n\t\trgb[2] = 255 * hw_vblend[2] * a;\n\n\t\tstd_alpha = 1 - a;\n\n#ifdef X11_GAMMA_WORKAROUND\n\t\tstd_alpha *= 256.0 / glConfig.gammacrap.size;\n\t\tfor (i = 0; i < glConfig.gammacrap.size; i++) {\n#else\n\t\tfor (i = 0; i < 256; i++) {\n#endif\n\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t// apply blend and contrast\n\t\t\t\tc = (i * std_alpha + rgb[j]) * current_contrast;\n\t\t\t\tif (c > 255) {\n\t\t\t\t\tc = 255;\n\t\t\t\t}\n\t\t\t\t// apply gamma\n\t\t\t\tc = 255 * pow(c / 255.5, current_gamma) + 0.5;\n\t\t\t\tc = bound(0, c, 255);\n\t\t\t\tramps[j][i] = c << 8;\n\t\t\t}\n\t\t}\n\n\t\ta *= 0.8;\n\t} while (VID_SetDeviceGammaRamp((unsigned short *)ramps) && a > 0.1);\n}\n\n// BorisU -->\nvoid V_TF_ClearGrenadeEffects (void)\n{\n\tcbuf_t *cbuf_tmp;\n\textern cvar_t scr_fov, default_fov;\n\t\n\tcbuf_tmp = cbuf_current;\n\tcbuf_current = &cbuf_svc;\n\t// Concussion effect off\n\tconcussioned = false;\n\tCvar_SetValue (&scr_fov, default_fov.value);\n\tCvar_SetValue (&v_idlescale, 0.0f);\n\n\t// Flash effect off\n\tif (flashed && Rulesets_ToggleWhenFlashed()) {\n\t\tV_TF_FlashSettings (false);\n\t}\n\tflashed = false;\n\n\tcshift_empty.destcolor[0] = 0;\n\tcshift_empty.destcolor[1] = 0;\n\tcshift_empty.destcolor[2] = 0;\n\tcshift_empty.percent = 0;\n\tcbuf_current = cbuf_tmp;\n}\n// <-- BorisU\n/* \n============================================================================== \n\t\t\t\t\t\t         VIEW RENDERING \n============================================================================== \n*/\n\nfloat angledelta (float a) {\n\ta = anglemod(a);\n\tif (a > 180)\n\t\ta -= 360;\n\treturn a;\n}\n\nvoid V_BoundOffsets (void) {\n\t// absolutely bound refresh relative to entity clipping hull\n\t// so the view can never be inside a solid wall\n\n\tif (r_refdef.vieworg[0] < cl.simorg[0] - 14)\n\t\tr_refdef.vieworg[0] = cl.simorg[0] - 14;\n\telse if (r_refdef.vieworg[0] > cl.simorg[0] + 14)\n\t\tr_refdef.vieworg[0] = cl.simorg[0] + 14;\n\n\tif (r_refdef.vieworg[1] < cl.simorg[1] - 14)\n\t\tr_refdef.vieworg[1] = cl.simorg[1] - 14;\n\telse if (r_refdef.vieworg[1] > cl.simorg[1] + 14)\n\t\tr_refdef.vieworg[1] = cl.simorg[1] + 14;\n\n\tif (r_refdef.vieworg[2] < cl.simorg[2] - 22)\n\t\tr_refdef.vieworg[2] = cl.simorg[2] - 22;\n\telse if (r_refdef.vieworg[2] > cl.simorg[2] + 30)\n\t\tr_refdef.vieworg[2] = cl.simorg[2] + 30;\n}\n\n//Idle swaying\nvoid V_AddIdle (void) {\n\tr_refdef.viewangles[ROLL] += v_idlescale.value * sin(cl.time * v_iroll_cycle.value) * v_iroll_level.value;\n\tr_refdef.viewangles[PITCH] += v_idlescale.value * sin(cl.time * v_ipitch_cycle.value) * v_ipitch_level.value;\n\tr_refdef.viewangles[YAW] += v_idlescale.value * sin(cl.time * v_iyaw_cycle.value) * v_iyaw_level.value;\n}\n\n//Roll is induced by movement and damage\nvoid V_CalcViewRoll (void) {\n\tfloat side, adjspeed;\n\n\tside = V_CalcRoll (cl.simangles, cl.simvel);\n\tadjspeed = cl_rollalpha.value * bound (2, Ruleset_RollAngle(), 45);\n\tif (side > cl.rollangle) {\n\t\tcl.rollangle += cls.frametime * adjspeed;\n\t\tif (cl.rollangle > side)\n\t\t\tcl.rollangle = side;\n\t} else if (side < cl.rollangle) {\n\t\tcl.rollangle -= cls.frametime * adjspeed;\n\t\tif (cl.rollangle < side)\n\t\t\tcl.rollangle = side;\n\t}\n\tr_refdef.viewangles[ROLL] += cl.rollangle;\n\n\tif (v_dmg_time > 0) {\n\t\tr_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;\n\t\tr_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;\n\t\tv_dmg_time -= cls.frametime;\n\t}\n}\n\n// tells the model number of the current carried/selected/preselected weapon\n// if user wish so, weapon pre-selection is also taken in account\n// todo: if user selects different weapon while the current one is still\n// firing, wait until the animation is finished\nstatic int V_CurrentWeaponModel(void)\n{\n\textern cvar_t cl_weaponpreselect;\n\tint bestgun;\n\tint realw = cl.stats[STAT_WEAPON];\n\n\tif (cls.demoplayback && r_viewlastfired.integer) {\n\t\tif (realw == 0) {\n\t\t\tcl.lastfired = realw;\n\t\t\tcl.lastviewplayernum = cl.viewplayernum;\n\t\t\treturn realw;\n\t\t}\n\t\tif (view_message.weaponframe) {\n\t\t\tcl.lastfired = realw;\n\t\t\tcl.lastviewplayernum = cl.viewplayernum;\n\t\t\treturn realw;\n\t\t}\n\t\telse if (cl.lastfired) {\n\t\t\tif (cl.lastviewplayernum == cl.viewplayernum) {\n\t\t\t\treturn cl.lastfired;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcl.lastfired = realw;\n\t\t\t\tcl.lastviewplayernum = cl.viewplayernum;\n\t\t\t\treturn realw;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\treturn realw;\n\t\t}\n\t}\n\telse {\n\t\tif (ShowPreselectedWeap() && r_viewpreselgun.integer && !view_message.weaponframe) {\n\t\t\tbestgun = IN_BestWeaponReal(true);\n\t\t\tif (bestgun == 1) {\n\t\t\t\treturn cl_modelindices[mi_vaxe];\n\t\t\t}\n\t\t\tif (bestgun > 1 && bestgun <= 8) {\n\t\t\t\treturn cl_modelindices[mi_weapon1 - 1 + bestgun];\n\t\t\t}\n\t\t}\n\t\treturn cl.stats[STAT_WEAPON];\n\t}\n}\n\nextern void TP_ParseWeaponModel(model_t *model);\n\nstatic void V_AddViewWeapon(float bob)\n{\n\tvec3_t forward, right, up;\n\tcentity_t *cent;\n\tint gunmodel = V_CurrentWeaponModel();\n\textern cvar_t scr_fov, scr_fovmode, scr_newHud;\n\n\tcent = CL_WeaponModelForView();\n\tTP_ParseWeaponModel(cl.model_precache[gunmodel]);\n\n\tif (!cl_drawgun.value\n\t\t|| (cl_drawgun.value == 2 && scr_fov.value > 90)\n\t\t|| ((view_message.flags & (PF_GIB | PF_DEAD)))\n\t\t|| (!cl_drawgun_invisible.value && cl.stats[STAT_ITEMS] & IT_INVISIBILITY)\n\t\t|| cl.stats[STAT_HEALTH] <= 0\n\t\t|| !Cam_DrawViewModel()) {\n\t\tcent->current.modelindex = 0;\t//no model\n\t\treturn;\n\t}\n\n\t//angles\n\tcent->current.angles[YAW] = r_refdef.viewangles[YAW];\n\tcent->current.angles[PITCH] = -r_refdef.viewangles[PITCH];\n\tcent->current.angles[ROLL] = r_refdef.viewangles[ROLL];\n\t//origin\n\n\tAngleVectors(r_refdef.viewangles, forward, right, up);\n\tVectorCopy(r_refdef.vieworg, cent->current.origin);\n\n\tVectorMA(cent->current.origin, bob * 0.4, forward, cent->current.origin);\n\n\tif (r_viewmodeloffset.string[0]) {\n\t\tfloat offset[3];\n\t\tint size = sizeof(offset) / sizeof(offset[0]);\n\n\t\tParseFloats(r_viewmodeloffset.string, offset, &size);\n\t\tVectorMA(cent->current.origin, offset[0], right, cent->current.origin);\n\t\tVectorMA(cent->current.origin, -offset[1], up, cent->current.origin);\n\t\tVectorMA(cent->current.origin, offset[2], forward, cent->current.origin);\n\t}\n\n\t// fudge position around to keep amount of weapon visible roughly equal with different FOV\n\tif (!scr_fovmode.value) {\n\t\tif (cl_sbar.value && scr_newHud.value == 0 && scr_viewsize.value == 110)\n\t\t\tcent->current.origin[2] += 1;\n\t\telse if (cl_sbar.value && scr_newHud.value == 0 && scr_viewsize.value == 100)\n\t\t\tcent->current.origin[2] += 2;\n\t\telse if (scr_viewsize.value == 90)\n\t\t\tcent->current.origin[2] += 1;\n\t\telse if (scr_viewsize.value == 80)\n\t\t\tcent->current.origin[2] += 0.5;\n\t}\n\n\tif (cent->current.modelindex != gunmodel) {\n\t\tcent->frametime = -1;\n\t}\n\telse {\n\t\tif (cent->current.frame != view_message.weaponframe) {\n\t\t\tcent->frametime = cl.time;\n\t\t\tcent->oldframe = cent->current.frame;\n\t\t}\n\t}\n\n\tcent->current.modelindex = gunmodel;\n\tcent->current.frame = view_message.weaponframe;\n}\n\nstatic void V_CalcIntermissionRefdef(void)\n{\n\tfloat old;\n\n\tVectorCopy (cl.simorg, r_refdef.vieworg);\n\tVectorCopy (cl.simangles, r_refdef.viewangles);\n\n\t// we don't draw weapon in intermission\n\tCL_WeaponModelForView()->current.modelindex = 0;\n\n\t// always idle in intermission\n\told = v_idlescale.value;\n\tv_idlescale.value = 1;\n\tV_AddIdle ();\n\tv_idlescale.value = old;\n}\n\nstatic void V_CalcRefdef(void)\n{\n\tvec3_t forward;\n\tfloat bob;\n\tfloat height_adjustment;\n\n\tV_DriftPitch();\n\n\tbob = V_CalcBob();\n\n\theight_adjustment = v_viewheight.value ? bound(-7, v_viewheight.value, 4) : V_CalcBob();\n\tif (cl_bobhead.integer) {\n\t\theight_adjustment += bob;\n\t\tbob = 0;\n\t}\n\n\t// set up the refresh position\n\tVectorCopy(cl.simorg, r_refdef.vieworg);\n\n\t// never let it sit exactly on a node line, because a water plane can\n\t// dissapear when viewed with the eye exactly on it.\n\t// the server protocol only specifies to 1/8 pixel, so add 1/16 in each axis\n\tr_refdef.vieworg[0] += 1.0 / 16;\n\tr_refdef.vieworg[1] += 1.0 / 16;\n\tr_refdef.vieworg[2] += 1.0 / 16;\n\n\t// add view height\n\tr_refdef.viewheight_test = 4;\n\tif (view_message.flags & PF_GIB) {\n\t\tr_refdef.vieworg[2] += 8;\t// gib view height\n\t}\n\telse if (view_message.flags & PF_DEAD && (cl.stats[STAT_HEALTH] <= 0)) {\n\t\tr_refdef.vieworg[2] -= 16;\t// corpse view height\n\t}\n\telse {\n\t\t// normal view height\n\t\t// Use STAT_VIEWHEIGHT in case of server support it or NQ demoplayback, if not then use default viewheight.\n\t\tr_refdef.vieworg[2] += ((cl.z_ext & Z_EXT_VIEWHEIGHT) || cls.nqdemoplayback) ? cl.stats[STAT_VIEWHEIGHT] : DEFAULT_VIEWHEIGHT;\n\n\t\tr_refdef.vieworg[2] += height_adjustment;\n\n\t\t// smooth out stair step ups\n\t\tr_refdef.vieworg[2] += cl.crouch;\n\n\t\t// standard offset\n\t\tr_refdef.viewheight_test = 10;\n\t}\n\n\t// set up refresh view angles\n\tVectorCopy(cl.simangles, r_refdef.viewangles);\n\tV_CalcViewRoll();\n\tV_AddIdle();\n\n\tif (v_gunkick.value) {\n\t\t// add weapon kick offset\n\t\tAngleVectors(r_refdef.viewangles, forward, NULL, NULL);\n\t\tVectorMA(r_refdef.vieworg, cl.punchangle, forward, r_refdef.vieworg);\n\n\t\t// add weapon kick angle\n\t\tr_refdef.viewangles[PITCH] += cl.punchangle * 0.5;\n\t}\n\n\tif (view_message.flags & PF_DEAD && (cl.stats[STAT_HEALTH] <= 0)) {\n\t\tr_refdef.viewangles[ROLL] = 80;\t// dead view angle\n\t}\n\n\t//VULT CAMERAS\n\tCameraUpdate(view_message.flags & PF_DEAD);\n\n\t// meag: really viewheight shouldn't be here, but it was incorrectly passed for years instead of bob,\n\t//       and so without it the gun is rendered too far forward if e.g. viewheight -6\n\tV_AddViewWeapon(bob + height_adjustment);\n}\n\nvoid DropPunchAngle (void) {\n\tif (cl.ideal_punchangle < cl.punchangle) {\n\t\tif (cl.ideal_punchangle == -2)\t// small kick\n\t\t\tcl.punchangle -= 20 * cls.frametime;\n\t\telse\t\t\t\t\t\t\t// big kick\n\t\t\tcl.punchangle -= 40 * cls.frametime;\n\n\t\tif (cl.punchangle <= cl.ideal_punchangle) {\n\t\t\tcl.punchangle = cl.ideal_punchangle;\n\t\t\tcl.ideal_punchangle = 0;\n\t\t}\n\t}else {\n\t\tcl.punchangle += 20 * cls.frametime;\n\t\tif (cl.punchangle > 0)\n\t\t\tcl.punchangle = 0;\n\t}\n}\n\n//The player's clipping box goes from (-16 -16 -24) to (16 16 32) from\n//the entity origin, so any view position inside that will be valid\nextern vrect_t scr_vrect;\n\nqbool V_PreRenderView(void)\n{\n\tchar *p;\n\n\tcl.simangles[ROLL] = 0;\t// FIXME @@@ \n\n\tif (cls.state != ca_active) {\n\t\tV_CalcBlend();\n\t}\n\telse {\n\t\tview_frame = &cl.frames[cl.validsequence & UPDATE_MASK];\n\t\tif (!cls.nqdemoplayback) {\n\t\t\tview_message = view_frame->playerstate[cl.viewplayernum];\n\t\t}\n\n\t\tDropPunchAngle();\n\t\tif (cl.intermission) {\n\t\t\t// intermission / finale rendering\n\t\t\tV_CalcIntermissionRefdef();\n\t\t}\n\t\telse {\n\t\t\tV_CalcRefdef();\n\t\t}\n\n\t\tMVD_PowerupCam_Frame();\n\n\t\tR_PushDlights();\n\n\t\tr_refdef2.time = cl.time;\n\t\tr_refdef2.sin_time = sin(r_refdef2.time);\n\t\tr_refdef2.cos_time = cos(r_refdef2.time);\n\n\t\t// scroll parameters for powerup shells\n\t\tr_refdef2.powerup_scroll_params[0] = cos(cl.time * 1.5);\n\t\tr_refdef2.powerup_scroll_params[1] = sin(cl.time * 1.1);\n\t\tr_refdef2.powerup_scroll_params[2] = cos(cl.time * -0.5);\n\t\tr_refdef2.powerup_scroll_params[3] = sin(cl.time * -0.5);\n\n\t\t// restrictions\n\t\tr_refdef2.allow_cheats = cls.demoplayback || (Info_ValueForKey(cl.serverinfo, \"*cheats\")[0] && com_serveractive);\n\t\tif (cls.demoplayback || cl.spectator) {\n\t\t\tr_refdef2.allow_lumas = true;\n\t\t\tr_refdef2.max_fbskins = 1;\n\t\t}\n\t\telse {\n\t\t\tr_refdef2.allow_lumas = !strcmp(Info_ValueForKey(cl.serverinfo, \"24bit_fbs\"), \"0\") ? false : true;\n\t\t\tr_refdef2.max_fbskins = *(p = Info_ValueForKey(cl.serverinfo, \"fbskins\")) ? bound(0, Q_atof(p), 1) : (cl.teamfortress ? 0 : 1);\n\t\t}\n\n\t\t// Only allow alpha water if the server allows it, or they are spectator and have novis enabled\n\t\t{\n\t\t\textern cvar_t r_novis;\n\n\t\t\tr_refdef2.max_watervis = *(p = Info_ValueForKey(cl.serverinfo, \"watervis\")) ? bound(0, Q_atof(p), 1) : 0;\n\t\t\tif ((cls.demoplayback || cl.spectator) && (r_novis.integer || r_refdef2.max_watervis > 0)) {\n\t\t\t\t// ignore server limit\n\t\t\t\tr_refdef2.max_watervis = 1;\n\t\t\t}\n\t\t\tr_refdef2.wateralpha = R_WaterAlpha();  // relies on r_refdef2.max_watervis\n\t\t}\n\n\t\t// time-savers\n\t\t{\n\t\t\textern cvar_t r_drawflat_mode, r_drawflat, r_fastturb, gl_caustics;\n\t\t\textern texture_ref underwatertexture;\n\n\t\t\tr_refdef2.drawFlatFloors = r_drawflat_mode.integer == 0 && (r_drawflat.integer == 2 || r_drawflat.integer == 1);\n\t\t\tr_refdef2.drawFlatWalls = r_drawflat_mode.integer == 0 && (r_drawflat.integer == 3 || r_drawflat.integer == 1);\n\t\t\tr_refdef2.solidTexTurb = (!r_fastturb.integer && r_refdef2.wateralpha == 1);\n\n\t\t\tr_refdef2.drawCaustics = (R_TextureReferenceIsValid(underwatertexture) && gl_caustics.integer);\n\t\t\tr_refdef2.drawWorldOutlines = R_DrawWorldOutlines();\n\t\t\tr_refdef2.distanceScale = tan(r_refdef.fov_x * (M_PI / 180) * 0.5f);\n\t\t\tVectorScale(vpn, 0.002 * r_refdef2.distanceScale, r_refdef2.outline_vpn);\n\t\t\tr_refdef2.outlineBase = 1 - DotProduct(r_origin, r_refdef2.outline_vpn);\n\t\t}\n\t}\n\n\trenderer.PreRenderView();\n\n\treturn cls.state == ca_active;\n}\n\n//============================================================================\n\nvoid V_Init (void) {\n\tCmd_AddCommand (\"v_cshift\", V_cshift_f);\t\n\tCmd_AddCommand (\"bf\", V_BonusFlash_f);\n\tCmd_AddCommand (\"centerview\", V_StartPitchDrift);\n\tCmd_AddCommand (\"force_centerview\", Force_Centerview_f);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_VIEW);\n\tCvar_Register (&v_centermove);\n\tCvar_Register (&v_centerspeed);\n\tCvar_Register (&cl_rollspeed);\n\tCvar_Register (&cl_rollangle);\n\tCvar_Register (&cl_rollalpha);\n\n\tCvar_Register (&v_idlescale);\n\tCvar_Register (&v_iyaw_cycle);\n\tCvar_Register (&v_iroll_cycle);\n\tCvar_Register (&v_ipitch_cycle);\n\tCvar_Register (&v_iyaw_level);\n\tCvar_Register (&v_iroll_level);\n\tCvar_Register (&v_ipitch_level);\n\tCvar_Register (&r_nearclip);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_CROSSHAIR);\n\tCvar_Register(&crosshaircolor);\n\tCvar_Register(&crosshair);\n\tCvar_Register(&crosshairsize);\n\tCvar_Register(&cl_crossx);\n\tCvar_Register(&cl_crossy);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_VIEWMODEL);\n\tCvar_Register(&cl_bob);\n\tCvar_Register(&cl_bobcycle);\n\tCvar_Register(&cl_bobup);\n\tCvar_Register(&cl_bobhead);\n\n\tCvar_Register(&cl_drawgun);\n\tCvar_Register(&cl_drawgun_invisible);\n\tCvar_Register(&r_viewmodelsize);\n\tCvar_Register(&r_viewmodeloffset);\n\tCvar_Register(&r_viewpreselgun);\n\tCvar_Register(&r_viewlastfired);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_VIEW);\n\tCvar_Register (&v_kicktime);\n\tCvar_Register (&v_kickroll);\n\tCvar_Register (&v_kickpitch);\n\tCvar_Register (&v_gunkick);\n\n\n\tCvar_Register (&v_viewheight);\n\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_BLEND);\n\n\tCvar_Register (&v_bonusflash);\n\tCvar_Register (&v_contentblend);\n\tCvar_Register (&v_damagecshift);\n\tCvar_Register (&v_quadcshift);\n\tCvar_Register (&v_suitcshift);\n\tCvar_Register (&v_ringcshift);\n\tCvar_Register (&v_pentcshift);\n\tCvar_Register (&cl_demoplay_flash); // from QW262\n\n\tCvar_Register(&v_dlightcshift);\n\tCvar_Register(&v_dlightcolor);\n\tCvar_Register(&v_dlightcshiftpercent);\n\tCvar_Register(&gl_cshiftpercent);\n\tCvar_Register(&gl_hwblend);\n\tCvar_Register(&vid_hwgamma_fps);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\tCvar_Register(&v_gamma);\n\tCvar_Register(&v_contrast);\n\n\t// we do not need this after host initialized\n\tif (!host_initialized) {\n\t\tint i;\n\t\tfloat def_gamma = 1.0f;\n\t\textern float vid_gamma;\n\n\t\tif ((i = COM_CheckParm(cmdline_param_client_gamma)) != 0 && i + 1 < COM_Argc()) {\n\t\t\tdef_gamma = Q_atof(COM_Argv(i + 1));\n\t\t\tdef_gamma = bound(0.3, def_gamma, 3);\n\t\t\tCvar_SetDefaultAndValue(&v_gamma, def_gamma, def_gamma);\n\t\t\tvid_gamma = def_gamma;\n\t\t}\n\t\telse {\n\t\t\tvid_gamma = 1.0;\n\t\t}\n\n\t\tv_gamma.modified = true;\n\t}\n\tCvar_ResetCurrentGroup();\n}\n"
  },
  {
    "path": "src/cl_view.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\nextern float v_blend[4];\nvoid V_AddLightBlend (float r, float g, float b, float a2, qbool suppress_polyblend);\n\nextern cvar_t v_gamma;\nextern cvar_t v_contrast;\n\nvoid V_Init (void);\nvoid V_UpdatePalette (void);\nvoid V_ParseDamage (void);\nvoid V_SetContentsColor (int contents);\nvoid V_CalcBlend (void);\n\nfloat V_CalcRoll (vec3_t angles, vec3_t velocity);\n\nvoid V_StartPitchDrift (void);\nvoid V_StopPitchDrift (void);\n\nvoid V_TF_ClearGrenadeEffects (void); // BorisU\n"
  },
  {
    "path": "src/client.h",
    "content": "/**\n\t\\file\n\n\t\\brief\n\tMain client structures\n**/\n\n/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: client.h,v 1.74 2007-10-12 00:08:42 cokeman1982 Exp $\n\n*/\n// client.h\n\n#ifndef EZQUAKE_CLIENT_HEADER\n#define EZQUAKE_CLIENT_HEADER\n\n#if defined(_MSC_VER) && !defined(__attribute__)\n#define __attribute__(A) /**/\n#endif\n\n#define MAXWEAPONS 10\n#define MAX_ANTIROLLOVER_LEVELS 32\n\n#define MAX_STATIC_SOUNDS 256\ntypedef struct\n{\n\tvec3_t\t\torg;\n\tint\t\t\tsound_num;\n\tint\t\t\tvol;\n\tint\t\t\tatten;\n} static_sound_t;\n\nextern cvar_t cl_demospeed;\nextern cvar_t cl_demoteamplay;\n\n#define MVD_FILE_PLAYBACK   1\n#define QTV_PLAYBACK        2           // cls.mvdplayback == QTV_PLAYBACK if QTV playback\n#define ISPAUSED (cl.paused || (!cl_demospeed.value && cls.demoplayback && cls.mvdplayback != QTV_PLAYBACK && !cls.timedemo))\n#define\tMAX_PROJECTILES\t32\n\ntypedef enum {\n\tskin_base,\n\tskin_base_teammate,\n\tskin_fb,\n\tskin_dead,\n\tskin_dead_teammate,\n\tskin_textures\n} skin_texture_t;\n\n#define MV_VIEWS 4\n\ntypedef struct \n{\n\tchar            name[16];\n\tqbool           failedload;             // the name isn't a valid skin\n\tqbool           warned;                 // warning about falied to load was alredy printed\n\tint             width, height;          // this is valid for pcx too, but used for 32bit skins only\n\tint             bpp;                    // used in gl, bpp = 1 for pcx and 4 for 32bit skins\n\ttexture_ref     texnum[skin_textures];  // texture num, used for 32bit skins, speed up\n\tbyte*           cached_data;\n} skin_t;\n\nenum {\n\tdbg_antilag_rewind_present = 1,\n\tdbg_antilag_client_present = 2,\n\tdbg_antilag_position_set   = 4\n};\n\n// player_state_t is the information needed by a player entity\n// to do move prediction and to generate a drawable entity\ntypedef struct \n{\n\tint\t\t\tmessagenum;\t\t// All players won't be updated each frame.\n\n\tdouble\t\tstate_time;\t\t// Not the same as the packet time,\n\t\t\t\t\t\t\t\t// Because player commands come asyncronously.\n\tusercmd_t\tcommand;\t\t// Last command for prediction.\n\n\tvec3_t\t\torigin;\n\tvec3_t\t\tviewangles;\t\t// Only for demos, not from server.\n\tvec3_t\t\tvelocity;\n\tint\t\t\tweaponframe;\n\n\tint\t\t\tmodelindex;\n\tint\t\t\tframe;\n\tint\t\t\tskinnum;\n\tint\t\t\teffects;\n\n\tint\t\t\tflags;\t\t\t// Dead, gib, etc.\n\n#ifdef FTE_PEXT_TRANS\n\tbyte\t\talpha;\n#endif\n#ifdef FTE_PEXT_COLOURMOD\n\tbyte\t\tcolourmod[3];\n#endif\n\n\tbyte\t\tvw_index;\n\tbyte\t\tpm_type;\n\tfloat\t\twaterjumptime;\n\tqbool\t\tonground;\n\tqbool\t\tjump_held;\n\tint\t\t\tjump_msec;\t\t// Fix bunny-hop flickering.\n\n\tvec3_t      current_origin; // current location (no antilag applied)\n\tvec3_t      rewind_origin;  // location antilag server has rewound to\n\tvec3_t      client_origin;  // location client rendered the player\n\tint         antilag_flags;  // bitmask: dbg_antilag_rewind_present | dbg_antilag_client_present\n} player_state_t;\n\ntypedef enum {\n\tgender_unknown = 0,\n\tgender_male,\n\tgender_female,\n\tgender_neutral\n} gender_id;\n\ntypedef struct player_info_s \n{\n\tint\t\tuserid;\n\n\t// Scoreboard information.\n\tchar\tname[MAX_SCOREBOARDNAME];\n\tchar    shortname[MAX_SCOREBOARDNAME];           // used in tracker, when user wants to remove prefixes\n\tfloat\tentertime;\n\tint\t\tfrags;\n\tint\t\tping;\n\tbyte\tpl;\n\tunsigned char spectator;\n\n\t// Skin information.\n\tunsigned char   topcolor;\n\tunsigned char   bottomcolor;\n\tqbool           teammate;\n\n\tunsigned char   _topcolor;\n\tunsigned char   _bottomcolor;\n\tqbool           _teammate;\n\n\tunsigned char   real_topcolor;\n\tunsigned char   real_bottomcolor;\n\n\tint    fps_msec;\n\tint    last_fps;\n\tint    fps;\t\t\t\t\t\t// > 0 - fps, < 0 - invalid, 0 - collecting\n\tint    fps_frames;\n\tdouble fps_measure_time;\n\tqbool isnear;\n\n\tskin_t\t*skin;\n\n\tqbool dead;\n\tqbool\tskin_refresh;\n\tqbool\tignored;                // for ignore\n\tqbool   vignored;               // for voip-ignore\n\n\t// VULT DEATH EFFECT\n\t// Better putting the dead flag here instead of on the entity so whats dead stays dead\n\tint\t\tstats[MAX_CL_STATS];\n\tbyte\ttranslations[VID_GRADES*256];\n\tchar\tuserinfo[MAX_INFO_STRING];\n\tchar\tteam[MAX_INFO_STRING];\n\tchar\t_team[MAX_INFO_STRING];\n\tint     known_team_color;\n\n\t// used for showing stack/health bar hud elements\n\tdouble  max_health_last_set;\n\tint     max_health;\n\tdouble  prev_health_last_set;\n\tint     prev_health;\n\n\t// authentication\n\tchar\tloginname[MAX_SCOREBOARDNAME];\n\tchar    loginflag[8];\n\tint     loginflag_id;\n\n\t// extracted from userinfo\n\tint           chatflag;\n\tgender_id     gender;\n} __attribute__((aligned(64))) player_info_t;\n\n\ntypedef struct \n{\n\tqbool\t\tinterpolate;\n\tvec3_t\t\torigin;\n\tvec3_t\t\tangles;\n\tint\t\t\toldindex;\n} interpolate_t;\n\n\ntypedef struct \n{\n\t// Generated on client side.\n\tusercmd_t           cmd;                        // Cmd that generated the frame.\n\tdouble              senttime;                   // Time cmd was sent off.\n\tint                 delta_sequence;             // Sequence number to delta from, -1 = full update.\n\tint                 sentsize;\n\n\t// Received from server.\n\tdouble              receivedtime;               // Time message was received, or -1.\n\tplayer_state_t      playerstate[MAX_CLIENTS];   // Message received that reflects performing the usercmd.\n\tpacket_entities_t   packet_entities;\n\tqbool               invalid;                    // True if the packet_entities delta was invalid\n\tint                 receivedsize;\n\tint                 seq_when_received;\n\n\tqbool               in_qwd;\n} frame_t;\n\ntypedef struct centity_trail_s {\n\tvec3_t          stop;          // last position the trail was generated in\n\tdouble          lasttime;      // last time this trail generated a particle, or 0 if new\n} centity_trail_t;\n\ntypedef struct \n{\n\tentity_state_t\tbaseline;\n\tentity_state_t\tcurrent;\n\n\tvec3_t\t\t\tlerp_origin;\n\n\tvec3_t\t\t\tvelocity; // hack\n\n\tvec3_t\t\t\told_origin;\n\tvec3_t\t\t\told_angles;\n\tint\t\t\t\toldframe;\n\n\tint\t\t\t\tsequence;\n\tint\t\t\t\toldsequence;\n\n\tdouble\t\t\tstartlerp;\n\tdouble\t\t\tdeltalerp;\n\tdouble\t\t\tframetime;\n\n\tint\t\t\t\told_vw_index;\t// player entities only\n\tint\t\t\t\told_vw_frame;\t// player entities only\n\n\tint             contents;\n\n\tcentity_trail_t trails[4];\n\tfloat           particle_emittime;\n\tint             trail_number;   // this changes as trails are killed and entity re-used (can be used to detect reset)\n\tint             corona_id;\n} centity_t;\n\ntypedef struct \n{\n\tint\t\tdestcolor[3];\n\tfloat\tpercent;\t\t// 0-256\n} cshift_t;\n\n#define\tCSHIFT_CONTENTS\t0\n#define\tCSHIFT_DAMAGE\t1\n#define\tCSHIFT_BONUS\t2\n#define\tCSHIFT_POWERUP\t3\n#define\tNUM_CSHIFTS\t\t4\n\n\n// clientState_t should hold all pieces of the client state\n\n#define\tMAX_DLIGHTS\t\t\t32\n#define\tMAX_STYLESTRING\t\t64\n\ntypedef enum {lt_default, lt_muzzleflash, lt_explosion, lt_rocket,\n\tlt_red, lt_blue, lt_redblue, lt_green, lt_redgreen, lt_bluegreen,\n\tlt_white, lt_custom, NUM_DLIGHTTYPES } dlighttype_t;\n\ntypedef struct {\n\tint\t\t\t\tkey;\t\t\t\t// so entities can reuse same entry\n\tvec3_t\t\t\torigin;\n\tfloat\t\t\tradius;\n\tfloat\t\t\tdie;\t\t\t\t// stop lighting after this time\n\tfloat\t\t\tdecay;\t\t\t\t// drop this each second\n\tfloat\t\t\tminlight;\t\t\t// don't add when contributing less\n\tint\t\t\t\tbubble;\t\t\t\t// non zero means no flashblend bubble\n\tdlighttype_t\ttype;\n\tbyte\t\t\tcolor[3];\t\t\t// use such color if type == lt_custom\n\tbyte unused[3];\n} dlight_t;\n\ntypedef struct customlight_s {\n\tdlighttype_t    type;\n\tbyte            color[3];           // use such color if type == lt_custom\n\tbyte            alpha;\n} customlight_t;\n\ntypedef struct {\n\tint\t\tlength;\n\tchar\tmap[MAX_STYLESTRING];\n} lightstyle_t;\n\n#define\tMAX_STATIC_ENTITIES\t2048\t\t// torches, etc\n#define\tMAX_DEMOS\t\t\t8\n#define\tMAX_DEMONAME\t\t16\n\ntypedef enum {\nca_disconnected, \t// full screen console with no connection\nca_demostart,\t\t// starting up a demo\nca_connected,\t\t// netchan_t established, waiting for svc_serverdata\nca_onserver,\t\t// processing data lists, donwloading, etc\nca_active\t\t\t// everything is in, so frames can be rendered\n} cactive_t;\n\ntypedef enum \n{\n\tdl_none,\n\tdl_model,\n\tdl_vwep_model,\n\tdl_sound,\n\tdl_skin,\n\tdl_single\n} dltype_t;\t\t// download type\n\ntypedef enum demoseekingtype_e\n{\n\tDST_SEEKING_NONE = 0,         ///< seeking nothing\n\tDST_SEEKING_NORMAL = 1,       ///< demo_jump seeking\n\tDST_SEEKING_DEMOMARK,         ///< demo_jump_mark seeking\n\tDST_SEEKING_STATUS,           ///< demo_jump_status seeking\n\tDST_SEEKING_FOUND,            ///< mark/status seeking, hint that we are done and should stop seeking\n\tDST_SEEKING_END,              ///< demo_jump_end seeking (intermission or 1 second before end)\n\tDST_SEEKING_FOUND_NOREWIND,   ///< mark/status seeking, no automatic rewind\n} demoseekingtype_t;\n\ntypedef enum\n{\n\tDEMOSEEKINGSTATUS_MATCH_EQUAL,\n\tDEMOSEEKINGSTATUS_MATCH_NOT_EQUAL,\n\tDEMOSEEKINGSTATUS_MATCH_LESS_THAN,\n\tDEMOSEEKINGSTATUS_MATCH_GREATER_THAN,\n\tDEMOSEEKINGSTATUS_MATCH_BIT_ON,\n\tDEMOSEEKINGSTATUS_MATCH_BIT_OFF\n} demoseekingstatus_matchtype_t;\n\ntypedef struct demoseekingstatus_condition_s demoseekingstatus_condition_t;\n\nstruct demoseekingstatus_condition_s {\n\tdemoseekingstatus_matchtype_t\ttype;\n\tint\t\tstat;\n\tint\t\tvalue;\n\tdemoseekingstatus_condition_t *or;\n\tdemoseekingstatus_condition_t *and;\n};\n\ntypedef struct {\n\tqbool\t\tnon_matching_found; // whether a non matching frame has been found yet\n\tdemoseekingstatus_condition_t *conditions;\n} demoseekingstatus_t;\n\n#define TIMEDEMO_OFF      0\n#define TIMEDEMO_CLASSIC  1\n#define TIMEDEMO_FIXEDFPS 2\n\n#define TIMEDEMO_FIXEDFPS_DEFAULT (308)\n#define TIMEDEMO_FIXEDFPS_MINIMUM (20)\n#define TIMEDEMO_FIXEDFPS_MAXIMUM (10000)\n\ntypedef struct {\n\tdouble last_snapshot_time;\n\tdouble last_run_time;\n\tdouble lastfps_value;\n\tdouble lastframetime_value;\n\tdouble time_of_last_minfps_update;\n\tdouble time_of_last_maxframetime_update;\n\tint fps_count;\n} perfinfo_t;\n\n/// A structure that is persistent through an arbitrary number of server connections.\ntypedef struct\n{\n\t/// Connection information.\n\tcactive_t\tstate;\n\n\t//\n\t// Time vars.\n\t//\n\tint         framecount;       ///< Incremented every frame, never reset.\n\tdouble      realtime;         ///< Scaled by cl_demospeed.\n\tdouble      demotime;         ///< Scaled by cl_demospeed, reset when starting a demo.\n\tdouble      demo_rewindtime;  ///< The time that we should jump to when rewinding.\n\tdouble      trueframetime;    ///< Time since last frame.\n\tdouble      frametime;        ///< Time since last frame, scaled by cl_demospeed.\n\tdouble      demopackettime;   ///< Timestamp of current demo packet, whether seeking or not\n\n\t//\n\t// Network stuff.\n\t//\n\tnetchan_t\tnetchan;\n\tchar\t\tservername[MAX_OSPATH];\t///< name of server from original connect\n\tint\t\t\tqport;\n\tnetadr_t\tserver_adr;\n\tint socketip;\n\n\t//\n\t// Variables related to client cmds aka clc_stringcmd, unreliable part, reliable part goes to cls.netchan.message\n\t//\n\n\tbyte\t\tcmdmsg_data[1024];\t\t///< have no idea which size here must be\n\tsizebuf_t\tcmdmsg;\n\n\t// TCPCONNECT\n\tint\t\t\tsockettcp;\n\tnetadr_t\tsockettcpdest;\n\tbyte\t\ttcpinbuffer[1500];\n\tint \t\ttcpinlen;\n\n\t//\n\t// Private userinfo for sending to masterless servers\n\t//\n\tchar\t\tuserinfo[MAX_INFO_STRING];\n\n\t//\n\t// On a local server these may differ from com_gamedirfile and com_gamedir.\n\t//\n\tchar\t\tgamedirfile[MAX_QPATH];\n\tchar\t\tgamedir[MAX_OSPATH];\n\n\t//\n\t// Download vars.\n\t//\n\tFILE\t\t*download;\t\t\t\t///< file transfer from server\n\tchar\t\tdownloadtempname[MAX_PATH];\n\tchar\t\tdownloadname[MAX_PATH];\n\tint\t\t\tdownloadnumber;\n\tdltype_t\tdownloadtype;\n\tint\t\t\tdownloadpercent;\n\tint\t\t\tdownloadrate;\n\tdouble\t\tdownloadstarttime;\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tenum {DL_NONE = 0, DL_QW, DL_QWCHUNKS} downloadmethod;\n#else\n\tenum {DL_NONE = 0, DL_QW             } downloadmethod;\n#endif\n\n#ifdef PROTOCOL_VERSION_FTE\n\tunsigned int fteprotocolextensions; ///< the extensions we told the server that we support.\n#endif\n#ifdef PROTOCOL_VERSION_FTE2\n\tunsigned int fteprotocolextensions2; ///< the extensions we told the server that we support.\n#endif\n#ifdef PROTOCOL_VERSION_MVD1\n\tunsigned int mvdprotocolextensions1;\n#endif\n\n\t//\n\t// Upload vars.\n\t//\n\tFILE\t\t*upload;\n\tchar\t\tuploadname[MAX_OSPATH];\n\tint\t\t\tuploadpercent;\n\tint\t\t\tuploadrate;\n\tqbool\t\tis_file;\n\tbyte\t\t*mem_upload;\n\tint\t\t\tupload_pos;\n\tint\t\t\tupload_size;\n\n\t//\n\t// Demo recording info must be here, because record\n\t// is started before entering a map (and clearing clientState_t)\n\t//\n\tqbool       demorecording;\n\tqbool       demoplayback;\n\tdemoseekingtype_t demoseeking;\n\tdemoseekingstatus_t demoseekingstatus;\n\tqbool       demorewinding;\n\tchar        demoname[MAX_PATH];\n\tqbool       nqdemoplayback;\n\tint         timedemo;\n\tdouble      td_lastframe;              ///< To meter out one message a frame.\n\tint         td_startframe;             ///< cls.framecount at start\n\tdouble      td_starttime;              ///< Realtime at second frame of timedemo.\n\tdouble      td_frametime;              ///< frametime for stop-motion timedemo (timedemo2)\n\tint         td_frametime_stats[1000];  ///< keep track of performance (to 0.1ms level, if it's over 100ms we're in bad shape)\n\tint         td_frametime_max;          ///< worse ms score so far\n\tint         td_frametime_max_frame;    ///< which frame# did we get that on?\n\tdouble      td_nonrendering;           ///< time not in the rendering hot loop\n\n\tqbool\t\tmvdrecording;\t\t///< this is not real mvd recording, but just cut particular moment of mvd stream\n\n\tsizebuf_t\tdemomessage;\n\n\tdouble      fps;\n\tdouble      min_fps;\n\tdouble      avg_frametime;\n\tdouble      max_frametime;\n\n\t// FPS stats for performance info\n\tperfinfo_t  fps_stats;\n\n\tint\t\t\tchallenge;\n\n\tfloat\t\tlatency;\t\t\t///< Rolling average\n\n\tint\t\t\tmvdplayback;\t\t///< 0 = Not playing MVD; 1 = Playing MVD; 2 = Playing QTV\n\tfloat\t\tqtv_svversion;\t\t///< version of qtvsv/proxy, note it float\n\tint\t\t\tqtv_ezquake_ext;\t///< qtv ezquake extensions supported by qtvsv/proxy\n\tqbool\t\tqtv_donotbuffer;\t///< do not try buffering even if not enough data\n\tchar        qtv_source[128];    ///< last qtv source sent (so we can re-send with challenge response)\n\n\t/// \\brief Tells which players are affected by a demo message.\n\t///\t- If multiple players are affected (dem_multiple) this will be a\n\t///\t  bit mask containing which players the last demo message relates to. (32-bits, 32 players)\n\t///\t- If only a single player should receive the message (dem_single),\n\t///\t  this is a a 5-bit number containing the player number. (2^5 = 32 unique player numbers)\n\tint\t\t\tlastto;\t\t\t\n\n\tint\t\t\tlasttype;\t\t///< The type of the last demo message.\n\tqbool\t\tfindtrack;\n\n\t// authenticating via web server\n\tchar        auth_logintoken[128];\n} clientPersistent_t;\n\nextern clientPersistent_t\tcls;\n\ntypedef struct antilag_pos_s {\n\tvec3_t      pos;\n\tqbool       present;\n\tvec3_t      clientpos;\n\tqbool       clientpresent;\n} antilag_pos_t;\n\ntypedef struct antilag_stats_s {\n\tdouble      client_rewind_distance;\n\tdouble      client_rewind_samples;\n} antilag_stats_t;\n\n// cl.paused flags\n\n#define PAUSED_SERVER\t\t1\n#define PAUSED_DEMO\t\t\t2\n\n#define MAX_DAMAGE_NOTIFICATION_TIME 1.5\n#define MAX_DAMAGE_NOTIFICATIONS 15 // ((int)(10 * MAX_DAMAGE_NOTIFICATION_TIME))\n\ntypedef struct scr_damage_s {\n\tchar text[64];\n\tdouble time;\n\tvec3_t origin;\n\tvec3_t offset;\n\tfloat vel[2];\n\n\t// position on screen\n\tfloat x, y;\n\tqbool visible;\n} scr_damage_t;\n\n/// a structure that is wiped completely at every server signon\ntypedef struct {\n\tint\t\t\tservercount;\t\t///< server identification for prespawns\n\n\tchar\t\tserverinfo[MAX_SERVERINFO_STRING];\n\n\tint\t\t\tprotoversion;\n\t// some important serverinfo keys are mirrored here:\n\tint\t\t\tdeathmatch;\n\tint\t\t\tteamplay;\n\tint\t\t\tgametype;\t\t\t///< GAME_COOP or GAME_DEATHMATCH\n\tqbool\t\tteamfortress;\t\t///< true if gamedir is \"fortress\"\n\tint\t\t\tfpd;\t\t\t\t///< FAQ proxy flags\n\tint\t\t\tz_ext;\t\t\t\t///< ZQuake protocol extensions flags\n\tqbool\t\tvwep_enabled;\n\tint\t\t\ttimelimit;\n\tint\t\t\tfraglimit;\n\tfloat\t\tmaxfps;\n\tfloat\t\tminpitch;\n\tfloat\t\tmaxpitch;\n\n\tint\t\t\tlast_fps;\n\n\tint\t\t\tparsecount;\t\t\t///< server message counter\n\tint\t\t\toldparsecount;\n\tint         parsecountmod;\n\tdouble      parsecounttime;\n\n\n\tint\t\t\tvalidsequence;\t\t///< this is the sequence number of the last good\n\t\t\t\t\t\t\t\t\t///< packetentity_t we got.  If this is 0, we can't\n\t\t\t\t\t\t\t\t\t///< render a frame yet\n\tint\t\t\toldvalidsequence;\n\tint\t\t\tdelta_sequence;\t\t///< sequence number of the packet we can request\n\t\t\t\t\t\t\t\t\t///< delta from\n\n\tint\t\t\tspectator;\n\n\tdouble\t\tlast_ping_request;\t///< while showing scoreboard\n\n\t// sentcmds[cl.netchan.outgoing_sequence & UPDATE_MASK] = cmd\n\tframe_t\t\tframes[UPDATE_BACKUP];\n\n\t// information for local display\n\tint\t\t\tstats[MAX_CL_STATS];///< health, etc\n\tfloat\t\titem_gettime[32];\t///< cl.time of acquiring item, for blinking\n\tfloat\t\tfaceanimtime;\t\t///< use anim frame if cl.time < this\n\tfloat\t\thurtblur;\t\t\t///< blur view caused by damage\n\n\tcshift_t\tcshifts[NUM_CSHIFTS];\t///< color shifts for damage, powerups and content types\n\n\t// the client maintains its own idea of view angles, which are\n\t// sent to the server each frame.  And only reset at level change\n\t// and teleport times\n\tvec3_t\t\tviewangles;\n\n\t// the client simulates or interpolates movement to get these values\n\tdouble\t\ttime;\t\t\t\t// this is the time value that the client\n\t\t\t\t\t\t\t\t\t// is rendering at.  always <= realtime\n\n\tdouble\t\tservertime;\n\tqbool\t\tservertime_works;\t///< Does the server actually send STAT_TIME/svc_time?\n\tdouble\t\tgametime;\t\t\t///< match duration\n\tdouble\t\tgamestarttime;\t\t///< this gets saved on match start\n\tdouble\t\tgamepausetime;\t\t///< this gets increased during the pause\n\n\n\tvec3_t\t\tsimorg;\n\tvec3_t\t\tsimvel;\n\tvec3_t\t\tsimangles;\n\n\t// pitch drifting vars\n\tfloat\t\tpitchvel;\n\tqbool\t\tnodrift;\n\tfloat\t\tdriftmove;\n\tdouble\t\tlaststop;\n\n\tqbool\t\tonground;\n\tfloat\t\tcrouch;\t\t\t\t///< local amount for smoothing stepups\n//\tfloat\t\tviewheight;\t\t\t///< removed, since it does not work well in case of MVD, search for cl.stats[STAT_VIEWHEIGHT] instead.\n\n\tqbool\t\tpaused;\t\t\t\t///< a combination of PAUSED_SERVER and PAUSED_DEMO flags\n\n\tfloat\t\tideal_punchangle;\t///< temporary view kick from weapon firing\n\tfloat\t\tpunchangle;\t\t\t///< drifts towards ideal_punchangle\n\tfloat\t\trollangle;\t\t\t///< smooth out rollangle changes when strafing\n\n\tint\t\t\tintermission;\t\t///< don't change view angle, full screen, etc\n\tint\t\t\tcompleted_time;\t\t///< latched from time at intermission start\n\tint\t\t\tsolo_completed_time;///< to draw on intermission screen\n\n\t// information that is static for the entire time connected to a server\n\tchar\t\tmodel_name[MAX_MODELS][MAX_QPATH];\n\tchar\t\tvw_model_name[MAX_VWEP_MODELS][MAX_QPATH];\t///< VWep support\n\tchar\t\tsound_name[MAX_SOUNDS][MAX_QPATH];\n\n\tstruct model_s\t*model_precache[MAX_MODELS];\n\tstruct model_s\t*vw_model_precache[MAX_VWEP_MODELS];\t///< VWep support\n\tstruct sfx_s\t*sound_precache[MAX_SOUNDS];\n\n\tcmodel_t\t*clipmodels[MAX_MODELS];\n\tunsigned\tmap_checksum2;\n\n\tstatic_sound_t\tstatic_sounds[MAX_STATIC_SOUNDS];\n\tint\t\t\tnum_static_sounds;\n\n\tchar\t\tlevelname[40];\t\t///< for display on solo scoreboard\n\tint\t\t\tplayernum;\n\tint\t\t\tviewplayernum;\t\t///< either playernum or spec_track (in chase camera mode)\n\n\t// refresh related state\n\tstruct model_s\t*worldmodel;\t///< cl_entitites[0].model\n\tstruct efrag_s\t*free_efrags;\n\tint\t\t\tnum_statics;\t\t///< stored top down in cl_entities\n\n\tint\t\t\tcdtrack;\t\t\t///< cd audio\n\n\tcentity_t viewent[MV_VIEWS];\t        // weapon models\n\n\t// all player information\n\tplayer_info_t\tplayers[MAX_CLIENTS];\n\n\t// sprint buffer\n\tint\t\t\tsprint_level;\n\twchar\t\tsprint_buf[2048];\n\n\t// localized movement vars\n\tfloat\t\tentgravity;\n\tfloat\t\tmaxspeed;\n\tfloat\t\tbunnyspeedcap;\n\n\tint\t\t\twaterlevel;\n\n\n\tdouble\t\twhensaid[10];       ///< For floodprots\n \tint\t\t\twhensaidhead;       ///< Head value for floodprots\n\n\n\tqbool\t\tstandby;\n\tqbool\t\tcountdown;\n\tqbool\t\tuserfb;\n\tint\t\t\tminlight;\n\n\tinterpolate_t\tint_projectiles[MAX_PROJECTILES];\n\n\tint         last_armor_pickup;\n\tint         last_weapon_pickup;\n\tint         last_ammo_pickup;\n\n\tfloat       mvd_time_offset;\n\tint         mvd_user_cmd[8];\n\tfloat       mvd_user_cmd_time[8];\n\n\tqbool       racing;\n\tint         race_pacemaker_ent;\n\n\tfloat       fakeshaft_policy;\n\n\tint         sv_maxclients;\n\n\tint         cif_flags;\n\n\tint         scoring_system;\n\n\tchar        fixed_team_names[4][16];\n\tqbool       mvd_ktx_markers;\n\n\t// r_viewmodellastfired\n\tint         lastfired;\n\tint         lastviewplayernum;\n\n\t// Weapon preferences\n\tint         weapon_order[MAXWEAPONS];\n\tint         weapon_order_clientside[MAXWEAPONS];\n\tint         weapon_order_sequence_set;\n\tqbool       weapon_order_use_clientside;\n\n\t// anti-rollover weapon firing\n\tint         ar_weapon_orders[MAX_ANTIROLLOVER_LEVELS][MAXWEAPONS];\n\tint         ar_keycodes[MAX_ANTIROLLOVER_LEVELS];\n\tint         ar_count;\n\n\t// When teamlock 1 is specified, lock in the selected team and don't change again\n\tchar        teamlock1_teamname[16];\n\n\t// authenticating via web server\n\tchar        auth_challenge[128];\n\n\t// camera tracking\n\tint         autocam;              // CAM_NONE or CAM_TRACK\n\tint         spec_track;           // player# of who we are tracking\n\tint         ideal_track;          // The currently tracked player.\n\tqbool       spec_locked;          // Is the spectator locked to a players view or free flying.\n\n\t// antilag debug playback\n\tantilag_pos_t antilag_positions[MAX_CLIENTS];\n\tantilag_stats_t antilag_stats[MAX_CLIENTS];\n\n\t// demoinfo (stats file embedded in demo)\n\tint         demoinfo_blocknumber;\n\tint         demoinfo_bytes;\n\n\t// damage notifications\n\tscr_damage_t damage_notifications[MAX_DAMAGE_NOTIFICATIONS];\n\n\t// fog info read from worldspawn\n\tvec3_t map_fog_color;\n\tfloat map_fog_density;\n\tqbool map_fog_enabled;\n\tfloat map_fog_sky;\n\n\t// sv_safestrafe client-side prediction\n\tstruct {\n\t\tint      pending_frames;    // Frames of forced stop remaining\n\t\tfloat    pending_direction; // Desired sidemove after stop\n\t\tint      stop_frames;       // Accumulated frames with sidemove=0\n\t\tfloat    last_sidemove;     // Previous frame's sidemove value\n\t} safestrafe;\n} clientState_t;\n\n#define SCORING_SYSTEM_DEFAULT   0\n#define SCORING_SYSTEM_STANDARD  1\n#define SCORING_SYSTEM_TEAMFRAGS 2\n\nextern\tclientState_t\tcl;\n\ntypedef enum visentlist_entrytype_s {\n\tvisent_outlines,\n\tvisent_firstpass,\n\tvisent_normal,\n\tvisent_alpha,\n\tvisent_shells,\n\tvisent_additive,\n\n\tvisent_max\n} visentlist_entrytype_t;\n\ntypedef struct visentity_s {\n\tentity_t    ent;\n\tmodtype_t   type;\n\tfloat       distance;\n\n\tqbool       draw[visent_max];\n} visentity_t;\n\n#define MAX_STANDARD_ENTITIES 2048\n\ntypedef struct visentlist_s {\n\tvisentity_t list[MAX_STANDARD_ENTITIES];\n\tint         count;\n\n\tint         typecount[visent_max];\n} visentlist_t;\n\nextern visentlist_t cl_visents;\n\n// ezQuake cvars\nextern cvar_t cl_floodprot;\nextern cvar_t cl_fp_messages;\nextern cvar_t cl_fp_persecond;\nextern cvar_t cl_cmdline;\nextern cvar_t b_switch;     // added for the sake of menu.c\nextern cvar_t w_switch;     // added for the sake of menu.c\nextern cvar_t gender;       // added for the sake of menu.c\n\nextern cvar_t  cl_mediaroot;\n\n// Multiview cvars\nextern cvar_t cl_multiview;\nextern cvar_t cl_mvdisplayhud;\nextern cvar_t cl_mvhudpos;\nextern cvar_t cl_mvhudvertical;\nextern cvar_t cl_mvhudflip;\nextern cvar_t cl_mvinset;\nextern cvar_t cl_mvinsetcrosshair;\nextern cvar_t cl_mvinsethud;\n\nextern cvar_t r_rocketlight;\nextern cvar_t r_rocketlightcolor;\nextern cvar_t r_explosionlightcolor;\nextern cvar_t r_explosionlight;\nextern cvar_t r_explosiontype;\nextern cvar_t r_flagcolor;\nextern cvar_t r_lightflicker;\nextern cvar_t r_lightmap_lateupload;\nextern cvar_t r_lightmap_packbytexture;\nextern cvar_t r_telesplash;\nextern cvar_t r_shaftalpha;\n\nextern cvar_t cl_restrictions;\n\n// ZQuake cvars\nextern cvar_t\tcl_warncmd;\nextern\tcvar_t\tcl_shownet;\nextern\tcvar_t\tcl_sbar;\nextern\tcvar_t\tcl_hudswap;\nextern\tcvar_t\tcl_deadbodyfilter;\nextern\tcvar_t\tcl_gibfilter;\nextern  cvar_t  cl_backpackfilter;\nextern\tcvar_t\tcl_muzzleflash;\nextern\tcvar_t\tcl_fakeshaft;\nextern\tcvar_t\tcl_fakeshaft_extra_updates;\n\nextern cvar_t r_rockettrail;\nextern cvar_t r_grenadetrail;\nextern cvar_t r_railtrail;\nextern cvar_t r_instagibtrail;\n\nextern cvar_t r_powerupglow;\n\n// FIXME, allocate dynamically\nextern\tcentity_t        cl_entities[CL_MAX_EDICTS];\nextern\tentity_t         cl_static_entities[MAX_STATIC_ENTITIES];\nextern\tlightstyle_t     cl_lightstyle[MAX_LIGHTSTYLES];\nextern\tdlight_t         cl_dlights[MAX_DLIGHTS];\nextern  unsigned int     cl_dlight_active[MAX_DLIGHTS/32];       \n\nextern byte\t\t*host_basepal;\nextern byte\t\t*host_colormap;\n\n//=============================================================================\n\n// cl_main\n\nvoid CL_Init (void);\nvoid CL_ClearState (void);\nvoid CL_BeginServerConnect(void);\nvoid CL_Disconnect (void);\nvoid CL_Disconnect_f (void);\nvoid CL_Reconnect_f (void);\nqbool CL_ConnectedToProxy(void);\nvoid CL_MakeActive(void);\n\nextern char emodel_name[], pmodel_name[];\n\n// cl_cmd\ntypedef struct {\n\tqbool forward, back, left, right, jump, attack, up, down;\n} usermainbuttons_t;\nvoid CL_PrintQStatReply (char *s);\n// returns last button user pressed\nusermainbuttons_t CL_GetLastCmd (int player_slot);\n\n// cl_nqdemo.c\nvoid NQD_StartPlayback (void);\nvoid NQD_LinkEntities (void);\nvoid NQD_ReadPackets (void);\nvoid NQD_SetSpectatorFlags (void);\n\n// cl_demo.c\nqbool CL_GetDemoMessage (void);\nvoid CL_WriteDemoCmd (usercmd_t *pcmd);\nvoid CL_WriteDemoMessage (sizebuf_t *msg);\nvoid CL_WriteDemoEntities (void);\nvoid CL_WriteServerdata (sizebuf_t *msg);\nvoid CL_StopPlayback (void);\nvoid CL_Stop_f (void);\nvoid CL_CheckQizmoCompletion(void);\nvoid CL_Demo_Jump(double seconds, int relative, demoseekingtype_t seeking);\nvoid CL_Demo_Init(void);\nvoid CL_Demo_Jump_Status_Check (void);\nvoid CL_Demo_Check_For_Rewind(float nextdemotime);\nvoid CL_Demo_Stop_Rewinding(void);\ndouble Demo_GetSpeed(void);\nvoid Demo_AdjustSpeed(void);\nqbool CL_IsDemoExtension(const char *filename);\nqbool CL_Demo_SkipMessage(qbool skip_if_seeking);\nqbool CL_Demo_NotForTrackedPlayer(void);\nqbool CL_DemoExtensionMatch(const char* path);\n\nvoid CL_AutoRecord_StopMatch(void);\nvoid CL_AutoRecord_CancelMatch(void);\nvoid CL_AutoRecord_StartMatch(char *demoname);\nqbool CL_AutoRecord_Status(void);\nvoid CL_AutoRecord_SaveMatch(void);\n\nqbool SCR_QTVBufferToBeDrawn(int options);\nint Demo_BufferSize(int* ms);\n\nextern double demostarttime;\nextern double nextdemotime, olddemotime;\n\n// cl_parse.c\n#define NET_TIMINGS 256\n#define NET_TIMINGSMASK 255\nextern int\tpacket_latency[NET_TIMINGS];\n\n// advanced network stats\n\n#define  NETWORK_STATS_SIZE  512    // must be power of 2\n#define  NETWORK_STATS_MASK  (NETWORK_STATS_SIZE-1)\n\ntypedef  enum{packet_ok, packet_dropped, packet_choked,\n              packet_delta, packet_netlimit}  packet_status;\n\ntypedef struct packet_info_s\n{\n    packet_status   status;\n\n    double          senttime;\n    double          receivedtime;\n\n    int             sentsize;\n    int             receivedsize;\n\n    int             seq_diff;   // frames elapsed between send and recv\n\n    qbool        delta;  // if deltaying\n}\npacket_info_t;\n\nextern packet_info_t network_stats[NETWORK_STATS_SIZE];\n\ntypedef struct net_stat_result_s\n{\n    int samples;    // samples processed to calculate stats\n\n    // latency [miliseconds]\n    float ping_min;\n    float ping_avg;\n    float ping_max;\n    float ping_dev;\n\n    // latency [frames]\n    int   ping_f_min;\n    int   ping_f_max;\n    float ping_f_avg;\n    float ping_f_dev;\n\n    // packet loss [percent]\n    float lost_lost;\n    float lost_rate;\n    float lost_delta;\n    float lost_netlimit;\n\n    // average packet sizes [bytes]\n    float size_in;\n    float size_out;\n\n    // bandwidth [bytes / sec]\n    float bandwidth_in;\n    float bandwidth_out;\n\n    // if delta compression occured\n    int delta;\n\n} net_stat_result_t;\n\nint CL_CalcNetStatistics(\n            float period,           // [IN] period of time\n            packet_info_t *samples, // [IN] statistics table\n            int num_samples,        // [IN] table size\n            net_stat_result_t *res); // [OUT]\n\nint CL_CalcNet (void);\nvoid CL_ParseServerMessage (void);\nvoid CL_NewTranslation (int slot);\nqbool CL_CheckOrDownloadFile (char *filename);\nqbool CL_IsUploading(void);\nvoid CL_NextUpload(void);\nvoid CL_StartUpload (byte *data, int size);\nvoid CL_StopUpload(void);\nvoid CL_StartFileUpload(void);\n\nvoid CL_ParseClientdata (void);\n\nvoid CL_FinishDownload(void);\n\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\nvoid\tCL_ParseChunkedDownload(void);\nvoid\tCL_Parse_OOB_ChunkedDownload(void);\nint\t\tCL_RequestADownloadChunk(void);\nvoid\tCL_SendChunkDownloadReq(void);\n\n#endif // FTE_PEXT_CHUNKEDDOWNLOADS\n\n// cl_tent.c\nvoid CL_InitTEnts (void);\nvoid CL_InitTEntsCvar(void);\nvoid CL_ClearTEnts (void);\nvoid CL_ParseTEnt (void);\nvoid CL_ExplosionSprite (vec3_t);\nvoid CL_ClearPlayerBeams(void);\nvoid CL_UpdateTEnts(void);\n\n// cl_ents.c\ntypedef enum cl_modelindex_s {\n\tmi_spike, mi_player, mi_eyes, mi_flag, mi_tf_flag, mi_tf_stan, mi_stag, mi_basrkey, mi_basbkey, mi_w_s_key, mi_w_g_key, mi_b_g_key, mi_b_s_key, mi_ff_flag, mi_harbflag, mi_princess, mi_explod1, mi_explod2, mi_h_player,\n\tmi_gib1, mi_gib2, mi_gib3, mi_rocket, mi_grenade, mi_bubble,\n\tmi_vaxe, mi_vbio, mi_vgrap, mi_vknife, mi_vknife2, mi_vmedi, mi_vspan,\n\tmi_flame,\t//joe: for changing flame/flame0 models\n\tmi_monster1,mi_m2,mi_m3,mi_m4,mi_m5,mi_m6,mi_m7,mi_m8,mi_m9,mi_m10,mi_m11,mi_m12,\n\tmi_m13, mi_m14, mi_m15, mi_m16, mi_m17,\n\tmi_weapon1, mi_weapon2, mi_weapon3, mi_weapon4, mi_weapon5, mi_weapon6, mi_weapon7, mi_weapon8,\n\tcl_num_modelindices,\n} cl_modelindex_t;\n\nextern int cl_modelindices[cl_num_modelindices];\nextern char *cl_modelnames[cl_num_modelindices];\n\nvoid CL_InitEnts(void);\nvoid CL_AddEntity (entity_t *ent);\nvoid CL_ClearScene (void) ;\nvoid CL_AddParticleTrail(entity_t* ent, centity_t* cent, customlight_t* cst_lt, entity_state_t *state);\n\ndlighttype_t dlightColor(float f, dlighttype_t def, qbool random);\ncustomlight_t *dlightColorEx(float f, char *str, dlighttype_t def, qbool random, customlight_t *l);\n\ndlight_t *CL_AllocDlight (int key);\nvoid CL_NewDlight (int key, vec3_t origin, float radius, float time, dlighttype_t type, int bubble);\nvoid CL_NewDlightEx (int key, vec3_t origin, float radius, float time, customlight_t *l, int bubble);\nvoid CL_DecayLights (void);\n\nvoid CL_SetSolidPlayers (int playernum);\nvoid CL_SetUpPlayerPrediction(qbool dopred);\nvoid CL_EmitEntities (void);\nvoid CL_ClearProjectiles (void);\nvoid CL_ParsePacketEntities (qbool delta);\nvoid CL_SetSolidEntities (void);\nvoid CL_ParsePlayerinfo (void);\nvoid CL_StorePausePredictionLocations(void);\n\n\nvoid MVD_Interpolate(void);\nvoid CL_ClearPredict(void);\nvoid CL_ParseProjectiles(qbool indexed);\n\n\n// cl_pred.c\nvoid CL_InitPrediction(void);\nvoid CL_PredictMove(qbool physframe);\nvoid CL_PredictUsercmd(player_state_t *from, player_state_t *to, usercmd_t *u);\nvoid CL_DisableLerpMove(void);\n\n// cl_cam.c\nvoid vectoangles(vec3_t vec, vec3_t ang);\n#define CAM_NONE\t0\n#define CAM_TRACK\t1\n\nint WhoIsSpectated (void);\nvoid CL_Cam_SetKiller(int killernum, int victimnum);\nvoid Cam_Angles_Set(float pitch, float yaw, float roll);\nvoid Cam_Pos_Set(float x, float y, float z);\n\nqbool Cam_DrawViewModel (void);\nqbool Cam_DrawPlayer (int playernum);\nvoid Cam_Track (usercmd_t *cmd);\nvoid Cam_FinishMove (usercmd_t *cmd);\nvoid Cam_Reset (void);\nvoid Cam_SetViewPlayer (void);\nvoid CL_InitCam (void);\nvoid Cam_TryLock (void);\n\nint Cam_TrackNum(void);\nvoid Cam_Lock(int playernum);\n\n// skin.c\nvoid Skin_Skins_f(void);\nvoid Skin_Clear(qbool download);\nvoid Skin_AllSkins_f(void);\nvoid Skin_NextDownload(void);\nvoid Skin_ShowSkins_f(void);\nvoid Skins_PreCache(void);\nvoid Skin_InvalidateTextures(void);\nvoid Skin_UserInfoChange(int slot, player_info_t* player, const char* key);\nvoid Skin_RegisterCvars(void);\n\n// match_tools.c\nvoid MT_Init(void);\nvoid MT_Frame(void);\nvoid MT_Disconnect(void);\nvoid MT_NewMap(void);\nchar *MT_MatchName(void);\nchar *MT_ShortStatus(void);\nvoid MT_Shutdown(void);\n\n// fragstats.c\n\nvoid Stats_Init(void);\nvoid Stats_Reset(void);\nvoid Stats_NewMap(void);\nvoid Stats_EnterSlot(int num);\nvoid Stats_ParsePrint(char *s, int level, cfrags_format *cff);\nvoid Stats_Shutdown(void);\n\nqbool Stats_IsActive(void);\nqbool Stats_IsFlagsParsed(void);\nvoid Stats_GetBasicStats(int num, int *stats);\nvoid Stats_GetFlagStats(int num, int *stats);\n\n#define RSSHOT_WIDTH 320\n#define RSSHOT_HEIGHT 200\n\nvoid CL_CalcPlayerFPS(player_info_t *info, int msec);\n\nqbool CL_DrawnPlayerPosition(int player_num, float* pos, int* msec);\n\n//\n// Multiview vars\n// ===================================================================================\nvoid CL_MultiviewInitialise (void);\nvoid CL_MultiviewTrackingAdjustment (int adjustment);\n\n// Status\nqbool CL_MultiviewEnabled (void);\nqbool CL_MultiviewInsetEnabled (void);\nqbool CL_MultiviewInsetView (void);\nint CL_MultiviewNumberViews (void);\nint CL_MultiviewActiveViews (void);\nint CL_MultiviewCurrentView (void);\nint CL_MultiviewNextPlayer (void);\nint CL_MultiviewAutotrackSlot (void);\nint CL_MultiviewMainView(void);\nvoid CL_MultiviewInsetSetScreenCoordinates(int x, int y, int width, int height);\n\nvoid CL_MultiviewSetTrackSlot (int trackNum, int player);\nvoid CL_MultiviewResetCvars (void);\n\n// Events\nvoid CL_MultiviewFrameStart (void);\nvoid CL_MultiviewPreUpdateScreen (void);\nqbool CL_MultiviewAdvanceView (void);\nvoid CL_MultiviewFrameFinish (void);\nvoid CL_MultiviewDemoStart (void);\nvoid CL_MultiviewDemoFinish (void);\nvoid CL_MultiviewDemoStartRewind (void);\nvoid CL_MultiviewDemoStopRewind (void);\n\n// Restore stats for main view hud\nvoid CL_MultiviewInsetRestoreStats(void);\n\n// Weapons\ncentity_t* CL_WeaponModelForView(void);\n\n// ===================================================================================\n// client side min_ping aka delay\n\nextern cvar_t cl_delay_packet;\nextern cvar_t cl_delay_packet_target;\nextern cvar_t cl_delay_packet_dev;\n\n#define CL_MAX_DELAYED_PACKETS 16 /* 13 * 16 = 208 ms, should be enough */\n#define CL_MAX_PACKET_DELAY 75 /* total delay two times more */\n#define CL_MAX_PACKET_DELAY_DEVIATION 5\n#define CL_MAX_PACKET_DELAY_TARGET 155\n\ntypedef struct cl_delayed_packet_s\n{\n\tbyte data[MSG_BUF_SIZE]; // packet data, perhaps we can/should use [MAX_MSGLEN + PACKET_HEADER]\n\tint length; // how much data we actually have in data[]\n\tnetadr_t addr; // to/from\n\n\tdouble time; // when we should read/send this packet\n\n} cl_delayed_packet_t;\n\nqbool CL_QueInputPacket(void);\nqbool CL_UnqueOutputPacket(qbool sendall);\nvoid CL_ClearQueuedPackets(void);\n\n// ===================================================================================\n\n// Sound\n#ifdef FTE_PEXT2_VOICECHAT\nvoid S_Voip_Transmit(unsigned char clc, sizebuf_t *buf);\nqbool S_Voip_ShowMeter(int* x, int* y);\nqbool S_Voip_Speaking (unsigned int player);\nvoid S_Capture_Shutdown(void);\nint S_Voip_Loudness(void);\nvoid S_Voip_MapChange(void);\nvoid S_Voip_Parse(void);\nvoid S_Voip_Ignore(unsigned int slot, qbool ignore);\nfloat S_VoipVoiceTransmitVolume(void);\n#else\n#define S_Voip_ShowMeter(x, y) false\n#define S_Voip_Speaking(x) false\n#define S_Voip_Loudness() (0)\n#define S_VoipVoiceTransmitVolume() (1)\n#endif\n\n// KTX\n#define KTX_RACING_PLAYER_MIN_DISTANCE 24.0f\n#define KTX_RACING_PLAYER_MAX_DISTANCE 256.0f\n#define KTX_RACING_PLAYER_ALPHA_SCALE  512.0f\n\n// Player Dead?\n#define ISDEAD(i) ( (i) >= 41 && (i) <= 102 )\n\n// Screenshot queue\ntypedef enum image_format_s {IMAGE_PCX, IMAGE_TGA, IMAGE_JPEG, IMAGE_PNG} image_format_t;\n\ntypedef struct scr_sshot_target_s {\n\tchar fileName[128];\n\tbyte* buffer;\n\tqbool freeMemory;\n\tqbool movie_capture;\n\tsize_t width;\n\tsize_t height;\n\timage_format_t format;\n} scr_sshot_target_t;\n\nint SCR_ScreenshotWrite(scr_sshot_target_t* target_params);\n\nqbool Movie_AnimatedPNG(void);\n\nqbool Movie_BackgroundCapture(scr_sshot_target_t* params);\nbyte* Movie_TempBuffer(size_t width, size_t height);\nqbool Movie_BackgroundInitialise(void);\nvoid Movie_BackgroundShutdown(void);\n\nvoid Cache_Flush(void);\n\n#define DEFAULT_CHAT_SOUND \"misc/talk.wav\"\n\n#ifdef WITH_RENDERING_TRACE\nvoid Dev_VidFrameStart(void);\nvoid Dev_VidFrameTrace(void);\nvoid Dev_VidTextureDump(void);\nvoid Dev_TextureList(void);\n#endif\n\n// weapons scripts\nint IN_BestWeapon(qbool rendering_only);\nint IN_BestWeaponReal(qbool rendering_only);\n\n// hud_common.c\nvoid CL_RemovePrefixFromName(int player);\n\n#endif // EZQUAKE_CLIENT_HEADER\n"
  },
  {
    "path": "src/cmd.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: cmd.c,v 1.92 2007-10-28 02:45:19 qqshka Exp $\n*/\n\n#ifndef _WIN32\n#include <strings.h>\n#endif\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"rulesets.h\"\n#include \"tp_triggers.h\"\n#include \"parser.h\"\n#include \"utils.h\"\n#include \"keys.h\"\n#include \"hash.h\"\n#include <pcre2.h>\n\ntypedef struct {\n\tchar name[MAX_MACRO_NAME];\n\tchar *(*func) (void);\n\tint teamplay;\n} macro_command_t;\n\n#define MACRO_DEF(x) { #x, NULL, 0 }\n\nstatic macro_command_t macro_commands[num_macros] = {\n#include \"macro_ids.h\"\n};\n\n#undef MACRO_DEF\n\nqbool CL_CheckServerCommand (void);\n\nstatic void Cmd_ExecuteStringEx (cbuf_t *context, char *text);\nstatic int gtf = 0; // global trigger flag\n\ncvar_t cl_warncmd = {\"cl_warncmd\", \"1\"};\n\ncvar_t cl_warnexec = {\"cl_warnexec\", \"1\"};\ncvar_t cl_curlybraces = {\"cl_curlybraces\", \"0\"};\n\n#define REMOTE_CAPABILITIES \"+attack,-attack,alias,bf,changing,cmd,color,download,exec,fullserverinfo,\" \\\n\t\t\t\t\"impulse,infoset,ktx_infoset,ktx_sinfoset,nextul,on_admin,on_connect,\" \\\n\t\t\t\t\"on_connect_ctf,on_connect_ffa,on_enter,on_enter_ctf,on_enter_ffa,on_matchend,\" \\\n\t\t\t\t\"on_matchstart,on_observe,on_observe_ctf,on_observe_ffa,on_spec_enter,\" \\\n\t\t\t\t\"on_spec_enter_ctf,on_spec_enter_ffa,on_spec_matchend,on_spec_matchstart,\" \\\n\t\t\t\t\"on_unadmin,packet,play,rate,reconnect,say,sinfoset,skin,skins,team,tempalias,\" \\\n\t\t\t\t\"track,wait\"\n\nstatic void OnChange_remote_capabilities(cvar_t *var, char *string, qbool *cancel);\ncvar_t cl_remote_capabilities = {\"cl_remote_capabilities\", REMOTE_CAPABILITIES, 0,\n\t\t\t\t   OnChange_remote_capabilities};\nhashtable_t *rc_hash;\n\ncvar_t cl_allow_downloads = {\"cl_allow_downloads\", \"bsp,lmp,loc,mdl,mvd,pcx,spr,wad,wav\"};\ncvar_t cl_allow_uploads = {\"cl_allow_uploads\", \"0\"};\n\ncbuf_t cbuf_main;\ncbuf_t cbuf_svc;\ncbuf_t cbuf_safe, cbuf_formatted_comms;\ncbuf_t cbuf_server;\n\ncbuf_t *cbuf_current = NULL;\n\nstatic void OnChange_remote_capabilities(cvar_t *var, char *string, qbool *cancel)\n{\n\tchar *cmd, *ent, *tmp;\n\n\tif (cls.state != ca_disconnected)\n\t{\n\t\tCom_Printf(\"You cannot change remote capabilities unless you are disconnected\\n\");\n\t\treturn;\n\t}\n\n\tif (!rc_hash || strlen(var->string) == 0)\n\t{\n\t\tgoto add;\n\t}\n\n\ttmp = Q_strdup(var->string);\n\tcmd = strtok(tmp, \",\");\n\twhile (cmd != NULL)\n\t{\n\t\tCom_DPrintf(\"Removing %s from remote capabilities\\n\", cmd);\n\n\t\tif ((ent = Hash_Get(rc_hash, cmd)))\n\t\t{\n\t\t\tQ_free(ent);\n\t\t\tHash_Remove(rc_hash, cmd);\n\t\t}\n\n\t\tcmd = strtok(NULL, \",\");\n\t}\n\tQ_free(tmp);\n\nadd:\n\tif (!rc_hash)\n\t{\n\t\trc_hash = Hash_InitTable(512);\n\t}\n\telse\n\t{\n\t\tHash_Flush(rc_hash);\n\t}\n\n\ttmp = Q_strdup(string);\n\tcmd = strtok(tmp, \",\");\n\twhile (cmd != NULL)\n\t{\n\t\tCom_DPrintf(\"Adding %s to capabilities\\n\", cmd);\n\n\t\tif (!Hash_Get(rc_hash, cmd))\n\t\t{\n\t\t\tent = Q_malloc(sizeof(char));\n\t\t\t*ent = 1;\n\t\t\tHash_Add(rc_hash, cmd, (void *)ent);\n\t\t}\n\n\t\tcmd = strtok(NULL, \",\");\n\t}\n\tQ_free(tmp);\n}\n\nqbool CL_IsDownloadableFileExtension(const char *filename)\n{\n\tqbool is_allowed = false;\n\tchar *ext, *str, *tmp;\n\n\text = COM_FileExtension(filename);\n\tstr = Q_strdup(cl_allow_downloads.string);\n\ttmp = strtok(str, \",\");\n\twhile (tmp != NULL) {\n\t\tif (strcmp(ext, tmp) == 0) {\n\t\t\tis_allowed = true;\n\t\t\tbreak;\n\t\t}\n\n\t\ttmp = strtok(NULL, \",\");\n\t}\n\tQ_free(str);\n\n\treturn is_allowed;\n}\n\n//=============================================================================\n\n//Causes execution of the remainder of the command buffer to be delayed until next frame.\n//This allows commands like: bind g \"impulse 5 ; +attack ; wait ; -attack ; impulse 2\"\nvoid Cmd_Wait_f (void)\n{\n\tif (cbuf_current)\n\t\tcbuf_current->wait = true;\n\n\treturn;\n}\n\n// copies the first argument to clipboard\nvoid Cmd_Clipboard_f(void) {\n\tCopyToClipboard(Cmd_Args());\n}\n\n/*\n=============================================================================\n\t\t\t\t\t\tCOMMAND BUFFER\n=============================================================================\n*/\n\nvoid Cbuf_AddText (const char *text)\n{\n\tCbuf_AddTextEx (&cbuf_main, text);\n}\n\nvoid Cbuf_InsertText (const char *text)\n{\n\tCbuf_InsertTextEx (&cbuf_main, text);\n}\n\nvoid Cbuf_Execute (void)\n{\n\tCbuf_ExecuteEx (&cbuf_main);\n\tCbuf_ExecuteEx (&cbuf_safe);\n\tCbuf_ExecuteEx (&cbuf_formatted_comms);\n\tCbuf_ExecuteEx (&cbuf_server);\n}\n\nvoid Cbuf_Flush(cbuf_t* cbuf)\n{\n\twhile (cbuf->text_end > cbuf->text_start)\n\t\tCbuf_ExecuteEx(cbuf);\n}\n\n//fuh : ideally we should have 'cbuf_t *Cbuf_Register(int maxsize, int flags, qbool (*blockcmd)(void))\n//fuh : so that cbuf_svc and cbuf_safe can be registered outside cmd.c in cl_* .c\n//fuh : flags can be used to deal with newline termination etc for cbuf_svc, and *blockcmd can be used for blocking cmd's for cbuf_svc\n//fuh : this way cmd.c would be independant of '#ifdef CLIENTONLY's'.\n//fuh : I'll take care of that one day.\nstatic void Cbuf_Register (cbuf_t *cbuf, int maxsize)\n{\n\tassert (!host_initialized);\n\tcbuf->maxsize = maxsize;\n\tcbuf->text_buf = (char *) Hunk_AllocName(maxsize, \"cbuf\");\n\tcbuf->text_start = cbuf->text_end = (cbuf->maxsize >> 1);\n\tcbuf->wait = false;\n\tcbuf->waitCount = 0;\n}\n\nvoid Cbuf_Init (void)\n{\n\tCbuf_Register (&cbuf_main, 1 << 18); // 256kb\n\tCbuf_Register (&cbuf_svc, 1 << 13); // 8kb\n\tCbuf_Register (&cbuf_safe, 1 << 11); // 2kb\n\tCbuf_Register (&cbuf_formatted_comms, 1 << 11); // 2kb\n\tCbuf_Register (&cbuf_server, 1 << 18); // 256kb\n}\n\n//Adds command text at the end of the buffer\nvoid Cbuf_AddTextEx (cbuf_t *cbuf, const char *text)\n{\n\tsize_t new_start, new_bufsize;\n\tsize_t len;\n\n\tlen = strlen (text);\n\n\tif (cbuf->text_end + len <= cbuf->maxsize) {\n\t\tmemcpy (cbuf->text_buf + cbuf->text_end, text, len);\n\t\tcbuf->text_end += len;\n\t\treturn;\n\t}\n\n\tnew_bufsize = cbuf->text_end-cbuf->text_start+len;\n\tif (new_bufsize > cbuf->maxsize) {\n\t\tCom_Printf (\"Cbuf_AddText: overflow\\n\");\n\t\treturn;\n\t}\n\n\t// Calculate optimal position of text in buffer\n\tnew_start = ((cbuf->maxsize - new_bufsize) >> 1);\n\n\tmemcpy (cbuf->text_buf + new_start, cbuf->text_buf + cbuf->text_start, cbuf->text_end-cbuf->text_start);\n\tmemcpy (cbuf->text_buf + new_start + cbuf->text_end-cbuf->text_start, text, len);\n\tcbuf->text_start = new_start;\n\tcbuf->text_end = cbuf->text_start + new_bufsize;\n}\n\n//Adds command text at the beginning of the buffer\nvoid Cbuf_InsertTextEx (cbuf_t *cbuf, const char *text)\n{\n\tsize_t new_start, new_bufsize;\n\tsize_t len;\n\n\tlen = strlen (text);\n\n\tif (len <= cbuf->text_start) {\n\t\tmemcpy (cbuf->text_buf + (cbuf->text_start - len), text, len);\n\t\tcbuf->text_start -= len;\n\t\treturn;\n\t}\n\n\tnew_bufsize = cbuf->text_end - cbuf->text_start + len;\n\tif (new_bufsize > cbuf->maxsize) {\n\t\tCom_Printf (\"Cbuf_InsertText: overflow\\n\");\n\t\treturn;\n\t}\n\n\t// Calculate optimal position of text in buffer\n\tnew_start = ((cbuf->maxsize - new_bufsize) >> 1);\n\n\tmemmove (cbuf->text_buf + (new_start + len), cbuf->text_buf + cbuf->text_start, cbuf->text_end - cbuf->text_start);\n\tmemcpy (cbuf->text_buf + new_start, text, len);\n\tcbuf->text_start = new_start;\n\tcbuf->text_end = cbuf->text_start + new_bufsize;\n}\n\n#define MAX_RUNAWAYLOOP 1000\n\nvoid Cbuf_ExecuteEx (cbuf_t *cbuf)\n{\n\tsize_t i, j;\n\tsize_t cursize, nextsize;\n\tchar *text, line[1024], *src, *dest;\n\tqbool comment;\n\tint quotes;\n\n\tif (cbuf == &cbuf_safe)\n\t\tgtf++;\n\n\tnextsize = cbuf->text_end - cbuf->text_start;\n\n\twhile (cbuf->text_end > cbuf->text_start)\n\t{\n\t\t// find a \\n or ; line break\n\t\ttext = (char *) cbuf->text_buf + cbuf->text_start;\n\n\t\tcursize = cbuf->text_end - cbuf->text_start;\n\t\tcomment = false;\n\t\tquotes = 0;\n\n\t\tfor (i = 0; i < cursize; i++)\n\t\t{\n\t\t\tif (cl_curlybraces.integer)\n\t\t\t{\n\t\t\t\tif (text[i] == '\\\\')\n\t\t\t\t{\n\t\t\t\t\tif (i + 1 < cursize && text[i+1] == '\\n')\n\t\t\t\t\t{ // escaped endline\n\t\t\t\t\t\ttext[i] = text[i+1] = '\\r'; // '\\r' removed later during copying\n\t\t\t\t\t\ti++;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\telse if (i + 2 < cursize && text[i+1] == '\\r' && text[i+2] == '\\n')\n\t\t\t\t\t{ // escaped dos endline\n\t\t\t\t\t\ttext[i] = text[i+2] = '\\r';\n\t\t\t\t\t\ti+=2;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (text[i] == '\\n')\n\t\t\t\tbreak;\n\n\t\t\tif (text[i] == '\"' && quotes <= 0)\n\t\t\t{\n\t\t\t\tif (!quotes)\n\t\t\t\t\tquotes = -1;\n\t\t\t\telse\n\t\t\t\t\tquotes = 0;\n\t\t\t}\n\t\t\telse if (quotes >= 0)\n\t\t\t{\n\t\t\t\tif (cl_curlybraces.integer)\n\t\t\t\t{\n\t\t\t\t\tif (text[i] == '{')\n\t\t\t\t\t\tquotes++;\n\t\t\t\t\telse if (text[i] == '}')\n\t\t\t\t\t\tquotes--;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (comment || quotes)\n\t\t\t\tcontinue;\n\n\t\t\tif (text[i] == '/' && i + 1 < cursize && text[i + 1] == '/')\n\t\t\t\tcomment = true;\n\t\t\telse if (text[i] == ';' && !quotes)\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif ((cursize - i) < nextsize) // have we reached the next command?\n\t\t\tnextsize = cursize - i;\n\n\t\t// don't execute lines without ending \\n; this fixes problems with\n\t\t// partially stuffed aliases not being executed properly\n\n\t\tif (cbuf_current == &cbuf_svc && i == cursize)\n\t\t\tbreak;\n\n\t\t// Copy text to line, skipping carriage return chars\n\t\tsrc = text;\n\t\tdest = line;\n\t\tj = min (i, sizeof (line) - 1);\n\t\tfor ( ; j; j--, src++)\n\t\t{\n\t\t\tif (*src != '\\r')\n\t\t\t\t*dest++ = *src;\n\t\t}\n\t\t*dest = 0;\n\n\t\t// delete the text from the command buffer and move remaining commands down  This is necessary\n\t\t// because commands (exec, alias) can insert data at the beginning of the text buffer\n\t\tif (i == cursize)\n\t\t{\n\t\t\tcbuf->text_start = cbuf->text_end = (cbuf->maxsize >> 1);\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti++;\n\t\t\tcbuf->text_start += i;\n\t\t}\n\n\t\tcursize = cbuf->text_end - cbuf->text_start;\n\n\t\t// TODO: make it in a more right way\n\t\t// since, hud262_add can not correctly create hud elements during normal start\n\t\t// (some cvars are not created/initialized at the time when we want to use them in hud262)\n\t\t// we should save these commands to buffer and execute it when all\n\t\t// cvars will be created\n\t\tif (!host_initialized) {\n\t\t\tHud_262CatchStringsOnLoad(line);\n\t\t}\n\n\t\tCmd_ExecuteStringEx (cbuf, line);\t// execute the command line\n\n\t\tif (cbuf->text_end - cbuf->text_start > cursize)\n\t\t\tcbuf->runAwayLoop++;\n\n\t\tif (cbuf->runAwayLoop > MAX_RUNAWAYLOOP)\n\t\t{\n\t\t\tCom_Printf(\"\\x02\" \"A recursive alias has caused an infinite loop.\");\n\t\t\tCom_Printf(\"\\x02\" \" Clearing execution buffer to prevent lockup.\\n\");\n\t\t\tcbuf->text_start = cbuf->text_end = (cbuf->maxsize >> 1);\n\t\t\tcbuf->runAwayLoop = 0;\n\t\t}\n\n\t\tif (cbuf->wait && cbuf->waitCount >= Rulesets_MaxSequentialWaitCommands())\n\t\t{\n\t\t\tCom_Printf(\"\\x02\" \"Max number of wait commands detected.\\n\");\n\t\t\tcbuf->text_start = cbuf->text_end = (cbuf->maxsize >> 1);\n\t\t\tcbuf->wait = false;\n\t\t\tcbuf->waitCount = 0;\n\t\t}\n\n\t\tif (cbuf->wait)\n\t\t{\n\t\t\t// skip out while text still remains in buffer, leaving it for next frame\n\t\t\tcbuf->wait = false;\n\t\t\t++cbuf->waitCount;\n\n\t\t\tcbuf->runAwayLoop += Q_rint (0.5 * cls.frametime * MAX_RUNAWAYLOOP);\n\n\t\t\tif (cbuf == &cbuf_safe)\n\t\t\t\tgtf--;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (cbuf == &cbuf_safe)\n\t\tgtf--;\n\n\tcbuf->runAwayLoop = 0;\n\tcbuf->waitCount = 0;\n\n\treturn;\n}\n\n/*\n==============================================================================\n\t\t\t\t\t\tSCRIPT COMMANDS\n==============================================================================\n*/\n\n/*\nSet commands are added early, so they are guaranteed to be set before\nthe client and server initialize for the first time.\n\nOther commands are added late, after all initialization is complete.\n*/\nvoid Cbuf_AddEarlyCommands (void)\n{\n\tint i;\n\n\tfor (i = 0; i < COM_Argc () - 2; i++) {\n\t\tif (strcasecmp (COM_Argv(i), \"+set\"))\n\t\t\tcontinue;\n\n\t\tCbuf_AddText (va (\"set %s %s\\n\", COM_Argv (i + 1), COM_Argv (i + 2)));\n\t\ti += 2;\n\t}\n}\n\nqbool Cmd_IsAllowedStuffCmdsCommand(const char *str)\n{\n\tchar*\tbanned_list[] = {\"set \", \"cfg_load \", NULL};\n\tchar**\tbanned_cmd = banned_list;\n\n\twhile(*banned_cmd)\n\t{\n\t\tif(strncasecmp(str, *banned_cmd, strlen(*banned_cmd)) == 0)\n\t\t{\n\t\t\t//+set is processed in Cbuf_AddEarlyCommands(), +cfg_load allowed only once in Host_Init()\n\t\t\tif((strncasecmp(str, \"set \", 4) != 0) && (strncasecmp(str, \"cfg_load \", 9) != 0))\n\t\t\t{\n\t\t\t\tCom_Printf(\"+%s is not allowed in cmdline or obsolete.\\n\", *banned_cmd);\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tbanned_cmd++;\n\t}\n\treturn true;\n}\n\n/*\nAdds command line parameters as script statements\nCommands lead with a +, and continue until a - or another +\nquake +prog jctest.qp +cmd amlev1\nquake -nosound +cmd amlev1\n*/\nvoid Cmd_StuffCmds_f (void)\n{\n\tint k, len = 0;\n\tchar *s, *text, *token;\n\n\t// build the combined string to parse from\n\tfor (k = 1; k < COM_Argc(); k++)\n\t\tlen += strlen (COM_Argv(k)) + 1;\n\n\tif (!len)\n\t\treturn;\n\n\ttext = (char *) Q_malloc(len + 1);\n\ttext[0] = '\\0';\n\tfor (k = 1; k < COM_Argc(); k++) {\n\t\tstrlcat (text, COM_Argv(k), len + 1);\n\t\tif (k != COM_Argc() - 1)\n\t\t\tstrlcat (text, \" \", len + 1);\n\t}\n\n\t// pull out the commands\n\ttoken = (char *) Q_malloc(len + 1);\n\n\ts = text;\n\twhile (*s) {\n\t\tif (*s == '+')\t{\n\t\t\tk = 0;\n\t\t\tfor (s = s + 1; s[0] && (s[0] != ' ' || (s[1] != '-' && s[1] != '+')); s++)\n\t\t\t\ttoken[k++] = s[0];\n\t\t\ttoken[k++] = '\\n';\n\t\t\ttoken[k] = 0;\n\t\t\tif(Cmd_IsAllowedStuffCmdsCommand(token))\n\t\t\t\tCbuf_AddText (token);\n\t\t} else if (*s == '-') {\n\t\t\tfor (s = s + 1; s[0] && s[0] != ' '; s++)\n\t\t\t\t;\n\t\t} else {\n\t\t\ts++;\n\t\t}\n\t}\n\n\tQ_free(text);\n\tQ_free (token);\n}\n\nvoid Cmd_Exec_f (void)\n{\n\tchar *f, name[MAX_OSPATH];\n\tchar reset_bindphysical[128];\n\tqbool server_command = false;\n\n\tif (Rulesets_RestrictExec()) {\n\t\tCom_Printf(\"The use of exec is not allowed during matches\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc () != 2) {\n\t\tCom_Printf (\"%s <filename> : execute a script file\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n#if !defined(SERVERONLY) && !defined(CLIENTONLY)\n\tserver_command = cbuf_current == &cbuf_server || !strcmp(Cmd_Argv(0), \"serverexec\");\n#endif\n\n\tif (CL_IsDownloadableFileExtension(Cmd_Argv(1))) {\n\t\tCom_Printf(\"Warning: \\\"%s\\\" is not allowed to be executed. Remove \\\"%s\\\" from cl_allow_downloads to allow execution\\n\", Cmd_Argv(1), COM_FileExtension(Cmd_Argv(1)));\n\t\treturn;\n\t}\n\n\tstrlcpy (name, Cmd_Argv(1), sizeof(name) - 4);\n\tif (!(f = (char *) FS_LoadHeapFile(name, NULL)))\t{\n\t\tconst char *p;\n\t\tp = COM_SkipPath (name);\n\t\tif (!strchr (p, '.')) {\n\t\t\t// no extension, so try the default (.cfg)\n\t\t\tstrlcat (name, \".cfg\", sizeof (name));\n\t\t\tf = (char *)FS_LoadHeapFile(name, NULL);\n\t\t}\n\t\tif (!f) {\n\t\t\tCom_Printf (\"couldn't exec %s\\n\", Cmd_Argv(1));\n\t\t\treturn;\n\t\t}\n\t}\n\tif (cl_warnexec.integer || developer.integer) {\n\t\tCom_Printf(\"execing %s/%s\\n\", FS_Locate_GetPath(name), name);\n\t}\n\n\t// All config files default to con_bindphysical 1, and would have to over-ride if they\n\t//   want different behaviour.\n\tsprintf(reset_bindphysical, \"\\ncon_bindphysical %d\\n\", con_bindphysical.integer);\n\tif (cbuf_current == &cbuf_svc) {\n\t\tCbuf_AddTextEx(&cbuf_main, \"con_bindphysical 1\\n\");\n\t\tCbuf_AddTextEx(&cbuf_main, f);\n\t\tCbuf_AddTextEx(&cbuf_main, reset_bindphysical);\n\t}\n\telse if (server_command) {\n\t\tCbuf_AddTextEx(&cbuf_server, f);\n\t}\n\telse {\n\t\tCbuf_InsertTextEx(&cbuf_main, reset_bindphysical);\n\t\tCbuf_InsertTextEx(&cbuf_main, f);\n\t\tCbuf_InsertTextEx(&cbuf_main, \"con_bindphysical 1\\n\");\n\t}\n\t\n\tQ_free(f);\n}\n\n//Just prints the rest of the line to the console\n/*void Cmd_Echo_f (void) {\n\tint i;\n\n\tfor (i = 1; i < Cmd_Argc(); i++)\n\t\tCom_Printf (\"%s \", Cmd_Argv(i));\n\tCom_Printf (\"\\n\");\n}*/\nvoid Cmd_Echo_f (void)\n{\n\tint\ti;\n\tchar *str;\n\tchar args[MAX_MACRO_STRING];\n\tchar buf[MAX_MACRO_STRING];\n\n\tmemset (args, 0, MAX_MACRO_STRING);\n\n\tsnprintf (args, MAX_MACRO_STRING, \"%s\", Cmd_Argv(1));\n\n\tfor (i = 2; i < Cmd_Argc(); i++) {\n\t\tstrlcat (args, \" \", MAX_MACRO_STRING);\n\t\tstrlcat (args, Cmd_Argv(i), MAX_MACRO_STRING);\n\t}\n\n\t//\tstr = TP_ParseMacroString(args);\n\n\tstr = TP_ParseMacroString(args);\n\tstr = TP_ParseFunChars(str, false);\n\n\tstrlcpy (buf, str, MAX_MACRO_STRING);\n\n\tCL_SearchForReTriggers (buf, RE_PRINT_ECHO); \t// BorisU\n\tPrint_flags[Print_current] |= PR_TR_SKIP;\n\tCom_Printf (\"%s\\n\", buf);\n}\n\n/*\n=============================================================================\n\t\t\t\t\t\t\t\tALIASES\n=============================================================================\n*/\n#define ALIAS_HASHPOOL_SIZE 256\ncmd_alias_t *cmd_alias_hash[ALIAS_HASHPOOL_SIZE];\ncmd_alias_t\t*cmd_alias;\n\ncmd_alias_t *Cmd_FindAlias (const char *name)\n{\n\tint key;\n\tcmd_alias_t *alias;\n\n\tkey = Com_HashKey (name) % ALIAS_HASHPOOL_SIZE;\n\tfor (alias = cmd_alias_hash[key]; alias; alias = alias->hash_next) {\n\t\tif (!strcasecmp(name, alias->name))\n\t\t\treturn alias;\n\t}\n\treturn NULL;\n}\n\nchar *Cmd_AliasString (char *name)\n{\n\tint key;\n\tcmd_alias_t *alias;\n\n\tkey = Com_HashKey (name) % ALIAS_HASHPOOL_SIZE;\n\tfor (alias = cmd_alias_hash[key]; alias; alias = alias->hash_next) {\n\t\tif (!strcasecmp(name, alias->name)) {\n\t\t\treturn alias->value;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nvoid Cmd_Viewalias_f (void)\n{\n\tcmd_alias_t\t*alias;\n\tchar\t\t*name;\n\tint\t\ti,m;\n\n\tif (Cmd_Argc() < 2) {\n\t\tCom_Printf (\"viewalias <cvar> [<cvar2>..] : view body of alias\\n\");\n\t\treturn;\n\t}\n\n\tfor (i=1; i<Cmd_Argc(); i++) {\n\t\tname = Cmd_Argv(i);\n\n\t\tif (IsRegexp(name)) {\n\t\t\tif (!ReSearchInitEx(name, false)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tCom_Printf (\"Current alias commands:\\n\");\n\n\t\t\tfor (alias = cmd_alias, i = m = 0; alias; alias = alias->next, i++) {\n\t\t\t\tif (ReSearchMatch(alias->name)) {\n\t\t\t\t\tCom_Printf(\"%s : %s\\n\", alias->name, alias->value);\n\t\t\t\t\tm++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tCom_Printf (\"------------\\n%i/%i aliases\\n\", m, i);\n\t\t\tReSearchDone();\n\t\t}\n\t\telse if ((alias = Cmd_FindAlias(name))) {\n\t\t\tCom_Printf(\"%s : \\\"%s\\\"\\n\", Cmd_Argv(i), alias->value);\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"No such alias: %s\\n\", Cmd_Argv(i));\n\t\t}\n\t}\n}\n\n\nint Cmd_AliasCompare (const void *p1, const void *p2)\n{\n\tcmd_alias_t *a1, *a2;\n\n\ta1 = *((cmd_alias_t **) p1);\n\ta2 = *((cmd_alias_t **) p2);\n\n\tif (a1->name[0] == '+') {\n\t\tif (a2->name[0] == '+')\n\t\t\treturn strcasecmp(a1->name + 1, a2->name + 1);\n\t\telse\n\t\t\treturn -1;\n\t} else if (a1->name[0] == '-') {\n\t\tif (a2->name[0] == '+')\n\t\t\treturn 1;\n\t\telse if (a2->name[0] == '-')\n\t\t\treturn strcasecmp(a1->name + 1, a2->name + 1);\n\t\telse\n\t\t\treturn -1;\n\t} else if (a2->name[0] == '+' || a2->name[0] == '-') {\n\t\treturn 1;\n\t} else {\n\t\treturn strcasecmp(a1->name, a2->name);\n\t}\n}\n\nvoid Cmd_AliasList_f (void)\n{\n\tcmd_alias_t *a;\n\tint i, c, m = 0;\n\tstatic int count;\n\tstatic cmd_alias_t *sorted_aliases[4096];\n\n#define MAX_SORTED_ALIASES (sizeof(sorted_aliases) / sizeof(sorted_aliases[0]))\n\n\tfor (a = cmd_alias, count = 0; a && count < MAX_SORTED_ALIASES; a = a->next, count++)\n\t\tsorted_aliases[count] = a;\n\tqsort(sorted_aliases, count, sizeof (cmd_alias_t *), Cmd_AliasCompare);\n\n\tif (count == MAX_SORTED_ALIASES)\n\t\tassert(!\"count == MAX_SORTED_ALIASES\");\n\n\tc = Cmd_Argc();\n\tif (c > 1 && !ReSearchInitEx(Cmd_Argv(1), false)) {\n\t\treturn;\n\t}\n\n\tCom_Printf (\"List of aliases:\\n\");\n\tfor (i = 0; i < count; i++) {\n\t\ta = sorted_aliases[i];\n\t\tif (c==1 || ReSearchMatch(a->name)) {\n\t\t\tCom_Printf (\"\\x02%s :\", sorted_aliases[i]->name);\n\t\t\tCom_Printf (\" %s\\n\", sorted_aliases[i]->value);\n\t\t\tm++;\n\t\t}\n\t}\n\n\tif (c>1)\n\t\tReSearchDone();\n\tCom_Printf (\"------------\\n%i/%i aliases\\n\", m, count);\n}\n\nvoid Cmd_AliasEdit_f (void)\n{\n\tcmd_alias_t\t*a;\n\tchar *s, *v, final_string[MAXCMDLINE - 1];\n\tint c;\n\n\tc = Cmd_Argc();\n\tif (c == 1)\t{\n\t\tCom_Printf (\"%s <name> : modify an alias\\n\", Cmd_Argv(0));\n\t\tCom_Printf (\"aliaslist : list all aliases\\n\");\n\t\treturn;\n\t}\n\n\ts = Cmd_Argv(1);\n\tif (s[0] == '\\0') {\n\t\tCom_Printf(\"Alias name must be specified\\n\");\n\t\treturn;\n\t} else if(strlen(s) >= MAX_ALIAS_NAME) {\n\t\tCom_Printf(\"Alias name is too long\\n\");\n\t\treturn;\n\t}\n\n\ta = Cmd_FindAlias(s);\n\tv = (a ? a->value : \"\");\n\n\tstrlcpy(final_string, \"/alias \\\"\", sizeof(final_string));\n\tstrlcat(final_string, s, sizeof(final_string));\n\tstrlcat(final_string, \"\\\" \\\"\", sizeof(final_string));\n\tstrlcat(final_string, v, sizeof(final_string));\n\tstrlcat(final_string, \"\\\"\", sizeof(final_string));\n\tKey_ClearTyping();\n\tkey_linepos = 9 + (int)strlen(s) + 3; // move to where the commands are in the alias\n\tmemcpy(key_lines[edit_line]+1, str2wcs(final_string), (strlen(final_string) + 1) * sizeof(wchar));\n}\n\nstatic cmd_alias_t* Cmd_AliasCreate (char* name)\n{\n\tcmd_alias_t\t*a;\n\tint key;\n\n\tkey = Com_HashKey(name) % ALIAS_HASHPOOL_SIZE;\n\n\ta = (cmd_alias_t *) Q_malloc(sizeof(cmd_alias_t));\n\ta->next = cmd_alias;\n\tcmd_alias = a;\n\ta->hash_next = cmd_alias_hash[key];\n\tcmd_alias_hash[key] = a;\n\n\tstrlcpy (a->name, name, sizeof (a->name));\n\treturn a;\n}\n\n//Creates a new command that executes a command string (possibly ; separated)\nvoid Cmd_Alias_f (void)\n{\n\tcmd_alias_t\t*a;\n\tchar *s;\n\tint c, key;\n\n\tc = Cmd_Argc();\n\tif (c == 1)\t{\n\t\tCom_Printf(\"%s <name> : show alias content\\n\", Cmd_Argv(0));\n\t\tCom_Printf(\"%s <name> <command> : create or modify an alias\\n\", Cmd_Argv(0));\n\t\tCom_Printf(\"use aliaslist to search for aliases\\n\");\n\t\treturn;\n\t}\n\n\ts = Cmd_Argv(1);\n\tif (strlen(s) >= MAX_ALIAS_NAME) {\n\t\tCom_Printf (\"Alias name is too long\\n\");\n\t\treturn;\n\t}\n\telse if (s[0] == '\\0') {\n\t\tCom_Printf(\"Alias name must be specified\\n\");\n\t\treturn;\n\t}\n\n\tkey = Com_HashKey(s) % ALIAS_HASHPOOL_SIZE;\n\n\t// if the alias already exists, reuse it\n\tfor (a = cmd_alias_hash[key]; a; a = a->hash_next) {\n\t\tif (!strcasecmp(a->name, s)) {\n\t\t\tif (Cmd_Argc() == 2) {\n\t\t\t\tCom_Printf(\"\\x02%s :\", a->name);\n\t\t\t\tCom_Printf(\" %s\\n\", a->value);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tQ_free(a->value);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!a)\t{\n\t\ta = (cmd_alias_t *) Q_malloc(sizeof(cmd_alias_t));\n\t\ta->next = cmd_alias;\n\t\tcmd_alias = a;\n\t\ta->hash_next = cmd_alias_hash[key];\n\t\tcmd_alias_hash[key] = a;\n\t}\n\n\tstrlcpy (a->name, s, sizeof (a->name));\n\n\ta->flags = 0;\n\t// QW262 -->\n\ts=Cmd_MakeArgs(2);\n\twhile (*s) {\n\t\tif (*s == '%' && ( s[1]>='0' || s[1]<='9')) {\n\t\t\ta->flags |= ALIAS_HAS_PARAMETERS;\n\t\t\tbreak;\n\t\t}\n\t\t++s;\n\t}\n\t// <-- QW262\n\n\tif (cbuf_current == &cbuf_svc)\n\t\ta->flags |= ALIAS_SERVER;\n\tif (!strcasecmp(Cmd_Argv(0), \"tempalias\"))\n\t\ta->flags |= ALIAS_TEMP;\n\n\t// copy the rest of the command line\n\ta->value = Q_strdup(Cmd_MakeArgs(2));\n}\n\nqbool Cmd_DeleteAlias (char *name)\n{\n\tcmd_alias_t *a, *prev;\n\tint key;\n\n\tkey = Com_HashKey (name) % ALIAS_HASHPOOL_SIZE;\n\n\tprev = NULL;\n\tfor (a = cmd_alias_hash[key]; a; a = a->hash_next) {\n\t\tif (!strcasecmp(a->name, name)) {\n\t\t\t// unlink from hash\n\t\t\tif (prev)\n\t\t\t\tprev->hash_next = a->hash_next;\n\t\t\telse\n\t\t\t\tcmd_alias_hash[key] = a->hash_next;\n\t\t\tbreak;\n\t\t}\n\t\tprev = a;\n\t}\n\n\tif (!a)\n\t\treturn false;\t// not found\n\n\tprev = NULL;\n\tfor (a = cmd_alias; a; a = a->next) {\n\t\tif (!strcasecmp(a->name, name)) {\n\t\t\t// unlink from alias list\n\t\t\tif (prev)\n\t\t\t\tprev->next = a->next;\n\t\t\telse\n\t\t\t\tcmd_alias = a->next;\n\n\t\t\t// free\n\t\t\tQ_free(a->value);\n\t\t\tQ_free(a);\n\t\t\treturn true;\n\t\t}\n\t\tprev = a;\n\t}\n\n\tassert(!\"Cmd_DeleteAlias: alias list broken\");\n\treturn false; // shut up compiler\n}\n\nvoid Cmd_UnAlias (qbool use_regex)\n{\n\tint \t\ti;\n\tchar\t\t*name;\n\tcmd_alias_t\t*a, *next;\n\tqbool\t\tre_search = false;\n\n\tif (Cmd_Argc() < 2) {\n\t\tCom_Printf (\"unalias <cvar> [<cvar2>..]: erase an existing alias\\n\");\n\t\treturn;\n\t}\n\n\tfor (i=1; i<Cmd_Argc(); i++) {\n\t\tname = Cmd_Argv(i);\n\n\t\tif (use_regex && (re_search = IsRegexp(name))) {\n\t\t\tif (!ReSearchInitEx(name, false)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (strlen(name) >= MAX_ALIAS_NAME) {\n\t\t\tCom_Printf (\"Alias name is too long: \\\"%s\\\"\\n\", Cmd_Argv(i));\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (use_regex && re_search) {\n\t\t\tfor (a = cmd_alias; a; a = next) {\n\t\t\t\tnext = a->next;\n\n\t\t\t\tif (ReSearchMatch(a->name))\n\t\t\t\t\tCmd_DeleteAlias(a->name);\n\t\t\t}\n\t\t} else {\n\t\t\tif (!Cmd_DeleteAlias(Cmd_Argv(i)))\n\t\t\t\tCom_Printf (\"unalias: unknown alias \\\"%s\\\"\\n\", Cmd_Argv(i));\n\t\t}\n\n\t\tif (use_regex && re_search)\n\t\t\tReSearchDone();\n\n\t}\n}\n\nvoid Cmd_UnAlias_f (void)\n{\n\tCmd_UnAlias(false);\n}\n\nvoid Cmd_UnAlias_re_f (void)\n{\n\tCmd_UnAlias(true);\n}\n\n/* \n * Remove all aliases unless connected, then remove\n * all aliases except the server created aliases\n */\nvoid Cmd_UnAliasAll_f (void)\n{\n\tcmd_alias_t\t*a, *next;\n\n/* FIXME: Optimize this, its n^2 slow atm since Cmd_DeleteAlias will loop through\n * the list again for each entry\n */\n\tif (cls.state >= ca_connected) {\n\t\tCom_Printf(\"Connected to a server, will not remove server aliases\\n\");\n\t\tfor (a = cmd_alias; a; a = next) {\n\t\t\tnext = a->next;\n\t\t\tif ((a->flags & ALIAS_SERVER) == 0) {\n\t\t\t\tCmd_DeleteAlias(a->name);\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor (a = cmd_alias; a ; a = next) {\n\t\t\tnext = a->next;\n\t\t\tQ_free(a->value);\n\t\t\tQ_free(a);\n\t\t}\n\t\tcmd_alias = NULL;\n\n\t\t// clear hash\n\t\tmemset (cmd_alias_hash, 0, sizeof(cmd_alias_t*) * ALIAS_HASHPOOL_SIZE);\n\t}\n}\n\nvoid DeleteServerAliases(void)\n{\n\tcmd_alias_t *a, *next;\n\n\tfor (a = cmd_alias; a; a = next) {\n\t\tnext = a->next;\n\n\t\tif (a->flags & ALIAS_SERVER)\n\t\t\tCmd_DeleteAlias (a->name);\n\t}\n}\n\n/*\n=============================================================================\n\t\t\t\t\tLEGACY COMMANDS\n=============================================================================\n*/\n\nlegacycmd_t *legacycmds = NULL;\n\nvoid Cmd_AddLegacyCommand(char *oldname, char *newname)\n{\n\tlegacycmd_t *cmd;\n\tcmd = (legacycmd_t *) Q_malloc(sizeof(legacycmd_t));\n\tcmd->next = legacycmds;\n\tlegacycmds = cmd;\n\n\tcmd->oldname = oldname;\n\tcmd->newname = newname;\n\tcmd->dummy_cmd.name = oldname;\n}\n\nqbool Cmd_IsLegacyCommand (char *oldname)\n{\n\tlegacycmd_t *cmd;\n\n\tfor (cmd = legacycmds; cmd; cmd = cmd->next) {\n\t\tif (!strcasecmp(cmd->oldname, oldname))\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic qbool Cmd_LegacyCommand (void)\n{\n\tstatic qbool recursive = false;\n\tlegacycmd_t *cmd;\n\tchar text[1024];\n\n\tfor (cmd = legacycmds; cmd; cmd = cmd->next) {\n\t\tif (!strcasecmp(cmd->oldname, Cmd_Argv(0)))\n\t\t\tbreak;\n\t}\n\tif (!cmd)\n\t\treturn false;\n\n\tif (!cmd->newname[0])\n\t\treturn true;\t\t// just ignore this command\n\n\t// build new command string\n\tstrlcpy(text, cmd->newname, sizeof(text));\n\tstrlcat(text, \" \", sizeof(text));\n\tstrlcat(text, Cmd_Args(), sizeof(text));\n\n\tif (recursive) {\n\t\tCom_Printf(\"error: recursive legacy command, aborting\");\n\t\trecursive = false;\n\t\treturn false;\n\t}\n\n\trecursive = true;\n\tCmd_ExecuteString(text);\n\trecursive = false;\n\n\treturn true;\n}\n\n/*\n=============================================================================\n\t\t\t\t\tCOMMAND EXECUTION\n=============================================================================\n*/\n\n#define CMD_HASHPOOL_SIZE 512\ncmd_function_t\t*cmd_hash_array[CMD_HASHPOOL_SIZE];\n/*static*/ cmd_function_t\t*cmd_functions;\t\t// possible commands to execute\n\nstatic  tokenizecontext_t cmd_tokenizecontext;\n\nstatic\tchar\t*cmd_null_string = \"\";\n\nint Cmd_ArgcEx (tokenizecontext_t *ctx)\n{\n\treturn ctx->cmd_argc;\n}\n\nchar *Cmd_ArgvEx (tokenizecontext_t *ctx, int arg)\n{\n\tif (arg >= ctx->cmd_argc || arg < 0)\n\t\treturn cmd_null_string;\n\n\treturn ctx->cmd_argv[arg];\n}\n\n// Returns a single string containing argv(1) to argv(argc() - 1)\nchar *Cmd_ArgsEx (tokenizecontext_t *ctx)\n{\n\treturn ctx->cmd_args;\n}\n\n// Returns a single string containing argv(start) to argv(argc() - 1)\n// Unlike Cmd_Args, shrinks spaces between argvs\nchar *Cmd_MakeArgsEx (tokenizecontext_t *ctx, int start)\n{\n\tint i, c;\n\n\tctx->text[0] = 0;\n\tc = Cmd_ArgcEx(ctx);\n\n\tfor (i = start; i < c; i++)\n\t{\n\t\tif (i > start)\n\t\t\tstrlcat (ctx->text, \" \", sizeof (ctx->text) - strlen (ctx->text));\n\n\t\tstrlcat (ctx->text, Cmd_ArgvEx(ctx, i), sizeof (ctx->text) - strlen (ctx->text));\n\t}\n\n\treturn ctx->text;\n}\n\n// Parses the given string into command line tokens.\nvoid Cmd_TokenizeStringEx2(tokenizecontext_t *ctx, const char *text, qbool curlybraces)\n{\n\tint idx = 0, token_len;\n\n\tmemset(ctx, 0, sizeof(*ctx));\n\n\twhile (1)\n\t{\n\t\t// skip whitespace\n\t\twhile (*text == ' ' || *text == '\\t' || *text == '\\r')\n\t\t\ttext++;\n\n\t\t// a newline separates commands in the buffer\n\t\tif (*text == '\\n')\n\t\t\treturn;\n\n\t\tif (!*text)\n\t\t\treturn;\n\n\t\tif (ctx->cmd_argc == 1)\n\t\t\tstrlcpy(ctx->cmd_args, text, sizeof(ctx->cmd_args));\n\n\t\ttext = COM_ParseEx(text, curlybraces);\n\t\tif (!text)\n\t\t\treturn;\n\n\t\tif (ctx->cmd_argc >= MAX_ARGS)\n\t\t\treturn;\n\n\t\ttoken_len = strlen(com_token);\n\n\t\t// ouch ouch, no more space\n\t\tif (idx + token_len + 1 > sizeof(ctx->argv_buf))\n\t\t\treturn;\n\n\t\tctx->cmd_argv[ctx->cmd_argc] = ctx->argv_buf + idx;\n\t\tstrcpy (ctx->cmd_argv[ctx->cmd_argc], com_token);\n\t\tctx->cmd_argc++;\n\n\t\tidx += token_len + 1;\n\t}\n}\n\nvoid Cmd_TokenizeStringEx(tokenizecontext_t* ctx, const char* text)\n{\n\tCmd_TokenizeStringEx2(ctx, text, false);\n}\n\n// and wrappers for backward compatibility\n\nint Cmd_Argc (void)\n{\n\treturn Cmd_ArgcEx(&cmd_tokenizecontext);\n}\n\nchar *Cmd_Argv (int arg)\n{\n\treturn Cmd_ArgvEx(&cmd_tokenizecontext, arg);\n}\n\n//Returns a single string containing argv(1) to argv(argc() - 1)\nchar *Cmd_Args (void)\n{\n\treturn Cmd_ArgsEx(&cmd_tokenizecontext);\n}\n\n//Returns a single string containing argv(start) to argv(argc() - 1)\n//Unlike Cmd_Args, shrinks spaces between argvs\nchar *Cmd_MakeArgs (int start)\n{\n\treturn Cmd_MakeArgsEx(&cmd_tokenizecontext, start);\n}\n\n//Parses the given string into command line tokens.\nvoid Cmd_TokenizeString (const char *text)\n{\n\tCmd_TokenizeStringEx(&cmd_tokenizecontext, text);\n}\n\n// save cmd_tokenizecontext struct to ctx\nvoid Cmd_SaveContext(tokenizecontext_t *ctx)\n{\n\tctx[0] = cmd_tokenizecontext;\n}\n\n// restore cmd_tokenizecontext struct from ctx\nvoid Cmd_RestoreContext(tokenizecontext_t *ctx)\n{\n\tcmd_tokenizecontext = ctx[0];\n}\n\nvoid Cmd_AddCommand (char *cmd_name, xcommand_t function)\n{\n\tcmd_function_t *cmd;\n\tint\tkey;\n\n\t/* commented out when vid_restart was added\n\tif (host_initialized)\t// because hunk allocation would get stomped\n\t\tassert (!\"Cmd_AddCommand after host_initialized\");\n\t*/\n\n/*\t// fail if the command is a variable name\n\tif (Cvar_Find(cmd_name)) {\n\t\tCom_Printf (\"Cmd_AddCommand: %s already defined as a var\\n\", cmd_name);\n\t\treturn;\n\t} */\n\n\tkey = Com_HashKey (cmd_name) % CMD_HASHPOOL_SIZE;\n\n\t// fail if the command already exists\n\tfor (cmd = cmd_hash_array[key]; cmd; cmd=cmd->hash_next) {\n\t\tif (!strcasecmp (cmd_name, cmd->name)) {\n\t\t\tCom_Printf (\"Cmd_AddCommand: %s already defined\\n\", cmd_name);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tcmd = (cmd_function_t *) Hunk_AllocName (sizeof(cmd_function_t), \"cmd\");\n\tcmd->name = cmd_name;\n\tcmd->function = function;\n\tcmd->zmalloced = false;\n\tcmd->next = cmd_functions;\n\tcmd_functions = cmd;\n\tcmd->hash_next = cmd_hash_array[key];\n\tcmd_hash_array[key] = cmd;\n}\n\nqbool Cmd_AddRemCommand (char *cmd_name, xcommand_t function)\n{\n\tcmd_function_t *cmd;\n\tint\tkey;\n\n\tkey = Com_HashKey (cmd_name) % CMD_HASHPOOL_SIZE;\n\n\t// fail if the command already exists\n\tfor (cmd = cmd_hash_array[key]; cmd; cmd=cmd->hash_next) {\n\t\tif (!strcasecmp (cmd_name, cmd->name)) {\n\t\t\tCom_Printf (\"Cmd_AddCommand: %s already defined\\n\", cmd_name);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tcmd = (cmd_function_t*)Q_malloc(sizeof(cmd_function_t)+strlen(cmd_name)+1);\n\tcmd->name = (char*)(cmd+1); // points to extra space created after the structure\n\tstrcpy(cmd->name, cmd_name);\n\tcmd->function = function;\n\tcmd->zmalloced = true;\n\tcmd->next = cmd_functions;\n\tcmd_functions = cmd;\n\tcmd->hash_next = cmd_hash_array[key];\n\tcmd_hash_array[key] = cmd;\n\n\treturn true;\n}\n\n// removes command from the hash map of the commands\ncmd_function_t *Cmd_RemoveCommand_Hash(char *cmd_name)\n{\n\tint key = Com_HashKey (cmd_name) % CMD_HASHPOOL_SIZE;\n\tcmd_function_t *cmd = cmd_hash_array[key];\n\tcmd_function_t *prev = NULL;\n\tcmd_function_t *retval = NULL;\n\n\tif (strcasecmp(cmd_name, cmd->name) == 0) {\n\t\tretval = cmd;\n\t\tcmd_hash_array[key] = cmd_hash_array[key]->hash_next;\n\t}\n\telse\n\t{\n\t\tprev = cmd;\n\t\tfor (cmd = cmd->hash_next; cmd; cmd = cmd->hash_next) {\n\t\t\tif (strcasecmp(cmd_name, cmd->name) == 0) {\n\t\t\t\tretval = cmd;\n\t\t\t\tprev->hash_next = cmd->hash_next;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn retval;\n}\n\n// removes command from the linked list of all commands\ncmd_function_t *Cmd_RemoveCommand_List (char *cmd_name)\n{\n\tcmd_function_t\t*cmd, **back;\n\n\tback = &cmd_functions;\n\twhile (1)\n\t{\n\t\tcmd = *back;\n\t\tif (!cmd)\n\t\t{\n\t\t\treturn NULL;\n\t\t}\n\t\tif (!strcmp (cmd_name, cmd->name))\n\t\t{\n\t\t\t*back = cmd->next;\n\t\t\treturn cmd;\n\t\t}\n\t\tback = &cmd->next;\n\t}\n}\n\n// removes command from all structures and deallocates it\nvoid Cmd_RemoveCommand (char *cmd_name)\n{\n\tcmd_function_t *cmd;\n\n\tcmd = Cmd_RemoveCommand_List(cmd_name);\n\tcmd = Cmd_RemoveCommand_Hash(cmd_name);\n\n\tif (cmd) {\n\t\tif (cmd->zmalloced)\n\t\t{\n\t\t\tQ_free(cmd);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf(\"Cmd_RemoveCommand: %s was not added dynamically\\n\", cmd_name);\n\t\t}\n\t}\n}\n\nqbool Cmd_Exists (char *cmd_name)\n{\n\tint\tkey;\n\tcmd_function_t\t*cmd;\n\n\tkey = Com_HashKey (cmd_name) % CMD_HASHPOOL_SIZE;\n\tfor (cmd=cmd_hash_array[key]; cmd; cmd = cmd->hash_next) {\n\t\tif (!strcasecmp (cmd_name, cmd->name))\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\ncmd_function_t *Cmd_FindCommand (const char *cmd_name)\n{\n\tint\tkey;\n\tcmd_function_t *cmd;\n\n\tkey = Com_HashKey (cmd_name) % CMD_HASHPOOL_SIZE;\n\tfor (cmd = cmd_hash_array[key]; cmd; cmd = cmd->hash_next) {\n\t\tif (!strcasecmp (cmd_name, cmd->name))\n\t\t\treturn cmd;\n\t}\n\treturn NULL;\n}\n\nchar *Cmd_CompleteCommand (char *partial)\n{\n\tcmd_function_t *cmd;\n\tint len;\n\tcmd_alias_t *alias;\n\tlegacycmd_t* legacy_cmd;\n\n\tlen = strlen(partial);\n\n\tif (!len)\n\t\treturn NULL;\n\n\t// check for exact match\n\tfor (cmd = cmd_functions; cmd; cmd = cmd->next)\n\t\tif (!strcasecmp (partial, cmd->name))\n\t\t\treturn cmd->name;\n\tfor (alias = cmd_alias; alias; alias = alias->next)\n\t\tif (!strcasecmp (partial, alias->name))\n\t\t\treturn alias->name;\n\tfor (legacy_cmd = legacycmds; legacy_cmd; legacy_cmd = legacy_cmd->next) {\n\t\tif (!strcasecmp(partial, legacy_cmd->oldname)) {\n\t\t\treturn legacy_cmd->oldname;\n\t\t}\n\t}\n\n\t// check for partial match\n\tfor (cmd = cmd_functions; cmd; cmd = cmd->next)\n\t\tif (!strncasecmp (partial, cmd->name, len))\n\t\t\treturn cmd->name;\n\tfor (alias = cmd_alias; alias; alias = alias->next)\n\t\tif (!strncasecmp (partial, alias->name, len))\n\t\t\treturn alias->name;\n\tfor (legacy_cmd = legacycmds; legacy_cmd; legacy_cmd = legacy_cmd->next) {\n\t\tif (!strncasecmp(partial, legacy_cmd->oldname, len)) {\n\t\t\treturn legacy_cmd->oldname;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nint Cmd_CompleteCountPossible (char *partial)\n{\n\tcmd_function_t *cmd;\n\tlegacycmd_t* legacy_cmd;\n\tint len, c = 0;\n\n\tlen = strlen(partial);\n\tif (!len)\n\t\treturn 0;\n\n\tfor (cmd = cmd_functions; cmd; cmd = cmd->next) {\n\t\tif (!strncasecmp(partial, cmd->name, len)) {\n\t\t\tc++;\n\t\t}\n\t}\n\n\t// Also check legacy commands\n\tfor (legacy_cmd = legacycmds; legacy_cmd; legacy_cmd = legacy_cmd->next) {\n\t\tif (!strncasecmp(partial, legacy_cmd->oldname, len)) {\n\t\t\t++c;\n\t\t}\n\t}\n\n\treturn c;\n}\n\nint Cmd_AliasCompleteCountPossible (char *partial)\n{\n\tcmd_alias_t *alias;\n\tint len, c = 0;\n\n\tlen = strlen(partial);\n\tif (!len)\n\t\treturn 0;\n\n\tfor (alias = cmd_alias; alias; alias = alias->next)\n\t\tif (!strncasecmp (partial, alias->name, len))\n\t\t\tc++;\n\n\treturn c;\n}\n\nint Cmd_CommandCompare (const void *p1, const void *p2)\n{\n\treturn strcmp((*((cmd_function_t **) p1))->name, (*((cmd_function_t **) p2))->name);\n}\n\nvoid Cmd_CmdList (qbool use_regex)\n{\n\tstatic cmd_function_t *sorted_cmds[1024];\n\tint i, c, m = 0, count;\n\tcmd_function_t *cmd;\n\tchar *pattern;\n\n#define MAX_SORTED_CMDS (sizeof (sorted_cmds) / sizeof (sorted_cmds[0]))\n\n\tfor (cmd = cmd_functions, count = 0; cmd && count < MAX_SORTED_CMDS; cmd = cmd->next, count++)\n\t\tsorted_cmds[count] = cmd;\n\tqsort (sorted_cmds, count, sizeof (cmd_function_t *), Cmd_CommandCompare);\n\n\tif (count == MAX_SORTED_CMDS)\n\t\tassert(!\"count == MAX_SORTED_CMDS\");\n\n\tpattern = (Cmd_Argc() > 1) ? Cmd_Argv(1) : NULL;\n\n\tif (((c = Cmd_Argc()) > 1) && use_regex) {\n\t\tif (!ReSearchInitEx(Cmd_Argv(1), false)) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\tCom_Printf (\"List of commands:\\n\");\n\tfor (i = 0; i < count; i++) {\n\t\tcmd = sorted_cmds[i];\n\t\tif (use_regex) {\n\t\t\tif (!(c == 1 || ReSearchMatch (cmd->name)))\n\t\t\t\tcontinue;\n\t\t} else {\n\t\t\tif (pattern && !Q_glob_match (pattern, cmd->name))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tCom_Printf (\"%s\\n\", cmd->name);\n\t\tm++;\n\t}\n\n\tif (use_regex && (c > 1))\n\t\tReSearchDone ();\n\n\tCom_Printf (\"------------\\n%i/%i commands\\n\", m,count);\n}\n\nvoid Cmd_CmdList_f (void)\n{\n\tCmd_CmdList (false);\n}\n\nvoid Cmd_CmdList_re_f (void)\n{\n\tCmd_CmdList (true);\n}\n\nvoid Cmd_ReInitAllMacro(void)\n{\n\tint i;\n\tint teamplay = (int)Rulesets_RestrictTriggers();\n\n\tfor (i = 0; i < num_macros; i++) {\n\t\tif (macro_commands[i].teamplay != MACRO_NORULES) {\n\t\t\tmacro_commands[i].teamplay = teamplay;\n\t\t}\n\t}\n}\n\nvoid Cmd_AddMacroEx(macro_id id, char *(*f) (void), int teamplay)\n{\n\tif (id < 0 || id >= num_macros) {\n\t\treturn;\n\t}\n\n\tmacro_commands[id].func = f;\n\tmacro_commands[id].teamplay = teamplay;\n}\n\nvoid Cmd_AddMacro (macro_id id, char *(*f) (void))\n{\n\tCmd_AddMacroEx(id, f, MACRO_NORULES);\n}\n\nchar *Cmd_MacroString (const char* s, int *macro_length)\n{\n\tint i;\n\tmacro_command_t\t*macro;\n\tint best = -1;\n\tint best_length = -1;\n\n\t*macro_length = 0;\n\tfor (i = 0; i < num_macros; i++) {\n\t\tmacro = &macro_commands[i];\n\t\tif (macro->func) {\n\t\t\tint name_length = strlen(macro->name);\n\t\t\tif (!strncasecmp(s, macro->name, name_length)) {\n\t\t\t\tif (best_length == -1 || best_length < name_length) {\n\t\t\t\t\tbest = i;\n\t\t\t\t\tbest_length = name_length;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (best >= 0) {\n\t\tif (cbuf_current == &cbuf_main && (macro_commands[best].teamplay == MACRO_DISALLOWED)) {\n\t\t\tcbuf_current = &cbuf_formatted_comms;\n\t\t}\n\t\t*macro_length = best_length;\n\t\treturn macro_commands[best].func();\n\t}\n\n\treturn NULL;\n}\n\nstatic int Cmd_MacroCompare (const void *p1, const void *p2)\n{\n\treturn strcmp ((*((macro_command_t **) p1))->name, (*((macro_command_t **) p2))->name);\n}\n\nconst char* Cmd_MacroName(macro_id id)\n{\n\treturn macro_commands[id].name;\n}\n\nqbool Cmd_MacroTeamplayRestricted(macro_id id)\n{\n\treturn macro_commands[id].teamplay;\n}\n\nvoid Cmd_MacroList_f (void)\n{\n\tint i, c, m = 0;\n\tstatic macro_command_t* sorted_macros[num_macros];\n\n\tfor (i = 0; i < num_macros; i++) {\n\t\tsorted_macros[i] = &macro_commands[i];\n\t}\n\tqsort(sorted_macros, num_macros, sizeof (macro_command_t *), Cmd_MacroCompare);\n\n\tc = Cmd_Argc();\n\tif (c > 1 && !ReSearchInitEx(Cmd_Argv(1), false)) {\n\t\treturn;\n\t}\n\n\tCom_Printf (\"List of macros:\\n\");\n\tfor (i = 0; i < num_macros; i++) {\n\t\tif (c==1 || ReSearchMatch (sorted_macros[i]->name)) {\n\t\t\tCom_Printf (\"$%s\\n\", sorted_macros[i]->name);\n\t\t\tm++;\n\t\t}\n\t}\n\n\tif (c > 1) {\n\t\tReSearchDone();\n\t}\n\n\tCom_Printf (\"------------\\n%i/%i macros\\n\", m, num_macros);\n}\n\nvoid TP_SetDefaultMacroFormat(char* cvar_lookup, int* fixed_width, int* alignment);\nchar* TP_AlignMacroText(char* text, int fixed_width, int alignment);\n\n//Expands all $cvar expressions to cvar values\n//Also expands $macro expressions\n//Note: dest must point to a 1024 byte buffer\nvoid Cmd_ExpandString (const char *data, char *dest)\n{\n\tunsigned int c;\n\tchar buf[255], *str;\n\tint i, len = 0, quotes = 0, name_length = 0;\n\tcvar_t *var, *bestvar;\n\tint macro_length;\n\n\twhile ((c = *data)) {\n\t\tif (c == '\"')\n\t\t\tquotes++;\n\n\t\tif (c == '$' && !(quotes & 1)) {\n\t\t\tdata++;\n\n\t\t\t// Copy the text after '$' to a temp buffer\n\t\t\ti = 0;\n\t\t\tbuf[0] = 0;\n\t\t\tbestvar = NULL;\n\t\t\twhile ((c = *data) > 32) {\n\t\t\t\tif (c == '$')\n\t\t\t\t\tbreak;\n\n\t\t\t\tdata++;\n\t\t\t\tbuf[i++] = c;\n\t\t\t\tbuf[i] = 0;\n\n\t\t\t\tif ((var = Cvar_Find(buf)))\n\t\t\t\t\tbestvar = var;\n\n\t\t\t\tif (i >= (int) sizeof (buf) - 1)\n\t\t\t\t\tbreak; // there no more space in buf\n\t\t\t}\n\n\t\t\tstr = Cmd_MacroString (buf, &macro_length);\n\t\t\tname_length = macro_length;\n\n\t\t\tif (bestvar && (!str || (strlen (bestvar->name) > macro_length))) {\n\t\t\t\tstr = bestvar->string;\n\t\t\t\tname_length = strlen(bestvar->name);\n                if (bestvar->teamplay)\n                    cbuf_current = &cbuf_formatted_comms;\n\t\t\t}\n\n\t\t\tif (str) {\n\t\t\t\tint fixed_width = 0;\n\t\t\t\tint alignment = 0;\n\n\t\t\t\tTP_SetDefaultMacroFormat(buf, &fixed_width, &alignment);\n\t\t\t\tif (fixed_width != 0)\n\t\t\t\t\tstr = TP_AlignMacroText(str, fixed_width, alignment);\n\n\t\t\t\t// check buffer size\n\t\t\t\tif (len + strlen (str) >= 1024 - 1)\n\t\t\t\t\tbreak;\n\n\t\t\t\tstrcpy (&dest[len], str);\n\t\t\t\tlen += strlen (str);\n\t\t\t\ti = name_length;\n\t\t\t\twhile (buf[i])\n\t\t\t\t\tdest[len++] = buf[i++];\n\t\t\t} else {\n\t\t\t\t// no matching cvar or macro\n\t\t\t\tdest[len++] = '$';\n\t\t\t\tif (len + strlen (buf) >= 1024 - 1)\n\t\t\t\t\tbreak;\n\n\t\t\t\tstrcpy (&dest[len], buf);\n\t\t\t\tlen += strlen (buf);\n\t\t\t}\n\t\t} else {\n\t\t\tdest[len] = c;\n\t\t\tdata++;\n\t\t\tlen++;\n\t\t\tif (len >= 1024 - 1)\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tdest[len] = 0;\n}\n\nint Commands_Compare_Func (const void * arg1, const void * arg2)\n{\n\treturn strcasecmp (*(char**) arg1, *(char**) arg2);\n}\n\nchar *msgtrigger_commands[] = {\n\t\"play\", \"playvol\", \"stopsound\", \"stopsound_script\", \"set\", \"echo\", \"say\", \"say_team\",\n\t\"alias\", \"unalias\", \"msg_trigger\", \"inc\", \"bind\", \"unbind\", \"record\",\n\t\"easyrecord\", \"stop\", \"if\", \"if_exists\", \"wait\", \"log\", \"match_forcestart\",\n\t\"dns\", \"addserver\", \"connect\", \"join\", \"observe\",\n\t\"tcl_proc\", \"tcl_exec\", \"tcl_eval\", \"exec\",\n\t\"set_ex\", \"set_alias_str\", \"set_bind_str\",\"unset\", \"unset_re\" ,\n\t\"toggle\", \"toggle_re\", \"set_calc\", \"rcon\", \"user\", \"users\",\n\t\"unalias\", \"unalias_re\",\n\t\"re_trigger\", \"re_trigger_options\", \"re_trigger_delete\",\n\t\"re_trigger_enable\",\"re_trigger_disable\", \"re_trigger_match\",\n\t\"hud262_add\",\"hud262_remove\",\"hud262_position\",\"hud262_bg\",\n\t\"hud262_move\",\"hud262_width\",\"hud262_alpha\",\"hud262_blink\",\n\t\"hud262_disable\",\"hud262_enable\",\"hud262_list\",\"hud262_bringtofront\",\n\t\"hud_262font\",\"hud262_hover\",\"hud262_button\",\n\t\"alias_in\", \"alias_out\", \"cvar_in\", \"cvar_out\"\n\t// ,NULL\n};\n\nchar *formatted_comms_commands[] = {\n\t\"if\", \"wait\", \"echo\", \"say\", \"say_team\", \"set_tp\",\n\t\"tp_point\", \"tp_pickup\", \"tp_took\",\n\t\"tp_msgreport\", \"tp_msgcoming\", \"tp_msglost\", \"tp_msgenemypwr\",\n\t\"tp_msgquaddead\", \"tp_msgsafe\", \"tp_msgkillme\", \"tp_msghelp\",\n\t\"tp_msggetquad\", \"tp_msggetpent\", \"tp_msgpoint\", \"tp_msgtook\",\n\t\"tp_msgtrick\", \"tp_msgreplace\", \"tp_msgneed\", \"tp_msgyesok\",\n\t\"tp_msgnocancel\", \"tp_msgutake\", \"tp_msgitemsoon\", \"tp_msgwaiting\",\n\t\"tp_msgslipped\",\n    NULL\n};\n\nfloat\timpulse_time = -9999;\nint\t\timpulse_counter;\n\nqbool AllowedImpulse(int imp)\n{\n\n\tstatic int Allowed_TF_Impulses[] = {\n\t                                   135, 99, 101, 102, 103, 104, 105, 106, 107, 108, 109, 23, 144, 145,\n\t                                   159, 160, 161, 162, 163, 164, 165, 166, 167\n\t                               };\n\n\tint i;\n\n\tif (!cl.teamfortress) return false;\n\tfor (i=0; i<sizeof(Allowed_TF_Impulses)/sizeof(Allowed_TF_Impulses[0]); i++) {\n\t\tif (Allowed_TF_Impulses[i] == imp) {\n\t\t\tif(++impulse_counter >= 30) {\n\t\t\t\tif (cls.realtime < impulse_time + 5 && !cls.demoplayback) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\timpulse_time = cls.realtime;\n\t\t\t\timpulse_counter = 0;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nstatic qbool Cmd_IsCommandAllowedInMessageTrigger( const char *command )\n{\n\tif( !strcasecmp( command, \"impulse\") )\n\t\treturn AllowedImpulse(Q_atoi(Cmd_Argv(1)));\n\n\treturn \t  bsearch( &(command), msgtrigger_commands,\n\t                   sizeof(msgtrigger_commands)/sizeof(msgtrigger_commands[0]),\n\t                   sizeof(msgtrigger_commands[0]),Commands_Compare_Func) != NULL;\n}\n\nstatic qbool Cmd_IsCommandAllowedInTeamPlayMacros( const char *command )\n{\n\tchar **s;\n\tfor (s = formatted_comms_commands; *s; s++) {\n\t\tif (!strcasecmp(command, *s))\n\t\t\tbreak;\n\t}\n\treturn *s != NULL;\n}\n\n//A complete command line has been parsed, so try to execute it\nstatic void Cmd_ExecuteStringEx (cbuf_t *context, char *text)\n{\n\tcvar_t *v;\n\tcmd_function_t *cmd;\n\tcmd_alias_t *a;\n\tstatic char buf[1024];\n\tcbuf_t *inserttarget, *oldcontext;\n\tchar *p, *n, *s;\n\tchar text_exp[1024];\n\tqbool is_server_alias = false;\n\n\toldcontext = cbuf_current;\n\tcbuf_current = context;\n\n\tCmd_ExpandString(text, text_exp);\n\tCmd_TokenizeStringEx2(&cmd_tokenizecontext, text_exp, cl_curlybraces.integer);\n\n\tif (!Cmd_Argc())\n\t\tgoto done; // no tokens\n\n\tif (cbuf_current == &cbuf_svc) {\n\t\tif (CL_CheckServerCommand())\n\t\t\tgoto done;\n\t}\n\n#ifndef CLIENTONLY\n\t// 'status' on remote ktx servers..\n\tif (!strcmp(Cmd_Argv(0), \"status\") && Cmd_Argc() == 1 && Cmd_FindAlias(\"status\")) {\n\t\tgoto checkaliases;\n\t}\n#endif\n\n\t// check functions\n\tif ((cmd = Cmd_FindCommand(Cmd_Argv(0)))) {\n\t\tif (gtf || cbuf_current == &cbuf_safe) {\n\t\t\tif (!Cmd_IsCommandAllowedInMessageTrigger(Cmd_Argv(0))) {\n\t\t\t\tCom_Printf (\"\\\"%s\\\" cannot be used in message triggers\\n\", Cmd_Argv(0));\n\t\t\t\tgoto done;\n\t\t\t}\n\t\t} else if (cbuf_current == &cbuf_formatted_comms) {\n\t\t\tif (!Cmd_IsCommandAllowedInTeamPlayMacros(Cmd_Argv(0))) {\n\t\t\t\tCom_Printf (\"\\\"%s\\\" cannot be used in combination with teamplay $macros\\n\", Cmd_Argv(0));\n\t\t\t\tgoto done;\n\t\t\t}\n\t\t}\n\n\t\tif (cmd->function) {\n\t\t\tif (cbuf_current == &cbuf_svc && !Hash_Get(rc_hash, cmd->name)) {\n\t\t\t\tCom_Printf(\"Blocked %s: not in cl_remote_capabilities\\n\", cmd->name);\n\t\t\t\tgoto done;\n\t\t\t}\n\n\t\t\tcmd->function();\n\t\t}\n\t\telse {\n\t\t\tCmd_ForwardToServer ();\n\t\t}\n\t\tgoto done;\n\t}\n\n\t// some bright guy decided to use \"skill\" as a mod command in Custom TF, sigh\n\tif (!strcmp(Cmd_Argv(0), \"skill\") && Cmd_Argc() == 1 && Cmd_FindAlias(\"skill\"))\n\t\tgoto checkaliases;\n\n\t// check cvars\n\tif ((v = Cvar_Find(Cmd_Argv(0)))) {\n\t\tif (cbuf_current == &cbuf_svc && !Hash_Get(rc_hash, v->name)) {\n\t\t\tCom_Printf(\"Blocked %s: not in cl_remote_capabilities\\n\", v->name);\n\t\t\tgoto done;\n\t\t}\n\n\t\tif (cbuf_current == &cbuf_formatted_comms) {\n\t\t\tCom_Printf (\"\\\"%s\\\" cannot be used in combination with teamplay $macros\\n\", Cmd_Argv(0));\n\t\t\tgoto done;\n\t\t}\n\t\tif (Cvar_Command())\n\t\t\tgoto done;\n\t}\n\n\t// check aliases\ncheckaliases:\n\tif ((a = Cmd_FindAlias(Cmd_Argv(0)))) {\n\t\tis_server_alias = a->flags & ALIAS_SERVER;\n\n\t\t// QW262 -->\n\t\tif (a->value[0] == '\\0') {\n\t\t\tgoto done; // alias is empty.\n\t\t}\n\n\t\tif(a->flags & ALIAS_HAS_PARAMETERS) { // %parameters are given in alias definition\n\t\t\ts=a->value;\n\t\t\tbuf[0] = '\\0';\n\t\t\tdo {\n\t\t\t\tn = strchr(s, '%');\n\t\t\t\tif(n) {\n\t\t\t\t\tif(*++n >= '1' && *n <= '9') {\n\t\t\t\t\t\tn[-1] = 0;\n\t\t\t\t\t\tstrlcat(buf, s, sizeof(buf));\n\t\t\t\t\t\tn[-1] = '%';\n\t\t\t\t\t\t// insert numbered parameter\n\t\t\t\t\t\tstrlcat(buf,Cmd_Argv(*n-'0'), sizeof(buf));\n\t\t\t\t\t} else if (*n == '0') {\n\t\t\t\t\t\tn[-1] = 0;\n\t\t\t\t\t\tstrlcat(buf, s, sizeof(buf));\n\t\t\t\t\t\tn[-1] = '%';\n\t\t\t\t\t\t// insert all parameters\n\t\t\t\t\t\tstrlcat(buf, Cmd_Args(), sizeof(buf));\n\t\t\t\t\t} else if (*n == '%') {\n\t\t\t\t\t\tn[0] = 0;\n\t\t\t\t\t\tstrlcat(buf, s, sizeof(buf));\n\t\t\t\t\t\tn[0] = '%';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (*n) {\n\t\t\t\t\t\t\tchar tmp = n[1];\n\t\t\t\t\t\t\tn[1] = 0;\n\t\t\t\t\t\t\tstrlcat(buf, s, sizeof(buf));\n\t\t\t\t\t\t\tn[1] = tmp;\n\t\t\t\t\t\t} else\n\t\t\t\t\t\t\tstrlcat(buf, s, sizeof(buf));\n\t\t\t\t\t}\n\t\t\t\t\ts=n+1;\n\t\t\t\t}\n\t\t\t} while(n);\n\t\t\tstrlcat(buf, s, sizeof(buf));\n\t\t\tp = buf;\n\n\t\t} else  // alias has no parameters\n\t\t\tp = a->value;\n\t\t// <-- QW262\n\n\t\tif (cbuf_current == &cbuf_svc)\n\t\t{\n\t\t\tinserttarget = is_server_alias ? &cbuf_svc : &cbuf_main;\n\t\t\tCbuf_AddTextEx (inserttarget, p);\n\t\t\tCbuf_AddTextEx (inserttarget, \"\\n\");\n\t\t} else\n\t\t{\n\t\t\tif (is_server_alias)\n\t\t\t\tinserttarget = &cbuf_svc;\n\t\t\telse\n\t\t\t\tinserttarget = cbuf_current ? cbuf_current : &cbuf_main;\n\n\t\t\tCbuf_InsertTextEx (inserttarget, \"\\n\");\n\n\t\t\t// if the alias value is a command or cvar and\n\t\t\t// the alias is called with parameters, add them\n\t\t\tif (Cmd_Argc() > 1 && !strchr(p, ' ') && !strchr(p, '\\t') &&\n\t\t\t        (Cvar_Find(p) || (Cmd_FindCommand(p) && p[0] != '+' && p[0] != '-'))\n\t\t\t   ) {\n\t\t\t\tCbuf_InsertTextEx (inserttarget, Cmd_Args());\n\t\t\t\tCbuf_InsertTextEx (inserttarget, \" \");\n\t\t\t}\n\t\t\tCbuf_InsertTextEx (inserttarget, p);\n\t\t}\n\t\tgoto done;\n\t}\n\n\tif (Cmd_LegacyCommand())\n\t\tgoto done;\n\n\tif (!host_initialized && Cmd_Argc() > 1) {\n\t\tif (Cvar_CreateTempVar())\n\t\t\tgoto done;\n\t}\n\n\tif (cbuf_current != &cbuf_svc)\n\t{\n\t\tif (cl_warncmd.integer || developer.integer)\n\t\t\tCom_Printf (\"Unknown command \\\"%s\\\"\\n\", Cmd_Argv(0));\n\t}\n\ndone:\n\tcbuf_current = oldcontext;\n}\n\nvoid Cmd_ExecuteString (char *text)\n{\n\tCmd_ExecuteStringEx (NULL, text);\n}\n\nstatic qbool is_numeric (char *c)\n{\n\treturn ( isdigit((int)(unsigned char)*c) ||\n\t         ((*c == '-' || *c == '+') && (c[1] == '.' || isdigit((int)(unsigned char)c[1]))) ||\n\t         (*c == '.' && isdigit((int)(unsigned char)c[1])) );\n}\n\nvoid Re_Trigger_Copy_Subpatterns (const char *s, size_t* offsets, int num, cvar_t *re_sub); // QW262\nextern cvar_t re_sub[10]; // QW262\n\nvoid Cmd_CatchTriggerSubpatterns(const char *s, size_t* offsets, int num)\n{\n\tRe_Trigger_Copy_Subpatterns(s, offsets, min(num, 10), re_sub);\n}\n\n// this is a test replacement of the \"if\" command\nvoid Cmd_If_New(void)\n{\n\t// syntax of this command has two possibilities:\n\t// if (<expr>) then <cmd1> [else <cmd2>]\n\t// if o1 o2 o3 [then] <cmd1> [else <cmd2>]\n\t// the second one is for backward compatibility\n\tparser_extra pars_ex;\n\tint c;\n\tint then_pos = 0;\n\tqbool then_found = false, else_found = false;\n\tint i, clen;\n\tsize_t expr_len = 0;\n\tchar* expr, * curarg;\n\tint result, error;\n\tchar buf[1024];\n\n\tpars_ex.subpatt_fnc = Cmd_CatchTriggerSubpatterns;\n\tpars_ex.var2val_fnc = NULL;\n\n\tc = Cmd_Argc();\n\n\t// 0  1 2    3\n\t// if e then c\n\tif (c < 4) {\n\t\tCom_Printf(\"Usage: if <expr> then <cmds> [else <cmds>]\\n\");\n\t\treturn;\n\t}\n\n\tfor (i = 2; i < c; i++) {\n\t\tif (!strcmp(Cmd_Argv(i), \"then\")) {\n\t\t\tthen_pos = i; then_found = true; break;\n\t\t}\n\t}\n\n\tif (!then_found) {\n\t\tCom_Printf(\"if command: \\\"then\\\" not found\\n\");\n\t\treturn;\n\t}\n\n\tfor (i = 1; i < then_pos; i++) {\n\t\tclen = strlen(Cmd_Argv(i));\n\t\texpr_len += clen ? clen + 1 : 3; // we will take '' as a representation of an empty string\n\t}\n\n\texpr = (char *) Q_malloc(expr_len + 1);\n\texpr[0] = '\\0';\n\n\tfor (i = 1; i < then_pos; i++) {\n\t\tif (i > 1)\n\t\t\tstrlcat(expr, \" \", expr_len + 1);\n\n\t\tcurarg = Cmd_Argv(i);\n\n\t\tif (*curarg)\n\t\t\tstrlcat(expr, curarg, expr_len + 1);\n\t\telse\n\t\t\tstrlcat(expr, \"''\", expr_len + 1);\n\t}\n\n\terror = Expr_Eval_Bool(expr, &pars_ex, &result);\n\tif (error != EXPR_EVAL_SUCCESS) {\n\t\tCom_Printf(\"Error in condition: %s (\\\"%s\\\")\\n\", Parser_Error_Description(error), expr);\n\t\tQ_free(expr);\n\t\treturn;\n\t}\n\n\tQ_free(expr);\n\n\tthen_pos++;\t// skip \"then\"\n\n\tbuf[0] = '\\0';\n\tif (result)\t// true case\n\t{\n\t\tfor (i = then_pos; i < c; i++) {\n\t\t\tif (!else_found && !strcmp(Cmd_Argv(i), \"else\"))\n\t\t\t\tbreak;\n\n\t\t\tif (buf[0])\n\t\t\t\tstrlcat (buf, \" \", sizeof(buf));\n\n\t\t\tstrlcat (buf, Cmd_Argv(i), sizeof(buf));\n\t\t}\n\t}\n\telse // result = false\n\t{\n\t\tfor (i = then_pos; i < c; i++) {\n\t\t\tif (else_found) {\n\t\t\t\tif (buf[0])\n\t\t\t\t\tstrlcat (buf, \" \", sizeof(buf));\n\t\t\t\tstrlcat (buf, Cmd_Argv(i), sizeof(buf));\n\t\t\t}\n\t\t\tif (!else_found && !strcmp(Cmd_Argv(i), \"else\")) else_found = true;\n\t\t}\n\t}\n\n\tstrlcat (buf, \"\\n\", sizeof(buf));\n\tCbuf_InsertTextEx (cbuf_current ? cbuf_current : &cbuf_main, buf);\n}\n\nvoid Cmd_If_Old (void)\n{\n\tint\ti, c;\n\tchar *op, buf[1024] = {0};\n\tqbool result;\n\n\tif ((c = Cmd_Argc()) < 5) {\n\t\tCom_Printf (\"Usage: if <expr1> <op> <expr2> <command> [else <command>]\\n\");\n\t\treturn;\n\t}\n\n\top = Cmd_Argv(2);\n\tif (!strcmp(op, \"==\") || !strcmp(op, \"=\") || !strcmp(op, \"!=\") || !strcmp(op, \"<>\")) {\n\t\tif (is_numeric(Cmd_Argv(1)) && is_numeric(Cmd_Argv(3)))\n\t\t\tresult = Q_atof(Cmd_Argv(1)) == Q_atof(Cmd_Argv(3));\n\t\telse\n\t\t\tresult = !strcmp(Cmd_Argv(1), Cmd_Argv(3));\n\n\t\tif (op[0] != '=')\n\t\t\tresult = !result;\n\t} else if (!strcmp(op, \">\")) {\n\t\tresult = Q_atof(Cmd_Argv(1)) > Q_atof(Cmd_Argv(3));\n\t} else if (!strcmp(op, \"<\")) {\n\t\tresult = Q_atof(Cmd_Argv(1)) < Q_atof(Cmd_Argv(3));\n\t} else if (!strcmp(op, \">=\")) {\n\t\tresult = Q_atof(Cmd_Argv(1)) >= Q_atof(Cmd_Argv(3));\n\t} else if (!strcmp(op, \"<=\")) {\n\t\tresult = Q_atof(Cmd_Argv(1)) <= Q_atof(Cmd_Argv(3));\n\n\t} else if (!strcmp(op, \"isin\")) {\n\t\tresult = (strstr(Cmd_Argv(3), Cmd_Argv(1)) ? 1 : 0);\n\t} else if (!strcmp(op, \"!isin\")) {\n\t\tresult = (strstr(Cmd_Argv(3), Cmd_Argv(1)) ? 0 : 1);\n\n\t} else if (!strcmp(op, \"=~\") || !strcmp(op, \"!~\")) {\n\t\tpcre2_code       *regexp;\n\t\tint              error;\n\t\tPCRE2_SIZE       error_offset;\n\t\tpcre2_match_data *match_data = NULL;\n\t\tint              rc;\n\n\t\tregexp = pcre2_compile ((PCRE2_SPTR)Cmd_Argv(3), PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL);\n\t\tif (!regexp) {\n\t\t\tPCRE2_UCHAR error_str[256];\n\t\t\tpcre2_get_error_message(error, error_str, sizeof(error_str));\n\t\t\tCom_Printf (\"Error in regexp: %s\\n\", error_str);\n\t\t\treturn;\n\t\t}\n\t\tmatch_data = pcre2_match_data_create_from_pattern(regexp, NULL);\n\t\trc = pcre2_match (regexp, (PCRE2_SPTR)Cmd_Argv(1), strlen(Cmd_Argv(1)),\n\t\t                0, 0, match_data, NULL);\n\t\tif (rc >= 0) {\n\t\t\tPCRE2_SIZE *offsets = pcre2_get_ovector_pointer(match_data);\n\t\t\tRe_Trigger_Copy_Subpatterns (Cmd_Argv(1), offsets, min(rc, 10), re_sub);\n\t\t\tresult = true;\n\t\t} else\n\t\t\tresult = false;\n\n\t\tif (op[0] != '=')\n\t\t\tresult = !result;\n\n\t\tpcre2_match_data_free (match_data);\n\t\tpcre2_code_free (regexp);\n\t} else {\n\t\tCom_Printf (\"unknown operator: %s\\n\", op);\n\t\tCom_Printf (\"valid operators are ==, =, !=, <>, >, <, >=, <=, isin, !isin, =~, !~\\n\");\n\t\treturn;\n\t}\n\n\tif (result)\t{\n\t\tfor (i = 4; i < c; i++) {\n\t\t\tif ((i == 4) && !strcasecmp(Cmd_Argv(i), \"then\"))\n\t\t\t\tcontinue;\n\t\t\tif (!strcasecmp(Cmd_Argv(i), \"else\"))\n\t\t\t\tbreak;\n\t\t\tif (buf[0])\n\t\t\t\tstrlcat (buf, \" \", sizeof (buf) - strlen (buf) - 1);\n\n\t\t\tstrlcat (buf, Cmd_Argv(i), sizeof (buf) - strlen (buf) - 1);\n\t\t}\n\t} else {\n\t\tfor (i = 4; i < c ; i++) {\n\t\t\tif (!strcasecmp(Cmd_Argv(i), \"else\"))\n\t\t\t\tbreak;\n\t\t}\n\t\tif (i == c)\n\t\t\treturn;\n\t\tfor (i++; i < c; i++) {\n\t\t\tif (buf[0])\n\t\t\t\tstrlcat (buf, \" \", sizeof (buf) - strlen (buf) - 1);\n\n\t\t\tstrlcat (buf, Cmd_Argv(i), sizeof (buf) - strlen (buf) - 1);\n\t\t}\n\t}\n\n\tstrlcat (buf, \"\\n\", sizeof (buf) - strlen (buf));\n\tCbuf_InsertTextEx (cbuf_current ? cbuf_current : &cbuf_main, buf);\n}\n\nvoid Cmd_If_f(void) {\n\tif (Cmd_Argc() > 2 && Cmd_Argv(1)[0] == '(')\n\t\t // new \"if\" requires parentheses around the condition\n\t\t // while the original \"if\" wouldn't work with these so it's safe\n\t\t // to presume noone used it there\n\t\t Cmd_If_New();\n\telse Cmd_If_Old();\n}\n\nvoid Cmd_If_Exists_f(void)\n{\n\tint\targc;\n\tchar\t*type;\n\tchar\t*name;\n\tqbool\texists;\n\tqbool\tiscvar, isalias, istrigger, ishud;\n\n\targc = Cmd_Argc();\n\tif ( argc < 4 || argc > 5) {\n\t\tCom_Printf (\"if_exists <type> <name> <cmd1> [<cmd2>] - conditional execution\\n\");\n\t\treturn;\n\t}\n\n\ttype = Cmd_Argv(1);\n\tname = Cmd_Argv(2);\n\tif ( ( (iscvar = !strcmp(type, \"cvar\")) && Cvar_Find(name) )\t\t\t||\n\t        ( (isalias = !strcmp(type, \"alias\")) && Cmd_FindAlias (name) )\t\t\t||\n\t        ( (istrigger = !strcmp(type, \"trigger\")) && CL_FindReTrigger (name) )\t||\n\t        ( (ishud = !strcmp(type, \"hud\")) && Hud_ElementExists (name) ) )\n\t\texists = true;\n\telse {\n\t\texists = false;\n\t\tif (!(iscvar || isalias || istrigger || ishud)) {\n\t\t\tCom_Printf(\"if_exists: <type> can be cvar, alias, trigger, hud\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (exists) {\n\t\tCbuf_InsertTextEx (cbuf_current ? cbuf_current : &cbuf_main,\"\\n\");\n\t\tCbuf_InsertTextEx (cbuf_current ? cbuf_current : &cbuf_main,Cmd_Argv(3));\n\t} else if (argc == 5) {\n\t\tCbuf_InsertTextEx (cbuf_current ? cbuf_current : &cbuf_main,\"\\n\");\n\t\tCbuf_InsertTextEx (cbuf_current ? cbuf_current : &cbuf_main,Cmd_Argv(4));\n\t} else\n\t\treturn;\n}\n\nvoid Cmd_Eval_f(void)\n{\n\tint errn;\n\texpr_val value;\n\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"Usage: eval <expression>\\n\"\n\t\t\t\"Prints the value of given expression after evaluation in the internal parser\\n\");\n\t\treturn;\n\t}\n\n\tvalue = Expr_Eval(Cmd_Argv(1), NULL, &errn);\n\n\tif (errn != EXPR_EVAL_SUCCESS)\n\t{\n\t\tCom_Printf(\"Error occured: %s\\n\", Parser_Error_Description(errn));\n\t\treturn;\n\t}\n\telse\n\t{\n\t\tswitch (value.type) {\n\t\tcase ET_INT:  Com_Printf(\"Result: %i (integer)\\n\", value.i_val); break;\n\t\tcase ET_DBL:  Com_Printf(\"Result: %f (double)\\n\",  value.d_val); break;\n\t\tcase ET_BOOL: Com_Printf(\"Result: %s (bool)\\n\", value.b_val ? \"true\" : \"false\"); break;\n\t\tcase ET_STR:  Com_Printf(\"Result: (string)\\n\\\"%s\\\"\\n\", value.s_val); Q_free(value.s_val); break;\n\t\tdefault:      Com_Printf(\"Error: Unknown value type\\n\"); break;\n\t\t}\n\t}\n}\n\n// QW262 -->\nstatic qbool do_in(char *buf, char *orig, char *str, int options)\n{\n\tif ((options & 2) && strstr(orig, str))\n\t\treturn false;\n\n\tif (options & 1) { // buf size is 1024 both in Cmd_Alias_In_f and Cmd_Cvar_In_f\n\t\tstrlcpy(buf, orig, 1024);\n\t\tstrlcat(buf, str, 1024);\n\t} else {\n\t\tstrlcpy(buf, str, 1024);\n\t\tstrlcat(buf, orig, 1024);\n\t}\n\treturn true;\n}\n\nstatic qbool do_out(char *orig, char *str, int options)\n{\n\tchar\t*p;\n\tint\t\tlen = strlen(str);\n\n\tif (!(p=strstr(orig, str)))\n\t\treturn false;\n\n\tif (!(options & 1))\n\t\tmemmove(p, p+len, strlen(p)+1);\n\treturn true;\n}\n\nvoid Cmd_Alias_In_f (void)\n{\n\tcmd_alias_t\t*alias;\n\tcvar_t\t\t*var;\n\tchar\t\tbuf[1024];\n\tchar\t\t*alias_name;\n\tint\t\t\toptions;\n\n\tif (Cmd_Argc() < 3 || Cmd_Argc() > 4) {\n\t\tCom_Printf (\"alias_in <alias> <cvar> [<options>]\\n\");\n\t\treturn;\n\t}\n\n\talias_name = Cmd_Argv(1);\n\talias = Cmd_FindAlias(alias_name);\n\toptions = atoi(Cmd_Argv(3));\n\n\tif (!alias) {\n\t\tif ((options & 8)) {\n\t\t\talias = Cmd_AliasCreate(alias_name);\n\t\t\tif (!alias)\n\t\t\t\treturn;\n\t\t\talias->value = Q_strdup(\"\");\n\t\t} else {\n\t\t\tCom_Printf (\"alias_in: unknown alias \\\"%s\\\"\\n\", alias_name);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tvar = Cvar_Find(Cmd_Argv(2));\n\tif (!var) {\n\t\tCom_Printf (\"alias_in: unknown cvar \\\"%s\\\"\\n\", Cmd_Argv(2));\n\t\treturn;\n\t}\n\n\tif (!do_in (buf, alias->value, var->string, options)) {\n\t\tif (options & 4)\n\t\t\tCom_Printf (\"alias_in: already inserted\\n\");\n\t\treturn;\n\t}\n\n\tQ_free(alias->value);\n\talias->value = Q_strdup(buf);\n\tif (strchr(buf, '%'))\n\t\talias->flags |= ALIAS_HAS_PARAMETERS;\n}\n\nvoid Cmd_Alias_Out_f (void)\n{\n\tcmd_alias_t\t*alias;\n\tcvar_t\t\t*var;\n\tint\t\t\toptions;\n\n\tif (Cmd_Argc() < 3 || Cmd_Argc() > 4) {\n\t\tCom_Printf (\"alias_out <alias> <cvar> [options]\\n\");\n\t\treturn;\n\t}\n\n\toptions = atoi(Cmd_Argv(3));\n\n\talias = Cmd_FindAlias(Cmd_Argv(1));\n\tif (!alias) {\n\t\tCom_Printf (\"alias_out: unknown alias \\\"%s\\\"\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tvar = Cvar_Find(Cmd_Argv(2));\n\tif (!var) {\n\t\tCom_Printf (\"alias_out: unknown cvar \\\"%s\\\"\\n\", Cmd_Argv(2));\n\t\treturn;\n\t}\n\n\tif (!do_out (alias->value, var->string, options)) {\n\t\tif (!(options & 2))\n\t\t\tCom_Printf (\"alias_out: not found\\n\");\n\t\treturn;\n\t}\n}\n\nvoid Cmd_Cvar_In_f (void)\n{\n\tcvar_t\t\t*var1;\n\tcvar_t\t\t*var2;\n\tchar\t\tbuf[1024];\n\tchar\t\t*var_name;\n\tint\t\t\toptions;\n\n\tif (Cmd_Argc() < 3 || Cmd_Argc() > 4) {\n\t\tCom_Printf (\"cvar_in <cvar1> <cvar2> [<options>]\\n\");\n\t\treturn;\n\t}\n\n\tvar_name = Cmd_Argv(1);\n\toptions = atoi(Cmd_Argv(3));\n\n\tvar1 = Cvar_Find(var_name);\n\tif (!var1) {\n\t\tif ((options & 8)) {\n\t\t\tvar1 = Cvar_Create (var_name, \"\", 0);\n\t\t} else {\n\t\t\tCom_Printf (\"cvar_in: unknown cvar \\\"%s\\\"\\n\", var_name);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tvar2 = Cvar_Find(Cmd_Argv(2));\n\tif (!var2) {\n\t\tCom_Printf (\"cvar_in: unknown cvar \\\"%s\\\"\\n\", Cmd_Argv(2));\n\t\treturn;\n\t}\n\n\tif (!do_in (buf, var1->string, var2->string, options)) {\n\t\tif (options & 4)\n\t\t\tCom_Printf (\"cvar_in: already inserted\\n\");\n\t\treturn;\n\t}\n\n\tCvar_Set (var1, buf);\n}\n\nvoid Cmd_Cvar_Out_f (void)\n{\n\tcvar_t\t\t*var1;\n\tcvar_t\t\t*var2;\n\tchar\t\tbuf[1024];\n\tint\t\t\toptions;\n\n\tif (Cmd_Argc()<3 || Cmd_Argc()>4){\n\t\tCom_Printf (\"cvar_out <cvar1> <cvar2> [<options>]\\n\");\n\t\treturn;\n\t}\n\n\toptions = atoi(Cmd_Argv(3));\n\tvar1 = Cvar_Find(Cmd_Argv(1));\n\tif (!var1) {\n\t\tCom_Printf (\"cvar_out: unknown cvar \\\"%s\\\"\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tvar2 = Cvar_Find(Cmd_Argv(2));\n\tif (!var2) {\n\t\tCom_Printf (\"cvar_out: unknown cvar \\\"%s\\\"\\n\", Cmd_Argv(2));\n\t\treturn;\n\t}\n\n\tstrcpy (buf, var1->string);\n\tif (!do_out (buf, var2->string, options)) {\n\t\tif (!(options & 2))\n\t\t\tCom_Printf (\"cvar_out: not found\\n\");\n\t\treturn;\n\t}\n\n\tCvar_Set (var1, buf);\n}\n// <-- QW262\n\n\nvoid Cmd_Init (void)\n{\n\t// register our commands\n\tCmd_AddCommand (\"exec\", Cmd_Exec_f);\n#ifndef SERVERONLY\n\tCmd_AddCommand (\"serverexec\", Cmd_Exec_f);\n#endif\n\tCmd_AddCommand (\"echo\", Cmd_Echo_f);\n\tCmd_AddCommand (\"aliaslist\", Cmd_AliasList_f);\n\tCmd_AddCommand (\"aliasedit\", Cmd_AliasEdit_f);\n\tCmd_AddCommand (\"alias\", Cmd_Alias_f);\n\tCmd_AddCommand (\"tempalias\", Cmd_Alias_f);\n\tCmd_AddCommand (\"viewalias\", Cmd_Viewalias_f);\n\tCmd_AddCommand (\"unaliasall\", Cmd_UnAliasAll_f);\n\tCmd_AddCommand (\"unalias\", Cmd_UnAlias_f);\n\tCmd_AddCommand (\"unalias_re\", Cmd_UnAlias_re_f);\n\tCmd_AddCommand (\"wait\", Cmd_Wait_f);\n\tCmd_AddCommand (\"cmdlist\", Cmd_CmdList_f);\n\tCmd_AddCommand (\"cmdlist_re\", Cmd_CmdList_re_f);\n\tCmd_AddCommand (\"if\", Cmd_If_f);\n\tCmd_AddCommand (\"if_exists\", Cmd_If_Exists_f);\n\tCmd_AddCommand (\"eval\", Cmd_Eval_f);\n\tCmd_AddCommand (\"clipboard\", Cmd_Clipboard_f);\n// QW262 -->\n\tCmd_AddCommand (\"alias_in\", Cmd_Alias_In_f);\n\tCmd_AddCommand (\"alias_out\", Cmd_Alias_Out_f);\n\tCmd_AddCommand (\"cvar_in\", Cmd_Cvar_In_f);\n\tCmd_AddCommand (\"cvar_out\", Cmd_Cvar_Out_f);\n// <-- QW262\n\n\tCvar_Register(&cl_curlybraces);\n\tCvar_Register(&cl_warnexec);\n\tCvar_Register(&cl_remote_capabilities);\n\tCvar_Register(&cl_allow_downloads);\n\tCvar_Register(&cl_allow_uploads);\n\n\tCmd_AddCommand (\"macrolist\", Cmd_MacroList_f);\n\tqsort(msgtrigger_commands,\n\t      sizeof(msgtrigger_commands)/sizeof(msgtrigger_commands[0]),\n\t      sizeof(msgtrigger_commands[0]),Commands_Compare_Func);\n}\n\nvoid Cmd_Shutdown(void)\n{\n\tint i;\n\tcmd_alias_t* alias;\n\tcmd_alias_t* next_alias;\n\tcmd_function_t* cmd;\n\tcmd_function_t* next_cmd;\n\tlegacycmd_t* legacycmd;\n\tlegacycmd_t* next_legacycmd;\n\n\tSys_Printf(\"Cmd_Shutdown(aliases)\\n\");\n\tfor (i = 0; i < sizeof(cmd_alias_hash) / sizeof(cmd_alias_hash[0]); ++i) {\n\t\tcmd_alias_hash[i] = NULL;\n\t}\n\n\tfor (alias = cmd_alias; alias; alias = next_alias) {\n\t\tnext_alias = alias->next;\n\t\tQ_free(alias->value);\n\t\tQ_free(alias);\n\t}\n\tcmd_alias = NULL;\n\n\tSys_Printf(\"Cmd_Shutdown(functions)\\n\");\n\tfor (cmd = cmd_functions; cmd; cmd = next_cmd) {\n\t\tnext_cmd = cmd->next;\n\n\t\tif (cmd->zmalloced) {\n\t\t\tQ_free(cmd);\n\t\t}\n\t}\n\tcmd_functions = NULL;\n\n\tSys_Printf(\"Cmd_Shutdown(legacy)\\n\");\n\tfor (legacycmd = legacycmds; legacycmd; legacycmd = next_legacycmd) {\n\t\tnext_legacycmd = legacycmd->next;\n\t\tQ_free(legacycmd);\n\t}\n}\n"
  },
  {
    "path": "src/cmd.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n// cmd.h -- Command buffer and command execution\n\n//===========================================================================\n\n/*\nAny number of commands can be added in a frame, from several different sources.\nMost commands come from either keybindings or console line input, but remote\nservers can also send across commands and entire text files can be execed.\n\nThe + command line options are also added to the command buffer.\n\nThe game starts with a Cbuf_AddText (\"exec quake.rc\\n\"); Cbuf_Execute ();\n*/\n\ntypedef struct cbuf_s {\n\tchar*   text_buf;\n\tsize_t  maxsize;\n\tsize_t  text_start;\n\tsize_t  text_end;\n\tqbool   wait;\n\tint     waitCount;\n\tint     runAwayLoop;\n} cbuf_t;\n\nextern cbuf_t cbuf_main;\nextern cbuf_t cbuf_safe; // msg_trigger commands\nextern cbuf_t cbuf_formatted_comms;\nextern cbuf_t cbuf_svc; // svc_stufftext commands\nextern cbuf_t cbuf_server;  // mod commands\nextern cbuf_t *cbuf_current;\n\nvoid Cbuf_AddTextEx (cbuf_t *cbuf, const char *text);\nvoid Cbuf_InsertTextEx (cbuf_t *cbuf, const char *text);\nvoid Cbuf_ExecuteEx (cbuf_t *cbuf);\n\nvoid Cbuf_Init (void);\n// allocates an initial text buffer that will grow as needed\n\nvoid Cbuf_AddText (const char *text);\n// as new commands are generated from the console or keybindings,\n// the text is added to the end of the command buffer.\n\nvoid Cbuf_InsertText (const char *text);\n// when a command wants to issue other commands immediately, the text is\n// inserted at the beginning of the buffer, before any remaining unexecuted\n// commands.\n\nvoid Cbuf_Execute (void);\n// Pulls off \\n terminated lines of text from the command buffer and sends\n// them through Cmd_ExecuteString.  Stops when the buffer is empty.\n// Normally called once per frame, but may be explicitly invoked.\n// Do not call inside a command function!\n\nvoid Cbuf_Flush(cbuf_t* cbuf);\n// Intended for startup... keeps executing until empty\n\n//===========================================================================\n\n/*\nCommand execution takes a null terminated string, breaks it into tokens,\nthen searches for a command or variable that matches the first token.\n*/\n\ntypedef void (*xcommand_t) (void);\n\ntypedef struct cmd_function_s {\n\tstruct cmd_function_s\t*hash_next;\n\tstruct cmd_function_s\t*next;\n\tchar\t\t\t\t\t*name;\n\txcommand_t\t\t\t\tfunction;\n\tqbool                   zmalloced;\n} cmd_function_t;\n\nvoid Cmd_Init (void);\nvoid Cmd_Shutdown (void);\n\nvoid Cmd_AddCommand (char *cmd_name, xcommand_t function);\n// called by the init functions of other parts of the program to\n// register commands and functions to call for them.\n// The cmd_name is referenced later, so it should not be in temp memory\n// if function is NULL, the command will be forwarded to the server\n// as a clc_stringcmd instead of executed locally\n\n// plugins use it; command is Q_malloced, name is stored within the same memory piece\nqbool Cmd_AddRemCommand (char *cmd_name, xcommand_t function);\n\n// only used by plugins, other commands stay in the client forever\nvoid Cmd_RemoveCommand (char *cmd_name);\n\nqbool Cmd_Exists (char *cmd_name);\n// used by the cvar code to check for cvar / command name overlap\n\ncmd_function_t *Cmd_FindCommand (const char *cmd_name);  // for message triggers\n\nchar *Cmd_CompleteCommand (char *partial);\n// attempts to match a partial command for automatic command line completion\n// returns NULL if nothing fits\n\n#define\tMAX_ARGS\t\t80\n\ntypedef struct tokenizecontext_s\n{\n\tint\t\tcmd_argc; // arguments count\n\tchar\t*cmd_argv[MAX_ARGS]; // links to argv_buf[]\n\n\t// FIXME: MAX_COM_TOKEN not defined here, need redesign headers or something\n\n\tchar\targv_buf[/*MAX_COM_TOKEN*/ 1024]; // here we store data for *cmd_argv[]\n\n\tchar\tcmd_args[/*MAX_COM_TOKEN*/ 1024 * 2]; // here we store original of what we parse, from argv(1) to argv(argc() - 1)\n\n\tchar\ttext[/*MAX_COM_TOKEN*/ 1024]; // this is used/overwrite each time we using Cmd_MakeArgs()\n\n} tokenizecontext_t;\n\nint Cmd_ArgcEx (tokenizecontext_t *ctx);\nchar *Cmd_ArgvEx (tokenizecontext_t *ctx, int arg);\n\n//Returns a single string containing argv(1) to argv(argc() - 1)\nchar *Cmd_ArgsEx (tokenizecontext_t *ctx);\n\n//Returns a single string containing argv(start) to argv(argc() - 1)\n//Unlike Cmd_Args, shrinks spaces between argvs\nchar *Cmd_MakeArgsEx (tokenizecontext_t *ctx, int start);\n\n//Parses the given string into command line tokens.\nvoid Cmd_TokenizeStringEx (tokenizecontext_t *ctx, const char *text);\n\nint\tCmd_Argc (void);\nchar *Cmd_Argv (int arg);\nchar *Cmd_Args (void);\nchar *Cmd_MakeArgs (int start);\n\n// save cmd_tokenizecontext struct to ctx\nvoid Cmd_SaveContext(tokenizecontext_t *ctx);\n\n// restore cmd_tokenizecontext struct from ctx\nvoid Cmd_RestoreContext(tokenizecontext_t *ctx);\n\n// The functions that execute commands get their parameters with these\n// functions. Cmd_Argv () will return an empty string, not a NULL\n// if arg > argc, so string operations are always safe.\n\nvoid Cmd_ExpandString (const char *data, char *dest);\n// Expands all $cvar or $macro expressions.\n// dest should point to a 1024-byte buffer\n\nvoid Cmd_TokenizeString (const char *text);\n// Takes a null terminated string.  Does not need to be /n terminated.\n// breaks the string up into arg tokens.\n\nvoid Cmd_ExecuteString (char *text);\n// Parses a single line of text into arguments and tries to execute it\n// as if it was typed at the console\n\nvoid Cmd_ForwardToServer (void);\n// adds the current command line as a clc_stringcmd to the client message.\n// things like godmode, noclip, etc, are commands directed to the server,\n// so when they are typed in at the console, they will need to be forwarded.\n\nvoid Cbuf_AddEarlyCommands (void);\nvoid Cmd_StuffCmds_f (void);\nqbool Cmd_IsLegacyCommand (char *oldname);\nvoid Cmd_AddLegacyCommand (char *oldname, char *newname);\nqbool CL_IsDownloadableFileExtension(const char *filename);\n\n//===========================================================================\n\n#define\tMAX_ALIAS_NAME 32\n\n#define ALIAS_ARCHIVE\t\t\t1\n#define ALIAS_SERVER\t\t\t2\n#define ALIAS_TEMP\t\t\t\t4\n#define\tALIAS_HAS_PARAMETERS\t8\n\ntypedef struct cmd_alias_s {\n\tstruct cmd_alias_s\t*hash_next;\n\tstruct cmd_alias_s\t*next;\n\tchar\t\t\t\tname[MAX_ALIAS_NAME];\n\tchar\t\t\t\t*value;\n\tint\t\t\t\t\tflags;\n} cmd_alias_t;\n\nqbool Cmd_DeleteAlias (char *name);\t// return true if successful\ncmd_alias_t *Cmd_FindAlias (const char *name); // returns NULL on failure\nchar *Cmd_AliasString (char *name); // returns NULL on failure\n\nvoid DeleteServerAliases (void);\n\n#define\tMAX_MACRO_NAME 32\n#define MACRO_NORULES -1\n#define MACRO_ALLOWED 0\n#define MACRO_DISALLOWED 1\n\n#include \"macro_definitions.h\"\n\nvoid Cmd_AddMacro(macro_id id, char *(*f)(void));\nvoid Cmd_AddMacroEx(macro_id id, char *(*f)(void), int teamplay);\nchar* Cmd_MacroString(const char *s, int *macro_length);\nconst char* Cmd_MacroName(macro_id id);\nqbool Cmd_MacroTeamplayRestricted(macro_id id);\n\n// only here for enumerating from keys.c (tab-complete) - move to cmd.c\ntypedef struct legacycmd_s\n{\n\tchar* oldname, * newname;\n\tcmd_function_t dummy_cmd;\n\tstruct legacycmd_s* next;\n} legacycmd_t;\n\nextern legacycmd_t* legacycmds;\n"
  },
  {
    "path": "src/cmdline_params.h",
    "content": "\n#ifndef EZQUAKE_CMDLINE_PARAMS_H\n#define EZQUAKE_CMDLINE_PARAMS_H\n\n#define CMDLINE_DEF(x, str) cmdline_param_ ## x\n\ntypedef enum {\n#include \"cmdline_params_ids.h\"\n\tnum_cmdline_params\n} cmdline_param_id;\n\n#undef CMDLINE_DEF\n\n#endif // EZQUAKE_CMDLINE_PARAMS_H\n\n"
  },
  {
    "path": "src/cmdline_params_ids.h",
    "content": "\nCMDLINE_DEF(client_nosound, \"-nosound\"),\nCMDLINE_DEF(client_democache, \"-democache\"),\nCMDLINE_DEF(client_norjscripts, \"-norjscripts\"),\nCMDLINE_DEF(client_noscripts, \"-noscripts\"),\nCMDLINE_DEF(client_noindphys, \"-noindphys\"),\nCMDLINE_DEF(client_nomultitexturing, \"-nomtex\"),\nCMDLINE_DEF(client_no_npot_textures, \"-nonpot\"),\nCMDLINE_DEF(client_notriplebuffering, \"-no-triple-gl-buffer\"),\nCMDLINE_DEF(client_noinverselightmaps, \"-noinvlmaps\"),\nCMDLINE_DEF(client_forwardonlyprofile, \"-gl-forward-only-profile\"),\nCMDLINE_DEF(client_detailtrails, \"-detailtrails\"),\nCMDLINE_DEF(client_maximum2textureunits, \"-maxtmu2\"),\nCMDLINE_DEF(client_no24bittextures, \"-no24bit\"),\nCMDLINE_DEF(client_forcetexturereload, \"-forcetexturereload\"),\nCMDLINE_DEF(client_showlibraryerrors, \"-showliberrors\"),\nCMDLINE_DEF(client_ruleset, \"-ruleset\"),\nCMDLINE_DEF(client_particlecount, \"-particles\"),\nCMDLINE_DEF(client_nohardwaretimers, \"-nohwtimer\"),\nCMDLINE_DEF(client_allowmultipleclients, \"-allowmultiple\"),\nCMDLINE_DEF(client_unaccelerated_visuals, \"-no-accel-visuals\"),\nCMDLINE_DEF(client_printopenglextensions, \"-gl_ext\"),\nCMDLINE_DEF(client_gamma, \"-gamma\"),\nCMDLINE_DEF(client_nohardwaregamma, \"-nohwgamma\"),\nCMDLINE_DEF(client_oldgammabehaviour, \"-oldgamma\"),\nCMDLINE_DEF(client_windowedmode, \"-window\"),\nCMDLINE_DEF(client_startwindowed, \"-startwindowed\"),\nCMDLINE_DEF(client_video_frequency, \"-freq\"),\nCMDLINE_DEF(client_video_bpp, \"-bpp\"),\nCMDLINE_DEF(client_video_width, \"-width\"),\nCMDLINE_DEF(client_video_height, \"-height\"),\nCMDLINE_DEF(client_video_displaynumber, \"-display\"),\nCMDLINE_DEF(client_video_conwidth, \"-conwidth\"),\nCMDLINE_DEF(client_video_conheight, \"-conheight\"),\nCMDLINE_DEF(client_video_glsl_renderer, \"-glsl-renderer\"),\nCMDLINE_DEF(client_video_r_debug, \"-r-debug\"),\nCMDLINE_DEF(client_video_r_trace, \"-r-trace\"),\nCMDLINE_DEF(client_video_r_dump_shaders, \"-r-dump-shaders\"),\nCMDLINE_DEF(client_nostdinput, \"-noconinput\"),\nCMDLINE_DEF(client_nostdoutput, \"-nostdout\"),\nCMDLINE_DEF(client_nolibpng, \"-nolibpng\"),\nCMDLINE_DEF(client_nolibjpeg, \"-nolibjpeg\"),\nCMDLINE_DEF(client_video_nodesktopres, \"-nodesktopres\"),\nCMDLINE_DEF(client_cd_audio, \"-cdaudio\"),\nCMDLINE_DEF(client_cd_device, \"-cddev\"),\nCMDLINE_DEF(client_noatlas, \"-noatlas\"),\nCMDLINE_DEF(client_verify_glstate, \"-r-verify\"),\nCMDLINE_DEF(client_novao, \"-r-novao\"),\nCMDLINE_DEF(client_nocallback, \"-r-nocallback\"),\nCMDLINE_DEF(client_nomultibind, \"-r-nomultibind\"),\nCMDLINE_DEF(client_no_amd_fix, \"-r-no-amd-fix\"),\n\nCMDLINE_DEF(filesystem_basedir, \"-basedir\"),\nCMDLINE_DEF(filesystem_nohome, \"-nohome\"),\nCMDLINE_DEF(filesystem_userdir, \"-userdir\"),\nCMDLINE_DEF(filesystem_game, \"-game\"),\nCMDLINE_DEF(filesystem_data, \"-data\"),\n\nCMDLINE_DEF(host_memory_minimum, \"-minmemory\"),\nCMDLINE_DEF(host_memory_kb, \"-heapsize\"),\nCMDLINE_DEF(host_memory_mb, \"-mem\"),\n\nCMDLINE_DEF(net_ipaddress, \"-ip\"),\nCMDLINE_DEF(net_clientport, \"-clientport\"),\nCMDLINE_DEF(net_serverport, \"-port\"),\n\nCMDLINE_DEF(console_buffersize, \"-conbufsize\"),\nCMDLINE_DEF(console_debug, \"-condebug\"),\nCMDLINE_DEF(developer_mode, \"-dev\"),\n\nCMDLINE_DEF(server_progtype, \"-progtype\"),\nCMDLINE_DEF(server_enablecheats, \"-cheats\"),\nCMDLINE_DEF(server_enablelocalcommand, \"-enablelocalcommand\"),\nCMDLINE_DEF(server_democache_kb, \"-democache\"),\n// leave trailing ,\n\n"
  },
  {
    "path": "src/cmodel.c",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// cmodel.c - collision model.\n\n#ifdef SERVERONLY\n#include \"qwsvdef.h\"\n#else\n#include \"common.h\"\n#include \"cvar.h\"\n#endif\n\ntypedef struct cnode_s {\n\t// common with leaf\n\tint                contents; // 0, to differentiate from leafs\n\tstruct cnode_s     *parent;\n\n\t// node specific\n\tmplane_t           *plane;\n\tstruct cnode_s     *children[2];\t\n} cnode_t;\n\ntypedef struct cleaf_s {\n\t// common with node\n\tint                contents;         // a negative contents number\n\tstruct cnode_s     *parent;\n\n\t// leaf specific\n\tbyte               ambient_sound_level[NUM_AMBIENTS];\n} cleaf_t;\n\nstatic char\t\t\tloadname[32];\t// for hunk tags\n\nstatic char\t\t\tmap_name[MAX_QPATH];\nstatic unsigned int\tmap_checksum, map_checksum2;\n\nstatic int\t\t\tnumcmodels;\nstatic cmodel_t\t\tmap_cmodels[MAX_MAP_MODELS];\n\nstatic mplane_t\t\t*map_planes;\nstatic int\t\t\tnumplanes;\n\nstatic cnode_t\t\t*map_nodes;\nstatic int\t\t\tnumnodes;\n\nstatic mclipnode_t\t*map_clipnodes;\nstatic int\t\t\tnumclipnodes;\n\nstatic cleaf_t\t\t*map_leafs;\nstatic int\t\t\tnumleafs;\nstatic int\t\t\tvisleafs;\n\nstatic byte\t\t\tmap_novis[MAX_MAP_LEAFS/8];\n\nstatic byte\t\t\t*map_pvs;\t\t\t\t\t// fully expanded and decompressed\nstatic byte\t\t\t*map_phs;\t\t\t\t\t// only valid if we are the server\nstatic int\t\t\tmap_vis_rowbytes;\t\t\t// for both pvs and phs\nstatic int\t\t\tmap_vis_rowlongs;\t\t\t// map_vis_rowbytes / 4\n\nstatic char\t\t\t*map_entitystring;\n\nstatic qbool\t\tmap_halflife;\n\nstatic mphysicsnormal_t* map_physicsnormals;     // must be same number as clipnodes to save reallocations in worst case scenario\n\n// lumps immediately follow:\ntypedef struct {\n\tchar lumpname[24];\n\tint fileofs;\n\tint filelen;\n} bspx_lump_t;\n\nstatic bspx_lump_t* CM_LoadBSPX(vfsfile_t* vf, dheader_t* header, bspx_header_t *xheader);\nstatic byte* CM_BSPX_ReadLump(vfsfile_t* vf, bspx_header_t *xheader, bspx_lump_t* lump, char* lumpname, int* plumpsize);\n\n/*\n===============================================================================\n\nHULL BOXES\n\n===============================================================================\n*/\n\nstatic hull_t\t\tbox_hull;\nstatic mclipnode_t\tbox_clipnodes[6];\nstatic mplane_t\t\tbox_planes[6];\n\n/*\n** CM_InitBoxHull\n**\n** Set up the planes and clipnodes so that the six floats of a bounding box\n** can just be stored out and get a proper hull_t structure.\n*/\nstatic void CM_InitBoxHull(void)\n{\n\tint side, i;\n\n\tbox_hull.clipnodes = box_clipnodes;\n\tbox_hull.planes = box_planes;\n\tbox_hull.firstclipnode = 0;\n\tbox_hull.lastclipnode = 5;\n\n\tfor (i = 0; i < 6; i++) {\n\t\tbox_clipnodes[i].planenum = i;\n\t\tside = i & 1;\n\t\tbox_clipnodes[i].children[side] = CONTENTS_EMPTY;\n\t\tbox_clipnodes[i].children[side ^ 1] = (i != 5) ? (i + 1) : CONTENTS_SOLID;\n\t\tbox_planes[i].type = i >> 1;\n\t\tbox_planes[i].normal[i >> 1] = 1;\n\t}\n}\n\n/*\n** CM_HullForBox\n**\n** To keep everything totally uniform, bounding boxes are turned into small\n** BSP trees instead of being compared directly.\n*/\nhull_t *CM_HullForBox (vec3_t mins, vec3_t maxs)\n{\n\tbox_planes[0].dist = maxs[0];\n\tbox_planes[1].dist = mins[0];\n\tbox_planes[2].dist = maxs[1];\n\tbox_planes[3].dist = mins[1];\n\tbox_planes[4].dist = maxs[2];\n\tbox_planes[5].dist = mins[2];\n\n\treturn &box_hull;\n}\n\nint CM_CachedHullPointContents(hull_t* hull, int num, vec3_t p, float* min_dist)\n{\n\tmclipnode_t* node;\n\tmplane_t* plane;\n\tfloat d;\n\n\t*min_dist = 999;\n\twhile (num >= 0) {\n\t\tif (num < hull->firstclipnode || num > hull->lastclipnode) {\n\t\t\tif (map_halflife && num == hull->lastclipnode + 1) {\n\t\t\t\treturn CONTENTS_EMPTY;\n\t\t\t}\n\t\t\tSys_Error(\"CM_HullPointContents: bad node number\");\n\t\t}\n\n\t\tnode = hull->clipnodes + num;\n\t\tplane = hull->planes + node->planenum;\n\n\t\td = PlaneDiff(p, plane);\n\t\tif (d < 0) {\n\t\t\t*min_dist = min(*min_dist, -d);\n\t\t\tnum = node->children[1];\n\t\t}\n\t\telse {\n\t\t\t*min_dist = min(*min_dist, d);\n\t\t\tnum = node->children[0];\n\t\t}\n\t}\n\n\treturn num;\n}\n\nint CM_HullPointContents(hull_t *hull, int num, vec3_t p)\n{\n\tmclipnode_t *node;\n\tmplane_t *plane;\n\tfloat d;\n\n\twhile (num >= 0) {\n\t\tif (num < hull->firstclipnode || num > hull->lastclipnode) {\n\t\t\tif (map_halflife && num == hull->lastclipnode + 1) {\n\t\t\t\treturn CONTENTS_EMPTY;\n\t\t\t}\n\t\t\tSys_Error(\"CM_HullPointContents: bad node number\");\n\t\t}\n\n\t\tnode = hull->clipnodes + num;\n\t\tplane = hull->planes + node->planenum;\n\n\t\td = PlaneDiff(p, plane);\n\t\tnum = (d < 0) ? node->children[1] : node->children[0];\n\t}\n\n\treturn num;\n}\n\n/*\n===============================================================================\n\nLINE TESTING IN HULLS\n\n===============================================================================\n*/\n\n// 1/32 epsilon to keep floating point happy\n#define\tDIST_EPSILON\t0.03125\n\nenum { TR_EMPTY, TR_SOLID, TR_BLOCKED };\n\ntypedef struct {\n\thull_t *hull;\n\ttrace_t\ttrace;\n\tint leafcount;\n} hulltrace_local_t;\n\n//====================\nint RecursiveHullTrace (hulltrace_local_t *htl, int num, float p1f, float p2f, const vec3_t p1, const vec3_t p2)\n{\n\tmplane_t\t*plane;\n\tfloat\t\tt1, t2;\n\tmclipnode_t *node;\n\tint\t\t\ti;\n\tint\t\t\tnearside;\n\tfloat\t\tfrac, midf;\n\tvec3_t\t\tmid;\n\tint\t\t\tcheck, oldcheck;\n\thull_t *hull = htl->hull;\n\ttrace_t *trace = &htl->trace;\n\nstart:\n\tif (num < 0) {\n\t\t// this is a leaf node\n\t\thtl->leafcount++;\n\t\tif (num == CONTENTS_SOLID) {\n\t\t\tif (htl->leafcount == 1)\n\t\t\t\ttrace->startsolid = true;\n\t\t\treturn TR_SOLID;\n\t\t}\n\t\telse {\n\t\t\tif (num == CONTENTS_EMPTY)\n\t\t\t\ttrace->inopen = true;\n\t\t\telse\n\t\t\t\ttrace->inwater = true;\n\t\t\treturn TR_EMPTY;\n\t\t}\n\t}\n\n\t// FIXME, check at load time\n\tif (num < hull->firstclipnode || num > hull->lastclipnode)\n\t{\n\t\tif (map_halflife && num == hull->lastclipnode + 1)\n\t\t\treturn TR_EMPTY;\n\t\tSys_Error (\"RecursiveHullTrace: bad node number\");\n\t}\n\n\tnode = hull->clipnodes + num;\n\n\t//\n\t// find the point distances\n\t//\n\tplane = hull->planes + node->planenum;\n\n\tif (plane->type < 3) {\n\t\tt1 = p1[plane->type] - plane->dist;\n\t\tt2 = p2[plane->type] - plane->dist;\n\t}\n\telse {\n\t\tt1 = DotProduct (plane->normal, p1) - plane->dist;\n\t\tt2 = DotProduct (plane->normal, p2) - plane->dist;\n\t}\n\n\t// see which sides we need to consider\n\tif (t1 >= 0 && t2 >= 0) {\n\t\tnum = node->children[0];\t// go down the front side\n\t\tgoto start;\n\t}\n\tif (t1 < 0 && t2 < 0) {\n\t\tnum = node->children[1];\t// go down the back side\n\t\tgoto start;\n\t}\n\n\t// find the intersection point\n\tfrac = t1 / (t1 - t2);\n\tfrac = bound (0, frac, 1);\n\tmidf = p1f + (p2f - p1f)*frac;\n\tfor (i = 0; i < 3; i++)\n\t\tmid[i] = p1[i] + frac*(p2[i] - p1[i]);\n\n\t// move up to the node\n\tnearside = (t1 < t2) ? 1 : 0;\n\tcheck = RecursiveHullTrace (htl, node->children[nearside], p1f, midf, p1, mid);\n\tif (check == TR_BLOCKED)\n\t\treturn check;\n\n\t// if we started in solid, allow us to move out to an empty area\n\tif (check == TR_SOLID && (trace->inopen || trace->inwater))\n\t\treturn check;\n\toldcheck = check;\n\n\t// go past the node\n\tcheck = RecursiveHullTrace (htl, node->children[1 - nearside], midf, p2f, mid, p2);\n\tif (check == TR_EMPTY || check == TR_BLOCKED)\n\t\treturn check;\n\n\tif (oldcheck != TR_EMPTY)\n\t\treturn check;\t// still in solid\n\n\t// near side is empty, far side is solid\n\t// this is the impact point\n\tif (!nearside) {\n\t\tVectorCopy (plane->normal, trace->plane.normal);\n\t\ttrace->plane.dist = plane->dist;\n\t}\n\telse {\n\t\tVectorNegate (plane->normal, trace->plane.normal);\n\t\ttrace->plane.dist = -plane->dist;\n\t}\n\n\t// put the final point DIST_EPSILON pixels on the near side\n\tif (t1 < t2)\n\t\tfrac = (t1 + DIST_EPSILON) / (t1 - t2);\n\telse\n\t\tfrac = (t1 - DIST_EPSILON) / (t1 - t2);\n\tfrac = bound (0, frac, 1);\n\tmidf = p1f + (p2f - p1f)*frac;\n\tfor (i = 0; i < 3; i++)\n\t\tmid[i] = p1[i] + frac*(p2[i] - p1[i]);\n\n\ttrace->fraction = midf;\n\tVectorCopy (mid, trace->endpos);\n\ttrace->physicsnormal = (!nearside ? num + 1 : -(num + 1));\n\n\treturn TR_BLOCKED;\n}\n\ntrace_t CM_HullTrace (hull_t *hull, vec3_t start, vec3_t end)\n{\n\tint check;\n\n\t// this structure is passed as a pointer to RecursiveHullTrace\n\t// so as not to use much stack but still be thread safe\n\thulltrace_local_t htl;\n\thtl.hull = hull;\n\thtl.leafcount = 0;\n\t// fill in a default trace\n\tmemset (&htl.trace, 0, sizeof(htl.trace));\n\thtl.trace.fraction = 1;\n\thtl.trace.startsolid = false;\n\tVectorCopy (end, htl.trace.endpos);\n\n\tcheck = RecursiveHullTrace (&htl, hull->firstclipnode, 0, 1, start, end);\n\n\tif (check == TR_SOLID) {\n\t\thtl.trace.startsolid = htl.trace.allsolid = true;\n\t\t// it would be logical to set fraction to 0, but original id code\n\t\t// would leave it at 1.   We emulate that just in case.\n\t\t// (FIXME: is it just QW, or NQ as well?)\n\t\t//htl.trace.fraction = 0;\n\t\tVectorCopy (start, htl.trace.endpos);\n\t}\n\n\treturn htl.trace;\n}\n\n//===========================================================================\n\nint\tCM_NumInlineModels (void)\n{\n\treturn numcmodels;\n}\n\nchar *CM_EntityString (void)\n{\n\treturn map_entitystring;\n}\n\nint CM_Leafnum (const cleaf_t *leaf)\n{\n\tassert (leaf);\n\treturn leaf - map_leafs;\n}\n\nint\tCM_LeafAmbientLevel (const cleaf_t *leaf, int ambient_channel)\n{\n\tassert ((unsigned)ambient_channel <= NUM_AMBIENTS);\n\tassert (leaf);\n\n\treturn leaf->ambient_sound_level[ambient_channel];\n}\n\n// always returns a valid cleaf_t pointer\ncleaf_t *CM_PointInLeaf (const vec3_t p)\n{\n\tfloat d;\n\tcnode_t *node;\n\tmplane_t *plane;\n\n\tif (!numnodes)\n\t\tHost_Error (\"CM_PointInLeaf: numnodes == 0\");\n\n\tnode = map_nodes;\n\twhile (1) {\n\t\tif (node->contents < 0) {\n\t\t\treturn (cleaf_t *)node;\n\t\t}\n\n\t\tplane = node->plane;\n\t\td = DotProduct(p, plane->normal) - plane->dist;\n\t\tnode = (d > 0) ? node->children[0] : node->children[1];\n\t}\n\n\treturn NULL; // never reached\n}\n\n\nbyte *CM_LeafPVS (const cleaf_t *leaf)\n{\n\tif (leaf == map_leafs)\n\t\treturn map_novis;\n\n\treturn map_pvs + (leaf - 1 - map_leafs) * map_vis_rowbytes;\n}\n\n\n/*\n** only the server may call this\n*/\nbyte *CM_LeafPHS (const cleaf_t *leaf)\n{\n\tif (leaf == map_leafs)\n\t\treturn map_novis;\n\n\tif (!map_phs) {\n\t\treturn NULL;\n\t}\n\n\treturn map_phs + (leaf - 1 - map_leafs) * map_vis_rowbytes;\n}\n\n/*\n=============================================================================\n\nThe PVS must include a small area around the client to allow head bobbing\nor other small motion on the client side.  Otherwise, a bob might cause an\nentity that should be visible to not show up, especially when the bob\ncrosses a waterline.\n\n=============================================================================\n*/\n\nstatic int\tfatbytes;\nstatic byte\tfatpvs[MAX_MAP_LEAFS/8];\nstatic vec3_t\tfatpvs_org;\n\nstatic void AddToFatPVS_r (cnode_t *node)\n{\n\tint i;\n\tfloat d;\n\tbyte *pvs;\n\tmplane_t *plane;\n\n\twhile (1)\n\t{ // if this is a leaf, accumulate the pvs bits\n\t\tif (node->contents < 0)\n\t\t{\n\t\t\tif (node->contents != CONTENTS_SOLID)\n\t\t\t{\n\t\t\t\tpvs = CM_LeafPVS ( (cleaf_t *)node);\n\t\t\t\tfor (i=0 ; i<fatbytes ; i++)\n\t\t\t\t\tfatpvs[i] |= pvs[i];\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tplane = node->plane;\n\t\td = DotProduct (fatpvs_org, plane->normal) - plane->dist;\n\t\tif (d > 8)\n\t\t\tnode = node->children[0];\n\t\telse if (d < -8)\n\t\t\tnode = node->children[1];\n\t\telse\n\t\t{ // go down both\n\t\t\tAddToFatPVS_r (node->children[0]);\n\t\t\tnode = node->children[1];\n\t\t}\n\t}\n}\n\n/*\n=============\nCM_FatPVS\n\nCalculates a PVS that is the inclusive or of all leafs within 8 pixels of the\ngiven point.\n=============\n*/\nbyte *CM_FatPVS (vec3_t org)\n{\n\tVectorCopy (org, fatpvs_org);\n\n\tfatbytes = (visleafs+31)>>3;\n\tmemset (fatpvs, 0, fatbytes);\n\tAddToFatPVS_r (map_nodes);\n\treturn fatpvs;\n}\n\n\n/*\n** Recursively build a list of leafs touched by a rectangular volume\n*/\nstatic int leafs_count;\nstatic int leafs_maxcount;\nstatic qbool leafs_overflow;\nstatic int *leafs_list;\nstatic int leafs_topnode;\nstatic vec3_t leafs_mins, leafs_maxs;\n\nstatic void FindTouchedLeafs_r(const cnode_t *node)\n{\n\tmplane_t *splitplane;\n\tcleaf_t *leaf;\n\tint sides;\n\n\twhile (1) {\n\t\tif (node->contents == CONTENTS_SOLID) {\n\t\t\treturn;\n\t\t}\n\n\t\t// the node is a leaf\n\t\tif (node->contents < 0) {\n\t\t\tif (leafs_count == leafs_maxcount) {\n\t\t\t\tleafs_overflow = true;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tleaf = (cleaf_t *)node;\n\t\t\tleafs_list[leafs_count++] = leaf - map_leafs;\n\t\t\treturn;\n\t\t}\n\n\t\t// NODE_MIXED\n\t\tsplitplane = node->plane;\n\t\tsides = BOX_ON_PLANE_SIDE(leafs_mins, leafs_maxs, splitplane);\n\n\t\t// recurse down the contacted sides\n\t\tif (sides == 1) {\n\t\t\tnode = node->children[0];\n\t\t}\n\t\telse if (sides == 2) {\n\t\t\tnode = node->children[1];\n\t\t}\n\t\telse {\n\t\t\tif (leafs_topnode == -1) {\n\t\t\t\tleafs_topnode = node - map_nodes;\n\t\t\t}\n\t\t\tFindTouchedLeafs_r(node->children[0]);\n\t\t\tnode = node->children[1];\n\t\t}\n\t}\n}\n\n/*\n** Returns an array filled with leaf nums\n*/\nint CM_FindTouchedLeafs (const vec3_t mins, const vec3_t maxs, int leafs[], int maxleafs, int headnode, int *topnode)\n{\n\tleafs_count = 0;\n\tleafs_maxcount = maxleafs;\n\tleafs_list = leafs;\n\tleafs_topnode = -1;\n\tleafs_overflow = false;\n\tVectorCopy (mins, leafs_mins);\n\tVectorCopy (maxs, leafs_maxs);\n\n\tFindTouchedLeafs_r (&map_nodes[headnode]);\n\n\tif (leafs_overflow) {\n\t\tleafs_count = -1;\n\t}\n\n\tif (topnode)\n\t\t*topnode = leafs_topnode;\n\n\treturn leafs_count;\n}\n\n\n/*\n===============================================================================\n\n                          BRUSHMODEL LOADING\n\n===============================================================================\n*/\n\nstatic void CM_LoadEntities (byte *buffer, int length)\n{\n\tif (!length) {\n\t\tmap_entitystring = NULL;\n\t\treturn;\n\t}\n\tmap_entitystring = (char *) Hunk_AllocName (length, loadname);\n\tmemcpy (map_entitystring, buffer, length);\n}\n\n\n/*\n=================\nCM_LoadSubmodels\n=================\n*/\nstatic void CM_LoadSubmodels (byte *buffer, int length)\n{\n\tdmodel_t *in;\n\tcmodel_t *out;\n\tint i, j, count;\n\n\tin = (dmodel_t *) buffer;\n\n\tif (length % sizeof(*in))\n\t\tHost_Error (\"CM_LoadMap: funny lump size\");\n\n\tcount = length / sizeof(*in);\n\n\tif (count < 1)\n\t\tHost_Error (\"Map with no models\");\n\n\tif (count > MAX_MAP_MODELS)\n\t\tHost_Error (\"Map has too many models (%d vs %d)\", count, MAX_MAP_MODELS);\n\n\tout = map_cmodels;\n\tnumcmodels = count;\n\n\tvisleafs = LittleLong (in[0].visleafs);\n\n\tfor (i = 0; i < count; i++, in++, out++)\n\t{\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\t// spread the mins / maxs by a pixel\n\t\t\tout->mins[j] = LittleFloat (in->mins[j]) - 1;\n\t\t\tout->maxs[j] = LittleFloat (in->maxs[j]) + 1;\n\t\t\tout->origin[j] = LittleFloat (in->origin[j]);\n\t\t}\n\t\tfor (j = 0; j < MAX_MAP_HULLS; j++) {\n\t\t\tout->hulls[j].planes = map_planes;\n\t\t\tout->hulls[j].clipnodes = map_clipnodes;\n\t\t\tout->hulls[j].firstclipnode = LittleLong (in->headnode[j]);\n\t\t\tout->hulls[j].lastclipnode = numclipnodes - 1;\n\t\t}\n\n\t\tVectorClear (out->hulls[0].clip_mins);\n\t\tVectorClear (out->hulls[0].clip_maxs);\n\n\t\tif (map_halflife)\n\t\t{\n\t\t\tVectorSet (out->hulls[1].clip_mins, -16, -16, -36);\n\t\t\tVectorSet (out->hulls[1].clip_maxs, 16, 16, 36);\n\n\t\t\tVectorSet (out->hulls[2].clip_mins, -32, -32, -36);\n\t\t\tVectorSet (out->hulls[2].clip_maxs, 32, 32, 36);\n\t\t\t// not really used\n\t\t\tVectorSet (out->hulls[3].clip_mins, -16, -16, -18);\n\t\t\tVectorSet (out->hulls[3].clip_maxs, 16, 16, 18);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorSet (out->hulls[1].clip_mins, -16, -16, -24);\n\t\t\tVectorSet (out->hulls[1].clip_maxs, 16, 16, 32);\n\n\t\t\tVectorSet (out->hulls[2].clip_mins, -32, -32, -24);\n\t\t\tVectorSet (out->hulls[2].clip_maxs, 32, 32, 64);\n\t\t}\n\t}\n}\n\n/*\n=================\nCM_SetParent\n=================\n*/\nstatic void CM_SetParent (cnode_t *node, cnode_t *parent)\n{\n\tnode->parent = parent;\n\tif (node->contents < 0)\n\t\treturn;\n\tCM_SetParent (node->children[0], node);\n\tCM_SetParent (node->children[1], node);\n}\n\n/*\n=================\nCM_LoadNodes\n=================\n*/\nstatic void CM_LoadNodes (byte *buffer, int length)\n{\n\tint i, j, count, p;\n\tdnode_t *in;\n\tcnode_t *out;\n\n\tin = (dnode_t *) buffer;\n\tif (length % sizeof(*in))\n\t\tHost_Error (\"CM_LoadMap: funny lump size\");\n\n\tcount = length / sizeof(*in);\n\tout = (cnode_t *) Hunk_AllocName ( count*sizeof(*out), loadname);\n\n\tmap_nodes = out;\n\tnumnodes = count;\n\n\tfor (i = 0; i < count; i++, in++, out++)\n\t{\n\t\tp = LittleLong(in->planenum);\n\t\tout->plane = map_planes + p;\n\n\t\tfor (j=0 ; j<2 ; j++)\n\t\t{\n\t\t\tp = LittleShort (in->children[j]);\n\t\t\tout->children[j] = (p >= 0) ? (map_nodes + p) : ((cnode_t *)(map_leafs + (-1 - p)));\n\t\t}\n\t}\n\n\tCM_SetParent (map_nodes, NULL); // sets nodes and leafs\n}\n\nstatic void CM_LoadNodes29a(byte *buffer, int length)\n{\n\tint i, j, count, p;\n\tdnode29a_t *in;\n\tcnode_t *out;\n\n\tin = (dnode29a_t *) buffer;\n\tif (length % sizeof(*in))\n\t\tHost_Error(\"CM_LoadMap: funny lump size\");\n\n\tcount = length / sizeof(*in);\n\tout = (cnode_t *)Hunk_AllocName(count * sizeof(*out), loadname);\n\n\tmap_nodes = out;\n\tnumnodes = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tp = LittleLong(in->planenum);\n\t\tout->plane = map_planes + p;\n\n\t\tfor (j = 0; j < 2; j++) {\n\t\t\tp = LittleLong(in->children[j]);\n\t\t\tout->children[j] = (p >= 0) ? (map_nodes + p) : ((cnode_t *)(map_leafs + (-1 - p)));\n\t\t}\n\t}\n\n\tCM_SetParent(map_nodes, NULL); // sets nodes and leafs\n}\n\nstatic void CM_LoadNodesBSP2(byte *buffer, int length)\n{\n\tint i, j, count, p;\n\tdnode_bsp2_t *in;\n\tcnode_t *out;\n\n\tin = (dnode_bsp2_t *) buffer;\n\tif (length % sizeof(*in))\n\t\tHost_Error(\"CM_LoadMap: funny lump size\");\n\n\tcount = length / sizeof(*in);\n\tout = (cnode_t *)Hunk_AllocName(count * sizeof(*out), loadname);\n\n\tmap_nodes = out;\n\tnumnodes = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tp = LittleLong(in->planenum);\n\t\tout->plane = map_planes + p;\n\n\t\tfor (j = 0; j < 2; j++) {\n\t\t\tp = LittleLong(in->children[j]);\n\t\t\tout->children[j] = (p >= 0) ? (map_nodes + p) : ((cnode_t *)(map_leafs + (-1 - p)));\n\t\t}\n\t}\n\n\tCM_SetParent(map_nodes, NULL); // sets nodes and leafs\n}\n\n/*\n** CM_LoadLeafs\n*/\nstatic void CM_LoadLeafs (byte *buffer, int length)\n{\n\tdleaf_t *in;\n\tcleaf_t *out;\n\tint i, j, count, p;\n\n\tin = (dleaf_t *) buffer;\n\n\tif (length % sizeof(*in))\n\t\tHost_Error (\"CM_LoadMap: funny lump size\");\n\tcount = length / sizeof(*in);\n\tout = (cleaf_t *) Hunk_AllocName ( count*sizeof(*out), loadname);\n\n\tmap_leafs = out;\n\tnumleafs = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tp = LittleLong(in->contents);\n\t\tout->contents = p;\n\t\tfor (j = 0; j < 4; j++)\n\t\t\tout->ambient_sound_level[j] = in->ambient_level[j];\n\t}\n}\n\nstatic void CM_LoadLeafs29a (byte *buffer, int length)\n{\n\tdleaf29a_t *in;\n\n\tcleaf_t *out;\n\tint i, j, count, p;\n\n\tin = (dleaf29a_t *) buffer;\n\tif (length % sizeof(*in)) {\n\t\tHost_Error(\"CM_LoadMap: funny lump size\");\n\t}\n\n\tcount = length / sizeof(*in);\n\tout = Hunk_AllocName ( count*sizeof(*out), loadname);\n\n\tmap_leafs = out;\n\tnumleafs = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tp = LittleLong(in->contents);\n\t\tout->contents = p;\n\t\tfor (j = 0; j < 4; j++)\n\t\t\tout->ambient_sound_level[j] = in->ambient_level[j];\n\t}\n\n}\n\nstatic void CM_LoadLeafsBSP2 (byte *buffer, int length)\n{\n\tdleaf_bsp2_t *in;\n\n\tcleaf_t *out;\n\tint i, j, count, p;\n\n\tin = (dleaf_bsp2_t *) buffer;\n\n\tif (length % sizeof(*in)) {\n\t\tHost_Error(\"CM_LoadMap: funny lump size\");\n\t}\n\n\tcount = length / sizeof(*in);\n\tout = Hunk_AllocName ( count*sizeof(*out), loadname);\n\n\tmap_leafs = out;\n\tnumleafs = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tp = LittleLong(in->contents);\n\t\tout->contents = p;\n\t\tfor (j = 0; j < 4; j++)\n\t\t\tout->ambient_sound_level[j] = in->ambient_level[j];\n\t}\n}\n\n/*\n=================\nCM_LoadClipnodes\n=================\n*/\nstatic void CM_LoadClipnodes(byte *buffer, int length)\n{\n\tdclipnode_t *in;\n\tmclipnode_t *out;\n\tint i, count;\n\n\tin = (dclipnode_t *) buffer;\n\tif (length % sizeof(*in))\n\t\tHost_Error(\"CM_LoadMap: funny lump size\");\n\tcount = length / sizeof(*in);\n\tout = (mclipnode_t *)Hunk_AllocName(count * sizeof(*out), loadname);\n\n\tmap_clipnodes = out;\n\tnumclipnodes = count;\n\n\tfor (i = 0; i < count; i++, out++, in++) {\n\t\tout->planenum = LittleLong(in->planenum);\n\t\tout->children[0] = LittleShort(in->children[0]);\n\t\tout->children[1] = LittleShort(in->children[1]);\n\t}\n}\n\nstatic void CM_LoadClipnodesBSP2(byte *buffer, int length)\n{\n\tdclipnode29a_t *in;\n\tmclipnode_t *out;\n\tint i, count;\n\n\tin = (void *) buffer;\n\tif (length % sizeof(*in))\n\t\tHost_Error(\"CM_LoadMap: funny lump size\");\n\tcount = length / sizeof(*in);\n\tout = Hunk_AllocName(count * sizeof(*out), loadname);\n\n\tmap_clipnodes = out;\n\tnumclipnodes = count;\n\n\tfor (i = 0; i < count; i++, out++, in++) {\n\t\tout->planenum = LittleLong(in->planenum);\n\t\tout->children[0] = LittleLong(in->children[0]);\n\t\tout->children[1] = LittleLong(in->children[1]);\n\t}\n}\n\nstatic qbool CM_LoadPhysicsNormalsData(byte* data, int datalength)\n{\n\tmphysicsnormal_t* in = (mphysicsnormal_t*)(data + 8);\n\tint i;\n\n\tif (datalength != 8 + sizeof(map_physicsnormals[0]) * numclipnodes) {\n\t\treturn false;\n\t}\n\n#ifndef CLIENTONLY\n\t{\n\t\tfloat* cvars = (float*)data;\n\t\textern cvar_t pm_rampjump;\n\n\t\tCvar_SetValue(&pm_rampjump, LittleFloat(cvars[0]));\n\t}\n#endif\n\t// Meag: previously the maximum speed was set here but I don't think it should be map-specific (?)\n\n\tfor (i = 0; i < numclipnodes; ++i) {\n\t\tmap_physicsnormals[i].normal[0] = LittleFloat(in[i].normal[0]);\n\t\tmap_physicsnormals[i].normal[1] = LittleFloat(in[i].normal[1]);\n\t\tmap_physicsnormals[i].normal[2] = LittleFloat(in[i].normal[2]);\n\t\tmap_physicsnormals[i].flags = PHYSICSNORMAL_SET | (int)LittleLong(in[i].flags);\n\t}\n\treturn true;\n}\n\nstatic void CM_LoadPhysicsNormals(byte *physnormals, int len_physnormals)\n{\n\t// Same logic as .lit file support: load from bspx, allow over-ride with .qpn files\n\t//   As client-side movement prediction will be incorrect if physics normals don't\n\t//     match, I strongly recommend the .bspx solution\n\tint i;\n\tqbool bspx_loaded = false;\n\n\t// Allocate memory, all maps default to rampjump off\n#ifndef CLIENTONLY\n\t{\n\t\textern cvar_t pm_rampjump;\n\t\tCvar_SetValue(&pm_rampjump, 0);\n\t}\n#endif\n\tmap_physicsnormals = Hunk_AllocName(numclipnodes * sizeof(map_physicsnormals[0]), loadname);\n\n\t// Try and load from BSPX lump\n\tif (physnormals) {\n\t\tbspx_loaded = CM_LoadPhysicsNormalsData(physnormals, len_physnormals);\n\t\tif (bspx_loaded) {\n\t\t\tCon_Printf(\"Loading BSPX physics normals\\n\");\n\t\t}\n\t}\n\n\t// If not supplied, initialise with default values from clipnodes\n\tif (!bspx_loaded) {\n\t\tfor (i = 0; i < numclipnodes; ++i) {\n\t\t\tVectorCopy(map_planes[map_clipnodes[i].planenum].normal, map_physicsnormals[i].normal);\n\t\t\tmap_physicsnormals[i].flags = PHYSICSNORMAL_SET;\n\t\t}\n\t}\n\n\t// Now over-ride from external file\n\t{\n\t\tchar extfile[MAX_OSPATH];\n\t\tint extfilesize = 0;\n\t\tvoid* data = NULL;\n\t\tint mark;\n\n\t\tmark = Hunk_LowMark();\n\t\tsnprintf(extfile, sizeof(extfile), \"maps/%s.qpn\", loadname);\n\t\tdata = FS_LoadHunkFile(extfile, &extfilesize);\n\t\tif (data) {\n\t\t\tif (CM_LoadPhysicsNormalsData(data, extfilesize)) {\n\t\t\t\tCon_Printf(\"Loading external physics normals\\n\");\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCon_Printf(\"%s is corrupt or wrong size\\n\", extfile);\n\t\t\t}\n\t\t\tHunk_FreeToLowMark(mark);\n\t\t}\n\t}\n}\n\n/*\n=================\nCM_MakeHull0\n\nDeplicate the drawing hull structure as a clipping hull\n=================\n*/\nstatic void CM_MakeHull0(void)\n{\n\tcnode_t *in, *child;\n\tmclipnode_t *out;\n\tint i, j, count;\n\n\tin = map_nodes;\n\tcount = numnodes;\n\tout = (mclipnode_t *)Hunk_AllocName(count * sizeof(*out), loadname);\n\n\t// fix up hull 0 in all cmodels\n\tfor (i = 0; i < numcmodels; i++) {\n\t\tmap_cmodels[i].hulls[0].clipnodes = out;\n\t\tmap_cmodels[i].hulls[0].lastclipnode = count - 1;\n\t}\n\n\t// build clipnodes from nodes\n\tfor (i = 0; i < count; i++, out++, in++) {\n\t\tout->planenum = in->plane - map_planes;\n\t\tfor (j = 0; j < 2; j++) {\n\t\t\tchild = in->children[j];\n\t\t\tout->children[j] = (child->contents < 0) ? (child->contents) : (child - map_nodes);\n\t\t}\n\t}\n}\n\n/*\n=================\nCM_LoadPlanes\n=================\n*/\nstatic void CM_LoadPlanes(byte *buffer, size_t length)\n{\n\tint i, j, count, bits;\n\tmplane_t *out;\n\tdplane_t *in;\n\n\tin = (dplane_t *) buffer;\n\n\tif (length % sizeof(*in))\n\t\tHost_Error(\"CM_LoadMap: funny lump size\");\n\n\tcount = length / sizeof(*in);\n\tout = (mplane_t *)Hunk_AllocName(count * sizeof(*out), loadname);\n\n\tmap_planes = out;\n\tnumplanes = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tbits = 0;\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tout->normal[j] = LittleFloat(in->normal[j]);\n\t\t\tif (out->normal[j] < 0)\n\t\t\t\tbits |= 1 << j;\n\t\t}\n\n\t\tout->dist = LittleFloat(in->dist);\n\t\tout->type = LittleLong(in->type);\n\t\tout->signbits = bits;\n\t}\n}\n\n\n/*\n** DecompressVis\n*/\nstatic byte *DecompressVis(byte *in)\n{\n\tstatic byte decompressed[MAX_MAP_LEAFS / 8];\n\tint c, row;\n\tbyte *out;\n\n\trow = (visleafs + 7) >> 3;\n\tout = decompressed;\n\n\tif (!in) { // no vis info, so make all visible\n\t\twhile (row) {\n\t\t\t*out++ = 0xff;\n\t\t\trow--;\n\t\t}\n\t\treturn decompressed;\n\t}\n\n\tdo {\n\t\tif (*in) {\n\t\t\t*out++ = *in++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tc = in[1];\n\t\tin += 2;\n\t\twhile (c) {\n\t\t\t*out++ = 0;\n\t\t\tc--;\n\t\t}\n\t} while (out - decompressed < row);\n\n\treturn decompressed;\n}\n\n\n/*\n** CM_BuildPVS\n**\n** Call after CM_LoadLeafs!\n*/\nstatic void CM_BuildPVS(byte *visdata, int vis_len, byte *leaf_buf, int leaf_len)\n{\n\tbyte *scan;\n\tdleaf_t *in;\n\tint i;\n\n\tmap_vis_rowlongs = (visleafs + 31) >> 5;\n\tmap_vis_rowbytes = map_vis_rowlongs * 4;\n\tmap_pvs = (byte *)Hunk_AllocName(map_vis_rowbytes * visleafs, \"pvs\");\n\n\tif (!vis_len) {\n\t\tmemset(map_pvs, 0xff, map_vis_rowbytes * visleafs);\n\t\treturn;\n\t}\n\n\t// FIXME, add checks for lump_vis->filelen and leafs' visofs\n\n\t// go through all leafs and decompress visibility data\n\tin = (dleaf_t *) leaf_buf;\n\tin++; // pvs row 0 is leaf 1\n\tscan = map_pvs;\n\tfor (i = 0; i < visleafs; i++, in++, scan += map_vis_rowbytes) {\n\t\tint p = LittleLong(in->visofs);\n\t\tmemcpy(scan, (p == -1) ? map_novis : DecompressVis(visdata + p), map_vis_rowbytes);\n\t}\n}\n\nstatic void CM_BuildPVS29a(byte *visdata, int vis_len, byte *leaf_buf, int leaf_len)\n{\n\tbyte *scan;\n\tdleaf29a_t *in;\n\tint i;\n\n\tmap_vis_rowlongs = (visleafs + 31) >> 5;\n\tmap_vis_rowbytes = map_vis_rowlongs * 4;\n\tmap_pvs = (byte *)Hunk_AllocName(map_vis_rowbytes * visleafs, \"pvs\");\n\n\tif (!vis_len) {\n\t\tmemset(map_pvs, 0xff, map_vis_rowbytes * visleafs);\n\t\treturn;\n\t}\n\n\t// FIXME, add checks for lump_vis->filelen and leafs' visofs\n\n\t// go through all leafs and decompress visibility data\n\tin = (dleaf29a_t *) leaf_buf;\n\tin++; // pvs row 0 is leaf 1\n\tscan = map_pvs;\n\tfor (i = 0; i < visleafs; i++, in++, scan += map_vis_rowbytes) {\n\t\tint p = LittleLong(in->visofs);\n\t\tmemcpy(scan, (p == -1) ? map_novis : DecompressVis(visdata + p), map_vis_rowbytes);\n\t}\n}\n\nstatic void CM_BuildPVSBSP2(byte *visdata, int vis_len, byte *leaf_buf, int leaf_len)\n{\n\tbyte *scan;\n\tdleaf_bsp2_t *in;\n\tint i;\n\n\tmap_vis_rowlongs = (visleafs + 31) >> 5;\n\tmap_vis_rowbytes = map_vis_rowlongs * 4;\n\tmap_pvs = (byte *)Hunk_AllocName(map_vis_rowbytes * visleafs, \"pvs\");\n\n\tif (!vis_len) {\n\t\tmemset(map_pvs, 0xff, map_vis_rowbytes * visleafs);\n\t\treturn;\n\t}\n\n\t// FIXME, add checks for lump_vis->filelen and leafs' visofs\n\n\t// go through all leafs and decompress visibility data\n\tin = (dleaf_bsp2_t *) leaf_buf;\n\tin++; // pvs row 0 is leaf 1\n\tscan = map_pvs;\n\tfor (i = 0; i < visleafs; i++, in++, scan += map_vis_rowbytes) {\n\t\tint p = LittleLong(in->visofs);\n\t\tmemcpy(scan, (p == -1) ? map_novis : DecompressVis(visdata + p), map_vis_rowbytes);\n\t}\n}\n\n/*\n** CM_BuildPHS\n**\n** Expands the PVS and calculates the PHS (potentially hearable set)\n** Call after CM_BuildPVS (so that map_vis_rowbytes & map_vis_rowlongs are set)\n*/\nstatic void CM_BuildPHS (void)\n{\n\tint i, j, k, l, index1, bitbyte;\n\tunsigned *dest, *src;\n\tbyte *scan;\n\n\tmap_phs = NULL;\n\tif (map_vis_rowbytes * visleafs > 0x100000) {\n\t\treturn;\n\t}\n\n\tmap_phs = (byte *) Hunk_AllocName (map_vis_rowbytes * visleafs, \"phs\");\n\tscan = map_pvs;\n\tdest = (unsigned *)map_phs;\n\tfor (i = 0; i < visleafs; i++, dest += map_vis_rowlongs, scan += map_vis_rowbytes)\n\t{\n\t\t// copy from pvs\n\t\tmemcpy (dest, scan, map_vis_rowbytes);\n\n\t\t// or in hearable leafs\n\t\tfor (j = 0; j < map_vis_rowbytes; j++)\n\t\t{\n\t\t\tbitbyte = scan[j];\n\t\t\tif (!bitbyte)\n\t\t\t\tcontinue;\n\t\t\tfor (k = 0; k < 8; k++)\n\t\t\t{\n\t\t\t\tif (! (bitbyte & (1<<k)) )\n\t\t\t\t\tcontinue;\n\t\t\t\t// or this pvs row into the phs\n\t\t\t\tindex1 = (j<<3) + k;\n\t\t\t\tif (index1 >= visleafs)\n\t\t\t\t\tcontinue;\n\t\t\t\tsrc = (unsigned *)map_pvs + index1 * map_vis_rowlongs;\n\t\t\t\tfor (l = 0; l < map_vis_rowlongs; l++)\n\t\t\t\t\tdest[l] |= src[l];\n\t\t\t}\n\t\t}\n\t}\n}\n\n\n\n/*\n** hunk was reset by host, so the data is no longer valid\n*/\nvoid CM_InvalidateMap (void)\n{\n\tmap_name[0] = 0;\n\n\t// null out the pointers to turn up any attempt to call CM functions\n\tmap_planes = NULL;\n\tmap_nodes = NULL;\n\tmap_clipnodes = NULL;\n\tmap_leafs = NULL;\n\tmap_pvs = NULL;\n\tmap_phs = NULL;\n\tmap_entitystring = NULL;\n\tmap_physicsnormals = NULL;\n}\n\nstatic vfsfile_t *CM_OpenMap(char *name, dheader_t *header)\n{\n#ifndef CLIENTONLY\n\textern cvar_t sv_bspversion, sv_halflifebsp;\n#endif\n\tvfsfile_t *vf;\n\tint i, read;\n\n\tvf = FS_OpenVFS(name, \"rb\", FS_ANY); // FIXME: should be FS_GAME.\n\tif (!vf) {\n\t\tHost_Error (\"CM_OpenMap: %s not found\", name);\n\t}\n\n\tread = VFS_READ(vf, header, sizeof(dheader_t), NULL);\n\tif (read != sizeof(dheader_t))\n\t{\n\t\tCon_Printf(\"Failed to read BSP header, got %d of %d bytes\\n\", read, sizeof(dheader_t));\n\t\tVFS_CLOSE(vf);\n\t\treturn NULL;\n\t}\n\n\tfor (i = 0; i < sizeof(dheader_t) / 4; i++) {\n\t\t((int *)header)[i] = LittleLong(((int *)header)[i]);\n\t}\n\n\tswitch (header->version) {\n\tcase Q1_BSPVERSION:\n\tcase HL_BSPVERSION:\n#ifndef CLIENTONLY\n\t  Cvar_SetROM(&sv_bspversion, \"1\");\n#endif\n\t  break;\n\tcase Q1_BSPVERSION2:\n\tcase Q1_BSPVERSION29a:\n#ifndef CLIENTONLY\n\t  Cvar_SetROM(&sv_bspversion, \"2\");\n#endif\n\t  break;\n\tdefault:\n\t\tVFS_CLOSE(vf);\n\t\tHost_Error (\"CM_OpenMap: %s has wrong version number (%i should be %i)\", name, i, Q1_BSPVERSION);\n\t\tbreak;\n\t}\n\n\tmap_halflife = (header->version == HL_BSPVERSION);\n\n#ifndef CLIENTONLY\n\tCvar_SetROM(&sv_halflifebsp, map_halflife ? \"1\" : \"0\");\n#endif\n\n\treturn vf;\n}\n\nstatic qbool CM_CalcChecksum(vfsfile_t *f, dheader_t *header, unsigned *checksum, unsigned *checksum2)\n{\n\tbyte *buf;\n\tint i, read;\n\n\t// checksum all of the map, except for entities\n\tmap_checksum = map_checksum2 = 0;\n\tfor (i = 0; i < HEADER_LUMPS; i++) {\n\t\tif (i == LUMP_ENTITIES)\n\t\t\tcontinue;\n\n\t\tif (VFS_SEEK(f, header->lumps[i].fileofs, SEEK_SET) < 0)\n\t\t{\n\t\t\tCon_Printf(\"Seek to BSP lump at %d failed\\n\", header->lumps[i].fileofs);\n\t\t\treturn false;\n\t\t}\n\n\t\tbuf = Hunk_TempAlloc (header->lumps[i].filelen);\n\n\t\tread = VFS_READ(f, buf, header->lumps[i].filelen, NULL);\n\t\tif (read != header->lumps[i].filelen)\n\t\t{\n\t\t\tCon_Printf(\"Failed to read BSP lump, got %d of %d bytes\\n\", read, header->lumps[i].filelen);\n\t\t\treturn false;\n\t\t}\n\n\t\tmap_checksum ^= LittleLong(Com_BlockChecksum(buf, header->lumps[i].filelen));\n\n\t\tif (i == LUMP_VISIBILITY || i == LUMP_LEAFS || i == LUMP_NODES)\n\t\t\tcontinue;\n\n\t\tmap_checksum2 ^= LittleLong(Com_BlockChecksum(buf, header->lumps[i].filelen));\n\t}\n\n\tif (checksum)\n\t\t*checksum = map_checksum;\n\n\tif (checksum2)\n\t\t*checksum2 = map_checksum2;\n\n\treturn true;\n}\n\nstatic byte *CM_ReadLump(vfsfile_t *vf, lump_t *lump)\n{\n\tbyte *out;\n\tint read;\n\n\tif (VFS_SEEK(vf, lump->fileofs, SEEK_SET) < 0)\n\t{\n\t\tCon_Printf(\"Seek to BSP lump at %d failed\\n\", lump->fileofs);\n\t\treturn NULL;\n\t}\n\tout = Hunk_TempAllocMore(lump->filelen);\n\n\tread = VFS_READ(vf, out, lump->filelen, NULL);\n\tif (read != lump->filelen)\n\t{\n\t\tCon_Printf(\"Failed to read BSP lump, got %d of %d bytes\\n\", read, lump->filelen);\n\t\treturn NULL;\n\t}\n\n\treturn out;\n}\n\n/*\n** CM_LoadMap\n*/\ntypedef void(*BuildPVSFunction)(byte *vis_buf, int vis_len, byte *leaf_buf, int leaf_len);\ncmodel_t *CM_LoadMap (char *name, qbool clientload, unsigned *checksum, unsigned *checksum2)\n{\n\tdheader_t header = { 0 };\n\tBuildPVSFunction cm_load_pvs_func = CM_BuildPVS;\n\tvfsfile_t *vf;\n\tbyte *l_planes, *l_leafs, *l_nodes, *l_clipnodes, *l_entities, *l_models, *l_vis;\n\tbspx_header_t xheader = { 0 };\n\tbspx_lump_t *xlumps;\n\tbyte *l_physnormals = NULL;\n\tint l_physnormals_len = 0;\n\n\tif (map_name[0]) {\n\t\tif (strcmp(name, map_name))\n\t\t\tCon_Printf(\"CM_LoadMap: '%s' != '%s'\\n\", name, map_name);\n\n\t\tif (checksum)\n\t\t\t*checksum = map_checksum;\n\t\t*checksum2 = map_checksum2;\n\t\treturn &map_cmodels[0]; // still have the right version\n\t}\n\n\tvf = CM_OpenMap(name, &header);\n\tif (!vf)\n\t{\n\t\treturn NULL;\n\t}\n\n\tif (!CM_CalcChecksum(vf, &header, checksum, checksum2))\n\t{\n\t\tVFS_CLOSE(vf);\n\t\treturn NULL;\n\t}\n\n\tCOM_FileBase (name, loadname);\n\n\t// Flush to temp zone to leave as much heap available to map loading as possible.\n\tHunk_TempFlush();\n\n\tl_planes = CM_ReadLump(vf, &header.lumps[LUMP_PLANES]);\n\tl_leafs = CM_ReadLump(vf, &header.lumps[LUMP_LEAFS]);\n\tl_nodes = CM_ReadLump(vf, &header.lumps[LUMP_NODES]);\n\tl_clipnodes = CM_ReadLump(vf, &header.lumps[LUMP_CLIPNODES]);\n\tl_entities = CM_ReadLump(vf, &header.lumps[LUMP_ENTITIES]);\n\tl_models = CM_ReadLump(vf, &header.lumps[LUMP_MODELS]);\n\tl_vis = CM_ReadLump(vf, &header.lumps[LUMP_VISIBILITY]);\n\n\txlumps = CM_LoadBSPX(vf, &header, &xheader);\n\tif (xlumps)\n\t\tl_physnormals = CM_BSPX_ReadLump(vf, &xheader, xlumps, \"MVDSV_PHYSICSNORMALS\", &l_physnormals_len);\n\n\tVFS_CLOSE(vf);\n\n\tif (!l_planes || !l_leafs || !l_nodes || !l_clipnodes || !l_entities || !l_models || !l_vis)\n\t{\n\t\treturn NULL;\n\t}\n\n\t// load into heap\n\tCM_LoadPlanes (l_planes, header.lumps[LUMP_PLANES].filelen);\n\tif (LittleLong(header.version) == Q1_BSPVERSION29a) {\n\t\tCM_LoadLeafs29a(l_leafs, header.lumps[LUMP_LEAFS].filelen);\n\t\tCM_LoadNodes29a(l_nodes, header.lumps[LUMP_NODES].filelen);\n\t\tCM_LoadClipnodesBSP2(l_clipnodes, header.lumps[LUMP_CLIPNODES].filelen);\n\t\tcm_load_pvs_func = CM_BuildPVS29a;\n\t}\n\telse if (LittleLong(header.version) == Q1_BSPVERSION2) {\n\t\tCM_LoadLeafsBSP2(l_leafs, header.lumps[LUMP_LEAFS].filelen);\n\t\tCM_LoadNodesBSP2(l_nodes, header.lumps[LUMP_NODES].filelen);\n\t\tCM_LoadClipnodesBSP2(l_clipnodes, header.lumps[LUMP_CLIPNODES].filelen);\n\t\tcm_load_pvs_func = CM_BuildPVSBSP2;\n\t}\n\telse {\n\t\tCM_LoadLeafs(l_leafs, header.lumps[LUMP_LEAFS].filelen);\n\t\tCM_LoadNodes(l_nodes, header.lumps[LUMP_NODES].filelen);\n\t\tCM_LoadClipnodes(l_clipnodes, header.lumps[LUMP_CLIPNODES].filelen);\n\t\tcm_load_pvs_func = CM_BuildPVS;\n\t}\n\tCM_LoadEntities (l_entities, header.lumps[LUMP_ENTITIES].filelen);\n\tCM_LoadSubmodels (l_models, header.lumps[LUMP_MODELS].filelen);\n\n\tCM_LoadPhysicsNormals(l_physnormals, l_physnormals_len);\n\tCM_MakeHull0 ();\n\n\tcm_load_pvs_func (l_vis, header.lumps[LUMP_VISIBILITY].filelen, l_leafs, header.lumps[LUMP_LEAFS].filelen);\n\n\tif (!clientload) // client doesn't need PHS\n\t\tCM_BuildPHS ();\n\n\tstrlcpy (map_name, name, sizeof(map_name));\n\n\t// Flush temp zone to leave as much heap available to mods as possible.\n\tHunk_TempFlush();\n\n\treturn &map_cmodels[0];\n}\n\ncmodel_t *CM_InlineModel (char *name)\n{\n\tint num;\n\n\tif (!name || name[0] != '*')\n\t\tHost_Error (\"CM_InlineModel: bad name\");\n\n\tnum = atoi (name+1);\n\tif (num < 1 || num >= numcmodels)\n\t\tHost_Error (\"CM_InlineModel: bad number\");\n\n\treturn &map_cmodels[num];\n}\n\nvoid CM_Init (void)\n{\n\tmemset (map_novis, 0xff, sizeof(map_novis));\n\tCM_InitBoxHull ();\n}\n\n#ifndef SERVER_ONLY\n// Allow in-memory modifications to ground normals...\nvoid CM_PhysicsNormalSet(int num, float x, float y, float z, int flags)\n{\n\tif (num > 0 && num <= numclipnodes) {\n\t\tVectorSet(map_physicsnormals[num - 1].normal, x, y, z);\n\t\tmap_physicsnormals[num - 1].flags = flags;\n\t}\n}\n\n// Allow map developer to dump normals\nvoid CM_PhysicsNormalDump(FILE* out, float rampjump, float maxgroundspeed)\n{\n\tif (map_physicsnormals) {\n\t\tfwrite(&rampjump, 4, 1, out);\n\t\tfwrite(&maxgroundspeed, 4, 1, out);\n\t\tfwrite(map_physicsnormals, sizeof(*map_physicsnormals) * numclipnodes, 1, out);\n\t}\n}\n#endif\n\nmphysicsnormal_t CM_PhysicsNormal(int num)\n{\n\tmphysicsnormal_t ret;\n\tqbool inverse = num < 0;\n\n\tmemset(&ret, 0, sizeof(ret));\n\n\tnum = abs(num);\n\n\tif (num > 0 && num <= numclipnodes) {\n\t\tret = map_physicsnormals[num - 1];\n\t\tif (inverse) {\n\t\t\tVectorNegate(ret.normal, ret.normal);\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nstatic int CM_BSPX_FindOffset(dheader_t *header, int filesize)\n{\n\tint i, xofs = 0;\n\n\tfor (i = 0; i < HEADER_LUMPS; i++) {\n\t\txofs = max(xofs, header->lumps[i].fileofs + header->lumps[i].filelen);\n\t}\n\n\tif (xofs + sizeof(bspx_header_t) > filesize) {\n\t\treturn -1;\n\t}\n\n\treturn xofs;\n}\n\nstatic qbool CM_BSPX_LoadLumps(bspx_lump_t *lump, int numlumps, int filesize)\n{\n\tint i;\n\n\t// byte-swap and check sanity\n\tfor (i = 0; i < numlumps; i++, lump++) {\n\t\tlump->lumpname[sizeof(lump->lumpname) - 1] = '\\0'; // make sure it ends with zero\n\t\tlump->fileofs = LittleLong(lump->fileofs);\n\t\tlump->filelen = LittleLong(lump->filelen);\n\t\tif (lump->fileofs < 0 || lump->filelen < 0 || (unsigned)(lump->fileofs + lump->filelen) >(unsigned)filesize) {\n\t\t\tCon_Printf(\"Invalid BSPX lump position, ofs: %d, len: %d, filelen: %d\\n\", lump->fileofs, lump->filelen, filesize);\n\t\t\treturn false;\n\t\t}\n\t}\n\n    return true;\n}\n\n#ifndef SERVERONLY\n// Used by ezquake\nvoid* Mod_BSPX_FindLump(bspx_header_t* bspx_header, char* lumpname, int* plumpsize, byte* mod_base)\n{\n\tint i;\n\tbspx_lump_t* lump;\n\n\tif (!bspx_header) {\n\t\treturn NULL;\n\t}\n\n\tlump = (bspx_lump_t*)(bspx_header + 1);\n\tfor (i = 0; i < bspx_header->numlumps; i++, lump++) {\n\t\tif (!strcmp(lump->lumpname, lumpname)) {\n\t\t\tif (plumpsize) {\n\t\t\t\t*plumpsize = lump->filelen;\n\t\t\t}\n\t\t\treturn mod_base + lump->fileofs;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n// Used by ezquake\nbspx_header_t* Mod_LoadBSPX(int filesize, byte* mod_base)\n{\n\tdheader_t* header;\n\tbspx_header_t* xheader;\n\tbspx_lump_t* lump;\n\tint xofs;\n\n\t// find end of last lump\n\theader = (dheader_t*)mod_base;\n\txofs = CM_BSPX_FindOffset(header, filesize);\n\tif (xofs < 0) {\n\t\treturn NULL;\n\t}\n\n\txheader = (bspx_header_t*)(mod_base + xofs);\n\txheader->numlumps = LittleLong(xheader->numlumps);\n\n\tif (xheader->numlumps < 0 || xofs + sizeof(bspx_header_t) + xheader->numlumps * sizeof(bspx_lump_t) > filesize) {\n\t\treturn NULL;\n\t}\n\n\tlump = (bspx_lump_t*)(xheader + 1); // lumps immediately follow the header\n\tif (!CM_BSPX_LoadLumps(lump, xheader->numlumps, filesize)) {\n\t\treturn NULL;\n\t}\n\n\t// success\n\treturn xheader;\n}\n#endif\n\nstatic byte* CM_BSPX_ReadLump(vfsfile_t *vf, bspx_header_t *xheader, bspx_lump_t* lump, char* lumpname, int* plumpsize)\n{\n\tbyte *buffer;\n\tint i, read;\n\n\tif (!lump) {\n\t\treturn NULL;\n\t}\n\n\tfor (i = 0; i < xheader->numlumps; i++, lump++) {\n\t\tif (!strcmp(lump->lumpname, lumpname)) {\n\t\t\tif (plumpsize) {\n\t\t\t\t*plumpsize = lump->filelen;\n\t\t\t}\n\n\t\t\tbuffer = Hunk_TempAllocMore(lump->filelen);\n\t\t\tif (VFS_SEEK(vf, lump->fileofs, SEEK_SET) < 0)\n\t\t\t{\n\t\t\t\tCon_Printf(\"Seek to BSPX lump at %d failed\\n\", lump->fileofs);\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tread = VFS_READ(vf, buffer, lump->filelen, NULL);\n\t\t\tif (read != lump->filelen)\n\t\t\t{\n\t\t\t\tCon_Printf(\"Failed to read BSPX lump, got %d of %d bytes\\n\", read, lump->filelen);\n\t\t\t\treturn NULL;\n\t\t\t}\n\n\t\t\treturn buffer;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nstatic bspx_lump_t* CM_LoadBSPX(vfsfile_t *vf, dheader_t *header, bspx_header_t *xheader)\n{\n\tbspx_lump_t* lump;\n\tint xofs;\n\tint read, lumpssize, filesize;\n\n\tfilesize = VFS_GETLEN(vf);\n\n\t// find end of last lump\n\txofs = CM_BSPX_FindOffset(header, filesize);\n\tif (xofs < 0) {\n\t\treturn NULL;\n\t}\n\n\tif (VFS_SEEK(vf, xofs, SEEK_SET) < 0)\n\t{\n\t\treturn NULL;\n\t}\n\n\tread = VFS_READ(vf, xheader, sizeof(bspx_header_t), NULL);\n\tif (read != sizeof(bspx_header_t))\n\t{\n\t\treturn NULL;\n\t}\n\n\txheader->numlumps = LittleLong(xheader->numlumps);\n\n\tlumpssize = sizeof(bspx_lump_t) * xheader->numlumps;\n\n\tif (xheader->numlumps < 0 || xofs + sizeof(bspx_header_t) + lumpssize > filesize) {\n        Con_Printf(\"Corrupt BSPX header\\n\");\n\t\treturn NULL;\n\t}\n\n\tlump = (bspx_lump_t*)Hunk_TempAllocMore(lumpssize);\n\n\tread = VFS_READ(vf, lump, lumpssize, NULL);\n\tif (read != lumpssize)\n\t{\n\t\tCon_Printf(\"Failed to read BSPX lumps header, got %d of %d bytes\\n\", read, lumpssize);\n\t\treturn NULL;\n\t}\n\n\tif (!CM_BSPX_LoadLumps(lump, xheader->numlumps, filesize)) {\n\t\treturn NULL;\n\t}\n\n\treturn lump;\n}\n"
  },
  {
    "path": "src/cmodel.h",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n\n// cmodel.h\n#ifndef __CMODEL_H__\n#define __CMODEL_H__\n\n#include \"bspfile.h\"\n\n// mplane->type\nenum {\n\tSIDE_FRONT = 0,\n\tSIDE_BACK = 1,\n\tSIDE_ON = 2\n};\n\n\n// plane_t structure\n// !!! if this is changed, it must be changed in asm_i386.h too !!!\ntypedef struct mplane_s {\n\tvec3_t\tnormal;\n\tfloat\tdist;\n\tbyte\ttype;\t\t\t// for texture axis selection and fast side tests\n\tbyte\tsignbits;\t\t// signx + signy<<1 + signz<<1\n\tbyte\tpad[2];\n} mplane_t;\n\n// !!! if this is changed, it must be changed in asm_i386.h too !!!\ntypedef struct {\n\tmclipnode_t\t*clipnodes;\n\tmplane_t\t*planes;\n\tint\t\t\tfirstclipnode;\n\tint\t\t\tlastclipnode;\n\tvec3_t\t\tclip_mins;\n\tvec3_t\t\tclip_maxs;\n} hull_t;\n\ntypedef struct {\n\tvec3_t\tnormal;\n\tfloat\tdist;\n} plane_t;\n\ntypedef struct mphysicsnormal_s {\n\tvec3_t             normal;\n\tint                flags;\n} mphysicsnormal_t;\n\n#define PHYSICSNORMAL_SET      1\n#define PHYSICSNORMAL_FLIPX    2\n#define PHYSICSNORMAL_FLIPY    4\n#define PHYSICSNORMAL_FLIPZ    8\n\nmphysicsnormal_t CM_PhysicsNormal(int num);\n#ifndef SERVER_ONLY\nvoid CM_PhysicsNormalSet(int num, float x, float y, float z, int flags);\nvoid CM_PhysicsNormalDump(FILE* out, float rampjump, float maxgroundspeed);\n#endif\n\ntypedef struct {\n\tqbool   allsolid;           // if true, plane is not valid\n\tqbool   startsolid;         // if true, the initial point was in a solid area\n\tqbool   inopen, inwater;\n\tfloat   fraction;           // time completed, 1.0 = didn't hit anything\n\tvec3_t  endpos;             // final position\n\tplane_t plane;              // surface normal at impact\n\tint     physicsnormal;      // surface normal for physics\n\tunion {                     // entity the surface is on\n\t\tint entnum;             // for pmove\n\t\tstruct edict_s *ent;    // for sv_world\n\t} e;\n} trace_t;\n\ntypedef struct {\n\tvec3_t\tmins, maxs;\n\tvec3_t\torigin;\n\thull_t\thulls[MAX_MAP_HULLS];\n} cmodel_t;\n\nhull_t *CM_HullForBox (vec3_t mins, vec3_t maxs);\nint CM_HullPointContents (hull_t *hull, int num, vec3_t p);\nint CM_CachedHullPointContents(hull_t* hull, int num, vec3_t p, float* min_dist);\ntrace_t CM_HullTrace (hull_t *hull, vec3_t start, vec3_t end);\nstruct cleaf_s *CM_PointInLeaf (const vec3_t p);\nint CM_Leafnum (const struct cleaf_s *leaf);\nint CM_LeafAmbientLevel (const struct cleaf_s *leaf, int ambient_channel);\nbyte *CM_LeafPVS (const struct cleaf_s *leaf);\nbyte *CM_LeafPHS (const struct cleaf_s *leaf); // only for the server\nbyte *CM_FatPVS (vec3_t org);\nint CM_FindTouchedLeafs (const vec3_t mins, const vec3_t maxs, int leafs[], int maxleafs, int headnode, int *topnode);\nchar *CM_EntityString (void);\nint CM_NumInlineModels (void);\ncmodel_t *CM_InlineModel (char *name);\nvoid CM_InvalidateMap (void);\ncmodel_t *CM_LoadMap (char *name, qbool clientload, unsigned *checksum, unsigned *checksum2);\nvoid CM_Init (void);\n\ntypedef struct bspx_header_s {\n\tchar id[4];  // 'BSPX'\n\tint numlumps;\n} bspx_header_t;\n\n#ifndef SERVERONLY\nvoid* Mod_BSPX_FindLump(bspx_header_t* bspx_header, char* lumpname, int* plumpsize, byte* mod_base);\nbspx_header_t* Mod_LoadBSPX(int filesize, byte* mod_base);\n#endif\n\n#endif /* !__CMODEL_H__ */\n"
  },
  {
    "path": "src/collision.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n#include \"quakedef.h\"\n#include \"pmove.h\"\n\nfloat CL_TraceLine (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal)\n{\n\ttrace_t trace = PM_TraceLine (start, end); /* PM_TraceLine hits bmodels and players */\n\tVectorCopy (trace.endpos, impact);\n\tif (normal)\n\t\tVectorCopy (trace.plane.normal, normal);\n\n\treturn 0.0;\n}\n"
  },
  {
    "path": "src/com_msg.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: com_msg.c,v 1.6 2006-07-24 15:13:28 disconn3ct Exp $\n*/\n\n#include \"quakedef.h\"\n\n/*\n==============================================================================\n                          MESSAGE IO FUNCTIONS\n==============================================================================\nHandles byte ordering and avoids alignment errors\n*/\n\n#ifdef FTE_PEXT_FLOATCOORDS\n\nint msg_coordsize = 2; // 2 or 4.\nint msg_anglesize = 1; // 1 or 2.\n\nfloat MSG_FromCoord(coorddata c, int bytes)\n{\n\tswitch(bytes)\n\t{\n\tcase 2:\t//encode 1/8th precision, giving -4096 to 4096 map sizes\n\t\treturn LittleShort(c.b2)/8.0f;\n\tcase 4:\n\t\treturn LittleFloat(c.f);\n\tdefault:\n\t\tHost_Error(\"MSG_FromCoord: not a sane size\");\n\t\treturn 0;\n\t}\n}\n\ncoorddata MSG_ToCoord(float f, int bytes)\t//return value should be treated as (char*)&ret;\n{\n\tcoorddata r;\n\tswitch(bytes)\n\t{\n\tcase 2:\n\t\tr.b4 = 0;\n\t\tif (f >= 0)\n\t\t\tr.b2 = LittleShort((short)(f*8+0.5f));\n\t\telse\n\t\t\tr.b2 = LittleShort((short)(f*8-0.5f));\n\t\tbreak;\n\tcase 4:\n\t\tr.f = LittleFloat(f);\n\t\tbreak;\n\tdefault:\n\t\tHost_Error(\"MSG_ToCoord: not a sane size\");\n\t\tr.b4 = 0;\n\t}\n\n\treturn r;\n}\n\ncoorddata MSG_ToAngle(float f, int bytes)\t//return value is NOT byteswapped.\n{\n\tcoorddata r;\n\tswitch(bytes)\n\t{\n\tcase 1:\n\t\tr.b4 = 0;\n\t\tif (f >= 0)\n\t\t\tr.b[0] = (int)(f*(256.0f/360.0f) + 0.5f) & 255;\n\t\telse\n\t\t\tr.b[0] = (int)(f*(256.0f/360.0f) - 0.5f) & 255;\n\t\tbreak;\n\tcase 2:\n\t\tr.b4 = 0;\n\t\tif (f >= 0)\n\t\t\tr.b2 = LittleShort((int)(f*(65536.0f/360.0f) + 0.5f) & 65535);\n\t\telse\n\t\t\tr.b2 = LittleShort((int)(f*(65536.0f/360.0f) - 0.5f) & 65535);\n\t\tbreak;\n//\tcase 4:\n//\t\tr.f = LittleFloat(f);\n//\t\tbreak;\n\tdefault:\n\t\tHost_Error(\"MSG_ToAngle: not a sane size\");\n\t\tr.b4 = 0;\n\t}\n\n\treturn r;\n}\n\n#endif\n\n// writing functions\nvoid MSG_WriteChar (sizebuf_t *sb, int c)\n{\n\tbyte *buf;\n\n#ifdef PARANOID\n\tif (c < -128 || c > 127)\n\t\tSys_Error (\"MSG_WriteChar: range error\");\n#endif\n\n\tbuf = SZ_GetSpace (sb, 1);\n\tbuf[0] = c;\n}\n\nvoid MSG_WriteByte (sizebuf_t *sb, int c)\n{\n\tbyte *buf;\n\n#ifdef PARANOID\n\tif (c < 0 || c > 255)\n\t\tSys_Error (\"MSG_WriteByte: range error\");\n#endif\n\n\tbuf = SZ_GetSpace (sb, 1);\n\tbuf[0] = c;\n}\n\nvoid MSG_WriteShort (sizebuf_t *sb, int c)\n{\n\tbyte *buf;\n\n#ifdef PARANOID\n\tif (c < ((short)0x8000) || c > (short)0x7fff)\n\t\tSys_Error (\"MSG_WriteShort: range error\");\n#endif\n\n\tbuf = SZ_GetSpace (sb, 2);\n\tbuf[0] = c & 0xff;\n\tbuf[1] = c >> 8;\n}\n\nvoid MSG_WriteLong (sizebuf_t *sb, int c)\n{\n\tbyte *buf;\n\n\tbuf = SZ_GetSpace (sb, 4);\n\tbuf[0] = c & 0xff;\n\tbuf[1] = (c >> 8) & 0xff;\n\tbuf[2] = (c >> 16) & 0xff;\n\tbuf[3] = c >> 24;\n}\n\nvoid MSG_WriteFloat (sizebuf_t *sb, float f)\n{\n\tunion {\n\t\tfloat\tf;\n\t\tint\tl;\n\t} dat;\n\n\tdat.f = f;\n\tdat.l = LittleLong (dat.l);\n\n\tSZ_Write (sb, &dat.l, 4);\n}\n\nvoid MSG_WriteString (sizebuf_t *sb, const char *s)\n{\n\tif (!s || !*s)\n\t\tSZ_Write (sb, \"\", 1);\n\telse\n\t\tSZ_Write (sb, s, strlen(s)+1);\n}\n\nvoid MSG_WriteUnterminatedString (sizebuf_t *sb, char *s)\n{\n\tif (s && *s)\n\t\tSZ_Write (sb, s, strlen(s));\n}\n\nvoid MSG_WriteCoord (sizebuf_t *sb, float f)\n{\n#ifdef FTE_PEXT_FLOATCOORDS\n\tcoorddata i = MSG_ToCoord(f, msg_coordsize);\n\tSZ_Write (sb, (void*)&i, msg_coordsize);\n#else\n\tMSG_WriteShort (sb, (int)(f * 8));\n#endif\n}\n\nvoid MSG_WriteAngle16 (sizebuf_t *sb, float f)\n{\n\tMSG_WriteShort (sb, Q_rint(f * 65536.0 / 360.0) & 65535);\n}\n\nvoid MSG_WriteAngle (sizebuf_t *sb, float f)\n{\n#ifdef FTE_PEXT_FLOATCOORDS\n\tif (msg_anglesize == 2)\n\t\tMSG_WriteAngle16(sb, f);\n//\telse if (msg_anglesize==4)\n//\t\tMSG_WriteFloat(sb, f);\n\telse\n#endif\n\t\tMSG_WriteByte (sb, Q_rint(f * 256.0 / 360.0) & 255);\n}\n\nint MSG_WriteDeltaUsercmd(sizebuf_t *buf, struct usercmd_s *from, struct usercmd_s *cmd, unsigned int mvdsv_extensions)\n{\n\tint bits;\n\n\t// send the movement message\n\tbits = 0;\n\tif (cmd->angles[0] != from->angles[0])\n\t\tbits |= CM_ANGLE1;\n\tif (cmd->angles[1] != from->angles[1])\n\t\tbits |= CM_ANGLE2;\n\tif (cmd->angles[2] != from->angles[2])\n\t\tbits |= CM_ANGLE3;\n\tif (cmd->forwardmove != from->forwardmove)\n\t\tbits |= CM_FORWARD;\n\tif (cmd->sidemove != from->sidemove)\n\t\tbits |= CM_SIDE;\n\tif (cmd->upmove != from->upmove)\n\t\tbits |= CM_UP;\n\tif (cmd->buttons != from->buttons)\n\t\tbits |= CM_BUTTONS;\n\tif (cmd->impulse != from->impulse)\n\t\tbits |= CM_IMPULSE;\n\n\tMSG_WriteByte (buf, bits);\n\n\tif (bits & CM_ANGLE1)\n\t\tMSG_WriteAngle16 (buf, cmd->angles[0]);\n\tif (bits & CM_ANGLE2)\n\t\tMSG_WriteAngle16 (buf, cmd->angles[1]);\n\tif (bits & CM_ANGLE3)\n\t\tMSG_WriteAngle16 (buf, cmd->angles[2]);\n\n\tif (bits & CM_FORWARD)\n\t\tMSG_WriteShort (buf, cmd->forwardmove);\n\tif (bits & CM_SIDE)\n\t\tMSG_WriteShort (buf, cmd->sidemove);\n\tif (bits & CM_UP)\n\t\tMSG_WriteShort (buf, cmd->upmove);\n\n\tif (bits & CM_BUTTONS)\n\t\tMSG_WriteByte (buf, cmd->buttons);\n\tif (bits & CM_IMPULSE) {\n\t\tMSG_WriteByte(buf, cmd->impulse);\n\t}\n\tMSG_WriteByte (buf, cmd->msec);\n\n\treturn bits;\n}\n\n//Writes part of a packetentities message.\n//Can delta from either a baseline or a previous packet_entity\nvoid MSG_WriteDeltaEntity (entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qbool force, unsigned int fte_extensions, unsigned int mvdsv_extensions)\n{\n\tint bits, i;\n#ifdef PROTOCOL_VERSION_FTE\n\tint evenmorebits = 0;\n\tunsigned int required_extensions = 0;\n#endif\n\n\t// send an update\n\tbits = 0;\n\n\tfor (i=0 ; i<3 ; i++)\n\t\tif ( to->origin[i] != from->origin[i] )\n\t\t\tbits |= U_ORIGIN1<<i;\n\n\tif ( to->angles[0] != from->angles[0] )\n\t\tbits |= U_ANGLE1;\n\n\tif ( to->angles[1] != from->angles[1] )\n\t\tbits |= U_ANGLE2;\n\n\tif ( to->angles[2] != from->angles[2] )\n\t\tbits |= U_ANGLE3;\n\n\tif ( to->colormap != from->colormap )\n\t\tbits |= U_COLORMAP;\n\n\tif ( to->skinnum != from->skinnum )\n\t\tbits |= U_SKIN;\n\n\tif ( to->frame != from->frame )\n\t\tbits |= U_FRAME;\n\n\tif ( to->effects != from->effects )\n\t\tbits |= U_EFFECTS;\n\n\tif (to->modelindex != from->modelindex) {\n\t\tbits |= U_MODEL;\n#ifdef FTE_PEXT_ENTITYDBL\n\t\tif (to->modelindex > 255) {\n\t\t\tif (to->modelindex > 512) {\n\t\t\t\tbits &= ~U_MODEL;\n\t\t\t}\n\t\t\tevenmorebits |= U_FTE_MODELDBL;\n\t\t\trequired_extensions |= FTE_PEXT_MODELDBL;\n\t\t}\n#endif\n\t}\n\n\tif (bits & U_CHECKMOREBITS)\n\t\tbits |= U_MOREBITS;\n\n\tif (to->flags & U_SOLID)\n\t\tbits |= U_SOLID;\n\n\t// Taken from FTE\n\tif (msg->cursize + 40 > msg->maxsize && !msg->overflow_handler)\n\t{\n\t\t//not enough space in the buffer, don't send the entity this frame. (not sending means nothing changes, and it takes no bytes!!)\n\t\tint oldnum = to->number;\n\t\t*to = *from;\n\t\tif (oldnum && !from->number) {\n\t\t\tto->number = oldnum;\n\t\t}\n\t\treturn;\n\t}\n\n\t//\n\t// write the message\n\t//\n\tif (!to->number) {\n\t\tSys_Error(\"Unset entity number\");\n\t}\n\n#ifdef PROTOCOL_VERSION_FTE\n\tif (to->number >= 512)\n\t{\n\t\tif (to->number >= 1024)\n\t\t{\n\t\t\tif (to->number >= 1024 + 512) {\n\t\t\t\tevenmorebits |= U_FTE_ENTITYDBL;\n\t\t\t\trequired_extensions |= FTE_PEXT_ENTITYDBL;\n\t\t\t}\n\n\t\t\tevenmorebits |= U_FTE_ENTITYDBL2;\n\t\t\trequired_extensions |= FTE_PEXT_ENTITYDBL2;\n\t\t\tif (to->number >= 2048) {\n\t\t\t\tSys_Error(\"Entity number >= 2048\");\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tevenmorebits |= U_FTE_ENTITYDBL;\n\t\t\trequired_extensions |= FTE_PEXT_ENTITYDBL;\n\t\t}\n\t}\n\n#ifdef U_FTE_TRANS\n\tif (to->trans != from->trans && (fte_extensions & FTE_PEXT_TRANS)) {\n\t\tevenmorebits |= U_FTE_TRANS;\n\t\trequired_extensions |= FTE_PEXT_TRANS;\n    }\n#endif\n\n#ifdef U_FTE_COLOURMOD\n\tif ((to->colourmod[0] != from->colourmod[0] ||\n\t\t to->colourmod[1] != from->colourmod[1] ||\n\t\t to->colourmod[2] != from->colourmod[2]) && (fte_extensions & FTE_PEXT_COLOURMOD)) {\n\t\tevenmorebits |= U_FTE_COLOURMOD;\n\t\trequired_extensions |= FTE_PEXT_COLOURMOD;\n    }\n#endif\n\n\tif (evenmorebits&0xff00)\n\t\tevenmorebits |= U_FTE_YETMORE;\n\tif (evenmorebits&0x00ff)\n\t\tbits |= U_FTE_EVENMORE;\n\tif (bits & 511)\n\t\tbits |= U_MOREBITS;\n#endif\n\n\tif (to->number >= MAX_EDICTS)\n\t{\n\t\t/*SV_Error*/\n\t\tCon_Printf (\"Entity number >= MAX_EDICTS (%d), set to MAX_EDICTS - 1\\n\", MAX_EDICTS);\n\t\tto->number = MAX_EDICTS - 1;\n\t}\n\n#ifdef PROTOCOL_VERSION_FTE\n\tif (evenmorebits && (fte_extensions & required_extensions) != required_extensions) {\n\t\treturn;\n\t}\n#endif\n\tif (!bits && !force) {\n\t\treturn;\t\t// nothing to send!\n\t}\n\n\ti = (to->number & U_CHECKMOREBITS) | (bits&~U_CHECKMOREBITS);\n\tif (i & U_REMOVE)\n\t\tSys_Error (\"U_REMOVE\");\n\tMSG_WriteShort (msg, i);\n\n\tif (bits & U_MOREBITS)\n\t\tMSG_WriteByte (msg, bits&255);\n#ifdef PROTOCOL_VERSION_FTE\n\tif (bits & U_FTE_EVENMORE)\n\t\tMSG_WriteByte (msg, evenmorebits&255);\n\tif (evenmorebits & U_FTE_YETMORE)\n\t\tMSG_WriteByte (msg, (evenmorebits>>8)&255);\n#endif\n\n\tif (bits & U_MODEL)\n\t\tMSG_WriteByte (msg, to->modelindex & 255);\n\telse if (evenmorebits & U_FTE_MODELDBL)\n\t\tMSG_WriteShort(msg, to->modelindex);\n\tif (bits & U_FRAME)\n\t\tMSG_WriteByte (msg, to->frame);\n\tif (bits & U_COLORMAP)\n\t\tMSG_WriteByte (msg, to->colormap);\n\tif (bits & U_SKIN)\n\t\tMSG_WriteByte (msg, to->skinnum);\n\tif (bits & U_EFFECTS)\n\t\tMSG_WriteByte (msg, to->effects);\n\tif (bits & U_ORIGIN1) {\n\t\tif (mvdsv_extensions & MVD_PEXT1_FLOATCOORDS) {\n\t\t\tMSG_WriteLongCoord(msg, to->origin[0]);\n\t\t}\n\t\telse {\n\t\t\tMSG_WriteCoord(msg, to->origin[0]);\n\t\t}\n\t}\n\tif (bits & U_ANGLE1)\n\t\tMSG_WriteAngle(msg, to->angles[0]);\n\tif (bits & U_ORIGIN2) {\n\t\tif (mvdsv_extensions & MVD_PEXT1_FLOATCOORDS) {\n\t\t\tMSG_WriteLongCoord(msg, to->origin[1]);\n\t\t}\n\t\telse {\n\t\t\tMSG_WriteCoord(msg, to->origin[1]);\n\t\t}\n\t}\n\tif (bits & U_ANGLE2)\n\t\tMSG_WriteAngle(msg, to->angles[1]);\n\tif (bits & U_ORIGIN3) {\n\t\tif (mvdsv_extensions & MVD_PEXT1_FLOATCOORDS) {\n\t\t\tMSG_WriteLongCoord(msg, to->origin[2]);\n\t\t}\n\t\telse {\n\t\t\tMSG_WriteCoord(msg, to->origin[2]);\n\t\t}\n\t}\n\tif (bits & U_ANGLE3)\n\t\tMSG_WriteAngle(msg, to->angles[2]);\n\n#ifdef U_FTE_TRANS\n\tif (evenmorebits & U_FTE_TRANS)\n\t\tMSG_WriteByte (msg, to->trans);\n#endif\n\n#ifdef U_FTE_COLOURMOD\n\tif (evenmorebits & U_FTE_COLOURMOD)\n\t{\n\t\tMSG_WriteByte (msg, to->colourmod[0]);\n\t\tMSG_WriteByte (msg, to->colourmod[1]);\n\t\tMSG_WriteByte (msg, to->colourmod[2]);\n\t}\n#endif\n}\n\n/********************************** READING **********************************/\n\nint msg_readcount;\nqbool msg_badread;\n\nvoid MSG_BeginReading (void)\n{\n\tmsg_readcount = 0;\n\tmsg_badread = false;\n}\n\nint MSG_GetReadCount(void)\n{\n\treturn msg_readcount;\n}\n\n// returns -1 and sets msg_badread if no more characters are available\nint MSG_ReadChar (void)\n{\n\tint\tc;\n\n\tif (msg_readcount + 1 > net_message.cursize) {\n\t\tmsg_badread = true;\n\t\treturn -1;\n\t}\n\n\tc = (signed char)net_message.data[msg_readcount];\n\tmsg_readcount++;\n\n\treturn c;\n}\n\nint MSG_ReadByte (void)\n{\n\tint\tc;\n\n\tif (msg_readcount + 1 > net_message.cursize) {\n\t\tmsg_badread = true;\n\t\treturn -1;\n\t}\n\n\tc = (unsigned char)net_message.data[msg_readcount];\n\tmsg_readcount++;\n\n\treturn c;\n}\n\nint MSG_ReadShort (void)\n{\n\tint\tc;\n\n\tif (msg_readcount + 2 > net_message.cursize) {\n\t\tmsg_badread = true;\n\t\treturn -1;\n\t}\n\n\tc = (short)(net_message.data[msg_readcount]\n\t\t+ (net_message.data[msg_readcount + 1] << 8));\n\n\tmsg_readcount += 2;\n\n\treturn c;\n}\n\nint MSG_ReadLong (void)\n{\n\tint\tc;\n\n\tif (msg_readcount + 4 > net_message.cursize) {\n\t\tmsg_badread = true;\n\t\treturn -1;\n\t}\n\n\tc = net_message.data[msg_readcount]\n\t\t+ (net_message.data[msg_readcount + 1] << 8)\n\t\t+ (net_message.data[msg_readcount + 2] << 16)\n\t\t+ (net_message.data[msg_readcount + 3] << 24);\n\n\tmsg_readcount += 4;\n\n\treturn c;\n}\n\nfloat MSG_ReadFloat (void)\n{\n\tunion {\n\t\tbyte b[4];\n\t\tfloat f;\n\t\tint\tl;\n\t} dat;\n\n\tdat.b[0] = net_message.data[msg_readcount];\n\tdat.b[1] = net_message.data[msg_readcount + 1];\n\tdat.b[2] = net_message.data[msg_readcount + 2];\n\tdat.b[3] = net_message.data[msg_readcount + 3];\n\tmsg_readcount += 4;\n\n\tdat.l = LittleLong (dat.l);\n\n\treturn dat.f;\n}\n\nchar *MSG_ReadString (void)\n{\n\tstatic char string[2048];\n\tunsigned int l;\n\tint c;\n\n\tl = 0;\n\tdo {\n\t\tc = MSG_ReadByte ();\n\n\t\tif (c == 255) // skip these to avoid security problems\n\t\t\tcontinue; // with old clients and servers\n\n\t\tif (c == -1 || c == 0) // msg_badread or end of string\n\t\t\tbreak;\n\n\t\tstring[l] = c;\n\t\tl++;\n\t} while (l < sizeof(string)-1);\n\n\tstring[l] = 0;\n\n\treturn string;\n}\n\nchar *MSG_ReadStringLine (void)\n{\n\tstatic char\tstring[2048];\n\tunsigned int l;\n\tint c;\n\n\tl = 0;\n\tdo {\n\t\tc = MSG_ReadByte ();\n\t\tif (c == 255)\n\t\t\tcontinue;\n\t\tif (c == -1 || c == 0 || c == '\\n')\n\t\t\tbreak;\n\t\tstring[l] = c;\n\t\tl++;\n\t} while (l < sizeof(string) - 1);\n\n\tstring[l] = 0;\n\n\treturn string;\n}\n\nfloat MSG_ReadCoord (void)\n{\n#ifdef FTE_PEXT_FLOATCOORDS\n\n\tcoorddata c = {0};\n\tMSG_ReadData(&c, msg_coordsize);\n\treturn MSG_FromCoord(c, msg_coordsize);\n\n#else // FTE_PEXT_FLOATCOORDS\n\n\treturn MSG_ReadShort() * (1.0 / 8);\n\n#endif // FTE_PEXT_FLOATCOORDS\n}\n\nfloat MSG_ReadFloatCoord(void)\n{\n\tfloat f;\n\tMSG_ReadData(&f, 4);\n\treturn LittleFloat(f);\n}\n\nvoid MSG_WriteLongCoord(sizebuf_t* sb, float f)\n{\n\tf = LittleFloat(f);\n\n\tMSG_WriteFloat(sb, f);\n}\n\nfloat MSG_ReadAngle16 (void)\n{\n\treturn MSG_ReadShort() * (360.0 / 65536);\n}\n\nfloat MSG_ReadAngle (void)\n{\n#ifdef FTE_PEXT_FLOATCOORDS\n\n\tswitch(msg_anglesize)\n\t{\n\tcase 1:\n\t\treturn MSG_ReadChar() * (360.0/256);\n\tcase 2:\n\t\treturn MSG_ReadAngle16();\n//\tcase 4:\n//\t\treturn MSG_ReadFloat();\n\tdefault:\n\t\tHost_Error(\"MSG_ReadAngle: Bad angle size\\n\");\n\t\treturn 0;\n\t}\n\n#else // FTE_PEXT_FLOATCOORDS\n\n\treturn MSG_ReadChar() * (360.0 / 256);\n\n#endif // FTE_PEXT_FLOATCOORDS\n}\n\n#define CM_MSEC\t(1 << 7) // same as CM_ANGLE2\n\nvoid MSG_ReadDeltaUsercmd (usercmd_t *from, usercmd_t *move, int protoversion)\n{\n\tint bits;\n\n\tmemcpy (move, from, sizeof(*move));\n\n\tbits = MSG_ReadByte ();\n\n\tif (protoversion <= 26) {\n\t\t// read current angles\n\t\tif (bits & CM_ANGLE1)\n\t\t\tmove->angles[0] = MSG_ReadAngle16 ();\n\t\tmove->angles[1] = MSG_ReadAngle16 (); // always sent\n\t\tif (bits & CM_ANGLE3)\n\t\t\tmove->angles[2] = MSG_ReadAngle16 ();\n\n\t\t// read movement\n\t\tif (bits & CM_FORWARD)\n\t\t\tmove->forwardmove = MSG_ReadChar() << 3;\n\t\tif (bits & CM_SIDE)\n\t\t\tmove->sidemove = MSG_ReadChar() << 3;\n\t\tif (bits & CM_UP)\n\t\t\tmove->upmove = MSG_ReadChar() << 3;\n\t} else {\n\t\t// read current angles\n\t\tif (bits & CM_ANGLE1)\n\t\t\tmove->angles[0] = MSG_ReadAngle16 ();\n\t\tif (bits & CM_ANGLE2)\n\t\t\tmove->angles[1] = MSG_ReadAngle16 ();\n\t\tif (bits & CM_ANGLE3)\n\t\t\tmove->angles[2] = MSG_ReadAngle16 ();\n\n\t\t// read movement\n\t\tif (bits & CM_FORWARD)\n\t\t\tmove->forwardmove = MSG_ReadShort ();\n\t\tif (bits & CM_SIDE)\n\t\t\tmove->sidemove = MSG_ReadShort ();\n\t\tif (bits & CM_UP)\n\t\t\tmove->upmove = MSG_ReadShort ();\n\t}\n\n\t// read buttons\n\tif (bits & CM_BUTTONS)\n\t\tmove->buttons = MSG_ReadByte ();\n\n\tif (bits & CM_IMPULSE)\n\t\tmove->impulse = MSG_ReadByte ();\n\n\t// read time to run command\n\tif (protoversion <= 26 && (bits & CM_MSEC)) {\n\t\tmove->msec = MSG_ReadByte ();\n\t} \n\telse if (protoversion >= 27) {\n\t\tmove->msec = MSG_ReadByte (); // always sent\n\t}\n}\n\nvoid MSG_ReadData (void *data, int len)\n{\n\tint\ti;\n\n\tfor (i = 0 ; i < len ; i++)\n\t\t((byte *)data)[i] = MSG_ReadByte ();\n}\n\nvoid MSG_ReadSkip(int bytes)\n{\n\tfor ( ; !msg_badread && bytes > 0; bytes--)\n\t{\n\t\tMSG_ReadByte ();\n\t}\n}\n\n#ifndef SERVERONLY\nbyte MSG_EncodeMVDSVWeaponFlags(int deathmatch, int weaponmode, int weaponhide, qbool weaponhide_axe, qbool forgetorder, qbool forgetondeath, int max_impulse)\n{\n\tbyte flags = clc_mvd_weapon_switching;\n\tqbool hide = ((weaponhide == 1) || ((weaponhide == 2) && (deathmatch == 1)));\n\n\tif (weaponmode == 3) {\n\t\tweaponmode = (deathmatch == 1 ? 1 : 0);\n\t}\n\telse if (weaponmode == 4) {\n\t\tweaponmode = (deathmatch == 1 ? 2 : 0);\n\t}\n\n\tif (weaponmode == 1) {\n\t\tflags |= clc_mvd_weapon_mode_presel;\n\t}\n\telse if (weaponmode == 2) {\n\t\tflags |= clc_mvd_weapon_mode_iffiring;\n\t}\n\n\tif (forgetorder) {\n\t\tflags |= clc_mvd_weapon_forget_ranking;\n\t}\n\n\tif (hide) {\n\t\tif (weaponhide_axe) {\n\t\t\tflags |= clc_mvd_weapon_hide_axe;\n\t\t}\n\t\telse {\n\t\t\tflags |= clc_mvd_weapon_hide_sg;\n\t\t}\n\t}\n\n\tflags |= (forgetondeath ? clc_mvd_weapon_reset_on_death : 0);\n\n\tflags |= (max_impulse >= 16 ? clc_mvd_weapon_full_impulse : 0);\n\n\treturn flags;\n}\n#endif\n\nvoid MSG_DecodeMVDSVWeaponFlags(int flags, int* weaponmode, int* weaponhide, qbool* forgetorder, int* sequence)\n{\n\n}\n\n"
  },
  {
    "path": "src/common.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: common.c,v 1.108 2007-10-13 15:36:55 cokeman1982 Exp $\n\n*/\n\n#include \"common.h\"\n\n#ifdef _WIN32\n#include <direct.h>\n#include <shlobj.h>\n#include <errno.h>\n#else\n#include <stdio.h>\n#include <unistd.h>\n#endif\n\n#include \"quakedef.h\"\n\n#include \"utils.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"crc.h\"\n#include \"stats_grid.h\"\n#include \"tp_triggers.h\"\n#include \"fs.h\"\n#include \"rulesets.h\"\n\ntypedef struct commandline_option_s {\n\tconst char* name;\n\tint position;\n} commandline_option_t;\n\n#define CMDLINE_DEF(x, str) { str, 0 }\n\nstatic commandline_option_t commandline_parameters[num_cmdline_params] = {\n#include \"cmdline_params_ids.h\"\n};\n\n#undef CMDLINE_DEF\n\nconst char* Cmd_CommandLineParamName(cmdline_param_id id)\n{\n\treturn commandline_parameters[id].name;\n}\n\nvoid Draw_BeginDisc (void);\nvoid Draw_EndDisc (void);\n\n#define MAX_NUM_ARGVS\t50\n\nusercmd_t nullcmd; // guaranteed to be zero\n\nstatic char\t*largv[MAX_NUM_ARGVS + 1];\n\ncvar_t\tdeveloper = {\"developer\", \"0\"};\ncvar_t\thost_mapname = {\"mapname\", \"\", CVAR_ROM};\n\nqbool com_serveractive = false;\n\n\n/*\nAll of Quake's data access is through a hierarchic file system, but the contents of the file system can be transparently merged from several sources.\n\nThe \"base directory\" is the path to the directory holding the quake.exe and all game directories.  The sys_* files pass this to host_init in quakeparms_t->basedir.  This can be overridden with the \"-basedir\" command line parm to allow code debugging in a different directory.  The base directory is\nonly used during filesystem initialization.\n\nThe \"game directory\" is the first tree on the search path and directory that all generated files (savegames, screenshots, demos, config files) will be saved to.  This can be overridden with the \"-game\" command line parameter.  The game directory can never be changed while quake is executing.  This is a precacution against having a malicious server instruct clients to write files over areas they shouldn't.\n*/\n\n//============================================================================\n\nvoid COM_StoreOriginalCmdline (int argc, char **argv)\n{\n\tchar buf[4096]; // enough?\n\tint i;\n\n\tbuf[0] = 0;\n\tstrlcat (buf, \" \", sizeof (buf));\n\n\tfor (i=0; i < argc; i++)\n\t\tstrlcat (buf, argv[i], sizeof (buf));\n\n\tcom_args_original = Q_strdup (buf);\n}\n\n//============================================================================\n\nconst char *COM_SkipPath (const char *pathname)\n{\n\tconst char *last;\n\tconst char *p;\n\n\tlast = p = pathname;\n\n\twhile (*p)\n\t{\n\t\tif (*p == '/' || *p == '\\\\')\n\t\t\tlast = p + 1;\n\n\t\tp++;\n\t}\n\n\treturn last;\n}\n\n//============================================================================\n\nchar *COM_SkipPathWritable (char *pathname)\n{\n\tchar *last;\n\tchar *p;\n\n\tlast = p = pathname;\n\n\twhile (*p)\n\t{\n\t\tif (*p == '/' || *p == '\\\\')\n\t\t\tlast = p + 1;\n\n\t\tp++;\n\t}\n\n\treturn last;\n}\n\n//\n// Makes a path fit in a specified sized string \"c:\\quake\\bla\\bla\\bla\" => \"c:\\quake...la\\bla\"\n//\nchar *COM_FitPath(char *dest, int destination_size, char *src, int size_to_fit)\n{\n\tif (!src)\n\t{\n\t\treturn dest;\n\t}\n\n\tif (size_to_fit > destination_size)\n\t{\n\t\tsize_to_fit = destination_size;\n\t}\n\n\tif (strlen(src) <= size_to_fit)\n\t{\n\t\t// Entire path fits in the destination.\n\t\tstrlcpy(dest, src, destination_size);\n\t}\n\telse\n\t{\n\t\t#define DOT_SIZE\t\t3\n\t\t#define MIN_LEFT_SIZE\t3\n\t\tint right_size = 0;\n\t\tint left_size = 0;\n\t\tint temp = 0;\n\t\tchar *right_dir = COM_SkipPathWritable(src);\n\n\t\t// Get the size of the right-most part of the path (filename/directory).\n\t\tright_size = strlen (right_dir);\n\n\t\t// Try to fit the dir/filename if possible.\n\t\ttemp = right_size + DOT_SIZE + MIN_LEFT_SIZE;\n\t\tright_size = (temp > size_to_fit) ? abs(right_size - (temp - size_to_fit)) : right_size;\n\n\t\t// Let the left have the rest.\n\t\tleft_size = size_to_fit - right_size - DOT_SIZE;\n\n\t\t// Only let the left have 35% of the total size.\n\t\tleft_size = min (left_size, Q_rint(0.35 * size_to_fit));\n\n\t\t// Get the final right size.\n\t\tright_size = size_to_fit - left_size - DOT_SIZE - 1;\n\n\t\t// Make sure we don't have negative values,\n\t\t// because then our safety checks won't work.\n\t\tleft_size = max (0, left_size);\n\t\tright_size = max (0, right_size);\n\n\t\tif (left_size < destination_size)\n\t\t{\n\t\t\tstrlcpy (dest, src, destination_size);\n\t\t\tstrlcpy (dest + left_size, \"...\", destination_size);\n\n\t\t\tif (left_size + DOT_SIZE + right_size < destination_size)\n\t\t\t{\n\t\t\t\tstrlcpy (dest + left_size + DOT_SIZE, src + strlen(src) - right_size, right_size + 1);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn dest;\n}\n\nvoid COM_StripExtension (const char *in, char *out, int out_size)\n{\n\tchar *dot;\n\n\tif (in != out)\n\t\tstrlcpy (out, in, out_size);\n\n\tdot = strrchr (out, '.');\n\tif (dot) {\n\t\t*dot = 0;\n\t}\n}\n\n\nchar *COM_FileExtension (const char *in)\n{\n\tstatic char exten[8];\n\tint i;\n\n\tif (!(in = strrchr(in, '.')))\n\t\treturn \"\";\n\tin++;\n\tfor (i = 0 ; i < 7 && *in ; i++,in++)\n\t\texten[i] = *in;\n\texten[i] = 0;\n\treturn exten;\n}\n\n//\n// Extract file name without extension, to be used for hunk tags (up to 32 characters, including trailing zero)\n//\nvoid COM_FileBase (const char *in, char *out)\n{\n\tconst char *start, *end;\n\tint\tlength;\n\n\tif (!(end = strrchr(in, '.')))\n\t\tend = in + strlen(in);\n\n\tif (!(start = strrchr(in, '/')))\n\t\tstart = in;\n\telse\n\t\tstart += 1;\n\n\tlength = end - start;\n\tif (length < 0)\n\t\tstrcpy (out, \"?empty filename?\");\n\telse if (length == 0)\n\t\tout[0] = 0;\n\telse {\n\t\tif (length > 31)\n\t\t\tlength = 31;\n\t\tmemcpy (out, start, length);\n\t\tout[length] = 0;\n\t}\n}\n\n//\n// If path doesn't have a .EXT, append extension (extension should include the .)\n//\nvoid COM_DefaultExtension (char *path, char *extension, size_t path_size)\n{\n\tchar *src;\n\n\tsrc = path + strlen(path) - 1;\n\n\twhile (*src != '/' && src != path) {\n\t\tif (*src == '.')\n\t\t\treturn;                 // it has an extension\n\t\tsrc--;\n\t}\n\n\tstrlcat (path, extension, path_size);\n}\n\n// If path doesn't have an extension or has a different extension, append(!) specified extension\n// Extension should include the .\n// path buffer supposed to be MAX_OSPATH size\nvoid COM_ForceExtension (char *path, char *extension)\n{\n\tchar *src;\n\n\tsrc = path + strlen(path) - strlen(extension);\n\tif (src >= path && !strcmp(src, extension))\n\t\treturn;\n\n\tstrlcat (path, extension, MAX_OSPATH);\n}\n\n// If path doesn't have an extension or has a different extension, append(!) specified extension\n// a bit extended version of COM_ForceExtension(), we suply size of path, so append safe, sure if u provide right path size\nvoid COM_ForceExtensionEx (char *path, char *extension, size_t path_size)\n{\n\tchar *src;\n\n\tif (path_size < 1 || path_size <= strlen (extension)) // too small buffer, can't even theoreticaly append extension\n\t\tSys_Error(\"COM_ForceExtensionEx: internall error\"); // this is looks like a bug, be fatal then\n\n\tsrc = path + strlen (path) - strlen (extension);\n\tif (src >= path && !strcmp (src, extension))\n\t\treturn; // seems we alredy have this extension\n\n\tstrlcat (path, extension, path_size);\n\n\tsrc = path + strlen (path) - strlen (extension);\n\tif (src >= path && !strcmp (src, extension))\n\t\treturn; // seems we succeed\n\n\tCom_Printf (\"Failed to force extension %s for %s\\n\", extension, path); // its good to know about failure\n}\n\n//\n// Get the path of a temporary directory.\n// Returns length of the path or negative value if error.\n//\nint COM_GetTempDir(char *buf, int bufsize)\n{\n\tint returnval = 0;\n#ifdef WIN32\n\treturnval = GetTempPath (bufsize, buf);\n\n\tif (returnval > bufsize || returnval == 0) {\n\t\treturn -1;\n\t}\n#else // UNIX\n\tchar *tmp;\n\tif (!(tmp = getenv (\"TMPDIR\")))\n\t\ttmp = P_tmpdir; // defined at <stdio.h>\n\n\treturnval = strlen (tmp);\n\n\tif (returnval > bufsize || returnval == 0) {\n\t\treturn -1;\n\t}\n\n\tstrlcpy (buf, tmp, bufsize);\n#endif // WIN32\n\n\treturn returnval;\n}\n\nqbool COM_WriteToUniqueTempFileVFS(char* path, int path_buffer_size, const char* ext, vfsfile_t* input)\n{\n\tsize_t bytes = VFS_GETLEN(input);\n\tbyte* buffer = Q_malloc(bytes);\n\tvfserrno_t err;\n\tqbool result;\n\n\tif (!buffer) {\n\t\treturn false;\n\t}\n\tVFS_READ(input, buffer, (int)bytes, &err);\n\tVFS_SEEK(input, 0, SEEK_SET);\n\n\tresult = COM_WriteToUniqueTempFile(path, path_buffer_size, ext, buffer, bytes);\n\tQ_free(buffer);\n\treturn result;\n}\n\n//\n// Get a unique temp filename.\n// Returns negative value on failure.\n//\nqbool COM_WriteToUniqueTempFile(char *path, int path_buffer_size, const char* ext, const byte* buffer, size_t bytes)\n{\n\tchar temp_path[MAX_OSPATH];\n\n\tif (ext == NULL) {\n\t\text = \"\";\n\t}\n\n\t// Get the path to temporary directory\n\tif (COM_GetTempDir(temp_path, sizeof(temp_path)) < 0) {\n\t\treturn false;\n\t}\n\n#ifdef _WIN32\n\t{\n\t\tGUID guid;\n\t\tchar* dot = \"\";\n\n\t\tdot = (ext[0] && ext[0] != '.' ? \".\" : \"\");\n\n\t\tif (path_buffer_size < MAX_PATH) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (CoCreateGuid(&guid) == S_OK) {\n\t\t\tif (path_buffer_size > snprintf(path, path_buffer_size, \"%s\\\\%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X%s%s\", temp_path, guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7], dot, ext)) {\n\t\t\t\tFS_WriteFile_2(path, buffer, (int)bytes);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n#else\n\t{\n\t\tint fd = 0;\n\t\tstrlcat(path, \"/ezquakeXXXXXX\", path_buffer_size);\n\t\tif (ext[0]) {\n\t\t\tint ext_len = 0;\n\n\t\t\tif (ext[0] != '.') {\n\t\t\t\tstrlcat(path, \".\", path_buffer_size);\n\t\t\t\tstrlcat(path, ext, path_buffer_size);\n\t\t\t\text_len = strlen(ext) + 1;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tstrlcat(path, ext, path_buffer_size);\n\t\t\t\text_len = strlen(ext);\n\t\t\t}\n\t\t\tfd = mkstemps(path, ext_len);\n\t\t}\n\t\telse {\n\t\t\tfd = mkstemp(path);\n\t\t}\n\n\t\tif (fd < 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\twrite(fd, buffer, bytes);\n\n\t\tclose(fd);\n\t\treturn true;\n\t}\n#endif\n}\n\nqbool COM_FileExists (char *path)\n{\n\tFILE *fexists = NULL;\n\n\t// Try opening the file to see if it exists.\n\tfexists = fopen(path, \"rb\");\n\n\t// The file exists.\n\tif (fexists)\n\t{\n\t\t// Make sure the file is closed.\n\t\tfclose (fexists);\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n\nchar\t\tcom_token[MAX_COM_TOKEN];\nstatic int\tcom_argc;\nstatic char\t**com_argv;\nchar \t\t*com_args_original;\n\ncom_tokentype_t com_tokentype;\n\n//Parse a token out of a string\nconst char* COM_ParseEx(const char *data, qbool curlybraces)\n{\n\tunsigned char c;\n\tint len;\n\tint quotes;\n\n\tlen = 0;\n\tcom_token[0] = 0;\n\n\tif (!data) {\n\t\treturn NULL;\n\t}\n\n\t// skip whitespace\n\twhile (true) {\n\t\twhile ((c = *data) == ' ' || c == '\\t' || c == '\\r' || c == '\\n') {\n\t\t\tdata++;\n\t\t}\n\n\t\tif (c == 0) {\n\t\t\treturn NULL;\t\t\t// end of file;\n\t\t}\n\n\t\t// skip // comments\n\t\tif (c == '/' && data[1] == '/') {\n\t\t\twhile (*data && *data != '\\n') {\n\t\t\t\tdata++;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// handle quoted strings specially\n\tif (c == '\\\"' || (c == '{' && curlybraces) ) {\n\t\tif (c == '{') {\n\t\t\tquotes = 1;\n\t\t}\n\t\telse {\n\t\t\tquotes = -1;\n\t\t}\n\t\tdata++;\n\t\twhile (1) {\n\t\t\tc = *data;\n\t\t\tdata++;\n\t\t\tif (quotes < 0) {\n\t\t\t\tif (c == '\\\"') {\n\t\t\t\t\tquotes++;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (c == '}' && curlybraces) {\n\t\t\t\t\tquotes--;\n\t\t\t\t}\n\t\t\t\telse if (c == '{' && curlybraces) {\n\t\t\t\t\tquotes++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!quotes || !c) {\n\t\t\t\tcom_token[len] = 0;\n\t\t\t\treturn c ? data : data - 1;\n\t\t\t}\n\t\t\tcom_token[len] = c;\n\t\t\tlen++;\n\n\t\t\tif (len >= sizeof(com_token) - 1) {\n\t\t\t\treturn NULL; // quoted section too long\n\t\t\t}\n\t\t}\n\t}\n\n\t// parse a regular word\n\tdo {\n\t\tcom_token[len] = c;\n\t\tdata++;\n\t\tlen++;\n\t\tif (len >= sizeof(com_token) - 1) {\n\t\t\tbreak;\n\t\t}\n\t\tc = *data;\n\t} while (c && c != ' ' && c != '\\t' && c != '\\n' && c != '\\r');\n\n\tcom_token[len] = 0;\n\treturn data;\n}\n\nconst char* COM_Parse(const char* data)\n{\n\treturn COM_ParseEx(data, false);\n}\n\n#define DEFAULT_PUNCTUATION \"(,{})(\\':;=!><&|+\"\nchar *COM_ParseToken (const char *data, const char *punctuation)\n{\n\tint c;\n\tint\tlen;\n\n\tif (!punctuation)\n\t\tpunctuation = DEFAULT_PUNCTUATION;\n\n\tlen = 0;\n\tcom_token[0] = 0;\n\n\tif (!data)\n\t{\n\t\tcom_tokentype = TTP_UNKNOWN;\n\t\treturn NULL;\n\t}\n\n\t// skip whitespace\nskipwhite:\n\twhile ((c = *(unsigned char *) data) <= ' ')\n\t{\n\t\tif (c == 0)\n\t\t{\n\t\t\tcom_tokentype = TTP_UNKNOWN;\n\t\t\treturn NULL; // end of file;\n\t\t}\n\n\t\tdata++;\n\t}\n\n\t// skip // comments\n\tif (c == '/')\n\t{\n\t\tif (data[1] == '/')\n\t\t{\n\t\t\twhile (*data && *data != '\\n')\n\t\t\t\tdata++;\n\n\t\t\tgoto skipwhite;\n\t\t}\n\t\telse if (data[1] == '*')\n\t\t{\n\t\t\tdata += 2;\n\n\t\t\twhile (*data && (*data != '*' || data[1] != '/'))\n\t\t\t\tdata++;\n\n\t\t\tdata += 2;\n\t\t\tgoto skipwhite;\n\t\t}\n\t}\n\n\n\t// handle quoted strings specially\n\tif (c == '\\\"')\n\t{\n\t\tcom_tokentype = TTP_STRING;\n\t\tdata++;\n\t\twhile (1)\n\t\t{\n\t\t\tif (len >= MAX_COM_TOKEN - 1)\n\t\t\t{\n\t\t\t\tcom_token[len] = '\\0';\n\t\t\t\treturn (char*) data;\n\t\t\t}\n\n\t\t\tc = *data++;\n\n\t\t\tif (c=='\\\"' || !c)\n\t\t\t{\n\t\t\t\tcom_token[len] = 0;\n\t\t\t\treturn (char*) data;\n\t\t\t}\n\n\t\t\tcom_token[len] = c;\n\t\t\tlen++;\n\t\t}\n\t}\n\n\tcom_tokentype = TTP_UNKNOWN;\n\n\t// parse single characters\n\tif (strchr (punctuation, c))\n\t{\n\t\tcom_token[len] = c;\n\t\tlen++;\n\t\tcom_token[len] = 0;\n\t\treturn (char*) (data + 1);\n\t}\n\n\t// parse a regular word\n\tdo\n\t{\n\t\tif (len >= MAX_COM_TOKEN - 1)\n\t\t\tbreak;\n\n\t\tcom_token[len] = c;\n\t\tdata++;\n\t\tlen++;\n\t\tc = *data;\n\t\tif (strchr (punctuation, c))\n\t\t\tbreak;\n\n\t} while (c > 32);\n\n\tcom_token[len] = 0;\n\treturn (char*) data;\n}\n\n//Adds the given string at the end of the current argument list\nvoid COM_AddParm (char *parm)\n{\n\tif (com_argc >= MAX_NUM_ARGVS)\n\t\treturn;\n\tlargv[com_argc++] = parm;\n}\n\n//Returns the position (1 to argc-1) in the program's argument list\n//where the given parameter appears, or 0 if not present\nint COM_FindParm(const char* parm)\n{\n\tint\t\ti;\n\n\tfor (i = 1; i < com_argc; i++) {\n\t\tif (!strcmp(parm, com_argv[i])) {\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint COM_CheckParm(cmdline_param_id id)\n{\n\tif (id < 0 || id >= num_cmdline_params) {\n\t\treturn 0;\n\t}\n\n\treturn commandline_parameters[id].position;\n}\n\n//Tei: added cos -data feature use it.\nint COM_CheckParmOffset(cmdline_param_id id, int offset)\n{\n\tint             i;\n\tconst char* parm;\n\n\tif (id < 0 || id >= num_cmdline_params) {\n\t\treturn 0;\n\t}\n\n\tparm = commandline_parameters[id].name;\n\tfor (i = offset; i < com_argc; i++) {\n\t\t// NEXTSTEP sometimes clears appkit vars.\n\t\tif (!com_argv[i]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!strcmp(parm, com_argv[i])) {\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn 0;\n}\n//qbism// 1999-12-23 Multiple \"-data\" parameters by Maddes  end\n\n\nint COM_Argc (void)\n{\n\treturn com_argc;\n}\n\nchar *COM_Argv(int arg)\n{\n\tif (arg < 0 || arg >= com_argc) {\n\t\treturn \"\";\n\t}\n\n\treturn com_argv[arg];\n}\n\nvoid COM_ClearArgv(int arg)\n{\n\tif (arg < 0 || arg >= com_argc) {\n\t\treturn;\n\t}\n\tcom_argv[arg] = \"\";\n}\n\nvoid COM_InitArgv(int argc, char **argv)\n{\n\tint id;\n\tint i;\n\n\tfor (i = 0, com_argc = 0; com_argc < MAX_NUM_ARGVS - 1 && i < argc; ++i) {\n\t\tif (argv[i]) {\n\t\t\t// follow qw urls if they are our argument without a +qwurl command\n\t\t\tif (!strncmp(argv[i], \"qw://\", 5) && (i == 0 || strncmp(argv[i - 1], \"+qwurl\", 6)) && com_argc < MAX_NUM_ARGVS - 1) {\n\t\t\t\tlargv[com_argc++] = \"+qwurl\";\n\t\t\t\tlargv[com_argc++] = argv[i];\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlargv[com_argc++] = argv[i];\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tlargv[com_argc++] = \"\";\n\t\t}\n\t}\n\n\tlargv[com_argc] = \"\";\n\tcom_argv = largv;\n\n\tfor (id = 0; id < num_cmdline_params; ++id) {\n\t\tcommandline_parameters[id].position = COM_FindParm(commandline_parameters[id].name);\n\t}\n}\n\nvoid COM_Init(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_NO_GROUP);\n\tCvar_Register(&developer);\n\tCvar_Register(&host_mapname);\n\n\tCvar_ResetCurrentGroup();\n}\n\n//does a varargs printf into a temp buffer, so I don't need to have varargs versions of all text functions.\nchar *va(const char *format, ...)\n{\n\tva_list argptr;\n\tstatic char string[32][2048];\n\tstatic int idx = 0;\n\n\tidx++;\n\tif (idx == 32) {\n\t\tidx = 0;\n\t}\n\n\tva_start (argptr, format);\n\tvsnprintf (string[idx], sizeof(string[idx]), format, argptr);\n\tva_end (argptr);\n\n\treturn string[idx];\n}\n\n// equals to consecutive calls of strtok(s, \" \") that assign values to array\nint COM_GetFloatTokens(const char *s, float *fl_array, int fl_array_size)\n{\n    int i;\n    if (!s || !*s) return 0;\n\n    for(i = 0; *s && i < fl_array_size; i++)\n    {\n        fl_array[i] = atof(s);\n        while(*s && *s != ' ') s++; // skips the number\n        while(*s && *s == ' ') s++; // skips the spaces\n    }\n\n    return i;\n}\n\n\n/*\n=====================================================================\n  INFO STRINGS\n=====================================================================\n*/\n\n//Searches the string for the given key and returns the associated value, or an empty string.\nchar *Info_ValueForKey (char *s, char *key) {\n\tchar pkey[512];\n\tstatic char value[4][512];\t// use two buffers so compares work without stomping on each other\n\tstatic int\tvalueindex;\n\tchar *o;\n\n\tvalueindex = (valueindex + 1) % 4;\n\tif (*s == '\\\\')\n\t\ts++;\n\twhile (1) {\n\t\to = pkey;\n\t\twhile (*s != '\\\\') {\n\t\t\tif (!*s)\n\t\t\t\treturn \"\";\n\t\t\t*o++ = *s++;\n\t\t}\n\t\t*o = 0;\n\t\ts++;\n\n\t\to = value[valueindex];\n\n\t\twhile (*s != '\\\\' && *s) {\n\t\t\tif (!*s)\n\t\t\t\treturn \"\";\n\t\t\t*o++ = *s++;\n\t\t}\n\t\t*o = 0;\n\n\t\tif (!strcmp (key, pkey) )\n\t\t\treturn value[valueindex];\n\n\t\tif (!*s)\n\t\t\treturn \"\";\n\t\ts++;\n\t}\n}\n\nvoid Info_RemoveKey (char *s, char *key) {\n\tchar *start, pkey[512], value[512], *o;\n\n\tif (strchr (key, '\\\\')) {\n\t\tCom_Printf (\"Can't use a key with a \\\\\\n\");\n\t\treturn;\n\t}\n\n\twhile (1) {\n\t\tstart = s;\n\t\tif (*s == '\\\\')\n\t\t\ts++;\n\t\to = pkey;\n\t\twhile (*s != '\\\\') {\n\t\t\tif (!*s)\n\t\t\t\treturn;\n\t\t\t*o++ = *s++;\n\t\t}\n\t\t*o = 0;\n\t\ts++;\n\n\t\to = value;\n\t\twhile (*s != '\\\\' && *s) {\n\t\t\tif (!*s)\n\t\t\t\treturn;\n\t\t\t*o++ = *s++;\n\t\t}\n\t\t*o = 0;\n\n\t\tif (!strcmp (key, pkey) ) {\n\t\t\tQ_strcpy (start, s);\t// remove this part\n\t\t\treturn;\n\t\t}\n\n\t\tif (!*s)\n\t\t\treturn;\n\t}\n\n}\n\nvoid Info_RemovePrefixedKeys (char *start, char prefix) {\n\tchar *s, pkey[512], value[512], *o;\n\n\ts = start;\n\n\twhile (1) {\n\t\tif (*s == '\\\\')\n\t\t\ts++;\n\t\to = pkey;\n\t\twhile (*s != '\\\\') {\n\t\t\tif (!*s)\n\t\t\t\treturn;\n\t\t\t*o++ = *s++;\n\t\t}\n\t\t*o = 0;\n\t\ts++;\n\n\t\to = value;\n\t\twhile (*s != '\\\\' && *s) {\n\t\t\tif (!*s)\n\t\t\t\treturn;\n\t\t\t*o++ = *s++;\n\t\t}\n\t\t*o = 0;\n\n\t\tif (pkey[0] == prefix) {\n\t\t\tInfo_RemoveKey (start, pkey);\n\t\t\ts = start;\n\t\t}\n\n\t\tif (!*s)\n\t\t\treturn;\n\t}\n\n}\n\nvoid Info_SetValueForStarKeyEx (char *s, char *key, char *value, int maxsize, qbool max_info_key_check)\n{\n\tchar new[1024], *v;\n\tint c;\n#ifndef CLIENTONLY\n//\textern cvar_t sv_highchars;\n#endif\n\n\tif (strchr (key, '\\\\') || strchr (value, '\\\\') ) {\n\t\tCom_Printf (\"Can't use keys or values with a \\\\\\n\");\n\t\treturn;\n\t}\n\n\tif (strchr (key, '\\\"') || strchr (value, '\\\"') ) {\n\t\tCom_Printf (\"Can't use keys or values with a \\\"\\n\");\n\t\treturn;\n\t}\n\n\tif (max_info_key_check)\n\t{\n\t\tif (strlen(key) >= MAX_INFO_KEY || strlen(value) >= MAX_INFO_KEY)\n\t\t{\n\t\t\tCom_Printf (\"Keys and values must be < %i characters.\\n\", MAX_INFO_KEY);\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// this next line is kinda trippy\n\tif (*(v = Info_ValueForKey(s, key))) {\n\t\t// key exists, make sure we have enough room for new value, if we don't,\n\t\t// don't change it!\n\t\tif ((int) (strlen(value) - strlen(v) + strlen(s)) >= maxsize) {\n\t\t\tCom_Printf (\"Info string length exceeded\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\tInfo_RemoveKey (s, key);\n\tif (!value || !strlen(value))\n\t\treturn;\n\n\tsnprintf (new, sizeof (new), \"\\\\%s\\\\%s\", key, value);\n\n\tif ((int) (strlen(new) + strlen(s)) >= maxsize) {\n\t\tCom_Printf (\"Info string length exceeded\\n\");\n\t\treturn;\n\t}\n\n\t// only copy ascii values\n\ts += strlen(s);\n\tv = new;\n#ifndef CLIENTONLY\n//\tif (!sv_highchars.value) {\n//\t\twhile (*v) {\n//\t\t\tc = (unsigned char)*v++;\n//\t\t\tif (c == ('\\\\'|128))\n//\t\t\t\tcontinue;\n//\t\t\tc &= 127;\n//\t\t\tif (c >= 32)\n//\t\t\t\t*s++ = c;\n//\t\t}\n//\t} else\n#endif\n\t{\n\t\twhile (*v) {\n\t\t\tc = (unsigned char)*v++;\n\t\t\tif (c > 13)\n\t\t\t\t*s++ = c;\n\t\t}\n\t\t*s = 0;\n\t}\n}\n\nvoid Info_SetValueForStarKey (char *s, char *key, char *value, int maxsize)\n{\n\tInfo_SetValueForStarKeyEx(s, key, value, maxsize, true);\n}\n\nvoid Info_SetValueForKeyEx (char *s, char *key, char *value, int maxsize, qbool max_info_key_check)\n{\n\tif (key[0] == '*')\n\t{\n\t\tCom_Printf (\"Can't set * keys\\n\");\n\t\treturn;\n\t}\n\n\tInfo_SetValueForStarKeyEx (s, key, value, maxsize, max_info_key_check);\n}\n\nvoid Info_SetValueForKey (char *s, char *key, char *value, int maxsize)\n{\n\tInfo_SetValueForKeyEx (s, key, value, maxsize, true);\n}\n\n#define INFO_PRINT_FIRST_COLUMN_WIDTH 20\nvoid Info_Print (char *s) {\n\tchar key[512], value[512], *o;\n\tint l;\n\n\tif (*s == '\\\\')\n\t\ts++;\n\twhile (*s) {\n\t\to = key;\n\t\twhile (*s && *s != '\\\\')\n\t\t\t*o++ = *s++;\n\n\t\tl = o - key;\n\t\tif (l < INFO_PRINT_FIRST_COLUMN_WIDTH) {\n\t\t\tmemset (o, ' ', INFO_PRINT_FIRST_COLUMN_WIDTH - l);\n\t\t\tkey[INFO_PRINT_FIRST_COLUMN_WIDTH] = 0;\n\t\t} else {\n\t\t\t*o = 0;\n\t\t}\n\t\tCom_Printf (\"%s\", key);\n\n\t\tif (!*s) {\n\t\t\tCom_Printf (\"MISSING VALUE\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\to = value;\n\t\ts++;\n\t\twhile (*s && *s != '\\\\')\n\t\t\t*o++ = *s++;\n\t\t*o = 0;\n\n\t\tif (*s)\n\t\t\ts++;\n\t\tCom_Printf (\"%s\\n\", value);\n\t}\n}\n\n//============================================================\n//\n// Alternative variant manipulation with info strings\n//\n//============================================================\n\n// this is seems to be \"better\" than Com_HashKey()\nstatic unsigned int Info_HashKey (const char *str)\n{\n\tunsigned int hash = 0;\n\tint c;\n\n\t// the (c&~32) makes it case-insensitive\n\t// hash function known as sdbm, used in gawk\n\twhile ((c = *str++))\n        hash = (c &~ 32) + (hash << 6) + (hash << 16) - hash;\n\n    return hash;\n}\n\n// used internally\nstatic info_t *_Info_Get (ctxinfo_t *ctx, const char *name)\n{\n\tinfo_t *a;\n\tint key;\n\n\tif (!ctx || !name || !name[0])\n\t\treturn NULL;\n\n\tkey = Info_HashKey (name) % INFO_HASHPOOL_SIZE;\n\n\tfor (a = ctx->info_hash[key]; a; a = a->hash_next)\n\t\tif (!strcasecmp(name, a->name))\n\t\t\treturn a;\n\n\treturn NULL;\n}\n\nchar *Info_Get(ctxinfo_t *ctx, const char *name)\n{\n\tstatic\tchar value[4][512];\n\tstatic\tint valueindex = 0;\n\n\tinfo_t *a = _Info_Get(ctx, name);\n\n\tif ( a )\n\t{\n\t\tvalueindex = (valueindex + 1) % 4;\n\n\t\tstrlcpy(value[valueindex], a->value, sizeof(value[0]));\n\n\t\treturn value[valueindex];\n\t}\n\telse\n\t{\n\t\treturn \"\";\n\t}\n}\n\nqbool Info_SetStar (ctxinfo_t *ctx, const char *name, const char *value)\n{\n\tinfo_t\t*a;\n\tint key;\n\n\tif (!value)\n\t\tvalue = \"\";\n\n\tif (!ctx || !name || !name[0])\n\t\treturn false;\n\n\t// empty value, instead of set just remove it\n\tif (!value[0])\n\t{\n\t\treturn Info_Remove(ctx, name);\n\t}\n\n\tif (strchr(name, '\\\\') || strchr(value, '\\\\'))\n\t\treturn false;\n\tif (strchr(name, 128 + '\\\\') || strchr(value, 128 + '\\\\'))\n\t\treturn false;\n\tif (strchr(name, '\"') || strchr(value, '\"'))\n\t\treturn false;\n\tif (strchr(name, '\\r') || strchr(value, '\\r')) // bad for print functions\n\t\treturn false;\n\tif (strchr(name, '\\n') || strchr(value, '\\n')) // bad for print functions\n\t\treturn false;\n\tif (strchr(name, '$') || strchr(value, '$')) // variable expansion may be exploited, escaping this\n\t\treturn false;\n\tif (strchr(name, ';') || strchr(value, ';')) // interpreter may be haxed, escaping this\n\t\treturn false;\n\n\tif (strlen(name) >= MAX_KEY_STRING || strlen(value) >= MAX_KEY_STRING)\n\t\treturn false; // too long name/value, its wrong\n\n\tkey = Info_HashKey(name) % INFO_HASHPOOL_SIZE;\n\n\t// if already exists, reuse it\n\tfor (a = ctx->info_hash[key]; a; a = a->hash_next)\n\t\tif (!strcasecmp(name, a->name))\n\t\t{\n\t\t\tQ_free (a->value);\n\t\t\tbreak;\n\t\t}\n\n\t// not found, create new one\n\tif (!a)\n\t{\n\t\tif (ctx->cur >= ctx->max)\n\t\t\treturn false; // too much infos\n\n\t\ta = (info_t *) Q_malloc (sizeof(info_t));\n\t\ta->next = ctx->info_list;\n\t\tctx->info_list = a;\n\t\ta->hash_next = ctx->info_hash[key];\n\t\tctx->info_hash[key] = a;\n\n\t\tctx->cur++; // increase counter\n\n\t\t// copy name\n\t\ta->name = Q_strdup (name);\n\t}\n\n\t// copy value\n#if 0\n\t{\n\t\t// unfortunatelly evil users use non printable/control chars, so that does not work well\n\t\ta->value = Q_strdup (value);\n\t}\n#else\n\t{\n\t\t// skip some control chars, doh\n\t\tchar v_buf[MAX_KEY_STRING] = {0}, *v = v_buf;\n\t\tint i;\n\n\t\tfor (i = 0; value[i]; i++) // len of 'value' should be less than MAX_KEY_STRING according to above checks\n\t\t{\n\t\t\tif ((unsigned char)value[i] > 13)\n\t\t\t\t*v++ = value[i];\n\t\t}\n\t\t*v = 0;\n\n\t\ta->value = Q_strdup (v_buf);\n\t}\n#endif\n\n\t// hrm, empty value, remove it then\n\tif (!a->value[0])\n\t{\n\t\treturn Info_Remove(ctx, name);\n\t}\n\n\treturn true;\n}\n\nqbool Info_Set (ctxinfo_t *ctx, const char *name, const char *value)\n{\n\tif (!value)\n\t\tvalue = \"\";\n\n\tif (!ctx || !name || !name[0])\n\t\treturn false;\n\n\tif (name[0] == '*')\n\t{\n\t\tCon_Printf (\"Can't set * keys\\n\");\n\t\treturn false;\n\t}\n\n\treturn Info_SetStar (ctx, name, value);\n}\n\n// used internally\nstatic void _Info_Free(info_t *a)\n{\n\tif (!a)\n\t\treturn;\n\n\tQ_free (a->name);\n\tQ_free (a->value);\n\tQ_free (a);\n}\n\nqbool Info_Remove (ctxinfo_t *ctx, const char *name)\n{\n\tinfo_t *a, *prev;\n\tint key;\n\n\tif (!ctx || !name || !name[0])\n\t\treturn false;\n\n\tkey = Info_HashKey (name) % INFO_HASHPOOL_SIZE;\n\n\tprev = NULL;\n\tfor (a = ctx->info_hash[key]; a; a = a->hash_next)\n\t{\n\t\tif (!strcasecmp(name, a->name))\n\t\t{\n\t\t\t// unlink from hash\n\t\t\tif (prev)\n\t\t\t\tprev->hash_next = a->hash_next;\n\t\t\telse\n\t\t\t\tctx->info_hash[key] = a->hash_next;\n\t\t\tbreak;\n\t\t}\n\t\tprev = a;\n\t}\n\n\tif (!a)\n\t\treturn false;\t// not found\n\n\tprev = NULL;\n\tfor (a = ctx->info_list; a; a = a->next)\n\t{\n\t\tif (!strcasecmp(name, a->name))\n\t\t{\n\t\t\t// unlink from info list\n\t\t\tif (prev)\n\t\t\t\tprev->next = a->next;\n\t\t\telse\n\t\t\t\tctx->info_list = a->next;\n\n\t\t\t// free\n\t\t\t_Info_Free(a);\n\n\t\t\tctx->cur--; // decrease counter\n\n\t\t\treturn true;\n\t\t}\n\t\tprev = a;\n\t}\n\n\tSys_Error(\"Info_Remove: info list broken\");\n\treturn false; // shut up compiler\n}\n\n// remove all infos\nvoid Info_RemoveAll (ctxinfo_t *ctx)\n{\n\tinfo_t\t*a, *next;\n\n\tif (!ctx)\n\t\treturn;\n\n\tfor (a = ctx->info_list; a; a = next) {\n\t\tnext = a->next;\n\n\t\t// free\n\t\t_Info_Free(a);\n\t}\n\tctx->info_list = NULL;\n\tctx->cur = 0; // set counter to 0\n\n\t// clear hash\n\tmemset (ctx->info_hash, 0, sizeof(ctx->info_hash));\n}\n\nqbool Info_Convert(ctxinfo_t *ctx, char *str)\n{\n\tchar name[MAX_KEY_STRING], value[MAX_KEY_STRING], *start;\n\n\tif (!ctx)\n\t\treturn false;\n\n\tfor ( ; str && str[0]; )\n\t{\n\t\tif (!(str = strchr(str, '\\\\')))\n\t\t\tbreak;\n\n\t\tstart = str; // start of name\n\n\t\tif (!(str = strchr(start + 1, '\\\\')))  // end of name\n\t\t\tbreak;\n\n\t\tstrlcpy(name, start + 1, min(str - start, (int)sizeof(name)));\n\n\t\tstart = str; // start of value\n\n\t\tstr = strchr(start + 1, '\\\\'); // end of value\n\n\t\tstrlcpy(value, start + 1, str ? min(str - start, (int)sizeof(value)) : (int)sizeof(value));\n\n\t\tInfo_SetStar(ctx, name, value);\n\t}\n\n\treturn true;\n}\n\nqbool Info_ReverseConvert(ctxinfo_t *ctx, char *str, int size)\n{\n\tinfo_t *a;\n\tint next_size;\n\t\n\tif (!ctx)\n\t\treturn false;\n\n\tif (!str || size < 1)\n\t\treturn false;\n\n\tstr[0] = 0;\n\n\tfor (a = ctx->info_list; a; a = a->next)\n\t{\n\t\tif (!a->value[0])\n\t\t\tcontinue; // empty\n\n\t\tnext_size = size - 2 - strlen(a->name) - strlen(a->value);\n\n\t\tif (next_size < 1)\n\t\t{\n\t\t\t// sigh, next snprintf will not fit\n\t\t\treturn false;\n\t\t}\n\n\t\tsnprintf(str, size, \"\\\\%s\\\\%s\", a->name, a->value);\n\t\tstr += (size - next_size);\n\t\tsize = next_size;\n\t}\n\n\treturn true;\n}\n\nqbool Info_CopyStar(ctxinfo_t *ctx_from, ctxinfo_t *ctx_to)\n{\n\tinfo_t *a;\n\n\tif (!ctx_from || !ctx_to)\n\t\treturn false;\n\n\tif (ctx_from == ctx_to)\n\t\treturn true; // hrm\n\n\tfor (a = ctx_from->info_list; a; a = a->next)\n\t{\n\t\tif (a->name[0] != '*')\n\t\t\tcontinue; // not a star key\n\n\t\t// do we need check status of this function?\n\t\tInfo_SetStar (ctx_to, a->name, a->value);\n\t}\n\n\treturn true;\n}\n\nvoid Info_PrintList(ctxinfo_t *ctx)\n{\n\tinfo_t *a;\n\tint cnt = 0;\n\n\tif (!ctx)\n\t\treturn;\n\n\tfor (a = ctx->info_list; a; a = a->next)\n\t{\n\t\tCon_Printf(\"%-20s %s\\n\", a->name, a->value);\n\t\tcnt++;\n\t}\n\n\t\n\tCon_DPrintf(\"%d infos\\n\", cnt);\n}\n\n//============================================================================\n\nstatic byte chktbl[1024] = {\n                               0x78,0xd2,0x94,0xe3,0x41,0xec,0xd6,0xd5,0xcb,0xfc,0xdb,0x8a,0x4b,0xcc,0x85,0x01,\n                               0x23,0xd2,0xe5,0xf2,0x29,0xa7,0x45,0x94,0x4a,0x62,0xe3,0xa5,0x6f,0x3f,0xe1,0x7a,\n                               0x64,0xed,0x5c,0x99,0x29,0x87,0xa8,0x78,0x59,0x0d,0xaa,0x0f,0x25,0x0a,0x5c,0x58,\n                               0xfb,0x00,0xa7,0xa8,0x8a,0x1d,0x86,0x80,0xc5,0x1f,0xd2,0x28,0x69,0x71,0x58,0xc3,\n                               0x51,0x90,0xe1,0xf8,0x6a,0xf3,0x8f,0xb0,0x68,0xdf,0x95,0x40,0x5c,0xe4,0x24,0x6b,\n                               0x29,0x19,0x71,0x3f,0x42,0x63,0x6c,0x48,0xe7,0xad,0xa8,0x4b,0x91,0x8f,0x42,0x36,\n                               0x34,0xe7,0x32,0x55,0x59,0x2d,0x36,0x38,0x38,0x59,0x9b,0x08,0x16,0x4d,0x8d,0xf8,\n                               0x0a,0xa4,0x52,0x01,0xbb,0x52,0xa9,0xfd,0x40,0x18,0x97,0x37,0xff,0xc9,0x82,0x27,\n                               0xb2,0x64,0x60,0xce,0x00,0xd9,0x04,0xf0,0x9e,0x99,0xbd,0xce,0x8f,0x90,0x4a,0xdd,\n                               0xe1,0xec,0x19,0x14,0xb1,0xfb,0xca,0x1e,0x98,0x0f,0xd4,0xcb,0x80,0xd6,0x05,0x63,\n                               0xfd,0xa0,0x74,0xa6,0x86,0xf6,0x19,0x98,0x76,0x27,0x68,0xf7,0xe9,0x09,0x9a,0xf2,\n                               0x2e,0x42,0xe1,0xbe,0x64,0x48,0x2a,0x74,0x30,0xbb,0x07,0xcc,0x1f,0xd4,0x91,0x9d,\n                               0xac,0x55,0x53,0x25,0xb9,0x64,0xf7,0x58,0x4c,0x34,0x16,0xbc,0xf6,0x12,0x2b,0x65,\n                               0x68,0x25,0x2e,0x29,0x1f,0xbb,0xb9,0xee,0x6d,0x0c,0x8e,0xbb,0xd2,0x5f,0x1d,0x8f,\n                               0xc1,0x39,0xf9,0x8d,0xc0,0x39,0x75,0xcf,0x25,0x17,0xbe,0x96,0xaf,0x98,0x9f,0x5f,\n                               0x65,0x15,0xc4,0x62,0xf8,0x55,0xfc,0xab,0x54,0xcf,0xdc,0x14,0x06,0xc8,0xfc,0x42,\n                               0xd3,0xf0,0xad,0x10,0x08,0xcd,0xd4,0x11,0xbb,0xca,0x67,0xc6,0x48,0x5f,0x9d,0x59,\n                               0xe3,0xe8,0x53,0x67,0x27,0x2d,0x34,0x9e,0x9e,0x24,0x29,0xdb,0x69,0x99,0x86,0xf9,\n                               0x20,0xb5,0xbb,0x5b,0xb0,0xf9,0xc3,0x67,0xad,0x1c,0x9c,0xf7,0xcc,0xef,0xce,0x69,\n                               0xe0,0x26,0x8f,0x79,0xbd,0xca,0x10,0x17,0xda,0xa9,0x88,0x57,0x9b,0x15,0x24,0xba,\n                               0x84,0xd0,0xeb,0x4d,0x14,0xf5,0xfc,0xe6,0x51,0x6c,0x6f,0x64,0x6b,0x73,0xec,0x85,\n                               0xf1,0x6f,0xe1,0x67,0x25,0x10,0x77,0x32,0x9e,0x85,0x6e,0x69,0xb1,0x83,0x00,0xe4,\n                               0x13,0xa4,0x45,0x34,0x3b,0x40,0xff,0x41,0x82,0x89,0x79,0x57,0xfd,0xd2,0x8e,0xe8,\n                               0xfc,0x1d,0x19,0x21,0x12,0x00,0xd7,0x66,0xe5,0xc7,0x10,0x1d,0xcb,0x75,0xe8,0xfa,\n                               0xb6,0xee,0x7b,0x2f,0x1a,0x25,0x24,0xb9,0x9f,0x1d,0x78,0xfb,0x84,0xd0,0x17,0x05,\n                               0x71,0xb3,0xc8,0x18,0xff,0x62,0xee,0xed,0x53,0xab,0x78,0xd3,0x65,0x2d,0xbb,0xc7,\n                               0xc1,0xe7,0x70,0xa2,0x43,0x2c,0x7c,0xc7,0x16,0x04,0xd2,0x45,0xd5,0x6b,0x6c,0x7a,\n                               0x5e,0xa1,0x50,0x2e,0x31,0x5b,0xcc,0xe8,0x65,0x8b,0x16,0x85,0xbf,0x82,0x83,0xfb,\n                               0xde,0x9f,0x36,0x48,0x32,0x79,0xd6,0x9b,0xfb,0x52,0x45,0xbf,0x43,0xf7,0x0b,0x0b,\n                               0x19,0x19,0x31,0xc3,0x85,0xec,0x1d,0x8c,0x20,0xf0,0x3a,0xfa,0x80,0x4d,0x2c,0x7d,\n                               0xac,0x60,0x09,0xc0,0x40,0xee,0xb9,0xeb,0x13,0x5b,0xe8,0x2b,0xb1,0x20,0xf0,0xce,\n                               0x4c,0xbd,0xc6,0x04,0x86,0x70,0xc6,0x33,0xc3,0x15,0x0f,0x65,0x19,0xfd,0xc2,0xd3,\n\n                               // Only the first 512 bytes of the table are initialized, the rest\n                               // is just zeros.\n                               // This is an idiocy in QW but we can't change this, or checksums\n                               // will not match.\n                           };\n\n//For proxy protecting\nbyte COM_BlockSequenceCRCByte (byte *base, int length, int sequence) {\n\tunsigned short crc;\n\tbyte *p;\n\tbyte chkb[60 + 4];\n\n\tp = chktbl + ((unsigned int)sequence % (sizeof(chktbl) - 4));\n\n\tif (length > 60)\n\t\tlength = 60;\n\tmemcpy (chkb, base, length);\n\n\tchkb[length] = (sequence & 0xff) ^ p[0];\n\tchkb[length+1] = p[1];\n\tchkb[length+2] = ((sequence>>8) & 0xff) ^ p[2];\n\tchkb[length+3] = p[3];\n\n\tlength += 4;\n\n\tcrc = CRC_Block (chkb, length);\n\n\tcrc &= 0xff;\n\n\treturn crc;\n}\n\n//=====================================================================\n\n#define\tMAXPRINTMSG\t4096\n\nvoid (*rd_print) (char *) = NULL;\n\nvoid Com_BeginRedirect (void (*RedirectedPrint) (char *)) {\n\trd_print = RedirectedPrint;\n}\n\nvoid Com_EndRedirect (void) {\n\trd_print = NULL;\n}\n\n// All console printing must go through this in order to be logged to disk\nunsigned Print_flags[16];\nint Print_current = 0;\n\n/* FIXME: Please make us thread safe! */\nvoid Com_Printf (char *fmt, ...) \n{\n\tva_list argptr;\n\tchar msg[MAXPRINTMSG];\n\n\tva_start (argptr, fmt);\n\tvsnprintf (msg, sizeof(msg), fmt, argptr);\n\tva_end (argptr);\n\n\tif (rd_print) {\n\t\t// add to redirected message\n\t\trd_print (msg);\n\t\treturn;\n\t}\n\n\t// also echo to debugging console\n\tSys_Printf (\"%s\", msg);\n\n\t/* This is beyond retarded because it may cause RECURSION */\n\t// Triggers with mask 64\n\tif (!(Print_flags[Print_current] & PR_TR_SKIP))\n\t\tCL_SearchForReTriggers (msg, RE_PRINT_INTERNAL);\n\n\t// write it to the scrollable buffer\n\t//\tCon_Print (va(\"ezQuake: %s\", msg));\n\tCon_PrintW (str2wcs(msg));\n}\n\nvoid Com_DPrintf (char *fmt, ...) \n{\n\tva_list argptr;\n\tchar msg[MAXPRINTMSG];\n\n\tif (!developer.value)\n\t\treturn;\t\t\t// don't confuse non-developers with techie stuff...\n\n\tPrint_flags[Print_current] |= PR_TR_SKIP;\n\tva_start (argptr,fmt);\n\tvsnprintf (msg, sizeof(msg), fmt, argptr);\n\tva_end (argptr);\n\n\tCom_Printf (\"%s\", msg);\n}\n\nvoid Com_Printf_State(int state, const char *fmt, ...)\n{\n/* Com_Printf equivalent with importance level as a first parm for each msg,\n   it should be one of PRINT_OK, PRINT_INFO, PRINT_FAIL defines\n   PRINT_OK won't be displayed unless we're in developer mode\n*/\n\tva_list argptr;\n\tchar *prefix = \"\";\n\tchar msg[MAXPRINTMSG];\n\n\tmsg[0] = 0;\n\n\tswitch (state)\n\t{\n\t\tcase PRINT_FAIL:\n\t\t\tprefix = \"\\x02\" \"\\x10\" \"fail\" \"\\x11 \";\n\t\t\tbreak;\n\t\tcase PRINT_OK:\n\n\t\t\tif (!developer.integer)\n\t\t\t\treturn;  // NOTE: print msgs only in developer mode\n\n\t\t\tprefix = \" \" \" ok \" \"  \";\n\t\t\tbreak;\n\t\tcase PRINT_DBG:\n\t\t\tif (!developer.integer)\n\t\t\t\treturn;  // NOTE: print msgs only in developer mode\n\n\t\t\tprefix = \"\";\n\t\t\tbreak;\n\n\t\tcase PRINT_WARNING:\n\t\tcase PRINT_ALL:\n\t\t\tprefix = \"\"; // FIXME: put here some color\n\t\t\tbreak;\n\t\tcase PRINT_ERR_FATAL:\n\t\t\tprefix = \"\";\n\t\t\tbreak;\n\t\tdefault:\n\t\tcase PRINT_INFO:\n\t\t\tprefix = \"\\x9c\\x9c\\x9c \";\n\t\t\tbreak;\n\t}\n\n\tva_start (argptr, fmt);\n\tvsnprintf (msg, sizeof(msg), fmt, argptr);\n\tva_end(argptr);\n\tCom_Printf (\"%s%s\", prefix, msg);\n\n\tif (state == PRINT_ERR_FATAL)\n\t\tSys_Error(msg);\n}\n\nvoid Com_PrintVerticalBar(int width)\n{\n\tint i;\n\tCom_Printf(\"\\x80\");\n\n\tfor (i = 0; i < width; i++)\n\t{\n\t\tCom_Printf(\"\\x81\");\n\t}\n\n\tCom_Printf(\"\\x82\");\n}\n\nvoid COM_ParseIPCData(const char *buf, unsigned int bufsize)\n{\n\tif (Rulesets_RestrictIPC()) {\n\t\tCom_Printf(\"The use of IPC is not allowed during matches\\n\");\n\t\treturn;\n\t}\n\n\tif (bufsize > 0)\n\t{\n\t\t// TODO : Expect some more fancy commands and stuff here instead.. if we want to use it for more than qw:// urls...\n\t\tif (!strncasecmp(buf, \"qw://\", 5))\n\t\t{\n\t\t\tCbuf_AddText(va(\"qwurl %s\\n\", buf));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCbuf_AddText(buf);\n\t\t}\n\t}\n}\n\n//\n// Check if the first command line argument is a .qtv or a demo to be played.\n//\nqbool COM_CheckArgsForPlayableFiles(char *commandbuf_out, unsigned int commandbuf_size)\n{\n\t// Check for .qtv or demo files to play.\n\tif (COM_Argc() >= 2) \n\t{\n\t\tchar *infile = COM_Argv(1);\n\n\t\tif (infile[0] && infile[0] != '-' && infile[0] != '+') \n\t\t{\n\t\t\tchar *ext = COM_FileExtension(infile);\n\n\t\t\tif (!strncasecmp(ext, \"qtv\", sizeof(\"qtv\")))\n\t\t\t{\n\t\t\t\tsnprintf(commandbuf_out, commandbuf_size, \"qtvplay \\\"#%s\\\"\\n\", infile);\n\t\t\t}\n\t\t\telse if (!strncasecmp(ext, \"qw\", sizeof(\"qw\")))\n\t\t\t{\n\t\t\t\tsnprintf(commandbuf_out, commandbuf_size, \"qtvplay \\\"#%s\\\"\\n\", infile);\n\t\t\t}\n\t\t\telse if (CL_IsDemoExtension(infile))\n\t\t\t{\n\t\t\t\tsnprintf(commandbuf_out, commandbuf_size, \"playdemo \\\"%s\\\"\\n\", infile);\n\t\t\t}\n\t\t\telse if (!strncasecmp(infile, \"qw://\", sizeof(\"qw://\") - 1))\n\t\t\t{\n\t\t\t\tsnprintf(commandbuf_out, commandbuf_size, \"qwurl \\\"%s\\\"\\n\", infile);\n\t\t\t}\n\t\t\telse \n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\t}\n\n    return false;\n}\n\n//============================================================================\n\nstatic char q_normalize_chartbl[256];\nstatic qbool q_normalize_chartbl_init;\n\nstatic void Q_normalizetext_Init (void)\n{\n\tint i;\n\n\tfor (i = 0; i < 32; i++)\n\t\tq_normalize_chartbl[i] = q_normalize_chartbl[i + 128] = '#';\n\tfor (i = 32; i < 128; i++)\n\t\tq_normalize_chartbl[i] = q_normalize_chartbl[i + 128] = i;\n\n\t// special cases\n\tq_normalize_chartbl[10] = 10;\n\tq_normalize_chartbl[13] = 13;\n\n\t// dot\n\tq_normalize_chartbl[5      ] = q_normalize_chartbl[14      ] = q_normalize_chartbl[15      ] = q_normalize_chartbl[28      ] = q_normalize_chartbl[46      ] = '.';\n\tq_normalize_chartbl[5 + 128] = q_normalize_chartbl[14 + 128] = q_normalize_chartbl[15 + 128] = q_normalize_chartbl[28 + 128] = q_normalize_chartbl[46 + 128] = '.';\n\n\t// numbers\n\tfor (i = 18; i < 28; i++)\n\t\tq_normalize_chartbl[i] = q_normalize_chartbl[i + 128] = i + 30;\n\n\t// brackets\n\tq_normalize_chartbl[16] = q_normalize_chartbl[16 + 128]= '[';\n\tq_normalize_chartbl[17] = q_normalize_chartbl[17 + 128] = ']';\n\tq_normalize_chartbl[29] = q_normalize_chartbl[29 + 128] = q_normalize_chartbl[128] = '(';\n\tq_normalize_chartbl[31] = q_normalize_chartbl[31 + 128] = q_normalize_chartbl[130] = ')';\n\n\t// left arrow\n\tq_normalize_chartbl[127] = '>';\n\t// right arrow\n\tq_normalize_chartbl[141] = '<';\n\n\t// '='\n\tq_normalize_chartbl[30] = q_normalize_chartbl[129] = q_normalize_chartbl[30 + 128] = '=';\n\n\tq_normalize_chartbl_init = true;\n}\n\n/*\n==================\nQ_normalizetext\nreturns readable extended quake names\n==================\n*/\nchar *Q_normalizetext (char *str)\n{\n\tunsigned char\t*i;\n\n\tif (!q_normalize_chartbl_init)\n\t\tQ_normalizetext_Init();\n\n\tfor (i = (unsigned char*)str; *i; i++)\n\t\t*i = q_normalize_chartbl[*i];\n\treturn str;\n}\n\n/*\n==================\nQ_redtext\nreturns extended quake names\n==================\n*/\nunsigned char *Q_redtext (unsigned char *str)\n{\n\tunsigned char *i;\n\tfor (i = str; *i; i++)\n\t\tif (*i > 32 && *i < 128)\n\t\t\t*i |= 128;\n\treturn str;\n}\n//<-\n\n/*\n==================\nQ_yelltext\nreturns extended quake names (yellow numbers)\n==================\n*/\nunsigned char *Q_yelltext (unsigned char *str)\n{\n\tunsigned char *i;\n\tfor (i = str; *i; i++)\n\t{\n\t\tif (*i >= '0' && *i <= '9')\n\t\t\t*i += 18 - '0';\n\t\telse if (*i > 32 && *i < 128)\n\t\t\t*i |= 128;\n\t\telse if (*i == 13)\n\t\t\t*i = ' ';\n\t}\n\treturn str;\n}\n\n//=====================================================================\n\n// \"GPL map\" support.  If we encounter a map with a known \"GPL\" CRC,\n// we fake the CRC so that, on the client side, the CRC of the original\n// map is transferred to the server, and on the server side, comparison\n// of clients' CRC is done against the orignal one\ntypedef struct {\n\tconst char *mapname;\n\tint original;\n\tint gpl;\n} csentry_t;\n\nstatic csentry_t table[] = {\n\t// CRCs for AquaShark's \"simpletextures\" maps\n\t{ \"dm1\",  0xc5c7dab3, 0x7d37618e },\n\t{ \"dm2\",  0x65f63634, 0x7b337440 },\n\t{ \"dm3\",  0x15e20df8, 0x912781ae },\n\t{ \"dm4\",  0x9c6fe4bf, 0xc374df89 },\n\t{ \"dm5\",  0xb02d48fd, 0x77ca7ce5 },\n\t{ \"dm6\",  0x5208da2b, 0x200c8b5d },\n\t{ \"end\",  0xbbd4b4a5, 0xf89b12ae }, // this is the version with the extra room\n\t{ \"end\",  0xbbd4b4a5, 0x924f4d33 }, // GPL end\n\t{ \"e2m2\", 0xaf961d4d, 0xa23126c5 }, // GPL e2m2\n\t{ NULL, 0, 0 },\n};\n\nint Com_TranslateMapChecksum (const char *mapname, int checksum)\n{\n\tcsentry_t *p;\n\n//\tCom_Printf (\"Map checksum (%s): 0x%x\\n\", mapname, checksum);\n\n\tfor (p = table; p->mapname; p++)\n\t\tif (!strcmp(p->mapname, mapname) && checksum == p->gpl)\n\t\t\treturn p->original;\n\n\treturn checksum;\n}\n\nfloat AdjustAngle(float current, float ideal, float fraction)\n{\n\tfloat move = ideal - current;\n\n\tif (move >= 180)\n\t\tmove -= 360;\n\telse if (move <= -180)\n\t\tmove += 360;\n\n\treturn current + fraction * move;\n}\n\nint Q_namecmp(const char* s1, const char* s2)\n{\n\tif (s1 == NULL && s2 == NULL)\n\t\treturn 0;\n\n\tif (s1 == NULL)\n\t\treturn -1;\n\n\tif (s2 == NULL)\n\t\treturn 1;\n\n\twhile (*s1 || *s2) {\n\t\tif (tolower(*s1 & 0x7f) != tolower(*s2 & 0x7f)) {\n\t\t\treturn tolower(*s1 & 0x7f) - tolower(*s2 & 0x7f);\n\t\t}\n\t\ts1++;\n\t\ts2++;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/common.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: common.h,v 1.76 2007-10-13 15:36:56 cokeman1982 Exp $\n*/\n// common.h  -- general definitions\n\n#ifndef __COMMON_H__\n#define __COMMON_H__\n\n//============================================================================\n\n// include frequently used headers\n\n// fixes mingw warning about winsock2.h\n#ifdef _WIN32\n#include <winsock2.h>\n#endif\n\n#include \"q_shared.h\"\n#include \"zone.h\"\n#include \"cvar.h\"\n#include \"cmd.h\"\n#include \"net.h\"\n#include \"protocol.h\"\n#include \"cmodel.h\"\n#include \"cmdline_params.h\"\n#include \"fs.h\"\n\n#if defined(_WIN64) && !defined(__MINGW64__)\nint Q_strlen(const char* s);\n#define strlen Q_strlen\n#endif\n\ntypedef struct texture_ref_s { unsigned int index; } texture_ref;\nextern const texture_ref null_texture_reference;\nqbool R_TextureValid(texture_ref ref);\n#define R_TextureReferenceIsValid(ref) ((ref).index && R_TextureValid(ref))\n#define R_TextureReferenceInvalidate(ref) { (ref).index = 0; }\n#define R_TextureReferenceEqual(ref1, ref2) ((ref1).index == (ref2).index)\n#define R_TextureReferenceCompare(ref1, ref2) (ref1.index < ref2.index ? -1 : ref1.index > ref2.index ? 1 : 0)\n\ntypedef enum\n{\n\tmod_brush,\n\tmod_sprite,\n\tmod_alias,\n\tmod_alias3,\n\n\tmod_unknown\n} modtype_t;\n\n//============================================================================\n\n// per-level limits\n#define CL_MAX_EDICTS           2048\t// FIXME: ouch! ouch! ouch!\n#define MAX_EDICTS              2048    // can't encode more than this, see SV_WriteDelta\n#define MAX_EDICTS_SAFE         512     // lower limit, to make sure no client limits exceeded\n#define MAX_LIGHTSTYLES         64\n#define MAX_MODELS              4096    // can't encode more than this, see SV_WriteDelta\n#define MAX_STATIC_ENTITIES     2048\n#define MAX_VWEP_MODELS         32\t    // could be increased to 256\n#define MAX_SOUNDS              256\t    // so they cannot be blindly increased\n\n#define\tSAVEGAME_COMMENT_LENGTH 39\n\n//============================================================================\n\n// stats are integers communicated to the client by the server\n#define\tMAX_CL_STATS\t\t32\n\n#define STAT_HEALTH             0\n//define STAT_FRAGS             1\n#define STAT_WEAPON             2\n#define STAT_AMMO               3\n#define STAT_ARMOR              4\n//define STAT_WEAPONFRAME       5\n#define STAT_SHELLS             6\n#define STAT_NAILS              7\n#define STAT_ROCKETS            8\n#define STAT_CELLS              9\n#define STAT_ACTIVEWEAPON       10\n#define STAT_TOTALSECRETS       11\n#define STAT_TOTALMONSTERS      12\n#define STAT_SECRETS            13\t\t// bumped on client side by svc_foundsecret\n#define STAT_MONSTERS           14\t\t// bumped by svc_killedmonster\n#define STAT_ITEMS              15\n#define STAT_VIEWHEIGHT         16\t\t// Z_EXT_VIEWHEIGHT protocol extension\n#define STAT_TIME               17\t\t// Z_EXT_TIME extension\n#define STAT_MATCHSTARTTIME     18\t\t// Server should send this as msec (int)\n\n// item flags\n#define\tIT_SHOTGUN              1\n#define\tIT_SUPER_SHOTGUN        2\n#define\tIT_NAILGUN              4\n#define\tIT_SUPER_NAILGUN        8\n#define\tIT_GRENADE_LAUNCHER     16\n#define\tIT_ROCKET_LAUNCHER      32\n#define\tIT_LIGHTNING            64\n#define\tIT_SUPER_LIGHTNING      128\n#define\tIT_SHELLS               256\n#define\tIT_NAILS                512\n#define\tIT_ROCKETS              1024\n#define\tIT_CELLS                2048\n#define\tIT_AXE                  4096\n#define\tIT_ARMOR1               8192\n#define\tIT_ARMOR2               16384\n#define\tIT_ARMOR3               32768\n#define\tIT_SUPERHEALTH          65536\n#define\tIT_KEY1                 131072\n#define\tIT_KEY2                 262144\n#define\tIT_INVISIBILITY         524288\n#define\tIT_INVULNERABILITY      1048576\n#define\tIT_SUIT                 2097152\n#define\tIT_QUAD                 4194304\n\n#define IT_ALL_WEAPONS          (IT_AXE | IT_SHOTGUN | IT_SUPER_SHOTGUN | IT_NAILGUN | IT_SUPER_NAILGUN | IT_GRENADE_LAUNCHER | IT_ROCKET_LAUNCHER | IT_LIGHTNING | IT_SUPER_LIGHTNING)\n\n#define\tIT_SIGIL1               (1<<28)\n#define\tIT_SIGIL2               (1<<29)\n#define\tIT_SIGIL3               (1<<30)\n#define\tIT_SIGIL4               (1<<31)\n\n//\n// entity effects\n//\n#define EF_BRIGHTFIELD        1\n#define EF_MUZZLEFLASH        2\n#define EF_GREEN              2     // D-Kure: EF_GREEN will replace\n#define EF_BRIGHTLIGHT        4     // EF_MUZZLEFLASH and provide RGB colours\n#define EF_DIMLIGHT           8     // Both are needed as NQ uses EF_MUZZLE..\n#define EF_FLAG1             16\n#define EF_FLAG2             32\n#define EF_BLUE              64\n#define EF_RED              128\n\n// game types sent by serverinfo\n// these determine which intermission screen plays\n#define\tGAME_COOP\t\t\t0\n#define\tGAME_DEATHMATCH\t\t1\n\n#define\tMAX_INFO_KEY 64\n//#define\tMAX_INFO_STRING\t1024\n#define\tMAX_SERVERINFO_STRING 512\n#define\tMAX_LOCALINFO_STRING 32768\n\n#define\tMAX_KEY_STRING      (MAX_INFO_KEY)     // mvdsv compatibility\n#define\tMAX_EXT_INFO_STRING (MAX_INFO_STRING)  // mvdsv compatibility\n\n//============================================================================\n\n#define Cvar_SetROM\tCvar_ForceSet\t// mvdsv compatibility\n\n#define Con_Printf  Com_Printf  \t// mvdsv compatibility\n#define Con_DPrintf Com_DPrintf\t\t// mvdsv compatibility\n#define fs_gamedir  com_gamedir\t\t// mvdsv compatibility\n\n//============================================================================\n\n\n#define MAX_COM_TOKEN 1024\n\nextern\tchar\tcom_token[MAX_COM_TOKEN];\nextern\tqbool\tcom_eof;\ntypedef enum {TTP_UNKNOWN, TTP_STRING} com_tokentype_t;\n\nconst char *COM_Parse(const char *data);\nconst char* COM_ParseEx(const char* data, qbool curlybraces);\nchar *COM_ParseToken (const char *data, const char *punctuation);\n\nchar *COM_Argv (int arg); // range and null checked\nvoid COM_ClearArgv (int arg);\nvoid COM_AddParm (char *parm);\nvoid COM_InitArgv (int argc, char **argv);\nint COM_Argc (void);\nint COM_CheckParm(cmdline_param_id parm_id);\nint COM_CheckParmOffset(cmdline_param_id parm_id, int offset);\nint COM_FindParm(const char* parm);\nconst char* Cmd_CommandLineParamName(cmdline_param_id id);\n\n#define IsDeveloperMode() (COM_CheckParm(cmdline_param_developer_mode))\n\n// equals to consecutive calls of strtok(s, \" \") that assign values to array\n// \"1 3.5 6 7\" will lead to fl_array[0] = 1.0; fl_array[1] = 3.5; ...\nint COM_GetFloatTokens(const char *s, float *fl_array, int fl_array_size);\n\nvoid COM_Init (void);\n\nconst char *COM_SkipPath (const char *pathname);\nchar *COM_SkipPathWritable (char *pathname);\nchar *COM_FitPath(char *dest, int destination_size, char *src, int size_to_fit);\nchar *COM_FileExtension (const char *in);\nvoid COM_StripExtension (const char *in, char *out, int out_size);\nvoid COM_FileBase (const char *in, char *out);\nvoid COM_DefaultExtension (char *path, char *extension, size_t path_len);\n// If path doesn't have an extension or has a different extension, append(!) specified extension.\n// path buffer supposed to be MAX_OSPATH size\nvoid COM_ForceExtension (char *path, char *extension);\n// If path doesn't have an extension or has a different extension, append(!) specified extension.\n// a bit extended version of COM_ForceExtension(), we suply size of path, so append safe, sure if u provide right path size\nvoid COM_ForceExtensionEx (char *path, char *extension, size_t path_size);\nint COM_GetTempDir(char *buf, int bufsize);\nqbool COM_WriteToUniqueTempFile(char* path, int path_buffer_size, const char* ext, const byte* buffer, size_t bytes);\nqbool COM_WriteToUniqueTempFileVFS(char* path, int path_buffer_size, const char* ext, vfsfile_t* input);\nqbool COM_FileExists(char *path);\nvoid COM_StoreOriginalCmdline(int argc, char **argv);\n\nextern char *SYSINFO_GetString(void);\n\nchar *va(const char *format, ...); // does a varargs printf into a temp buffer\n\n//============================================================================\n// Quake File System\n// for more File System control, include fs.h in your sources\n\nextern int fs_filepos;\nextern char *com_filesearchpath;\nextern char\tfs_netpath[MAX_OSPATH];\nstruct cache_user_s;\n\nextern char\tcom_gamedir[MAX_OSPATH];\nextern char\tcom_basedir[MAX_OSPATH];\nextern char\tcom_gamedirfile[MAX_QPATH];\nextern char com_homedir[MAX_PATH];\n\nextern qbool file_from_gamedir;\t// set if file came from a gamedir (and gamedir wasn't id1/qw)\n\nvoid FS_InitFilesystem (void);\nvoid FS_Shutdown(void);\nvoid FS_SetGamedir (char *dir, qbool force);\nint FS_FOpenFile (const char *filename, FILE **file);\nint FS_FOpenPathFile (const char *filename, FILE **file);\nbyte *FS_LoadTempFile (char *path, int *len);\nbyte *FS_LoadHunkFile (char *path, int *len);\nbyte *FS_LoadHeapFile (const char *path, int *len);\nqbool FS_WriteFile(const char *filename, const void *data, int len); //The filename will be prefixed by com_basedir\nqbool FS_WriteFile_2(const char *filename, const void *data, int len); //The filename used as is\nvoid FS_CreatePath (char *path);\nint FS_FCreateFile (char *filename, FILE **file, char *path, char *mode);\nchar *FS_LegacyDir (char *media_dir);\n\nint FS_FileLength (FILE *f);\nint FS_FileOpenRead (char *path, FILE **hndl);\n\nchar * FS_Locate_GetPath (const char *file);\n\n//============================================================\n// Alternative variant manipulation with info strings\n//============================================================\n\n#define INFO_HASHPOOL_SIZE 256\n\n#define MAX_CLIENT_INFOS 128\n\ntypedef struct info_s {\n\tstruct info_s\t*hash_next;\n\tstruct info_s\t*next;\n\n\tchar\t\t\t\t*name;\n\tchar\t\t\t\t*value;\n\n} info_t;\n\ntypedef struct ctxinfo_s {\n\n\tinfo_t\t*info_hash[INFO_HASHPOOL_SIZE];\n\tinfo_t\t*info_list;\n\n\tint\t\tcur; // current infos\n\tint\t\tmax; // max    infos\n\n} ctxinfo_t;\n\n// return value for given key\nchar\t\t\t*Info_Get(ctxinfo_t *ctx, const char *name);\n// set value for given key\nqbool\t\t\tInfo_Set (ctxinfo_t *ctx, const char *name, const char *value);\n// set value for given star key\nqbool\t\t\tInfo_SetStar (ctxinfo_t *ctx, const char *name, const char *value);\n// remove given key\nqbool\t\t\tInfo_Remove (ctxinfo_t *ctx, const char *name);\n// remove all infos\nvoid\t\t\tInfo_RemoveAll (ctxinfo_t *ctx);\n// convert old way infostring to new way: \\name\\qqshka\\noaim\\1 to hashed variant\nqbool\t\t\tInfo_Convert(ctxinfo_t *ctx, char *str);\n// convert new way to old way\nqbool\t\t\tInfo_ReverseConvert(ctxinfo_t *ctx, char *str, int size);\n// copy star keys from ont ctx to other\nqbool\t\t\tInfo_CopyStar(ctxinfo_t *ctx_from, ctxinfo_t *ctx_to);\n// just print all key value pairs\nvoid\t\t\tInfo_PrintList(ctxinfo_t *ctx);\n\n//============================================================================\n\nchar *Info_ValueForKey (char *s, char *key);\nvoid Info_RemoveKey (char *s, char *key);\nvoid Info_RemovePrefixedKeys (char *start, char prefix);\nvoid Info_SetValueForKey (char *s, char *key, char *value, int maxsize);\nvoid Info_SetValueForStarKey (char *s, char *key, char *value, int maxsize);\nvoid Info_Print (char *s);\n\n// Same as Info_SetValueForStarKey() but allow do not check for key and value length is less than MAX_INFO_KEY.\n// Please do NOT use it unless you sure.\nvoid Info_SetValueForStarKeyEx (char *s, char *key, char *value, int maxsize, qbool max_info_key_check);\n// Same as Info_SetValueForKey() but allow do not check for key and value length is less than MAX_INFO_KEY.\n// Please do NOT use it unless you sure.\nvoid Info_SetValueForKeyEx (char *s, char *key, char *value, int maxsize, qbool max_info_key_check);\n\nunsigned Com_BlockChecksum (void *buffer, int length);\nvoid Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf);\nbyte COM_BlockSequenceCRCByte (byte *base, int length, int sequence);\n\n//============================================================================\n\nvoid Com_BeginRedirect (void (*RedirectedPrint) (char *));\nvoid Com_EndRedirect (void);\nvoid Com_Printf (char *fmt, ...);\nvoid Com_DPrintf (char *fmt, ...);\nvoid Com_PrintVerticalBar(int width);\n\n// why not just 1 2 3 4 ... ?\n#define PRINT_OK\t\t(1<<0) // have prefix, printed only if developer != 0\n#define PRINT_INFO\t\t(1<<1) // have prefix\n#define PRINT_FAIL\t\t(1<<2) // have prefix\n#define PRINT_WARNING\t(1<<3) // do not have prefix\n#define PRINT_ALL\t\t(1<<4) // do not have prefix\n#define PRINT_ERR_FATAL\t(1<<5) // do Sys_Error()\n#define PRINT_DBG\t\t(1<<6) // do not have prefix, printed only if developer != 0\n\nvoid Com_Printf_State(int state, const char *fmt, ...);\n\nextern unsigned\tPrint_flags[16];\nextern int\tPrint_current;\n\n#define     PR_SKIP     1\n#define     PR_LOG_SKIP 2\n#define     PR_TR_SKIP  4\n#define     PR_IS_CHAT  8\n#define     PR_NONOTIFY 16\n#define     PR_NORESET  32        // Don't reset at the end of each Print() call\n\n//============================================================================\n\n#ifdef FTE_PEXT_FLOATCOORDS\n\ntypedef union {\t//note: reading from packets can be misaligned\n\tint b4;\n\tfloat f;\n\tshort b2;\n\tchar b[4];\n} coorddata;\n\nextern int msg_coordsize; // 2 or 4.\nextern int msg_anglesize; // 1 or 2.\n\nfloat MSG_FromCoord(coorddata c, int bytes);\ncoorddata MSG_ToCoord(float f, int bytes);\t//return value should be treated as (char*)&ret;\ncoorddata MSG_ToAngle(float f, int bytes);\t//return value is NOT byteswapped.\n\n#endif\n\nstruct usercmd_s;\n\nextern struct usercmd_s nullcmd;\n\nvoid MSG_WriteChar (sizebuf_t *sb, int c);\nvoid MSG_WriteByte (sizebuf_t *sb, int c);\nvoid MSG_WriteShort (sizebuf_t *sb, int c);\nvoid MSG_WriteLong (sizebuf_t *sb, int c);\nvoid MSG_WriteFloat (sizebuf_t *sb, float f);\nvoid MSG_WriteString (sizebuf_t *sb, const char *s);\nvoid MSG_WriteUnterminatedString (sizebuf_t *sb, char *s);\nvoid MSG_WriteCoord (sizebuf_t *sb, float f);\nvoid MSG_WriteLongCoord (sizebuf_t *sb, float f);\nvoid MSG_WriteAngle (sizebuf_t *sb, float f);\nvoid MSG_WriteAngle16 (sizebuf_t *sb, float f);\nint MSG_WriteDeltaUsercmd (sizebuf_t *sb, struct usercmd_s *from, struct usercmd_s *cmd, unsigned int mvdsv_extensions);\nvoid MSG_WriteDeltaEntity  (entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qbool force, unsigned int fte_extensions, unsigned int mvdsv_extensions);\n\nextern\tint\tmsg_readcount;\nextern\tqbool\tmsg_badread; // set if a read goes beyond end of message\n\nvoid MSG_BeginReading (void);\nint MSG_GetReadCount(void);\nint MSG_ReadChar (void);\nint MSG_ReadByte (void);\nint MSG_ReadShort (void);\nint MSG_ReadLong (void);\nfloat MSG_ReadFloat (void);\nchar *MSG_ReadString (void);\nchar *MSG_ReadStringLine (void);\n\nfloat MSG_ReadCoord (void);\nfloat MSG_ReadFloatCoord (void);\nfloat MSG_ReadAngle (void);\nfloat MSG_ReadAngle16 (void);\nvoid MSG_ReadDeltaUsercmd (struct usercmd_s *from, struct usercmd_s *cmd, int protoversion);\n\nvoid MSG_ReadData (void *data, int len);\nvoid MSG_ReadSkip(int bytes);\n\n//============================================================================\n\nextern cvar_t\tdeveloper;\nextern cvar_t\thost_mapname;\n\nextern qbool\tcom_serveractive; // true if sv.state != ss_dead\nextern int\tCL_ClientState (void); // returns cls.state\n\nextern double\tcurtime; // not bounded or scaled, shared by local client and server\n\n// host\nextern qbool host_initialized;\nextern qbool host_everything_loaded;\nextern int host_memsize;\n\nvoid Host_Init (int argc, char **argv, int default_memsize);\nvoid Host_ClearMemory (void);\nvoid Host_Shutdown (void);\nvoid Host_Frame (double time);\nvoid Host_Abort (void);\t // longjmp() to Host_Frame\nvoid Host_EndGame (void); // kill local client and server\nvoid Host_Error (char *error, ...);\nvoid Host_Quit (void);\n\nvoid CL_Init (void);\nvoid CL_Shutdown (void);\nvoid CL_Frame (double time);\nvoid CL_Disconnect(void);\nvoid CL_BeginLocalConnection (void);\nvoid CL_UpdateCaption(qbool force);\nvoid Con_Init (void);\n\nvoid SV_Init (void);\nvoid SV_Shutdown (char *finalmsg);\nvoid SV_Frame (double time);\n\nvoid SV_Error (char *error, ...);\n\nvoid COM_ParseIPCData(const char *buf, unsigned int bufsize);\n\nqbool COM_CheckArgsForPlayableFiles(char *commandbuf_out, unsigned int commandbuf_size);\n\nint Com_TranslateMapChecksum (const char *mapname, int checksum);\nfloat AdjustAngle(float current, float ideal, float fraction);\n\nchar *Q_normalizetext(char *str);\nunsigned char *Q_redtext(unsigned char *str);\nunsigned char *Q_yelltext(unsigned char *str);\n\n// Name comparison: case insensitive, red/white text insensitive\nint Q_namecmp(const char* s1, const char* s2);\n\n#endif /* !__COMMON_H__ */\n\n"
  },
  {
    "path": "src/common_draw.c",
    "content": "/*\nModule for common graphics (soft and GL)\n\nCopyright (C) 2011 ezQuake team and kazik\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include <time.h>\n#include \"quakedef.h\"\n#include \"localtime.h\"\n#include \"common_draw.h\"\n#include \"stats_grid.h\"\n#include \"utils.h\"\n#include \"Ctrl.h\"\n\n#include \"gl_model.h\"\n\nvoid Draw_ClearConback(void);\n\n// FIXME: this is horrible - points to &hud_gameclock_offset->integer\nint* gameclockoffset;\n\n#if 0\nvoid Draw_CenterString (int y, char *str)\n{\n    Draw_String((vid.width-(8*strlen(str)))/2, y, str);\n}\n\nvoid Draw_CenterAltString (int y, char *str)\n{\n    Draw_Alt_String((vid.width-(8*strlen(str)))/2, y, str);\n}\n\n/*\n==================\nSCR_DrawDemoStatus\ndraws demo name, status and progress\n==================\n*/\nint SCR_DrawDemoStatus(void)\n{\n    extern cvar_t demo_statustime;\n    char st[128];\n    int x, y;\n    int w, i;\n    int mins, secs;\n\n    if (cls.timedemo || !cls.demoplayback)\n        return 0;\n\n    if (!cls.demopaused  &&  (realtime - cls.demobartime > demo_statustime.value  ||  cls.demobartime < 0))\n        return 0;\n\n    w = min(((vid.width / 2) / 16) * 2, 40);\n    //w = 20;\n\n    x = vid.width / 2 - (w*8/2);\n    y = vid.height - sb_lines - 8;\n\n    // status\n    if (cls.demopaused)\n        Draw_String(vid.width/2-4*6, y-16, \"paused\");\n\n    // speed\n    snprintf (st, sizeof (st), \"%d%%\", cls.demospeed);\n    Draw_String(x+8, y-16, st);\n\n    // status - time\n    mins = ((int)(hosttime-cls.demostarttime)) / 60;\n    secs = ((int)(hosttime-cls.demostarttime)) % 60;\n    snprintf (st, sizeof (st), \"%d:%02d\", mins, secs);\n    Draw_String(x+8*(w-strlen(st)-1), y-16, st);\n\n    // progress bar\n    memset(st, '\\x81', w);\n    st[0] = '\\x80';\n    st[w-1] = '\\x82';\n    st[w] = 0;\n    Draw_String(x, y-24, st);\n    Draw_Character((int)(x + 8*((cls.demopos / cls.demolength)*(w-3)+1)), y-24, '\\x83');\n\n    // demo name\n    strlcpy (st, cls.demoname, sizeof (st));\n    st[vid.width/8] = 0;\n    Draw_Alt_String(vid.width/2 - 4*strlen(st), y-4, st);\n\n    return 1;\n}\n\n/*\n==================\nSCR_PrepareCrosshair\nprepare crosshair shape in memory for drawing functions (both soft and GL)\n==================\n*/\n#define CSET(x,y) tab[x+5][y+5]=1\nvoid PrepareCrosshair(int num, byte tab[10][10])\n{\n    int i, j;\n    for (i=0; i < 10; i++)\n        for (j=0; j < 10; j++)\n            tab[i][j] = 0;\n\n    switch (num)\n    {\n    case 3:\n        CSET(0-2,0-2);\n        CSET(0-3,0-3);\n        CSET(0-4,0-4);\n        CSET(0+2,0+2);\n        CSET(0+3,0+3);\n        CSET(0+4,0+4);\n        CSET(0+2,0-2);\n        CSET(0+3,0-3);\n        CSET(0+4,0-4);\n        CSET(0-2,0+2);\n        CSET(0-3,0+3);\n        CSET(0-4,0+4);\n        break;\n    case 4:\n        CSET(0 - 2, 0 - 2);\n        CSET(0 - 3, 0 - 3);\n        CSET(0 + 2, 0 + 2);\n        CSET(0 + 3, 0 + 3);\n        CSET(0 + 2, 0 - 2);\n        CSET(0 + 3, 0 - 3);\n        CSET(0 - 2, 0 + 2);\n        CSET(0 - 3, 0 + 3);\n        break;\n    case 5:\n        CSET(0, 0 - 2);\n        CSET(0, 0 - 3);\n        CSET(0, 0 - 4);\n        CSET(0 + 2, 0 + 2);\n        CSET(0 + 3, 0 + 3);\n        CSET(0 + 4, 0 + 4);\n        CSET(0 - 2, 0 + 2);\n        CSET(0 - 3, 0 + 3);\n        CSET(0 - 4, 0 + 4);\n        break;\n    case 6:\n        CSET(0    , 0 - 1);\n        CSET(0 - 1, 0    );\n        CSET(0    , 0    );\n        CSET(0 + 1, 0    );\n        CSET(0    , 0 + 1);\n        break;\n    case 7:\n        CSET(0 - 1, 0 - 1);\n        CSET(0    , 0 - 1);\n        CSET(0 + 1, 0 - 1);\n        CSET(0 - 1, 0    );\n        CSET(0    , 0    );\n        CSET(0 + 1, 0    );\n        CSET(0 - 1, 0 + 1);\n        CSET(0    , 0 + 1);\n        CSET(0 + 1, 0 + 1);\n        break;\n    case 8:\n        CSET(0 + 1, 0 + 1);\n        CSET(0    , 0 + 1);\n        CSET(0 + 1, 0    );\n        CSET(0    , 0    );\n        break;\n    case 9:\n        CSET(0, 0);\n        break;\n    case 10:\n        CSET(-2, -2);\n        CSET(-2, -1);\n        CSET(-1, -2);\n        CSET(2, -2);\n        CSET(2, -1);\n        CSET(1, -2);\n        CSET(-2, 2);\n        CSET(-2, 1);\n        CSET(-1, 2);\n\n        CSET(2, 2);\n        CSET(2, 1);\n        CSET(1, 2);\n        break;\n    case 11:\n        CSET(0, -1);\n        CSET(1, -1);\n        CSET(-1, 0);\n        CSET(2, 0);\n        CSET(-1, 1);\n        CSET(2, 1);\n        CSET(0, 2);\n        CSET(1, 2);\n        break;\n    case 12:\n        CSET(0, -1);\n        CSET(1, -1);\n        CSET(-1, 0);\n        CSET(2, 0);\n        CSET(-1, 1);\n        CSET(2, 1);\n        CSET(0, 2);\n        CSET(1, 2);\n\n        CSET(0, 0);\n        CSET(1, 0);\n        CSET(0, 1);\n        CSET(1, 1);\n        break;\n    case 13:\n        CSET(0, -2);\n        CSET(1, -2);\n        CSET(-1, -1);\n        CSET(0, -1);\n        CSET(1, -1);\n        CSET(2, -1);\n        CSET(-2, 0);\n        CSET(-1, 0);\n        CSET(2, 0);\n        CSET(3, 0);\n        CSET(-2, 1);\n        CSET(-1, 1);\n        CSET(2, 1);\n        CSET(3, 1);\n        CSET(-1, 2);\n        CSET(0, 2);\n        CSET(1, 2);\n        CSET(2, 2);\n        CSET(0, 3);\n        CSET(1, 3);\n        break;\n    case 14:\n        CSET(0, -2);\n        CSET(1, -2);\n        CSET(-1, -1);\n        CSET(0, -1);\n        CSET(1, -1);\n        CSET(2, -1);\n        CSET(-2, 0);\n        CSET(-1, 0);\n        CSET(2, 0);\n        CSET(3, 0);\n        CSET(-2, 1);\n        CSET(-1, 1);\n        CSET(2, 1);\n        CSET(3, 1);\n        CSET(-1, 2);\n        CSET(0, 2);\n        CSET(1, 2);\n        CSET(2, 2);\n        CSET(0, 3);\n        CSET(1, 3);\n\n        CSET(0, 0);\n        CSET(1, 0);\n        CSET(0, 1);\n        CSET(1, 1);\n        break;\n    case 15:\n        CSET(-1, -3);\n        CSET( 0, -3);\n        CSET( 1, -3);\n\n        CSET(-2, -2);\n        CSET(-1, -2);\n        CSET( 0, -2);\n        CSET( 1, -2);\n        CSET( 2, -2);\n\n        CSET(-3, -1);\n        CSET(-2, -1);\n        CSET(-1, -1);\n        CSET( 1, -1);\n        CSET( 2, -1);\n        CSET( 3, -1);\n\n        CSET(-3, 0);\n        CSET(-2, 0);\n        CSET( 2, 0);\n        CSET( 3, 0);\n\n        CSET(-3, 1);\n        CSET(-2, 1);\n        CSET(-1, 1);\n        CSET( 1, 1);\n        CSET( 2, 1);\n        CSET( 3, 1);\n\n        CSET(-2, 2);\n        CSET(-1, 2);\n        CSET( 0, 2);\n        CSET( 1, 2);\n        CSET( 2, 2);\n\n        CSET(-1, 3);\n        CSET( 0, 3);\n        CSET( 1, 3);\n        break;\n    case 16:\n        CSET(-1, -3);\n        CSET( 0, -3);\n        CSET( 1, -3);\n\n        CSET(-2, -2);\n        CSET(-1, -2);\n        CSET( 0, -2);\n        CSET( 1, -2);\n        CSET( 2, -2);\n\n        CSET(-3, -1);\n        CSET(-2, -1);\n        CSET(-1, -1);\n        CSET( 1, -1);\n        CSET( 2, -1);\n        CSET( 3, -1);\n\n        CSET(-3, 0);\n        CSET(-2, 0);\n        CSET( 2, 0);\n        CSET( 3, 0);\n\n        CSET(-3, 1);\n        CSET(-2, 1);\n        CSET(-1, 1);\n        CSET( 1, 1);\n        CSET( 2, 1);\n        CSET( 3, 1);\n\n        CSET(-2, 2);\n        CSET(-1, 2);\n        CSET( 0, 2);\n        CSET( 1, 2);\n        CSET( 2, 2);\n\n        CSET(-1, 3);\n        CSET( 0, 3);\n        CSET( 1, 3);\n\n        CSET(-1,  0);\n        CSET( 1,  0);\n        CSET( 0, -1);\n        CSET( 0,  1);\n        CSET( 0,  0);\n        break;\n    default:\n        CSET(0 - 1, 0);\n        CSET(0 - 3, 0);\n        CSET(0 + 1, 0);\n        CSET(0 + 3, 0);\n        CSET(0, 0 - 1);\n        CSET(0, 0 - 3);\n        CSET(0, 0 + 1);\n        CSET(0, 0 + 3);\n        break;\n    }\n}\n\n/*\n==================\nSCR_DrawClients\ndraws clients list with selected values from userinfo\n==================\n*/\nvoid SCR_DrawClients(void)\n{\n\textern sb_showclients;\n\tint uid_w, clients;\n\tint i, y, x;\n\tchar line[128], buf[64];\n\n\tif (!sb_showclients || cls.state == ca_disconnected)\n\t\treturn;\n\n\t// prepare\n\tclients = 0;\n\tuid_w = 3;\n\tfor (i=0; i < MAX_CLIENTS; i++)\n\t{\n\t\tint w;\n\n\t\tif (!cl.players[i].name[0])\n\t\t\tcontinue;\n\n\t\tclients++;\n\t\tw = strlen(va(\"%d\", cl.players[i].userid));\n\n\t\tif (w > uid_w)\n\t\t\tuid_w = w;\n\t}\n\n\ty = (vid.height - sb_lines - 8 * (clients + 6));\n\ty = max (y, 0);\n\tx = (vid.width - 320) / 2 + 4;\n\n\tstrlcpy (line, \" # \", sizeof (line));\n\tsnprintf (buf, sizeof (buf), \"%*.*s \", uid_w, uid_w, \"uid\");\n\tstrlcat (line, buf, sizeof (line));\n\tsnprintf (buf, sizeof (buf), \"%-*.*s \", 16-uid_w, 16-uid_w, \"name\");\n\tstrlcat (line, buf, sizeof (line));\n\tstrlcat (line, \"team skin     rate\", sizeof (line));\n\n\tDraw_String (x, y, line);\n\ty += 8;\n\n\tstrlcpy (line, \"\\x1D\\x1F \\x1D\", sizeof (line));\n\tsnprintf (buf, sizeof (buf), \"%*.*s\", uid_w-2, uid_w-2, \"\\x1E\\x1E\\x1E\\x1E\");\n\tstrlcat (line, buf, sizeof (line));\n\tstrlcat (line, \"\\x1F \\x1D\", sizeof (line));\n\tsnprintf (buf, sizeof (buf), \"%*.*s\", 16-uid_w-2, 16-uid_w-2, \"\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\");\n\tstrlcat (line, buf, sizeof (line));\n    strlcat (line, \"\\x1F \\x1D\\x1E\\x1E\\x1F \\x1D\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1F \\x1D\\x1E\\x1E\\x1F\", sizeof (line));\n\n\tDraw_String(x, y, line);\n    y += 8;\n\n\tfor (i=0; i < MAX_CLIENTS; i++)\n\t{\n\t\tif (!cl.players[i].name[0])\n\t\t\tcontinue;\n\n\t\tif (y > vid.height - 8)\n\t\t\tbreak;\n\n\t\tline[0] = 0;\n\n\t\tsnprintf (buf, sizeof (buf), \"%2d \", i);\n\t\tstrlcat (line, buf, sizeof (line));\n\n\t\tsnprintf (buf, sizeof (buf), \"%*d \", uid_w, cl.players[i].userid);\n        strlcat(line, buf, sizeof (line));\n\n\t\tsnprintf (buf, sizeof (buf), \"%-*.*s \", 16-uid_w, 16-uid_w, cl.players[i].name);\n\t\tstrlcat (line, buf, sizeof (line));\n\n\t\tsnprintf(buf, sizeof (buf), \"%-4.4s \", Info_ValueForKey(cl.players[i].userinfo, \"team\"));\n\t\tstrlcat (line, buf, sizeof (line));\n\n\t\tif (cl.players[i].spectator)\n\t\t\tstrlcpy (buf, \"<spec>   \", sizeof (buf));\n\t\telse\n\t\t\tsnprintf (buf, sizeof (buf), \"%-8.8s \", Info_ValueForKey(cl.players[i].userinfo, \"skin\"));\n\n\t\tstrlcat (line, buf, sizeof (line));\n\n\t\tsnprintf (buf, sizeof (buf), \"%4d\", min(9999, atoi(Info_ValueForKey(cl.players[i].userinfo, \"rate\"))));\n\t\tstrlcat (line, buf, sizeof (line));\n\n\t\tDraw_String (x, y, line);\n\t\ty += 8;\n\t}\n}\n#endif\n\ncachepic_node_t *cachepics[CACHED_PICS_HDSIZE];\n\n/*\n * Removes the pic from the cache if refcount hits zero,\n * otherwise decreases refcount.\n */\nqbool CachePic_Remove(const char *path)\n{\n\tint key = Com_HashKey(path) % CACHED_PICS_HDSIZE;\n\tcachepic_node_t *searchpos = cachepics[key];\n\tcachepic_node_t *old = NULL;\n\n\twhile (searchpos) {\n\t\tif (!strcmp(searchpos->data.name, path)) {\n\t\t\tsearchpos->refcount--;\n\n\t\t\tif (searchpos->refcount == 0) {\n\t\t\t\tif (old == NULL) {\n\t\t\t\t\t/* It's the first entry in list.\n\t\t\t\t\t * Set new start to next item in list\n\t\t\t\t\t * or NULL if we're the only entry.*/\n\t\t\t\t\tcachepics[key] = searchpos->next;\n\t\t\t\t} else {\n\t\t\t\t\t/* It's in the middle or end of the list.\n\t\t\t\t\t * Need to make sure the prev entry points\n\t\t\t\t\t * to the next one and skips us. */\n\t\t\t\t\told->next = searchpos->next;\n\t\t\t\t}\n\t\t\t\t/* Free both data and our entry itself */\n\t\t\t\tQ_free(searchpos->data.pic);\n\t\t\t\tQ_free(searchpos);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\told = searchpos;\n\t\tsearchpos = searchpos->next;\n\t}\n\treturn false;\n}\n\n// Same as CachePic_Remove, but without specifying the name\nqbool CachePic_RemoveByPic(mpic_t* pic)\n{\n\tint key;\n\n\tfor (key = 0; key < CACHED_PICS_HDSIZE; ++key) {\n\t\tcachepic_node_t *searchpos = cachepics[key];\n\t\tcachepic_node_t *old = NULL;\n\n\t\twhile (searchpos) {\n\t\t\tif (searchpos->data.pic == pic) {\n\t\t\t\tsearchpos->refcount--;\n\n\t\t\t\tif (searchpos->refcount == 0) {\n\t\t\t\t\tif (old == NULL) {\n\t\t\t\t\t\t/* It's the first entry in list.\n\t\t\t\t\t\t* Set new start to next item in list\n\t\t\t\t\t\t* or NULL if we're the only entry.*/\n\t\t\t\t\t\tcachepics[key] = searchpos->next;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\t/* It's in the middle or end of the list.\n\t\t\t\t\t\t* Need to make sure the prev entry points\n\t\t\t\t\t\t* to the next one and skips us. */\n\t\t\t\t\t\told->next = searchpos->next;\n\t\t\t\t\t}\n\t\t\t\t\t/* Free both data and our entry itself */\n\t\t\t\t\tQ_free(searchpos->data.pic);\n\t\t\t\t\tQ_free(searchpos);\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\told = searchpos;\n\t\t\tsearchpos = searchpos->next;\n\t\t}\n\t}\n\treturn false;\n}\n\nmpic_t *CachePic_Find(const char *path, qbool inc_refcount) \n{\n\tint key = Com_HashKey(path) % CACHED_PICS_HDSIZE;\n\tcachepic_node_t *searchpos = cachepics[key];\n\n\twhile (searchpos) {\n\t\tif (!strcmp(searchpos->data.name, path)) {\n\t\t\tif (inc_refcount) {\n\t\t\t\tsearchpos->refcount++;\n\t\t\t}\n\t\t\treturn searchpos->data.pic;\n\t\t}\n\t\tsearchpos = searchpos->next;\n\t}\n\n\treturn NULL;\n}\n\n/* Copies data if necessary to make use of the refcount */\nmpic_t *CachePic_Add(const char *path, mpic_t *pic) \n{\n\tint key = Com_HashKey(path) % CACHED_PICS_HDSIZE;\n\tcachepic_node_t *searchpos = cachepics[key];\n\tcachepic_node_t **nextp = cachepics + key;\n\n\twhile (searchpos) {\n\t\t/* Check if we already have the entry */\n\t\tif (!strcmp(searchpos->data.name, path)) {\n\t\t\tsearchpos->refcount++;\n\t\t\treturn searchpos->data.pic;\n\t\t}\n\t\tnextp = &searchpos->next;\n\t\tsearchpos = searchpos->next;\n\t}\n\n\t/* We didn't find a matching entry, create a new one */\n\tsearchpos = Q_malloc_named(sizeof(cachepic_node_t), path);\n\tsearchpos->data.pic = Q_malloc_named(sizeof(mpic_t), path);\n\tmemcpy(searchpos->data.pic, pic, sizeof(mpic_t));\n\t\n\tstrlcpy(searchpos->data.name, path, sizeof(searchpos->data.name));\n\tsearchpos->next = NULL; // Terminate the list.\n\tsearchpos->refcount++;\n\t*nextp = searchpos;\t\t// Connect to the list.\n\n\treturn searchpos->data.pic;\n}\n\nvoid CachePics_Shutdown(void)\n{\n\tint i;\n\tcachepic_node_t *cur = NULL, *nxt = NULL;\n\n\tfor (i = 0; i < CACHED_PICS_HDSIZE; i++)\n\t{\n\t\tfor (cur = cachepics[i]; cur; cur = nxt)\n\t\t{\n\t\t\tnxt = cur->next;\n\t\t\tQ_free(cur->data.pic);\n\t\t\tQ_free(cur);\n\t\t}\n\t}\n\n\tmemset(cachepics, 0, sizeof(cachepics));\n\tDraw_ClearConback();\n}\n\nconst int COLOR_WHITE = 0xFFFFFFFF;\n\ncolor_t RGBA_TO_COLOR(byte r, byte g, byte b, byte a) \n{\n\treturn ((r << 0) | (g << 8) | (b << 16) | (a << 24)) & 0xFFFFFFFF;\n}\n\ncolor_t RGBAVECT_TO_COLOR(byte rgba[4])\n{\n\treturn ((rgba[0] << 0) | (rgba[1] << 8) | (rgba[2] << 16) | (rgba[3] << 24)) & 0xFFFFFFFF;\n}\n\ncolor_t RGBAVECT_TO_COLOR_PREMULT(byte rgba[4])\n{\n\tbyte premult[4];\n\tfloat alpha = rgba[3] / 255.0f;\n\n\tpremult[0] = rgba[0] * alpha;\n\tpremult[1] = rgba[1] * alpha;\n\tpremult[2] = rgba[2] * alpha;\n\tpremult[3] = rgba[3];\n\n\treturn ((premult[0] << 0) | (premult[1] << 8) | (premult[2] << 16) | (premult[3] << 24)) & 0xFFFFFFFF;\n}\n\ncolor_t RGBAVECT_TO_COLOR_PREMULT_SPECIFIC(byte rgba[4], float alpha)\n{\n\tbyte premult[4];\n\n\tpremult[0] = rgba[0] * alpha;\n\tpremult[1] = rgba[1] * alpha;\n\tpremult[2] = rgba[2] * alpha;\n\tpremult[3] = 255 * alpha;\n\n\treturn ((premult[0] << 0) | (premult[1] << 8) | (premult[2] << 16) | (premult[3] << 24)) & 0xFFFFFFFF;\n}\n\nbyte* COLOR_TO_RGBA(color_t i, byte rgba[4]) \n{\n\trgba[0] = (i >> 0  & 0xFF);\n\trgba[1] = (i >> 8  & 0xFF);\n\trgba[2] = (i >> 16 & 0xFF);\n\trgba[3] = (i >> 24 & 0xFF);\n\n\treturn rgba;\n}\n\nbyte* COLOR_TO_RGBA_PREMULT(color_t i, byte rgba[4])\n{\n\tfloat alpha;\n\n\trgba[0] = (i >> 0 & 0xFF);\n\trgba[1] = (i >> 8 & 0xFF);\n\trgba[2] = (i >> 16 & 0xFF);\n\trgba[3] = (i >> 24 & 0xFF);\n\n\talpha = rgba[3] / 255.0f;\n\trgba[0] *= alpha;\n\trgba[1] *= alpha;\n\trgba[2] *= alpha;\n\n\treturn rgba;\n}\n\nfloat* COLOR_TO_FLOATVEC_PREMULT(color_t i, float rgba[4])\n{\n\tfloat alpha = (i >> 24 & 0xFF) / 255.0f;\n\n\trgba[0] = ((i >> 0 & 0xFF) * alpha) / 255.0f;\n\trgba[1] = ((i >> 8 & 0xFF) * alpha) / 255.0f;\n\trgba[2] = ((i >> 16 & 0xFF) * alpha) / 255.0f;\n\trgba[3] = alpha;\n\n\treturn rgba;\n}\n\n//\n// Draw a subpic fitted inside a specified area.\n//\nvoid Draw_FitAlphaSubPic (int x, int y, int target_width, int target_height, \n\t\t\t\t\t\t  mpic_t *gl, int srcx, int srcy, int src_width, int src_height, float alpha)\n{\n\tfloat scale_x = (target_width / (float)src_width);\n\tfloat scale_y = (target_height / (float)src_height);\n\n\tDraw_SAlphaSubPic2(x, y, gl, srcx, srcy, src_width, src_height, scale_x, scale_y, alpha);\n\n\treturn;\n}\n\n//\n// Draws a subpic tiled inside of a specified target area.\n//\nvoid Draw_SubPicTiled(int x, int y, \n\t\t\t\t\tint target_width, int target_height, \n\t\t\t\t\tmpic_t *pic, int src_x, int src_y, \n\t\t\t\t\tint src_width, int src_height,\n\t\t\t\t\tfloat alpha)\n{\n    int cx\t\t\t\t\t= 0;\n\tint cy\t\t\t\t\t= 0;\n\tint scaled_src_width\t= src_width; //(src_width * scale);\n\tint scaled_src_height\t= src_height; //(src_height * scale); \n\tint draw_width;\n\tint draw_height;\n\n\twhile (cy < target_height)\n    {\n\t\twhile (cx < target_width)\n        {\n\t\t\tdraw_width = min(src_width, (target_width - cx));\n\t\t\tdraw_height = min(src_height, (target_height - cy));\n\n\t\t\t// TODO : Make this work properly in GL so that scale can be used (causes huge rounding issues).\n\t\t\tDraw_AlphaSubPic((x + cx), (y + cy), \n\t\t\t\t\t\t\tpic, \n\t\t\t\t\t\t\tsrc_x, src_y, \n\t\t\t\t\t\t\tdraw_width, draw_height, \n\t\t\t\t\t\t\talpha);\n\n            cx += scaled_src_width;\n        }\n\n        cx = 0;\n        cy += scaled_src_height;\n    }\n}\n\nconst char* SCR_GetTime (const char *format)\n{\n\tstatic char buf[128];\n\ttime_t t;\n\tstruct tm *ptm;\n\n\ttime (&t);\n\tptm = localtime (&t);\n\tif (!ptm) {\n\t\treturn \"-:-\";\n\t}\n\tif (!strftime(buf, sizeof(buf) - 1, format, ptm)) {\n\t\treturn \"-:-\";\n\t}\n\t//snprintf(buf, sizeof (buf), \"%2d:%02d:%02d\", tm->wHour, tm->wMinute, tm->wSecond);\n\treturn buf;\n}\n\nchar* SCR_GetGameTime(int t)\n{\n\tstatic char str[9];\n\tfloat timelimit;\n\n\ttimelimit = (t == TIMETYPE_GAMECLOCKINV) ? 60 * Q_atof(Info_ValueForKey(cl.serverinfo, \"timelimit\")) + 1: 0;\n\n\tif (cl.countdown || cl.standby)\n\t\tstrlcpy (str, SecondsToMinutesString(timelimit), sizeof(str));\n\telse\n\t\tstrlcpy (str, SecondsToMinutesString((int)fabs(timelimit - cl.gametime + *gameclockoffset)), sizeof(str));\n\n\treturn str;\n}\n\nchar* SCR_GetDemoTime(void)\n{\n\tstatic char str[9]; // '01:02:03\\0'\n\tstrlcpy (str, SecondsToMinutesString((int) (cls.demotime - demostarttime)), sizeof(str));\n\treturn str;\n}\n\nchar* SCR_GetHostTime(void)\n{\n\tstatic char str[9]; // '01:02:03\\0'\n\tstrlcpy(str, SecondsToMinutesString((int)curtime), sizeof(str));\n\treturn str;\n}\n\nchar* SCR_GetConnectedTime(void)\n{\n\tstatic char str[9]; // '01:02:03\\0'\n\tfloat time = (cl.servertime_works) ? cl.servertime : cls.realtime;\n\tstrlcpy(str, SecondsToHourString((int)time), sizeof(str));\n\treturn str;\n}\n\nint SCR_GetClockStringWidth(const char *s, qbool big, float scale, qbool proportional)\n{\n\tint w = 0;\n\tif (big) {\n\t\twhile (*s) {\n\t\t\tw += (*s++ == ':') ? (16 * scale) : (24 * scale);\n\t\t}\n\t}\n\telse {\n\t\tw = Draw_StringLength(s, -1, scale, proportional);\n\t}\n\treturn w;\n}\n\nint SCR_GetClockStringHeight(qbool big, float scale)\n{\n\tif (big) {\n\t\treturn (24 * scale);\n\t}\n\telse {\n\t\t// FIXME: proportional\n\t\treturn (LETTERWIDTH * scale);\n\t}\n}\n\nconst char* SCR_GetTimeString(int timetype, const char *format)\n{\n\tswitch (timetype) {\n\t\tcase TIMETYPE_GAMECLOCK:\n\t\tcase TIMETYPE_GAMECLOCKINV:\n\t\t\treturn SCR_GetGameTime(timetype);\n\n\t\tcase TIMETYPE_DEMOCLOCK:\n\t\t\treturn SCR_GetDemoTime();\n\n\t\tcase TIMETYPE_HOSTCLOCK:\n\t\t\treturn SCR_GetHostTime();\n\n\t\tcase TIMETYPE_CONNECTEDCLOCK:\n\t\t\treturn SCR_GetConnectedTime();\n\n\t\tcase TIMETYPE_CLOCK:\n\t\tdefault:\n\t\t\treturn SCR_GetTime(format);\n\t}\n}\n\n//\n// Finds the coordinates in the bigfont texture of a given character.\n//\nvoid Draw_GetBigfontSourceCoords(char c, int char_width, int char_height, int *sx, int *sy)\n{\n\tif (c >= 'A' && c <= 'Z')\n\t{\n\t\t(*sx) = ((c - 'A') % 8) * char_width;\n\t\t(*sy) = ((c - 'A') / 8) * char_height;\n\t}\n\telse if (c >= 'a' && c <= 'z')\n\t{\n\t\t// Skip A-Z, hence + 26.\n\t\t(*sx) = ((c - 'a' + 26) % 8) * char_width;\n\t\t(*sy) = ((c - 'a' + 26) / 8) * char_height;\n\t}\n\telse if (c >= '0' && c <= '9')\n\t{\n\t\t// Skip A-Z and a-z.\n\t\t(*sx) = ((c - '0' + 26 * 2) % 8 ) * char_width;\n\t\t(*sy) = ((c - '0' + 26 * 2) / 8) * char_height;\n\t}\n\telse if (c == ':')\n\t{\n\t\t// Skip A-Z, a-z and 0-9.\n\t\t(*sx) = ((c - '0' + 26 * 2 + 10) % 8) * char_width;\n\t\t(*sy) = ((c - '0' + 26 * 2 + 10) / 8) * char_height;\n\t}\n\telse if (c == '/')\n\t{\n\t\t// Skip A-Z, a-z, 0-9 and :\n\t\t(*sx) = ((c - '0' + 26 * 2 + 11) % 8) * char_width;\n\t\t(*sy) = ((c - '0' + 26 * 2 + 11) / 8) * char_height;\n\t}\n\telse\n\t{\n\t\t(*sx) = -1;\n\t\t(*sy) = -1;\n\t}\n}\n\nqbool Draw_BigFontAvailable(void)\n{\n\t// mcharset looks ugly in software rendering, therefore don't allow it in there\n\treturn Draw_CachePicSafe(MCHARSET_PATH, false, true) != NULL;\n}\n\nvoid SCR_DrawWadString(int x, int y, float scale, const char *t)\n{\n    extern  mpic_t\t*sb_nums[2][11];\n    extern  mpic_t\t*sb_colon, *sb_slash;\n\tqbool\t\t\tred = false;\n\n\twhile (*t)\n    {\n        if (*t >= '0'  &&  *t <= '9')\n        {\n\t\t\tDraw_STransPic(x, y, sb_nums[red ? 1 : 0][*t-'0'], scale);\n            x += 24*scale;\n        }\n        else if (*t == ':')\n        {\n\t\t\tDraw_STransPic (x, y, sb_colon, scale);\n\t\t\tx += 16*scale;\n        }\n\t\telse if (*t == '/')\n\t\t{\n\t\t\tDraw_STransPic(x, y, sb_slash, scale);\n            x += 16*scale;\n\t\t}\n\t\telse if (*t == '-')\n\t\t{\n\t\t\tx -= 12*scale;\n\t\t\tDraw_STransPic (x, y, sb_nums[red ? 1 : 0][10], scale);\n\t\t\tx += 28*scale;\n\t\t}\n\t\telse if (*t == ' ')\n\t\t{\n\t\t\tx += 16*scale;\n\t\t}\n\t\telse if(*t == 'c')\n\t\t{\n\t\t\tred = true;\n\t\t}\n\t\telse if(*t == 'r')\n\t\t{\n\t\t\tred = false;\n\t\t}\n        t++;\n    }\n}\n\n// little shortcut for SCR_HUD_DrawBar* functions\nvoid SCR_HUD_DrawBar(int direction, int value, float max_value, byte *color, int x, int y, int width, int height)\n{\n\tint amount;\n\n\tif(direction >= 2)\n\t\t// top-down\n\t\tamount = Q_rint(fabs((height * value) / max_value));\n\telse// left-right\n\t\tamount = Q_rint(fabs((width * value) / max_value));\n\n\tif(direction == 0)\n\t\t// left->right\n\t\tDraw_AlphaFillRGB(x, y, amount, height, RGBAVECT_TO_COLOR(color));\n\telse if (direction == 1)\n\t\t// right->left\n\t\tDraw_AlphaFillRGB(x + width - amount, y, amount, height, RGBAVECT_TO_COLOR(color));\n\telse if (direction == 2)\n\t\t// down -> up\n\t\tDraw_AlphaFillRGB(x, y + height - amount, width, amount, RGBAVECT_TO_COLOR(color));\n\telse\n\t\t// up -> down\n\t\tDraw_AlphaFillRGB(x, y, width, amount, RGBAVECT_TO_COLOR(color));\n}\n\n"
  },
  {
    "path": "src/common_draw.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n#ifndef __COMMON_DRAW__H__\n#define __COMMON_DRAW__H__\n\n#define TIMETYPE_CLOCK             0\n#define TIMETYPE_GAMECLOCK         1\n#define TIMETYPE_GAMECLOCKINV      2\n#define TIMETYPE_DEMOCLOCK         3\n#define TIMETYPE_HOSTCLOCK         4\n#define TIMETYPE_CONNECTEDCLOCK    5\n\nvoid CommonDraw_Init(void);\n\ntypedef struct cachepic_s \n{\n\tchar\t\tname[MAX_QPATH];\n\tmpic_t\t\t*pic;\n} cachepic_t;\n\ntypedef struct cachepic_node_s \n{\n\tcachepic_t data;\n\tunsigned int refcount;\n\tstruct cachepic_node_s *next;\n\tstruct cachepic_node_s *size_order;\n\tint width, height;\n} cachepic_node_t;\n\n#define\tCACHED_PICS_HDSIZE\t\t64\n\nmpic_t *CachePic_Find(const char *path, qbool inc_refcount);\nmpic_t* CachePic_Add(const char *path, mpic_t *pic);\nqbool CachePic_Remove(const char *path);\nqbool CachePic_RemoveByPic(mpic_t* pic);\nvoid CachePics_Shutdown(void);\n\nint SCR_GetClockStringWidth(const char *s, qbool big, float scale, qbool proportional);\nint SCR_GetClockStringHeight(qbool big, float scale);\nconst char* SCR_GetTimeString(int timetype, const char *format);\nvoid SCR_NetStats(int x, int y, float period);\n\nvoid Draw_FitAlphaSubPic (int x, int y, int target_width, int target_height, \n\t\t\t\t\t\t  mpic_t *gl, int srcx, int srcy, int src_width, int src_height, float alpha);\n\nvoid Draw_SubPicTiled(int x, int y, \n\t\t\t\t\tint target_width, int target_height, \n\t\t\t\t\tmpic_t *pic, int src_x, int src_y, \n\t\t\t\t\tint src_width, int src_height,\n\t\t\t\t\tfloat alpha);\n\nvoid HUD_BeforeDraw(void);\nvoid HUD_AfterDraw(void);\n\nqbool Draw_BigFontAvailable(void);\n\nextern int *gameclockoffset;  // hud_gameclock time offset in seconds\n\nvoid SCR_DrawWadString(int x, int y, float scale, const char *t);\nvoid SCR_HUD_DrawBar(int direction, int value, float max_value, byte *color, int x, int y, int width, int height);\n\n#endif  // __COMMON_DRAW__H__\n"
  },
  {
    "path": "src/config_manager.c",
    "content": "/*\nCopyright (C) 2001-2002 A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut\tWITHOUT ANY WARRANTY; without even the implied warranty\tof\nMERCHANTABILITY or FITNESS FOR A PARTICULARPURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou\tshould have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: config_manager.c,v 1.49 2007-10-11 05:55:47 dkure Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"input.h\"\n#include \"utils.h\"\n#include \"keys.h\"\n#include \"fs.h\"\n#include \"config_manager.h\"\n#include \"version.h\"\n#include \"gl_model.h\"\n#include \"vfs.h\"\n\nchar *Key_KeynumToString (int keynum);\n\nvoid DumpSkyGroups(FILE *f);\nqbool Key_IsLeftRightSameBind(int b);\nvoid DumpMapGroups(FILE *f);\nvoid TP_DumpTriggers(FILE *);\nvoid TP_DumpMsgFilters(FILE *f);\nvoid TP_ResetAllTriggers(void);\nqbool Cmd_DeleteAlias (char *);\nvoid DumpFlagCommands(FILE *);\nint Cvar_CvarCompare (const void *p1, const void *p2);\nint Cmd_AliasCompare (const void *p1, const void *p2);\n\nextern void WriteSourcesConfiguration(FILE *f);\nvoid MarkDefaultSources(void);\n\nextern cvar_group_t *cvar_groups;\nextern cmd_alias_t *cmd_alias;\nextern cvar_t *cvar_vars;\n\nextern kbutton_t\tin_mlook, in_klook,\tin_left, in_right, in_forward, in_back;\nextern kbutton_t\tin_lookup, in_lookdown,\tin_moveleft, in_moveright;\nextern kbutton_t\tin_strafe, in_speed, in_use, in_jump, in_attack, in_up,\tin_down;\n\nextern qbool\t\tsb_showscores, sb_showteamscores;\n\nextern cvar_t\t\tcl_teamtopcolor, cl_teambottomcolor, cl_enemytopcolor, cl_enemybottomcolor;\nextern char\t\tallskins[MAX_OSPATH];\n\ncvar_t\tcfg_save_unchanged\t=\t{\"cfg_save_unchanged\", \"0\"};\ncvar_t\tcfg_save_userinfo\t=\t{\"cfg_save_userinfo\", \"2\"};\ncvar_t\tcfg_save_onquit\t\t=\t{\"cfg_save_onquit\", \"0\"};\ncvar_t\tcfg_save_cvars\t\t=\t{\"cfg_save_cvars\", \"1\"};\ncvar_t\tcfg_save_aliases\t=\t{\"cfg_save_aliases\", \"1\"};\ncvar_t\tcfg_save_cmds\t\t=\t{\"cfg_save_cmds\", \"1\"};\ncvar_t\tcfg_save_binds\t\t=\t{\"cfg_save_binds\", \"1\"};\ncvar_t\tcfg_save_sysinfo\t=\t{\"cfg_save_sysinfo\", \"0\"};\ncvar_t\tcfg_save_cmdline\t=\t{\"cfg_save_cmdline\", \"1\"};\ncvar_t\tcfg_backup\t\t\t=\t{\"cfg_backup\", \"0\"};\ncvar_t\tcfg_legacy_exec\t\t=\t{\"cfg_legacy_exec\", \"1\"};\ncvar_t  cfg_use_home\t\t=\t{\"cfg_use_home\", \"0\"};\ncvar_t  cfg_use_gamedir\t\t=\t{\"cfg_use_gamedir\", \"0\"};\n\n/************************************ DUMP FUNCTIONS ************************************/\n\n#define BIND_ALIGN_COL 20\nvoid DumpBindings (FILE *f)\n{\n\tint i, leftright;\n\tchar *spaces, *string;\n\tqbool printed = false;\n\n\tfprintf(f, \"unbindall\\n\");\n\tfor (i = 0; i < (sizeof(keybindings) / sizeof(*keybindings)); i++) {\n\n\t\tleftright = Key_IsLeftRightSameBind(i) ? 1 : 0;\n\t\tif (keybindings[i] || leftright) {\n\t\t\tprinted = true;\n\t\t\tstring = Key_KeynumToString(i);\n\t\t\tspaces = CreateSpaces(BIND_ALIGN_COL - strlen(string) - 6);\n\t\t\tif (i == ';')\n\t\t\t\tfprintf (f, \"bind  \\\";\\\"%s\\\"%s\\\"\\n\", spaces, keybindings[i]);\n\t\t\telse\n\t\t\t\tfprintf (f, \"bind  %s%s\\\"%s\\\"\\n\", string, spaces, keybindings[leftright ? i + 1 : i]);\n\n\t\t\tif (leftright)\n\t\t\t\ti += 2;\n\t\t}\n\t}\n\tif (!printed)\n\t\tfprintf(f, \"//no bindings\\n\");\n}\n\nstatic qbool Config_Unsaved_Cvar(const char *name)\n{\n\treturn (!strcmp(name, \"cl_delay_packet\"))\n        || (!strcmp(name, \"cl_proxyaddr\"))\n        || (!strcmp(name, \"hud_planmode\"))\n        || (!strcmp(name, \"con_bindphysical\"));\n}\n\n#define CONFIG_MAX_COL 60\n#define MAX_DUMPED_CVARS 4096\nstatic void DumpVariables(FILE\t*f)\n{\n\tcvar_t *var, *sorted_vars[MAX_DUMPED_CVARS];\n\tcvar_group_t *group;\n\tchar *spaces;\n\tint count, i, col_size;\n\tqbool skip_userinfo = false;\n\n\n\n\tfor (col_size = 0,  var = cvar_vars; var; var = var->next) {\n\t\tif ( !(\n\t\t            (var->flags & (CVAR_USER_CREATED | CVAR_ROM | CVAR_INIT)) ||\n\t\t            (var->group && (!strcmp(CVAR_GROUP_NO_GROUP, var->group->name) || !strcmp(CVAR_GROUP_SERVERINFO, var->group->name)) )\n\t\t        )) {\n\t\t\tcol_size = max(col_size, strlen(var->name));\n\t\t}\n\t}\n\tcol_size = min(col_size + 2, CONFIG_MAX_COL);\n\n\n\tif (cfg_save_unchanged.value) {\n\t\tfprintf(f, \"//All variables (even those with default values) are listed below.\\n\");\n\t\tfprintf(f, \"//You can use \\\"cfg_save_unchanged 0\\\" to save only changed variables.\\n\\n\");\n\t} else {\n\t\tfprintf(f, \"//Only variables with non-default values are listed below.\\n\");\n\t\tfprintf(f, \"//You can use \\\"cfg_save_unchanged 1\\\" to save all variables.\\n\\n\");\n\t}\n\n\n\tfor (group = cvar_groups; group; group = group->next)  {\n\n\t\tif (\n\t\t    !strcmp(CVAR_GROUP_NO_GROUP, group->name) ||\n\t\t    !strcmp(CVAR_GROUP_SERVERINFO, group->name) ||\n\t\t    (!cfg_save_userinfo.value && !strcmp(CVAR_GROUP_USERINFO, group->name))\n\t\t)\n\t\t\tcontinue;\n\n\t\tskip_userinfo = ((cfg_save_userinfo.value == 1) && !strcmp(CVAR_GROUP_USERINFO, group->name)) ? true : false;\n\n\n\t\tfor (count = 0, var = group->head; var && count < MAX_DUMPED_CVARS; var = var->next_in_group) {\n\t\t\tif (skip_userinfo && (\n\t\t\t            !strcmp(var->name, \"team\") || !strcmp(var->name, \"skin\") ||\n\t\t\t            !strcmp(var->name, \"spectator\") || !strcmp(var->name, \"topcolor\") ||\n\t\t\t\t    !strcmp(var->name, \"bottomcolor\")\n\t\t\t        ))\n\t\t\t\tcontinue;\n\t\t\t\n\t\t\tif (Config_Unsaved_Cvar(var->name)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t\n\t\t\tif (!(var->flags & (CVAR_USER_CREATED |\tCVAR_ROM | CVAR_INIT))) {\n\t\t\t\tif (cfg_save_unchanged.value || strcmp(var->string, var->defaultvalue)) {\n\t\t\t\t\tsorted_vars[count++] = var;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!count)\n\t\t\tcontinue;\n\n\n\t\tif (\n\t\t    strcmp(group->name, CVAR_GROUP_ITEM_NAMES) &&\n\t\t    strcmp(group->name, CVAR_GROUP_ITEM_NEED) &&\n\t\t    strcmp(group->name, CVAR_GROUP_USERINFO) &&\n\t\t    strcmp(group->name, CVAR_GROUP_SKIN)\n\t\t)\n\t\t\tqsort(sorted_vars, count, sizeof (cvar_t *), Cvar_CvarCompare);\n\n\n\t\tfprintf(f, \"//%s\\n\", group->name);\n\n\n\t\tfor (i = 0; i < count; i++) {\n\t\t\tvar = sorted_vars[i];\n\t\t\tif (cfg_save_unchanged.value || strcmp(var->string, var->defaultvalue)) {\n\t\t\t\tspaces = CreateSpaces(col_size - strlen(var->name));\n\t\t\t\tfprintf(f, \"%s%s\\\"%s\\\"\\n\", var->name, spaces, var->string);\n\t\t\t}\n\t\t}\n\n\t\tfprintf(f, \"\\n\");\n\t}\n\n\n\n\tfor (count = 0, var = cvar_vars; var && count < MAX_DUMPED_CVARS; var = var->next) {\n\t\tif (!var->group && !(var->flags & (CVAR_USER_CREATED | CVAR_ROM | CVAR_INIT))) {\n\t\t\tif (cfg_save_unchanged.value || strcmp(var->string, var->defaultvalue)) {\n\t\t\t\tsorted_vars[count++] = var;\n\t\t\t}\n\t\t}\n\t}\n\tif (count) {\n\n\t\tqsort(sorted_vars, count, sizeof (cvar_t *), Cvar_CvarCompare);\n\n\n\t\tfprintf(f, \"//Unsorted Variables\\n\");\n\n\n\t\tfor (i = 0; i < count; i++) {\n\t\t\tvar = sorted_vars[i];\n\t\t\tspaces = CreateSpaces(col_size - strlen(var->name));\n\t\t\tfprintf(f, \"%s%s\\\"%s\\\"\\n\", var->name, spaces, var->string);\n\t\t}\n\n\t\tfprintf(f, \"\\n\");\n\t}\n\n\n\n\tfor\t(col_size = col_size - 2, count = 0, var = cvar_vars; var && count < MAX_DUMPED_CVARS; var = var->next)\t{\n\t\tif ((var->flags & CVAR_USER_CREATED) && !(var->flags & CVAR_MOD_CREATED)) {\n\t\t\tsorted_vars[count++] = var;\n\t\t\tcol_size = max(col_size, strlen(var->name));\n\t\t}\n\t}\n\tif (!count)\n\t\treturn;\n\n\tcol_size = min(col_size + 2, CONFIG_MAX_COL);\n\n\n\tqsort(sorted_vars, count, sizeof (cvar_t *), Cvar_CvarCompare);\n\n\n\tfprintf(f, \"//User Created Variables\\n\");\n\n\n\tfor (i = 0; i < count; i++) {\n\t\tvar = sorted_vars[i];\n\n\t\tspaces = CreateSpaces(col_size - strlen(var->name) - 5);\n\t\tfprintf\t(f, \"%s %s%s\\\"%s\\\"\\n\", (var->teamplay) ? \"set_tp\" : \"set\", var->name, spaces, var->string);\n\t}\n}\n\nvoid DumpVariablesDefaults_f(void)\n{\n\tcvar_t *var;\n\tcvar_group_t *group;\n    char filepath[MAX_PATH];\n    FILE *f;\n\tqbool anyUngrouped = false;\n\n    snprintf(filepath, sizeof(filepath), \"%s/ezquake/configs/cvar_defaults.cfg\", com_basedir);\n\n    f = fopen(filepath, \"w\");\n    if (!f)\n    {\n        Com_Printf(\"Couldn't open %s for writing\\n\", filepath);\n        return;\n    }\n\n\tfor (group = cvar_groups; group; group = group->next) {\n\t    fprintf(f, \"\\n//%s\\n\", group->name);\n\n        for (var = group->head; var; var = var->next_in_group) {\n\t\t\tfprintf(f, \"%s \\\"%s\\\"\\n\", var->name, var->defaultvalue);\n\t\t}\n\t}\n\n\t// Add those without\n\tfor (var = cvar_vars; var; var = var->next) {\n\t\tif (!var->group && !(var->flags & (CVAR_USER_CREATED | CVAR_ROM | CVAR_INIT))) {\n\t\t\tif (!anyUngrouped)\n\t\t\t\tfprintf(f, \"\\n//\\n\");\n\t\t\tfprintf(f, \"%s \\\"%s\\\"\\n\", var->name, var->defaultvalue);\n\t\t\tanyUngrouped = true;\n\t\t}\n\t}\n\n    if (fclose(f))\n        Com_Printf(\"Couldn't close %s\", filepath);\n    else\n        Com_Printf(\"Variables default values dumped to:\\n%s\\n\", filepath);\n}\n\n#define MAX_ALIGN_COL 60\nstatic void DumpAliases(FILE *f)\n{\n\tint maxlen, i, j, count, lonely_count, minus_index, minus_count;\n\tchar *spaces;\n\tcmd_alias_t\t*b, *a, *sorted_aliases[2048], *lonely_pluses[512];\n\tqbool partner, printed;\n\n\tfprintf(f, \"unaliasall\\n\");\n\tfor (count = maxlen = 0, a = cmd_alias;\t(count < (sizeof(sorted_aliases) / sizeof(*sorted_aliases))) && a; a = a->next) {\n\t\tif (!(a->flags & (ALIAS_SERVER|ALIAS_TEMP))) {\n\t\t\tmaxlen = max(maxlen, strlen(a->name));\n\t\t\tcount++;\n\t\t}\n\t}\n\n\tif (!count) {\n\t\tfprintf(f, \"//no aliases\\n\");\n\t\treturn;\n\t}\n\n\tfor (i = 0, a = cmd_alias; i < count; a = a->next) {\n\t\tif (!(a->flags & (ALIAS_SERVER|ALIAS_TEMP)))\n\t\t\tsorted_aliases[i++] = a;\n\t}\n\n\tqsort(sorted_aliases, count, sizeof (cmd_alias_t *), Cmd_AliasCompare);\n\n\n\n\tfor (minus_index = -1, minus_count = i = 0; i < count; i++) {\n\t\ta = sorted_aliases[i];\n\n\t\tif (a->name[0] == '-') {\n\t\t\tif (minus_index == -1)\n\t\t\t\tminus_index = i;\n\t\t\tminus_count++;\n\t\t} else if (a->name[0] != '+')\n\t\t\tbreak;\n\t}\n\n\tprinted = false;\n\n\tfor (lonely_count = i = 0; i < count; i++) {\n\t\ta = sorted_aliases[i];\n\n\t\tif (a->name[0] != '+')\n\t\t\tbreak;\n\n\t\tfor (partner = false, j = minus_index; j >= 0 && j < minus_index + minus_count; j++) {\n\t\t\tb = sorted_aliases[j];\n\n\t\t\tif (!strcasecmp(b->name + 1, a->name + 1)) {\n\n\t\t\t\tspaces = CreateSpaces(maxlen + 3 - strlen(a->name));\n\t\t\t\tfprintf\t(f, \"alias %s%s\\\"%s\\\"\\n\", a->name, spaces, a->value);\n\t\t\t\tspaces = CreateSpaces(maxlen + 3 - strlen(b->name));\n\t\t\t\tfprintf\t(f, \"alias %s%s\\\"%s\\\"\\n\", b->name, spaces, b->value);\n\t\t\t\tprinted = partner = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!partner)\n\t\t\tlonely_pluses[lonely_count++] = a;\n\t}\n\n\n\tfor (i = 0; i < lonely_count; i++) {\n\t\ta = lonely_pluses[i];\n\n\t\tspaces = CreateSpaces(maxlen + 3 - strlen(a->name));\n\t\tfprintf\t(f, \"alias %s%s\\\"%s\\\"\\n\", a->name, spaces, a->value);\n\t\tprinted = true;\n\t}\n\n\n\tfor (i = minus_index; i >= 0 && i < minus_index + minus_count; i++) {\n\t\ta = sorted_aliases[i];\n\n\t\tfor (partner = false, j = 0; j < minus_index; j++) {\n\t\t\tb = sorted_aliases[j];\n\n\t\t\tif (!strcasecmp(b->name + 1, a->name + 1)) {\n\n\t\t\t\tpartner = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!partner) {\n\n\t\t\tspaces = CreateSpaces(maxlen + 3 - strlen(a->name));\n\t\t\tfprintf\t(f, \"alias %s%s\\\"%s\\\"\\n\", a->name, spaces, a->value);\n\t\t\tprinted = true;\n\t\t}\n\t}\n\tfor (i = (minus_index == -1 ? 0 : minus_index + minus_count); i < count; i++) {\n\t\ta = sorted_aliases[i];\n\n\t\tif (minus_index != -1 || a->name[0] != '+') {\n\t\t\tif (printed)\n\t\t\t\tfprintf(f, \"\\n\");\n\t\t\tprinted = false;\n\t\t\tspaces = CreateSpaces(maxlen + 3 - strlen(a->name));\n\t\t\tfprintf\t(f, \"alias %s%s\\\"%s\\\"\\n\", a->name, spaces, a->value);\n\t\t}\n\t}\n}\n\nstatic void DumpPlusCommand(FILE *f, kbutton_t\t*b,\tconst char *name)\n{\n\tif (b->state & 1 && b->down[0] < 0)\n\t\tfprintf(f, \"+%s\\n\",\tname);\n\telse\n\t\tfprintf(f, \"-%s\\n\",\tname);\n}\n\nstatic void DumpPlusCommands(FILE *f)\n{\n\tDumpPlusCommand(f, &in_up, \"moveup\");\n\tDumpPlusCommand(f, &in_down, \"movedown\");\n\tDumpPlusCommand(f, &in_left, \"left\");\n\tDumpPlusCommand(f, &in_right, \"right\");\n\tDumpPlusCommand(f, &in_forward,\t\"forward\");\n\tDumpPlusCommand(f, &in_back, \"back\");\n\tDumpPlusCommand(f, &in_lookup, \"lookup\");\n\tDumpPlusCommand(f, &in_lookdown, \"lookdown\");\n\tDumpPlusCommand(f, &in_strafe, \"strafe\");\n\tDumpPlusCommand(f, &in_moveleft, \"moveleft\");\n\tDumpPlusCommand(f, &in_moveright, \"moveright\");\n\tDumpPlusCommand(f, &in_speed, \"speed\");\n\tDumpPlusCommand(f, &in_attack, \"attack\");\n\tDumpPlusCommand(f, &in_use,\t\"use\");\n\tDumpPlusCommand(f, &in_jump, \"jump\");\n\tDumpPlusCommand(f, &in_klook, \"klook\");\n\tDumpPlusCommand(f, &in_mlook, \"mlook\");\n\n\tfprintf(f, sb_showscores ? \"+showscores\\n\" : \"-showscores\\n\");\n\tfprintf(f, sb_showteamscores ? \"+showteamscores\\n\" : \"-showteamscores\\n\");\n}\n\nstatic void DumpTeamplay(FILE *f)\n{\n\n\tif (allskins[0]) {\n\t\tfprintf(f, \"allskins \\\"%s\\\"\\n\", allskins);\n\t}\n\n\tfprintf(f, \"\\n\");\n\n\tDumpFlagCommands(f);\n\n\tfprintf(f, \"\\n\");\n\tTP_DumpMsgFilters(f);\n\n\tfprintf(f, \"\\n\");\n\tTP_DumpTriggers(f);\n}\n\n#ifndef CLIENTONLY\nvoid DumpFloodProtSettings(FILE *f)\n{\n\textern int fp_messages, fp_persecond, fp_secondsdead;\n\tfprintf(f, \"floodprot %d %d %d\\n\", fp_messages, fp_persecond, fp_secondsdead);\n}\n#endif\n\nvoid DumpMisc(FILE *f)\n{\n\n\tDumpMapGroups(f);\n\tfprintf(f, \"\\n\");\n\n\tDumpSkyGroups(f);\n\tfprintf(f, \"\\n\");\n\n#ifndef CLIENTONLY\n\tDumpFloodProtSettings(f);\n\tfprintf(f, \"\\n\");\n#endif\n\n\tfprintf(f, \"hud_recalculate\\n\\n\");\n\n\tif (cl.teamfortress) {\n\t\tif (!strcasecmp(Info_ValueForKey (cls.userinfo, \"ec\"), \"on\") ||\n\t\t        !strcasecmp(Info_ValueForKey (cls.userinfo, \"exec_class\"), \"on\")\n\t\t   ) {\n\t\t\tfprintf(f, \"setinfo ec on\\n\");\n\t\t}\n\t\tif (!strcasecmp(Info_ValueForKey (cls.userinfo, \"em\"), \"on\") ||\n\t\t        !strcasecmp(Info_ValueForKey (cls.userinfo, \"exec_map\"), \"on\")\n\t\t   ) {\n\t\t\tfprintf(f, \"setinfo em on\\n\");\n\t\t}\n\t}\n}\n\nvoid DumpCmdLine(FILE *f)\n{\n\tfprintf(f, \"// %s\\n\", cl_cmdline.string);\n}\n\nvoid DumpHUD262(FILE *f)\n{\n\thud_element_t *elem;\n\tchar *type;\n\tchar *param;\n\tint i, mask;\n\textern hud_element_t *hud_list;\n\n\telem = hud_list;\n\n\tfor(i = 0; elem; elem = elem->next, i++) {\n\t\tif (elem->flags & HUD_CVAR) {\n\t\t\ttype = \"cvar\";\n\t\t\tparam = ((cvar_t*)elem->contents)->name;\n\t\t} else if (elem->flags & HUD_STRING) {\n\t\t\ttype = \"str\";\n\t\t\tparam = elem->contents;\n\t\t} else {\n\t\t\tcontinue;\n\t\t}\n\n\t\tmask = (elem->flags & HUD_BLINK_F) ? 1 : 0 + (elem->flags & HUD_BLINK_B) ? 2 : 0;\n\t\tfprintf(f, \"hud262_add %s %s %s\\n\", elem->name, type, param);\n\t\tfprintf(f, \"hud262_alpha %s %1.2f\\n\", elem->name, elem->alpha);\n\t\tfprintf(f, \"hud262_bg %s %d\\n\", elem->name, elem->coords[3]);\n\t\tfprintf(f, \"hud262_blink %s %d %d\\n\", elem->name, (int) ceil(elem->blink * 1000.0), mask);\n\t\tfprintf(f, \"hud262_%s %s\\n\", (elem->flags & HUD_ENABLED) ? \"enable\" : \"disable\", elem->name);\n\t\tfprintf(f, \"hud262_position %s %d %d %d\\n\", elem->name, elem->coords[0], elem->coords[1], elem->coords[2]);\n\t\tfprintf(f, \"hud262_width %s %d\\n\", elem->name, elem->width);\n\t}\n}\n\n/************************************ RESET FUNCTIONS ************************************/\n\nstatic void ResetVariables(int cvar_flags, qbool resetting_before_load)\n{\n\tcvar_t *var;\n\n\tif (resetting_before_load) {\n\t\tif (!cfg_save_userinfo.integer) {\n\t\t\tcvar_flags |= CVAR_USERINFO;\n\t\t}\n\t\telse if (cfg_save_userinfo.integer == 1) {\n\t\t\tcvar_flags |= CVAR_USERINFONORESET;\n\t\t}\n\t}\n\n\tfor (var = cvar_vars; var; var = var->next) {\n\t\t// don't reset based on flags (see cfg_save_userinfo)\n\t\tif (var->flags & (cvar_flags | CVAR_ROM | CVAR_INIT | CVAR_USER_CREATED | CVAR_NO_RESET)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// don't reset ungrouped cvars (... why?)\n\t\tif (var->group && !strcmp(var->group->name, CVAR_GROUP_NO_GROUP)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tCvar_ResetVar(var);\n\t}\n}\n\nstatic void DeleteUserAliases(void)\n{\n\tcmd_alias_t *a, *next;\n\n\tfor (a = cmd_alias; a; a = next) {\n\t\tnext = a->next;\n\n\t\tif (!(a->flags & (ALIAS_SERVER|ALIAS_TEMP)))\n\t\t\tCmd_DeleteAlias(a->name);\n\t}\n}\n\nstatic void DeleteUserVariables(void)\n{\n\tcvar_t *var, *next;\n\n\tfor (var = cvar_vars; var; var = next) {\n\t\tnext = var->next;\n\n\t\tif ((var->flags & CVAR_USER_CREATED) && !(var->flags & CVAR_MOD_CREATED)) {\n\t\t\tCvar_Delete(var->name);\n\t\t}\n\t}\n\n}\n\nstatic void ResetPlusCommands(void)\n{\n\tCbuf_AddText(\"-moveup;-movedown\\n\");\n\tCbuf_AddText(\"-left;-right\\n\");\n\tCbuf_AddText(\"-forward;-back\\n\");\n\tCbuf_AddText(\"-lookup;-lookdown\\n\");\n\tCbuf_AddText(\"-strafe\\n\");\n\tCbuf_AddText(\"-moveleft;-moveright\\n\");\n\tCbuf_AddText(\"-speed\\n\");\n\tCbuf_AddText(\"-attack\\n\");\n\tCbuf_AddText(\"-use\\n\");\n\tCbuf_AddText(\"-jump\\n\");\n\tCbuf_AddText(\"-klook\\n\");\n\tCbuf_AddText(\"-mlook\\n\");\n\n\tCbuf_AddText(\"-showscores\\n\");\n\tCbuf_AddText(\"-showteamscores\\n\");\n}\n\nvoid ResetBinds(void)\n{\n\tKey_Unbindall_f();\n\n\tKey_SetBinding(K_ESCAPE, \"togglemenu\");\n\tKey_SetBinding('`',      \"toggleconsole\");\n#ifdef __APPLE__\n\tKey_SetBinding('~',      \"toggleconsole\");\n#endif\n\tKey_SetBinding(K_PAUSE,  \"toggleproxymenu\");\n\n\tKey_SetBinding(K_MOUSE1, \"+attack\");\n\tKey_SetBinding(K_CTRL,   \"+attack\");\n\tKey_SetBinding(K_MOUSE2, \"+jump\");\n\tKey_SetBinding(K_SPACE,  \"+jump\");\n\n\tKey_SetBinding('w',      \"+forward\");\n\tKey_SetBinding('s',      \"+back\");\n\tKey_SetBinding('a',      \"+moveleft\");\n\tKey_SetBinding('d',      \"+moveright\");\n\tKey_SetBinding('e',      \"impulse 10\");\n\tKey_SetBinding('q',      \"impulse 12\");\n\tKey_SetBinding('t',      \"messagemode\");\n\tKey_SetBinding('y',      \"messagemode2\");\n\tKey_SetBinding('1',\t\t \"impulse 1\");\n\tKey_SetBinding('2',\t\t \"impulse 2\");\n\tKey_SetBinding('3',\t\t \"impulse 3\");\n\tKey_SetBinding('4',\t\t \"impulse 4\");\n\tKey_SetBinding('5',\t\t \"impulse 5\");\n\tKey_SetBinding('6',\t\t \"impulse 6\");\n\tKey_SetBinding('7',\t\t \"impulse 7\");\n\tKey_SetBinding('8',\t\t \"impulse 8\");\n\tKey_SetBinding('9',\t\t \"impulse 9\");\n\tKey_SetBinding('0',\t\t \"impulse 10\");\n\n\tKey_SetBinding(K_ALT,    \"+zoom\");\n\tKey_SetBinding(K_TAB,    \"+showscores\");\n\n\tKey_SetBinding('r',      \"tp_msgreport\");\n\tKey_SetBinding('z',      \"tp_msgtook\");\n\tKey_SetBinding('x',\t\t \"tp_msgsafe\");\n\tKey_SetBinding('c',      \"tp_msghelp\");\n\tKey_SetBinding('v',      \"tp_msgpoint\");\n\n\t// user will get confusing warnings on these if he joins a server where these do not exist\n\t// maybe some wrappers should be made for these commands which would\n\t// before calling them check if they exist\n    Key_SetBinding('f',      \"shownick\");\n\tKey_SetBinding(K_F1,     \"yes\");\n\tKey_SetBinding(K_F2,     \"agree\");\n\tKey_SetBinding(K_F3,     \"ready\");\n\tKey_SetBinding(K_F4,     \"break\");\n\tKey_SetBinding(K_F5,     \"join\");\n\tKey_SetBinding(K_F6,     \"observe\");\n\tKey_SetBinding(K_F10,    \"quit\");\n\tKey_SetBinding(K_F12,    \"screenshot\");\n}\n\nstatic void ResetTeamplayCommands(void)\n{\n\tallskins[0]\t= 0;\n\tCbuf_AddText(\"enemycolor off\\nteamcolor\toff\\n\");\n\tCbuf_AddText(\"filter clear\\n\");\n\tTP_ResetAllTriggers();\n\tCbuf_AddText(\"tp_took default\\ntp_pickup default\\ntp_point default\\n\");\n}\n\nstatic void ResetMiscCommands(void)\n{\n\tCbuf_AddText(\"mapgroup clear\\n\");\n\tCbuf_AddText(\"skygroup clear\\n\");\n\n\tMarkDefaultSources();\n\n\tInfo_RemoveKey(cls.userinfo, \"ec\");\n\tInfo_RemoveKey(cls.userinfo, \"exec_class\");\n\tInfo_RemoveKey(cls.userinfo, \"em\");\n\tInfo_RemoveKey(cls.userinfo, \"exec_map\");\n}\n\n/************************************ PRINTING FUNCTIONS ************************************/\n\n#define CONFIG_WIDTH 100\n\nstatic void Config_PrintBorder(FILE *f)\n{\n\tchar buf[CONFIG_WIDTH + 1] = {0};\n\n\tif (!buf[0]) {\n\t\tmemset(buf, '/', CONFIG_WIDTH);\n\t\tbuf[CONFIG_WIDTH] = 0;\n\t}\n\tfprintf(f, \"%s\\n\", buf);\n}\n\nstatic void Config_PrintLine(FILE *f, char *title, int width)\n{\n\tchar buf[CONFIG_WIDTH + 1] = {0};\n\tint title_len, i;\n\n\twidth = bound(1, width, CONFIG_WIDTH << 3);\n\n\tfor (i = 0; i < width; i++)\n\t\tbuf[i] = buf[CONFIG_WIDTH - 1 - i] = '/';\n\tmemset(buf + width,  ' ', CONFIG_WIDTH - 2 * width);\n\tif (strlen(title) > CONFIG_WIDTH - (2 * width + 4))\n\t\ttitle = \"Config_PrintLine : TITLE TOO BIG\";\n\ttitle_len = strlen(title);\n\tmemcpy(buf + width + ((CONFIG_WIDTH - title_len - 2 * width) >>\t1),\ttitle, title_len);\n\tbuf[CONFIG_WIDTH] = 0;\n\tfprintf(f, \"%s\\n\", buf);\n}\n\nstatic void Config_PrintHeading(FILE *f, char *title)\n{\n\tConfig_PrintBorder(f);\n\tConfig_PrintLine(f, \"\", 2);\n\tConfig_PrintLine(f, title, 2);\n\tConfig_PrintLine(f, \"\", 2);\n\tConfig_PrintBorder(f);\n\tfprintf(f, \"\\n\\n\");\n}\n\nstatic void Config_PrintPreamble(FILE *f)\n{\n\textern cvar_t name;\n\tchar\t*newlines = \"\\n\";\n\n\tConfig_PrintBorder(f);\n\tConfig_PrintBorder(f);\n\tConfig_PrintLine(f, \"\", 3);\n\tConfig_PrintLine(f, \"\", 3);\n\tConfig_PrintLine(f, \"E Z Q U A K E   C O N F I G U R A T I O N\", 3);\n\tConfig_PrintLine(f, \"\", 3);\n\tConfig_PrintLine(f, \"\", 3);\n\tConfig_PrintBorder(f);\n\tConfig_PrintBorder(f);\n\tfprintf(f,\"\\n// %s's config\\n\\n\", name.string);\n\tfprintf(f,\"// ezQuake %s \" __DATE__ \", \" __TIME__\"\\n\", VersionString());\n\n\tif (cfg_save_cmdline.value) {\n\t\tDumpCmdLine(f);\n\t\tfprintf(f, \"%s\", newlines);\n\t}\n}\n\n/************************************ MAIN FUCTIONS\t************************************/\n\n// Executes default.cfg as long as it isn't the one from pak0.pak or nQuake's default (which is executed from autoexec.cfg ...)\nvoid Cfg_ExecuteDefaultConfig(void)\n{\n\tflocation_t loc;\n\n\tif (FS_FLocateFile(\"default.cfg\", FSLFRT_IFFOUND, &loc)) {\n\t\tchar pak0default[MAX_OSPATH];\n\t\tchar nquakedefault[MAX_OSPATH];\n\n\t\tstrlcpy(pak0default, com_basedir, sizeof(pak0default));\n\t\tstrlcat(pak0default, \"/id1/pak0.pak/default.cfg\", sizeof(pak0default));\n\n\t\tstrlcpy(nquakedefault, com_basedir, sizeof(nquakedefault));\n\t\tstrlcat(nquakedefault, \"/qw/nquake.pk3\", sizeof(nquakedefault));\n\n\t\tif (strcasecmp(pak0default, loc.rawname) && strcasecmp(nquakedefault, loc.rawname)) {\n\t\t\tCbuf_AddText(\"exec default.cfg\\n\");\n\t\t}\n\t}\n}\n\nstatic void ResetConfigs(qbool explicit_reset, qbool read_legacy_configs)\n{\n\tvfsfile_t *v;\n\n\tResetVariables(CVAR_SERVERINFO, !explicit_reset);\n\tDeleteUserAliases();\n\tDeleteUserVariables();\n\tResetBinds();\n\tResetPlusCommands();\n\tResetTeamplayCommands();\n\tResetMiscCommands();\n\n\tif (read_legacy_configs) {\n\t\tCbuf_AddText(\"cl_warncmd 0\\n\");\n\t\tCfg_ExecuteDefaultConfig();\n\t\tCbuf_Execute();\n\t\tif ((v = FS_OpenVFS(\"autoexec.cfg\", \"rb\", FS_ANY))) {\n\t\t\tCbuf_AddText(\"exec autoexec.cfg\\n\");\n\t\t\tVFS_CLOSE(v);\n\t\t}\n\t\tCbuf_AddText(\"cl_warncmd 1\\n\");\n\t}\n}\n\nvoid Cfg_GetConfigPath(char* path, size_t max_length, const char* name)\n{\n\tconst char* default_gamedir = (cfg_use_home.integer ? \"\" : \"ezquake\");\n\tconst char* base_directory = (cfg_use_home.integer ? com_homedir : com_basedir);\n\n\tstrlcpy(path, base_directory, max_length);\n\tif (path[0]) {\n\t\tstrlcat(path, \"/\", max_length);\n\t}\n\tstrlcat(path, (strcmp(com_gamedirfile, \"qw\") == 0 || !cfg_use_gamedir.integer) ? default_gamedir : com_gamedirfile, max_length);\n\tif (path[0]) {\n\t\tstrlcat(path, \"/\", max_length);\n\t}\n\tif (!cfg_use_home.integer) {\n\t\tstrlcat(path, \"configs/\", max_length);\n\t}\n\tstrlcat(path, name, max_length);\n}\n\nvoid DumpConfig(char *name)\n{\n\tchar outfile[MAX_OSPATH];\n\tFILE* f;\n\tchar* newlines = \"\\n\";\n\n\tCfg_GetConfigPath(outfile, sizeof(outfile) / sizeof(outfile[0]), name);\n\n\tif (!(f\t= fopen(outfile, \"w\"))) {\n\t\tFS_CreatePath(outfile);\n\t\tif (!(f\t= fopen\t(outfile, \"w\"))) {\n\t\t\tCom_Printf (\"Couldn't write\t%s.\\n\",\tname);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tCom_Printf(\"Saving configuration to %s\\n\", outfile);\n\n\tConfig_PrintPreamble(f);\n\n\tif (cfg_save_cvars.value) {\n\t\tConfig_PrintHeading(f, \"V A R I A B L E S\");\n\t\tDumpVariables(f);\n\t\tfprintf(f, \"%s\", newlines);\n\t}\n\n\tif (cfg_save_cmds.value) {\n\t\tConfig_PrintHeading(f, \"S E L E C T E D   S O U R C E S\");\n\t\tWriteSourcesConfiguration(f);\n\t\tfprintf(f, \"%s\", newlines);\n\t}\n\n\tif (cfg_save_aliases.value) {\n\t\tConfig_PrintHeading(f, \"A L I A S E S\");\n\t\tDumpAliases(f);\n\t\tfprintf(f, \"%s\", newlines);\n\t}\n\n\tif (cfg_save_cmds.value) {\n\t\tConfig_PrintHeading(f, \"Q W 2 6 2   H U D\");\n\t\tDumpHUD262(f);\n\t\tfprintf(f, \"%s\", newlines);\n\n\t\tConfig_PrintHeading(f, \"T E A M P L A Y   C O M M A N D S\");\n\t\tDumpTeamplay(f);\n\t\tfprintf(f, \"%s\", newlines);\n\n\t\tConfig_PrintHeading(f, \"M I S C E L L A N E O U S   C O M M A N D S\");\n\t\tDumpMisc(f);\n\t\tfprintf(f, \"%s\", newlines);\n\n\t\tConfig_PrintHeading(f, \"P L U S   C O M M A N D S\");\n\t\tDumpPlusCommands(f);\n\t\tfprintf(f, \"%s\", newlines);\n\t}\n\n\tif (cfg_save_binds.value) {\n\t\tConfig_PrintHeading(f, \"K E Y   B I N D I N G S\");\n\t\tDumpBindings(f);\n\t}\n\n\tfclose(f);\n}\n\nvoid DumpHUD(const char *name)\n{\n\t// Dumps all variables from CFG_GROUP_HUD into a file\n\textern cvar_t scr_newHud;\n\n\tFILE *f;\n\tint max_width = 0, i = 0, j;\n\tchar *outfile, *spaces;\n\tcvar_t *var;\n\tcvar_t *sorted[MAX_DUMPED_CVARS];\n\n\toutfile = va(\"%s/ezquake/configs/%s\", com_basedir, name);\n\tif (!(f\t= fopen\t(outfile, \"w\"))) {\n\t\tFS_CreatePath(outfile);\n\t\tif (!(f\t= fopen\t(outfile, \"w\"))) {\n\t\t\tCom_Printf (\"Couldn't write\t%s.\\n\",\tname);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tfprintf(f, \"//\\n\");\n\tfprintf(f, \"// Head Up Display Configuration Dump\\n\");\n\tfprintf(f, \"//\\n\\n\");\n\n\tfor(var = cvar_vars; var; var = var->next)\n\t\tif(var->group && !strcmp(var->group->name, CVAR_GROUP_HUD)) {\n\t\t\tmax_width = max(max_width, strlen(var->name));\n\t\t\tsorted[i++] = var;\n\t\t}\n\n\tmax_width++;\n\tqsort(sorted, i, sizeof(cvar_t *), Cvar_CvarCompare);\n\n\tspaces = CreateSpaces(max_width - strlen(scr_newHud.name));\n\tfprintf(f, \"%s%s\\\"%d\\\"\\n\", scr_newHud.name, spaces, scr_newHud.integer);\n\n\tfor(j = 0; j < i; j++) {\n\t\tspaces = CreateSpaces(max_width - strlen(sorted[j]->name));\n\t\tfprintf(f, \"%s%s\\\"%s\\\"\\n\", sorted[j]->name, spaces, sorted[j]->string);\n\t}\n\n\tfprintf(f, \"hud_recalculate\\n\");\n\n\tfclose(f);\n}\n/************************************ API ************************************/\n\nextern qbool filesystemchanged; // fix bug 2359900\n\nstatic void SaveConfig(const char *cfgname)\n{\n\tchar filename[MAX_PATH] = {0};\n\tFILE *f;\n\n\tif (cfgname[0] && FS_UnsafeFilename(cfgname)) {\n\t\tCon_Printf(\"Invalid/unsafe filename, config not saved.\\n\");\n\t\treturn;\n\t}\n\n\tsnprintf(filename, sizeof(filename) - 4, \"%s\", cfgname[0] ? cfgname : MAIN_CONFIG_FILENAME); // use config.cfg if no params was specified\n\tCOM_ForceExtensionEx (filename, \".cfg\", sizeof (filename));\n\n\tif (cfg_backup.integer) {\n\t\tchar backupname_ext[MAX_PATH];\n\t\tchar filename_ext[MAX_OSPATH];\n\n\t\tCfg_GetConfigPath(filename_ext, sizeof(filename_ext) / sizeof(filename_ext[0]), filename);\n\n\t\tif ((f = fopen(filename_ext, \"r\"))) {\n\t\t\tfclose(f);\n\t\t\tstrlcpy(backupname_ext, filename_ext, sizeof(backupname_ext));\n\t\t\tstrlcat(backupname_ext, \".bak\", sizeof(backupname_ext));\n\n\t\t\tif ((f = fopen(backupname_ext, \"r\"))) {\n\t\t\t\tfclose(f);\n\t\t\t\tif (remove(backupname_ext)) {\n\t\t\t\t\tCon_Printf(\"Failed to delete old backup file %s - save cancelled\\n\", backupname_ext);\n\t\t\t\t\tCon_Printf(\"  If you want to save config without a backup, set %s 0\\n\", cfg_backup.name);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (rename(filename_ext, backupname_ext)) {\n\t\t\t\tCon_Printf(\"Failed to backup config file %s - save cancelled\\n\", filename_ext);\n\t\t\t\tCon_Printf(\"  If you want to save config without a backup, set %s 0\\n\", cfg_backup.name);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tDumpConfig(filename);\n\tfilesystemchanged = true; // fix bug 2359900\n}\n\nvoid SaveConfig_f(void)\n{\n\tSaveConfig(Cmd_Argv(1));\n}\n\nvoid Config_QuitSave(void)\n{\n\tif (cfg_save_onquit.integer) {\n\t\tSaveConfig(\"\");\n\t}\n}\n\nvoid ResetConfigs_f(void)\n{\n\tint argc = Cmd_Argc();\n\tqbool read_legacy_configs = argc == 2 && !strcmp(Cmd_Argv(1), \"full\");\n\n\tif (argc != 1 && !read_legacy_configs) {\n\t\tCom_Printf(\"Usage: %s [full]\\n\",\tCmd_Argv(0));\n\t\treturn;\n\t}\n\tCom_Printf(\"Resetting configuration to default state...\\n\");\n\n\tResetConfigs(true, read_legacy_configs);\n}\n\n// well exec /home/qqshka/ezquake/config.cfg does't work, security or something, so adding this\n// so this is some replacement for exec\nqbool LoadCfg(FILE *f)\n{\n\tchar *fileBuffer;\n\tchar reset_bindphysical[128];\n    int size;\n\n\tif (!f) {\n\t\treturn false;\n\t}\n\n\tsize = FS_FileLength(f);\n\tfileBuffer = Q_malloc(size + 1); // +1 for null terminator\n\tif (fread(fileBuffer, 1, size, f) != size) {\n\t\tCom_Printf(\"Error reading config file\\n\");\n\t\tQ_free(fileBuffer);\n\t\treturn false;\n\t}\n\tfileBuffer[size] = 0;\n\n\tsnprintf(reset_bindphysical, sizeof(reset_bindphysical), \"\\ncon_bindphysical %d\\n\", con_bindphysical.integer);\n\tCbuf_AddText(\"con_bindphysical 1\\n\");\n\tCbuf_AddText(fileBuffer);\n\tCbuf_AddText(reset_bindphysical);\n\tQ_free(fileBuffer);\n\treturn true;\n}\n\n/*\n\texample how it works\n\t\n\t=== ./ezquake -game testmod -config testcfg\n\thomedir/testmod/testcfg.cfg (fullname)\n\thomedir/testcfg.cfg (fullname_moddefault)\n\tquakedir/testmod/configs/testcfg.cfg\n\tquakedir/ezquake/configs/testcfg.cfg\n\tbuilt-in ezquake config\n*/\nvoid LoadConfig_f(void)\n{\n\tFILE\t*f = NULL;\n\tchar\tfilename[MAX_PATH] = {0},\n\t\t\tfullname[MAX_PATH] = {0},\n\t\t\tfullname_moddefault[MAX_PATH] = {0},\n\t\t\t*arg1;\n\tint\t\tuse_home;\n\n\targ1 = COM_SkipPathWritable(Cmd_Argv(1));\n\n\tsnprintf(filename, sizeof(filename) - 4, \"%s\", arg1[0] ? arg1 : MAIN_CONFIG_FILENAME); // use config.cfg if no params was specified\n\n\tCOM_ForceExtensionEx (filename, \".cfg\", sizeof (filename));\n\tuse_home = cfg_use_home.integer || !host_everything_loaded;\n\n\t// home\n\tsnprintf(fullname, sizeof(fullname), \"%s/%s%s\", com_homedir, (strcmp(com_gamedirfile, \"qw\") == 0) ? \"\" : va(\"%s/\", com_gamedirfile), filename);\n\tsnprintf(fullname_moddefault, sizeof(fullname_moddefault), \"%s/%s\", com_homedir, filename);\n\n\tif (use_home) {\n\t\tif (cfg_use_gamedir.integer) {\n\t\t\tf = fopen(fullname, \"rb\");\n\t\t}\n\t\tif (f == NULL) {\n\t\t\tf = fopen(fullname_moddefault, \"rb\");\n\t\t}\n\t\tif (f == NULL) {\n\t\t\tuse_home = false;\n\t\t}\n\t}\n\n\t// basedir\n\tsnprintf(fullname, sizeof(fullname), \"%s/%s/configs/%s\", com_basedir, (strcmp(com_gamedirfile, \"qw\") == 0) ? \"ezquake\" : com_gamedirfile, filename);\n\tsnprintf(fullname_moddefault, sizeof(fullname_moddefault), \"%s/ezquake/configs/%s\", com_basedir, filename);\n\n\tif(!use_home) {\n\t\tif (cfg_use_gamedir.integer) {\n\t\t\tf = fopen(fullname, \"rb\");\n\t\t}\n\t\tif (f == NULL) {\n\t\t\tf = fopen(fullname_moddefault, \"rb\");\n\t\t}\n\t}\n\n\tif (f == NULL) {\n\t\tCom_Printf(\"Couldn't load %s %s\\n\", filename, (cfg_use_gamedir.integer) ? \"(using gamedir search)\" : \"(not using gamedir search)\");\n\t\treturn;\n\t}\n\n\tcon_suppress = true;\n\tResetConfigs(false, true);\n\tcon_suppress = false;\n\n\tif(use_home)\n\t\tCom_Printf(\"Loading %s%s (Using Home Directory) ...\\n\", (strcmp(com_gamedirfile, \"qw\") == 0) ? \"\" : va(\"%s/\",com_gamedirfile), filename);\n\telse\n\t\tCom_Printf(\"Loading %s/configs/%s ...\\n\", (strcmp(com_gamedirfile, \"qw\") == 0) ? \"ezquake\" : com_gamedirfile, filename);\n\t\n\tCbuf_AddText (\"cl_warncmd 0\\n\");\n\n\tLoadCfg(f);\n\tfclose(f);\n\n\t/* johnnycz:\n\t  This should be called with TP_ExecTrigger(\"f_cfgload\"); but definition\n\t  of f_cfgload alias is stored in config which is waiting to be executed\n\t  in command queue so nothing would happen. We have to add f_cfgload as\n\t  a standard command to the queue. Since warnings are off this is OK but\n\t  regarding to other f_triggers non-standard.\n\t*/\n\tCbuf_AddText (\"f_cfgload\\n\");\n\n\tCbuf_AddText (\"cl_warncmd 1\\n\");\n}\n\nvoid DumpHUD_f(void)\n{\n\tchar *filename;\n\tchar buf[MAX_PATH];\n\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"Usage: %s <filename>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\tfilename = COM_SkipPathWritable(Cmd_Argv(1));\n\tstrlcpy(buf, filename, sizeof(buf));\n\tCOM_ForceExtensionEx (buf, \".cfg\", sizeof(buf));\n\tDumpHUD(buf);\n\tCom_Printf(\"HUD variables exported to %s\\n\",buf);\n}\n\nvoid Config_TroubleShoot_Tip(const char* problem, const char* description,\n\t\t\t\t\t\t\t const char* solution, int priority)\n{\n\tcon_ormask = 128;\n\tswitch (priority) {\n\tcase 0:\n\t\tCom_Printf(\"Minor issue: \");\n\t\tbreak;\n\tcase 1: default:\n\t\tCom_Printf(\"Major issue: \");\n\t\tbreak;\n\t}\n\tcon_ormask = 0;\n\n\tCom_Printf(\"%s\\n\", problem);\n\n\tcon_margin = 2;\n\tCom_Printf(\"%s\\n\\n\", description);\n\tcon_margin = 0;\n\n\tcon_ormask = 128;\n\tCom_Printf(\"Solution: \");\n\tcon_ormask = 0;\n\n\tcon_margin = 2;\n\tCom_Printf(\"%s\\n\\n\", solution);\n\tcon_margin = 0;\n}\n\nvoid Config_TroubleShoot_f(void)\n{\n\tunsigned problems = 0;\n\textern cvar_t r_novis, r_swapInterval;\n\textern cvar_t m_filter, sys_yieldcpu, cl_maxfps, hud_planmode;\n\textern cvar_t in_raw;\n\n\tif (r_novis.value) {\n\t\tConfig_TroubleShoot_Tip(\"r_novis is enabled\",\n\t\t\t\"r_novis causes a major performance hit, it's only useful if you need transparent liquids \"\n\t\t\t\"on non-vised maps\",\n\t\t\t\"set r_novis to 0\", 1);\n\t\tproblems++;\n\t}\n\n\tif (m_filter.value) {\n\t\tConfig_TroubleShoot_Tip(\"m_filter is enabled\",\n\t\t\t\"m_filter causes a serious delay in processing of the mouse input data\",\n\t\t\t\"set m_filter to 0\", 1);\n\t\tproblems++;\n\t}\n\n\tif (cl_maxfps.integer == 0 && sys_yieldcpu.value == 0 && r_swapInterval.value == 0) {\n\t\tConfig_TroubleShoot_Tip(\"cl_maxfps, sys_yieldcpu, and vid_vsync are all 0\",\n\t\t\t\"unlimited FPS with CPU yielding disabled typically leads to interruptions \"\n\t\t\t\"in reading input devices (keyboard, mouse)\",\n\t\t\t\"either set sys_yieldcpu 1, vid_vsync 1, or or limit your FPS with cl_maxfps\", 1);\n\t\tproblems++;\n\t}\n\n\tif (cl_maxfps.integer == 0 && sys_yieldcpu.value == 0) {\n\t\tConfig_TroubleShoot_Tip(\"cl_maxfps and sys_yieldcpu are 0\",\n\t\t\t\"unlimited FPS with CPU yielding disabled typically leads to interruptions \"\n\t\t\t\"in reading input devices (keyboard, mouse)\",\n\t\t\t\"either set sys_yieldcpu 1 or or limit your FPS with cl_maxfps\", 1);\n\t\tproblems++;\n\t}\n\n\tif (in_raw.integer != 1) {\n\t\tConfig_TroubleShoot_Tip(\"in_raw is not set to 1\",\n\t\t\t\"It is the recommended setting for mouse input\",\n\t\t\t\"set in_raw to 1\", 0);\n\t\tproblems++;\n\t}\n\n\tif (hud_planmode.value) {\n\t\tConfig_TroubleShoot_Tip(\"hud_planmode is enabled\",\n\t\t\t\"hud_planmode turns on the display of all possible head up display elements \"\n\t\t\t\"so that you can fine-tune your head up display settings\",\n\t\t\t\"set hud_planmode to 0\", 1);\n\t}\n\n\tif (!problems) {\n\t\tCom_Printf(\"No problems detected. For more help, please visit the forum in %s\\n\\n\", CharsToBrownStatic(\"www.QuakeWorld.nu\"));\n\t}\n}\n\nvoid ConfigManager_Init(void)\n{\n\tCmd_AddCommand(\"cfg_save\", SaveConfig_f);\n\tCmd_AddCommand(\"cfg_load\", LoadConfig_f);\n\tCmd_AddCommand(\"cfg_reset\",\tResetConfigs_f);\n\tCmd_AddCommand(\"hud_export\", DumpHUD_f);\n\tif (IsDeveloperMode()) {\n\t\tCmd_AddCommand(\"dev_dump_defaults\", DumpVariablesDefaults_f);\n\t}\n\tCmd_AddCommand(\"troubleshoot\", Config_TroubleShoot_f);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_CONFIG);\n\tCvar_Register(&cfg_save_unchanged);\n\tCvar_Register(&cfg_save_userinfo);\n\tCvar_Register(&cfg_save_onquit);\n\tCvar_Register(&cfg_save_cvars);\n\tCvar_Register(&cfg_save_aliases);\n\tCvar_Register(&cfg_save_cmds);\n\tCvar_Register(&cfg_save_binds);\n\tCvar_Register(&cfg_save_cmdline);\n\tCvar_Register(&cfg_save_sysinfo);\n\tCvar_Register(&cfg_backup);\n\tCvar_Register(&cfg_legacy_exec);\n\tCvar_Register(&cfg_use_home);\n\tCvar_Register(&cfg_use_gamedir);\n\tCvar_ResetCurrentGroup();\n}\n"
  },
  {
    "path": "src/config_manager.h",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef _CONFIG_MANAGER_H_\n\n#define _CONFIG_MANAGER_H_\n\nvoid ConfigManager_Init (void);\nvoid Config_QuitSave(void);\nvoid ResetBinds(void);\nvoid Cfg_ExecuteDefaultConfig(void);\nvoid Cfg_GetConfigPath(char* path, size_t max_length, const char* name);\n\nextern cvar_t\tcfg_save_unchanged, cfg_legacy_exec;\n\n#define MAIN_CONFIG_FILENAME \"config.cfg\"\n\n#endif\n"
  },
  {
    "path": "src/console.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: console.c,v 1.63 2007-10-04 13:48:11 dkure Exp $\n*/\n// console.c\n\n#include \"quakedef.h\"\n#include <fcntl.h>\n#ifdef _WIN32\n#include <io.h>\n#else\n#include <stdio.h>\n#endif\n#include \"gl_model.h\"\n#if defined(_WIN32) || defined(__linux__) || defined(__FreeBSD__)\n#include \"tr_types.h\"\n#endif // _WIN32 || __linux__ || __FreeBSD__\n#include \"keys.h\"\n#include \"ignore.h\"\n#include \"logging.h\"\n#include \"utils.h\"\n#include \"localtime.h\"\n#include \"irc.h\"\n#include \"fonts.h\"\n#include \"rulesets.h\"\n#include \"menu.h\"\n\n#define     MINIMUM_CONBUFSIZE     (1 << 15)\n#define     DEFAULT_CONBUFSIZE     (1 << 16)\n#define     MAXIMUM_CONBUFSIZE     (1 << 22)\n#define     MAX_NOTIFICATION_TIME  10\n\nconsole_t\tcon;\nint         con_margin=0;       // kazik: margin on the left side\n\nqbool    con_addtimestamp;\n\nint\t\t\tcon_ormask;\nint \t\tcon_linewidth;\t\t// characters across screen\nint\t\t\tcon_totallines;\t\t// total lines in console scrollback\nfloat\t\tcon_cursorspeed = 4;\n\ncvar_t\t\tcon_notify = {\"con_notify\", \"1\"};\ncvar_t\t\t_con_notifylines = {\"con_notifylines\",\"4\"};\ncvar_t\t\tcon_notifytime = {\"con_notifytime\",\"3\"};\t\t//seconds\ncvar_t\t\tcon_wordwrap = {\"con_wordwrap\",\"1\"};\ncvar_t\t\tcon_clearnotify = {\"con_clearnotify\",\"1\"};\n\ncvar_t\t\tcon_highlight  \t\t= {\"con_highlight\",\"0\"};\ncvar_t\t\tcon_highlight_mark \t= {\"con_highlight_mark\",\"\"};\n\ncvar_t      con_sound_mm1_file      = {\"s_mm1_file\",      \"misc/talk.wav\"};\ncvar_t      con_sound_mm2_file      = {\"s_mm2_file\",      \"misc/talk.wav\"};\ncvar_t      con_sound_spec_file     = {\"s_spec_file\",     \"misc/talk.wav\"};\ncvar_t      con_sound_other_file    = {\"s_otherchat_file\",    \"misc/talk.wav\"};\ncvar_t      con_sound_mm1_volume    = {\"s_mm1_volume\",    \"1\"};\ncvar_t      con_sound_mm2_volume    = {\"s_mm2_volume\",    \"1\"};\ncvar_t      con_sound_spec_volume   = {\"s_spec_volume\",   \"1\"};\ncvar_t      con_sound_other_volume  = {\"s_otherchat_volume\",  \"1\"};\n\ncvar_t      con_timestamps          = {\"con_timestamps\", \"0\"};\ncvar_t      con_shift               = {\"con_shift\", \"-10\"};\ncvar_t      cl_textEncoding         = {\"cl_textencoding\", \"0\"};\ncvar_t      con_mm2_only            = {\"con_mm2_only\", \"0\"};\ncvar_t      con_proportional        = {\"con_proportional\", \"0\"};\ncvar_t      con_margin_left         = {\"con_margin_left\", \"0\"};\n\n#ifdef _WIN32\ncvar_t      con_toggle_deadkey      = {\"con_deadkey\", \"1\"};\n#endif\n\n#define\tNUM_CON_TIMES 16\nfloat\t\tcon_times[NUM_CON_TIMES];\t// cls.realtime time the line was generated\n\t\t\t\t\t\t\t\t\t\t// for transparent notify lines\n\nint\t\t\tcon_vislines;\nint\t\t\tcon_notifylines;\t\t\t// scan lines to clear for notify lines\n\nqbool\tcon_initialized = false;\nqbool\tcon_suppress = false;\n\nFILE\t\t*qconsole_log = NULL;\n\n\nchar *months[12] = {\n    \"jan\",\n    \"feb\",\n    \"mar\",\n    \"apr\",\n    \"may\",\n    \"jun\",\n    \"jul\",\n    \"aug\",\n    \"sep\",\n    \"oct\",\n    \"nov\",\n    \"dec\" };\n\nchar *days[7] = {\n    \"Sun\",\n    \"Mon\",\n    \"Tue\",\n    \"Wed\",\n    \"Thu\",\n    \"Fri\",\n    \"Sat\"}; \n\nvoid Date_f (void)\n{\n    // Sun  Nov 05 2000, 16:39\n    SYSTEMTIME tm;\n    char *day, *month;\n    GetLocalTime(&tm);\n    month = months[tm.wMonth-1];\n    day = days[tm.wDayOfWeek];\n\n    con_ormask = 128;\n    Com_Printf (\"%s %s %02d, %2d:%02d %d\\n\", day, month, tm.wDay, tm.wHour, tm.wMinute, tm.wYear);\n    con_ormask = 0;\n}\n\nstatic qbool Con_MenuWillDrawConsole(void)\n{\n\tif (m_state == m_none || key_dest != key_menu || m_state == m_proxy) {\n\t\treturn false;\n\t}\n\treturn (SCR_NEED_CONSOLE_BACKGROUND);\n}\n\nvoid MakeStringRed(char *s)\n{\n    while (*s)\n    {\n        if (*s >= '0'  &&  *s <= '9')\n            *s |= 128;\n        s++;\n    }\n}\n\nvoid MakeStringYellow(char *s)\n{\n    while (*s)\n    {\n        if (*s >= '0'  &&  *s <= '9')\n            *s -= 30;\n        s++;\n    }\n}\n\n/*\n==================\nCalendar_f - prints calendar\n==================\n*/\nvoid Calendar_f(void)\n{\n    int alldays, day, column;\n    SYSTEMTIME tm;\n    GetLocalTime(&tm);\n\n    Date_f();\n    Com_Printf (\" mo tu we th fr sa su\\n\");\n\n    alldays = 31;\n    day = tm.wDay;\n    day -= (tm.wDayOfWeek==0 ? 7 : tm.wDayOfWeek) - 1;\n    day = (day % 7);\n    if (day > 1)\n        day -= 7;\n    column = 0;\n\n    switch (tm.wMonth)\n    {\n    case  1:    alldays = 31; break;\n\tcase  2:    alldays = (tm.wYear % 400 ? 28 : (tm.wYear % 100 ? 29 : (tm.wYear % 4) ? 28 : 29)); break;\n    case  3:    alldays = 31; break;\n    case  4:    alldays = 30; break;\n    case  5:    alldays = 31; break;\n    case  6:    alldays = 30; break;\n    case  7:    alldays = 31; break;\n    case  8:    alldays = 31; break;\n    case  9:    alldays = 30; break;\n    case 10:    alldays = 31; break;\n    case 11:    alldays = 30; break;\n    case 12:    alldays = 31; break;\n    default:    alldays = 31; break;\n    }\n\n    while (day <= alldays)\n    {\n        column ++;\n\n        if (day > 0)\n        {\n            char buf[8];\n            snprintf(buf, sizeof (buf), \"%3d\", day);\n            if (day == tm.wDay)\n                ; // MakeStringYellow(buf);\n            else\n            {\n                if (column == 7)\n                    MakeStringYellow(buf);\n                else\n                    MakeStringRed(buf);\n            }\n            Com_Printf (\"%s\", buf);\n        }\n        else\n            Com_Printf (\"   \");\n\n\n        if (day == alldays  ||  column == 7)\n        {\n            column = 0;\n            Com_Printf (\"\\n\");\n        }\n\n        day++;\n    }\n}\n\nvoid Con_ToggleConsole_f (void) {\n\tKey_ClearTyping ();\n\n\tif (key_dest == key_console) {\n\t\tif (!SCR_NEED_CONSOLE_BACKGROUND)\n\t\t\tkey_dest = key_dest_beforecon;\n\t} else {\n\t\tkey_dest_beforecon = key_dest;\n\t\tkey_dest = key_console;\n\n#ifdef _WIN32\n\t\tif (con_toggle_deadkey.integer) {\n\t\t\tSys_CancelDeadKey ();\n\t\t}\n#endif\n\t}\n\n\tif (con_clearnotify.value)\n\t\tCon_ClearNotify ();\n}\n\nvoid Con_SetColor(int idx_from, int count, int c) {\n\tint i;\n\n\tif (idx_from < 0 || idx_from >= con.maxsize || count < 0 || idx_from + count > con.maxsize)\n\t\tSys_Error(\"Con_SetColor: wrong idx\");;\n\n\tfor (i = idx_from; i < count; i++) {\n\t\tmemset(&con.clr[i], 0, sizeof(clrinfo_t)); // zeroing whole struct\n\t\tcon.clr[i].c = c;\n\t\tcon.clr[i].i = i;\n\t}\n}\n\nvoid Con_SetWhite (void) {\n// no need for memset(), Con_SetColor() do it too\n//\tmemset (con.clr, 0, con.maxsize * sizeof(clrinfo_t)); // set whole struct array to zero\n\n\tCon_SetColor(0, con.maxsize, COLOR_WHITE); // set white color\n}\n\nvoid Con_Clear_f (void) {\n\tint\ti;\n\n\tcon.numlines = 0;\n\tfor (i = 0; i < con.maxsize; i++)\n\t\tcon.text[i] = ' ';\n\tcon.display = con.current;\n\tCon_SetWhite(); // set default color to white\n}\n\nvoid Con_ClearNotify (void) {\n\tint i;\n\n\tfor (i = 0; i < NUM_CON_TIMES; i++)\n\t\tcon_times[i] = 0;\n}\n\nvoid Con_MessageMode_Common (chat_type t) {\n\tif (cls.state != ca_active)\n\t\treturn;\n\n\tchat_team = t;\n\tkey_dest_beforemm = key_dest; // where to return after we finish typing\n\tkey_dest = key_message;\n\tchat_buffer[0] = 0;\n\tchat_linepos = 0;\n}\n\nvoid Con_MessageMode_f (void) {\n\tCon_MessageMode_Common(chat_mm1);\n}\n\nvoid Con_MessageMode2_f (void) {\n\tif (cls.mvdplayback == QTV_PLAYBACK)\n\t{\n\t\t// mm2 has no meaning in QTV so let's save some keys and make it QTV->game chat\n\t\tCon_MessageMode_Common(chat_qtvtogame);\n\t}\n\telse\n\t{\n\t\tCon_MessageMode_Common(chat_mm2);\n\t}\n}\n\n#ifdef WITH_IRC\nvoid Con_MessageModeIRC_f (void) {\n\tCon_MessageMode_Common(chat_irc);\n}\n#endif\n\nvoid Con_MessageModeQTVtoGAME_f (void) {\n\tCon_MessageMode_Common(chat_qtvtogame);\n}\n\n//If the line width has changed, reformat the buffer\nvoid Con_CheckResize (void) {\n\tint i, j, width, oldwidth, oldtotallines, numlines, numchars;\n\twchar *tempbuf;\n\n\twidth = (vid.width >> 3) - 2;\n\n\tif (width == con_linewidth)\n\t\treturn;\n\n\tif (width < 1) { // video hasn't been initialized yet\n#if defined(_WIN32) || defined(__linux__) || defined(__FreeBSD__)\n\t\tcvar_t *cv = Cvar_Find(r_conwidth.name); // r_conwidth not yet registered, but let user specifie it via\n\t\t\t\t\t\t\t\t\t\t\t\t\t // config.cfg or somehow else\n\t\tif ( cv ) {\n\t\t\twidth = max(320, cv->integer);\n\t\t\twidth &= 0xfff8; // make it a multiple of eight\n\t\t\twidth = (width >> 3) - 2;\n\t\t\twidth = max(width, 38);\n\t\t}\n\t\telse\n#endif\n\t\t\twidth = 38;\n\n\t\tcon_linewidth = width;\n\t\tcon_totallines = con.maxsize / con_linewidth;\n\t\tfor (i = 0; i < con.maxsize; i++)\n\t\t\tcon.text[i] = ' ';\n\t\tCon_SetWhite();\n\t} else {\n\t\tint idx_old, idx_new;\n\t\tclrinfo_t *clr;\n\n\t\toldwidth = con_linewidth;\n\t\tcon_linewidth = width;\n\t\toldtotallines = con_totallines;\n\t\tcon_totallines = con.maxsize / con_linewidth;\n\t\tnumlines = oldtotallines;\n\n\t\tif (con_totallines < numlines)\n\t\t\tnumlines = con_totallines;\n\n\t\tnumchars = oldwidth;\n\n\t\tif (con_linewidth < numchars)\n\t\t\tnumchars = con_linewidth;\n\n\t\ttempbuf = (wchar *) Hunk_TempAlloc(con.maxsize * sizeof(wchar));\n\t\tmemcpy (tempbuf, con.text, con.maxsize * sizeof(wchar));\n\t\tfor (i = 0; i < con.maxsize; i++)\n\t\t\tcon.text[i] = ' ';\n\n\t\tclr = Q_malloc(con.maxsize * sizeof(clrinfo_t)); // alloc temporaly\n\t\tmemcpy(clr, con.clr, con.maxsize * sizeof(clrinfo_t)); // save color array\n\t\tCon_SetWhite(); // wipe color array entirely\n\n\t\tfor (i = 0; i < numlines; i++) {\n\t\t\tfor (j = 0; j < numchars; j++) {\n\t\t\t\tidx_new = (con_totallines - 1 - i) * con_linewidth + j;\n\t\t\t\tidx_old = ((con.current - i + oldtotallines) % oldtotallines) * oldwidth + j;\n\t\t\t\tcon.text[idx_new] = tempbuf[idx_old];\n\n\t\t\t\tcon.clr[idx_new]   = clr[idx_old]; // restore color info\n\t\t\t\tcon.clr[idx_new].i = idx_new; // re-index\n\t\t\t}\n\t\t}\n\n\t\tCon_ClearNotify ();\n\t\tQ_free(clr); // free\n\t}\n\n\tcon.current = con_totallines - 1;\n\tcon.display = con.current;\n}\n\n\nchar readableChars[256] = {\t'.', '_' , '_' , '_' , '_' , '.' , '_' , '_' , '_' , '_' , '\\n' , '_' , '\\n' , '>' , '.' , '.',\n\t\t\t\t\t\t'[', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '_', '_', '_'};\n\n\nstatic void Con_CreateReadableChars(void) {\n\tint i;\n\n\tfor (i = 32; i < 127; i++)\n\t\treadableChars[i] = readableChars[128 + i] = i;\n\treadableChars[127] = readableChars[128 + 127] = '_';\n\n\tfor (i = 0; i < 32; i++)\n\t\treadableChars[128 + i] = readableChars[i];\n\treadableChars[128] = '_';\n\treadableChars[10 + 128] = '_';\n\treadableChars[12 + 128] = '_';\n}\n\n\nstatic void Con_InitConsoleBuffer(console_t *conbuffer, int size) {\n\tcon.maxsize = size;\n\tcon.text = (wchar *) Hunk_AllocName(con.maxsize * sizeof(wchar), \"console_buffer\");\n\tcon.clr = (clrinfo_t *) Hunk_AllocName(con.maxsize * sizeof(clrinfo_t), \"console_clr\");\n}\n\nvoid Con_Init (void) {\n\tint i, conbufsize;\n\n\t//Tei: moved there, because on windows can't capture the output for debug purposes\n\tif (!qconsole_log && COM_CheckParm(cmdline_param_console_debug)) {\n\t\tchar tmp_path[MAX_OSPATH] = {0};\n\n\t\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s/qw/qconsole.log\", com_basedir);\n\t\tqconsole_log = fopen(tmp_path, \"a\");\n\t}\n\n\tif ((i = COM_CheckParm(cmdline_param_console_buffersize)) && i + 1 < COM_Argc()) {\n\t\tconbufsize = Q_atoi(COM_Argv(i + 1)) << 10;\n\t\tconbufsize = bound (MINIMUM_CONBUFSIZE , conbufsize, MAXIMUM_CONBUFSIZE);\n\t} else {\n\t\tconbufsize = DEFAULT_CONBUFSIZE;\n\t}\n\tCon_InitConsoleBuffer(&con, conbufsize);\n\n\tcon_linewidth = -1;\n\tCon_CheckResize ();\n\n\tCon_CreateReadableChars();\n\n\tcon_initialized = true;\n\tCom_Printf_State (PRINT_OK, \"Console initialized\\n\");\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_CONSOLE);\n\t// register our commands and cvars\n\tCvar_Register (&_con_notifylines);\n\tCvar_Register (&con_notifytime);\n\tCvar_Register (&con_wordwrap);\n\tCvar_Register (&con_clearnotify);\n\tCvar_Register (&con_notify);\n#ifdef _WIN32\n\tCvar_Register (&con_toggle_deadkey);\n#endif\n\n\t// added by jogi start\n\tCvar_Register (&con_highlight);\n\tCvar_Register (&con_highlight_mark);\n\t// added by jogi stop\n\n\tCvar_Register (&con_timestamps); \n\tCvar_Register (&con_shift); \n\tCvar_Register (&con_mm2_only);\n\tCvar_Register (&con_proportional);\n\tCvar_Register (&con_margin_left);\n\n\tCvar_ResetCurrentGroup();\n\n    Cvar_SetCurrentGroup(CVAR_GROUP_SOUND);\n\n\tCvar_Register (&con_sound_mm1_file); \n\tCvar_Register (&con_sound_mm2_file); \n\tCvar_Register (&con_sound_spec_file); \n\tCvar_Register (&con_sound_other_file); \n\tCvar_Register (&con_sound_mm1_volume); \n\tCvar_Register (&con_sound_mm2_volume); \n\tCvar_Register (&con_sound_spec_volume); \n\tCvar_Register (&con_sound_other_volume); \n\n    Cvar_ResetCurrentGroup();\n\n\tCvar_Register(&cl_textEncoding);\n\n\tCmd_AddCommand (\"toggleconsole\", Con_ToggleConsole_f);\n\tCmd_AddCommand (\"messagemode\", Con_MessageMode_f);\n\tCmd_AddCommand (\"messagemode2\", Con_MessageMode2_f);\n#ifdef WITH_IRC\n\tCmd_AddCommand (\"messagemodeirc\", Con_MessageModeIRC_f);\n#endif\n\tCmd_AddCommand (\"messagemodeqtvtogame\", Con_MessageModeQTVtoGAME_f);\n\tCmd_AddCommand (\"clear\", Con_Clear_f);\n    Cmd_AddCommand (\"date\", Date_f);\n\tCmd_AddCommand (\"calendar\", Calendar_f);\n  \n}\n\nvoid Con_Shutdown (void) {\n\tif (qconsole_log)\n\t\tfclose(qconsole_log);\n}\n\nvoid Con_Linefeed (void) {\n\tint idx, i;\n\tcon.x = 0;\n\tcon.x = con_margin;    // kazik\n\tif (con.display == con.current)\n\t\tcon.display++;\n\tcon.current++;\n\tif (con.numlines < con_totallines)\n\t\tcon.numlines++;\n\tidx = (con.current%con_totallines)*con_linewidth;\n\tfor (i = 0; i < con_linewidth; i++)\n\t\tcon.text[idx + i] = ' ';\n\tCon_SetColor(idx, con_linewidth, COLOR_WHITE);\n\n\t// mark time for transparent overlay\n\tif (con.current >= 0) {\n\t\tcon_times[con.current % NUM_CON_TIMES] = (Print_flags[Print_current] & PR_NONOTIFY ? 0 : cls.realtime);\n\t}\n}\n\n/*\n==================\nCon_SafePrintf\n\nOkay to call even when the screen can't be updated\n\n==================\n*/\nvoid Con_SafePrintf (char *fmt, ...)\n{\n    va_list     argptr;\n    char        msg[1024];\n    int         temp;\n\n    va_start (argptr,fmt);\n    vsnprintf (msg,sizeof(msg),fmt,argptr);\n    va_end (argptr);\n\n    temp = scr_disabled_for_loading;\n    scr_disabled_for_loading = true;\n    Com_Printf (\"%s\", msg);\n    scr_disabled_for_loading = temp;\n}\n\n//Handles cursor positioning, line wrapping, etc\nvoid Con_PrintW(wchar *txt)\n{\n\tint y, c, l, d, mask, color = COLOR_WHITE, r, g, b, idx;\n\twchar *s;\n\tstatic int cr;\n\n\tif (!(Print_flags[Print_current] & PR_LOG_SKIP)) {\n\t\tif (Ruleset_CanLogConsole() && qconsole_log) {\n\t\t\tchar *tempbuf = Q_wcs2str_malloc(txt);\n\t\t\tfprintf(qconsole_log, \"%s\", tempbuf);\n\t\t\tfflush(qconsole_log);\n\t\t\tQ_free(tempbuf);\n\t\t}\n\t\tif (Log_IsLogging()) {\n\t\t\tif (log_readable.integer) {\n\t\t\t\tchar *s, *tempbuf = Q_wcs2str_malloc(txt);\n\t\t\t\tfor (s = tempbuf; *s; s++)\n\t\t\t\t\t*s = readableChars[(unsigned char)*s];\n\t\t\t\tLog_Write(tempbuf);\n\t\t\t\tQ_free(tempbuf);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tLog_Write(wcs2str(txt));\n\t\t\t}\n\t\t}\n\t}\n\n\tif ((Print_flags[Print_current] & PR_SKIP))\n\t\tgoto zomfg;\n\n\tif (!con_initialized || con_suppress)\n\t\tgoto zomfg;\n\n\tif (txt[0] == 1 || txt[0] == 2) {\n\t\tmask = 128;\t\t// go to colored text\n\t\ttxt++;\n\t}\n\telse {\n\t\tmask = 0;\n\t}\n\n\twhile ((c = *txt)) {\n\t\t// get color modificator if any\n\t\tif (*txt == '&') {\n\t\t\tif (txt[1] == 'c' && txt[2] && txt[3] && txt[4]) {\n\t\t\t\tr = HexToInt(txt[2]);\n\t\t\t\tg = HexToInt(txt[3]);\n\t\t\t\tb = HexToInt(txt[4]);\n\t\t\t\tif (r >= 0 && g >= 0 && b >= 0) {\n\t\t\t\t\tcolor = RGBA_TO_COLOR(255 * r / 16, 255 * g / 16, 255 * b / 16, 255);\n\t\t\t\t\ttxt += 5;\n\t\t\t\t\tcontinue; // we got color, get now normal char\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (txt[1] == 'r') {\n\t\t\t\tcolor = COLOR_WHITE;\n\t\t\t\ttxt += 2;\n\t\t\t\tcontinue; // we got color, get now normal char\n\t\t\t}\n\t\t}\n\n\t\t// count word length\n\t\tfor (s = txt, l = 0; s[0] && l < con_linewidth;) {\n\t\t\t// skip color, not count in word length\n\t\t\tif (*s == '&') {\n\t\t\t\tif (s[1] == 'c' && s[2] && s[3] && s[4]) {\n\t\t\t\t\tr = HexToInt(s[2]);\n\t\t\t\t\tg = HexToInt(s[3]);\n\t\t\t\t\tb = HexToInt(s[4]);\n\t\t\t\t\tif (r >= 0 && g >= 0 && b >= 0) {\n\t\t\t\t\t\ts += 5;\n\t\t\t\t\t\tcontinue; // we got color, get now normal char\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (s[1] == 'r') {\n\t\t\t\t\ts += 2;\n\t\t\t\t\tcontinue; // we got color, get now normal char\n\t\t\t\t}\n\t\t\t}\n\n\t\t\td = (s[0] & ~128);\n\t\t\tif ((con_wordwrap.value && (!d || d == 0x09 || d == 0x0D || d == 0x0A || d == 0x20))\n\t\t\t\t|| (!con_wordwrap.value && d <= 32) // 32 is a space as well as 0x20\n\t\t\t\t)\n\t\t\t\tbreak;\n\n\t\t\tl++; // increase word length\n\t\t\ts++; // get next char\n\t\t}\n\n\t\t// word wrap\n\t\tif (l != con_linewidth && con.x + l > con_linewidth)\n\t\t\tcon.x = 0;\n\n\t\ttxt++;\n\n\t\tif (cr) {\n\t\t\tcon.current--;\n\t\t\tcr = false;\n\t\t}\n\n\t\tif (!con.x)\n\t\t\tCon_Linefeed();\n\n\t\tswitch (c) {\n\t\t\tcase '\\n':\n\t\t\t\tcon.x = 0;\n\t\t\t\tbreak;\n\n\t\t\tcase '\\r':\n\t\t\t\tcon.x = 0;\n\t\t\t\tcr = 1;\n\t\t\t\tbreak;\n\n\t\t\tdefault:\t// display character and advance\n\t\t\t\tif (con.x >= con_linewidth) {\n\t\t\t\t\tCon_Linefeed();\n\t\t\t\t}\n\t\t\t\ty = con.current % con_totallines;\n\t\t\t\tidx = y * con_linewidth + con.x + con_margin_left.integer;\n\t\t\t\tcon.text[idx] = c | (c > 0 && c <= 0x7F ? (mask | con_ormask) : 0);\t// only apply mask if in 'standard' charset\n\t\t\t\tmemset(&con.clr[idx], 0, sizeof(clrinfo_t)); // zeroing whole struct\n\t\t\t\tcon.clr[idx].c = color;\n\t\t\t\tcon.clr[idx].i = idx; // no, that not stupid :P\n\t\t\t\tcon.x++;\n\t\t\t\tbreak;\n\t\t}\n\t}\nzomfg:\n\tif (!(Print_flags[Print_current] & PR_NORESET)) {\n\t\tPrint_flags[Print_current] = 0;\n\t}\n}\n\n/*\n==============================================================================\nDRAWING\n==============================================================================\n*/\n\n//The input line scrolls horizontally if typing goes beyond the right edge\nstatic void Con_DrawInput(void) {\n\tint\t\tlen, i;\n\twchar\t*text;\n\twchar\ttemp[MAXCMDLINE + 1];       //+ 1 for cursor if stlen(key_lines[edit_line]) == 255\n\n\tif (key_dest != key_console && cls.state == ca_active) {\n\t\treturn;\n\t}\n\n\tqwcslcpy (temp, key_lines[edit_line], MAXCMDLINE);\n\tlen = qwcslen(temp);\n\n\ttext = temp;\n\n\t// fill out remainder with spaces\n\tfor (i = 0; i < MAXCMDLINE - len; i++) {\n\t\t(text + len)[i] = ' ';\n\t}\n\t\n\ttext[MAXCMDLINE] = 0;\n\n\t// add the cursor frame\n\tif ((int)(curtime*con_cursorspeed) & 1) {\n\t\ttext[key_linepos] = con_redchars ? (11 + 128) : 11;\n\t}\n\n\t//\tprestep if horizontally scrolling\n\tif (key_linepos >= con_linewidth) {\n\t\ttext += 1 + key_linepos - con_linewidth;\n\t}\n\n\tDraw_ConsoleString(8, con_vislines - 22 + bound(0, con_shift.value, 8), text, NULL, 0, 0, 1, con_proportional.integer);\n}\n\n// Returns first line to start printing from in order to fill up notify area\nstatic int Con_FirstNotifyLine (int notification_lines, float notification_timelimit)\n{\n\tint maxlines = bound(0, notification_lines, NUM_CON_TIMES - 1);\n\tint first_line = con.current;\n\tfloat threshold_time = cls.realtime - notification_timelimit;\n\n\t// Work backwards to skip non-ignored lines\n\twhile (first_line >= 0 && first_line > (con.current - NUM_CON_TIMES + 1) && maxlines) {\n\t\tfloat line_time = con_times[first_line % NUM_CON_TIMES];\n\t\tif (line_time && line_time > threshold_time) {\n\t\t\t--maxlines;\n\t\t}\n\t\tif (maxlines)\n\t\t\t--first_line;\n\t}\n\n\treturn first_line;\n}\n\nstatic qbool Con_NotifyMessageLine(float posX, float posY, float width, float height, float scale, qbool proportional)\n{\n\tint x;\n\n\tif (key_dest == key_message) {\n\t\twchar temp[MAXCMDLINE + 1];\n\t\twchar* s;\n\t\tqbool display_cursor;\n\n\t\tclearnotify = 0;\n\t\tscr_copytop = 1;\n\n\t\tif (chat_team == chat_mm2) {\n\t\t\tposX += Draw_SString(posX, posY + bound(0, con_shift.value, 8), \"say_team:\", scale, proportional);\n\t\t}\n\t\telse if (chat_team == chat_mm1) {\n\t\t\tposX += Draw_SString(posX, posY + bound(0, con_shift.value, 8), \"say:\", scale, proportional);\n\t\t}\n#ifdef WITH_IRC\n\t\telse if (chat_team == chat_irc) {\n\t\t\tchar dest[256];\n\n\t\t\tstrlcpy(dest, IRC_GetCurrentChan(), sizeof(dest));\n\t\t\tstrlcat(dest, \":\", sizeof(dest));\n\n\t\t}\n#endif\n\t\telse if (chat_team == chat_qtvtogame) {\n\t\t\tposX += Draw_SString(posX, posY + bound(0, con_shift.value, 8), \"say_game:\", scale, proportional);\n\t\t}\n\n\t\t// FIXME: clean this up\n\t\tqwcslcpy(temp, chat_buffer, sizeof(temp) / sizeof(wchar));\n\t\ts = temp;\n\n\t\t// add the cursor frame\n\t\tdisplay_cursor = (int)(curtime * con_cursorspeed) & 1;\n\t\tif (display_cursor && chat_linepos == (int)qwcslen(s)) {\n\t\t\ts[chat_linepos + 1] = '\\0';\n\t\t}\n\n\t\t// prestep if horizontally scrolling\n\t\tif (!proportional) {\n\t\t\tif (chat_linepos * 8 * scale + posX >= width) {\n\t\t\t\ts += (int)((1 + chat_linepos)) + (int)((posX - width) / (8 * scale));\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t// measure full string, cut off characters until string fits\n\t\t\tfloat length = Draw_StringLengthW(s, chat_linepos, scale, proportional);\n\n\t\t\twhile (*s && length >= 0 && length >= width - posX) {\n\t\t\t\tlength -= FontCharacterWidthWide(*s, scale, proportional);\n\t\t\t\t++s;\n\t\t\t}\n\t\t}\n\n\t\tx = 0;\n\t\twhile (s[x] && posX < width) {\n\t\t\tfloat char_width = Draw_CharacterWSP(posX, posY + bound(0, con_shift.value, 8), s[x], scale, proportional);\n\n\t\t\tif (display_cursor && x == chat_linepos) {\n\t\t\t\tDraw_SCharacterP(posX, posY + bound(0, con_shift.value, 8), 11, scale, proportional);\n\t\t\t\tdisplay_cursor = false;\n\t\t\t}\n\t\t\tposX += char_width;\n\t\t\tx++;\n\t\t}\n\t\tif (display_cursor) {\n\t\t\tDraw_SCharacterP(posX, posY + bound(0, con_shift.value, 8), 11, scale, proportional);\n\t\t}\n\t\tposY += 8 * scale;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n//Draws the last few lines of output transparently over the game top\nvoid Con_DrawNotify(void)\n{\n\tif (con_notify.integer) {\n\t\tSCR_DrawNotify(8, bound(0, con_shift.value, 8), 1.0f, con_notifytime.value, _con_notifylines.integer, con_linewidth, con_proportional.integer);\n\t}\n}\n\n// Draws the last few lines of output as a custom HUD element.\nvoid SCR_DrawNotify(int posX, int posY, float scale, int notifyTime, int notifyLines, int notifyCols, qbool proportional)\n{\n\tint v, i, idx;\n\tfloat time;\n\tfloat timeout = bound (0, notifyTime, MAX_NOTIFICATION_TIME);\n\n\tnotifyCols = bound(10, notifyCols, con_linewidth);\n\n\tv = 0;\n\tif (notifyLines) {\n\t\tint first_line = Con_FirstNotifyLine(notifyLines, timeout);\n\n\t\tfor (i = first_line; i <= con.current; i++) {\n\t\t\tif (i < 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttime = con_times[i % NUM_CON_TIMES];\n\t\t\tif (time == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttime = cls.realtime - time;\n\t\t\tif (time > timeout) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tidx = (i % con_totallines) * con_linewidth;\n\n\t\t\tclearnotify = 0;\n\t\t\tscr_copytop = 1;\n\n\t\t\tint printed = 0;\n\t\t\tint last = idx + con_linewidth - 1;\n\t\t\twhile (last > idx && (con.text + last)[0] == 32) {\n\t\t\t\t--last;\n\t\t\t}\n\n\t\t\twhile (last > idx) {\n\t\t\t\tprinted = Draw_ConsoleString(posX, v + posY, con.text + idx, con.clr + idx, min(notifyCols, last - idx + 1), 0, scale, proportional);\n\t\t\t\tidx += printed;\n\t\t\t\tv += (8 * scale);\n\t\t\t\tif (printed == 0 || con.text[idx] == 0 || con.text[idx] == 0x80) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (Con_NotifyMessageLine(posX, v + posY, notifyCols * 8 * scale, v * 8 * scale, scale, proportional)) {\n\t\tv += 8 * scale;\n\t}\n\n\tif (v > con_notifylines) {\n\t\tcon_notifylines = v + bound(0, con_shift.value, 8);\n\t}\n}\n\n//Draws the console with the solid background\nvoid Con_DrawConsole (int lines) {\n\tint i, j, x, y, n=0, rows, row, idx, probing_progress;\n\tchar *text, dlbar[1024];\n\tqbool is_probing;\n\n\tif (lines <= 0 || Con_MenuWillDrawConsole())\n\t\treturn;\n\n\t// draw the background\n\tDraw_ConsoleBackground (lines);\n\n\t// draw the text\n\tcon_vislines = lines;\n\n\t// changed to line things up better\n\trows = (lines - 22) >> 3;\t\t// rows of text to draw\n\n\ty = lines - 30;\n\n\trow = con.display;\n\n\t// draw from the bottom up\n\tif (con.display != con.current) {\n\t// draw arrows to show the buffer is backscrolled\n\t\tfor (x = 0; x < con_linewidth; x += 4)\n\t\t\tDraw_Character ((x + 1) << 3, y + bound(0, con_shift.value, 8), '^');\n\n\t\ty -= 8;\n\t\trows--;\n\t\trow--;\n\t}\n\n\tfor (i = 0; i < rows; i++, y -= 8, row--) {\n\t\tif (row < 0)\n\t\t\tbreak;\n\t\tif (con.current - row >= con_totallines)\n\t\t\tbreak;\t\t// past scrollback wrap point\n\n\t\tidx = (row % con_totallines)*con_linewidth;\n\n\t\t// copy current line to buffer\n\t\tDraw_ConsoleString( 1 << 3, y + bound(0, con_shift.value, 8), con.text + idx, con.clr + idx, con_linewidth, 0, 1, con_proportional.integer);\n\t}\n\n\tis_probing = IsPortPingProbeEnabled() && NET_GetPortPingProbeStatus() == PORTPINGPROBE_PROBING;\n\tprobing_progress = IsPortPingProbeEnabled() && is_probing ? NET_GetPortPingProbeProgress() : 0;\n\n\t// draw the download bar\n\t// figure out width\n\tif (cls.download || cls.upload || is_probing) {\n\t\tint\tslider_box_position = -1;\n\n\t\tif (cls.download) {\n\t\t\tif ((text = strrchr(cls.downloadname, '/')) != NULL)\n\t\t\t\ttext++;\n\t\t\telse\n\t\t\t\ttext = cls.downloadname;\n\t\t} else if (cls.upload) {\n\t\t\tif ((text = strrchr(cls.uploadname, '/')) != NULL)\n\t\t\t\ttext++;\n\t\t\telse\n\t\t\t\ttext = cls.uploadname;\n\t\t} else if (is_probing) {\n\t\t\ttext = \"probing\";\n\t\t} else\n\t\t\treturn;\n\n\t\tx = con_linewidth - ((con_linewidth * 7) / 40);\n\t\ty = x - strlen(text) - 8;\n\t\ti = con_linewidth/3;\n\t\tif (strlen(text) > i) {\n\t\t\ty = x - i - 11;\n\t\t\tstrlcpy (dlbar, text, i);\n\t\t\tstrlcat (dlbar, \"...\", sizeof (dlbar));\n\t\t} else {\n\t\t\tstrlcpy (dlbar, text, sizeof (dlbar));\n\t\t}\n\t\tstrlcat (dlbar, \": \", sizeof (dlbar));\n\t\ti = strlen (dlbar);\n\t\tdlbar[i++] = '\\x80';\n\t\t// where's the dot go?\n\t\tif (cls.download) {\n\t\t\tif (cls.downloadpercent == 0)\n\t\t\t\tn = 0;\n\t\t\telse\n\t\t\t\tn = y * cls.downloadpercent / 100;\n\t\t} else if (cls.upload) {\n\t\t\tif (cls.uploadpercent == 0)\n\t\t\t\tn = 0;\n\t\t\telse\n\t\t\t\tn = y * cls.uploadpercent / 100;\n\t\t} else if (is_probing) {\n\t\t\tn = y * probing_progress / 100;\n\t\t}\n\n\t\tfor (j = 0; j < y; j++)\n\t\t{\n\t\t\tif (j == n)\n\t\t\t\tslider_box_position = i;\n\n\t\t\tdlbar[i++] = '\\x81';\n\t\t}\n\t\tif (slider_box_position >= 0)\n\t\t\tdlbar[slider_box_position] = '\\x83';\n\t\tdlbar[i++] = '\\x82';\n\t\tdlbar[i] = 0;\n\n\t\ti = strlen (dlbar);\n\t\tif (cls.download)\n\t\t\tsnprintf (dlbar + i, sizeof (dlbar) - i, \" %02d%%(%dkb/s)\", cls.downloadpercent, cls.downloadrate);\n\t\telse if (cls.upload)\n\t\t\tsnprintf (dlbar + i, sizeof (dlbar) - i, \" %02d%%(%dkb/s)\", cls.uploadpercent, cls.uploadrate);\n\t\telse if (is_probing)\n\t\t\tsnprintf (dlbar + i, sizeof (dlbar) - i, \" %02d%%\", probing_progress);\n\t\telse\n\t\t\treturn;\n\n\t\t// draw it\n\t\ty = con_vislines - 22 + 8;\n\t\tDraw_String (8, y + bound(0, con_shift.value, 8), dlbar);\n\t}\n\n\t// draw the input prompt, user text, and cursor if desired\n\tCon_DrawInput ();\n}\n\n"
  },
  {
    "path": "src/console.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n//\n// console\n//\n\n#ifndef\t_CONSOLE_H_\n\n#define _CONSOLE_H_\n\ntypedef struct\n{\n\twchar\t*text;\n\tint\t\tmaxsize;\n\tint\t\tcurrent;\t\t// line where next message will be printed\n\tint\t\tx;\t\t\t\t// offset in current line for next print\n\tint\t\tdisplay;\t\t// bottom of console displays this line\n\tint\t\tnumlines;\t\t// number of non-blank text lines, used for backscroling\n\tclrinfo_t *clr; // clr[maxsize]\n} console_t;\n\nextern\tconsole_t\tcon;\n\n// colored frag messages offsets, used to add team/enemy colors to frag messages\ntypedef struct cfrags_format_s {\n\tint p1pos;\t// position\n\tint p1len;\t// length, if zero - no coloring\n\tint p1col;\t// color\n\tint p2pos;\n\tint p2len;\n\tint p2col;\n\tqbool isFragMsg; // true if that was frag msg\n} cfrags_format;\n\nextern  qbool    con_addtimestamp;\n\nextern\tint\t\t\tcon_ormask;\n\nextern int con_totallines;\nextern int con_linewidth;\nextern qbool con_initialized, con_suppress;\nextern\tint\tcon_notifylines;\t\t// scan lines to clear for notify lines\n\nvoid Con_CheckResize (void);\nvoid Con_Init (void);\nvoid Con_Shutdown (void);\nvoid Con_DrawConsole (int lines);\nvoid Con_SafePrintf (char *fmt, ...);\nvoid Con_PrintW (wchar *txt);\nvoid Con_Clear_f (void);\nvoid Con_DrawNotify (void);\n\nvoid SCR_DrawNotify(int x, int y, float scale, int notifyTime, int notifyLines, int notifyCols, qbool proportional);\n\nvoid Con_ClearNotify (void);\nvoid Con_ToggleConsole_f (void);\n\nextern cvar_t con_timestamps;\n\nextern cvar_t con_shift;\n\nextern cvar_t con_sound_mm1_file;\nextern cvar_t con_sound_mm1_volume;\nextern cvar_t con_sound_mm2_file;\nextern cvar_t con_sound_mm2_volume;\nextern cvar_t con_sound_spec_file;\nextern cvar_t con_sound_spec_volume;\nextern cvar_t con_sound_other_file;\nextern cvar_t con_sound_other_volume;\nextern cvar_t con_mm2_only;\nextern  int         con_margin;         // kazik: margin on the left side\n\n#endif\t\t//_CONSOLE_H_\n"
  },
  {
    "path": "src/crc.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n/* crc.c */\n\n#ifdef SERVERONLY\n#include \"qwsvdef.h\"\n#else\n#include \"common.h\"\n#endif\n#include \"crc.h\"\n\n// this is a 16 bit, non-reflected CRC using the polynomial 0x1021\n// and the initial and final xor values shown below...  in other words, the\n// CCITT standard CRC used by XMODEM\n\n#define CRC_INIT_VALUE\t0xffff\n#define CRC_XOR_VALUE\t0x0000\n\nstatic unsigned short crctable[256] =\n{\n\t0x0000,\t0x1021,\t0x2042,\t0x3063,\t0x4084,\t0x50a5,\t0x60c6,\t0x70e7,\n\t0x8108,\t0x9129,\t0xa14a,\t0xb16b,\t0xc18c,\t0xd1ad,\t0xe1ce,\t0xf1ef,\n\t0x1231,\t0x0210,\t0x3273,\t0x2252,\t0x52b5,\t0x4294,\t0x72f7,\t0x62d6,\n\t0x9339,\t0x8318,\t0xb37b,\t0xa35a,\t0xd3bd,\t0xc39c,\t0xf3ff,\t0xe3de,\n\t0x2462,\t0x3443,\t0x0420,\t0x1401,\t0x64e6,\t0x74c7,\t0x44a4,\t0x5485,\n\t0xa56a,\t0xb54b,\t0x8528,\t0x9509,\t0xe5ee,\t0xf5cf,\t0xc5ac,\t0xd58d,\n\t0x3653,\t0x2672,\t0x1611,\t0x0630,\t0x76d7,\t0x66f6,\t0x5695,\t0x46b4,\n\t0xb75b,\t0xa77a,\t0x9719,\t0x8738,\t0xf7df,\t0xe7fe,\t0xd79d,\t0xc7bc,\n\t0x48c4,\t0x58e5,\t0x6886,\t0x78a7,\t0x0840,\t0x1861,\t0x2802,\t0x3823,\n\t0xc9cc,\t0xd9ed,\t0xe98e,\t0xf9af,\t0x8948,\t0x9969,\t0xa90a,\t0xb92b,\n\t0x5af5,\t0x4ad4,\t0x7ab7,\t0x6a96,\t0x1a71,\t0x0a50,\t0x3a33,\t0x2a12,\n\t0xdbfd,\t0xcbdc,\t0xfbbf,\t0xeb9e,\t0x9b79,\t0x8b58,\t0xbb3b,\t0xab1a,\n\t0x6ca6,\t0x7c87,\t0x4ce4,\t0x5cc5,\t0x2c22,\t0x3c03,\t0x0c60,\t0x1c41,\n\t0xedae,\t0xfd8f,\t0xcdec,\t0xddcd,\t0xad2a,\t0xbd0b,\t0x8d68,\t0x9d49,\n\t0x7e97,\t0x6eb6,\t0x5ed5,\t0x4ef4,\t0x3e13,\t0x2e32,\t0x1e51,\t0x0e70,\n\t0xff9f,\t0xefbe,\t0xdfdd,\t0xcffc,\t0xbf1b,\t0xaf3a,\t0x9f59,\t0x8f78,\n\t0x9188,\t0x81a9,\t0xb1ca,\t0xa1eb,\t0xd10c,\t0xc12d,\t0xf14e,\t0xe16f,\n\t0x1080,\t0x00a1,\t0x30c2,\t0x20e3,\t0x5004,\t0x4025,\t0x7046,\t0x6067,\n\t0x83b9,\t0x9398,\t0xa3fb,\t0xb3da,\t0xc33d,\t0xd31c,\t0xe37f,\t0xf35e,\n\t0x02b1,\t0x1290,\t0x22f3,\t0x32d2,\t0x4235,\t0x5214,\t0x6277,\t0x7256,\n\t0xb5ea,\t0xa5cb,\t0x95a8,\t0x8589,\t0xf56e,\t0xe54f,\t0xd52c,\t0xc50d,\n\t0x34e2,\t0x24c3,\t0x14a0,\t0x0481,\t0x7466,\t0x6447,\t0x5424,\t0x4405,\n\t0xa7db,\t0xb7fa,\t0x8799,\t0x97b8,\t0xe75f,\t0xf77e,\t0xc71d,\t0xd73c,\n\t0x26d3,\t0x36f2,\t0x0691,\t0x16b0,\t0x6657,\t0x7676,\t0x4615,\t0x5634,\n\t0xd94c,\t0xc96d,\t0xf90e,\t0xe92f,\t0x99c8,\t0x89e9,\t0xb98a,\t0xa9ab,\n\t0x5844,\t0x4865,\t0x7806,\t0x6827,\t0x18c0,\t0x08e1,\t0x3882,\t0x28a3,\n\t0xcb7d,\t0xdb5c,\t0xeb3f,\t0xfb1e,\t0x8bf9,\t0x9bd8,\t0xabbb,\t0xbb9a,\n\t0x4a75,\t0x5a54,\t0x6a37,\t0x7a16,\t0x0af1,\t0x1ad0,\t0x2ab3,\t0x3a92,\n\t0xfd2e,\t0xed0f,\t0xdd6c,\t0xcd4d,\t0xbdaa,\t0xad8b,\t0x9de8,\t0x8dc9,\n\t0x7c26,\t0x6c07,\t0x5c64,\t0x4c45,\t0x3ca2,\t0x2c83,\t0x1ce0,\t0x0cc1,\n\t0xef1f,\t0xff3e,\t0xcf5d,\t0xdf7c,\t0xaf9b,\t0xbfba,\t0x8fd9,\t0x9ff8,\n\t0x6e17,\t0x7e36,\t0x4e55,\t0x5e74,\t0x2e93,\t0x3eb2,\t0x0ed1,\t0x1ef0\n};\n\nvoid CRC_Init(unsigned short *crcvalue)\n{\n\t*crcvalue = CRC_INIT_VALUE;\n}\n\nvoid CRC_ProcessByte(unsigned short *crcvalue, byte data)\n{\n\t*crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data];\n}\n\nunsigned short CRC_Value(unsigned short crcvalue)\n{\n\treturn crcvalue ^ CRC_XOR_VALUE;\n}\n\nunsigned short CRC_Block (byte *start, unsigned int count)\n{\n\tunsigned short\tcrc;\n\n\tCRC_Init (&crc);\n\twhile (count--)\n\t\tcrc = (crc << 8) ^ crctable[(crc >> 8) ^ *start++];\n\n\treturn crc;\n}\n\nvoid CRC_AddBlock (unsigned short *crcvalue, byte *start, int count)\n{\n    while (count--)\n\t\tCRC_ProcessByte(crcvalue, *start++);\n}\n"
  },
  {
    "path": "src/crc.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n/* crc.h */\n\n#ifndef __CRC_H__\n#define __CRC_H__\n\nvoid CRC_Init(unsigned short *crcvalue);\nvoid CRC_ProcessByte(unsigned short *crcvalue, byte data);\nunsigned short CRC_Value(unsigned short crcvalue);\nunsigned short CRC_Block (byte *start, unsigned int count);\nvoid CRC_AddBlock (unsigned short *crcvalue, byte *start, int count);\n\n#endif /* !__CRC_H__ */\n"
  },
  {
    "path": "src/cvar.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\n*/\n// cvar.c -- dynamic variable tracking\n\n#ifdef SERVERONLY\n#include \"qwsvdef.h\"\n\n#define VAR_HASHPOOL_SIZE 32\n#else\n#include \"common.h\"\n#include \"teamplay.h\"\n#include \"rulesets.h\"\n#include \"keys.h\"\n#include \"utils.h\"\n#include \"parser.h\"\n#include \"r_renderer.h\"\n\nstatic void Cvar_ApplyLatchedUpdate(cvar_t* var);\n\nvoid CL_UserinfoChanged(char *key, char *value);\nvoid SV_ServerinfoChanged(char *key, char *value);\nvoid Help_DescribeCvar(cvar_t *v);\nvoid VID_ReloadCvarChanged(cvar_t* var);\n\nextern cvar_t r_fullbrightSkins;\nextern cvar_t cl_fakeshaft;\nextern cvar_t allow_scripts;\n\nstatic cvar_t cvar_viewdefault = {\"cvar_viewdefault\", \"1\"};\nstatic cvar_t cvar_viewhelp    = {\"cvar_viewhelp\",    \"1\"};\nstatic cvar_t cvar_viewlatched = {\"cvar_viewlatched\", \"1\"};\n\n#define VAR_HASHPOOL_SIZE 1024\n\nstatic void Cvar_AddCvarToGroup(cvar_t *var);\n#endif\n\nstatic cvar_t *cvar_hash[VAR_HASHPOOL_SIZE];\ncvar_t *cvar_vars;\nstatic char\t*cvar_null_string = \"\";\n\n// Use this to walk through all vars\ncvar_t* Cvar_Next(cvar_t *var)\n{\n\tif (var) {\n\t\treturn var->next;\n\t}\n\telse {\n\t\treturn cvar_vars;\n\t}\n}\n\ncvar_t *Cvar_Find(const char *var_name)\n{\n\tcvar_t *var;\n\tint key = Com_HashKey (var_name) % VAR_HASHPOOL_SIZE;\n\n\tfor (var = cvar_hash[key]; var; var = var->hash_next) {\n\t\tif (!strcasecmp(var_name, var->name)) {\n\t\t\treturn var;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nfloat Cvar_Value (const char *var_name)\n{\n\tcvar_t *var = Cvar_Find (var_name);\n\n\tif (!var) {\n\t\treturn 0;\n\t}\n\treturn Q_atof (var->string);\n}\n\nchar *Cvar_String (const char *var_name)\n{\n\tcvar_t *var = Cvar_Find (var_name);\n\n\tif (!var)\n\t\treturn cvar_null_string;\n\treturn var->string;\n}\n\nvoid SV_SendServerInfoChange(char *key, char *value);\n\nchar* Cvar_ServerInfoValue(char* key, char* value)\n{\n\t// force serverinfo \"0\" vars to be \"\".\n\t// meag: deathmatch is a special case as clients default 'not in serverinfo' to non-coop\n\tif (!strcmp(value, \"0\") && strcmp(key, \"deathmatch\")) {\n\t\treturn \"\";\n\t}\n\treturn value;\n}\n\nvoid Cvar_SetEx(cvar_t *var, char *value, qbool ignore_callback)\n{\n\tstatic qbool changing = false;\n\tchar *new_val;\n#ifndef SERVERONLY\n\textern cvar_t cl_warncmd;\n\textern cvar_t re_subi[10];\n\tfloat test;\n\tqbool same_value;\n#endif\n\n\tif (!var)\n\t\treturn;\n\n\t// force serverinfo \"0\" vars to be \"\".\n\tif (var->flags & CVAR_SERVERINFO) {\n\t\tvalue = Cvar_ServerInfoValue(var->name, value);\n\t}\n\n#ifndef SERVERONLY\n\t// C code may wrongly use Cvar_Set on non registered variable, some 99.99% accurate check\n\t// variables for internal triggers are not registered intentionally\n\tif (var < re_subi || var > re_subi + 9) {\n\t\tif (!var->next /* this is fast, but a bit flawed logic */ && !Cvar_Find(var->name)) {\n\t\t\tCom_Printf(\"Cvar_Set: on non linked var %s\\n\", var->name);\n\t\t\treturn;\n\t\t}\n\t}\n#endif\n\n\tif (var->flags & CVAR_ROM) {\n#ifndef SERVERONLY\n\t\tCom_Printf (\"\\\"%s\\\" is write protected\\n\", var->name);\n#endif\n\t\treturn;\n\t}\n\n#ifndef SERVERONLY\n\tif (var->flags & CVAR_RULESET_MIN) {\n\t\ttest  = Q_atof (value);\n\t\tif (test < var->minrulesetvalue) {\n\t\t\tCom_Printf (\"min \\\"%s\\\" is limited to %0.2f\\n\", var->name,var->minrulesetvalue);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (var->flags & CVAR_RULESET_MAX) {\n\t\ttest  = Q_atof (value);\n\t\tif (test > var->maxrulesetvalue) {\n\t\t\tCom_Printf (\"max \\\"%s\\\" is limited to %0.2f\\n\", var->name,var->maxrulesetvalue);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif ((var->flags & CVAR_INIT) && host_initialized) {\n\t\tif (cl_warncmd.value || developer.value)\n\t\t\tCom_Printf (\"\\\"%s\\\" can only be changed with \\\"+set %s %s\\\" on the command line.\\n\", var->name, var->name, value);\n\t\treturn;\n\t}\n\n\t// We do this before OnChange check, which means no OnChange check for latched cvars.\n\tif (var->flags & CVAR_LATCH)\n\t{\n\t\tif (var->latchedString)\n\t\t{\n\t\t\tif (strcmp(value, var->latchedString) == 0)\n\t\t\t\treturn; // latched string alredy has this value\n\t\t\tQ_free(var->latchedString); // switching latching string to other, so free it\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (strcmp(value, var->string) == 0)\n\t\t\t\treturn; // we change string value to the same, do no create latched string then\n\t\t}\n\n\t\t// HACK: sometime I need somehow silently change latched cvars.\n\t\t//       So, flag CVAR_SILENT for latched cvars mean no this warning.\n\t\t//       However keep this flag always on latched variable is stupid imo.\n\t\t//       So, do not mix CVAR_LATCHED | CVAR_SILENT in cvar definition.\n\t\tif ( !(var->flags & CVAR_SILENT) ) {\n\t\t\tconst char* restartcmd = \"vid_restart (video/graphics)\";\n\t\t\tif (strncmp(var->name, \"in_\", 3) == 0) {\n\t\t\t\trestartcmd = \"in_restart (input)\";\n\t\t\t}\n\t\t\telse if (strncmp(var->name, \"s_\", 2) == 0) {\n\t\t\t\trestartcmd = \"s_restart (sound)\";\n\t\t\t}\n\t\t\tCom_Printf (\"%s needs %s to take effect.\\n\", var->name, restartcmd);\n\t\t}\n\t\tvar->latchedString = Q_strdup_named(value, var->name);\n\t\tvar->modified = true; // set to true even car->string is not changed yet, that how q3 does\n\t\treturn;\n\t}\n#endif\n\n\tif (!ignore_callback && var->OnChange && !changing) {\n\t\tqbool cancel = false;\n\n\t\tchanging = true;\n\t\tvar->flags &= ~(CVAR_AUTOSETRECENT);\n\t\tvar->OnChange(var, value, &cancel);\n\t\tchanging = false;\n\n\t\tif (cancel) {\n\t\t\treturn; // change rejected.\n\t\t}\n\t}\n\n#ifndef SERVERONLY\n\tsame_value = value && var->string && (value == var->string || !strcmp (value, var->string));\n#endif\n\t// dup string first (before free) since 'value' and 'var->string' can point at the same memory area.\n\tnew_val = Q_strdup_named(value, var->name);\n\t// free the old value string\n\tQ_free(var->string);\n\t// assign new value\n\tvar->string = new_val;\n\tvar->value = Q_atof (var->string);\n#ifndef SERVERONLY\n\tvar->integer = Q_atoi (var->string);\n\tif (var->flags & CVAR_TRACKERCOLOR) {\n\t\tTrackerStringToRGB_W(var->string, var->color);\n\t}\n\telse {\n\t\tStringToRGB_W(var->string, var->color);\n\t}\n\tif (!same_value && !(var->flags & CVAR_AUTOSETRECENT)) {\n\t\tCvar_AutoReset (var);\n\t}\n\tvar->flags &= ~(CVAR_AUTOSETRECENT);\n\tvar->modified = true;\n#endif\n\n#ifndef CLIENTONLY\n\tif (var->flags & CVAR_SERVERINFO) {\n\t\tSV_ServerinfoChanged(var->name, var->string);\n\t}\n#endif\n\n#ifndef SERVERONLY\n\tif (var->flags & CVAR_USERINFO) {\n\t\tCL_UserinfoChanged(var->name, var->string);\n\t}\n\tif (var->flags & CVAR_RECOMPILE_PROGS) {\n\t\trenderer.CvarForceRecompile(var);\n\t}\n\tif ((var->flags & CVAR_RELOAD_GFX) && host_everything_loaded && !same_value) {\n\t\tVID_ReloadCvarChanged(var);\n\t}\n#endif\n}\n\nvoid Cvar_SetIgnoreCallback(cvar_t *var, char *value)\n{\n\tCvar_SetEx(var, value, true);\n}\n\nvoid Cvar_Set(cvar_t *var, char *value)\n{\n\tCvar_SetEx(var, value, false);\n}\n\nvoid Cvar_ForceSet (cvar_t *var, char *value)\n{\n\tint saved_flags;\n\n\tif (!var)\n\t\treturn;\n\n\tsaved_flags = var->flags;\n\tvar->flags &= ~CVAR_ROM;\n\tCvar_Set (var, value);\n\tvar->flags = saved_flags;\n}\n\nvoid Cvar_SetByName (const char *var_name, char *value)\n{\n\tcvar_t\t*var;\n\n\tvar = Cvar_Find (var_name);\n\tif (!var)\n\t{\t// there is an error in C code if this happens\n\t\tCon_DPrintf (\"Cvar_Set: variable %s not found\\n\", var_name);\n\t\treturn;\n\t}\n\n\tCvar_Set (var, value);\n}\n\nstatic void Cvar_ValueToString(char* val, int size, float value)\n{\n\tint\ti;\n\n\tsnprintf(val, size - 1, \"%f\", value);\n\tfor (i = strlen(val) - 1; i > 0 && val[i] == '0'; i--) {\n\t\tval[i] = 0;\n\t}\n\n\tif (val[i] == '.') {\n\t\tval[i] = '\\0';\n\t}\n}\n\nvoid Cvar_SetValue (cvar_t *var, const float value)\n{\n\tchar val[128];\n\n\tCvar_ValueToString(val, sizeof(val), value);\n\n\tCvar_Set(var, val);\n}\n\nvoid Cvar_SetValueByName(const char *var_name, const float value)\n{\n\tchar val[32];\n\n\tsnprintf(val, sizeof(val), \"%.8g\", value);\n\tCvar_SetByName(var_name, val);\n}\n\nvoid Cvar_Register(cvar_t *var)\n{\n\tint key;\n\tcvar_t *old = Cvar_Find(var->name);\n\n\t// All variables must be named :)\n\tif (var->name[0] == '\\0')\n\t\treturn;\n\n#ifdef SERVERONLY\n\tchar* value;\n\n\t// first check to see if it has already been defined\n\tif (old) {\n\t\tCon_Printf(\"Can't register variable %s, already defined\\n\", var->name);\n\t\treturn;\n\t}\n\n\t// check for overlap with a command\n\tif (Cmd_Exists(var->name)) {\n\t\tCon_Printf(\"Cvar_Register: %s is a command\\n\", var->name);\n\t\treturn;\n\t}\n\n\t// link the variable in\n\tkey = Com_HashKey(var->name);\n\tvar->hash_next = cvar_hash[key];\n\tcvar_hash[key] = var;\n\tvar->next = cvar_vars;\n\tcvar_vars = var;\n\n\t// set it through the function to be consistent\n\tvalue = var->string;\n\tvar->string = Q_strdup(\"\");\n\tCvar_SetROM(var, value);\n#else\n\t// we already register cvar, warn about it\n\tif (old && !(old->flags & CVAR_USER_CREATED)) {\n\t\t// allow re-register latched cvar\n\t\tif (old->flags & CVAR_LATCH) {\n\t\t\t// if we have a latched string, take that value now\n\t\t\tCvar_ApplyLatchedUpdate(old);\n\t\t\tCvar_AutoReset(old);\n\n\t\t\treturn;\n\t\t}\n\n\t\t// warn if CVAR_SILENT is not set\n\t\tif (!(old->flags & CVAR_SILENT)) {\n\t\t\tCom_Printf(\"Can't register variable %s, already defined\\n\", var->name);\n\t\t}\n\n\t\treturn;\n\t}\n\n\tif (old && old == var) {\n\t\tSys_Error(\"Cvar_Register: something wrong with %s\", var->name);\n\t}\n\n\tif (var->defaultvalue) {\n\t\tSys_Error(\"Cvar_Register: defaultvalue already set for %s\", var->name);\n\t}\n\n\tvar->defaultvalue = Q_strdup_named(var->string, var->name);\n\tif (old) {\n\t\tchar string[512];\n\t\tqbool queued_replace;\n\n\t\t// Trigger change later if this is a proper cvar replacing a user one\n\t\tqueued_replace = (old->flags & CVAR_USER_CREATED) && (var->flags & CVAR_QUEUED_TRIGGER) && !(var->flags & CVAR_USER_CREATED);\n\t\tif (queued_replace) {\n\t\t\t// Created by executing config, now we have the 'proper' cvar - store old value to trigger later\n\t\t\tvar->latchedString = Q_strdup_named(old->string, var->name);\n\t\t}\n\n\t\tvar->flags |= old->flags & ~(CVAR_USER_CREATED | CVAR_TEMP);\n\t\tstrlcpy(string, (var->flags & CVAR_ROM) || queued_replace ? var->string : old->string, sizeof(string));\n\t\tCvar_Delete(old->name);\n\t\tvar->string = Q_strdup_named(string, var->name);\n\t}\n\telse {\n\t\t// allocate the string on zone because future sets will Q_free it\n\t\tvar->string = Q_strdup_named(var->string, var->name);\n\t}\n\n\tvar->value = Q_atof(var->string);\n\tif (((var->flags & CVAR_RULESET_MIN) && var->value < var->minrulesetvalue) ||\n\t    ((var->flags & CVAR_RULESET_MAX) && var->value > var->maxrulesetvalue)) {\n\t\tchar val[128];\n\n\t\tvar->value = bound(var->minrulesetvalue, var->value, var->maxrulesetvalue);\n\t\tCvar_ValueToString(val, sizeof(val), var->value);\n\n\t\tQ_free(var->string);\n\t\tvar->string = Q_strdup_named(val, var->name);\n\t}\n\tvar->integer = Q_atoi(var->string);\n\tStringToRGB_W(var->string, var->color);\n\tvar->modified = true;\n\n\t// link the variable in\n\tkey = Com_HashKey(var->name) % VAR_HASHPOOL_SIZE;\n\tvar->hash_next = cvar_hash[key];\n\tcvar_hash[key] = var;\n\tvar->next = cvar_vars;\n\tcvar_vars = var;\n\n\tCvar_AddCvarToGroup(var);\n\n#ifndef CLIENTONLY\n\tif (var->flags & CVAR_SERVERINFO) {\n\t\tSV_ServerinfoChanged(var->name, var->string);\n\t}\n#endif\n\n\tif (var->flags & CVAR_USERINFO) {\n\t\tCL_UserinfoChanged(var->name, var->string);\n\t}\n#endif // !SERVERONLY\n}\n\nstatic void Cvar_ApplyLatchedUpdate(cvar_t* var)\n{\n\tif (var->latchedString) {\n\t\t// I did't want bother with all this CVAR_ROM and OnChange handler, just set value\n\t\tQ_free(var->string);\n\t\tvar->string = var->latchedString;\n\t\tvar->latchedString = NULL;\n\t\tvar->value = Q_atof(var->string);\n\t\tvar->integer = Q_atoi(var->string);\n\t\tStringToRGB_W(var->string, var->color);\n\t\tvar->modified = true;\n\t}\n}\n\nqbool Cvar_Command (void)\n{\n\tcvar_t *v;\n\n\t// check variables\n\tif (!(v = Cvar_Find (Cmd_Argv(0))))\n\t\treturn false;\n\n\tif (Cmd_Argc() == 1) {\n#ifndef SERVERONLY\n\t\tif (cvar_viewhelp.value)\n\t\t\tHelp_DescribeCvar (v);\n\n\t\tif (cvar_viewdefault.value) {\n\t\t\tchar *spaces = CreateSpaces(strlen(v->name) + 2);\n\n\t\t\tCon_Printf (\"%s : default value is \\\"%s\\\"\\n\", v->name, v->defaultvalue);\n\n\t\t\tCon_Printf (\"%s current value is \\\"%s\\\"\\n\", spaces, v->string);\n\n\t\t\tif ((v->flags & CVAR_AUTO) && v->autoString) {\n\t\t\t\tCon_Printf (\"%s auto    value is \\\"%s\\\"\\n\", spaces, v->autoString);\n\t\t\t}\n\n\t\t\tif (cvar_viewlatched.integer && v->latchedString) {\n\t\t\t\tCon_Printf (\"%s latched value is \\\"%s\\\"\\n\", spaces, v->latchedString);\n\t\t\t}\n\t\t}\n\t\telse {\n#endif\n\t\t\tCon_Printf (\"\\\"%s\\\" is \\\"%s\\\"\\n\", v->name, v->string);\n#ifndef SERVERONLY\n\t\t\tif ((v->flags & CVAR_AUTO) && v->autoString) {\n\t\t\t\tCon_Printf (\"auto: \\\"%s\\\"\\n\", v->autoString);\n\t\t\t}\n\n\t\t\tif (cvar_viewlatched.integer && v->latchedString) {\n\t\t\t\tCon_Printf (\"latched: \\\"%s\\\"\\n\", v->latchedString);\n\t\t\t}\n\t\t}\n#endif\n\t}\n\telse {\n#ifndef SERVERONLY\n\t\t// RestrictTriggers means that advanced (possibly cheaty) scripts are not allowed\n\t\t// So we will force the usage of user-created variables to go through the set command\n\t\tif (cbuf_current == &cbuf_server) {\n\t\t\t// Keep the mod from changing user cvars that it didn't create\n\t\t\tif ((v->flags & CVAR_USER_CREATED) && !(v->flags & CVAR_MOD_CREATED)) {\n\t\t\t\tCon_Printf (\"Error: server attempt to \\\"set\\\" user created variable [%s]\\n\", v->name);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\telse if ((v->flags & CVAR_USER_CREATED) && Rulesets_RestrictTriggers()) {\n\t\t\tCon_Printf (\"Current ruleset requires \\\"set\\\" with user created variables [%s]\\n\", v->name);\n\t\t\treturn true;\n\t\t}\n#endif\n\t\tCvar_Set (v, Cmd_MakeArgs(1));\n\t}\n\n\treturn true;\n}\n\nstatic void Cvar_Toggle_f_base (qbool use_regex)\n{\n\tcvar_t *var;\n\tchar *name;\n\tint i;\n\n\tif (Cmd_Argc() < 2) {\n\t\tCon_Printf(\"%s <cvar> [<cvar>..]: toggle a cvar on/off\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tfor (i = 1; i < Cmd_Argc(); i++) {\n\t\tname = Cmd_Argv(i);\n\n\t\tif (use_regex && IsRegexp(name)) {\n\t\t\tif (!ReSearchInitEx(name, false)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (var = cvar_vars; var; var = var->next) {\n\t\t\t\tif (ReSearchMatch(var->name)) {\n\t\t\t\t\tCvar_Set(var, var->value ? \"0\" : \"1\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tReSearchDone();\n\t\t}\n\t\telse {\n\t\t\tvar = Cvar_Find(name);\n\n\t\t\tif (!(var)) {\n\t\t\t\tCon_Printf(\"Unknown variable \\\"%s\\\"\\n\", name);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tCvar_Set(var, var->value ? \"0\" : \"1\");\n\t\t}\n\t}\n}\n\nvoid Cvar_Toggle_f (void)\n{\n\tCvar_Toggle_f_base(false);\n}\n\nvoid Cvar_Toggle_re_f (void)\n{\n\tCvar_Toggle_f_base(true);\n}\n\nvoid Cvar_Toggle (cvar_t *var)\n{\n\tCvar_SetValue (var, (var->value == 0) ? 1 : 0);\n}\n\nint Cvar_CvarCompare (const void *p1, const void *p2)\n{\n\treturn strcmp((*((cvar_t **) p1))->name, (*((cvar_t **) p2))->name);\n}\n\nvoid Cvar_CvarList(qbool use_regex)\n{\n\tstatic cvar_t *sorted_cvars[4096];\n\tint i, c, m = 0, count;\n\tcvar_t *var;\n\tchar *pattern;\n\n#define MAX_SORTED_CVARS (sizeof (sorted_cvars) / sizeof (sorted_cvars[0]))\n\n\tfor (var = cvar_vars, count = 0; var && count < MAX_SORTED_CVARS; var = var->next, count++) {\n\t\tsorted_cvars[count] = var;\n\t}\n\tqsort(sorted_cvars, count, sizeof(cvar_t *), Cvar_CvarCompare);\n\n\tif (count == MAX_SORTED_CVARS) {\n\t\tassert(!\"count == MAX_SORTED_CVARS\");\n\t}\n\n\tpattern = (Cmd_Argc() > 1) ? Cmd_Argv(1) : NULL;\n\n\tif (((c = Cmd_Argc()) > 1) && use_regex) {\n\t\tif (!ReSearchInitEx(Cmd_Argv(1), false)) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\tCon_Printf(\"List of cvars:\\n\");\n\tfor (i = 0; i < count; i++) {\n\t\tvar = sorted_cvars[i];\n\t\tif (use_regex) {\n\t\t\tif (!(c == 1 || ReSearchMatch(var->name))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (pattern && !Q_glob_match(pattern, var->name)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tCon_Printf(\"%c%c %s\\n\",\n#ifndef SERVERONLY\n\t\t\tvar->flags & CVAR_USERINFO ? 'u' :\n#endif\n\t\t\t' ',\n#ifndef CLIENTONLY\n\t\t\tvar->flags & CVAR_SERVERINFO ? 's' : \n#endif\n\t\t\t' ',\n\t\t\tvar->name);\n\t\tm++;\n\t}\n\n\tif (use_regex && (c > 1)) {\n\t\tReSearchDone();\n\t}\n\n\tCon_Printf(\"------------\\n%i/%i %svariables\\n\", m, count, (pattern) ? \"matching \" : \"\");\n}\n\nvoid Cvar_CvarList_f (void)\n{\n\tCvar_CvarList (false);\n}\n\nvoid Cvar_CvarList_re_f (void)\n{\n\tCvar_CvarList (true);\n}\n\nstatic void Cvar_CvarEdit_f(void)\n{\n\tcvar_t* cvar;\n\tchar *s, final_string[MAXCMDLINE - 1];\n\tint c;\n\n\tc = Cmd_Argc();\n\tif (c == 1) {\n\t\tCom_Printf(\"%s <name> : modify a cvar\\n\", Cmd_Argv(0));\n\t\tCom_Printf(\"cvarlist : list all cvars\\n\");\n\t\treturn;\n\t}\n\n\tcvar = Cvar_Find(Cmd_Argv(1));\n\tif (cvar == NULL) {\n\t\tCom_Printf(\"\\\"%s\\\" not a valid cvar\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\telse {\n\t\ts = Q_strdup(cvar->string);\n\t}\n\n\tsnprintf(final_string, sizeof(final_string), \"/%s \\\"%s\\\"\", Cmd_Argv(1), s);\n\tKey_ClearTyping();\n\tmemcpy(key_lines[edit_line] + 1, str2wcs(final_string), strlen(final_string) * sizeof(wchar));\n\tQ_free(s);\n}\n\ncvar_t *Cvar_Create(const char *name, const char *string, int cvarflags)\n{\n\tcvar_t *v;\n\tint key;\n\n\tv = Cvar_Find(name);\n\tif (v) {\n#ifndef SERVERONLY\n\t\tv->flags &= ~CVAR_TEMP;\n#endif\n\t\tv->flags |= cvarflags;\n\t\treturn v;\n\t}\n\tv = (cvar_t *)Q_malloc(sizeof(cvar_t));\n\t// Cvar doesn't exist, so we create it\n\tv->next = cvar_vars;\n\tcvar_vars = v;\n\n\tkey = Com_HashKey(name) % VAR_HASHPOOL_SIZE;\n\tv->hash_next = cvar_hash[key];\n\tcvar_hash[key] = v;\n\n\tv->name = Q_strdup_named(name, name);\n\tv->string = Q_strdup_named(string, name);\n\tv->value = Q_atof(v->string);\n\tv->flags = cvarflags;\n#ifndef SERVERONLY\n\tv->flags |= CVAR_USER_CREATED;\n\tv->defaultvalue = Q_strdup_named(string, name);\n\tif (cbuf_current == &cbuf_server) {\n\t\tv->flags |= CVAR_MOD_CREATED;\n\t}\n\tv->integer = Q_atoi(v->string);\n\tStringToRGB_W(v->string, v->color);\n\tv->modified = true;\n#endif\n\n\treturn v;\n}\n\n// returns true if the cvar was found (and deleted)\nqbool Cvar_Delete(const char *name)\n{\n\tcvar_t *var, *prev = NULL;\n\tint key = Com_HashKey(name) % VAR_HASHPOOL_SIZE;\n\n\tfor (var = cvar_hash[key]; var; var = var->hash_next) {\n\t\tif (!strcasecmp(var->name, name)) {\n\t\t\t// unlink from hash\n\t\t\tif (prev) {\n\t\t\t\tprev->hash_next = var->hash_next;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcvar_hash[key] = var->hash_next;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tprev = var;\n\t}\n\n\tif (!var) {\n\t\treturn false;\n\t}\n\n\tprev = NULL;\n\tfor (var = cvar_vars; var; var = var->next) {\n\t\tif (!strcasecmp(var->name, name)) {\n\t\t\t// unlink from cvar list\n\t\t\tif (prev) {\n\t\t\t\tprev->next = var->next;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcvar_vars = var->next;\n\t\t\t}\n#ifndef SERVERONLY\n\t\t\tQ_free(var->defaultvalue);\n#endif\n\t\t\t// free\n\t\t\tQ_free(var->string);\n\t\t\tQ_free(var->name);\n\t\t\tQ_free(var);\n\t\t\treturn true;\n\t\t}\n\t\tprev = var;\n\t}\n\n\tSys_Error(\"Cvar list broken\");\n\treturn false;\n}\n\nvoid Cvar_Set_f (void)\n{\n\tcvar_t *var;\n\tchar *var_name;\n\n\tif (Cmd_Argc() != 3) {\n\t\tCon_Printf (\"Usage: %s <cvar> <value>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tvar_name = Cmd_Argv (1);\n\tvar = Cvar_Find (var_name);\n\n\tif (var) {\n\t\tCvar_Set(var, Cmd_Argv(2));\n\t}\n\telse {\n\t\tif (Cmd_Exists(var_name)) {\n\t\t\tCon_Printf(\"\\\"%s\\\" is a command\\n\", var_name);\n\t\t\treturn;\n\t\t}\n\n\t\tvar = Cvar_Create (var_name, Cmd_Argv(2), cbuf_current == &cbuf_server ? CVAR_MOD_CREATED : CVAR_USER_CREATED);\n\t}\n}\n\nvoid Cvar_Inc_f(void)\n{\n\tint     c;\n\tcvar_t  *var;\n\tfloat   delta;\n\n\tc = Cmd_Argc();\n\tif (c != 2 && c != 3) {\n\t\tCon_Printf(\"Usage: inc <cvar> [value]\\n\");\n\t\treturn;\n\t}\n\n\tvar = Cvar_Find(Cmd_Argv(1));\n\tif (!var) {\n\t\tCon_Printf(\"Unknown variable \\\"%s\\\"\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\tdelta = (c == 3) ? atof(Cmd_Argv(2)) : 1;\n\n\tCvar_SetValue(var, var->value + delta);\n}\n\nint Cvar_GetFlags (cvar_t *var)\n{\n\treturn var->flags;\n}\n\n#ifndef SERVERONLY\nqbool Cvar_ForceCallback(cvar_t *var)\n{\n\tqbool cancel = false;\n\n\tif (!var || !var->OnChange)\n\t\treturn cancel;\n\n\tvar->OnChange(var, var->string, &cancel);\n\treturn cancel;\n}\n\nvoid Cvar_ResetVar (cvar_t *var)\n{\n\tif (var && strcmp(var->string, var->defaultvalue)) {\n\t\tCvar_Set(var, var->defaultvalue);\n\t}\n}\n\nvoid Cvar_Reset(qbool use_regex)\n{\n\tqbool re_search = false;\n\tcvar_t *var;\n\tchar *name;\n\tint i;\n\n\tif (Cmd_Argc() < 2) {\n\t\tCom_Printf(\"%s <cvar> [<cvar2>..]: reset variable to it default value\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tfor (i = 1; i < Cmd_Argc(); i++) {\n\t\tname = Cmd_Argv(i);\n\n\t\tif (use_regex && (re_search = IsRegexp(name))) {\n\t\t\tif (!ReSearchInitEx(name, false)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (use_regex && re_search) {\n\t\t\tfor (var = cvar_vars; var; var = var->next) {\n\t\t\t\tif (ReSearchMatch(var->name)) {\n\t\t\t\t\tCvar_ResetVar(var);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif ((var = Cvar_Find(name))) {\n\t\t\t\tCvar_ResetVar(var);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCom_Printf(\"%s : No variable with name %s\\n\", Cmd_Argv(0), name);\n\t\t\t}\n\t\t}\n\t\tif (use_regex && re_search) {\n\t\t\tReSearchDone();\n\t\t}\n\t}\n}\n\nvoid Cvar_Reset_f (void)\n{\n\tCvar_Reset(false);\n}\n\nvoid Cvar_Reset_re_f (void)\n{\n\tCvar_Reset(true);\n}\n\nvoid Cvar_SetDefaultAndValue(cvar_t *var, float default_value, float actual_value)\n{\n\tchar val[128];\n\tint i;\n\n\tsnprintf (val, sizeof(val), \"%f\", default_value);\n\tfor (i = strlen(val) - 1; i > 0 && val[i] == '0'; i--)\n\t\tval[i] = 0;\n\tif (val[i] == '.')\n\t\tval[i] = 0;\n\tQ_free(var->defaultvalue);\n\tvar->defaultvalue = Q_strdup_named(val, var->name);\n\tCvar_SetValue(var, actual_value);\n}\n\nchar *Cvar_CompleteVariable (char *partial)\n{\n\tcvar_t *cvar;\n\tint len;\n\n\tif (!(len = strlen(partial)))\n\t\treturn NULL;\n\n\t// check exact match\n\tfor (cvar = cvar_vars; cvar; cvar = cvar->next)\n\t\tif (!strcasecmp (partial,cvar->name))\n\t\t\treturn cvar->name;\n\n\t// check partial match\n\tfor (cvar = cvar_vars; cvar; cvar = cvar->next)\n\t\tif (!strncasecmp (partial,cvar->name, len))\n\t\t\treturn cvar->name;\n\n\treturn NULL;\n}\n\nint Cvar_CompleteCountPossible (char *partial)\n{\n\tcvar_t *cvar;\n\tint len, c = 0;\n\n\tif (!(len = strlen(partial)))\n\t\treturn 0;\n\n\t// check partial match\n\tfor (cvar = cvar_vars; cvar; cvar = cvar->next)\n\t\tif (!strncasecmp (partial, cvar->name, len))\n\t\t\tc++;\n\n\treturn c;\n}\n\nvoid Cvar_RulesetSet(cvar_t *var, char *val, int m)\n{\n\tfloat rulesetval_f = Q_atof (val);\n\n\tswitch (m) {\n\tcase 0:\n\t\tvar->minrulesetvalue = rulesetval_f;\n\t\tbreak;\n\tcase 1:\n\t\tvar->maxrulesetvalue = rulesetval_f;\n\t\tbreak;\n\tcase 2:\n\t\tvar->minrulesetvalue = rulesetval_f;\n\t\tvar->maxrulesetvalue = rulesetval_f;\n\t\tbreak;\n\tdefault:\n\t\treturn;\n\t}\n}\n\nvoid Cvar_AutoSet(cvar_t *var, char *value)\n{\n\tif (!var || !(var->flags & CVAR_AUTO) || !value) {\n\t\treturn;\n\t}\n\n\tQ_free(var->autoString);\n\n\tvar->autoString = Q_strdup(value);\n\tvar->flags |= CVAR_AUTOSETRECENT;\n}\n\nvoid Cvar_AutoSetInt(cvar_t *var, int value)\n{\n\tchar val[128];\n\n\tif (!var || !(var->flags & CVAR_AUTO)) {\n\t\treturn;\n\t}\n\n\tQ_free(var->autoString);\n\n\tsnprintf(&val[0], sizeof(val), \"%d\", value);\n\n\tvar->autoString = Q_strdup_named(val, var->name);\n\tvar->flags |= CVAR_AUTOSETRECENT;\n}\n\nvoid Cvar_AutoReset(cvar_t *var)\n{\n\tif (!var) {\n\t\treturn;\n\t}\n\n\tQ_free(var->autoString);\n}\n\n// silently set value for latched cvar\nvoid Cvar_LatchedSet(cvar_t *var, char *value)\n{\n\t// yea, do not mess things\n\tif (!(var->flags & CVAR_LATCH)) {\n\t\tSys_Error(\"Cvar_LatchedSet: non latched var %s\", var->name);\n\t}\n\n\tvar->flags |= CVAR_SILENT; // shut up warnings, at least some...\n\tCvar_Set(var, value);\n\t// remove this flag, btw if variable have this flag before set this flag will be removed anyway..\n\tvar->flags &= ~CVAR_SILENT;\n\t// actually set value\n\tCvar_Register(var);\n}\n\n// silently set value for latched cvar\nvoid Cvar_LatchedSetValue(cvar_t *var, float value)\n{\n\t// yea, do not mess things\n\tif (!(var->flags & CVAR_LATCH)) {\n\t\tSys_Error(\"Cvar_LatchedSet: non latched var %s\", var->name);\n\t}\n\n\tvar->flags |= CVAR_SILENT; // shut up warnings, at least some...\n\tCvar_SetValue(var, value);\n\t// remove this flag, btw if variable have this flag before set this flag will be removed anyway..\n\tvar->flags &= ~CVAR_SILENT;\n\t// actually set value\n\tCvar_Register(var);\n}\n\nvoid Cvar_SetFlags(cvar_t *var, int flags)\n{\n\tvar->flags = flags;\n}\n\nstatic cvar_group_t *current = NULL;\ncvar_group_t *cvar_groups = NULL;\n\n#define CVAR_GROUPS_DEFINE_VARIABLES\n#include \"cvar_groups.h\"\n#undef CVAR_GROUPS_DEFINE_VARIABLES\n\nstatic cvar_group_t *Cvar_AddGroup(char *name)\n{\n\tstatic qbool initialised = false;\n\tcvar_group_t *newgroup;\n\tchar **s;\n\n\tif (!initialised) {\n\t\tinitialised = true;\n\t\tfor (s = cvar_groups_list; *s; s++)\n\t\t\tCvar_AddGroup(*s);\n\t}\n\n\tfor (newgroup = cvar_groups; newgroup; newgroup = newgroup->next)\n\t\tif (!strcasecmp(newgroup->name, name))\n\t\t\treturn newgroup;\n\n\tnewgroup = (cvar_group_t *) Q_malloc(sizeof(cvar_group_t));\n\tstrlcpy(newgroup->name, name, sizeof(newgroup->name));\n\tnewgroup->count = 0;\n\tnewgroup->head = NULL;\n\tnewgroup->next = cvar_groups;\n\tcvar_groups = newgroup;\n\n\treturn newgroup;\n}\n\nvoid Cvar_SetCurrentGroup(char *name)\n{\n\tcvar_group_t *group;\n\n\tfor (group = cvar_groups; group; group = group->next) {\n\t\tif (!strcmp(group->name, name)) {\n\t\t\tcurrent = group;\n\t\t\treturn;\n\t\t}\n\t}\n\tcurrent = Cvar_AddGroup(name);\n}\n\nvoid Cvar_ResetCurrentGroup(void)\n{\n\tcurrent = NULL;\n}\n\nstatic void Cvar_AddCvarToGroup(cvar_t *var)\n{\n\tif ((var->group = current)) {\n\t\tvar->next_in_group = current->head;\n\t\tcurrent->head = var;\n\t\tcurrent->count++;\n\t}\n\telse {\n\t\tvar->next_in_group = NULL;\n\t}\n}\n\nvoid Cvar_Set_tp_f(void)\n{\n\tcvar_t *var;\n\tchar *var_name;\n\n\tif (Cmd_Argc() != 3) {\n\t\tCom_Printf(\"Usage: %s <cvar> <value>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tvar_name = Cmd_Argv(1);\n\tvar = Cvar_Find(var_name);\n\n\tif (var) {\n\t\tif (!var->teamplay) {\n\t\t\tCom_Printf(\"\\\"%s\\\" is not a teamplay variable\\n\", var->name);\n\t\t\treturn;\n\t\t}\n\t\telse {\n\t\t\tCvar_Set(var, Cmd_Argv(2));\n\t\t}\n\t}\n\telse {\n\t\tif (Cmd_Exists(var_name)) {\n\t\t\tCom_Printf(\"\\\"%s\\\" is a command\\n\", var_name);\n\t\t\treturn;\n\t\t}\n\t\tvar = Cvar_Create(var_name, Cmd_Argv(2), 0);\n\t\tvar->teamplay = true;\n\t}\n}\n\nstatic void Cvar_Set_ex_f(void)\n{\n\tcvar_t* var;\n\tchar* var_name;\n\tchar* st = NULL;\n\tchar\ttext_exp[1024];\n\tqbool   parse_funchars = !strcasecmp(Cmd_Argv(0), \"set_ex\");\n\n\tif (Rulesets_RestrictSetEx()) {\n\t\tCom_Printf(\"The use of set_ex/set_ex2 is not allowed during matches\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() != 3) {\n\t\tCom_Printf(\"usage: %s <cvar> <value>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tvar_name = Cmd_Argv(1);\n\tvar = Cvar_Find(var_name);\n\n\tif (!var) {\n\t\tif (Cmd_Exists(var_name)) {\n\t\t\tCom_Printf(\"\\\"%s\\\" is a command\\n\", var_name);\n\t\t\treturn;\n\t\t}\n\t\tvar = Cvar_Create(var_name, \"\", 0);\n\t}\n\n\tCmd_ExpandString(Cmd_Argv(2), text_exp);\n\tst = TP_ParseMacroString(text_exp);\n\tif (parse_funchars) {\n\t\tst = TP_ParseFunChars(st, false);\n\t}\n\n\tCvar_Set(var, st);\n}\n\nvoid Cvar_Set_Alias_Str_f (void)\n{\n\tcvar_t\t\t*var;\n\tchar\t\t*var_name;\n\tchar\t\t*alias_name;\n\t//char\t\tstr[1024];\n\tchar\t\t*v /*,*s*/;\n\tif (Cmd_Argc() != 3) {\n\t\tCom_Printf (\"usage: set_alias_str <cvar> <alias>\\n\");\n\t\treturn;\n\t}\n\n\tvar_name = Cmd_Argv (1);\n\talias_name = Cmd_Argv (2);\n\tvar = Cvar_Find (var_name);\n\tv = Cmd_AliasString( alias_name );\n\n\tif ( !var) {\n\t\tif (Cmd_Exists(var_name)) {\n\t\t\tCom_Printf (\"\\\"%s\\\" is a command\\n\", var_name);\n\t\t\treturn;\n\t\t}\n\t\tvar = Cvar_Create(var_name, \"\", 0);\n\t}\n\n\tif (!var) {\n\t\tCom_Printf (\"Unknown variable \\\"%s\\\"\\n\", var_name);\n\t\treturn;\n\t}\n\telse if (!v) {\n\t\tCom_Printf (\"Unknown alias \\\"%s\\\"\\n\", alias_name);\n\t\treturn;\n\t}\n\telse {\n\t\tCvar_Set (var, v);\n\t}\n}\n\nvoid Cvar_Set_Bind_Str_f(void)\n{\n\tcvar_t\t\t*var;\n\tint\t\t\tkeynum;\n\tchar\t\t*var_name;\n\tchar\t\t*key_name;\n\t//char\t\tstr[1024];\n\t//char\t\t*v,*s;\n\n\tif (Cmd_Argc() != 3) {\n\t\tCom_Printf(\"usage: set_bind_str <cvar> <key>\\n\");\n\t\treturn;\n\t}\n\n\tvar_name = Cmd_Argv(1);\n\tkey_name = Cmd_Argv(2);\n\tvar = Cvar_Find(var_name);\n\tkeynum = Key_StringToKeynum(key_name);\n\n\tif (!var) {\n\t\tif (Cmd_Exists(var_name)) {\n\t\t\tCom_Printf(\"\\\"%s\\\" is a command\\n\", var_name);\n\t\t\treturn;\n\t\t}\n\t\tvar = Cvar_Create(var_name, \"\", 0);\n\t}\n\n\tif (!var) {\n\t\tCom_Printf(\"Unknown variable \\\"%s\\\"\\n\", var_name);\n\t\treturn;\n\t}\n\telse if (keynum == -1) {\n\t\tCom_Printf(\"Unknown key \\\"%s\\\"\\n\", key_name);\n\t\treturn;\n\t}\n\telse {\n\t\tif (keybindings[keynum]) {\n\t\t\tCvar_Set(var, keybindings[keynum]);\n\t\t}\n\t\telse {\n\t\t\tCvar_Set(var, \"\");\n\t\t}\n\t}\n}\n\n// disconnect -->\nvoid Cvar_UnSet (qbool use_regex)\n{\n\tcvar_t\t*var, *next;\n\tchar\t*name;\n\tint\t\ti;\n\tqbool\tre_search = false;\n\n\tif (Cmd_Argc() < 2) {\n\t\tCom_Printf (\"unset <cvar> [<cvar2>..]: erase user-created variable\\n\");\n\t\treturn;\n\t}\n\n\tfor (i=1; i<Cmd_Argc(); i++) {\n\t\tname = Cmd_Argv(i);\n\n\t\tif (use_regex && (re_search = IsRegexp(name))) {\n\t\t\tif (!ReSearchInitEx(name, false)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (use_regex && re_search) {\n\t\t\tfor (var = cvar_vars ; var ; var = next) {\n\t\t\t\tnext = var->next;\n\n\t\t\t\tif (ReSearchMatch(var->name)) {\n\t\t\t\t\tif ((var->flags & CVAR_USER_CREATED) && !(var->flags & CVAR_MOD_CREATED)) {\n\t\t\t\t\t\tCvar_Delete(var->name);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tCom_Printf(\"Can't delete not user created cvars (\\\"%s\\\")\\n\", var->name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (!(var = Cvar_Find(name))) {\n\t\t\t\tCom_Printf(\"Can't delete \\\"%s\\\": no such cvar\\n\", name);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ((var->flags & CVAR_USER_CREATED) && !(var->flags & CVAR_MOD_CREATED)) {\n\t\t\t\tCvar_Delete(name);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCom_Printf(\"Can't delete not user created cvars (\\\"%s\\\")\\n\", name);\n\t\t\t}\n\t\t}\n\t\tif (use_regex && re_search)\n\t\t\tReSearchDone();\n\t}\n}\n\nvoid Cvar_UnSet_f(void)\n{\n\tCvar_UnSet(false);\n}\n\nvoid Cvar_UnSet_re_f(void)\n{\n\tCvar_UnSet(true);\n}\n// <-- disconnect\n\n// QW262 -->\n\n// XXX: this could do with some refactoring\nvoid Cvar_Set_Calc_f(void)\n{\n\tcvar_t *var, *var2;\n\tchar *var_name, *op, *a2, *a3;\n\tint\tpos, len, division_by_zero = 0;\n\tfloat num1, num2, result;\n\tchar buf[1024];\n\n\tif (Rulesets_RestrictSetCalc()) {\n\t\tCom_Printf(\"The use of set_calc is not allowed during matches\\n\");\n\t\treturn;\n\t}\n\n\tvar_name = Cmd_Argv (1);\n\tvar = Cvar_Find (var_name);\n\n\tif (!var)\n\t\tvar = Cvar_Create (var_name, Cmd_Argv (2), 0);\n\n\ta2 = Cmd_Argv (2);\n\ta3 = Cmd_Argv (3);\n\n\tif (!strcmp (a2, \"strlen\")) {\n\t\tCvar_SetValue (var, strlen (a3));\n\t\treturn;\n\t} else if (!strcmp (a2, \"int\")) {\n\t\tCvar_SetValue (var, (int) Q_atof (a3));\n\t\treturn;\n\t} else if (!strcmp (a2, \"substr\")) {\n\t\tint var2len;\n\t\tvar2 = Cvar_Find (a3);\n\n\t\tif (!var2) {\n\t\t\tCom_Printf (\"Unknown variable \\\"%s\\\"\\n\", a3);\n\t\t\treturn;\n\t\t}\n\n\t\tpos = atoi (Cmd_Argv (4));\n\t\tlen = (Cmd_Argc () < 6)? 1 : atoi (Cmd_Argv (5));\n\n\t\tif (len == 0) {\n\t\t\tCvar_Set (var, \"\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (len < 0 || pos < 0) {\n\t\t\tCom_Printf (\"substr: invalid len or pos\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tvar2len = strlen (var2->string);\n\n\t\tif (var2len < pos) {\n\t\t\tCom_Printf (\"substr: string length exceeded\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tlen = min (var2len - pos, len);\n\t\tstrlcpy (buf, var2->string + pos, len);\n\t\tCvar_Set (var, buf);\n\t\treturn;\n\n\t} else if (!strcmp (a2, \"set_substr\")) {\n\t\tint\tvar1len,var2len;\n\t\tvar2 = Cvar_Find (a3);\n\n\t\tif (!var2) {\n\t\t\tCom_Printf (\"Unknown variable \\\"%s\\\"\\n\", a3);\n\t\t\treturn;\n\t\t}\n\n\t\tvar1len = strlen (var->string);\n\t\tvar2len = strlen (var2->string);\n\t\tpos = atoi (Cmd_Argv (4));\n\n\t\tstrlcpy (buf, var->string, sizeof (buf));\n\n\t\tif (pos + var2len > var1len) { // need to expand\n\t\t\tint i;\n\t\t\tfor (i = var1len; i < pos + var2len; i++)\n\t\t\t\tbuf[i] = ' ';\n\t\t\tbuf[pos+var2len] = 0;\n\t\t}\n\n\t\tstrlcpy (buf + pos, var2->string, sizeof (buf) - pos);\n\t\tCvar_Set (var, buf);\n\n\t\treturn;\n\t} else if (!strcmp (a2, \"pos\")) {\n\t\tvar2 = Cvar_Find (a3);\n\n\t\tif (!var2) {\n\t\t\tCom_Printf (\"Unknown variable \\\"%s\\\"\\n\", a3);\n\t\t\treturn;\n\t\t}\n\n\t\top = strstr (var2->string, Cmd_Argv (4));\n\t\tCvar_SetValue (var, op ? op - var2->string : -1);\n\t\treturn;\n\t} else if (!strcmp (a2, \"tobrown\")) {\n\t\tstrlcpy (buf, var->string, sizeof (buf));\n\t\tCharsToBrown (buf, buf + strlen (buf));\n\t\tCvar_Set (var, buf);\n\t\treturn;\n\t} else if (!strcmp (a2, \"towhite\")) {\n\t\tstrlcpy (buf, var->string, sizeof (buf));\n\t\tCharsToWhite (buf, buf + strlen (buf));\n\t\tCvar_Set (var, buf);\n\t\treturn;\n\t}\n\n\tnum1 = Q_atof (a2);\n\top = a3;\n\tnum2 = Q_atof (Cmd_Argv (4));\n\n\tif (!strcmp (op, \"+\"))\n\t\tresult = num1 + num2;\n\telse if (!strcmp (op, \"-\"))\n\t\tresult = num1 - num2;\n\telse if (!strcmp (op, \"*\"))\n\t\tresult = num1 * num2;\n\telse if (!strcmp (op, \"/\")) {\n\t\tif (num2 != 0)\n\t\t\tresult = num1 / num2;\n\t\telse\n\t\t\tdivision_by_zero = 1;\n\t} else if (!strcmp (op, \"%\")) {\n\t\tif ((int) num2 != 0)\n\t\t\tresult = (int) num1 % (int) num2;\n\t\telse\n\t\t\tdivision_by_zero = 1;\n\t} else if (!strcmp (op, \"div\")) {\n\t\tif ((int) num2 != 0)\n\t\t\tresult = (int) num1 / (int) num2;\n\t\telse\n\t\t\tdivision_by_zero = 1;\n\t} else if (!strcmp (op, \"and\")) {\n\t\tresult = (int) num1 & (int) num2;\n\t} else if (!strcmp (op, \"or\")) {\n\t\tresult = (int) num1 | (int) num2;\n\t} else if (!strcmp (op, \"xor\")) {\n\t\tresult = (int) num1 ^ (int) num2;\n\t} else {\n\t\tCom_Printf (\"set_calc: unknown operator: %s\\nvalid operators are: + - * / div %% and or xor\\n\", op);\n\t\t// Com_Printf(\"set_calc: unknown command: %s\\nvalid commands are: strlen int substr set_substr pos\\n\", a2);\n\t\treturn;\n\t}\n\n\tif (division_by_zero) {\n\t\tCom_Printf (\"set_calc: can't divide by zero\\n\");\n\t\tresult = 999;\n\t}\n\n\tCvar_SetValue (var, result);\n}\n\n// <-- QW262\n\nvoid Cvar_Set_Eval_f(void)\n{\n\tif (Rulesets_RestrictSetEval()) {\n\t\tCom_Printf(\"The use of set_eval is not allowed during matches\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() < 3) {\n\t\tCom_Printf(\"Usage:\\n\"\n\t\t\t\"set_eval <variable> <expression>\\n\"\n\t\t\t\"set_eval is similar to set_calc but supports richer expressions\\n\");\n\t\treturn;\n\t}\n\telse {\n\t\tconst char *expression;\n\t\tchar *var_name;\n\t\tcvar_t *var;\n\t\tint errn;\n\t\texpr_val result;\n\n\t\tvar_name = Cmd_Argv (1);\n\t\tvar = Cvar_Find (var_name);\n\n\t\tif (!var)\n\t\t\tvar = Cvar_Create (var_name, Cmd_Argv (1), 0);\n\n\t\texpression = Cmd_MakeArgs(2);\n\n\t\tresult = Expr_Eval(expression, NULL, &errn);\n\n\t\tif (errn == EXPR_EVAL_SUCCESS) {\n\t\t\tswitch (result.type) {\n\t\t\tcase ET_INT:\n\t\t\t\tCvar_SetValue(var, result.i_val);\n\t\t\t\tbreak;\n\t\t\tcase ET_DBL:\n\t\t\t\tCvar_SetValue(var, result.d_val);\n\t\t\t\tbreak;\n\t\t\tcase ET_BOOL:\n\t\t\t\tCvar_SetValue(var, result.b_val ? 1 : 0);\n\t\t\t\tbreak;\n\t\t\tcase ET_STR:\n\t\t\t\tCvar_Set(var, result.s_val);\n\t\t\t\tQ_free(result.s_val);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"Error in expression: %s\\n\", Parser_Error_Description(errn));\n\t\t}\n\t}\n}\n\n\n\n// if an unknown command with parameters was encountered when loading\n// config.cfg, assume it's a cvar set and spawn a temp var\nqbool Cvar_CreateTempVar (void)\n{\n\tchar *name = Cmd_Argv(0);\n\t// FIXME, make sure it's a valid cvar name, return false if not\n\tCvar_Create (name, Cmd_MakeArgs(1), CVAR_TEMP);\n\treturn true;\n}\n\n// if none of the subsystems claimed the cvar from config.cfg, remove it\nvoid Cvar_CleanUpTempVars (void)\n{\n\tcvar_t *var, *next;\n\n\tfor (var = cvar_vars; var; var = next) {\n\t\tnext = var->next;\n\t\tif (var->flags & CVAR_TEMP)\n\t\t\tCvar_Delete (var->name);\n\t}\n}\n#endif\n\nvoid Cvar_Init(void)\n{\n\tCmd_AddCommand(\"cvarlist\", Cvar_CvarList_f);\n\tCmd_AddCommand(\"cvaredit\", Cvar_CvarEdit_f);\n\tCmd_AddCommand(\"cvarlist_re\", Cvar_CvarList_re_f);\n\tCmd_AddCommand(\"toggle\", Cvar_Toggle_f);\n\tCmd_AddCommand(\"set\", Cvar_Set_f);\n\tCmd_AddCommand(\"inc\", Cvar_Inc_f);\n\n#ifndef SERVERONLY\n\tCmd_AddCommand(\"set_tp\", Cvar_Set_tp_f);\n\tCmd_AddCommand(\"set_ex\", Cvar_Set_ex_f);\n\tCmd_AddCommand(\"set_ex2\", Cvar_Set_ex_f);\n\tCmd_AddCommand(\"set_alias_str\", Cvar_Set_Alias_Str_f);\n\tCmd_AddCommand(\"set_bind_str\", Cvar_Set_Bind_Str_f);\n\tCmd_AddCommand(\"unset\", Cvar_UnSet_f);\n\tCmd_AddCommand(\"unset_re\", Cvar_UnSet_re_f);\n\tCmd_AddCommand(\"toggle_re\", Cvar_Toggle_re_f);\n\tCmd_AddCommand(\"cvar_reset\", Cvar_Reset_f);\n\tCmd_AddCommand(\"cvar_reset_re\", Cvar_Reset_re_f);\n\tCmd_AddCommand(\"set_calc\", Cvar_Set_Calc_f);\n\tCmd_AddCommand(\"set_eval\", Cvar_Set_Eval_f);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_CONSOLE);\n\tCvar_Register(&cvar_viewdefault);\n\tCvar_Register(&cvar_viewhelp);\n\tCvar_Register(&cvar_viewlatched);\n\n\tCvar_ResetCurrentGroup();\n#endif\n}\n\nvoid Cvar_Shutdown(void)\n{\n\tcvar_t *cvar;\n\tcvar_t *next;\n\tcvar_group_t* group;\n\tcvar_group_t* next_group;\n\n\tfor (cvar = cvar_vars; cvar; cvar = next) {\n\t\tnext = cvar->next;\n\n#ifndef SERVERONLY\n\t\tQ_free(cvar->latchedString);\n\t\tQ_free(cvar->autoString);\n\t\tQ_free(cvar->defaultvalue);\n#endif\n\t\tQ_free(cvar->string);\n\n#ifndef SERVERONLY\n\t\tif (cvar->flags & CVAR_USER_CREATED) {\n\t\t\tQ_free(cvar->name);\n\t\t\tQ_free(cvar);\n\t\t}\n#endif\n\t}\n\n#ifndef SERVERONLY\n\tfor (group = cvar_groups; group; group = next_group) {\n\t\tnext_group = group->next;\n\t\tQ_free(group);\n\t}\n#endif\n}\n\nvoid Cvar_ExecuteQueuedChanges(void)\n{\n\tcvar_t* cvar;\n\tqbool vid_restart = false;\n\tqbool vid_reload = false;\n\tqbool sound_restart = false;\n\n\tfor (cvar = cvar_vars; cvar; cvar = cvar->next) {\n\t\tif ((cvar->flags & CVAR_QUEUED_TRIGGER) && cvar->latchedString) {\n\t\t\tif (cvar->OnChange) {\n\t\t\t\tqbool cancel = false;\n\n\t\t\t\tcvar->OnChange(cvar, cvar->latchedString, &cancel);\n\t\t\t\tif (!cancel) {\n\t\t\t\t\tCvar_ApplyLatchedUpdate(cvar);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tQ_free(cvar->latchedString);\n\t\t}\n\n\t\tvid_restart |= (cvar->flags & CVAR_LATCH_GFX) && cvar->latchedString;\n\t\tvid_reload |= (cvar->flags & CVAR_RELOAD_GFX && cvar->modified);\n\t\tsound_restart |= (cvar->flags & CVAR_LATCH_SOUND) && cvar->latchedString;\n\t}\n\n\tif (vid_restart) {\n\t\tCbuf_AddTextEx(&cbuf_main, \"\\nvid_restart\\n\");\n\t}\n\telse {\n\t\tif (sound_restart) {\n\t\t\tCbuf_AddTextEx(&cbuf_main, \"\\ns_restart\\n\");\n\t\t}\n\t\tif (vid_reload) {\n\t\t\tCbuf_AddTextEx(&cbuf_main, \"\\nvid_reload\\n\");\n\t\t}\n\t}\n\n\tCvar_ClearAllModifiedFlags(CVAR_RELOAD_GFX);\n}\n\nvoid Cvar_ClearAllModifiedFlags(int flags)\n{\n\tcvar_t* cvar;\n\n\tfor (cvar = cvar_vars; cvar; cvar = cvar->next) {\n\t\tif (cvar->flags & flags) {\n\t\t\tcvar->modified = false;\n\t\t}\n\t}\n}\n\nqbool Cvar_AnyModified(int flags)\n{\n\tcvar_t* cvar;\n\n\tfor (cvar = cvar_vars; cvar; cvar = cvar->next) {\n\t\tif ((cvar->flags & flags) && cvar->modified) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n"
  },
  {
    "path": "src/cvar.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n// cvar.h\n#ifndef __CVAR_H__\n#define __CVAR_H__\n\n#ifndef SERVERONLY\n#include \"cvar_groups.h\"\n#endif\n\n// cvar flags\n#define CVAR_NONE\t\t\t (0)\n#define CVAR_ARCHIVE         (1<<0)  // !!!obsolete!!!\n#define CVAR_USERINFO        (1<<1)  // mirrored to userinfo\n#define CVAR_SERVERINFO      (1<<2)  // mirrored to serverinfo\n#define CVAR_ROM             (1<<3)  // read only\n#define CVAR_INIT            (1<<4)  // can only be set during initialization\n#define\tCVAR_USER_CREATED    (1<<5)  // created by a set command\n#define\tCVAR_USER_ARCHIVE    (1<<6)  // created by a seta command !!!obsolete!!!\n#define CVAR_RULESET_MAX     (1<<7)  // limited by ruleset\n#define CVAR_RULESET_MIN     (1<<8)  // limited by ruleset\n#define CVAR_NO_RESET        (1<<9)  // do not perform reset to default in /cfg_load command, but /cvar_reset will still work\n#define CVAR_TEMP            (1<<10) // created during config.cfg execution, before subsystems are initialized\n#define CVAR_LATCH_GFX       (1<<11) // will only change when C code next does a Cvar_Register(), so it can't be changed\n                                     // without proper initialization.  modified will be set, even though the value hasn't changed yet\n                                     // will trigger vid_restart if modified on startup\n#define CVAR_SILENT          (1<<12) // skip warning when trying Cvar_Register() second time\n#define CVAR_COLOR           (1<<13) // on change convert the string to internal RGBA type\n#define CVAR_AUTO            (1<<14) // Cvar can possibly have an automatically calculated value that shouldn't be saved, but still presentable\n#define CVAR_MOD_CREATED     (1<<15) // Cvar created by the mod issuing 'set' command\n#define CVAR_RECOMPILE_PROGS (1<<16) // Flag all programs to be recompiled if the value changes\n#define CVAR_TRACKERCOLOR    (1<<17) // Convert the string from tracker format (0-9)(0-9)(0-9)\n#define CVAR_QUEUED_TRIGGER  (1<<18) // Found in config and then registered...\n#define CVAR_AUTOSETRECENT   (1<<19) // Ugh... temporary flag so Cvar_SetEx() knows if auto-value set during on-change event\n#define CVAR_USERINFONORESET (1<<20) // won't be reset by cfg_reset/cfg_load when \n#define CVAR_LATCH_SOUND     (1<<21) // will only change when C code next does a Cvar_Register()... will trigger sound restart if modified on startup\n#define CVAR_RELOAD_GFX      (1<<22) // changes immediately but takes effect as textures load, prompt for vid_reload\n\n#define CVAR_LATCH (CVAR_LATCH_GFX | CVAR_LATCH_SOUND)\n\n#define CVAR_USERINFO_NO_CFG_RESET (CVAR_USERINFO | CVAR_USERINFONORESET)\n\ntypedef struct cvar_s {\n\tchar    *name;\n\tchar    *string;\n\tint     flags;\n\tvoid    (*OnChange)(struct cvar_s *var, char *value, qbool *cancel);\n\tfloat   value;              // may be set in Cvar_Set(), Cvar_Register(), Cvar_Create()\n\n#ifndef SERVERONLY\n\tfloat   maxrulesetvalue;\n\tfloat   minrulesetvalue;\n\tchar    *defaultvalue;\n\tchar    *latchedString;\n\tchar    *autoString;        // Must be set via Cvar_AutoSet()\n\tint     integer;            // may be set in Cvar_Set(), Cvar_Register(), Cvar_Create()\n\tbyte    color[4];           // gets set in Cvar_Set, Cvar_Register, Cvar_Create\n\tqbool   modified;           // set to true in Cvar_Set(), Cvar_Register(), Cvar_Create(), reset to false manually in C code\n\tqbool   teamplay;           // is this variable protected so that it can be only used within messaging?\n\n\tstruct cvar_group_s *group;\n\tstruct cvar_s       *next_in_group;\n#endif\n\n\tstruct cvar_s *hash_next;\n\tstruct cvar_s *next;\n} cvar_t;\n\n#ifndef SERVERONLY\ntypedef struct cvar_group_s {\n\tchar\tname[65];\n\tint\t\tcount;\n\tcvar_t\t*head;\n\tstruct cvar_group_s *next;\n} cvar_group_t;\n#else\n#define Cvar_SetCurrentGroup(...)\t\t// ezquake compatibility\n#define Cvar_ResetCurrentGroup(...)\t\t// ezquake compatibility\n#endif\n\n// registers a cvar that already has the name, string, and optionally the\n// archive elements set.\nvoid Cvar_Register (cvar_t *variable);\n\n// Use this to walk through all vars\ncvar_t *Cvar_Next (cvar_t *var);\n\n// creates a cvar dynamically\ncvar_t *Cvar_Create (const char *name, const char *string, int cvarflags);\n\n// equivalent to \"<name> <variable>\" typed at the console\nvoid Cvar_Set (cvar_t *var, char *value);\nvoid Cvar_SetIgnoreCallback(cvar_t *var, char *value);\n\n// equivalent to \"<name> <variable>\" typed at the console\nvoid Cvar_SetByName (const char *var_name, char *value);\nvoid Cvar_SetValueByName (const char *var_name, const float value);\nvoid Cvar_LatchedSetValue(cvar_t *var, float value);\n\n// expands value to a string and calls Cvar_Set\nvoid Cvar_SetValue (cvar_t *var, const float value);\n\n// force a set even if the cvar is read only\nvoid Cvar_ForceSet (cvar_t *var, char *value);\n#define Cvar_SetROM Cvar_ForceSet\n\n// toggle boolean variable\nvoid Cvar_Toggle (cvar_t *var);\n\nint Cvar_GetFlags (cvar_t *var);\n\n// returns 0 if not defined or non numeric\nfloat Cvar_Value (const char *var_name);\n\n// returns an empty string if not defined\nchar *Cvar_String (const char *var_name);\n\n// called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known\n// command.  Returns true if the command was a variable reference that\n// was handled. (print or change)\nqbool Cvar_Command (void);\n\ncvar_t *Cvar_Find (const char *name);\nqbool Cvar_Delete (const char *name);\n\nvoid Cvar_Init(void);\nvoid Cvar_Shutdown(void);\n\n#ifndef SERVERONLY\n// Used for automatically calculated variables (for presentation)\n// but not to be saved in config\nvoid Cvar_AutoSet(cvar_t *var, char *value);\nvoid Cvar_AutoSetInt(cvar_t *var, int value);\nvoid Cvar_AutoReset(cvar_t *var);\n\n// equivalent to \"<name> <variable>\" typed at the console, but silent for latched var\nvoid Cvar_LatchedSet (cvar_t *var, char *value);\n\n// sets ruleset limit for variable\n// when ruleset is active you can't set lower/higher value than this\nvoid Cvar_RulesetSet(cvar_t *var, char *val, int m); // m=0 --> min, m=1--> max, m=2-->locked(max&min)\n\nvoid Cvar_SetFlags (cvar_t *var, int flags);\n\nvoid Cvar_SetDefaultAndValue(cvar_t *var, float default_value, float actual_value);\n\n// attempts to match a partial variable name for command line completion\n// returns NULL if nothing fits\nchar  *Cvar_CompleteVariable (char *partial);\n\n// call OnChange callback.\nqbool Cvar_ForceCallback(cvar_t *var);\nvoid Cvar_ResetVar (cvar_t *var);\n\nvoid Cvar_SetCurrentGroup(char *name);\nvoid Cvar_ResetCurrentGroup(void);\n\nqbool Cvar_CreateTempVar (void);\t// when parsing config.cfg\nvoid Cvar_CleanUpTempVars (void);\t// clean up afterwards\n#else\n// ezquake compatibility - integrate into mvdsv?\n#define IsRegexp(...) false\n#define ReSearchInit(...) false\n#define ReSearchInitEx(...) false\n#define ReSearchMatch(...) false\n#define ReSearchDone(...)\n#endif\n\nchar* Cvar_ServerInfoValue(char* key, char* value);\n\nvoid Cvar_ClearAllModifiedFlags(int flags);\nvoid Cvar_ExecuteQueuedChanges(void);\nqbool Cvar_AnyModified(int flags);\n\n#endif /* !__CVAR_H__ */\n\n"
  },
  {
    "path": "src/cvar_groups.h",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef CVAR_GROUPS_DEFINE_VARIABLES\n\n#define CVAR_GROUP_NO_GROUP                 \"#No Group#\"\n#define CVAR_GROUP_SERVER_PERMISSIONS       \"Serverside Permissions\"\n#define CVAR_GROUP_SERVER_PHYSICS           \"Serverside Physics\"\n#define CVAR_GROUP_SERVERINFO               \"Serverinfo Keys\"\n#define CVAR_GROUP_SERVER_MAIN              \"Server Settings\"\n#define CVAR_GROUP_ITEM_NEED                \"Item Need Amounts\"\n#define CVAR_GROUP_ITEM_NAMES               \"Item Names\"\n#define CVAR_GROUP_SPECTATOR                \"Spectator Tracking\"\n#define CVAR_GROUP_SCREENSHOTS              \"Screenshot Settings\"\n#define CVAR_GROUP_DEMO                     \"Demo Handling\"\n#define CVAR_GROUP_MATCH_TOOLS              \"Match Tools\"\n#define CVAR_GROUP_VIEW                     \"View Settings\"\n#define CVAR_GROUP_BLEND                    \"Screen & Powerup Blends\"\n#define CVAR_GROUP_SCREEN                   \"Screen Settings\"\n#define CVAR_GROUP_CROSSHAIR                \"Crosshair Settings\"\n#define CVAR_GROUP_SBAR                     \"Status Bar and Scoreboard\"\n#define CVAR_GROUP_SERVER_BROWSER           \"Server Browser\"\n#define CVAR_GROUP_NETWORK                  \"Network Settings\"\n#define CVAR_GROUP_INPUT_MISC               \"Input - Misc\"\n#define CVAR_GROUP_INPUT_MOUSE              \"Input - Mouse\"\n#define CVAR_GROUP_INPUT_KEYBOARD           \"Input - Keyboard\"\n#define CVAR_GROUP_INPUT_JOYSTICK           \"Input - Joystick\"\n#define CVAR_GROUP_SOUND                    \"Sound Settings\"\n#define CVAR_GROUP_VIDEO                    \"Video Settings\"\n#define CVAR_GROUP_SYSTEM_SETTINGS          \"System Settings\"\n#define CVAR_GROUP_OPENGL                   \"OpenGL Rendering\"\n#define CVAR_GROUP_TEXTURES                 \"Texture Settings\"\n#define CVAR_GROUP_VIEWMODEL                \"Weapon View Model Settings\"\n#define CVAR_GROUP_TURB                     \"Turbulency and Sky Settings\"\n#define CVAR_GROUP_LIGHTING                 \"Lighting\"\n#define CVAR_GROUP_PARTICLES                \"Particle Effects\"\n#define CVAR_GROUP_EYECANDY                 \"FPS and EyeCandy Settings\"\n#define CVAR_GROUP_CHAT                     \"Chat Settings\"\n#define CVAR_GROUP_CONSOLE                  \"Console Settings\"\n#define CVAR_GROUP_SKIN                     \"Skin Settings\"\n#define CVAR_GROUP_COMMUNICATION            \"Teamplay Communications\"\n\n#define CVAR_GROUP_USERINFO                 \"Player Settings\"\n#define CVAR_GROUP_CONFIG                   \"Config Management\"\n#define CVAR_GROUP_HUD                      \"MQWCL HUD\" // HUD -> hexum\n\n#define CVAR_GROUP_MVD                      \"MultiView Demos\"\n#define CVAR_GROUP_QTV                      \"QTV Settings\"\n#define CVAR_GROUP_MENU                     \"Menu\"\n#define CVAR_GROUP_FILESYSTEM               \"File System\"\n\n#else   // CVAR_GROUPS_DEFINE_VARIABLES\n\nchar *cvar_groups_list[] = {\n\tCVAR_GROUP_NO_GROUP,\n\tCVAR_GROUP_SERVER_PERMISSIONS,\n\tCVAR_GROUP_SERVER_PHYSICS,\n\tCVAR_GROUP_SERVERINFO,\n\tCVAR_GROUP_SERVER_MAIN,\n\tCVAR_GROUP_ITEM_NEED,\n\tCVAR_GROUP_ITEM_NAMES,\n\tCVAR_GROUP_SPECTATOR,\n\tCVAR_GROUP_SCREENSHOTS,\n\tCVAR_GROUP_DEMO,\n\tCVAR_GROUP_MATCH_TOOLS,\n\tCVAR_GROUP_VIEW,\n\tCVAR_GROUP_BLEND,\n\tCVAR_GROUP_SCREEN,\n\tCVAR_GROUP_CROSSHAIR,\n\tCVAR_GROUP_SBAR,\n\tCVAR_GROUP_SERVER_BROWSER,\n\tCVAR_GROUP_NETWORK,\n\tCVAR_GROUP_INPUT_MISC,\n\tCVAR_GROUP_INPUT_MOUSE,\n\tCVAR_GROUP_INPUT_KEYBOARD,\n\tCVAR_GROUP_INPUT_JOYSTICK,\n\tCVAR_GROUP_SOUND,\n\tCVAR_GROUP_VIDEO,\n\tCVAR_GROUP_SYSTEM_SETTINGS,\n\tCVAR_GROUP_OPENGL,\n\tCVAR_GROUP_CONFIG,\n\tCVAR_GROUP_TEXTURES,\n\tCVAR_GROUP_VIEWMODEL,\n\tCVAR_GROUP_TURB,\n\tCVAR_GROUP_LIGHTING,\n\tCVAR_GROUP_PARTICLES,\n\tCVAR_GROUP_EYECANDY,\n\tCVAR_GROUP_CHAT,\n\tCVAR_GROUP_CONSOLE,\n\tCVAR_GROUP_SKIN,\n\tCVAR_GROUP_COMMUNICATION,\n\tCVAR_GROUP_USERINFO,\n\tCVAR_GROUP_HUD,        // HUD -> hexum\n\tCVAR_GROUP_MVD,\n\tCVAR_GROUP_QTV,\n\tCVAR_GROUP_MENU,\n\tCVAR_GROUP_FILESYSTEM,\n\tNULL\n};\n\n#endif //CVAR_GROUPS_VARIABLES\n"
  },
  {
    "path": "src/demo_controls.c",
    "content": "\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: demo_controls.c,v 1.78 2007/10/27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"keys.h\"\n#include \"ez_controls.h\"\n#include \"ez_slider.h\"\n#include \"ez_window.h\"\n#include \"ez_label.h\"\n#include \"ez_button.h\"\n#include \"demo_controls.h\"\n\nfloat CL_GetDemoLength(void);\ndouble Demo_GetSpeed(void);\nextern cvar_t cl_demospeed;\n\n// The control tree.\nstatic ez_tree_t\tdemocontrol_tree;\n\n// Controls.\nstatic ez_control_t *root;\t\t\t\t// Root of the control tree (transparent, as big as the conwidth/height, \n\t\t\t\t\t\t\t\t\t\t// just here to be able to get all mouse events on the screen).\nstatic ez_window_t\t*window;\t\t\t// The demo controls window.\nstatic ez_slider_t\t*demo_slider;\t\t// The demo time slider.\nstatic ez_label_t\t*timelabel;\t\t\t// The text label that shows the current time.\nstatic ez_label_t\t*hover_timelabel;\t// The text label that shows the time where the cursor is positioned on the slider.\nstatic ez_label_t\t*speed_title_label;\t// The title text label for the speed.\nstatic ez_label_t\t*speed_label;\t\t// The text label that shows the current demo speed in percent.\n\nstatic ez_control_t\t*button_container;\t// A container to easily move all the buttons around together.\nstatic ez_button_t\t*fast_button;\t\t// Faster button.\nstatic ez_button_t\t*slow_button;\t\t// Slower button.\nstatic ez_button_t\t*play_button;\t\t// Play button.\nstatic ez_button_t\t*pause_button;\t\t// Pause button.\nstatic ez_button_t\t*stop_button;\t\t// Stop button.\n//static ez_button_t\t*prev_button;\t\t// Play next demo in the playlist.\n//static ez_button_t\t*next_button;\t\t// Play previous demo in the playlist.\n\n// State.\nstatic qbool slider_updating = false;\nstatic qbool democontrols_on = false;\nstatic double previous_demospeed = 1.0;\n\nstatic void DemoControls_SetTimeLabelText(ez_label_t *label, double time)\n{\n\tEZ_label_SetText(label, va(\"%02d:%02d\", Q_rint(time / 60), Q_rint((int)time % 60)));\n}\n\nstatic void DemoControls_Destroy(void)\n{\n\tdemocontrols_on = false;\n\tkey_dest = key_game;\n\tkey_dest_beforecon = key_game;\n\tEZ_tree_Destroy(&democontrol_tree);\n}\n\nvoid DemoControls_Draw(void)\n{\n\tif (democontrols_on)\n\t{\n\t\t// Position the slider based on the current demo time.\n\t\tif (cls.demoplayback)\n\t\t{\n\t\t\tfloat demo_length = CL_GetDemoLength();\n\t\t\tfloat current_demotime = cls.demotime - demostarttime; // TODO: Make a function for this in cl_demo.c\n\t\t\t\n\t\t\tint pos = Q_rint(demo_slider->max_value * (double)(cls.demotime - demostarttime) / max(1, demo_length));\n\n\t\t\tDemoControls_SetTimeLabelText(timelabel, current_demotime);\n\n\t\t\tslider_updating = true;\n\t\t\tEZ_slider_SetPosition(demo_slider, pos);\n\t\t\tslider_updating = false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDemoControls_Destroy();\n\t\t}\n\t}\n\n\t// Should always be run so that any final cleanup is done on a destroy.\n\tEZ_tree_EventLoop(&democontrol_tree);\n}\n\nstatic int DemoControls_SliderChanged(ez_control_t *self, void *payload, void *ext_event_info)\n{\n\tez_slider_t *demo_slider = (ez_slider_t *)self;\n\n\tif (!slider_updating)\n\t{\n\t\tfloat demo_length = CL_GetDemoLength();\n\t\tdouble newdemotime = ((demo_slider->slider_pos * demo_length) / (double)demo_slider->max_value);\n\n\t\tDemoControls_SetTimeLabelText(timelabel, newdemotime);\n\n\t\tCL_Demo_Jump(newdemotime, false, DST_SEEKING_NORMAL);\n\t}\n\n\treturn 0;\n}\n\nstatic int DemoControls_Slider_OnMouseEnter(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\n\tEZ_control_SetVisible((ez_control_t *)hover_timelabel, true);\n\treturn 0;\n}\n\nstatic int DemoControls_Slider_OnMouseLeave(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\n\tEZ_control_SetVisible((ez_control_t *)hover_timelabel, false);\n\treturn 0;\n}\n\nstatic int DemoControls_Slider_OnMouseHover(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\n\tez_slider_t *demo_slider = (ez_slider_t *)self;\n\tfloat demo_length = CL_GetDemoLength();\n\tdouble demotime = ((EZ_slider_GetPositionFromMouse(demo_slider, ms->x, ms->y) * demo_length) / (double)demo_slider->max_value);\n\n\tEZ_control_SetPosition((ez_control_t *)hover_timelabel, (ms->x - self->absolute_x), -8);\n\tEZ_label_SetText(hover_timelabel, va(\"%02d:%02d\", Q_rint(demotime / 60), max(0, Q_rint((int)demotime % 60))));\n\n\treturn 0;\n}\n\nstatic void DemoControls_SetSpeedLabel(void)\n{\n\tchar label_text[20] = { 0 };\n\n\tsnprintf(label_text, sizeof(label_text) - 1, \"%4i%%\", Q_rint(Demo_GetSpeed() * 100));\n\n\tEZ_label_SetText(speed_label, label_text);\n}\n\nstatic void DemoControls_SetDemoSpeed(double speed)\n{\n\tspeed = bound(0.0, speed, 10.0);\n\n\tif (speed != previous_demospeed)\n\t{\n\t\tprevious_demospeed = Demo_GetSpeed();\n\t}\n\n\tCvar_SetValue(&cl_demospeed, speed);\n\tDemoControls_SetSpeedLabel();\n}\n\nstatic int DemoControls_SpeedButton_OnMouseClick(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\t\n\tfloat delta = (self == ((ez_control_t *)fast_button)) ? 0.1 : -0.1;\t\n\t\n\tDemoControls_SetDemoSpeed(cl_demospeed.value + delta);\n\t\n\treturn 1;\n}\n\nstatic int DemoControls_PlayButton_OnMouseClick(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\n\tif ((previous_demospeed != 0.0) && (Demo_GetSpeed() == 0.0))\n\t{\n\t\tDemoControls_SetDemoSpeed(previous_demospeed);\n\t}\n\telse\n\t{\n\t\t// We're not paused so just reset the speed when clicking the play button.\n\t\tDemoControls_SetDemoSpeed(1.0);\n\t}\n\t\n\treturn 1;\n}\n\nstatic int DemoControls_PauseButton_OnMouseClick(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\n\tDemoControls_SetDemoSpeed(0.0);\t\n\treturn 1;\n}\n\nstatic int DemoControls_StopButton_OnMouseClick(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\n\tCbuf_AddText(\"disconnect\\n\");\n\tDemoControls_Destroy();\n\treturn 1;\n}\n\n// TODO: Fade these gradually instead.\nstatic int DemoControls_Window_OnMouseEnter(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\n\tEZ_control_SetOpacity(self, 1.0);\n\treturn 0;\n}\n\nstatic int DemoControls_Window_OnMouseLeave(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\n\tEZ_control_SetOpacity(self, 0.5);\n\treturn 0;\n}\n\nstatic int DemoControls_Window_OnCloseClick(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\n\tDemoControls_Destroy();\n\treturn 0;\n}\n\nstatic void DemoControls_Init(void)\n{\n\t#define DEMO_BUTTON_GAP\t\t5\n\t#define DEMO_BUTTON_HEIGHT\t16\n\t#define DEMO_BUTTON_WIDTH\t24\n\n\t// Root.\n\t{\n\t\troot = EZ_control_Create(&democontrol_tree, NULL, \"Root\", \"\", 0, 0, vid.conwidth, vid.conheight, 0);\n\t}\n\n\t// Demo time slider.\n\t{\n\t\tdemo_slider = EZ_slider_Create(&democontrol_tree, root, \n\t\t\t\"Demo slider\", \"\", 5, 5, 8, 8, control_focusable | control_contained);\n\n\t\tEZ_control_SetAnchor((ez_control_t *)demo_slider, anchor_left | anchor_right);\n\t\tEZ_slider_SetMax(demo_slider, 1000);\n\n\t\t// Add a event handler for when the slider handle changes position.\n\t\tEZ_slider_AddOnSliderPositionChanged(demo_slider, DemoControls_SliderChanged, NULL);\n\t\tEZ_control_AddOnMouseHover((ez_control_t *)demo_slider, DemoControls_Slider_OnMouseHover, NULL);\n\t\tEZ_control_AddOnMouseEnter((ez_control_t *)demo_slider, DemoControls_Slider_OnMouseEnter, NULL);\n\t\tEZ_control_AddOnMouseLeave((ez_control_t *)demo_slider, DemoControls_Slider_OnMouseLeave, NULL);\n\n\t\t// When we click a point on the slider, make the slider handle jump there.\n\t\tEZ_slider_SetJumpToClick(demo_slider, true);\n\t}\n\n\t// Demo time label.\n\t{\n\t\ttimelabel = EZ_label_Create(&democontrol_tree, root, \n\t\t\t\"Demo time label\", \"\", -15, 5, 40, 16,  \n\t\t\tcontrol_focusable | control_contained | control_resizeable, \n\t\t\t0, \"\");\n\n\t\tEZ_label_SetReadOnly(timelabel, true);\n\t\tEZ_label_SetTextSelectable(timelabel, false);\n\t\tEZ_control_SetAnchor((ez_control_t *)timelabel, anchor_right | anchor_top);\n\t}\n\n\t// Demo hover time label (when the slider is hovered).\n\t{\n\t\tez_control_t *tmp;\n\t\thover_timelabel = EZ_label_Create(&democontrol_tree, (ez_control_t *)demo_slider, \n\t\t\t\"Hover demo time label\", \"\", 0, 0, 40, 8, 0, 0, \"\");\n\n\t\ttmp = (ez_control_t *)hover_timelabel;\n\n\t\tEZ_label_SetReadOnly(hover_timelabel, true);\n\t\tEZ_label_SetTextSelectable(hover_timelabel, false);\n\t\tEZ_label_SetTextColor(hover_timelabel, 125, 125, 0, 255);\n\t\tEZ_control_SetAnchor(tmp, anchor_left | anchor_top);\n\t\tEZ_control_SetVisible(tmp, false);\n\t\tEZ_control_SetContained(tmp, false);\n\t\tEZ_control_SetBackgroundColor(tmp, 0, 0, 0, 125);\n\t\tEZ_control_SetFocusable(tmp, false);\n\t}\n\n\t// Demo speed title label.\n\t{\n\t\tspeed_title_label = EZ_label_Create(&democontrol_tree, root, \n\t\t\t\"Demo speed title label\", \"\", -(15 + 32), 15, 64, 16,\n\t\t\tcontrol_contained | control_resizeable, \n\t\t\t0, \"\");\n\n\t\tEZ_label_SetAutoSize(speed_title_label, true);\n\t\tEZ_label_SetText(speed_title_label, \"Speed:\");\n\t\tEZ_label_SetTextColor(speed_title_label, 255, 150, 0, 255);\n\t\tEZ_label_SetReadOnly(speed_title_label, true);\n\t\tEZ_label_SetTextSelectable(speed_title_label, false);\n\t\tEZ_control_SetAnchor((ez_control_t *)speed_title_label, anchor_right | anchor_top);\n\t}\n\n\t// Demo speed label.\n\t{\n\t\tspeed_label = EZ_label_Create(&democontrol_tree, root, \n\t\t\t\"Demo speed label\", \"\", -7, 15, 32, 16,\n\t\t\tcontrol_contained | control_resizeable, \n\t\t\t0, \"\");\n\n\t\tEZ_label_SetAutoSize(speed_label, true);\n\t\tDemoControls_SetSpeedLabel();\n\t\tEZ_label_SetReadOnly(speed_label, true);\n\t\tEZ_label_SetTextSelectable(speed_label, false);\n\t\tEZ_control_SetAnchor((ez_control_t *)speed_label, anchor_right | anchor_top);\n\t}\n\n\t// Buttons.\n\t{\n\t\t// Button container.\n\t\t{\n\t\t\tbutton_container = EZ_control_Create(&democontrol_tree, root, \n\t\t\t\t\"Demo button container\", NULL, 10, 25, 200, 16, control_contained | control_focusable);\n\n\t\t\tEZ_control_SetAnchor(button_container, anchor_left | anchor_right);\n\t\t}\t\t\n\n\t\t// Slower button.\n\t\t{\n\t\t\tslow_button = EZ_button_Create(&democontrol_tree, button_container,\n\t\t\t\t\"Slower button\", \"Slows down demo playback\", 0, 0, DEMO_BUTTON_WIDTH, DEMO_BUTTON_HEIGHT, control_contained);\n\n\t\t\tEZ_button_SetTextAlignment(slow_button, middle_center);\n\t\t\tEZ_button_SetText(slow_button, \" <<\");\n\t\t\tEZ_control_SetAnchor((ez_control_t *)slow_button, anchor_top);\n\t\t\tEZ_control_AddOnMouseClick((ez_control_t *)slow_button, DemoControls_SpeedButton_OnMouseClick, NULL);\n\t\t}\n\n\t\t// Play button.\n\t\t{\n\t\t\tplay_button = EZ_button_Create(&democontrol_tree, button_container,\n\t\t\t\t\"Play button\", \"Starts demo playback or resets it to normal speed\", \n\t\t\t\t(DEMO_BUTTON_WIDTH + DEMO_BUTTON_GAP), 0, \n\t\t\t\tDEMO_BUTTON_WIDTH, DEMO_BUTTON_HEIGHT, control_contained);\n\n\t\t\tEZ_button_SetTextAlignment(play_button, middle_center);\n\t\t\tEZ_button_SetText(play_button, \" >\");\n\t\t\tEZ_control_SetAnchor((ez_control_t *)play_button, anchor_top);\n\t\t\tEZ_control_AddOnMouseClick((ez_control_t *)play_button, DemoControls_PlayButton_OnMouseClick, NULL);\n\t\t}\n\n\t\t// Pause button.\n\t\t{\n\t\t\tpause_button = EZ_button_Create(&democontrol_tree, button_container,\n\t\t\t\t\"Pause button\", \"Pauses demo playback\", \n\t\t\t\t2 * (DEMO_BUTTON_WIDTH + DEMO_BUTTON_GAP), 0, \n\t\t\t\tDEMO_BUTTON_WIDTH, DEMO_BUTTON_HEIGHT, control_contained);\n\n\t\t\tEZ_button_SetTextAlignment(pause_button, middle_center);\n\t\t\tEZ_button_SetText(pause_button, \" ||\");\n\t\t\tEZ_control_SetAnchor((ez_control_t *)pause_button, anchor_top);\n\t\t\tEZ_control_AddOnMouseClick((ez_control_t *)pause_button, DemoControls_PauseButton_OnMouseClick, NULL);\n\t\t}\n\n\t\t// Stop button.\n\t\t{\n\t\t\tstop_button = EZ_button_Create(&democontrol_tree, button_container,\n\t\t\t\t\"Stop button\", \"Stop demo playback\", \n\t\t\t\t3 * (DEMO_BUTTON_WIDTH + DEMO_BUTTON_GAP), 0, \n\t\t\t\tDEMO_BUTTON_WIDTH, DEMO_BUTTON_HEIGHT, control_contained);\n\n\t\t\tEZ_button_SetTextAlignment(stop_button, middle_center);\n\t\t\tEZ_button_SetText(stop_button, \" []\");\n\t\t\tEZ_control_SetAnchor((ez_control_t *)stop_button, anchor_top);\n\t\t\tEZ_control_AddOnMouseClick((ez_control_t *)stop_button, DemoControls_StopButton_OnMouseClick, NULL);\n\t\t}\n\n\t\t// Faster button.\n\t\t{\n\t\t\tfast_button = EZ_button_Create(&democontrol_tree, button_container,\n\t\t\t\t\"Faster button\", \"Speeds up demo playback\", \n\t\t\t\t4 * (DEMO_BUTTON_WIDTH + DEMO_BUTTON_GAP), 0, 24, 16, control_contained);\n\n\t\t\tEZ_button_SetTextAlignment(fast_button, middle_center);\n\t\t\tEZ_button_SetText(fast_button, \" >>\");\n\t\t\tEZ_control_SetAnchor((ez_control_t *)fast_button, anchor_top);\n\t\t\tEZ_control_AddOnMouseClick((ez_control_t *)fast_button, DemoControls_SpeedButton_OnMouseClick, NULL);\n\t\t}\n\t}\n\n\t// Window.\n\t{\n\t\twindow = EZ_window_Create(&democontrol_tree, root, \n\t\t\t\"Demo controls window\", NULL, 50, (vid.conheight - 100), 250, 100, control_resize_h | control_resize_v);\n\t\tEZ_control_SetBackgroundColor((ez_control_t *)window, 0, 0, 0, 150);\n\n\t\tEZ_control_AddOnMouseEnter((ez_control_t *)window, DemoControls_Window_OnMouseEnter, NULL);\n\t\tEZ_control_AddOnMouseLeave((ez_control_t *)window, DemoControls_Window_OnMouseLeave, NULL);\n\n\t\t// Add our precious children :D\t\t\n\t\tEZ_window_AddChild(window, (ez_control_t *)demo_slider);\n\t\tEZ_window_AddChild(window, (ez_control_t *)timelabel);\n\t\tEZ_window_AddChild(window, (ez_control_t *)speed_title_label);\n\t\tEZ_window_AddChild(window, (ez_control_t *)speed_label);\n\t\tEZ_window_AddChild(window, (ez_control_t *)button_container);\n\n\t\t// Size the children to fit within the window properly.\n\t\tEZ_control_SetSize((ez_control_t *)demo_slider, (window->window_area->virtual_width - 64), 8);\n\t\tEZ_control_SetSize((ez_control_t *)button_container, (window->window_area->virtual_width - 20), button_container->height);\n\n\t\t// Enable the window close button.\n\t\tEZ_control_AddOnMouseClick((ez_control_t *)window->close_button, DemoControls_Window_OnCloseClick, NULL);\n\t}\n\n\t// Make sure all the controls are properly position and sized.\n\tEZ_tree_Refresh(&democontrol_tree);\n\n\tkey_dest = key_demo_controls;\n}\n\nqbool DemoControls_MouseEvent(mouse_state_t *ms)\n{\n\treturn EZ_tree_MouseEvent(&democontrol_tree, ms);\n}\n\nstatic void DemoControls_Toggle(void)\n{\n\tdemocontrols_on = !democontrols_on;\n\n\tif (democontrols_on)\n\t{\n\t\tDemoControls_Init();\n\t}\n\telse\n\t{\n\t\tkey_dest = key_game;\n\t\tkey_dest_beforecon = key_game;\n\t\tDemoControls_Destroy();\n\t}\n}\n\nqbool DemoControls_KeyEvent(int key, int unichar, qbool down)\n{\n\tswitch (key)\n\t{\n\t\tcase K_ESCAPE :\n\t\t\tDemoControls_Destroy();\n\t\t\treturn true;\n\t}\n\n\treturn EZ_tree_KeyEvent(&democontrol_tree, key, unichar, down);\n}\n\nvoid DemoControls_f(void)\n{\n\tif (!cls.demoplayback && !democontrols_on)\n\t{\n\t\tCom_Printf(\"Error: not playing a demo\\n\");\n\t\treturn;\n\t}\n\n\tDemoControls_Toggle();\n}\n\n"
  },
  {
    "path": "src/demo_controls.h",
    "content": "\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: demo_controls.c,v 1.78 2007/10/27 14:51:15 cokeman1982 Exp $\n*/\n\nvoid DemoControls_Draw(void);\nqbool DemoControls_MouseEvent(mouse_state_t *ms);\nqbool DemoControls_KeyEvent(int key, int unichar, qbool down);\nvoid DemoControls_f(void);\n\n\n"
  },
  {
    "path": "src/demo_spawnwarn.c",
    "content": "/*\n * Copyright (C) 2026 Oscar Linderholm <osm@recv.se>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n */\n\n#include \"quakedef.h\"\n#include \"demo_spawnwarn.h\"\n\n#define MAX_DEMO_SPAWNWARN_POINTS 128\n\nstatic const double demo_spawnwarn_ignore_duration = 1.0;\nstatic const vec3_t demo_spawnwarn_touch_mins = { -16, -16, -24 };\nstatic const vec3_t demo_spawnwarn_touch_maxs = { 16, 16, 32 };\n\ntypedef struct\n{\n\tchar classname[MAX_COM_TOKEN];\n\tchar target[MAX_COM_TOKEN];\n\tchar targetname[MAX_COM_TOKEN];\n\tvec3_t origin;\n\tqbool has_origin;\n\tqbool has_target;\n\tqbool has_targetname;\n} demo_spawnwarn_entity_t;\n\ntypedef struct\n{\n\tchar targetname[MAX_COM_TOKEN];\n\tvec3_t origin;\n} demo_spawnwarn_teleport_destination_t;\n\ntypedef struct\n{\n\tchar target[MAX_COM_TOKEN];\n} demo_spawnwarn_teleport_trigger_t;\n\nstatic vec3_t cl_spawnwarn_points[MAX_DEMO_SPAWNWARN_POINTS];\nstatic int cl_num_spawnwarn_points;\nstatic qbool cl_spawnwarn_active;\nstatic double cl_spawnwarn_warning_suppressed_until;\nstatic qbool cl_spawnwarn_point_overflow_warned;\n\ncvar_t demo_spawnwarn = { \"demo_spawnwarn\", \"0\" };\ncvar_t demo_spawnwarn_text = {\n\t\"demo_spawnwarn_text\",\n\t\"you crossed a spawn\"\n};\n\nextern temp_entity_list_t temp_entities;\n\nstatic void CL_SpawnWarn_ResetState(void)\n{\n\tmemset(cl_spawnwarn_points, 0, sizeof(cl_spawnwarn_points));\n\tcl_num_spawnwarn_points = 0;\n\tcl_spawnwarn_active = false;\n\tcl_spawnwarn_warning_suppressed_until = 0;\n\tcl_spawnwarn_point_overflow_warned = false;\n}\n\nstatic qbool CL_SpawnWarn_ParseEntityOrigin(const char *value, vec3_t origin)\n{\n\treturn sscanf(value, \"%f %f %f\", &origin[0], &origin[1], &origin[2]) == 3;\n}\n\nstatic void CL_SpawnWarn_WarnOverflow(void)\n{\n\tif (!cl_spawnwarn_point_overflow_warned)\n\t{\n\t\tCon_Printf(\"demo_spawnwarn: too many points on map, limit is %d\\n\",\n\t\t\tMAX_DEMO_SPAWNWARN_POINTS);\n\t\tcl_spawnwarn_point_overflow_warned = true;\n\t}\n}\n\nstatic void CL_SpawnWarn_AddPoint(const vec3_t origin)\n{\n\tint point_index;\n\n\tif (cl_num_spawnwarn_points >= MAX_DEMO_SPAWNWARN_POINTS)\n\t{\n\t\tCL_SpawnWarn_WarnOverflow();\n\t\treturn;\n\t}\n\n\tpoint_index = cl_num_spawnwarn_points++;\n\tVectorCopy(origin, cl_spawnwarn_points[point_index]);\n}\n\nstatic void CL_SpawnWarn_ClearEntity(demo_spawnwarn_entity_t *entity)\n{\n\tmemset(entity, 0, sizeof(*entity));\n}\n\nstatic void CL_SpawnWarn_AddTeleportTrigger(\n\tconst char *target,\n\tdemo_spawnwarn_teleport_trigger_t triggers[],\n\tint *num_triggers)\n{\n\tint trigger_index;\n\n\tif (*num_triggers >= MAX_DEMO_SPAWNWARN_POINTS)\n\t{\n\t\tCL_SpawnWarn_WarnOverflow();\n\t\treturn;\n\t}\n\n\ttrigger_index = *num_triggers;\n\tstrlcpy(triggers[trigger_index].target,\n\t\ttarget,\n\t\tsizeof(triggers[trigger_index].target)\n\t);\n\t*num_triggers = trigger_index + 1;\n}\n\nstatic void CL_SpawnWarn_AddTeleportDestination(\n\tconst char *targetname,\n\tconst vec3_t origin,\n\tdemo_spawnwarn_teleport_destination_t destinations[],\n\tint *num_destinations)\n{\n\tint destination_index;\n\n\tif (*num_destinations >= MAX_DEMO_SPAWNWARN_POINTS)\n\t{\n\t\tCL_SpawnWarn_WarnOverflow();\n\t\treturn;\n\t}\n\n\tdestination_index = *num_destinations;\n\tstrlcpy(destinations[destination_index].targetname,\n\t\ttargetname,\n\t\tsizeof(destinations[destination_index].targetname));\n\tVectorCopy(origin, destinations[destination_index].origin);\n\t*num_destinations = destination_index + 1;\n}\n\nstatic void CL_SpawnWarn_CollectPointData(\n\tconst demo_spawnwarn_entity_t *entity,\n\tdemo_spawnwarn_teleport_trigger_t triggers[],\n\tint *num_triggers,\n\tdemo_spawnwarn_teleport_destination_t destinations[],\n\tint *num_destinations)\n{\n\tif (!strcmp(entity->classname, \"info_player_deathmatch\") && entity->has_origin)\n\t{\n\t\tCL_SpawnWarn_AddPoint(entity->origin);\n\t}\n\telse if (!strcmp(entity->classname, \"trigger_teleport\")\n\t\t&& entity->has_target && !entity->has_targetname)\n\t{\n\t\t// Store the trigger target so we can resolve the exit entity\n\t\t// later.\n\t\tCL_SpawnWarn_AddTeleportTrigger(entity->target, triggers, num_triggers);\n\t}\n\telse if (entity->has_targetname && entity->has_origin)\n\t{\n\t\t// Store possible teleport destinations; matching targetname\n\t\t// becomes the exit.\n\t\tCL_SpawnWarn_AddTeleportDestination(entity->targetname,\n\t\t\tentity->origin,\n\t\t\tdestinations,\n\t\t\tnum_destinations);\n\t}\n}\n\nstatic void CL_SpawnWarn_ResolveTeleportExitPoints(\n\tconst demo_spawnwarn_teleport_trigger_t triggers[],\n\tint num_triggers,\n\tconst demo_spawnwarn_teleport_destination_t destinations[],\n\tint num_destinations)\n{\n\tint i;\n\n\tfor (i = 0; i < num_triggers; i++)\n\t{\n\t\tint j;\n\n\t\tfor (j = 0; j < num_destinations; j++)\n\t\t{\n\t\t\tif (!strcmp(triggers[i].target, destinations[j].targetname))\n\t\t\t{\n\t\t\t\tCL_SpawnWarn_AddPoint(destinations[j].origin);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic qbool CL_SpawnWarn_ParseEntityBlock(\n\tconst char **data_p,\n\tdemo_spawnwarn_entity_t *entity)\n{\n\tchar key[MAX_COM_TOKEN];\n\tconst char *data = *data_p;\n\n\tCL_SpawnWarn_ClearEntity(entity);\n\n\twhile (1)\n\t{\n\t\tdata = COM_Parse(data);\n\t\tif (!data)\n\t\t{\n\t\t\tCon_DPrintf(\n\t\t\t\t\"CL_SpawnWarn_ParseEntityBlock: unexpected end of entity key\\n\");\n\t\t\treturn false;\n\t\t}\n\n\t\tif (com_token[0] == '}')\n\t\t{\n\t\t\t*data_p = data;\n\t\t\treturn true;\n\t\t}\n\n\t\tstrlcpy(key, com_token, sizeof(key));\n\n\t\tdata = COM_Parse(data);\n\t\tif (!data)\n\t\t{\n\t\t\tCon_DPrintf(\n\t\t\t\t\"CL_SpawnWarn_ParseEntityBlock: unexpected end of entity value\\n\");\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!strcmp(key, \"classname\"))\n\t\t{\n\t\t\tstrlcpy(entity->classname, com_token, sizeof(entity->classname));\n\t\t}\n\t\telse if (!strcmp(key, \"origin\"))\n\t\t{\n\t\t\tentity->has_origin = CL_SpawnWarn_ParseEntityOrigin(com_token,\n\t\t\t\tentity->origin);\n\t\t}\n\t\telse if (!strcmp(key, \"target\"))\n\t\t{\n\t\t\tentity->has_target = true;\n\t\t\tstrlcpy(entity->target, com_token, sizeof(entity->target));\n\t\t}\n\t\telse if (!strcmp(key, \"targetname\"))\n\t\t{\n\t\t\tentity->has_targetname = true;\n\t\t\tstrlcpy(entity->targetname, com_token, sizeof(entity->targetname));\n\t\t}\n\t}\n}\n\nstatic qbool CL_SpawnWarn_ParseEntityLump(const char *entstring)\n{\n\tdemo_spawnwarn_teleport_destination_t teleport_destinations[MAX_DEMO_SPAWNWARN_POINTS];\n\tdemo_spawnwarn_teleport_trigger_t teleport_triggers[MAX_DEMO_SPAWNWARN_POINTS];\n\tconst char *data;\n\tint num_teleport_destinations = 0;\n\tint num_teleport_triggers = 0;\n\n\tif (!entstring || !entstring[0])\n\t{\n\t\treturn true;\n\t}\n\n\tdata = entstring;\n\twhile ((data = COM_Parse(data)) != NULL)\n\t{\n\t\tdemo_spawnwarn_entity_t entity;\n\n\t\tif (com_token[0] != '{')\n\t\t{\n\t\t\tCon_DPrintf(\"CL_SpawnWarn_ParseEntityLump: found %s when expecting {\\n\",\n\t\t\t\tcom_token);\n\t\t\tCL_SpawnWarn_ResetState();\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!CL_SpawnWarn_ParseEntityBlock(&data, &entity))\n\t\t{\n\t\t\tCL_SpawnWarn_ResetState();\n\t\t\treturn false;\n\t\t}\n\n\t\tCL_SpawnWarn_CollectPointData(&entity,\n\t\t\tteleport_triggers,\n\t\t\t&num_teleport_triggers,\n\t\t\tteleport_destinations,\n\t\t\t&num_teleport_destinations);\n\t}\n\n\tCL_SpawnWarn_ResolveTeleportExitPoints(teleport_triggers,\n\t\tnum_teleport_triggers,\n\t\tteleport_destinations,\n\t\tnum_teleport_destinations);\n\n\treturn true;\n}\n\nvoid CL_SpawnWarn_LoadPoints(void)\n{\n\tCL_SpawnWarn_ResetState();\n\n\tif (!cls.demoplayback)\n\t{\n\t\treturn;\n\t}\n\n\tCL_SpawnWarn_ParseEntityLump(CM_EntityString());\n}\n\nstatic qbool CL_SpawnWarn_BoxesTouch(\n\tconst vec3_t absmin1,\n\tconst vec3_t absmax1,\n\tconst vec3_t absmin2,\n\tconst vec3_t absmax2)\n{\n\treturn !(absmin1[0] > absmax2[0] || absmin1[1] > absmax2[1]\n\t\t|| absmin1[2] > absmax2[2]\n\t\t|| absmax1[0] < absmin2[0] || absmax1[1] < absmin2[1]\n\t\t|| absmax1[2] < absmin2[2]);\n}\n\nstatic void CL_SpawnWarn_CheckTeleportSuppression(double current_time)\n{\n\tint i;\n\n\tfor (i = 0; i < MAX_TEMP_ENTITIES; i++)\n\t{\n\t\ttemp_entity_t *temp = &temp_entities.list[i];\n\t\tvec3_t teleport_absmin, teleport_absmax, player_absmin, player_absmax;\n\n\t\tif (temp->type != TE_TELEPORT\n\t\t\t|| current_time - temp->time > demo_spawnwarn_ignore_duration)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tVectorAdd(temp->pos, demo_spawnwarn_touch_mins, teleport_absmin);\n\t\tVectorAdd(temp->pos, demo_spawnwarn_touch_maxs, teleport_absmax);\n\t\tVectorAdd(cl.simorg, demo_spawnwarn_touch_mins, player_absmin);\n\t\tVectorAdd(cl.simorg, demo_spawnwarn_touch_maxs, player_absmax);\n\n\t\tif (CL_SpawnWarn_BoxesTouch(teleport_absmin,\n\t\t\tteleport_absmax,\n\t\t\tplayer_absmin,\n\t\t\tplayer_absmax))\n\t\t{\n\t\t\tCL_SpawnWarn_SuppressAfterTeleport();\n\t\t}\n\t}\n}\n\nstatic void CL_SpawnWarn_SuppressWarning(void)\n{\n\tif (!cls.demoplayback)\n\t{\n\t\treturn;\n\t}\n\n\tcl_spawnwarn_warning_suppressed_until = max(\n\t\tcl_spawnwarn_warning_suppressed_until,\n\t\tcls.demotime + demo_spawnwarn_ignore_duration);\n}\n\nvoid CL_SpawnWarn_ClearPoints(void)\n{\n\tCL_SpawnWarn_ResetState();\n}\n\nvoid CL_SpawnWarn_SuppressAfterRespawn(void)\n{\n\tCL_SpawnWarn_SuppressWarning();\n}\n\nvoid CL_SpawnWarn_SuppressAfterTeleport(void)\n{\n\tCL_SpawnWarn_SuppressWarning();\n}\n\nvoid CL_SpawnWarn_UpdateWarning(void)\n{\n\tqbool any_spawn_triggered = false;\n\tdouble current_time = cls.demotime;\n\tint i;\n\n\tif (!cls.demoplayback)\n\t{\n\t\treturn;\n\t}\n\n\tif (!demo_spawnwarn.integer)\n\t{\n\t\treturn;\n\t}\n\n\tif (cl.stats[STAT_HEALTH] <= 0)\n\t{\n\t\treturn;\n\t}\n\n\tCL_SpawnWarn_CheckTeleportSuppression(current_time);\n\n\tif (current_time < cl_spawnwarn_warning_suppressed_until)\n\t{\n\t\tcl_spawnwarn_active = false;\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < cl_num_spawnwarn_points; i++)\n\t{\n\t\tvec3_t spawn_absmin, spawn_absmax, player_absmin, player_absmax;\n\n\t\tVectorAdd(cl_spawnwarn_points[i], demo_spawnwarn_touch_mins, spawn_absmin);\n\t\tVectorAdd(cl_spawnwarn_points[i], demo_spawnwarn_touch_maxs, spawn_absmax);\n\t\tVectorAdd(cl.simorg, demo_spawnwarn_touch_mins, player_absmin);\n\t\tVectorAdd(cl.simorg, demo_spawnwarn_touch_maxs, player_absmax);\n\n\t\tif (CL_SpawnWarn_BoxesTouch(spawn_absmin,\n\t\t\tspawn_absmax,\n\t\t\tplayer_absmin,\n\t\t\tplayer_absmax))\n\t\t{\n\t\t\tany_spawn_triggered = true;\n\t\t}\n\t}\n\n\tif (any_spawn_triggered && !cl_spawnwarn_active)\n\t{\n\t\tSCR_CenterPrint(demo_spawnwarn_text.string);\n\t\tcl_spawnwarn_active = true;\n\t}\n\telse if (!any_spawn_triggered)\n\t{\n\t\tcl_spawnwarn_active = false;\n\t}\n}\n"
  },
  {
    "path": "src/demo_spawnwarn.h",
    "content": "/*\n * Copyright (C) 2026 Oscar Linderholm <osm@recv.se>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n */\n\n#ifndef EZQUAKE_DEMO_SPAWNWARN_H\n#define EZQUAKE_DEMO_SPAWNWARN_H\n\n#include \"cvar.h\"\n\nextern cvar_t demo_spawnwarn;\nextern cvar_t demo_spawnwarn_text;\n\nvoid CL_SpawnWarn_ClearPoints(void);\nvoid CL_SpawnWarn_LoadPoints(void);\nvoid CL_SpawnWarn_UpdateWarning(void);\nvoid CL_SpawnWarn_SuppressAfterRespawn(void);\nvoid CL_SpawnWarn_SuppressAfterTeleport(void);\n\n#endif\n"
  },
  {
    "path": "src/document_rendering.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n\n\n// document constants\n#define DOCUMENT_INDENT     2\n\n\n#define LINE_MAX_LEN    512\n\n#include \"expat.h\"\n#include \"xsd.h\"\n#include \"document_rendering.h\"\n\ntypedef struct document_rendering_context_s\n{\n    // current buffer status\n    int     line;\n    int     line_pos;\n    byte    *buf;\n    int     nocopy;\n    int     nometadata;\n    int     width;\n\n    // link table\n    document_rendered_link_t *links;\n    document_rendered_link_t *inline_links; // current inline links\n\n    // section table\n    document_rendered_section_t *sections;\n    document_rendered_section_t *current_section;\n\n    // where last blank line was added\n    int     last_blank;\n\n    // visual settings\n    int     l_margin;\n    int     r_margin;\n\n    // shared line buffer\n    char line_buf[LINE_MAX_LEN];\n\n    // some state markers\n    int     list_count;\n    document_align_t align;\n    int     do_color;\n}\ndocument_rendering_context_t;\n\n\nchar * Add_Inline_Tag(document_rendering_context_t *cx, char *text, document_tag_t *tag);\nchar *Add_Inline_String(char *text, char *string);\nstatic void RenderBlockChain(document_rendering_context_t *cx, document_tag_t *tag);\n\n\n\n\n\n// check if given tag is a block rather than inline\nstatic int IsBlockElement(document_tag_t *tag)\n{\n    switch (tag->type)\n    {\n    case tag_section:\n    case tag_p:\n    case tag_list:\n    case tag_dict:\n    case tag_hr:\n    case tag_pre:\n    case tag_h:\n        return 1;\n\n    default:\n        return 0;\n    }\n}\n\n// finish current line and add it to the buffer\nstatic void LineFeed(document_rendering_context_t *cx)\n{\n    if (cx->line_pos != cx->l_margin)\n    {\n        // check alignment\n        if (cx->align != align_left)\n        {\n            int l, r;\n            int offset;\n\n            l = cx->l_margin;\n            r = cx->width - cx->r_margin;\n            while ((cx->line_buf[r-1] == 0  ||  cx->line_buf[r-1] == ' ')  &&\n                r > l)\n                r--;\n\n            offset = 0;\n            if (cx->align == align_right)\n            {\n                offset = cx->width-cx->r_margin-r;\n            }\n            if (cx->align == align_center)\n            {\n                offset = (cx->width-cx->r_margin-cx->l_margin - (r - l)) / 2;\n            }\n\n            if (offset)\n            {\n                document_rendered_link_t *l;\n\n                memmove(cx->line_buf+offset, cx->line_buf, r);\n                memset(cx->line_buf, 0, offset);\n\n                // change links\n                l = cx->inline_links;\n                while (l)\n                {\n                    if (l->start >= cx->line*cx->width  &&  l->start < (cx->line+1)*cx->width)\n                        l->start += offset;\n                    if (l->end >= cx->line*cx->width  &&  l->end < (cx->line+1)*cx->width)\n                        l->end += offset;\n\n                    l = l->next;\n                }\n            }\n        }\n\n        // copy to buffer\n        if (!cx->nocopy)\n            memcpy(cx->buf + cx->line*cx->width, cx->line_buf, cx->width);\n\n        // manage state variables\n        cx->line ++;\n        memset(cx->line_buf, 0, LINE_MAX_LEN);\n        cx->line_pos = cx->l_margin;\n    }\n}\n\n// add blank line, checking if there aren't any already\nstatic void AddBlankLine(document_rendering_context_t *cx)\n{\n//    int i;\n\n    // do not add blank as the first line\n    if (cx->line == 0)\n        goto skip;\n\n    // check if previous line is not blank\n    if (cx->line-1 == cx->last_blank)\n        goto skip;\n\n    // add line\n    memset(cx->line_buf, 0, LINE_MAX_LEN);\n    cx->last_blank = cx->line;\n    cx->line ++;\n\nskip:\n\n    cx->line_pos = cx->l_margin;\n}\n\n// add link to the inline link table\nstatic void AddInlineLink(document_rendering_context_t *cx, document_rendered_link_t *link)\n{\n    if (cx->nometadata)\n    {\n        Q_free(link);\n    }\n    else\n    {\n        document_rendered_link_t *last;\n\n        last = cx->inline_links;\n\n        if (last == NULL)\n            cx->inline_links = link;\n        else\n        {\n            while (last->next)\n                last = last->next;\n            last->next = link;\n        }\n    }\n}\n\n// strip spaces from inline text, making links working\nchar * StripInlineSpaces(char *str, document_rendered_link_t *links)\n{\n    int cut = 0;\n\n    char *buf, *ret;\n    int p=0, q=0;\n\n    if (str == NULL)\n        return str;\n\n    buf = (char *) Q_malloc(strlen(str)+1);\n    for (p=0; p < strlen(str); p++)\n    {\n        // offset links\n        document_rendered_link_t *l = links;\n        while (l)\n        {\n            if (l->start == p)\n                l->start -= cut;\n            if (l->end == p)\n                l->end -= cut;\n\n            l = l->next;\n        }\n\n        if (XSD_IsSpace(str[p]))\n        {\n            if (q == 0  ||  XSD_IsSpace(buf[q-1]))\n                cut++;\n            else\n            {\n                // add this char, but replace with pure space\n                buf[q++] = ' ';\n            }\n        }\n        else\n            buf[q++] = str[p];\n    }\n\n    // strip spaces from the end\n    while (q > 0  &&  XSD_IsSpace(buf[q-1]))\n        q--;\n    buf[q] = 0;\n\n\tret = (char *) Q_strdup(buf);\n    Q_free(buf);\n    Q_free(str);\n    return ret;\n}\n\nstatic char ChangeColor(char c)\n{\n    char low = c & 127;\n\n    if (low > ' '  &&  low <= '~')\n        return c ^ 128;\n\n    return c;\n}\n\nstatic void Render_String(document_rendering_context_t *cx, char *text)\n{\n    int l;\n    char c;\n    document_rendered_link_t *link;\n\n    char *txt = text;\n    int t_width = cx->width - cx->l_margin - cx->r_margin;\n\n    if (t_width < 8)    // witdth of text\n        return;\n\n    if (txt == NULL)\n        return;\n\n    // make links negative\n    link = cx->inline_links;\n    while (link)\n    {\n        link->start *= -1;\n        link->end *= -1;\n        link = link->next;\n    }\n\n    while ((c = *txt))\n    {\n        // count word length\n        for (l=0 ; l < t_width; l++)\n            if (isspace2(txt[l]) || txt[l]=='\\n'+(char)128  ||  !txt[l])\n                break;\n\n        // word wrap\n        if (l != t_width && (cx->line_pos + l > cx->width - cx->r_margin) )\n            LineFeed(cx);\n\n        // recalculate links indexes to absolute\n        link = cx->inline_links;\n        while (link)\n        {\n            int i = txt - text;\n\n            if (link->start <= 0  &&  i >= -link->start)\n                link->start = cx->line*cx->width + cx->line_pos;\n            if (link->end <= 0  &&  i >= -link->end)\n                link->end = cx->line*cx->width + cx->line_pos;\n            link = link->next;\n        }\n        \n        txt++;\n\n        switch (c)\n        {\n        case (char)('\\n' | 128):\n            LineFeed(cx);\n            break;\n\n        case (char)(' ' | 128):\n            cx->line_buf[cx->line_pos++] = ' ';\n            break;\n\n        default:\n            cx->line_buf[cx->line_pos++] = c;\n        }\n\n        if (cx->line_pos >= cx->width - cx->r_margin)\n        {\n            LineFeed(cx);\n\n            // we linefeed because of no space, so skip spaces which are next\n            while (isspace2(*txt))\n                txt++;\n        }\n    }\n    LineFeed(cx);\n\n    // add inline links to document links\n    if (cx->links == NULL)\n        cx->links = cx->inline_links;\n    else\n    {\n        link = cx->links;\n        while (link->next)\n            link = link->next;\n        link->next = cx->inline_links;\n    }\n    cx->inline_links = NULL;\n}\n\n// render P tag\nstatic void Render_P(document_rendering_context_t *cx, document_tag_p_t *p)\n{\n    document_align_t old_align = cx->align;\n    cx->align = p->align;\n\n    if (p->indent)\n    {\n        cx->l_margin += DOCUMENT_INDENT;\n        AddBlankLine(cx);\n    }\n\n    RenderBlockChain(cx, p->tags);\n\n    if (p->indent)\n    {\n        cx->l_margin -= DOCUMENT_INDENT;\n        AddBlankLine(cx);\n    }\n\n    cx->align = old_align;\n}\n\n// render Section tag\nstatic void Render_Section(document_rendering_context_t *cx, document_tag_section_t *p)\n{\n    if (p->indent)\n    {\n        cx->l_margin += DOCUMENT_INDENT;\n        AddBlankLine(cx);\n    }\n\n    RenderBlockChain(cx, p->tags);\n\n    if (p->indent)\n    {\n        cx->l_margin -= DOCUMENT_INDENT;\n        AddBlankLine(cx);\n    }\n}\n\n// render H tag\nstatic void Render_H(document_rendering_context_t *cx, document_tag_h_t *h)\n{\n    document_align_t old_align;\n    int old_do_color;\n    \n    old_align = cx->align;\n    cx->align = h->align;\n\n    old_do_color = cx->do_color;\n    cx->do_color = 1;\n\n    RenderBlockChain(cx, h->tags);\n\n    cx->do_color = old_do_color;\n    cx->align = old_align;\n}\n\n// render PRE tag\nstatic void Render_Pre(document_rendering_context_t *cx, document_tag_pre_t *pre)\n{\n    int     l_width, width;\n    char    *text, *s, *d;\n    \n    if (!pre->text)\n        return;\n\n    // duplicate text and calculate width\n    width = l_width = 0;\n    s = pre->text;\n    d = text = (char *) Q_malloc(strlen(s)+1);\n    while (*s)\n    {\n        char c = *s++;\n\n        // skip LF and TAB\n        if (c == '\\r'  ||  c == '\\t')\n            continue;\n\n        // copy character to output\n        *d++ = c=='\\n' ? (char)('\\n' + 128) : c;\n\n        // check line width\n        if (c == '\\n')\n        {\n            if (l_width > width)\n                width = l_width;\n            l_width = 0;\n        }\n        else\n        {\n            l_width++;\n        }\n    }\n    *d = 0;\n\n    // check if our render context width allow for such image\n    if (width <= cx->width - cx->l_margin - cx->r_margin - 1)\n    {\n        // render inline\n\n        document_align_t align;\n        int offset;\n\n        // remporarily disable alignment, because we use our own\n        align = cx->align;\n        cx->align = align_left;\n\n        // calculate alignment offset\n        switch (align)\n        {\n        default:\n        case align_left:\n            offset = 0;\n            break;\n        case align_right:\n            offset = cx->width - cx->l_margin - cx->r_margin - width - 1;\n            break;\n        case align_center:\n            offset = (cx->width - cx->l_margin - cx->r_margin - width - 1) / 2;\n            break;\n        }\n\n        cx->l_margin += offset;\n        AddBlankLine(cx);\n        Render_String(cx, text);\n\n        cx->l_margin -= offset;\n        AddBlankLine(cx);\n\n        cx->align = align;\n    }\n    else\n    {\n        // render as link\n\n        char *t = Q_strdup(\"\");\n\n        t = Add_Inline_String(t, \"\\x90\");\n        t = Add_Inline_String(t, pre->alt ? pre->alt : \"picture\");\n        t = Add_Inline_String(t, \"\\x91\");\n\n        Render_String(cx, t);\n\n        Q_free(t);\n    }\n\n    Q_free(text);\n}\n\n// render HR tag\nstatic void Render_Hr(document_rendering_context_t *cx, document_tag_hr_t *hr)\n{\n    int i;\n\n    document_align_t old_align = cx->align;\n    cx->align = align_center;\n\n    AddBlankLine(cx);\n\n    cx->line_buf[cx->line_pos++] = 29;\n    for (i=0; i < (cx->width - cx->l_margin - cx->r_margin) / 2; i++)\n        cx->line_buf[cx->line_pos++] = 30;\n    cx->line_buf[cx->line_pos++] = 31;\n\n    LineFeed(cx);\n\n    AddBlankLine(cx);\n\n    cx->align = old_align;\n}\n\n// render LIST tag\nstatic void Render_List(document_rendering_context_t *cx, document_tag_list_t *list)\n{\n    char separator[128];\n    int indent;\n    document_tag_li_t *item;\n    int item_num;\n    int num_width = 0;\n\n    // first check, how much indentation we should use\n    indent = 2;\n    if (list->separator != list_separator_none)\n        indent++;\n    switch (list->bullet)\n    {\n    case list_bullet_none:\n        break;\n\n    case list_bullet_dot:\n    case list_bullet_letter:\n    case list_bullet_bigletter:\n        indent++;\n        break;\n\n    case list_bullet_number:\n        {\n            // count list size\n            item_num = 0;\n            item = list->items;\n            while (item)\n            {\n                item_num++;\n                item = (document_tag_li_t *) item->next;\n            }\n            if (item_num == 0)\n                break;\n            num_width = log10(item_num) + 1;\n            indent += num_width;\n        }\n    }\n\n    cx->list_count++;\n    cx->l_margin += indent;\n    AddBlankLine(cx);\n\n    item = list->items;\n    item_num = 1;\n\n    while (item)\n    {\n        // make separator\n        strlcpy (separator, \" \", sizeof (separator));\n        switch (list->bullet)\n        {\n        case list_bullet_none:\n            break;\n        case list_bullet_dot:\n            strlcat (separator, va(\"%c\", (cx->list_count%2) ? 143 : 15), sizeof (separator));\n            break;\n        case list_bullet_letter:\n            strlcat(separator, va(\"%c\", (item_num % ('z'-'a'+1)) + 'a'), sizeof (separator));\n            break;\n        case list_bullet_bigletter:\n            strlcat(separator, va(\"%c\", (item_num % ('Z'-'A'+1)) + 'A'), sizeof (separator));\n            break;\n        case list_bullet_number:\n            strlcat(separator, va(\"%*d\", num_width, item_num), sizeof (separator));\n            break;\n        }\n        switch (list->separator)\n        {\n        case list_separator_none:\n            break;\n        case list_separator_dot:\n            strlcat(separator, \".\", sizeof (separator));\n            break;\n        case list_separator_par:\n            strlcat(separator, \")\", sizeof (separator));\n            break;\n        }\n        strlcat(separator, \" \", sizeof (separator));\n\n        memcpy(cx->line_buf + (cx->l_margin - indent), separator, strlen(separator));\n        RenderBlockChain(cx, item->tags);\n\n        item_num++;\n        item = (document_tag_li_t *) item->next;\n    }\n\n    cx->l_margin -= indent;\n    AddBlankLine(cx);\n    cx->list_count--;\n}\n\n// render LIST tag\nstatic void Render_Dict(document_rendering_context_t *cx, document_tag_dict_t *dict)\n{\n    char separator[128];\n    int indent;\n    document_tag_di_t *item;\n    int item_num;\n    int num_width = 0;\n\n    // first check, how much indentation we should use\n    indent = 2;\n    if (dict->separator != list_separator_none)\n        indent++;\n    switch (dict->bullet)\n    {\n    case list_bullet_none:\n        break;\n\n    case list_bullet_dot:\n    case list_bullet_letter:\n    case list_bullet_bigletter:\n        indent++;\n        break;\n\n    case list_bullet_number:\n        {\n            // count dict size\n            item_num = 0;\n            item = dict->items;\n            while (item)\n            {\n                item_num++;\n                item = (document_tag_di_t *) item->next;\n            }\n            if (item_num == 0)\n                break;\n            num_width = log10(item_num) + 1;\n            indent += num_width;\n        }\n    }\n\n    cx->list_count++;\n    cx->l_margin += indent;\n    AddBlankLine(cx);\n\n    item = dict->items;\n    item_num = 1;\n\n    while (item)\n    {\n        int old_do_color;\n\n        // make separator\n        strlcpy(separator, \" \", sizeof (separator));\n        switch (dict->bullet)\n        {\n        case list_bullet_none:\n            break;\n        case list_bullet_dot:\n            strlcat(separator, va(\"%c\", (cx->list_count%2) ? 143 : 15), sizeof (separator));\n            break;\n        case list_bullet_letter:\n            strlcat(separator, va(\"%c\", (item_num % ('z'-'a'+1)) + 'a'), sizeof (separator));\n            break;\n        case list_bullet_bigletter:\n            strlcat(separator, va(\"%c\", (item_num % ('Z'-'A'+1)) + 'A'), sizeof (separator));\n            break;\n        case list_bullet_number:\n            strlcat(separator, va(\"%*d\", num_width, item_num), sizeof (separator));\n            break;\n        }\n        switch (dict->separator)\n        {\n        case list_separator_none:\n            break;\n        case list_separator_dot:\n            strlcat(separator, \".\", sizeof (separator));\n            break;\n        case list_separator_par:\n            strlcat(separator, \")\", sizeof (separator));\n            break;\n        }\n        strlcat(separator, \" \", sizeof (separator));\n\n        memcpy(cx->line_buf + (cx->l_margin - indent), separator, strlen(separator));\n        old_do_color = cx->do_color;\n        cx->do_color = 1;\n        RenderBlockChain(cx, item->name);\n        cx->do_color = old_do_color;\n\n        // render description\n        cx->l_margin += 2;\n        AddBlankLine(cx);\n        RenderBlockChain(cx, item->description);\n        cx->l_margin -= 2;\n        AddBlankLine(cx);\n\n        item_num++;\n        item = (document_tag_di_t *) item->next;\n    }\n\n    cx->l_margin -= indent;\n    AddBlankLine(cx);\n    cx->list_count--;\n}\n\n\n// adds string\nchar *Add_Inline_String (char *text, char *string)\n{\n\tsize_t size = strlen (text) + strlen (string) + 1;\n    char *buf = (char *) Q_malloc (size);\n    strlcpy (buf, text, size);\n    Q_free(text);\n    strlcat (buf, string, size);\n    return buf;\n}\n\n// adds text element\nchar *Add_Inline_Text (document_rendering_context_t *cx, char *text, document_tag_text_t *tag)\n{\n\tchar *s, *d;\n\tchar *buf;\n\tsize_t size;\n\n\tsize = strlen (text) + strlen (tag->text) + 1;\n\tbuf = (char *) Q_malloc (size);\n\tstrlcpy (buf, text, size);\n\tQ_free (text);\n\n\td = buf + strlen(buf);\n\ts = tag->text;\n\n\twhile (*s)\n\t{\n\t\tchar c = *s++;\n\n\t\tif (cx->do_color)\n\t\t\tc = ChangeColor(c);\n\n\t\t*d++ = c;\n\t}\n\n\t*d = 0;\n\n\treturn buf;\n}\n\n// adds EM element\nchar * Add_Inline_Em(document_rendering_context_t *cx, char *text, document_tag_em_t *tag)\n{\n    text = Add_Inline_String(text, \"_\");\n    text = Add_Inline_Tag(cx, text, tag->tags);\n    text = Add_Inline_String(text, \"_\");\n    return text;\n}\n\n// adds COLOR element\nchar * Add_Inline_Color(document_rendering_context_t *cx, char *text, document_tag_color_t *tag)\n{\n    int old_do_color = cx->do_color;\n    cx->do_color = true;\n\n    text = Add_Inline_Tag(cx, text, tag->tags);\n\n    cx->do_color = old_do_color;\n    return text;\n}\n\n// adds A element\nchar * Add_Inline_A(document_rendering_context_t *cx, char *text, document_tag_a_t *tag)\n{\n    // create new link object\n    document_rendered_link_t *link;\n\n    link = (document_rendered_link_t *) Q_malloc(sizeof(document_rendered_link_t));\n    memset(link, 0, sizeof(document_rendered_link_t));\n\n    link->tag = tag;\n    link->start = strlen(text) + 1;\n\n    text = Add_Inline_String(text, \"\\x90\");\n    text = Add_Inline_Tag(cx, text, tag->tags);\n    text = Add_Inline_String(text, \"\\x91\");\n\n    link->end = strlen(text) - 1;\n    AddInlineLink(cx, link);\n\n    return text;\n}\n\n// adds IMG element\nchar * Add_Inline_Img(document_rendering_context_t *cx, char *text, document_tag_img_t *tag)\n{\n    text = Add_Inline_String(text, \"\\x90\");\n    text = Add_Inline_Tag(cx, text, tag->tags);\n    text = Add_Inline_String(text, \"\\x91\");\n    return text;\n}\n\n// adds BR element\nchar * Add_Inline_Br(document_rendering_context_t *cx, char *text, document_tag_br_t *tag)\n{\n    text = Add_Inline_String(text, va(\"%c\", '\\n' | 128));\n    return text;\n}\n\n// adds SP element\nchar * Add_Inline_Sp(document_rendering_context_t *cx, char *text, document_tag_sp_t *tag)\n{\n    text = Add_Inline_String(text, va(\"%c\", ' ' | 128));\n    return text;\n}\n\n// add inline element\nchar * Add_Inline_Tag(document_rendering_context_t *cx, char *text, document_tag_t *tag)\n{\n    if (tag == NULL)\n        return text;\n\n    switch (tag->type)\n    {\n    case tag_text:\n        text = Add_Inline_Text(cx, text, (document_tag_text_t *) tag);\n        break;\n\n    case tag_em:\n        text = Add_Inline_Em(cx, text, (document_tag_em_t *) tag);\n        break;\n\n    case tag_color:\n        text = Add_Inline_Color(cx, text, (document_tag_color_t *) tag);\n        break;\n\n    case tag_a:\n        text = Add_Inline_A(cx, text, (document_tag_a_t *) tag);\n        break;\n\n    case tag_img:\n        text = Add_Inline_Img(cx, text, (document_tag_img_t *) tag);\n        break;\n\n    case tag_br:\n        text = Add_Inline_Br(cx, text, (document_tag_br_t *) tag);\n        break;\n\n    case tag_sp:\n        text = Add_Inline_Sp(cx, text, (document_tag_sp_t *) tag);\n        break;\n\n    case tag_di:\n    case tag_dict:\n    case tag_h:\n    case tag_hr:\n    case tag_li:\n    case tag_list:\n    case tag_p:\n    case tag_pre:\n    case tag_section:\n\t/* TODO */\n\tbreak;\n    }\n\n    return text;\n}\n\n// renders inline chain, stops at first non-inline tag and returns this tag\nstatic document_tag_t * RenderInlineChain(document_rendering_context_t *cx, document_tag_t *tag)\n{\n    char *text = Q_strdup(\"\");\n\n    while (tag && !IsBlockElement(tag))\n    {\n        text = Add_Inline_Tag(cx, text, tag);\n\n        tag = tag->next;\n    }\n\n    text = StripInlineSpaces(text, cx->inline_links);\n    Render_String(cx, text);\n    LineFeed(cx);\n\n    Q_free(text);\n\n    return tag;\n}\n\n// render block chain\nstatic void RenderBlockChain(document_rendering_context_t *cx, document_tag_t *tag)\n{\n    while (tag)\n    {\n        document_tag_t *next = tag->next;\n\n        AddBlankLine(cx);\n\n        if (IsBlockElement(tag))\n        {\n            // process tag with appropiate renderer\n            switch (tag->type)\n            {\n            case tag_section:\n                Render_Section(cx, (document_tag_section_t *) tag);\n                break;\n\n            case tag_p:\n                Render_P(cx, (document_tag_p_t *) tag);\n                break;\n\n            case tag_list:\n                Render_List(cx, (document_tag_list_t *) tag);\n                break;\n\n            case tag_dict:\n                Render_Dict(cx, (document_tag_dict_t *) tag);\n                break;\n\n            case tag_hr:\n                Render_Hr(cx, (document_tag_hr_t *) tag);\n                break;\n\n            case tag_pre:\n                Render_Pre(cx, (document_tag_pre_t *) tag);\n                break;\n\n            case tag_h:\n                Render_H(cx, (document_tag_h_t *) tag);\n                break;\n\n            default:    // skip this tag\n                break;\n            }\n        }\n        else\n        {\n            next = RenderInlineChain(cx, tag);\n        }\n\n        AddBlankLine(cx);\n\n        tag = next;\n    }\n}\n\n// renders document into memory buffer,\n// returns total lines processed,\n// with nocopy=true does not affect output buffer (it may be NULL),\n// which may be used for calculating needed buffer size\nstatic int XSD_RenderDocumentOnce(xml_document_t *doc, byte *buf, int width, int lines,\n                                  document_rendered_link_t **link, document_rendered_section_t **section)\n{\n    document_rendering_context_t context;\n\n//    int i;\n//    int l;\n//    int total_lines = 0;\n//    qbool first = true;\n//    document_tag_p_t *tag = (document_tag_p_t *) doc->content;\n//    byte line[1024];\n\n    memset(&context, 0, sizeof(context));\n    context.buf = buf;\n    context.line = 0;\n    context.line_pos = 0;\n    context.nocopy = lines==0 ? 1 : 0;\n    context.nometadata = link==NULL  ||  section==NULL;\n    context.last_blank = -1;\n\n    context.l_margin = 0;\n    context.r_margin = 0;\n    context.width = width;\n\n    context.list_count = 0;\n    context.align = align_left;\n    context.do_color = 0;\n\n    memset(context.line_buf, 0, LINE_MAX_LEN);\n\n    // zero the buffer\n    if (lines > 0)\n        memset(buf, 0, lines * width);\n\n    RenderBlockChain(&context, doc->content);\n\n    // skip last blank line\n    if (context.line - 1 == context.last_blank)\n        context.line--;\n\n    // set links\n    if (!context.nometadata)\n    {\n        *link = context.links;\n        *section = context.sections;\n    }\n\n    return context.line;\n}\n\n// renders document into memory buffer,\nint XSD_RenderDocument(document_rendered_t *ret, xml_document_t *doc, int width)\n{\n    int lines;\n\n    memset(ret, 0, sizeof(document_rendered_t));\n\n    // render document title\n    if (doc->title)\n    {\n        int lines;\n        document_tag_text_t *text;\n        document_tag_p_t *p;\n        xml_document_t *tdoc;\n\n        tdoc = XSD_Document_New();\n\n        // create p tag\n        p = (document_tag_p_t *) Q_malloc(sizeof(document_tag_p_t));\n        memset(p, 0, sizeof(document_tag_p_t));\n        p->type = tag_p;\n        p->align = align_center;\n\n        tdoc->content = (document_tag_t *) p;\n\n        // create text tag\n        text = (document_tag_text_t *) Q_malloc(sizeof(document_tag_text_t));\n        memset(text, 0, sizeof(document_tag_text_t));\n        text->type = tag_text;\n        text->text = Q_strdup(doc->title);\n\n        p->tags = (document_tag_t *) text;\n\n        lines = XSD_RenderDocumentOnce(tdoc, NULL, width, 0, NULL, NULL);\n        if (lines > 0)\n        {\n            ret->title = (char *) Q_malloc(lines*width);\n\t    ret->title_lines = XSD_RenderDocumentOnce(tdoc,(byte *) ret->title, width, lines, NULL, NULL);\n        }\n\n        XSD_Document_Free((xml_t *)tdoc);\n    }\n\n    // render document body\n    lines = XSD_RenderDocumentOnce(doc, NULL, width, 0, NULL, NULL);\n    if (lines <= 0)\n        goto error;\n    ret->text = (char *) Q_malloc(lines*width);\n    ret->text_lines = XSD_RenderDocumentOnce(doc,(byte *) ret->text, width, lines, &ret->links, &ret->sections);\n    return 1;\n\nerror:\n    XSD_RenderClear(ret);\n    return 0;\n}\n\nvoid RenderFreeSection(document_rendered_section_t *section)\n{\n    document_rendered_section_t *next;\n\n    if (section == NULL)\n        return;\n\n    while (section)\n    {\n        next = section->next;\n\n        RenderFreeSection(section->children);\n        Q_free(section);\n\n        section = next;\n    }\n}\n\n// free rendered document\nvoid XSD_RenderClear(document_rendered_t *r)\n{\n    document_rendered_link_t *link, *next;\n\n    if (r->text)\n        Q_free(r->text);\n    if (r->title)\n        Q_free(r->title);\n    \n    link = r->links;\n    while (link)\n    {\n        next = link->next;\n\n        Q_free(link);\n        link = next;\n    }\n\n    RenderFreeSection(r->sections);\n}\n"
  },
  {
    "path": "src/document_rendering.h",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n#ifndef __DOCUMENT_RENDERING_H__\n#define __DOCUMENT_RENDERING_H__\n\n\ntypedef struct document_rendered_link_s\n{\n    int start;\n    int end;\n    document_tag_a_t *tag;\n\n    struct document_rendered_link_s *next;\n}\ndocument_rendered_link_t;\n\ntypedef struct document_rendered_section_s\n{\n    int start;\n    document_tag_section_t *tag;\n\n    struct document_rendered_section_s *children;\n    struct document_rendered_section_s *next;\n}\ndocument_rendered_section_t;\n\n\ntypedef struct document_renered_s\n{\n    // head\n    char *title;\n    int title_lines;\n\n    // body\n    char *text;\n    int text_lines;\n\n    // metadata\n    document_rendered_link_t *links;\n    document_rendered_section_t *sections;\n}\ndocument_rendered_t;\n\n// renders document into memory buffer,\n int XSD_RenderDocument(document_rendered_t *r, xml_document_t *doc, int width);\n\n// free rendered document\nvoid XSD_RenderClear(document_rendered_t *);\n\n\n\n\n#endif // __DOCUMENT_RENDERING_H__\n"
  },
  {
    "path": "src/draw.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n// draw.h -- these are the only functions outside the refresh allowed\n// to touch the vid buffer\n\n#ifndef __DRAW_H__\n#define __DRAW_H__\n\ntypedef struct\n{\n\tint          width, height;\n\ttexture_ref  texnum;\n\tfloat        sl, tl, sh, th;\n} mpic_t;\n\ntypedef enum {\n\tCACHEPIC_PAUSE,\n\tCACHEPIC_LOADING,\n\tCACHEPIC_BOX_TL,\n\tCACHEPIC_BOX_ML,\n\tCACHEPIC_BOX_BL,\n\tCACHEPIC_BOX_TM,\n\tCACHEPIC_BOX_MM,\n\tCACHEPIC_BOX_MM2,\n\tCACHEPIC_BOX_BM,\n\tCACHEPIC_BOX_TR,\n\tCACHEPIC_BOX_MR,\n\tCACHEPIC_BOX_BR,\n\tCACHEPIC_TTL_MAIN,\n\tCACHEPIC_MAINMENU,\n\tCACHEPIC_MENUDOT1,\n\tCACHEPIC_MENUDOT2,\n\tCACHEPIC_MENUDOT3,\n\tCACHEPIC_MENUDOT4,\n\tCACHEPIC_MENUDOT5,\n\tCACHEPIC_MENUDOT6,\n\tCACHEPIC_TTL_SGL,\n\tCACHEPIC_QPLAQUE,\n\tCACHEPIC_SP_MENU,\n\tCACHEPIC_P_LOAD,\n\tCACHEPIC_P_SAVE,\n\tCACHEPIC_P_MULTI,\n\tCACHEPIC_RANKING,\n\tCACHEPIC_COMPLETE,\n\tCACHEPIC_INTER,\n\tCACHEPIC_FINALE,\n\n\tCACHEPIC_NUM_OF_PICS\n} cache_pic_id_t;\n\nvoid Draw_AdjustConback (void);\n\nextern\tmpic_t\t\t*draw_disc;\t// also used on sbar\n\n#define MCHARSET_PATH \"gfx/mcharset.png\"\n\ntypedef int color_t;\n\nextern const color_t COLOR_WHITE;\n\ncolor_t RGBA_TO_COLOR(byte r, byte g, byte b, byte a);\ncolor_t RGBAVECT_TO_COLOR(byte rgba[4]);\ncolor_t RGBAVECT_TO_COLOR_PREMULT(byte rgba[4]);\ncolor_t RGBAVECT_TO_COLOR_PREMULT_SPECIFIC(byte rgba[4], float alpha);\nbyte* COLOR_TO_RGBA(int i, byte rgba[4]);\nbyte* COLOR_TO_RGBA_PREMULT(color_t i, byte rgba[4]);\nfloat* COLOR_TO_FLOATVEC_PREMULT(color_t i, float rgba[4]);\nvoid Draw_SetOverallAlpha(float opacity);\nfloat Draw_MultiplyOverallAlpha(float alpha);\n\nvoid Draw_Init(void);\nvoid Draw_Shutdown(void);\nvoid Draw_Character(float x, float y, int num);\nvoid Draw_CharacterW(float x, float y, wchar num);\nfloat Draw_CharacterWSP(float x, float y, wchar num, float scale, qbool proportional);\nvoid Draw_SubPic(float x, float y, mpic_t *pic, int srcx, int srcy, int width, int height);\nvoid Draw_Pic(float x, float y, mpic_t *pic);\nvoid Draw_TransPic(float x, float y, mpic_t *pic);\nvoid Draw_ConsoleBackground (int lines);\nvoid Draw_BeginDisc(void);\nvoid Draw_EndDisc(void);\nvoid Draw_TileClear(int x, int y, int w, int h);\nvoid Draw_Fill(int x, int y, int w, int h, byte c);\nvoid Draw_FadeScreen(float alpha);\n\ntypedef struct clrinfo_s\n{\n\tcolor_t c;\t// Color.\n\tint i;\t\t// Index when this colors starts.\n} clrinfo_t;\n\ntypedef enum {\n\ttext_align_left,\n\ttext_align_center,\n\ttext_align_right\n} text_alignment_t;\n\nvoid Draw_AdjustImages(int first, int last, float x_offset);\nint Draw_ImagePosition(void);\nvoid Draw_SStringAligned(int x, int y, const char *text, float scale, float alpha, qbool proportional, text_alignment_t align, float max_x);\nvoid Draw_SColoredStringAligned(int x, int y, const char *text, clrinfo_t* color, int color_count, float scale, float alpha, qbool proportional, text_alignment_t align, float max_x);\n\nvoid Draw_GetBigfontSourceCoords(char c, int char_width, int char_height, int *sx, int *sy);\nvoid Draw_BigString(float x, float y, const char *text, clrinfo_t *color, int color_count, float scale, float alpha, int char_gap);\nvoid Draw_String(float x, float y, const char *str);\nvoid Draw_AlphaString(float x, float y, const char *str, float alpha);\nvoid Draw_Alt_String(float x, float y, const char *str, float scale, qbool proportional);\nvoid Draw_ColoredString(float x, float y, const char *str, int red, qbool proportional);\nvoid Draw_SColoredStringBasic(float x, float y, const char *text, int red, float scale, qbool proportional);\nvoid Draw_ColoredString3(float x, float y, const char *text, clrinfo_t *clr, int clr_cnt, int red);\nfloat Draw_SColoredAlphaString(float x, float y, const char *text, clrinfo_t *color, int color_count, int red, float scale, float alpha, qbool proportional);\nvoid Draw_SAlt_String(float x, float y, const char *text, float scale, qbool proportional);\nfloat Draw_SString(float x, float y, const char *str, float scale, qbool proportional);\nfloat Draw_SStringAlpha(int x, int y, const char* text, float scale, float alpha, qbool proportional);\n\nint Draw_ConsoleString(float x, float y, const wchar *text, clrinfo_t *clr, int clr_cnt, int red, float scale, qbool proportional);\nfloat Draw_StringLengthW(const wchar *text, int length, float scale, qbool proportional);\nfloat Draw_StringLength(const char *text, int length, float scale, qbool proportional);\nfloat Draw_StringLengthColors(const char *text, int length, float scale, qbool proportional);\nfloat Draw_StringLengthColorsByTerminator(const char *text, int length, float scale, qbool proportional, char terminator);\nint Draw_CharacterFit(const char* text, float width, float scale, qbool proportional);\n\nmpic_t *Draw_CachePicSafe(const char *path, qbool crash, qbool only24bit);\nmpic_t *Draw_CachePic(cache_pic_id_t id);\nmpic_t *Draw_CacheWadPic(char *name, int code);\nvoid Draw_Crosshair(void);\nvoid Draw_TextBox(float x, float y, int width, int lines);\n\nfloat Draw_SCharacterP(float x, float y, int num, float scale, qbool proportional);\n\nvoid Draw_SCharacter(float x, float y, int num, float scale);\nvoid Draw_SPic(float x, float y, mpic_t *, float scale);\nvoid Draw_FitPic(float x, float y, int fit_width, int fit_height, mpic_t *gl); // Will fit image into given area; will keep it's proportions.\nvoid Draw_FitPicAlpha(float x, float y, int fit_width, int fit_height, mpic_t *gl, float alpha);\nvoid Draw_FitPicAlphaCenter(float x, float y, int fit_width, int fit_height, mpic_t* gl, float alpha);\nvoid Draw_SAlphaPic(float x, float y, mpic_t *, float alpha, float scale);\nvoid Draw_SSubPic(float x, float y, mpic_t *, int srcx, int srcy, int width, int height, float scale);\nvoid Draw_STransPic(float x, float y, mpic_t *, float scale);\n\nvoid Draw_AlphaPieSlice (int x, int y, float radius, float startangle, float endangle, float thickness, qbool fill, byte c, float alpha);\n\nvoid Draw_AlphaCircleRGB(float x, float y, float radius, float thickness, qbool fill, color_t color);\nvoid Draw_AlphaCircle(float x, float y, float radius, float thickness, qbool fill, byte c, float alpha);\nvoid Draw_AlphaCircleOutlineRGB(float x, float y, float radius, float thickness, color_t color);\nvoid Draw_AlphaCircleOutline(float x, float y, float radius, float thickness, byte color, float alpha);\nvoid Draw_AlphaCircleFillRGB(float x, float y, float radius, color_t color);\nvoid Draw_AlphaCircleFill(float x, float y, float radius, byte color, float alpha);\n\nvoid Draw_AlphaLineRGB(float x_start, float y_start, float x_end, float y_end, float thickness, color_t color);\nvoid Draw_AlphaLine(float x_start, float y_start, float x_end, float y_end, float thickness, byte c, float alpha);\n\nvoid Draw_AlphaFill(float x, float y, float w, float h, byte c, float alpha);\nvoid Draw_AlphaRectangleRGB(float x, float y, float w, float h, float thickness, qbool fill, color_t color);\nvoid Draw_AlphaFillRGB(float x, float y, float w, float h, color_t color);\n\nvoid Draw_AlphaSubPic(float x, float y, mpic_t *pic, int srcx, int srcy, int width, int height, float alpha);\nvoid Draw_SAlphaSubPic(float x, float y, mpic_t *pic, int src_x, int src_y, int src_width, int src_height, float scale, float alpha);\nvoid Draw_SAlphaSubPic2(float x, float y, mpic_t *pic, int src_x, int src_y, int src_width, int src_height, float scale_x, float scale_y, float alpha);\nvoid Draw_AlphaPic(float x, float y, mpic_t *pic, float alpha);\nvoid Draw_2dAlphaTexture(float x, float y, float width, float height, texture_ref texture_num, float alpha);\n\nqbool R_CharAvailable (wchar num);\n\nvoid Draw_EnableScissorRectangle(float x, float y, float width, float height);\nvoid Draw_EnableScissor(float left, float right, float top, float bottom);\nvoid Draw_DisableScissor(void);\n\nvoid InitTracker(void);\n\nenum {\n\tWADPIC_RAM = 0,\n\tWADPIC_NET,\n\tWADPIC_TURTLE,\n\tWADPIC_DISC,\n\tWADPIC_BACKTILE,\n\tWADPIC_NUM_0,\n\tWADPIC_NUM_1,\n\tWADPIC_NUM_2,\n\tWADPIC_NUM_3,\n\tWADPIC_NUM_4,\n\tWADPIC_NUM_5,\n\tWADPIC_NUM_6,\n\tWADPIC_NUM_7,\n\tWADPIC_NUM_8,\n\tWADPIC_NUM_9,\n\tWADPIC_ANUM_0,\n\tWADPIC_ANUM_1,\n\tWADPIC_ANUM_2,\n\tWADPIC_ANUM_3,\n\tWADPIC_ANUM_4,\n\tWADPIC_ANUM_5,\n\tWADPIC_ANUM_6,\n\tWADPIC_ANUM_7,\n\tWADPIC_ANUM_8,\n\tWADPIC_ANUM_9,\n\tWADPIC_NUM_MINUS,\n\tWADPIC_ANUM_MINUS,\n\tWADPIC_NUM_COLON,\n\tWADPIC_NUM_SLASH,\n\tWADPIC_INV_SHOTGUN,\n\tWADPIC_INV_SSHOTGUN,\n\tWADPIC_INV_NAILGUN,\n\tWADPIC_INV_SNAILGUN,\n\tWADPIC_INV_RLAUNCH,\n\tWADPIC_INV_SRLAUNCH,\n\tWADPIC_INV_LIGHTNG,\n\tWADPIC_INV2_SHOTGUN,\n\tWADPIC_INV2_SSHOTGUN,\n\tWADPIC_INV2_NAILGUN,\n\tWADPIC_INV2_SNAILGUN,\n\tWADPIC_INV2_RLAUNCH,\n\tWADPIC_INV2_SRLAUNCH,\n\tWADPIC_INV2_LIGHTNG,\n\tWADPIC_INVA1_SHOTGUN,\n\tWADPIC_INVA2_SHOTGUN,\n\tWADPIC_INVA3_SHOTGUN,\n\tWADPIC_INVA4_SHOTGUN,\n\tWADPIC_INVA5_SHOTGUN,\n\tWADPIC_INVA1_SSHOTGUN,\n\tWADPIC_INVA2_SSHOTGUN,\n\tWADPIC_INVA3_SSHOTGUN,\n\tWADPIC_INVA4_SSHOTGUN,\n\tWADPIC_INVA5_SSHOTGUN,\n\tWADPIC_INVA1_NAILGUN,\n\tWADPIC_INVA2_NAILGUN,\n\tWADPIC_INVA3_NAILGUN,\n\tWADPIC_INVA4_NAILGUN,\n\tWADPIC_INVA5_NAILGUN,\n\tWADPIC_INVA1_SNAILGUN,\n\tWADPIC_INVA2_SNAILGUN,\n\tWADPIC_INVA3_SNAILGUN,\n\tWADPIC_INVA4_SNAILGUN,\n\tWADPIC_INVA5_SNAILGUN,\n\tWADPIC_INVA1_RLAUNCH,\n\tWADPIC_INVA2_RLAUNCH,\n\tWADPIC_INVA3_RLAUNCH,\n\tWADPIC_INVA4_RLAUNCH,\n\tWADPIC_INVA5_RLAUNCH,\n\tWADPIC_INVA1_SRLAUNCH,\n\tWADPIC_INVA2_SRLAUNCH,\n\tWADPIC_INVA3_SRLAUNCH,\n\tWADPIC_INVA4_SRLAUNCH,\n\tWADPIC_INVA5_SRLAUNCH,\n\tWADPIC_INVA1_LIGHTNG,\n\tWADPIC_INVA2_LIGHTNG,\n\tWADPIC_INVA3_LIGHTNG,\n\tWADPIC_INVA4_LIGHTNG,\n\tWADPIC_INVA5_LIGHTNG,\n\tWADPIC_SB_SHELLS,\n\tWADPIC_SB_NAILS,\n\tWADPIC_SB_ROCKET,\n\tWADPIC_SB_CELLS,\n\tWADPIC_SB_ARMOR1,\n\tWADPIC_SB_ARMOR2,\n\tWADPIC_SB_ARMOR3,\n\tWADPIC_SB_KEY1,\n\tWADPIC_SB_KEY2,\n\tWADPIC_SB_INVIS,\n\tWADPIC_SB_INVULN,\n\tWADPIC_SB_SUIT,\n\tWADPIC_SB_QUAD,\n\tWADPIC_SB_SIGIL1,\n\tWADPIC_SB_SIGIL2,\n\tWADPIC_SB_SIGIL3,\n\tWADPIC_SB_SIGIL4,\n\tWADPIC_SB_FACE1,\n\tWADPIC_SB_FACE_P1,\n\tWADPIC_SB_FACE2,\n\tWADPIC_SB_FACE_P2,\n\tWADPIC_SB_FACE3,\n\tWADPIC_SB_FACE_P3,\n\tWADPIC_SB_FACE4,\n\tWADPIC_SB_FACE_P4,\n\tWADPIC_SB_FACE5,\n\tWADPIC_SB_FACE_P5,\n\tWADPIC_FACE_INVIS,\n\tWADPIC_FACE_INVUL2,\n\tWADPIC_FACE_INV2,\n\tWADPIC_FACE_QUAD,\n\tWADPIC_SB_SBAR,\n\tWADPIC_SB_IBAR,\n\tWADPIC_SB_SCOREBAR,\n\n\tWADPIC_SB_IBAR_AMMO1,\n\tWADPIC_SB_IBAR_AMMO2,\n\tWADPIC_SB_IBAR_AMMO3,\n\tWADPIC_SB_IBAR_AMMO4,\n\n\tWADPIC_PIC_COUNT\n};\n\ntypedef struct wadpic_s {\n\tchar name[32];\n\tmpic_t* pic;\n} wadpic_t;\n\nextern wadpic_t wad_pictures[WADPIC_PIC_COUNT];\n\nvoid CachePics_CreateAtlas(void);\nvoid CachePics_AtlasFrame(void);\nvoid CachePics_MarkAtlasDirty(void);\nvoid Atlas_SolidTextureCoordinates(texture_ref* ref, float* s, float* t);\nqbool Draw_IsConsoleBackground(mpic_t* pic);\nqbool Draw_KeepOffAtlas(const char* path);\nmpic_t* Mod_SimpleTextureForHint(int model_hint, int skinnum);\n\n#endif // __DRAW_H__\n"
  },
  {
    "path": "src/ez_button.c",
    "content": "\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_scrollpane.c,v 1.78 2007/10/27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"keys.h\"\n#include \"utils.h\"\n#include \"common_draw.h\"\n#include \"ez_controls.h\"\n#include \"ez_button.h\"\n\n#ifdef _MSC_VER\n#pragma warning( disable : 4189 )\n#endif\n\n// =========================================================================================\n// Button\n// =========================================================================================\n\n//\n// Button - Recalculates and sets the position of the buttons label.\n//\nstatic void EZ_button_RecalculateLabelPosition(ez_button_t *button)\n{\n\tez_control_t *self\t\t\t\t= ((ez_control_t *)button);\n\tez_label_t *label\t\t\t\t= button->text_label;\n\tez_control_t *text_label_ctrl\t= ((ez_control_t *)label);\n//\tez_textalign_t alignment\t\t= button->text_alignment;\n\tint new_x\t\t\t\t\t\t= text_label_ctrl->x;\n\tint new_y\t\t\t\t\t\t= text_label_ctrl->y;\n\tint new_anchor\t\t\t\t\t= EZ_control_GetAnchor(text_label_ctrl);\n\n\t// TODO : Hmm, should we even bothered with a special case for text alignment? Why not just use ez_anchor_t stuff? Also remember to add support for middle_top and such.\n\tswitch (button->text_alignment)\n\t{\n\t\tcase top_left :\n\t\t{\n\t\t\tnew_anchor = (anchor_left | anchor_top);\n\t\t\tnew_x = 0;\n\t\t\tnew_y = 0;\n\t\t\tbreak;\n\t\t}\n\t\tcase top_right :\n\t\t{\n\t\t\tnew_anchor = (anchor_right | anchor_top);\n\t\t\tnew_x = 0;\n\t\t\tnew_y = 0;\n\t\t\tbreak;\n\t\t}\n\t\tcase top_center :\n\t\t{\n\t\t\tnew_anchor = anchor_top;\n\t\t\tnew_x = Q_rint((self->width  - text_label_ctrl->width) / 2.0);\n\t\t\tnew_y = 0;\n\t\t\tbreak;\n\t\t}\n\t\tcase bottom_left :\n\t\t{\n\t\t\tnew_anchor = (anchor_left | anchor_bottom);\n\t\t\tnew_x = 0;\n\t\t\tnew_y = 0;\n\t\t\tbreak;\n\t\t}\n\t\tcase bottom_right :\n\t\t{\n\t\t\tnew_anchor = (anchor_right | anchor_bottom);\n\t\t\tnew_x = 0;\n\t\t\tnew_y = 0;\n\t\t\tbreak;\n\t\t}\n\t\tcase bottom_center :\n\t\t{\n\t\t\tnew_anchor = anchor_bottom;\n\t\t\tnew_x = Q_rint((self->width  - text_label_ctrl->width) / 2.0);\n\t\t\tnew_y = 0;\n\t\t\tbreak;\n\t\t}\n\t\tcase middle_right :\n\t\t{\n\t\t\tnew_anchor = anchor_right;\n\t\t\tnew_x = 0;\n\t\t\tnew_y = Q_rint((self->height - text_label_ctrl->height) / 2.0);\n\t\t\tbreak;\n\t\t}\n\t\tcase middle_left :\n\t\t{\n\t\t\tnew_anchor = anchor_left;\n\t\t\tnew_x = 0;\n\t\t\tnew_y = Q_rint((self->height - text_label_ctrl->height) / 2.0);\n\t\t\tbreak;\n\t\t}\n\t\tcase middle_center :\n\t\t{\n\t\t\tnew_anchor = (anchor_left | anchor_top);\n\t\t\tnew_x = Q_rint((self->width  - text_label_ctrl->width) / 2.0);\n\t\t\tnew_y = Q_rint((self->height - text_label_ctrl->height) / 2.0);\n\t\t\tbreak;\n\t\t}\n\t\tdefault :\n\t\t\tbreak;\n\t}\n\n\tif (new_anchor != EZ_control_GetAnchor(text_label_ctrl))\n\t{\n\t\tEZ_control_SetAnchor(text_label_ctrl, new_anchor);\n\t}\n\n\tif ((new_x != text_label_ctrl->x) || (new_y != text_label_ctrl->y))\n\t{\n\t\tEZ_control_SetPosition(text_label_ctrl, new_x, new_y);\n\t}\n}\n\n//\n// Button - Event handler for the buttons OnTextChanged event.\n// \nstatic int EZ_button_OnLabelTextChanged(ez_control_t *self, void *payload, void *ext_event_info)\n{\n\tez_button_t *button = NULL;\n\t\n\tif ((self->parent == NULL) || (self->parent->CLASS_ID != EZ_BUTTON_ID))\n\t{\n\t\tSys_Error(\"EZ_button_OnLabelTextChanged: The buttons text labels parent isn't a button!\");\n\t}\n\t\n\tbutton = (ez_button_t *)self->parent;\n\n\tEZ_button_RecalculateLabelPosition(button);\n\n\treturn 0;\n}\n\n//\n// Button - Creates a new button and initializes it.\n//\nez_button_t *EZ_button_Create(ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\n{\n\tez_button_t *button = NULL;\n\n\t// We have to have a tree to add the control to.\n\tif (!tree)\n\t{\n\t\treturn NULL;\n\t}\n\n\tbutton = (ez_button_t *)Q_malloc(sizeof(ez_button_t));\n\tmemset(button, 0, sizeof(ez_button_t));\n\n\tEZ_button_Init(button, tree, parent, name, description, x, y, width, height, flags);\n\t\n\treturn button;\n}\n\n//\n// Button - Sets either the background image or color.\n//\nstatic void EZ_button_SetColorOrBackground(ez_control_t *self, mpic_t *background, byte *c)\n{\n\tez_button_t *button = (ez_button_t *)self;\n\n\tif (button->ext_flags & button_use_images)\n\t{\n\t\tself->background = background;\n\t}\n\telse\n\t{\n\t\tEZ_control_SetBackgroundColor(self, c[0], c[1], c[2], c[3]);\n\t}\n}\n\n//\n// Button - Initializes a button.\n//\nvoid EZ_button_Init(ez_button_t *button, ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\n{\n\t// Initialize the inherited class first.\n\tEZ_control_Init(&button->super, tree, parent, name, description, x, y, width, height, flags);\n\n\t// Initilize the button specific stuff.\n\t((ez_control_t *)button)->CLASS_ID\t= EZ_BUTTON_ID;\n\n\t// TODO : Make a default macro for button flags.\n\t((ez_control_t *)button)->ext_flags\t|= (flags | control_focusable | control_contained | control_bg_tile_center | control_bg_tile_edges);\n\n\t// Override the draw function.\n\tCONTROL_REGISTER_EVENT(button, EZ_button_OnDraw, OnDraw, ez_control_t);\n\tCONTROL_REGISTER_EVENT(button, EZ_button_OnResize, OnResize, ez_control_t);\n\tCONTROL_REGISTER_EVENT(button, EZ_button_OnMouseClick, OnMouseClick, ez_control_t);\n\tCONTROL_REGISTER_EVENT(button, EZ_button_OnMouseLeave, OnMouseLeave, ez_control_t);\n\tCONTROL_REGISTER_EVENT(button, EZ_button_OnMouseEnter, OnMouseEnter, ez_control_t);\t\n\tCONTROL_REGISTER_EVENT(button, EZ_button_OnMouseDown, OnMouseDown, ez_control_t);\n\tCONTROL_REGISTER_EVENT(button, EZ_button_OnMouseUp, OnMouseUp, ez_control_t);\t\n\n\t// Button specific events.\n\tCONTROL_REGISTER_EVENT(button, EZ_button_OnAction, OnAction, ez_button_t);\n\tCONTROL_REGISTER_EVENT(button, EZ_button_OnTextAlignmentChanged, OnTextAlignmentChanged, ez_button_t);\n\tCONTROL_REGISTER_EVENT(button, EZ_button_OnToggled, OnToggled, ez_button_t);\n\n\t// Create the buttons text label.\n\t{\n\t\tbutton->text_label = EZ_label_Create(tree, (ez_control_t *)button, \"Button text label\", \"\", button->padding_left, button->padding_top, 1, 1, 0, 0, \"\");\n\n\t\tEZ_label_AddOnTextChanged(button->text_label, EZ_button_OnLabelTextChanged, NULL);\n\n\t\t// Don't allow any interaction with the label, it's just there to show text.\n\t\tEZ_label_SetReadOnly(button->text_label, true);\n\t\tEZ_label_SetTextSelectable(button->text_label, false);\n\t\tEZ_label_SetAutoSize(button->text_label, true);\n\t\tEZ_control_SetIgnoreMouse((ez_control_t *)button->text_label, true);\n\t\tEZ_control_SetFocusable((ez_control_t *)button->text_label, false);\n\t\tEZ_control_SetMovable((ez_control_t *)button->text_label, false);\n\n\t\tCONTROL_RAISE_EVENT(NULL, ((ez_control_t *)button), ez_control_t, OnResize, NULL);\n\t}\n\n\tEZ_button_SetNormalImage(button, EZ_BUTTON_DEFAULT_NORMAL_IMAGE);\n\tEZ_button_SetHoverImage(button, EZ_BUTTON_DEFAULT_HOVER_IMAGE);\n\tEZ_button_SetPressedImage(button, EZ_BUTTON_DEFAULT_PRESSED_IMAGE);\n\tEZ_button_SetToggledHoverImage(button, EZ_BUTTON_DEFAULT_PRESSED_HOVER_IMAGE);\n\n\tbutton->ext_flags |= button_use_images;\n\n\tEZ_button_SetColorOrBackground((ez_control_t *)button, button->normal_image, button->color_normal);\n}\n\n//\n// Button - Destroys the button.\n//\nvoid EZ_button_Destroy(ez_control_t *self, qbool destroy_children)\n{\n\tez_button_t *button = (ez_button_t *)self;\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnDestroy, destroy_children);\n\n\tEZ_control_Destroy(&button->super, destroy_children);\n\n\tEZ_eventhandler_Remove(button->event_handlers.OnAction, NULL, true);\n\tEZ_eventhandler_Remove(button->event_handlers.OnTextAlignmentChanged, NULL, true);\n}\n\n//\n// Button - OnAction event handler.\n//\nint EZ_button_OnAction(ez_control_t *self, void *ext_event_info)\n{\n\tez_button_t *button = (ez_button_t *)self;\n\tCONTROL_EVENT_HANDLER_CALL(NULL, button, ez_button_t, OnAction, NULL);\n\n\treturn 0;\n}\n\n//\n// Button - OnResize event handler.\n//\nint EZ_button_OnResize(ez_control_t *self, void *ext_event_info)\n{\n\tez_button_t *button = (ez_button_t *)self;\n\n\t// Run the super class implementation.\n\tEZ_control_OnResize(self, NULL);\n\n\tEZ_button_RecalculateLabelPosition(button);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnResize, NULL);\n\n\treturn 0;\n}\n\n//\n// Button - Use images for the button?\n//\nvoid EZ_button_SetUseImages(ez_button_t *button, qbool useimages)\n{\n\tSET_FLAG(button->ext_flags, button_use_images, useimages);\n}\n\n//\n// Button - Sets if the button is toggleable.\n//\nvoid EZ_button_SetToggleable(ez_button_t *button, qbool toggleable)\n{\n\tSET_FLAG(button->ext_flags, button_is_toggleable, toggleable);\n}\n\n//\n// Button - Gets if the button is toggled.\n//\nqbool EZ_button_GetIsToggled(ez_button_t *button)\n{\n\treturn (button->int_flags & button_toggled);\n}\n\n//\n// Button - Set the text of the button. \n//\nvoid EZ_button_SetText(ez_button_t *button, const char *text)\n{\n\tEZ_label_SetText(button->text_label, text);\n}\n\n//\n// Button - Set the text of the button. \n//\nvoid EZ_button_SetTextAlignment(ez_button_t *button, ez_textalign_t text_alignment)\n{\n\tbutton->text_alignment = text_alignment;\n\n\tCONTROL_RAISE_EVENT(NULL, button, ez_button_t, OnTextAlignmentChanged, NULL);\n}\n\n//\n// Button - Set the event handler for the OnTextAlignmentChanged event.\n//\nvoid EZ_button_AddOnTextAlignmentChanged(ez_button_t *button, ez_eventhandler_fp OnTextAlignmentChanged, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(button, EZ_CONTROL_HANDLER, OnTextAlignmentChanged, ez_button_t, OnTextAlignmentChanged, payload);\n\tCONTROL_RAISE_EVENT(NULL, button, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n// \n// Button - Sets the OnToggled event handler.\n//\nvoid EZ_button_AddOnToggled(ez_button_t *button, ez_eventhandler_fp OnToggled, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(button, EZ_CONTROL_HANDLER, OnToggled, ez_button_t, OnToggled, payload);\n\tCONTROL_RAISE_EVENT(NULL, button, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Button - Set the normal image for the button.\n//\nvoid EZ_button_SetNormalImage(ez_button_t *button, const char *normal_image)\n{\n\tbutton->normal_image = normal_image ? Draw_CachePicSafe(normal_image, false, true) : NULL;\n}\n\n//\n// Button - Set the hover image for the button.\n//\nvoid EZ_button_SetHoverImage(ez_button_t *button, const char *hover_image)\n{\n\tbutton->hover_image = hover_image ? Draw_CachePicSafe(hover_image, false, true) : NULL;\n}\n\n//\n// Button - Set the hover image for the button.\n//\nvoid EZ_button_SetPressedImage(ez_button_t *button, const char *pressed_image)\n{\n\tbutton->pressed_image = pressed_image ? Draw_CachePicSafe(pressed_image, false, true) : NULL;\n}\n\n//\n// Button - Set the hover image for the button when it is toggled.\n//\nvoid EZ_button_SetToggledHoverImage(ez_button_t *button, const char *toggled_hover_image)\n{\n\tbutton->toggled_hover_image = toggled_hover_image ? Draw_CachePicSafe(toggled_hover_image, false, true) : NULL;\n}\n\n//\n// Button - Sets the normal color of the button.\n//\nvoid EZ_button_SetNormalColor(ez_button_t *self, byte r, byte g, byte b, byte alpha)\n{\n\tself->color_normal[0] = r;\n\tself->color_normal[1] = g;\n\tself->color_normal[2] = b;\n\tself->color_normal[3] = alpha;\n}\n\n//\n// Button - Sets the pressed color of the button.\n//\nvoid EZ_button_SetPressedColor(ez_button_t *self, byte r, byte g, byte b, byte alpha)\n{\n\tself->color_pressed[0] = r;\n\tself->color_pressed[1] = g;\n\tself->color_pressed[2] = b;\n\tself->color_pressed[3] = alpha;\n}\n\n//\n// Button - Sets the hover color of the button.\n//\nvoid EZ_button_SetHoverColor(ez_button_t *self, byte r, byte g, byte b, byte alpha)\n{\n\tself->color_hover[0] = r;\n\tself->color_hover[1] = g;\n\tself->color_hover[2] = b;\n\tself->color_hover[3] = alpha;\n}\n\n//\n// Button - Sets the toggled hover color of the button.\n//\nvoid EZ_button_SetToggledHoverColor(ez_button_t *self, byte r, byte g, byte b, byte alpha)\n{\n\tself->color_toggled_hover[0] = r;\n\tself->color_toggled_hover[1] = g;\n\tself->color_toggled_hover[2] = b;\n\tself->color_toggled_hover[3] = alpha;\n}\n\n//\n// Button - Sets the focused color of the button.\n//\nvoid EZ_button_SetFocusedColor(ez_button_t *self, byte r, byte g, byte b, byte alpha)\n{\n\tself->color_focused[0] = r;\n\tself->color_focused[1] = g;\n\tself->color_focused[2] = b;\n\tself->color_focused[3] = alpha;\n}\n\n//\n// Button - Sets the OnAction event handler.\n//\nvoid EZ_button_AddOnAction(ez_button_t *self, ez_eventhandler_fp OnAction, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnAction, ez_button_t, OnAction, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Button - OnDraw event.\n//\nint EZ_button_OnDraw(ez_control_t *self, void *ext_event_info)\n{\n\tez_button_t *button = (ez_button_t *)self;\n\n\tint x, y;\n\tEZ_control_GetDrawingPosition(self, &x, &y);\n\n\t// Run the super class's implementation first.\n\tEZ_control_OnDraw(self, NULL);\n\n\tif (self->int_flags & control_focused)\n\t{\n\t\tif (button->ext_flags & button_use_images)\n\t\t{\n\t\t}\n\t\telse\n\t\t{\n\t\t\tDraw_AlphaRectangleRGB(x, y, self->width, self->height, 1, false, RGBAVECT_TO_COLOR(button->color_focused));\n\t\t}\n\t}\n\n\t// Draw control specifics.\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnDraw, NULL);\n\treturn 0;\n}\n\n//\n// Button - OnTextAlignmentChanged event.\n//\nint EZ_button_OnTextAlignmentChanged(ez_control_t *self, void *ext_event_info)\n{\n\tez_button_t *button = (ez_button_t *)self;\n\n\tEZ_button_RecalculateLabelPosition(button);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, button, ez_button_t, OnTextAlignmentChanged, NULL);\n\treturn 0;\n}\n\n//\n// Button - OnMouseClick event.\n//\nint EZ_button_OnMouseClick(ez_control_t *self, mouse_state_t *mouse_state)\n{\n\tint mouse_handled = true;\n\tez_button_t *button = (ez_button_t *)self;\n\tEZ_control_OnMouseClick(self, mouse_state);\n\n\t// Toggle the button.\n\tif (button->ext_flags & button_is_toggleable)\n\t{\n\t\tbutton->int_flags ^= button_toggled;\n\t\tCONTROL_RAISE_EVENT(NULL, button, ez_button_t, OnToggled, NULL);\n\t}\n\telse\n\t{\n\t\tbutton->int_flags &= ~button_toggled;\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled, self, ez_control_t, OnMouseClick, mouse_state);\n\treturn mouse_handled;\n}\n\n//\n// Button - OnToggled event. The button was toggled.\n//\nint EZ_button_OnToggled(ez_control_t *self, void *ext_event_info)\n{\n\tez_button_t *button = (ez_button_t *)self;\n\n\t// Set the background based on the new state.\n\tif (button->int_flags & button_toggled)\n\t{\n\t\tEZ_button_SetColorOrBackground(self, button->pressed_image, button->color_pressed);\n\t}\n\telse\n\t{\n\t\tEZ_button_SetColorOrBackground(self, button->normal_image, button->color_normal);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, button, ez_button_t, OnToggled, NULL);\n\treturn 0;\n}\n\n//\n// Button - OnMouseEnter event.\n//\nint EZ_button_OnMouseEnter(ez_control_t *self, mouse_state_t *mouse_state)\n{\n\tez_button_t *button = (ez_button_t *)self;\n\tEZ_control_OnMouseEnter(self, mouse_state);\n\n\t// Set the background based on the new state.\n\tif (button->int_flags & button_toggled)\n\t{\n\t\tEZ_button_SetColorOrBackground(self, button->toggled_hover_image, button->color_toggled_hover);\n\t}\n\telse\n\t{\n\t\tEZ_button_SetColorOrBackground(self, button->hover_image, button->color_hover);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnMouseEnter, NULL);\n\treturn 0;\n}\n\n//\n// Button - OnMouseLeave event.\n//\nint EZ_button_OnMouseLeave(ez_control_t *self, mouse_state_t *mouse_state)\n{\n\tez_button_t *button = (ez_button_t *)self;\n\tEZ_control_OnMouseLeave(self, mouse_state);\n\n\t// Set the background based on the new state.\n\tif (button->int_flags & button_toggled)\n\t{\n\t\tEZ_button_SetColorOrBackground(self, button->pressed_image, button->color_pressed);\n\t}\n\telse\n\t{\n\t\tEZ_button_SetColorOrBackground(self, button->normal_image, button->color_normal);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, button, ez_control_t, OnMouseLeave, NULL);\n\treturn 0;\n}\n\n//\n// Button - OnMouseDown event.\n//\nint EZ_button_OnMouseDown(ez_control_t *self, mouse_state_t *mouse_state)\n{\n\tez_button_t *button = (ez_button_t *)self;\n\tEZ_control_OnMouseDown(self, mouse_state);\n\n\tEZ_button_SetColorOrBackground(self, button->toggled_hover_image, button->color_toggled_hover);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, button, ez_control_t, OnMouseDown, NULL);\n\treturn 1;\n}\n\n//\n// Button - OnMouseDown event.\n//\nint EZ_button_OnMouseUp(ez_control_t *self, mouse_state_t *mouse_state)\n{\n\tez_button_t *button = (ez_button_t *)self;\n\tEZ_control_OnMouseUp(self, mouse_state);\n\n\tif (!(button->ext_flags & button_is_toggleable))\n\t{\n\t\tEZ_button_SetColorOrBackground(self, button->hover_image, button->color_hover);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, button, ez_control_t, OnMouseUp, NULL);\n\treturn 1;\n}\n\n\n"
  },
  {
    "path": "src/ez_button.h",
    "content": "\n#ifndef __EZ_BUTTON_H__\n#define __EZ_BUTTON_H__\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_button.h,v 1.55 2007-10-27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"ez_controls.h\"\n#include \"ez_label.h\"\n\n// =========================================================================================\n// Button\n// =========================================================================================\n\n#define EZ_BUTTON_DEFAULT_NORMAL_IMAGE\t\t\t\"gfx/ui/ez_button_normal\"\n#define EZ_BUTTON_DEFAULT_HOVER_IMAGE\t\t\t\"gfx/ui/ez_button_hover\"\n#define EZ_BUTTON_DEFAULT_PRESSED_IMAGE\t\t\t\"gfx/ui/ez_button_pressed\"\n#define EZ_BUTTON_DEFAULT_PRESSED_HOVER_IMAGE\t\"gfx/ui/ez_button_pressed_hover\"\n\ntypedef struct ez_button_eventcount_s\n{\n\tint\tOnAction;\n\tint\tOnTextAlignmentChanged;\n\tint OnToggled;\n} ez_button_eventcount_t;\n\ntypedef struct ez_button_events_s\n{\n\tez_event_fp\tOnAction;\t\t\t\t// The event that's raised when the button is clicked / activated via a button.\n\tez_event_fp\tOnTextAlignmentChanged;\t// Text alignment changed.\n\tez_event_fp\tOnToggled;\t\t\t\t// The button was toggled.\n} ez_button_events_t;\n\ntypedef struct ez_button_eventhandlers_s\n{\n\tez_eventhandler_t\t\t*OnAction;\n\tez_eventhandler_t\t\t*OnTextAlignmentChanged;\n\tez_eventhandler_t\t\t*OnToggled;\n} ez_button_eventhandlers_t;\n\ntypedef enum ez_button_iflags_e\n{\n\tbutton_toggled = (1 << 0)\n} ez_button_iflags_t;\n\ntypedef enum ez_button_flags_e\n{\n\tbutton_use_images\t\t= (1 << 0),\n\tbutton_is_toggleable\t= (1 << 1)\n} ez_button_flags_t;\n\ntypedef enum ez_textalign_e\n{\n\ttop_left,\n\ttop_center,\n\ttop_right,\n\tmiddle_left,\n\tmiddle_center,\n\tmiddle_right,\n\tbottom_left,\n\tbottom_center,\n\tbottom_right\n} ez_textalign_t;\n\ntypedef struct ez_button_s\n{\n\tez_control_t\t\t\tsuper;\t\t\t\t// The super class.\n\n\tez_button_events_t\t\tevents;\t\t\t\t// Specific events for the button control.\n\tez_button_eventhandlers_t event_handlers;\t// Specific event handlers for the button control.\n\tez_button_eventcount_t\tinherit_levels;\n\tez_button_eventcount_t\toverride_counts;\n\n\tez_label_t\t\t\t\t*text_label;\t\t// The buttons text label.\n\n\tmpic_t\t\t\t\t\t*focused_image;\t\t// The image for the button when it is focused.\n\tmpic_t\t\t\t\t\t*normal_image;\t\t// The normal image used for the button.\n\tmpic_t\t\t\t\t\t*hover_image;\t\t// The image that is shown for the button when the mouse is over it.\n\tmpic_t\t\t\t\t\t*pressed_image;\t\t// The image that is shown when the button is pressed.\n\tmpic_t\t\t\t\t\t*toggled_hover_image; // The image that is shown for the button when the mouse is over it while toggled.\n\n\tbyte\t\t\t\t\tcolor_focused[4];\t// Color of the focus indicator of the button.\n\tbyte\t\t\t\t\tcolor_normal[4];\t// The normal color of the button.\n\tbyte\t\t\t\t\tcolor_hover[4];\t\t// Color when the button is hovered.\n\tbyte\t\t\t\t\tcolor_pressed[4];\t// Color when the button is pressed.\n\tbyte\t\t\t\t\tcolor_toggled_hover[4]; // Color when the button is hovered while toggled.\n\n\tez_button_iflags_t\t\tint_flags;\t\t\t// Internal flags.\n\tez_button_flags_t\t\text_flags;\t\t\t// External flags.\n\n\tint\t\t\t\t\t\tpadding_top;\n\tint\t\t\t\t\t\tpadding_left;\n\tint\t\t\t\t\t\tpadding_right;\n\tint\t\t\t\t\t\tpadding_bottom;\n\tez_textalign_t\t\t\ttext_alignment;\t\t// Text alignment.\n\tclrinfo_t\t\t\t\tfocused_text_color;\t// Text color when the button is focused.\n\tclrinfo_t\t\t\t\tnormal_text_color;\t// Text color when the button is in it's normal state.\n\tclrinfo_t\t\t\t\thover_text_color;\t// Text color when the button is hovered.\n\tclrinfo_t\t\t\t\tpressed_text_color;\t// Text color when the button is pressed.\n\n\tint\t\t\t\t\t\toverride_count;\t\t// These are needed so that subclasses can override button specific events.\n\tint\t\t\t\t\t\tinheritance_level;\n} ez_button_t;\n\n//\n// Button - Creates a new button and initializes it.\n//\nez_button_t *EZ_button_Create(ez_tree_t *tree, ez_control_t *parent, \n\t\t\t\t\t\t\t  char *name, char *description, \n\t\t\t\t\t\t\t  int x, int y, int width, int height, \n\t\t\t\t\t\t\t  ez_control_flags_t flags);\n\n//\n// Button - Initializes a button.\n//\nvoid EZ_button_Init(ez_button_t *button, ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\n\n//\n// Button - Destroys the button.\n//\nvoid EZ_button_Destroy(ez_control_t *self, qbool destroy_children);\n\n//\n// Button - OnTextAlignmentChanged event.\n//\nint EZ_button_OnTextAlignmentChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Button - OnAction event handler.\n//\nint EZ_button_OnAction(ez_control_t *self, void *ext_event_info);\n\n//\n// Button - OnResize event handler.\n//\nint EZ_button_OnResize(ez_control_t *self, void *ext_event_info);\n\n//\n// Button - Use images for the button?\n//\nvoid EZ_button_SetUseImages(ez_button_t *button, qbool useimages);\n\n//\n// Button - Sets if the button is toggleable.\n//\nvoid EZ_button_SetToggleable(ez_button_t *button, qbool toggleable);\n\n//\n// Button - Gets if the button is toggled.\n//\nqbool EZ_button_GetIsToggled(ez_button_t *button);\n\n//\n// Button - Set the text of the button. \n//\nvoid EZ_button_SetText(ez_button_t *button, const char *text);\n\n//\n// Button - Set the text of the button. \n//\nvoid EZ_button_SetTextAlignment(ez_button_t *button, ez_textalign_t text_alignment);\n\n//\n// Button - Set if the edges of the button should be tiled or stretched.\n//\nvoid EZ_button_SetTileCenter(ez_button_t *button, qbool tileedges);\n\n//\n// Button - Set if the center of the button should be tiled or stretched.\n//\nvoid EZ_button_SetTileCenter(ez_button_t *button, qbool tilecenter);\n\n//\n// Button - Set the event handler for the OnTextAlignmentChanged event.\n//\nvoid EZ_button_AddOnTextAlignmentChanged(ez_button_t *button, ez_eventhandler_fp OnTextAlignmentChanged, void *payload);\n\n//\n// Button - Set the normal image for the button.\n//\nvoid EZ_button_SetNormalImage(ez_button_t *button, const char *normal_image);\n\n//\n// Button - Set the hover image for the button.\n//\nvoid EZ_button_SetHoverImage(ez_button_t *button, const char *hover_image);\n\n//\n// Button - Set the hover image for the button.\n//\nvoid EZ_button_SetPressedImage(ez_button_t *button, const char *pressed_image);\n\n//\n// Button - Set the hover image for the button when it is toggled.\n//\nvoid EZ_button_SetToggledHoverImage(ez_button_t *button, const char *toggled_hover_image);\n\n// \n// Button - Sets the normal color of the button.\n//\nvoid EZ_button_SetNormalColor(ez_button_t *self, byte r, byte g, byte b, byte alpha);\n\n// \n// Button - Sets the pressed color of the button.\n//\nvoid EZ_button_SetPressedColor(ez_button_t *self, byte r, byte g, byte b, byte alpha);\n\n// \n// Button - Sets the hover color of the button.\n//\nvoid EZ_button_SetHoverColor(ez_button_t *self, byte r, byte g, byte b, byte alpha);\n\n//\n// Button - Sets the toggled hover color of the button.\n//\nvoid EZ_button_SetToggledHoverColor(ez_button_t *self, byte r, byte g, byte b, byte alpha);\n\n// \n// Button - Sets the focused color of the button.\n//\nvoid EZ_button_SetFocusedColor(ez_button_t *self, byte r, byte g, byte b, byte alpha);\n\n// \n// Button - Sets the OnAction event handler.\n//\nvoid EZ_button_AddOnAction(ez_button_t *self, ez_eventhandler_fp OnAction, void *payload);\n\n// \n// Button - Sets the OnAction event handler.\n//\nvoid EZ_button_AddOnToggled(ez_button_t *self, ez_eventhandler_fp OnToggled, void *payload);\n\n//\n// Button - OnDraw event.\n//\nint EZ_button_OnDraw(ez_control_t *self, void *ext_event_info);\n\n//\n// Button - OnMouseClick event.\n//\nint EZ_button_OnMouseClick(ez_control_t *self, mouse_state_t *mouse_state);\n\n//\n// Button - OnToggled event. The button was toggled.\n//\nint EZ_button_OnToggled(ez_control_t *self, void *ext_event_info);\n\n//\n// Button - OnMouseEnter event.\n//\nint EZ_button_OnMouseEnter(ez_control_t *self, mouse_state_t *mouse_state);\n\n//\n// Button - OnMouseLeave event.\n//\nint EZ_button_OnMouseLeave(ez_control_t *self, mouse_state_t *mouse_state);\n\n//\n// Button - OnMouseDown event.\n//\nint EZ_button_OnMouseDown(ez_control_t *self, mouse_state_t *mouse_state);\n\n//\n// Button - OnMouseDown event.\n//\nint EZ_button_OnMouseUp(ez_control_t *self, mouse_state_t *mouse_state);\n\n#endif // __EZ_BUTTON_H__\n"
  },
  {
    "path": "src/ez_controls.c",
    "content": "\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_controls.c,v 1.78 2007/10/27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"keys.h\"\n#include \"utils.h\"\n#include \"common_draw.h\"\n#include \"ez_controls.h\"\n\n#ifdef _MSC_VER\n#pragma warning( disable : 4189 )\n#endif\n\n// =========================================================================================\n// Double Linked List\n// =========================================================================================\n\n//\n// Add item to double linked list.\n//\nvoid EZ_double_linked_list_Add(ez_double_linked_list_t *list, void *payload)\n{\n\tez_dllist_node_t *item = (ez_dllist_node_t *)Q_malloc(sizeof(ez_dllist_node_t));\n\tmemset(item, 0, sizeof(ez_dllist_node_t));\n\n\titem->payload = payload;\n\titem->next = NULL;\n\n\t// Add the item to the start of the list if it's empty\n\t// otherwise add it to the tail.\n\tif(!list->head)\n\t{\n\t\tlist->head = item;\n\t\titem->next = NULL;\n\t\titem->previous = NULL;\n\t}\n\telse\n\t{\n\t\titem->previous = list->tail;\n\t}\n\n\t// Make sure the previous item knows who we are.\n\tif(item->previous)\n\t{\n\t\titem->previous->next = item;\n\t}\n\n\tlist->tail = item;\n\tlist->count++;\n}\n\n//\n// Finds a given node based on the specified payload.\n//\nez_dllist_node_t *EZ_double_linked_list_FindByPayload(const ez_double_linked_list_t *list, const void *payload)\n{\n\tez_dllist_node_t *iter = list->head;\n\n\twhile(iter)\n\t{\n\t\tif(iter->payload == payload)\n\t\t{\n\t\t\treturn iter;\n\t\t}\n\n\t\titer = iter->next;\n\t}\n\n\treturn NULL;\n}\n\n//\n// Double Linked List - Find a item by its index.\n//\nez_dllist_node_t *EZ_double_linked_list_FindByIndex(const ez_double_linked_list_t *list, int index)\n{\n\tint i;\n\tez_dllist_node_t *iter = NULL;\n\tqbool fromStart = (list->count - index) >= index;\n\n\tif (fromStart)\n\t{\n\t\titer = list->head;\n\t\t\n\t\tfor (i = 0; i < index; i++)\n\t\t{\n\t\t\titer = iter->next;\n\t\t}\n\t}\n\telse\n\t{\n\t\titer = list->tail;\n\t\t\n\t\tfor (i = list->count - 1; i >= index; i--)\n\t\t{\n\t\t\titer = iter->previous;\n\t\t}\n\t}\n\t\n\treturn iter;\n}\n\n//\n// Double Linked List - Removes the first occurance of the item from double linked list and returns its payload.\n//\nvoid *EZ_double_linked_list_Remove(ez_double_linked_list_t *list, ez_dllist_node_t *item)\n{\n\tvoid *payload = item->payload;\n\n\tif (item->previous)\n\t{\n\t\titem->previous->next = item->next;\n\t}\n\telse\n\t{\n\t\t// We removed the first item, so make sure we still have a head.\n\t\tlist->head = item->next;\n\t}\n\n\tif (item->next)\n\t{\n\t\titem->next->previous = item->previous;\n\t}\n\telse if (item->previous)\n\t{\n\t\t// We removed the last item, so make sure we have a tail.\n\t\tlist->tail = item->previous;\n\t}\n\n\tlist->count--;\n\n\tQ_free(item);\n\n\treturn payload; // WARNING THIS MUST BE FREED BY THE CALLER!\n}\n\n//\n// Double Linked List - Removes an item from a linked list by its index.\n//\nvoid *EZ_double_linked_list_RemoveByIndex(ez_double_linked_list_t *list, int index)\n{\n\tez_dllist_node_t *node;\n\t\n\tif ((index < 0) || (index >= list->count))\n\t{\n\t\treturn NULL;\n\t}\n\n\tnode = EZ_double_linked_list_FindByIndex(list, index);\n\treturn EZ_double_linked_list_Remove(list, node);\n}\n\n//\n// Double Linked List - Removes a range of items in the list. \n//\t\t\t\t\t\tA cleanup function needs to be supplied so that the payload of the item is also cleaned up.\n//\nvoid EZ_double_linked_list_RemoveRange(ez_double_linked_list_t *list, int start, int end, ez_removerange_cleanup_function_t func)\n{\n\tint i;\n\tez_dllist_node_t *iter = list->head;\n\tez_dllist_node_t *tmp;\n\n\tif (!iter || (start < 0) || (end < 0) || (start >= list->count) || (end >= list->count) || (start > end))\n\t{\n\t\treturn;\n\t}\n\n\t// Find the start index.\n\tfor (i = 0; i <= start; i++)\n\t{\n\t\titer = iter->next;\n\t}\n\n\t// Remove all items until the end.\n\tfor (i = start; i <= end; i++)\n\t{\n\t\ttmp = iter;\n\n\t\t// Run the cleanup function on the current \n\t\tfunc(EZ_double_linked_list_Remove(list, iter));\n\t\titer = tmp->next;\n\t}\n}\n\n//\n// Double Linked List - Removes an item from a linked list by its payload.\n//\nvoid *EZ_double_linked_list_RemoveByPayload(ez_double_linked_list_t *list, void *payload)\n{\n\tez_dllist_node_t *node = EZ_double_linked_list_FindByPayload(list, payload);\n\treturn EZ_double_linked_list_Remove(list, node);\n}\n\ntypedef void * PVOID;\n\n//\n// Double Linked List - Orders a list.\n//\nvoid EZ_double_linked_list_Sort(ez_double_linked_list_t *list, PtFuncCompare compare_function)\n{\n\tint i = 0;\n\tez_dllist_node_t *iter = NULL;\n\tPVOID **items = (PVOID **)Q_calloc(list->count, sizeof(PVOID *));\n\n\titer = list->head;\n\n\twhile(iter)\n\t{\n\t\titems[i] = iter->payload;\n\t\ti++;\n\n\t\titer = iter->next;\n\t}\n\n\tqsort(items, list->count, sizeof(PVOID *), compare_function);\n\n\titer = list->head;\n\n\tfor(i = 0; i < list->count; i++)\n\t{\n\t\titer->payload = items[i];\n\t\titer = iter->next;\n\t}\n\n\tQ_free(items);\n}\n\n// =========================================================================================\n// Control Tree\n// =========================================================================================\n\n//\n// Control tree -\n// Sets the drawing bounds for a control and then calls the function\n// recursivly on all it's children. These bounds are used to restrict the drawing\n// of all children that should be contained within it's parent, and their children\n// to within the bounds of the parent.\n//\nstatic void EZ_tree_SetDrawBounds(ez_control_t *control)\n{\n\tez_dllist_node_t *iter = NULL;\n\tez_control_t *child = NULL;\n\tez_control_t *p = control->parent;\n\tqbool contained = control->ext_flags & control_contained;\n\n\t// Calculate the controls bounds.\n\tint top\t\t= control->absolute_y;\n\tint bottom\t= control->absolute_y + control->height;\n\tint left\t= control->absolute_x;\n\tint right\t= control->absolute_x + control->width;\n\n\tint p_bound_top\t\t= p ? p->bound_top\t\t: 0;\n\tint p_bound_bottom\t= p ? p->bound_bottom\t: 0;\n\tint p_bound_left\t= p ? p->bound_left\t\t: 0;\n\tint p_bound_right\t= p ? p->bound_right\t: 0;\n\n\t// Change the bounds so that we don't draw over our parents resize handles.\n\tif (p)\n\t{\n\t\tif (p->ext_flags & control_resize_h)\n\t\t{\n\t\t\tp_bound_right\t-= p->resize_handle_thickness;\n\t\t\tp_bound_left\t+= p->resize_handle_thickness;\n\t\t}\n\n\t\tif (p->ext_flags & control_resize_v)\n\t\t{\n\t\t\tp_bound_bottom\t-= p->resize_handle_thickness;\n\t\t\tp_bound_top\t\t+= p->resize_handle_thickness;\n\t\t}\n\t}\n\n\t// If the control has a parent (and should be contained within it's parent), \n\t// set the corresponding bound to the parents bound (ex. button), \n\t// otherwise use the drawing area of the control as bounds (ex. windows).\n\tcontrol->bound_top\t\t= (p && contained && (top\t < p_bound_top))\t\t? (p_bound_top)\t\t: top;\n\tcontrol->bound_bottom\t= (p && contained && (bottom > p_bound_bottom))\t\t? (p_bound_bottom)\t: bottom;\n\tcontrol->bound_left\t\t= (p && contained && (left\t < p_bound_left))\t\t? (p_bound_left)\t: left;\n\tcontrol->bound_right\t= (p && contained && (right\t > p_bound_right))\t\t? (p_bound_right)\t: right;\n\n\t// Make sure that the left bounds isn't greater than the right bounds and so on.\n\t// This would lead to controls being visible in a few incorrect cases.\n\tclamp(control->bound_top, 0, max(0, control->bound_bottom));\n\tclamp(control->bound_bottom, control->bound_top, vid.conheight);\n\tclamp(control->bound_left, 0, max(0, control->bound_right));\n\tclamp(control->bound_right, control->bound_left, vid.conwidth);\n\n\t// Calculate the bounds for the children.\n\tfor (iter = control->children.head; iter; iter = iter->next)\n\t{\n\t\tchild = (ez_control_t *)iter->payload;\n\n\t\t// TODO : Probably should some better check for infinte loop here also.\n\t\tif (child == control)\n\t\t{\n\t\t\tSys_Error(\"EZ_tree_SetDrawBounds(): Infinite loop, child is its own parent.\\n\");\n\t\t}\n\n\t\tEZ_tree_SetDrawBounds(child);\n\t}\n}\n\n//\n// Control Tree - Draws a control tree.\n//\nstatic void EZ_tree_Draw(ez_tree_t *tree)\n{\n\tez_control_t *payload = NULL;\n\tez_dllist_node_t *iter = tree->drawlist.head;\n\n\twhile (iter)\n\t{\n\t\tpayload = (ez_control_t *)iter->payload;\n\n\t\t// TODO : Remove this test stuff.\n\t\t/*\n\t\tDraw_AlphaRectangleRGB(\n\t\t\tpayload->absolute_virtual_x, \n\t\t\tpayload->absolute_virtual_y, \n\t\t\tpayload->virtual_width, \n\t\t\tpayload->virtual_height, \n\t\t\t1, false, RGBA_TO_COLOR(255, 0, 0, 125));\n\t\t\t*/\n\n\t\t// Don't draw the invisible controls.\n\t\tif (!(payload->ext_flags & control_visible) || (payload->int_flags & control_hidden_by_parent))\n\t\t{\n\t\t\titer = iter->next;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// TODO : Remove this test stuff.\n\t\t/*\n\t\tif (!strcmp(payload->name, \"Close button\"))\n\t\t{\n\t\t\tDraw_String(payload->absolute_virtual_x, payload->absolute_virtual_y - 10, \n\t\t\t\tva(\"x: %i y: %i\", payload->x, payload->y));\n\t\t}\n\t\t*/\n\n\t\t/*\n\t\tif (!strcmp(payload->name, \"label\"))\n\t\t{\n\t\t\tDraw_String(payload->absolute_virtual_x, payload->absolute_virtual_y - 10, \n\t\t\t\tva(\"avx: %i avy: %i vx: %i vy %i\", \n\t\t\t\tpayload->absolute_virtual_x, payload->absolute_virtual_y, payload->virtual_x, payload->virtual_y));\n\t\t}\n\n\t\tif (!strcasecmp(payload->name, \"Child 1\"))\n\t\t{\n\t\t\tDraw_String(payload->absolute_virtual_x, payload->absolute_virtual_y - 10, \n\t\t\t\tva(\"vw: %i vh: %i w: %i h %i\", \n\t\t\t\tpayload->virtual_width, payload->virtual_height, payload->width, payload->height));\n\t\t}\n\t\t*/\n\n\t\t/*\n\t\tif (!strcasecmp(payload->name, \"button\"))\n\t\t{\n\t\t\tDraw_AlphaLineRGB(payload->bound_left, 0, payload->bound_left, vid.conheight, 1, RGBA_TO_COLOR(255, 0, 0, 255));\n\t\t\tDraw_AlphaLineRGB(payload->bound_right, 0, payload->bound_right, vid.conheight, 1, RGBA_TO_COLOR(255, 0, 0, 255));\n\t\t}\n\t\t*/\n\n\t\t/*\n\t\tif (!strcasecmp(payload->name, \"Vertical scrollbar\"))\n\t\t{\n\t\t\tDraw_String(payload->absolute_virtual_x, payload->absolute_virtual_y - 10, \n\t\t\t\tva(\"b: %i r: %i\", payload->bottom_edge_gap, payload->right_edge_gap));\n\t\t}\n\t\t*/\n\n\t\t/*\n\t\tif (!strcmp(payload->name, \"Close button\") || !strcmp(payload->name, \"Window titlebar\"))\n\t\t{\n\t\t\tDraw_AlphaRectangleRGB(\n\t\t\t\tpayload->absolute_virtual_x, \n\t\t\t\tpayload->absolute_virtual_y, \n\t\t\t\tpayload->virtual_width, \n\t\t\t\tpayload->virtual_height, \n\t\t\t\t1, false, RGBA_TO_COLOR(0, 0, 255, 125));\n\t\t}\n\t\t*/\n\t\t\n\t\t// Bugfix: Make sure we don't even bother trying to draw something that is completly offscreen\n\t\t// it will cause a weird flickering bug because of glScissor.\n\t\tif ((payload->bound_left == payload->bound_right) || (payload->bound_top == payload->bound_bottom))\n\t\t{\n\t\t\titer = iter->next;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Make sure we keep raising Mouse hover events while inside the control.\n\t\tif (POINT_IN_CONTROL_DRAWBOUNDS(payload, payload->prev_mouse_state.x, payload->prev_mouse_state.y))\n\t\t{\n\t\t\tCONTROL_RAISE_EVENT(NULL, payload, ez_control_t, OnMouseHover, &payload->prev_mouse_state);\n\t\t}\n\n\t\t// Make sure that the control draws only within its bounds.\n\t\tDraw_EnableScissor(payload->bound_left, payload->bound_right, payload->bound_top, payload->bound_bottom);\n\t\tDraw_SetOverallAlpha(payload->overall_opacity);\n\n\t\t// Raise the draw event for the control.\n\t\tCONTROL_RAISE_EVENT(NULL, payload, ez_control_t, OnDraw, NULL);\n\n\t\t// Reset the drawing bounds to the entire screen after the control has been drawn.\n\t\tDraw_DisableScissor();\n\t\tDraw_SetOverallAlpha(1.0);\n\n\t\titer = iter->next;\n\t}\n}\n\n//\n// Control Tree - Checks how long mouse buttons have been pressed.\n//\nstatic void EZ_tree_RaiseRepeatedMouseButtonEvents(ez_tree_t *tree)\n{\n\tif (tree->focused_node && tree->focused_node->payload)\n\t{\n\t\tez_control_t *control = (ez_control_t *)tree->focused_node->payload;\n\n\t\t// Notify controls that are listening to repeat mouse events \n\t\t// if the control's set delay has been reached.\n\t\tif (control->ext_flags & control_listen_repeat_mouse)\n\t\t{\n\t\t\tint i;\n\t\t\tdouble now\t\t\t= Sys_DoubleTime();\n\t\t\tmouse_state_t ms\t= tree->prev_mouse_state;\n\t\t\tms.button_up\t\t= 0;\n\t\t\t\n\t\t\t// Go through all the mouse buttons and compare the time since\n\t\t\t// the mouse buttons where pressed with the controls delay time.\n\t\t\tfor (i = 1; i <= 8; i++)\n\t\t\t{\n\t\t\t\t// Only repeat the mouse click if the button is already down\n\t\t\t\t// and the repeat isn't set to be all the time.\n\t\t\t\tif (ms.buttons[i]\n\t\t\t\t &&\t(control->mouse_repeat_delay > 0.0) && (tree->mouse_pressed_time[i] > 0.0)\n\t\t\t\t && ((now - tree->mouse_pressed_time[i]) >= control->mouse_repeat_delay))\n\t\t\t\t{\n\t\t\t\t\tms.button_down = i;\n\t\t\t\t\tCONTROL_RAISE_EVENT(NULL, control, ez_control_t, OnMouseEvent, &ms);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n//\n// Control Tree - Needs to be called every frame to keep the tree alive.\n//\nvoid EZ_tree_EventLoop(ez_tree_t *tree)\n{\n\tif (!tree)\n\t{\n\t\treturn;\n\t}\n\n\t// We wait with destroying until after all events have run.\n\tif (tree->destroying)\n\t{\n\t\tEZ_control_Destroy(tree->root, true);\n\t\tmemset(tree, 0, sizeof(ez_tree_t));\n\t\treturn;\n\t}\n\n\tif (!tree->root)\n\t{\n\t\treturn;\n\t}\n\n\t// Check if it's time to raise any repeat mouse events for controls listening to those.\n\tEZ_tree_RaiseRepeatedMouseButtonEvents(tree);\n\n\t// Calculate the drawing bounds for all the controls in the control tree.\n\tEZ_tree_SetDrawBounds(tree->root);\n\n\tEZ_tree_Draw(tree);\n}\n\n//\n// Control Tree - Dispatches a mouse event to a control tree.\n//\nqbool EZ_tree_MouseEvent(ez_tree_t *tree, mouse_state_t *ms)\n{\n\tint mouse_handled = false;\n\tez_control_t *control = NULL;\n\tez_dllist_node_t *iter = NULL;\n\n\tif (!tree)\n\t{\n\t\tSys_Error(\"EZ_tree_MouseEvent: NULL tree reference.\\n\");\n\t}\n\n\t// Save the time that the specified button was last pressed.\n\tif (ms->button_down || ms->button_up)\n\t{\n\t\t// Set the time for the pressed button.\n\t\tif (ms->button_down)\n\t\t{\n\t\t\ttree->mouse_pressed_time[ms->button_down] = Sys_DoubleTime();\n\t\t}\n\t\telse if (ms->button_up)\n\t\t{\n\t\t\ttree->mouse_pressed_time[ms->button_up] = 0.0;\n\t\t}\n\t}\n\n\t// Save the mouse state so that it can be used when raising\n\t// repeated mouse click events for controls that wants them.\n\ttree->prev_mouse_state = *ms;\n\n\t// Propagate the mouse event in the opposite order that we drew\n\t// the controls (Since they are drawn from back to front), so\n\t// that the foremost control gets it first.\n\tfor (iter = tree->drawlist.tail; iter; iter = iter->previous)\n\t{\n\t\tcontrol = (ez_control_t *)iter->payload;\n\n\t\t// Notify the control of the mouse event.\n\t\tCONTROL_RAISE_EVENT(&mouse_handled, control, ez_control_t, OnMouseEvent, ms);\n\n\t\tif (mouse_handled)\n\t\t{\n\t\t\treturn mouse_handled;\n\t\t}\n\t}\n\n\treturn mouse_handled;\n}\n\n//\n// Control Tree - Refreshes the position of all controls in the tree.\n//\nvoid EZ_tree_Refresh(ez_tree_t *tree)\n{\n\tif (tree->root)\n\t{\n\t\tez_dllist_node_t *iter = tree->drawlist.head;\n\t\tez_control_t *payload = NULL;\n\n\t\t//CONTROL_RAISE_EVENT(NULL, (ez_control_t *)tree->root, ez_control_t, OnResize, NULL);\n\t\t//CONTROL_RAISE_EVENT(NULL, (ez_control_t *)tree->root, ez_control_t, OnMove, NULL);\n\n\t\twhile (iter)\n\t\t{\n\t\t\tpayload = (ez_control_t *)iter->payload;\n\n\t\t\tCONTROL_RAISE_EVENT(NULL, payload, ez_control_t, OnResize, NULL);\n\t\t\tCONTROL_RAISE_EVENT(NULL, payload, ez_control_t, OnMove, NULL);\n\t\t\tCONTROL_RAISE_EVENT(NULL, payload, ez_control_t, OnAnchorChanged, NULL);\n\n\t\t\titer = iter->next;\n\t\t}\n\t}\n}\n\n//\n// Control Tree - Moves the focus to the next control in the control tree.\n//\nvoid EZ_tree_ChangeFocus(ez_tree_t *tree, qbool next_control)\n{\n\tqbool found = false;\n\tez_dllist_node_t *node_iter = NULL;\n\n\tif(tree->focused_node)\n\t{\n\t\tnode_iter = (next_control) ? tree->focused_node->next : tree->focused_node->previous;\n\n\t\t// Find the next control that can be focused.\n\t\twhile(node_iter && !found)\n\t\t{\n\t\t\t// ez_control_t *ha = (ez_control_t *)node_iter->payload;\n\t\t\tfound = EZ_control_SetFocusByNode((ez_control_t *)node_iter->payload, node_iter);\n\t\t\tnode_iter = (next_control) ? node_iter->next : node_iter->previous;\n\t\t}\n\t}\n\n\t// We haven't found a focusable control yet,\n\t// or there was no focused control to start with.\n\t// So search for one from the start/end of the tab list\n\t// (depending on what direction we're searching in)\n\tif(!found || !tree->focused_node)\n\t{\n\t\tnode_iter = (next_control) ? tree->tablist.head : tree->tablist.tail;\n\n\t\t// Find the next control that can be focused.\n\t\twhile(node_iter && !found)\n\t\t{\n\t\t\tfound = EZ_control_SetFocusByNode((ez_control_t *)node_iter->payload, node_iter);\n\t\t\tnode_iter = (next_control) ? node_iter->next : node_iter->previous;\n\t\t}\n\t}\n\n\t// There is nothing to focus on.\n\tif(!found)\n\t{\n\t\ttree->focused_node = NULL;\n\t}\n}\n\n//\n// Control Tree - Key event.\n//\nqbool EZ_tree_KeyEvent(ez_tree_t *tree, int key, int unichar, qbool down)\n{\n\tint key_handled = false;\n//\tez_control_t *payload = NULL;\n//\tez_dllist_node_t *iter = NULL;\n\n\tif (!tree)\n\t{\n\t\tSys_Error(\"EZ_tree_KeyEvent(): NULL control tree specified.\\n\");\n\t}\n\n\tif (tree->root && down)\n\t{\n\t\tswitch (key)\n\t\t{\n\t\t\tcase 'k' :\n\t\t\t\tkey_handled = true;\n\t\t\t\tbreak;\n\t\t\tcase K_TAB :\n\t\t\t{\n\t\t\t\t// Focus on the next focusable control (TAB)\n\t\t\t\t// or the previous (Shift + TAB)\n\t\t\t\tEZ_tree_ChangeFocus(tree, !keydown[K_SHIFT]);\n\t\t\t\tkey_handled = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Send key events to the focused control.\n\tif (tree->focused_node && tree->focused_node->payload)\n\t{\n\t\tCONTROL_RAISE_EVENT(&key_handled, (ez_control_t *)tree->focused_node->payload, ez_control_t, OnKeyEvent, key, unichar, down);\n\t}\n\n\treturn key_handled;\n}\n\n//\n// Control Tree - Finds any orphans and adds them to the root control.\n//\nvoid EZ_tree_UnOrphanizeChildren(ez_tree_t *tree)\n{\n\tez_control_t *payload = NULL;\n\tez_dllist_node_t *iter = NULL;\n\n\tif(!tree)\n\t{\n\t\tassert(!\"EZ_control_UnOrphanizeChildren: No control tree specified.\\n\");\n\t\treturn;\n\t}\n\n\titer = tree->drawlist.head;\n\n\twhile(iter)\n\t{\n\t\tpayload = (ez_control_t *)iter->payload;\n\n\t\tif(!payload->parent && payload != tree->root)\n\t\t{\n\t\t\t// The control is an orphan, and not the root control.\n\t\t\tEZ_double_linked_list_Add(&tree->root->children, payload);\n\t\t}\n\t\telse if(!tree->root)\n\t\t{\n\t\t\t// There was no root (should never happen).\n\t\t\ttree->root = payload;\n\t\t}\n\n\t\titer = iter->next;\n\t}\n}\n\n//\n// Control Tree - Destroys a tree. Will not free the memory for the tree.\n//\nvoid EZ_tree_Destroy(ez_tree_t *tree)\n{\n\tif (!tree)\n\t{\n\t\treturn;\n\t}\n\n\t// Make sure we abort all events before we destroy the tree\n\t// or they might try to read from freed memory. \n\t// The actual destruction is done in EZ_tree_EventLoop.\n\ttree->destroying = true;\n}\n\n//\n// Control Tree - Order function for the draw list based on the draw_order property.\n//\nstatic int EZ_tree_DrawOrderFunc(const void *val1, const void *val2)\n{\n\tconst ez_control_t **control1 = (const ez_control_t **)val1;\n\tconst ez_control_t **control2 = (const ez_control_t **)val2;\n\n\treturn ((*control1)->draw_order - (*control2)->draw_order);\n}\n\n//\n// Control Tree - Orders the draw list based on the draw order property.\n//\nvoid EZ_tree_OrderDrawList(ez_tree_t *tree)\n{\n\tEZ_double_linked_list_Sort(&tree->drawlist, EZ_tree_DrawOrderFunc);\n}\n\n//\n// Control Tree - Tree Order function for the tab list based on the tab_order property.\n//\nstatic int EZ_tree_TabOrderFunc(const void *val1, const void *val2)\n{\n\tconst ez_control_t **control1 = (const ez_control_t **)val1;\n\tconst ez_control_t **control2 = (const ez_control_t **)val2;\n\n\treturn ((*control1)->tab_order - (*control2)->tab_order);\n}\n\n//\n// Control Tree - Orders the tab list based on the tab order property.\n//\nvoid EZ_tree_OrderTabList(ez_tree_t *tree)\n{\n\tEZ_double_linked_list_Sort(&tree->tablist, EZ_tree_TabOrderFunc);\n}\n\n// =========================================================================================\n// Control\n// =========================================================================================\n\n//\n// Control - Creates a new control and initializes it.\n//\nez_control_t *EZ_control_Create(ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  int flags)\n{\n\tez_control_t *control = NULL;\n\n\t// We have to have a tree to add the control to.\n\tif(!tree || tree->destroying)\n\t{\n\t\treturn NULL;\n\t}\n\n\tcontrol = (ez_control_t *)Q_malloc(sizeof(ez_control_t));\n\tmemset(control, 0, sizeof(ez_control_t));\n\n\tEZ_control_Init(control, tree, parent, name, description, x, y, width, height, flags);\n\n\treturn control;\n}\n\n//\n// Control - \n// Returns the screen position of the control. This will be different for a scrollable window\n// since it's drawing position differs from the windows actual position on screen.\n//\nvoid EZ_control_GetDrawingPosition(ez_control_t *self, int *x, int *y)\n{\n\tif (self->ext_flags & control_scrollable)\n\t{\n\t\t(*x) = self->absolute_virtual_x;\n\t\t(*y) = self->absolute_virtual_y;\n\t}\n\telse\n\t{\n\t\t(*x) = self->absolute_x;\n\t\t(*y) = self->absolute_y;\n\t}\n}\n\n//\n// Eventhandler - Creates a eventhandler.\n//\nez_eventhandler_t *EZ_eventhandler_Create(void *event_func, int func_type, void *payload)\n{\n\tez_eventhandler_t *e\t= Q_calloc(1, sizeof(ez_eventhandler_t));\n\te->function_type\t\t= func_type;\n\te->payload\t\t\t\t= payload;\n\n\tswitch(func_type)\n\t{\n\t\tcase EZ_CONTROL_HANDLER :\n\t\t\te->function.normal = (ez_eventhandler_fp)event_func;\n\t\t\tbreak;\n\t\tcase EZ_CONTROL_MOUSE_HANDLER :\n\t\t\te->function.mouse = (ez_mouse_eventhandler_fp)event_func;\n\t\t\tbreak;\n\t\tcase EZ_CONTROL_KEY_HANDLER :\n\t\t\te->function.key = (ez_key_eventhandler_fp)event_func;\n\t\t\tbreak;\n\t\tcase EZ_CONTROL_KEYSP_HANDLER :\n\t\t\te->function.key_sp = (ez_keyspecific_eventhandler_fp)event_func;\n\t\t\tbreak;\n\t\tcase EZ_CONTROL_DESTROY_HANDLER :\n\t\t\te->function.destroy = (ez_destroy_eventhandler_fp)event_func;\n\t\t\tbreak;\n\t\tcase EZ_CONTROL_CHILD_HANDLER :\n\t\t\te->function.child = (ez_child_eventhandler_event_fp)event_func;\n\t\t\tbreak;\n\t\tdefault :\n\t\t\te->function.normal = NULL;\n\t\t\tbreak;\n\t}\n\n\te->next\t= NULL;\n\n\treturn e;\n}\t\n\n//\n// Eventhandler - Goes through the list of events and removes the one with the specified function.\n//\nvoid EZ_eventhandler_Remove(ez_eventhandler_t *eventhandler, void *event_func, qbool all)\n{\n\tez_eventhandler_t *it = eventhandler;\n\tez_eventhandler_t *prev = NULL;\n\n\twhile (it)\n\t{\n\t\tif (all)\n\t\t{\n\t\t\tprev = it->next;\n\t\t\tQ_free(it);\n\t\t\tit = prev;\n\t\t}\n\t\telse \n\t\t{\n\t\t\tif (event_func && ((void *)it->function.normal == (void *)event_func))\n\t\t\t{\n\t\t\t\tif (prev)\n\t\t\t\t{\n\t\t\t\t\tprev->next = it->next;\n\t\t\t\t}\n\n\t\t\t\tQ_free(it);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tprev = it;\n\t\t\tit = it->next;\n\t\t}\n\t}\n}\n\n//\n// Eventhandler - Execute an event handler.\n//\nvoid EZ_eventhandler_Exec(ez_eventhandler_t *event_handler, ez_control_t *ctrl, ...)\n{\n\tva_list argptr;\n\tint ft = event_handler->function_type;\n\tvoid *payload = event_handler->payload;\n\tez_eventhandlerfunction_t *et = &event_handler->function;\n\n\tva_start(argptr, ctrl);\n\n\t// Pass on the proper amount of arguments, depending on the type of function.\n\tif (ft == EZ_CONTROL_HANDLER)\n\t{\n\t\tet->normal(ctrl, payload, va_arg(argptr, void *));\n\t}\n\telse if (ft == EZ_CONTROL_MOUSE_HANDLER)\n\t{\n\t\tet->mouse(ctrl, payload, va_arg(argptr, mouse_state_t *));\t\n\t}\n\telse if (ft == EZ_CONTROL_KEY_HANDLER)\n\t{\n\t\tet->key(ctrl, payload, va_arg(argptr, int), va_arg(argptr, int), va_arg(argptr, qbool));\n\t}\n\telse if (ft == EZ_CONTROL_KEYSP_HANDLER)\n\t{\n\t\tet->key_sp(ctrl, payload, va_arg(argptr, int), va_arg(argptr, int));\n\t}\n\telse if (ft == EZ_CONTROL_DESTROY_HANDLER)\t\n\t{\n\t\tet->destroy(ctrl, payload, va_arg(argptr, qbool));\n\t}\n\telse if (ft == EZ_CONTROL_CHILD_HANDLER)\t\n\t{\n\t\tet->child(ctrl, payload, va_arg(argptr, ez_control_t *));\n\t}\n\n\tva_end(argptr);\n}\n\n//\n// Control - Initializes a control and adds it to the specified control tree.\n//\nvoid EZ_control_Init(ez_control_t *control, ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height, \n\t\t\t\t\t\t\t  ez_control_flags_t flags)\n{\n\tstatic int order\t\t= 0;\n\n\tcontrol->initializing\t= true;\n\n\tcontrol->CLASS_ID\t\t= EZ_CONTROL_ID;\n\n\tcontrol->control_tree\t= tree;\n\tcontrol->name\t\t\t= name;\n\tcontrol->description\t= description;\n\tcontrol->ext_flags\t\t= flags | control_enabled | control_visible | control_bg_tile_center | control_bg_tile_edges | control_resizeable;\n\tcontrol->anchor_flags\t= anchor_none;\n\t\n\t// Set the default opacity to full :)\n\tcontrol->overall_opacity = 1.0;\n\tcontrol->opacity = 1.0;\n\n\t// Default to containing a child within it's parent\n\t// if the parent is being contained by it's parent.\n\tif (parent && (parent->ext_flags & control_contained))\n\t{\n\t\tcontrol->ext_flags |= control_contained;\n\t}\n\n\tcontrol->draw_order\t\t\t\t\t= order++;\n\n\tcontrol->resize_handle_thickness\t= 5;\n\tcontrol->width_max\t\t\t\t\t= vid.conwidth;\n\tcontrol->height_max\t\t\t\t\t= vid.conheight;\n\tcontrol->width_min\t\t\t\t\t= 5;\n\tcontrol->height_min\t\t\t\t\t= 5;\n\n\t// Setup the default event functions, none of these should ever be NULL.\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnMouseEvent, OnMouseEvent, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnMouseClick, OnMouseClick, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnMouseDown,\tOnMouseDown, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnMouseUp, OnMouseUp, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnMouseUpOutside, OnMouseUpOutside, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnMouseEnter, OnMouseEnter, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnMouseLeave, OnMouseLeave, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnMouseHover, OnMouseHover, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_Destroy, OnDestroy, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnDraw, OnDraw, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnGotFocus, OnGotFocus, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnKeyEvent, OnKeyEvent, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnKeyDown, OnKeyDown, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnKeyUp, OnKeyUp, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnLostFocus, OnLostFocus, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnLayoutChildren, OnLayoutChildren, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnMove, OnMove, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnScroll, OnScroll, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnParentScroll, OnParentScroll, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnResize, OnResize, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnParentResize, OnParentResize, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnMinVirtualResize, OnMinVirtualResize, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnVirtualResize, OnVirtualResize, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnFlagsChanged, OnFlagsChanged, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnEventHandlerChanged, OnEventHandlerChanged, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnAnchorChanged, OnAnchorChanged, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnVisibilityChanged, OnVisibilityChanged, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnOpacityChanged, OnOpacityChanged, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnChildMoved, OnChildMoved, ez_control_t);\n\tCONTROL_REGISTER_EVENT(control, EZ_control_OnChildResize, OnChildResize, ez_control_t);\n\n\t// Add the control to the draw and tab list.\n\tEZ_double_linked_list_Add(&tree->drawlist, (void *)control);\n\tEZ_double_linked_list_Add(&tree->tablist, (void *)control);\n\n\t// Order the lists.\n\tEZ_tree_OrderDrawList(tree);\n\tEZ_tree_OrderTabList(tree);\n\n\t// Position and size of the control.\n\tcontrol->width\t\t\t\t\t= width;\n\tcontrol->height\t\t\t\t\t= height;\n\tcontrol->virtual_width\t\t\t= width;\n\tcontrol->virtual_height\t\t\t= height;\n\tcontrol->prev_width\t\t\t\t= width;\n\tcontrol->prev_height\t\t\t= height;\n\tcontrol->prev_virtual_width\t\t= width;\n\tcontrol->prev_virtual_height\t= height;\n\n\tcontrol->int_flags |= control_update_anchorgap;\n\n\t// Set the background settings.\n\tEZ_control_SetBackgroundImageOpacity(control, 1.0);\n\tEZ_control_SetBackgroundImageEdgePercentage(control, 10);\n\n\tEZ_control_SetVirtualSize(control, width, height);\n\tEZ_control_SetMinVirtualSize(control, 1, 1);\n\tEZ_control_SetPosition(control, x, y);\n\tEZ_control_SetSize(control, width, height);\n\n\t// Set a default delay for raising new mouse click events.\n\tEZ_control_SetRepeatMouseClickDelay(control, 0.2);\n\t\n\t// Add the control to the control tree.\n\tif (!tree->root)\n\t{\n\t\t// The first control will be the root.\n\t\ttree->root = control;\n\t}\n\telse if (!parent)\n\t{\n\t\t// No parent was given so make the control a root child.\n\t\tEZ_control_AddChild(tree->root, control);\n\t}\n\telse\n\t{\n\t\t// Add the control to the specified parent.\n\t\tEZ_control_AddChild(parent, control);\n\t}\n\n\tcontrol->initializing = false;\n}\n\n//\n// Control - Destroys a specified control.\n//\nint EZ_control_Destroy(ez_control_t *self, qbool destroy_children)\n{\n\tez_dllist_node_t *iter = NULL;\n\tez_dllist_node_t *temp = NULL;\n\n\t// Nothing to destroy :(\n\tif (!self)\n\t{\n\t\treturn 0;\n\t}\n\n\tif (!self->control_tree)\n\t{\n\t\t// Very bad!\n\t\tSys_Error(\"EZ_control_Destroy(): tried to destroy control without a tree.\\n\");\n\t\treturn 0;\n\t}\n\n\t// Cleanup any specifics this control may have.\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnDestroy, destroy_children);\n\n\titer = self->children.head;\n\n\t// Destroy the children!\n\twhile (iter)\n\t{\n\t\tif (destroy_children)\n\t\t{\n\t\t\t// Destroy the child!\n\t\t\tCONTROL_RAISE_EVENT(NULL, ((ez_control_t *)iter->payload), ez_control_t, OnDestroy, destroy_children);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// The child becomes an orphan :~/\n\t\t\t((ez_control_t *)iter->payload)->parent = NULL;\n\t\t}\n\n\t\ttemp = iter;\n\t\titer = iter->next;\n\n\t\t// Remove the child from the list.\n\t\tEZ_double_linked_list_Remove(&self->children, temp);\n\t}\n\n\tEZ_eventhandler_Remove(self->event_handlers.OnDestroy, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnDraw, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnEventHandlerChanged, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnFlagsChanged, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnGotFocus, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnKeyDown, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnKeyEvent, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnKeyUp, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnLayoutChildren, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnLostFocus, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnMinVirtualResize, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnMouseClick, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnMouseDown, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnMouseEnter, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnMouseEvent, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnMouseHover, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnMouseLeave, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnMouseUp, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnMouseUpOutside, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnMove, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnParentResize, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnParentScroll, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnResize, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnResizeHandleThicknessChanged, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnScroll, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnVirtualResize, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnAnchorChanged, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnVisibilityChanged, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnChildMoved, NULL, true);\n\tEZ_eventhandler_Remove(self->event_handlers.OnChildResize, NULL, true);\n\n\tQ_free(self);\n\n\treturn 0;\n}\n\n//\n// Control - Sets the OnDestroy event handler.\n//\nvoid EZ_control_AddOnDestroy(ez_control_t *self, ez_destroy_eventhandler_fp OnDestroy, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_DESTROY_HANDLER, OnDestroy, ez_control_t, OnDestroy, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnFlagsChanged event handler.\n//\nvoid EZ_control_AddOnFlagsChanged(ez_control_t *self, ez_eventhandler_fp OnFlagsChanged, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnFlagsChanged, ez_control_t, OnFlagsChanged, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnLayoutChildren event handler.\n//\nvoid EZ_control_AddOnLayoutChildren(ez_control_t *self, ez_eventhandler_fp OnLayoutChildren, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnLayoutChildren, ez_control_t, OnLayoutChildren, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnMove event handler.\n//\nvoid EZ_control_AddOnMove(ez_control_t *self, ez_eventhandler_fp OnMove, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnMove, ez_control_t, OnMove, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnScroll event handler.\n//\nvoid EZ_control_AddOnScroll(ez_control_t *self, ez_eventhandler_fp OnScroll, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnScroll, ez_control_t, OnScroll, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnResize event handler.\n//\nvoid EZ_control_AddOnResize(ez_control_t *self, ez_eventhandler_fp OnResize, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnResize, ez_control_t, OnResize, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnParentResize event handler.\n//\nvoid EZ_control_AddOnParentResize(ez_control_t *self, ez_eventhandler_fp OnParentResize, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnParentResize, ez_control_t, OnParentResize, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnMinVirtualResize event handler.\n//\nvoid EZ_control_AddOnMinVirtualResize(ez_control_t *self, ez_eventhandler_fp OnMinVirtualResize, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnMinVirtualResize, ez_control_t, OnMinVirtualResize, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnVirtualResize event handler.\n//\nvoid EZ_control_AddOnVirtualResize(ez_control_t *self, ez_eventhandler_fp OnVirtualResize, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnVirtualResize, ez_control_t, OnVirtualResize, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnKeyEvent event handler.\n//\nvoid EZ_control_AddOnKeyEvent(ez_control_t *self, ez_key_eventhandler_fp OnKeyEvent, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_KEY_HANDLER, OnKeyEvent, ez_control_t, OnKeyEvent, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnLostFocus event handler.\n//\nvoid EZ_control_AddOnLostFocus(ez_control_t *self, ez_eventhandler_fp OnLostFocus, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnLostFocus, ez_control_t, OnLostFocus, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnGotFocus event handler.\n//\nvoid EZ_control_AddOnGotFocus(ez_control_t *self, ez_eventhandler_fp OnGotFocus, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnGotFocus, ez_control_t, OnGotFocus, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnMouseHover event handler.\n//\nvoid EZ_control_AddOnMouseHover(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseHover, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_MOUSE_HANDLER, OnMouseHover, ez_control_t, OnMouseHover, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnMouseLeave event handler.\n//\nvoid EZ_control_AddOnMouseLeave(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseLeave, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_MOUSE_HANDLER, OnMouseLeave, ez_control_t, OnMouseLeave, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnMouseEnter event handler.\n//\nvoid EZ_control_AddOnMouseEnter(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseEnter, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_MOUSE_HANDLER, OnMouseEnter, ez_control_t, OnMouseEnter, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnMouseClick event handler.\n//\nvoid EZ_control_AddOnMouseClick(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseClick, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_MOUSE_HANDLER, OnMouseClick, ez_control_t, OnMouseClick, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnMouseUp event handler.\n//\nvoid EZ_control_AddOnMouseUp(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseUp, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_MOUSE_HANDLER, OnMouseUp, ez_control_t, OnMouseUp, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnMouseUpOutside event handler.\n//\nvoid EZ_control_AddOnMouseUpOutside(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseUpOutside, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_MOUSE_HANDLER, OnMouseUpOutside, ez_control_t, OnMouseUpOutside, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnMouseDown event handler.\n//\nvoid EZ_control_AddOnMouseDown(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseDown, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_MOUSE_HANDLER, OnMouseDown, ez_control_t, OnMouseDown, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnMouseEvent event handler.\n//\nvoid EZ_control_AddOnMouseEvent(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseEvent, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_MOUSE_HANDLER, OnMouseEvent, ez_control_t, OnMouseEvent, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnDraw event handler.\n//\nvoid EZ_control_AddOnDraw(ez_control_t *self, ez_eventhandler_fp OnDraw, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnDraw, ez_control_t, OnDraw, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Set the event handler for the OnEventHandlerChanged event.\n//\nvoid EZ_control_AddOnEventHandlerChanged(ez_control_t *self, ez_eventhandler_fp OnEventHandlerChanged, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnEventHandlerChanged, ez_control_t, OnEventHandlerChanged, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnResizeHandleThicknessChanged event handler.\n//\nvoid EZ_control_AddOnResizeHandleThicknessChanged(ez_control_t *self, ez_eventhandler_fp OnResizeHandleThicknessChanged, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnResizeHandleThicknessChanged, ez_control_t, OnResizeHandleThicknessChanged, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnChildMoved event handler.\n//\nvoid EZ_control_AddOnChildMoved(ez_control_t *self, ez_eventhandler_fp OnChildMoved, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnChildMoved, ez_control_t, OnChildMoved, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Sets the OnChildResize event handler.\n//\nvoid EZ_control_AddOnChildResize(ez_control_t *self, ez_eventhandler_fp OnChildResize, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(self, EZ_CONTROL_HANDLER, OnChildResize, ez_control_t, OnChildResize, payload);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Control - Focuses on a control.\n//\nqbool EZ_control_SetFocus(ez_control_t *self)\n{\n\treturn EZ_control_SetFocusByNode(self, EZ_double_linked_list_FindByPayload(&self->control_tree->tablist, self));\n}\n\n//\n// Control - Focuses on a control associated with a specified node from the tab list.\n//\nqbool EZ_control_SetFocusByNode(ez_control_t *self, ez_dllist_node_t *node)\n{\n\tez_tree_t *tree = NULL;\n\n\tif(!self || !node->payload)\n\t{\n\t\tSys_Error(\"EZ_control_SetFocus(): Cannot focus on a NULL control.\\n\");\n\t}\n\n\t// The nodes payload and the control must be the same.\n\tif(self != node->payload)\n\t{\n\t\treturn false;\n\t}\n\n\t// We can't focus on this control.\n\tif(!(self->ext_flags & control_focusable) || !(self->ext_flags & control_enabled))\n\t{\n\t\treturn false;\n\t}\n\n\ttree = self->control_tree;\n\n\t// Steal the focus from the currently focused control.\n\tif(tree->focused_node)\n\t{\n\t\tez_control_t *payload = NULL;\n\n\t\tif(!tree->focused_node->payload)\n\t\t{\n\t\t\tSys_Error(\"EZ_control_SetFocus(): Focused node has a NULL payload.\\n\");\n\t\t}\n\n\t\tpayload = (ez_control_t *)tree->focused_node->payload;\n\n\t\t// We're trying to focus on the already focused node.\n\t\tif(payload == self)\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\t// Remove the focus flag and set the focus node to NULL.\n\t\tpayload->int_flags &= ~control_focused;\n\t\ttree->focused_node = NULL;\n\n\t\t// Reset all manipulation flags.\n\t\tpayload->int_flags &= ~(control_clicked | control_moving | control_resizing_left | control_resizing_right | control_resizing_bottom | control_resizing_top);\n\n\t\t// Raise event for losing the focus.\n\t\tCONTROL_RAISE_EVENT(NULL, payload, ez_control_t, OnLostFocus, NULL);\n\t}\n\n\t// Set the new focus.\n\tself->int_flags |= control_focused;\n\ttree->focused_node = node;\n\n\t// Raise event for getting focus.\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnGotFocus, NULL);\n\n\treturn true;\n}\n\n//\n// Control - Set the background image for the control.\n//\nvoid EZ_control_SetBackgroundImage(ez_control_t *self, const char *background_path)\n{\n\tself->background = background_path ? Draw_CachePicSafe(background_path, false, true) : NULL;\n}\n\n//\n// Control - Set the opacity for the background image for the control.\n//\nvoid EZ_control_SetBackgroundImageOpacity(ez_control_t *self, float opacity)\n{\n\tself->bg_opacity = opacity;\n}\n\n//\n// Control - Set how much percentage of the width of the background image that should be used when drawing the edges of the control.\n//\nvoid EZ_control_SetBackgroundImageEdgePercentage(ez_control_t *self, int percentage)\n{\n\tself->bg_edge_size_ratio = percentage / 100.0;\n\tclamp(self->bg_edge_size_ratio, 0.0, 1.0);\n}\n\n//\n// Control - Set if the center of the button should be tiled or stretched.\n//\nvoid EZ_control_SetBackgroundTileCenter(ez_control_t *self, qbool tilecenter)\n{\n\tSET_FLAG(self->ext_flags, control_bg_tile_center, tilecenter);\n}\n\n//\n// Control - Set if the edges of the button should be tiled or stretched.\n//\nvoid EZ_control_SetBackgroundTileEdges(ez_control_t *self, qbool tileedges)\n{\n\tSET_FLAG(self->ext_flags, control_bg_tile_edges, tileedges);\n}\n\n//\n// Control - Sets whetever the control is contained within the bounds of it's parent or not, or is allowed to draw outside it.\n//\nvoid EZ_control_SetContained(ez_control_t *self, qbool contained)\n{\n\tSET_FLAG(self->ext_flags, control_contained, contained);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets whetever the control is enabled or not.\n//\nvoid EZ_control_SetEnabled(ez_control_t *self, qbool enabled)\n{\n\t// TODO : Make sure input isn't allowed if a control isn't enabled.\n\tSET_FLAG(self->ext_flags, control_enabled, enabled);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets whetever the control is movable.\n//\nvoid EZ_control_SetMovable(ez_control_t *self, qbool movable)\n{\n\tSET_FLAG(self->ext_flags, control_movable, movable);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets whetever the control is focusable.\n//\nvoid EZ_control_SetFocusable(ez_control_t *self, qbool focusable)\n{\n\tSET_FLAG(self->ext_flags, control_focusable, focusable);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets whetever the control is resizeable horizontally by the user.\n//\nvoid EZ_control_SetResizeableHorizontally(ez_control_t *self, qbool resize_horizontally)\n{\n\tSET_FLAG(self->ext_flags, control_resize_h, resize_horizontally);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets whetever the control is resizeable both horizontally and vertically by the user.\n//\nvoid EZ_control_SetResizeableBoth(ez_control_t *self, qbool resize)\n{\n\tSET_FLAG(self->ext_flags, (control_resize_h | control_resize_v), resize);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets whetever the control is resizeable at all, not just by the user.\n//\nvoid EZ_control_SetResizeable(ez_control_t *self, qbool resizeable)\n{\n\t// TODO : Is it confusing having this resizeable?\n\tSET_FLAG(self->ext_flags, control_resizeable, resizeable);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets whetever the control is resizeable vertically by the user.\n//\nvoid EZ_control_SetResizeableVertically(ez_control_t *self, qbool resize_vertically)\n{\n\tSET_FLAG(self->ext_flags, control_resize_v, resize_vertically);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets whetever the control is visible or not.\n//\nvoid EZ_control_SetVisible(ez_control_t *self, qbool visible)\n{\n\tSET_FLAG(self->ext_flags, control_visible, visible);\n\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnVisibilityChanged, NULL);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets whetever the control is scrollable or not.\n//\nvoid EZ_control_SetScrollable(ez_control_t *self, qbool scrollable)\n{\n\tSET_FLAG(self->ext_flags, control_scrollable, scrollable);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets if the control should move it's parent when it moves itself.\n//\nvoid EZ_control_SetMovesParent(ez_control_t *self, qbool moves_parent)\n{\n\tSET_FLAG(self->ext_flags, control_move_parent, moves_parent);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets whetever the control should care about mouse input or not.\n//\nvoid EZ_control_SetIgnoreMouse(ez_control_t *self, qbool ignore_mouse)\n{\n\tSET_FLAG(self->ext_flags, control_ignore_mouse, ignore_mouse);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Listen to repeated mouse click events when holding down a mouse button. \n//           The delay between events is set using EZ_control_SetRepeatMouseClickDelay(...)\n//\nvoid EZ_control_SetListenToRepeatedMouseClicks(ez_control_t *self, qbool listen_repeat)\n{\n\tSET_FLAG(self->ext_flags, control_listen_repeat_mouse, listen_repeat);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets the amount of time to wait between each new mouse click event\n//           when holding down the mouse over a control.\n//\nvoid EZ_control_SetRepeatMouseClickDelay(ez_control_t *self, double delay)\n{\n\tself->mouse_repeat_delay = delay;\n}\n\n//\n// Control - Sets the external flags of the control.\n//\nvoid EZ_control_SetFlags(ez_control_t *self, ez_control_flags_t flags)\n{\n\tself->ext_flags = flags;\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Control - Sets the external flags of the control.\n//\nez_control_flags_t EZ_control_GetFlags(ez_control_t *self)\n{\n\treturn self->ext_flags;\n}\n\n//\n// Control - Gets the anchor flags.\n//\nez_anchor_t EZ_control_GetAnchor(ez_control_t *self)\n{\n\treturn self->anchor_flags;\n}\n\n//\n// Control - Sets the tab order of a control.\n//\nvoid EZ_control_SetTabOrder(ez_control_t *self, int tab_order)\n{\n\tself->tab_order = tab_order;\n\tEZ_tree_OrderTabList(self->control_tree);\n}\n\n//\n// Control - Calculates the position percentages based on the current pixel position.\n//\nstatic void EZ_control_CalculatePositionPercentages(ez_control_t *self)\n{\n\tif (self->parent)\n\t{\n\t\tez_control_t *p = self->parent;\n\t\t// TODO: Should we ever use the normal width of the parent here? If the control is out of the parents view port in this case, it will never be visible.\n\t\tint p_width\t\t= (self->int_flags & control_anchor_viewport) ? p->width  : p->virtual_width;\n\t\tint p_height\t= (self->int_flags & control_anchor_viewport) ? p->height : p->virtual_height;\n\n\t\tself->x_percent = self->x / (float)p_width;\n\t\tself->y_percent = self->y / (float)p_height;\n\n\t\tself->x_percent = bound(0, self->x_percent, 1.0);\n\t\tself->y_percent = bound(0, self->y_percent, 1.0);\n\t}\n\telse\n\t{\n\t\tself->x_percent = 0.0;\n\t\tself->y_percent = 0.0;\n\t}\n}\n\n//\n// Control - Updates the anchor gaps between the controls edges and its parent \n//           (the distance to maintain from the parent when anchored to opposite edges).\n//\nstatic void EZ_control_UpdateAnchorGap(ez_control_t *self)\n{\n\tif (!(self->int_flags & control_update_anchorgap))\n\t{\n\t\treturn;\n\t}\n\telse if (self->parent)\n\t{\n\t\tqbool anchor_viewport\t= self->ext_flags & control_anchor_viewport;\n\t\tint p_height\t\t\t= anchor_viewport ? self->parent->height : self->parent->virtual_height;\n\t\tint p_width\t\t\t\t= anchor_viewport ? self->parent->width  : self->parent->virtual_width;\n\t\t\n\t\t// Anchored to both top and bottom edges.\n\t\tif ((self->anchor_flags & (anchor_bottom | anchor_top)) == (anchor_bottom | anchor_top))\n\t\t{\n\t\t\tself->top_edge_gap\t\t= self->y;\n\t\t\tself->bottom_edge_gap\t= p_height - (self->y + self->height);\n\t\t}\n\n\t\t// Anchored to both left and right edges.\n\t\tif ((self->anchor_flags & (anchor_left | anchor_right)) == (anchor_left | anchor_right))\n\t\t{\n\t\t\tself->left_edge_gap\t\t= self->x;\n\t\t\tself->right_edge_gap\t= p_width - (self->x + self->width);\n\t\t}\n\t}\n}\n\n//\n// Control - Sets the anchoring of the control to it's parent.\n//           !!! NOTE !!! \n//           If you set an anchoring to a single edge, make sure you do this\n//           after the parent has its final size, otherwise you might get some\n//           goofy behavior.\n//           !!! NOTE !!!\n//\nvoid EZ_control_SetAnchor(ez_control_t *self, ez_anchor_t anchor_flags)\n{\n\tself->anchor_flags = anchor_flags;\n\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnAnchorChanged, NULL);\n}\n\n//\n// Control - Sets the tab order of a control.\n//\nvoid EZ_control_SetDrawOrder(ez_control_t *self, int draw_order, qbool update_children)\n{\n\t// TODO : Is this the best way to change the draw order for the children?\n\tif (update_children && self->children.count > 0)\n\t{\n\t\tez_dllist_node_t *it\t= self->children.head;\n\t\tez_control_t *child\t\t= (ez_control_t *)it->payload;\n\t\tint draw_order_delta\t= draw_order - self->draw_order;\n\n\t\twhile (it)\n\t\t{\n\t\t\tchild = (ez_control_t *)it->payload;\n\t\t\tEZ_control_SetDrawOrder(child, (draw_order + draw_order_delta), update_children);\n\t\t\tit = it->next;\n\t\t}\n\t}\n\n\tself->draw_order = draw_order;\n\n\t// TODO : Force teh user to do this explicitly? Because it will be run a lot of times if there's many children. \n\tEZ_tree_OrderDrawList(self->control_tree);\n}\n\n//\n// Control - Sets the payload for the control.\n//\nvoid EZ_control_SetPayload(ez_control_t *self, void *payload)\n{\n\tself->payload = payload;\n}\n\n//\n// Control - Sets the size of a control.\n//\nvoid EZ_control_SetSize(ez_control_t *self, int width, int height)\n{\n\tself->prev_width = self->width;\n\tself->prev_height = self->height;\n\n\tself->width  = width;\n\tself->height = height;\n\n\tclamp(self->height, self->height_min, self->height_max);\n\tclamp(self->width, self->width_min, self->width_max);\n\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnResize, NULL);\n}\n\n//\n// Control - Set the thickness of the resize handles (if any).\n//\nvoid EZ_control_SetResizeHandleThickness(ez_control_t *self, int thickness)\n{\n\tself->resize_handle_thickness = thickness;\n}\n\n//\n// Control - Set the max size for the control.\n//\nvoid EZ_control_SetMaxSize(ez_control_t *self, int max_width, int max_height)\n{\n\tself->width_max = max(0, max_width);\n\tself->height_max = max(0, max_height);\n\n\t// Do we need to change the size of the control to fit?\n\tif ((self->width > self->width_max) || (self->height > self->height_max))\n\t{\n\t\tEZ_control_SetSize(self, min(self->width, self->width_max), min(self->height, self->height_max));\n\t}\n}\n\n//\n// Control - Set the min size for the control.\n//\nvoid EZ_control_SetMinSize(ez_control_t *self, int min_width, int min_height)\n{\n\tself->width_min = max(0, min_width);\n\tself->height_min = max(0, min_height);\n\n\t// Do we need to change the size of the control to fit?\n\tif ((self->width < self->width_min) || (self->height < self->height_min))\n\t{\n\t\tEZ_control_SetSize(self, max(self->width, self->width_min), max(self->height, self->height_min));\n\t}\n}\n\n//\n// Control - Set color of a control.\n//\nvoid EZ_control_SetBackgroundColor(ez_control_t *self, byte r, byte g, byte b, byte alpha)\n{\n\tself->background_color[0] = r;\n\tself->background_color[1] = g;\n\tself->background_color[2] = b;\n\tself->background_color[3] = alpha;\n}\n\n//\n// Control - Sets the position of a control, relative to it's parent.\n//\nvoid EZ_control_SetPosition(ez_control_t *self, int x, int y)\n{\n\t// Set the new relative position.\n\tself->x = x;\n\tself->y = y;\n\n\t// Raise the event that we have moved.\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnMove, NULL);\n}\n\n//\n// Control - Sets the part of the control that should be shown if it's scrollable.\n//\nvoid EZ_control_SetScrollPosition(ez_control_t *self, int scroll_x, int scroll_y)\n{\n\t// Only scroll scrollable controls.\n\tif (!(self->ext_flags & control_scrollable))\n\t{\n\t\treturn;\n\t}\n\n\t// Don't allow scrolling outside the scroll area.\n\tif ((scroll_x > (self->virtual_width - self->width)) \n\t || (scroll_y > (self->virtual_height - self->height))\n\t || (scroll_x < 0)\n\t || (scroll_y < 0))\n\t{\n\t\treturn;\n\t}\n\n\tself->virtual_x = scroll_x;\n\tself->virtual_y = scroll_y;\n \n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnScroll, NULL);\n}\n\n//\n// Control - Convenient function for changing the scroll position by a specified amount.\n//\nvoid EZ_control_SetScrollChange(ez_control_t *self, int delta_scroll_x, int delta_scroll_y)\n{\n\tEZ_control_SetScrollPosition(self, (self->virtual_x + delta_scroll_x), (self->virtual_y + delta_scroll_y));\n}\n\n//\n// Control - Sets the virtual size of the control (this area can be scrolled around in).\n//\nvoid EZ_control_SetVirtualSize(ez_control_t *self, int virtual_width, int virtual_height)\n{\n\tself->prev_virtual_width = self->virtual_width;\n\tself->prev_virtual_height = self->virtual_height;\n\n\t// Set the new virtual size of the control (cannot be smaller than the current size of the control).\n\tself->virtual_width = max(virtual_width, self->virtual_width_min);\n\tself->virtual_height = max(virtual_height, self->virtual_height_min);\n\n\tself->virtual_width = max(self->virtual_width, self->width + self->virtual_x);\n\tself->virtual_height = max(self->virtual_height, self->height + self->virtual_y);\n\n\t// Raise the event that we have changed the virtual size of the control.\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnVirtualResize, NULL);\n}\n\n//\n// Control - Set the min virtual size for the control, the control size is not allowed to be larger than this.\n//\nvoid EZ_control_SetMinVirtualSize(ez_control_t *self, int min_virtual_width, int min_virtual_height)\n{\n\tself->virtual_width_min = max(0, min_virtual_width);\n\tself->virtual_height_min = max(0, min_virtual_height);\n\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnMinVirtualResize, NULL);\n}\n\n//\n// Control - Returns true if this control is the root control.\n//\nqbool EZ_control_IsRoot(ez_control_t *self)\n{\n\treturn (self->parent == NULL);\n}\n\n//\n// Control - Adds a child to the control.\n//\nvoid EZ_control_AddChild(ez_control_t *self, ez_control_t *child)\n{\n\t// Remove the control from it's current parent.\n\tif (child->parent)\n\t{\n\t\tEZ_double_linked_list_RemoveByPayload(&child->parent->children, child);\n\t}\n \n\t// Set the new parent of the child.\n\tchild->parent = self;\n\n\t// Add the child to the new parents list of children.\n\tEZ_double_linked_list_Add(&self->children, child);\n}\n\n//\n// Control - Remove a child from the control. Returns a reference to the child that was removed.\n//\nez_control_t *EZ_control_RemoveChild(ez_control_t *self, ez_control_t *child)\n{\n\treturn (ez_control_t *)EZ_double_linked_list_RemoveByPayload(&self->children, child);\n}\n\n//\n// Control - The anchoring for the control changed.\n//\nint EZ_control_OnAnchorChanged(ez_control_t *self, void *ext_event_info)\n{\n\t// Calculate the gaps to the parents edges (used for anchoring, see OnParentResize for detailed explination).\n\tEZ_control_UpdateAnchorGap(self);\n\n\t// Calculate the position percentages in relation to the parents size.\n\t// We'll use this in the case where the control is only anchored to\n\t// one edge of its parent so that it will \"flow\" as the parent is resized.\n\tEZ_control_CalculatePositionPercentages(self);\n\n\t// Make sure the control is repositioned correctly.\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnMove, NULL);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnParentResize, NULL);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnAnchorChanged, NULL);\n\treturn 0;\n}\n\n//\n// Control - The control got focus.\n//\nint EZ_control_OnGotFocus(ez_control_t *self, void *ext_event_info)\n{\n\tif(!(self->int_flags & control_focused))\n\t{\n\t\treturn 0;\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnGotFocus, NULL);\n\n\treturn 0;\n}\n\n//\n// Control - The control lost focus.\n//\nint EZ_control_OnLostFocus(ez_control_t *self, void *ext_event_info)\n{\n\tif (self->int_flags & control_focused)\n\t{\n\t\treturn 0;\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnLostFocus, NULL);\n\n\treturn 0;\n}\n\n//\n// Control - The control was resized.\n//\nint EZ_control_OnResize(ez_control_t *self, void *ext_event_info)\n{\n\t// Calculate how much the width has changed so that we can\n\t// compensate by scrolling.\n\tint scroll_change_x = (self->prev_width - self->width);\n\tint scroll_change_y = (self->prev_height - self->height);\n\n\tez_control_t *payload = NULL;\n\tez_dllist_node_t *iter = self->children.head;\n\n\t// Calculate the gaps to the parents edges (used for anchoring, see OnParentResize for detailed explination).\n\tEZ_control_UpdateAnchorGap(self);\n\n\t// Make sure the virtual size never is smaller than the normal size of the control\n\t// and that it's never smaller than the virtual max size.\n\tEZ_control_SetVirtualSize(self, self->width, self->height);\n\n\t// First scroll the window so that we use the current virtual space fully\n\t// before expanding it. That is, only start expanding when the:\n\t// REAL_SIZE > VIRTUAL_MIN_SIZE\n\tif (scroll_change_x < 0)\n\t{\n\t\tEZ_control_SetScrollChange(self, scroll_change_x, 0);\n\t}\n\n\tif (scroll_change_y < 0)\n\t{\n\t\tEZ_control_SetScrollChange(self, 0, scroll_change_y);\n\t}\n\n\t// Tell the children we've resized.\n\twhile (iter)\n\t{\n\t\tpayload = (ez_control_t *)iter->payload;\n\t\tCONTROL_RAISE_EVENT(NULL, payload, ez_control_t, OnParentResize, NULL);\n\t\titer = iter->next;\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnResize, NULL);\n\n\t// Tell our parent we've moved.\n\tif (self->parent)\n\t{\n\t\tCONTROL_RAISE_EVENT(NULL, self->parent, ez_control_t, OnChildResize, self);\n\t}\n\n\treturn 0;\n}\n\n//\n// Control - The controls parent was resized.\n//\nint EZ_control_OnParentResize(ez_control_t *self, void *ext_event_info)\n{\n\tif (self->parent && (self->ext_flags & control_resizeable))\n\t{\n\t\tez_control_t *p\t\t\t= self->parent;\n\t\tint x\t\t\t\t\t= self->x;\n\t\tint y\t\t\t\t\t= self->y;\n\t\tint new_width\t\t\t= self->width;\n\t\tint new_height\t\t\t= self->height;\n\t\tqbool anchor_viewport\t= (self->ext_flags & control_anchor_viewport);\n//\t\tint parent_prev_width\t= anchor_viewport ? p->prev_width\t: p->prev_virtual_width;\n//\t\tint parent_prev_height\t= anchor_viewport ? p->prev_height\t: p->prev_virtual_height;\n\t\tint parent_width\t\t= anchor_viewport ? p->width\t\t: p->virtual_width; \n\t\tint parent_height\t\t= anchor_viewport ? p->height\t\t: p->virtual_height;\n\n\t\t//\n\t\t// Calculate our new position.\n\t\t//\n\t\t{\n\t\t\tint new_x = self->x;\n\t\t\tint new_y = self->y;\t\t\t\n\n\t\t\t// If we're exlusivly anchored to one edge, we change our position\n\t\t\t// based on the parents size.\n\t\t\tif ((self->anchor_flags == anchor_top) || (self->anchor_flags == anchor_bottom))\n\t\t\t{\n\t\t\t\tnew_x = Q_rint(parent_width * self->x_percent);\n\t\t\t}\n\t\t\telse if ((self->anchor_flags == anchor_left) || (self->anchor_flags == anchor_right))\n\t\t\t{\n\t\t\t\tnew_y = Q_rint(parent_height * self->y_percent);\n\t\t\t}\n\n\t\t\t// Only update the position if it has changed.\n\t\t\tif ((new_x != self->x) || (new_y != self->y))\n\t\t\t{\n\t\t\t\t// Only update the position if our calculated position is still within\n\t\t\t\t// the bounds of the parent. If we don't check for this, the control\n\t\t\t\t// will never be visible in the case were you position a child outside of\n\t\t\t\t// the parents viewport, then set the anchoring, and then resize the parent\n\t\t\t\t// to its final size. This is because the x/y_percentage vars are only\n\t\t\t\t// set on the OnAnchoringChanged event, where x_percent = x / parent_width.\n\t\t\t\t// if x is greater than parent_width x_percent will be > 1 which means\n\t\t\t\t// any position we calculate here will be outside of the parent control.\n\t\t\t\t// (Yes this is confusing :D).\n\t\t\t\tif ((new_x < parent_width) && (new_y < parent_height))\n\t\t\t\t{\n\t\t\t\t\tEZ_control_SetPosition(self, new_x, new_y);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ((self->anchor_flags & (anchor_left | anchor_right)) == (anchor_left | anchor_right))\n\t\t{\t\t\t\n\t\t\t// Set the new width so that the right side of the control is\n\t\t\t// still the same distance from the right side of the parent.\n\t\t\tnew_width = parent_width - (x + self->right_edge_gap);\n\t\t}\n\n\t\tif ((self->anchor_flags & (anchor_top | anchor_bottom)) == (anchor_top | anchor_bottom))\n\t\t{\n\t\t\tnew_height = parent_height - (y + self->bottom_edge_gap);\n\t\t}\n\n\t\t// Set the new size if it changed.\n\t\tif ((self->width != new_width) || (self->height != new_height))\n\t\t{\n\t\t\t// When the control is anchored to two opposit edges we want to stretch it\n\t\t\t// when it's parent is resized. If the parent becomes either\n\t\t\t// smaller or larger than the controls max/min size when doing this, it will\n\t\t\t// make us stop resizing the child. And since we want the child to\n\n\t\t\t// We need special behaviour when resizing a control that is anchored to two\n\t\t\t// opposite edges of it's parent (it should be stretched). A child control is placed\n\t\t\t// inside of a parent, and it's given a size and anchoring points. When resizing the\n\t\t\t// parent, we want to maintain the distance between the parents edges and the childs edges,\n\t\t\t// that is, if the childs right edge was 10 pixels from it's parents right edge before\n\t\t\t// we resized the parent, we want it to be the same afterwards also. We achieve this by\n\t\t\t// resizing the child (stretching it) to maintain the same gap. \n\t\t\t//\n\t\t\t// This all works fine if the child is allowed to have any size (even negative), since it will\n\t\t\t// always maintain the same gap between the edges. The problem arises when you introduce the \n\t\t\t// possibility for a control to have a min/max size, in this case we need to stop updating \n\t\t\t// the gap when the min/max size has been reached, and remember the last used gap size until\n\t\t\t// the parent goes back to a size that let's its child keep the correct edge gap without \n\t\t\t// violating its size bounds again. \n\t\t\t//\n\t\t\t// We do this by only updating the gap size either when setting new anchoring points,\n\t\t\t// or when someone explicitly changes the size of the child control. Therefore any resize of the child\n\t\t\t// control that is triggered by its parent being resized doesn't produce a new gap size.\n\t\t\tself->int_flags &= ~control_update_anchorgap;\n\t\t\t\n\t\t\tEZ_control_SetSize(self, new_width, new_height);\n\n\t\t\tself->int_flags |= control_update_anchorgap;\n\t\t}\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnParentResize, NULL);\n\n\treturn 0;\n}\n\n//\n// Control - The minimum virtual size has changed for the control.\n//\nint EZ_control_OnMinVirtualResize(ez_control_t *self, void *ext_event_info)\n{\n\tint new_virtual_width\t= max(self->virtual_width_min, self->virtual_width);\n\tint new_virtual_height\t= max(self->virtual_height_min, self->virtual_height);\n\n\t// Make sure the virtual size isn't smaller than the new minimum virtual size.\n\tif ((new_virtual_width != self->virtual_width) || (new_virtual_height != self->virtual_height))\n\t{\n\t\tEZ_control_SetVirtualSize(self, new_virtual_width, new_virtual_height);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnMinVirtualResize, NULL);\n\n\treturn 0;\n}\n\n//\n// Control - Hide a control if its parent is hidden. We don't unset control_visible for this\n//\t\t\tbecause we want to let any child inside of a control to decide if it should be\n//\t\t\tvisible or not on it's own, if we just set control_visible here we'd overwrite\n//\t\t\tthe childs visibility.\n//\nstatic void EZ_control_SetHiddenByParent(ez_control_t *self, qbool hidden)\n{\n\tez_dllist_node_t *iter\t= self->children.head;\n\tez_control_t *child\t\t= NULL;\n\n\tSET_FLAG(self->int_flags, control_hidden_by_parent, hidden);\n\n\t// Show or hide our children also.\n\twhile(iter)\n\t{\n\t\tchild = (ez_control_t *)iter->payload;\n\t\tEZ_control_SetHiddenByParent(child, hidden);\n\t\titer = iter->next;\n\t}\n}\n\n//\n// Control - Visibility changed.\n//\nint EZ_control_OnVisibilityChanged(ez_control_t *self, void *ext_event_info)\n{\n\t// Hide any children.\n\tEZ_control_SetHiddenByParent(self, !(self->ext_flags & control_visible));\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnVisibilityChanged, NULL);\n\treturn 0;\n}\n\n//\n// Label - The flags for the control changed.\n//\nint EZ_control_OnFlagsChanged(ez_control_t *self, void *ext_event_info)\n{\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnFlagsChanged, NULL);\n\treturn 0;\n}\n\n//\n// Control - OnResizeHandleThicknessChanged event.\n//\nint EZ_control_OnResizeHandleThicknessChanged(ez_control_t *self, void *ext_event_info)\n{\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnResizeHandleThicknessChanged, NULL);\n\treturn 0;\n}\n\n//\n// Label - The virtual size of the control has changed.\n//\nint EZ_control_OnVirtualResize(ez_control_t *self, void *ext_event_info)\n{\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnVirtualResize, NULL);\n\n\treturn 0;\n}\n\n//\n// Control - The control was moved.\n//\nint EZ_control_OnMove(ez_control_t *self, void *ext_event_info)\n{\t\n\tez_control_t *child\t\t= NULL;\n\tez_dllist_node_t *iter\t= self->children.head;\n\tqbool anchor_viewport\t= (self->ext_flags & control_anchor_viewport);\n\tint parent_x\t\t\t= 0;\n\tint parent_y\t\t\t= 0;\n\n\tself->prev_absolute_x = self->absolute_x;\n\tself->prev_absolute_y = self->absolute_y;\n\n\t// Get the position of the area we're anchoring to. Normal behaviour is to anchor to the virtual area.\n\tif (self->parent)\n\t{\n\t\tparent_x = anchor_viewport ? self->parent->absolute_x : self->parent->absolute_virtual_x;\n\t\tparent_y = anchor_viewport ? self->parent->absolute_y : self->parent->absolute_virtual_y;\n\t}\n\n\t// Update the absolute screen position based on the parents position.\n\tself->absolute_x = (self->parent ? parent_x : 0) + self->x;\n\tself->absolute_y = (self->parent ? parent_y : 0) + self->y;\n\n\tif (self->parent)\n\t{\n\t\t// If the control has a parent, position it in relation to it\n\t\t// and the way it's anchored to it.\n//\t\tint parent_prev_width\t= anchor_viewport ? self->parent->prev_width\t: self->parent->prev_virtual_width;\n//\t\tint parent_prev_height\t= anchor_viewport ? self->parent->prev_height\t: self->parent->prev_virtual_height;\n\t\tint parent_width\t\t= anchor_viewport ? self->parent->width\t\t\t: self->parent->virtual_width; \n\t\tint parent_height\t\t= anchor_viewport ? self->parent->height\t\t: self->parent->virtual_height;\n\t\t\n\t\t/*\n\t\tEZ_control_CalculatePositionPercentages(self);\n\n\t\t// If we're exlusivly anchored to one edge, we change our position\n\t\t// based on the parents width.\n\t\tif ((self->anchor_flags == anchor_top) || (self->anchor_flags == anchor_bottom))\n\t\t{\n\t\t\tself->x = Q_rint(parent_width * self->x_percent);\n\t\t}\n\t\telse if ((self->anchor_flags == anchor_left) || (self->anchor_flags == anchor_right)\n\t\t{\n\t\t\tself->y = Q_rint(parent_height * self->y_percent);\n\t\t}*/\n\n\t\t// If we're anchored to both the left and right part of the parent we position\n\t\t// based on the parents left pos \n\t\t// (We will stretch to the right if the control is resizable and if the parent is resized).\n\t\tif ((self->anchor_flags & anchor_left) && !(self->anchor_flags & anchor_right))\n\t\t{\n\t\t\tself->absolute_x = parent_x + self->x;\n\t\t}\n\t\telse if ((self->anchor_flags & anchor_right) && !(self->anchor_flags & anchor_left))\n\t\t{\n\t\t\tself->absolute_x = parent_x + parent_width + (self->x - self->width);\n\t\t}\n\n\t\tif ((self->anchor_flags & anchor_top) && !(self->anchor_flags & anchor_bottom))\n\t\t{\n\t\t\tself->absolute_y = parent_y + self->y;\n\t\t}\n\t\telse if ((self->anchor_flags & anchor_bottom) && !(self->anchor_flags & anchor_top))\n\t\t{\n\t\t\tself->absolute_y = parent_y + parent_height + (self->y - self->height);\n\t\t}\n\t}\n\telse\n\t{\n\t\t// No parent, position based on screen coordinates.\n\t\tself->absolute_x = self->x;\n\t\tself->absolute_y = self->y;\n\t}\n\n\t// We need to move the virtual area of the control also.\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnScroll, NULL);\n\n\t// Tell the children we've moved.\n\twhile(iter)\n\t{\n\t\tchild = (ez_control_t *)iter->payload;\n\t\tCONTROL_RAISE_EVENT(NULL, child, ez_control_t, OnMove, NULL);\n\t\titer = iter->next;\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnMove, NULL);\n\n\t// Tell our parent we've moved.\n\tif (self->parent)\n\t{\n\t\tCONTROL_RAISE_EVENT(NULL, self->parent, ez_control_t, OnChildMoved, self);\n\t}\n\n\treturn 0;\n}\n\n//\n// Control - On scroll event.\n//\nint EZ_control_OnScroll(ez_control_t *self, void *ext_event_info)\n{\n\tez_control_t *child = NULL;\n\tez_dllist_node_t *iter = self->children.head;\n\n\tself->absolute_virtual_x = self->absolute_x - self->virtual_x;\n\tself->absolute_virtual_y = self->absolute_y - self->virtual_y;\n\n\t// Tell the children we've scrolled. And make them recalculate their\n\t// absolute position by telling them they've moved.\n\twhile(iter)\n\t{\n\t\tchild = (ez_control_t *)iter->payload;\n\t\tCONTROL_RAISE_EVENT(NULL, child, ez_control_t, OnMove, NULL);\n\t\tCONTROL_RAISE_EVENT(NULL, child, ez_control_t, OnParentScroll, NULL);\n\t\titer = iter->next;\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnScroll, NULL);\n\n\treturn 0;\n}\n\n//\n// Control - On parent scroll event.\n//\nint EZ_control_OnParentScroll(ez_control_t *self, void *ext_event_info)\n{\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnParentScroll, NULL);\n\treturn 0;\n}\n\n//\n// Control - Layouts children.\n//\nint EZ_control_OnLayoutChildren(ez_control_t *self, void *ext_event_info)\n{\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnLayoutChildren, NULL);\n\treturn 0;\n}\n\n//\n// Control - Event for when a new event handler is set for an event.\n//\nint EZ_control_OnEventHandlerChanged(ez_control_t *self, void *ext_event_info)\n{\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnEventHandlerChanged, NULL);\n\treturn 0;\n}\n\n//\n// Control - Draw the background.\n//\nstatic void EZ_control_DrawBackgroundImage(ez_control_t *self)\n{\n\tint edge_size;\t\t// The number of pixel from the edge of the texture \n\tint edge_size2;\t\t// to use when drawing the buttons edges.\n\n\tmpic_t *pic = self->background;\n\n\tint x, y;\n\n\tif (!pic)\n\t{\n\t\treturn;\n\t}\n\n\t// Calculate the number of pixels to use for the edges.\n\tedge_size = Q_rint(self->bg_edge_size_ratio * pic->width);\n\tedge_size2 = (2 * edge_size);\n\n\tEZ_control_GetDrawingPosition(self, &x, &y);\n\n\t// Center background.\n\tif (self->ext_flags & control_bg_tile_center)\n\t{\n\t\t// Tiled.\n\t\tDraw_SubPicTiled((x + edge_size), (y + edge_size), \n\t\t\t(self->width - edge_size2), (self->height - edge_size2),\n\t\t\tpic, \n\t\t\tedge_size, edge_size, \n\t\t\t(pic->width - edge_size2), (pic->height - edge_size2),\n\t\t\tself->bg_opacity);\n\t}\n\telse\n\t{\n\t\t// Stretch the image.\n\t\tDraw_FitAlphaSubPic((x + edge_size), (y + edge_size), (self->width - edge_size2), (self->height - edge_size2), \n\t\t\tpic, edge_size, edge_size, (pic->width - edge_size2), (pic->height - edge_size2), self->bg_opacity);\n\t}\n\n\t// Only draw an edge if we have something to draw ;)\n\tif (edge_size > 0)\n\t{\n\t\tif (self->ext_flags & control_bg_tile_edges)\n\t\t{\n\t\t\t// Tiled.\n\n\t\t\t// Top center.\n\t\t\tDraw_SubPicTiled((x + edge_size), y, (self->width - edge_size2), edge_size, \n\t\t\t\t\t\t\tpic, edge_size, 0, (pic->width - edge_size2), edge_size, self->bg_opacity);\n\n\t\t\t// Bottom center.\n\t\t\tDraw_SubPicTiled((x + edge_size), (y + self->height - edge_size), (self->width - edge_size2), edge_size, \n\t\t\t\t\t\t\tpic, edge_size, (pic->height - edge_size), (pic->width - edge_size2), edge_size, self->bg_opacity);\n\t\t\t\n\t\t\t// Left center.\n\t\t\tDraw_SubPicTiled(x, (y + edge_size), edge_size, (self->height - edge_size2), \n\t\t\t\t\t\t\tpic, 0, edge_size, edge_size, (pic->height - edge_size2), self->bg_opacity);\n\n\t\t\t// Right center.\n\t\t\tDraw_SubPicTiled((x + self->width - edge_size), (y + edge_size), edge_size, (self->height - edge_size2), \n\t\t\t\t\t\t\tpic, (pic->width - edge_size), edge_size, edge_size, (pic->height - edge_size2), self->bg_opacity);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Stretched.\n\n\t\t\t// Top center.\n\t\t\tDraw_FitAlphaSubPic((x + edge_size), y, (self->width - edge_size), edge_size, \n\t\t\t\t\t\t\t\tpic, edge_size, 0, (pic->width - edge_size2), edge_size, self->bg_opacity);\n\n\t\t\t// Bottom center.\n\t\t\tDraw_FitAlphaSubPic((x + edge_size), (y + self->height - edge_size), (self->width - edge_size), edge_size, \n\t\t\t\t\t\t\t\tpic, edge_size, (pic->height - edge_size), (pic->width - edge_size2), edge_size, self->bg_opacity);\n\n\t\t\t// Left center.\n\t\t\tDraw_FitAlphaSubPic(x, (y + edge_size), edge_size, (self->height - edge_size2), \n\t\t\t\t\t\t\t\tpic, 0, edge_size, edge_size, (pic->height - edge_size2), self->bg_opacity);\n\n\t\t\t// Right center.\n\t\t\tDraw_FitAlphaSubPic((x + self->width - edge_size), (y + edge_size), edge_size, (self->height - edge_size2), \n\t\t\t\t\t\t\t\tpic, (pic->width - edge_size), edge_size, edge_size, (pic->height - edge_size2), self->bg_opacity);\n\t\t}\n\n\t\t// Top left corner.\n\t\tDraw_AlphaSubPic(x, y, pic, 0, 0, edge_size, edge_size, self->bg_opacity);\n\n\t\t// Top right corner.\n\t\tDraw_AlphaSubPic((x + self->width - edge_size), y, pic, (pic->width - edge_size), 0, edge_size, edge_size, self->bg_opacity);\n\n\t\t// Bottom left corner.\n\t\tDraw_AlphaSubPic(x, (y + self->height - edge_size), pic, 0, (pic->height - edge_size), edge_size, edge_size, self->bg_opacity);\n\n\t\t// Bottom right corner.\n\t\tDraw_AlphaSubPic((x + self->width - edge_size), (y + self->height - edge_size), \n\t\t\t\t\tpic, \n\t\t\t\t\t(pic->width - edge_size), (pic->height - edge_size), \n\t\t\t\t\tedge_size, edge_size, self->bg_opacity);\n\t}\n}\n\n//\n// Control - Draws the control.\n//\nint EZ_control_OnDraw(ez_control_t *self, void *ext_event_info)\n{\n\tint x, y;\n\tEZ_control_GetDrawingPosition(self, &x, &y);\n\n\tif (self->background)\n\t{\n\t\tEZ_control_DrawBackgroundImage(self);\n\t}\n\telse if (self->background_color[3] > 0)\n\t{\n\t\tDraw_AlphaRectangleRGB(self->absolute_x, self->absolute_y, self->width, self->height, 1, true, RGBAVECT_TO_COLOR(self->background_color));\n\t}\n\n\t// TODO : Remove this test stuff.\n\t/*\n\tDraw_String(x, y, va(\"%s%s%s%s\",\n\t\t\t((self->int_flags & control_moving) ? \"M\" : \" \"),\n\t\t\t((self->int_flags & control_focused) ? \"F\" : \" \"),\n\t\t\t((self->int_flags & control_clicked) ? \"C\" : \" \"),\n\t\t\t((self->int_flags & control_resizing_left) ? \"R\" : \" \")\n\t\t\t));\n\t*/\n\n\t// Draw control specifics.\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnDraw, NULL);\n\n\treturn 0;\n}\n\n//\n// Control - Key down event.\n//\nint EZ_control_OnKeyDown(ez_control_t *self, int key, int unichar)\n{\n\tint key_handled = false;\n\n\tCONTROL_EVENT_HANDLER_CALL(&key_handled, self, ez_control_t, OnKeyDown, key, unichar);\n\n\treturn key_handled;\n}\n\n//\n// Control - Key up event.\n//\nint EZ_control_OnKeyUp(ez_control_t *self, int key, int unichar)\n{\n\tint key_handled = false;\n\n\tCONTROL_EVENT_HANDLER_CALL(&key_handled, self, ez_control_t, OnKeyUp, key, unichar);\n\n\treturn key_handled;\n}\n\n//\n// Control - Key event.\n//\nint EZ_control_OnKeyEvent(ez_control_t *self, int key, int unichar, qbool down)\n{\n\tint key_handled\t\t\t= false;\n//\tez_control_t *payload\t= NULL;\n//\tez_dllist_node_t *iter\t= self->children.head;\n\n\tif (down)\n\t{\n\t\tCONTROL_RAISE_EVENT(&key_handled, self, ez_control_t, OnKeyDown, key, unichar);\n\t}\n\telse\n\t{\n\t\tCONTROL_RAISE_EVENT(&key_handled, self, ez_control_t, OnKeyUp, key, unichar);\n\t}\n\n\tif (key_handled)\n\t{\n\t\treturn true;\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(&key_handled, self, ez_control_t, OnKeyEvent, key, unichar, down);\n\n\treturn key_handled;\n}\n\ntypedef enum\n{\n\tRESIZE_LEFT\t\t= (1 << 0),\n\tRESIZE_RIGHT\t= (1 << 1),\n\tRESIZE_UP\t\t= (1 << 2),\n\tRESIZE_DOWN\t\t= (1 << 3)\n} resize_direction_t;\n\n//\n// Control - Resizes the control by moving the left corner.\n//\nstatic void EZ_control_ResizeByDirection(ez_control_t *self, mouse_state_t *ms, int delta_x, int delta_y, resize_direction_t direction)\n{\n\tint width\t\t\t\t= self->width;\n\tint height\t\t\t\t= self->height;\n\tint x\t\t\t\t\t= self->x;\n\tint y\t\t\t\t\t= self->y;\n\tqbool resizing_width\t= (direction & RESIZE_LEFT) || (direction & RESIZE_RIGHT);\n\tqbool resizing_height\t= (direction & RESIZE_UP) || (direction & RESIZE_DOWN);\n\n\tif (resizing_width)\n\t{\n\t\t// Set the new width based on how much the mouse has moved\n\t\t// keeping it within the allowed bounds.\n\t\twidth = self->width + ((direction & RESIZE_LEFT) ? delta_x : -delta_x);\n\t\tclamp(width, self->width_min, self->width_max);\n\n\t\t// Move the control to counteract the resizing when resizing to the left.\n\t\tx = self->x + ((direction & RESIZE_LEFT) ? (self->width - width) : 0);\n\t}\n\n\tif (resizing_height)\n\t{\n\t\theight = self->height + ((direction & RESIZE_UP) ? delta_y : -delta_y);\n\t\tclamp(height, self->height_min, self->height_max);\n\t\ty = self->y + ((direction & RESIZE_UP) ? (self->height - height) : 0);\n\t}\n\n\t// If the child should be contained within it's parent the\n\t// mouse should stay within the parent also when resizing the control.\n\tif (ms && CONTROL_IS_CONTAINED(self))\n\t{\n\t\tif (resizing_width && !POINT_X_IN_CONTROL_DRAWBOUNDS(self->parent, ms->x))\n\t\t{\n\t\t\tms->x = ms->x_old;\n\t\t\tx = self->x;\n\t\t\twidth = self->width;\n\t\t}\n\n\t\tif (resizing_height && !POINT_Y_IN_CONTROL_DRAWBOUNDS(self->parent, ms->y))\n\t\t{\n\t\t\tms->y = ms->y_old;\n\t\t\ty = self->y;\n\t\t\theight = self->height;\n\t\t}\n\t}\n\n\tEZ_control_SetSize(self, width, height);\n\tEZ_control_SetPosition(self, x, y);\n}\n\n//\n// Control -\n// The initial mouse event is handled by this, and then raises more specialized event handlers\n// based on the new mouse state.\n// \n// NOTICE! When extending this event you need to make sure that you need to tell the framework\n// that you've handled all mouse events that happened within the controls bounds in your own\n// implementation by returning true whenever the mouse is inside the control.\n// This can easily be done with the following macro: MOUSE_INSIDE_CONTROL(self, ms); \n// If this is not done, all mouse events will \"fall through\" to controls below.\n//\nint EZ_control_OnMouseEvent(ez_control_t *self, mouse_state_t *ms)\n{\n\tmouse_state_t *old_ms\t\t\t= &self->prev_mouse_state;\n\tqbool mouse_inside\t\t\t\t= false;\n\tqbool prev_mouse_inside\t\t\t= false;\n//\tqbool mouse_inside_parent\t\t= false;\n//\tqbool prev_mouse_inside_parent\t= false;\n//\tqbool is_contained\t\t\t\t= CONTROL_IS_CONTAINED(self);\n\tint mouse_handled_tmp\t\t\t= false;\n\tint mouse_handled\t\t\t\t= false;\n\tint mouse_delta_x\t\t\t\t= 0;\n\tint mouse_delta_y\t\t\t\t= 0;\n\n\tif (!ms)\n\t{\n\t\tSys_Error(\"EZ_control_OnMouseEvent(): mouse_state_t NULL\\n\");\n\t}\n\n\t// Ignore the mouse?\n\tif (self->ext_flags & control_ignore_mouse)\n\t{\n\t\treturn false;\n\t}\n\n\tmouse_delta_x = Q_rint(ms->x_old - ms->x);\n\tmouse_delta_y = Q_rint(ms->y_old - ms->y);\n\n\tmouse_inside\t\t= POINT_IN_CONTROL_DRAWBOUNDS(self, ms->x, ms->y);\n\tprev_mouse_inside\t= POINT_IN_CONTROL_DRAWBOUNDS(self, ms->x_old, ms->y_old);\n\n\t// Raise more specific events.\n\tif (mouse_inside)\n\t{\n\t\tif (!prev_mouse_inside)\n\t\t{\n\t\t\t// Were we inside of the control last time? Otherwise we've just entered it.\n\t\t\tCONTROL_RAISE_EVENT(&mouse_handled_tmp, self, ez_control_t, OnMouseEnter, ms);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// We're hovering the control.\n\t\t\tCONTROL_RAISE_EVENT(&mouse_handled_tmp, self, ez_control_t, OnMouseHover, ms);\n\t\t}\n\n\t\tmouse_handled = (mouse_handled || mouse_handled_tmp);\n\n\t\tif (ms->button_down)\n\t\t{\n\t\t\t// Mouse down.\n\t\t\tCONTROL_RAISE_EVENT(&mouse_handled_tmp, self, ez_control_t, OnMouseDown, ms);\n\t\t\tmouse_handled = (mouse_handled || mouse_handled_tmp);\n\t\t}\n\n\t\tif (ms->button_up)\n\t\t{\n\t\t\t// Mouse up.\n\t\t\tCONTROL_RAISE_EVENT(&mouse_handled_tmp, self, ez_control_t, OnMouseUp, ms);\n\t\t\tmouse_handled = (mouse_handled || mouse_handled_tmp);\n\t\t}\n\t}\n\telse if (prev_mouse_inside)\n\t{\n\t\t// Mouse leave.\n\t\tCONTROL_RAISE_EVENT(&mouse_handled_tmp, self, ez_control_t, OnMouseLeave, ms);\n\t\tmouse_handled = (mouse_handled || mouse_handled_tmp);\n\t}\n\n\t// Send a mouse up event to controls if the mouse is outside also.\n\tif (ms->button_up && (ms->button_up != old_ms->button_up))\n\t{\n\t\t// Mouse up.\n\t\tCONTROL_RAISE_EVENT(&mouse_handled_tmp, self, ez_control_t, OnMouseUpOutside, ms);\n\t\tmouse_handled = (mouse_handled || mouse_handled_tmp);\n\t}\n\n\t// Make sure we remove the click flag always when releasing the button\n\t// not just when we're hovering above the control.\n\tif (ms->button_up && (ms->button_up != old_ms->button_up))\n\t{\n\t\tself->int_flags &= ~control_clicked;\n\t}\n\n\t// TODO : Move these to new methods.\n\n\tif (!mouse_handled)\n\t{\n\t\t// Check for moving and resizing.\n\t\tif ((self->int_flags & control_resizing_left)\n\t\t || (self->int_flags & control_resizing_right)\n\t\t || (self->int_flags & control_resizing_top)\n\t\t || (self->int_flags & control_resizing_bottom))\n\t\t{\n\t\t\t// These can be combined to grab the corners for resizing.\n\n\t\t\t// Resize by width.\n\t\t\tif (self->int_flags & control_resizing_left)\n\t\t\t{\n\t\t\t\tEZ_control_ResizeByDirection(self, ms, mouse_delta_x, mouse_delta_y, RESIZE_LEFT);\n\t\t\t\tmouse_handled = true;\n\t\t\t}\n\t\t\telse if (self->int_flags & control_resizing_right)\n\t\t\t{\n\t\t\t\tEZ_control_ResizeByDirection(self, ms, mouse_delta_x, mouse_delta_y, RESIZE_RIGHT);\n\t\t\t\tmouse_handled = true;\n\t\t\t}\n\n\t\t\t// Resize by height.\n\t\t\tif (self->int_flags & control_resizing_top)\n\t\t\t{\n\t\t\t\tEZ_control_ResizeByDirection(self, ms, mouse_delta_x, mouse_delta_y, RESIZE_UP);\n\t\t\t\tmouse_handled = true;\n\t\t\t}\n\t\t\telse if (self->int_flags & control_resizing_bottom)\n\t\t\t{\n\t\t\t\tEZ_control_ResizeByDirection(self, ms, mouse_delta_x, mouse_delta_y, RESIZE_DOWN);\n\t\t\t\tmouse_handled = true;\n\t\t\t}\n\t\t}\n\t\telse if (self->int_flags & control_moving)\n\t\t{\n\t\t\t// Root control will be moved relative to the screen,\n\t\t\t// others relative to their parent.\n\t\t\tint m_delta_x = Q_rint(ms->x - ms->x_old);\n\t\t\tint m_delta_y = Q_rint(ms->y - ms->y_old);\n\t\t\tint x = self->x + m_delta_x;\n\t\t\tint y = self->y + m_delta_y;\n\n\t\t\t// Should the control be contained within it's parent?\n\t\t\t// Then don't allow the mouse to move outside the parent\n\t\t\t// while moving the control.\n\t\t\tif (CONTROL_IS_CONTAINED(self))\n\t\t\t{\n\t\t\t\tif (!POINT_X_IN_CONTROL_DRAWBOUNDS(self->parent, ms->x))\n\t\t\t\t{\n\t\t\t\t\tms->x = ms->x_old;\n\t\t\t\t\tx = self->x;\n\t\t\t\t\tmouse_handled = true;\n\t\t\t\t}\n\n\t\t\t\tif (!POINT_Y_IN_CONTROL_DRAWBOUNDS(self->parent, ms->y))\n\t\t\t\t{\n\t\t\t\t\tms->y = ms->y_old;\n\t\t\t\t\ty = self->y;\n\t\t\t\t\tmouse_handled = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (self->parent && (self->ext_flags & control_move_parent))\n\t\t\t{\n\t\t\t\t// Move the parent instead of just the control, the parent will in turn move the control.\n\t\t\t\t// TODO : Do we need to keep the mouse inside the parents parent here also?\n\t\t\t\tEZ_control_SetPosition(self->parent, (self->parent->x + m_delta_x), (self->parent->y + m_delta_y));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tEZ_control_SetPosition(self, x, y);\n\t\t\t}\n\n\t\t\tmouse_handled = true;\n\t\t}\n\t}\n\n\t// Let any event handler run if the mouse event wasn't handled.\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled_tmp, self, ez_control_t, OnMouseEvent, ms);\n\tmouse_handled = (mouse_handled || mouse_handled_tmp);\n\n\t// Save the mouse state for the next time we check.\n\tself->prev_mouse_state = *ms;\n\n\treturn mouse_handled;\n}\n\n//\n// Control - The mouse was pressed and then released within the bounds of the control.\n//\nint EZ_control_OnMouseClick(ez_control_t *self, mouse_state_t *mouse_state)\n{\n\tint mouse_handled = false;\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled, self, ez_control_t, OnMouseClick, mouse_state);\n\treturn mouse_handled;\n}\n\n//\n// Control - The mouse entered the controls bounds.\n//\nint EZ_control_OnMouseEnter(ez_control_t *self, mouse_state_t *mouse_state)\n{\n\tint mouse_handled = false;\n\tself->int_flags |= control_mouse_over;\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled, self, ez_control_t, OnMouseEnter, mouse_state);\n\treturn mouse_handled;\n}\n\n//\n// Control - The mouse left the controls bounds.\n//\nint EZ_control_OnMouseLeave(ez_control_t *self, mouse_state_t *mouse_state)\n{\n\tint mouse_handled = false;\n\n\t// Stop moving since the mouse is outside the control.\n\tself->int_flags &= ~(/*control_moving |*/ control_mouse_over);\n\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled, self, ez_control_t, OnMouseLeave, mouse_state);\n\treturn mouse_handled;\n}\n\n//\n// Control - A mouse button was released either inside or outside of the control.\n//\nint EZ_control_OnMouseUpOutside(ez_control_t *self, mouse_state_t *ms)\n{\n\tint mouse_handled_tmp\t= false;\n\tint mouse_handled\t\t= false;\n\n\t// Stop moving / resizing.\n\tself->int_flags &= ~(control_moving | control_resizing_left | control_resizing_right | control_resizing_top | control_resizing_bottom);\n\n\t// Call event handler.\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled_tmp, self, ez_control_t, OnMouseUpOutside, ms);\n\tmouse_handled = (mouse_handled || mouse_handled_tmp);\n\n\treturn mouse_handled;\n}\n\n//\n// Control - A mouse button was released within the bounds of the control.\n//\nint EZ_control_OnMouseUp(ez_control_t *self, mouse_state_t *mouse_state)\n{\n\tint mouse_handled_tmp\t= false;\n\tint mouse_handled\t\t= false;\n\n\t// Stop moving / resizing.\n\tself->int_flags &= ~(control_moving | control_resizing_left | control_resizing_right | control_resizing_top | control_resizing_bottom);\n\n\t// Raise a click event.\n\tif (self->int_flags & control_clicked)\n\t{\n\t\tself->int_flags &= ~control_clicked;\n\t\tCONTROL_RAISE_EVENT(&mouse_handled_tmp, self, ez_control_t, OnMouseClick, mouse_state);\n\t\tmouse_handled = (mouse_handled || mouse_handled_tmp);\n\t}\n\n\t// Call event handler.\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled_tmp, self, ez_control_t, OnMouseUp, mouse_state);\n\tmouse_handled = (mouse_handled || mouse_handled_tmp);\n\n\treturn mouse_handled;\n}\n\n//\n// Control - A mouse button was pressed within the bounds of the control.\n//\nint EZ_control_OnMouseDown(ez_control_t *self, mouse_state_t *ms)\n{\n\tint mouse_handled_tmp\t= false;\n\tint mouse_handled\t\t= false;\n\n\tif (!(self->ext_flags & control_enabled))\n\t{\n\t\treturn false;\n\t}\n\n\t// Make sure the current control is focused.\n\tif (self->control_tree->focused_node)\n\t{\n\t\t// If the focused node isn't this one, refocus.\n\t\tif (self->control_tree->focused_node->payload != self)\n\t\t{\n\t\t\tmouse_handled = EZ_control_SetFocus(self);\n\t\t}\n\t}\n\telse\n\t{\n\t\t// If there's no focused node, focus on this one.\n\t\tmouse_handled = EZ_control_SetFocus(self);\n\t}\n\n\tif (self->int_flags & control_focused)\n\t{\n\t\t// Check if the mouse is at the edges of the control, and turn on the correct reszie mode if it is.\n\t\tif (((self->ext_flags & control_resize_h) || (self->ext_flags & control_resize_v)) && (ms->button_down == 1))\n\t\t{\n\t\t\tif (self->ext_flags & control_resize_h)\n\t\t\t{\n\t\t\t\t// Left side of the control.\n\t\t\t\tif (POINT_IN_RECTANGLE(ms->x, ms->y,\n\t\t\t\t\tself->absolute_x, self->absolute_y,\n\t\t\t\t\tself->resize_handle_thickness, self->height))\n\t\t\t\t{\n\t\t\t\t\tself->int_flags |= control_resizing_left;\n\t\t\t\t\tmouse_handled = true;\n\t\t\t\t}\n\n\t\t\t\t// Right side of the control.\n\t\t\t\tif (POINT_IN_RECTANGLE(ms->x, ms->y,\n\t\t\t\t\tself->absolute_x + self->width - self->resize_handle_thickness, self->absolute_y,\n\t\t\t\t\tself->resize_handle_thickness, self->height))\n\t\t\t\t{\n\t\t\t\t\tself->int_flags |= control_resizing_right;\n\t\t\t\t\tmouse_handled = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (self->ext_flags & control_resize_v)\n\t\t\t{\n\t\t\t\t// Top of the control.\n\t\t\t\tif (POINT_IN_RECTANGLE(ms->x, ms->y,\n\t\t\t\t\tself->absolute_x, self->absolute_y,\n\t\t\t\t\tself->width, self->resize_handle_thickness))\n\t\t\t\t{\n\t\t\t\t\tself->int_flags |= control_resizing_top;\n\t\t\t\t\tmouse_handled = true;\n\t\t\t\t}\n\n\t\t\t\t// Bottom of the control.\n\t\t\t\tif (POINT_IN_RECTANGLE(ms->x, ms->y,\n\t\t\t\t\tself->absolute_x, self->absolute_y + self->height - self->resize_handle_thickness,\n\t\t\t\t\tself->width, self->resize_handle_thickness))\n\t\t\t\t{\n\t\t\t\t\tself->int_flags |= control_resizing_bottom;\n\t\t\t\t\tmouse_handled = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// The control is being moved.\n\t\tif ((self->ext_flags & control_movable) && (ms->button_down == 1))\n\t\t{\n\t\t\tself->int_flags |= control_moving;\n\t\t\tmouse_handled = true;\n\t\t}\n\n\t\tif (ms->button_down)\n\t\t{\n\t\t\tself->int_flags |= control_clicked;\n\t\t\tmouse_handled = true;\n\t\t}\n\t}\n\n\t/*\n\tTODO : Fix OnMouseClick\n\tif(!mouse_handled)\n\t{\n\t\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled, self, OnMouseClick, mouse_state);\n\t}\n\t*/\n\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled_tmp, self, ez_control_t, OnMouseDown, ms);\n\tmouse_handled = (mouse_handled || mouse_handled_tmp);\n\n\treturn mouse_handled;\n}\n\n/*\n// TODO : Add support for mouse wheel.\n//\n// Control - The mouse wheel was triggered within the bounds of the control.\n//\nint EZ_control_OnMouseWheel(ez_control_t *self, mouse_state_t *mouse_state)\n{\n\tint wheel_handled = false;\n\tCONTROL_EVENT_HANDLER_CALL(&wheel_handled, self, ez_control_t, OnMouseWheel, mouse_state);\n\treturn wheel_handled;\n}\n*/\n\n//\n// Control - The mouse is hovering within the bounds of the control.\n//\nint EZ_control_OnMouseHover(ez_control_t *self, mouse_state_t *mouse_state)\n{\n\tint mouse_handled = false;\n\n\tif (!mouse_handled)\n\t{\n\t\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled, self, ez_control_t, OnMouseHover, mouse_state);\n\t}\n\n\treturn mouse_handled;\n}\n\n//\n// Control - Sets the overall opacity of the control (including its children).\n//\nvoid EZ_control_SetOpacity(ez_control_t *self, float opacity)\n{\n\tself->opacity = bound(0, opacity, 1.0);\n\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnOpacityChanged, NULL);\n}\n\n//\n// Control - The opacity of the control has just changed.\n//\nint EZ_control_OnOpacityChanged(ez_control_t *self, void *ext_event_info)\n{\n\tez_control_t *child = NULL;\n\tez_dllist_node_t *iter = self->children.head;\n\n\t// Add our parents opacity to the equation.\n\tself->overall_opacity = self->parent ? (self->opacity * self->parent->overall_opacity) : self->opacity;\n\n\t// Tell the children we've changed opacity.\n\twhile (iter)\n\t{\n\t\tchild = (ez_control_t *)iter->payload;\n\t\tCONTROL_RAISE_EVENT(NULL, child, ez_control_t, OnOpacityChanged, NULL);\n\t\titer = iter->next;\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnOpacityChanged, NULL);\n\treturn 0;\n}\n\n//\n// Control - Resizes the virtual size of the control based on the position/size of the child\n//           to make sure that it fits within the parents scrollable area.\n//\nstatic void EZ_control_ResizeVirtualSizeBasedOnChild(ez_control_t *self, ez_control_t *child)\n{\n\tint child_right_edge\t= (child->x + child->width);\n\tint child_bottom_edge\t= (child->y + child->height);\n\tint new_vwidth\t\t\t= self->virtual_width;\n\tint new_vheight\t\t\t= self->virtual_height;\n\n\tif (child_right_edge > self->virtual_width)\n\t{\n\t\tnew_vwidth = self->virtual_width + child_right_edge;\n\t}\n\n\tif (child_bottom_edge > self->virtual_height)\n\t{\n\t\tnew_vheight = self->virtual_height + child_bottom_edge;\n\t}\n\n\tif ((new_vwidth != self->virtual_width) && (new_vheight != self->virtual_height))\n\t{\n\t\tEZ_control_SetVirtualSize(self, new_vwidth, new_vheight);\n\t}\n\n\t// TODO: Hmm should probably shrink the virtual size also, but then we'd have to loop through all children to make sure none are left out.\n}\n\n//\n// Control - A child control has been moved.\n//\nint EZ_control_OnChildMoved(ez_control_t *self, ez_control_t *child)\n{\n\tEZ_control_ResizeVirtualSizeBasedOnChild(self, child);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnChildMoved, child);\n\treturn 0;\n}\n\n//\n// Control - A child control has been resized.\n//\nint EZ_control_OnChildResize(ez_control_t *self, ez_control_t *child)\n{\n\tEZ_control_ResizeVirtualSizeBasedOnChild(self, child);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnChildResize, child);\n\treturn 0;\n}\n"
  },
  {
    "path": "src/ez_controls.h",
    "content": "#ifndef __EZ_CONTROLS_H__\n#define __EZ_CONTROLS_H__\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_controls.h,v 1.55 2007-10-27 14:51:15 cokeman1982 Exp $\n*/\n\n//\n// Description\n//\n// Basic design princilples\n// ------------------------\n// The base design principle for the framework is a \"light\" version of Object Orientated Programming (OOP).\n// That is, all controls are regarded as objects that has methods associated with them that are used\n// to manipulate them and change their inner state.\n//\n// Implementation\n// --------------\n// Each \"class\" in the framework consists of a struct and a set of methods, with the following name schemes:\n// Struct: ez_nameofobject_t\n// Method: EZ_name_of_object_NameOfMethod(...)\n// \n// All methods that are associated with a particular object ALWAYS has a pointer to the object's struct type as the first\n// argument of the function, that is:\n// EZ_nameofobject_NameOfMethod(ez_nameofobject_t *self, ...);\n//\n// This means that \"self\" in this case can be regarded as the keyword \"this\" usually used in OOP languages.\n// This way only objects that are of the type ez_nameofobject_t are allowed to run the method. A NULL pointer\n// must never be passed to a method.\n//\n// Each object has the following types of methods:\n//\n// - Create method:\n//   The create method allocates the memory of the object and then passes it on to the Init method.\n//\n// - Init method:\n//   The init method initializes all the variables of the object and sets all the function pointers\n//   for the different methods.\n//\n// - Set methods: \n//   Used to set properties of the object, such as changing the inner state of the object struct.\n//\n// - Get methods: \n//   Gets inner state from the object.\n//\n// - Event implementations: \n//   This is a core implementation for the object, such as OnDraw, that draws the control which will be \n//   called on all objects by the framework. An object must have an implementation for all of the events\n//   that are associated with it. An event is raised using CONTROL_RAISE_EVENT when something happens\n//   inside of the object, such as the width changes or a key is pressed.\n//   (See the comment for the CONTROL_RAISE_EVENT macro for more detail on how this works).\n//\n// - Event handlers: \n//   These are optional functions that are set by the user of the framework, these can be set for each of \n//   the events described above. For instance, this can be used for running some specified function when \n//   a button is pressed. These are only run after all event implementations for that particular event\n//   has been run in the class tree (see inheritance).\n//   (See the comment for CONTROL_EVENT_HANDLER_CALL for more detail on how this works).\n//\n// The events and event handlers associated with an object are specified in a separate struct named\n// \"ez_nameofobject_events_t\", which contains a set of function pointers to the different events associated\n// with the object type. Each object then has two variables of this struct type in it named \"events\"\n// and \"event_handlers\". All the function pointers in the events variable has to be set in the objects\n// init function, but the function pointers in the \"event_handlers\" variable doesn't need to be set at all,\n// they're set by the user of the object when needed.\n//\n// NOTICE! It is important that the contents of the object structs are NEVER changed directly, but always\n// to the methods specified for the object. This is an important principle in OOP programming, hiding\n// internal information from the outside world. Changing struct information directly might lead to crucial\n// events not being raised which in turn puts the object in an undefined state.\n//\n// Inheritance\n// -----------\n// An object in the framework can inherit functionality from other objects, and override / reimplement their\n// event methods (actually, more like extending than overriding to be precise). This is achieved by creating\n// a new object, and at the beginning of the objects struct we place the object of the type we want to inherit from.\n// That is:\n//\n// typedef struct ez_object2_s\n// {\n//\t\tez_object1_t super;\n//\t\t...\n// } ez_object2_t;\n//\n// It is now possible to cast ez_object2_t to ez_object1_t, and use it with any object1 methods:\n// ez_object2_t obj2 = EZ_object2_Create(...);\n// EZ_object1_SomeObject1Method((ez_object1_t *)obj2);\n//\n// To be able to call all the methods of a particular event implementation involving inheritance, and the only\n// run the event handler associated with that event ONCE at the end. That is, first let the control change it's\n// internal state, and then finally signaling the outside world. The macros CONTROL_RAISE_EVENT and \n// CONTROL_EVENT_HANDLER_CALL are used, see the comment for them below for more detailed information on how they work.\n//\n// Each object struct contains a CLASS_ID used to identify what type of object it is, this should be unique for\n// each object type. This is set at init of the control, and should never be changed after that.\n//\n// Control objects (ez_control_t / EZ_control_*)\n// ---------------------------------------------\n// Building on the principles described above the base object for all controls in the UI framework is defined\n// with the ez_control_t struct and EZ_control_* methods. This object implements all the base functionality that\n// are needed for a control, such as:\n// - Mouse handling\n//   * Each control gets an initial event whenever the mouse state changes in some way, \n//     mouse moved, or a button was pressed.\n//   * The event is processed and if the mouse was inside the control and a button was pressed for instance\n//     a more detailed event is raised, targeted only to that control. Examples of this:\n//     OnMouseDown, OnMouseUp, OnMouseHover, OnMouseEnter, OnMouseLeave...\n// - Key events\n// - Moving\n// - Resizing\n// - Scrolling\n//   * Each control has a visible area, and a \"virtual\" area, which can be scrolled within.\n//\n// The ez_control_t object also contains a list of children, sub-controls that are placed inside of that control\n//\n// All controls then inherits from this object, and extends the events it has defined, or adds new events.\n//\n// Control tree\n// ------------\n// To tie all these controls together, a root object exists called ez_tree_t. This tree has a pointer to\n// the root control of the tree, which all of the other controls are contained within (this control usually\n// has the same size as the screen). It also contains two lists, one is the draw list which holds the\n// order in which the controls should be drawn in. The other is the tab list, which holds the order that\n// the controls can be \"tabbed\" between (if the control is focusable).\n//\n// All instances of a control MUST be associated with a control tree that is passed to the creation function\n// of the control.\n//\n// All input is sent to the control tree, which in turn propagates them to the controls that should have it.\n// The control tree is also responsible for raising the OnDraw event for all the controls (in the order\n// specified in it's draw list).\n//\n// There can be any number of control trees running at the same time, but the intended practice is to\n// only use one at any given time.\n//\n// -- Cokeman 2007\n//\n\n#define POINT_IN_RECTANGLE(p_x, p_y, r_x, r_y, vid_width, vid_height) ((p_x >= r_x) && (p_y >= r_y) && (p_x <= (r_x + vid_width)) && (p_y <= (r_y + vid_height)))\n#define POINT_X_IN_BOUNDS(p_x, left, right)\t((p_x >= left) && (p_x <= right))\n#define POINT_Y_IN_BOUNDS(p_y, top, bottom)\t((p_y >= top)  && (p_y <= bottom))\n#define POINT_IN_BOUNDS(p_x, p_y, left, right, top, bottom)\t(POINT_X_IN_BOUNDS(p_x, left, right) && POINT_Y_IN_BOUNDS(p_y, top, bottom))\n\n#define POINT_X_IN_CONTROL_DRAWBOUNDS(ctrl, p_x) (POINT_X_IN_BOUNDS(p_x, (ctrl)->bound_left, (ctrl)->bound_right))\n#define POINT_Y_IN_CONTROL_DRAWBOUNDS(ctrl, p_y) (POINT_Y_IN_BOUNDS(p_y, (ctrl)->bound_top, (ctrl)->bound_bottom))\n#define POINT_IN_CONTROL_DRAWBOUNDS(ctrl, p_x, p_y) (POINT_X_IN_CONTROL_DRAWBOUNDS(ctrl, p_x) && POINT_Y_IN_CONTROL_DRAWBOUNDS(ctrl, p_y))\n#define POINT_IN_CONTROL_RECT(ctrl, p_x, p_y) POINT_IN_RECTANGLE(p_x, p_y, (ctrl)->absolute_x, (ctrl)->absolute_y, (ctrl)->width, (ctrl)->height)\n\n#define SET_FLAG(flag_var, flag, on) ((flag_var) = ((on) ? ((flag_var) | (flag)) : ((flag_var) & ~(flag))))\n\ntypedef enum ez_control_id_e\n{\n\tEZ_CONTROL_ID,\n\tEZ_BUTTON_ID,\n\tEZ_LABEL_ID,\n\tEZ_SLIDER_ID,\n\tEZ_SCROLLBAR_ID,\n\tEZ_SCROLLPANE_ID,\n\tEZ_WINDOW_ID,\n\tEZ_LISTVIEW_ID,\n\tEZ_LISTVIEWITEM_ID\n} ez_control_id_t;\n\n// =========================================================================================\n// Double Linked List\n// =========================================================================================\n\n// \n// Double Linked List - Function pointer types.\n//\ntypedef int (* PtFuncCompare)(const void *, const void *);\n\ntypedef struct ez_dllist_node_s\n{\n\tstruct ez_dllist_node_s *next;\n\tstruct ez_dllist_node_s *previous;\n\tvoid *payload;\n} ez_dllist_node_t;\n\ntypedef struct ez_double_linked_list_s\n{\n\tint count;\n\tez_dllist_node_t *head;\n\tez_dllist_node_t *tail;\n} ez_double_linked_list_t;\n\n//\n// Double Linked List - Add item to double linked list.\n//\nvoid EZ_double_linked_list_Add(ez_double_linked_list_t *list, void *payload);\n\n//\n// Double Linked List - Finds a given node based on the specified payload.\n//\nez_dllist_node_t *EZ_double_linked_list_FindByPayload(const ez_double_linked_list_t *list, const void *payload);\n\n//\n// Double Linked List - Find a item by its index.\n//\nez_dllist_node_t *EZ_double_linked_list_FindByIndex(const ez_double_linked_list_t *list, int index);\n\n//\n// Double Linked List - Removes an item from a linked list by its index.\n//\nvoid *EZ_double_linked_list_RemoveByIndex(ez_double_linked_list_t *list, int index);\n\n//\n// Double Linked List - Removes an item from a linked list by its payload.\n//\nvoid *EZ_double_linked_list_RemoveByPayload(ez_double_linked_list_t *list, void *payload);\n\n//\n// Double Linked List - A function pointer to a cleanup function that is run on each item when removing a range of items.\n//\ntypedef void (*ez_removerange_cleanup_function_t)(void *payload);\n\n//\n// Double Linked List - Removes a range of items in the list. \n//\t\t\t\t\t\tA cleanup function needs to be supplied so that the payload of the item is also cleaned up.\n//\nvoid EZ_double_linked_list_RemoveRange(ez_double_linked_list_t *list, int start, int end, ez_removerange_cleanup_function_t func);\n\n//\n// Double Linked List - Removes the first occurance of the item from double linked list and returns its payload.\n//\nvoid *EZ_double_linked_list_Remove(ez_double_linked_list_t *list, ez_dllist_node_t *item);\n\n//\n// Double Linked List - Orders a list.\n//\nvoid EZ_double_linked_list_Sort(ez_double_linked_list_t *list, PtFuncCompare compare_function);\n\n// =========================================================================================\n// Control Tree\n// =========================================================================================\n\ntypedef struct ez_tree_s\n{\n\tstruct ez_control_s\t\t*root;\t\t\t\t\t// The control tree.\n\tqbool\t\t\t\t\tdestroying;\t\t\t\t// Is the tree being destroyed? So we don't try to run any events when stuff is being removed.\n\tez_dllist_node_t\t\t*focused_node;\t\t\t// The node of focused control (from the tablist). \n\tez_double_linked_list_t\tdrawlist;\t\t\t\t// A list with the controls ordered in their drawing order.\n\tez_double_linked_list_t\ttablist;\t\t\t\t// A list with the controls ordered in their tabbing order.\n\n\tmouse_state_t\t\t\tprev_mouse_state;\t\t// The last mouse state we received.\n\tdouble\t\t\t\t\tmouse_pressed_time[9];\t// How long the mouse buttons have been pressed. (Used for repeating mouse click events).\n} ez_tree_t;\n\n//\n// Control Tree - Orders the tab list based on the tab order property.\n//\nvoid EZ_tree_OrderTabList(ez_tree_t *tree);\n\n//\n// Control Tree - Orders the draw list based on the draw order property.\n//\nvoid EZ_tree_OrderDrawList(ez_tree_t *tree);\n\n//\n// Control Tree - Needs to be called every frame to keep the tree alive.\n//\nvoid EZ_tree_EventLoop(ez_tree_t *tree);\n\n//\n// Control Tree - Dispatches a mouse event to a control tree.\n//\nqbool EZ_tree_MouseEvent(ez_tree_t *tree, mouse_state_t *ms);\n\n//\n// Control Tree - Key event.\n//\nqbool EZ_tree_KeyEvent(ez_tree_t *tree, int key, int unichar, qbool down);\n\n//\n// Tree Control - Finds any orphans and adds them to the root control.\n//\nvoid EZ_tree_UnOrphanizeChildren(ez_tree_t *tree);\n\n//\n// Control Tree - Refreshes the position of all controls in the tree.\n//\nvoid EZ_tree_Refresh(ez_tree_t *tree);\n\n//\n// Control Tree - Destroys a tree. Will not free the memory for the tree.\n//\nvoid EZ_tree_Destroy(ez_tree_t *tree);\n\n// =========================================================================================\n// Event handler \n// =========================================================================================\n\n//\n// Event function pointer types.\n//\ntypedef int (*ez_event_fp) (struct ez_control_s *self, void *ext_event_info);\ntypedef int (*ez_mouse_event_fp) (struct ez_control_s *self, mouse_state_t *mouse_state);\ntypedef int (*ez_key_event_fp) (struct ez_control_s *self, int key, int unichar, qbool down);\ntypedef int (*ez_keyspecific_event_fp) (struct ez_control_s *self, int key, int unichar);\ntypedef int (*ez_destroy_event_fp) (struct ez_control_s *self, qbool destroy_children);\ntypedef int (*ez_child_event_fp) (struct ez_control_s *self, struct ez_control_s *child);\n\n//\n// Event handlers function pointer types (same as event function types, except they have a payload also).\n//\ntypedef int (*ez_eventhandler_fp) (struct ez_control_s *self, void *payload, void *ext_info);\ntypedef int (*ez_mouse_eventhandler_fp) (struct ez_control_s *self, void *payload, mouse_state_t *mouse_state);\ntypedef int (*ez_key_eventhandler_fp) (struct ez_control_s *self, void *payload, int key, int unichar, qbool down);\ntypedef int (*ez_keyspecific_eventhandler_fp) (struct ez_control_s *self, void *payload, int key, int unichar);\ntypedef int (*ez_destroy_eventhandler_fp) (struct ez_control_s *self, void *payload, qbool destroy_children);\ntypedef int (*ez_child_eventhandler_event_fp) (struct ez_control_s *self, void *payload, struct ez_control_s *child);\n\n#define EZ_CONTROL_HANDLER\t\t\t0\n#define EZ_CONTROL_MOUSE_HANDLER\t1\n#define EZ_CONTROL_KEY_HANDLER\t\t2\n#define EZ_CONTROL_KEYSP_HANDLER\t3\n#define EZ_CONTROL_DESTROY_HANDLER\t4\n#define EZ_CONTROL_CHILD_HANDLER\t5\n\ntypedef union ez_eventhandlerfunction_u\n{\n\tez_eventhandler_fp\t\t\t\tnormal;\n\tez_mouse_eventhandler_fp\t\tmouse;\n\tez_key_eventhandler_fp\t\t\tkey;\n\tez_keyspecific_eventhandler_fp\tkey_sp;\n\tez_destroy_eventhandler_fp\t\tdestroy;\n\tez_child_eventhandler_event_fp\tchild;\n} ez_eventhandlerfunction_t;\n\ntypedef struct ez_eventhandler_s\n{\n\tint\t\t\t\t\t\t\tfunction_type;\n\tez_eventhandlerfunction_t\tfunction;\n\tvoid\t\t\t\t\t\t*payload;\n\tstruct ez_eventhandler_s\t*next;\n} ez_eventhandler_t;\n\n#define CONTROL_ADD_EVENTHANDLER(ctrl, func_type, eventfunc, eventroot, eventname, payload)\t\\\n{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tez_eventhandler_t *e = EZ_eventhandler_Create((void *)eventfunc, func_type, payload);\t\\\n\teventroot *c = (eventroot *)ctrl;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tif (c->event_handlers.eventname)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\te->next = c->event_handlers.eventname;\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tc->event_handlers.eventname = e;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n}\n\n#define CONTROL_REMOVE_EVENTHANDLER(ctrl, func, eventroot, eventname)\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\teventroot *c = (eventroot *)ctrl;\t\t\t\t\t\t\t\t\t\t\t\\\n\tEZ_eventhandler_Remove(c->event_handlers.eventname, func, false);\t\t\t\\\n}\n\n//\n// Eventhandler - Goes through the list of events and removes the one with the specified function.\n//\nvoid EZ_eventhandler_Remove(ez_eventhandler_t *eventhandler, void *event_func, qbool all);\n\n//\n// Eventhandler - Creates a eventhandler.\n//\nez_eventhandler_t *EZ_eventhandler_Create(void *event_func, int func_type, void *payload);\n\n// =========================================================================================\n// Control\n// =========================================================================================\n\n#define CONTROL_IS_CONTAINED(self) (self->parent && (self->ext_flags & control_contained))\n\n//\n// Raises an event. (See the CONTROL_EVENT_HANDLER_CALL for a more detailed explination of how this works).\n//\n#ifdef __INTEL_COMPILER\n\n#define CONTROL_RAISE_EVENT(retval, ctrl, eventroot, eventhandler, ...)\t\t\t\t\t\t\t\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tint temp = 0;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tint *p = (int *)retval;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\teventroot *c = (eventroot *)(ctrl);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tc->override_counts.eventhandler = c->inherit_levels.eventhandler;\t\t\t\t\t\t\t\t\t\t\t\\\n\tif(!c->events.eventhandler)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tSys_Error(\"CONTROL_RAISE_EVENT : \"#ctrl\" (\"#eventroot\") has no event function for \"#eventhandler);\t\t\\\n\ttemp = c->events.eventhandler((ez_control_t *)(ctrl), __VA_ARGS__);\t\t\t\t\t\t\t\t\t\t\t\\\n\tif(p) (*p) = temp;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\n#else\n\n#define CONTROL_RAISE_EVENT(retval, ctrl, eventroot, eventhandler, ...)\t\t\t\t\t\t\t\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tint temp = 0;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tint *p = (int *)retval;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\teventroot *c = (eventroot *)(ctrl);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tc->override_counts.eventhandler = c->inherit_levels.eventhandler;\t\t\t\t\t\t\t\t\t\t\t\\\n\tif(!c->events.eventhandler)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tSys_Error(\"CONTROL_RAISE_EVENT : \"#ctrl\" (\"#eventroot\") has no event function for \"#eventhandler);\t\t\\\n\ttemp = c->events.eventhandler((ez_control_t *)(ctrl), ##__VA_ARGS__);\t\t\t\t\t\t\t\t\t\t\\\n\tif(p) (*p) = temp;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n}\n\n#endif // __INTEL_COMPILER\n\n//\n// Calls the event handler associated with an event after all controls have run their implementation of the event.\n//\n// Example: OnMouseUp defined in ez_control_t\n// 1. Run EZ_control_OnMouseUp\n// 2. Run EZ_label_OnMouseUp (Set in ctrl->events.OnMouseUp, which in turn MUST call EZ_control_OnMouseUp\n//    the first thing it does).\n// 3. Run any specified event handler associated with the OnMouseUp event. ctrl->event_handlers.OnMouseUp\n//\n// This works by having an \"inheritance level\" and \"override count\" in each control struct:\n// Inheritance level:\n// ------------------\n// ez_control_t = 0\n// ez_label_t\t= 1\n//\n// When an initial event is raised using the CONTROL_RAISE_EVENT macro, the \"override count\" for the struct \n// is reset to the value of the \"inheritance level\" (which is always the same). Then each time that\n// the CONTROL_EVENT_HANDLER_CALL is called, first in the ez_control_t implementation of the event\n// and then in the ez_label_t implementation, the \"override count\" is decremented, and when it reaches\n// ZERO the event handler (if there's one set) will be run.\n//\n// If a control such as ez_label_t implements new events OnTextChanged for example, any class inheriting\n// from ez_label_t will have to have a inheritance level relative to ez_label_t also.\n// Example:\n// ez_control_t inheritance level:\t\t\t0\t\t\t\t1\t\t\t\t2\n// ez_label_t\tinheritance level:\t\t\t-\t\t\t\t0\t\t\t\t1\n// Inheritance:\t\t\t\t\t\t\tez_control_t <- ez_label_t <- ez_crazylabel_t\n//\n// ez_crazylabel_t *crazylabel = CREATE(...);\n// ((ez_control_t *)crazylabel)->inheritance_level = 2; // Relative to ez_control_t\n// ((ez_label_t *)  crazylabel)->inheritance_level = 1;\t// Relative to ez_label_t\n//\n// So if I now want to override OnTextChanged specified in ez_label_t for ez_crazylabel_t, the \n// ez_label_t inheritance level is used by the macro. And if I override OnMouseUp, the ez_control_t\n// inheritance level is used instead.\n//\n// retval\t\t= An integer pointer to the var that should receive the return value of the event handler.\n// ctrl\t\t\t= The control struct to call the event handler on.\n// eventroot\t= The name of the control struct that has the \"root implementation\" of the specified event.\n// eventhandler = The name of the event that should be called.\n//\n#ifdef __INTEL_COMPILER\n\n#define CONTROL_EVENT_HANDLER_CALL(retval, ctrl, eventroot, evnthndler, ...)\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tint *p = (int *)retval, temp = 0;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\teventroot *c = (eventroot *)ctrl;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tif (c->event_handlers.evnthndler)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tif (c->override_counts.evnthndler == 1)\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tez_eventhandler_t *e = c->event_handlers.evnthndler;\t\t\t\t\t\t\t\\\n\t\t\twhile (e)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tEZ_eventhandler_Exec(e, (ez_control_t *)ctrl, __VA_ARGS__);\t\t\t\t\t\\\n\t\t\t\te = e->next;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\telse\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tc->override_counts.evnthndler--;\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tif(p) (*p) = temp;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n}\n\n\n#else\n\n#define CONTROL_EVENT_HANDLER_CALL(retval, ctrl, eventroot, evnthndler, ...)\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tint *p = (int *)retval, temp = 0;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\teventroot *c = (eventroot *)ctrl;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tif (c->event_handlers.evnthndler)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tif (c->override_counts.evnthndler == 1)\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tez_eventhandler_t *e = c->event_handlers.evnthndler;\t\t\t\t\t\t\t\\\n\t\t\twhile (e)\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tEZ_eventhandler_Exec(e, (ez_control_t *)ctrl, ##__VA_ARGS__);\t\t\t\t\\\n\t\t\t\te = e->next;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\telse\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tc->override_counts.evnthndler--;\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tif(p) (*p) = temp;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n}\n\n#endif // __INTEL_COMPILER\n\n//\n// Registers a event function an event and sets it's inheritance level.\n// ctrl\t\t\t= The control that the event function should be registered to.\n// eventfunc\t= The function implementing the event. (Ex. EZ_control_OnMouseDown)\n// eventname\t= The name of the event. (Ex. OnMouseDown)\n// eventroot\t= The control that initially defined this event. (Ex. ez_control_t)\n//\n#define CONTROL_REGISTER_EVENT(ctrl, eventfunc, eventname, eventroot)\t\\\n{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t((eventroot *)ctrl)->events.eventname = eventfunc;\t\t\t\t\t\\\n\t((eventroot *)ctrl)->inherit_levels.eventname++;\t\t\t\t\t\\\n}\n\ntypedef struct ez_control_eventcount_s\n{\n\tint\tOnMouseEvent;\n\tint\tOnMouseClick;\n\tint\tOnMouseUp;\n\tint\tOnMouseUpOutside;\n\tint\tOnMouseDown;\n\tint\tOnMouseHover;\n\tint\tOnMouseEnter;\n\tint\tOnMouseLeave;\n\tint\tOnKeyEvent;\n\tint\tOnKeyDown;\n\tint\tOnKeyUp;\n\tint\tOnLayoutChildren;\n\tint\tOnDraw;\n\tint\tOnDestroy;\n\tint\tOnMove;\n\tint\tOnScroll;\n\tint\tOnParentScroll;\n\tint\tOnResize;\n\tint\tOnParentResize;\n\tint\tOnGotFocus;\n\tint\tOnLostFocus;\n\tint\tOnVirtualResize;\n\tint\tOnMinVirtualResize;\n\tint\tOnFlagsChanged;\n\tint OnResizeHandleThicknessChanged;\n\tint OnEventHandlerChanged;\n\tint OnAnchorChanged;\n\tint OnVisibilityChanged;\n\tint OnOpacityChanged;\n\tint OnChildMoved;\n\tint OnChildResize;\n} ez_control_eventcount_t;\n\ntypedef struct ez_control_events_s\n{\n\tez_mouse_event_fp\t\t\tOnMouseEvent;\n\tez_mouse_event_fp\t\t\tOnMouseClick;\n\tez_mouse_event_fp\t\t\tOnMouseUp;\n\tez_mouse_event_fp\t\t\tOnMouseUpOutside;\n\tez_mouse_event_fp\t\t\tOnMouseDown;\n\tez_mouse_event_fp\t\t\tOnMouseHover;\n\tez_mouse_event_fp\t\t\tOnMouseEnter;\n\tez_mouse_event_fp\t\t\tOnMouseLeave;\n\tez_key_event_fp\t\t\t\tOnKeyEvent;\n\tez_keyspecific_event_fp\t\tOnKeyDown;\n\tez_keyspecific_event_fp\t\tOnKeyUp;\n\tez_event_fp\t\t\t\t\tOnLayoutChildren;\n\tez_event_fp\t\t\t\t\tOnDraw;\n\tez_destroy_event_fp\t\t\tOnDestroy;\n\tez_event_fp\t\t\t\t\tOnMove;\n\tez_event_fp\t\t\t\t\tOnScroll;\n\tez_event_fp\t\t\t\t\tOnParentScroll;\n\tez_event_fp\t\t\t\t\tOnResize;\n\tez_event_fp\t\t\t\t\tOnParentResize;\n\tez_event_fp\t\t\t\t\tOnGotFocus;\n\tez_event_fp\t\t\t\t\tOnLostFocus;\n\tez_event_fp\t\t\t\t\tOnVirtualResize;\n\tez_event_fp\t\t\t\t\tOnMinVirtualResize;\n\tez_event_fp\t\t\t\t\tOnFlagsChanged;\n\tez_event_fp\t\t\t\t\tOnResizeHandleThicknessChanged;\n\tez_event_fp\t\t\t\t\tOnEventHandlerChanged;\n\tez_event_fp\t\t\t\t\tOnAnchorChanged;\n\tez_event_fp\t\t\t\t\tOnVisibilityChanged;\n\tez_event_fp\t\t\t\t\tOnOpacityChanged;\n\tez_child_event_fp\t\t\tOnChildMoved;\n\tez_child_event_fp\t\t\tOnChildResize;\n} ez_control_events_t;\n\ntypedef struct ez_control_eventhandlers_s\n{\t\t\t\t\t\n\tez_eventhandler_t\t*OnMouseEvent;\n\tez_eventhandler_t\t*OnMouseClick;\n\tez_eventhandler_t\t*OnMouseUp;\n\tez_eventhandler_t\t*OnMouseUpOutside;\n\tez_eventhandler_t\t*OnMouseDown;\n\tez_eventhandler_t\t*OnMouseHover;\n\tez_eventhandler_t\t*OnMouseEnter;\n\tez_eventhandler_t\t*OnMouseLeave;\n\tez_eventhandler_t\t*OnKeyEvent;\n\tez_eventhandler_t\t*OnKeyDown;\n\tez_eventhandler_t\t*OnKeyUp;\n\tez_eventhandler_t\t*OnLayoutChildren;\n\tez_eventhandler_t\t*OnDraw;\n\tez_eventhandler_t\t*OnDestroy;\n\tez_eventhandler_t\t*OnMove;\n\tez_eventhandler_t\t*OnScroll;\n\tez_eventhandler_t\t*OnParentScroll;\n\tez_eventhandler_t\t*OnResize;\n\tez_eventhandler_t\t*OnParentResize;\n\tez_eventhandler_t\t*OnGotFocus;\n\tez_eventhandler_t\t*OnLostFocus;\n\tez_eventhandler_t\t*OnVirtualResize;\n\tez_eventhandler_t\t*OnMinVirtualResize;\n\tez_eventhandler_t\t*OnFlagsChanged;\n\tez_eventhandler_t\t*OnResizeHandleThicknessChanged;\n\tez_eventhandler_t\t*OnEventHandlerChanged;\n\tez_eventhandler_t\t*OnAnchorChanged;\n\tez_eventhandler_t\t*OnVisibilityChanged;\n\tez_eventhandler_t\t*OnOpacityChanged;\n\tez_eventhandler_t\t*OnChildMoved;\n\tez_eventhandler_t\t*OnChildResize;\n} ez_control_eventhandlers_t;\n\ntypedef enum ez_anchor_e\n{\n\tanchor_none\t\t= (1 << 0),\n\tanchor_left\t\t= (1 << 1), \n\tanchor_right\t= (1 << 2),\n\tanchor_top\t\t= (1 << 3),\n\tanchor_bottom\t= (1 << 4)\n} ez_anchor_t;\n\n//\n// Control - External flags.\n//\ntypedef enum ez_control_flags_e\n{\n\tcontrol_enabled\t\t\t\t= (1 << 0),\t\t// Is the control usable?\n\tcontrol_movable\t\t\t\t= (1 << 1),\t\t// Can the control be moved?\n\tcontrol_focusable\t\t\t= (1 << 2),\t\t// Can the control be given focus?\n\tcontrol_resize_h\t\t\t= (1 << 3),\t\t// Is the control resizeable horizontally?\n\tcontrol_resize_v\t\t\t= (1 << 4),\t\t// Is the control resizeable vertically?\n\tcontrol_resizeable\t\t\t= (1 << 5),\t\t// Is the control resizeable at all, not just by the user? // TODO : Should this be external?\n\tcontrol_contained\t\t\t= (1 << 6),\t\t// Is the control contained within it's parent or can it go outside its edges?\n\tcontrol_visible\t\t\t\t= (1 << 7),\t\t// Is the control visible?\n\tcontrol_scrollable\t\t\t= (1 << 8),\t\t// Is the control scrollable\n\tcontrol_ignore_mouse\t\t= (1 << 9),\t\t// Should the control ignore mouse input?\n\tcontrol_anchor_viewport\t\t= (1 << 10),\t// Anchor to the visible edges of the controls instead of the virtual edges.\n\tcontrol_move_parent\t\t\t= (1 << 11),\t// When moving this control, should we also move it's parent?\n\tcontrol_listen_repeat_mouse\t= (1 << 12),\t// Should the control listen to repeated mouse button events?\n\tcontrol_bg_tile_center\t\t= (1 << 13),\t// Tile the center of the background image (otherwise it will be stretched).\n\tcontrol_bg_tile_edges\t\t= (1 << 14)\t\t// Tile the edges of the background image.\n} ez_control_flags_t;\n\n#define DEFAULT_CONTROL_FLAGS\t(control_enabled | control_focusable | control_contained | control_scrollable | control_visible)\n\n//\n// Control - Internal flags.\n//\ntypedef enum ez_control_iflags_e\n{\n\tcontrol_focused\t\t\t\t= (1 << 0),\t\t// Is the control currently in focus?\n\tcontrol_moving\t\t\t\t= (1 << 1),\t\t// Is the control in the process of being moved?\n\tcontrol_resizing_left\t\t= (1 << 2),\t\t// Are we resizing the control to the left?\n\tcontrol_resizing_right\t\t= (1 << 3),\t\t// Are we resizing the control to the right?\n\tcontrol_resizing_top\t\t= (1 << 4),\t\t// Resizing upwards.\n\tcontrol_resizing_bottom\t\t= (1 << 5),\t\t// Resizing downards.\n\tcontrol_clicked\t\t\t\t= (1 << 6),\t\t// Is the control being clicked? (If the mouse button is released outside the control a click event isn't raised).\n\tcontrol_mouse_over\t\t\t= (1 << 7),\t\t// Is the mouse over the control?\n\tcontrol_update_anchorgap\t= (1 << 8),\t\t// Update the anchor gap when resizing the control?\n\tcontrol_hidden_by_parent\t= (1 << 9)\t\t// Is the control hidden cause it's parent is not visible?\n} ez_control_iflags_t;\n\ntypedef struct ez_control_s\n{\n\tez_control_id_t\t\t\tCLASS_ID;\t\t\t\t// An ID unique for this class, this is set at initilization\n\t\t\t\t\t\t\t\t\t\t\t\t\t// and should never be changed after that.\n\tchar\t\t\t\t\t*name;\t\t\t\t\t// The name of the control.\n\tchar\t\t\t\t\t*description;\t\t\t// A short description of the control.\n\t\n\tint\t \t\t\t\t\tx;\t\t\t\t\t\t// Relative position to it's parent.\n\tint\t\t\t\t\t\ty;\n\n\tint\t\t\t\t\t\tprev_x;\t\t\t\t\t// Previous position.\n\tint\t\t\t\t\t\tprev_y;\n\n\tint\t\t\t\t\t\tabsolute_x;\t\t\t\t// The absolute screen coordinates for the control.\n\tint\t\t\t\t\t\tabsolute_y;\t\n\n\tint\t\t\t\t\t\tprev_absolute_x;\t\t// Previous absolute position.\n\tint\t\t\t\t\t\tprev_absolute_y;\n\t\n\tint \t\t\t\t\twidth;\t\t\t\t\t// Size.\n\tint\t\t\t\t\t\theight;\n\t\n\tint\t\t\t\t\t\tprev_width;\t\t\t\t// Previous size.\n\tint\t\t\t\t\t\tprev_height;\n\n\tint\t\t\t\t\t\tvirtual_x;\t\t\t\t// The relative position in the virtual window that the \"real\" window is showing.\n\tint\t\t\t\t\t\tvirtual_y;\n\n\tint\t\t\t\t\t\tabsolute_virtual_x;\t\t// The absolute position of the virtual window on the screen.\n\tint\t\t\t\t\t\tabsolute_virtual_y;\n\n\tint\t\t\t\t\t\tvirtual_width;\t\t\t// The virtual size of the control (scrollable area).\n\tint\t\t\t\t\t\tvirtual_height;\n\n\tint\t\t\t\t\t\tprev_virtual_width;\t\t// The previous virtual size of the control.\n\tint\t\t\t\t\t\tprev_virtual_height;\n\n\tint\t\t\t\t\t\tvirtual_width_min;\t\t// The virtual min size for the control.\n\tint\t\t\t\t\t\tvirtual_height_min;\n\t\n\tint\t\t\t\t\t\twidth_max;\t\t\t\t// Max/min sizes for the control.\n\tint\t\t\t\t\t\twidth_min;\n\tint\t\t\t\t\t\theight_max;\n\tint\t\t\t\t\t\theight_min;\n\n\tint\t\t\t\t\t\tbound_top;\t\t\t\t// The bounds the control is allowed to draw within.\n\tint\t\t\t\t\t\tbound_left;\n\tint\t\t\t\t\t\tbound_right;\n\tint\t\t\t\t\t\tbound_bottom;\n\n\tint\t\t\t\t\t\tleft_edge_gap;\t\t\t// The gap to the different edges, used for anchoring\n\tint\t\t\t\t\t\tright_edge_gap;\t\t\t// if the parent controls size becomes less than the\n\tint\t\t\t\t\t\ttop_edge_gap;\t\t\t// childs min size, we need to keep track of how far\n\tint\t\t\t\t\t\tbottom_edge_gap;\t\t// from the edge we were.\n\n\tez_anchor_t\t\t\t\tanchor_flags;\t\t\t// Which parts of it's parent the control is anchored to.\n\n\tint\t\t\t\t\t\tresize_handle_thickness;// The thickness of the resize handles on the sides of the control.\n\n\tint\t\t\t\t\t\tdraw_order;\t\t\t\t// The order the control is drawn in.\n\tint\t\t\t\t\t\ttab_order;\t\t\t\t// The tab number of the control.\n\t\n\tez_control_flags_t\t\text_flags;\t\t\t\t// External flags that decide how the control should behave. Movable, resizeable and such.\n\tez_control_iflags_t\t\tint_flags;\t\t\t\t// Internal flags that holds the current state of the control, moving, resizing and so on.\n\n\tbyte\t\t\t\t\tbackground_color[4];\t// The background color of the control RGBA.\n\tmpic_t\t\t\t\t\t*background;\t\t\t// The background picture.\n\tfloat\t\t\t\t\tbg_edge_size_ratio;\t\t// The percentage of the width of the background picture to use for drawing the edges \n\t\t\t\t\t\t\t\t\t\t\t\t\t// of the background when drawn tiled. Value range 0.0 - 1.0\n\tfloat\t\t\t\t\tbg_opacity;\t\t\t\t// The opacity of the control.\n\tfloat\t\t\t\t\topacity;\t\t\t\t// My own opacity only.\n\tfloat\t\t\t\t\toverall_opacity;\t\t// The overall opacity of the control (including my parents opacity).\n\n\tfloat\t\t\t\t\tx_percent;\t\t\t\t// The x position in percentage based on the parents width.\n\tfloat\t\t\t\t\ty_percent;\t\t\t\t// The y position in percentage based on the parents height.\n\n\tvoid\t\t\t\t\t*payload;\t\t\t\t// A pointer to some user specified data associated with the control. Up to the caller to clean this up.\n\n\tez_control_events_t\t\t\tevents;\t\t\t\t// The base reaction for events. Is only set at initialization.\n\tez_control_eventhandlers_t\tevent_handlers;\n\tez_control_eventcount_t\t\tinherit_levels;\t\t// The number of times each event has been overriden. \n\t\t\t\t\t\t\t\t\t\t\t\t\t// (Countdown before executing the event handler for the event, after all\n\t\t\t\t\t\t\t\t\t\t\t\t\t// event implementations in the inheritance chain have been run).\n\tez_control_eventcount_t override_counts;\t\t// This is reset each time an event is raised by CONTROL_RAISE_EVENT to the\n\t\t\t\t\t\t\t\t\t\t\t\t\t// inheritance level for the event in question.\n\n\tqbool\t\t\t\t\tinitializing;\t\t\t// Is the control initializing?\n\n\tstruct ez_control_s\t\t*parent;\t\t\t\t// The parent of the control. Only the root node has no parent.\n\tez_double_linked_list_t\tchildren;\t\t\t\t// List of children belonging to the control.\n\n\tstruct ez_tree_s\t\t*control_tree;\t\t\t// The control tree the control belongs to.\n\n\tmouse_state_t\t\t\tprev_mouse_state;\t\t// The previous mouse event that was passed on to this control.\n\tdouble\t\t\t\t\tmouse_repeat_delay;\t\t// The time to wait before raising a new mouse click event for this control (if control_listen_repeat_mouse is set).\n} ez_control_t;\n\n//\n// Eventhandler - Execute an event handler. (We declare this here cause ez_control_t hasn't been defined above)\n//\nvoid EZ_eventhandler_Exec(ez_eventhandler_t *event_handler, ez_control_t *ctrl, ...);\n\n//\n// Control - Creates a new control and initializes it.\n//\nez_control_t *EZ_control_Create(ez_tree_t *tree, ez_control_t *parent, \n\t\t\t\t\t\t\t  char *name, char *description, \n\t\t\t\t\t\t\t  int x, int y, int width, int height, \n\t\t\t\t\t\t\t  int flags);\n\n//\n// Control - Initializes a control and adds it to the specified control tree.\n//\nvoid EZ_control_Init(ez_control_t *control, ez_tree_t *tree, ez_control_t *parent, \n\t\t\t\t\t\t\t  char *name, char *description, \n\t\t\t\t\t\t\t  int x, int y, int width, int height, \n\t\t\t\t\t\t\t  ez_control_flags_t flags);\n\n//\n// Control - Destroys a specified control.\n//\nint EZ_control_Destroy(ez_control_t *self, qbool destroy_children);\n\n//\n// Control - \n// Returns the screen position of the control. This will be different for a scrollable window\n// since it's drawing position differs from the windows actual position on screen.\n//\nvoid EZ_control_GetDrawingPosition(ez_control_t *self, int *x, int *y);\n\n//\n// Control - Gets the anchor flags.\n//\nez_anchor_t EZ_control_GetAnchor(ez_control_t *self);\n\n//\n// Control - Sets the external flags of the control.\n//\nez_control_flags_t EZ_control_GetFlags(ez_control_t *self);\n\n//\n// Control - Sets the external flags of the control.\n//\nvoid EZ_control_SetFlags(ez_control_t *self, ez_control_flags_t flags);\n\n//\n// Control - Set the thickness of the resize handles (if any).\n//\nvoid EZ_control_SetResizeHandleThickness(ez_control_t *self, int thickness);\n\n//\n// Control - Sets whetever the control is scrollable or not.\n//\nvoid EZ_control_SetScrollable(ez_control_t *self, qbool scrollable);\n\n//\n// Control - Sets if the control should move it's parent when it moves itself.\n//\nvoid EZ_control_SetMovesParent(ez_control_t *self, qbool moves_parent);\n\n//\n// Control - Sets whetever the control is visible or not.\n//\nvoid EZ_control_SetVisible(ez_control_t *self, qbool visible);\n\n//\n// Control - Sets whetever the control is resizeable vertically by the user.\n//\nvoid EZ_control_SetResizeableVertically(ez_control_t *self, qbool resize_vertically);\n\n//\n// Control - Sets whetever the control is resizeable at all, not just by the user.\n//\nvoid EZ_control_SetResizeable(ez_control_t *self, qbool resizeable);\n\n//\n// Control - Sets whetever the control is resizeable both horizontally and vertically by the user.\n//\nvoid EZ_control_SetResizeableBoth(ez_control_t *self, qbool resize);\n\n//\n// Control - Sets whetever the control is resizeable horizontally by the user.\n//\nvoid EZ_control_SetResizeableHorizontally(ez_control_t *self, qbool resize_horizontally);\n\n//\n// Control - Sets whetever the control is focusable.\n//\nvoid EZ_control_SetFocusable(ez_control_t *self, qbool focusable);\n\n//\n// Control - Sets whetever the control is movable.\n//\nvoid EZ_control_SetMovable(ez_control_t *self, qbool movable);\n\n//\n// Control - Sets whetever the control is enabled or not.\n//\nvoid EZ_control_SetEnabled(ez_control_t *self, qbool enabled);\n\n//\n// Control - Set the background image for the control.\n//\nvoid EZ_control_SetBackgroundImage(ez_control_t *self, const char *background_path);\n\n//\n// Control - Set the opacity for the background image for the control.\n//\nvoid EZ_control_SetBackgroundImageOpacity(ez_control_t *self, float opacity);\n\n//\n// Control - Set how much percentage of the width of the background image that should be used when drawing the edges of the control.\n//\nvoid EZ_control_SetBackgroundImageEdgePercentage(ez_control_t *self, int percentage);\n\n//\n// Control - Set if the center of the button should be tiled or stretched.\n//\nvoid EZ_control_SetBackgroundTileCenter(ez_control_t *self, qbool tilecenter);\n\n//\n// Control - Set if the edges of the button should be tiled or stretched.\n//\nvoid EZ_control_SetBackgroundTileEdges(ez_control_t *self, qbool tileedges);\n\n//\n// Control - Sets whetever the control is contained within the bounds of it's parent or not, or is allowed to draw outside it.\n//\nvoid EZ_control_SetContained(ez_control_t *self, qbool contained);\n\n//\n// Control - Sets whetever the control should care about mouse input or not.\n//\nvoid EZ_control_SetIgnoreMouse(ez_control_t *self, qbool ignore_mouse);\n\n//\n// Control - Listen to repeated mouse click events when holding down a mouse button. \n//           The delay between events is set using EZ_control_SetRepeatMouseClickDelay(...)\n//\nvoid EZ_control_SetListenToRepeatedMouseClicks(ez_control_t *self, qbool listen_repeat);\n\n//\n// Control - Sets the amount of time to wait between each new mouse click event\n//           when holding down the mouse over a control.\n//\nvoid EZ_control_SetRepeatMouseClickDelay(ez_control_t *self, double delay);\n\n//\n// Control - Sets the OnDestroy event handler.\n//\nvoid EZ_control_AddOnDestroy(ez_control_t *self, ez_destroy_eventhandler_fp OnDestroy, void *payload);\n\n//\n// Control - Sets the OnLayoutChildren event handler.\n//\nvoid EZ_control_AddOnLayoutChildren(ez_control_t *self, ez_eventhandler_fp OnLayoutChildren, void *payload);\n\n//\n// Control - Sets the OnMove event handler.\n//\nvoid EZ_control_AddOnMove(ez_control_t *self, ez_eventhandler_fp OnMove, void *payload);\n\n//\n// Control - Sets the OnScroll event handler.\n//\nvoid EZ_control_AddOnScroll(ez_control_t *self, ez_eventhandler_fp OnScroll, void *payload);\n\n//\n// Control - Sets the OnResize event handler.\n//\nvoid EZ_control_AddOnResize(ez_control_t *self, ez_eventhandler_fp OnResize, void *payload);\n\n//\n// Control - Sets the OnParentResize event handler.\n//\nvoid EZ_control_AddOnParentResize(ez_control_t *self, ez_eventhandler_fp OnParentResize, void *payload);\n\n//\n// Control - Sets the OnVirtualResize event handler.\n//\nvoid EZ_control_AddOnVirtualResize(ez_control_t *self, ez_eventhandler_fp OnVirtualResize, void *payload);\n\n//\n// Control - Sets the OnMinVirtualResize event handler.\n//\nvoid EZ_control_AddOnMinVirtualResize(ez_control_t *self, ez_eventhandler_fp OnMinVirtualResize, void *payload);\n\n//\n// Control - Sets the OnFlagsChanged event handler.\n//\nvoid EZ_control_AddOnFlagsChanged(ez_control_t *self, ez_eventhandler_fp OnFlagsChanged, void *payload);\n\n//\n// Control - Sets the OnKeyEvent event handler.\n//\nvoid EZ_control_AddOnKeyEvent(ez_control_t *self, ez_key_eventhandler_fp OnKeyEvent, void *payload);\n\n//\n// Control - Sets the OnLostFocus event handler.\n//\nvoid EZ_control_AddOnLostFocus(ez_control_t *self, ez_eventhandler_fp OnLostFocus, void *payload);\n\n//\n// Control - Sets the OnGotFocus event handler.\n//\nvoid EZ_control_AddOnGotFocus(ez_control_t *self, ez_eventhandler_fp OnGotFocus, void *payload);\n\n//\n// Control - Sets the OnMouseHover event handler.\n//\nvoid EZ_control_AddOnMouseHover(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseHover, void *payload);\n\n//\n// Control - Sets the OnMouseLeave event handler.\n//\nvoid EZ_control_AddOnMouseLeave(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseLeave, void *payload);\n\n//\n// Control - Sets the OnMouseEnter event handler.\n//\nvoid EZ_control_AddOnMouseEnter(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseEnter, void *payload);\n\n//\n// Control - Sets the OnMouseClick event handler.\n//\nvoid EZ_control_AddOnMouseClick(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseClick, void *payload);\n\n//\n// Control - Sets the OnMouseEvent event handler.\n//\nvoid EZ_control_AddOnMouseUp(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseUp, void *payload);\n\n//\n// Control - Sets the OnMouseUp event handler.\n//\nvoid EZ_control_AddOnMouseUp(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseUp, void *payload);\n\n//\n// Control - Sets the OnMouseDown event handler.\n//\nvoid EZ_control_AddOnMouseDown(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseDown, void *payload);\n\n//\n// Control - Sets the OnMouseEvent event handler.\n//\nvoid EZ_control_AddOnMouseEvent(ez_control_t *self, ez_mouse_eventhandler_fp OnMouseEvent, void *payload);\n\n//\n// Control - Sets the OnDraw event handler.\n//\nvoid EZ_control_AddOnDraw(ez_control_t *self, ez_eventhandler_fp OnDraw, void *payload);\n\n//\n// Control - Set the event handler for the OnEventHandlerChanged event.\n//\nvoid EZ_control_AddOnEventHandlerChanged(ez_control_t *self, ez_eventhandler_fp OnEventHandlerChanged, void *payload);\n\n//\n// Control - Sets the OnResizeHandleThicknessChanged event handler.\n//\nvoid EZ_control_AddOnResizeHandleThicknessChanged(ez_control_t *self, ez_eventhandler_fp OnResizeHandleThicknessChanged, void *payload);\n\n//\n// Control - Sets the OnChildMoved event handler.\n//\nvoid EZ_control_AddOnChildMoved(ez_control_t *self, ez_eventhandler_fp OnChildMoved, void *payload);\n\n//\n// Control - Sets the OnChildResize event handler.\n//\nvoid EZ_control_AddOnChildResize(ez_control_t *self, ez_eventhandler_fp OnChildResize, void *payload);\n\n//\n// Control - Set color of a control.\n//\nvoid EZ_control_SetBackgroundColor(ez_control_t *self, byte r, byte g, byte b, byte alpha);\n\n//\n// Control - Sets the tab order of a control.\n//\nvoid EZ_control_SetDrawOrder(ez_control_t *self, int draw_order, qbool update_children);\n\n//\n// Control - Sets the payload for the control.\n//\nvoid EZ_control_SetPayload(ez_control_t *self, void *payload);\n\n//\n// Control - Sets the size of a control.\n//\nvoid EZ_control_SetSize(ez_control_t *self, int width, int height);\n\n//\n// Control - Set the max size for the control.\n//\nvoid EZ_control_SetMaxSize(ez_control_t *self, int max_width, int max_height);\n\n//\n// Control - Set the min size for the control.\n//\nvoid EZ_control_SetMinSize(ez_control_t *self, int min_width, int min_height);\n\n//\n// Control - Sets the position of a control.\n//\nvoid EZ_control_SetPosition(ez_control_t *self, int x, int y);\n\n//\n// Control - Sets the virtual size of the control (this area can be scrolled around in).\n//\nvoid EZ_control_SetVirtualSize(ez_control_t *self, int virtual_width, int virtual_height);\n\n//\n// Control - Set the min virtual size for the control, the control size is not allowed to be larger than this.\n//\nvoid EZ_control_SetMinVirtualSize(ez_control_t *self, int min_virtual_width, int min_virtual_height);\n\n//\n// Control - Sets the part of the control that should be shown if it's scrollable.\n//\nvoid EZ_control_SetScrollPosition(ez_control_t *self, int scroll_x, int scroll_y);\n\n//\n// Control - Sets the anchoring of the control to it's parent.\n//           !!! NOTE !!! \n//           If you set an anchoring to a single edge, make sure you do this\n//           after the parent has its final size, otherwise you might get some\n//           goofy behavior.\n//           !!! NOTE !!!\n//\nvoid EZ_control_SetAnchor(ez_control_t *self, ez_anchor_t anchor_flags);\n\n//\n// Control - Sets the tab order of a control.\n//\nvoid EZ_control_SetTabOrder(ez_control_t *self, int tab_order);\n\n//\n// Control - Focuses on a control.\n//\nqbool EZ_control_SetFocus(ez_control_t *self);\n\n//\n// Control - Focuses on a control associated with a specified node from the tab list.\n//\nqbool EZ_control_SetFocusByNode(ez_control_t *self, ez_dllist_node_t *node);\n\n//\n// Control - Returns true if this control is the root control.\n//\nqbool EZ_control_IsRoot(ez_control_t *self);\n\n//\n// Control - Adds a child to the control.\n//\nvoid EZ_control_AddChild(ez_control_t *self, ez_control_t *child);\n\n//\n// Control - Remove a child from the control. Returns a reference to the child that was removed.\n//\nez_control_t *EZ_control_RemoveChild(ez_control_t *self, ez_control_t *child);\n\n// =========================================================================================\n// Control - Base Event Handlers (Any inheriting controls can have their own implementation)\n// =========================================================================================\n\n//\n// Control - Event for when a new event handler is set for an event.\n//\nint EZ_control_OnEventHandlerChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - The control got focus.\n//\nint EZ_control_OnGotFocus(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - The control lost focus.\n//\nint EZ_control_OnLostFocus(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - The control was moved.\n//\nint EZ_control_OnMove(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - On scroll event.\n//\nint EZ_control_OnScroll(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - On parent scroll event.\n//\nint EZ_control_OnParentScroll(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - Convenient function for changing the scroll position by a specified amount.\n//\nvoid EZ_control_SetScrollChange(ez_control_t *self, int delta_scroll_x, int delta_scroll_y);\n\n//\n// Control - The control was resized.\n//\nint EZ_control_OnResize(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - The anchoring for the control changed.\n//\nint EZ_control_OnAnchorChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - The controls parent was resized.\n//\nint EZ_control_OnParentResize(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - The minimum virtual size has changed for the control.\n//\nint EZ_control_OnMinVirtualResize(ez_control_t *self, void *ext_event_info);\n\n//\n// Label - The virtual size of the control has changed.\n//\nint EZ_control_OnVirtualResize(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - Layouts children.\n//\nint EZ_control_OnLayoutChildren(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - Visibility changed.\n//\nint EZ_control_OnVisibilityChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Label - The flags for the control changed.\n//\nint EZ_control_OnFlagsChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - OnResizeHandleThicknessChanged event.\n//\nint EZ_control_OnResizeHandleThicknessChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - Draws the control.\n//\nint EZ_control_OnDraw(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - Key down event.\n//\nint EZ_control_OnKeyDown(ez_control_t *self, int key, int unichar);\n\n//\n// Control - Key up event.\n//\nint EZ_control_OnKeyUp(ez_control_t *self, int key, int unichar);\n\n//\n// Control - Key event.\n//\nint EZ_control_OnKeyEvent(ez_control_t *self, int key, int unichar, qbool down);\n\n//\n// Control -\n// The initial mouse event is handled by this, and then raises more specialized event handlers\n// based on the new mouse state.\n// \n// NOTICE! When extending this event you need to make sure that you tell the framework\n// that you've handled all mouse events that happened within the controls bounds in your own\n// implementation by returning true whenever the mouse is inside the control.\n// This can easily be done with the following macro: MOUSE_INSIDE_CONTROL(self, ms); \n// If this is not done, all mouse events will \"fall through\" to controls below.\n//\nint EZ_control_OnMouseEvent(ez_control_t *self, mouse_state_t *ms);\n\n//\n// Control - The mouse was pressed and then released within the bounds of the control.\n//\nint EZ_control_OnMouseClick(ez_control_t *self, mouse_state_t *mouse_state);\n\n//\n// Control - The mouse entered the controls bounds.\n//\nint EZ_control_OnMouseEnter(ez_control_t *self, mouse_state_t *mouse_state);\n\n//\n// Control - The mouse left the controls bounds.\n//\nint EZ_control_OnMouseLeave(ez_control_t *self, mouse_state_t *mouse_state);\n\n//\n// Control - A mouse button was released within the bounds of the control.\n//\nint EZ_control_OnMouseUp(ez_control_t *self, mouse_state_t *mouse_state);\n\n//\n// Control - A mouse button was released either inside or outside of the control.\n//\nint EZ_control_OnMouseUpOutside(ez_control_t *self, mouse_state_t *ms);\n\n//\n// Control - A mouse button was pressed within the bounds of the control.\n//\nint EZ_control_OnMouseDown(ez_control_t *self, mouse_state_t *mouse_state);\n\n//\n// Control - The mouse wheel was triggered within the bounds of the control.\n//\nint EZ_control_OnMouseWheel(ez_control_t *self, mouse_state_t *mouse_state);\n\n//\n// Control - The mouse is hovering within the bounds of the control.\n//\nint EZ_control_OnMouseHover(ez_control_t *self, mouse_state_t *mouse_state);\n\n//\n// Control - Sets the overall opacity of the control (including its children).\n//\nvoid EZ_control_SetOpacity(ez_control_t *self, float opacity);\n\n//\n// Control - The opacity of the control has just changed.\n//\nint EZ_control_OnOpacityChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Control - A child control has been moved.\n//\nint EZ_control_OnChildMoved(ez_control_t *self, ez_control_t *child);\n\n//\n// Control - A child control has been resized.\n//\nint EZ_control_OnChildResize(ez_control_t *self, ez_control_t *child);\n\n#endif // __EZ_CONTROLS_H__\t\n\n\n\n"
  },
  {
    "path": "src/ez_label.c",
    "content": "\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_scrollpane.c,v 1.78 2007/10/27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"keys.h\"\n#include \"utils.h\"\n#include \"common_draw.h\"\n#include \"ez_controls.h\"\n#include \"ez_label.h\"\n\n#ifdef _MSC_VER\n#pragma warning( disable : 4189 )\n#endif\n\n// =========================================================================================\n// Label\n// =========================================================================================\n\n//\n// Label - Creates a label control and initializes it.\n//\nez_label_t *EZ_label_Create(ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags, ez_label_flags_t text_flags,\n\t\t\t\t\t\t\t  const char *text)\n{\n\tez_label_t *label = NULL;\n\n\t// We have to have a tree to add the control to.\n\tif (!tree)\n\t{\n\t\treturn NULL;\n\t}\n\n\tlabel = (ez_label_t *)Q_malloc(sizeof(ez_label_t));\n\tmemset(label, 0, sizeof(ez_label_t));\n\n\tEZ_label_Init(label, tree, parent, name, description, x, y, width, height, flags, text_flags, text);\n\t\n\treturn label;\n}\n\n//\n// Label - Initializes a label control.\n//\nvoid EZ_label_Init(ez_label_t *label, ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t  char *name, char *description,\n\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t  ez_control_flags_t flags, ez_label_flags_t text_flags,\n\t\t\t\t  const char *text)\n{\n\t// Initialize the inherited class first.\n\tEZ_control_Init(&label->super, tree, parent, name, description, x, y, width, height, flags);\n\n\tlabel->super.CLASS_ID = EZ_LABEL_ID;\n\n\t// Overriden events.\n\tCONTROL_REGISTER_EVENT(label, EZ_label_OnDraw, OnDraw, ez_control_t);\n\tCONTROL_REGISTER_EVENT(label, EZ_label_OnKeyDown, OnKeyDown, ez_control_t);\n\tCONTROL_REGISTER_EVENT(label, EZ_label_OnKeyUp, OnKeyUp, ez_control_t);\n\tCONTROL_REGISTER_EVENT(label, EZ_label_OnMouseDown, OnMouseDown, ez_control_t);\n\tCONTROL_REGISTER_EVENT(label, EZ_label_OnMouseUp, OnMouseUp, ez_control_t);\n\tCONTROL_REGISTER_EVENT(label, EZ_label_OnMouseHover, OnMouseHover, ez_control_t);\n\tCONTROL_REGISTER_EVENT(label, EZ_label_OnResize, OnResize, ez_control_t);\n\tCONTROL_REGISTER_EVENT(label, EZ_label_Destroy, OnDestroy, ez_control_t);\n\n\t// Label specific events.\n\tCONTROL_REGISTER_EVENT(label, EZ_label_OnTextChanged, OnTextChanged, ez_label_t);\n\tCONTROL_REGISTER_EVENT(label, EZ_label_OnCaretMoved, OnCaretMoved, ez_label_t);\n\tCONTROL_REGISTER_EVENT(label, EZ_label_OnTextScaleChanged, OnTextScaleChanged, ez_label_t);\n\tCONTROL_REGISTER_EVENT(label, EZ_label_OnTextFlagsChanged, OnTextFlagsChanged, ez_label_t);\n\n\t((ez_control_t *)label)->ext_flags\t\t\t\t|= control_contained;\n\n\tEZ_label_SetTextFlags(label, text_flags | label_selectable);\n\tEZ_label_SetTextScale(label, 1.0);\n\tEZ_label_DeselectText(label);\n\n\tif (text) {\n\t\tEZ_label_SetText(label, text);\n\t}\n\n\tlabel->color.i = 0;\n\tlabel->color.c = RGBA_TO_COLOR(255, 255, 255, 255);\n}\n\n//\n// Label - Destroys a label control.\n//\nint EZ_label_Destroy(ez_control_t *self, qbool destroy_children)\n{\n\tez_label_t *label = (ez_label_t *)self;\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnDestroy, destroy_children);\n\n\tQ_free(label->text);\n\n\tEZ_eventhandler_Remove(label->event_handlers.OnCaretMoved, NULL, true);\n\tEZ_eventhandler_Remove(label->event_handlers.OnTextChanged, NULL, true);\n\tEZ_eventhandler_Remove(label->event_handlers.OnTextFlagsChanged, NULL, true);\n\tEZ_eventhandler_Remove(label->event_handlers.OnTextScaleChanged, NULL, true);\n\n\t// TODO: Can we just free a part like this here? How about children, will they be properly destroyed?\n\tEZ_control_Destroy(&label->super, destroy_children);\n\n\treturn 0;\n}\n\n//\n// Label - Sets the event handler for the OnTextChanged event.\n//\nvoid EZ_label_AddOnTextChanged(ez_label_t *label, ez_eventhandler_fp OnTextChanged, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(label, EZ_CONTROL_HANDLER, OnTextChanged, ez_label_t, OnTextChanged, payload);\n\tCONTROL_RAISE_EVENT(NULL, label, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Label - Sets the event handler for the OnTextScaleChanged event.\n//\nvoid EZ_label_AddOnTextScaleChanged(ez_label_t *label, ez_eventhandler_fp OnTextScaleChanged, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(label, EZ_CONTROL_HANDLER, OnTextScaleChanged, ez_label_t, OnTextScaleChanged, payload);\n\tCONTROL_RAISE_EVENT(NULL, label, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Label - Sets the event handler for the OnCaretMoved event.\n//\nvoid EZ_label_AddOnTextOnCaretMoved(ez_label_t *label, ez_eventhandler_fp OnCaretMoved, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(label, EZ_CONTROL_HANDLER, OnCaretMoved, ez_label_t, OnCaretMoved, payload);\n\tCONTROL_RAISE_EVENT(NULL, label, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Label - Calculates where in the label text that the wordwraps will be done.\n//\nstatic void EZ_label_CalculateWordwraps(ez_label_t *label)\n{\n\tint i\t\t\t\t\t= 0;\n\tint current_index\t\t= -1;\n\tint last_index\t\t\t= -1;\n//\tint current_col\t\t\t= 0;\n\tint scaled_char_size\t= label->scaled_char_size;\n\n\tlabel->num_rows\t\t\t= 1;\n\tlabel->num_cols\t\t\t= 0;\n\n\tif (label->text && (label->ext_flags & label_wraptext))\n\t{\t\n\t\t// Wordwrap the string to the virtual size of the control and save the\n\t\t// indexes where each row ends in an array.\n\t\twhile ((i < LABEL_MAX_WRAPS) && Util_GetNextWordwrapString(label->text, NULL, (current_index + 1), &current_index, LABEL_LINE_SIZE, label->super.virtual_width, scaled_char_size))\n\t\t{\n\t\t\tlabel->wordwraps[i].index = current_index;\n\t\t\tlabel->wordwraps[i].col = current_index - last_index;\n\t\t\tlabel->wordwraps[i].row = label->num_rows - 1;\n\t\t\ti++;\n\n\t\t\t// Find the number of rows and columns.\n\t\t\tlabel->num_cols = max(label->num_cols, label->wordwraps[i].col);\n\t\t\tlabel->num_rows++;\n\n\t\t\tlast_index = current_index;\n\t\t}\n\t}\n\telse if (label->text)\n\t{\n\t\t// Normal non-wrapped text, still save new line locations.\n\t\tcurrent_index = 0; // TODO : Will this be ok? Otherwise it will be -1, which definantly is bad :p\n\n\t\twhile ((i < LABEL_MAX_WRAPS) && label->text[current_index])\n\t\t{\n\t\t\tif (label->text[current_index] == '\\n')\n\t\t\t{\n\t\t\t\tlabel->wordwraps[i].index = current_index;\n\t\t\t\tlabel->wordwraps[i].col = current_index - last_index;\n\t\t\t\tlabel->wordwraps[i].row = label->num_rows - 1;\n\t\t\t\ti++;\n\n\t\t\t\t// Find the number of rows and columns.\n\t\t\t\tlabel->num_cols = max(label->num_cols, label->wordwraps[i].col);\n\t\t\t\tlabel->num_rows++;\n\t\t\t}\n\t\t\t\n\t\t\tcurrent_index++;\n\t\t}\n\t}\n\n\t// Save the row/col information for the last row also.\n\tif (label->text)\n\t{\n\t\tlabel->wordwraps[i].index\t= -1;\n\t\tlabel->wordwraps[i].row\t\t= label->num_rows - 1;\n\t\tlabel->wordwraps[i].col\t\t= label->text_length - label->wordwraps[max(0, i - 1)].index;\n\t\tlabel->num_cols\t\t\t\t= max(label->num_cols, label->wordwraps[i].col);\n\t}\n\telse\n\t{\n\t\t// No text.\n\t\tlabel->wordwraps[0].index\t= -1;\n\t\tlabel->wordwraps[0].row\t\t= -1;\n\t\tlabel->wordwraps[0].col\t\t= -1;\n\t\tlabel->num_cols\t\t\t\t= 0;\n\t\tlabel->num_rows\t\t\t\t= 0;\n\t}\n\n\t// Change the virtual height of the control to fit the text when wrapping.\n\tif (label->ext_flags & label_wraptext)\n\t{\n\t\tEZ_control_SetMinVirtualSize((ez_control_t *)label, label->super.virtual_width_min, scaled_char_size * (label->num_rows + 1));\n\t}\n\telse\n\t{\n\t\tEZ_control_SetMinVirtualSize((ez_control_t *)label, scaled_char_size * (label->num_cols + 1), scaled_char_size * (label->num_rows + 1));\n\t}\n}\n\n//\n// Label - Sets if the label should use the large charset or the normal one.\n//\nvoid EZ_label_SetLargeFont(ez_label_t *label, qbool large_font)\n{\n\tSET_FLAG(label->ext_flags, label_largefont, large_font);\n\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextFlagsChanged, NULL);\n}\n\n//\n// Label - Sets if the label should autosize itself to fit the text in the label.\n//\nvoid EZ_label_SetAutoSize(ez_label_t *label, qbool auto_size)\n{\n\tSET_FLAG(label->ext_flags, label_autosize, auto_size);\n\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextFlagsChanged, NULL);\n}\n\n//\n// Label - Sets if the label should automatically add \"...\" at the end of the string when it doesn't fit in the label.\n//\nvoid EZ_label_SetAutoEllipsis(ez_label_t *label, qbool auto_ellipsis)\n{\n\tSET_FLAG(label->ext_flags, label_autoellipsis, auto_ellipsis);\n\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextFlagsChanged, NULL);\n}\n\n//\n// Label - Sets if the text in the label should wrap to fit the width of the label.\n//\nvoid EZ_label_SetWrapText(ez_label_t *label, qbool wrap_text)\n{\n\tSET_FLAG(label->ext_flags, label_wraptext, wrap_text);\n\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextFlagsChanged, NULL);\n}\n\n//\n// Label - Sets if the text in the label should be selectable.\n//\nvoid EZ_label_SetTextSelectable(ez_label_t *label, qbool selectable)\n{\n\tSET_FLAG(label->ext_flags, label_selectable, selectable);\n\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextFlagsChanged, NULL);\n}\n\n//\n// Label - Sets if the label should be read only.\n//\nvoid EZ_label_SetReadOnly(ez_label_t *label, qbool read_only)\n{\n\tSET_FLAG(label->ext_flags, label_readonly, read_only);\n\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextFlagsChanged, NULL);\n}\n\n//\n// Label - Sets the text flags for the label.\n//\nvoid EZ_label_SetTextFlags(ez_label_t *label, ez_label_flags_t flags)\n{\n\tlabel->ext_flags = flags;\n\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextFlagsChanged, NULL);\n}\n\n//\n// Label - Gets the text flags for the label.\n//\nez_label_flags_t EZ_label_GetTextFlags(ez_label_t *label)\n{\n\treturn label->ext_flags;\n}\n\n//\n// Label - Set the text scale for the label.\n//\nvoid EZ_label_SetTextScale(ez_label_t *label, float scale)\n{\n\tlabel->scale = scale;\n\n\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextScaleChanged, NULL);\n}\n\n//\n// Label - Sets the text color of a label.\n//\nvoid EZ_label_SetTextColor(ez_label_t *label, byte r, byte g, byte b, byte alpha)\n{\n\tlabel->color.c = RGBA_TO_COLOR(r, g, b, alpha);\n}\n\n//\n// Label - Hides the caret.\n//\nvoid EZ_label_HideCaret(ez_label_t *label)\n{\n\tlabel->caret_pos.index = -1;\n}\n\n//\n// Label - Deselects the text in the label.\n//\nvoid EZ_label_DeselectText(ez_label_t *label)\n{\n\tlabel->int_flags\t&= ~label_selecting;\n\tlabel->select_start = -1;\n\tlabel->select_end\t= -1;\n}\n\n//\n// Label - Gets the size of the selected text.\n//\nint EZ_label_GetSelectedTextSize(ez_label_t *label)\n{\n\tif ((label->select_start > -1) && (label->select_end > -1))\n\t{\n\t\treturn abs(label->select_end - label->select_start) + 1;\n\t}\n\n\treturn 0;\n}\n\n//\n// Label - Gets the selected text.\n//\nvoid EZ_label_GetSelectedText(ez_label_t *label, char *target, int target_size)\n{\n\tif ((label->select_start > -1) && (label->select_end > -1))\n\t{\n\t\tint sublen = abs(label->select_end - label->select_start) + 1;\n\t\t\n\t\tif (sublen > 0)\n\t\t{\n\t\t\tsnprintf(target, min(sublen, target_size), \"%s\", label->text + min(label->select_start, label->select_end));\n\t\t}\n\t}\n}\n\n//\n// Label - Appends text at the given position in the text.\n//\nvoid EZ_label_AppendText(ez_label_t *label, int position, const char *append_text)\n{\n\tint text_len\t\t= label->text_length;\n\tint append_text_len\t= strlen(append_text);\n\n\tclamp(position, 0, text_len);\n\n\tif (!label->text)\n\t{\n\t\treturn;\n\t}\n\n\t// Reallocate the string to fit the new text.\n\tlabel->text = (char *)Q_realloc((void *)label->text, (text_len + append_text_len + 1) * sizeof(char));\n\n\t// Move the part of the string after the specified position.\n\tmemmove(\n\t\tlabel->text + position + append_text_len,\t\t// Destination, make room for the append text.\n\t\tlabel->text + position,\t\t\t\t\t\t\t// Move everything after the specified position.\n\t\t(text_len + 1 - position) * sizeof(char));\n\n\t// Copy the append text into the newly created space in the string.\n\tmemcpy(label->text + position, append_text, append_text_len * sizeof(char));\n\n\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextChanged, NULL);\n}\n\n//\n// Label - Removes text between two given indexes.\n//\nvoid EZ_label_RemoveText(ez_label_t *label, int start_index, int end_index)\n{\n\tclamp(start_index, 0, label->text_length);\n\tclamp(end_index, start_index, label->text_length);\n\n\tif (!label->text)\n\t{\n\t\treturn;\n\t}\n\n\tmemmove(\n\t\tlabel->text + start_index,\t\t\t\t\t\t\t\t// Destination.\n\t\tlabel->text + end_index,\t\t\t\t\t\t\t\t// Source.\n\t\t(label->text_length - end_index + 1) * sizeof(char));\t// Size.\n\t\n\tlabel->text = Q_realloc(label->text, (strlen(label->text) + 1) * sizeof(char));\n\n\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextChanged, NULL);\n}\n\n//\n// Label - Sets the text of a label.\n//\nvoid EZ_label_SetText(ez_label_t *label, const char *text)\n{\n\tint text_len = strlen(text) + 1;\n\n\tQ_free(label->text);\n\n\tif (text)\n\t{\n\t\tlabel->text = Q_calloc(text_len, sizeof(char));\n\t\tstrlcpy(label->text, text, text_len);\n\t}\n\n\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextChanged, NULL);\n}\n\n//\n// Label - The text flags for the label changed.\n//\nint EZ_label_OnTextFlagsChanged(ez_control_t *self, void *ext_event_info)\n{\n\tez_label_t *label = (ez_label_t *)self;\n\n\t// Deselect anything selected and hide the caret.\n\tif (!(label->ext_flags & label_selectable))\n\t{\n\t\tEZ_label_HideCaret(label);\n\t\tEZ_label_DeselectText(label);\n\t}\n\t\n\t// Make sure we recalculate the char size if we changed to large font.\n\tif (label->ext_flags & label_largefont)\n\t{\n\t\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextScaleChanged, NULL);\n\t}\n\n\t// The label will autosize on a text change, so trigger an event for that.\n\tif (label->ext_flags & label_autosize)\n\t{\n\t\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnTextChanged, NULL);\n\t}\n\n\t// Recalculate the line ends.\n\tif (label->ext_flags & label_wraptext)\n\t{\n\t\tCONTROL_RAISE_EVENT(NULL, self, ez_control_t, OnResize, NULL);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, label, ez_label_t, OnTextFlagsChanged, NULL);\n\n\treturn 0;\n}\n\n//\n// Label - The scale of the text changed.\n//\nint EZ_label_OnTextScaleChanged(ez_control_t *self, void *ext_event_info)\n{\n\tez_label_t *label\t\t= (ez_label_t *)self;\n\tint char_size\t\t\t= (label->ext_flags & label_largefont) ? 64 : 8;\n\n\tlabel->scaled_char_size\t= Q_rint(char_size * label->scale);\n\t\n\t// We need to recalculate the wordwrap stuff since the size changed.\n\tEZ_label_CalculateWordwraps(label);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, label, ez_label_t, OnTextScaleChanged, NULL);\n\n\treturn 0;\n}\n\n//\n// Label - Happens when the control has resized.\n//\nint EZ_label_OnResize(ez_control_t *self, void *ext_event_info)\n{\n\tez_label_t *label = (ez_label_t *)self;\n\t\n\t// Let the super class try first.\n\tEZ_control_OnResize(self, NULL);\n\n\t// Calculate where to wrap the text.\n\tEZ_label_CalculateWordwraps(label);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnResize, NULL);\n\n\treturn 0;\n}\n\n//\n// Label - Draws a label control.\n//\nint EZ_label_OnDraw(ez_control_t *self, void *ext_event_info)\n{\n\tint x, y, i = 0;\n\tchar line[LABEL_LINE_SIZE];\n\tez_label_t *label\t\t= (ez_label_t *)self;\n\tint scaled_char_size\t= label->scaled_char_size;\n\tint last_index\t\t\t= -1;\n\tint curr_row\t\t\t= 0;\n\tint curr_col\t\t\t= 0;\n\tcolor_t selection_color\t= RGBA_TO_COLOR(178, 0, 255, 125);\n\tcolor_t caret_color\t\t= RGBA_TO_COLOR(255, 0, 0, 125);\n\n\t// Let the super class draw first.\n\tEZ_control_OnDraw(self, NULL);\n\n\t// Get the position we're drawing at.\n\tEZ_control_GetDrawingPosition(self, &x, &y);\n\n\t// Find any newlines and draw line by line.\n\tfor (i = 0; i <= label->text_length; i++)\n\t{\n\t\t// Draw selection markers.\n\t\tif (label->ext_flags & label_selectable)\n\t\t{\n\t\t\tif (((label->select_start > -1) && (label->select_end > -1)\t\t\t// Is something selected at all?\n\t\t\t\t&& (label->select_end != label->select_start))\t\t\t\t\t// Only highlight if we have at least 1 char selected.\n\t\t\t\t&& (((i >= label->select_start) && (i < label->select_end))\t\t// Is this index selected?\n\t\t\t\t\t|| ((i >= label->select_end) && (i < label->select_start)))\n\t\t\t\t)\n\t\t\t{\n\t\t\t\t// If this is one of the selected letters, draw a selection thingie behind it.\n\t\t\t\t// TODO : Make this an XOR drawing ontop of the text instead?\n\t\t\t\tDraw_AlphaFillRGB(\n\t\t\t\t\tx + (curr_col * scaled_char_size), \n\t\t\t\t\ty + (curr_row * scaled_char_size), \n\t\t\t\t\tscaled_char_size, scaled_char_size, \n\t\t\t\t\tselection_color);\n\t\t\t}\n\n\t\t\tif (i == label->caret_pos.index)\n\t\t\t{\n\t\t\t\t// Draw the caret.\n\t\t\t\tDraw_AlphaFillRGB(\n\t\t\t\t\tx + (curr_col * scaled_char_size), \n\t\t\t\t\ty + (curr_row * scaled_char_size), \n\t\t\t\t\tmax(5, Q_rint(scaled_char_size * 0.1)), scaled_char_size, \n\t\t\t\t\tcaret_color);\n\t\t\t}\n\t\t}\n\n\t\tif (i == label->wordwraps[curr_row].index || label->text[i] == '\\0')\n\t\t{\n\t\t\t// We found the end of a line, copy the contents of the line to the line buffer.\n\t\t\tsnprintf(line, min(LABEL_LINE_SIZE, (i - last_index) + 1), \"%s\", (label->text + last_index + 1));\n\t\t\tlast_index = i;\t// Skip the newline character\n\n\t\t\tif (label->ext_flags & label_largefont) {\n\t\t\t\tDraw_BigString(x, y + (curr_row * scaled_char_size), line, NULL, 0, label->scale, 1, 0);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SColoredAlphaString(x, y + (curr_row * scaled_char_size), line, &label->color, 1, 0, label->scale, 1, false);\n\t\t\t}\n\n\t\t\tcurr_row++;\n\t\t\tcurr_col = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcurr_col++;\n\t\t}\n\t}\n\n\t// TODO : Remove this test stuff.\n\t{\n\t\tchar tmp[1024];\n\n\t\tint sublen = abs(label->select_end - label->select_start);\n\t\tif (sublen > 0)\n\t\t{\n\t\t\tsnprintf(tmp, sublen + 1, \"%s\", label->text + min(label->select_start, label->select_end));\n\t\t\tDraw_String(x + (self->width / 2), y + (self->height / 2) + 8, tmp);\n\t\t}\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnDraw, NULL);\n\n\treturn 0;\n}\n\n//\n// Label - Finds the row and column for a specified index. The proper wordwrap indexes must've been calculated first.\n//\nstatic void EZ_label_FindRowColumnByIndex(ez_label_t *label, int index, int *row, int *column)\n{\n\tint i = 0;\n\n\t// Find the row the index is on.\n\tif (row)\n\t{\n\t\t// Find the wordwrap that's closest to the index but still larger\n\t\t// that will be the end of the row that the index is on.\n\t\twhile ((label->wordwraps[i].index != -1) && (label->wordwraps[i].index < index))\n\t\t{\n\t\t\ti++;\n\t\t}\n\n\t\t// Save the row. (The last row as -1 as index, but still has the correct row saved).\n\t\t(*row) = label->wordwraps[i].row;\n\t}\n\n\tif (column)\n\t{\n\t\t// We don't have the last rows end index saved explicitly, so get it here.\n\t\tint row_end_index = (label->wordwraps[i].index < 0) ? strlen(label->text) : label->wordwraps[i].index;\n\n\t\t// The column at the end of the row minus the difference between\n\t\t// the given index and the index at the end of the row gives us\n\t\t// what column the index is in.\n\t\t(*column) = label->wordwraps[i].col - (row_end_index - index);\n\t}\n}\n\n//\n// Label - Finds the index in the label text at the specified row and column.\n//\nstatic int EZ_label_FindIndexByRowColumn(ez_label_t *label, int row, int column)\n{\n\tint row_end_index = 0;\n\n\t// Make sure we don't do anythin stupid.\n\tif ((row < 0) || (row > (label->num_rows - 1)) || (column < 0)) // || (label->wordwraps[row].index < 0))\n\t{\n\t\treturn -1;\n\t}\n\n\t// We don't have the last rows end index saved explicitly, so get it here.\n\trow_end_index = (label->wordwraps[row].index < 0) ? strlen(label->text) : label->wordwraps[row].index;\n\n\tif (column > label->wordwraps[row].col)\n\t{\n\t\t// There is no such column on the specified row\n\t\t// return the index of the last character on that row instead.\n\t\treturn row_end_index;\n\t}\n\n\t// Return the index of the specified column.\n\treturn  row_end_index - (label->wordwraps[row].col - column);\n}\n\n//\n// Label - Finds at what text index the mouse was clicked.\n//\nstatic int EZ_label_FindMouseTextIndex(ez_label_t *label, mouse_state_t *ms)\n{\n\t// TODO : Support big font gaps?\n\tint x, y;\n\tint row, col;\n\n\t// Get the position we're drawing at.\n\tEZ_control_GetDrawingPosition((ez_control_t *)label, &x, &y);\n\n\t// Find the row and column where the mouse is.\n\trow = ((ms->y - y) / label->scaled_char_size);\n\tcol = ((ms->x - x) / label->scaled_char_size);\n\n\t// Give the last index if the mouse is past the last row.\n\tif (row > (label->num_rows - 1))\n\t{\n\t\treturn label->text_length;\n\t}\n\n\treturn EZ_label_FindIndexByRowColumn(label, row, col) + 1;\n}\n\n//\n// Label - Set the caret position.\n//\nvoid EZ_label_SetCaretPosition(ez_label_t *label, int caret_pos)\n{\n\tif (label->ext_flags & label_selectable)\n\t{\n\t\tlabel->caret_pos.index = caret_pos;\n\t\tclamp(label->caret_pos.index, -1, label->text_length);\n\t}\n\telse\n\t{\n\t\tlabel->caret_pos.index = -1;\n\t\tEZ_label_DeselectText(label);\n\t}\n\n\tCONTROL_RAISE_EVENT(NULL, label, ez_label_t, OnCaretMoved, NULL);\n}\n\n//\n// Label - Moves the caret up or down.\n//\nstatic void EZ_label_MoveCaretVertically(ez_label_t *label, int amount)\n{\n\t// Find the index of the character above/below that position,\n\t// and set the caret to that position, if there is no char\n\t// exactly above that point, jump to the end of the line\n\t// of the previous/next row instead.\n\n\tint new_caret_index = EZ_label_FindIndexByRowColumn(label, max(0, label->caret_pos.row + amount), label->caret_pos.col);\n\t\n\tif (new_caret_index >= 0)\n\t{\n\t\tEZ_label_SetCaretPosition(label, new_caret_index);\n\t}\n}\n\n//\n// Label - The caret was moved.\n//\nint EZ_label_OnCaretMoved(ez_control_t *self, void *ext_event_info)\n{\n\tez_label_t *label\t\t= (ez_label_t *)self;\n\tint scaled_char_size\t= label->scaled_char_size;\n\tint row_visible_start\t= 0;\n\tint row_visible_end\t\t= 0;\n\tint\tcol_visible_start\t= 0;\n\tint col_visible_end\t\t= 0;\n\tint prev_caret_row\t\t= label->caret_pos.row;\n\tint prev_caret_col\t\t= label->caret_pos.col;\n\tint new_scroll_x\t\t= self->virtual_x;\n\tint new_scroll_y\t\t= self->virtual_y;\n\tint num_visible_rows\t= Q_rint((float)self->height / scaled_char_size - 1);\t// The number of currently visible rows.\n\tint num_visible_cols\t= Q_rint((float)self->width / scaled_char_size - 1);\n\n\t// Find the new row and column of the caret.\n\tEZ_label_FindRowColumnByIndex(label, label->caret_pos.index, &label->caret_pos.row, &label->caret_pos.col);\n\n\trow_visible_start\t= (self->virtual_y / scaled_char_size);\n\trow_visible_end\t\t= (row_visible_start + num_visible_rows);\n\n\t// Vertical scrolling.\n\tif (label->caret_pos.row <= row_visible_start)\n\t{\n\t\t// Caret at the top of the visible part of the text.\n\t\tnew_scroll_y = label->caret_pos.row * scaled_char_size;\n\t}\n\telse if (prev_caret_row >= label->caret_pos.row)\n\t{\n\t\t// Caret in the middle.\n\t}\n\telse if (label->caret_pos.row >= (row_visible_end - 1))\n\t{\n\t\t// Caret at the bottom of the visible part.\n\t\tnew_scroll_y = (label->caret_pos.row - num_visible_rows) * scaled_char_size;\n\n\t\t// Special case if it's the last line. Make sure we're allowed to\n\t\t// see the entire row, by scrolling a bit extra at the end.\n\t\tif ((label->caret_pos.row == (label->num_rows - 1)) && (new_scroll_y < label->super.virtual_height_min))\n\t\t{\n\t\t\tnew_scroll_y += Q_rint(0.2 * scaled_char_size);\n\t\t}\n\t}\n\n\t// Only scroll horizontally if the text is not wrapped.\n\tif (!(label->ext_flags & label_wraptext))\n\t{\n\t\tcol_visible_start\t= (self->virtual_x / scaled_char_size);\n\t\tcol_visible_end\t\t= (col_visible_start + num_visible_cols);\n\n\t\tif (label->caret_pos.col <= (col_visible_start + 1))\n\t\t{\n\t\t\t// At the start of the visible text (columns).\n\t\t\tnew_scroll_x = (label->caret_pos.col - 1) * scaled_char_size;\n\t\t}\n\t\telse if (prev_caret_col >= label->caret_pos.col)\n\t\t{\n\t\t\t// In the middle of the visible text.\n\t\t}\n\t\telse if (label->caret_pos.col >= (col_visible_end - 1))\n\t\t{\n\t\t\t// At the end of the visible text.\n\t\t\tnew_scroll_x = (label->caret_pos.col - num_visible_cols) * scaled_char_size;\n\t\t}\n\t}\n\n\t// Only set a new scroll if it changed.\n\tif ((new_scroll_x != self->virtual_x) || (new_scroll_y != self->virtual_y))\n\t{\n\t\tEZ_control_SetScrollPosition(self, new_scroll_x, new_scroll_y);\n\t}\n\n\t// Select while holding down shift.\n\tif (keydown[K_SHIFT] && (label->select_start > -1))\n\t{\n\t\tlabel->int_flags |= label_selecting;\n\t\tlabel->select_end = label->caret_pos.index;\n\t}\n\n\t// Deselect any selected text if we're not in selection mode.\n\tif (!(label->int_flags & label_selecting))\n\t{\n\t\tEZ_label_DeselectText(label);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, label, ez_label_t, OnCaretMoved, NULL);\n\n\treturn 0;\n}\n\n//\n// Label - The text changed in the label.\n//\nint EZ_label_OnTextChanged(ez_control_t *self, void *ext_event_info)\n{\n\tez_label_t *label = (ez_label_t *)self;\n\n\t// Make sure we have the correct text length saved.\n\tlabel->text_length = label->text ? strlen(label->text) : 0;\n\n\t// Find the new places to wrap on.\n\tEZ_label_CalculateWordwraps(label);\n\n\tif (label->ext_flags & label_autosize)\n\t{\n\t\t// Only resize after the number of rows if we're wrapping the text.\n\t\tif (label->ext_flags & label_wraptext)\n\t\t{\n\t\t\tEZ_control_SetSize(self, self->width, (label->scaled_char_size * label->num_rows));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tEZ_control_SetSize(self, (label->scaled_char_size * label->num_cols), (label->scaled_char_size * label->num_rows));\n\t\t}\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, label, ez_label_t, OnTextChanged, NULL);\n\n\treturn 0;\n}\n\n//\n// Label - Handle a page up/dn key press.\n//\nstatic void EZ_label_PageUpDnKeyDown(ez_label_t *label, int key)\n{\n\tint num_visible_rows = Q_rint((float)label->super.height / label->scaled_char_size);\n\n\tswitch (key)\n\t{\n\t\tcase K_PGUP :\n\t\t{\n\t\t\tEZ_label_MoveCaretVertically(label, -num_visible_rows);\n\t\t\tbreak;\n\t\t}\n\t\tcase K_PGDN :\n\t\t{\n\t\t\tEZ_label_MoveCaretVertically(label, num_visible_rows);\n\t\t\tbreak;\n\t\t}\n\t\tdefault :\n\t\t{\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n//\n// Label - Gets the next word boundary.\n//\nstatic int EZ_label_GetNextWordBoundary(ez_label_t *label, int cur_pos, qbool forward)\n{\n\tchar *txt\t= label->text;\n\tint dir\t\t= forward ? 1 : -1;\t\t\n\n\t// Make sure we're not out of bounds.\n\tclamp(cur_pos, 0, label->text_length);\n\n\t// Eat all the whitespaces.\n\twhile ((cur_pos >= 0) && (cur_pos <= label->text_length) && ((txt[cur_pos] == ' ') || (txt[cur_pos] == '\\n')))\n\t{\n\t\tcur_pos += dir;\n\t}\n\t\n\t// Find the next word boundary.\n\twhile ((cur_pos >= 0) && (cur_pos <= label->text_length) && (txt[cur_pos] != ' ') && (txt[cur_pos] != '\\n'))\n\t{\n\t\tcur_pos += dir;\n\t}\n\n\treturn clamp(cur_pos, 0, label->text_length);\n}\n\n//\n// Label - Handle a arrow key press.\n//\nstatic void EZ_label_ArrowKeyDown(ez_label_t *label, int key)\n{\n\tswitch(key)\n\t{\n\t\tcase K_LEFTARROW :\n\t\t{\n\t\t\tif (keydown[K_CTRL])\n\t\t\t{\n\t\t\t\tEZ_label_SetCaretPosition(label, EZ_label_GetNextWordBoundary(label, label->caret_pos.index, false));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tEZ_label_SetCaretPosition(label, max(0, label->caret_pos.index - 1));\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase K_RIGHTARROW :\n\t\t{\n\t\t\tif (keydown[K_CTRL])\n\t\t\t{\n\t\t\t\tEZ_label_SetCaretPosition(label, EZ_label_GetNextWordBoundary(label, label->caret_pos.index, true));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tEZ_label_SetCaretPosition(label, label->caret_pos.index + 1);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase K_UPARROW :\n\t\t{\n\t\t\tEZ_label_MoveCaretVertically(label, -1);\n\t\t\tbreak;\n\t\t}\n\t\tcase K_DOWNARROW :\n\t\t{\n\t\t\tEZ_label_MoveCaretVertically(label, 1);\n\t\t\tbreak;\n\t\t}\n\t\tdefault :\n\t\t{\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n//\n// Label - Handle a HOME/END key press.\n//\nstatic void EZ_label_EndHomeKeyDown(ez_label_t *label, int key)\n{\n\tswitch(key)\n\t{\n\t\tcase K_HOME :\n\t\t{\n\t\t\tif (keydown[K_CTRL])\n\t\t\t{\n\t\t\t\t// Move the caret to the start of the text.\n\t\t\t\tEZ_label_SetCaretPosition(label, 0);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Move the caret to the start of the current line.\n\t\t\t\tEZ_label_SetCaretPosition(label, label->caret_pos.index - label->caret_pos.col + 1);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase K_END :\n\t\t{\n\t\t\tif (keydown[K_CTRL])\n\t\t\t{\n\t\t\t\t// Move the caret to the end of the text.\n\t\t\t\tEZ_label_SetCaretPosition(label, label->text_length);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Move the caret to the end of the line.\n\t\t\t\tint i = 0;\n\t\t\t\tint line_end_index = 0;\n\n\t\t\t\t// Find the last index of the current row.\n\t\t\t\twhile ((label->wordwraps[i].index > 0) && (label->wordwraps[i].index < label->caret_pos.index))\n\t\t\t\t{\n\t\t\t\t\ti++;\n\t\t\t\t}\n\n\t\t\t\t// Special case for last line.\n\t\t\t\tline_end_index = (label->wordwraps[i].index < 0) ? label->text_length : label->wordwraps[i].index;\n\t\t\t\tEZ_label_SetCaretPosition(label, line_end_index);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tdefault :\n\t\t{\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n//\n// Label - Handle backspace/delete key presses.\n//\nstatic void EZ_label_BackspaceDeleteKeyDown(ez_label_t *label, int key)\n{\n\t// Don't allow deleting if read only.\n\tif (label->ext_flags & label_readonly)\n\t{\n\t\treturn;\n\t}\n\n\tif (LABEL_TEXT_SELECTED(label))\n\t{\n\t\t// Find the start and end of the selected text.\n\t\tint start = (label->select_start < label->select_end) ? label->select_start : label->select_end;\n\t\tint end\t  = (label->select_start < label->select_end) ? label->select_end : label->select_start;\n\n\t\t// Remove a selected chunk of text.\t\t\t\t\n\t\tEZ_label_RemoveText(label, start, end);\n\t\t\n\t\t// When deleting we need to move the cursor so that\n\t\t// it's at the \"same point\" in the text afterwards.\n\t\tif (label->select_start < label->select_end)\n\t\t{\n\t\t\tEZ_label_SetCaretPosition(label, label->select_start);\n\t\t}\n\n\t\t// Deselect what we just deleted.\n\t\tEZ_label_DeselectText(label);\n\t}\n\telse if (key == K_BACKSPACE)\n\t{\n\t\t// Remove a single character.\n\t\tEZ_label_RemoveText(label, label->caret_pos.index - 1, label->caret_pos.index);\n\t\tEZ_label_SetCaretPosition(label, label->caret_pos.index - 1);\n\t}\n\telse if (key == K_DEL)\n\t{\n\t\tEZ_label_RemoveText(label, label->caret_pos.index, label->caret_pos.index + 1);\n\t}\n}\n\n//\n// Label - Handles Ctrl combination key presses.\n//\nstatic void EZ_label_CtrlComboKeyDown(ez_label_t *label, int key)\n{\n\tchar c = (char)key;\n\n\tswitch (c)\n\t{\n\t\tcase 'c' :\n\t\tcase 'C' :\n\t\t{\n\t\t\tif (LABEL_TEXT_SELECTED(label))\n\t\t\t{\n\t\t\t\t// CTRL + C (Copy to clipboard).\n\t\t\t\tint selected_len = EZ_label_GetSelectedTextSize(label) + 1;\n\t\t\t\tchar *selected_text = (char *)Q_malloc(selected_len * sizeof(char));\n\n\t\t\t\tEZ_label_GetSelectedText(label, selected_text, selected_len);\n\n\t\t\t\tCopyToClipboard(selected_text);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tcase 'v' :\n\t\tcase 'V' :\n\t\t{\n\t\t\t// CTRL + V (Paste from clipboard).\n\t\t\tchar *pasted_text = ReadFromClipboard();\n\t\t\tEZ_label_AppendText(label, label->caret_pos.index, pasted_text);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'a' :\n\t\tcase 'A' :\n\t\t{\n\t\t\t// CTRL + A (Select all).\n\t\t\tlabel->select_start = 0;\n\t\t\tlabel->select_end\t= label->text_length;\n\t\t\tlabel->int_flags\t|= label_selecting;\n\t\t\tEZ_label_SetCaretPosition(label, label->select_end);\n\t\t\tlabel->int_flags\t&= ~label_selecting;\n\t\t\tbreak;\n\t\t}\n\t\tdefault :\n\t\t{\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n//\n// Label - Handles input key was presses.\n//\nstatic void EZ_label_InputKeyDown(ez_label_t *label, int key)\n{\n\tchar c = (char)key;\n\n\t// Don't allow input if read only.\n\tif (label->ext_flags & label_readonly)\n\t{\n\t\treturn;\n\t}\n\n\t// User typing text into the label.\n\t// TODO : Add support for input of all quake chars\n\tif ((label->caret_pos.index >= 0) \n\t\t&& (\t\n\t\t\t\t((c >= 'a') && (c <= 'z'))\n\t\t\t\t||\t\n\t\t\t\t((c >= 'A') && (c <= 'Z'))\n\t\t\t)\n\t\t)\n\t{\n\t\tchar char_str[2];\t\t\t\t\n\n\t\t// First remove any selected text.\n\t\tif (LABEL_TEXT_SELECTED(label))\n\t\t{\n\t\t\tint start = (label->select_start < label->select_end) ? label->select_start : label->select_end;\n\t\t\tint end\t  = (label->select_start < label->select_end) ? label->select_end : label->select_start;\n\n\t\t\tEZ_label_RemoveText(label, start, end);\n\n\t\t\t// Make sure the caret stays in the same place.\n\t\t\tEZ_label_SetCaretPosition(label, (label->select_start < label->select_end) ? label->select_start : label->select_end);\n\t\t}\n\n\t\t// Turn off selecting mode, since we don't want to be selecting\n\t\t// when typing capital letters for instance.\n\t\tEZ_label_DeselectText(label);\n\n\t\t// Add the text.\n\t\tsnprintf(char_str, 2, \"%c\", (char)key);\n\t\tEZ_label_AppendText(label, label->caret_pos.index, char_str);\n\t\tEZ_label_SetCaretPosition(label, label->caret_pos.index + 1);\n\t}\n}\n\n//\n// Label - Key down event.\n//\nint EZ_label_OnKeyDown(ez_control_t *self, int key, int unichar)\n{\n\tez_label_t *label\t= (ez_label_t *)self;\n\tint key_handled\t\t= false;\n\n\tkey_handled = EZ_control_OnKeyDown(self, key, unichar);\n\n\tif (key_handled)\n\t{\n\t\treturn true;\n\t}\n\n\tkey_handled = true;\n\n\t// Only handle keys if text is selectable.\n\tif (label->ext_flags & label_selectable)\n\t{\n\t\t// Key down.\n\t\tswitch (key)\n\t\t{\n\t\t\tcase K_LEFTARROW :\n\t\t\tcase K_RIGHTARROW :\n\t\t\tcase K_UPARROW :\n\t\t\tcase K_DOWNARROW :\n\t\t\t{\n\t\t\t\tEZ_label_ArrowKeyDown(label, key);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase K_PGDN :\n\t\t\tcase K_PGUP :\n\t\t\t{\n\t\t\t\tEZ_label_PageUpDnKeyDown(label, key);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase K_HOME :\n\t\t\tcase K_END :\n\t\t\t{\n\t\t\t\tEZ_label_EndHomeKeyDown(label, key);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase K_TAB :\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase K_LSHIFT :\n\t\t\tcase K_RSHIFT :\n\t\t\tcase K_SHIFT :\n\t\t\t{\n\t\t\t\t// Start selecting.\n\t\t\t\tlabel->select_start = label->caret_pos.index;\n\t\t\t\tlabel->int_flags |= label_selecting;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase K_RCTRL :\n\t\t\tcase K_LCTRL :\n\t\t\tcase K_CTRL :\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase K_DEL :\n\t\t\tcase K_BACKSPACE :\n\t\t\t{\n\t\t\t\tEZ_label_BackspaceDeleteKeyDown(label, key);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault :\n\t\t\t{\n\t\t\t\tif (keydown[K_CTRL])\n\t\t\t\t{\n\t\t\t\t\tEZ_label_CtrlComboKeyDown(label, key);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tEZ_label_InputKeyDown(label, key);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(&key_handled, self, ez_control_t, OnKeyDown, key, unichar);\n\treturn key_handled;\n}\n\n//\n// Label - Key up event.\n//\nint EZ_label_OnKeyUp(ez_control_t *self, int key, int unichar)\n{\n\tez_label_t *label\t= (ez_label_t *)self;\n\tint key_handled\t\t= false;\n\n\tkey_handled = EZ_control_OnKeyUp(self, key, unichar);\n\n\tif (key_handled)\n\t{\n\t\treturn true;\n\t}\n\n\tkey_handled = true;\n\n\t// Key up.\n\tswitch (key)\n\t{\n\t\tcase K_SHIFT :\n\t\t{\n\t\t\tlabel->int_flags &= ~label_selecting;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn key_handled;\n}\n\n//\n// Label - The mouse is hovering within the bounds of the label.\n//\nint EZ_label_OnMouseHover(ez_control_t *self, mouse_state_t *ms)\n{\n\tqbool mouse_handled = false;\n\tez_label_t *label = (ez_label_t *)self;\n\n\t// Call the super class first.\n\tmouse_handled = EZ_control_OnMouseHover(self, ms);\n\n\t// Find at what index in the text where the mouse is currently over.\n\tif (label->int_flags & label_selecting)\n\t{\n\t\tlabel->select_end = EZ_label_FindMouseTextIndex(label, ms);\n\t\tEZ_label_SetCaretPosition(label, label->select_end);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnMouseHover, ms);\n\n\treturn mouse_handled;\n}\n\n//\n// Label - Handles the mouse down event.\n//\nint EZ_label_OnMouseDown(ez_control_t *self, mouse_state_t *ms)\n{\n\tint mouse_handled = false;\n\tez_label_t *label = (ez_label_t *)self;\n\n\t// Call the super class first.\n\tmouse_handled = EZ_control_OnMouseDown(self, ms);\n\n\t// Find at what index in the text the mouse was clicked.\n\tlabel->select_start = EZ_label_FindMouseTextIndex(label, ms);\n\n\t// Reset the caret and selection end since we just started a new select / caret positioning.\n\tlabel->select_end = -1;\n\tEZ_label_HideCaret(label);\n\n\t// We just started to select some text.\n\tlabel->int_flags |= label_selecting;\n\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled, self, ez_control_t, OnMouseDown, ms);\n\n\treturn true;\n}\n\n//\n// Label - Handles the mouse up event.\n//\nint EZ_label_OnMouseUp(ez_control_t *self, mouse_state_t *ms)\n{\n\tint mouse_handled = false;\n\tez_label_t *label = (ez_label_t *)self;\n\n\t// Call the super class first.\n\tmouse_handled = EZ_control_OnMouseUp(self, ms);\n\n\t// Find at what index in the text where the mouse was released.\n\tif ((self->int_flags & control_focused) && (label->int_flags & label_selecting))\n\t{\n\t\tlabel->select_end = EZ_label_FindMouseTextIndex(label, ms);\n\t\t\n\t\tEZ_label_SetCaretPosition(label, label->select_end);\n\t}\n\telse\n\t{\n\t\tlabel->select_start\t\t= -1;\n\t\tlabel->select_end\t\t= -1;\n\t\tEZ_label_SetCaretPosition(label, -1);\n\t}\n\n\t// We've stopped selecting.\n\tlabel->int_flags &= ~label_selecting;\n\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled, self, ez_control_t, OnMouseUp, ms);\n\n\treturn mouse_handled;\n}\n\n\n"
  },
  {
    "path": "src/ez_label.h",
    "content": "\n#ifndef __EZ_LABEL_H__\n#define __EZ_LABEL_H__\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_label.h,v 1.55 2007-10-27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"ez_controls.h\"\n\n// =========================================================================================\n// Label\n// =========================================================================================\n\ntypedef enum ez_label_flags_e\n{\n\tlabel_largefont\t\t= (1 << 0),\t\t// Use the large font.\n\tlabel_autosize\t\t= (1 << 1),\t\t// Autosize the label to fit the text (if it's not wrapped).\n\tlabel_autoellipsis\t= (1 << 2),\t\t// Show ... at the end of the text if it doesn't fit within the label.\n\tlabel_wraptext\t\t= (1 << 3),\t\t// Wrap the text to fit inside the label.\n\tlabel_selectable\t= (1 << 4),\t\t// Is text selectable?\n\tlabel_readonly\t\t= (1 << 5)\t\t// Is input allowed?\n} ez_label_flags_t;\n\n#define LABEL_DEFAULT_FLAGS\t(label_wraptext | label_selectable)\n\ntypedef enum ez_label_iflags_e\n{\n\tlabel_selecting\t\t= (1 << 0)\t\t// Are we selecting text?\n} ez_label_iflags_t;\n\n#define LABEL_MAX_WRAPS\t\t512\n#define LABEL_LINE_SIZE\t\t1024\n\n// Is any text selected in the label?\n#define LABEL_TEXT_SELECTED(label) ((label->select_start > -1) && (label->select_end > - 1) && abs(label->select_end - label->select_start) > 0)\n\ntypedef struct ez_label_eventcount_s\n{\n\tint\tOnTextChanged;\n\tint\tOnCaretMoved;\n\tint\tOnTextScaleChanged;\n\tint\tOnTextFlagsChanged;\n} ez_label_eventcount_t;\n\ntypedef struct ez_label_events_s\n{\n\tez_event_fp\tOnTextChanged;\t\t\t// Event raised when the text in the label is changed.\n\tez_event_fp\tOnCaretMoved;\t\t\t// The caret was moved.\n\tez_event_fp\tOnTextScaleChanged;\t\t// The scale of the text changed.\n\tez_event_fp\tOnTextFlagsChanged;\t\t// The text flags changed.\n} ez_label_events_t;\n\ntypedef struct ez_label_eventhandlers_s\n{\n\tez_eventhandler_t\t\t*OnTextChanged;\n\tez_eventhandler_t\t\t*OnCaretMoved;\n\tez_eventhandler_t\t\t*OnTextScaleChanged;\n\tez_eventhandler_t\t\t*OnTextFlagsChanged;\n} ez_label_eventhandlers_t;\n\ntypedef struct ez_label_textpos_s\n{\n\tint index;\n\tint row;\n\tint col;\n} ez_label_textpos_t;\n\ntypedef struct ez_label_s\n{\n\tez_control_t\t\t\tsuper;\t\t\t\t\t\t// Super class.\n\n\tez_label_events_t\t\tevents;\t\t\t\t\t\t// Specific events for the label control.\n\tez_label_eventhandlers_t event_handlers;\t\t\t// Specific event handlers for the label control.\n\tez_label_eventcount_t\tinherit_levels;\n\tez_label_eventcount_t\toverride_counts;\n\n\tchar\t\t\t\t\t*text;\t\t\t\t\t\t// The text to be shown in the label.\n\tint\t\t\t\t\t\ttext_length;\t\t\t\t// The length of the label text (excluding \\0).\n\t\n\tez_label_flags_t\t\text_flags;\t\t\t\t\t// External flags for how the text in the label is formatted.\n\tez_label_iflags_t\t\tint_flags;\t\t\t\t\t// Internal flags for the current state of the label.\n\n\tez_label_textpos_t\t\twordwraps[LABEL_MAX_WRAPS+1];\t// Positions in the text where line breaks are located.\n\tclrinfo_t\t\t\t\tcolor;\t\t\t\t\t\t// The text color of the label. // TODO : Make this a pointer?\n\tfloat\t\t\t\t\tscale;\t\t\t\t\t\t// The scale of the text in the label.\n\tint\t\t\t\t\t\tscaled_char_size;\t\t\t// The actual size of the characters in the text.\n\tint\t\t\t\t\t\tselect_start;\t\t\t\t// At what index the currently selected text starts at \n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// (this can be greater than select_end).\n\tint\t\t\t\t\t\tselect_end;\t\t\t\t\t// The end index of the selected text.\n\tez_label_textpos_t\t\tcaret_pos;\t\t\t\t\t// The position of the caret (index / row / column).\n\tint\t\t\t\t\t\tnum_rows;\t\t\t\t\t// The current number of rows in the label.\n\tint\t\t\t\t\t\tnum_cols;\t\t\t\t\t// The current number of cols (for the longest row).\n} ez_label_t;\n\nez_label_t *EZ_label_Create(ez_tree_t *tree, ez_control_t *parent, \n\t\t\t\t\t\t\t  char *name, char *description, \n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags, ez_label_flags_t text_flags,\n\t\t\t\t\t\t\t  const char *text);\n\nvoid EZ_label_Init(ez_label_t *label, ez_tree_t *tree, ez_control_t *parent, \n\t\t\t\t  char *name, char *description, \n\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t  ez_control_flags_t flags, ez_label_flags_t text_flags,\n\t\t\t\t  const char *text);\n\n//\n// Label - Destroys a label control.\n//\nint EZ_label_Destroy(ez_control_t *self, qbool destroy_children);\n\n//\n// Label - Sets if the label should use the large charset or the normal one.\n//\nvoid EZ_label_SetLargeFont(ez_label_t *label, qbool large_font);\n\n//\n// Label - Sets if the label should autosize itself to fit the text in the label.\n//\nvoid EZ_label_SetAutoSize(ez_label_t *label, qbool auto_size);\n\n//\n// Label - Sets if the label should automatically add \"...\" at the end of the string when it doesn't fit in the label.\n//\nvoid EZ_label_SetAutoEllipsis(ez_label_t *label, qbool auto_ellipsis);\n\n//\n// Label - Sets if the text in the label should wrap to fit the width of the label.\n//\nvoid EZ_label_SetWrapText(ez_label_t *label, qbool wrap_text);\n\n//\n// Label - Sets the text flags for the label.\n//\nvoid EZ_label_SetTextFlags(ez_label_t *label, ez_label_flags_t flags);\n\n//\n// Label - Sets if the label should be read only.\n//\nvoid EZ_label_SetReadOnly(ez_label_t *label, qbool read_only);\n\n//\n// Label - Sets if the text in the label should be selectable.\n//\nvoid EZ_label_SetTextSelectable(ez_label_t *label, qbool selectable);\n\n//\n// Label - Sets the event handler for the OnTextChanged event.\n//\nvoid EZ_label_AddOnTextChanged(ez_label_t *label, ez_eventhandler_fp OnTextChanged, void *payload);\n\n//\n// Label - Sets the event handler for the OnTextScaleChanged event.\n//\nvoid EZ_label_AddOnTextScaleChanged(ez_label_t *label, ez_eventhandler_fp OnTextScaleChanged, void *payload);\n\n//\n// Label - Sets the event handler for the OnCaretMoved event.\n//\nvoid EZ_label_AddOnTextOnCaretMoved(ez_label_t *label, ez_eventhandler_fp OnCaretMoved, void *payload);\n\n//\n// Label - Hides the caret.\n//\nvoid EZ_label_HideCaret(ez_label_t *label);\n\n//\n// Label - Gets the size of the selected text.\n//\nint EZ_label_GetSelectedTextSize(ez_label_t *label);\n\n//\n// Label - Gets the selected text.\n//\nvoid EZ_label_GetSelectedText(ez_label_t *label, char *target, int target_size);\n\n//\n// Label - Deselects the text in the label.\n//\nvoid EZ_label_DeselectText(ez_label_t *label);\n\n//\n// Label - Set the text scale for the label.\n//\nvoid EZ_label_SetTextScale(ez_label_t *label, float scale);\n\n//\n// Label - Sets the color of the text in the label.\n//\nvoid EZ_label_SetTextColor(ez_label_t *self, byte r, byte g, byte b, byte alpha);\n\n//\n// Label - Sets the text of the label (will be reallocated and copied to the heap).\n//\nvoid EZ_label_SetText(ez_label_t *self, const char *text);\n\n//\n// Label - Gets the text flags for the label.\n//\nez_label_flags_t EZ_label_GetTextFlags(ez_label_t *label);\n\n//\n// Label - Sets the text flags for the label.\n//\nvoid EZ_label_SetTextFlags(ez_label_t *label, ez_label_flags_t flags);\n\n//\n// Label - Appends text at the given position in the text.\n//\nvoid EZ_label_AppendText(ez_label_t *label, int position, const char *append_text);\n\n//\n// Label - Set the caret position.\n//\nvoid EZ_label_SetCaretPosition(ez_label_t *label, int caret_pos);\n\n//\n// Label - The text flags for the label changed.\n//\nint EZ_label_OnTextFlagsChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Label - The scale of the text changed.\n//\nint EZ_label_OnTextScaleChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Label - Happens when the control has resized.\n//\nint EZ_label_OnResize(ez_control_t *self, void *ext_event_info);\n\n//\n// Label - On Draw.\n//\nint EZ_label_OnDraw(ez_control_t *label, void *ext_event_info);\n\n//\n// Label - The caret was moved.\n//\nint EZ_label_OnCaretMoved(ez_control_t *self, void *ext_event_info);\n\n//\n// Label - The text changed in the label.\n//\nint EZ_label_OnTextChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Label - Key down event.\n//\nint EZ_label_OnKeyDown(ez_control_t *self, int key, int unichar);\n\n//\n// Label - Key up event.\n//\nint EZ_label_OnKeyUp(ez_control_t *self, int key, int unichar);\n\n//\n// Label - Key event.\n//\nint EZ_label_OnKeyEvent(ez_control_t *self, int key, int unichar, qbool down);\n\n//\n// Label - Handles the mouse down event.\n//\nint EZ_label_OnMouseDown(ez_control_t *self, mouse_state_t *ms);\n\n//\n// Label - Handles the mouse up event.\n//\nint EZ_label_OnMouseUp(ez_control_t *self, mouse_state_t *ms);\n\n//\n// Label - The mouse is hovering within the bounds of the label.\n//\nint EZ_label_OnMouseHover(ez_control_t *self, mouse_state_t *mouse_state);\n\n#endif // __EZ_LABEL_H__\n\n"
  },
  {
    "path": "src/ez_listview.c",
    "content": "\r\n/*\r\nCopyright (C) 2007 ezQuake team\r\n\r\nThis program is free software; you can redistribute it and/or\r\nmodify it under the terms of the GNU General Public License\r\nas published by the Free Software Foundation; either version 2\r\nof the License, or (at your option) any later version.\r\n\r\nThis program is distributed in the hope that it will be useful,\r\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\r\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\r\n\r\nSee the GNU General Public License for more details.\r\n\r\nYou should have received a copy of the GNU General Public License\r\nalong with this program; if not, write to the Free Software\r\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\r\n\r\n$Id: ez_listview.c,v 1.78 2007/10/27 14:51:15 cokeman1982 Exp $\r\n*/\r\n\r\n#include \"quakedef.h\"\r\n#include \"keys.h\"\r\n#include \"utils.h\"\r\n#include \"common_draw.h\"\r\n#include \"ez_listview.h\"\r\n\r\n#ifdef _WIN32\r\n#pragma warning( disable : 4189 )\r\n#endif\r\n\r\n//\r\n// Listview - Lays out the control.\r\n//\r\nstatic void EZ_listview_Layout(ez_listview_t *self)\r\n{\r\n\tez_dllist_node_t *it = (self->sort_ascending ? self->items.head : self->items.tail);\r\n\tez_control_t *lvi = NULL;\r\n\tint y = 0;\r\n\r\n\t// Make sure the header hasn't moved.\r\n\tEZ_control_SetPosition((ez_control_t *)self->header, 0, 0);\r\n\ty += ((ez_control_t *)self->header)->height + self->row_gap;\r\n\r\n\t// Position all the listview items according to the sort order.\r\n\tif (self->items.count > 0)\r\n\t{\r\n\t\twhile ((self->sort_ascending ? it->next : it->previous))\r\n\t\t{\r\n\t\t\tlvi = (ez_control_t *)it->payload;\r\n\t\t\t\r\n\t\t\tEZ_control_SetPosition(lvi, 0, y);\r\n\t\t\ty += lvi->height + self->row_gap;\r\n\r\n\t\t\tit = (self->sort_ascending ? it->next : it->previous);\r\n\t\t}\r\n\t}\r\n}\r\n\r\n//\r\n// Listview - On Mouse Click event handler for when a header label is clicked.\r\n//\r\nstatic int EZ_listview_OnHeaderMouseClick(ez_control_t *self, void *payload, mouse_state_t *ms)\r\n{\r\n\tint column = (int)(uintptr_t)payload;\r\n\tez_listview_t *listview = (ez_listview_t *)self;\r\n\r\n\t// Change the sorting.\r\n\tlistview->sort_ascending = !listview->sort_ascending;\r\n\tEZ_listview_SetSortColumn(listview, column);\r\n\r\n\t// Sort!\r\n\tEZ_listview_SortByColumn(listview);\r\n\r\n\tEZ_listview_Layout(listview);\r\n\r\n\treturn 0;\r\n}\r\n\r\n//\r\n// Listview - Creates a new listview and initializes it.\r\n//\r\nez_listview_t *EZ_listview_Create(ez_tree_t *tree, ez_control_t *parent,\r\n\t\t\t\t\t\t\t  char *name, char *description,\r\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\r\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\r\n{\r\n\tez_listview_t *listview = NULL;\r\n\r\n\t// We have to have a tree to add the control to.\r\n\tif (!tree)\r\n\t{\r\n\t\treturn NULL;\r\n\t}\r\n\r\n\tlistview = (ez_listview_t *)Q_malloc(sizeof(ez_listview_t));\r\n\tmemset(listview, 0, sizeof(ez_listview_t));\r\n\r\n\tEZ_listview_Init(listview, tree, parent, name, description, x, y, width, height, flags);\r\n\t\r\n\treturn listview;\r\n}\r\n\r\n//\r\n// Listview - Initializes a listview.\r\n//\r\nvoid EZ_listview_Init(ez_listview_t *listview, ez_tree_t *tree, ez_control_t *parent,\r\n\t\t\t\t\t\t\t  char *name, char *description,\r\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\r\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\r\n{\r\n\tint i;\r\n\tez_listview_subitem_t subitem;\r\n\tez_control_t *listview_ctrl\t= (ez_control_t *)listview;\r\n\r\n\t// Initialize the inherited class first.\r\n\t//EZ_scrollpane_Init(&listview->super, tree, parent, name, description, x, y, width, height, flags);\r\n\tEZ_control_Init(&listview->super, tree, parent, name, description, x, y, width, height, flags);\r\n\r\n\t((ez_control_t *)listview)->CLASS_ID\t\t= EZ_LISTVIEW_ID;\r\n\t((ez_control_t *)listview)->ext_flags\t\t|= (flags | control_focusable | control_contained | control_resizeable);\r\n\r\n\t// Overridden events.\r\n\t//CONTROL_REGISTER_EVENT(listview_ctrl, EZ_listview_OnResize, OnResize, ez_control_t);\r\n\r\n\t// Listview specific events.\r\n\tCONTROL_REGISTER_EVENT(listview, EZ_listview_OnItemAdded, OnItemAdded, ez_listview_t);\r\n\tCONTROL_REGISTER_EVENT(listview, EZ_listview_OnRowGapChanged, OnRowGapChanged, ez_listview_t);\r\n\tCONTROL_REGISTER_EVENT(listview, EZ_listview_OnColumnGapChanged, OnColumnGapChanged, ez_listview_t);\r\n\tCONTROL_REGISTER_EVENT(listview, EZ_listview_OnColumnWidthChanged, OnColumnWidthChanged, ez_listview_t);\r\n\tCONTROL_REGISTER_EVENT(listview, EZ_listview_OnRowHeightChanged, OnRowHeightChanged, ez_listview_t);\r\n\r\n\t// Create the header listviewitem and add subitems to it and set their text to \"\" to start with.\r\n\t{\r\n\t\tez_label_t *curlabel = NULL;\r\n\t\tlistview->header = EZ_listviewitem_Create(listview_ctrl->control_tree, listview_ctrl, \"List view header\", \"\", \r\n\t\t\t\t\t\t\t\t\t0, 0, listview_ctrl->width, 8, 0);\r\n\r\n\t\tsubitem.payload = NULL;\r\n\t\tsubitem.text = \"HEJ\";\r\n\r\n\t\t// Init the labels in the header list view item.\r\n\t\tfor (i = 0; i < LISTVIEW_COLUMN_COUNT; i++)\r\n\t\t{\r\n\t\t\tEZ_listviewitem_AddColumn(listview->header, subitem, 30);\r\n\t\t\tcurlabel = listview->header->items[i];\r\n\r\n\t\t\t// Pass the column index as payload (so we know what column to sort by when a header is clicked).\r\n\t\t\tEZ_control_AddOnMouseClick((ez_control_t *)curlabel, (void *)(uintptr_t)i, EZ_listview_OnHeaderMouseClick);\r\n\t\t\t\r\n\t\t\tEZ_control_SetAnchor((ez_control_t *)curlabel, anchor_left | anchor_top);\r\n\t\t\tEZ_label_SetReadOnly(curlabel, true);\r\n\t\t\tEZ_label_SetAutoSize(curlabel, false);\r\n\t\t\tEZ_label_SetTextSelectable(curlabel, false);\r\n\t\t\tEZ_control_SetBackgroundColor((ez_control_t *)curlabel, 0, 255, 0, 125); // TODO: Remove this.\r\n\t\t}\r\n\r\n\t\t// TODO: Add ez_control_t's that will act as resize handles between the column headers.\r\n\t}\r\n\r\n\tEZ_listview_Layout(listview);\r\n}\r\n\r\n//\r\n// Listview - Cleans up a listview item when it is removed in a range.\r\n//\r\nstatic void EZ_listview_CleanupRangeItem(void *payload)\r\n{\r\n\tif (payload != NULL)\r\n\t{\r\n\t\tez_listviewitem_t *item = (ez_listviewitem_t *)payload;\r\n\t\tEZ_listviewitem_Destroy((ez_control_t *)item, true);\r\n\t}\r\n}\r\n\r\n//\r\n// Listview - Destroys a listview.\r\n//\r\nint EZ_listview_Destroy(ez_control_t *self, qbool destroy_children)\r\n{\r\n\tez_listview_t *listview = (ez_listview_t *)self;\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnDestroy, destroy_children); \r\n\r\n\t// TODO : Remove any event handlers.\r\n\r\n\t// Cleanup listview items.\r\n\tEZ_double_linked_list_RemoveRange(&listview->items, 0, listview->items.count - 1, EZ_listview_CleanupRangeItem);\r\n\t\r\n\tEZ_control_Destroy(self, destroy_children);\r\n\r\n\treturn 0;\r\n}\r\n\r\n//\r\n// Listview - Adds a listview item to the listview. Expects an array of column items as argument (and a count of how many).\r\n//\r\nvoid EZ_listview_AddItem(ez_listview_t *self, const ez_listview_subitem_t *sub_items, int subitem_count, void *payload)\r\n{\r\n\tint i;\r\n\tez_control_t *ctrl_lstview = (ez_control_t *)self;\r\n\tez_listviewitem_t *item;\r\n\t\r\n\titem = EZ_listviewitem_Create(ctrl_lstview->control_tree, ctrl_lstview, \"List view item\", \"\", \r\n\t\t\t\t\t\t\t\t0, 0, ctrl_lstview->width, self->row_height, 0);\r\n\r\n\tfor (i = 0; i < subitem_count; i++)\r\n\t{\r\n\t\tEZ_listviewitem_AddColumn(item, sub_items[i], 30); // TODO: Hmm what default width should we use?\r\n\t}\r\n\r\n\tEZ_double_linked_list_Add(&self->items, (void *)item);\r\n\r\n\tCONTROL_RAISE_EVENT(NULL, self, ez_listview_t, OnItemAdded, NULL)\r\n}\r\n\r\n//\r\n// Listview - An item was added to the listview.\r\n//\r\nint EZ_listview_OnItemAdded(ez_control_t *self, void *ext_event_info)\r\n{\r\n\tez_listview_t *listview = (ez_listview_t *)self;\r\n\r\n\t// TODO: Maybe we should allow you to supress layout while adding a bunch of items.\r\n\tEZ_listview_Layout(listview);\r\n\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, listview, ez_listview_t, OnItemAdded, NULL);\r\n\r\n\treturn 0;\r\n}\r\n\r\n//\r\n// Listview - Set the text of the header.\r\n//\r\nvoid EZ_listview_SetHeaderText(ez_listview_t *self, int column, const char *text)\r\n{\r\n\tif (column < 0 || column >= LISTVIEW_COLUMN_COUNT)\r\n\t{\r\n\t\treturn;\r\n\t}\r\n\r\n\tEZ_label_SetText(self->header->items[column], text);\r\n}\r\n\r\n//\r\n// Listview - Removes an item at a specific index.\r\n//\r\nvoid EZ_listview_RemoveItemByIndex(ez_listview_t *self, int index)\r\n{\r\n\tez_listviewitem_t *item = EZ_double_linked_list_RemoveByIndex(&self->items, index);\r\n\t\r\n\tif (item != NULL)\r\n\t{\r\n\t\tEZ_listviewitem_Destroy((ez_control_t *)item, true);\r\n\t}\r\n}\r\n\r\n//\r\n// Listview - Removes an item with a specific payload.\r\n//\r\nvoid EZ_listview_RemoveItemByPayload(ez_listview_t *self, void *payload)\r\n{\r\n\tez_listviewitem_t *item = EZ_double_linked_list_RemoveByPayload(&self->items, payload);\r\n\t\r\n\tif (item != NULL)\r\n\t{\r\n\t\tEZ_listviewitem_Destroy((ez_control_t *)item, true);\r\n\t}\r\n}\r\n\r\n//\r\n// Listview - Removes a range of items from a listview.\r\n//\r\nvoid EZ_listview_RemoveRange(ez_listview_t *self, int start, int end)\r\n{\r\n\tEZ_double_linked_list_RemoveRange(&self->items, start, end, EZ_listview_CleanupRangeItem);\r\n}\r\n\r\n//\r\n// Listview - Compares two columns to each other (based on the sort column set in the listview object they are related with).\r\n//\r\nstatic int EZ_listview_ColumnCompareFunc(const void *it1, const void *it2)\r\n{\r\n\tint sci;\r\n\tint res = 0;\r\n\tez_listviewitem_t *lvi1 = GET_LISTVIEW_ITEM(it1);\r\n\tez_listviewitem_t *lvi2 = GET_LISTVIEW_ITEM(it2);\r\n\tez_listview_t *lv = (ez_listview_t *)lvi1->super.parent; // We assume the item has a listview as parent, otherwise something is wrong.\r\n\r\n\tsci = lv->sort_column_index;\r\n\r\n\tif (sci < 0 || sci >= LISTVIEW_COLUMN_COUNT)\r\n\t{\r\n\t\treturn 0;\r\n\t}\r\n\r\n\tres = strcmp(lvi1->items[sci]->text, lvi2->items[sci]->text);\r\n\r\n\treturn res; // (lv->sort_ascending) ? res : -res; // HMM! Nevermind this, since we're using a double linked list to store the listview items we can just layout them from tail -> head instead of doing a sort :)\r\n}\r\n\r\n//\r\n// Listview - Sorts the list view by a user supplied function.\r\n//\r\nvoid EZ_listview_SortByUserFunc(ez_listview_t *self, PtFuncCompare compare_function)\r\n{\r\n\tEZ_double_linked_list_Sort(&self->items, EZ_listview_ColumnCompareFunc);\r\n}\r\n\r\n//\r\n// Listview - Sorts the listview items by a specified column.\r\n//\r\nvoid EZ_listview_SortByColumn(ez_listview_t *self)\r\n{\r\n\tEZ_listview_SortByUserFunc(self, EZ_listview_ColumnCompareFunc);\r\n}\r\n\r\n//\r\n// Listview - Set the column to sort by.\r\n//\r\nvoid EZ_listview_SetSortColumn(ez_listview_t *self, int column)\r\n{\r\n\tself->sort_column_index = column;\r\n}\r\n\r\n/*\r\n//\r\n// Listview - OnResize event handler.\r\n//\r\nint EZ_listview_OnResize(ez_control_t *self, void *ext_event_info)\r\n{\r\n\tez_listview_t *listview = (ez_listview_t *)self;\r\n\r\n\t// Run the super class implementation.\r\n\tEZ_scrollpane_OnResize(self, ext_event_info);\r\n\r\n\t//EZ_listview_Layout(listview);\r\n\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnResize, NULL);\r\n\r\n\treturn 0;\r\n}\r\n*/\r\n\r\n//\r\n// Listview - Sets the row gap size of the listview.\r\n//\r\nvoid EZ_listview_SetRowGap(ez_listview_t *self, int gap)\r\n{\r\n\tself->row_gap = gap;\r\n\tEZ_listview_Layout(self);\r\n\r\n\tCONTROL_RAISE_EVENT(NULL, self, ez_listview_t, OnRowGapChanged, NULL);\r\n}\r\n\r\n//\r\n// Listview - The row gap size has changed.\r\n//\r\nint EZ_listview_OnRowGapChanged(ez_control_t *self, void *ext_event_info)\r\n{\r\n\tez_listview_t *listview = (ez_listview_t *)self;\r\n\r\n\tEZ_listview_Layout(listview);\r\n\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, listview, ez_listview_t, OnRowGapChanged, ext_event_info);\r\n\r\n\treturn 0;\r\n}\r\n\r\n//\r\n// Listview - Sets the column gap size of the listview.\r\n//\r\nvoid EZ_listview_SetColumnGap(ez_listview_t *self, int gap)\r\n{\r\n\tself->col_gap = gap;\r\n\tCONTROL_RAISE_EVENT(NULL, self, ez_listview_t, OnRowGapChanged, NULL);\r\n}\r\n\r\n//\r\n// Listview - The column gap size has changed.\r\n//\r\nint EZ_listview_OnColumnGapChanged(ez_control_t *self, void *ext_event_info)\r\n{\r\n\tez_listview_t *listview = (ez_listview_t *)self;\r\n\tez_dllist_node_t *it = listview->items.head;\r\n\tez_listviewitem_t *lvi = NULL;\r\n\r\n\t// Update all listview items to know about the new gap size.\r\n\tif (listview->items.count > 0)\r\n\t{\r\n\t\twhile (it->next)\r\n\t\t{\r\n\t\t\tlvi = (ez_listviewitem_t *)it->payload;\r\n\r\n\t\t\tEZ_listviewitem_SetColumnGap(lvi, listview->col_gap);\r\n\r\n\t\t\tit = it->next;\r\n\t\t}\r\n\t}\r\n\r\n\tEZ_listview_Layout(listview);\r\n\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, listview, ez_listview_t, OnColumnGapChanged, ext_event_info);\r\n\r\n\treturn 0;\r\n}\r\n\r\n//\r\n// Listview - Set the width of a column.\r\n//\r\nvoid EZ_listview_SetColumnWidth(ez_listview_t *self, int column, int width)\r\n{\r\n\tez_listview_t *listview = (ez_listview_t *)self;\r\n\r\n\tif (column < 0 || column >= LISTVIEW_COLUMN_COUNT)\r\n\t\treturn;\r\n\r\n\tself->col_widths[column] = width;\r\n\r\n\tCONTROL_RAISE_EVENT(NULL, listview, ez_listview_t, OnColumnWidthChanged, (void *)(uintptr_t)column);\r\n}\r\n\r\n//\r\n// Listview - The width of a column has changed.\r\n//\r\nint EZ_listview_OnColumnWidthChanged(ez_control_t *self, void *ext_event_info)\r\n{\r\n\tez_listview_t *listview = (ez_listview_t *)self;\r\n\tez_dllist_node_t *it = listview->items.head;\r\n\tez_listviewitem_t *lvi = NULL;\r\n\tint column = (int)(uintptr_t)ext_event_info;\r\n\r\n\tif (listview->items.count > 0)\r\n\t{\r\n\t\twhile (it->next)\r\n\t\t{\r\n\t\t\tlvi = (ez_listviewitem_t *)it->payload;\r\n\t\t\tEZ_listviewitem_SetColumnWidth(lvi, column, listview->col_widths[column]);\r\n\t\t\tit = it->next;\r\n\t\t}\r\n\t}\r\n\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, listview, ez_listview_t, OnColumnWidthChanged, ext_event_info);\r\n\treturn 0;\r\n}\r\n\r\n//\r\n// Listview - Set the width of a column.\r\n//\r\nvoid EZ_listview_SetRowHeight(ez_listview_t *self, int row_height)\r\n{\r\n\tself->row_height = row_height;\r\n\r\n\tCONTROL_RAISE_EVENT(NULL, self, ez_listview_t, OnRowHeightChanged, NULL);\r\n}\r\n\r\n//\r\n// Listview - The height of the rows has changed.\r\n//\r\nint EZ_listview_OnRowHeightChanged(ez_control_t *self, void *ext_event_info)\r\n{\r\n\tez_listview_t *listview = (ez_listview_t *)self;\r\n\tez_dllist_node_t *it = listview->items.head;\r\n\tez_control_t *lvi = NULL;\r\n\r\n\tif (listview->items.count > 0)\r\n\t{\r\n\t\twhile (it->next)\r\n\t\t{\r\n\t\t\tlvi = (ez_control_t *)it->payload;\r\n\t\t\tEZ_control_SetSize(lvi, lvi->width, lvi->height);\r\n\t\t\tit = it->next;\r\n\t\t}\r\n\t}\r\n\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, listview, ez_listview_t, OnRowHeightChanged, ext_event_info);\r\n\treturn 0;\r\n}\r\n\r\n\r\n"
  },
  {
    "path": "src/ez_listview.h",
    "content": "\r\n#ifndef __EZ_LISTVIEW_H__\r\n#define __EZ_LISTVIEW_H__\r\n/*\r\nCopyright (C) 2007 ezQuake team\r\n\r\nThis program is free software; you can redistribute it and/or\r\nmodify it under the terms of the GNU General Public License\r\nas published by the Free Software Foundation; either version 2\r\nof the License, or (at your option) any later version.\r\n\r\nThis program is distributed in the hope that it will be useful,\r\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\r\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\r\n\r\nSee the GNU General Public License for more details.\r\n\r\nYou should have received a copy of the GNU General Public License\r\nalong with this program; if not, write to the Free Software\r\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\r\n\r\n$Id: ez_listviewitem.h,v 1.55 2007-10-27 14:51:15 cokeman1982 Exp $\r\n*/\r\n\r\n#include \"ez_controls.h\"\r\n#include \"ez_scrollpane.h\"\r\n#include \"ez_listviewitem.h\"\r\n\r\n// =========================================================================================\r\n// Listview\r\n// =========================================================================================\r\n\r\n//\r\n// Information associated with the change of an item column change.\r\n//\r\ntypedef struct ez_listview_changeinfo_s\r\n{\r\n\tez_listview_subitem_t *item;\t\t// Contains the info about the new text of the listview column that was changed, and its user associated payload.\r\n\tint row;\t\t\t\t\t\t\t// The row of the listview that this item is on.\r\n\tint column;\t\t\t\t\t\t\t// The column of the listview that this item is on.\r\n\tvoid *payload;\t\t\t\t\t\t// The user associated payload associated with this listview item (the entire row, not just the column).\r\n} ez_listview_changeinfo_t;\r\n\r\ntypedef struct ez_listview_eventcount_s\r\n{\r\n\tint OnItemAdded;\r\n\tint OnRowGapChanged;\r\n\tint OnColumnGapChanged;\r\n\tint OnColumnWidthChanged;\r\n\tint OnRowHeightChanged;\r\n} ez_listview_eventcount_t;\r\n\r\ntypedef struct ez_listview_events_s\r\n{\r\n\tez_event_fp OnItemAdded;\t\t\t// When an item is added to the listview.\r\n\tez_event_fp OnRowGapChanged;\t\t// The row gap has changed.\r\n\tez_event_fp OnColumnGapChanged;\t\t// The column gap has changed.\r\n\tez_event_fp OnColumnWidthChanged;\t// The width of a column has changed.\r\n\tez_event_fp OnRowHeightChanged;\t\t// The height of the rows has been changed.\r\n} ez_listview_events_t;\r\n\r\ntypedef struct ez_listview_eventhandlers_s\r\n{\r\n\tez_eventhandler_t\t*OnItemAdded;\r\n\tez_eventhandler_t\t*OnRowGapChanged;\r\n\tez_eventhandler_t\t*OnColumnGapChanged;\r\n\tez_eventhandler_t\t*OnColumnWidthChanged;\r\n\tez_eventhandler_t\t*OnRowHeightChanged;\r\n} ez_listview_eventhandlers_t;\r\n\r\ntypedef struct ez_listview_s\r\n{\r\n\tez_control_t\t\t\tsuper;\r\n\t//ez_scrollpane_t\t\t\tsuper;\t\t\t\t// The super class\r\n\t\t\t\t\t\t\t\t\t\t\t\t// (we inherit a scrollpane instead of a normal control so we get scrolling also)\r\n\r\n\tez_listview_events_t\tevents;\t\t\t\t// Specific events for the listview control.\r\n\tez_listview_eventhandlers_t event_handlers;\t// Specific event handlers for the listview control.\r\n\tez_listview_eventcount_t inherit_levels;\r\n\tez_listview_eventcount_t override_counts;\r\n\r\n\tez_listviewitem_t\t\t*header;\t\t\t// The header row control of the listview.\r\n\tez_double_linked_list_t\titems;\t\t\t\t// The listview items.\r\n\tint\t\t\t\t\t\trow_height;\t\t\t// The height of the listview rows.\r\n\tint\t\t\t\t\t\tcol_widths[LISTVIEW_COLUMN_COUNT];\t// The widths of the  items.\r\n\tint\t\t\t\t\t\tcol_gap;\t\t\t// The gap to use between columns.\r\n\tint\t\t\t\t\t\trow_gap;\t\t\t// The gap to use between rows.\r\n\r\n\tint\t\t\t\t\t\tsort_column_index;\t// The index of the column that we should sort by.\r\n\tqbool\t\t\t\t\tsort_ascending;\t\t// Sort ascending or descending when sorting by column?\r\n\r\n\tint\t\t\t\t\t\toverride_count;\t\t// These are needed so that subclasses can override listview specific events.\r\n\tint\t\t\t\t\t\tinheritance_level;\r\n} ez_listview_t;\r\n\r\n//\r\n// Listview - Creates a new listview and initializes it.\r\n//\r\nez_listview_t *EZ_listview_Create(ez_tree_t *tree, ez_control_t *parent,\r\n\t\t\t\t\t\t\t  char *name, char *description,\r\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\r\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\r\n\r\n//\r\n// Listview - Initializes a listview.\r\n//\r\nvoid EZ_listview_Init(ez_listview_t *listview, ez_tree_t *tree, ez_control_t *parent,\r\n\t\t\t\t\t\t\t  char *name, char *description,\r\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\r\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\r\n\r\n//\r\n// Listview - Destroys a listview.\r\n//\r\nint EZ_listview_Destroy(ez_control_t *self, qbool destroy_children);\r\n\r\n//\r\n// Listview - Adds a listview item to the listview. Expects an array of column items as argument (and a count of how many).\r\n//\r\nvoid EZ_listview_AddItem(ez_listview_t *self, const ez_listview_subitem_t *sub_items, int subitem_count, void *payload);\r\n\r\n//\r\n// Listview - Removes an item at a specific index.\r\n//\r\nvoid EZ_listview_RemoveItemByIndex(ez_listview_t *self, int index);\r\n\r\n//\r\n// Listview - Removes an item with a specific payload.\r\n//\r\nvoid EZ_listview_RemoveItemByPayload(ez_listview_t *self, void *payload);\r\n\r\n//\r\n// Listview - Removes a range of items from a listview.\r\n//\r\nvoid EZ_listview_RemoveRange(ez_listview_t *self, int start, int end);\r\n\r\n// \r\n// Convenience macro for getting the listview items from a void pointer in comparison functions.\r\n//\r\n#define GET_LISTVIEW_ITEM(lnode) ((ez_listviewitem_t *)( (ez_dllist_node_t *)(lnode))->payload)\r\n\r\n//\r\n// Listview - Sorts the list view by a user supplied function. (The listview items will have the listview as a parent).\r\n//\r\nvoid EZ_listview_SortByUserFunc(ez_listview_t *self, PtFuncCompare compare_function);\r\n\r\n//\r\n// Listview - Sorts the listview items by a specified column.\r\n//\r\nvoid EZ_listview_SortByColumn(ez_listview_t *self);\r\n\r\n//\r\n// Listview - Set the column to sort by.\r\n//\r\nvoid EZ_listview_SetSortColumn(ez_listview_t *self, int column);\r\n\r\n//\r\n// Listview - Set the text of the header.\r\n//\r\nvoid EZ_listview_SetHeaderText(ez_listview_t *self, int column, const char *text);\r\n\r\n//\r\n// Listview - On Resize event handler.\r\n//\r\nint EZ_listview_OnResize(ez_control_t *self, void *ext_event_info);\r\n\r\n//\r\n// Listview - An item was added to the listview.\r\n//\r\nint EZ_listview_OnItemAdded(ez_control_t *self, void *ext_event_info);\r\n\r\n//\r\n// Listview - Sets the column gap size of the listview.\r\n//\r\nvoid EZ_listview_SetColumnGap(ez_listview_t *self, int gap);\r\n\r\n//\r\n// Listview - Sets the row gap size of the listview.\r\n//\r\nvoid EZ_listview_SetRowGap(ez_listview_t *self, int gap);\r\n\r\n//\r\n// Listview - The column gap size has changed.\r\n//\r\nint EZ_listview_OnColumnGapChanged(ez_control_t *self, void *ext_event_info);\r\n\r\n//\r\n// Listview - The row gap size has changed.\r\n//\r\nint EZ_listview_OnRowGapChanged(ez_control_t *self, void *ext_event_info);\r\n\r\n//\r\n// Listview - Set the width of a column.\r\n//\r\nvoid EZ_listview_SetColumnWidth(ez_listview_t *self, int column, int width);\r\n\r\n//\r\n// Listview - The width of a column has changed.\r\n//\r\nint EZ_listview_OnColumnWidthChanged(ez_control_t *self, void *ext_event_info);\r\n\r\n//\r\n// Listview - Set the width of a column.\r\n//\r\nvoid EZ_listview_SetRowHeight(ez_listview_t *self, int row_height);\r\n\r\n//\r\n// Listview - The height of the rows has changed.\r\n//\r\nint EZ_listview_OnRowHeightChanged(ez_control_t *self, void *ext_event_info);\r\n\r\n#endif // __EZ_LISTVIEW_H__\r\n\r\n"
  },
  {
    "path": "src/ez_listviewitem.c",
    "content": "\r\n/*\r\nCopyright (C) 2008 ezQuake team\r\n\r\nThis program is free software; you can redistribute it and/or\r\nmodify it under the terms of the GNU General Public License\r\nas published by the Free Software Foundation; either version 2\r\nof the License, or (at your option) any later version.\r\n\r\nThis program is distributed in the hope that it will be useful,\r\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\r\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\r\n\r\nSee the GNU General Public License for more details.\r\n\r\nYou should have received a copy of the GNU General Public License\r\nalong with this program; if not, write to the Free Software\r\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\r\n\r\n$Id: ez_listviewitem.c,v 1.78 2007/10/27 14:51:15 cokeman1982 Exp $\r\n*/\r\n\r\n#include \"quakedef.h\"\r\n#include \"keys.h\"\r\n#include \"utils.h\"\r\n#include \"common_draw.h\"\r\n#include \"ez_listviewitem.h\"\r\n\r\n#define VALID_COLUMN(column) ((column >= 0) && (column < self->item_count))\r\n\r\n//\r\n// Listview item - Creates a new listview item and initializes it.\r\n//\r\nez_listviewitem_t *EZ_listviewitem_Create(ez_tree_t *tree, ez_control_t *parent,\r\n\t\t\t\t\t\t\t  char *name, char *description,\r\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\r\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\r\n{\r\n\tez_listviewitem_t *listviewitem = NULL;\r\n\r\n\t// We have to have a tree to add the control to.\r\n\tif (!tree)\r\n\t{\r\n\t\treturn NULL;\r\n\t}\r\n\r\n\tlistviewitem = (ez_listviewitem_t *)Q_malloc(sizeof(ez_listviewitem_t));\r\n\tmemset(listviewitem, 0, sizeof(ez_listviewitem_t));\r\n\r\n\tEZ_listviewitem_Init(listviewitem, tree, parent, name, description, x, y, width, height, flags);\r\n\t\r\n\treturn listviewitem;\r\n}\r\n\r\n//\r\n// Listview item - Initializes a listview item.\r\n//\r\nvoid EZ_listviewitem_Init(ez_listviewitem_t *listviewitem, ez_tree_t *tree, ez_control_t *parent,\r\n\t\t\t\t\t\t\t  char *name, char *description,\r\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\r\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\r\n{\r\n\tint i;\r\n\r\n\t// Initialize the inherited class first.\r\n\tEZ_control_Init(&listviewitem->super, tree, parent, name, description, x, y, width, height, flags);\r\n\r\n\t((ez_control_t *)listviewitem)->CLASS_ID\t\t= EZ_LISTVIEWITEM_ID;\r\n\t((ez_control_t *)listviewitem)->ext_flags\t\t|= (flags | control_focusable | control_contained | control_resizeable);\r\n\r\n\t// Make all columns visible by default :)\r\n\tfor (i = 0; i < LISTVIEW_COLUMN_COUNT; i++)\r\n\t{\r\n\t\tlistviewitem->item_visibile[i] = true;\r\n\t}\r\n\r\n\t// Listview item specific events.\r\n\tCONTROL_REGISTER_EVENT(listviewitem, EZ_listviewitem_OnColumnAdded, OnColumnAdded, ez_listviewitem_t);\r\n\tCONTROL_REGISTER_EVENT(listviewitem, EZ_listviewitem_OnColumnVisibilityChanged, OnColumnVisibilityChanged, ez_listviewitem_t);\r\n\tCONTROL_REGISTER_EVENT(listviewitem, EZ_listviewitem_OnColumnGapChanged, OnColumnGapChanged, ez_listviewitem_t);\r\n\tCONTROL_REGISTER_EVENT(listviewitem, EZ_listviewitem_OnColumnWidthChanged, OnColumnWidthChanged, ez_listviewitem_t);\r\n\t\r\n\t//CONTROL_REGISTER_EVENT(listviewitem, EZ_listviewitem_OnSubItemChanged, OnSubItemChanged, ez_listviewitem_t);\r\n}\r\n\r\n//\r\n// Listview item - Destroys a listview item.\r\n//\r\nint EZ_listviewitem_Destroy(ez_control_t *self, qbool destroy_children)\r\n{\r\n\tint i;\r\n\tez_listviewitem_t *listviewitem = (ez_listviewitem_t *)self;\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnDestroy, destroy_children); \r\n\r\n\t// TODO : Remove any event handlers.\r\n\tEZ_eventhandler_Remove(listviewitem->event_handlers.OnColumnAdded, NULL, true);\r\n\tEZ_eventhandler_Remove(listviewitem->event_handlers.OnColumnVisibilityChanged, NULL, true);\r\n\t//EZ_eventhandler_Remove(listviewitem->event_handlers.OnSubItemChanged, NULL, true);\r\n\r\n\t// Cleanup columns.\r\n\tfor (i = 0; i < listviewitem->item_count; i++)\r\n\t{\r\n\t\t// TODO: Hmm should we raise it like this?\r\n\t\tCONTROL_RAISE_EVENT(NULL, listviewitem->items[i], ez_control_t, OnDestroy, true);\r\n\t}\r\n\r\n\tEZ_control_Destroy((ez_control_t *)self, destroy_children);\r\n\r\n\treturn 0;\r\n}\r\n\r\n//\r\n// Listview item - Adds a column to the listview item.\r\n//\r\nvoid EZ_listviewitem_AddColumn(ez_listviewitem_t *self, ez_listview_subitem_t data, int width)\r\n{\r\n\tint column = 0;\r\n\tez_label_t *label = NULL;\r\n\tez_control_t *ctrl = (ez_control_t *)self;\r\n\r\n\tif (self->item_count >= sizeof(self->items))\r\n\t{\r\n\t\tSys_Error(\"EZ_listviewitem_AddColumn: Max allowed columns %i exceeded.\\n\", sizeof(self->items));\r\n\t}\r\n\r\n\t// Create a new label to use as the column.\r\n\tlabel = EZ_label_Create(ctrl->control_tree, ctrl, \"Listview item column\", \"\", 0, 0, width, 5, 0, 0, data.text);\r\n\tEZ_control_SetPayload(ctrl, data.payload);\r\n\tEZ_control_SetAnchor(ctrl, anchor_left | anchor_top | anchor_bottom);\r\n\r\n\t// Add the item to the columns.\r\n\tself->items[self->item_count] = label;\r\n\tcolumn = self->item_count;\r\n\tself->item_count++;\r\n\r\n\tCONTROL_RAISE_EVENT(NULL, ctrl, ez_listviewitem_t, OnColumnAdded, (void *)(uintptr_t)column);\r\n}\r\n\r\n//\r\n// Listview item - Event for when a new column was added to the listview item.\r\n//\r\nint EZ_listviewitem_OnColumnAdded(ez_control_t *self, void *column)\r\n{\r\n\tez_listviewitem_t *lvi = (ez_listviewitem_t *)self;\r\n\tEZ_listviewitem_LayoutControl(lvi);\r\n\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, lvi, ez_listviewitem_t, OnColumnAdded, column);\r\n\t// TODO: Layout the control again if some mischevaous event handler changed the size/position of the columns? :D\r\n\r\n\treturn 0;\r\n}\r\n\r\n//\r\n// Listview item - Lays out the control.\r\n//\r\nvoid EZ_listviewitem_LayoutControl(ez_listviewitem_t *self)\r\n{\r\n\tint i;\r\n\tint x = 0;\r\n\tez_control_t *ctrl\t\t= (ez_control_t *)self;\r\n\tez_control_t *lblctrl\t= NULL;\r\n\r\n\tfor (i = 0; i < self->item_count; i++)\r\n\t{\r\n\t\tif (!self->item_visibile[i])\r\n\t\t\tcontinue;\r\n\r\n\t\tlblctrl = (ez_control_t *)self->items[i];\r\n\r\n\t\t// Space out the labels in columns.\r\n\t\tEZ_control_SetPosition(lblctrl, x, 0);\r\n\t\tx += lblctrl->width + self->item_gap;\r\n\r\n\t\t// Make sure we reset the height if some naughty person changed it since last time.\r\n\t\tEZ_control_SetSize(lblctrl, lblctrl->width, ctrl->height);\r\n\t}\r\n}\r\n\r\n//\r\n// Listview item - Gets a column from the listview item.\r\n//\r\nez_label_t *EZ_listviewitem_GetColumn(ez_listviewitem_t *self, int column)\r\n{\r\n\treturn VALID_COLUMN(column) ? self->items[column] : NULL;\r\n}\r\n\r\n//\r\n// Listview item - Sets if a column should be visible or not.\r\n//\r\nvoid EZ_listviewitem_SetColumnVisible(ez_listviewitem_t *self, int column, qbool visible)\r\n{\r\n\tif (!VALID_COLUMN(column))\r\n\t\treturn;\r\n\r\n\tself->item_visibile[column] = visible;\r\n\tEZ_control_SetVisible((ez_control_t *)self->items[column], visible);\r\n\r\n\tCONTROL_RAISE_EVENT(NULL, self, ez_listviewitem_t, OnColumnVisibilityChanged, (void *)(uintptr_t)column);\r\n}\r\n\r\n//\r\n// Listview item - The visibility changed for a column.\r\n//\r\nint EZ_listviewitem_OnColumnVisibilityChanged(ez_control_t *self, void *column)\r\n{\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_listviewitem_t, OnColumnVisibilityChanged, column);\r\n\treturn 0;\r\n}\r\n\r\n//\r\n// Listview item - Sets the gap between columns.\r\n//\r\nvoid EZ_listviewitem_SetColumnGap(ez_listviewitem_t *self, int gap)\r\n{\r\n\tself->item_gap = gap;\r\n\r\n\tCONTROL_RAISE_EVENT(NULL, self, ez_listviewitem_t, OnColumnGapChanged, NULL);\r\n}\r\n\r\n//\r\n// Listview item - Event for when the column gap has changed.\r\n//\r\nint EZ_listviewitem_OnColumnGapChanged(ez_control_t *self, void *ext_event_info)\r\n{\r\n\tez_listviewitem_t *lvi = (ez_listviewitem_t *)self;\r\n\tEZ_listviewitem_LayoutControl(lvi);\r\n\t\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_listviewitem_t, OnColumnGapChanged, NULL);\r\n\treturn 0;\r\n}\r\n\r\n//\r\n// Listview item - Sets the column width for a given column.\r\n//\r\nvoid EZ_listviewitem_SetColumnWidth(ez_listviewitem_t *self, int column, int width)\r\n{\r\n\tif (!VALID_COLUMN(column))\r\n\t\treturn;\r\n\r\n\tself->item_widths[column] = width;\r\n\r\n\tCONTROL_RAISE_EVENT(NULL, self, ez_listviewitem_t, OnColumnWidthChanged, (void *)(uintptr_t)column);\r\n}\r\n\r\n//\r\n// Listview item - The column width has changed for some column.\r\n//\r\nint EZ_listviewitem_OnColumnWidthChanged(ez_control_t *self, void *ext_event_info)\r\n{\r\n\tez_listviewitem_t *lvi = (ez_listviewitem_t *)self;\r\n\tint column = (int)(uintptr_t)ext_event_info;\r\n\r\n\tEZ_control_SetSize(self, lvi->item_widths[column], self->height);\r\n\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, lvi, ez_listviewitem_t, OnColumnWidthChanged, ext_event_info);\r\n\treturn 0;\r\n}\r\n\r\n#if 0\r\n//\r\n// Listview item - A sub items text has changed.\r\n//\r\nint EZ_listviewitem_OnSubItemChanged(ez_control_t *self, void *ext_event_info)\r\n{\r\n\tez_listviewitem_changeinfo_t *change = (ez_listviewitem_changeinfo_t *)ext_event_info;\r\n\r\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_listviewitem_t, OnSubItemChanged, ext_event_info);\r\n\treturn 0;\r\n}\r\n\r\n\r\n//\r\n// Listview item - The text of a label in a sub item changed (event handler).\r\n//\r\nstatic int EZ_listviewitem_OnLabelTextChanged(ez_control_t *self, void *payload, void *ext_event_info)\r\n{\r\n\tez_label_t *label = (ez_label_t *)self;\r\n\tez_listviewitem_changeinfo_t change;\r\n\tez_listview_subitem_t item;\r\n\r\n\titem.payload\t= self->payload;\r\n\t//item.text\t\t= label->\r\n\t\r\n\tchange.column\t= (int)*self->payload; // TODO: Save the column in the label controls payload.\r\n\tchange.payload\t= self;\r\n\t\r\n\r\n\tCONTROL_RAISE_EVENT(NULL, self, ez_listviewitem_t, OnSubItemChanged, (void *)&change);\r\n}\r\n#endif\r\n\r\n"
  },
  {
    "path": "src/ez_listviewitem.h",
    "content": "\r\n#ifndef __EZ_LISTVIEWITEM_H__\r\n#define __EZ_LISTVIEWITEM_H__\r\n/*\r\nCopyright (C) 2008 ezQuake team\r\n\r\nThis program is free software; you can redistribute it and/or\r\nmodify it under the terms of the GNU General Public License\r\nas published by the Free Software Foundation; either version 2\r\nof the License, or (at your option) any later version.\r\n\r\nThis program is distributed in the hope that it will be useful,\r\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\r\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\r\n\r\nSee the GNU General Public License for more details.\r\n\r\nYou should have received a copy of the GNU General Public License\r\nalong with this program; if not, write to the Free Software\r\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\r\n\r\n$Id: ez_listviewitem.h,v 1.55 2007-10-27 14:51:15 cokeman1982 Exp $\r\n*/\r\n\r\n#include \"ez_controls.h\"\r\n#include \"ez_label.h\"\r\n\r\n// =========================================================================================\r\n// Listview item\r\n// =========================================================================================\r\n\r\n#define LISTVIEW_COLUMN_COUNT\t32\r\n\r\ntypedef struct ez_listview_subitem_s\r\n{\r\n\tchar *text;\t\t\t// The text that should be used in the listview column. The contents of this pointer will be COPIED not used directly.\r\n\tvoid *payload;\t\t// A pointer to a payload item supplied by the user. Up to the caller to clean this up.\r\n} ez_listview_subitem_t;\r\n\r\n//\r\n// Information associated with the change of an item column change.\r\n//\r\ntypedef struct ez_listviewitem_changeinfo_s\r\n{\r\n\t//ez_listview_subitem_t\t*item;\t\t// Contains the info about the new text of the listview column that was changed, and its user associated payload.\r\n\tez_label_t\t\t\t\t*item;\r\n\tint\t\t\t\t\t\tcolumn;\t\t// The column of the listview that this item is on.\r\n\tvoid\t\t\t\t\t*payload;\t// The user associated payload associated with this listview item (the entire row, not just the column).\r\n} ez_listviewitem_changeinfo_t;\r\n\r\ntypedef struct ez_listviewitem_eventcount_s\r\n{\r\n\tint OnColumnAdded;\r\n\tint OnColumnVisibilityChanged;\r\n\tint OnColumnGapChanged;\r\n\tint OnColumnWidthChanged;\r\n\t//int OnSubItemChanged;\r\n} ez_listviewitem_eventcount_t;\r\n\r\ntypedef struct ez_listviewitem_events_s\r\n{\r\n\tez_event_fp OnColumnAdded;\r\n\tez_event_fp\tOnColumnVisibilityChanged;\r\n\tez_event_fp OnColumnGapChanged;\r\n\tez_event_fp OnColumnWidthChanged;\r\n\t//ez_event_fp OnSubItemChanged;\r\n} ez_listviewitem_events_t;\r\n\r\ntypedef struct ez_listviewitem_eventhandlers_s\r\n{\r\n\tez_eventhandler_t *OnColumnAdded;\r\n\tez_eventhandler_t *OnColumnVisibilityChanged;\r\n\tez_eventhandler_t *OnColumnGapChanged;\r\n\tez_eventhandler_t *OnColumnWidthChanged;\r\n\t//ez_eventhandler_t *OnSubItemChanged;\r\n} ez_listviewitem_eventhandlers_t;\r\n\r\ntypedef struct ez_listviewitem_s\r\n{\r\n\tez_control_t\t\t\tsuper;\t\t\t\t\t\t\t// The super class.\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// (we inherit a scrollpane instead of a normal control so we get scrolling also)\r\n\r\n\tez_listviewitem_events_t\t\tevents;\r\n\tez_listviewitem_eventhandlers_t\tevent_handlers;\r\n\tez_listviewitem_eventcount_t\tinherit_levels;\r\n\tez_listviewitem_eventcount_t\toverride_counts;\r\n\r\n\tez_label_t\t\t\t\t*items[LISTVIEW_COLUMN_COUNT];\t\t\t// The sub items (what's shown in the columns).\r\n\tint\t\t\t\t\t\titem_count;\t\t\t\t\t\t\t\t// \r\n\tint\t\t\t\t\t\titem_gap;\t\t\t\t\t\t\t\t// The gap between two controls.\r\n\tint\t\t\t\t\t\titem_widths[LISTVIEW_COLUMN_COUNT];\t\t// The width of the sub items.\r\n\tqbool\t\t\t\t\titem_visibile[LISTVIEW_COLUMN_COUNT];\t// Which columns are visible?\r\n\r\n\tint\t\t\t\t\t\toverride_count;\t\t\t\t\t\t\t// These are needed so that subclasses can override listview specific events.\r\n\tint\t\t\t\t\t\tinheritance_level;\r\n} ez_listviewitem_t;\r\n\r\n//\r\n// Listview item - Creates a new listview item and initializes it.\r\n//\r\nez_listviewitem_t *EZ_listviewitem_Create(ez_tree_t *tree, ez_control_t *parent,\r\n\t\t\t\t\t\t\t  char *name, char *description,\r\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\r\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\r\n\r\n//\r\n// Listview item - Initializes a listview item.\r\n//\r\nvoid EZ_listviewitem_Init(ez_listviewitem_t *listviewitem, ez_tree_t *tree, ez_control_t *parent,\r\n\t\t\t\t\t\t\t  char *name, char *description,\r\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\r\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\r\n\r\n//\r\n// Listview item - Destroys a listview item.\r\n//\r\nint EZ_listviewitem_Destroy(ez_control_t *self, qbool destroy_children);\r\n\r\n//\r\n// Listview item - Event for when a new column was added to the listview item.\r\n//\r\nint EZ_listviewitem_OnColumnAdded(ez_control_t *self, void *column);\r\n\r\n//\r\n// Listview item - The visibility changed for a column.\r\n//\r\nint EZ_listviewitem_OnColumnVisibilityChanged(ez_control_t *self, void *ext_event_info);\r\n\r\n//\r\n// Listview item - Adds a column to the listview item.\r\n//\r\nvoid EZ_listviewitem_AddColumn(ez_listviewitem_t *self, ez_listview_subitem_t data, int width);\r\n\r\n//\r\n// Listview item - Lays out the control.\r\n//\r\nvoid EZ_listviewitem_LayoutControl(ez_listviewitem_t *self);\r\n\r\n//\r\n// Listview item - Gets a column from the listview item.\r\n//\r\nez_label_t *EZ_listviewitem_GetColumn(ez_listviewitem_t *self, int column);\r\n\r\n//\r\n// Listview item - Sets if a column should be visible or not.\r\n//\r\nvoid EZ_listviewitem_SetColumnVisible(ez_listviewitem_t *self, int column, qbool visible);\r\n\r\n//\r\n// Listview item - A sub items text has changed.\r\n//\r\nint EZ_listviewitem_OnSubItemChanged(ez_control_t *self, void *ext_event_info);\r\n\r\n//\r\n// Listview item - Event for when the column gap has changed.\r\n//\r\nint EZ_listviewitem_OnColumnGapChanged(ez_control_t *self, void *ext_event_info);\r\n\r\n//\r\n// Listview item - Sets the gap between columns.\r\n//\r\nvoid EZ_listviewitem_SetColumnGap(ez_listviewitem_t *self, int gap);\r\n\r\n//\r\n// Listview item - Sets the column width for a given column.\r\n//\r\nvoid EZ_listviewitem_SetColumnWidth(ez_listviewitem_t *self, int column, int width);\r\n\r\n//\r\n// Listview item - The column width has changed for some column.\r\n//\r\nint EZ_listviewitem_OnColumnWidthChanged(ez_control_t *self, void *ext_event_info);\r\n\r\n#endif // __EZ_LISTVIEWITEM_H__\r\n\r\n"
  },
  {
    "path": "src/ez_scrollbar.c",
    "content": "\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_scrollpane.c,v 1.78 2007/10/27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"keys.h\"\n#include \"utils.h\"\n#include \"common_draw.h\"\n#include \"ez_controls.h\"\n#include \"ez_scrollbar.h\"\n\n#ifdef _MSC_VER\n#pragma warning( disable : 4189 )\n#endif\n\n// =========================================================================================\n// Scrollbar\n// =========================================================================================\n\n//\n// Scrollbar - OnMouseDown event.\n//\nint EZ_scrollbar_OnSliderMouseDown(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\n\tif (!self->parent || self->parent->CLASS_ID != EZ_SCROLLBAR_ID)\n\t{\n\t\tSys_Error(\"EZ_scrollbar_OnSliderMove(): Parent of slider is not a scrollbar!\");\n\t}\n\telse\n\t{\n\t\tez_scrollbar_t *scrollbar = (ez_scrollbar_t *)self->parent;\n\n\t\t// Start sliding.\n\t\tscrollbar->int_flags |= sliding;\n\t}\n\n\treturn true;\n}\n\n//\n// Scrollbar - Generic scroll button action event handler.\n//\nstatic void EZ_scrollbar_OnScrollButtonMouseDown(ez_scrollbar_t *scrollbar, qbool back)\n{\n\tez_control_t *scrollbar_ctrl = (ez_control_t *)scrollbar;\n\tez_control_t *scroll_target = (scrollbar->ext_flags & target_parent) ? scrollbar_ctrl->parent : scrollbar->target;\n\n\tif (!scroll_target)\n\t{\n\t\treturn; // We have nothing to scroll.\n\t}\n\n\tif (scrollbar->orientation == vertical)\n\t{\n\t\tEZ_control_SetScrollChange(scroll_target, 0, (back ? -scrollbar->scroll_delta_y : scrollbar->scroll_delta_y));\n\t}\n\telse\n\t{\n\t\tEZ_control_SetScrollChange(scroll_target, (back ? -scrollbar->scroll_delta_x : scrollbar->scroll_delta_x), 0);\n\t}\n}\n\n//\n// Scrollbar - Event handler for pressing the back button (left or up).\n//\nstatic int EZ_scrollbar_OnBackButtonMouseDown(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\n\tif (!self->parent || self->parent->CLASS_ID != EZ_SCROLLBAR_ID)\n\t{\n\t\tSys_Error(\"EZ_scrollbar_OnBackButtonMouseDown(): Parent of back button is not a scrollbar!\");\n\t}\n\telse \n\t{\n\t\tez_scrollbar_t *scrollbar = (ez_scrollbar_t *)self->parent;\n\t\tEZ_scrollbar_OnScrollButtonMouseDown(scrollbar, true);\n\t}\n\n\treturn true;\n}\n\n//\n// Scrollbar - Event handler for pressing the back button (left or up).\n//\nstatic int EZ_scrollbar_OnForwardButtonMouseDown(ez_control_t *self, void *payload, mouse_state_t *ms)\n{\n\tif (!self->parent || self->parent->CLASS_ID != EZ_SCROLLBAR_ID)\n\t{\n\t\tSys_Error(\"EZ_scr\tollbar_OnForwardButtonMouseDown(): Parent of forward button is not a scrollbar!\");\n\t}\n\telse\n\t{\n\t\tez_scrollbar_t *scrollbar = (ez_scrollbar_t *)self->parent;\n\t\tEZ_scrollbar_OnScrollButtonMouseDown(scrollbar, false);\n\t}\n\n\treturn true;\n}\n\n//\n// Scrollbar - Creates a new scrollbar and initializes it.\n//\nez_scrollbar_t *EZ_scrollbar_Create(ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\n{\n\tez_scrollbar_t *scrollbar = NULL;\n\n\t// We have to have a tree to add the control to.\n\tif (!tree)\n\t{\n\t\treturn NULL;\n\t}\n\n\tscrollbar = (ez_scrollbar_t *)Q_malloc(sizeof(ez_scrollbar_t));\n\tmemset(scrollbar, 0, sizeof(ez_scrollbar_t));\n\n\tEZ_scrollbar_Init(scrollbar, tree, parent, name, description, x, y, width, height, flags);\n\t\n\treturn scrollbar;\n}\n\n//\n// Scrollbar - Initializes a scrollbar.\n//\nvoid EZ_scrollbar_Init(ez_scrollbar_t *scrollbar, ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\n{\n\t// Initialize the inherited class first.\n\tEZ_control_Init(&scrollbar->super, tree, parent, name, description, x, y, width, height, flags);\n\n\t// Initilize the button specific stuff.\n\t((ez_control_t *)scrollbar)->CLASS_ID\t= EZ_SCROLLBAR_ID;\n\t((ez_control_t *)scrollbar)->ext_flags\t|= (flags | control_focusable | control_contained | control_resizeable);\n\n\t// Override control events.\n\tCONTROL_REGISTER_EVENT(scrollbar, EZ_scrollbar_Destroy, OnDestroy, ez_control_t);\n\tCONTROL_REGISTER_EVENT(scrollbar, EZ_scrollbar_OnResize, OnResize, ez_control_t);\n\tCONTROL_REGISTER_EVENT(scrollbar, EZ_scrollbar_OnParentResize, OnParentResize, ez_control_t);\n\tCONTROL_REGISTER_EVENT(scrollbar, EZ_scrollbar_OnMouseEvent, OnMouseEvent, ez_control_t);\n\tCONTROL_REGISTER_EVENT(scrollbar, EZ_scrollbar_OnMouseDown, OnMouseDown, ez_control_t);\n\tCONTROL_REGISTER_EVENT(scrollbar, EZ_scrollbar_OnMouseUpOutside, OnMouseUpOutside, ez_control_t);\n\tCONTROL_REGISTER_EVENT(scrollbar, EZ_scrollbar_OnParentScroll, OnParentScroll, ez_control_t);\n\n\t// Scrollbar specific events.\n\tCONTROL_REGISTER_EVENT(scrollbar, EZ_scrollbar_OnTargetChanged, OnTargetChanged, ez_scrollbar_t);\n\n\tscrollbar->back\t\t= EZ_button_Create(tree, (ez_control_t *)scrollbar, \"Scrollbar back button\", \"\", 0, 0, 0, 0, control_contained | control_enabled);\n\tscrollbar->forward\t= EZ_button_Create(tree, (ez_control_t *)scrollbar, \"Scrollbar forward button\", \"\", 0, 0, 0, 0, control_contained | control_enabled);\n\tscrollbar->slider\t= EZ_button_Create(tree, (ez_control_t *)scrollbar, \"Scrollbar slider button\", \"\", 0, 0, 0, 0, control_contained | control_enabled);\n\t\n\tEZ_control_AddOnMouseDown((ez_control_t *)scrollbar->slider, EZ_scrollbar_OnSliderMouseDown, NULL);\n\tEZ_control_AddOnMouseDown((ez_control_t *)scrollbar->back, EZ_scrollbar_OnBackButtonMouseDown, NULL);\n\tEZ_control_AddOnMouseDown((ez_control_t *)scrollbar->forward, EZ_scrollbar_OnForwardButtonMouseDown, NULL);\n\n\tscrollbar->slider_minsize = 5;\n\tscrollbar->scroll_delta_x = 1;\n\tscrollbar->scroll_delta_y = 1;\n\n\t// Listen to repeated mouse events so that we continue scrolling when\n\t// holding down the mouse button over the scroll arrows.\n\tEZ_control_SetListenToRepeatedMouseClicks((ez_control_t *)scrollbar->back, true);\n\tEZ_control_SetListenToRepeatedMouseClicks((ez_control_t *)scrollbar->forward, true);\n\t\n\t// TODO : Remove this test stuff.\n\t/*\n\t{\n\t\tEZ_button_SetNormalColor(scrollbar->back, 255, 125, 125, 125);\n\t\tEZ_button_SetNormalColor(scrollbar->forward, 255, 125, 125, 125);\n\t\tEZ_button_SetNormalColor(scrollbar->slider, 255, 125, 125, 125);\n\t}\n\t*/\n\n\tCONTROL_RAISE_EVENT(NULL, (ez_control_t *)scrollbar, ez_control_t, OnResize, NULL);\n}\n\n//\n// Scrollbar - Destroys the scrollbar.\n//\nint EZ_scrollbar_Destroy(ez_control_t *self, qbool destroy_children)\n{\n\tez_scrollbar_t *scrollbar = (ez_scrollbar_t *)self;\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnDestroy, destroy_children);\n\n\tEZ_eventhandler_Remove(scrollbar->event_handlers.OnTargetChanged, NULL, true);\n\n\tEZ_control_Destroy(self, destroy_children);\n\n\treturn 0;\n}\n\n//\n// Scrollbar - Calculates and sets the parents scroll position based on where the slider button is.\n//\nstatic void EZ_scrollbar_CalculateParentScrollPosition(ez_scrollbar_t *scrollbar, ez_control_t *target)\n{\n//\tez_control_t *self\t\t\t= (ez_control_t *)scrollbar;\n\tez_control_t *back_ctrl\t\t= (ez_control_t *)scrollbar->back;\n//\tez_control_t *forward_ctrl\t= (ez_control_t *)scrollbar->forward;\n\tez_control_t *slider_ctrl\t= (ez_control_t *)scrollbar->slider;\n\tfloat scroll_ratio;\n\n\tif (!target)\n\t{\n\t\treturn;\n\t}\n\n\tif (scrollbar->orientation == vertical)\n\t{\n\t\tscroll_ratio = (slider_ctrl->y - back_ctrl->height) / (float)scrollbar->scroll_area;\n\t\tEZ_control_SetScrollPosition(target, target->virtual_x, Q_rint(scroll_ratio * target->virtual_height));\n\t}\n\telse\n\t{\n\t\tscroll_ratio = (slider_ctrl->x - back_ctrl->width) / (float)scrollbar->scroll_area;\n\t\tEZ_control_SetScrollPosition(target, Q_rint(scroll_ratio * target->virtual_width), target->virtual_y);\n\t}\n}\n\n//\n// Scrollbar - Updates the slider position of the scrollbar based on the parents scroll position.\n//\nstatic void EZ_scrollbar_UpdateSliderBasedOnTarget(ez_scrollbar_t *scrollbar, ez_control_t *target)\n{\n\tez_control_t *self\t\t\t= (ez_control_t *)scrollbar;\n\tez_control_t *back_ctrl\t\t= (ez_control_t *)scrollbar->back;\n\tez_control_t *forward_ctrl\t= (ez_control_t *)scrollbar->forward;\n\tez_control_t *slider_ctrl\t= (ez_control_t *)scrollbar->slider;\n\tfloat scroll_ratio;\n\n\tif (!target)\n\t{\n\t\treturn;\n\t}\n\n\t// Don't do anything if this is the user moving the slider using the mouse.\n\tif (scrollbar->int_flags & scrolling)\n\t{\n\t\treturn;\n\t}\n\n\tif (scrollbar->orientation == vertical)\n\t{\n\t\tint new_y;\n\n\t\t// Find how far down on the parent control we're scrolled (percentage).\n\t\tscroll_ratio = fabs(target->virtual_y / (float)target->virtual_height);\n\t\t\n\t\t// Calculate the position of the slider by multiplying the scroll areas\n\t\t// height with the scroll ratio.\n\t\tnew_y = back_ctrl->height + Q_rint(scroll_ratio * scrollbar->scroll_area);\n\t\tclamp(new_y, back_ctrl->height, (self->height - forward_ctrl->height - slider_ctrl->height));\n\n\t\tEZ_control_SetPosition(slider_ctrl, 0, new_y);\n\t}\n\telse\n\t{\n\t\tint new_x;\n\t\tscroll_ratio = fabs(target->virtual_x / (float)target->virtual_width);\n\t\t\n\t\tnew_x = back_ctrl->width + Q_rint(scroll_ratio * scrollbar->scroll_area);\n\t\tclamp(new_x, back_ctrl->width, (self->width - forward_ctrl->width - slider_ctrl->width));\n\t\t\n\t\tEZ_control_SetPosition(slider_ctrl, new_x, 0);\n\t}\n}\n\n//\n// Scrollbar - Sets if the target for the scrollbar should be it's parent, or a specified target control.\n//\t\t\t\t(The target controls OnScroll event handler will be used if it's not the parent)\n//\nvoid EZ_scrollbar_SetTargetIsParent(ez_scrollbar_t *scrollbar, qbool targetparent)\n{\n\tSET_FLAG(scrollbar->ext_flags, target_parent, targetparent);\n}\n\n//\n// Scrollbar - Set the minimum slider size.\n//\nvoid EZ_scrollbar_SetSliderMinSize(ez_scrollbar_t *scrollbar, int minsize)\n{\n\tscrollbar->slider_minsize = max(1, minsize);\n}\n\n//\n// Scrollbar - Sets the amount to scroll the parent control when pressing the scrollbars scroll buttons.\n//\nvoid EZ_scrollbar_SetScrollDelta(ez_scrollbar_t *scrollbar, int scroll_delta_x, int scroll_delta_y)\n{\n\tscrollbar->scroll_delta_x = max(1, scroll_delta_x);\n\tscrollbar->scroll_delta_y = max(1, scroll_delta_y);\n}\n\n//\n// Scrollbar - Set if the scrollbar should be vertical or horizontal.\n//\nvoid EZ_scrollbar_SetIsVertical(ez_scrollbar_t *scrollbar, qbool is_vertical)\n{\n\tscrollbar->orientation = (is_vertical ? vertical : horizontal);\n}\n\n//\n// Scrollbar - Calculates the size of the slider button.\n//\nstatic void EZ_scrollbar_CalculateSliderSize(ez_scrollbar_t *scrollbar, ez_control_t *target)\n{\n\tez_control_t *self = (ez_control_t *)scrollbar;\n\n\tif (target)\n\t{\n\t\tif (scrollbar->orientation == vertical)\n\t\t{\n\t\t\t// Get the percentage of the parent that is shown and calculate the new slider button size from that. \n\t\t\tfloat target_height_ratio\t= (target->height / (float)target->virtual_height);\n\t\t\tint new_slider_height\t\t= max(scrollbar->slider_minsize, Q_rint(target_height_ratio * scrollbar->scroll_area));\n\n\t\t\tEZ_control_SetSize((ez_control_t *)scrollbar->slider, self->width, new_slider_height);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfloat target_width_ratio\t= (target->width / (float)target->virtual_width);\n\t\t\tint new_slider_width\t\t= max(scrollbar->slider_minsize, Q_rint(target_width_ratio * scrollbar->scroll_area));\n\n\t\t\tEZ_control_SetSize((ez_control_t *)scrollbar->slider, new_slider_width, self->height);\n\t\t}\n\t}\n}\n\n//\n// Scrollbar - Repositions the back / forward buttons according to orientation.\n//\nstatic void EZ_scrollbar_RepositionScrollButtons(ez_scrollbar_t *scrollbar)\n{\n\tez_control_t *self\t\t\t= (ez_control_t *)scrollbar;\n\tez_control_t *back_ctrl\t\t= (ez_control_t *)scrollbar->back;\n\tez_control_t *slider_ctrl\t= (ez_control_t *)scrollbar->slider;\n\tez_control_t *forward_ctrl\t= (ez_control_t *)scrollbar->forward;\n\n\t// Let the super class do it's thing first.\n\tEZ_control_OnResize(self, NULL);\n\n\tif (scrollbar->orientation == vertical)\n\t{\n\t\t// Up button.\n\t\tEZ_control_SetSize(back_ctrl, self->width, self->width);\n\t\tEZ_control_SetAnchor(back_ctrl, anchor_left | anchor_top | anchor_right);\n\n\t\t// Slider.\n\t\tEZ_control_SetPosition(slider_ctrl, 0, self->width);\n\t\tEZ_control_SetAnchor(slider_ctrl, anchor_left | anchor_right);\n\n\t\t// Down button.\n\t\tEZ_control_SetSize(forward_ctrl, self->width, self->width);\n\t\tEZ_control_SetAnchor(forward_ctrl, anchor_left | anchor_bottom | anchor_right);\n\n\t\tscrollbar->scroll_area = self->height - (forward_ctrl->height + back_ctrl->height);\n\t}\n\telse\n\t{\n\t\t// Left button.\n\t\tEZ_control_SetSize(back_ctrl, self->height, self->height);\n\t\tEZ_control_SetAnchor(back_ctrl, anchor_left | anchor_top | anchor_bottom);\n\n\t\t// Slider.\n\t\tEZ_control_SetPosition(slider_ctrl, self->height, 0);\n\t\tEZ_control_SetAnchor(slider_ctrl, anchor_top | anchor_bottom);\n\n\t\t// Right button.\n\t\tEZ_control_SetSize(forward_ctrl, self->height, self->height);\n\t\tEZ_control_SetAnchor(forward_ctrl, anchor_top | anchor_bottom | anchor_right);\n\n\t\tscrollbar->scroll_area = self->width - (forward_ctrl->width + back_ctrl->width);\n\t}\n}\n\n//\n// Control - Event handler for when the target control gets scrolled.\n//\nstatic int EZ_scrollbar_OnTargetScroll(ez_control_t *self, void *payload, void *ext_event_info)\n{\n\tez_scrollbar_t *scrollbar = NULL;\n\n\tif (!payload)\n\t{\n\t\tSys_Error(\"EZ_scrollbar_OnTargetResize(): Payload is NULL, so no scrollbar object.\");\n\t}\n\n\tscrollbar = (ez_scrollbar_t *)payload;\n\n\tif (!(scrollbar->ext_flags & target_parent))\n\t{\n\t\tEZ_scrollbar_UpdateSliderBasedOnTarget(scrollbar, scrollbar->target);\n\t}\n\n\treturn 0;\n}\n\n//\n// Scrollbar - Generic function of what to do when one of the scrollbars target was resized.\n//\nstatic int EZ_scrollbar_OnTargetResize(ez_control_t *self, void *payload, void *ext_event_info)\n{\n\tez_scrollbar_t *scrollbar = NULL;\n\n\tif (!payload)\n\t{\n\t\tSys_Error(\"EZ_scrollbar_OnTargetResize(): Payload is NULL, so no scrollbar object.\");\n\t}\n\n\tscrollbar = (ez_scrollbar_t *)payload;\n\n\tif (!(scrollbar->ext_flags & target_parent))\n\t{\n\t\tEZ_scrollbar_CalculateSliderSize(scrollbar, scrollbar->target);\n\t}\n\n\treturn 0;\n}\n\n//\n// Scrollbar - Set the target control that the scrollbar should scroll.\n//\nvoid EZ_scrollbar_SetTarget(ez_scrollbar_t *scrollbar, ez_control_t *target)\n{\n\t// Remove the event handlers from the old target.\n\tif (scrollbar->target)\n\t{\n\t\tEZ_eventhandler_Remove(scrollbar->target->event_handlers.OnResize, EZ_scrollbar_OnTargetResize, false);\n\t\tEZ_eventhandler_Remove(scrollbar->target->event_handlers.OnScroll, EZ_scrollbar_OnTargetScroll, false);\n\t}\n\n\tscrollbar->target = target;\n\tCONTROL_RAISE_EVENT(NULL, scrollbar, ez_scrollbar_t, OnTargetChanged, NULL);\n}\n\n//\n// Scrollbar - The target of the scrollbar changed.\n//\nint EZ_scrollbar_OnTargetChanged(ez_control_t *self, void *ext_event_info)\n{\n\tez_scrollbar_t *scrollbar = (ez_scrollbar_t *)self;\n\n\tif (scrollbar->target)\n\t{\n\t\tEZ_control_AddOnScroll(scrollbar->target, EZ_scrollbar_OnTargetScroll, scrollbar);\n\t\tEZ_control_AddOnResize(scrollbar->target, EZ_scrollbar_OnTargetResize, scrollbar);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, scrollbar, ez_scrollbar_t, OnTargetChanged, NULL);\n\treturn 0;\n}\n\n//\n// Scrollbar - OnResize event.\n//\nint EZ_scrollbar_OnResize(ez_control_t *self, void *ext_event_info)\n{\n\tez_scrollbar_t *scrollbar = (ez_scrollbar_t *)self;\n\n\t// Let the super class do it's thing first.\n\tEZ_control_OnResize(self, NULL);\n\n\t// Make sure the buttons are in the correct place.\n\tEZ_scrollbar_RepositionScrollButtons(scrollbar);\n\tEZ_scrollbar_CalculateSliderSize(scrollbar, ((scrollbar->ext_flags & target_parent) ? self->parent : scrollbar->target));\n\n\t// Reset the min virtual size to 1x1 so that the \n\t// scrollbar won't stop resizing when it's parent is resized.\n\tEZ_control_SetMinVirtualSize(self, 1, 1);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnResize, NULL);\n\treturn 0;\n}\n\n//\n// Scrollbar - OnParentResize event.\n//\nint EZ_scrollbar_OnParentResize(ez_control_t *self, void *ext_event_info)\n{\n\tez_scrollbar_t *scrollbar = (ez_scrollbar_t *)self;\n\n\t// Let the super class do it's thing first.\n\tEZ_control_OnParentResize(self, NULL);\n\n\tif (scrollbar->ext_flags & target_parent)\n\t{\n\t\tEZ_scrollbar_CalculateSliderSize(scrollbar, self->parent);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnParentResize, NULL);\n\treturn 0;\n}\n\n//\n// Scrollbar - OnMouseDown event.\n//\nint EZ_scrollbar_OnMouseDown(ez_control_t *self, mouse_state_t *ms)\n{\n\tqbool  mouse_handled = false;\n//\tez_scrollbar_t *scrollbar\t= (ez_scrollbar_t *)self;\n\n\tEZ_control_OnMouseDown(self, ms);\n\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled, self, ez_control_t, OnMouseDown, ms);\n\n\treturn mouse_handled;\n}\n\n//\n// Scrollbar - OnMouseUpOutside event.\n//\nint EZ_scrollbar_OnMouseUpOutside(ez_control_t *self, mouse_state_t *ms)\n{\n\tez_scrollbar_t *scrollbar\t= (ez_scrollbar_t *)self;\n\tqbool mouse_handled\t\t\t= false;\n\n\tEZ_control_OnMouseUpOutside(self, ms);\n\n\tscrollbar->int_flags &= ~sliding;\n\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled, self, ez_control_t, OnMouseUpOutside, ms);\n\n\treturn false;\n}\n\n//\n// Scrollbar - Mouse event.\n//\nint EZ_scrollbar_OnMouseEvent(ez_control_t *self, mouse_state_t *ms)\n{\n\tez_scrollbar_t *scrollbar\t\t= (ez_scrollbar_t *)self;\n\tez_control_t *back_ctrl\t\t\t= (ez_control_t *)scrollbar->back;\n\tez_control_t *forward_ctrl\t\t= (ez_control_t *)scrollbar->forward;\n\tez_control_t *slider_ctrl\t\t= (ez_control_t *)scrollbar->slider;\n\tint m_delta_x\t\t\t\t\t= Q_rint(ms->x - ms->x_old);\n\tint m_delta_y\t\t\t\t\t= Q_rint(ms->y - ms->y_old);\n\tqbool mouse_handled\t\t\t\t= false;\n\tqbool mouse_handled_tmp\t\t\t= false;\n\n\tmouse_handled = EZ_control_OnMouseEvent(self, ms);\n\n\tif (!mouse_handled)\n\t{\n\t\tif (scrollbar->int_flags & sliding)\n\t\t{\n\t\t\tif (scrollbar->orientation == vertical)\n\t\t\t{\n//\t\t\t\tfloat scroll_ratio = 0;\n\n\t\t\t\t// Reposition the slider within the scrollbar control based on where the mouse moves.\n\t\t\t\tint new_y = slider_ctrl->y + m_delta_y;\n\n\t\t\t\t// Only allow moving the scroll slider in the area between the two buttons (the scroll area).\n\t\t\t\tclamp(new_y, back_ctrl->height, (self->height - forward_ctrl->height - slider_ctrl->height));\n\t\t\t\tEZ_control_SetPosition(slider_ctrl, 0, new_y);\n\n\t\t\t\tmouse_handled = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tint new_x = slider_ctrl->x + m_delta_x;\n\t\t\t\tclamp(new_x, back_ctrl->width, (self->width - forward_ctrl->width - slider_ctrl->width));\n\t\t\t\tEZ_control_SetPosition(slider_ctrl, new_x, 0);\n\t\t\t\tmouse_handled = true;\n\t\t\t}\n\n\t\t\t// Make sure we don't try to set the position of the slider\n\t\t\t// as the parents scroll position changes, like normal.\n\t\t\tscrollbar->int_flags |= scrolling;\n\n\t\t\tif (scrollbar->ext_flags & target_parent)\n\t\t\t{\n\t\t\t\tEZ_scrollbar_CalculateParentScrollPosition(scrollbar, self->parent);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tEZ_scrollbar_CalculateParentScrollPosition(scrollbar, scrollbar->target);\n\t\t\t}\n\t\t\t\n\t\t\tscrollbar->int_flags &= ~scrolling;\n\t\t}\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled_tmp, self, ez_control_t, OnMouseEvent, ms);\n\tmouse_handled = (mouse_handled | mouse_handled_tmp);\n\n\treturn mouse_handled;\n}\n\n//\n// Scrollbar - OnParentScroll event.\n//\nint EZ_scrollbar_OnParentScroll(ez_control_t *self, void *ext_event_info)\n{\n\tez_scrollbar_t *scrollbar = (ez_scrollbar_t *)self;\n\n\t// Super class.\n\tEZ_control_OnParentScroll(self, NULL);\n\n\t// Update the slider button to match the new scroll position.\n\tif (scrollbar->ext_flags & target_parent)\n\t{\n\t\tEZ_scrollbar_UpdateSliderBasedOnTarget(scrollbar, self->parent);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnParentScroll, NULL);\n\treturn 0;\n}\n\n\n\n"
  },
  {
    "path": "src/ez_scrollbar.h",
    "content": "\n#ifndef __EZ_SCROLLBAR_H__\n#define __EZ_SCROLLBAR_H__\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_scrollbar.h,v 1.55 2007-10-27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"ez_controls.h\"\n#include \"ez_button.h\"\n\n// =========================================================================================\n// Scrollbar\n// =========================================================================================\n\ntypedef enum ez_orientation_s\n{\n\tvertical\t= 0,\n\thorizontal\t= 1\n} ez_orientation_t;\n\ntypedef enum ez_scrollbar_flags_e\n{\n\ttarget_parent\t= (1 << 0)\n} ez_scrollbar_flags_t;\n\ntypedef enum ez_scrollbar_iflags_e\n{\n\tsliding\t\t\t= (1 << 0),\n\tscrolling\t\t= (1 << 1)\n} ez_scrollbar_iflags_t;\n\ntypedef struct ez_scrollbar_eventcount_s\n{\n\tint OnTargetChanged;\n} ez_scrollbar_eventcount_t;\n\ntypedef struct ez_scrollbar_events_s\n{\n\tez_event_fp\tOnTargetChanged;\n} ez_scrollbar_events_t;\n\ntypedef struct ez_scrollbar_eventhandlers_s\n{\n\tez_eventhandler_t\t\t*OnTargetChanged;\n} ez_scrollbar_eventhandlers_t;\n\ntypedef struct ez_scrollbar_s\n{\n\tez_control_t\t\t\t\tsuper;\t\t\t\t// The super class.\n\n\tez_scrollbar_events_t\t\tevents;\n\tez_scrollbar_eventhandlers_t event_handlers;\n\tez_scrollbar_eventcount_t\tinherit_levels;\n\tez_scrollbar_eventcount_t\toverride_counts;\n\n\tez_button_t\t\t\t\t\t*back;\t\t\t\t// Up / left button depending on the scrollbars orientation.\n\tez_button_t\t\t\t\t\t*slider;\t\t\t// The slider button.\n\tez_button_t\t\t\t\t\t*forward;\t\t\t// Down / right button.\n\t\n\tez_orientation_t\t\t\torientation;\t\t// The orientation of the scrollbar, vertical / horizontal.\n\tint\t\t\t\t\t\t\tscroll_area;\t\t// The width or height (depending on orientation) of the area between\n\t\t\t\t\t\t\t\t\t\t\t\t\t// the forward/back buttons. That is, the area you can move the slider in.\n\n\tint\t\t\t\t\t\t\tslider_minsize;\t\t// The minimum size of the slider button.\n\tint\t\t\t\t\t\t\tscroll_delta_x;\t\t// How much should the scrollbar scroll it's parent when the scroll buttons are pressed?\n\tint\t\t\t\t\t\t\tscroll_delta_y;\n\n\tez_control_t\t\t\t\t*target;\t\t\t// The target of the scrollbar if the parent isn't targeted (ext_flag & target_parent).\n\n\tez_scrollbar_flags_t\t\text_flags;\t\t\t// External flags for the scrollbar.\n\tez_scrollbar_iflags_t\t\tint_flags;\t\t\t// The internal flags for the scrollbar.\n} ez_scrollbar_t;\n\n//\n// Scrollbar - Creates a new scrollbar and initializes it.\n//\nez_scrollbar_t *EZ_scrollbar_Create(ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\n\n//\n// Scrollbar - Initializes a scrollbar.\n//\nvoid EZ_scrollbar_Init(ez_scrollbar_t *scrollbar, ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\n\n//\n// Scrollbar - Destroys the scrollbar.\n//\nint EZ_scrollbar_Destroy(ez_control_t *self, qbool destroy_children);\n\n//\n// Scrollbar - Set the target control that the scrollbar should scroll.\n//\nvoid EZ_scrollbar_SetTarget(ez_scrollbar_t *scrollbar, ez_control_t *target);\n\n//\n// Scrollbar - Set if the scrollbar should be vertical or horizontal.\n//\nvoid EZ_scrollbar_SetIsVertical(ez_scrollbar_t *scrollbar, qbool is_vertical);\n\n//\n// Scrollbar - Sets if the target for the scrollbar should be it's parent, or a specified target control.\n//\t\t\t\t(The target controls OnScroll event handler will be used if it's not the parent)\n//\nvoid EZ_scrollbar_SetTargetIsParent(ez_scrollbar_t *scrollbar, qbool targetparent);\n\n//\n// Scrollbar - The target of the scrollbar changed.\n//\nint EZ_scrollbar_OnTargetChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Scrollbar - OnResize event.\n//\nint EZ_scrollbar_OnResize(ez_control_t *self, void *ext_event_info);\n\n//\n// Scrollbar - OnParentResize event.\n//\nint EZ_scrollbar_OnParentResize(ez_control_t *self, void *ext_event_info);\n\n//\n// Scrollbar - OnMouseDown event.\n//\nint EZ_scrollbar_OnMouseDown(ez_control_t *self, mouse_state_t *ms);\n\n//\n// Scrollbar - OnMouseUpOutside event.\n//\nint EZ_scrollbar_OnMouseUpOutside(ez_control_t *self, mouse_state_t *ms);\n\n//\n// Scrollbar - Mouse event.\n//\nint EZ_scrollbar_OnMouseEvent(ez_control_t *self, mouse_state_t *ms);\n\n//\n// Scrollbar - OnParentScroll event.\n//\nint EZ_scrollbar_OnParentScroll(ez_control_t *self, void *ext_event_info);\n\n#endif // __EZ_SCROLLBAR_H__\n"
  },
  {
    "path": "src/ez_scrollpane.c",
    "content": "\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_scrollpane.c,v 1.78 2007/10/27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"keys.h\"\n#include \"utils.h\"\n#include \"common_draw.h\"\n#include \"ez_controls.h\"\n#include \"ez_scrollpane.h\"\n\n#ifdef _MSC_VER\n#pragma warning( disable : 4189 )\n#endif\n\n// =========================================================================================\n// Scrollpane\n// =========================================================================================\n\n//\n// Scrollpane - Adjusts the scrollpanes target to fit in between the scrollbars.\n//\nstatic void EZ_scrollpane_AdjustTargetSize(ez_scrollpane_t *scrollpane)\n{\n\tez_control_t *scrollpane_ctrl\t= (ez_control_t *)scrollpane;\n\n\tqbool show_v\t= (scrollpane->ext_flags & always_h_scrollbar) || (scrollpane->int_flags & show_v_scrollbar);\n\tqbool show_h\t= (scrollpane->ext_flags & always_v_scrollbar) || (scrollpane->int_flags & show_h_scrollbar);\n\tint rh_size_v\t= (scrollpane_ctrl->ext_flags & control_resize_v ? scrollpane_ctrl->resize_handle_thickness : 0); \n\tint rh_size_h\t= (scrollpane_ctrl->ext_flags & control_resize_h ? scrollpane_ctrl->resize_handle_thickness : 0); \n\n\t// Make sure we don't listen to the targets OnResize event when resizing it ourselves\n\t// or we'll get a stack overflow due to an infinite loop :S\n\tscrollpane->int_flags |= resizing_target;\n\t\n\tEZ_control_SetSize(scrollpane->target, \n\t\t\t\t\t\t(scrollpane_ctrl->width  - (show_v ? scrollpane->scrollbar_thickness : 0) - rh_size_h),\n\t\t\t\t\t\t(scrollpane_ctrl->height - (show_h ? scrollpane->scrollbar_thickness : 0) - rh_size_v));\n\n\tscrollpane->int_flags &= ~resizing_target;\n}\n\n//\n// Scrollpane - Resize the scrollbars based on the targets state.\n//\nstatic void EZ_scrollpane_ResizeScrollbars(ez_scrollpane_t *scrollpane)\n{\n\tez_control_t *scrollpane_ctrl\t= (ez_control_t *)scrollpane;\n\tez_control_t *v_scroll_ctrl\t\t= (ez_control_t *)scrollpane->v_scrollbar;\n\tez_control_t *h_scroll_ctrl\t\t= (ez_control_t *)scrollpane->h_scrollbar;\n\n//\tqbool size_changed\t= false;\n\tqbool show_v\t\t= (scrollpane->ext_flags & always_h_scrollbar) || (scrollpane->int_flags & show_v_scrollbar);\n\tqbool show_h\t\t= (scrollpane->ext_flags & always_v_scrollbar) || (scrollpane->int_flags & show_h_scrollbar);\n\tint rh_size_v\t\t= (scrollpane_ctrl->ext_flags & control_resize_v ? scrollpane_ctrl->resize_handle_thickness : 0); \n\tint rh_size_h\t\t= (scrollpane_ctrl->ext_flags & control_resize_h ? scrollpane_ctrl->resize_handle_thickness : 0); \n\n\tEZ_control_SetVisible((ez_control_t *)scrollpane->v_scrollbar, show_v);\n\tEZ_control_SetVisible((ez_control_t *)scrollpane->h_scrollbar, show_h);\n\n\t// Resize the scrollbars depending on if both are shown or not.\n\tEZ_control_SetSize(v_scroll_ctrl,\n\t\t\t\t\t\tscrollpane->scrollbar_thickness, \n\t\t\t\t\t\tscrollpane_ctrl->height - (show_h ? scrollpane->scrollbar_thickness : 0) - (2 * rh_size_v));\n\n\tEZ_control_SetSize(h_scroll_ctrl,\n\t\t\t\t\t\tscrollpane_ctrl->width - (show_v ? scrollpane->scrollbar_thickness : 0) - (2 * rh_size_h), \n\t\t\t\t\t\tscrollpane->scrollbar_thickness);\n\n\t// Resize the target control so that it doesn't overlap with the scrollbars.\n\tif (scrollpane->target)\n\t{\n\t\t// Since this resize operation is going to raise a new OnResize event on the target control\n\t\t// which in turn calls this function again we only change the size of the target to fit\n\t\t// within the scrollbars when the scrollbars actually changed size. Otherwise we'd get an infinite recursion.\n\t\tEZ_scrollpane_AdjustTargetSize(scrollpane);\n\t}\n}\n\n//\n// Scrollpane - Determine if the scrollbars should be visible based on the target controls state.\n//\nstatic void EZ_scrollpane_DetermineScrollbarVisibility(ez_scrollpane_t *scrollpane)\n{\n\tSET_FLAG(scrollpane->int_flags, show_v_scrollbar, (scrollpane->target->height <= scrollpane->target->virtual_height_min));\n\tSET_FLAG(scrollpane->int_flags, show_h_scrollbar, (scrollpane->target->width <= scrollpane->target->virtual_width_min));\n}\n\n// TODO : Add an event handler for handling when the target control changes it's min virtual size, we might need to show/hide the scrollbars if that happens.\n\n//\n// Scrollpane - Target changed it's virtual size.\n//\nstatic int EZ_scrollpane_OnTargetVirtualResize(ez_control_t *self, void *payload, void *ext_event_info)\n{\n\tez_scrollpane_t *scrollpane = NULL;\n\n\tif (!self->parent || (self->parent->CLASS_ID != EZ_SCROLLPANE_ID))\n\t{\n\t\tSys_Error(\"EZ_scrollpane_OnTargetVirtualResize(): Target parent is not a scrollpane!.\\n\");\n\t}\n\n\tscrollpane = (ez_scrollpane_t *)self->parent;\n\n\t// Check if we should show/hide the scrollbars.\n\tEZ_scrollpane_DetermineScrollbarVisibility(scrollpane);\n\n\treturn 0;\n}\n\n//\n// Scrollpane - Creates a new Scrollpane and initializes it.\n//\nez_scrollpane_t *EZ_scrollpane_Create(ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\n{\n\tez_scrollpane_t *scrollpane = NULL;\n\n\t// We have to have a tree to add the control to.\n\tif (!tree)\n\t{\n\t\treturn NULL;\n\t}\n\n\tscrollpane = (ez_scrollpane_t *)Q_malloc(sizeof(ez_scrollpane_t));\n\tmemset(scrollpane, 0, sizeof(ez_scrollpane_t));\n\n\tEZ_scrollpane_Init(scrollpane, tree, parent, name, description, x, y, width, height, flags);\n\t\n\treturn scrollpane;\n}\n\n//\n// Scrollpane - Initializes a Scrollpane.\n//\nvoid EZ_scrollpane_Init(ez_scrollpane_t *scrollpane, ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\n{\n\tint rh_size_h = 0;\n\tint rh_size_v = 0;\n\tez_control_t *scrollpane_ctrl = (ez_control_t *)scrollpane;\n\n\t// Initialize the inherited class first.\n\tEZ_control_Init(&scrollpane->super, tree, parent, name, description, x, y, width, height, flags);\n\n\t// Initilize the button specific stuff.\n\t((ez_control_t *)scrollpane)->CLASS_ID\t= EZ_SCROLLPANE_ID;\n\t((ez_control_t *)scrollpane)->ext_flags\t|= (flags | control_focusable | control_contained | control_resizeable);\n\n\t// Override control events.\n\tCONTROL_REGISTER_EVENT(scrollpane, EZ_scrollpane_OnResize, OnResize, ez_control_t);\n\n\t// Scrollpane specific events.\n\tCONTROL_REGISTER_EVENT(scrollpane, EZ_scrollpane_OnTargetChanged, OnTargetChanged, ez_scrollpane_t);\n\tCONTROL_REGISTER_EVENT(scrollpane, EZ_scrollpane_OnScrollbarThicknessChanged, OnScrollbarThicknessChanged, ez_scrollpane_t);\n\n\tscrollpane->scrollbar_thickness = 10;\n\n\trh_size_h = (scrollpane_ctrl->ext_flags & control_resize_h) ? scrollpane_ctrl->resize_handle_thickness : 0;\n\trh_size_v = (scrollpane_ctrl->ext_flags & control_resize_v) ? scrollpane_ctrl->resize_handle_thickness : 0;\n\n\tEZ_control_SetMinVirtualSize(scrollpane_ctrl, 1, 1);\n\n\tscrollpane->int_flags |= (show_h_scrollbar | show_v_scrollbar);\n\n\t// Create vertical scrollbar.\n\t{\n\t\tscrollpane->v_scrollbar = EZ_scrollbar_Create(tree, scrollpane_ctrl, \"Vertical scrollbar\", \"\", 0, 0, 10, 10, control_anchor_viewport);\n\t\tEZ_control_SetVisible((ez_control_t *)scrollpane->v_scrollbar, true);\n\t\tEZ_control_SetPosition((ez_control_t *)scrollpane->v_scrollbar, -rh_size_h, rh_size_v);\n\t\tEZ_control_SetAnchor((ez_control_t *)scrollpane->v_scrollbar, (anchor_top | anchor_bottom | anchor_right));\n\n\t\tEZ_scrollbar_SetTargetIsParent(scrollpane->v_scrollbar, false);\n \n\t\tEZ_control_AddChild(scrollpane_ctrl, (ez_control_t *)scrollpane->v_scrollbar);\n\t}\n\n\t// Create horizontal scrollbar.\n\t{\n\t\tscrollpane->h_scrollbar = EZ_scrollbar_Create(tree, (ez_control_t *)scrollpane, \"Horizontal scrollbar\", \"\", 0, 0, 10, 10, control_anchor_viewport);\n\t\n\t\tEZ_control_SetVisible((ez_control_t *)scrollpane->h_scrollbar, true);\n\t\tEZ_control_SetPosition((ez_control_t *)scrollpane->h_scrollbar, rh_size_h, -rh_size_v);\n\t\tEZ_control_SetAnchor((ez_control_t *)scrollpane->h_scrollbar, (anchor_left | anchor_bottom | anchor_right));\n\n\t\tEZ_scrollbar_SetTargetIsParent(scrollpane->h_scrollbar, false);\n\t\tEZ_scrollbar_SetIsVertical(scrollpane->h_scrollbar, false);\n\n\t\tEZ_control_AddChild(scrollpane_ctrl, (ez_control_t *)scrollpane->h_scrollbar);\n\t}\n\n\tEZ_scrollpane_ResizeScrollbars(scrollpane);\n}\n\n//\n// Scrollpane - Destroys the scrollpane.\n//\nint EZ_scrollpane_Destroy(ez_control_t *self, qbool destroy_children)\n{\n\tez_scrollpane_t *scrollpane = (ez_scrollpane_t *)self;\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnDestroy, destroy_children);\n\n\tEZ_eventhandler_Remove(scrollpane->event_handlers.OnScrollbarThicknessChanged, NULL, true);\n\tEZ_eventhandler_Remove(scrollpane->event_handlers.OnTargetChanged, NULL, true);\n\n\tEZ_control_Destroy(self, destroy_children);\n\n\treturn 0;\n}\n\n//\n// Scrollpane - Always show the vertical scrollbar.\n//\nvoid EZ_scrollpane_SetAlwaysShowVerticalScrollbar(ez_scrollpane_t *scrollpane, qbool always_show)\n{\n\tSET_FLAG(scrollpane->ext_flags, always_v_scrollbar, always_show);\n}\n\n//\n// Scrollpane - Always show the horizontal scrollbar.\n//\nvoid EZ_scrollpane_SetAlwaysShowHorizontalScrollbar(ez_scrollpane_t *scrollpane, qbool always_show)\n{\n\tSET_FLAG(scrollpane->ext_flags, always_h_scrollbar, always_show);\n}\n\n//\n// Scrollpane - Set the target control of the scrollpane (the one to be scrolled).\n//\nvoid EZ_scrollpane_SetTarget(ez_scrollpane_t *scrollpane, ez_control_t *target)\n{\n\tscrollpane->prev_target = scrollpane->target;\n\tscrollpane->target = target;\n\tCONTROL_RAISE_EVENT(NULL, scrollpane, ez_scrollpane_t, OnTargetChanged, NULL);\n}\n\n//\n// Scrollpane - Set the thickness of the scrollbar controls.\n//\nvoid EZ_scrollpane_SetScrollbarThickness(ez_scrollpane_t *scrollpane, int scrollbar_thickness)\n{\n\tscrollpane->scrollbar_thickness = scrollbar_thickness;\n\tCONTROL_RAISE_EVENT(NULL, scrollpane, ez_scrollpane_t, OnScrollbarThicknessChanged, NULL);\n}\n\n//\n// Scrollpane - OnResize event.\n//\nint EZ_scrollpane_OnResize(ez_control_t *self, void *ext_event_info)\n{\n\tez_scrollpane_t *scrollpane = (ez_scrollpane_t *)self;\n\n\tEZ_control_OnResize(self, NULL);\n\n\t// Make sure we don't listen to resizing events of the target that we raised\n\t// ourselves, it causes an infinite loop.\n\tif (!(scrollpane->int_flags & resizing_target))\n\t{\n\t\t// Resize the scrollbars and target based on the state of the target.\n\t\tEZ_scrollpane_ResizeScrollbars(scrollpane);\n\t}\n\t\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnResize, NULL);\n\treturn 0;\n}\n\n//\n// Scrollpane - The target control changed.\n//\nint EZ_scrollpane_OnTargetChanged(ez_control_t *self, void *ext_event_info)\n{\n\tez_scrollpane_t *scrollpane = (ez_scrollpane_t *)self;\n\n\t// Clean up the old target.\n\tif (scrollpane->prev_target)\n\t{\n\t\tEZ_eventhandler_Remove(scrollpane->prev_target->event_handlers.OnVirtualResize, EZ_scrollpane_OnTargetVirtualResize, false);\n\t\tEZ_control_RemoveChild(self, scrollpane->target);\n\t\tEZ_control_SetMovesParent(scrollpane->prev_target, false);\n\t}\n\n\t// Set the new target for the scrollbars so they know what to scroll.\n\tEZ_scrollbar_SetTarget(scrollpane->v_scrollbar, scrollpane->target);\n\tEZ_scrollbar_SetTarget(scrollpane->h_scrollbar, scrollpane->target);\n\n\tif (scrollpane->target)\n\t{\n\t\t// Subscribe to the targets resize and scroll events.\n\t\tEZ_control_AddOnVirtualResize(scrollpane->target, EZ_scrollpane_OnTargetVirtualResize, scrollpane);\n\t\tEZ_control_AddChild(self, scrollpane->target);\n\n\t\tEZ_control_SetScrollable(scrollpane->target, true);\n\t\tEZ_control_SetMovable(scrollpane->target, true);\n\t\tEZ_control_SetResizeableBoth(scrollpane->target, true);\n\t\tEZ_control_SetResizeable(scrollpane->target, true);\n\n\t\t// Reposition the target inside the scrollpane.\n\t\tEZ_control_SetPosition(scrollpane->target, 0, 0);\n\t\tEZ_control_SetSize(scrollpane->target, (self->width - scrollpane->scrollbar_thickness), (self->height - scrollpane->scrollbar_thickness));\n\t\tEZ_control_SetAnchor(scrollpane->target, (anchor_left | anchor_right | anchor_top | anchor_bottom));\n\n\t\t// Make sure the target is drawn infront of the scrollpane.\n\t\tEZ_control_SetDrawOrder(scrollpane->target, ((ez_control_t *)scrollpane)->draw_order + 1, true);\n\n\t\t// When moving the target move the scrollpane with it\n\t\t// and don't allow moving the target inside of the scrollpane.\n\t\tEZ_control_SetMovesParent(scrollpane->target, true);\n\n\t\t// Resize the scrollbars / target to fit properly.\n\t\tEZ_scrollpane_DetermineScrollbarVisibility(scrollpane);\n\t\tEZ_scrollpane_ResizeScrollbars(scrollpane);\n\t\tEZ_scrollpane_AdjustTargetSize(scrollpane);\n\n\t\tCONTROL_RAISE_EVENT(NULL, (ez_control_t *)scrollpane->target, ez_control_t, OnResize, NULL);\n\t\tCONTROL_RAISE_EVENT(NULL, (ez_control_t *)scrollpane->target, ez_control_t, OnMove, NULL);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, scrollpane, ez_scrollpane_t, OnTargetChanged, NULL);\n\treturn 0;\n}\n\n//\n// Scrollpane - The scrollbar thickness changed.\n//\nint EZ_scrollpane_OnScrollbarThicknessChanged(ez_control_t *self, void *ext_event_info)\n{\n\tez_scrollpane_t *scrollpane = (ez_scrollpane_t *)self;\n\n\t// Resize the scrollbars and target with the new scrollbar thickness.\n\tEZ_scrollpane_ResizeScrollbars(scrollpane);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, scrollpane, ez_scrollpane_t, OnScrollbarThicknessChanged, NULL);\n\treturn 0;\n}\n\n\n"
  },
  {
    "path": "src/ez_scrollpane.h",
    "content": "\n#ifndef __EZ_SCROLLPANE_H__\n#define __EZ_SCROLLPANE_H__\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_scrollpane.h,v 1.55 2007-10-27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"ez_controls.h\"\n#include \"ez_scrollbar.h\"\n\n// =========================================================================================\n// Scrollpane\n// =========================================================================================\n\ntypedef enum ez_scrollpane_flags_e\n{\n\talways_v_scrollbar\t= (1 << 0),\n\talways_h_scrollbar\t= (1 << 1)\n} ez_scrollpane_flags_t;\t\t\n\ntypedef enum ez_scrollpane_iflags_e\n{\n\tshow_v_scrollbar\t\t= (1 << 0),\t// Should the vertical scrollbar be shown?\n\tshow_h_scrollbar\t\t= (1 << 1),\t// Should the horizontal scrollbar be shown?\n\tresizing_target\t\t\t= (1 << 2)\t// Are we resizing the target (if we are, don't care about OnResize events, it'll cause an infinite loop).\n} ez_scrollpane_iflags_t;\n\ntypedef struct ez_scrollpane_eventcount_s\n{\n\tint OnTargetChanged;\n\tint OnScrollbarThicknessChanged;\n} ez_scrollpane_eventcount_t;\n\ntypedef struct ez_scrollpane_events_s\n{\n\tez_event_fp\tOnTargetChanged;\n\tez_event_fp\tOnTargetResize;\n\tez_event_fp\tOnTargetScroll;\n\tez_event_fp\tOnScrollbarThicknessChanged;\n} ez_scrollpane_events_t;\n\ntypedef struct ez_scrollpane_eventhandlers_s\n{\n\tez_eventhandler_t\t\t*OnTargetChanged;\n\tez_eventhandler_t\t\t*OnTargetResize;\n\tez_eventhandler_t\t\t*OnTargetScroll;\n\tez_eventhandler_t\t\t*OnScrollbarThicknessChanged;\n} ez_scrollpane_eventhandlers_t;\n\ntypedef struct ez_scrollpane_s\n{\n\tez_control_t\t\t\t\tsuper;\t\t\t\t// The super class.\n\n\tez_scrollpane_events_t\t\tevents;\n\tez_scrollpane_eventhandlers_t event_handlers;\n\tez_scrollpane_eventcount_t\tinherit_levels;\n\tez_scrollpane_eventcount_t\toverride_counts;\n\n\tez_control_t\t\t\t\t*target;\t\t\t// The target of the scrollbar if the parent isn't targeted (ext_flag & target_parent).\n\tez_control_t\t\t\t\t*prev_target;\t\t// The previous target control. We keep this to clean up when we change target.\n\n\tez_scrollbar_t\t\t\t\t*v_scrollbar;\t\t// The vertical scrollbar.\n\tez_scrollbar_t\t\t\t\t*h_scrollbar;\t\t// The horizontal scrollbar.\n\n\tint\t\t\t\t\t\t\tscrollbar_thickness; // The thickness of the scrollbars.\n\n\tez_scrollpane_flags_t\t\text_flags;\t\t\t// External flags for the scrollbar.\n\tez_scrollpane_iflags_t\t\tint_flags;\t\t\t// The internal flags for the scrollbar.\n} ez_scrollpane_t;\n\n//\n// Scrollpane - Creates a new Scrollpane and initializes it.\n//\nez_scrollpane_t *EZ_scrollpane_Create(ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\n\n//\n// Scrollpane - Initializes a Scrollpane.\n//\nvoid EZ_scrollpane_Init(ez_scrollpane_t *scrollpane, ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\n\n//\n// Scrollpane - Destroys the scrollpane.\n//\nint EZ_scrollpane_Destroy(ez_control_t *self, qbool destroy_children);\n\n//\n// Scrollpane - Set the target control of the scrollpane (the one to be scrolled).\n//\nvoid EZ_scrollpane_SetTarget(ez_scrollpane_t *scrollpane, ez_control_t *target);\n\n//\n// Scrollpane - Set the thickness of the scrollbar controls.\n//\nvoid EZ_scrollpane_SetScrollbarThickness(ez_scrollpane_t *scrollpane, int scrollbar_thickness);\n\n//\n// Scrollpane - The target control changed.\n//\nint EZ_scrollpane_OnTargetChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Scrollpane - The scrollbar thickness changed.\n//\nint EZ_scrollpane_OnScrollbarThicknessChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Scrollpane - OnResize event.\n//\nint EZ_scrollpane_OnResize(ez_control_t *self, void *ext_event_info);\n\n#endif // __EZ_SCROLLPANE_H__\n"
  },
  {
    "path": "src/ez_slider.c",
    "content": "\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_scrollpane.c,v 1.78 2007/10/27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"keys.h\"\n#include \"utils.h\"\n#include \"common_draw.h\"\n#include \"ez_slider.h\"\n\n// =========================================================================================\n// Slider\n// =========================================================================================\n\n// TODO : Slider - Show somehow that it's focused?\n\n//\n// Slider - Creates a new button and initializes it.\n//\nez_slider_t *EZ_slider_Create(ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\n{\n\tez_slider_t *slider = NULL;\n\n\t// We have to have a tree to add the control to.\n\tif (!tree)\n\t{\n\t\treturn NULL;\n\t}\n\n\tslider = (ez_slider_t *)Q_malloc(sizeof(ez_slider_t));\n\tmemset(slider, 0, sizeof(ez_slider_t));\n\n\tEZ_slider_Init(slider, tree, parent, name, description, x, y, width, height, flags);\n\t\n\treturn slider;\n}\n\n//\n// Slider - Initializes a button.\n//\nvoid EZ_slider_Init(ez_slider_t *slider, ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\n{\n\theight = max(height, 8);\n\n\t// Initialize the inherited class first.\n\tEZ_control_Init(&slider->super, tree, parent, name, description, x, y, width, height, flags);\n\n\t// Initilize the button specific stuff.\n\t((ez_control_t *)slider)->CLASS_ID\t\t\t\t\t= EZ_SLIDER_ID;\n\t((ez_control_t *)slider)->ext_flags\t\t\t\t\t|= (flags | control_focusable | control_contained | control_resizeable);\n\n\t// Overrided control events.\n\tCONTROL_REGISTER_EVENT(slider, EZ_slider_OnDraw, OnDraw, ez_control_t);\n\tCONTROL_REGISTER_EVENT(slider, EZ_slider_OnMouseDown, OnMouseDown, ez_control_t);\n\tCONTROL_REGISTER_EVENT(slider, EZ_slider_OnMouseUpOutside, OnMouseUpOutside, ez_control_t);\n\tCONTROL_REGISTER_EVENT(slider, EZ_slider_OnMouseEvent, OnMouseEvent, ez_control_t);\n\tCONTROL_REGISTER_EVENT(slider, EZ_slider_OnResize, OnResize, ez_control_t);\n\tCONTROL_REGISTER_EVENT(slider, EZ_slider_OnKeyDown, OnKeyDown, ez_control_t);\n\n\t// Slider specific events.\n\tCONTROL_REGISTER_EVENT(slider, EZ_slider_OnSliderPositionChanged, OnSliderPositionChanged, ez_slider_t);\n\tCONTROL_REGISTER_EVENT(slider, EZ_slider_OnMaxValueChanged, OnMaxValueChanged, ez_slider_t);\n\tCONTROL_REGISTER_EVENT(slider, EZ_slider_OnMinValueChanged, OnMinValueChanged, ez_slider_t);\n\tCONTROL_REGISTER_EVENT(slider, EZ_slider_OnScaleChanged, OnScaleChanged, ez_slider_t);\n\n\t// Set default values.\n\tEZ_slider_SetMax(slider, 100);\n\tEZ_slider_SetScale(slider, 1.0);\n\tEZ_slider_SetPosition(slider, 0);\n}\n\n//\n// Slider - Calculates the actual slider position.\n//\nstatic inline void EZ_slider_CalculateRealSliderPos(ez_slider_t *slider)\n{\n\tint pos = slider->slider_pos - slider->min_value;\n\n\t// Calculate the real position of the slider by multiplying by the gap size between each value.\n\t// (Don't start drawing at the exact start cause that would overwrite the edge marker)\n\tslider->real_slider_pos = Q_rint((slider->scaled_char_size / 2.0) + (pos * slider->gap_size));\n}\n\n//\n// Slider - Calculates the gap size between values.\n//\nstatic inline void EZ_slider_CalculateGapSize(ez_slider_t *slider)\n{\n\tslider->range = abs(slider->max_value - slider->min_value);\n\n\t// Calculate the gap between each value in pixels (floating point so that we don't get rounding errors).\n\t// Don't include the first and last characters in the calculation, the slider is not allowed to move over those.\n\tslider->gap_size = ((float)(((ez_control_t *)slider)->width - (2 * slider->scaled_char_size)) / (float)slider->range);\n}\n\n//\n// Slider - Event handler for OnSliderPositionChanged.\n//\nvoid EZ_slider_AddOnSliderPositionChanged(ez_slider_t *slider, ez_eventhandler_fp OnSliderPositionChanged, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(slider, EZ_CONTROL_HANDLER, OnSliderPositionChanged, ez_slider_t, OnSliderPositionChanged, payload);\n\tCONTROL_RAISE_EVENT(NULL, slider, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Slider - Event handler for OnMaxValueChanged.\n//\nvoid EZ_slider_AddOnMaxValueChanged(ez_slider_t *slider, ez_eventhandler_fp OnMaxValueChanged, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(slider, EZ_CONTROL_HANDLER, OnMaxValueChanged, ez_slider_t, OnMaxValueChanged, payload);\n\tCONTROL_RAISE_EVENT(NULL, slider, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Slider - Event handler for OnMinValueChanged.\n//\nvoid EZ_slider_AddOnMinValueChanged(ez_slider_t *slider, ez_eventhandler_fp OnMinValueChanged, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(slider, EZ_CONTROL_HANDLER, OnMinValueChanged, ez_slider_t, OnMinValueChanged, payload);\n\tCONTROL_RAISE_EVENT(NULL, slider, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Slider - Event handler for OnSliderPositionChanged.\n//\nvoid EZ_slider_AddOnScaleChanged(ez_slider_t *slider, ez_eventhandler_fp OnScaleChanged, void *payload)\n{\n\tCONTROL_ADD_EVENTHANDLER(slider, EZ_CONTROL_HANDLER, OnScaleChanged, ez_slider_t, OnScaleChanged, payload);\n\tCONTROL_RAISE_EVENT(NULL, slider, ez_control_t, OnEventHandlerChanged, NULL);\n}\n\n//\n// Slider - Gets the current position of the mouse in slider scale. (This does not care if the mouse is within the control)\n//\nint EZ_slider_GetPositionFromMouse(ez_slider_t *slider, float mouse_x, float mouse_y)\n{\n\tez_control_t *self = (ez_control_t *)slider;\n\tint x = Q_rint(self->absolute_virtual_x + (slider->scaled_char_size / 2.0));\n\treturn Q_rint((mouse_x - x) / slider->gap_size);\n}\n\n//\n// Slider - Get the current slider position.\n//\nint EZ_slider_GetPosition(ez_slider_t *slider)\n{\n\treturn slider->slider_pos;\n}\n\n//\n// Slider - Set the slider position.\n//\nvoid EZ_slider_SetPosition(ez_slider_t *slider, int slider_pos)\n{\n\tclamp(slider_pos, slider->min_value, slider->max_value);\n\n\tslider->slider_pos = slider_pos;\n\n\tCONTROL_RAISE_EVENT(NULL, slider, ez_slider_t, OnSliderPositionChanged, NULL);\n}\n\n//\n// Slider - Set the max slider value.\n//\nvoid EZ_slider_SetMax(ez_slider_t *slider, int max_value)\n{\n\tslider->max_value = max_value;\n\n\tCONTROL_RAISE_EVENT(NULL, slider, ez_slider_t, OnMaxValueChanged, NULL);\n}\n\n//\n// Slider - Set the max slider value.\n//\nvoid EZ_slider_SetMin(ez_slider_t *slider, int min_value)\n{\n\tslider->min_value = min_value;\n\n\tCONTROL_RAISE_EVENT(NULL, slider, ez_slider_t, OnMinValueChanged, NULL);\n}\n\n//\n// Slider - Set the scale of the slider characters.\n//\nvoid EZ_slider_SetScale(ez_slider_t *slider, float scale)\n{\n\tslider->scale = max(0.1, scale);\n\n\tCONTROL_RAISE_EVENT(NULL, slider, ez_slider_t, OnScaleChanged, NULL);\n}\n\n//\n// Slider - Scale changed.\n//\nint EZ_slider_OnScaleChanged(ez_control_t *self, void *ext_event_info)\n{\n\tez_slider_t *slider = (ez_slider_t *)self;\n\n\t// Calculate the new character size.\n\tslider->scaled_char_size = slider->scale * 8;\n\n\t// Refit the control to fit the slider.\n\tEZ_control_SetSize(self, slider->super.width + slider->scaled_char_size, slider->scaled_char_size);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, slider, ez_slider_t, OnScaleChanged, NULL);\n\n\treturn 0;\n}\n\n//\n// Slider - Draw function for the slider.\n//\nint EZ_slider_OnDraw(ez_control_t *self, void *ext_event_info)\n{\n\tint x, y, i;\n\tez_slider_t *slider = (ez_slider_t *)self;\n\n\tEZ_control_GetDrawingPosition(self, &x, &y);\n\n\t// Draw the background.\n\t{\n\t\t// Left edge.\n\t\tDraw_SCharacter(x, y, 128, slider->scale);\n\n\t\tfor (i = 1; i < Q_rint((float)(self->width - slider->scaled_char_size) / slider->scaled_char_size); i++)\n\t\t{\n\t\t\tDraw_SCharacter(x + (i * slider->scaled_char_size), y, 129, slider->scale);\n\t\t}\n\n\t\t// Right edge.\n\t\tDraw_SCharacter(x + (i * slider->scaled_char_size), y, 130, slider->scale);\n\t}\n\n\t// Slider.\n\tDraw_SCharacter(x + slider->real_slider_pos, y, 131, slider->scale);\n\n\treturn 0;\n}\n\n//\n// Slider - The max value changed.\n//\nint EZ_slider_OnMaxValueChanged(ez_control_t *self, void *ext_event_info)\n{\n\tez_slider_t *slider = (ez_slider_t *)self;\n\n\t// Only allow positive values greater than the min value.\n\tslider->max_value = max(0, max(slider->max_value, slider->min_value));\n\n\t// Calculate the gap between each slider value.\n\tEZ_slider_CalculateGapSize(slider);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, slider, ez_slider_t, OnMaxValueChanged, NULL);\n\n\treturn 0;\n}\n\n//\n// Slider - The min value changed.\n//\nint EZ_slider_OnMinValueChanged(ez_control_t *self, void *ext_event_info)\n{\n\tez_slider_t *slider = (ez_slider_t *)self;\n\n\t// Only allow positive values less than the max value.\n\tslider->min_value = max(0, min(slider->max_value, slider->min_value));\n\n\t// Calculate the gap between each slider value.\n\tEZ_slider_CalculateGapSize(slider);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, slider, ez_slider_t, OnMinValueChanged, NULL);\n\n\treturn 0;\n}\n\n//\n// Slider - The slider position changed.\n//\nint EZ_slider_OnSliderPositionChanged(ez_control_t *self, void *ext_event_info)\n{\n\tez_slider_t *slider = (ez_slider_t *)self;\n\n\t// Recalculate the drawing position.\n\tEZ_slider_CalculateRealSliderPos(slider);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, slider, ez_slider_t, OnSliderPositionChanged, NULL);\n\n\treturn 0;\n}\n\n//\n// Slider - Jumps to the point that was clicked on the slider \n//          (Normal behavior is to make a \"big step\" when clicking the slider outside the handle).\n//\nvoid EZ_slider_SetJumpToClick(ez_slider_t *slider, qbool jump_to_click)\n{\n\tSET_FLAG(slider->ext_flags, slider_jump_to_click, jump_to_click);\n\tCONTROL_RAISE_EVENT(NULL, (ez_control_t *)slider, ez_control_t, OnFlagsChanged, NULL);\n}\n\n//\n// Slider - Mouse down event.\n//\nint EZ_slider_OnMouseDown(ez_control_t *self, mouse_state_t *ms)\n{\n\tez_slider_t *slider\t\t= (ez_slider_t *)self;\n\tint big_step\t\t\t= max(1, slider->range / 10);\n\tint slider_left_edge; \n\tint slider_right_edge;\n\tint x, y;\n\t\n\tEZ_control_GetDrawingPosition(self, &x, &y);\n\n\tslider_left_edge\t= (x + slider->real_slider_pos);\n\tslider_right_edge\t= (x + slider->real_slider_pos + slider->scaled_char_size);\n\n\t// Super class.\n\tEZ_control_OnMouseDown(self, ms);\n\n\tif ((ms->x >= slider_left_edge) && (ms->x <= slider_right_edge))\n\t{\n\t\tslider->int_flags |= slider_dragging;\n\t}\n\telse if (slider->ext_flags & slider_jump_to_click)\n\t{\n\t\t// Jump to the exact position on the slider that was clicked.\n\t\tint newpos = EZ_slider_GetPositionFromMouse(slider, ms->x, ms->y);\n\t\tEZ_slider_SetPosition(slider, newpos);\n\t}\n\telse if (ms->x < slider_left_edge)\n\t{\n\t\t// When clicking on the slider (but not the slider handle)\n\t\t// make a \"big step\" instead of dragging it.\n\t\tEZ_slider_SetPosition(slider, slider->slider_pos - big_step);\t\t\n\t}\n\telse if (ms->x > slider_right_edge)\n\t{\n\t\tEZ_slider_SetPosition(slider, slider->slider_pos + big_step);\n\t}\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnMouseDown, ms);\n\n\treturn true;\n}\n\n//\n// Slider - Mouse up event.\n//\nint EZ_slider_OnMouseUpOutside(ez_control_t *self, mouse_state_t *ms)\n{\n\tez_slider_t *slider = (ez_slider_t *)self;\n\n\t// Super class.\n\tEZ_control_OnMouseUpOutside(self, ms);\n\n\t// Not dragging anymore.\n\tslider->int_flags &= ~slider_dragging;\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnMouseUp, ms);\n\n\treturn true;\n}\n\n//\n// Slider - Handles a mouse event.\n//\nint EZ_slider_OnMouseEvent(ez_control_t *self, mouse_state_t *ms)\n{\n\t// Make sure we handle all mouse events when we're over the control\n\t// otherwise they will fall through to controls below.\n\tint mouse_handled\t= POINT_IN_CONTROL_RECT(self, ms->x, ms->y); \n\tez_slider_t *slider = (ez_slider_t *)self;\n\n\t// Call the super class first.\n\tEZ_control_OnMouseEvent(self, ms);\n\t\n\tif (slider->int_flags & slider_dragging)\n\t{\n\t\tint new_slider_pos = slider->min_value + Q_rint((ms->x - self->absolute_x) / slider->gap_size);\n\t\tEZ_slider_SetPosition(slider, new_slider_pos);\n\t\tmouse_handled = true;\n\t}\n\t\n\t// Event handler call.\n\t{\n\t\tint mouse_handled_tmp = false;\n\t\tCONTROL_EVENT_HANDLER_CALL(&mouse_handled_tmp, self, ez_control_t, OnMouseEvent, ms);\n\t\tmouse_handled = (mouse_handled || mouse_handled_tmp);\n\t}\n\n\treturn mouse_handled;\n}\n\n//\n// Slider - The slider was resized.\n//\nint EZ_slider_OnResize(ez_control_t *self, void *ext_event_info)\n{\n\tez_slider_t *slider = (ez_slider_t *)self;\n\n\t// Call super class.\n\tEZ_control_OnResize(self, NULL);\n\n\tEZ_slider_CalculateGapSize(slider);\n\tEZ_slider_CalculateRealSliderPos(slider);\n\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnResize, NULL);\n\n\treturn 0;\n}\n\n//\n// Slider - Key event.\n//\nint EZ_slider_OnKeyDown(ez_control_t *self, int key, int unichar)\n{\n\tqbool key_handled\t= false;\n\tez_slider_t *slider = (ez_slider_t *)self;\n\tint big_step\t\t= max(1, slider->max_value / 10);\n\tint step\t\t\t= keydown[K_CTRL] ? big_step : 1;\n\n\tswitch(key)\n\t{\n\t\tcase K_RIGHTARROW :\n\t\t{\n\t\t\tEZ_slider_SetPosition(slider, slider->slider_pos + step);\n\t\t\tkey_handled = true;\n\t\t\tbreak;\n\t\t}\n\t\tcase K_LEFTARROW :\n\t\t{\n\t\t\tEZ_slider_SetPosition(slider, slider->slider_pos - step);\n\t\t\tkey_handled = true;\n\t\t\tbreak;\n\t\t}\n\t\tdefault :\n\t\t{\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn key_handled;\n}\n\n\n"
  },
  {
    "path": "src/ez_slider.h",
    "content": "\n#ifndef __EZ_SLIDER_H__\n#define __EZ_SLIDER_H__\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_slider.h,v 1.55 2007-10-27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"ez_controls.h\"\n\n// =========================================================================================\n// Slider\n// =========================================================================================\n\ntypedef enum ez_slider_iflags_e\n{\n\tslider_dragging\t= (1 << 0)\n} ez_slider_iflags_t;\n\ntypedef enum ez_slider_flags_e\n{\n\tslider_jump_to_click = (1 << 0)\n} ez_slider_flags_t;\n\ntypedef struct ez_slider_eventcount_s\n{\n\tint\tOnSliderPositionChanged;\n\tint\tOnMaxValueChanged;\n\tint\tOnMinValueChanged;\n\tint\tOnScaleChanged;\n} ez_slider_eventcount_t;\n\ntypedef struct ez_slider_events_s\n{\n\tez_event_fp\tOnSliderPositionChanged;\n\tez_event_fp\tOnMaxValueChanged;\n\tez_event_fp\tOnMinValueChanged;\n\tez_event_fp\tOnScaleChanged;\n} ez_slider_events_t;\n\ntypedef struct ez_slider_eventhandlers_s\n{\n\tez_eventhandler_t\t\t*OnSliderPositionChanged;\n\tez_eventhandler_t\t\t*OnMaxValueChanged;\n\tez_eventhandler_t\t\t*OnMinValueChanged;\n\tez_eventhandler_t\t\t*OnScaleChanged;\n} ez_slider_eventhandlers_t;\n\ntypedef struct ez_slider_s\n{\n\tez_control_t\t\t\tsuper;\t\t\t\t// The super class.\n\n\tez_slider_events_t\t\tevents;\t\t\t\t// Slider specific events.\n\tez_slider_eventhandlers_t event_handlers;\t// Slider specific event handlers.\n\tez_slider_eventcount_t\tinherit_levels;\n\tez_slider_eventcount_t\toverride_counts;\n\n\tez_slider_flags_t\t\text_flags;\t\t\t// External slider flags.\n\tez_slider_iflags_t\t\tint_flags;\t\t\t// Slider specific internal flags.\n\tint\t\t\t\t\t\tslider_pos;\t\t\t// The position of the slider.\n\tint\t\t\t\t\t\treal_slider_pos;\t// The actual slider position in pixels.\n\tint\t\t\t\t\t\tmax_value;\t\t\t// The max value allowed for the slider.\n\tint\t\t\t\t\t\tmin_value;\t\t\t// The min value allowed for the slider.\n\tint\t\t\t\t\t\trange;\t\t\t\t// The number of values between the min and max values.\n\tfloat\t\t\t\t\tgap_size;\t\t\t// The pixel gap between each value.\n\tfloat\t\t\t\t\tscale;\t\t\t\t// The scale of the characters used for drawing the slider.\n\tint\t\t\t\t\t\tscaled_char_size;\t// The scaled character size in pixels after applying scale to the slider chars.\n\n\tint\t\t\t\t\t\toverride_count;\t\t// These are needed so that subclasses can override slider specific events.\n\tint\t\t\t\t\t\tinheritance_level;\n} ez_slider_t;\n\n//\n// Slider - Creates a new slider and initializes it.\n//\nez_slider_t *EZ_slider_Create(ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\n\n//\n// Slider - Initializes a slider.\n//\nvoid EZ_slider_Init(ez_slider_t *slider, ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\n\n//\n// Slider - Event handler for OnSliderPositionChanged.\n//\nvoid EZ_slider_AddOnSliderPositionChanged(ez_slider_t *slider, ez_eventhandler_fp OnSliderPositionChanged, void *payload);\n\n//\n// Slider - Event handler for OnSliderPositionChanged.\n//\nvoid EZ_slider_AddOnScaleChanged(ez_slider_t *slider, ez_eventhandler_fp OnScaleChanged, void *payload);\n\n//\n// Slider - Event handler for OnMaxValueChanged.\n//\nvoid EZ_slider_AddOnMaxValueChanged(ez_slider_t *slider, ez_eventhandler_fp OnMaxValueChanged, void *payload);\n\n//\n// Slider - Event handler for OnMinValueChanged.\n//\nvoid EZ_slider_AddOnMinValueChanged(ez_slider_t *slider, ez_eventhandler_fp OnMinValueChanged, void *payload);\n\n//\n// Slider - Gets the current position of the mouse in slider scale. (This does not care if the mouse is within the control)\n//\nint EZ_slider_GetPositionFromMouse(ez_slider_t *slider, float mouse_x, float mouse_y);\n\n//\n// Slider - Get the current slider position.\n//\nint EZ_slider_GetPosition(ez_slider_t *slider);\n\n//\n// Slider - Set the slider position.\n//\nvoid EZ_slider_SetPosition(ez_slider_t *slider, int slider_pos);\n\n//\n// Slider - Set the max slider value.\n//\nvoid EZ_slider_SetMax(ez_slider_t *slider, int max_value);\n\n//\n// Slider - Set the max slider value.\n//\nvoid EZ_slider_SetMin(ez_slider_t *slider, int min_value);\n\n//\n// Slider - Set the scale of the slider characters.\n//\nvoid EZ_slider_SetScale(ez_slider_t *slider, float scale);\n\n//\n// Slider - Draw function for the slider.\n//\nint EZ_slider_OnDraw(ez_control_t *self, void *ext_event_info);\n\n//\n// Slider - The max value changed.\n//\nint EZ_slider_OnMaxValueChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Slider - The min value changed.\n//\nint EZ_slider_OnMinValueChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Slider - Scale changed.\n//\nint EZ_slider_OnScaleChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Slider - The slider position changed.\n//\nint EZ_slider_OnSliderPositionChanged(ez_control_t *self, void *ext_event_info);\n\n//\n// Slider - Mouse down event.\n//\nint EZ_slider_OnMouseDown(ez_control_t *self, mouse_state_t *ms);\n\n//\n// Slider - Mouse up event.\n//\nint EZ_slider_OnMouseUpOutside(ez_control_t *self, mouse_state_t *ms);\n\n//\n// Slider - Handles a mouse event.\n//\nint EZ_slider_OnMouseEvent(ez_control_t *self, mouse_state_t *ms);\n\n//\n// Slider - The slider was resized.\n//\nint EZ_slider_OnResize(ez_control_t *self, void *ext_event_info);\n\n//\n// Slider - Key event.\n//\nint EZ_slider_OnKeyDown(ez_control_t *self, int key, int unichar);\n\n//\n// Slider - Jumps to the point that was clicked on the slider \n//          (Normal behavior is to make a \"big step\" when clicking the slider outside the handle).\n//\nvoid EZ_slider_SetJumpToClick(ez_slider_t *slider, qbool jump_to_click);\n\n#endif // __EZ_SLIDER_H__\n"
  },
  {
    "path": "src/ez_window.c",
    "content": "\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_window.c,v 1.78 2007/10/27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"keys.h\"\n#include \"utils.h\"\n#include \"common_draw.h\"\n#include \"ez_window.h\"\n\n#ifdef _MSC_VER\n#pragma warning( disable : 4189 )\n#endif\n\n// =========================================================================================\n// Window\n// =========================================================================================\n\n//\n// Window - Creates a new window and initializes it.\n//\nez_window_t *EZ_window_Create(ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\n{\n\tez_window_t *window = NULL;\n\n\t// We have to have a tree to add the control to.\n\tif (!tree)\n\t{\n\t\treturn NULL;\n\t}\n\n\twindow = (ez_window_t *)Q_malloc(sizeof(ez_window_t));\n\tmemset(window, 0, sizeof(ez_window_t));\n\n\tEZ_window_Init(window, tree, parent, name, description, x, y, width, height, flags);\n\t\n\treturn window;\n}\n\n//\n// Window - Initializes a window.\n//\nvoid EZ_window_Init(ez_window_t *window, ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags)\n{\n\tez_control_t *window_ctrl\t\t= (ez_control_t *)window;\n\tez_control_t *titlebar_ctrl\t\t= NULL;\n\tez_control_t *scrollpane_ctrl\t= NULL;\n\tint rh_size\t\t\t\t\t\t= 0;\n\n\t// Initialize the inherited class first.\n\tEZ_control_Init(&window->super, tree, parent, name, description, x, y, width, height, flags);\n\n\t((ez_control_t *)window)->CLASS_ID\t\t= EZ_WINDOW_ID;\n\t((ez_control_t *)window)->ext_flags\t\t|= (flags | control_focusable | control_contained | control_resizeable);\n\n\trh_size = window_ctrl->resize_handle_thickness;\n\n\t// Set the background.\n\tEZ_control_SetBackgroundImage(window_ctrl, EZ_WINDOW_DEFAULT_BACKGROUND_IMAGE);\n\tEZ_control_SetBackgroundImageOpacity(window_ctrl, 1.0);\n\tEZ_control_SetBackgroundImageEdgePercentage(window_ctrl, 20);\n\n\t// Title bar.\n\t{\n\t\t// Set the tilebar to move it's parent.\n\t\twindow->titlebar = EZ_control_Create(tree, window_ctrl, \"Window titlebar\", NULL, rh_size, rh_size, window_ctrl->width, 15, \n\t\t\t(control_focusable | control_move_parent | control_movable | control_resizeable | control_enabled | control_anchor_viewport));\n\n\t\ttitlebar_ctrl = (ez_control_t *)window->titlebar;\n\t\tEZ_control_SetBackgroundImage(titlebar_ctrl, \"gfx/ui/ez_titlebar\");\n\n\t\t// Set the size to fit within the resize handles.\n\t\tEZ_control_SetSize(titlebar_ctrl, (window_ctrl->width - (2 * rh_size)), 15);\n\t\tEZ_control_SetAnchor(titlebar_ctrl, (anchor_left | anchor_top | anchor_right));\n\n\t\tEZ_control_SetDrawOrder(titlebar_ctrl, window_ctrl->draw_order + 1, true);\n\n\t\t// Close button.\n\t\t{\n\t\t\t#define CLOSE_BUTTON_EDGE_GAP\t2\n\t\t\tint cb_sidelength = 0;\n\t\t\tez_control_t *close_ctrl = NULL;\n\t\t\twindow->close_button = EZ_button_Create(tree, titlebar_ctrl, \"Close button\", NULL, \n\t\t\t\t\t\t\t\t\t\t\t\t\t0, 0, 10, 10, control_enabled);\n\n\t\t\tclose_ctrl = (ez_control_t *)window->close_button;\n\n\t\t\tcb_sidelength = (titlebar_ctrl->height - (2 * CLOSE_BUTTON_EDGE_GAP));\n\n\t\t\t// Position the close button CLOSE_BUTTON_EDGE_GAP number of pixels from the edge\n\t\t\t// of the titlebar, and size it accordingly.\n\t\t\tEZ_control_SetPosition(close_ctrl, -CLOSE_BUTTON_EDGE_GAP, CLOSE_BUTTON_EDGE_GAP);\n\t\t\tEZ_control_SetSize(close_ctrl, cb_sidelength, cb_sidelength);\n\t\t\tEZ_control_SetAnchor(close_ctrl, (anchor_top | anchor_right));\n\t\t}\n\t}\n\n\t// Scrollpane.\n\t{\n\t\twindow->scrollpane = EZ_scrollpane_Create(tree, window_ctrl, \"Window scrollpane\", NULL, rh_size, (rh_size + titlebar_ctrl->height), 10, 10, 0);\n\t\tscrollpane_ctrl = (ez_control_t *)window->scrollpane;\n\n\t\t// Size the scrollpane to fit inside the window control.\n\t\tEZ_control_SetSize(scrollpane_ctrl, \n\t\t\t(window_ctrl->width - (2 * rh_size)), \n\t\t\t(window_ctrl->height - (titlebar_ctrl->height + (2 * rh_size))));\n\n\t\tEZ_control_SetAnchor(scrollpane_ctrl, (anchor_left | anchor_right | anchor_bottom | anchor_top));\n\n\t\t// Window area.\n\t\t{\n\t\t\twindow->window_area = EZ_control_Create(tree, scrollpane_ctrl, \"Window area\", NULL, 0, 0, 10, 10, (control_scrollable | control_enabled | control_resizeable));\n\n\t\t\t// Set the window area as the target of the scrollpane \n\t\t\t// (this will make it a child of the scrollpane, so don't bother to add it as a child to the window itself).\n\t\t\tEZ_scrollpane_SetTarget(window->scrollpane, window->window_area);\n\t\t\tEZ_control_SetDrawOrder(window->window_area, scrollpane_ctrl->draw_order + 1, true);\n\t\t}\n\t}\n}\n\n//\n// Window - Destroys a window.\n//\nint EZ_window_Destroy(ez_control_t *self, qbool destroy_children)\n{\n//\tez_window_t *window = (ez_window_t *)self;\n\tCONTROL_EVENT_HANDLER_CALL(NULL, self, ez_control_t, OnDestroy, destroy_children);\n\n\tEZ_control_Destroy(self, destroy_children);\n\n\t// TODO : Remove any event handlers.\n\n\treturn 0;\n}\n\n//\n// Window - Set window area virtual size (The part where you can put controls in the window).\n//\nvoid EZ_window_SetWindowAreaMinVirtualSize(ez_window_t *window, int min_virtual_width, int min_virtual_height)\n{\n\tif (window->window_area)\n\t{\n\t\tEZ_control_SetMinVirtualSize(window->window_area, min_virtual_width, min_virtual_height);\n\t}\n}\n\n//\n// Window - Adds a child control to the window.\n//\nvoid EZ_window_AddChild(ez_window_t *window, ez_control_t *child)\n{\n\tif (!window->scrollpane->target)\n\t{\n\t\tSys_Error(\"EZ_window_AddChild(): Window scrollpane has a NULL target.\\n\");\n\t}\n\n\tEZ_control_AddChild(window->window_area, child);\n\tEZ_control_SetDrawOrder(child, window->window_area->draw_order + 1, true);\n}\n\n// TODO: Add an event for when pressing the close button on the window control.\n\n"
  },
  {
    "path": "src/ez_window.h",
    "content": "\n#ifndef __EZ_WINDOW_H__\n#define __EZ_WINDOW_H__\n/*\nCopyright (C) 2007 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: ez_window.h,v 1.55 2007-10-27 14:51:15 cokeman1982 Exp $\n*/\n\n#include \"ez_controls.h\"\n#include \"ez_scrollpane.h\"\n\n// =========================================================================================\n// Window\n// =========================================================================================\n\n#define EZ_WINDOW_DEFAULT_BACKGROUND_IMAGE\t\"gfx/ui/ez_window\"\n\ntypedef struct ez_window_s\n{\n\tez_control_t\t\t\tsuper;\t\t\t\t// The super class.\n\n\tez_scrollpane_t\t\t\t*scrollpane;\t\t// The scrollpane for the window to enable scrolling.\n\tez_control_t\t\t\t*window_area;\t\t// The window area where child controls are placed, also the target of the scrollpane.\n\n\tez_control_t\t\t\t*titlebar;\t\t\t// The titlebar for the window.\n\tez_button_t\t\t\t\t*close_button;\t\t// The close button in the upper right corner.\n} ez_window_t;\n\n//\n// Window - Creates a new slider and initializes it.\n//\nez_window_t *EZ_window_Create(ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\n\n//\n// Window - Initializes a slider.\n//\nvoid EZ_window_Init(ez_window_t *window, ez_tree_t *tree, ez_control_t *parent,\n\t\t\t\t\t\t\t  char *name, char *description,\n\t\t\t\t\t\t\t  int x, int y, int width, int height,\n\t\t\t\t\t\t\t  ez_control_flags_t flags);\n\n//\n// Window - Destroys a window.\n//\nint EZ_window_Destroy(ez_control_t *self, qbool destroy_children);\n\n//\n// Window - Adds a child control to the window.\n//\nvoid EZ_window_AddChild(ez_window_t *window, ez_control_t *child);\n\n//\n// Window - Set window area virtual size (The part where you can put controls in the window).\n//\nvoid EZ_window_SetWindowAreaMinVirtualSize(ez_window_t *window, int min_virtual_width, int min_virtual_height);\n\n#endif // __EZ_WINDOW_H__\n\n"
  },
  {
    "path": "src/ezquake-icon.c",
    "content": "/* GIMP RGBA C-Source image dump (ezquake-icon.c) */\n\nstatic const struct {\n  unsigned int \t width;\n  unsigned int \t height;\n  unsigned int \t bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */ \n  unsigned char\t pixel_data[48 * 48 * 4 + 1];\n} ezquake_icon = {\n  48, 48, 4,\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\3\\0\\0\\0%\\0\\0\\0T\\0\\0\\0\\203\\0\\0\\0\\237\\0\\0\\0\\252\\0\\0\\0\\252\\0\\0\\0\\240\"\n  \"\\0\\0\\0\\210\\0\\0\\0V\\0\\0\\0\\40\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\30\\0\\0\\0p\\0\\0\\0\\304\\30\\30\\24\\367KL<\\377{|`\\377\\227\"\n  \"\\230u\\377\\241\\243{\\377\\242\\244{\\377\\231\\233t\\377\\201\\202a\\377IJ8\\377\\14\\14\"\n  \"\\12\\360\\0\\0\\0\\251\\0\\0\\0C\\0\\0\\0\\2\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0'\\0\\0\\0\\14\"\n  \"\\0\\0\\0\\260LM<\\377\\270\\271\\215\\377\\360\\363\\263\\377\\377\\377\\276\\377\\377\\377\"\n  \"\\300\\377\\377\\377\\276\\377\\377\\377\\271\\377\\377\\377\\262\\377\\377\\377\\257\\377\"\n  \"\\377\\377\\264\\377\\377\\377\\271\\377\\361\\363\\250\\377\\230\\232j\\377)(\\35\\375\\0\"\n  \"\\0\\0\\253\\0\\0\\0+\\0\\0\\0\\0\\0\\0\\0\\1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\15\\0\\0\\0\\220\\10\\10\\7\\377\\1\\1\\0\\215\\0\\0\\0\\206mmR\\377\"\n  \"\\377\\377\\301\\377\\377\\377\\250\\377\\372\\374\\227\\377\\307\\311v\\377\\213\\214R\\377\"\n  \"QR0\\37722\\36\\377''\\27\\376,,\\32\\377CD)\\377\\203\\204Q\\377\\316\\320\\203\\377\\365\"\n  \"\\367\\237\\377\\213\\214[\\377\\6\\6\\5\\353\\0\\0\\0Y\\0\\0\\0\\0\\0\\0\\0\\1\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0$\\0\\0\\0\\313__F\\377\\311\\311\\220\\377\\20\\20\"\n  \"\\14\\347\\0\\0\\0e\\27\\27\\15\\361\\324\\325~\\377\\224\\224S\\377..\\31\\377\\2\\2\\1\\345\"\n  \"\\0\\0\\0\\270\\0\\0\\0\\216\\0\\0\\0u\\0\\0\\0j\\0\\0\\0l\\0\\0\\0\\177\\0\\0\\0\\252\\0\\0\\0\\335-\"\n  \".\\33\\377\\253\\254i\\377\\274\\275t\\377\\36\\36\\22\\374\\0\\0\\0l\\0\\0\\0\\0\\0\\0\\0\\2\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0/\\1\\2\\2\\336\\212\\211d\\377\\377\\377\\265\\377\\377\"\n  \"\\377\\252\\377[[8\\377\\0\\0\\0\\216\\1\\1\\1\\261\\17\\17\\10\\377\\0\\0\\0\\307\\0\\0\\0~\\0\\0\"\n  \"\\0J\\0\\0\\0/\\0\\0\\0$\\0\\0\\0\\35\\0\\0\\0\\32\\0\\0\\0\\31\\0\\0\\0\\33\\0\\0\\0\\\"\\0\\0\\0\"\"8\\0\"\n  \"\\0\\0r\\0\\0\\0\\317II*\\377\\246\\245d\\377&%\\31\\373\\0\\0\\0f\\0\\0\\0\\2\\0\\0\\0\\2\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0&\\0\\0\\1\\342\\236\\235q\\377\\377\\377\\262\\377\\374\\371\\223\\377\\377\\377\"\n  \"\\221\\377\\261\\260b\\377\\0\\0\\0\\325\\0\\0\\0L\\0\\0\\0Y\\0\\0\\0\"\"5\\0\\0\\0#\\0\\0\\0\\26\\0\"\n  \"\\0\\0\\15\\0\\0\\0\\7\\0\\0\\0\\4\\0\\0\\0\\2\\0\\0\\0\\2\\0\\0\\0\\3\\0\\0\\0\\7\\0\\0\\0\\15\\0\\0\\0\\24\"\n  \"\\0\\0\\0+\\0\\0\\0\\207\\25\\25\\14\\365zzJ\\377\\33\\33\\21\\371\\0\\0\\0T\\0\\0\\0\\2\\0\\0\\0\\1\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\11\"\n  \"\\0\\0\\0\\306\\213\\212d\\377\\377\\377\\260\\377\\373\\366\\216\\377\\377\\377\\206\\377\\327\"\n  \"\\324j\\377'&\\23\\377\\0\\0\\0\\243\\0\\0\\0(\\0\\0\\0\\23\\0\\0\\0\\20\\0\\0\\0\\10\\0\\0\\0\\2\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\3\\0\"\n  \"\\0\\0\\13\\0\\0\\0\\25\\0\\0\\0`\\22\\22\\11\\354^]9\\377\\13\\13\\5\\344\\0\\0\\0.\\0\\0\\0\\2\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\202\"\n  \"ZYA\\377\\377\\377\\256\\377\\374\\366\\217\\377\\377\\375\\204\\377\\326\\322i\\377\\36\\35\"\n  \"\\17\\377\\0\\0\\0\\232\\0\\0\\0\"\"3\\0\\0\\0\\26\\0\\0\\0\\4\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\6\\0\\0\\0\\16\\0\\0\\0]\\26\\26\\16\\366?=&\\377\\0\\0\\0\\257\\0\\0\\0\\17\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\"3\\26\\25\\20\\367\\346\"\n  \"\\343\\235\\377\\377\\371\\226\\377\\377\\366\\202\\377\\347\\341p\\377&$\\22\\377\\0\\0\\0\"\n  \"\\232\\0\\0\\0\"\"2\\0\\0\\0\\30\\0\\0\\0\\6\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\4\\0\\0\\0\\11\\0\\0\\0m!\\40\\24\\377%$\\26\\377\\0\\0\\0e\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\250\\210\\206^\\377\\377\\377\\242\"\n  \"\\377\\372\\357\\201\\377\\377\\366{\\377VR(\\377\\0\\0\\0\\262\\0\\0\\0\"\"7\\0\\0\\0\\27\\0\\0\"\n  \"\\0\\5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\20\\0\\0\\0\\243NL.\\377\\232\\227Z\\377\\0\\0\\0\\307\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0.\\30\\27\\22\\370\\353\\345\\231\\377\\375\\362\\212\\377\"\n  \"\\377\\370}\\377\\251\\241N\\377\\0\\0\\0\\337\\0\\0\\0K\\0\\0\\0\\34\\0\\0\\0\\6\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\36\\0\\0\\0\\302g\"\n  \"cD\\377\\330\\317\\201\\37742\\35\\377\\0\\0\\0\\243\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\205roM\\377\\377\\375\\234\\377\\373\\354{\\377\\366\\350p\\377\"\n  \"1.\\26\\377\\0\\0\\0\\201\\0\\0\\0#\\0\\0\\0\\12\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0C\\2\\2\\2\\341\\223\\215d\\377\\333\\322\\214\\377\"\n  \"'%\\27\\377\\0\\0\\0\\243\\0\\0\\0\"\"6\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\11\\0\\0\\0\\320\\305\\276~\\377\\377\\363\\213\\377\\377\\361w\\377\\260\\244N\\377\\0\"\n  \"\\0\\0\\326\\0\\0\\0<\\0\\0\\0\\23\\0\\0\\0\\2\\0\\0\\0\\0\\0\\0\\0\\1\\0\\0\\0\\1\\0\\0\\0\\2\\0\\0\\0\\2\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0r\\25\\24\\20\\373\\304\\275\\205\\377\\321\\310\\205\\377\\23\\22\"\n  \"\\13\\374\\0\\0\\0\\223\\0\\0\\0\"\"4\\0\\0\\0\\31\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0.!\\40\\27\\371\\365\\350\\223\\377\\373\\350|\\377\\377\\357r\\377e\\\\+\\377\\0\"\n  \"\\0\\0\\230\\0\\0\\0!\\0\\0\\0\\10\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\34\\0\\0\\0\\321\\0\\0\\0l\\0\\0\\0\"\n  \"\\1\\0\\0\\0\\1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\13\\0\\0\\0\\2352/#\\377\\347\\337\\232\\377\\305\\275{\\377\\20\\16\\10\\371\\0\"\n  \"\\0\\0\\210\\0\\0\\0\"\"1\\0\\0\\0\\27\\0\\0\\0\\5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0ZTO5\\377\\377\\362\\220\\377\\372\\344q\\377\\366\\337f\\377,'\\22\\377\\0\\0\\0\"\n  \"`\\0\\0\\0\\15\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0]\\1\\1\\0\\377\\0\\0\\0\\227\\0\\0\\0\\15\\0\"\n  \"\\0\\0\\2\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0#\\0\\0\"\n  \"\\0\\304ZU=\\377\\375\\364\\245\\377\\261\\247h\\377\\10\\7\\4\\366\\0\\0\\0\\202\\0\\0\\0.\\0\"\n  \"\\0\\0\\25\\0\\0\\0\\5\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\1\\0\\0\\0\\17\\0\\0\\0\\40\\0\\0\\0\"\"7\\0\\0\\0X\\0\\0\\0s\\0\\0\\0\\201\\0\\0\\0\\311\"\n  \"\\211\\200Q\\377\\377\\361\\206\\377\\373\\342k\\377\\351\\321^\\377\\24\\22\\11\\367\\0\\0\"\n  \"\\0x\\0\\0\\0K\\0\\0\\0@\\0\\0\\0=\\0\\0\\0a\\15\\15\\7\\343:6&\\377\\0\\0\\0\\240\\0\\0\\0\\25\\0\\0\"\n  \"\\0\\3\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0D\\3\\3\\3\\350\\220\"\n  \"\\207`\\377\\377\\377\\251\\377\\224\\212S\\377\\0\\0\\0\\356\\0\\0\\0s\\0\\0\\0+\\0\\0\\0\\22\\0\"\n  \"\\0\\0\\3\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\2\\0\\0\\0\\24\\0\\0\\0*\\0\\0\\0Q\\0\\0\\0{\\0\\0\"\n  \"\\0\\233\\0\\0\\0\\301\\5\\5\\4\\341\\27\\26\\17\\361-+\\37\\376NI4\\377jcE\\377zrN\\377\\222\"\n  \"\\207W\\377\\352\\327}\\377\\372\\341q\\377\\367\\335c\\377\\367\\335`\\377od-\\377/*\\27\"\n  \"\\37751\\36\\37751\\\"\\37730\\\"\\377LG1\\377\\276\\261s\\377\\203{N\\377\\0\\0\\0\\241\\0\\0\"\n  \"\\0\\26\\0\\0\\0\\3\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0j\\21\\21\\15\\370\"\n  \"\\267\\255x\\377\\377\\377\\243\\377\\201vD\\377\\0\\0\\0\\347\\0\\0\\0i\\0\\0\\0)\\0\\0\\0\\21\"\n  \"\\0\\0\\0\\3\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0i\\0\\0\\0\\327\\0\\0\\0\\355\\34\\32\\22\\373\"\n  \"QK4\\377\\200wR\\377\\237\\223c\\377\\302\\264x\\377\\336\\315\\210\\377\\354\\331\\215\\377\"\n  \"\\373\\346\\223\\377\\377\\356\\225\\377\\377\\361\\223\\377\\377\\357\\216\\377\\377\\355\"\n  \"\\205\\377\\372\\340q\\377\\366\\331d\\377\\366\\330^\\377\\367\\331]\\377\\375\\342f\\377\"\n  \"\\372\\340o\\377\\373\\343\\177\\377\\374\\346\\212\\377\\374\\347\\216\\377\\377\\355\\220\"\n  \"\\377\\377\\370\\214\\377|o;\\377\\0\\0\\0\\241\\0\\0\\0\\26\\0\\0\\0\\3\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\10\\0\\0\\0\\224*'\\35\\377\\324\\310\\211\\377\\377\\371\\230\\377oc7\\377\"\n  \"\\0\\0\\0\\340\\0\\0\\0`\\0\\0\\0(\\0\\0\\0\\20\\0\\0\\0\\2\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0[\\0\\0\\0\\311\\0\\0\\0\\347\\35\\33\\23\\376PJ3\\377\\211}P\\377\\270\\247e\\377\\324\"\n  \"\\277m\\377\\356\\325s\\377\\376\\341u\\377\\377\\344r\\377\\377\\346p\\377\\377\\345m\\377\"\n  \"\\377\\345k\\377\\377\\343g\\377\\367\\327_\\377\\366\\325[\\377\\366\\324Y\\377\\372\\331\"\n  \"Z\\377\\377\\341_\\377\\377\\342d\\377\\377\\344j\\377\\377\\344m\\377\\377\\344n\\377\\377\"\n  \"\\344m\\377\\377\\360n\\377}n2\\377\\0\\0\\0\\241\\0\\0\\0\\26\\0\\0\\0\\3\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\\"\\0\\0\\0\\303XQ;\\377\\362\\344\\227\\377\\374\\355\\212\\377WM)\\377\\0\\0\\0\\321\"\n  \"\\0\\0\\0T\\0\\0\\0$\\0\\0\\0\\16\\0\\0\\0\\2\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\11\\0\\0\\0\\34\\0\\0\\0;\\0\\0\\0^\\0\\0\\0\\177\\0\\0\\0\\246\\0\\0\\0\\312\\0\\0\\0\\341\\22\"\n  \"\\20\\10\\3660)\\23\\377E<\\32\\377\\\\O\\\"\\377o_)\\377p`(\\377\\214x2\\377\\363\\321V\\377\"\n  \"\\367\\324V\\377\\374\\330W\\377\\320\\263H\\377SG\\35\\377NC\\34\\377PE\\35\\377PE\\35\\377\"\n  \"KA\\34\\377NC\\35\\377\\241\\213:\\377wg,\\377\\0\\0\\0\\241\\0\\0\\0\\26\\0\\0\\0\\3\\0\\0\\0\\0\"\n  \"\\0\\0\\0B\\1\\1\\2\\344\\204yU\\377\\377\\363\\234\\377\\365\\341}\\377OE\\\"\\377\\0\\0\\0\\307\"\n  \"\\0\\0\\0L\\0\\0\\0\\\"\\0\\0\\0\\14\\0\\0\\0\\1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\4\\0\\0\\0\\12\\0\\0\\0\\20\\0\\0\\0\\24\\0\\0\\0\\32\\0\\0\\0\\\"\\0\\0\\0.\\0\\0\\0\"\n  \"=\\0\\0\\0T\\0\\0\\0m\\0\\0\\0\\200\\0\\0\\0\\222\\0\\0\\0\\241\\0\\0\\0\\243\\0\\0\\0\\344\\275\\242\"\n  \"C\\377\\377\\331X\\377\\377\\334W\\377\\222|1\\377\\0\\0\\0\\326\\0\\0\\0\\216\\0\\0\\0\\214\\0\"\n  \"\\0\\0\\212\\0\\0\\0\\207\\0\\0\\0\\213\\2\\2\\1\\323\\23\\20\\7\\377\\0\\0\\0\\241\\0\\0\\0\\23\\0\\0\"\n  \"\\0\\0\\0\\0\\0b\\17\\17\\14\\367\\255\\236m\\377\\377\\367\\231\\377\\354\\323p\\377A8\\33\\377\"\n  \"\\0\\0\\0\\303\\0\\0\\0F\\0\\0\\0\\40\\0\\0\\0\\12\\0\\0\\0\\1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\1\\0\\0\\0\\4\\0\\0\\0\"\n  \"\\7\\0\\0\\0\\13\\0\\0\\0\\17\\0\\0\\0\\23\\0\\0\\0\\26\\0\\0\\0\\32\\0\\0\\0\\36\\0\\0\\0\\\"\\0\\0\\0\\40\"\n  \"\\0\\0\\0\\250\\214x6\\377\\377\\334\\\\\\377\\375\\324R\\377\\312\\252B\\377\\4\\3\\1\\345\\0\"\n  \"\\0\\0C\\0\\0\\0#\\0\\0\\0\\36\\0\\0\\0\\36\\0\\0\\0\\34\\0\\0\\0V\\0\\0\\0\\377\\0\\0\\0\\235\\0\\0\\0\"\n  \"\\31\\0\\0\\0\\214)&\\33\\377\\321\\276\\200\\377\\377\\364\\220\\377\\337\\303b\\3774,\\25\"\n  \"\\377\\0\\0\\0\\270\\0\\0\\0C\\0\\0\\0\\36\\0\\0\\0\\11\\0\\0\\0\\1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\2\\0\\0\\0\\3\\0\\0\\0\\5\\0\\0\\0\\5\\0\"\n  \"\\0\\0\\4\\0\\0\\0cE<\\36\\377\\377\\330`\\377\\367\\315N\\377\\373\\321P\\377=4\\25\\377\\0\"\n  \"\\0\\0m\\0\\0\\0\\14\\0\\0\\0\\5\\0\\0\\0\\4\\0\\0\\0\\4\\0\\0\\0\\37\\0\\0\\0\\274\\0\\0\\0\\224\\0\\0\\0\"\n  \"\\276ME-\\377\\354\\325\\210\\377\\377\\357\\205\\377\\321\\263U\\377!\\34\\14\\377\\0\\0\\0\"\n  \"\\247\\0\\0\\0;\\0\\0\\0\\34\\0\\0\\0\\7\\0\\0\\0\\1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0(\\12\\11\\6\\354\\333\\272V\\377\\372\\320P\\377\\377\\325N\\377\\226}2\\377\"\n  \"\\0\\0\\0\\271\\0\\0\\0\\23\\0\\0\\0\\2\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0L\\0\\0\\0\\342vh<\\377\"\n  \"\\374\\337\\177\\377\\377\\347v\\377\\304\\245K\\377\\30\\24\\10\\377\\0\\0\\0\\234\\0\\0\\0\"\"7\"\n  \"\\0\\0\\0\\33\\0\\0\\0\\7\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\1\"\n  \"\\0\\0\\0\\7\\0\\0\\0\\251\\211t8\\377\\377\\327W\\377\\370\\312H\\377\\345\\275I\\377\\26\\22\"\n  \"\\11\\366\\0\\0\\0E\\0\\0\\0\\6\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0_\\12\\12\\10\\363\\240\\214T\\377\\377\"\n  \"\\341x\\377\\377\\336g\\377\\273\\233B\\377\\20\\14\\4\\373\\0\\0\\0\\220\\0\\0\\0\"\"3\\0\\0\\0\"\n  \"\\31\\0\\0\\0\\6\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\1\\0\\0\\0N#\\35\\17\\377\\356\\304S\\377\\377\\317G\\377\\374\\315I\\377=2\\24\"\n  \"\\377\\0\\0\\0\\204\\0\\0\\0\\4\\0\\0\\0\\2\\0\\0\\0\\217)%\\32\\377\\316\\266s\\377\\377\\341y\\377\"\n  \"\\377\\331^\\377\\247\\2105\\377\\11\\7\\3\\365\\0\\0\\0\\177\\0\\0\\0.\\0\\0\\0\\26\\0\\0\\0\\4\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0J\\0\\0\\0\"\"3\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\2\\0\\0\\0\\20\\0\\0\\0\\273\\220v3\\377\\350\\274E\\377B4\\21\\377\\0\\0\\0\\322\\0\\0\\0\"\n  \"D\\0\\0\\0\\40\\0\\0\\0\\261KB.\\377\\353\\320\\204\\377\\377\\336w\\377\\377\\326\\\\\\377\\230\"\n  \"z.\\377\\3\\2\\0\\362\\0\\0\\0y\\0\\0\\0+\\0\\0\\0\\24\\0\\0\\0\\4\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0I\\0\\0\"\n  \"\\0\\377\\0\\0\\0a\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\3\\0\\0\"\n  \"\\0K\\25\\21\\10\\372\\35\\30\\12\\377\\0\\0\\0\\253\\0\\0\\0\"\"8\\0\\0\\0J\\0\\0\\0\\330k^=\\377\"\n  \"\\372\\333\\206\\377\\377\\330m\\377\\377\\322U\\377\\210m(\\377\\0\\0\\0\\354\\0\\0\\0p\\0\\0\"\n  \"\\0*\\0\\0\\0\\22\\0\\0\\0\\3\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0.\\0\\0\\0\\357\\0\\0\\0\\322\\0\\0\\0,\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\3\\0\\0\\0\\16\\0\\0\\0d\\0\"\n  \"\\0\\0p\\0\\0\\0)\\0\\0\\0s\\12\\11\\6\\361\\236\\210P\\377\\377\\335|\\377\\374\\320a\\377\\374\"\n  \"\\312L\\377oX\\36\\377\\0\\0\\0\\340\\0\\0\\0c\\0\\0\\0(\\0\\0\\0\\20\\0\\0\\0\\2\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0/\\0\\0\\0\\343\\0\\0\\0\\374\\0\\0\\0a\\0\\0\\0\\35\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\6\\0\\0\\0\\13\\0\\0\\0\\24\\0\\0\\0\\223*$\\25\\377\"\n  \"\\310\\251_\\377\\377\\333q\\377\\370\\311V\\377\\363\\300C\\377_J\\27\\377\\0\\0\\0\\323\\0\"\n  \"\\0\\0W\\0\\0\\0$\\0\\0\\0\\16\\0\\0\\0\\2\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0>\\1\\1\\0\\344\\0\\0\\0\\377\\0\"\n  \"\\0\\0\\223\\0\\0\\0(\\0\\0\\0\\20\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\20\\0\\0\\0\\326\\207tC\\377\\377\\342s\\377\\370\\305\"\n  \"O\\377\\374\\304@\\377\\307\\231-\\377\\0\\0\\0\\373\\0\\0\\0{\\0\\0\\0!\\0\\0\\0\\10\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\6\\0\\0\\0v\\17\\16\\11\\367\\12\\11\\6\\377\\0\\0\\0\\266\\0\\0\\0\"\"7\\0\\0\\0\"\n  \"\\30\\0\\0\\0\\4\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\3\\0\\0\\0I\\0\\0\\1\\347~e,\\377\\370\\301@\\377\\377\\3036\\377\\363\"\n  \"\\2701\\377\\207g\\35\\377\\17\\16\\5\\367\\0\\0\\0\\244\\0\\0\\0;\\0\\0\\0\\10\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\12\\0\\0\\0O\\7\\6\\2\"\n  \"\\312<4\\\"\\377,&\\31\\377\\0\\0\\0\\304\\0\\0\\0A\\0\\0\\0\\35\\0\\0\\0\\6\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\3\\0\"\n  \"\\0\\0\\13\\0\\0\\0@\\0\\0\\0\\3064(\\14\\377\\272\\215'\\377\\375\\3012\\377\\377\\3138\\377\"\n  \"\\345\\261=\\377\\202i.\\377#\\36\\21\\374\\0\\0\\0\\317\\0\\0\\0\\206\\0\\0\\0Q\\0\\0\\0*\\0\\0\"\n  \"\\0\\33\\0\\0\\0\\32\\0\\0\\0)\\0\\0\\0Q\\0\\0\\0\\216\\5\\4\\3\\334MB*\\377\\177nF\\3771+\\33\\377\"\n  \"\\0\\0\\0\\270\\0\\0\\0B\\0\\0\\0\\35\\0\\0\\0\\10\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\4\\0\\0\\0\"\n  \"\\15\\0\\0\\0'\\0\\0\\0\\206\\2\\2\\1\\351O<\\20\\377\\271\\214'\\377\\370\\276:\\377\\377\\326\"\n  \"P\\377\\370\\312Z\\377\\301\\235P\\377ye7\\377E:#\\377\\35\\30\\21\\370\\22\\20\\11\\353\\23\"\n  \"\\21\\11\\353\\\"\\35\\22\\370TH-\\377\\214wI\\377\\257\\225[\\377tc<\\377\\17\\14\\11\\364\"\n  \"\\0\\0\\0\\225\\0\\0\\0:\\0\\0\\0\\35\\0\\0\\0\\10\\0\\0\\0\\1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\2\\0\\0\\0\\13\\0\\0\\0\\26\\0\\0\\0?\\0\\0\\0\\225\\1\\1\\0\\341+\\40\\12\\377uY\\34\\377\"\n  \"\\261\\210.\\377\\343\\262E\\377\\371\\306V\\377\\372\\312`\\377\\353\\277`\\377\\334\\265\"\n  \"_\\377\\326\\261`\\377\\321\\255`\\377\\260\\223S\\377gV1\\377\\23\\17\\12\\367\\0\\0\\0\\271\"\n  \"\\0\\0\\0`\\0\\0\\0-\\0\\0\\0\\30\\0\\0\\0\\7\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\5\\0\\0\\0\\15\\0\\0\\0\\30\\0\\0\\0\"\"7\\0\\0\\0h\\0\\0\\0\\233\"\n  \"\\0\\0\\0\\313\\7\\5\\1\\345\\33\\22\\6\\364$\\36\\12\\373*\\\"\\15\\374)\\40\\16\\373\\37\\32\\12\"\n  \"\\363\\13\\6\\3\\345\\0\\0\\0\\307\\0\\0\\0\\222\\0\\0\\0]\\0\\0\\0\"\"2\\0\\0\\0\\36\\0\\0\\0\\17\\0\\0\"\n  \"\\0\\4\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\1\\0\\0\\0\\5\\0\\0\\0\\14\\0\\0\\0\\23\\0\\0\\0\\35\\0\\0\\0-\\0\\0\\0@\\0\\0\"\n  \"\\0Q\\0\\0\\0`\\0\\0\\0d\\0\\0\\0a\\0\\0\\0U\\0\\0\\0F\\0\\0\\0\"\"4\\0\\0\\0%\\0\\0\\0\\32\\0\\0\\0\\20\"\n  \"\\0\\0\\0\\6\\0\\0\\0\\1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\2\\0\\0\\0\\6\"\n  \"\\0\\0\\0\\13\\0\\0\\0\\17\\0\\0\\0\\23\\0\\0\\0\\25\\0\\0\\0\\26\\0\\0\\0\\27\\0\\0\\0\\26\\0\\0\\0\\23\"\n  \"\\0\\0\\0\\17\\0\\0\\0\\10\\0\\0\\0\\4\\0\\0\\0\\1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\1\\0\\0\\0\\1\\0\\0\\0\"\n  \"\\1\\0\\0\\0\\1\\0\\0\\0\\1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\",\n};\n\n"
  },
  {
    "path": "src/fchecks.c",
    "content": "/*\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: fchecks.c,v 1.19 2007-09-14 13:29:28 disconn3ct Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"rulesets.h\"\n#include \"version.h\"\n#include \"fmod.h\"\n#include \"utils.h\"\n\n\nstatic float\nf_system_reply_time,\nf_cmdline_reply_time,\nf_scripts_reply_time,\nf_fshaft_reply_time,\nf_ruleset_reply_time,\nf_reply_time,\nf_mod_reply_time,\nf_version_reply_time,\nf_skins_reply_time,\nf_server_reply_time;\n\ncvar_t allow_f_system  = {\"allow_f_system\",  \"1\"};\ncvar_t allow_f_cmdline = {\"allow_f_cmdline\", \"1\"};\n\nextern cvar_t enemyforceskins;\nextern cvar_t cl_independentPhysics;\nextern cvar_t allow_scripts;\nextern cvar_t cl_delay_packet;\nextern cvar_t r_fullbrightSkins;\nextern cvar_t cl_fakeshaft;\nextern cvar_t cl_iDrive;\n\n\nstatic void FChecks_VersionResponse (void)\n{\n\tCbuf_AddText (va(\"say ezQuake %s \" QW_PLATFORM \":\" QW_RENDERER \"\\n\", VersionString()));\n}\n\nstatic char *FChecks_FServerResponse_Text(void)\n{\n\tnetadr_t adr;\n\n\tif (!NET_StringToAdr (cls.servername, &adr))\n\t\treturn NULL;\n\n\tif (adr.port == 0)\n\t\tadr.port = BigShort (PORT_SERVER);\n\n\treturn NET_AdrToString(adr);\n}\n\nstatic void FChecks_FServerResponse (void)\n{\n\tchar *text = FChecks_FServerResponse_Text();\n\n\tif (!text)\n\t\treturn;\n\n\tCbuf_AddText(va(\"say ezQuake f_server response: %s\\n\", text));\n}\n\nstatic void FChecks_SkinsResponse (float fbskins)\n{\n\tchar *sf = enemyforceskins.integer ? \", individual enemy skin forcing\" : \"\";\n\n\tif (fbskins > 0)\n\t\tCbuf_AddText (va(\"say all skins %d%% fullbright%s\\n\", (int) (fbskins * 100), sf));\n\telse\n\t\tCbuf_AddText (va(\"say not using fullbright skins%s\\n\", sf));\n}\n\nstatic void FChecks_ScriptsResponse (void)\n{\n\tif (allow_scripts.integer < 1)\n\t\tCbuf_AddText (\"say not using scripts\\n\");\n\telse if (allow_scripts.integer < 2)\n\t\tCbuf_AddText (\"say using simple scripts\\n\");\n\telse\n\t\tCbuf_AddText (\"say using advanced scripts\\n\");\n}\n\nstatic qbool FChecks_ScriptsRequest (const char *s)\n{\n\tif (cl.spectator || (f_scripts_reply_time && cls.realtime - f_scripts_reply_time < 20))\n\t\treturn false;\n\n\tif (Util_F_Match(s, \"f_scripts\")) {\n\t\tFChecks_ScriptsResponse();\n\t\tf_scripts_reply_time = cls.realtime;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic void FChecks_FakeshaftResponse (void)\n{\n\tif (cl_fakeshaft.integer == 2)\n\t\tCbuf_AddText(\"say fakeshaft 2 (emulation of fakeshaft 0 for servers with antilag feature)\\n\");\n\telse if (cl_fakeshaft.value > 0.999)\n\t\tCbuf_AddText(\"say fakeshaft on\\n\");\n\telse if (cl_fakeshaft.value < 0.001)\n\t\tCbuf_AddText(\"say fakeshaft off\\n\");\n\telse\n\t\tCbuf_AddText(va(\"say fakeshaft %.1f%%\\n\", cl_fakeshaft.value * 100.0));\n}\n\nstatic qbool FChecks_FakeshaftRequest (const char *s)\n{\n\tif (cl.spectator || (f_fshaft_reply_time && cls.realtime - f_fshaft_reply_time < 20))\n\t\treturn false;\n\n\tif (Util_F_Match(s, \"f_fakeshaft\"))\t{\n\t\tFChecks_FakeshaftResponse();\n\t\tf_fshaft_reply_time = cls.realtime;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic qbool FChecks_VersionRequest (const char *s)\n{\n\tif (cl.spectator || (f_version_reply_time && cls.realtime - f_version_reply_time < 20))\n\t\treturn false;\n\n\tif (Util_F_Match(s, \"f_version\")) {\n\t\tFChecks_VersionResponse();\n\t\tf_version_reply_time = cls.realtime;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic qbool FChecks_SkinRequest (const char *s)\n{\n\tchar *fbs;\n\tqbool fbskins_policy = (cls.demoplayback || cl.spectator) ? 1 :\n\t\t*(fbs = Info_ValueForKey(cl.serverinfo, \"fbskins\")) ? bound(0, Q_atof(fbs), 1) :\n\t\tcl.teamfortress ? 0 : 1;\n\tfloat fbskins = bound (0, r_fullbrightSkins.value, fbskins_policy);\n\tif (cl.spectator || (f_skins_reply_time && cls.realtime - f_skins_reply_time < 20))\n\t\treturn false;\n\n\tif (Util_F_Match(s, \"f_skins\"))\t{\n\t\tFChecks_SkinsResponse(fbskins);\n\t\tf_skins_reply_time = cls.realtime;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic qbool FChecks_CheckFModRequest (const char *s)\n{\n\tif (cl.spectator || (f_mod_reply_time && cls.realtime - f_mod_reply_time < 20))\n\t\treturn false;\n\n\tif (Util_F_Match(s, \"f_modified\")) {\n\t\tFMod_Response();\n\t\tf_mod_reply_time = cls.realtime;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic qbool FChecks_CheckFServerRequest (const char *s)\n{\n\tnetadr_t adr;\n\n\tif (cl.spectator || (f_server_reply_time && cls.realtime - f_server_reply_time < 20))\n\t\treturn false;\n\n\tif (Util_F_Match(s, \"f_server\") && NET_StringToAdr (cls.servername, &adr)) {\n\t\tFChecks_FServerResponse();\n\t\tf_server_reply_time = cls.realtime;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid FChecks_RulesetFeatureAppend(qbool on, const char *code, char *feat_on_buf,\n\t\t\t\t\t\t\t\t  size_t feat_on_size, char *feat_off_buf, size_t feat_off_size)\n{\n\tif (on) {\n\t\tstrlcat(feat_on_buf, code, feat_on_size);\n\t}\n\telse {\n\t\tstrlcat(feat_off_buf, code, feat_off_size);\n\t}\n}\n\n// constructs a string telling which extra features are enabled\n// besides those covered by the current ruleset)\n// format: [+<enabled features>][-<disabled features>]\nconst char* FChecks_RulesetAdditionString(void)\n{\n\tchar feat_on_buf[16] = \"+\";\n\tchar feat_off_buf[16] = \"-\";\n\tstatic char features[32] = \"\";\n\n\t*features = '\\0';\n\n\t#define APPENDFEATURE(on,code) FChecks_RulesetFeatureAppend((on),(code),feat_on_buf,sizeof (feat_on_buf),feat_off_buf,sizeof (feat_off_buf))\n\t// modified models or sounds\n\tAPPENDFEATURE((strcmp(FMod_Response_Text(), \"all models ok\") != 0),\"m\");\n\n\t// movement scripts enabled\n\tAPPENDFEATURE((allow_scripts.integer),\"s\");\n\n\t// enemy skin forcing enabled\n\tAPPENDFEATURE((enemyforceskins.integer),\"f\");\n\n\t// cl_iDrive - strafing aid\n\tAPPENDFEATURE((cl_iDrive.integer),\"i\");\n\t#undef APPENDFEATURE\n\n\tif (strlen(feat_on_buf) > 1) {\n\t\tstrlcat(features, feat_on_buf, sizeof (features));\n\t}\n\tif (strlen(feat_off_buf) > 1) {\n\t\tstrlcat(features, feat_off_buf, sizeof (features));\n\t}\n\n\treturn features;\n}\n\nstatic qbool FChecks_CheckFRulesetRequest (const char *s)\n{\n\t// format of the reply:\n\t// [nick: ]\n\t// [padding] - so that \"nick: \" + padding is 17 chars long\n\t// [ip address] - padded with spaces to 21 chars\n\t// - this fits to 38 chars which is length of line with conwidth 320\n\t// [space]\n\t// [client version] - padded to 16 chars\n\t// [ruleset name]\n\t// [ruleset addition]\n\t// - these 4 should be less than 38 chars so that reply does never take up more than 2 lines\n\tchar *fServer;\n\tconst char *features;\n\tchar *emptystring = \"\";\n\tchar *brief_version = \"ezq\" VERSION_NUMBER;\n\tconst char *ruleset = Rulesets_Ruleset();\n\tsize_t name_len = strlen(cl.players[cl.playernum].name);\n\tsize_t pad_len = 15 - min(name_len, 15);\n\n\tif (cl.spectator || (f_ruleset_reply_time && cls.realtime - f_ruleset_reply_time < 20))\n\t\treturn false;\n\n\tif (Util_F_Match(s, \"f_ruleset\"))\t{\n\t\tfeatures = FChecks_RulesetAdditionString();\n\t\tfServer = FChecks_FServerResponse_Text();\n\t\tif (!fServer) {\n\t\t\tfServer = \"server-na\";\n\t\t}\n\n\t\tCbuf_AddText(va(\"say \\\"%*s%21s %16s %s%s\\\"\\n\",\n\t\t\tpad_len, emptystring, fServer, brief_version, ruleset, features));\n\t\tf_ruleset_reply_time = cls.realtime;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid FChecks_FRuleset_cmd(void)\n{\n\tif (Cmd_Argc() < 2 || strcmp(Cmd_Argv(1), \"check\") != 0) {\n\t\tCom_Printf(\"Purpose:\\n  \"\n\t\t\t\"All clients on the server should respond to \\\"f_ruleset\\\" message with their client settings in the reply\\n\"\n\t\t\t\"Usage:\\n  f_ruleset - prints this help\\n\"\n\t\t\t\"  f_ruleset check - sends check message to all players on the server\\n\"\n\t\t\t\"Flags:\\n  End of the reply is ruleset name + flags denoting enabled and disabled features:\\n\"\n\t\t\t\"  m - modified models or sounds\\n\"\n\t\t\t\"  s - movement scripts\\n\"\n\t\t\t\"  f - individual enemy skin forcing\\n\"\n\t\t\t\"  i - side step aid (strafescript)\\n\"\n\t\t\t\"  flags that start with + mean feature is enabled, - means disabled\\n\");\n\t}\n\telse {\n\t\tCbuf_AddText(\"say \\\"f_ruleset\\\"\\n\");\n\t}\t\t\n}\n\nstatic qbool FChecks_CmdlineRequest (const char *s)\n{\n\tif (cl.spectator || (f_cmdline_reply_time && cls.realtime - f_cmdline_reply_time < 20))\n\t\treturn false;\n\n\tif (Util_F_Match(s, \"f_cmdline\"))\t{\n\t\tif (!allow_f_cmdline.integer) {\n\t\t\tCbuf_AddText(\"say disabled\\n\");\n\t\t} else {\n\t\t\tCbuf_AddText(\"say \");\n\t\t\tCbuf_AddText(com_args_original);\n\t\t\tCbuf_AddText(\"\\n\");\n\t\t}\n\n\t\tf_cmdline_reply_time = cls.realtime;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic qbool FChecks_SystemRequest (const char *s)\n{\n\tif (cl.spectator || (f_system_reply_time && cls.realtime - f_system_reply_time < 20))\n\t\treturn false;\n\n\tif (Util_F_Match(s, \"f_system\")) {\n\t\tchar *sys_string;\n\n\t\tsys_string = (allow_f_system.integer) ? SYSINFO_GetString() : \"disabled\";\n\n\t\t//if (sys_string != NULL && sys_string[0]) {\n\t\tCbuf_AddText(\"say \");\n\t\tCbuf_AddText(sys_string);\n\t\tCbuf_AddText(\"\\n\");\n\t\t//}\n\n\t\tf_system_reply_time = cls.realtime;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid FChecks_CheckRequest (const char *s)\n{\n\tqbool fcheck = false;\n\n\tfcheck |= FChecks_VersionRequest (s);\n\tfcheck |= FChecks_CmdlineRequest (s);\n\tfcheck |= FChecks_SystemRequest (s);\n\tfcheck |= FChecks_SkinRequest (s);\n\tfcheck |= FChecks_ScriptsRequest (s);\n\tfcheck |= FChecks_FakeshaftRequest (s);\n\tfcheck |= FChecks_CheckFModRequest (s);\n\tfcheck |= FChecks_CheckFServerRequest (s);\n\tfcheck |= FChecks_CheckFRulesetRequest (s);\n\n\tif (fcheck)\n\t\tf_reply_time = cls.realtime;\n}\n\nvoid FChecks_Init (void)\n{\n\tCvar_SetCurrentGroup (CVAR_GROUP_CHAT);\n\tCvar_Register (&allow_f_system);\n\tCvar_Register (&allow_f_cmdline);\n\tCvar_ResetCurrentGroup();\n\n\tFMod_Init();\n\tCmd_AddCommand (\"f_server\", FChecks_FServerResponse);\n\tCmd_AddCommand (\"f_ruleset\", FChecks_FRuleset_cmd);\n}\n"
  },
  {
    "path": "src/fchecks.h",
    "content": "/*\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: fchecks.h,v 1.4 2007-09-14 13:29:28 disconn3ct Exp $\n*/\n\n#ifndef __FCHECKS_H__\n#define __FCHECKS_H__\n\nvoid FChecks_Init (void);\nvoid FChecks_CheckRequest (const char *);\n\n#endif /* !__FCHECKS_H__ */\n"
  },
  {
    "path": "src/fmod.c",
    "content": "/*\nCopyright (C) 2001-2002 A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: fmod.c,v 1.12 2007-09-14 13:29:28 disconn3ct Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"fmod.h\"\n#include \"sha1.h\"\n#include \"rulesets.h\"\n\ntypedef qbool (*pRulesetFilter)(const char* modelName);\n\ntypedef struct check_models_hashes_entry_s {\n\tconst unsigned int hash[5];\n\tstruct check_models_hashes_entry_s* next;\n} check_models_hashes_entry_t;\n\n// original models\nstatic const int progs_armor_mdl_FMOD_DM_FMOD_TF[5]\t\t\t\t= {0x18d1b8ef, 0xfe43d973, 0xbb104313, 0xef6a9090, 0x2b048696};\nstatic const int progs_backpack_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0x80cedaeb, 0x18f7d282, 0xa911e7c2, 0xffe609aa, 0x050a6005};\nstatic const int progs_bolt2_mdl_FMOD_DM_FMOD_TF[5]\t\t\t\t= {0x14fba8de, 0x99237ae4, 0xc8806043, 0xebeea454, 0x024a99a9};\nstatic const int progs_end1_mdl_FMOD_DM_FMOD_TF[5]\t\t\t\t= {0xd1fed19f, 0x5d67c632, 0x1d72a0e6, 0x14ab39d7, 0x4bf435c4};\nstatic const int progs_end2_mdl_FMOD_DM_FMOD_TF[5]\t\t\t\t= {0x98eee620, 0x107acd79, 0x8331620d, 0x2a9a1848, 0x64d29e1a};\nstatic const int progs_end3_mdl_FMOD_DM_FMOD_TF[5]\t\t\t\t= {0x7f1e336d, 0xe04f4bb0, 0x4ac25a1a, 0x2caedde7, 0x09973319};\nstatic const int progs_end4_mdl_FMOD_DM_FMOD_TF[5]\t\t\t\t= {0xa4b572aa, 0xdd00c38d, 0x130f8da2, 0x797f0e55, 0xcca52b76};\nstatic const int progs_eyes_mdl_FMOD_DM_FMOD_TF[5]\t\t\t\t= {0x276f25a8, 0xc6b8f782, 0x3ef75d52, 0x911d163e, 0x5d79e557};\nstatic const int progs_g_light_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0xcb9b9366, 0x1fd74d91, 0x9a6a82f6, 0x5d6b2e3e, 0xc458f5ac};\nstatic const int progs_g_nail_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0x7b209f71, 0xa77cd00f, 0x91f24953, 0x932f264b, 0x350e7440};\nstatic const int progs_g_nail2_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0x97cb4499, 0x88e3e11a, 0xecec6eca, 0x883f6c8f, 0xac7ecf0c};\nstatic const int progs_g_rock_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0x9017dd83, 0x150d954c, 0x9b5f4544, 0x10843b72, 0x4697068e};\nstatic const int progs_g_rock2_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0x5a47ec20, 0xd0211cdc, 0xd6b8af60, 0xaf813eab, 0xba330b5b};\nstatic const int progs_g_shot_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0x35a735e1, 0xc1ee6090, 0x9f8940b5, 0x6cdefd1c, 0x7eec1d67};\nstatic const int progs_gib1_mdl_FMOD_DM_FMOD_TF[5]\t\t\t\t= {0x99dc9ea4, 0x6e9bf74a, 0x25710a1e, 0x701fc77b, 0x09777092};\nstatic const int progs_gib2_mdl_FMOD_DM_FMOD_TF[5]\t\t\t\t= {0x5ac3e29b, 0xd66358d9, 0x1044d27a, 0xb3da48ad, 0x5f1e9fbb};\nstatic const int progs_gib3_mdl_FMOD_DM_FMOD_TF[5]\t\t\t\t= {0xc6558e61, 0x13ea4f63, 0x20c9da45, 0x0640212e, 0x7b98f350};\nstatic const int progs_grenade_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0x60dfffb8, 0xfc871f0c, 0xd9f3c325, 0x61aadcaf, 0x0ec37cbf};\nstatic const int progs_invisibl_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0x23e2f389, 0x8479657f, 0x437e0d25, 0xee100bae, 0xbad6a775};\nstatic const int progs_invulner_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0x987ee175, 0xfd0d4f35, 0x06d2641c, 0x725c0dc8, 0x871f537a};\nstatic const int progs_missile_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0x9adfeee8, 0x185872c1, 0xb3bb36f8, 0x996e29ab, 0xd46ab2a9};\nstatic const int progs_quaddama_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0x2760f663, 0x32dc8405, 0x057563df, 0x9614c3a7, 0x0125949b};\nstatic const int progs_s_spike_mdl_FMOD_DM_FMOD_TF[5]\t\t\t= {0xf308f8e5, 0xcdc242e2, 0x4f71b01f, 0xafb9880a, 0x52198e9f};\nstatic const int progs_spike_mdl_FMOD_DM_FMOD_TF[5]\t\t\t\t= {0xebd9adaf, 0xfb3b2f28, 0x67cc2c34, 0x926ec21a, 0x09e1a233};\nstatic const int progs_suit_mdl_FMOD_DM_FMOD_TF[5]\t\t\t\t= {0xb7dcb9dd, 0xed8da03b, 0x416efc5e, 0x8ee38d5a, 0x4063bf25};\nstatic const int progs_player_mdl_FMOD_DM[5]\t\t\t\t\t= {0x95ca0ab4, 0x021be62e, 0x6655e9a5, 0xd4a7ef1c, 0xb484582f};\nstatic const int progs_player_mdl_FMOD_TF[5]\t\t\t\t\t= {0x8fcab658, 0x027a97ef, 0xa96eee3d, 0xc1e44f46, 0x18a533c9};\nstatic const int progs_tf_flag_mdl_FMOD_TF[5]\t\t\t\t\t= {0x6e969df0, 0xc91b9eef, 0x84cb1a40, 0xcaee122e, 0x1b1d3d28};\nstatic const int progs_stag_mdl_FMOD_TF[5]\t\t\t\t\t\t= {0x6e970df0, 0xc91b9eef, 0x84cb1a40, 0xcaee122e, 0x1b1d3d28};\nstatic const int progs_basrkey_bsp_FMOD_TF[5]\t\t\t\t\t= {0x6e971df0, 0xc11b9eef, 0x84cb1a40, 0xcaee122e, 0x1b1d3d28};\nstatic const int progs_basbkey_bsp_FMOD_TF[5]\t\t\t\t\t= {0x6e972df0, 0xc21b9eef, 0x84cb1a40, 0xcaee122e, 0x1b1d3d28};\nstatic const int progs_w_s_key_mdl_FMOD_TF[5]\t\t\t\t\t= {0x6e973df0, 0xc31b9eef, 0x84cb1a40, 0xcaee122e, 0x1b1d3d28};\nstatic const int progs_w_g_key_mdl_FMOD_TF[5]\t\t\t\t\t= {0x6e974df0, 0xc41b9eef, 0x84cb1a40, 0xcaee122e, 0x1b1d3d28};\nstatic const int progs_b_g_key_mdl_FMOD_TF[5]\t\t\t\t\t= {0x6e975df0, 0xc51b9eef, 0x84cb1a40, 0xcaee122e, 0x1b1d3d28};\nstatic const int progs_b_s_key_mdl_FMOD_TF[5]\t\t\t\t\t= {0x6e976df0, 0xc61b9eef, 0x84cb1a40, 0xcaee122e, 0x1b1d3d28};\nstatic const int progs_ff_flag_mdl_FMOD_TF[5]\t\t\t\t\t= {0x6e977df0, 0xc71b9eef, 0x84cb1a40, 0xcaee122e, 0x1b1d3d28};\nstatic const int progs_harbflag_mdl_FMOD_TF[5]\t\t\t\t\t= {0x6e978df0, 0xc81b9eef, 0x84cb1a40, 0xcaee122e, 0x1b1d3d28};\nstatic const int progs_princess_mdl_FMOD_TF[5]\t\t\t\t\t= {0x6e979df0, 0xc92b9eef, 0x84cb1a40, 0xcaee122e, 0x1b1d3d28};\nstatic const int progs_turrbase_mdl_FMOD_TF[5]\t\t\t\t\t= {0x0cda73d5, 0x822703cb, 0x9e6cbe6b, 0x94fb95f7, 0xc7c04bed};\nstatic const int progs_turrgun_mdl_FMOD_TF[5]\t\t\t\t\t= {0x314b126f, 0x7d8d7cb5, 0x8df185c8, 0x1f3662f7, 0xa272957b};\nstatic const int progs_disp_mdl_FMOD_TF[5]\t\t\t\t\t\t= {0x5596fa4a, 0xd94e2457, 0x140f84df, 0x5859dda8, 0xe96752fc};\nstatic const int progs_tf_stan_mdl_FMOD_TF[5]\t\t\t\t\t= {0x1b732a1b, 0x763069c2, 0xe779e270, 0x252b9daa, 0xddf7db2a};\nstatic const int progs_hgren2_mdl_FMOD_TF[5]\t\t\t\t\t= {0x1dc3715f, 0x58c53c0f, 0xdb1504fc, 0xab354bdf, 0x6aacee97};\nstatic const int progs_grenade2_mdl_FMOD_TF[5]\t\t\t\t\t= {0x4b116cef, 0xebc6f074, 0x2acdea73, 0x876a8716, 0xe5e7409d};\nstatic const int progs_detpack_mdl_FMOD_TF[5]\t\t\t\t\t= {0xaf73d4e1, 0x0c98dc38, 0x6d3983c0, 0xd7a70033, 0x616a675e};\nstatic const int progs_s_bubble_spr_FMOD_DM_FMOD_TF[5]\t\t\t= {0x3dbefaf2, 0x73b38654, 0x70910774, 0xdfe285f3, 0x4db5d90e};\nstatic const int progs_s_explod_spr_FMOD_DM_FMOD_TF[5]\t\t\t= {0x295a7b06, 0xacdf1f88, 0x50a20894, 0x5c75905f, 0xb9d95d0b};\nstatic const int maps_b_bh100_bsp_FMOD_DM_FMOD_TF[5]\t\t\t= {0x65e7e302, 0x94a85b4d, 0x8092e674, 0xf700e5f0, 0xde667fcc};\nstatic const int sound_buttons_airbut1_wav_FMOD_DM_FMOD_TF[5]\t= {0x47d5141e, 0xc925e8eb, 0x26e5583c, 0xc8dfd021, 0x226792ef};\nstatic const int sound_items_armor1_wav_FMOD_DM_FMOD_TF[5]\t\t= {0x44488db0, 0xef0b0a1d, 0x3acda8b4, 0x3d87b467, 0xe4dd4fcc};\nstatic const int sound_items_damage_wav_FMOD_DM_FMOD_TF[5]\t\t= {0x197778ac, 0x92a252c5, 0x90c30256, 0x7ba6f264, 0x56ab654f};\nstatic const int sound_items_damage2_wav_FMOD_DM_FMOD_TF[5]\t\t= {0x719a9b4d, 0x1c76b218, 0xfcbf979f, 0x5ea5e6fa, 0x68afda0e};\nstatic const int sound_items_damage3_wav_FMOD_DM_FMOD_TF[5]\t\t= {0xfa07de6c, 0x6e306d39, 0x7d18f3ed, 0x90272558, 0x63d01e7a};\nstatic const int sound_items_health1_wav_FMOD_DM_FMOD_TF[5]\t\t= {0xebdd17b2, 0xa1289b80, 0x7810e3f2, 0xdb8d01bf, 0x0b495a96};\nstatic const int sound_items_inv1_wav_FMOD_DM_FMOD_TF[5]\t\t= {0xa01240b8, 0xb588d430, 0xc62406e0, 0x39e59dfd, 0xad5a4b98};\nstatic const int sound_items_inv2_wav_FMOD_DM_FMOD_TF[5]\t\t= {0x9ab6025d, 0x2d9d24a2, 0xc127b17c, 0x019e908a, 0xa521f7d3};\nstatic const int sound_items_inv3_wav_FMOD_DM_FMOD_TF[5]\t\t= {0xfb785777, 0x9c622826, 0x6121d05c, 0xf42d5661, 0xa34a5429};\nstatic const int sound_items_itembk2_wav_FMOD_DM_FMOD_TF[5]\t\t= {0x178c519b, 0x3b030527, 0x759aaed2, 0xf7dca7d7, 0xf01a1e36};\nstatic const int sound_player_land_wav_FMOD_DM_FMOD_TF[5]\t\t= {0xb8bb5e41, 0xf087ad8e, 0x13323cd5, 0x2e2d2052, 0x338e9e38};\nstatic const int sound_player_land2_wav_FMOD_DM_FMOD_TF[5]\t\t= {0x6c13b55c, 0xc46c1589, 0xeeabac42, 0x087cd24e, 0xd9552386};\nstatic const int sound_misc_outwater_wav_FMOD_DM_FMOD_TF[5]\t\t= {0x9cb6fc36, 0x9c20e9ba, 0x595f8418, 0x50e76d9f, 0xa7503dfd};\nstatic const int sound_weapons_pkup_wav_FMOD_DM_FMOD_TF[5]\t\t= {0x05a43523, 0x09bbab60, 0x77ce67a0, 0xb52fe23d, 0xf2715701};\nstatic const int sound_player_plyrjmp8_wav_FMOD_DM_FMOD_TF[5]\t= {0x75e2e946, 0xc9ad54f2, 0x6cd47b03, 0xeef0c9d4, 0x5d3386da};\nstatic const int sound_items_protect_wav_FMOD_DM_FMOD_TF[5]\t\t= {0x38ff1351, 0x7d6799c7, 0x7577256d, 0xbf233192, 0xb6223baa};\nstatic const int sound_items_protect2_wav_FMOD_DM_FMOD_TF[5]\t= {0x588dd159, 0xae489f2c, 0x22f7118e, 0x487eeb4c, 0x3c54c997};\nstatic const int sound_items_protect3_wav_FMOD_DM_FMOD_TF[5]\t= {0x869a1143, 0xc9e6a404, 0xcbd5d90e, 0xa1b57e4c, 0xae77dd20};\nstatic const int sound_items_r_item1_wav_FMOD_DM_FMOD_TF[5]     = {0xdde12941, 0xfbb3b804, 0x776cf5fa, 0xe81df8ea, 0xa1429f63};\nstatic const int sound_items_r_item2_wav_FMOD_DM_FMOD_TF[5]     = {0xd9da4047, 0x5a7b191a, 0xc02d860d, 0x18f679de, 0xc47bd93a};\nstatic const int sound_misc_water1_wav_FMOD_DM_FMOD_TF[5]\t\t= {0x8625dbc7, 0xbaf30ae6, 0xfdb53965, 0x2d7eb9d6, 0xf9fd9304};\nstatic const int sound_misc_water2_wav_FMOD_DM_FMOD_TF[5]\t\t= {0xccda75ec, 0xfb5cd780, 0xe2d73b5b, 0x9f3560ad, 0x4e116b85};\n\nstatic const int sound_misc_menu1_wav_FMOD_DM_FMOD_TF[5]\t\t= {0x2e19b817, 0x0c0e1c5f, 0xd7a0ecf8, 0xb278c27e, 0xb0e1923c};\nstatic const int sound_misc_menu2_wav_FMOD_DM_FMOD_TF[5]\t\t= {0xd6719ab1, 0x0a407e2b, 0xb03a053b, 0x2a4bc2cb, 0x61cd7ff3};\nstatic const int sound_misc_menu3_wav_FMOD_DM_FMOD_TF[5]\t\t= {0xae120b9a, 0x3a217d2e, 0xc34f0990, 0x2d4332ed, 0x97c18f76};\nstatic const int sound_misc_talk_wav_FMOD_DM_FMOD_TF[5]\t\t\t= {0x5b15321c, 0x1af2d826, 0xb0228e9f, 0x49c06f56, 0xa8355e8b};\nstatic const int sound_misc_basekey_wav_FMOD_DM_FMOD_TF[5]\t\t= {0x926134ce, 0x22806dd3, 0x1952624a, 0x8f43f7e9, 0xa6fdfe64};\nstatic const int sound_doors_runeuse_wav_FMOD_DM_FMOD_TF[5]\t\t= {0xf41d6d59, 0xe99b93e8, 0x15edfd25, 0x668a499c, 0x1ac62572};\n\nstatic const int gfx_colormap_lmp_FMOD_DM_FMOD_TF[5]\t\t\t= {0xa93b3795, 0x17016397, 0x0a761d38, 0x4866e67d, 0x3c2a2a75};\nstatic const int gfx_palette_lmp_FMOD_DM_FMOD_TF[5]\t\t\t= {0xa6a2e242, 0xbad0f7da, 0xe263351f, 0x6d51f8ad, 0x49a45d4a};\n\n// debug\nstatic check_models_hashes_entry_t mdlhash_debug_armor = { {0x8d264ffc, 0xfabb1e7c, 0xc81128bc, 0x21633d32, 0xfa0b594f}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_backpack = { {0xe7c1f382, 0x0c3dc22e, 0xd31a04c0, 0x7251ed52, 0x45baf223}, NULL };\n//static check_models_hashes_entry_t mdlhash_debug_flame = { {0xcfd8e629, 0x143a35a7, 0x4781ad82, 0x4254f247, 0xe07cd565}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_g_light = { {0x9d78b478, 0xfb44c849, 0xc56b6d9d, 0xab737839, 0x1fcfd848}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_g_nail = { {0xc6619f5d, 0x55511a85, 0x6de0cc63, 0xef61171a, 0x36b1b173}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_g_nail2 = { {0x9cd58369, 0xe54c8527, 0xcea234b4, 0x472bd93e, 0x7dfa8655}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_g_rock = { {0x334e9253, 0xa5a5a252, 0x68b9a856, 0x5e226647, 0xe4baeec7}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_g_rock2 = { {0xe24e10f9, 0x0f53bc41, 0x6043ee2b, 0x3d577eec, 0xbc75124c}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_g_shot = { {0x7bbba828, 0x99438f98, 0x975e3747, 0x6cbc8a2b, 0xd3a64db7}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_gib1 = { {0x69ee06fd, 0x8bac8483, 0xf9c5a43e, 0xd7513722, 0x55d5a4ff}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_gib2 = { {0x3251a1df, 0x50e68208, 0xeff0f797, 0x89894e71, 0x65505be3}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_gib3 = { {0x798f0b46, 0xe55c7250, 0x882ff3f5, 0x75495a80, 0x19a3ed99}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_grenade = { {0xdaf5db12, 0x41d4fc02, 0x764dd35a, 0xa4490888, 0xd5d26cea}, NULL };\n//static check_models_hashes_entry_t mdlhash_debug_h_player = { {0x22ee657b, 0x31ac0cb9, 0x8ce678cc, 0xb174b570, 0x1a102fd9}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_invisibl = { {0xe5acae60, 0x8b2fe8fd, 0xb8ef8e78, 0x8d236ae4, 0xc3db0be3}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_invulner = { {0x57b00a3e, 0x009afa6d, 0xa4c2c8cb, 0xa7b0eccc, 0xa9e57049}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_missile = { {0x2ae7a078, 0xc39393d4, 0x73576788, 0x242699d2, 0x8f190bfd}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_quaddama = { {0x9010a156, 0x1b63addb, 0xbc9bd9e3, 0xff8d6e4e, 0xcecd1260}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_s_spike = { {0xb159e4cc, 0xbc5dccf0, 0x656e93ab, 0x3e72dd24, 0x10446fc6}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_spike = { {0xe78bb944, 0x92a753e4, 0x435c226b, 0x4021a65e, 0xef388c6b}, NULL };\n/*\nstatic check_models_hashes_entry_t mdlhash_debug_v_axe = { {0x77c9f8d4, 0x66d8b9e8, 0xaa75b3ce, 0xe6325d71, 0x70f57eea}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_v_light = { {0xf1ac7d77, 0x9ff2321d, 0x4ad3b6f0, 0x4372d11e, 0x46765fa7}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_v_nail = { {0x2322c673, 0xd5ec1e41, 0xe762d27e, 0x89c6ae72, 0x9006bd90}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_v_nail2 = { {0x636a9adb, 0xaeca483a, 0x284f0999, 0x3198b142, 0xd174e963}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_v_rock = { {0xf13c7070, 0xecc5c390, 0x22412e14, 0x51b13f01, 0x3c803dee}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_v_rock2 = { {0xda85f801, 0xa8bd2523, 0xa338f574, 0xa0466a00, 0xc5dc3964}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_v_shot = { {0x67e9aa5b, 0x294674a8, 0x553aea03, 0x13165b3c, 0x161e851e}, NULL };\nstatic check_models_hashes_entry_t mdlhash_debug_v_shot2 = { {0x309594be, 0xe11e461a, 0x92cfcfdd, 0xe9b78788, 0x650daa89}, NULL };\n*/\n\n// Primevil deathmatch pack\nstatic check_models_hashes_entry_t mdlhash_pdp_g_nail = { {0x86dd1964, 0xebe66f85, 0xf27e5b4c, 0x3230aeda, 0x10c13860}, NULL };\nstatic check_models_hashes_entry_t mdlhash_pdp_g_rock = { {0x10e990fe, 0x3b401ed2, 0x9a9d71ad, 0x9085f059, 0x7710088f}, NULL };\nstatic check_models_hashes_entry_t mdlhash_pdp_g_rock2 = { {0x3c4280d9, 0xd8a43a56, 0xeff931eb, 0xb76310af, 0xb28c39ad}, NULL };\nstatic check_models_hashes_entry_t mdlhash_pdp_g_shot = { {0x3a9f015f, 0xd817a29e, 0xdc7a87fa, 0xc6e31628, 0xaa991119}, NULL };\n\n// ruohis\nstatic check_models_hashes_entry_t mdlhash_ruohis_armor = { {0x7772c52a, 0xdee37c13, 0xbbbc8523, 0x348525ba, 0x6e1bd649}, NULL };\n//static check_models_hashes_entry_t mdlhash_ruohis_backpack = { {0x1a57bc37, 0xabe69424, 0xaa3b6b52, 0x64af310b, 0x177b9c20}, NULL };\n\nstatic check_models_hashes_entry_t mdlhash_ruohis_g_light = { {0xfe2d50b8, 0x6ad56eb4, 0xe579fceb, 0x43e46f6d, 0x82f616da}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_g_nail = { {0xbe7da37e, 0xb531272b, 0x0c7de89a, 0x26f387bd, 0x20dc31ee}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_g_nail2 = { {0x38df15ca, 0xe2db4350, 0x5c065841, 0x67ec548c, 0x3db3f0b2}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_g_rock = { {0xd2e3e29a, 0x1e3a2acf, 0xa2f21d53, 0x60452add, 0xd8732afa}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_g_rock2 = { {0x54350c58, 0x6a09fd88, 0xae214b80, 0xc71d71de, 0x109d0fe6}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_g_shot = { {0xc74cafd8, 0xbc3af902, 0xbb52b588, 0x6f6fca30, 0x5b2ab554}, NULL };\n\nstatic check_models_hashes_entry_t mdlhash_ruohis_gib1 = { {0x05ff57f8, 0x1073a096, 0xc7e5d055, 0x9c6c040b, 0x96d2eb8a}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_gib2 = { {0xdf23d687, 0x51ea9047, 0xbfdb3497, 0xed7b63db, 0x131a1bfb}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_gib3 = { {0xff5df01e, 0x7606952f, 0xdd36c784, 0xa0d72d33, 0x066afa33}, NULL };\n\nstatic check_models_hashes_entry_t mdlhash_ruohis_invisibl = { {0xb7507e7e, 0x84dc7019, 0x8f5f1c39, 0xc0df2979, 0xe86b93dd}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_invulner = { {0xddcca118, 0x9e143d41, 0xa2a8dc57, 0x82744eb7, 0x09af301b}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_quaddama = { {0xdb93cf6c, 0x7006a0b8, 0x90b28c56, 0xf97afba7, 0x423699cb}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_suit = { {0xbf4de4f3, 0xe60927c1, 0xf4824f00, 0x21feef56, 0xac00a897}, NULL };\n\nstatic check_models_hashes_entry_t mdlhash_ruohis_grenade = { {0xece5571e, 0xa35d61e8, 0xe2b6df49, 0xf35372d9, 0x7ac78910}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_missile = { {0x7e844aca, 0xb1b07ef9, 0x3d8994d8, 0xe6b4d14e, 0x56c49858}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_spike = { {0xd8225980, 0x0299e9f7, 0x6732fd66, 0x54555264, 0x67bda403}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_s_spike = { {0x40bf84c5, 0xcab85a9c, 0x498baa24, 0xbe07d9c6, 0x6b666756}, NULL };\n\nstatic check_models_hashes_entry_t mdlhash_ruohis_end1 = { {0x51ff9322, 0x8b31b83d, 0x8988bebf, 0x0917dce7, 0xd9578e04}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_end2 = { {0x2ea71e4b, 0x0ef6cf22, 0x86d53ba5, 0x1145433d, 0xe6712054}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_end3 = { {0x04c3eda0, 0x2b4f2534, 0x127abbca, 0x755c97cf, 0x135b6597}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_end4 = { {0x4d5a54c1, 0x1fa8aaba, 0x600a5b2b, 0x7508ff3f, 0xc7af03de}, NULL };\n\nstatic check_models_hashes_entry_t mdlhash_ruohis_b_bh100 = { {0x527fd5b6, 0xd490707f, 0xabd69622, 0xf3d18a69, 0xe9bc0eb9}, NULL };\nstatic check_models_hashes_entry_t mdlhash_ruohis_b_bh100_other = { {0xb42135c1, 0x6816429c, 0x21e3ce3e, 0xfab9f8fc, 0x09387ae1}, NULL };\n\n// plagues\nstatic check_models_hashes_entry_t mdlhash_plaguespak_armor = { {0x5647e7b2, 0x52465746, 0xc10320fa, 0x1e92c90c, 0x27577c72}, NULL };\n//static check_models_hashes_entry_t mdlhash_plaguespak_b_g_key = { {0x3f9779fe, 0x5ec019b7, 0xa25fe622, 0x6e217fa6, 0xe84cee45}, NULL };\n//static check_models_hashes_entry_t mdlhash_plaguespak_b_s_key = { {0x370e1810, 0xb9bc0a5b, 0x4bb3fb54, 0x1573b0f5, 0x351207ab}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_g_light = { {0x6231b892, 0x1f39d616, 0x1442715d, 0xfbfc2826, 0xc866d8d6}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_g_nail = { {0xcb2e31c6, 0x1f1064ab, 0x5e0be681, 0x5e658642, 0xd9411ef4}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_g_nail2 = { {0x7d3fe401, 0x738a4344, 0x4ab62439, 0xba7313eb, 0x6a6f8536}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_g_rock = { {0xe31fc7b0, 0x35200618, 0x8ba6973c, 0x1296c555, 0xb2541bde}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_g_rock2 = { {0x674eb3e9, 0xde325927, 0xdabd4337, 0xc97a755c, 0x97f4f1f9}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_g_shot = { {0xd3486358, 0x4a3a3d37, 0x0efc43e4, 0x55a42f89, 0xf8078519}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_grenade = { {0xe3a1b910, 0x28c4e36a, 0x1af69321, 0x9d19df99, 0x5bcebfcb}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_missile = { {0xe047b3ec, 0xad03d2e2, 0x2a146207, 0x99e1f2df, 0xfb229f42}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_s_spike = { {0xe7c61b11, 0x703a7f30, 0x0051a5da, 0xb84a5bd1, 0xe23645ac}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_spike = { {0x28f1cb95, 0xafb8ed91, 0x6a8300ff, 0xeb29c03f, 0x28a2bbcb}, NULL };\n/*\nstatic check_models_hashes_entry_t mdlhash_plaguespak_v_light = { {0x3c9ca1d7, 0x77303f95, 0x0ad05a82, 0x64a83757, 0x8257cf75}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_v_nail = { {0x71876b6d, 0x934d5f0a, 0x322763f9, 0x924fb71c, 0x5027e3b4}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_v_nail2 = { {0x0b154635, 0x5a49f995, 0x9671641a, 0x2dfb1212, 0xe2631d44}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_v_rock = { {0xbac51d54, 0x884906a3, 0xa7ebec69, 0xe994be29, 0xdd326508}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_v_rock2 = { {0x170a3ecf, 0x5cd131ab, 0xfcecd4fb, 0x62151b89, 0x3bc663b7}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_v_shot = { {0x93dc8f24, 0x6739d838, 0x996b00cc, 0x85c2d98f, 0x430b8e30}, NULL };\nstatic check_models_hashes_entry_t mdlhash_plaguespak_v_shot2 = { {0x58e68b33, 0x1127186f, 0xd1bdae79, 0x66fd9ca3, 0x82421f9b}, NULL };\n*/\n\n// unknown\n// these come with nQuake and are told to be \"harmless\"\nstatic check_models_hashes_entry_t mdlhash_unknown_end2 = { {0xe3a1b910, 0x28c4e36a, 0x1af69321, 0x9d19df99, 0x5bcebfcb }, NULL };\nstatic check_models_hashes_entry_t mdlhash_unknown_end3 = { {0xe3a1b910, 0x28c4e36a, 0x1af69321, 0x9d19df99, 0x5bcebfcb }, NULL };\nstatic check_models_hashes_entry_t mdlhash_unknown_end4 = { {0xe3a1b910, 0x28c4e36a, 0x1af69321, 0x9d19df99, 0x5bcebfcb }, NULL };\n\n// grenade converted from CPMA\n//static check_models_hashes_entry_t mdlhash_cpma_grenade = { {0xe89d9866, 0xf52324c9, 0x005d0507, 0x083c8cb4, 0xc1918636 }, NULL };\n// megaheath converted from Generations Arena\nstatic check_models_hashes_entry_t mdlhash_generations_b_bh100 = { {0xc6b455ff, 0xad7cb845, 0xa7356729, 0x35375fcf, 0x10adb479}, NULL };\n\n// megahealth alternatives\n// this one is said to be used in the US\nstatic check_models_hashes_entry_t sound_items_r_item2_wav_us = {{0xf62a00d2, 0x5fceaaca, 0x25f91692, 0xf7602cb7, 0x230da525}, NULL};\n// this is just 11kHz version of the US one, but resampled to 11 kHz\n// this one might be used widely in russia\nstatic check_models_hashes_entry_t sound_items_r_item2_wav_ru = {{0x83a8e646, 0xbc4cc313, 0xa07aa96e, 0x9d81eada, 0xf619bd10}, NULL};\n\n// Mindgrid Audio sound files\nstatic check_models_hashes_entry_t sound_buttons_mindgrid_airbut1 = { {0xd5fbbce0, 0x2083e431, 0xaa11c54d, 0x089ce053, 0xdf03ce11}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_armor1 = { {0x1b47daed, 0x5a60cd6a, 0x83940499, 0x65eb651b, 0x4441f6cf}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_damage = { {0x3da087a3, 0x1799d4c7, 0x8ddabf31, 0x42e2de6f, 0x5bfdabec}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_damage2 = { {0xd0eb779c, 0x0bf1d83c, 0x120e1c7e, 0x50fa449b, 0x2cd4655b}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_damage3 = { {0x2e0c3b71, 0xdd52cfc9, 0x8e5fe1e5, 0x0ac48fd9, 0xa2d8b970}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_inv1 = { {0x787708ba, 0x63f52c51, 0x55835932, 0x7db46d86, 0xccc830cc}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_inv2 = { {0xb2a3bc8c, 0x7eb56b5f, 0x675e4d67, 0x24054040, 0x3b2ecd0b}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_inv3 = { {0xa1945433, 0x7f3e2c52, 0x9144528a, 0x02891cf0, 0xd95eb255}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_itembk2 = { {0x41dd6dad, 0x45a8c24b, 0xef56b75e, 0x5ab3915a, 0x2901a70f}, NULL };\nstatic check_models_hashes_entry_t sound_misc_mindgrid_outwater = { {0x6be98515, 0xfeab0126, 0x80edc811, 0xe7cf7012, 0x1def4980}, NULL };\nstatic check_models_hashes_entry_t sound_weapons_mindgrid_pkup = { {0xda261fbc, 0x12f47c68, 0x9e91e794, 0xce0a434d, 0xc4e35e77}, NULL };\nstatic check_models_hashes_entry_t sound_player_mindgrid_plyrjmp8 = { {0x5a9e8263, 0xb9b3f06e, 0x8a047de1, 0x72c18bec, 0x547df977}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_protect = { {0x5948333f, 0xa28e882a, 0xb4b82964, 0xe10679c5, 0x191f8cb1}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_protect2 = { {0x29e977b0, 0x0d9378fe, 0xb4489037, 0x02d83d62, 0xce0b718d}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_protect3 = { {0x7619314c, 0x07b6901c, 0x000601e2, 0xc358367e, 0x999f90b1}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_r_item1 = { {0x0f5754d4, 0xa8b84c5f, 0x0001903a, 0xd3ab289c, 0xa93bc848}, NULL };\nstatic check_models_hashes_entry_t sound_items_mindgrid_r_item2 = { {0xd2d0de39, 0x2bb78d33, 0x09ef3081, 0xbe90bacf, 0xbf71ecce}, NULL };\nstatic check_models_hashes_entry_t sound_misc_mindgrid_water1 = { {0x838a0318, 0x0805e53e, 0x6ebf4065, 0xaa5fb9a0, 0xe1cad807}, NULL };\nstatic check_models_hashes_entry_t sound_misc_mindgrid_water2 = { {0xfb0a6b17, 0x065fbc33, 0x5fab7cc5, 0x110c01aa, 0x278b612e}, NULL };\n\n// CapnBub's new player model, see http://www.quakeworld.nu/forum/topic/6073/new-quake-models/page/1\nstatic check_models_hashes_entry_t mdlhash_player_mdl_CapNBubs_FMOD_DM = { {0xC0223459, 0x42B97D8D, 0xC52FF472, 0xF3EE0710, 0x41E21132}, NULL };\n\n// gpl_maps.pk3 include a small wav file to allow map to load\nstatic check_models_hashes_entry_t sound_gpl_maps_silence_wav = { {0xc99871d4, 0xc60e0fef, 0x14e64bf9, 0xbaf43934, 0x5376df18}, NULL };\n\n// rerelease\nstatic check_models_hashes_entry_t sound_buttons_rerelease_airbut1 = { {0x81911593, 0x3e85656b, 0x7a9475d0, 0x87de29a0, 0x157d3ef9}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_armor1 = { {0x1bde8fc5, 0xc979543f, 0x8fc7f57b, 0xe2c1367b, 0x62cac72f}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_damage = { {0x4dde0192, 0x1791d662, 0xa33168eb, 0xb98d083f, 0x15ba0ded}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_damage2 = { {0x6c9b6f7c, 0x85e48943, 0x803ddafd, 0x8249af31, 0x9967d89c}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_damage3 = { {0x18a390ad, 0x0c47d3e9, 0x19db8c3b, 0x1426eda5, 0xa2feb55a}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_health1 = { {0x3ef1d863, 0x683899cb, 0xd9191607, 0x02353423, 0x0737419c}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_inv1 = { {0x24c5cf56, 0xb3569f7a, 0xb21bb96a, 0x95638065, 0x2ea0d036}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_inv2 = { {0x810f09ea, 0x34d8c482, 0xcc9561b1, 0xffeba6d0, 0xc6610c57}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_inv3 = { {0x037ed5b5, 0x27ab500b, 0xcee3abb1, 0xd882645a, 0x48e02c87}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_protect = { {0x7f894d4b, 0xe4a5c499, 0xd06df01a, 0xb1e57bc8, 0xb2d552aa}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_protect2 = { {0xa8a31b8a, 0xf96967b1, 0xc394287f, 0x3a5bf203, 0xe80d111b}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_protect3 = { {0xd8a88cdf, 0x394d1b5b, 0x28fb397b, 0x4120f605, 0x3e5dbefa}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_r_item1 = { {0xc850a036, 0x86199e27, 0x8550ac67, 0x7fc29886, 0x1d80678a}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_r_item2 = { {0xb0e2cb5d, 0xcc12db33, 0x5d599343, 0x2835ac62, 0x76fa4846}, NULL };\nstatic check_models_hashes_entry_t sound_items_rerelease_itembk2 = { {0xf3937197, 0x71d317b7, 0x7827aee9, 0x68ab6f3a, 0x5cb0e9c7}, NULL };\nstatic check_models_hashes_entry_t sound_player_rerelease_land = { {0x349bc931, 0x8621a826, 0xca259e9d, 0x5eb296d1, 0xe0f4ac70}, NULL };\nstatic check_models_hashes_entry_t sound_player_rerelease_land2 = { {0x8ac31277, 0xdfc8eedf, 0xf6f322ca, 0x37188a1f, 0xe3f7a688}, NULL };\nstatic check_models_hashes_entry_t sound_misc_rerelease_outwater = { {0x13930c6c, 0xaa57abbf, 0x5356d6ea, 0x1a2e6d40, 0x6a818b4c}, NULL };\nstatic check_models_hashes_entry_t sound_weapons_rerelease_pkup = { {0x6a0c9888, 0x2d68fa77, 0x5e448d95, 0x2a95666c, 0x842ee9a9}, NULL };\nstatic check_models_hashes_entry_t sound_player_rerelease_plyrjmp8 = { {0xd33205fa, 0x2ec2aa75, 0x074e991b, 0x2f32c63e, 0xafbbbb6a}, NULL };\nstatic check_models_hashes_entry_t sound_misc_rerelease_water1 = { {0xa694280d, 0xd6b28324, 0x9e2c2def, 0x007d86f7, 0xbb583438}, NULL };\nstatic check_models_hashes_entry_t sound_misc_rerelease_water2 = { {0xd974d2db, 0x7c609ba1, 0x9ffefe98, 0x376e5f5d, 0x8cc0cbb9}, NULL };\nstatic check_models_hashes_entry_t sound_misc_rerelease_menu1 = { {0x0849e8f9, 0x5828515c, 0x55d6b38c, 0xf7299ac3, 0x0655378e}, NULL };\nstatic check_models_hashes_entry_t sound_misc_rerelease_menu2 = { {0xe028d3c0, 0x710031e1, 0xd83221ad, 0xd28205c7, 0x51c10a10}, NULL };\nstatic check_models_hashes_entry_t sound_misc_rerelease_menu3 = { {0x2205061f, 0x00f4c078, 0xfa26934b, 0x15bacdfd, 0x98d90a7b}, NULL };\nstatic check_models_hashes_entry_t sound_misc_rerelease_talk = { {0xf28522df, 0x7ea37652, 0xc4605d1f, 0xce10e1cf, 0x2d1fc5fc}, NULL };\nstatic check_models_hashes_entry_t sound_misc_rerelease_basekey = { {0x6871924c, 0xbcc9e6dc, 0x6cea01c2, 0x367f979f, 0xb16619ae}, NULL };\nstatic check_models_hashes_entry_t sound_doors_rerelease_runeuse = { {0x00ffef66, 0x4ed15aae, 0x8e67b5ef, 0x563f0c0f, 0x70b11c41}, NULL };\n\n#define MAX_CHECK_MODELS 256\n#define\tFMOD_DM 1\n#define FMOD_TF 2\n\ntypedef struct check_models_s {\n\tchar\t\tname[MAX_QPATH];\n\tqbool\t\tmodified;\n\tqbool\t\tchecked;\n\tint\t\t\tflags;\n\tconst void*\thash;\n\tcheck_models_hashes_entry_t*\n\t\t\t\thash_alt;\n} check_models_t;\n\nstatic check_models_t check_models[MAX_CHECK_MODELS];\nstatic int check_models_num = 0;\nstatic float fmod_warn_time = 0.0f;\n\nstatic qbool FMod_IsModelModified (const char *name, const int flags, const byte *buf, const size_t len)\n{\n\tint i;\n\tunsigned char hash[DIGEST_SIZE];\n\tSHA1_CTX context;\n\tqbool found = false;\n\n\tSHA1Init (&context);\n\tSHA1Update (&context, (unsigned char *) buf, len);\n\tSHA1Final (hash, &context);\n\n\tfor (i = 0; i < check_models_num; i++) {\n\t\tif (strcmp(name, check_models[i].name) == 0) {\n\t\t\tif (check_models[i].flags == flags) {\n\t\t\t\tfound = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (found) {\n\t\tif (memcmp(check_models[i].hash, hash, DIGEST_SIZE) == 0) {\n\t\t\t// original model\n\t\t\treturn false;\n\t\t}\n\t\telse {\n\t\t\tcheck_models_hashes_entry_t *cur = check_models[i].hash_alt;\n\t\t\twhile (cur) {\n\t\t\t\tif (memcmp(cur->hash, hash, DIGEST_SIZE) == 0 && Rulesets_AllowAlternateModel(name)) {\n\t\t\t\t\t// alternative legal model\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tcur = cur->next;\n\t\t\t}\n\t\t}\n\t}\n\n\t// no match found\n\treturn true;\n}\n\nstatic int FMod_AddModel (const char *name, const qbool flags, const void *hash)\n{\n\tif (check_models_num >= MAX_CHECK_MODELS)\n\t\treturn -1;\n\n\tstrlcpy (check_models[check_models_num].name, name, sizeof (check_models[check_models_num].name));\t\n\tcheck_models[check_models_num].checked = check_models[check_models_num].modified = false;\n\tcheck_models[check_models_num].flags = flags;\n\tcheck_models[check_models_num].hash = hash;\n\tcheck_models[check_models_num].hash_alt = NULL;\n\treturn check_models_num++;\n}\n\n/// adds alternative models' hashes\nstatic void FMod_AddModelAlt (int cm_num, check_models_hashes_entry_t *hash_new)\n{\n\tcheck_models_hashes_entry_t* curr_head;\n\tif (cm_num < 0) return;\n\n\tcurr_head = check_models[cm_num].hash_alt;\n\tcheck_models[cm_num].hash_alt = hash_new;\n\thash_new->next = curr_head;\n}\n\nvoid FMod_CheckModel (const char *name, const void *buf, const size_t len)\n{\n\tint i;\n\tqbool modified, relevent;\n\n\tfor (i = 0; i < MAX_CHECK_MODELS && i < check_models_num; i++) {\n\t\trelevent = (cl.teamfortress && (check_models[i].flags & FMOD_TF)) ||\n\t\t\t(!cl.teamfortress && (check_models[i].flags & FMOD_DM));\n\n\t\tif (relevent && !strcasecmp (name, check_models[i].name))\n\t\t\tbreak;\n\t}\n\n\tif (i >= check_models_num)\n\t\treturn;\n\n\tmodified = FMod_IsModelModified (name, check_models[i].flags, buf, len);\n\n\tif (check_models[i].checked && !check_models[i].modified && modified) {\n\t\tif (!fmod_warn_time || (float) cls.realtime - fmod_warn_time >= 3.0f) {\n\t\t\tCbuf_AddText(\"say warning: models changed !!  Use f_modified again\\n\");\n\t\t\tfmod_warn_time = (float) cls.realtime;\n\t\t}\n\t}\n\n\tcheck_models[i].modified = modified;\n\tcheck_models[i].checked = true;\n}\n\nvoid FMod_Init (void)\n{\n\tint lastid;\n\n\tCmd_AddCommand(\"f_modified\", FMod_Response);\n\n\tmemset (check_models, 0, sizeof (check_models));\n\n\tlastid = FMod_AddModel (\"progs/armor.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_armor_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_armor);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_armor);\n\tFMod_AddModelAlt(lastid, &mdlhash_plaguespak_armor);\n\n\tlastid = FMod_AddModel (\"progs/backpack.mdl\",\t\tFMOD_DM | FMOD_TF,\tprogs_backpack_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_backpack);\n\n\tFMod_AddModel (\"progs/bolt2.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_bolt2_mdl_FMOD_DM_FMOD_TF);\n\tlastid = FMod_AddModel (\"progs/end1.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_end1_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_end1);\n\tlastid = FMod_AddModel (\"progs/end2.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_end2_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_unknown_end2);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_end2);\n\tlastid = FMod_AddModel (\"progs/end3.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_end3_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_unknown_end3);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_end3);\n\tlastid = FMod_AddModel (\"progs/end4.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_end4_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_unknown_end4);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_end4);\n\tFMod_AddModel (\"progs/eyes.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_eyes_mdl_FMOD_DM_FMOD_TF);\n\tlastid = FMod_AddModel (\"progs/g_light.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_g_light_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_g_light);\n\tFMod_AddModelAlt(lastid, &mdlhash_plaguespak_g_light);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_g_light);\n\n\tlastid = FMod_AddModel (\"progs/g_nail.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_g_nail_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_g_nail);\n\tFMod_AddModelAlt(lastid, &mdlhash_plaguespak_g_nail);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_g_nail);\n\tFMod_AddModelAlt(lastid, &mdlhash_pdp_g_nail);\n\n\tlastid = FMod_AddModel (\"progs/g_nail2.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_g_nail2_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_g_nail2);\n\tFMod_AddModelAlt(lastid, &mdlhash_plaguespak_g_nail2);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_g_nail2);\n\n\tlastid = FMod_AddModel (\"progs/g_rock.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_g_rock_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_g_rock);\n\tFMod_AddModelAlt(lastid, &mdlhash_plaguespak_g_rock);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_g_rock);\n\tFMod_AddModelAlt(lastid, &mdlhash_pdp_g_rock);\n\n\tlastid = FMod_AddModel (\"progs/g_rock2.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_g_rock2_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_g_rock2);\n\tFMod_AddModelAlt(lastid, &mdlhash_plaguespak_g_rock2);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_g_rock2);\n\tFMod_AddModelAlt(lastid, &mdlhash_pdp_g_rock2);\n\n\tlastid = FMod_AddModel (\"progs/g_shot.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_g_shot_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_g_shot);\n\tFMod_AddModelAlt(lastid, &mdlhash_plaguespak_g_shot);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_g_shot);\n\tFMod_AddModelAlt(lastid, &mdlhash_pdp_g_shot);\n\n\tlastid = FMod_AddModel (\"progs/gib1.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_gib1_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_gib1);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_gib1);\n\n\tlastid = FMod_AddModel (\"progs/gib2.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_gib2_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_gib2);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_gib2);\n\n\tlastid = FMod_AddModel (\"progs/gib3.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_gib3_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_gib3);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_gib3);\n\n\tlastid = FMod_AddModel (\"progs/grenade.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_grenade_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_grenade);\n\tFMod_AddModelAlt(lastid, &mdlhash_plaguespak_grenade);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_grenade);\n\t//FMod_AddModelAlt(lastid, &mdlhash_cpma_grenade);\n\n\tlastid = FMod_AddModel (\"progs/invisibl.mdl\",\t\tFMOD_DM | FMOD_TF,\tprogs_invisibl_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_invisibl);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_invisibl);\n\n\tlastid = FMod_AddModel (\"progs/invulner.mdl\",\t\tFMOD_DM | FMOD_TF,\tprogs_invulner_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_invulner);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_invulner);\n\n\tlastid = FMod_AddModel (\"progs/missile.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_missile_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_missile);\n\tFMod_AddModelAlt(lastid, &mdlhash_plaguespak_missile);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_missile);\n\n\tlastid = FMod_AddModel (\"progs/quaddama.mdl\",\t\tFMOD_DM | FMOD_TF,\tprogs_quaddama_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_quaddama);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_quaddama);\n\n\tlastid = FMod_AddModel (\"progs/s_spike.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_s_spike_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_s_spike);\n\tFMod_AddModelAlt(lastid, &mdlhash_plaguespak_s_spike);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_s_spike);\n\n\tlastid = FMod_AddModel (\"progs/spike.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_spike_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_debug_spike);\n\tFMod_AddModelAlt(lastid, &mdlhash_plaguespak_spike);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_spike);\n\n\tlastid = FMod_AddModel (\"progs/suit.mdl\",\t\t\tFMOD_DM | FMOD_TF,\tprogs_suit_mdl_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_suit);\n\n\tlastid = FMod_AddModel (\"progs/player.mdl\",\t\t\tFMOD_DM,\t\t\tprogs_player_mdl_FMOD_DM);\n\tFMod_AddModelAlt (lastid, &mdlhash_player_mdl_CapNBubs_FMOD_DM);\n\tFMod_AddModel (\"progs/player.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_player_mdl_FMOD_TF);\n\n\tFMod_AddModel (\"progs/tf_flag.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_tf_flag_mdl_FMOD_TF);\n\tFMod_AddModel(\"progs/stag.mdl\",\t\t\t\tFMOD_TF,\t\t\tprogs_stag_mdl_FMOD_TF);\n\tFMod_AddModel(\"progs/basrkey.bsp\",\t\t\tFMOD_TF,\t\t\tprogs_basrkey_bsp_FMOD_TF);\n\tFMod_AddModel(\"progs/basbkey.bsp\",\t\t\tFMOD_TF,\t\t\tprogs_basbkey_bsp_FMOD_TF);\n\tFMod_AddModel(\"progs/w_s_key.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_w_s_key_mdl_FMOD_TF);\n\tFMod_AddModel(\"progs/w_g_key.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_w_g_key_mdl_FMOD_TF);\n\tFMod_AddModel(\"progs/b_g_key.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_b_g_key_mdl_FMOD_TF);\n\tFMod_AddModel(\"progs/b_s_key.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_b_s_key_mdl_FMOD_TF);\n\tFMod_AddModel(\"progs/ff_flag.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_ff_flag_mdl_FMOD_TF);\n\tFMod_AddModel(\"progs/harbflag.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_harbflag_mdl_FMOD_TF);\n\tFMod_AddModel(\"progs/princess.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_princess_mdl_FMOD_TF);\n\tFMod_AddModel (\"progs/turrbase.mdl\",\t\tFMOD_TF,\t\t\tprogs_turrbase_mdl_FMOD_TF);\n\tFMod_AddModel (\"progs/turrgun.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_turrgun_mdl_FMOD_TF);\n\tFMod_AddModel (\"progs/disp.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_disp_mdl_FMOD_TF);\n\tFMod_AddModel (\"progs/tf_stan.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_tf_stan_mdl_FMOD_TF);\n\tFMod_AddModel (\"progs/hgren2.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_hgren2_mdl_FMOD_TF); // gren1\n\tFMod_AddModel (\"progs/grenade2.mdl\",\t\tFMOD_TF,\t\t\tprogs_grenade2_mdl_FMOD_TF); // gren2\n\tFMod_AddModel (\"progs/detpack.mdl\",\t\t\tFMOD_TF,\t\t\tprogs_detpack_mdl_FMOD_TF); // detpack\n\n\tFMod_AddModel (\"progs/s_bubble.spr\",\t\tFMOD_DM | FMOD_TF,\tprogs_s_bubble_spr_FMOD_DM_FMOD_TF);\n\tFMod_AddModel (\"progs/s_explod.spr\",\t\tFMOD_DM | FMOD_TF,\tprogs_s_explod_spr_FMOD_DM_FMOD_TF);\n\n\tlastid = FMod_AddModel (\"maps/b_bh100.bsp\",\t\t\tFMOD_DM | FMOD_TF,\tmaps_b_bh100_bsp_FMOD_DM_FMOD_TF);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_b_bh100);\n\tFMod_AddModelAlt(lastid, &mdlhash_ruohis_b_bh100_other);\n\tFMod_AddModelAlt(lastid, &mdlhash_generations_b_bh100);\n\n\t//Wav files\n\tlastid = FMod_AddModel (\"sound/buttons/airbut1.wav\",\tFMOD_DM | FMOD_TF,\tsound_buttons_airbut1_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_buttons_mindgrid_airbut1);\n\t\tFMod_AddModelAlt(lastid, &sound_buttons_rerelease_airbut1);\n\tlastid = FMod_AddModel (\"sound/items/armor1.wav\",\tFMOD_DM | FMOD_TF,\tsound_items_armor1_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_armor1);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_armor1);\n\tlastid = FMod_AddModel (\"sound/items/damage.wav\",\tFMOD_DM | FMOD_TF,\tsound_items_damage_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_damage);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_damage);\n\tlastid = FMod_AddModel (\"sound/items/damage2.wav\",\tFMOD_DM | FMOD_TF,\tsound_items_damage2_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_damage2);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_damage2);\n\tlastid = FMod_AddModel (\"sound/items/damage3.wav\",\tFMOD_DM | FMOD_TF,\tsound_items_damage3_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_damage3);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_damage3);\n\tlastid = FMod_AddModel (\"sound/items/health1.wav\",\tFMOD_DM | FMOD_TF,\tsound_items_health1_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_health1);\n\tlastid = FMod_AddModel (\"sound/items/inv1.wav\",\t\tFMOD_DM | FMOD_TF,\tsound_items_inv1_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_inv1);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_inv1);\n\tlastid = FMod_AddModel (\"sound/items/inv2.wav\",\t\tFMOD_DM | FMOD_TF,\tsound_items_inv2_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_inv2);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_inv2);\n\tlastid = FMod_AddModel (\"sound/items/inv3.wav\",\t\tFMOD_DM | FMOD_TF,\tsound_items_inv3_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_inv3);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_inv3);\n\tlastid = FMod_AddModel (\"sound/items/itembk2.wav\",\tFMOD_DM | FMOD_TF,\tsound_items_itembk2_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_itembk2);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_itembk2);\n\tlastid = FMod_AddModel (\"sound/player/land.wav\",\t\tFMOD_DM | FMOD_TF,\tsound_player_land_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_player_rerelease_land);\n\tlastid = FMod_AddModel (\"sound/player/land2.wav\",\tFMOD_DM | FMOD_TF,\tsound_player_land2_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_player_rerelease_land2);\n\tlastid = FMod_AddModel (\"sound/misc/outwater.wav\",\tFMOD_DM | FMOD_TF,\tsound_misc_outwater_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_misc_mindgrid_outwater);\n\t\tFMod_AddModelAlt(lastid, &sound_misc_rerelease_outwater);\n\tlastid = FMod_AddModel (\"sound/weapons/pkup.wav\",\tFMOD_DM | FMOD_TF,\tsound_weapons_pkup_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_weapons_mindgrid_pkup);\n\t\tFMod_AddModelAlt(lastid, &sound_weapons_rerelease_pkup);\n\tlastid = FMod_AddModel (\"sound/player/plyrjmp8.wav\",\tFMOD_DM | FMOD_TF,\tsound_player_plyrjmp8_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_player_mindgrid_plyrjmp8);\n\t\tFMod_AddModelAlt(lastid, &sound_player_rerelease_plyrjmp8);\n\tlastid = FMod_AddModel (\"sound/items/protect.wav\",\tFMOD_DM | FMOD_TF,\tsound_items_protect_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_protect);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_protect);\n\tlastid = FMod_AddModel (\"sound/items/protect2.wav\",\tFMOD_DM | FMOD_TF,\tsound_items_protect2_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_protect2);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_protect2);\n\tlastid = FMod_AddModel (\"sound/items/protect3.wav\",\tFMOD_DM | FMOD_TF,\tsound_items_protect3_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_protect3);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_protect3);\n\tlastid = FMod_AddModel (\"sound/items/r_item1.wav\",\tFMOD_DM | FMOD_TF,\tsound_items_r_item1_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_r_item1);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_r_item1);\n\n\tlastid = FMod_AddModel (\"sound/items/r_item2.wav\",\tFMOD_DM | FMOD_TF,\tsound_items_r_item2_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_items_r_item2_wav_us);\n\t\tFMod_AddModelAlt(lastid, &sound_items_r_item2_wav_ru);\n\t\tFMod_AddModelAlt(lastid, &sound_items_mindgrid_r_item2);\n\t\tFMod_AddModelAlt(lastid, &sound_items_rerelease_r_item2);\n\n\tlastid = FMod_AddModel (\"sound/misc/water1.wav\",\t\tFMOD_DM | FMOD_TF,\tsound_misc_water1_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_misc_mindgrid_water1);\n\t\tFMod_AddModelAlt(lastid, &sound_misc_rerelease_water1);\n\tlastid = FMod_AddModel (\"sound/misc/water2.wav\",\t\tFMOD_DM | FMOD_TF,\tsound_misc_water2_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_misc_mindgrid_water2);\n\t\tFMod_AddModelAlt(lastid, &sound_misc_rerelease_water2);\n\n\tlastid = FMod_AddModel(\"sound/misc/menu1.wav\", FMOD_DM | FMOD_TF, sound_misc_menu1_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_misc_rerelease_menu1);\n\tlastid = FMod_AddModel(\"sound/misc/menu2.wav\", FMOD_DM | FMOD_TF, sound_misc_menu2_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_misc_rerelease_menu2);\n\tlastid = FMod_AddModel(\"sound/misc/menu3.wav\", FMOD_DM | FMOD_TF, sound_misc_menu3_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_misc_rerelease_menu3);\n\tlastid = FMod_AddModel(\"sound/misc/talk.wav\", FMOD_DM | FMOD_TF, sound_misc_talk_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_misc_rerelease_talk);\n\tlastid = FMod_AddModel(\"sound/misc/basekey.wav\", FMOD_DM | FMOD_TF, sound_misc_basekey_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt (lastid, &sound_gpl_maps_silence_wav);\n\t\tFMod_AddModelAlt(lastid, &sound_misc_rerelease_basekey);\n\tlastid = FMod_AddModel(\"sound/doors/runeuse.wav\", FMOD_DM | FMOD_TF, sound_doors_runeuse_wav_FMOD_DM_FMOD_TF);\n\t\tFMod_AddModelAlt(lastid, &sound_doors_rerelease_runeuse);\n\n\tFMod_AddModel (\"gfx/colormap.lmp\",\t\t\tFMOD_DM | FMOD_TF,\tgfx_colormap_lmp_FMOD_DM_FMOD_TF);\n\tFMod_AddModel (\"gfx/palette.lmp\",\t\t\tFMOD_DM | FMOD_TF,\tgfx_palette_lmp_FMOD_DM_FMOD_TF);\n}\n\nchar *FMod_Response_Text(void)\n{\n\tstatic char buf[512];\n\tint i, count;\n\tqbool relevent;\n\n\tstrlcpy(buf, \"modified:\", sizeof(buf));\n\n\tfor (i = count = 0; i < check_models_num; i++) {\n\t\trelevent = (cl.teamfortress && (check_models[i].flags & FMOD_TF)) || \n\t\t\t(!cl.teamfortress && (check_models[i].flags & FMOD_DM));\n\n\t\tif (check_models[i].checked && check_models[i].modified && relevent ) {\n\t\t\tif (strlen (buf) < 240) {\n\t\t\t\tstrlcat (buf, va(\" %s\", COM_SkipPath (check_models[i].name)), sizeof (buf));\n\t\t\t\tcount++;\n\t\t\t} else {\n\t\t\t\tstrlcat (buf, \" & more...\", sizeof (buf));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!count)\n\t\tstrlcpy (buf, \"all models ok\", sizeof (buf));\n\t\n\treturn buf;\n}\n\nvoid FMod_Response (void)\n{\n\tCbuf_AddText (va (\"%s %s\\n\", cls.state == ca_disconnected ? \"echo\" : \"say\", FMod_Response_Text()));\n}\n"
  },
  {
    "path": "src/fmod.h",
    "content": "/*\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: fmod.h,v 1.3 2007-09-14 13:29:28 disconn3ct Exp $\n*/\n\n#ifndef __FMOD_H__\n#define __FMOD_H__\n\nvoid FMod_Init (void);\nvoid FMod_CheckModel (const char *name, const void *buf, const size_t len);\nchar *FMod_Response_Text(void);\nvoid FMod_Response (void);\n\n#endif /* !__FMOD_H__ */\n"
  },
  {
    "path": "src/fonts.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// fonts.c\n#ifdef EZ_FREETYPE_SUPPORT\n#include <ft2build.h>\n#include FT_FREETYPE_H\n#include \"pcre2.h\"\n#endif\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_texture.h\"\n#include \"fonts.h\"\n#include \"r_renderer.h\"\n#include \"rulesets.h\"\n\n#ifdef EZ_FREETYPE_SUPPORT\ntypedef struct glyphinfo_s {\n\tfloat offsets[2];\n\tint sizes[2];\n\tfloat advance[2];\n\tqbool loaded;\n} glyphinfo_t;\n\n#ifdef EZ_FREETYPE_SUPPORT_STATIC\n#define ezFT_Init_FreeType   FT_Init_FreeType\n#define ezFT_New_Face        FT_New_Face\n#define ezFT_Done_FreeType   FT_Done_FreeType\n#define ezFT_Set_Pixel_Sizes FT_Set_Pixel_Sizes\n#define ezFT_Load_Char       FT_Load_Char\n#define ezFT_Render_Glyph    FT_Render_Glyph\n#define ezCloseFTDLL(x)\n#else\n#define ezCloseFTDLL(dll)    if (dll) { Sys_DLClose(dll); }\n#endif\n\nstatic glyphinfo_t glyphs[4096];\nstatic float max_glyph_width;\nstatic float max_num_glyph_width;\n\n#define FONT_TEXTURE_SIZE 1024\ncharset_t proportional_fonts[MAX_CHARSETS];\n\nint Font_Load(const char* path);\nstatic void OnChange_font_facepath(cvar_t* cvar, char* newvalue, qbool* cancel);\n\nstatic cvar_t font_facepath                   = { \"font_facepath\", \"\", 0, OnChange_font_facepath };\nstatic cvar_t font_capitalize                 = { \"font_capitalize\", \"0\" };\nstatic cvar_t font_gradient_normal_color1     = { \"font_gradient_normal_color1\", \"255 255 255\", CVAR_COLOR };\nstatic cvar_t font_gradient_normal_color2     = { \"font_gradient_normal_color2\", \"107 98 86\", CVAR_COLOR };\nstatic cvar_t font_gradient_normal_percent    = { \"font_gradient_normal_percent\", \"0.2\" };\nstatic cvar_t font_gradient_alternate_color1  = { \"font_gradient_alternate_color1\", \"175 120 52\", CVAR_COLOR };\nstatic cvar_t font_gradient_alternate_color2  = { \"font_gradient_alternate_color2\", \"75 52 22\", CVAR_COLOR };\nstatic cvar_t font_gradient_alternate_percent = { \"font_gradient_alternate_percent\", \"0.4\" };\nstatic cvar_t font_gradient_number_color1     = { \"font_gradient_number_color1\", \"255 255 150\", CVAR_COLOR };\nstatic cvar_t font_gradient_number_color2     = { \"font_gradient_number_color2\", \"218 132 7\", CVAR_COLOR };\nstatic cvar_t font_gradient_number_percent    = { \"font_gradient_number_percent\", \"0.2\" };\nstatic cvar_t font_outline                    = { \"font_outline_width\", \"2\" };\n\ntypedef struct gradient_def_s {\n\tcvar_t* top_color;\n\tcvar_t* bottom_color;\n\tcvar_t* gradient_start;\n} gradient_def_t;\n\nstatic gradient_def_t standard_gradient = { &font_gradient_normal_color1, &font_gradient_normal_color2, &font_gradient_normal_percent };\nstatic gradient_def_t brown_gradient    = { &font_gradient_alternate_color1, &font_gradient_alternate_color2, &font_gradient_alternate_percent };\nstatic gradient_def_t numbers_gradient  = { &font_gradient_number_color1, &font_gradient_number_color2, &font_gradient_number_percent };\n\nstatic void FontSetColor(byte* color, byte alpha, gradient_def_t* gradient, float relative_y)\n{\n\tfloat base_color[3];\n\tfloat grad_start = bound(0, gradient->gradient_start->value, 1);\n\n\tif (relative_y <= grad_start) {\n\t\tVectorScale(gradient->top_color->color, 1.0f / 255, base_color);\n\t}\n\telse {\n\t\tfloat mix = (relative_y - grad_start) / (1.0f - grad_start);\n\n\t\tVectorScale(gradient->top_color->color, (1.0f - mix) / 255.0f, base_color);\n\t\tVectorMA(base_color, mix / 255.0f, gradient->bottom_color->color, base_color);\n\t}\n\n\tcolor[0] = min(1, base_color[0]) * alpha;\n\tcolor[1] = min(1, base_color[1]) * alpha;\n\tcolor[2] = min(1, base_color[2]) * alpha;\n\tcolor[3] = alpha;\n}\n\n// Very simple: boost the alpha of every pixel to strongest alpha nearby\n// - small adjustment made for distance\n// - not a great solution but works for the moment\n// - can supply matrix to freetype, should use that instead\nstatic void SimpleOutline(byte* image_buffer, int base_font_width, int base_font_height)\n{\n\tint x, y;\n\tint xdiff, ydiff;\n\tbyte* font_buffer = Q_malloc(base_font_width * base_font_height * 4);\n\tconst int search_distance = bound(0, font_outline.integer, 4);\n\n\tmemcpy(font_buffer, image_buffer, base_font_width * base_font_height * 4);\n\tfor (x = 0; x < base_font_width; ++x) {\n\t\tfor (y = 0; y < base_font_height; ++y) {\n\t\t\tint base = (x + y * base_font_width) * 4;\n\t\t\tint best_alpha = 0;\n\n\t\t\tif (font_buffer[base + 3] == 255) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (xdiff = max(0, x - search_distance); xdiff <= min(x + search_distance, base_font_width - 1); ++xdiff) {\n\t\t\t\tfor (ydiff = max(0, y - search_distance); ydiff <= min(y + search_distance, base_font_height - 1); ++ydiff) {\n\t\t\t\t\tfloat dist = abs(x - xdiff) + abs(y - ydiff);\n\t\t\t\t\tint this_alpha = font_buffer[(xdiff + ydiff * base_font_width) * 4 + 3] / (0.3 * (dist + 1));\n\n\t\t\t\t\tif (this_alpha >= best_alpha) {\n\t\t\t\t\t\tbest_alpha = min(255, this_alpha);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\timage_buffer[base + 3] = best_alpha;\n\t\t}\n\t}\n\n\tQ_free(font_buffer);\n}\n\nstatic void FontLoadBitmap(int ch, FT_Face face, int base_font_width, int base_font_height, byte* image_buffer, gradient_def_t* gradient)\n{\n\tbyte* font_buffer;\n\tint x, y;\n\tint outline = bound(0, font_outline.integer, 4);\n\n\tglyphs[ch].loaded = true;\n\tglyphs[ch].advance[0] = ((face->glyph->metrics.horiAdvance / 64.0f) + (2 * outline)) / (base_font_width / 2);\n\tglyphs[ch].advance[1] = ((face->glyph->metrics.vertAdvance / 64.0f) + (2 * outline)) / (base_font_height / 2);\n\tglyphs[ch].offsets[0] = face->glyph->metrics.horiBearingX / 64.0f - (outline);\n\tglyphs[ch].offsets[1] = face->glyph->metrics.horiBearingY / 64.0f - (outline);\n\tglyphs[ch].sizes[0] = face->glyph->bitmap.width;\n\tglyphs[ch].sizes[1] = face->glyph->bitmap.rows;\n\n\tmax_glyph_width = max(max_glyph_width, glyphs[ch].advance[0] * 8);\n\tif ((ch >= '0' && ch <= '9') || ch == '+' || ch == '-') {\n\t\tmax_num_glyph_width = max(max_num_glyph_width, glyphs[ch].advance[0] * 8);\n\t}\n\n\tfont_buffer = face->glyph->bitmap.buffer;\n\tfor (y = 0; y < face->glyph->bitmap.rows && y + outline < base_font_height - 4; ++y, font_buffer += face->glyph->bitmap.pitch) {\n\t\tfor (x = 0; x < face->glyph->bitmap.width && x + outline < base_font_width - 4; ++x) {\n\t\t\tint base = (x + outline + (y + outline) * base_font_width) * 4;\n\t\t\tbyte alpha = font_buffer[x];\n\n\t\t\tFontSetColor(&image_buffer[base], alpha, gradient, y * 1.0f / face->glyph->bitmap.rows);\n\t\t}\n\t}\n\n\tif (outline) {\n\t\tSimpleOutline(image_buffer, base_font_width, base_font_height);\n\t}\n}\n\nstatic void FontClear(int grouping)\n{\n\tcharset_t* charset;\n\n\tmemset(glyphs, 0, sizeof(glyphs));\n\tmax_glyph_width = max_num_glyph_width = 0;\n\n\tcharset = &proportional_fonts[grouping];\n\tmemset(charset, 0, sizeof(*charset));\n}\n\nstatic qbool FontCreate(int grouping, const char* userpath)\n{\n\tchar path[MAX_OSPATH];\n\tFT_Library library;\n\tFT_Error error;\n\tFT_Face face;\n\tint ch;\n\tbyte* temp_buffer;\n\tbyte* full_buffer;\n\tint original_width, original_height, original_left, original_top;\n\tint texture_width, texture_height;\n\tint base_font_width, base_font_height;\n\tint baseline_offset;\n\tcharset_t* charset;\n\tint outline_width = bound(0, font_outline.integer, 4);\n#ifndef EZ_FREETYPE_SUPPORT_STATIC\n\t// freetyped.lib;\n\tDL_t dll = NULL;\n\tFT_Error(*ezFT_Init_FreeType)(FT_Library* library) = NULL;\n\tFT_Error(*ezFT_New_Face)(FT_Library library, const char* filepathname, FT_Long face_index, FT_Face* aface) = NULL;\n\tFT_Error(*ezFT_Done_FreeType)(FT_Library library) = NULL;\n\tFT_Error(*ezFT_Set_Pixel_Sizes)(FT_Face face, FT_UInt pixel_width, FT_UInt pixel_height) = NULL;\n\tFT_Error(*ezFT_Load_Char)(FT_Face face, FT_ULong char_code, FT_Int32 load_flags) = NULL;\n\tFT_Error(*ezFT_Render_Glyph)(FT_GlyphSlot slot, FT_Render_Mode render_mode) = NULL;\n\n\tif (Rulesets_RestrictPacket()) {\n\t\tCon_Printf(\"Cannot change font during match\\n\");\n\t\treturn false;\n\t}\n\n\tdll = Sys_DLOpen(\"freetype.dll\");\n\tif (dll) {\n\t\tezFT_Init_FreeType = Sys_DLProc(dll, \"FT_Init_FreeType\");\n\t\tezFT_New_Face = Sys_DLProc(dll, \"FT_New_Face\");\n\t\tezFT_Done_FreeType = Sys_DLProc(dll, \"FT_Done_FreeType\");\n\t\tezFT_Set_Pixel_Sizes = Sys_DLProc(dll, \"FT_Set_Pixel_Sizes\");\n\t\tezFT_Load_Char = Sys_DLProc(dll, \"FT_Load_Char\");\n\t\tezFT_Render_Glyph = Sys_DLProc(dll, \"FT_Render_Glyph\");\n\t}\n\n\tif (ezFT_Init_FreeType == NULL || ezFT_New_Face == NULL || ezFT_Done_FreeType == NULL ||\n\t\tezFT_Set_Pixel_Sizes == NULL || ezFT_Load_Char == NULL || ezFT_Render_Glyph == NULL) {\n\t\tCon_Printf(\"Unable to load freetype library, proportional fonts not available\\n\");\n\t\tezCloseFTDLL(dll);\n\t\treturn false;\n\t}\n#endif\n\n\terror = ezFT_Init_FreeType(&library);\n\tif (error) {\n\t\tCon_Printf(\"Error during freetype initialisation\\n\");\n\t\tezCloseFTDLL(dll);\n\t\treturn false;\n\t}\n\n\tstrlcpy(path, Sys_FontsDirectory(), sizeof(path));\n\tstrlcat(path, \"/\", sizeof(path));\n\tstrlcat(path, userpath, sizeof(path));\n\tCOM_DefaultExtension(path, \".ttf\", sizeof(path));\n\n\terror = ezFT_New_Face(library, path, 0, &face);\n\tif (error == FT_Err_Unknown_File_Format) {\n\t\tCon_Printf(\"Font file found, but format is unsupported\\n\");\n\t\tezCloseFTDLL(dll);\n\t\treturn false;\n\t}\n\telse if (error) {\n\t\tCon_Printf(\"Font file could not be opened\\n\");\n\t\tCon_Printf(\"> Attempted path: %s\\n\", path);\n\t\tezCloseFTDLL(dll);\n\t\treturn false;\n\t}\n\n\tcharset = &proportional_fonts[grouping];\n\tif (R_TextureReferenceIsValid(charset->master)) {\n\t\toriginal_width = R_TextureWidth(charset->master);\n\t\toriginal_height = R_TextureHeight(charset->master);\n\t\toriginal_left = 0;\n\t\toriginal_top = 0;\n\t\ttexture_width = original_width;\n\t\ttexture_height = original_height;\n\t}\n\telse {\n\t\toriginal_width = texture_width = FONT_TEXTURE_SIZE * 2;\n\t\toriginal_height = texture_height = FONT_TEXTURE_SIZE * 2;\n\t\toriginal_left = original_top = 0;\n\n\t\trenderer.TextureCreate2D(&charset->master, texture_width, texture_height, \"font\", false);\n\t}\n\tbase_font_width = texture_width / 16;\n\tbase_font_height = texture_height / 16;\n\tbaseline_offset = 0;\n\n\tmemset(glyphs, 0, sizeof(glyphs));\n\tmax_glyph_width = max_num_glyph_width = 0;\n\n\tezFT_Set_Pixel_Sizes(\n\t\tface,\n\t\tbase_font_width / 2 - 2 * outline_width,\n\t\tbase_font_height / 2 - 2 * outline_width\n\t);\n\n\ttemp_buffer = full_buffer = Q_malloc(4 * base_font_width * base_font_height * 256);\n\tfor (ch = 18; ch < 128; ++ch, temp_buffer += 4 * base_font_width * base_font_height) {\n\t\tFT_UInt glyph_index;\n\t\tint offset128 = 4 * base_font_width * base_font_height * 128;\n\t\tint offsetCaps = 4 * base_font_width * base_font_height * ('a' - 'A');\n\n\t\tif (ch >= 28 && ch < 32) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (font_capitalize.integer && ch >= 'a' && ch <= 'z') {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (ch < 32) {\n\t\t\tglyph_index = ezFT_Load_Char(face, '0' + (ch - 18), FT_LOAD_RENDER);\n\t\t}\n\t\telse {\n\t\t\tglyph_index = ezFT_Load_Char(face, ch, FT_LOAD_RENDER);\n\t\t}\n\n\t\tif (glyph_index) {\n\t\t\tcontinue;\n\t\t}\n\n\t\terror = ezFT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);\n\t\tif (error) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (ch < 32) {\n\t\t\tFontLoadBitmap(ch, face, base_font_width, base_font_height, temp_buffer, &numbers_gradient);\n\t\t\tFontLoadBitmap(ch + 128, face, base_font_width, base_font_height, temp_buffer + offset128, &numbers_gradient);\n\t\t}\n\t\telse {\n\t\t\tFontLoadBitmap(ch, face, base_font_width, base_font_height, temp_buffer, &standard_gradient);\n\t\t\tFontLoadBitmap(ch + 128, face, base_font_width, base_font_height, temp_buffer + offset128, &brown_gradient);\n\n\t\t\tif (font_capitalize.integer && ch >= 'A' && ch <= 'Z') {\n\t\t\t\tFontLoadBitmap(ch + ('a' - 'A'), face, base_font_width, base_font_height, temp_buffer + offsetCaps, &standard_gradient);\n\t\t\t\tFontLoadBitmap(ch + ('a' - 'A') + 128, face, base_font_width, base_font_height, temp_buffer + offset128 + offsetCaps, &brown_gradient);\n\t\t\t}\n\t\t}\n\t}\n\tezFT_Done_FreeType(library);\n\tezCloseFTDLL(dll);\n\n\t// Work out where the baseline is...\n\t{\n\t\tint max_beneath = 0;\n\t\tint beneath_baseline;\n\n\t\tfor (ch = 18; ch < 128; ++ch) {\n\t\t\tif (!glyphs[ch].loaded) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tbeneath_baseline = glyphs[ch].sizes[1] - glyphs[ch].offsets[1];\n\t\t\tmax_beneath = max(max_beneath, beneath_baseline);\n\t\t}\n\n\t\tbaseline_offset = base_font_height / 2 - 1 - max_beneath - outline_width;\n\t}\n\n\t// Update charset image\n\ttemp_buffer = full_buffer;\n\tmemset(charset->glyphs, 0, sizeof(charset->glyphs));\n\tfor (ch = 18; ch < 256; ++ch, temp_buffer += 4 * base_font_width * base_font_height) {\n\t\tint xbase = (ch % 16) * base_font_width;\n\t\tint ybase = (ch / 16) * base_font_height;\n\t\tint yoffset = max(0, baseline_offset - glyphs[ch].offsets[1]);\n\t\tfloat width = max(base_font_width / 2, glyphs[ch].sizes[0]);\n\t\tfloat height = max(base_font_height / 2, glyphs[ch].sizes[1]);\n\n\t\tif (!glyphs[ch].loaded) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (yoffset) {\n\t\t\tmemmove(temp_buffer + yoffset * base_font_width * 4, temp_buffer, (base_font_height - yoffset) * base_font_width * 4);\n\t\t\tmemset(temp_buffer, 0, yoffset * base_font_width * 4);\n\t\t}\n\n\t\tglyphs[ch].offsets[0] /= (base_font_width / 2);\n\t\tglyphs[ch].offsets[1] /= (base_font_height / 2);\n\n\t\tcharset->glyphs[ch].width = width;\n\t\tcharset->glyphs[ch].height = height;\n\t\tcharset->glyphs[ch].sl = (original_left + xbase) * 1.0f / texture_width;\n\t\tcharset->glyphs[ch].tl = (original_top + ybase) * 1.0f / texture_height;\n\t\tcharset->glyphs[ch].sh = charset->glyphs[ch].sl + width * 1.0f / texture_width;\n\t\tcharset->glyphs[ch].th = charset->glyphs[ch].tl + height * 1.0f / texture_height;\n\t\tcharset->glyphs[ch].texnum = charset->master;\n\n\t\trenderer.TextureReplaceSubImageRGBA(charset->master, original_left + xbase, original_top + ybase, base_font_width, base_font_height, temp_buffer);\n\t}\n\tQ_free(full_buffer);\n\tcharset->custom_scale_x = charset->custom_scale_y = 1;\n\n\tCachePics_MarkAtlasDirty();\n\n\treturn true;\n}\n\n// Used for allocating space - if we just measure the string then other hud elements\n//   might move around as content changes, which is probably not what is wanted\nint FontFixedWidth(int max_length, float scale, qbool digits_only, qbool proportional)\n{\n\tif (!proportional || !R_TextureReferenceIsValid(proportional_fonts[0].master)) {\n\t\treturn max_length * 8 * scale;\n\t}\n\n\treturn ceil((digits_only ? max_num_glyph_width : max_glyph_width) * max_length * scale);\n}\n\nstatic void OnChange_font_facepath(cvar_t* cvar, char* newvalue, qbool* cancel)\n{\n\tif (newvalue && !newvalue[0]) {\n\t\tFontClear(0);\n\t\t*cancel = false;\n\t}\n\telse {\n\t\t*cancel = !FontCreate(0, Cmd_Argv(1));\n\t}\n}\n\nvoid Draw_LoadFont_f(void)\n{\n\tswitch (Cmd_Argc()) {\n\tcase 1:\n\t\tif (font_facepath.string[0]) {\n\t\t\tCom_Printf(\"Current proportional font is \\\"%s\\\"\\n\", font_facepath.string);\n\t\t\tCom_Printf(\"To clear, use \\\"%s none\\\"\\n\", Cmd_Argv(0));\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"No proportional font loaded\\n\");\n\t\t}\n\t\tbreak;\n\tcase 2:\n\t\tif (!strcmp(Cmd_Argv(1), \"none\")) {\n\t\t\tCvar_Set(&font_facepath, \"\");\n\t\t}\n\t\telse {\n\t\t\tCvar_Set(&font_facepath, Cmd_Argv(1));\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tCom_Printf(\"Usage: \\\"%s <path>\\\" or \\\"%s none\\\"\\n\", Cmd_Argv(0), Cmd_Argv(0));\n\t\tbreak;\n\t}\n}\n\n// FIXME, should be in client but relies on Sys_listdir\n#ifndef CLIENTONLY\nvoid Draw_ListFonts_f(void)\n{\n\tchar path[MAX_OSPATH];\n\tdir_t dir;\n\tint i, printed;\n\tpcre2_code* regexp = NULL;\n\tpcre2_match_data *match_data = NULL;\n\n\tstrlcpy(path, Sys_FontsDirectory(), sizeof(path));\n\n\tif (Cmd_Argc() > 1) {\n\t\tint error = 0;\n\t\tPCRE2_SIZE error_offset = 0;\n\n\t\tregexp = pcre2_compile((PCRE2_SPTR)Cmd_Argv(1), PCRE2_ZERO_TERMINATED, PCRE2_CASELESS, &error, &error_offset, NULL);\n\t\tif (!regexp) {\n\t\t\tPCRE2_UCHAR error_str[256];\n\t\t\tpcre2_get_error_message(error, error_str, sizeof(error_str));\n\t\t\tCon_Printf(\"Error in regular expression: %s\\n\", error_str);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tdir = Sys_listdir(path, \".ttf\", SORT_BY_NAME);\n\tfor (i = 0, printed = 0; i < dir.numfiles; ++i) {\n\t\tif (dir.files[i].isdir) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (regexp) {\n\t\t\tmatch_data = pcre2_match_data_create_from_pattern(regexp, NULL);\n\t\t\tif (pcre2_match(regexp, (PCRE2_SPTR)dir.files[i].name, strlen(dir.files[i].name), 0, 0, match_data, NULL) < 0) {\n\t\t\t\tpcre2_match_data_free(match_data);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpcre2_match_data_free(match_data);\n\t\t}\n\n\t\tCon_Printf(\"  %s\\n\", dir.files[i].name);\n\t\tprinted++;\n\t}\n\n\tif (regexp) {\n\t\tpcre2_code_free(regexp);\n\t}\n\tCon_Printf(\"Found %d/%d files in %s\\n\", printed, dir.numfiles, path);\n}\n#endif\n\nvoid FontInitialise(void)\n{\n\tCmd_AddCommand(\"fontload\", Draw_LoadFont_f);\n#ifndef CLIENTONLY // FIXME\n\tCmd_AddCommand(\"fontlist\", Draw_ListFonts_f);\n#endif\n\n\tCvar_Register(&font_facepath);\n\tCvar_Register(&font_capitalize);\n\tCvar_Register(&font_gradient_normal_color1);\n\tCvar_Register(&font_gradient_normal_color2);\n\tCvar_Register(&font_gradient_normal_percent);\n\tCvar_Register(&font_gradient_alternate_color1);\n\tCvar_Register(&font_gradient_alternate_color2);\n\tCvar_Register(&font_gradient_alternate_percent);\n\tCvar_Register(&font_gradient_number_color1);\n\tCvar_Register(&font_gradient_number_color2);\n\tCvar_Register(&font_gradient_number_percent);\n\tCvar_Register(&font_outline);\n}\n\n#endif // EZ_FREETYPE_SUPPORT\n\nqbool FontAlterCharCoordsWide(float* x, float* y, wchar ch, qbool bigchar, float scale, qbool proportional)\n{\n\tint char_size = (bigchar ? 64 : 8);\n\n\t// Totally off screen.\n\tif (*y <= (-char_size * scale)) {\n\t\treturn false;\n\t}\n\n\t// Space (& 'red' version).\n\tif (ch == 32 || ch == (32 | 128)) {\n\t\t*x += FontCharacterWidthWide(ch, scale, proportional);\n\t\treturn false;\n\t}\n\n#ifdef EZ_FREETYPE_SUPPORT\n\tif (proportional && ch < sizeof(glyphs) / sizeof(glyphs[0]) && glyphs[ch].loaded) {\n\t\t*x += glyphs[ch].offsets[0] * char_size * scale;\n\t}\n#endif\n\n\treturn true;\n}\n\nfloat FontCharacterWidthWide(wchar ch, float scale, qbool proportional)\n{\n\tint charset = ((ch >> 8) & 0xFF);\n\tfloat width = 8 * scale;\n\n#ifdef EZ_FREETYPE_SUPPORT\n\tif (proportional && ch < sizeof(glyphs) / sizeof(glyphs[0]) && glyphs[ch].loaded) {\n\t\treturn width * glyphs[ch].advance[0];\n\t}\n#endif\n\n\tif (charset >= 0 && charset < sizeof(char_textures) / sizeof(char_textures[0]) && char_textures[charset].custom_scale_x != 0) {\n\t\twidth *= char_textures[charset].custom_scale_x;\n\t}\n\n\treturn width;\n}\n\nfloat FontCharacterWidth(char ch_, float scale, qbool proportional)\n{\n\tint charset = 0;\n\n#ifdef EZ_FREETYPE_SUPPORT\n\tint ch = (unsigned char)(ch_);\n\n\tif (proportional && ch < sizeof(glyphs) / sizeof(glyphs[0]) && glyphs[ch].loaded) {\n\t\treturn 8 * glyphs[ch].advance[0] * scale;\n\t}\n#endif\n\n\tif (charset < 0 || charset >= MAX_CHARSETS || char_textures[charset].custom_scale_x == 0) {\n\t\treturn 8 * scale;\n\t}\n\n\treturn 8 * char_textures[charset].custom_scale_x * scale;\n}\n\nvoid Draw_InitFont(void)\n{\n#ifdef EZ_FREETYPE_SUPPORT\n\tif (!font_facepath.string[0]) {\n\t\tFontClear(0);\n\t}\n\telse {\n\t\tFontCreate(0, font_facepath.string);\n\t}\n#endif\n}\n"
  },
  {
    "path": "src/fonts.h",
    "content": "\n#ifndef EZQUAKE_FONTS_HEADER\n#define EZQUAKE_FONTS_HEADER\n\n#ifdef EZ_FREETYPE_SUPPORT\nvoid FontInitialise(void);\nint FontFixedWidth(int max_length, float scale, qbool digits_only, qbool proportional);\n#else\n#define FontFixedWidth(max_length, scale, digits_only, proportional) (max_length * 8.0f * scale)\n#endif\n\nqbool FontAlterCharCoordsWide(float* x, float* y, wchar ch, qbool bigchar, float scale, qbool proportional);\nfloat FontCharacterWidthWide(wchar ch, float scale, qbool proportional);\nfloat FontCharacterWidth(char ch, float scale, qbool proportional);\n\n#endif\n"
  },
  {
    "path": "src/fragstats.c",
    "content": "/*\nCopyright (C) 2003       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: fragstats.c,v 1.22 2007-10-04 14:56:55 dkure Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"vx_stuff.h\"\n#include \"vx_tracker.h\"\n#include \"utils.h\"\n\ncvar_t cl_parsefrags = {\"cl_parseFrags\", \"1\"};\ncvar_t cl_showFragsMessages = {\"con_fragmessages\", \"1\"};\ncvar_t cl_loadFragfiles = {\"cl_loadFragfiles\", \"1\"};\n\ncvar_t cl_useimagesinfraglog = {\"cl_useimagesinfraglog\", \"0\"};\n\n#define FUH_FRAGFILE_VERSION_1_00\t\"1.00\" /* for compatibility with fuh */\n#define FRAGFILE_VERSION_1_00\t\t\"ezquake-1.00\" /* fuh suggest such format */\n\n// The following values are fetched from nquake.pk3 and\n// misc/fragfile/fragfile.dat respectively.\n#define EZQUAKE_SG_QUAD   \"&c06fQ&r-sg\"\n#define EZQUAKE_SSG_QUAD  \"&c06fQ&r-ssg\"\n#define EZQUAKE_SNG_QUAD  \"&c06fQ&r-sng\"\n#define EZQUAKE_RL_QUAD   \"&c06fQ&r-rl\"\n#define EZQUAKE_LG_QUAD   \"&c06fQ&r-lg\"\n#define EZQUAKE_QUAD      \"&c06fQ&r-\"\n#define NQUAKE_SG_QUAD    \"sg &c06fQ&r\"\n#define NQUAKE_SSG_QUAD   \"ssg &c06fQ&r\"\n#define NQUAKE_SNG_QUAD   \"sng &c06fQ&r\"\n#define NQUAKE_RL_QUAD    \"rl &c06fQ&r\"\n#define NQUAKE_LG_QUAD    \"lg &c06fQ&r\"\n#define NQUAKE_QUAD       \" &c06fQ&r\"\n#define WEAPON_CLASS_SG   \"sg\"\n#define WEAPON_CLASS_SSG  \"ssg\"\n#define WEAPON_CLASS_SNG  \"sng\"\n#define WEAPON_CLASS_RL   \"rl\"\n#define WEAPON_CLASS_LG   \"lg\"\n\nvoid Update_FlagStatus(int player_num, char *team, qbool got_flag);\n\ntypedef enum msgtype_s {\n\tmt_fragged,\n\tmt_frags,\n\tmt_tkills,\n\tmt_tkilled,\n\n\tmt_death,\n\tmt_suicide,\n\tmt_frag,\n\tmt_tkill,\n\tmt_tkilled_unk,\n\tmt_flagtouch,\n\tmt_flagdrop,\n\tmt_flagcap}\nmsgtype_t;\n\ntypedef struct wclass_s {\n\tchar\t*keyword;\n\tchar\t*name;\n\tchar\t*shortname;\n\tchar\t*imagename;\n\tchar    *colorname;\n\tint     colorname_rgb_offset;\n\tqbool   colorname_skip;\n} wclass_t;\n\ntypedef struct fragmsg_s {\n\tchar\t\t*msg1;\n\tchar\t\t*msg2;\n\tmsgtype_t\ttype;\n\tint\t\t\twclass_index;\n} fragmsg_t;\n\ntypedef struct fragdef_s {\n\tqbool\tactive;\n\tqbool\tflagalerts;\n\n\tint\t\t\tversion;\n\tchar\t\t*gamedir;\n\n\tchar\t\t*title;\n\tchar\t\t*description;\n\tchar\t\t*author;\n\tchar\t\t*email;\n\tchar\t\t*webpage;\n\n\tfragmsg_t\t*fragmsgs[MAX_FRAG_DEFINITIONS];\n\tfragmsg_t\tmsgdata[MAX_FRAG_DEFINITIONS];\n\tint\t\t\tnum_fragmsgs;\n} fragdef_t;\n\nstatic fragdef_t fragdefs;\nstatic wclass_t wclasses[MAX_WEAPON_CLASSES];\nstatic int num_wclasses;\n\nstatic int fragmsg1_indexes[26];\n\n\n#define MYISLOWER(c)\t(c >= 'a' && c <= 'z')\nint Compare_FragMsg (const void *p1, const void *p2) {\n\tunsigned char a, b;\n\tchar *s, *s1, *s2;\n\n\ts1 = (*((fragmsg_t **) p1))->msg1;\n\ts2 = (*((fragmsg_t **) p2))->msg1;\n\n\tfor (s = s1; *s && isspace(*s & 127); s++)\n\t\t;\n\ta = tolower(*s & 127);\n\n\tfor (s = s2; *s && isspace(*s & 127); s++)\n\t\t;\n\tb = tolower(*s & 127);\n\n\tif (MYISLOWER(a) && MYISLOWER(b)) {\n\t\treturn (a != b) ? a - b : -1 * strcasecmp(s1, s2);\n\t} else {\n\t\treturn MYISLOWER(a) ? 1 : MYISLOWER(b) ? -1 : a != b ? a - b : -1 * strcasecmp(s1, s2);\n\t}\n}\n\nstatic void Build_FragMsg_Indices(void) {\n\tint i, j = -1, c;\n\tchar *s;\n\n\tfor (i = 0; i < fragdefs.num_fragmsgs; i++) {\n\t\tfor (s = fragdefs.fragmsgs[i]->msg1; *s && isspace(*s & 127); s++)\n\t\t\t;\n\t\tc = tolower(*s & 127);\n\t\tif (!MYISLOWER(c))\n\t\t\tcontinue;\n\n\t\tif (c == 'a' + j)\n\t\t\tcontinue;\n\t\twhile (++j < c - 'a')\n\t\t\tfragmsg1_indexes[j] = -1;\n\n\t\tfragmsg1_indexes[j] = i;\n\t}\n\n\twhile (++j <= 'z' - 'a')\n\t\tfragmsg1_indexes[j] = -1;\n}\n\nstatic void InitFragDefs(qbool restart)\n{\n\tint i;\n\n\tQ_free(fragdefs.gamedir);\n\tQ_free(fragdefs.title);\n\tQ_free(fragdefs.author);\n\tQ_free(fragdefs.email);\n\tQ_free(fragdefs.webpage);\n\tQ_free(fragdefs.description);\n\n\tfor (i = 0; i < fragdefs.num_fragmsgs; i++) \n\t{\n\t\tQ_free(fragdefs.msgdata[i].msg1);\n\t\tQ_free(fragdefs.msgdata[i].msg2);\n\t}\n\n\tfor (i = 0; i < num_wclasses; i++) \n\t{\n\t\tQ_free(wclasses[i].keyword);\n\t\tQ_free(wclasses[i].name);\n\t\tQ_free(wclasses[i].shortname);\n\t\tQ_free(wclasses[i].imagename);\n\n\t\tif (wclasses[i].colorname != NULL)\n\t\t\tQ_free(wclasses[i].colorname);\n\t}\n\n\tmemset(&fragdefs, 0, sizeof(fragdefs));\n\tmemset(wclasses, 0, sizeof(wclasses));\n\n\tif (restart) {\n\t\twclasses[0].name = Q_strdup(\"Unknown\");\n\t\tnum_wclasses = 1;\n\t\tVX_TrackerInit();\n\t}\n}\n\n#define _checkargs(x)\t\tif (c != x) {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\tCom_Printf(\"Fragfile warning (line %d): %d tokens expected\\n\", line, x);\t\t\t\\\n\t\t\t\t\t\t\t\tgoto nextline;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t}\n\n#define _checkargs2(x, y)\tif (c != x && c != y) {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\tCom_Printf(\"Fragfile warning (line %d): %d or %d tokens expected\\n\", line, x, y);\t\\\n\t\t\t\t\t\t\t\tgoto nextline;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t}\n\n#define _checkargs3(x, y)\tif (c < x || c > y) {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\tCom_Printf(\"Fragfile warning (line %d): %d to %d tokens expected\\n\", line, x, y);\t\\\n\t\t\t\t\t\t\t\tgoto nextline;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t}\n\n\n#define\t_check_version_defined\tif (!gotversion) {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\tCom_Printf(\"Fragfile error: #FRAGFILE VERSION must be defined at the head of the file\\n\");\t\\\n\t\t\t\t\t\t\t\t\tgoto end;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t}\n\nstatic void LoadFragFile(char *filename, qbool quiet)\n{\n\tint c, line, i;\n\tmsgtype_t msgtype;\n\tchar save, *buffer = NULL, *start, *end, *token, fragfilename[MAX_OSPATH];\n\tqbool gotversion = false, warned_flagmsg_overflow = false;\n\n\tInitFragDefs(true);\n\n\tstrlcpy(fragfilename, filename, sizeof(fragfilename));\n\tCOM_ForceExtensionEx(fragfilename, \".dat\", sizeof(fragfilename));\n\n\t// if it fragfile.dat then try to load from ezquake dir first,\n\t// because we have a bit different fragfile format comparing to fuhquake\n\tif (!strcasecmp(fragfilename, \"fragfile.dat\") && (buffer = (char *) FS_LoadHeapFile(\"../ezquake/fragfile.dat\", NULL)))\n\t\tstrlcpy(fragfilename, \"ezquake/fragfile.dat\", sizeof(fragfilename));\n\n\tif (!buffer && !(buffer = (char *)FS_LoadHeapFile(fragfilename, NULL))) {\n\t\tif (!quiet)\n\t\t\tCom_Printf(\"Couldn't load fragfile \\\"%s\\\"\\n\", fragfilename);\n\t\treturn;\n\t}\n\n\tline = 1;\n\tstart = end = buffer;\n\twhile (1) {\n\t\tfor ( ; *end && *end != '\\n'; end++)\n\t\t\t;\n\n\t\tsave = *end;\n\t\t*end = 0;\n\t\tCmd_TokenizeString(start);\n\t\t*end = save;\n\n\t\tif (!(c = Cmd_Argc()))\n\t\t\tgoto nextline;\n\n\t\tif (!strcasecmp(Cmd_Argv(0), \"#FRAGFILE\")) {\n\n\t\t\t_checkargs(3);\n\t\t\tif (!strcasecmp(Cmd_Argv(1), \"VERSION\")) {\n\t\t\t\tif (gotversion) {\n\t\t\t\t\tCom_Printf(\"Fragfile error (line %d): VERSION redefined\\n\", line);\n\t\t\t\t\tgoto end;\n\t\t\t\t}\n\t\t\t\ttoken = Cmd_Argv(2);\n\t\t\t\tif (!strcasecmp(token, FUH_FRAGFILE_VERSION_1_00))\n\t\t\t\t\ttoken = FRAGFILE_VERSION_1_00; // so we compatible with old fuh format, because our format include all fuh features + our\n\n\t\t\t\tif (!strcasecmp(token, FRAGFILE_VERSION_1_00) && (token = strchr(token, '-'))) {\n\t\t\t\t\ttoken++; // find float component of string like this \"ezquake-x.yz\"\n\t\t\t\t\tfragdefs.version = (int) (100 * Q_atof(token));\n\t\t\t\t\tgotversion = true;\n\t\t\t\t} else {\n\t\t\t\t\tCom_Printf(\"Error: fragfile version (\\\"%s\\\") unsupported \\n\", Cmd_Argv(2));\n\t\t\t\t\tgoto end;\n\t\t\t\t}\n\t\t\t} else if (!strcasecmp(Cmd_Argv(1), \"GAMEDIR\")) {\n\t\t\t\t_check_version_defined;\n\t\t\t\tif (!strcasecmp(Cmd_Argv(2), \"ANY\"))\n\t\t\t\t\tfragdefs.gamedir = NULL;\n\t\t\t\telse\n\t\t\t\t\tfragdefs.gamedir = Q_strdup(Cmd_Argv(2));\n\t\t\t} else {\n\t\t\t\t_check_version_defined;\n\t\t\t\tCom_Printf(\"Fragfile warning (line %d): unexpected token \\\"%s\\\"\\n\", line, Cmd_Argv(1));\n\t\t\t\tgoto nextline;\n\t\t\t}\n\t\t} else if (!strcasecmp(Cmd_Argv(0), \"#META\")) {\n\n\t\t\t_check_version_defined;\n\t\t\t_checkargs(3);\n\t\t\tif (!strcasecmp(Cmd_Argv(1), \"TITLE\")) {\n\t\t\t\tfragdefs.title = Q_strdup(Cmd_Argv(2));\n\t\t\t} else if (!strcasecmp(Cmd_Argv(1), \"AUTHOR\")) {\n\t\t\t\tfragdefs.author = Q_strdup(Cmd_Argv(2));\n\t\t\t} else if (!strcasecmp(Cmd_Argv(1), \"DESCRIPTION\")) {\n\t\t\t\tfragdefs.description = Q_strdup(Cmd_Argv(2));\n\t\t\t} else if (!strcasecmp(Cmd_Argv(1), \"EMAIL\")) {\n\t\t\t\tfragdefs.email = Q_strdup(Cmd_Argv(2));\n\t\t\t} else if (!strcasecmp(Cmd_Argv(1), \"WEBPAGE\")) {\n\t\t\t\tfragdefs.webpage = Q_strdup(Cmd_Argv(2));\n\t\t\t} else {\n\t\t\t\tCom_Printf(\"Fragfile warning (line %d): unexpected token \\\"%s\\\"\\n\", line, Cmd_Argv(1));\n\t\t\t\tgoto nextline;\n\t\t\t}\n\t\t} else if (!strcasecmp(Cmd_Argv(0), \"#DEFINE\")) {\n\t\t\t_check_version_defined;\n\t\t\tif (!strcasecmp(Cmd_Argv(1), \"WEAPON_CLASS\") || !strcasecmp(Cmd_Argv(1), \"WC\")) {\n\n\t\t\t\t_checkargs3(4, 6);\n\n\t\t\t\ttoken = Cmd_Argv(2);\n\t\t\t\tif (!strcasecmp(token, \"NOWEAPON\") || !strcasecmp(token, \"NONE\") || !strcasecmp(token, \"NULL\")) {\n\t\t\t\t\tCom_Printf(\"Fragfile warning (line %d): \\\"%s\\\" is a reserved keyword\\n\", line, token);\n\t\t\t\t\tgoto nextline;\n\t\t\t\t}\n\t\t\t\tfor (i = 1; i < num_wclasses; i++) {\n\t\t\t\t\tif (!strcasecmp(token, wclasses[i].keyword)) {\n\t\t\t\t\t\tCom_Printf(\"Fragfile warning (line %d): WEAPON_CLASS \\\"%s\\\" already defined\\n\", line, wclasses[i].keyword);\n\t\t\t\t\t\tgoto nextline;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (num_wclasses == MAX_WEAPON_CLASSES) {\n\t\t\t\t\tCom_Printf(\"Fragfile warning (line %d): only %d WEAPON_CLASS's may be #DEFINE'd\\n\", line, MAX_WEAPON_CLASSES);\n\t\t\t\t\tgoto nextline;\n\t\t\t\t}\n\t\t\t\twclasses[num_wclasses].keyword   = Q_strdup(token);\n\t\t\t\twclasses[num_wclasses].name      = Q_strdup(Cmd_Argv(3));\n\t\t\t\twclasses[num_wclasses].shortname = Q_strdup(Cmd_Argv(4));\n\t\t\t\twclasses[num_wclasses].imagename = Q_strdup(Cmd_Argv(5));\n\t\t\t\twclasses[num_wclasses].colorname = NULL;\n\t\t\t\twclasses[num_wclasses].colorname_rgb_offset = -1;\n\t\t\t\twclasses[num_wclasses].colorname_skip = false;\n\n\t\t\t\tnum_wclasses++;\n\t\t\t} else if (\t!strcasecmp(Cmd_Argv(1), \"OBITUARY\") || !strcasecmp(Cmd_Argv(1), \"OBIT\")) {\n\n\t\t\t\tif (!strcasecmp(Cmd_Argv(2), \"PLAYER_DEATH\")) {\n\t\t\t\t\t_checkargs(5);\n\t\t\t\t\tmsgtype = mt_death;\n\t\t\t\t} else if (!strcasecmp(Cmd_Argv(2), \"PLAYER_SUICIDE\")) {\n\t\t\t\t\t_checkargs(5);\n\t\t\t\t\tmsgtype = mt_suicide;\n\t\t\t\t} else if (!strcasecmp(Cmd_Argv(2), \"X_FRAGS_UNKNOWN\")) {\n\t\t\t\t\t_checkargs(5);\n\t\t\t\t\tmsgtype = mt_frag;\n\t\t\t\t} else if (!strcasecmp(Cmd_Argv(2), \"X_TEAMKILLS_UNKNOWN\")) {\n\t\t\t\t\t_checkargs(5);\n\t\t\t\t\tmsgtype = mt_tkill;\n\t\t\t\t} else if (!strcasecmp(Cmd_Argv(2), \"X_FRAGS_Y\")) {\n\t\t\t\t\t_checkargs2(5, 6);\n\t\t\t\t\tmsgtype = mt_frags;\n\t\t\t\t} else if (!strcasecmp(Cmd_Argv(2), \"X_FRAGGED_BY_Y\")) {\n\t\t\t\t\t_checkargs2(5, 6);\n\t\t\t\t\tmsgtype = mt_fragged;\n\t\t\t\t} else if (!strcasecmp(Cmd_Argv(2), \"X_TEAMKILLS_Y\")) {\n\t\t\t\t\t_checkargs2(5, 6);\n\t\t\t\t\tmsgtype = mt_tkills;\n\t\t\t\t} else if (!strcasecmp(Cmd_Argv(2), \"X_TEAMKILLED_BY_Y\")) {\n\t\t\t\t\t_checkargs2(5, 6);\n\t\t\t\t\tmsgtype = mt_tkilled;\n\t\t\t\t} else if (!strcasecmp(Cmd_Argv(2), \"X_TEAMKILLED_UNKNOWN\")) {\n\t\t\t\t\t_checkargs(5);\n\t\t\t\t\tmsgtype = mt_tkilled_unk;\n\t\t\t\t} else {\n\t\t\t\t\tCom_Printf(\"Fragfile warning (line %d): unexpected token \\\"%s\\\"\\n\", line, Cmd_Argv(2));\n\t\t\t\t\tgoto nextline;\n\t\t\t\t}\n\n\t\t\t\tif (fragdefs.num_fragmsgs == MAX_FRAG_DEFINITIONS) {\n\t\t\t\t\tif (!warned_flagmsg_overflow) {\n\t\t\t\t\t\tCom_Printf(\"Fragfile warning (line %d): only %d OBITUARY's and FLAG_ALERT's's may be #DEFINE'd\\n\",\n\t\t\t\t\t\tline, MAX_FRAG_DEFINITIONS);\n\t\t\t\t\t}\n\t\t\t\t\twarned_flagmsg_overflow = true;\n\t\t\t\t\tgoto nextline;\n\t\t\t\t}\n\n\t\t\t\tif (strlen(Cmd_Argv(4)) >= MAX_FRAGMSG_LENGTH || (c == 6 && strlen(Cmd_Argv(5)) >= MAX_FRAGMSG_LENGTH)) {\n\t\t\t\t\tCom_Printf(\"Fragfile warning (line %d): Message token cannot exceed %d characters\\n\", line, MAX_FRAGMSG_LENGTH - 1);\n\t\t\t\t\tgoto nextline;\n\t\t\t\t}\n\n\t\t\t\ttoken = Cmd_Argv(3);\n\t\t\t\tif (!strcasecmp(token, \"NOWEAPON\") || !strcasecmp(token, \"NONE\") || !strcasecmp(token, \"NULL\")) {\n\t\t\t\t\tfragdefs.msgdata[fragdefs.num_fragmsgs].wclass_index = 0;\n\t\t\t\t} else {\n\t\t\t\t\tfor (i = 1; i < num_wclasses; i++) {\n\t\t\t\t\t\tif (!strcasecmp(token, wclasses[i].keyword)) {\n\t\t\t\t\t\t\tfragdefs.msgdata[fragdefs.num_fragmsgs].wclass_index = i;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (i == num_wclasses) {\n\t\t\t\t\t\tCom_Printf(\"Fragfile warning (line %d): \\\"%s\\\" is not a defined WEAPON_CLASS\\n\", line, token);\n\t\t\t\t\t\tgoto nextline;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfragdefs.msgdata[fragdefs.num_fragmsgs].type = msgtype;\n\t\t\t\tfragdefs.msgdata[fragdefs.num_fragmsgs].msg1 = Q_strdup (Cmd_Argv(4));\n\t\t\t\tfragdefs.msgdata[fragdefs.num_fragmsgs].msg2 = (c == 6) ? Q_strdup(Cmd_Argv(5)) : NULL;\n\t\t\t\tfragdefs.num_fragmsgs++;\n\t\t\t} else if (!strcasecmp(Cmd_Argv(1), \"FLAG_ALERT\") || !strcasecmp(Cmd_Argv(1), \"FLAG_MSG\")) {\n\n\t\t\t\t_checkargs(4);\n\t\t\t\tif\n\t\t\t\t(\n\t\t\t\t\t!strcasecmp(Cmd_Argv(2), \"X_TOUCHES_FLAG\") ||\n\t\t\t\t\t!strcasecmp(Cmd_Argv(2), \"X_GETS_FLAG\") ||\n\t\t\t\t\t!strcasecmp(Cmd_Argv(2), \"X_TAKES_FLAG\")\n\t\t\t\t) {\n\t\t\t\t\tmsgtype = mt_flagtouch;\n\t\t\t\t} else if\n\t\t\t\t(\n\t\t\t\t\t!strcasecmp(Cmd_Argv(2), \"X_DROPS_FLAG\") ||\n\t\t\t\t\t!strcasecmp(Cmd_Argv(2), \"X_FUMBLES_FLAG\") ||\n\t\t\t\t\t!strcasecmp(Cmd_Argv(2), \"X_LOSES_FLAG\")\n\t\t\t\t) {\n\t\t\t\t\tmsgtype = mt_flagdrop;\n\t\t\t\t} else if\n\t\t\t\t(\n\t\t\t\t\t!strcasecmp(Cmd_Argv(2), \"X_CAPTURES_FLAG\") ||\n\t\t\t\t\t!strcasecmp(Cmd_Argv(2), \"X_CAPS_FLAG\") ||\n\t\t\t\t\t!strcasecmp(Cmd_Argv(2), \"X_SCORES\")\n\t\t\t\t) {\n\t\t\t\t\tmsgtype = mt_flagcap;\n\t\t\t\t} else {\n\t\t\t\t\tCom_Printf(\"Fragfile warning (line %d): unexpected token \\\"%s\\\"\\n\", line, Cmd_Argv(2));\n\t\t\t\t\tgoto nextline;\n\t\t\t\t}\n\n\t\t\t\tif (fragdefs.num_fragmsgs == MAX_FRAG_DEFINITIONS) {\n\t\t\t\t\tif (!warned_flagmsg_overflow) {\n\t\t\t\t\t\tCom_Printf(\"Fragfile warning (line %d): only %d OBITUARY's and FLAG_ALERT's's may be #DEFINE'd\\n\",\n\t\t\t\t\t\t\tline, MAX_FRAG_DEFINITIONS);\n\t\t\t\t\t}\n\t\t\t\t\twarned_flagmsg_overflow = true;\n\t\t\t\t\tgoto nextline;\n\t\t\t\t}\n\t\t\t\tfragdefs.msgdata[fragdefs.num_fragmsgs].type = msgtype;\n\t\t\t\tfragdefs.msgdata[fragdefs.num_fragmsgs].msg1 = Q_strdup(Cmd_Argv(3));\n\t\t\t\tfragdefs.msgdata[fragdefs.num_fragmsgs].msg2 = NULL;\n\t\t\t\tfragdefs.num_fragmsgs++;\n\n\t\t\t\tfragdefs.flagalerts = true;\n\t\t\t}\n\t\t} else {\n\t\t\t_check_version_defined;\n\t\t\tCom_Printf(\"Fragfile warning (line %d): unexpected token \\\"%s\\\"\\n\", line, Cmd_Argv(0));\n\t\t\tgoto nextline;\n\t\t}\n\nnextline:\n\t\tif (!*end)\n\t\t\tbreak;\n\n\t\tstart = end = end + 1;\n\t\tline++;\n\t}\n\n\tif (fragdefs.num_fragmsgs) {\n\n\t\tfor (i = 0; i < fragdefs.num_fragmsgs; i++)\n\t\t\tfragdefs.fragmsgs[i] = &fragdefs.msgdata[i];\n\t\tqsort(fragdefs.fragmsgs, fragdefs.num_fragmsgs, sizeof(fragmsg_t *), Compare_FragMsg);\n\t\tBuild_FragMsg_Indices();\n\n\t\tfragdefs.active = true;\n\t\tif (!quiet)\n\t\t\tCom_Printf(\"Loaded fragfile \\\"%s\\\" (%d frag defs)\\n\", fragfilename, fragdefs.num_fragmsgs);\n\t\tgoto end;\n\t} else {\n\t\tif (!quiet)\n\t\t\tCom_Printf(\"Fragfile \\\"%s\\\" was empty\\n\", fragfilename);\n\t\tgoto end;\n\t}\n\nend:\n\tQ_free(buffer);\n\tVX_TrackerInit();\n}\n\nvoid Load_FragFile_f(void) {\n\tswitch (Cmd_Argc()) {\n\t\tcase 2:\n\t\t\tLoadFragFile(Cmd_Argv(1), false);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tCom_Printf(\"Usage: %s <filename>\\n\", Cmd_Argv(0));\n\t\t\tbreak;\n\t}\n}\n\n\ntypedef struct fragstats_s {\n\tint deaths[MAX_CLIENTS];\n\tint teamdeaths[MAX_CLIENTS];\n\tint kills[MAX_CLIENTS];\n\tint teamkills[MAX_CLIENTS];\n\tint wdeaths[MAX_WEAPON_CLASSES];\n\tint wkills[MAX_WEAPON_CLASSES];\n\t//VULT DISPLAY NAMES\n\tint wsuicides[MAX_WEAPON_CLASSES];\n\tint streak;\n\n\tint totaldeaths;\n\tint totalfrags;\n\tint totalteamkills;\n\tint totalsuicides;\n\n\tint touches;\n\tint fumbles;\n\tint captures;\n} fragstats_t;\n\nstatic fragstats_t fragstats[MAX_CLIENTS];\nstatic qbool flag_dropped, flag_touched, flag_captured;\n\nstatic void Stats_ParsePrintLine(const char *s, cfrags_format *cff, int offset) \n{\n\tint start_search, end_Search, i, j, k, p1len, msg1len, msg2len, p2len, killer, victim;\n\tfragmsg_t *fragmsg;\n\tconst char *start, *name1, *name2, *t;\n\tplayer_info_t *player1 = NULL, *player2 = NULL;\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) \n\t{\n\t\tstart = s;\n\t\tplayer1 = &cl.players[i];\n\t\n\t\tif (!player1->name[0] || player1->spectator)\n\t\t\tcontinue;\n\n\t\tname1 = Info_ValueForKey(player1->userinfo, \"name\");\n\t\tp1len = min(strlen(name1), 31);\n\t\t\n\t\tif (!strncmp(start, name1, p1len)) \n\t\t{\n\t\t\tcff->p1pos = offset;\n\t\t\tcff->p1len = p1len; \n\t\t\tcff->p1col = player1->topcolor;\n\t\t\t\n\t\t\tfor (t = start + p1len; *t && isspace(*t & 127); t++)\n\t\t\t\t;\n\n\t\t\tk = tolower(*t & 127);\n\t\t\t\n\t\t\tif (MYISLOWER(k))\n\t\t\t{\n\t\t\t\tstart_search = fragmsg1_indexes[k - 'a'];\n\t\t\t\tend_Search = (k == 'z') ? fragdefs.num_fragmsgs : fragmsg1_indexes[k - 'a' + 1];\n\t\t\t}\n\t\t\telse \n\t\t\t{\n\t\t\t\tstart_search = 0;\n\t\t\t\tend_Search = fragmsg1_indexes[0];\n\t\t\t}\n\n\t\t\tif (start_search == -1)\n\t\t\t\tcontinue;\n\n\t\t\tif (end_Search == -1)\n\t\t\t\tend_Search = fragdefs.num_fragmsgs;\n\n\t\t\tfor (j = start_search; j < end_Search; j++) \n\t\t\t{\n\t\t\t\tstart = s + p1len;\n\t\t\t\tfragmsg = fragdefs.fragmsgs[j];\n\t\t\t\tmsg1len = strlen(fragmsg->msg1);\n\t\t\t\tif (!strncmp(start, fragmsg->msg1, msg1len)) \n\t\t\t\t{\n\t\t\t\t\tif (fragmsg->type == mt_fragged || fragmsg->type == mt_frags ||\n\t\t\t\t\t\tfragmsg->type == mt_tkills || fragmsg->type == mt_tkilled) \n\t\t\t\t\t{\n\t\t\t\t\t\tfor (k = 0; k < MAX_CLIENTS; k++) \n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstart = s + p1len + msg1len;\n\t\t\t\t\t\t\tplayer2 = &cl.players[k];\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tif (!player2->name[0] || player2->spectator)\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tname2 = Info_ValueForKey(player2->userinfo, \"name\");\n\t\t\t\t\t\t\tp2len = min(strlen(name2), 31);\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tif (!strncmp(start, name2, p2len)) \n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcff->p2pos = start - s + offset;\n\t\t\t\t\t\t\t\tcff->p2len = p2len;\n\t\t\t\t\t\t\t\tcff->p2col = player2->topcolor;\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tif (fragmsg->msg2) \n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tif (!*(start = s + p1len + msg1len + p2len))\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\t\t\t\tmsg2len = strlen(fragmsg->msg2);\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tif (!strncmp(start, fragmsg->msg2, msg2len))\n\t\t\t\t\t\t\t\t\t\tgoto foundmatch;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse \n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tgoto foundmatch;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse \n\t\t\t\t\t{\n\t\t\t\t\t\tgoto foundmatch;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn;\n\t\nfoundmatch:\n\ti = player1 - cl.players;\n\tif (player2)\n\t\tj = player2 - cl.players;\n\n\tswitch (fragmsg->type) \n\t{\n\t\tcase mt_death:\n\t\t{\n\t\t\tcff->isFragMsg = true;\n\t\t\tfragstats[i].totaldeaths++;\n\t\t\tfragstats[i].wdeaths[fragmsg->wclass_index]++;\n\t\t\tVX_TrackerDeath(i, fragmsg->wclass_index, fragstats[i].wdeaths[fragmsg->wclass_index]);\n\t\t\tVX_TrackerStreakEnd(i, i, fragstats[i].streak);\n\t\t\tfragstats[i].streak=0;\n\t\t\tbreak;\n\t\t}\n\t\tcase mt_suicide:\n\t\t{\n\t\t\tcff->isFragMsg = true;\n\t\t\tfragstats[i].totalsuicides++;\n\t\t\tfragstats[i].totaldeaths++;\n\t\t\tfragstats[i].wsuicides[fragmsg->wclass_index]++;\n\t\t\tVX_TrackerSuicide(i, fragmsg->wclass_index, fragstats[i].wsuicides[fragmsg->wclass_index]);\n\t\t\tVX_TrackerStreakEnd(i, i, fragstats[i].streak);\n\t\t\tfragstats[i].streak=0;\n\t\t\tbreak;\n\t\t}\n\t\tcase mt_fragged:\n\t\tcase mt_frags:\n\t\t{\n\t\t\tcff->isFragMsg = true;\n\t\t\tkiller = (fragmsg->type == mt_fragged) ? j : i;\n\t\t\tvictim = (fragmsg->type == mt_fragged) ? i : j;\n\t\t\tCL_Cam_SetKiller(killer,victim);\n\t\t\tfragstats[killer].kills[victim]++;\n\t\t\tfragstats[killer].totalfrags++;\n\t\t\tfragstats[killer].wkills[fragmsg->wclass_index]++;\n\t\t\tfragstats[victim].deaths[killer]++;\n\t\t\tfragstats[victim].totaldeaths++;\n\t\t\tfragstats[victim].wdeaths[fragmsg->wclass_index]++;\n\t\t\tVX_TrackerFragXvsY(victim, killer, fragmsg->wclass_index,\n\t\t\t\t\tfragstats[victim].wdeaths[fragmsg->wclass_index], fragstats[killer].wkills[fragmsg->wclass_index]);\n\t\t\tfragstats[killer].streak++;\n\t\t\tVX_TrackerStreak(killer, fragstats[killer].streak);\n\t\t\tVX_TrackerStreakEnd(victim, killer, fragstats[victim].streak);\n\t\t\tfragstats[victim].streak=0;\n\t\t\tbreak;\n\t\t}\n\t\tcase mt_frag:\n\t\t{\n\t\t\tcff->isFragMsg = true;\n\t\t\tfragstats[i].totalfrags++;\n\t\t\tfragstats[i].wkills[fragmsg->wclass_index]++;\n\t\t\tVX_TrackerOddFrag(i, fragmsg->wclass_index, fragstats[i].wkills[fragmsg->wclass_index]);\n\t\t\tfragstats[i].streak++;\n\t\t\tVX_TrackerStreak(i, fragstats[i].streak);\n\t\t\tbreak;\n\t\t}\n\t\tcase mt_tkilled:\n\t\tcase mt_tkills:\n\t\t{\t\n\t\t\tcff->isFragMsg = true;\n\t\t\tkiller = (fragmsg->type == mt_tkilled) ? j : i;\n\t\t\tvictim = (fragmsg->type == mt_tkilled) ? i : j;\n\t\t\tCL_Cam_SetKiller(killer,victim);\n\n\t\t\tfragstats[killer].teamkills[victim]++;\n\t\t\tfragstats[killer].totalteamkills++;\n\n\t\t\tfragstats[victim].teamdeaths[killer]++;\n\t\t\tfragstats[victim].totaldeaths++;\n\n\t\t\tVX_TrackerTK_XvsY(victim, killer, fragmsg->wclass_index,\n\t\t\t\t\t\tfragstats[killer].totalteamkills, fragstats[victim].teamdeaths[killer],\n\t\t\t\t\t\tfragstats[killer].totalteamkills, fragstats[killer].teamkills[victim]);\n\t\t\tVX_TrackerStreakEnd(victim, killer, fragstats[victim].streak);\n\t\t\tfragstats[victim].streak=0;\n\t\t\tbreak;\n\t\t}\n\t\tcase mt_tkilled_unk:\n\t\t{\n\t\t\tcff->isFragMsg = true;\n\t\t\tfragstats[i].totaldeaths++;\n\t\t\tVX_TrackerOddTeamkilled(i, fragmsg->wclass_index);\n\t\t\tVX_TrackerStreakEndOddTeamkilled(i, fragstats[i].streak);\n\t\t\tfragstats[i].streak=0;\n\t\t\tbreak;\n\t\t}\n\t\tcase mt_tkill:\n\t\t{\n\t\t\tcff->isFragMsg = true;\n\t\t\tfragstats[i].totalteamkills++;\n\t\t\tVX_TrackerOddTeamkill(i, fragmsg->wclass_index, fragstats[i].totalteamkills);\n\t\t\t// not enough data to stop streaks of killed man\n\t\t\tbreak;\n\t\t}\n\t\tcase mt_flagtouch:\n\t\t{\n\t\t\tfragstats[i].touches++;\n\t\t\tUpdate_FlagStatus(i, player1->team, true);\n\t\t\tif (cl.playernum == i || (i == Cam_TrackNum() && cl.spectator))\n\t\t\t\tVX_TrackerFlagTouch(fragstats[i].touches);\n\t\t\tflag_touched = true;\n\t\t\tbreak;\n\t\t}\n\t\tcase mt_flagdrop:\n\t\t{\t\t\n\t\t\tfragstats[i].fumbles++;\n\t\t\tUpdate_FlagStatus(i, player1->team, false);\n\t\t\tif (cl.playernum == i || (i == Cam_TrackNum() && cl.spectator))\n\t\t\t\tVX_TrackerFlagDrop(fragstats[i].fumbles);\n\t\t\tflag_dropped = true;\n\t\t\tbreak;\n\t\t}\n\t\tcase mt_flagcap:\n\t\t{\t\t\n\t\t\tfragstats[i].captures++;\n\t\t\tUpdate_FlagStatus(i, player1->team, false);\n\t\t\tif (cl.playernum == i || (i == Cam_TrackNum() && cl.spectator))\n\t\t\t\tVX_TrackerFlagCapture(fragstats[i].captures);\n\t\t\tflag_captured = true;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nvoid Stats_ParsePrint(char *s, int level, cfrags_format *cff) {\n\tchar *start, *end, save;\n\tint offset = 0;\n\n\tif (!Stats_IsActive())\n\t\treturn;\n\n\tif (level != PRINT_MEDIUM && level != PRINT_HIGH)\n\t\treturn;\n\n\tstart = end = s;\n\twhile (1) {\n\t\tfor ( ; *end && *end != '\\n'; end++)\n\t\t\t;\n\n\t\tif (*end) {\n\t\t\tend++;\n\t\t\tsave = *end;\n\t\t\t*end = 0;\n\t\t\tStats_ParsePrintLine(start, cff, offset);\n\t\t\t*end = save;\n\t\t\toffset += (end - s);\n\t\t} else {\n\t\t\tStats_ParsePrintLine(start, cff, offset);\n\t\t\tbreak;\n\t\t}\n\n\t\tstart = end;\n\t}\n}\n\nvoid Stats_Reset(void) {\n\tmemset(&fragstats, 0, sizeof(fragstats));\n\tflag_touched = flag_dropped = flag_captured = false;\n}\n\nvoid Stats_NewMap(void) {\n\tstatic char last_gamedir[MAX_OSPATH] = {0};\n\n\tif (!last_gamedir[0] || strcasecmp(last_gamedir, cls.gamedirfile)) {\n\t\tif (cl_loadFragfiles.value) {\n\t\t\tstrlcpy(last_gamedir, cls.gamedirfile, sizeof(last_gamedir));\n\t\t\tLoadFragFile(\"fragfile\", true);\n\t\t} else {\n\t\t\tInitFragDefs(true);\n\t\t}\n\t}\n\n\tStats_Reset();\n}\n\nvoid Stats_EnterSlot(int num) {\n\tint i;\n\n\tif (num < 0 || num >= MAX_CLIENTS)\n\t\tSys_Error(\"Stats_EnterSlot: num < 0 || num >= MAX_CLIENTS\");\n\n\tmemset(&fragstats[num], 0, sizeof(fragstats[num]));\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!cl.players[i].name[0] || cl.players[i].spectator)\n\t\t\tcontinue;\n\t\tif (i == num)\n\t\t\tcontinue;\n\n\t\tfragstats[i].teamdeaths[num] = fragstats[i].deaths[num] = 0;\n\t\tfragstats[i].teamkills[num] = fragstats[i].kills[num] = 0;\n\t}\n}\n\n\nvoid Stats_GetBasicStats(int num, int *playerstats) {\n\tif (num < 0 || num >= MAX_CLIENTS)\n\t\tSys_Error(\"Stats_EnterSlot: num < 0 || num >= MAX_CLIENTS\");\n\n\tplayerstats[0] = fragstats[num].totalfrags;\n\tplayerstats[1] = fragstats[num].totaldeaths;\n\tplayerstats[2] = fragstats[num].totalteamkills;\n\tplayerstats[3] = fragstats[num].totalsuicides;\n}\n\nvoid Stats_GetFlagStats(int num, int *playerstats) {\n\tif (num < 0 || num >= MAX_CLIENTS)\n\t\tSys_Error(\"Stats_EnterSlot: num < 0 || num >= MAX_CLIENTS\");\n\n\tif (!flag_touched && flag_dropped && flag_captured)\n\t\tplayerstats[0] = fragstats[num].fumbles + fragstats[num].captures;\n\telse\n\t\tplayerstats[0] = fragstats[num].touches;\n\n\tplayerstats[1] = fragstats[num].fumbles;\n\tplayerstats[2] = fragstats[num].captures;\n}\n\nqbool Stats_IsActive(void) {\n\treturn (fragdefs.active && cl_parsefrags.value) ? true : false;\n}\n\nqbool Stats_IsFlagsParsed(void) {\n\treturn (Stats_IsActive() && fragdefs.flagalerts) ? true : false;\n}\n\nvoid Stats_Init(void) {\n\tInitFragDefs(true);\n\tCvar_SetCurrentGroup(CVAR_GROUP_SBAR);\n\tCvar_Register(&cl_parsefrags);\n\tCvar_Register(&cl_showFragsMessages);\n\tCvar_Register(&cl_loadFragfiles);\n\tCvar_Register(&cl_useimagesinfraglog);\n\tCvar_ResetCurrentGroup();\n\tCmd_AddCommand(\"loadFragfile\", Load_FragFile_f);\n}\n\n//VULT DISPLAYNAMES\nchar *GetWeaponName (int num)\n{\n\tif (cl_useimagesinfraglog.integer && wclasses[num].imagename && wclasses[num].imagename[0])\n\t\treturn wclasses[num].imagename;\n\n\tif (wclasses[num].shortname && wclasses[num].shortname[0])\n\t\treturn wclasses[num].shortname;\n\n\tif (wclasses[num].name && wclasses[num].name[0])\n\t\treturn wclasses[num].name;\n\n\treturn \"Unknown\";\n}\n\nstatic void InitColoredWeapon (int num, const byte *color)\n{\n\tchar *original_weapon, *weapon, *prefix = NULL, *suffix = NULL;\n\tint original_weapon_len = 0, weapon_len = 0, prefix_len = 0;\n\n\toriginal_weapon = GetWeaponName(num);\n\toriginal_weapon_len = strlen(original_weapon);\n\n\t// Check whether or not the original_weapon is a known string that we\n\t// need to handle in a specific way.\n\t//\n\t// The code supports the ezquake and nquake fragfile.dat values for quad\n\t// related weapons.\n\t//\n\t// We store the offset to 'R' in the \"c&RGB\" string so that we easily\n\t// can overwrite the RGB value later on.\n\tif (strcmp(original_weapon, EZQUAKE_SG_QUAD) == 0) {\n\t\tweapon = WEAPON_CLASS_SG;\n\t\tprefix = EZQUAKE_QUAD;\n\t\twclasses[num].colorname_rgb_offset = 11;\n\t} else if (strcmp(original_weapon, EZQUAKE_SSG_QUAD) == 0) {\n\t\tweapon = WEAPON_CLASS_SSG;\n\t\tprefix = EZQUAKE_QUAD;\n\t\twclasses[num].colorname_rgb_offset = 11;\n\t} else if (strcmp(original_weapon, EZQUAKE_SNG_QUAD) == 0) {\n\t\tweapon = WEAPON_CLASS_SNG;\n\t\tprefix = EZQUAKE_QUAD;\n\t\twclasses[num].colorname_rgb_offset = 11;\n\t} else if (strcmp(original_weapon, EZQUAKE_RL_QUAD) == 0) {\n\t\tweapon = WEAPON_CLASS_RL;\n\t\tprefix = EZQUAKE_QUAD;\n\t\twclasses[num].colorname_rgb_offset = 11;\n\t} else if (strcmp(original_weapon, EZQUAKE_LG_QUAD) == 0) {\n\t\tweapon = WEAPON_CLASS_LG;\n\t\tprefix = EZQUAKE_QUAD;\n\t\twclasses[num].colorname_rgb_offset = 11;\n\t} else if (strcmp(original_weapon, NQUAKE_SG_QUAD) == 0) {\n\t\tweapon = WEAPON_CLASS_SG;\n\t\tsuffix = NQUAKE_QUAD;\n\t\twclasses[num].colorname_rgb_offset = 2;\n\t} else if (strcmp(original_weapon, NQUAKE_SSG_QUAD) == 0) {\n\t\tweapon = WEAPON_CLASS_SSG;\n\t\tsuffix = NQUAKE_QUAD;\n\t\twclasses[num].colorname_rgb_offset = 2;\n\t} else if (strcmp(original_weapon, NQUAKE_SNG_QUAD) == 0) {\n\t\tweapon = WEAPON_CLASS_SNG;\n\t\tsuffix = NQUAKE_QUAD;\n\t\twclasses[num].colorname_rgb_offset = 2;\n\t} else if (strcmp(original_weapon, NQUAKE_RL_QUAD) == 0) {\n\t\tweapon = WEAPON_CLASS_RL;\n\t\tsuffix = NQUAKE_QUAD;\n\t\twclasses[num].colorname_rgb_offset = 2;\n\t} else if (strcmp(original_weapon, NQUAKE_LG_QUAD) == 0) {\n\t\tweapon = WEAPON_CLASS_LG;\n\t\tsuffix = NQUAKE_QUAD;\n\t\twclasses[num].colorname_rgb_offset = 2;\n\t} else if ((strstr(original_weapon, \"&c\")) == NULL) {\n\t\t// No fancy formatting is applied to the weapon.\n\t\tweapon = original_weapon;\n\t\twclasses[num].colorname_rgb_offset = 2;\n\t} else  {\n\t\t// This case covers an unknown name that contains a \"&c\"\n\t\t// sequence. These weapon strings will be ignored.\n\t\twclasses[num].colorname_skip = true;\n\t\treturn;\n\t}\n\n\t// Allocate and initialize the memory for the colored weapon string.\n\t// We'll use the original weapon length + 8 additional bytes. The 8\n\t// bytes are for \"&cRGB\", \"&r\" and a trailing '\\0' character.\n\twclasses[num].colorname = Q_malloc(original_weapon_len + 8);\n\tmemset(wclasses[num].colorname, 0, original_weapon_len + 8);\n\n\tif (prefix != NULL) {\n\t\tprefix_len = strlen(prefix);\n\t\tmemcpy(wclasses[num].colorname, prefix, prefix_len);\n\t}\n\n\tweapon_len = strlen(weapon);\n\tmemcpy(wclasses[num].colorname + prefix_len, \"&cRGB\", 5);\n\tmemcpy(wclasses[num].colorname + prefix_len + 5, weapon, weapon_len);\n\tmemcpy(wclasses[num].colorname + prefix_len + 5 + weapon_len, \"&r\", 2);\n\n\tif (suffix != NULL) {\n\t\tmemcpy(wclasses[num].colorname + prefix_len + 5 + weapon_len + 2, suffix, strlen(suffix));\n\t}\n}\n\nchar *GetColoredWeaponName (int num, const byte *color)\n{\n\tchar rgb[3];\n\n\tif (!wclasses[num].colorname_skip && wclasses[num].colorname == NULL)\n\t\tInitColoredWeapon(num, color);\n\n\tif (wclasses[num].colorname_skip)\n\t\treturn GetWeaponName(num);\n\n\t// Convert color to string and overwrite the RGB values.\n\tRGBToString(color, rgb);\n\tmemcpy(wclasses[num].colorname + wclasses[num].colorname_rgb_offset, rgb, 3);\n\n\treturn wclasses[num].colorname;\n}\n\nconst char* GetWeaponImageName(int num)\n{\n\tif (wclasses[num].imagename && wclasses[num].imagename[0]) {\n\t\treturn wclasses[num].imagename;\n\t}\n\n\treturn NULL;\n}\n\nconst char* GetWeaponTextName(int num)\n{\n\tif (wclasses[num].shortname && wclasses[num].shortname[0]) {\n\t\treturn wclasses[num].shortname;\n\t}\n\n\tif (wclasses[num].name && wclasses[num].name[0]) {\n\t\treturn wclasses[num].name;\n\t}\n\n\treturn NULL;\n}\n\nvoid Stats_Shutdown(void)\n{\n\tInitFragDefs(false);\n}\n"
  },
  {
    "path": "src/fs.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: fs.c,v 1.60 2007-10-25 14:06:20 dkure Exp $\n*/\n\n/**\n  File System related code\n  - old Quake FS - declarations in common.h\n  - Virtual Quake System - vfs.h\n  - GZIP/ZIP support - fs.h\n*/\n\n#include \"quakedef.h\"\n#include \"common.h\"\n#include \"hash.h\"\n#include \"fs.h\"\n#include \"vfs.h\"\n#include \"utils.h\"\n#ifdef _WIN32\n#include <errno.h>\n#else\n#include <unistd.h>\n#include <strings.h>\n#if defined(__APPLE__)\n#include <libproc.h>\n#elif defined(__FreeBSD__)\n#include <sys/sysctl.h>\n#endif\n#endif\n\nstatic void FS_RebuildFSHash(void);\n#ifdef SERVERONLY\nstatic const char *FS_GetCleanPath(const char *pattern, char *outbuf, int outlen);\n#endif\nchar *com_filesearchpath;\n\n/*\n=============================================================================\n                        QUAKE FILESYSTEM\n=============================================================================\n*/\n\nint\t\tfs_filepos;\nchar\tfs_netpath[MAX_OSPATH];\n\n// WARNING: if u add some FS related global variable then made appropriate change to FS_ShutDown() too, if required.\n\nchar\tcom_gamedirfile[MAX_QPATH]; // qw tf ctf and etc. In other words single dir name without path\nchar\tcom_gamedir[MAX_OSPATH];    // c:/quake/qw\nchar\tcom_basedir[MAX_OSPATH];\t// c:/quake\nchar\tcom_homedir[MAX_PATH];\t\t// something really long C:/Documents and Settings/qqshka\nchar\tuserdirfile[MAX_OSPATH] = {0};\nchar\tcom_userdir[MAX_OSPATH] = {0};\nint\t\tuserdir_type = -1;\n\nsearchpath_t\t*fs_searchpaths = NULL;\nsearchpath_t\t*fs_base_searchpaths = NULL;\t// without gamedirs\nsearchpath_t\t*fs_purepaths;\n\nstatic int FS_AddPak(char *pathto, char *pakname, searchpath_t *search, searchpathfuncs_t *funcs);\nstatic qbool FS_RemovePak (const char *pakfile);\n\n//============================================================================\n#include \"q_shared.h\"\n\n#ifndef CLIENTONLY\n#include \"server.h\"\n#endif // CLIENTONLY\n\n// To include pak3 support add this define\n//#define WITH_PK3\n\nhashtable_t *filesystemhash;\nqbool filesystemchanged = true;\nint fs_hash_dups;\nint fs_hash_files;\n\ncvar_t fs_cache = {\"fs_cache\", \"1\"};\nstatic cvar_t fs_savegame_home = { \"fs_savegame_home\", \"1\" };\n\nstatic void FS_CreatePathRelative(const char *pname, int relativeto);\nvoid FS_ForceToPure(char *str, const char *crcs, int seed);\nint FS_FLocateFile(const char *filename, FSLF_ReturnType_e returntype, flocation_t *loc); \nvoid FS_EnumerateFiles (char *match, int (*func)(char *, int, void *), void *parm);\nint FS_FileOpenRead (char *path, FILE **hndl);\nvoid FS_ReloadPackFiles_f(void);\nvoid FS_ListFiles_f(void);\nvoid FS_FlushFSHash(void);\nvoid FS_AddHomeDirectory(char *dir, FS_Load_File_Types loadstuff);\n\nstatic void FS_AddDataFiles(char *pathto, searchpath_t *search, char *extension, searchpathfuncs_t *funcs);\nstatic searchpath_t *FS_AddPathHandle(char *probablepath, searchpathfuncs_t *funcs, void *handle, qbool copyprotect, qbool istemporary, FS_Load_File_Types loadstuff);\n\nqbool Sys_PathProtection(const char *pattern);\nvoid FS_Dir_f (void);\nvoid FS_Locate_f (void);\n\n// VFS-FIXME: Debug file for trying to open files\nstatic void FS_DiffFile_f(void);\n\n\n//============================================================================\n\n\n\n/*\n================\nFS_FileLength\n================\n*/\nint FS_FileLength (FILE *f)\n{\n\tint\t\tpos;\n\tint\t\tend;\n\n\tpos = ftell (f);\n\tfseek (f, 0, SEEK_END);\n\tend = ftell (f);\n\tfseek (f, pos, SEEK_SET);\n\n\treturn end;\n}\n\n/*\n================\nFS_FileOpenRead\n================\n*/\nint FS_FileOpenRead (char *path, FILE **hndl)\n{\n\tFILE *f;\n\n\tif (!(f = fopen(path, \"rb\"))) {\n\t\t*hndl = NULL;\n\t\treturn -1;\n\t}\n\t*hndl = f;\n\n\treturn FS_FileLength(f);\n}\n\n/*\n============\nFS_Path_f\n============\n*/\nvoid FS_Path_f (void)\n{\n\tsearchpath_t\t*search;\n\n\tCom_Printf (\"Current search path:\\n\");\n\tif (fs_purepaths)\n\t{\n\t\tfor (search=fs_purepaths ; search ; search=search->nextpure)\n\t\t{\n\t\t\tsearch->funcs->PrintPath(search->handle);\n\t\t}\n\t\tCom_Printf (\"----------\\n\");\n\t}\n\n\n\tfor (search=fs_searchpaths ; search ; search=search->next)\n\t{\n\t\tif (search == fs_base_searchpaths && !fs_purepaths)\n\t\t\tCom_Printf (\"----------\\n\");\n\n\t\tsearch->funcs->PrintPath(search->handle);\n\t}\n}\n\n\n// VFS-FIXME: D-Kure This removes a sanity check\nint FS_FCreateFile (char *filename, FILE **file, char *path, char *mode)\n{\n\tchar fullpath[MAX_OSPATH];\n\n\tif (path == NULL)\n\t\tpath = com_gamedir;\n\n\tif (mode == NULL)\n\t\tmode = \"wb\";\n\n\t// try to create\n\tsnprintf(fullpath, sizeof(fullpath), \"%s/%s/%s\", com_basedir, path, filename);\n\tFS_CreatePath(fullpath);\n\t*file = fopen(fullpath, mode);\n\n\tif (*file == NULL) {\n\t\t// no Sys_Error, quake can be run from read-only media like cd-rom\n\t\treturn 0;\n\t}\n\n\tSys_Printf (\"FCreateFile: %s\\n\", filename);\n\n\treturn 1;\n}\n\n//The filename will be prefixed by com_basedir\nstatic qbool FS_WriteFileRelative(const char *filename, const void *data, int len, int relativeto)\n{\n\tvfsfile_t *f;\n\n\tFS_CreatePathRelative(filename, relativeto);\n\tf = FS_OpenVFS(filename, \"wb\", relativeto);\n\tif (f) \n\t{\n\t\tVFS_WRITE(f, data, len);\n\t\tVFS_CLOSE(f);\n\t} else {\n\t\treturn false;\n\t}\n\n\tfilesystemchanged=true;\n\n\treturn true;\n}\n\n\n/*\n============\nFS_WriteFile\n\nThe filename will be prefixed by the current game directory\n============\n*/\nqbool FS_WriteFile(const char *filename, const void *data, int len)\n{\n\tSys_Printf (\"FS_WriteFile: %s\\n\", filename);\n\treturn FS_WriteFileRelative(filename, data, len, FS_GAME_OS);\n}\n\n//The filename used as is\nqbool FS_WriteFile_2(const char *filename, const void *data, int len)\n{\n\tSys_Printf (\"FS_WriteFile_2: %s\\n\", filename);\n\treturn FS_WriteFileRelative(filename, data, len, FS_NONE_OS);\n}\n\n#if 0\nFILE *FS_WriteFileOpen (char *filename)\t//like fopen, but based around quake's paths.\n{\n\tFILE\t*f;\n\tchar\tname[MAX_OSPATH];\n\n\tsnprintf (name, sizeof (name), \"%s/%s\", com_gamedir, filename);\n\n\tFS_CreatePath(name);\n\n\tf = fopen (name, \"wb\");\n\n\treturn f;\n}\n#endif\n\n//Only used for CopyFile and download\n\n\n/*\n============\nFS_CreatePath\n\nOnly used for CopyFile and download\n============\n*/\nvoid FS_CreatePath(char *path)\n{\n\tchar *s, save;\n\n\tif (!*path)\n\t\treturn;\n\n\tfor (s = path + 1; *s; s++) {\n#ifdef _WIN32\n\t\tif (*s == '/' || *s == '\\\\') {\n#else\n\t\tif (*s == '/') {\n#endif\n\t\t\tsave = *s;\n\t\t\t*s = 0;\n\t\t\tSys_mkdir(path);\n\t\t\t*s = save;\n\t\t}\n\t}\n}\n\nint FS_FOpenPathFile (const char *filename, FILE **file) {\n\n\t*file = NULL;\n\tfs_filepos = 0;\n\tfs_netpath[0] = 0;\n\n\tif ((*file = fopen (filename, \"rb\"))) {\n\n\t\tif (developer.value)\n\t\t\tSys_Printf (\"FindFile: %s\\n\", fs_netpath);\n\n\t\treturn FS_FileLength (*file);\n\t}\n\n\tif (developer.value)\n\t\tSys_Printf (\"FindFile: can't find %s\\n\", filename);\n\n\treturn -1;\n}\n\n//Finds the file in the search path.\n//Sets fs_netpath and one of handle or file\n//Sets fs_filepos to 0 for non paks, and to beging of file in pak file\nqbool\tfile_from_pak;\t\t// global indicating file came from a packfile\nqbool\tfile_from_gamedir;\t// global indicating file came from a gamedir (and gamedir wasn't id1/qw)\n\n// VFS-FIXME: D-Kure: This function will be removed once we have the VFS layer\n\n// Filename are relative to the quake directory.\n// Always appends a 0 byte to the loaded data.\nstatic byte *FS_LoadFile (const char *path, int usehunk, int *file_length)\n{\n\tvfsfile_t *f = NULL;\n\tvfserrno_t err;\n\tflocation_t loc;\n\tbyte *buf;\n\tchar base[32];\n\tint len;\n\n\t// Look for it in the filesystem or pack files.\n\t//blanket-bans - Avoid combination of / & \\ for directories\n    if (Sys_PathProtection(path)) \n\t\treturn NULL;\n\n\t// VFS-FIXME: This only checks the pak files, not the base dir's\n    FS_FLocateFile(path, FSLFRT_LENGTH, &loc);\n\tif (loc.search) {\n\t\tf = loc.search->funcs->OpenVFS(loc.search->handle, &loc, \"rb\");\n\t} else {\n\t\tf = FS_OpenVFS(path, \"rb\", FS_ANY);\n\t} \n\n\tif (!f)\n\t\treturn NULL;\n\tlen = VFS_GETLEN(f);\n\tif(len == -1) {\n\t\tVFS_CLOSE(f);\n\t\treturn NULL;\n\t}\n\tif (file_length)\n\t\t*file_length = len;\n\n\t// Extract the filename base name for hunk tag.\n\tCOM_FileBase (path, base);\n\n\t// TODO: Make these into defines.\n\tif (usehunk == 1)\n\t{\n\t\tbuf = (byte *) Hunk_AllocName (len + 1, base);\n\t}\n\telse if (usehunk == 2)\n\t{\n\t\tbuf = (byte *) Hunk_TempAlloc (len + 1);\n\t}\n\telse if (usehunk == 5)\n\t{\n\t\tbuf = Q_malloc_named(len + 1, path);\n\t}\n\telse\n\t{\n\t\tSys_Error (\"FS_LoadFile: bad usehunk\\n\");\n\t\treturn NULL;\n\t}\n\n\tif (!buf) {\n\t\tSys_Error (\"FS_LoadFile: not enough space for %s\\n\", path);\n\t\treturn NULL;\n\t}\n\n\t((byte *)buf)[len] = 0;\n\n\tDraw_BeginDisc ();\n\n\tVFS_READ(f, buf, len, &err);\n\tVFS_CLOSE(f);\n\n\tDraw_EndDisc ();\n\n\treturn buf;\n}\n\nbyte *FS_LoadHunkFile (char *path, int *len) {\n\treturn FS_LoadFile (path, 1, len);\n}\n\nbyte *FS_LoadTempFile (char *path, int *len) {\n\treturn FS_LoadFile (path, 2, len);\n}\n\n// use Q_malloc, do not forget Q_free when no needed more\nbyte *FS_LoadHeapFile (const char *path, int *len)\n{\n\treturn FS_LoadFile (path, 5, len);\n}\n\n// QW262 -->\n/*\n================\nFS_SetUserDirectory\n================\n*/\nvoid FS_SetUserDirectory (char *dir, char *type) {\n\tchar tmp[sizeof(com_gamedirfile)];\n\n\tif (strstr(dir, \"..\") || strchr(dir, '/')\n\t        || strchr(dir, '\\\\') || strchr(dir, ':') ) {\n\t\tCom_Printf (\"Userdir should be a single filename, not a path\\n\");\n\t\treturn;\n\t}\n\tstrlcpy(userdirfile, dir, sizeof(userdirfile));\n\tuserdir_type = Q_atoi(type);\n\n\tstrlcpy(tmp, com_gamedirfile, sizeof(tmp)); // save\n\tcom_gamedirfile[0]='\\0'; // force reread\n\tFS_SetGamedir(tmp, false); // restore\n}\n\n// ==========\n// FS_AddPak\n// ==========\n// Return Value:\n// 0  - Pak added succesfully\n// 1  - File does not exsist\n// -1 - Error loading file\n\nstatic int FS_AddPak(char *pathto, char *pakname, searchpath_t *search, searchpathfuncs_t *funcs) \n{\n\tvfsfile_t \t\t*vfs;\n\tflocation_t \tloc;\n\tchar \t\t\tpakfile[MAX_OSPATH];\n\tvoid\t\t\t*handle;\n\tchar \t\t\t*ext;\n\n\text = COM_FileExtension(pakname);\n\tif (!funcs) {\n\t\tif (strcasecmp(ext, \"pak\") == 0) \n\t\t\tfuncs = &packfilefuncs;\n#ifdef WITH_ZIP\n\t\telse if (strcasecmp(ext, \"pk3\") == 0) \n\t\t\tfuncs = &zipfilefuncs;\n\t\telse if (strcasecmp(ext, \"pk4\") == 0) \n\t\t\tfuncs = &zipfilefuncs;\n#endif\t// WITH_ZIP\n#ifdef DOOMWADS\n\t\telse if (strcasecmp(ext, \"wad\") == 0)\n\t\t\tfuncs = &doomwadfilefuncs;\n#endif\n\t\telse \n\t\t{\n\t\t\tCom_Printf(\"Unknown pak file type\");\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t/* Check the pak exists */\n\tif (!search->funcs->FindFile(search->handle, &loc, pakname, NULL))\n\t\treturn 1;\t//not found..\n\n\t/* Load the pak file */\n\tsnprintf(pakfile, sizeof(pakfile), \"%s%s\", pathto, pakname);\n\tvfs = search->funcs->OpenVFS(search->handle, &loc, \"r\");\n\tif (!vfs)\n\t\treturn -1;\n\thandle = funcs->OpenNew(vfs, pakfile);\n\tif (!handle) {\n\t\tVFS_CLOSE(vfs);\n\t\treturn -1;\n\t}\n\tsnprintf (pakfile, sizeof(pakfile), \"%s%s/\", pathto, pakname);\n\tFS_AddPathHandle(pakfile, funcs, handle, true, false, FS_LOAD_FILE_ALL);\n\n\treturn 0;\n}\n\nstatic qbool FS_RemovePak (const char *pakfile) {\n\tsearchpath_t *cur = fs_searchpaths;\n\tqbool ret = false;\n\n\twhile (cur) {\n\t\tcur = cur->next;\n\t}\n\n\treturn ret;\n}\n\n// ==============\n// FS_AddUserPaks\n// ==============\n// Reads the pak.lst from the give directory \n// and adds the given paks \n\nstatic void FS_AddUserPaks(char *dir, searchpath_t *parent, FS_Load_File_Types loadstuff) \n{\n\tFILE\t*f;\n\tchar\tpakfile[MAX_OSPATH];\n\tchar\tuserpak[MAX_OSPATH];\n\n\t// add paks listed in paks.lst\n\tsnprintf (pakfile, sizeof (pakfile), \"%s/pak.lst\", dir);\n\tf = fopen(pakfile, \"r\");\n\tif (f) {\n\t\tint len;\n\t\twhile (1) {\n\t\t\tif (!fgets(userpak, MAX_OSPATH, f))\n\t\t\t\tbreak;\n\t\t\tlen = strlen(userpak);\n\t\t\t// strip endline\n\t\t\tif (userpak[len-1] == '\\n') {\n\t\t\t\tuserpak[len-1] = '\\0';\n\t\t\t\t--len;\n\t\t\t}\n\t\t\tif (userpak[len-1] == '\\r') {\n\t\t\t\tuserpak[len-1] = '\\0';\n\t\t\t\t--len;\n\t\t\t}\n\t\t\tif (len < 5)\n\t\t\t\tcontinue;\n\t\t\tif (!strncasecmp(userpak,\"soft\",4))\n\t\t\t\tcontinue;\n\t\t\tFS_AddPak(dir, userpak, parent, NULL);\n\t\t}\n\t\tfclose(f);\n\t}\n\t// add userdir.pak\n\tif (UserdirSet) {\n\t\tsnprintf (pakfile, sizeof (pakfile), \"%s.pak\", userdirfile);\n\t\tFS_AddPak(dir, pakfile, parent, NULL);\n#ifdef WITH_ZIP\n\t\tsnprintf (pakfile, sizeof (pakfile), \"%s.pk3\", userdirfile);\n\t\tFS_AddPak(dir, pakfile, parent, NULL);\n#endif // WITH_ZIP\n\t}\n}\n\n\n// <-- QW262\n\nvoid FS_AddUserDirectory(char *dir)\n{\n\tsize_t dir_len;\n\tchar *malloc_dir;\n\n\tif (!UserdirSet) {\n\t\treturn;\n\t}\n\n\tswitch (userdir_type) {\n\tcase 0:\tsnprintf(com_userdir, sizeof(com_userdir), \"%s/%s\", com_gamedir, userdirfile); break;\n\tcase 1:\tsnprintf(com_userdir, sizeof(com_userdir), \"%s/%s/%s\", com_basedir, userdirfile, dir); break;\n\tcase 2: snprintf(com_userdir, sizeof(com_userdir), \"%s/qw/%s/%s\", com_basedir, userdirfile, dir); break;\n\tcase 3: snprintf(com_userdir, sizeof(com_userdir), \"%s/qw/%s\", com_basedir, userdirfile); break;\n\tcase 4: snprintf(com_userdir, sizeof(com_userdir), \"%s/%s\", com_basedir, userdirfile); break;\n\tcase 5: {\n\t\tconst char* homedir = Sys_HomeDirectory();\n\t\tif (homedir && homedir[0]) {\n\t\t\tsnprintf(com_userdir, sizeof(com_userdir), \"%s/qw/%s\", homedir, userdirfile);\n\t\t}\n\t\tbreak;\n\t}\n\tdefault:\n\t\treturn;\n\t}\n\n\tdir_len = strlen(com_userdir) + 2;\n\tmalloc_dir = Q_malloc(sizeof(char)*dir_len);\n\tsnprintf(malloc_dir, dir_len, \"%s/\", com_userdir);\n\tFS_AddPathHandle(com_userdir, &osfilefuncs, malloc_dir, false, false, FS_LOAD_FILE_ALL);\n}\n\nvoid Draw_InitConback(void);\n\n// Sets the gamedir and path to a different directory.\nvoid FS_SetGamedir(char* dir, qbool force)\n{\n\tsearchpath_t* next;\n\n\tif (dir == NULL || !dir[0]) {\n\t\tdir = \"qw\";\n\t}\n\n\tif (strstr(dir, \"..\") || strchr(dir, '/') || strchr(dir, '\\\\') || strchr(dir, ':')) {\n\t\tCom_Printf(\"Gamedir should be a single filename, not a path\\n\");\n\t\treturn;\n\t}\n\n\tif (!force && !strcmp(com_gamedirfile, dir)) {\n\t\t// Still the same\n\t\treturn;\n\t}\n\n\tstrlcpy(com_gamedirfile, dir, sizeof(com_gamedirfile));\n\n\t// Free up any current game dir info.\n\tFS_FlushFSHash();\n\n\t// free up any current game dir info\n\twhile (fs_searchpaths != fs_base_searchpaths)\n\t{\n\t\tfs_searchpaths->funcs->ClosePath(fs_searchpaths->handle);\n\t\tnext = fs_searchpaths->next;\n\t\tQ_free(fs_searchpaths);\n\t\tfs_searchpaths = next;\n\t}\n\n\tfilesystemchanged = true;\n\n\t// Flush all data, so it will be forced to reload.\n\tCache_Flush();\n\n\tsnprintf(com_gamedir, sizeof(com_gamedir), \"%s/%s\", com_basedir, dir);\n\n\tFS_AddGameDirectory(com_gamedir, FS_LOAD_FILE_ALL);\n\tif (*com_homedir) {\n\t\tchar tmp[MAX_OSPATH];\n\t\tsnprintf(&tmp[0], sizeof(tmp), \"%s/%s\", com_homedir, dir);\n\t\tFS_AddHomeDirectory(tmp, FS_LOAD_FILE_ALL);\n\t}\n\n\t// Reload gamedir specific conback as its not flushed\n\tDraw_InitConback();\n\n\tFS_AddUserDirectory(dir);\n}\n\nchar *FS_NextPath (char *prevpath)\n{\n\tsearchpath_t\t*s;\n\tchar\t\t\t*prev;\n\n\tif (!prevpath)\n\t\treturn com_gamedir;\n\n\tprev = com_gamedir;\n\tfor (s=fs_searchpaths ; s ; s=s->next)\n\t{\n\t\tif (s->funcs != &osfilefuncs)\n\t\t\tcontinue;\n\n\t\tif (prevpath == prev)\n\t\t\treturn s->handle;\n\t\tprev = s->handle;\n\t}\n\n\treturn NULL;\n}\n\nvoid FS_ShutDown( void ) {\n\n\t// free data\n\twhile (fs_searchpaths)\t{\n\t\tsearchpath_t  *next;\n\t\tnext = fs_searchpaths->next;\n\t\tQ_free (fs_searchpaths);\n\t\tfs_searchpaths = next;\n\t}\n\n\t// flush all data, so it will be forced to reload\n\tCache_Flush ();\n\n\t// reset globals\n\n\tfs_base_searchpaths = fs_searchpaths = NULL;\n\n\tcom_gamedirfile[0]\t= 0;\n\tcom_gamedir[0]\t\t= 0;\n\tcom_basedir[0]\t\t= 0;\n\tcom_homedir[0]\t\t= 0;\n\n\tuserdirfile[0]\t\t= 0;\n\tcom_userdir[0]\t\t= 0;\n\tuserdir_type\t\t= -1;\n}\n\nvoid SysLibrarySupportDir(char *basedir, int length);\n\nvoid FS_InitFilesystemEx( qbool guess_cwd ) {\n\tint i;\n\tchar tmp_path[MAX_OSPATH];\n\n\tFS_ShutDown();\n\n\tif (guess_cwd) { // so, com_basedir directory will be where ezquake*.exe located\n#ifndef __APPLE__\n\t\tchar *e;\n#endif\n\n#if defined(_WIN32)\n\t\tif(!(i = GetModuleFileName(NULL, com_basedir, sizeof(com_basedir)-1)))\n\t\t\tSys_Error(\"FS_InitFilesystemEx: GetModuleFileName failed\");\n\t\tcom_basedir[i] = 0; // ensure null terminator\n#elif defined(__linux__)\n\t\tif (!Sys_fullpath(com_basedir, \"/proc/self/exe\", sizeof(com_basedir)))\n\t\t\tSys_Error(\"FS_InitFilesystemEx: Sys_fullpath failed\");\n#elif defined(__APPLE__)\n\t\tSysLibrarySupportDir(com_basedir, MAX_OSPATH);\n#elif defined(__FreeBSD__)\n\t\tint mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};\n\t\tsize_t com_basedirlen = sizeof(com_basedir);\n\t\tif (sysctl(mib, 4, com_basedir, &com_basedirlen, NULL, 0) < 0)\n\t\t\tSys_Error(\"FS_InitFilesystemEx: sysctl failed\");\n#else\n\t\tcom_basedir[0] = 0; // FIXME: others\n#endif\n\n#ifndef __APPLE__\n\t\t// strip ezquake*.exe, we need only path\n\t\tfor (e = com_basedir+strlen(com_basedir)-1; e >= com_basedir; e--)\n\t\t\tif (*e == '/' || *e == '\\\\')\n\t\t\t{\n\t\t\t\t*e = 0;\n\t\t\t\tbreak;\n\t\t\t}\n#endif\n\t}\n\telse if ((i = COM_CheckParm (cmdline_param_filesystem_basedir)) && i < COM_Argc() - 1) {\n\t\t// -basedir <path>\n\t\t// Overrides the system supplied base directory (under id1)\n\t\tstrlcpy (com_basedir, COM_Argv(i + 1), sizeof(com_basedir));\n\t}\n \telse { // made com_basedir equa to cwd\n//#ifdef __FreeBSD__\n//\t\tstrlcpy(com_basedir, DATADIR, sizeof(com_basedir) - 1);\n//#else\n\n\t\tSys_getcwd(com_basedir, sizeof(com_basedir) - 1); // FIXME strlcpy (com_basedir, \".\", sizeof(com_basedir)); ?\n//#endif\n\t}\n\n\tfor (i = 0; i < (int) strlen(com_basedir); i++)\n\t\tif (com_basedir[i] == '\\\\')\n\t\t\tcom_basedir[i] = '/';\n\n\ti = strlen(com_basedir) - 1;\n\tif (i >= 0 && com_basedir[i] == '/')\n\t\tcom_basedir[i] = 0;\n\n\tstrlcpy(com_homedir, Sys_HomeDirectory(), sizeof(com_homedir));\n\n\tif (COM_CheckParm(cmdline_param_filesystem_nohome)) {\n\t\tcom_homedir[0] = 0;\n\t}\n\n\tif (com_homedir[0])\n\t{\n#ifdef _WIN32\n\t\tstrlcat(com_homedir, \"/ezQuake\", sizeof(com_homedir));\n#else\n\t\tstrlcat(com_homedir, \"/.ezquake\", sizeof(com_homedir));\n#endif\n\t\tCom_Printf(\"Using home directory \\\"%s\\\"\\n\", com_homedir);\n\t}\n\n\t// start up with id1 by default\n\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s/%s\", com_basedir, \"id1\");\n\tif (!COM_FileExists(tmp_path)) {\n\t\t// (try upper-case ID1 if it's there... Steam...)\n\t\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s/%s\", com_basedir, \"ID1\");\n\t\tFS_AddGameDirectory(tmp_path, FS_LOAD_FILE_ALL);\n\t}\n\telse {\n\t\tFS_AddGameDirectory(tmp_path, FS_LOAD_FILE_ALL);\n\t}\n\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s/%s\", com_basedir, \"ezquake\");\n\tFS_AddGameDirectory(tmp_path, FS_LOAD_FILE_ALL);\n\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s/%s\", com_basedir, \"qw\");\n\tFS_AddGameDirectory(tmp_path, FS_LOAD_FILE_ALL);\n\tif (*com_homedir) {\n\t    FS_AddHomeDirectory(com_homedir, FS_LOAD_FILE_ALL);\n\t}\n\n\t// -data <datadir>\n\t// Adds datadirs similar to \"-game\"\n\t//\n\t//Tei: original code from qbism.\n\n\ti = 1;\n\twhile((i = COM_CheckParmOffset (cmdline_param_filesystem_data, i))) {\n\t\tif (i && i < COM_Argc()-1) {\n\t\t\tchar tmp_path[MAX_OSPATH];\n\t\t\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s%s\", com_basedir, COM_Argv(i+1)); /* FIXME: No slash?? Intentional?? */\n\t\t\tFS_AddGameDirectory(tmp_path, FS_LOAD_FILE_ALL);\n\t\t}\n\t\ti++;\n\t}\n\n\n\t// any set gamedirs will be freed up to here\n\tfs_base_searchpaths = fs_searchpaths;\n\n\tif ((i = COM_CheckParm(cmdline_param_filesystem_userdir)) && i < COM_Argc() - 2)\n\t\tFS_SetUserDirectory(COM_Argv(i+1), COM_Argv(i+2));\n\n\t// the user might want to override default game directory\n\tif (!(i = COM_CheckParm (cmdline_param_filesystem_game)))\n\t\ti = COM_FindParm(\"+gamedir\");\n\tif (i && i < COM_Argc() - 1)\n\t\tFS_SetGamedir (COM_Argv(i + 1), true);\n}\n\nvoid FS_InitFilesystem( void ) {\n\tvfsfile_t *vfs;\n\n\tFS_InitModuleFS();\n\tFS_InitFilesystemEx( false ); // first attempt, simplified\n\tvfs = FS_OpenVFS(\"gfx.wad\", \"rb\", FS_ANY); \n\tif (vfs) { // // we found gfx.wad, seems we have proper com_basedir\n\t\tVFS_CLOSE(vfs);\n\t\treturn;\n\t}\n\n\tFS_InitFilesystemEx( true );  // second attempt\n}\n\n// allow user select differet \"style\" how/where open/save different media files.\n// so user select media_dir is relative to quake base dir or some system HOME dir or may be full path\n// NOTE: using static buffer, use with care\nchar *FS_LegacyDir(char *media_dir)\n{\n\tstatic char dir[MAX_PATH];\n\n\t// dir empty, return gamedir\n\tif (!media_dir || !media_dir[0]) {\n\t\tstrlcpy(dir, cls.gamedir, sizeof(dir));\n\t\treturn dir;\n\t}\n\n\tswitch (cl_mediaroot.integer) {\n\t\tcase 1:  //\t\t\t/home/qqshka/ezquake/<demo_dir>\n\t\t\twhile(media_dir[0] == '/' || media_dir[0] == '\\\\')\n\t\t\t\tmedia_dir++; // skip precending / probably smart\n\n\t\t\tsnprintf(dir, sizeof(dir), \"%s/%s\", com_homedir, media_dir);\n\t\t\treturn dir;\n\t\tcase 2:  //\t\t\t/fullpath\n\t\t  snprintf(dir, sizeof(dir), \"%s\", media_dir);\n\t\t\treturn dir;\n\n\t\tdefault: //     /basedir/<demo_dir>\n\t\t\twhile(media_dir[0] == '/' || media_dir[0] == '\\\\')\n\t\t\t\tmedia_dir++; // skip precending / probably smart\n\n\t\t  snprintf(dir, sizeof(dir), \"%s/%s\", com_basedir, media_dir);\n\t\t\treturn dir;\n\t}\n}\n\n\n//=============================================================================\n// VFS\n\nvoid VFS_CHECKCALL (struct vfsfile_s *vf, void *fld, char *emsg) {\n\tif (!fld)\n\t\tSys_Error(\"%s\", emsg);\n}\n\nvoid VFS_CLOSE (struct vfsfile_s *vf) {\n\tassert(vf);\n\tVFS_CHECKCALL(vf, vf->Close, \"VFS_CLOSE\");\n\tvf->Close(vf);\n}\n\nunsigned long VFS_TELL (struct vfsfile_s *vf) {\n\tassert(vf);\n\tVFS_CHECKCALL(vf, vf->Tell, \"VFS_TELL\");\n\treturn vf->Tell(vf);\n}\n\nunsigned long VFS_GETLEN (struct vfsfile_s *vf) {\n\tassert(vf);\n\tVFS_CHECKCALL(vf, vf->GetLen, \"VFS_GETLEN\");\n\treturn vf->GetLen(vf);\n}\n\n/**\n * VFS_SEEK() reposition a stream\n * If whence is set to SEEK_SET, SEEK_CUR, or SEEK_END, the offset  is\n * relative to the  start of the file, the current position indicator, or\n * end-of-file, respectively.\n * Return Value\n * Upon successful completion, VFS_SEEK(), returns 0. \n * Otherwise, -1 is returned\n */\nint VFS_SEEK (struct vfsfile_s *vf, unsigned long pos, int whence) {\n\tassert(vf);\n\tVFS_CHECKCALL(vf, vf->Seek, \"VFS_SEEK\");\n\treturn vf->Seek(vf, pos, whence);\n}\n\nint VFS_READ (struct vfsfile_s *vf, void *buffer, int bytestoread, vfserrno_t *err) {\n\tassert(vf);\n\tVFS_CHECKCALL(vf, vf->ReadBytes, \"VFS_READ\");\n\treturn vf->ReadBytes(vf, buffer, bytestoread, err);\n}\n\n// FIXME: bytestowrite should be size_t\nint VFS_WRITE (struct vfsfile_s *vf, const void *buffer, int bytestowrite) {\n\tassert(vf);\n\tVFS_CHECKCALL(vf, vf->WriteBytes, \"VFS_WRITE\");\n\treturn vf->WriteBytes(vf, buffer, bytestowrite);\n}\n\nvoid VFS_FLUSH (struct vfsfile_s *vf) {\n\tassert(vf);\n\tif(vf->Flush)\n\t\tvf->Flush(vf);\n}\n\n// return null terminated string\nchar *VFS_GETS(struct vfsfile_s *vf, char *buffer, int buflen)\n{\n\tchar in;\n\tchar *out = buffer;\n\tint len = buflen-1;\n\n\tassert(vf);\n\tVFS_CHECKCALL(vf, vf->ReadBytes, \"VFS_GETS\");\n\n//\tif (len == 0)\n//\t\treturn NULL;\n\n// FIXME: I am not sure how to handle this better\n\tif (len <= 0)\n\t\tSys_Error(\"VFS_GETS: len <= 0\");\n\n\twhile (len > 0)\n\t{\n\t\tif (!VFS_READ(vf, &in, 1, NULL))\n\t\t{\n\t\t\tif (len == buflen-1)\n\t\t\t\treturn NULL;\n\t\t\t*out = '\\0';\n\t\t\treturn buffer;\n\t\t}\n\t\tif (in == '\\n')\n\t\t\tbreak;\n\t\t*out++ = in;\n\t\tlen--;\n\t}\n\t*out = '\\0';\n\n\treturn buffer;\n}\n\nqbool VFS_COPYPROTECTED(struct vfsfile_s *vf) {\n\tassert(vf);\n\treturn vf->copyprotected;\n}\n\n//\n// some general function to open VFS file, except VFSTCP\n//\n\n/* ================\n * FS_OpenVFS\n * ================\n * This should be how all files are opened, will either give a valid vfsfile_t\n * or NULL. We first check the home directory for the file first, if not found \n * we try the basedir.\n */\n// VFS-XXX: This is very similar to FS_OpenVFS in fs.c\n// VFS-FIXME: Clean up this function to reduce the relativeto options \nvfsfile_t *FS_OpenVFS(const char *filename, char *mode, relativeto_t relativeto)\n{\n\tchar fullname[MAX_OSPATH];\n\tflocation_t loc;\n\tvfsfile_t *vfs = NULL;\n\n\t//blanket-bans - Avoid combination of / & \\ for directories\n\tif (Sys_PathProtection(filename)) \n\t\treturn NULL;\n\n\tif (strcmp(mode, \"rb\") && strcmp(mode, \"wb\") && strcmp(mode, \"ab\")) {\n\t\treturn NULL; //urm, unable to write/append\n\t}\n\n\t/* General opening of files */\n\tswitch (relativeto)\n\t{\n\tcase FS_NONE_OS: \t//OS access only, no paks, open file as is\n\t\tif (*com_homedir)\n\t\t{\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s\", com_homedir, filename);\n\t\t\tvfs = VFSOS_Open(fullname, mode);\n\t\t\tif (vfs)\n\t\t\t\treturn vfs;\n\t\t}\n\n\t\tsnprintf(fullname, sizeof(fullname), \"%s\", filename);\n\t\treturn VFSOS_Open(fullname, mode);\n\n\tcase FS_GAME_OS:\t//OS access only, no paks\n\t\t//sss: check userdir first\n\t\tif (*com_userdir) {\n\t\t\tsnprintf (fullname, sizeof (fullname), \"%s/%s\", com_userdir, filename);\n\t\t\tvfs = VFSOS_Open (fullname, mode);\n\t\t\tif (vfs) {\n\t\t\t\treturn vfs;\n\t\t\t}\n\t\t}\n\n\t\tif (*com_homedir)\n\t\t{\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s/%s\", com_homedir, com_gamedirfile, filename);\n\t\t\tvfs = VFSOS_Open(fullname, mode);\n\t\t\tif (vfs) {\n\t\t\t\treturn vfs;\n\t\t\t}\n\t\t}\n\n\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s/%s\", com_basedir, com_gamedirfile, filename);\n\t\treturn VFSOS_Open(fullname, mode);\n\n/* VFS-XXX: Removed as we don't really use this\n *\tcase FS_SKINS:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/qw/skins/%s\", com_homedir, filename);\n\t\telse\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/qw/skins/%s\", com_basedir, filename);\n\t\tbreak;\n */\n\n\tcase FS_BASE:\n//\t\tif (*com_homedir)\n//\t\t{\n//\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s\", com_homedir, filename);\n//\t\t\tvfs = VFSOS_Open(fullname, mode);\n//\t\t\tif (vfs)\n//\t\t\t\treturn vfs;\n//\t\t}\n\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s\", com_basedir, filename);\n\t\treturn VFSOS_Open(fullname, mode);\n\n/* VFS-XXX: Removed as we don't really use this\n * case FS_CONFIGONLY:\n\t\tif (*com_homedir)\n\t\t{\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s\", com_homedir, filename);\n\t\t\tvfs = VFSOS_Open(fullname, mode);\n\t\t\tif (vfs)\n\t\t\t\treturn vfs;\n\t\t}\n\t\tsnprintf(fullname, sizeof(fullname), \"%s/ezquake/%s\", com_basedir, filename);\n\t\treturn VFSOS_Open(fullname, mode);\n */\n\tcase FS_HOME:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s\", com_homedir, filename);\n\t\telse\n\t\t\treturn NULL;\n\t\treturn VFSOS_Open(fullname, mode);\n\n\tcase FS_PAK:\n\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s/%s\", com_basedir, com_gamedirfile, filename);\n\t\tbreak;\n\n\tcase FS_ANY:\n\t\tvfs = FS_OpenVFS(filename, mode, FS_NONE_OS);\n\t\tif (vfs)\n\t\t\treturn vfs;\n\n\t\tvfs = FS_OpenVFS(filename, mode, FS_HOME);\n\t\tif (vfs)\n\t\t\treturn vfs;\n\n\t\tvfs = FS_OpenVFS(filename, mode, FS_GAME_OS);\n\t\tif (vfs)\n\t\t\treturn vfs;\n\n\t\treturn FS_OpenVFS(filename, mode, FS_PAK);\n\n\tdefault:\n\t\tSys_Error(\"FS_OpenVFS: Bad relative path (%i)\", relativeto);\n\t\tbreak;\n\t}\n\n\tFS_FLocateFile(filename, FSLFRT_IFFOUND, &loc);\n\n\tif (loc.search)\n\t{\n\t\treturn loc.search->funcs->OpenVFS(loc.search->handle, &loc, mode);\n\t\t//return VFS_Filter(filename, loc.search->funcs->OpenVFS(loc.search->handle, &loc, mode));\n\t}\n\n\t//if we're meant to be writing, best write to it.\n\tif (strchr(mode , 'w') || strchr(mode , 'a'))\n\t\treturn VFSOS_Open(fullname, mode);\n\treturn NULL;\n}\n\n\n\n// VFS\n//======================================================================================================\n\ntypedef enum { PAKOP_ADD, PAKOP_REM } pak_operation_t;\n\nstatic qbool FS_PakOperation(char* pakfile, pak_operation_t op)\n{\n\tswitch (op) {\n\tcase PAKOP_REM: return FS_RemovePak(pakfile);\n\tcase PAKOP_ADD: return false; // VFS-FIXME: return FS_AddPak(pakfile);\n\t}\n\n\treturn false;\n}\n\nstatic qbool FS_PakOper_NoPath(char* pakfile, pak_operation_t op)\n{\n\tchar pathbuf[MAX_PATH];\n\t\n\tif (op != PAKOP_REM) // do not allow removing e.g. \"pak\"\n\t\tif (FS_PakOperation(pakfile, op)) return true;\n\n\t// This is nonstandard, therefore should be discussed first\n\t// snprintf(pathbuf, sizeof(pathbuf), \"addons/%s.pak\", pakfile);\n\t// if (FS_PakOperation(pathbuf, op)) return true;\n\n\tsnprintf(pathbuf, sizeof(pathbuf), \"ezquake/%s.pak\", pakfile);\n\tif (FS_PakOperation(pathbuf, op)) return true;\n\n\tsnprintf(pathbuf, sizeof(pathbuf), \"qw/%s.pak\", pakfile);\n\tif (FS_PakOperation(pathbuf, op)) return true;\n\n\tsnprintf(pathbuf, sizeof(pathbuf), \"id1/%s.pak\", pakfile);\n\tif (FS_PakOperation(pathbuf, op)) return true;\n\n\treturn false;\n}\n\nstatic void FS_PakOper_Process(pak_operation_t op)\n{\n\tint i;\n\tint c = Cmd_Argc();\n\n\tif (cls.state != ca_disconnected && !cls.demoplayback && !cls.mvdplayback) {\n\t\tCom_Printf(\"This command cannot be used while connected\\n\");\n\t\treturn;\n\t}\n\tif (c < 2) {\n\t\tCom_Printf(\"Usage: %s <pakname> [<pakname> [<pakname> ...]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tfor (i = 1; i < c; i++)\n\t{\n\t\tif (FS_PakOper_NoPath(Cmd_Argv(i), op)) {\n\t\t\tCom_Printf(\"Pak %s has been %s\\n\", Cmd_Argv(i), op == PAKOP_ADD ? \"added\" : \"removed\");\n\t\t\tCache_Flush();\n\t\t}\n\t\telse Com_Printf(\"Pak not %s\\n\", op == PAKOP_ADD ? \"added\" : \"removed\");\n\t}\n}\n\nvoid FS_PakAdd_f(void) { FS_PakOper_Process(PAKOP_ADD); }\nvoid FS_PakRem_f(void) { FS_PakOper_Process(PAKOP_REM); }\n\n#define CHUNK 16384\n\n#ifdef WITH_ZLIB\n\nint FS_GZipPack (char *source_path,\n\t\t\t\t  char *destination_path,\n\t\t\t\t  qbool overwrite)\n{\n\tFILE *source\t\t\t= NULL;\n\tgzFile gzip_destination = NULL;\n\n\t// Open source file.\n\tsource = fopen (source_path, \"rb\");\n\n\t// Failed to open source.\n\tif (!source_path)\n\t{\n\t\treturn 0;\n\t}\n\n\t// Check if the destination file exists and\n\t// if we're allowed to overwrite it.\n\tif (COM_FileExists (destination_path) && !overwrite)\n\t{\n\t\treturn 0;\n\t}\n\n\t// Create the path for the destination.\n\tFS_CreatePath (COM_SkipPathWritable (destination_path));\n\n\t// Open destination file.\n\tgzip_destination = gzopen (destination_path, \"wb\");\n\n\t// Failed to open destination.\n\tif (!gzip_destination)\n\t{\n\t\treturn 0;\n\t}\n\n\t// Pack.\n\t{\n\t\tunsigned char inbuf[CHUNK];\n\t\tint bytes_read = 0;\n\n\t\twhile ((bytes_read = (int)fread (inbuf, 1, (int)sizeof(inbuf), source)) > 0)\n\t\t{\n\t\t\tgzwrite (gzip_destination, inbuf, bytes_read);\n\t\t}\n\n\t\tfclose (source);\n\t\tgzclose (gzip_destination);\n\t}\n\n\treturn 1;\n}\n\n//\n// Unpack a .gz file.\n//\nint FS_GZipUnpack (char *source_path,\t\t// The path to the compressed source file.\n\t\t\t\t\tchar *destination_path, // The destination file path.\n\t\t\t\t\tqbool overwrite)\t\t// Overwrite the destination file if it exists?\n{\n\tFILE *dest\t\t= NULL;\n\tint retval\t\t= 0;\n\n\t// Check if the destination file exists and\n\t// if we're allowed to overwrite it.\n\tif (COM_FileExists(destination_path) && !overwrite) {\n\t\treturn 0;\n\t}\n\n\t// Create the path for the destination.\n\tFS_CreatePath(COM_SkipPathWritable(destination_path));\n\n\t// Open destination.\n\tdest = fopen(destination_path, \"wb\");\n\n\t// Failed to open the file for writing.\n\tif (!dest) {\n\t\treturn 0;\n\t}\n\n\t// Unpack.\n\t{\n\t\tunsigned char out[CHUNK];\n\t\tgzFile gzip_file = gzopen (source_path, \"rb\");\n\t\tif (gzip_file == NULL) {\n\t\t\tfclose(dest);\n\t\t\tSys_remove(destination_path);\n\t\t\treturn 0;\n\t\t}\n\n\t\twhile ((retval = gzread (gzip_file, out, CHUNK)) > 0) {\n\t\t\tfwrite (out, 1, retval, dest);\n\t\t}\n\n\t\tgzclose (gzip_file);\n\t\tfclose (dest);\n\t}\n\n\treturn 1;\n}\n\n#define GZIP_MEMORY_DEFAULT   (4 * 1024 * 1024)\n#define GZIP_MEMORY_INCREMENT (2 * 1024 * 1024)\n\nvoid* FS_GZipUnpackToMemory(char* source_path, size_t* unpacked_length)\n{\n\tbyte* memory = NULL;\n\tsize_t actual_length = 0;\n\tsize_t allocated_length = 0;\n\tgzFile gzip_file;\n\n\tgzip_file = gzopen(source_path, \"rb\");\n\tif (gzip_file == NULL) {\n\t\treturn NULL;\n\t}\n\n\t// Unpack.\n\tmemory = Q_malloc_named(GZIP_MEMORY_DEFAULT, source_path);\n\tallocated_length = GZIP_MEMORY_DEFAULT;\n\t{\n\t\tunsigned char out[CHUNK];\n\t\tint retval; // bytes output\n\n\t\twhile ((retval = gzread(gzip_file, out, CHUNK)) > 0) {\n\t\t\tif (actual_length + retval > allocated_length) {\n\t\t\t\t// Need to increase memory buffer\n\t\t\t\tbyte* new_memory = Q_realloc_named(memory, allocated_length + GZIP_MEMORY_INCREMENT, source_path);\n\n\t\t\t\t// Failed to reallocate, clean-up and error\n\t\t\t\tif (new_memory == NULL) {\n\t\t\t\t\tgzclose(gzip_file);\n\t\t\t\t\tQ_free(memory);\n\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\n\t\t\t\t// Carry on\n\t\t\t\tmemory = new_memory;\n\t\t\t\tallocated_length = allocated_length + GZIP_MEMORY_INCREMENT;\n\t\t\t}\n\t\t\tmemcpy(memory + actual_length, out, retval);\n\t\t\tactual_length += retval;\n\t\t}\n\n\t\tgzclose(gzip_file);\n\n\t\tif (unpacked_length) {\n\t\t\t*unpacked_length = actual_length;\n\t\t}\n\t\treturn memory;\n\t}\n}\n\n//\n// Inflates source file into the dest file. (Stolen from a zlib example :D) ... NOT the same as gzip!\n//\nint FS_ZlibInflate(FILE *source, FILE *dest)\n{\n\tint ret = 0;\n\tunsigned have = 0;\n\tz_stream strm;\n\tunsigned char in[CHUNK];\n\tunsigned char out[CHUNK];\n\n\t// Allocate inflate state.\n\tstrm.zalloc\t\t= Z_NULL;\n\tstrm.zfree\t\t= Z_NULL;\n\tstrm.opaque\t\t= Z_NULL;\n\tstrm.avail_in\t= 0;\n\tstrm.next_in\t= Z_NULL;\n\tret\t\t\t\t= inflateInit(&strm);\n\n\tif (ret != Z_OK)\n\t{\n\t\treturn ret;\n\t}\n\n\t// Decompress until deflate stream ends or end of file.\n\tdo\n\t{\n\t\tstrm.avail_in = (int)fread(in, 1, CHUNK, source);\n\n\t\tif (ferror(source))\n\t\t{\n\t\t\t(void)inflateEnd(&strm);\n\t\t\treturn Z_ERRNO;\n\t\t}\n\n\t\tif (strm.avail_in == 0)\n\t\t{\n\t\t\tbreak;\n\t\t}\n\n\t\tstrm.next_in = in;\n\n\t\t// Run inflate() on input until output buffer not full.\n\t\tdo\n\t\t{\n\t\t\tstrm.avail_out = CHUNK;\n\t\t\tstrm.next_out = out;\n\t\t\tret = inflate(&strm, Z_NO_FLUSH);\n\n\t\t\t// State not clobbered.\n\t\t\tassert(ret != Z_STREAM_ERROR);\n\n\t\t\tswitch (ret)\n\t\t\t{\n\t\t\t\tcase Z_NEED_DICT:\n\t\t\t\t\tret = Z_DATA_ERROR; // Fall through.\n\t\t\t\tcase Z_DATA_ERROR:\n\t\t\t\tcase Z_MEM_ERROR:\n\t\t\t\t\t(void)inflateEnd(&strm);\n\t\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\thave = CHUNK - strm.avail_out;\n\n\t\t\tif (fwrite(out, 1, have, dest) != have || ferror(dest))\n\t\t\t{\n\t\t\t\t(void)inflateEnd(&strm);\n\t\t\t\treturn Z_ERRNO;\n\t\t\t}\n\t\t} while (strm.avail_out == 0);\n\n\t\t// Done when inflate() says it's done\n\t} while (ret != Z_STREAM_END);\n\n\t// Clean up and return.\n\t(void)inflateEnd(&strm);\n\n\treturn (ret == Z_STREAM_END) ? Z_OK : Z_DATA_ERROR;\n}\n\n//\n// Unpack a zlib file. ... NOT the same as gzip!\n//\nint FS_ZlibUnpack (char *source_path,\t\t// The path to the compressed source file.\n\t\t\t\t\tchar *destination_path, // The destination file path.\n\t\t\t\t\tqbool overwrite)\t\t// Overwrite the destination file if it exists?\n{\n\tFILE *source\t= NULL;\n\tFILE *dest\t\t= NULL;\n\tint retval\t\t= 0;\n\n\t// Open source.\n\tif (FS_FOpenPathFile (source_path, &source) < 0 || !source)\n\t{\n\t\treturn 0;\n\t}\n\n\t// Check if the destination file exists and\n\t// if we're allowed to overwrite it.\n\tif (COM_FileExists (destination_path) && !overwrite)\n\t{\n\t\tfclose (source);\n\t\treturn 0;\n\t}\n\n\t// Create the path for the destination.\n\tFS_CreatePath (COM_SkipPathWritable (destination_path));\n\n\t// Open destination.\n\tdest = fopen (destination_path, \"wb\");\n\n\t// Failed to open the file for writing.\n\tif (!dest)\n\t{\n\t\treturn 0;\n\t}\n\n\t// Unpack.\n\tretval = FS_ZlibInflate (source, dest);\n\n\tfclose (source);\n\tfclose (dest);\n\n\treturn (retval != Z_OK) ? 0 : 1;\n}\n\n/*\n//\n// Unpack a zlib file to a temp file... NOT the same as gzip!\n//\nint FS_ZlibUnpackToTemp (char *source_path,\t\t// The compressed source file.\n\t\t\t\t\t\t  char *unpack_path,\t\t// A buffer that will contain the path to the unpacked file.\n\t\t\t\t\t\t  int unpack_path_size,\t\t// The size of the buffer.\n\t\t\t\t\t\t  char *append_extension)\t// The extension if any that should be appended to the filename.\n{\n\tint retval;\n\t// Get a unique temp filename.\n\tif (COM_GetUniqueTempFilename (NULL, unpack_path, unpack_path_size, true) < 0)\n\t{\n\t\treturn 0;\n\t}\n\n\t// Delete the existing temp file (it is created when the filename is received above).\n\tretval = unlink (unpack_path);\n\tif (retval == -1 && qerrno != ENOENT)\n\t{\n\t\tunpack_path[0] = 0;\n\t\treturn 0;\n\t}\n\n\t// Append the extension if any.\n\tif (append_extension != NULL)\n\t{\n\t\tchar tmp_path[MAX_OSPATH];\n\t\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s%s\", unpack_path, append_extension);\n\t\tstrlcpy (unpack_path, tmp_path, unpack_path_size);\n\t}\n\n\t// Unpack the file.\n\tif (!FS_ZlibUnpack (source_path, unpack_path, true))\n\t{\n\t\tunpack_path[0] = 0;\n\t\treturn 0;\n\t}\n\n\treturn 1;\n}\n*/\n\n#endif // WITH_ZLIB\n\n#ifdef WITH_ZIP\n\n#define ZIP_WRITEBUFFERSIZE (8192)\n\nvoid* FS_ZipUnpackOneFileToMemory(\n\tunzFile zip_file,\n\tconst char *filename_inzip,\n\tqbool case_sensitive,\n\tqbool keep_path,\n\tconst char *password,\n\tsize_t* unpacked_length\n)\n{\n\tchar\t\t\tfilename[MAX_PATH_LENGTH];\n\tunz_file_info\tfile_info;\n\tbyte*           extracted_file = NULL;\n\tunsigned int    position = 0;\n\n\tif (filename_inzip == NULL || zip_file == NULL) {\n\t\treturn NULL;\n\t}\n\n\t// Locate the file.\n\tif (unzLocateFile(zip_file, filename_inzip, case_sensitive) != UNZ_OK) {\n\t\treturn NULL;\n\t}\n\n\t// unzLocateFile has set the current file\n\tif (unzGetCurrentFileInfo(zip_file, &file_info, filename, sizeof(filename), NULL, 0, NULL, 0) != UNZ_OK) {\n\t\treturn NULL;\n\t}\n\n\textracted_file = Q_malloc(file_info.uncompressed_size);\n\tif (extracted_file == NULL) {\n\t\treturn NULL;\n\t}\n\t*unpacked_length = file_info.uncompressed_size;\n\n\t//\n\t// Extract the file.\n\t//\n\t{\n#define\tEXPECTED_BYTES_READ\t1\n\t\tint\tbytes_read = 0;\n\n\t\t//\n\t\t// Open the zip file.\n\t\t//\n\t\tif (unzOpenCurrentFilePassword(zip_file, password) != UNZ_OK) {\n\t\t\treturn NULL;\n\t\t}\n\n\t\t//\n\t\t// Write the decompressed file to the destination.\n\t\t//\n\t\tdo\n\t\t{\n\t\t\tif (position >= file_info.uncompressed_size) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Read the decompressed data from the zip file.\n\t\t\tbytes_read = unzReadCurrentFile(zip_file, extracted_file + position, file_info.uncompressed_size - position);\n\n\t\t\tif (bytes_read > 0) {\n\t\t\t\tposition += bytes_read;\n\t\t\t}\n\t\t} while (bytes_read > 0);\n\n\t\t//\n\t\t// Close the zip file\n\t\t//\n\t\tunzCloseCurrentFile(zip_file);\n\t}\n\n\treturn extracted_file;\n}\n\nint FS_ZipBreakupArchivePath (char *archive_extension,\t\t\t// The extension of the archive type we're looking fore \"zip\" for example.\n\t\t\t\t\t\t\t   char *path,\t\t\t\t\t\t// The path that should be broken up into parts.\n\t\t\t\t\t\t\t   char *archive_path,\t\t\t\t// The buffer that should contain the archive path after the breakup.\n\t\t\t\t\t\t\t   int archive_path_size,\t\t\t// The size of the archive path buffer.\n\t\t\t\t\t\t\t   char *inzip_path,\t\t\t\t// The buffer that should contain the inzip path after the breakup.\n\t\t\t\t\t\t\t   int inzip_path_size)\t\t\t\t// The size of the inzip path buffer.\n{\n\tchar *archive_path_found = NULL;\n\tchar *inzip_path_found = NULL;\n\tchar regexp[MAX_PATH];\n\tint result_length = 0;\n\n\tsnprintf(regexp, sizeof(regexp), \"(.*?\\\\.%s)(\\\\\\\\|/)(.*)\", archive_extension);\n\n\t// Get the archive path.\n\tif (Utils_RegExpGetGroup (regexp, path, (const char **) &archive_path_found, &result_length, 1))\n\t{\n\t\tstrlcpy (archive_path, archive_path_found, archive_path_size);\n\n\t\t// Get the path of the demo in the zip.\n\t\tif (Utils_RegExpGetGroup (regexp, path, (const char **) &inzip_path_found, &result_length, 3))\n\t\t{\n\t\t\tstrlcpy (inzip_path, inzip_path_found, inzip_path_size);\n\t\t\tUtils_RegExpFreeSubstring(archive_path_found);\n\t\t\tUtils_RegExpFreeSubstring(inzip_path_found);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tUtils_RegExpFreeSubstring(archive_path_found);\n\tUtils_RegExpFreeSubstring(inzip_path_found);\n\n\treturn -1;\n}\n\n//\n// Does the given path point to a zip file?\n//\nqbool FS_IsArchive (char *zip_path)\n{\n\treturn (!strcasecmp(COM_FileExtension (zip_path), \"zip\"));\n}\n\nunzFile FS_ZipUnpackOpenFile (const char *zip_path)\n{\n\treturn unzOpen (zip_path);\n}\n\nint FS_ZipUnpackCloseFile (unzFile zip_file)\n{\n\tif (zip_file == NULL)\n\t{\n\t\treturn UNZ_OK;\n\t}\n\n\treturn unzClose (zip_file);\n}\n\n//\n// Creates a directory entry from a unzip fileinfo struct.\n//\nstatic void FS_ZipMakeDirent (sys_dirent *ent, char *filename_inzip, unz_file_info *unzip_fileinfo)\n{\n\t// Save the name.\n    strlcpy(ent->fname, filename_inzip, sizeof(ent->fname));\n    ent->fname[MAX_PATH_LENGTH-1] = 0;\n\n\t// TODO : Zip size is unsigned long, dir entry unsigned int, data loss possible for really large files.\n\tent->size = (unsigned int)unzip_fileinfo->uncompressed_size;\n\n    // Get the filetime.\n\tent->time.wYear = unzip_fileinfo->tmu_date.tm_year;\n\tent->time.wMonth = unzip_fileinfo->tmu_date.tm_mon + 1;\n\tent->time.wDay = unzip_fileinfo->tmu_date.tm_mday;\n\tent->time.wHour = unzip_fileinfo->tmu_date.tm_hour;\n\tent->time.wMinute = unzip_fileinfo->tmu_date.tm_min;\n\n\t// FIXME: There is no directory structure inside of zip files, but the files are named as if there is.\n\t// that is, if the file is in the root it will be named \"file\" in the zip file info. If it's in a directory\n\t// it will be named \"dir/file\". So we could find out if it's a directory or not by checking the filename here.\n\tent->directory = 0;\n\tent->hidden = 0;\n}\n\nint FS_ZipUnpack(\n\tunzFile zip_file,\n\tchar *destination_path,\n\tqbool case_sensitive,\n\tqbool keep_path,\n\tqbool overwrite,\n\tconst char *password\n)\n{\n\tint error = UNZ_OK;\n\tunsigned long file_num = 0;\n\tunz_global_info global_info;\n\n\t// Get the number of files in the zip archive.\n\terror = unzGetGlobalInfo (zip_file, &global_info);\n\n\tif (error != UNZ_OK)\n\t{\n\t\treturn error;\n\t}\n\n\tfor (file_num = 0; file_num < global_info.number_entry; file_num++)\n\t{\n\t\tif (FS_ZipUnpackCurrentFile (zip_file, destination_path, case_sensitive, keep_path, overwrite, password) != UNZ_OK)\n\t\t{\n\t\t\t// We failed to extract a file, so there must be something wrong.\n\t\t\tbreak;\n\t\t}\n\n\t\tif ((file_num + 1) < global_info.number_entry)\n\t\t{\n\t\t\terror = unzGoToNextFile (zip_file);\n\n\t\t\tif (error != UNZ_OK)\n\t\t\t{\n\t\t\t\t// Either we're at the end or something went wrong.\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn error;\n}\n\nint FS_ZipUnpackOneFile (unzFile zip_file,\t\t\t\t// The zip file opened with FS_ZipUnpackOpenFile(..)\n\t\t\t\t\t\t  const char *filename_inzip,\t// The name of the file to unpack inside the zip.\n\t\t\t\t\t\t  const char *destination_path, // The destination path where to extract the file to.\n\t\t\t\t\t\t  qbool case_sensitive,\t\t\t// Should we look for the filename case sensitivly?\n\t\t\t\t\t\t  qbool keep_path,\t\t\t\t// Should the path inside the zip be preserved when unpacking?\n\t\t\t\t\t\t  qbool overwrite,\t\t\t\t// Overwrite any existing file with the same name when unpacking?\n\t\t\t\t\t\t  const char *password)\t\t\t// The password to use when extracting the file.\n{\n\tint\tretval = UNZ_OK;\n\n\tif (filename_inzip == NULL || zip_file == NULL)\n\t{\n\t\treturn UNZ_ERRNO;\n\t}\n\n\t// Locate the file.\n\tretval = unzLocateFile (zip_file, filename_inzip, case_sensitive);\n\n\tif (retval == UNZ_END_OF_LIST_OF_FILE || retval != UNZ_OK)\n\t{\n\t\treturn retval;\n\t}\n\n\t// Unpack the file.\n\tFS_ZipUnpackCurrentFile (zip_file, destination_path, case_sensitive, keep_path, overwrite, password);\n\n\treturn retval;\n}\n\nint FS_ZipUnpackCurrentFile (unzFile zip_file,\n\t\t\t\t\t\t\t  const char *destination_path,\n\t\t\t\t\t\t\t  qbool case_sensitive,\n\t\t\t\t\t\t\t  qbool keep_path,\n\t\t\t\t\t\t\t  qbool overwrite,\n\t\t\t\t\t\t\t  const char *password)\n{\n\tint\t\t\t\terror = UNZ_OK;\n\tvoid\t\t\t*buf = NULL;\n\tunsigned int\tsize_buf = ZIP_WRITEBUFFERSIZE;\n\tchar\t\t\tfilename[MAX_PATH_LENGTH];\n\tunz_file_info\tfile_info;\n\n\t// Nothing to extract from.\n\tif (zip_file == NULL)\n\t{\n\t\treturn UNZ_ERRNO;\n\t}\n\n\t// Get the file info (including the filename).\n\terror = unzGetCurrentFileInfo (zip_file, &file_info, filename, sizeof(filename), NULL, 0, NULL, 0);\n\n\tif (error != UNZ_OK)\n\t{\n\t\tgoto finish;\n\t}\n\n\t// Should the path in the zip file be preserved when extracting or not?\n\tif (!keep_path)\n\t{\n\t\t// Only keep the filename.\n\t\tstrlcpy (filename, COM_SkipPath (filename), sizeof(filename));\n\t}\n\n\t//\n\t// Check if the file already exists and create the path if needed.\n\t//\n\t{\n\t\tchar tmp_path[MAX_OSPATH];\n\t\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s/%s\", destination_path, filename);\n\n\t\t// Check if the file exists.\n\t\tif (COM_FileExists(tmp_path) && !overwrite) {\n\t\t\terror = UNZ_ERRNO;\n\t\t\tgoto finish;\n\t\t}\n\n\t\t// Create the destination dir if it doesn't already exist.\n\t\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s%s\", destination_path, PATH_SEPARATOR);\n\t\tFS_CreatePath(tmp_path);\n\n\t\t// Create the relative path before extracting.\n\t\tif (keep_path) {\n\t\t\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s%s%s\", destination_path, PATH_SEPARATOR, filename);\n\t\t\tFS_CreatePath(tmp_path);\n\t\t}\n\t}\n\n\t//\n\t// Extract the file.\n\t//\n\t{\n\t\t#define\tEXPECTED_BYTES_READ\t1\n\t\tint\tbytes_read\t= 0;\n\t\tFILE *fout\t\t= NULL;\n\n\t\terror = UNZ_OK;\n\n\t\t//\n\t\t// Open the zip file.\n\t\t//\n\t\t{\n\t\t\terror = unzOpenCurrentFilePassword (zip_file, password);\n\n\t\t\t// Failed opening the zip file.\n\t\t\tif (error != UNZ_OK)\n\t\t\t{\n\t\t\t\tgoto finish;\n\t\t\t}\n\t\t}\n\n\t\t//\n\t\t// Open the destination file for writing.\n\t\t//\n\t\t{\n\t\t\tchar tmp_path[MAX_OSPATH];\n\t\t\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s/%s\", destination_path, filename);\n\t\t\tfout = fopen(tmp_path, \"wb\");\n\n\t\t\t// Failure.\n\t\t\tif (fout == NULL)\n\t\t\t{\n\t\t\t\terror = UNZ_ERRNO;\n\t\t\t\tgoto finish;\n\t\t\t}\n\t\t}\n\n\t\t//\n\t\t// Write the decompressed file to the destination.\n\t\t//\n\t\tbuf = Q_malloc (size_buf);\n\t\tdo\n\t\t{\n\t\t\t// Read the decompressed data from the zip file.\n\t\t\tbytes_read = unzReadCurrentFile (zip_file, buf, size_buf);\n\n\t\t\tif (bytes_read > 0)\n\t\t\t{\n\t\t\t\t// Write the decompressed data to the destination file.\n\t\t\t\tif (fwrite (buf, bytes_read, EXPECTED_BYTES_READ, fout) != EXPECTED_BYTES_READ)\n\t\t\t\t{\n\t\t\t\t\t// We failed to write the specified number of bytes.\n\t\t\t\t\terror = UNZ_ERRNO;\n\t\t\t\t\tfclose (fout);\n\t\t\t\t\tunzCloseCurrentFile (zip_file);\n\t\t\t\t\tgoto finish;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\twhile (bytes_read > 0);\n\n\t\t//\n\t\t// Close the zip file + destination file.\n\t\t//\n\t\t{\n\t\t\tif (fout)\n\t\t\t{\n\t\t\t\tfclose (fout);\n\t\t\t}\n\n\t\t\tunzCloseCurrentFile (zip_file);\n\t\t}\n\n\t\t// TODO : Change file date for the file.\n\t}\n\nfinish:\n\tQ_free (buf);\n\n\treturn error;\n}\n\n// Gets the details about the file and save them in the sys_dirent struct.\nstatic int FS_ZipGetDetails (unzFile zip_file, sys_dirent *ent)\n{\n\tint error = UNZ_OK;\n\tchar filename_inzip[MAX_PATH_LENGTH];\n\tunz_file_info unzip_fileinfo;\n\n\terror = unzGetCurrentFileInfo (zip_file,\n\t\t&unzip_fileinfo,\t\t\t\t\t\t// File info.\n\t\tfilename_inzip, sizeof(filename_inzip), // The files name will be copied to this.\n\t\tNULL, 0, NULL, 0);\t\t\t\t\t\t// Extras + comment stuff. We don't care about this.\n\n\tif (error != UNZ_OK)\n\t{\n\t\treturn error;\n\t}\n\n\t// Populate the directory entry object.\n\tFS_ZipMakeDirent (ent, filename_inzip, &unzip_fileinfo);\n\n\treturn error;\n}\n\nint FS_ZipGetFirst (unzFile zip_file, sys_dirent *ent)\n{\n\tint error = UNZ_OK;\n\n\t// Go to the first file in the zip.\n\tif ((error = unzGoToFirstFile (zip_file)) != UNZ_OK)\n\t{\n\t\treturn error;\n\t}\n\n\t// Get details.\n\tif ((error = FS_ZipGetDetails(zip_file, ent)) != UNZ_OK)\n\t{\n\t\treturn error;\n\t}\n\n\treturn 1;\n}\n\nint FS_ZipGetNextFile (unzFile zip_file, sys_dirent *ent)\n{\n\tint error = UNZ_OK;\n\n\t// Get the next file.\n\terror = unzGoToNextFile (zip_file);\n\n\tif (error == UNZ_END_OF_LIST_OF_FILE || error != UNZ_OK)\n\t{\n\t\treturn error;\n\t}\n\n\t// Get details.\n\tif ((error = FS_ZipGetDetails(zip_file, ent)) != UNZ_OK)\n\t{\n\t\treturn error;\n\t}\n\n\treturn 1;\n}\n\n#endif // WITH_ZIP\n\n//============================================================================\n\nvoid FS_InitModuleFS (void)\n{\n\tCmd_AddCommand(\"fs_loadpak\", FS_PakAdd_f);\n\tCmd_AddLegacyCommand(\"loadpak\", \"fs_loadpak\");\n\tCmd_AddCommand(\"fs_removepak\", FS_PakRem_f);\n\tCmd_AddLegacyCommand(\"removepak\", \"fs_removepak\");\n\tCmd_AddCommand(\"fs_path\", FS_Path_f);\n\tCmd_AddLegacyCommand(\"path\", \"fs_path\");\n\tCmd_AddCommand(\"fs_restart\", FS_ReloadPackFiles_f);\n\tCmd_AddCommand(\"fs_diff\", FS_DiffFile_f); \t\t// VFS-FIXME <-- Only a debug function\n\tCmd_AddCommand(\"fs_dir\", FS_Dir_f);\n\tCmd_AddLegacyCommand(\"dir\", \"fs_dir\");\n\tCmd_AddCommand(\"fs_locate\", FS_Locate_f);\n\tCmd_AddLegacyCommand(\"locate\", \"fs_locate\");\n\tCmd_AddCommand(\"fs_search\", FS_ListFiles_f);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_FILESYSTEM);\n\tCvar_Register(&fs_cache);\n\tCvar_Register(&fs_savegame_home);\n\tCvar_ResetCurrentGroup();\n\n\tCom_Printf(\"Initialising quake VFS filesystem\\n\");\n}\n\n\n/******************************************************************************\n *     TODO:\n ****************************\n * 2) Need to check all the VFS-FIXME's\n * \t\tD-Kure: I have made comment on the ones I unsure of,\n * \t\t        others just need the time to do what the comment says.\n *\n * 3) Need to add Sys_EnumerateFiles for sys_mac.c\n * \t\tD-Kure: I have noooo idea what the mac eqivalent functions are.\n *\n * 4) Replace some of the functions (marked with VFS-XXX) with the ezquake equivlant\n * \t\tD-Kure: This functions seem to be in common.c and are marked \n * \t\t        FS_* instead of COM_*.\n *\n * 9) Renaming of lots of the COM_* functions to FS_*\n *\n ****************************\n *     No particular order\n ****************************\n *  - Add tar.gz support\n *  - Replace zipped/tar/tar.gz demo opening with VFS calls\n *\n *****************************************************************************/\n\nstatic searchpath_t *FS_AddPathHandle(char *probablepath, searchpathfuncs_t *funcs, void *handle, qbool copyprotect, qbool istemporary, FS_Load_File_Types loadstuff)\n{\n\tsearchpath_t *search;\n\n\tsearch = (searchpath_t*)Q_malloc (sizeof(searchpath_t));\n\tsearch->copyprotected = copyprotect;\n\tsearch->istemporary = istemporary;\n\tsearch->handle = handle;\n\tsearch->funcs = funcs;\n\n\tsearch->next = fs_searchpaths;\n\tfs_searchpaths = search;\n\n\tfilesystemchanged = true;\n\n\t//add any data files too\n\tif (loadstuff & FS_LOAD_FILE_PAK)\n\t\tFS_AddDataFiles(probablepath, search, \"pak\", &packfilefuncs);//q1/hl/h2/q2\n\t//pk2s never existed.\n#ifdef WITH_ZIP\n\tif (loadstuff & FS_LOAD_FILE_PK3)\n\t\tFS_AddDataFiles(probablepath, search, \"pk3\", &zipfilefuncs);\t//q3 + offspring\n\tif (loadstuff & FS_LOAD_FILE_PK4)\n\t\tFS_AddDataFiles(probablepath, search, \"pk4\", &zipfilefuncs);\t//q4\n\t//we could easily add zip, but it's friendlier not to\n#endif\n\n#ifdef DOOMWADS\n\tif (loadstuff & FS_LOAD_FILE_DOOMWAD)\n\t\tFS_AddDataFiles(probablepath, search, \"wad\", &doomwadfilefuncs);\t//q4\n#endif\n\tif (loadstuff & FS_LOAD_FROM_PAKLST)\n\t\tFS_AddUserPaks(probablepath, search, loadstuff);\n\n\treturn search;\n}\n\n/*\n============\nFS_Dir_f\n============\n*/\n#define FS_DIR_HIDE_DIRECTORY 1\n#define FS_DIR_HIDE_EXTENSION 2\n#define FS_DIR_HIDE_SIZE      4\n\nstatic int FS_Dir_List(char *name, int size, void *parm)\n{\n\tchar filename[MAX_QPATH];\n\tint options = (int)(intptr_t)parm;\n\n\tif (options & FS_DIR_HIDE_DIRECTORY) {\n\t\tname = COM_SkipPathWritable(name);\n\t}\n\n\tif (options & FS_DIR_HIDE_EXTENSION) {\n\t\tCOM_StripExtension(name, filename, sizeof(filename));\n\t\tname = filename;\n\t}\n\n\tif (name && name[0]) {\n\t\tif (options & FS_DIR_HIDE_SIZE) {\n\t\t\tCom_Printf(\"%s\\n\", name);\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"%s  (%i)\\n\", name, size);\n\t\t}\n\t}\n\treturn 1;\n}\n\nvoid FS_Dir_f(void)\n{\n\tchar match[MAX_QPATH];\n\tint options = 0;\n\n\tif (Cmd_Argc() == 1) {\n\t\tCom_Printf(\"Usage: %s [directory [file_suffix [options]]]\\n\", Cmd_Argv(0));\n\t\tCom_Printf(\"Options: hideext, hidedir, hidesize\\n\");\n\t\treturn;\n\t}\n\n\tstrlcpy(match, Cmd_Argv(1), sizeof(match));\n\n\tif (Cmd_Argc() > 2 && Cmd_Argv(2)[0]) {\n\t\tstrlcat(match, \"/*.\", sizeof(match));\n\t\tstrlcat(match, Cmd_Argv(2), sizeof(match));\n\t}\n\telse {\n\t\tstrlcat(match, \"/*\", sizeof(match));\n\t}\n\n\tif (Cmd_Argc() > 3) {\n\t\tint i;\n\t\tfor (i = 3; i < Cmd_Argc(); ++i) {\n\t\t\tif (!strcmp(Cmd_Argv(i), \"hidedir\")) {\n\t\t\t\toptions |= FS_DIR_HIDE_DIRECTORY;\n\t\t\t}\n\t\t\telse if (!strcmp(Cmd_Argv(i), \"hideext\")) {\n\t\t\t\toptions |= FS_DIR_HIDE_EXTENSION;\n\t\t\t}\n\t\t\telse if (!strcmp(Cmd_Argv(i), \"hidesize\")) {\n\t\t\t\toptions |= FS_DIR_HIDE_SIZE;\n\t\t\t}\n\t\t}\n\t}\n\n\tFS_EnumerateFiles(match, FS_Dir_List, (void*)(intptr_t)options);\n}\n\n/*\n============\nFS_Locate_f\n============\n*/\nchar * FS_Locate_GetPath (const char *file);\nvoid FS_Locate_f (void)\n{\n\tflocation_t loc;\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"Usage: %s filename\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif (FS_FLocateFile(Cmd_Argv(1), FSLFRT_LENGTH, &loc)>=0)\n\t{\n\t\tif (!*loc.rawname)\n\t\t{\n\t\t\tCom_Printf(\"File is compressed inside \");\n\t\t\tloc.search->funcs->PrintPath(loc.search->handle);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCom_Printf(\"Inside %s\\n\", loc.rawname);\n\t\t\tloc.search->funcs->PrintPath(loc.search->handle);\n\t\t}\n\t}\n\telse\n\t\tCom_Printf(\"Not found\\n\");\n}\n\nchar * FS_Locate_GetPath (const char *file)\n{\n\tflocation_t loc;\n\tif (FS_FLocateFile(file, FSLFRT_LENGTH, &loc)>=0)\n\t{\t//loc.search->funcs->PrintPath(loc.search->handle);\n\t\treturn (char *) loc.search->handle;\n\t}\n\telse\n\t{\n\t\treturn \"\";\n\t}\n}\n\nint fs_hash_dups;\nint fs_hash_files;\n\nvoid FS_FlushFSHash(void)\n{\n\tif (filesystemhash)\n\t{\n\t\tHash_Flush(filesystemhash);\n\t}\n\n\tfilesystemchanged = true;\n}\n\nvoid FS_RebuildFSHash(void)\n{\n\tsearchpath_t\t*search;\n\tif (!filesystemhash)\n\t{\n\t\tfilesystemhash = Hash_InitTable(1024);\n\t}\n\telse\n\t{\n\t\tFS_FlushFSHash();\n\t}\n\n\tfs_hash_dups = 0;\n\tfs_hash_files = 0;\n\n\tif (fs_purepaths)\n\t{\t\n\t\t// Go for the pure paths first.\n\t\tfor (search = fs_purepaths; search; search = search->nextpure)\n\t\t{\n\t\t\tsearch->funcs->BuildHash(search->handle);\n\t\t}\n\t}\n\tfor (search = fs_searchpaths ; search ; search = search->next)\n\t{\n\t\tsearch->funcs->BuildHash(search->handle);\n\t}\n\n\tfilesystemchanged = false;\n\n\tCom_DPrintf(\"%i unique files, %i duplicates\\n\", fs_hash_files, fs_hash_dups);\n}\n\n/*\n============\nFS_FLocateFile\n\nFinds the file in the search path.\nLook FSLF_ReturnType_e definition so you know that it returns.\n============\n*/\nint FS_FLocateFile(const char *filename, FSLF_ReturnType_e returntype, flocation_t *loc)\n{\n\tint             depth = 0;\n\tint             len;\n\tsearchpath_t    *search;\n#ifdef SERVERONLY\n\tchar            cleanpath[MAX_OSPATH];\n#endif\n\tvoid            *pf = NULL;\n\n#ifdef SERVERONLY\n\tfilename = FS_GetCleanPath(filename, cleanpath, sizeof(cleanpath));\n\tif (!filename) {\n\t\tgoto fail;\n\t}\n#endif\n\n\tif (fs_cache.value) {\n\t\tif (filesystemchanged) {\n\t\t\tFS_RebuildFSHash();\n\t\t}\n\t\tpf = Hash_GetInsensitive(filesystemhash, filename);\n\t\tif (!pf) {\n\t\t\tgoto fail;\n\t\t}\n\t}\n\n#ifndef SERVERONLY\n\tif (fs_purepaths) {\n\t\tfor (search = fs_purepaths; search; search = search->nextpure) {\n\t\t\tif (search->funcs->FindFile(search->handle, loc, filename, pf)) {\n\t\t\t\tif (loc) {\n\t\t\t\t\tloc->search = search;\n\t\t\t\t\tlen = loc->len;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tlen = 0;\n\t\t\t\t}\n\t\t\t\tgoto out;\n\t\t\t}\n\t\t\tdepth += (search->funcs != &osfilefuncs || returntype == FSLFRT_DEPTH_ANYPATH);\n\t\t}\n\t}\n#endif\n\n\t//\n\t// search through the path, one element at a time.\n\t//\n\tfor (search = fs_searchpaths; search; search = search->next) {\n\t\tif (search->funcs->FindFile(search->handle, loc, filename, pf)) {\n\t\t\tif (loc) {\n\t\t\t\tloc->search = search;\n\t\t\t\tlen = loc->len;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlen = 0;\n\t\t\t}\n\n\t\t\tgoto out;\n\t\t}\n\n\t\tdepth += (search->funcs == &osfilefuncs || returntype == FSLFRT_DEPTH_ANYPATH);\n\t}\n\nfail:\n\tif (loc)\n\t\tloc->search = NULL;\n\tdepth = 0x7fffffff; // NOTE: weird, we return it on fail in some cases, may cause mistakes by user.\n\tlen = -1;\n\nout:\n\tif (returntype == FSLFRT_IFFOUND) {\n\t\treturn len != -1;\n\t}\n\telse if (returntype == FSLFRT_LENGTH) {\n\t\treturn len;\n\t}\n\telse {\n\t\treturn depth;\n\t}\n}\n\nchar *FS_GetPackHashes(char *buffer, int buffersize, qbool referencedonly)\n{\n\tsearchpath_t\t*search;\n\tbuffersize--;\n\t*buffer = 0;\n\n\tif (fs_purepaths)\n\t{\n\t\tfor (search = fs_purepaths ; search ; search = search->nextpure)\n\t\t{\n\t\t\tchar tmp[64];\n\t\t\tsnprintf(&tmp[0], sizeof(tmp), \"%i \", search->crc_check);\n\t\t\tstrlcat(buffer, tmp, buffersize);\n\t\t}\n\t\treturn buffer;\n\t}\n\telse\n\t{\n\t\tfor (search = fs_searchpaths ; search ; search = search->next)\n\t\t{\n\t\t\tif (!search->crc_check && search->funcs->GeneratePureCRC)\n\t\t\t\tsearch->crc_check = search->funcs->GeneratePureCRC(search->handle, 0, 0);\n\t\t\tif (search->crc_check)\n\t\t\t{\n\t\t\t\tchar tmp[64];\n\t\t\t\tsnprintf(&tmp[0], sizeof(tmp), \"%i \", search->crc_check);\n\t\t\t\tstrlcat(buffer, tmp, buffersize);\n\t\t\t}\n\t\t}\n\t\treturn buffer;\n\t}\n}\nchar *FS_GetPackNames(char *buffer, int buffersize, qbool referencedonly)\n{\n\treturn \"\";\n}\n\n\n//true if protection kicks in\nqbool Sys_PathProtection(const char *pattern)\n{\n\tif (strchr(pattern, '\\\\'))\n\t{\n\t\tchar *s;\n\t\t//Com_Printf(\"Warning: \\\\ charactures in filename %s\\n\", pattern);\n\t\twhile((s = strchr(pattern, '\\\\')))\n\t\t\t*s = '/';\n\t}\n\n\treturn false; // Ignore the stuff below so we always succed\n\n/*\tif (strstr(pattern, \"..\"))\n\t\tCom_Printf(\"Error: '..' charactures in filename %s\\n\", pattern);\n\telse if (pattern[0] == '/')\n\t\tCom_Printf(\"Error: absolute path in filename %s\\n\", pattern);\n\telse if (strstr(pattern, \":\")) //win32 drive seperator (or mac path seperator, but / works there and they're used to it)\n\t\tCom_Printf(\"Error: absolute path in filename %s\\n\", pattern);\n\telse\n\t\treturn false;\n\treturn true;*/\n}\n\n#ifdef WITH_ZIP\ntypedef struct {\n\tunsigned char ident1;\n\tunsigned char ident2;\n\tunsigned char cm;\n\tunsigned char flags;\n\tunsigned int mtime;\n\tunsigned char xflags;\n\tunsigned char os;\n} gzheader_t;\n#define sizeofgzheader_t 10\n\n#define\tGZ_FTEXT\t1\n#define\tGZ_FHCRC\t2\n#define GZ_FEXTRA\t4\n#define GZ_FNAME\t8\n#define GZ_FCOMMENT\t16\n#define GZ_RESERVED (32|64|128)\n\n#include <zlib.h>\n\nvfsfile_t *FS_DecompressGZip(vfsfile_t *infile, gzheader_t *header)\n{\n\tchar inchar;\n\tunsigned short inshort;\n\tvfsfile_t *temp;\n\tvfserrno_t err;\n\n\tif (header->flags & GZ_RESERVED)\n\t{\t//reserved bits should be 0\n\t\t//this is probably static, so it's not a gz. doh.\n\t\tVFS_SEEK(infile, 0, SEEK_SET);\n\t\treturn infile;\n\t}\n\n\tif (header->flags & GZ_FEXTRA)\n\t{\n\t\tVFS_READ(infile, &inshort, sizeof(inshort), &err);\n\t\tinshort = LittleShort(inshort);\n\t\tVFS_SEEK(infile, VFS_TELL(infile) + inshort, SEEK_SET);\n\t}\n\n\tif (header->flags & GZ_FNAME)\n\t{\n\t\tCom_Printf(\"gzipped file name: \");\n\t\tdo {\n\t\t\tif (VFS_READ(infile, &inchar, sizeof(inchar), &err) != 1)\n\t\t\t\tbreak;\n\t\t\tCom_Printf(\"%c\", inchar);\n\t\t} while(inchar);\n\t\tCom_Printf(\"\\n\");\n\t}\n\n\tif (header->flags & GZ_FCOMMENT)\n\t{\n\t\tCom_Printf(\"gzipped file comment: \");\n\t\tdo {\n\t\t\tif (VFS_READ(infile, &inchar, sizeof(inchar), &err) != 1)\n\t\t\t\tbreak;\n\t\t\tCom_Printf(\"%c\", inchar);\n\t\t} while(inchar);\n\t\tCom_Printf(\"\\n\");\n\t}\n\n\tif (header->flags & GZ_FHCRC)\n\t{\n\t\tVFS_READ(infile, &inshort, sizeof(inshort), &err);\n\t}\n\n\n\n\ttemp = FS_OpenTemp();\n\tif (!temp)\n\t{\n\t\tVFS_SEEK(infile, 0, SEEK_SET);\t//doh\n\t\treturn infile;\n\t}\n\n\n\t{\n\t\tBytef inbuffer[16384];\n\t\tBytef outbuffer[16384];\n\t\tint ret;\n\n\t\tz_stream strm = {\n\t\t\tinbuffer,\n\t\t\t0,\n\t\t\t0,\n\n\t\t\toutbuffer,\n\t\t\tsizeof(outbuffer),\n\t\t\t0,\n\n\t\t\tNULL,\n\t\t\tNULL,\n\n\t\t\tNULL,\n\t\t\tNULL,\n\t\t\tNULL,\n\n\t\t\tZ_UNKNOWN,\n\t\t\t0,\n\t\t\t0\n\t\t};\n\n\t\tstrm.avail_in = VFS_READ(infile, inbuffer, sizeof(inbuffer), &err);\n\t\tstrm.next_in = inbuffer;\n\n\t\tinflateInit2(&strm, -MAX_WBITS);\n\n\t\twhile ((ret=inflate(&strm, Z_SYNC_FLUSH)) != Z_STREAM_END)\n\t\t{\n\t\t\tif (strm.avail_in == 0 || strm.avail_out == 0)\n\t\t\t{\n\t\t\t\tif (strm.avail_in == 0)\n\t\t\t\t{\n\t\t\t\t\tstrm.avail_in = VFS_READ(infile, inbuffer, sizeof(inbuffer), &err);\n\t\t\t\t\tstrm.next_in = inbuffer;\n\t\t\t\t}\n\n\t\t\t\tif (strm.avail_out == 0)\n\t\t\t\t{\n\t\t\t\t\tstrm.next_out = outbuffer;\n\t\t\t\t\tVFS_WRITE(temp, outbuffer, strm.total_out);\n\t\t\t\t\tstrm.total_out = 0;\n\t\t\t\t\tstrm.avail_out = sizeof(outbuffer);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t//doh, it terminated for no reason\n\t\t\tinflateEnd(&strm);\n\t\t\tif (ret != Z_STREAM_END)\n\t\t\t{\n\t\t\t\tCom_Printf(\"Couldn't decompress gz file\\n\");\n\t\t\t\tVFS_CLOSE(temp);\n\t\t\t\tVFS_CLOSE(infile);\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n\t\t//we got to the end\n\t\tVFS_WRITE(temp, outbuffer, strm.total_out);\n\n\t\tinflateEnd(&strm);\n\n\t\tVFS_SEEK(temp, 0, SEEK_SET);\n\t}\n\tVFS_CLOSE(infile);\n\n\treturn temp;\n}\n#endif\n\nvfsfile_t *VFS_Filter(const char *filename, vfsfile_t *handle)\n{\n#ifdef WITH_ZIP\n\tvfserrno_t err;\n\tchar *ext;\n\n\tif (!handle || handle->WriteBytes || handle->seekingisabadplan)\t//only on readonly files\n\t\treturn handle;\n\n\text = COM_FileExtension(filename);\n\tif (!strcasecmp(ext, \"gz\"))\n\t{\n\t\tgzheader_t gzh;\n\t\tif (VFS_READ(handle, &gzh, sizeofgzheader_t, &err) == sizeofgzheader_t)\n\t\t{\n\t\t\tif (gzh.ident1 == 0x1f && gzh.ident2 == 0x8b && gzh.cm == 8)\n\t\t\t{\t//it'll do\n\t\t\t\treturn FS_DecompressGZip(handle, &gzh);\n\t\t\t}\n\t\t}\n\t\tVFS_SEEK(handle, 0, SEEK_SET);\n\t}\n#endif\n\treturn handle;\n}\n\nint FS_Rename2(char *oldf, char *newf, relativeto_t oldrelativeto, relativeto_t newrelativeto)\n{\n\tchar oldfullname[MAX_OSPATH];\n\tchar newfullname[MAX_OSPATH];\n\n\tswitch (oldrelativeto)\n\t{\n\tcase FS_GAME_OS:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(oldfullname, sizeof(oldfullname), \"%s/%s/\", com_homedir, com_gamedirfile);\n\t\telse\n\t\t\tsnprintf(oldfullname, sizeof(oldfullname), \"%s%s/\", com_basedir, com_gamedirfile);\n\t\tbreak;\n/*\tcase FS_SKINS:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(oldfullname, sizeof(oldfullname), \"%sqw/skins/\", com_homedir);\n\t\telse\n\t\t\tsnprintf(oldfullname, sizeof(oldfullname), \"%sqw/skins/\", com_basedir);\n\t\tbreak;\n */\n\n\tcase FS_BASE:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(oldfullname, sizeof(oldfullname), \"%s\", com_homedir);\n\t\telse\n\t\t\tsnprintf(oldfullname, sizeof(oldfullname), \"%s\", com_basedir);\n\t\tbreak;\n\tdefault:\n\t\tSys_Error(\"FS_Rename case not handled\\n\");\n\t}\n\n\tswitch (newrelativeto)\n\t{\n\tcase FS_GAME_OS:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(newfullname, sizeof(newfullname), \"%s/%s/\", com_homedir, com_gamedirfile);\n\t\telse\n\t\t\tsnprintf(newfullname, sizeof(newfullname), \"%s%s/\", com_basedir, com_gamedirfile);\n\t\tbreak;\n\n/*\tcase FS_SKINS:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(newfullname, sizeof(newfullname), \"%s/qw/skins/\", com_homedir);\n\t\telse\n\t\t\tsnprintf(newfullname, sizeof(newfullname), \"%sqw/skins/\", com_basedir);\n\t\tbreak;\n */\n\tcase FS_BASE:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(newfullname, sizeof(newfullname), \"%s\", com_homedir);\n\t\telse\n\t\t\tsnprintf(newfullname, sizeof(newfullname), \"%s\", com_basedir);\n\t\tbreak;\n\tdefault:\n\t\tSys_Error(\"FS_Rename case not handled\\n\");\n\t}\n\n\tstrlcat(oldfullname, oldf, sizeof(oldfullname));\n\tstrlcat(newfullname, newf, sizeof(newfullname));\n\n\tFS_CreatePathRelative(newf, newrelativeto);\n\treturn rename(oldfullname, newfullname);\n}\nint FS_Rename(char *oldf, char *newf, relativeto_t relativeto)\n{\n\tchar oldfullname[MAX_OSPATH];\n\tchar newfullname[MAX_OSPATH];\n\n\tswitch (relativeto)\n\t{\n\tcase FS_GAME_OS:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(oldfullname, sizeof(oldfullname), \"%s/%s/\", com_homedir, com_gamedirfile);\n\t\telse\n\t\t\tsnprintf(oldfullname, sizeof(oldfullname), \"%s%s/\", com_basedir, com_gamedirfile);\n\t\tbreak;\n\n/*\tcase FS_SKINS:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(oldfullname, sizeof(oldfullname), \"%s/qw/skins/\", com_homedir);\n\t\telse\n\t\t\tsnprintf(oldfullname, sizeof(oldfullname), \"%sqw/skins/\", com_basedir);\n\t\tbreak;\n */\n\n\tcase FS_BASE:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(oldfullname, sizeof(oldfullname), \"%s\", com_homedir);\n\t\telse\n\t\t\tsnprintf(oldfullname, sizeof(oldfullname), \"%s\", com_basedir);\n\t\tbreak;\n\n\tdefault:\n\t\tSys_Error(\"FS_Rename case not handled\\n\");\n\t}\n\n\tstrlcpy(newfullname, oldfullname, sizeof(newfullname));\n\tstrlcat(oldfullname, oldf, sizeof(oldfullname));\n\tstrlcat(newfullname, newf, sizeof(newfullname));\n\n\treturn rename(oldfullname, newfullname);\n}\n\nint FS_Remove(char *fname, int relativeto)\n{\n\tchar fullname[MAX_OSPATH];\n\n\tswitch (relativeto)\n\t{\n\tcase FS_GAME_OS:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s/%s\", com_homedir, com_gamedirfile, fname);\n\t\telse\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s%s/%s\", com_basedir, com_gamedirfile, fname);\n\t\tbreak;\n\n/*\tcase FS_SKINS:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/qw/skins/%s\", com_homedir, fname);\n\t\telse\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%sqw/skins/%s\", com_basedir, fname);\n\t\tbreak;\n */\n\tcase FS_BASE:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s\", com_homedir, fname);\n\t\telse\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s%s\", com_basedir, fname);\n\t\tbreak;\n\tdefault:\n\t\tSys_Error(\"FS_Rename case not handled\\n\");\n\t\treturn 0;\n\t}\n\n\treturn unlink (fullname);\n}\n\nstatic void FS_CreatePathRelative(const char *pname, int relativeto)\n{\n\tchar fullname[MAX_OSPATH];\n\tswitch (relativeto)\n\t{\n\tcase FS_NONE_OS:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s\", com_homedir, pname);\n\t\telse\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s\", pname);\n\t\tbreak;\n\n\tcase FS_GAME_OS:\n\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s\", com_gamedir, pname);\n\t\tbreak;\n\n\tcase FS_BASE:\n\tcase FS_ANY:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/%s\", com_homedir, pname);\n\t\telse\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s%s\", com_basedir, pname);\n\t\tbreak;\n/*\tcase FS_SKINS:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/qw/skins/%s\", com_homedir, pname);\n\t\telse\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%sqw/skins/%s\", com_basedir, pname);\n\t\tbreak;\n\tcase FS_CONFIGONLY:\n\t\tif (*com_homedir)\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%s/fte/%s\", com_homedir, pname);\n\t\telse\n\t\t\tsnprintf(fullname, sizeof(fullname), \"%sfte/%s\", com_basedir, pname);\n\t\tbreak;*/\n\tdefault:\n\t\tSys_Error(\"FS_CreatePathRelative: Bad relative path (%i)\", relativeto);\n\t\tbreak;\n\t}\n\tFS_CreatePath(fullname);\n}\n\n// Compile this part of code for all wildcard searching \n// for pak files to be opened\n\ntypedef struct {\n\tsearchpathfuncs_t *funcs;\n\tsearchpath_t *parentpath;\n\tchar *parentdesc;\n} wildpaks_t;\n\nstatic int FS_AddWildDataFiles (char *descriptor, int size, void *vparam)\n{\n\twildpaks_t *param = vparam;\n\tvfsfile_t *vfs;\n\tsearchpathfuncs_t *funcs = param->funcs;\n\tsearchpath_t\t*search;\n\tvoid \t\t\t*pak;\n\tchar\t\t\tpakfile[MAX_OSPATH];\n\tflocation_t loc;\n\n\tsnprintf (pakfile, sizeof (pakfile), \"%s%s\", param->parentdesc, descriptor);\n\n\tfor (search = fs_searchpaths; search; search = search->next)\n\t{\n\t\tif (search->funcs != funcs)\n\t\t\tcontinue;\n\t\tif (!strcasecmp((char*)search->handle, pakfile))\t//assumption: first member of structure is a char array\n\t\t\treturn true; //already loaded (base paths?)\n\t}\n\n\tsearch = param->parentpath;\n\n\tif (!search->funcs->FindFile(search->handle, &loc, descriptor, NULL))\n\t\treturn true;\t//not found..\n\tvfs = search->funcs->OpenVFS(search->handle, &loc, \"rb\");\n\tpak = funcs->OpenNew (vfs, pakfile);\n\tif (!pak)\n\t\treturn true;\n\n\tsnprintf (pakfile, sizeof (pakfile), \"%s%s/\", param->parentdesc, descriptor);\n\tFS_AddPathHandle(pakfile, funcs, pak, true, false, FS_LOAD_FILE_ALL);\n\n\treturn true;\n}\n\nstatic void FS_AddDataFiles(char *pathto, searchpath_t *parent, char *extension, searchpathfuncs_t *funcs)\n{\n\tint\t\t\t\ti;\n\tchar\t\t\tpakfile[MAX_OSPATH];\n\twildpaks_t wp;\n\tFILE *pak_lst;\n\n\tfor (i = 0; ; i++)\n\t{\n\t\t// try lower-case first\n\t\tsnprintf (pakfile, sizeof(pakfile), \"pak%i.%s\", i, extension);\n\t\tif (FS_AddPak(pathto, pakfile, parent, funcs)) {\n\t\t\t// originals might be PAK0.PAK, try again\n\t\t\tQ_strupr(pakfile);\n\t\t\tif (FS_AddPak(pathto, pakfile, parent, funcs)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* VFS-FIXME: Sure there is a better way to do this.... */\n\tsnprintf (pakfile, sizeof (pakfile), \"%s/pak.lst\", pathto);\n\tpak_lst = fopen(pakfile, \"r\");\n\tif (!pak_lst) {\n\t\tsnprintf (pakfile, sizeof (pakfile), \"*.%s\", extension);\n\t\twp.funcs = funcs;\n\t\twp.parentdesc = pathto;\n\t\twp.parentpath = parent;\n\t\tparent->funcs->EnumerateFiles(parent->handle, pakfile, FS_AddWildDataFiles, &wp);\n\t} else {\n\t\tfclose(pak_lst);\n\t}\n}\n\nvoid FS_RefreshFSCache_f(void)\n{\n\tfilesystemchanged=true;\n}\n\nvoid FS_FlushFSCache(void)\n{\n\tif (fs_cache.value != 2)\n\t\tfilesystemchanged=true;\n}\n\n\n/*\n================\nFS_AddGameDirectory\n\nSets com_gamedir, adds the directory to the head of the path,\nthen loads and adds pak1.pak pak2.pak ...\n================\n*/\nvoid FS_AddGameDirectory (char *dir, FS_Load_File_Types loadstuff)\n{\n\tsize_t size;\n\tsearchpath_t *search;\n\tchar *p;\n\tchar tmp_path[MAX_OSPATH];\n\n\tif ((p = strrchr(dir, '/')) != NULL)\n\t\tstrlcpy (com_gamedirfile, ++p, sizeof (com_gamedirfile));\n\telse\n\t\tstrlcpy (com_gamedirfile, dir, sizeof (com_gamedirfile));\n\n\tif (com_gamedir != dir) strlcpy (com_gamedir, dir, sizeof (com_gamedir));\n\n\tfor (search = fs_searchpaths; search; search = search->next)\n\t{\n\t\tif (search->funcs != &osfilefuncs)\n\t\t\tcontinue;\n\n\t\tif (!strcasecmp(search->handle, com_gamedir))\n\t\t\treturn; //already loaded (base paths?)\n\t}\n\n\t// add the directory to the search path\n\tsize = strlen (dir) + 1;\n\tp = Q_malloc_named(size, dir);\n\tstrlcpy (p, dir, size);\n\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s/\", dir);\n\tFS_AddPathHandle (tmp_path, &osfilefuncs, p, false, false, loadstuff);\n}\n\n/*\n================\nFS_AddHomeDirectory\n\nAdds the home directory to the head of the path,\nthen loads and adds pak1.pak pak2.pak ...\n================\n*/\nvoid FS_AddHomeDirectory (char *dir, FS_Load_File_Types loadstuff)\n{\n\tsize_t size;\n\tsearchpath_t *search;\n\tchar *p;\n\tchar tmp_path[MAX_OSPATH];\n\n\tfor (search = fs_searchpaths; search; search = search->next)\n\t{\n\t\tif (search->funcs != &osfilefuncs)\n\t\t\tcontinue;\n\n\t\tif (!strcasecmp(search->handle, com_homedir))\n\t\t\treturn; //already loaded (base paths?)\n\t}\n\n\t// add the directory to the search path\n\tsize = strlen (dir) + 1;\n\tp = Q_malloc (size);\n\tstrlcpy (p, dir, size);\n\n\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s/\", dir);\n\tFS_AddPathHandle (tmp_path, &osfilefuncs, p, false, false, loadstuff);\n}\n\n//space-seperate pk3 names followed by space-seperated crcs\n//note that we'll need to reorder and filter out files that don't match the crc.\nvoid FS_ForceToPure(char *str, const char *crcs, int seed)\n{\n\t//pure files are more important than non-pure.\n\n\tsearchpath_t *sp;\n\tsearchpath_t *lastpure = NULL;\n\tint crc;\n\n\tif (!str)\n\t{\t//pure isn't in use.\n\t\tfs_purepaths = NULL;\n\t\tFS_FlushFSHash();\n\t\treturn;\n\t}\n\n\tfor (sp = fs_searchpaths; sp; sp = sp->next)\n\t{\n\t\tif (sp->funcs->GeneratePureCRC)\n\t\t{\n\t\t\tsp->nextpure = (void*)0x1;\n\t\t\tsp->crc_check = sp->funcs->GeneratePureCRC(sp->handle, seed, 0);\n\t\t\tsp->crc_reply = sp->funcs->GeneratePureCRC(sp->handle, seed, 1);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsp->crc_check = 0;\n\t\t\tsp->crc_reply = 0;\n\t\t}\n\t}\n\n\twhile(crcs)\n\t{\n\t\tcrcs = COM_Parse(crcs);\n\t\tcrc = atoi(com_token);\n\n\t\tif (!crc)\n\t\t\tcontinue;\n\n\t\tfor (sp = fs_searchpaths; sp; sp = sp->next)\n\t\t{\n\t\t\tif (sp->nextpure == (void*)0x1)\t//don't add twice.\n\t\t\t\tif (sp->crc_check == crc)\n\t\t\t\t{\n\t\t\t\t\tif (lastpure)\n\t\t\t\t\t\tlastpure->nextpure = sp;\n\t\t\t\t\telse\n\t\t\t\t\t\tfs_purepaths = sp;\n\t\t\t\t\tsp->nextpure = NULL;\n\t\t\t\t\tlastpure = sp;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\t\tif (!sp)\n\t\t\tCom_Printf(\"Pure crc %i wasn't found\\n\", crc);\n\t}\n\n/* don't add any extras.\n\tfor (sp = fs_searchpaths; sp; sp = sp->next)\n\t{\n\t\tif (sp->nextpure == (void*)0x1)\n\t\t{\n\t\t\tif (lastpure)\n\t\t\t\tlastpure->nextpure = sp;\n\t\t\tsp->nextpure = NULL;\n\t\t\tlastpure = sp;\n\t\t}\n\t}\n*/\n\n\tFS_FlushFSHash();\n}\n\nchar *FS_GenerateClientPacksList(char *buffer, int maxlen, int basechecksum)\n{\n\tflocation_t loc;\n\tint numpaks = 0;\n\tsearchpath_t *sp;\n\tchar tmp[64];\n\n\tFS_FLocateFile(\"vm/cgame.qvm\", FSLFRT_LENGTH, &loc);\n\t\n\tsnprintf(&tmp[0], sizeof(tmp), \"%i \", loc.search->crc_reply);\n\tstrlcat(buffer, tmp, maxlen);\n\n\tbasechecksum ^= loc.search->crc_reply;\n\n\tFS_FLocateFile(\"vm/ui.qvm\", FSLFRT_LENGTH, &loc);\n\n\tsnprintf(&tmp[0], sizeof(tmp), \"%i \", loc.search->crc_reply);\n\tstrlcat(buffer, tmp, maxlen);\n\n\tbasechecksum ^= loc.search->crc_reply;\n\n\tstrlcat(buffer, \"@ \", maxlen);\n\n\tfor (sp = fs_purepaths; sp; sp = sp->nextpure) {\n\t\tif (sp->crc_reply) {\n\t\t\tsnprintf(&tmp[0], sizeof(tmp), \"%i \", sp->crc_reply);\n\t\t\tstrlcat(buffer, tmp, maxlen);\n\t\t\tbasechecksum ^= sp->crc_reply;\n\t\t\tnumpaks++;\n\t\t}\n\t}\n\n\tbasechecksum ^= numpaks;\n\tsnprintf(&tmp[0], sizeof(tmp), \"%i \", basechecksum);\n\tstrlcat (buffer, tmp, maxlen);\n\n\treturn buffer;\n}\n\n/*\n================\nFS_ReloadPackFiles\n================\n\nCalled when the client has downloaded a new pak/pk3 file\n*/\nvoid FS_ReloadPackFilesFlags(FS_Load_File_Types reloadflags)\n{\n\tsearchpath_t\t*oldpaths;\n\tsearchpath_t\t*oldbase;\n\tsearchpath_t\t*next;\n\n\t//a lame way to fix pure paks\n\tif (cls.state)\n\t{\n\t\tCL_Disconnect_f();\n\t\tCL_Reconnect_f();\n\t}\n\n\tFS_FlushFSHash();\n\n\toldpaths = fs_searchpaths;\n\tfs_searchpaths = NULL;\n\tfs_purepaths = NULL;\n\toldbase = fs_base_searchpaths;\n\tfs_base_searchpaths = NULL;\n\n\t//invert the order\n\tnext = NULL;\n\twhile(oldpaths)\n\t{\n\t\toldpaths->nextpure = next;\n\t\tnext = oldpaths;\n\t\toldpaths = oldpaths->next;\n\t}\n\toldpaths = next;\n\n\tfs_base_searchpaths = NULL;\n\n\twhile(oldpaths)\n\t{\n\t\tnext = oldpaths->nextpure;\n\n\t\tif (oldbase == oldpaths)\n\t\t\tfs_base_searchpaths = fs_searchpaths;\n\n\t\tif (oldpaths->funcs == &osfilefuncs)\n\t\t\tFS_AddGameDirectory(oldpaths->handle, reloadflags);\n\n\t\toldpaths->funcs->ClosePath(oldpaths->handle);\n\t\tQ_free(oldpaths);\n\t\toldpaths = next;\n\t}\n\n\tif (!fs_base_searchpaths)\n\t\tfs_base_searchpaths = fs_searchpaths;\n}\n\nvoid FS_UnloadPackFiles(void)\n{\n\tFS_ReloadPackFilesFlags(FS_LOAD_NONE);\n}\n\nvoid FS_ReloadPackFiles(void)\n{\n\tFS_ReloadPackFilesFlags(FS_LOAD_FILE_ALL);\n}\n\nvoid FS_ReloadPackFiles_f(void)\n{\n\tif (Cmd_Argc() > 2) {\n\t\tCom_Printf(\"Usage: %s [reload flags]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\tif (atoi(Cmd_Argv(1))) {\n\t\tFS_ReloadPackFilesFlags(atoi(Cmd_Argv(1)));\n\t}\n\telse {\n\t\tFS_ReloadPackFilesFlags(FS_LOAD_FILE_ALL);\n\t}\n\tFS_Path_f();\n}\n\nvoid FS_ListFiles_f(void)\n{\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"Usage: fs_search <extension>\\nLists files in search paths with given extension\\n\");\n\t}\n\telse if (!fs_cache.integer) {\n\t\tCom_Printf(\"Can't search, fs_cache must be turned on\\n\");\n\t}\n\telse {\n\t\tint i;\n\t\tchar *ext = Cmd_Argv(1);\n\t\tsize_t ext_len = strlen(ext);\n\n\t\tfor (i = 0; i < filesystemhash->numbuckets; i++) {\n\t\t\tbucket_t *b = filesystemhash->bucket[i];\n\t\t\twhile (b) {\n\t\t\t\tchar *key = b->keystring;\n\t\t\t\tsize_t len = strlen(key);\n\t\t\t\tif (strcmp(key+len-ext_len, ext) == 0) {\n\t\t\t\t\tCom_Printf(\"%s\\n\", b->keystring);\n\t\t\t\t}\n\t\t\t\tb = b->next;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid FS_EnumerateFiles (char *match, int (*func)(char *, int, void *), void *parm)\n{\n\tsearchpath_t *search;\n\tfor (search = fs_searchpaths; search ; search = search->next)\n\t\tif (!search->funcs->EnumerateFiles (search->handle, match, func, parm)) // is the element a pak file?\n\t\t\tbreak;\n}\n\n// DEBUG FUNCTION\n// ===============\n// FS_DiffFile_f\n// ===============\n// Premitive compare of two files\n// This allows verifing of the VFS\n// just use at a quake conosle fs_diff <file1> <file2>\n// where file1 is a OS based file & file2 is a file\n// placed in a zip, gzip, pak, pk3 etc\nstatic void FS_DiffFile_f(void) \n{\n\tchar buf1[CHUNK], buf2[CHUNK];\n\tchar *filename1, *filename2;\n\tvfsfile_t *file1, *file2;\n\tvfserrno_t err1, err2;\n\tint differences = 0;\n\tint bytes_read1, bytes_read2, bytes_total = 0;\n\n\tif (Cmd_Argc() != 3) {\n\t\tCom_Printf(\"Usage: %s filename1 filename2\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\t\n\tfilename1 = Cmd_Argv(1);\n\tfilename2 = Cmd_Argv(2);\n\n\tfile1 = FS_OpenVFS(filename1, \"rb\", FS_NONE_OS);\n\tif (file1 == NULL) {\n\t\tCom_Printf(\"Unable to open %s\\n\", filename1);\n\t\treturn;\n\t}\n\tfile2 = FS_OpenVFS(filename2, \"rb\", FS_NONE_OS);\n\tif (file2 == NULL) {\n\t\tCom_Printf(\"Unable to open %s\\n\", filename2);\n\t\treturn;\n\t}\n\n\tdo {\n\t\tint i;\n\t\tbytes_read1 = VFS_READ(file1, buf1, sizeof(buf1), &err1);\n\t\tbytes_read2 = VFS_READ(file2, buf2, sizeof(buf2), &err2);\n\n\t\tif (bytes_read1 != bytes_read2) {\n\t\t\tCom_Printf(\"%s: Filesizes seem different, assuming files differ\\n\", Cmd_Argv(0));\n\t\t\tdifferences = 1;\n\t\t\tgoto end;\n\t\t}\n\n\t\t/* Check the bytes read */\n\t\tfor (i = 0; i < bytes_read1; i++, bytes_total++) {\n\t\t\tif (buf1[i] != buf2[i]) {\n\t\t\t\tdifferences = 1;\n\t\t\t\tgoto end;\n\t\t\t}\n\t\t}\n\t} while (err1 != VFSERR_EOF && err2 != VFSERR_EOF);\n\n\tif (err1 != err2) {\n\t\tdifferences = 1;\n\t} \n\nend:\n\tif (!differences) {\n\t\tCom_Printf(\"%s & %s match\\n\", filename1, filename2);\n\t} else {\n\t\tCom_Printf(\"%s & %s differ on byte %d\\n\", filename1, filename2, bytes_total);\n\t}\n\tVFS_CLOSE(file1);\n\tVFS_CLOSE(file2);\n}\n\n#ifdef SERVERONLY\n/*\n================\nFS_GetCleanPath\n\n================\n*/\nstatic const char *FS_GetCleanPath(const char *pattern, char *outbuf, int outlen)\n{\n\tchar *s;\n\n\tif (strchr(pattern, '\\\\'))\n\t{\n\t\tstrlcpy(outbuf, pattern, outlen);\n\t\tpattern = outbuf;\n\n\t\tCon_Printf(\"Warning: \\\\ characters in filename %s\\n\", pattern);\n\n\t\tfor (s = (char*)pattern; (s = strchr(s, '\\\\')); s++)\n\t\t\t*s = '/';\n\t}\n\n\tif (*pattern == '/' || strstr(pattern, \"..\") || strstr(pattern, \":\"))\n\t\tCon_Printf(\"Error: absolute path in filename %s\\n\", pattern);\n\telse\n\t\treturn pattern;\n\n\treturn NULL;\n}\n#endif\n\n/*\n===========\nFS_UnsafeFilename\n\nReturns true if user-specified path is unsafe\n===========\n*/\nqbool FS_UnsafeFilename(const char* fileName)\n{\n\treturn !fileName ||\n\t\t!*fileName || // invalid name.\n\t\tfileName[1] == ':' ||\t// dos filename absolute path specified - reject.\n\t\t*fileName == '\\\\' ||\n\t\t*fileName == '/' ||\t// absolute path was given - reject.\n\t\tstrstr(fileName, \"..\");\n}\n\nvoid FS_Shutdown(void)\n{\n\tsearchpath_t* path;\n\tsearchpath_t* next;\n\n\tHash_ShutdownTable(filesystemhash);\n\tfilesystemhash = NULL;\n\n\tfor (path = fs_searchpaths; path; path = next) {\n\t\tpath->funcs->ClosePath(path->handle);\n\t\tnext = path->next;\n\t\tQ_free(path);\n\t}\n}\n\nvoid FS_SaveGameDirectory(char* buffer, int buffer_size)\n{\n\textern cvar_t fs_savegame_home;\n\n\tif (fs_savegame_home.integer) {\n\t\tsnprintf(buffer, buffer_size, \"%s/%s/save/\", com_homedir, com_gamedirfile);\n\t}\n\telse {\n\t\tsnprintf(buffer, buffer_size, \"%s/save/\", com_gamedir);\n\t}\n}\n"
  },
  {
    "path": "src/fs.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef __FS_H__\n#define __FS_H__\n\n#ifdef WITH_ZLIB\n#include \"zlib.h\"\n#endif\n#ifdef WITH_ZIP\n#include \"unzip.h\"\n#endif\n\nvoid FS_InitModuleFS (void);\n\n// ====================================================================\n// Quake File System addons\n// original common FS functions are declared in common.h\n\n// QW262 -->\n#define UserdirSet (userdirfile[0] != '\\0')\nextern\tchar userdirfile[MAX_OSPATH];\nextern\tchar com_userdir[MAX_OSPATH];\nvoid FS_SetUserDirectory (char *dir, char *type);\n// <-- QW262\n\n// ====================================================================\n// Virtual File System\n\ntypedef enum {\n\tVFSERR_NONE,\n\tVFSERR_EOF\n} vfserrno_t;\n\ntypedef struct vfsfile_s {\n\tint (*ReadBytes) (struct vfsfile_s *file, void *buffer, int bytestoread, vfserrno_t *err);\n\tint (*WriteBytes) (struct vfsfile_s *file, const void *buffer, int bytestowrite);\n\tint (*Seek) (struct vfsfile_s *file, unsigned long pos, int whence);\t// Returns 0 on sucess, -1 otherwise\n\tunsigned long (*Tell) (struct vfsfile_s *file);\n\tunsigned long (*GetLen) (struct vfsfile_s *file);\t// Could give some lag\n\tvoid (*Close) (struct vfsfile_s *file);\n\tvoid (*Flush) (struct vfsfile_s *file);\n\tqbool seekingisabadplan;\n\tqbool copyprotected;\t\t\t\t\t\t\t// File found was in a pak\n} vfsfile_t;\n\n// VFS-FIXME: D-Kure Clean up this structure\ntypedef enum {\n\tFS_NONE_OS, // file name used as is, opened with OS functions (no paks)\n\tFS_GAME_OS, // file used as com_basedir/filename, opened with OS functions \n\t\t\t\t// (no paks)\n\tFS_BASE,\t// file is relative to the com_basedir/com_homedir\n\tFS_HOME,\n\tFS_PAK,\n\tFS_ANY      // file searched on quake file system even in paks, u may use\n\t\t\t\t// only \"rb\" mode for file since u can't write to pak\n} relativeto_t;\n\n// mostly analogs for stdio functions\nvoid \t\t\tVFS_CLOSE  (struct vfsfile_s *vf);\nunsigned long\tVFS_TELL   (struct vfsfile_s *vf);\nunsigned long\tVFS_GETLEN (struct vfsfile_s *vf);\nint\t\t\t\tVFS_SEEK   (struct vfsfile_s *vf, unsigned long pos, int whence);\nint\t\t\t\tVFS_READ   (struct vfsfile_s *vf, void *buffer, int bytestoread, vfserrno_t *err);\nint\t\t\t\tVFS_WRITE  (struct vfsfile_s *vf, const void *buffer, int bytestowrite);\nvoid\t\t\tVFS_FLUSH  (struct vfsfile_s *vf);\nchar\t\t   *VFS_GETS   (struct vfsfile_s *vf, char *buffer, int buflen); \n\t\t\t\t// return null terminated string\n\nvoid\t\t\tVFS_TICK   (void);  // fill in/out our internall buffers \n\t\t\t\t\t\t\t\t\t// (do read/write on socket)\nvfsfile_t      *VFS_Filter(const char *filename, vfsfile_t *handle);\nqbool\t\t\tVFS_COPYPROTECTED(struct vfsfile_s *vf);\n\n// some general function to open VFS file, except TCP\nvfsfile_t *FS_OpenVFS(const char *filename, char *mode,relativeto_t relativeto);\n\n// TCP VFS file\nvfsfile_t *FS_OpenTCP(char *name);\n\ntypedef enum {\n\tFS_LOAD_NONE     = 1,\n\tFS_LOAD_FILE_PAK = 2,\n\tFS_LOAD_FILE_PK3 = 4,\n\tFS_LOAD_FILE_PK4 = 8,\n\tFS_LOAD_FILE_DOOMWAD = 16,\n\tFS_LOAD_FROM_PAKLST = 32,\n\tFS_LOAD_FILE_ALL = FS_LOAD_FILE_PAK | FS_LOAD_FILE_PK3 \n\t\t| FS_LOAD_FILE_PK4 | FS_LOAD_FILE_DOOMWAD | FS_LOAD_FROM_PAKLST,\n} FS_Load_File_Types;\n\nvoid FS_AddGameDirectory (char *dir, FS_Load_File_Types loadstuff);\n\nchar *FS_NextPath (char *prevpath);\n\nextern cvar_t fs_cache;\nextern qbool filesystemchanged;\n\n// ====================================================================\n// GZIP & ZIP De/compression\n\n#ifdef WITH_ZLIB\nint FS_GZipPack (char *source_path,\n\t\t\t\t  char *destination_path,\n\t\t\t\t  qbool overwrite);\n\nint FS_GZipUnpack (char *source_path,\t\t// The path to the compressed source file.\n\t\t\t\t\tchar *destination_path, // The destination file path.\n\t\t\t\t\tqbool overwrite);\t\t// Overwrite the destination file if it exists?\n\nvoid* FS_GZipUnpackToMemory(char* source_path, size_t* unpacked_length);\n\nint FS_ZlibInflate(FILE *source, FILE *dest);\n\nint FS_ZlibUnpack (char *source_path,\t\t// The path to the compressed source file.\n\t\t\t\t\tchar *destination_path, // The destination file path.\n\t\t\t\t\tqbool overwrite);\t\t// Overwrite the destination file if it exists?\n\nint FS_ZlibUnpackToTemp (char *source_path,\t\t// The compressed source file.\n\t\t\t\t\t\t  char *unpack_path,\t\t// A buffer that will contain the path to the unpacked file.\n\t\t\t\t\t\t  int unpack_path_size,\t\t// The size of the buffer.\t\n\t\t\t\t\t\t  char *append_extension);\t// The extension if any that should be appended to the filename.\n#endif // WITH_ZLIB\n\n#ifdef WITH_ZIP\nqbool FS_IsArchive (char *path);\n\nint FS_ZipBreakupArchivePath (char *archive_extension,\t\t\t// The extension of the archive type we're looking fore \"zip\" for example.\n\t\t\t\t\t\t\t   char *path,\t\t\t\t\t\t// The path that should be broken up into parts.\n\t\t\t\t\t\t\t   char *archive_path,\t\t\t\t// The buffer that should contain the archive path after the breakup.\n\t\t\t\t\t\t\t   int archive_path_size,\t\t\t// The size of the archive path buffer.\n\t\t\t\t\t\t\t   char *inzip_path,\t\t\t\t// The buffer that should contain the inzip path after the breakup.\n\t\t\t\t\t\t\t   int inzip_path_size);\t\t\t// The size of the inzip path buffer.\n\nunzFile FS_ZipUnpackOpenFile (const char *zip_path);\n\nint FS_ZipUnpackCloseFile (unzFile zip_file);\n\nint FS_ZipUnpack (unzFile zip_file, \n\t\t\t\t   char *destination_path, \n\t\t\t\t   qbool case_sensitive, \n\t\t\t\t   qbool keep_path, \n\t\t\t\t   qbool overwrite, \n\t\t\t\t   const char *password);\n\nint FS_ZipUnpackToTemp (unzFile zip_file, \n\t\t\t\t   qbool case_sensitive, \n\t\t\t\t   qbool keep_path, \n\t\t\t\t   const char *password,\n\t\t\t\t   char *unpack_path,\t\t\t\t\t// The path where the file was unpacked.\n\t\t\t\t   int unpack_path_size);\t\t\t\t// The size of the buffer for \"unpack_path\", MAX_PATH is a goode idea.)\n\nint FS_ZipUnpackOneFile (unzFile zip_file,\t\t\t\t// The zip file opened with FS_ZipUnpackOpenFile(..)\n\t\t\t\t\t\t  const char *filename_inzip,\t// The name of the file to unpack inside the zip.\n\t\t\t\t\t\t  const char *destination_path, // The destination path where to extract the file to.\n\t\t\t\t\t\t  qbool case_sensitive,\t\t\t// Should we look for the filename case sensitivly?\n\t\t\t\t\t\t  qbool keep_path,\t\t\t\t// Should the path inside the zip be preserved when unpacking?\n\t\t\t\t\t\t  qbool overwrite,\t\t\t\t// Overwrite any existing file with the same name when unpacking?\n\t\t\t\t\t\t  const char *password);\t\t// The password to use when extracting the file.\n\nvoid* FS_ZipUnpackOneFileToMemory(\n\tunzFile zip_file,\n\tconst char* filename_inzip,\n\tqbool case_sensitive,\n\tqbool keep_path,\n\tconst char* password,\n\tsize_t* unpacked_length\n);\n\nint FS_ZipUnpackCurrentFile (unzFile zip_file, \n\t\t\t\t\t\t\t  const char *destination_path, \n\t\t\t\t\t\t\t  qbool case_sensitive, \n\t\t\t\t\t\t\t  qbool keep_path, \n\t\t\t\t\t\t\t  qbool overwrite, \n\t\t\t\t\t\t\t  const char *password);\n\nint FS_ZipGetFirst (unzFile zip_file, sys_dirent *ent);\n\nint FS_ZipGetNextFile (unzFile zip_file, sys_dirent *ent);\n\n#endif // WITH_ZIP\n\nqbool FS_UnsafeFilename(const char* name);\nvoid FS_SaveGameDirectory(char* buffer, int buffer_size);\n\n#endif // __FS_H__\n"
  },
  {
    "path": "src/g_public.h",
    "content": "/*\n *  QW262\n *  Copyright (C) 2004  [sd] angel\n *\n *  This code is based on Q3 VM code by Id Software, Inc.\n *\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by\n *  the Free Software Foundation; either version 2 of the License, or\n *  (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n *\n *\n */\n\n#ifndef __G_PUBLIC_H__\n#define __G_PUBLIC_H__\n\n\n// Copyright (C) 1999-2000 Id Software, Inc.\n//\n// g_public.h -- game module information visible to server\n\n#define\tGAME_API_VERSION\t16\n\n/*\n * Changes in GAME_API_VERSION 16:\n * - server edict data removed from game edict: typedef struct shared_edict_s { entvars_t v;} edict_t;\n * - SetSting works for PR1 only\n * - ported VM from Q3\n * - QVM mods should get mapname and client netnames with infokey trap\n *\n * mod should get client netname in GAME_CLIENT_CONNECT call:\n *  self = PROG_TO_EDICT(g_globalvars.self);\n *  self->s.v.netname = netnames[NUM_FOR_EDICT(self)-1];\n *  infokey( self, \"netname\", self->s.v.netname,  32);\n *\n * mod should get mapname in GAME_START_FRAME call:\n *  if (framecount == 0)\n *  {\n *     infokey(world, \"mapname\", mapname, sizeof(mapname));\n *     infokey(world, \"modelname\", worldmodel, sizeof(worldmodel));\n *     world->model = worldmodel;\n *  }\n *\n *  infokey( world, \"mapname\", mapname, sizeof(mapname) );\n *\n * - QVM GAME_CLIENT_USERINFO_CHANGED call now have integer paramater\n *\n * called with 0 before changing, with 1 after changing\n * mod must update client netname in call with param 1 and key = \"name\"\n */\n\n//===============================================================\n\n// !!! new traps comes to end of list !!!\n\n//\n// system traps provided by the main engine\n//\ntypedef enum\n{\n\t//============== general Quake services ==================\n\n\tG_GETAPIVERSION,\t\t// ( void);\n\n\tG_DPRINT,\t\t// ( const char *string );\n\t// print message on the local console\n\n\tG_ERROR,\t\t// ( const char *string );\n\t// abort the game\n\tG_GetEntityToken,\n\n\tG_SPAWN_ENT,\n\tG_REMOVE_ENT,\n\tG_PRECACHE_SOUND,\n\tG_PRECACHE_MODEL,\n\tG_LIGHTSTYLE,\n\tG_SETORIGIN,\n\tG_SETSIZE,\n\tG_SETMODEL,\n\tG_BPRINT,\n\tG_SPRINT,\n\tG_CENTERPRINT,\n\tG_AMBIENTSOUND,\n\tG_SOUND,\n\tG_TRACELINE,\n\tG_CHECKCLIENT,\n\tG_STUFFCMD,\n\tG_LOCALCMD,\n\tG_CVAR,\n\tG_CVAR_SET,\n\tG_FINDRADIUS,\n\tG_WALKMOVE,\n\tG_DROPTOFLOOR,\n\tG_CHECKBOTTOM,\n\tG_POINTCONTENTS,\n\tG_NEXTENT,\n\tG_AIM,\n\tG_MAKESTATIC,\n\tG_SETSPAWNPARAMS,\n\tG_CHANGELEVEL,\n\tG_LOGFRAG,\n\tG_GETINFOKEY,\n\tG_MULTICAST,\n\tG_DISABLEUPDATES,\n\tG_WRITEBYTE,     \n\tG_WRITECHAR,     \n\tG_WRITESHORT,    \n\tG_WRITELONG,     \n\tG_WRITEANGLE,    \n\tG_WRITECOORD,    \n\tG_WRITESTRING,   \n\tG_WRITEENTITY,\n\tG_FLUSHSIGNON,\n\tg_memset,\n\tg_memcpy,\t\t\n\tg_strncpy,\t\t\n\tg_sin,\t\n\tg_cos,\t\n\tg_atan2,\t\n\tg_sqrt,\t\n\tg_floor,\t\n\tg_ceil,\t\n\tg_acos,\n\tG_CMD_ARGC,\n\tG_CMD_ARGV,\n\tG_TraceCapsule,\n\tG_FSOpenFile,\n\tG_FSCloseFile,\n\tG_FSReadFile,\n\tG_FSWriteFile,\n\tG_FSSeekFile,\n\tG_FSTellFile,\n\tG_FSGetFileList,\n\tG_CVAR_SET_FLOAT,\n\tG_CVAR_STRING,\n\tG_Map_Extension,\n\tG_strcmp,\n\tG_strncmp,\n\tG_stricmp,\n\tG_strnicmp,\n\tG_Find,\n\tG_executecmd,\n\tG_conprint,\n\tG_readcmd,\n\tG_redirectcmd,\n\tG_Add_Bot,\n\tG_Remove_Bot,\n\tG_SetBotUserInfo,\n\tG_SetBotCMD,\n\tG_QVMstrftime,\n\tG_CMD_ARGS,\n\tG_CMD_TOKENIZE,\n\tg_strlcpy,\n\tg_strlcat,\n\tG_MAKEVECTORS,\n\tG_NEXTCLIENT,\n\tG_PRECACHE_VWEP_MODEL,\n\tG_SETPAUSE,\n\tG_SETUSERINFO,\n\tG_MOVETOGOAL,\n\tG_VISIBLETO,\n\t_G__LASTAPI\n} gameImport_t;\n\n// !!! new things comes to end of list !!!\n\n// G_Map_Extension syscalls\n#define G_EXTENSIONS_FIRST 256\n\n//\n// functions exported by the game subsystem\n//\ntypedef enum\n{\n\tGAME_INIT,\t// ( int levelTime, int randomSeed, int restart );\n\t// init and shutdown will be called every single level\n\t// The game should call G_GET_ENTITY_TOKEN to parse through all the\n\t// entity configuration text and spawn gentities.\n\tGAME_LOADENTS,\n\tGAME_SHUTDOWN,\t// (void);\n\n\tGAME_CLIENT_CONNECT,\t \t// ( int clientNum ,int isSpectator);\n\t// ( int clientNum, qbool firstTime, qbool isBot );\n\t// return NULL if the client is allowed to connect, otherwise return\n\t// a text string with the reason for denial\n\tGAME_PUT_CLIENT_IN_SERVER,\n\t//GAME_CLIENT_BEGIN,\t\t\t\t// ( int clientNum ,int isSpectator);\n\n\tGAME_CLIENT_USERINFO_CHANGED,\t// ( int clientNum,int isSpectator );\n\n\tGAME_CLIENT_DISCONNECT,\t\t\t// ( int clientNum,int isSpectator );\n\n\tGAME_CLIENT_COMMAND,\t\t\t// ( int clientNum,int isSpectator );\n\n\tGAME_CLIENT_PRETHINK,\n\tGAME_CLIENT_THINK,\t\t\t\t// ( int clientNum,int isSpectator );\n\tGAME_CLIENT_POSTTHINK,\n\n\tGAME_START_FRAME,\t\t\t\t\t// ( int levelTime );\n\tGAME_SETCHANGEPARMS, //self\n\tGAME_SETNEWPARMS,\n\tGAME_CONSOLE_COMMAND,\t\t\t// ( void );\n\tGAME_EDICT_TOUCH,                      //(self,other)\n\tGAME_EDICT_THINK,                      //(self,other=world,time)\n\tGAME_EDICT_BLOCKED,                     //(self,other)\n\t// ConsoleCommand will be called when a command has been issued\n\t// that is not recognized as a builtin function.\n\t// The game can issue trap_argc() / trap_argv() commands to get the command\n\t// and parameters.  Return qfalse if the game doesn't recognize it as a command.\n\tGAME_CLIENT_SAY,\t\t\t// ( int isTeamSay );\n\tGAME_PAUSED_TIC,\t\t\t// ( int duration_msec );\t// duration is in msecs\n\tGAME_CLEAR_EDICT,           // (self)\n} gameExport_t;\n\n\ntypedef enum\n{\n\tF_INT, \n\tF_FLOAT,\n\tF_LSTRING,\t\t\t// string on disk, pointer in memory, TAG_LEVEL\n//\tF_GSTRING,\t\t\t// string on disk, pointer in memory, TAG_GAME\n\tF_VECTOR,\n\tF_ANGLEHACK,\n//\tF_ENTITY,\t\t\t// index on disk, pointer in memory\n//\tF_ITEM,\t\t\t\t// index on disk, pointer in memory\n//\tF_CLIENT,\t\t\t// index on disk, pointer in memory\n\tF_IGNORE\n} fieldtype_t;\n\ntypedef struct\n{\n\tstringptr_t   name;\n\tint           ofs;\n\tfieldtype_t   type;\n} field_t;\n\ntypedef struct\n{\n\tint   name;\n\tint   ofs;\n\tint   type;\n} field_vm_t;\n\ntypedef struct\n{\n\tintptr_t        ents;\n\tint             sizeofent;\n\tintptr_t        global;\n\tintptr_t        fields;\n\tint             APIversion;\n\tint             maxentities;\n} gameData_t;\n\ntypedef struct\n{\n\tint         ents_p;\n\tint         sizeofent;\n\tint         global_p;\n\tint         fields_p;\n\tint         APIversion;\n\tint         maxentities;\n} gameData_vm_t;\ntypedef int\t\tfileHandle_t;\n\ntypedef enum {\n\tFS_READ_BIN,\n\tFS_READ_TXT,\n\tFS_WRITE_BIN,\n\tFS_WRITE_TXT,\n\tFS_APPEND_BIN,\n\tFS_APPEND_TXT\n} fsMode_t;\n\ntypedef enum {\n\tFS_SEEK_CUR,\n\tFS_SEEK_END,\n\tFS_SEEK_SET\n} fsOrigin_t;\n\n#endif /* !__G_PUBLIC_H__ */\n"
  },
  {
    "path": "src/gl_aliasmodel.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// Alias model rendering (common to OpenGL)\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_local.h\"\n#include \"r_aliasmodel.h\"\n\n"
  },
  {
    "path": "src/gl_aliasmodel_md3.c",
    "content": "/*\nCopyright (C) 2011 fuh and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Code to display .md3 files\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"r_aliasmodel_md3.h\"\n#include \"r_aliasmodel.h\"\n#include \"vx_vertexlights.h\" \n#include \"r_matrix.h\"\n#include \"glsl/constants.glsl\"\n\nvoid GL_AliasModelSetVertexDirection(int num_triangles, vbo_model_vert_t* vbo_buffer, int v1, int v2, qbool limit_lerp, int key_pose);\nvoid GL_AliasModelFixNormals(vbo_model_vert_t* vbo_buffer, int v, int vertsPerPose);\n\nvoid GLM_MakeAlias3DisplayLists(model_t* model)\n{\n\tmd3Surface_t* surf;\n\tmd3St_t* texCoords;\n\tezMd3XyzNormal_t* vertices;\n\tint surfnum;\n\tint framenum;\n\tmd3Triangle_t* triangles;\n\tint v;\n\tmd3model_t* md3Model = (md3model_t *)Mod_Extradata(model);\n\tmd3Header_t* pheader = MD3_HeaderForModel(md3Model);\n\tvbo_model_vert_t* vbo;\n\n\t// Work out how many verts we are going to need to store in VBO\n\tmodel->vertsInVBO = 0;\n\tMD3_ForEachSurface(pheader, surf, surfnum) {\n\t\tmodel->vertsInVBO += 3 * surf->numTriangles;\n\t}\n\tmodel->vertsInVBO *= pheader->numFrames;\n\tmodel->temp_vbo_buffer = vbo = Q_malloc(sizeof(vbo_model_vert_t) * model->vertsInVBO);\n\n\t// foreach frame\n\tfor (framenum = 0, v = 0; framenum < pheader->numFrames; ++framenum) {\n\t\tint initial_v = v;\n\n\t\t// loop through the surfaces.\n\t\tMD3_ForEachSurface(pheader, surf, surfnum) {\n\t\t\tint i, triangle;\n\n\t\t\ttexCoords = MD3_SurfaceTextureCoords(surf);\n\t\t\tvertices = MD3_SurfaceVertices(surf);\n\t\t\ttriangles = MD3_SurfaceTriangles(surf);\n\n\t\t\tfor (triangle = 0; triangle < surf->numTriangles; ++triangle) {\n\t\t\t\tfor (i = 0; i < 3; ++i, ++v) {\n\t\t\t\t\tint vertexNumber = framenum * surf->numVerts + triangles[triangle].indexes[i];\n\t\t\t\t\tint nextFrame = Mod_ExpectedNextFrame(model, framenum, pheader->numFrames);\n\t\t\t\t\tint nextVertexNumber = nextFrame * surf->numVerts + triangles[triangle].indexes[i];\n\t\t\t\t\tezMd3XyzNormal_t* vert = &vertices[vertexNumber];\n\t\t\t\t\tezMd3XyzNormal_t* nextVert = &vertices[nextVertexNumber];\n\t\t\t\t\tfloat s, t;\n\n\t\t\t\t\ts = texCoords[triangles[triangle].indexes[i]].s;\n\t\t\t\t\tt = texCoords[triangles[triangle].indexes[i]].t;\n\n\t\t\t\t\tVectorCopy(vert->xyz, vbo[v].position);\n\t\t\t\t\tVectorCopy(vert->normal, vbo[v].normal);\n\t\t\t\t\tvbo[v].texture_coords[0] = s;\n\t\t\t\t\tvbo[v].texture_coords[1] = t;\n\t\t\t\t\tvbo[v].flags = 0;\n\n\t\t\t\t\t// Set direction\n\t\t\t\t\tVectorSubtract(nextVert->xyz, vert->xyz, vbo[v].direction);\n\t\t\t\t\tif (model->renderfx & RF_LIMITLERP) {\n\t\t\t\t\t\tif ((vert->xyz[0] > 0) != (nextVert->xyz[0] > 0)) {\n\t\t\t\t\t\t\tvbo[v].flags |= AM_VERTEX_NOLERP;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tGL_AliasModelFixNormals(vbo, initial_v, v - initial_v);\n\t}\n}\n"
  },
  {
    "path": "src/gl_buffers.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n// OpenGL buffer handling\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"tr_types.h\"\n#include \"r_local.h\"\n#include \"glm_vao.h\"\n#include \"r_trace.h\"\n#include \"r_buffers.h\"\n#include \"r_renderer.h\"\n\nstatic void GL_BufferStartFrame(void);\nstatic void GL_BufferEndFrame(void);\nstatic qbool GL_BuffersReady(void);\n\nstatic void GL_InitialiseBufferState(void);\n\nstatic qbool GL_CreateFixedBuffer(r_buffer_id id, buffertype_t type, const char* name, int size, void* data, bufferusage_t usage);\nstatic void GL_GenFixedBuffer(r_buffer_id id, buffertype_t type, const char* name, int size, void* data, bufferusage_t usage);\nstatic uintptr_t GL_BufferOffset(r_buffer_id ref);\n\nstatic size_t GL_BufferSize(r_buffer_id id);\n\nstatic void GL_BindBuffer(r_buffer_id id);\nstatic void GL_BindBufferBase(r_buffer_id id, unsigned int index);\nstatic void GL_BindBufferRange(r_buffer_id id, unsigned int index, ptrdiff_t offset, int size);\nstatic void GL_UnBindBuffer(buffertype_t type);\nstatic void GL_UpdateBuffer(r_buffer_id id, int size, void* data);\nstatic void GL_UpdateBufferSection(r_buffer_id id, ptrdiff_t offset, int size, const void* data);\nstatic void GL_ResizeBuffer(r_buffer_id id, int size, void* data);\nstatic void GL_EnsureBufferSize(r_buffer_id id, int size);\n\nstatic qbool GL_BindBufferImpl(GLenum target, GLuint buffer);\n\nstatic qbool R_Stub_BufferNotValid(r_buffer_id id)\n{\n\treturn false;\n}\n\ntypedef struct buffer_data_s {\n\tGLuint glref;\n\tchar name[64];\n\n\t// These set at creation\n\tbuffertype_t type;\n\tbufferusage_t usage;\n\tGLsizei size;\n\tGLuint storageFlags;         // flags for BufferStorage()\n\tqbool using_storage;\n\n\tvoid* persistent_mapped_ptr;\n} buffer_data_t;\n\nstatic GLenum GL_BufferTypeToTarget(buffertype_t type)\n{\n\tswitch (type) {\n\t\tcase buffertype_index:\n\t\t\treturn GL_ELEMENT_ARRAY_BUFFER;\n\t\tcase buffertype_indirect:\n\t\t\treturn GL_DRAW_INDIRECT_BUFFER;\n\t\tcase buffertype_storage:\n\t\t\treturn GL_VersionAtLeast(4, 3) ? GL_SHADER_STORAGE_BUFFER : GL_UNIFORM_BUFFER;\n\t\tcase buffertype_vertex:\n\t\t\treturn GL_ARRAY_BUFFER;\n\t\tcase buffertype_uniform:\n\t\t\treturn GL_UNIFORM_BUFFER;\n\t\tdefault:\n\t\t\tassert(false);\n\t\t\treturn 0;\n\t}\n}\n\nstatic GLenum GL_BufferUsageToGLUsage(bufferusage_t usage)\n{\n\tswitch (usage) {\n\t\tcase bufferusage_once_per_frame:\n\t\t\treturn GL_STREAM_DRAW;\n\t\tcase bufferusage_reuse_per_frame:\n\t\t\treturn GL_STREAM_DRAW;\n\t\tcase bufferusage_reuse_many_frames:\n\t\t\treturn GL_STATIC_DRAW;\n\t\tcase bufferusage_constant_data:\n\t\t\treturn GL_STATIC_COPY;\n\t\tdefault:\n\t\t\tassert(false);\n\t\t\treturn 0;\n\t}\n}\n\n// Buffer functions\nGL_StaticProcedureDeclaration(glBindBuffer, \"target=%u, buffer=%u\", GLenum target, GLuint buffer)\nGL_StaticProcedureDeclaration(glBufferData, \"target=%u, size=%u, data=%p, usage=%u\", GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage)\nGL_StaticProcedureDeclaration(glBufferSubData, \"target=%u, offset=%p, size=%p, data=%p\", GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data)\nGL_StaticProcedureDeclaration(glGenBuffers, \"n=%d, buffers=%p\", GLsizei n, GLuint* buffers)\nGL_StaticProcedureDeclaration(glDeleteBuffers, \"n=%d, buffers=%p\", GLsizei n, const GLuint* buffers)\nGL_StaticProcedureDeclaration(glBindBufferBase, \"target=%u, index=%u, buffer=%u\", GLenum target, GLuint index, GLuint buffer)\nGL_StaticProcedureDeclaration(glBindBufferRange, \"target=%u, index=%u, buffer=%u, offset=%p, size=%p\", GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size)\nGL_StaticProcedureDeclaration(glUnmapBuffer, \"target=%u\", GLenum target)\n\n// DSA\nGL_StaticProcedureDeclaration(glNamedBufferSubData, \"buffer=%u, offset=%u, size=%d, data=%p\", GLuint buffer, GLintptr offset, GLsizei size, const void* data)\nGL_StaticProcedureDeclaration(glNamedBufferData, \"buffer=%u, size=%d, data=%p, usage=%u\", GLuint buffer, GLsizei size, const void* data, GLenum usage)\nGL_StaticProcedureDeclaration(glUnmapNamedBuffer, \"buffer=%u\", GLuint buffer);\n\n// AZDO-buffer-streaming (4.4)\n// Persistent mapped buffers\nGL_StaticFunctionDeclaration(glMapBufferRange, \"mtarget=%u, offset=%u, length=%p, access=%u\", \"returns %p\", void*, GLenum mtarget, GLintptr offset, GLsizeiptr length, GLbitfield access)\nGL_StaticFunctionWrapperBody(glMapBufferRange, void*, mtarget, offset, length, access)\nGL_StaticProcedureDeclaration(glBufferStorage, \"target=%u, size=%p, data=%p, flags=%u\", GLenum target, GLsizeiptr size, const GLvoid* data, GLbitfield flags)\nGL_StaticFunctionDeclaration(glFenceSync, \"condition=%u, flags=%u\", \"returns %p\", GLsync, GLenum condition, GLbitfield flags)\nGL_StaticFunctionWrapperBody(glFenceSync, GLsync, condition, flags)\nGL_StaticFunctionDeclaration(glClientWaitSync, \"sync=%p, flags=%u, timeout=%UI64\", \"returns %u\", GLenum, GLsync sync, GLbitfield flags, GLuint64 timeout)\nGL_StaticFunctionWrapperBody(glClientWaitSync, GLenum, sync, flags, timeout)\nGL_StaticProcedureDeclaration(glDeleteSync, \"sync=%p\", GLsync sync)\n\n// Cache OpenGL state\nstatic struct {\n\tbuffer_data_t buffers[r_buffer_count];\n\tqbool tripleBuffer_supported;\n\tGLsync tripleBufferSyncObjects[3];\n\n\tGLuint currentArrayBuffer;\n\tGLuint currentUniformBuffer;\n\tGLuint currentDrawIndirectBuffer;\n\tGLuint currentElementArrayBuffer;\n} glBufferState;\n\n#define GL_BufferInvalidateReference(name, glref) { name = ((name) == (glref)) ? 0 : (name); }\n\nstatic buffer_data_t* GL_BufferAllocateSlot(r_buffer_id id, buffertype_t type, const char* name, GLsizei size, bufferusage_t usage)\n{\n\tbuffer_data_t* buffer = &glBufferState.buffers[id];\n\n\tassert(usage < bufferusage_count);\n\n\tif (buffer->glref) {\n\t\tGL_BufferInvalidateReference(glBufferState.currentArrayBuffer, buffer->glref);\n\t\tGL_BufferInvalidateReference(glBufferState.currentUniformBuffer, buffer->glref);\n\t\tGL_BufferInvalidateReference(glBufferState.currentDrawIndirectBuffer, buffer->glref);\n\t\tGL_BufferInvalidateReference(glBufferState.currentElementArrayBuffer, buffer->glref);\n\t\tif (buffer->type == buffertype_index) {\n\t\t\tR_BindVertexArrayElementBuffer(r_buffer_none);\n\t\t}\n\t\tR_BufferInvalidateBoundState(id);\n\t\tGL_Procedure(glDeleteBuffers, 1, &buffer->glref);\n\t\tbuffer->glref = 0;\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\t\tSys_Printf(\"\\nbuffer,free,%d,%s,%u\\n\", id, name, buffer->size * (buffer->persistent_mapped_ptr ? 3 : 1));\n#endif\n\t}\n\n\tmemset(buffer, 0, sizeof(*buffer));\n\tstrlcpy(buffer->name, name && name[0] ? name : \"?\", sizeof(buffer->name));\n\tbuffer->size = size;\n\tbuffer->usage = usage;\n\tbuffer->type = type;\n\tGL_Procedure(glGenBuffers, 1, &buffer->glref);\n\treturn buffer;\n}\n\nstatic void GL_GenFixedBuffer(r_buffer_id id, buffertype_t type, const char* name, int size, void* data, bufferusage_t usage)\n{\n\tGLenum target = GL_BufferTypeToTarget(type);\n\tGLenum glUsage = GL_BufferUsageToGLUsage(usage);\n\tbuffer_data_t* buffer = GL_BufferAllocateSlot(id, type, name, size, usage);\n\n\tbuffer->persistent_mapped_ptr = NULL;\n\tbuffer->using_storage = false;\n\tGL_BindBufferImpl(target, buffer->glref);\n\tGL_TraceObjectLabelSet(GL_BUFFER, buffer->glref, -1, name);\n\tGL_Procedure(glBufferData, target, size, data, glUsage);\n\tif (target == GL_ELEMENT_ARRAY_BUFFER) {\n\t\tR_BindVertexArrayElementBuffer(id);\n\t}\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tSys_Printf(\"\\nbuffer,alloc,%d,%s,%u\\n\", id, name, size);\n#endif\n}\n\nstatic qbool GL_CreateFixedBuffer(r_buffer_id id, buffertype_t type, const char* name, int size, void* data, bufferusage_t usage)\n{\n\tGLenum target = GL_BufferTypeToTarget(type);\n\tbuffer_data_t* buffer;\n\n\tqbool tripleBuffer = false;\n\tqbool useStorage = false;\n\tGLbitfield storageFlags = 0;\n\tGLsizei alignment = 1;\n\n\tif (!size) {\n\t\treturn false;\n\t}\n\n\tDebugOutput(va(\"Generating buffer %s\\n\", name));\n\tbuffer = GL_BufferAllocateSlot(id, type, name, size, usage);\n\n\tif (usage == bufferusage_once_per_frame) {\n\t\tuseStorage = tripleBuffer = glBufferState.tripleBuffer_supported;\n\t\tstorageFlags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;\n\t}\n\telse if (usage == bufferusage_reuse_per_frame) {\n\t\tuseStorage = GL_Available(glBufferStorage);\n\t\tstorageFlags = GL_DYNAMIC_STORAGE_BIT;\n\t\ttripleBuffer = false;\n\t}\n\telse if (usage == bufferusage_reuse_many_frames) {\n\t\tstorageFlags = GL_MAP_WRITE_BIT;\n\t\tuseStorage = GL_Available(glBufferStorage);\n\t\tif (!data) {\n\t\t\tSys_Error(\"Buffer %s flagged as constant but no initial data\", name);\n\t\t\treturn false;\n\t\t}\n\t}\n\telse if (usage == bufferusage_constant_data) {\n\t\tstorageFlags = (data ? 0 : GL_DYNAMIC_STORAGE_BIT);\n\t\tuseStorage = GL_Available(glBufferStorage);\n\t\tif (!data) {\n\t\t\tSys_Error(\"Buffer %s flagged as constant but no initial data\", name);\n\t\t\treturn false;\n\t\t}\n\t}\n\telse {\n\t\tSys_Error(\"Unknown usage flag passed to GL_CreateFixedBuffer\");\n\t\treturn false;\n\t}\n\n\tif (!useStorage) {\n\t\t// Fall back to standard mutable buffer\n\t\tGL_GenFixedBuffer(id, type, name, size, data, usage);\n\t\treturn true;\n\t}\n\n\tGL_BindBufferImpl(target, buffer->glref);\n\tif (target == GL_UNIFORM_BUFFER) {\n\t\talignment = glConfig.uniformBufferOffsetAlignment;\n\t}\n\telse if (target == GL_SHADER_STORAGE_BUFFER) {\n\t\talignment = glConfig.shaderStorageBufferOffsetAlignment;\n\t}\n\n\tif (alignment > 1) {\n\t\tbuffer->size = ((size + (alignment - 1)) / alignment) * alignment;\n\t}\n\n\tGL_TraceObjectLabelSet(GL_BUFFER, buffer->glref, -1, name);\n\n\tif (tripleBuffer) {\n\t\tGL_Procedure(glBufferStorage, target, buffer->size * 3, NULL, storageFlags);\n\t\tbuffer->persistent_mapped_ptr = GL_Function(glMapBufferRange, target, 0, buffer->size * 3, storageFlags);\n\n\t\tif (buffer->persistent_mapped_ptr) {\n\t\t\tif (data) {\n\t\t\t\tvoid* base = (void*)((uintptr_t)buffer->persistent_mapped_ptr + buffer->size * glConfig.tripleBufferIndex);\n\n\t\t\t\tmemcpy(base, data, size);\n\t\t\t}\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\t\t\tSys_Printf(\"\\nbuffer,alloc,%d,%s,%u\\n\", id, name, size * 3);\n#endif\n\t\t}\n\t\telse {\n\t\t\tCon_Printf(\"\\20opengl\\21 triple-buffered allocation failed (%dKB)\\n\", (size * 3) / 1024);\n\t\t\tGL_GenFixedBuffer(id, type, name, size, data, usage);\n\t\t\treturn true;\n\t\t}\n\t}\n\telse {\n\t\tGL_Procedure(glBufferStorage, target, size, data, storageFlags);\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\t\tSys_Printf(\"\\nbuffer,alloc,%d,%s,%u\\n\", id, name, size);\n#endif\n\t}\n\n\tbuffer->using_storage = true;\n\tbuffer->storageFlags = storageFlags;\n\treturn true;\n}\n\nstatic void GL_UpdateBuffer(r_buffer_id id, int size, void* data)\n{\n\tassert(id != r_buffer_none);\n\tassert(glBufferState.buffers[id].glref);\n\tassert(data != NULL);\n\tassert(size <= glBufferState.buffers[id].size);\n\n\tif (glBufferState.buffers[id].persistent_mapped_ptr) {\n\t\tvoid* start = (void*)((uintptr_t)glBufferState.buffers[id].persistent_mapped_ptr + GL_BufferOffset(id));\n\n\t\tR_TraceLogAPICall(\"GL_UpdateBuffer[memcpy](%s, %u)\", glBufferState.buffers[id].name, size);\n\t\tmemcpy(start, data, size);\n\t}\n\telse {\n\t\tR_TraceLogAPICall(\"GL_UpdateBuffer(%s)\", glBufferState.buffers[id].name);\n\t\tif (GL_Available(glNamedBufferSubData)) {\n\t\t\tGL_Procedure(glNamedBufferSubData, glBufferState.buffers[id].glref, 0, (GLsizei)size, data);\n\t\t}\n\t\telse {\n\t\t\tGL_BindBuffer(id);\n\t\t\tGL_Procedure(glBufferSubData, GL_BufferTypeToTarget(glBufferState.buffers[id].type), 0, (GLsizeiptr)size, data);\n\t\t}\n\t}\n}\n\nstatic size_t GL_BufferSize(r_buffer_id id)\n{\n\tif (id == r_buffer_none) {\n\t\treturn 0;\n\t}\n\n\treturn glBufferState.buffers[id].size;\n}\n\nstatic void GL_ResizeBuffer(r_buffer_id id, int size, void* data)\n{\n\tassert(id != r_buffer_none);\n\tassert(glBufferState.buffers[id].glref);\n\n\tif (glBufferState.buffers[id].using_storage) {\n\t\tif (glBufferState.buffers[id].persistent_mapped_ptr) {\n\t\t\tif (GL_Available(glUnmapNamedBuffer)) {\n\t\t\t\tGL_Procedure(glUnmapNamedBuffer, glBufferState.buffers[id].glref);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tGL_BindBuffer(id);\n\t\t\t\tGL_Procedure(glUnmapBuffer, GL_BufferTypeToTarget(glBufferState.buffers[id].type));\n\t\t\t}\n\t\t}\n\t\tGL_Procedure(glDeleteBuffers, 1, &glBufferState.buffers[id].glref);\n\t\tglBufferState.buffers[id].glref = 0;\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\t\tSys_Printf(\"\\nbuffer,free-resize,%d,%s,%u\\n\", id, glBufferState.buffers[id].name, glBufferState.buffers[id].size * (glBufferState.buffers[id].persistent_mapped_ptr ? 3 : 1));\n#endif\n\n\t\tGL_CreateFixedBuffer(id, glBufferState.buffers[id].type, glBufferState.buffers[id].name, size, data, glBufferState.buffers[id].usage);\n\t}\n\telse {\n\t\tif (GL_Available(glNamedBufferData)) {\n\t\t\tGL_Procedure(glNamedBufferData, glBufferState.buffers[id].glref, size, data, GL_BufferUsageToGLUsage(glBufferState.buffers[id].usage));\n\t\t}\n\t\telse {\n\t\t\tGL_BindBuffer(id);\n\t\t\tGL_Procedure(glBufferData, GL_BufferTypeToTarget(glBufferState.buffers[id].type), size, data, GL_BufferUsageToGLUsage(glBufferState.buffers[id].usage));\n\t\t}\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\t\tSys_Printf(\"\\nbuffer,free-resize,%d,%s,%u\\n\", id, glBufferState.buffers[id].name, glBufferState.buffers[id].size);\n\t\tSys_Printf(\"\\nbuffer,alloc-resize,%d,%s,%u\\n\", id, glBufferState.buffers[id].name, size);\n#endif\n\n\t\tglBufferState.buffers[id].size = size;\n\t\tR_TraceLogAPICall(\"GL_ResizeBuffer(%s)\", glBufferState.buffers[id].name);\n\t}\n}\n\nstatic void GL_UpdateBufferSection(r_buffer_id id, ptrdiff_t offset, int size, const void* data)\n{\n\tif (id == r_buffer_none) {\n\t\tSys_Error(\"GL_UpdateBufferSection: id == r_buffer_none\");\n\t\treturn;\n\t}\n\telse if (glBufferState.buffers[id].glref == 0) {\n\t\tSys_Error(\"GL_UpdateBufferSection: id(%d:%s) invalid GL ref\", id, glBufferState.buffers[id].name);\n\t\treturn;\n\t}\n\telse if (data == 0) {\n\t\tSys_Error(\"GL_UpdateBufferSection: id(%d:%s) update with NULL data\", id, glBufferState.buffers[id].name);\n\t\treturn;\n\t}\n\telse if (offset < 0) {\n\t\tSys_Error(\"GL_UpdateBufferSection: id(%d:%s) offset < 0\", id, glBufferState.buffers[id].name);\n\t\treturn;\n\t}\n\telse if (offset >= glBufferState.buffers[id].size) {\n\t\tSys_Error(\"GL_UpdateBufferSection: id(%d:%s) offset >= bufsize (%d >= %d)\", id, glBufferState.buffers[id].name, (int)offset, glBufferState.buffers[id].size);\n\t\treturn;\n\t}\n\telse if (offset + size > glBufferState.buffers[id].size) {\n\t\tSys_Error(\"GL_UpdateBufferSection: id(%d:%s) offset + size >= bufsize (%d + %d > %d)\", id, glBufferState.buffers[id].name, (int)offset, size, glBufferState.buffers[id].size);\n\t\treturn;\n\t}\n\n\tR_TraceLogAPICall(\"GL_UpdateBufferSection(%s)\", glBufferState.buffers[id].name);\n\tif (glBufferState.buffers[id].persistent_mapped_ptr) {\n\t\tvoid* base = (void*)((uintptr_t)glBufferState.buffers[id].persistent_mapped_ptr + GL_BufferOffset(id) + offset);\n\n\t\tmemcpy(base, data, size);\n\t}\n\telse {\n\t\tif (GL_Available(glNamedBufferSubData)) {\n\t\t\tGL_Procedure(glNamedBufferSubData, glBufferState.buffers[id].glref, offset, size, data);\n\t\t}\n\t\telse {\n\t\t\tGL_BindBuffer(id);\n\t\t\tGL_Procedure(glBufferSubData, GL_BufferTypeToTarget(glBufferState.buffers[id].type), offset, size, data);\n\t\t}\n\t}\n}\n\nstatic void GL_BufferShutdown(void)\n{\n\tint i;\n\n\tfor (i = 0; i < r_buffer_count; ++i) {\n\t\tif (glBufferState.buffers[i].glref) {\n\t\t\tif (GL_Available(glDeleteBuffers)) {\n\t\t\t\tGL_Procedure(glDeleteBuffers, 1, &glBufferState.buffers[i].glref);\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\t\t\t\tSys_Printf(\"\\nbuffer,free,%d,%s,%u\\n\", i, glBufferState.buffers[i].name, glBufferState.buffers[i].size * (glBufferState.buffers[i].persistent_mapped_ptr ? 3 : 1));\n#endif\n\t\t\t}\n\t\t\tglBufferState.buffers[i].glref = 0;\n\t\t}\n\t}\n\n\tfor (i = 0; i < 3; ++i) {\n\t\tif (glBufferState.tripleBufferSyncObjects[i]) {\n\t\t\tGL_Procedure(glDeleteSync, glBufferState.tripleBufferSyncObjects[i]);\n\t\t}\n\t}\n\tmemset(&glBufferState, 0, sizeof(glBufferState));\n}\n\nstatic void GL_BindBufferBase(r_buffer_id id, unsigned int index)\n{\n\tassert(id != r_buffer_none);\n\tassert(glBufferState.buffers[id].glref);\n\n\tGL_Procedure(glBindBufferBase, GL_BufferTypeToTarget(glBufferState.buffers[id].type), index, glBufferState.buffers[id].glref);\n}\n\nstatic void GL_BindBufferRange(r_buffer_id id, unsigned int index, ptrdiff_t offset, int size)\n{\n\tif (size == 0) {\n\t\treturn;\n\t}\n\n\tassert(id != r_buffer_none);\n\tassert(glBufferState.buffers[id].glref);\n\tassert(size >= 0);\n\tassert(offset >= 0);\n\tassert(offset + size <= glBufferState.buffers[id].size * (glBufferState.buffers[id].persistent_mapped_ptr ? 3 : 1));\n\n\tGL_Procedure(glBindBufferRange, GL_BufferTypeToTarget(glBufferState.buffers[id].type), index, glBufferState.buffers[id].glref, offset, size);\n}\n\nstatic qbool GL_BindBufferImpl(GLenum target, GLuint buffer)\n{\n\tif (target == GL_ARRAY_BUFFER) {\n\t\tif (buffer == glBufferState.currentArrayBuffer) {\n\t\t\treturn false;\n\t\t}\n\t\tglBufferState.currentArrayBuffer = buffer;\n\t}\n\telse if (target == GL_UNIFORM_BUFFER) {\n\t\tif (buffer == glBufferState.currentUniformBuffer) {\n\t\t\treturn false;\n\t\t}\n\t\tglBufferState.currentUniformBuffer = buffer;\n\t}\n\telse if (target == GL_DRAW_INDIRECT_BUFFER) {\n\t\tif (buffer == glBufferState.currentDrawIndirectBuffer) {\n\t\t\treturn false;\n\t\t}\n\t\tglBufferState.currentDrawIndirectBuffer = buffer;\n\t}\n\telse if (target == GL_ELEMENT_ARRAY_BUFFER) {\n\t\tif (buffer == glBufferState.currentElementArrayBuffer) {\n\t\t\treturn false;\n\t\t}\n\t\tglBufferState.currentElementArrayBuffer = buffer;\n\t}\n\n\tGL_Procedure(glBindBuffer, target, buffer);\n\treturn true;\n}\n\nstatic void GL_BindBuffer(r_buffer_id id)\n{\n\tqbool switched;\n\tGLenum glTarget;\n\n\tif (!(R_BufferReferenceIsValid(id) && glBufferState.buffers[id].glref)) {\n\t\tR_TraceLogAPICall(\"GL_BindBuffer(<invalid-reference:%s>)\", glBufferState.buffers[id].name);\n\t\treturn;\n\t}\n\n\tglTarget = GL_BufferTypeToTarget(glBufferState.buffers[id].type);\n\tswitched = GL_BindBufferImpl(glTarget, glBufferState.buffers[id].glref);\n\tif (glBufferState.buffers[id].type == buffertype_index) {\n\t\tR_BindVertexArrayElementBuffer(id);\n\t}\n\n\tif (switched) {\n\t\tR_TraceLogAPICall(\"GL_BindBuffer(%s)\", glBufferState.buffers[id].name);\n\t}\n}\n\nstatic void GL_InitialiseBufferState(void)\n{\n\tqbool tripleBufferSupported = glBufferState.tripleBuffer_supported;\n\tmemset(&glBufferState, 0, sizeof(glBufferState));\n\tglBufferState.tripleBuffer_supported = tripleBufferSupported;\n\tR_BindVertexArrayElementBuffer(r_buffer_none);\n\tR_InitialiseVAOState();\n}\n\nstatic void GL_UnBindBuffer(buffertype_t type)\n{\n\tGLenum target = GL_BufferTypeToTarget(type);\n\tGL_BindBufferImpl(target, 0);\n\tR_TraceLogAPICall(\"GL_UnBindBuffer(%s)\", target == GL_ARRAY_BUFFER ? \"array-buffer\" : (target == GL_ELEMENT_ARRAY_BUFFER ? \"element-array\" : \"?\"));\n}\n\nstatic qbool GL_BufferValid(r_buffer_id id)\n{\n\treturn id > r_buffer_none && id < r_buffer_count && glBufferState.buffers[id].glref != 0;\n}\n\n// Called when the VAO is bound\nstatic void GL_SetElementArrayBuffer(r_buffer_id id)\n{\n\tif (GL_BufferValid(id)) {\n\t\tglBufferState.currentElementArrayBuffer = glBufferState.buffers[id].glref;\n\t}\n\telse {\n\t\tglBufferState.currentElementArrayBuffer = 0;\n\t}\n}\n\nstatic void GL_EnsureBufferSize(r_buffer_id id, int size)\n{\n\tassert(id);\n\tassert(glBufferState.buffers[id].glref);\n\n\tif (glBufferState.buffers[id].size < size) {\n\t\tGL_ResizeBuffer(id, size, NULL);\n\t}\n}\n\nstatic void GL_BufferStartFrame(void)\n{\n\tif (glBufferState.tripleBuffer_supported && glBufferState.tripleBufferSyncObjects[glConfig.tripleBufferIndex]) {\n\t\tGLenum waitRet = GL_Function(glClientWaitSync, glBufferState.tripleBufferSyncObjects[glConfig.tripleBufferIndex], 0, 0);\n\t\twhile (waitRet == GL_TIMEOUT_EXPIRED) {\n\t\t\t// Flush commands and wait for longer\n\t\t\twaitRet = GL_Function(glClientWaitSync, glBufferState.tripleBufferSyncObjects[glConfig.tripleBufferIndex], GL_SYNC_FLUSH_COMMANDS_BIT, 1000000000);\n\t\t}\n\t}\n}\n\nstatic void GL_BufferEndFrame(void)\n{\n\tif (glBufferState.tripleBuffer_supported) {\n\t\tif (glBufferState.tripleBufferSyncObjects[glConfig.tripleBufferIndex]) {\n\t\t\tGL_Procedure(glDeleteSync, glBufferState.tripleBufferSyncObjects[glConfig.tripleBufferIndex]);\n\t\t}\n\t\tglBufferState.tripleBufferSyncObjects[glConfig.tripleBufferIndex] = GL_Function(glFenceSync, GL_SYNC_GPU_COMMANDS_COMPLETE, 0);\n\t\tglConfig.tripleBufferIndex = (glConfig.tripleBufferIndex + 1) % 3;\n\t}\n}\n\nstatic uintptr_t GL_BufferOffset(r_buffer_id id)\n{\n\treturn glBufferState.buffers[id].persistent_mapped_ptr ? glBufferState.buffers[id].size * glConfig.tripleBufferIndex : 0;\n}\n\nstatic qbool GL_BuffersReady(void)\n{\n\tif (glBufferState.tripleBuffer_supported && glBufferState.tripleBufferSyncObjects[glConfig.tripleBufferIndex]) {\n\t\tif (GL_Function(glClientWaitSync, glBufferState.tripleBufferSyncObjects[glConfig.tripleBufferIndex], 0, 0) == GL_TIMEOUT_EXPIRED) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\n#ifdef WITH_RENDERING_TRACE\nstatic void GL_PrintBufferState(FILE* debug_frame_out, int debug_frame_depth)\n{\n\tchar label[64];\n\n\tif (glBufferState.currentArrayBuffer) {\n\t\tGL_TraceObjectLabelGet(GL_BUFFER, glBufferState.currentArrayBuffer, sizeof(label), NULL, label);\n\t\tfprintf(debug_frame_out, \"%.*s   ArrayBuffer: %u (%s)\\n\", debug_frame_depth, \"                                                          \", glBufferState.currentArrayBuffer, label);\n\t}\n\tif (glBufferState.currentElementArrayBuffer) {\n\t\tGL_TraceObjectLabelGet(GL_BUFFER, glBufferState.currentElementArrayBuffer, sizeof(label), NULL, label);\n\t\tfprintf(debug_frame_out, \"%.*s   ElementArray: %u (%s)\\n\", debug_frame_depth, \"                                                          \", glBufferState.currentElementArrayBuffer, label);\n\t}\n}\n#endif\n\nvoid R_Stub_NoOperation(void)\n{\n}\n\nqbool R_Stub_True(void)\n{\n\treturn true;\n}\n\nqbool R_Stub_False(void)\n{\n\treturn false;\n}\n\nuintptr_t R_Stub_BufferZero(r_buffer_id ref)\n{\n\treturn 0;\n}\n\nvoid GL_InitialiseBufferHandling(api_buffers_t* api)\n{\n\tqbool buffers_supported = renderer.vaos_supported;\n\n\tmemset(api, 0, sizeof(*api));\n\n\tGL_LoadMandatoryFunctionExtension(glBindBuffer, buffers_supported);\n\tGL_LoadMandatoryFunctionExtension(glBufferData, buffers_supported);\n\tGL_LoadMandatoryFunctionExtension(glBufferSubData, buffers_supported);\n\tGL_LoadMandatoryFunctionExtension(glGenBuffers, buffers_supported);\n\tGL_LoadMandatoryFunctionExtension(glDeleteBuffers, buffers_supported);\n\tGL_LoadMandatoryFunctionExtension(glUnmapBuffer, buffers_supported);\n\n\tR_TraceAPI(\"Buffers supported: %s\", buffers_supported ? \"yes\" : \"no (!)\");\n\n\t// OpenGL 3.0 onwards, for 4.3+ support only\n\tif (GL_VersionAtLeast(3, 0)) {\n\t\tGL_LoadOptionalFunction(glBindBufferBase);\n\t\tGL_LoadOptionalFunction(glBindBufferRange);\n\t} else {\n\t\tGL_InvalidateFunction(glBindBufferBase);\n\t\tGL_InvalidateFunction(glBindBufferRange);\n\t}\n\n\t// OpenGL 4.4, persistent mapping of buffers\n\tglBufferState.tripleBuffer_supported = !COM_CheckParm(cmdline_param_client_notriplebuffering);\n\tif (GL_VersionAtLeast(3, 2) || SDL_GL_ExtensionSupported(\"GL_ARB_sync\")) {\n\t\tGL_LoadMandatoryFunctionExtension(glFenceSync, glBufferState.tripleBuffer_supported);\n\t\tGL_LoadMandatoryFunctionExtension(glClientWaitSync, glBufferState.tripleBuffer_supported);\n\t\tGL_LoadMandatoryFunctionExtension(glDeleteSync, glBufferState.tripleBuffer_supported);\n\t}\n\telse {\n\t\tglBufferState.tripleBuffer_supported = false;\n\t\tGL_InvalidateFunction(glFenceSync);\n\t\tGL_InvalidateFunction(glClientWaitSync);\n\t\tGL_InvalidateFunction(glDeleteSync);\n\t}\n\tif (GL_VersionAtLeast(4, 4) || SDL_GL_ExtensionSupported(\"GL_ARB_buffer_storage\")) {\n\t\tGL_LoadMandatoryFunctionExtension(glBufferStorage, glBufferState.tripleBuffer_supported);\n\t}\n\telse {\n\t\tglBufferState.tripleBuffer_supported = false;\n\t\tGL_InvalidateFunction(glBufferStorage);\n\t}\n\tif (GL_VersionAtLeast(3, 0)) {\n\t\tGL_LoadMandatoryFunctionExtension(glMapBufferRange, glBufferState.tripleBuffer_supported);\n\t}\n\telse {\n\t\tglBufferState.tripleBuffer_supported = false;\n\t\tGL_InvalidateFunction(glMapBufferRange);\n\t}\n\n\t// OpenGL 4.5 onwards, update directly\n\tif (GL_UseDirectStateAccess()) {\n\t\tGL_LoadOptionalFunction(glNamedBufferSubData);\n\t\tGL_LoadOptionalFunction(glNamedBufferData);\n\t\tGL_LoadOptionalFunction(glUnmapNamedBuffer);\n\t} else {\n\t\tGL_InvalidateFunction(glNamedBufferSubData);\n\t\tGL_InvalidateFunction(glNamedBufferData);\n\t\tGL_InvalidateFunction(glUnmapNamedBuffer);\n\t}\n\n\tglBufferState.tripleBuffer_supported &= buffers_supported;\n\n\tapi->supported = buffers_supported;\n\tif (!api->supported) {\n\t\tapi->FrameReady = R_Stub_True;\n\t\tapi->IsValid = R_Stub_BufferNotValid;\n\t\tapi->InitialiseState = api->Shutdown = R_Stub_NoOperation;\n\t\tapi->StartFrame = api->EndFrame = R_Stub_NoOperation;\n\t\tapi->BufferOffset = R_Stub_BufferZero;\n\n\t\tCon_Printf(\"Using GL buffers: disabled\\n\");\n\t}\n\telse {\n\t\tapi->Bind = GL_BindBuffer;\n\t\tapi->BindBase = GL_BindBufferBase;\n\t\tapi->BindRange = GL_BindBufferRange;\n\t\tapi->BufferOffset = GL_BufferOffset;\n\t\tapi->Create = GL_CreateFixedBuffer;\n\n\t\tapi->StartFrame = GL_BufferStartFrame;\n\t\tapi->EndFrame = GL_BufferEndFrame;\n\t\tapi->FrameReady = GL_BuffersReady;\n\n\t\tapi->EnsureSize = GL_EnsureBufferSize;\n\t\tapi->Resize = GL_ResizeBuffer;\n\n\t\tapi->InitialiseState = GL_InitialiseBufferState;\n\t\tapi->Size = GL_BufferSize;\n\t\tapi->UnBind = GL_UnBindBuffer;\n\t\tapi->Update = GL_UpdateBuffer;\n\t\tapi->UpdateSection = GL_UpdateBufferSection;\n\n\t\tapi->Shutdown = GL_BufferShutdown;\n\t\tapi->SetElementArray = GL_SetElementArrayBuffer;\n\t\tapi->IsValid = GL_BufferValid;\n#ifdef WITH_RENDERING_TRACE\n\t\tapi->PrintState = GL_PrintBufferState;\n#endif\n\t\tapi->supported = true;\n\n\t\tCon_Printf(\"Triple-buffering of GL buffers: %s\\n\", glBufferState.tripleBuffer_supported ? \"enabled\" : \"disabled\");\n\t}\n}\n"
  },
  {
    "path": "src/gl_debug.c",
    "content": "/*\nCopyright (C) 2002-2003 A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n// gl_debug.c - OpenGL dev-only & debugging wrappers\n#include <SDL.h>\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"r_texture.h\"\n#include \"r_renderer.h\"\n#include \"utils.h\"\n#include \"gl_texture_internal.h\"\n\n#ifdef WITH_RENDERING_TRACE\nstatic int debug_frame_depth = 0;\nstatic unsigned long regions_trace_only;\nstatic qbool dev_frame_debug_queued;\nstatic FILE* debug_frame_out;\nvoid GL_VerifyState(FILE* output);\n\n#define DEBUG_FRAME_DEPTH_CHARS 2\n#endif\n\nGLuint GL_TextureNameFromReference(texture_ref ref);\n\n// <debug-functions (4.3)>\nGL_StaticProcedureDeclaration(glDebugMessageCallback, \"callback=%p, userParam=%p\", GLDEBUGPROC callback, void* userParam)\nGL_StaticProcedureDeclaration(glDebugMessageControl, \"source=%u, type=%u, severity=%u, count=%d, ids=%p, enabled=%d\", GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint* ids, GLboolean enabled)\n\n#ifdef WITH_RENDERING_TRACE\nGL_StaticProcedureDeclaration(glPushDebugGroup, \"source=%u, id=%u, length=%d, message=%s\", GLenum source, GLuint id, GLsizei length, const char* message)\nGL_StaticProcedureDeclaration(glPopDebugGroup, \"\", void)\nGL_StaticProcedureDeclaration(glObjectLabel, \"identifier=%u, name=%u, length=%d, label=%s\", GLenum identifier, GLuint name, GLsizei length, const char* label)\nGL_StaticProcedureDeclaration(glGetObjectLabel, \"identifier=%u, name=%u, bufSize=%d, length=%p, label=%s\", GLenum identifier, GLuint name, GLsizei bufSize, GLsizei* length, char* label)\n#endif\n// </debug-functions>\n\n// Debugging\n// In Visual Studio, messages from the driver appear in Output tab\nvoid APIENTRY MessageCallback(GLenum source,\n\t\t\t\t\t\t\t  GLenum type,\n\t\t\t\t\t\t\t  GLuint id,\n\t\t\t\t\t\t\t  GLenum severity,\n\t\t\t\t\t\t\t  GLsizei length,\n\t\t\t\t\t\t\t  const GLchar* message,\n\t\t\t\t\t\t\t  const void* userParam\n)\n{\n\t// FIXME: think this is caused by having fixed maximum array of samplers\n\t// - either bind to solidblack or have multiple programs and switch depending on # of texture units being used\n\tif (severity == GL_DEBUG_SEVERITY_LOW && strstr(message, \"texture object (0) bound to\")) {\n\t\treturn;\n\t}\n\n\tif (source != GL_DEBUG_SOURCE_APPLICATION) {\n\t\tchar buffer[1024] = { 0 };\n\n\t\tif (type == GL_DEBUG_TYPE_ERROR) {\n\t\t\tsnprintf(buffer, sizeof(buffer) - 1,\n\t\t\t\t\t \"GL CALLBACK: ** GL ERROR ** type = 0x%x, severity = 0x%x, message = %s\\n\",\n\t\t\t\t\t type, severity, message);\n\t\t}\n\t\telse {\n\t\t\tsnprintf(buffer, sizeof(buffer) - 1,\n\t\t\t\t\t \"GL CALLBACK: type = 0x%x, severity = 0x%x, message = %s\\n\",\n\t\t\t\t\t type, severity, message);\n\t\t}\n\n#ifdef WITH_RENDERING_TRACE\n\t\tif (debug_frame_out) {\n\t\t\tfprintf(debug_frame_out, \"%s\", buffer);\n\t\t\tfflush(debug_frame_out);\n\t\t}\n#endif\n#ifdef _WIN32\n\t\tif (IsDebuggerPresent()) {\n\t\t\tOutputDebugString(buffer);\n\t\t}\n\t\telse {\n\t\t\tprintf(\"[OGL] %s\\n\", buffer);\n\t\t}\n#else\n\t\tprintf(\"[OGL] %s\\n\", buffer);\n#endif\n\t}\n}\n\nvoid GL_InitialiseDebugging(void)\n{\n\t// During init, enable debug output\n\tif (R_DebugProfileContext()) {\n\t\tGL_LoadOptionalFunction(glDebugMessageCallback);\n\t\tGL_LoadOptionalFunction(glDebugMessageControl);\n\n\t\tif (GL_Available(glDebugMessageCallback) && !COM_CheckParm(cmdline_param_client_nocallback)) {\n\t\t\tglEnable(GL_DEBUG_OUTPUT);\n\t\t\tglEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);\n\t\t\tGL_Procedure(glDebugMessageCallback, (GLDEBUGPROC)MessageCallback, 0);\n\t\t}\n\n\t\tif (GL_Available(glDebugMessageControl) && !COM_CheckParm(cmdline_param_client_nocallback)) {\n\t\t\tGL_Procedure(glDebugMessageControl, GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);\n\t\t}\n\t}\n\n#ifdef WITH_RENDERING_TRACE\n\tGL_LoadOptionalFunction(glObjectLabel);\n\tGL_LoadOptionalFunction(glGetObjectLabel);\n\tGL_LoadOptionalFunction(glPushDebugGroup);\n\tGL_LoadOptionalFunction(glPopDebugGroup);\n#endif\n}\n\n#ifdef WITH_RENDERING_TRACE\nvoid R_TraceEnterRegion(const char* regionName, qbool trace_only)\n{\n\tif (debug_frame_out) {\n\t\tfprintf(debug_frame_out, \"Enter: %.*s %s {\\n\", debug_frame_depth, \"                                                          \", regionName);\n\t\tfflush(debug_frame_out);\n\t\tdebug_frame_depth += DEBUG_FRAME_DEPTH_CHARS;\n\t}\n\telse if (R_UseModernOpenGL()) {\n\t\tif (!trace_only && GL_Available(glPushDebugGroup)) {\n\t\t\tGL_Procedure(glPushDebugGroup, GL_DEBUG_SOURCE_APPLICATION, 0, -1, regionName);\n\t\t}\n\t}\n\n\tregions_trace_only <<= 1;\n\tregions_trace_only &= (trace_only ? 1 : 0);\n}\n\nvoid R_TraceLeaveRegion(qbool trace_only)\n{\n\tif (debug_frame_out) {\n\t\tdebug_frame_depth -= DEBUG_FRAME_DEPTH_CHARS;\n\t\tdebug_frame_depth = max(debug_frame_depth, 0);\n\t\tfprintf(debug_frame_out, \"Leave: %.*s }\\n\", debug_frame_depth, \"                                                          \");\n\t\tfflush(debug_frame_out);\n\t}\n\telse if (R_UseModernOpenGL()) {\n\t\tif (!trace_only && GL_Available(glPopDebugGroup)) {\n\t\t\tGL_ProcedureNoArgs(glPopDebugGroup);\n\t\t}\n\t}\n}\n\nqbool R_TraceLoggingEnabled(void)\n{\n\treturn debug_frame_out != NULL;\n}\n\nvoid R_TraceAPI(const char* format, ...)\n{\n\tif (debug_frame_out) {\n\t\tva_list argptr;\n\t\tchar msg[4096];\n\n\t\tva_start(argptr, format);\n\t\tvsnprintf(msg, sizeof(msg), format, argptr);\n\t\tva_end(argptr);\n\n\t\tfprintf(debug_frame_out, \"API:   %.*s %s\\n\", debug_frame_depth, \"                                                          \", msg);\n\t\tfflush(debug_frame_out);\n\t}\n\telse  {\n\n\t}\n}\n\nvoid R_TraceLogAPICallDirect(const char* format, ...)\n{\n\tif (debug_frame_out) {\n\t\tva_list argptr;\n\t\tchar msg[4096];\n\n\t\tva_start(argptr, format);\n\t\tvsnprintf(msg, sizeof(msg), format, argptr);\n\t\tva_end(argptr);\n\n\t\tfprintf(debug_frame_out, \"API:   %.*s %s\\n\", debug_frame_depth, \"                                                          \", msg);\n\t\tfflush(debug_frame_out);\n\t}\n}\n\nvoid R_TraceResetRegion(qbool start)\n{\n\tif (start && debug_frame_out) {\n\t\tfclose(debug_frame_out);\n\t\tdebug_frame_out = NULL;\n\t\tdev_frame_debug_queued = COM_CheckParm(cmdline_param_client_video_r_trace);\n\t}\n\n\tif (start && dev_frame_debug_queued) {\n\t\tchar fileName[MAX_PATH];\n#ifndef _WIN32\n\t\ttime_t t;\n\t\tstruct tm date;\n\t\tt = time(NULL);\n\t\tlocaltime_r(&t, &date);\n\n\t\tsnprintf(fileName, sizeof(fileName), \"%s/qw/%s/frame_%04d-%02d-%02d_%02d-%02d-%02d.txt\",\n\t\t\t\t com_basedir, COM_CheckParm(cmdline_param_client_video_r_trace) ? \"trace/\" : \"\", 1900 + date.tm_year, 1 + date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min, date.tm_sec);\n#else\n\t\tSYSTEMTIME date;\n\t\tint i;\n\n\t\tGetLocalTime(&date);\n\n\t\tfor (i = 0; i < 1000; ++i) {\n\t\t\tFILE* temp;\n\n\t\t\tsnprintf(fileName, sizeof(fileName), \"%s/qw/%s/frame_%04d-%02d-%02d_%02d-%02d-%02d_%04d.txt\",\n\t\t\t\tcom_basedir, COM_CheckParm(cmdline_param_client_video_r_trace) ? \"trace/\" : \"\", date.wYear, date.wMonth, date.wDay, date.wHour, date.wMinute, date.wSecond, i);\n\n\t\t\tif ((temp = fopen(fileName, \"rt\"))) {\n\t\t\t\tfclose(temp);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n#endif\n\n\t\tif (COM_CheckParm(cmdline_param_client_video_r_trace)) {\n\t\t\tSys_mkdir(va(\"%s/qw/trace\", com_basedir));\n\t\t}\n\n\t\tdebug_frame_out = fopen(fileName, \"wt\");\n\t\tdev_frame_debug_queued = false;\n\t}\n\n\tif (debug_frame_out) {\n\t\tfprintf(debug_frame_out, \"---Reset---\\n\");\n\t\tfflush(debug_frame_out);\n\t\tdebug_frame_depth = 0;\n\t}\n}\n\nvoid GL_TraceObjectLabelSet(GLenum identifier, GLuint name, int length, const char* label)\n{\n\tif (GL_Available(glObjectLabel)) {\n\t\tGL_Procedure(glObjectLabel, identifier, name, length, label ? label : \"?\");\n\t}\n}\n\nvoid GL_TraceObjectLabelGet(GLenum identifier, GLuint name, int bufSize, int* length, char* label)\n{\n\tlabel[0] = '\\0';\n\tif (GL_Available(glGetObjectLabel)) {\n\t\tGL_Procedure(glGetObjectLabel, identifier, name, bufSize, length, label);\n\t}\n}\n\nvoid GL_TextureLabelSet(texture_ref ref, const char* label)\n{\n\tGL_TraceObjectLabelSet(GL_TEXTURE, GL_TextureNameFromReference(ref), -1, label);\n}\n\nvoid GL_TextureLabelGet(texture_ref ref, int bufSize, int* length, char* label)\n{\n\tGL_TraceObjectLabelGet(GL_TEXTURE, GL_TextureNameFromReference(ref), bufSize, length, label);\n}\n\nvoid Dev_VidFrameStart(void)\n{\n\tDev_VidFrameTrace();\n\tR_TraceResetRegion(true);\n}\n\nvoid Dev_VidFrameTrace(void)\n{\n\tdev_frame_debug_queued = true;\n}\n\nvoid R_TraceDebugState(void)\n{\n\tif (debug_frame_out) {\n\t\tR_TracePrintState(debug_frame_out, debug_frame_depth);\n\t}\n\tif (COM_CheckParm(cmdline_param_client_verify_glstate)) {\n\t\tGL_VerifyState(debug_frame_out);\n\t}\n}\n\nvoid Dev_VidTextureDump(void)\n{\n\tchar folder[MAX_PATH];\n\tbyte* buffer = NULL;\n\tbyte* row = NULL;\n\tint buffer_size = 0;\n\tint row_size = 0;\n\tint i, j;\n#ifndef _WIN32\n\ttime_t t;\n\tstruct tm date;\n\tt = time(NULL);\n\tlocaltime_r(&t, &date);\n\n\tsnprintf(folder, sizeof(folder), \"%s/qw/textures_%04d-%02d-%02d_%02d-%02d-%02d\",\n\t\tcom_basedir, 1900 + date.tm_year, 1 + date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min, date.tm_sec);\n#else\n\tSYSTEMTIME date;\n\tGetLocalTime(&date);\n\n\tsnprintf(folder, sizeof(folder), \"%s/qw/textures_%04d-%02d-%02d_%02d-%02d-%02d\",\n\t\tcom_basedir, date.wYear, date.wMonth, date.wDay, date.wHour, date.wMinute, date.wSecond);\n#endif\n\n\tstrlcat(folder, \"/\", sizeof(folder));\n\tFS_CreatePath(folder);\n\n\tfor (i = 0; i < R_TextureCount(); ++i) {\n\t\ttexture_ref ref = { i };\n\n\t\tif (R_TextureReferenceIsValid(ref)) {\n\t\t\tint row_length = R_TextureWidth(ref) * 4;\n\t\t\tint size = R_TextureWidth(ref) * R_TextureHeight(ref) * 4;\n\t\t\tif (size > buffer_size) {\n\t\t\t\tQ_free(buffer);\n\t\t\t\tbuffer = Q_malloc(size);\n\t\t\t\tbuffer_size = size;\n\t\t\t}\n\t\t\telse if (buffer_size) {\n\t\t\t\tmemset(buffer, 0, buffer_size);\n\t\t\t}\n\n\t\t\tif (row_length > row_size) {\n\t\t\t\tQ_free(row);\n\t\t\t\trow = Q_malloc(row_length);\n\t\t\t\trow_size = row_length;\n\t\t\t}\n\n\t\t\tif (!size) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (size) {\n\t\t\t\tchar reftext[10];\n\t\t\t\tchar filename[MAX_OSPATH];\n\n\t\t\t\tsnprintf(reftext, sizeof(reftext), \"%03d\", i);\n\t\t\t\tstrlcpy(filename, reftext, sizeof(filename));\n\t\t\t\tstrlcat(filename, \"-\", sizeof(filename));\n\t\t\t\tstrlcat(filename, R_TextureIdentifier(ref), sizeof(filename));\n\t\t\t\tUtil_ToValidFileName(filename, filename, sizeof(filename));\n\n\t\t\t\tif (R_TextureType(ref) == texture_type_2d) {\n\t\t\t\t\tscr_sshot_target_t* sshot_params = Q_malloc(sizeof(scr_sshot_target_t));\n\n\t\t\t\t\trenderer.TextureGet(ref, buffer_size, buffer, 3);\n\n\t\t\t\t\tCOM_ForceExtension(filename, \".png\");\n\t\t\t\t\tsshot_params->buffer = buffer;\n\t\t\t\t\tsshot_params->freeMemory = false;\n\t\t\t\t\tsshot_params->format = IMAGE_PNG;\n\t\t\t\t\tstrlcpy(sshot_params->fileName, folder, sizeof(sshot_params->fileName));\n\t\t\t\t\tstrlcat(sshot_params->fileName, filename, sizeof(sshot_params->fileName));\n\t\t\t\t\tsshot_params->width = R_TextureWidth(ref);\n\t\t\t\t\tsshot_params->height = R_TextureHeight(ref);\n\n\t\t\t\t\tfor (j = 0; j < sshot_params->height / 2; ++j) {\n\t\t\t\t\t\tsize_t row_bytes = sshot_params->width * 3;\n\t\t\t\t\t\tbyte* this_row = &buffer[j * row_bytes];\n\t\t\t\t\t\tbyte* flipped_row = &buffer[(sshot_params->height - j - 1) * row_bytes];\n\n\t\t\t\t\t\tmemcpy(row, this_row, row_bytes);\n\t\t\t\t\t\tmemcpy(this_row, flipped_row, row_bytes);\n\t\t\t\t\t\tmemcpy(flipped_row, row, row_bytes);\n\t\t\t\t\t}\n\t\t\t\t\tSCR_ScreenshotWrite(sshot_params);\n\t\t\t\t}\n\t\t\t\telse if (R_TextureType(ref) == texture_type_cubemap) {\n\t\t\t\t\tchar side_filename[MAX_OSPATH];\n\t\t\t\t\tconst char* side_labels[] = { \"-pos-x\", \"-neg-x\", \"-pos-y\", \"-neg-y\", \"-pos-z\", \"-neg-z\" };\n\t\t\t\t\tint j;\n\n\t\t\t\t\tGL_BindTextureUnit(GL_TEXTURE0, ref);\n\t\t\t\t\tfor (j = 0; j < 6; ++j) {\n\t\t\t\t\t\tscr_sshot_target_t* sshot_params = Q_malloc(sizeof(scr_sshot_target_t));\n\n\t\t\t\t\t\tstrlcpy(side_filename, filename, sizeof(side_filename));\n\t\t\t\t\t\tstrlcat(side_filename, side_labels[j], sizeof(side_filename));\n\t\t\t\t\t\tCOM_ForceExtension(side_filename, \".png\");\n\n\t\t\t\t\t\tglGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);\n\n\t\t\t\t\t\tsshot_params->buffer = buffer;\n\t\t\t\t\t\tsshot_params->freeMemory = false;\n\t\t\t\t\t\tsshot_params->format = IMAGE_PNG;\n\t\t\t\t\t\tstrlcpy(sshot_params->fileName, folder, sizeof(sshot_params->fileName));\n\t\t\t\t\t\tstrlcat(sshot_params->fileName, side_filename, sizeof(sshot_params->fileName));\n\t\t\t\t\t\tsshot_params->width = R_TextureWidth(ref);\n\t\t\t\t\t\tsshot_params->height = R_TextureHeight(ref);\n\t\t\t\t\t\tSCR_ScreenshotWrite(sshot_params);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tQ_free(buffer);\n\tQ_free(row);\n}\n\n#endif // WITH_RENDERING_TRACE\n"
  },
  {
    "path": "src/gl_drawcall_wrappers.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include <SDL.h>\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"tr_types.h\"\n#include \"r_vao.h\"\n\n// <draw-functions (various)>\nGL_StaticProcedureDeclaration(glMultiDrawArrays, \"mode=%u, first=%p, count=%p, drawcount=%d\", GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount)\nGL_StaticProcedureDeclaration(glMultiDrawElements, \"mode=%u, count=%p, type=%u, indices=%p, drawcount=%d\", GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, GLsizei drawcount)\nGL_StaticProcedureDeclaration(glDrawElementsBaseVertex, \"mode=%u, count=%d, type=%u, indices=%p, basevertex=%d\", GLenum mode, GLsizei count, GLenum type, GLvoid* indices, GLint basevertex)\nGL_StaticProcedureDeclaration(glPrimitiveRestartIndex, \"index=%u\", GLuint index)\n\n// (modern/4.3+)\n// typedef void (APIENTRY *glDrawArraysInstanced_t)(GLenum mode, GLint first, GLsizei count, GLsizei primcount);\nGL_StaticProcedureDeclaration(glMultiDrawArraysIndirect, \"mode=%u, indirect=%p, drawcount=%d, stride=%d\", GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride)\nGL_StaticProcedureDeclaration(glMultiDrawElementsIndirect, \"mode=%u, type=%u, indirect=%p, drawcount=%d, stride=%d\", GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride)\nGL_StaticProcedureDeclaration(glDrawArraysInstancedBaseInstance, \"mode=%u, first=%d, count=%d, primcount=%d, baseinstance=%u\", GLenum mode, GLint first, GLsizei count, GLsizei primcount, GLuint baseinstance)\nGL_StaticProcedureDeclaration(glDrawElementsInstancedBaseInstance, \"mode=%u, count=%d, type=%u, indices=%p, primcount=%d, baseinstance=%d\", GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei primcount, GLuint baseinstance)\nGL_StaticProcedureDeclaration(glDrawElementsInstancedBaseVertexBaseInstance, \"mode=%u, count=%d, type=%u, indices=%p, primcount=%d, basevertex=%d, baseinstance=%u\", GLenum mode, GLsizei count, GLenum type, GLvoid* indices, GLsizei primcount, GLint basevertex, GLuint baseinstance)\nGL_StaticProcedureDeclaration(glDrawElementsIndirect, \"mode=%u, type=%u, indirect=%p\", GLenum mode, GLenum type, GLvoid* indirect)\nGL_StaticProcedureDeclaration(glDrawArraysIndirect, \"mode=%u, indirect=%p\", GLenum mode, const void* indirect)\nGL_StaticProcedureDeclaration(glUniformBlockBinding, \"program=%u, index=%u, binding=%u\", GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding)\nGL_StaticFunctionDeclaration(glGetUniformBlockIndex, \"program=%u, name=%s\", \"returns %u\", GLuint, GLuint program, const GLchar *uniformBlockName)\nGL_StaticFunctionWrapperBody(glGetUniformBlockIndex, GLuint, program, uniformBlockName)\n// </draw-functions>\n\nvoid GL_LoadDrawFunctions(void)\n{\n\tglConfig.supported_features &= ~(R_SUPPORT_INDIRECT_RENDERING | R_SUPPORT_INSTANCED_RENDERING | R_SUPPORT_PRIMITIVERESTART);\n\n\tif (GL_VersionAtLeast(4, 3) || SDL_GL_ExtensionSupported(\"GL_ARB_multi_draw_indirect\")) {\n\t\tqbool all_available = true;\n\n\t\tGL_LoadMandatoryFunctionExtension(glMultiDrawArraysIndirect, all_available);\n\t\tGL_LoadMandatoryFunctionExtension(glMultiDrawElementsIndirect, all_available);\n\n\t\tglConfig.supported_features |= (all_available ? R_SUPPORT_INDIRECT_RENDERING : 0);\n\t} else {\n\t\tGL_InvalidateFunction(glMultiDrawArraysIndirect);\n\t\tGL_InvalidateFunction(glMultiDrawElementsIndirect);\n\t}\n\n\tif (GL_VersionAtLeast(4, 2) || SDL_GL_ExtensionSupported(\"GL_ARB_base_instance\")) {\n\t\tqbool all_available = true;\n\n\t\tGL_LoadMandatoryFunctionExtension(glDrawArraysInstancedBaseInstance, all_available);\n\t\tGL_LoadMandatoryFunctionExtension(glDrawElementsInstancedBaseInstance, all_available);\n\t\tGL_LoadMandatoryFunctionExtension(glDrawElementsInstancedBaseVertexBaseInstance, all_available);\n\n\t\tglConfig.supported_features |= (all_available ? R_SUPPORT_INSTANCED_RENDERING : 0);\n\t} else {\n\t\tGL_InvalidateFunction(glDrawArraysInstancedBaseInstance);\n\t\tGL_InvalidateFunction(glDrawElementsInstancedBaseInstance);\n\t\tGL_InvalidateFunction(glDrawElementsInstancedBaseVertexBaseInstance);\n\t}\n\n\t// GL 4.0 core: indirect draw and UBO binding functions\n\tif (GL_VersionAtLeast(4, 0)) {\n\t\tGL_LoadOptionalFunction(glDrawElementsIndirect);\n\t\tGL_LoadOptionalFunction(glDrawArraysIndirect);\n\t} else {\n\t\tGL_InvalidateFunction(glDrawElementsIndirect);\n\t\tGL_InvalidateFunction(glDrawArraysIndirect);\n\t}\n\tif (GL_VersionAtLeast(3, 1)) {\n\t\tGL_LoadOptionalFunction(glUniformBlockBinding);\n\t\tGL_LoadOptionalFunction(glGetUniformBlockIndex);\n\t} else {\n\t\tGL_InvalidateFunction(glUniformBlockBinding);\n\t\tGL_InvalidateFunction(glGetUniformBlockIndex);\n\t}\n\n\t// Draw functions used for modern & classic\n\tGL_LoadOptionalFunction(glMultiDrawArrays);\n\tGL_LoadOptionalFunction(glMultiDrawElements);\n\n\t// Use this instead of glDrawArrays() if on particular drivers (see github bug #416)\n\tif (GL_Available(glMultiDrawArrays) && glConfig.amd_issues) {\n\t\tglConfig.broken_features |= R_BROKEN_PREFERMULTIDRAW;\n\t}\n\n\tif (GL_VersionAtLeast(3, 2) || SDL_GL_ExtensionSupported(\"GL_ARB_draw_elements_base_vertex\")) {\n\t\tGL_LoadOptionalFunction(glDrawElementsBaseVertex);\n\t} else {\n\t\tGL_InvalidateFunction(glDrawElementsBaseVertex);\n\t}\n\n\tGL_InvalidateFunction(glPrimitiveRestartIndex);\n\n\tglConfig.supported_features &= ~R_SUPPORT_PRIMITIVERESTART;\n\tif (R_UseModernOpenGL() || GL_VersionAtLeast(3, 1)) {\n\t\tGL_LoadOptionalFunction(glPrimitiveRestartIndex);\n\t\tif (GL_Available(glPrimitiveRestartIndex)) {\n\t\t\tglEnable(GL_PRIMITIVE_RESTART);\n\t\t\tif (GL_VersionAtLeast(4, 3)) {\n\t\t\t\tglEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tGL_Procedure(glPrimitiveRestartIndex, (~(GLuint)0));\n\t\t\t}\n\t\t\tglConfig.supported_features |= R_SUPPORT_PRIMITIVERESTART;\n\t\t}\n\t}\n}\n\n// Wrappers around drawing functions\nvoid GL_MultiDrawArrays(GLenum mode, GLint* first, GLsizei* count, GLsizei primcount)\n{\n\tif (GL_Available(glMultiDrawArrays)) {\n\t\tGL_Procedure(glMultiDrawArrays, mode, first, count, primcount);\n\t\t++frameStats.draw_calls;\n\t\tframeStats.subdraw_calls += primcount;\n\t}\n\telse {\n\t\tint i;\n\t\tfor (i = 0; i < primcount; ++i) {\n\t\t\tGL_DrawArrays(mode, first[i], count[i]);\n\t\t}\n\t}\n}\n\nvoid GL_DrawArrays(GLenum mode, GLint first, GLsizei count)\n{\n\tR_TraceLogAPICall(\"glDrawArrays(%d verts)\", count);\n\tassert(R_VAOBound());\n\tif (!R_VAOBound()) {\n\t\tCon_Printf(\"GL_DrawArrays() with no VAO bound\\n\");\n\t\treturn;\n\t}\n\tif (GL_WorkaroundNeeded(R_BROKEN_PREFERMULTIDRAW) && GL_Available(glMultiDrawArrays)) {\n\t\tGL_Procedure(glMultiDrawArrays, mode, &first, &count, 1);\n\t}\n\telse {\n\t\tGL_BuiltinProcedure(glDrawArrays, \"(mode=%u, first=%d, count=%d)\", mode, first, count);\n\t}\n\t++frameStats.draw_calls;\n}\n\nvoid GL_DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, GLvoid* indices, GLint basevertex)\n{\n\tif (basevertex && !GL_Available(glDrawElementsBaseVertex)) {\n\t\tSys_Error(\"glDrawElementsBaseVertex called, not supported\");\n\t}\n\telse if (GL_Available(glDrawElementsBaseVertex)) {\n\t\tGL_Procedure(glDrawElementsBaseVertex, mode, count, type, indices, basevertex);\n\t}\n\telse {\n\t\tGL_BuiltinProcedure(glDrawElements, \"mode=%u, count=%d, type=%u, indices=%p\", mode, count, type, indices);\n\t}\n\t++frameStats.draw_calls;\n}\n\nqbool GL_DrawElementsBaseVertexAvailable(void)\n{\n\treturn GL_Available(glDrawElementsBaseVertex);\n}\n\nvoid GL_DrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices)\n{\n\tR_TraceLogAPICall(\"glDrawElements(%d verts)\", count);\n\tglDrawElements(mode, count, type, indices);\n\t++frameStats.draw_calls;\n}\n\nvoid GL_MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride)\n{\n\tif (GL_Available(glMultiDrawArraysIndirect)) {\n\t\tGL_Procedure(glMultiDrawArraysIndirect, mode, indirect, drawcount, stride);\n\t}\n\telse if (GL_Available(glDrawArraysIndirect)) {\n\t\tint actual_stride = stride ? stride : (4 * sizeof(GLuint));\n\t\tint i;\n\t\tfor (i = 0; i < drawcount; i++) {\n\t\t\tGL_Procedure(glDrawArraysIndirect, mode, (const char*)indirect + i * actual_stride);\n\t\t}\n\t}\n\t++frameStats.draw_calls;\n\tframeStats.subdraw_calls += drawcount;\n}\n\nvoid GL_MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride)\n{\n\tif (GL_Available(glMultiDrawElementsIndirect)) {\n\t\tGL_Procedure(glMultiDrawElementsIndirect, mode, type, indirect, drawcount, stride);\n\t}\n\telse if (GL_Available(glDrawElementsIndirect)) {\n\t\tint actual_stride = stride ? stride : (5 * sizeof(GLuint));\n\t\tint i;\n\t\tfor (i = 0; i < drawcount; i++) {\n\t\t\tGL_Procedure(glDrawElementsIndirect, mode, type, (const char*)indirect + i * actual_stride);\n\t\t}\n\t}\n\t++frameStats.draw_calls;\n\tframeStats.subdraw_calls += drawcount;\n}\n\nvoid GL_DrawElementsIndirect(GLenum mode, GLenum type, GLvoid* indirect)\n{\n\tGL_Procedure(glDrawElementsIndirect, mode, type, indirect);\n\t++frameStats.draw_calls;\n}\n\nvoid GL_DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, GLvoid* indices, GLsizei primcount, GLint basevertex, GLuint baseinstance)\n{\n\tif (GL_Available(glDrawElementsInstancedBaseVertexBaseInstance)) {\n\t\tGL_Procedure(glDrawElementsInstancedBaseVertexBaseInstance, mode, count, type, indices, primcount, basevertex, baseinstance);\n\t}\n\telse {\n\t\tGL_DrawElementsBaseVertex(mode, count, type, indices, basevertex);\n\t}\n\t++frameStats.draw_calls;\n}\n\nvoid GL_UniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding)\n{\n\tGL_Procedure(glUniformBlockBinding, program, uniformBlockIndex, uniformBlockBinding);\n}\n\nGLuint GL_GetUniformBlockIndex(GLuint program, const GLchar *uniformBlockName)\n{\n\tif (!GL_Available(glGetUniformBlockIndex)) {\n\t\treturn GL_INVALID_INDEX;\n\t}\n\treturn GL_Function(glGetUniformBlockIndex, program, uniformBlockName);\n}\n"
  },
  {
    "path": "src/gl_framebuffer.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Loads and does all the framebuffer stuff\n\n// Seems the original was for camquake & ported across to draw the hud on, but not used\n//   Re-using file for deferred rendering & post-processing in modern\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"gl_framebuffer.h\"\n#include \"tr_types.h\"\n#include \"r_texture.h\"\n#include \"gl_texture_internal.h\"\n#include \"r_renderer.h\"\n#include \"rulesets.h\"\n\nextern cvar_t vid_framebuffer;\nextern cvar_t vid_framebuffer_depthformat;\nextern cvar_t vid_software_palette;\nextern cvar_t vid_framebuffer_hdr;\nextern cvar_t vid_framebuffer_blit;\nextern cvar_t vid_framebuffer_smooth;\nextern cvar_t vid_framebuffer_multisample;\nextern cvar_t gl_outline;\n\nstatic framebuffer_id VID_MultisampledAlternateId(framebuffer_id id);\n\n#ifndef GL_NEGATIVE_ONE_TO_ONE\n#define GL_NEGATIVE_ONE_TO_ONE            0x935E\n#endif\n\n#ifndef GL_ZERO_TO_ONE\n#define GL_ZERO_TO_ONE                    0x935F\n#endif\n\n// If this isn't defined, we use simple texture for depth buffer\n//   in theory render buffers _might_ be better, as they can't be read from\n#define GL_USE_RENDER_BUFFERS\n\n// OpenGL functionality from elsewhere\nGLuint GL_TextureNameFromReference(texture_ref ref);\n\n// OpenGL wrapper functions\n#ifdef GL_USE_RENDER_BUFFERS\n#define EZ_USE_FIXED_SAMPLE_LOCATIONS (GL_TRUE)\nstatic void GL_RenderBufferStorageMultisample(GLuint renderBuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);\nstatic void GL_RenderBufferStorage(GLuint renderBuffer, GLenum internalformat, GLsizei width, GLsizei height);\nstatic void GL_FramebufferRenderbuffer(GLuint fbref, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);\nstatic void GL_GenRenderBuffers(GLsizei n, GLuint* buffers);\n#else\n#define EZ_USE_FIXED_SAMPLE_LOCATIONS (GL_FALSE)\n#endif\nstatic GLenum GL_CheckFramebufferStatus(GLuint fbref);\nstatic void GL_FramebufferTexture(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level);\nstatic void GL_GenFramebuffers(GLsizei n, GLuint* buffers);\nstatic void GL_FramebufferBlitSimple(framebuffer_id source_id, framebuffer_id destination_id);\n\ntypedef struct framebuffer_data_s {\n\tGLuint glref;\n\ttexture_ref texture[fbtex_count];\n\tGLuint depthBuffer;\n\tGLenum depthFormat;\n\n\tGLsizei width;\n\tGLsizei height;\n\tGLenum status;\n\tint samples;\n} framebuffer_data_t;\n\nstatic const char* framebuffer_names[] = {\n\t\"none\", // framebuffer_none\n\t\"std\", // framebuffer_std,\n\t\"std-ms\", // framebuffer_std_ms,\n\t\"hud\", // framebuffer_hud\n\t\"hud-ms\", // framebuffer_hud_ms,\n\t\"std-blit\", // framebuffer_std_blit\n\t\"std-blit-ms\", // framebuffer_std_blit_ms\n\t\"hud-blit\", // framebuffer_hud_blit\n\t\"hud-blit-ms\", // framebuffer_hud_blit_ms\n};\nstatic const char* framebuffer_texture_names[] = {\n\t\"std\", // fbtex_standard,\n\t\"depth\", // fbtex_depth\n\t\"env\", // fbtex_bloom,\n\t\"norms\", // fbtex_worldnormals,\n};\nstatic qbool framebuffer_depth_buffer[] = {\n\tfalse, // framebuffer_none\n\ttrue, // framebuffer_std\n\ttrue, // framebuffer_std_ms\n\tfalse, // framebuffer_hud\n\tfalse, // framebuffer_hud_ms\n\tfalse, // framebuffer_std_blit\n\tfalse, // framebuffer_std_blit_ms\n\tfalse, // framebuffer_hud_blit\n\tfalse, // framebuffer_hud_blit_ms\n};\nstatic qbool framebuffer_hdr[] = {\n\tfalse, // framebuffer_none\n\ttrue, // framebuffer_std\n\ttrue, // framebuffer_std_ms\n\tfalse, // framebuffer_hud\n\tfalse, // framebuffer_hud_ms\n\tfalse, // framebuffer_std_blit\n\tfalse, // framebuffer_std_blit_ms\n\tfalse, // framebuffer_hud_blit\n\tfalse, // framebuffer_hud_blit_ms\n};\nstatic qbool framebuffer_alpha[] = {\n\tfalse, // framebuffer_none\n\tfalse, // framebuffer_std\n\tfalse, // framebuffer_std_ms\n\ttrue, // framebuffer_hud\n\ttrue, // framebuffer_hud_ms\n\tfalse, // framebuffer_std_blit\n\tfalse, // framebuffer_std_blit_ms\n\tfalse, // framebuffer_hud_blit\n\tfalse, // framebuffer_hud_blit_ms\n};\nstatic qbool framebuffer_multisampled[] = {\n\tfalse, // framebuffer_none\n\tfalse, // framebuffer_std\n\ttrue, // framebuffer_std_ms\n\tfalse, // framebuffer_hud\n\ttrue, // framebuffer_hud_ms\n\tfalse, // framebuffer_std_blit\n\ttrue, // framebuffer_std_blit_ms\n\tfalse, // framebuffer_hud_blit\n\ttrue, // framebuffer_hud_blit_ms\n};\nstatic framebuffer_id framebuffer_multisample_alternate[] = {\n\tframebuffer_none,\n\t// rendering\n\tframebuffer_std_ms,\n\tframebuffer_std_ms,\n\tframebuffer_hud_ms,\n\tframebuffer_hud_ms,\n\t// framebuffers used to resolve multisampling\n\tframebuffer_none,\n\tframebuffer_none,\n\tframebuffer_none,\n\tframebuffer_none,\n};\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(framebuffer_names) / sizeof(framebuffer_names[0]) == framebuffer_count);\nC_ASSERT(sizeof(framebuffer_texture_names) / sizeof(framebuffer_texture_names[0]) == fbtex_count);\nC_ASSERT(sizeof(framebuffer_depth_buffer) / sizeof(framebuffer_depth_buffer[0]) == framebuffer_count);\nC_ASSERT(sizeof(framebuffer_multisampled) / sizeof(framebuffer_multisampled[0]) == framebuffer_count);\nC_ASSERT(sizeof(framebuffer_alpha) / sizeof(framebuffer_alpha[0]) == framebuffer_count);\nC_ASSERT(sizeof(framebuffer_hdr) / sizeof(framebuffer_hdr[0]) == framebuffer_count);\nC_ASSERT(sizeof(framebuffer_multisample_alternate) / sizeof(framebuffer_multisample_alternate[0]) == framebuffer_count);\n#endif\n\nstatic framebuffer_data_t framebuffer_data[framebuffer_count];\n\n//\nGL_StaticProcedureDeclaration(glGenFramebuffers, \"n=%d, ids=%p\", GLsizei n, GLuint* ids)\nGL_StaticProcedureDeclaration(glDeleteFramebuffers, \"n=%d, ids=%p\", GLsizei n, GLuint* ids)\nGL_StaticProcedureDeclaration(glBindFramebuffer, \"target=%u, framebuffer=%u\", GLenum target, GLuint framebuffer)\n\nGL_StaticProcedureDeclaration(glGenRenderbuffers, \"n=%d, ids=%p\", GLsizei n, GLuint* ids)\nGL_StaticProcedureDeclaration(glDeleteRenderbuffers, \"n=%d, ids=%p\", GLsizei n, GLuint* ids)\nGL_StaticProcedureDeclaration(glBindRenderbuffer, \"target=%u, renderbuffer=%u\", GLenum target, GLuint renderbuffer)\nGL_StaticProcedureDeclaration(glRenderbufferStorage, \"target=%u, internalformat=%u, width=%d, height=%d\", GLenum target, GLenum internalformat, GLsizei width, GLsizei height)\nGL_StaticProcedureDeclaration(glNamedRenderbufferStorage, \"renderbuffer=%u, internalformat=%u, width=%d, height=%d\", GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height)\nGL_StaticProcedureDeclaration(glFramebufferRenderbuffer, \"target=%u, attachment=%u, renderbuffertarget=%u, renderbuffer=%u\", GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)\nGL_StaticProcedureDeclaration(glNamedFramebufferRenderbuffer, \"framebuffer=%u, attachment=%u, renderbuffertarget=%u, renderbuffer=%u\", GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)\n\nGL_StaticProcedureDeclaration(glFramebufferTexture, \"target=%u, attachment=%u, texture=%u, level=%d\", GLenum target, GLenum attachment, GLuint texture, GLint level);\nGL_StaticProcedureDeclaration(glNamedFramebufferTexture, \"framebuffer=%u, attachment=%u, texture=%u, level=%d\", GLuint framebuffer, GLenum attachment, GLuint texture, GLint level)\nGL_StaticFunctionDeclaration(glCheckFramebufferStatus, \"target=%u\", \"result=%u\", GLenum, GLenum target)\nGL_StaticFunctionWrapperBody(glCheckFramebufferStatus, GLenum, target)\nGL_StaticFunctionDeclaration(glCheckNamedFramebufferStatus, \"framebuffer=%u, target=%u\", \"result=%u\", GLenum, GLuint framebuffer, GLenum target)\nGL_StaticFunctionWrapperBody(glCheckNamedFramebufferStatus, GLenum, framebuffer, target)\nGL_StaticProcedureDeclaration(glBlitFramebuffer, \"srcX0=%d, srcY0=%d, srcX1=%d, srcY1=%d, dstX0=%d, dstY0=%d, dstX1=%d, dstY1=%d, mask=%u, filter=%u\", GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)\nGL_StaticProcedureDeclaration(glBlitNamedFramebuffer, \"readFrameBuffer=%u, drawFramebuffer=%u, srcX0=%d, srcY0=%d, srcX1=%d, srcY1=%d, dstX0=%d, dstY0=%d, dstX1=%d, dstY1=%d, mask=%u, filter=%u\", GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)\nGL_StaticProcedureDeclaration(glDrawBuffers, \"n=%d, bufs=%p\", GLsizei n, const GLenum* bufs)\nGL_StaticProcedureDeclaration(glClearBufferfv, \"buffer=%u, drawbuffer=%d, value=%p\", GLenum buffer, GLint drawbuffer, const GLfloat* value)\nGL_StaticProcedureDeclaration(glClipControl, \"origin=%u, depth=%u\", GLenum origin, GLenum depth)\n\n// Multi-sampled\nGL_StaticProcedureDeclaration(glRenderbufferStorageMultisample, \"target=%x, samples=%d, internalformat=%x, width=%d, height=%d\", GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)\nGL_StaticProcedureDeclaration(glNamedRenderbufferStorageMultisample, \"renderbuffer=%x, samples=%d, internalformat=%x, width=%d, height=%d\", GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)\n\nstatic GLenum glDepthFormats[] = { 0, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT32F };\ntypedef enum { r_depthformat_best, r_depthformat_16bit, r_depthformat_24bit, r_depthformat_32bit, r_depthformat_32bit_float, r_depthformat_count } r_depthformat;\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(framebuffer_names) / sizeof(framebuffer_names[0]) == framebuffer_count);\nC_ASSERT(sizeof(framebuffer_texture_names) / sizeof(framebuffer_texture_names[0]) == fbtex_count);\nC_ASSERT(sizeof(framebuffer_depth_buffer) / sizeof(framebuffer_depth_buffer[0]) == framebuffer_count);\nC_ASSERT(sizeof(glDepthFormats) / sizeof(glDepthFormats[0]) == r_depthformat_count);\n#endif\n\n//\n// Initialize framebuffer stuff, Loads procaddresses and such.\n//\nvoid GL_InitialiseFramebufferHandling(void)\n{\n\tqbool framebuffers_supported = true;\n\n\tglConfig.supported_features &= ~(R_SUPPORT_FRAMEBUFFERS | R_SUPPORT_FRAMEBUFFERS_BLIT);\n\n\tif (GL_VersionAtLeast(3,0) || SDL_GL_ExtensionSupported(\"GL_ARB_framebuffer_object\")) {\n\t\tGL_LoadMandatoryFunction(glGenFramebuffers, framebuffers_supported);\n\t\tGL_LoadMandatoryFunction(glDeleteFramebuffers, framebuffers_supported);\n\t\tGL_LoadMandatoryFunction(glBindFramebuffer, framebuffers_supported);\n\n\t\tGL_LoadMandatoryFunction(glGenRenderbuffers, framebuffers_supported);\n\t\tGL_LoadMandatoryFunction(glDeleteRenderbuffers, framebuffers_supported);\n\t\tGL_LoadMandatoryFunction(glBindRenderbuffer, framebuffers_supported);\n\t\tGL_LoadMandatoryFunction(glRenderbufferStorage, framebuffers_supported);\n\t\tGL_LoadMandatoryFunction(glFramebufferRenderbuffer, framebuffers_supported);\n\t\tGL_LoadMandatoryFunction(glBlitFramebuffer, framebuffers_supported);\n\n\t\tGL_LoadMandatoryFunction(glFramebufferTexture, framebuffers_supported);\n\t\tGL_LoadMandatoryFunction(glCheckFramebufferStatus, framebuffers_supported);\n\t\tGL_LoadMandatoryFunction(glDrawBuffers, framebuffers_supported);\n\t\tGL_LoadMandatoryFunction(glClearBufferfv, framebuffers_supported);\n\n\t\tGL_LoadOptionalFunction(glRenderbufferStorageMultisample);\n\n\t\tglConfig.supported_features |= (framebuffers_supported ? (R_SUPPORT_FRAMEBUFFERS | R_SUPPORT_FRAMEBUFFERS_BLIT) : 0);\n\t\tglConfig.supported_features |= (framebuffers_supported && GL_Available(glRenderbufferStorageMultisample) ? R_SUPPORT_FRAMEBUFFER_MS : 0);\n\t\tif (GL_VersionAtLeast(3, 0) || SDL_GL_ExtensionSupported(\"GL_ARB_depth_buffer_float\")) {\n\t\t\tglConfig.supported_features |= R_SUPPORT_DEPTH32F;\n\t\t}\n\t}\n\telse if (GL_VersionAtLeast(3, 0) || (SDL_GL_ExtensionSupported(\"GL_EXT_framebuffer_object\") && SDL_GL_ExtensionSupported(\"GL_ARB_depth_texture\"))) {\n\t\tGL_LoadMandatoryFunctionEXT(glGenFramebuffers, framebuffers_supported);\n\t\tGL_LoadMandatoryFunctionEXT(glDeleteFramebuffers, framebuffers_supported);\n\t\tGL_LoadMandatoryFunctionEXT(glBindFramebuffer, framebuffers_supported);\n\n\t\tGL_LoadMandatoryFunctionEXT(glGenRenderbuffers, framebuffers_supported);\n\t\tGL_LoadMandatoryFunctionEXT(glDeleteRenderbuffers, framebuffers_supported);\n\t\tGL_LoadMandatoryFunctionEXT(glBindRenderbuffer, framebuffers_supported);\n\t\tGL_LoadMandatoryFunctionEXT(glRenderbufferStorage, framebuffers_supported);\n\t\tGL_LoadMandatoryFunctionEXT(glFramebufferRenderbuffer, framebuffers_supported);\n\t\tif (GL_VersionAtLeast(3, 0) || SDL_GL_ExtensionSupported(\"GL_EXT_framebuffer_blit\")) {\n\t\t\tGL_LoadOptionalFunctionEXT(glBlitFramebuffer);\n\t\t}\n\t\tGL_LoadMandatoryFunctionEXT(glDrawBuffers, framebuffers_supported);\n\n\t\tGL_LoadMandatoryFunctionEXT(glFramebufferTexture, framebuffers_supported);\n\t\tGL_LoadMandatoryFunctionEXT(glCheckFramebufferStatus, framebuffers_supported);\n\t\tGL_LoadMandatoryFunctionEXT(glClearBufferfv, framebuffers_supported);\n\n\t\tglConfig.supported_features |= (framebuffers_supported ? (R_SUPPORT_FRAMEBUFFERS) : 0);\n\t\tglConfig.supported_features |= (framebuffers_supported && GL_Available(glBlitFramebuffer) ? (R_SUPPORT_FRAMEBUFFERS_BLIT) : 0);\n\t\tglConfig.supported_features |= GL_VersionAtLeast(3, 0) || SDL_GL_ExtensionSupported(\"GL_ARB_depth_buffer_float\") ? R_SUPPORT_DEPTH32F : 0;\n\t}\n\n\tglConfig.supported_features |= GL_VersionAtLeast(3, 0) || SDL_GL_ExtensionSupported(\"GL_ARB_framebuffer_sRGB\") ? R_SUPPORT_FRAMEBUFFERS_SRGB : 0;\n\n\tif (GL_UseDirectStateAccess()) {\n\t\tGL_LoadOptionalFunction(glNamedRenderbufferStorage);\n\t\tGL_LoadOptionalFunction(glNamedFramebufferRenderbuffer);\n\t\tGL_LoadOptionalFunction(glBlitNamedFramebuffer);\n\t\tGL_LoadOptionalFunction(glNamedFramebufferTexture);\n\t\tGL_LoadOptionalFunction(glCheckNamedFramebufferStatus);\n\t\tGL_LoadOptionalFunction(glNamedRenderbufferStorageMultisample);\n\t}\n\n\t// meag: disabled (classic needs glFrustum replaced, modern needs non-rubbish viewweapon\n\t//                 depth hack, and near-plane clipping issues when the player is gibbed)\n\t/*if (GL_VersionAtLeast(4, 5) || SDL_GL_ExtensionSupported(\"GL_ARB_clip_control\")) {\n\t\tGL_LoadOptionalFunction(glClipControl);\n\t}*/\n\n\tmemset(framebuffer_data, 0, sizeof(framebuffer_data));\n}\n\nvoid GL_FramebufferSetFiltering(qbool linear)\n{\n\ttexture_ref tex = framebuffer_data[framebuffer_std].texture[fbtex_standard];\n\n\tif (R_TextureReferenceIsValid(tex)) {\n\t\ttexture_minification_id min_filter = linear ? texture_minification_linear : texture_minification_nearest;\n\t\ttexture_magnification_id mag_filter = linear ? texture_magnification_linear : texture_magnification_nearest;\n\n\t\trenderer.TextureSetFiltering(tex, min_filter, mag_filter);\n\t}\n}\n\nstatic qbool GL_FramebufferCreateRenderingTexture(framebuffer_data_t* fb, fbtex_id tex_id, r_texture_type_id texture_type, GLenum framebuffer_format, const char* label, int width, int height)\n{\n\tGL_CreateTexturesWithIdentifier(texture_type, 1, &fb->texture[tex_id], label);\n\tGL_TexStorage2D(fb->texture[tex_id], 1, framebuffer_format, width, height, false);\n\trenderer.TextureLabelSet(fb->texture[tex_id], label);\n\trenderer.TextureWrapModeClamp(fb->texture[tex_id]);\n\trenderer.TextureSetFiltering(fb->texture[tex_id], texture_minification_nearest, texture_magnification_nearest);\n\tR_TextureSetFlag(fb->texture[tex_id], R_TextureGetFlag(fb->texture[tex_id]) | TEX_NO_TEXTUREMODE);\n\treturn true;\n}\n\nqbool GL_FramebufferCreate(framebuffer_id id, int width, int height)\n{\n\tframebuffer_data_t* fb = NULL;\n\tchar label[128];\n\tqbool hdr = (vid_framebuffer_hdr.integer && GL_VersionAtLeast(3, 0) && framebuffer_hdr[id]);\n\tGLenum framebuffer_format;\n\n\tif (!GL_Supported(R_SUPPORT_FRAMEBUFFERS)) {\n\t\treturn false;\n\t}\n\n\tif (hdr) {\n\t\tframebuffer_format = GL_RGB16F;\n\t}\n\telse if (vid_gammacorrection.integer) {\n\t\tif (GL_Supported(R_SUPPORT_FRAMEBUFFERS_SRGB)) {\n\t\t\tframebuffer_format = framebuffer_alpha[id] ? GL_SRGB8_ALPHA8 : GL_SRGB8;\n\t\t}\n\t\telse {\n\t\t\tCon_Printf(\"sRGB framebuffers are not supported on your hardware.\\n\");\n\t\t\tCvar_LatchedSetValue(&vid_framebuffer, 0);\n\t\t\treturn false;\n\t\t}\n\t}\n\telse {\n\t\tframebuffer_format = framebuffer_alpha[id] ? GL_RGBA8 : GL_RGB8;\n\t}\n\n\tfb = &framebuffer_data[id];\n\tif (fb->glref) {\n\t\treturn false;\n\t}\n\n\tmemset(fb, 0, sizeof(*fb));\n\n\t// Multi-sampling round up to power of 2\n\tif (framebuffer_multisampled[id]) {\n\t\tQ_ROUND_POWER2(vid_framebuffer_multisample.integer, fb->samples);\n\t\tfb->samples = bound(0, fb->samples, glConfig.max_multisampling_level);\n\t\tif (vid_framebuffer_multisample.integer != fb->samples) {\n\t\t\tCvar_SetValue(&vid_framebuffer_multisample, fb->samples);\n\t\t\tCon_Printf(\"&cff0vid_framebuffer_multisample&r has been set to %d\\n\", fb->samples);\n\t\t}\n\t}\n\n\t// Render to texture\n\tstrlcpy(label, framebuffer_names[id], sizeof(label));\n\tstrlcat(label, \"/\", sizeof(label));\n\tstrlcat(label, framebuffer_texture_names[fbtex_standard], sizeof(label));\n\n\t// Create standard rendering texture\n\tGL_FramebufferCreateRenderingTexture(fb, fbtex_standard, texture_type_2d, framebuffer_format, label, width, height);\n\n\t// Create multi-sampled texture\n\tif (fb->samples) {\n\t\tGL_CreateTexturesWithIdentifier(texture_type_2d_multisampled, 1, &fb->texture[fbtex_standard], label);\n\t\tGL_TexStorage2DMultisample(fb->texture[fbtex_standard], fb->samples, framebuffer_format, width, height, EZ_USE_FIXED_SAMPLE_LOCATIONS);\n\t}\n\n\t// Set linear filtering if requested\n\tGL_FramebufferSetFiltering(vid_framebuffer_smooth.integer);\n\n\t// Create frame buffer with texture & depth\n\tGL_GenFramebuffers(1, &fb->glref);\n\tGL_TraceObjectLabelSet(GL_FRAMEBUFFER, fb->glref, -1, framebuffer_names[id]);\n\n\t// Depth buffer\n\tif (framebuffer_depth_buffer[id]) {\n\t\tGLenum depthFormat = glDepthFormats[bound(0, vid_framebuffer_depthformat.integer, r_depthformat_count - 1)];\n\t\tif (depthFormat == 0) {\n\t\t\tdepthFormat = GL_Available(glClipControl) ? GL_DEPTH_COMPONENT32F : GL_DEPTH_COMPONENT32;\n\t\t}\n\t\tif (depthFormat == GL_DEPTH_COMPONENT32F && !GL_Supported(R_SUPPORT_DEPTH32F)) {\n\t\t\tdepthFormat = GL_DEPTH_COMPONENT32;\n\t\t}\n\n#ifdef GL_USE_RENDER_BUFFERS\n\t\tGL_GenRenderBuffers(1, &fb->depthBuffer);\n\t\tGL_TraceObjectLabelSet(GL_RENDERBUFFER, fb->depthBuffer, -1, \"depth-buffer\");\n\t\tif (fb->samples) {\n\t\t\tGL_RenderBufferStorageMultisample(fb->depthBuffer, fb->samples, fb->depthFormat = depthFormat, width, height);\n\t\t\tGL_FramebufferRenderbuffer(fb->glref, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fb->depthBuffer);\n\t\t}\n\t\telse {\n\t\t\tGL_RenderBufferStorage(fb->depthBuffer, fb->depthFormat = depthFormat, width, height);\n\t\t\tGL_FramebufferRenderbuffer(fb->glref, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fb->depthBuffer);\n\t\t}\n#else\n\t\t// create depth texture\n\t\tstrlcpy(label, framebuffer_names[id], sizeof(label));\n\t\tstrlcat(label, \"/\", sizeof(label));\n\t\tstrlcat(label, framebuffer_texture_names[fbtex_depth], sizeof(label));\n\n\t\tGL_CreateTexturesWithIdentifier(texture_type_2d, 1, &fb->texture[fbtex_depth], label);\n\t\tGL_TexStorage2D(fb->texture[fbtex_depth], 1, depthFormat, width, height, false);\n\t\trenderer.TextureSetFiltering(fb->texture[fbtex_depth], min_filter, mag_filter);\n\t\trenderer.TextureWrapModeClamp(fb->texture[fbtex_depth]);\n\n\t\tGL_FramebufferTexture(fb->glref, GL_DEPTH_ATTACHMENT, GL_TextureNameFromReference(fb->texture[fbtex_depth]), 0);\n#endif\n\n\t\tif (GL_Available(glClipControl)) {\n\t\t\tif (depthFormat == GL_DEPTH_COMPONENT32F) {\n\t\t\t\tGL_Procedure(glClipControl, GL_LOWER_LEFT, GL_ZERO_TO_ONE);\n\t\t\t\tglClearDepth(0.0f);\n\t\t\t\tglConfig.reversed_depth = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tGL_Procedure(glClipControl, GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE);\n\t\t\t\tglClearDepth(1.0f);\n\t\t\t\tglConfig.reversed_depth = false;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tglConfig.reversed_depth = false;\n\t\t\tglClearDepth(1.0f);\n\t\t}\n\t}\n\n\tGL_FramebufferTexture(fb->glref, GL_COLOR_ATTACHMENT0, GL_TextureNameFromReference(fb->texture[fbtex_standard]), 0);\n\n\tfb->status = GL_CheckFramebufferStatus(fb->glref);\n\tfb->width = width;\n\tfb->height = height;\n\n\tGL_Procedure(glBindFramebuffer, GL_FRAMEBUFFER, 0);\n\n\tswitch (fb->status) {\n\tcase GL_FRAMEBUFFER_COMPLETE:\n\t\treturn true;\n\tcase GL_FRAMEBUFFER_UNDEFINED:\n\t\tCon_Printf(\"&cf00error&r: the specified framebuffer is the default read or draw framebuffer, but the default framebuffer does not exist\\n\");\n\t\tbreak;\n\tcase GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:\n\t\tCon_Printf(\"&cf00error&r: one of the framebuffer attachment points is framebuffer incomplete\\n\");\n\t\tbreak;\n\tcase GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:\n\t\tCon_Printf(\"&cf00error&r: framebuffer does not have at least one image attached\\n\");\n\t\tbreak;\n\tcase GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:\n\t\tCon_Printf(\"&cf00error&r: incomplete draw buffer\\n\");\n\t\tbreak;\n\tcase GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:\n\t\tCon_Printf(\"&cf00error&r: incomplete read buffer\\n\");\n\t\tbreak;\n\tcase GL_FRAMEBUFFER_UNSUPPORTED:\n\t\tCon_Printf(\"&cf00error&r: combination of formats is not supported\\n\");\n\t\tbreak;\n\tcase GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:\n\t\tCon_Printf(\"&cf00error&r: mixture of renderbuffer/texture sampling levels\\n\");\n\t\tbreak;\n\tcase GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:\n\t\tCon_Printf(\"&cf00error&r: incomplete layer targets\\n\");\n\t\tbreak;\n\t}\n\n\t// Rollback, delete objects\n\tGL_FramebufferDelete(id);\n\treturn false;\n}\n\nqbool GL_FramebufferStartWorldNormals(framebuffer_id id)\n{\n\tframebuffer_data_t* fb = NULL;\n\tGLenum buffers[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };\n\tfloat clearValue[] = { 0.0f, 0.0f, 0.0f, 0.0f };\n\n\tif (!(gl_outline.integer & 2) || !GL_Supported(R_SUPPORT_FRAMEBUFFERS) || !RuleSets_AllowEdgeOutline()) {\n\t\treturn false;\n\t}\n\n\tid = VID_MultisampledAlternateId(id);\n\tfb = &framebuffer_data[id];\n\tif (!fb->glref) {\n\t\treturn false;\n\t}\n\n\tif (!R_TextureReferenceIsValid(fb->texture[fbtex_worldnormals])) {\n\t\tchar label[128];\n\n\t\t// normals\n\t\tstrlcpy(label, framebuffer_names[id], sizeof(label));\n\t\tstrlcat(label, \"/\", sizeof(label));\n\t\tstrlcat(label, framebuffer_texture_names[fbtex_worldnormals], sizeof(label));\n\n\t\tif (fb->samples) {\n\t\t\tGL_CreateTexturesWithIdentifier(texture_type_2d_multisampled, 1, &fb->texture[fbtex_worldnormals], label);\n\t\t\tif (!R_TextureReferenceIsValid(fb->texture[fbtex_worldnormals])) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tGL_TexStorage2DMultisample(fb->texture[fbtex_worldnormals], fb->samples, GL_RGBA16F, fb->width, fb->height, EZ_USE_FIXED_SAMPLE_LOCATIONS);\n\t\t}\n\t\telse {\n\t\t\tGL_CreateTexturesWithIdentifier(texture_type_2d, 1, &fb->texture[fbtex_worldnormals], label);\n\t\t\tif (!R_TextureReferenceIsValid(fb->texture[fbtex_worldnormals])) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tGL_TexStorage2D(fb->texture[fbtex_worldnormals], 1, GL_RGBA16F, fb->width, fb->height, false);\n\t\t\trenderer.TextureSetFiltering(fb->texture[fbtex_worldnormals], texture_minification_nearest, texture_magnification_nearest);\n\t\t\trenderer.TextureWrapModeClamp(fb->texture[fbtex_worldnormals]);\n\t\t}\n\t\tR_TextureSetFlag(fb->texture[fbtex_worldnormals], R_TextureGetFlag(fb->texture[fbtex_worldnormals]) | TEX_NO_TEXTUREMODE);\n\t}\n\n\tGL_FramebufferTexture(fb->glref, GL_COLOR_ATTACHMENT1, GL_TextureNameFromReference(fb->texture[fbtex_worldnormals]), 0);\n\tGL_Procedure(glDrawBuffers, 2, buffers);\n\tGL_Procedure(glClearBufferfv, GL_COLOR, 1, clearValue);\n\treturn true;\n}\n\nstatic void GL_MultiSamplingResolve(framebuffer_id src, framebuffer_id target, fbtex_id texture, framebuffer_id temp_src, framebuffer_id temp_dst)\n{\n\t// Copy from (multi-sampled) src[texture] => (non-multi-sampled) target[texture]\n\tframebuffer_data_t* fb_orig_src = &framebuffer_data[src];\n\tframebuffer_data_t* fb_orig_dst = &framebuffer_data[target];\n\tframebuffer_data_t* fb_blit_src = &framebuffer_data[temp_src];\n\tframebuffer_data_t* fb_blit_dst = &framebuffer_data[temp_dst];\n\n\tif (!vid_framebuffer_multisample.integer || !fb_orig_src->glref || !fb_orig_dst->glref) {\n\t\t// nothing to do\n\t\treturn;\n\t}\n\n\tif (temp_src) {\n\t\tif (fb_blit_src->width != fb_orig_src->width || fb_blit_src->height != fb_orig_src->height) {\n\t\t\tGL_FramebufferDelete(temp_src);\n\t\t}\n\t\tif (!fb_blit_src->glref && !GL_FramebufferCreate(temp_src, fb_orig_src->width, fb_orig_src->height)) {\n\t\t\treturn;\n\t\t}\n\t}\n\tif (temp_dst) {\n\t\tif (fb_blit_dst->width != fb_orig_dst->width || fb_blit_src->height != fb_orig_dst->height) {\n\t\t\tGL_FramebufferDelete(temp_dst);\n\t\t}\n\t\tif (!fb_blit_dst->glref && !GL_FramebufferCreate(temp_dst, fb_orig_dst->width, fb_orig_dst->height)) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\tGL_FramebufferTexture(fb_blit_src->glref, GL_COLOR_ATTACHMENT0, GL_TextureNameFromReference(fb_orig_src->texture[texture]), 0);\n\tGL_FramebufferTexture(fb_blit_dst->glref, GL_COLOR_ATTACHMENT0, GL_TextureNameFromReference(fb_orig_dst->texture[texture]), 0);\n\n\tGL_FramebufferBlitSimple(temp_src, temp_dst);\n\n\tGL_FramebufferTexture(fb_blit_src->glref, GL_COLOR_ATTACHMENT0, 0, 0);\n\tGL_FramebufferTexture(fb_blit_dst->glref, GL_COLOR_ATTACHMENT0, 0, 0);\n}\n\nqbool GL_FramebufferEndWorldNormals(framebuffer_id id)\n{\n\tframebuffer_data_t* fb = NULL;\n\tGLenum buffer = GL_COLOR_ATTACHMENT0;\n\n\tif (!GL_Supported(R_SUPPORT_FRAMEBUFFERS)) {\n\t\treturn false;\n\t}\n\n\tid = VID_MultisampledAlternateId(id);\n\tfb = &framebuffer_data[id];\n\tif (!fb->glref) {\n\t\treturn false;\n\t}\n\n\tGL_FramebufferTexture(fb->glref, GL_COLOR_ATTACHMENT1, 0, 0);\n\tGL_Procedure(glDrawBuffers, 1, &buffer);\n\n\tif (fb->samples && id == framebuffer_std_ms) {\n\t\t// Resolve multi-samples\n\t\tGL_MultiSamplingResolve(framebuffer_std_ms, framebuffer_std, fbtex_worldnormals, framebuffer_std_blit_ms, framebuffer_std_blit);\n\t}\n\treturn true;\n}\n\nvoid GL_FramebufferDelete(framebuffer_id id)\n{\n\tint i;\n\tframebuffer_data_t* fb = &framebuffer_data[id];\n\n\tif (id != framebuffer_none && fb->glref) {\n\t\tif (fb->depthBuffer) {\n\t\t\tGL_Procedure(glDeleteRenderbuffers, 1, &fb->depthBuffer);\n\t\t}\n\t\tfor (i = 0; i < sizeof(fb->texture) / sizeof(fb->texture[0]); ++i) {\n\t\t\tif (R_TextureReferenceIsValid(fb->texture[i])) {\n\t\t\t\tR_DeleteTexture(&fb->texture[i]);\n\t\t\t}\n\t\t}\n\t\tif (fb->glref) {\n\t\t\tGL_Procedure(glDeleteFramebuffers, 1, &fb->glref);\n\t\t}\n\n\t\tmemset(fb, 0, sizeof(*fb));\n\t}\n}\n\nvoid GL_FramebufferStartUsing(framebuffer_id id)\n{\n\tGL_Procedure(glBindFramebuffer, GL_FRAMEBUFFER, framebuffer_data[id].glref);\n}\n\nvoid GL_FramebufferStartUsingScreen(void)\n{\n\t// resolve any multi-sampling\n\tGL_MultiSamplingResolve(framebuffer_std_ms, framebuffer_std, fbtex_standard, framebuffer_std_blit_ms, framebuffer_std_blit);\n\tGL_MultiSamplingResolve(framebuffer_hud_ms, framebuffer_hud, fbtex_standard, framebuffer_hud_blit_ms, framebuffer_hud_blit);\n\n\tGL_Procedure(glBindFramebuffer, GL_FRAMEBUFFER, 0);\n}\n\ntexture_ref GL_FramebufferTextureReference(framebuffer_id id, fbtex_id tex_id)\n{\n\treturn framebuffer_data[id].texture[tex_id];\n}\n\n// OpenGL wrapper functions\n#ifdef GL_USE_RENDER_BUFFERS\nstatic void GL_RenderBufferStorage(GLuint renderBuffer, GLenum internalformat, GLsizei width, GLsizei height)\n{\n\tif (GL_Available(glNamedRenderbufferStorage)) {\n\t\tGL_Procedure(glNamedRenderbufferStorage, renderBuffer, internalformat, width, height);\n\t}\n\telse if (GL_Available(glBindRenderbuffer) && GL_Available(glRenderbufferStorage)) {\n\t\tGL_Procedure(glBindRenderbuffer, GL_RENDERBUFFER, renderBuffer);\n\t\tGL_Procedure(glRenderbufferStorage, GL_RENDERBUFFER, internalformat, width, height);\n\t}\n\telse {\n\t\tSys_Error(\"ERROR: %s called without driver support\", __func__);\n\t}\n}\n\nstatic void GL_RenderBufferStorageMultisample(GLuint renderBuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)\n{\n\tif (GL_Available(glNamedRenderbufferStorageMultisample)) {\n\t\tGL_Procedure(glNamedRenderbufferStorageMultisample, renderBuffer, samples, internalformat, width, height);\n\t}\n\telse if (GL_Available(glBindRenderbuffer) && GL_Available(glRenderbufferStorageMultisample)) {\n\t\tGL_Procedure(glBindRenderbuffer, GL_RENDERBUFFER, renderBuffer);\n\t\tGL_Procedure(glRenderbufferStorageMultisample, GL_RENDERBUFFER, samples, internalformat, width, height);\n\t}\n\telse {\n\t\tSys_Error(\"ERROR: %s called without driver support\", __func__);\n\t}\n}\n\nstatic void GL_FramebufferRenderbuffer(GLuint fbref, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)\n{\n\tif (GL_Available(glNamedFramebufferRenderbuffer)) {\n\t\tGL_Procedure(glNamedFramebufferRenderbuffer, fbref, attachment, renderbuffertarget, renderbuffer);\n\t}\n\telse if (GL_Available(glBindFramebuffer) && GL_Available(glFramebufferRenderbuffer)) {\n\t\tGL_Procedure(glBindFramebuffer, GL_FRAMEBUFFER, fbref);\n\t\tGL_Procedure(glFramebufferRenderbuffer, GL_FRAMEBUFFER, attachment, renderbuffertarget, renderbuffer);\n\t}\n\telse {\n\t\tSys_Error(\"ERROR: %s called without driver support\", __func__);\n\t}\n}\n\nstatic void GL_GenRenderBuffers(GLsizei n, GLuint* buffers)\n{\n\tint i;\n\n\tGL_Procedure(glGenRenderbuffers, n, buffers);\n\n\tfor (i = 0; i < n; ++i) {\n\t\tGL_Procedure(glBindRenderbuffer, GL_RENDERBUFFER, buffers[i]);\n\t}\n}\n#endif\n\nstatic GLenum GL_CheckFramebufferStatus(GLuint fbref)\n{\n\tif (GL_Available(glCheckNamedFramebufferStatus)) {\n\t\treturn GL_Function(glCheckNamedFramebufferStatus, fbref, GL_FRAMEBUFFER);\n\t}\n\telse if (GL_Available(glBindFramebuffer) && GL_Available(glCheckFramebufferStatus)) {\n\t\tGL_Procedure(glBindFramebuffer, GL_FRAMEBUFFER, fbref);\n\t\treturn GL_Function(glCheckFramebufferStatus, GL_FRAMEBUFFER);\n\t}\n\telse {\n\t\tSys_Error(\"ERROR: %s called without driver support\", __func__);\n\t\treturn 0;\n\t}\n}\n\nstatic void GL_FramebufferTexture(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level)\n{\n\tif (GL_Available(glNamedFramebufferTexture)) {\n\t\tGL_Procedure(glNamedFramebufferTexture, framebuffer, attachment, texture, level);\n\t}\n\telse if (GL_Available(glBindFramebuffer) && GL_Available(glFramebufferTexture)) {\n\t\tGL_Procedure(glBindFramebuffer, GL_FRAMEBUFFER, framebuffer);\n\t\tGL_Procedure(glFramebufferTexture, GL_FRAMEBUFFER, attachment, texture, level);\n\t}\n\telse {\n\t\tSys_Error(\"ERROR: %s called without driver support\", __func__);\n\t}\n}\n\nstatic void GL_GenFramebuffers(GLsizei n, GLuint* buffers)\n{\n\tint i;\n\n\tGL_Procedure(glGenFramebuffers, n, buffers);\n\tfor (i = 0; i < n; ++i) {\n\t\tGL_Procedure(glBindFramebuffer, GL_FRAMEBUFFER, buffers[i]);\n\t}\n}\n\nint GL_FrameBufferWidth(framebuffer_id id)\n{\n\treturn framebuffer_data[id].width;\n}\n\nint GL_FrameBufferHeight(framebuffer_id id)\n{\n\treturn framebuffer_data[id].height;\n}\n\nstatic void GL_FramebufferBlitSimple(framebuffer_id source_id, framebuffer_id destination_id)\n{\n\tframebuffer_data_t* src = &framebuffer_data[source_id];\n\tframebuffer_data_t* dest = &framebuffer_data[destination_id];\n\tGLint srcTL[2] = { 0, 0 };\n\tGLint srcSize[2];\n\tGLint destTL[2] = { 0, 0 };\n\tGLint destSize[2];\n\tGLenum filter = GL_NEAREST;\n\n\tsrcSize[0] = src->glref ? src->width : glConfig.vidWidth;\n\tsrcSize[1] = src->glref ? src->height : glConfig.vidHeight;\n\tdestSize[0] = destination_id ? dest->width : glConfig.vidWidth;\n\tdestSize[1] = destination_id ? dest->height : glConfig.vidHeight;\n\tif (srcSize[0] != destSize[0] || srcSize[1] != destSize[1]) {\n\t\tfilter = GL_LINEAR;\n\t}\n\n\tif (GL_Available(glBlitNamedFramebuffer)) {\n\t\tGL_Procedure(\n\t\t\tglBlitNamedFramebuffer, src->glref, dest->glref,\n\t\t\tsrcTL[0], srcTL[1], srcTL[0] + srcSize[0], srcTL[1] + srcSize[1],\n\t\t\tdestTL[0], destTL[1], destTL[0] + destSize[0], destTL[1] + destSize[1],\n\t\t\tGL_COLOR_BUFFER_BIT, filter\n\t\t);\n\t}\n\telse if (GL_Available(glBlitFramebuffer)) {\n\t\t// ARB_framebuffer\n\t\tGL_Procedure(glBindFramebuffer, GL_READ_FRAMEBUFFER, src->glref);\n\t\tGL_Procedure(glBindFramebuffer, GL_DRAW_FRAMEBUFFER, dest->glref);\n\n\t\tGL_Procedure(\n\t\t\tglBlitFramebuffer,\n\t\t\tsrcTL[0], srcTL[1], srcTL[0] + srcSize[0], srcTL[1] + srcSize[1],\n\t\t\tdestTL[0], destTL[1], destTL[0] + destSize[0], destTL[1] + destSize[1],\n\t\t\tGL_COLOR_BUFFER_BIT, filter\n\t\t);\n\t}\n\telse {\n\t\t// Shouldn't have been called, not supported\n\t}\n}\n\n// --- Wrapper functionality over, rendering logic below ---\nqbool GL_FramebufferEnabled3D(void)\n{\n\tframebuffer_data_t* fb = &framebuffer_data[framebuffer_std];\n\n\treturn vid_framebuffer.integer && fb->glref && R_TextureReferenceIsValid(fb->texture[fbtex_standard]);\n}\n\nqbool GL_FramebufferEnabled2D(void)\n{\n\tframebuffer_data_t* fb = &framebuffer_data[framebuffer_hud];\n\n\treturn vid_framebuffer.integer && fb->glref && R_TextureReferenceIsValid(fb->texture[fbtex_standard]);\n}\n\nstatic void VID_FramebufferFlip(void)\n{\n\tqbool flip3d = vid_framebuffer.integer && GL_FramebufferEnabled3D();\n\tqbool flip2d = vid_framebuffer.integer == USE_FRAMEBUFFER_3DONLY && GL_FramebufferEnabled2D();\n\n\tif (flip3d || flip2d) {\n\t\t// Screen-wide framebuffer without any processing required, so we can just blit\n\t\tqbool should_blit = (\n\t\t\t//vid_framebuffer_multisample.integer == 0 &&\n\t\t\tvid_software_palette.integer == 0 &&\n\t\t\tvid_framebuffer.integer != USE_FRAMEBUFFER_3DONLY &&\n\t\t\tvid_framebuffer_blit.integer &&\n\t\t\t(glConfig.supported_features & R_SUPPORT_FRAMEBUFFERS_BLIT)\n\t\t);\n\n\t\t// render to screen from now on\n\t\tGL_FramebufferStartUsingScreen();\n\n\t\tif (should_blit && flip3d) {\n\t\t\t// Blit to screen\n\t\t\tGL_FramebufferBlitSimple(framebuffer_std, framebuffer_none);\n\t\t}\n\t\telse {\n\t\t\trenderer.RenderFramebuffers();\n\t\t}\n\t}\n\telse if (vid_software_palette.integer) {\n\t\trenderer.RenderFramebuffers();\n\t}\n}\n\nstatic void GL_FramebufferEnsureCreated(framebuffer_id id, int width, int height)\n{\n\tframebuffer_data_t* fb = &framebuffer_data[id];\n\tint expected_samples = framebuffer_multisampled[id] ? vid_framebuffer_multisample.integer : 0;\n\n\tif (!fb->glref) {\n\t\tGL_FramebufferCreate(id, width, height);\n\t}\n\telse if (fb->width != width || fb->height != height || fb->samples != expected_samples) {\n\t\tGL_FramebufferDelete(id);\n\n\t\tGL_FramebufferCreate(id, width, height);\n\t}\n}\n\nstatic void GL_FramebufferEnsureDeleted(framebuffer_id id)\n{\n\tframebuffer_data_t* fb = &framebuffer_data[id];\n\n\tif (fb->glref) {\n\t\tGL_FramebufferDelete(id);\n\t}\n}\n\nstatic framebuffer_id VID_MultisampledAlternateId(framebuffer_id id)\n{\n\tif (vid_framebuffer_multisample.integer && framebuffer_multisample_alternate[id] && framebuffer_multisample_alternate[id] != id) {\n\t\treturn framebuffer_multisample_alternate[id];\n\t}\n\treturn id;\n}\n\nstatic qbool VID_FramebufferInit(framebuffer_id id, int effective_width, int effective_height)\n{\n\tif (effective_width && effective_height) {\n\t\tGL_FramebufferEnsureCreated(id, effective_width, effective_height);\n\n\t\tif (framebuffer_data[id].glref) {\n\t\t\tframebuffer_id ms_alt = VID_MultisampledAlternateId(id);\n\n\t\t\tif (ms_alt != id) {\n\t\t\t\tGL_FramebufferEnsureCreated(ms_alt, effective_width, effective_height);\n\n\t\t\t\tif (framebuffer_data[ms_alt].glref) {\n\t\t\t\t\tGL_FramebufferStartUsing(ms_alt);\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tGL_FramebufferStartUsing(id);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\tGL_FramebufferEnsureDeleted(id);\n\t}\n\n\treturn false;\n}\n\nvoid GL_FramebufferScreenDrawStart(void)\n{\n\tif (vid_framebuffer.integer) {\n\t\tVID_FramebufferInit(framebuffer_std, VID_ScaledWidth3D(), VID_ScaledHeight3D());\n\t}\n}\n\nqbool GL_Framebuffer2DSwitch(void)\n{\n\tif (vid_framebuffer.integer == USE_FRAMEBUFFER_3DONLY) {\n\n\t\tif (VID_FramebufferInit(framebuffer_hud, glConfig.vidWidth, glConfig.vidHeight)) {\n\t\t\tR_Viewport(0, 0, glConfig.vidWidth, glConfig.vidHeight);\n\t\t\tR_ClearColor(0.0f, 0.0f, 0.0f, 0.0f);\n\t\t\tglClear(GL_COLOR_BUFFER_BIT);\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tR_Viewport(glx, gly, glwidth, glheight);\n\treturn false;\n}\n\nvoid GL_FramebufferPostProcessScreen(void)\n{\n\tqbool framebuffer_active = GL_FramebufferEnabled3D() || GL_FramebufferEnabled2D();\n\n\tif (framebuffer_active || vid_software_palette.integer) {\n\t\tR_Viewport(glx, gly, glConfig.vidWidth, glConfig.vidHeight);\n\n\t\tVID_FramebufferFlip();\n\t}\n\n\tif (!vid_software_palette.integer) {\n\t\trenderer.BrightenScreen();\n\n\t\t// Hardware palette changes\n\t\tV_UpdatePalette();\n\t}\n}\n\nconst char* GL_FramebufferZBufferString(framebuffer_id ref)\n{\n\tif (framebuffer_data[ref].depthFormat == GL_DEPTH_COMPONENT16) {\n\t\treturn \"16-bit float z-buffer\";\n\t}\n\telse if (framebuffer_data[ref].depthFormat == GL_DEPTH_COMPONENT24) {\n\t\treturn \"24-bit float z-buffer\";\n\t}\n\telse if (framebuffer_data[ref].depthFormat == GL_DEPTH_COMPONENT32) {\n\t\treturn \"32-bit z-buffer\";\n\t}\n\telse if (framebuffer_data[ref].depthFormat == GL_DEPTH_COMPONENT32F) {\n\t\treturn \"32-bit float z-buffer\";\n\t}\n\telse {\n\t\treturn \"unknown z-buffer\";\n\t}\n}\n\nstatic qbool GL_ScreenshotFramebuffer(void)\n{\n\textern cvar_t vid_framebuffer_sshotmode;\n\n\treturn vid_framebuffer_sshotmode.integer && vid_framebuffer.integer == USE_FRAMEBUFFER_SCREEN && GL_FramebufferEnabled3D();\n}\n\nvoid GL_Screenshot(byte* buffer, size_t size)\n{\n\tsize_t width = renderer.ScreenshotWidth();\n\tsize_t height = renderer.ScreenshotHeight();\n\n\tGL_PackAlignment(1);\n\tif (GL_Available(glBindFramebuffer)) {\n\t\tif (GL_ScreenshotFramebuffer()) {\n\t\t\tGL_Procedure(glBindFramebuffer, GL_READ_FRAMEBUFFER, framebuffer_data[framebuffer_std].glref);\n\t\t}\n\t\telse {\n\t\t\tGL_Procedure(glBindFramebuffer, GL_READ_FRAMEBUFFER, 0);\n\t\t}\n\t}\n\tglReadPixels(0, 0, (GLsizei)width, (GLsizei)height, GL_RGB, GL_UNSIGNED_BYTE, buffer);\n}\n\nsize_t GL_ScreenshotWidth(void)\n{\n\tif (GL_ScreenshotFramebuffer()) {\n\t\treturn framebuffer_data[framebuffer_std].width;\n\t}\n\n\treturn glConfig.vidWidth;\n}\n\nsize_t GL_ScreenshotHeight(void)\n{\n\tif (GL_ScreenshotFramebuffer()) {\n\t\treturn framebuffer_data[framebuffer_std].height;\n\t}\n\n\treturn glConfig.vidHeight;\n}\n\nint GL_FramebufferMultisamples(framebuffer_id framebuffer)\n{\n\tframebuffer_data_t* fb = &framebuffer_data[framebuffer];\n\n\tif (vid_framebuffer.integer && fb->glref && R_TextureReferenceIsValid(fb->texture[fbtex_standard])) {\n\t\treturn fb->samples;\n\t}\n\n\treturn 0;\n}\n\nvoid GL_FramebufferDeleteAll(void)\n{\n\tframebuffer_id i;\n\n\tfor (i = 0; i < framebuffer_count; ++i) {\n\t\tGL_FramebufferEnsureDeleted(i);\n\t}\n}\n\nint GL_FramebufferFxaaPreset(void)\n{\n\tstatic const int fxaa_cvar_to_preset[18] = {\n\t\t\t0, 10, 11, 12, 13, 14, 15, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 39\n\t};\n\textern cvar_t vid_framebuffer_fxaa;\n\n\treturn fxaa_cvar_to_preset[bound(0, vid_framebuffer_fxaa.integer, 17)];\n}\n"
  },
  {
    "path": "src/gl_framebuffer.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef __GL_FRAMEBUFFER_H__\n#define __GL_FRAMEBUFFER_H__\n\ntypedef enum {\n\tframebuffer_none,\n\t// rendering\n\tframebuffer_std,\n\tframebuffer_std_ms,\n\tframebuffer_hud,\n\tframebuffer_hud_ms,\n\t// framebuffers used to resolve multisampling\n\tframebuffer_std_blit,\n\tframebuffer_std_blit_ms,\n\tframebuffer_hud_blit,\n\tframebuffer_hud_blit_ms,\n\tframebuffer_count\n} framebuffer_id;\n\ntypedef enum {\n\tfbtex_standard,\n\tfbtex_depth,\n\tfbtex_bloom,\n\tfbtex_worldnormals,\n\tfbtex_count\n} fbtex_id;\n\nvoid GL_InitialiseFramebufferHandling(void);\nqbool GL_FramebufferCreate(framebuffer_id id, int width, int height);\nvoid GL_FramebufferDelete(framebuffer_id id);\nvoid GL_FramebufferStartUsing(framebuffer_id id);\nvoid GL_FramebufferStartUsingScreen(void);\ntexture_ref GL_FramebufferTextureReference(framebuffer_id id, fbtex_id tex_id);\nint GL_FrameBufferWidth(framebuffer_id ref);\nint GL_FrameBufferHeight(framebuffer_id ref);\nconst char* GL_FramebufferZBufferString(framebuffer_id ref);\n\nvoid GL_FramebufferScreenDrawStart(void);\nqbool GL_Framebuffer2DSwitch(void);\nvoid GL_FramebufferPostProcessScreen(void);\nqbool GL_FramebufferEnabled2D(void);\nqbool GL_FramebufferEnabled3D(void);\n\nqbool GL_FramebufferStartWorldNormals(framebuffer_id id);\nqbool GL_FramebufferEndWorldNormals(framebuffer_id id);\n\nint GL_FramebufferMultisamples(framebuffer_id framebuffer);\nvoid GL_FramebufferDeleteAll(void);\nint GL_FramebufferFxaaPreset(void);\n\n#define USE_FRAMEBUFFER_SCREEN    1\n#define USE_FRAMEBUFFER_3DONLY    2\n\n#endif // __GL_FRAMEBUFFER_H__\n"
  },
  {
    "path": "src/gl_local.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n// gl_local.h -- private refresh defs\n#ifndef __GL_LOCAL_H__\n#define __GL_LOCAL_H__\n\n#include <SDL_opengl.h>\n#include <SDL_opengl_glext.h>\n\n#include \"gl_model.h\"\n#include \"r_framestats.h\"\n#include \"r_trace.h\"\n#include \"r_local.h\"\n#include \"tr_types.h\"\n\nvoid R_TimeRefresh_f (void);\ntexture_t *R_TextureAnimation(entity_t* ent, texture_t *base);\n\n//====================================================\n\n// view origin\nextern\tvec3_t\tvup;\nextern\tvec3_t\tvpn;\nextern\tvec3_t\tvright;\nextern\tvec3_t\tr_origin;\n\n// screen size info\nextern\ttexture_t\t*r_notexture_mip;\n\nextern\ttexture_ref netgraphtexture;\n\n// Tomaz - Fog Begin\nextern  cvar_t  gl_fogenable;\nextern  cvar_t  gl_fogstart;\nextern  cvar_t  gl_fogend;\nextern  cvar_t  gl_fogred;\nextern  cvar_t  gl_fogblue;\nextern  cvar_t  gl_foggreen;\nextern  cvar_t  gl_fogsky;\n// Tomaz - Fog End\n\nextern\tcvar_t\tr_drawentities;\nextern\tcvar_t\tr_drawflame;\nextern\tcvar_t\tr_speeds;\nextern\tcvar_t\tr_fullbright;\nextern\tcvar_t\tr_shadows;\nextern\tcvar_t\tr_mirroralpha;\nextern\tcvar_t\tr_dynamic;\nextern\tcvar_t\tr_novis;\nextern\tcvar_t\tr_netgraph;\nextern\tcvar_t\tr_netstats;\nextern\tcvar_t\tr_fullbrightSkins;\nextern\tcvar_t\tr_fastsky;\nextern\tcvar_t\tr_skycolor;\nextern\tcvar_t\tr_drawflat;\nextern\tcvar_t\tr_drawflat_mode;\nextern\tcvar_t\tr_wallcolor;\nextern\tcvar_t\tr_floorcolor;\nextern\tcvar_t\tr_bloom;\nextern\tcvar_t\tr_bloom_alpha;\nextern\tcvar_t\tr_bloom_diamond_size;\nextern\tcvar_t\tr_bloom_intensity;\nextern\tcvar_t\tr_bloom_darken;\nextern\tcvar_t\tr_bloom_sample_size;\nextern\tcvar_t\tr_bloom_fast_sample;\n\nextern\tcvar_t\tr_skyname;\nextern  cvar_t  gl_caustics;\nextern  cvar_t  gl_detail;\nextern  cvar_t  gl_fog;\nextern  cvar_t  gl_fog_density;\nextern  cvar_t  gl_waterfog;\nextern  cvar_t  gl_waterfog_density;\n\nextern\tcvar_t\tgl_subdivide_size;\nextern\tcvar_t\tgl_clear;\nextern\tcvar_t\tgl_polyblend;\nextern\tcvar_t\tgl_nocolors;\nextern\tcvar_t\tgl_finish;\nextern\tcvar_t\tgl_fb_bmodels;\nextern\tcvar_t\tgl_fb_models;\nextern\tcvar_t\tgl_playermip;\n\nextern  cvar_t gl_part_explosions;\nextern  cvar_t gl_part_bloodtrails;\nextern  cvar_t gl_part_trails;\nextern  cvar_t gl_part_spikes;\nextern  cvar_t gl_part_gunshots;\nextern  cvar_t gl_part_blood;\nextern  cvar_t gl_part_telesplash;\nextern  cvar_t gl_part_blobs;\nextern  cvar_t gl_part_lavasplash;\nextern\tcvar_t gl_part_inferno;\nextern  cvar_t gl_part_bubble;\nextern\tcvar_t gl_part_detpackexplosion_fire_color;\nextern\tcvar_t gl_part_detpackexplosion_ray_color;\n\nextern\tcvar_t gl_powerupshells;\n\nextern cvar_t vid_gammacorrection;\n\n// gl_rmain.c\nqbool R_CullBox (vec3_t mins, vec3_t maxs);\nqbool R_CullSphere (vec3_t centre, float radius);\nvoid R_BrightenScreen (void);\n\nextern int dlightcolor[NUM_DLIGHTTYPES][3];\n\n// gl_ngraph.c\n//void R_NetGraph (void); // HUD -> hexum\nvoid R_MQW_NetGraph(int outgoing_sequence, int incoming_sequence, int *packet_latency,\n                int lost, int minping, int avgping, int maxping, int devping,\n                int posx, int posy, int width, int height, int revx, int revy);\n\n// gl_rmisc.c\nvoid R_InitOtherTextures(void);\n\n//vid_common_gl.c\n\n//anisotropic filtering\n#ifndef GL_EXT_texture_filter_anisotropic\n#define GL_EXT_texture_filter_anisotropic 1\n#define GL_TEXTURE_MAX_ANISOTROPY_EXT\t\t\t\t0x84FE\n#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT\t\t\t0x84FF\n#endif\n\n//multitexturing\n#define\tGL_TEXTURE0_ARB \t\t\t0x84C0\n#define\tGL_TEXTURE1_ARB \t\t\t0x84C1\n#define\tGL_TEXTURE2_ARB \t\t\t0x84C2\n#define\tGL_TEXTURE3_ARB \t\t\t0x84C3\n#define GL_MAX_TEXTURE_UNITS_ARB\t0x84E2\n\n//texture compression\n#define GL_COMPRESSED_ALPHA_ARB\t\t\t\t\t0x84E9\n#define GL_COMPRESSED_LUMINANCE_ARB\t\t\t\t0x84EA\n#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB\t\t0x84EB\n#define GL_COMPRESSED_INTENSITY_ARB\t\t\t\t0x84EC\n#define GL_COMPRESSED_RGB_ARB\t\t\t\t\t0x84ED\n#define GL_COMPRESSED_RGBA_ARB\t\t\t\t\t0x84EE\n#define GL_TEXTURE_COMPRESSION_HINT_ARB\t\t\t0x84EF\n#define GL_TEXTURE_IMAGE_SIZE_ARB\t\t\t\t0x86A0\n#define GL_TEXTURE_COMPRESSED_ARB\t\t\t\t0x86A1\n#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB\t0x86A2\n#define GL_COMPRESSED_TEXTURE_FORMATS_ARB\t\t0x86A3\n\n//sRGB gamma correction\n#define GL_SRGB8 0x8C41\n#define GL_SRGB8_ALPHA8 0x8C43\n#define GL_FRAMEBUFFER_SRGB 0x8DB9\n\nextern byte color_white[4], color_black[4];\nextern qbool gl_mtexable;\nextern int gl_textureunits;\n\nvoid GL_LoadProgramFunctions(void);\nvoid GL_LoadStateFunctions(void);\nvoid GL_LoadTextureManagementFunctions(void);\nvoid GL_LoadDrawFunctions(void);\nvoid GL_InitialiseDebugging(void);\n\n// Which renderer to use\n#define GL_VersionAtLeast(major, minor) (glConfig.majorVersion > (major) || (glConfig.majorVersion == (major) && glConfig.minorVersion >= (minor)))\n\n// When SSBOs are unavailable (GL < 4.3), storage buffers become UBOs.\n// UBOs share the binding namespace with GlobalState (binding 0), so offset by 1.\n#define EZQ_STORAGE_BLOCK_BINDING(n) (GL_VersionAtLeast(4, 3) ? (n) : ((n) + 1))\n\nvoid GL_TextureInitialiseState(void);\nvoid GL_InvalidateTextureReferences(GLuint texture);\n\n// Functions\nvoid GL_BeginDrawSprites(void);\nvoid GL_EndDrawSprites(void);\n\n// \nvoid R_RenderDynamicLightmaps(msurface_t *fa, qbool world);\nvoid R_DrawViewModel(void);\nvoid R_LightmapFrameInit(void);\nvoid R_UploadChangedLightmaps(void);\nvoid R_UploadLightMap(int textureUnit, int lightmapnum);\n\nmspriteframe_t* R_GetSpriteFrame(entity_t *e, msprite2_t *psprite);\n\nvoid GLC_DrawSpriteModel(entity_t* e);\nvoid GLC_PolyBlend(float v_blend[4]);\nvoid GLC_BrightenScreen(void);\nvoid GLC_EmitCausticsPolys(void);\nvoid GLC_DrawWorld(void);\nvoid GLC_ClearTextureChains(void);\nqbool GLC_SetTextureLightmap(int textureUnit, int lightmap_num);\nqbool GLC_IsLightmapBound(int textureUnit, int lightmap_num);\ntexture_ref GLC_LightmapTexture(int index);\nvoid GLC_ClearLightmapPolys(void);\nvoid GLC_AddToLightmapChain(msurface_t* s);\nvoid GLC_LightmapUpdate(int index);\nglpoly_t* GLC_LightmapChain(int i);\nint GLC_LightmapCount(void);\nvoid GLC_DrawMapOutline(model_t *model);\nvoid GLC_MultiTexCoord2f(GLenum target, float s, float t);\n\nvoid GLM_DrawSpriteModel(entity_t* e);\nvoid GLM_PolyBlend(float v_blend[4]);\ntexture_ref GLM_LightmapArray(void);\nvoid GLM_PostProcessScreen(void);\nvoid GLM_RenderView(void);\nvoid GLM_UploadFrameConstants(void);\nvoid GLM_PrepareWorldModelBatch(void);\n\nvoid GL_InitialiseState(void);\n\nbyte* SurfaceFlatTurbColor(texture_t* texture);\n\nstruct custom_model_color_s;\n\nvoid R_StateBeginAlphaLineRGB(float thickness);\nvoid R_SetupFrame(void);\n\nvoid GL_InitialiseFramebufferHandling(void);\n\n// Rendering functions\nvoid GL_DrawArrays(GLenum mode, GLint first, GLsizei count);\nvoid GL_MultiDrawArrays(GLenum mode, GLint* first, GLsizei* count, GLsizei primcount);\nvoid GL_DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, GLvoid* indices, GLint basevertex);\nvoid GL_DrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices);\nvoid GL_MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);\nvoid GL_MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);\nvoid GL_DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, GLvoid* indices, GLsizei primcount, GLint basevertex, GLuint baseinstance);\nvoid GL_DrawElementsIndirect(GLenum mode, GLenum type, GLvoid* indirect);\nqbool GL_DrawElementsBaseVertexAvailable(void);\n\nGLuint GL_GetUniformBlockIndex(GLuint program, const GLchar *uniformBlockName);\nvoid GL_UniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);\n\nvoid GL_BindImageTexture(GLuint unit, texture_ref texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format);\nGLenum GL_ProcessAllErrors(const char* message);\n\n#ifdef WITH_RENDERING_TRACE\n#define GL_ProcessErrors GL_ProcessAllErrors\n\n#define GL_LoadRequiredFunction(varName, functionName)           (((varName) = (functionName##_t)SDL_GL_GetProcAddress(#functionName)) != NULL)\n#define GL_LoadMandatoryFunction(functionName,testFlag)          { testFlag &= ((q##functionName##_impl = (functionName##_t)SDL_GL_GetProcAddress(#functionName)) != NULL); }\n#define GL_LoadMandatoryFunctionEXT(functionName,testFlag)       { testFlag &= ((q##functionName##_impl = (functionName##_t)SDL_GL_GetProcAddress(#functionName \"EXT\")) != NULL); }\n#define GL_LoadMandatoryFunctionExtension(functionName,testFlag) { testFlag &= ((q##functionName##_impl = (functionName##_t)SDL_GL_GetProcAddress(#functionName)) != NULL); }\n#define GL_InvalidateFunction(functionName)      { q##functionName##_impl = NULL; }\n#define GL_LoadOptionalFunction(functionName)    { q##functionName##_impl = (functionName##_t)SDL_GL_GetProcAddress(#functionName); }\n#define GL_LoadOptionalFunctionEXT(functionName) { q##functionName##_impl = (functionName##_t)SDL_GL_GetProcAddress(#functionName \"EXT\"); }\n#define GL_LoadOptionalFunctionARB(functionName) { q##functionName##_impl = (functionName##_t)SDL_GL_GetProcAddress(#functionName \"ARB\"); }\n#define GL_UseDirectStateAccess() (GL_VersionAtLeast(4, 5) || SDL_GL_ExtensionSupported(\"GL_ARB_direct_state_access\"))\n#define GL_StaticProcedureDeclaration(name, formatString, ...) \\\n\ttypedef void (APIENTRY *name ## _t)(__VA_ARGS__); \\\n\tstatic name ## _t    q ## name ## _impl; \\\n\tstatic const char* q ## name ## _formatString = formatString;\n#define GL_StaticFunctionDeclaration(name, formatString, resultFormatString, returnType, ...)\\\n\ttypedef returnType (APIENTRY *name ## _t)(__VA_ARGS__); \\\n\tstatic name ## _t    q ## name ## _impl; \\\n\tstatic const char* q ## name ## _formatString = formatString; \\\n\tstatic const char* q ## name ## _resultString = resultFormatString; \\\n\tstatic returnType GL_Wrapper_ ## name(__VA_ARGS__)\n\n#define GL_StaticFunctionWrapperBody(name, returnType, ...) \\\n{ \\\n\treturnType result; \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tconst char* args = va(q ## name ## _formatString, __VA_ARGS__); \\\n\\\n\t\tR_TraceAPI(\"%s(%s)@%s,%d\", #name, args, __FILE__, __LINE__); \\\n\t} \\\n\tresult = q ## name ## _impl(__VA_ARGS__); \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_debug) || COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tGL_ProcessErrors(#name); \\\n\t\tR_TraceAPI(q ## name ## _resultString, result); \\\n\t} \\\n\\\n\treturn result; \\\n}\n\n#define GL_StaticFunctionWrapperBodyNoArgs(name, returnType) \\\n{ \\\n\treturnType result; \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tR_TraceAPI(\"%s()@%s,%d\", #name, __FILE__, __LINE__); \\\n\t} \\\n\tresult = q ## name ## _impl(); \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_debug) || COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tGL_ProcessErrors(#name); \\\n\t\tR_TraceAPI(q ## name ## _resultString, result); \\\n\t} \\\n\\\n\treturn result; \\\n}\n\n#define GL_Procedure(name, ...) \\\n{ \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_debug)) { \\\n\t\tconst char* ez_gldebug_args = va(q ## name ## _formatString, __VA_ARGS__); \\\n\\\n\t\tR_TraceAPI(\"%s(%s)@%s,%d\", #name, ez_gldebug_args, __FILE__, __LINE__); \\\n\t} \\\n\tq ## name ## _impl(__VA_ARGS__); \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_debug) || COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tGL_ProcessErrors(#name); \\\n\t} \\\n}\n\n#define GL_ProcedureNoArgs(name) \\\n{ \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tR_TraceAPI(\"%s()@%s,%d\", #name, __FILE__, __LINE__); \\\n\t} \\\n\tq ## name ## _impl(); \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_debug) || COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tGL_ProcessErrors(#name); \\\n\t} \\\n}\n\n#define GL_ProcedureReturnError(name, ...) \\\n{ \\\n\tGLenum ez_gl_error = GL_NO_ERROR; \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tconst char* ez_gldebug_args = va(q ## name ## _formatString, __VA_ARGS__); \\\n\\\n\t\tR_TraceAPI(\"%s(%s)@%s,%d\", #name, ez_gldebug_args, __FILE__, __LINE__); \\\n\t} \\\n\tq ## name ## _impl(__VA_ARGS__); \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_debug) || COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tez_gl_error = GL_ProcessErrors(#name); \\\n\t} \\\n\telse { \\\n\t\tez_gl_error = glGetError(); \\\n\t} \\\n\treturn ez_gl_error; \\\n}\n\n#define GL_ProcedureReturnIfError(name, ...) \\\n{ \\\n\tGLenum ez_gl_error = GL_NO_ERROR; \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tconst char* ez_gldebug_args = va(q ## name ## _formatString, __VA_ARGS__); \\\n\\\n\t\tR_TraceAPI(\"%s(%s)@%s,%d\", #name, ez_gldebug_args, __FILE__, __LINE__); \\\n\t} \\\n\tq ## name ## _impl(__VA_ARGS__); \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_debug) || COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tez_gl_error = GL_ProcessErrors(#name); \\\n\t} \\\n\telse { \\\n\t\tez_gl_error = glGetError(); \\\n\t} \\\n\tif (ez_gl_error != GL_NO_ERROR) { \\\n\t\treturn ez_gl_error; \\\n\t} \\\n}\n\n#define GL_BuiltinProcedure(name, formatString, ...) \\\n{ \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tconst char* ez_gldebug_args = va(formatString, __VA_ARGS__); \\\n\\\n\t\tR_TraceAPI(\"%s(%s)@%s,%d\", #name, ez_gldebug_args, __FILE__, __LINE__); \\\n\t} \\\n\tname(__VA_ARGS__); \\\n\tif (COM_CheckParm(cmdline_param_client_video_r_debug) || COM_CheckParm(cmdline_param_client_video_r_trace)) { \\\n\t\tGL_ProcessErrors(#name); \\\n\t} \\\n}\n#define GL_Function(name, ...)    GL_Wrapper_ ## name(__VA_ARGS__)\n#define GL_FunctionNoArgs(name)   GL_Wrapper_ ## name()\n#define GL_BuiltinFunction(name, returnType, ...)\n#define GL_Available(name)        ((q ## name ## _impl) != NULL)\n#else\n// No direct tracing\n#ifndef EZ_OPENGL_NO_EXTENSIONS\n#define GL_LoadRequiredFunction(varName, functionName) (((varName) = (functionName##_t)SDL_GL_GetProcAddress(#functionName)) != NULL)\n#define GL_LoadMandatoryFunction(functionName,testFlag) { testFlag &= ((q##functionName##_impl = (functionName##_t)SDL_GL_GetProcAddress(#functionName)) != NULL); }\n#define GL_LoadMandatoryFunctionEXT(functionName,testFlag) { testFlag &= ((q##functionName##_impl = (functionName##_t)SDL_GL_GetProcAddress(#functionName \"EXT\")) != NULL); }\n#define GL_LoadMandatoryFunctionExtension(functionName,testFlag) { testFlag &= ((q##functionName##_impl = (functionName##_t)SDL_GL_GetProcAddress(#functionName)) != NULL); }\n#define GL_LoadOptionalFunction(functionName) { q##functionName##_impl = (functionName##_t)SDL_GL_GetProcAddress(#functionName); }\n#define GL_LoadOptionalFunctionEXT(functionName) { q##functionName##_impl = (functionName##_t)SDL_GL_GetProcAddress(#functionName \"EXT\"); }\n#define GL_LoadOptionalFunctionARB(functionName) { q##functionName##_impl = (functionName##_t)SDL_GL_GetProcAddress(#functionName \"ARB\"); }\n#define GL_UseDirectStateAccess() (GL_VersionAtLeast(4, 5) || SDL_GL_ExtensionSupported(\"GL_ARB_direct_state_access\"))\n#else\n#define GL_LoadMandatoryFunction(functionName,testFlag) { q##functionName##_impl = NULL; testFlag = false; }\n#define GL_LoadMandatoryFunctionEXT(functionName,testFlag) { q##functionName##_impl = NULL; testFlag = false; }\n#define GL_LoadMandatoryFunctionExtension(functionName,testFlag) { q##functionName##_impl = NULL; testFlag = false; }\n#define GL_LoadOptionalFunction(functionName) { q##functionName##_impl = NULL; }\n#define GL_LoadOptionalFunctionEXT(functionName) { q##functionName##_impl = NULL; }\n#define GL_LoadOptionalFunctionARB(functionName) { q##functionName##_impl = NULL; }\n#define GL_UseDirectStateAccess() (false)\n#endif\n#define GL_InvalidateFunction(functionName)      { q##functionName##_impl = NULL; }\n#define GL_StaticProcedureDeclaration(name, formatString, ...) \\\n\ttypedef void (APIENTRY *name ## _t)(__VA_ARGS__); \\\n\tstatic name ## _t    q ## name ## _impl;\n#define GL_StaticFunctionDeclaration(name, formatString, resultFormatString, returnType, ...) \\\n\ttypedef returnType (APIENTRY *name ## _t)(__VA_ARGS__); \\\n\tstatic name ## _t    q ## name ## _impl;\n#define GL_StaticFunctionWrapperBody(...)\n#define GL_StaticFunctionWrapperBodyNoArgs(...)\n#define GL_Procedure(name, ...)   { q ## name ## _impl(__VA_ARGS__); }\n#define GL_ProcedureNoArgs(name)  { q ## name ## _impl(); }\n#define GL_BuiltinProcedure(name, formatString, ...) { name(__VA_ARGS__); }\n#define GL_Function(name, ...) q ## name ## _impl(__VA_ARGS__)\n#define GL_FunctionNoArgs(name)   q ## name ## _impl()\n#define GL_Available(name) ((q ## name ## _impl) != NULL)\n#define GL_ProcedureReturnError(name, ...) { q ## name ## _impl(__VA_ARGS__); return glGetError(); }\n#define GL_ProcedureReturnIfError(name, ...) { \\\n\tGLenum ez_gl_error; \\\n\tq ## name ## _impl(__VA_ARGS__); \\\n\tez_gl_error = glGetError(); \\\n\tif (ez_gl_error != GL_NO_ERROR) { \\\n\t\treturn ez_gl_error; \\\n\t} \\\n}\n#define GL_ProcessErrors(text)\n#endif // \n\nvoid GLC_ClientActiveTexture(GLenum texture_unit);\nvoid GL_ConsumeErrors(void);\n\nvoid VK_PrintGfxInfo(void);\n\n// Trace functions\n#ifdef WITH_RENDERING_TRACE\nvoid GL_TraceObjectLabelSet(GLenum identifier, GLuint name, int length, const char* label);\nvoid GL_TraceObjectLabelGet(GLenum identifier, GLuint name, int bufSize, int* length, char* label);\n#else\n#define GL_TraceObjectLabelSet(...)\n#define GL_TraceObjectLabelGet(...)\n#endif\n\n// For context creation\ntypedef struct opengl_version_s {\n\tint majorVersion;\n\tint minorVersion;\n\tqbool core;\n\tqbool legacy;\n} opengl_version_t;\n\nSDL_GLContext GL_SDL_CreateBestContext(SDL_Window* window, const opengl_version_t* versions, int count);\n\nvoid GL_PackAlignment(int alignment_in_bytes);\n\n#endif /* !__GL_LOCAL_H__ */\n"
  },
  {
    "path": "src/gl_misc.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n// gl_misc.c\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"tr_types.h\"\n#include \"r_renderer.h\"\n#include \"r_lightmaps_internal.h\"\n#include \"gl_texture_internal.h\"\n\ntypedef struct gl_enum_value_s {\n\tGLenum value;\n\tconst char* name;\n\tint benchmark_bpp;\n} gl_enum_value_t;\n\nstatic gl_enum_value_t image_formats[] = {\n\t{ GL_RED, \"RED\", 0 },\n\t{ GL_RG, \"RG\", 0 },\n\t{ GL_RGB, \"RGB\", 0 },\n\t{ GL_BGR, \"BGR\", 0 },\n\t{ GL_RGBA, \"RGBA\", 4 },\n\t{ GL_BGRA, \"BGRA\", 4 },\n\t{ GL_RED_INTEGER, \"RED_INT\" },\n\t{ GL_RG_INTEGER, \"RG_INT\" },\n\t{ GL_RGB_INTEGER, \"RGB_INT\" },\n\t{ GL_BGR_INTEGER, \"BGR_INT\" },\n\t{ GL_RGBA_INTEGER, \"RGBA_INT\" },\n\t{ GL_BGRA_INTEGER, \"BGRA_INT\" },\n\t{ GL_STENCIL_INDEX, \"STENCIL_INDEX\" },\n\t{ GL_DEPTH_COMPONENT, \"DEPTH_COMPONENT\" },\n\t{ GL_DEPTH_STENCIL, \"DEPTH_STENCIL\" }\n};\nstatic gl_enum_value_t image_types[] = {\n\t{ GL_UNSIGNED_BYTE, \"UBYTE\", 3 },\n\t{ GL_BYTE, \"BYTE\", 3 },\n\t{ GL_UNSIGNED_SHORT, \"USHORT\", 0 },\n\t{ GL_SHORT, \"SHORT\", 0 },\n\t{ GL_UNSIGNED_INT, \"UINT\", 4 },\n\t{ GL_INT, \"INT\", 4 },\n\t{ GL_FLOAT, \"FLOAT\", 0 },\n\t{ GL_UNSIGNED_BYTE_3_3_2, \"UBYTE_332\", 0 },\n\t{ GL_UNSIGNED_BYTE_2_3_3_REV, \"UBYTE_233R\", 0 },\n\t{ GL_UNSIGNED_SHORT_5_6_5, \"USHORT_565\", 0 },\n\t{ GL_UNSIGNED_SHORT_5_6_5_REV, \"USHORT_565R\", 0 },\n\t{ GL_UNSIGNED_SHORT_4_4_4_4, \"USHORT_4444\", 0 },\n\t{ GL_UNSIGNED_SHORT_4_4_4_4_REV, \"USHORT_4444R\", 0 },\n\t{ GL_UNSIGNED_SHORT_5_5_5_1, \"USHORT_5551\", 0 },\n\t{ GL_UNSIGNED_SHORT_1_5_5_5_REV, \"USHORT_1555R\", 0 },\n\t{ GL_UNSIGNED_INT_8_8_8_8, \"UINT_8888\", 4 },\n\t{ GL_UNSIGNED_INT_8_8_8_8_REV, \"UINT_8888R\", 4 },\n\t{ GL_UNSIGNED_INT_10_10_10_2, \"UINT_101010_2\", 4 },\n\t{ GL_UNSIGNED_INT_2_10_10_10_REV, \"UINT_2_101010R\", 4 }\n};\n\n\nvoid GL_Clear(qbool clear_color)\n{\n\tR_ApplyRenderingState(r_state_default_3d);\n\tglClear((clear_color ? GL_COLOR_BUFFER_BIT : 0) | GL_DEPTH_BUFFER_BIT);\n}\n\nvoid GL_EnsureFinished(void)\n{\n\tglFinish();\n}\n\nGLenum GL_ProcessErrors(const char* message);\n\nGLenum GL_ProcessAllErrors(const char* message)\n{\n\tGLenum error = glGetError();\n\tGLenum firstError = error;\n\n\twhile (error != GL_NO_ERROR) {\n\t\tif (error == GL_INVALID_ENUM) {\n\t\t\tR_TraceLogAPICall(\"  ERROR: %s (GL_INVALID_ENUM)\\n\", message);\n\t\t}\n\t\telse if (error == GL_INVALID_VALUE) {\n\t\t\tR_TraceLogAPICall(\"  ERROR: %s (GL_INVALID_VALUE)\\n\", message);\n\t\t}\n\t\telse if (error == GL_INVALID_OPERATION) {\n\t\t\tR_TraceLogAPICall(\"  ERROR: %s (GL_INVALID_OPERATION)\\n\", message);\n\t\t}\n\t\telse if (error == GL_STACK_OVERFLOW) {\n\t\t\tR_TraceLogAPICall(\"  ERROR: %s (GL_STACK_OVERFLOW)\\n\", message);\n\t\t}\n\t\telse if (error == GL_STACK_UNDERFLOW) {\n\t\t\tR_TraceLogAPICall(\"  ERROR: %s (GL_STACK_UNDERFLOW)\\n\", message);\n\t\t}\n\t\telse if (error == GL_OUT_OF_MEMORY) {\n\t\t\tR_TraceLogAPICall(\"  ERROR: %s (GL_OUT_OF_MEMORY)\\n\", message);\n\t\t}\n\t\telse {\n\t\t\tR_TraceLogAPICall(\"  ERROR: %s (UNKNOWN_ERROR 0x%X)\\n\", message, error);\n\t\t}\n\t\terror = glGetError();\n\t}\n\treturn firstError;\n}\n\nvoid GL_ConsumeErrors(void)\n{\n\tGL_ProcessAllErrors(\"Consuming prior errors...\");\n}\n\nstatic void GL_PrintInfoLine(const char* label, int labelsize, const char* fmt, ...)\n{\n\tva_list argptr;\n\tchar msg[128];\n\n\tva_start(argptr, fmt);\n\tvsnprintf(msg, sizeof(msg), fmt, argptr);\n\tva_end(argptr);\n\n\tCom_Printf_State(PRINT_ALL, \"  %-*s \", labelsize, label);\n\tcon_margin = labelsize + 3;\n\tCom_Printf_State(PRINT_ALL, \"%s\", msg);\n\tcon_margin = 0;\n\tCom_Printf_State(PRINT_ALL, \"\\n\");\n}\n\nvoid GL_PrintGfxInfo(void)\n{\n\tSDL_DisplayMode current;\n\tGLint num_extensions;\n\tint i;\n\n\tCom_Printf_State(PRINT_ALL, \"\\nOpenGL (%s)\\n\", R_UseImmediateOpenGL() ? \"classic\" : \"glsl\");\n\tGL_PrintInfoLine(\"Vendor:\", 9, \"%s\", (const char*)glConfig.vendor_string);\n\tGL_PrintInfoLine(\"Renderer:\", 9, \"%s\", (const char*)glConfig.renderer_string);\n\tGL_PrintInfoLine(\"GLSL:\", 9, \"%s\", (const char*)glConfig.glsl_version);\n\tGL_PrintInfoLine(\"Version:\", 9, \"%s\", (const char*)glConfig.version_string);\n\n\tif (r_showextensions.integer) {\n\t\tCom_Printf_State(PRINT_ALL, \"GL_EXTENSIONS: \");\n\t\tif (GL_VersionAtLeast(3, 0)) {\n\t\t\ttypedef const GLubyte* (APIENTRY *glGetStringi_t)(GLenum name, GLuint index);\n\t\t\tglGetStringi_t glGetStringi = (glGetStringi_t)SDL_GL_GetProcAddress(\"glGetStringi\");\n\t\t\tif (glGetStringi) {\n\t\t\t\tglGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);\n\t\t\t\tfor (i = 0; i < num_extensions; ++i) {\n\t\t\t\t\tCom_Printf_State(PRINT_ALL, \"%s%s\", i ? \" \" : \"\", glGetStringi(GL_EXTENSIONS, i));\n\t\t\t\t}\n\t\t\t\tCom_Printf_State(PRINT_ALL, \"\\n\");\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCom_Printf_State(PRINT_ALL, \"(list unavailable)\\n\");\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tCom_Printf_State(PRINT_ALL, \"%s\\n\", (const char*)glGetString(GL_EXTENSIONS));\n\t\t}\n\t}\n\n\tCom_Printf_State(PRINT_ALL, \"Textures:\\n\");\n\tGL_PrintInfoLine(\"Units:\", 14, \"%d\", glConfig.texture_units);\n\tGL_PrintInfoLine(\"Size:\", 14, \"%d\", glConfig.gl_max_size_default);\n\tif (R_UseModernOpenGL()) {\n\t\tGL_PrintInfoLine(\"3D Sizes:\", 14, \"%dx%dx%d\\n\", glConfig.max_3d_texture_size, glConfig.max_3d_texture_size, glConfig.max_texture_depth);\n\t}\n\tif (glConfig.preferred_format || glConfig.preferred_type) {\n\t\tCom_Printf_State(PRINT_ALL, \"Preferences\\n\");\n\t\tconst char* format = \"?\";\n\t\tconst char* type = \"?\";\n\n\t\tfor (i = 0; i < sizeof(image_formats) / sizeof(image_formats[0]); ++i) {\n\t\t\tif (image_formats[i].value == glConfig.preferred_format) {\n\t\t\t\tformat = image_formats[i].name;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tfor (i = 0; i < sizeof(image_types) / sizeof(image_types[0]); ++i) {\n\t\t\tif (image_types[i].value == glConfig.preferred_type) {\n\t\t\t\ttype = image_types[i].name;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tGL_PrintInfoLine(\"Image Format:\", 14, \"0x%x (%s)\", glConfig.preferred_format, format);\n\t\tGL_PrintInfoLine(\"Image Type:\", 14, \"0x%x (%s)\", glConfig.preferred_type, type);\n\t}\n\tGL_PrintInfoLine(\"Lightmaps:\", 14, \"%s/%s\", GL_Supported(R_SUPPORT_BGRA_LIGHTMAPS) ? \"BGRA\" : \"RGBA\", GL_Supported(R_SUPPORT_INT8888R_LIGHTMAPS) ? \"UINT8888R\" : \"UBYTE\");\n\n\tCom_Printf_State(PRINT_ALL, \"Supported features:\\n\");\n\tGL_PrintInfoLine(\"Shaders:\", 15, \"%s\", GL_Supported(R_SUPPORT_RENDERING_SHADERS) ? \"&c0f0available&r\" : \"&cf00unsupported&r\");\n\tGL_PrintInfoLine(\"Compute:\", 15, \"%s\", GL_Supported(R_SUPPORT_COMPUTE_SHADERS) ? \"&c0f0available&r\" : \"&cf00unsupported&r\");\n\tGL_PrintInfoLine(\"Framebuffers:\", 15, \"%s\", GL_Supported(R_SUPPORT_FRAMEBUFFERS) ? \"&c0f0available&r\" : \"&cf00unsupported&r\");\n\tGL_PrintInfoLine(\"Tex arrays:\", 15, \"%s\", GL_Supported(R_SUPPORT_TEXTURE_ARRAYS) ? \"&c0f0available&r\" : \"&cf00unsupported&r\");\n\tGL_PrintInfoLine(\"Tex samplers:\", 15, \"%s\", GL_Supported(R_SUPPORT_TEXTURE_SAMPLERS) ? \"&c0f0available&r\" : \"&cf00unsupported&r\");\n\tGL_PrintInfoLine(\"HW lighting:\", 15, \"%s\", GL_Supported(R_SUPPORT_FEATURE_HW_LIGHTING) ? \"&c0f0available&r\" : \"&cf00unsupported&r\");\n\n\tif (SDL_GetCurrentDisplayMode(VID_DisplayNumber(r_fullscreen.value), &current) != 0) {\n\t\tcurrent.refresh_rate = 0; // print 0Hz if we run into problem fetching data\n\t}\n\n\tCom_Printf_State(PRINT_ALL, \"Video\\n\");\n\tGL_PrintInfoLine(\"Resolution:\", 12, \"%dx%d@%dhz [%s]\", current.w, current.h, current.refresh_rate, r_fullscreen.integer ? \"fullscreen\" : \"windowed\");\n\tGL_PrintInfoLine(\"Format:\", 12, \"%2d-bit color\\n%2d-bit z-buffer\\n%2d-bit stencil\", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits);\n\tif (GL_FramebufferEnabled3D()) {\n\t\tGL_PrintInfoLine(\"Framebuffer:\", 12, \"%dx%d,%s\\n\", GL_FrameBufferWidth(framebuffer_std), GL_FrameBufferHeight(framebuffer_std), GL_FramebufferZBufferString(framebuffer_std));\n\t}\n\tif (vid.aspect) {\n\t\tGL_PrintInfoLine(\"Aspect:\", 12, \"%f%%\", vid.aspect);\n\t}\n\tif (r_conwidth.integer || r_conheight.integer) {\n\t\tGL_PrintInfoLine(\"Console Res:\", 12,\"%d x %d\", r_conwidth.integer, r_conheight.integer);\n\t}\n}\n\nvoid GL_Viewport(int x, int y, int width, int height)\n{\n\tglViewport(x, y, width, height);\n}\n\nconst char* GL_DescriptiveString(void)\n{\n\treturn (const char*)glGetString(GL_RENDERER);\n}\n\ntypedef struct {\n\tint format;\n\tint type;\n\tfloat result;\n} lightmap_benchmark_t;\n\nint LightmapBenchmarkComparison(const void* lhs_, const void* rhs_)\n{\n\tlightmap_benchmark_t* lhs = (lightmap_benchmark_t*)lhs_;\n\tlightmap_benchmark_t* rhs = (lightmap_benchmark_t*)rhs_;\n\n\tif (lhs->result < rhs->result) {\n\t\treturn -1;\n\t}\n\tif (lhs->result > rhs->result) {\n\t\treturn 1;\n\t}\n\treturn (uintptr_t)lhs < (uintptr_t)rhs ? -1 : 1;\n}\n\nstatic byte gl_lightmap_data[LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * 16];\n\nvoid GL_BenchmarkLightmapFormats(void)\n{\n\ttexture_ref texture;\n\tint format;\n\tint type;\n\tlightmap_benchmark_t results[(sizeof(image_formats) / sizeof(image_formats[0])) * (sizeof(image_types) / sizeof(image_types[0]))];\n\tint count = 0, i;\n\n\tfor (format = 0; format < sizeof(image_formats) / sizeof(image_formats[0]); ++format) {\n\t\tfor (type = 0; type < sizeof(image_types) / sizeof(image_types[0]); ++type) {\n\t\t\tdouble started;\n\t\t\tdouble finished;\n\n\t\t\tif (!image_formats[format].benchmark_bpp || !image_types[type].benchmark_bpp || image_types[type].benchmark_bpp > image_formats[format].benchmark_bpp) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tmemset(gl_lightmap_data, 255, sizeof(gl_lightmap_data));\n\n\t\t\tGL_ProcessErrors(\"Pre-init\");\n\n\t\t\tGL_CreateTexturesWithIdentifier(texture_type_2d, 1, &texture, \"lightmap-benchmark\");\n\t\t\trenderer.TextureUnitBind(0, texture);\n\t\t\tglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, 0, image_formats[format].value, image_types[type].value, gl_lightmap_data);\n\t\t\tif (glGetError() != GL_NO_ERROR) {\n\t\t\t\tCon_Printf(\"%s/%s: error\\n\", image_formats[format].name, image_types[type].name);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\trenderer.TextureSetFiltering(texture, texture_minification_linear, texture_magnification_linear);\n\t\t\trenderer.TextureWrapModeClamp(texture);\n\t\t\tglFinish();\n\n\t\t\tmemset(gl_lightmap_data, 0, sizeof(gl_lightmap_data));\n\t\t\tstarted = Sys_DoubleTime();\n\t\t\tfor (i = 0; i < 1000; ++i) {\n\t\t\t\tglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, image_formats[format].value, image_types[type].value, gl_lightmap_data);\n\t\t\t}\n\t\t\tglFinish();\n\t\t\tfinished = Sys_DoubleTime();\n\n\t\t\tGL_ProcessErrors(\"Post-test\");\n\t\t\trenderer.TextureDelete(texture);\n\n\t\t\tresults[count].format = format;\n\t\t\tresults[count].type = type;\n\t\t\tresults[count].result = (finished - started) * 1000.0f;\n\t\t\t++count;\n\t\t}\n\t}\n\n\tqsort(results, count, sizeof(results[0]), LightmapBenchmarkComparison);\n\n\tCon_Printf(\"Results:\\n\");\n\tfor (i = 0; i < count; ++i) {\n\t\tqbool preferred;\n\t\tqbool current;\n\t\tchar label[20];\n\n\t\tformat = results[i].format;\n\t\ttype = results[i].type;\n\n\t\tstrlcpy(label, image_formats[format].name, sizeof(label));\n\t\tstrlcat(label, \"/\", sizeof(label));\n\t\tstrlcat(label, image_types[type].name, sizeof(label));\n\n\t\tpreferred = (image_formats[format].value == glConfig.preferred_format && image_types[type].value == glConfig.preferred_type);\n\t\tcurrent = (GL_Supported(R_SUPPORT_BGRA_LIGHTMAPS) ? GL_BGRA : GL_RGBA) == image_formats[format].value &&\n\t\t          (GL_Supported(R_SUPPORT_INT8888R_LIGHTMAPS) ? GL_UNSIGNED_INT_8_8_8_8_REV : GL_UNSIGNED_BYTE) == image_types[type].value;\n\n\t\tCon_Printf(\"%s %02d %-20s: %8.3fms%s&r\\n\", current ? \"&c0f0>>>\" : \"   \", i + 1, label, results[i].result, current && preferred ? \" <<< preferred\" : preferred ? \" &cff0<<< preferred\" : \"\");\n\t}\n}\n"
  },
  {
    "path": "src/gl_model.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef __MODEL__\n#define __MODEL__\n\n#include \"modelgen.h\"\n#include \"spritegn.h\"\n#include \"bspfile.h\"\n\n#define VBO_FIELDOFFSET(type, field) (void*)((uintptr_t)&(((type*)0)->field))\n#define VK_VBO_FIELDOFFSET(type, field) (uint32_t)((uintptr_t)&(((type*)0)->field))\n\n/*\nd*_t structures are on-disk representations\nm*_t structures are in-memory\n*/\n\n\n/*\n==============================================================================\n\nBRUSH MODELS\n\n==============================================================================\n*/\n\n\n//\n// in memory representation\n//\n// !!! if this is changed, it must be changed in asm_draw.h too !!!\ntypedef struct mvertex_s {\n\tvec3_t\t\tposition;\n} mvertex_t;\n\ntypedef struct texture_s {\n\tchar                name[16];\n\tunsigned int        width, height;\n\ttexture_ref         gl_texturenum;\n\ttexture_ref         fb_texturenum;\t\t\t\t//index of fullbright mask or 0\n\tstruct msurface_s*  texturechain;\n\tstruct msurface_s** texturechain_tail;          //Points to the node right after the last non-NULL node in the texturechain.\n\n\tint                 anim_total;                 //total tenths in sequence ( 0 = no)\n\tint                 anim_min, anim_max;         //time for this frame min <=time< max\n\tstruct texture_s*   anim_next;                  //in the animation sequence\n\tstruct texture_s*   alternate_anims;            //bmodels in frame 1 use these\n\tunsigned int        offsets[MIPLEVELS];         //four mip maps stored\n\tunsigned int        flatcolor3ub;               //just for r_fastturb's sake\n\tqbool               loaded;                     //help speed up vid_restart, actual only for brush models\n\tint                 isLumaTexture;              //fb is luma texture, rather than normal fb\n\tqbool               isAlphaTested;              //texture is flagged for alpha-testing\n\tqbool               isLitTurb;                  //turb texture has lightmap\n\tint                 turbType;\n\n\tint                 next_same_size;\n\tqbool               size_start;\n\n\ttexture_ref         gl_texture_array;\n\tunsigned int        gl_texture_index;\n\tfloat               gl_texture_scaleS;\n\tfloat               gl_texture_scaleT;\n\n\tint                 index;\n} texture_t;\n\n\n#define\tSURF_PLANEBACK\t\t2\n#define\tSURF_DRAWSKY\t\t4\n#define SURF_DRAWSPRITE\t\t8\n#define SURF_DRAWTURB\t\t16\n#define SURF_DRAWTILED\t\t32\n#define SURF_DRAWBACKGROUND\t64\n#define SURF_UNDERWATER\t\t128\n#define SURF_DRAWALPHA\t\t256\n#define SURF_DRAWFLAT_FLOOR 512\n\n// !!! if this is changed, it must be changed in asm_draw.h too !!!\ntypedef struct medge_s {\n\tunsigned int\t\tv[2];\n\tunsigned int\t\tcachededgeoffset;\n} medge_t;\n\ntypedef struct mtexinfo_s {\n\tfloat         vecs[2][4];\n\ttexture_t     *texture;\n\tint           miptex;\n\tint           flags;\n\tqbool         skippable;\n\tint           surfaces;\n} mtexinfo_t;\n\n// Filled in CopyVertToBuffer in glm_brushmodel\ntypedef struct vbo_world_vert_s {\n\t// Positions 0/4/8\n\tvec3_t position;\n\n\t// Positions 12/16/20\n\tvec3_t material_coords;\n\n\t// Positions 24/28/32\n\tvec3_t lightmap_coords;\n\n\t// Positions 36/40\n\tfloat detail_coords[2];\n\n\t// Positions 44/48/52\n\tfloat flatcolor[3];\n\n\t// Position 56\n\tint surface_num;\n\n\t// Position 60 (VBO_WORLD_X)\n\tint flags;\n} vbo_world_vert_t;\n\n// Trying to pad out to keep ATI/AMD happy (doesn't seem to make a difference?)\n// https://developer.amd.com/wordpress/media/2012/10/ATI_OpenGL_Programming_and_Optimization_Guide.pdf\ntypedef struct glc_vbo_world_vert_s {\n\tfloat position[4];\n\tfloat material_coords[2];\n\tfloat detail_coords[2];\n\tfloat lightmap_coords[4];\n\n\tfloat flatstyle;\n\tfloat padding[3];\n} glc_vbo_world_vert_t;\n\n// GLC only...\n// For turb surfaces, we use lightmap tex coords to store 8*sin(<s or t>), and detail coords to store 8*cos(<s or t>)\n#define VERTEXSIZE 9  //xyz s1t1 s2t2 s3t3 where xyz = vert coords; s1t1 = normal tex coords; \n\t\t\t\t\t  //s2t2 = lightmap tex coords; s3t3 = detail tex coords\n\ntypedef struct vbo_model_vert_s {\n\tvec3_t position;\n\tint lightnormalindex;\n\tvec3_t normal;\n\tint padding2;\n\tvec3_t direction;\n\tint padding3;\n\tfloat texture_coords[2];\n\tunsigned int flags;\n\tint padding4;\n} vbo_model_vert_t;\n\ntypedef struct glpoly_s {\n\tstruct\tglpoly_s\t*next;\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tstruct\tglpoly_s\t*chain;\t\t\t\t\t\t//next lightmap poly in chain\n\tstruct\tglpoly_s\t*fb_chain;\t\t\t\t\t//next fb poly in chain\n\tstruct\tglpoly_s\t*luma_chain;\t\t\t\t//next luma poly in chain\n\tstruct\tglpoly_s\t*caustics_chain;\t\t\t//next caustic poly in chain\n\tstruct\tglpoly_s\t*detail_chain;\t\t\t\t//next detail poly in chain\n#endif\n\n\tunsigned int        vbo_start;\n\tint\t\t\t\t\tnumverts;\n\tfloat\t\t\t\tverts[4][VERTEXSIZE];\t\t// variable sized (xyz s1t1 s2t2 l)\n} glpoly_t;\n\ntypedef struct msurface_s {\n\tint                 visframe;                   // should be drawn when node is crossed\n\n\tmplane_t*           plane;\n\tint                 flags;\n\n\tint                 firstedge;                  // look up in model->surfedges[], negative numbers\n\tint                 numedges;                   // are backwards edges\n\t\n\tshort               texturemins[2];\n\tshort               extents[2];\n\n\tshort               lmshift;\n\n\t// gl lightmap coordinates\n\tint                 light_s;\n\tint                 light_t;\n\n\tglpoly_t            *polys;                     // multiple if warped\n\tglpoly_t            *subdivided;\n\tstruct\tmsurface_s  *texturechain;\n\tstruct\tmsurface_s  *drawflatchain;\n\n\tmtexinfo_t          *texinfo;\n\n\t// decoupled lm\n\tfloat               lmvecs[2][4];\n\tfloat               lmvlen[2];\n\n\t// lighting info\n\tint                 dlightframe;\n\tint                 dlightbits;\n\n\t// This is not an OpenGL texture name, it's the lightmap index\n\tint                 lightmaptexturenum;\n\tint                 surfacenum;\n\n\tbyte                styles[MAXLIGHTMAPS];\n\tint                 cached_light[MAXLIGHTMAPS]; // values currently used in lightmap\n\tqbool               cached_dlight;              // true if dynamic light in cache\n\tbyte                *samples;                   // [numstyles*surfsize]\n} msurface_t;\n\ntypedef struct mnode_s {\n\t// common with leaf\n\tint\t\t\t\t\tcontents;\t\t\t\t\t// 0, to differentiate from leafs\n\tint\t\t\t\t\tvisframe;\t\t\t\t\t// node needs to be traversed if current\n\t\n\tfloat\t\t\t\tminmaxs[6];\t\t\t\t\t// for bounding box culling\n\n\tstruct mnode_s\t\t*parent;\n\n\t// node specific\n\tmplane_t\t\t\t*plane;\n\tstruct mnode_s\t\t*children[2];\t\n\n\tunsigned int\t\tfirstsurface;\n\tunsigned int\t\tnumsurfaces;\n} mnode_t;\n\ntypedef struct mleaf_s {\n\t// common with node\n\tint\t\t\t\t\tcontents;\t\t\t\t\t// wil be a negative contents number\n\tint\t\t\t\t\tvisframe;\t\t\t\t\t// node needs to be traversed if current\n\n\tfloat\t\t\t\tminmaxs[6];\t\t\t\t\t// for bounding box culling\n\n\tstruct mnode_s\t\t*parent;\n\n\t// leaf specific\n\tbyte\t\t\t\t*compressed_vis;\n\tstruct efrag_s\t\t*efrags;\n\n\tmsurface_t\t\t\t**firstmarksurface;\n\tint\t\t\t\t\tnummarksurfaces;\n} mleaf_t;\n\n\n/*\n==============================================================================\n\nSPRITE MODELS\n\n==============================================================================\n*/\n\n\n// FIXME: shorten these?\ntypedef struct mspriteframe_s {\n\tint                 width;\n\tint                 height;\n\tfloat               up, down, left, right;\n\ttexture_ref         gl_texturenum;\n\ttexture_ref         gl_arraynum;\n\tint                 gl_arrayindex;\n\n\tfloat               gl_scalingS;\n\tfloat               gl_scalingT;\n} mspriteframe_t;\n\ntypedef struct mspritegroup_s {\n\tint\t\t\t\t\tnumframes;\n\tfloat\t\t\t\t*intervals;\n\tmspriteframe_t\t\t*frames[1];\n} mspritegroup_t;\n\ntypedef struct mspriteframedesc_s {\n\tspriteframetype_t\ttype;\n\tmspriteframe_t\t\t*frameptr;\n} mspriteframedesc_t;\n\ntypedef struct msprite_s {\n\tint\t\t\t\t\ttype;\n\tint\t\t\t\t\tmaxwidth;\n\tint\t\t\t\t\tmaxheight;\n\tint\t\t\t\t\tnumframes;\n\tfloat\t\t\t\tbeamlength; // remove?\n\tvoid\t\t\t\t*cachespot; // remove?\n\tmspriteframedesc_t\tframes[1];\n} msprite_t;\n\n#define MAX_SPRITE_FRAMES (512)\n\ntypedef struct mspriteframe2_s {\n\tmspriteframe_t\t\tframe;\n\tfloat\t\t\t\tinterval;\n} mspriteframe2_t;\n\ntypedef struct mspriteframedesc2_s {\n\tspriteframetype_t\ttype;\n\n\tint\t\t\t\t\tnumframes;\t\t\t// always 1 for type == SPR_SINGLE\n\tint\t\t\t\t\toffset;\n\n} mspriteframedesc2_t;\n\ntypedef struct msprite2_s {\n\tint\t\t\t\t\ttype;\n\tint\t\t\t\t\tmaxwidth;\n\tint\t\t\t\t\tmaxheight;\n\tint\t\t\t\t\tnumframes;\n\n\tmspriteframedesc2_t\tframes[MAX_SPRITE_FRAMES];\n} msprite2_t; // actual frames follow after struct immidiately\n\n\n/*\n==============================================================================\n\nALIAS MODELS\n\nAlias models are position independent, so the cache manager can move them.\n==============================================================================\n*/\n\n\ntypedef struct maliasframedesc_s {\n\tint\t\t\t\t\tfirstpose;\n\tint\t\t\t\t\tnumposes;\n\tfloat\t\t\t\tinterval;\n\tvec3_t\t\t\t\tbboxmin;\n\tvec3_t\t\t\t\tbboxmax;\n\tfloat\t\t\t\tradius;\n\tint\t\t\t\t\tframe;\n\tchar\t\t\t\tname[16];\n\tchar                groupname[16];\n\tint                 groupnumber;\n\tint                 nextpose;\n} maliasframedesc_t;\n\ntypedef struct maliasgroupframedesc_s {\n\ttrivertx_t\t\t\tbboxmin;\n\ttrivertx_t\t\t\tbboxmax;\n\tint\t\t\t\t\tframe;\n} maliasgroupframedesc_t;\n\ntypedef struct maliasgroup_s {\n\tint\t\t\t\t\tnumframes;\n\tint\t\t\t\t\tintervals;\n\tmaliasgroupframedesc_t frames[1];\n} maliasgroup_t;\n\n// !!! if this is changed, it must be changed in asm_draw.h too !!!\ntypedef struct mtriangle_s {\n\tint\t\t\t\t\tfacesfront;\n\tint\t\t\t\t\tvertindex[3];\n} mtriangle_t;\n\n\n#define MAX_SKINS 32\ntypedef struct aliashdr_s {\n\tint          ident;\n\tint          version;\n\tvec3_t       scale;\n\tvec3_t       scale_origin;\n\tfloat        boundingradius;\n\tvec3_t       eyeposition;\n\tint          numskins;\n\tint          skinwidth;\n\tint          skinheight;\n\tint          numverts;\n\tint          numtris;\n\tint          numframes;\n\tsynctype_t   synctype;\n\tint          flags;\n\tfloat        size;\n\n\tint          numposes;\n\tint          poseverts;\n\tint          posedata;\t// numposes*poseverts ez_trivertx_t\n\tint          commands;\t// gl command list with embedded s/t\n\ttexture_ref  gl_texturenum[MAX_SKINS][4];\n\ttexture_ref  glc_fb_texturenum[MAX_SKINS][4];\n\n\tint          vertsPerPose;\n\n\tmaliasframedesc_t\tframes[1];\t// variable sized\n} aliashdr_t;\n\n//===================================================================\n\n//\n// Whole model\n//\n\n#define\tMAXALIASVERTS\t2048\n#define\tMAXALIASFRAMES\t256\n#define\tMAXALIASTRIS\t2048\nextern\taliashdr_t\t\t*pheader;\nextern\tstvert_t\t\tstverts[MAXALIASVERTS];\nextern\tmtriangle_t\t\ttriangles[MAXALIASTRIS];\nextern\ttrivertx_t\t\t*poseverts[MAXALIASFRAMES];\n\n// some models are special\ntypedef enum \n{\n\tMOD_NORMAL, \n\tMOD_PLAYER, \n\tMOD_EYES, \n\tMOD_FLAME,\n\tMOD_THUNDERBOLT,\n\tMOD_BACKPACK,\n\tMOD_FLAG,\n\tMOD_SPIKE, \n\tMOD_TF_TRAIL,\n\tMOD_GRENADE, \n\tMOD_TESLA, \n\tMOD_SENTRYGUN, \n\tMOD_DETPACK,\n\tMOD_LASER, \n\tMOD_DEMON, \n\tMOD_SOLDIER,\n\tMOD_OGRE,\n\tMOD_ENFORCER,\n\tMOD_VOORSPIKE,\n\tMOD_LAVABALL,\n\tMOD_RAIL, \n\tMOD_RAIL2,\n\tMOD_BUILDINGGIBS,\n\tMOD_CLUSTER,\n\tMOD_SHAMBLER, \n\tMOD_TELEPORTDESTINATION,\n\tMOD_GIB,\n\tMOD_VMODEL,\n\tMOD_ROCKET,\n\tMOD_ARMOR,\n\tMOD_MEGAHEALTH,\n\tMOD_ROCKETLAUNCHER,\n\tMOD_LIGHTNINGGUN,\n\tMOD_QUAD,\n\tMOD_PENT,\n\tMOD_RING,\n\tMOD_FLAME0,\n\tMOD_FLAME2,\n\tMOD_FLAME3,\n\tMOD_SUIT,\n\tMOD_GRENADELAUNCHER,\n\n\tMOD_NUMBER_HINTS\n} modhint_t;\n\n#define\tEF_ROCKET\t1\t\t\t// leave a trail\n#define\tEF_GRENADE\t2\t\t\t// leave a trail\n#define\tEF_GIB\t\t4\t\t\t// leave a trail\n#define\tEF_ROTATE\t8\t\t\t// rotate (bonus items)\n#define\tEF_TRACER\t16\t\t\t// green split trail\n#define\tEF_ZOMGIB\t32\t\t\t// small blood trail\n#define\tEF_TRACER2\t64\t\t\t// orange split trail + rotate\n#define\tEF_TRACER3\t128\t\t\t// purple trail\n\n#define MOD_HDRLIGHTING (1u<<13)\t//spike -- light samples are in e5bgr9 format. int aligned.\n\n#define MAX_SIMPLE_TEXTURES 5\n#define MAX_TEXTURE_ARRAYS_PER_MODEL 64\n\ntypedef struct worldspawn_info_s {\n\tchar skybox_name[MAX_QPATH];\n\tfloat fog_density;\n\tvec3_t fog_color;\n\tfloat fog_sky;\n\n\tfloat wateralpha;\n\tfloat lavaalpha;\n\tfloat telealpha;\n\tfloat slimealpha;\n} worldspawn_info_t;\n\ntypedef struct model_s {\n\tchar\t\t\t\tname[MAX_QPATH];\n\tqbool\t\t\t\tneedload; // bmodels and sprites don't cache normally\n\n\tunsigned short\t\tcrc;\n\n\ttexture_ref         simpletexture[MAX_SIMPLE_TEXTURES]; // for simpleitmes\n\n\tmodhint_t\t\t\tmodhint;\n\tmodhint_t\t\t\tmodhint_trail;\n\n\tmodtype_t\t\t\ttype;\n\tint\t\t\t\t\tnumframes;\n\tsynctype_t\t\t\tsynctype;\n\t\n\tint\t\t\t\t\tflags;\n\n\t// volume occupied by the model graphics\n\tvec3_t\t\t\t\tmins, maxs;\n\tfloat\t\t\t\tradius;\n\n\t// brush model\n\tint\t\t\t\t\tfirstmodelsurface, nummodelsurfaces;\n\n\t// FIXME, don't really need these two\n\tint\t\t\t\t\tnumsubmodels;\n\tdmodel_t\t\t\t*submodels;\n\n\tint\t\t\t\t\tnumplanes;\n\tmplane_t\t\t\t*planes;\n\n\tint\t\t\t\t\tnumleafs; // number of visible leafs, not counting 0\n\tmleaf_t\t\t\t\t*leafs;\n\n\tint\t\t\t\t\tnumvertexes;\n\tmvertex_t\t\t\t*vertexes;\n\n\tint\t\t\t\t\tnumedges;\n\tmedge_t\t\t\t\t*edges;\n\n\tint\t\t\t\t\tnumnodes;\n\tint\t\t\t\t\tfirstnode;\n\tmnode_t\t\t\t\t*nodes;\n\n\tint\t\t\t\t\tnumtexinfo;\n\tmtexinfo_t\t\t\t*texinfo;\n\n\tint\t\t\t\t\tnumsurfaces;\n\tmsurface_t\t\t\t*surfaces;\n\n\tint\t\t\t\t\tnumsurfedges;\n\tint\t\t\t\t\t*surfedges;\n\n\tint\t\t\t\t\tnummarksurfaces;\n\tmsurface_t\t\t\t**marksurfaces;\n\n\tint\t\t\t\t\tnumtextures;\n\ttexture_t\t\t\t**textures;\n\n\tbyte*               visdata;\n\tint                 visdata_length;\n\tbyte*               lightdata;\n\tsize_t              lightdatasamplesize;\n\n\tint\t\t\t\t\tbspversion;\n\tqbool\t\t\t\tisworldmodel;\n\n\t// additional model data\n\tvoid*               cached_data;\n\n\ttexture_ref         texture_arrays[MAX_TEXTURE_ARRAYS_PER_MODEL];\n\tfloat               texture_arrays_scale_s[MAX_TEXTURE_ARRAYS_PER_MODEL];\n\tfloat               texture_arrays_scale_t[MAX_TEXTURE_ARRAYS_PER_MODEL];\n\tunsigned int        texture_array_count;\n\tint                 texture_array_first[MAX_TEXTURE_ARRAYS_PER_MODEL];\n\n\tvoid*               temp_vbo_buffer;\n\tfloat               min_tex[2];\n\tfloat               max_tex[2];\n\n\tunsigned int        vbo_start;\n\tint                 vertsInVBO;\n\n\ttexture_ref         simpletexture_array[MAX_SIMPLE_TEXTURES];\n\tint                 simpletexture_indexes[MAX_SIMPLE_TEXTURES];\n\tfloat               simpletexture_scalingS[MAX_SIMPLE_TEXTURES];\n\tfloat               simpletexture_scalingT[MAX_SIMPLE_TEXTURES];\n\n\tmsurface_t*         drawflat_chain;\n\tqbool               drawflat_todo;\n\tqbool               alphapass_todo;\n\tint                 first_texture_chained;\n\tint                 last_texture_chained;\n\tqbool               texturechains_have_lumas;\n\n\tint                 renderfx;\n} model_t;\n\n//============================================================================\n\nvoid\tMod_Init (void);\nvoid\tMod_ClearAll (void);\nmodel_t *Mod_ForName (const char *name, qbool crash);\nvoid\t*Mod_Extradata (model_t *mod); // handles caching\nvoid\tMod_TouchModel (char *name);\nvoid\tMod_TouchModels (void); // for vid_restart\nvoid Mod_ReloadModels(qbool vid_restart);\nvoid Mod_FreeAllCachedData(void);\n\nmleaf_t *Mod_PointInLeaf(vec3_t p, model_t *model);\nbyte\t*Mod_LeafPVS(mleaf_t *leaf, model_t *model);\n\nqbool\tImg_HasFullbrights (byte *pixels, int size);\nvoid\tMod_ReloadModelsTextures (void); // for vid_restart\n\ntexture_ref Mod_LoadSimpleTexture(model_t *mod, int skinnum);\nvoid    Mod_ClearSimpleTextures(void);\n\nqbool Mod_IsAlphaTextureName(model_t* model, const char* name);\nqbool Mod_IsSkyTextureName(model_t* model, const char* name);\nqbool Mod_IsTurbTextureName(model_t* model, const char* name);\n\nfloat RadiusFromBounds(vec3_t mins, vec3_t maxs);\nvoid Mod_AddModelFlags(model_t *mod);\nvoid R_LoadBrushModelTextures(model_t *m);\nvoid R_BrushModelPolygonToTriangleStrip(glpoly_t* poly);\n\n#include \"r_aliasmodel_md3.h\"\n\ntypedef enum {\n\tcustom_model_explosion,\n\tcustom_model_bolt,\n\tcustom_model_bolt2,\n\tcustom_model_bolt3,\n\tcustom_model_beam,\n\tcustom_model_flame0,\n\n\tcustom_model_count\n} custom_model_id_t;\n\nextern model_t* cl_custommodels[custom_model_count];\n\nmodel_t* Mod_CustomModel(custom_model_id_t id, qbool crash);\n\nint Mod_ExpectedNextFrame(model_t* mod, int framenum, int framecount);\n\n// aliasmodels\n#define NUMVERTEXNORMALS    162\n#define SHADEDOT_QUANT       64\n\n#define MD3_INTERP_MAXDIST  300\n\n// brushmodel\nextern vec3_t modelorg;\n// \nextern int      r_visframecount;\nextern int      r_framecount;\nextern mplane_t frustum[4];\n\n// sky\n#define BACKFACE_EPSILON\t0.01\n\n\n#define DEFAULT_LMSHIFT     4\n\n#endif\t// __MODEL__\n"
  },
  {
    "path": "src/gl_program.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// GL_program.c\n// - All glsl program-related code\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"r_program.h\"\n#include \"tr_types.h\"\n#include \"glsl/constants.glsl\"\n\ntypedef enum {\n\trenderer_classic = 1,\n\trenderer_modern,\n} renderer_id;\n\n#define STDOPTIONS_FOG_LINEAR     (1 << 0)\n#define STDOPTIONS_FOG_EXP        (1 << 1)\n#define STDOPTIONS_FOG_EXP2       (1 << 2)\n\n#define STDOPTIONS_NONE           (0)\n#define STDOPTIONS_FOG            (STDOPTIONS_FOG_LINEAR | STDOPTIONS_FOG_EXP | STDOPTIONS_FOG_EXP2)\n\n#define GL_DefineProgram_VF(program_id, name, expect_params, sourcename, renderer, compile_function, standard_flags) \\\n\t{ \\\n\t\textern unsigned char sourcename##_vertex_glsl[]; \\\n\t\textern unsigned int sourcename##_vertex_glsl_len; \\\n\t\textern unsigned char sourcename##_fragment_glsl[]; \\\n\t\textern unsigned int sourcename##_fragment_glsl_len; \\\n\t\textern qbool compile_function(void); \\\n\t\tint i; \\\n\\\n\t\tfor (i = 0; i < MAX_SUBPROGRAMS; ++i) { \\\n\t\t\tgl_program_t* prog = R_SpecificSubProgram((program_id), i); \\\n\t\t\tmemset(&prog->shaders, 0, sizeof(prog->shaders)); \\\n\t\t\tstrlcpy(prog->friendly_name, va(\"%s[%d]\", (name), i), sizeof(prog->friendly_name)); \\\n\t\t\tprog->needs_params = expect_params; \\\n\t\t\tprog->shaders[shadertype_vertex].text = (const char*)sourcename##_vertex_glsl; \\\n\t\t\tprog->shaders[shadertype_vertex].length = sourcename##_vertex_glsl_len; \\\n\t\t\tprog->shaders[shadertype_fragment].text = (const char*)sourcename##_fragment_glsl; \\\n\t\t\tprog->shaders[shadertype_fragment].length = sourcename##_fragment_glsl_len; \\\n\t\t\tprog->initialised = true; \\\n\t\t\tprog->renderer_id = renderer; \\\n\t\t\tprog->compile_func = compile_function; \\\n\t\t\tprog->standard_option_flags = standard_flags; \\\n\t\t} \\\n\t}\n\n#define GL_DefineProgram_CS(program_id, name, expect_params, sourcename, renderer, compile_function, option_flags) \\\n\t{ \\\n\t\textern unsigned char sourcename##_compute_glsl[]; \\\n\t\textern unsigned int sourcename##_compute_glsl_len; \\\n\t\textern qbool compile_function(void); \\\n\t\tint i; \\\n\\\n\t\tfor (i = 0; i < MAX_SUBPROGRAMS; ++i) { \\\n\t\t\tgl_program_t* prog = R_SpecificSubProgram((program_id), i); \\\n\t\t\tmemset(prog->shaders, 0, sizeof(prog->shaders)); \\\n\t\t\tstrlcat(prog->friendly_name, name, sizeof(prog->friendly_name)); \\\n\t\t\tprog->needs_params = expect_params; \\\n\t\t\tprog->shaders[shadertype_compute].text = (const char*)sourcename##_compute_glsl; \\\n\t\t\tprog->shaders[shadertype_compute].length = sourcename##_compute_glsl_len; \\\n\t\t\tprog->initialised = true; \\\n\t\t\tprog->renderer_id = renderer; \\\n\t\t\tprog->compile_func = compile_function; \\\n\t\t\tprog->standard_option_flags = option_flags; \\\n\t\t} \\\n\t}\n\nstatic void GL_BuildCoreDefinitions(void);\n\nstatic const GLenum glBarrierFlags[] = {\n\tGL_SHADER_IMAGE_ACCESS_BARRIER_BIT,\n\tGL_TEXTURE_FETCH_BARRIER_BIT\n};\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(glBarrierFlags) / sizeof(glBarrierFlags[0]) == r_program_memory_barrier_count);\n#endif\n\nenum {\n\tshadertype_vertex,\n\tshadertype_fragment,\n\tshadertype_geometry,\n\tshadertype_compute,\n\n\tshadertype_count\n};\n\ntypedef struct gl_shader_def_s {\n\tconst char* text;\n\tunsigned int length;\n} gl_shader_def_t;\n\ntypedef qbool(*program_compile_func_t)(void);\n\n#define MAX_SUBPROGRAMS 32\n#define R_CurrentSubProgram(program_id) (&program_data[(program_id)][program_currentSubProgram[(program_id)]])\n#define R_SpecificSubProgram(program_id, sub_id) (&program_data[(program_id)][(sub_id)])\n\ntypedef struct {\n\tqbool found;\n\tint location;\n\tint int_value;\n} gl_program_uniform_t;\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\ntypedef struct {\n\tqbool found;\n\tGLint location;\n} gl_program_attribute_t;\n#endif\n\ntypedef enum {\n\tr_program_std_uniform_fog_linear_start,\n\tr_program_std_uniform_fog_linear_end,\n\tr_program_std_uniform_fog_density,\n\tr_program_std_uniform_fog_color,\n\tr_program_std_uniform_count\n} r_program_std_uniform_id;\n\ntypedef struct {\n\tr_program_id program_id;\n\tconst char* name;\n\tint count;\n\tqbool transpose;\n} r_program_uniform_t;\n\nstatic r_program_uniform_t program_std_uniforms[] = {\n\t// r_program_std_uniform_fog_linear_start\n\t{ r_program_none, \"fogMinZ\", 1, false },\n\t// r_program_std_uniform_fog_linear_end,\n\t{ r_program_none, \"fogMaxZ\", 1, false },\n\t// r_program_std_uniform_fog_density,\n\t{ r_program_none, \"fogDensity\", 1, false },\n\t// r_program_std_uniform_fog_color,\n\t{ r_program_none, \"fogColor\", 1, false }\n};\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(program_std_uniforms) / sizeof(program_std_uniforms[0]) == r_program_std_uniform_count);\n#endif\n\ntypedef struct gl_program_s {\n\tchar friendly_name[128];\n\tqbool needs_params;\n\tprogram_compile_func_t compile_func;\n\tqbool initialised;\n\tgl_shader_def_t shaders[shadertype_count];\n\n\tGLuint shader_handles[shadertype_count];\n\tGLuint program;\n\tGLbitfield memory_barrier;\n\n\tchar* included_definitions;\n\tgl_program_uniform_t uniforms[r_program_uniform_count];\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tgl_program_attribute_t attributes[r_program_attribute_count];\n#endif\n\n\tunsigned int custom_options;\n\tqbool force_recompile;\n\trenderer_id renderer_id;\n\tunsigned int standard_options;\n\tunsigned int standard_option_flags;\n\tgl_program_uniform_t std_uniforms[r_program_std_uniform_count];\n} gl_program_t;\n\nstatic r_program_uniform_t program_uniforms[] = {\n\t// r_program_uniform_aliasmodel_drawmode\n\t{ r_program_aliasmodel, \"mode\", 1, false },\n\t// r_program_uniform_brushmodel_outlines\n\t{ r_program_brushmodel, \"draw_outlines\", 1, false },\n\t// r_program_uniform_brushmodel_sampler\n\t{ r_program_brushmodel, \"SamplerNumber\", 1, false },\n\t// r_program_uniform_sprite3d_alpha_test\n\t{ r_program_sprite3d, \"alpha_test\", 1, false },\n\t// r_program_uniform_hud_circle_matrix\n\t{ r_program_hud_circles, \"matrix\", 1, false },\n\t// r_program_uniform_hud_circle_color\n\t{ r_program_hud_circles, \"color\", 1, false },\n\t// r_program_uniform_post_process_glc_gamma\n\t{ r_program_post_process_glc, \"gamma\", 1, false },\n\t// r_program_uniform_post_process_glc_base,\n\t{ r_program_post_process_glc, \"base\", 1, false },\n\t// r_program_uniform_post_process_glc_overlay,\n\t{ r_program_post_process_glc, \"overlay\", 1, false },\n\t// r_program_uniform_post_process_glc_v_blend,\n\t{ r_program_post_process_glc, \"v_blend\", 1, false },\n\t// r_program_uniform_post_process_glc_contrast,\n\t{ r_program_post_process_glc, \"contrast\", 1, false },\n\t// r_program_uniform_post_process_glc_r_inv_width,\n\t{ r_program_post_process_glc, \"r_inv_width\", 1, false },\n\t// r_program_uniform_post_process_glc_r_inv_height,\n\t{ r_program_post_process_glc, \"r_inv_height\", 1, false },\n\t// r_program_uniform_sky_glc_cameraPosition,\n\t{ r_program_sky_glc, \"cameraPosition\", 1, false },\n\t// r_program_uniform_sky_glc_speedscale,\n\t{ r_program_sky_glc, \"skySpeedscale\", 1, false },\n\t// r_program_uniform_sky_glc_speedscale2,\n\t{ r_program_sky_glc, \"skySpeedscale2\", 1, false },\n\t// r_program_uniform_sky_glc_skyTex,\n\t{ r_program_sky_glc, \"skyTex\", 1, false },\n\t// r_program_uniform_sky_glc_skyWind,\n\t{ r_program_sky_glc, \"skyWind\", 1, false },\n\t// r_program_uniform_sky_glc_skyDomeTex,\n\t{ r_program_sky_glc, \"skyDomeTex\", 1, false },\n\t// r_program_uniform_sky_glc_skyDomeCloudTex,\n\t{ r_program_sky_glc, \"skyDomeCloudTex\", 1, false },\n\t// r_program_uniform_turb_glc_texSampler,\n\t{ r_program_turb_glc, \"texSampler\", 1, false },\n\t// r_program_uniform_turb_glc_time,\n\t{ r_program_turb_glc, \"time\", 1, false },\n\t// r_program_uniform_caustics_glc_texSampler,\n\t{ r_program_caustics_glc, \"texSampler\", 1, false },\n\t// r_program_uniform_caustics_glc_time,\n\t{ r_program_caustics_glc, \"time\", 1, false },\n\t// r_program_uniform_aliasmodel_std_glc_angleVector,\n\t{ r_program_aliasmodel_std_glc, \"angleVector\", 1, false },\n\t// r_program_uniform_aliasmodel_std_glc_shadelight,\n\t{ r_program_aliasmodel_std_glc, \"shadelight\", 1, false },\n\t// r_program_uniform_aliasmodel_std_glc_ambientlight,\n\t{ r_program_aliasmodel_std_glc, \"ambientlight\", 1, false },\n\t// r_program_uniform_aliasmodel_std_glc_texSampler,\n\t{ r_program_aliasmodel_std_glc, \"texSampler\", 1, false },\n\t// r_program_uniform_aliasmodel_std_glc_fsTextureEnabled,\n\t{ r_program_aliasmodel_std_glc, \"fsTextureEnabled\", 1, false },\n\t// r_program_uniform_aliasmodel_std_glc_fsMinLumaMix,\n\t{ r_program_aliasmodel_std_glc, \"fsMinLumaMix\", 1, false },\n\t// r_program_uniform_aliasmodel_std_glc_time,\n\t{ r_program_aliasmodel_std_glc, \"time\", 1, false },\n\t// r_program_uniform_aliasmodel_std_glc_fsCausticEffects\n\t{ r_program_aliasmodel_std_glc, \"fsCausticEffects\", 1, false },\n\t// r_program_uniform_aliasmodel_std_glc_lerpFraction\n\t{ r_program_aliasmodel_std_glc, \"lerpFraction\", 1, false },\n\t// r_program_uniform_aliasmodel_std_glc_causticsSampler\n\t{ r_program_aliasmodel_std_glc, \"causticsSampler\", 1, false },\n\t// r_program_uniform_aliasmodel_shell_glc_fsBaseColor1,\n\t{ r_program_aliasmodel_shell_glc, \"fsBaseColor1\", 1, false },\n\t// r_program_uniform_aliasmodel_shell_glc_fsBaseColor2,\n\t{ r_program_aliasmodel_shell_glc, \"fsBaseColor2\", 1, false },\n\t// r_program_uniform_aliasmodel_shell_glc_texSampler,\n\t{ r_program_aliasmodel_shell_glc, \"texSampler\", 1, false },\n\t// r_program_uniform_aliasmodel_shell_glc_lerpFraction,\n\t{ r_program_aliasmodel_shell_glc, \"lerpFraction\", 1, false },\n\t// r_program_uniform_aliasmodel_shell_glc_scroll,\n\t{ r_program_aliasmodel_shell_glc, \"scroll\", 1, false },\n\t// r_program_uniform_aliasmodel_shadow_glc_lerpFraction,\n\t{ r_program_aliasmodel_shadow_glc, \"lerpFraction\", 1, false },\n\t// r_program_uniform_aliasmodel_shadow_glc_shadevector,\n\t{ r_program_aliasmodel_shadow_glc, \"shadevector\", 1, false },\n\t// r_program_uniform_aliasmodel_shadow_glc_lheight,\n\t{ r_program_aliasmodel_shadow_glc, \"lheight\", 1, false },\n\t// r_program_uniform_world_drawflat_glc_wallcolor,\n\t{ r_program_world_drawflat_glc, \"wallcolor\", 1, false },\n\t// r_program_uniform_world_drawflat_glc_floorcolor,\n\t{ r_program_world_drawflat_glc, \"floorcolor\", 1, false },\n\t// r_program_uniform_world_drawflat_glc_skycolor,\n\t{ r_program_world_drawflat_glc, \"skycolor\", 1, false },\n\t// r_program_uniform_world_drawflat_glc_watercolor,\n\t{ r_program_world_drawflat_glc, \"watercolor\", 1, false },\n\t// r_program_uniform_world_drawflat_glc_slimecolor,\n\t{ r_program_world_drawflat_glc, \"slimecolor\", 1, false },\n\t// r_program_uniform_world_drawflat_glc_lavacolor,\n\t{ r_program_world_drawflat_glc, \"lavacolor\", 1, false },\n\t// r_program_uniform_world_drawflat_glc_telecolor,\n\t{ r_program_world_drawflat_glc, \"telecolor\", 1, false },\n\t// r_program_uniform_world_textured_glc_lightmapSampler,\n\t{ r_program_world_textured_glc, \"lightmapSampler\", 1, false },\n\t// r_program_uniform_world_textured_glc_texSampler,\n\t{ r_program_world_textured_glc, \"texSampler\", 1, false },\n\t// r_program_uniform_world_textured_glc_lumaSampler,\n\t{ r_program_world_textured_glc, \"lumaSampler\", 1, false },\n\t// r_program_uniform_world_textured_glc_causticSampler\n\t{ r_program_world_textured_glc, \"causticsSampler\", 1, false },\n\t// r_program_uniform_world_textured_glc_detailSampler\n\t{ r_program_world_textured_glc, \"detailSampler\", 1, false },\n\t// r_program_uniform_world_textured_glc_time\n\t{ r_program_world_textured_glc, \"time\", 1, false },\n\t// r_program_uniform_world_textured_glc_lumaScale\n\t{ r_program_world_textured_glc, \"lumaMultiplier\", 1, false },\n\t// r_program_uniform_world_textured_glc_fbScale\n\t{ r_program_world_textured_glc, \"fbMultiplier\", 1, false },\n\t// r_program_uniform_world_textured_glc_r_floorcolor\n\t{ r_program_world_textured_glc, \"r_floorcolor\", 1, false },\n\t// r_program_uniform_world_textured_glc_r_wallcolor\n\t{ r_program_world_textured_glc, \"r_wallcolor\", 1, false },\n\t// r_program_uniform_sprites_glc_materialSampler,\n\t{ r_program_sprites_glc, \"materialSampler\", 1, false },\n\t// r_program_uniform_sprites_glc_alphaThreshold,\n\t{ r_program_sprites_glc, \"alphaThreshold\", 1, false },\n\t// r_program_uniform_hud_images_glc_primarySampler,\n\t{ r_program_hud_images_glc, \"primarySampler\", 1, false },\n\t// r_program_uniform_hud_images_glc_secondarySampler\n\t{ r_program_hud_images_glc, \"secondarySampler\", 1, false },\n\t// r_program_uniform_aliasmodel_outline_glc_lerpFraction\n\t{ r_program_aliasmodel_outline_glc, \"lerpFraction\", 1, false },\n\t// r_program_uniform_aliasmodel_outline_glc_outlineScale\n\t{ r_program_aliasmodel_outline_glc, \"outlineScale\", 1, false },\n\t// r_program_uniform_brushmodel_alphatested_outlines,\n\t{ r_program_brushmodel_alphatested, \"draw_outlines\", 1, false },\n\t// r_program_uniform_brushmodel_alphatested_sampler,\n\t{ r_program_brushmodel_alphatested, \"SamplerNumber\", 1, false },\n\t// r_program_uniform_turb_glc_alpha\n\t{ r_program_turb_glc, \"alpha\", 1, false },\n\t// r_program_uniform_turb_glc_color\n\t{ r_program_turb_glc, \"color\", 1, false },\n\t// r_program_uniform_simple_color\n\t{ r_program_simple, \"color\", 1, false },\n\t// r_program_uniform_world_textures_glc_texture_multiplier\n\t{ r_program_world_textured_glc, \"texture_multiplier\", 1, false },\n\t// r_program_uniform_simple3d_color\n\t{ r_program_simple3d, \"color\", 1, false },\n\t// r_program_uniform_lighting_firstLightmap,\n\t{ r_program_lightmap_compute, \"firstLightmap\", 1, false },\n\t// r_program_uniform_turb_glc_fog_skyFogMix,\n\t{ r_program_sky_glc, \"skyFogMix\", 1, false },\n\t// r_program_uniform_outline_color\n\t{ r_program_fx_world_geometry, \"outline_color\", 1, false },\n\t// r_program_uniform_outline_depth_threshold\n\t{ r_program_fx_world_geometry, \"outline_depth_threshold\", 1, false },\n\t// r_program_uniform_outline_normal_threshold\n\t{ r_program_fx_world_geometry, \"outline_normal_threshold\", 1, false },\n\t// r_program_uniform_outline_scale\n\t{ r_program_fx_world_geometry, \"outline_scale\", 1, false },\n\t// r_program_uniform_aliasmodel_outline_color_model\n\t{ r_program_aliasmodel, \"outline_color\", 1, false },\n\t// r_program_uniform_aliasmodel_outline_color_team\n\t{ r_program_aliasmodel, \"outline_color_team\", 1, false },\n\t// r_program_uniform_aliasmodel_outline_color_enemy\n\t{ r_program_aliasmodel, \"outline_color_enemy\", 1, false },\n\t// r_program_uniform_aliasmodel_outline_use_player_color\n\t{ r_program_aliasmodel, \"outline_use_player_color\", 1, false },\n\t// r_program_uniform_aliasmodel_outline_scale\n\t{ r_program_aliasmodel, \"outline_scale\", 1, false },\n\t// r_program_uniform_brushmodel_detailtex\n\t{ r_program_brushmodel, \"detailTex\", 1, false },\n\t// r_program_uniform_brushmodel_causticstex\n\t{ r_program_brushmodel, \"causticsTex\", 1, false },\n\t// r_program_uniform_brushmodel_skytex\n\t{ r_program_brushmodel, \"skyTex\", 1, false },\n\t// r_program_uniform_brushmodel_skydometex\n\t{ r_program_brushmodel, \"skyDomeTex\", 1, false },\n\t// r_program_uniform_brushmodel_skydomecloudtex\n\t{ r_program_brushmodel, \"skyDomeCloudTex\", 1, false },\n\t// r_program_uniform_brushmodel_lightmaptex\n\t{ r_program_brushmodel, \"lightmapTex\", 1, false },\n\t// r_program_uniform_brushmodel_materialtex\n\t{ r_program_brushmodel, \"materialTex\", 1, false },\n\t// r_program_uniform_brushmodel_at_detailtex\n\t{ r_program_brushmodel_alphatested, \"detailTex\", 1, false },\n\t// r_program_uniform_brushmodel_at_causticstex\n\t{ r_program_brushmodel_alphatested, \"causticsTex\", 1, false },\n\t// r_program_uniform_brushmodel_at_skytex\n\t{ r_program_brushmodel_alphatested, \"skyTex\", 1, false },\n\t// r_program_uniform_brushmodel_at_skydometex\n\t{ r_program_brushmodel_alphatested, \"skyDomeTex\", 1, false },\n\t// r_program_uniform_brushmodel_at_skydomecloudtex\n\t{ r_program_brushmodel_alphatested, \"skyDomeCloudTex\", 1, false },\n\t// r_program_uniform_brushmodel_at_lightmaptex\n\t{ r_program_brushmodel_alphatested, \"lightmapTex\", 1, false },\n\t// r_program_uniform_brushmodel_at_materialtex\n\t{ r_program_brushmodel_alphatested, \"materialTex\", 1, false },\n\t// r_program_uniform_aliasmodel_causticstex\n\t{ r_program_aliasmodel, \"causticsTex\", 1, false },\n\t// r_program_uniform_aliasmodel_samplers\n\t{ r_program_aliasmodel, \"samplers\", 1, false },\n\t// r_program_uniform_sprites_materialtex\n\t{ r_program_sprite3d, \"materialTex\", 1, false },\n\t// r_program_uniform_postprocess_base\n\t{ r_program_post_process, \"base\", 1, false },\n\t// r_program_uniform_postprocess_overlay\n\t{ r_program_post_process, \"overlay\", 1, false },\n\t// r_program_uniform_fxworldgeometry_normaltex\n\t{ r_program_fx_world_geometry, \"normal_texture\", 1, false },\n\t// r_program_uniform_hudimage_tex\n\t{ r_program_hud_images, \"tex\", 1, false },\n\t// r_program_uniform_aliasmodel_instanceOffset\n\t{ r_program_aliasmodel, \"instanceOffset\", 1, false },\n\t// r_program_uniform_brushmodel_instanceOffset\n\t{ r_program_brushmodel, \"instanceOffset\", 1, false },\n\t// r_program_uniform_brushmodel_alphatested_instanceOffset\n\t{ r_program_brushmodel_alphatested, \"instanceOffset\", 1, false },\n};\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(program_uniforms) / sizeof(program_uniforms[0]) == r_program_uniform_count);\n#endif\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\ntypedef struct r_program_attribute_s {\n\tr_program_id program_id;\n\tconst char* name;\n} r_program_attribute_t;\n\nstatic r_program_attribute_t program_attributes[] = {\n\t// r_program_attribute_aliasmodel_std_glc_flags\n\t{ r_program_aliasmodel_std_glc, \"flags\" },\n\t// r_program_attribute_aliasmodel_shell_glc_flags\n\t{ r_program_aliasmodel_shell_glc, \"flags\" },\n\t// r_program_attribute_aliasmodel_shadow_glc_flags\n\t{ r_program_aliasmodel_shadow_glc, \"flags\" },\n\t// r_program_attribute_aliasmodel_outline_glc_flags\n\t{ r_program_aliasmodel_outline_glc, \"flags\" },\n\t// r_program_attribute_world_drawflat_style\n\t{ r_program_world_drawflat_glc, \"style\" },\n\t// r_program_attribute_world_textured_style\n\t{ r_program_world_textured_glc, \"style\" },\n\t// r_program_attribute_world_textured_detailCoord\n\t{ r_program_world_textured_glc, \"detailCoordInput\" },\n};\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(program_attributes) / sizeof(program_attributes[0]) == r_program_attribute_count);\n#endif // C_ASSERT\n\nstatic void R_ProgramFindAttributesForProgram(r_program_id program_id);\n#else // RENDERER_OPTION_CLASSIC_OPENGL\n#define R_ProgramFindAttributesForProgram(x)\n#endif // !RENDERER_OPTION_CLASSIC_OPENGL\n\nstatic qbool GL_CompileComputeShaderProgram(gl_program_t* prog, const char* shadertext, unsigned int length);\n\nstatic gl_program_t program_data[r_program_count][MAX_SUBPROGRAMS];\nstatic int program_currentSubProgram[r_program_count];\n\n// Cached OpenGL state\nstatic r_program_id currentProgram = 0;\nstatic int currentProgramOptionSet = 0;\n\n// Shader functions\nGL_StaticFunctionDeclaration(glCreateShader, \"shaderType=%u\", \"returned=%u\", GLuint, GLenum shaderType)\nGL_StaticFunctionWrapperBody(glCreateShader, GLuint, shaderType)\nGL_StaticProcedureDeclaration(glShaderSource, \"shader=%u, count=%d, string=%p, length=%p\", GLuint shader, GLsizei count, const GLchar** string, const GLint* length)\nGL_StaticProcedureDeclaration(glCompileShader, \"shader=%u\", GLuint shader)\nGL_StaticProcedureDeclaration(glDeleteShader, \"shader=%u\", GLuint shader)\nGL_StaticProcedureDeclaration(glGetShaderInfoLog, \"shader=%u, maxLength=%d, length=%p, infoLog=%p\", GLuint shader, GLsizei maxLength, GLsizei* length, GLchar* infoLog)\nGL_StaticProcedureDeclaration(glGetShaderiv, \"shader=%u, pname=%u, params=%p\", GLuint shader, GLenum pname, GLint* params)\nGL_StaticProcedureDeclaration(glGetShaderSource, \"shader=%u, bufSize=%d, length=%p, source=%p\", GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* source)\n\n// Program functions\nGL_StaticFunctionDeclaration(glCreateProgram, \"\", \"result=%u\", GLuint, void)\nGL_StaticFunctionWrapperBodyNoArgs(glCreateProgram, GLuint)\n\nGL_StaticProcedureDeclaration(glLinkProgram, \"program=%u\", GLuint program)\nGL_StaticProcedureDeclaration(glDeleteProgram, \"program=%u\", GLuint program)\nGL_StaticProcedureDeclaration(glGetProgramInfoLog, \"program=%u, maxLength=%d, length=%p, infoLog=%p\", GLuint program, GLsizei maxLength, GLsizei* length, GLchar* infoLog)\nGL_StaticProcedureDeclaration(glUseProgram, \"program=%u\", GLuint program)\nGL_StaticProcedureDeclaration(glAttachShader, \"program=%u, shader=%u\", GLuint program, GLuint shader)\nGL_StaticProcedureDeclaration(glDetachShader, \"program=%u, shader=%u\", GLuint program, GLuint shader)\nGL_StaticProcedureDeclaration(glGetProgramiv, \"program=%u, pname=%u, params=%p\", GLuint program, GLenum pname, GLint* params)\n\n// Uniforms\nGL_StaticFunctionDeclaration(glGetUniformLocation, \"program=%u, name=%s\", \"result=%d\", GLint, GLuint program, const GLchar* name)\nGL_StaticFunctionWrapperBody(glGetUniformLocation, GLint, program, name)\n\nGL_StaticProcedureDeclaration(glUniform1i, \"location=%d, v0=%d\", GLint location, GLint v0)\nGL_StaticProcedureDeclaration(glUniform1f, \"location=%d, value=%f\", GLint location, GLfloat value)\nGL_StaticProcedureDeclaration(glUniform2fv, \"location=%d, count=%d, value=%p\", GLint location, GLsizei count, const GLfloat* value)\nGL_StaticProcedureDeclaration(glUniform3fv, \"location=%d, count=%d, value=%p\", GLint location, GLsizei count, const GLfloat* value)\nGL_StaticProcedureDeclaration(glUniform4fv, \"location=%d, count=%d, value=%p\", GLint location, GLsizei count, const GLfloat* value)\nGL_StaticProcedureDeclaration(glUniformMatrix4fv, \"location=%d, count=%d, transpose=%d, value=%p\", GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)\nGL_StaticProcedureDeclaration(glProgramUniform1i, \"program=%u, location=%u, v0=%d\", GLuint program, GLuint location, GLint v0)\nGL_StaticProcedureDeclaration(glProgramUniform1f, \"program=%u, location=%u, value=%f\", GLuint program, GLuint location, GLfloat value)\nGL_StaticProcedureDeclaration(glProgramUniform2fv, \"program=%u, location=%u, count=%d, value=%p\", GLuint program, GLuint location, GLsizei count, const GLfloat* value)\nGL_StaticProcedureDeclaration(glProgramUniform3fv, \"program=%u, location=%u, count=%d, value=%p\", GLuint program, GLuint location, GLsizei count, const GLfloat* value)\nGL_StaticProcedureDeclaration(glProgramUniform4fv, \"program=%u, location=%u, count=%d, value=%p\", GLuint program, GLuint location, GLsizei count, const GLfloat* value)\nGL_StaticProcedureDeclaration(glProgramUniformMatrix4fv, \"program=%u, location=%d, count=%d, transpose=%d, value=%p\", GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n// Attributes\nGL_StaticFunctionDeclaration(glGetAttribLocation, \"program=%u, name=%s\", \"result=%d\", GLint, GLuint program, const GLchar* name)\nGL_StaticFunctionWrapperBody(glGetAttribLocation, GLint, program, name)\n#endif\n\n// Compute shaders\nGL_StaticProcedureDeclaration(glDispatchCompute, \"num_groups_x=%u, num_groups_y=%u, num_groups_z=%u\", GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z)\nGL_StaticProcedureDeclaration(glMemoryBarrier, \"barriers=%u\", GLbitfield barriers)\n\n#define MAX_SHADER_COMPONENTS 8\n#define EZQUAKE_DEFINITIONS_STRING \"#ezquake-definitions\"\n\nstatic char macro_definitions[4096];\nstatic char core_definitions[2048];\nstatic char standard_definitions[2048];\n\nstatic unsigned int R_ProgramStandardOptions(unsigned int option_flags)\n{\n\tunsigned int flags =\n\t\t(r_refdef2.fog_calculation == fogcalc_linear ? STDOPTIONS_FOG_LINEAR : 0) |\n\t\t(r_refdef2.fog_calculation == fogcalc_exp    ? STDOPTIONS_FOG_EXP    : 0) |\n\t\t(r_refdef2.fog_calculation == fogcalc_exp2   ? STDOPTIONS_FOG_EXP2   : 0);\n\n\treturn flags & option_flags;\n}\n\nstatic void R_ProgramBuildStandardDefines(unsigned int option_flags)\n{\n\tunsigned int options = R_ProgramStandardOptions(option_flags);\n\n\tstandard_definitions[0] = '\\0';\n\tif (options & STDOPTIONS_FOG) {\n\t\tstrlcat(standard_definitions, \"#define DRAW_FOG\\n\", sizeof(standard_definitions));\n\t\tif (options & STDOPTIONS_FOG_LINEAR) {\n\t\t\tstrlcat(standard_definitions, \"#define FOG_LINEAR\\n\", sizeof(standard_definitions));\n\t\t}\n\t\telse if (options & STDOPTIONS_FOG_EXP) {\n\t\t\tstrlcat(standard_definitions, \"#define FOG_EXP\\n\", sizeof(standard_definitions));\n\t\t}\n\t\telse if (options & STDOPTIONS_FOG_EXP2) {\n\t\t\tstrlcat(standard_definitions, \"#define FOG_EXP2\\n\", sizeof(standard_definitions));\n\t\t}\n\t}\n}\n\n// GLM Utility functions\nstatic void GL_ConPrintShaderLog(GLuint shader)\n{\n\tGLint log_length;\n\tGLint src_length;\n\tchar* buffer;\n\n\tGL_Procedure(glGetShaderiv, shader, GL_INFO_LOG_LENGTH, &log_length);\n\tGL_Procedure(glGetShaderiv, shader, GL_SHADER_SOURCE_LENGTH, &src_length);\n\tif (log_length || (src_length > 0 && developer.integer == 3)) {\n\t\tGLsizei written;\n\n\t\tbuffer = Q_malloc(max(log_length, src_length) + 1);\n\t\tGL_Procedure(glGetShaderInfoLog, shader, log_length, &written, buffer);\n\t\tCon_Printf(\"-- Version: %s\\n\", glConfig.version_string);\n\t\tCon_Printf(\"--    GLSL: %s\\n\", glConfig.glsl_version);\n\t\tCon_Printf(\"%s\\n\", buffer);\n\n\t\tif (developer.integer == 2 || developer.integer == 3) {\n\t\t\tGL_Procedure(glGetShaderSource, shader, src_length, &written, buffer);\n\t\t\tCon_Printf(\"%s\\n\", buffer);\n\t\t}\n\n\t\tQ_free(buffer);\n\t}\n}\n\nstatic void GL_ConPrintProgramLog(GLuint program)\n{\n\tGLint log_length;\n\tchar* buffer;\n\n\tGL_Procedure(glGetProgramiv, program, GL_INFO_LOG_LENGTH, &log_length);\n\tif (log_length) {\n\t\tGLsizei written;\n\n\t\tbuffer = Q_malloc(log_length);\n\t\tGL_Procedure(glGetProgramInfoLog, program, log_length, &written, buffer);\n\t\tCon_Printf(buffer);\n\t\tQ_free(buffer);\n\t}\n}\n\n// Uniform utility functions\nstatic gl_program_uniform_t* GL_ProgramUniformFind(r_program_uniform_id uniform_id)\n{\n\tr_program_uniform_t* uniform = &program_uniforms[uniform_id];\n\tr_program_id program_id = uniform->program_id;\n\tgl_program_t* program = R_CurrentSubProgram(program_id);\n\tgl_program_uniform_t* program_uniform = &program->uniforms[uniform_id];\n\n\tif (program->program && !program_uniform->found) {\n\t\tprogram_uniform->location = GL_Function(glGetUniformLocation, program->program, uniform->name);\n\t\tprogram_uniform->found = true;\n\t}\n\n\treturn program_uniform;\n}\n\nstatic void R_ProgramFindUniformsForProgram(r_program_id program_id)\n{\n\tr_program_uniform_id u;\n\n\tfor (u = 0; u < r_program_uniform_count; ++u) {\n\t\tif (program_uniforms[u].program_id == program_id) {\n\t\t\tGL_ProgramUniformFind(u);\n\t\t}\n\t}\n}\n\nstatic qbool GL_CompileShader(GLsizei shaderComponents, const char* shaderText[], GLint shaderTextLength[], GLenum shaderType, GLuint* shaderId)\n{\n\tGLuint shader;\n\tGLint result;\n\n\t*shaderId = 0;\n\tshader = GL_Function(glCreateShader, shaderType);\n\tif (shader) {\n\t\tGL_Procedure(glShaderSource, shader, shaderComponents, shaderText, shaderTextLength);\n\t\tGL_Procedure(glCompileShader, shader);\n\t\tGL_Procedure(glGetShaderiv, shader, GL_COMPILE_STATUS, &result);\n\t\tif (result) {\n\t\t\t*shaderId = shader;\n\t\t\tif (developer.integer == 3) {\n\t\t\t\tCon_Printf(\"Shader->Compile(%X) succeeded\\n\", shaderType);\n\t\t\t\tGL_ConPrintShaderLog(shader);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\tCon_Printf(\"Shader->Compile(%X) failed\\n\", shaderType);\n\t\tGL_ConPrintShaderLog(shader);\n\t\tGL_Procedure(glDeleteShader, shader);\n\t}\n\telse {\n\t\tCon_Printf(\"glCreateShader failed\\n\");\n\t}\n\treturn false;\n}\n\n// Couldn't find standard library call to do this (!?)\n// Search for <search_string> in non-nul-terminated <source>\nstatic const char* safe_strstr(const char* source, size_t max_length, const char* search_string)\n{\n\tsize_t search_length = strlen(search_string);\n\tconst char* position;\n\n\twhile (true) {\n\t\tposition = (const char*)memchr(source, search_string[0], max_length);\n\t\tif (!position) {\n\t\t\tbreak;\n\t\t}\n\n\t\t// Move along\n\t\tif (max_length < (position - source)) {\n\t\t\tbreak;\n\t\t}\n\t\tmax_length -= (position - source);\n\n\t\tif (max_length < search_length) {\n\t\t\tbreak;\n\t\t}\n\t\tif (!memcmp(position, search_string, search_length)) {\n\t\t\treturn position;\n\t\t}\n\n\t\t// Try again\n\t\tsource = position + 1;\n\t\tmax_length -= 1;\n\t}\n\n\treturn NULL;\n}\n\n#if 0\nstatic void GL_DebugPrintShaderText(GLuint shader)\n{\n\tint length = 0;\n\tchar* src;\n\n\tGL_Procedure(glGetShaderiv, shader, GL_SHADER_SOURCE_LENGTH, &length);\n\tsrc = Q_malloc(length + 1);\n\tGL_Procedure(glGetShaderSource, shader, length, NULL, src);\n\n\tDebugOutput(src);\n\tQ_free(src);\n}\n#endif\n\nstatic int GL_InsertDefinitions(\n\tconst char* strings[],\n\tGLint lengths[],\n\tconst char* definitions,\n\tGLenum shader_type\n)\n{\n\tstatic unsigned char *glsl_constants_glsl = (unsigned char *)\"\", *glsl_common_glsl = (unsigned char *)\"\";\n\tunsigned int glsl_constants_glsl_len = 0, glsl_common_glsl_len = 0;\n\tconst char* break_point;\n\tint i;\n\n\tif (!strings[0] || !strings[0][0]) {\n\t\treturn 0;\n\t}\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\tif (R_UseModernOpenGL()) {\n\t\textern unsigned char constants_glsl[], common_glsl[];\n\t\textern unsigned int constants_glsl_len, common_glsl_len;\n\n\t\tglsl_constants_glsl = constants_glsl;\n\t\tglsl_common_glsl = common_glsl;\n\t\tglsl_constants_glsl_len = constants_glsl_len;\n\t\tglsl_common_glsl_len = common_glsl_len;\n\t}\n#endif\n\n\tbreak_point = safe_strstr(strings[0], lengths[0], EZQUAKE_DEFINITIONS_STRING);\n\t\n\tif (break_point) {\n\t\tint position = break_point - strings[0];\n\n\t\t// Order: version, constants, macros, common, standard_defs, included_defs, core_defs, shader\n\t\tlengths[7] = lengths[0] - position - strlen(EZQUAKE_DEFINITIONS_STRING);\n\t\tlengths[6] = strlen(core_definitions);\n\t\tlengths[5] = definitions ? strlen(definitions) : 0;\n\t\tlengths[4] = strlen(standard_definitions);\n\t\tlengths[3] = glsl_common_glsl_len;\n\t\tlengths[2] = strlen(macro_definitions);\n\t\tlengths[1] = glsl_constants_glsl_len;\n\t\tlengths[0] = position;\n\t\tstrings[7] = break_point + strlen(EZQUAKE_DEFINITIONS_STRING);\n\t\tstrings[6] = core_definitions;\n\t\tstrings[5] = definitions ? definitions : \"\";\n\t\tstrings[4] = standard_definitions;\n\t\tstrings[3] = (const char*)glsl_common_glsl;\n\t\tstrings[2] = macro_definitions;\n\t\tstrings[1] = (const char*)glsl_constants_glsl;\n\n\t\t// Inject the appropriate GLSL version string (with shader type for GLC core profile)\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\t\tif (R_UseModernOpenGL()) {\n\t\t\tstrings[0] = GL_VersionAtLeast(4, 3) ? \"#version 430\\n\\n\" : \"#version 410\\n\\n\";\n\t\t}\n\t\telse\n#endif\n\t\t{\n\t\t\tif (glConfig.coreProfile) {\n\t\t\t\tif (shader_type == GL_VERTEX_SHADER) {\n\t\t\t\t\tstrings[0] = \"#version 330\\n#define EZ_VERTEX_SHADER\\n\\n\";\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tstrings[0] = \"#version 330\\n#define EZ_FRAGMENT_SHADER\\n\\n\";\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tstrings[0] = \"#version 120\\n\\n\";\n\t\t\t}\n\t\t}\n\t\tlengths[0] = strlen(strings[0]);\n\n\t\t// Some drivers interpret length 0 as nul terminated\n\t\t// spec is < 0 (https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glShaderSource.xhtml)\n\t\tfor (i = 0; i < MAX_SHADER_COMPONENTS; ++i) {\n\t\t\tif (lengths[i] == 0) {\n\t\t\t\tstrings[i] = \"\";\n\t\t\t}\n\t\t}\n\n\t\treturn 8;\n\t}\n\n\treturn 1;\n}\n\nstatic void GL_DumpShader(const char *name, const char *suffix, int components, const char* strings[], GLint lengths[])\n{\n\tchar filename[MAX_OSPATH];\n\tchar buf[256];\n\tint i;\n\tFILE *fd;\n\n\tif (strings[0] == NULL) {\n\t\treturn;\n\t}\n\n\tsnprintf(filename, sizeof(filename), \"%s/qw/shaders\", com_basedir);\n\tSys_mkdir(filename);\n\tsnprintf(filename, sizeof(filename), \"%s/qw/shaders/%s.%s\", com_basedir, name, suffix);\n\n\tfd = fopen(filename, \"wt\");\n\tif (!fd) {\n\t\tSys_Error(\"Could not open %s for writing\\n\", filename);\n\t}\n\n\tfor (i = 0; i < components; i++) {\n\t\tif (!lengths[i]) {\n\t\t\tcontinue;\n\t\t}\n\t\t// Original source up to #ezquake-definitions, likely just \"#version ...\"\n\t\tif (i == 0) {\n\t\t\tif (sizeof(buf) <= lengths[0]) {\n\t\t\t\tSys_Error(\"Unexpected shader preamble size (%d, max %d)\\n\", lengths[0], sizeof(buf));\n\t\t\t}\n\t\t\tstrlcpy(buf, strings[0], lengths[0]);\n\t\t\tfprintf(fd, \"%s\\n\", buf);\n\t\t}\n\t\telse {\n\t\t\tfprintf(fd, \"%s\\n\", strings[i]);\n\t\t}\n\t}\n\n\tfclose(fd);\n}\n\nstatic qbool GL_CompileProgram(\n\tgl_program_t* program\n)\n{\n\tGLuint shaders[shadertype_count] = { 0 };\n\tGLuint program_handle = 0;\n\tGLint result = 0;\n\tint i;\n\n\tGLsizei vertex_components = 1;\n\tconst char* vertex_shader_text[MAX_SHADER_COMPONENTS] = { program->shaders[shadertype_vertex].text, \"\", \"\", \"\", \"\", \"\", \"\" };\n\tGLint vertex_shader_text_length[MAX_SHADER_COMPONENTS] = { program->shaders[shadertype_vertex].length, 0, 0, 0, 0, 0, 0 };\n\tGLsizei geometry_components = 1;\n\tconst char* geometry_shader_text[MAX_SHADER_COMPONENTS] = { program->shaders[shadertype_geometry].text, \"\", \"\", \"\", \"\", \"\", \"\" };\n\tGLint geometry_shader_text_length[MAX_SHADER_COMPONENTS] = { program->shaders[shadertype_geometry].length, 0, 0, 0, 0, 0, 0 };\n\tGLsizei fragment_components = 1;\n\tconst char* fragment_shader_text[MAX_SHADER_COMPONENTS] = { program->shaders[shadertype_fragment].text, \"\", \"\", \"\", \"\", \"\", \"\" };\n\tGLint fragment_shader_text_length[MAX_SHADER_COMPONENTS] = { program->shaders[shadertype_fragment].length, 0, 0, 0, 0, 0, 0 };\n\tGLsizei compute_components = 1;\n\tconst char* compute_shader_text[MAX_SHADER_COMPONENTS] = { program->shaders[shadertype_compute].text, \"\", \"\", \"\", \"\", \"\", \"\" };\n\tGLint compute_shader_text_length[MAX_SHADER_COMPONENTS] = { program->shaders[shadertype_compute].length, 0, 0, 0, 0, 0, 0 };\n\n\tDebugOutput(va(\"Compiling: %s\\n\", program->friendly_name));\n\n\tR_ProgramBuildStandardDefines(program->standard_option_flags);\n\n\tvertex_components = GL_InsertDefinitions(vertex_shader_text, vertex_shader_text_length, program->included_definitions, GL_VERTEX_SHADER);\n\tgeometry_components = GL_InsertDefinitions(geometry_shader_text, geometry_shader_text_length, program->included_definitions, GL_GEOMETRY_SHADER);\n\tfragment_components = GL_InsertDefinitions(fragment_shader_text, fragment_shader_text_length, program->included_definitions, GL_FRAGMENT_SHADER);\n\tcompute_components = GL_InsertDefinitions(compute_shader_text, compute_shader_text_length, program->included_definitions, GL_COMPUTE_SHADER);\n\n\tif (COM_CheckParm(cmdline_param_client_video_r_dump_shaders)) {\n\t\tGL_DumpShader(program->friendly_name, \"vert\", vertex_components, vertex_shader_text, vertex_shader_text_length);\n\t\tGL_DumpShader(program->friendly_name, \"frag\", fragment_components, fragment_shader_text, fragment_shader_text_length);\n\t\tGL_DumpShader(program->friendly_name, \"geom\", geometry_components, geometry_shader_text, geometry_shader_text_length);\n\t\tGL_DumpShader(program->friendly_name, \"comp\", compute_components, compute_shader_text, compute_shader_text_length);\n\t}\n\n\tif (program->shaders[shadertype_compute].text) {\n\t\treturn GL_CompileComputeShaderProgram(program, compute_shader_text[0], compute_shader_text_length[0]);\n\t}\n\n\tif (GL_CompileShader(vertex_components, vertex_shader_text, vertex_shader_text_length, GL_VERTEX_SHADER, &shaders[shadertype_vertex])) {\n\t\tif (geometry_shader_text[0] == NULL || GL_CompileShader(geometry_components, geometry_shader_text, geometry_shader_text_length, GL_GEOMETRY_SHADER, &shaders[shadertype_geometry])) {\n\t\t\tif (GL_CompileShader(fragment_components, fragment_shader_text, fragment_shader_text_length, GL_FRAGMENT_SHADER, &shaders[shadertype_fragment])) {\n\t\t\t\tCon_DPrintf(\"Shader compilation completed successfully\\n\");\n\n\t\t\t\tprogram_handle = GL_FunctionNoArgs(glCreateProgram);\n\t\t\t\tif (program_handle) {\n\t\t\t\t\tGL_Procedure(glAttachShader, program_handle, shaders[shadertype_fragment]);\n\t\t\t\t\tGL_Procedure(glAttachShader, program_handle, shaders[shadertype_vertex]);\n\t\t\t\t\tif (shaders[shadertype_geometry]) {\n\t\t\t\t\t\tGL_Procedure(glAttachShader, program_handle, shaders[shadertype_geometry]);\n\t\t\t\t\t}\n\t\t\t\t\tGL_Procedure(glLinkProgram, program_handle);\n\t\t\t\t\tGL_Procedure(glGetProgramiv, program_handle, GL_LINK_STATUS, &result);\n\n\t\t\t\t\tif (result) {\n\t\t\t\t\t\tCon_DPrintf(\"ShaderProgram.Link() was successful\\n\");\n\t\t\t\t\t\tmemcpy(program->shader_handles, shaders, sizeof(shaders));\n\t\t\t\t\t\tprogram->program = program_handle;\n\t\t\t\t\t\tmemset(program->uniforms, 0, sizeof(program->uniforms));\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t\t\t\t\t\tmemset(program->attributes, 0, sizeof(program->attributes));\n#endif\n\t\t\t\t\t\tprogram->force_recompile = false;\n\t\t\t\t\t\tprogram->standard_options = R_ProgramStandardOptions(program->standard_option_flags);\n\n\t\t\t\t\t\tGL_TraceObjectLabelSet(GL_PROGRAM, program->program, -1, program->friendly_name);\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tCon_Printf(\"ShaderProgram.Link(%s) failed\\n\", program->friendly_name);\n\t\t\t\t\t\tGL_ConPrintProgramLog(program_handle);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCon_Printf(\"FragmentShader.Compile(%s) failed\\n\", program->friendly_name);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tCon_Printf(\"GeometryShader.Compile(%s) failed\\n\", program->friendly_name);\n\t\t}\n\t}\n\telse {\n\t\tCon_Printf(\"VertexShader.Compile(%s) failed\\n\", program->friendly_name);\n\t}\n\n\tif (program_handle) {\n\t\tGL_Procedure(glDeleteProgram, program_handle);\n\t}\n\tfor (i = 0; i < sizeof(shaders) / sizeof(shaders[0]); ++i) {\n\t\tif (shaders[i]) {\n\t\t\tGL_Procedure(glDeleteShader, shaders[i]);\n\t\t}\n\t}\n\treturn false;\n}\n\nstatic void GL_CleanupShader(GLuint program, GLuint shader)\n{\n\tif (shader) {\n\t\tif (program) {\n\t\t\tGL_Procedure(glDetachShader, program, shader);\n\t\t}\n\t\tGL_Procedure(glDeleteShader, shader);\n\t}\n}\n\n// Called during vid_shutdown\nvoid GL_ProgramsShutdown(qbool restarting)\n{\n\tr_program_id p;\n\tgl_program_t* prog;\n\tint sub_program;\n\n\tR_ProgramUse(r_program_none);\n\n\t// Detach & delete shaders\n\tfor (p = r_program_none; p < r_program_count; ++p) {\n\t\tfor (sub_program = 0; sub_program < MAX_SUBPROGRAMS; ++sub_program) {\n\t\t\tint i;\n\n\t\t\tprog = R_SpecificSubProgram(p, sub_program);\n\t\t\tfor (i = 0; i < sizeof(prog->shaders) / sizeof(prog->shaders[0]); ++i) {\n\t\t\t\tGL_CleanupShader(prog->program, prog->shader_handles[i]);\n\t\t\t\tprog->shader_handles[i] = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (p = r_program_none; p < r_program_count; ++p) {\n\t\tfor (sub_program = 0; sub_program < MAX_SUBPROGRAMS; ++sub_program) {\n\t\t\tprog = R_SpecificSubProgram(p, sub_program);\n\t\t\tif (prog->program) {\n\t\t\t\tGL_Procedure(glDeleteProgram, prog->program);\n\t\t\t\tprog->program = 0;\n\t\t\t}\n\n\t\t\tif (!restarting) {\n\t\t\t\t// Keep definitions if we're about to recompile after restart\n\t\t\t\tQ_free(prog->included_definitions);\n\t\t\t}\n\n\t\t\tmemset(prog->uniforms, 0, sizeof(prog->uniforms));\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t\t\tmemset(prog->attributes, 0, sizeof(prog->attributes));\n#endif\n\t\t}\n\t}\n\n\tmemset(program_currentSubProgram, 0, sizeof(program_currentSubProgram));\n}\n\nstatic qbool GL_AppropriateRenderer(renderer_id renderer)\n{\n\tif (R_UseImmediateOpenGL() && renderer == renderer_classic) {\n\t\treturn true;\n\t}\n\tif (R_UseModernOpenGL() && renderer == renderer_modern) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n// Called during vid_restart, as starting up again\nvoid GL_ProgramsInitialise(void)\n{\n\tr_program_id p;\n\tint i;\n\n\tif (!(glConfig.supported_features & R_SUPPORT_RENDERING_SHADERS)) {\n\t\treturn;\n\t}\n\n\tGL_BuildCoreDefinitions();\n\n\tfor (i = 0; i < MAX_SUBPROGRAMS; ++i) {\n\t\tgl_program_t* prog = R_SpecificSubProgram(r_program_none, i);\n\n\t\tstrlcpy(prog->friendly_name, \"(none)\", sizeof(prog->friendly_name));\n\t}\n\n\tfor (p = r_program_none; p < r_program_count; ++p) {\n\t\tfor (i = 0; i < MAX_SUBPROGRAMS; ++i) {\n\t\t\tgl_program_t* prog = R_SpecificSubProgram(p, i);\n\n\t\t\tif (!GL_AppropriateRenderer(prog->renderer_id)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!prog->program && !prog->needs_params && prog->initialised) {\n\t\t\t\tgl_shader_def_t * compute = &prog->shaders[shadertype_compute];\n\t\t\t\tif (compute->length) {\n\t\t\t\t\tGL_CompileComputeShaderProgram(prog, compute->text, compute->length);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tGL_CompileProgram(prog);\n\t\t\t\t}\n\t\t\t\tR_ProgramFindUniformsForProgram(p);\n\t\t\t\tR_ProgramFindAttributesForProgram(p);\n\t\t\t}\n\t\t}\n\t}\n\n\tmemset(program_currentSubProgram, 0, sizeof(program_currentSubProgram));\n}\n\nqbool R_ProgramRecompileNeeded(r_program_id program_id, unsigned int options)\n{\n\t//\n\tconst gl_program_t* program = R_CurrentSubProgram(program_id);\n\tunsigned int standard_options = R_ProgramStandardOptions(program->standard_option_flags);\n\n\treturn (!program->program) || program->force_recompile || program->custom_options != options || program->standard_options != standard_options;\n}\n\nGLuint R_ProgramId(r_program_id program_id)\n{\n\treturn R_CurrentSubProgram(program_id)->program;\n}\n\nvoid GL_CvarForceRecompile(cvar_t* cvar)\n{\n\tr_program_id p;\n\tint i;\n\n\tfor (p = r_program_none; p < r_program_count; ++p) {\n\t\tfor (i = 0; i < MAX_SUBPROGRAMS; ++i) {\n\t\t\tR_SpecificSubProgram(p, i)->force_recompile = true;\n\t\t}\n\t}\n\n\tGL_BuildCoreDefinitions();\n}\n\nstatic qbool GL_CompileComputeShaderProgram(gl_program_t* program, const char* shadertext, unsigned int length)\n{\n\tconst char* shader_text[MAX_SHADER_COMPONENTS] = { shadertext, \"\", \"\", \"\", \"\", \"\", \"\" };\n\tGLint shader_text_length[MAX_SHADER_COMPONENTS] = { length, 0, 0, 0, 0, 0, 0 };\n\tint components;\n\tGLuint shader;\n\tint standard_options = R_ProgramStandardOptions(program->standard_option_flags);\n\n\tif (!GL_Supported(R_SUPPORT_COMPUTE_SHADERS)) {\n\t\treturn false;\n\t}\n\n\tprogram->program = 0;\n\n\tcomponents = GL_InsertDefinitions(shader_text, shader_text_length, \"\", GL_COMPUTE_SHADER);\n\tif (GL_CompileShader(components, shader_text, shader_text_length, GL_COMPUTE_SHADER, &shader)) {\n\t\tGLuint shader_program = GL_FunctionNoArgs(glCreateProgram);\n\t\tif (shader_program) {\n\t\t\tGLint result;\n\n\t\t\tGL_Procedure(glAttachShader, shader_program, shader);\n\t\t\tGL_Procedure(glLinkProgram, shader_program);\n\t\t\tGL_Procedure(glGetProgramiv, shader_program, GL_LINK_STATUS, &result);\n\n\t\t\tif (result) {\n\t\t\t\tCon_DPrintf(\"ShaderProgram.Link() was successful\\n\");\n\t\t\t\tprogram->shader_handles[shadertype_compute] = shader;\n\t\t\t\tprogram->program = shader_program;\n\t\t\t\tmemset(program->uniforms, 0, sizeof(program->uniforms));\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t\t\t\tmemset(program->attributes, 0, sizeof(program->attributes));\n#endif\n\t\t\t\tprogram->force_recompile = false;\n\t\t\t\tprogram->standard_options = standard_options;\n\n\t\t\t\tGL_TraceObjectLabelSet(GL_PROGRAM, program->program, -1, program->friendly_name);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCon_Printf(\"ShaderProgram.Link() failed\\n\");\n\t\t\t\tGL_ConPrintProgramLog(shader_program);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n\nvoid GL_LoadProgramFunctions(void)\n{\n\tqbool rendering_shaders_support = true;\n\n\tglConfig.supported_features &= ~(R_SUPPORT_RENDERING_SHADERS | R_SUPPORT_COMPUTE_SHADERS);\n\t{\n\t\t// These are all core in OpenGL 2.0\n\t\tGL_LoadMandatoryFunctionExtension(glCreateShader, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glShaderSource, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glGetShaderSource, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glCompileShader, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glDeleteShader, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glGetShaderInfoLog, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glGetShaderiv, rendering_shaders_support);\n\n\t\tGL_LoadMandatoryFunctionExtension(glCreateProgram, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glLinkProgram, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glDeleteProgram, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glUseProgram, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glAttachShader, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glDetachShader, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glGetProgramInfoLog, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glGetProgramiv, rendering_shaders_support);\n\n\t\tGL_LoadMandatoryFunctionExtension(glGetUniformLocation, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glUniform1i, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glUniform1f, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glUniform2fv, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glUniform3fv, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glUniform4fv, rendering_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glUniformMatrix4fv, rendering_shaders_support);\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t\tGL_LoadMandatoryFunctionExtension(glGetAttribLocation, rendering_shaders_support);\n#endif\n\n\t\tglConfig.supported_features |= (rendering_shaders_support ? R_SUPPORT_RENDERING_SHADERS : 0);\n\t}\n\n\tif (GL_UseDirectStateAccess() || GL_VersionAtLeast(4, 1)) {\n\t\tGL_LoadOptionalFunction(glProgramUniform1i);\n\t\tGL_LoadOptionalFunction(glProgramUniform1f);\n\t\tGL_LoadOptionalFunction(glProgramUniform2fv);\n\t\tGL_LoadOptionalFunction(glProgramUniform3fv);\n\t\tGL_LoadOptionalFunction(glProgramUniform4fv);\n\t\tGL_LoadOptionalFunction(glProgramUniformMatrix4fv);\n\t} else {\n\t\tGL_InvalidateFunction(glProgramUniform1i);\n\t\tGL_InvalidateFunction(glProgramUniform1f);\n\t\tGL_InvalidateFunction(glProgramUniform2fv);\n\t\tGL_InvalidateFunction(glProgramUniform3fv);\n\t\tGL_InvalidateFunction(glProgramUniform4fv);\n\t\tGL_InvalidateFunction(glProgramUniformMatrix4fv);\n\t}\n\n\tif (GL_VersionAtLeast(4, 3) || (GL_VersionAtLeast(4, 2) && SDL_GL_ExtensionSupported(\"GL_ARB_compute_shader\") && SDL_GL_ExtensionSupported(\"GL_ARB_shader_image_load_store\"))) {\n\t\tqbool compute_shaders_support = true;\n\n\t\tGL_LoadMandatoryFunctionExtension(glDispatchCompute, compute_shaders_support);\n\t\tGL_LoadMandatoryFunctionExtension(glMemoryBarrier, compute_shaders_support);\n\n\t\tglConfig.supported_features |= (compute_shaders_support ? R_SUPPORT_COMPUTE_SHADERS : 0);\n\t} else {\n\t\tGL_InvalidateFunction(glDispatchCompute);\n\t\tGL_InvalidateFunction(glMemoryBarrier);\n\t}\n}\n\nvoid R_ProgramUse(r_program_id program_id)\n{\n\tif (program_id != currentProgram || currentProgramOptionSet != program_currentSubProgram[program_id]) {\n\t\tif (GL_Available(glUseProgram)) {\n\t\t\tR_TraceLogAPICall(\"R_ProgramUse(%s[%d])\", R_CurrentSubProgram(program_id)->friendly_name, program_currentSubProgram[program_id]);\n\t\t\tGL_Procedure(glUseProgram, R_CurrentSubProgram(program_id)->program);\n\t\t}\n\n\t\tcurrentProgram = program_id;\n\t\tcurrentProgramOptionSet = program_currentSubProgram[program_id];\n\t}\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\tif (R_UseModernOpenGL() && program_id != r_program_none) {\n\t\tGLM_UploadFrameConstants();\n\t}\n#endif\n}\n\nr_program_id R_ProgramInUse(void)\n{\n\treturn currentProgram;\n}\n\nvoid R_ProgramInitialiseState(void)\n{\n\tcurrentProgram = 0;\n\n\tGL_BuildCoreDefinitions();\n}\n\n// Compute shaders\nstatic void GL_ProgramComputeDispatch(r_program_id program_id, unsigned int num_groups_x, unsigned int num_groups_y, unsigned int num_groups_z)\n{\n\tR_ProgramUse(program_id);\n\tGL_Procedure(glDispatchCompute, num_groups_x, num_groups_y, num_groups_z);\n}\n\nstatic void GL_ProgramMemoryBarrier(r_program_id program_id)\n{\n\tif (R_CurrentSubProgram(program_id)->memory_barrier) {\n\t\tGL_Procedure(glMemoryBarrier, R_CurrentSubProgram(program_id)->memory_barrier);\n\t}\n}\n\nvoid R_ProgramMemoryBarrier(r_program_id program_id)\n{\n\tGL_ProgramMemoryBarrier(program_id);\n}\n\nvoid R_ProgramComputeDispatch(r_program_id program_id, unsigned int num_groups_x, unsigned int num_groups_y, unsigned int num_groups_z)\n{\n\tGL_ProgramComputeDispatch(program_id, num_groups_x, num_groups_y, num_groups_z);\n}\n\nvoid R_ProgramComputeSetMemoryBarrierFlag(r_program_id program_id, r_program_memory_barrier_id barrier_id)\n{\n\tR_CurrentSubProgram(program_id)->memory_barrier |= glBarrierFlags[barrier_id];\n}\n\n// Wrappers\nint R_ProgramCustomOptions(r_program_id program_id)\n{\n\treturn R_CurrentSubProgram(program_id)->custom_options;\n}\n\nvoid R_ProgramSetCustomOptions(r_program_id program_id, int options)\n{\n\tR_CurrentSubProgram(program_id)->custom_options = options;\n}\n\nqbool R_ProgramReady(r_program_id program_id)\n{\n\treturn R_CurrentSubProgram(program_id)->program != 0;\n}\n\nvoid R_ProgramUniform1i(r_program_uniform_id uniform_id, int value)\n{\n\tr_program_uniform_t* base_uniform = &program_uniforms[uniform_id];\n\tgl_program_uniform_t* program_uniform = GL_ProgramUniformFind(uniform_id);\n\tgl_program_t* prog = R_CurrentSubProgram(base_uniform->program_id);\n\n\tif (program_uniform->location >= 0) {\n\t\tR_TraceLogAPICall(\"%s(%s/%s,%d)\", __func__, prog->friendly_name, base_uniform->name, value);\n\t\tif (GL_Available(glProgramUniform1i)) {\n\t\t\tGL_Procedure(glProgramUniform1i, prog->program, program_uniform->location, value);\n\t\t}\n\t\telse {\n\t\t\tR_ProgramUse(base_uniform->program_id);\n\t\t\tGL_Procedure(glUniform1i, program_uniform->location, value);\n\t\t}\n\t\tprogram_uniform->int_value = value;\n\t}\n}\n\nvoid R_ProgramUniform1iArrayBase(r_program_uniform_id uniform_id, int count, int base_value)\n{\n\tr_program_uniform_t* base_uniform = &program_uniforms[uniform_id];\n\tgl_program_uniform_t* program_uniform = GL_ProgramUniformFind(uniform_id);\n\tint j;\n\n\tif (program_uniform->location >= 0) {\n\t\tR_ProgramUse(base_uniform->program_id);\n\t\tfor (j = 0; j < count; ++j) {\n\t\t\tGL_Procedure(glUniform1i, program_uniform->location + j, base_value + j);\n\t\t}\n\t\tprogram_uniform->int_value = base_value;\n\t}\n}\n\nvoid R_ProgramUniform1f(r_program_uniform_id uniform_id, float value)\n{\n\tr_program_uniform_t* base_uniform = &program_uniforms[uniform_id];\n\tgl_program_uniform_t* program_uniform = GL_ProgramUniformFind(uniform_id);\n\tgl_program_t* prog = R_CurrentSubProgram(base_uniform->program_id);\n\n\tif (program_uniform->location >= 0) {\n\t\tR_TraceLogAPICall(\"%s(%s/%s,%f)\", __func__, prog->friendly_name, base_uniform->name, value);\n\t\tif (GL_Available(glProgramUniform1f)) {\n\t\t\tGL_Procedure(glProgramUniform1f, prog->program, program_uniform->location, value);\n\t\t}\n\t\telse {\n\t\t\tR_ProgramUse(base_uniform->program_id);\n\t\t\tGL_Procedure(glUniform1f, program_uniform->location, value);\n\t\t}\n\t\tprogram_uniform->int_value = value;\n\t}\n}\n\nvoid R_ProgramUniform2fv(r_program_uniform_id uniform_id, const float* values)\n{\n\tr_program_uniform_t* base_uniform = &program_uniforms[uniform_id];\n\tgl_program_uniform_t* program_uniform = GL_ProgramUniformFind(uniform_id);\n\tgl_program_t* prog = R_CurrentSubProgram(base_uniform->program_id);\n\n\tif (program_uniform->location >= 0) {\n\t\tR_TraceLogAPICall(\"%s(%s/%s)\", __func__, prog->friendly_name, base_uniform->name);\n\t\tif (GL_Available(glProgramUniform2fv)) {\n\t\t\tGL_Procedure(glProgramUniform2fv, prog->program, program_uniform->location, base_uniform->count, values);\n\t\t}\n\t\telse {\n\t\t\tR_ProgramUse(base_uniform->program_id);\n\t\t\tGL_Procedure(glUniform2fv, program_uniform->location, base_uniform->count, values);\n\t\t}\n\t}\n}\n\nvoid R_ProgramUniform3f(r_program_uniform_id uniform_id, float x, float y, float z)\n{\n\tfloat vec[3] = { x, y, z };\n\n\tR_ProgramUniform3fv(uniform_id, vec);\n}\n\nstatic void R_ProgramStandardUniform3fv(r_program_id program_id, r_program_std_uniform_id id, const float* values)\n{\n\tgl_program_t* program = R_CurrentSubProgram(program_id);\n\tr_program_uniform_t* std_uniform = &program_std_uniforms[id];\n\tgl_program_uniform_t* program_uniform = &program->std_uniforms[id];\n\n\tif (program->program && !program_uniform->found) {\n\t\tprogram_uniform->location = GL_Function(glGetUniformLocation, program->program, std_uniform->name);\n\t\tprogram_uniform->found = true;\n\t}\n\n\tif (program_uniform->location >= 0) {\n\t\tR_TraceLogAPICall(\"%s(%s/%s)\", __func__, program->friendly_name, std_uniform->name);\n\t\tif (GL_Available(glProgramUniform3fv)) {\n\t\t\tGL_Procedure(glProgramUniform3fv, program->program, program_uniform->location, std_uniform->count, values);\n\t\t}\n\t\telse {\n\t\t\tR_ProgramUse(program_id);\n\t\t\tGL_Procedure(glUniform3fv, program_uniform->location, std_uniform->count, values);\n\t\t}\n\t}\n}\n\nstatic void R_ProgramStandardUniform1f(r_program_id program_id, r_program_std_uniform_id id, float value)\n{\n\tgl_program_t* program = R_CurrentSubProgram(program_id);\n\tr_program_uniform_t* std_uniform = &program_std_uniforms[id];\n\tgl_program_uniform_t* program_uniform = &program->std_uniforms[id];\n\n\tif (program->program && !program_uniform->found) {\n\t\tprogram_uniform->location = GL_Function(glGetUniformLocation, program->program, std_uniform->name);\n\t\tprogram_uniform->found = true;\n\t}\n\n\tif (program_uniform->location >= 0) {\n\t\tR_TraceLogAPICall(\"%s(%s/%s)\", __func__, program->friendly_name, std_uniform->name);\n\t\tif (GL_Available(glProgramUniform1f)) {\n\t\t\tGL_Procedure(glProgramUniform1f, program->program, program_uniform->location, value);\n\t\t}\n\t\telse {\n\t\t\tR_ProgramUse(program_id);\n\t\t\tGL_Procedure(glUniform1f, program_uniform->location, value);\n\t\t}\n\t}\n}\n\nvoid R_ProgramUniform3fv(r_program_uniform_id uniform_id, const float* values)\n{\n\tr_program_uniform_t* base_uniform = &program_uniforms[uniform_id];\n\tgl_program_uniform_t* program_uniform = GL_ProgramUniformFind(uniform_id);\n\tgl_program_t* prog = R_CurrentSubProgram(base_uniform->program_id);\n\n\tif (program_uniform->location >= 0) {\n\t\tR_TraceLogAPICall(\"%s(%s/%s)\", __func__, prog->friendly_name, base_uniform->name);\n\t\tif (GL_Available(glProgramUniform3fv)) {\n\t\t\tGL_Procedure(glProgramUniform3fv, prog->program, program_uniform->location, base_uniform->count, values);\n\t\t}\n\t\telse {\n\t\t\tR_ProgramUse(base_uniform->program_id);\n\t\t\tGL_Procedure(glUniform3fv, program_uniform->location, base_uniform->count, values);\n\t\t}\n\t}\n}\n\nvoid R_ProgramUniform3fNormalize(r_program_uniform_id uniform_id, const byte* values)\n{\n\tfloat float_values[3];\n\n\tfloat_values[0] = values[0] * 1.0f / 255;\n\tfloat_values[1] = values[1] * 1.0f / 255;\n\tfloat_values[2] = values[2] * 1.0f / 255;\n\n\tR_ProgramUniform3fv(uniform_id, float_values);\n}\n\nvoid R_ProgramUniform4fv(r_program_uniform_id uniform_id, const float* values)\n{\n\tr_program_uniform_t* base_uniform = &program_uniforms[uniform_id];\n\tgl_program_uniform_t* program_uniform = GL_ProgramUniformFind(uniform_id);\n\tgl_program_t* prog = R_CurrentSubProgram(base_uniform->program_id);\n\n\tif (program_uniform->location >= 0) {\n\t\tR_TraceLogAPICall(\"%s(%s/%s)\", __func__, prog->friendly_name, base_uniform->name);\n\t\tif (GL_Available(glProgramUniform4fv)) {\n\t\t\tGL_Procedure(glProgramUniform4fv, prog->program, program_uniform->location, base_uniform->count, values);\n\t\t}\n\t\telse {\n\t\t\tR_ProgramUse(base_uniform->program_id);\n\t\t\tGL_Procedure(glUniform4fv, program_uniform->location, base_uniform->count, values);\n\t\t}\n\t}\n}\n\nvoid R_ProgramUniformMatrix4fv(r_program_uniform_id uniform_id, const float* values)\n{\n\tr_program_uniform_t* base_uniform = &program_uniforms[uniform_id];\n\tgl_program_uniform_t* program_uniform = GL_ProgramUniformFind(uniform_id);\n\tgl_program_t* prog = R_CurrentSubProgram(base_uniform->program_id);\n\n\tif (program_uniform->location >= 0) {\n\t\tR_TraceLogAPICall(\"%s(%s/%s)\", __func__, prog->friendly_name, base_uniform->name);\n\t\tif (GL_Available(glProgramUniformMatrix4fv)) {\n\t\t\tGL_Procedure(glProgramUniformMatrix4fv, prog->program, program_uniform->location, base_uniform->count, base_uniform->transpose, values);\n\t\t}\n\t\telse {\n\t\t\tR_ProgramUse(base_uniform->program_id);\n\t\t\tGL_Procedure(glUniformMatrix4fv, program_uniform->location, base_uniform->count, base_uniform->transpose, values);\n\t\t}\n\t}\n}\n\nint R_ProgramUniformGet1i(r_program_uniform_id uniform_id, int default_value)\n{\n\tgl_program_uniform_t* program_uniform = GL_ProgramUniformFind(uniform_id);\n\n\tif (program_uniform->location >= 0) {\n\t\treturn program_uniform->int_value;\n\t}\n\treturn default_value;\n}\n\n// Set UBO block bindings manually when layout(binding=N) is unavailable (GL < 4.2)\nstatic void R_PatchProgramBindings(r_program_id program_id)\n{\n\tstatic const struct { const char* name; int binding; } storage_bindings[] = {\n\t\t{ \"WorldCvars\",            EZQ_GL_BINDINGPOINT_BRUSHMODEL_DRAWDATA },\n\t\t{ \"SamplerMappingsBuffer\", EZQ_GL_BINDINGPOINT_BRUSHMODEL_SAMPLERS },\n\t\t{ \"AliasModelData\",        EZQ_GL_BINDINGPOINT_ALIASMODEL_DRAWDATA },\n\t\t{ \"surface_data\",          EZQ_GL_BINDINGPOINT_WORLDMODEL_SURFACES },\n\t};\n\tGLuint prog, idx;\n\tint i;\n\n\tif (GL_VersionAtLeast(4, 2)) {\n\t\treturn; // layout(binding=N) works in-shader\n\t}\n\n\tprog = R_ProgramId(program_id);\n\n\t// Set UBO binding for GlobalState\n\tidx = GL_GetUniformBlockIndex(prog, \"GlobalState\");\n\tif (idx != GL_INVALID_INDEX) {\n\t\tGL_UniformBlockBinding(prog, idx, EZQ_GL_BINDINGPOINT_FRAMECONSTANTS);\n\t}\n\n\t// Set UBO bindings for storage blocks (SSBOs that became UBOs)\n\tfor (i = 0; i < (int)(sizeof(storage_bindings) / sizeof(storage_bindings[0])); i++) {\n\t\tidx = GL_GetUniformBlockIndex(prog, storage_bindings[i].name);\n\t\tif (idx != GL_INVALID_INDEX) {\n\t\t\tGL_UniformBlockBinding(prog, idx, EZQ_STORAGE_BLOCK_BINDING(storage_bindings[i].binding));\n\t\t}\n\t}\n}\n\nqbool R_ProgramCompile(r_program_id program_id)\n{\n\treturn R_ProgramCompileWithInclude(program_id, NULL);\n}\n\nqbool R_ProgramCompileWithInclude(r_program_id program_id, const char* included_definitions)\n{\n\tgl_program_t* program = R_CurrentSubProgram(program_id);\n\n\tmemset(program->shader_handles, 0, sizeof(program->shader_handles));\n\tQ_free(program->included_definitions);\n\tprogram->included_definitions = included_definitions ? Q_strdup(included_definitions) : NULL;\n\n\tif (GL_CompileProgram(program)) {\n\t\tif (program_id == currentProgram && GL_Available(glUseProgram)) {\n\t\t\tGL_Procedure(glUseProgram, R_CurrentSubProgram(program_id)->program);\n\t\t}\n\t\tR_ProgramFindUniformsForProgram(program_id);\n\t\tR_ProgramFindAttributesForProgram(program_id);\n\t\tR_PatchProgramBindings(program_id);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic void GL_BuildCoreDefinitions(void)\n{\n\t// macro_definitions: injected BEFORE common.glsl (macros used by common.glsl)\n\tmemset(macro_definitions, 0, sizeof(macro_definitions));\n\tstrlcpy(macro_definitions, (R_UseModernOpenGL() ? \"#define EZ_MODERN_GL\\n\" : \"#define EZ_LEGACY_GL\\n\"), sizeof(macro_definitions));\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\tif (R_UseModernOpenGL()) {\n\t\tif (GL_VersionAtLeast(4, 3)) {\n\t\t\tstrlcat(macro_definitions,\n\t\t\t\t\"#define EZ_HAS_SSBO 1\\n\"\n\t\t\t\t\"#define EZ_LAYOUT_BINDING(n) layout(binding=n)\\n\"\n\t\t\t\t\"#define EZ_SSBO_LAYOUT(qualifier, binding_name) layout(qualifier, binding=binding_name)\\n\"\n\t\t\t\t\"#define EZ_SSBO(name) buffer name\\n\"\n\t\t\t\t\"#define EZ_SSBO_ARRAY_SIZE(max_size)\\n\",\n\t\t\t\tsizeof(macro_definitions));\n\t\t}\n\t\telse {\n\t\t\tstrlcat(macro_definitions,\n\t\t\t\t\"#define EZ_LAYOUT_BINDING(n)\\n\"\n\t\t\t\t\"#define EZ_SSBO_LAYOUT(qualifier, binding_name) layout(qualifier)\\n\"\n\t\t\t\t\"#define EZ_SSBO(name) uniform name\\n\"\n\t\t\t\t\"#define EZ_SSBO_ARRAY_SIZE(max_size) max_size\\n\",\n\t\t\t\tsizeof(macro_definitions));\n\t\t}\n\t}\n#endif\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t// GLC core profile compatibility macros (only in actual core profile context)\n\tif (!R_UseModernOpenGL() && glConfig.coreProfile) {\n\t\tstrlcat(macro_definitions,\n\t\t\t// Shared uniforms (available in both vertex and fragment shaders)\n\t\t\t\"uniform mat4 ezModelViewProjectionMatrix;\\n\"\n\t\t\t\"uniform mat4 ezTextureMatrix[4];\\n\"\n\t\t\t\"#define gl_ModelViewProjectionMatrix ezModelViewProjectionMatrix\\n\"\n\t\t\t\"#define gl_TextureMatrix ezTextureMatrix\\n\"\n\t\t\t// Vertex shader compatibility\n\t\t\t\"#ifdef EZ_VERTEX_SHADER\\n\"\n\t\t\t\"#define attribute in\\n\"\n\t\t\t\"#define varying out\\n\"\n\t\t\t\"in vec4 ezVertex;\\n\"\n\t\t\t\"in vec3 ezNormal;\\n\"\n\t\t\t\"in vec4 ezColor;\\n\"\n\t\t\t\"in vec4 ezMultiTexCoord0;\\n\"\n\t\t\t\"in vec4 ezMultiTexCoord1;\\n\"\n\t\t\t\"#define gl_Vertex ezVertex\\n\"\n\t\t\t\"#define gl_Normal ezNormal\\n\"\n\t\t\t\"#define gl_Color ezColor\\n\"\n\t\t\t\"#define gl_MultiTexCoord0 ezMultiTexCoord0\\n\"\n\t\t\t\"#define gl_MultiTexCoord1 ezMultiTexCoord1\\n\"\n\t\t\t\"#define ftransform() (ezModelViewProjectionMatrix * ezVertex)\\n\"\n\t\t\t\"#endif\\n\"\n\t\t\t// Fragment shader compatibility\n\t\t\t\"#ifdef EZ_FRAGMENT_SHADER\\n\"\n\t\t\t\"#define varying in\\n\"\n\t\t\t\"out vec4 ezFragColor;\\n\"\n\t\t\t\"#define gl_FragColor ezFragColor\\n\"\n\t\t\t\"#define texture2D texture\\n\"\n\t\t\t\"#define texture2DArray texture\\n\"\n\t\t\t\"#define textureCube texture\\n\"\n\t\t\t\"#endif\\n\",\n\t\t\tsizeof(macro_definitions));\n\t}\n#endif\n\n\t// core_definitions: injected AFTER common.glsl (fog functions that use GlobalState members)\n\tmemset(core_definitions, 0, sizeof(core_definitions));\n\tstrlcat(core_definitions,\n\t\t\"#ifdef DRAW_FOG\\n\"\n\t\t\t\"#ifdef FOG_EXP\\n\"\n\t\t\t\"#ifdef EZ_LEGACY_GL\\n\"\n\t\t\t\t\"uniform float fogDensity;\\n\"\n\t\t\t\t\"uniform vec3 fogColor;\\n\"\n\t\t\t\"#endif\\n\"\n\t\t\t\t\"vec4 applyFog(vec4 vecinput, float z) {\\n\"\n\t\t\t\t\"\tfloat fogmix = exp(-fogDensity * z);\\n\"\n\t\t\t\t\"\tfogmix = clamp(fogmix, 0.0, 1.0); \\n\"\n\t\t\t\t\"\treturn vec4(mix(fogColor, vecinput.rgb, fogmix), 1) * vecinput.a; \\n\"\n\t\t\t\t\"}\\n\"\n\t\t\t\t\"vec4 applyFogBlend(vec4 vecinput, float z) {\\n\"\n\t\t\t\t\"\tfloat fogmix = exp(-fogDensity * z);\\n\"\n\t\t\t\t\"\tfogmix = clamp(fogmix, 0.0, 1.0); \\n\"\n\t\t\t\t\"\treturn vecinput * vec4(1, 1, 1, fogmix);\\n\"\n\t\t\t\t\"}\\n\"\n\t\t\t\"#elif defined(FOG_EXP2)\\n\"\n\t\t\t\t\"const float LOG2 = 1.442695;\\n\"\n\t\t\t\"#ifdef EZ_LEGACY_GL\\n\"\n\t\t\t\t\"uniform float fogDensity;\\n\"\n\t\t\t\t\"uniform vec3 fogColor;\\n\"\n\t\t\t\"#endif\"\n\t\t\t\t\"\\n\"\n\t\t\t\t\"vec4 applyFog(vec4 vecinput, float z) {\\n\"\n\t\t\t\t\"\tfloat fogmix = exp2(-fogDensity * z * z * LOG2);\\n\"\n\t\t\t\t\"\tfogmix = clamp(fogmix, 0.0, 1.0);\\n\"\n\t\t\t\t\"\treturn vec4(mix(fogColor, vecinput.rgb, fogmix), 1) * vecinput.a;\\n\"\n\t\t\t\t\"}\\n\"\n\t\t\t\t\"vec4 applyFogBlend(vec4 vecinput, float z) {\\n\"\n\t\t\t\t\"\tfloat fogmix = exp2(-fogDensity * z * z * LOG2);\\n\"\n\t\t\t\t\"\tfogmix = clamp(fogmix, 0.0, 1.0);\\n\"\n\t\t\t\t\"\treturn vecinput * vec4(1, 1, 1, fogmix);\\n\"\n\t\t\t\t\"}\\n\"\n\t\t\t\"#elif defined(FOG_LINEAR)\\n\"\n\t\t\t\"#ifdef EZ_LEGACY_GL\\n\"\n\t\t\t\t\"uniform float fogMinZ; \\n\"\n\t\t\t\t\"uniform float fogMaxZ; \\n\"\n\t\t\t\t\"uniform vec3 fogColor; \\n\"\n\t\t\t\"#endif\"\n\t\t\t\t\"\\n\"\n\t\t\t\t\"vec4 applyFog(vec4 vecinput, float z) {\\n\"\n\t\t\t\t\t\"float fogmix = (fogMaxZ - z) / (fogMaxZ - fogMinZ); \\n\"\n\t\t\t\t\t\"fogmix = clamp(fogmix, 0.0, 1.0); \\n\"\n\t\t\t\t\t\"return vec4(mix(fogColor, vecinput.rgb / vecinput.a, fogmix), 1) * vecinput.a; \\n\"\n\t\t\t\t\"}\\n\"\n\t\t\t\t\"vec4 applyFogBlend(vec4 vecinput, float z) {\\n\"\n\t\t\t\t\t\"float fogmix = (fogMaxZ - z) / (fogMaxZ - fogMinZ); \\n\"\n\t\t\t\t\t\"fogmix = clamp(fogmix, 0.0, 1.0); \\n\"\n\t\t\t\t\t\"return vecinput * vec4(1, 1, 1, fogmix);\\n\"\n\t\t\t\t\"}\\n\"\n\t\t\t\"#else\\n\"\n\t\t\t\t\"vec4 applyFog(vec4 vecinput, float z) {\\n\"\n\t\t\t\t\"\treturn vecinput; \\n\"\n\t\t\t\t\"}\\n\"\n\t\t\t\t\"vec4 applyFogBlend(vec4 vecinput, float z) {\\n\"\n\t\t\t\t\"\treturn vecinput; \\n\"\n\t\t\t\t\"}\\n\"\n\t\t\t\"#endif\\n\"\n\t\t\"#endif // DRAW_FOG\\n\", sizeof(core_definitions)\n\t);\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\tGL_DefineProgram_VF(r_program_aliasmodel, \"aliasmodel\", true, draw_aliasmodel, renderer_modern, GLM_CompileAliasModelProgram, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_brushmodel, \"brushmodel\", true, draw_world, renderer_modern, GLM_CompileDrawWorldProgram, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_brushmodel_alphatested, \"brushmodel-alphatested\", true, draw_world, renderer_modern, GLM_CompileDrawWorldProgramAlphaTested, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_sprite3d, \"3d-sprites\", false, draw_sprites, renderer_modern, GLM_Compile3DSpriteProgram, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_hud_images, \"image-draw\", true, hud_draw_image, renderer_modern, GLM_CreateMultiImageProgram, STDOPTIONS_NONE);\n\tGL_DefineProgram_VF(r_program_hud_circles, \"circle-draw\", false, hud_draw_circle, renderer_modern, GLM_CompileHudCircleProgram, STDOPTIONS_NONE);\n\tGL_DefineProgram_VF(r_program_post_process, \"post-process-screen\", true, post_process_screen, renderer_modern, GLM_CompilePostProcessProgram, STDOPTIONS_NONE);\n\tGL_DefineProgram_CS(r_program_lightmap_compute, \"lightmaps\", false, lighting, renderer_modern, GLM_CompileLightmapComputeProgram, STDOPTIONS_NONE);\n\tGL_DefineProgram_VF(r_program_fx_world_geometry, \"world-geometry\", true, fx_world_geometry, renderer_modern, GLM_CompileWorldGeometryProgram, STDOPTIONS_NONE);\n\tGL_DefineProgram_VF(r_program_simple, \"simple\", false, simple, renderer_modern, GLM_CompileSimpleProgram, STDOPTIONS_NONE);\n\tGL_DefineProgram_VF(r_program_simple3d, \"simple3d\", false, simple3d, renderer_modern, GLM_CompileSimple3dProgram, STDOPTIONS_NONE);\n#endif\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tGL_DefineProgram_VF(r_program_post_process_glc, \"post-process-screen\", true, glc_post_process_screen, renderer_classic, GLC_CompilePostProcessProgram, STDOPTIONS_NONE);\n\tGL_DefineProgram_VF(r_program_sky_glc, \"sky-rendering\", true, glc_sky, renderer_classic, GLC_SkyProgramCompile, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_turb_glc, \"turb-rendering\", true, glc_turbsurface, renderer_classic, GLC_TurbSurfaceProgramCompile, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_caustics_glc, \"caustics-rendering\", true, glc_caustics, renderer_classic, GLC_CausticsProgramCompile, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_aliasmodel_std_glc, \"aliasmodel-std\", true, glc_aliasmodel_std, renderer_classic, GLC_AliasModelStandardCompile, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_aliasmodel_shell_glc, \"aliasmodel-shell\", true, glc_aliasmodel_shell, renderer_classic, GLC_AliasModelShellCompile, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_aliasmodel_shadow_glc, \"aliasmodel-shadow\", true, glc_aliasmodel_shadow, renderer_classic, GLC_AliasModelShadowCompile, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_aliasmodel_outline_glc, \"aliasmodel-outline\", true, glc_aliasmodel_std, renderer_classic, GLC_AliasModelOutlineCompile, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_world_drawflat_glc, \"drawflat-world\", true, glc_world_drawflat, renderer_classic, GLC_DrawflatProgramCompile, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_world_textured_glc, \"textured-world\", true, glc_world_textured, renderer_classic, GLC_PreCompileWorldPrograms, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_world_secondpass_glc, \"secondpass-world\", true, glc_world_secondpass, renderer_classic, GLC_PreCompileWorldPrograms, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_sprites_glc, \"3d-sprites\", true, glc_draw_sprites, renderer_classic, GLC_CompileSpriteProgram, STDOPTIONS_FOG);\n\tGL_DefineProgram_VF(r_program_hud_images_glc, \"hud-images\", true, glc_hud_images, renderer_classic, GLC_ProgramHudImagesCompile, STDOPTIONS_NONE);\n#endif\n}\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\nint R_ProgramAttributeLocation(r_program_attribute_id attr_id)\n{\n\tr_program_id program_id;\n\tgl_program_attribute_t* attr;\n\n\t// FIXME: why is this is in release build?\n\tif (attr_id < 0 || attr_id >= r_program_attribute_count) {\n\t\treturn -1;\n\t}\n\n\tprogram_id = program_attributes[attr_id].program_id;\n\tattr = &(R_CurrentSubProgram(program_id)->attributes[attr_id]);\n\tif (!attr->found) {\n\t\treturn -1;\n\t}\n\n\treturn attr->location;\n}\n\nr_program_id R_ProgramForAttribute(r_program_attribute_id attr_id)\n{\n\tif (attr_id < 0 || attr_id >= r_program_attribute_count) {\n\t\treturn r_program_none;\n\t}\n\n\treturn program_attributes[attr_id].program_id;\n}\n\nstatic r_program_attribute_t* GL_ProgramAttributeFind(r_program_attribute_id attribute_id)\n{\n\tr_program_attribute_t* attribute = &program_attributes[attribute_id];\n\tr_program_id program_id = attribute->program_id;\n\tgl_program_t* program = R_CurrentSubProgram(program_id);\n\tgl_program_attribute_t* program_attr = &program->attributes[attribute_id];\n\n\tif (program->program && !program_attr->found) {\n\t\tprogram_attr->location = GL_Function(glGetAttribLocation, program->program, attribute->name);\n\t\tprogram_attr->found = true;\n\t}\n\n\treturn attribute;\n}\n\nstatic void R_ProgramFindAttributesForProgram(r_program_id program_id)\n{\n\tr_program_attribute_id a;\n\n\tfor (a = 0; a < r_program_attribute_count; ++a) {\n\t\tif (program_attributes[a].program_id == program_id) {\n\t\t\tR_CurrentSubProgram(program_id)->attributes[a].found = false;\n\t\t\tGL_ProgramAttributeFind(a);\n\t\t}\n\t}\n}\n#endif\n\nvoid R_ProgramCompileAll(void)\n{\n\tint i;\n\tint sub_program;\n\n\tfor (i = 0; i < r_program_count; ++i) {\n\t\tfor (sub_program = 0; sub_program < MAX_SUBPROGRAMS; ++sub_program) {\n\t\t\tgl_program_t* prog = R_SpecificSubProgram(i, sub_program);\n\n\t\t\tR_ProgramSetSubProgram(i, sub_program);\n\t\t\tif (!GL_AppropriateRenderer(prog->renderer_id)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (prog->initialised && prog->compile_func) {\n\t\t\t\tprog->compile_func();\n\t\t\t}\n\t\t}\n\t\tR_ProgramSetSubProgram(i, 0);\n\t}\n}\n\nvoid R_ProgramSetSubProgram(r_program_id program_id, int sub_index)\n{\n\tprogram_currentSubProgram[program_id] = sub_index;\n}\n\nvoid R_ProgramSetStandardUniforms(r_program_id program_id)\n{\n\tgl_program_t* program = R_CurrentSubProgram(program_id);\n\n\tif (program->standard_options & STDOPTIONS_FOG) {\n\t\tif (r_refdef2.fog_enabled) {\n\t\t\tR_ProgramStandardUniform3fv(program_id, r_program_std_uniform_fog_color, r_refdef2.fog_color);\n\n\t\t\tif (r_refdef2.fog_calculation == fogcalc_linear) {\n\t\t\t\tR_ProgramStandardUniform1f(program_id, r_program_std_uniform_fog_linear_start, r_refdef2.fog_linear_start);\n\t\t\t\tR_ProgramStandardUniform1f(program_id, r_program_std_uniform_fog_linear_end, r_refdef2.fog_linear_end);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tR_ProgramStandardUniform1f(program_id, r_program_std_uniform_fog_density, r_refdef2.fog_density);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/gl_sdl.c",
    "content": "/*\n===========================================================================\nCopyright (C) 1999-2005 Id Software, Inc.\n\nThis file is part of Quake III Arena source code.\n\nQuake III Arena source code is free software; you can redistribute it\nand/or modify it under the terms of the GNU General Public License as\npublished by the Free Software Foundation; either version 2 of the License,\nor (at your option) any later version.\n\nQuake III Arena source code is distributed in the hope that it will be\nuseful, but WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Foobar; if not, write to the Free Software\nFoundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n===========================================================================\n\n*/\n\n#include <SDL.h>\n#include \"quakedef.h\"\n#include \"gl_local.h\"\n\nstatic void GL_SDL_SetupAttributes(const opengl_version_t* version)\n{\n\tif (version->legacy) {\n\t\t// SDL defaults - take what we can get.  SDL rules are profile_mask == flags == 0 && majorversion < 3 => legacy profile\n\t\tSDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 0);\n\t\tSDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);\n\t\tSDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);\n\t\tSDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);\n\t}\n\telse {\n\t\tint contextFlags = 0;\n\n\t\tSDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, version->majorVersion);\n\t\tSDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, version->minorVersion);\n\t\tSDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, version->core ? SDL_GL_CONTEXT_PROFILE_CORE : SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);\n\n#ifdef __APPLE__\n\t\t// https://www.khronos.org/opengl/wiki/OpenGL_Context\n\t\t// Recommendation: You should use the forward compatibility bit only if you need compatibility with MacOS.\n\t\t// That API requires the forward compatibility bit to create any core profile context.\n\t\tcontextFlags |= version->core ? SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG : 0;\n#else\n\t\tcontextFlags |= version->core && COM_CheckParm(cmdline_param_client_forwardonlyprofile) ? SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG : 0;\n#endif\n\t\tcontextFlags |= R_DebugProfileContext() ? SDL_GL_CONTEXT_DEBUG_FLAG : 0;\n\t\tSDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, contextFlags);\n\t}\n}\n\nSDL_GLContext GL_SDL_CreateBestContext(SDL_Window* window, const opengl_version_t* versions, int count)\n{\n\tSDL_GLContext context;\n\tint attempt;\n#ifdef _WIN32\n\tint accelerated = 0;\n\n\tSDL_GL_GetAttribute(SDL_GL_ACCELERATED_VISUAL, &accelerated);\n#endif\n\n\tfor (attempt = 0; attempt < count; ++attempt) {\n\t\textern cvar_t vid_gl_core_profile;\n\t\tconst opengl_version_t* version = &versions[attempt];\n\n\t\tif (vid_gl_core_profile.integer && !version->core && !version->legacy) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tGL_SDL_SetupAttributes(version);\n\n\t\tcontext = SDL_GL_CreateContext(window);\n\t\tif (COM_CheckParm(cmdline_param_console_debug)) {\n\t\t\tCon_Printf(\"Asked for OpenGL %d.%d, got %s\\n\", version->majorVersion, version->minorVersion, context ? (const char*)glGetString(GL_VENDOR) : \"failure\");\n\t\t}\n\n#ifdef _WIN32\n\t\t// SDL2 falls back to not even asking if it's accelerated, so we might get software :(\n\t\tif (context && (version->core || accelerated) && strstr((const char*)glGetString(GL_VENDOR), \"Microsoft\") != NULL) {\n\t\t\tif (COM_CheckParm(cmdline_param_console_debug)) {\n\t\t\t\tCon_Printf(\"... rejecting unaccelerated context\\n\");\n\t\t\t}\n\t\t\tSDL_GL_DeleteContext(context);\n\t\t\tcontext = NULL;\n\t\t}\n#endif\n\t\tif (context) {\n\t\t\tif (glGetString(GL_VERSION) && atoi((const char*)glGetString(GL_VERSION)) >= version->majorVersion) {\n\t\t\t\treturn context;\n\t\t\t}\n\t\t\t\n\t\t\tif (COM_CheckParm(cmdline_param_console_debug)) {\n\t\t\t\tCon_Printf(\"... rejecting lower OpenGL version (%d vs %d)\\n\", atoi((const char*)glGetString(GL_VERSION)), version->majorVersion);\n\t\t\t}\n\t\t\tSDL_GL_DeleteContext(context);\n\t\t\tcontext = NULL;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n"
  },
  {
    "path": "src/gl_sprite3d.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// 3D sprites\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"gl_sprite3d.h\"\n#include \"r_sprite3d_internal.h\"\n#include \"tr_types.h\"\n\nGLenum glPrimitiveTypes[r_primitive_count] = {\n\tGL_TRIANGLE_STRIP,\n\tGL_TRIANGLE_FAN,\n\tGL_TRIANGLES\n};\n\nvoid GL_DrawSequentialBatchImpl(gl_sprite3d_batch_t* batch, int first_batch, int last_batch, int index_offset, GLuint maximum_batch_size)\n{\n\tint vertOffset = batch->glFirstVertices[first_batch];\n\tint numVertices = batch->numVertices[first_batch];\n\tint batch_count = last_batch - first_batch;\n\tvoid* indexes = (void*)(index_offset * sizeof(GLuint));\n\tint terminators = GL_Supported(R_SUPPORT_PRIMITIVERESTART) ? 1 : (numVertices % 2 == 0 ? 2 : 3);\n\n\t// Ugh!\n\tif (!GL_Supported(R_SUPPORT_PRIMITIVERESTART) && batch->primitive_id == r_primitive_triangle_fan) {\n\t\tmaximum_batch_size = 1;\n\t}\n\n\twhile (batch_count > maximum_batch_size) {\n\t\tGL_DrawElementsBaseVertex(glPrimitiveTypes[batch->primitive_id], maximum_batch_size * numVertices + (maximum_batch_size - 1) * terminators, GL_UNSIGNED_INT, indexes, vertOffset);\n\t\tbatch_count -= maximum_batch_size;\n\t\tvertOffset += maximum_batch_size * numVertices;\n\t}\n\tif (batch_count) {\n\t\tGL_DrawElementsBaseVertex(glPrimitiveTypes[batch->primitive_id], batch_count * numVertices + (batch_count - 1) * terminators, GL_UNSIGNED_INT, indexes, vertOffset);\n\t}\n}\n"
  },
  {
    "path": "src/gl_sprite3d.h",
    "content": "\n#ifndef EZQUAKE_GL_SPRITE3D_HEADER\n#define EZQUAKE_GL_SPRITE3D_HEADER\n\n#include \"r_sprite3d.h\"\n#include \"r_sprite3d_internal.h\"\n\n// GL only\nextern GLenum glPrimitiveTypes[r_primitive_count];\n\nvoid GL_DrawSequentialBatchImpl(gl_sprite3d_batch_t* batch, int first_batch, int last_batch, int index_offset, GLuint maximum_batch_size);\n\nextern int indexes_start_quads;\nextern int indexes_start_flashblend;\nextern int indexes_start_sparks;\n\n#endif // #ifndef EZQUAKE_GL_SPRITE3D_HEADER\n"
  },
  {
    "path": "src/gl_state.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// gl_state.c\n// State caching for OpenGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"tr_types.h\"\n#include \"r_state.h\"\n#include \"r_texture.h\"\n#include \"r_vao.h\"\n#include \"glm_vao.h\"\n#include \"glc_vao.h\"\n#include \"vk_vao.h\"\n#include \"r_matrix.h\"\n#include \"r_buffers.h\"\n#include \"glm_local.h\"\n#include \"gl_texture_internal.h\"\n#include \"r_renderer.h\"\n#include \"r_program.h\"\n#include \"glc_state.h\"\n\nstatic void GLC_ToggleAlphaTesting(const rendering_state_t* state, rendering_state_t* current);\nstatic rendering_state_t states[r_state_count];\n\n// Texture functions\nGL_StaticProcedureDeclaration(glBindTextures, \"first=%u, count=%d, format=%p\", GLuint first, GLsizei count, const GLuint* format)\nGL_StaticProcedureDeclaration(glBindImageTexture, \"unit=%u, texture=%u, level=%d, layered=%d, layer=%d, access=%u, format=%u\", GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format)\nGL_StaticProcedureDeclaration(glActiveTexture, \"target=%u\", GLenum target)\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\nGL_StaticProcedureDeclaration(glMultiTexCoord2f, \"target=%u, s=%f, t=%f\", GLenum target, GLfloat s, GLfloat t)\nGL_StaticProcedureDeclaration(glClientActiveTexture, \"target=%u\", GLenum target)\n#endif\n\n#ifdef WITH_RENDERING_TRACE\nstatic const char* gl_friendlyTextureTargetNames[] = {\n\t\"GL_TEXTURE_2D\",              // texture_type_2d,\n\t\"GL_TEXTURE_2D_ARRAY\",        // texture_type_2d_array,\n\t\"GL_TEXTURE_CUBE_MAP\",        // texture_type_cubemap,\n\t\"GL_TEXTURE_2D_MULTISAMPLE\"   // texture_type_2d_multisampled,\n};\n\nstatic const char* fogModeDescriptions[] = {\n\t\"disabled\",\n\t\"enabled\"\n};\n#ifdef C_ASSERT\nC_ASSERT(sizeof(fogModeDescriptions) / sizeof(fogModeDescriptions[0]) == r_fogmode_count);\n#endif\n#endif\n\nvoid R_InitialiseStates(void);\n\n// VAOs\nstatic r_vao_id currentVAO = vao_none;\nstatic const char* vaoNames[] = {\n\t\"none\",\n\t\"aliasmodel\",\n\t\"brushmodel\",\n\t\"3d-sprites\",\n\t\"hud:circles\",\n\t\"hud:images\",\n\t\"hud:lines\",\n\t\"hud:polygons\",\n\t\"post-process\",\n\t\"powerupshell\",\n\t\"brushmodel_details\",\n\t\"brushmodel_lightmap_pass\",\n\t\"brushmodel_lm_unit1\",\n\t\"brushmodel_simpletex\",\n\t\"vao_hud_images_non_glsl\"\n};\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(vaoNames) / sizeof(vaoNames[0]) == vao_count);\n#endif\n\n#define MAX_LOGGED_TEXTURE_UNITS 32\n#define MAX_LOGGED_IMAGE_UNITS   32\n\ntypedef struct image_unit_binding_s {\n\tGLuint texture;\n\tGLint level;\n\tGLboolean layered;\n\tGLint layer;\n\tGLenum access;\n\tGLenum format;\n} image_unit_binding_t;\n\nstatic void GL_BindTexture(GLenum targetType, GLuint texnum, qbool warning);\n\n/*typedef struct {\n\tGLenum currentTextureUnit;\n\tGLenum bound_texture_state[texture_type_count][MAX_LOGGED_TEXTURE_UNITS];\n\timage_unit_binding_t bound_images[MAX_LOGGED_IMAGE_UNITS];\n\n\tGLenum unit_texture_mode[texture_type_count][MAX_LOGGED_TEXTURE_UNITS]\n\tqbool texunitenabled[MAX_LOGGED_TEXTURE_UNITS];\n} texture_state_t;\n*/\ntypedef struct {\n\trendering_state_t rendering_state;\n\t//texture_state_t textures;\n} opengl_state_t;\n\nstatic GLenum currentTextureUnit;\nstatic GLuint bound_texture_state[texture_type_count][MAX_LOGGED_TEXTURE_UNITS];\nstatic image_unit_binding_t bound_images[MAX_LOGGED_IMAGE_UNITS];\n\nstatic opengl_state_t opengl;\nstatic GLenum glDepthFunctions[] = {\n\tGL_LESS,   // r_depthfunc_less,\n\tGL_EQUAL,  // r_depthfunc_equal,\n\tGL_LEQUAL, // r_depthfunc_lessorequal,\n};\nstatic GLenum glReversedDepthFunctions[] = {\n\tGL_GREATER, // inv(r_depthfunc_less)\n\tGL_EQUAL,   // inv(r_depthfunc_equal)\n\tGL_GEQUAL   // inv(r_depthfunc_lessorequal)\n};\nstatic GLenum glCullFaceValues[] = {\n\tGL_FRONT,  // r_cullface_front\n\tGL_BACK,   // r_cullface_back\n};\nstatic GLenum glBlendFuncValuesSource[] = {\n\tGL_ONE, // r_blendfunc_overwrite,\n\tGL_ONE, // r_blendfunc_additive_blending,\n\tGL_ONE, // r_blendfunc_premultiplied_alpha,\n\tGL_DST_COLOR, // r_blendfunc_src_dst_color_dest_zero,\n\tGL_DST_COLOR, // r_blendfunc_src_dst_color_dest_one,\n\tGL_DST_COLOR, // r_blendfunc_src_dst_color_dest_src_color,\n\tGL_ZERO, // r_blendfunc_src_zero_dest_one_minus_src_color,\n\tGL_ZERO, // r_blendfunc_src_zero_dest_src_color,\n\tGL_ONE, // r_blendfunc_src_one_dest_zero,\n\tGL_ZERO, // r_blendfunc_src_zero_dest_one,\n\tGL_ONE, // r_blendfunc_src_one_dest_one_minus_src_color,\n};\nstatic GLenum glBlendFuncValuesDestination[] = {\n\tGL_ZERO, // r_blendfunc_overwrite,\n\tGL_ONE, // r_blendfunc_additive_blending,\n\tGL_ONE_MINUS_SRC_ALPHA, // r_blendfunc_premultiplied_alpha,\n\tGL_ZERO, // r_blendfunc_src_dst_color_dest_zero,\n\tGL_ONE, // r_blendfunc_src_dst_color_dest_one,\n\tGL_SRC_COLOR, // r_blendfunc_src_dst_color_dest_src_color,\n\tGL_ONE_MINUS_SRC_COLOR, // r_blendfunc_src_zero_dest_one_minus_src_color,\n\tGL_SRC_COLOR, // r_blendfunc_src_zero_dest_src_color,\n\tGL_ZERO, // r_blendfunc_src_one_dest_zero,\n\tGL_ONE, // r_blendfunc_src_zero_dest_one,\n\tGL_ONE_MINUS_SRC_COLOR, // r_blendfunc_src_one_dest_one_minus_src_color,\n};\nstatic GLenum glPolygonModeValues[] = {\n\tGL_FILL, // r_polygonmode_fill,\n\tGL_LINE, // r_polygonmode_line,\n};\nstatic GLenum glAlphaTestModeValues[] = {\n\tGL_ALWAYS,  // r_alphatest_func_always,\n\tGL_GREATER, // r_alphatest_func_greater,\n};\nstatic GLenum glTextureEnvModeValues[] = {\n\tGL_BLEND, GL_REPLACE, GL_MODULATE, GL_DECAL, GL_ADD\n};\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(glBlendFuncValuesSource) / sizeof(glBlendFuncValuesSource[0]) == r_blendfunc_count);\nC_ASSERT(sizeof(glBlendFuncValuesDestination) / sizeof(glBlendFuncValuesDestination[0]) == r_blendfunc_count);\nC_ASSERT(sizeof(glTextureEnvModeValues) / sizeof(glTextureEnvModeValues[0]) == r_texunit_mode_count);\nC_ASSERT(sizeof(glDepthFunctions) / sizeof(glDepthFunctions[0]) == r_depthfunc_count);\nC_ASSERT(sizeof(glReversedDepthFunctions) / sizeof(glReversedDepthFunctions[0]) == r_depthfunc_count);\nC_ASSERT(sizeof(glCullFaceValues) / sizeof(glCullFaceValues[0]) == r_cullface_count);\nC_ASSERT(sizeof(glPolygonModeValues) / sizeof(glPolygonModeValues[0]) == r_polygonmode_count);\nC_ASSERT(sizeof(glAlphaTestModeValues) / sizeof(glAlphaTestModeValues[0]) == r_alphatest_func_count);\n#endif\n\n#ifdef WITH_RENDERING_TRACE\nstatic const char* txtDepthFunctions[] = {\n\t\"<\", // r_depthfunc_less,\n\t\"=\", // r_depthfunc_equal,\n\t\"<=\", // r_depthfunc_lessorequal,\n};\nstatic const char* txtReversedDepthFunctions[] = {\n\t\">\", // r_depthfunc_less,\n\t\"=\", // r_depthfunc_equal,\n\t\">=\", // r_depthfunc_lessorequal,\n};\nstatic const char* txtCullFaceValues[] = {\n\t\"front\", // r_cullface_front,\n\t\"back\", // r_cullface_back,\n};\nstatic const char* txtBlendFuncNames[] = {\n\t\"overwrite\", // r_blendfunc_overwrite,\n\t\"additive\",        // r_blendfunc_additive_blending,\n\t\"premul-alpha\",    // r_blendfunc_premultiplied_alpha,\n\t\"src_dst_color_dest_zero\", // r_blendfunc_src_dst_color_dest_zero\n\t\"src_dst_color_dest_one\", // r_blendfunc_src_dst_color_dest_one\n\t\"src_dst_color_dest_src_color\", // r_blendfunc_src_dst_color_dest_src_color\n\t\"src_zero_dest_one_minus_src_color\", // r_blendfunc_src_zero_dest_one_minus_src_color\n\t\"src_zero_dest_src_color\", // r_blendfunc_src_zero_dest_src_color\n\t\"src_one_dest_zero\", // r_blendfunc_src_one_dest_zero\n\t\"src_zero_dest_one\", // r_blendfunc_src_zero_dest_one\n\t\"src_one_dest_one_minus_src_color\", // \"r_blendfunc_src_one_dest_one_minus_src_color\"\n};\nstatic const char* txtPolygonModeValues[] = {\n\t\"fill\", // r_polygonmode_fill,\n\t\"line\", // r_polygonmode_line,\n};\nstatic const char* txtAlphaTestModeValues[] = {\n\t\"always\", // r_alphatest_func_always,\n\t\"greater\", // r_alphatest_func_greater,\n};\nstatic const char* txtTextureEnvModeValues[] = {\n\t\"GL_BLEND\", \"GL_REPLACE\", \"GL_MODULATE\", \"GL_DECAL\", \"GL_ADD\"\n};\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(txtDepthFunctions) / sizeof(txtDepthFunctions[0]) == r_depthfunc_count);\nC_ASSERT(sizeof(txtReversedDepthFunctions) / sizeof(txtReversedDepthFunctions[0]) == r_depthfunc_count);\nC_ASSERT(sizeof(txtCullFaceValues) / sizeof(txtCullFaceValues[0]) == r_cullface_count);\nC_ASSERT(sizeof(txtBlendFuncNames) / sizeof(txtCullFaceValues[0]) == r_blendfunc_count);\nC_ASSERT(sizeof(txtPolygonModeValues) / sizeof(txtPolygonModeValues[0]) == r_polygonmode_count);\nC_ASSERT(sizeof(txtAlphaTestModeValues) / sizeof(txtAlphaTestModeValues[0]) == r_alphatest_func_count);\nC_ASSERT(sizeof(txtTextureEnvModeValues) / sizeof(txtTextureEnvModeValues[0]) == r_texunit_mode_count);\n#endif\n#endif\n\nrendering_state_t* R_InitRenderingState(r_state_id id, qbool default_state, const char* name, r_vao_id vao)\n{\n\trendering_state_t* state = &states[id];\n\tSDL_Window* window = SDL_GL_GetCurrentWindow();\n\n\tstrlcpy(state->name, name, sizeof(state->name));\n\n\tstate->depth.func = r_depthfunc_less;\n\tstate->depth.nearRange = 0;\n\tstate->depth.farRange = 1;\n\tstate->depth.test_enabled = false;\n\tstate->depth.mask_enabled = false;\n\n\tstate->blendFunc = r_blendfunc_overwrite;\n\tstate->blendingEnabled = false;\n\tR_GLC_ConfigureAlphaTesting(state, false, r_alphatest_func_always, 0);\n\n\tstate->currentViewportX = 0;\n\tstate->currentViewportY = 0;\n\tSDL_GL_GetDrawableSize(window, &state->currentViewportWidth, &state->currentViewportHeight);\n\n\tstate->cullface.enabled = false;\n\tstate->cullface.mode = r_cullface_back;\n\n\tstate->framebuffer_srgb = false;\n\tstate->line.smooth = false;\n\tstate->line.width = 1.0f;\n\tstate->fog.mode = r_fogmode_disabled;\n\tstate->polygonOffset.option = r_polygonoffset_disabled;\n\tstate->polygonOffset.factor = 0;\n\tstate->polygonOffset.units = 0;\n\tstate->polygonOffset.fillEnabled = false;\n\tstate->polygonOffset.lineEnabled = false;\n\tstate->polygonMode = r_polygonmode_fill;\n\tstate->clearColor[0] = state->clearColor[1] = state->clearColor[2] = state->clearColor[3] = 0;\n\tstate->color[0] = state->color[1] = state->color[2] = state->color[3] = 1;\n\tstate->colorMask[0] = state->colorMask[1] = state->colorMask[2] = state->colorMask[3] = true;\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t{\n\t\tint i;\n\n\t\tmemset(state->textureUnits, 0, sizeof(state->textureUnits));\n\n\t\tfor (i = 0; i < sizeof(state->textureUnits) / sizeof(state->textureUnits[0]); ++i) {\n\t\t\tstate->textureUnits[i].mode = r_texunit_mode_modulate;\n\t\t\tstate->textureUnits[i].va.size = 4;\n\t\t\tstate->textureUnits[i].va.type = GL_FLOAT;\n\t\t}\n\t}\n\n\tstate->vertex_array.size = 4;\n\tstate->vertex_array.type = GL_FLOAT;\n\n\tstate->color_array.size = 4;\n\tstate->color_array.type = GL_FLOAT;\n\n\tstate->normal_array.type = GL_FLOAT;\n#endif\n\n\t// Not applied each time, kept distinct\n\tstate->pack_alignment = 4;\n\n\tstate->vao_id = vao;\n\n\tif (default_state) {\n\t\tstate->cullface.mode = r_cullface_front;\n\t\tstate->cullface.enabled = true;\n\n\t\tstate->depth.func = r_depthfunc_lessorequal;\n\t\tR_GLC_ConfigureAlphaTesting(state, false, r_alphatest_func_greater, 0.666f);\n\t\tstate->blendingEnabled = false;\n\t\tstate->depth.test_enabled = true;\n\t\tstate->depth.mask_enabled = true;\n\n\t\tstate->clearColor[0] = 0;\n\t\tstate->clearColor[1] = 0;\n\t\tstate->clearColor[2] = 0;\n\t\tstate->clearColor[3] = 1;\n\n\t\tstate->polygonMode = r_polygonmode_fill;\n\t\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\t\tR_GLC_TextureUnitSet(state, 0, false, r_texunit_mode_replace);\n\t\tstate->framebuffer_srgb = true;\n\n\t\tstate->pack_alignment = 1;\n\t}\n\n\tstate->initialized = true;\n\treturn state;\n}\n\nrendering_state_t* R_Init3DSpriteRenderingState(r_state_id id, const char* name)\n{\n\trendering_state_t* state = R_InitRenderingState(id, true, name, vao_3dsprites);\n\n\tstate->fog.mode = r_fogmode_enabled;\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tstate->cullface.enabled = false;\n\tR_GLC_ConfigureAlphaTesting(state, false, r_alphatest_func_greater, 0.333f);\n\n\treturn state;\n}\n\n#define GL_ApplySimpleToggle(state, current, field, option) \\\n\tif (state->field != current->field) { \\\n\t\tif (state->field) { \\\n\t\t\tglEnable(option); \\\n\t\t\tR_TraceLogAPICall(\"glEnable(\" # option \")\"); \\\n\t\t} \\\n\t\telse { \\\n\t\t\tglDisable(option); \\\n\t\t\tR_TraceLogAPICall(\"glDisable(\" # option \")\"); \\\n\t\t} \\\n\t\tcurrent->field = state->field; \\\n\t}\n\nvoid GL_ApplyRenderingState(r_state_id id)\n{\n\trendering_state_t* state = &states[id];\n\textern cvar_t gl_brush_polygonoffset;\n\trendering_state_t* current = &opengl.rendering_state;\n\tfloat zRange[2] = {\n\t\tglConfig.reversed_depth && false ? 1.0f - state->depth.nearRange : state->depth.nearRange,\n\t\tglConfig.reversed_depth && false ? 1.0f - state->depth.farRange : state->depth.farRange,\n\t};\n\n\tR_TraceEnterRegion(va(\"GL_ApplyRenderingState(%s)\", state->name), true);\n\n\tif (state->depth.func != current->depth.func) {\n\t\tcurrent->depth.func = state->depth.func;\n\t\tif (glConfig.reversed_depth) {\n\t\t\tglDepthFunc(glReversedDepthFunctions[current->depth.func]);\n\t\t\tR_TraceLogAPICall(\"glDepthFunc(reversed(%s)=%s)\", txtDepthFunctions[current->depth.func], txtReversedDepthFunctions[current->depth.func]);\n\t\t}\n\t\telse {\n\t\t\tglDepthFunc(glDepthFunctions[current->depth.func]);\n\t\t\tR_TraceLogAPICall(\"glDepthFunc(%s)\", txtDepthFunctions[current->depth.func]);\n\t\t}\n\t}\n\tif (zRange[0] != current->depth.nearRange || zRange[1] != current->depth.farRange) {\n\t\tglDepthRange(\n\t\t\tcurrent->depth.nearRange = zRange[0],\n\t\t\tcurrent->depth.farRange = zRange[1]\n\t\t);\n\t\tR_TraceLogAPICall(\"glDepthRange(%f,%f)\", zRange[0], zRange[1]);\n\t}\n\tif (state->cullface.mode != current->cullface.mode) {\n\t\tglCullFace(glCullFaceValues[current->cullface.mode = state->cullface.mode]);\n\t\tR_TraceLogAPICall(\"glCullFace(%s)\", txtCullFaceValues[state->cullface.mode]);\n\t}\n\tif (state->blendFunc != current->blendFunc) {\n\t\tcurrent->blendFunc = state->blendFunc;\n\t\tglBlendFunc(\n\t\t\tglBlendFuncValuesSource[state->blendFunc],\n\t\t\tglBlendFuncValuesDestination[state->blendFunc]\n\t\t);\n\t\tR_TraceLogAPICall(\"glBlendFunc(%s)\", txtBlendFuncNames[state->blendFunc]);\n\t}\n\tif (state->line.width != current->line.width) {\n\t\tglLineWidth(current->line.width = state->line.width);\n\t\tR_TraceLogAPICall(\"glLineWidth(%f)\", current->line.width);\n\t}\n\tGL_ApplySimpleToggle(state, current, depth.test_enabled, GL_DEPTH_TEST);\n\tif (state->depth.mask_enabled != current->depth.mask_enabled) {\n\t\tglDepthMask((current->depth.mask_enabled = state->depth.mask_enabled) ? GL_TRUE : GL_FALSE);\n\t\tR_TraceLogAPICall(\"glDepthMask(%s)\", current->depth.mask_enabled ? \"on\" : \"off\");\n\t}\n\tif (vid_gammacorrection.integer) {\n\t\tGL_ApplySimpleToggle(state, current, framebuffer_srgb, GL_FRAMEBUFFER_SRGB);\n\t}\n\tGL_ApplySimpleToggle(state, current, cullface.enabled, GL_CULL_FACE);\n\tGL_ApplySimpleToggle(state, current, line.smooth, GL_LINE_SMOOTH);\n\n\tif (state->polygonOffset.option != current->polygonOffset.option || gl_brush_polygonoffset.modified) {\n\t\tR_CustomPolygonOffset(state->polygonOffset.option);\n\n\t\tgl_brush_polygonoffset.modified = false;\n\t}\n\tif (state->polygonMode != current->polygonMode) {\n\t\tglPolygonMode(GL_FRONT_AND_BACK, glPolygonModeValues[current->polygonMode = state->polygonMode]);\n\n\t\tR_TraceLogAPICall(\"glPolygonMode(%s)\", txtPolygonModeValues[state->polygonMode]);\n\t}\n\tif (state->clearColor[0] != current->clearColor[0] || state->clearColor[1] != current->clearColor[1] || state->clearColor[2] != current->clearColor[2] || state->clearColor[3] != current->clearColor[3]) {\n\t\tglClearColor(\n\t\t\tcurrent->clearColor[0] = state->clearColor[0],\n\t\t\tcurrent->clearColor[1] = state->clearColor[1],\n\t\t\tcurrent->clearColor[2] = state->clearColor[2],\n\t\t\tcurrent->clearColor[3] = state->clearColor[3]\n\t\t);\n\t\tR_TraceLogAPICall(\"glClearColor(...)\");\n\t}\n\tGL_ApplySimpleToggle(state, current, blendingEnabled, GL_BLEND);\n\tif (state->colorMask[0] != current->colorMask[0] || state->colorMask[1] != current->colorMask[1] || state->colorMask[2] != current->colorMask[2] || state->colorMask[3] != current->colorMask[3]) {\n\t\tglColorMask(\n\t\t\t(current->colorMask[0] = state->colorMask[0]) ? GL_TRUE : GL_FALSE,\n\t\t\t(current->colorMask[1] = state->colorMask[1]) ? GL_TRUE : GL_FALSE,\n\t\t\t(current->colorMask[2] = state->colorMask[2]) ? GL_TRUE : GL_FALSE,\n\t\t\t(current->colorMask[3] = state->colorMask[3]) ? GL_TRUE : GL_FALSE\n\t\t);\n\t\tR_TraceLogAPICall(\"glColorMask(%s,%s,%s,%s)\", state->colorMask[0] ? \"on\" : \"off\", state->colorMask[1] ? \"on\" : \"off\", state->colorMask[2] ? \"on\" : \"off\", state->colorMask[3] ? \"on\" : \"off\");\n\t}\n\tif (buffers.supported && (state->vao_id != currentVAO || current->glc_vao_force_rebind)) {\n\t\tR_BindVertexArray(state->vao_id);\n\t}\n\n#ifdef WITH_RENDERING_TRACE\n\tR_TraceDebugState();\n#endif\n\tR_TraceLeaveRegion(true);\n}\n\n// vid_common_gl.c\n// gl_texture.c\nGLuint GL_TextureNameFromReference(texture_ref ref);\nGLenum GL_TextureTargetFromReference(texture_ref ref);\n\nvoid GL_BindTextureToTarget(GLenum textureUnit, GLenum targetType, GLuint name)\n{\n\tGL_SelectTexture(textureUnit);\n\tGL_BindTexture(targetType, name, true);\n}\n\nr_texture_type_id GL_TargetTypeToTextureType(GLuint targetType)\n{\n\tif (targetType == GL_TEXTURE_2D_ARRAY) {\n\t\treturn texture_type_2d_array;\n\t}\n\telse if (targetType == GL_TEXTURE_2D) {\n\t\treturn texture_type_2d;\n\t}\n\telse if (targetType == GL_TEXTURE_CUBE_MAP) {\n\t\treturn texture_type_cubemap;\n\t}\n\telse if (targetType == GL_TEXTURE_2D_MULTISAMPLE) {\n\t\treturn texture_type_2d_multisampled;\n\t}\n\telse {\n\t\tSys_Error(\"GL_TargetTypeToTextureType(%x) - unsupported\", targetType);\n\t\treturn 0;\n\t}\n}\n\nqbool GL_IsTextureBound(GLuint unit, texture_ref reference)\n{\n\tint unit_num = unit - GL_TEXTURE0;\n\tGLuint texture = GL_TextureNameFromReference(reference);\n\tGLenum targetType = GL_TextureTargetFromReference(reference);\n\n\tif (unit_num >= 0 && unit_num < sizeof(bound_texture_state) / sizeof(bound_texture_state[0])) {\n\t\treturn bound_texture_state[GL_TargetTypeToTextureType(targetType)][unit_num] == texture;\n\t}\n\treturn false;\n}\n\nstatic qbool GL_BindTextureUnitImpl(GLuint unit, texture_ref reference, qbool always_select_unit)\n{\n\tint unit_num = unit - GL_TEXTURE0;\n\tGLuint texture = GL_TextureNameFromReference(reference);\n\tGLenum targetType = GL_TextureTargetFromReference(reference);\n\n\tif (unit_num >= 0 && unit_num < sizeof(bound_texture_state) / sizeof(bound_texture_state[0])) {\n\t\tqbool bound = (bound_texture_state[GL_TargetTypeToTextureType(targetType)][unit_num] == texture);\n\t\tif (bound) {\n\t\t\tif (always_select_unit) {\n\t\t\t\tGL_SelectTexture(unit);\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tGL_SelectTexture(unit);\n\tGL_BindTexture(targetType, texture, true);\n\treturn true;\n}\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\nvoid GLM_ApplyRenderingState(r_state_id id)\n{\n\tGL_ApplyRenderingState(id);\n}\n#endif\n\nqbool GL_EnsureTextureUnitBound(int unit, texture_ref reference)\n{\n\treturn GL_BindTextureUnitImpl(GL_TEXTURE0 + unit, reference, false);\n}\n\nqbool GL_EnsureTextureUnitBoundAndSelect(int unit, texture_ref reference)\n{\n\treturn GL_BindTextureUnitImpl(GL_TEXTURE0 + unit, reference, true);\n}\n\nvoid GL_BindTextureUnit(GLuint unit, texture_ref reference)\n{\n\tGL_BindTextureUnitImpl(unit, reference, true);\n}\n\nvoid R_Viewport(int x, int y, int width, int height)\n{\n\trendering_state_t* state = &opengl.rendering_state;\n\n\tif (x != state->currentViewportX || y != state->currentViewportY || width != state->currentViewportWidth || height != state->currentViewportHeight) {\n\t\trenderer.Viewport(x, y, width, height);\n\n\t\tstate->currentViewportX = x;\n\t\tstate->currentViewportY = y;\n\t\tstate->currentViewportWidth = width;\n\t\tstate->currentViewportHeight = height;\n\t}\n}\n\nvoid R_GetViewport(int* view)\n{\n\trendering_state_t* state = &opengl.rendering_state;\n\n\tview[0] = state->currentViewportX;\n\tview[1] = state->currentViewportY;\n\tview[2] = state->currentViewportWidth;\n\tview[3] = state->currentViewportHeight;\n}\n\nvoid R_SetFullScreenViewport(int x, int y, int width, int height)\n{\n\trendering_state_t* state = &opengl.rendering_state;\n\n\tstate->fullScreenViewportX = x;\n\tstate->fullScreenViewportY = y;\n\tstate->fullScreenViewportWidth = width;\n\tstate->fullScreenViewportHeight = height;\n}\n\nvoid R_GetFullScreenViewport(int* viewport)\n{\n\trendering_state_t* state = &opengl.rendering_state;\n\n\tviewport[0] = state->fullScreenViewportX;\n\tviewport[1] = state->fullScreenViewportY;\n\tviewport[2] = state->fullScreenViewportWidth;\n\tviewport[3] = state->fullScreenViewportHeight;\n}\n\nvoid GL_InitialiseState(void)\n{\n\tR_InitRenderingState(r_state_default_opengl, false, \"opengl\", vao_none);\n\topengl.rendering_state = states[r_state_default_opengl];\n\tR_InitialiseStates();\n\n\tR_SetIdentityMatrix(R_ProjectionMatrix());\n\tR_SetIdentityMatrix(R_ModelviewMatrix());\n\n\tmemset(bound_texture_state, 0, sizeof(bound_texture_state));\n\tmemset(bound_images, 0, sizeof(bound_images));\n\n\tif (buffers.supported) {\n\t\tbuffers.InitialiseState();\n\t}\n\tR_ProgramInitialiseState();\n}\n\n// These functions taken from gl_texture.c\nstatic void GL_BindTexture(GLenum targetType, GLuint texnum, qbool warning)\n{\n\tr_texture_type_id texture_type = GL_TargetTypeToTextureType(targetType);\n\n\tassert(targetType);\n\tassert(texnum);\n\n\tif (bound_texture_state[texture_type][currentTextureUnit - GL_TEXTURE0] == texnum) {\n\t\treturn;\n\t}\n\n\tR_TraceLogAPICall(\"glBindTexture(unit=GL_TEXTURE%d, target=%s, texnum=%u[%s])\", currentTextureUnit - GL_TEXTURE0, gl_friendlyTextureTargetNames[texture_type], texnum, GL_TextureIdentifierByGLReference(texnum));\n\n\tGL_BuiltinProcedure(glBindTexture, \"target=%u, texnum=%u\", targetType, texnum);\n\tbound_texture_state[texture_type][currentTextureUnit - GL_TEXTURE0] = texnum;\n\n\t++frameStats.texture_binds;\n}\n\nvoid GL_SelectTexture(GLenum textureUnit)\n{\n\tif (textureUnit == currentTextureUnit) {\n\t\treturn;\n\t}\n\n\tR_TraceLogAPICall(\"glActiveTexture(GL_TEXTURE%d)\", textureUnit - GL_TEXTURE0);\n\tif (GL_Available(glActiveTexture)) {\n\t\tGL_Procedure(glActiveTexture, textureUnit);\n\t}\n\n\tcurrentTextureUnit = textureUnit;\n}\n\nvoid GL_TextureInitialiseState(void)\n{\n\t// Multi texture.\n\tcurrentTextureUnit = GL_TEXTURE0;\n\n\tmemset(bound_images, 0, sizeof(bound_images));\n\tmemset(bound_texture_state, 0, sizeof(bound_texture_state));\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t{\n\t\tint i;\n\t\tfor (i = 0; i < sizeof(opengl.rendering_state.textureUnits) / sizeof(opengl.rendering_state.textureUnits[0]); ++i) {\n\t\t\topengl.rendering_state.textureUnits[i].enabled = false;\n\t\t\topengl.rendering_state.textureUnits[i].mode = GL_MODULATE;\n\t\t}\n\t}\n#endif\n}\n\nvoid GL_InvalidateTextureReferences(GLuint texture)\n{\n\tint i, j;\n\n\t// glDeleteTextures(texture) has been called - same reference might be re-used in future\n\t// If a texture that is currently bound is deleted, the binding reverts to 0 (the default texture)\n\tfor (i = 0; i < sizeof(bound_texture_state) / sizeof(bound_texture_state[0]); ++i) {\n\t\tfor (j = 0; j < sizeof(bound_texture_state[0]) / sizeof(bound_texture_state[0][0]); ++j) {\n\t\t\tif (bound_texture_state[i][j] == texture) {\n\t\t\t\tbound_texture_state[i][j] = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (i = 0; i < sizeof(bound_images) / sizeof(bound_images[0]); ++i) {\n\t\tif (bound_images[i].texture == texture) {\n\t\t\tbound_images[i].texture = 0;\n\t\t}\n\t}\n}\n\nvoid GL_TextureUnitMultiBind(int first, int count, texture_ref* textures)\n{\n\tGLuint glTextures[MAX_LOGGED_TEXTURE_UNITS] = { 0 };\n\tGLuint postbind_texture_state[texture_type_count][MAX_LOGGED_TEXTURE_UNITS];\n\tqbool already_bound[MAX_LOGGED_TEXTURE_UNITS] = { false };\n\t//r_texture_type_id types[MAX_LOGGED_TEXTURE_UNITS] = { 0 };\n\tqbool multi = false;\n\tint i, j, first_to_change = -1;\n\n\tif (first + count > MAX_LOGGED_TEXTURE_UNITS) {\n\t\tSys_Error(\"GL_TextureUnitMultiBind(first=%d, count=%d)... not supported\", first, count);\n\t\treturn;\n\t}\n\n\t// assume no change\n\tmemcpy(postbind_texture_state, bound_texture_state, sizeof(postbind_texture_state));\n\n\t// find out which texture units we need to change\n\tfor (i = 0; i < count; ++i) {\n\t\tint unit = first + i;\n\n\t\tglTextures[i] = GL_TextureNameFromReference(textures[i]);\n\t\talready_bound[i] = true;\n\n\t\tif (glTextures[i] == 0) {\n\t\t\t// unbind from all targets...\n\t\t\tfor (j = 0; j < sizeof(postbind_texture_state) / sizeof(postbind_texture_state[0]); ++j) {\n\t\t\t\tif (postbind_texture_state[j][unit]) {\n\t\t\t\t\tpostbind_texture_state[j][unit] = 0;\n\t\t\t\t\tmulti = (first_to_change >= 0);\n\t\t\t\t\tfirst_to_change = (first_to_change < 0 ? i : first_to_change);\n\t\t\t\t\talready_bound[i] = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tGLenum target = GL_TextureTargetFromReference(textures[i]);\n\t\t\tr_texture_type_id type = GL_TargetTypeToTextureType(target);\n\n\t\t\talready_bound[i] = (bound_texture_state[type][unit] == glTextures[i]);\n\t\t\tif (!already_bound[i]) {\n\t\t\t\tfirst_to_change = (first_to_change < 0 ? i : first_to_change);\n\t\t\t\tmulti = (first_to_change >= 0);\n\t\t\t\tpostbind_texture_state[type][unit] = glTextures[i];\n\t\t\t}\n\t\t}\n\t}\n\n\tif (multi && GL_Available(glBindTextures)) {\n\t\tGL_Procedure(glBindTextures, first, count, glTextures);\n\n#ifdef WITH_RENDERING_TRACE\n\t\tif (R_TraceLoggingEnabled()) {\n\t\t\tstatic char temp[1024];\n\n\t\t\ttemp[0] = '\\0';\n\t\t\tfor (i = 0; i < count; ++i) {\n\t\t\t\tif (i) {\n\t\t\t\t\tstrlcat(temp, \",\", sizeof(temp));\n\t\t\t\t}\n\t\t\t\tstrlcat(temp, R_TextureIdentifier(textures[i]), sizeof(temp));\n\t\t\t}\n\t\t\tR_TraceLogAPICall(\"glBindTextures(GL_TEXTURE%d, %d[%s])\", first, count, temp);\n\t\t}\n#endif\n\t}\n\telse if (multi) {\n\t\tfor (i = 0; i < count; ++i) {\n\t\t\tif (!already_bound[i]) {\n\t\t\t\trenderer.TextureUnitBind(first + i, textures[i]);\n\t\t\t}\n\t\t}\n\t}\n\telse if (first_to_change >= 0) {\n\t\trenderer.TextureUnitBind(first + first_to_change, textures[first_to_change]);\n\t}\n\tmemcpy(bound_texture_state, postbind_texture_state, sizeof(bound_texture_state));\n}\n\nvoid GL_BindImageTexture(GLuint unit, texture_ref texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format)\n{\n\tGLuint glRef = 0;\n\n\tif (R_TextureReferenceIsValid(texture)) {\n\t\tglRef = GL_TextureNameFromReference(texture);\n\n\t\tif (unit < MAX_LOGGED_IMAGE_UNITS) {\n\t\t\tif (bound_images[unit].texture == glRef && bound_images[unit].level == level && bound_images[unit].layered == layered && bound_images[unit].layer == layer && bound_images[unit].access == access && bound_images[unit].format == format) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tbound_images[unit].texture = glRef;\n\t\t\tbound_images[unit].level = level;\n\t\t\tbound_images[unit].layered = layered;\n\t\t\tbound_images[unit].layer = layer;\n\t\t\tbound_images[unit].access = access;\n\t\t\tbound_images[unit].format = format;\n\t\t}\n\t}\n\telse {\n\t\tif (unit < MAX_LOGGED_IMAGE_UNITS) {\n\t\t\tmemset(&bound_images[unit], 0, sizeof(bound_images[unit]));\n\t\t}\n\t}\n\n\tGL_Procedure(glBindImageTexture, unit, glRef, level, layered, layer, access, format);\n}\n\n#ifdef WITH_RENDERING_TRACE\nvoid R_TracePrintState(FILE* debug_frame_out, int debug_frame_depth)\n{\n// meag: disabled just for clarity, only enable if you fear the states aren't being reset correctly\n//       less useful now we have all the states enumerated, the state manager should be setting everything\n#if 0\n\tdebug_frame_depth += 7;\n\n\tif (debug_frame_out) {\n\t\trendering_state_t* current = &opengl.rendering_state;\n\n\t\tfprintf(debug_frame_out, \"%.*s <state-dump>\\n\", debug_frame_depth, \"                                                          \");\n\t\tfprintf(debug_frame_out, \"%.*s   Z-Buffer: %s, func %s range %f=>%f [mask %s]\\n\", debug_frame_depth, \"                                                          \", current->depth.test_enabled ? \"on\" : \"off\", txtDepthFunctions[current->depth.func], current->depth.nearRange, current->depth.farRange, current->depth.mask_enabled ? \"on\" : \"off\");\n\t\tfprintf(debug_frame_out, \"%.*s   Cull-face: %s, mode %s\\n\", debug_frame_depth, \"                                                          \", current->cullface.enabled ? \"enabled\" : \"disabled\", txtCullFaceValues[current->cullface.mode]);\n\t\tfprintf(debug_frame_out, \"%.*s   Blending: %s, func %s\\n\", debug_frame_depth, \"                                                          \", current->blendingEnabled ? \"enabled\" : \"disabled\", txtBlendFuncNames[current->blendFunc]);\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t\tfprintf(debug_frame_out, \"%.*s   AlphaTest: %s\", debug_frame_depth, \"                                                          \", current->alphaTesting.enabled ? \"on\" : \"off\");\n\t\tif (current->alphaTesting.enabled) {\n\t\t\tfprintf(debug_frame_out, \"func %s(%f)\\n\", txtAlphaTestModeValues[current->alphaTesting.func], current->alphaTesting.value);\n\t\t}\n\t\telse {\n\t\t\tfprintf(debug_frame_out, \"\\n\");\n\t\t}\n\t\tfprintf(debug_frame_out, \"%.*s   Texture units: [\", debug_frame_depth, \"                                                          \");\n\t\t{\n\t\t\tint i;\n\n\t\t\tfor (i = 0; i < sizeof(current->textureUnits) / sizeof(current->textureUnits[0]); ++i) {\n\t\t\t\tfprintf(debug_frame_out, \"%s\", i ? \",\" : \"\");\n\t\t\t\tif (current->textureUnits[i].enabled && bound_textures[i]) {\n\t\t\t\t\tfprintf(debug_frame_out, \"%s(%s)\", txtTextureEnvModeValues[current->textureUnits[i].mode], GL_TextureIdentifierByGLReference(bound_textures[i]));\n\t\t\t\t}\n\t\t\t\telse if (current->textureUnits[i].enabled) {\n\t\t\t\t\tfprintf(debug_frame_out, \"%s(<null>)\", txtTextureEnvModeValues[current->textureUnits[i].mode]);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfprintf(debug_frame_out, \"<off>\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfprintf(debug_frame_out, \"]\\n\");\n\t\t{\n\t\t\tfloat matrix[16];\n\t\t\tint i;\n\n\t\t\tR_GetModelviewMatrix(matrix);\n\t\t\tfprintf(debug_frame_out, \"%.*s   Modelview matrix:\\n\", debug_frame_depth, \"                                                          \");\n\t\t\tfor (i = 0; i < 16; i += 4) {\n\t\t\t\tfprintf(debug_frame_out, \"%.*s       [%8.5f %8.5f %8.5f %8.5f]\\n\", debug_frame_depth, \"                                                          \", matrix[i + 0], matrix[i + 1], matrix[i + 2], matrix[i + 3]);\n\t\t\t}\n\n\t\t\tR_GetProjectionMatrix(matrix);\n\t\t\tfprintf(debug_frame_out, \"%.*s   Projection matrix:\\n\", debug_frame_depth, \"                                                          \");\n\t\t\tfor (i = 0; i < 16; i += 4) {\n\t\t\t\tfprintf(debug_frame_out, \"%.*s       [%8.5f %8.5f %8.5f %8.5f]\\n\", debug_frame_depth, \"                                                          \", matrix[i + 0], matrix[i + 1], matrix[i + 2], matrix[i + 3]);\n\t\t\t}\n\t\t}\n#endif\n\t\tfprintf(debug_frame_out, \"%.*s   glPolygonMode: %s\\n\", debug_frame_depth, \"                                                          \", txtPolygonModeValues[current->polygonMode]);\n\t\tfprintf(debug_frame_out, \"%.*s   vao: %s\\n\", debug_frame_depth, \"                                                          \", vaoNames[currentVAO]);\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t\tif (R_VAOBound() && R_UseImmediateOpenGL()) {\n\t\t\tGLC_PrintVAOState(debug_frame_out, debug_frame_depth, currentVAO);\n\t\t}\n#endif\n\t\tif (buffers.PrintState) {\n\t\t\tbuffers.PrintState(debug_frame_out, debug_frame_depth);\n\t\t}\n\t\tfprintf(debug_frame_out, \"%.*s </state-dump>\\n\", debug_frame_depth, \"                                                          \");\n\t}\n#endif\n}\n#endif\n\nvoid GL_LoadStateFunctions(void)\n{\n\tglConfig.supported_features &= ~(R_SUPPORT_MULTITEXTURING | R_SUPPORT_IMAGE_PROCESSING);\n\n\tGL_LoadOptionalFunction(glActiveTexture);\n\tglConfig.supported_features |= (GL_Available(glActiveTexture) ? R_SUPPORT_MULTITEXTURING : 0);\n\n\tif (GL_VersionAtLeast(4, 2) || SDL_GL_ExtensionSupported(\"GL_ARB_shader_image_load_store\")) {\n\t\tGL_LoadOptionalFunction(glBindImageTexture);\n\t\tglConfig.supported_features |= (GL_Available(glBindImageTexture) ? R_SUPPORT_IMAGE_PROCESSING : 0);\n\t} else {\n\t\tGL_InvalidateFunction(glBindImageTexture);\n\t}\n\n\t// 4.4 - binds textures to consecutive texture units\n\tGL_InvalidateFunction(glBindTextures);\n\tif ((GL_VersionAtLeast(4, 4) || SDL_GL_ExtensionSupported(\"GL_ARB_multi_bind\")) && !COM_CheckParm(cmdline_param_client_nomultibind)) {\n\t\tGL_LoadOptionalFunction(glBindTextures);\n\n\t\t// Invalidate if on particular drivers (see github bug #416)\n\t\tif (GL_Available(glBindTextures) && glConfig.amd_issues) {\n\t\t\tGL_InvalidateFunction(glBindTextures);\n\t\t\tglConfig.broken_features |= R_BROKEN_GLBINDTEXTURES;\n\t\t}\n\t}\n}\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\nvoid GLC_ClientActiveTexture(GLenum texture_unit)\n{\n\tif (GL_Available(glClientActiveTexture)) {\n\t\tGL_Procedure(glClientActiveTexture, texture_unit);\n\t}\n\telse {\n\t\tassert(texture_unit == GL_TEXTURE0);\n\t}\n}\n#endif // RENDERER_OPTION_CLASSIC_OPENGL\n\nvoid R_ApplyRenderingState(r_state_id state)\n{\n\tassert(state != r_state_null);\n\tif (!state) {\n\t\tCon_Printf(\"ERROR: NULL rendering state\\n\");\n\t\treturn;\n\t}\n\tassert(states[state].initialized);\n\n\trenderer.ApplyRenderingState(state);\n}\n\nvoid R_CustomColor(float r, float g, float b, float a)\n{\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tif (!opengl.rendering_state.colorValid || opengl.rendering_state.color[0] != r || opengl.rendering_state.color[1] != g || opengl.rendering_state.color[2] != b || opengl.rendering_state.color[3] != a) {\n\t\t\tglColor4f(\n\t\t\t\topengl.rendering_state.color[0] = r,\n\t\t\t\topengl.rendering_state.color[1] = g,\n\t\t\t\topengl.rendering_state.color[2] = b,\n\t\t\t\topengl.rendering_state.color[3] = a\n\t\t\t);\n\t\t\topengl.rendering_state.colorValid = true;\n\t\t}\n\t}\n#endif\n}\n\nvoid R_CustomLineWidth(float width)\n{\n\tif (width != opengl.rendering_state.line.width) {\n\t\tif (R_UseImmediateOpenGL() || R_UseModernOpenGL()) {\n\t\t\tglLineWidth(opengl.rendering_state.line.width = width);\n\t\t\tR_TraceLogAPICall(\"glLineWidth(%f)\", width);\n\t\t}\n\t\telse if (R_UseVulkan()) {\n\t\t\t// Requires VK_DYNAMIC_STATE_LINE_WIDTH\n\t\t\t// Requires wide lines feature (if not, must be 1)\n\n\t\t\t// vkCmdSetLineWidth(commandBuffer, width)\n\t\t\topengl.rendering_state.line.width = width;\n\t\t}\n\t}\n}\n\nvoid R_CustomPolygonOffset(r_polygonoffset_t mode)\n{\n\trendering_state_t* current = &opengl.rendering_state;\n\n\tif (mode != current->polygonOffset.option) {\n\t\textern cvar_t gl_brush_polygonoffset, gl_brush_polygonoffset_factor;\n\n\t\tfloat factor = (mode == r_polygonoffset_standard ? bound(-1, gl_brush_polygonoffset_factor.value, 1) : 1);\n\t\tfloat units = (mode == r_polygonoffset_standard ? bound(0, gl_brush_polygonoffset.value, 2) : 1);\n\t\tqbool enabled = (mode == r_polygonoffset_standard || mode == r_polygonoffset_outlines) && units != 0;\n\n\t\tif (enabled) {\n\t\t\tif (!current->polygonOffset.fillEnabled) {\n\t\t\t\tglEnable(GL_POLYGON_OFFSET_FILL);\n\t\t\t\tR_TraceLogAPICall(\"glEnable(GL_POLYGON_OFFSET_FILL)\");\n\t\t\t\tcurrent->polygonOffset.fillEnabled = true;\n\t\t\t}\n\t\t\tif (!current->polygonOffset.lineEnabled) {\n\t\t\t\tglEnable(GL_POLYGON_OFFSET_LINE);\n\t\t\t\tR_TraceLogAPICall(\"glEnable(GL_POLYGON_OFFSET_LINE)\");\n\t\t\t\tcurrent->polygonOffset.lineEnabled = true;\n\t\t\t}\n\n\t\t\tif (current->polygonOffset.factor != factor || current->polygonOffset.units != units) {\n\t\t\t\tglPolygonOffset(factor, units);\n\t\t\t\tR_TraceLogAPICall(\"glPolygonOffset(factor %f, units %f)\", factor, units);\n\n\t\t\t\tcurrent->polygonOffset.factor = factor;\n\t\t\t\tcurrent->polygonOffset.units = units;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (current->polygonOffset.fillEnabled) {\n\t\t\t\tglDisable(GL_POLYGON_OFFSET_FILL);\n\t\t\t\tR_TraceLogAPICall(\"glDisable(GL_POLYGON_OFFSET_FILL)\");\n\t\t\t\tcurrent->polygonOffset.fillEnabled = false;\n\t\t\t}\n\t\t\tif (current->polygonOffset.lineEnabled) {\n\t\t\t\tglDisable(GL_POLYGON_OFFSET_LINE);\n\t\t\t\tR_TraceLogAPICall(\"glDisable(GL_POLYGON_OFFSET_LINE)\");\n\t\t\t\tcurrent->polygonOffset.lineEnabled = false;\n\t\t\t}\n\t\t}\n\t\tcurrent->polygonOffset.option = mode;\n\t}\n}\n\nvoid R_CustomColor4ubv(const byte* color)\n{\n\tfloat r = color[0] / 255.0f;\n\tfloat g = color[1] / 255.0f;\n\tfloat b = color[2] / 255.0f;\n\tfloat a = color[3] / 255.0f;\n\n\tR_CustomColor(r, g, b, a);\n}\n\nvoid R_EnableScissorTest(int x, int y, int width, int height)\n{\n\tif (R_UseImmediateOpenGL() || R_UseModernOpenGL()) {\n\t\tglEnable(GL_SCISSOR_TEST);\n\t\tglScissor(x, y, width, height);\n\t}\n}\n\nvoid R_DisableScissorTest(void)\n{\n\tif (R_UseImmediateOpenGL() || R_UseModernOpenGL()) {\n\t\tglDisable(GL_SCISSOR_TEST);\n\t}\n}\n\nvoid R_ClearColor(float r, float g, float b, float a)\n{\n\tif (R_UseImmediateOpenGL()) {\n\t\tglClearColor(\n\t\t\topengl.rendering_state.clearColor[0] = r,\n\t\t\topengl.rendering_state.clearColor[1] = g,\n\t\t\topengl.rendering_state.clearColor[2] = b,\n\t\t\topengl.rendering_state.clearColor[3] = a\n\t\t);\n\t}\n\telse if (R_UseModernOpenGL()) {\n\t\tglClearColor(\n\t\t\topengl.rendering_state.clearColor[0] = r,\n\t\t\topengl.rendering_state.clearColor[1] = g,\n\t\t\topengl.rendering_state.clearColor[2] = b,\n\t\t\topengl.rendering_state.clearColor[3] = a\n\t\t);\n\t}\n}\n\nrendering_state_t* R_CopyRenderingState(r_state_id dest_id, r_state_id source_id, const char* name)\n{\n\trendering_state_t* state = &states[dest_id];\n\tconst rendering_state_t* src = &states[source_id];\n\n\tmemcpy(state, src, sizeof(*state));\n\tstrlcpy(state->name, name, sizeof(state->name));\n\treturn state;\n}\n\nvoid R_InitialiseVAOState(void)\n{\n\tcurrentVAO = vao_none;\n}\n\nqbool R_VertexArrayCreated(r_vao_id vao)\n{\n\treturn renderer.VertexArrayCreated(vao);\n}\n\nvoid R_BindVertexArray(r_vao_id vao)\n{\n\tif (currentVAO != vao || opengl.rendering_state.glc_vao_force_rebind) {\n\t\tR_TraceEnterRegion(va(\"R_BindVertexArray(%s)\", vaoNames[vao]), true);\n\n\t\tassert(vao == vao_none || R_VertexArrayCreated(vao));\n\n\t\trenderer.BindVertexArray(vao);\n\n\t\tcurrentVAO = vao;\n\t\topengl.rendering_state.glc_vao_force_rebind = false;\n\t\tR_TraceLeaveRegion(true);\n\t}\n}\n\nvoid R_GenVertexArray(r_vao_id vao)\n{\n\trenderer.GenVertexArray(vao, vaoNames[vao]);\n}\n\nqbool R_VAOBound(void)\n{\n\treturn currentVAO != vao_none;\n}\n\nvoid R_BindVertexArrayElementBuffer(r_buffer_id ref)\n{\n\tif (currentVAO != vao_none) {\n\t\trenderer.BindVertexArrayElementBuffer(currentVAO, ref);\n\t}\n}\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\nvoid R_GLC_VertexPointer(r_buffer_id buf, qbool enabled, int size, GLenum type, int stride, void* pointer_or_offset)\n{\n\tif (enabled) {\n\t\tglc_vertex_array_element_t* array = &opengl.rendering_state.vertex_array;\n\n\t\tif (R_BufferReferenceIsValid(buf)) {\n\t\t\tbuffers.Bind(buf);\n\t\t}\n\t\telse {\n\t\t\tbuffers.UnBind(buffertype_vertex);\n\t\t}\n\n\t\tif (!R_BufferReferencesEqual(buf, array->buf) || size != array->size || type != array->type || stride != array->stride || pointer_or_offset != array->pointer_or_offset) {\n\t\t\tglVertexPointer(array->size = size, array->type = type, array->stride = stride, array->pointer_or_offset = pointer_or_offset);\n\t\t\tarray->buf = buf;\n\t\t\tR_TraceLogAPICall(\"glVertexPointer(size %d, type %s, stride %d, ptr %p)\", size, type == GL_FLOAT ? \"FLOAT\" : type == GL_UNSIGNED_BYTE ? \"UBYTE\" : \"???\", stride, pointer_or_offset);\n\t\t}\n\t\tif (!opengl.rendering_state.vertex_array.enabled) {\n\t\t\tglEnableClientState(GL_VERTEX_ARRAY);\n\t\t\tR_TraceLogAPICall(\"glEnableClientState(GL_VERTEX_ARRAY)\");\n\t\t\topengl.rendering_state.vertex_array.enabled = true;\n\t\t}\n\t}\n\telse if (!enabled && opengl.rendering_state.vertex_array.enabled) {\n\t\tglDisableClientState(GL_VERTEX_ARRAY);\n\t\tR_TraceLogAPICall(\"glDisableClientState(GL_VERTEX_ARRAY)\");\n\t\topengl.rendering_state.vertex_array.enabled = false;\n\t}\n}\n\nvoid R_GLC_ColorPointer(r_buffer_id buf, qbool enabled, int size, GLenum type, int stride, void* pointer_or_offset)\n{\n\tif (enabled) {\n\t\tglc_vertex_array_element_t* array = &opengl.rendering_state.color_array;\n\n\t\tif (R_BufferReferenceIsValid(buf)) {\n\t\t\tbuffers.Bind(buf);\n\t\t}\n\t\telse {\n\t\t\tbuffers.UnBind(buffertype_vertex);\n\t\t}\n\n\t\tif (!R_BufferReferencesEqual(buf, array->buf) || size != array->size || type != array->type || stride != array->stride || pointer_or_offset != array->pointer_or_offset) {\n\t\t\tglColorPointer(array->size = size, array->type = type, array->stride = stride, array->pointer_or_offset = pointer_or_offset);\n\t\t\tR_TraceLogAPICall(\"glColorPointer(size %d, type %s, stride %d, ptr %p)\", size, type == GL_FLOAT ? \"FLOAT\" : type == GL_UNSIGNED_BYTE ? \"UBYTE\" : \"???\", stride, pointer_or_offset);\n\t\t}\n\t\tif (!opengl.rendering_state.color_array.enabled) {\n\t\t\tglEnableClientState(GL_COLOR_ARRAY);\n\t\t\tR_TraceLogAPICall(\"glEnableClientState(GL_COLOR_ARRAY)\");\n\t\t\topengl.rendering_state.colorValid = false;\n\t\t\topengl.rendering_state.color_array.enabled = true;\n\t\t}\n\t}\n\telse if (!enabled && opengl.rendering_state.color_array.enabled) {\n\t\tglDisableClientState(GL_COLOR_ARRAY);\n\t\tR_TraceLogAPICall(\"glDisableClientState(GL_COLOR_ARRAY)\");\n\t\topengl.rendering_state.colorValid = false;\n\t\topengl.rendering_state.color_array.enabled = false;\n\t}\n}\n\nvoid R_GLC_NormalPointer(r_buffer_id buf, qbool enabled, int size, GLenum type, int stride, void* pointer_or_offset)\n{\n\tif (enabled) {\n\t\tglc_vertex_array_element_t* array = &opengl.rendering_state.normal_array;\n\n\t\tif (R_BufferReferenceIsValid(buf)) {\n\t\t\tbuffers.Bind(buf);\n\t\t}\n\t\telse {\n\t\t\tbuffers.UnBind(buffertype_vertex);\n\t\t}\n\n\t\tif (!R_BufferReferencesEqual(buf, array->buf) || size != array->size || type != array->type || stride != array->stride || pointer_or_offset != array->pointer_or_offset) {\n\t\t\tglNormalPointer(array->type = type, array->stride = stride, array->pointer_or_offset = pointer_or_offset);\n\t\t\tarray->size = size;\n\t\t\tR_TraceLogAPICall(\"glColorPointer(size %d, type %s, stride %d, ptr %p)\", size, type == GL_FLOAT ? \"FLOAT\" : type == GL_UNSIGNED_BYTE ? \"UBYTE\" : \"???\", stride, pointer_or_offset);\n\t\t}\n\t\tif (!opengl.rendering_state.normal_array.enabled) {\n\t\t\tglEnableClientState(GL_NORMAL_ARRAY);\n\t\t\tR_TraceLogAPICall(\"glEnableClientState(GL_NORMAL_ARRAY)\");\n\t\t\topengl.rendering_state.normal_array.enabled = true;\n\t\t}\n\t}\n\telse if (!enabled && opengl.rendering_state.normal_array.enabled) {\n\t\tglDisableClientState(GL_NORMAL_ARRAY);\n\t\tR_TraceLogAPICall(\"glDisableClientState(GL_NORMAL_ARRAY)\");\n\t\topengl.rendering_state.normal_array.enabled = false;\n\t}\n}\n\nvoid R_GLC_DisableColorPointer(void)\n{\n\tif (opengl.rendering_state.color_array.enabled) {\n\t\tglDisableClientState(GL_COLOR_ARRAY);\n\t\tR_TraceLogAPICall(\"glDisableClientState(GL_COLOR_ARRAY)\");\n\t\topengl.rendering_state.colorValid = false;\n\t\topengl.rendering_state.color_array.enabled = false;\n\t\topengl.rendering_state.glc_vao_force_rebind = true;\n\t}\n}\n\nvoid R_GLC_DisableTexturePointer(int unit)\n{\n\tif (unit < 0 || unit >= sizeof(opengl.rendering_state.textureUnits) / sizeof(opengl.rendering_state.textureUnits[0])) {\n\t\treturn;\n\t}\n\n\tif (opengl.rendering_state.textureUnits[unit].va.enabled) {\n\t\tGLC_ClientActiveTexture(GL_TEXTURE0 + unit);\n\t\tglDisableClientState(GL_TEXTURE_COORD_ARRAY);\n\t\tR_TraceLogAPICall(\"glDisableClientState[unit %d](GL_TEXTURE_COORD_ARRAY)\", unit);\n\t\topengl.rendering_state.textureUnits[unit].va.enabled = false;\n\t\topengl.rendering_state.glc_vao_force_rebind = true;\n\t}\n}\n\nvoid R_GLC_TexturePointer(r_buffer_id buf, int unit, qbool enabled, int size, GLenum type, int stride, void* pointer_or_offset)\n{\n\tif (unit < 0 || unit >= sizeof(opengl.rendering_state.textureUnits) / sizeof(opengl.rendering_state.textureUnits[0])) {\n\t\treturn;\n\t}\n\n\tif (enabled) {\n\t\tglc_vertex_array_element_t* array = &opengl.rendering_state.textureUnits[unit].va;\n\t\tqbool pointer_call_needed = (!R_BufferReferencesEqual(buf, array->buf) || size != array->size || type != array->type || stride != array->stride || pointer_or_offset != array->pointer_or_offset);\n\n\t\tif (R_BufferReferenceIsValid(buf)) {\n\t\t\tbuffers.Bind(buf);\n\t\t}\n\t\telse {\n\t\t\tbuffers.UnBind(buffertype_vertex);\n\t\t}\n\n\t\tif (!array->enabled || pointer_call_needed) {\n\t\t\tGLC_ClientActiveTexture(GL_TEXTURE0 + unit);\n\t\t}\n\t\tif (pointer_call_needed) {\n\t\t\tglTexCoordPointer(array->size = size, array->type = type, array->stride = stride, array->pointer_or_offset = pointer_or_offset);\n\t\t\tarray->buf = buf;\n\t\t\tR_TraceLogAPICall(\"glTexCoordPointer[unit %d](size %d, type %s, stride %d, ptr %p)\", unit, size, type == GL_FLOAT ? \"FLOAT\" : type == GL_UNSIGNED_BYTE ? \"UBYTE\" : \"???\", stride, pointer_or_offset);\n\t\t}\n\t\tif (!array->enabled) {\n\t\t\tglEnableClientState(GL_TEXTURE_COORD_ARRAY);\n\t\t\tR_TraceLogAPICall(\"glEnableClientState[unit %d](GL_TEXTURE_COORD_ARRAY)\", unit);\n\t\t\tarray->enabled = true;\n\t\t}\n\t}\n\telse if (!enabled) {\n\t\tR_GLC_DisableTexturePointer(unit);\n\t}\n}\n\nvoid GLC_CustomAlphaTesting(qbool enabled)\n{\n\trendering_state_t state;\n\trendering_state_t* current = &opengl.rendering_state;\n\n\tif (enabled) {\n\t\tR_GLC_ConfigureAlphaTesting(&state, true, r_alphatest_func_greater, 0.333f);\n\t}\n\telse {\n\t\tR_GLC_ConfigureAlphaTesting(&state, false, r_alphatest_func_always, 0);\n\t}\n\n\tGLC_ToggleAlphaTesting(&state, current);\n}\n\nstatic void GLC_ToggleAlphaTesting(const rendering_state_t* state, rendering_state_t* current)\n{\n\tGL_ApplySimpleToggle(state, current, alphaTesting.enabled, GL_ALPHA_TEST);\n\tif (current->alphaTesting.enabled && (state->alphaTesting.func != current->alphaTesting.func || state->alphaTesting.value != current->alphaTesting.value)) {\n\t\tglAlphaFunc(\n\t\t\tglAlphaTestModeValues[current->alphaTesting.func = state->alphaTesting.func],\n\t\t\tcurrent->alphaTesting.value = state->alphaTesting.value\n\t\t);\n\t\tR_TraceLogAPICall(\"glAlphaFunc(%s %f)\", txtAlphaTestModeValues[state->alphaTesting.func], state->alphaTesting.value);\n\t}\n}\n\nvoid GLC_ApplyRenderingState(r_state_id id)\n{\n\trendering_state_t* current = &opengl.rendering_state;\n\trendering_state_t* state = &states[id];\n\tint i;\n\n\tGL_ProcessErrors(\"Pre-GLC_ApplyRenderingState()\");\n\tR_TraceEnterRegion(va(\"GLC_ApplyRenderingState(%s)\", state->name), true);\n\n\t// Alpha-testing\n\tGLC_ToggleAlphaTesting(state, current);\n\n\t// Texture units\n\tfor (i = 0; i < sizeof(current->textureUnits) / sizeof(current->textureUnits[0]) && i < glConfig.texture_units; ++i) {\n\t\tif (state->textureUnits[i].enabled != current->textureUnits[i].enabled) {\n\t\t\tif ((current->textureUnits[i].enabled = state->textureUnits[i].enabled)) {\n\t\t\t\tR_TraceLogAPICall(\"Enabling texturing on unit %d\", i);\n\t\t\t\tGL_SelectTexture(GL_TEXTURE0 + i);\n\t\t\t\tglEnable(GL_TEXTURE_2D);\n\t\t\t\tif (state->textureUnits[i].mode != current->textureUnits[i].mode) {\n\t\t\t\t\tR_TraceLogAPICall(\"texture unit mode[%d] = %s\", i, txtTextureEnvModeValues[state->textureUnits[i].mode]);\n\t\t\t\t\tglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, glTextureEnvModeValues[current->textureUnits[i].mode = state->textureUnits[i].mode]);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tR_TraceLogAPICall(\"Disabling texturing on unit %d\", i);\n\t\t\t\tGL_SelectTexture(GL_TEXTURE0 + i);\n\t\t\t\tglDisable(GL_TEXTURE_2D);\n\t\t\t}\n\t\t}\n\t\telse if (current->textureUnits[i].enabled && state->textureUnits[i].mode != current->textureUnits[i].mode) {\n\t\t\tR_TraceLogAPICall(\"texture unit mode[%d] = %s\", i, txtTextureEnvModeValues[state->textureUnits[i].mode]);\n\t\t\tGL_SelectTexture(GL_TEXTURE0 + i);\n\t\t\tglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, glTextureEnvModeValues[current->textureUnits[i].mode = state->textureUnits[i].mode]);\n\t\t}\n\t}\n\tGL_ProcessErrors(\"Post-GLC_ApplyRenderingState(texture-units)\");\n\n\t// Color\n\tif (!current->colorValid || current->color[0] != state->color[0] || current->color[1] != state->color[1] || current->color[2] != state->color[2] || current->color[3] != state->color[3]) {\n\t\tglColor4f(\n\t\t\tcurrent->color[0] = state->color[0],\n\t\t\tcurrent->color[1] = state->color[1],\n\t\t\tcurrent->color[2] = state->color[2],\n\t\t\tcurrent->color[3] = state->color[3]\n\t\t);\n\t\tR_TraceLogAPICall(\"glColor4f(%f %f %f %f)\", state->color[0], state->color[1], state->color[2], state->color[3]);\n\t\tcurrent->colorValid = true;\n\t}\n\n\t// Fog\n\tif (r_refdef2.fog_render) {\n\t\tif (state->fog.mode == r_fogmode_disabled && current->fog.mode != r_fogmode_disabled) {\n\t\t\tcurrent->fog.mode = r_fogmode_disabled;\n\t\t\tglDisable(GL_FOG);\n\t\t\tR_TraceLogAPICall(\"glDisable(GL_FOG)\");\n\t\t}\n\t\telse if (state->fog.mode == r_fogmode_enabled) {\n\t\t\tif (current->fog.mode != r_fogmode_enabled) {\n\t\t\t\tcurrent->fog.mode = r_fogmode_enabled;\n\t\t\t\tglEnable(GL_FOG);\n\t\t\t\tif (r_refdef2.fog_calculation == fogcalc_linear) {\n\t\t\t\t\tif (current->fog.calculation != r_refdef2.fog_calculation) {\n\t\t\t\t\t\tglFogi(GL_FOG_MODE, GL_LINEAR);\n\t\t\t\t\t\tcurrent->fog.calculation = r_refdef2.fog_calculation;\n\t\t\t\t\t}\n\t\t\t\t\tif (current->fog.start != r_refdef2.fog_linear_start) {\n\t\t\t\t\t\tglFogf(GL_FOG_START, r_refdef2.fog_linear_start);\n\t\t\t\t\t\tcurrent->fog.start = r_refdef2.fog_linear_start;\n\t\t\t\t\t}\n\t\t\t\t\tif (current->fog.end != r_refdef2.fog_linear_end) {\n\t\t\t\t\t\tglFogf(GL_FOG_END, r_refdef2.fog_linear_end);\n\t\t\t\t\t\tcurrent->fog.end = r_refdef2.fog_linear_end;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (r_refdef2.fog_calculation == fogcalc_exp) {\n\t\t\t\t\tif (current->fog.calculation != r_refdef2.fog_calculation) {\n\t\t\t\t\t\tglFogi(GL_FOG_MODE, GL_EXP);\n\t\t\t\t\t\tcurrent->fog.calculation = r_refdef2.fog_calculation;\n\t\t\t\t\t}\n\t\t\t\t\tif (current->fog.density != r_refdef2.fog_density) {\n\t\t\t\t\t\tglFogf(GL_FOG_DENSITY, r_refdef2.fog_density);\n\t\t\t\t\t\tcurrent->fog.density = r_refdef2.fog_density;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (r_refdef2.fog_calculation == fogcalc_exp2) {\n\t\t\t\t\tif (current->fog.calculation != r_refdef2.fog_calculation) {\n\t\t\t\t\t\tglFogi(GL_FOG_MODE, GL_EXP2);\n\t\t\t\t\t\tcurrent->fog.calculation = r_refdef2.fog_calculation;\n\t\t\t\t\t}\n\t\t\t\t\tif (current->fog.density != r_refdef2.fog_density) {\n\t\t\t\t\t\tglFogf(GL_FOG_DENSITY, r_refdef2.fog_density);\n\t\t\t\t\t\tcurrent->fog.density = r_refdef2.fog_density;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tR_TraceLogAPICall(\"glEnable(GL_FOG)\");\n\t\t\t}\n\t\t\tif (memcmp(current->fog.color, r_refdef2.fog_color, sizeof(current->fog.color))) {\n\t\t\t\tglFogfv(GL_FOG_COLOR, r_refdef2.fog_color);\n\t\t\t\tmemcpy(current->fog.color, r_refdef2.fog_color, sizeof(current->fog.color));\n\t\t\t\tR_TraceLogAPICall(\"glFogfv(GL_FOG_COLOR, [%d %d %d %d])\", (int)(current->fog.color[0] * 255), (int)(current->fog.color[1] * 255), (int)(current->fog.color[2] * 255), (int)(current->fog.color[3] * 255));\n\t\t\t}\n\t\t}\n\t}\n\telse if (current->fog.mode != r_fogmode_disabled) {\n\t\tcurrent->fog.mode = r_fogmode_disabled;\n\t\tglDisable(GL_FOG);\n\t\tR_TraceLogAPICall(\"glDisable(GL_FOG)\");\n\t}\n\n\tif (state->vao_id) {\n\t\tGLC_EnsureVAOCreated(state->vao_id);\n\t}\n\n\tGL_ProcessErrors(\"Pre-GLC_ApplyRenderingState()\");\n\tGL_ApplyRenderingState(id);\n\tGL_ProcessErrors(\"Post-ApplyRenderingState()\");\n\n\tR_TraceLeaveRegion(true);\n}\n\nvoid R_GLC_TextureUnitSet(rendering_state_t* state, int index, qbool enabled, r_texunit_mode_t mode)\n{\n\tstate->textureUnits[index].enabled = enabled;\n\tstate->textureUnits[index].mode = mode;\n}\n\nvoid R_GLC_ConfigureAlphaTesting(rendering_state_t* state, qbool enabled, r_alphatest_func_t func, float value)\n{\n\tstate->alphaTesting.enabled = enabled;\n\tstate->alphaTesting.func = func;\n\tstate->alphaTesting.value = value;\n}\n\n#endif // RENDERER_OPTION_CLASSIC_OPENGL\n\nvoid R_BufferInvalidateBoundState(r_buffer_id ref)\n{\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tint i;\n\n\tif (R_BufferReferencesEqual(opengl.rendering_state.vertex_array.buf, ref)) {\n\t\topengl.rendering_state.vertex_array.buf = r_buffer_none;\n\t}\n\tif (R_BufferReferencesEqual(opengl.rendering_state.color_array.buf, ref)) {\n\t\topengl.rendering_state.color_array.buf = r_buffer_none;\n\t}\n\tif (R_BufferReferencesEqual(opengl.rendering_state.normal_array.buf, ref)) {\n\t\topengl.rendering_state.normal_array.buf = r_buffer_none;\n\t}\n\tfor (i = 0; i < sizeof(opengl.rendering_state.textureUnits) / sizeof(opengl.rendering_state.textureUnits[0]); ++i) {\n\t\tif (R_BufferReferencesEqual(opengl.rendering_state.textureUnits[i].va.buf, ref)) {\n\t\t\topengl.rendering_state.textureUnits[i].va.buf = r_buffer_none;\n\t\t}\n\t}\n#endif\n}\n\nvoid GL_InvalidateViewport(void)\n{\n\trendering_state_t* state = &opengl.rendering_state;\n\tstate->currentViewportX = 0;\n\tstate->currentViewportY = 0;\n\tstate->currentViewportWidth = 0;\n\tstate->currentViewportHeight = 0;\n}\n\nvoid GL_PackAlignment(int alignment_in_bytes)\n{\n\tif (opengl.rendering_state.pack_alignment != alignment_in_bytes) {\n\t\tglPixelStorei(GL_PACK_ALIGNMENT, alignment_in_bytes);\n\t\topengl.rendering_state.pack_alignment = alignment_in_bytes;\n\t}\n}\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\nvoid GLC_MultiTexCoord2f(GLenum target, float s, float t)\n{\n\tif (GL_Available(glMultiTexCoord2f)) {\n\t\tGL_Procedure(glMultiTexCoord2f, target, s, t);\n\t}\n}\n\nvoid GL_CheckMultiTextureExtensions(void)\n{\n\tif (!COM_CheckParm(cmdline_param_client_nomultitexturing) && (GL_VersionAtLeast(2, 0) || SDL_GL_ExtensionSupported(\"GL_ARB_multitexture\"))) {\n\t\tGL_LoadOptionalFunction(glMultiTexCoord2f);\n\t\tif (!GL_Available(glMultiTexCoord2f)) {\n\t\t\tGL_LoadOptionalFunctionARB(glMultiTexCoord2f);\n\t\t}\n\t\tGL_LoadOptionalFunction(glActiveTexture);\n\t\tif (!GL_Available(glActiveTexture)) {\n\t\t\tGL_LoadOptionalFunctionARB(glActiveTexture);\n\t\t}\n\t\tGL_LoadOptionalFunction(glClientActiveTexture);\n\t\tif (!GL_Available(glMultiTexCoord2f) || !GL_Available(glActiveTexture) || !GL_Available(glClientActiveTexture)) {\n\t\t\treturn;\n\t\t}\n\t\tCom_Printf_State(PRINT_OK, \"Multitexture extensions found\\n\");\n\t\tgl_mtexable = true;\n\t} else {\n\t\tGL_InvalidateFunction(glMultiTexCoord2f);\n\t\tGL_InvalidateFunction(glActiveTexture);\n\t\tGL_InvalidateFunction(glClientActiveTexture);\n\t}\n\n\tgl_textureunits = min(glConfig.texture_units, 4);\n\n\tif (COM_CheckParm(cmdline_param_client_maximum2textureunits)) {\n\t\tgl_textureunits = min(gl_textureunits, 2);\n\t}\n\n\tif (gl_textureunits < 2) {\n\t\tgl_mtexable = false;\n\t}\n\n\tif (!gl_mtexable) {\n\t\tgl_textureunits = 1;\n\t}\n\telse {\n\t\tCom_Printf_State(PRINT_OK, \"Enabled %i texture units on hardware\\n\", gl_textureunits);\n\t}\n}\n#else\nvoid GL_CheckMultiTextureExtensions(void)\n{\n\tif (GL_VersionAtLeast(2, 0) || SDL_GL_ExtensionSupported(\"GL_ARB_multitexture\"))\n\t{\n\t\tGL_LoadOptionalFunction(glActiveTexture);\n\t\tif (!GL_Available(glActiveTexture)) {\n\t\t\tSys_Error(\"Required OpenGL function not supported: glActiveTexture\");\n\t\t\treturn;\n\t\t}\n\n\t\tgl_textureunits = min(glConfig.texture_units, 4);\n\t\tgl_mtexable = true;\n\t} else {\n\t\tSys_Error(\"Required extension not supported: GL_ARB_multitexture\");\n\t}\n\n}\n#endif\n\n#ifdef WITH_RENDERING_TRACE\nstatic int GL_FindSpecificInEnum(GLenum value, GLenum* enum_array, int length)\n{\n\tint i;\n\n\tfor (i = 0; i < length; ++i) {\n\t\tif (value == enum_array[i]) {\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn length;\n}\n\nstatic int GL_FindIntegerInEnum(GLenum name, GLenum* enum_array, int length)\n{\n\tGLint glInt = 0;\n\n\tGL_BuiltinProcedure(glGetIntegerv, \"name=%u, output=%p\", name, &glInt);\n\n\treturn GL_FindSpecificInEnum(glInt, enum_array, length);\n}\n\nstatic float GL_GetFloat(GLenum name)\n{\n\tfloat result = NAN;\n\n\tGL_BuiltinProcedure(glGetFloatv, \"name=%u, output=%p\", name, &result);\n\n\treturn result;\n}\n\nstatic r_buffer_id GL_VerifyBufferState(GLenum name)\n{\n\tGLint glInt = 0;\n\n\tGL_BuiltinProcedure(glGetIntegerv, \"name=%u, output=%p\", name, &glInt);\n\n\treturn r_buffer_none; // FIXME\n}\n\nstatic qbool GL_IsEnabled_(GLenum pname, const char* name)\n{\n\tqbool result;\n\n\tR_TraceAPI(\"glIsEnabled(%s)\", name);\n\tresult = glIsEnabled(pname);\n\tGL_ProcessErrors(va(\"glIsEnabled(%s)\", name));\n\treturn result;\n}\n\n#define GL_IsEnabled(pname) GL_IsEnabled_(pname, #pname)\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\nstatic void GLC_VerifyArray(glc_vertex_array_element_t* arr, GLenum enabled, GLenum bufferBinding, GLenum pointer, GLenum size, GLenum stride, GLenum type)\n{\n\tif (renderer.vaos_supported) {\n\t\tarr->enabled = GL_IsEnabled(enabled);\n\t\tarr->buf = GL_VerifyBufferState(bufferBinding);\n\t\tGL_BuiltinProcedure(glGetPointerv, \"name=%u, output=%p\", pointer, &arr->pointer_or_offset);\n\t\tif (size) {\n\t\t\tGL_BuiltinProcedure(glGetIntegerv, \"name=%u, output=%p\", size, &arr->size);\n\t\t}\n\t\tGL_BuiltinProcedure(glGetIntegerv, \"name=%u, output=%p\", stride, &arr->stride);\n\t\tGL_BuiltinProcedure(glGetIntegerv, \"name=%u, output=%p\", type, &arr->type);\n\t}\n\telse {\n\t\tmemset(arr, 0, sizeof(*arr));\n\t\tarr->size = (enabled == GL_NORMAL_ARRAY ? 0 : 4);\n\t\tarr->type = GL_FLOAT;\n\t}\n}\n\nstatic void GLC_DownloadVAOState(rendering_state_t* state)\n{\n\tGLenum texture_modes[4] = { 0 };\n\tint i = 0;\n\n\tif (R_UseImmediateOpenGL()) {\n\t\t// vertex array\n\t\tGLC_VerifyArray(&state->vertex_array, GL_VERTEX_ARRAY, GL_VERTEX_ARRAY_BUFFER_BINDING, GL_VERTEX_ARRAY_POINTER, GL_VERTEX_ARRAY_SIZE, GL_VERTEX_ARRAY_STRIDE, GL_VERTEX_ARRAY_TYPE);\n\t\t// color array\n\t\tGLC_VerifyArray(&state->color_array, GL_COLOR_ARRAY, GL_COLOR_ARRAY_BUFFER_BINDING, GL_COLOR_ARRAY_POINTER, GL_COLOR_ARRAY_SIZE, GL_COLOR_ARRAY_STRIDE, GL_COLOR_ARRAY_TYPE);\n\t\t// normal array\n\t\tGLC_VerifyArray(&state->normal_array, GL_NORMAL_ARRAY, GL_NORMAL_ARRAY_BUFFER_BINDING, GL_NORMAL_ARRAY_POINTER, 0, GL_NORMAL_ARRAY_STRIDE, GL_NORMAL_ARRAY_TYPE);\n\n\t\tstate->textureUnits[i].mode = r_texunit_mode_modulate;\n\t\tstate->textureUnits[i].va.type = GL_FLOAT;\n\t\tstate->textureUnits[i].enabled = false;\n\t\tfor (i = 0; i < sizeof(state->textureUnits) / sizeof(state->textureUnits[0]); ++i) {\n\t\t\tif (i < glConfig.texture_units) {\n\t\t\t\tGLC_ClientActiveTexture(GL_TEXTURE0 + i);\n\t\t\t\tGLC_VerifyArray(&state->textureUnits[i].va, GL_TEXTURE_COORD_ARRAY, GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING, GL_TEXTURE_COORD_ARRAY_POINTER, GL_TEXTURE_COORD_ARRAY_SIZE, GL_TEXTURE_COORD_ARRAY_STRIDE, GL_TEXTURE_COORD_ARRAY_TYPE);\n\t\t\t\tGL_SelectTexture(GL_TEXTURE0 + i);\n\t\t\t\tGL_BuiltinProcedure(glGetTexEnviv, \"target=%u, pname=%u, params=%p\", GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &texture_modes[i]);\n\t\t\t\tstate->textureUnits[i].mode = GL_FindSpecificInEnum(texture_modes[i], glTextureEnvModeValues, sizeof(glTextureEnvModeValues) / sizeof(glTextureEnvModeValues[0]));\n\t\t\t\tstate->textureUnits[i].enabled = GL_IsEnabled(GL_TEXTURE_2D);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tstate->textureUnits[i].va.size = 4;\n\t\t\t\tstate->textureUnits[i].va.type = GL_FLOAT;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void GLC_DownloadAlphaTestingState(rendering_state_t* state)\n{\n\tif (R_UseImmediateOpenGL()) {\n\t\tstate->alphaTesting.enabled = GL_IsEnabled(GL_ALPHA_TEST);\n\t\tstate->alphaTesting.func = GL_FindIntegerInEnum(GL_ALPHA_TEST_FUNC, glAlphaTestModeValues, sizeof(glAlphaTestModeValues) / sizeof(glAlphaTestModeValues[0]));\n\t\tstate->alphaTesting.value = GL_GetFloat(GL_ALPHA_TEST_REF);\n\t}\n}\n\nstatic qbool GLC_CompareAlphaTesting(FILE* output, const rendering_state_t* expected, const rendering_state_t* found)\n{\n\tif (memcmp(&expected->alphaTesting, &found->alphaTesting, sizeof(expected->alphaTesting))) {\n\t\tR_TraceAPI(\n\t\t\t\"!ALPHA-TESTING: expected %d,%d,%f found %d,%d,%f\",\n\t\t\texpected->alphaTesting.enabled, expected->alphaTesting.func, expected->alphaTesting.value,\n\t\t\tfound->alphaTesting.enabled, found->alphaTesting.func, found->alphaTesting.value\n\t\t);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic qbool GLC_CompareVertexArray(FILE* output, const glc_vertex_array_element_t* expected, const glc_vertex_array_element_t* found, const char* name)\n{\n\tif (memcmp(expected, found, sizeof(*expected))) {\n\t\tR_TraceAPI(\"!%s: expected enabled(%d),p_offset(%p),buf(%d),size(%d),stride(%d),type(%d), found enabled(%d),p_offset(%p),buf(%d),size(%d),stride(%d),type(%d)\", name,\n\t\t\texpected->enabled, expected->pointer_or_offset, expected->buf, expected->size, expected->stride, expected->type,\n\t\t\tfound->enabled, found->pointer_or_offset, found->buf, found->size, found->stride, found->type\n\t\t);\n\t\treturn false;\n\t}\n\treturn false;\n}\n\nstatic qbool GLC_CompareVertexArrays(FILE* output, const rendering_state_t* expected, const rendering_state_t* found)\n{\n\tqbool problem = false;\n\n\tproblem = GLC_CompareVertexArray(output, &expected->vertex_array, &found->vertex_array, \"VertexArray\") || problem;\n\tproblem = GLC_CompareVertexArray(output, &expected->color_array, &found->color_array, \"ColorArray\") || problem;\n\tproblem = GLC_CompareVertexArray(output, &expected->normal_array, &found->normal_array, \"NormalArray\") || problem;\n\tproblem = GLC_CompareVertexArray(output, &expected->textureUnits[0].va, &found->textureUnits[0].va, \"TextureArray0\") || problem;\n\tproblem = GLC_CompareVertexArray(output, &expected->textureUnits[1].va, &found->textureUnits[1].va, \"TextureArray1\") || problem;\n\tproblem = GLC_CompareVertexArray(output, &expected->textureUnits[2].va, &found->textureUnits[2].va, \"TextureArray2\") || problem;\n\tproblem = GLC_CompareVertexArray(output, &expected->textureUnits[3].va, &found->textureUnits[3].va, \"TextureArray3\") || problem;\n\n\treturn problem;\n}\n\n// Adjust for what OpenGL doesn't know about\n#define GLC_AssumeState(state) { from_gl.state = opengl.rendering_state.state; }\n#else\n#define GLC_DownloadAlphaTestingState(...)\n#define GLC_DownloadVAOState(...)\n#define GLC_CompareAlphaTesting(...) (false)\n#define GLC_CompareVertexArrays(...) (false)\n#define GLC_AssumeState(...)\n#endif\n\nstatic void GL_DownloadTextureUnitState(GLuint* bound_textures, GLuint* bound_arrays, GLuint* bound_cubemaps)\n{\n\tint i;\n\tint units = min(glConfig.texture_units, MAX_LOGGED_TEXTURE_UNITS);\n\n\tfor (i = 0; i < units; ++i) {\n\t\tbound_cubemaps[i] = bound_textures[i] = bound_arrays[i] = 0;\n\n\t\tGL_SelectTexture(GL_TEXTURE0 + i);\n\t\tglGetIntegerv(GL_TEXTURE_BINDING_2D, &bound_textures[i]);\n\t\tif (GL_Supported(R_SUPPORT_TEXTURE_ARRAYS)) {\n\t\t\tglGetIntegerv(GL_TEXTURE_BINDING_2D_ARRAY, &bound_arrays[i]);\n\t\t}\n\t\tif (GL_Supported(R_SUPPORT_CUBE_MAPS)) {\n\t\t\tglGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &bound_cubemaps[i]);\n\t\t}\n\t}\n}\n\nstatic void GL_DownloadState(rendering_state_t* state, GLuint* gl_bound2d, GLuint* gl_bound3d, GLuint* gl_boundCubemaps)\n{\n\tmemset(state, 0, sizeof(*state));\n\n\tGLC_DownloadAlphaTestingState(state);\n\n\t// Blending - we use a single enum for the pairing of src & dst\n\t{\n\t\tGLint src, dst;\n\t\tint i;\n\n\t\tGL_BuiltinProcedure(glGetIntegerv, \"name=%u, output=%p\", GL_BLEND_SRC, &src);\n\t\tGL_BuiltinProcedure(glGetIntegerv, \"name=%u, output=%p\", GL_BLEND_DST, &dst);\n\n\t\tfor (i = 0; i < sizeof(glBlendFuncValuesSource) / sizeof(glBlendFuncValuesSource[0]); ++i) {\n\t\t\tif (glBlendFuncValuesSource[i] == src && glBlendFuncValuesDestination[i] == dst) {\n\t\t\t\tstate->blendFunc = i;\n\t\t\t}\n\t\t}\n\t}\n\tstate->blendingEnabled = GL_IsEnabled(GL_BLEND);\n\n\t// Colors\n\tGL_BuiltinProcedure(glGetFloatv, \"pname=%u, params=%p\", GL_COLOR_CLEAR_VALUE, state->clearColor);\n\t{\n\t\tGLint colorMask_[4];\n\t\tGL_BuiltinProcedure(glGetIntegerv, \"name=%u, output=%p\", GL_COLOR_WRITEMASK, colorMask_);\n\t\tstate->colorMask[0] = colorMask_[0];\n\t\tstate->colorMask[1] = colorMask_[1];\n\t\tstate->colorMask[2] = colorMask_[2];\n\t\tstate->colorMask[3] = colorMask_[3];\n\t}\n\tif (R_UseImmediateOpenGL()) {\n\t\tGL_BuiltinProcedure(glGetFloatv, \"pname=%u, params=%p\", GL_CURRENT_COLOR, state->color);\n\t}\n\telse {\n\t\tstate->color[0] = state->color[1] = state->color[2] = state->color[3] = 1.0f;\n\t}\n\n\t// cullface\n\tstate->cullface.enabled = GL_IsEnabled(GL_CULL_FACE);\n\tstate->cullface.mode = GL_FindIntegerInEnum(GL_CULL_FACE_MODE, glCullFaceValues, sizeof(glCullFaceValues) / sizeof(glCullFaceValues[0]));\n\n\t{\n\t\tGLint viewport[4];\n\n\t\tGL_BuiltinProcedure(glGetIntegerv, \"pname=%u, params=%p\", GL_VIEWPORT, viewport);\n\t\tstate->currentViewportX = viewport[0];\n\t\tstate->currentViewportY = viewport[1];\n\t\tstate->currentViewportWidth = viewport[2];\n\t\tstate->currentViewportHeight = viewport[3];\n\t}\n\n\t// depth buffer\n\t{\n\t\tfloat range[2];\n\t\tGLint glInt = 0;\n\n\t\tglGetFloatv(GL_DEPTH_RANGE, range);\n\n\t\tstate->depth.farRange = range[1];\n\t\tstate->depth.func = GL_FindIntegerInEnum(GL_DEPTH_FUNC, glDepthFunctions, sizeof(glDepthFunctions) / sizeof(glDepthFunctions[0]));\n\t\tglGetIntegerv(GL_DEPTH_WRITEMASK, &glInt);\n\t\tstate->depth.mask_enabled = (glInt != GL_FALSE);\n\t\tstate->depth.nearRange = range[0];\n\t\tstate->depth.test_enabled = GL_IsEnabled(GL_DEPTH_TEST);\n\t}\n\n\t// fog\n\tif (GL_Supported(R_SUPPORT_FOG)) {\n\t\tstate->fog.mode = GL_IsEnabled(GL_FOG) ? r_fogmode_enabled : r_fogmode_disabled;\n\t}\n\n\t//\n\tif (GL_Supported(R_SUPPORT_FRAMEBUFFERS_SRGB)) {\n\t\tstate->framebuffer_srgb = GL_IsEnabled(GL_FRAMEBUFFER_SRGB);\n\t}\n\telse {\n\t\tstate->framebuffer_srgb = false;\n\t}\n\n\t// TODO: GLC attributes\n\n\t// line\n\tstate->line.smooth = GL_IsEnabled(GL_LINE_SMOOTH);\n\tglGetFloatv(GL_LINE_WIDTH, &state->line.width);\n\tGL_ProcessErrors(\"VerifyState[end-3]\");\n\n\t// \n\t{\n\t\tGLint polygonModes[2];\n\t\tglGetIntegerv(GL_POLYGON_MODE, polygonModes);\n\t\tstate->polygonMode = GL_FindSpecificInEnum(polygonModes[0], glPolygonModeValues, sizeof(glPolygonModeValues) / sizeof(glPolygonModeValues[0]));\n\t}\n\n\t// polygon offset\n\tGL_BuiltinProcedure(glGetFloatv, \"pname=%u, params=%p\", GL_POLYGON_OFFSET_FACTOR, &state->polygonOffset.factor);\n\tstate->polygonOffset.fillEnabled = GL_IsEnabled(GL_POLYGON_OFFSET_FILL);\n\tstate->polygonOffset.lineEnabled = GL_IsEnabled(GL_POLYGON_OFFSET_LINE);\n\tGL_BuiltinProcedure(glGetFloatv, \"pname=%u, params=%p\", GL_POLYGON_OFFSET_UNITS, &state->polygonOffset.units);\n\n\t// pack offset\n\tglGetIntegerv(GL_PACK_ALIGNMENT, &state->pack_alignment);\n\n\tGL_ProcessErrors(\"VerifyState[end-2]\");\n\tGLC_DownloadVAOState(state);\n\tGL_ProcessErrors(\"VerifyState[end-1]\");\n\tGL_DownloadTextureUnitState(gl_bound2d, gl_bound3d, gl_boundCubemaps);\n\tGL_ProcessErrors(\"VerifyState[end]\");\n}\n\nstatic qbool GL_CompareBlending(FILE* output, const rendering_state_t* expected, const rendering_state_t* found)\n{\n\tif (expected->blendingEnabled != found->blendingEnabled || expected->blendFunc != found->blendFunc) {\n\t\tR_TraceAPI(\n\t\t\t\"!BLENDING: expected %d,%d found %d,%d\",\n\t\t\texpected->blendingEnabled, expected->blendFunc,\n\t\t\tfound->blendingEnabled, found->blendFunc\n\t\t);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic qbool GL_CompareColors(FILE* output, const rendering_state_t* expected, const rendering_state_t* found)\n{\n\tqbool problem = false;\n\tif (memcmp(expected->clearColor, found->clearColor, sizeof(expected->clearColor))) {\n\t\tR_TraceAPI(\n\t\t\t\"!CLEARCOLOR: expected %f,%f,%f,%f found %f,%f,%f,%f\",\n\t\t\texpected->clearColor[0], expected->clearColor[1], expected->clearColor[2], expected->clearColor[3],\n\t\t\tfound->clearColor[0], found->clearColor[1], found->clearColor[2], found->clearColor[3]\n\t\t);\n\t\tproblem = true;\n\t}\n\tif (memcmp(expected->colorMask, found->colorMask, sizeof(expected->colorMask))) {\n\t\tR_TraceAPI(\n\t\t\t\"!COLORMASK: expected %d,%d,%d,%d found %d,%d,%d,%d\",\n\t\t\texpected->colorMask[0], expected->colorMask[1], expected->colorMask[2], expected->colorMask[3],\n\t\t\tfound->colorMask[0], found->colorMask[1], found->colorMask[2], found->colorMask[3]\n\t\t);\n\t\tproblem = true;\n\t}\n\tif (memcmp(expected->color, found->color, sizeof(expected->color))) {\n\t\tR_TraceAPI(\n\t\t\t\"!COLOR: expected %f,%f,%f,%f found %f,%f,%f,%f\",\n\t\t\texpected->color[0], expected->color[1], expected->color[2], expected->color[3],\n\t\t\tfound->color[0], found->color[1], found->color[2], found->color[3]\n\t\t);\n\t\tproblem = true;\n\t}\n\treturn problem;\n}\n\nstatic qbool GL_CompareCulling(FILE* output, const rendering_state_t* expected, const rendering_state_t* found)\n{\n\tif (memcmp(&expected->cullface, &found->cullface, sizeof(expected->cullface))) {\n\t\tR_TraceAPI(\n\t\t\t\"!CULLING: expected %d,%d found %d,%d\",\n\t\t\texpected->cullface.enabled, expected->cullface.mode,\n\t\t\tfound->cullface.enabled, found->cullface.mode\n\t\t);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic qbool GL_CompareViewport(FILE* output, const rendering_state_t* expected, const rendering_state_t* found)\n{\n\tif (expected->currentViewportX != found->currentViewportX ||\n\t\texpected->currentViewportHeight != found->currentViewportHeight ||\n\t\texpected->currentViewportWidth != found->currentViewportWidth ||\n\t\texpected->currentViewportY != found->currentViewportY)\n\t{\n\t\tR_TraceAPI(\n\t\t\t\"!VIEWPORT: expected %d,%d,%d,%d found %d,%d,%d,%d\",\n\t\t\texpected->currentViewportX, expected->currentViewportY, expected->currentViewportWidth, expected->currentViewportHeight,\n\t\t\tfound->currentViewportX, found->currentViewportY, found->currentViewportWidth, found->currentViewportHeight\n\t\t);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic qbool GL_CompareDepth(FILE* output, const rendering_state_t* expected, const rendering_state_t* found)\n{\n\tif (memcmp(&expected->depth, &found->depth, sizeof(expected->depth))) {\n\t\tR_TraceAPI(\n\t\t\t\"!DEPTH: expected %d,%d,%d,%f,%f found %d,%d,%d,%f,%f\",\n\t\t\texpected->depth.test_enabled, expected->depth.mask_enabled, expected->depth.func, expected->depth.nearRange, expected->depth.farRange,\n\t\t\tfound->depth.test_enabled, found->depth.mask_enabled, found->depth.func, found->depth.nearRange, found->depth.farRange\n\t\t);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic qbool GL_CompareLine(FILE* output, const rendering_state_t* expected, const rendering_state_t* found)\n{\n\tif (expected->line.smooth != found->line.smooth || expected->line.width != found->line.width) {\n\t\tR_TraceAPI(\n\t\t\t\"!LINE: expected %d,%f found %d,%f\",\n\t\t\texpected->line.smooth, expected->line.width,\n\t\t\tfound->line.smooth, found->line.width\n\t\t);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic qbool GL_CompareMisc(FILE* output, const rendering_state_t* expected, const rendering_state_t* found)\n{\n\tqbool problem = false;\n\tif (expected->fog.mode != found->fog.mode) {\n\t\tR_TraceAPI(\"!FOG: expected %s, found %s\", fogModeDescriptions[expected->fog.mode], fogModeDescriptions[found->fog.mode]);\n\t}\n\tif (expected->framebuffer_srgb != found->framebuffer_srgb) {\n\t\tR_TraceAPI(\"!SRGB: expected %d, found %d\", expected->framebuffer_srgb, found->framebuffer_srgb);\n\t}\n\tif (expected->polygonMode != found->polygonMode) {\n\t\tR_TraceAPI(\"!POLYMODE: expected %d, found %d\", expected->polygonMode, found->polygonMode);\n\t}\n\treturn problem;\n}\n\n\nstatic qbool GL_CompareBoundTextures(FILE* output, const GLuint* expected, const GLuint* found, const char* name)\n{\n\tint units = min(MAX_LOGGED_TEXTURE_UNITS, glConfig.texture_units);\n\tint i;\n\tqbool problem = false;\n\n\tfor (i = 0; i < units; ++i) {\n\t\tif (expected[i] != found[i]) {\n\t\t\tR_TraceAPI(\"!%s%d: expected %u, found %u\", name, i, expected[i], found[i]);\n\t\t\tproblem = true;\n\t\t}\n\t}\n\n\treturn problem;\n}\n\nstatic void GL_CompareState(FILE* output, const rendering_state_t* found, const GLuint* gl_bound2d, const GLuint* gl_bound3d, const GLuint* gl_boundCubemap)\n{\n\tqbool problems = false;\n\tconst rendering_state_t* expected = &opengl.rendering_state;\n\n\tproblems = GL_CompareBlending(output, expected, found) || problems;\n\tproblems = GL_CompareColors(output, expected, found) || problems;\n\tproblems = GL_CompareCulling(output, expected, found) || problems;\n\tproblems = GL_CompareViewport(output, expected, found) || problems;\n\tproblems = GL_CompareDepth(output, expected, found) || problems;\n\tproblems = GL_CompareLine(output, expected, found) || problems;\n\tproblems = GL_CompareMisc(output, expected, found) || problems;\n\n\tif (R_UseImmediateOpenGL()) {\n\t\tproblems = GLC_CompareAlphaTesting(output, expected, found) || problems;\n\t\tproblems = GLC_CompareVertexArrays(output, expected, found) || problems;\n\t}\n\n\tproblems = GL_CompareBoundTextures(output, bound_texture_state[texture_type_2d], gl_bound2d, \"2DTEXTURE\") || problems;\n\tproblems = GL_CompareBoundTextures(output, bound_texture_state[texture_type_2d_array], gl_bound3d, \"TEXTUREARRAY\") || problems;\n\tproblems = GL_CompareBoundTextures(output, bound_texture_state[texture_type_cubemap], gl_boundCubemap, \"CUBEMAP\") || problems;\n\n\tif (problems) {\n\t\tCon_Printf(\"*** PROBLEM ***\\n\");\n\t}\n\n\treturn;\n}\n\nvoid GL_VerifyState(FILE* output)\n{\n\trendering_state_t from_gl;\n\tGLuint gl_bound2d[MAX_LOGGED_TEXTURE_UNITS] = { 0 };\n\tGLuint gl_bound3d[MAX_LOGGED_TEXTURE_UNITS] = { 0 };\n\tGLuint gl_boundCubemap[MAX_LOGGED_TEXTURE_UNITS] = { 0 };\n\n\tR_TraceEnterFunctionRegion;\n\tGL_ProcessErrors(__func__);\n\tGL_DownloadState(&from_gl, gl_bound2d, gl_bound3d, gl_boundCubemap);\n\n\tGLC_AssumeState(normal_array.buf);\n\tGLC_AssumeState(normal_array.size);\n\tGLC_AssumeState(color_array.buf);\n\tGLC_AssumeState(vertex_array.buf);\n\tGLC_AssumeState(textureUnits[0].va.buf);\n\tGLC_AssumeState(textureUnits[1].va.buf);\n\tGLC_AssumeState(textureUnits[2].va.buf);\n\tGLC_AssumeState(textureUnits[3].va.buf);\n\n\t// Compare\n\tGL_CompareState(output, &from_gl, gl_bound2d, gl_bound3d, gl_boundCubemap);\n\tR_TraceLeaveFunctionRegion;\n}\n#endif\n"
  },
  {
    "path": "src/gl_texture.c",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: gl_texture.c,v 1.44 2007-10-05 19:06:24 johnnycz Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"crc.h\"\n#include \"image.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"r_renderer.h\"\n#include \"tr_types.h\"\n#include \"r_texture.h\"\n#include \"r_texture_internal.h\"\n#include \"r_brushmodel_sky.h\"\n#include \"r_local.h\"\n#include \"r_aliasmodel.h\" // for shelltexture only\n#include \"r_lighting.h\"\n#include \"gl_texture_internal.h\"\n\nconst texture_ref null_texture_reference = { 0 };\nstatic GLenum gl_solid_format = GL_RGB8, gl_alpha_format = GL_RGBA8;\n\nextern unsigned d_8to24table2[256];\n\nextern int anisotropy_ext;\n\nstatic GLint glTextureMinificationOptions[] = {\n\tGL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR\n};\nstatic GLint glTextureMagnificationOptions[] = {\n\tGL_NEAREST, GL_LINEAR\n};\nstatic GLenum glTextureTargetForType[] = {\n\tGL_TEXTURE_2D, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_CUBE_MAP, GL_TEXTURE_2D_MULTISAMPLE\n};\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(glTextureMinificationOptions) / sizeof(glTextureMinificationOptions[0]) == texture_minification_count);\nC_ASSERT(sizeof(glTextureMagnificationOptions) / sizeof(glTextureMagnificationOptions[0]) == texture_magnification_count);\nC_ASSERT(sizeof(glTextureTargetForType) / sizeof(glTextureTargetForType[0]) == texture_type_count);\n#endif\n\nstatic int GL_InternalFormat(int mode)\n{\n\tif (vid_gammacorrection.integer) {\n\t\treturn (mode & TEX_ALPHA) ? GL_SRGB8_ALPHA8 : GL_SRGB8;\n\t}\n\telse if (mode & TEX_NOCOMPRESS) {\n\t\treturn (mode & TEX_ALPHA) ? GL_RGBA8 : GL_RGB8;\n\t}\n\telse {\n\t\treturn (mode & TEX_ALPHA) ? gl_alpha_format : gl_solid_format;\n\t}\n}\n\nstatic int GL_StorageFormat(int mode)\n{\n\tif (mode & TEX_FLOAT) {\n\t\treturn (mode & TEX_ALPHA) ? GL_RGBA16F : GL_RGB16F;\n\t}\n\telse if (vid_gammacorrection.integer) {\n\t\treturn (mode & TEX_ALPHA) ? GL_SRGB8_ALPHA8 : GL_SRGB8;\n\t}\n\telse {\n\t\treturn (mode & TEX_ALPHA) ? GL_RGBA8 : GL_RGB8;\n\t}\n}\n\nvoid GL_UploadTexture(texture_ref texture, int mode, int width, int height, byte* newdata)\n{\n\t// Upload the main texture to OpenGL.\n\tGL_TexSubImage2D(GL_TEXTURE0, texture, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, newdata);\n\tif (mode & TEX_MIPMAP) {\n\t\tGL_TextureMipmapGenerateWithData(GL_TEXTURE0, texture, (byte*)newdata, width, height, GL_InternalFormat(mode));\n\t}\n}\n\nvoid GL_CreateTexturesWithIdentifier(r_texture_type_id type, int n, texture_ref* textures, const char* identifier)\n{\n\tGLenum textureUnit = GL_TEXTURE0;\n\tGLenum target = glTextureTargetForType[type];\n\tGLsizei i;\n\n\tfor (i = 0; i < n; ++i) {\n\t\tgltexture_t* glt = R_NextTextureSlot(type);\n\n\t\tGL_CreateTextureNames(textureUnit, target, 1, &glt->texnum);\n\t\tglt->type = type;\n\t\tGL_BindTextureUnit(textureUnit, glt->reference);\n\n\t\ttextures[i] = glt->reference;\n\n\t\tif (identifier) {\n\t\t\tstrlcpy(glt->identifier, identifier, sizeof(glt->identifier));\n\t\t\tGL_TraceObjectLabelSet(GL_TEXTURE, glt->texnum, -1, identifier);\n\t\t}\n\t}\n}\n\nvoid GL_TexturesCreate(r_texture_type_id type, int n, texture_ref* textures)\n{\n\tGL_CreateTexturesWithIdentifier(type, n, textures, NULL);\n}\n\n// For OpenGL wrapper functions\nGLuint GL_TextureNameFromReference(texture_ref ref)\n{\n\tassert(ref.index < sizeof(gltextures) / sizeof(gltextures[0]));\n\n\treturn gltextures[ref.index].texnum;\n}\n\n// For OpenGL wrapper functions\nGLenum GL_TextureTargetFromReference(texture_ref ref)\n{\n\tassert(ref.index < sizeof(gltextures) / sizeof(gltextures[0]));\n\n\treturn glTextureTargetForType[gltextures[ref.index].type];\n}\n\nconst char* GL_TextureIdentifierByGLReference(GLuint texnum)\n{\n\tstatic char name[256];\n\n\tmemset(name, 0, sizeof(name));\n\tGL_TraceObjectLabelGet(GL_TEXTURE, texnum, sizeof(name), NULL, name);\n\tif (!name[0]) {\n\t\tR_TextureFindIdentifierByReference(texnum, name, sizeof(name));\n\t}\n\n\treturn name;\n}\n\nvoid GL_TextureReplace2D(\n\tGLenum textureUnit, GLenum target, texture_ref* ref, GLint internalformat,\n\tGLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, GLvoid *pixels\n)\n{\n\tgltexture_t* tex;\n\n\tif (!R_TextureReferenceIsValid(*ref)) {\n\t\trenderer.TexturesCreate(texture_type_2d, 1, ref);\n\t}\n\n\ttex = &gltextures[ref->index];\n\tif (tex->texture_width != width || tex->texture_height != height || glTextureTargetForType[tex->type] != target || GL_InternalFormat(tex->texmode) != internalformat) {\n\t\tgltexture_t old = *tex;\n\t\tR_DeleteTexture(ref);\n\t\t*ref = R_LoadTexturePixels(pixels, old.identifier, width, height, old.texmode);\n\t}\n\telse {\n\t\tGL_TexSubImage2D(textureUnit, *ref, 0, 0, 0, width, height, format, type, pixels);\n\t}\n}\n\nvoid GL_TextureSetFiltering(texture_ref texture, texture_minification_id minification_filter, texture_magnification_id magnification_filter)\n{\n\tassert(minification_filter >= 0 && minification_filter < texture_minification_count);\n\tassert(magnification_filter >= 0 && magnification_filter < texture_magnification_count);\n\n\tif (!R_TextureReferenceIsValid(texture)) {\n\t\treturn;\n\t}\n\n\tgltextures[texture.index].minification_filter = minification_filter;\n\tgltextures[texture.index].magnification_filter = magnification_filter;\n\n\tGL_TexParameteri(GL_TEXTURE0, texture, GL_TEXTURE_MIN_FILTER, glTextureMinificationOptions[minification_filter]);\n\tGL_TexParameteri(GL_TEXTURE0, texture, GL_TEXTURE_MAG_FILTER, glTextureMagnificationOptions[magnification_filter]);\n}\n\nvoid GL_TextureWrapModeClamp(texture_ref tex)\n{\n\tif (GL_VersionAtLeast(1, 2)) {\n\t\tGL_TexParameteri(GL_TEXTURE0, tex, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n\t\tGL_TexParameteri(GL_TEXTURE0, tex, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n\t\tif (R_TextureType(tex) == texture_type_cubemap) {\n\t\t\tGL_TexParameteri(GL_TEXTURE0, tex, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);\n\t\t}\n\t}\n\telse {\n\t\tGL_TexParameteri(GL_TEXTURE0, tex, GL_TEXTURE_WRAP_S, GL_CLAMP);\n\t\tGL_TexParameteri(GL_TEXTURE0, tex, GL_TEXTURE_WRAP_T, GL_CLAMP);\n\t}\n}\n\nvoid GL_TextureSetAnisotropy(texture_ref texture, int anisotropy)\n{\n\tif (anisotropy_ext) {\n\t\tGL_TexParameterf(GL_TEXTURE0, texture, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);\n\t}\n}\n\nvoid GL_TextureDelete(texture_ref texture)\n{\n\tgltexture_t* slot = &gltextures[texture.index];\n\tGLuint texture_num = slot->texnum;\n\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tSys_Printf(\"\\nopengl-texture,free,%u,%d,%d,%d,%s\\n\", texture.index, slot->texture_width, slot->texture_height, slot->texture_width * slot->texture_height * max(slot->depth,1) * slot->bpp, slot->identifier);\n#endif\n\n\tGL_BuiltinProcedure(glDeleteTextures, \"n=%d, textures=%p\", 1, &texture_num);\n\n\t// Might have been bound when deleted, update state\n\tGL_InvalidateTextureReferences(texture_num);\n\tslot->texnum = 0;\n}\n\nvoid GL_AllocateStorage(gltexture_t* texture)\n{\n\tGL_TexStorage2D(texture->reference, texture->miplevels, GL_StorageFormat(texture->texmode), texture->texture_width, texture->texture_height, false);\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tSys_Printf(\"\\nopengl-texture,alloc,%u,%d,%d,%d,%s\\n\", texture->reference.index, texture->texture_width, texture->texture_height, texture->texture_width * texture->texture_height * texture->bpp, texture->identifier);\n#endif\n}\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\nqbool GLM_TextureAllocateArrayStorage(gltexture_t* slot)\n{\n\tGLenum error;\n\n\tR_TraceAPI(\"Allocating %d x %d x %d, %d miplevels\\n\", slot->texture_width, slot->texture_height, slot->depth, slot->miplevels);\n\tGL_ProcessErrors(\"GLM_TextureAllocateArrayStorage flush\");\n\tGL_ConsumeErrors();\n\terror = GL_TexStorage3D(GL_TEXTURE0, slot->reference, slot->miplevels, GL_StorageFormat(TEX_ALPHA), slot->texture_width, slot->texture_height, slot->depth, false);\n\n\tif (error != GL_NO_ERROR) {\n#ifdef WITH_RENDERING_TRACE\n\t\tint array_width, array_height, array_depth;\n\n\t\tarray_width = R_TextureWidth(slot->reference);\n\t\tarray_height = R_TextureHeight(slot->reference);\n\t\tarray_depth = R_TextureDepth(slot->reference);\n\n\t\tR_TraceAPI(\"!!ERROR Array allocation failed, error %X: [mip %d, %d x %d x %d]\\n\", error, slot->miplevels, slot->texture_width, slot->texture_height, slot->depth);\n\t\tR_TraceAPI(\" > Sizes reported: %d x %d x %d\\n\", array_width, array_height, array_depth);\n#endif\n\t\treturn false;\n\t}\n\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tSys_Printf(\"\\nopengl-texture,alloc,%u,%d,%d,%d,%s\\n\", slot->reference.index, slot->texture_width, slot->texture_height, slot->texture_width * slot->texture_height * slot->depth * (slot->texmode & TEX_ALPHA ? 4 : 3), slot->identifier);\n#endif\n\n\treturn true;\n}\n#endif // RENDERER_OPTION_MODERN_OPENGL\n\nvoid GL_AllocateTextureNames(gltexture_t* glt)\n{\n\tassert(glt->type >= 0 && glt->type < sizeof(glTextureTargetForType) / sizeof(glTextureTargetForType[0]));\n\n\tGL_CreateTextureNames(GL_TEXTURE0, glTextureTargetForType[glt->type], 1, &glt->texnum);\n}\n\nvoid GL_TextureCreate2D(texture_ref* texture, int width, int height, const char* name, qbool is_lightmap)\n{\n\tGL_CreateTexturesWithIdentifier(texture_type_2d, 1, texture, name);\n\tGL_TexStorage2D(*texture, 1, GL_RGBA8, width, height, is_lightmap);\n\trenderer.TextureSetFiltering(*texture, texture_minification_linear, texture_magnification_linear);\n\tGL_TextureWrapModeClamp(*texture);\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tSys_Printf(\"\\nopengl-texture,alloc,%u,%d,%d,%d,%s\\n\", texture->index, width, height, width * height * 4, name);\n#endif\n}\n\nvoid GL_TextureCompressionSet(qbool enabled)\n{\n\tgl_alpha_format = (enabled ? GL_COMPRESSED_RGBA_ARB : GL_RGBA8);\n\tgl_solid_format = (enabled ? GL_COMPRESSED_RGB_ARB : GL_RGB8);\n}\n\nvoid GL_TextureGet(texture_ref texture, int buffer_size, byte* buffer, int bpp)\n{\n\tGL_GetTexImage(GL_TEXTURE0, texture, 0, bpp == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, buffer_size, buffer);\n}\n\nvoid GL_TextureLoadCubemapFace(texture_ref cubemap, r_cubemap_direction_id direction, const byte* data, int width, int height)\n{\n\tGL_TexSubImage3D(0, cubemap, 0, 0, 0, direction, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, data);\n}\n"
  },
  {
    "path": "src/gl_texture.h",
    "content": "/*\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_GL_TEXTURE_H\n#define EZQUAKE_GL_TEXTURE_H\n\n#include \"r_texture.h\"\n#include \"r_texture_internal.h\"\n\n// External API\nqbool GL_EnsureTextureUnitBound(int unit, texture_ref texture);\nvoid GL_TextureMipmapGenerate(texture_ref texture);\nvoid GL_TextureAnistropyChanged(texture_ref texture);\nvoid GL_TextureGet(texture_ref tex, int buffer_size, byte* buffer, int bpp);\nvoid GL_TextureWrapModeClamp(texture_ref tex);\nvoid GL_UploadTexture(texture_ref texture, int mode, int width, int height, byte* newdata);\nvoid GL_ReplaceSubImageRGBA(texture_ref ref, int offsetx, int offsety, int width, int height, byte* buffer);\n\n// Internal\nqbool GLM_TextureAllocateArrayStorage(gltexture_t* slot);\nvoid GL_AllocateStorage(gltexture_t* texture);\nvoid GL_AllocateTextureNames(gltexture_t* glt);\n\n// Samplers\nvoid GL_SamplerSetNearest(unsigned int texture_unit_number);\nvoid GL_SamplerSetLinear(unsigned int texture_unit_number);\nvoid GL_SamplerClear(unsigned int texture_unit_number);\nvoid GL_DeleteSamplers(void);\n\n#endif // EZQUAKE_GL_TEXTURE_H\n"
  },
  {
    "path": "src/gl_texture_functions.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// Wrapper functions over newer OpenGL texture management functions\n\n#include <SDL.h>\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"image.h\"\n#include \"tr_types.h\"\n#include \"r_texture_internal.h\"\n#include \"gl_texture_internal.h\"\n#include \"r_trace.h\"\n#include \"r_renderer.h\"\n\n// Samplers\nstatic unsigned int nearest_sampler;\nstatic unsigned int linear_sampler;\n\nGLuint GL_TextureNameFromReference(texture_ref ref);\nGLenum GL_TextureTargetFromReference(texture_ref ref);\n\n// <DSA-functions (4.5)>\n// These allow modification of textures without binding (-bind-to-edit)\nGL_StaticProcedureDeclaration(glGetTextureLevelParameterfv, \"texture=%u, level=%d, pname=%u, params=%p\", GLuint texture, GLint level, GLenum pname, GLfloat* params)\nGL_StaticProcedureDeclaration(glGetTextureLevelParameteriv, \"texture=%u, level=%d, pname=%u, params=%p\", GLuint texture, GLint level, GLenum pname, GLint* params)\nGL_StaticProcedureDeclaration(glGenerateTextureMipmap, \"texture=%u\", GLuint texture)\nGL_StaticProcedureDeclaration(glGetTextureImage, \"texture=%u, level=%d, format=%u, type=%u, bufSize=%d, pixels=%p\", GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels)\nGL_StaticProcedureDeclaration(glCreateTextures, \"target=%u, n=%d, textures=%p\", GLenum target, GLsizei n, GLuint* textures)\nGL_StaticProcedureDeclaration(glGetnTexImage, \"target=%u, level=%d, format=%u, type=%u, bufSize=%d, pixels=%p\", GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels)\nGL_StaticProcedureDeclaration(glTextureParameteri, \"texture=%u, pname=%u, param=%d\", GLuint texture, GLenum pname, GLint param)\nGL_StaticProcedureDeclaration(glTextureParameterf, \"texture=%u, pname=%u, param=%f\", GLuint texture, GLenum pname, GLfloat param)\nGL_StaticProcedureDeclaration(glTextureParameterfv, \"texture=%u, pname=%u, params=%p\", GLuint texture, GLenum pname, const GLfloat* params)\nGL_StaticProcedureDeclaration(glTextureParameteriv, \"texture=%u, pname=%u, params=%p\", GLuint texture, GLenum pname, const GLint* params)\nGL_StaticProcedureDeclaration(glTextureStorage3D, \"texture=%u, levels=%d, internalformat=%u, width=%d, height=%d, depth=%d\", GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth)\nGL_StaticProcedureDeclaration(glTextureStorage2D, \"texture=%u, levels=%d, internalformat=%u, width=%d, height=%d\", GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)\nGL_StaticProcedureDeclaration(glTextureSubImage2D, \"texture=%u, level=%d, xoffset=%d, yoffset=%d, width=%d, height=%d, format=%u, type=%u, pixels=%p\", GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid* pixels)\nGL_StaticProcedureDeclaration(glTextureSubImage3D, \"texture=%u, level=%d, xoffset=%d, yoffset=%d, zoffset=%d, width=%d, height=%d, depth=%d, format=%u, type=%u, pixels=%p\", GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid* pixels)\n\nGL_StaticProcedureDeclaration(glTexStorage2D, \"target=%u, levels=%d, internalformat=%u, width=%d, height=%d\", GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)\nGL_StaticProcedureDeclaration(glTexSubImage3D, \"target=%u, level=%d, xoffset=%d, yoffset=%d, zoffset=%d, width=%d, height=%d, depth=%d, format=%u, type=%u, pixels=%p\", GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid* pixels)\nGL_StaticProcedureDeclaration(glTexStorage3D, \"target=%u, levels=%d, internalformat=%u, width=%d, height=%d, depth=%d\", GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth)\nGL_StaticProcedureDeclaration(glTexImage3D, \"target=%u, level=%d, internalformat=%d, width=%d, height=%d, depth=%d, border=%d, format=%u, type=%u, data=%p\", GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid* data)\nGL_StaticProcedureDeclaration(glGenerateMipmap, \"target=%u\", GLenum target)\n\nGL_StaticProcedureDeclaration(glGenSamplers, \"n=%d, samplers=%p\", GLsizei n, GLuint* samplers)\nGL_StaticProcedureDeclaration(glDeleteSamplers, \"n=%d, samplers=%p\", GLsizei n, const GLuint* samplers)\nGL_StaticProcedureDeclaration(glSamplerParameterf, \"sampler=%u, pname=%u, param=%f\", GLuint sampler, GLenum pname, GLfloat param)\nGL_StaticProcedureDeclaration(glBindSampler, \"unit=%u, sampler=%u\", GLuint unit, GLuint sampler)\n\nGL_StaticProcedureDeclaration(glGetInternalformativ, \"target=%u, internalformat=%u, pname=%u, bufSize=%d, params=%p\", GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params)\n\n// Multi-sampled textures\nGL_StaticProcedureDeclaration(glTextureStorage2DMultisample, \"texture=%d, samples=%d, internalformat=%d, width=%d, height=%d, fixedsamplelocations=%d\", GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations)\nGL_StaticProcedureDeclaration(glTexStorage2DMultisample, \"target=%x, samples=%d, internalformat=%d, width=%d, height=%d, fixedsamplelocations=%d\", GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations)\nGL_StaticProcedureDeclaration(glTexImage2DMultisample, \"target=%x, samples=%d, internalformat=%d, width=%d, height=%d, fixedsamplelocations=%d\", GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations)\n\nvoid GL_LoadTextureManagementFunctions(void)\n{\n\tglConfig.supported_features &= ~(R_SUPPORT_TEXTURE_ARRAYS | R_SUPPORT_TEXTURE_SAMPLERS);\n\tnearest_sampler = linear_sampler = 0;\n\n\tglConfig.preferred_format = glConfig.preferred_type = 0;\n\tif (GL_VersionAtLeast(4, 3) || SDL_GL_ExtensionSupported(\"GL_ARB_internalformat_query2\")) {\n\t\tGL_LoadOptionalFunction(glGetInternalformativ);\n\n\t\tif (GL_Available(glGetInternalformativ)) {\n\t\t\tGLint tempFormat = 0, tempType = 0;\n\n\t\t\tGL_Procedure(glGetInternalformativ, GL_TEXTURE_2D, GL_RGBA8, GL_TEXTURE_IMAGE_FORMAT, 1, &tempFormat);\n\t\t\tGL_Procedure(glGetInternalformativ, GL_TEXTURE_2D, GL_RGBA8, GL_TEXTURE_IMAGE_TYPE, 1, &tempType);\n\n\t\t\tglConfig.preferred_format = tempFormat;\n\t\t\tglConfig.preferred_type = tempType;\n\t\t}\n\t} else {\n\t\tGL_InvalidateFunction(glGetInternalformativ);\n\t}\n\n\tif (GL_VersionAtLeast(4, 2) || SDL_GL_ExtensionSupported(\"GL_ARB_texture_storage\")) {\n\t\tGL_LoadOptionalFunction(glTexStorage2D);\n\t\tGL_LoadOptionalFunction(glTexStorage3D);\n\t} else {\n\t\tGL_InvalidateFunction(glTexStorage2D);\n\t\tGL_InvalidateFunction(glTexStorage3D);\n\t}\n\n\tif (GL_VersionAtLeast(3,0) || SDL_GL_ExtensionSupported(\"GL_EXT_texture_array\")) {\n\t\tqbool all_available = true;\n\n\t\t// OpenGL 1.2\n\t\tGL_LoadMandatoryFunctionExtension(glTexSubImage3D, all_available);\n\t\tGL_LoadMandatoryFunctionExtension(glTexImage3D, all_available);\n\n\t\tglConfig.supported_features |= (all_available ? R_SUPPORT_TEXTURE_ARRAYS : 0);\n\t} else {\n\t\tGL_InvalidateFunction(glTexSubImage3D);\n\t\tGL_InvalidateFunction(glTexImage3D);\n\t}\n\n\tif (GL_VersionAtLeast(3, 3) || SDL_GL_ExtensionSupported(\"GL_ARB_sampler_objects\")) {\n\t\tqbool all_available = true;\n\n\t\t// Texture samplers in 3.3\n\t\tall_available = true;\n\t\tGL_LoadMandatoryFunctionExtension(glGenSamplers, all_available);\n\t\tGL_LoadMandatoryFunctionExtension(glDeleteSamplers, all_available);\n\t\tGL_LoadMandatoryFunctionExtension(glSamplerParameterf, all_available);\n\t\tGL_LoadMandatoryFunctionExtension(glBindSampler, all_available);\n\t\tglConfig.supported_features |= (all_available ? R_SUPPORT_TEXTURE_SAMPLERS : 0);\n\t} else {\n\t\tGL_InvalidateFunction(glGenSamplers);\n\t\tGL_InvalidateFunction(glDeleteSamplers);\n\t\tGL_InvalidateFunction(glSamplerParameterf);\n\t\tGL_InvalidateFunction(glBindSampler);\n\t}\n\n\tif (GL_VersionAtLeast(3, 0)) {\n\t\t// Core in 3.0\n\t\tGL_LoadOptionalFunction(glGenerateMipmap);\n\t}\n\telse if (SDL_GL_ExtensionSupported(\"GL_EXT_framebuffer_object\")) {\n\t\tGL_LoadOptionalFunction(glGenerateMipmap);\n\t\tif (!GL_Available(glGenerateMipmap)) {\n\t\t\tGL_LoadOptionalFunctionEXT(glGenerateMipmap);\n\t\t}\n\t} else {\n\t\tGL_InvalidateFunction(glGenerateMipmap);\n\t}\n\n\tif (GL_VersionAtLeast(3, 2)) {\n\t\tGL_LoadOptionalFunction(glTextureStorage2DMultisample);\n\t\tGL_LoadOptionalFunction(glTexStorage2DMultisample);\n\t\tGL_LoadOptionalFunction(glTexImage2DMultisample);\n\t} else {\n\t\tGL_InvalidateFunction(glTextureStorage2DMultisample);\n\t\tGL_InvalidateFunction(glTexStorage2DMultisample);\n\t\tGL_InvalidateFunction(glTexImage2DMultisample);\n\t}\n\n\tif (GL_UseDirectStateAccess()) {\n\t\tGL_LoadOptionalFunction(glGenerateTextureMipmap);\n\t\tGL_LoadOptionalFunction(glGetTextureImage);\n\t\tGL_LoadOptionalFunction(glCreateTextures);\n\t\tGL_LoadOptionalFunction(glTextureParameteri);\n\t\tGL_LoadOptionalFunction(glTextureParameterf);\n\t\tGL_LoadOptionalFunction(glTextureParameterfv);\n\t\tGL_LoadOptionalFunction(glTextureParameteriv);\n\t\tGL_LoadOptionalFunction(glTextureStorage3D);\n\t\tGL_LoadOptionalFunction(glTextureStorage2D);\n\t\tGL_LoadOptionalFunction(glTextureSubImage2D);\n\t\tGL_LoadOptionalFunction(glTextureSubImage3D);\n\t\tGL_LoadOptionalFunction(glGetTextureLevelParameterfv);\n\t\tGL_LoadOptionalFunction(glGetTextureLevelParameterfv);\n\t\tGL_LoadOptionalFunction(glGetTextureLevelParameteriv);\n\t} else {\n\t\tGL_InvalidateFunction(glGenerateTextureMipmap);\n\t\tGL_InvalidateFunction(glGetTextureImage);\n\t\tGL_InvalidateFunction(glCreateTextures);\n\t\tGL_InvalidateFunction(glTextureParameteri);\n\t\tGL_InvalidateFunction(glTextureParameterf);\n\t\tGL_InvalidateFunction(glTextureParameterfv);\n\t\tGL_InvalidateFunction(glTextureParameteriv);\n\t\tGL_InvalidateFunction(glTextureStorage3D);\n\t\tGL_InvalidateFunction(glTextureStorage2D);\n\t\tGL_InvalidateFunction(glTextureSubImage2D);\n\t\tGL_InvalidateFunction(glTextureSubImage3D);\n\t\tGL_InvalidateFunction(glGetTextureLevelParameterfv);\n\t\tGL_InvalidateFunction(glGetTextureLevelParameterfv);\n\t\tGL_InvalidateFunction(glGetTextureLevelParameteriv);\n\t}\n\n\tif (GL_VersionAtLeast(3, 3)) {\n\t\tglConfig.supported_features |= R_SUPPORT_TEXTURE_ARRAYS;\n\t}\n\n\tif (GL_VersionAtLeast(4, 5)) {\n\t\tGL_LoadOptionalFunction(glGetnTexImage);\n\t} else {\n\t\tGL_InvalidateFunction(glGetnTexImage);\n\t}\n\n\tif (GL_VersionAtLeast(3, 2) || SDL_GL_ExtensionSupported(\"GL_ARB_seamless_cube_map\")) {\n\t\tglConfig.supported_features |= R_SUPPORT_SEAMLESS_CUBEMAPS;\n\t\tglEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);\n\t}\n\n\tif (GL_VersionAtLeast(1, 3)) {\n\t\tglConfig.supported_features |= R_SUPPORT_CUBE_MAPS;\n\t}\n\n\tif (GL_VersionAtLeast(1, 4)) {\n\t\tglConfig.supported_features |= R_SUPPORT_FOG;\n\t}\n\n\tglConfig.max_multisampling_level = 0;\n\tglGetIntegerv(GL_MAX_SAMPLES, &glConfig.max_multisampling_level);\n}\n\nvoid GL_TexSubImage3D(\n\tint unit, texture_ref texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,\n\tGLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid * pixels\n)\n{\n\tGLenum target = GL_TextureTargetFromReference(texture);\n\n\tR_TraceLogAPICall(\"GL_TexSubImage3D(unit=GL_TEXTURE%d, texture=%u)\", unit, GL_TextureNameFromReference(texture));\n\tif (target != GL_TEXTURE_CUBE_MAP && GL_Available(glTextureSubImage3D)) {\n\t\tGL_Procedure(glTextureSubImage3D, GL_TextureNameFromReference(texture), level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels);\n\t}\n\telse {\n\t\trenderer.TextureUnitBind(unit, texture);\n\t\tif (target == GL_TEXTURE_CUBE_MAP) {\n\t\t\tGL_BuiltinProcedure(glTexSubImage2D, \"target=%u, level=%d, xoffset=%d, yoffset=%d, width=%d, height=%d, format=%u, type=%u, pixels=%p\", GL_TEXTURE_CUBE_MAP_POSITIVE_X + zoffset, level, xoffset, yoffset, width, height, format, type, pixels);\n\t\t}\n\t\telse {\n\t\t\tGL_Procedure(glTexSubImage3D, target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels);\n\t\t}\n\t}\n}\n\nvoid GL_TexSubImage2D(\n\tGLenum textureUnit, texture_ref texture, GLint level,\n\tGLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels\n)\n{\n\tR_TraceLogAPICall(\"GL_TexSubImage2D(unit=GL_TEXTURE%d, texture=%u)\", textureUnit - GL_TEXTURE0, GL_TextureNameFromReference(texture));\n\tif (GL_Available(glTextureSubImage2D)) {\n\t\tGL_Procedure(glTextureSubImage2D, GL_TextureNameFromReference(texture), level, xoffset, yoffset, width, height, format, type, pixels);\n\t}\n\telse {\n\t\tGLenum target = GL_TextureTargetFromReference(texture);\n\t\tGL_BindTextureUnit(textureUnit, texture);\n\t\tGL_BuiltinProcedure(glTexSubImage2D, \"target=%u, level=%d, xoffset=%d, yoffset=%d, width=%d, height=%d, format=%u, type=%u, pixels=%p\", target, level, xoffset, yoffset, width, height, format, type, pixels);\n\t}\n}\n\nvoid GL_TexStorage2DMultisample(texture_ref texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations)\n{\n\tint tempWidth = width;\n\tint tempHeight = height;\n\n\tGL_ProcessErrors(va(\"pre-%s\", __func__));\n\n\tR_TextureSizeRoundUp(tempWidth, tempHeight, &width, &height);\n\n\tif (GL_Available(glTextureStorage2DMultisample)) {\n\t\tGL_Procedure(glTextureStorage2DMultisample, GL_TextureNameFromReference(texture), samples, internalformat, width, height, fixedsamplelocations);\n\t}\n\telse {\n\t\tGLenum textureUnit = GL_TEXTURE0;\n\t\tGLenum target = GL_TextureTargetFromReference(texture);\n\n\t\tGL_BindTextureUnit(textureUnit, texture);\n\t\tif (GL_Available(glTexStorage2DMultisample)) {\n\t\t\tGL_Procedure(glTexStorage2DMultisample, target, samples, internalformat, width, height, fixedsamplelocations);\n\t\t}\n\t\telse if (GL_Available(glTexImage2DMultisample)) {\n\t\t\tGL_BindTextureUnit(textureUnit, texture);\n\t\t\tGL_Procedure(glTexImage2DMultisample, target, samples, internalformat, width, height, fixedsamplelocations);\n\t\t}\n\t}\n\tR_TextureSetDimensions(texture, width, height);\n}\n\nvoid GL_TexStorage2D(\n\ttexture_ref texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, qbool is_lightmap\n)\n{\n\tint tempWidth = width;\n\tint tempHeight = height;\n\n\tGL_ProcessErrors(va(\"pre-%s\", __func__));\n\n\tR_TextureSizeRoundUp(tempWidth, tempHeight, &width, &height);\n\n\tif (GL_Available(glTextureStorage2D)) {\n\t\tGL_Procedure(glTextureStorage2D, GL_TextureNameFromReference(texture), levels, internalformat, width, height);\n\t}\n\telse {\n\t\tGLenum textureUnit = GL_TEXTURE0;\n\t\tGLenum target = GL_TextureTargetFromReference(texture);\n\n\t\tGL_BindTextureUnit(textureUnit, texture);\n\t\tif (GL_Available(glTexStorage2D)) {\n\t\t\tGL_Procedure(glTexStorage2D, target, levels, internalformat, width, height);\n\t\t}\n\t\telse {\n\t\t\tint level;\n\t\t\tGLsizei level_width = width;\n\t\t\tGLsizei level_height = height;\n\t\t\tGLenum format = (internalformat == GL_RGBA8 || internalformat == GL_SRGB8_ALPHA8 || internalformat == GL_RGBA16F ? GL_RGBA : GL_RGB);\n\t\t\tGLenum type = (internalformat == GL_RGBA16F || internalformat == GL_RGB16F ? GL_FLOAT : GL_UNSIGNED_BYTE);\n\n\t\t\t// this might be completely useless (we don't upload data anyway) but just to keep all calls to the texture consistent\n\t\t\tformat = ((is_lightmap && GL_Supported(R_SUPPORT_BGRA_LIGHTMAPS)) ? GL_BGRA : format);\n\t\t\ttype = ((is_lightmap && GL_Supported(R_SUPPORT_INT8888R_LIGHTMAPS)) ? GL_UNSIGNED_INT_8_8_8_8_REV : type);\n\n\t\t\tfor (level = 0; level < levels; ++level) {\n\t\t\t\tGL_BuiltinProcedure(glTexImage2D, \"target=%u, level=%d, internalformat=%d, width=%d, height=%d, border=%d, format=%u, type=%u, pixels=%p\", target, level, internalformat, level_width, level_height, 0, format, type, NULL);\n\n\t\t\t\tlevel_width = max(1, level_width / 2);\n\t\t\t\tlevel_height = max(1, level_height / 2);\n\t\t\t}\n\t\t}\n\t}\n\tR_TextureSetDimensions(texture, width, height);\n}\n\nGLenum GL_TexStorage3D(GLenum textureUnit, texture_ref texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, qbool is_lightmap)\n{\n\tif (GL_Available(glTextureStorage3D)) {\n\t\tGL_ProcedureReturnError(glTextureStorage3D, GL_TextureNameFromReference(texture), levels, internalformat, width, height, depth);\n\t}\n\telse if (GL_Available(glTexStorage3D)) {\n\t\tGLenum target = GL_TextureTargetFromReference(texture);\n\t\tGL_BindTextureUnit(textureUnit, texture);\n\t\tGL_ProcedureReturnError(glTexStorage3D, target, levels, internalformat, width, height, depth);\n\t}\n\telse {\n\t\tint level, z;\n\t\tGLenum target = GL_TextureTargetFromReference(texture);\n\t\tGLenum format = (internalformat == GL_RGBA8 || internalformat == GL_SRGB8_ALPHA8 || internalformat == GL_RGBA16F ? GL_RGBA : GL_RGB);\n\t\tGLenum type = (internalformat == GL_RGBA16F || internalformat == GL_RGB16F ? GL_FLOAT : GL_UNSIGNED_BYTE);\n\n\t\t// this might be completely useless (we don't upload data anyway) but just to keep all calls to the texture consistent\n\t\tformat = ((is_lightmap && GL_Supported(R_SUPPORT_BGRA_LIGHTMAPS)) ? GL_BGRA : format);\n\t\ttype = ((is_lightmap && GL_Supported(R_SUPPORT_INT8888R_LIGHTMAPS)) ? GL_UNSIGNED_INT_8_8_8_8_REV : type);\n\n\t\tfor (z = 0; z < depth; ++z) {\n\t\t\tGLsizei level_width = width;\n\t\t\tGLsizei level_height = height;\n\n\t\t\tfor (level = 0; level < levels; ++level) {\n\t\t\t\tGL_ProcedureReturnIfError(glTexImage3D, target, level, internalformat, level_width, level_height, z, 0, format, type, NULL);\n\n\t\t\t\tlevel_width = max(1, level_width / 2);\n\t\t\t\tlevel_height = max(1, level_height / 2);\n\t\t\t}\n\t\t}\n\t\treturn GL_NO_ERROR;\n\t}\n}\n\nvoid GL_TexParameterf(GLenum textureUnit, texture_ref texture, GLenum pname, GLfloat param)\n{\n\tif (GL_Available(glTextureParameterf)) {\n\t\tGL_Procedure(glTextureParameterf, GL_TextureNameFromReference(texture), pname, param);\n\t}\n\telse {\n\t\tGLenum target = GL_TextureTargetFromReference(texture);\n\t\tGL_BindTextureUnit(textureUnit, texture);\n\t\tGL_BuiltinProcedure(glTexParameterf, \"target=%u, pname=%u, param=%f\", target, pname, param);\n\t}\n}\n\nvoid GL_TexParameterfv(GLenum textureUnit, texture_ref texture, GLenum pname, const GLfloat *params)\n{\n\tif (GL_Available(glTextureParameterfv)) {\n\t\tGL_Procedure(glTextureParameterfv, GL_TextureNameFromReference(texture), pname, params);\n\t}\n\telse {\n\t\tGLenum target = GL_TextureTargetFromReference(texture);\n\t\tGL_BindTextureUnit(textureUnit, texture);\n\t\tGL_BuiltinProcedure(glTexParameterfv, \"target=%u, pname=%u, params=%p\", target, pname, params);\n\t}\n}\n\nvoid GL_TexParameteri(GLenum textureUnit, texture_ref texture, GLenum pname, GLint param)\n{\n\tif (GL_Available(glTextureParameteri)) {\n\t\tGL_Procedure(glTextureParameteri, GL_TextureNameFromReference(texture), pname, param);\n\t}\n\telse {\n\t\tGLenum target = GL_TextureTargetFromReference(texture);\n\t\tGL_BindTextureUnit(textureUnit, texture);\n\t\tGL_BuiltinProcedure(glTexParameteri, \"target=%u, pname=%u, param=%d\", target, pname, param);\n\t}\n}\n\nvoid GL_TexParameteriv(GLenum textureUnit, texture_ref texture, GLenum pname, const GLint *params)\n{\n\tif (GL_Available(glTextureParameteriv)) {\n\t\tGL_Procedure(glTextureParameteriv, GL_TextureNameFromReference(texture), pname, params);\n\t}\n\telse {\n\t\tGLenum target = GL_TextureTargetFromReference(texture);\n\t\tGL_BindTextureUnit(textureUnit, texture);\n\t\tGL_BuiltinProcedure(glTexParameteriv, \"target=%u, pname=%u, params=%p\", target, pname, params);\n\t}\n}\n\nvoid GL_CreateTextureNames(GLenum textureUnit, GLenum target, GLsizei n, GLuint* textures)\n{\n\tint i;\n\n\tif (GL_Available(glCreateTextures)) {\n\t\tGL_Procedure(glCreateTextures, target, n, textures);\n\t}\n\telse {\n\t\tGL_BuiltinProcedure(glGenTextures, \"n=%d, textures=%p\", n, textures);\n\t}\n\n\t// glCreateTextures() shouldn't require this, but textures can't be sampled from buggy AMD drivers (reporting x.y.13399) otherwise\n\t// see https://github.com/ezQuake/ezquake-source/issues/416\n\tfor (i = 0; i < n; ++i) {\n\t\tGL_BindTextureToTarget(textureUnit, target, textures[i]);\n\t}\n}\n\nvoid GL_GetTexLevelParameteriv(GLenum textureUnit, texture_ref texture, GLint level, GLenum pname, GLint* params)\n{\n\tif (GL_Available(glGetTextureLevelParameteriv)) {\n\t\tGL_Procedure(glGetTextureLevelParameteriv, GL_TextureNameFromReference(texture), level, pname, params);\n\t}\n\telse {\n\t\tGLenum target = GL_TextureTargetFromReference(texture);\n\t\tGL_BindTextureUnit(textureUnit, texture);\n\t\tGL_BuiltinProcedure(glGetTexLevelParameteriv, \"target=%u, level=%d, pname=%u, params=%p\", target, level, pname, params);\n\t}\n}\n\nvoid GL_GetTexImage(GLenum textureUnit, texture_ref texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* buffer)\n{\n\tGL_PackAlignment(1);\n\tif (GL_Available(glGetTextureImage)) {\n\t\tGL_Procedure(glGetTextureImage, GL_TextureNameFromReference(texture), level, format, type, bufSize, buffer);\n\t}\n\telse {\n\t\tGLenum target = GL_TextureTargetFromReference(texture);\n\t\tGL_BindTextureUnit(textureUnit, texture);\n\t\tif (GL_Available(glGetnTexImage)) {\n\t\t\tGL_Procedure(glGetnTexImage, target, level, format, type, bufSize, buffer);\n\t\t}\n\t\telse {\n\t\t\tGL_BuiltinProcedure(glGetTexImage, \"target=%u, level=%d, format=%u, type=%u, buffer=%p\", target, level, format, type, buffer);\n\t\t}\n\t}\n}\n\nvoid GL_TextureMipmapGenerateWithData(GLenum textureUnit, texture_ref texture, byte* newdata, int width, int height, GLint internal_format)\n{\n\tif (GL_Available(glGenerateTextureMipmap)) {\n\t\tGL_Procedure(glGenerateTextureMipmap, GL_TextureNameFromReference(texture));\n\t}\n\telse {\n\t\tGLenum target = GL_TextureTargetFromReference(texture);\n\t\tGL_BindTextureUnit(textureUnit, texture);\n\t\tif (GL_Available(glGenerateMipmap)) {\n\t\t\tGL_Procedure(glGenerateMipmap, target);\n\t\t}\n\t\telse if (newdata) {\n\t\t\tint miplevel = 0;\n\n\t\t\t// Calculate the mip maps for the images.\n\t\t\twhile (width > 1 || height > 1) {\n\t\t\t\tImage_MipReduce((byte *)newdata, (byte *)newdata, &width, &height, 4);\n\t\t\t\tmiplevel++;\n\t\t\t\tGL_TexSubImage2D(GL_TEXTURE0, texture, miplevel, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, newdata);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid GL_TextureMipmapGenerate(texture_ref texture)\n{\n\tGL_TextureMipmapGenerateWithData(GL_TEXTURE0, texture, NULL, 0, 0, 0);\n}\n\nvoid GL_SamplerSetNearest(unsigned int texture_unit_number)\n{\n\tif (!nearest_sampler) {\n\t\tGL_Procedure(glGenSamplers, 1, &nearest_sampler);\n\n\t\tGL_Procedure(glSamplerParameterf, nearest_sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n\t\tGL_Procedure(glSamplerParameterf, nearest_sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n\t}\n\n\tGL_Procedure(glBindSampler, texture_unit_number, nearest_sampler);\n}\n\nvoid GL_SamplerSetLinear(unsigned int texture_unit_number)\n{\n\tif (!linear_sampler) {\n\t\tGL_Procedure(glGenSamplers, 1, &linear_sampler);\n\n\t\tGL_Procedure(glSamplerParameterf, linear_sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n\t\tGL_Procedure(glSamplerParameterf, linear_sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n\t}\n\n\tGL_Procedure(glBindSampler, texture_unit_number, linear_sampler);\n}\n\nvoid GL_SamplerClear(unsigned int texture_unit_number)\n{\n\tGL_Procedure(glBindSampler, texture_unit_number, 0);\n}\n\nvoid GL_DeleteSamplers(void)\n{\n\tif (GL_Available(glDeleteSamplers)) {\n\t\tGL_Procedure(glDeleteSamplers, 1, &nearest_sampler);\n\t}\n\tnearest_sampler = 0;\n}\n\n// meag: *RGBA is bit ugly, just trying to keep OpenGL enums out of the way for now\nvoid GL_TextureReplaceSubImageRGBA(texture_ref ref, int offsetx, int offsety, int width, int height, byte* buffer)\n{\n\tGL_TexSubImage2D(GL_TEXTURE0, ref, 0, offsetx, offsety, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);\n}\n"
  },
  {
    "path": "src/gl_texture_internal.h",
    "content": "/*\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef GL_TEXTURE_INTERNAL_HEADER\n#define GL_TEXTURE_INTERNAL_HEADER\n\n#include \"r_texture.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n\n// --------------\n// Texture functions\n// --------------\n\n// Replaces top-level of a texture - if dimensions don't match then texture is reloaded\nvoid GL_TextureReplace2D(\n\tGLenum textureUnit, GLenum target, texture_ref* ref, GLint internalformat,\n\tGLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, GLvoid *pixels\n);\nconst char* GL_TextureIdentifierByGLReference(GLuint texnum);\nvoid GL_SelectTexture(GLenum target);\n\nvoid GL_CreateTexturesWithIdentifier(r_texture_type_id type, int n, texture_ref* references, const char* identifier);\nvoid GL_TexStorage2D(texture_ref reference, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, qbool is_lightmap);\nGLenum GL_TexStorage3D(GLenum textureUnit, texture_ref reference, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, qbool is_lightmap);\nvoid GL_TexStorage2DMultisample(texture_ref texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations);\nvoid GL_TextureMipmapGenerate(texture_ref reference);\nvoid GL_TextureMipmapGenerateWithData(GLenum textureUnit, texture_ref texture, byte* newdata, int width, int height, GLint internal_format);\n\nvoid GL_TexParameterf(GLenum textureUnit, texture_ref reference, GLenum pname, GLfloat param);\nvoid GL_TexParameterfv(GLenum textureUnit, texture_ref reference, GLenum pname, const GLfloat *params);\nvoid GL_TexParameteri(GLenum textureUnit, texture_ref reference, GLenum pname, GLint param);\nvoid GL_TexParameteriv(GLenum textureUnit, texture_ref reference, GLenum pname, const GLint *params);\nvoid GL_GetTexLevelParameteriv(GLenum textureUnit, texture_ref reference, GLint level, GLenum pname, GLint* params);\n\nvoid GL_TexSubImage2D(GLenum textureUnit, texture_ref reference, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels);\nvoid GL_TexSubImage3D(int textureUnit, texture_ref reference, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid * pixels);\nvoid GL_GetTexImage(GLenum textureUnit, texture_ref reference, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* buffer);\n\nvoid GL_BindTextureUnit(GLuint unit, texture_ref reference);\nqbool GL_EnsureTextureUnitBound(int unit, texture_ref reference);\n\nvoid GL_CreateTextureNames(GLenum textureUnit, GLenum target, GLsizei n, GLuint* textures);\nvoid GL_BindTextureToTarget(GLenum textureUnit, GLenum targetType, GLuint name);\n\n#endif // GL_TEXTURE_INTERNAL_HEADER\n"
  },
  {
    "path": "src/glc_aliasmodel.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// Alias model (.mdl) rendering, classic (immediate mode) GL only\n// Most code taken from gl_rmain.c\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"vx_stuff.h\"\n#include \"vx_vertexlights.h\"\n#include \"utils.h\"\n#include \"qsound.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"rulesets.h\"\n#include \"teamplay.h\"\n#include \"r_aliasmodel.h\"\n#include \"crc.h\"\n#include \"r_matrix.h\"\n#include \"r_state.h\"\n#include \"r_texture.h\"\n#include \"r_vao.h\"\n#include \"glc_vao.h\"\n#include \"r_brushmodel.h\" // R_PointIsUnderwater only\n#include \"r_buffers.h\"\n#include \"glc_local.h\"\n#include \"glc_matrix.h\"\n#include \"glc_state.h\"\n#include \"r_program.h\"\n#include \"tr_types.h\"\n#include \"r_renderer.h\"\n\nstatic void GLC_DrawAliasModelShadowDrawCall(entity_t* ent, vec3_t shadevector);\nstatic void GLC_DrawCachedAliasOutlineFrame(model_t* model, GLenum primitive, int firstVert, int verts, qbool weaponmodel);\n\nextern float r_avertexnormals[NUMVERTEXNORMALS][3];\n\nextern cvar_t    r_lerpframes;\nextern cvar_t    gl_outline;\nextern cvar_t    gl_program_aliasmodels;\nextern cvar_t    gl_smoothmodels;\n\nextern float     r_framelerp;\n\n// Temporary r_lerpframes fix, unless we go for shaders-in-classic...\n//   After all models loaded, this needs to be allocated enough space to hold a complete pose of verts\n//   We fill it in with the lerped positions, then render model from there\n// Ugly, but don't think there's another way to do this in fixed pipeline\ntypedef struct glc_aliasmodel_vert_s {\n\tfloat position[3];\n\tfloat texture_coords[2];\n\tbyte color[4];\n} glc_aliasmodel_vert_t;\n\nstatic glc_aliasmodel_vert_t* temp_aliasmodel_buffer;\nstatic int temp_aliasmodel_buffer_size;\n\nstatic void GLC_ConfigureAliasModelState(void)\n{\n\textern cvar_t gl_vbo_clientmemory;\n\n\tif (gl_program_aliasmodels.integer) {\n\t\tR_GenVertexArray(vao_aliasmodel);\n\t\tGLC_VAOSetVertexBuffer(vao_aliasmodel, r_buffer_aliasmodel_vertex_data);\n\t\tGLC_VAOEnableVertexPointer(vao_aliasmodel, 3, GL_FLOAT, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, position));\n\t\tGLC_VAOEnableNormalPointer(vao_aliasmodel, 3, GL_FLOAT, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, normal));\n\t\tGLC_VAOEnableTextureCoordPointer(vao_aliasmodel, 0, 2, GL_FLOAT, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, texture_coords));\n\t\tGLC_VAOEnableTextureCoordPointer(vao_aliasmodel, 1, 3, GL_FLOAT, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, direction));\n\t\tGLC_VAOEnableCustomAttribute(vao_aliasmodel, 0, r_program_attribute_aliasmodel_std_glc_flags, 1, GL_INT, false, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, flags));\n\t\tGLC_VAOEnableCustomAttribute(vao_aliasmodel, 1, r_program_attribute_aliasmodel_shell_glc_flags, 1, GL_INT, false, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, flags));\n\t\tGLC_VAOEnableCustomAttribute(vao_aliasmodel, 2, r_program_attribute_aliasmodel_shadow_glc_flags, 1, GL_INT, false, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, flags));\n\t\tGLC_VAOEnableCustomAttribute(vao_aliasmodel, 3, r_program_attribute_aliasmodel_outline_glc_flags, 1, GL_INT, false, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, flags));\n\t}\n\telse {\n\t\tif (gl_vbo_clientmemory.integer) {\n\t\t\tR_GenVertexArray(vao_aliasmodel);\n\t\t\tGLC_VAOEnableVertexPointer(vao_aliasmodel, 3, GL_FLOAT, sizeof(temp_aliasmodel_buffer[0]), &temp_aliasmodel_buffer[0].position[0]);\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao_aliasmodel, 0, 2, GL_FLOAT, sizeof(temp_aliasmodel_buffer[0]), &temp_aliasmodel_buffer[0].texture_coords[0]);\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao_aliasmodel, 1, 2, GL_FLOAT, sizeof(temp_aliasmodel_buffer[0]), &temp_aliasmodel_buffer[0].texture_coords[0]);\n\t\t\tGLC_VAOEnableColorPointer(vao_aliasmodel, 4, GL_UNSIGNED_BYTE, sizeof(temp_aliasmodel_buffer[0]), &temp_aliasmodel_buffer[0].color[0]);\n\t\t}\n\t\telse {\n\t\t\tR_GenVertexArray(vao_aliasmodel);\n\t\t\tGLC_VAOSetVertexBuffer(vao_aliasmodel, r_buffer_aliasmodel_glc_pose_data);\n\t\t\tGLC_VAOEnableVertexPointer(vao_aliasmodel, 3, GL_FLOAT, sizeof(temp_aliasmodel_buffer[0]), VBO_FIELDOFFSET(glc_aliasmodel_vert_t, position));\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao_aliasmodel, 0, 2, GL_FLOAT, sizeof(temp_aliasmodel_buffer[0]), VBO_FIELDOFFSET(glc_aliasmodel_vert_t, texture_coords));\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao_aliasmodel, 1, 2, GL_FLOAT, sizeof(temp_aliasmodel_buffer[0]), VBO_FIELDOFFSET(glc_aliasmodel_vert_t, texture_coords));\n\t\t\tGLC_VAOEnableColorPointer(vao_aliasmodel, 4, GL_UNSIGNED_BYTE, sizeof(temp_aliasmodel_buffer[0]), VBO_FIELDOFFSET(glc_aliasmodel_vert_t, color));\n\t\t}\n\n\t\tR_GenVertexArray(vao_aliasmodel_powerupshell);\n\t\tGLC_VAOEnableVertexPointer(vao_aliasmodel_powerupshell, 3, GL_FLOAT, sizeof(temp_aliasmodel_buffer[0]), &temp_aliasmodel_buffer[0].position);\n\t\tGLC_VAOEnableTextureCoordPointer(vao_aliasmodel_powerupshell, 0, 2, GL_FLOAT, sizeof(temp_aliasmodel_buffer[0]), &temp_aliasmodel_buffer[0].texture_coords[0]);\n\t}\n}\n\nvoid GLC_AllocateAliasPoseBuffer(void)\n{\n\tif (!gl_program_aliasmodels.integer) {\n\t\tint max_verts = 0;\n\t\tint i;\n\n\t\tfor (i = 1; i < MAX_MODELS; ++i) {\n\t\t\tmodel_t* mod = cl.model_precache[i];\n\n\t\t\tif (mod && mod->type == mod_alias) {\n\t\t\t\taliashdr_t* hdr = (aliashdr_t *)Mod_Extradata(mod);\n\n\t\t\t\tif (hdr) {\n\t\t\t\t\tmax_verts = max(max_verts, hdr->vertsPerPose);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (i = 0; i < MAX_VWEP_MODELS; i++) {\n\t\t\tmodel_t* mod = cl.vw_model_precache[i];\n\n\t\t\tif (mod && mod->type == mod_alias) {\n\t\t\t\taliashdr_t* hdr = (aliashdr_t *)Mod_Extradata(mod);\n\n\t\t\t\tif (hdr) {\n\t\t\t\t\tmax_verts = max(max_verts, hdr->vertsPerPose);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (i = 0; i < custom_model_count; ++i) {\n\t\t\tmodel_t* mod = Mod_CustomModel(i, false);\n\n\t\t\tif (mod && mod->type == mod_alias) {\n\t\t\t\taliashdr_t* hdr = (aliashdr_t *)Mod_Extradata(mod);\n\n\t\t\t\tif (hdr) {\n\t\t\t\t\tmax_verts = max(max_verts, hdr->vertsPerPose);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (temp_aliasmodel_buffer == NULL || temp_aliasmodel_buffer_size < max_verts) {\n\t\t\tQ_free(temp_aliasmodel_buffer);\n\t\t\ttemp_aliasmodel_buffer = Q_malloc(sizeof(temp_aliasmodel_buffer[0]) * max_verts);\n\t\t\ttemp_aliasmodel_buffer_size = max_verts;\n\t\t}\n\n\t\tif (R_BufferReferenceIsValid(r_buffer_aliasmodel_glc_pose_data)) {\n\t\t\tbuffers.Resize(r_buffer_aliasmodel_glc_pose_data, sizeof(temp_aliasmodel_buffer[0]) * max_verts, NULL);\n\t\t}\n\t\telse {\n\t\t\tbuffers.Create(r_buffer_aliasmodel_glc_pose_data, buffertype_vertex, \"glc-alias-pose\", sizeof(temp_aliasmodel_buffer[0]) * max_verts, NULL, bufferusage_reuse_per_frame);\n\t\t}\n\t}\n\n\tGLC_ConfigureAliasModelState();\n}\n\nvoid GLC_FreeAliasPoseBuffer(void)\n{\n\tQ_free(temp_aliasmodel_buffer);\n\ttemp_aliasmodel_buffer_size = 0;\n}\n\nstatic void GLC_AliasModelLightPoint(float color[4], entity_t* ent, vbo_model_vert_t* verts1, vbo_model_vert_t* verts2, float lerpfrac)\n{\n\tfloat l;\n\n\t// VULT VERTEX LIGHTING\n\tif (amf_lighting_vertex.integer && !ent->full_light) {\n\t\tvec3_t lc;\n\n\t\tl = VLight_LerpLight(verts1->lightnormalindex, verts2->lightnormalindex, lerpfrac, ent->angles[0], ent->angles[1]);\n\t\tl *= (ent->shadelight + ent->ambientlight) / 256.0;\n\t\tl = min(l, 1);\n\n\t\tif (amf_lighting_colour.integer) {\n\t\t\tint i;\n\t\t\tfor (i = 0; i < 3; i++) {\n\t\t\t\tlc[i] = l * ent->lightcolor[i] / 255;\n\t\t\t\tlc[i] = min(lc[i], 1);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tVectorSet(lc, l, l, l);\n\t\t}\n\n\t\tif (ent->r_modelcolor[0] < 0) {\n\t\t\t// normal color\n\t\t\tVectorCopy(lc, color);\n\t\t}\n\t\telse {\n\t\t\tcolor[0] = ent->r_modelcolor[0] * lc[0];\n\t\t\tcolor[1] = ent->r_modelcolor[1] * lc[1];\n\t\t\tcolor[2] = ent->r_modelcolor[2] * lc[2];\n\t\t}\n\t}\n\telse {\n\t\tl = FloatInterpolate(shadedots[verts1->lightnormalindex], lerpfrac, shadedots[verts2->lightnormalindex]) / 127.0;\n\t\tl = (l * ent->shadelight + ent->ambientlight) / 256.0;\n\t\tl = min(l, 1);\n\n\t\tif (ent->custom_model == NULL) {\n\t\t\tif (ent->r_modelcolor[0] < 0) {\n\t\t\t\tcolor[0] = color[1] = color[2] = l;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcolor[0] = ent->r_modelcolor[0] * l;\n\t\t\t\tcolor[1] = ent->r_modelcolor[1] * l;\n\t\t\t\tcolor[2] = ent->r_modelcolor[2] * l;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tcolor[0] = ent->custom_model->color_cvar.color[0] / 255.0f;\n\t\t\tcolor[1] = ent->custom_model->color_cvar.color[1] / 255.0f;\n\t\t\tcolor[2] = ent->custom_model->color_cvar.color[2] / 255.0f;\n\t\t}\n\t}\n\n\tcolor[0] *= ent->r_modelalpha;\n\tcolor[1] *= ent->r_modelalpha;\n\tcolor[2] *= ent->r_modelalpha;\n\tcolor[3] = ent->r_modelalpha;\n}\n\n#define DRAWFLAGS_CAUSTICS     1\n#define DRAWFLAGS_TEXTURED     2\n#define DRAWFLAGS_FULLBRIGHT   4\n#define DRAWFLAGS_MUZZLEHACK   8\n//#define DRAWFLAGS_FLATSHADING  16     // Disabled until we can specify GLSL 1.3 dynamically, MESA drivers very strict\n#define DRAWFLAGS_MAXIMUM      (DRAWFLAGS_CAUSTICS | DRAWFLAGS_TEXTURED | DRAWFLAGS_FULLBRIGHT | DRAWFLAGS_MUZZLEHACK /* | DRAWFLAGS_FLATSHADING*/)\n\nint GLC_AliasModelSubProgramIndex(qbool textured, qbool fullbright, qbool caustics, qbool muzzlehack)\n{\n\t// gl_smoothmodels disabled for now\n\treturn\n\t\t(textured ? DRAWFLAGS_TEXTURED : 0) | \n\t\t(fullbright ? DRAWFLAGS_FULLBRIGHT : 0) | /*(gl_smoothmodels.integer ? 0 : DRAWFLAGS_FLATSHADING)) |*/\n\t\t(caustics ? DRAWFLAGS_CAUSTICS : 0) | \n\t\t(muzzlehack ? DRAWFLAGS_MUZZLEHACK : 0);\n}\n\nqbool GLC_AliasModelStandardCompileSpecific(int subprogram_index)\n{\n\tint flags = subprogram_index;\n\n\tR_ProgramSetSubProgram(r_program_aliasmodel_std_glc, subprogram_index);\n\tif (R_ProgramRecompileNeeded(r_program_aliasmodel_std_glc, flags)) {\n\t\tchar included_definitions[512];\n\n\t\tincluded_definitions[0] = '\\0';\n\t\tif (subprogram_index & DRAWFLAGS_TEXTURED) {\n\t\t\tstrlcat(included_definitions, \"#define TEXTURING_ENABLED\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (subprogram_index & DRAWFLAGS_FULLBRIGHT) {\n\t\t\tstrlcat(included_definitions, \"#define FULLBRIGHT_MODELS\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (subprogram_index & DRAWFLAGS_CAUSTICS) {\n\t\t\tstrlcat(included_definitions, \"#define DRAW_CAUSTIC_TEXTURES\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (subprogram_index & DRAWFLAGS_MUZZLEHACK) {\n\t\t\tstrlcat(included_definitions, \"#define EZQ_ALIASMODEL_MUZZLEHACK\\n\", sizeof(included_definitions));\n\t\t}\n#ifdef DRAWFLAGS_FLATSHADING\n\t\tif (subprogram_index & DRAWFLAGS_FLATSHADING) {\n\t\t\tstrlcat(included_definitions, \"#define EZQ_ALIASMODEL_FLATSHADING\\n\", sizeof(included_definitions));\n\t\t}\n#endif\n\t\tR_ProgramCompileWithInclude(r_program_aliasmodel_std_glc, included_definitions);\n\t\tR_ProgramUniform1i(r_program_uniform_aliasmodel_std_glc_texSampler, 0);\n\t\tR_ProgramUniform1i(r_program_uniform_aliasmodel_std_glc_causticsSampler, 1);\n\t\tR_ProgramSetCustomOptions(r_program_aliasmodel_std_glc, flags);\n\t}\n\n\tR_ProgramSetStandardUniforms(r_program_aliasmodel_std_glc);\n\n\treturn R_ProgramReady(r_program_aliasmodel_std_glc);\n}\n\n// Only called from vid system startup, not from rendering loop\nqbool GLC_AliasModelStandardCompile(void)\n{\n\tint i;\n\n\tfor (i = 0; i < DRAWFLAGS_MAXIMUM; ++i) {\n\t\tGLC_AliasModelStandardCompileSpecific(i);\n\t}\n\n\treturn true;\n}\n\nqbool GLC_AliasModelShadowCompile(void)\n{\n\tif (R_ProgramRecompileNeeded(r_program_aliasmodel_shadow_glc, 0)) {\n\t\tR_ProgramCompile(r_program_aliasmodel_shadow_glc);\n\t}\n\n\tR_ProgramSetStandardUniforms(r_program_aliasmodel_shadow_glc);\n\n\treturn R_ProgramReady(r_program_aliasmodel_shadow_glc);\n}\n\nqbool GLC_AliasModelShellCompile(void)\n{\n\textern cvar_t r_lerpmuzzlehack;\n\tint flags = (r_lerpmuzzlehack.integer ? DRAWFLAGS_MUZZLEHACK : 0);\n\n\tif (R_ProgramRecompileNeeded(r_program_aliasmodel_shell_glc, flags)) {\n\t\tchar included_definitions[512];\n\n\t\tincluded_definitions[0] = '\\0';\n\t\tif (flags & DRAWFLAGS_MUZZLEHACK) {\n\t\t\tstrlcat(included_definitions, \"#define EZQ_ALIASMODEL_MUZZLEHACK\\n\", sizeof(included_definitions));\n\t\t}\n\n\t\tR_ProgramCompileWithInclude(r_program_aliasmodel_shell_glc, included_definitions);\n\t\tR_ProgramUniform1i(r_program_uniform_aliasmodel_shell_glc_texSampler, 0);\n\t\tR_ProgramSetCustomOptions(r_program_aliasmodel_shell_glc, flags);\n\t}\n\n\tR_ProgramSetStandardUniforms(r_program_aliasmodel_shell_glc);\n\n\treturn R_ProgramReady(r_program_aliasmodel_shell_glc);\n}\n\nqbool GLC_AliasModelOutlineCompile(void)\n{\n\textern cvar_t r_lerpmuzzlehack;\n\tint flags = (r_lerpmuzzlehack.integer ? DRAWFLAGS_MUZZLEHACK : 0);\n\n\tif (R_ProgramRecompileNeeded(r_program_aliasmodel_outline_glc, flags)) {\n\t\tchar included_definitions[512];\n\n\t\tstrlcpy(included_definitions, \"#define BACKFACE_PASS\\n\", sizeof(included_definitions));\n\t\tif (flags & DRAWFLAGS_MUZZLEHACK) {\n\t\t\tstrlcat(included_definitions, \"#define EZQ_ALIASMODEL_MUZZLEHACK\\n\", sizeof(included_definitions));\n\t\t}\n\n\t\tR_ProgramCompileWithInclude(r_program_aliasmodel_outline_glc, included_definitions);\n\t\tR_ProgramSetCustomOptions(r_program_aliasmodel_outline_glc, flags);\n\t}\n\n\treturn R_ProgramReady(r_program_aliasmodel_outline_glc);\n}\n\nstatic void GLC_DrawAliasFrameImpl_Program(entity_t* ent, model_t* model, int pose1, int pose2, texture_ref texture, texture_ref fb_texture, qbool outline, int effects, int render_effects, float lerpfrac)\n{\n\textern cvar_t r_lerpmuzzlehack, gl_program_aliasmodels;\n\taliashdr_t* paliashdr = (aliashdr_t*)Mod_Extradata(model);\n\tfloat color[4];\n\tqbool invalidate_texture;\n\tfloat angle_radians = -ent->angles[YAW] * M_PI / 180.0;\n\tvec3_t angle_vector = { cos(angle_radians), sin(angle_radians), 1 };\n\tint firstVert = model->vbo_start + pose1 * paliashdr->vertsPerPose;\n\tint subprogram;\n\n\tif (outline) {\n\t\tif (buffers.supported && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_AliasModelOutlineCompile()) {\n\t\t\tR_ProgramUse(r_program_aliasmodel_outline_glc);\n\t\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_outline_glc_lerpFraction, lerpfrac);\n\t\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_outline_glc_outlineScale, ent->outlineScale);\n\t\t\tGLC_DrawCachedAliasOutlineFrame(model, GL_TRIANGLES, firstVert, paliashdr->vertsPerPose, ent->renderfx & RF_WEAPONMODEL);\n\t\t}\n\t}\n\telse {\n\t\tVectorNormalize(angle_vector);\n\t\tR_AliasModelColor(ent, color, &invalidate_texture);\n\n\t\tsubprogram = GLC_AliasModelSubProgramIndex(\n\t\t\t!invalidate_texture,\n\t\t\tgl_fb_models.integer && (ent->ambientlight == 4096 && ent->shadelight == 4096),\n\t\t\tr_refdef2.drawCaustics && (render_effects & RF_CAUSTICS),\n\t\t\tr_lerpmuzzlehack.integer && (render_effects & RF_WEAPONMODEL)\n\t\t);\n\n\t\tif (buffers.supported && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_AliasModelStandardCompileSpecific(subprogram)) {\n\t\t\tR_ProgramUse(r_program_aliasmodel_std_glc);\n\t\t\tR_ProgramUniform3fv(r_program_uniform_aliasmodel_std_glc_angleVector, angle_vector);\n\t\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_std_glc_shadelight, ent->shadelight / 256.0f);\n\t\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_std_glc_ambientlight, ent->ambientlight / 256.0f);\n\t\t\tR_ProgramUniform1i(r_program_uniform_aliasmodel_std_glc_fsTextureEnabled, invalidate_texture ? 0 : 1);\n\t\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_std_glc_fsMinLumaMix, 1.0f - (ent->full_light ? bound(0, gl_fb_models.integer, 1) : 0));\n\t\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_std_glc_fsCausticEffects, render_effects & RF_CAUSTICS ? 1 : 0);\n\t\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_std_glc_lerpFraction, lerpfrac);\n\t\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_std_glc_time, cl.time);\n\n\t\t\tif (ent->r_modelalpha < 1) {\n\t\t\t\tGLC_StateBeginDrawAliasZPass(render_effects & RF_WEAPONMODEL);\n\t\t\t\tGL_DrawArrays(GL_TRIANGLES, firstVert, paliashdr->vertsPerPose);\n\t\t\t}\n\t\t\tGLC_StateBeginDrawAliasFrameProgram(texture, underwatertexture, render_effects, ent->custom_model, ent->r_modelalpha, false);\n\t\t\tR_CustomColor(color[0], color[1], color[2], color[3]);\n\t\t\tGL_DrawArrays(GL_TRIANGLES, firstVert, paliashdr->vertsPerPose);\n\t\t\tif (render_effects & RF_CAUSTICS) {\n\t\t\t\tGLC_StateEndUnderwaterAliasModelCaustics();\n\t\t\t}\n\t\t\tR_ProgramUse(r_program_none);\n\t\t}\n\t}\n}\n\nstatic void GLC_DrawAliasFrameImpl_Immediate_Cache(aliashdr_t* paliashdr, entity_t* ent, vbo_model_vert_t* verts1, vbo_model_vert_t* verts2, float lerpfracDefault, qbool limit_lerp, qbool outline, qbool mtex)\n{\n\tint i;\n\tvec3_t interpolated_verts;\n\n\tfor (i = 0; i < paliashdr->vertsPerPose; ++i, ++verts1, ++verts2) {\n\t\tfloat color[4];\n\t\tfloat s = verts1->texture_coords[0];\n\t\tfloat t = verts1->texture_coords[1];\n\t\tfloat lerpfrac = lerpfracDefault;\n\n\t\tif (limit_lerp && !VectorL2Compare(verts1->position, verts2->position, ALIASMODEL_MAX_LERP_DISTANCE)) {\n\t\t\tlerpfrac = 1;\n\t\t}\n\n\t\tGLC_AliasModelLightPoint(color, ent, verts1, verts2, lerpfrac);\n\t\tif (outline) {\n\t\t\tvec3_t v1, v2;\n\t\t\tVectorMA(verts1->position, ent->outlineScale, verts1->normal, v1);\n\t\t\tVectorMA(verts2->position, ent->outlineScale, verts2->normal, v2);\n\t\t\tVectorInterpolate(v1, lerpfrac, v2, interpolated_verts);\n\t\t}\n\t\telse {\n\t\t\tVectorInterpolate(verts1->position, lerpfrac, verts2->position, interpolated_verts);\n\t\t}\n\t\ttemp_aliasmodel_buffer[i].texture_coords[0] = s;\n\t\ttemp_aliasmodel_buffer[i].texture_coords[1] = t;\n\t\ttemp_aliasmodel_buffer[i].color[0] = color[0] * 255;\n\t\ttemp_aliasmodel_buffer[i].color[1] = color[1] * 255;\n\t\ttemp_aliasmodel_buffer[i].color[2] = color[2] * 255;\n\t\ttemp_aliasmodel_buffer[i].color[3] = color[3] * 255;\n\t\tVectorCopy(interpolated_verts, temp_aliasmodel_buffer[i].position);\n\t}\n}\n\nstatic void GLC_DrawAliasFrameImpl_Immediate_TrueImmediate(aliashdr_t* paliashdr, qbool mtex)\n{\n\tint i;\n\n\tGLC_Begin(GL_TRIANGLES);\n\tfor (i = 0; i < paliashdr->vertsPerPose; ++i) {\n\t\t// texture coordinates come from the draw list\n\t\tfloat s = temp_aliasmodel_buffer[i].texture_coords[0];\n\t\tfloat t = temp_aliasmodel_buffer[i].texture_coords[1];\n\t\tbyte* color = temp_aliasmodel_buffer[i].color;\n\n\t\tif (mtex) {\n\t\t\tGLC_MultiTexCoord2f(GL_TEXTURE0, s, t);\n\t\t\tGLC_MultiTexCoord2f(GL_TEXTURE1, s, t);\n\t\t}\n\t\telse {\n\t\t\tglTexCoord2f(s, t);\n\t\t}\n\t\tR_CustomColor4ubv(color);\n\t\tGLC_Vertex3fv(temp_aliasmodel_buffer[i].position);\n\t}\n\tGLC_End();\n}\n\nstatic void GLC_DrawAliasFrameImpl_Immediate(entity_t* ent, model_t* model, int pose1, int pose2, texture_ref texture, texture_ref fb_texture, qbool outline, int effects, int render_effects, float lerpfracDefault)\n{\n\textern cvar_t r_lerpmuzzlehack;\n\n\taliashdr_t* paliashdr = (aliashdr_t*)Mod_Extradata(model);\n\tqbool cache = buffers.supported && temp_aliasmodel_buffer_size >= paliashdr->poseverts;\n\tqbool mtex = R_TextureReferenceIsValid(fb_texture) && gl_mtexable;\n\tvbo_model_vert_t* vbo_buffer = (vbo_model_vert_t*)model->temp_vbo_buffer;\n\tvbo_model_vert_t* verts1, * verts2;\n\tqbool limit_lerp = r_lerpmuzzlehack.integer && (ent->model->renderfx & RF_LIMITLERP);\n\tqbool alpha_blend = (render_effects & RF_ALPHABLEND) || ent->r_modelalpha < 1;\n\tqbool is_weapon_model = (ent->renderfx & RF_WEAPONMODEL);\n\n\tverts1 = &vbo_buffer[pose1 * paliashdr->poseverts];\n\tverts2 = &vbo_buffer[pose2 * paliashdr->poseverts];\n\n\tGLC_DrawAliasFrameImpl_Immediate_Cache(paliashdr, ent, verts1, verts2, lerpfracDefault, limit_lerp, outline, mtex);\n\tif (cache) {\n\t\tbuffers.Update(r_buffer_aliasmodel_glc_pose_data, sizeof(temp_aliasmodel_buffer[0]) * paliashdr->vertsPerPose, temp_aliasmodel_buffer);\n\t}\n\n\tR_ProgramUse(r_program_none);\n\tif (alpha_blend && !outline) {\n\t\tGLC_StateBeginDrawAliasZPass(is_weapon_model);\n\t\tif (cache) {\n\t\t\tGL_DrawArrays(GL_TRIANGLES, 0, paliashdr->vertsPerPose);\n\t\t}\n\t\telse {\n\t\t\tGLC_DrawAliasFrameImpl_Immediate_TrueImmediate(paliashdr, mtex);\n\t\t}\n\t}\n\n\tif (outline) {\n\t\tGLC_StateBeginAliasOutlineFrame(is_weapon_model);\n\t}\n\telse if (render_effects & RF_CAUSTICS) {\n\t\tGLC_StateBeginUnderwaterAliasModelCaustics(texture, fb_texture);\n\t}\n\telse {\n\t\tGLC_StateBeginDrawAliasFrame(texture, fb_texture, mtex, alpha_blend, ent->custom_model, is_weapon_model);\n\t}\n\n\tif (cache) {\n\t\tGL_DrawArrays(GL_TRIANGLES, 0, paliashdr->vertsPerPose);\n\t}\n\telse {\n\t\tGLC_DrawAliasFrameImpl_Immediate_TrueImmediate(paliashdr, mtex);\n\t}\n\n\tif (render_effects & RF_CAUSTICS) {\n\t\tGLC_StateEndUnderwaterAliasModelCaustics();\n\t}\n}\n\nvoid GLC_DrawAliasFrame(entity_t* ent, model_t* model, int pose1, int pose2, texture_ref texture, texture_ref fb_texture, qbool outline, int effects, int render_effects, float lerpfrac)\n{\n\tqbool draw_caustics = r_refdef2.drawCaustics && gl_mtexable && R_PointIsUnderwater(ent->origin);\n\n\tif (gl_program_aliasmodels.integer) {\n\t\tGLC_DrawAliasFrameImpl_Program(ent, model, pose1, pose2, texture, fb_texture, outline, effects, render_effects | (draw_caustics ? RF_CAUSTICS : 0), lerpfrac);\n\t}\n\telse if (outline) {\n\t\tGLC_DrawAliasFrameImpl_Immediate(ent, model, pose1, pose2, texture, fb_texture, outline, effects, render_effects, lerpfrac);\n\t}\n\telse {\n\t\tif (R_TextureReferenceIsValid(fb_texture) && gl_mtexable) {\n\t\t\tGLC_DrawAliasFrameImpl_Immediate(ent, model, pose1, pose2, texture, fb_texture, outline, effects, render_effects, lerpfrac);\n\t\t}\n\t\telse {\n\t\t\tGLC_DrawAliasFrameImpl_Immediate(ent, model, pose1, pose2, texture, null_texture_reference, outline, effects, render_effects, lerpfrac);\n\n\t\t\tif (R_TextureReferenceIsValid(fb_texture)) {\n\t\t\t\tGLC_DrawAliasFrameImpl_Immediate(ent, model, pose1, pose2, fb_texture, null_texture_reference, false, effects, render_effects | RF_ALPHABLEND, lerpfrac);\n\t\t\t}\n\t\t}\n\n\t\tif (draw_caustics) {\n\t\t\tGLC_DrawAliasFrameImpl_Immediate(ent, model, pose1, pose2, texture, underwatertexture, false, 0, RF_ALPHABLEND | RF_CAUSTICS, lerpfrac);\n\t\t}\n\t}\n}\n\n// This can be used with program or immediate mode\nstatic void GLC_DrawCachedAliasOutlineFrame(model_t* model, GLenum primitive, int firstVert, int verts, qbool weaponmodel)\n{\n\tGLC_StateBeginAliasOutlineFrame(weaponmodel);\n\n\tGL_DrawArrays(primitive, firstVert, verts);\n}\n\nvoid GLC_PowerupShellColor(int layer_no, int effects, float* color)\n{\n\t// set color: alpha so we can see colour underneath still\n\tfloat base_level = bound(0, (layer_no == 0 ? gl_powerupshells_base1level.value : gl_powerupshells_base2level.value), 1);\n\tfloat effect_level = bound(0, (layer_no == 0 ? gl_powerupshells_effect1level.value : gl_powerupshells_effect2level.value), 1);\n\tfloat shell_alpha = bound(0, gl_powerupshells.value, 1);\n\n\tcolor[0] = (base_level + ((effects & EF_RED) ? effect_level : 0)) * shell_alpha;\n\tcolor[1] = (base_level + ((effects & EF_GREEN) ? effect_level : 0)) * shell_alpha;\n\tcolor[2] = (base_level + ((effects & EF_BLUE) ? effect_level : 0)) * shell_alpha;\n\tcolor[3] = shell_alpha;\n}\n\nvoid GLC_SetPowerupShellColor(int layer_no, int effects)\n{\n\tfloat color[4];\n\n\tGLC_PowerupShellColor(layer_no, effects, color);\n\n\tR_CustomColor(color[0], color[1], color[2], color[3]);\n}\n\nconst float* GLC_PowerupShell_ScrollParams(void)\n{\n\treturn r_refdef2.powerup_scroll_params;\n}\n\nstatic void GLC_DrawPowerupShell_Program(entity_t* ent, int pose1, float fraclerp)\n{\n\tif (buffers.supported && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_AliasModelShellCompile()) {\n\t\taliashdr_t* paliashdr = (aliashdr_t*)Mod_Extradata(ent->model);\n\t\tint firstVert = ent->model->vbo_start + pose1 * paliashdr->vertsPerPose;\n\t\tfloat color1[4], color2[4];\n\n\t\tGLC_PowerupShellColor(0, ent->effects, color1);\n\t\tGLC_PowerupShellColor(1, ent->effects, color2);\n\n\t\tR_ProgramUse(r_program_aliasmodel_shell_glc);\n\t\tGLC_StateBeginAliasPowerupShell(ent->renderfx & RF_WEAPONMODEL);\n\t\tGLC_BindVertexArrayAttributes(vao_aliasmodel);\n\t\tR_ProgramUniform4fv(r_program_uniform_aliasmodel_shell_glc_fsBaseColor1, color1);\n\t\tR_ProgramUniform4fv(r_program_uniform_aliasmodel_shell_glc_fsBaseColor2, color2);\n\t\tR_ProgramUniform4fv(r_program_uniform_aliasmodel_shell_glc_scroll, GLC_PowerupShell_ScrollParams());\n\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_shell_glc_lerpFraction, fraclerp);\n\t\tGL_DrawArrays(GL_TRIANGLES, firstVert, paliashdr->vertsPerPose);\n\t\tR_ProgramUse(r_program_none);\n\t}\n}\n\nstatic void GLC_DrawPowerupShell_Immediate(entity_t* ent, int pose1, int pose2, float fraclerp)\n{\n\tez_trivertx_t* verts1;\n\tez_trivertx_t* verts2;\n\tint layer_no;\n\tint *order, count;\n\tvec3_t v1, v2, v;\n\taliashdr_t* paliashdr = (aliashdr_t*)Mod_Extradata(ent->model);\n\n\tqbool cache = buffers.supported && temp_aliasmodel_buffer_size >= paliashdr->poseverts;\n\tint position;\n\tconst float* scroll = GLC_PowerupShell_ScrollParams();\n\n\tR_ProgramUse(r_program_none);\n\tGLC_StateBeginAliasPowerupShell(ent->renderfx & RF_WEAPONMODEL);\n\tfor (position = 0, layer_no = 0; layer_no <= 1; ++layer_no) {\n\t\t// get the vertex count and primitive type\n\t\torder = (int *)((byte *)paliashdr + paliashdr->commands);\n\t\tverts1 = verts2 = (ez_trivertx_t *)((byte *)paliashdr + paliashdr->posedata);\n\t\tverts1 += pose1 * paliashdr->poseverts;\n\t\tverts2 += pose2 * paliashdr->poseverts;\n\n\t\tfor (;;) {\n\t\t\tGLenum drawMode = GL_TRIANGLE_STRIP;\n\n\t\t\tcount = *order++;\n\t\t\tif (!count) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (count < 0) {\n\t\t\t\tcount = -count;\n\t\t\t\tdrawMode = GL_TRIANGLE_FAN;\n\t\t\t}\n\n\t\t\tif (!cache) {\n\t\t\t\tGLC_SetPowerupShellColor(layer_no, ent->effects);\n\t\t\t\tGLC_Begin(drawMode);\n\t\t\t}\n\n\t\t\tdo {\n\t\t\t\tfloat s = ((float *)order)[0] * 2.0f + scroll[layer_no * 2];\n\t\t\t\tfloat t = ((float *)order)[1] * 2.0f + scroll[layer_no * 2 + 1];\n\n\t\t\t\torder += 2;\n\n\t\t\t\tVectorMA(verts1->v, 0.5f, r_avertexnormals[verts1->lightnormalindex], v1);\n\t\t\t\tVectorMA(verts2->v, 0.5f, r_avertexnormals[verts2->lightnormalindex], v2);\n\t\t\t\tVectorInterpolate(v1, fraclerp, v2, v);\n\n\t\t\t\tif (cache) {\n\t\t\t\t\ttemp_aliasmodel_buffer[position].texture_coords[0] = s;\n\t\t\t\t\ttemp_aliasmodel_buffer[position].texture_coords[1] = t;\n\t\t\t\t\tVectorCopy(v, temp_aliasmodel_buffer[position].position);\n\t\t\t\t\tposition++;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tglTexCoord2f(s, t);\n\t\t\t\t\tGLC_Vertex3f(v[0], v[1], v[2]);\n\t\t\t\t}\n\n\t\t\t\tverts1++;\n\t\t\t\tverts2++;\n\t\t\t} while (--count);\n\n\t\t\tif (cache) {\n\t\t\t\tint i;\n\n\t\t\t\tR_BindVertexArray(vao_aliasmodel_powerupshell);\n\t\t\t\tGLC_SetPowerupShellColor(0, ent->effects);\n\t\t\t\tGL_DrawArrays(drawMode, 0, position);\n\n\t\t\t\t// And quickly update texture-coordinates and run again...\n\t\t\t\tGLC_SetPowerupShellColor(1, ent->effects);\n\t\t\t\tfor (i = 0; i < position; ++i) {\n\t\t\t\t\ttemp_aliasmodel_buffer[i].texture_coords[0] += scroll[2] - scroll[0];\n\t\t\t\t\ttemp_aliasmodel_buffer[i].texture_coords[1] += scroll[3] - scroll[1];\n\t\t\t\t}\n\t\t\t\tGL_DrawArrays(drawMode, 0, position);\n\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tGLC_End();\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void GLC_DrawPowerupShell(entity_t* ent, maliasframedesc_t *oldframe, maliasframedesc_t *frame)\n{\n\tint pose1, pose2;\n\tfloat lerp;\n\n\tR_AliasModelDeterminePoses(oldframe, frame, &pose1, &pose2, &lerp);\n\n\tif (gl_program_aliasmodels.integer) {\n\t\tGLC_DrawPowerupShell_Program(ent, pose1, lerp);\n\t}\n\telse {\n\t\tGLC_DrawPowerupShell_Immediate(ent, pose1, pose2, lerp);\n\t}\n}\n\nvoid GLC_DrawAliasModelShadow(entity_t* ent)\n{\n\tfloat theta;\n\tfloat oldMatrix[16];\n\tstatic float shadescale = 0;\n\tvec3_t shadevector;\n\n\tif (!shadescale) {\n\t\tshadescale = 1 / sqrt(2);\n\t}\n\ttheta = -ent->angles[1] / 180 * M_PI;\n\n\tVectorSet(shadevector, cos(theta) * shadescale, sin(theta) * shadescale, shadescale);\n\n\tR_PushModelviewMatrix(oldMatrix);\n\tR_TranslateModelview(ent->origin[0], ent->origin[1], ent->origin[2]);\n\tR_RotateModelview(ent->angles[1], 0, 0, 1);\n\n\tGLC_StateBeginAliasModelShadow();\n\tGLC_DrawAliasModelShadowDrawCall(ent, shadevector);\n\tR_PopModelviewMatrix(oldMatrix);\n}\n\nstatic void GLC_DrawAliasModelShadowDrawCall_Program(entity_t* ent, int pose1, float lerpfrac, float lheight, vec3_t shadevector)\n{\n\tif (buffers.supported && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_AliasModelShadowCompile()) {\n\t\tconst aliashdr_t* paliashdr = (aliashdr_t *)Mod_Extradata(ent->model); // locate the proper data\n\t\tint firstVert = ent->model->vbo_start + pose1 * paliashdr->vertsPerPose;\n\n\t\tR_ProgramUse(r_program_aliasmodel_shadow_glc);\n\t\tGLC_BindVertexArrayAttributes(vao_aliasmodel);\n\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_shadow_glc_lerpFraction, lerpfrac);\n\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_shadow_glc_lheight, lheight);\n\t\tR_ProgramUniform2fv(r_program_uniform_aliasmodel_shadow_glc_shadevector, shadevector);\n\t\tGL_DrawArrays(GL_TRIANGLES, firstVert, paliashdr->vertsPerPose);\n\t\tR_ProgramUse(r_program_none);\n\t}\n}\n\nstatic void GLC_DrawAliasModelShadowDrawCall_Immediate(entity_t* ent, int pose1, float lerpfrac, float lheight, vec3_t shadevector)\n{\n\tconst aliashdr_t* paliashdr = (aliashdr_t *)Mod_Extradata(ent->model); // locate the proper data\n\tint *order, count;\n\tvec3_t point;\n\tez_trivertx_t *verts;\n\tfloat height = (1 - lheight);\n\n\tverts = (ez_trivertx_t *)((byte *)paliashdr + paliashdr->posedata);\n\tverts += pose1 * paliashdr->poseverts;\n\torder = (int *)((byte *)paliashdr + paliashdr->commands);\n\n\tR_ProgramUse(r_program_none);\n\twhile ((count = *order++)) {\n\t\t// get the vertex count and primitive type\n\t\tif (count < 0) {\n\t\t\tcount = -count;\n\t\t\tGLC_Begin(GL_TRIANGLE_FAN);\n\t\t}\n\t\telse {\n\t\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\t}\n\n\t\tdo {\n\t\t\t//no texture for shadows\n\t\t\torder += 2;\n\n\t\t\t// normals and vertexes come from the frame list\n\t\t\tpoint[0] = verts->v[0] + paliashdr->scale_origin[0];\n\t\t\tpoint[1] = verts->v[1] + paliashdr->scale_origin[1];\n\t\t\tpoint[2] = verts->v[2] + paliashdr->scale_origin[2];\n\n\t\t\tpoint[0] -= shadevector[0] * (point[2] + lheight);\n\t\t\tpoint[1] -= shadevector[1] * (point[2] + lheight);\n\t\t\tpoint[2] = height;\n\n\t\t\tGLC_Vertex3fv(point);\n\n\t\t\tverts++;\n\t\t} while (--count);\n\n\t\tGLC_End();\n\t}\n}\n\nstatic void GLC_DrawAliasModelShadowDrawCall(entity_t* ent, vec3_t shadevector)\n{\n\tconst aliashdr_t* paliashdr = (aliashdr_t *)Mod_Extradata(ent->model); // locate the proper data\n\tconst maliasframedesc_t* frame = &paliashdr->frames[ent->frame];\n\tconst maliasframedesc_t* oldframe = &paliashdr->frames[ent->oldframe];\n\tfloat lheight = ent->origin[2] - ent->lightspot[2];\n\tint pose1, pose2;\n\tfloat lerpfrac;\n\n\tR_AliasModelDeterminePoses(oldframe, frame, &pose1, &pose2, &lerpfrac);\n\n\tif (gl_program_aliasmodels.integer) {\n\t\tGLC_DrawAliasModelShadowDrawCall_Program(ent, pose1, lerpfrac, lheight, shadevector);\n\t}\n\telse {\n\t\tGLC_DrawAliasModelShadowDrawCall_Immediate(ent, pose1, lerpfrac, lheight, shadevector);\n\t}\n}\n\nvoid GLC_DrawAliasModelPowerupShell(entity_t *ent)\n{\n\taliashdr_t* paliashdr = (aliashdr_t *)Mod_Extradata(ent->model); // locate the proper data\n\tmaliasframedesc_t *oldframe, *frame;\n\tfloat oldMatrix[16];\n\n\tif (!(ent->effects & (EF_RED | EF_GREEN | EF_BLUE)) || !R_TextureReferenceIsValid(shelltexture)) {\n\t\treturn;\n\t}\n\n\t// FIXME: This is all common with R_DrawAliasModel(), and if passed there, don't need to be run here...\n\tif (!Ruleset_AllowPowerupShell(ent->model) || R_FilterEntity(ent)) {\n\t\treturn;\n\t}\n\n\tent->frame = bound(0, ent->frame, paliashdr->numframes - 1);\n\tent->oldframe = bound(0, ent->oldframe, paliashdr->numframes - 1);\n\n\tframe = &paliashdr->frames[ent->frame];\n\toldframe = &paliashdr->frames[ent->oldframe];\n\n\tr_framelerp = 1.0;\n\tif (r_lerpframes.integer && ent->framelerp >= 0 && ent->oldframe != ent->frame) {\n\t\tr_framelerp = min(ent->framelerp, 1);\n\t}\n\n\tif (R_CullAliasModel(ent, oldframe, frame)) {\n\t\treturn;\n\t}\n\n\tframeStats.classic.polycount[polyTypeAliasModel] += paliashdr->numtris;\n\n\tR_TraceEnterRegion(va(\"%s(%s)\", __func__, ent->model->name), true);\n\n\t// FIXME: think need put it after caustics\n\tR_PushModelviewMatrix(oldMatrix);\n\tR_StateBeginDrawAliasModel(ent, paliashdr);\n\tGLC_DrawPowerupShell(ent, oldframe, frame);\n\tR_PopModelviewMatrix(oldMatrix);\n\n\tR_TraceLeaveRegion(true);\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_aliasmodel_mesh.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#if 0\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_local.h\"\n#include \"r_aliasmodel.h\"\n\n/*\n=================================================================\n\nALIAS MODEL DISPLAY LIST GENERATION\n\n=================================================================\n*/\n\nstatic byte\tused[8192];\n\n// the command list holds counts and s/t values that are valid for\n// every frame\nstatic int commands[8192];\nstatic int numcommands;\n\n// all frames will have their vertexes rearranged and expanded\n// so they are in the order expected by the command list\nstatic int vertexorder[8192];\nstatic int numorder;\n\nstatic int allverts, alltris;\n\nstatic int stripverts[128];\nstatic int striptris[128];\nstatic int stripcount;\n\n/*\n================\nStripLength\n================\n*/\nstatic int StripLength(int starttri, int startv)\n{\n\tint m1, m2, j, k;\n\tmtriangle_t *last, *check;\n\n\tused[starttri] = 2;\n\n\tlast = &triangles[starttri];\n\n\tstripverts[0] = last->vertindex[(startv) % 3];\n\tstripverts[1] = last->vertindex[(startv + 1) % 3];\n\tstripverts[2] = last->vertindex[(startv + 2) % 3];\n\n\tstriptris[0] = starttri;\n\tstripcount = 1;\n\n\tm1 = last->vertindex[(startv + 2) % 3];\n\tm2 = last->vertindex[(startv + 1) % 3];\n\n\t// look for a matching triangle\nnexttri:\n\tfor (j = starttri + 1, check = &triangles[starttri + 1]; j < pheader->numtris; j++, check++) {\n\t\tif (check->facesfront != last->facesfront) {\n\t\t\tcontinue;\n\t\t}\n\t\tfor (k = 0; k < 3; k++) {\n\t\t\tif (check->vertindex[k] != m1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (check->vertindex[(k + 1) % 3] != m2) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// this is the next part of the fan\n\n\t\t\t// if we can't use this triangle, this tristrip is done\n\t\t\tif (used[j]) {\n\t\t\t\tgoto done;\n\t\t\t}\n\n\t\t\t// the new edge\n\t\t\tif (stripcount & 1) {\n\t\t\t\tm2 = check->vertindex[(k + 2) % 3];\n\t\t\t}\n\t\t\telse {\n\t\t\t\tm1 = check->vertindex[(k + 2) % 3];\n\t\t\t}\n\n\t\t\tstripverts[stripcount + 2] = check->vertindex[(k + 2) % 3];\n\t\t\tstriptris[stripcount] = j;\n\t\t\tstripcount++;\n\n\t\t\tused[j] = 2;\n\t\t\tgoto nexttri;\n\t\t}\n\t}\n\ndone:\n\t// clear the temp used flags\n\tfor (j = starttri + 1; j < pheader->numtris; j++) {\n\t\tif (used[j] == 2) {\n\t\t\tused[j] = 0;\n\t\t}\n\t}\n\n\treturn stripcount;\n}\n\n#if 0\n/*\n===========\nFanLength\n===========\n*/\nstatic int FanLength(int starttri, int startv)\n{\n\tint m1, m2, j, k;\n\tmtriangle_t\t*last, *check;\n\n\tused[starttri] = 2;\n\n\tlast = &triangles[starttri];\n\n\tstripverts[0] = last->vertindex[(startv) % 3];\n\tstripverts[1] = last->vertindex[(startv + 1) % 3];\n\tstripverts[2] = last->vertindex[(startv + 2) % 3];\n\n\tstriptris[0] = starttri;\n\tstripcount = 1;\n\n\tm1 = last->vertindex[(startv + 0) % 3];\n\tm2 = last->vertindex[(startv + 2) % 3];\n\n\t// look for a matching triangle\nnexttri:\n\tfor (j = starttri + 1, check = &triangles[starttri + 1]; j < pheader->numtris; j++, check++) {\n\t\tif (check->facesfront != last->facesfront) {\n\t\t\tcontinue;\n\t\t}\n\t\tfor (k = 0; k < 3; k++) {\n\t\t\tif (check->vertindex[k] != m1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (check->vertindex[(k + 1) % 3] != m2) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// this is the next part of the fan\n\n\t\t\t// if we can't use this triangle, this tristrip is done\n\t\t\tif (used[j]) {\n\t\t\t\tgoto done;\n\t\t\t}\n\n\t\t\t// the new edge\n\t\t\tm2 = check->vertindex[(k + 2) % 3];\n\n\t\t\tstripverts[stripcount + 2] = m2;\n\t\t\tstriptris[stripcount] = j;\n\t\t\tstripcount++;\n\n\t\t\tused[j] = 2;\n\t\t\tgoto nexttri;\n\t\t}\n\t}\ndone:\n\n\t// clear the temp used flags\n\tfor (j = starttri + 1; j < pheader->numtris; j++) {\n\t\tif (used[j] == 2) {\n\t\t\tused[j] = 0;\n\t\t}\n\t}\n\n\treturn stripcount;\n}\n#endif\n\n/*\n================\nBuildTris\n\nGenerate a list of trifans or strips\nfor the model, which holds for all frames\n================\n*/\nstatic void BuildTris(void)\n{\n\tint\t\ti, j, k;\n\tint\t\tstartv;\n\tfloat\ts, t;\n\tint\t\tlen, bestlen;\n\tint\t\tbestverts[1024];\n\tint\t\tbesttris[1024];\n\tqbool duplicate = false;\n\tfloat previous_s = 0, previous_t = 0;\n\n\t//\n\t// build tristrips\n\t//\n\tnumorder = 0;\n\tnumcommands = 0;\n\tmemset(used, 0, sizeof(used));\n\tmemset(commands, 0, sizeof(commands));\n\n\tfor (i = 0; i < pheader->numtris; i++) {\n\t\t// pick an unused triangle and start the trifan\n\t\tif (used[i]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tbestlen = 0;\n\n\t\tfor (startv = 0; startv < 3; startv++) {\n\t\t\tlen = StripLength(i, startv);\n\n\t\t\tif (len > bestlen) {\n\t\t\t\tbestlen = len;\n\t\t\t\tfor (j = 0; j < bestlen + 2; j++) {\n\t\t\t\t\tbestverts[j] = stripverts[j];\n\t\t\t\t}\n\t\t\t\tfor (j = 0; j < bestlen; j++) {\n\t\t\t\t\tbesttris[j] = striptris[j];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// mark the tris on the best strip as used\n\t\tfor (j = 0; j < bestlen; j++) {\n\t\t\tused[besttris[j]] = 1;\n\t\t}\n\n\t\tduplicate = false;\n\t\tcommands[0] += bestlen + 2;\n\t\tif (numcommands) {\n\t\t\t// duplicate previous vertex to keep triangle strip running\n\t\t\tvertexorder[numorder] = vertexorder[numorder - 1];\n\t\t\t++numorder;\n\n\t\t\t// duplicate s & t coordinates\n\t\t\t*(float *)&commands[numcommands++] = previous_s;\n\t\t\t*(float *)&commands[numcommands++] = previous_t;\n\t\t\tcommands[0] += 2;\n\n\t\t\t// Duplicate the current one too\n\t\t\tduplicate = true;\n\t\t}\n\t\telse {\n\t\t\tduplicate = false;\n\t\t\t++numcommands;\n\t\t}\n\n\t\tfor (j = 0; j < bestlen + 2; j++) {\n\t\t\t// emit a vertex into the reorder buffer\n\t\t\tk = bestverts[j];\n\t\t\tvertexorder[numorder++] = k;\n\n\t\t\t// emit s/t coords into the commands stream\n\t\t\ts = stverts[k].s;\n\t\t\tt = stverts[k].t;\n\t\t\tif (!triangles[besttris[0]].facesfront && stverts[k].onseam) {\n\t\t\t\ts += pheader->skinwidth / 2;\t// on back side\n\t\t\t}\n\t\t\ts = (s + 0.5) / pheader->skinwidth;\n\t\t\tt = (t + 0.5) / pheader->skinheight;\n\n\t\t\tif (duplicate) {\n\t\t\t\t*(float *)&commands[numcommands++] = s;\n\t\t\t\t*(float *)&commands[numcommands++] = t;\n\t\t\t\tvertexorder[numorder++] = k;\n\n\t\t\t\tif (numorder % 2 == 0) {\n\t\t\t\t\t*(float *)&commands[numcommands++] = s;\n\t\t\t\t\t*(float *)&commands[numcommands++] = t;\n\t\t\t\t\tvertexorder[numorder++] = k;\n\t\t\t\t\t++commands[0];\n\t\t\t\t}\n\t\t\t}\n\t\t\tprevious_s = *(float *)&commands[numcommands++] = s;\n\t\t\tprevious_t = *(float *)&commands[numcommands++] = t;\n\n\t\t\tduplicate = false;\n\t\t}\n\t}\n\n\tcommands[numcommands++] = 0;\t\t// end of list marker\n\n\tCom_DPrintf(\"%3i tri %3i vert %3i cmd\\n\", pheader->numtris, numorder, numcommands);\n\n\tallverts += numorder;\n\talltris += pheader->numtris;\n}\n\nvoid GLC_PrepareAliasModel(model_t* m, aliashdr_t* hdr)\n{\n\tGL_PrepareAliasModel(m, hdr);\n}\n\n#endif // #if 0\n"
  },
  {
    "path": "src/glc_bloom.c",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n/**\n\n\tGL Bloom\n\n\tPorted by Cokeman, June 2007\n\tlast edit:\n\t$Id: gl_bloom.c,v 1.5 2007-09-13 14:49:30 disconn3ct Exp $\n\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n// glc_bloom.c: 2D lighting post process effect (immediate-mode OpenGL only)\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"r_texture.h\"\n#include \"r_matrix.h\"\n#include \"r_renderer.h\"\n#include \"glc_local.h\"\n#include \"tr_types.h\"\n\n/*\n==============================================================================\n\n\t\t\t\t\t\tLIGHT BLOOMS\n\n==============================================================================\n*/\n\nstatic float Diamond8x[8][8] = {\n\t{0.0f, 0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f, 0.0f},\n\t{0.0f, 0.0f, 0.2f, 0.3f, 0.3f, 0.2f, 0.0f, 0.0f},\n\t{0.0f, 0.2f, 0.4f, 0.6f, 0.6f, 0.4f, 0.2f, 0.0f},\n\t{0.1f, 0.3f, 0.6f, 0.9f, 0.9f, 0.6f, 0.3f, 0.1f},\n\t{0.1f, 0.3f, 0.6f, 0.9f, 0.9f, 0.6f, 0.3f, 0.1f},\n\t{0.0f, 0.2f, 0.4f, 0.6f, 0.6f, 0.4f, 0.2f, 0.0f},\n\t{0.0f, 0.0f, 0.2f, 0.3f, 0.3f, 0.2f, 0.0f, 0.0f},\n\t{0.0f, 0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f, 0.0f} };\n\nstatic float Diamond6x[6][6] = {\n\t{0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f},\n\t{0.0f, 0.3f, 0.5f, 0.5f, 0.3f, 0.0f},\n\t{0.1f, 0.5f, 0.9f, 0.9f, 0.5f, 0.1f},\n\t{0.1f, 0.5f, 0.9f, 0.9f, 0.5f, 0.1f},\n\t{0.0f, 0.3f, 0.5f, 0.5f, 0.3f, 0.0f},\n\t{0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f} };\n\nstatic float Diamond4x[4][4] = {\n\t{0.3f, 0.4f, 0.4f, 0.3f},\n\t{0.4f, 0.9f, 0.9f, 0.4f},\n\t{0.4f, 0.9f, 0.9f, 0.4f},\n\t{0.3f, 0.4f, 0.4f, 0.3f} };\n\nstatic int BLOOM_SIZE;\n\ncvar_t      r_bloom = { \"r_bloom\", \"0\", true };\ncvar_t      r_bloom_alpha = { \"r_bloom_alpha\", \"0.5\", true };\ncvar_t      r_bloom_diamond_size = { \"r_bloom_diamond_size\", \"8\", true };\ncvar_t      r_bloom_intensity = { \"r_bloom_intensity\", \"1\", true };\ncvar_t      r_bloom_darken = { \"r_bloom_darken\", \"3\", true };\ncvar_t      r_bloom_sample_size = { \"r_bloom_sample_size\", \"256\", true };\ncvar_t      r_bloom_fast_sample = { \"r_bloom_fast_sample\", \"0\", true };\n\ntexture_ref r_bloomscreentexture;\ntexture_ref r_bloomeffecttexture;\ntexture_ref r_bloombackuptexture;\ntexture_ref r_bloomdownsamplingtexture;\n\nstatic int      r_screendownsamplingtexture_size;\nstatic int      screen_texture_width, screen_texture_height;\nstatic int      r_screenbackuptexture_size;\n\n// Current refdef size:\nstatic int  curView_x;\nstatic int  curView_y;\nstatic int  curView_width;\nstatic int  curView_height;\n\n// Texture coordinates of screen data inside screentexture.\nstatic float screenText_tcw;\nstatic float screenText_tch;\n\nstatic int  sample_width;\nstatic int  sample_height;\n\n// Texture coordinates of adjusted textures.\nstatic float sampleText_tcw;\nstatic float sampleText_tch;\n\n// This macro is in sample size workspace coordinates.\n#define GLC_Bloom_SamplePass( xpos, ypos )                         \\\n\tGLC_Begin(GL_QUADS);                                             \\\n\tglTexCoord2f(  0,                      sampleText_tch);        \\\n\tGLC_Vertex2f(    xpos,                   ypos);                  \\\n\tglTexCoord2f(  0,                      0);                     \\\n\tGLC_Vertex2f(    xpos,                   ypos+sample_height);    \\\n\tglTexCoord2f(  sampleText_tcw,         0);                     \\\n\tGLC_Vertex2f(    xpos+sample_width,      ypos+sample_height);    \\\n\tglTexCoord2f(  sampleText_tcw,         sampleText_tch);        \\\n\tGLC_Vertex2f(    xpos+sample_width,      ypos);                  \\\n\tGLC_End();\n\n#define GLC_Bloom_Quad( x, y, width, height, textwidth, textheight ) \\\n\tGLC_Begin(GL_QUADS);                                             \\\n\tglTexCoord2f(  0,          textheight);                        \\\n\tGLC_Vertex2f(    x,          y);                                 \\\n\tglTexCoord2f(  0,          0);                                 \\\n\tGLC_Vertex2f(    x,          y+height);                          \\\n\tglTexCoord2f(  textwidth,  0);                                 \\\n\tGLC_Vertex2f(    x+width,    y+height);                          \\\n\tglTexCoord2f(  textwidth,  textheight);                        \\\n\tGLC_Vertex2f(    x+width,    y);                                 \\\n\tGLC_End();\n\n// Meag: #define DEBUG_BLOOM, if bloom enabled and you set `developer 1`,\n//   for a single frame it will output copies of the intermediate textures\n//   then set developer 0 again.\n#ifdef DEBUG_BLOOM\nstatic void GLC_Bloom_DebugTexture(texture_ref tex, const char* filename)\n{\n\tif (developer.integer) {\n\t\tint buffer_size = 4 * R_TextureWidth(tex) * R_TextureHeight(tex);\n\t\tbyte* buffer = Q_malloc(buffer_size);\n\t\tscr_sshot_target_t* sshot_params = Q_malloc(sizeof(scr_sshot_target_t));\n\n\t\trenderer.TextureGet(tex, buffer_size, buffer, 3);\n\n\t\tsshot_params->buffer = buffer;\n\t\tsshot_params->freeMemory = true;\n\t\tstrlcpy(sshot_params->fileName, filename, sizeof(sshot_params->fileName));\n\t\tsshot_params->format = IMAGE_JPEG;\n\t\tsshot_params->width = R_TextureWidth(tex);\n\t\tsshot_params->height = R_TextureHeight(tex);\n\t\tSCR_ScreenshotWrite(sshot_params);\n\t}\n}\n#else\n#define GLC_Bloom_DebugTexture(tex, filename)\n#endif\n\n//=================\n// GLC_Bloom_InitBackUpTexture\n// =================\nstatic void GLC_Bloom_InitBackUpTexture(int width, int height)\n{\n\tunsigned char *data;\n\n\tdata = (unsigned char *)Q_calloc(width * height, sizeof(int));\n\n\tr_screenbackuptexture_size = width;\n\n\tr_bloombackuptexture = R_LoadTexture(\"***r_bloombackuptexture***\", width, height, data, 0, 4);\n\n\tQ_free(data);\n}\n\n// =================\n// GLC_Bloom_InitEffectTexture\n// =================\nstatic void GLC_Bloom_InitEffectTexture(void)\n{\n\tunsigned char *data;\n\tfloat bloomsizecheck;\n\n\tif (r_bloom_sample_size.value < 32) {\n\t\tCvar_SetValue(&r_bloom_sample_size, 32);\n\t}\n\n\t// Make sure bloom size is a power of 2.\n\tBLOOM_SIZE = r_bloom_sample_size.value;\n\tbloomsizecheck = (float)BLOOM_SIZE;\n\n\twhile (bloomsizecheck > 1.0f) {\n\t\tbloomsizecheck /= 2.0f;\n\t}\n\n\tif (bloomsizecheck != 1.0f) {\n\t\tBLOOM_SIZE = 32;\n\n\t\twhile (BLOOM_SIZE < r_bloom_sample_size.value) {\n\t\t\tBLOOM_SIZE *= 2;\n\t\t}\n\t}\n\n\t// Make sure bloom size doesn't have stupid values.\n\tif (BLOOM_SIZE > screen_texture_width || BLOOM_SIZE > screen_texture_height) {\n\t\tBLOOM_SIZE = min(screen_texture_width, screen_texture_height);\n\t}\n\n\tif (BLOOM_SIZE != r_bloom_sample_size.value) {\n\t\tCvar_SetValue(&r_bloom_sample_size, BLOOM_SIZE);\n\t}\n\n\tdata = (unsigned char *)Q_calloc(BLOOM_SIZE * BLOOM_SIZE, sizeof(int));\n\n\tr_bloomeffecttexture = R_LoadTexture(\"***r_bloomeffecttexture***\", BLOOM_SIZE, BLOOM_SIZE, data, 0, 4);\n\n\tQ_free(data);\n}\n\n// =================\n// GLC_Bloom_InitTextures\n// =================\nstatic void GLC_Bloom_InitTextures(void)\n{\n\tunsigned char *data;\n\tint maxtexsize = glConfig.gl_max_size_default;\n\tsize_t size;\n\n\t// Find closer power of 2 to screen size.\n\tfor (screen_texture_width = 1; screen_texture_width < glwidth; screen_texture_width *= 2);\n\tfor (screen_texture_height = 1; screen_texture_height < glheight; screen_texture_height *= 2);\n\n\t// Disable blooms if we can't handle a texture of that size.\n\tif (screen_texture_width > maxtexsize || screen_texture_height > maxtexsize) {\n\t\tscreen_texture_width = screen_texture_height = 0;\n\t\tCvar_SetValue(&r_bloom, 0);\n\t\tCom_Printf(\"WARNING: 'R_InitBloomScreenTexture' too high resolution for Light Bloom. Effect disabled\\n\");\n\t\treturn;\n\t}\n\n\t// Init the screen texture.\n\tsize = screen_texture_width * screen_texture_height * sizeof(int);\n\tdata = Q_malloc(size);\n\tmemset(data, 255, size);\n\n\tif (!R_TextureReferenceIsValid(r_bloomscreentexture) || R_TextureWidth(r_bloomscreentexture) != screen_texture_width || R_TextureHeight(r_bloomscreentexture) != screen_texture_height) {\n\t\tif (R_TextureReferenceIsValid(r_bloomscreentexture)) {\n\t\t\trenderer.TextureDelete(r_bloomscreentexture);\n\t\t}\n\t\tr_bloomscreentexture = R_LoadTexture(\"bloom:screentexture\", screen_texture_width, screen_texture_height, data, TEX_NOCOMPRESS | TEX_NOSCALE | TEX_NO_TEXTUREMODE, 4);\n\t\trenderer.TextureSetFiltering(r_bloomscreentexture, texture_minification_nearest, texture_magnification_nearest);\n\t}\n\n\tQ_free(data);\n\n\t// Validate bloom size and init the bloom effect texture.\n\tGLC_Bloom_InitEffectTexture();\n\n\t// If screensize is more than 2x the bloom effect texture, set up for stepped downsampling.\n\tR_TextureReferenceInvalidate(r_bloomdownsamplingtexture);\n\tr_screendownsamplingtexture_size = 0;\n\tif (glwidth > (BLOOM_SIZE * 2) && !r_bloom_fast_sample.value) {\n\t\tr_screendownsamplingtexture_size = (int)(BLOOM_SIZE * 2);\n\t\tdata = Q_calloc(r_screendownsamplingtexture_size * r_screendownsamplingtexture_size, sizeof(int));\n\t\tr_bloomdownsamplingtexture = R_LoadTexture(\"***r_bloomdownsamplingtexture***\", r_screendownsamplingtexture_size, r_screendownsamplingtexture_size, data, 0, 4);\n\t\tQ_free(data);\n\t}\n\n\t// Init the screen backup texture.\n\tif (r_screendownsamplingtexture_size) {\n\t\tGLC_Bloom_InitBackUpTexture(r_screendownsamplingtexture_size, r_screendownsamplingtexture_size);\n\t}\n\telse {\n\t\tGLC_Bloom_InitBackUpTexture(BLOOM_SIZE, BLOOM_SIZE);\n\t}\n}\n\n// =================\n// R_InitBloomTextures\n// =================\nvoid GLC_InitBloomTextures(void)\n{\n\tBLOOM_SIZE = 0;\n\tif (!r_bloom.value) {\n\t\treturn;\n\t}\n\n\t// This came from a vid_restart, where none of the textures are valid any more.\n\tR_TextureReferenceInvalidate(r_bloomscreentexture);\n\tR_TextureReferenceInvalidate(r_bloomeffecttexture);\n\tR_TextureReferenceInvalidate(r_bloombackuptexture);\n\tR_TextureReferenceInvalidate(r_bloomdownsamplingtexture);\n\n\tGLC_Bloom_InitTextures();\n}\n\n// =================\n// R_Bloom_DrawEffect\n// =================\nstatic void GLC_Bloom_DrawEffect(void)\n{\n\tGLC_StateBeginBloomDraw(r_bloomeffecttexture);\n\n\tGLC_Begin(GL_QUADS);\n\tglTexCoord2f(0, sampleText_tch);\n\tGLC_Vertex2f(curView_x, curView_y);\n\tglTexCoord2f(0, 0);\n\tGLC_Vertex2f(curView_x, curView_y + curView_height);\n\tglTexCoord2f(sampleText_tcw, 0);\n\tGLC_Vertex2f(curView_x + curView_width, curView_y + curView_height);\n\tglTexCoord2f(sampleText_tcw, sampleText_tch);\n\tGLC_Vertex2f(curView_x + curView_width, curView_y);\n\tGLC_End();\n}\n\n// =================\n// R_Bloom_GeneratexDiamonds\n//=================\nstatic void GLC_Bloom_GeneratexDiamonds(void)\n{\n\tint         i, j;\n\tstatic float intensity;\n\n\t// Setup sample size workspace\n\tR_Viewport(0, 0, sample_width, sample_height);\n\tR_OrthographicProjection(0, sample_width, sample_height, 0, -10, 100);\n\tR_IdentityModelView();\n\n\t// Copy small scene into r_bloomeffecttexture.\n\trenderer.TextureUnitBind(0, r_bloomeffecttexture);\n\tglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, sample_width, sample_height);\n\n\t// Start modifying the small scene corner.\n\n\t// Darkening passes\n\tif (r_bloom_darken.value) {\n\t\tR_ApplyRenderingState(r_state_postprocess_bloom_darkenpass);\n\n\t\tfor (i = 0; i < r_bloom_darken.integer; i++) {\n\t\t\tGLC_Bloom_SamplePass(0, 0);\n\t\t}\n\t\tglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, sample_width, sample_height);\n\t\tGLC_Bloom_DebugTexture(r_bloomeffecttexture, \"./qw/bloom-darkenedpass.jpg\");\n\t}\n\n\t// Bluring passes.\n\tR_ApplyRenderingState(r_state_postprocess_bloom_blurpass);\n\tR_CustomColor(1.0f, 1.0f, 1.0f, 1.0f);\n\n\tif (r_bloom_diamond_size.value > 7 || r_bloom_diamond_size.value <= 3) {\n\t\tif (r_bloom_diamond_size.integer != 8) {\n\t\t\tCvar_SetValue(&r_bloom_diamond_size, 8);\n\t\t}\n\n\t\tfor (i = 0; i < r_bloom_diamond_size.integer; i++) {\n\t\t\tfor (j = 0; j < r_bloom_diamond_size.integer; j++) {\n\t\t\t\tintensity = r_bloom_intensity.value * 0.3 * Diamond8x[i][j];\n\t\t\t\tif (intensity < 0.01f) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tR_CustomColor(intensity, intensity, intensity, 1.0);\n\t\t\t\tGLC_Bloom_SamplePass(i - 4, j - 4);\n\t\t\t}\n\t\t}\n\t}\n\telse if (r_bloom_diamond_size.integer > 5) {\n\t\tif (r_bloom_diamond_size.integer != 6) {\n\t\t\tCvar_SetValue(&r_bloom_diamond_size, 6);\n\t\t}\n\n\t\tfor (i = 0; i < r_bloom_diamond_size.integer; i++) {\n\t\t\tfor (j = 0; j < r_bloom_diamond_size.integer; j++) {\n\t\t\t\tintensity = r_bloom_intensity.value * 0.5 * Diamond6x[i][j];\n\t\t\t\tif (intensity < 0.01f) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tR_CustomColor(intensity, intensity, intensity, 1.0);\n\t\t\t\tGLC_Bloom_SamplePass(i - 3, j - 3);\n\t\t\t}\n\t\t}\n\t}\n\telse if (r_bloom_diamond_size.value > 3) {\n\t\tif (r_bloom_diamond_size.integer != 4) {\n\t\t\tCvar_SetValue(&r_bloom_diamond_size, 4);\n\t\t}\n\n\t\tfor (i = 0; i < r_bloom_diamond_size.integer; i++) {\n\t\t\tfor (j = 0; j < r_bloom_diamond_size.integer; j++) {\n\t\t\t\tintensity = r_bloom_intensity.value * 0.8f * Diamond4x[i][j];\n\t\t\t\tif (intensity < 0.01f) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tR_CustomColor(intensity, intensity, intensity, 1.0);\n\t\t\t\tGLC_Bloom_SamplePass(i - 2, j - 2);\n\t\t\t}\n\t\t}\n\t}\n\n\tglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, sample_width, sample_height);\n\tGLC_Bloom_DebugTexture(r_bloomeffecttexture, \"./qw/bloom-blurred.jpg\");\n\n\t// Restore full screen workspace.\n\tR_Viewport(0, 0, glwidth, glheight);\n\tR_OrthographicProjection(0, glwidth, glheight, 0, -10, 100);\n\tR_IdentityModelView();\n}\n\n// =================\n// R_Bloom_DownsampleView\n// =================\nstatic void GLC_Bloom_DownsampleView(void)\n{\n\tR_ApplyRenderingState(r_state_postprocess_bloom_downsample);\n\tR_CustomColor(1.0f, 1.0f, 1.0f, 1.0f);\n\n\t// Stepped downsample.\n\tif (r_screendownsamplingtexture_size) {\n\t\tint     midsample_width = r_screendownsamplingtexture_size * sampleText_tcw;\n\t\tint     midsample_height = r_screendownsamplingtexture_size * sampleText_tch;\n\n\t\t// Copy the screen and draw resized.\n\t\trenderer.TextureUnitBind(0, r_bloomscreentexture);\n\t\tglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, curView_x, glheight - (curView_y + curView_height), curView_width, curView_height);\n\t\tGLC_Bloom_Quad(0, glheight - midsample_height, midsample_width, midsample_height, screenText_tcw, screenText_tch);\n\n\t\t// Now copy into Downsampling (mid-sized) texture.\n\t\trenderer.TextureUnitBind(0, r_bloomdownsamplingtexture);\n\t\tglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, midsample_width, midsample_height);\n\t\tGLC_Bloom_DebugTexture(r_bloombackuptexture, \"./qw/bloom-downsampled1.jpg\");\n\n\t\t// Now draw again in bloom size.\n\t\tR_CustomColor(0.5f, 0.5f, 0.5f, 1.0f);\n\t\tGLC_Bloom_Quad(0, glheight - sample_height, sample_width, sample_height, sampleText_tcw, sampleText_tch);\n\n\t\t// Now blend the big screen texture into the bloom generation space (hoping it adds some blur).\n\t\tR_ApplyRenderingState(r_state_postprocess_bloom_downsample_blend);\n\t\tR_CustomColor(0.5f, 0.5f, 0.5f, 1.0f);\n\t\trenderer.TextureUnitBind(0, r_bloomscreentexture);\n\t\tGLC_Bloom_Quad(0, glheight - sample_height, sample_width, sample_height, screenText_tcw, screenText_tch);\n\t}\n\telse {\n\t\t// Downsample simple.\n\t\trenderer.TextureUnitBind(0, r_bloomscreentexture);\n\t\tglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, curView_x, glheight - (curView_y + curView_height), curView_width, curView_height);\n\t\tGLC_Bloom_DebugTexture(r_bloombackuptexture, \"./qw/bloom-downsampled2.jpg\");\n\t\tGLC_Bloom_Quad(0, glheight - sample_height, sample_width, sample_height, screenText_tcw, screenText_tch);\n\t}\n}\n\n// =================\n// R_BloomBlend\n// =================\nvoid GLC_BloomBlend(void)\n{\n\textern vrect_t\tscr_vrect;\n\n\tif (!r_bloom.value) {\n\t\treturn;\n\t}\n\n\tif (!BLOOM_SIZE || screen_texture_width < glwidth || screen_texture_height < glheight) {\n\t\tGLC_Bloom_InitTextures();\n\t}\n\n\tif (screen_texture_width < BLOOM_SIZE || screen_texture_height < BLOOM_SIZE) {\n\t\treturn;\n\t}\n\n\t// Set up full screen workspace.\n\tR_Viewport(0, 0, glwidth, glheight);\n\tR_OrthographicProjection(0, glwidth, glheight, 0, -10, 100);\n\tR_IdentityModelView();\n\n\tR_ApplyRenderingState(r_state_postprocess_bloom1);\n\tR_CustomColor(1, 1, 1, 1);\n\n\t// Setup current sizes\n\tcurView_x = scr_vrect.x * ((float)glwidth / vid.width);\n\tcurView_y = scr_vrect.y * ((float)glheight / vid.height);\n\tcurView_width = scr_vrect.width * ((float)glwidth / vid.width);\n\tcurView_height = scr_vrect.height * ((float)glheight / vid.height);\n\tscreenText_tcw = ((float)curView_width / (float)screen_texture_width);\n\tscreenText_tch = ((float)curView_height / (float)screen_texture_height);\n\n\tif (scr_vrect.height > scr_vrect.width) {\n\t\tsampleText_tcw = ((float)scr_vrect.width / (float)scr_vrect.height);\n\t\tsampleText_tch = 1.0f;\n\t}\n\telse {\n\t\tsampleText_tcw = 1.0f;\n\t\tsampleText_tch = ((float)scr_vrect.height / (float)scr_vrect.width);\n\t}\n\n\tsample_width = BLOOM_SIZE * sampleText_tcw;\n\tsample_height = BLOOM_SIZE * sampleText_tch;\n\n\t// Copy the screen space we'll use to work into the backup texture.\n\trenderer.TextureUnitBind(0, r_bloombackuptexture);\n\tglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, r_screenbackuptexture_size * sampleText_tcw, r_screenbackuptexture_size * sampleText_tch);\n\tGLC_Bloom_DebugTexture(r_bloombackuptexture, \"./qw/bloom-copied.jpg\");\n\n\t// Create the bloom image.\n\tGLC_Bloom_DownsampleView();\n\tGLC_Bloom_GeneratexDiamonds();\n\t//R_Bloom_GeneratexCross();\n\n\t// Restore the screen-backup to the screen.\n\tGLC_Bloom_DebugTexture(r_bloombackuptexture, \"./qw/bloom-restored.jpg\");\n\tR_ApplyRenderingState(r_state_postprocess_bloom_restore);\n\trenderer.TextureUnitBind(0, r_bloombackuptexture);\n\tR_CustomColor(1, 1, 1, 1);\n\tGLC_Bloom_Quad(0,\n\t\tglheight - (r_screenbackuptexture_size * sampleText_tch),\n\t\tr_screenbackuptexture_size * sampleText_tcw,\n\t\tr_screenbackuptexture_size * sampleText_tch,\n\t\tsampleText_tcw,\n\t\tsampleText_tch\n\t);\n\n\tGLC_Bloom_DrawEffect();\n\n#ifdef DEBUG_BLOOM\n\tCvar_SetValue(&developer, 0);\n#endif\n}\n\nvoid GLC_BloomRegisterCvars(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_EYECANDY);\n\tCvar_Register(&r_bloom);\n\tCvar_Register(&r_bloom_darken);\n\tCvar_Register(&r_bloom_alpha);\n\tCvar_Register(&r_bloom_diamond_size);\n\tCvar_Register(&r_bloom_intensity);\n\tCvar_Register(&r_bloom_sample_size);\n\tCvar_Register(&r_bloom_fast_sample);\n\tCvar_ResetCurrentGroup();\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n"
  },
  {
    "path": "src/glc_brushmodel.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: gl_model.c,v 1.41 2007-10-07 08:06:33 tonik Exp $\n*/\n// gl_model.c  -- model loading and caching\n\n// models are the only shared resource between a client and server running on the same machine.\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"rulesets.h\"\n#include \"r_framestats.h\"\n#include \"r_texture.h\"\n#include \"glc_state.h\"\n#include \"glc_vao.h\"\n#include \"r_brushmodel.h\"\n#include \"r_brushmodel_sky.h\"\n#include \"glc_local.h\"\n#include \"tr_types.h\"\n#include \"r_renderer.h\"\n#include \"glsl/constants.glsl\"\n#include \"r_lightmaps.h\"\n#include \"r_trace.h\"\n\nextern glpoly_t *fullbright_polys[MAX_GLTEXTURES];\nextern glpoly_t *luma_polys[MAX_GLTEXTURES];\n\nvoid GLC_LightmapArrayToggle(qbool use_array);\n\nglpoly_t *caustics_polys = NULL;\nglpoly_t *detail_polys = NULL;\n\nvoid GLC_EnsureVAOCreated(r_vao_id vao)\n{\n\tif (R_VertexArrayCreated(vao)) {\n\t\treturn;\n\t}\n\n\tif (!R_BufferReferenceIsValid(r_buffer_brushmodel_vertex_data)) {\n\t\t// TODO: vbo data in client memory\n\t\treturn;\n\t}\n\n\tR_GenVertexArray(vao);\n\tGLC_VAOSetVertexBuffer(vao, r_buffer_brushmodel_vertex_data);\n\t// TODO: index data _not_ in client memory\n\n\tswitch (vao) {\n\t\tcase vao_brushmodel:\n\t\t{\n\t\t\t// tmus: [material, material2, lightmap]\n\t\t\tGLC_VAOEnableVertexPointer(vao, 3, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, position));\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao, 0, 2, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, material_coords));\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao, 1, 2, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, material_coords));\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao, 2, 3, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, lightmap_coords));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 0, r_program_attribute_world_drawflat_style, 1, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, flatstyle));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 1, r_program_attribute_world_textured_style, 1, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, flatstyle));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 2, r_program_attribute_world_textured_detailCoord, 2, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, detail_coords));\n\t\t\tbreak;\n\t\t}\n\t\tcase vao_brushmodel_lm_unit1:\n\t\t{\n\t\t\t// tmus: [material, lightmap, material2]\n\t\t\tGLC_VAOEnableVertexPointer(vao, 3, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, position));\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao, 0, 2, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, material_coords));\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao, 1, 3, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, lightmap_coords));\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao, 2, 2, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, material_coords));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 0, r_program_attribute_world_drawflat_style, 1, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, flatstyle));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 1, r_program_attribute_world_textured_style, 1, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, flatstyle));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 2, r_program_attribute_world_textured_detailCoord, 2, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, detail_coords));\n\t\t\tbreak;\n\t\t}\n\t\tcase vao_brushmodel_details:\n\t\t{\n\t\t\t// tmus: [details]\n\t\t\tGLC_VAOEnableVertexPointer(vao, 3, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, position));\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao, 0, 2, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, detail_coords));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 0, r_program_attribute_world_drawflat_style, 1, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, flatstyle));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 1, r_program_attribute_world_textured_style, 1, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, flatstyle));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 2, r_program_attribute_world_textured_detailCoord, 2, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, detail_coords));\n\t\t\tbreak;\n\t\t}\n\t\tcase vao_brushmodel_lightmap_pass:\n\t\t{\n\t\t\t// tmus: [lightmap]\n\t\t\tGLC_VAOEnableVertexPointer(vao, 3, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, position));\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao, 0, 3, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, lightmap_coords));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 0, r_program_attribute_world_drawflat_style, 1, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, flatstyle));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 1, r_program_attribute_world_textured_style, 1, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, flatstyle));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 2, r_program_attribute_world_textured_detailCoord, 2, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, detail_coords));\n\t\t\tbreak;\n\t\t}\n\t\tcase vao_brushmodel_simpletex:\n\t\t{\n\t\t\t// tmus: [material]\n\t\t\tGLC_VAOEnableVertexPointer(vao, 3, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, position));\n\t\t\tGLC_VAOEnableTextureCoordPointer(vao, 0, 2, GL_FLOAT, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, material_coords));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 0, r_program_attribute_world_drawflat_style, 1, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, flatstyle));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 1, r_program_attribute_world_textured_style, 1, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, flatstyle));\n\t\t\tGLC_VAOEnableCustomAttribute(vao, 2, r_program_attribute_world_textured_detailCoord, 2, GL_FLOAT, GL_FALSE, sizeof(glc_vbo_world_vert_t), VBO_FIELDOFFSET(glc_vbo_world_vert_t, detail_coords));\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t{\n\t\t\tassert(false);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic void GLC_BlendLightmaps(void);\nstatic void GLC_BlendLightmaps_GLSL(void);\nvoid GLC_RenderFullbrights(void);\nvoid GLC_RenderLumas(void);\nvoid GLC_RenderLumas_GLSL(void);\nvoid GLC_RenderFullbrights_GLSL(void);\n\n//#define GLC_WORLD_TEXTURELESS    (1 << 0)\n#define GLC_WORLD_FULLBRIGHTS    (1 << 1)\n#define GLC_WORLD_LUMATEXTURES   (1 << 2)\n#define GLC_WORLD_DETAIL         (1 << 3)\n#define GLC_WORLD_CAUSTICS       (1 << 4)\n#define GLC_WORLD_LIGHTMAPS      (1 << 5)\n#define GLC_WORLD_DF_NORMAL      (1 << 6)\n#define GLC_WORLD_DF_TINTED      (1 << 7)\n#define GLC_WORLD_DF_BRIGHT      (1 << 8)\n#define GLC_WORLD_DF_FLOORS      (1 << 9)\n#define GLC_WORLD_DF_WALLS       (1 << 10)\n#define GLC_WORLD_FOG_LINEAR     (1 << 11)\n#define GLC_WORLD_FOG_EXP        (1 << 12)\n#define GLC_WORLD_FOG_EXP2       (1 << 13)\n\n#define GLC_USE_FULLBRIGHT_TEX  (GLC_WORLD_LUMATEXTURES | GLC_WORLD_FULLBRIGHTS)\n#define GLC_USE_DRAWFLAT        (GLC_WORLD_DF_NORMAL | GLC_WORLD_DF_TINTED | GLC_WORLD_DF_BRIGHT)\n#define GLC_USE_FOG             (GLC_WORLD_FOG_LINEAR | GLC_WORLD_FOG_EXP | GLC_WORLD_FOG_EXP2)\n\nstatic void GLC_GLSL_AddFogOptions(char* definitions, int definitions_length, int options)\n{\n\tif (options & GLC_USE_FOG) {\n\t\tstrlcat(definitions, \"#define DRAW_FOG\\n\", definitions_length);\n\t\tif (options & GLC_WORLD_FOG_LINEAR) {\n\t\t\tstrlcat(definitions, \"#define FOG_LINEAR\\n\", definitions_length);\n\t\t}\n\t\telse if (options & GLC_WORLD_FOG_EXP) {\n\t\t\tstrlcat(definitions, \"#define FOG_EXP\\n\", definitions_length);\n\t\t}\n\t\telse if (options & GLC_WORLD_FOG_EXP2) {\n\t\t\tstrlcat(definitions, \"#define FOG_EXP2\\n\", definitions_length);\n\t\t}\n\t}\n}\n\nqbool GLC_DrawflatProgramCompile(void)\n{\n\tint flags = 0;\n\n\tif (R_ProgramRecompileNeeded(r_program_world_drawflat_glc, flags)) {\n\t\tchar included_definitions[512];\n\n\t\tincluded_definitions[0] = '\\0';\n\t\tif (glConfig.supported_features & R_SUPPORT_TEXTURE_ARRAYS) {\n\t\t\tstrlcpy(included_definitions, \"#define EZ_USE_TEXTURE_ARRAYS\\n\", sizeof(included_definitions));\n\t\t}\n\t\tGLC_GLSL_AddFogOptions(included_definitions, sizeof(included_definitions), flags);\n\t\tR_ProgramCompileWithInclude(r_program_world_drawflat_glc, included_definitions);\n\n\t\tR_ProgramSetCustomOptions(r_program_world_drawflat_glc, flags);\n\t}\n\n\tR_ProgramSetStandardUniforms(r_program_world_drawflat_glc);\n\n\treturn R_ProgramReady(r_program_world_drawflat_glc);\n}\n\nstatic void GLC_SurfaceColor(const msurface_t* s, byte* desired)\n{\n\tif (s->flags & SURF_DRAWSKY) {\n\t\tif (r_refdef2.fog_enabled) {\n\t\t\tdesired[0] = (int)(r_refdef2.fog_skycolor[0] * 255);\n\t\t\tdesired[1] = (int)(r_refdef2.fog_skycolor[1] * 255);\n\t\t\tdesired[2] = (int)(r_refdef2.fog_skycolor[2] * 255);\n\t\t}\n\t\telse {\n\t\t\tmemcpy(desired, r_skycolor.color, 3);\n\t\t}\n\t}\n\telse if (s->flags & SURF_DRAWTURB) {\n\t\tmemcpy(desired, SurfaceFlatTurbColor(s->texinfo->texture), 3);\n\t}\n\telse if (s->flags & SURF_DRAWFLAT_FLOOR) {\n\t\tmemcpy(desired, r_floorcolor.color, 3);\n\t}\n\telse {\n\t\tmemcpy(desired, r_wallcolor.color, 3);\n\t}\n}\n\nstatic void GLC_DrawFlat_GLSL(model_t* model, qbool polygonOffset)\n{\n\textern cvar_t r_watercolor, r_slimecolor, r_lavacolor, r_telecolor;\n\tfloat color[4] = { 0, 0, 0, 1 };\n\tint index_count = 0;\n\tmsurface_t* s;\n\tmsurface_t* prev;\n\tint i;\n\tunsigned int lightmap_count = R_LightmapCount();\n\tqbool first_lightmap_surf = true;\n\tqbool draw_caustics = r_refdef2.drawCaustics;\n\tqbool clear_chains = !r_refdef2.drawWorldOutlines;\n\n\tGLC_LightmapArrayToggle(true);\n\n\tR_ProgramUse(r_program_world_drawflat_glc);\n\tVectorScale(r_wallcolor.color, 1.0f / 255.0f, color);\n\tR_ProgramUniform4fv(r_program_uniform_world_drawflat_glc_wallcolor, color);\n\tVectorScale(r_floorcolor.color, 1.0f / 255.0f, color);\n\tR_ProgramUniform4fv(r_program_uniform_world_drawflat_glc_floorcolor, color);\n\tVectorScale(r_refdef2.fog_skycolor, 1.0f, color);\n\tR_ProgramUniform4fv(r_program_uniform_world_drawflat_glc_skycolor, color);\n\tVectorScale(r_watercolor.color, 1.0f / 255.0f, color);\n\tR_ProgramUniform4fv(r_program_uniform_world_drawflat_glc_watercolor, color);\n\tVectorScale(r_slimecolor.color, 1.0f / 255.0f, color);\n\tR_ProgramUniform4fv(r_program_uniform_world_drawflat_glc_slimecolor, color);\n\tVectorScale(r_lavacolor.color, 1.0f / 255.0f, color);\n\tR_ProgramUniform4fv(r_program_uniform_world_drawflat_glc_lavacolor, color);\n\tVectorScale(r_telecolor.color, 1.0f / 255.0f, color);\n\tR_ProgramUniform4fv(r_program_uniform_world_drawflat_glc_telecolor, color);\n\n\tif (model->drawflat_chain) {\n\t\tR_ApplyRenderingState(r_state_drawflat_without_lightmaps_glc);\n\t\tR_CustomPolygonOffset(polygonOffset ? r_polygonoffset_standard : r_polygonoffset_disabled);\n\n\t\t// drawflat_chain has no lightmaps\n\t\ts = model->drawflat_chain;\n\t\twhile (s) {\n\t\t\tif (s->polys) {\n\t\t\t\tglpoly_t* p;\n\n\t\t\t\tfor (p = s->polys; p; p = p->next) {\n\t\t\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t\t\t}\n\n\t\t\t\t// START shaman FIX /r_drawflat + /gl_caustics {\n\t\t\t\tif ((s->flags & SURF_UNDERWATER) && draw_caustics) {\n\t\t\t\t\ts->polys->caustics_chain = caustics_polys;\n\t\t\t\t\tcaustics_polys = s->polys;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// } END shaman FIX /r_drawflat + /gl_caustics\n\n\t\t\tif (clear_chains) {\n\t\t\t\tprev = s;\n\t\t\t\ts = s->drawflatchain;\n\t\t\t\tprev->drawflatchain = NULL;\n\t\t\t}\n\t\t\telse {\n\t\t\t\ts = s->drawflatchain;\n\t\t\t}\n\t\t}\n\t\tif (index_count) {\n\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\tindex_count = 0;\n\t\t}\n\n\t\tif (clear_chains) {\n\t\t\tmodel->drawflat_chain = NULL;\n\t\t}\n\t}\n\n\t// go through lightmap chains\n\tfor (i = 0; i < lightmap_count; ++i) {\n\t\tmsurface_t* surf = R_DrawflatLightmapChain(i);\n\n\t\tif (surf) {\n\t\t\tif (first_lightmap_surf) {\n\t\t\t\tR_ApplyRenderingState(r_state_drawflat_with_lightmaps_glc);\n\t\t\t\tR_CustomPolygonOffset(polygonOffset ? r_polygonoffset_standard : r_polygonoffset_disabled);\n\t\t\t\tfirst_lightmap_surf = false;\n\t\t\t}\n\n\t\t\tif (index_count && !GLC_IsLightmapBound(0, i)) {\n\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\t\tindex_count = 0;\n\t\t\t}\n\t\t\tGLC_SetTextureLightmap(0, i);\n\n\t\t\twhile (surf) {\n\t\t\t\tglpoly_t* p;\n\n\t\t\t\tfor (p = surf->polys; p; p = p->next) {\n\t\t\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t\t\t}\n\n\t\t\t\tif (clear_chains) {\n\t\t\t\t\tprev = surf;\n\t\t\t\t\tsurf = surf->drawflatchain;\n\t\t\t\t\tprev->drawflatchain = NULL;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tsurf = surf->drawflatchain;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (clear_chains) {\n\t\t\t\tR_ClearDrawflatLightmapChain(i);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (index_count) {\n\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\tindex_count = 0;\n\t}\n\tR_ProgramUse(r_program_none);\n}\n\nstatic void GLC_DrawFlat_Immediate(model_t* model, qbool polygonOffset)\n{\n\tint i;\n\tqbool first_surf = true;\n\tqbool draw_caustics = r_refdef2.drawCaustics;\n\tqbool use_vbo = buffers.supported && modelIndexes;\n\tqbool clear_chains = !r_refdef2.drawWorldOutlines;\n\tbyte current[3] = { 255, 255, 255 }, desired[4] = { 255, 255, 255, 255 };\n\tunsigned int lightmap_count = R_LightmapCount();\n\tint index_count = 0;\n\tmsurface_t *s, *prev;\n\tint k;\n\tfloat *v;\n\tqbool sky = false;\n\n\tGLC_LightmapArrayToggle(false);\n\n\ts = model->drawflat_chain;\n\tR_ProgramUse(r_program_none);\n\twhile (s) {\n\t\tqbool sky_surface = (s->flags & SURF_DRAWSKY);\n\t\tqbool sky_switch = (sky_surface != sky && r_refdef2.fog_enabled);\n\n\t\tGLC_SurfaceColor(s, desired);\n\n\t\tif (first_surf || sky_switch || (use_vbo && (desired[0] != current[0] || desired[1] != current[1] || desired[2] != current[2]))) {\n\t\t\tif (index_count) {\n\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\t\tindex_count = 0;\n\t\t\t}\n\t\t\tif (first_surf || sky_switch) {\n\t\t\t\tR_ApplyRenderingState((sky_surface || !r_refdef2.fog_enabled ? r_state_drawflat_without_lightmaps_unfogged_glc : r_state_drawflat_without_lightmaps_glc));\n\t\t\t\tR_CustomPolygonOffset(polygonOffset ? r_polygonoffset_standard : r_polygonoffset_disabled);\n\t\t\t}\n\t\t\tR_CustomColor4ubv(desired);\n\t\t}\n\n\t\tVectorCopy(desired, current);\n\t\tfirst_surf = false;\n\t\tsky = sky_surface;\n\n\t\t{\n\t\t\tglpoly_t *p;\n\t\t\tfor (p = s->polys; p; p = p->next) {\n\t\t\t\tv = p->verts[0];\n\n\t\t\t\tif (use_vbo) {\n\t\t\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tR_CustomColor4ubv(desired);\n\t\t\t\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\t\t\t\tfor (k = 0; k < p->numverts; k++, v += VERTEXSIZE) {\n\t\t\t\t\t\tGLC_Vertex3fv(v);\n\t\t\t\t\t}\n\t\t\t\t\tGLC_End();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// START shaman FIX /r_drawflat + /gl_caustics {\n\t\tif ((s->flags & SURF_UNDERWATER) && draw_caustics) {\n\t\t\ts->polys->caustics_chain = caustics_polys;\n\t\t\tcaustics_polys = s->polys;\n\t\t}\n\t\t// } END shaman FIX /r_drawflat + /gl_caustics\n\n\t\tif (clear_chains) {\n\t\t\tprev = s;\n\t\t\ts = s->drawflatchain;\n\t\t\tprev->drawflatchain = NULL;\n\t\t}\n\t\telse {\n\t\t\ts = s->drawflatchain;\n\t\t}\n\t}\n\tif (clear_chains) {\n\t\tmodel->drawflat_chain = NULL;\n\t}\n\n\tif (index_count) {\n\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\tindex_count = 0;\n\t}\n\n\tfirst_surf = true;\n\tfor (i = 0; i < lightmap_count; ++i) {\n\t\tmsurface_t* surf = R_DrawflatLightmapChain(i);\n\n\t\tif (surf) {\n\t\t\tif (index_count && !GLC_IsLightmapBound(0, i)) {\n\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\t\tindex_count = 0;\n\t\t\t}\n\t\t\tGLC_SetTextureLightmap(0, i);\n\n\t\t\twhile (surf) {\n\t\t\t\tglpoly_t* p;\n\n\t\t\t\tGLC_SurfaceColor(surf, desired);\n\n\t\t\t\tif (first_surf || (use_vbo && (desired[0] != current[0] || desired[1] != current[1] || desired[2] != current[2]))) {\n\t\t\t\t\tif (index_count) {\n\t\t\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\t\t\t\tindex_count = 0;\n\t\t\t\t\t}\n\t\t\t\t\tif (first_surf) {\n\t\t\t\t\t\tR_ApplyRenderingState(r_state_drawflat_with_lightmaps_glc);\n\t\t\t\t\t\tR_CustomPolygonOffset(polygonOffset ? r_polygonoffset_standard : r_polygonoffset_disabled);\n\t\t\t\t\t}\n\t\t\t\t\tR_CustomColor4ubv(desired);\n\t\t\t\t}\n\t\t\t\tVectorCopy(desired, current);\n\t\t\t\tfirst_surf = false;\n\n\t\t\t\tfor (p = surf->polys; p; p = p->next) {\n\t\t\t\t\tv = p->verts[0];\n\n\t\t\t\t\tif (use_vbo) {\n\t\t\t\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tR_CustomColor4ubv(desired);\n\t\t\t\t\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\t\t\t\t\tfor (k = 0; k < p->numverts; k++, v += VERTEXSIZE) {\n\t\t\t\t\t\t\tglTexCoord2f(v[5], v[6]);\n\t\t\t\t\t\t\tGLC_Vertex3fv(v);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tGLC_End();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (clear_chains) {\n\t\t\t\t\tprev = surf;\n\t\t\t\t\tsurf = surf->drawflatchain;\n\t\t\t\t\tprev->drawflatchain = NULL;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tsurf = surf->drawflatchain;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (clear_chains) {\n\t\t\t\tR_ClearDrawflatLightmapChain(i);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (index_count) {\n\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t}\n}\n\nstatic void GLC_DrawFlat(model_t *model, qbool polygonOffset)\n{\n\textern cvar_t gl_program_world;\n\n\tif (!model->drawflat_todo) {\n\t\treturn;\n\t}\n\n\tR_TraceEnterFunctionRegion;\n\n\tif (gl_program_world.integer && buffers.supported && modelIndexes && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_DrawflatProgramCompile()) {\n\t\tGLC_DrawFlat_GLSL(model, polygonOffset);\n\t}\n\telse {\n\t\tGLC_DrawFlat_Immediate(model, polygonOffset);\n\t}\n\n\tmodel->drawflat_chain = NULL;\n\tmodel->drawflat_todo = false;\n\n\t// START shaman FIX /r_drawflat + /gl_caustics {\n\tif (r_refdef2.drawCaustics && caustics_polys) {\n\t\tGLC_EmitCausticsPolys();\n\t}\n\tcaustics_polys = NULL;\n\t// } END shaman FIX /r_drawflat + /gl_caustics\n\n\tR_TraceLeaveFunctionRegion;\n}\n\ntypedef struct texture_unit_allocation_s {\n\tint matTextureUnit;\n\tint fbTextureUnit;\n\tint lmTextureUnit;\n\tint detailTextureUnit;\n\tint causticTextureUnit;\n\ttexture_ref null_fb_texture;\n\n\tint texture_unit_count;\n\tqbool couldUseLumaTextures;         // true if user requested lumas and is allowed\n\tqbool useLumaTextures;              // true if lumas could be used, and model has them\n\tqbool second_pass_caustics;         // draw caustics in second pass\n\tqbool second_pass_detail;           // draw detail/noise texture in second pass\n\tqbool second_pass_luma;             // draw fullbright/luma textures in second pass\n\n\tr_state_id rendering_state;         // basic rendering state\n} texture_unit_allocation_t;\n\nstatic void GLC_WorldAllocateTextureUnits(texture_unit_allocation_t* allocations, model_t* model, qbool program_compilation, qbool allow_lumas)\n{\n\textern cvar_t gl_lumatextures;\n\tqbool lumas;\n\n\tallocations->texture_unit_count = 1;\n\tallocations->null_fb_texture = null_texture_reference;\n\tallocations->couldUseLumaTextures = allow_lumas && gl_lumatextures.integer && r_refdef2.allow_lumas;\n\tallocations->useLumaTextures = allocations->couldUseLumaTextures && model->texturechains_have_lumas;\n\tallocations->matTextureUnit = allocations->fbTextureUnit = allocations->lmTextureUnit = allocations->detailTextureUnit = allocations->causticTextureUnit = -1;\n\n\tlumas = program_compilation ? allocations->couldUseLumaTextures : allocations->useLumaTextures;\n\n\tallocations->matTextureUnit = 0;\n\tif (gl_mtexable) {\n\t\tallocations->texture_unit_count = gl_textureunits >= 3 ? 3 : 2;\n\n\t\tif (lumas && !gl_fb_bmodels.integer) {\n\t\t\t// blend(material + fb, lightmap)\n\t\t\tallocations->rendering_state = r_state_world_material_fb_lightmap;\n\t\t\tallocations->null_fb_texture = solidblack_texture;\n\t\t\tallocations->fbTextureUnit = 1;\n\t\t\tallocations->lmTextureUnit = (gl_textureunits >= 3 ? 2 : -1);\n\t\t}\n\t\telse if (lumas) {\n\t\t\t// blend(material, lightmap) + luma\n\t\t\tallocations->rendering_state = r_state_world_material_lightmap_luma;\n\t\t\tallocations->lmTextureUnit = 1;\n\t\t\tallocations->fbTextureUnit = (gl_textureunits >= 3 ? 2 : -1);\n\t\t\tallocations->null_fb_texture = solidblack_texture; // GL_ADD adds colors, multiplies alphas\n\t\t}\n\t\telse if (gl_fb_bmodels.integer) {\n\t\t\t// blend(blend(material, lightmap), fb)\n\t\t\tallocations->rendering_state = r_state_world_material_lightmap_fb;\n\t\t\tallocations->lmTextureUnit = 1;\n\t\t\tallocations->fbTextureUnit = (gl_textureunits >= 3 ? 2 : -1);\n\t\t\tallocations->null_fb_texture = transparent_texture; // GL_DECAL mixes color by alpha\n\t\t}\n\t\telse {\n\t\t\t// blend(material, lightmap) \n\t\t\tallocations->rendering_state = r_state_world_material_lightmap;\n\t\t\tallocations->lmTextureUnit = 1;\n\t\t\tallocations->fbTextureUnit = -1;\n\t\t\tallocations->null_fb_texture = solidblack_texture; // GL_ADD adds colors, multiplies alphas\n\t\t\tallocations->texture_unit_count = 2;\n\t\t}\n\n\t\tif (r_refdef2.drawCaustics && allocations->texture_unit_count < gl_textureunits) {\n\t\t\tallocations->causticTextureUnit = allocations->texture_unit_count++;\n\t\t}\n\n\t\tif (gl_detail.integer && R_TextureReferenceIsValid(detailtexture) && allocations->texture_unit_count < gl_textureunits) {\n\t\t\tallocations->detailTextureUnit = allocations->texture_unit_count++;\n\t\t}\n\t}\n\telse {\n\t\tallocations->rendering_state = r_state_world_singletexture_glc;\n\t}\n\n\tallocations->second_pass_caustics = r_refdef2.drawCaustics && allocations->causticTextureUnit < 0;\n\tallocations->second_pass_detail = R_TextureReferenceIsValid(detailtexture) && gl_detail.integer && allocations->detailTextureUnit < 0;\n\tallocations->second_pass_luma = lumas && allocations->fbTextureUnit < 0;\n}\n\nstatic void GLC_DrawTextureChains_Immediate(entity_t* ent, model_t *model, qbool caustics, qbool polygonOffset, qbool lumas_only, qbool alpha_surfaces)\n{\n\ttexture_unit_allocation_t allocations;\n\textern cvar_t gl_lumatextures;\n\textern cvar_t gl_textureless;\n\tint index_count = 0;\n\ttexture_ref fb_texturenum = null_texture_reference;\n\tint i, k;\n\tmsurface_t *s, *prev;\n\tfloat *v;\n\n\tqbool use_vbo = buffers.supported && modelIndexes;\n\n\tqbool drawfullbrights = false;\n\tqbool drawlumas = false;\n\tqbool clear_chains = !r_refdef2.drawWorldOutlines;\n\n\tqbool texture_change;\n\ttexture_ref current_material = null_texture_reference;\n\ttexture_ref current_material_fb = null_texture_reference;\n\tint current_lightmap = -1;\n\n\ttexture_ref desired_textures[8] = { { 0 } };\n\n\tqbool draw_textureless = gl_textureless.integer && model->isworldmodel && !alpha_surfaces;\n\n\tGLC_WorldAllocateTextureUnits(&allocations, model, false, lumas_only);\n\tif (lumas_only && !allocations.useLumaTextures) {\n\t\treturn;\n\t}\n\n\tR_TraceEnterFunctionRegion;\n\n\tGLC_LightmapArrayToggle(false);\n\tR_ApplyRenderingState(allocations.rendering_state);\n\tif (ent && ent->alpha) {\n\t\tR_CustomColor(ent->alpha, ent->alpha, ent->alpha, ent->alpha);\n\t}\n\tif (polygonOffset) {\n\t\tR_CustomPolygonOffset(r_polygonoffset_standard);\n\t}\n\tif (alpha_surfaces) {\n\t\tGLC_CustomAlphaTesting(true);\n\t}\n\n\tif (allocations.causticTextureUnit >= 0) {\n\t\tallocations.causticTextureUnit = -1;\n\t\tallocations.second_pass_caustics = true;\n\t\t--allocations.texture_unit_count;\n\t}\n\tif (allocations.detailTextureUnit >= 0) {\n\t\tallocations.detailTextureUnit = -1;\n\t\tallocations.second_pass_detail = true;\n\t\t--allocations.texture_unit_count;\n\t}\n\n\t//Tei: textureless for the world brush models (Qrack)\n\tif (draw_textureless) {\n\t\tif (use_vbo) {\n\t\t\t// meag: better to have different states for this\n\t\t\tR_GLC_DisableTexturePointer(0);\n\t\t\tif (allocations.fbTextureUnit >= 0) {\n\t\t\t\tR_GLC_DisableTexturePointer(allocations.fbTextureUnit);\n\t\t\t}\n\t\t}\n\n\t\tif (GL_Supported(R_SUPPORT_MULTITEXTURING)) {\n\t\t\tGLC_MultiTexCoord2f(GL_TEXTURE0, 0, 0);\n\n\t\t\tif (allocations.fbTextureUnit >= 0) {\n\t\t\t\tGLC_MultiTexCoord2f(GL_TEXTURE0 + allocations.fbTextureUnit, 0, 0);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tglTexCoord2f(0, 0);\n\t\t}\n\t}\n\n\tR_ProgramUse(r_program_none);\n\tfor (i = 0; i < model->numtextures; i++) {\n\t\ttexture_t* t;\n\n\t\tif (!model->textures[i] || !model->textures[i]->texturechain) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tt = R_TextureAnimation(ent, model->textures[i]);\n\t\tif (lumas_only && !t->isLumaTexture) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (alpha_surfaces != t->isAlphaTested) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (allocations.useLumaTextures && t->isLumaTexture) {\n\t\t\tfb_texturenum = t->fb_texturenum;\n\t\t}\n\t\telse if (!t->isLumaTexture && R_TextureReferenceIsValid(t->fb_texturenum)) {\n\t\t\tfb_texturenum = t->fb_texturenum;\n\t\t}\n\t\telse {\n\t\t\tfb_texturenum = allocations.null_fb_texture;\n\t\t}\n\n\t\t//bind the world texture\n\t\ttexture_change = !R_TextureReferenceEqual(t->gl_texturenum, current_material);\n\t\ttexture_change |= !R_TextureReferenceEqual(fb_texturenum, current_material_fb);\n\n\t\tcurrent_material = t->gl_texturenum;\n\t\tcurrent_material_fb = fb_texturenum;\n\n\t\tdesired_textures[allocations.matTextureUnit] = current_material;\n\t\tif (allocations.fbTextureUnit >= 0) {\n\t\t\tdesired_textures[allocations.fbTextureUnit] = current_material_fb;\n\t\t}\n\n\t\ts = model->textures[i]->texturechain;\n\t\twhile (s) {\n\t\t\tif (!(s->texinfo->flags & TEX_SPECIAL)) {\n\t\t\t\tif (allocations.lmTextureUnit >= 0) {\n\t\t\t\t\tdesired_textures[allocations.lmTextureUnit] = GLC_LightmapTexture(s->lightmaptexturenum);\n\t\t\t\t\ttexture_change |= !R_TextureReferenceEqual(desired_textures[allocations.lmTextureUnit], GLC_LightmapTexture(current_lightmap));\n\t\t\t\t\tcurrent_lightmap = s->lightmaptexturenum;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tGLC_AddToLightmapChain(s);\n\t\t\t\t}\n\n\t\t\t\tif (texture_change) {\n\t\t\t\t\tif (index_count) {\n\t\t\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\t\t\t\tindex_count = 0;\n\t\t\t\t\t}\n\n\t\t\t\t\trenderer.TextureUnitMultiBind(0, allocations.texture_unit_count, desired_textures);\n\t\t\t\t\tif (r_lightmap_lateupload.integer && allocations.lmTextureUnit >= 0) {\n\t\t\t\t\t\tR_UploadLightMap(allocations.lmTextureUnit, current_lightmap);\n\t\t\t\t\t}\n\n\t\t\t\t\ttexture_change = false;\n\t\t\t\t}\n\n\t\t\t\tif (use_vbo) {\n\t\t\t\t\tindex_count = GLC_DrawIndexedPoly(s->polys, modelIndexes, modelIndexMaximum, index_count);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tv = s->polys->verts[0];\n\n\t\t\t\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\t\t\t\tfor (k = 0; k < s->polys->numverts; k++, v += VERTEXSIZE) {\n\t\t\t\t\t\tif (allocations.lmTextureUnit >= 0) {\n\t\t\t\t\t\t\tGLC_MultiTexCoord2f(GL_TEXTURE0 + allocations.lmTextureUnit, v[5], v[6]);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!draw_textureless) {\n\t\t\t\t\t\t\tif (GL_Supported(R_SUPPORT_MULTITEXTURING)) {\n\t\t\t\t\t\t\t\tGLC_MultiTexCoord2f(GL_TEXTURE0, v[3], v[4]);\n\n\t\t\t\t\t\t\t\tif (allocations.fbTextureUnit >= 0) {\n\t\t\t\t\t\t\t\t\tGLC_MultiTexCoord2f(GL_TEXTURE0 + allocations.fbTextureUnit, v[3], v[4]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tglTexCoord2f(v[3], v[4]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tGLC_Vertex3fv(v);\n\t\t\t\t\t}\n\t\t\t\t\tGLC_End();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (allocations.second_pass_caustics && ((s->flags & SURF_UNDERWATER) || caustics)) {\n\t\t\t\ts->polys->caustics_chain = caustics_polys;\n\t\t\t\tcaustics_polys = s->polys;\n\t\t\t}\n\n\t\t\tif (allocations.second_pass_detail && !(s->flags & SURF_UNDERWATER)) {\n\t\t\t\ts->polys->detail_chain = detail_polys;\n\t\t\t\tdetail_polys = s->polys;\n\t\t\t}\n\n\t\t\tif (!R_TextureReferenceEqual(fb_texturenum, allocations.null_fb_texture) && gl_fb_bmodels.integer && allocations.fbTextureUnit < 0) {\n\t\t\t\tif (allocations.useLumaTextures) {\n\t\t\t\t\ts->polys->luma_chain = luma_polys[fb_texturenum.index];\n\t\t\t\t\tluma_polys[fb_texturenum.index] = s->polys;\n\t\t\t\t\tdrawlumas = true;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\ts->polys->fb_chain = fullbright_polys[fb_texturenum.index];\n\t\t\t\t\tfullbright_polys[fb_texturenum.index] = s->polys;\n\t\t\t\t\tdrawfullbrights = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (clear_chains) {\n\t\t\t\tprev = s;\n\t\t\t\ts = s->texturechain;\n\t\t\t\tprev->texturechain = NULL;\n\t\t\t}\n\t\t\telse {\n\t\t\t\ts = s->texturechain;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (index_count) {\n\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t}\n\n\tif (gl_fb_bmodels.integer) {\n\t\tif (allocations.lmTextureUnit < 0) {\n\t\t\tGLC_BlendLightmaps();\n\t\t}\n\t\tif (drawfullbrights) {\n\t\t\tGLC_RenderFullbrights();\n\t\t}\n\t\tif (drawlumas) {\n\t\t\tGLC_RenderLumas();\n\t\t}\n\t}\n\telse {\n\t\tif (drawlumas) {\n\t\t\tGLC_RenderLumas();\n\t\t}\n\t\tif (allocations.lmTextureUnit < 0) {\n\t\t\tGLC_BlendLightmaps();\n\t\t}\n\t\tif (drawfullbrights) {\n\t\t\tGLC_RenderFullbrights();\n\t\t}\n\t}\n\n\tGLC_EmitCausticsPolys();\n\tGLC_EmitDetailPolys(use_vbo);\n\n\tif (alpha_surfaces) {\n\t\tGLC_CustomAlphaTesting(false);\n\t}\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nstatic qbool GLC_WorldTexturedProgramCompile(texture_unit_allocation_t* allocations, model_t* model, qbool alpha_surfaces)\n{\n\textern cvar_t gl_lumatextures, gl_textureless;\n\tint options;\n\tqbool drawLumas = false;\n\t\n\tmemset(allocations, 0, sizeof(*allocations));\n\tGLC_WorldAllocateTextureUnits(allocations, model, true, true);\n\tallocations->rendering_state = r_state_world_material_lightmap_luma;\n\n\tdrawLumas = allocations->couldUseLumaTextures;\n\n\toptions =\n\t\t(allocations->lmTextureUnit >= 0 ? GLC_WORLD_LIGHTMAPS : 0) |\n\t\t(drawLumas ? GLC_WORLD_LUMATEXTURES : 0) |\n\t\t(allocations->fbTextureUnit >= 0 && gl_fb_bmodels.integer ? GLC_WORLD_FULLBRIGHTS : 0) |\n\t\t(allocations->detailTextureUnit >= 0 ? GLC_WORLD_DETAIL : 0) |\n\t\t(allocations->causticTextureUnit >= 0 ? GLC_WORLD_CAUSTICS : 0) |\n\t\t(r_drawflat.integer && r_drawflat_mode.integer == 0 ? GLC_WORLD_DF_NORMAL : 0) |\n\t\t(r_drawflat.integer && r_drawflat_mode.integer == 1 ? GLC_WORLD_DF_TINTED : 0) |\n\t\t(r_drawflat.integer && r_drawflat_mode.integer == 2 ? GLC_WORLD_DF_BRIGHT : 0) |\n\t\t(r_drawflat.integer == 1 || r_drawflat.integer == 2 ? GLC_WORLD_DF_FLOORS : 0) |\n\t\t(r_drawflat.integer == 1 || r_drawflat.integer == 3 ? GLC_WORLD_DF_WALLS : 0);\n\n\tR_ProgramSetSubProgram(r_program_world_textured_glc, (alpha_surfaces ? 1 : 0));\n\tif (R_ProgramRecompileNeeded(r_program_world_textured_glc, options)) {\n\t\tint sampler = 0;\n\t\tchar definitions[2048] = { 0 };\n\n\t\tallocations->matTextureUnit = 0;\n\n\t\tif (options & GLC_WORLD_LUMATEXTURES) {\n\t\t\tstrlcat(definitions, \"#define DRAW_LUMA_TEXTURES\\n\", sizeof(definitions));\n\t\t}\n\t\tif (options & GLC_WORLD_FULLBRIGHTS) {\n\t\t\tstrlcat(definitions, \"#define DRAW_FULLBRIGHT_TEXTURES\\n\", sizeof(definitions));\n\t\t}\n\t\tif (options & GLC_USE_FULLBRIGHT_TEX) {\n\t\t\tstrlcat(definitions, \"#define DRAW_EXTRA_TEXTURES\\n\", sizeof(definitions));\n\t\t}\n\t\tif (options & GLC_WORLD_CAUSTICS) {\n\t\t\tstrlcat(definitions, \"#define DRAW_CAUSTICS\\n\", sizeof(definitions));\n\t\t}\n\t\tif (options & GLC_WORLD_DETAIL) {\n\t\t\tstrlcat(definitions, \"#define DRAW_DETAIL\\n\", sizeof(definitions));\n\t\t}\n\t\tif (options & GLC_WORLD_LIGHTMAPS) {\n\t\t\tstrlcat(definitions, \"#define DRAW_LIGHTMAPS\\n\", sizeof(definitions));\n\t\t}\n\t\tif (glConfig.supported_features & R_SUPPORT_TEXTURE_ARRAYS) {\n\t\t\tstrlcat(definitions, \"#define EZ_USE_TEXTURE_ARRAYS\\n\", sizeof(definitions));\n\t\t}\n\t\tif (options & GLC_WORLD_DF_NORMAL) {\n\t\t\tstrlcat(definitions, \"#define DRAW_DRAWFLAT_NORMAL\\n\", sizeof(definitions));\n\t\t}\n\t\telse if (options & GLC_WORLD_DF_TINTED) {\n\t\t\tstrlcat(definitions, \"#define DRAW_DRAWFLAT_TINTED\\n\", sizeof(definitions));\n\t\t}\n\t\telse if (options & GLC_WORLD_DF_BRIGHT) {\n\t\t\tstrlcat(definitions, \"#define DRAW_DRAWFLAT_BRIGHT\\n\", sizeof(definitions));\n\t\t}\n\t\tif (options & GLC_WORLD_DF_FLOORS) {\n\t\t\tstrlcat(definitions, \"#define DRAW_FLATFLOORS\\n\", sizeof(definitions));\n\t\t}\n\t\tif (options & GLC_WORLD_DF_WALLS) {\n\t\t\tstrlcat(definitions, \"#define DRAW_FLATWALLS\\n\", sizeof(definitions));\n\t\t}\n\t\tif (alpha_surfaces) {\n\t\t\tstrlcat(definitions, \"#define DRAW_ALPHATEST_ENABLED\\n\", sizeof(definitions));\n\t\t}\n\t\tGLC_GLSL_AddFogOptions(definitions, sizeof(definitions), options);\n\n\t\tR_ProgramCompileWithInclude(r_program_world_textured_glc, definitions);\n\n\t\tallocations->causticTextureUnit = (options & GLC_WORLD_CAUSTICS) && allocations->causticTextureUnit >= 0 ? sampler++ : -1;\n\t\tallocations->detailTextureUnit = (options & GLC_WORLD_DETAIL) && allocations->detailTextureUnit >= 0 ? sampler++ : -1;\n\t\tallocations->lmTextureUnit = (options & GLC_WORLD_LIGHTMAPS) && allocations->lmTextureUnit >= 0 ? sampler++ : -1;\n\t\tallocations->matTextureUnit = allocations->matTextureUnit >= 0 ? sampler++ : -1;\n\t\tallocations->fbTextureUnit = (options & GLC_USE_FULLBRIGHT_TEX) && allocations->fbTextureUnit >= 0 ? sampler++ : -1;\n\n\t\tR_ProgramUniform1i(r_program_uniform_world_textured_glc_causticSampler, allocations->causticTextureUnit);\n\t\tR_ProgramUniform1i(r_program_uniform_world_textured_glc_detailSampler, allocations->detailTextureUnit);\n\t\tR_ProgramUniform1i(r_program_uniform_world_textured_glc_lightmapSampler, allocations->lmTextureUnit);\n\t\tR_ProgramUniform1i(r_program_uniform_world_textured_glc_texSampler, allocations->matTextureUnit);\n\t\tR_ProgramUniform1i(r_program_uniform_world_textured_glc_lumaSampler, allocations->fbTextureUnit);\n\n\t\tR_ProgramSetCustomOptions(r_program_world_textured_glc, options);\n\t}\n\telse {\n\t\tallocations->causticTextureUnit = R_ProgramUniformGet1i(r_program_uniform_world_textured_glc_causticSampler, -1);\n\t\tallocations->detailTextureUnit = R_ProgramUniformGet1i(r_program_uniform_world_textured_glc_detailSampler, -1);\n\t\tallocations->lmTextureUnit = R_ProgramUniformGet1i(r_program_uniform_world_textured_glc_lightmapSampler, -1);\n\t\tallocations->matTextureUnit = R_ProgramUniformGet1i(r_program_uniform_world_textured_glc_texSampler, -1);\n\t\tallocations->fbTextureUnit = R_ProgramUniformGet1i(r_program_uniform_world_textured_glc_lumaSampler, -1);\n\n\t\tallocations->texture_unit_count =\n\t\t\t(allocations->causticTextureUnit >= 0 ? 1 : 0) +\n\t\t\t(allocations->detailTextureUnit >= 0 ? 1 : 0) +\n\t\t\t(allocations->lmTextureUnit >= 0 ? 1 : 0) +\n\t\t\t(allocations->matTextureUnit >= 0 ? 1 : 0) +\n\t\t\t(allocations->fbTextureUnit >= 0 ? 1 : 0);\n\t}\n\n\tR_ProgramSetStandardUniforms(r_program_world_textured_glc);\n\n\tR_TraceLogAPICall(\"--- units      = %d\", allocations->texture_unit_count);\n\tR_TraceLogAPICall(\"... caustics   = %d\", allocations->causticTextureUnit);\n\tR_TraceLogAPICall(\"... detail     = %d\", allocations->detailTextureUnit);\n\tR_TraceLogAPICall(\"... lightmaps  = %d\", allocations->lmTextureUnit);\n\tR_TraceLogAPICall(\"... materials  = %d\", allocations->matTextureUnit);\n\tR_TraceLogAPICall(\"... fullbright = %d\", allocations->fbTextureUnit);\n\n\tif (R_ProgramRecompileNeeded(r_program_world_secondpass_glc, 0)) {\n\t\t// we could make multiple copies of the standard textured program,\n\t\t//  so that multiple passes use multiple textures\n\t\tR_ProgramCompile(r_program_world_secondpass_glc);\n\n\t\tR_ProgramUniform1i(r_program_uniform_world_textured_glc_causticSampler, 0);\n\t}\n\n\tR_ProgramSetStandardUniforms(r_program_world_secondpass_glc);\n\n\treturn R_ProgramReady(r_program_world_textured_glc) && R_ProgramReady(r_program_world_secondpass_glc);\n}\n\nstatic void GLC_DrawTextureChains_GLSL(entity_t* ent, model_t *model, qbool caustics, qbool polygonOffset, texture_unit_allocation_t* allocations, qbool alpha_surfaces)\n{\n\textern cvar_t gl_lumatextures;\n\tint index_count = 0;\n\tint i;\n\tmsurface_t *s, *prev;\n\n\tqbool requires_fullbright_pass = false;\n\tqbool requires_luma_pass = false;\n\tqbool clear_chains = !r_refdef2.drawWorldOutlines;\n\n\tqbool texture_change, uniform_change;\n\ttexture_ref current_material = null_texture_reference;\n\ttexture_ref current_material_fb = null_texture_reference;\n\ttexture_ref desired_textures[8] = { { 0 } };\n\tint current_lightmap = -1;\n\tfloat current_lumaScale = -1, current_fbScale = -1;\n\n\tR_TraceEnterFunctionRegion;\n\n\tGLC_LightmapArrayToggle(true);\n\n\tR_ApplyRenderingState(allocations->rendering_state);\n\tif (ent && ent->alpha) {\n\t\tR_CustomColor(ent->alpha, ent->alpha, ent->alpha, ent->alpha);\n\t}\n\tif (polygonOffset) {\n\t\tR_CustomPolygonOffset(r_polygonoffset_standard);\n\t}\n\n\t// Common textures\n\tif (allocations->causticTextureUnit >= 0) {\n\t\tdesired_textures[allocations->causticTextureUnit] = underwatertexture;\n\t}\n\tif (allocations->detailTextureUnit >= 0) {\n\t\tdesired_textures[allocations->detailTextureUnit] = detailtexture;\n\t}\n\n\tfor (i = 0; i < model->numtextures; i++) {\n\t\ttexture_t* t;\n\t\ttexture_ref fb_texturenum = null_texture_reference;\n\t\tfloat lumaScale = 0.0f, fbScale = 0.0f;\n\n\t\tif (!model->textures[i] || !model->textures[i]->texturechain) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (model->textures[i]->isAlphaTested != alpha_surfaces) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tt = R_TextureAnimation(ent, model->textures[i]);\n\t\tif (allocations->useLumaTextures && t->isLumaTexture && gl_fb_bmodels.integer) {\n\t\t\tfb_texturenum = t->fb_texturenum;\n\t\t\tlumaScale = 1.0f;\n\t\t\t//fbScale = 1.0f;\n\t\t}\n\t\telse if (allocations->fbTextureUnit >= 0 && R_TextureReferenceIsValid(t->fb_texturenum)) {\n\t\t\tfb_texturenum = t->fb_texturenum;\n\t\t\tlumaScale = (allocations->useLumaTextures && t->isLumaTexture && !gl_fb_bmodels.integer ? 1.0f : 0);\n\t\t\tfbScale = (t->isLumaTexture ? 0.0f : 1.0f);\n\t\t}\n\t\telse {\n\t\t\tfb_texturenum = allocations->null_fb_texture;\n\t\t}\n\n\t\t//bind the world texture\n\t\ttexture_change = !R_TextureReferenceEqual(t->gl_texturenum, current_material);\n\t\ttexture_change |= !R_TextureReferenceEqual(fb_texturenum, current_material_fb);\n\t\tcurrent_material = t->gl_texturenum;\n\t\tcurrent_material_fb = fb_texturenum;\n\n\t\tuniform_change = (current_lumaScale != lumaScale);\n\t\tuniform_change |= (current_fbScale != fbScale);\n\n\t\tdesired_textures[allocations->matTextureUnit] = current_material;\n\t\tif (allocations->fbTextureUnit >= 0) {\n\t\t\tdesired_textures[allocations->fbTextureUnit] = current_material_fb;\n\t\t}\n\n\t\ts = model->textures[i]->texturechain;\n\t\twhile (s) {\n\t\t\tif (!(s->texinfo->flags & TEX_SPECIAL)) {\n\t\t\t\tif (allocations->lmTextureUnit >= 0) {\n\t\t\t\t\ttexture_change |= !R_TextureReferenceEqual(GLC_LightmapTexture(s->lightmaptexturenum), GLC_LightmapTexture(current_lightmap));\n\n\t\t\t\t\tdesired_textures[allocations->lmTextureUnit] = GLC_LightmapTexture(s->lightmaptexturenum);\n\t\t\t\t\tcurrent_lightmap = s->lightmaptexturenum;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tGLC_AddToLightmapChain(s);\n\t\t\t\t}\n\n\t\t\t\tif (texture_change) {\n\t\t\t\t\tif (index_count) {\n\t\t\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\t\t\t\tindex_count = 0;\n\t\t\t\t\t}\n\n\t\t\t\t\trenderer.TextureUnitMultiBind(0, allocations->texture_unit_count, desired_textures);\n\t\t\t\t\tif (r_lightmap_lateupload.integer && allocations->lmTextureUnit >= 0) {\n\t\t\t\t\t\tR_UploadLightMap(allocations->lmTextureUnit, current_lightmap);\n\t\t\t\t\t}\n\n\t\t\t\t\ttexture_change = false;\n\t\t\t\t}\n\t\t\t\tif (uniform_change) {\n\t\t\t\t\tif (index_count) {\n\t\t\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\t\t\t\tindex_count = 0;\n\t\t\t\t\t}\n\t\t\t\t\tif (current_lumaScale != lumaScale) {\n\t\t\t\t\t\tR_ProgramUniform1f(r_program_uniform_world_textured_glc_lumaScale, current_lumaScale = lumaScale);\n\t\t\t\t\t}\n\t\t\t\t\tif (current_fbScale != fbScale) {\n\t\t\t\t\t\tR_ProgramUniform1f(r_program_uniform_world_textured_glc_fbScale, current_fbScale = fbScale);\n\t\t\t\t\t}\n\t\t\t\t\tuniform_change = false;\n\t\t\t\t}\n\n\t\t\t\tindex_count = GLC_DrawIndexedPoly(s->polys, modelIndexes, modelIndexMaximum, index_count);\n\t\t\t}\n\n\t\t\tif (allocations->second_pass_caustics && ((s->flags & SURF_UNDERWATER) || caustics)) {\n\t\t\t\ts->polys->caustics_chain = caustics_polys;\n\t\t\t\tcaustics_polys = s->polys;\n\t\t\t}\n\n\t\t\tif (allocations->second_pass_detail && !(s->flags & SURF_UNDERWATER)) {\n\t\t\t\ts->polys->detail_chain = detail_polys;\n\t\t\t\tdetail_polys = s->polys;\n\t\t\t}\n\n\t\t\tif (allocations->second_pass_luma && !R_TextureReferenceEqual(fb_texturenum, allocations->null_fb_texture)) {\n\t\t\t\tif (allocations->useLumaTextures) {\n\t\t\t\t\ts->polys->luma_chain = luma_polys[fb_texturenum.index];\n\t\t\t\t\tluma_polys[fb_texturenum.index] = s->polys;\n\t\t\t\t\trequires_luma_pass = true;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\ts->polys->fb_chain = fullbright_polys[fb_texturenum.index];\n\t\t\t\t\tfullbright_polys[fb_texturenum.index] = s->polys;\n\t\t\t\t\trequires_fullbright_pass = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (clear_chains) {\n\t\t\t\tprev = s;\n\t\t\t\ts = s->texturechain;\n\t\t\t\tprev->texturechain = NULL;\n\t\t\t}\n\t\t\telse {\n\t\t\t\ts = s->texturechain;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (index_count) {\n\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t}\n\n\tif (gl_fb_bmodels.integer) {\n\t\tif (allocations->lmTextureUnit < 0) {\n\t\t\tGLC_BlendLightmaps_GLSL();\n\t\t}\n\t\tif (requires_fullbright_pass) {\n\t\t\tGLC_RenderFullbrights_GLSL();\n\t\t}\n\t\tif (requires_luma_pass) {\n\t\t\tGLC_RenderLumas_GLSL();\n\t\t}\n\t}\n\telse {\n\t\tif (requires_luma_pass) {\n\t\t\tGLC_RenderLumas_GLSL();\n\t\t}\n\t\tif (allocations->lmTextureUnit < 0) {\n\t\t\tGLC_BlendLightmaps_GLSL();\n\t\t}\n\t\tif (requires_fullbright_pass) {\n\t\t\tGLC_RenderFullbrights_GLSL();\n\t\t}\n\t}\n\n\tif (allocations->second_pass_caustics) {\n\t\tGLC_EmitCausticsPolys();\n\t}\n\tif (allocations->second_pass_detail) {\n\t\tGLC_EmitDetailPolys_GLSL();\n\t}\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nstatic void GLC_DrawTextureChains(entity_t* ent, model_t *model, qbool caustics, qbool polygonOffset, qbool alpha_surfaces)\n{\n\textern cvar_t gl_program_world, gl_textureless;\n\ttexture_unit_allocation_t allocations;\n\n\tif (alpha_surfaces) {\n\t\t// No alpha surfaces to draw\n\t\tif (!model->alphapass_todo) {\n\t\t\treturn;\n\t\t}\n\t}\n\telse if (r_drawflat.integer == 1 && r_drawflat_mode.integer == 0 && model->isworldmodel) {\n\t\t// Guaranteed to be no texturing\n\t\treturn;\n\t}\n\n\tif (gl_program_world.integer && buffers.supported && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_WorldTexturedProgramCompile(&allocations, model, alpha_surfaces)) {\n\t\tR_ProgramUse(r_program_world_textured_glc);\n\t\tR_ProgramUniform1f(r_program_uniform_world_textured_glc_time, cl.time);\n\t\tR_ProgramUniform3f(r_program_uniform_world_textured_glc_r_floorcolor, r_floorcolor.color[0] / 255.0f, r_floorcolor.color[1] / 255.0f, r_floorcolor.color[2] / 255.0f);\n\t\tR_ProgramUniform3f(r_program_uniform_world_textured_glc_r_wallcolor, r_wallcolor.color[0] / 255.0f, r_wallcolor.color[1] / 255.0f, r_wallcolor.color[2] / 255.0f);\n\t\tR_ProgramUniform1f(r_program_uniform_world_textures_glc_texture_multiplier, model->isworldmodel && gl_textureless.integer ? 0.0f : 1.0f);\n\n\t\tGLC_DrawTextureChains_GLSL(ent, model, caustics, polygonOffset, &allocations, alpha_surfaces);\n\t\tR_ProgramUse(r_program_none);\n\t}\n\telse {\n\t\tif (model->texturechains_have_lumas) {\n\t\t\tGLC_DrawTextureChains_Immediate(ent, model, caustics, polygonOffset, true, alpha_surfaces);\n\t\t}\n\t\tGLC_DrawTextureChains_Immediate(ent, model, caustics, polygonOffset, false, alpha_surfaces);\n\t}\n}\n\nqbool GLC_PreCompileWorldPrograms(void)\n{\n\textern cvar_t gl_program_world;\n\ttexture_unit_allocation_t allocations;\n\n\treturn (gl_program_world.integer && buffers.supported && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_WorldTexturedProgramCompile(&allocations, cl.worldmodel, false) && GLC_WorldTexturedProgramCompile(&allocations, cl.worldmodel, true));\n}\n\n// This is now very similar to GLC_DrawBrushModel, but we don't draw sky surfaces at this stage\n//  (they've already been drawn)\n// Also water surfaces are drawn immediately after if opaque, but after entities if not (on sub/instanced models\n//   water surfaces are always drawn during the texture chaining phase, which is something to fix)\nvoid GLC_DrawWorld(void)\n{\n\tR_TraceEnterFunctionRegion;\n\n\tGLC_DrawTextureChains(NULL, cl.worldmodel, false, false, false);\n\tGLC_DrawFlat(cl.worldmodel, false);\n\tGLC_DrawMapOutline(cl.worldmodel);\n\tGLC_DrawTextureChains(NULL, cl.worldmodel, false, false, true);\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_DrawBrushModel(entity_t* e, qbool polygonOffset, qbool caustics)\n{\n\tGLC_DrawTextureChains(e, e->model, caustics, polygonOffset, false);\n\tGLC_DrawFlat(e->model, polygonOffset);\n\tGLC_DrawMapOutline(e->model);\n\tGLC_SkyDrawChainedSurfaces();\n\tGLC_DrawTextureChains(e, e->model, caustics, polygonOffset, true);\n\t//GLC_DrawAlphaChain(alphachain, polyTypeBrushModel);\n}\n\n/*\n// This populates VBO, splitting up by lightmap for efficient\n//   rendering when not using texture arrays\nint GLC_PopulateVBOForBrushModel(model_t* m, float* vbo_buffer, int vbo_pos)\n{\n\tint i, j;\n\tint combinations = 0;\n\tint original_pos = vbo_pos;\n\n\tfor (i = 0; i < m->numtextures; ++i) {\n\t\tif (m->textures[i]) {\n\t\t\tm->textures[i]->gl_first_lightmap = -1;\n\t\t\tfor (j = 0; j < MAX_LIGHTMAPS; ++j) {\n\t\t\t\tm->textures[i]->gl_next_lightmap[j] = -1;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Order vertices in the VBO by texture & lightmap\n\tfor (i = 0; i < m->numtextures; ++i) {\n\t\tint lightmap = -1;\n\t\tint length = 0;\n\t\tint surface_count = 0;\n\t\tint tex_vbo_start = vbo_pos;\n\n\t\tif (!m->textures[i]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Find first lightmap for this texture\n\t\tfor (j = 0; j < m->numsurfaces; ++j) {\n\t\t\tmsurface_t* surf = m->surfaces + j;\n\n\t\t\tif (surf->texinfo->miptex != i) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!(surf->flags & (SURF_DRAWTURB | SURF_DRAWSKY))) {\n\t\t\t\tif (surf->texinfo->flags & TEX_SPECIAL) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (surf->lightmaptexturenum >= 0 && (lightmap < 0 || surf->lightmaptexturenum < lightmap)) {\n\t\t\t\tlightmap = surf->lightmaptexturenum;\n\t\t\t}\n\t\t}\n\n\t\tm->textures[i]->gl_first_lightmap = lightmap;\n\n\t\t// Build the VBO in order of lightmaps...\n\t\twhile (lightmap >= 0) {\n\t\t\tint next_lightmap = -1;\n\n\t\t\tlength = 0;\n\t\t\tm->textures[i]->gl_vbo_start[lightmap] = vbo_pos / VERTEXSIZE;\n\t\t\t++combinations;\n\n\t\t\tfor (j = 0; j < m->numsurfaces; ++j) {\n\t\t\t\tmsurface_t* surf = m->surfaces + j;\n\t\t\t\tglpoly_t* poly;\n\n\t\t\t\tif (surf->texinfo->miptex != i) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (surf->lightmaptexturenum > lightmap && (next_lightmap < 0 || surf->lightmaptexturenum < next_lightmap)) {\n\t\t\t\t\tnext_lightmap = surf->lightmaptexturenum;\n\t\t\t\t}\n\n\t\t\t\tif (surf->lightmaptexturenum == lightmap) {\n\t\t\t\t\t// copy verts into buffer (alternate to turn fan into triangle strip)\n\t\t\t\t\tfor (poly = surf->polys; poly; poly = poly->next) {\n\t\t\t\t\t\tint end_vert = 0;\n\t\t\t\t\t\tint start_vert = 1;\n\t\t\t\t\t\tint output = 0;\n\t\t\t\t\t\tint material = m->textures[i]->gl_texture_index;\n\t\t\t\t\t\tfloat scaleS = m->textures[i]->gl_texture_scaleS;\n\t\t\t\t\t\tfloat scaleT = m->textures[i]->gl_texture_scaleT;\n\n\t\t\t\t\t\tif (!poly->numverts) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Store position for drawing individual polys\n\t\t\t\t\t\tpoly->vbo_start = vbo_pos / VERTEXSIZE;\n\t\t\t\t\t\tvbo_pos = CopyVertToBuffer(vbo_buffer, vbo_pos, poly->verts[0], surf->lightmaptexturenum, material, scaleS, scaleT);\n\t\t\t\t\t\t++output;\n\n\t\t\t\t\t\tstart_vert = 1;\n\t\t\t\t\t\tend_vert = poly->numverts - 1;\n\n\t\t\t\t\t\twhile (start_vert <= end_vert) {\n\t\t\t\t\t\t\tvbo_pos = CopyVertToBuffer(vbo_buffer, vbo_pos, poly->verts[start_vert], surf->lightmaptexturenum, material, scaleS, scaleT);\n\t\t\t\t\t\t\t++output;\n\n\t\t\t\t\t\t\tif (start_vert < end_vert) {\n\t\t\t\t\t\t\t\tvbo_pos = CopyVertToBuffer(vbo_buffer, vbo_pos, poly->verts[end_vert], surf->lightmaptexturenum, material, scaleS, scaleT);\n\t\t\t\t\t\t\t\t++output;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t++start_vert;\n\t\t\t\t\t\t\t--end_vert;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlength += poly->numverts;\n\t\t\t\t\t\t++surface_count;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tm->textures[i]->gl_vbo_length[lightmap] = length;\n\t\t\tm->textures[i]->gl_next_lightmap[lightmap] = next_lightmap;\n\t\t\tlightmap = next_lightmap;\n\t\t}\n\t}\n\n\tCon_Printf(\"%s = %d verts, reserved %d\\n\", m->name, (vbo_pos - original_pos) / VERTEXSIZE, GLM_MeasureVBOSizeForBrushModel(m));\n\treturn vbo_pos;\n}\n*/\n\nstatic void GLC_BlendLightmaps_GLSL(void)\n{\n\tint i;\n\n\tGLC_StateBeginBlendLightmaps(true);\n\tR_ProgramUse(r_program_world_secondpass_glc);\n\n\tfor (i = 0; i < GLC_LightmapCount(); i++) {\n\t\tGLuint index_count = 0;\n\t\tglpoly_t *p = GLC_LightmapChain(i);\n\n\t\tif (p) {\n\t\t\tGLC_LightmapUpdate(i);\n\t\t\trenderer.TextureUnitBind(0, GLC_LightmapTexture(i));\n\n\t\t\tfor (; p; p = p->chain) {\n\t\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t\t}\n\n\t\t\tif (index_count) {\n\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\t\tindex_count = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tGLC_ClearLightmapPolys();\n}\n\nstatic void GLC_BlendLightmaps(void)\n{\n\tint i, j;\n\tglpoly_t *p;\n\tfloat *v;\n\tqbool use_vbo = buffers.supported && modelIndexes;\n\n\tR_ProgramUse(r_program_none);\n\tGLC_StateBeginBlendLightmaps(use_vbo);\n\t\n\tfor (i = 0; i < GLC_LightmapCount(); i++) {\n\t\tif (!(p = GLC_LightmapChain(i))) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tGLC_LightmapUpdate(i);\n\t\trenderer.TextureUnitBind(0, GLC_LightmapTexture(i));\n\t\tif (use_vbo) {\n\t\t\tGLuint index_count = 0;\n\n\t\t\tfor (; p; p = p->chain) {\n\t\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t\t}\n\n\t\t\tif (index_count) {\n\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tfor (; p; p = p->chain) {\n\t\t\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\t\t\tv = p->verts[0];\n\t\t\t\tfor (j = 0; j < p->numverts; j++, v += VERTEXSIZE) {\n\t\t\t\t\tglTexCoord2f(v[5], v[6]);\n\t\t\t\t\tGLC_Vertex3fv(v);\n\t\t\t\t}\n\t\t\t\tGLC_End();\n\t\t\t}\n\t\t}\n\t}\n\tGLC_ClearLightmapPolys();\n}\n\n//draws transparent textures for HL world and nonworld models\nvoid GLC_DrawAlphaChain(msurface_t* alphachain, frameStatsPolyType polyType)\n{\n\tint k;\n\tmsurface_t *s;\n\tfloat *v;\n\n\tif (!alphachain) {\n\t\treturn;\n\t}\n\n\tR_ProgramUse(r_program_none);\n\tGLC_StateBeginAlphaChain();\n\n\tfor (s = alphachain; s; s = s->texturechain) {\n\t\t++frameStats.classic.polycount[polyType];\n\t\tR_RenderDynamicLightmaps(s, false);\n\n\t\tGLC_StateBeginAlphaChainSurface(s);\n\n\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\tv = s->polys->verts[0];\n\t\tfor (k = 0; k < s->polys->numverts; k++, v += VERTEXSIZE) {\n\t\t\tif (gl_mtexable) {\n\t\t\t\tGLC_MultiTexCoord2f(GL_TEXTURE0, v[3], v[4]);\n\t\t\t\tGLC_MultiTexCoord2f(GL_TEXTURE1, v[5], v[6]);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tglTexCoord2f(v[3], v[4]);\n\t\t\t}\n\t\t\tGLC_Vertex3fv(v);\n\t\t}\n\t\tGLC_End();\n\t}\n\talphachain = NULL;\n}\n\nint GLC_BrushModelCopyVertToBuffer(model_t* mod, void* vbo_buffer_, int position, float* source, int lightmap, int material, float scaleS, float scaleT, msurface_t* surf, qbool has_fb_texture, qbool has_luma_texture)\n{\n\tglc_vbo_world_vert_t* target = (glc_vbo_world_vert_t*)vbo_buffer_ + position;\n\n\tVectorCopy(source, target->position);\n\ttarget->material_coords[0] = source[3];\n\ttarget->material_coords[1] = source[4];\n\ttarget->lightmap_coords[0] = source[5];\n\ttarget->lightmap_coords[1] = source[6];\n\ttarget->lightmap_coords[2] = lightmap;\n\ttarget->detail_coords[0] = source[7];\n\ttarget->detail_coords[1] = source[8];\n\tif (scaleS) {\n\t\ttarget->material_coords[0] *= scaleS;\n\t}\n\tif (scaleT) {\n\t\ttarget->material_coords[1] *= scaleT;\n\t}\n\n\ttarget->flatstyle = 0;\n\tif (surf->flags & SURF_DRAWSKY) {\n\t\ttarget->flatstyle = 1;\n\t}\n\telse if (surf->flags & SURF_DRAWTURB) {\n\t\tif (surf->texinfo->texture->turbType == TEXTURE_TURB_WATER) {\n\t\t\ttarget->flatstyle = 2;\n\t\t}\n\t\telse if (surf->texinfo->texture->turbType == TEXTURE_TURB_SLIME) {\n\t\t\ttarget->flatstyle = 4;\n\t\t}\n\t\telse if (surf->texinfo->texture->turbType == TEXTURE_TURB_LAVA) {\n\t\t\ttarget->flatstyle = 8;\n\t\t}\n\t\telse if (surf->texinfo->texture->turbType == TEXTURE_TURB_TELE) {\n\t\t\ttarget->flatstyle = 16;\n\t\t}\n\t\telse if (surf->texinfo->texture->turbType == TEXTURE_TURB_SKY) {\n\t\t\ttarget->flatstyle = 32;\n\t\t}\n\t}\n\telse if (mod->isworldmodel) {\n\t\ttarget->flatstyle = (surf->flags & SURF_DRAWFLAT_FLOOR ? 64 : 128);\n\t}\n\n\ttarget->flatstyle += (has_luma_texture ? 256 : 0);\n\ttarget->flatstyle += (mod->isworldmodel && (surf->flags & SURF_UNDERWATER)) ? 512 : 0;\n\ttarget->flatstyle += (has_fb_texture ? 1024 : 0);\n\n\treturn position + 1;\n}\n\nvoid GLC_ChainBrushModelSurfaces(model_t* clmodel, entity_t* ent)\n{\n\textern void GLC_EmitWaterPoly(msurface_t* fa);\n\tqbool glc_first_water_poly = true;\n\tmsurface_t* psurf;\n\tint i;\n\tqbool drawFlatFloors = clmodel->isworldmodel && (r_drawflat.integer == 2 || r_drawflat.integer == 1) && r_drawflat_mode.integer == 0;\n\tqbool drawFlatWalls = clmodel->isworldmodel && (r_drawflat.integer == 3 || r_drawflat.integer == 1) && r_drawflat_mode.integer == 0;\n\textern msurface_t* skychain;\n\textern msurface_t* alphachain;\n\n\tpsurf = &clmodel->surfaces[clmodel->firstmodelsurface];\n\tfor (i = 0; i < clmodel->nummodelsurfaces; i++, psurf++) {\n\t\t// find which side of the node we are on\n\t\tmplane_t* pplane = psurf->plane;\n\t\tfloat dot = PlaneDiff(modelorg, pplane);\n\t\tqbool isFloor = (psurf->flags & SURF_DRAWFLAT_FLOOR);\n\n\t\t//draw the water surfaces now, and setup sky/normal chains\n\t\tif (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||\n\t\t\t(!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) {\n\t\t\tif (psurf->flags & SURF_DRAWSKY) {\n\t\t\t\tif (r_fastsky.integer) {\n\t\t\t\t\tCHAIN_SURF_B2F(psurf, clmodel->drawflat_chain);\n\t\t\t\t\tclmodel->drawflat_todo = true;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tCHAIN_SURF_B2F(psurf, skychain);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (psurf->flags & SURF_DRAWTURB) {\n\t\t\t\tif (glc_first_water_poly) {\n\t\t\t\t\tGLC_StateBeginWaterSurfaces();\n\t\t\t\t\tglc_first_water_poly = false;\n\t\t\t\t}\n\t\t\t\tGLC_EmitWaterPoly(psurf);\n\t\t\t}\n\t\t\telse if (psurf->flags & SURF_DRAWALPHA) {\n\t\t\t\tCHAIN_SURF_B2F(psurf, alphachain);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (drawFlatFloors && isFloor) {\n\t\t\t\t\tR_AddDrawflatChainSurface(psurf, false);\n\t\t\t\t\tclmodel->drawflat_todo = true;\n\t\t\t\t}\n\t\t\t\telse if (drawFlatWalls && !isFloor) {\n\t\t\t\t\tR_AddDrawflatChainSurface(psurf, true);\n\t\t\t\t\tclmodel->drawflat_todo = true;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tchain_surfaces_by_lightmap(&psurf->texinfo->texture->texturechain, psurf);\n\n\t\t\t\t\tclmodel->texturechains_have_lumas |= R_TextureAnimation(ent, psurf->texinfo->texture)->isLumaTexture;\n\t\t\t\t\tclmodel->first_texture_chained = min(clmodel->first_texture_chained, psurf->texinfo->miptex);\n\t\t\t\t\tclmodel->last_texture_chained = max(clmodel->last_texture_chained, psurf->texinfo->miptex);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_draw.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\nCopyright (C) 2017-2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"glm_draw.h\"\n#include \"r_draw.h\"\n#include \"r_state.h\"\n#include \"r_matrix.h\"\n#include \"r_texture.h\"\n#include \"glc_vao.h\"\n#include \"glsl/constants.glsl\" // FIXME: remove\n#include \"glc_local.h\"\n#include \"r_renderer.h\"\n#include \"tr_types.h\"\n#include \"gl_texture.h\"\n\nstatic texture_ref glc_last_texture_used;\nstatic int extraIndexesPerImage;\nextern float overall_alpha;\nextern float cachedMatrix[16];\n\nqbool GLC_ProgramHudImagesCompile(void);\n\nvoid GLC_DrawDisc(void)\n{\n\n}\n\nvoid GLC_HudDrawComplete(void)\n{\n\tif (R_TextureReferenceIsValid(glc_last_texture_used)) {\n\t\trenderer.TextureSetFiltering(glc_last_texture_used, texture_minification_linear, texture_magnification_linear);\n\t}\n}\n\nvoid GLC_HudDrawCircles(texture_ref texture, int start, int end)\n{\n\t// FIXME: Not very efficient (but rarely used either)\n\tint i, j;\n\n\tstart = max(0, start);\n\tend = min(end, circleData.circleCount - 1);\n\n\tR_ProgramUse(r_program_none);\n\tfor (i = start; i <= end; ++i) {\n\t\tGLC_StateBeginDrawAlphaPieSliceRGB(circleData.drawCircleThickness[i]);\n\t\tR_CustomColor(\n\t\t\tcircleData.drawCircleColors[i][0],\n\t\t\tcircleData.drawCircleColors[i][1],\n\t\t\tcircleData.drawCircleColors[i][2],\n\t\t\tcircleData.drawCircleColors[i][3]\n\t\t);\n\n\t\tGLC_Begin(circleData.drawCircleFill[i] ? GL_TRIANGLE_STRIP : GL_LINE_LOOP);\n\t\tfor (j = 0; j < circleData.drawCirclePoints[i]; ++j) {\n\t\t\tGLC_Vertex2fv(&circleData.drawCirclePointData[i * FLOATS_PER_CIRCLE + j * 2]);\n\t\t}\n\t\tGLC_End();\n\t}\n}\n\nvoid GLC_HudPrepareCircles(void)\n{\n\n}\n\nstatic void GLC_HudDrawLinesVertexArray(r_program_id program_id, texture_ref texture, int start, int end)\n{\n\textern cvar_t gl_vbo_clientmemory;\n\tuintptr_t offset = gl_vbo_clientmemory.integer ? 0 : buffers.BufferOffset(r_buffer_hud_image_vertex_data) / sizeof(imageData.images[0]);\n\tint i;\n\n\tR_ProgramUse(program_id);\n\tR_ApplyRenderingState(program_id == r_program_none ? r_state_hud_images_glc_non_glsl : r_state_hud_images_glc);\n\trenderer.TextureUnitBind(0, texture);\n\tfor (i = start; i <= end; ++i) {\n\t\tR_StateBeginAlphaLineRGB(lineData.line_thickness[i]);\n\n\t\tGL_DrawArrays(GL_LINES, offset + lineData.imageIndex[i], 2);\n\t}\n}\n\nstatic void GLC_HudDrawLinesImmediate(texture_ref texture, int start, int end)\n{\n\tint i;\n\n\tR_ProgramUse(r_program_none);\n\tR_ApplyRenderingState(r_state_hud_images_glc_non_glsl);\n\trenderer.TextureUnitBind(0, texture);\n\n\tfor (i = start; i <= end; ++i) {\n\t\tglm_image_t* img = &imageData.images[lineData.imageIndex[i]];\n\n\t\tR_StateBeginAlphaLineRGB(lineData.line_thickness[i]);\n\t\tglTexCoord2f(img[0].tex[0], img[0].tex[1]);\n\n\t\tGLC_Begin(GL_LINES);\n\t\tR_CustomColor4ubv(img[0].colour);\n\t\tGLC_Vertex2fv(img[0].pos);\n\t\tR_CustomColor4ubv(img[1].colour);\n\t\tGLC_Vertex2fv(img[1].pos);\n\t\tGLC_End();\n\t}\n}\n\nvoid GLC_HudDrawLines(texture_ref texture, int start, int end)\n{\n\textern cvar_t gl_program_hud;\n\n\tif (buffers.supported && gl_program_hud.integer && GLC_ProgramHudImagesCompile()) {\n\t\tGLC_HudDrawLinesVertexArray(r_program_hud_images_glc, texture, start, end);\n\t}\n\telse if (R_VAOBound()) {\n\t\tGLC_HudDrawLinesVertexArray(r_program_none, texture, start, end);\n\t}\n\telse {\n\t\tGLC_HudDrawLinesImmediate(texture, start, end);\n\t}\n}\n\nstatic void GLC_HudDrawPolygonsImmediate(texture_ref texture, int start, int end)\n{\n\tint i, j;\n\n\tR_ProgramUse(r_program_none);\n\tR_ApplyRenderingState(r_state_hud_images_glc_non_glsl);\n\trenderer.TextureUnitBind(0, texture);\n\n\tfor (i = start; i <= end; ++i) {\n\t\tglm_image_t* img = &imageData.images[polygonData.polygonImageIndexes[i]];\n\n\t\tR_CustomColor4ubv(img[0].colour);\n\t\tglTexCoord2f(img[0].tex[0], img[0].tex[1]);\n\n\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\tfor (j = 0; j < polygonData.polygonVerts[i]; j++) {\n\t\t\tGLC_Vertex2fv(img[j].pos);\n\t\t}\n\t\tGLC_End();\n\t}\n}\n\nstatic void GLC_HudDrawPolygonsVertexArray(r_program_id program_id, texture_ref texture, int start, int end)\n{\n\textern cvar_t gl_vbo_clientmemory;\n\tuintptr_t offset = gl_vbo_clientmemory.integer ? 0 : buffers.BufferOffset(r_buffer_hud_image_vertex_data) / sizeof(imageData.images[0]);\n\tint i;\n\n\tR_ProgramUse(program_id);\n\tR_ApplyRenderingState(program_id == r_program_none ? r_state_hud_images_glc_non_glsl : r_state_hud_images_glc);\n\trenderer.TextureUnitBind(0, texture);\n\n\tfor (i = start; i <= end; ++i) {\n\t\tGL_DrawArrays(GL_TRIANGLE_STRIP, offset + polygonData.polygonImageIndexes[i], polygonData.polygonVerts[i]);\n\t}\n}\n\nvoid GLC_HudDrawPolygons(texture_ref texture, int start, int end)\n{\n\textern cvar_t gl_program_hud;\n\n\tif (buffers.supported && gl_program_hud.integer && GLC_ProgramHudImagesCompile()) {\n\t\tGLC_HudDrawPolygonsVertexArray(r_program_hud_images_glc, texture, start, end);\n\t}\n\telse if (R_VAOBound()) {\n\t\tGLC_HudDrawPolygonsVertexArray(r_program_none, texture, start, end);\n\t}\n\telse {\n\t\tGLC_HudDrawPolygonsImmediate(texture, start, end);\n\t}\n}\n\nstatic void GLC_SetCoordinates(glm_image_t* targ, float x1, float y1, float x2, float y2, float s, float s_width, float t, float t_height, qbool nearest)\n{\n\tfloat v1[4] = { x1, y1, 0, 1 };\n\tfloat v2[4] = { x2, y2, 0, 1 };\n\n\tR_MultiplyVector(cachedMatrix, v1, v1);\n\tR_MultiplyVector(cachedMatrix, v2, v2);\n\n\t// TL TL BR BR\n\ttarg[0].pos[0] = v1[0]; targ[0].tex[0] = s;\n\ttarg[1].pos[0] = v1[0]; targ[1].tex[0] = s;\n\ttarg[2].pos[0] = v2[0]; targ[2].tex[0] = s + s_width;\n\ttarg[3].pos[0] = v2[0]; targ[3].tex[0] = s + s_width;\n\t// TL BR TL BR\n\ttarg[0].pos[1] = v1[1]; targ[0].tex[1] = t;\n\ttarg[1].pos[1] = v2[1]; targ[1].tex[1] = t + t_height;\n\ttarg[2].pos[1] = v1[1]; targ[2].tex[1] = t;\n\ttarg[3].pos[1] = v2[1]; targ[3].tex[1] = t + t_height;\n\n\ttarg[0].tex[3] = targ[1].tex[3] = targ[2].tex[3] = targ[3].tex[3] = nearest ? 0.0f : 1.0f;\n}\n\nvoid GLC_DrawRectangle(float x, float y, float width, float height, byte* color)\n{\n\ttexture_ref solid_texture;\n\tfloat s, t;\n\tfloat alpha;\n\tglm_image_t* img = &imageData.images[imageData.imageCount * 4];\n\n\tif (imageData.imageCount >= MAX_MULTI_IMAGE_BATCH) {\n\t\treturn;\n\t}\n\n\tAtlas_SolidTextureCoordinates(&solid_texture, &s, &t);\n\tif (!R_LogCustomImageTypeWithTexture(imagetype_image, imageData.imageCount, solid_texture)) {\n\t\treturn;\n\t}\n\n\talpha = (color[3] * overall_alpha / 255.0f);\n\n\timg[0].colour[0] = img[1].colour[0] = img[2].colour[0] = img[3].colour[0] = color[0] * alpha;\n\timg[0].colour[1] = img[1].colour[1] = img[2].colour[1] = img[3].colour[1] = color[1] * alpha;\n\timg[0].colour[2] = img[1].colour[2] = img[2].colour[2] = img[3].colour[2] = color[2] * alpha;\n\timg[0].colour[3] = img[1].colour[3] = img[2].colour[3] = img[3].colour[3] = color[3] * overall_alpha;\n\tGLC_SetCoordinates(img, x, y, x + width, y + height, s, 0, t, 0, false);\n\n\t++imageData.imageCount;\n}\n\nvoid GLC_DrawImage(float x, float y, float width, float height, float tex_s, float tex_t, float tex_width, float tex_height, byte* color, int flags)\n{\n\tint imageIndex = imageData.imageCount * 4;\n\tglm_image_t* img = &imageData.images[imageData.imageCount * 4];\n\tfloat alpha = (color[3] * overall_alpha / 255.0f);\n\n\timg[0].colour[0] = img[1].colour[0] = img[2].colour[0] = img[3].colour[0] = color[0] * alpha;\n\timg[0].colour[1] = img[1].colour[1] = img[2].colour[1] = img[3].colour[1] = color[1] * alpha;\n\timg[0].colour[2] = img[1].colour[2] = img[2].colour[2] = img[3].colour[2] = color[2] * alpha;\n\timg[0].colour[3] = img[1].colour[3] = img[2].colour[3] = img[3].colour[3] = color[3] * overall_alpha;\n\tGLC_SetCoordinates(&imageData.images[imageIndex], x, y, x + width, y + height, tex_s, tex_width, tex_t, tex_height, flags & IMAGEPROG_FLAGS_NEAREST);\n\n\timageData.images[imageData.imageCount].flags = flags;\n\t++imageData.imageCount;\n}\n\nvoid GLC_AdjustImages(int first, int last, float x_offset)\n{\n\tint i;\n\n\tfor (i = first; i < last; ++i) {\n\t\timageData.images[i * 4 + 0].pos[0] += x_offset;\n\t\timageData.images[i * 4 + 1].pos[0] += x_offset;\n\t\timageData.images[i * 4 + 2].pos[0] += x_offset;\n\t\timageData.images[i * 4 + 3].pos[0] += x_offset;\n\t}\n}\n\nstatic void GLC_CreateImageVAO(void)\n{\n\textern cvar_t gl_vbo_clientmemory;\n\n\tR_TraceEnterFunctionRegion;\n\tR_TraceAPI(\"clientmemory: %s\", gl_vbo_clientmemory.integer ? \"true\" : \"false\");\n\tif (gl_vbo_clientmemory.integer) {\n\t\tR_GenVertexArray(vao_hud_images);\n\t\tGLC_VAOSetIndexBuffer(vao_hud_images, r_buffer_hud_image_index_data);\n\t\tGLC_VAOEnableVertexPointer(vao_hud_images, 2, GL_FLOAT, sizeof(glm_image_t), (GLvoid*)&imageData.images[0].pos);\n\t\tGLC_VAOEnableTextureCoordPointer(vao_hud_images, 0, 4, GL_FLOAT, sizeof(glm_image_t), (GLvoid*)&imageData.images[0].tex);\n\t\tGLC_VAOEnableColorPointer(vao_hud_images, 4, GL_UNSIGNED_BYTE, sizeof(glm_image_t), (GLvoid*)&imageData.images[0].colour);\n\t\tR_BindVertexArray(vao_none);\n\n\t\t// A copy of the above, but only 2 texture coordinates sent\n\t\tR_GenVertexArray(vao_hud_images_non_glsl);\n\t\tGLC_VAOSetIndexBuffer(vao_hud_images_non_glsl, r_buffer_hud_image_index_data);\n\t\tGLC_VAOEnableVertexPointer(vao_hud_images_non_glsl, 2, GL_FLOAT, sizeof(glm_image_t), (GLvoid*)&imageData.images[0].pos);\n\t\tGLC_VAOEnableTextureCoordPointer(vao_hud_images_non_glsl, 0, 2, GL_FLOAT, sizeof(glm_image_t), (GLvoid*)&imageData.images[0].tex);\n\t\tGLC_VAOEnableColorPointer(vao_hud_images_non_glsl, 4, GL_UNSIGNED_BYTE, sizeof(glm_image_t), (GLvoid*)&imageData.images[0].colour);\n\t\tR_BindVertexArray(vao_none);\n\t}\n\telse {\n\t\tR_GenVertexArray(vao_hud_images);\n\t\tGLC_VAOSetIndexBuffer(vao_hud_images, r_buffer_hud_image_index_data);\n\t\tGLC_VAOSetVertexBuffer(vao_hud_images, r_buffer_hud_image_vertex_data);\n\t\tGLC_VAOEnableVertexPointer(vao_hud_images, 2, GL_FLOAT, sizeof(glm_image_t), VBO_FIELDOFFSET(glm_image_t, pos));\n\t\tGLC_VAOEnableTextureCoordPointer(vao_hud_images, 0, 4, GL_FLOAT, sizeof(glm_image_t), VBO_FIELDOFFSET(glm_image_t, tex));\n\t\tGLC_VAOEnableColorPointer(vao_hud_images, 4, GL_UNSIGNED_BYTE, sizeof(glm_image_t), VBO_FIELDOFFSET(glm_image_t, colour));\n\t\tR_BindVertexArray(vao_none);\n\n\t\t// A copy of the above, but only 2 texture coordinates sent\n\t\tR_GenVertexArray(vao_hud_images_non_glsl);\n\t\tGLC_VAOSetIndexBuffer(vao_hud_images_non_glsl, r_buffer_hud_image_index_data);\n\t\tGLC_VAOSetVertexBuffer(vao_hud_images_non_glsl, r_buffer_hud_image_vertex_data);\n\t\tGLC_VAOEnableVertexPointer(vao_hud_images_non_glsl, 2, GL_FLOAT, sizeof(glm_image_t), VBO_FIELDOFFSET(glm_image_t, pos));\n\t\tGLC_VAOEnableTextureCoordPointer(vao_hud_images_non_glsl, 0, 2, GL_FLOAT, sizeof(glm_image_t), VBO_FIELDOFFSET(glm_image_t, tex));\n\t\tGLC_VAOEnableColorPointer(vao_hud_images_non_glsl, 4, GL_UNSIGNED_BYTE, sizeof(glm_image_t), VBO_FIELDOFFSET(glm_image_t, colour));\n\t\tR_BindVertexArray(vao_none);\n\t}\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_HudPrepareImages(void)\n{\n\textern cvar_t gl_vbo_clientmemory;\n\n\tR_TextureReferenceInvalidate(glc_last_texture_used);\n\n\tif (buffers.supported && !gl_vbo_clientmemory.integer) {\n\t\tif (!R_BufferReferenceIsValid(r_buffer_hud_image_vertex_data)) {\n\t\t\tbuffers.Create(r_buffer_hud_image_vertex_data, buffertype_vertex, \"image-vbo\", sizeof(imageData.images), imageData.images, bufferusage_once_per_frame);\n\t\t}\n\t\telse {\n\t\t\tbuffers.Update(r_buffer_hud_image_vertex_data, sizeof(imageData.images[0]) * imageData.imageCount * 4, imageData.images);\n\t\t}\n\t}\n\n\tif (buffers.supported && !R_BufferReferenceIsValid(r_buffer_hud_image_index_data)) {\n\t\tR_BindVertexArray(vao_none);\n\t\tif (!R_BufferReferenceIsValid(r_buffer_hud_image_index_data)) {\n\t\t\tunsigned int data[MAX_MULTI_IMAGE_BATCH * 6 * 3];\n\t\t\tint pos, i;\n\n\t\t\tfor (i = 0, pos = 0; i < MAX_MULTI_IMAGE_BATCH * 3; ++i) {\n\t\t\t\tif (i && !GL_Supported(R_SUPPORT_PRIMITIVERESTART)) {\n\t\t\t\t\tdata[pos++] = i * 4;\n\t\t\t\t}\n\t\t\t\tdata[pos++] = i * 4;\n\t\t\t\tdata[pos++] = i * 4 + 1;\n\t\t\t\tdata[pos++] = i * 4 + 2;\n\t\t\t\tdata[pos++] = i * 4 + 3;\n\t\t\t\tif (GL_Supported(R_SUPPORT_PRIMITIVERESTART)) {\n\t\t\t\t\tdata[pos++] = ~(unsigned int)0;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tdata[pos++] = i * 4 + 3;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbuffers.Create(r_buffer_hud_image_index_data, buffertype_index, \"hudimage-elements\", pos * sizeof(unsigned int), data, bufferusage_reuse_many_frames);\n\t\t\textraIndexesPerImage = GL_Supported(R_SUPPORT_PRIMITIVERESTART) ? 1 : 2;\n\t\t}\n\t}\n\n\tR_TraceAPI(\"%s pre-check: %s, %s\", __func__, (buffers.supported ? \"buffers-supported\" : \"no-buffers\"), R_VertexArrayCreated(vao_hud_images) ? \"vao-created\" : \"vao-not-created\");\n\tif (buffers.supported && !R_VertexArrayCreated(vao_hud_images)) {\n\t\tGLC_CreateImageVAO();\n\t}\n}\n\nstatic void GLC_HudDrawImagesVertexArray(texture_ref ref, int start, int end)\n{\n\textern cvar_t gl_vbo_clientmemory;\n\tuintptr_t offset = gl_vbo_clientmemory.integer ? 0 : buffers.BufferOffset(r_buffer_hud_image_vertex_data) / (sizeof(imageData.images[0]) * 4);\n\tqbool nearest = (imageData.images[start].flags & IMAGEPROG_FLAGS_NEAREST);\n\tint i;\n\n\tR_ProgramUse(r_program_none);\n\trenderer.TextureSetFiltering(ref, nearest ? texture_minification_nearest : texture_minification_linear, nearest ? texture_magnification_nearest : texture_magnification_linear);\n\trenderer.TextureUnitBind(0, ref);\n\tfor (i = start; i < end; ++i) {\n\t\tif (R_TextureReferenceIsValid(ref) && nearest != (imageData.images[i].flags & IMAGEPROG_FLAGS_NEAREST)) {\n\t\t\tif (i > start) {\n\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, (i - start) * (4 + extraIndexesPerImage), GL_UNSIGNED_INT, (void*)((offset + start) * (4 + extraIndexesPerImage) * sizeof(GLuint)));\n\t\t\t}\n\n\t\t\tnearest = (imageData.images[i].flags & IMAGEPROG_FLAGS_NEAREST);\n\t\t\tstart = i;\n\n\t\t\trenderer.TextureSetFiltering(ref, nearest ? texture_minification_nearest : texture_minification_linear, nearest ? texture_magnification_nearest : texture_magnification_linear);\n\t\t}\n\t}\n\n\tGL_DrawElements(GL_TRIANGLE_STRIP, (end - start + 1) * (4 + extraIndexesPerImage), GL_UNSIGNED_INT, (void*)((offset + start) * (4 + extraIndexesPerImage) * sizeof(GLuint)));\n}\n\n#define GLC_PROGRAMFLAGS_SMOOTHCROSSHAIR     1\n#define GLC_PROGRAMFLAGS_SMOOTHIMAGES        2\n#define GLC_PROGRAMFLAGS_SMOOTHTEXT          4\n#define GLC_PROGRAMFLAGS_ALPHAHACK           8\n#define GLC_PROGRAMFLAGS_SMOOTHEVERYTHING    (GLC_PROGRAMFLAGS_SMOOTHCROSSHAIR | GLC_PROGRAMFLAGS_SMOOTHIMAGES | GLC_PROGRAMFLAGS_SMOOTHTEXT)\n\nqbool GLC_ProgramHudImagesCompile(void)\n{\n\textern cvar_t r_smoothtext, r_smoothcrosshair, r_smoothimages, r_smoothalphahack;\n\tint flags =\n\t\t(r_smoothtext.integer ? GLC_PROGRAMFLAGS_SMOOTHTEXT : 0) |\n\t\t(r_smoothcrosshair.integer ? GLC_PROGRAMFLAGS_SMOOTHCROSSHAIR : 0) |\n\t\t(r_smoothimages.integer ? GLC_PROGRAMFLAGS_SMOOTHIMAGES : 0) |\n\t\t(r_smoothalphahack.integer ? GLC_PROGRAMFLAGS_ALPHAHACK : 0);\n\t\n\tif (R_ProgramRecompileNeeded(r_program_hud_images_glc, flags)) {\n\t\tstatic char included_definitions[512];\n\t\tint smooth_flags = (flags & GLC_PROGRAMFLAGS_SMOOTHEVERYTHING);\n\t\tqbool mixed_sampling = GL_Supported(R_SUPPORT_TEXTURE_SAMPLERS) && smooth_flags != 0 && smooth_flags != GLC_PROGRAMFLAGS_SMOOTHEVERYTHING;\n\n\t\tmemset(included_definitions, 0, sizeof(included_definitions));\n\t\tif (mixed_sampling) {\n\t\t\tstrlcat(included_definitions, \"#define MIXED_SAMPLING\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (flags & GLC_PROGRAMFLAGS_ALPHAHACK) {\n\t\t\tstrlcat(included_definitions, \"#define PREMULT_ALPHA_HACK\\n\", sizeof(included_definitions));\n\t\t}\n\n\t\tR_ProgramCompileWithInclude(r_program_hud_images_glc, included_definitions);\n\t\tR_ProgramUniform1i(r_program_uniform_hud_images_glc_primarySampler, 0);\n\t\tif (mixed_sampling) {\n\t\t\tR_ProgramUniform1i(r_program_uniform_hud_images_glc_secondarySampler, 1);\n\t\t}\n\t\tR_ProgramSetCustomOptions(r_program_hud_images_glc, flags);\n\t}\n\n\treturn R_ProgramReady(r_program_hud_images_glc);\n}\n\nstatic void GLC_HudDrawImagesProgram(r_program_id program_id, texture_ref ref, int start, int end)\n{\n\textern cvar_t gl_vbo_clientmemory;\n\tuintptr_t offset = gl_vbo_clientmemory.integer ? 0 : buffers.BufferOffset(r_buffer_hud_image_vertex_data) / (sizeof(imageData.images[0]) * 4);\n\tint i;\n\n\tR_ProgramUse(program_id);\n\tif (program_id != r_program_none && GL_Supported(R_SUPPORT_TEXTURE_SAMPLERS)) {\n\t\tqbool everything_nearest = (R_ProgramCustomOptions(r_program_hud_images_glc) & GLC_PROGRAMFLAGS_SMOOTHEVERYTHING) == 0;\n\t\tqbool everything_linear = (R_ProgramCustomOptions(r_program_hud_images_glc) & GLC_PROGRAMFLAGS_SMOOTHEVERYTHING) == GLC_PROGRAMFLAGS_SMOOTHEVERYTHING;\n\n\t\trenderer.TextureUnitBind(0, ref);\n\t\tif (everything_nearest) {\n\t\t\t// Everything is GL_NEAREST, no smoothing\n\t\t\tGL_SamplerSetNearest(0);\n\t\t}\n\t\telse if (!everything_linear) {\n\t\t\t// Mixed, second texture unit for GL_NEAREST\n\t\t\trenderer.TextureUnitBind(1, ref);\n\t\t\tGL_SamplerSetNearest(1);\n\t\t}\n\n\t\tGL_DrawElements(GL_TRIANGLE_STRIP, (end - start + 1) * (4 + extraIndexesPerImage), GL_UNSIGNED_INT, (void*)((offset + start) * (4 + extraIndexesPerImage) * sizeof(GLuint)));\n\n\t\tif (everything_nearest) {\n\t\t\tGL_SamplerClear(0);\n\t\t}\n\t\telse if (!everything_linear) {\n\t\t\tGL_SamplerClear(1);\n\t\t}\n\t}\n\telse {\n\t\tqbool nearest = (imageData.images[start].flags & IMAGEPROG_FLAGS_NEAREST);\n\t\trenderer.TextureSetFiltering(ref, nearest ? texture_minification_nearest : texture_minification_linear, nearest ? texture_magnification_nearest : texture_magnification_linear);\n\t\trenderer.TextureUnitBind(0, ref);\n\t\tfor (i = start; i < end; ++i) {\n\t\t\tif (R_TextureReferenceIsValid(ref) && nearest != (imageData.images[i].flags & IMAGEPROG_FLAGS_NEAREST)) {\n\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, (i - start) * (4 + extraIndexesPerImage), GL_UNSIGNED_INT, (void*)((offset + start) * (4 + extraIndexesPerImage) * sizeof(GLuint)));\n\n\t\t\t\tnearest = (imageData.images[i].flags & IMAGEPROG_FLAGS_NEAREST);\n\t\t\t\tstart = i;\n\n\t\t\t\trenderer.TextureSetFiltering(ref, nearest ? texture_minification_nearest : texture_minification_linear, nearest ? texture_magnification_nearest : texture_magnification_linear);\n\t\t\t}\n\t\t}\n\n\t\tGL_DrawElements(GL_TRIANGLE_STRIP, (end - start + 1) * (4 + extraIndexesPerImage), GL_UNSIGNED_INT, (void*)((offset + start) * (4 + extraIndexesPerImage) * sizeof(GLuint)));\n\t}\n\tR_ProgramUse(r_program_none);\n}\n\nstatic void GLC_HudDrawImages_Immediate(texture_ref ref, int start, int end)\n{\n\tint i;\n\tbyte current_color[4];\n\tqbool nearest = (imageData.images[start].flags & IMAGEPROG_FLAGS_NEAREST);\n\n\trenderer.TextureSetFiltering(ref, nearest ? texture_minification_nearest : texture_minification_linear, nearest ? texture_magnification_nearest : texture_magnification_linear);\n\trenderer.TextureUnitBind(0, ref);\n\tR_CustomColor4ubv(imageData.images[start * 4].colour);\n\tmemcpy(current_color, imageData.images[start * 4].colour, sizeof(current_color));\n\n\tR_ProgramUse(r_program_none);\n\tGLC_Begin(GL_QUADS);\n\tfor (i = start * 4; i <= 4 * end + 3; i += 4) {\n\t\tglm_image_t* next = &imageData.images[i];\n\n\t\tR_CustomColor4ubv(next->colour);\n\n\t\tif (R_TextureReferenceIsValid(ref) && nearest != (imageData.images[i / 4].flags & IMAGEPROG_FLAGS_NEAREST)) {\n\t\t\tGLC_End();\n\n\t\t\tnearest = (imageData.images[i / 4].flags & IMAGEPROG_FLAGS_NEAREST);\n\t\t\trenderer.TextureSetFiltering(ref, nearest ? texture_minification_nearest : texture_minification_linear, nearest ? texture_magnification_nearest : texture_magnification_linear);\n\n\t\t\tGLC_Begin(GL_QUADS);\n\t\t}\n\n\t\tglTexCoord2f(next[0].tex[0], next[0].tex[1]);\n\t\tGLC_Vertex2f(next[0].pos[0], next[0].pos[1]);\n\t\tglTexCoord2f(next[2].tex[0], next[2].tex[1]);\n\t\tGLC_Vertex2f(next[2].pos[0], next[2].pos[1]);\n\t\tglTexCoord2f(next[3].tex[0], next[3].tex[1]);\n\t\tGLC_Vertex2f(next[3].pos[0], next[3].pos[1]);\n\t\tglTexCoord2f(next[1].tex[0], next[1].tex[1]);\n\t\tGLC_Vertex2f(next[1].pos[0], next[1].pos[1]);\n\t}\n\tGLC_End();\n}\n\nvoid GLC_HudDrawImages(texture_ref ref, int start, int end)\n{\n\textern cvar_t gl_program_hud;\n\n\tR_TraceEnterFunctionRegion;\n\tif (R_TextureReferenceIsValid(glc_last_texture_used) && !R_TextureReferenceEqual(glc_last_texture_used, ref)) {\n\t\trenderer.TextureSetFiltering(glc_last_texture_used, texture_minification_linear, texture_magnification_linear);\n\t}\n\tglc_last_texture_used = ref;\n\n\tif (buffers.supported && gl_program_hud.integer && GLC_ProgramHudImagesCompile()) {\n\t\tGLC_StateBeginImageDraw(imageData.images[start].flags & IMAGEPROG_FLAGS_TEXT);\n\t\tGLC_HudDrawImagesProgram(r_program_hud_images_glc, ref, start, end);\n\t}\n\telse if (R_VAOBound()) {\n\t\tGLC_StateBeginImageDrawNonGLSL(imageData.images[start].flags & IMAGEPROG_FLAGS_TEXT);\n\t\tGLC_HudDrawImagesVertexArray(ref, start, end);\n\t}\n\telse {\n\t\tGLC_StateBeginImageDrawNonGLSL(imageData.images[start].flags & IMAGEPROG_FLAGS_TEXT);\n\t\tGLC_HudDrawImages_Immediate(ref, start, end);\n\t}\n\tR_TraceLeaveFunctionRegion;\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_framebuffer.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// GLM_FrameBuffer.c\n// Framebuffer support in OpenGL core\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n// For the moment, also contains general framebuffer code as immediate-mode isn't supported\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"glc_local.h\"\n#include \"gl_framebuffer.h\"\n#include \"tr_types.h\"\n#include \"r_buffers.h\"\n#include \"r_state.h\"\n#include \"r_program.h\"\n#include \"r_renderer.h\"\n#include \"r_matrix.h\"\n#include \"r_texture.h\"\n\n#define POST_PROCESS_PALETTE    1\n#define POST_PROCESS_3DONLY     2\n#define POST_PROCESS_FXAA       4\n\nstatic texture_ref non_framebuffer_screen_texture;\n\nqbool GLC_CompilePostProcessProgram(void)\n{\n\textern cvar_t vid_software_palette, vid_framebuffer;\n\tint fxaa_preset = GL_FramebufferFxaaPreset();\n\tint post_process_flags =\n\t\t(vid_software_palette.integer ? POST_PROCESS_PALETTE : 0) |\n\t\t(vid_framebuffer.integer == USE_FRAMEBUFFER_3DONLY ? POST_PROCESS_3DONLY : 0) |\n\t\t(fxaa_preset > 0 ? POST_PROCESS_FXAA : 0) | (fxaa_preset << 4); // mix in preset to detect change\n\n\tif (R_ProgramRecompileNeeded(r_program_post_process_glc, post_process_flags)) {\n\t\tstatic char included_definitions[131072];\n\n\t\tmemset(included_definitions, 0, sizeof(included_definitions));\n\t\tif (post_process_flags & POST_PROCESS_PALETTE) {\n\t\t\tstrlcat(included_definitions, \"#define EZ_POSTPROCESS_PALETTE\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (post_process_flags & POST_PROCESS_3DONLY) {\n\t\t\tstrlcat(included_definitions, \"#define EZ_POSTPROCESS_OVERLAY\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (post_process_flags & POST_PROCESS_FXAA) {\n\t\t\tqbool supported = true;\n\t\t\tif (glConfig.coreProfile) {\n\t\t\t\t// gpu_shader4 and gpu_shader5 functionality is core in GL 3.3+\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (!GL_VersionAtLeast(3, 0) || !SDL_GL_ExtensionSupported(\"GL_EXT_gpu_shader4\"))\n\t\t\t\t{\n\t\t\t\t\tCom_Printf(\"WARNING: Missing GL_EXT_gpu_shader4, FXAA not available.\");\n\t\t\t\t\tsupported = false;\n\t\t\t\t}\n\n\t\t\t\tif (!GL_VersionAtLeast(4, 2) || !SDL_GL_ExtensionSupported(\"GL_ARB_gpu_shader5\"))\n\t\t\t\t{\n\t\t\t\t\tCom_Printf(\"WARNING: Missing GL_ARB_gpu_shader5, FXAA not available.\");\n\t\t\t\t\tsupported = false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (supported)\n\t\t\t{\n\t\t\t\textern const unsigned char fxaa_h_glsl[];\n\t\t\t\tchar buffer[64] = {0};\n\t\t\t\tconst char *settings;\n\t\t\t\tif (glConfig.coreProfile) {\n\t\t\t\t\tsettings =\n\t\t\t\t\t\t\"#define EZ_POSTPROCESS_FXAA\\n\"\n\t\t\t\t\t\t\"#define FXAA_PC 1\\n\"\n\t\t\t\t\t\t\"#define FXAA_GLSL_130 1\\n\"\n\t\t\t\t\t\t\"#define FXAA_GREEN_AS_LUMA 1\\n\";\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tsettings =\n\t\t\t\t\t\t\"#extension GL_EXT_gpu_shader4 : enable\\n\"\n#ifndef __APPLE__\n\t\t\t\t\t\t\"#extension GL_ARB_gpu_shader5 : enable\\n\"\n#endif\n\t\t\t\t\t\t\"#define EZ_POSTPROCESS_FXAA\\n\"\n\t\t\t\t\t\t\"#define FXAA_PC 1\\n\"\n\t\t\t\t\t\t\"#define FXAA_GLSL_120 1\\n\"\n\t\t\t\t\t\t\"#define FXAA_GREEN_AS_LUMA 1\\n\";\n\t\t\t\t}\n\t\t\t\tsnprintf(buffer, sizeof(buffer), \"#define FXAA_QUALITY__PRESET %d\\n\", fxaa_preset);\n\t\t\t\tstrlcat(included_definitions, settings, sizeof(included_definitions));\n\t\t\t\tstrlcat(included_definitions, buffer, sizeof(included_definitions));\n\t\t\t\tstrlcat(included_definitions, (const char *)fxaa_h_glsl, sizeof(included_definitions));\n\t\t\t}\n\t\t}\n\n\t\t// Initialise program for drawing image\n\t\tR_ProgramCompileWithInclude(r_program_post_process_glc, included_definitions);\n\n\t\tR_ProgramSetCustomOptions(r_program_post_process_glc, post_process_flags);\n\t}\n\n\treturn R_ProgramReady(r_program_post_process_glc);\n}\n\nvoid GLC_RenderFramebuffers(void)\n{\n\tqbool flip2d = GL_FramebufferEnabled2D();\n\tqbool flip3d = GL_FramebufferEnabled3D();\n\n\tif (GLC_CompilePostProcessProgram()) {\n\t\textern cvar_t gl_hwblend;\n\n\t\tfloat blend_alpha = (!vid_hwgamma_enabled || !gl_hwblend.value || cl.teamfortress) ? 0 : v_blend[3];\n\t\tfloat blend_values[4] = {\n\t\t\tv_blend[0] * blend_alpha,\n\t\t\tv_blend[1] * blend_alpha,\n\t\t\tv_blend[2] * blend_alpha,\n\t\t\t1 - blend_alpha\n\t\t};\n\n\t\tR_ProgramUse(r_program_post_process_glc);\n\t\tR_ProgramUniform1f(r_program_uniform_post_process_glc_gamma, v_gamma.value);\n\t\tR_ProgramUniform4fv(r_program_uniform_post_process_glc_v_blend, blend_values);\n\t\tR_ProgramUniform1f(r_program_uniform_post_process_glc_contrast, bound(1, v_contrast.value, 3));\n\t\tR_ProgramUniform1f(r_program_uniform_post_process_glc_r_inv_width, 1.0f / (float)VID_ScaledWidth3D());\n\t\tR_ProgramUniform1f(r_program_uniform_post_process_glc_r_inv_height, 1.0f / (float)VID_ScaledHeight3D());\n\t\tR_ApplyRenderingState(r_state_default_2d);\n\n\t\tif (flip2d && flip3d) {\n\t\t\trenderer.TextureUnitBind(0, GL_FramebufferTextureReference(framebuffer_std, fbtex_standard));\n\t\t\trenderer.TextureUnitBind(1, GL_FramebufferTextureReference(framebuffer_hud, fbtex_standard));\n\n\t\t\tR_ProgramUniform1i(r_program_uniform_post_process_glc_base, 0);\n\t\t\tR_ProgramUniform1i(r_program_uniform_post_process_glc_overlay, 1);\n\t\t}\n\t\telse if (flip3d) {\n\t\t\trenderer.TextureUnitBind(0, GL_FramebufferTextureReference(framebuffer_std, fbtex_standard));\n\n\t\t\tR_ProgramUniform1i(r_program_uniform_post_process_glc_base, 0);\n\t\t}\n\t\telse if (flip2d) {\n\t\t\trenderer.TextureUnitBind(0, GL_FramebufferTextureReference(framebuffer_hud, fbtex_standard));\n\n\t\t\tR_ProgramUniform1i(r_program_uniform_post_process_glc_overlay, 0);\n\t\t}\n\t\telse {\n\t\t\t// Create screen texture if required\n\t\t\tif (!R_TextureReferenceIsValid(non_framebuffer_screen_texture) || R_TextureWidth(non_framebuffer_screen_texture) != glwidth || R_TextureHeight(non_framebuffer_screen_texture) != glheight) {\n\t\t\t\tif (R_TextureReferenceIsValid(non_framebuffer_screen_texture)) {\n\t\t\t\t\trenderer.TextureDelete(non_framebuffer_screen_texture);\n\t\t\t\t}\n\t\t\t\tnon_framebuffer_screen_texture = R_LoadTexture(\"glc:postprocess\", glwidth, glheight, NULL, TEX_NOCOMPRESS | TEX_NOSCALE | TEX_NO_TEXTUREMODE, 4);\n\t\t\t\tif (R_TextureReferenceIsValid(non_framebuffer_screen_texture))\n\t\t\t\t\trenderer.TextureSetFiltering(non_framebuffer_screen_texture, texture_minification_nearest, texture_magnification_nearest);\n\t\t\t}\n\n\t\t\tif (!R_TextureReferenceIsValid(non_framebuffer_screen_texture)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Copy from screen to texture\n\t\t\trenderer.TextureUnitBind(0, non_framebuffer_screen_texture);\n\t\t\tGL_BuiltinProcedure(glCopyTexSubImage2D, \"mode=GL_TEXTURE_2D(%u), level=%d, xoffset=%d, yoffset=%d, x=%d, y=%d, width=%d, height=%d\", GL_TEXTURE_2D, 0, 0, 0, 0, 0, glwidth, glheight);\n\t\t}\n\n\t\tR_IdentityModelView();\n\t\tR_IdentityProjectionView();\n\n\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\t// Top left corner.\n\t\tglTexCoord2f(0, 0);\n\t\tGLC_Vertex2f(-1, -1);\n\n\t\t// Upper right corner.\n\t\tglTexCoord2f(0, 1);\n\t\tGLC_Vertex2f(-1, 1);\n\n\t\t// Bottom right corner.\n\t\tglTexCoord2f(1, 0);\n\t\tGLC_Vertex2f(1, -1);\n\n\t\t// Bottom left corner.\n\t\tglTexCoord2f(1, 1);\n\t\tGLC_Vertex2f(1, 1);\n\t\tGLC_End();\n\n\t\tR_ProgramUse(r_program_none);\n\t}\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_lightmaps.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"r_lightmaps_internal.h\"\n#include \"r_texture.h\"\n#include \"gl_texture_internal.h\"\n#include \"r_renderer.h\"\n#include \"tr_types.h\"\n#include \"r_lightmaps.h\"\n\n// FIXME: Check required depth against GL_MAX_ARRAY_TEXTURE_LAYERS\nstatic texture_ref lightmap_texture_array;\nstatic unsigned int lightmap_depth = 0;\n\nstatic void GLC_CreateLightmapTextureArray(void);\nstatic void GLC_CreateLightmapTexturesIndividual(void);\n\nvoid GLC_UploadLightmap(int textureUnit, int lightmapnum);\n\nqbool GLC_IsLightmapBound(int textureUnit, int lightmap_num)\n{\n\tif (R_TextureReferenceIsValid(lightmap_texture_array)) {\n\t\treturn renderer.TextureIsUnitBound(textureUnit, lightmap_texture_array);\n\t}\n\telse {\n\t\treturn renderer.TextureIsUnitBound(textureUnit, lightmaps[lightmap_num].gl_texref);\n\t}\n}\n\nqbool GLC_SetTextureLightmap(int textureUnit, int lightmap_num)\n{\n\t//update lightmap if it has been modified by dynamic lights\n\tR_UploadLightMap(textureUnit, lightmap_num);\n\tif (R_TextureReferenceIsValid(lightmap_texture_array)) {\n\t\treturn renderer.TextureUnitBind(textureUnit, lightmap_texture_array);\n\t}\n\telse {\n\t\treturn renderer.TextureUnitBind(textureUnit, lightmaps[lightmap_num].gl_texref);\n\t}\n}\n\nvoid GLC_LightmapArrayToggle(qbool use_array)\n{\n\tqbool array_in_use = R_TextureReferenceIsValid(lightmap_texture_array);\n\tuse_array &= (glConfig.supported_features & R_SUPPORT_TEXTURE_ARRAYS) != 0;\n\n\tif (array_in_use == use_array) {\n\t\treturn;\n\t}\n\n\tif (use_array && !array_in_use) {\n\t\tGLC_CreateLightmapTextureArray();\n\t}\n\telse if (array_in_use && !use_array) {\n\t\tGLC_CreateLightmapTexturesIndividual();\n\t\tR_DeleteTextureArray(&lightmap_texture_array);\n\t\tlightmap_depth = 0;\n\t}\n\n\t// Force all lightmaps to be updated\n\tR_ForceReloadLightMaps();\n}\n\nvoid GLC_InvalidateLightmapTextures(void)\n{\n\tR_TextureReferenceInvalidate(lightmap_texture_array);\n\tlightmap_depth = 0;\n}\n\nvoid GLC_ClearLightmapPolys(void)\n{\n\tint i;\n\n\tfor (i = 0; i < lightmap_array_size; ++i) {\n\t\tlightmaps[i].poly_chain = NULL;\n\t}\n}\n\ntexture_ref GLC_LightmapTexture(int index)\n{\n\tif (R_TextureReferenceIsValid(lightmap_texture_array)) {\n\t\treturn lightmap_texture_array;\n\t}\n\telse {\n\t\tif (index < 0 || index >= lightmap_array_size) {\n\t\t\treturn lightmaps[0].gl_texref;\n\t\t}\n\t\treturn lightmaps[index].gl_texref;\n\t}\n}\n\nstatic void GLC_CreateLightmapTextureArray(void)\n{\n\tGLenum error;\n\n\tif (R_TextureReferenceIsValid(lightmap_texture_array) && lightmap_depth >= lightmap_array_size) {\n\t\t// Nothing to do\n\t\treturn;\n\t}\n\tif (R_TextureReferenceIsValid(lightmap_texture_array)) {\n\t\tR_DeleteTextureArray(&lightmap_texture_array);\n\t}\n\tGL_ConsumeErrors();\n\tGL_CreateTexturesWithIdentifier(texture_type_2d_array, 1, &lightmap_texture_array, \"lightmap_texture_array\");\n\terror = GL_TexStorage3D(GL_TEXTURE0, lightmap_texture_array, 1, GL_RGBA8, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, lightmap_array_size, true);\n\tif (error != GL_NO_ERROR) {\n\t\tSys_Error(\"Unable to create lightmap texture array: error %u\\n\", error);\n\t\treturn;\n\t}\n\tR_SetTextureArraySize(lightmap_texture_array, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, lightmap_array_size, 4);\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tSys_Printf(\"\\nopengl-texture,alloc,%u,%d,%d,%d,%s\\n\", lightmap_texture_array.index, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * lightmap_array_size * 4, \"lightmap_texture_array\");\n#endif\n\trenderer.TextureSetFiltering(lightmap_texture_array, texture_minification_linear, texture_magnification_linear);\n\trenderer.TextureWrapModeClamp(lightmap_texture_array);\n\tlightmap_depth = lightmap_array_size;\n}\n\nstatic void GLC_CreateLightmapTexturesIndividual(void)\n{\n\tint i;\n\tchar name[64];\n\n\tfor (i = 0; i < lightmap_array_size; ++i) {\n\t\tif (!R_TextureReferenceIsValid(lightmaps[i].gl_texref)) {\n\t\t\tsnprintf(name, sizeof(name), \"lightmap-%03d\", i);\n\n\t\t\trenderer.TextureCreate2D(&lightmaps[i].gl_texref, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, name, true);\n\t\t}\n\t}\n}\n\nvoid GLC_CreateLightmapTextures(void)\n{\n\textern cvar_t gl_program_world;\n\tqbool supported = (glConfig.supported_features & R_SUPPORT_TEXTURE_ARRAYS);\n\tqbool requested = gl_program_world.integer;\n\n\tif (supported && requested) {\n\t\tGLC_CreateLightmapTextureArray();\n\t}\n\telse {\n\t\tGLC_CreateLightmapTexturesIndividual();\n\t}\n}\n\nvoid GLC_LightmapUpdate(int index)\n{\n\tif (index >= 0 && index < lightmap_array_size) {\n\t\tif (lightmaps[index].modified) {\n\t\t\tGLC_UploadLightmap(0, index);\n\t\t}\n\t}\n}\n\nvoid GLC_AddToLightmapChain(msurface_t* s)\n{\n\ts->polys->chain = lightmaps[s->lightmaptexturenum].poly_chain;\n\tlightmaps[s->lightmaptexturenum].poly_chain = s->polys;\n}\n\nglpoly_t* GLC_LightmapChain(int i)\n{\n\tif (i < 0 || i >= lightmap_array_size) {\n\t\treturn NULL;\n\t}\n\treturn lightmaps[i].poly_chain;\n}\n\nint GLC_LightmapCount(void)\n{\n\treturn lightmap_array_size;\n}\n\nvoid GLC_BuildLightmap(int i)\n{\n\tGLenum format = GL_Supported(R_SUPPORT_BGRA_LIGHTMAPS) ? GL_BGRA : GL_RGBA;\n\tGLenum type = GL_Supported(R_SUPPORT_INT8888R_LIGHTMAPS) ? GL_UNSIGNED_INT_8_8_8_8_REV : GL_UNSIGNED_BYTE;\n\n\tif (R_TextureReferenceIsValid(lightmap_texture_array)) {\n\t\tGL_TexSubImage3D(0, lightmap_texture_array, 0, 0, 0, i, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, 1, format, type, lightmaps[i].rawdata);\n\t}\n\telse {\n\t\tGL_TexSubImage2D(\n\t\t\tGL_TEXTURE0, lightmaps[i].gl_texref, 0, 0, 0,\n\t\t\tLIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, format, type,\n\t\t\tlightmaps[i].rawdata\n\t\t);\n\t}\n}\n\nvoid GLC_UploadLightmap(int textureUnit, int lightmapnum)\n{\n\tconst lightmap_data_t* lm = &lightmaps[lightmapnum];\n\tconst void* data_source = lm->rawdata + (lm->change_area.t) * LIGHTMAP_WIDTH * 4;\n\tGLenum format = GL_Supported(R_SUPPORT_BGRA_LIGHTMAPS) ? GL_BGRA : GL_RGBA;\n\tGLenum type = GL_Supported(R_SUPPORT_INT8888R_LIGHTMAPS) ? GL_UNSIGNED_INT_8_8_8_8_REV : GL_UNSIGNED_BYTE;\n\n\tif (R_TextureReferenceIsValid(lightmap_texture_array)) {\n\t\tGL_TexSubImage3D(textureUnit, lightmap_texture_array, 0, 0, lm->change_area.t, lightmapnum, LIGHTMAP_WIDTH, lm->change_area.h, 1, format, type, data_source);\n\t}\n\telse {\n\t\tGL_TexSubImage2D(GL_TEXTURE0 + textureUnit, lm->gl_texref, 0, 0, lm->change_area.t, LIGHTMAP_WIDTH, lm->change_area.h, format, type, data_source);\n\t}\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_local.h",
    "content": "\n#ifndef EZQUAKE_GLC_LOCAL_HEADER\n#define EZQUAKE_GLC_LOCAL_HEADER\n\nvoid GLC_FreeAliasPoseBuffer(void);\nvoid GLC_Shutdown(r_shutdown_mode_t mode);\n\nvoid GLC_PreRenderView(void);\nvoid GLC_SetupGL(void);\nvoid GLC_StateBeginAliasOutlineFrame(qbool weaponmodel);\nvoid GLC_StateBeginBrightenScreen(void);\nvoid GLC_StateBeginFastSky(qbool world);\nvoid GLC_StateBeginSkyZBufferPass(void);\nvoid GLC_StateBeginSingleTextureSkyDome(void);\nvoid GLC_StateBeginSingleTextureSkyDomeCloudPass(void);\nvoid GLC_StateBeginMultiTextureSkyDome(qbool use_program);\nvoid GLC_StateBeginMultiTextureSkyChain(void);\nvoid GLC_StateBeginSingleTextureSkyPass(void);\nvoid GLC_StateBeginSingleTextureCloudPass(void);\nvoid GLC_StateBeginRenderFullbrights(void);\nvoid GLC_StateBeginRenderLumas(void);\nvoid GLC_StateBeginEmitDetailPolys(void);\nvoid GLC_StateBeginDrawMapOutline(void);\nvoid GLC_StateBeginDrawAliasFrame(texture_ref texture, texture_ref fb_texture, qbool mtex, qbool alpha_blend, struct custom_model_color_s* custom_model, qbool weapon_model);\nvoid GLC_StateBeginDrawAliasFrameProgram(texture_ref texture, texture_ref fb_texture, int render_effects, struct custom_model_color_s* custom_model, float ent_alpha, qbool additive_pass);\nvoid GLC_StateBeginDrawAliasZPass(qbool weapon_model);\nvoid GLC_StateBeginAliasModelShadow(void);\nvoid GLC_StateBeginFastTurbPoly(byte color[4]);\nvoid GLC_StateBeginBlendLightmaps(qbool use_buffers);\nvoid GLC_StateBeginCausticsPolys(void);\nvoid GLC_StateBeginUnderwaterAliasModelCaustics(texture_ref base_texture, texture_ref caustics_texture);\nvoid GLC_StateEndUnderwaterAliasModelCaustics(void);\nvoid GLC_StateBeginBloomDraw(texture_ref texture);\nvoid GLC_StateBeginImageDraw(qbool is_text);\nvoid GLC_StateBeginImageDrawNonGLSL(qbool is_text);\nvoid GLC_StateBeginDrawAlphaPieSliceRGB(float thickness);\n\nvoid GLC_Begin(GLenum primitive);\nvoid GLC_End(void);\nvoid GLC_Vertex2f(GLfloat x, GLfloat y);\nvoid GLC_Vertex2fv(const GLfloat* v);\nvoid GLC_Vertex3f(GLfloat x, GLfloat y, GLfloat z);\nvoid GLC_Vertex3fv(const GLfloat* v);\n\nunsigned int GLC_DrawIndexedPoly(glpoly_t* p, unsigned int* modelIndexes, unsigned int modelIndexMaximum, unsigned int index_count);\n\n#endif\n"
  },
  {
    "path": "src/glc_main.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"r_renderer.h\"\n#include \"r_buffers.h\"\n#include \"glc_vao.h\"\n#include \"r_brushmodel.h\"\n#include \"r_aliasmodel.h\"\n#include \"tr_types.h\"\n\ncvar_t gl_program_sky          = { \"gl_program_sky\",           \"1\" };\ncvar_t gl_program_turbsurfaces = { \"gl_program_turbsurfaces\",  \"1\" };\ncvar_t gl_program_aliasmodels  = { \"gl_program_aliasmodels\",   \"1\", CVAR_LATCH_GFX };\ncvar_t gl_program_world        = { \"gl_program_world\",         \"1\" };\ncvar_t gl_program_sprites      = { \"gl_program_sprites\",       \"1\" };\ncvar_t gl_program_hud          = { \"gl_program_hud\",           \"1\" };\n\nstatic qbool glc_program_cvars_initialized = false;\nstatic cvar_t* gl_program_cvars[] = {\n\t&gl_program_sky,\n\t&gl_program_turbsurfaces,\n\t&gl_program_aliasmodels,\n\t&gl_program_world,\n\t&gl_program_sprites,\n\t&gl_program_hud,\n};\n\nvoid GL_Init(void);\nqbool GLC_InitialiseVAOHandling(void);\nvoid GL_InitialiseBufferHandling(api_buffers_t* buffers);\nvoid GL_InitialiseState(void);\n\nstatic void GLC_NoOperation(void)\n{\n}\n\nstatic void GLC_BindVertexArrayElementBuffer(r_vao_id vao, r_buffer_id ref)\n{\n}\n\n#ifndef WITH_RENDERING_TRACE\nstatic void GLC_TextureLabelSetNull(texture_ref texture, const char* name)\n{\n}\n#endif\n\nqbool GLC_False(void)\n{\n\treturn false;\n}\n\nstatic void GLC_ScreenDrawStart(void)\n{\n\tGL_FramebufferScreenDrawStart();\n}\n\nstatic void GLC_PostProcessScreen(void)\n{\n\tGL_FramebufferPostProcessScreen();\n}\n\nstatic void GLC_Begin2DRendering(void)\n{\n\tGL_Framebuffer2DSwitch();\n}\n\n#define GLC_PrintGfxInfo                   GL_PrintGfxInfo\n#define GLC_Viewport                       GL_Viewport\n#define GLC_InvalidateViewport             GL_InvalidateViewport\n#define GLC_RenderDynamicLightmaps         R_RenderDynamicLightmaps\n#define GLC_LightmapFrameInit              GLC_NoOperation\n#define GLC_LightmapShutdown               GLC_NoOperation\n#define GLC_ClearRenderingSurface          GL_Clear\n#define GLC_EnsureFinished                 GL_EnsureFinished\n#define GLC_RenderView                     GLC_NoOperation\n#define GLC_Screenshot                     GL_Screenshot\n#define GLC_ScreenshotWidth                GL_ScreenshotWidth\n#define GLC_ScreenshotHeight               GL_ScreenshotHeight\n#define GLC_InitialiseVAOState             GL_InitialiseVAOState\n#define GLC_DescriptiveString              GL_DescriptiveString\n#define GLC_Draw3DSprites                  GLC_NoOperation\n#define GLC_Prepare3DSprites               GLC_NoOperation\n#define GLC_IsFramebufferEnabled3D         GL_FramebufferEnabled3D\n#define GLC_TextureDelete                  GL_TextureDelete\n#define GLC_TextureMipmapGenerate          GL_TextureMipmapGenerate\n#define GLC_TextureWrapModeClamp           GL_TextureWrapModeClamp\n#ifdef WITH_RENDERING_TRACE\n#define GLC_TextureLabelSet                GL_TextureLabelSet\n#else\n#define GLC_TextureLabelSet                GLC_TextureLabelSetNull\n#endif\n#define GLC_TextureIsUnitBound             GL_IsTextureBound\n#define GLC_TextureUnitBind                GL_EnsureTextureUnitBoundAndSelect\n#define GLC_TextureGet                     GL_TextureGet\n#define GLC_TextureCompressionSet          GL_TextureCompressionSet\n#define GLC_TextureCreate2D                GL_TextureCreate2D\n#define GLC_TextureUnitMultiBind           GL_TextureUnitMultiBind\n#define GLC_TexturesCreate                 GL_TexturesCreate\n#define GLC_TextureReplaceSubImageRGBA     GL_TextureReplaceSubImageRGBA\n#define GLC_TextureSetAnisotropy           GL_TextureSetAnisotropy\n#define GLC_TextureSetFiltering            GL_TextureSetFiltering\n#define GLC_TextureLoadCubemapFace         GL_TextureLoadCubemapFace\n#define GLC_CvarForceRecompile             GL_CvarForceRecompile\n#define GLC_ProgramsInitialise             GL_ProgramsInitialise\n#define GLC_ProgramsShutdown               GL_ProgramsShutdown\n#define GLC_FramebufferCreate              GL_FramebufferCreate\n#define GLC_PrepareAliasModel              GL_PrepareAliasModel\n\n#define RENDERER_METHOD(returntype, name, ...) \\\n{ \\\n\textern returntype GLC_ ## name(__VA_ARGS__); \\\n\trenderer.name = GLC_ ## name; \\\n}\n\nvoid GLC_Initialise(void)\n{\n\tint i;\n\textern cvar_t vid_gl_core_profile;\n#include \"r_renderer_structure.h\"\n\n\tif (!glc_program_cvars_initialized) {\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_OPENGL);\n\t\tfor (i = 0; i < sizeof(gl_program_cvars) / sizeof(gl_program_cvars[0]); ++i) {\n\t\t\tif (!(gl_program_cvars[i]->flags & CVAR_LATCH)) {\n\t\t\t\tCvar_Register(gl_program_cvars[i]);\n\t\t\t}\n\t\t}\n\n\t\tCvar_ResetCurrentGroup();\n\t\tglc_program_cvars_initialized = true;\n\t}\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_OPENGL);\n\tfor (i = 0; i < sizeof(gl_program_cvars) / sizeof(gl_program_cvars[0]); ++i) {\n\t\tif (gl_program_cvars[i]->flags & CVAR_LATCH) {\n\t\t\tCvar_Register(gl_program_cvars[i]);\n\t\t}\n\t}\n\tCvar_ResetCurrentGroup();\n\n\tGL_Init();\n\trenderer.vaos_supported = GLC_InitialiseVAOHandling();\n\tGL_ProcessErrors(\"post-GLC_InitialiseVAOHandling\");\n\tGL_InitialiseBufferHandling(&buffers);\n\tGL_ProcessErrors(\"post-GL_InitialiseBufferHandling\");\n\tGL_InitialiseState();\n\tGL_ProcessErrors(\"post-GL_InitialiseState\");\n\n\tif (!GL_Supported(R_SUPPORT_RENDERING_SHADERS)) {\n\t\t// GLSL not supported for some reason\n\t\tfor (i = 0; i < sizeof(gl_program_cvars) / sizeof(gl_program_cvars[0]); ++i) {\n\t\t\tint flags = Cvar_GetFlags(gl_program_cvars[i]);\n\t\t\tif (flags & CVAR_LATCH) {\n\t\t\t\tCvar_LatchedSetValue(gl_program_cvars[i], 0);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCvar_SetValue(gl_program_cvars[i], 0);\n\t\t\t}\n\t\t\tCvar_SetFlags(gl_program_cvars[i], flags | CVAR_ROM);\n\t\t}\n\t\tCon_Printf(\"&cf00ERROR&r: GLSL programs not supported.\\n\");\n\t\tglConfig.supported_features |= R_SUPPORT_IMMEDIATEMODE;\n\t}\n\telse if (vid_gl_core_profile.integer && buffers.supported && GL_Supported(R_SUPPORT_RENDERING_SHADERS)) {\n\t\t// Force GLSL\n\t\tfor (i = 0; i < sizeof(gl_program_cvars) / sizeof(gl_program_cvars[0]); ++i) {\n\t\t\tint flags = Cvar_GetFlags(gl_program_cvars[i]);\n\t\t\tif (flags & CVAR_LATCH) {\n\t\t\t\tCvar_LatchedSetValue(gl_program_cvars[i], 1);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCvar_SetValue(gl_program_cvars[i], 1);\n\t\t\t}\n\t\t\tCvar_SetFlags(gl_program_cvars[i], flags | CVAR_ROM);\n\t\t}\n\t\tCon_Printf(\"&c0f0INFO&r: immediate-mode rendering disabled.\\n\");\n\t}\n\telse {\n\t\t// make optional\n\t\tfor (i = 0; i < sizeof(gl_program_cvars) / sizeof(gl_program_cvars[0]); ++i) {\n\t\t\tCvar_SetFlags(gl_program_cvars[i], Cvar_GetFlags(gl_program_cvars[i]) & ~CVAR_ROM);\n\t\t}\n\t\tif (!renderer.vaos_supported) {\n\t\t\t// disable aliasmodel program rendering, it requires attributes\n\t\t\tCvar_LatchedSetValue(&gl_program_aliasmodels, 0);\n\t\t\tCvar_SetFlags(&gl_program_aliasmodels, Cvar_GetFlags(&gl_program_aliasmodels) | CVAR_ROM);\n\t\t}\n\t\tglConfig.supported_features |= R_SUPPORT_IMMEDIATEMODE;\n\t}\n}\n\nvoid GLC_PrepareModelRendering(qbool vid_restart)\n{\n\tif (buffers.supported) {\n\t\tR_CreateInstanceVBO();\n\n\t\tR_CreateAliasModelVBO();\n\t\tR_BrushModelCreateVBO();\n\t}\n\n\trenderer.ProgramsInitialise();\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_matrix.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"r_matrix.h\"\n#include \"r_draw.h\"\n#include \"gl_texture_internal.h\"\n\nstatic qbool glc_pause_updates;\n\nvoid GLC_RotateModelview(float angle, float x, float y, float z)\n{\n\tif (!glc_pause_updates) {\n\t\tR_TraceLogAPICall(\"%s()\", __func__);\n\t\tglMatrixMode(GL_MODELVIEW);\n\t\tglRotatef(angle, x, y, z);\n\t}\n}\n\nvoid GLC_IdentityModelview(void)\n{\n\tif (!glc_pause_updates) {\n\t\tR_TraceLogAPICall(\"%s()\", __func__);\n\t\tglMatrixMode(GL_MODELVIEW);\n\t\tglLoadIdentity();\n\t}\n}\n\nvoid GLC_OrthographicProjection(float left, float right, float top, float bottom, float zNear, float zFar)\n{\n\tR_TraceLogAPICall(\"%s()\", __func__);\n\tglMatrixMode(GL_PROJECTION);\n\tglLoadIdentity();\n\tglOrtho(left, right, top, bottom, zNear, zFar);\n}\n\nvoid GLC_IdentityProjectionView(void)\n{\n\tR_TraceLogAPICall(\"%s()\", __func__);\n\tglMatrixMode(GL_PROJECTION);\n\tglLoadIdentity();\n}\n\nvoid GLC_PopModelviewMatrix(const float* matrix)\n{\n\tR_TraceLogAPICall(\"%s()\", __func__);\n\tglMatrixMode(GL_MODELVIEW);\n\tglLoadMatrixf(matrix);\n}\n\nvoid GLC_PopProjectionMatrix(const float* matrix)\n{\n\tR_TraceLogAPICall(\"%s()\", __func__);\n\tglMatrixMode(GL_PROJECTION);\n\tglLoadMatrixf(matrix);\n}\n\nvoid GLC_ScaleModelview(float xScale, float yScale, float zScale)\n{\n\tif (!glc_pause_updates) {\n\t\tR_TraceLogAPICall(\"%s()\", __func__);\n\t\tglMatrixMode(GL_MODELVIEW);\n\t\tglScalef(xScale, yScale, zScale);\n\t}\n}\n\nvoid GLC_Frustum(double left, double right, double bottom, double top, double zNear, double zFar)\n{\n\tR_TraceLogAPICall(\"%s()\", __func__);\n\tglFrustum(left, right, bottom, top, zNear, zFar);\n}\n\nvoid GLC_PauseMatrixUpdate(void)\n{\n\tglc_pause_updates = true;\n}\n\nvoid GLC_ResumeMatrixUpdate(void)\n{\n\tglc_pause_updates = false;\n}\n\nvoid GLC_BeginCausticsTextureMatrix(void)\n{\n\tR_TraceLogAPICall(\"%s()\", __func__);\n\tGL_SelectTexture(GL_TEXTURE1);\n\tglMatrixMode(GL_TEXTURE);\n\tglLoadIdentity();\n\tglScalef(0.5, 0.5, 1);\n\tglRotatef(r_refdef2.time * 10, 1, 0, 0);\n\tglRotatef(r_refdef2.time * 10, 0, 1, 0);\n\tglMatrixMode(GL_MODELVIEW);\n}\n\nvoid GLC_EndCausticsTextureMatrix(void)\n{\n\tR_TraceLogAPICall(\"%s()\", __func__);\n\tGL_SelectTexture(GL_TEXTURE1);\n\tglMatrixMode(GL_TEXTURE);\n\tglLoadIdentity();\n\tglMatrixMode(GL_MODELVIEW);\n}\n\nvoid GLC_TranslateModelview(float x, float y, float z)\n{\n\tif (!glc_pause_updates) {\n\t\tR_TraceLogAPICall(\"%s()\", __func__);\n\t\tglMatrixMode(GL_MODELVIEW);\n\t\tglTranslatef(x, y, z);\n\t}\n}\n\nvoid GLC_LoadModelviewMatrix(void)\n{\n\tR_TraceLogAPICall(\"%s()\", __func__);\n\tglMatrixMode(GL_MODELVIEW);\n\tglLoadMatrixf(R_ModelviewMatrix());\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_matrix.h",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_GLC_MATRIX_HEADER\n#define EZQUAKE_GLC_MATRIX_HEADER\n\n#include \"r_matrix.h\"\n\nvoid GLC_IdentityModelview(void);\nvoid GLC_RotateModelview(float angle, float x, float y, float z);\nvoid GLC_TranslateModelview(float x, float y, float z);\nvoid GLC_PopModelviewMatrix(const float* matrix);\nvoid GLC_LoadModelviewMatrix(void);\n\nvoid GLC_IdentityProjectionView(void);\nvoid GLC_OrthographicProjection(float left, float right, float top, float bottom, float zNear, float zFar);\n\nvoid GLC_PopProjectionMatrix(const float* matrix);\nvoid GLC_ScaleModelview(float xScale, float yScale, float zScale);\nvoid GLC_Frustum(double left, double right, double bottom, double top, double zNear, double zFar);\nvoid GLC_PauseMatrixUpdate(void);\nvoid GLC_ResumeMatrixUpdate(void);\nvoid GLC_BeginCausticsTextureMatrix(void);\nvoid GLC_EndCausticsTextureMatrix(void);\n\nvoid GLC_PauseMatrixUpdate(void);\nvoid GLC_ResumeMatrixUpdate(void);\n\n#endif // EZQUAKE_GLC_MATRIX_HEADER\n"
  },
  {
    "path": "src/glc_md3.c",
    "content": "/*\nCopyright (C) 2011 fuh and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Code to display MD3 models (classic GL/immediate-mode)\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"glc_local.h\"\n#include \"r_aliasmodel_md3.h\"\n#include \"vx_vertexlights.h\" \n#include \"r_matrix.h\"\n#include \"r_state.h\"\n#include \"r_aliasmodel.h\"\n#include \"glc_local.h\"\n#include \"r_renderer.h\"\n#include \"r_program.h\"\n#include \"tr_types.h\"\n#include \"rulesets.h\"\n\nconst float* GLC_PowerupShell_ScrollParams(void);\nvoid GLC_SetPowerupShellColor(int layer_no, int effects);\nvoid GLC_PowerupShellColor(int layer_no, int effects, float* color);\nqbool GLC_AliasModelStandardCompileSpecific(int subprogram_index);\nint GLC_AliasModelSubProgramIndex(qbool textured, qbool fullbright, qbool caustics, qbool muzzlehack);\nqbool GLC_AliasModelShellCompile(void);\n\n#if 0\n// FIXME: get rid of cos/sin lookups etc\nstatic void GLC_AliasModelLightPointMD3(float color[4], const entity_t* ent, vbo_model_vert_t* vert1, vbo_model_vert_t* vert2, float lerpfrac)\n{\n\tfloat l;\n\textern cvar_t amf_lighting_vertex, amf_lighting_colour;\n\n\t// VULT VERTEX LIGHTING\n\t/*if (amf_lighting_vertex.integer && !ent->full_light) {\n\t\tvec3_t lc;\n\n\t\tl = VLight_LerpLightByAngles(vert1->normal_lat, vert1->normal_lng, vert2->normal_lat, vert2->normal_lng, lerpfrac, ent->angles[0], ent->angles[1]);\n\t\tl *= (ent->shadelight + ent->ambientlight) / 256.0;\n\t\tl = min(l, 1);\n\n\t\tif (amf_lighting_colour.integer) {\n\t\t\tint i;\n\t\t\tfor (i = 0; i < 3; i++) {\n\t\t\t\tlc[i] = l * ent->lightcolor[i] / 255;\n\t\t\t\tlc[i] = min(lc[i], 1);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tVectorSet(lc, l, l, l);\n\t\t}\n\n\t\tif (ent->r_modelcolor[0] < 0) {\n\t\t\t// normal color\n\t\t\tVectorCopy(lc, color);\n\t\t}\n\t\telse {\n\t\t\tcolor[0] = ent->r_modelcolor[0] * lc[0];\n\t\t\tcolor[1] = ent->r_modelcolor[1] * lc[1];\n\t\t\tcolor[2] = ent->r_modelcolor[2] * lc[2];\n\t\t}\n\t}\n\telse*/ {\n\t\tfloat yaw_rad = ent->angles[YAW] * M_PI / 180.0;\n\t\tvec3_t angleVector = { cos(-yaw_rad), sin(yaw_rad), 1 };\n\n\t\tVectorNormalize(angleVector);\n\t\tl = FloatInterpolate(DotProduct(angleVector, vert1->normal), lerpfrac, DotProduct(angleVector, vert2->normal));\n\t\tl = (l * ent->shadelight + ent->ambientlight) / 256.0;\n\t\tl = min(l, 1);\n\n\t\tif (ent->custom_model == NULL) {\n\t\t\tif (ent->r_modelcolor[0] < 0) {\n\t\t\t\tcolor[0] = color[1] = color[2] = l;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcolor[0] = ent->r_modelcolor[0] * l;\n\t\t\t\tcolor[1] = ent->r_modelcolor[1] * l;\n\t\t\t\tcolor[2] = ent->r_modelcolor[2] * l;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tcolor[0] = ent->custom_model->color_cvar.color[0] / 255.0f;\n\t\t\tcolor[1] = ent->custom_model->color_cvar.color[1] / 255.0f;\n\t\t\tcolor[2] = ent->custom_model->color_cvar.color[2] / 255.0f;\n\t\t}\n\t}\n\n\tcolor[0] *= ent->r_modelalpha;\n\tcolor[1] *= ent->r_modelalpha;\n\tcolor[2] *= ent->r_modelalpha;\n\tcolor[3] = ent->r_modelalpha;\n}\n#endif\n\nstatic void GLC_DrawMD3Frame(const entity_t* ent, const float* modelColor, md3Header_t* pheader, int frame1, int frame2, float lerpfracDefault, const surfinf_t* surface_info, qbool invalidate_texture, qbool outline, qbool additive_pass)\n{\n\tmd3Surface_t *surf;\n\tint surfnum;\n\tint numverts, i;\n\tvbo_model_vert_t* vbo_buffer = (vbo_model_vert_t*)ent->model->temp_vbo_buffer;\n\tint vertsPerFrame = ent->model->vertsInVBO / pheader->numFrames;\n\tint first_vert_f1 = vertsPerFrame * frame1;\n\tint first_vert_f2 = vertsPerFrame * frame2;\n\tvbo_model_vert_t* verts1 = &vbo_buffer[first_vert_f1];\n\tvbo_model_vert_t* verts2 = &vbo_buffer[first_vert_f2];\n\tqbool limit_lerp = false; // r_lerpmuzzlehack.integer && (ent->model->renderfx & RF_LIMITLERP);\n\n\tif (!vbo_buffer) {\n\t\treturn;\n\t}\n\n\tMD3_ForEachSurface(pheader, surf, surfnum) {\n\t\tvec3_t interpolated_verts;\n\t\t// FIXME: hack for not reading shader types\n\t\tqbool additive_surface = ((ent->model && ent->model->modhint & MOD_VMODEL) && surfnum >= 1);\n\t\tnumverts = surf->numTriangles * 3;\n\n\t\tif (additive_surface != additive_pass) {\n\t\t\tverts1 += numverts;\n\t\t\tverts2 += numverts;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (R_TextureReferenceIsValid(surface_info[surfnum].texnum) && !invalidate_texture) {\n\t\t\trenderer.TextureUnitBind(0, surface_info[surfnum].texnum);\n\t\t}\n\n\t\tGLC_Begin(GL_TRIANGLES);\n\t\tfor (i = 0; i < numverts; ++i) {\n\t\t\tfloat lerpfrac = lerpfracDefault;\n\n\t\t\tif (limit_lerp && !VectorL2Compare(verts1->position, verts2->position, ALIASMODEL_MAX_LERP_DISTANCE)) {\n\t\t\t\tlerpfrac = 1;\n\t\t\t}\n\n\t\t\tif (outline) {\n\t\t\t\tvec3_t v1, v2;\n\t\t\t\tVectorMA(verts1->position, ent->outlineScale, verts1->normal, v1);\n\t\t\t\tVectorMA(verts2->position, ent->outlineScale, verts2->normal, v2);\n\t\t\t\tVectorInterpolate(v1, lerpfrac, v2, interpolated_verts);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tVectorInterpolate(verts1->position, lerpfrac, verts2->position, interpolated_verts);\n\t\t\t\t// FIXME: another hack!\n\t\t\t\tif (!additive_pass) {\n\t\t\t\t\t//GLC_AliasModelLightPointMD3(vertexColor, ent, verts1, verts2, lerpfrac);\n\t\t\t\t\tR_CustomColor(modelColor[0], modelColor[1], modelColor[2], modelColor[3]);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tR_CustomColor(ent->r_modelalpha, ent->r_modelalpha, ent->r_modelalpha, ent->r_modelalpha);\n\t\t\t\t}\n\t\t\t}\n\t\t\tglTexCoord2f(verts1->texture_coords[0], verts1->texture_coords[1]);\n\t\t\tGLC_Vertex3fv(interpolated_verts);\n\n\t\t\t++verts1;\n\t\t\t++verts2;\n\t\t}\n\t\tGLC_End();\n\n\t\tframeStats.classic.polycount[polyTypeAliasModel] += surf->numTriangles;\n\t}\n}\n\nstatic void GLC_DrawAlias3ModelProgram(entity_t* ent, int frame1, qbool invalidate_texture, float* vertexColor, float lerpfrac, qbool outline, qbool additive_pass)\n{\n\tmodel_t *mod = ent->model;\n\tmd3model_t *mhead = (md3model_t *)Mod_Extradata(mod);\n\tmd3Header_t *pheader = MD3_HeaderForModel(mhead);\n\tfloat angle_radians = -ent->angles[YAW] * M_PI / 180.0;\n\tvec3_t angle_vector = { cos(angle_radians), sin(angle_radians), 1 };\n\tint vertsPerFrame = mod->vertsInVBO / pheader->numFrames;\n\tint first_vert = mod->vbo_start + vertsPerFrame * frame1;\n\tint vert_index;\n\tsurfinf_t* sinf = MD3_ExtraSurfaceInfoForModel(mhead);\n\tmd3Surface_t* surf;\n\tint surfnum;\n\n\tif (ent->skinnum >= 0 && ent->skinnum < pheader->numSkins) {\n\t\tsinf += ent->skinnum * pheader->numSurfaces;\n\t}\n\n\tif (outline) {\n\t\tqbool weaponmodel = (ent->renderfx & RF_WEAPONMODEL);\n\n\t\tR_ProgramUse(r_program_aliasmodel_outline_glc);\n\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_outline_glc_lerpFraction, lerpfrac);\n\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_outline_glc_outlineScale, ent->outlineScale);\n\t\tGLC_StateBeginAliasOutlineFrame(weaponmodel);\n\t\tvert_index = first_vert;\n\t\tMD3_ForEachSurface(pheader, surf, surfnum) {\n\t\t\t// FIXME: hack for not reading shader types\n\t\t\tqbool additive_surface = ((mod->modhint & MOD_VMODEL) && surfnum >= 1);\n\n\t\t\t// don't outline these\n\t\t\tif (additive_surface) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tGL_DrawArrays(GL_TRIANGLES, vert_index, 3 * surf->numTriangles);\n\t\t\tvert_index += 3 * surf->numTriangles;\n\t\t}\n\t}\n\telse {\n\t\t// Temporarily disable caustics\n\t\tR_ProgramUse(r_program_aliasmodel_std_glc);\n\t\tR_ProgramUniform3fv(r_program_uniform_aliasmodel_std_glc_angleVector, angle_vector);\n\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_std_glc_shadelight, ent->shadelight / 256.0f);\n\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_std_glc_ambientlight, ent->ambientlight / 256.0f);\n\t\tR_ProgramUniform1i(r_program_uniform_aliasmodel_std_glc_fsTextureEnabled, invalidate_texture ? 0 : 1);\n\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_std_glc_fsMinLumaMix, 1.0f - (ent->full_light ? bound(0, gl_fb_models.integer, 1) : 0));\n\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_std_glc_fsCausticEffects, 0 /*ent->renderfx & RF_CAUSTICS ? 1 : 0*/);\n\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_std_glc_lerpFraction, lerpfrac);\n\t\tR_ProgramUniform1f(r_program_uniform_aliasmodel_std_glc_time, cl.time);\n\n\t\t// z-pass\n\t\tif (ent->r_modelalpha < 1) {\n\t\t\tGLC_StateBeginDrawAliasZPass(ent->renderfx & RF_WEAPONMODEL);\n\t\t\tvert_index = first_vert;\n\t\t\tMD3_ForEachSurface(pheader, surf, surfnum) {\n\t\t\t\tGL_DrawArrays(GL_TRIANGLES, vert_index, 3 * surf->numTriangles);\n\t\t\t\tvert_index += 3 * surf->numTriangles;\n\t\t\t}\n\t\t}\n\n\t\tGLC_StateBeginDrawAliasFrameProgram(sinf->texnum, null_texture_reference, ent->renderfx, ent->custom_model, ent->r_modelalpha, additive_pass);\n\t\tvert_index = first_vert;\n\t\tMD3_ForEachSurface(pheader, surf, surfnum) {\n\t\t\t// FIXME: hack for not reading shader types\n\t\t\tqbool additive_surface = ((mod->modhint & MOD_VMODEL) && surfnum >= 1);\n\n\t\t\tif (additive_surface == additive_pass) {\n\t\t\t\tif (additive_pass) {\n\t\t\t\t\tR_CustomColor(ent->r_modelalpha, ent->r_modelalpha, ent->r_modelalpha, ent->r_modelalpha);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tR_CustomColor(vertexColor[0], vertexColor[1], vertexColor[2], vertexColor[3]);\n\t\t\t\t}\n\t\t\t\tif (R_TextureReferenceIsValid(sinf[surfnum].texnum) && !invalidate_texture) {\n\t\t\t\t\trenderer.TextureUnitBind(0, sinf[surfnum].texnum);\n\t\t\t\t}\n\n\t\t\t\tGL_DrawArrays(GL_TRIANGLES, vert_index, 3 * surf->numTriangles);\n\t\t\t}\n\t\t\tvert_index += 3 * surf->numTriangles;\n\t\t}\n\t\tR_ProgramUse(r_program_none);\n\t}\n}\n\nstatic void GLC_DrawAlias3ModelImmediate(entity_t* ent, int frame1, int frame2, qbool invalidate_texture, float* vertexColor, float lerpfrac, qbool outline, qbool additive_pass)\n{\n\tmodel_t* mod = ent->model;\n\tmd3model_t* mhead = (md3model_t *)Mod_Extradata(mod);\n\tmd3Header_t* pheader = MD3_HeaderForModel(mhead);\n\tsurfinf_t* sinf = MD3_ExtraSurfaceInfoForModel(mhead);\n\n\tif (additive_pass && outline) {\n\t\treturn;\n\t}\n\n\t// Immediate mode\n\tR_ProgramUse(r_program_none);\n\tif (outline) {\n\t\tGLC_StateBeginAliasOutlineFrame(ent->renderfx & RF_WEAPONMODEL);\n\t\tGLC_DrawMD3Frame(ent, vertexColor, pheader, frame1, frame2, lerpfrac, sinf, true, true, additive_pass);\n\t}\n\telse {\n\t\tif (ent->skinnum >= 0 && ent->skinnum < pheader->numSkins) {\n\t\t\tsinf += ent->skinnum * pheader->numSurfaces;\n\t\t}\n\t\tif (ent->r_modelalpha < 1 && !additive_pass) {\n\t\t\tGLC_StateBeginDrawAliasZPass(ent->renderfx & RF_WEAPONMODEL);\n\t\t\tGLC_DrawMD3Frame(ent, vertexColor, pheader, frame1, frame2, lerpfrac, sinf, invalidate_texture, false, false);\n\t\t}\n\t\tGLC_StateBeginMD3Draw(ent->r_modelalpha, R_TextureReferenceIsValid(sinf->texnum) && !invalidate_texture, ent->renderfx & RF_WEAPONMODEL, additive_pass);\n\t\tGLC_DrawMD3Frame(ent, vertexColor, pheader, frame1, frame2, lerpfrac, sinf, invalidate_texture, false, additive_pass);\n\t}\n}\n\n/*\nTo draw, for each surface, run through the triangles, getting tex coords from s+t, \n*/\nvoid GLC_DrawAlias3Model(entity_t *ent, qbool outline, qbool additive_pass)\n{\n\textern cvar_t gl_fb_models, gl_program_aliasmodels;\n\n\tfloat lerpfrac;\n\tfloat vertexColor[4];\n\n\tmodel_t *mod = ent->model;\n\tmd3model_t *mhead = (md3model_t *)Mod_Extradata(mod);\n\tmd3Header_t *pheader = MD3_HeaderForModel(mhead);\n\tqbool invalidate_texture = false;\n\n\tint frame1, frame2;\n\tfloat oldMatrix[16];\n\tint subprogram;\n\textern cvar_t r_lerpmuzzlehack;\n\tint render_effects = ent->renderfx;\n\n\tR_PushModelviewMatrix(oldMatrix);\n\tR_AliasModelPrepare(ent, pheader->numFrames, &frame1, &frame2, &lerpfrac, &outline);\n\tR_AliasModelColor(ent, vertexColor, &invalidate_texture);\n\n\tsubprogram = GLC_AliasModelSubProgramIndex(!invalidate_texture, ent->full_light, gl_caustics.integer && (ent->renderfx & RF_CAUSTICS), r_lerpmuzzlehack.integer && (render_effects & RF_WEAPONMODEL));\n\n\tif (gl_program_aliasmodels.integer && buffers.supported && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_AliasModelStandardCompileSpecific(subprogram)) {\n\t\tGLC_DrawAlias3ModelProgram(ent, frame1, invalidate_texture, vertexColor, lerpfrac, outline, additive_pass);\n\t}\n\telse if (GL_Supported(R_SUPPORT_IMMEDIATEMODE)) {\n\t\tGLC_DrawAlias3ModelImmediate(ent, frame1, frame2, invalidate_texture, vertexColor, lerpfrac, outline, additive_pass);\n\t}\n\tR_PopModelviewMatrix(oldMatrix);\n}\n\nstatic void GLC_DrawAlias3ModelPowerupShellProgram(model_t * mod, md3Header_t * pheader, int frame1, entity_t * ent, float lerpfrac)\n{\n\tint vertsPerFrame = mod->vertsInVBO / pheader->numFrames;\n\tint first_vert = mod->vbo_start + vertsPerFrame * frame1;\n\tfloat color1[4], color2[4];\n\n\tGLC_PowerupShellColor(0, ent->effects, color1);\n\tGLC_PowerupShellColor(1, ent->effects, color2);\n\n\tR_ProgramUse(r_program_aliasmodel_shell_glc);\n\tR_ProgramUniform4fv(r_program_uniform_aliasmodel_shell_glc_fsBaseColor1, color1);\n\tR_ProgramUniform4fv(r_program_uniform_aliasmodel_shell_glc_fsBaseColor2, color2);\n\tR_ProgramUniform4fv(r_program_uniform_aliasmodel_shell_glc_scroll, GLC_PowerupShell_ScrollParams());\n\tR_ProgramUniform1f(r_program_uniform_aliasmodel_shell_glc_lerpFraction, lerpfrac);\n\tGL_DrawArrays(GL_TRIANGLES, first_vert, vertsPerFrame);\n\tR_ProgramUse(r_program_none);\n}\n\nstatic void GLC_DrawAlias3ModelPowerupShellImmediate(model_t * mod, md3Header_t * pheader, int frame1, int frame2, entity_t * ent, float lerpfrac)\n{\n\tint layer_no;\n\tconst int distance = MD3_INTERP_MAXDIST / MD3_XYZ_SCALE;\n\n\tR_ProgramUse(r_program_none);\n\n\tfor (layer_no = 0; layer_no <= 1; ++layer_no) {\n\t\tmd3Surface_t* surf;\n\t\tint surfnum;\n\t\t \n\t\tMD3_ForEachSurface(pheader, surf, surfnum) {\n\t\t\t// loop through the surfaces.\n\t\t\tint pose1 = frame1 * surf->numVerts;\n\t\t\tint pose2 = frame2 * surf->numVerts;\n\t\t\tint numtris, i;\n\t\t\tunsigned int* tris;\n\t\t\tmd3St_t* tc;\n\t\t\tezMd3XyzNormal_t* verts;\n\n\t\t\t//skin texture coords.\n\t\t\ttc = MD3_SurfaceTextureCoords(surf);\n\t\t\tverts = MD3_SurfaceVertices(surf);\n\n\t\t\ttris = (unsigned int *)((char *)surf + surf->ofsTriangles);\n\t\t\tnumtris = surf->numTriangles * 3;\n\n\t\t\tGLC_SetPowerupShellColor(layer_no, ent->effects);\n\t\t\tGLC_Begin(GL_TRIANGLES);\n\t\t\tfor (i = 0; i < numtris; i++) {\n\t\t\t\tfloat s, t;\n\t\t\t\tvec3_t vec1pos, vec2pos, interpolated_verts;\n\t\t\t\tezMd3XyzNormal_t *v1, *v2;\n\n\t\t\t\tv1 = verts + *tris + pose1;\n\t\t\t\tv2 = verts + *tris + pose2;\n\n\t\t\t\ts = tc[*tris].s * 2.0f + r_refdef2.powerup_scroll_params[layer_no * 2];\n\t\t\t\tt = tc[*tris].t * 2.0f + r_refdef2.powerup_scroll_params[layer_no * 2 + 1];\n\n\t\t\t\tlerpfrac = VectorL2Compare(v1->xyz, v2->xyz, distance) ? lerpfrac : 1;\n\t\t\t\tVectorAdd(v1->normal, v1->xyz, vec1pos);\n\t\t\t\tVectorAdd(v2->normal, v2->xyz, vec2pos);\n\t\t\t\tVectorInterpolate(vec1pos, lerpfrac, vec2pos, interpolated_verts);\n\n\t\t\t\tglTexCoord2f(s, t);\n\t\t\t\tGLC_Vertex3fv(interpolated_verts);\n\n\t\t\t\ttris++;\n\t\t\t}\n\t\t\tGLC_End();\n\t\t}\n\t}\n}\n\n/*\nTo draw, for each surface, run through the triangles, getting tex coords from s+t,\n*/\nvoid GLC_DrawAlias3ModelPowerupShell(entity_t *ent)\n{\n\textern cvar_t gl_fb_models, gl_program_aliasmodels;\n\tmodel_t *mod = ent->model;\n\tmd3model_t *mhead = (md3model_t *)Mod_Extradata(mod);\n\tmd3Header_t *pheader = MD3_HeaderForModel(mhead);\n\tfloat oldMatrix[16];\n\tint frame1, frame2;\n\tfloat lerpfrac;\n\tqbool outline = false;\n\n\tR_PushModelviewMatrix(oldMatrix);\n\tR_AliasModelPrepare(ent, pheader->numFrames, &frame1, &frame2, &lerpfrac, &outline);\n\n\tGLC_StateBeginAliasPowerupShell(ent->renderfx & RF_WEAPONMODEL);\n\tif (gl_program_aliasmodels.integer && buffers.supported && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_AliasModelShellCompile()) {\n\t\tGLC_DrawAlias3ModelPowerupShellProgram(mod, pheader, frame1, ent, lerpfrac);\n\t}\n\telse if (GL_Supported(R_SUPPORT_IMMEDIATEMODE)) {\n\t\tGLC_DrawAlias3ModelPowerupShellImmediate(mod, pheader, frame1, frame2, ent, lerpfrac);\n\t}\n\n\tR_PopModelviewMatrix(oldMatrix);\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_misc.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"glc_local.h\"\n#include \"r_matrix.h\"\n#include \"r_state.h\"\n#include \"r_texture.h\"\n#include \"r_renderer.h\"\n#include \"gl_texture.h\"\n#include \"r_program.h\"\n\n// motion blur.\nvoid GLC_PolyBlend(float v_blend[4])\n{\n\tcolor_t v_blend_color = RGBA_TO_COLOR(\n\t\tbound(0, v_blend[0], 1) * 255,\n\t\tbound(0, v_blend[1], 1) * 255,\n\t\tbound(0, v_blend[2], 1) * 255,\n\t\tbound(0, v_blend[3], 1) * 255\n\t);\n\n\tR_ApplyRenderingState(r_state_poly_blend);\n\tDraw_AlphaRectangleRGB(r_refdef.vrect.x, r_refdef.vrect.y, r_refdef.vrect.width, r_refdef.vrect.height, 0.0f, true, v_blend_color);\n}\n\nvoid GLC_BrightenScreen(void)\n{\n\tfloat f;\n\n\tif (vid_hwgamma_enabled) {\n\t\treturn;\n\t}\n\tif (v_contrast.value <= 1.0) {\n\t\treturn;\n\t}\n\n\tf = min(v_contrast.value, 3);\n\tif (R_OldGammaBehaviour()) {\n\t\textern float vid_gamma;\n\n\t\tf = pow(f, vid_gamma);\n\t}\n\n\tR_ProgramUse(r_program_none);\n\tGLC_StateBeginBrightenScreen();\n\n\tGLC_Begin(GL_QUADS);\n\twhile (f > 1) {\n\t\tif (f >= 2) {\n\t\t\tR_CustomColor(1, 1, 1, 1);\n\t\t}\n\t\telse {\n\t\t\tR_CustomColor(f - 1, f - 1, f - 1, 1);\n\t\t}\n\n\t\tGLC_Vertex2f(-1, -1);\n\t\tGLC_Vertex2f(-1, 1);\n\t\tGLC_Vertex2f(1, 1);\n\t\tGLC_Vertex2f(1, -1);\n\n\t\tf *= 0.5;\n\t}\n\tGLC_End();\n}\n\nvoid GLC_PreRenderView(void)\n{\n\t// TODO\n}\n\nvoid GLC_SetupGL(void)\n{\n}\n\nvoid GLC_Shutdown(r_shutdown_mode_t mode)\n{\n\tif (mode != r_shutdown_reload) {\n\t\tGLC_FreeAliasPoseBuffer();\n\t\trenderer.ProgramsShutdown(mode == r_shutdown_restart);\n\t\tGL_DeleteSamplers();\n\t}\n\tGL_FramebufferDeleteAll();\n}\n\nvoid GLC_TextureInitialiseState(void)\n{\n\tGL_TextureInitialiseState();\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_particles.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"gl_sprite3d.h\"\n#include \"glm_particles.h\"\n#include \"particles_classic.h\"\n\nvoid GLC_DrawClassicParticles(int particles_to_draw)\n{\n\textern texture_ref particletexture;\n\tr_sprite3d_vert_t* vert;\n\n\tR_Sprite3DInitialiseBatch(SPRITE3D_PARTICLES_CLASSIC, r_state_particles_classic, particletexture, particletexture_array_index, r_primitive_triangles);\n\tvert = R_Sprite3DAddEntry(SPRITE3D_PARTICLES_CLASSIC, 3 * particles_to_draw);\n\tif (vert) {\n\t\textern r_sprite3d_vert_t glvertices[ABSOLUTE_MAX_PARTICLES * 3];\n\n\t\tmemcpy(vert, glvertices, particles_to_draw * 3 * sizeof(glvertices[0]));\n\t}\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_performance.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: gl_rmisc.c,v 1.27 2007-09-17 19:37:55 qqshka Exp $\n*/\n// gl_rmisc.c\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"tr_types.h\"\n#include \"r_renderer.h\"\n\nvoid GLC_TimeRefresh(void)\n{\n\tint i;\n\tfloat start, stop, time;\n\n\tif (!GL_FramebufferEnabled2D()) {\n#ifndef __APPLE__\n\t\tif (glConfig.hardwareType != GLHW_INTEL) {\n\t\t\t// Causes the console to flicker on Intel cards.\n\t\t\tGL_BuiltinProcedure(glDrawBuffer, \"mode=GL_FRONT\", GL_FRONT)\n\t\t}\n#endif\n\t}\n\n\trenderer.EnsureFinished();\n\n\tstart = Sys_DoubleTime();\n\tfor (i = 0; i < 128; i++) {\n\t\tr_refdef.viewangles[1] = i * (360.0 / 128.0);\n\t\tR_SetupFrame();\n\t\tR_RenderView();\n\t}\n\n\trenderer.EnsureFinished();\n\tstop = Sys_DoubleTime();\n\ttime = stop - start;\n\tCom_Printf(\"%f seconds (%f fps)\\n\", time, 128 / time);\n\n\tif (!GL_FramebufferEnabled2D()) {\n#ifndef __APPLE__\n\t\tif (glConfig.hardwareType != GLHW_INTEL) {\n\t\t\tGL_BuiltinProcedure(glDrawBuffer, \"mode=GL_BACK\", GL_BACK)\n\t\t}\n#endif\n\t}\n\n\tR_EndRendering();\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_sdl.c",
    "content": "/*\n===========================================================================\nCopyright (C) 1999-2005 Id Software, Inc.\n\nThis file is part of Quake III Arena source code.\n\nQuake III Arena source code is free software; you can redistribute it\nand/or modify it under the terms of the GNU General Public License as\npublished by the Free Software Foundation; either version 2 of the License,\nor (at your option) any later version.\n\nQuake III Arena source code is distributed in the hope that it will be\nuseful, but WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Foobar; if not, write to the Free Software\nFoundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n===========================================================================\n\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include <SDL.h>\n#include \"quakedef.h\"\n#include \"gl_local.h\"\n\nstatic opengl_version_t versions[] = {\n\t{ 4, 3, false, false },\n\t{ 3, 2, false, false },\n\t{ 0, 0, false, true },\n\t{ 4, 3, true, false },\n\t{ 3, 2, true, false },\n\t{ 0, 0, false, true },\n};\n\nSDL_GLContext GLC_SDL_CreateContext(SDL_Window* window)\n{\n\treturn GL_SDL_CreateBestContext(window, versions, sizeof(versions) / sizeof(versions[0]));\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_sky.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"r_brushmodel_sky.h\"\n#include \"glc_local.h\"\n#include \"r_brushmodel_sky.h\"\n#include \"r_renderer.h\"\n#include \"tr_types.h\"\n#include \"r_brushmodel.h\"\n#include \"r_program.h\"\n#include \"r_matrix.h\"\n\n#define SUBDIVISIONS 10\n\nstatic float skymins[2][6], skymaxs[2][6];\n\nstatic void GLC_DrawSkyBox(void);\nstatic void GLC_DrawSkyDome(void);\nstatic void GLC_DrawSkyFace(int axis, qbool multitexture);\nstatic void GLC_MakeSkyVec(float s, float t, int axis);\n\nstatic void R_SkyAddBoxSurface(msurface_t *fa);\nstatic void R_SkyClearVisibleLimits(void);\n\nstatic float speedscale, speedscale2;\t\t// for sky and clouds, respectively\n\ntypedef enum {\n\tskypoly_mode_mtex,\n\tskypoly_mode_sky,\n\tskypoly_mode_clouds,\n} skypoly_mode_id;\n\nstatic void GLC_DrawSky_Program(void);\nqbool GLC_SkyProgramCompile(void);\nextern cvar_t gl_program_sky;\n\n/*\n==============\nR_DrawSkyBox\n==============\n*/\n\n// 1 = s, 2 = t, 3 = 2048\nstatic int st_to_vec[6][3] = {\n\t{3,-1,2},\n\t{-3,1,2},\n\n\t{1,3,2},\n\t{-1,-3,2},\n\n\t{-2,-1,3},\t\t// 0 degrees yaw, look straight up\n\t{2,-1,-3}\t\t// look straight down\n};\n\n// s = [0]/[2], t = [1]/[2]\nstatic int vec_to_st[6][3] = {\n\t{-2,3,1},\n\t{2,3,-1},\n\n\t{1,3,2},\n\t{-1,3,-2},\n\n\t{-2,-1,3},\n\t{-2,1,-3}\n};\n\n// s and t range from -1 to 1\nstatic void Sky_MakeSkyVec2(float s, float t, int axis, vec3_t v)\n{\n\tvec3_t\t\tb;\n\tint\t\t\tj, k;\n\tb[0] = s;\n\tb[1] = t;\n\tb[2] = 1;\n\n\tfor (j = 0; j < 3; j++) {\n\t\tk = st_to_vec[axis][j];\n\t\tif (k < 0) {\n\t\t\tv[j] = -b[-k - 1];\n\t\t}\n\t\telse {\n\t\t\tv[j] = b[k - 1];\n\t\t}\n\t}\n}\n\nstatic void GLC_DrawFlatSkyPoly(glpoly_t* p, qbool texture)\n{\n\tint i;\n\n\tR_TraceAPI(\"%s()\", __func__);\n\n\tGLC_Begin(GL_TRIANGLE_STRIP);\n\tfor (i = 0; i < p->numverts; ++i) {\n\t\tfloat* v = p->verts[i];\n\t\tif (texture) {\n\t\t\tvec3_t direction;\n\t\t\t\n\t\t\tVectorSubtract(v, r_refdef.vieworg, direction);\n\n\t\t\tglTexCoord3f(direction[0], direction[2], direction[1]);\n\t\t}\n\t\tGLC_Vertex3fv(v);\n\t}\n\tGLC_End();\n}\n\nstatic void GLC_DrawFastSkyChain(void)\n{\n\tqbool use_vbo = buffers.supported && modelIndexes;\n\tint index_count = 0;\n\tmsurface_t* fa;\n\n\tfor (fa = skychain; fa; fa = fa->texturechain) {\n\t\tglpoly_t *p;\n\n\t\tfor (p = fa->polys; p; p = p->next) {\n\t\t\tif (use_vbo) {\n\t\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tGLC_DrawFlatSkyPoly(p, false);\n\t\t\t}\n\t\t}\n\t}\n\tif (index_count) {\n\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t}\n}\n\nstatic void GLC_EmitSkyPolys(msurface_t *fa, skypoly_mode_id mode)\n{\n\tglpoly_t *p;\n\tfloat *v, s, t, ss = 0.0, tt = 0.0, length;\n\tint i;\n\tvec3_t dir;\n\n\tR_TraceAPI(\"%s()\", __func__);\n\tfor (p = fa->polys; p; p = p->next) {\n\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\tfor (i = 0, v = p->verts[0]; i < p->numverts; i++, v += VERTEXSIZE) {\n\t\t\tVectorSubtract(v, r_origin, dir);\n\t\t\tdir[2] *= 3;\t// flatten the sphere\n\n\t\t\tlength = VectorLength(dir);\n\t\t\tlength = 6 * 63 / length;\n\n\t\t\tdir[0] *= length;\n\t\t\tdir[1] *= length;\n\n\t\t\ts = (speedscale + dir[0]) * (1.0 / 128);\n\t\t\tt = (speedscale + dir[1]) * (1.0 / 128);\n\n\t\t\tss = (speedscale2 + dir[0]) * (1.0 / 128);\n\t\t\ttt = (speedscale2 + dir[1]) * (1.0 / 128);\n\n\t\t\tswitch (mode) {\n\t\t\t\tcase skypoly_mode_mtex:\n\t\t\t\t\tGLC_MultiTexCoord2f(GL_TEXTURE0, s, t);\n\t\t\t\t\tGLC_MultiTexCoord2f(GL_TEXTURE1, ss, tt);\n\t\t\t\t\tbreak;\n\t\t\t\tcase skypoly_mode_sky:\n\t\t\t\t\tglTexCoord2f(s, t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase skypoly_mode_clouds:\n\t\t\t\t\tglTexCoord2f(ss, tt);\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tGLC_Vertex3fv(v);\n\t\t}\n\t\tGLC_End();\n\t}\n}\n\n// This is used for brushmodel entity surfaces only\nvoid GLC_SkyDrawChainedSurfaces(void)\n{\n\tmsurface_t *fa;\n\n\tif (!skychain) {\n\t\treturn;\n\t}\n\n\tif (gl_program_sky.integer && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_SkyProgramCompile()) {\n\t\tGLC_DrawSky_Program();\n\t}\n\telse {\n\t\tR_ProgramUse(r_program_none);\n\t\tif (r_fastsky.integer || cl.worldmodel->bspversion == HL_BSPVERSION) {\n\t\t\tGLC_StateBeginFastSky(false);\n\t\t\tGLC_DrawFastSkyChain();\n\t\t}\n\t\telse if (gl_mtexable) {\n\t\t\tGLC_StateBeginMultiTextureSkyChain();\n\n\t\t\tspeedscale = r_refdef2.time * 8;\n\t\t\tspeedscale -= (int)speedscale & ~127;\n\t\t\tspeedscale2 = r_refdef2.time * 16;\n\t\t\tspeedscale2 -= (int)speedscale2 & ~127;\n\n\t\t\tfor (fa = skychain; fa; fa = fa->texturechain) {\n\t\t\t\tGLC_EmitSkyPolys(fa, skypoly_mode_mtex);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tGLC_StateBeginSingleTextureSkyPass();\n\n\t\t\tspeedscale = r_refdef2.time * 8;\n\t\t\tspeedscale -= (int)speedscale & ~127;\n\n\t\t\tfor (fa = skychain; fa; fa = fa->texturechain) {\n\t\t\t\tGLC_EmitSkyPolys(fa, skypoly_mode_sky);\n\t\t\t}\n\n\t\t\tGLC_StateBeginSingleTextureCloudPass();\n\n\t\t\tspeedscale = r_refdef2.time * 16;\n\t\t\tspeedscale -= (int)speedscale & ~127;\n\n\t\t\tfor (fa = skychain; fa; fa = fa->texturechain) {\n\t\t\t\tGLC_EmitSkyPolys(fa, skypoly_mode_clouds);\n\t\t\t}\n\t\t}\n\t}\n\n\tskychain = NULL;\n\tskychain_tail = &skychain;\n}\n\nstatic qbool R_DetermineSkyLimits(qbool *ignore_z)\n{\n\tif (r_viewleaf->contents == CONTENTS_SOLID) {\n\t\t// always draw if we're in a solid leaf (probably outside the level)\n\t\t// FIXME: we don't really want to add all six planes every time!\n\t\tint i;\n\t\tfor (i = 0; i < 6; i++) {\n\t\t\tskymins[0][i] = skymins[1][i] = -1;\n\t\t\tskymaxs[0][i] = skymaxs[1][i] = 1;\n\t\t}\n\t\t*ignore_z = true;\n\t}\n\telse {\n\t\tmsurface_t* fa;\n\n\t\t// no sky at all\n\t\tif (!skychain) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// figure out how much of the sky box we need to draw\n\t\tR_SkyClearVisibleLimits();\n\t\tfor (fa = skychain; fa; fa = fa->texturechain) {\n\t\t\tR_SkyAddBoxSurface(fa);\n\t\t}\n\n\t\t*ignore_z = false;\n\t}\n\n\treturn true;\n}\n\n#define PROGRAMFLAGS_SKYBOX       1\n#define PROGRAMFLAGS_SKYWIND      2\n\nqbool GLC_SkyProgramCompile(void)\n{\n\tint flags = (r_skyboxloaded && R_UseCubeMapForSkyBox() ? PROGRAMFLAGS_SKYBOX : 0);\n\tif (flags && Skywind_Active()) {\n\t\tflags |= PROGRAMFLAGS_SKYWIND;\n\t}\n\n\tif (R_ProgramRecompileNeeded(r_program_sky_glc, flags)) {\n\t\tstatic char included_definitions[512];\n\n\t\tmemset(included_definitions, 0, sizeof(included_definitions));\n\t\tif (flags & PROGRAMFLAGS_SKYBOX) {\n\t\t\tstrlcat(included_definitions, \"#define DRAW_SKYBOX\\n\", sizeof(included_definitions));\n\t\t\tif (flags & PROGRAMFLAGS_SKYWIND) {\n\t\t\t\tstrlcat(included_definitions, \"#define DRAW_SKYWIND\\n\", sizeof(included_definitions));\n\t\t\t}\n\t\t}\n\n\t\tR_ProgramCompileWithInclude(r_program_sky_glc, included_definitions);\n\t\tif (flags & PROGRAMFLAGS_SKYBOX) {\n\t\t\tR_ProgramUniform1i(r_program_uniform_sky_glc_skyTex, 0);\n\t\t}\n\t\telse {\n\t\t\tR_ProgramUniform1i(r_program_uniform_sky_glc_skyDomeTex, 0);\n\t\t\tR_ProgramUniform1i(r_program_uniform_sky_glc_skyDomeCloudTex, 1);\n\t\t}\n\t\tR_ProgramSetCustomOptions(r_program_sky_glc, flags);\n\t}\n\n\tR_ProgramSetStandardUniforms(r_program_sky_glc);\n\n\treturn R_ProgramReady(r_program_sky_glc);\n}\n\nstatic void GLC_DrawSky_Program(void)\n{\n\textern msurface_t *skychain;\n\tfloat skySpeedscale = r_refdef2.time * 8;\n\tfloat skySpeedscale2 = r_refdef2.time * 16;\n\tfloat skyWind[4];\n\n\tskySpeedscale -= (int)skySpeedscale & ~127;\n\tskySpeedscale2 -= (int)skySpeedscale2 & ~127;\n\n\tskySpeedscale /= 128.0f;\n\tskySpeedscale2 /= 128.0f;\n\n\tR_TraceAPI(\"%s()\", __func__);\n\tR_ProgramUse(r_program_sky_glc);\n\tR_ProgramUniform1f(r_program_uniform_sky_glc_speedscale, skySpeedscale);\n\tR_ProgramUniform1f(r_program_uniform_sky_glc_speedscale2, skySpeedscale2);\n\tR_ProgramUniform3fv(r_program_uniform_sky_glc_cameraPosition, r_refdef.vieworg);\n\tif (Skywind_GetDirectionAndPhase(skyWind, &skyWind[3])) {\n\t\tR_ProgramUniform4fv(r_program_uniform_sky_glc_skyWind, skyWind);\n\t}\n\n\tif (r_skyboxloaded && R_UseCubeMapForSkyBox()) {\n\t\textern texture_ref skybox_cubeMap;\n\n\t\tglEnable(GL_TEXTURE_CUBE_MAP);\n\t\tR_ApplyRenderingState(r_state_skybox);\n\t\trenderer.TextureUnitBind(0, skybox_cubeMap);\n\t\tGLC_DrawFastSkyChain();\n\t\tglDisable(GL_TEXTURE_CUBE_MAP);\n\t}\n\telse {\n\t\tGLC_StateBeginMultiTextureSkyDome(true);\n\t\tGLC_DrawFastSkyChain();\n\t}\n\n\tR_ProgramUse(r_program_none);\n\tskychain = NULL;\n}\n\nvoid GLC_DrawSky(void)\n{\n\tmsurface_t\t*fa;\n\tqbool\t\tignore_z;\n\textern msurface_t *skychain;\n\n\tR_TraceAPI(\"%s()\", __func__);\n\tif (gl_program_sky.integer && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_SkyProgramCompile()) {\n\t\tGLC_DrawSky_Program();\n\t}\n\telse if (r_fastsky.integer) {\n\t\tR_ProgramUse(r_program_none);\n\t\tGLC_StateBeginFastSky(true);\n\t\tGLC_DrawFastSkyChain();\n\t}\n\telse if (r_skyboxloaded && R_UseCubeMapForSkyBox()) {\n\t\textern texture_ref skybox_cubeMap;\n\n\t\tR_ProgramUse(r_program_none);\n\t\tR_ApplyRenderingState(r_state_skybox);\n\t\trenderer.TextureUnitBind(0, skybox_cubeMap);\n\t\tGL_BuiltinProcedure(glEnable, \"cap=GL_TEXTURE_CUBE_MAP\", GL_TEXTURE_CUBE_MAP);\n\n\t\tfor (fa = skychain; fa; fa = fa->texturechain) {\n\t\t\tglpoly_t *p;\n\n\t\t\tfor (p = fa->polys; p; p = p->next) {\n\t\t\t\tGLC_DrawFlatSkyPoly(p, true);\n\t\t\t}\n\t\t}\n\n\t\tGL_BuiltinProcedure(glDisable, \"cap=GL_TEXTURE_CUBE_MAP\", GL_TEXTURE_CUBE_MAP);\n\t}\n\telse if (R_DetermineSkyLimits(&ignore_z)) {\n\t\t// draw a skybox or classic quake clouds\n\t\tR_ProgramUse(r_program_none);\n\t\tif (r_skyboxloaded) {\n\t\t\tGLC_DrawSkyBox();\n\t\t}\n\t\telse {\n\t\t\tGLC_DrawSkyDome();\n\t\t}\n\n\t\t// draw the sky polys into the Z buffer\n\t\t// don't need depth test yet\n\t\tif (!ignore_z) {\n\t\t\tGLC_StateBeginSkyZBufferPass();\n\t\t\tGLC_DrawFastSkyChain();\n\t\t}\n\t}\n\n\tskychain = NULL;\n\treturn;\n}\n\nstatic void EmitSkyVert(vec3_t v, qbool multitexture)\n{\n\tvec3_t dir;\n\tfloat length;\n\n\tVectorCopy(v, dir);\n\tVectorAdd(v, r_origin, v);\n\t//VectorSubtract(v, r_origin, dir);\n\tdir[2] *= 3;\t// flatten the sphere\n\n\tlength = VectorLength(dir);\n\tlength = 6 * 63 / length;\n\n\tdir[0] *= length;\n\tdir[1] *= length;\n\n\tif (multitexture) {\n\t\tGLC_MultiTexCoord2f(GL_TEXTURE0, (speedscale + dir[0]) * (1.0 / 128), (speedscale + dir[1]) * (1.0 / 128));\n\n\t\tGLC_MultiTexCoord2f(GL_TEXTURE1, (speedscale2 + dir[0]) * (1.0 / 128), (speedscale2 + dir[1]) * (1.0 / 128));\n\t}\n\telse {\n\t\tglTexCoord2f((speedscale + dir[0]) * (1.0 / 128), (speedscale + dir[1]) * (1.0 / 128));\n\t}\n\tGLC_Vertex3fv(v);\n}\n\nstatic void GLC_MakeSkyVec2(float s, float t, int axis, float range, vec3_t v)\n{\n\tSky_MakeSkyVec2(s, t, axis, v);\n\n\tv[0] *= range;\n\tv[1] *= range;\n\tv[2] *= range;\n}\n\nstatic void GLC_DrawSkyFace(int axis, qbool multitexture)\n{\n\tint i, j;\n\tvec3_t vecs[4];\n\tfloat s, t;\n\tint v;\n\tfloat fstep = 2.0 / SUBDIVISIONS;\n\tfloat skyrange = R_FarPlaneZ() * 0.577; // 0.577 < 1/sqrt(3)\n\n\tGLC_Begin(GL_QUADS);\n\tfor (v = 0, i = 0; i < SUBDIVISIONS; i++, v++)\n\t{\n\t\ts = (float)(i*2 - SUBDIVISIONS) / SUBDIVISIONS;\n\n\t\tif (s + fstep < skymins[0][axis] || s > skymaxs[0][axis]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (j = 0; j < SUBDIVISIONS; j++, v++) {\n\t\t\tt = (float)(j * 2 - SUBDIVISIONS) / SUBDIVISIONS;\n\n\t\t\tif (t + fstep < skymins[1][axis] || t > skymaxs[1][axis]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tGLC_MakeSkyVec2(s, t, axis, skyrange, vecs[0]);\n\t\t\tGLC_MakeSkyVec2(s, t + fstep, axis, skyrange, vecs[1]);\n\t\t\tGLC_MakeSkyVec2(s + fstep, t + fstep, axis, skyrange, vecs[2]);\n\t\t\tGLC_MakeSkyVec2(s + fstep, t, axis, skyrange, vecs[3]);\n\n\t\t\tEmitSkyVert(vecs[0], multitexture);\n\t\t\tEmitSkyVert(vecs[1], multitexture);\n\t\t\tEmitSkyVert(vecs[2], multitexture);\n\t\t\tEmitSkyVert(vecs[3], multitexture);\n\t\t}\n\t}\n\tGLC_End();\n}\n\nstatic void GLC_DrawSkyDome(void)\n{\n\tint i;\n\n\tif (gl_mtexable) {\n\t\tGLC_StateBeginMultiTextureSkyDome(false);\n\t}\n\telse {\n\t\tGLC_StateBeginSingleTextureSkyDome();\n\t}\n\n\tspeedscale = r_refdef2.time * 8;\n\tspeedscale -= (int)speedscale & ~127;\n\n\tspeedscale2 = r_refdef2.time * 16;\n\tspeedscale2 -= (int)speedscale2 & ~127;\n\n\tfor (i = 0; i < 6; i++) {\n\t\tif ((skymins[0][i] >= skymaxs[0][i] || skymins[1][i] >= skymaxs[1][i])) {\n\t\t\tcontinue;\n\t\t}\n\t\tGLC_DrawSkyFace(i, gl_mtexable);\n\t}\n\n\tif (!gl_mtexable) {\n\t\tGLC_StateBeginSingleTextureSkyDomeCloudPass();\n\n\t\tspeedscale = speedscale2;\n\n\t\tfor (i = 0; i < 6; i++) {\n\t\t\tif ((skymins[0][i] >= skymaxs[0][i] || skymins[1][i] >= skymaxs[1][i])) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tGLC_DrawSkyFace(i, false);\n\t\t}\n\t}\n}\n\nstatic void GLC_DrawSkyBox(void)\n{\n\tstatic int skytexorder[MAX_SKYBOXTEXTURES] = {0,2,1,3,4,5};\n\tint i;\n\n\tR_ApplyRenderingState(r_state_skybox);\n\n\tif (R_UseCubeMapForSkyBox()) {\n\t\textern texture_ref skybox_cubeMap;\n\n\t\trenderer.TextureUnitBind(0, skybox_cubeMap);\n\t\tglEnable(GL_TEXTURE_CUBE_MAP);\n\t}\n\n\tfor (i = 0; i < MAX_SKYBOXTEXTURES; i++) {\n\t\tif ((skymins[0][i] >= skymaxs[0][i] || skymins[1][i] >= skymaxs[1][i])) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!R_UseCubeMapForSkyBox()) {\n\t\t\trenderer.TextureUnitBind(0, skyboxtextures[(int)bound(0, skytexorder[i], MAX_SKYBOXTEXTURES - 1)]);\n\t\t}\n\n\t\tGLC_Begin(GL_QUADS);\n\t\tGLC_MakeSkyVec(skymins[0][i], skymins[1][i], i);\n\t\tGLC_MakeSkyVec(skymins[0][i], skymaxs[1][i], i);\n\t\tGLC_MakeSkyVec(skymaxs[0][i], skymaxs[1][i], i);\n\t\tGLC_MakeSkyVec(skymaxs[0][i], skymins[1][i], i);\n\t\tGLC_End();\n\t}\n\n\tif (R_UseCubeMapForSkyBox()) {\n\t\textern texture_ref skybox_cubeMap;\n\n\t\trenderer.TextureUnitBind(0, skybox_cubeMap);\n\t\tglDisable(GL_TEXTURE_CUBE_MAP);\n\t}\n}\n\nstatic void GLC_MakeSkyVec(float s, float t, int axis)\n{\n\tvec3_t v, b;\n\tfloat skyrange = R_FarPlaneZ() * 0.577; // 0.577 < 1/sqrt(3)\n\n\tSky_MakeSkyVec2(s, t, axis, b);\n\tVectorMA(r_origin, skyrange, b, v);\n\n\tif (R_UseCubeMapForSkyBox()) {\n\t\tglTexCoord3f(v[0], v[2], v[1]);\n\t}\n\telse {\n\t\t// avoid bilerp seam\n\t\ts = (s + 1) * 0.5;\n\t\tt = (t + 1) * 0.5;\n\n\t\ts = bound(1.0 / 512, s, 511.0 / 512);\n\t\tt = bound(1.0 / 512, t, 511.0 / 512);\n\n\t\tt = 1.0 - t;\n\n\t\tglTexCoord2f(s, t);\n\t}\n\tGLC_Vertex3fv(v);\n}\n\nstatic vec3_t skyclip[6] = {\n\t{ 1,1,0 },\n\t{ 1,-1,0 },\n\t{ 0,-1,1 },\n\t{ 0,1,1 },\n\t{ 1,0,1 },\n\t{ -1,0,1 }\n};\n\n#define skybox_range\t2400.0\n\nstatic void R_SkyPolygonCalcLimits(int nump, vec3_t vecs)\n{\n\tint i, j, axis;\n\tvec3_t v, av;\n\tfloat s, t, dv, *vp;\n\n\t// decide which face it maps to\n\tVectorClear(v);\n\tfor (i = 0, vp = vecs; i < nump; i++, vp += 3) {\n\t\tVectorAdd(vp, v, v);\n\t}\n\n\tav[0] = fabs(v[0]);\n\tav[1] = fabs(v[1]);\n\tav[2] = fabs(v[2]);\n\tif (av[0] > av[1] && av[0] > av[2]) {\n\t\taxis = (v[0] < 0) ? 1 : 0;\n\t}\n\telse if (av[1] > av[2] && av[1] > av[0]) {\n\t\taxis = (v[1] < 0) ? 3 : 2;\n\t}\n\telse {\n\t\taxis = (v[2] < 0) ? 5 : 4;\n\t}\n\n\t// project new texture coords\n\tfor (i = 0; i < nump; i++, vecs += 3) {\n\t\tj = vec_to_st[axis][2];\n\t\tdv = (j > 0) ? vecs[j - 1] : -vecs[-j - 1];\n\n\t\tj = vec_to_st[axis][0];\n\t\ts = (j < 0) ? -vecs[-j - 1] / dv : vecs[j - 1] / dv;\n\n\t\tj = vec_to_st[axis][1];\n\t\tt = (j < 0) ? -vecs[-j - 1] / dv : vecs[j - 1] / dv;\n\n\t\tif (s < skymins[0][axis]) {\n\t\t\tskymins[0][axis] = s;\n\t\t}\n\t\tif (t < skymins[1][axis]) {\n\t\t\tskymins[1][axis] = t;\n\t\t}\n\t\tif (s > skymaxs[0][axis]) {\n\t\t\tskymaxs[0][axis] = s;\n\t\t}\n\t\tif (t > skymaxs[1][axis]) {\n\t\t\tskymaxs[1][axis] = t;\n\t\t}\n\t}\n}\n\n#define\tMAX_CLIP_VERTS\t64\n\nstatic void R_SkyPolygonClip(int nump, vec3_t vecs, int stage)\n{\n\tfloat *norm, *v, d, e, dists[MAX_CLIP_VERTS];\n\tqbool front, back;\n\tint sides[MAX_CLIP_VERTS], newc[2], i, j;\n\tvec3_t newv[2][MAX_CLIP_VERTS];\n\n\tif (nump > MAX_CLIP_VERTS - 2) {\n\t\tSys_Error(\"ClipSkyPolygon: nump > MAX_CLIP_VERTS - 2\");\n\t}\n\tif (stage == 6) {\n\t\t// fully clipped, so draw it\n\t\tR_SkyPolygonCalcLimits(nump, vecs);\n\t\treturn;\n\t}\n\n\tfront = back = false;\n\tnorm = skyclip[stage];\n\tfor (i = 0, v = vecs; i < nump; i++, v += 3) {\n\t\td = DotProduct(v, norm);\n\t\tif (d > ON_EPSILON) {\n\t\t\tfront = true;\n\t\t\tsides[i] = SIDE_FRONT;\n\t\t}\n\t\telse if (d < -ON_EPSILON) {\n\t\t\tback = true;\n\t\t\tsides[i] = SIDE_BACK;\n\t\t}\n\t\telse {\n\t\t\tsides[i] = SIDE_ON;\n\t\t}\n\t\tdists[i] = d;\n\t}\n\n\tif (!front || !back) {\n\t\t// not clipped\n\t\tR_SkyPolygonClip(nump, vecs, stage + 1);\n\t\treturn;\n\t}\n\n\t// clip it\n\tsides[i] = sides[0];\n\tdists[i] = dists[0];\n\tVectorCopy(vecs, (vecs + (i * 3)));\n\tnewc[0] = newc[1] = 0;\n\n\tfor (i = 0, v = vecs; i < nump; i++, v += 3) {\n\t\tswitch (sides[i]) {\n\t\t\tcase SIDE_FRONT:\n\t\t\t\tVectorCopy(v, newv[0][newc[0]]);\n\t\t\t\tnewc[0]++;\n\t\t\t\tbreak;\n\t\t\tcase SIDE_BACK:\n\t\t\t\tVectorCopy(v, newv[1][newc[1]]);\n\t\t\t\tnewc[1]++;\n\t\t\t\tbreak;\n\t\t\tcase SIDE_ON:\n\t\t\t\tVectorCopy(v, newv[0][newc[0]]);\n\t\t\t\tnewc[0]++;\n\t\t\t\tVectorCopy(v, newv[1][newc[1]]);\n\t\t\t\tnewc[1]++;\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (sides[i] == SIDE_ON || sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])\n\t\t\tcontinue;\n\n\t\td = dists[i] / (dists[i] - dists[i + 1]);\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\te = v[j] + d * (v[j + 3] - v[j]);\n\t\t\tnewv[0][newc[0]][j] = e;\n\t\t\tnewv[1][newc[1]][j] = e;\n\t\t}\n\t\tnewc[0]++;\n\t\tnewc[1]++;\n\t}\n\n\t// continue\n\tR_SkyPolygonClip(newc[0], newv[0][0], stage + 1);\n\tR_SkyPolygonClip(newc[1], newv[1][0], stage + 1);\n}\n\n/*\n=================\nR_AddSkyBoxSurface\n=================\n*/\nstatic void R_SkyAddBoxSurface(msurface_t *fa)\n{\n\tint i, j, v;\n\tvec3_t verts[MAX_CLIP_VERTS];\n\tglpoly_t *p;\n\n\t// calculate vertex values for sky box\n\tfor (p = fa->polys; p; p = p->next) {\n\t\t// Meag: the R_SkyPolygonClip() requires verts in convex polygon order, i'm not messing\n\t\t//       with it... so we add the verts in a weird order to get back from triangle strip\n\t\t//       to verts\n\t\tVectorSubtract(p->verts[0], r_origin, verts[0]);\n\t\tfor (v = 1, i = 1, j = p->numverts - 1; v < p->numverts; ++v) {\n\t\t\tif (v % 2) {\n\t\t\t\tVectorSubtract(p->verts[v], r_origin, verts[i]);\n\t\t\t\t++i;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tVectorSubtract(p->verts[v], r_origin, verts[j]);\n\t\t\t\t--j;\n\t\t\t}\n\t\t}\n\t\tR_SkyPolygonClip(p->numverts, verts[0], 0);\n\t}\n}\n\nstatic void R_SkyClearVisibleLimits(void)\n{\n\tint i;\n\tfor (i = 0; i < 6; i++) {\n\t\tskymins[0][i] = skymins[1][i] = 9999;\n\t\tskymaxs[0][i] = skymaxs[1][i] = -9999;\n\t}\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_sprite3d.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n// 3D sprites\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"r_local.h\"\n#include \"r_state.h\"\n#include \"gl_sprite3d.h\"\n#include \"r_sprite3d_internal.h\"\n#include \"glc_vao.h\"\n#include \"tr_types.h\"\n#include \"glc_local.h\"\n#include \"r_renderer.h\"\n#include \"r_texture.h\"\n\nstatic void GLC_Create3DSpriteVAO(void)\n{\n\tif (buffers.supported) {\n\t\tR_GenVertexArray(vao_3dsprites);\n\t\tR_Sprite3DCreateVBO();\n\t\tR_Sprite3DCreateIndexBuffer();\n\n\t\tGLC_VAOSetIndexBuffer(vao_3dsprites, r_buffer_sprite_index_data);\n\t\tGLC_VAOSetVertexBuffer(vao_3dsprites, r_buffer_sprite_vertex_data);\n\t\tGLC_VAOEnableVertexPointer(vao_3dsprites, 3, GL_FLOAT, sizeof(r_sprite3d_vert_t), VBO_FIELDOFFSET(r_sprite3d_vert_t, position));\n\t\tGLC_VAOEnableColorPointer(vao_3dsprites, 4, GL_UNSIGNED_BYTE, sizeof(r_sprite3d_vert_t), VBO_FIELDOFFSET(r_sprite3d_vert_t, color));\n\t\tGLC_VAOEnableTextureCoordPointer(vao_3dsprites, 0, 2, GL_FLOAT, sizeof(r_sprite3d_vert_t), VBO_FIELDOFFSET(r_sprite3d_vert_t, tex));\n\t}\n}\n\nstatic void GLC_DrawSequentialBatch(gl_sprite3d_batch_t* batch, int index_offset, GLuint maximum_batch_size)\n{\n\tif (R_TextureReferenceIsValid(batch->texture)) {\n\t\tR_ApplyRenderingState(batch->rendering_state);\n\t\trenderer.TextureUnitBind(0, batch->texture);\n\n\t\t// All batches are the same texture, so no issues\n\t\tGL_DrawSequentialBatchImpl(batch, 0, batch->count, index_offset, maximum_batch_size);\n\t}\n\telse {\n\t\t// Group by texture usage\n\t\tint start = 0, end = 1;\n\n\t\tR_ApplyRenderingState(batch->rendering_state);\n\t\tif (R_TextureReferenceIsValid(batch->textures[start])) {\n\t\t\trenderer.TextureUnitBind(0, batch->textures[start]);\n\t\t}\n\t\telse {\n\t\t\trenderer.TextureUnitBind(0, solidwhite_texture);\n\t\t}\n\t\tfor (end = 1; end < batch->count; ++end) {\n\t\t\tif (!R_TextureReferenceEqual(batch->textures[start], batch->textures[end])) {\n\t\t\t\tGL_DrawSequentialBatchImpl(batch, start, end, index_offset, maximum_batch_size);\n\n\t\t\t\tR_ApplyRenderingState(batch->rendering_state);\n\t\t\t\tif (R_TextureReferenceIsValid(batch->textures[end])) {\n\t\t\t\t\trenderer.TextureUnitBind(0, batch->textures[end]);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\trenderer.TextureUnitBind(0, solidwhite_texture);\n\t\t\t\t}\n\t\t\t\tstart = end;\n\t\t\t}\n\t\t}\n\n\t\tif (end > start) {\n\t\t\tGL_DrawSequentialBatchImpl(batch, start, end, index_offset, maximum_batch_size);\n\t\t}\n\t}\n}\n\nqbool GLC_CompileSpriteProgram(void)\n{\n\tif (R_ProgramRecompileNeeded(r_program_sprites_glc, 0)) {\n\t\tchar included_definitions[1024];\n\n\t\tmemset(included_definitions, 0, sizeof(included_definitions));\n\n\t\tR_ProgramCompileWithInclude(r_program_sprites_glc, included_definitions);\n\t\tR_ProgramUniform1i(r_program_uniform_sprites_glc_materialSampler, 0);\n\t\tR_ProgramUniform1f(r_program_uniform_sprites_glc_alphaThreshold, 0);\n\t}\n\n\tR_ProgramSetStandardUniforms(r_program_sprites_glc);\n\n\treturn R_ProgramReady(r_program_sprites_glc);\n}\n\nstatic void GLC_Draw3DSpritesVertexArray(gl_sprite3d_batch_t* batch)\n{\n\tif (batch->count == 1) {\n\t\tR_ApplyRenderingState(batch->rendering_state);\n\t\tif (R_TextureReferenceIsValid(batch->textures[0])) {\n\t\t\trenderer.TextureUnitBind(0, batch->textures[0]);\n\t\t}\n\t\telse if (R_TextureReferenceIsValid(batch->texture)) {\n\t\t\trenderer.TextureUnitBind(0, batch->texture);\n\t\t}\n\t\telse {\n\t\t\trenderer.TextureUnitBind(0, solidwhite_texture);\n\t\t}\n\t\tGL_DrawArrays(glPrimitiveTypes[batch->primitive_id], batch->glFirstVertices[0], batch->numVertices[0]);\n\t}\n\telse if (GL_DrawElementsBaseVertexAvailable() && batch->allSameNumber && batch->numVertices[0] == 4 && batch->primitive_id == r_primitive_triangle_strip) {\n\t\tGLC_DrawSequentialBatch(batch, indexes_start_quads, INDEXES_MAX_QUADS);\n\t}\n\telse if (GL_DrawElementsBaseVertexAvailable() && batch->allSameNumber && batch->numVertices[0] == 9 && GL_Supported(R_SUPPORT_PRIMITIVERESTART)) {\n\t\tGLC_DrawSequentialBatch(batch, indexes_start_sparks, INDEXES_MAX_SPARKS);\n\t}\n\telse if (GL_DrawElementsBaseVertexAvailable() && batch->allSameNumber && batch->numVertices[0] == 18 && GL_Supported(R_SUPPORT_PRIMITIVERESTART)) {\n\t\tGLC_DrawSequentialBatch(batch, indexes_start_flashblend, INDEXES_MAX_FLASHBLEND);\n\t}\n\telse if (R_TextureReferenceIsValid(batch->texture)) {\n\t\trenderer.TextureUnitBind(0, batch->texture);\n\t\tR_ApplyRenderingState(batch->rendering_state);\n\n\t\tGL_MultiDrawArrays(glPrimitiveTypes[batch->primitive_id], batch->glFirstVertices, batch->numVertices, batch->count);\n\t}\n\telse {\n\t\tint first = 0, last = 1;\n\n\t\tR_ApplyRenderingState(batch->rendering_state);\n\t\tif (R_TextureReferenceIsValid(batch->textures[0])) {\n\t\t\trenderer.TextureUnitBind(0, batch->textures[0]);\n\t\t}\n\t\telse {\n\t\t\trenderer.TextureUnitBind(0, solidwhite_texture);\n\t\t}\n\n\t\twhile (last < batch->count) {\n\t\t\tif (!R_TextureReferenceEqual(batch->textures[first], batch->textures[last])) {\n\t\t\t\tGL_MultiDrawArrays(glPrimitiveTypes[batch->primitive_id], batch->glFirstVertices, batch->numVertices, last - first);\n\n\t\t\t\tif (R_TextureReferenceIsValid(batch->textures[last])) {\n\t\t\t\t\trenderer.TextureUnitBind(0, batch->textures[last]);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\trenderer.TextureUnitBind(0, solidwhite_texture);\n\t\t\t\t}\n\t\t\t\tfirst = last;\n\t\t\t}\n\t\t\t++last;\n\t\t}\n\n\t\tGL_MultiDrawArrays(glPrimitiveTypes[batch->primitive_id], batch->glFirstVertices, batch->numVertices, last - first);\n\t}\n}\n\nstatic void GLC_Draw3DSpritesImmediate(gl_sprite3d_batch_t* batch)\n{\n\tint j, k;\n\n\t// Immediate mode, no VBOs\n\tfor (j = 0; j < batch->count; ++j) {\n\t\tr_sprite3d_vert_t* v;\n\n\t\tR_ApplyRenderingState(batch->rendering_state);\n\t\tif (R_TextureReferenceIsValid(batch->textures[j])) {\n\t\t\trenderer.TextureUnitBind(0, batch->textures[j]);\n\t\t}\n\t\telse {\n\t\t\trenderer.TextureUnitBind(0, batch->texture);\n\t\t}\n\n\t\tGLC_Begin(glPrimitiveTypes[batch->primitive_id]);\n\t\tv = &verts[batch->firstVertices[j]];\n\t\tfor (k = 0; k < batch->numVertices[j]; ++k, ++v) {\n\t\t\tglTexCoord2fv(v->tex);\n\t\t\tR_CustomColor4ubv(v->color);\n\t\t\tGLC_Vertex3fv(v->position);\n\t\t}\n\t\tGLC_End();\n\t}\n}\n\nvoid GLC_Draw3DSpritesInline(void)\n{\n\tunsigned int i;\n\n\tif (!batchCount || !vertexCount || (batchCount == 1 && !batches[0].count)) {\n\t\treturn;\n\t}\n\n\tif (buffers.supported) {\n\t\textern cvar_t gl_program_sprites;\n\n\t\tif (!R_VertexArrayCreated(vao_3dsprites)) {\n\t\t\tGLC_Create3DSpriteVAO();\n\t\t}\n\t\tbuffers.Update(r_buffer_sprite_vertex_data, vertexCount * sizeof(verts[0]), verts);\n\n\t\tif (gl_program_sprites.integer && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_CompileSpriteProgram()) {\n\t\t\tR_ProgramUse(r_program_sprites_glc);\n\t\t}\n\t\telse {\n\t\t\tR_ProgramUse(r_program_none);\n\t\t}\n\t}\n\telse {\n\t\tR_ProgramUse(r_program_none);\n\t}\n\n\tfor (i = 0; i < batchCount; ++i) {\n\t\tgl_sprite3d_batch_t* batch = &batches[i];\n\n\t\tif (!batch->count) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (buffers.supported) {\n\t\t\tGLC_Draw3DSpritesVertexArray(batch);\n\t\t}\n\t\telse {\n\t\t\tGLC_Draw3DSpritesImmediate(batch);\n\t\t}\n\n\t\tbatch->count = 0;\n\t}\n\n\tR_Sprite3DClearBatches();\n\tR_ProgramUse(r_program_none);\n}\n\nvoid GLC_DrawSimpleItem(model_t* model, int skin, vec3_t org, float sprsize, vec3_t up, vec3_t right)\n{\n\ttexture_ref simpletexture = model->simpletexture[skin];\n\tr_sprite3d_vert_t* vert = R_Sprite3DAddEntrySpecific(SPRITE3D_ENTITIES, 4, simpletexture, 0);\n\n\tif (vert) {\n\t\tR_Sprite3DRender(vert, org, up, right, sprsize, -sprsize, -sprsize, sprsize, 1, 1, 0);\n\t}\n}\n\nvoid GLC_DrawSpriteModel(entity_t* e)\n{\n\tvec3_t right, up;\n\tmspriteframe_t *frame;\n\tmsprite2_t *psprite;\n\tr_sprite3d_vert_t* vert;\n\n\t// don't even bother culling, because it's just a single\n\t// polygon without a surface cache\n\tpsprite = (msprite2_t*)Mod_Extradata(e->model);\t//locate the proper data\n\tframe = R_GetSpriteFrame(e, psprite);\n\n\tif (!frame || !R_TextureReferenceIsValid(frame->gl_texturenum))\n\t\treturn;\n\n\tif (psprite->type == SPR_ORIENTED) {\n\t\t// bullet marks on walls\n\t\tAngleVectors(e->angles, NULL, right, up);\n\t}\n\telse if (psprite->type == SPR_FACING_UPRIGHT) {\n\t\tVectorSet(up, 0, 0, 1);\n\t\tright[0] = e->origin[1] - r_origin[1];\n\t\tright[1] = -(e->origin[0] - r_origin[0]);\n\t\tright[2] = 0;\n\t\tVectorNormalizeFast(right);\n\t}\n\telse if (psprite->type == SPR_VP_PARALLEL_UPRIGHT) {\n\t\tVectorSet(up, 0, 0, 1);\n\t\tVectorCopy(vright, right);\n\t}\n\telse {\t// normal sprite\n\t\tVectorCopy(vup, up);\n\t\tVectorCopy(vright, right);\n\t}\n\n\tvert = R_Sprite3DAddEntrySpecific(SPRITE3D_ENTITIES, 4, frame->gl_texturenum, 0);\n\tif (vert) {\n\t\tvec3_t points[4];\n\n\t\tVectorMA(e->origin, frame->up, up, points[0]);\n\t\tVectorMA(points[0], frame->left, right, points[0]);\n\t\tVectorMA(e->origin, frame->down, up, points[1]);\n\t\tVectorMA(points[1], frame->left, right, points[1]);\n\t\tVectorMA(e->origin, frame->up, up, points[2]);\n\t\tVectorMA(points[2], frame->right, right, points[2]);\n\t\tVectorMA(e->origin, frame->down, up, points[3]);\n\t\tVectorMA(points[3], frame->right, right, points[3]);\n\n\t\tR_Sprite3DSetVert(vert++, points[0][0], points[0][1], points[0][2], 0, 0, color_white, 0);\n\t\tR_Sprite3DSetVert(vert++, points[1][0], points[1][1], points[1][2], 0, 1, color_white, 0);\n\t\tR_Sprite3DSetVert(vert++, points[2][0], points[2][1], points[2][2], 1, 0, color_white, 0);\n\t\tR_Sprite3DSetVert(vert++, points[3][0], points[3][1], points[3][2], 1, 1, color_white, 0);\n\t}\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_state.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_local.h\"\n#include \"glc_state.h\"\n#include \"glc_vao.h\"\n#include \"r_trace.h\"\n#include \"r_aliasmodel.h\"\n#include \"r_matrix.h\"\n#include \"r_renderer.h\"\n#include \"r_program.h\"\n\nextern texture_ref solidskytexture, alphaskytexture;\n\nvoid GLC_StateBeginFastTurbPoly(byte color[4])\n{\n\tfloat wateralpha = r_refdef2.wateralpha;\n\twateralpha = bound(0, wateralpha, 1);\n\n\tR_TraceEnterFunctionRegion;\n\n\t// START shaman FIX /gl_turbalpha + /r_fastturb {\n\tR_CustomColor(color[0] * wateralpha / 255.0f, color[1] * wateralpha / 255.0f, color[2] * wateralpha / 255.0f, wateralpha);\n\t// END shaman FIX /gl_turbalpha + /r_fastturb {\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_StateBeginBlendLightmaps(qbool use_buffers)\n{\n\tR_ApplyRenderingState(r_state_world_blend_lightmaps);\n}\n\nvoid GLC_StateBeginCausticsPolys(void)\n{\n\tR_ApplyRenderingState(r_state_world_caustics);\n}\n\n// Alias models\nvoid GLC_StateBeginUnderwaterAliasModelCaustics(texture_ref base_texture, texture_ref caustics_texture)\n{\n\tR_ApplyRenderingState(r_state_aliasmodel_caustics);\n\trenderer.TextureUnitBind(0, base_texture);\n\tGLC_BeginCausticsTextureMatrix();\n\trenderer.TextureUnitBind(1, caustics_texture);\n}\n\nvoid GLC_StateEndUnderwaterAliasModelCaustics(void)\n{\n\tGLC_EndCausticsTextureMatrix();\n}\n\nvoid GLC_StateBeginWaterSurfaces(void)\n{\n\textern cvar_t r_fastturb;\n\tfloat wateralpha = r_refdef2.wateralpha;\n\n\tif (r_fastturb.integer) {\n\t\tif (wateralpha < 1.0) {\n\t\t\tR_ApplyRenderingState(r_state_world_fast_translucent_water);\n\t\t\tR_CustomColor(wateralpha, wateralpha, wateralpha, wateralpha);\n\t\t}\n\t\telse {\n\t\t\tR_ApplyRenderingState(r_state_world_fast_opaque_water);\n\t\t}\n\t}\n\telse {\n\t\tif (wateralpha < 1.0) {\n\t\t\tR_ApplyRenderingState(r_state_world_translucent_water);\n\t\t\tR_CustomColor(wateralpha, wateralpha, wateralpha, wateralpha);\n\t\t}\n\t\telse {\n\t\t\tR_ApplyRenderingState(r_state_world_opaque_water);\n\t\t}\n\t}\n}\n\nvoid GLC_StateBeginAlphaChain(void)\n{\n\tR_ApplyRenderingState(r_state_world_alpha_surfaces);\n}\n\nvoid GLC_StateBeginAlphaChainSurface(msurface_t* s)\n{\n\ttexture_t* t = s->texinfo->texture;\n\n\tR_TraceEnterFunctionRegion;\n\n\t//bind the world texture\n\trenderer.TextureUnitBind(0, t->gl_texturenum);\n\tif (gl_mtexable) {\n\t\trenderer.TextureUnitBind(1, GLC_LightmapTexture(s->lightmaptexturenum));\n\t}\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_StateBeginRenderFullbrights(void)\n{\n\tR_ApplyRenderingState(r_state_world_fullbrights);\n}\n\nvoid GLC_StateBeginRenderLumas(void)\n{\n\tR_ApplyRenderingState(r_state_world_lumas);\n}\n\nvoid GLC_StateBeginEmitDetailPolys(void)\n{\n\textern texture_ref detailtexture;\n\n\tR_ApplyRenderingState(r_state_world_details);\n\trenderer.TextureUnitBind(0, detailtexture);\n}\n\nvoid GLC_StateBeginDrawMapOutline(void)\n{\n\tR_ApplyRenderingState(r_state_world_outline);\n}\n\nvoid GLC_StateBeginAliasPowerupShell(qbool weapon)\n{\n\textern texture_ref shelltexture;\n\n\tR_ApplyRenderingState(weapon ? r_state_weaponmodel_powerupshell : r_state_aliasmodel_powerupshell);\n\trenderer.TextureUnitBind(0, shelltexture);\n}\n\nvoid GLC_StateBeginMD3Draw(float alpha, qbool textured, qbool weapon, qbool additive_pass)\n{\n\tqbool transparent = (alpha < 1);\n\n\tif (additive_pass) {\n\t\tR_ApplyRenderingState(weapon ? r_state_weaponmodel_singletexture_additive : textured ? r_state_aliasmodel_singletexture_additive : r_state_aliasmodel_notexture_additive);\n\t}\n\telse if (weapon) {\n\t\tR_ApplyRenderingState(transparent ? r_state_weaponmodel_singletexture_transparent : r_state_weaponmodel_singletexture_opaque);\n\t}\n\telse if (textured) {\n\t\tR_ApplyRenderingState(transparent ? r_state_aliasmodel_singletexture_transparent : r_state_aliasmodel_singletexture_opaque);\n\t}\n\telse {\n\t\tR_ApplyRenderingState(transparent ? r_state_aliasmodel_notexture_transparent : r_state_aliasmodel_notexture_opaque);\n\t}\n}\n\nvoid GLC_StateBeginDrawAliasZPass(qbool weapon_model)\n{\n\tR_ApplyRenderingState(weapon_model ? r_state_weaponmodel_transparent_zpass : r_state_aliasmodel_transparent_zpass);\n}\n\nvoid GLC_StateBeginDrawAliasFrameProgram(texture_ref texture, texture_ref fb_texture, int render_effects, struct custom_model_color_s* custom_model, float ent_alpha, qbool additive_pass)\n{\n\tqbool weapon_model = render_effects & RF_WEAPONMODEL;\n\tqbool alpha_blend = (render_effects & RF_ALPHABLEND) || ent_alpha < 1;\n\tqbool no_texture = !weapon_model && (!R_TextureReferenceIsValid(texture) || (custom_model && custom_model->fullbright_cvar.integer));\n\tqbool multi_texture = custom_model == NULL && R_TextureReferenceIsValid(fb_texture);\n\n\tR_TraceEnterFunctionRegion;\n\n\tif (no_texture) {\n\t\tR_ApplyRenderingState(additive_pass ? r_state_aliasmodel_notexture_additive : alpha_blend ? r_state_aliasmodel_notexture_transparent : r_state_aliasmodel_notexture_opaque);\n\t}\n\telse if (multi_texture) {\n\t\t// meag: no additive_pass here yet as .mdl has multitexture, .md3 has additive surfaces\n\t\tif (weapon_model) {\n\t\t\tR_ApplyRenderingState(alpha_blend ? r_state_weaponmodel_multitexture_transparent : r_state_weaponmodel_multitexture_opaque);\n\t\t}\n\t\telse {\n\t\t\tR_ApplyRenderingState(alpha_blend ? r_state_aliasmodel_multitexture_transparent : r_state_aliasmodel_multitexture_opaque);\n\t\t}\n\t\trenderer.TextureUnitBind(0, texture);\n\t\tif (render_effects & RF_CAUSTICS) {\n\t\t\tGLC_BeginCausticsTextureMatrix();\n\t\t}\n\t\trenderer.TextureUnitBind(1, fb_texture);\n\t}\n\telse {\n\t\tif (weapon_model) {\n\t\t\tR_ApplyRenderingState(additive_pass ? r_state_weaponmodel_singletexture_additive : alpha_blend ? r_state_weaponmodel_singletexture_transparent : r_state_weaponmodel_singletexture_opaque);\n\t\t}\n\t\telse {\n\t\t\tR_ApplyRenderingState(additive_pass ? r_state_aliasmodel_singletexture_additive : alpha_blend ? r_state_aliasmodel_singletexture_transparent : r_state_aliasmodel_singletexture_opaque);\n\t\t}\n\t\trenderer.TextureUnitBind(0, texture);\n\t}\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_StateBeginDrawAliasFrame(texture_ref texture, texture_ref fb_texture, qbool mtex, qbool alpha_blend, struct custom_model_color_s* custom_model, qbool weapon_model)\n{\n\tR_TraceEnterFunctionRegion;\n\n\tif (!weapon_model && (!R_TextureReferenceIsValid(texture) || (custom_model && (custom_model->fullbright_cvar.integer || custom_model->disable_texturing)))) {\n\t\tR_ApplyRenderingState(alpha_blend ? r_state_aliasmodel_notexture_transparent : r_state_aliasmodel_notexture_opaque);\n\t}\n\telse if (custom_model == NULL && R_TextureReferenceIsValid(fb_texture) && mtex) {\n\t\tR_ApplyRenderingState(weapon_model ? (alpha_blend ? r_state_weaponmodel_multitexture_transparent : r_state_weaponmodel_multitexture_opaque) : (alpha_blend ? r_state_aliasmodel_multitexture_transparent : r_state_aliasmodel_multitexture_opaque));\n\t\trenderer.TextureUnitBind(0, texture);\n\t\trenderer.TextureUnitBind(1, fb_texture);\n\t}\n\telse {\n\t\tR_ApplyRenderingState(weapon_model ? (alpha_blend ? r_state_weaponmodel_singletexture_transparent : r_state_weaponmodel_singletexture_opaque) : (alpha_blend ? r_state_aliasmodel_singletexture_transparent : r_state_aliasmodel_singletexture_opaque));\n\t\trenderer.TextureUnitBind(0, texture);\n\t}\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_StateBeginAliasModelShadow(void)\n{\n\tR_ApplyRenderingState(r_state_aliasmodel_shadows);\n}\n\n#ifdef WITH_RENDERING_TRACE\nstatic int glcVertsPerPrimitive = 0;\nstatic int glcBaseVertsPerPrimitive = 0;\nstatic int glcVertsSent = 0;\nstatic const char* glcPrimitiveName = \"?\";\n#endif\n\nvoid GLC_Begin(GLenum primitive)\n{\n#ifdef WITH_RENDERING_TRACE\n\tglcVertsSent = 0;\n\tglcVertsPerPrimitive = 0;\n\tglcBaseVertsPerPrimitive = 0;\n\tglcPrimitiveName = \"?\";\n\n\tGL_ProcessErrors(\"glBegin\");\n\tswitch (primitive) {\n\t\tcase GL_QUADS:\n\t\t\tglcVertsPerPrimitive = 4;\n\t\t\tglcBaseVertsPerPrimitive = 0;\n\t\t\tglcPrimitiveName = \"GL_QUADS\";\n\t\t\tbreak;\n\t\tcase GL_POLYGON:\n\t\t\tglcVertsPerPrimitive = 0;\n\t\t\tglcBaseVertsPerPrimitive = 0;\n\t\t\tglcPrimitiveName = \"GL_POLYGON\";\n\t\t\tbreak;\n\t\tcase GL_TRIANGLE_FAN:\n\t\t\tglcVertsPerPrimitive = 1;\n\t\t\tglcBaseVertsPerPrimitive = 2;\n\t\t\tglcPrimitiveName = \"GL_TRIANGLE_FAN\";\n\t\t\tbreak;\n\t\tcase GL_TRIANGLE_STRIP:\n\t\t\tglcVertsPerPrimitive = 1;\n\t\t\tglcBaseVertsPerPrimitive = 2;\n\t\t\tglcPrimitiveName = \"GL_TRIANGLE_STRIP\";\n\t\t\tbreak;\n\t\tcase GL_LINE_LOOP:\n\t\t\tglcVertsPerPrimitive = 1;\n\t\t\tglcBaseVertsPerPrimitive = 1;\n\t\t\tglcPrimitiveName = \"GL_LINE_LOOP\";\n\t\t\tbreak;\n\t\tcase GL_LINES:\n\t\t\tglcVertsPerPrimitive = 2;\n\t\t\tglcBaseVertsPerPrimitive = 0;\n\t\t\tglcPrimitiveName = \"GL_LINES\";\n\t\t\tbreak;\n\t}\n#endif\n\n\t++frameStats.draw_calls;\n\tglBegin(primitive);\n\tR_TraceLogAPICall(\"glBegin(%s...)\", glcPrimitiveName);\n}\n\n#undef glEnd\n\nvoid GLC_End(void)\n{\n#ifdef WITH_RENDERING_TRACE\n\tint primitives;\n\tconst char* count_name = \"vertices\";\n#endif\n\n\tglEnd();\n\n#ifdef WITH_RENDERING_TRACE\n\tprimitives = max(0, glcVertsSent - glcBaseVertsPerPrimitive);\n\tif (glcVertsPerPrimitive) {\n\t\tprimitives = glcVertsSent / glcVertsPerPrimitive;\n\t\tcount_name = \"primitives\";\n\t}\n\tR_TraceLogAPICall(\"glEnd(%s: %d %s)\", glcPrimitiveName, primitives, count_name);\n\tGL_ProcessErrors(\"glEnd\");\n#endif\n}\n\nvoid GLC_Vertex2f(GLfloat x, GLfloat y)\n{\n\tglVertex2f(x, y);\n#ifdef WITH_RENDERING_TRACE\n\t++glcVertsSent;\n#endif\n}\n\nvoid GLC_Vertex2fv(const GLfloat* v)\n{\n\tglVertex2fv(v);\n#ifdef WITH_RENDERING_TRACE\n\t++glcVertsSent;\n#endif\n}\n\nvoid GLC_Vertex3f(GLfloat x, GLfloat y, GLfloat z)\n{\n\tglVertex3f(x, y, z);\n#ifdef WITH_RENDERING_TRACE\n\t++glcVertsSent;\n#endif\n}\n\nvoid GLC_Vertex3fv(const GLfloat* v)\n{\n\tglVertex3fv(v);\n#ifdef WITH_RENDERING_TRACE\n\t++glcVertsSent;\n#endif\n}\n\nvoid GLC_InitialiseSkyStates(void)\n{\n\trendering_state_t* state;\n\n\tstate = R_InitRenderingState(r_state_sky_fast, true, \"fastSkyState\", vao_brushmodel);\n\tstate->depth.test_enabled = false;\n\n\tstate = R_InitRenderingState(r_state_skybox, true, \"glcSkyBox\", vao_brushmodel);\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_replace);\n\n\tstate = R_CopyRenderingState(r_state_sky_fast_bmodel, r_state_sky_fast, \"fastSkyState_bmodel\");\n\tstate->depth.test_enabled = true;\n\n\tstate = R_InitRenderingState(r_state_skydome_zbuffer_pass, true, \"skyDomeZPassState\", vao_brushmodel);\n\tstate->depth.test_enabled = true;\n\tstate->blendingEnabled = true;\n\tstate->fog.mode = r_fogmode_disabled;\n\tstate->colorMask[0] = state->colorMask[1] = state->colorMask[2] = state->colorMask[3] = false;\n\tstate->blendFunc = r_blendfunc_src_zero_dest_one;\n\n\tstate = R_InitRenderingState(r_state_skydome_zbuffer_pass_fogged, true, \"skyDomeZPassFoggedState\", vao_brushmodel);\n\tstate->depth.test_enabled = true;\n\tstate->blendingEnabled = true;\n\tstate->fog.mode = r_fogmode_enabled;\n\tstate->blendFunc = r_blendfunc_src_one_dest_zero;\n\n\tstate = R_InitRenderingState(r_state_skydome_background_pass, true, \"skyDomeFirstPassState\", vao_none);\n\tstate->depth.test_enabled = false;\n\tstate->blendingEnabled = false;\n\tstate->textureUnits[0].enabled = true;\n\tstate->textureUnits[0].mode = r_texunit_mode_replace;\n\n\tstate = R_CopyRenderingState(r_state_skydome_background_pass_bmodel, r_state_skydome_background_pass, \"skyDomeFirstPassState_bmodel\");\n\tstate->depth.test_enabled = true;\n\n\tstate = R_InitRenderingState(r_state_skydome_cloud_pass, true, \"skyDomeCloudPassState\", vao_none);\n\tstate->depth.test_enabled = false;\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tstate->textureUnits[0].enabled = true;\n\tstate->textureUnits[0].mode = r_texunit_mode_replace;\n\n\tstate = R_CopyRenderingState(r_state_skydome_cloud_pass_bmodel, r_state_skydome_cloud_pass, \"skyDomeCloudPassState_bmodel\");\n\tstate->depth.test_enabled = true;\n\n\t// Used when rendering the skydome/cloud background, prior to z-pass\n\tstate = R_InitRenderingState(r_state_skydome_single_pass, true, \"skyDomeSinglePassState\", vao_brushmodel);\n\tstate->depth.test_enabled = false;\n\tstate->blendingEnabled = false;\n\tstate->textureUnits[0].enabled = true;\n\tstate->textureUnits[0].mode = r_texunit_mode_replace;\n\tstate->textureUnits[1].enabled = true;\n\tstate->textureUnits[1].mode = r_texunit_mode_decal;\n\n\t// Used when rendering the polys directly (like r_fastsky) but texturing\n\tstate = R_CopyRenderingState(r_state_skydome_single_pass_program, r_state_skydome_single_pass, \"skyDomeSinglePass(program)\");\n\tstate->depth.test_enabled = true;\n\n\t// Deliberately not using r_state_skydome_single_pass_program just so we can tie it to vbo etc in future\n\tstate = R_CopyRenderingState(r_state_skydome_single_pass_bmodel, r_state_skydome_single_pass, \"skyDomeSinglePass(bmodel)\");\n\tstate->depth.test_enabled = true;\n}\n\nvoid GLC_StateBeginFastSky(qbool world)\n{\n\tR_TraceEnterFunctionRegion;\n\n\tR_ApplyRenderingState(world ? r_state_sky_fast : r_state_sky_fast_bmodel);\n\tR_CustomColor(r_refdef2.fog_skycolor[0], r_refdef2.fog_skycolor[1], r_refdef2.fog_skycolor[2], 1.0f);\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_StateBeginSkyZBufferPass(void)\n{\n\tR_TraceEnterFunctionRegion;\n\n\tif (r_refdef2.fog_render && r_refdef2.fog_sky > 0) {\n\t\tR_ApplyRenderingState(r_state_skydome_zbuffer_pass_fogged);\n\t\tR_CustomColor(r_refdef2.fog_color[0], r_refdef2.fog_color[1], r_refdef2.fog_color[2], 1);\n\t}\n\telse {\n\t\tR_ApplyRenderingState(r_state_skydome_zbuffer_pass);\n\t}\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_StateBeginSingleTextureSkyDome(void)\n{\n\tR_TraceEnterFunctionRegion;\n\n\tR_ApplyRenderingState(r_state_skydome_background_pass);\n\trenderer.TextureUnitBind(0, solidskytexture);\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_StateBeginSingleTextureSkyDomeCloudPass(void)\n{\n\tR_TraceEnterFunctionRegion;\n\n\tR_ApplyRenderingState(r_state_skydome_cloud_pass);\n\trenderer.TextureUnitBind(0, alphaskytexture);\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_StateBeginMultiTextureSkyDome(qbool use_program)\n{\n\tR_TraceEnterFunctionRegion;\n\n\tR_ApplyRenderingState(use_program ? r_state_skydome_single_pass_program : r_state_skydome_single_pass);\n\trenderer.TextureUnitBind(0, solidskytexture);\n\trenderer.TextureUnitBind(1, alphaskytexture);\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_StateBeginMultiTextureSkyChain(void)\n{\n\tR_TraceEnterFunctionRegion;\n\n\tR_ApplyRenderingState(r_state_skydome_single_pass_bmodel);\n\trenderer.TextureUnitBind(0, solidskytexture);\n\trenderer.TextureUnitBind(1, alphaskytexture);\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_StateBeginSingleTextureSkyPass(void)\n{\n\tR_TraceEnterFunctionRegion;\n\n\tR_ApplyRenderingState(r_state_skydome_background_pass_bmodel);\n\trenderer.TextureUnitBind(0, solidskytexture);\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_StateBeginSingleTextureCloudPass(void)\n{\n\tR_TraceEnterFunctionRegion;\n\n\tR_ApplyRenderingState(r_state_skydome_cloud_pass_bmodel);\n\trenderer.TextureUnitBind(0, alphaskytexture);\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid GLC_StateBeginBrightenScreen(void)\n{\n\tR_ApplyRenderingState(r_state_brighten_screen);\n}\n\nvoid GLC_StateBeginDrawAlphaPieSliceRGB(float thickness)\n{\n\t// Same as lineState\n\tR_ApplyRenderingState(r_state_line);\n\tif (thickness > 0.0) {\n\t\tR_CustomLineWidth(thickness);\n\t}\n}\n\nvoid GLC_StateBeginBloomDraw(texture_ref texture)\n{\n\tR_ApplyRenderingState(r_state_postprocess_bloom_draweffect);\n\tR_CustomColor(r_bloom_alpha.value, r_bloom_alpha.value, r_bloom_alpha.value, 1.0f);\n\trenderer.TextureUnitBind(0, texture);\n}\n\nvoid GLC_StateBeginImageDraw(qbool is_text)\n{\n\textern cvar_t gl_alphafont;\n\n\tif (is_text && !gl_alphafont.integer) {\n\t\tR_ApplyRenderingState(r_state_hud_images_alphatested_glc);\n\t}\n\telse {\n\t\tR_ApplyRenderingState(r_state_hud_images_glc);\n\t}\n}\n\nvoid GLC_StateBeginImageDrawNonGLSL(qbool is_text)\n{\n\textern cvar_t gl_alphafont;\n\n\tif (is_text && !gl_alphafont.integer) {\n\t\tR_ApplyRenderingState(r_state_hud_images_alphatested_glc_non_glsl);\n\t}\n\telse {\n\t\tR_ApplyRenderingState(r_state_hud_images_glc_non_glsl);\n\t}\n}\n\nvoid GLC_StateBeginAliasOutlineFrame(qbool weaponmodel)\n{\n\tR_ApplyRenderingState(weaponmodel ? r_state_weaponmodel_outline : r_state_aliasmodel_outline);\n\tR_GLC_DisableColorPointer();\n\tR_CustomColor(0, 0, 0, 1);\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_state.h",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_GLC_STATE_HEADER\n#define EZQUAKE_GLC_STATE_HEADER\n\n#include \"r_state.h\"\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\nvoid R_GLC_TextureUnitSet(rendering_state_t* state, int index, qbool enabled, r_texunit_mode_t mode);\nvoid GLC_InitialiseSkyStates(void);\nvoid GLC_CustomAlphaTesting(qbool enabled);\nvoid R_GLC_ConfigureAlphaTesting(rendering_state_t* state, qbool enabled, r_alphatest_func_t func, float value);\n#define R_GLC_EnableAlphaTesting(state) { state->alphaTesting.enabled = true; }\n#define R_GLC_DisableAlphaTesting(state) { state->alphaTesting.enabled = false; }\n\nvoid R_GLC_DisableColorPointer(void);\nvoid R_GLC_DisableTexturePointer(int unit);\n#else\n#define R_GLC_TextureUnitSet(...)\n#define R_GLC_ConfigureAlphaTesting(...)\n#define GLC_CustomAlphaTesting(...)\n#define R_GLC_EnableAlphaTesting(...)\n#define R_GLC_DisableAlphaTesting(...)\n#endif // RENDERER_OPTION_CLASSIC_OPENGL\n\n#endif // EZQUAKE_GLC_STATE_HEADER\n"
  },
  {
    "path": "src/glc_surf.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// glc_surf.c: classic surface-related refresh code\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"rulesets.h\"\n#include \"utils.h\"\n#include \"tr_types.h\"\n#include \"r_texture.h\"\n#include \"r_vao.h\"\n#include \"glc_local.h\"\n#include \"r_brushmodel.h\"\n#include \"r_renderer.h\"\n#include \"r_program.h\"\n#include \"r_lightmaps.h\"\n\n// This is a chain of polys, only used in classic when multi-texturing not available\nglpoly_t *fullbright_polys[MAX_GLTEXTURES];\nglpoly_t *luma_polys[MAX_GLTEXTURES];\n\nextern glpoly_t *caustics_polys;\nextern glpoly_t *detail_polys;\n\nvoid GLC_ClearLightmapPolys(void);\n\nvoid GLC_ClearTextureChains(void)\n{\n\tGLC_ClearLightmapPolys();\n\tmemset(fullbright_polys, 0, sizeof(fullbright_polys));\n\tmemset(luma_polys, 0, sizeof(luma_polys));\n}\n\nvoid GLC_DrawChainOutline(msurface_t* surf, qbool texture_chains)\n{\n\tmsurface_t* prev;\n\tmsurface_t* s = surf;\n\n\twhile (s) {\n\t\tint front = 0, back = s->polys->numverts - 1;\n\n\t\tGLC_Begin(GL_LINE_LOOP);\n\t\twhile (front < back) {\n\t\t\tGLC_Vertex3fv(s->polys->verts[front]);\n\t\t\tfront += 2;\n\t\t}\n\t\twhile (back > 0) {\n\t\t\tGLC_Vertex3fv(s->polys->verts[back]);\n\t\t\tback -= 2;\n\t\t}\n\t\tGLC_End();\n\n\t\tprev = s;\n\t\tif (texture_chains) {\n\t\t\ts = s->texturechain;\n\t\t\tprev->texturechain = NULL;\n\t\t}\n\t\telse {\n\t\t\ts = s->drawflatchain;\n\t\t\tprev->drawflatchain = NULL;\n\t\t}\n\t}\n}\n\nvoid GLC_DrawMapOutline(model_t *model)\n{\n\tint i;\n\tunsigned int lightmap_count;\n\n\tif (!(model->isworldmodel && r_refdef2.drawWorldOutlines)) {\n\t\treturn;\n\t}\n\n\tlightmap_count = R_LightmapCount();\n\tGLC_StateBeginDrawMapOutline();\n\tfor (i = 0; i < model->numtextures; i++) {\n\t\ttexture_t* t;\n\n\t\tif (!model->textures[i] || !model->textures[i]->texturechain) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tt = R_TextureAnimation(NULL, model->textures[i]);\n\t\tGLC_DrawChainOutline(t->texturechain, true);\n\t}\n\tGLC_DrawChainOutline(model->drawflat_chain, false);\n\tfor (i = 0; i < lightmap_count; ++i) {\n\t\tGLC_DrawChainOutline(R_DrawflatLightmapChain(i), false);\n\t\tR_ClearDrawflatLightmapChain(i);\n\t}\n}\n\nstatic void DrawGLPoly(glpoly_t *p)\n{\n\tint i;\n\tfloat *v;\n\n\tGLC_Begin(GL_TRIANGLE_STRIP);\n\tv = p->verts[0];\n\tfor (i = 0; i < p->numverts; i++, v += VERTEXSIZE) {\n\t\tglTexCoord2f(v[3], v[4]);\n\t\tGLC_Vertex3fv(v);\n\t}\n\tGLC_End();\n}\n\nunsigned int GLC_DrawIndexedPoly(glpoly_t* p, unsigned int* modelIndexes, unsigned int modelIndexMaximum, unsigned int index_count)\n{\n\tint k;\n\n\tif (GL_Supported(R_SUPPORT_PRIMITIVERESTART)) {\n\t\tif (index_count + 1 + p->numverts > modelIndexMaximum) {\n\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\tindex_count = 0;\n\t\t}\n\n\t\tif (index_count) {\n\t\t\tmodelIndexes[index_count++] = ~(GLuint)0;\n\t\t}\n\t}\n\telse {\n\t\tif (index_count + 3 + p->numverts > modelIndexMaximum) {\n\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\tindex_count = 0;\n\t\t}\n\n\t\tif (index_count) {\n\t\t\tint prev = index_count - 1;\n\t\t\tif (index_count % 2 == 1) {\n\t\t\t\tmodelIndexes[index_count++] = modelIndexes[prev];\n\t\t\t}\n\t\t\tmodelIndexes[index_count++] = modelIndexes[prev];\n\t\t\tmodelIndexes[index_count++] = p->vbo_start;\n\t\t}\n\t}\n\n\tfor (k = 0; k < p->numverts; ++k) {\n\t\tmodelIndexes[index_count++] = p->vbo_start + k;\n\t}\n\n\treturn index_count;\n}\n\nvoid GLC_RenderFullbrights_GLSL(void)\n{\n\tint i;\n\tglpoly_t *p;\n\ttexture_ref texture;\n\n\tGLC_StateBeginRenderFullbrights();\n\tR_ProgramUse(r_program_world_secondpass_glc);\n\n\tfor (i = 1; i < MAX_GLTEXTURES; i++) {\n\t\tint index_count = 0;\n\n\t\tif (!fullbright_polys[i]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttexture.index = i;\n\t\trenderer.TextureUnitBind(0, texture);\n\n\t\tfor (p = fullbright_polys[i]; p; p = p->fb_chain) {\n\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t}\n\n\t\tif (index_count) {\n\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\tindex_count = 0;\n\t\t}\n\t\tfullbright_polys[i] = NULL;\n\t}\n}\n\nvoid GLC_RenderFullbrights(void)\n{\n\tint i;\n\tglpoly_t *p;\n\ttexture_ref texture;\n\n\tGLC_StateBeginRenderFullbrights();\n\n\tfor (i = 1; i < MAX_GLTEXTURES; i++) {\n\t\tif (!fullbright_polys[i]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttexture.index = i;\n\t\trenderer.TextureUnitBind(0, texture);\n\t\tif (R_VAOBound()) {\n\t\t\tint index_count = 0;\n\n\t\t\tfor (p = fullbright_polys[i]; p; p = p->fb_chain) {\n\t\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t\t}\n\n\t\t\tif (index_count) {\n\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tfor (p = fullbright_polys[i]; p; p = p->fb_chain) {\n\t\t\t\tDrawGLPoly(p);\n\t\t\t}\n\t\t}\n\t\tfullbright_polys[i] = NULL;\n\t}\n}\n\nvoid GLC_RenderLumas_GLSL(void)\n{\n\tint i;\n\tglpoly_t *p;\n\ttexture_ref texture;\n\tint index_count = 0;\n\n\tGLC_StateBeginRenderLumas();\n\tR_ProgramUse(r_program_world_secondpass_glc);\n\n\tfor (i = 1; i < MAX_GLTEXTURES; i++) {\n\t\tif (!luma_polys[i]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttexture.index = i;\n\t\trenderer.TextureUnitBind(0, texture);\n\n\t\tfor (p = luma_polys[i]; p; p = p->luma_chain) {\n\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t}\n\n\t\tif (index_count) {\n\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t}\n\t\tluma_polys[i] = NULL;\n\t}\n}\n\nvoid GLC_RenderLumas(void)\n{\n\tint i;\n\tglpoly_t *p;\n\ttexture_ref texture;\n\tqbool use_vbo = buffers.supported && modelIndexes;\n\n\tGLC_StateBeginRenderLumas();\n\n\tfor (i = 1; i < MAX_GLTEXTURES; i++) {\n\t\tif (!luma_polys[i]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttexture.index = i;\n\t\trenderer.TextureUnitBind(0, texture);\n\t\tif (use_vbo) {\n\t\t\tint index_count = 0;\n\n\t\t\tfor (p = luma_polys[i]; p; p = p->luma_chain) {\n\t\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t\t}\n\n\t\t\tif (index_count) {\n\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tfor (p = luma_polys[i]; p; p = p->luma_chain) {\n\t\t\t\tDrawGLPoly(p);\n\t\t\t}\n\t\t}\n\t\tluma_polys[i] = NULL;\n\t}\n}\n\nvoid GLC_EmitDetailPolys_GLSL(void)\n{\n\tglpoly_t *p;\n\tGLuint index_count = 0;\n\n\tif (!detail_polys) {\n\t\treturn;\n\t}\n\n\tGLC_StateBeginEmitDetailPolys();\n\tR_ProgramUse(r_program_world_secondpass_glc);\n\n\tfor (p = detail_polys; p; p = p->detail_chain) {\n\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t}\n\n\tif (index_count) {\n\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t}\n\n\tR_ProgramUse(r_program_none);\n\tdetail_polys = NULL;\n}\n\nvoid GLC_EmitDetailPolys(qbool use_vbo)\n{\n\tglpoly_t *p;\n\tint i;\n\tfloat *v;\n\tGLuint index_count = 0;\n\n\tif (!detail_polys) {\n\t\treturn;\n\t}\n\n\tGLC_StateBeginEmitDetailPolys();\n\n\tfor (p = detail_polys; p; p = p->detail_chain) {\n\t\tif (use_vbo) {\n\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t}\n\t\telse {\n\t\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\t\tv = p->verts[0];\n\t\t\tfor (i = 0; i < p->numverts; i++, v += VERTEXSIZE) {\n\t\t\t\tglTexCoord2f(v[7], v[8]);\n\t\t\t\tGLC_Vertex3fv(v);\n\t\t\t}\n\t\t\tGLC_End();\n\t\t}\n\t}\n\n\tif (index_count) {\n\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t}\n\n\tdetail_polys = NULL;\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_turb_surface.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// glc_turb_surface.c: surface-related refresh code\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"glc_local.h\"\n#include \"rulesets.h\"\n#include \"utils.h\"\n#include \"r_brushmodel.h\"\n#include \"r_renderer.h\"\n#include \"tr_types.h\"\n#include \"r_program.h\"\n\nextern msurface_t* waterchain;\nvoid GLC_EmitWaterPoly(msurface_t* fa);\n\n#define TURBFLAGS_FLATCOLOR           (1 << 0)\n#define TURBFLAGS_FOG_LINEAR          (1 << 1)\n#define TURBFLAGS_FOG_EXP             (1 << 2)\n#define TURBFLAGS_FOG_EXP2            (1 << 3)\n#define TURBFLAGS_FOG_ENABLED         (TURBFLAGS_FOG_LINEAR | TURBFLAGS_FOG_EXP | TURBFLAGS_FOG_EXP2)\n\nqbool GLC_TurbSurfaceProgramCompile(void)\n{\n\textern cvar_t r_fastturb;\n\tint option = (r_fastturb.integer ? TURBFLAGS_FLATCOLOR : 0);\n\n\tif (R_ProgramRecompileNeeded(r_program_turb_glc, option)) {\n\t\tchar included_definitions[512];\n\n\t\tincluded_definitions[0] = '\\0';\n\n\t\tif (option & TURBFLAGS_FLATCOLOR) {\n\t\t\tstrlcat(included_definitions, \"#define FLAT_COLOR\\n\", sizeof(included_definitions));\n\t\t}\n\n\t\tR_ProgramCompileWithInclude(r_program_turb_glc, included_definitions);\n\t\tR_ProgramUniform1i(r_program_uniform_turb_glc_texSampler, 0);\n\t\tR_ProgramSetCustomOptions(r_program_turb_glc, option);\n\t}\n\n\tR_ProgramSetStandardUniforms(r_program_turb_glc);\n\n\treturn R_ProgramReady(r_program_turb_glc);\n}\n\nstatic void GLC_DrawWaterSurfaces_Program(void)\n{\n\tqbool use_vbo = buffers.supported && modelIndexes;\n\tmsurface_t* fa;\n\ttexture_ref prev_tex = null_texture_reference;\n\tint index_count = 0;\n\tfloat water_alpha = r_refdef2.wateralpha;\n\tqbool textured = !(R_ProgramCustomOptions(r_program_turb_glc) & TURBFLAGS_FLATCOLOR);\n\n\tR_ProgramUse(r_program_turb_glc);\n\tif (textured) {\n\t\tR_ProgramUniform1f(r_program_uniform_turb_glc_time, cl.time);\n\t\tR_ProgramUniform1f(r_program_uniform_turb_glc_alpha, water_alpha);\n\t}\n\n\tfor (fa = waterchain; fa; fa = fa->texturechain) {\n\t\tglpoly_t *p;\n\n\t\tif (!R_TextureReferenceEqual(fa->texinfo->texture->gl_texturenum, prev_tex)) {\n\t\t\tprev_tex = fa->texinfo->texture->gl_texturenum;\n\n\t\t\tif (index_count) {\n\t\t\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t\t\t\tindex_count = 0;\n\t\t\t}\n\t\t\tif (textured) {\n\t\t\t\trenderer.TextureUnitBind(0, prev_tex);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tbyte* base = SurfaceFlatTurbColor(fa->texinfo->texture);\n\t\t\t\tfloat color[4];\n\n\t\t\t\tcolor[0] = (base[0] / 255.0f) * water_alpha;\n\t\t\t\tcolor[1] = (base[1] / 255.0f) * water_alpha;\n\t\t\t\tcolor[2] = (base[2] / 255.0f) * water_alpha;\n\t\t\t\tcolor[3] = water_alpha;\n\n\t\t\t\tR_ProgramUniform4fv(r_program_uniform_turb_glc_color, color);\n\t\t\t}\n\t\t}\n\n\t\tif (use_vbo) {\n\t\t\tfor (p = fa->polys; p; p = p->next) {\n\t\t\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tGLC_EmitWaterPoly(fa);\n\t\t}\n\t}\n\n\tif (index_count) {\n\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t}\n\tR_ProgramUse(r_program_none);\n}\n\nstatic void GLC_DrawWaterSurfaces_Immediate(void)\n{\n\tmsurface_t *s;\n\n\tfor (s = waterchain; s; s = s->texturechain) {\n\t\trenderer.TextureUnitBind(0, s->texinfo->texture->gl_texturenum);\n\n\t\tGLC_EmitWaterPoly(s);\n\t}\n}\n\nvoid GLC_DrawWaterSurfaces(void)\n{\n\textern cvar_t gl_program_turbsurfaces;\n\n\tif (!waterchain) {\n\t\treturn;\n\t}\n\n\tR_TraceEnterRegion(__func__, true);\n\tGLC_StateBeginWaterSurfaces();\n\n\tif (gl_program_turbsurfaces.integer && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_TurbSurfaceProgramCompile()) {\n\t\tGLC_DrawWaterSurfaces_Program();\n\t}\n\telse {\n\t\tGLC_DrawWaterSurfaces_Immediate();\n\t}\n\n\tR_TraceLeaveRegion(true);\n\n\twaterchain = NULL;\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_vao.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"r_vao.h\"\n#include \"glc_state.h\"\n#include \"glc_vao.h\"\n#include \"r_buffers.h\"\n#include \"r_program.h\"\n\nGL_StaticProcedureDeclaration(glVertexAttribPointer, \"index=%u, size=%d, type=%u, normalized=%d, stride=%d, pointer=%p\", GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer)\nGL_StaticProcedureDeclaration(glDisableVertexAttribArray, \"index=%u\", GLuint index)\nGL_StaticProcedureDeclaration(glEnableVertexAttribArray, \"index=%u\", GLuint index)\n\nvoid R_GLC_TexturePointer(r_buffer_id buffer_id, int unit, qbool enabled, int size, GLenum type, int stride, void* pointer_or_offset);\nvoid R_GLC_ColorPointer(r_buffer_id buffer_id, qbool enabled, int size, GLenum type, int stride, void* pointer_or_offset);\nvoid R_GLC_VertexPointer(r_buffer_id buffer_id, qbool enabled, int size, GLenum type, int stride, void* pointer_or_offset);\nvoid R_GLC_NormalPointer(r_buffer_id buffer_id, qbool enabled, int size, GLenum type, int stride, void* pointer_or_offset);\n\n// GLC uses vertex array, all client state\ntypedef struct {\n\tqbool enabled;\n\n\tint size;\n\tunsigned int type;\n\tint stride;\n\tvoid* pointer_or_offset;\n} glc_va_element;\n\ntypedef struct glc_va_attribute_s {\n\tqbool enabled;\n\n\tr_program_attribute_id attr_id;\n\tGLint size;\n\tGLenum type;\n\tGLboolean normalized;\n\tGLsizei stride;\n\tconst GLvoid* pointer;\n} glc_va_attribute_t;\n\ntypedef struct glc_vao_s {\n\tqbool initialised;\n\tr_buffer_id vertex_buffer;\n\tr_buffer_id element_index_buffer;\n\n\tglc_va_element vertex_array;\n\tglc_va_element normal_array;\n\tglc_va_element color_array;\n\tglc_va_element texture_array[MAX_GLC_TEXTURE_UNIT_STATES];\n\tglc_va_attribute_t attributes[MAX_GLC_ATTRIBUTES];\n} glc_vao_t;\n\nstatic glc_vao_t vaos[vao_count];\n\nqbool GLC_InitialiseVAOHandling(void)\n{\n\tqbool vaos_supported = true;\n\n\tmemset(&vaos, 0, sizeof(vaos));\n\n\tif (COM_CheckParm(cmdline_param_client_novao)) {\n\t\tGL_InvalidateFunction(glEnableVertexAttribArray);\n\t\tGL_InvalidateFunction(glDisableVertexAttribArray);\n\t\tGL_InvalidateFunction(glVertexAttribPointer);\n\t\treturn false;\n\t}\n\n\t// OpenGL 2.0\n\tGL_LoadMandatoryFunctionExtension(glEnableVertexAttribArray, vaos_supported);\n\tGL_LoadMandatoryFunctionExtension(glDisableVertexAttribArray, vaos_supported);\n\tGL_LoadMandatoryFunctionExtension(glVertexAttribPointer, vaos_supported);\n\n\tR_TraceAPI(\"VAOs supported: %s\", vaos_supported ? \"yes\" : \"no (!)\");\n\n\treturn vaos_supported;\n}\n\nstatic glc_attribute_t attributes[MAX_GLC_ATTRIBUTES];\n\nvoid GLC_BindVertexArrayAttributes(r_vao_id vao)\n{\n\tint i;\n\tfor (i = 0; i < MAX_GLC_ATTRIBUTES; ++i) {\n\t\tglc_va_attribute_t* attr = &vaos[vao].attributes[i];\n\n\t\tif (attr->enabled && R_ProgramInUse() == R_ProgramForAttribute(attr->attr_id)) {\n\t\t\tGLint location = R_ProgramAttributeLocation(attr->attr_id);\n\n\t\t\tif (location >= 0) {\n\t\t\t\tGL_Procedure(glEnableVertexAttribArray, location);\n\t\t\t\tGL_Procedure(glVertexAttribPointer, location, attr->size, attr->type, attr->normalized ? GL_TRUE : GL_FALSE, attr->stride, attr->pointer);\n\t\t\t\tattributes[i].location = location;\n\t\t\t\tattributes[i].enabled = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tGL_Procedure(glDisableVertexAttribArray, attributes[i].location);\n\t\t\t\tattributes[i].enabled = false;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tGL_Procedure(glDisableVertexAttribArray, attributes[i].location);\n\t\t\tattributes[i].enabled = false;\n\t\t}\n\t}\n}\n\nvoid GLC_BindVertexArray(r_vao_id vao)\n{\n\tglc_va_element* vertexes = &vaos[vao].vertex_array;\n\tglc_va_element* colors = &vaos[vao].color_array;\n\tglc_va_element* normals = &vaos[vao].normal_array;\n\tr_buffer_id buf = vaos[vao].vertex_buffer;\n\tint i;\n\n\t// Unbind any active attributes\n\tfor (i = 0; i < MAX_GLC_ATTRIBUTES; ++i) {\n\t\tif (attributes[i].enabled) {\n\t\t\tGL_Procedure(glDisableVertexAttribArray, attributes[i].location);\n\t\t\tattributes[i].enabled = false;\n\t\t}\n\t}\n\n\tR_GLC_VertexPointer(buf, vertexes->enabled, vertexes->size, vertexes->type, vertexes->stride, vertexes->pointer_or_offset);\n\tR_GLC_ColorPointer(buf, colors->enabled, colors->size, colors->type, colors->stride, colors->pointer_or_offset);\n\tR_GLC_NormalPointer(buf, normals->enabled, normals->size, normals->type, normals->stride, normals->pointer_or_offset);\n\tfor (i = 0; i < sizeof(vaos[vao].texture_array) / sizeof(vaos[vao].texture_array[0]); ++i) {\n\t\tglc_va_element* textures = &vaos[vao].texture_array[i];\n\n\t\tif (i < gl_textureunits) {\n\t\t\tR_GLC_TexturePointer(buf, i, textures->enabled, textures->size, textures->type, textures->stride, textures->pointer_or_offset);\n\t\t}\n\t}\n\n\tfor (i = 0; i < MAX_GLC_ATTRIBUTES; ++i) {\n\t\tglc_va_attribute_t* attr = &vaos[vao].attributes[i];\n\n\t\tif (attr->enabled && R_ProgramInUse() == R_ProgramForAttribute(attr->attr_id)) {\n\t\t\tGLint location = R_ProgramAttributeLocation(attr->attr_id);\n\n\t\t\tif (location >= 0) {\n\t\t\t\tGL_Procedure(glEnableVertexAttribArray, location);\n\t\t\t\tGL_Procedure(glVertexAttribPointer, location, attr->size, attr->type, attr->normalized ? GL_TRUE : GL_FALSE, attr->stride, attr->pointer);\n\t\t\t\tattributes[i].location = location;\n\t\t\t\tattributes[i].enabled = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (R_BufferReferenceIsValid(vaos[vao].element_index_buffer)) {\n\t\tbuffers.Bind(vaos[vao].element_index_buffer);\n\t}\n\telse {\n\t\tbuffers.UnBind(buffertype_index);\n\t}\n}\n\nqbool GLC_VertexArrayCreated(r_vao_id vao)\n{\n\treturn vaos[vao].initialised;\n}\n\nvoid GLC_GenVertexArray(r_vao_id vao, const char* name)\n{\n\tmemset(&vaos[vao], 0, sizeof(vaos[vao]));\n\tvaos[vao].initialised = true;\n}\n\nvoid GLC_DeleteVAOs(void)\n{\n\tmemset(vaos, 0, sizeof(vaos));\n}\n\nstatic void GLC_VAOEnableComponent(glc_va_element* el, int size, GLenum type, GLsizei stride, GLvoid* pointer)\n{\n\tel->enabled = true;\n\tel->size = size;\n\tel->stride = stride;\n\tel->type = type;\n\tel->pointer_or_offset = pointer;\n}\n\nstatic void GLC_VAODisableComponent(glc_va_element* el)\n{\n\tel->enabled = false;\n}\n\nvoid GLC_VAOEnableVertexPointer(r_vao_id vao, int size, GLenum type, GLsizei stride, GLvoid* pointer)\n{\n\tGLC_VAOEnableComponent(&vaos[vao].vertex_array, size, type, stride, pointer);\n}\n\nvoid GLC_VAODisableVertexPointer(r_vao_id vao)\n{\n\tGLC_VAODisableComponent(&vaos[vao].vertex_array);\n}\n\nvoid GLC_VAOEnableNormalPointer(r_vao_id vao, int size, GLenum type, GLsizei stride, GLvoid* pointer)\n{\n\tGLC_VAOEnableComponent(&vaos[vao].normal_array, size, type, stride, pointer);\n}\n\nvoid GLC_VAODisableNormalPointer(r_vao_id vao)\n{\n\tGLC_VAODisableComponent(&vaos[vao].normal_array);\n}\n\nvoid GLC_VAOEnableColorPointer(r_vao_id vao, int size, GLenum type, GLsizei stride, GLvoid* pointer)\n{\n\tGLC_VAOEnableComponent(&vaos[vao].color_array, size, type, stride, pointer);\n}\n\nvoid GLC_VAODisableColorPointer(r_vao_id vao)\n{\n\tGLC_VAODisableComponent(&vaos[vao].color_array);\n}\n\nvoid GLC_VAOEnableTextureCoordPointer(r_vao_id vao, int index, int size, GLenum type, GLsizei stride, GLvoid* pointer)\n{\n\tGLC_VAOEnableComponent(&vaos[vao].texture_array[index], size, type, stride, pointer);\n}\n\nvoid GLC_VAODisableTextureCoordPointer(r_vao_id vao, int index)\n{\n\tGLC_VAODisableComponent(&vaos[vao].texture_array[index]);\n}\n\nvoid GLC_VAOEnableCustomAttribute(r_vao_id vao, int index, r_program_attribute_id attr_id, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer)\n{\n\tglc_va_attribute_t* attr = &vaos[vao].attributes[index];\n\n\tmemset(attr, 0, sizeof(*attr));\n\tattr->enabled = true;\n\tattr->attr_id = attr_id;\n\tattr->size = size;\n\tattr->type = type;\n\tattr->normalized = normalized;\n\tattr->stride = stride;\n\tattr->pointer = pointer;\n}\n\nvoid GLC_VAODisableCustomAttribute(r_vao_id vao, int index)\n{\n\tglc_va_attribute_t* attr = &vaos[vao].attributes[index];\n\n\tattr->enabled = false;\n}\n\nvoid GLC_VAOSetVertexBuffer(r_vao_id vao, r_buffer_id ref)\n{\n\tvaos[vao].vertex_buffer = ref;\n}\n\nvoid GLC_VAOSetIndexBuffer(r_vao_id vao, r_buffer_id ref)\n{\n\tvaos[vao].element_index_buffer = ref;\n}\n\n#ifdef WITH_RENDERING_TRACE\nvoid GLC_PrintVAOState(FILE* output, int indent, r_vao_id vao)\n{\n\tint i;\n\n\tindent += 2;\n\tif (vaos[vao].vertex_array.enabled) {\n\t\tglc_va_element* el = &vaos[vao].vertex_array;\n\t\tfprintf(output, \"%.*s   vertex-array: enabled(type %u, size %d, stride %d, pointer %p)\\n\", indent, \"                                                          \", el->type, el->size, el->stride, el->pointer_or_offset);\n\t}\n\tif (vaos[vao].color_array.enabled) {\n\t\tglc_va_element* el = &vaos[vao].color_array;\n\t\tfprintf(output, \"%.*s   color_array: enabled(type %u, size %d, stride %d, pointer %p)\\n\", indent, \"                                                          \", el->type, el->size, el->stride, el->pointer_or_offset);\n\t}\n\tif (vaos[vao].normal_array.enabled) {\n\t\tglc_va_element* el = &vaos[vao].normal_array;\n\t\tfprintf(output, \"%.*s   normal_array: enabled(type %u, size %d, stride %d, pointer %p)\\n\", indent, \"                                                          \", el->type, el->size, el->stride, el->pointer_or_offset);\n\t}\n\tfor (i = 0; i < sizeof(vaos[vao].texture_array) / sizeof(vaos[vao].texture_array[0]); ++i) {\n\t\tglc_va_element* el = &vaos[vao].texture_array[i];\n\t\tif (el->enabled) {\n\t\t\tfprintf(output, \"%.*s   texture-array[%d]: enabled(type %u, size %d, stride %d, pointer %p)\\n\", indent, \"                                                          \", i, el->type, el->size, el->stride, el->pointer_or_offset);\n\t\t}\n\t}\n}\n#endif\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glc_vao.h",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_GLC_VAO_HEADER\n#define EZQUAKE_GLC_VAO_HEADER\n\n#include \"gl_local.h\"\n#include \"r_vao.h\"\n#include \"r_program.h\"\n\nvoid GLC_VAOEnableVertexPointer(r_vao_id vao, int size, GLenum type, GLsizei stride, GLvoid* pointer);\nvoid GLC_VAODisableVertexPointer(r_vao_id vao);\nvoid GLC_VAOEnableColorPointer(r_vao_id vao, int size, GLenum type, GLsizei stride, GLvoid* pointer);\nvoid GLC_VAODisableColorPointer(r_vao_id vao);\nvoid GLC_VAOEnableNormalPointer(r_vao_id vao, int size, GLenum type, GLsizei stride, GLvoid* pointer);\nvoid GLC_VAODisableNormalPointer(r_vao_id vao);\nvoid GLC_VAOEnableTextureCoordPointer(r_vao_id vao, int index, int size, GLenum type, GLsizei stride, GLvoid* pointer);\nvoid GLC_VAODisableTextureCoordPointer(r_vao_id vao, int index);\nvoid GLC_VAOSetIndexBuffer(r_vao_id vao, r_buffer_id ref);\nvoid GLC_VAOSetVertexBuffer(r_vao_id vao, r_buffer_id ref);\nvoid GLC_VAOEnableCustomAttribute(r_vao_id vao, int index, r_program_attribute_id attr_id, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer);\nvoid GLC_VAODisableCustomAttribute(r_vao_id vao, int index);\n\nvoid GLC_DeleteVAOs(void);\nvoid GLC_GenVertexArray(r_vao_id vao, const char* name);\nqbool GLC_VertexArrayCreated(r_vao_id vao);\nvoid GLC_BindVertexArray(r_vao_id vao);\nvoid GLC_BindVertexArrayAttributes(r_vao_id vao);\nqbool GLC_InitialiseVAOHandling(void);\nvoid GLC_EnsureVAOCreated(r_vao_id vao);\n\n#ifdef WITH_RENDERING_TRACE\nvoid GLC_PrintVAOState(FILE* output, int indent, r_vao_id vao);\n#endif\n\n#endif // EZQUAKE_GLC_VAO_HEADER\n"
  },
  {
    "path": "src/glc_warp.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\n#include \"quakedef.h\"\n#include \"vx_stuff.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"glsl/constants.glsl\"\n#include \"r_texture.h\"\n#include \"glc_local.h\"\n#include \"r_renderer.h\"\n#include \"r_brushmodel.h\"\n#include \"r_program.h\"\n#include \"tr_types.h\"\n\n#define TURBSINSIZE 128\n#define TURBSCALE ((float) TURBSINSIZE / (2 * M_PI))\n\nstatic byte turbsin[TURBSINSIZE] = {\n\t127, 133, 139, 146, 152, 158, 164, 170, 176, 182, 187, 193, 198, 203, 208, 213, \n\t217, 221, 226, 229, 233, 236, 239, 242, 245, 247, 249, 251, 252, 253, 254, 254, \n\t255, 254, 254, 253, 252, 251, 249, 247, 245, 242, 239, 236, 233, 229, 226, 221, \n\t217, 213, 208, 203, 198, 193, 187, 182, 176, 170, 164, 158, 152, 146, 139, 133, \n\t127, 121, 115, 108, 102, 96, 90, 84, 78, 72, 67, 61, 56, 51, 46, 41, \n\t37, 33, 28, 25, 21, 18, 15, 12, 9, 7, 5, 3, 2, 1, 0, 0, \n\t0, 0, 0, 1, 2, 3, 5, 7, 9, 12, 15, 18, 21, 25, 28, 33, \n\t37, 41, 46, 51, 56, 61, 67, 72, 78, 84, 90, 96, 102, 108, 115, 121, \n};\n\n__inline static float SINTABLE_APPROX(float time) {\n\tfloat sinlerpf, lerptime, lerp;\n\tint sinlerp1, sinlerp2;\n\n\tsinlerpf = time * TURBSCALE;\n\tsinlerp1 = floor(sinlerpf);\n\tsinlerp2 = sinlerp1 + 1;\n\tlerptime = sinlerpf - sinlerp1;\n\n\tlerp =\tturbsin[sinlerp1 & (TURBSINSIZE - 1)] * (1 - lerptime) +\n\t\tturbsin[sinlerp2 & (TURBSINSIZE - 1)] * lerptime;\n\treturn -8 + 16 * lerp / 255.0;\n}\n\nvoid GLC_EmitWaterPoly(msurface_t* fa)\n{\n\textern cvar_t r_fastturb;\n\tglpoly_t *p;\n\tfloat* v;\n\tint i;\n\n\tif (r_fastturb.integer) {\n\t\tbyte* col = SurfaceFlatTurbColor(fa->texinfo->texture);\n\t\tbyte color[4] = { col[0], col[1], col[2], 255 };\n\n\t\tGLC_StateBeginFastTurbPoly(color);\n\t\tfor (p = fa->polys; p; p = p->next) {\n\t\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\t\tfor (i = 0, v = p->verts[0]; i < p->numverts; i++, v += VERTEXSIZE) {\n\t\t\t\tGLC_Vertex3fv(v);\n\t\t\t}\n\t\t\tGLC_End();\n\t\t}\n\t}\n\telse {\n\t\trenderer.TextureUnitBind(0, fa->texinfo->texture->gl_texturenum);\n\t\tfor (p = fa->subdivided; p; p = p->next) {\n\t\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\t\tfor (i = 0, v = p->verts[0]; i < p->numverts; i++, v += VERTEXSIZE) {\n\t\t\t\tfloat os = v[3];\n\t\t\t\tfloat ot = v[4];\n\t\t\t\t// meag: v[5] = 8 * sin(2t), v[6] = 8 * sin(2s)\n\t\t\t\t//       v[7] = 8 * cos(2t), v[8] = 8 * cos(2s)\n\t\t\t\t// was: SINTABLE_APPROX(ot * 2 + r_refdef2.time)) / 64.0f, SINTABLE_APPROX(os * 2 + r_refdef2.time)) / 64.0f\n\t\t\t\tfloat s = os + v[5] * r_refdef2.cos_time + v[7] * r_refdef2.sin_time;\n\t\t\t\tfloat t = ot + v[6] * r_refdef2.cos_time + v[8] * r_refdef2.sin_time;\n\n\t\t\t\tglTexCoord2f(s, t);\n\t\t\t\tGLC_Vertex3fv(v);\n\t\t\t}\n\t\t\tGLC_End();\n\t\t}\n\t}\n}\n\n// Caustics\nqbool GLC_CausticsProgramCompile(void)\n{\n\tif (R_ProgramRecompileNeeded(r_program_caustics_glc, 0)) {\n\t\tR_ProgramCompile(r_program_caustics_glc);\n\t\tR_ProgramUniform1i(r_program_uniform_caustics_glc_texSampler, 0);\n\t\tR_ProgramSetCustomOptions(r_program_caustics_glc, 0);\n\t}\n\n\treturn R_ProgramReady(r_program_caustics_glc);\n}\n\nstatic void GLC_CalcCausticTexCoords(float *v, float *s, float *t)\n{\n\tfloat os, ot;\n\n\tos = v[3];\n\tot = v[4];\n\n\t*s = os + SINTABLE_APPROX(0.465 * (r_refdef2.time + ot));\n\t*s *= -3 * (0.5 / 64);\n\n\t*t = ot + SINTABLE_APPROX(0.465 * (r_refdef2.time + os));\n\t*t *= -3 * (0.5 / 64);\n}\n\nstatic void GLC_EmitCausticPolys_Program(glpoly_t* caustics_polys)\n{\n\tunsigned int index_count = 0;\n\tglpoly_t *p;\n\n\tR_ProgramUse(r_program_caustics_glc);\n\tR_ProgramUniform1f(r_program_uniform_caustics_glc_time, cl.time);\n\n\tfor (p = caustics_polys; p; p = p->caustics_chain) {\n\t\tindex_count = GLC_DrawIndexedPoly(p, modelIndexes, modelIndexMaximum, index_count);\n\t}\n\n\tif (index_count) {\n\t\tGL_DrawElements(GL_TRIANGLE_STRIP, index_count, GL_UNSIGNED_INT, modelIndexes);\n\t}\n\tR_ProgramUse(r_program_none);\n}\n\nstatic void GLC_EmitCausticPolys_Immediate(glpoly_t* caustics_polys)\n{\n\tglpoly_t *p;\n\tfloat s, t;\n\tint i;\n\n\tfor (p = caustics_polys; p; p = p->caustics_chain) {\n\t\tGLC_Begin(GL_TRIANGLE_STRIP);\n\t\tfor (i = 0; i < p->numverts; ++i) {\n\t\t\tGLC_CalcCausticTexCoords(p->verts[i], &s, &t);\n\t\t\tglTexCoord2f(s, t);\n\t\t\tGLC_Vertex3fv(p->verts[i]);\n\t\t}\n\t\tGLC_End();\n\t}\n}\n\nvoid GLC_EmitCausticsPolys(void)\n{\n\textern glpoly_t *caustics_polys;\n\textern cvar_t gl_program_turbsurfaces;\n\tqbool use_vbo = buffers.supported && modelIndexes;\n\n\tif (!caustics_polys) {\n\t\treturn;\n\t}\n\n\tR_TraceEnterRegion(__func__, true);\n\tGLC_StateBeginCausticsPolys();\n\n\trenderer.TextureUnitBind(0, underwatertexture);\n\n\tif (use_vbo && gl_program_turbsurfaces.integer && GL_Supported(R_SUPPORT_RENDERING_SHADERS) && GLC_CausticsProgramCompile()) {\n\t\tGLC_EmitCausticPolys_Program(caustics_polys);\n\t}\n\telse {\n\t\tGLC_EmitCausticPolys_Immediate(caustics_polys);\n\t}\n\n\tcaustics_polys = NULL;\n\tR_TraceLeaveRegion(true);\n}\n\n#endif // #ifdef RENDERER_OPTION_CLASSIC_OPENGL\n"
  },
  {
    "path": "src/glm_aliasmodel.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// Alias model rendering (modern)\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"r_aliasmodel.h\"\n#include \"tr_types.h\"\n#include \"glsl/constants.glsl\"\n#include \"rulesets.h\"\n#include \"r_matrix.h\"\n#include \"glm_vao.h\"\n#include \"r_texture.h\"\n#include \"r_brushmodel.h\" // R_PointIsUnderwater only\n#include \"r_buffers.h\"\n#include \"r_program.h\"\n#include \"r_renderer.h\"\n\nvoid GLM_StateBeginAliasModelZPassBatch(void);\nvoid GLM_StateBeginAliasModelBatch(qbool translucent, qbool additive);\nvoid GLM_StateBeginAliasOutlineBatch(void);\n\n// MAX_STANDARD_ENTITIES used to be 512, so lets pretend like\n// there can't be more aliasmodel entities, as that will cause\n// the arrays sized according to this limit to become too big\n// for caches etc. Should there be more aliasmodels than this,\n// there will be an error printed.\n#define MAXIMUM_ALIASMODEL_DRAWCALLS 512   // ridiculous\n#define MAXIMUM_MATERIAL_SAMPLERS 32\n\ntypedef enum aliasmodel_draw_type_s {\n\taliasmodel_draw_std,\n\taliasmodel_draw_alpha,\n\taliasmodel_draw_outlines,\n\taliasmodel_draw_outlines_spec,\n\taliasmodel_draw_shells,\n\taliasmodel_draw_postscene,\n\taliasmodel_draw_postscene_additive,\n\taliasmodel_draw_postscene_shells,\n\n\taliasmodel_draw_max\n} aliasmodel_draw_type_t;\n\ntypedef struct DrawArraysIndirectCommand_s {\n\tGLuint count;\n\tGLuint instanceCount;\n\tGLuint first;\n\tGLuint baseInstance;\n} DrawArraysIndirectCommand_t;\n\n// Internal structure, filled in during initial pass over entities\ntypedef struct aliasmodel_draw_data_s {\n\t// Need to split standard & alpha model draws to cope with # of texture units available\n\t//   Outlines & shells are easier as they have 0 & 1 textures respectively\n\tDrawArraysIndirectCommand_t indirect_buffer[MAXIMUM_ALIASMODEL_DRAWCALLS];\n\ttexture_ref bound_textures[MAXIMUM_ALIASMODEL_DRAWCALLS][MAXIMUM_MATERIAL_SAMPLERS];\n\tint num_textures[MAXIMUM_ALIASMODEL_DRAWCALLS];\n\tint num_cmds[MAXIMUM_ALIASMODEL_DRAWCALLS];\n\tint num_calls;\n\tint current_call;\n\n\tunsigned int indirect_buffer_offset;\n} aliasmodel_draw_instructions_t;\n\nstatic aliasmodel_draw_instructions_t alias_draw_instructions[aliasmodel_draw_max];\nstatic int alias_draw_count;\n\ntypedef struct uniform_block_aliasmodel_s {\n\tfloat modelViewMatrix[16];\n\t// offset: 16 * float\n\tfloat color[4];\n\tfloat plrtopcolor[4];\n\tfloat plrbotcolor[4];\n\tint amFlags;\n\tfloat yaw_angle_rad;\n\tfloat shadelight;\n\tfloat ambientlight;\n\tint materialSamplerMapping;\n\tfloat lerpFraction;\n\tfloat minLumaMix;\n\tfloat outline_normal_scale;\n} uniform_block_aliasmodel_t;\n\ntypedef struct block_aliasmodels_s {\n\tuniform_block_aliasmodel_t models[MAX_STANDARD_ENTITIES];\n} uniform_block_aliasmodels_t;\n\nextern float r_framelerp;\n\n#define DRAW_DETAIL_TEXTURES      (1 << 0)\n#define DRAW_CAUSTIC_TEXTURES     (1 << 1)\n#define DRAW_REVERSED_DEPTH       (1 << 2)\n#define DRAW_LERP_MUZZLEHACK      (1 << 3)\n#define DRAW_FLAT_SHADING         (1 << 4)\n#define DRAW_INSTANCED            (1 << 5)\n\nstatic uniform_block_aliasmodels_t aliasdata;\n\nstatic int cached_mode;\n\n#define GET_COLOR_VALUES(colr) (float[]){(float)gl_outline_color_##colr.color[0] / 255.0f,(float)gl_outline_color_##colr.color[1] / 255.0f,(float)gl_outline_color_##colr.color[2] / 255.0f}\n\nstatic void R_SetAliasModelUniforms(int mode)\n{\n\textern cvar_t gl_outline_use_player_color, gl_outline_color_model, gl_outline_color_team, gl_outline_color_enemy;\n\tfloat scale = RuleSets_ModelOutlineScale();\n\tfloat *color_model, *color_enemy, *color_team;\n\tint use_player_color = gl_outline_use_player_color.integer;\n\tcolor_model = GET_COLOR_VALUES(model);\n\tcolor_enemy = gl_outline_color_enemy.string[0] ? GET_COLOR_VALUES(enemy) : color_model;\n\tcolor_team  = gl_outline_color_team .string[0] ? GET_COLOR_VALUES(team)  : color_model;\n\n\tif (cached_mode != mode) {\n\t\tR_ProgramUniform1i(r_program_uniform_aliasmodel_drawmode, mode);\n\t\tcached_mode = mode;\n\t}\n\n\tR_ProgramUniform1f(r_program_uniform_aliasmodel_outline_scale, scale);\n\tR_ProgramUniform1i(r_program_uniform_aliasmodel_outline_use_player_color, use_player_color);\n\tR_ProgramUniform3fv(r_program_uniform_aliasmodel_outline_color_model, color_model);\n\tR_ProgramUniform3fv(r_program_uniform_aliasmodel_outline_color_enemy, color_enemy);\n\tR_ProgramUniform3fv(r_program_uniform_aliasmodel_outline_color_team, color_team);\n}\n\n#undef GET_COLOR_VALUES\n\nstatic int material_samplers_max;\nstatic int TEXTURE_UNIT_MATERIAL;\nstatic int TEXTURE_UNIT_CAUSTICS;\n\nqbool GLM_CompileAliasModelProgram(void)\n{\n\textern cvar_t r_lerpmuzzlehack, gl_smoothmodels;\n\n\tunsigned int drawAlias_desiredOptions =\n\t\t(r_refdef2.drawCaustics ? DRAW_CAUSTIC_TEXTURES : 0) |\n\t\t(glConfig.reversed_depth ? DRAW_REVERSED_DEPTH : 0) |\n\t\t(r_lerpmuzzlehack.integer ? DRAW_LERP_MUZZLEHACK : 0) |\n\t\t(gl_smoothmodels.integer ? 0 : DRAW_FLAT_SHADING) |\n\t\t(GL_Supported(R_SUPPORT_INSTANCED_RENDERING) ? DRAW_INSTANCED : 0);\n\n\tif (R_ProgramRecompileNeeded(r_program_aliasmodel, drawAlias_desiredOptions)) {\n\t\tstatic char included_definitions[1024];\n\n\t\tincluded_definitions[0] = '\\0';\n\t\tif (drawAlias_desiredOptions & DRAW_CAUSTIC_TEXTURES) {\n\t\t\tmaterial_samplers_max = glConfig.texture_units - 1;\n\t\t\tTEXTURE_UNIT_CAUSTICS = 0;\n\t\t\tTEXTURE_UNIT_MATERIAL = 1;\n\n\t\t\tstrlcat(included_definitions, \"#define DRAW_CAUSTIC_TEXTURES\\n\", sizeof(included_definitions));\n\t\t\tstrlcat(included_definitions, \"#define SAMPLER_CAUSTIC_TEXTURE 0\\n\", sizeof(included_definitions));\n\t\t\tstrlcat(included_definitions, \"#define SAMPLER_MATERIAL_TEXTURE_START 1\\n\", sizeof(included_definitions));\n\t\t}\n\t\telse {\n\t\t\tmaterial_samplers_max = glConfig.texture_units;\n\t\t\tTEXTURE_UNIT_MATERIAL = 0;\n\n\t\t\tstrlcat(included_definitions, \"#define SAMPLER_MATERIAL_TEXTURE_START 0\\n\", sizeof(included_definitions));\n\t\t}\n\n\t\tstrlcat(included_definitions, va(\"#define SAMPLER_COUNT %d\\n\", material_samplers_max), sizeof(included_definitions));\n\t\tif (drawAlias_desiredOptions & DRAW_REVERSED_DEPTH) {\n\t\t\tstrlcat(included_definitions, \"#define EZQ_REVERSED_DEPTH\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (drawAlias_desiredOptions & DRAW_LERP_MUZZLEHACK) {\n\t\t\tstrlcat(included_definitions, \"#define EZQ_ALIASMODEL_MUZZLEHACK\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (drawAlias_desiredOptions & DRAW_FLAT_SHADING) {\n\t\t\tstrlcat(included_definitions, \"#define EZQ_ALIASMODEL_FLATSHADING\\n\", sizeof(included_definitions));\n\t\t}\n\n\t\t// Initialise program for drawing image\n\t\tR_ProgramCompileWithInclude(r_program_aliasmodel, included_definitions);\n\n\t\t// Set sampler uniforms for GL < 4.2 (no layout(binding=N) support)\n\t\tif (!GL_VersionAtLeast(4, 2) && R_ProgramReady(r_program_aliasmodel)) {\n\t\t\tR_ProgramUse(r_program_aliasmodel);\n\t\t\tif (drawAlias_desiredOptions & DRAW_CAUSTIC_TEXTURES) {\n\t\t\t\tR_ProgramUniform1i(r_program_uniform_aliasmodel_causticstex, TEXTURE_UNIT_CAUSTICS);\n\t\t\t}\n\t\t\tR_ProgramUniform1iArrayBase(r_program_uniform_aliasmodel_samplers, material_samplers_max, TEXTURE_UNIT_MATERIAL);\n\t\t}\n\n\t\tR_ProgramSetCustomOptions(r_program_aliasmodel, drawAlias_desiredOptions);\n\t}\n\tcached_mode = R_ProgramUniformGet1i(r_program_uniform_aliasmodel_drawmode, 0);\n\n\tif (!R_BufferReferenceIsValid(r_buffer_aliasmodel_drawcall_indirect)) {\n\t\tbuffers.Create(r_buffer_aliasmodel_drawcall_indirect, buffertype_indirect, \"aliasmodel-indirect-draw\", sizeof(alias_draw_instructions[0].indirect_buffer) * aliasmodel_draw_max, NULL, bufferusage_once_per_frame);\n\t}\n\n\tif (!R_BufferReferenceIsValid(r_buffer_aliasmodel_model_data)) {\n\t\tbuffers.Create(r_buffer_aliasmodel_model_data, buffertype_storage, \"alias-data\", sizeof(aliasdata), NULL, bufferusage_once_per_frame);\n\t}\n\n\treturn R_ProgramReady(r_program_aliasmodel);\n}\n\nvoid GLM_CreateAliasModelVAO(void)\n{\n\tR_GenVertexArray(vao_aliasmodel);\n\n\tGLM_ConfigureVertexAttribPointer(vao_aliasmodel, r_buffer_aliasmodel_vertex_data, 0, 3, GL_FLOAT, GL_FALSE, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, position), 0);\n\tGLM_ConfigureVertexAttribPointer(vao_aliasmodel, r_buffer_aliasmodel_vertex_data, 1, 2, GL_FLOAT, GL_FALSE, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, texture_coords), 0);\n\tGLM_ConfigureVertexAttribPointer(vao_aliasmodel, r_buffer_aliasmodel_vertex_data, 2, 3, GL_FLOAT, GL_FALSE, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, normal), 0);\n\tGLM_ConfigureVertexAttribIPointer(vao_aliasmodel, r_buffer_instance_number, 3, 1, GL_UNSIGNED_INT, sizeof(GLuint), 0, 1);\n\tGLM_ConfigureVertexAttribPointer(vao_aliasmodel, r_buffer_aliasmodel_vertex_data, 4, 3, GL_FLOAT, GL_FALSE, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, direction), 0);\n\tGLM_ConfigureVertexAttribIPointer(vao_aliasmodel, r_buffer_aliasmodel_vertex_data, 5, 1, GL_UNSIGNED_INT, sizeof(vbo_model_vert_t), VBO_FIELDOFFSET(vbo_model_vert_t, flags), 0);\n\n\tR_BindVertexArray(vao_none);\n}\n\nstatic int AssignSampler(aliasmodel_draw_instructions_t* instr, texture_ref texture)\n{\n\tint i;\n\ttexture_ref* allocated_samplers;\n\n\tif (!R_TextureReferenceIsValid(texture)) {\n\t\treturn 0;\n\t}\n\tif (instr->current_call >= sizeof(instr->bound_textures) / sizeof(instr->bound_textures[0])) {\n\t\treturn -1;\n\t}\n\n\tallocated_samplers = instr->bound_textures[instr->current_call];\n\tfor (i = 0; i < instr->num_textures[instr->current_call]; ++i) {\n\t\tif (R_TextureReferenceEqual(texture, allocated_samplers[i])) {\n\t\t\treturn i;\n\t\t}\n\t}\n\n\tif (instr->num_textures[instr->current_call] < material_samplers_max) {\n\t\tallocated_samplers[instr->num_textures[instr->current_call]] = texture;\n\t\treturn instr->num_textures[instr->current_call]++;\n\t}\n\n\treturn -1;\n}\n\nstatic qbool GLM_NextAliasModelDrawCall(aliasmodel_draw_instructions_t* instr, qbool bind_default_textures)\n{\n\tif (instr->num_calls >= sizeof(instr->num_textures) / sizeof(instr->num_textures[0])) {\n\t\treturn false;\n\t}\n\n\tinstr->current_call = instr->num_calls;\n\tinstr->num_calls++;\n\tinstr->num_textures[instr->current_call] = 0;\n\tif (bind_default_textures) {\n\t\tif (R_ProgramCustomOptions(r_program_aliasmodel) & DRAW_CAUSTIC_TEXTURES) {\n\t\t\tinstr->bound_textures[instr->current_call][0] = underwatertexture;\n\t\t\tinstr->num_textures[instr->current_call]++;\n\t\t}\n\t}\n\treturn true;\n}\n\nstatic void GLM_QueueDrawCall(aliasmodel_draw_type_t type, int vbo_start, int vbo_count, int instance)\n{\n\tint pos;\n\taliasmodel_draw_instructions_t* instr = &alias_draw_instructions[type];\n\tDrawArraysIndirectCommand_t* indirect;\n\n\tif (!instr->num_calls) {\n\t\tGLM_NextAliasModelDrawCall(instr, false);\n\t}\n\n\tif (instr->num_cmds[instr->current_call] >= sizeof(instr->indirect_buffer) / sizeof(instr->indirect_buffer[0])) {\n\t\treturn;\n\t}\n\n\tpos = instr->num_cmds[instr->current_call]++;\n\tindirect = &instr->indirect_buffer[pos];\n\tindirect->instanceCount = 1;\n\tindirect->baseInstance = instance;\n\tindirect->first = vbo_start;\n\tindirect->count = vbo_count;\n}\n\nstatic void GLM_QueueAliasModelDrawImpl(\n\tentity_t* ent, model_t* model, float* color, int vbo_start, int vbo_count, texture_ref texture,\n\tint effects, float lerpFraction, int lerpFrameVertOffset, qbool outline, int render_effects\n)\n{\n\tqbool shell = (effects & (EF_RED | EF_BLUE | EF_GREEN)) && R_TextureReferenceIsValid(shelltexture) && Ruleset_AllowPowerupShell(model);\n\tuniform_block_aliasmodel_t* uniform;\n\taliasmodel_draw_type_t type = aliasmodel_draw_std;\n\taliasmodel_draw_type_t shelltype = aliasmodel_draw_shells;\n\taliasmodel_draw_instructions_t* instr;\n\tint textureSampler = -1;\n\textern cvar_t gl_spec_xray;\n\tint i;\n\n\t// Compile here so we can work out how many samplers we have free to allocate per draw-call\n\tif (!GLM_CompileAliasModelProgram()) {\n\t\treturn;\n\t}\n\n\tif (render_effects & RF_ADDITIVEBLEND) {\n\t\ttype = aliasmodel_draw_postscene_additive;\n\t\tshell = false;\n\t\toutline = false;\n\t}\n\telse if ((render_effects & RF_WEAPONMODEL) && color[3] < 1) {\n\t\ttype = aliasmodel_draw_postscene;\n\t\tshelltype = aliasmodel_draw_postscene_shells;\n\t\toutline = false;\n\t}\n\telse if (color[3] < 1) {\n\t\ttype = aliasmodel_draw_alpha;\n\t\toutline = false;\n\t}\n\n\tinstr = &alias_draw_instructions[type];\n\n\t// Should never happen...\n\tif (alias_draw_count >= sizeof(aliasdata.models) / sizeof(aliasdata.models[0])) {\n\t\treturn;\n\t}\n\n\tif (!instr->num_calls) {\n\t\tGLM_NextAliasModelDrawCall(instr, true);\n\t}\n\n\t// Assign samplers - if we're over limit, need to flush and try again\n\ttextureSampler = AssignSampler(instr, texture);\n\tif (textureSampler < 0) {\n\t\tif (!GLM_NextAliasModelDrawCall(instr, true)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttextureSampler = AssignSampler(instr, texture);\n\t}\n\n\t// Store static data ready for upload\n\tmemset(&aliasdata.models[alias_draw_count], 0, sizeof(aliasdata.models[alias_draw_count]));\n\tuniform = &aliasdata.models[alias_draw_count];\n\tR_GetModelviewMatrix(uniform->modelViewMatrix);\n\tuniform->amFlags =\n\t\t(effects & EF_RED ? AMF_SHELLMODEL_RED : 0) |\n\t\t(effects & EF_GREEN ? AMF_SHELLMODEL_GREEN : 0) |\n\t\t(effects & EF_BLUE ? AMF_SHELLMODEL_BLUE : 0) |\n\t\t(R_TextureReferenceIsValid(texture) ? AMF_TEXTURE_MATERIAL : 0) |\n\t\t(render_effects & RF_CAUSTICS ? AMF_CAUSTICS : 0) |\n\t\t(render_effects & RF_WEAPONMODEL ? AMF_WEAPONMODEL : 0) |\n\t\t(ent->scoreboard != NULL ? ent->scoreboard->teammate ? AMF_TEAMMATE : 0 : 0) |\n\t\t(ent->renderfx & RF_BEHINDWALL ? AMF_BEHINDWALL : 0) |\n\t\t(ent->renderfx & RF_PLAYERMODEL ? AMF_PLAYERMODEL : 0) |\n\t\t(ent->renderfx & RF_VWEPMODEL ? AMF_VWEPMODEL : 0);\n\tuniform->yaw_angle_rad = ent->angles[YAW] * M_PI / 180.0;\n\tuniform->shadelight = ent->shadelight;\n\tuniform->ambientlight = ent->ambientlight;\n\tuniform->lerpFraction = lerpFraction;\n\tuniform->color[0] = color[0];\n\tuniform->color[1] = color[1];\n\tuniform->color[2] = color[2];\n\tuniform->color[3] = color[3];\n\tuniform->materialSamplerMapping = textureSampler;\n\tuniform->minLumaMix = 1.0f - (ent->full_light ? bound(0, gl_fb_models.integer, 1) : 0);\n\tuniform->outline_normal_scale = ent->outlineScale;\n\tif(ent->scoreboard != NULL) {\n\t\tint tc = 16 * (bound(0, ent->scoreboard->topcolor, 13)) + 8;\n\t\tint bc = 16 * (bound(0, ent->scoreboard->bottomcolor, 13)) + 8;\n\t\tbyte top[] = { host_basepal[tc * 3], host_basepal[tc * 3 + 1], host_basepal[tc * 3 + 2] };\n\t\tbyte bot[] = { host_basepal[bc * 3], host_basepal[bc * 3 + 1], host_basepal[bc * 3 + 2] };\n\t\tfor(i = 0; i < 3; i++) uniform->plrtopcolor[i] = (float)top[i] / 256.0f;\n\t\tfor(i = 0; i < 3; i++) uniform->plrbotcolor[i] = (float)bot[i] / 256.0f;\n\t}\n\n\t// Add to queues\n\tGLM_QueueDrawCall(type, vbo_start, vbo_count, alias_draw_count);\n\tif (outline) {\n\t\tGLM_QueueDrawCall(aliasmodel_draw_outlines, vbo_start, vbo_count, alias_draw_count);\n\t}\n\tif (shell) {\n\t\tGLM_QueueDrawCall(shelltype, vbo_start, vbo_count, alias_draw_count);\n\t}\n\tif((render_effects & RF_PLAYERMODEL) && (render_effects & RF_BEHINDWALL) &&\n\t   (cls.demoplayback || cls.mvdplayback) && gl_spec_xray.value)\n\t{\n\t\tGLM_QueueDrawCall(aliasmodel_draw_outlines_spec, vbo_start, vbo_count, alias_draw_count);\n\t}\n\n\talias_draw_count++;\n}\n\n// Called for .mdl & .md3\nvoid GLM_DrawAliasModelFrame(\n\tentity_t* ent, model_t* model, int poseVertIndex, int poseVertIndex2, int vertsPerPose,\n\ttexture_ref texture, qbool outline, int effects, int render_effects, float lerp_fraction\n)\n{\n\tfloat color[4];\n\tqbool invalidate_texture;\n\n\tif (lerp_fraction == 1) {\n\t\tposeVertIndex = poseVertIndex2;\n\t\tlerp_fraction = 0;\n\t}\n\n\t// TODO: Vertex lighting etc\n\t// TODO: Coloured lighting per-vertex?\n\tR_AliasModelColor(ent, color, &invalidate_texture);\n\n\tif (invalidate_texture) {\n\t\tR_TextureReferenceInvalidate(texture);\n\t}\n\n\tif (r_refdef2.drawCaustics && R_PointIsUnderwater(ent->origin)) {\n\t\trender_effects |= RF_CAUSTICS;\n\t}\n\n\tGLM_QueueAliasModelDrawImpl(\n\t\tent, model, color, poseVertIndex, vertsPerPose,\n\t\ttexture, effects, lerp_fraction, poseVertIndex2, outline, render_effects\n\t);\n}\n\n// .mdl rendering\nvoid GLM_DrawAliasFrame(\n\tentity_t* ent, model_t* model, int pose1, int pose2,\n\ttexture_ref texture, texture_ref fb_texture,\n\tqbool outline, int effects, int render_effects, float lerpfrac\n)\n{\n\taliashdr_t* paliashdr = (aliashdr_t*) Mod_Extradata(model);\n\tint vertIndex = model->vbo_start + pose1 * paliashdr->vertsPerPose;\n\tint nextVertIndex = model->vbo_start + pose2 * paliashdr->vertsPerPose;\n\n\tGLM_DrawAliasModelFrame(\n\t\tent, model, vertIndex, nextVertIndex, paliashdr->vertsPerPose,\n\t\ttexture, outline, effects, render_effects, lerpfrac\n\t);\n}\n\nvoid GLM_PrepareAliasModelBatches(void)\n{\n\tif (!GLM_CompileAliasModelProgram() || !alias_draw_count) {\n\t\treturn;\n\t}\n\n\tR_TraceEnterNamedRegion(__func__);\n\n\t// Update VBO with data about each entity\n\tbuffers.Update(r_buffer_aliasmodel_model_data, sizeof(aliasdata.models[0]) * alias_draw_count, aliasdata.models);\n\tbuffers.BindRange(r_buffer_aliasmodel_model_data, EZQ_STORAGE_BLOCK_BINDING(EZQ_GL_BINDINGPOINT_ALIASMODEL_DRAWDATA), buffers.BufferOffset(r_buffer_aliasmodel_model_data), sizeof(aliasdata.models[0]) * alias_draw_count);\n\n\t// Build & update list of indirect calls\n\t{\n\t\tint i, j;\n\t\tint offset = 0;\n\n\t\tfor (i = 0; i < aliasmodel_draw_max; ++i) {\n\t\t\taliasmodel_draw_instructions_t* instr = &alias_draw_instructions[i];\n\t\t\tint total_cmds = 0;\n\t\t\tint size;\n\n\t\t\tif (!instr->num_calls) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tinstr->indirect_buffer_offset = offset;\n\t\t\tfor (j = 0; j < instr->num_calls; ++j) {\n\t\t\t\ttotal_cmds += instr->num_cmds[j];\n\t\t\t}\n\n\t\t\tsize = sizeof(instr->indirect_buffer[0]) * total_cmds;\n\t\t\tbuffers.UpdateSection(r_buffer_aliasmodel_drawcall_indirect, offset, size, instr->indirect_buffer);\n\t\t\toffset += size;\n\t\t}\n\t}\n\n\tR_TraceLeaveNamedRegion();\n}\n\n// GL 4.1 fallback: no baseInstance, so use instanceOffset uniform to index correct model\nstatic int GLM_DrawAliasModelsNoBaseInstance(aliasmodel_draw_instructions_t* instr, int cmd_start, int call_index)\n{\n\tint j;\n\n\tfor (j = 0; j < instr->num_cmds[call_index]; ++j) {\n\t\tDrawArraysIndirectCommand_t* cmd = &instr->indirect_buffer[cmd_start + j];\n\n\t\t// Without baseInstance support, _instanceId vertex attribute is always 0.\n\t\t// Use instanceOffset uniform so shader reads models[0 + baseInstance].\n\t\tR_ProgramUniform1i(r_program_uniform_aliasmodel_instanceOffset, cmd->baseInstance);\n\n\t\tGL_DrawArrays(GL_TRIANGLES, cmd->first, cmd->count);\n\t}\n\n\t// Reset for potential use with baseInstance path\n\tR_ProgramUniform1i(r_program_uniform_aliasmodel_instanceOffset, 0);\n\n\treturn instr->num_cmds[call_index];\n}\n\nstatic void GLM_RenderPreparedEntities(aliasmodel_draw_type_t type)\n{\n\taliasmodel_draw_instructions_t* instr = &alias_draw_instructions[type];\n\tGLint mode = (type == aliasmodel_draw_shells || type == aliasmodel_draw_postscene_shells ? EZQ_ALIAS_MODE_SHELLS : EZQ_ALIAS_MODE_NORMAL);\n\tunsigned int extra_offset = 0;\n\tint i, call_start;\n\tqbool translucent = (type != aliasmodel_draw_std && type != aliasmodel_draw_postscene_additive);\n\tqbool additive = (type == aliasmodel_draw_postscene_additive);\n\tqbool shells = (type == aliasmodel_draw_shells || type == aliasmodel_draw_postscene_shells);\n\tqbool no_base_instance = !GL_Supported(R_SUPPORT_INSTANCED_RENDERING);\n\n\tif (!instr->num_calls || !GLM_CompileAliasModelProgram()) {\n\t\treturn;\n\t}\n\n\tif (!no_base_instance) {\n\t\tbuffers.Bind(r_buffer_aliasmodel_drawcall_indirect);\n\t\textra_offset = buffers.BufferOffset(r_buffer_aliasmodel_drawcall_indirect);\n\t}\n\n\tR_ProgramUse(r_program_aliasmodel);\n\tR_SetAliasModelUniforms(mode);\n\t// We have prepared the draw calls earlier in the frame so very trival logic here\n\tif (r_refdef2.drawCaustics) {\n\t\trenderer.TextureUnitBind(TEXTURE_UNIT_CAUSTICS, underwatertexture);\n\t}\n\tif (shells) {\n\t\trenderer.TextureUnitBind(TEXTURE_UNIT_MATERIAL, shelltexture);\n\t}\n\n\t// Depth pre-pass to make viewmodel look good.\n\tif (type == aliasmodel_draw_postscene) {\n\t\tGLM_StateBeginAliasModelZPassBatch();\n\t\tcall_start = 0;\n\t\tfor (i = 0; i < instr->num_calls; ++i) {\n\t\t\tif (no_base_instance) {\n\t\t\t\tcall_start += GLM_DrawAliasModelsNoBaseInstance(instr, call_start, i);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tGL_MultiDrawArraysIndirect(\n\t\t\t\t\tGL_TRIANGLES,\n\t\t\t\t\t(const void*)(uintptr_t)(instr->indirect_buffer_offset + extra_offset),\n\t\t\t\t\tinstr->num_cmds[i],\n\t\t\t\t\t0\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tGLM_StateBeginAliasModelBatch(translucent, additive);\n\tcall_start = 0;\n\tfor (i = 0; i < instr->num_calls; ++i) {\n\t\tif (!shells && instr->num_textures[i]) {\n\t\t\trenderer.TextureUnitMultiBind(TEXTURE_UNIT_MATERIAL, instr->num_textures[i], instr->bound_textures[i]);\n\t\t}\n\n\t\tif (no_base_instance) {\n\t\t\tcall_start += GLM_DrawAliasModelsNoBaseInstance(instr, call_start, i);\n\t\t}\n\t\telse {\n\t\t\tGL_MultiDrawArraysIndirect(\n\t\t\t\tGL_TRIANGLES,\n\t\t\t\t(const void*)(uintptr_t)(instr->indirect_buffer_offset + extra_offset),\n\t\t\t\tinstr->num_cmds[i],\n\t\t\t\t0\n\t\t\t);\n\t\t}\n\t}\n\n\tif (type == aliasmodel_draw_std && alias_draw_instructions[aliasmodel_draw_outlines_spec].num_calls) {\n\t\tinstr = &alias_draw_instructions[aliasmodel_draw_outlines_spec];\n\n\t\tR_TraceEnterNamedRegion(\"GLM_DrawOutlineBatch\");\n\t\tR_SetAliasModelUniforms(EZQ_ALIAS_MODE_OUTLINES_SPEC);\n\n\t\tR_ApplyRenderingState(r_state_aliasmodel_outline_spec);\n\n\t\tcall_start = 0;\n\t\tfor (i = 0; i < instr->num_calls; ++i) {\n\t\t\tif (no_base_instance) {\n\t\t\t\tcall_start += GLM_DrawAliasModelsNoBaseInstance(instr, call_start, i);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tGL_MultiDrawArraysIndirect(\n\t\t\t\t\t\tGL_TRIANGLES,\n\t\t\t\t\t\t(const void*)(uintptr_t)(instr->indirect_buffer_offset + extra_offset),\n\t\t\t\t\t\tinstr->num_cmds[i],\n\t\t\t\t\t\t0\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tR_TraceLeaveNamedRegion();\n\t}\n\n\tif (type == aliasmodel_draw_std && alias_draw_instructions[aliasmodel_draw_outlines].num_calls) {\n\t\tinstr = &alias_draw_instructions[aliasmodel_draw_outlines];\n\n\t\tR_TraceEnterNamedRegion(\"GLM_DrawOutlineBatch\");\n\t\tR_SetAliasModelUniforms(EZQ_ALIAS_MODE_OUTLINES);\n\n\t\tGLM_StateBeginAliasOutlineBatch();\n\n\t\tcall_start = 0;\n\t\tfor (i = 0; i < instr->num_calls; ++i) {\n\t\t\tif (no_base_instance) {\n\t\t\t\tcall_start += GLM_DrawAliasModelsNoBaseInstance(instr, call_start, i);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tGL_MultiDrawArraysIndirect(\n\t\t\t\t\tGL_TRIANGLES,\n\t\t\t\t\t(const void*)(uintptr_t)(instr->indirect_buffer_offset + extra_offset),\n\t\t\t\t\tinstr->num_cmds[i],\n\t\t\t\t\t0\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tR_TraceLeaveNamedRegion();\n\t}\n}\n\nvoid GLM_DrawAliasModelBatches(void)\n{\n\tGLM_RenderPreparedEntities(aliasmodel_draw_std);\n\tGLM_RenderPreparedEntities(aliasmodel_draw_alpha);\n\tGLM_RenderPreparedEntities(aliasmodel_draw_shells);\n}\n\nvoid GLM_DrawAliasModelPostSceneBatches(void)\n{\n\tGLM_RenderPreparedEntities(aliasmodel_draw_postscene);\n\tGLM_RenderPreparedEntities(aliasmodel_draw_postscene_additive);\n\tGLM_RenderPreparedEntities(aliasmodel_draw_postscene_shells);\n}\n\nvoid GLM_InitialiseAliasModelBatches(void)\n{\n\talias_draw_count = 0;\n\tmemset(alias_draw_instructions, 0, sizeof(alias_draw_instructions));\n}\n\nvoid GLM_AliasModelShadow(entity_t* ent)\n{\n\t// MEAG: TODO\n\t// aliashdr_t* paliashdr = (aliashdr_t *)Mod_Extradata(ent->model); // locate the proper data\n}\n\n#endif // RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_brushmodel.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"rulesets.h\"\n#include \"utils.h\"\n#include \"glsl/constants.glsl\"\n#include \"glm_brushmodel.h\"\n#include \"tr_types.h\"\n#include <limits.h>\n#include \"glm_vao.h\"\n#include \"r_buffers.h\"\n#include \"r_brushmodel.h\"\n\ntypedef struct vbo_world_surface_s {\n\tfloat normal[4];\n\tfloat lm_vecs0[4];\n\tfloat lm_vecs1[4];\n} vbo_world_surface_t;\n\nvoid GLM_CreateBrushModelVAO(void)\n{\n\tint i;\n\n\tif (!R_BufferReferenceIsValid(r_buffer_brushmodel_vertex_data) || !R_BufferReferenceIsValid(r_buffer_brushmodel_index_data)) {\n\t\treturn;\n\t}\n\n\t// Create vao\n\tR_GenVertexArray(vao_brushmodel);\n\tbuffers.Bind(r_buffer_brushmodel_index_data);\n\n\tGLM_ConfigureVertexAttribPointer(vao_brushmodel, r_buffer_brushmodel_vertex_data, 0, 3, GL_FLOAT, GL_FALSE, sizeof(vbo_world_vert_t), VBO_FIELDOFFSET(vbo_world_vert_t, position), 0);\n\tGLM_ConfigureVertexAttribPointer(vao_brushmodel, r_buffer_brushmodel_vertex_data, 1, 3, GL_FLOAT, GL_FALSE, sizeof(vbo_world_vert_t), VBO_FIELDOFFSET(vbo_world_vert_t, material_coords), 0);\n\tGLM_ConfigureVertexAttribPointer(vao_brushmodel, r_buffer_brushmodel_vertex_data, 2, 3, GL_FLOAT, GL_FALSE, sizeof(vbo_world_vert_t), VBO_FIELDOFFSET(vbo_world_vert_t, lightmap_coords), 0);\n\tGLM_ConfigureVertexAttribPointer(vao_brushmodel, r_buffer_brushmodel_vertex_data, 3, 2, GL_FLOAT, GL_FALSE, sizeof(vbo_world_vert_t), VBO_FIELDOFFSET(vbo_world_vert_t, detail_coords), 0);\n\t// \n\tGLM_ConfigureVertexAttribIPointer(vao_brushmodel, r_buffer_instance_number, 4, 1, GL_UNSIGNED_INT, sizeof(GLuint), 0, 1);\n\tGLM_ConfigureVertexAttribIPointer(vao_brushmodel, r_buffer_brushmodel_vertex_data, 5, 1, GL_UNSIGNED_INT, sizeof(vbo_world_vert_t), VBO_FIELDOFFSET(vbo_world_vert_t, flags), 0);\n\tGLM_ConfigureVertexAttribPointer(vao_brushmodel, r_buffer_brushmodel_vertex_data, 6, 3, GL_FLOAT, GL_FALSE, sizeof(vbo_world_vert_t), VBO_FIELDOFFSET(vbo_world_vert_t, flatcolor), 0);\n\tGLM_ConfigureVertexAttribIPointer(vao_brushmodel, r_buffer_brushmodel_vertex_data, 7, 1, GL_UNSIGNED_INT, sizeof(vbo_world_vert_t), VBO_FIELDOFFSET(vbo_world_vert_t, surface_num), 0);\n\n\tR_BindVertexArray(vao_none);\n\n\t// Create surface information SSBO\n\tif (cl.worldmodel) {\n\t\tvbo_world_surface_t* surfaces = (vbo_world_surface_t*) Q_malloc(cl.worldmodel->numsurfaces * sizeof(vbo_world_surface_t));\n\t\tfor (i = 0; i < cl.worldmodel->numsurfaces; ++i) {\n\t\t\tmsurface_t* surf = cl.worldmodel->surfaces + i;\n\n\t\t\tVectorCopy(surf->plane->normal, surfaces[i].normal);\n\t\t\tsurfaces[i].normal[3] = surf->plane->dist;\n\t\t\tmemcpy(surfaces[i].lm_vecs0, surf->lmvecs[0], sizeof(surf->lmvecs[0]));\n\t\t\tmemcpy(surfaces[i].lm_vecs1, surf->lmvecs[1], sizeof(surf->lmvecs[1]));\n\t\t\tsurfaces[i].lm_vecs0[3] = surf->lmvlen[0];\n\t\t\tsurfaces[i].lm_vecs1[3] = surf->lmvlen[1];\n\t\t}\n\t\tbuffers.Create(r_buffer_brushmodel_surface_data, buffertype_storage, \"brushmodel-surfs\", cl.worldmodel->numsurfaces * sizeof(vbo_world_surface_t), surfaces, bufferusage_constant_data);\n\t\tQ_free(surfaces);\n\t\tbuffers.BindBase(r_buffer_brushmodel_surface_data, EZQ_STORAGE_BLOCK_BINDING(EZQ_GL_BINDINGPOINT_WORLDMODEL_SURFACES));\n\t}\n}\n\n// 'source' is from GLC's float[VERTEXSIZE]\nint GLM_BrushModelCopyVertToBuffer(model_t* mod, void* vbo_buffer_, int position, float* source, int lightmap, int material, float scaleS, float scaleT, msurface_t* surf, qbool has_fb_texture, qbool has_luma_texture)\n{\n\tvbo_world_vert_t* target = (vbo_world_vert_t*)vbo_buffer_ + position;\n\n\tVectorCopy(source, target->position);\n\ttarget->material_coords[0] = source[3] * (scaleS ? scaleS : 1);\n\ttarget->material_coords[1] = source[4] * (scaleT ? scaleT : 1);\n\ttarget->material_coords[2] = material;\n\ttarget->lightmap_coords[0] = source[5];\n\ttarget->lightmap_coords[1] = source[6];\n\ttarget->lightmap_coords[2] = lightmap;\n\ttarget->detail_coords[0] = source[7];\n\ttarget->detail_coords[1] = source[8];\n\n\tif (surf->flags & SURF_DRAWSKY) {\n\t\ttarget->flags = TEXTURE_TURB_SKY;\n\t}\n\telse if (surf->flags & SURF_DRAWTURB) {\n\t\ttarget->flags = (surf->texinfo->texture->turbType & EZQ_SURFACE_TYPE);\n\t\tif (!target->flags) {\n\t\t\ttarget->flags = TEXTURE_TURB_OTHER;\n\t\t}\n\t}\n\telse if (mod->isworldmodel) {\n\t\ttarget->flags = EZQ_SURFACE_WORLD;\n\t\ttarget->flags += (surf->flags & SURF_DRAWFLAT_FLOOR ? EZQ_SURFACE_IS_FLOOR: 0);\n\t\ttarget->flags += (surf->flags & SURF_UNDERWATER ? EZQ_SURFACE_UNDERWATER : 0);\n\t}\n\telse {\n\t\ttarget->flags = 0;\n\t}\n\ttarget->flags += (surf->flags & SURF_DRAWALPHA ? EZQ_SURFACE_ALPHATEST : 0);\n\n\t{\n\t\tbyte rgba[4];\n\n\t\tCOLOR_TO_RGBA(surf->texinfo->texture->flatcolor3ub, rgba);\n\n\t\tVectorScale(rgba, 1 / 255.0f, target->flatcolor);\n\t}\n\ttarget->surface_num = mod->isworldmodel ? surf - mod->surfaces : 0;\n\n\treturn position + 1;\n}\n\nvoid GLM_ChainBrushModelSurfaces(model_t* clmodel, entity_t* ent)\n{\n\tint i;\n\tmsurface_t* psurf;\n\tqbool drawFlatFloors = r_drawflat_mode.integer == 0 && (r_drawflat.integer == 2 || r_drawflat.integer == 1) && clmodel->isworldmodel;\n\tqbool drawFlatWalls = r_drawflat_mode.integer == 0 && (r_drawflat.integer == 3 || r_drawflat.integer == 1) && clmodel->isworldmodel;\n\n\t// GLSL mode - always render the whole model, the surfaces will be re-used if there is\n\t//   another entity with the same model later in the scene\n\tpsurf = &clmodel->surfaces[clmodel->firstmodelsurface];\n\tfor (i = 0; i < clmodel->nummodelsurfaces; i++, psurf++) {\n\t\tif (psurf->flags & SURF_DRAWSKY) {\n\t\t\t// FIXME: Find an example...\n\t\t\tCHAIN_SURF_B2F(psurf, clmodel->drawflat_chain);\n\t\t\tclmodel->drawflat_todo = true;\n\n\t\t\tclmodel->first_texture_chained = min(clmodel->first_texture_chained, psurf->texinfo->miptex);\n\t\t\tclmodel->last_texture_chained = max(clmodel->last_texture_chained, psurf->texinfo->miptex);\n\t\t}\n\t\telse if (psurf->flags & SURF_DRAWTURB) {\n\t\t\textern cvar_t r_fastturb;\n\t\t\t// FIXME: Find an example...\n\t\t\tif (r_fastturb.integer) {\n\t\t\t\tCHAIN_SURF_B2F(psurf, clmodel->drawflat_chain);\n\t\t\t\tclmodel->drawflat_todo = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCHAIN_SURF_B2F(psurf, psurf->texinfo->texture->texturechain);\n\t\t\t}\n\n\t\t\tclmodel->first_texture_chained = min(clmodel->first_texture_chained, psurf->texinfo->miptex);\n\t\t\tclmodel->last_texture_chained = max(clmodel->last_texture_chained, psurf->texinfo->miptex);\n\t\t}\n\t\telse {\n\t\t\tif (drawFlatFloors && (psurf->flags & SURF_DRAWFLAT_FLOOR)) {\n\t\t\t\tchain_surfaces_simple_drawflat(&clmodel->drawflat_chain, psurf);\n\t\t\t\tclmodel->drawflat_todo = true;\n\t\t\t}\n\t\t\telse if (drawFlatWalls && !(psurf->flags & SURF_DRAWFLAT_FLOOR)) {\n\t\t\t\tchain_surfaces_simple_drawflat(&clmodel->drawflat_chain, psurf);\n\t\t\t\tclmodel->drawflat_todo = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tchain_surfaces_simple(&psurf->texinfo->texture->texturechain, psurf);\n\n\t\t\t\tclmodel->first_texture_chained = min(clmodel->first_texture_chained, psurf->texinfo->miptex);\n\t\t\t\tclmodel->last_texture_chained = max(clmodel->last_texture_chained, psurf->texinfo->miptex);\n\t\t\t}\n\t\t}\n\t}\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_brushmodel.h",
    "content": "\n#ifndef EZQUAKE_GLM_BRUSHMODEL_HEADER\n#define EZQUAKE_GLM_BRUSHMODEL_HEADER\n\n#include \"r_brushmodel.h\"\n\n// Our limit, user's limit will be dictated by graphics card (glConfig.texture_units set during startup)\n#define MAXIMUM_MATERIAL_SAMPLERS 32\n\n// Cross-material textures which might be used up before we start binding materials\n// May well be lower in practise, depending on configuration options enabled\n// Currently lightmaps + detail + caustics + (2*skydome|1*skybox) = 5\n#define MAX_STANDARD_TEXTURES 5\n\nvoid GLM_CreateBrushModelVAO(void);\n\n#endif\n"
  },
  {
    "path": "src/glm_draw.c",
    "content": "/*\nCopyright (C) 2017-2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"wad.h\"\n#include \"stats_grid.h\"\n#include \"utils.h\"\n#include \"sbar.h\"\n#include \"common_draw.h\"\n#include \"tr_types.h\"\n#include \"glm_draw.h\"\n#include \"glm_vao.h\"\n#include \"r_draw.h\"\n#include \"r_matrix.h\"\n#include \"glsl/constants.glsl\"\n#include \"glm_local.h\"\n#include \"r_program.h\"\n#include \"r_renderer.h\"\n#include \"gl_texture.h\"\n\nvoid Atlas_SolidTextureCoordinates(texture_ref* ref, float* s, float* t);\nqbool GLM_CreateMultiImageProgram(void);\n\nextern float overall_alpha;\nextern float cachedMatrix[16];\n\n// Flags to detect when the user has changed settings\n#define GLM_HUDIMAGES_SMOOTHTEXT        1\n#define GLM_HUDIMAGES_SMOOTHCROSSHAIR   2\n#define GLM_HUDIMAGES_SMOOTHIMAGES      4\n#define GLM_HUDIMAGES_ALPHAHACK         8\n#define GLM_HUDIMAGES_SMOOTHEVERYTHING  (GLM_HUDIMAGES_SMOOTHTEXT | GLM_HUDIMAGES_SMOOTHCROSSHAIR | GLM_HUDIMAGES_SMOOTHIMAGES)\n\nstatic void GLM_SetCoordinates(glm_image_t* targ, float x1, float y1, float x2, float y2, float s, float s_width, float t, float t_height, int flags)\n{\n\tfloat v1[4] = { x1, y1, 0, 1 };\n\tfloat v2[4] = { x2, y2, 0, 1 };\n\n\tR_MultiplyVector(cachedMatrix, v1, v1);\n\tR_MultiplyVector(cachedMatrix, v2, v2);\n\n\t// TL TL BR BR\n\ttarg[0].pos[0] = v1[0]; targ[0].tex[0] = s;\n\ttarg[1].pos[0] = v1[0]; targ[1].tex[0] = s;\n\ttarg[2].pos[0] = v2[0]; targ[2].tex[0] = s + s_width;\n\ttarg[3].pos[0] = v2[0]; targ[3].tex[0] = s + s_width;\n\t// TL BR TL BR\n\ttarg[0].pos[1] = v1[1]; targ[0].tex[1] = t;\n\ttarg[1].pos[1] = v2[1]; targ[1].tex[1] = t + t_height;\n\ttarg[2].pos[1] = v1[1]; targ[2].tex[1] = t;\n\ttarg[3].pos[1] = v2[1]; targ[3].tex[1] = t + t_height;\n\n\ttarg[0].flags = targ[1].flags = targ[2].flags = targ[3].flags = flags;\n}\n\nvoid GLM_HudDrawComplete(void)\n{\n\tif ((R_ProgramCustomOptions(r_program_hud_images) & GLM_HUDIMAGES_SMOOTHEVERYTHING) == 0) {\n\t\tGL_SamplerClear(0);\n\t}\n\telse if ((R_ProgramCustomOptions(r_program_hud_images) & GLM_HUDIMAGES_SMOOTHEVERYTHING) != GLM_HUDIMAGES_SMOOTHEVERYTHING) {\n\t\tGL_SamplerClear(1);\n\t}\n}\n\nvoid GLM_HudDrawPolygons(texture_ref texture, int start, int end)\n{\n\tint i;\n\tuintptr_t offset = buffers.BufferOffset(r_buffer_hud_image_vertex_data) / sizeof(imageData.images[0]);\n\n\tGLM_StateBeginImageDraw();\n\tR_ProgramUse(r_program_hud_images);\n\trenderer.TextureUnitBind(0, texture);\n\n\tfor (i = start; i <= end; ++i) {\n\t\tGL_DrawArrays(GL_TRIANGLE_STRIP, offset + polygonData.polygonImageIndexes[i], polygonData.polygonVerts[i]);\n\t}\n}\n\nvoid GLM_HudDrawLines(texture_ref texture, int start, int end)\n{\n\tif (R_ProgramReady(r_program_hud_images) && R_VertexArrayCreated(vao_hud_images)) {\n\t\tint i;\n\t\tuintptr_t offset = buffers.BufferOffset(r_buffer_hud_image_vertex_data) / sizeof(imageData.images[0]);\n\n\t\tR_ApplyRenderingState(r_state_hud_images_glm);\n\t\tR_ProgramUse(r_program_hud_images);\n\t\trenderer.TextureUnitBind(0, texture);\n\n\t\tfor (i = start; i <= end; ++i) {\n\t\t\tR_StateBeginAlphaLineRGB(lineData.line_thickness[i]);\n\t\t\tGL_DrawArrays(GL_LINES, offset + lineData.imageIndex[i], 2);\n\t\t}\n\t}\n}\n\nvoid GLM_DrawRectangle(float x, float y, float width, float height, byte* color)\n{\n\ttexture_ref solid_texture;\n\tfloat s, t, alpha;\n\tglm_image_t* img;\n\n\tif (imageData.imageCount >= MAX_MULTI_IMAGE_BATCH) {\n\t\treturn;\n\t}\n\n\tAtlas_SolidTextureCoordinates(&solid_texture, &s, &t);\n\tif (!R_LogCustomImageTypeWithTexture(imagetype_image, imageData.imageCount, solid_texture)) {\n\t\treturn;\n\t}\n\n\timg = &imageData.images[imageData.imageCount * 4];\n\talpha = (color[3] * overall_alpha / 255.0f);\n\n\timg->colour[0] = (img + 1)->colour[0] = (img + 2)->colour[0] = (img + 3)->colour[0] = color[0] * alpha;\n\timg->colour[1] = (img + 1)->colour[1] = (img + 2)->colour[1] = (img + 3)->colour[1] = color[1] * alpha;\n\timg->colour[2] = (img + 1)->colour[2] = (img + 2)->colour[2] = (img + 3)->colour[2] = color[2] * alpha;\n\timg->colour[3] = (img + 1)->colour[3] = (img + 2)->colour[3] = (img + 3)->colour[3] = color[3] * overall_alpha;\n\n\tGLM_SetCoordinates(img, x, y, x + width, y + height, s, 0, t, 0, IMAGEPROG_FLAGS_TEXTURE);\n\n\t++imageData.imageCount;\n}\n\nvoid GLM_DrawImage(float x, float y, float width, float height, float tex_s, float tex_t, float tex_width, float tex_height, byte* color, int flags)\n{\n\tfloat alpha = (color[3] * overall_alpha / 255.0f);\n\tglm_image_t* img = &imageData.images[imageData.imageCount * 4];\n\n\timg->colour[0] = (img + 1)->colour[0] = (img + 2)->colour[0] = (img + 3)->colour[0] = color[0] * alpha;\n\timg->colour[1] = (img + 1)->colour[1] = (img + 2)->colour[1] = (img + 3)->colour[1] = color[1] * alpha;\n\timg->colour[2] = (img + 1)->colour[2] = (img + 2)->colour[2] = (img + 3)->colour[2] = color[2] * alpha;\n\timg->colour[3] = (img + 1)->colour[3] = (img + 2)->colour[3] = (img + 3)->colour[3] = color[3] * overall_alpha;\n\n\tGLM_SetCoordinates(img, x, y, x + width, y + height, tex_s, tex_width, tex_t, tex_height, flags);\n\n\t++imageData.imageCount;\n}\n\nvoid GLM_AdjustImages(int first, int last, float x_offset)\n{\n\tint i;\n\n\tfor (i = first; i < last; ++i) {\n\t\timageData.images[i * 4 + 0].pos[0] += x_offset;\n\t\timageData.images[i * 4 + 1].pos[0] += x_offset;\n\t\timageData.images[i * 4 + 2].pos[0] += x_offset;\n\t\timageData.images[i * 4 + 3].pos[0] += x_offset;\n\t}\n}\n\nvoid GLM_HudPrepareImages(void)\n{\n\tGLM_CreateMultiImageProgram();\n\n\tif (imageData.imageCount) {\n\t\tbuffers.Update(r_buffer_hud_image_vertex_data, sizeof(imageData.images[0]) * imageData.imageCount * 4, imageData.images);\n\t\tif ((R_ProgramCustomOptions(r_program_hud_images) & GLM_HUDIMAGES_SMOOTHEVERYTHING) == 0) {\n\t\t\t// Everything is GL_NEAREST, no smoothing\n\t\t\tGL_SamplerSetNearest(0);\n\t\t}\n\t\telse if ((R_ProgramCustomOptions(r_program_hud_images) & GLM_HUDIMAGES_SMOOTHEVERYTHING) != GLM_HUDIMAGES_SMOOTHEVERYTHING) {\n\t\t\t// Mixed, second texture unit for GL_NEAREST\n\t\t\tGL_SamplerSetNearest(1);\n\t\t}\n\t}\n}\n\nqbool GLM_CreateMultiImageProgram(void)\n{\n\textern cvar_t r_smoothtext, r_smoothcrosshair, r_smoothimages, r_smoothalphahack;\n\n\tint program_flags =\n\t\t(r_smoothtext.integer ? GLM_HUDIMAGES_SMOOTHTEXT : 0) |\n\t\t(r_smoothcrosshair.integer ? GLM_HUDIMAGES_SMOOTHCROSSHAIR : 0) |\n\t\t(r_smoothimages.integer ? GLM_HUDIMAGES_SMOOTHIMAGES : 0) |\n\t\t(r_smoothalphahack.integer ? GLM_HUDIMAGES_ALPHAHACK : 0);\n\n\tif (R_ProgramRecompileNeeded(r_program_hud_images, program_flags)) {\n\t\tchar included_definitions[512];\n\t\tint smooth_flags = (program_flags & GLM_HUDIMAGES_SMOOTHEVERYTHING);\n\t\tqbool mixed_sampling = smooth_flags != 0 && smooth_flags != GLM_HUDIMAGES_SMOOTHEVERYTHING;\n\n\t\tincluded_definitions[0] = '\\0';\n\n\t\tif (mixed_sampling) {\n\t\t\t// depends on flags on individual elements\n\t\t\tstrlcpy(included_definitions, \"#define MIXED_SAMPLING\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (program_flags & GLM_HUDIMAGES_ALPHAHACK) {\n\t\t\tstrlcpy(included_definitions, \"#define PREMULT_ALPHA_HACK\\n\", sizeof(included_definitions));\n\t\t}\n\n\t\t// Initialise program for drawing image\n\t\tR_ProgramCompileWithInclude(r_program_hud_images, included_definitions);\n\n\t\tif (!GL_VersionAtLeast(4, 2) && R_ProgramReady(r_program_hud_images)) {\n\t\t\tR_ProgramUse(r_program_hud_images);\n\t\t\tR_ProgramUniform1i(r_program_uniform_hudimage_tex, 0);\n\t\t}\n\n\t\tR_ProgramSetCustomOptions(r_program_hud_images, program_flags);\n\t}\n\n\tif (!R_BufferReferenceIsValid(r_buffer_hud_image_vertex_data)) {\n\t\tbuffers.Create(r_buffer_hud_image_vertex_data, buffertype_vertex, \"image-vbo\", sizeof(imageData.images), imageData.images, bufferusage_once_per_frame);\n\t}\n\n\tif (!R_VertexArrayCreated(vao_hud_images)) {\n\t\tR_GenVertexArray(vao_hud_images);\n\n\t\tif (!R_BufferReferenceIsValid(r_buffer_hud_image_index_data)) {\n\t\t\tGLuint i;\n\t\t\tint imageIndexLength = MAX_MULTI_IMAGE_BATCH * 5 * sizeof(GLuint);\n\t\t\tGLuint* imageIndexData = Q_malloc(imageIndexLength);\n\n\t\t\tfor (i = 0; i < MAX_MULTI_IMAGE_BATCH; ++i) {\n\t\t\t\timageIndexData[i * 5 + 0] = i * 4;\n\t\t\t\timageIndexData[i * 5 + 1] = i * 4 + 1;\n\t\t\t\timageIndexData[i * 5 + 2] = i * 4 + 2;\n\t\t\t\timageIndexData[i * 5 + 3] = i * 4 + 3;\n\t\t\t\timageIndexData[i * 5 + 4] = ~(GLuint)0;\n\t\t\t}\n\n\t\t\tbuffers.Create(r_buffer_hud_image_index_data, buffertype_index, \"image-indexes\", imageIndexLength, imageIndexData, bufferusage_reuse_many_frames);\n\t\t\tQ_free(imageIndexData);\n\t\t}\n\n\t\tbuffers.Bind(r_buffer_hud_image_index_data);\n\t\tGLM_ConfigureVertexAttribPointer(vao_hud_images, r_buffer_hud_image_vertex_data, 0, 2, GL_FLOAT, GL_FALSE, sizeof(imageData.images[0]), VBO_FIELDOFFSET(glm_image_t, pos), 0);\n\t\tGLM_ConfigureVertexAttribPointer(vao_hud_images, r_buffer_hud_image_vertex_data, 1, 2, GL_FLOAT, GL_FALSE, sizeof(imageData.images[0]), VBO_FIELDOFFSET(glm_image_t, tex), 0);\n\t\tGLM_ConfigureVertexAttribPointer(vao_hud_images, r_buffer_hud_image_vertex_data, 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(imageData.images[0]), VBO_FIELDOFFSET(glm_image_t, colour), 0);\n\t\tGLM_ConfigureVertexAttribIPointer(vao_hud_images, r_buffer_hud_image_vertex_data, 3, 1, GL_INT, sizeof(imageData.images[0]), VBO_FIELDOFFSET(glm_image_t, flags), 0);\n\t\tR_BindVertexArray(vao_none);\n\t}\n\n\treturn R_ProgramReady(r_program_hud_images);\n}\n\nvoid GLM_HudDrawImages(texture_ref texture, int start, int end)\n{\n\tuintptr_t index_offset = (start * 5 * sizeof(GLuint));\n\tuintptr_t buffer_offset = buffers.BufferOffset(r_buffer_hud_image_vertex_data) / sizeof(imageData.images[0]);\n\n\tR_ProgramUse(r_program_hud_images);\n\tGLM_StateBeginImageDraw();\n\tif (R_TextureReferenceIsValid(texture)) {\n\t\tif ((R_ProgramCustomOptions(r_program_hud_images) & GLM_HUDIMAGES_SMOOTHEVERYTHING) != GLM_HUDIMAGES_SMOOTHEVERYTHING) {\n\t\t\ttexture_ref textures[] = { texture, texture };\n\n\t\t\trenderer.TextureUnitMultiBind(0, 2, textures);\n\t\t}\n\t\telse {\n\t\t\trenderer.TextureUnitBind(0, texture);\n\t\t}\n\t}\n\tGL_DrawElementsBaseVertex(GL_TRIANGLE_STRIP, (end - start + 1) * 5, GL_UNSIGNED_INT, (GLvoid*)index_offset, buffer_offset);\n}\n\nqbool GLM_CompileHudCircleProgram(void)\n{\n\tif (R_ProgramRecompileNeeded(r_program_hud_circles, 0)) {\n\t\tR_ProgramCompile(r_program_hud_circles);\n\t}\n\n\treturn R_ProgramReady(r_program_hud_circles);\n}\n\nvoid GLM_HudPrepareCircles(void)\n{\n\t// Build VBO\n\tif (!R_BufferReferenceIsValid(r_buffer_hud_circle_vertex_data)) {\n\t\tbuffers.Create(r_buffer_hud_circle_vertex_data, buffertype_vertex, \"circle-vbo\", sizeof(circleData.drawCirclePointData), circleData.drawCirclePointData, bufferusage_once_per_frame);\n\t}\n\telse if (circleData.circleCount) {\n\t\tbuffers.Update(r_buffer_hud_circle_vertex_data, circleData.circleCount * FLOATS_PER_CIRCLE * sizeof(circleData.drawCirclePointData[0]), circleData.drawCirclePointData);\n\t}\n\n\t// Build VAO\n\tif (!R_VertexArrayCreated(vao_hud_circles)) {\n\t\tR_GenVertexArray(vao_hud_circles);\n\n\t\tGLM_ConfigureVertexAttribPointer(vao_hud_circles, r_buffer_hud_circle_vertex_data, 0, 2, GL_FLOAT, GL_FALSE, 0, NULL, 0);\n\t}\n}\n\nvoid GLM_HudDrawCircles(texture_ref texture, int start, int end)\n{\n\t// FIXME: Not very efficient (but rarely used either)\n\tint i;\n\tuintptr_t offset = buffers.BufferOffset(r_buffer_hud_circle_vertex_data) / (sizeof(float) * 2);\n\n\tstart = max(0, start);\n\tend = min(end, circleData.circleCount - 1);\n\n\tR_ProgramUse(r_program_hud_circles);\n\tR_BindVertexArray(vao_hud_circles);\n\tR_ProgramUniformMatrix4fv(r_program_uniform_hud_circle_matrix, R_ProjectionMatrix());\n\n\tfor (i = start; i <= end; ++i) {\n\t\tR_ProgramUniform4fv(r_program_uniform_hud_circle_color, circleData.drawCircleColors[i]);\n\n\t\tGL_DrawArrays(circleData.drawCircleFill[i] ? GL_TRIANGLE_STRIP : GL_LINE_LOOP, offset + i * FLOATS_PER_CIRCLE / 2, circleData.drawCirclePoints[i]);\n\t}\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_draw.h",
    "content": "\n#ifndef EZQUAKE_GLM_DRAW_HEADER\n#define EZQUAKE_GLM_DRAW_HEADER\n\n#define MAX_LINES_PER_FRAME 128\n\ntypedef struct glm_line_point_s {\n\tvec3_t position;\n\tbyte color[4];\n} glm_line_point_t;\n\ntypedef struct glm_line_framedata_s {\n\tfloat line_thickness[MAX_LINES_PER_FRAME];\n\tint imageIndex[MAX_LINES_PER_FRAME];\n\tint lineCount;\n} glm_line_framedata_t;\n\n#define CIRCLE_LINE_COUNT\t40\n#define FLOATS_PER_CIRCLE ((3 + 2 * CIRCLE_LINE_COUNT) * 2)\n#define CIRCLES_PER_FRAME 256\n\ntypedef struct glm_circle_framedata_s {\n\tfloat drawCirclePointData[FLOATS_PER_CIRCLE * CIRCLES_PER_FRAME];\n\tfloat drawCircleColors[CIRCLES_PER_FRAME][4];\n\tqbool drawCircleFill[CIRCLES_PER_FRAME];\n\tint drawCirclePoints[CIRCLES_PER_FRAME];\n\tfloat drawCircleThickness[CIRCLES_PER_FRAME];\n\n\tint circleCount;\n} glm_circle_framedata_t;\n\n#define MAX_POLYGONS_PER_FRAME 8\n\ntypedef struct glm_polygon_framedata_s {\n\tint polygonVerts[MAX_POLYGONS_PER_FRAME];\n\tint polygonImageIndexes[MAX_POLYGONS_PER_FRAME];\n\tint polygonCount;\n} glm_polygon_framedata_t;\n\n#define MAX_MULTI_IMAGE_BATCH 4096\ntypedef enum {\n\timagetype_image,\n\timagetype_circle,\n\timagetype_polygon,\n\timagetype_line,\n\n\timagetype_count\n} r_image_type_t;\n\ntypedef struct glm_image_s {\n\tfloat pos[2];\n\tfloat tex[4];\n\tunsigned char colour[4];\n\tint flags;\n} glm_image_t;\n\ntypedef struct glm_image_framedata_s {\n\tglm_image_t images[MAX_MULTI_IMAGE_BATCH * 4];\n\n\tint imageCount;\n} glm_image_framedata_t;\n\nextern glm_circle_framedata_t circleData;\nextern glm_image_framedata_t imageData;\nextern glm_polygon_framedata_t polygonData;\nextern glm_line_framedata_t lineData;\n\nqbool R_LogCustomImageType(r_image_type_t type, int index);\nqbool R_LogCustomImageTypeWithTexture(r_image_type_t type, int index, texture_ref texture);\nvoid R_HudUndoLastElement(void);\n\n#endif\n"
  },
  {
    "path": "src/glm_framebuffer.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n// GLM_FrameBuffer.c\n// Framebuffer support in OpenGL core\n\n// For the moment, also contains general framebuffer code as immediate-mode isn't supported\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"gl_framebuffer.h\"\n#include \"tr_types.h\"\n#include \"glm_vao.h\"\n#include \"r_buffers.h\"\n#include \"glm_local.h\"\n#include \"r_state.h\"\n#include \"r_program.h\"\n#include \"r_renderer.h\"\n#include \"r_texture.h\"\n\n#define POST_PROCESS_PALETTE         1\n#define POST_PROCESS_3DONLY          2\n#define POST_PROCESS_TONEMAP         4\n#define POST_PROCESS_FXAA            8\n\nextern cvar_t vid_software_palette, vid_framebuffer, vid_framebuffer_hdr, vid_framebuffer_hdr_tonemap, vid_framebuffer_multisample;\nstatic texture_ref non_framebuffer_screen_texture;\n\nqbool GLM_CompilePostProcessVAO(void)\n{\n\tif (!R_BufferReferenceIsValid(r_buffer_postprocess_vertex_data)) {\n\t\tfloat verts[4][5] = { { 0 } };\n\n\t\tVectorSet(verts[0], -1, -1, 0);\n\t\tverts[0][3] = 0;\n\t\tverts[0][4] = 0;\n\n\t\tVectorSet(verts[1], -1, 1, 0);\n\t\tverts[1][3] = 0;\n\t\tverts[1][4] = 1;\n\n\t\tVectorSet(verts[2], 1, -1, 0);\n\t\tverts[2][3] = 1;\n\t\tverts[2][4] = 0;\n\n\t\tVectorSet(verts[3], 1, 1, 0);\n\t\tverts[3][3] = 1;\n\t\tverts[3][4] = 1;\n\n\t\tbuffers.Create(r_buffer_postprocess_vertex_data, buffertype_vertex, \"post-process-screen\", sizeof(verts), verts, bufferusage_reuse_many_frames);\n\t}\n\n\tif (R_BufferReferenceIsValid(r_buffer_postprocess_vertex_data) && !R_VertexArrayCreated(vao_postprocess)) {\n\t\tR_GenVertexArray(vao_postprocess);\n\n\t\tGLM_ConfigureVertexAttribPointer(vao_postprocess, r_buffer_postprocess_vertex_data, 0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 5, (void*)0, 0);\n\t\tGLM_ConfigureVertexAttribPointer(vao_postprocess, r_buffer_postprocess_vertex_data, 1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 5, (void*)(sizeof(float) * 3), 0);\n\n\t\tR_BindVertexArray(vao_none);\n\t}\n\n\treturn R_VertexArrayCreated(vao_postprocess);\n}\n\n// If this returns false then the framebuffer will be blitted instead\nqbool GLM_CompilePostProcessProgram(void)\n{\n\tint fxaa_preset = GL_FramebufferFxaaPreset();\n\tint post_process_flags =\n\t\t(vid_software_palette.integer ? POST_PROCESS_PALETTE : 0) |\n\t\t(vid_framebuffer.integer == USE_FRAMEBUFFER_3DONLY ? POST_PROCESS_3DONLY : 0) |\n\t\t(vid_framebuffer_hdr.integer && vid_framebuffer_hdr_tonemap.integer ? POST_PROCESS_TONEMAP : 0) |\n\t\t(fxaa_preset > 0 ? POST_PROCESS_FXAA : 0) | (fxaa_preset << 4); // mix in preset to detect change\n\n\tif (R_ProgramRecompileNeeded(r_program_post_process, post_process_flags)) {\n\t\tstatic char included_definitions[131072];\n\n\t\tmemset(included_definitions, 0, sizeof(included_definitions));\n\t\tif (post_process_flags & POST_PROCESS_PALETTE) {\n\t\t\tstrlcat(included_definitions, \"#define EZ_POSTPROCESS_PALETTE\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (post_process_flags & POST_PROCESS_3DONLY) {\n\t\t\tstrlcat(included_definitions, \"#define EZ_POSTPROCESS_OVERLAY\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (post_process_flags & POST_PROCESS_TONEMAP) {\n\t\t\tstrlcat(included_definitions, \"#define EZ_POSTPROCESS_TONEMAP\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (post_process_flags & POST_PROCESS_FXAA) {\n\t\t\textern const unsigned char fxaa_h_glsl[];\n\t\t\tchar buffer[33];\n\t\t\tconst char *settings =\n\t\t\t\t\t\"#define EZ_POSTPROCESS_FXAA\\n\" \\\n\t\t\t\t\t\"#define FXAA_PC 1\\n\" \\\n\t\t\t\t\t\"#define FXAA_GLSL_130 1\\n\" \\\n\t\t\t\t\t\"#define FXAA_GREEN_AS_LUMA 1\\n\";\n\t\t\tsnprintf(buffer, sizeof(buffer), \"#define FXAA_QUALITY__PRESET %d\\n\", fxaa_preset);\n\t\t\tstrlcat(included_definitions, settings, sizeof(included_definitions));\n\t\t\tstrlcat(included_definitions, buffer, sizeof(included_definitions));\n\t\t\tstrlcat(included_definitions, (const char *)fxaa_h_glsl, sizeof(included_definitions));\n\t\t}\n\n\t\t// Initialise program for drawing image\n\t\tR_ProgramCompileWithInclude(r_program_post_process, included_definitions);\n\n\t\tif (!GL_VersionAtLeast(4, 2) && R_ProgramReady(r_program_post_process)) {\n\t\t\tR_ProgramUse(r_program_post_process);\n\t\t\tR_ProgramUniform1i(r_program_uniform_postprocess_base, 0);\n\t\t\tR_ProgramUniform1i(r_program_uniform_postprocess_overlay, 1);\n\t\t}\n\n\t\tR_ProgramSetCustomOptions(r_program_post_process, post_process_flags);\n\t}\n\n\treturn R_ProgramReady(r_program_post_process) && GLM_CompilePostProcessVAO();\n}\n\nqbool GLM_CompileSimpleProgram(void)\n{\n\tif (R_ProgramRecompileNeeded(r_program_simple, 0)) {\n\t\tR_ProgramCompile(r_program_simple);\n\t}\n\n\treturn R_ProgramReady(r_program_simple) && GLM_CompilePostProcessVAO();\n}\n\nvoid GLM_RenderFramebuffers(void)\n{\n\tqbool flip2d = GL_FramebufferEnabled2D();\n\tqbool flip3d = GL_FramebufferEnabled3D();\n\n\tif (GLM_CompilePostProcessProgram()) {\n\t\tR_ProgramUse(r_program_post_process);\n\t\tR_ApplyRenderingState(r_state_default_2d);\n\n\t\tif (flip2d && flip3d) {\n\t\t\trenderer.TextureUnitBind(0, GL_FramebufferTextureReference(framebuffer_std, fbtex_standard));\n\t\t\trenderer.TextureUnitBind(1, GL_FramebufferTextureReference(framebuffer_hud, fbtex_standard));\n\t\t}\n\t\telse if (flip3d) {\n\t\t\trenderer.TextureUnitBind(0, GL_FramebufferTextureReference(framebuffer_std, fbtex_standard));\n\t\t}\n\t\telse if (flip2d) {\n\t\t\trenderer.TextureUnitBind(0, GL_FramebufferTextureReference(framebuffer_hud, fbtex_standard));\n\t\t}\n\t\telse {\n\t\t\t// Create screen texture if required\n\t\t\tif (!R_TextureReferenceIsValid(non_framebuffer_screen_texture) || R_TextureWidth(non_framebuffer_screen_texture) != glwidth || R_TextureHeight(non_framebuffer_screen_texture) != glheight) {\n\t\t\t\tif (R_TextureReferenceIsValid(non_framebuffer_screen_texture)) {\n\t\t\t\t\trenderer.TextureDelete(non_framebuffer_screen_texture);\n\t\t\t\t}\n\t\t\t\tnon_framebuffer_screen_texture = R_LoadTexture(\"glm:postprocess\", glwidth, glheight, NULL, TEX_NOCOMPRESS | TEX_NOSCALE | TEX_NO_TEXTUREMODE, 4);\n\t\t\t\tif (R_TextureReferenceIsValid(non_framebuffer_screen_texture))\n\t\t\t\t\trenderer.TextureSetFiltering(non_framebuffer_screen_texture, texture_minification_nearest, texture_magnification_nearest);\n\t\t\t}\n\n\t\t\tif (!R_TextureReferenceIsValid(non_framebuffer_screen_texture)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Copy from screen to texture\n\t\t\trenderer.TextureUnitBind(0, non_framebuffer_screen_texture);\n\t\t\tglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, glwidth, glheight);\n\t\t}\n\t\tGL_DrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n\t}\n}\n\nvoid GLM_BrightenScreen(void)\n{\n\tfloat f;\n\n\tif (vid_hwgamma_enabled) {\n\t\treturn;\n\t}\n\tif (v_contrast.value <= 1.0) {\n\t\treturn;\n\t}\n\n\tf = min(v_contrast.value, 3);\n\tif (R_OldGammaBehaviour()) {\n\t\textern float vid_gamma;\n\n\t\tf = pow(f, vid_gamma);\n\t}\n\n\tif (GLM_CompileSimpleProgram()) {\n\t\tR_ProgramUse(r_program_simple);\n\t\tR_ApplyRenderingState(r_state_brighten_screen);\n\n\t\twhile (f > 1) {\n\t\t\tif (f >= 2) {\n\t\t\t\tfloat white[4] = { 1, 1, 1, 1 };\n\n\t\t\t\tR_ProgramUniform4fv(r_program_uniform_simple_color, white);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfloat shade[4] = { f - 1, f - 1, f - 1, f - 1 };\n\n\t\t\t\tR_ProgramUniform4fv(r_program_uniform_simple_color, shade);\n\t\t\t}\n\n\t\t\tGL_DrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n\n\t\t\tf *= 0.5;\n\t\t}\n\t}\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_lightmaps.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_local.h\"\n#include \"gl_local.h\"\n#include \"r_lightmaps.h\"\n#include \"r_lighting.h\"\n#include \"r_texture.h\"\n#include \"glsl/constants.glsl\"\n#include \"r_lightmaps_internal.h\"\n#include \"glm_local.h\"\n#include \"gl_texture_internal.h\"\n#include \"r_program.h\"\n#include \"r_renderer.h\"\n#include \"tr_types.h\"\n\n// Hardware lighting: flag surfaces as we add them\nstatic texture_ref lightmap_texture_array;\nstatic texture_ref lightmap_data_array;\nstatic texture_ref lightmap_source_array;\nstatic unsigned int lightmap_depth;\nstatic unsigned int* surfaceTodoData;\nstatic int surfaceTodoLength;\nstatic int maximumSurfaceNumber;\n\ntexture_ref GLM_LightmapArray(void)\n{\n\treturn lightmap_texture_array;\n}\n\nqbool GLM_CompileLightmapComputeProgram(void)\n{\n\tif (!GL_Supported(R_SUPPORT_COMPUTE_SHADERS)) {\n\t\treturn false;\n\t}\n\n\tif (R_ProgramRecompileNeeded(r_program_lightmap_compute, 0) && R_ProgramCompile(r_program_lightmap_compute)) {\n\t\tR_ProgramComputeSetMemoryBarrierFlag(r_program_lightmap_compute, r_program_memory_barrier_image_access);\n\t\tR_ProgramComputeSetMemoryBarrierFlag(r_program_lightmap_compute, r_program_memory_barrier_texture_access);\n\t}\n\n\treturn R_ProgramReady(r_program_lightmap_compute);\n}\n\nvoid GLM_ComputeLightmaps(void)\n{\n\tint i, start;\n\n\tif (!GLM_CompileLightmapComputeProgram()) {\n\t\treturn;\n\t}\n\n\tif (!R_BufferReferenceIsValid(r_buffer_brushmodel_lightstyles_ssbo)) {\n\t\tbuffers.Create(r_buffer_brushmodel_lightstyles_ssbo, buffertype_storage, \"lightstyles\", sizeof(d_lightstylevalue), d_lightstylevalue, bufferusage_once_per_frame);\n\t}\n\telse {\n\t\tbuffers.Update(r_buffer_brushmodel_lightstyles_ssbo, sizeof(d_lightstylevalue), d_lightstylevalue);\n\t}\n\tbuffers.BindRange(r_buffer_brushmodel_lightstyles_ssbo, EZQ_STORAGE_BLOCK_BINDING(EZQ_GL_BINDINGPOINT_LIGHTSTYLES), buffers.BufferOffset(r_buffer_brushmodel_lightstyles_ssbo), sizeof(d_lightstylevalue));\n\tbuffers.Update(r_buffer_brushmodel_surfacestolight_ssbo, surfaceTodoLength, surfaceTodoData);\n\tbuffers.BindRange(r_buffer_brushmodel_surfacestolight_ssbo, EZQ_STORAGE_BLOCK_BINDING(EZQ_GL_BINDINGPOINT_SURFACES_TO_LIGHT), buffers.BufferOffset(r_buffer_brushmodel_surfacestolight_ssbo), surfaceTodoLength);\n\n\tGL_BindImageTexture(0, lightmap_source_array, 0, GL_TRUE, 0, GL_READ_ONLY, GL_RGBA32UI);\n\tGL_BindImageTexture(1, lightmap_texture_array, 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA8);\n\tGL_BindImageTexture(2, lightmap_data_array, 0, GL_TRUE, 0, GL_READ_ONLY, GL_RGBA32I);\n\n\tstart = -1;\n\tfor (i = 0; i < lightmap_array_size; ++i) {\n\t\tif (lightmaps[i].modified) {\n\t\t\tif (start < 0) {\n\t\t\t\tstart = i;\n\t\t\t}\n\t\t\tlightmaps[i].modified = false;\n\t\t}\n\t\telse if (start >= 0) {\n\t\t\tR_ProgramUniform1i(r_program_uniform_lighting_firstLightmap, start);\n\t\t\tR_ProgramComputeDispatch(r_program_lightmap_compute, LIGHTMAP_WIDTH / HW_LIGHTING_BLOCK_SIZE, LIGHTMAP_HEIGHT / HW_LIGHTING_BLOCK_SIZE, i - start);\n\n\t\t\tstart = -1;\n\t\t}\n\t}\n\n\tif (start >= 0) {\n\t\tR_ProgramUniform1i(r_program_uniform_lighting_firstLightmap, start);\n\t\tR_ProgramComputeDispatch(r_program_lightmap_compute, LIGHTMAP_WIDTH / HW_LIGHTING_BLOCK_SIZE, LIGHTMAP_HEIGHT / HW_LIGHTING_BLOCK_SIZE, lightmap_array_size - start);\n\t}\n\tR_ProgramMemoryBarrier(r_program_lightmap_compute);\n}\n\nvoid GLM_CreateLightmapTextures(void)\n{\n\tint i, j;\n\n\tmaximumSurfaceNumber = 0;\n\tfor (j = 1; j < MAX_MODELS; j++) {\n\t\tmodel_t* m = cl.model_precache[j];\n\t\tif (!m) {\n\t\t\tbreak;\n\t\t}\n\t\tif (m->name[0] == '*') {\n\t\t\tcontinue;\n\t\t}\n\t\tif (m->isworldmodel) {\n\t\t\tmaximumSurfaceNumber = max(maximumSurfaceNumber, m->numsurfaces);\n\t\t}\n\t}\n\n\t// Round up to nearest 32\n\tsurfaceTodoLength = ((maximumSurfaceNumber + 31) / 32) * sizeof(unsigned int);\n\tQ_free(surfaceTodoData);\n\tsurfaceTodoData = (unsigned int*)Q_malloc(surfaceTodoLength);\n\tif (!R_BufferReferenceIsValid(r_buffer_brushmodel_surfacestolight_ssbo)) {\n\t\tbuffers.Create(r_buffer_brushmodel_surfacestolight_ssbo, buffertype_storage, \"surfaces-to-light\", surfaceTodoLength, NULL, bufferusage_once_per_frame);\n\t}\n\telse {\n\t\tbuffers.EnsureSize(r_buffer_brushmodel_surfacestolight_ssbo, surfaceTodoLength);\n\t}\n\n\tif (R_TextureReferenceIsValid(lightmap_texture_array) && lightmap_depth >= lightmap_array_size) {\n\t\treturn;\n\t}\n\n\tif (R_TextureReferenceIsValid(lightmap_texture_array)) {\n\t\tR_DeleteTextureArray(&lightmap_texture_array);\n\t}\n\n\tif (R_TextureReferenceIsValid(lightmap_data_array)) {\n\t\tR_DeleteTextureArray(&lightmap_data_array);\n\t}\n\n\tif (R_TextureReferenceIsValid(lightmap_source_array)) {\n\t\tR_DeleteTextureArray(&lightmap_source_array);\n\t}\n\n\tGL_CreateTexturesWithIdentifier(texture_type_2d_array, 1, &lightmap_texture_array, \"lightmap_texture_array\");\n\tGL_TexStorage3D(GL_TEXTURE0, lightmap_texture_array, 1, GL_RGBA8, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, lightmap_array_size, true);\n\tR_SetTextureArraySize(lightmap_texture_array, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, lightmap_array_size, 4);\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tSys_Printf(\"\\nopengl-texture,alloc,%u,%d,%d,%d,%s\\n\", lightmap_texture_array.index, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * lightmap_array_size * 4, \"lightmap_texture_array\");\n#endif\n\trenderer.TextureSetFiltering(lightmap_texture_array, texture_minification_linear, texture_magnification_linear);\n\trenderer.TextureWrapModeClamp(lightmap_texture_array);\n\tlightmap_depth = lightmap_array_size;\n\tfor (i = 0; i < lightmap_array_size; ++i) {\n\t\tlightmaps[i].gl_texref = lightmap_texture_array;\n\t}\n\n\tGL_CreateTexturesWithIdentifier(texture_type_2d_array, 1, &lightmap_source_array, \"lightmap_source_array\");\n\tGL_TexStorage3D(GL_TEXTURE0, lightmap_source_array, 1, GL_RGBA32UI, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, lightmap_array_size, false);\n\tR_SetTextureArraySize(lightmap_source_array, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, lightmap_array_size, 16);\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tSys_Printf(\"\\nopengl-texture,alloc,%u,%d,%d,%d,%s\\n\", lightmap_source_array.index, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * lightmap_array_size * 16, \"lightmap_source_array\");\n#endif\n\n\tGL_CreateTexturesWithIdentifier(texture_type_2d_array, 1, &lightmap_data_array, \"lightmap_data_array\");\n\tGL_TexStorage3D(GL_TEXTURE0, lightmap_data_array, 1, GL_RGBA32I, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, lightmap_array_size, false);\n\tR_SetTextureArraySize(lightmap_data_array, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, lightmap_array_size, 16);\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tSys_Printf(\"\\nopengl-texture,alloc,%u,%d,%d,%d,%s\\n\", lightmap_data_array.index, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * lightmap_array_size * 16, \"lightmap_data_array\");\n#endif\n}\n\nvoid GLM_InvalidateLightmapTextures(void)\n{\n\tR_TextureReferenceInvalidate(lightmap_texture_array);\n\tR_TextureReferenceInvalidate(lightmap_data_array);\n\tR_TextureReferenceInvalidate(lightmap_source_array);\n\tlightmap_depth = 0;\n}\n\nvoid GLM_LightmapFrameInit(void)\n{\n\tmemset(surfaceTodoData, 0, surfaceTodoLength);\n}\n\nvoid GLM_LightmapShutdown(void)\n{\n\tQ_free(surfaceTodoData);\n\tsurfaceTodoLength = 0;\n}\n\nvoid GLM_RenderDynamicLightmaps(msurface_t* s, qbool world)\n{\n\tR_RenderDynamicLightmaps(s, world);\n\n\tif (world && surfaceTodoData && s->surfacenum < maximumSurfaceNumber) {\n\t\tsurfaceTodoData[s->surfacenum / 32] |= (1 << (s->surfacenum % 32));\n\t\tif (s->lightmaptexturenum >= 0 && s->lightmaptexturenum < lightmap_array_size) {\n\t\t\tlightmaps[s->lightmaptexturenum].modified = true;\n\t\t}\n\t}\n}\n\nvoid GLM_BuildLightmap(int i)\n{\n\tGLenum format = GL_Supported(R_SUPPORT_BGRA_LIGHTMAPS) ? GL_BGRA : GL_RGBA;\n\tGLenum type = GL_UNSIGNED_INT_8_8_8_8_REV;\n\n\tGL_TexSubImage3D(\n\t\t0, lightmap_texture_array, 0, 0, 0, i, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, 1, format, type,\n\t\tlightmaps[i].rawdata\n\t);\n\n\tGL_TexSubImage3D(\n\t\t0, lightmap_source_array, 0, 0, 0, i,\n\t\tLIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, 1, GL_RGBA_INTEGER, GL_UNSIGNED_INT,\n\t\tlightmaps[i].sourcedata\n\t);\n\n\tGL_TexSubImage3D(\n\t\t0, lightmap_data_array, 0, 0, 0, i,\n\t\tLIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, 1, GL_RGBA_INTEGER, GL_INT,\n\t\tlightmaps[i].computeData\n\t);\n}\n\nvoid GLM_UploadLightmap(int textureUnit, int lightmapnum)\n{\n\tconst lightmap_data_t* lm = &lightmaps[lightmapnum];\n\tconst void* data_source = lm->rawdata + (lm->change_area.t) * LIGHTMAP_WIDTH * 4;\n\tGLenum format = GL_Supported(R_SUPPORT_BGRA_LIGHTMAPS) ? GL_BGRA : GL_RGBA;\n\tGLenum type = GL_Supported(R_SUPPORT_INT8888R_LIGHTMAPS) ? GL_UNSIGNED_INT_8_8_8_8_REV : GL_UNSIGNED_BYTE;\n\n\tGL_TexSubImage3D(textureUnit, lightmap_texture_array, 0, 0, lm->change_area.t, lightmapnum, LIGHTMAP_WIDTH, lm->change_area.h, 1, format, type, data_source);\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_local.h",
    "content": "\n#ifndef EZQUAKE_GLM_LOCAL_HEADER\n#define EZQUAKE_GLM_LOCAL_HEADER\n\nvoid GLM_BuildCommonTextureArrays(qbool vid_restart);\nvoid GLM_Shutdown(r_shutdown_mode_t mode);\n\n// Reference cvars for 3D views...\ntypedef struct uniform_block_frame_constants_s {\n\tfloat modelViewMatrix[16];\n\tfloat projectionMatrix[16];\n\n\tfloat lightPosition[MAX_DLIGHTS][4];\n\tfloat lightColor[MAX_DLIGHTS][4];\n\n\tfloat position[3];\n\tint lightsActive;\n\n\t// Drawflat colors\n\tfloat r_wallcolor[4];\n\tfloat r_floorcolor[4];\n\tfloat r_telecolor[4];\n\tfloat r_lavacolor[4];\n\tfloat r_slimecolor[4];\n\tfloat r_watercolor[4];\n\tfloat r_skycolor[4];\n\tfloat v_blend[4];\n\n\t//\n\tfloat time;\n\tfloat gamma;\n\tfloat contrast;\n\tint r_alphatestfont;\n\n\t// turb settings\n\tfloat skySpeedscale;\n\tfloat skySpeedscale2;\n\tfloat r_farclip_unused;                  // NO LONGER USED, replace\n\tfloat padding;\n\n\t// animated skybox\n\tvec3_t windDir;\n\tfloat  windPhase;\n\n\t// drawflat toggles (combine into bitfield?)\n\tint r_drawflat;\n\tint r_fastturb;\n\tint r_fastsky;\n\tint r_textureless;\n\n\tfloat unused_r_texture_luma_fb_bmodels;\n\n\t// powerup shells round alias models\n\tfloat shellSize_unused;                  // IS NOW CONSTANT 0.5, replace\n\tfloat shell_base_level1;\n\tfloat shell_base_level2;\n\tfloat shell_effect_level1;\n\tfloat shell_effect_level2;\n\tfloat shell_alpha;\n\n\t// hardware lighting scale\n\tfloat lightScale;\n\n\t// [4-byte break]\n\tint r_width;\n\tint r_height;\n\tfloat r_zFar;\n\tfloat r_zNear;\n\n\t// fog parameters\n\tfloat fogColor[3];\n\tfloat fogDensity;\n\n\tfloat skyFogMix;\n\tfloat fogMinZ;\n\tfloat fogMaxZ;\n\t// camangles [0]\n\n\tfloat camangles[3]; // [1] [2]\n\tfloat r_inv_width;\n\tfloat r_inv_height;\n} uniform_block_frame_constants_t;\n\n#define MAX_WORLDMODEL_BATCH     64\n#define MAX_SPRITE_BATCH         MAX_STANDARD_ENTITIES\n#define MAX_SAMPLER_MAPPINGS    256\n\ntypedef struct sampler_mapping_s {\n\tint samplerIndex;\n\tfloat arrayIndex;\n\tint flags;\n\tint padding;\n} sampler_mapping_t;\n\ntypedef struct uniform_block_world_calldata_s {\n\tfloat modelMatrix[16];\n\tfloat alpha;\n\tint samplerBase;\n\tint flags;\n\tint sampler;\n} uniform_block_world_calldata_t;\n\ntypedef struct uniform_block_sprite_s {\n\tfloat modelView[16];\n\tfloat tex[2];\n\tint skinNumber;\n\tint padding;\n} uniform_block_sprite_t;\n\ntypedef struct uniform_block_spritedata_s {\n\tuniform_block_sprite_t sprites[MAX_SPRITE_BATCH];\n} uniform_block_spritedata_t;\n\nvoid GLM_PreRenderView(void);\nvoid GLM_SetupGL(void);\n\nvoid GLM_InitialiseAliasModelBatches(void);\nvoid GLM_PrepareAliasModelBatches(void);\nvoid GLM_DrawAliasModelBatches(void);\nvoid GLM_DrawAliasModelPostSceneBatches(void);\n\nvoid GLM_StateBeginPolyBlend(void);\nvoid GLM_StateBeginDraw3DSprites(void);\nvoid GLM_BeginDrawWorld(qbool alpha_surfaces, qbool polygon_offset);\n\nvoid GLM_UploadFrameConstants(void);\n\nvoid GLM_StateBeginImageDraw(void);\n\ntypedef enum {\n\topaque_world,      // also contains alpha-tested\n\talpha_surfaces\n} glm_brushmodel_drawcall_type;\n\nvoid GLM_DrawWorldModelBatch(glm_brushmodel_drawcall_type type);\nvoid GLM_DrawWorld(void);\n\n#endif // EZQUAKE_GLM_LOCAL_HEADER\n"
  },
  {
    "path": "src/glm_main.c",
    "content": "\n#include \"quakedef.h\"\n#include \"glm_vao.h\"\n#include \"r_renderer.h\"\n#include \"gl_framebuffer.h\"\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\nvoid GL_Init(void);\nvoid GL_InitialiseBufferHandling(api_buffers_t* api);\nvoid GL_InitialiseState(void);\n\nstatic void R_Stubs_NoOperationEntity(entity_t* ent)\n{\n}\n\nstatic void R_Stubs_NoOperation(void)\n{\n}\n\n#ifndef WITH_RENDERING_TRACE\nstatic void GLM_TextureLabelSetNull(texture_ref texture, const char* name)\n{\n}\n#endif\n\nstatic void GLM_Begin2DRendering(void)\n{\n\tGL_Framebuffer2DSwitch();\n}\n\n#define GLM_PrintGfxInfo                   GL_PrintGfxInfo\n#define GLM_Viewport                       GL_Viewport\n#define GLM_InvalidateViewport             GL_InvalidateViewport\n#define GLM_ClearRenderingSurface          GL_Clear\n#define GLM_EnsureFinished                 GL_EnsureFinished\n#define GLM_Screenshot                     GL_Screenshot\n#define GLM_ScreenshotWidth                GL_ScreenshotWidth\n#define GLM_ScreenshotHeight               GL_ScreenshotHeight\n#define GLM_InitialiseVAOState             GL_InitialiseVAOState\n#define GLM_DescriptiveString              GL_DescriptiveString\n#define GLM_Draw3DSpritesInline            R_Stubs_NoOperation\n#define GLM_DrawDisc                       R_Stubs_NoOperation\n#define GLM_IsFramebufferEnabled3D         GL_FramebufferEnabled3D\n#define GLM_DrawAliasModelShadow           R_Stubs_NoOperationEntity\n#define GLM_DrawAliasModelPowerupShell     R_Stubs_NoOperationEntity\n#define GLM_DrawAlias3ModelPowerupShell    R_Stubs_NoOperationEntity\n#define GLM_TextureInitialiseState         GL_TextureInitialiseState\n#define GLM_TextureDelete                  GL_TextureDelete\n#define GLM_TextureMipmapGenerate          GL_TextureMipmapGenerate\n#define GLM_TextureWrapModeClamp           GL_TextureWrapModeClamp\n#ifdef WITH_RENDERING_TRACE\n#define GLM_TextureLabelSet                GL_TextureLabelSet\n#else\n#define GLM_TextureLabelSet                GLM_TextureLabelSetNull\n#endif\n#define GLM_TextureUnitBind                GL_EnsureTextureUnitBound\n#define GLM_TextureGet                     GL_TextureGet\n#define GLM_TextureCompressionSet          GL_TextureCompressionSet\n#define GLM_TextureCreate2D                GL_TextureCreate2D\n#define GLM_TextureUnitMultiBind           GL_TextureUnitMultiBind\n#define GLM_TexturesCreate                 GL_TexturesCreate\n#define GLM_TextureReplaceSubImageRGBA     GL_TextureReplaceSubImageRGBA\n#define GLM_TextureSetAnisotropy           GL_TextureSetAnisotropy\n#define GLM_TextureSetFiltering            GL_TextureSetFiltering\n#define GLM_TextureLoadCubemapFace         GL_TextureLoadCubemapFace\n#define GLM_CvarForceRecompile             GL_CvarForceRecompile\n#define GLM_ProgramsInitialise             GL_ProgramsInitialise\n#define GLM_ProgramsShutdown               GL_ProgramsShutdown\n#define GLM_DrawSky                        R_Stubs_NoOperation\n#define GLM_PrepareAliasModel              GL_PrepareAliasModel\n#define GLM_TextureIsUnitBound             GL_IsTextureBound\n#define GLM_FramebufferCreate              GL_FramebufferCreate\n\n#define RENDERER_METHOD(returntype, name, ...) \\\n{ \\\n\textern returntype GLM_ ## name(__VA_ARGS__); \\\n\trenderer.name = GLM_ ## name; \\\n}\n\nvoid GLM_Initialise(void)\n{\n#include \"r_renderer_structure.h\"\n\n\tGL_Init();\n\trenderer.vaos_supported = GLM_InitialiseVAOHandling();\n\tGL_InitialiseBufferHandling(&buffers);\n\tGL_InitialiseState();\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_md3.c",
    "content": "/*\nCopyright (C) 2011 fuh and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Code to display .md3 files\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"r_aliasmodel_md3.h\"\n#include \"r_aliasmodel.h\"\n#include \"vx_vertexlights.h\" \n#include \"r_matrix.h\"\n#include \"rulesets.h\"\n\nvoid GLM_DrawAlias3Model(entity_t* ent, qbool outline, qbool additive_pass)\n{\n\textern void R_AliasSetupLighting(entity_t* ent);\n\n\tfloat lerpfrac = 1;\n\tfloat oldMatrix[16];\n\tint v1, v2;\n\n\tmodel_t* mod = ent->model;\n\tmd3model_t* md3Model = (md3model_t *)Mod_Extradata(mod);\n\tsurfinf_t* surfaceInfo = MD3_ExtraSurfaceInfoForModel(md3Model);\n\tmd3Header_t* pHeader = MD3_HeaderForModel(md3Model);\n\tmd3Surface_t* surf;\n\tint frame1, frame2, surfnum, vertsPerFrame = 0;\n\n\tMD3_ForEachSurface(pHeader, surf, surfnum) {\n\t\tvertsPerFrame += 3 * surf->numTriangles;\n\t}\n\n\tif (ent->skinnum >= 0 && ent->skinnum < pHeader->numSkins) {\n\t\tsurfaceInfo += ent->skinnum * pHeader->numSurfaces;\n\t}\n\n\tR_PushModelviewMatrix(oldMatrix);\n\tR_AliasModelPrepare(ent, pHeader->numFrames, &frame1, &frame2, &lerpfrac, &outline);\n\n\tv1 = mod->vbo_start + vertsPerFrame * frame1;\n\tv2 = mod->vbo_start + vertsPerFrame * frame2;\n\tMD3_ForEachSurface(pHeader, surf, surfnum) {\n\t\t// FIXME: hack for not reading different shader types\n\t\tint extra_fx = ((mod->modhint & MOD_VMODEL) && surfnum >= 1 ? RF_ADDITIVEBLEND : 0);\n\n\t\tGLM_DrawAliasModelFrame(\n\t\t\tent, mod, v1, v2, 3 * surf->numTriangles,\n\t\t\tsurfaceInfo[surfnum].texnum, outline, ent->effects, ent->renderfx | extra_fx, lerpfrac\n\t\t);\n\t\tv1 += 3 * surf->numTriangles;\n\t\tv2 += 3 * surf->numTriangles;\n\t}\n\n\tR_PopModelviewMatrix(oldMatrix);\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_misc.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"gl_framebuffer.h\"\n#include \"tr_types.h\"\n#include \"glsl/constants.glsl\"\n#include \"r_lighting.h\"\n#include \"r_matrix.h\"\n#include \"r_buffers.h\"\n#include \"glm_local.h\"\n#include \"r_renderer.h\"\n#include \"gl_texture.h\"\n#include \"r_brushmodel_sky.h\"\n\nstatic uniform_block_frame_constants_t frameConstants;\nstatic qbool frameConstantsUploaded = false;\n\nvoid GLM_UploadFrameConstants(void);\n\n#define PASS_COLOR_AS_4F(target, cvar) \\\n{ \\\n\ttarget[0] = (cvar.color[0] * 1.0f / 255); \\\n\ttarget[1] = (cvar.color[1] * 1.0f / 255); \\\n\ttarget[2] = (cvar.color[2] * 1.0f / 255); \\\n\ttarget[3] = 1.0f; \\\n}\n\n// TODO: !?  Called as first step in 2D.  Include in frame-buffer at end of 3D scene rendering?\nvoid GLM_PolyBlend(float v_blend[4])\n{\n\tcolor_t v_blend_color = RGBA_TO_COLOR(\n\t\tbound(0, v_blend[0], 1) * 255,\n\t\tbound(0, v_blend[1], 1) * 255,\n\t\tbound(0, v_blend[2], 1) * 255,\n\t\tbound(0, v_blend[3], 1) * 255\n\t);\n\n\tGLM_StateBeginPolyBlend();\n\tDraw_AlphaRectangleRGB(r_refdef.vrect.x, r_refdef.vrect.y, r_refdef.vrect.width, r_refdef.vrect.height, 0.0f, true, v_blend_color);\n}\n\nvoid GLM_PreRenderView(void)\n{\n\textern cvar_t gl_alphafont, gl_max_size;\n\textern cvar_t r_telecolor, r_lavacolor, r_slimecolor, r_watercolor, r_fastturb, r_skycolor;\n\textern cvar_t gl_textureless, gl_hwblend;\n\tint i, active_lights = 0;\n\tfloat blend_alpha;\n\n\t// General constants\n\tframeConstants.time = cl.time;\n\tframeConstants.gamma = bound(0.3, v_gamma.value, 3);\n\tframeConstants.contrast = bound(1, v_contrast.value, 3);\n\tframeConstants.r_alphatestfont = gl_alphafont.integer ? 0 : 1;\n\tblend_alpha = (!vid_hwgamma_enabled || !gl_hwblend.value || cl.teamfortress) ? 0 : v_blend[3];\n\tframeConstants.v_blend[0] = v_blend[0] * blend_alpha;\n\tframeConstants.v_blend[1] = v_blend[1] * blend_alpha;\n\tframeConstants.v_blend[2] = v_blend[2] * blend_alpha;\n\tframeConstants.v_blend[3] = 1 - blend_alpha;\n\n\t// Lights\n\tfor (i = 0; i < MAX_DLIGHTS; ++i) {\n\t\tif (cl_dlight_active[i / 32] & (1 << (i % 32))) {\n\t\t\textern cvar_t gl_colorlights;\n\n\t\t\tdlight_t* light = &cl_dlights[i];\n\t\t\tfloat* lightColor = frameConstants.lightColor[active_lights];\n\t\t\tfloat* lightPosition = frameConstants.lightPosition[active_lights];\n\t\t\t\n\t\t\tVectorCopy(light->origin, lightPosition);\n\t\t\tlightPosition[3] = light->radius;\n\t\t\tif (gl_colorlights.integer) {\n\t\t\t\tif (light->type == lt_custom) {\n\t\t\t\t\tlightColor[0] = light->color[0] / 255.0;\n\t\t\t\t\tlightColor[1] = light->color[1] / 255.0;\n\t\t\t\t\tlightColor[2] = light->color[2] / 255.0;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tlightColor[0] = dlightcolor[light->type][0] / 255.0;\n\t\t\t\t\tlightColor[1] = dlightcolor[light->type][1] / 255.0;\n\t\t\t\t\tlightColor[2] = dlightcolor[light->type][2] / 255.0;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlightColor[0] = lightColor[1] = lightColor[2] = 0.5;\n\t\t\t}\n\t\t\tlightColor[3] = light->minlight;\n\t\t\t++active_lights;\n\t\t}\n\t}\n\tframeConstants.lightsActive = active_lights;\n\tframeConstants.lightScale = ((lightmode == 2 ? 1.5 : 2) * bound(0.5, gl_modulate.value, 3));\n\n\t// Draw-world constants\n\tframeConstants.r_textureless = gl_textureless.integer || gl_max_size.integer == 1;\n\tframeConstants.skySpeedscale = r_refdef2.time * 8;\n\tframeConstants.skySpeedscale -= (int)frameConstants.skySpeedscale & ~127;\n\tframeConstants.skySpeedscale /= 128.0f;\n\tframeConstants.skySpeedscale2 = r_refdef2.time * 16;\n\tframeConstants.skySpeedscale2 -= (int)frameConstants.skySpeedscale2 & ~127;\n\tframeConstants.skySpeedscale2 /= 128.0f;\n\n\tframeConstants.r_drawflat = r_drawflat.integer;\n\tPASS_COLOR_AS_4F(frameConstants.r_wallcolor, r_wallcolor);\n\tPASS_COLOR_AS_4F(frameConstants.r_floorcolor, r_floorcolor);\n\n\tframeConstants.r_fastturb = r_fastturb.integer;\n\tPASS_COLOR_AS_4F(frameConstants.r_telecolor, r_telecolor);\n\tPASS_COLOR_AS_4F(frameConstants.r_lavacolor, r_lavacolor);\n\tPASS_COLOR_AS_4F(frameConstants.r_slimecolor, r_slimecolor);\n\tPASS_COLOR_AS_4F(frameConstants.r_watercolor, r_watercolor);\n\n\tPASS_COLOR_AS_4F(frameConstants.r_skycolor, r_skycolor);\n\n\t// Alias models\n\t{\n\t\textern cvar_t gl_powerupshells_base1level, gl_powerupshells_base2level;\n\t\textern cvar_t gl_powerupshells_effect1level, gl_powerupshells_effect2level;\n\n\t\tframeConstants.shellSize_unused = 0.5f;\n\t\tframeConstants.shell_base_level1 = gl_powerupshells_base1level.value;\n\t\tframeConstants.shell_base_level2 = gl_powerupshells_base2level.value;\n\t\tframeConstants.shell_effect_level1 = gl_powerupshells_effect1level.value;\n\t\tframeConstants.shell_effect_level2 = gl_powerupshells_effect2level.value;\n\t\tframeConstants.shell_alpha = bound(0, gl_powerupshells.value, 1);\n\t}\n\n\t// Window constants\n\tframeConstants.r_width = VID_ScaledWidth3D();\n\tframeConstants.r_height = VID_ScaledHeight3D();\n\tframeConstants.r_inv_width = 1.0f / (float) frameConstants.r_width;\n\tframeConstants.r_inv_height = 1.0f / (float) frameConstants.r_height;\n\tframeConstants.r_zFar = R_FarPlaneZ();\n\tframeConstants.r_zNear = R_NearPlaneZ();\n\n\t// fog colors\n\tVectorCopy(r_refdef2.fog_color, frameConstants.fogColor);\n\tframeConstants.fogMinZ = r_refdef2.fog_linear_start;\n\tframeConstants.fogMaxZ = r_refdef2.fog_linear_end;\n\tframeConstants.skyFogMix = r_refdef2.fog_sky;\n\tframeConstants.fogDensity = r_refdef2.fog_density; // (r_refdef2.fog_calculation == fogcalc_exp2 ? r_refdef2.fog_density * r_refdef2.fog_density : r_refdef2.fog_density);\n\n\tSkywind_GetDirectionAndPhase(frameConstants.windDir, &frameConstants.windPhase);\n\n\tframeConstantsUploaded = false;\n}\n\nvoid GLM_SetupGL(void)\n{\n\tmemcpy(frameConstants.modelViewMatrix, R_ModelviewMatrix(), sizeof(frameConstants.modelViewMatrix));\n\tmemcpy(frameConstants.projectionMatrix, R_ProjectionMatrix(), sizeof(frameConstants.projectionMatrix));\n\tVectorCopy(r_refdef.vieworg, frameConstants.position);\n\n\tframeConstants.camangles[PITCH] = DEG2RAD(r_refdef.viewangles[PITCH]);\n\tframeConstants.camangles[YAW] = DEG2RAD(r_refdef.viewangles[YAW]);\n\tframeConstants.camangles[ROLL] = DEG2RAD(r_refdef.viewangles[ROLL]);\n\n\tframeConstantsUploaded = false;\n}\n\nvoid GLM_UploadFrameConstants(void)\n{\n\tif (!frameConstantsUploaded) {\n\t\tif (!R_BufferReferenceIsValid(r_buffer_frame_constants)) {\n\t\t\tbuffers.Create(r_buffer_frame_constants, buffertype_uniform, \"frameConstants\", sizeof(frameConstants), &frameConstants, bufferusage_once_per_frame);\n\t\t}\n\n\t\tbuffers.BindRange(r_buffer_frame_constants, EZQ_GL_BINDINGPOINT_FRAMECONSTANTS, buffers.BufferOffset(r_buffer_frame_constants), sizeof(frameConstants));\n\t\tbuffers.Update(r_buffer_frame_constants, sizeof(frameConstants), &frameConstants);\n\t\tframeConstantsUploaded = true;\n\t}\n}\n\nvoid GLM_ScreenDrawStart(void)\n{\n\tGL_FramebufferScreenDrawStart();\n}\n\nvoid GLM_PostProcessScreen(void)\n{\n\tGL_FramebufferPostProcessScreen();\n}\n\nvoid GLM_Shutdown(r_shutdown_mode_t mode)\n{\n\tif (mode != r_shutdown_reload) {\n\t\trenderer.ProgramsShutdown(mode == r_shutdown_restart);\n\t\tGL_DeleteSamplers();\n\t}\n\tGL_FramebufferDeleteAll();\n}\n\n#endif\n"
  },
  {
    "path": "src/glm_particles.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include \"quakedef.h\"\n#include \"glm_particles.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"gl_texture_internal.h\"\n#include \"gl_sprite3d.h\"\n#include \"particles_classic.h\"\n\nextern texture_ref particletexture;\ntexture_ref particletexture_array;\nint particletexture_array_index;\nfloat particletexture_array_xpos_tr;\nfloat particletexture_array_ypos_tr;\nfloat particletexture_array_max_s = 1.0f;\nfloat particletexture_array_max_t = 1.0f;\nstatic float particletexture_scale_s;\nstatic float particletexture_scale_t;\n\nbyte* Classic_CreateParticleTexture(int width, int height);\n\nvoid Part_FlagTexturesForArray(texture_flag_t* texture_flags)\n{\n\tif (R_TextureReferenceIsValid(particletexture)) {\n\t\ttexture_flags[particletexture.index].flags |= (1 << TEXTURETYPES_SPRITES);\n\t}\n}\n\nvoid Part_ImportTexturesForArrayReferences(texture_flag_t* texture_flags)\n{\n\tif (R_TextureReferenceIsValid(particletexture)) {\n\t\ttexture_array_ref_t* array_ref = &texture_flags[particletexture.index].array_ref[TEXTURETYPES_SPRITES];\n\n\t\tif (!R_TexturesAreSameSize(array_ref->ref, particletexture)) {\n\t\t\tint width = R_TextureWidth(array_ref->ref);\n\t\t\tint height = R_TextureHeight(array_ref->ref);\n\t\t\tbyte* data = Classic_CreateParticleTexture(width, height);\n\n\t\t\tGL_TexSubImage3D(0, array_ref->ref, 0, 0, 0, array_ref->index, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, data);\n\n\t\t\tQ_free(data);\n\t\t}\n\n\t\t{\n\t\t\tfloat width = R_TextureWidth(array_ref->ref);\n\t\t\tfloat height = R_TextureHeight(array_ref->ref);\n\n\t\t\tparticletexture_array = array_ref->ref;\n\t\t\tparticletexture_array_xpos_tr = (width - 1.0f) / width;\n\t\t\tparticletexture_array_ypos_tr = (height - 1.0f) / height;\n\t\t\tparticletexture_array_max_s = min(width, height) / width;\n\t\t\tparticletexture_array_max_t = min(width, height) / height;\n\t\t\tparticletexture_array_index = array_ref->index;\n\t\t\tparticletexture_scale_s = array_ref->scale_s;\n\t\t\tparticletexture_scale_t = array_ref->scale_t;\n\t\t}\n\t}\n}\n\nvoid GLM_LoadParticleTextures(void)\n{\n\tif (R_TextureReferenceIsValid(particletexture_array)) {\n\t\tint width = R_TextureWidth(particletexture_array);\n\t\tint height = R_TextureHeight(particletexture_array);\n\t\tbyte* data = Classic_CreateParticleTexture(width, height);\n\n\t\tGL_TexSubImage3D(0, particletexture_array, 0, 0, 0, particletexture_array_index, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, data);\n\t\tGL_TextureMipmapGenerate(particletexture_array);\n\n\t\tQ_free(data);\n\t}\n}\n\nvoid GLM_DrawClassicParticles(int particles_to_draw)\n{\n\tr_sprite3d_vert_t* vert;\n\n\tR_Sprite3DInitialiseBatch(SPRITE3D_PARTICLES_CLASSIC, r_state_particles_classic, particletexture_array, particletexture_array_index, r_primitive_triangles);\n\tvert = R_Sprite3DAddEntry(SPRITE3D_PARTICLES_CLASSIC, 3 * particles_to_draw);\n\tif (vert) {\n\t\textern r_sprite3d_vert_t glvertices[ABSOLUTE_MAX_PARTICLES * 3];\n\n\t\tmemcpy(vert, glvertices, particles_to_draw * 3 * sizeof(glvertices[0]));\n\t}\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_particles.h",
    "content": "/*\nCopyright (C) 2018 ezQuake team.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_GLM_PARTICLES_HEADER\n#define EZQUAKE_GLM_PARTICLES_HEADER\n\n#include \"glm_texture_arrays.h\"\n#include \"r_texture.h\"\n\nextern texture_ref particletexture_array;\nextern int particletexture_array_index;\n\nvoid GLM_LoadParticleTextures(void);\nvoid Part_ImportTexturesForArrayReferences(texture_flag_t* texture_flags);\nvoid Part_FlagTexturesForArray(texture_flag_t* texture_flags);\n\n#endif // EZQUAKE_GLM_PARTICLES_HEADER\n"
  },
  {
    "path": "src/glm_performance.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: gl_rmisc.c,v 1.27 2007-09-17 19:37:55 qqshka Exp $\n*/\n// gl_rmisc.c\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"tr_types.h\"\n#include \"r_renderer.h\"\n\nvoid GLM_TimeRefresh(void)\n{\n\tint i;\n\tfloat start, stop, time;\n\n\tif (!GL_FramebufferEnabled2D()) {\n#ifndef __APPLE__\n\t\tif (glConfig.hardwareType != GLHW_INTEL) {\n\t\t\t// Causes the console to flicker on Intel cards.\n\t\t\tglDrawBuffer(GL_FRONT);\n\t\t}\n#endif\n\t}\n\n\trenderer.EnsureFinished();\n\n\tstart = Sys_DoubleTime();\n\tfor (i = 0; i < 128; i++) {\n\t\tr_refdef.viewangles[1] = i * (360.0 / 128.0);\n\t\tR_SetupFrame();\n\t\tR_RenderView();\n\t}\n\n\trenderer.EnsureFinished();\n\tstop = Sys_DoubleTime();\n\ttime = stop - start;\n\tCom_Printf(\"%f seconds (%f fps)\\n\", time, 128 / time);\n\n\tif (!GL_FramebufferEnabled2D()) {\n#ifndef __APPLE__\n\t\tif (glConfig.hardwareType != GLHW_INTEL) {\n\t\t\tglDrawBuffer(GL_BACK);\n\t\t}\n#endif\n\t}\n\n\tR_EndRendering();\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_rmain.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"glm_local.h\"\n#include \"glm_brushmodel.h\"\n#include \"gl_framebuffer.h\"\n#include \"tr_types.h\"\n#include \"glm_vao.h\"\n#include \"r_buffers.h\"\n#include \"glm_local.h\"\n#include \"r_program.h\"\n#include \"r_renderer.h\"\n#include \"r_aliasmodel.h\"\n#include \"r_renderer.h\"\n#include \"r_sprite3d.h\"\n#include \"r_state.h\"\n#include \"r_matrix.h\"\n\ntexture_ref GL_FramebufferTextureReference(framebuffer_id id, fbtex_id tex_id);\nqbool GLM_CompilePostProcessVAO(void);\n\n// If this returns false then the framebuffer will be blitted instead\nqbool GLM_CompileWorldGeometryProgram(void)\n{\n\tint post_process_flags = 0;\n\n\tif (R_ProgramRecompileNeeded(r_program_fx_world_geometry, post_process_flags)) {\n\t\t// Initialise program for drawing image\n\t\tR_ProgramCompile(r_program_fx_world_geometry);\n\n\t\tif (!GL_VersionAtLeast(4, 2) && R_ProgramReady(r_program_fx_world_geometry)) {\n\t\t\tR_ProgramUse(r_program_fx_world_geometry);\n\t\t\tR_ProgramUniform1i(r_program_uniform_fxworldgeometry_normaltex, 0);\n\t\t}\n\n\t\tR_ProgramSetCustomOptions(r_program_fx_world_geometry, post_process_flags);\n\t}\n\n\treturn R_ProgramReady(r_program_fx_world_geometry) && GLM_CompilePostProcessVAO();\n}\n\nstatic void GLM_DrawWorldOutlines(void)\n{\n\ttexture_ref normals = GL_FramebufferTextureReference(framebuffer_std, fbtex_worldnormals);\n\n\tif (R_TextureReferenceIsValid(normals) && GLM_CompileWorldGeometryProgram()) {\n\t\tint viewport[4];\n\t\tint fullscreen_viewport[4];\n\t\textern cvar_t gl_outline_color_world, gl_outline_world_depth_threshold, gl_outline_world_normal_threshold;\n\n\t\tR_GetViewport(viewport);\n\n\t\t// If we are only rendering to a section of the screen then that is the only part of the texture that will be filled in\n\t\tif (CL_MultiviewEnabled()) {\n\t\t\tR_GetFullScreenViewport(fullscreen_viewport);\n\t\t\tR_Viewport(fullscreen_viewport[0], fullscreen_viewport[1], fullscreen_viewport[2], fullscreen_viewport[3]);\n\t\t\tR_EnableScissorTest(viewport[0], viewport[1], viewport[2], viewport[3]);\n\t\t} else {\n\t\t\t// ignore viewsize and allat crap and set the viewport size to the whole window.\n\t\t\t// previously the viewport was already resized, and then resized again later, making the outlines not align.\n\t\t\tR_Viewport(0, 0, VID_ScaledWidth3D(), VID_ScaledHeight3D());\n\t\t}\n\n\t\trenderer.TextureUnitBind(0, normals);\n\n\t\tR_ProgramUniform1f(r_program_uniform_outline_depth_threshold, bound(1, gl_outline_world_depth_threshold.value, 16));\n\t\tR_ProgramUniform1f(r_program_uniform_outline_normal_threshold, bound(0, gl_outline_world_normal_threshold.value, 0.999));\n\t\tR_ProgramUniform3f(r_program_uniform_outline_color,\n                           (float)gl_outline_color_world.color[0] / 255.0f,\n                           (float)gl_outline_color_world.color[1] / 255.0f,\n                           (float)gl_outline_color_world.color[2] / 255.0f);\n\n\t\t// Scaling the outline with framebuffer resolution for consistency\n\t\t// Outline scaling factor bounds:\n\t\t// - 1:1 for upscaling a smaller FB (only one pixel needed, as they get bigger and bigger and bloat the view),\n\t\t// - 4:1 for downscaling a larger FB (max effective framebuffer scale)\n\t\t// MAX is better at high scales and mismatched x/y ratios\n\t\t// (maintains more outline at high FB scale, consistent with base res).\n\t\t// MIN/MAX makes no difference at tiny scales\n\n\t\tfloat fb_scale_x = VID_ScaledWidth3D() / glConfig.vidWidth;\n\t\tfloat fb_scale_y = VID_ScaledHeight3D() / glConfig.vidHeight;\n\t\tfloat fb_scaling = bound(1,(max(fb_scale_x, fb_scale_y)),4);\n\t\tR_ProgramUniform1f(r_program_uniform_outline_scale, fb_scaling);\n\n\t\tR_ProgramUse(r_program_fx_world_geometry);\n\t\tR_ApplyRenderingState(r_state_fx_world_geometry);\n\n\t\tGL_DrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n\n\t\t// Restore viewport\n\t\tR_Viewport(viewport[0], viewport[1], viewport[2], viewport[3]);\n\t\tif (CL_MultiviewEnabled()) {\n\t\t\tR_DisableScissorTest();\n\t\t}\n\t}\n}\n\nvoid GLM_RenderView(void)\n{\n\tGLM_UploadFrameConstants();\n\tR_UploadChangedLightmaps();\n\tGLM_PrepareWorldModelBatch();\n\tGLM_PrepareAliasModelBatches();\n\trenderer.Prepare3DSprites();\n\n\tif (GL_FramebufferStartWorldNormals(framebuffer_std)) {\n\t\tGLM_DrawWorldModelBatch(opaque_world);\n\t\tGL_FramebufferEndWorldNormals(framebuffer_std);\n\t\tGLM_DrawWorldOutlines();\n\t}\n\telse {\n\t\tGLM_DrawWorldModelBatch(opaque_world);\n\t}\n\n\tR_TraceEnterNamedRegion(\"GLM_DrawEntities\");\n\tGLM_DrawAliasModelBatches();\n\tR_TraceLeaveNamedRegion();\n\n\trenderer.Draw3DSprites();\n\n\tGLM_DrawWorldModelBatch(alpha_surfaces);\n\n\tGLM_DrawAliasModelPostSceneBatches();\n}\n\nvoid GLM_PrepareModelRendering(qbool vid_restart)\n{\n\tGLM_BuildCommonTextureArrays(vid_restart);\n\n\tif (cls.state != ca_disconnected) {\n\t\tR_CreateInstanceVBO();\n\t\tR_CreateAliasModelVBO();\n\t\tR_BrushModelCreateVBO();\n\n\t\tGLM_CreateBrushModelVAO();\n\t}\n\trenderer.ProgramsInitialise();\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n\n"
  },
  {
    "path": "src/glm_rsurf.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// glm_surf.c: surface-related refresh code (modern OpenGL)\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"rulesets.h\"\n#include \"utils.h\"\n#include \"tr_types.h\"\n#include \"glsl/constants.glsl\"\n#include \"glm_brushmodel.h\"\n#include \"r_brushmodel_sky.h\"\n#include \"r_matrix.h\"\n#include \"glm_vao.h\"\n#include \"glm_local.h\"\n#include \"r_texture.h\"\n#include \"r_brushmodel.h\"\n#include \"r_buffers.h\"\n#include \"r_program.h\"\n#include \"r_renderer.h\"\n\n#define GLM_DRAWCALL_INCREMENT 8\n\nstatic GLuint index_count;\n\ntypedef struct glm_worldmodel_req_s {\n\t// This is DrawElementsIndirectCmd, from OpenGL spec\n\tGLuint count;           // Number of indexes to pull\n\tGLuint instanceCount;   // Always 1... ?\n\tGLuint firstIndex;      // Position of first index in array\n\tGLuint baseVertex;      // Offset of vertices in VBO\n\tGLuint baseInstance;    // We use this to pull from array of uniforms in shader\n\n\tfloat mvMatrix[16];\n\tint flags;\n\tint samplerMappingBase;\n\tint samplerMappingCount;\n\tint firstTexture;\n\tqbool isAlphaTested;\n\tfloat alpha;\n\tqbool polygonOffset;\n\tqbool worldmodel;\n\tmodel_t* model;\n\tint nonDynamicSampler;\n} glm_worldmodel_req_t;\n\ntypedef struct glm_brushmodel_drawcall_s {\n\tuniform_block_world_calldata_t calls[MAX_WORLDMODEL_BATCH];\n\tsampler_mapping_t mappings[MAX_SAMPLER_MAPPINGS];\n\n\tglm_worldmodel_req_t worldmodel_requests[MAX_WORLDMODEL_BATCH];\n\ttexture_ref allocated_samplers[MAXIMUM_MATERIAL_SAMPLERS];\n\tint material_samplers;\n\tint sampler_mappings;\n\tint batch_count;\n\tGLintptr indirectDrawOffset;\n\n\tint polygonOffsetSplit;\n\tglm_brushmodel_drawcall_type type;\n\n\tstruct glm_brushmodel_drawcall_s* next;\n} glm_brushmodel_drawcall_t;\n\nstatic glm_brushmodel_drawcall_t* GL_PrepareWorldModelBatch(glm_brushmodel_drawcall_type type);\nstatic void GL_SortDrawCalls(glm_brushmodel_drawcall_t* drawcall);\n\nstatic glm_brushmodel_drawcall_t* drawcalls;\nstatic int maximum_drawcalls = 0;\nstatic int current_drawcall = 0;\n\nstatic void GLM_CheckDrawCallSize(void)\n{\n\tif (current_drawcall >= maximum_drawcalls) {\n\t\tmaximum_drawcalls += GLM_DRAWCALL_INCREMENT;\n\t\tdrawcalls = Q_realloc(drawcalls, sizeof(glm_brushmodel_drawcall_t) * maximum_drawcalls);\n\t}\n}\n\n#define DRAW_DETAIL_TEXTURES       (1 <<  0)\n#define DRAW_CAUSTIC_TEXTURES      (1 <<  1)\n#define DRAW_LUMA_TEXTURES         (1 <<  2)\n#define DRAW_SKYBOX                (1 <<  3)\n#define DRAW_SKYDOME               (1 <<  4)\n#define DRAW_FLATFLOORS            (1 <<  5)\n#define DRAW_FLATWALLS             (1 <<  6)\n#define DRAW_LUMA_TEXTURES_FB      (1 <<  7)\n#define DRAW_TEXTURELESS           (1 <<  8)\n#define DRAW_GEOMETRY              (1 <<  9)\n#define DRAW_DRAWFLAT_NORMAL       (1 << 10)\n#define DRAW_DRAWFLAT_TINTED       (1 << 11)\n#define DRAW_DRAWFLAT_BRIGHT       (1 << 12)\n#define DRAW_ALPHATESTED           (1 << 13)\n#define DRAW_SKYWIND               (1 << 14)\n#define DRAW_INSTANCED             (1 << 15)\n\nstatic int material_samplers_max;\nstatic int TEXTURE_UNIT_MATERIAL; // Must always be the first non-standard texture unit\nstatic int TEXTURE_UNIT_LIGHTMAPS;\nstatic int TEXTURE_UNIT_DETAIL;\nstatic int TEXTURE_UNIT_CAUSTICS;\nstatic int TEXTURE_UNIT_SKYBOX;\nstatic int TEXTURE_UNIT_SKYDOME_TEXTURE;\nstatic int TEXTURE_UNIT_SKYDOME_CLOUD_TEXTURE;\n\n// We re-compile whenever certain options change, to save texture bindings/lookups\nstatic qbool GLM_CompileDrawWorldProgramImpl(r_program_id program_id, qbool alpha_test)\n{\n\textern cvar_t gl_lumatextures;\n\textern cvar_t gl_textureless;\n\textern cvar_t gl_outline;\n\n\tqbool detail_textures = gl_detail.integer && R_TextureReferenceIsValid(detailtexture);\n\tqbool caustic_textures = r_refdef2.drawCaustics;\n\tqbool luma_textures = gl_lumatextures.integer && r_refdef2.allow_lumas;\n\tqbool skybox = r_skyboxloaded && !r_fastsky.integer;\n\tqbool skydome = !skybox && !r_fastsky.integer && R_TextureReferenceIsValid(solidskytexture);\n\tqbool skywind = skybox && Skywind_Active();\n\n\tint drawworld_desiredOptions =\n\t\t(detail_textures ? DRAW_DETAIL_TEXTURES : 0) |\n\t\t(caustic_textures ? DRAW_CAUSTIC_TEXTURES : 0) |\n\t\t(luma_textures ? DRAW_LUMA_TEXTURES : 0) |\n\t\t(gl_fb_bmodels.integer ? DRAW_LUMA_TEXTURES_FB : 0) |\n\t\t(skybox ? DRAW_SKYBOX : (skydome ? DRAW_SKYDOME : 0)) |\n\t\t(r_drawflat_mode.integer == 0 && r_drawflat.integer != 0 ? DRAW_DRAWFLAT_NORMAL : 0) |\n\t\t(r_drawflat_mode.integer == 1 && r_drawflat.integer != 0 ? DRAW_DRAWFLAT_TINTED : 0) |\n\t\t(r_drawflat_mode.integer == 2 && r_drawflat.integer != 0 ? DRAW_DRAWFLAT_BRIGHT : 0) |\n\t\t(r_drawflat.integer == 1 || r_drawflat.integer == 2 ? DRAW_FLATFLOORS : 0) |\n\t\t(r_drawflat.integer == 1 || r_drawflat.integer == 3 ? DRAW_FLATWALLS : 0) |\n\t\t(gl_textureless.integer ? DRAW_TEXTURELESS : 0) |\n\t\t((gl_outline.integer & 2) && GL_VersionAtLeast(4, 3) ? DRAW_GEOMETRY : 0) |\n\t\t(alpha_test ? DRAW_ALPHATESTED : 0) |\n\t\t(skywind ? DRAW_SKYWIND : 0) |\n\t\t(GL_Supported(R_SUPPORT_INSTANCED_RENDERING) ? DRAW_INSTANCED : 0);\n\n\tif (R_ProgramRecompileNeeded(program_id, drawworld_desiredOptions)) {\n\t\tstatic char included_definitions[2048];\n\t\tint samplers = 0;\n\n\t\tmemset(included_definitions, 0, sizeof(included_definitions));\n\t\tif (detail_textures) {\n\t\t\tTEXTURE_UNIT_DETAIL = samplers++;\n\n\t\t\tstrlcat(included_definitions, \"#define DRAW_DETAIL_TEXTURES\\n\", sizeof(included_definitions));\n\t\t\tstrlcat(included_definitions, va(\"#define SAMPLER_DETAIL_TEXTURE %d\\n\", TEXTURE_UNIT_DETAIL), sizeof(included_definitions));\n\t\t}\n\t\tif (caustic_textures) {\n\t\t\tTEXTURE_UNIT_CAUSTICS = samplers++;\n\n\t\t\tstrlcat(included_definitions, \"#define DRAW_CAUSTIC_TEXTURES\\n\", sizeof(included_definitions));\n\t\t\tstrlcat(included_definitions, va(\"#define SAMPLER_CAUSTIC_TEXTURE %d\\n\", TEXTURE_UNIT_CAUSTICS), sizeof(included_definitions));\n\t\t}\n\t\tif (r_drawflat.integer != 1 || r_drawflat_mode.integer != 0) {\n\t\t\tif (luma_textures) {\n\t\t\t\tstrlcat(included_definitions, \"#define DRAW_LUMA_TEXTURES\\n\", sizeof(included_definitions));\n\t\t\t}\n\t\t\tif (gl_fb_bmodels.integer) {\n\t\t\t\tstrlcat(included_definitions, \"#define DRAW_LUMA_TEXTURES_FB\\n\", sizeof(included_definitions));\n\t\t\t}\n\t\t}\n\t\tif (drawworld_desiredOptions & DRAW_DRAWFLAT_NORMAL) {\n\t\t\tstrlcat(included_definitions, \"#define DRAW_DRAWFLAT_NORMAL\\n\", sizeof(included_definitions));\n\t\t}\n\t\telse if (drawworld_desiredOptions & DRAW_DRAWFLAT_TINTED) {\n\t\t\tstrlcat(included_definitions, \"#define DRAW_DRAWFLAT_TINTED\\n\", sizeof(included_definitions));\n\t\t}\n\t\telse if (drawworld_desiredOptions & DRAW_DRAWFLAT_BRIGHT) {\n\t\t\tstrlcat(included_definitions, \"#define DRAW_DRAWFLAT_BRIGHT\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (drawworld_desiredOptions & DRAW_ALPHATESTED) {\n\t\t\tstrlcat(included_definitions, \"#define DRAW_ALPHATEST_ENABLED\\n\", sizeof(included_definitions));\n\t\t}\n\n\t\tif (skybox) {\n\t\t\tTEXTURE_UNIT_SKYBOX = samplers++;\n\n\t\t\tstrlcat(included_definitions, \"#define DRAW_SKYBOX\\n\", sizeof(included_definitions));\n\t\t\tif (skywind) {\n\t\t\t\tstrlcat(included_definitions, \"#define DRAW_SKYWIND\\n\", sizeof(included_definitions));\n\t\t\t}\n\t\t\tstrlcat(included_definitions, va(\"#define SAMPLER_SKYBOX_TEXTURE %d\\n\", TEXTURE_UNIT_SKYBOX), sizeof(included_definitions));\n\n\t\t}\n\t\telse if (skydome) {\n\t\t\tTEXTURE_UNIT_SKYDOME_TEXTURE = samplers++;\n\t\t\tTEXTURE_UNIT_SKYDOME_CLOUD_TEXTURE = samplers++;\n\n\t\t\tstrlcat(included_definitions, \"#define DRAW_SKYDOME\\n\", sizeof(included_definitions));\n\t\t\tstrlcat(included_definitions, va(\"#define SAMPLER_SKYDOME_TEXTURE %d\\n\", TEXTURE_UNIT_SKYDOME_TEXTURE), sizeof(included_definitions));\n\t\t\tstrlcat(included_definitions, va(\"#define SAMPLER_SKYDOME_CLOUDTEXTURE %d\\n\", TEXTURE_UNIT_SKYDOME_CLOUD_TEXTURE), sizeof(included_definitions));\n\t\t}\n\t\tif (gl_outline.integer & 2 && GL_VersionAtLeast(4, 3)) {\n\t\t\tstrlcat(included_definitions, \"#define DRAW_GEOMETRY\\n\", sizeof(included_definitions));\n\t\t}\n\t\tTEXTURE_UNIT_LIGHTMAPS = samplers++;\n\t\tstrlcat(included_definitions, va(\"#define SAMPLER_LIGHTMAP_TEXTURE %d\\n\", TEXTURE_UNIT_LIGHTMAPS), sizeof(included_definitions));\n\t\tTEXTURE_UNIT_MATERIAL = samplers++;\n\t\tstrlcat(included_definitions, va(\"#define SAMPLER_MATERIAL_TEXTURE_START %d\\n\", TEXTURE_UNIT_MATERIAL), sizeof(included_definitions));\n\t\tmaterial_samplers_max = min(glConfig.texture_units - TEXTURE_UNIT_MATERIAL, MAXIMUM_MATERIAL_SAMPLERS);\n\t\tstrlcat(included_definitions, va(\"#define SAMPLER_MATERIAL_TEXTURE_COUNT %d\\n\", material_samplers_max), sizeof(included_definitions));\n\t\tstrlcat(included_definitions, va(\"#define MAX_INSTANCEID %d\\n\", MAX_WORLDMODEL_BATCH), sizeof(included_definitions));\n\t\tif (r_drawflat.integer == 1 || r_drawflat.integer == 2) {\n\t\t\tstrlcat(included_definitions, \"#define DRAW_FLATFLOORS\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (r_drawflat.integer == 1 || r_drawflat.integer == 3) {\n\t\t\tstrlcat(included_definitions, \"#define DRAW_FLATWALLS\\n\", sizeof(included_definitions));\n\t\t}\n\t\tif (gl_textureless.integer) {\n\t\t\tstrlcat(included_definitions, \"#define DRAW_TEXTURELESS\\n\", sizeof(included_definitions));\n\t\t}\n\n\t\t// Initialise program for drawing image\n\t\tR_ProgramCompileWithInclude(program_id, included_definitions);\n\n\t\t// Set sampler uniforms for GL < 4.2 (no layout(binding=N) support)\n\t\tif (!GL_VersionAtLeast(4, 2) && R_ProgramReady(program_id)) {\n\t\t\tint base = alpha_test ? r_program_uniform_brushmodel_at_detailtex : r_program_uniform_brushmodel_detailtex;\n\t\t\tR_ProgramUse(program_id);\n\t\t\tif (detail_textures) {\n\t\t\t\tR_ProgramUniform1i(base + 0, TEXTURE_UNIT_DETAIL);\n\t\t\t}\n\t\t\tif (caustic_textures) {\n\t\t\t\tR_ProgramUniform1i(base + 1, TEXTURE_UNIT_CAUSTICS);\n\t\t\t}\n\t\t\tif (skybox) {\n\t\t\t\tR_ProgramUniform1i(base + 2, TEXTURE_UNIT_SKYBOX);\n\t\t\t}\n\t\t\telse if (skydome) {\n\t\t\t\tR_ProgramUniform1i(base + 3, TEXTURE_UNIT_SKYDOME_TEXTURE);\n\t\t\t\tR_ProgramUniform1i(base + 4, TEXTURE_UNIT_SKYDOME_CLOUD_TEXTURE);\n\t\t\t}\n\t\t\tR_ProgramUniform1i(base + 5, TEXTURE_UNIT_LIGHTMAPS);\n\t\t\tR_ProgramUniform1iArrayBase(base + 6, material_samplers_max, TEXTURE_UNIT_MATERIAL);\n\t\t}\n\n\t\tR_ProgramSetCustomOptions(program_id, drawworld_desiredOptions);\n\t}\n\n\tif (!R_BufferReferenceIsValid(r_buffer_brushmodel_drawcall_data)) {\n\t\tbuffers.Create(r_buffer_brushmodel_drawcall_data, buffertype_storage, \"ssbo_worldcvars\", sizeof(drawcalls[0].calls) * GLM_DRAWCALL_INCREMENT, NULL, bufferusage_once_per_frame);\n\t}\n\tif (!R_BufferReferenceIsValid(r_buffer_brushmodel_worldsamplers_ssbo)) {\n\t\tbuffers.Create(r_buffer_brushmodel_worldsamplers_ssbo, buffertype_storage, \"ssbo_worldsamplers\", sizeof(drawcalls[0].mappings) * GLM_DRAWCALL_INCREMENT, NULL, bufferusage_once_per_frame);\n\t}\n\n\tif (!R_BufferReferenceIsValid(r_buffer_brushmodel_drawcall_indirect)) {\n\t\tbuffers.Create(r_buffer_brushmodel_drawcall_indirect, buffertype_indirect, \"world-indirect\", sizeof(drawcalls[0].worldmodel_requests) * GLM_DRAWCALL_INCREMENT, NULL, bufferusage_once_per_frame);\n\t}\n\n\treturn R_ProgramReady(program_id);\n}\n\nqbool GLM_CompileDrawWorldProgram(void)\n{\n\treturn GLM_CompileDrawWorldProgramImpl(r_program_brushmodel, false);\n}\n\nqbool GLM_CompileDrawWorldProgramAlphaTested(void)\n{\n\treturn GLM_CompileDrawWorldProgramImpl(r_program_brushmodel_alphatested, true);\n}\n\nstatic glm_worldmodel_req_t* GLM_CurrentRequest(void)\n{\n\tglm_brushmodel_drawcall_t* call = &drawcalls[current_drawcall];\n\n\treturn &call->worldmodel_requests[call->batch_count - 1];\n}\n\nstatic void GL_StartWorldBatch(qbool alphatest_enabled)\n{\n\ttexture_ref std_textures[MAX_STANDARD_TEXTURES];\n\tr_program_id program_id = (alphatest_enabled ? r_program_brushmodel_alphatested : r_program_brushmodel);\n\tint options = R_ProgramCustomOptions(program_id);\n\n\tR_ProgramUse(program_id);\n\tR_BindVertexArray(vao_brushmodel);\n\n\t// Bind standard textures\n\tstd_textures[TEXTURE_UNIT_LIGHTMAPS] = GLM_LightmapArray();\n\tif (options & DRAW_DETAIL_TEXTURES) {\n\t\tstd_textures[TEXTURE_UNIT_DETAIL] = detailtexture;\n\t}\n\tif (options & DRAW_CAUSTIC_TEXTURES) {\n\t\tstd_textures[TEXTURE_UNIT_CAUSTICS] = underwatertexture;\n\t}\n\tif (options & DRAW_SKYBOX) {\n\t\textern texture_ref skybox_cubeMap;\n\n\t\tstd_textures[TEXTURE_UNIT_SKYBOX] = skybox_cubeMap;\n\t}\n\telse if (options & DRAW_SKYDOME) {\n\t\textern texture_ref solidskytexture, alphaskytexture;\n\n\t\tstd_textures[TEXTURE_UNIT_SKYDOME_TEXTURE] = solidskytexture;\n\t\tstd_textures[TEXTURE_UNIT_SKYDOME_CLOUD_TEXTURE] = alphaskytexture;\n\t}\n\trenderer.TextureUnitMultiBind(0, TEXTURE_UNIT_MATERIAL, std_textures);\n}\n\nvoid GLM_EnterBatchedWorldRegion(void)\n{\n\tGLM_CompileDrawWorldProgram();\n\tGLM_CompileDrawWorldProgramAlphaTested();\n\n\tcurrent_drawcall = 0;\n\tindex_count = 0;\n\tGLM_CheckDrawCallSize();\n\tmemset(drawcalls, 0, sizeof(drawcalls[0]) * maximum_drawcalls);\n}\n\nstatic qbool GLM_AssignTexture(int texture_num, texture_t* texture)\n{\n\tglm_worldmodel_req_t* req = GLM_CurrentRequest();\n\tint index = req->samplerMappingBase + texture_num;\n\tint i;\n\tint sampler = -1;\n\tglm_brushmodel_drawcall_t* drawcall = &drawcalls[current_drawcall];\n\n\tif (index >= sizeof(drawcall->mappings) / sizeof(drawcall->mappings[0])) {\n\t\treturn false;\n\t}\n\n\tfor (i = 0; i < drawcall->material_samplers; ++i) {\n\t\tif (R_TextureReferenceEqual(texture->gl_texture_array, drawcall->allocated_samplers[i])) {\n\t\t\tsampler = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (sampler < 0) {\n\t\tif (drawcall->material_samplers >= material_samplers_max) {\n\t\t\tdrawcall = GL_PrepareWorldModelBatch(drawcall->type);\n\t\t}\n\n\t\tsampler = drawcall->material_samplers++;\n\t\tdrawcall->allocated_samplers[sampler] = texture->gl_texture_array;\n\t}\n\n\tdrawcall->mappings[index].samplerIndex = sampler;\n\tdrawcall->mappings[index].arrayIndex = texture->gl_texture_index;\n\tdrawcall->mappings[index].flags = R_TextureReferenceIsValid(texture->fb_texturenum) && texture->isLumaTexture ? EZQ_SURFACE_HAS_LUMA : 0;\n\tdrawcall->mappings[index].flags |= R_TextureReferenceIsValid(texture->fb_texturenum) && !texture->isLumaTexture ? EZQ_SURFACE_HAS_FB : 0;\n\tdrawcall->mappings[index].flags |= R_TextureReferenceIsValid(texture->fb_texturenum) && texture->isAlphaTested ? EZQ_SURFACE_ALPHATEST : 0;\n\tdrawcall->mappings[index].flags |= texture->isLitTurb ? EZQ_SURFACE_LIT_TURB : 0;\n\treturn true;\n}\n\nstatic qbool GLM_DuplicatePreviousRequest(model_t* model, float alpha, int num_textures, int first_texture, qbool polygonOffset, qbool caustics)\n{\n\tglm_worldmodel_req_t* req;\n\tqbool worldmodel = model ? model->isworldmodel : false;\n\tint flags = (caustics ? EZQ_SURFACE_UNDERWATER : 0);\n\tglm_brushmodel_drawcall_t* drawcall = &drawcalls[current_drawcall];\n\n\t// If user has switched off caustics (or no texture), ignore\n\tif (caustics) {\n\t\tcaustics &= ((R_ProgramCustomOptions(r_program_brushmodel) & DRAW_CAUSTIC_TEXTURES) == DRAW_CAUSTIC_TEXTURES);\n\t}\n\n\t// See if previous batch has same texture & matrix, if so just continue\n\tif (drawcall->batch_count) {\n\t\treq = &drawcall->worldmodel_requests[drawcall->batch_count - 1];\n\n\t\tif (model == req->model && req->samplerMappingCount == num_textures && req->firstTexture == first_texture && drawcall->batch_count < MAX_WORLDMODEL_BATCH) {\n\t\t\t// Duplicate details from previous request, but with different matrix\n\t\t\tglm_worldmodel_req_t* newreq = &drawcall->worldmodel_requests[drawcall->batch_count];\n\n\t\t\tmemcpy(newreq, req, sizeof(*newreq));\n\t\t\tR_GetModelviewMatrix(newreq->mvMatrix);\n\t\t\tnewreq->alpha = alpha;\n\t\t\tnewreq->flags = flags;\n\t\t\tnewreq->polygonOffset = polygonOffset;\n\t\t\tnewreq->worldmodel = worldmodel;\n\t\t\tnewreq->baseInstance = drawcall->batch_count;\n\t\t\t++drawcall->batch_count;\n\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nstatic glm_worldmodel_req_t* GLM_NextBatchRequest(model_t* model, float alpha, int num_textures, int first_texture, qbool polygonOffset, qbool caustics, qbool allow_duplicate, qbool isAlphaTested)\n{\n\tglm_worldmodel_req_t* req;\n\tqbool worldmodel = model ? model->isworldmodel : false;\n\tint flags = (caustics ? EZQ_SURFACE_UNDERWATER : 0);\n\tglm_brushmodel_drawcall_t* drawcall = &drawcalls[current_drawcall];\n\tfloat mvMatrix[16];\n\n\tglm_brushmodel_drawcall_type desired_type = alpha == 0.0f ? opaque_world : alpha_surfaces;\n\n\tR_GetModelviewMatrix(mvMatrix);\n\n\t// If user has switched off caustics (or no texture), ignore\n\tif (caustics) {\n\t\tcaustics &= ((R_ProgramCustomOptions(r_program_brushmodel) & DRAW_CAUSTIC_TEXTURES) == DRAW_CAUSTIC_TEXTURES);\n\t}\n\n\tif (drawcall->type != desired_type) {\n\t\tdrawcall = GL_PrepareWorldModelBatch(desired_type);\n\t}\n\telse if (drawcall->batch_count && drawcall->type == opaque_world) {\n\t\t// See if previous request has same texture & matrix, if so just continue\n\t\t// as long as drawcall is not alpha as such requests must be drawn in the\n\t\t// predetermined order.\n\t\treq = &drawcall->worldmodel_requests[drawcall->batch_count - 1];\n\n\t\tif (allow_duplicate && model == req->model && req->samplerMappingCount == num_textures && req->firstTexture == first_texture && drawcall->batch_count < MAX_WORLDMODEL_BATCH && isAlphaTested == req->isAlphaTested) {\n\t\t\t// Duplicate details from previous request, but with different matrix\n\t\t\tglm_worldmodel_req_t* newreq = &drawcall->worldmodel_requests[drawcall->batch_count];\n\n\t\t\tmemcpy(newreq, req, sizeof(*newreq));\n\t\t\tR_GetModelviewMatrix(newreq->mvMatrix);\n\t\t\tnewreq->alpha = alpha;\n\t\t\tnewreq->flags = flags;\n\t\t\tnewreq->polygonOffset = polygonOffset;\n\t\t\tnewreq->worldmodel = worldmodel;\n\t\t\tnewreq->baseInstance = drawcall->batch_count;\n\t\t\t++drawcall->batch_count;\n\n\t\t\treturn NULL;\n\t\t}\n\n\t\t// Try and continue the previous batch\n\t\tif (worldmodel == req->worldmodel && !memcmp(req->mvMatrix, mvMatrix, sizeof(req->mvMatrix)) && polygonOffset == req->polygonOffset && req->flags == flags && req->isAlphaTested == isAlphaTested) {\n\t\t\tif (num_textures == 0) {\n\t\t\t\t// We don't care about materials, so can draw with previous batch\n\t\t\t\treturn req;\n\t\t\t}\n\t\t\telse if (req->samplerMappingCount == 0) {\n\t\t\t\t// Previous block didn't care about materials, allocate now\n\t\t\t\tif (drawcall->sampler_mappings < MAX_SAMPLER_MAPPINGS) {\n\t\t\t\t\treq->samplerMappingBase = drawcall->sampler_mappings - first_texture;\n\t\t\t\t\treq->samplerMappingCount = min(num_textures, MAX_SAMPLER_MAPPINGS - drawcall->sampler_mappings);\n\t\t\t\t\treq->firstTexture = first_texture;\n\t\t\t\t\tdrawcall->sampler_mappings += req->samplerMappingCount;\n\t\t\t\t\treturn req;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (drawcall->sampler_mappings >= MAX_SAMPLER_MAPPINGS || drawcall->batch_count >= MAX_WORLDMODEL_BATCH) {\n\t\tdrawcall = GL_PrepareWorldModelBatch(drawcall->type);\n\t}\n\n\treq = &drawcall->worldmodel_requests[drawcall->batch_count];\n\n\t// If matrix list was full previously, will be okay now\n\tmemcpy(req->mvMatrix, mvMatrix, sizeof(req->mvMatrix));\n\treq->alpha = alpha;\n\treq->samplerMappingBase = drawcall->sampler_mappings - first_texture;\n\treq->samplerMappingCount = min(num_textures, MAX_SAMPLER_MAPPINGS - drawcall->sampler_mappings);\n\treq->flags = flags;\n\treq->polygonOffset = polygonOffset;\n\treq->worldmodel = worldmodel;\n\treq->model = model;\n\treq->firstTexture = first_texture;\n\treq->isAlphaTested = isAlphaTested;\n\n\treq->count = 0;\n\treq->instanceCount = 1;\n\treq->firstIndex = index_count;\n\treq->baseVertex = 0;\n\treq->baseInstance = drawcall->batch_count;\n\n\tdrawcall->sampler_mappings += req->samplerMappingCount;\n\t++drawcall->batch_count;\n\treturn req;\n}\n\nvoid GLM_DrawWaterSurfaces(void)\n{\n\textern msurface_t* waterchain;\n\tmsurface_t* surf;\n\tglm_worldmodel_req_t* req = NULL;\n\tfloat alpha;\n\tint v;\n\n\tif (!waterchain) {\n\t\treturn;\n\t}\n\n\talpha = r_refdef2.wateralpha;\n\n\t// Waterchain has list of alpha-blended surfaces\n\tR_TraceEnterNamedRegion(__func__);\n\n\tfor (surf = waterchain; surf; surf = surf->texturechain) {\n\t\tglpoly_t* poly;\n\t\ttexture_t* tex = R_TextureAnimation(NULL, surf->texinfo->texture);\n\n\t\treq = GLM_NextBatchRequest(NULL, alpha, 1, surf->texinfo->miptex, false, false, false, tex->isAlphaTested);\n\t\tGLM_AssignTexture(surf->texinfo->miptex, tex);\n\t\tfor (poly = surf->polys; poly; poly = poly->next) {\n\t\t\tint newVerts = poly->numverts;\n\n\t\t\tif (index_count + 1 + newVerts > modelIndexMaximum) {\n\t\t\t\t// Should never happen now, we allocate enough space\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (req->count) {\n\t\t\t\tmodelIndexes[index_count++] = ~(GLuint)0;\n\t\t\t\treq->count++;\n\t\t\t}\n\n\t\t\tfor (v = 0; v < newVerts; ++v) {\n\t\t\t\tmodelIndexes[index_count++] = poly->vbo_start + v;\n\t\t\t\treq->count++;\n\t\t\t}\n\t\t}\n\t}\n\n\tR_TraceLeaveNamedRegion();\n\n\twaterchain = NULL;\n}\n\nstatic glm_worldmodel_req_t* GLM_DrawFlatChain(glm_worldmodel_req_t* req, msurface_t* surf)\n{\n\tglpoly_t* poly;\n\tint v;\n\n\twhile (surf) {\n\t\tfor (poly = surf->polys; poly; poly = poly->next) {\n\t\t\tint newVerts = poly->numverts;\n\n\t\t\tif (index_count + 1 + newVerts > modelIndexMaximum) {\n\t\t\t\t// Should never happen now\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (req->count) {\n\t\t\t\tmodelIndexes[index_count++] = ~(GLuint)0;\n\t\t\t\treq->count++;\n\t\t\t}\n\n\t\t\tfor (v = 0; v < newVerts; ++v) {\n\t\t\t\tmodelIndexes[index_count++] = poly->vbo_start + v;\n\t\t\t\treq->count++;\n\t\t\t}\n\t\t}\n\n\t\tsurf = surf->drawflatchain;\n\t}\n\n\treturn req;\n}\n\nstatic glm_worldmodel_req_t* GLM_DrawTexturedChain(glm_worldmodel_req_t* req, msurface_t* surf)\n{\n\tglpoly_t* poly;\n\tint v;\n\n\twhile (surf) {\n\t\tfor (poly = surf->polys; poly; poly = poly->next) {\n\t\t\tint newVerts = poly->numverts;\n\n\t\t\tif (index_count + 1 + newVerts > modelIndexMaximum) {\n\t\t\t\t// Should never happen now\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (req->count) {\n\t\t\t\tmodelIndexes[index_count++] = ~(GLuint)0;\n\t\t\t\treq->count++;\n\t\t\t}\n\n\t\t\tfor (v = 0; v < newVerts; ++v) {\n\t\t\t\tmodelIndexes[index_count++] = poly->vbo_start + v;\n\t\t\t\treq->count++;\n\t\t\t}\n\t\t}\n\n\t\tsurf = surf->texturechain;\n\t}\n\n\treturn req;\n}\n\nqbool GLM_CompilePostProcessVAO(void);\n\nqbool GLM_CompileSimple3dProgram(void)\n{\n\tif (R_ProgramRecompileNeeded(r_program_simple3d, 0)) {\n\t\tR_ProgramCompile(r_program_simple3d);\n\t}\n\n\treturn R_ProgramReady(r_program_simple3d) && GLM_CompilePostProcessVAO();\n}\n\nstatic glm_brushmodel_drawcall_t* GL_PrepareWorldModelBatch(glm_brushmodel_drawcall_type type)\n{\n\tglm_brushmodel_drawcall_t* prev = &drawcalls[current_drawcall];\n\tglm_brushmodel_drawcall_t* current;\n\n\tif (prev->batch_count == 0)\t{\n\t\tprev->type = type;\n\t\treturn prev;\n\t}\n\n\tcurrent_drawcall++;\n\n\tGLM_CheckDrawCallSize();\n\n\tcurrent = &drawcalls[current_drawcall];\n\n\tmemset(current, 0, sizeof(*current));\n\tcurrent->type = type;\n\treturn current;\n}\n\nvoid GLM_PrepareWorldModelBatch(void)\n{\n\tGLintptr drawOffset = 0;\n\tGLintptr indexOffset = buffers.BufferOffset(r_buffer_brushmodel_index_data) / sizeof(modelIndexes[0]);\n\tint samplerMappingOffset = 0;\n\tint batchOffset = 0;\n\tint draw, i;\n\n\tR_TraceEnterNamedRegion(__func__);\n\tbuffers.Update(r_buffer_brushmodel_index_data, sizeof(modelIndexes[0]) * index_count, modelIndexes);\n\tbuffers.EnsureSize(r_buffer_brushmodel_drawcall_indirect, sizeof(drawcalls[0].worldmodel_requests) * maximum_drawcalls);\n\tbuffers.EnsureSize(r_buffer_brushmodel_drawcall_data, sizeof(drawcalls[0].calls) * maximum_drawcalls);\n\tbuffers.EnsureSize(r_buffer_brushmodel_worldsamplers_ssbo, sizeof(drawcalls[0].mappings) * maximum_drawcalls);\n\n\tbuffers.BindRange(r_buffer_brushmodel_drawcall_data, EZQ_STORAGE_BLOCK_BINDING(EZQ_GL_BINDINGPOINT_BRUSHMODEL_DRAWDATA), buffers.BufferOffset(r_buffer_brushmodel_drawcall_data), sizeof(drawcalls[0].calls) * maximum_drawcalls);\n\tbuffers.BindRange(r_buffer_brushmodel_worldsamplers_ssbo, EZQ_STORAGE_BLOCK_BINDING(EZQ_GL_BINDINGPOINT_BRUSHMODEL_SAMPLERS), buffers.BufferOffset(r_buffer_brushmodel_worldsamplers_ssbo), sizeof(drawcalls[0].mappings) * maximum_drawcalls);\n\n\tfor (draw = 0; draw <= current_drawcall; ++draw) {\n\t\tglm_brushmodel_drawcall_t* drawcall = &drawcalls[draw];\n\n\t\tif (!drawcall->batch_count) {\n\t\t\tbreak;\n\t\t}\n\n\t\tGL_SortDrawCalls(drawcall);\n\n\t\tfor (i = 0; i < drawcall->batch_count; ++i) {\n\t\t\tdrawcall->calls[i].samplerBase += samplerMappingOffset;\n\t\t\tdrawcall->worldmodel_requests[i].baseInstance += batchOffset;\n\t\t\tdrawcall->worldmodel_requests[i].firstIndex += indexOffset;\n\t\t}\n\n\t\tdrawcall->indirectDrawOffset = drawOffset;\n\t\tbuffers.UpdateSection(r_buffer_brushmodel_drawcall_indirect, drawOffset, sizeof(drawcall->worldmodel_requests[0]) * drawcall->batch_count, &drawcall->worldmodel_requests);\n\t\tdrawOffset += sizeof(drawcall->worldmodel_requests[0]) * drawcall->batch_count;\n\n\t\tbuffers.UpdateSection(r_buffer_brushmodel_drawcall_data, sizeof(drawcall->calls[0]) * batchOffset, sizeof(drawcall->calls[0]) * drawcall->batch_count, &drawcall->calls);\n\t\tbuffers.UpdateSection(r_buffer_brushmodel_worldsamplers_ssbo, sizeof(drawcall->mappings[0]) * samplerMappingOffset, sizeof(drawcall->mappings[0]) * drawcall->sampler_mappings, &drawcall->mappings);\n\n\t\tbatchOffset += drawcall->batch_count;\n\t\tsamplerMappingOffset += drawcall->sampler_mappings;\n\t}\n\n\tR_TraceLeaveNamedRegion();\n}\n\nstatic void GLM_DrawWorldExecuteCalls(glm_brushmodel_drawcall_t* drawcall, uintptr_t offset, int begin, int count)\n{\n\tint i;\n\tqbool prev_alphaTested = false;\n\tqbool no_base_instance = !GL_Supported(R_SUPPORT_INSTANCED_RENDERING);\n\n\tfor (i = begin; i < begin + count; ++i) {\n\t\tglm_worldmodel_req_t* req = &drawcall->worldmodel_requests[i];\n\t\tint batchCount = 1;\n\n\t\tif (req->isAlphaTested != prev_alphaTested) {\n\t\t\tif (req->isAlphaTested) {\n\t\t\t\tR_ProgramUse(r_program_brushmodel_alphatested);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tR_ProgramUse(r_program_brushmodel);\n\t\t\t}\n\t\t\tprev_alphaTested = req->isAlphaTested;\n\t\t}\n\n\t\tif (no_base_instance) {\n\t\t\t// GL 4.1: no baseInstance support, _instanceId is always 0\n\t\t\t// Use instanceOffset uniform so shader reads drawInfo[0 + baseInstance]\n\t\t\t// Set on both programs since either may be active (alpha surfaces start\n\t\t\t// with alphatested program but prev_alphaTested tracks request state)\n\t\t\tR_ProgramUniform1i(r_program_uniform_brushmodel_instanceOffset, req->baseInstance);\n\t\t\tR_ProgramUniform1i(r_program_uniform_brushmodel_alphatested_instanceOffset, req->baseInstance);\n\t\t\tGL_DrawElementsBaseVertex(\n\t\t\t\tGL_TRIANGLE_STRIP,\n\t\t\t\treq->count,\n\t\t\t\tGL_UNSIGNED_INT,\n\t\t\t\t(void*)(req->firstIndex * sizeof(GLuint)),\n\t\t\t\treq->baseVertex\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\twhile (i + batchCount < begin + count && drawcall->worldmodel_requests[i + batchCount].isAlphaTested == req->isAlphaTested) {\n\t\t\t++batchCount;\n\t\t}\n\n\t\tif (batchCount == 1) {\n\t\t\tGL_DrawElementsInstancedBaseVertexBaseInstance(\n\t\t\t\tGL_TRIANGLE_STRIP,\n\t\t\t\treq->count,\n\t\t\t\tGL_UNSIGNED_INT,\n\t\t\t\t(void*)(req->firstIndex * sizeof(GLuint)),\n\t\t\t\treq->instanceCount,\n\t\t\t\treq->baseVertex,\n\t\t\t\treq->baseInstance\n\t\t\t);\n\t\t}\n\t\telse {\n\t\t\tGL_MultiDrawElementsIndirect(GL_TRIANGLE_STRIP, GL_UNSIGNED_INT, (void*)(offset + (i - begin) * sizeof(drawcall->worldmodel_requests[0])), batchCount, sizeof(drawcall->worldmodel_requests[0]));\n\t\t\ti += batchCount - 1;\n\t\t}\n\t}\n}\n\nvoid GLM_DrawWorldModelBatch(glm_brushmodel_drawcall_type type)\n{\n\tint draw;\n\tqbool first = true;\n\tunsigned int extra_offset = 0;\n\tqbool alphablended = (type == alpha_surfaces);\n\n\tfor (draw = 0; draw <= current_drawcall; ++draw) {\n\t\tglm_brushmodel_drawcall_t* drawcall = &drawcalls[draw];\n\n\t\tif (!drawcall->batch_count || drawcall->type != type) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (first) {\n\t\t\tR_TraceEnterNamedRegion(__func__);\n\t\t\tGL_StartWorldBatch(alphablended);\n\t\t\tbuffers.Bind(r_buffer_brushmodel_index_data);\n\t\t\tbuffers.Bind(r_buffer_brushmodel_drawcall_indirect);\n\t\t\textra_offset = buffers.BufferOffset(r_buffer_brushmodel_drawcall_indirect);\n\n\t\t\tfirst = false;\n\t\t}\n\n\t\tR_TraceEnterNamedRegion(va(\"WorldModelBatch(%d/%d)\", draw, current_drawcall));\n\n\t\t// Bind texture units\n\t\trenderer.TextureUnitMultiBind(TEXTURE_UNIT_MATERIAL, drawcall->material_samplers, drawcall->allocated_samplers);\n\n\t\tif (drawcall->polygonOffsetSplit >= 0 && drawcall->polygonOffsetSplit < drawcall->batch_count) {\n\t\t\tuintptr_t normal_offset = drawcall->indirectDrawOffset + extra_offset;\n\t\t\tuintptr_t polygonOffset_offset = (extra_offset + drawcall->indirectDrawOffset + sizeof(drawcall->worldmodel_requests[0]) * drawcall->polygonOffsetSplit);\n\n\t\t\tif (drawcall->polygonOffsetSplit) {\n\t\t\t\tGLM_BeginDrawWorld(alphablended, false);\n\t\t\t\tGLM_DrawWorldExecuteCalls(drawcall, normal_offset, 0, drawcall->polygonOffsetSplit);\n\t\t\t}\n\n\t\t\tGLM_BeginDrawWorld(alphablended, true);\n\t\t\tGLM_DrawWorldExecuteCalls(drawcall, polygonOffset_offset, drawcall->polygonOffsetSplit, drawcall->batch_count - drawcall->polygonOffsetSplit);\n\t\t}\n\t\telse {\n\t\t\tGLM_BeginDrawWorld(alphablended, false);\n\t\t\tGLM_DrawWorldExecuteCalls(drawcall, extra_offset + drawcall->indirectDrawOffset, 0, drawcall->batch_count);\n\t\t}\n\n\t\tR_TraceLeaveNamedRegion();\n\n\t\tframeStats.subdraw_calls += drawcall->batch_count;\n\t\tdrawcall->batch_count = 0;\n\t\tdrawcall->material_samplers = 0;\n\t\tdrawcall->sampler_mappings = 0;\n\t}\n\n\tif (!first) {\n\t\tR_TraceLeaveNamedRegion();\n\t}\n}\n\nvoid GLM_DrawBrushModel(entity_t* ent, qbool polygonOffset, qbool caustics)\n{\n\tint i;\n\tglm_worldmodel_req_t* req = NULL;\n\tmodel_t* model = ent->model;\n\n\tif (GLM_DuplicatePreviousRequest(model, ent->alpha, model->last_texture_chained - model->first_texture_chained + 1, model->first_texture_chained, polygonOffset, caustics)) {\n\t\treturn;\n\t}\n\n\tif (model->drawflat_chain) {\n\t\treq = GLM_NextBatchRequest(model, ent->alpha, 0, 0, false, false, false, false);\n\n\t\treq = GLM_DrawFlatChain(req, model->drawflat_chain);\n\n\t\tmodel->drawflat_chain = NULL;\n\t}\n\n\tif (model->last_texture_chained < 0) {\n\t\treturn;\n\t}\n\n\tfor (i = model->first_texture_chained; i <= model->last_texture_chained; ++i) {\n\t\ttexture_t* tex = model->textures[i];\n\n\t\tif (!tex || !tex->loaded || !tex->texturechain || !R_TextureReferenceIsValid(tex->gl_texture_array)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\treq = GLM_NextBatchRequest(model, ent->alpha, 1, i, polygonOffset, caustics, false, tex->isAlphaTested);\n\t\ttex = R_TextureAnimation(ent, tex);\n\t\tif (!GLM_AssignTexture(i, tex)) {\n\t\t\treq = GLM_NextBatchRequest(model, ent->alpha, 1, i, polygonOffset, caustics, false, tex->isAlphaTested);\n\t\t\tGLM_AssignTexture(i, tex);\n\t\t}\n\n\t\treq = GLM_DrawTexturedChain(req, model->textures[i]->texturechain);\n\t}\n}\n\nstatic int GL_DrawCallComparison(const void* lhs_, const void* rhs_)\n{\n\tconst glm_worldmodel_req_t* lhs = (glm_worldmodel_req_t*)lhs_;\n\tconst glm_worldmodel_req_t* rhs = (glm_worldmodel_req_t*)rhs_;\n\n\tif (lhs->polygonOffset && !rhs->polygonOffset) {\n\t\treturn 1;\n\t}\n\tif (!lhs->polygonOffset && rhs->polygonOffset) {\n\t\treturn -1;\n\t}\n\n\tif (lhs->isAlphaTested && !rhs->isAlphaTested) {\n\t\treturn 1;\n\t}\n\telse if (!lhs->isAlphaTested && rhs->isAlphaTested) {\n\t\treturn -1;\n\t}\n\n\treturn lhs->nonDynamicSampler - rhs->nonDynamicSampler;\n}\n\nstatic void GL_SortDrawCalls(glm_brushmodel_drawcall_t* drawcall)\n{\n\tint i;\n\n\tdrawcall->polygonOffsetSplit = drawcall->batch_count;\n\tif (drawcall->batch_count == 0) {\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < drawcall->batch_count; ++i) {\n\t\tglm_worldmodel_req_t* thisReq = &drawcall->worldmodel_requests[i];\n\t\tint mappingIndex = bound(0, thisReq->samplerMappingBase + thisReq->firstTexture, sizeof(drawcall->mappings) / sizeof(drawcall->mappings[0]) - 1);\n\t\tint sampler = drawcall->mappings[mappingIndex].samplerIndex;\n\n\t\tthisReq->nonDynamicSampler = sampler;\n\n\t\tif (thisReq->polygonOffset && drawcall->polygonOffsetSplit > i) {\n\t\t\tdrawcall->polygonOffsetSplit = i;\n\t\t}\n\t}\n\n\t// Translucent bmodels are put into requests based on their distance from view and sorting here will break that order.\n\tif (drawcall->type == opaque_world) {\n\t\tqsort(drawcall->worldmodel_requests, drawcall->batch_count, sizeof(drawcall->worldmodel_requests[0]), GL_DrawCallComparison);\n\t}\n\n\tfor (i = 0; i < drawcall->batch_count; ++i) {\n\t\tglm_worldmodel_req_t* thisReq = &drawcall->worldmodel_requests[i];\n\n\t\tdrawcall->calls[i].alpha = thisReq->alpha == 0.0f ? 1.0f : thisReq->alpha;\n\t\tdrawcall->calls[i].flags = thisReq->flags;\n\t\tmemcpy(drawcall->calls[i].modelMatrix, thisReq->mvMatrix, sizeof(drawcall->calls[i].modelMatrix));\n\t\tdrawcall->calls[i].samplerBase = thisReq->samplerMappingBase;\n\t\tdrawcall->calls[i].sampler = thisReq->nonDynamicSampler;\n\t\tthisReq->baseInstance = i;\n\t}\n}\n\nvoid GLM_DrawWorld(void)\n{\n\tentity_t ent;\n\n\tmemset(&ent, 0, sizeof(ent));\n\tent.model = cl.worldmodel;\n\n\tGLM_EnterBatchedWorldRegion();\n\tGLM_DrawBrushModel(&ent, false, false);\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_sdl.c",
    "content": "/*\n===========================================================================\nCopyright (C) 1999-2005 Id Software, Inc.\n\nThis file is part of Quake III Arena source code.\n\nQuake III Arena source code is free software; you can redistribute it\nand/or modify it under the terms of the GNU General Public License as\npublished by the Free Software Foundation; either version 2 of the License,\nor (at your option) any later version.\n\nQuake III Arena source code is distributed in the hope that it will be\nuseful, but WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Foobar; if not, write to the Free Software\nFoundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n===========================================================================\n\n*/\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include <SDL.h>\n#include \"quakedef.h\"\n#include \"gl_local.h\"\n\nstatic opengl_version_t versions[] = {\n\t{ 4, 6, false },\n\t{ 4, 5, false },\n\t{ 4, 4, false },\n\t{ 4, 3, false },\n\t{ 4, 6, true },\n\t{ 4, 5, true },\n\t{ 4, 4, true },\n\t{ 4, 3, true },\n\t{ 4, 1, true },\n};\n\nSDL_GLContext GLM_SDL_CreateContext(SDL_Window* window)\n{\n\treturn GL_SDL_CreateBestContext(window, versions, sizeof(versions) / sizeof(versions[0]));\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_sprite.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"tr_types.h\"\n#include \"gl_sprite3d.h\"\n\n// For drawing sprites in 3D space\nvoid GLM_DrawSimpleItem(model_t* m, int skin, vec3_t origin, float scale_, vec3_t up, vec3_t right)\n{\n\ttexture_ref texture_array = m->simpletexture_array[skin];\n\tint texture_index = m->simpletexture_indexes[skin];\n\tfloat scale_s = m->simpletexture_scalingS[skin];\n\tfloat scale_t = m->simpletexture_scalingT[skin];\n\tr_sprite3d_vert_t* vert;\n\n\tvert = R_Sprite3DAddEntrySpecific(SPRITE3D_ENTITIES, 4, texture_array, texture_index);\n\tif (vert) {\n\t\tR_Sprite3DRender(vert, origin, up, right, scale_, -scale_, -scale_, scale_, scale_s, scale_t, texture_index);\n\t}\n}\n\nvoid GLM_DrawSpriteModel(entity_t* e)\n{\n\tvec3_t right, up;\n\tmspriteframe_t *frame;\n\tmsprite2_t *psprite;\n\tr_sprite3d_vert_t* vert;\n\n\t// don't even bother culling, because it's just a single\n\t// polygon without a surface cache\n\tpsprite = (msprite2_t*)Mod_Extradata(e->model);\t//locate the proper data\n\tframe = R_GetSpriteFrame(e, psprite);\n\n\t// Check for null texture after s_null sprite found\n\tif (!frame || !R_TextureReferenceIsValid(frame->gl_arraynum)) {\n\t\treturn;\n\t}\n\n\tif (psprite->type == SPR_ORIENTED) {\n\t\t// bullet marks on walls\n\t\tAngleVectors(e->angles, NULL, right, up);\n\t}\n\telse if (psprite->type == SPR_FACING_UPRIGHT) {\n\t\tVectorSet(up, 0, 0, 1);\n\t\tright[0] = e->origin[1] - r_origin[1];\n\t\tright[1] = -(e->origin[0] - r_origin[0]);\n\t\tright[2] = 0;\n\t\tVectorNormalizeFast(right);\n\t}\n\telse if (psprite->type == SPR_VP_PARALLEL_UPRIGHT) {\n\t\tVectorSet(up, 0, 0, 1);\n\t\tVectorCopy(vright, right);\n\t}\n\telse {\t// normal sprite\n\t\tVectorCopy(vup, up);\n\t\tVectorCopy(vright, right);\n\t}\n\n\tvert = R_Sprite3DAddEntrySpecific(SPRITE3D_ENTITIES, 4, frame->gl_arraynum, frame->gl_arrayindex);\n\tif (vert) {\n\t\tR_Sprite3DRender(vert, e->origin, up, right, frame->up, frame->down, frame->left, frame->right, frame->gl_scalingS, frame->gl_scalingT, frame->gl_arrayindex);\n\t}\n}\n\nvoid GL_BeginDrawSprites(void)\n{\n}\n\nvoid GL_EndDrawSprites(void)\n{\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_sprite3d.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n// 3D sprites\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"gl_sprite3d.h\"\n#include \"r_sprite3d_internal.h\"\n#include \"glm_vao.h\"\n#include \"tr_types.h\"\n#include \"r_state.h\"\n#include \"glm_local.h\"\n#include \"r_program.h\"\n#include \"r_renderer.h\"\n\nstatic void GLM_Create3DSpriteVAO(void)\n{\n\tR_Sprite3DCreateVBO();\n\n\tif (!R_VertexArrayCreated(vao_3dsprites)) {\n\t\tR_GenVertexArray(vao_3dsprites);\n\n\t\tR_Sprite3DCreateIndexBuffer();\n\t\tbuffers.Bind(r_buffer_sprite_index_data);\n\n\t\t// position\n\t\tGLM_ConfigureVertexAttribPointer(vao_3dsprites, r_buffer_sprite_vertex_data, 0, 3, GL_FLOAT, GL_FALSE, sizeof(r_sprite3d_vert_t), VBO_FIELDOFFSET(r_sprite3d_vert_t, position), 0);\n\t\t// texture coordinates\n\t\tGLM_ConfigureVertexAttribPointer(vao_3dsprites, r_buffer_sprite_vertex_data, 1, 3, GL_FLOAT, GL_FALSE, sizeof(r_sprite3d_vert_t), VBO_FIELDOFFSET(r_sprite3d_vert_t, tex), 0);\n\t\t// color\n\t\tGLM_ConfigureVertexAttribPointer(vao_3dsprites, r_buffer_sprite_vertex_data, 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(r_sprite3d_vert_t), VBO_FIELDOFFSET(r_sprite3d_vert_t, color), 0);\n\n\t\tR_BindVertexArray(vao_none);\n\t}\n}\n\n#define DRAW_FOG_LINEAR       (1 << 0)\n#define DRAW_FOG_EXP          (1 << 1)\n#define DRAW_FOG_EXP2         (1 << 2)\n\n#define DRAW_FOG              (DRAW_FOG_LINEAR | DRAW_FOG_EXP | DRAW_FOG_EXP2)\n\nqbool GLM_Compile3DSpriteProgram(void)\n{\n\tif (R_ProgramRecompileNeeded(r_program_sprite3d, 0)) {\n\t\tR_ProgramCompile(r_program_sprite3d);\n\n\t\tif (!GL_VersionAtLeast(4, 2) && R_ProgramReady(r_program_sprite3d)) {\n\t\t\tR_ProgramUse(r_program_sprite3d);\n\t\t\tR_ProgramUniform1i(r_program_uniform_sprites_materialtex, 0);\n\t\t}\n\t}\n\n\treturn R_ProgramReady(r_program_sprite3d);\n}\n\nstatic qbool GLM_3DSpritesInit(void)\n{\n\t// Create program\n\tGLM_Compile3DSpriteProgram();\n\tGLM_Create3DSpriteVAO();\n\n\treturn (R_ProgramReady(r_program_sprite3d) && R_VertexArrayCreated(vao_3dsprites));\n}\n\nstatic void GLM_DrawSequentialBatch(gl_sprite3d_batch_t* batch, int index_offset, GLuint maximum_batch_size)\n{\n\tif (R_TextureReferenceIsValid(batch->texture)) {\n\t\t// All batches are the same texture, so no issues\n\t\tGL_DrawSequentialBatchImpl(batch, 0, batch->count, index_offset, maximum_batch_size);\n\t}\n\telse {\n\t\t// Group by texture usage\n\t\tint start = 0, end = 1;\n\n\t\trenderer.TextureUnitBind(0, batch->textures[start]);\n\t\tfor (end = 1; end < batch->count; ++end) {\n\t\t\tif (!R_TextureReferenceEqual(batch->textures[start], batch->textures[end])) {\n\t\t\t\tGL_DrawSequentialBatchImpl(batch, start, end, index_offset, maximum_batch_size);\n\n\t\t\t\trenderer.TextureUnitBind(0, batch->textures[end]);\n\t\t\t\tstart = end;\n\t\t\t}\n\t\t}\n\n\t\tif (end > start) {\n\t\t\tGL_DrawSequentialBatchImpl(batch, start, end, index_offset, maximum_batch_size);\n\t\t}\n\t}\n}\n\nvoid GLM_Prepare3DSprites(void)\n{\n\tif (!batchCount || !vertexCount) {\n\t\treturn;\n\t}\n\n\tR_TraceEnterNamedRegion(__func__);\n\n\tGLM_Create3DSpriteVAO();\n\n\tif (R_BufferReferenceIsValid(r_buffer_sprite_vertex_data)) {\n\t\tbuffers.Update(r_buffer_sprite_vertex_data, vertexCount * sizeof(verts[0]), verts);\n\t}\n\n\tR_TraceLeaveNamedRegion();\n}\n\nvoid GLM_Draw3DSprites(void)\n{\n\tunsigned int i;\n\tqbool current_alpha_test = false;\n\tqbool first_batch = true;\n\n\tif (!batchCount || !vertexCount || (batchCount == 1 && !batches[0].count)) {\n\t\treturn;\n\t}\n\n\tR_TraceEnterNamedRegion(__func__);\n\n\tif (!GLM_3DSpritesInit()) {\n\t\tR_TraceLeaveNamedRegion();\n\t\treturn;\n\t}\n\n\tR_ProgramUse(r_program_sprite3d);\n\n\tfor (i = 0; i < batchCount; ++i) {\n\t\tgl_sprite3d_batch_t* batch = &batches[i];\n\t\tqbool alpha_test = (batch->id == SPRITE3D_ENTITIES);\n\n\t\tif (!batch->count) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tR_TraceEnterNamedRegion(batch->name);\n\t\tif (first_batch || current_alpha_test != alpha_test) {\n\t\t\tR_ProgramUniform1i(r_program_uniform_sprite3d_alpha_test, current_alpha_test = alpha_test);\n\t\t}\n\t\tfirst_batch = false;\n\n\t\tR_ApplyRenderingState(batch->rendering_state);\n\t\tif (R_TextureReferenceIsValid(batch->texture)) {\n\t\t\trenderer.TextureUnitBind(0, batch->texture);\n\t\t}\n\t\telse if (!R_TextureReferenceIsValid(batch->textures[0])) {\n\t\t\textern texture_ref particletexture_array;\n\n\t\t\trenderer.TextureUnitBind(0, batch->texture = particletexture_array);\n\t\t}\n\n\t\tif (batch->count == 1) {\n\t\t\tif (R_TextureReferenceIsValid(batch->textures[0])) {\n\t\t\t\trenderer.TextureUnitBind(0, batch->textures[0]);\n\t\t\t}\n\t\t\tGL_DrawArrays(glPrimitiveTypes[batch->primitive_id], batch->glFirstVertices[0], batch->numVertices[0]);\n\t\t}\n\t\telse if (batch->allSameNumber && batch->numVertices[0] == 4 && batch->primitive_id == r_primitive_triangle_strip) {\n\t\t\tGLM_DrawSequentialBatch(batch, indexes_start_quads, INDEXES_MAX_QUADS);\n\t\t}\n\t\telse if (batch->allSameNumber && batch->numVertices[0] == 9 && GL_Supported(R_SUPPORT_PRIMITIVERESTART)) {\n\t\t\tGLM_DrawSequentialBatch(batch, indexes_start_sparks, INDEXES_MAX_SPARKS);\n\t\t}\n\t\telse if (batch->allSameNumber && batch->numVertices[0] == 18 && GL_Supported(R_SUPPORT_PRIMITIVERESTART)) {\n\t\t\tGLM_DrawSequentialBatch(batch, indexes_start_flashblend, INDEXES_MAX_FLASHBLEND);\n\t\t}\n\t\telse if (R_TextureReferenceIsValid(batch->texture)) {\n\t\t\tGL_MultiDrawArrays(glPrimitiveTypes[batch->primitive_id], batch->glFirstVertices, batch->numVertices, batch->count);\n\t\t}\n\t\telse {\n\t\t\tint first = 0, last = 1;\n\t\t\trenderer.TextureUnitBind(0, batch->textures[0]);\n\t\t\twhile (last < batch->count) {\n\t\t\t\tif (!R_TextureReferenceEqual(batch->textures[first], batch->textures[last])) {\n\t\t\t\t\tGL_MultiDrawArrays(glPrimitiveTypes[batch->primitive_id], batch->glFirstVertices, batch->numVertices, last - first);\n\n\t\t\t\t\trenderer.TextureUnitBind(0, batch->textures[last]);\n\t\t\t\t\tfirst = last;\n\t\t\t\t}\n\t\t\t\t++last;\n\t\t\t}\n\n\t\t\tGL_MultiDrawArrays(glPrimitiveTypes[batch->primitive_id], batch->glFirstVertices, batch->numVertices, last - first);\n\t\t}\n\n\t\tR_TraceLeaveNamedRegion();\n\n\t\tbatch->count = 0;\n\t}\n\n\tR_TraceLeaveNamedRegion();\n\n\tR_Sprite3DClearBatches();\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_state.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#include \"quakedef.h\"\n#include \"r_local.h\"\n#include \"r_trace.h\"\n#include \"r_state.h\"\n\nvoid GLM_BeginDrawWorld(qbool alpha_surfaces, qbool polygon_offset)\n{\n\tif (alpha_surfaces && polygon_offset) {\n\t\tR_ApplyRenderingState(r_state_alpha_surfaces_offset_glm);\n\t}\n\telse if (alpha_surfaces) {\n\t\tR_ApplyRenderingState(r_state_alpha_surfaces_glm);\n\t}\n\telse if (polygon_offset) {\n\t\tR_ApplyRenderingState(r_state_opaque_surfaces_offset_glm);\n\t}\n\telse {\n\t\tR_ApplyRenderingState(r_state_opaque_surfaces_glm);\n\t}\n}\n\nvoid GLM_StateBeginDraw3DSprites(void)\n{\n}\n\nvoid GLM_StateBeginPolyBlend(void)\n{\n\tR_ApplyRenderingState(r_state_poly_blend);\n}\n\nvoid GLM_StateBeginImageDraw(void)\n{\n\tR_ApplyRenderingState(r_state_hud_images_glm);\n}\n\nvoid GLM_StateBeginAliasOutlineBatch(void)\n{\n\tR_ApplyRenderingState(r_state_aliasmodel_outline);\n}\n\nvoid GLM_StateBeginAliasModelBatch(qbool translucent, qbool additive)\n{\n\tif (additive) {\n\t\tR_ApplyRenderingState(r_state_aliasmodel_additive_batch);\n\t}\n\telse if (translucent) {\n\t\tR_ApplyRenderingState(r_state_aliasmodel_translucent_batch);\n\t}\n\telse {\n\t\tR_ApplyRenderingState(r_state_aliasmodel_opaque_batch);\n\t}\n}\n\nvoid GLM_StateBeginAliasModelZPassBatch(void)\n{\n\tR_ApplyRenderingState(r_state_aliasmodel_translucent_batch_zpass);\n}\n"
  },
  {
    "path": "src/glm_texture_arrays.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// glm_texture_arrays.c\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n// gathers textures of common size into arrays so we can pack more references into fewer samplers\n\n#include \"quakedef.h\"\n#include \"vx_stuff.h\"\n#include \"vx_tracker.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"rulesets.h\"\n#ifndef  __APPLE__\n#include \"tr_types.h\"\n#endif\n#include \"glm_texture_arrays.h\"\n#include \"r_texture.h\"\n#include \"r_chaticons.h\"\n#include \"gl_texture_internal.h\"\n#include \"r_renderer.h\"\n\nstatic const texture_array_ref_t zero_array_ref[TEXTURETYPES_COUNT];\nstatic texture_flag_t texture_flags[MAX_GLTEXTURES];\nstatic int flagged_type = 0;\n\nstatic int SortFlaggedTextures(const void* lhs_, const void* rhs_)\n{\n\tint width_diff;\n\tint height_diff;\n\ttexture_flag_t* tex1 = (texture_flag_t*)lhs_;\n\ttexture_flag_t* tex2 = (texture_flag_t*)rhs_;\n\n\tqbool included1 = tex1->flags & (1 << flagged_type);\n\tqbool included2 = tex2->flags & (1 << flagged_type);\n\n\tif (included1 && !included2) {\n\t\treturn -1;\n\t}\n\telse if (!included1 && included2) {\n\t\treturn 1;\n\t}\n\n\tif (!R_TextureReferenceIsValid(tex1->ref)) {\n\t\tif (!R_TextureReferenceIsValid(tex2->ref)) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn 1;\n\t}\n\telse if (!R_TextureReferenceIsValid(tex2->ref)) {\n\t\treturn -1;\n\t}\n\n\twidth_diff = R_TextureWidth(tex1->ref) - R_TextureWidth(tex2->ref);\n\theight_diff = R_TextureHeight(tex1->ref) - R_TextureHeight(tex2->ref);\n\n\tif (width_diff) {\n\t\treturn width_diff;\n\t}\n\tif (height_diff) {\n\t\treturn height_diff;\n\t}\n\n\treturn 0;\n}\n\nstatic int SortTexturesByReference(const void* lhs_, const void* rhs_)\n{\n\ttexture_flag_t* tex1 = (texture_flag_t*)lhs_;\n\ttexture_flag_t* tex2 = (texture_flag_t*)rhs_;\n\n\tif (tex1->ref.index < tex2->ref.index) {\n\t\treturn -1;\n\t}\n\treturn 1;\n}\n\nstatic int SortTexturesByArrayReference(const void* lhs_, const void* rhs_)\n{\n\ttexture_flag_t* tex1 = (texture_flag_t*)lhs_;\n\ttexture_flag_t* tex2 = (texture_flag_t*)rhs_;\n\n\tint zero1 = !memcmp(tex1->array_ref, zero_array_ref, sizeof(zero_array_ref));\n\tint zero2 = !memcmp(tex2->array_ref, zero_array_ref, sizeof(zero_array_ref));\n\n\tif (zero1 && !zero2) {\n\t\treturn 1;\n\t}\n\telse if (!zero1 && zero2) {\n\t\treturn -1;\n\t}\n\treturn 0;\n}\n\nstatic void GL_DeleteExistingTextureArrays(qbool delete_textures)\n{\n\tint i;\n\n\tif (delete_textures) {\n\t\tint j;\n\n\t\tfor (i = 0; i < MAX_GLTEXTURES; ++i) {\n\t\t\tif (!memcmp(zero_array_ref, texture_flags[i].array_ref, sizeof(zero_array_ref))) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tfor (j = 0; j < TEXTURETYPES_COUNT; ++j) {\n\t\t\t\tif (R_TextureReferenceIsValid(texture_flags[i].array_ref[j].ref)) {\n\t\t\t\t\tR_DeleteTextureArray(&texture_flags[i].array_ref[j].ref);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tmemset(texture_flags, 0, sizeof(texture_flags));\n\tfor (i = 0; i < MAX_GLTEXTURES; ++i) {\n\t\ttexture_flags[i].ref.index = i;\n\t}\n}\n\nstatic qbool GL_SkipTexture(model_t* mod, texture_t* tx);\n\nstatic GLubyte* tempTextureBuffer;\nstatic GLubyte* emptyTextureBuffer;\nstatic GLuint tempTextureBufferSize;\nstatic GLuint emptyTextureBufferSize;\n\n/*\nstatic qbool AliasModelIsAnySize(model_t* mod)\n{\n\t// Technically every alias model is any size?\n\t// Not putting alias models in an array at the moment just because player skins are a special case\n\t//   and can change dynamically due to all the different skin-override settings\n\n\treturn false;\n}\n\nstatic qbool BrushModelIsAnySize(model_t* mod)\n{\n\t// Some brush models texture repeat and others don't - if it doesn't repeat then we should be\n\t//   able to insert onto a larger texture and then adjust texture coordinates so it doesn't matter\n\n\treturn false;\n}\n*/\n\nvoid GL_AddTextureToArray(texture_ref arrayTexture, int index, texture_ref tex2dname, qbool tile)\n{\n\tint width = R_TextureWidth(tex2dname);\n\tint height = R_TextureHeight(tex2dname);\n\tint final_width = R_TextureWidth(arrayTexture);\n\tint final_height = R_TextureHeight(arrayTexture);\n\n\tint ratio_x = tile ? final_width / width : 0;\n\tint ratio_y = tile ? final_height / height : 0;\n\tint x, y;\n\n\tif (ratio_x == 0) {\n\t\tratio_x = 1;\n\t}\n\tif (ratio_y == 0) {\n\t\tratio_y = 1;\n\t}\n\n\tif (tempTextureBufferSize < width * height * 4 * sizeof(GLubyte)) {\n\t\tQ_free(tempTextureBuffer);\n\t\ttempTextureBufferSize = width * height * 4 * sizeof(GLubyte);\n\t\ttempTextureBuffer = Q_malloc(tempTextureBufferSize);\n\t}\n\n\tif (emptyTextureBufferSize < final_width * final_height * 4 * sizeof(GLubyte)) {\n\t\tQ_free(emptyTextureBuffer);\n\t\temptyTextureBufferSize = final_width * final_height * 4 * sizeof(GLubyte);\n\t\temptyTextureBuffer = Q_malloc(emptyTextureBufferSize);\n\t}\n\n\tGL_GetTexImage(GL_TEXTURE0, tex2dname, 0, GL_RGBA, GL_UNSIGNED_BYTE, tempTextureBufferSize, tempTextureBuffer);\n\n\t// Clear\n\tGL_TexSubImage3D(0, arrayTexture, 0, 0, 0, index, final_width, final_height, 1, GL_RGBA, GL_UNSIGNED_BYTE, emptyTextureBuffer);\n\n\t// Might need to tile multiple times\n\tfor (x = 0; x < ratio_x; ++x) {\n\t\tfor (y = 0; y < ratio_y; ++y) {\n\t\t\tGL_TexSubImage3D(0, arrayTexture, 0, x * width, y * height, index, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, tempTextureBuffer);\n\t\t}\n\t}\n}\n\nstatic void GL_FlagTexturesForModel(model_t* mod)\n{\n\tint j;\n\n\tswitch (mod->type) {\n\tcase mod_alias:\n\tcase mod_alias3:\n\t\tfor (j = 0; j < MAX_SIMPLE_TEXTURES; ++j) {\n\t\t\tif (R_TextureReferenceIsValid(mod->simpletexture[j])) {\n\t\t\t\ttexture_flags[mod->simpletexture[j].index].flags |= (1 << TEXTURETYPES_SPRITES);\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase mod_sprite:\n\t\t{\n\t\t\tmsprite2_t* psprite = (msprite2_t*)Mod_Extradata(mod);\n\n\t\t\tfor (j = 0; j < psprite->numframes; ++j) {\n\t\t\t\tint offset    = psprite->frames[j].offset;\n\t\t\t\tint numframes = psprite->frames[j].numframes;\n\t\t\t\tmspriteframe_t* frame;\n\n\t\t\t\tif (offset < (int)sizeof(msprite2_t) || numframes < 1) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tframe = ((mspriteframe_t*)((byte*)psprite + offset));\n\t\t\t\tif (R_TextureReferenceIsValid(frame->gl_texturenum)) {\n\t\t\t\t\ttexture_flags[frame->gl_texturenum.index].flags |= (1 << TEXTURETYPES_SPRITES);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\tcase mod_brush:\n\t\t{\n\t\t\tint i, j;\n\n\t\t\t// Ammo-boxes etc can be replaced with simple textures\n\t\t\tfor (j = 0; j < MAX_SIMPLE_TEXTURES; ++j) {\n\t\t\t\tif (R_TextureReferenceIsValid(mod->simpletexture[j])) {\n\t\t\t\t\ttexture_flags[mod->simpletexture[j].index].flags |= (1 << TEXTURETYPES_SPRITES);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Brush models can be boxes (ammo, health), static world or moving platforms\n\t\t\tfor (i = 0; i < mod->numtextures; i++) {\n\t\t\t\ttexture_t* tx = mod->textures[i];\n\t\t\t\ttexture_type type = mod->isworldmodel ? TEXTURETYPES_WORLDMODEL : TEXTURETYPES_BRUSHMODEL;\n\n\t\t\t\tif (GL_SkipTexture(mod, tx) || !R_TextureReferenceIsValid(tx->gl_texturenum)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (R_TextureReferenceIsValid(tx->fb_texturenum) && !R_TexturesAreSameSize(tx->gl_texturenum, tx->fb_texturenum)) {\n\t\t\t\t\tCon_Printf(\"Warning: luma texture mismatch: %s (%dx%d vs %dx%d)\\n\", tx->name, R_TextureWidth(tx->gl_texturenum), R_TextureHeight(tx->gl_texturenum), R_TextureWidth(tx->fb_texturenum), R_TextureHeight(tx->fb_texturenum));\n\t\t\t\t\tR_TextureReferenceInvalidate(tx->fb_texturenum);\n\t\t\t\t}\n\n\t\t\t\ttexture_flags[tx->gl_texturenum.index].flags |= (1 << type);\n\t\t\t\tif (R_TextureReferenceIsValid(tx->fb_texturenum)) {\n\t\t\t\t\ttexture_flags[tx->gl_texturenum.index].subsequent = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\tcase mod_unknown:\n\t\t// Keep compiler happy\n\t\tbreak;\n\t}\n}\n\nstatic qbool GL_SkipTexture(model_t* mod, texture_t* tx)\n{\n\tint j;\n\n\tif (!tx || !tx->loaded) {\n\t\treturn true;\n\t}\n\n\tif (tx->anim_next) {\n\t\treturn false;\n\t}\n\n\tfor (j = 0; j < mod->numtexinfo; ++j) {\n\t\tif (mod->texinfo[j].texture == tx) {\n\t\t\tif (mod->texinfo[j].surfaces && !mod->texinfo[j].skippable) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\nstatic int GLM_CountTextureArrays(model_t* mod)\n{\n\tint i, j;\n\tint num_arrays = 0;\n\n\tfor (i = 0; i < mod->numtextures; ++i) {\n\t\ttexture_t* tex = mod->textures[i];\n\t\tqbool seen_prev = false;\n\n\t\tif (!tex || !tex->loaded || !R_TextureReferenceIsValid(tex->gl_texture_array)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (j = 0; j < i; ++j) {\n\t\t\ttexture_t* prev_tex = mod->textures[j];\n\t\t\tif (prev_tex && R_TextureReferenceEqual(prev_tex->gl_texture_array, tex->gl_texture_array)) {\n\t\t\t\tseen_prev = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!seen_prev) {\n\t\t\t++num_arrays;\n\t\t}\n\t}\n\n\treturn num_arrays;\n}\n\nstatic void GLM_SetTextureArrays(model_t* mod)\n{\n\tint i, j;\n\tint num_arrays = 0;\n\n\tfor (i = 0; i < mod->numtextures; ++i) {\n\t\ttexture_t* tex = mod->textures[i];\n\t\tqbool seen_prev = false;\n\n\t\tif (!tex) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttex->next_same_size = -1;\n\t\tif (!tex->loaded || !R_TextureReferenceIsValid(tex->gl_texture_array)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (j = i - 1; j >= 0; --j) {\n\t\t\ttexture_t* prev_tex = mod->textures[j];\n\t\t\tif (prev_tex && R_TextureReferenceEqual(prev_tex->gl_texture_array, tex->gl_texture_array)) {\n\t\t\t\tseen_prev = true;\n\t\t\t\tprev_tex->next_same_size = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!seen_prev) {\n\t\t\tmod->texture_array_first[num_arrays] = i;\n\t\t\tmod->texture_arrays[num_arrays] = tex->gl_texture_array;\n\t\t\tmod->textures[i]->size_start = true;\n\t\t\t++num_arrays;\n\t\t}\n\t}\n}\n\nstatic void GL_ImportTexturesForModel(model_t* mod)\n{\n\tint j;\n\n\tfor (j = 0; j < MAX_SIMPLE_TEXTURES; ++j) {\n\t\tif (R_TextureReferenceIsValid(mod->simpletexture[j])) {\n\t\t\ttexture_array_ref_t* array_ref = &texture_flags[mod->simpletexture[j].index].array_ref[TEXTURETYPES_SPRITES];\n\n\t\t\tmod->simpletexture_array[j] = array_ref->ref;\n\t\t\tmod->simpletexture_indexes[j] = array_ref->index;\n\t\t\tmod->simpletexture_scalingS[j] = array_ref->scale_s;\n\t\t\tmod->simpletexture_scalingT[j] = array_ref->scale_t;\n\t\t}\n\t}\n\n\tif (mod->type == mod_sprite) {\n\t\tmsprite2_t* psprite = (msprite2_t*)Mod_Extradata(mod);\n\n\t\tfor (j = 0; j < psprite->numframes; ++j) {\n\t\t\tint offset    = psprite->frames[j].offset;\n\t\t\tint numframes = psprite->frames[j].numframes;\n\t\t\tmspriteframe_t* frame;\n\n\t\t\tif (offset < (int)sizeof(msprite2_t) || numframes < 1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tframe = ((mspriteframe_t*)((byte*)psprite + offset));\n\n\t\t\tif (R_TextureReferenceIsValid(frame->gl_texturenum)) {\n\t\t\t\ttexture_array_ref_t* array_ref = &texture_flags[frame->gl_texturenum.index].array_ref[TEXTURETYPES_SPRITES];\n\n\t\t\t\tframe->gl_arraynum = array_ref->ref;\n\t\t\t\tframe->gl_arrayindex = array_ref->index;\n\t\t\t\tframe->gl_scalingS = array_ref->scale_s;\n\t\t\t\tframe->gl_scalingT = array_ref->scale_t;\n\t\t\t}\n\t\t}\n\t}\n\telse if (mod->type == mod_brush) {\n\t\ttexture_type type = mod->isworldmodel ? TEXTURETYPES_WORLDMODEL : TEXTURETYPES_BRUSHMODEL;\n\t\tint i;\n\n\t\tfor (i = 0; i < mod->numtextures; i++) {\n\t\t\ttexture_t* tx = mod->textures[i];\n\t\t\ttexture_array_ref_t* array_ref;\n\n\t\t\tif (GL_SkipTexture(mod, tx) || !R_TextureReferenceIsValid(tx->gl_texturenum)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tarray_ref = &texture_flags[tx->gl_texturenum.index].array_ref[type];\n\t\t\ttx->gl_texture_array = array_ref->ref;\n\t\t\ttx->gl_texture_index = array_ref->index;\n\t\t\ttx->gl_texture_scaleS = array_ref->scale_s;\n\t\t\ttx->gl_texture_scaleT = array_ref->scale_t;\n\n\t\t\t// fb has to be the same size and will be subsequent\n\t\t\tif (R_TextureReferenceIsValid(tx->fb_texturenum)) {\n\t\t\t\tGL_AddTextureToArray(array_ref->ref, array_ref->index + 1, tx->fb_texturenum, false);\n\n\t\t\t\t// flag so we delete the 2d version later\n\t\t\t\ttexture_flags[tx->fb_texturenum.index].flags |= (1 << type);\n\t\t\t}\n\t\t}\n\n\t\tmod->texture_array_count = GLM_CountTextureArrays(mod);\n\t\tif (mod->texture_array_count >= MAX_TEXTURE_ARRAYS_PER_MODEL) {\n\t\t\t// FIXME: Simply fail & fallback to simple textures... or use allocated memory, don't crash the client\n\t\t\tSys_Error(\"Model %s has >= %d texture dimensions\", mod->name, MAX_TEXTURE_ARRAYS_PER_MODEL);\n\t\t\treturn;\n\t\t}\n\n\t\tGLM_SetTextureArrays(mod);\n\t}\n}\n\nstatic int GLM_FindPotentialSizes(int i, int* potential_sizes, int flagged_type, qbool maximise, int* req_width, int* req_height)\n{\n\tint size_index = 0;\n\tint j;\n\tint width = R_TextureWidth(texture_flags[i].ref);\n\tint height = R_TextureHeight(texture_flags[i].ref);\n\n\tpotential_sizes[size_index++] = 1 + texture_flags[i].subsequent;\n\n\t// Count how many textures of the same size we have\n\tfor (j = i + 1; j < MAX_GLTEXTURES; ++j) {\n\t\tif (!(texture_flags[j].flags & (1 << flagged_type))) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (maximise) {\n\t\t\twidth = max(width, R_TextureWidth(texture_flags[j].ref));\n\t\t\theight = max(height, R_TextureHeight(texture_flags[j].ref));\n\t\t}\n\t\telse if (R_TextureWidth(texture_flags[j].ref) != width || R_TextureHeight(texture_flags[j].ref) != height) {\n\t\t\tbreak;\n\t\t}\n\n\t\tpotential_sizes[size_index] = potential_sizes[size_index - 1] + 1 + texture_flags[j].subsequent;\n\t\t++size_index;\n\t}\n\n\t*req_width = width;\n\t*req_height = height;\n\treturn size_index;\n}\n\nstatic void GLM_CopyTexturesToArray(texture_ref array_ref, int flagged_type, int min_index, int max_index, int width, int height)\n{\n\tint array_index = 0;\n\tint k;\n\n\t// texture created ok\n\tif (flagged_type == TEXTURETYPES_SPRITES) {\n\t\trenderer.TextureWrapModeClamp(array_ref);\n\t}\n\n\t// Copy the 2D textures across\n\tfor (k = min_index; k <= max_index; ++k) {\n\t\ttexture_ref ref_2d = texture_flags[k].ref;\n\n\t\t// TODO: compression: flag as ANYSIZE and set scale_s/scale_t accordingly\n\t\tGL_AddTextureToArray(array_ref, array_index, ref_2d, false);\n\t\ttexture_flags[k].array_ref[flagged_type].ref = array_ref;\n\t\ttexture_flags[k].array_ref[flagged_type].index = array_index;\n\t\ttexture_flags[k].array_ref[flagged_type].scale_s = (R_TextureWidth(ref_2d) * 1.0f) / width;\n\t\ttexture_flags[k].array_ref[flagged_type].scale_t = (R_TextureHeight(ref_2d) * 1.0f) / height;\n\n\t\t// (subsequent are the luma textures on brush models)\n\t\tarray_index += 1 + texture_flags[k].subsequent;\n\t}\n}\n\nstatic int GLM_CreateArrayFromPotentialSizes(int i, int* potential_sizes, int size_index_max, qbool return_on_failure, int width, int height)\n{\n\tint size_attempt, depth;\n\n\tconst char* textureTypeNames[] = {\n\t\t\"aliasmodel\",\n\t\t\"brushmodel\",\n\t\t\"worldmodel\",\n\t\t\"sprites\",\n\t};\n\n\tfor (size_attempt = size_index_max - 1; size_attempt >= 0; --size_attempt) {\n\t\t// create array of desired size\n\t\tchar name[64];\n\t\ttexture_ref array_ref;\n\n\t\tR_TextureReferenceInvalidate(array_ref);\n\t\tdepth = potential_sizes[size_attempt];\n\t\tsnprintf(name, sizeof(name), \"%s_%d[%dx%dx%d]\", textureTypeNames[flagged_type], i, width, height, depth);\n\n\t\tarray_ref = R_CreateTextureArray(name, width, height, depth, TEX_MIPMAP | TEX_ALPHA);\n\t\tif (R_TextureReferenceIsValid(array_ref)) {\n\t\t\tGLM_CopyTexturesToArray(array_ref, flagged_type, i, i + size_attempt, width, height);\n\t\t\treturn size_attempt;\n\t\t}\n\t}\n\n\tif (!return_on_failure) {\n\t\tSys_Error(\"Failed to create array size %dx%dx%d\\n\", width, height, potential_sizes[size_index_max - 1]);\n\t}\n\treturn -1;\n}\n\n// Called from R_NewMap\nvoid GLM_BuildCommonTextureArrays(qbool vid_restart)\n{\n\tint i;\n\tint potential_sizes[MAX_GLTEXTURES];\n\n\tGL_DeleteExistingTextureArrays(!vid_restart);\n\tR_ClearModelTextureData();\n\n\tif (cls.state == ca_disconnected) {\n\t\treturn;\n\t}\n\n\tfor (i = 1; i < MAX_MODELS; ++i) {\n\t\tmodel_t* mod = cl.model_precache[i];\n\n\t\tif (mod && (mod == cl.worldmodel || !mod->isworldmodel)) {\n\t\t\tGL_FlagTexturesForModel(mod);\n\t\t}\n\t}\n\n\tfor (i = 0; i < MAX_VWEP_MODELS; i++) {\n\t\tmodel_t* mod = cl.vw_model_precache[i];\n\n\t\tif (mod) {\n\t\t\tGL_FlagTexturesForModel(mod);\n\t\t}\n\t}\n\n\t// custom models are explicitly loaded by client, not notified by server\n\tfor (i = 0; i < custom_model_count; ++i) {\n\t\tmodel_t* mod = Mod_CustomModel(i, false);\n\n\t\tif (mod) {\n\t\t\tGL_FlagTexturesForModel(mod);\n\t\t}\n\t}\n\n\t// Add non-model textures we need (generally sprites)\n\t{\n\t\tQMB_FlagTexturesForArray(texture_flags);\n\t\tR_FlagChatIconTexturesForArray(texture_flags);\n\t\tVX_FlagTexturesForArray(texture_flags);\n\t\tPart_FlagTexturesForArray(texture_flags);\n\t}\n\n\tfor (flagged_type = 0; flagged_type < TEXTURETYPES_COUNT; ++flagged_type) {\n\t\tqsort(texture_flags, MAX_GLTEXTURES, sizeof(texture_flags[0]), SortFlaggedTextures);\n\n\t\tfor (i = 0; i < MAX_GLTEXTURES; ++i) {\n\t\t\tint size_index_max = 0;\n\t\t\tint extra_slots_added;\n\t\t\tint req_width, req_height;\n\n\t\t\tif (!(texture_flags[i].flags & (1 << flagged_type))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tsize_index_max = GLM_FindPotentialSizes(i, potential_sizes, flagged_type, flagged_type == TEXTURETYPES_SPRITES, &req_width, &req_height);\n\t\t\textra_slots_added = GLM_CreateArrayFromPotentialSizes(i, potential_sizes, size_index_max, flagged_type == TEXTURETYPES_SPRITES, req_width, req_height);\n\t\t\tif (extra_slots_added == -1) {\n\t\t\t\t// try again without maximising\n\t\t\t\tGLM_FindPotentialSizes(i, potential_sizes, flagged_type, false, &req_width, &req_height);\n\t\t\t\textra_slots_added = GLM_CreateArrayFromPotentialSizes(i, potential_sizes, size_index_max, false, req_width, req_height);\n\t\t\t}\n\t\t\ti += extra_slots_added;\n\t\t}\n\t}\n\n\tqsort(texture_flags, MAX_GLTEXTURES, sizeof(texture_flags[0]), SortTexturesByReference);\n\n\t{\n\t\tQMB_ImportTextureArrayReferences(texture_flags);\n\t\tR_ImportChatIconTextureArrayReferences(texture_flags);\n\t\tVX_ImportTextureArrayReferences(texture_flags);\n\t\tPart_ImportTexturesForArrayReferences(texture_flags);\n\n\t\t// Go back through all models, importing textures into arrays and creating new VBO\n\t\tfor (i = 1; i < MAX_MODELS; ++i) {\n\t\t\tmodel_t* mod = cl.model_precache[i];\n\n\t\t\tif (mod) {\n\t\t\t\tGL_ImportTexturesForModel(mod);\n\t\t\t}\n\t\t}\n\n\t\tfor (i = 0; i < MAX_VWEP_MODELS; i++) {\n\t\t\tmodel_t* mod = cl.vw_model_precache[i];\n\n\t\t\tif (mod) {\n\t\t\t\tGL_ImportTexturesForModel(mod);\n\t\t\t}\n\t\t}\n\n\t\t// custom models are explicitly loaded by client, not notified by server\n\t\tfor (i = 0; i < custom_model_count; ++i) {\n\t\t\tmodel_t* mod = Mod_CustomModel(i, false);\n\n\t\t\tif (mod) {\n\t\t\t\tGL_ImportTexturesForModel(mod);\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2nd pass through the models should have filled the arrays, so can create mipmaps now\n\tqsort(texture_flags, MAX_GLTEXTURES, sizeof(texture_flags[0]), SortTexturesByArrayReference);\n\n\tfor (i = 0; i < MAX_GLTEXTURES; ++i) {\n\t\tint j;\n\t\tqbool any = false;\n\n\t\tfor (j = 0; j < TEXTURETYPES_COUNT; ++j) {\n\t\t\tif (R_TextureReferenceIsValid(texture_flags[i].array_ref[j].ref)) {\n\t\t\t\tR_GenerateMipmapsIfNeeded(texture_flags[i].array_ref[j].ref);\n\n\t\t\t\tif (j == TEXTURETYPES_BRUSHMODEL) {\n\t\t\t\t\tR_DeleteTexture(&texture_flags[i].ref);\n\t\t\t\t}\n\t\t\t\tany = true;\n\t\t\t}\n\t\t}\n\n\t\tif (!any) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tQ_free(tempTextureBuffer);\n\tQ_free(emptyTextureBuffer);\n\temptyTextureBufferSize = tempTextureBufferSize = 0;\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_texture_arrays.h",
    "content": "\n#ifndef GLM_TEXTURE_ARRAYS_HEADER\n#define GLM_TEXTURE_ARRAYS_HEADER\n\ntypedef enum {\n\tTEXTURETYPES_ALIASMODEL,\n\tTEXTURETYPES_BRUSHMODEL,\n\tTEXTURETYPES_WORLDMODEL,\n\tTEXTURETYPES_SPRITES,\n\n\tTEXTURETYPES_COUNT\n} texture_type;\n\ntypedef struct texture_array_ref_s {\n\ttexture_ref ref;\n\tint index;\n\tfloat scale_s;\n\tfloat scale_t;\n} texture_array_ref_t;\n\ntypedef struct texture_flag_s {\n\ttexture_ref ref;\n\tint subsequent;\n\tint flags;\n\n\ttexture_array_ref_t array_ref[TEXTURETYPES_COUNT];\n} texture_flag_t;\n\nvoid QMB_FlagTexturesForArray(texture_flag_t* texture_flags);\nvoid QMB_ImportTextureArrayReferences(texture_flag_t* texture_flags);\n\nvoid VX_FlagTexturesForArray(texture_flag_t* texture_flags);\nvoid VX_ImportTextureArrayReferences(texture_flag_t* texture_flags);\n\nvoid Part_FlagTexturesForArray(texture_flag_t* texture_flags);\nvoid Part_ImportTexturesForArrayReferences(texture_flag_t* texture_flags);\n\nvoid R_ImportChatIconTextureArrayReferences(texture_flag_t* texture_flags);\nvoid R_FlagChatIconTexturesForArray(texture_flag_t* texture_flags);\n\n#endif // #ifndef GLM_TEXTURE_ARRAYS_HEADER\n"
  },
  {
    "path": "src/glm_vao.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"glm_vao.h\"\n#include \"r_buffers.h\"\n\ntypedef struct r_vao_s {\n\tGLuint vao;\n\n\tr_buffer_id element_array_buffer;\n} glm_vao_t;\n\nstatic glm_vao_t vaos[vao_count];\n\n// VAOs\nGL_StaticProcedureDeclaration(glGenVertexArrays, \"size=%d, arrays=%p\", GLsizei n, GLuint* arrays)\nGL_StaticProcedureDeclaration(glBindVertexArray, \"arrayNum=%u\", GLuint arrayNum)\nGL_StaticProcedureDeclaration(glDeleteVertexArrays, \"n=%d, arrays=%p\", GLsizei n, const GLuint* arrays)\nGL_StaticProcedureDeclaration(glEnableVertexAttribArray, \"index=%u\", GLuint index)\nGL_StaticProcedureDeclaration(glVertexAttribPointer, \"index=%u, size=%d, type=%u, normalized=%d, stride=%d, pointer=%p\", GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer)\nGL_StaticProcedureDeclaration(glVertexAttribIPointer, \"index=%u, size=%d, type=%u, stride=%d, pointer=%p\", GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid* pointer)\nGL_StaticProcedureDeclaration(glVertexAttribDivisor, \"index=%u, divisor=%u\", GLuint index, GLuint divisor)\n\nqbool GLM_InitialiseVAOHandling(void)\n{\n\tqbool vaos_supported = true;\n\n\t// VAOs: OpenGL 3.0\n\tif (GL_VersionAtLeast(3, 0) || SDL_GL_ExtensionSupported(\"GL_ARB_vertex_array_object\")) {\n\t\tGL_LoadMandatoryFunctionExtension(glGenVertexArrays, vaos_supported);\n\t\tGL_LoadMandatoryFunctionExtension(glBindVertexArray, vaos_supported);\n\t\tGL_LoadMandatoryFunctionExtension(glDeleteVertexArrays, vaos_supported);\n\t}\n\telse {\n\t\tvaos_supported = false;\n\t}\n\n\t// OpenGL 2.0\n\tGL_LoadMandatoryFunctionExtension(glEnableVertexAttribArray, vaos_supported);\n\tGL_LoadMandatoryFunctionExtension(glVertexAttribPointer, vaos_supported);\n\n\t// OpengGL 3.0\n\tGL_LoadMandatoryFunctionExtension(glVertexAttribIPointer, vaos_supported);\n\n\tif (GL_VersionAtLeast(3, 3) ||SDL_GL_ExtensionSupported(\"GL_ARB_instanced_arrays\")) {\n\t\tGL_LoadMandatoryFunctionExtension(glVertexAttribDivisor, vaos_supported);\n\t}\n\telse {\n\t\tvaos_supported = false;\n\t}\n\n\treturn vaos_supported;\n}\n\nvoid GLM_BindVertexArray(r_vao_id vao)\n{\n\tGL_Procedure(glBindVertexArray, vaos[vao].vao);\n\tif (vao) {\n\t\tbuffers.SetElementArray(vaos[vao].element_array_buffer);\n\t}\n}\n\nvoid GLM_ConfigureVertexAttribPointer(r_vao_id vao, r_buffer_id vbo, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer, int divisor)\n{\n\tassert(vao);\n\tassert(vaos[vao].vao);\n\n\tR_BindVertexArray(vao);\n\tif (R_BufferReferenceIsValid(vbo)) {\n\t\tbuffers.Bind(vbo);\n\t}\n\telse {\n\t\tbuffers.UnBind(buffertype_vertex);\n\t}\n\n\tGL_Procedure(glEnableVertexAttribArray, index);\n\tGL_Procedure(glVertexAttribPointer, index, size, type, normalized, stride, pointer);\n\tGL_Procedure(glVertexAttribDivisor, index, divisor);\n}\n\nvoid GLM_ConfigureVertexAttribIPointer(r_vao_id vao, r_buffer_id vbo, GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid* pointer, int divisor)\n{\n\tassert(vao);\n\tassert(vaos[vao].vao);\n\n\tR_BindVertexArray(vao);\n\tif (R_BufferReferenceIsValid(vbo)) {\n\t\tbuffers.Bind(vbo);\n\t}\n\telse {\n\t\tbuffers.UnBind(buffertype_vertex);\n\t}\n\n\tGL_Procedure(glEnableVertexAttribArray, index);\n\tGL_Procedure(glVertexAttribIPointer, index, size, type, stride, pointer);\n\tGL_Procedure(glVertexAttribDivisor, index, divisor);\n}\n\nvoid GLM_SetVertexArrayElementBuffer(r_vao_id vao, r_buffer_id ibo)\n{\n\tassert(vao);\n\tassert(vaos[vao].vao);\n\n\tR_BindVertexArray(vao);\n\tif (R_BufferReferenceIsValid(ibo)) {\n\t\tbuffers.Bind(ibo);\n\t}\n\telse {\n\t\tbuffers.UnBind(buffertype_index);\n\t}\n}\n\nvoid GLM_GenVertexArray(r_vao_id vao, const char* name)\n{\n\tif (vaos[vao].vao) {\n\t\tGL_Procedure(glDeleteVertexArrays, 1, &vaos[vao].vao);\n\t}\n\tGL_Procedure(glGenVertexArrays, 1, &vaos[vao].vao);\n\tR_BindVertexArray(vao);\n\tGL_TraceObjectLabelSet(GL_VERTEX_ARRAY, vaos[vao].vao, -1, name);\n\tbuffers.SetElementArray(r_buffer_none);\n}\n\nvoid GLM_DeleteVAOs(void)\n{\n\tr_vao_id id;\n\n\tif (GL_Available(glBindVertexArray)) {\n\t\tGL_Procedure(glBindVertexArray, 0);\n\t}\n\n\tfor (id = 0; id < vao_count; ++id) {\n\t\tif (vaos[id].vao) {\n\t\t\tif (GL_Available(glDeleteVertexArrays)) {\n\t\t\t\tGL_Procedure(glDeleteVertexArrays, 1, &vaos[id].vao);\n\t\t\t}\n\t\t\tvaos[id].vao = 0;\n\t\t}\n\t}\n}\n\nqbool GLM_VertexArrayCreated(r_vao_id vao)\n{\n\treturn vaos[vao].vao != 0;\n}\n\nvoid GLM_BindVertexArrayElementBuffer(r_vao_id vao, r_buffer_id ref)\n{\n\tif (vaos[vao].vao) {\n\t\tvaos[vao].element_array_buffer = ref;\n\t}\n}\n\n#endif // #ifdef RENDERER_OPTION_MODERN_OPENGL\n"
  },
  {
    "path": "src/glm_vao.h",
    "content": "/*\nCopyright (C) 2018 ezQuake team.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_GLM_VAO_HEADER\n#define EZQUAKE_GLM_VAO_HEADER\n\n#include \"gl_local.h\"\n#include \"r_vao.h\"\n\nvoid GLM_BindVertexArray(r_vao_id vao);\nvoid GLM_GenVertexArray(r_vao_id vao, const char* name);\nvoid GLM_SetVertexArrayElementBuffer(r_vao_id vao, r_buffer_id ibo);\nvoid GLM_DeleteVAOs(void);\n\nqbool GLM_InitialiseVAOHandling(void);\nqbool GLM_VertexArrayCreated(r_vao_id vao);\n\nvoid GLM_ConfigureVertexAttribPointer(r_vao_id vao, r_buffer_id vbo, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer, int divisor);\nvoid GLM_ConfigureVertexAttribIPointer(r_vao_id vao, r_buffer_id vbo, GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid* pointer, int divisor);\n\nvoid GLM_BindVertexArrayElementBuffer(r_vao_id, r_buffer_id ref);\n\n#endif // EZQUAKE_GLM_VAO_HEADER\n"
  },
  {
    "path": "src/glsl/common.glsl",
    "content": "\n// This file included in all .glsl files (inserted at #ezquake-definitions point)\n\n#define MAX_DLIGHTS 32\n\nEZ_SSBO_LAYOUT(std140, EZQ_GL_BINDINGPOINT_FRAMECONSTANTS) uniform GlobalState {\n\tmat4 modelViewMatrix;\n\tmat4 projectionMatrix;\n\n\tvec4 lightPositions[MAX_DLIGHTS];\n\tvec4 lightColors[MAX_DLIGHTS];\n\n\tvec3 cameraPosition;\n\tint lightsActive;\n\n\t// drawflat colours\n\tvec4 r_wallcolor;\n\tvec4 r_floorcolor;\n\tvec4 r_telecolor;\n\tvec4 r_lavacolor;\n\tvec4 r_slimecolor;\n\tvec4 r_watercolor;\n\tvec4 r_skycolor;\n\tvec4 v_blend;\n\n\tfloat time;\n\tfloat gamma;\n\tfloat contrast;\n\tint r_alphatestfont;\n\n\t// sky\n\tfloat skySpeedscale;\n\tfloat skySpeedscale2;\n\tfloat r_farclip_unused;              // Replace\n\tfloat padding;\n\n\t// animated skybox\n\tvec4  skyWind;\n\n\t// drawflat toggles\n\tint r_drawflat;\n\tint r_fastturb;\n\tint r_fastsky;\n\tint r_textureless;\n\n\t// [4-byte break]\n\tfloat r_lerpmodels;\n\n\t// powerup shells round alias models\n\tfloat shellSize_unused;               // Replace\n\tfloat shell_base_level1;\n\tfloat shell_base_level2;\n\tfloat shell_effect_level1;\n\tfloat shell_effect_level2;\n\tfloat shell_alpha;\n\n\t// lighting\n\tfloat lightScale;\n\n\t// [4-byte break]\n\tint r_width;\n\tint r_height;\n\tfloat r_zFar;\n\tfloat r_zNear;\n\n\t// fog parameters\n\tvec3 fogColor;\n\tfloat fogDensity;\n\n\tfloat skyFogMix;\n\tfloat fogMinZ;\n\tfloat fogMaxZ;\n\t// camAngles.x\n\n\tvec3 camAngles; // camAngles.yz\n\tfloat r_inv_width;\n\tfloat r_inv_height;\n};\n\nstruct WorldDrawInfo {\n\tmat4 mvMatrix;\n\tfloat alpha;\n\tint samplerBase;\n\tint drawFlags;\n\tint sampler;\n};\n\nstruct SamplerMapping {\n\tint sampler;\n\tfloat layer;\n\tint flags;\n};\n\nstruct AliasModelVert {\n\tfloat x, y, z;\n\tfloat nx, ny, nz;\n\tfloat dx, dy, dz;\n\tfloat s, t;\n\tint padding;\n};\n\nstruct AliasModel {\n\tmat4 modelView;\n\tvec4 color;\n\tvec4 topcolor;\n\tvec4 bottomcolor;\n\tint flags;\n\tfloat yaw_angle_rad;\n\tfloat shadelight;\n\tfloat ambientlight;\n\tint materialTextureMapping;\n\tfloat lerpFraction;\n\tfloat minLumaMix;\n\tfloat outlineNormalScale;\n};\n\nstruct model_surface {\n\tvec4 normal;\n\tvec4 vecs0;\n\tvec4 vecs1;\n};\n"
  },
  {
    "path": "src/glsl/constants.glsl",
    "content": "\n// This file included in all .glsl files (inserted at #ezquake-definitions point)\n//   and also .c source files.  For compile-time constants only\n\n#ifndef MAX_LIGHTSTYLES\n#define MAX_LIGHTSTYLES         64\n#endif\n\n// Alias-model flags\n#define AMF_SHELLMODEL_RED      1\n#define AMF_SHELLMODEL_BLUE     2\n#define AMF_SHELLMODEL_GREEN    4\n#define AMF_CAUSTICS            8\n#define AMF_TEXTURE_MATERIAL   16\n#define AMF_TEXTURE_LUMA       32\n#define AMF_WEAPONMODEL        64\n#define AMF_PLAYERMODEL       128\n#define AMF_TEAMMATE          256\n#define AMF_BEHINDWALL        512\n#define AMF_VWEPMODEL        1024\n#define AMF_SHELLFLAGS       (AMF_SHELLMODEL_RED | AMF_SHELLMODEL_BLUE | AMF_SHELLMODEL_GREEN)\n\n#define AM_VERTEX_NOLERP        1 // the alias model vertex should not be lerped, and always use lerpfraction 1 (meag: update shader if value no longer 1)\n#define AM_VERTEX_NORMALFIXED   2 // set after the alias model pose has been checked for matching vertices with different normals\n\n// Brush-model flags\n#define EZQ_SURFACE_TYPE   7    // must cover all bits required for TEXTURE_TURB_*\n#define TEXTURE_TURB_WATER 1\n#define TEXTURE_TURB_SLIME 2\n#define TEXTURE_TURB_LAVA  3\n#define TEXTURE_TURB_TELE  4\n#define TEXTURE_TURB_OTHER 5\n#define TEXTURE_TURB_SKY   6\n\n#define EZQ_SURFACE_IS_FLOOR   8    // should be drawn as floor for r_drawflat\n#define EZQ_SURFACE_UNDERWATER 16   // requires caustics, if enabled\n#define EZQ_SURFACE_HAS_LUMA   32   // surface has luma texture in next array index\n#define EZQ_SURFACE_WORLD      64   // world-surface (should have detail textures applied, r_drawflat applied)\n#define EZQ_SURFACE_ALPHATEST  128  // alpha-testing should take place when rendering\n#define EZQ_SURFACE_HAS_FB     256  // surface has fb texture in next array index\n#define EZQ_SURFACE_LIT_TURB   512  // turb surface has lightmap\n\n#define MAX_SAMPLER_MAPPINGS 256\n\n// SSBO/UBO bindings\n#define EZQ_GL_BINDINGPOINT_FRAMECONSTANTS      0\n\n// Storage buffer binding points (SSBO on 4.3+, UBO on 4.1)\n// In GLSL: offset by 1 when used as UBOs to avoid conflict with GlobalState at 0\n// In C code: EZQ_STORAGE_BLOCK_BINDING() macro applies the offset at runtime\n#define EZQ_GL_BINDINGPOINT_WORLDMODEL_SURFACES 0\n#define EZQ_GL_BINDINGPOINT_ALIASMODEL_DRAWDATA 1\n#define EZQ_GL_BINDINGPOINT_BRUSHMODEL_DRAWDATA 2\n#define EZQ_GL_BINDINGPOINT_BRUSHMODEL_SAMPLERS 3\n#define EZQ_GL_BINDINGPOINT_LIGHTSTYLES         4\n#define EZQ_GL_BINDINGPOINT_SURFACES_TO_LIGHT   5\n\n// Alias models\n#define EZQ_ALIAS_MODE_NORMAL        0\n#define EZQ_ALIAS_MODE_SHELLS        1\n#define EZQ_ALIAS_MODE_OUTLINES      2\n#define EZQ_ALIAS_MODE_OUTLINES_SPEC 4\n\n// 8x8 block\n#define HW_LIGHTING_BLOCK_SIZE 4\n\n// HUD rendering\n#define IMAGEPROG_FLAGS_TEXTURE     1    // Texture the image (otherwise colored rectangle)\n#define IMAGEPROG_FLAGS_ALPHATEST   2    // Enable alpha-testing when rendering\n#define IMAGEPROG_FLAGS_TEXT        4    // Use r_alphatestfont to determine alpha-testing\n#define IMAGEPROG_FLAGS_NEAREST     8    // Simulate GL_NEAREST when sampling texture\n"
  },
  {
    "path": "src/glsl/draw_aliasmodel.fragment.glsl",
    "content": "#ezquake-definitions\n\nuniform int mode;\nuniform vec3 outline_color;\nuniform vec3 outline_color_team;\nuniform vec3 outline_color_enemy;\nuniform int outline_use_player_color;\n\n#ifdef DRAW_CAUSTIC_TEXTURES\nEZ_LAYOUT_BINDING(SAMPLER_CAUSTIC_TEXTURE) uniform sampler2D causticsTex;\n#endif\nEZ_LAYOUT_BINDING(SAMPLER_MATERIAL_TEXTURE_START) uniform sampler2D samplers[SAMPLER_COUNT];\n\nin vec2 fsTextureCoord;\nin vec2 fsAltTextureCoord;\n#ifdef EZQ_ALIASMODEL_FLATSHADING\nflat in vec4 fsBaseColor;\n#else\nin vec4 fsBaseColor;\n#endif\nflat in int fsFlags;\nflat in int fsTextureEnabled;\nflat in int fsMaterialSampler;\nflat in float fsMinLumaMix;\nflat in vec4 plrtopcolor;\nflat in vec4 plrbotcolor;\n\nout vec4 frag_colour;\n\nbool texture_coord_is_on_legs() {\n\t// both front and back legs at the same y level on the texture\n\tif(fsTextureCoord.y >= 0.42 && fsTextureCoord.y <= 1)\n\t           /*                   front legs                  */    /*                   back legs                   */\n\t\treturn fsTextureCoord.x >= 0.19 && fsTextureCoord.x <= 0.4 || fsTextureCoord.x >= 0.69 && fsTextureCoord.x <= 0.9;\n\n\treturn false;\n}\n\nvoid main()\n{\n\tif((fsFlags & AMF_PLAYERMODEL) != 0) {\n\t\tif(outline_use_player_color != 0) {\n\t\t\tif((fsFlags & AMF_VWEPMODEL) != 0) { // vwep model is top color\n\t\t\t\tfrag_colour = vec4(plrtopcolor.rgb, 1.0f);\n\t\t\t} else {\n\t\t\t\tif (texture_coord_is_on_legs())\n\t\t\t\t\tfrag_colour = vec4(plrbotcolor.rgb, 1.0f);\n\t\t\t\telse\n\t\t\t\t\tfrag_colour = vec4(plrtopcolor.rgb, 1.0f);\n\t\t\t}\n\t\t} else {\n\t\t\tif((fsFlags & AMF_TEAMMATE) != 0)\n\t\t\t\tfrag_colour = vec4(outline_color_team, 1.0f);\n\t\t\telse\n\t\t\t\tfrag_colour = vec4(outline_color_enemy, 1.0f);\n\t\t}\n\t} else {\n\t\tfrag_colour = vec4(outline_color, 1.0f);\n\t}\n\n\tif (mode != EZQ_ALIAS_MODE_OUTLINES && mode != EZQ_ALIAS_MODE_OUTLINES_SPEC) {\n\t\tvec4 tex = texture(samplers[fsMaterialSampler], fsTextureCoord.st);\n\t\tvec4 altTex = texture(samplers[fsMaterialSampler], fsAltTextureCoord.st);\n#ifdef DRAW_CAUSTIC_TEXTURES\n\t\tvec4 caustic = texture(\n\t\t\tcausticsTex,\n\t\t\tvec2(\n\t\t\t\t// Using multipler of 3 here - not in other caustics logic but range\n\t\t\t\t//   isn't enough otherwise, effect too subtle\n\t\t\t\t(fsTextureCoord.s + sin(0.465 * (time + fsTextureCoord.t))) * 3 * -0.1234375,\n\t\t\t\t(fsTextureCoord.t + sin(0.465 * (time + fsTextureCoord.s))) * 3 * -0.1234375\n\t\t\t)\n\t\t);\n#endif\n\n\t\tif (mode == EZQ_ALIAS_MODE_SHELLS) {\n\t\t\tvec4 color1 = vec4(\n\t\t\t\tshell_base_level1 + ((fsFlags & AMF_SHELLMODEL_RED) != 0 ? shell_effect_level1 : 0),\n\t\t\t\tshell_base_level1 + ((fsFlags & AMF_SHELLMODEL_GREEN) != 0 ? shell_effect_level1 : 0),\n\t\t\t\tshell_base_level1 + ((fsFlags & AMF_SHELLMODEL_BLUE) != 0 ? shell_effect_level1 : 0),\n\t\t\t\t0\n\t\t\t);\n\t\t\tvec4 color2 = vec4(\n\t\t\t\tshell_base_level2 + ((fsFlags & AMF_SHELLMODEL_RED) != 0 ? shell_effect_level2 : 0),\n\t\t\t\tshell_base_level2 + ((fsFlags & AMF_SHELLMODEL_GREEN) != 0 ? shell_effect_level2 : 0),\n\t\t\t\tshell_base_level2 + ((fsFlags & AMF_SHELLMODEL_BLUE) != 0 ? shell_effect_level2 : 0),\n\t\t\t\t0\n\t\t\t);\n\n\t\t\tfrag_colour = color1 * tex + color2 * altTex;\n#ifdef DRAW_FOG\n\t\t\tfrag_colour = applyFogBlend(frag_colour, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n\t\t\tfrag_colour *= shell_alpha;\n\t\t}\n\t\telse {\n\t\t\tfrag_colour = vec4(fsBaseColor.rgb, 1);\n\t\t\tif (fsTextureEnabled != 0) {\n\t\t\t\tfrag_colour = vec4(mix(tex.rgb, tex.rgb * fsBaseColor.rgb, max(fsMinLumaMix, tex.a)), 1);\n\t\t\t}\n#ifdef DRAW_CAUSTIC_TEXTURES\n\t\t\tif ((fsFlags & AMF_CAUSTICS) == AMF_CAUSTICS) {\n\t\t\t\t// FIXME: Do proper GL_DECAL etc\n\t\t\t\tfrag_colour = vec4(caustic.rgb * frag_colour.rgb * 1.8, 1);\n\t\t\t}\n#endif\n#ifdef DRAW_FOG\n\t\t\tfrag_colour = applyFog(frag_colour, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n\t\t\tfrag_colour *= fsBaseColor.a;\n\t\t}\n\t} else {\n#ifdef DRAW_FOG\n\t\tfrag_colour = applyFog(frag_colour, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n\t}\n}\n"
  },
  {
    "path": "src/glsl/draw_aliasmodel.vertex.glsl",
    "content": "#ezquake-definitions\n\nuniform int mode;\nuniform float outline_scale;\n#ifndef DRAW_INSTANCED\nuniform int instanceOffset;\n#endif\n\nlayout(location = 0) in vec3 vboPosition;\nlayout(location = 1) in vec2 vboTex;\nlayout(location = 2) in vec3 vboNormalCoords;\nlayout(location = 3) in int _instanceId;\nlayout(location = 4) in vec3 vboDirection;\nlayout(location = 5) in int vboFlags;\n\nEZ_SSBO_LAYOUT(std140, EZQ_GL_BINDINGPOINT_ALIASMODEL_DRAWDATA) EZ_SSBO(AliasModelData) {\n\tAliasModel models[EZ_SSBO_ARRAY_SIZE(64)];\n};\n\nout vec2 fsTextureCoord;\nout vec2 fsAltTextureCoord;\n#ifdef EZQ_ALIASMODEL_FLATSHADING\nflat out vec4 fsBaseColor;\n#else\nout vec4 fsBaseColor;\n#endif\nflat out int fsFlags;\nflat out int fsTextureEnabled;\nflat out int fsMaterialSampler;\nflat out float fsMinLumaMix;\nflat out vec4 plrtopcolor;\nflat out vec4 plrbotcolor;\n\nvoid main()\n{\n#ifndef DRAW_INSTANCED\n\tuint modelId = _instanceId + instanceOffset;\n#else\n\tuint modelId = _instanceId;\n#endif\n\tfloat lerpFrac = models[modelId].lerpFraction;\n\n\tvec3 position = vboPosition;\n\tvec2 tex = vboTex;\n\tvec3 normalCoords = vboNormalCoords;\n\n\tfsFlags = models[modelId].flags;\n\tfsMinLumaMix = models[modelId].minLumaMix;\n\n\tplrtopcolor = models[modelId].topcolor;\n\tplrbotcolor = models[modelId].bottomcolor;\n\n#ifdef EZQ_ALIASMODEL_MUZZLEHACK\n\tlerpFrac = sign(lerpFrac) * max(lerpFrac, (vboFlags & AM_VERTEX_NOLERP));\n#endif\n\tposition = position + vboDirection * lerpFrac;\n\tfsMaterialSampler = models[modelId].materialTextureMapping;\n\n\tif (mode == EZQ_ALIAS_MODE_NORMAL) {\n\t\tgl_Position = projectionMatrix * models[modelId].modelView * vec4(position, 1);\n\n\t\tfsAltTextureCoord = fsTextureCoord = vec2(tex.s, tex.t);\n\t\tfsTextureEnabled = (models[modelId].flags & AMF_TEXTURE_MATERIAL);\n\n\t\t// Lighting: this is rough approximation\n\t\t//   Credit to mh @ http://forums.insideqc.com/viewtopic.php?f=3&t=2983\n\t\tif (models[modelId].shadelight < 1000) {\n\t\t\tfloat l = 1;\n\t\t\tvec3 angleVector = normalize(vec3(cos(-models[modelId].yaw_angle_rad), sin(-models[modelId].yaw_angle_rad), 1));\n\n\t\t\tl = floor((dot(normalCoords, angleVector) + 1) * 127) / 127;\n\t\t\tl = min((l * models[modelId].shadelight + models[modelId].ambientlight) / 256.0, 1);\n\n\t\t\tfsBaseColor = vec4(models[modelId].color.rgb * l, models[modelId].color.a);\n\t\t}\n\t\telse {\n\t\t\tfsBaseColor = models[modelId].color;\n\t\t}\n\t}\n\telse if (mode == EZQ_ALIAS_MODE_OUTLINES || mode == EZQ_ALIAS_MODE_OUTLINES_SPEC) {\n\t\tgl_Position = projectionMatrix * models[modelId].modelView * vec4(position + /*models[modelId].outlineNormalScale **/ normalCoords * outline_scale, 1);\n\t\tfsTextureCoord = vec2(tex.x, tex.y);\n\t}\n\telse {\n\t\tgl_Position = projectionMatrix * models[modelId].modelView * vec4(position + normalCoords * 0.5, 1);\n\t\tfsTextureCoord = vec2(tex.s * 2 + cos(time * 1.5), tex.t * 2 + sin(time * 1.1));\n\t\tfsAltTextureCoord = vec2(tex.s * 2 + cos(time * -0.5), tex.t * 2 + sin(time * -0.5));\n\t\tfsTextureEnabled = 1;\n\t\tfsMaterialSampler = 0;\n\t}\n\n\tif ((fsFlags & AMF_WEAPONMODEL) != 0) {\n#ifdef EZQ_REVERSED_DEPTH\n\t\tgl_Position.z *= 1 / 0.3;\n#else\n\t\tgl_Position.z *= 0.3;\n#endif\n\t}\n}\n"
  },
  {
    "path": "src/glsl/draw_sprites.fragment.glsl",
    "content": "#ezquake-definitions\n\nEZ_LAYOUT_BINDING(0) uniform sampler2DArray materialTex;\nuniform bool alpha_test;\n\nin vec3 TextureCoord;\nin vec4 fsColor;\n\nout vec4 frag_color;\n\nvoid main()\n{\n\tfrag_color = texture(materialTex, TextureCoord);\n\n\tfrag_color *= fsColor;\n\n\tif (alpha_test && frag_color.a < 0.3) {\n\t\tdiscard;\n\t}\n\n#ifdef DRAW_FOG\n\tfrag_color = applyFogBlend(frag_color, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n}\n"
  },
  {
    "path": "src/glsl/draw_sprites.vertex.glsl",
    "content": "#ezquake-definitions\n\nlayout(location=0) in vec3 position;\nlayout(location=1) in vec3 texCoord;\nlayout(location=2) in vec4 colour;\n\nout vec3 TextureCoord;\nout vec4 fsColor;\n\nvoid main()\n{\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1);\n\tTextureCoord = texCoord;\n\tfsColor = colour;\n}\n"
  },
  {
    "path": "src/glsl/draw_world.fragment.glsl",
    "content": "#ezquake-definitions\n\nuniform int draw_outlines;\n\n#ifdef DRAW_DETAIL_TEXTURES\nEZ_LAYOUT_BINDING(SAMPLER_DETAIL_TEXTURE) uniform sampler2D detailTex;\n#endif\n#ifdef DRAW_CAUSTIC_TEXTURES\nEZ_LAYOUT_BINDING(SAMPLER_CAUSTIC_TEXTURE) uniform sampler2D causticsTex;\n#endif\n#if defined(DRAW_SKYBOX)\nEZ_LAYOUT_BINDING(SAMPLER_SKYBOX_TEXTURE) uniform samplerCube skyTex;\n#elif defined(DRAW_SKYDOME)\nEZ_LAYOUT_BINDING(SAMPLER_SKYDOME_TEXTURE) uniform sampler2D skyDomeTex;\nEZ_LAYOUT_BINDING(SAMPLER_SKYDOME_CLOUDTEXTURE) uniform sampler2D skyDomeCloudTex;\n#endif\nEZ_LAYOUT_BINDING(SAMPLER_LIGHTMAP_TEXTURE) uniform sampler2DArray lightmapTex;\nEZ_LAYOUT_BINDING(SAMPLER_MATERIAL_TEXTURE_START) uniform sampler2DArray materialTex[SAMPLER_MATERIAL_TEXTURE_COUNT];\n\nin vec3 TextureCoord;\ncentroid in vec3 TexCoordLightmap;\n#ifdef DRAW_DETAIL_TEXTURES\nin vec2 DetailCoord;\n#endif\n#if defined(DRAW_LUMA_TEXTURES) || defined(DRAW_LUMA_TEXTURES_FB)\nin vec3 LumaCoord;\n#endif\nin vec3 FlatColor;\nflat in int Flags;\n#if defined(DRAW_SKYBOX) || defined(DRAW_SKYDOME)\nin vec3 Direction;\n#endif\n#ifdef DRAW_GEOMETRY\nin vec3 Normal;\nin vec4 UnClipped;\n#endif\n\n#ifdef DRAW_FLATFLOORS\nin float mix_floor;\n#endif\n#ifdef DRAW_FLATWALLS\nin float mix_wall;\n#endif\n#ifdef DRAW_ALPHATEST_ENABLED\nin float alpha;\n#endif\nflat in int SamplerNumber;\n\nlayout(location=0) out vec4 frag_colour;\n#ifdef DRAW_GEOMETRY\nlayout(location=1) out vec4 normal_texture;\n#endif\n\n// Drawflat mode TINTED... Amend texture color based on floor/wall\n// FIXME: common with glsl, do some kind of #include support\n#ifdef DRAW_DRAWFLAT_TINTED\nvec4 applyColorTinting(vec4 frag_colour)\n{\n#if defined(DRAW_FLATFLOORS) && defined(DRAW_FLATWALLS)\n\tvec3 inter = mix(frag_colour.rgb, frag_colour.rgb * r_floorcolor.rgb, mix_floor);\n\tfrag_colour = vec4(mix(inter, inter * r_wallcolor.rgb, mix_wall), frag_colour.a);\n#elif defined(DRAW_FLATWALLS)\n\tfrag_colour = vec4(mix(frag_colour.rgb, frag_colour.rgb * r_wallcolor.rgb, mix_wall), frag_colour.a);\n#elif defined(DRAW_FLATFLOORS)\n\tfrag_colour = vec4(mix(frag_colour.rgb, frag_colour.rgb * r_floorcolor.rgb, mix_floor), frag_colour.a);\n#endif\n\treturn frag_colour;\n}\n#elif defined(DRAW_DRAWFLAT_BRIGHT)\nvec4 applyColorTinting(vec4 frag_colour)\n{\n\t// evaluate brightness as per lightmap scaling (\"// kudos to Darel Rex Finley for his HSP color model\")\n\tfloat brightness = sqrt(frag_colour.r * frag_colour.r * 0.241 + frag_colour.g * frag_colour.g * 0.691 + frag_colour.b * frag_colour.b * 0.068);\n\n#if defined(DRAW_FLATWALLS)\n\tfrag_colour = vec4(mix(frag_colour.rgb, r_wallcolor.rgb * brightness, mix_wall), frag_colour.a);\n#endif\n#if defined(DRAW_FLATFLOORS)\n\tfrag_colour = vec4(mix(frag_colour.rgb, r_floorcolor.rgb * brightness, mix_floor), frag_colour.a);\n#endif\n\n\treturn frag_colour;\n}\n#else\n#define applyColorTinting(x) (x)\n#endif\n\n#if defined(DRAW_TEXTURELESS) && defined(DRAW_ALPHATEST_ENABLED)\n// We pass the original coordinates in TextureCoord & use them for alpha-test, but this is where color should come from\nout vec3 TextureLessCoord;\n#endif\n\nvoid main()\n{\n\tvec4 texColor;\n\tvec4 lmColor;\n\tint turbType;\n\n#ifdef DRAW_GEOMETRY\n\tint surface = Flags & EZQ_SURFACE_TYPE;\n\t// if the texture is a turb, force outline between it and regular textures, but not between other turbs of the same type\n\tfloat depth = (surface != 0) ? -float(surface) : abs(UnClipped.z / r_zFar);\n\tnormal_texture = vec4(Normal, depth);\n#endif\n\n\tif (draw_outlines == 1) {\n\t\tfrag_colour = vec4(0.5, 0.5, 0.5, 1);\n\t\treturn;\n\t}\n\n#ifdef DRAW_DETAIL_TEXTURES\n\tvec4 detail = texture(detailTex, DetailCoord);\n#endif\n#ifdef DRAW_CAUSTIC_TEXTURES\n\tvec4 caustic = texture(\n\t\tcausticsTex,\n\t\tvec2(\n\t\t\t(TextureCoord.s + sin(0.465 * (time + TextureCoord.t))) * -0.1234375,\n\t\t\t(TextureCoord.t + sin(0.465 * (time + TextureCoord.s))) * -0.1234375\n\t\t)\n\t);\n#endif\n#if defined(DRAW_LUMA_TEXTURES) || defined(DRAW_LUMA_TEXTURES_FB)\n\tvec4 lumaColor = texture(materialTex[SamplerNumber], LumaCoord);\n#endif\n\n\tvec3 tex = TextureCoord;\n\n\ttex.s = mix(TextureCoord.s, TextureCoord.s + (sin((TextureCoord.t + time) * 1.5) * 0.125), min(1, Flags & EZQ_SURFACE_TYPE));\n\ttex.t = mix(TextureCoord.t, TextureCoord.t + (sin((TextureCoord.s + time) * 1.5) * 0.125), min(1, Flags & EZQ_SURFACE_TYPE));\n\n\tlmColor = texture(lightmapTex, TexCoordLightmap);\n\ttexColor = texture(materialTex[SamplerNumber], tex);\n\n#ifdef DRAW_ALPHATEST_ENABLED\n\t#ifdef DRAW_TEXTURELESS\n\t\ttexColor = vec4(texture(materialTex[SamplerNumber], TextureLessCoord).rgb, texColor.a);\n\t#endif\n\tif ((Flags & EZQ_SURFACE_ALPHATEST) == EZQ_SURFACE_ALPHATEST && texColor.a < 0.5) {\n\t\tdiscard;\n\t}\n#endif\n\n#if defined(DRAW_ALPHATEST_ENABLED) || defined(DRAW_FOG)\n\t// Avoid black artifacts at border at edge of fence textures and guarantee that\n\t// fog function doesn't multiply with zero.\n\ttexColor = vec4(texColor.rgb, 1.0);\n#endif\n\n\tturbType = Flags & EZQ_SURFACE_TYPE;\n\tif (turbType != 0) {\n\t\t// Turb surface\n\t\tif (turbType != TEXTURE_TURB_SKY && r_fastturb != 0) {\n\t\t\tif (turbType == TEXTURE_TURB_WATER) {\n\t\t\t\tfrag_colour = r_watercolor;\n\t\t\t}\n\t\t\telse if (turbType == TEXTURE_TURB_SLIME) {\n\t\t\t\tfrag_colour = r_slimecolor;\n\t\t\t}\n\t\t\telse if (turbType == TEXTURE_TURB_LAVA) {\n\t\t\t\tfrag_colour = r_lavacolor;\n\t\t\t}\n\t\t\telse if (turbType == TEXTURE_TURB_TELE) {\n\t\t\t\tfrag_colour = r_telecolor;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfrag_colour = vec4(FlatColor, 1);\n\t\t\t}\n#ifdef DRAW_FOG\n\t\t\tfrag_colour = applyFog(frag_colour, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n#ifdef DRAW_ALPHATEST_ENABLED\n\t\t\tfrag_colour *= alpha;\n#endif\n\t\t}\n\t\telse if (turbType == TEXTURE_TURB_SKY) {\n#if defined(DRAW_SKYBOX)\n\t\t\tfrag_colour = texture(skyTex, Direction);\n#if defined(DRAW_SKYWIND)\n\t\t\tfloat t1 = skyWind.w;\n\t\t\tfloat t2 = fract(t1) - 0.5;\n\t\t\tfloat blend = abs(t1 * 2.0);\n\t\t\tvec3 dir = normalize(Direction);\n\t\t\tvec4 layer1 = texture(skyTex, dir + t1 * skyWind.xyz);\n\t\t\tvec4 layer2 = texture(skyTex, dir + t2 * skyWind.xyz);\n\t\t\tlayer1.a *= 1.0 - blend;\n\t\t\tlayer2.a *= blend;\n\t\t\tlayer1.rgb *= layer1.a;\n\t\t\tlayer2.rgb *= layer2.a;\n\t\t\tvec4 combined = layer1 + layer2;\n\t\t\tfrag_colour = vec4(frag_colour.rgb * (1.0 - combined.a) + combined.rgb, 1);\n#endif\n#elif defined(DRAW_SKYDOME)\n\t\t\tconst float len = 3.09375;\n\t\t\t// Flatten it out\n\t\t\tvec3 dir = normalize(vec3(Direction.x, Direction.y, 3 * Direction.z));\n\n\t\t\tvec4 skyColor = texture(skyDomeTex, vec2(skySpeedscale + dir.x * len, skySpeedscale + dir.y * len));\n\t\t\tvec4 cloudColor = texture(skyDomeCloudTex, vec2(skySpeedscale2 + dir.x * len, skySpeedscale2 + dir.y * len));\n\n\t\t\tfrag_colour = mix(skyColor, cloudColor, cloudColor.a);\n#else\n\t\t\tfrag_colour = r_skycolor;\n#endif\n#ifdef DRAW_FOG\n\t\t\tfrag_colour = vec4(mix(frag_colour.rgb, fogColor, skyFogMix), frag_colour.a);\n#endif\n\t\t}\n\t\telse {\n\t\t\tfrag_colour = texColor;\n\t\t\tif ((Flags & EZQ_SURFACE_LIT_TURB) > 0) {\n\t\t\t\tfrag_colour = vec4(lmColor.rgb, 1) * frag_colour;\n\t\t\t}\n#ifdef DRAW_FOG\n\t\t\tfrag_colour = applyFog(frag_colour, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n#ifdef DRAW_ALPHATEST_ENABLED\n\t\t\tfrag_colour *= alpha;\n#endif\n\t\t}\n\t}\n\telse {\n\t\t// Typical material\n#if defined(DRAW_DRAWFLAT_NORMAL) && defined(DRAW_FLATFLOORS) && defined(DRAW_FLATWALLS)\n\t\ttexColor = vec4(mix(mix(texColor.rgb, r_floorcolor.rgb, mix_floor), r_wallcolor.rgb, mix_wall), texColor.a);\n\t#ifdef DRAW_LUMA_TEXTURES\n\t\tlumaColor = vec4(0, 0, 0, 0);\n\t#endif\n#elif defined(DRAW_DRAWFLAT_NORMAL) && defined(DRAW_FLATFLOORS)\n\t\ttexColor = vec4(mix(texColor.rgb, r_floorcolor.rgb, mix_floor), texColor.a);\n\t#ifdef DRAW_LUMA_TEXTURES\n\t\tlumaColor = mix(lumaColor, vec4(0, 0, 0, 0), mix_floor);\n\t#endif\n#elif defined(DRAW_DRAWFLAT_NORMAL) && defined(DRAW_FLATWALLS)\n\t\ttexColor = vec4(mix(texColor.rgb, r_wallcolor.rgb, mix_wall), texColor.a);\n\t#ifdef DRAW_LUMA_TEXTURES\n\t\tlumaColor = mix(lumaColor, vec4(0, 0, 0, 0), mix_wall);\n\t#endif\n#endif\n\n#if defined(DRAW_LUMA_TEXTURES) && !defined(DRAW_LUMA_TEXTURES_FB)\n\t\ttexColor = vec4(mix(texColor.rgb, texColor.rgb + lumaColor.rgb, min(1, Flags & EZQ_SURFACE_HAS_LUMA)), texColor.a);\n#endif\n\t\ttexColor = applyColorTinting(texColor);\n\t\tfrag_colour = vec4(lmColor.rgb, 1) * texColor;\n#if defined(DRAW_LUMA_TEXTURES) && defined(DRAW_LUMA_TEXTURES_FB)\n\t\tlumaColor = applyColorTinting(lumaColor);\n\t\tfrag_colour = vec4(mix(frag_colour.rgb, frag_colour.rgb + lumaColor.rgb, min(1, Flags & EZQ_SURFACE_HAS_LUMA)), frag_colour.a);\n\t\tfrag_colour = vec4(mix(frag_colour.rgb, lumaColor.rgb, min(1, Flags & EZQ_SURFACE_HAS_FB) * lumaColor.a), frag_colour.a);\n#elif !defined(DRAW_LUMA_TEXTURES) && defined(DRAW_LUMA_TEXTURES_FB)\n\t\t// GL_DECAL\n\t\tlumaColor = applyColorTinting(lumaColor);\n\t\tfrag_colour = vec4(mix(frag_colour.rgb, lumaColor.rgb, min(1, Flags & EZQ_SURFACE_HAS_FB) * lumaColor.a), frag_colour.a);\n#endif\n\n#ifdef DRAW_CAUSTIC_TEXTURES\n\t\tfrag_colour = vec4(mix(frag_colour.rgb, caustic.rgb * frag_colour.rgb * 2.0, min(1, Flags & EZQ_SURFACE_UNDERWATER)), frag_colour.a);\n#endif\n\n#ifdef DRAW_DETAIL_TEXTURES\n\t\tfrag_colour = vec4(mix(frag_colour.rgb, detail.rgb * frag_colour.rgb * 2.0, min(1, Flags & EZQ_SURFACE_WORLD)), frag_colour.a);\n#endif\n\n#ifdef DRAW_FOG\n\t\tfrag_colour = applyFog(frag_colour, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n\n#ifdef DRAW_ALPHATEST_ENABLED\n\t\tfrag_colour *= alpha;\n#endif\n\t}\n}\n"
  },
  {
    "path": "src/glsl/draw_world.vertex.glsl",
    "content": "#ezquake-definitions\n\nlayout(location = 0) in vec3 position;\nlayout(location = 1) in vec3 tex;\nlayout(location = 2) in vec3 lightmapCoord;\nlayout(location = 3) in vec2 detailCoord;\nlayout(location = 4) in int _instanceId;\nlayout(location = 5) in int vboFlags;\nlayout(location = 6) in vec3 flatColor;\nlayout(location = 7) in int surfaceNumber;\n\n#ifndef DRAW_INSTANCED\nuniform int instanceOffset;\n#endif\n\ncentroid out vec3 TexCoordLightmap;\nout vec3 TextureCoord;\n#ifdef DRAW_TEXTURELESS\nout vec3 TextureLessCoord;\n#endif\n#if defined(DRAW_LUMA_TEXTURES) || defined(DRAW_LUMA_TEXTURES_FB)\nout vec3 LumaCoord;\n#endif\n#ifdef DRAW_DETAIL_TEXTURES\nout vec2 DetailCoord;\n#endif\nout vec3 FlatColor;\nflat out int Flags;\n#if defined(DRAW_SKYBOX) || defined(DRAW_SKYDOME)\nout vec3 Direction;\n#endif\n#ifdef DRAW_GEOMETRY\nout vec3 Normal;\nout vec4 UnClipped;\n\nEZ_SSBO_LAYOUT(std140, EZQ_GL_BINDINGPOINT_WORLDMODEL_SURFACES) EZ_SSBO(surface_data) {\n\tmodel_surface surfaces[EZ_SSBO_ARRAY_SIZE(8192)];\n};\n#endif\n\n#ifdef DRAW_FLATFLOORS\nout float mix_floor;\n#endif\n#ifdef DRAW_FLATWALLS\nout float mix_wall;\n#endif\n#ifdef DRAW_ALPHATEST_ENABLED\nout float alpha;\n#endif\nflat out int SamplerNumber;\n\nEZ_SSBO_LAYOUT(std140, EZQ_GL_BINDINGPOINT_BRUSHMODEL_DRAWDATA) EZ_SSBO(WorldCvars) {\n\tWorldDrawInfo drawInfo[EZ_SSBO_ARRAY_SIZE(64)];\n};\nEZ_SSBO_LAYOUT(std140, EZQ_GL_BINDINGPOINT_BRUSHMODEL_SAMPLERS) EZ_SSBO(SamplerMappingsBuffer) {\n\tSamplerMapping samplerMapping[EZ_SSBO_ARRAY_SIZE(256)];\n};\n\n\nvoid main()\n{\n#ifndef DRAW_INSTANCED\n\tint modelId = _instanceId + instanceOffset;\n#else\n\tint modelId = _instanceId;\n#endif\n\tint materialNumber = int(tex.z);\n\tfloat materialArrayIndex = samplerMapping[drawInfo[modelId].samplerBase + materialNumber].layer;\n\tint drawCallFlags = drawInfo[modelId].drawFlags;\n\tint textureFlags = samplerMapping[drawInfo[modelId].samplerBase + materialNumber].flags;\n#ifdef DRAW_ALPHATEST_ENABLED\n\talpha = drawInfo[modelId].alpha;\n#endif\n\tSamplerNumber = drawInfo[modelId].sampler;\n\n\tgl_Position = projectionMatrix * drawInfo[modelId].mvMatrix * vec4(position, 1.0);\n#ifdef DRAW_GEOMETRY\n\tNormal = surfaces[surfaceNumber].normal.xyz;\n\tUnClipped = drawInfo[modelId].mvMatrix * vec4(position, 1.0);\n#endif\n\n\tFlatColor = flatColor;\n\tFlags = vboFlags | drawCallFlags | textureFlags;\n\n\tif (lightmapCoord.z < 0) {\n\t\tTextureCoord = vec3(tex.xy, materialArrayIndex);\n\t\tTexCoordLightmap = vec3(0, 0, 0);\n#if defined(DRAW_SKYBOX) || defined(DRAW_SKYDOME)\n\t\tDirection = (position - cameraPosition);\n#if defined(DRAW_SKYBOX)\n\t\tDirection = vec3(-Direction.y, Direction.z, Direction.x);\n#endif\n#endif\n#ifdef DRAW_DETAIL_TEXTURES\n\t\tDetailCoord = vec2(0, 0);\n#endif\n#if defined(DRAW_LUMA_TEXTURES) || defined(DRAW_LUMA_TEXTURES_FB)\n\t\tLumaCoord = TextureCoord;\n#endif\n\n#ifdef DRAW_FLATFLOORS\n\t\tmix_floor = 0;\n#endif\n#ifdef DRAW_FLATWALLS\n\t\tmix_wall = 0;\n#endif\n\t}\n\telse {\n#if defined(DRAW_TEXTURELESS) && !defined(DRAW_ALPHATEST_ENABLED)\n\t\tTextureCoord = vec3(mix(tex.xy, vec2(0, 0), min(Flags & EZQ_SURFACE_WORLD, 1)), materialArrayIndex);\n#elif defined(DRAW_TEXTURELESS)\n\t\tTextureLessCoord = vec3(mix(tex.xy, vec2(0, 0), min(Flags & EZQ_SURFACE_WORLD, 1)), materialArrayIndex);\n\t\tTextureCoord = vec3(tex.xy, materialArrayIndex);\n#else\n\t\tTextureCoord = vec3(tex.xy, materialArrayIndex);\n#endif\n\n#if defined(DRAW_LUMA_TEXTURES) || defined(DRAW_LUMA_TEXTURES_FB)\n\t\tLumaCoord = (Flags & (EZQ_SURFACE_HAS_LUMA | EZQ_SURFACE_HAS_FB)) != 0 ? vec3(TextureCoord.st, TextureCoord.z + 1) : TextureCoord;\n#endif\n\t\tTexCoordLightmap = lightmapCoord;\n#ifdef DRAW_DETAIL_TEXTURES\n\t\tDetailCoord = detailCoord;\n#endif\n\n#ifdef DRAW_FLATFLOORS\n\t\tmix_floor = min(1, (Flags & EZQ_SURFACE_WORLD) * (Flags & EZQ_SURFACE_IS_FLOOR));\n#endif\n#ifdef DRAW_FLATWALLS\n\t\tmix_wall = min(1, (Flags & EZQ_SURFACE_WORLD) * (1 - mix_floor));\n#endif\n\t}\n}\n"
  },
  {
    "path": "src/glsl/fx_world_geometry.fragment.glsl",
    "content": "#ezquake-definitions\n\nEZ_LAYOUT_BINDING(0) uniform sampler2D normal_texture;\n\nuniform vec3 outline_color;\nuniform float outline_scale;\nuniform float outline_depth_threshold;\nuniform float outline_normal_threshold;\n\nin vec2 TextureCoord;\nout vec4 frag_colour;\n\nbool vec_nequ(vec3 a, vec3 b) {\n\treturn dot(a, b) < outline_normal_threshold;\n}\n\nvoid main()\n{\n\tivec2 coords = ivec2(TextureCoord.x * r_width, TextureCoord.y * r_height);\n\n\t// Scale the sampling offsets by outline_scale\n\tivec2 offset_x = ivec2(int(outline_scale), 0);\n\tivec2 offset_y = ivec2(0, int(outline_scale));\n\n\tvec4 center = texelFetch(normal_texture, coords, 0);\n\tvec4 left   = texelFetch(normal_texture, coords - offset_x, 0);\n\tvec4 right  = texelFetch(normal_texture, coords + offset_x, 0);\n\tvec4 up     = texelFetch(normal_texture, coords - offset_y, 0);\n\tvec4 down   = texelFetch(normal_texture, coords + offset_y, 0);\n\n\tbool ignore = center.a == left.a && center.a == right.a && center.a == up.a && center.a == down.a;\n\tif(ignore) {\n\t\tfrag_colour = vec4(0);\n\t\treturn;\n\t}\n\n\tif(center.a == 0) {\n\t\tfrag_colour = vec4(0);\n\t\treturn;\n\t}\n\n\tif ((left.a  != 0 && vec_nequ(center.rgb, left.rgb )) ||\n\t\t(right.a != 0 && vec_nequ(center.rgb, right.rgb)) ||\n\t\t(up.a    != 0 && vec_nequ(center.rgb, up.rgb   )) ||\n\t\t(down.a  != 0 && vec_nequ(center.rgb, down.rgb ))\n\t) {\n\t\tfrag_colour.rgb = outline_color;\n\t\tfrag_colour.a = 1;\n\t\treturn;\n\t}\n\n\tbool z_diff = r_zFar * abs((right.a - center.a) - (center.a - left.a)) > outline_depth_threshold;\n\tbool z_diff2 = r_zFar * abs((down.a - center.a) - (center.a - up.a)) > outline_depth_threshold;\n\n\tif (center.a != 0 && (\n\t(left.a  != 0 && right.a != 0 && z_diff) ||\n\t(down.a  != 0 && up.a    != 0 && z_diff2)\n\t)) {\n\t\tfrag_colour = vec4(outline_color, 1.0f);\n\t\treturn;\n\t}\n\tfrag_colour = vec4(0.0f);\n}\n"
  },
  {
    "path": "src/glsl/fx_world_geometry.vertex.glsl",
    "content": "#ezquake-definitions\n\nlayout(location = 0) in vec2 inPosition;\nlayout(location = 1) in vec2 inTextureCoord;\n\nout vec2 TextureCoord;\n\nvoid main()\n{\n\tgl_Position = vec4(inPosition, 0, 1);\n\tTextureCoord = inTextureCoord;\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_aliasmodel_shadow.fragment.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nvoid main()\n{\n\tgl_FragColor = vec4(0, 0, 0, 0.5);\n\n#ifdef DRAW_FOG\n\tgl_FragColor = applyFog(gl_FragColor, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_aliasmodel_shadow.vertex.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nattribute float flags;\n\nuniform float lerpFraction;\nuniform vec2 shadevector;\nuniform float lheight;\n\nvoid main()\n{\n\tfloat lerpFrac = lerpFraction;\n\n#ifdef EZQ_ALIASMODEL_MUZZLEHACK\n\t// #define AM_VERTEX_NOLERP 1 - no bittest in glsl 1.2\n\tlerpFrac = sign(lerpFrac) * max(lerpFrac, mod(flags, 2));\n#endif\n\n\tvec4 pos = gl_Vertex + lerpFrac * vec4(gl_MultiTexCoord1.xyz, 0);\n\n\tpos.x -= shadevector[0] * (pos[2] + lheight);\n\tpos.y -= shadevector[1] * (pos[2] + lheight);\n\tpos.z = (1 - lheight);\n\n\tgl_Position = gl_ModelViewProjectionMatrix * pos;\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_aliasmodel_shell.fragment.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nuniform sampler2D texSampler;\nuniform vec4 fsBaseColor1;\nuniform vec4 fsBaseColor2;\n\nvarying vec2 fsTextureCoord;\nvarying vec2 fsAltTextureCoord;\n\nvoid main()\n{\n\tvec4 tex = texture2D(texSampler, fsTextureCoord);\n\tvec4 alt = texture2D(texSampler, fsAltTextureCoord);\n\n\tgl_FragColor = tex * fsBaseColor1 + alt * fsBaseColor2;\n\n#ifdef DRAW_FOG\n\tgl_FragColor = applyFog(gl_FragColor, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_aliasmodel_shell.vertex.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nattribute float flags;\n\nvarying vec2 fsTextureCoord;\nvarying vec2 fsAltTextureCoord;\nuniform float lerpFraction;      // 0 to 1\nuniform vec4 scroll;\n\nvoid main()\n{\n\tfloat lerpFrac = lerpFraction;\n\n#ifdef EZQ_ALIASMODEL_MUZZLEHACK\n\t// #define AM_VERTEX_NOLERP 1 - no bittest in glsl 1.2\n\tlerpFrac = sign(lerpFrac) * max(lerpFrac, mod(flags, 2));\n#endif\n\n\tgl_Position = gl_ModelViewProjectionMatrix * ((gl_Vertex + lerpFrac * vec4(gl_MultiTexCoord1.xyz, 0)) + vec4(gl_Normal * 0.5, 0));\n\tfsTextureCoord = vec2(gl_MultiTexCoord0.s * 2 + scroll.x, gl_MultiTexCoord0.t * 2 + scroll.y);\n\tfsAltTextureCoord = vec2(gl_MultiTexCoord0.s * 2 + scroll.z, gl_MultiTexCoord0.t * 2 + scroll.a);\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_aliasmodel_std.fragment.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\n#ifdef TEXTURING_ENABLED\nuniform sampler2D texSampler;\nuniform float fsMinLumaMix;\n#endif\n\n#ifdef DRAW_CAUSTIC_TEXTURES\nuniform sampler2D causticsSampler;\nuniform float time;\nuniform float fsCausticEffects;\n#endif\n\n#if defined(TEXTURING_ENABLED) || defined(DRAW_CAUSTIC_TEXTURES)\nvarying vec2 fsTextureCoord;\n#endif\n\n#ifdef EZQ_ALIASMODEL_FLATSHADING\nvarying flat vec4 fsBaseColor;\n#else\nvarying vec4 fsBaseColor;\n#endif\n\nvoid main()\n{\n#ifdef BACKFACE_PASS\n\tgl_FragColor = fsBaseColor;\n#else\n\t#ifdef TEXTURING_ENABLED\n\t\tvec4 tex = texture2D(texSampler, fsTextureCoord);\n\t\tvec3 texMix = mix(tex.rgb, tex.rgb * fsBaseColor.rgb, max(fsMinLumaMix, tex.a));\n\n\t\tgl_FragColor = vec4(texMix, fsBaseColor.a);\n\t#else\n\t\tgl_FragColor = fsBaseColor;\n\t#endif\n\n\t#ifdef DRAW_CAUSTIC_TEXTURES\n\t\tvec4 causticCoord = vec4(\n\t\t\t// Using multipler of 3 here - not in other caustics logic but range\n\t\t\t//   isn't enough otherwise, effect too subtle\n\t\t\t(fsTextureCoord.s + sin(0.465 * (time + fsTextureCoord.t))) * 3 * -0.1234375,\n\t\t\t(fsTextureCoord.t + sin(0.465 * (time + fsTextureCoord.s))) * 3 * -0.1234375,\n\t\t\t0,\n\t\t\t1\n\t\t);\n\t\tvec4 caustic = texture2D(causticsSampler, (gl_TextureMatrix[1] * causticCoord).st);\n\n\t\t// FIXME: Do proper GL_DECAL etc\n\t\tgl_FragColor = vec4(caustic.rgb * gl_FragColor.rgb * 1.8, gl_FragColor.a);\n\t#endif\n#endif // BACKFACE_PASS\n\n#ifdef DRAW_FOG\n\tgl_FragColor = applyFog(gl_FragColor, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_aliasmodel_std.vertex.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nattribute float flags;\n\n#ifdef BACKFACE_PASS\n\n// for outline\nuniform float outlineScale;\n\n#else\n\n#if defined(TEXTURING_ENABLED) || defined(DRAW_CAUSTIC_TEXTURES)\nvarying vec2 fsTextureCoord;\n#endif\n\n#ifndef FULLBRIGHT_MODELS\nuniform vec3 angleVector;        // normalized\nuniform float shadelight;        // divided by 256 in C\nuniform float ambientlight;      // divided by 256 in C\n#endif\n\n#endif // BACKFACE_PASS (outlining)\n\nuniform float lerpFraction;      // 0 to 1\n#ifdef EZQ_ALIASMODEL_FLATSHADING\nvarying flat vec4 fsBaseColor;\n#else\nvarying vec4 fsBaseColor;\n#endif\n\n\n#define AM_VERTEX_NOLERP 1\n\nvoid main()\n{\n\tfloat lerpFrac = lerpFraction;\n#ifdef EZQ_ALIASMODEL_MUZZLEHACK\n\tlerpFrac = sign(lerpFrac) * max(lerpFrac, mod(flags, 2));\n#endif\n\n#ifdef BACKFACE_PASS\n\tgl_Position = gl_ModelViewProjectionMatrix * (gl_Vertex + lerpFrac * vec4(gl_MultiTexCoord1.xyz, 0) + vec4(outlineScale * gl_Normal, 0));\n\t// gl_Position += gl_ModelViewProjectionMatrix * \n\tfsBaseColor = gl_Color;\n#else\n\t\tgl_Position = gl_ModelViewProjectionMatrix * (gl_Vertex + lerpFrac * vec4(gl_MultiTexCoord1.xyz, 0));\n\t#if defined(TEXTURING_ENABLED) || defined(DRAW_CAUSTIC_TEXTURES)\n\t\tfsTextureCoord = gl_MultiTexCoord0.st;\n\t#endif // TEXTURING\n\n\t#ifdef FULLBRIGHT_MODELS\n\t\tfsBaseColor = gl_Color;\n\t#else\n\t\t// Lighting: this is rough approximation\n\t\t//   Credit to mh @ http://forums.insideqc.com/viewtopic.php?f=3&t=2983\n\t\tfloat l = (1 - step(1000, shadelight)) * min((dot(gl_Normal, angleVector) + 1) * shadelight + ambientlight, 1);\n\n\t\tfsBaseColor = vec4(gl_Color.rgb * l, gl_Color.a);\n\t#endif // FULLBRIGHT_MODELS\n#endif // BACKFACE_PASS\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_caustics.fragment.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nuniform sampler2D texSampler;\nuniform float time;\n\nvarying vec2 TextureCoord;\n\nvoid main()\n{\n\tvec2 tex;\n\n\ttex.s = (TextureCoord.s + 8 * sin((TextureCoord.t + time) * 0.465)) * 0.0234375;\n\ttex.t = (TextureCoord.t + 8 * sin((TextureCoord.s + time) * 0.465)) * 0.0234375;\n\n\tgl_FragColor = texture2D(texSampler, tex);\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_caustics.vertex.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nvarying vec2 TextureCoord;\n\nvoid main()\n{\n\tgl_Position = ftransform();\n\tTextureCoord = gl_MultiTexCoord0.st;\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_draw_sprites.fragment.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nuniform sampler2D materialSampler;\nuniform float alphaThreshold;\n\nvarying vec2 TextureCoord;\nvarying vec4 fsColor;\n\nvoid main()\n{\n\tgl_FragColor = texture2D(materialSampler, TextureCoord) * fsColor;\n\n#ifdef DRAW_FOG\n\tgl_FragColor = applyFogBlend(gl_FragColor, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_draw_sprites.vertex.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nvarying vec2 TextureCoord;\nvarying vec4 fsColor;\n\nvoid main()\n{\n\tgl_Position = ftransform();\n\tTextureCoord = gl_MultiTexCoord0.st;\n\tfsColor = gl_Color;\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_hud_images.fragment.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nuniform sampler2D primarySampler;\n#ifdef MIXED_SAMPLING\nuniform sampler2D secondarySampler;\n#endif\n\nvarying vec4 TextureCoord;\nvarying vec4 fsColor;\n\nvoid main()\n{\n\tgl_FragColor = texture2D(primarySampler, TextureCoord.st);\n\n#ifdef MIXED_SAMPLING\n\tgl_FragColor *= TextureCoord.a;\n\tgl_FragColor += texture2D(secondarySampler, TextureCoord.st) * (1 - TextureCoord.a);\n#endif\n\n#ifdef PREMULT_ALPHA_HACK\n\t// Some people prefer the smoothing effect from ezQuake < 3.5,\n\t//   caused by the alpha being blended incorrectly & effectively applied twice\n\tgl_FragColor.r *= gl_FragColor.a;\n\tgl_FragColor.g *= gl_FragColor.a;\n\tgl_FragColor.b *= gl_FragColor.a;\n#endif\n\n\tgl_FragColor *= fsColor;\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_hud_images.vertex.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nvarying vec4 TextureCoord;\nvarying vec4 fsColor;\n\nvoid main()\n{\n\tgl_Position = ftransform();\n\tTextureCoord = gl_MultiTexCoord0;\n\tfsColor = gl_Color;\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_post_process_screen.fragment.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nuniform sampler2D base;\n#ifdef EZ_POSTPROCESS_OVERLAY\nuniform sampler2D overlay;\n#endif\n\nuniform float gamma;\nuniform vec4 v_blend;\nuniform float contrast;\nuniform float r_inv_width;\nuniform float r_inv_height;\n\nvarying vec2 TextureCoord;\n\nvec4 sampleBase(void)\n{\n#ifdef EZ_POSTPROCESS_FXAA\n\t// This is technically not the correct place to apply FXAA as it should sample\n\t// a tonemapped, and gamma adjusted texture. However, the rendering pipeline\n\t// prohibits this as this post_process_screen shader performs both tonemapping\n\t// gamma, as well as combining HUD and world textures, so placing FXAA after\n\t// this shader would apply FXAA to the HUD as well, which is detrimental.\n\t// As it happens, applying FXAA to the world before tonemapping and gamma still\n\t// looks ok, so it's still valuable without shuffling around shaders.\n\t// Should world tonemap and gamma be broken out to a separate shader at some point\n\t// that shader would ideally then also populate luma in the alpha channel, to be\n\t// used by the FXAA algorithm instead of relying on green channel.\n\treturn FxaaPixelShader(\n\t\tTextureCoord,                       // FxaaFloat2 pos,\n\t\tFxaaFloat4(0.0f, 0.0f, 0.0f, 0.0f), // FxaaFloat4 fxaaConsolePosPos,\n\t\tbase,                               // FxaaTex tex,\n\t\tbase,                               // FxaaTex fxaaConsole360TexExpBiasNegOne,\n\t\tbase,                               // FxaaTex fxaaConsole360TexExpBiasNegTwo,\n\t\tvec2(r_inv_width, r_inv_height),    // FxaaFloat2 fxaaQualityRcpFrame,\n\t\tFxaaFloat4(0.0f, 0.0f, 0.0f, 0.0f), // FxaaFloat4 fxaaConsoleRcpFrameOpt,\n\t\tFxaaFloat4(0.0f, 0.0f, 0.0f, 0.0f), // FxaaFloat4 fxaaConsoleRcpFrameOpt2,\n\t\tFxaaFloat4(0.0f, 0.0f, 0.0f, 0.0f), // FxaaFloat4 fxaaConsole360RcpFrameOpt2,\n\t\t0.75f,                              // FxaaFloat fxaaQualitySubpix (default),\n\t\t0.166f,                             // FxaaFloat fxaaQualityEdgeThreshold (default),\n\t\t0.0f,                               // FxaaFloat fxaaQualityEdgeThresholdMin (default if green as luma),\n\t\t0.0f,                               // FxaaFloat fxaaConsoleEdgeSharpness,\n\t\t0.0f,                               // FxaaFloat fxaaConsoleEdgeThreshold,\n\t\t0.0f,                               // FxaaFloat fxaaConsoleEdgeThresholdMin,\n\t\tFxaaFloat4(0.0f, 0.0f, 0.0f, 0.0f)  // FxaaFloat4 fxaaConsole360ConstDir,\n\t);\n#else\n\treturn texture2D(base, TextureCoord);\n#endif\n}\n\n\nvoid main()\n{\n\tvec4 frag_colour = sampleBase();\n\n#ifdef EZ_POSTPROCESS_OVERLAY\n\tvec4 add = texture2D(overlay, TextureCoord);\n\tfrag_colour *= 1 - add.a;\n\tfrag_colour += add;\n#endif\n\n#ifdef EZ_POSTPROCESS_PALETTE\n\t// apply blend & contrast\n\tfrag_colour = vec4((frag_colour.rgb * v_blend.a + v_blend.rgb) * contrast, 1);\n\n\t// apply gamma\n\tfrag_colour = vec4(pow(frag_colour.rgb, vec3(gamma)), 1);\n#endif\n\n\tgl_FragColor = frag_colour;\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_post_process_screen.vertex.glsl",
    "content": "#ezquake-definitions\n\nvarying vec2 TextureCoord;\n\nvoid main()\n{\n\tgl_Position = gl_Vertex;\n\tTextureCoord = gl_MultiTexCoord0.xy;\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_sky.fragment.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\n#ifdef DRAW_SKYBOX\nuniform samplerCube skyTex;\n#ifdef DRAW_SKYWIND\nuniform vec4 skyWind;\n#endif\n#else\nuniform sampler2D skyDomeTex;\nuniform sampler2D skyDomeCloudTex;\n#endif\n\n#ifdef DRAW_FOG\nuniform float skyFogMix;\n#endif\n\nuniform float skySpeedscale;\nuniform float skySpeedscale2;\n\nvarying vec3 Direction;\n\nvoid main()\n{\n#if defined(DRAW_SKYBOX)\n\tgl_FragColor = textureCube(skyTex, Direction);\n#if defined(DRAW_SKYWIND)\n\tfloat t1 = skyWind.w;\n\tfloat t2 = fract(t1) - 0.5;\n\tfloat blend = abs(t1 * 2.0);\n\tvec3 dir = normalize(Direction);\n\tvec4 layer1 = textureCube(skyTex, dir + t1 * skyWind.xyz);\n\tvec4 layer2 = textureCube(skyTex, dir + t2 * skyWind.xyz);\n\tlayer1.a *= 1.0 - blend;\n\tlayer2.a *= blend;\n\tlayer1.rgb *= layer1.a;\n\tlayer2.rgb *= layer2.a;\n\tvec4 combined = layer1 + layer2;\n\tgl_FragColor = vec4(gl_FragColor.rgb * (1.0 - combined.a) + combined.rgb, 1);\n#endif\n#else\n\tconst float len = 3.09375;\n\t// Flatten it out\n\tvec3 dir = normalize(vec3(Direction.x, Direction.y, 3 * Direction.z));\n\n\tvec4 skyColor = texture2D(skyDomeTex, vec2(skySpeedscale + dir.x * len, skySpeedscale + dir.y * len));\n\tvec4 cloudColor = texture2D(skyDomeCloudTex, vec2(skySpeedscale2 + dir.x * len, skySpeedscale2 + dir.y * len));\n\n\tgl_FragColor = mix(skyColor, cloudColor, cloudColor.a);\n#endif\n\n#ifdef DRAW_FOG\n\tgl_FragColor = vec4(mix(gl_FragColor.rgb, fogColor, skyFogMix), gl_FragColor.a);\n#endif\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_sky.vertex.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nuniform vec3 cameraPosition;\n\nvarying vec3 Direction;\n\nvoid main()\n{\n\tgl_Position = ftransform();\n\tDirection = (gl_Vertex.xyz - cameraPosition);\n#if defined(DRAW_SKYBOX)\n\tDirection = vec3(-Direction.y, Direction.z, Direction.x);\n#endif\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_turbsurface.fragment.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\n#ifdef FLAT_COLOR\nuniform vec4 color;\n#else\nuniform sampler2D texSampler;\nvarying vec2 TextureCoord;\nuniform float alpha;\nuniform float time;\n#endif\n\nvoid main()\n{\n#ifdef FLAT_COLOR\n\tgl_FragColor = color;\n#else\n\tvec2 tex;\n\n\ttex.s = TextureCoord.s + (sin((TextureCoord.t + time) * 1.5) * 0.125);\n\ttex.t = TextureCoord.t + (sin((TextureCoord.s + time) * 1.5) * 0.125);\n\n\tgl_FragColor = texture2D(texSampler, tex);\n\tgl_FragColor *= alpha;\n#endif\n\n#ifdef DRAW_FOG\n\tgl_FragColor = applyFog(gl_FragColor, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_turbsurface.vertex.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\n#ifndef FLAT_COLOR\nvarying vec2 TextureCoord;\n#endif\n\nvoid main()\n{\n\tgl_Position = ftransform();\n#ifndef FLAT_COLOR\n\tTextureCoord = gl_MultiTexCoord0.st;\n#endif\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_world_drawflat.fragment.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\n#ifdef EZ_USE_TEXTURE_ARRAYS\n#extension GL_EXT_texture_array : enable\nuniform sampler2DArray texSampler;\ncentroid varying vec3 TextureCoord;\n#else\nuniform sampler2D texSampler;\ncentroid varying vec2 TextureCoord;\n#endif\n\nvarying float lightmapScale;\nvarying float fogScale;\n\nvarying vec4 color;\n\nvoid main()\n{\n\t// lightmap\n#ifdef EZ_USE_TEXTURE_ARRAYS\n\tvec4 lightmap = vec4(1, 1, 1, 1 + lightmapScale) - lightmapScale * texture2DArray(texSampler, TextureCoord);\n#else\n\tvec4 lightmap = vec4(1, 1, 1, 1 + lightmapScale) - lightmapScale * texture2D(texSampler, TextureCoord);\n#endif\n\n\tgl_FragColor = color * lightmap;\n\n#ifdef DRAW_FOG\n\tgl_FragColor = applyFog(gl_FragColor, fogScale * gl_FragCoord.z / gl_FragCoord.w);\n#endif\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_world_drawflat.vertex.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nattribute float style;\nvarying vec4 color;\n#ifdef EZ_USE_TEXTURE_ARRAYS\ncentroid varying vec3 TextureCoord;\n#else\ncentroid varying vec2 TextureCoord;\n#endif\nvarying float lightmapScale;\nvarying float fogScale;\n\nuniform vec4 wallcolor;\nuniform vec4 floorcolor;\nuniform vec4 skycolor;\nuniform vec4 watercolor;\nuniform vec4 slimecolor;\nuniform vec4 lavacolor;\nuniform vec4 telecolor;\n\nvoid main()\n{\n\tgl_Position = ftransform();\n#ifdef EZ_USE_TEXTURE_ARRAYS\n\tTextureCoord = gl_MultiTexCoord0.xyz;\n#else\n\tTextureCoord = gl_MultiTexCoord0.st;\n#endif\n\n\tlightmapScale = step(64, mod(style, 256));\n\n\tcolor = vec4(0, 0, 0, 1);\n\tcolor += mod(style, 2) * skycolor;\n\tcolor += mod(floor(style / 2), 2) * watercolor;\n\tcolor += mod(floor(style / 4), 2) * slimecolor;\n\tcolor += mod(floor(style / 8), 2) * lavacolor;\n\tcolor += mod(floor(style / 16), 2) * telecolor;\n\tcolor += mod(floor(style / 32), 2) * skycolor;\n\tcolor += mod(floor(style / 64), 2) * floorcolor;\n\tcolor += mod(floor(style / 128), 2) * wallcolor;\n\tcolor.a = 1;\n\n\t// don't apply z-fog to the sky\n\tfogScale = 1 - mod(style, 2);\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_world_secondpass.fragment.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nuniform sampler2D texSampler;\nvarying vec2 TextureCoord;\n\nvoid main()\n{\n\tgl_FragColor = texture2D(texSampler, TextureCoord);\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_world_secondpass.vertex.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nvarying vec2 TextureCoord;\n\nvoid main()\n{\n\tgl_Position = ftransform();\n\tTextureCoord = gl_MultiTexCoord0.st;\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_world_textured.fragment.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\n#ifdef EZ_USE_TEXTURE_ARRAYS\n#extension GL_EXT_texture_array : enable\n#endif\n\nvarying float mix_floor;\nvarying float mix_wall;\n\nuniform vec3 r_wallcolor;\nuniform vec3 r_floorcolor;\n\n// Drawflat mode TINTED... Amend texture color based on floor/wall\n// FIXME: common with glsl, do some kind of #include support\n#ifdef DRAW_DRAWFLAT_TINTED\nvec4 applyColorTinting(vec4 frag_colour)\n{\n#if defined(DRAW_FLATFLOORS) && defined(DRAW_FLATWALLS)\n\tvec3 inter = mix(frag_colour.rgb, frag_colour.rgb * r_floorcolor.rgb, mix_floor);\n\tfrag_colour = vec4(mix(inter, inter * r_wallcolor.rgb, mix_wall), frag_colour.a);\n#elif defined(DRAW_FLATWALLS)\n\tfrag_colour = vec4(mix(frag_colour.rgb, frag_colour.rgb * r_wallcolor.rgb, mix_wall), frag_colour.a);\n#elif defined(DRAW_FLATFLOORS)\n\tfrag_colour = vec4(mix(frag_colour.rgb, frag_colour.rgb * r_floorcolor.rgb, mix_floor), frag_colour.a);\n#endif\n\treturn frag_colour;\n}\n#elif defined(DRAW_DRAWFLAT_BRIGHT)\nvec4 applyColorTinting(vec4 frag_colour)\n{\n\t// evaluate brightness as per lightmap scaling (\"// kudos to Darel Rex Finley for his HSP color model\")\n\tfloat brightness = sqrt(frag_colour.r * frag_colour.r * 0.241 + frag_colour.g * frag_colour.g * 0.691 + frag_colour.b * frag_colour.b * 0.068);\n\n#if defined(DRAW_FLATWALLS)\n\tfrag_colour = vec4(mix(frag_colour.rgb, r_wallcolor.rgb * brightness, mix_wall), frag_colour.a);\n#endif\n#if defined(DRAW_FLATFLOORS)\n\tfrag_colour = vec4(mix(frag_colour.rgb, r_floorcolor.rgb * brightness, mix_floor), frag_colour.a);\n#endif\n\n\treturn frag_colour;\n}\n#elif defined(DRAW_DRAWFLAT_NORMAL)\nvec4 applyColorTinting(vec4 frag_colour)\n{\n#if defined(DRAW_FLATFLOORS) && defined(DRAW_FLATWALLS)\n\tfrag_colour = vec4(mix(mix(frag_colour.rgb, r_wallcolor.rgb, mix_wall), r_floorcolor.rgb, mix_floor), frag_colour.a);\n#elif defined(DRAW_FLATWALLS)\n\tfrag_colour = vec4(mix(frag_colour.rgb, r_wallcolor.rgb, mix_wall), frag_colour.a);\n#elif defined(DRAW_FLATFLOORS)\n\tfrag_colour = vec4(mix(frag_colour.rgb, r_floorcolor.rgb, mix_floor), frag_colour.a);\n#endif\n\treturn frag_colour;\n}\n#else\n#define applyColorTinting(x) (x)\n#endif\n\nuniform sampler2D texSampler;\nvarying vec2 TextureCoord;\n\n#ifdef DRAW_LIGHTMAPS\n#ifdef EZ_USE_TEXTURE_ARRAYS\nuniform sampler2DArray lightmapSampler;\ncentroid varying vec3 LightmapCoord;\n#else\nuniform sampler2D lightmapSampler;\ncentroid varying vec2 LightmapCoord;\n#endif\n#endif\n\n#ifdef DRAW_EXTRA_TEXTURES\nuniform sampler2D lumaSampler;\nvarying float lumaScale;\nvarying float fbScale;\n#endif\n\n#ifdef DRAW_CAUSTICS\nuniform sampler2D causticsSampler;\nvarying float causticsScale;\nuniform float time;\n#endif\n\n#ifdef DRAW_DETAIL\nuniform sampler2D detailSampler;\nvarying vec2 DetailCoord;\n#endif\n\n#if defined(DRAW_ALPHATEST_ENABLED)\n// 0 for textureless, 1 for normal\nuniform float texture_multiplier;\n#endif\n\nvoid main()\n{\n\tgl_FragColor = texture2D(texSampler, TextureCoord);\n#ifdef DRAW_ALPHATEST_ENABLED\n\tgl_FragColor = vec4(texture2D(texSampler, TextureCoord * texture_multiplier).rgb, gl_FragColor.a);\n\n\tif (gl_FragColor.a < 0.333) {\n\t\tdiscard;\n\t}\n\n\tgl_FragColor = vec4(gl_FragColor.rgb, 1.0);\n#endif\n\n#ifdef DRAW_EXTRA_TEXTURES\n\tvec4 lumaColor = texture2D(lumaSampler, TextureCoord);\n#endif\n\n#ifdef DRAW_LUMA_TEXTURES\n#ifndef DRAW_FULLBRIGHT_TEXTURES\n\t// Luma textures enabled but fullbright bmodels disabled - so we mix in the luma texture pre-lightmap (qqshka 2008)\n\tgl_FragColor = vec4(mix(gl_FragColor.rgb, gl_FragColor.rgb + lumaColor.rgb, lumaScale * lumaColor.a), gl_FragColor.a);\n#endif\n#endif\n\n\t// Apply color tinting\n\tgl_FragColor = applyColorTinting(gl_FragColor);\n\n#ifdef DRAW_LIGHTMAPS\n#ifdef EZ_USE_TEXTURE_ARRAYS\n\t// Apply lightmap from texture array\n\tgl_FragColor *= (vec4(1, 1, 1, 2) - texture2DArray(lightmapSampler, LightmapCoord));\n#else\n\t// Apply lightmap from simple atlas\n\tgl_FragColor *= (vec4(1, 1, 1, 2) - texture2D(lightmapSampler, LightmapCoord));\n#endif\n#endif\n\n#ifdef DRAW_LUMA_TEXTURES\n#ifdef DRAW_FULLBRIGHT_TEXTURES\n\t// Apply color tinting before merging\n\tlumaColor = applyColorTinting(lumaColor);\n\t// Lumas enabled, fullbrights enabled - mix in the luma post-lightmap\n\tgl_FragColor = vec4(mix(gl_FragColor.rgb, gl_FragColor.rgb + lumaColor.rgb, lumaScale * lumaColor.a), gl_FragColor.a);\n\t// Fullbrights - simple mix no add\n\tgl_FragColor = vec4(mix(gl_FragColor.rgb, lumaColor.rgb, fbScale * lumaColor.a), gl_FragColor.a);\n#endif\n#else\n#ifdef DRAW_FULLBRIGHT_TEXTURES\n\t// Apply color tinting before merging\n\tlumaColor = applyColorTinting(lumaColor);\n\t// Fullbrights, lumas disabled (GL_DECAL)\n\tgl_FragColor = vec4(mix(gl_FragColor.rgb, lumaColor.rgb, fbScale * lumaColor.a), gl_FragColor.a);\n#endif\n#endif\n\n#ifdef DRAW_CAUSTICS\n\tvec2 causticsCoord = vec2(\n\t\t(TextureCoord.s + sin(0.465 * (time + TextureCoord.t))) * -0.1234375,\n\t\t(TextureCoord.t + sin(0.465 * (time + TextureCoord.s))) * -0.1234375\n\t);\n\tvec4 caustic = texture2D(causticsSampler, causticsCoord);\n\n\tgl_FragColor = vec4(mix(gl_FragColor.rgb, caustic.rgb * gl_FragColor.rgb * 2.0, causticsScale), gl_FragColor.a);\n#endif\n\n#ifdef DRAW_DETAIL\n\tvec4 detail = texture2D(detailSampler, DetailCoord);\n\n\tgl_FragColor = vec4(detail.rgb * gl_FragColor.rgb * 2.0, gl_FragColor.a);\n#endif\n\n#ifdef DRAW_FOG\n\tgl_FragColor = applyFog(gl_FragColor, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n}\n"
  },
  {
    "path": "src/glsl/glc/glc_world_textured.vertex.glsl",
    "content": "#version 120\n\n#ezquake-definitions\n\nvarying vec2 TextureCoord;\nattribute float style;\n\n#ifdef DRAW_LIGHTMAPS\n#ifdef EZ_USE_TEXTURE_ARRAYS\ncentroid varying vec3 LightmapCoord;\n#else\ncentroid varying vec2 LightmapCoord;\n#endif\n#endif\n#ifdef DRAW_EXTRA_TEXTURES\nuniform float lumaMultiplier;\nuniform float fbMultiplier;\nvarying float lumaScale;\nvarying float fbScale;\n#endif\n#ifdef DRAW_DETAIL\nattribute vec2 detailCoordInput;\nvarying vec2 DetailCoord;\n#endif\n#ifdef DRAW_CAUSTICS\nattribute vec2 causticsCoord;\nvarying float causticsScale;\n#endif\n\n// 0 for textureless, 1 for normal\nuniform float texture_multiplier;\n\nvarying float mix_floor;\nvarying float mix_wall;\n\nvoid main()\n{\n\tgl_Position = ftransform();\n#ifdef DRAW_ALPHATEST_ENABLED\n\tTextureCoord = gl_MultiTexCoord0.st;\n#else\n\tTextureCoord = gl_MultiTexCoord0.st * texture_multiplier;\n#endif\n\n#ifdef DRAW_LIGHTMAPS\n#ifdef EZ_USE_TEXTURE_ARRAYS\n\tLightmapCoord = gl_MultiTexCoord1.xyz;\n#else\n\tLightmapCoord = gl_MultiTexCoord1.st;\n#endif\n#endif\n\n#ifdef DRAW_EXTRA_TEXTURES\n\tlumaScale = lumaMultiplier * mod(floor(style / 256), 2);\n\tfbScale = fbMultiplier * mod(floor(style / 1024), 2);\n#endif\n#ifdef DRAW_CAUSTICS\n\tcausticsScale = mod(floor(style / 512), 2);\n#endif\n#ifdef DRAW_DETAIL\n\tDetailCoord = detailCoordInput;\n#endif\n\n\tmix_floor = mod(floor(style / 64), 2);\n\tmix_wall = mod(floor(style / 128), 2);\n}\n"
  },
  {
    "path": "src/glsl/hud_draw_circle.fragment.glsl",
    "content": "#ezquake-definitions\n\nuniform vec4 color;\n\nout vec4 frag_colour;\n\nvoid main(void)\n{\n\tfrag_colour = color;\n}\n"
  },
  {
    "path": "src/glsl/hud_draw_circle.vertex.glsl",
    "content": "#ezquake-definitions\n\nlayout(location = 0) in vec2 position;\n\nuniform mat4 matrix;\n\nvoid main(void)\n{\n\tgl_Position = matrix * vec4(position, 0, 1);\n}\n"
  },
  {
    "path": "src/glsl/hud_draw_image.fragment.glsl",
    "content": "#ezquake-definitions\n\n#ifdef MIXED_SAMPLING\nEZ_LAYOUT_BINDING(0) uniform sampler2D tex[2];\n#else\nEZ_LAYOUT_BINDING(0) uniform sampler2D tex;\n#endif\n\nin vec2 TextureCoord;\nin vec4 Colour;\nin float AlphaTest;\n#ifdef MIXED_SAMPLING\nflat in int IsNearest;\n#endif\n\nout vec4 frag_colour;\n\nvoid main()\n{\n\tvec4 texColor;\n\n#ifdef MIXED_SAMPLING\n\ttexColor = texture(tex[IsNearest], TextureCoord);\n#else\n\ttexColor = texture(tex, TextureCoord);\n#endif\n\n\tif (AlphaTest != 0 && texColor.a < 0.666) {\n\t\tdiscard;\n\t}\n\n#ifdef PREMULT_ALPHA_HACK\n\t// Some people prefer the smoothing effect from ezQuake < 3.5,\n\t//   caused by the alpha being blended incorrectly & effectively applied twice\n\ttexColor.r *= texColor.a;\n\ttexColor.g *= texColor.a;\n\ttexColor.b *= texColor.a;\n#endif\n\n\tfrag_colour = texColor * Colour;\n}\n"
  },
  {
    "path": "src/glsl/hud_draw_image.geometry.glsl",
    "content": "#ezquake-definitions\n\n// Converts a single point to a quad with correct texture co-ords for character\n\nlayout(points) in;\nlayout(triangle_strip, max_vertices = 4) out;\n\nin vec4 gPositionTL[1];\nin vec4 gPositionBR[1];\nin vec2 gTexCoordTL[1];\nin vec2 gTexCoordBR[1];\nin vec4 gColour[1];\nin float gAlphaTest[1];\n\nout vec2 TextureCoord;\nout vec4 Colour;\nout float AlphaTest;\n\nvoid main()\n{\n\tColour = gColour[0];\n\tAlphaTest = gAlphaTest[0];\n\n\tgl_Position = gPositionTL[0];\n\tTextureCoord = gTexCoordTL[0];\n\tEmitVertex();\n\n\tgl_Position = vec4(gPositionTL[0].x, gPositionBR[0].y, 0, 1);\n\tTextureCoord = vec2(gTexCoordTL[0].s, gTexCoordBR[0].t);\n\tEmitVertex();\n\n\tgl_Position = vec4(gPositionBR[0].x, gPositionTL[0].y, 0, 1);\n\tTextureCoord = vec2(gTexCoordBR[0].s, gTexCoordTL[0].t);\n\tEmitVertex();\n\n\tgl_Position = gPositionBR[0];\n\tTextureCoord = gTexCoordBR[0];\n\tEmitVertex();\n\n\tEndPrimitive();\n}\n"
  },
  {
    "path": "src/glsl/hud_draw_image.vertex.glsl",
    "content": "#ezquake-definitions\n\nlayout(location = 0) in vec2 inPosition;\nlayout(location = 1) in vec2 inTexCoord;\nlayout(location = 2) in vec4 inColour;\nlayout(location = 3) in int inFlags;\n\nout vec2 TextureCoord;\nout vec4 Colour;\nout float AlphaTest;\n#ifdef MIXED_SAMPLING\nflat out int IsNearest;\n#endif\n\nvoid main()\n{\n\tgl_Position = vec4(inPosition, 0, 1);\n\tTextureCoord = inTexCoord;\n\tColour = inColour;\n#ifdef MIXED_SAMPLING\n\tIsNearest = (inFlags & IMAGEPROG_FLAGS_NEAREST) != 0 ? 1 : 0;\n#endif\n\tAlphaTest = (inFlags & IMAGEPROG_FLAGS_TEXT) * r_alphatestfont + (inFlags & IMAGEPROG_FLAGS_ALPHATEST);\n}\n"
  },
  {
    "path": "src/glsl/hud_draw_line.fragment.glsl",
    "content": "#ezquake-definitions\n\nin vec4 inColor;\nout vec4 color;\n\nvoid main(void)\n{\n\tcolor = inColor;\n}\n"
  },
  {
    "path": "src/glsl/hud_draw_line.vertex.glsl",
    "content": "#ezquake-definitions\n\nlayout(location=0) in vec3 position;\nlayout(location=1) in vec4 color;\n\nout vec4 inColor;\n\nvoid main(void)\n{\n\tgl_Position = vec4(position, 1);\n\tinColor = color;\n}\n"
  },
  {
    "path": "src/glsl/hud_draw_polygon.fragment.glsl",
    "content": "#ezquake-definitions\n\nuniform vec4 color;\n\nout vec4 frag_colour;\n\nvoid main(void)\n{\n\tfrag_colour = color;\n}\n"
  },
  {
    "path": "src/glsl/hud_draw_polygon.vertex.glsl",
    "content": "#ezquake-definitions\n\nlayout(location = 0) in vec3 position;\n\nvoid main(void)\n{\n\tgl_Position = vec4(position, 1);\n}\n"
  },
  {
    "path": "src/glsl/lighting.compute.glsl",
    "content": "#ezquake-definitions\n\nlayout(local_size_x = HW_LIGHTING_BLOCK_SIZE, local_size_y = HW_LIGHTING_BLOCK_SIZE) in;\nEZ_LAYOUT_BINDING(0) layout(rgba32ui) uniform uimage2DArray sourceBlocklights;\nEZ_LAYOUT_BINDING(1) layout(rgba8)    uniform image2DArray  destinationLightmap;\nEZ_LAYOUT_BINDING(2) layout(rgba32i)  uniform iimage2DArray sourceLightmapData;\n\nEZ_SSBO_LAYOUT(std140, EZQ_GL_BINDINGPOINT_WORLDMODEL_SURFACES) EZ_SSBO(surface_data) {\n\tmodel_surface surfaces[EZ_SSBO_ARRAY_SIZE(8192)];\n};\nEZ_SSBO_LAYOUT(std430, EZQ_GL_BINDINGPOINT_LIGHTSTYLES) EZ_SSBO(lightstyle_data) {\n\tuint dlightstyles[MAX_LIGHTSTYLES];\n};\nEZ_SSBO_LAYOUT(std430, EZQ_GL_BINDINGPOINT_SURFACES_TO_LIGHT) EZ_SSBO(todolist_data) {\n\tuint surfaces_to_light[EZ_SSBO_ARRAY_SIZE(8192)];\n};\n\nuniform int firstLightmap;\n\nvoid main()\n{\n\tint i;\n\n\tivec3 coord = ivec3(gl_WorkGroupID.xy * HW_LIGHTING_BLOCK_SIZE + gl_LocalInvocationID.xy, gl_WorkGroupID.z + firstLightmap);\n\tuvec4 blocklight = imageLoad(sourceBlocklights, coord);\n\tivec4 srcData = imageLoad(sourceLightmapData, coord);\n\n\tint surfaceNumber = srcData.x;\n\tif (surfaceNumber >= 0) {\n\n\t\tif ((surfaces_to_light[surfaceNumber / 32] & (1 << (surfaceNumber % 32))) != 0) {\n\t\t\tfloat sdelta = float(srcData.y);\n\t\t\tfloat tdelta = float(srcData.z);\n\t\t\tuvec4 mapIndexes = uvec4(\n\t\t\t\t(blocklight.a >> 24) & 0xFF,\n\t\t\t\t(blocklight.a >> 16) & 0xFF,\n\t\t\t\t(blocklight.a >> 8) & 0xFF,\n\t\t\t\t(blocklight.a >> 0) & 0xFF\n\t\t\t);\n\t\t\tblocklight.a = 0;\n\n\t\t\tvec4 Plane = surfaces[surfaceNumber].normal;\n\t\t\tvec4 PlaneMins0 = surfaces[surfaceNumber].vecs0;\n\t\t\tvec4 PlaneMins1 = surfaces[surfaceNumber].vecs1;\n\n\t\t\tfloat vlen0 = PlaneMins0.w;\n\t\t\tfloat vlen1 = PlaneMins1.w;\n\n\t\t\t// Build static lights: default to black\n\t\t\tvec4 baseLightmap = vec4(0, 0, 0, 0);\n\t\t\tif (mapIndexes.a < 64) {\n\t\t\t\tbaseLightmap = (blocklight & 0xFF) * dlightstyles[mapIndexes.a];\n\t\t\t\tif (mapIndexes.b < 64) {\n\t\t\t\t\tblocklight >>= 8;\n\t\t\t\t\tbaseLightmap += (blocklight & 0xFF) * dlightstyles[mapIndexes.b];\n\t\t\t\t\tif (mapIndexes.g < 64) {\n\t\t\t\t\t\tblocklight >>= 8;\n\t\t\t\t\t\tbaseLightmap += (blocklight & 0xFF) * dlightstyles[mapIndexes.g];\n\t\t\t\t\t\tif (mapIndexes.r < 64) {\n\t\t\t\t\t\t\tblocklight >>= 8;\n\t\t\t\t\t\t\tbaseLightmap += (blocklight & 0xFF) * dlightstyles[mapIndexes.r];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbaseLightmap *= 1.0 / (256.0 * 256.0);\n\n\t\t\t// Dynamic lights\n\t\t\tfor (i = 0; i < lightsActive; ++i) {\n\t\t\t\tfloat dist = dot(lightPositions[i].xyz, Plane.xyz) - Plane.a;\n\t\t\t\tfloat rad = lightPositions[i].a - abs(dist);\n\t\t\t\tfloat minlight = lightColors[i].a;\n\n\t\t\t\tif (rad >= minlight) {\n\t\t\t\t\tminlight = rad - minlight;\n\n\t\t\t\t\tvec3 impact = lightPositions[i].xyz - Plane.xyz * dist;\n\t\t\t\t\tvec2 local = vec2(dot(impact, PlaneMins0.xyz), dot(impact, PlaneMins1.xyz));\n\n\t\t\t\t\tint sd = int(abs(local[0] - sdelta) * vlen0); // sdelta = s * (1 << surf->lmshift) + surf->texturemins[0] - surf->lmvecs[0][3];\n\t\t\t\t\tint td = int(abs(local[1] - tdelta) * vlen1); // tdelta = t * (1 << surf->lmshift) + surf->texturemins[1] - surf->lmvecs[1][3];\n\n\t\t\t\t\tif (sd > td) {\n\t\t\t\t\t\tdist = sd + (td >> 1);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tdist = td + (sd >> 1);\n\t\t\t\t\t}\n\t\t\t\t\tif (dist < minlight) {\n\t\t\t\t\t\t// Increase blocklights...\n\t\t\t\t\t\tfloat increase = (rad - dist) / 128.0f;\n\n\t\t\t\t\t\tbaseLightmap.r += increase * lightColors[i].r;\n\t\t\t\t\t\tbaseLightmap.g += increase * lightColors[i].g;\n\t\t\t\t\t\tbaseLightmap.b += increase * lightColors[i].b;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Scale back... if we do simple scaling colour ratios will be lost\n\t\t\t//   (red 1.5 green 1.0 would become ... whatever red+green is)\n\t\t\tbaseLightmap *= lightScale;\n\t\t\tbaseLightmap /= max(1, max(baseLightmap.r, max(baseLightmap.g, baseLightmap.b)));\n\t\t\tbaseLightmap.a = 1;\n\n\t\t\timageStore(destinationLightmap, coord, baseLightmap);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/glsl/post_process_screen.fragment.glsl",
    "content": "#ezquake-definitions\n\nin vec2 TextureCoord;\nout vec4 frag_colour;\n\nEZ_LAYOUT_BINDING(0) uniform sampler2D base;\n#ifdef EZ_POSTPROCESS_OVERLAY\nEZ_LAYOUT_BINDING(1) uniform sampler2D overlay;\n#endif // EZ_POSTPROCESS_OVERLAY\n\nvec4 sampleBase(void)\n{\n#ifdef EZ_POSTPROCESS_FXAA\n\t// This is technically not the correct place to apply FXAA as it should sample\n\t// a tonemapped, and gamma adjusted texture. However, the rendering pipeline\n\t// prohibits this as this post_process_screen shader performs both tonemapping\n\t// gamma, as well as combining HUD and world textures, so placing FXAA after\n\t// this shader would apply FXAA to the HUD as well, which is detrimental.\n\t// As it happens, applying FXAA to the world before tonemapping and gamma still\n\t// looks ok, so it's still valuable without shuffling around shaders.\n\t// Should world tonemap and gamma be broken out to a separate shader at some point\n\t// that shader would ideally then also populate luma in the alpha channel, to be\n\t// used by the FXAA algorithm instead of relying on green channel.\n\treturn FxaaPixelShader(\n\t\tTextureCoord,                       // FxaaFloat2 pos,\n\t\tFxaaFloat4(0.0f, 0.0f, 0.0f, 0.0f), // FxaaFloat4 fxaaConsolePosPos,\n\t\tbase,                               // FxaaTex tex,\n\t\tbase,                               // FxaaTex fxaaConsole360TexExpBiasNegOne,\n\t\tbase,                               // FxaaTex fxaaConsole360TexExpBiasNegTwo,\n\t\tvec2(r_inv_width, r_inv_height),    // FxaaFloat2 fxaaQualityRcpFrame,\n\t\tFxaaFloat4(0.0f, 0.0f, 0.0f, 0.0f), // FxaaFloat4 fxaaConsoleRcpFrameOpt,\n\t\tFxaaFloat4(0.0f, 0.0f, 0.0f, 0.0f), // FxaaFloat4 fxaaConsoleRcpFrameOpt2,\n\t\tFxaaFloat4(0.0f, 0.0f, 0.0f, 0.0f), // FxaaFloat4 fxaaConsole360RcpFrameOpt2,\n\t\t0.75f,                              // FxaaFloat fxaaQualitySubpix (default),\n\t\t0.166f,                             // FxaaFloat fxaaQualityEdgeThreshold (default),\n\t\t0.0f,                               // FxaaFloat fxaaQualityEdgeThresholdMin (default if green as luma),\n\t\t0.0f,                               // FxaaFloat fxaaConsoleEdgeSharpness,\n\t\t0.0f,                               // FxaaFloat fxaaConsoleEdgeThreshold,\n\t\t0.0f,                               // FxaaFloat fxaaConsoleEdgeThresholdMin,\n\t\tFxaaFloat4(0.0f, 0.0f, 0.0f, 0.0f)  // FxaaFloat4 fxaaConsole360ConstDir,\n\t);\n#else\n\treturn texture(base, TextureCoord);\n#endif\n}\n\n#ifdef EZ_POSTPROCESS_OVERLAY\nvec4 sampleOverlay(void)\n{\n\treturn texture(overlay, TextureCoord);\n}\n#endif // EZ_POSTPROCESS_OVERLAY\n\nvoid main()\n{\n\tvec4 result = sampleBase();\n#ifdef EZ_POSTPROCESS_TONEMAP\n\tresult.r = result.r / (result.r + 1);\n\tresult.g = result.g / (result.g + 1);\n\tresult.b = result.b / (result.b + 1);\n#endif\n#ifdef EZ_POSTPROCESS_OVERLAY\n\tvec4 add = sampleOverlay();\n\tresult *= 1 - add.a;\n\tresult += add;\n#endif\n\n#ifdef EZ_POSTPROCESS_PALETTE\n\t// apply blend & contrast\n\tresult = vec4((result.rgb * v_blend.a + v_blend.rgb) * contrast, 1);\n\n\t// apply gamma\n\tresult = vec4(pow(result.rgb, vec3(gamma)), 1);\n#endif\n\n\tfrag_colour = result;\n}\n"
  },
  {
    "path": "src/glsl/post_process_screen.vertex.glsl",
    "content": "#ezquake-definitions\n\nlayout(location = 0) in vec2 inPosition;\nlayout(location = 1) in vec2 inTextureCoord;\n\nout vec2 TextureCoord;\n\nvoid main()\n{\n\tgl_Position = vec4(inPosition, 0, 1);\n\tTextureCoord = inTextureCoord;\n}\n"
  },
  {
    "path": "src/glsl/shared/fxaa.h.glsl",
    "content": "//----------------------------------------------------------------------------------\n// File:        es3-kepler\\FXAA/FXAA3_11.h\n// SDK Version: v3.00 \n// Email:       gameworks@nvidia.com\n// Site:        http://developer.nvidia.com/\n//\n// Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions\n// are met:\n//  * Redistributions of source code must retain the above copyright\n//    notice, this list of conditions and the following disclaimer.\n//  * Redistributions in binary form must reproduce the above copyright\n//    notice, this list of conditions and the following disclaimer in the\n//    documentation and/or other materials provided with the distribution.\n//  * Neither the name of NVIDIA CORPORATION nor the names of its\n//    contributors may be used to endorse or promote products derived\n//    from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY\n// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n//\n//----------------------------------------------------------------------------------\n/*============================================================================\n\n\n                    NVIDIA FXAA 3.11 by TIMOTHY LOTTES\n\n------------------------------------------------------------------------------\n                           INTEGRATION CHECKLIST\n------------------------------------------------------------------------------\n(1.)\nIn the shader source, setup defines for the desired configuration.\nWhen providing multiple shaders (for different presets),\nsimply setup the defines differently in multiple files.\nExample,\n\n  #define FXAA_PC 1\n  #define FXAA_HLSL_5 1\n  #define FXAA_QUALITY__PRESET 12\n\nOr,\n\n  #define FXAA_360 1\n  \nOr,\n\n  #define FXAA_PS3 1\n  \nEtc.\n\n(2.)\nThen include this file,\n\n  #include \"Fxaa3_11.h\"\n\n(3.)\nThen call the FXAA pixel shader from within your desired shader.\nLook at the FXAA Quality FxaaPixelShader() for docs on inputs.\nAs for FXAA 3.11 all inputs for all shaders are the same \nto enable easy porting between platforms.\n\n  return FxaaPixelShader(...);\n\n(4.)\nInsure pass prior to FXAA outputs RGBL (see next section).\nOr use,\n\n  #define FXAA_GREEN_AS_LUMA 1\n\n(5.)\nSetup engine to provide the following constants\nwhich are used in the FxaaPixelShader() inputs,\n\n  FxaaFloat2 fxaaQualityRcpFrame,\n  FxaaFloat4 fxaaConsoleRcpFrameOpt,\n  FxaaFloat4 fxaaConsoleRcpFrameOpt2,\n  FxaaFloat4 fxaaConsole360RcpFrameOpt2,\n  FxaaFloat fxaaQualitySubpix,\n  FxaaFloat fxaaQualityEdgeThreshold,\n  FxaaFloat fxaaQualityEdgeThresholdMin,\n  FxaaFloat fxaaConsoleEdgeSharpness,\n  FxaaFloat fxaaConsoleEdgeThreshold,\n  FxaaFloat fxaaConsoleEdgeThresholdMin,\n  FxaaFloat4 fxaaConsole360ConstDir\n\nLook at the FXAA Quality FxaaPixelShader() for docs on inputs.\n\n(6.)\nHave FXAA vertex shader run as a full screen triangle,\nand output \"pos\" and \"fxaaConsolePosPos\" \nsuch that inputs in the pixel shader provide,\n\n  // {xy} = center of pixel\n  FxaaFloat2 pos,\n\n  // {xy__} = upper left of pixel\n  // {__zw} = lower right of pixel\n  FxaaFloat4 fxaaConsolePosPos,\n\n(7.)\nInsure the texture sampler(s) used by FXAA are set to bilinear filtering.\n\n\n------------------------------------------------------------------------------\n                    INTEGRATION - RGBL AND COLORSPACE\n------------------------------------------------------------------------------\nFXAA3 requires RGBL as input unless the following is set, \n\n  #define FXAA_GREEN_AS_LUMA 1\n\nIn which case the engine uses green in place of luma,\nand requires RGB input is in a non-linear colorspace.\n\nRGB should be LDR (low dynamic range).\nSpecifically do FXAA after tonemapping.\n\nRGB data as returned by a texture fetch can be non-linear,\nor linear when FXAA_GREEN_AS_LUMA is not set.\nNote an \"sRGB format\" texture counts as linear,\nbecause the result of a texture fetch is linear data.\nRegular \"RGBA8\" textures in the sRGB colorspace are non-linear.\n\nIf FXAA_GREEN_AS_LUMA is not set,\nluma must be stored in the alpha channel prior to running FXAA.\nThis luma should be in a perceptual space (could be gamma 2.0).\nExample pass before FXAA where output is gamma 2.0 encoded,\n\n  color.rgb = ToneMap(color.rgb); // linear color output\n  color.rgb = sqrt(color.rgb);    // gamma 2.0 color output\n  return color;\n\nTo use FXAA,\n\n  color.rgb = ToneMap(color.rgb);  // linear color output\n  color.rgb = sqrt(color.rgb);     // gamma 2.0 color output\n  color.a = dot(color.rgb, FxaaFloat3(0.299, 0.587, 0.114)); // compute luma\n  return color;\n\nAnother example where output is linear encoded,\nsay for instance writing to an sRGB formated render target,\nwhere the render target does the conversion back to sRGB after blending,\n\n  color.rgb = ToneMap(color.rgb); // linear color output\n  return color;\n\nTo use FXAA,\n\n  color.rgb = ToneMap(color.rgb); // linear color output\n  color.a = sqrt(dot(color.rgb, FxaaFloat3(0.299, 0.587, 0.114))); // compute luma\n  return color;\n\nGetting luma correct is required for the algorithm to work correctly.\n\n\n------------------------------------------------------------------------------\n                          BEING LINEARLY CORRECT?\n------------------------------------------------------------------------------\nApplying FXAA to a framebuffer with linear RGB color will look worse.\nThis is very counter intuitive, but happends to be true in this case.\nThe reason is because dithering artifacts will be more visiable \nin a linear colorspace.\n\n\n------------------------------------------------------------------------------\n                             COMPLEX INTEGRATION\n------------------------------------------------------------------------------\nQ. What if the engine is blending into RGB before wanting to run FXAA?\n\nA. In the last opaque pass prior to FXAA,\n   have the pass write out luma into alpha.\n   Then blend into RGB only.\n   FXAA should be able to run ok\n   assuming the blending pass did not any add aliasing.\n   This should be the common case for particles and common blending passes.\n\nA. Or use FXAA_GREEN_AS_LUMA.\n\n============================================================================*/\n\n/*============================================================================\n\n                             INTEGRATION KNOBS\n\n============================================================================*/\n//\n// FXAA_PS3 and FXAA_360 choose the console algorithm (FXAA3 CONSOLE).\n// FXAA_360_OPT is a prototype for the new optimized 360 version.\n//\n// 1 = Use API.\n// 0 = Don't use API.\n//\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_PS3\n    #define FXAA_PS3 0\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_360\n    #define FXAA_360 0\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_360_OPT\n    #define FXAA_360_OPT 0\n#endif\n/*==========================================================================*/\n#ifndef FXAA_PC\n    //\n    // FXAA Quality\n    // The high quality PC algorithm.\n    //\n    #define FXAA_PC 0\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_PC_CONSOLE\n    //\n    // The console algorithm for PC is included\n    // for developers targeting really low spec machines.\n    // Likely better to just run FXAA_PC, and use a really low preset.\n    //\n    #define FXAA_PC_CONSOLE 0\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_GLSL_120\n    #define FXAA_GLSL_120 0\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_GLSL_130\n    #define FXAA_GLSL_130 0\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_HLSL_3\n    #define FXAA_HLSL_3 0\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_HLSL_4\n    #define FXAA_HLSL_4 0\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_HLSL_5\n    #define FXAA_HLSL_5 0\n#endif\n/*==========================================================================*/\n#ifndef FXAA_GREEN_AS_LUMA\n    //\n    // For those using non-linear color,\n    // and either not able to get luma in alpha, or not wanting to,\n    // this enables FXAA to run using green as a proxy for luma.\n    // So with this enabled, no need to pack luma in alpha.\n    //\n    // This will turn off AA on anything which lacks some amount of green.\n    // Pure red and blue or combination of only R and B, will get no AA.\n    //\n    // Might want to lower the settings for both,\n    //    fxaaConsoleEdgeThresholdMin\n    //    fxaaQualityEdgeThresholdMin\n    // In order to insure AA does not get turned off on colors \n    // which contain a minor amount of green.\n    //\n    // 1 = On.\n    // 0 = Off.\n    //\n    #define FXAA_GREEN_AS_LUMA 0\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_EARLY_EXIT\n    //\n    // Controls algorithm's early exit path.\n    // On PS3 turning this ON adds 2 cycles to the shader.\n    // On 360 turning this OFF adds 10ths of a millisecond to the shader.\n    // Turning this off on console will result in a more blurry image.\n    // So this defaults to on.\n    //\n    // 1 = On.\n    // 0 = Off.\n    //\n    #define FXAA_EARLY_EXIT 1\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_DISCARD\n    //\n    // Only valid for PC OpenGL currently.\n    // Probably will not work when FXAA_GREEN_AS_LUMA = 1.\n    //\n    // 1 = Use discard on pixels which don't need AA.\n    //     For APIs which enable concurrent TEX+ROP from same surface.\n    // 0 = Return unchanged color on pixels which don't need AA.\n    //\n    #define FXAA_DISCARD 0\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_FAST_PIXEL_OFFSET\n    //\n    // Used for GLSL 120 only.\n    //\n    // 1 = GL API supports fast pixel offsets\n    // 0 = do not use fast pixel offsets\n    //\n    #ifdef GL_EXT_gpu_shader4\n        #define FXAA_FAST_PIXEL_OFFSET 1\n    #endif\n    #ifdef GL_NV_gpu_shader5\n        #define FXAA_FAST_PIXEL_OFFSET 1\n    #endif\n    #ifdef GL_ARB_gpu_shader5\n        #define FXAA_FAST_PIXEL_OFFSET 1\n    #endif\n    #ifndef FXAA_FAST_PIXEL_OFFSET\n        #define FXAA_FAST_PIXEL_OFFSET 0\n    #endif\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_GATHER4_ALPHA\n    //\n    // 1 = API supports gather4 on alpha channel.\n    // 0 = API does not support gather4 on alpha channel.\n    //\n    #if (FXAA_HLSL_5 == 1)\n        #define FXAA_GATHER4_ALPHA 1\n    #endif\n    #ifdef GL_ARB_gpu_shader5\n        #define FXAA_GATHER4_ALPHA 1\n    #endif\n    #ifdef GL_NV_gpu_shader5\n        #define FXAA_GATHER4_ALPHA 1\n    #endif\n    #ifndef FXAA_GATHER4_ALPHA\n        #define FXAA_GATHER4_ALPHA 0\n    #endif\n#endif\n\n/*============================================================================\n                      FXAA CONSOLE PS3 - TUNING KNOBS\n============================================================================*/\n#ifndef FXAA_CONSOLE__PS3_EDGE_SHARPNESS\n    //\n    // Consoles the sharpness of edges on PS3 only.\n    // Non-PS3 tuning is done with shader input.\n    //\n    // Due to the PS3 being ALU bound,\n    // there are only two safe values here: 4 and 8.\n    // These options use the shaders ability to a free *|/ by 2|4|8.\n    //\n    // 8.0 is sharper\n    // 4.0 is softer\n    // 2.0 is really soft (good for vector graphics inputs)\n    //\n    #if 1\n        #define FXAA_CONSOLE__PS3_EDGE_SHARPNESS 8.0\n    #endif\n    #if 0\n        #define FXAA_CONSOLE__PS3_EDGE_SHARPNESS 4.0\n    #endif\n    #if 0\n        #define FXAA_CONSOLE__PS3_EDGE_SHARPNESS 2.0\n    #endif\n#endif\n/*--------------------------------------------------------------------------*/\n#ifndef FXAA_CONSOLE__PS3_EDGE_THRESHOLD\n    //\n    // Only effects PS3.\n    // Non-PS3 tuning is done with shader input.\n    //\n    // The minimum amount of local contrast required to apply algorithm.\n    // The console setting has a different mapping than the quality setting.\n    //\n    // This only applies when FXAA_EARLY_EXIT is 1.\n    //\n    // Due to the PS3 being ALU bound,\n    // there are only two safe values here: 0.25 and 0.125.\n    // These options use the shaders ability to a free *|/ by 2|4|8.\n    //\n    // 0.125 leaves less aliasing, but is softer\n    // 0.25 leaves more aliasing, and is sharper\n    //\n    #if 1\n        #define FXAA_CONSOLE__PS3_EDGE_THRESHOLD 0.125\n    #else\n        #define FXAA_CONSOLE__PS3_EDGE_THRESHOLD 0.25\n    #endif\n#endif\n\n/*============================================================================\n                        FXAA QUALITY - TUNING KNOBS\n------------------------------------------------------------------------------\nNOTE the other tuning knobs are now in the shader function inputs!\n============================================================================*/\n#ifndef FXAA_QUALITY__PRESET\n    //\n    // Choose the quality preset.\n    // This needs to be compiled into the shader as it effects code.\n    // Best option to include multiple presets is to \n    // in each shader define the preset, then include this file.\n    // \n    // OPTIONS\n    // -----------------------------------------------------------------------\n    // 10 to 15 - default medium dither (10=fastest, 15=highest quality)\n    // 20 to 29 - less dither, more expensive (20=fastest, 29=highest quality)\n    // 39       - no dither, very expensive \n    //\n    // NOTES\n    // -----------------------------------------------------------------------\n    // 12 = slightly faster then FXAA 3.9 and higher edge quality (default)\n    // 13 = about same speed as FXAA 3.9 and better than 12\n    // 23 = closest to FXAA 3.9 visually and performance wise\n    //  _ = the lowest digit is directly related to performance\n    // _  = the highest digit is directly related to style\n    // \n    #define FXAA_QUALITY__PRESET 12\n#endif\n\n\n/*============================================================================\n\n                           FXAA QUALITY - PRESETS\n\n============================================================================*/\n\n/*============================================================================\n                     FXAA QUALITY - MEDIUM DITHER PRESETS\n============================================================================*/\n#if (FXAA_QUALITY__PRESET == 10)\n    #define FXAA_QUALITY__PS 3\n    #define FXAA_QUALITY__P0 1.5\n    #define FXAA_QUALITY__P1 3.0\n    #define FXAA_QUALITY__P2 12.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 11)\n    #define FXAA_QUALITY__PS 4\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 3.0\n    #define FXAA_QUALITY__P3 12.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 12)\n    #define FXAA_QUALITY__PS 5\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 4.0\n    #define FXAA_QUALITY__P4 12.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 13)\n    #define FXAA_QUALITY__PS 6\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 2.0\n    #define FXAA_QUALITY__P4 4.0\n    #define FXAA_QUALITY__P5 12.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 14)\n    #define FXAA_QUALITY__PS 7\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 2.0\n    #define FXAA_QUALITY__P4 2.0\n    #define FXAA_QUALITY__P5 4.0\n    #define FXAA_QUALITY__P6 12.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 15)\n    #define FXAA_QUALITY__PS 8\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 2.0\n    #define FXAA_QUALITY__P4 2.0\n    #define FXAA_QUALITY__P5 2.0\n    #define FXAA_QUALITY__P6 4.0\n    #define FXAA_QUALITY__P7 12.0\n#endif\n\n/*============================================================================\n                     FXAA QUALITY - LOW DITHER PRESETS\n============================================================================*/\n#if (FXAA_QUALITY__PRESET == 20)\n    #define FXAA_QUALITY__PS 3\n    #define FXAA_QUALITY__P0 1.5\n    #define FXAA_QUALITY__P1 2.0\n    #define FXAA_QUALITY__P2 8.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 21)\n    #define FXAA_QUALITY__PS 4\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 8.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 22)\n    #define FXAA_QUALITY__PS 5\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 2.0\n    #define FXAA_QUALITY__P4 8.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 23)\n    #define FXAA_QUALITY__PS 6\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 2.0\n    #define FXAA_QUALITY__P4 2.0\n    #define FXAA_QUALITY__P5 8.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 24)\n    #define FXAA_QUALITY__PS 7\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 2.0\n    #define FXAA_QUALITY__P4 2.0\n    #define FXAA_QUALITY__P5 3.0\n    #define FXAA_QUALITY__P6 8.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 25)\n    #define FXAA_QUALITY__PS 8\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 2.0\n    #define FXAA_QUALITY__P4 2.0\n    #define FXAA_QUALITY__P5 2.0\n    #define FXAA_QUALITY__P6 4.0\n    #define FXAA_QUALITY__P7 8.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 26)\n    #define FXAA_QUALITY__PS 9\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 2.0\n    #define FXAA_QUALITY__P4 2.0\n    #define FXAA_QUALITY__P5 2.0\n    #define FXAA_QUALITY__P6 2.0\n    #define FXAA_QUALITY__P7 4.0\n    #define FXAA_QUALITY__P8 8.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 27)\n    #define FXAA_QUALITY__PS 10\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 2.0\n    #define FXAA_QUALITY__P4 2.0\n    #define FXAA_QUALITY__P5 2.0\n    #define FXAA_QUALITY__P6 2.0\n    #define FXAA_QUALITY__P7 2.0\n    #define FXAA_QUALITY__P8 4.0\n    #define FXAA_QUALITY__P9 8.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 28)\n    #define FXAA_QUALITY__PS 11\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 2.0\n    #define FXAA_QUALITY__P4 2.0\n    #define FXAA_QUALITY__P5 2.0\n    #define FXAA_QUALITY__P6 2.0\n    #define FXAA_QUALITY__P7 2.0\n    #define FXAA_QUALITY__P8 2.0\n    #define FXAA_QUALITY__P9 4.0\n    #define FXAA_QUALITY__P10 8.0\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_QUALITY__PRESET == 29)\n    #define FXAA_QUALITY__PS 12\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.5\n    #define FXAA_QUALITY__P2 2.0\n    #define FXAA_QUALITY__P3 2.0\n    #define FXAA_QUALITY__P4 2.0\n    #define FXAA_QUALITY__P5 2.0\n    #define FXAA_QUALITY__P6 2.0\n    #define FXAA_QUALITY__P7 2.0\n    #define FXAA_QUALITY__P8 2.0\n    #define FXAA_QUALITY__P9 2.0\n    #define FXAA_QUALITY__P10 4.0\n    #define FXAA_QUALITY__P11 8.0\n#endif\n\n/*============================================================================\n                     FXAA QUALITY - EXTREME QUALITY\n============================================================================*/\n#if (FXAA_QUALITY__PRESET == 39)\n    #define FXAA_QUALITY__PS 12\n    #define FXAA_QUALITY__P0 1.0\n    #define FXAA_QUALITY__P1 1.0\n    #define FXAA_QUALITY__P2 1.0\n    #define FXAA_QUALITY__P3 1.0\n    #define FXAA_QUALITY__P4 1.0\n    #define FXAA_QUALITY__P5 1.5\n    #define FXAA_QUALITY__P6 2.0\n    #define FXAA_QUALITY__P7 2.0\n    #define FXAA_QUALITY__P8 2.0\n    #define FXAA_QUALITY__P9 2.0\n    #define FXAA_QUALITY__P10 4.0\n    #define FXAA_QUALITY__P11 8.0\n#endif\n\n\n\n/*============================================================================\n\n                                API PORTING\n\n============================================================================*/\n#if (FXAA_GLSL_120 == 1) || (FXAA_GLSL_130 == 1)\n    #define FxaaBool bool\n    #define FxaaDiscard discard\n    #define FxaaFloat float\n    #define FxaaFloat2 vec2\n    #define FxaaFloat3 vec3\n    #define FxaaFloat4 vec4\n    #define FxaaHalf float\n    #define FxaaHalf2 vec2\n    #define FxaaHalf3 vec3\n    #define FxaaHalf4 vec4\n    #define FxaaInt2 ivec2\n    #define FxaaSat(x) clamp(x, 0.0, 1.0)\n    #define FxaaTex sampler2D\n#else\n    #define FxaaBool bool\n    #define FxaaDiscard clip(-1)\n    #define FxaaFloat float\n    #define FxaaFloat2 float2\n    #define FxaaFloat3 float3\n    #define FxaaFloat4 float4\n    #define FxaaHalf half\n    #define FxaaHalf2 half2\n    #define FxaaHalf3 half3\n    #define FxaaHalf4 half4\n    #define FxaaSat(x) saturate(x)\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_GLSL_120 == 1)\n    // Requires,\n    //  #version 120\n    // And at least,\n    //  #extension GL_EXT_gpu_shader4 : enable\n    //  (or set FXAA_FAST_PIXEL_OFFSET 1 to work like DX9)\n    #define FxaaTexTop(t, p) texture2DLod(t, p, 0.0)\n    #if (FXAA_FAST_PIXEL_OFFSET == 1)\n        #define FxaaTexOff(t, p, o, r) texture2DLodOffset(t, p, 0.0, o)\n    #else\n        #define FxaaTexOff(t, p, o, r) texture2DLod(t, p + (o * r), 0.0)\n    #endif\n    #if (FXAA_GATHER4_ALPHA == 1)\n        // use #extension GL_ARB_gpu_shader5 : enable\n        #define FxaaTexAlpha4(t, p) textureGather(t, p, 3)\n        #define FxaaTexOffAlpha4(t, p, o) textureGatherOffset(t, p, o, 3)\n        #define FxaaTexGreen4(t, p) textureGather(t, p, 1)\n        #define FxaaTexOffGreen4(t, p, o) textureGatherOffset(t, p, o, 1)\n    #endif\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_GLSL_130 == 1)\n    // Requires \"#version 130\" or better\n    #define FxaaTexTop(t, p) textureLod(t, p, 0.0)\n    #define FxaaTexOff(t, p, o, r) textureLodOffset(t, p, 0.0, o)\n    #if (FXAA_GATHER4_ALPHA == 1)\n        // use #extension GL_ARB_gpu_shader5 : enable\n        #define FxaaTexAlpha4(t, p) textureGather(t, p, 3)\n        #define FxaaTexOffAlpha4(t, p, o) textureGatherOffset(t, p, o, 3)\n        #define FxaaTexGreen4(t, p) textureGather(t, p, 1)\n        #define FxaaTexOffGreen4(t, p, o) textureGatherOffset(t, p, o, 1)\n    #endif\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_HLSL_3 == 1) || (FXAA_360 == 1) || (FXAA_PS3 == 1)\n    #define FxaaInt2 float2\n    #define FxaaTex sampler2D\n    #define FxaaTexTop(t, p) tex2Dlod(t, float4(p, 0.0, 0.0))\n    #define FxaaTexOff(t, p, o, r) tex2Dlod(t, float4(p + (o * r), 0, 0))\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_HLSL_4 == 1)\n    #define FxaaInt2 int2\n    struct FxaaTex { SamplerState smpl; Texture2D tex; };\n    #define FxaaTexTop(t, p) t.tex.SampleLevel(t.smpl, p, 0.0)\n    #define FxaaTexOff(t, p, o, r) t.tex.SampleLevel(t.smpl, p, 0.0, o)\n#endif\n/*--------------------------------------------------------------------------*/\n#if (FXAA_HLSL_5 == 1)\n    #define FxaaInt2 int2\n    struct FxaaTex { SamplerState smpl; Texture2D tex; };\n    #define FxaaTexTop(t, p) t.tex.SampleLevel(t.smpl, p, 0.0)\n    #define FxaaTexOff(t, p, o, r) t.tex.SampleLevel(t.smpl, p, 0.0, o)\n    #define FxaaTexAlpha4(t, p) t.tex.GatherAlpha(t.smpl, p)\n    #define FxaaTexOffAlpha4(t, p, o) t.tex.GatherAlpha(t.smpl, p, o)\n    #define FxaaTexGreen4(t, p) t.tex.GatherGreen(t.smpl, p)\n    #define FxaaTexOffGreen4(t, p, o) t.tex.GatherGreen(t.smpl, p, o)\n#endif\n\n\n/*============================================================================\n                   GREEN AS LUMA OPTION SUPPORT FUNCTION\n============================================================================*/\n#if (FXAA_GREEN_AS_LUMA == 0)\n    FxaaFloat FxaaLuma(FxaaFloat4 rgba) { return rgba.w; }\n#else\n    FxaaFloat FxaaLuma(FxaaFloat4 rgba) { return rgba.y; }\n#endif    \n\n\n\n\n/*============================================================================\n\n                             FXAA3 QUALITY - PC\n\n============================================================================*/\n#if (FXAA_PC == 1)\n/*--------------------------------------------------------------------------*/\nFxaaFloat4 FxaaPixelShader(\n    //\n    // Use noperspective interpolation here (turn off perspective interpolation).\n    // {xy} = center of pixel\n    FxaaFloat2 pos,\n    //\n    // Used only for FXAA Console, and not used on the 360 version.\n    // Use noperspective interpolation here (turn off perspective interpolation).\n    // {xy__} = upper left of pixel\n    // {__zw} = lower right of pixel\n    FxaaFloat4 fxaaConsolePosPos,\n    //\n    // Input color texture.\n    // {rgb_} = color in linear or perceptual color space\n    // if (FXAA_GREEN_AS_LUMA == 0)\n    //     {___a} = luma in perceptual color space (not linear)\n    FxaaTex tex,\n    //\n    // Only used on the optimized 360 version of FXAA Console.\n    // For everything but 360, just use the same input here as for \"tex\".\n    // For 360, same texture, just alias with a 2nd sampler.\n    // This sampler needs to have an exponent bias of -1.\n    FxaaTex fxaaConsole360TexExpBiasNegOne,\n    //\n    // Only used on the optimized 360 version of FXAA Console.\n    // For everything but 360, just use the same input here as for \"tex\".\n    // For 360, same texture, just alias with a 3nd sampler.\n    // This sampler needs to have an exponent bias of -2.\n    FxaaTex fxaaConsole360TexExpBiasNegTwo,\n    //\n    // Only used on FXAA Quality.\n    // This must be from a constant/uniform.\n    // {x_} = 1.0/screenWidthInPixels\n    // {_y} = 1.0/screenHeightInPixels\n    FxaaFloat2 fxaaQualityRcpFrame,\n    //\n    // Only used on FXAA Console.\n    // This must be from a constant/uniform.\n    // This effects sub-pixel AA quality and inversely sharpness.\n    //   Where N ranges between,\n    //     N = 0.50 (default)\n    //     N = 0.33 (sharper)\n    // {x___} = -N/screenWidthInPixels  \n    // {_y__} = -N/screenHeightInPixels\n    // {__z_} =  N/screenWidthInPixels  \n    // {___w} =  N/screenHeightInPixels \n    FxaaFloat4 fxaaConsoleRcpFrameOpt,\n    //\n    // Only used on FXAA Console.\n    // Not used on 360, but used on PS3 and PC.\n    // This must be from a constant/uniform.\n    // {x___} = -2.0/screenWidthInPixels  \n    // {_y__} = -2.0/screenHeightInPixels\n    // {__z_} =  2.0/screenWidthInPixels  \n    // {___w} =  2.0/screenHeightInPixels \n    FxaaFloat4 fxaaConsoleRcpFrameOpt2,\n    //\n    // Only used on FXAA Console.\n    // Only used on 360 in place of fxaaConsoleRcpFrameOpt2.\n    // This must be from a constant/uniform.\n    // {x___} =  8.0/screenWidthInPixels  \n    // {_y__} =  8.0/screenHeightInPixels\n    // {__z_} = -4.0/screenWidthInPixels  \n    // {___w} = -4.0/screenHeightInPixels \n    FxaaFloat4 fxaaConsole360RcpFrameOpt2,\n    //\n    // Only used on FXAA Quality.\n    // This used to be the FXAA_QUALITY__SUBPIX define.\n    // It is here now to allow easier tuning.\n    // Choose the amount of sub-pixel aliasing removal.\n    // This can effect sharpness.\n    //   1.00 - upper limit (softer)\n    //   0.75 - default amount of filtering\n    //   0.50 - lower limit (sharper, less sub-pixel aliasing removal)\n    //   0.25 - almost off\n    //   0.00 - completely off\n    FxaaFloat fxaaQualitySubpix,\n    //\n    // Only used on FXAA Quality.\n    // This used to be the FXAA_QUALITY__EDGE_THRESHOLD define.\n    // It is here now to allow easier tuning.\n    // The minimum amount of local contrast required to apply algorithm.\n    //   0.333 - too little (faster)\n    //   0.250 - low quality\n    //   0.166 - default\n    //   0.125 - high quality \n    //   0.063 - overkill (slower)\n    FxaaFloat fxaaQualityEdgeThreshold,\n    //\n    // Only used on FXAA Quality.\n    // This used to be the FXAA_QUALITY__EDGE_THRESHOLD_MIN define.\n    // It is here now to allow easier tuning.\n    // Trims the algorithm from processing darks.\n    //   0.0833 - upper limit (default, the start of visible unfiltered edges)\n    //   0.0625 - high quality (faster)\n    //   0.0312 - visible limit (slower)\n    // Special notes when using FXAA_GREEN_AS_LUMA,\n    //   Likely want to set this to zero.\n    //   As colors that are mostly not-green\n    //   will appear very dark in the green channel!\n    //   Tune by looking at mostly non-green content,\n    //   then start at zero and increase until aliasing is a problem.\n    FxaaFloat fxaaQualityEdgeThresholdMin,\n    // \n    // Only used on FXAA Console.\n    // This used to be the FXAA_CONSOLE__EDGE_SHARPNESS define.\n    // It is here now to allow easier tuning.\n    // This does not effect PS3, as this needs to be compiled in.\n    //   Use FXAA_CONSOLE__PS3_EDGE_SHARPNESS for PS3.\n    //   Due to the PS3 being ALU bound,\n    //   there are only three safe values here: 2 and 4 and 8.\n    //   These options use the shaders ability to a free *|/ by 2|4|8.\n    // For all other platforms can be a non-power of two.\n    //   8.0 is sharper (default!!!)\n    //   4.0 is softer\n    //   2.0 is really soft (good only for vector graphics inputs)\n    FxaaFloat fxaaConsoleEdgeSharpness,\n    //\n    // Only used on FXAA Console.\n    // This used to be the FXAA_CONSOLE__EDGE_THRESHOLD define.\n    // It is here now to allow easier tuning.\n    // This does not effect PS3, as this needs to be compiled in.\n    //   Use FXAA_CONSOLE__PS3_EDGE_THRESHOLD for PS3.\n    //   Due to the PS3 being ALU bound,\n    //   there are only two safe values here: 1/4 and 1/8.\n    //   These options use the shaders ability to a free *|/ by 2|4|8.\n    // The console setting has a different mapping than the quality setting.\n    // Other platforms can use other values.\n    //   0.125 leaves less aliasing, but is softer (default!!!)\n    //   0.25 leaves more aliasing, and is sharper\n    FxaaFloat fxaaConsoleEdgeThreshold,\n    //\n    // Only used on FXAA Console.\n    // This used to be the FXAA_CONSOLE__EDGE_THRESHOLD_MIN define.\n    // It is here now to allow easier tuning.\n    // Trims the algorithm from processing darks.\n    // The console setting has a different mapping than the quality setting.\n    // This only applies when FXAA_EARLY_EXIT is 1.\n    // This does not apply to PS3, \n    // PS3 was simplified to avoid more shader instructions.\n    //   0.06 - faster but more aliasing in darks\n    //   0.05 - default\n    //   0.04 - slower and less aliasing in darks\n    // Special notes when using FXAA_GREEN_AS_LUMA,\n    //   Likely want to set this to zero.\n    //   As colors that are mostly not-green\n    //   will appear very dark in the green channel!\n    //   Tune by looking at mostly non-green content,\n    //   then start at zero and increase until aliasing is a problem.\n    FxaaFloat fxaaConsoleEdgeThresholdMin,\n    //    \n    // Extra constants for 360 FXAA Console only.\n    // Use zeros or anything else for other platforms.\n    // These must be in physical constant registers and NOT immedates.\n    // Immedates will result in compiler un-optimizing.\n    // {xyzw} = float4(1.0, -1.0, 0.25, -0.25)\n    FxaaFloat4 fxaaConsole360ConstDir\n) {\n/*--------------------------------------------------------------------------*/\n    FxaaFloat2 posM;\n    posM.x = pos.x;\n    posM.y = pos.y;\n    #if (FXAA_GATHER4_ALPHA == 1)\n        #if (FXAA_DISCARD == 0)\n            FxaaFloat4 rgbyM = FxaaTexTop(tex, posM);\n            #if (FXAA_GREEN_AS_LUMA == 0)\n                #define lumaM rgbyM.w\n            #else\n                #define lumaM rgbyM.y\n            #endif\n        #endif\n        #if (FXAA_GREEN_AS_LUMA == 0)\n            FxaaFloat4 luma4A = FxaaTexAlpha4(tex, posM);\n            FxaaFloat4 luma4B = FxaaTexOffAlpha4(tex, posM, FxaaInt2(-1, -1));\n        #else\n            FxaaFloat4 luma4A = FxaaTexGreen4(tex, posM);\n            FxaaFloat4 luma4B = FxaaTexOffGreen4(tex, posM, FxaaInt2(-1, -1));\n        #endif\n        #if (FXAA_DISCARD == 1)\n            #define lumaM luma4A.w\n        #endif\n        #define lumaE luma4A.z\n        #define lumaS luma4A.x\n        #define lumaSE luma4A.y\n        #define lumaNW luma4B.w\n        #define lumaN luma4B.z\n        #define lumaW luma4B.x\n    #else\n        FxaaFloat4 rgbyM = FxaaTexTop(tex, posM);\n        #if (FXAA_GREEN_AS_LUMA == 0)\n            #define lumaM rgbyM.w\n        #else\n            #define lumaM rgbyM.y\n        #endif\n        FxaaFloat lumaS = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 0, 1), fxaaQualityRcpFrame.xy));\n        FxaaFloat lumaE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 1, 0), fxaaQualityRcpFrame.xy));\n        FxaaFloat lumaN = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 0,-1), fxaaQualityRcpFrame.xy));\n        FxaaFloat lumaW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, 0), fxaaQualityRcpFrame.xy));\n    #endif\n/*--------------------------------------------------------------------------*/\n    FxaaFloat maxSM = max(lumaS, lumaM);\n    FxaaFloat minSM = min(lumaS, lumaM);\n    FxaaFloat maxESM = max(lumaE, maxSM);\n    FxaaFloat minESM = min(lumaE, minSM);\n    FxaaFloat maxWN = max(lumaN, lumaW);\n    FxaaFloat minWN = min(lumaN, lumaW);\n    FxaaFloat rangeMax = max(maxWN, maxESM);\n    FxaaFloat rangeMin = min(minWN, minESM);\n    FxaaFloat rangeMaxScaled = rangeMax * fxaaQualityEdgeThreshold;\n    FxaaFloat range = rangeMax - rangeMin;\n    FxaaFloat rangeMaxClamped = max(fxaaQualityEdgeThresholdMin, rangeMaxScaled);\n    FxaaBool earlyExit = range < rangeMaxClamped;\n/*--------------------------------------------------------------------------*/\n    if(earlyExit)\n        #if (FXAA_DISCARD == 1)\n            FxaaDiscard;\n        #else\n            return rgbyM;\n        #endif\n/*--------------------------------------------------------------------------*/\n    #if (FXAA_GATHER4_ALPHA == 0)\n        FxaaFloat lumaNW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1,-1), fxaaQualityRcpFrame.xy));\n        FxaaFloat lumaSE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 1, 1), fxaaQualityRcpFrame.xy));\n        FxaaFloat lumaNE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 1,-1), fxaaQualityRcpFrame.xy));\n        FxaaFloat lumaSW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, 1), fxaaQualityRcpFrame.xy));\n    #else\n        FxaaFloat lumaNE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(1, -1), fxaaQualityRcpFrame.xy));\n        FxaaFloat lumaSW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, 1), fxaaQualityRcpFrame.xy));\n    #endif\n/*--------------------------------------------------------------------------*/\n    FxaaFloat lumaNS = lumaN + lumaS;\n    FxaaFloat lumaWE = lumaW + lumaE;\n    FxaaFloat subpixRcpRange = 1.0/range;\n    FxaaFloat subpixNSWE = lumaNS + lumaWE;\n    FxaaFloat edgeHorz1 = (-2.0 * lumaM) + lumaNS;\n    FxaaFloat edgeVert1 = (-2.0 * lumaM) + lumaWE;\n/*--------------------------------------------------------------------------*/\n    FxaaFloat lumaNESE = lumaNE + lumaSE;\n    FxaaFloat lumaNWNE = lumaNW + lumaNE;\n    FxaaFloat edgeHorz2 = (-2.0 * lumaE) + lumaNESE;\n    FxaaFloat edgeVert2 = (-2.0 * lumaN) + lumaNWNE;\n/*--------------------------------------------------------------------------*/\n    FxaaFloat lumaNWSW = lumaNW + lumaSW;\n    FxaaFloat lumaSWSE = lumaSW + lumaSE;\n    FxaaFloat edgeHorz4 = (abs(edgeHorz1) * 2.0) + abs(edgeHorz2);\n    FxaaFloat edgeVert4 = (abs(edgeVert1) * 2.0) + abs(edgeVert2);\n    FxaaFloat edgeHorz3 = (-2.0 * lumaW) + lumaNWSW;\n    FxaaFloat edgeVert3 = (-2.0 * lumaS) + lumaSWSE;\n    FxaaFloat edgeHorz = abs(edgeHorz3) + edgeHorz4;\n    FxaaFloat edgeVert = abs(edgeVert3) + edgeVert4;\n/*--------------------------------------------------------------------------*/\n    FxaaFloat subpixNWSWNESE = lumaNWSW + lumaNESE;\n    FxaaFloat lengthSign = fxaaQualityRcpFrame.x;\n    FxaaBool horzSpan = edgeHorz >= edgeVert;\n    FxaaFloat subpixA = subpixNSWE * 2.0 + subpixNWSWNESE;\n/*--------------------------------------------------------------------------*/\n    if(!horzSpan) lumaN = lumaW;\n    if(!horzSpan) lumaS = lumaE;\n    if(horzSpan) lengthSign = fxaaQualityRcpFrame.y;\n    FxaaFloat subpixB = (subpixA * (1.0/12.0)) - lumaM;\n/*--------------------------------------------------------------------------*/\n    FxaaFloat gradientN = lumaN - lumaM;\n    FxaaFloat gradientS = lumaS - lumaM;\n    FxaaFloat lumaNN = lumaN + lumaM;\n    FxaaFloat lumaSS = lumaS + lumaM;\n    FxaaBool pairN = abs(gradientN) >= abs(gradientS);\n    FxaaFloat gradient = max(abs(gradientN), abs(gradientS));\n    if(pairN) lengthSign = -lengthSign;\n    FxaaFloat subpixC = FxaaSat(abs(subpixB) * subpixRcpRange);\n/*--------------------------------------------------------------------------*/\n    FxaaFloat2 posB;\n    posB.x = posM.x;\n    posB.y = posM.y;\n    FxaaFloat2 offNP;\n    offNP.x = (!horzSpan) ? 0.0 : fxaaQualityRcpFrame.x;\n    offNP.y = ( horzSpan) ? 0.0 : fxaaQualityRcpFrame.y;\n    if(!horzSpan) posB.x += lengthSign * 0.5;\n    if( horzSpan) posB.y += lengthSign * 0.5;\n/*--------------------------------------------------------------------------*/\n    FxaaFloat2 posN;\n    posN.x = posB.x - offNP.x * FXAA_QUALITY__P0;\n    posN.y = posB.y - offNP.y * FXAA_QUALITY__P0;\n    FxaaFloat2 posP;\n    posP.x = posB.x + offNP.x * FXAA_QUALITY__P0;\n    posP.y = posB.y + offNP.y * FXAA_QUALITY__P0;\n    FxaaFloat subpixD = ((-2.0)*subpixC) + 3.0;\n    FxaaFloat lumaEndN = FxaaLuma(FxaaTexTop(tex, posN));\n    FxaaFloat subpixE = subpixC * subpixC;\n    FxaaFloat lumaEndP = FxaaLuma(FxaaTexTop(tex, posP));\n/*--------------------------------------------------------------------------*/\n    if(!pairN) lumaNN = lumaSS;\n    FxaaFloat gradientScaled = gradient * 1.0/4.0;\n    FxaaFloat lumaMM = lumaM - lumaNN * 0.5;\n    FxaaFloat subpixF = subpixD * subpixE;\n    FxaaBool lumaMLTZero = lumaMM < 0.0;\n/*--------------------------------------------------------------------------*/\n    lumaEndN -= lumaNN * 0.5;\n    lumaEndP -= lumaNN * 0.5;\n    FxaaBool doneN = abs(lumaEndN) >= gradientScaled;\n    FxaaBool doneP = abs(lumaEndP) >= gradientScaled;\n    if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P1;\n    if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P1;\n    FxaaBool doneNP = (!doneN) || (!doneP);\n    if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P1;\n    if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P1;\n/*--------------------------------------------------------------------------*/\n    if(doneNP) {\n        if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));\n        if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));\n        if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;\n        if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;\n        doneN = abs(lumaEndN) >= gradientScaled;\n        doneP = abs(lumaEndP) >= gradientScaled;\n        if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P2;\n        if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P2;\n        doneNP = (!doneN) || (!doneP);\n        if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P2;\n        if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P2;\n/*--------------------------------------------------------------------------*/\n        #if (FXAA_QUALITY__PS > 3)\n        if(doneNP) {\n            if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));\n            if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));\n            if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;\n            if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;\n            doneN = abs(lumaEndN) >= gradientScaled;\n            doneP = abs(lumaEndP) >= gradientScaled;\n            if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P3;\n            if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P3;\n            doneNP = (!doneN) || (!doneP);\n            if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P3;\n            if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P3;\n/*--------------------------------------------------------------------------*/\n            #if (FXAA_QUALITY__PS > 4)\n            if(doneNP) {\n                if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));\n                if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));\n                if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;\n                if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;\n                doneN = abs(lumaEndN) >= gradientScaled;\n                doneP = abs(lumaEndP) >= gradientScaled;\n                if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P4;\n                if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P4;\n                doneNP = (!doneN) || (!doneP);\n                if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P4;\n                if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P4;\n/*--------------------------------------------------------------------------*/\n                #if (FXAA_QUALITY__PS > 5)\n                if(doneNP) {\n                    if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));\n                    if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));\n                    if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;\n                    if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;\n                    doneN = abs(lumaEndN) >= gradientScaled;\n                    doneP = abs(lumaEndP) >= gradientScaled;\n                    if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P5;\n                    if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P5;\n                    doneNP = (!doneN) || (!doneP);\n                    if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P5;\n                    if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P5;\n/*--------------------------------------------------------------------------*/\n                    #if (FXAA_QUALITY__PS > 6)\n                    if(doneNP) {\n                        if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));\n                        if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));\n                        if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;\n                        if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;\n                        doneN = abs(lumaEndN) >= gradientScaled;\n                        doneP = abs(lumaEndP) >= gradientScaled;\n                        if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P6;\n                        if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P6;\n                        doneNP = (!doneN) || (!doneP);\n                        if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P6;\n                        if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P6;\n/*--------------------------------------------------------------------------*/\n                        #if (FXAA_QUALITY__PS > 7)\n                        if(doneNP) {\n                            if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));\n                            if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));\n                            if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;\n                            if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;\n                            doneN = abs(lumaEndN) >= gradientScaled;\n                            doneP = abs(lumaEndP) >= gradientScaled;\n                            if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P7;\n                            if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P7;\n                            doneNP = (!doneN) || (!doneP);\n                            if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P7;\n                            if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P7;\n/*--------------------------------------------------------------------------*/\n    #if (FXAA_QUALITY__PS > 8)\n    if(doneNP) {\n        if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));\n        if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));\n        if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;\n        if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;\n        doneN = abs(lumaEndN) >= gradientScaled;\n        doneP = abs(lumaEndP) >= gradientScaled;\n        if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P8;\n        if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P8;\n        doneNP = (!doneN) || (!doneP);\n        if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P8;\n        if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P8;\n/*--------------------------------------------------------------------------*/\n        #if (FXAA_QUALITY__PS > 9)\n        if(doneNP) {\n            if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));\n            if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));\n            if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;\n            if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;\n            doneN = abs(lumaEndN) >= gradientScaled;\n            doneP = abs(lumaEndP) >= gradientScaled;\n            if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P9;\n            if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P9;\n            doneNP = (!doneN) || (!doneP);\n            if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P9;\n            if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P9;\n/*--------------------------------------------------------------------------*/\n            #if (FXAA_QUALITY__PS > 10)\n            if(doneNP) {\n                if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));\n                if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));\n                if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;\n                if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;\n                doneN = abs(lumaEndN) >= gradientScaled;\n                doneP = abs(lumaEndP) >= gradientScaled;\n                if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P10;\n                if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P10;\n                doneNP = (!doneN) || (!doneP);\n                if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P10;\n                if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P10;\n/*--------------------------------------------------------------------------*/\n                #if (FXAA_QUALITY__PS > 11)\n                if(doneNP) {\n                    if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));\n                    if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));\n                    if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;\n                    if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;\n                    doneN = abs(lumaEndN) >= gradientScaled;\n                    doneP = abs(lumaEndP) >= gradientScaled;\n                    if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P11;\n                    if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P11;\n                    doneNP = (!doneN) || (!doneP);\n                    if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P11;\n                    if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P11;\n/*--------------------------------------------------------------------------*/\n                    #if (FXAA_QUALITY__PS > 12)\n                    if(doneNP) {\n                        if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));\n                        if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));\n                        if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;\n                        if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;\n                        doneN = abs(lumaEndN) >= gradientScaled;\n                        doneP = abs(lumaEndP) >= gradientScaled;\n                        if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P12;\n                        if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P12;\n                        doneNP = (!doneN) || (!doneP);\n                        if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P12;\n                        if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P12;\n/*--------------------------------------------------------------------------*/\n                    }\n                    #endif\n/*--------------------------------------------------------------------------*/\n                }\n                #endif\n/*--------------------------------------------------------------------------*/\n            }\n            #endif\n/*--------------------------------------------------------------------------*/\n        }\n        #endif\n/*--------------------------------------------------------------------------*/\n    }\n    #endif\n/*--------------------------------------------------------------------------*/\n                        }\n                        #endif\n/*--------------------------------------------------------------------------*/\n                    }\n                    #endif\n/*--------------------------------------------------------------------------*/\n                }\n                #endif\n/*--------------------------------------------------------------------------*/\n            }\n            #endif\n/*--------------------------------------------------------------------------*/\n        }\n        #endif\n/*--------------------------------------------------------------------------*/\n    }\n/*--------------------------------------------------------------------------*/\n    FxaaFloat dstN = posM.x - posN.x;\n    FxaaFloat dstP = posP.x - posM.x;\n    if(!horzSpan) dstN = posM.y - posN.y;\n    if(!horzSpan) dstP = posP.y - posM.y;\n/*--------------------------------------------------------------------------*/\n    FxaaBool goodSpanN = (lumaEndN < 0.0) != lumaMLTZero;\n    FxaaFloat spanLength = (dstP + dstN);\n    FxaaBool goodSpanP = (lumaEndP < 0.0) != lumaMLTZero;\n    FxaaFloat spanLengthRcp = 1.0/spanLength;\n/*--------------------------------------------------------------------------*/\n    FxaaBool directionN = dstN < dstP;\n    FxaaFloat dst = min(dstN, dstP);\n    FxaaBool goodSpan = directionN ? goodSpanN : goodSpanP;\n    FxaaFloat subpixG = subpixF * subpixF;\n    FxaaFloat pixelOffset = (dst * (-spanLengthRcp)) + 0.5;\n    FxaaFloat subpixH = subpixG * fxaaQualitySubpix;\n/*--------------------------------------------------------------------------*/\n    FxaaFloat pixelOffsetGood = goodSpan ? pixelOffset : 0.0;\n    FxaaFloat pixelOffsetSubpix = max(pixelOffsetGood, subpixH);\n    if(!horzSpan) posM.x += pixelOffsetSubpix * lengthSign;\n    if( horzSpan) posM.y += pixelOffsetSubpix * lengthSign;\n    #if (FXAA_DISCARD == 1)\n        return FxaaTexTop(tex, posM);\n    #else\n        return FxaaFloat4(FxaaTexTop(tex, posM).xyz, lumaM);\n    #endif\n}\n/*==========================================================================*/\n#endif\n\n\n\n\n/*============================================================================\n\n                         FXAA3 CONSOLE - PC VERSION\n                         \n------------------------------------------------------------------------------\nInstead of using this on PC, I'd suggest just using FXAA Quality with\n    #define FXAA_QUALITY__PRESET 10\nOr \n    #define FXAA_QUALITY__PRESET 20\nEither are higher qualilty and almost as fast as this on modern PC GPUs.\n============================================================================*/\n#if (FXAA_PC_CONSOLE == 1)\n/*--------------------------------------------------------------------------*/\nFxaaFloat4 FxaaPixelShader(\n    // See FXAA Quality FxaaPixelShader() source for docs on Inputs!\n    FxaaFloat2 pos,\n    FxaaFloat4 fxaaConsolePosPos,\n    FxaaTex tex,\n    FxaaTex fxaaConsole360TexExpBiasNegOne,\n    FxaaTex fxaaConsole360TexExpBiasNegTwo,\n    FxaaFloat2 fxaaQualityRcpFrame,\n    FxaaFloat4 fxaaConsoleRcpFrameOpt,\n    FxaaFloat4 fxaaConsoleRcpFrameOpt2,\n    FxaaFloat4 fxaaConsole360RcpFrameOpt2,\n    FxaaFloat fxaaQualitySubpix,\n    FxaaFloat fxaaQualityEdgeThreshold,\n    FxaaFloat fxaaQualityEdgeThresholdMin,\n    FxaaFloat fxaaConsoleEdgeSharpness,\n    FxaaFloat fxaaConsoleEdgeThreshold,\n    FxaaFloat fxaaConsoleEdgeThresholdMin,\n    FxaaFloat4 fxaaConsole360ConstDir\n) {\n/*--------------------------------------------------------------------------*/\n    FxaaFloat lumaNw = FxaaLuma(FxaaTexTop(tex, fxaaConsolePosPos.xy));\n    FxaaFloat lumaSw = FxaaLuma(FxaaTexTop(tex, fxaaConsolePosPos.xw));\n    FxaaFloat lumaNe = FxaaLuma(FxaaTexTop(tex, fxaaConsolePosPos.zy));\n    FxaaFloat lumaSe = FxaaLuma(FxaaTexTop(tex, fxaaConsolePosPos.zw));\n/*--------------------------------------------------------------------------*/\n    FxaaFloat4 rgbyM = FxaaTexTop(tex, pos.xy);\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        FxaaFloat lumaM = rgbyM.w;\n    #else\n        FxaaFloat lumaM = rgbyM.y;\n    #endif\n/*--------------------------------------------------------------------------*/\n    FxaaFloat lumaMaxNwSw = max(lumaNw, lumaSw);\n    lumaNe += 1.0/384.0;\n    FxaaFloat lumaMinNwSw = min(lumaNw, lumaSw);\n/*--------------------------------------------------------------------------*/\n    FxaaFloat lumaMaxNeSe = max(lumaNe, lumaSe);\n    FxaaFloat lumaMinNeSe = min(lumaNe, lumaSe);\n/*--------------------------------------------------------------------------*/\n    FxaaFloat lumaMax = max(lumaMaxNeSe, lumaMaxNwSw);\n    FxaaFloat lumaMin = min(lumaMinNeSe, lumaMinNwSw);\n/*--------------------------------------------------------------------------*/\n    FxaaFloat lumaMaxScaled = lumaMax * fxaaConsoleEdgeThreshold;\n/*--------------------------------------------------------------------------*/\n    FxaaFloat lumaMinM = min(lumaMin, lumaM);\n    FxaaFloat lumaMaxScaledClamped = max(fxaaConsoleEdgeThresholdMin, lumaMaxScaled);\n    FxaaFloat lumaMaxM = max(lumaMax, lumaM);\n    FxaaFloat dirSwMinusNe = lumaSw - lumaNe;\n    FxaaFloat lumaMaxSubMinM = lumaMaxM - lumaMinM;\n    FxaaFloat dirSeMinusNw = lumaSe - lumaNw;\n    if(lumaMaxSubMinM < lumaMaxScaledClamped) return rgbyM;\n/*--------------------------------------------------------------------------*/\n    FxaaFloat2 dir;\n    dir.x = dirSwMinusNe + dirSeMinusNw;\n    dir.y = dirSwMinusNe - dirSeMinusNw;\n/*--------------------------------------------------------------------------*/\n    FxaaFloat2 dir1 = normalize(dir.xy);\n    FxaaFloat4 rgbyN1 = FxaaTexTop(tex, pos.xy - dir1 * fxaaConsoleRcpFrameOpt.zw);\n    FxaaFloat4 rgbyP1 = FxaaTexTop(tex, pos.xy + dir1 * fxaaConsoleRcpFrameOpt.zw);\n/*--------------------------------------------------------------------------*/\n    FxaaFloat dirAbsMinTimesC = min(abs(dir1.x), abs(dir1.y)) * fxaaConsoleEdgeSharpness;\n    FxaaFloat2 dir2 = clamp(dir1.xy / dirAbsMinTimesC, -2.0, 2.0);\n/*--------------------------------------------------------------------------*/\n    FxaaFloat4 rgbyN2 = FxaaTexTop(tex, pos.xy - dir2 * fxaaConsoleRcpFrameOpt2.zw);\n    FxaaFloat4 rgbyP2 = FxaaTexTop(tex, pos.xy + dir2 * fxaaConsoleRcpFrameOpt2.zw);\n/*--------------------------------------------------------------------------*/\n    FxaaFloat4 rgbyA = rgbyN1 + rgbyP1;\n    FxaaFloat4 rgbyB = ((rgbyN2 + rgbyP2) * 0.25) + (rgbyA * 0.25);\n/*--------------------------------------------------------------------------*/\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        FxaaBool twoTap = (rgbyB.w < lumaMin) || (rgbyB.w > lumaMax);\n    #else\n        FxaaBool twoTap = (rgbyB.y < lumaMin) || (rgbyB.y > lumaMax);\n    #endif\n    if(twoTap) rgbyB.xyz = rgbyA.xyz * 0.5;\n    return rgbyB; }\n/*==========================================================================*/\n#endif\n\n\n\n/*============================================================================\n\n                      FXAA3 CONSOLE - 360 PIXEL SHADER \n\n------------------------------------------------------------------------------\nThis optimized version thanks to suggestions from Andy Luedke.\nShould be fully tex bound in all cases.\nAs of the FXAA 3.11 release, I have still not tested this code,\nhowever I fixed a bug which was in both FXAA 3.9 and FXAA 3.10.\nAnd note this is replacing the old unoptimized version.\nIf it does not work, please let me know so I can fix it.\n============================================================================*/\n#if (FXAA_360 == 1)\n/*--------------------------------------------------------------------------*/\n[reduceTempRegUsage(4)]\nfloat4 FxaaPixelShader(\n    // See FXAA Quality FxaaPixelShader() source for docs on Inputs!\n    FxaaFloat2 pos,\n    FxaaFloat4 fxaaConsolePosPos,\n    FxaaTex tex,\n    FxaaTex fxaaConsole360TexExpBiasNegOne,\n    FxaaTex fxaaConsole360TexExpBiasNegTwo,\n    FxaaFloat2 fxaaQualityRcpFrame,\n    FxaaFloat4 fxaaConsoleRcpFrameOpt,\n    FxaaFloat4 fxaaConsoleRcpFrameOpt2,\n    FxaaFloat4 fxaaConsole360RcpFrameOpt2,\n    FxaaFloat fxaaQualitySubpix,\n    FxaaFloat fxaaQualityEdgeThreshold,\n    FxaaFloat fxaaQualityEdgeThresholdMin,\n    FxaaFloat fxaaConsoleEdgeSharpness,\n    FxaaFloat fxaaConsoleEdgeThreshold,\n    FxaaFloat fxaaConsoleEdgeThresholdMin,\n    FxaaFloat4 fxaaConsole360ConstDir\n) {\n/*--------------------------------------------------------------------------*/\n    float4 lumaNwNeSwSe;\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        asm { \n            tfetch2D lumaNwNeSwSe.w___, tex, pos.xy, OffsetX = -0.5, OffsetY = -0.5, UseComputedLOD=false\n            tfetch2D lumaNwNeSwSe._w__, tex, pos.xy, OffsetX =  0.5, OffsetY = -0.5, UseComputedLOD=false\n            tfetch2D lumaNwNeSwSe.__w_, tex, pos.xy, OffsetX = -0.5, OffsetY =  0.5, UseComputedLOD=false\n            tfetch2D lumaNwNeSwSe.___w, tex, pos.xy, OffsetX =  0.5, OffsetY =  0.5, UseComputedLOD=false\n        };\n    #else\n        asm { \n            tfetch2D lumaNwNeSwSe.y___, tex, pos.xy, OffsetX = -0.5, OffsetY = -0.5, UseComputedLOD=false\n            tfetch2D lumaNwNeSwSe._y__, tex, pos.xy, OffsetX =  0.5, OffsetY = -0.5, UseComputedLOD=false\n            tfetch2D lumaNwNeSwSe.__y_, tex, pos.xy, OffsetX = -0.5, OffsetY =  0.5, UseComputedLOD=false\n            tfetch2D lumaNwNeSwSe.___y, tex, pos.xy, OffsetX =  0.5, OffsetY =  0.5, UseComputedLOD=false\n        };\n    #endif\n/*--------------------------------------------------------------------------*/\n    lumaNwNeSwSe.y += 1.0/384.0;\n    float2 lumaMinTemp = min(lumaNwNeSwSe.xy, lumaNwNeSwSe.zw);\n    float2 lumaMaxTemp = max(lumaNwNeSwSe.xy, lumaNwNeSwSe.zw);\n    float lumaMin = min(lumaMinTemp.x, lumaMinTemp.y);\n    float lumaMax = max(lumaMaxTemp.x, lumaMaxTemp.y);\n/*--------------------------------------------------------------------------*/\n    float4 rgbyM = tex2Dlod(tex, float4(pos.xy, 0.0, 0.0));\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        float lumaMinM = min(lumaMin, rgbyM.w);\n        float lumaMaxM = max(lumaMax, rgbyM.w);\n    #else\n        float lumaMinM = min(lumaMin, rgbyM.y);\n        float lumaMaxM = max(lumaMax, rgbyM.y);\n    #endif        \n    if((lumaMaxM - lumaMinM) < max(fxaaConsoleEdgeThresholdMin, lumaMax * fxaaConsoleEdgeThreshold)) return rgbyM;\n/*--------------------------------------------------------------------------*/\n    float2 dir;\n    dir.x = dot(lumaNwNeSwSe, fxaaConsole360ConstDir.yyxx);\n    dir.y = dot(lumaNwNeSwSe, fxaaConsole360ConstDir.xyxy);\n    dir = normalize(dir);\n/*--------------------------------------------------------------------------*/\n    float4 dir1 = dir.xyxy * fxaaConsoleRcpFrameOpt.xyzw;\n/*--------------------------------------------------------------------------*/\n    float4 dir2;\n    float dirAbsMinTimesC = min(abs(dir.x), abs(dir.y)) * fxaaConsoleEdgeSharpness;\n    dir2 = saturate(fxaaConsole360ConstDir.zzww * dir.xyxy / dirAbsMinTimesC + 0.5);\n    dir2 = dir2 * fxaaConsole360RcpFrameOpt2.xyxy + fxaaConsole360RcpFrameOpt2.zwzw;\n/*--------------------------------------------------------------------------*/\n    float4 rgbyN1 = tex2Dlod(fxaaConsole360TexExpBiasNegOne, float4(pos.xy + dir1.xy, 0.0, 0.0));\n    float4 rgbyP1 = tex2Dlod(fxaaConsole360TexExpBiasNegOne, float4(pos.xy + dir1.zw, 0.0, 0.0));\n    float4 rgbyN2 = tex2Dlod(fxaaConsole360TexExpBiasNegTwo, float4(pos.xy + dir2.xy, 0.0, 0.0));\n    float4 rgbyP2 = tex2Dlod(fxaaConsole360TexExpBiasNegTwo, float4(pos.xy + dir2.zw, 0.0, 0.0));\n/*--------------------------------------------------------------------------*/\n    float4 rgbyA = rgbyN1 + rgbyP1;\n    float4 rgbyB = rgbyN2 + rgbyP2 + rgbyA * 0.5;\n/*--------------------------------------------------------------------------*/\n    float4 rgbyR = ((FxaaLuma(rgbyB) - lumaMax) > 0.0) ? rgbyA : rgbyB; \n    rgbyR = ((FxaaLuma(rgbyB) - lumaMin) > 0.0) ? rgbyR : rgbyA; \n    return rgbyR; }\n/*==========================================================================*/\n#endif\n\n\n\n/*============================================================================\n\n         FXAA3 CONSOLE - OPTIMIZED PS3 PIXEL SHADER (NO EARLY EXIT)\n\n==============================================================================\nThe code below does not exactly match the assembly.\nI have a feeling that 12 cycles is possible, but was not able to get there.\nMight have to increase register count to get full performance.\nNote this shader does not use perspective interpolation.\n\nUse the following cgc options,\n\n  --fenable-bx2 --fastmath --fastprecision --nofloatbindings\n\n------------------------------------------------------------------------------\n                             NVSHADERPERF OUTPUT\n------------------------------------------------------------------------------\nFor reference and to aid in debug, output of NVShaderPerf should match this,\n\nShader to schedule:\n  0: texpkb h0.w(TRUE), v5.zyxx, #0\n  2: addh h2.z(TRUE), h0.w, constant(0.001953, 0.000000, 0.000000, 0.000000).x\n  4: texpkb h0.w(TRUE), v5.xwxx, #0\n  6: addh h0.z(TRUE), -h2, h0.w\n  7: texpkb h1.w(TRUE), v5, #0\n  9: addh h0.x(TRUE), h0.z, -h1.w\n 10: addh h3.w(TRUE), h0.z, h1\n 11: texpkb h2.w(TRUE), v5.zwzz, #0\n 13: addh h0.z(TRUE), h3.w, -h2.w\n 14: addh h0.x(TRUE), h2.w, h0\n 15: nrmh h1.xz(TRUE), h0_n\n 16: minh_m8 h0.x(TRUE), |h1|, |h1.z|\n 17: maxh h4.w(TRUE), h0, h1\n 18: divx h2.xy(TRUE), h1_n.xzzw, h0_n\n 19: movr r1.zw(TRUE), v4.xxxy\n 20: madr r2.xz(TRUE), -h1, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).zzww, r1.zzww\n 22: minh h5.w(TRUE), h0, h1\n 23: texpkb h0(TRUE), r2.xzxx, #0\n 25: madr r0.zw(TRUE), h1.xzxz, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w), r1\n 27: maxh h4.x(TRUE), h2.z, h2.w\n 28: texpkb h1(TRUE), r0.zwzz, #0\n 30: addh_d2 h1(TRUE), h0, h1\n 31: madr r0.xy(TRUE), -h2, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).xyxx, r1.zwzz\n 33: texpkb h0(TRUE), r0, #0\n 35: minh h4.z(TRUE), h2, h2.w\n 36: fenct TRUE\n 37: madr r1.xy(TRUE), h2, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).xyxx, r1.zwzz\n 39: texpkb h2(TRUE), r1, #0\n 41: addh_d2 h0(TRUE), h0, h2\n 42: maxh h2.w(TRUE), h4, h4.x\n 43: minh h2.x(TRUE), h5.w, h4.z\n 44: addh_d2 h0(TRUE), h0, h1\n 45: slth h2.x(TRUE), h0.w, h2\n 46: sgth h2.w(TRUE), h0, h2\n 47: movh h0(TRUE), h0\n 48: addx.c0 rc(TRUE), h2, h2.w\n 49: movh h0(c0.NE.x), h1\n\nIPU0 ------ Simplified schedule: --------\nPass |  Unit  |  uOp |  PC:  Op\n-----+--------+------+-------------------------\n   1 | SCT0/1 |  mov |   0:  TXLr h0.w, g[TEX1].zyxx, const.xxxx, TEX0;\n     |    TEX |  txl |   0:  TXLr h0.w, g[TEX1].zyxx, const.xxxx, TEX0;\n     |   SCB1 |  add |   2:  ADDh h2.z, h0.--w-, const.--x-;\n     |        |      |\n   2 | SCT0/1 |  mov |   4:  TXLr h0.w, g[TEX1].xwxx, const.xxxx, TEX0;\n     |    TEX |  txl |   4:  TXLr h0.w, g[TEX1].xwxx, const.xxxx, TEX0;\n     |   SCB1 |  add |   6:  ADDh h0.z,-h2, h0.--w-;\n     |        |      |\n   3 | SCT0/1 |  mov |   7:  TXLr h1.w, g[TEX1], const.xxxx, TEX0;\n     |    TEX |  txl |   7:  TXLr h1.w, g[TEX1], const.xxxx, TEX0;\n     |   SCB0 |  add |   9:  ADDh h0.x, h0.z---,-h1.w---;\n     |   SCB1 |  add |  10:  ADDh h3.w, h0.---z, h1;\n     |        |      |\n   4 | SCT0/1 |  mov |  11:  TXLr h2.w, g[TEX1].zwzz, const.xxxx, TEX0;\n     |    TEX |  txl |  11:  TXLr h2.w, g[TEX1].zwzz, const.xxxx, TEX0;\n     |   SCB0 |  add |  14:  ADDh h0.x, h2.w---, h0;\n     |   SCB1 |  add |  13:  ADDh h0.z, h3.--w-,-h2.--w-;\n     |        |      |\n   5 |   SCT1 |  mov |  15:  NRMh h1.xz, h0;\n     |    SRB |  nrm |  15:  NRMh h1.xz, h0;\n     |   SCB0 |  min |  16:  MINh*8 h0.x, |h1|, |h1.z---|;\n     |   SCB1 |  max |  17:  MAXh h4.w, h0, h1;\n     |        |      |\n   6 |   SCT0 |  div |  18:  DIVx h2.xy, h1.xz--, h0;\n     |   SCT1 |  mov |  19:  MOVr r1.zw, g[TEX0].--xy;\n     |   SCB0 |  mad |  20:  MADr r2.xz,-h1, const.z-w-, r1.z-w-;\n     |   SCB1 |  min |  22:  MINh h5.w, h0, h1;\n     |        |      |\n   7 | SCT0/1 |  mov |  23:  TXLr h0, r2.xzxx, const.xxxx, TEX0;\n     |    TEX |  txl |  23:  TXLr h0, r2.xzxx, const.xxxx, TEX0;\n     |   SCB0 |  max |  27:  MAXh h4.x, h2.z---, h2.w---;\n     |   SCB1 |  mad |  25:  MADr r0.zw, h1.--xz, const, r1;\n     |        |      |\n   8 | SCT0/1 |  mov |  28:  TXLr h1, r0.zwzz, const.xxxx, TEX0;\n     |    TEX |  txl |  28:  TXLr h1, r0.zwzz, const.xxxx, TEX0;\n     | SCB0/1 |  add |  30:  ADDh/2 h1, h0, h1;\n     |        |      |\n   9 |   SCT0 |  mad |  31:  MADr r0.xy,-h2, const.xy--, r1.zw--;\n     |   SCT1 |  mov |  33:  TXLr h0, r0, const.zzzz, TEX0;\n     |    TEX |  txl |  33:  TXLr h0, r0, const.zzzz, TEX0;\n     |   SCB1 |  min |  35:  MINh h4.z, h2, h2.--w-;\n     |        |      |\n  10 |   SCT0 |  mad |  37:  MADr r1.xy, h2, const.xy--, r1.zw--;\n     |   SCT1 |  mov |  39:  TXLr h2, r1, const.zzzz, TEX0;\n     |    TEX |  txl |  39:  TXLr h2, r1, const.zzzz, TEX0;\n     | SCB0/1 |  add |  41:  ADDh/2 h0, h0, h2;\n     |        |      |\n  11 |   SCT0 |  min |  43:  MINh h2.x, h5.w---, h4.z---;\n     |   SCT1 |  max |  42:  MAXh h2.w, h4, h4.---x;\n     | SCB0/1 |  add |  44:  ADDh/2 h0, h0, h1;\n     |        |      |\n  12 |   SCT0 |  set |  45:  SLTh h2.x, h0.w---, h2;\n     |   SCT1 |  set |  46:  SGTh h2.w, h0, h2;\n     | SCB0/1 |  mul |  47:  MOVh h0, h0;\n     |        |      |\n  13 |   SCT0 |  mad |  48:  ADDxc0_s rc, h2, h2.w---;\n     | SCB0/1 |  mul |  49:  MOVh h0(NE0.xxxx), h1;\n \nPass   SCT  TEX  SCB\n  1:   0% 100%  25%\n  2:   0% 100%  25%\n  3:   0% 100%  50%\n  4:   0% 100%  50%\n  5:   0%   0%  50%\n  6: 100%   0%  75%\n  7:   0% 100%  75%\n  8:   0% 100% 100%\n  9:   0% 100%  25%\n 10:   0% 100% 100%\n 11:  50%   0% 100%\n 12:  50%   0% 100%\n 13:  25%   0% 100%\n\nMEAN:  17%  61%  67%\n\nPass   SCT0  SCT1   TEX  SCB0  SCB1\n  1:    0%    0%  100%    0%  100%\n  2:    0%    0%  100%    0%  100%\n  3:    0%    0%  100%  100%  100%\n  4:    0%    0%  100%  100%  100%\n  5:    0%    0%    0%  100%  100%\n  6:  100%  100%    0%  100%  100%\n  7:    0%    0%  100%  100%  100%\n  8:    0%    0%  100%  100%  100%\n  9:    0%    0%  100%    0%  100%\n 10:    0%    0%  100%  100%  100%\n 11:  100%  100%    0%  100%  100%\n 12:  100%  100%    0%  100%  100%\n 13:  100%    0%    0%  100%  100%\n\nMEAN:   30%   23%   61%   76%  100%\nFragment Performance Setup: Driver RSX Compiler, GPU RSX, Flags 0x5\nResults 13 cycles, 3 r regs, 923,076,923 pixels/s\n============================================================================*/\n#if (FXAA_PS3 == 1) && (FXAA_EARLY_EXIT == 0)\n/*--------------------------------------------------------------------------*/\n#pragma regcount 7\n#pragma disablepc all\n#pragma option O3\n#pragma option OutColorPrec=fp16\n#pragma texformat default RGBA8\n/*==========================================================================*/\nhalf4 FxaaPixelShader(\n    // See FXAA Quality FxaaPixelShader() source for docs on Inputs!\n    FxaaFloat2 pos,\n    FxaaFloat4 fxaaConsolePosPos,\n    FxaaTex tex,\n    FxaaTex fxaaConsole360TexExpBiasNegOne,\n    FxaaTex fxaaConsole360TexExpBiasNegTwo,\n    FxaaFloat2 fxaaQualityRcpFrame,\n    FxaaFloat4 fxaaConsoleRcpFrameOpt,\n    FxaaFloat4 fxaaConsoleRcpFrameOpt2,\n    FxaaFloat4 fxaaConsole360RcpFrameOpt2,\n    FxaaFloat fxaaQualitySubpix,\n    FxaaFloat fxaaQualityEdgeThreshold,\n    FxaaFloat fxaaQualityEdgeThresholdMin,\n    FxaaFloat fxaaConsoleEdgeSharpness,\n    FxaaFloat fxaaConsoleEdgeThreshold,\n    FxaaFloat fxaaConsoleEdgeThresholdMin,\n    FxaaFloat4 fxaaConsole360ConstDir\n) {\n/*--------------------------------------------------------------------------*/\n// (1)\n    half4 dir;\n    half4 lumaNe = h4tex2Dlod(tex, half4(fxaaConsolePosPos.zy, 0, 0));\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        lumaNe.w += half(1.0/512.0);\n        dir.x = -lumaNe.w;\n        dir.z = -lumaNe.w;\n    #else\n        lumaNe.y += half(1.0/512.0);\n        dir.x = -lumaNe.y;\n        dir.z = -lumaNe.y;\n    #endif\n/*--------------------------------------------------------------------------*/\n// (2)\n    half4 lumaSw = h4tex2Dlod(tex, half4(fxaaConsolePosPos.xw, 0, 0));\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        dir.x += lumaSw.w;\n        dir.z += lumaSw.w;\n    #else\n        dir.x += lumaSw.y;\n        dir.z += lumaSw.y;\n    #endif        \n/*--------------------------------------------------------------------------*/\n// (3)\n    half4 lumaNw = h4tex2Dlod(tex, half4(fxaaConsolePosPos.xy, 0, 0));\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        dir.x -= lumaNw.w;\n        dir.z += lumaNw.w;\n    #else\n        dir.x -= lumaNw.y;\n        dir.z += lumaNw.y;\n    #endif\n/*--------------------------------------------------------------------------*/\n// (4)\n    half4 lumaSe = h4tex2Dlod(tex, half4(fxaaConsolePosPos.zw, 0, 0));\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        dir.x += lumaSe.w;\n        dir.z -= lumaSe.w;\n    #else\n        dir.x += lumaSe.y;\n        dir.z -= lumaSe.y;\n    #endif\n/*--------------------------------------------------------------------------*/\n// (5)\n    half4 dir1_pos;\n    dir1_pos.xy = normalize(dir.xyz).xz;\n    half dirAbsMinTimesC = min(abs(dir1_pos.x), abs(dir1_pos.y)) * half(FXAA_CONSOLE__PS3_EDGE_SHARPNESS);\n/*--------------------------------------------------------------------------*/\n// (6)\n    half4 dir2_pos;\n    dir2_pos.xy = clamp(dir1_pos.xy / dirAbsMinTimesC, half(-2.0), half(2.0));\n    dir1_pos.zw = pos.xy;\n    dir2_pos.zw = pos.xy;\n    half4 temp1N;\n    temp1N.xy = dir1_pos.zw - dir1_pos.xy * fxaaConsoleRcpFrameOpt.zw;\n/*--------------------------------------------------------------------------*/\n// (7)\n    temp1N = h4tex2Dlod(tex, half4(temp1N.xy, 0.0, 0.0));\n    half4 rgby1;\n    rgby1.xy = dir1_pos.zw + dir1_pos.xy * fxaaConsoleRcpFrameOpt.zw;\n/*--------------------------------------------------------------------------*/\n// (8)\n    rgby1 = h4tex2Dlod(tex, half4(rgby1.xy, 0.0, 0.0));\n    rgby1 = (temp1N + rgby1) * 0.5;\n/*--------------------------------------------------------------------------*/\n// (9)\n    half4 temp2N;\n    temp2N.xy = dir2_pos.zw - dir2_pos.xy * fxaaConsoleRcpFrameOpt2.zw;\n    temp2N = h4tex2Dlod(tex, half4(temp2N.xy, 0.0, 0.0));\n/*--------------------------------------------------------------------------*/\n// (10)\n    half4 rgby2;\n    rgby2.xy = dir2_pos.zw + dir2_pos.xy * fxaaConsoleRcpFrameOpt2.zw;\n    rgby2 = h4tex2Dlod(tex, half4(rgby2.xy, 0.0, 0.0));\n    rgby2 = (temp2N + rgby2) * 0.5;\n/*--------------------------------------------------------------------------*/\n// (11)\n    // compilier moves these scalar ops up to other cycles\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        half lumaMin = min(min(lumaNw.w, lumaSw.w), min(lumaNe.w, lumaSe.w));\n        half lumaMax = max(max(lumaNw.w, lumaSw.w), max(lumaNe.w, lumaSe.w));\n    #else\n        half lumaMin = min(min(lumaNw.y, lumaSw.y), min(lumaNe.y, lumaSe.y));\n        half lumaMax = max(max(lumaNw.y, lumaSw.y), max(lumaNe.y, lumaSe.y));\n    #endif        \n    rgby2 = (rgby2 + rgby1) * 0.5;\n/*--------------------------------------------------------------------------*/\n// (12)\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        bool twoTapLt = rgby2.w < lumaMin;\n        bool twoTapGt = rgby2.w > lumaMax;\n    #else\n        bool twoTapLt = rgby2.y < lumaMin;\n        bool twoTapGt = rgby2.y > lumaMax;\n    #endif\n/*--------------------------------------------------------------------------*/\n// (13)\n    if(twoTapLt || twoTapGt) rgby2 = rgby1;\n/*--------------------------------------------------------------------------*/\n    return rgby2; }\n/*==========================================================================*/\n#endif\n\n\n\n/*============================================================================\n\n       FXAA3 CONSOLE - OPTIMIZED PS3 PIXEL SHADER (WITH EARLY EXIT)\n\n==============================================================================\nThe code mostly matches the assembly.\nI have a feeling that 14 cycles is possible, but was not able to get there.\nMight have to increase register count to get full performance.\nNote this shader does not use perspective interpolation.\n\nUse the following cgc options,\n\n --fenable-bx2 --fastmath --fastprecision --nofloatbindings\n\nUse of FXAA_GREEN_AS_LUMA currently adds a cycle (16 clks).\nWill look at fixing this for FXAA 3.12.\n------------------------------------------------------------------------------\n                             NVSHADERPERF OUTPUT\n------------------------------------------------------------------------------\nFor reference and to aid in debug, output of NVShaderPerf should match this,\n\nShader to schedule:\n  0: texpkb h0.w(TRUE), v5.zyxx, #0\n  2: addh h2.y(TRUE), h0.w, constant(0.001953, 0.000000, 0.000000, 0.000000).x\n  4: texpkb h1.w(TRUE), v5.xwxx, #0\n  6: addh h0.x(TRUE), h1.w, -h2.y\n  7: texpkb h2.w(TRUE), v5.zwzz, #0\n  9: minh h4.w(TRUE), h2.y, h2\n 10: maxh h5.x(TRUE), h2.y, h2.w\n 11: texpkb h0.w(TRUE), v5, #0\n 13: addh h3.w(TRUE), -h0, h0.x\n 14: addh h0.x(TRUE), h0.w, h0\n 15: addh h0.z(TRUE), -h2.w, h0.x\n 16: addh h0.x(TRUE), h2.w, h3.w\n 17: minh h5.y(TRUE), h0.w, h1.w\n 18: nrmh h2.xz(TRUE), h0_n\n 19: minh_m8 h2.w(TRUE), |h2.x|, |h2.z|\n 20: divx h4.xy(TRUE), h2_n.xzzw, h2_n.w\n 21: movr r1.zw(TRUE), v4.xxxy\n 22: maxh h2.w(TRUE), h0, h1\n 23: fenct TRUE\n 24: madr r0.xy(TRUE), -h2.xzzw, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).zwzz, r1.zwzz\n 26: texpkb h0(TRUE), r0, #0\n 28: maxh h5.x(TRUE), h2.w, h5\n 29: minh h5.w(TRUE), h5.y, h4\n 30: madr r1.xy(TRUE), h2.xzzw, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).zwzz, r1.zwzz\n 32: texpkb h2(TRUE), r1, #0\n 34: addh_d2 h2(TRUE), h0, h2\n 35: texpkb h1(TRUE), v4, #0\n 37: maxh h5.y(TRUE), h5.x, h1.w\n 38: minh h4.w(TRUE), h1, h5\n 39: madr r0.xy(TRUE), -h4, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).xyxx, r1.zwzz\n 41: texpkb h0(TRUE), r0, #0\n 43: addh_m8 h5.z(TRUE), h5.y, -h4.w\n 44: madr r2.xy(TRUE), h4, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).xyxx, r1.zwzz\n 46: texpkb h3(TRUE), r2, #0\n 48: addh_d2 h0(TRUE), h0, h3\n 49: addh_d2 h3(TRUE), h0, h2\n 50: movh h0(TRUE), h3\n 51: slth h3.x(TRUE), h3.w, h5.w\n 52: sgth h3.w(TRUE), h3, h5.x\n 53: addx.c0 rc(TRUE), h3.x, h3\n 54: slth.c0 rc(TRUE), h5.z, h5\n 55: movh h0(c0.NE.w), h2\n 56: movh h0(c0.NE.x), h1\n\nIPU0 ------ Simplified schedule: --------\nPass |  Unit  |  uOp |  PC:  Op\n-----+--------+------+-------------------------\n   1 | SCT0/1 |  mov |   0:  TXLr h0.w, g[TEX1].zyxx, const.xxxx, TEX0;\n     |    TEX |  txl |   0:  TXLr h0.w, g[TEX1].zyxx, const.xxxx, TEX0;\n     |   SCB0 |  add |   2:  ADDh h2.y, h0.-w--, const.-x--;\n     |        |      |\n   2 | SCT0/1 |  mov |   4:  TXLr h1.w, g[TEX1].xwxx, const.xxxx, TEX0;\n     |    TEX |  txl |   4:  TXLr h1.w, g[TEX1].xwxx, const.xxxx, TEX0;\n     |   SCB0 |  add |   6:  ADDh h0.x, h1.w---,-h2.y---;\n     |        |      |\n   3 | SCT0/1 |  mov |   7:  TXLr h2.w, g[TEX1].zwzz, const.xxxx, TEX0;\n     |    TEX |  txl |   7:  TXLr h2.w, g[TEX1].zwzz, const.xxxx, TEX0;\n     |   SCB0 |  max |  10:  MAXh h5.x, h2.y---, h2.w---;\n     |   SCB1 |  min |   9:  MINh h4.w, h2.---y, h2;\n     |        |      |\n   4 | SCT0/1 |  mov |  11:  TXLr h0.w, g[TEX1], const.xxxx, TEX0;\n     |    TEX |  txl |  11:  TXLr h0.w, g[TEX1], const.xxxx, TEX0;\n     |   SCB0 |  add |  14:  ADDh h0.x, h0.w---, h0;\n     |   SCB1 |  add |  13:  ADDh h3.w,-h0, h0.---x;\n     |        |      |\n   5 |   SCT0 |  mad |  16:  ADDh h0.x, h2.w---, h3.w---;\n     |   SCT1 |  mad |  15:  ADDh h0.z,-h2.--w-, h0.--x-;\n     |   SCB0 |  min |  17:  MINh h5.y, h0.-w--, h1.-w--;\n     |        |      |\n   6 |   SCT1 |  mov |  18:  NRMh h2.xz, h0;\n     |    SRB |  nrm |  18:  NRMh h2.xz, h0;\n     |   SCB1 |  min |  19:  MINh*8 h2.w, |h2.---x|, |h2.---z|;\n     |        |      |\n   7 |   SCT0 |  div |  20:  DIVx h4.xy, h2.xz--, h2.ww--;\n     |   SCT1 |  mov |  21:  MOVr r1.zw, g[TEX0].--xy;\n     |   SCB1 |  max |  22:  MAXh h2.w, h0, h1;\n     |        |      |\n   8 |   SCT0 |  mad |  24:  MADr r0.xy,-h2.xz--, const.zw--, r1.zw--;\n     |   SCT1 |  mov |  26:  TXLr h0, r0, const.xxxx, TEX0;\n     |    TEX |  txl |  26:  TXLr h0, r0, const.xxxx, TEX0;\n     |   SCB0 |  max |  28:  MAXh h5.x, h2.w---, h5;\n     |   SCB1 |  min |  29:  MINh h5.w, h5.---y, h4;\n     |        |      |\n   9 |   SCT0 |  mad |  30:  MADr r1.xy, h2.xz--, const.zw--, r1.zw--;\n     |   SCT1 |  mov |  32:  TXLr h2, r1, const.xxxx, TEX0;\n     |    TEX |  txl |  32:  TXLr h2, r1, const.xxxx, TEX0;\n     | SCB0/1 |  add |  34:  ADDh/2 h2, h0, h2;\n     |        |      |\n  10 | SCT0/1 |  mov |  35:  TXLr h1, g[TEX0], const.xxxx, TEX0;\n     |    TEX |  txl |  35:  TXLr h1, g[TEX0], const.xxxx, TEX0;\n     |   SCB0 |  max |  37:  MAXh h5.y, h5.-x--, h1.-w--;\n     |   SCB1 |  min |  38:  MINh h4.w, h1, h5;\n     |        |      |\n  11 |   SCT0 |  mad |  39:  MADr r0.xy,-h4, const.xy--, r1.zw--;\n     |   SCT1 |  mov |  41:  TXLr h0, r0, const.zzzz, TEX0;\n     |    TEX |  txl |  41:  TXLr h0, r0, const.zzzz, TEX0;\n     |   SCB0 |  mad |  44:  MADr r2.xy, h4, const.xy--, r1.zw--;\n     |   SCB1 |  add |  43:  ADDh*8 h5.z, h5.--y-,-h4.--w-;\n     |        |      |\n  12 | SCT0/1 |  mov |  46:  TXLr h3, r2, const.xxxx, TEX0;\n     |    TEX |  txl |  46:  TXLr h3, r2, const.xxxx, TEX0;\n     | SCB0/1 |  add |  48:  ADDh/2 h0, h0, h3;\n     |        |      |\n  13 | SCT0/1 |  mad |  49:  ADDh/2 h3, h0, h2;\n     | SCB0/1 |  mul |  50:  MOVh h0, h3;\n     |        |      |\n  14 |   SCT0 |  set |  51:  SLTh h3.x, h3.w---, h5.w---;\n     |   SCT1 |  set |  52:  SGTh h3.w, h3, h5.---x;\n     |   SCB0 |  set |  54:  SLThc0 rc, h5.z---, h5;\n     |   SCB1 |  add |  53:  ADDxc0_s rc, h3.---x, h3;\n     |        |      |\n  15 | SCT0/1 |  mul |  55:  MOVh h0(NE0.wwww), h2;\n     | SCB0/1 |  mul |  56:  MOVh h0(NE0.xxxx), h1;\n \nPass   SCT  TEX  SCB\n  1:   0% 100%  25%\n  2:   0% 100%  25%\n  3:   0% 100%  50%\n  4:   0% 100%  50%\n  5:  50%   0%  25%\n  6:   0%   0%  25%\n  7: 100%   0%  25%\n  8:   0% 100%  50%\n  9:   0% 100% 100%\n 10:   0% 100%  50%\n 11:   0% 100%  75%\n 12:   0% 100% 100%\n 13: 100%   0% 100%\n 14:  50%   0%  50%\n 15: 100%   0% 100%\n\nMEAN:  26%  60%  56%\n\nPass   SCT0  SCT1   TEX  SCB0  SCB1\n  1:    0%    0%  100%  100%    0%\n  2:    0%    0%  100%  100%    0%\n  3:    0%    0%  100%  100%  100%\n  4:    0%    0%  100%  100%  100%\n  5:  100%  100%    0%  100%    0%\n  6:    0%    0%    0%    0%  100%\n  7:  100%  100%    0%    0%  100%\n  8:    0%    0%  100%  100%  100%\n  9:    0%    0%  100%  100%  100%\n 10:    0%    0%  100%  100%  100%\n 11:    0%    0%  100%  100%  100%\n 12:    0%    0%  100%  100%  100%\n 13:  100%  100%    0%  100%  100%\n 14:  100%  100%    0%  100%  100%\n 15:  100%  100%    0%  100%  100%\n\nMEAN:   33%   33%   60%   86%   80%\nFragment Performance Setup: Driver RSX Compiler, GPU RSX, Flags 0x5\nResults 15 cycles, 3 r regs, 800,000,000 pixels/s\n============================================================================*/\n#if (FXAA_PS3 == 1) && (FXAA_EARLY_EXIT == 1)\n/*--------------------------------------------------------------------------*/\n#pragma regcount 7\n#pragma disablepc all\n#pragma option O2\n#pragma option OutColorPrec=fp16\n#pragma texformat default RGBA8\n/*==========================================================================*/\nhalf4 FxaaPixelShader(\n    // See FXAA Quality FxaaPixelShader() source for docs on Inputs!\n    FxaaFloat2 pos,\n    FxaaFloat4 fxaaConsolePosPos,\n    FxaaTex tex,\n    FxaaTex fxaaConsole360TexExpBiasNegOne,\n    FxaaTex fxaaConsole360TexExpBiasNegTwo,\n    FxaaFloat2 fxaaQualityRcpFrame,\n    FxaaFloat4 fxaaConsoleRcpFrameOpt,\n    FxaaFloat4 fxaaConsoleRcpFrameOpt2,\n    FxaaFloat4 fxaaConsole360RcpFrameOpt2,\n    FxaaFloat fxaaQualitySubpix,\n    FxaaFloat fxaaQualityEdgeThreshold,\n    FxaaFloat fxaaQualityEdgeThresholdMin,\n    FxaaFloat fxaaConsoleEdgeSharpness,\n    FxaaFloat fxaaConsoleEdgeThreshold,\n    FxaaFloat fxaaConsoleEdgeThresholdMin,\n    FxaaFloat4 fxaaConsole360ConstDir\n) {\n/*--------------------------------------------------------------------------*/\n// (1)\n    half4 rgbyNe = h4tex2Dlod(tex, half4(fxaaConsolePosPos.zy, 0, 0));\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        half lumaNe = rgbyNe.w + half(1.0/512.0);\n    #else\n        half lumaNe = rgbyNe.y + half(1.0/512.0);\n    #endif\n/*--------------------------------------------------------------------------*/\n// (2)\n    half4 lumaSw = h4tex2Dlod(tex, half4(fxaaConsolePosPos.xw, 0, 0));\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        half lumaSwNegNe = lumaSw.w - lumaNe;\n    #else\n        half lumaSwNegNe = lumaSw.y - lumaNe;\n    #endif\n/*--------------------------------------------------------------------------*/\n// (3)\n    half4 lumaNw = h4tex2Dlod(tex, half4(fxaaConsolePosPos.xy, 0, 0));\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        half lumaMaxNwSw = max(lumaNw.w, lumaSw.w);\n        half lumaMinNwSw = min(lumaNw.w, lumaSw.w);\n    #else\n        half lumaMaxNwSw = max(lumaNw.y, lumaSw.y);\n        half lumaMinNwSw = min(lumaNw.y, lumaSw.y);\n    #endif\n/*--------------------------------------------------------------------------*/\n// (4)\n    half4 lumaSe = h4tex2Dlod(tex, half4(fxaaConsolePosPos.zw, 0, 0));\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        half dirZ =  lumaNw.w + lumaSwNegNe;\n        half dirX = -lumaNw.w + lumaSwNegNe;\n    #else\n        half dirZ =  lumaNw.y + lumaSwNegNe;\n        half dirX = -lumaNw.y + lumaSwNegNe;\n    #endif\n/*--------------------------------------------------------------------------*/\n// (5)\n    half3 dir;\n    dir.y = 0.0;\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        dir.x =  lumaSe.w + dirX;\n        dir.z = -lumaSe.w + dirZ;\n        half lumaMinNeSe = min(lumaNe, lumaSe.w);\n    #else\n        dir.x =  lumaSe.y + dirX;\n        dir.z = -lumaSe.y + dirZ;\n        half lumaMinNeSe = min(lumaNe, lumaSe.y);\n    #endif\n/*--------------------------------------------------------------------------*/\n// (6)\n    half4 dir1_pos;\n    dir1_pos.xy = normalize(dir).xz;\n    half dirAbsMinTimes8 = min(abs(dir1_pos.x), abs(dir1_pos.y)) * half(FXAA_CONSOLE__PS3_EDGE_SHARPNESS);\n/*--------------------------------------------------------------------------*/\n// (7)\n    half4 dir2_pos;\n    dir2_pos.xy = clamp(dir1_pos.xy / dirAbsMinTimes8, half(-2.0), half(2.0));\n    dir1_pos.zw = pos.xy;\n    dir2_pos.zw = pos.xy;\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        half lumaMaxNeSe = max(lumaNe, lumaSe.w);\n    #else\n        half lumaMaxNeSe = max(lumaNe, lumaSe.y);\n    #endif\n/*--------------------------------------------------------------------------*/\n// (8)\n    half4 temp1N;\n    temp1N.xy = dir1_pos.zw - dir1_pos.xy * fxaaConsoleRcpFrameOpt.zw;\n    temp1N = h4tex2Dlod(tex, half4(temp1N.xy, 0.0, 0.0));\n    half lumaMax = max(lumaMaxNwSw, lumaMaxNeSe);\n    half lumaMin = min(lumaMinNwSw, lumaMinNeSe);\n/*--------------------------------------------------------------------------*/\n// (9)\n    half4 rgby1;\n    rgby1.xy = dir1_pos.zw + dir1_pos.xy * fxaaConsoleRcpFrameOpt.zw;\n    rgby1 = h4tex2Dlod(tex, half4(rgby1.xy, 0.0, 0.0));\n    rgby1 = (temp1N + rgby1) * 0.5;\n/*--------------------------------------------------------------------------*/\n// (10)\n    half4 rgbyM = h4tex2Dlod(tex, half4(pos.xy, 0.0, 0.0));\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        half lumaMaxM = max(lumaMax, rgbyM.w);\n        half lumaMinM = min(lumaMin, rgbyM.w);\n    #else\n        half lumaMaxM = max(lumaMax, rgbyM.y);\n        half lumaMinM = min(lumaMin, rgbyM.y);\n    #endif\n/*--------------------------------------------------------------------------*/\n// (11)\n    half4 temp2N;\n    temp2N.xy = dir2_pos.zw - dir2_pos.xy * fxaaConsoleRcpFrameOpt2.zw;\n    temp2N = h4tex2Dlod(tex, half4(temp2N.xy, 0.0, 0.0));\n    half4 rgby2;\n    rgby2.xy = dir2_pos.zw + dir2_pos.xy * fxaaConsoleRcpFrameOpt2.zw;\n    half lumaRangeM = (lumaMaxM - lumaMinM) / FXAA_CONSOLE__PS3_EDGE_THRESHOLD;\n/*--------------------------------------------------------------------------*/\n// (12)\n    rgby2 = h4tex2Dlod(tex, half4(rgby2.xy, 0.0, 0.0));\n    rgby2 = (temp2N + rgby2) * 0.5;\n/*--------------------------------------------------------------------------*/\n// (13)\n    rgby2 = (rgby2 + rgby1) * 0.5;\n/*--------------------------------------------------------------------------*/\n// (14)\n    #if (FXAA_GREEN_AS_LUMA == 0)\n        bool twoTapLt = rgby2.w < lumaMin;\n        bool twoTapGt = rgby2.w > lumaMax;\n    #else\n        bool twoTapLt = rgby2.y < lumaMin;\n        bool twoTapGt = rgby2.y > lumaMax;\n    #endif\n    bool earlyExit = lumaRangeM < lumaMax;\n    bool twoTap = twoTapLt || twoTapGt;\n/*--------------------------------------------------------------------------*/\n// (15)\n    if(twoTap) rgby2 = rgby1;\n    if(earlyExit) rgby2 = rgbyM;\n/*--------------------------------------------------------------------------*/\n    return rgby2; }\n/*==========================================================================*/\n#endif\n"
  },
  {
    "path": "src/glsl/simple.fragment.glsl",
    "content": "#ezquake-definitions\n\nuniform vec4 color;\nout vec4 frag_color;\n\nvoid main(void)\n{\n\tfrag_color = color;\n}\n"
  },
  {
    "path": "src/glsl/simple.vertex.glsl",
    "content": "#ezquake-definitions\n\nlayout(location = 0) in vec2 position;\n\nvoid main(void)\n{\n\tgl_Position = vec4(position, 0, 1);\n}\n"
  },
  {
    "path": "src/glsl/simple3d.fragment.glsl",
    "content": "#ezquake-definitions\n\nuniform vec4 color;\nout vec4 frag_color;\n\nvoid main(void)\n{\n\tfrag_color = color;\n\n#ifdef DRAW_FOG\n\tfrag_color = applyFog(frag_color, gl_FragCoord.z / gl_FragCoord.w);\n#endif\n}\n"
  },
  {
    "path": "src/glsl/simple3d.vertex.glsl",
    "content": "#ezquake-definitions\n\nlayout(location = 0) in vec3 position;\nlayout(location = 6) in int _instanceId;\n\nEZ_SSBO_LAYOUT(std140, EZQ_GL_BINDINGPOINT_BRUSHMODEL_DRAWDATA) EZ_SSBO(WorldCvars) {\n\tWorldDrawInfo drawInfo[EZ_SSBO_ARRAY_SIZE(64)];\n};\n\nvoid main(void)\n{\n\tgl_Position = projectionMatrix * drawInfo[_instanceId].mvMatrix * vec4(position, 1.0);\n}\n"
  },
  {
    "path": "src/hash.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifdef SERVERONLY\n\n#include \"qwsvdef.h\"\n\n#else\n\n#include <stdio.h> // <-- only needed for Hash_BucketStats\n#include <stdlib.h>\n#include <string.h>\n#include <ctype.h>\n\n#include \"quakedef.h\"\n#include \"q_shared.h\"\n#include \"hash.h\"\n\n#endif\n\nhashtable_t *Hash_InitTable(int numbucks)\n{\n\thashtable_t *table;\n\n\ttable = Q_malloc(sizeof(*table));\n\n\ttable->bucket = (bucket_t**)Q_calloc(numbucks, sizeof(bucket_t *));\n\ttable->numbuckets = numbucks;\n\n\treturn table;\n}\n\nvoid Hash_ShutdownTable(hashtable_t* table)\n{\n\tint i;\n\n\tif (!table) {\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < table->numbuckets; ++i) {\n\t\tbucket_t* list = table->bucket[i];\n\n\t\twhile (list) {\n\t\t\tbucket_t* next = list->next;\n\t\t\tif (list->flags & HASH_BUCKET_KEYSTRING_OWNED) {\n\t\t\t\tQ_free(list->keystring);\n\t\t\t}\n\t\t\tQ_free(list);\n\t\t\tlist = next;\n\t\t}\n\t}\n\n\tQ_free(table->bucket);\n\tQ_free(table);\n}\n\n/* http://www.cse.yorku.ca/~oz/hash.html\n * djb2\n * This algorithm (k=33) was first reported by dan bernstein many years ago\n * in comp.lang.c. another version of this algorithm (now favored by bernstein)\n * uses xor: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why\n * it works better than many other constants, prime or not) has never been \n * adequately explained.\n */\nint Hash_Key(char *name, int modulus) {\n\tunsigned int key = 5381;\n\n\tfor (key = 5381; *name; name++)\n\t\tkey = ((key << 5) + key) + *name; /* key * 33 + c */\n\n\treturn (int) (key % modulus);\n}\n\nint Hash_KeyInsensitive(const char *name, int modulus) {\n\tunsigned int key = 5381;\n\n\tfor (key = 5381; *name; name++)\n\t\tkey = ((key << 5) + key) + tolower(*name); /* key * 33 + c */\n\n\treturn (int) (key % modulus);\n}\n\n#if 0\nint Hash_Key(char *name, int modulus)\n{\t//fixme: optimize.\n\tunsigned int key;\n\tfor (key=0;*name; name++)\n\t\tkey += ((key<<3) + (key>>28) + *name);\n\t\t\n\treturn (int)(key%modulus);\n}\nint Hash_KeyInsensitive(char *name, int modulus)\n{\t//fixme: optimize.\n\tunsigned int key;\n\tfor (key=0;*name; name++)\n\t{\n\t\tif (*name >= 'A' && *name <= 'Z')\n\t\t\tkey += ((key<<3) + (key>>28) + (*name-'A'+'a'));\n\t\telse\n\t\t\tkey += ((key<<3) + (key>>28) + *name);\n\t}\n\t\t\n\treturn (int)(key%modulus);\n}\n#endif\n\nvoid *Hash_Get(hashtable_t *table, char *name)\n{\n\tint bucknum = Hash_Key(name, table->numbuckets);\n\tbucket_t *buck;\n\n\tbuck = table->bucket[bucknum];\n\n\twhile(buck)\n\t{\n\t\tif (!STRCMP(name, buck->keystring))\n\t\t\treturn buck->data;\n\n\t\tbuck = buck->next;\n\t}\n\treturn NULL;\n}\n\nvoid *Hash_GetInsensitive(hashtable_t *table, const char *name)\n{\n\tint bucknum = Hash_KeyInsensitive(name, table->numbuckets);\n\tbucket_t *buck;\n\n\tbuck = table->bucket[bucknum];\n\n\twhile(buck)\n\t{\n\t\tif (!strcasecmp(name, buck->keystring))\n\t\t\treturn buck->data;\n\n\t\tbuck = buck->next;\n\t}\n\treturn NULL;\n}\n\nvoid *Hash_GetKey(hashtable_t *table, char *key)\n{\n\tint bucknum = ((uintptr_t) key) % table->numbuckets;\n\tbucket_t *buck;\n\n\tbuck = table->bucket[bucknum];\n\n\twhile(buck)\n\t{\n\t\tif (buck->keystring == key)\n\t\t\treturn buck->data;\n\n\t\tbuck = buck->next;\n\t}\n\treturn NULL;\n}\n\nvoid *Hash_GetNext(hashtable_t *table, char *name, void *old)\n{\n\tint bucknum = Hash_Key(name, table->numbuckets);\n\tbucket_t *buck;\n\n\tbuck = table->bucket[bucknum];\n\n\twhile(buck)\n\t{\n\t\tif (!STRCMP(name, buck->keystring))\n\t\t{\n\t\t\tif (buck->data == old)\t//found the old one\n\t\t\t\tbreak;\n\t\t}\n\n\t\tbuck = buck->next;\n\t}\n\tif (!buck)\n\t\treturn NULL;\n\n\tbuck = buck->next;//don't return old\n\twhile(buck)\n\t{\n\t\tif (!STRCMP(name, buck->keystring))\n\t\t\treturn buck->data;\n\n\t\tbuck = buck->next;\n\t}\n\treturn NULL;\n}\n\nvoid *Hash_GetNextInsensitive(hashtable_t *table, char *name, void *old)\n{\n\tint bucknum = Hash_KeyInsensitive(name, table->numbuckets);\n\tbucket_t *buck;\n\n\tbuck = table->bucket[bucknum];\n\n\twhile(buck)\n\t{\n\t\tif (!STRCMP(name, buck->keystring))\n\t\t{\n\t\t\tif (buck->data == old)\t//found the old one\n\t\t\t\tbreak;\n\t\t}\n\n\t\tbuck = buck->next;\n\t}\n\tif (!buck)\n\t\treturn NULL;\n\n\tbuck = buck->next;//don't return old\n\twhile(buck)\n\t{\n\t\tif (!STRCMP(name, buck->keystring))\n\t\t\treturn buck->data;\n\n\t\tbuck = buck->next;\n\t}\n\treturn NULL;\n}\n\nvoid *Hash_Add(hashtable_t *table, char *name, void *data) \n{\n\tint bucknum = Hash_Key(name, table->numbuckets);\n\n\tbucket_t *buck = (bucket_t *) Q_malloc(sizeof(bucket_t));\n\tchar *keystring = Q_strdup(name);\n\n\tbuck->data = data;\n\tbuck->keystring = keystring;\n\tbuck->next = table->bucket[bucknum];\n\tbuck->flags = HASH_BUCKET_KEYSTRING_OWNED;\n\ttable->bucket[bucknum] = buck;\n\n\treturn buck;\n}\n\nvoid *Hash_AddInsensitive(hashtable_t *table, char *name, void *data) \n{\n\tint bucknum = Hash_KeyInsensitive(name, table->numbuckets);\n\n\tbucket_t *buck = (bucket_t *) Q_malloc(sizeof(bucket_t));\n\tchar *keystring = Q_strdup(name);\n\n\tbuck->data = data;\n\tbuck->keystring = keystring;\n\tbuck->next = table->bucket[bucknum];\n\tbuck->flags = HASH_BUCKET_KEYSTRING_OWNED;\n\ttable->bucket[bucknum] = buck;\n\n\treturn buck;\n}\n\nvoid *Hash_AddKey(hashtable_t *table, char *key, void *data, bucket_t *buck)\n{\n\tint bucknum = ((uintptr_t) key) % table->numbuckets;\n\n\tbuck->data = data;\n\tbuck->keystring = key;\n\tbuck->next = table->bucket[bucknum];\n\ttable->bucket[bucknum] = buck;\n\n\treturn buck;\n}\n\nvoid Hash_Remove(hashtable_t *table, char *name)\n{\n\tint bucknum = Hash_Key(name, table->numbuckets);\n\tbucket_t *buck, *to_remove;\n\n\tbuck = table->bucket[bucknum];\n\n\tif (!STRCMP(name, buck->keystring))\n\t{\n\t\ttable->bucket[bucknum] = buck->next;\n\t\tQ_free(buck->keystring);\n\t\tQ_free(buck);\n\t\treturn;\n\t}\n\n\n\twhile(buck->next)\n\t{\n\t\tif (!STRCMP(name, buck->next->keystring))\n\t\t{\n\t\t\tto_remove = buck->next;\n\t\t\tbuck->next = to_remove->next;\n\t\t\tQ_free(to_remove->keystring);\n\t\t\tQ_free(to_remove);\n\t\t\treturn;\n\t\t}\n\n\t\tbuck = buck->next;\n\t}\n\treturn;\n}\n\nvoid Hash_RemoveData(hashtable_t *table, char *name, void *data)\n{\n\tint bucknum = Hash_Key(name, table->numbuckets);\n\tbucket_t *buck;\t\n\n\tbuck = table->bucket[bucknum];\n\n\tif (buck->data == data)\n\t\tif (!STRCMP(name, buck->keystring))\n\t\t{\n\t\t\ttable->bucket[bucknum] = buck->next;\n\t\t\tQ_free(buck->keystring);\n\t\t\tQ_free(buck);\n\t\t\treturn;\n\t\t}\n\n\n\twhile(buck->next)\n\t{\n\t\tif (buck->next->data == data)\n\t\t\tif (!STRCMP(name, buck->next->keystring))\n\t\t\t{\n\t\t\t\tbuck->next = buck->next->next;\n\t\t\t\tQ_free(buck->next->keystring);\n\t\t\t\tQ_free(buck->next);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\tbuck = buck->next;\n\t}\n\treturn;\n}\n\nvoid Hash_RemoveKey(hashtable_t *table, char *key)\n{\n\tint bucknum = ((uintptr_t) key) % table->numbuckets;\n\tbucket_t *buck, *to_remove;\n\n\tbuck = table->bucket[bucknum];\n\n\tif (buck->keystring == key)\n\t{\n\t\ttable->bucket[bucknum] = buck->next;\n\t\tQ_free(buck->keystring);\n\t\tQ_free(buck);\n\t\treturn;\n\t}\n\n\n\twhile(buck->next)\n\t{\n\t\tif (buck->next->keystring == key)\n\t\t{\n\t\t\tto_remove = buck->next;\n\t\t\tbuck->next = to_remove->next;\n\t\t\tQ_free(to_remove->keystring);\n\t\t\tQ_free(to_remove);\n\t\t\treturn;\n\t\t}\n\n\t\tbuck = buck->next;\n\t}\n\treturn;\n}\n\nvoid Hash_Flush(hashtable_t *table) \n{\n\tint i;\n\tfor (i = 0; i < table->numbuckets; i++)\n\t{\n\t\tbucket_t *bucket, *next;\n\t\tbucket = table->bucket[i];\n\t\ttable->bucket[i] = NULL;\n\t\t\n\t\twhile (bucket)\n\t\t{\n\t\t\tnext = bucket->next;\n\t\t\tQ_free(bucket->keystring);\n\t\t\tQ_free(bucket);\n\t\t\tbucket = next;\n\t\t}\n\t}\n\treturn;\n}\n\n#if 0\nvoid Hash_BucketStats(hashtable_t *table)\n{\n\tint i;\n\tbucket_t *buck;\t\n\tint bucket_count;\n\n\tfor (i = 0; i < table->numbuckets; i++) {\n\t\tbucket_count = 0;\n\t\tfor (buck = table->bucket[i]; buck; buck = buck->next) {\n\t\t\tbucket_count++;\n\t\t}\n\t\tprintf(\"table[%d] = %d\\n\", i, bucket_count);\n\t}\n}\n#endif\n"
  },
  {
    "path": "src/hash.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n//=============================\n//David's hash tables\n//string based.\n\n#ifndef __HASH_H__\n#define __HASH_H__\n\n#define STRCMP(s1,s2) (((*s1)!=(*s2)) || strcmp(s1+1,s2+1))\t//saves about 2-6 out of 120 - expansion of idea from fastqcc\ntypedef struct bucket_s {\n\tvoid *data;\n\tchar *keystring;\n\tstruct bucket_s *next;\n\tint flags;\n} bucket_t;\n\n#define HASH_BUCKET_KEYSTRING_OWNED 1\n\ntypedef struct hashtable_s {\n\tint numbuckets;\n\tbucket_t **bucket;\n} hashtable_t;\n\nhashtable_t *Hash_InitTable(int numbucks);\nvoid Hash_ShutdownTable(hashtable_t* table);\n\nint Hash_Key(char *name, int modulus);\nvoid *Hash_Get(hashtable_t *table, char *name);\nvoid *Hash_GetInsensitive(hashtable_t *table, const char *name);\nvoid *Hash_GetKey(hashtable_t *table, char *key);\nvoid *Hash_GetNext(hashtable_t *table, char *name, void *old);\nvoid *Hash_GetNextInsensitive(hashtable_t *table, char *name, void *old);\nvoid *Hash_Add(hashtable_t *table, char *name, void *data); \nvoid *Hash_AddInsensitive(hashtable_t *table, char *name, void *data); \nvoid Hash_Remove(hashtable_t *table, char *name);\nvoid Hash_RemoveData(hashtable_t *table, char *name, void *data);\nvoid Hash_RemoveKey(hashtable_t *table, char *key);\nvoid *Hash_AddKey(hashtable_t *table, char *key, void *data, bucket_t *buck);\nvoid Hash_Flush(hashtable_t *table);\n\n#if 0\n/* Print some stats on the bucket distrubution */\nvoid Hash_BucketStats(hashtable_t *table);\n#endif\n\n#endif // __HASH_H__\n"
  },
  {
    "path": "src/help.c",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: help.c,v 1.16 2007-09-30 22:59:23 disconn3ct Exp $\n*/\n\n/*\n * Variables and commands help system\n */\n\n// for QuakeWorld Tutorial menu, look into help_files.c\n\n#include \"quakedef.h\"\n#include <jansson.h>\n\nextern void CharsToBrown(char*, char*);\nextern char* CharsToBrownStatic(char* in);\n\ntypedef enum {\n\tt_boolean,\n\tt_enum,         // \"value1, value2, value_wih_spaces\"\n\tt_integer,      // \"min max\"\n\tt_float,        // \"min max\"\n\tt_string,       // \"max length\"\n\tt_unknown       // (missing from docs)\n} cvartype_t;\n\nenum {\n\thelpdoc_variables,\n\thelpdoc_commands,\n\thelpdoc_cmdlineparams,\n\thelpdoc_macros,\n\n\thelpdoc_count\n};\n\n#define CONSOLE_HELP_MARGIN 2\nstatic json_t* document_root[helpdoc_count];\n\ntypedef struct json_variable_s\n{\n\tconst char *name;\n\tconst char *default_value;\n\tconst char *description;\n\tint value_type;\n\tconst json_t* values;\n\tconst char *remarks;\n\tconst char* group_id;\n\tjson_t* json;\n} json_variable_t;\n\ntypedef struct json_command_s\n{\n\tconst char *name;\n\tconst char *description;\n\tconst char *syntax;\n\tconst json_t* arguments;\n\tconst char *remarks;\n} json_command_t;\n\ntypedef struct json_mapping_s {\n\tconst char* name;\n\tint value;\n} json_mapping_t;\n\nstatic json_mapping_t variabletype_mappings[] = {\n\t{ \"string\", t_string },\n\t{ \"integer\", t_integer },\n\t{ \"float\", t_float },\n\t{ \"boolean\", t_boolean },\n\t{ \"bool\", t_boolean },\n\t{ \"enum\", t_enum }\n};\n\n#define CMDLINEPARAM_FLAGS_NONE       0\n#define CMDLINEPARAM_FLAGS_INCOMPLETE 1\n\nstatic json_mapping_t cmdlineparam_flags_mappings[] = {\n\t{ \"incomplete\", CMDLINEPARAM_FLAGS_INCOMPLETE }\n};\n\n#define CMDLINEPARAM_BUILDS_DEBUG     1\n#define CMDLINEPARAM_BUILDS_RELEASE   2\n#define CMDLINEPARAM_BUILDS_ALL       (CMDLINEPARAM_BUILDS_DEBUG | CMDLINEPARAM_BUILDS_RELEASE)\n\nstatic json_mapping_t cmdlineparam_build_mappings[] = {\n\t{ \"debug\", CMDLINEPARAM_BUILDS_DEBUG },\n\t{ \"release\", CMDLINEPARAM_BUILDS_RELEASE }\n};\n\n#define CMDLINEPARAM_SYSTEMS_WINDOWS  1\n#define CMDLINEPARAM_SYSTEMS_LINUX    2\n#define CMDLINEPARAM_SYSTEMS_OSX      4\n#define CMDLINEPARAM_SYSTEMS_ALL      (CMDLINEPARAM_SYSTEMS_WINDOWS | CMDLINEPARAM_SYSTEMS_LINUX | CMDLINEPARAM_SYSTEMS_OSX)\n\nstatic json_mapping_t cmdlineparam_systems_mappings[] = {\n\t{ \"windows\", CMDLINEPARAM_SYSTEMS_WINDOWS },\n\t{ \"linux\", CMDLINEPARAM_SYSTEMS_LINUX },\n\t{ \"osx\", CMDLINEPARAM_SYSTEMS_OSX }\n};\n\ntypedef struct json_cmdlineparam_s {\n\tconst char* name;\n\tconst char* description;\n\tconst char* remarks;\n\tint systems;\n\tint builds;\n\tint flags;\n\tconst char* arguments;\n} json_cmdlineparam_t;\n\n#define MACROS_FLAGS_NONE             0\n#define MACROS_FLAGS_SPECTATORONLY    1\n\nstatic json_mapping_t macros_flags_mappings[] = {\n\t{ \"spectator-only\", MACROS_FLAGS_SPECTATORONLY }\n};\n\ntypedef struct json_macro_s {\n\tconst char* name;\n\tconst char* description;\n\tconst char* remarks;\n\tconst char* type;\n\tconst json_t* related_cvars;\n\tint flags;\n\n\tjson_t* json;\n} json_macro_t;\n\n#define HelpReadBitField(json, array_name, default_value, mappings) (JSON_ReadBitField(json_object_get(json, array_name), default_value, mappings, sizeof(mappings) / sizeof(mappings[0])))\n\nstatic qbool JSON_MapValue(const char* string, json_mapping_t* mappings, int mapping_count, int* value)\n{\n\tint i;\n\n\tif (string && string[0]) {\n\t\tfor (i = 0; i < mapping_count; ++i) {\n\t\t\tif (!strcmp(string, mappings[i].name)) {\n\t\t\t\t*value = mappings[i].value;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n\nstatic int JSON_ReadBitField(const json_t* arr, int empty_value, json_mapping_t* mappings, int mapping_count)\n{\n\tif (!json_is_array(arr)) {\n\t\tif (json_is_string(arr)) {\n\t\t\tint result = 0;\n\n\t\t\tif (JSON_MapValue(json_string_value(arr), mappings, mapping_count, &result)) {\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\t// error: name not valid\n\t\t\treturn empty_value;\n\t\t}\n\t\t// else error: specified but not string|array\n\t\treturn empty_value;\n\t}\n\telse if (json_array_size(arr) == 0) {\n\t\treturn empty_value;\n\t}\n\telse {\n\t\tsize_t index;\n\t\tconst json_t* value;\n\t\tint result = 0;\n\n\t\tjson_array_foreach(arr, index, value)\n\t\t{\n\t\t\tif (json_is_string(value)) {\n\t\t\t\tconst char* stringvalue = json_string_value(value);\n\t\t\t\tint flagvalue = 0;\n\n\t\t\t\tif (JSON_MapValue(stringvalue, mappings, mapping_count, &flagvalue)) {\n\t\t\t\t\tresult |= flagvalue;\n\t\t\t\t}\n\t\t\t\t// else error: string not valid\n\t\t\t}\n\t\t\t// else error: expected string\n\t\t}\n\n\t\treturn result;\n\t}\n}\n\nstatic void Help_DescribeCmd(const json_command_t *cmd)\n{\n\tif (!cmd) \n\t\treturn;\n\n    // description\n    con_margin = CONSOLE_HELP_MARGIN;\n    Com_Printf(\"%s\\n\", cmd->description);\n    con_margin = 0;\n\n    // syntax\n    Com_Printf(\"\\n\");\n\tif (cmd->syntax) {\n\t\tcon_ormask = 128;\n\t\tCom_Printf (\"syntax\\n\");\n\t\tcon_ormask = 0;\n\t\tcon_margin = CONSOLE_HELP_MARGIN;\n\t\tCom_Printf (\"%s %s\\n\", cmd->name, cmd->syntax);\n\t\tcon_margin = 0;\n\t}\n\n    // arguments\n    if (cmd->arguments)\n    {\n\t\tsize_t i = 0;\n\t\tsize_t argCount = json_array_size(cmd->arguments);\n\n        Com_Printf(\"\\n\");\n        con_ormask = 128;\n        Com_Printf(\"arguments\\n\");\n        con_ormask = 0;\n\n\t\tfor (i = 0; i < argCount; ++i) { \n\t\t\tjson_t* arg = json_array_get(cmd->arguments, i);\n\n            con_ormask = 128;\n            con_margin = CONSOLE_HELP_MARGIN;\n            Com_Printf(\"%s\", json_string_value(json_object_get(arg, \"name\")));\n            con_ormask = 0;\n            con_margin += CONSOLE_HELP_MARGIN;\n            Com_Printf(\" - %s\\n\", json_string_value(json_object_get(arg, \"description\")));\n            con_margin = 0;\n        }\n    }\n\n    // remarks\n    if (cmd->remarks)\n    {\n        Com_Printf(\"\\n\");\n        con_ormask = 128;\n        Com_Printf(\"remarks\\n\");\n        con_ormask = 0;\n        con_margin = CONSOLE_HELP_MARGIN;\n        Com_Printf(\"%s\\n\", cmd->remarks);\n        con_margin = 0;\n    }\n}\n\nstatic void Help_DescribeVar(const json_variable_t *var)\n{\n\tif (!var) \n\t\treturn;\n\n\t// description\n\tif (var->description && strlen(var->description))\n\t{\n\t\tcon_margin = CONSOLE_HELP_MARGIN;\n\t\tCom_Printf(\"%s\\n\", var->description);\n\t\tcon_margin = 0;\n\t}\n\n    // value\n\tif (var->values)\n\t{\n\t\tsize_t valueCount = json_array_size(var->values);\n\t\tsize_t i = 0;\n\n\t\tCom_Printf(\"\\n\");\n\t\tcon_ormask = 128;\n\t\tCom_Printf(\"values\\n\");\n\t\tcon_ormask = 0;\n\t\tcon_margin = CONSOLE_HELP_MARGIN;\n\t\tfor (i = 0; i < valueCount; ++i) {\n\t\t\tconst json_t* value       = json_array_get(var->values, i);\n\t\t\tconst char*   name        = json_string_value(json_object_get(value, \"name\"));\n\t\t\tconst char*   description = json_string_value(json_object_get(value, \"description\"));\n\t\t\t// if value is *, just show basic description\n\t\t\tif (strcmp(name, \"*\")) {\n\t\t\t\tcon_ormask = 128;\n\t\t\t\tif (var->value_type == t_boolean && !strcmp(name, \"false\")) {\n\t\t\t\t\tCom_Printf(\"0\");\n\t\t\t\t}\n\t\t\t\telse if (var->value_type == t_boolean && !strcmp(name, \"true\")) {\n\t\t\t\t\tCom_Printf(\"1\");\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tCom_Printf(\"%s\", name);\n\t\t\t\t}\n\t\t\t\tcon_ormask = 0;\n\t\t\t\tcon_margin += CONSOLE_HELP_MARGIN;\n\t\t\t\tCom_Printf(\" - %s\\n\", description);\n\t\t\t\tcon_margin -= CONSOLE_HELP_MARGIN;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCom_Printf(\"%s\\n\", description);\n\t\t\t}\n\t\t}\n\t\tcon_margin = 0;\n\t}\n\telse if (var->value_type == t_boolean)\n\t{\n\t\tcon_margin += CONSOLE_HELP_MARGIN;\n\t\tCom_Printf(\"Boolean value. Use \");\n\t\tcon_ormask = 128;\n\t\tCom_Printf(\"0 or 1\");\n\t\tcon_ormask = 0;\n\t\tCom_Printf(\" as a value.\\n\");\n\t\tcon_margin -= CONSOLE_HELP_MARGIN;\n\t}\n\n    // remarks\n\tif (var->remarks && strlen(var->remarks))\n    {\n        Com_Printf(\"\\n\");\n        con_ormask = 128;\n        Com_Printf(\"remarks\\n\");\n        con_ormask = 0;\n        con_margin = CONSOLE_HELP_MARGIN;\n        Com_Printf(\"%s\\n\", var->remarks);\n        con_margin = 0;\n    }\n}\n\nstatic void Help_DescribeMacro(const json_macro_t* macro)\n{\n\tif (!macro) {\n\t\treturn;\n\t}\n\n\t// description\n\tif (macro->description && strlen(macro->description)) {\n\t\tcon_margin = CONSOLE_HELP_MARGIN;\n\t\tCom_Printf(\"%s\\n\", macro->description);\n\t\tcon_margin = 0;\n\t}\n\n\t// value\n\tif (macro->type && strlen(macro->type)) {\n\t\tcon_ormask = 128;\n\t\tCom_Printf(\"Type: \");\n\t\tcon_ormask = 0;\n\n\t\tCom_Printf(\"%s\\n\", macro->type);\n\t}\n\n\t// remarks\n\tif ((macro->remarks && macro->remarks[0]) || macro->flags) {\n\t\tCom_Printf(\"\\n\");\n\t\tcon_ormask = 128;\n\t\tCom_Printf(\"remarks\\n\");\n\t\tcon_ormask = 0;\n\t\tcon_margin = CONSOLE_HELP_MARGIN;\n\t\tif (macro->flags & MACROS_FLAGS_SPECTATORONLY) {\n\t\t\tCom_Printf(\"Limited to spectator mode only\\n\");\n\t\t}\n\t\tif (macro->remarks && macro->remarks[0]) {\n\t\t\tCom_Printf(\"%s\\n\", macro->remarks);\n\t\t}\n\t\tcon_margin = 0;\n\t}\n\n\tif (macro->related_cvars) {\n\t\tint i;\n\t\tconst json_t* cvar;\n\n\t\tCom_Printf(\"\\n\");\n\t\tcon_ormask = 128;\n\t\tCom_Printf(\"related cvars:\\n\");\n\t\tcon_ormask = 0;\n\t\tcon_margin = CONSOLE_HELP_MARGIN;\n\t\tjson_array_foreach(macro->related_cvars, i, cvar) {\n\t\t\tif (json_is_string(cvar)) {\n\t\t\t\tCom_Printf(\"%s\\n\", json_string_value(cvar));\n\t\t\t}\n\t\t}\n\t\tcon_margin = 0;\n\t}\n}\n\nstatic void Help_DescribeCommandLineParam(const json_cmdlineparam_t* param)\n{\n\tqbool system_comment = false;\n\n\tif (!param) {\n\t\treturn;\n\t}\n\n#if defined(_WIN32)\n\tsystem_comment = !(param->systems & CMDLINEPARAM_SYSTEMS_WINDOWS);\n#elif defined(__APPLE__)\n\tsystem_comment = !(param->systems & CMDLINEPARAM_SYSTEMS_OSX);\n#else\n\tsystem_comment = !(param->systems & CMDLINEPARAM_SYSTEMS_LINUX);\n#endif\n\n\t// description\n\tif (param->description && strlen(param->description)) {\n\t\tcon_margin = CONSOLE_HELP_MARGIN;\n\t\tCom_Printf(\"%s\\n\", param->description);\n\t\tcon_margin = 0;\n\t}\n\n\tif (param->arguments && param->arguments[0]) {\n\t\tCom_Printf(\"\\n\");\n\t\tcon_ormask = 128;\n\t\tCom_Printf(\"arguments: \");\n\t\tcon_ormask = 0;\n\t\tCom_Printf(\"%s\\n\", param->arguments);\n\t}\n\n\t// remarks\n\tif ((param->remarks && param->remarks[0]) || param->flags || system_comment) {\n\t\tCom_Printf(\"\\n\");\n\t\tcon_ormask = 128;\n\t\tCom_Printf(\"remarks\\n\");\n\t\tcon_ormask = 0;\n\t\tcon_margin = CONSOLE_HELP_MARGIN;\n\t\tif (system_comment) {\n\t\t\tCom_Printf(\"(not supported on this operating system)\\n\");\n\t\t}\n\t\tif (param->flags & CMDLINEPARAM_FLAGS_INCOMPLETE) {\n\t\t\tCom_Printf(\"(this documentation is flagged as incomplete)\\n\");\n\t\t}\n\t\tif (param->remarks && param->remarks[0]) {\n\t\t\tCom_Printf(\"%s\\n\", param->remarks);\n\t\t}\n\t\tcon_margin = 0;\n\t}\n}\n\nstatic const json_command_t* JSON_Command_Load(const char* name)\n{\n\tstatic json_command_t result;\n\tconst json_t* command = NULL;\n\n\tcommand = json_object_get(document_root[helpdoc_commands], name);\n\tif (command == NULL)\n\t\treturn NULL;\n\n\tmemset(&result, 0, sizeof(result));\n\tresult.name = name;\n\tresult.description = json_string_value(json_object_get(command, \"description\"));\n\tresult.syntax = json_string_value(json_object_get(command, \"syntax\"));\n\tresult.remarks = json_string_value(json_object_get(command, \"remarks\"));\n\tresult.arguments = json_object_get(command, \"arguments\");\n\tif (!json_is_array(result.arguments))\n\t\tresult.arguments = NULL;\n\n\treturn &result;\n}\n\nstatic const json_variable_t* JSON_Variable_Load(const char* name)\n{\n\tstatic json_variable_t result;\n\tconst json_t* varsObj = NULL;\n\tjson_t* variable = NULL;\n\tconst char* variableType = NULL;\n\n\tvarsObj = json_object_get(document_root[helpdoc_variables], \"vars\");\n\tif (varsObj == NULL) {\n\t\treturn NULL;\n\t}\n\n\tvariable = json_object_get(varsObj, name);\n\tif (variable == NULL) {\n\t\treturn NULL;\n\t}\n\tvariableType = json_string_value(json_object_get(variable, \"type\"));\n\n\tmemset(&result, 0, sizeof(result));\n\tresult.default_value = json_string_value(json_object_get(variable, \"default\"));\n\tresult.description = json_string_value(json_object_get(variable, \"desc\"));\n\tresult.name = name;\n\tresult.remarks = json_string_value(json_object_get(variable, \"remarks\"));\n\tif (!JSON_MapValue(variableType, variabletype_mappings, sizeof(variabletype_mappings) / sizeof(variabletype_mappings[0]), &result.value_type)) {\n\t\tresult.value_type = t_unknown;\n\t}\n\tresult.values = json_object_get(variable, \"values\");\n\tif (!json_is_array(result.values)) {\n\t\tresult.values = NULL;\n\t}\n\tresult.group_id = json_string_value(json_object_get(variable, \"group_id\"));\n\tresult.json = variable;\n\n\treturn &result;\n}\n\nconst json_cmdlineparam_t* JSON_CommandLineParam_Load(const char* name)\n{\n\tstatic json_cmdlineparam_t result;\n\tconst json_t* json = NULL;\n\n\tjson = json_object_get(document_root[helpdoc_cmdlineparams], name);\n\tif (json == NULL) {\n\t\treturn NULL;\n\t}\n\n\tmemset(&result, 0, sizeof(result));\n\tresult.name = name;\n\tresult.description = json_string_value(json_object_get(json, \"description\"));\n\tresult.remarks = json_string_value(json_object_get(json, \"remarks\"));\n\tresult.builds = HelpReadBitField(json, \"builds\", CMDLINEPARAM_BUILDS_ALL, cmdlineparam_build_mappings);\n\tresult.systems = HelpReadBitField(json, \"systems\", CMDLINEPARAM_SYSTEMS_ALL, cmdlineparam_systems_mappings);\n\tresult.flags = HelpReadBitField(json, \"flags\", CMDLINEPARAM_FLAGS_NONE, cmdlineparam_flags_mappings);\n\tresult.arguments = json_string_value(json_object_get(json, \"arguments\"));\n\n\treturn &result;\n}\n\nconst json_macro_t* JSON_Macro_Load(const char* name)\n{\n\tstatic json_macro_t result;\n\tjson_t* json = NULL;\n\n\tjson = json_object_get(document_root[helpdoc_macros], name);\n\tif (json == NULL) {\n\t\treturn NULL;\n\t}\n\n\tmemset(&result, 0, sizeof(result));\n\tresult.name = name;\n\tresult.description = json_string_value(json_object_get(json, \"description\"));\n\tresult.remarks = json_string_value(json_object_get(json, \"remarks\"));\n\tresult.flags = HelpReadBitField(json, \"flags\", 0, macros_flags_mappings);\n\tresult.related_cvars = json_object_get(json, \"related-cvars\");\n\tif (!json_is_array(result.related_cvars) || json_array_size(result.related_cvars) == 0) {\n\t\tresult.related_cvars = NULL;\n\t}\n\tresult.json = json;\n\n\treturn &result;\n}\n\nvoid Help_DescribeCvar (cvar_t *v)\n{\n\tHelp_DescribeVar(JSON_Variable_Load(v->name));\n}\n\nvoid Help_VarDescription (const char *varname, char* buf, size_t bufsize)\n{\n\textern cvar_t menu_advanced;\n\tconst json_variable_t *var;\n\n\tvar = JSON_Variable_Load (varname);\n\t\n\tif(menu_advanced.integer && strlen(varname)){\n\t\tstrlcat(buf, CharsToBrownStatic(\"Variable Name: \"), bufsize);\n\t\tstrlcat(buf, varname, bufsize);\n\t\tstrlcat(buf, \"\\n\", bufsize);\n\n\t\tif (var->default_value) {\n\t\t\tstrlcat(buf, CharsToBrownStatic(\"Default: \"), bufsize);\n\t\t\tstrlcat(buf, var->default_value, bufsize);\n\t\t\tstrlcat(buf, \"\\n\", bufsize);\n\t\t}\n\t}\n\t\n\tif (!var)\n\t\treturn;\n\n\tif (var->description && strlen (var->description) > 1) {\n\t\tstrlcat (buf, var->description, bufsize);\n\t\tstrlcat (buf, \"\\n\", bufsize);\n\t}\n\n\tif (var->values)\n\t{\n\t\tsize_t valueCount = json_array_size(var->values);\n\t\tsize_t i = 0;\n\n\t\tstrlcat (buf, \"\\n\", bufsize);\n\t\tstrlcat (buf, CharsToBrownStatic(\"values\"), bufsize);\n\t\tstrlcat (buf, \"\\n\", bufsize);\n\t\tfor (i = 0; i < valueCount; ++i) {\n\t\t\tconst json_t* value = json_array_get(var->values, i);\n\t\t\tconst char*   name = json_string_value(json_object_get(value, \"name\"));\n\t\t\tconst char*   description = json_string_value(json_object_get(value, \"description\"));\n\n\t\t\tswitch (var->value_type)\n\t\t\t{\n\t\t\tcase t_string:\n\t\t\tcase t_integer:\n\t\t\tcase t_float:\n\t\t\t\tstrlcat (buf, description, bufsize);\n\t\t\t\tstrlcat (buf, \"\\n\", bufsize);\n\t\t\t\tbreak;\n\t\t\tcase t_boolean:\n\t\t\tcase t_enum:\n\t\t\tdefault:\n\t\t\t\tif (var->value_type == t_boolean && !strcmp(name, \"false\"))\n\t\t\t\t\tstrlcat (buf, CharsToBrownStatic(\"0\"), bufsize);\n\t\t\t\telse if (var->value_type == t_boolean && !strcmp(name, \"true\"))\n\t\t\t\t\tstrlcat (buf, CharsToBrownStatic(\"1\"), bufsize);\n\t\t\t\telse\n\t\t\t\t\tstrlcat (buf, CharsToBrownStatic((char*)name), bufsize);\n\n\t\t\t\tstrlcat (buf, \" - \", bufsize);\n\t\t\t\tstrlcat (buf, description, bufsize);\n\t\t\t\tstrlcat (buf, \"\\n\", bufsize);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (var->remarks) {\n\t\tstrlcat(buf, CharsToBrownStatic(\"remarks\"), bufsize);\n\t\tstrlcat(buf, \"\\n\", bufsize);\n\t\tstrlcat(buf, var->remarks, bufsize);\n\t\tstrlcat(buf, \"\\n\", bufsize);\n\t}\n}\n\nstatic void Help_UnloadDocument(json_t** root)\n{\n\tif (*root != NULL) {\n\t\tjson_decref(*root);\n\t\t*root = NULL;\n\t}\n}\n\nstatic void Help_UnloadDocuments(void)\n{\n\tint i;\n\n\tfor (i = 0; i < helpdoc_count; ++i) {\n\t\tHelp_UnloadDocument(&document_root[i]);\n\t}\n}\n\n#define DeclareHelpDocument(id, docname) \\\n{ \\\n\textern unsigned char docname ## _json[]; \\\n\textern unsigned int docname ## _json_len; \\\n\\\n\tdocument_names[helpdoc_ ## id] = #docname; \\\n\tdocument_content[helpdoc_ ## id] = docname ## _json; \\\n\tdocument_length[helpdoc_ ## id] = docname ## _json_len; \\\n}\n\nstatic void Help_LoadDocuments(void)\n{\n\tjson_error_t error;\n\tconst char* document_names[helpdoc_count] = { 0 };\n\tunsigned char* document_content[helpdoc_count] = { 0 };\n\tunsigned int document_length[helpdoc_count] = { 0 };\n\tint i;\n\n\tDeclareHelpDocument(macros, help_macros)\n\tDeclareHelpDocument(commands, help_commands)\n\tDeclareHelpDocument(variables, help_variables)\n\tDeclareHelpDocument(cmdlineparams, help_cmdline_params)\n\n\tfor (i = 0; i < helpdoc_count; ++i) {\n\t\tHelp_UnloadDocument(&document_root[i]);\n\n\t\tdocument_root[i] = json_loadb((char*)document_content[i], document_length[i], 0, &error);\n\t\tif (document_root[i] == NULL) {\n\t\t\tCom_Printf(\"\\020%s\\021: JSON error on line %d: %s\\n\", document_names[i], error.line, error.text);\n\t\t}\n\t\telse if (!json_is_object(document_root[i])) {\n\t\t\tCom_Printf(\"\\020%s\\021: invalid JSON, root is not an object\\n\", document_names[i]);\n\t\t\tHelp_UnloadDocument(&document_root[i]);\n\t\t}\n\t}\n}\n\nvoid Help_Describe_f(void)\n{\n    qbool found = false;\n    char *name;\n\n    if (Cmd_Argc() != 2)\n    {\n        Com_Printf(\"usage: /describe <command|variable>\\ne.g. /describe ignore\\n\");\n        return;\n    }\n\n    name = Cmd_Argv(1);\n    \n    // check for command\n\t{\n\t\tconst json_command_t* cmd = JSON_Command_Load(name);\n\t\tif (cmd) {\n\t\t\tfound = true;\n\n\t\t\t// name\n\t\t\tcon_ormask = 128;\n\t\t\tCom_Printf(\"%s is a command\\n\", cmd->name);\n\t\t\tcon_ormask = 0;\n\n\t\t\tHelp_DescribeCmd(cmd);\n\t\t}\n\t}\n\n    // check for variable\n\t{\n\t\tconst json_variable_t* var = JSON_Variable_Load(name);\n\t\tif (var) {\n\t\t\tif (found) {\n\t\t\t\tCom_Printf(\"\\n\\n\\n\");\n\t\t\t}\n\t\t\tfound = true;\n\n\t\t\t// name\n\t\t\tcon_ormask = 128;\n\t\t\tCom_Printf(\"%s is a variable\\n\", var->name);\n\t\t\tcon_ormask = 0;\n\n\t\t\tHelp_DescribeVar(var);\n\t\t}\n\t}\n\n\t// check for macro\n\t{\n\t\tconst json_macro_t* macro = JSON_Macro_Load(name);\n\t\tif (macro) {\n\t\t\tif (found) {\n\t\t\t\tCom_Printf(\"\\n\\n\\n\");\n\t\t\t}\n\t\t\tfound = true;\n\n\t\t\t// name\n\t\t\tcon_ormask = 128;\n\t\t\tCom_Printf(\"%s is a macro\\n\", macro->name);\n\t\t\tcon_ormask = 0;\n\n\t\t\tHelp_DescribeMacro(macro);\n\t\t}\n\t}\n\n\t// check for commandline arg\n\t{\n\t\tconst json_cmdlineparam_t* param = JSON_CommandLineParam_Load(name);\n\t\tif (param) {\n\t\t\tif (found) {\n\t\t\t\tCom_Printf(\"\\n\\n\\n\");\n\t\t\t}\n\t\t\tfound = true;\n\n\t\t\t// name\n\t\t\tcon_ormask = 128;\n\t\t\tCom_Printf(\"%s is a command-line parameter\\n\", param->name);\n\t\t\tcon_ormask = 0;\n\n\t\t\tHelp_DescribeCommandLineParam(param);\n\t\t}\n\t}\n\n    // if not found\n\tif (!found) {\n\t\tCom_Printf(\"Nothing found.\\n\");\n\t}\n}\n\nstatic qbool Help_VariableIgnoreByCvar(cvar_t* cvar)\n{\n\tif (Cvar_GetFlags(cvar) & CVAR_USER_CREATED) {\n\t\treturn true;\n\t}\n\n\t// don't expect complete documentation right now (tighten this in future)\n\treturn (!strncmp(cvar->name, \"hud_\", 4) || !strncmp(cvar->name, \"sv_\", 3));\n}\n\nstatic qbool Help_VariableIgnoreByGroup(const json_variable_t* var, const json_t* groupsObj)\n{\n\t// ignore documentation for server variables (tighten this in future)\n\t{\n\t\tconst char* group_id = var->group_id;\n\t\tint i;\n\n\t\tfor (i = 0; i < json_array_size(groupsObj); ++i) {\n\t\t\tconst char* id = json_string_value(json_object_get(json_array_get(groupsObj, i), \"id\"));\n\t\t\tconst char* major_group = json_string_value(json_object_get(json_array_get(groupsObj, i), \"major-group\"));\n\n\t\t\tif (id && group_id && !strcmp(id, group_id) && major_group) {\n\t\t\t\tif (!strcmp(major_group, \"Server\") || !strcmp(major_group, \"Obselete\")) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n\n// Check all variable values against help files\nstatic void Help_VerifyConfig_f(void)\n{\n\tjson_t* varsObj = NULL;\n\tint num_errors = 0;\n\tconst char* name = NULL;\n\tjson_t* variable = NULL;\n\n\tif (!document_root[helpdoc_variables]) {\n\t\treturn;\n\t}\n\n\tvarsObj = json_object_get(document_root[helpdoc_variables], \"vars\");\n\tif (!varsObj) {\n\t\treturn;\n\t}\n\n\tjson_object_foreach(varsObj, name, variable)\n\t{\n\t\tcvar_t* cvar        = Cvar_Find(name);\n\t\tconst char* type    = json_string_value(json_object_get(variable, \"type\"));\n\t\tjson_t* examples    = json_object_get(variable, \"values\");\n\t\tsize_t num_examples = json_is_array(examples) ? json_array_size(examples) : 0;\n\n\t\t// obsolete cvars might be in documentation so people can see notes using /describe\n\t\tif (cvar == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!strcmp(\"boolean\", type) && cvar->value != 0 && cvar->value != 1) {\n\t\t\tcon_ormask = 128;\n\t\t\tCom_Printf (\"%s\", name);\n\t\t\tcon_ormask = 0;\n\t\t\tCom_Printf(\": boolean, value %f\\n\", cvar->value);\n\t\t\t++num_errors;\n\t\t}\n\t\telse if (!strcmp(\"enum\", type) && num_examples > 0) {\n\t\t\tqbool found = false;\n\t\t\tint i;\n\n\t\t\tfor (i = 0; i < num_examples; ++i) {\n\t\t\t\tconst json_t* value = json_array_get(examples, i);\n\t\t\t\tconst char*   name = json_string_value(json_object_get(value, \"name\"));\n\n\t\t\t\tfound |= !strcmp(name, cvar->string);\n\t\t\t}\n\n\t\t\tif (!found) {\n\t\t\t\tcon_ormask = 128;\n\t\t\t\tCon_Printf(\"%s\", name);\n\t\t\t\tcon_ormask = 0;\n\t\t\t\tCon_Printf(\": enum, value %s\\n\", cvar->string);\n\t\t\t}\n\t\t}\n\t}\n\n\tCon_Printf(\"%d variables with values that don't match documentation.\\n\", num_errors);\n}\n\nstatic void Help_FindConsoleVariableIssues(qbool generate)\n{\n\textern cvar_t* cvar_vars;\n\tcvar_t* cvar;\n\n\tint num_errors = 0;\n\tconst json_variable_t* jsonVar;\n\tconst json_t* groupsJsonObj = json_object_get(document_root[helpdoc_variables], \"groups\");\n\tjson_t* variableJsonObj = json_object_get(document_root[helpdoc_variables], \"vars\");\n\n\tif (!variableJsonObj) {\n\t\tCon_Printf(\"'vars' missing from help_variables.json\\n\");\n\t\treturn;\n\t}\n\n\tif (!groupsJsonObj) {\n\t\tCon_Printf(\"'Groups' missing from help_variables.json\\n\");\n\t\treturn;\n\t}\n\n\tif (!generate) {\n\t\tCon_Printf(\"Variables with issues:\\n\");\n\t\tcon_margin = CONSOLE_HELP_MARGIN;\n\t}\n\n\tfor (cvar = cvar_vars; cvar; cvar = cvar->next) {\n\t\tif (Help_VariableIgnoreByCvar(cvar)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tjsonVar = JSON_Variable_Load(cvar->name);\n\t\tif (!jsonVar) {\n\t\t\tif (generate) {\n\t\t\t\tjson_t* obj = json_object();\n\t\t\t\tjson_object_set(obj, \"group-id\", json_string(\"0\"));\n\t\t\t\tjson_object_set(obj, \"system-generated\", json_boolean(1));\n\t\t\t\tjson_object_set_new(obj, \"default\", json_string(cvar->defaultvalue));\n\t\t\t\tjson_object_set_new(variableJsonObj, cvar->name, obj);\n\t\t\t\t++num_errors;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcon_ormask = 128;\n\t\t\t\tCon_Printf(\"%s\", cvar->name);\n\t\t\t\tcon_ormask = 0;\n\t\t\t\tCon_Printf(\": missing from help\\n\", cvar->name);\n\t\t\t\t++num_errors;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (generate) {\n\t\t\tjson_object_set_new(jsonVar->json, \"default\", json_string(cvar->defaultvalue));\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (Help_VariableIgnoreByGroup(jsonVar, groupsJsonObj)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t{\n\t\t\tconst char* desc = jsonVar->description;\n\t\t\tqbool valid_desc = (desc && desc[0]);\n\t\t\tqbool valid_type, enum_without_examples;\n\t\t\tsize_t num_examples;\n\n\t\t\tvalid_type = (jsonVar->value_type != t_unknown);\n\t\t\tnum_examples = jsonVar->values ? json_array_size(jsonVar->values) : 0;\n\t\t\tenum_without_examples = (jsonVar->value_type == t_enum && num_examples == 0);\n\t\t\tif (!valid_type || !(valid_desc || num_examples) || enum_without_examples) {\n\t\t\t\tcon_ormask = 128;\n\t\t\t\tCon_Printf(\"%s\", cvar->name);\n\t\t\t\tcon_ormask = 0;\n\t\t\t\tCon_Printf(\":\");\n\t\t\t\tif (!valid_type) {\n\t\t\t\t\tCon_Printf(\"invalid type\");\n\t\t\t\t}\n\t\t\t\telse if (enum_without_examples) {\n\t\t\t\t\tCon_Printf(\"invalid examples for enum\");\n\t\t\t\t}\n\t\t\t\telse if (!valid_desc && num_examples == 0) {\n\t\t\t\t\tCon_Printf(\"invalid description/examples\");\n\t\t\t\t}\n\t\t\t\telse if (!valid_desc) {\n\t\t\t\t\tCon_Printf(\"invalid description\");\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tCon_Printf(\"invalid examples\");\n\t\t\t\t}\n\t\t\t\tCon_Printf(\"\\n\");\n\t\t\t\t++num_errors;\n\t\t\t}\n\t\t}\n\t}\n\tcon_margin = 0;\n\tCon_Printf(\"Number of variables with issues: %d\\n\", num_errors);\n}\n\nstatic void Help_FindCommandIssues(qbool generate)\n{\n\textern cmd_function_t *cmd_functions;\n\tcmd_function_t *cmd_func = 0;\n\tint num_errors = 0;\n\n\tfor (cmd_func = cmd_functions; cmd_func; (cmd_func = cmd_func->next)) {\n\t\tconst json_command_t* help_cmd = JSON_Command_Load(cmd_func->name);\n\t\textern void HUD_Plus_f(void);\n\t\textern void HUD_Minus_f(void);\n\t\textern void HUD_Func_f(void);\n\n\t\tif (cmd_func->function == HUD_Plus_f || cmd_func->function == HUD_Minus_f || cmd_func->function == HUD_Func_f) {\n\t\t\t// not interested in hud's system-generated commands for the moment\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!help_cmd) {\n\t\t\t// missing\n\t\t\tif (generate) {\n\t\t\t\tjson_t* obj = json_object();\n\t\t\t\tjson_object_set(obj, \"system-generated\", json_boolean(1));\n\t\t\t\tjson_object_set_new(document_root[helpdoc_commands], cmd_func->name, obj);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (num_errors == 0) {\n\t\t\t\t\tCon_Printf(\"Commands with issues:\\n\");\n\t\t\t\t\tcon_margin = CONSOLE_HELP_MARGIN;\n\t\t\t\t}\n\n\t\t\t\tcon_ormask = 128;\n\t\t\t\tCon_Printf(\"%s\", cmd_func->name);\n\t\t\t\tcon_ormask = 0;\n\t\t\t\tCon_Printf(\": missing from documentation\\n\");\n\t\t\t}\n\t\t\t++num_errors;\n\t\t}\n\t}\n\n\tcon_margin = 0;\n\tCon_Printf(\"Number of commands with issues: %d\\n\", num_errors);\n}\n\nstatic void Help_FindMacroIssues(qbool generate)\n{\n\tint i;\n\tint issues = 0;\n\n\tfor (i = 0; i < num_macros; i++) {\n\t\tconst char* name = Cmd_MacroName(i);\n\t\tconst json_macro_t* help_macro = JSON_Macro_Load(name);\n\t\tqbool teamplay = Cmd_MacroTeamplayRestricted(i);\n\n\t\tif (help_macro && generate) {\n\t\t\tjson_object_set_new(help_macro->json, \"teamplay-restricted\", json_boolean(teamplay ? 1 : 0));\n\t\t}\n\t\telse if (generate) {\n\t\t\tjson_t* obj = json_object();\n\t\t\tjson_object_set(obj, \"system-generated\", json_boolean(1));\n\t\t\tjson_object_set(obj, \"teamplay-restricted\", json_boolean(teamplay ? 1 : 0));\n\t\t\tjson_object_set_new(document_root[helpdoc_macros], name, obj);\n\t\t\t++issues;\n\t\t}\n\t\telse if (!help_macro) {\n\t\t\tif (issues == 0) {\n\t\t\t\tCon_Printf(\"Macros with issues:\\n\");\n\t\t\t\tcon_margin = CONSOLE_HELP_MARGIN;\n\t\t\t}\n\n\t\t\tcon_ormask = 128;\n\t\t\tCon_Printf(\"%s\", name);\n\t\t\tcon_ormask = 0;\n\t\t\tCon_Printf(\": missing from documentation\\n\");\n\t\t\t++issues;\n\t\t}\n\t}\n\n\tcon_margin = 0;\n\tCon_Printf(\"Number of macros with issues: %d\\n\", issues);\n}\n\nstatic void Help_FindCommandLineParamIssues(qbool generate)\n{\n\tint i;\n\tint issues = 0;\n\n\tfor (i = 0; i < num_cmdline_params; i++) {\n\t\tconst char* name = Cmd_CommandLineParamName(i);\n\t\tconst json_cmdlineparam_t* help_param = JSON_CommandLineParam_Load(name);\n\n\t\tif (help_param) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (generate) {\n\t\t\tjson_t* obj = json_object();\n\t\t\tjson_object_set(obj, \"system-generated\", json_boolean(1));\n\t\t\tjson_object_set_new(document_root[helpdoc_cmdlineparams], name, obj);\n\t\t\t++issues;\n\t\t}\n\t\telse {\n\t\t\tif (issues == 0) {\n\t\t\t\tCon_Printf(\"Command-line parameters with issues:\\n\");\n\t\t\t\tcon_margin = CONSOLE_HELP_MARGIN;\n\t\t\t}\n\t\t\tcon_ormask = 128;\n\t\t\tCon_Printf(\"%s\", name);\n\t\t\tcon_ormask = 0;\n\t\t\tCon_Printf(\": missing from documentation\\n\");\n\t\t\t++issues;\n\t\t}\n\t}\n\n\tcon_margin = 0;\n\tCon_Printf(\"Number of command-line params with issues: %d\\n\", issues);\n}\n\nstatic void Help_Issues_f(void)\n{\n\tqbool generate = Cmd_Argc() >= 1 && !strcmp(Cmd_Argv(1), \"generate\");\n\n\tHelp_FindConsoleVariableIssues(generate);\n\tHelp_FindCommandIssues(generate);\n\tHelp_FindMacroIssues(generate);\n\tHelp_FindCommandLineParamIssues(generate);\n\n\tif (generate) {\n\t\tjson_dump_file(document_root[helpdoc_variables], \"qw/help_variables.json\", JSON_ENSURE_ASCII | JSON_SORT_KEYS | JSON_INDENT(2));\n\t\tjson_dump_file(document_root[helpdoc_commands], \"qw/help_commands.json\", JSON_ENSURE_ASCII | JSON_SORT_KEYS | JSON_INDENT(2));\n\t\tjson_dump_file(document_root[helpdoc_macros], \"qw/help_macros.json\", JSON_ENSURE_ASCII | JSON_SORT_KEYS | JSON_INDENT(2));\n\t\tjson_dump_file(document_root[helpdoc_cmdlineparams], \"qw/help_cmdline_params.json\", JSON_ENSURE_ASCII | JSON_SORT_KEYS | JSON_INDENT(2));\n\t}\n}\n\nvoid Help_Init(void)\n{\n\tCmd_AddCommand(\"describe\", Help_Describe_f);\n\tif (IsDeveloperMode()) {\n\t\tCmd_AddCommand(\"dev_help_verify_config\", Help_VerifyConfig_f);\n\t\tCmd_AddCommand(\"dev_help_issues\", Help_Issues_f);\n\t}\n\n\tHelp_LoadDocuments();\n}\n\nvoid Help_Shutdown(void)\n{\n\tHelp_UnloadDocuments();\n}\n"
  },
  {
    "path": "src/help.h",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: help.h,v 1.6 2007-09-30 22:59:23 disconn3ct Exp $\n*/\n\n#ifndef __HELP_H__\n#define __HELP_H__\n\n// variable description\nvoid Help_VarDescription (const char *varname, char* buf, size_t bufsize);\n\n// initialize help system\nvoid Help_Init (void);\nvoid Help_Shutdown (void);\n\n// help menu\nvoid Menu_Help_Init (void);\nvoid Menu_Help_Draw (int x, int y, int w, int h);\nvoid Menu_Help_Key (int key, wchar unichar);\nqbool Menu_Help_Mouse_Event (const mouse_state_t *ms);\n\nvoid Menu_Help_Shutdown(void);\nvoid Menu_Demo_Shutdown(void);\nvoid Menu_Options_Shutdown(void);\nvoid Menu_Ingame_Shutdown(void);\nvoid Menu_MultiPlayer_Shutdown(void);\n\n#endif // __HELP_H__\n"
  },
  {
    "path": "src/help_files.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/*\n * Help menu\n *  - browser tab\n *  - index tab\n *  - tutorials tab\n */\n\n// maybe rename this file to menu_help.c\n\n#include \"quakedef.h\"\n#include \"menu.h\"\n#include \"EX_FileList.h\"\n#include \"expat.h\"\n#include \"xsd.h\"\n#include \"document_rendering.h\"\n#include \"Ctrl_PageViewer.h\"\n#include \"Ctrl_Tab.h\"\n\nenum {\n    HELPM_BROWSER,\n\tHELPM_INDEX,\n    HELPM_TUTORIALS\n};\n\nCTab_t help_tab;\n\nCPageViewer_t help_viewer;\n\nvoid Help_Browser_Init(void)\n{\n    CPageViewer_Init(&help_viewer);\n    CPageViewer_GoUrl(&help_viewer, \"help/index.xml\");\n}\n\nvoid Help_Browser_Shutdown(void)\n{\n\tCPageViewer_Free(&help_viewer);\n}\n\nint Help_Browser_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n    return CPageViewer_Key(&help_viewer, key, unichar);\n}\n\nvoid Help_Browser_Draw(int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n    CPageViewer_Draw(&help_viewer, x, y, w, h);\n}\n\nvoid Help_Browser_Focus(void)\n{\n    CTab_SetCurrentId(&help_tab, HELPM_BROWSER);\n}\n\nstatic qbool Help_Browser_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn true;\n}\n\nfilelist_t help_index_fl;\nfilelist_t help_tutorials_fl;\n\nstatic void Help_Index_Init(void)\n{\n    FL_Init(&help_index_fl,\t\"./ezquake/help/manual\");\n    FL_AddFileType(&help_index_fl, 0, \".xml\");\n\tFL_SetDirUpOption(&help_index_fl, false);\n\tFL_SetDirsOption(&help_index_fl, false);\n}\n\nstatic void Help_Tutorials_Init(void)\n{\n    FL_Init(&help_tutorials_fl,\t\"./ezquake/help/manual/demos\");\n    FL_AddFileType(&help_tutorials_fl, 0, \".mvd\");\n\tFL_SetDirUpOption(&help_tutorials_fl, false);\n\tFL_SetDirsOption(&help_tutorials_fl, false);\n}\n\nstatic int Help_Index_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n    qbool processed;\n\n    processed = FL_Key(&help_index_fl, key);\n\n    if (!processed)\n    {\n        if (key == K_ENTER || key == K_MOUSE1)\n        {\n            CPageViewer_Go(&help_viewer, FL_GetCurrentPath(&help_index_fl), NULL);\n            Help_Browser_Focus();\n\n            processed = true;\n        }\n    }\n\n    return processed;\n}\n\nstatic void Help_Index_Draw(int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n    FL_Draw(&help_index_fl, x, y, w, h);\n}\n\nstatic qbool Help_Index_Mouse_Event(const mouse_state_t *ms)\n{\n    if (FL_Mouse_Event(&help_index_fl, ms)) {\n        return true;\n    } else if (ms->button_up >= 1 && ms->button_up <= 2) {\n        Help_Index_Key(K_MOUSE1 - 1 + ms->button_up, 0, &help_tab, help_tab.pages + HELPM_INDEX);\n        return true;\n    }\n\n    // this specially \"eats\" button_up event, there is no reason to process other events anyway\n\treturn true;\n}\n\n\nstatic int Help_Tutorials_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n    qbool processed;\n\n    processed = FL_Key(&help_tutorials_fl, key);\n\n    if (!processed)\n    {\n        if (key == K_ENTER || key == K_MOUSE1)\n        {\n\t\t\tM_LeaveMenus();\n\t\t\tCbuf_AddText(va(\"playdemo \\\"%s\\\"\\n\", FL_GetCurrentPath(&help_tutorials_fl)));\n\n            processed = true;\n        }\n    }\n\n    return processed;\n}\n\nstatic void Help_Tutorials_Draw(int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n    FL_Draw(&help_tutorials_fl, x, y, w, h);\n}\n\nstatic qbool Help_Tutorials_Mouse_Event(const mouse_state_t *ms)\n{\n    if (FL_Mouse_Event(&help_tutorials_fl, ms)) {\n        return true;\n    } else if (ms->button_up >= 1 && ms->button_up <= 2) {\n        Help_Tutorials_Key(K_MOUSE1 - 1 + ms->button_up, 0, &help_tab, help_tab.pages + HELPM_TUTORIALS);\n        return true;\n    }\n\n    // this specially \"eats\" button_up event, there is no reason to process other events anyway\n\treturn true;\n}\n\n\nCTabPage_Handlers_t help_browser_handlers = {\n\tHelp_Browser_Draw,\n\tHelp_Browser_Key,\n\tNULL,\n\tHelp_Browser_Mouse_Event\n};\n\nCTabPage_Handlers_t help_index_handlers = {\n\tHelp_Index_Draw,\n\tHelp_Index_Key,\n\tNULL,\n\tHelp_Index_Mouse_Event\n};\n\nCTabPage_Handlers_t help_tutorials_handlers = {\n\tHelp_Tutorials_Draw,\n\tHelp_Tutorials_Key,\n\tNULL,\n\tHelp_Tutorials_Mouse_Event\n};\n\n\nvoid Menu_Help_Init(void)\n{\n    // initialize tab control\n    CTab_Init(&help_tab);\n\n    Help_Browser_Init();\n    Help_Index_Init();\n\tHelp_Tutorials_Init();\n\n    CTab_AddPage(&help_tab, \"Browser\", HELPM_BROWSER, &help_browser_handlers);\n\tCTab_AddPage(&help_tab, \"Index\", HELPM_INDEX, &help_index_handlers);\n\tCTab_AddPage(&help_tab, \"Tutorials\", HELPM_TUTORIALS, &help_tutorials_handlers);\n\n    CTab_SetCurrentId(&help_tab, HELPM_BROWSER);\n}\n\nvoid Menu_Help_Shutdown(void)\n{\n\tHelp_Browser_Shutdown();\n\tFL_Shutdown(&help_index_fl);\n\tFL_Shutdown(&help_tutorials_fl);\n}\n\nvoid Menu_Help_Key(int key, wchar unichar)\n{\n\textern void M_Menu_Main_f (void);\n\n    int handled = CTab_Key(&help_tab, key, unichar);\n\n    if (!handled)\n    {\n        if (key == K_ESCAPE || key == K_MOUSE2)\n        {\n            M_Menu_Main_f();\n        }\n    }\n}\n\nvoid Menu_Help_Draw(int x, int y, int w, int h)\n{\n    CTab_Draw(&help_tab, x, y, w, h);\n}\n\nqbool Menu_Help_Mouse_Event(const mouse_state_t *ms)\n{\n\tmouse_state_t nms = *ms;\n\n    if (ms->button_up == 2) {\n        Menu_Help_Key(K_MOUSE2, 0);\n        return true;\n    }\n\n\t// we are sending relative coordinates\n\tnms.x -= OPTPADDING;\n\tnms.y -= OPTPADDING;\n\tnms.x_old -= OPTPADDING;\n\tnms.y_old -= OPTPADDING;\n\n\tif (nms.x < 0 || nms.y < 0) return false;\n\n\treturn CTab_Mouse_Event(&help_tab, &nms);\n}\n"
  },
  {
    "path": "src/host.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n \nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n \nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n \nSee the GNU General Public License for more details.\n \nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n \n\t$Id: host.c,v 1.60 2007-10-17 17:07:08 dkure Exp $\n*/\n// this should be the only file that includes both server.h and client.h\n\n#include <setjmp.h>\n\n#ifdef __FreeBSD__\n#include <sys/types.h>\n#include <sys/sysctl.h>\n#include <osreldate.h>\n#include <sys/time.h>\n#include <machine/cpufunc.h>\n#endif\n#ifdef __APPLE__\n#include <sys/types.h>\n#include <sys/sysctl.h>\n#endif\n#include <SDL.h>\n#include \"quakedef.h\"\n#include \"EX_browser.h\"\n#include \"fs.h\"\n#include \"gl_model.h\"\n#include \"rulesets.h\"\n#include \"teamplay.h\"\n#include \"pmove.h\"\n#include \"version.h\"\n#include \"qsound.h\"\n#include \"keys.h\"\n#include \"config_manager.h\"\n#include \"EX_qtvlist.h\"\n#include \"r_renderer.h\"\n#include \"central.h\"\n#include <curl/curl.h>\n\ndouble\t\tcurtime;\n\nstatic int\thost_hunklevel;\nstatic void\t*host_membase;\n\nqbool\thost_initialized;\t// true if into command execution\nqbool\thost_everything_loaded;\t// true if OnChange() applied to every var, end of Host_Init()\nint\t\thost_memsize;\n\nstatic jmp_buf \thost_abort;\n\nextern void COM_StoreOriginalCmdline(int argc, char **argv);\n\nchar f_system_string[1024] = \"\";\n\nchar * SYSINFO_GetString(void)\n{\n\treturn f_system_string;\n}\n\nunsigned long long\tSYSINFO_memory = 0;\nint\t\t\t\t\tSYSINFO_MHz = 0;\nchar\t\t\t\t*SYSINFO_processor_description = NULL;\nchar\t\t\t\t*SYSINFO_3D_description        = NULL;\n\nstatic void SYSINFO_Shutdown(void)\n{\n\tQ_free(SYSINFO_processor_description);\n\tQ_free(SYSINFO_3D_description);\n}\n\n#ifdef _WIN32\ntypedef BOOL (WINAPI *PGMSE)(LPMEMORYSTATUSEX);\n\nvoid SYSINFO_Init(void)\n{\n\tLONG  ret;\n\tHKEY  hKey;\n\tPGMSE pGMSE;\n\n\tconst char *gl_renderer = renderer.DescriptiveString();\n\n\t// Get memory size.\n\tif ((pGMSE = (PGMSE)GetProcAddress(GetModuleHandle(TEXT(\"kernel32.dll\")), \"GlobalMemoryStatusEx\")) != NULL) {\n\t\tMEMORYSTATUSEX\tmemstat;\n\t\tmemstat.dwLength = sizeof(memstat);\n\t\tpGMSE(&memstat);\n\t\tSYSINFO_memory = memstat.ullTotalPhys;\n\t} else {\n\t\t// Win9x doesn't have GlobalMemoryStatusEx.\n\t\tMEMORYSTATUS memstat;\n\t\tGlobalMemoryStatus(&memstat);\n\t\tSYSINFO_memory = memstat.dwTotalPhys;\n\t}\n\n\t// Get processor info.\n\tret = RegOpenKey(\n\t          HKEY_LOCAL_MACHINE,\n\t          \"HARDWARE\\\\DESCRIPTION\\\\System\\\\CentralProcessor\\\\0\",\n\t          &hKey);\n\n\tif (ret == ERROR_SUCCESS) {\n\t\tDWORD type;\n\t\tbyte  data[1024];\n\t\tDWORD datasize;\n\n\t\tdatasize = 1024;\n\t\tret = RegQueryValueEx(\n\t\t          hKey,\n\t\t          \"~MHz\",\n\t\t          NULL,\n\t\t          &type,\n\t\t          data,\n\t\t          &datasize);\n\n\t\tif (ret == ERROR_SUCCESS  &&  datasize > 0  &&  type == REG_DWORD)\n\t\t\tSYSINFO_MHz = *((DWORD *)data);\n\n\t\tdatasize = 1024;\n\t\tret = RegQueryValueEx(\n\t\t          hKey,\n\t\t          \"ProcessorNameString\",\n\t\t          NULL,\n\t\t          &type,\n\t\t          data,\n\t\t          &datasize);\n\n\t\tif (ret == ERROR_SUCCESS  &&  datasize > 0  &&  type == REG_SZ)\n\t\t\tSYSINFO_processor_description = Q_strdup((char *) data);\n\n\t\tRegCloseKey(hKey);\n\t}\n\n\tif (gl_renderer && gl_renderer[0]) {\n\t\tif (SYSINFO_3D_description != NULL) {\n\t\t\tQ_free(SYSINFO_3D_description);\n\t\t}\n\t\tSYSINFO_3D_description = Q_strdup(gl_renderer);\n\t}\n\n\t//\n\t// Create the f_system string.\n\t//\n\t\n\tsnprintf(f_system_string, sizeof(f_system_string), \"%uMiB\", (unsigned)((SYSINFO_memory / (double) 1048576u)+0.5));\n\n\tif (SYSINFO_processor_description) {\n\t\tstrlcat(f_system_string, \", \", sizeof(f_system_string));\n\t\tstrlcat(f_system_string, SYSINFO_processor_description, sizeof(f_system_string));\n\t}\n\n\tif (SYSINFO_MHz) {\n\t\tstrlcat(f_system_string, va(\" %dMHz\", SYSINFO_MHz), sizeof(f_system_string));\n\t}\n\n\tif (SYSINFO_3D_description) {\n\t\tstrlcat(f_system_string, \", \", sizeof(f_system_string));\n\t\tstrlcat(f_system_string, SYSINFO_3D_description, sizeof(f_system_string));\n\t}\n}\n#elif defined(__linux__)\nvoid SYSINFO_Init(void)\n{\n\t// disconnect: which way is best(MEM/CPU-MHZ/CPU-MODEL)?\n\tf_system_string[0] = 0;\n\tchar buffer[1024] = {0};\n\tchar cpu_model[255] = {0};\n\tchar *match;\n\tFILE *f;\n\n\tconst char *gl_renderer = renderer.DescriptiveString();\n\n\t// MEM\n\tf = fopen(\"/proc/meminfo\", \"r\");\n\tif (f) {\n\t\tif (fscanf (f, \"%*s %llu %*s\\n\", &SYSINFO_memory) != 1) {\n\t\t\tCom_Printf (\"could not read /proc/meminfo!\\n\");\n\t\t} else {\n\t\t\tSYSINFO_memory /= 1024;\n\t\t}\n\t\tfclose (f);\n\t} else {\n\t\tCom_Printf (\"could not open /proc/meminfo!\\n\");\n\t\tSYSINFO_memory = 0;\n\t}\n\n\t//CPU-MHZ\n\tf = fopen(\"/proc/cpuinfo\", \"r\");\n\tif (f) {\n\t\tbuffer[fread (buffer, 1, sizeof(buffer) - 1, f)] = '\\0';\n\t\tfclose (f);\n\t\tmatch = strstr (buffer, \"cpu MHz\");\n\t\tif (match) {\n\t\t\tsscanf (match, \"cpu MHz : %i\", &SYSINFO_MHz);\n\t\t}\n\t} else {\n\t\tCom_Printf (\"could not open /proc/cpuinfo!\\n\");\n\t}\n\n\t//CPU-MODEL\n\tf = fopen(\"/proc/cpuinfo\", \"r\");\n\tif (f) {\n\t\twhile (!feof(f)) {\n\t\t\tif (fgets (buffer, sizeof(buffer), f) == NULL && !feof(f)) {\t// disconnect: sizeof(buffer) - 1 ?\n\t\t\t\tCom_Printf(\"Error reading /proc/cpuinfo\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!strncmp( buffer, \"model name\", 10)) {\n\t\t\t\tmatch = strchr( buffer, ':' );\n\t\t\t\tmatch++;\n\t\t\t\twhile (isspace(*match)) match++;\n\t\t\t\tmemcpy(cpu_model, match, sizeof(cpu_model));\n\t\t\t\tcpu_model[strlen(cpu_model) - 1] = '\\0';\n\t\t\t\tSYSINFO_processor_description= Q_strdup (cpu_model);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tfclose(f);\t\n\t} else {\n\t\tCom_Printf(\"could not open /proc/cpuinfo!\\n\");\n\t}\n\n\n\tif (gl_renderer && gl_renderer[0]) {\n\t\tif (SYSINFO_3D_description != NULL) {\n\t\t\tfree(SYSINFO_3D_description);\n\t\t}\n\t\tSYSINFO_3D_description = Q_strdup(gl_renderer);\n\t}\n\n\tsnprintf(f_system_string, sizeof(f_system_string), \"%dMB\", (int)(SYSINFO_memory));\n\n\tif (SYSINFO_processor_description) {\n\t\tstrlcat(f_system_string, \", \", sizeof(f_system_string));\n\t\tstrlcat(f_system_string, SYSINFO_processor_description, sizeof(f_system_string));\n\t}\n\tif (SYSINFO_MHz) {\n\t\tstrlcat(f_system_string, va(\" %dMHz\", SYSINFO_MHz), sizeof(f_system_string));\n\t}\n\tif (SYSINFO_3D_description) {\n\t\tstrlcat(f_system_string, \", \", sizeof(f_system_string));\n\t\tstrlcat(f_system_string, SYSINFO_3D_description, sizeof(f_system_string));\n\t}\n}\n#elif defined(__APPLE__)\nvoid SYSINFO_Init(void)\n{\n\tint mib[2];\n\tmib[0] = CTL_HW;\n\tmib[1] = HW_MEMSIZE;\n\tint64_t memsize_value;\n\tint cpu_frequency_value;\n\tsize_t length = sizeof(memsize_value);\n\tchar cpu_brand_string[100] = {0};\n\tsize_t cpu_brand_string_len = sizeof(cpu_brand_string) - 1; /* Don't trust Apple, make sure its NULL terminated */\n\n\tconst char *gl_renderer = renderer.DescriptiveString();\n\n\tif (sysctl(mib, 2, &memsize_value, &length, NULL, 0) != -1) {\n\t\tSYSINFO_memory = memsize_value;\n\t}\n\n\tif (sysctlbyname(\"machdep.cpu.brand_string\", &cpu_brand_string, &cpu_brand_string_len, NULL, 0) != -1) {\n\t\tSYSINFO_processor_description = Q_strdup(cpu_brand_string);\n\t}\n\n\tmib[0] = CTL_HW;\n\tmib[1] = HW_CPU_FREQ;\n\tlength = sizeof(cpu_frequency_value);\n\tif (sysctl(mib, 2, &cpu_frequency_value, &length, NULL, 0) != -1) {\n\t\tSYSINFO_MHz = cpu_frequency_value / 1000. / 1000. + .5;\n\t}\n\n\n\tif (gl_renderer && gl_renderer[0]) {\n\t\tif (SYSINFO_3D_description != NULL) {\n\t\t\tQ_free(SYSINFO_3D_description);\n\t\t}\n\t\tSYSINFO_3D_description = Q_strdup(gl_renderer);\n\t}\n\n\tsnprintf(f_system_string, sizeof(f_system_string), \"%dMB\", (int)(SYSINFO_memory / 1024. / 1024. + .5));\n\n\tif (SYSINFO_processor_description) {\n\t\tstrlcat(f_system_string, \", \", sizeof(f_system_string));\n\t\tstrlcat(f_system_string, SYSINFO_processor_description, sizeof(f_system_string));\n\t}\n\tif (SYSINFO_MHz) {\n\t\tstrlcat(f_system_string, va(\" %dMHz\", SYSINFO_MHz), sizeof(f_system_string));\n\t}\n\tif (SYSINFO_3D_description) {\n\t\tstrlcat(f_system_string, \", \", sizeof(f_system_string));\n\t\tstrlcat(f_system_string, SYSINFO_3D_description, sizeof(f_system_string));\n\t}\n}\n#elif defined(__FreeBSD__)\nvoid SYSINFO_Init(void)\n{\n\tchar cpu_model[256];\n\n\tint mib[2], val;\n\tunsigned long val_ul;\n\tsize_t len;\n\tunsigned long long old_tsc, tsc_freq;\n\tstruct timeval tp, old_tp;\n\tconst char *gl_renderer = renderer.DescriptiveString();\n\n\tmib[0] = CTL_HW;\n\tmib[1] =\n#if __FreeBSD_version >= 500000\n\t\tHW_REALMEM;\n#else\n\t\tHW_PHYSMEM;\n#endif\n// VVD: We can use HW_REALMEM (hw.realmem) for RELENG_5/6/7 for getting exact result,\n// but RELENG_4 have only HW_PHYSMEM (hw.physmem).\n\tlen = sizeof(val);\n\tsysctl(mib, sizeof(mib) / sizeof(mib[0]), &val_ul, &len, NULL, 0);\n\n\tSYSINFO_memory = val_ul;\n\n\tmib[0] = CTL_HW;\n\tmib[1] = HW_MODEL;\n\tlen = sizeof(cpu_model);\n\tsysctl(mib, sizeof(mib) / sizeof(mib[0]), cpu_model, &len, NULL, 0);\n\tcpu_model[sizeof(cpu_model) - 1] = '\\0';\n\n\tSYSINFO_processor_description = cpu_model;\n\n\tgettimeofday(&old_tp, NULL);\n#ifdef __powerpc__\n\t__asm__ __volatile__(\"mfspr %%r3, 268\": \"=r\" (old_tsc));\n#else\n\told_tsc = rdtsc();\n#endif\n\tdo {\n\t\tgettimeofday(&tp, NULL);\n\t} while ((tp.tv_sec - old_tp.tv_sec) * 1000000. + tp.tv_usec - old_tp.tv_usec < 1000000.);\n#ifdef __powerpc__\n\t__asm__ __volatile__(\"mfspr %%r3, 268\": \"=r\" (tsc_freq));\n#else\n\ttsc_freq = rdtsc();\n#endif\n\tSYSINFO_MHz = (int)((tsc_freq - old_tsc) /\n\t\t\t\t\t\t(tp.tv_sec - old_tp.tv_sec + (tp.tv_usec - old_tp.tv_usec) / 1000000.) /\n\t\t\t\t\t\t1000000. + .5);\n// VVD: We can use sysctl hw.clockrate, but it don't work on i486 - always 0.\n// Must work on Pentium 1/2/3; tested on Pentium 4. And RELENG_4 have no this sysctl.\n\n\tif (gl_renderer  &&  gl_renderer[0]) {\n\t\tif (SYSINFO_3D_description != NULL) {\n\t\t\tQ_free(SYSINFO_3D_description);\n\t\t}\n\t\tSYSINFO_3D_description = Q_strdup(gl_renderer);\n\t}\n\n\tsnprintf(f_system_string, sizeof(f_system_string), \"%dMB\", (int)(SYSINFO_memory / 1024. / 1024. + .5));\n\n\tif (SYSINFO_processor_description) {\n\t\tstrlcat(f_system_string, \", \", sizeof(f_system_string));\n\t\tstrlcat(f_system_string, SYSINFO_processor_description, sizeof(f_system_string));\n\t}\n\tif (SYSINFO_MHz) {\n\t\tstrlcat(f_system_string, va(\" (%dMHz)\", SYSINFO_MHz), sizeof(f_system_string));\n\t}\n\tif (SYSINFO_3D_description) {\n\t\tstrlcat(f_system_string, \", \", sizeof(f_system_string));\n\t\tstrlcat(f_system_string, SYSINFO_3D_description, sizeof(f_system_string));\n\t}\n}\n#else\nvoid SYSINFO_Init(void)\n{\n\tstrlcpy(f_system_string, \"Unknown system.\", sizeof(f_system_string));\n}\n#endif\n\nvoid Host_Abort (void)\n{\n\tlongjmp (host_abort, 1);\n}\n\nvoid Host_EndGame (void)\n{\n#ifndef CLIENTONLY\n\tSV_Shutdown (\"Server was killed\");\n#endif\n\tCL_Disconnect();\n\t// clear disconnect messages from loopback\n\tCL_ClearQueuedPackets();\n\tNET_ClearLoopback();\n}\n\n//This shuts down both the client and server\nvoid Host_Error (char *error, ...)\n{\n\tva_list argptr;\n\tchar string[1024];\n\tstatic qbool inerror = false;\n\n\tif (inerror)\n\t\tSys_Error (\"Host_Error: recursively entered\");\n\tinerror = true;\n\n\tva_start (argptr,error);\n\tvsnprintf (string, sizeof(string), error, argptr);\n\tva_end (argptr);\n\n\tCom_Printf (\"\\n===========================\\n\");\n\tCom_Printf (\"Host_Error: %s\\n\",string);\n\tCom_Printf (\"===========================\\n\\n\");\n\n#ifndef CLIENTONLY\n\tSV_Shutdown (va(\"server crashed: %s\\n\", string));\n#endif\n\tCL_Disconnect ();\n\n\tif (!host_initialized)\n\t\tSys_Error (\"Host_Error: %s\", string);\n\n\tinerror = false;\n\n\tHost_Abort ();\n}\n\n//memsize is the recommended amount of memory to use for hunk\nvoid Host_InitMemory (int memsize)\n{\n\tint t;\n\n\tif (COM_CheckParm (cmdline_param_host_memory_minimum))\n\t\tmemsize = MINIMUM_MEMORY;\n\n\tif ((t = COM_CheckParm (cmdline_param_host_memory_kb)) != 0 && t + 1 < COM_Argc())\n\t\tmemsize = Q_atoi (COM_Argv(t + 1)) * 1024;\n\n\tif ((t = COM_CheckParm (cmdline_param_host_memory_mb)) != 0 && t + 1 < COM_Argc())\n\t\tmemsize = Q_atoi (COM_Argv(t + 1)) * 1024 * 1024;\n\n\tif (memsize < MINIMUM_MEMORY)\n\t\tSys_Error (\"Only %4.1f megs of memory reported, can't execute game\", memsize / (float)0x100000);\n\n\thost_memsize = memsize;\n\thost_membase = Q_malloc (host_memsize);\n\tMemory_Init (host_membase, host_memsize);\n}\n\n//Free hunk memory up to host_hunklevel\n//Can only be called when changing levels!\nvoid Host_ClearMemory (void)\n{\n\t// FIXME, move to CL_ClearState\n\tMod_ClearAll ();\n\n\tCM_InvalidateMap ();\n\n\t// any data previously allocated on hunk is no longer valid\n\tHunk_FreeToLowMark (host_hunklevel);\n}\n\nvoid Host_Frame (double time)\n{\n\tif (setjmp (host_abort))\n\t\treturn;\t\t\t// something bad happened, or the server disconnected\n\n\tcurtime += time;\n\n\tCL_Frame (time);\t// will also call SV_Frame\n\n\tCentral_ProcessResponses();\n}\n\nchar *Host_PrintBars(char *s, int len)\n{\n\tstatic char temp[512];\n\tunsigned int i, count;\n\n\ttemp[0] = 0;\n\n\tcount = (len - 2 - 2 - strlen (s)) / 2;\n\tif (count > sizeof (temp) / 2 - 8)\n\t\treturn temp;\n\n\tstrlcat (temp, \"\\x1d\", sizeof (temp));\n\n\tfor (i = 0; i < count; i++)\n\t\tstrlcat(temp, \"\\x1e\", sizeof (temp));\n\n\tstrlcat (temp, \" \", sizeof (temp));\n\tstrlcat (temp, s, sizeof (temp));\n\tstrlcat (temp, \" \", sizeof (temp));\n\n\tfor (i = 0; i < count; i++)\n\t\tstrlcat(temp, \"\\x1e\", sizeof (temp));\n\n\tstrlcat(temp, \"\\x1f\", sizeof (temp));\n\tstrlcat(temp, \"\\n\\n\", sizeof (temp));\n\n\treturn temp;\n}\n\nstatic void Commands_For_Configs_Init (void)\n{\n\textern void SV_Floodprot_f (void);\n\textern void SV_Floodprotmsg_f (void);\n\textern void TP_MsgTrigger_f (void);\n\textern void TP_MsgFilter_f (void);\n\textern void TP_Took_f (void);\n\textern void TP_Pickup_f (void);\n\textern void TP_Point_f (void);\n\textern void MT_AddMapGroups (void);\n\textern void MT_MapGroup_f (void);\n\textern void MT_AddSkyGroups (void);\n\textern void MT_SkyGroup_f (void);\n\textern void SB_SourceUnmarkAll(void);\n\textern void SB_SourceMark(void);\n\textern void LoadConfig_f(void);\n\n\t//disconnect: fix it if i forgot something\n#ifndef CLIENTONLY\n\tCmd_AddCommand (\"floodprot\", SV_Floodprot_f);\n\tCmd_AddCommand (\"floodprotmsg\", SV_Floodprotmsg_f);\n#endif\n\tCmd_AddCommand (\"msg_trigger\", TP_MsgTrigger_f);\n\tCmd_AddCommand (\"filter\", TP_MsgFilter_f);\n\tCmd_AddCommand (\"tp_took\", TP_Took_f);\n\tCmd_AddCommand (\"tp_pickup\", TP_Pickup_f);\n\tCmd_AddCommand (\"tp_point\", TP_Point_f);\n\n\tMT_AddMapGroups ();\n\tCmd_AddCommand (\"mapgroup\", MT_MapGroup_f);\n\n\tMT_AddSkyGroups ();\n\tCmd_AddCommand (\"skygroup\", MT_SkyGroup_f);\n\tCmd_AddCommand (\"allskins\", Skin_AllSkins_f);\n\n\tCmd_AddCommand (\"sb_sourceunmarkall\", SB_SourceUnmarkAll);\n\tCmd_AddCommand (\"sb_sourcemark\", SB_SourceMark);\n}\n\n// meag: try to move all renamed cvars here so they exist prior to config load, and\n//       eventually old configs will set the new values\n// fixme: would be better to have these all in the format of v_gamma, or (dream) all built-in cvars as enumerated type\nstatic void Host_RegisterLegacyCvars(void)\n{\n\t// Sound\n\tCmd_AddLegacyCommand(\"nosound\", \"s_nosound\");\n\tCmd_AddLegacyCommand(\"precache\", \"s_precache\");\n\tCmd_AddLegacyCommand(\"loadas8bit\", \"s_loadas8bit\");\n\tCmd_AddLegacyCommand(\"ambient_level\", \"s_ambientlevel\");\n\tCmd_AddLegacyCommand(\"ambient_fade\", \"s_ambientfade\");\n\tCmd_AddLegacyCommand(\"snd_show\", \"s_show\");\n\tCmd_AddLegacyCommand(\"cl_chatsound\", \"s_chat_custom\");\n\n\t// Weaponstats\n\tCmd_AddLegacyCommand(\"scr_weaponstats_order\", \"hud_weaponstats_format\");\n\tCmd_AddLegacyCommand(\"scr_weaponstats_frame_color\", \"hud_weaponstats_frame_color\");\n\tCmd_AddLegacyCommand(\"scr_weaponstats_scale\", \"hud_weaponstats_scale\");\n\tCmd_AddLegacyCommand(\"scr_weaponstats_y\", \"hud_weaponstats_y\");\n\tCmd_AddLegacyCommand(\"scr_weaponstats_x\", \"hud_weaponstats_x\");\n\n\t// Drawing (now r_smoothtext/images/crosshair)\n\tCmd_AddLegacyCommand(\"gl_smoothfont\", \"r_smoothtext\");\n\n\t// Typo\n\tCmd_AddLegacyCommand(\"gl_lighting_colour\", \"gl_lighting_color\");\n\n\tCmd_AddLegacyCommand(\"cl_truelightning\", \"cl_fakeshaft\");\n\tCmd_AddLegacyCommand(\"demotimescale\", \"cl_demospeed\");\n\n\tCmd_AddLegacyCommand(\"gamma\", v_gamma.name);\n\tCmd_AddLegacyCommand(\"contrast\", v_contrast.name);\n\tCmd_AddLegacyCommand(\"gl_gammacorrection\", \"vid_gammacorrection\");\n\n\t// if you don't like renaming things in this way, let's have some talk with tea - johnnycz\n\tCmd_AddLegacyCommand(\"con_sound_mm1_file\", \"s_mm1_file\");\n\tCmd_AddLegacyCommand(\"con_sound_mm2_file\", \"s_mm2_file\");\n\tCmd_AddLegacyCommand(\"con_sound_spec_file\", \"s_spec_file\");\n\tCmd_AddLegacyCommand(\"con_sound_other_file\", \"s_otherchat_file\");\n\tCmd_AddLegacyCommand(\"con_sound_mm1_volume\", \"s_mm1_volume\");\n\tCmd_AddLegacyCommand(\"con_sound_mm2_volume\", \"s_mm2_volume\");\n\tCmd_AddLegacyCommand(\"con_sound_spec_volume\", \"s_spec_volume\");\n\tCmd_AddLegacyCommand(\"con_sound_other_volume\", \"s_otherchat_volume\");\n}\n\nvoid Startup_Place(void)\n{\n    extern cvar_t cl_onload;\n\textern cvar_t cl_startupdemo;\n\n\tif (cl_startupdemo.string[0]) {\n\t\tCbuf_AddText(va(\"playdemo %s\\n\", cl_startupdemo.string));\n\t\tif (!strcmp(cl_onload.string, \"menu\")) {\n\t\t\tkey_dest = key_startupdemo_menu;\n\t\t}\n\t\telse if (!strcmp(cl_onload.string, \"browser\")) {\n\t\t\tkey_dest = key_startupdemo_browser;\n\t\t}\n\t\telse if (!strcmp(cl_onload.string, \"console\")) {\n\t\t\tkey_dest = key_startupdemo_console;\n\t\t}\n\t\telse {\n\t\t\tkey_dest = key_startupdemo_game;\n\t\t}\n\t}\n\telse if (!strcmp (cl_onload.string, \"menu\")) {\n\t\tCbuf_AddText (\"togglemenu\\n\");\n\t}\n\telse if (!strcmp (cl_onload.string, \"browser\")) {\n\t\tCbuf_AddText (\"menu_slist\\n\");\n\t}\n\telse if (!strcmp (cl_onload.string, \"console\")) {\n\t\tkey_dest = key_console;\n\t}\n\telse {\n\t\tkey_dest = key_console;\n\t\tCbuf_AddText (va (\"%s\\n\", cl_onload.string));\n\t}\n}\n\nvoid Host_Init (int argc, char **argv, int default_memsize)\n{\n\tvfsfile_t *vf;\n\tcvar_t *v;\n\tchar cfg[MAX_PATH] = {0};\n\tint i;\n\tchar *cfg_name;\n\n\t// central, version check etc\n\tcurl_global_init(CURL_GLOBAL_DEFAULT);\n\n\tCOM_InitArgv (argc, argv);\n\tCOM_StoreOriginalCmdline(argc, argv);\n\n\tif (SDL_Init(0) != 0)\n\t{\n\t\tfprintf(stderr, \"Failed to initialize SDL: %s\\n\", SDL_GetError());\n\t\texit(EXIT_FAILURE);\n\t}\n\tatexit(SDL_Quit);\n\n\tHost_InitMemory (default_memsize);\n\n\tCbuf_Init ();\n\tCmd_Init ();\n\tCvar_Init ();\n\tCOM_Init ();\n\tKey_Init ();\n\n\tFS_InitFilesystem ();\n\tNET_Init ();\n\n#ifdef WITH_RENDERING_TRACE\n\t// Start immediately: (have to wait until filesystem is started up so we know where to save files)\n\tif (COM_CheckParm(cmdline_param_client_video_r_trace)) {\n\t\tDev_VidFrameStart();\n\t}\n#endif\n\n\tCommands_For_Configs_Init ();\n\tHost_RegisterLegacyCvars();\n\tBrowser_Init2();\n\tConfigManager_Init();\n\tResetBinds();\n\tCfg_ExecuteDefaultConfig();\n\tCbuf_Execute();\n\n\ti = COM_FindParm(\"+cfg_load\");\n\n\tif (i && (i + 1 < COM_Argc())) {\n\t\tcfg_name = COM_Argv(i + 1);\n\t}\n\telse {\n\t\tcfg_name = MAIN_CONFIG_FILENAME;\n\t}\n\tsnprintf(cfg, sizeof(cfg), \"%s\", cfg_name);\n\tCOM_ForceExtensionEx (cfg, \".cfg\", sizeof (cfg));\n\tCbuf_AddText(va(\"cfg_load %s\\n\", cfg));\n\tCbuf_Execute();\n\n\tCbuf_AddEarlyCommands ();\n\tCbuf_Execute ();\n\n\tCon_Init ();\n\tNET_InitClient ();\n\tNetchan_Init ();\n\n\tSys_Init ();\n\tSys_CvarInit();\n\tCM_Init ();\n\tMod_Init ();\n\tVersionCheck_Init();\n\n#ifndef CLIENTONLY\n\tSV_Init ();\n#endif\n\tCL_Init ();\n\tCentral_Init();\n\n\tCvar_CleanUpTempVars ();\n\n\tSYSINFO_Init();\n\n\tHunk_AllocName (0, \"-HOST_HUNKLEVEL-\");\n\thost_hunklevel = Hunk_LowMark ();\n\n\thost_initialized = true;\n\n\t// walk through all vars and force OnChange event if cvar was modified,\n\t// also apply that to variables which mirrored in userinfo because of cl_parsefunchars wasn't applied at this moment,\n\t// same for serverinfo and maybe this fix something also.\n\tfor ( v = NULL; (v = Cvar_Next ( v )); ) {\n\t\tchar val[2048];\n\n//\t\tif ( !v->modified )\n//\t\t\tcontinue; // not modified even that strange at this moment\n\n\t\tif ( Cvar_GetFlags( v ) & (CVAR_ROM | CVAR_INIT) )\n\t\t\tcontinue;\n\n\t\tsnprintf(val, sizeof(val), \"%s\", v->string);\n\t\tCvar_Set(v, val);\n\t}\n\t\n\tHud_262LoadOnFirstStart();\n\n\tCom_Printf_State (PRINT_INFO, \"Exe: \"__DATE__\" \"__TIME__\"\\n\");\n\tCom_Printf_State (PRINT_INFO, \"Hunk allocation: %4.1f MB\\n\", (float) host_memsize / (1024 * 1024));\n\tCom_Printf(\"\\n\");\n\tCom_Printf(EZ_VERSION_WEBSITE \"\\n\");\n\tCom_Printf(\"\\n\");\n//\tCom_Printf(Host_PrintBars(\"ezQuake\\x9c\" \"SourceForge\\x9c\" \"net\", 38));\n\tCom_Printf(\"ezQuake %s\\n\", VersionStringColour());\n\tCom_Printf(\"\\n\");\n\tCom_Printf(Host_PrintBars(\"&c1e1ezQuake Initialized&r\", 38));\n\tCom_Printf(\"\\n\");\n\tCom_Printf(\"Type /help to access the manual.\\nUse /describe for help on commands.\\n\\n\", VersionString());\n\n\tif ((vf = FS_OpenVFS(\"autoexec.cfg\", \"rb\", FS_ANY))) {\n\t\tCbuf_AddText (\"exec autoexec.cfg\\n\\n\");\n\t\tVFS_CLOSE(vf);\n\t}\n\n\tCmd_StuffCmds_f ();\t\t// process command line arguments\n\tCbuf_AddText(\"cl_warncmd 1\\n\");\n\n\tSys_CheckQWProtocolHandler();\n\n\t// Check if a qtv/demo file is specified as the first argument, in that case play that\n\t// otherwise, do some more checks of what to show at startup.\n\t{\n\t\tchar cmd[1024] = {0};\n\n\t\tif (COM_CheckArgsForPlayableFiles(cmd, sizeof(cmd))) {\n\t\t\tCbuf_AddText(cmd);\n\t\t}\n\t\telse {\n\t\t\tStartup_Place();\n\t\t}\n\t}\n\n\t// Trigger changes config has made to defaults\n\tCbuf_Flush(&cbuf_main);\n\tCvar_ExecuteQueuedChanges();\n\tCbuf_Execute();\n\n\thost_everything_loaded = true;\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tSys_Printf(\"\\nevent,init\\n\");\n#endif\n}\n\n//FIXME: this is a callback from Sys_Quit and Sys_Error.  It would be better\n//to run quit through here before the final handoff to the sys code.\nvoid Host_Shutdown (void)\n{\n\tstatic qbool isdown = false;\n\n\tif (isdown) {\n\t\tprintf (\"recursive shutdown\\n\");\n\t\treturn;\n\t}\n\tisdown = true;\n\n\t// on low-end systems quit process may last long time (was about 1 minute for me on old compo),\n\t// at the same time may repeats repeats repeats some sounds, trying preventing this\n\tS_StopAllSounds();\n\tS_Update (vec3_origin, vec3_origin, vec3_origin, vec3_origin);\n\n#ifndef CLIENTONLY\n\tSV_Shutdown (\"Server quit\\n\");\n#endif\n\n\tCentral_Shutdown();\n\tCL_Shutdown ();\n\tNET_Shutdown ();\n\tCon_Shutdown();\n\tqtvlist_deinit();\n\tCvar_Shutdown();\n\tFS_Shutdown();\n\tSYSINFO_Shutdown();\n\tQ_free(com_args_original);\n\n\tVersionCheck_Shutdown();\n\n\tcurl_global_cleanup();\n}\n\nvoid Host_Quit (void)\n{\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tSys_Printf(\"\\nevent,quit\\n\");\n#endif\n\n\t// execute user's trigger\n\tTP_ExecTrigger (\"f_exit\");\n\tCbuf_Execute();\n\t\n\t// save config (conditional)\n\tConfig_QuitSave();\n\n\t// turn off\n\tHost_Shutdown ();\n\n\tQ_free(host_membase);\n\n\tSys_Quit ();\n}\n"
  },
  {
    "path": "src/hud.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n//\n// HUD commands\n//\n\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"keys.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"hud_editor.h\"\n#include \"utils.h\"\n#include \"sbar.h\"\n#include \"rulesets.h\"\n\nstatic qbool HUD_ShouldShow(const hud_t* hud)\n{\n\tif (hud->show->integer == 2 || hud->show->integer == 3) {\n\t\treturn ((hud->show->integer == 2) == Ruleset_IsLocalSinglePlayerGame());\n\t}\n\treturn (hud->show->integer != 0);\n}\n\nstatic qbool HUD_ShouldDraw(const hud_t* hud)\n{\n\t// 0 = don't draw, 1 = always, 2 = single player only, 3 = multiplayer only\n\tif (hud->draw->integer == 2 || hud->draw->integer == 3) {\n\t\treturn ((hud->draw->integer == 2) == Ruleset_IsLocalSinglePlayerGame());\n\t}\n\treturn (hud->draw->integer != 0);\n}\n\n#define sbar_last_width 320  // yeah yeah I know, *garbage* -> leave it be :>\n\nchar *align_strings_x[] = {\n\t\"left\",\n\t\"center\",\n\t\"right\",\n\t\"before\",\n\t\"after\"\n};\n#define  num_align_strings_x  (sizeof(align_strings_x) / sizeof(align_strings_x[0]))\n\nchar *align_strings_y[] = {\n\t\"top\",\n\t\"center\",\n\t\"bottom\",\n\t\"before\",\n\t\"after\",\n\t\"console\"\n};\n#define  num_align_strings_y  (sizeof(align_strings_y) / sizeof(align_strings_y[0]))\n\nchar *snap_strings[] = {\n\t\"screen\",\n\t\"top\",\n\t\"view\",\n\t\"sbar\",\n\t\"ibar\",\n\t\"hbar\",\n\t\"sfree\",\n\t\"ifree\",\n\t\"hfree\",\n};\n#define  num_snap_strings  (sizeof(snap_strings) / sizeof(snap_strings[0]))\n\n// Hud elements list.\nhud_t *hud_huds = NULL;\n\n//\n// Hud plus func - show element.\n//\nvoid HUD_Plus_f(void)\n{\n\tchar *t;\n\thud_t *hud;\n\n\tif (Cmd_Argc() < 1)\n\t\treturn;\n\n\tt = Cmd_Argv(0);\n\tif (strncmp(t, \"+hud_\", 5))\n\t\treturn;\n\n\thud = HUD_Find(t + 5);\n\tif (!hud)\n\t{\n\t\t// This should never happen...\n\t\treturn;\n\t}\n\n\tif (!hud->show)\n\t{\n\t\t// This should never happen...\n\t\treturn;\n\t}\n\n\tCvar_Set(hud->show, \"1\");\n}\n\n//\n// Hud minus func - hide element.\n//\nvoid HUD_Minus_f(void)\n{\n\tchar *t;\n\thud_t *hud;\n\n\tif (Cmd_Argc() < 1)\n\t\treturn;\n\n\tt = Cmd_Argv(0);\n\tif (strncmp(t, \"-hud_\", 5))\n\t\treturn;\n\n\thud = HUD_Find(t + 5);\n\tif (!hud)\n\t{\n\t\t// this should never happen...\n\t\treturn;\n\t}\n\n\tif (!hud->show)\n\t{\n\t\t// this should never happen...\n\t\treturn;\n\t}\n\n\tCvar_Set(hud->show, \"0\");\n}\n\n//\n// Hud element func - describe it\n// this also solves the TAB completion problem\n//\nvoid HUD_Func_f(void)\n{\n\tint i;\n\thud_t *hud;\n\n\thud = HUD_Find(Cmd_Argv(0));\n\n\tif (!hud)\n\t{\n\t\t// This should never happen...\n\t\tCom_Printf(\"Hud element not found\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() > 1)\n\t{\n\t\tchar buf[512];\n\n\t\tsnprintf(buf, sizeof(buf), \"hud_%s_%s\", hud->name, Cmd_Argv(1));\n\t\tif (Cvar_Find(buf) != NULL)\n\t\t{\n\t\t\tCbuf_AddText(buf);\n\t\t\tif (Cmd_Argc() > 2)\n\t\t\t{\n\t\t\t\tCbuf_AddText(\" \");\n\t\t\t\tCbuf_AddText(Cmd_Argv(2));\n\t\t\t}\n\t\t\tCbuf_AddText(\"\\n\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCom_Printf(\"Trying \\\"%s\\\" - no such variable\\n\", buf);\n\t\t}\n\t\treturn;\n\t}\n\n\t// Description.\n\tCom_Printf(\"%s\\n\\n\", hud->description);\n\n\t// Show status.\n\tif (hud->show != NULL && hud->draw != NULL)\n\t{\n\t\tCom_Printf(\"Current status: %s, %s\\n\", HUD_ShouldShow(hud) ? \"shown\" : \"hidden\", HUD_ShouldDraw(hud) ? \"drawn\" : \"no content\");\n\t}\n\n\tif (hud->frame != NULL)\n\t{\n\t\tCom_Printf(\"Frame:          %s\\n\\n\", hud->frame->string);\n\t}\n\n\tif (hud->frame_color != NULL)\n\t{\n\t\tCom_Printf(\"Frame color:          %s\\n\\n\", hud->frame_color->string);\n\t}\n\n\t// Placement.\n\tCom_Printf(\"Placement:        %s\\n\", hud->place->string);\n\n\t// Alignment.\n\tCom_Printf(\"Alignment (x y):  %s %s\\n\", hud->align_x->string, hud->align_y->string);\n\n\t// Position.\n\tCom_Printf(\"Offset (x y):     %d %d\\n\", (int)(hud->pos_x->value), (int)(hud->pos_y->value));\n\n\t// Ordering.\n\tCom_Printf(\"Draw Order (z):   %d\\n\", (int)hud->order->value);\n\n\t// Additional parameters.\n\tif (hud->num_params > 0)\n\t{\n\t\tint prefix_l = strlen(va(\"hud_%s_\", hud->name));\n\t\tCom_Printf(\"\\nParameters:\\n\");\n\t\tfor (i=0; i < hud->num_params; i++)\n\t\t{\n\t\t\tif (strlen(hud->params[i]->name) > prefix_l)\n\t\t\t\tCom_Printf(\"  %-15s %s\\n\", hud->params[i]->name + prefix_l,\n\t\t\t\t\thud->params[i]->string);\n\t\t}\n\t}\n}\n\n//\n// Find the elements with the max and min z-order.\n//\nvoid HUD_FindMaxMinOrder(int *max, int *min)\n{\n\thud_t *hud = hud_huds;\n\n\twhile(hud)\n\t{\n\t\t(*min) = ((int)hud->order->value < (*min)) ? (int)hud->order->value : (*min);\n\t\t(*max) = ((int)hud->order->value > (*max)) ? (int)hud->order->value : (*max);\n\n\t\thud = hud->next;\n\t}\n}\n\n//\n// Find hud placement by string\n// return 0 if error\n//\nint HUD_FindPlace(hud_t *hud)\n{\n\tint i;\n\thud_t *par;\n\tqbool out;\n\tchar *t;\n\n\t// First try standard strings.\n\tfor (i=0; i < num_snap_strings; i++)\n\t{\n\t\tif (!strcasecmp(hud->place->string, snap_strings[i]))\n\t\t{\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (i < num_snap_strings)\n\t{\n\t\t// Found.\n\t\thud->place_num = i+1;\n\t\thud->place_hud = NULL;\n\t\treturn 1;\n\t}\n\n\t// then try another HUD element\n\tout = true;\n\tt = hud->place->string;\n\tif (hud->place->string[0] == '@')\n\t{\n\t\t// place inside\n\t\tout = false;\n\t\tt++;\n\t}\n\n\tpar = hud_huds;\n\twhile (par)\n\t{\n\t\tif (par != hud && !strcmp(t, par->name))\n\t\t{\n\t\t\thud->place_outside = out;\n\t\t\thud->place_hud = par;\n\t\t\thud->place_num = HUD_PLACE_SCREEN;\n\t\t\treturn 1;\n\t\t}\n\t\tpar = par->next;\n\t}\n\n\t// No way.\n\thud->place_num = HUD_PLACE_SCREEN;\n\thud->place_hud = NULL;\n\treturn 0;\n}\n\n//\n// Find hud alignment by strings\n// return 0 if error\n//\nint HUD_FindAlignX(hud_t *hud)\n{\n\tint i;\n\n\t// First try standard strings.\n\tfor (i=0; i < num_align_strings_x; i++)\n\t{\n\t\tif (!strcasecmp(hud->align_x->string, align_strings_x[i]))\n\t\t{\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (i < num_align_strings_x)\n\t{\n\t\t// Found.\n\t\thud->align_x_num = i+1;\n\t\treturn 1;\n\t}\n\telse\n\t{\n\t\t// Error.\n\t\thud->align_x_num = HUD_ALIGN_LEFT; // left\n\t\treturn 0;\n\t}\n}\n\n//\n// Find the alignment for a hud element.\n//\nint HUD_FindAlignY(hud_t *hud)\n{\n\tint i;\n\n\t// First try standard strings.\n\tfor (i=0; i < num_align_strings_y; i++)\n\t{\n\t\tif (!strcasecmp(hud->align_y->string, align_strings_y[i]))\n\t\t{\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (i < num_align_strings_y)\n\t{\n\t\t// Found.\n\t\thud->align_y_num = i + 1;\n\t\treturn 1;\n\t}\n\telse\n\t{\n\t\t// Error.\n\t\thud->align_y_num = HUD_ALIGN_TOP; // Left.\n\t\treturn 0;\n\t}\n}\n\nint Hud_HudCompare (const void *p1, const void *p2)\n{\n\treturn strcmp((*((hud_t **) p1))->name, (*((hud_t **) p2))->name);\n}\n\n//\n// List hud elements\n//\nvoid HUD_List (void)\n{\n\tstatic hud_t *sorted_huds[256];\n\tint i, count;\n\thud_t *hud;\n\n#define MAX_SORTED_HUDS (sizeof (sorted_huds) / sizeof (sorted_huds[0]))\n\n\tfor (hud = hud_huds, count = 0; hud && count < MAX_SORTED_HUDS; hud = hud->next, count++)\n\t\tsorted_huds[count] = hud;\n\tqsort (sorted_huds, count, sizeof (hud_t *), Hud_HudCompare);\n\n\tif (count == MAX_SORTED_HUDS)\n\t\tassert(!\"count == MAX_SORTED_HUDS\");\n\n\tCom_Printf(\"name            status\\n\");\n\tCom_Printf(\"--------------- ------\\n\");\n\tfor (i = 0; i < count; i++) {\n\t\thud = sorted_huds[i];\n\n\t\tCom_Printf(\"%-15s %s\\n\", hud->name, HUD_ShouldShow(hud) ? \"shown\" : \"hidden\");\n\t}\n}\n\n//\n// Show the specified hud element.\n//\nvoid HUD_Show_f (void)\n{\n\thud_t *hud;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCom_Printf(\"Usage: show [<name> | all]\\n\");\n\t\tCom_Printf(\"Show given HUD element.\\n\");\n\t\tCom_Printf(\"use \\\"show all\\\" to show all elements.\\n\");\n\t\tCom_Printf(\"Current elements status:\\n\\n\");\n\t\tHUD_List();\n\t\treturn;\n\t}\n\n\tif (!strcasecmp(Cmd_Argv(1), \"all\"))\n\t{\n\t\thud = hud_huds;\n\t\twhile (hud)\n\t\t{\n\t\t\tCvar_SetValue(hud->show, 1);\n\t\t\thud = hud->next;\n\t\t}\n\t}\n\telse\n\t{\n\t\thud = HUD_Find(Cmd_Argv(1));\n\n\t\tif (!hud)\n\t\t{\n\t\t\tCom_Printf(\"No such element: %s\\n\", Cmd_Argv(1));\n\t\t\treturn;\n\t\t}\n\n\t\tCvar_SetValue(hud->show, 1);\n\t}\n}\n\n\n//\n// Hide the specified hud element.\n//\nvoid HUD_Hide_f (void)\n{\n\thud_t *hud;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCom_Printf(\"Usage: hide [<name> | all]\\n\");\n\t\tCom_Printf(\"Hide given HUD element\\n\");\n\t\tCom_Printf(\"use \\\"hide all\\\" to hide all elements.\\n\");\n\t\tCom_Printf(\"Current elements status:\\n\\n\");\n\t\tHUD_List();\n\t\treturn;\n\t}\n\n\tif (!strcasecmp(Cmd_Argv(1), \"all\"))\n\t{\n\t\thud = hud_huds;\n\t\twhile (hud)\n\t\t{\n\t\t\tCvar_SetValue(hud->show, 0);\n\t\t\thud = hud->next;\n\t\t}\n\t}\n\telse\n\t{\n\t\thud = HUD_Find(Cmd_Argv(1));\n\n\t\tif (!hud)\n\t\t{\n\t\t\tCom_Printf(\"No such element: %s\\n\", Cmd_Argv(1));\n\t\t\treturn;\n\t\t}\n\n\t\tCvar_SetValue(hud->show, 0);\n\t}\n}\n\n//\n// Toggles specified hud element.\n//\nvoid HUD_Toggle_f (void)\n{\n\thud_t *hud;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCom_Printf(\"Usage: togglehud <name> | <variable>\\n\");\n\t\tCom_Printf(\"Show/hide given HUD element, or toggles variable value.\\n\");\n\t\treturn;\n\t}\n\n\thud = HUD_Find(Cmd_Argv(1));\n\n\tif (!hud)\n\t{\n\t\t// look for cvar\n\t\tcvar_t *var = Cvar_Find(Cmd_Argv(1));\n\t\tif (!var)\n\t\t{\n\t\t\tCom_Printf(\"No such element or variable: %s\\n\", Cmd_Argv(1));\n\t\t\treturn;\n\t\t}\n\n\t\tCvar_Set (var, var->value ? \"0\" : \"1\");\n\t\treturn;\n\t}\n\n\tCvar_Set (hud->show, HUD_ShouldShow(hud) ? \"0\" : \"1\");\n}\n\n//\n// Move the specified hud element relative to placement/alignment.\n//\nvoid HUD_Move_f (void)\n{\n\thud_t *hud;\n\n\tif (Cmd_Argc() != 4 &&  Cmd_Argc() != 2)\n\t{\n\t\tCom_Printf(\"Usage: move <name> [<x> <y>]\\n\");\n\t\tCom_Printf(\"Set offset for given HUD element\\n\");\n\t\treturn;\n\t}\n\n\thud = HUD_Find(Cmd_Argv(1));\n\n\tif (!hud)\n\t{\n\t\tCom_Printf(\"No such element: %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() == 2)\n\t{\n\t\tCom_Printf(\"Current %s offset is:\\n\", Cmd_Argv(1));\n\t\tCom_Printf(\"  x:  %s\\n\", hud->pos_x->string);\n\t\tCom_Printf(\"  y:  %s\\n\", hud->pos_y->string);\n\t\treturn;\n\t}\n\n\tCvar_SetValue(hud->pos_x, atof(Cmd_Argv(2)));\n\tCvar_SetValue(hud->pos_y, atof(Cmd_Argv(3)));\n}\n\n//\n// Resets a hud item to the center of the screen.\n//\nvoid HUD_Reset_f (void)\n{\n\thud_t *hud = NULL;\n\tchar *hudname = NULL;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCom_Printf(\"Usage: reset <name>\\n\");\n\t\tCom_Printf(\"Resets the position of the given HUD element to the center of the screen.\\n\");\n\t\treturn;\n\t}\n\n\thudname = Cmd_Argv(1);\n\n\thud = HUD_Find(hudname);\n\n\tif (!hud)\n\t{\n\t\tCom_Printf(\"No such HUD element %s.\\n\", hudname);\n\t\treturn;\n\t}\n\t\n\tCbuf_AddText(va(\"place %s screen\\n\", hudname));\n\tCbuf_AddText(va(\"move %s 0 0\\n\", hudname));\n\tCbuf_AddText(va(\"align %s center center\\n\", hudname));\n}\n\n//\n// Reorders children so that they are place infront of their parent.\n//\nvoid HUD_ReorderChildren(void)\n{\n\thud_t *hud = hud_huds;\n\n\t// Give all children a higher Z-order.\n\twhile(hud)\n\t{\n\t\tif(hud->place_hud && hud->order->value <= hud->place_hud->order->value)\n\t\t{\n\t\t\tCvar_SetValue(hud->order, hud->place_hud->order->value + 1);\n\t\t}\n\n\t\thud = hud->next;\n\t}\n}\n\n//\n// Place the specified hud element.\n//\nvoid HUD_Place_f (void)\n{\n\thud_t *hud;\n\tchar temp[512];\n\n\tif (Cmd_Argc() < 2 || Cmd_Argc() > 3)\n\t{\n\t\tCom_Printf(\"Usage: move <name> [<area>]\\n\");\n\t\tCom_Printf(\"Place HUD element at given area.\\n\");\n\t\tCom_Printf(\"\\nPossible areas are:\\n\");\n\t\tCom_Printf(\"  screen - screen area\\n\");\n\t\tCom_Printf(\"  top    - screen minus status bar\\n\");\n\t\tCom_Printf(\"  view   - view\\n\");\n\t\tCom_Printf(\"  sbar   - status bar\\n\");\n\t\tCom_Printf(\"  ibar   - inventory bar\\n\");\n\t\tCom_Printf(\"  hbar   - health bar\\n\");\n\t\tCom_Printf(\"  sfree  - status bar free area\\n\");\n\t\tCom_Printf(\"  ifree  - inventory bar free area\\n\");\n\t\tCom_Printf(\"  hfree  - health bar free area\\n\");\n\t\tCom_Printf(\"You can also use any other HUD element as a base alignment. In such case you should specify area as:\\n\");\n\t\tCom_Printf(\"  @elem  - if you want to place\\n\");\n\t\tCom_Printf(\"           it inside elem\\n\");\n\t\tCom_Printf(\"   elem  - if you want to place\\n\");\n\t\tCom_Printf(\"           it outside elem\\n\");\n\t\tCom_Printf(\"Examples:\\n\");\n\t\tCom_Printf(\"  place fps view\\n\");\n\t\tCom_Printf(\"  place fps @ping\\n\");\n\t\treturn;\n\t}\n\n\thud = HUD_Find(Cmd_Argv(1));\n\n\tif (!hud)\n\t{\n\t\tCom_Printf(\"No such element: %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() == 2)\n\t{\n\t\tCom_Printf(\"Current %s placement: %s\\n\", hud->name, hud->place->string);\n\t\treturn;\n\t}\n\n\t// Place with helper.\n\tstrlcpy(temp, hud->place->string, sizeof(temp));\n\tCvar_Set(hud->place, Cmd_Argv(2));\n\tif (!HUD_FindPlace(hud))\n\t{\n\t\tCom_Printf(\"place: invalid area argument: %s\\n\", Cmd_Argv(2));\n\t\tCvar_Set(hud->place, temp); // Restore old value.\n\t}\n\telse\n\t{\n\t\tHUD_ReorderChildren();\n\t}\n}\n\n//\n// Sets the z-order of a HUD element.\n//\nvoid HUD_Order_f (void)\n{\n\tint max = 0;\n\tint min = 0;\n\tchar *option = NULL;\n\thud_t *hud = NULL;\n\n\tif (Cmd_Argc() < 2 || Cmd_Argc() > 3)\n\t{\n\t\tCom_Printf(\"Usage: order <name> [<option>]\\n\");\n\t\tCom_Printf(\"Set HUD element draw order\\n\");\n\t\tCom_Printf(\"\\nPossible values for option:\\n\");\n\t\tCom_Printf(\"  #         - An integer representing the order.\\n\");\n\t\tCom_Printf(\"  backward  - Send the element backwards in the order.\\n\");\n\t\tCom_Printf(\"  forward   - Send the element forward in the order.\\n\");\n\t\tCom_Printf(\"  front     - Bring the element to the front.\\n\");\n\t\tCom_Printf(\"  back      - Put the element at the far back.\\n\");\n\t\treturn;\n\t}\n\n\thud = HUD_Find (Cmd_Argv(1));\n\n\tif (!hud)\n\t{\n\t\tCom_Printf(\"No such element: %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() == 2)\n\t{\n\t\tCom_Printf(\"Current order for %s is:\\n\", Cmd_Argv(1));\n\t\tCom_Printf(\"  order:  %d\\n\", (int)hud->order->value);\n\t\treturn;\n\t}\n\n\toption = Cmd_Argv(2);\n\n\tHUD_FindMaxMinOrder (&max, &min);\n\n\tif (!strncasecmp (option, \"backward\", 8))\n\t{\n\t\t// Send backward one step.\n\t\tCvar_SetValue(hud->order, (int)hud->order->value - 1);\n\t}\n\telse if (!strncasecmp (option, \"forward\", 7))\n\t{\n\t\t// Move forward one step.\n\t\tCvar_SetValue(hud->order, (int)hud->order->value + 1);\n\t}\n\telse if (!strncasecmp (option, \"front\", 5))\n\t{\n\t\t// Bring to front.\n\t\tCvar_SetValue(hud->order, max + 1);\n\t}\n\telse if (!strncasecmp (option, \"back\", 8))\n\t{\n\t\t// Send to far back.\n\t\tCvar_SetValue(hud->order, min - 1);\n\t}\n\telse\n\t{\n\t\t// Order #\n\t\tCvar_SetValue (hud->order, atoi(Cmd_Argv(2)));\n\t}\n}\n\n//\n// Align the specified hud element\n//\nvoid HUD_Align_f (void)\n{\n\thud_t *hud;\n\n\tif (Cmd_Argc() != 4  &&  Cmd_Argc() != 2)\n\t{\n\t\tCom_Printf(\"Usage: align <name> [<ax> <ay>]\\n\");\n\t\tCom_Printf(\"Set HUD element alignment\\n\");\n\t\tCom_Printf(\"\\nPossible values for ax are:\\n\");\n\t\tCom_Printf(\"  left    - left area edge\\n\");\n\t\tCom_Printf(\"  center  - area center\\n\");\n\t\tCom_Printf(\"  right   - right area edge\\n\");\n\t\tCom_Printf(\"  before  - before area (left)\\n\");\n\t\tCom_Printf(\"  after   - after area (right)\\n\");\n\t\tCom_Printf(\"\\nPossible values for ay are:\\n\");\n\t\tCom_Printf(\"  top     - screen top\\n\");\n\t\tCom_Printf(\"  center  - screen center\\n\");\n\t\tCom_Printf(\"  bottom  - screen bottom\\n\");\n\t\tCom_Printf(\"  before  - before area (top)\\n\");\n\t\tCom_Printf(\"  after   - after area (bottom)\\n\");\n\t\tCom_Printf(\"  console - below console\\n\");\n\t\treturn;\n\t}\n\n\thud = HUD_Find(Cmd_Argv(1));\n\n\tif (!hud)\n\t{\n\t\tCom_Printf(\"No such element: %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() == 2)\n\t{\n\t\tCom_Printf(\"Current alignment for %s is:\\n\", Cmd_Argv(1));\n\t\tCom_Printf(\"  horizontal (x):  %s\\n\", hud->align_x->string);\n\t\tCom_Printf(\"  vertical (y):    %s\\n\", hud->align_y->string);\n\t\treturn;\n\t}\n\n\t// validate and set\n\tCvar_Set(hud->align_x, Cmd_Argv(2));\n\tif (!HUD_FindAlignX(hud))\n\t\tCom_Printf(\"align: invalid X alignment: %s\\n\", Cmd_Argv(2));\n\n\tCvar_Set(hud->align_y, Cmd_Argv(3));\n\tif (!HUD_FindAlignY(hud))\n\t\tCom_Printf(\"align: invalid Y alignment: %s\\n\", Cmd_Argv(3));\n}\n\n//\n// Recalculate all elements\n// should be called if some HUD parameters (like place)\n// were changed directly by vars, not comands (like place)\n// - after execing cfg or sth\n//\nvoid HUD_Recalculate(void)\n{\n\thud_t *hud = hud_huds;\n\n\twhile (hud)\n\t{\n\t\tHUD_FindPlace(hud);\n\t\tHUD_FindAlignX(hud);\n\t\tHUD_FindAlignY(hud);\n\n\t\thud = hud->next;\n\t}\n}\nvoid HUD_Recalculate_f(void)\n{\n\tHUD_Recalculate();\n}\n\n//\n// Initialize HUD.\n//\nvoid HUD_Init(void)\n{\n\t// from hud.c, doesn't suit anywhere\n\tvoid HUD_Inputlag_hit_f(void);\n\n\t// Commands.\n\tCmd_AddCommand (\"show\", HUD_Show_f);\n\tCmd_AddCommand (\"hide\", HUD_Hide_f);\n\tCmd_AddCommand (\"move\", HUD_Move_f);\n\tCmd_AddCommand (\"place\", HUD_Place_f);\n\tCmd_AddCommand (\"reset\", HUD_Reset_f);\n\tCmd_AddCommand (\"order\", HUD_Order_f);\n\tCmd_AddCommand (\"togglehud\", HUD_Toggle_f);\n\tCmd_AddCommand (\"align\", HUD_Align_f);\n\tCmd_AddCommand (\"hud_recalculate\", HUD_Recalculate_f);\n\n\t// Variables.\n\tCvar_SetCurrentGroup(CVAR_GROUP_HUD);\n\tCvar_ResetCurrentGroup();\n\n\t// Register the hud items.\n\tCommonDraw_Init();\n\n\t// Sort the elements.\n\tHUD_Sort();\n}\n\n//\n// Calculate frame extents.\n//\nvoid HUD_CalcFrameExtents(hud_t *hud, int width, int height,\t\t\t\t\t\t\t\t\t// In.\n\t\t\t\t\t\t  int *frame_left, int *frame_right, int *frame_top, int *frame_bottom) // Out.\n{\n\tif ((hud->flags & HUD_NO_GROW) && hud->frame->value != 2)\n\t{\n\t\t*frame_left = *frame_right = *frame_top = *frame_bottom = 0;\n\t\treturn;\n\t}\n\n\tif (hud->frame->value == 2) // Treat text box separately.\n\t{\n\t\tint ax\t\t\t= (width % 16);\n\t\tint ay\t\t\t= (height % 8);\n\t\t*frame_left\t\t= 8 + ax / 2;\n\t\t*frame_top\t\t= 8 + ay / 2;\n\t\t*frame_right\t= 8 + ax - ax / 2;\n\t\t*frame_bottom\t= 8 + ay - ay / 2;\n\t}\n\telse if (hud->frame->value > 0  &&  hud->frame->value <= 1)\n\t{\n\t\tint frame_x, frame_y;\n\t\tframe_x = 2;\n\t\tframe_y = 2;\n\n\t\tif (width > 8)\n\t\t{\n\t\t\tframe_x <<= 1;\n\t\t}\n\n\t\tif (height > 8)\n\t\t{\n\t\t\tframe_y <<= 1;\n\t\t}\n\n\t\t*frame_left\t\t= frame_x;\n\t\t*frame_right\t= frame_x;\n\t\t*frame_top\t\t= frame_y;\n\t\t*frame_bottom\t= frame_y;\n\t}\n\telse\n\t{\n\t\t// No frame at all.\n\t\t*frame_left = *frame_right = *frame_top = *frame_bottom = 0;\n\t}\n}\n\nvoid HUD_OnChangeFrameColor(cvar_t *var, char *newval, qbool *cancel)\n{\n\t// Converts \"red\" into \"255 0 0\", etc. or returns input as it was.\n\tchar *new_color = ColorNameToRGBString (newval);\n\tchar buf[256], buf2[MAX_COM_TOKEN];\n\tsize_t hudname_len;\n\thud_t* hud_elem;\n\tbyte* b_colors;\n\n\thudname_len = min (sizeof (buf), strlen (var->name) - strlen (\"_frame_color\") - strlen (\"hud_\") + 1);\n\tstrlcpy (buf, var->name + 4, hudname_len);\n\thud_elem = HUD_Find (buf);\n\n\tstrlcpy(buf2,new_color,sizeof(buf2));\n\tb_colors = StringToRGB (buf2);\n\n\tmemcpy (hud_elem->frame_color_cache, b_colors, sizeof (byte) * 3);\n}\n\n//\n// Draw frame for HUD element.\n//\nvoid HUD_DrawFrame(hud_t *hud, int x, int y, int width, int height)\n{\n\tif (hud->frame_hide) {\n\t\treturn;\n\t}\n\n\tif (!hud->frame->value)\n\t\treturn;\n\n\tif (hud->frame->value > 0  &&  hud->frame->value <= 1)\n\t{\n\t\thud->frame_color_cache[3] = (byte)(255 * hud->frame->value);\n\n\t\tDraw_AlphaFillRGB(x, y, width, height, RGBAVECT_TO_COLOR(hud->frame_color_cache));\n\t\treturn;\n\t}\n\telse\n\t{\n\t\tswitch ((int)(hud->frame->value))\n\t\t{\n\t\tcase 2:     // Text box.\n\t\t\tDraw_TextBox(x, y, width/8-2, height/8-2);\n\t\t\tbreak;\n\t\tdefault:    // More will probably come.\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n//\n// Calculate element placement.\n//\nqbool HUD_PrepareDrawByName(char *name, int width, int height,\t// In.\n\t\t\t\t\t\t\tint *ret_x, int *ret_y)\t\t\t\t// Out (Position).\n{\n\thud_t *hud = HUD_Find(name);\n\tif (hud == NULL)\n\t{\n\t\treturn false; // error in C code\n\t}\n\n\treturn HUD_PrepareDraw(hud, width, height, ret_x, ret_y);\n}\n\n//\n// Calculate object extents and draws frame if needed.\n//\nqbool HUD_PrepareDraw(hud_t *hud, int width, int height, // In.\n\t\t\t\t\t  int *ret_x, int *ret_y)\t\t\t // Out (Position).\n{\n\textern vrect_t scr_vrect;\n\tint x, y;\n\tint frame_left, frame_right, frame_top, frame_bottom;\t// Frame left, right, top and bottom.\n\tint area_x, area_y, area_width, area_height;\t\t\t// Area coordinates & sizes to align.\n\tint bounds_x, bounds_y, bounds_width, bounds_height;\t// Bounds to draw within.\n\tqbool draw;\n\tqbool show = HUD_ShouldShow(hud);\n\n\t// Don't show the hud element.\n\tif (cls.state < hud->min_state || !show) {\n\t\treturn false;\n\t}\n\n\tdraw = HUD_ShouldDraw(hud);\n\n\tHUD_CalcFrameExtents(hud, width, height, &frame_left, &frame_right, &frame_top, &frame_bottom);\n\n\twidth  += frame_left + frame_right;\n\theight += frame_top + frame_bottom;\n\n\t//\n\t// Placement.\n\t//\n\tswitch (hud->place_num)\n\t{\n\t\tdefault:\n\t\tcase HUD_PLACE_SCREEN:\n\t\t\tbounds_x = bounds_y = 0;\n\t\t\tbounds_width = vid.width;\n\t\t\tbounds_height = vid.height;\n\t\t\tbreak;\n\t\tcase HUD_PLACE_TOP: // Top = screen - sbar\n\t\t\tbounds_x = bounds_y = 0;\n\t\t\tbounds_width = vid.width;\n\t\t\tbounds_height = vid.height - sb_lines;\n\t\t\tbreak;\n\t\tcase HUD_PLACE_VIEW:\n\t\t\tbounds_x = scr_vrect.x;\n\t\t\tbounds_y = scr_vrect.y;\n\t\t\tbounds_width = scr_vrect.width;\n\t\t\tbounds_height = scr_vrect.height;\n\t\t\tbreak;\n\t\tcase HUD_PLACE_SBAR:\n\t\t\tbounds_x = 0;\n\t\t\tbounds_y = vid.height - sb_lines;\n\t\t\tbounds_width = sbar_last_width;\n\t\t\tbounds_height = sb_lines;\n\t\t\tbreak;\n\t\tcase HUD_PLACE_IBAR:\n\t\t\tbounds_width = sbar_last_width;\n\t\t\tbounds_height = max(sb_lines - SBAR_HEIGHT, 0);\n\t\t\tbounds_x = 0;\n\t\t\tbounds_y = vid.height - sb_lines;\n\t\t\tbreak;\n\t\tcase HUD_PLACE_HBAR:\n\t\t\tbounds_width = sbar_last_width;\n\t\t\tbounds_height = min(SBAR_HEIGHT, sb_lines);\n\t\t\tbounds_x = 0;\n\t\t\tbounds_y = vid.height - bounds_height;\n\t\t\tbreak;\n\t\tcase HUD_PLACE_SFREE:\n\t\t\tbounds_x = sbar_last_width;\n\t\t\tbounds_y = vid.height - sb_lines;\n\t\t\tbounds_width = vid.width - sbar_last_width;\n\t\t\tbounds_height = sb_lines;\n\t\t\tbreak;\n\t\tcase HUD_PLACE_IFREE:\n\t\t\tbounds_width = vid.width - sbar_last_width;\n\t\t\tbounds_height = max(sb_lines - SBAR_HEIGHT, 0);\n\t\t\tbounds_x = sbar_last_width;\n\t\t\tbounds_y = vid.height - sb_lines;\n\t\t\tbreak;\n\t\tcase HUD_PLACE_HFREE:\n\t\t\tbounds_width = vid.width - sbar_last_width;\n\t\t\tbounds_height = min(SBAR_HEIGHT, sb_lines);\n\t\t\tbounds_x = sbar_last_width;\n\t\t\tbounds_y = vid.height - bounds_height;\n\t\t\tbreak;\n\t}\n\n\tif (hud->place_hud == NULL)\n\t{\n\t\t// Accepted boundaries are our area.\n\t\tarea_x\t\t= bounds_x;\n\t\tarea_y\t\t= bounds_y;\n\t\tarea_width\t= bounds_width;\n\t\tarea_height\t= bounds_height;\n\t}\n\telse\n\t{\n\t\t// Out area is our parent area.\n\t\tarea_x\t\t= hud->place_hud->lx;\n\t\tarea_y\t\t= hud->place_hud->ly;\n\t\tarea_width\t= hud->place_hud->lw;\n\t\tarea_height = hud->place_hud->lh;\n\n\t\tif (hud->place_outside)\n\t\t{\n\t\t\tarea_x\t\t-= hud->place_hud->al;\n\t\t\tarea_y\t\t-= hud->place_hud->at;\n\t\t\tarea_width\t+= hud->place_hud->al + hud->place_hud->ar;\n\t\t\tarea_height += hud->place_hud->at + hud->place_hud->ab;\n\t\t}\n\t}\n\n\t//\n\t// Horizontal pos.\n\t//\n\tswitch (hud->align_x_num)\n\t{\n\t\tdefault:\n\t\tcase HUD_ALIGN_LEFT:\n\t\t\tx = area_x;\n\t\t\tbreak;\n\t\tcase HUD_ALIGN_CENTER:\n\t\t\tx = area_x + (area_width - width) / 2;\n\t\t\tbreak;\n\t\tcase HUD_ALIGN_RIGHT:\n\t\t\tx = area_x + area_width - width;\n\t\t\tbreak;\n\t\tcase HUD_ALIGN_BEFORE:\n\t\t\tx = area_x - width;\n\t\t\tbreak;\n\t\tcase HUD_ALIGN_AFTER:\n\t\t\tx = area_x + area_width;\n\t\t\tbreak;\n\t}\n\n\tx += hud->pos_x->value;\n\n\t//\n\t// Vertical pos.\n\t//\n\tswitch (hud->align_y_num)\n\t{\n\t\tdefault:\n\t\tcase HUD_ALIGN_TOP:\n\t\t\ty = area_y;\n\t\t\tbreak;\n\t\tcase HUD_ALIGN_CENTER:\n\t\t\ty = area_y + (area_height - height) / 2;\n\t\t\tbreak;\n\t\tcase HUD_ALIGN_BOTTOM:\n\t\t\ty = area_y + area_height - height;\n\t\t\tbreak;\n\t\tcase HUD_ALIGN_BEFORE:\n\t\t\ty = area_y - height;\n\t\t\tbreak;\n\t\tcase HUD_ALIGN_AFTER:\n\t\t\ty = area_y + area_height;\n\t\t\tbreak;\n\t\tcase HUD_ALIGN_CONSOLE:\n\t\t\ty = max(area_y, scr_con_current);\n\t\t\tbreak;\n\t}\n\n\ty += hud->pos_y->value;\n\n\t// Draw frame.\n\tif (draw) {\n\t\tHUD_DrawFrame(hud, x, y, width, height);\n\t}\n\n\t// Assign values.\n\t*ret_x = x + frame_left;\n\t*ret_y = y + frame_top;\n\n\t// Remember values for children.\n\thud->lx = x + frame_left;\n\thud->ly = y + frame_top;\n\thud->lw = width - frame_left - frame_right;\n\thud->lh = height - frame_top - frame_bottom;\n\thud->al = frame_left;\n\thud->ar = frame_right;\n\thud->at = frame_top;\n\thud->ab = frame_bottom;\n\n\t// Check if we're supposed to draw the entire item or just the outline/frame.\n\t// (If we're in hud editor align/place mode)\n\tif (!HUD_Editor_ConfirmDraw(hud)) {\n\t\treturn false;\n\t}\n\n\t// Remember drawing sequence.\n\thud->last_draw_sequence = host_screenupdatecount;\n\treturn draw;\n}\n\n//\n// Creates a HUD variable based on a name.\n//\ncvar_t * HUD_CreateVar(char *hud_name, char *subvar, char *value)\n{\n\tcvar_t *var;\n\tchar buf[128];\n\n\tsnprintf (buf, sizeof (buf), \"hud_%s_%s\", hud_name, subvar);\n\tvar = (cvar_t *)Q_malloc(sizeof(cvar_t));\n\tmemset(var, 0, sizeof(cvar_t));\n\n\t// Set name.\n\tvar->name = Q_strdup(buf);\n\n\t// Default. Cvar_Register will dup this string so we don't need to.\n\tvar->string = value;\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_HUD);\n\tCvar_Register(var);\n\tCvar_ResetCurrentGroup();\n\treturn var;\n}\n\n//\n// Onchange for when z-order changes for a hud element. Resorts the elements.\n//\nvoid HUD_OnChangeOrder(cvar_t *var, char *val, qbool *cancel)\n{\n\tCvar_SetValue (var, atoi(val));\n\n\tHUD_ReorderChildren();\n\n\tHUD_Sort();\n}\n\n//\n// Registers a new HUD element to the list of HUD elements.\n//\nhud_t * HUD_Register(char *name, char *var_alias, char *description,\n\t\t\t\t\t int flags, cactive_t min_state, int draw_order,\n\t\t\t\t\t hud_func_type draw_func,\n\t\t\t\t\t char *show, char *place, char *align_x, char *align_y,\n\t\t\t\t\t char *pos_x, char *pos_y, char *frame, char *frame_color,\n\t\t\t\t\t char *item_opacity,\n\t\t\t\t\t char *params, ...)\n{\n\tint i;\n\tva_list     argptr;\n\thud_t\t\t*hud;\n\tchar\t\t*subvar;\n\n\t// We want to include Frame, frame_color, item_opacity in the list of\n\t// available cvar's for the user also. If any additional cvars that\n\t// common for all hud elements are added this needs to be increased.\n\tint\t\t\tnum_params = 3;\n\n\t// Allocate room for the HUD.\n\thud = (hud_t *) Q_malloc(sizeof(hud_t));\n\tmemset(hud, 0, sizeof(hud_t));\n\thud->next = hud_huds;\n\thud_huds = hud;\n\thud->min_state = min_state;\n\thud->draw_func = draw_func;\n\n\t// Name.\n\thud->name = (char *) Q_malloc(strlen(name)+1);\n\tstrcpy(hud->name, name);\n\n\t// Description.\n\thud->description = (char *) Q_malloc(strlen(description)+1);\n\tstrcpy(hud->description, description);\n\n\t// Count the number of params.\n\tsubvar = params;\n\tva_start (argptr, params);\n\twhile(subvar)\n\t{\n\t\tnum_params++;\n\t\tsubvar = va_arg(argptr, char *);\n\t}\n\tva_end (argptr);\n\n\t// Allocate the params array.\n\thud->params = Q_calloc(num_params, sizeof(cvar_t *));\n\n\t// Set flags.\n\thud->flags = flags;\n\n\tCmd_AddCommand(name, HUD_Func_f);\n\n\t//\n\t// Create standard variables.\n\t//\n\n\t//\n\t// Ordering\n\t//\n\t{\n\t\tchar order[18];\n\t\tsnprintf (order, sizeof(order), \"%d\", draw_order);\n\t\thud->order = HUD_CreateVar(name, \"order\", order);\n\t\thud->order->OnChange = HUD_OnChangeOrder;\n\t}\n\n\t//\n\t// Place.\n\t//\n\thud->place = HUD_CreateVar(name, \"place\", place);\n\ti = HUD_FindPlace(hud);\n\tif (i == 0)\n\t{\n\t\t// Probably parent should be registered earlier.\n\t\t// (This doesn't matter, since we'll re-place all elements after\n\t\t// all the elements have been registered)\n\t\thud->place_num = 0;\n\t\thud->place_hud = NULL;\n\t}\n\n\t//\n\t// Show.\n\t//\n\tif (show)\n\t{\n\t\thud->show = HUD_CreateVar(name, \"show\", show);\n\n\t\tif (flags & HUD_PLUSMINUS)\n\t\t{\n\t\t\tchar cmdname[128];\n\n\t\t\tstrlcpy(cmdname, \"+hud_\", sizeof(cmdname));\n\t\t\tstrlcat(cmdname, name, sizeof(cmdname));\n\t\t\tCmd_AddRemCommand(cmdname, HUD_Plus_f);\n\n\t\t\tcmdname[0] = '-';\n\t\t\tCmd_AddRemCommand(cmdname, HUD_Minus_f);\n\n\t\t\t// Add plus and minus commands.\n\t\t\t//Cmd_AddCommand(Q_strdup(va(\"+hud_%s\", name)), HUD_Plus_f);\n\t\t\t//Cmd_AddCommand(Q_strdup(va(\"-hud_%s\", name)), HUD_Minus_f);\n\t\t}\n\t}\n\telse\n\t{\n\t\thud->flags |= HUD_NO_SHOW;\n\t}\n\n\t//\n\t// X align & pos.\n\t//\n\tif (pos_x  &&  align_x)\n\t{\n\t\thud->pos_x = HUD_CreateVar(name, \"pos_x\", pos_x);\n\t\thud->align_x = HUD_CreateVar(name, \"align_x\", align_x);\n\t}\n\telse\n\t{\n\t\thud->flags |= HUD_NO_POS_X;\n\t}\n\n\t//\n\t// Y align & pos.\n\t//\n\tif (pos_y  &&  align_y)\n\t{\n\t\thud->pos_y = HUD_CreateVar(name, \"pos_y\", pos_y);\n\t\thud->align_y = HUD_CreateVar(name, \"align_y\", align_y);\n\t}\n\telse\n\t{\n\t\thud->flags |= HUD_NO_POS_Y;\n\t}\n\n\t//\n\t// Frame.\n\t//\n\tif (frame)\n\t{\n\t\thud->frame = HUD_CreateVar(name, \"frame\", frame);\n\t\thud->params[hud->num_params++] = hud->frame;\n\n\t\thud->frame_color = HUD_CreateVar(name, \"frame_color\", frame_color);\n\t\thud->frame_color->OnChange = HUD_OnChangeFrameColor;\n\t\thud->params[hud->num_params++] = hud->frame_color;\n\t}\n\telse\n\t{\n\t\thud->flags |= HUD_NO_FRAME;\n\t}\n\n\t//\n\t// Item Opacity.\n\t//\n\t{\n\t\thud->opacity = HUD_CreateVar(name, \"item_opacity\", (item_opacity) ? item_opacity : \"1\");\n\t\thud->flags |= HUD_OPACITY;\n\t\thud->params[hud->num_params++] = hud->opacity;\n\t}\n\n\t// Draw... if not set, will be measured (unlike ->show) but nothing rendered (assuming it follows standard pattern)\n\thud->draw = HUD_CreateVar(name, \"draw\", \"1\");\n\n\t//\n\t// Create parameters.\n\t//\n\tsubvar = params;\n\tva_start (argptr, params);\n\n\twhile (subvar) {\n\t\tchar *value = va_arg(argptr, char *);\n\t\tif (value == NULL || hud->num_params >= HUD_MAX_PARAMS || hud->num_params >= num_params) {\n\t\t\tSys_Error(\"HUD_Register: HUD_MAX_PARAMS overflow\");\n\t\t}\n\n\t\thud->params[hud->num_params] = HUD_CreateVar(name, subvar, value);\n\t\thud->num_params++;\n\t\tsubvar = va_arg(argptr, char *);\n\t}\n\n\tva_end (argptr);\n\n\treturn hud;\n}\n\n//\n// Find element in list.\n//\nhud_t * HUD_Find(char *name)\n{\n\thud_t *hud = hud_huds;\n\n\twhile (hud)\n\t{\n\t\tif (!strcasecmp(hud->name, name))\n\t\t{\n\t\t\treturn hud;\n\t\t}\n\n\t\thud = hud->next;\n\t}\n\treturn NULL;\n}\n\n//\n// Retrieve hud cvar.\n//\ncvar_t *HUD_FindVar(hud_t *hud, char *subvar)\n{\n\tint i;\n\tchar buf[128];\n\n\tsnprintf(buf, sizeof(buf), \"hud_%s_%s\", hud->name, subvar);\n\n\tfor (i=0; i < hud->num_params; i++)\n\t{\n\t\tif (!strcmp(buf, hud->params[i]->name))\n\t\t{\n\t\t\treturn hud->params[i];\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n//\n// HUD_FindInitTextColorVar searches for a HUD element by name.\n//\n// If not found, returns NULL (same as HUD_FindVar).\n//\n// If found, apply the CVAR_COLOR flag to indicate the value should be parsed as\n// a color.\n//\n// If the variable has a value, it is re-assigned to trigger correct color\n// parsing.\ncvar_t *HUD_FindInitTextColorVar(hud_t *hud, char *name)\n{\n\tcvar_t *cvar;\n\n\tif ((cvar = HUD_FindVar(hud, name)) == NULL)\n\t{\n\t\treturn NULL;\n\t}\n\n\tCvar_SetFlags(cvar, Cvar_GetFlags(cvar) | CVAR_COLOR);\n\n\tif (strlen(cvar->string) > 0)\n\t{\n\t\tCvar_Set(cvar, cvar->string);\n\t}\n\n\treturn cvar;\n}\n\n//\n// Draws single HUD element.\n//\nvoid HUD_DrawObject(hud_t *hud)\n{\n\textern qbool sb_showscores, sb_showteamscores;\n\n\t// Already tried to draw this frame.\n\tif (hud->last_try_sequence == host_screenupdatecount)\n\t{\n\t\treturn;\n\t}\n\n\thud->last_try_sequence = host_screenupdatecount;\n\n\t// Check if we should show this.\n\tif (!HUD_ShouldShow(hud)) {\n\t\treturn;\n\t}\n\n\tif (cls.state < hud->min_state)\n\t{\n\t\treturn;\n\t}\n\n\n\tif(hud->flags & HUD_NO_DRAW) {\n\t\t// draw only during specified events (e.g. HUD_ON_INTERMISSION)\n\t\tqbool draw = false;\n\n\t\tif (cl.intermission == 1 && (hud->flags & HUD_ON_INTERMISSION))\n\t\t\tdraw = true;\n\n\t\telse if (cl.intermission == 2 && !(hud->flags & HUD_ON_FINALE))\n\t\t\tdraw = true;\n\n\t\telse if ((sb_showscores || sb_showteamscores) && (hud->flags & HUD_ON_SCORES))\n\t\t\tdraw = true;\n\n\t\t//else if (???????? && (hud->flags & HUD_ON_DIALOG))\n\t\t//\tdraw = true;\n\n\t\tif(!draw)\n\t\t\treturn;\n\n\t} else {\n\t\tif (cl.intermission == 1 && !(hud->flags & HUD_ON_INTERMISSION))\n\t\t\treturn;\n\n\t\tif (cl.intermission == 2 && !(hud->flags & HUD_ON_FINALE))\n\t\t\treturn;\n\n\t\tif ((sb_showscores || sb_showteamscores) && !(hud->flags & HUD_ON_SCORES))\n\t\t\treturn;\n\t}\n\n\tif (hud->place_hud)\n\t{\n\t\t// Parent should be drawn it should be first.\n\t\tHUD_DrawObject(hud->place_hud);\n\n\t\t// If parent was not drawn, we refuse to draw too\n\t\tif (hud->place_hud->last_draw_sequence < host_screenupdatecount)\n\t\t{\n\t\t\treturn;\n\t\t}\n\t}\n\n\t//\n\t// Let the HUD element draw itself - updates last_draw_sequence itself.\n\t//\n\tDraw_SetOverallAlpha(hud->opacity->value);\n\thud->draw_func(hud);\n\tDraw_SetOverallAlpha(1.0);\n\n\t// last_draw_sequence is update by HUD_PrepareDraw\n\t// if object was succesfully drawn (wasn't outside area etc..)\n}\n\n//\n// Draw all active elements.\n//\nvoid HUD_Draw(void)\n{\n\textern cvar_t scr_newHud;\n\thud_t *hud;\n\n\t// Only draw the hud once in multiview.\n\tif (cl_multiview.integer && cls.mvdplayback)\n\t{\n\t\tif (CL_MultiviewCurrentView() != 1)\n\t\t{\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (mvd_autohud.integer && !autohud_loaded)\n\t{\n\t\tHUD_AutoLoad_MVD((int) mvd_autohud.value);\n\t\tCom_DPrintf(\"Loading AUTOHUD...\\n\");\n\t\tautohud_loaded = true;\n\t}\n\n\tif (scr_newHud.value == 0)\n\t{\n\t\treturn;\n\t}\n\n\thud = hud_huds;\n\n\tHUD_BeforeDraw();\n\n\twhile (hud)\n\t{\n\t\t// Draw.\n\t\tHUD_DrawObject(hud);\n\n\t\t// Go to next.\n\t\thud = hud->next;\n\t}\n\n\tHUD_AfterDraw();\n}\n\n//\n// Compares two hud elements.\n//\nint HUD_OrderFunc(const void * p_h1, const void * p_h2)\n{\n\tconst hud_t *h1 = *((hud_t **)p_h1);\n\tconst hud_t *h2 = *((hud_t **)p_h2);\n\n\treturn (int)h1->order->value - (int)h2->order->value;\n}\n\n//\n// Last phase of initialization.\n//\nvoid HUD_Sort(void)\n{\n\t// Sort elements based on their draw order.\n\tint i;\n\thud_t *huds[MAX_HUD_ELEMENTS];\n\tint count = 0;\n\thud_t *hud;\n\n\t// Copy to table.\n\thud = hud_huds;\n\twhile (hud)\n\t{\n\t\thuds[count++] = hud;\n\t\thud = hud->next;\n\t}\n\n\tif (count <= 0)\n\t\treturn;\n\n\t// Sort table.\n\tqsort(huds, count, sizeof(huds[0]), HUD_OrderFunc);\n\n\t// Back to list.\n\thud_huds = huds[0];\n\thud = hud_huds;\n\thud->next = NULL;\n\tfor (i=1; i < count; i++)\n\t{\n\t\thud->next = huds[i];\n\t\thud = hud->next;\n\t\thud->next = NULL;\n\t}\n\n\t// Recalculate elements so vars are parsed.\n\tHUD_Recalculate();\n}\n\n#define HUD_FreeVar(var) \\\n\tif ((var)) { \\\n\t\tCvar_Delete(var->name); \\\n\t\t(var) = NULL; \\\n\t}\n\nvoid HUD_Shutdown(void)\n{\n\thud_t* hud = hud_huds;\n\tint i;\n\n\twhile (hud) {\n\t\thud_t* next = hud->next;\n\n\t\tQ_free(hud->description);\n\t\tHUD_FreeVar(hud->order);\n\t\tHUD_FreeVar(hud->place);\n\t\tHUD_FreeVar(hud->show);\n\t\tHUD_FreeVar(hud->draw);\n\t\tHUD_FreeVar(hud->pos_x);\n\t\tHUD_FreeVar(hud->align_x);\n\t\tHUD_FreeVar(hud->pos_y);\n\t\tHUD_FreeVar(hud->align_y);\n\n\t\t// don't delete hud->frame/frame_color/opacity, they're in params array\n\t\tfor (i = 0; i < hud->num_params; ++i) {\n\t\t\tHUD_FreeVar(hud->params[i]);\n\t\t}\n\t\tQ_free(hud->params);\n\t\tQ_free(hud->name);\n\t\tQ_free(hud);\n\n\t\thud = next;\n\t}\n\thud_huds = NULL;\n}\n"
  },
  {
    "path": "src/hud.h",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n//\n// HUD commands\n//\n\n#ifndef __hud_h__\n#define __hud_h__\n\n// flags\n#define HUD_NO_DRAW            (1 <<  0)  // draw only during specified events (e.g. elements with flags = HUD_NO_DRAW | HUD_ON_INTERMISSION\n                                          // will only draw during the intermission)\n#define HUD_NO_SHOW            (1 <<  1)  // doesn't support show/hide\n#define HUD_NO_POS_X           (1 <<  2)  // doesn't support x positioning\n#define HUD_NO_POS_Y           (1 <<  3)  // doesn't support y positioning\n#define HUD_NO_FRAME           (1 <<  4)  // don't add frame\n#define HUD_ON_DIALOG          (1 <<  5)  // draw on dialog too\n#define HUD_ON_INTERMISSION    (1 <<  6)  // draw on intermission too\n#define HUD_ON_FINALE          (1 <<  7)  // draw on finale too\n#define HUD_ON_SCORES          (1 <<  8)  // draw on +showscores too\n#define HUD_NO_GROW            (1 <<  9)  // no frame grow\n#define HUD_PLUSMINUS          (1 << 10)  // auto add +/- commands\n#define HUD_OPACITY\t\t\t\t(1 << 11)\n\n#define HUD_INVENTORY          (HUD_NO_GROW)   // aply for sbar elements\n\n#define HUD_MAX_PARAMS  32\n\n#define\tHUD_REGEXP_OFFSET_COUNT\t20\n\n// Placement\n#define HUD_PLACE_SCREEN\t\t1\n#define HUD_PLACE_TOP\t\t\t2\n#define HUD_PLACE_VIEW\t\t\t3\n#define HUD_PLACE_SBAR\t\t\t4\n#define HUD_PLACE_IBAR\t\t\t5\n#define HUD_PLACE_HBAR\t\t\t6\n#define HUD_PLACE_SFREE\t\t\t7\n#define HUD_PLACE_IFREE\t\t\t8\n#define HUD_PLACE_HFREE\t\t\t9\n\n// Alignment\n#define HUD_ALIGN_LEFT\t\t\t1\n#define HUD_ALIGN_TOP\t\t\t1\n#define HUD_ALIGN_CENTER\t\t2\n#define HUD_ALIGN_RIGHT\t\t\t3\n#define HUD_ALIGN_BOTTOM\t\t3\n#define HUD_ALIGN_BEFORE\t\t4\n#define HUD_ALIGN_AFTER\t\t\t5\n#define HUD_ALIGN_CONSOLE\t\t6\n\ntypedef struct hud_s\n{\n    char *name;\t\t\t\t\t\t\t// Element name.\n    char *description;\t\t\t\t\t// Little help.\n\n    void (*draw_func) (struct hud_s *);\t// Drawing function.\n\n\tcvar_t *order;\t\t\t\t\t\t// Higher it is, later this element will be drawn\n\t\t\t\t\t\t\t\t\t\t// and more probable that will be on top.\n\n    cvar_t* show;                       // Show cvar.\n    cvar_t* draw;                       // Draw cvar\n    cvar_t* frame;                      // Frame cvar.\n    cvar_t* frame_color;                // Frame color cvar.\n    byte    frame_color_cache[4];       // Cache for parsed frame color.\n    qbool   frame_hide;                 // Toggle whether frame should be displayed or not.\n\n\tcvar_t *opacity;\t\t\t\t\t// The overall opacity of the entire HUD element.\n\n    // placement\n    cvar_t *place;\t\t\t\t\t\t// Place string, parsed to:\n    struct hud_s *place_hud;\t\t\t// if snapped to hud element\n    qbool place_outside;\t\t\t\t// if hud: inside ot outside\n    int place_num;\t\t\t\t\t\t// place number (our or parent if hud)\n\t\t\t\t\t\t\t\t\t\t// note: item is placed at another HUD element\n\t\t\t\t\t\t\t\t\t\t// if place_hud != NULL\n\n    cvar_t *align_x;\t\t\t\t\t// Alignment cvars (left, right, ...)\n    cvar_t *align_y;\n    int    align_x_num;\t\t\t\t\t// Parsed alignment.\n    int    align_y_num;\n\n    cvar_t *pos_x;\t\t\t\t\t\t// Position cvars.\n    cvar_t *pos_y;\n\n\tcvar_t **params;\t\t\t\t\t// Registered parameters for the HUD element.\n    int num_params;\n\n    cactive_t  min_state;\t\t\t\t// At least this state is required\n\t\t\t\t\t\t\t\t\t\t// to draw this element.\n\n    unsigned   flags;\n\n    // Last draw parameters (mostly used by children)\n    int lx, ly, lw, lh;\t\t\t\t\t// Last position.\n    int al, ar, at, ab;\t\t\t\t\t// Last frame params.\n\n    int last_try_sequence;\t\t\t\t// Sequence, at which object tried to draw itself.\n    int last_draw_sequence;\t\t\t\t// Sequence, at which it was last drawn successfully.\n\n    struct hud_s *next;\t\t\t\t\t// Next HUD in the list.\n} hud_t;\n\ntypedef  void (*hud_func_type) (struct hud_s *);\n\n#define MAX_HUD_ELEMENTS 256\n\n//\n// Initialize\n// \nvoid HUD_Init(void);\n\n//\n// Add element to list\n// parameter format: \"name1\", \"default1\", ..., \"nameX\", \"defaultX\", NULL\n//\nhud_t * HUD_Register(char *name, char *var_alias, char *description,\n                     int flags, cactive_t min_state, int draw_order,\n                     hud_func_type draw_func,\n                     char *show, char *place, char *align_x, char *align_y,\n                     char *pos_x, char *pos_y, char *frame, char *frame_color,\n                     char *item_opacity, char *params, ...);\n\n\n//\n// Draw all active elements.\n//\nvoid HUD_Draw(void);\n\n//\n// Retrieve hud cvar.\n//\ncvar_t *HUD_FindVar(hud_t *hud, char *subvar);\n\n//\n// Retrieve hud cvar by name and apply text color flags and parsing.\n//\ncvar_t *HUD_FindInitTextColorVar(hud_t *hud, char *name);\n\n//\n// Find element in list.\n//\nhud_t * HUD_Find(char *name);\n\n//\n// Calculate screen position of element.\n// return value:\n//    true  - draw it\n//    false - don't draw, it is off screen (mayby partially)\n//\nqbool HUD_PrepareDraw(\n\t\t\thud_t *hud, int width, int height,\t\t// In.\n\t\t\tint *ret_x, int *ret_y);\t\t\t\t// Out.\n\nqbool HUD_PrepareDrawByName(\n\t\t\tchar *element, int width, int height,\t// In.\n\t\t\tint *ret_x, int *ret_y);\t\t\t\t// Out.\n\n\n// Sort all HUD Elements.\nvoid HUD_Sort(void);\n\nvoid HUD_Shutdown(void);\n\n// Recalculate the position of all hud elements.\nvoid HUD_Recalculate(void);\n\n// when show pre-selected weapon/ammo? 1) player uses this system 2) not dead 3) when playing\n#define ShowPreselectedWeap()  (cl_weaponpreselect.value && cl.stats[STAT_HEALTH] > 0 && !cls.demoplayback && !cl.spectator)\n\n#define HUD_SHOW_ONLY_IN_TEAMPLAY\t\t1\n#define HUD_SHOW_ONLY_IN_DEMOPLAYBACK\t2\n\nqbool HUD_ShowInDemoplayback(int val);\n\n// Returns the total damage a player can sustain (stack)\nfloat SCR_HUD_TotalStrength(float health, float armorValue, float armorType);\n\n// Converts stats[STAT_ITEM] to armor %\nfloat SCR_HUD_ArmorType(int items);\n\n// Radar functions\nvoid Radar_HudInit(void);\nvoid HUD_NewRadarMap(void);\n\n#endif // __hud_h__\n"
  },
  {
    "path": "src/hud_262.c",
    "content": "/*\nCopyright (C) 1996-2003 Id Software, Inc., A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// Moved from cl_screen.c\n/// declarations may be found in screen.h\n\n#include \"quakedef.h\"\n#include <time.h>\n#include \"keys.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"hud_editor.h\"\n#include \"utils.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"input.h\"\n#include \"utils.h\"\n#include \"sbar.h\"\n#include \"menu.h\"\n#include \"image.h\"\n\nqbool CachePic_RemoveByPic(mpic_t* pic);\n\nstatic char *hud262_load_buff = NULL;\n\nextern cvar_t cl_hud;\n\n/**************************************** 262 HUD *****************************/\n// QW262 -->\nhud_element_t *hud_list = NULL;\nhud_element_t *prev;\n\nchar *Macro_BestAmmo(void); // Teamplay.c\nvoid Draw_AlphaRectangle(int x, int y, int w, int h, int c, float thickness, qbool fill, float alpha); // gl_draw.c\n\nvoid SCR_OnChangeMVHudPos(cvar_t *var, char *newval, qbool *cancel);\n\nvoid Hud_262LoadOnFirstStart(void)\n{\n\tif (hud262_load_buff != NULL) {\n\t\tCbuf_AddText(hud262_load_buff);\n\t\tCbuf_Execute();\n\t}\n\tQ_free(hud262_load_buff);\n}\n// <-- QW262\n\nhud_element_t *Hud_FindElement(const char *name)\n{\n\thud_element_t *elem;\n\n\tprev = NULL;\n\tfor (elem = hud_list; elem; elem = elem->next) {\n\t\tif (!strcasecmp(name, elem->name))\n\t\t\treturn elem;\n\t\tprev = elem;\n\t}\n\n\treturn NULL;\n}\n\nqbool Hud_ElementExists(const char* name)\n{\n\treturn Hud_FindElement(name) != NULL;\n}\n\nstatic hud_element_t* Hud_NewElement(void)\n{\n\thud_element_t*\telem;\n\telem = (hud_element_t *)Q_malloc(sizeof(hud_element_t));\n\telem->next = hud_list;\n\thud_list = elem;\n\telem->name = Q_strdup(Cmd_Argv(1));\n\treturn elem;\n}\n\nstatic void Hud_DeleteElement(hud_element_t *elem)\n{\n\tif (elem->flags & (HUD_STRING))\n\t\tQ_free(elem->contents);\n\tif (elem->flags & (HUD_IMAGE))\n\t\tCachePic_RemoveByPic(elem->contents);\n\tif (elem->f_hover)\n\t\tQ_free(elem->f_hover);\n\tif (elem->f_button)\n\t\tQ_free(elem->f_button);\n\tQ_free(elem->name);\n\tQ_free(elem);\n}\n\ntypedef void Hud_Elem_func(hud_element_t*);\n\nvoid Hud_ReSearch_do(Hud_Elem_func f)\n{\n\thud_element_t *elem;\n\n\tfor (elem = hud_list; elem; elem = elem->next) {\n\t\tif (ReSearchMatch(elem->name))\n\t\t\tf(elem);\n\t}\n}\n\nvoid Hud_Add_f(void)\n{\n\thud_element_t*\telem;\n\tstruct hud_element_s*\tnext = NULL;\t// Sergio\n\tqbool\thud_restore = false;\t// Sergio\n\tcvar_t\t\t*var;\n\tchar\t\t*a2, *a3;\n\t//Hud_Func\tfunc;\n\tunsigned\told_coords = 0;\n\tunsigned\told_width = 0;\n\tfloat\t\told_alpha = 1;\n\tqbool\told_enabled = true;\n\n\tif (Cmd_Argc() != 4)\n\t\tCom_Printf(\"Usage: hud_add <name> <type> <param>\\n\");\n\telse {\n\t\tif ((elem = Hud_FindElement(Cmd_Argv(1)))) {\n\t\t\tif (elem) {\n\t\t\t\told_coords = *((unsigned*)&(elem->coords));\n\t\t\t\told_width = elem->width;\n\t\t\t\told_alpha = elem->alpha;\n\t\t\t\told_enabled = elem->flags & HUD_ENABLED;\n\t\t\t\tnext = elem->next; // Sergio\n\t\t\t\tif (prev) {\n\t\t\t\t\tprev->next = elem->next;\n\t\t\t\t\thud_restore = true; // Sergio\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\thud_list = elem->next;\n\t\t\t\tHud_DeleteElement(elem);\n\t\t\t}\n\t\t}\n\n\t\ta2 = Cmd_Argv(2);\n\t\ta3 = Cmd_Argv(3);\n\n\t\tif (!strcasecmp(a2, \"cvar\")) {\n\t\t\tif ((var = Cvar_Find(a3))) {\n\t\t\t\telem = Hud_NewElement();\n\t\t\t\telem->contents = var;\n\t\t\t\telem->flags = HUD_CVAR | HUD_ENABLED;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCom_Printf(\"cvar \\\"%s\\\" not found\\n\", a3);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\telse if (!strcasecmp(a2, \"str\")) {\n\t\t\telem = Hud_NewElement();\n\t\t\telem->contents = Q_strdup(a3);\n\t\t\telem->flags = HUD_STRING | HUD_ENABLED;\n\t\t}\n\t\telse if (!strcasecmp(a2, \"img\")) {\n\t\t\tchar* pic_path = Q_strdup(a3);\n\t\t\tmpic_t* tmp_pic;\n\t\t\tif (pic_path && strlen(pic_path) > 0) {\n\t\t\t\t// Try loading the pic.\n\t\t\t\tif (!(tmp_pic = Draw_CachePicSafe(pic_path, false, true))) {\n\t\t\t\t\tCom_Printf(\"Couldn't load picture %s for '%s'\\n\", pic_path, a2);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\telem = Hud_NewElement();\n\t\t\t\telem->contents = tmp_pic;\n\t\t\t\telem->flags = HUD_IMAGE | HUD_ENABLED;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"\\\"%s\\\" is not a valid hud type\\n\", a2);\n\t\t\treturn;\n\t\t}\n\n\t\t*((unsigned*)&(elem->coords)) = old_coords;\n\t\telem->width = old_width;\n\t\telem->alpha = old_alpha;\n\t\tif (!old_enabled)\n\t\t\telem->flags &= ~HUD_ENABLED;\n\n\t\t// Sergio -->\n\t\t// Restoring old hud place in hud_list\n\t\tif (hud_restore) {\n\t\t\thud_list = elem->next;\n\t\t\telem->next = next;\n\t\t\tprev->next = elem;\n\t\t}\n\t\t// <-- Sergio\n\t}\n}\n\nvoid Hud_Elem_Remove(hud_element_t *elem)\n{\n\tif (prev)\n\t\tprev->next = elem->next;\n\telse\n\t\thud_list = elem->next;\n\tHud_DeleteElement(elem);\n}\n\nvoid Hud_Remove_f(void)\n{\n\thud_element_t\t*elem, *next_elem;\n\tchar\t\t\t*name;\n\tint\t\t\t\ti;\n\n\tfor (i = 1; i < Cmd_Argc(); i++) {\n\t\tname = Cmd_Argv(i);\n\t\tif (IsRegexp(name)) {\n\t\t\tif (!ReSearchInit(name))\n\t\t\t\treturn;\n\t\t\tprev = NULL;\n\t\t\tfor (elem = hud_list; elem; ) {\n\t\t\t\tif (ReSearchMatch(elem->name)) {\n\t\t\t\t\tnext_elem = elem->next;\n\t\t\t\t\tHud_Elem_Remove(elem);\n\t\t\t\t\telem = next_elem;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tprev = elem;\n\t\t\t\t\telem = elem->next;\n\t\t\t\t}\n\t\t\t}\n\t\t\tReSearchDone();\n\t\t}\n\t\telse {\n\t\t\tif ((elem = Hud_FindElement(name)))\n\t\t\t\tHud_Elem_Remove(elem);\n\t\t\telse\n\t\t\t\tCom_Printf(\"HudElement \\\"%s\\\" not found\\n\", name);\n\t\t}\n\t}\n}\n\nvoid Hud_Position_f(void)\n{\n\thud_element_t *elem;\n\n\tif (Cmd_Argc() != 5) {\n\t\tCom_Printf(\"Usage: hud_position <name> <pos_type> <x> <y>\\n\");\n\t\treturn;\n\t}\n\tif (!(elem = Hud_FindElement(Cmd_Argv(1)))) {\n\t\tCom_Printf(\"HudElement \\\"%s\\\" not found\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\telem->coords[0] = atoi(Cmd_Argv(2));\n\telem->coords[1] = atoi(Cmd_Argv(3));\n\telem->coords[2] = atoi(Cmd_Argv(4));\n}\n\nvoid Hud_Elem_Bg(hud_element_t *elem)\n{\n\telem->coords[3] = atoi(Cmd_Argv(2));\n}\n\nvoid Hud_Bg_f(void)\n{\n\thud_element_t *elem;\n\tchar\t*name = Cmd_Argv(1);\n\n\tif (Cmd_Argc() != 3)\n\t\tCom_Printf(\"Usage: hud_bg <name> <bgcolor>\\n\");\n\telse if (IsRegexp(name)) {\n\t\tif (!ReSearchInit(name))\n\t\t\treturn;\n\t\tHud_ReSearch_do(Hud_Elem_Bg);\n\t\tReSearchDone();\n\t}\n\telse {\n\t\tif ((elem = Hud_FindElement(name)))\n\t\t\tHud_Elem_Bg(elem);\n\t\telse\n\t\t\tCom_Printf(\"HudElement \\\"%s\\\" not found\\n\", name);\n\t}\n}\n\nvoid Hud_Elem_Move(hud_element_t *elem)\n{\n\telem->coords[1] += atoi(Cmd_Argv(2));\n\telem->coords[2] += atoi(Cmd_Argv(3));\n}\n\nvoid Hud262_Move_f(void)\n{\n\thud_element_t *elem;\n\tchar\t*name = Cmd_Argv(1);\n\n\tif (Cmd_Argc() != 4)\n\t\tCom_Printf(\"Usage: hud_move <name> <dx> <dy>\\n\");\n\telse if (IsRegexp(name)) {\n\t\tif (!ReSearchInit(name))\n\t\t\treturn;\n\t\tHud_ReSearch_do(Hud_Elem_Move);\n\t\tReSearchDone();\n\t}\n\telse {\n\t\tif ((elem = Hud_FindElement(name)))\n\t\t\tHud_Elem_Move(elem);\n\t\telse\n\t\t\tCom_Printf(\"HudElement \\\"%s\\\" not found\\n\", name);\n\t}\n}\n\nvoid Hud_Elem_Width(hud_element_t *elem)\n{\n\tif (elem->flags & HUD_IMAGE) {\n\t\tmpic_t *pic = elem->contents;\n\t\tint width = atoi(Cmd_Argv(2)) * 8;\n\t\tint height = width * pic->height / pic->width;\n\t\tpic->height = height;\n\t\tpic->width = width;\n\t}\n\telem->width = max(min(atoi(Cmd_Argv(2)), 128), 0);\n}\n\nvoid Hud_Width_f(void)\n{\n\thud_element_t *elem;\n\tchar\t*name = Cmd_Argv(1);\n\n\tif (Cmd_Argc() != 3)\n\t\tCom_Printf(\"Usage: hud_width <name> <width>\\n\");\n\telse if (IsRegexp(name)) {\n\t\tif (!ReSearchInit(name))\n\t\t\treturn;\n\t\tHud_ReSearch_do(Hud_Elem_Width);\n\t\tReSearchDone();\n\t}\n\telse {\n\t\tif ((elem = Hud_FindElement(name)))\n\t\t\tHud_Elem_Width(elem);\n\t\telse\n\t\t\tCom_Printf(\"HudElement \\\"%s\\\" not found\\n\", name);\n\t}\n}\n\nvoid Hud_Elem_Alpha(hud_element_t *elem)\n{\n\tfloat alpha = atof(Cmd_Argv(2));\n\telem->alpha = bound(0, alpha, 1);\n}\n\nvoid Hud_Alpha_f(void)\n{\n\thud_element_t *elem;\n\tchar\t*name = Cmd_Argv(1);\n\n\tif (Cmd_Argc() != 3) {\n\t\tCom_Printf(\"hud_alpha <name> <value> : set HUD transparency (0..1)\\n\");\n\t\treturn;\n\t}\n\tif (IsRegexp(name)) {\n\t\tif (!ReSearchInit(name))\n\t\t\treturn;\n\t\tHud_ReSearch_do(Hud_Elem_Alpha);\n\t\tReSearchDone();\n\t}\n\telse {\n\t\tif ((elem = Hud_FindElement(name)))\n\t\t\tHud_Elem_Alpha(elem);\n\t\telse\n\t\t\tCom_Printf(\"HudElement \\\"%s\\\" not found\\n\", name);\n\t}\n}\n\nvoid Hud_Elem_Blink(hud_element_t *elem)\n{\n\tdouble\t\tblinktime;\n\tunsigned\tmask;\n\n\tblinktime = atof(Cmd_Argv(2)) / 1000.0;\n\tmask = atoi(Cmd_Argv(3));\n\n\tif (mask > 3) return; // bad mask\n\tif (blinktime < 0.0 || blinktime > 5.0) return;\n\n\telem->blink = blinktime;\n\telem->flags = (elem->flags & (~(HUD_BLINK_F | HUD_BLINK_B))) | (mask << 3);\n}\n\nvoid Hud_Blink_f(void)\n{\n\thud_element_t *elem;\n\tchar\t*name = Cmd_Argv(1);\n\n\tif (Cmd_Argc() != 4)\n\t\tCom_Printf(\"Usage: hud_blink <name> <ms> <mask>\\n\");\n\telse if (IsRegexp(name)) {\n\t\tif (!ReSearchInit(name))\n\t\t\treturn;\n\t\tHud_ReSearch_do(Hud_Elem_Blink);\n\t\tReSearchDone();\n\t}\n\telse {\n\t\tif ((elem = Hud_FindElement(name)))\n\t\t\tHud_Elem_Blink(elem);\n\t\telse\n\t\t\tCom_Printf(\"HudElement \\\"%s\\\" not found\\n\", name);\n\t}\n}\n\nvoid Hud_Elem_Disable(hud_element_t *elem)\n{\n\telem->flags &= ~HUD_ENABLED;\n}\n\nvoid Hud_Disable_f(void)\n{\n\thud_element_t\t*elem;\n\tchar\t\t\t*name;\n\tint\t\t\t\ti;\n\n\tfor (i = 1; i < Cmd_Argc(); i++) {\n\t\tname = Cmd_Argv(i);\n\t\tif (IsRegexp(name)) {\n\t\t\tif (!ReSearchInit(name))\n\t\t\t\treturn;\n\t\t\tHud_ReSearch_do(Hud_Elem_Disable);\n\t\t\tReSearchDone();\n\t\t}\n\t\telse {\n\t\t\tif ((elem = Hud_FindElement(name)))\n\t\t\t\tHud_Elem_Disable(elem);\n\t\t\telse\n\t\t\t\tCom_Printf(\"HudElement \\\"%s\\\" not found\\n\", name);\n\t\t}\n\t}\n}\n\nvoid Hud_Elem_Enable(hud_element_t *elem)\n{\n\telem->flags |= HUD_ENABLED;\n}\n\nvoid Hud_Enable_f(void)\n{\n\thud_element_t\t*elem;\n\tchar\t\t\t*name;\n\tint\t\t\t\ti;\n\n\tfor (i = 1; i < Cmd_Argc(); i++) {\n\t\tname = Cmd_Argv(i);\n\t\tif (IsRegexp(name)) {\n\t\t\tif (!ReSearchInit(name))\n\t\t\t\treturn;\n\t\t\tHud_ReSearch_do(Hud_Elem_Enable);\n\t\t\tReSearchDone();\n\t\t}\n\t\telse {\n\t\t\tif ((elem = Hud_FindElement(name)))\n\t\t\t\tHud_Elem_Enable(elem);\n\t\t\telse\n\t\t\t\tCom_Printf(\"HudElement \\\"%s\\\" not found\\n\", name);\n\t\t}\n\t}\n}\n\nvoid Hud_List_f(void)\n{\n\thud_element_t\t*elem;\n\tchar\t\t\t*type;\n\tchar\t\t\t*param;\n\tint\t\t\t\tc, i, m;\n\n\tc = Cmd_Argc();\n\tif (c > 1)\n\t\tif (!ReSearchInit(Cmd_Argv(1)))\n\t\t\treturn;\n\n\tCom_Printf(\"List of hud elements:\\n\");\n\tfor (elem = hud_list, i = m = 0; elem; elem = elem->next, i++) {\n\t\tif (c == 1 || ReSearchMatch(elem->name)) {\n\t\t\tif (elem->flags & HUD_CVAR) {\n\t\t\t\ttype = \"cvar\";\n\t\t\t\tparam = ((cvar_t*)elem->contents)->name;\n\t\t\t}\n\t\t\telse if (elem->flags & HUD_STRING) {\n\t\t\t\ttype = \"str\";\n\t\t\t\tparam = elem->contents;\n\t\t\t}\n\t\t\telse if (elem->flags & HUD_FUNC) {\n\t\t\t\ttype = \"std\";\n\t\t\t\tparam = \"***\";\n\t\t\t}\n\t\t\telse if (elem->flags & HUD_IMAGE) {\n\t\t\t\ttype = \"img\";\n\t\t\t\tparam = \"***\";\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttype = \"invalid type\";\n\t\t\t\tparam = \"***\";\n\t\t\t}\n\t\t\tm++;\n\t\t\tCom_Printf(\"%s : %s : %s\\n\", elem->name, type, param);\n\t\t}\n\t}\n\n\tCom_Printf(\"------------\\n%i/%i hud elements\\n\", m, i);\n\tif (c > 1)\n\t\tReSearchDone();\n}\n\nvoid Hud_BringToFront_f(void)\n{\n\thud_element_t *elem, *start, *end;\n\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"Usage: hud_bringtofront <name>\\n\");\n\t\treturn;\n\t}\n\n\tend = Hud_FindElement(Cmd_Argv(1));\n\tif (end) {\n\t\tif (end->next) {\n\t\t\tstart = hud_list;\n\t\t\thud_list = end->next;\n\t\t\tend->next = NULL;\n\t\t\tfor (elem = hud_list; elem->next; elem = elem->next) {}\n\t\t\telem->next = start;\n\t\t}\n\t}\n\telse {\n\t\tCom_Printf(\"HudElement \\\"%s\\\" not found\\n\", Cmd_Argv(1));\n\t}\n}\n\nstatic qbool Hud_TranslateCoords(hud_element_t *elem, int *x, int *y)\n{\n\tint l;\n\n\tif (!elem->scr_width || !elem->scr_height)\n\t\treturn false;\n\n\tl = elem->scr_width / 8;\n\n\tswitch (elem->coords[0]) {\n\tcase 1:\t*x = elem->coords[1] * 8 + 1;\t\t\t\t\t// top left\n\t\t*y = elem->coords[2] * 8;\n\t\tbreak;\n\tcase 2:\t*x = vid.conwidth - (elem->coords[1] + l) * 8 - 1;\t// top right\n\t\t*y = elem->coords[2] * 8;\n\t\tbreak;\n\tcase 3:\t*x = vid.conwidth - (elem->coords[1] + l) * 8 - 1;\t// bottom right\n\t\t*y = vid.conheight - sb_lines - (elem->coords[2] + 1) * 8;\n\t\tbreak;\n\tcase 4:\t*x = elem->coords[1] * 8 + 1;\t\t\t\t\t// bottom left\n\t\t*y = vid.conheight - sb_lines - (elem->coords[2] + 1) * 8;\n\t\tbreak;\n\tcase 5:\t*x = vid.conwidth / 2 - l * 4 + elem->coords[1] * 8;// top center\n\t\t*y = elem->coords[2] * 8;\n\t\tbreak;\n\tcase 6:\t*x = vid.conwidth / 2 - l * 4 + elem->coords[1] * 8;// bottom center\n\t\t*y = vid.conheight - sb_lines - (elem->coords[2] + 1) * 8;\n\t\tbreak;\n\tdefault:\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nvoid SCR_DrawHud(void)\n{\n\thud_element_t* elem;\n\tint x, y;\n\tunsigned int l;\n\tchar buf[1024];\n\tchar *st = NULL;\n\tHud_Func func;\n\tdouble tblink = 0.0;\n\tmpic_t *img = NULL;\n\n\tif (hud_list && cl_hud.value && !((cls.demoplayback || cl.spectator) && cl_restrictions.value)) {\n\t\tfor (elem = hud_list; elem; elem = elem->next) {\n\t\t\tif (!(elem->flags & HUD_ENABLED)) {\n\t\t\t\tcontinue; // do not draw disabled elements\n\t\t\t}\n\n\t\t\telem->scr_height = 8;\n\n\t\t\tif (elem->flags & HUD_CVAR) {\n\t\t\t\tst = ((cvar_t*)elem->contents)->string;\n\t\t\t\tstrlcpy(buf, st, sizeof(buf));\n\t\t\t\tst = buf;\n\t\t\t\tl = strlen_color(st);\n\t\t\t}\n\t\t\telse if (elem->flags & HUD_STRING) {\n\t\t\t\tCmd_ExpandString(elem->contents, buf); //, sizeof(buf));\n\t\t\t\tst = TP_ParseMacroString(buf);\n\t\t\t\tst = TP_ParseFunChars(st, false);\n\t\t\t\tl = strlen_color(st);\n\t\t\t}\n\t\t\telse if (elem->flags & HUD_FUNC) {\n\t\t\t\tfunc = (Hud_Func)elem->contents;\n\t\t\t\tst = (*func)();\n\t\t\t\tl = strlen(st);\n\t\t\t} else if (elem->flags & HUD_IMAGE) {\n\t\t\t\timg = (mpic_t*)elem->contents;\n\t\t\t\tl = img->width/8;\n\t\t\t\telem->scr_height = img->height;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (elem->width && !(elem->flags & (HUD_FUNC | HUD_IMAGE))) {\n\t\t\t\tif (elem->width < l) {\n\t\t\t\t\tl = elem->width;\n\t\t\t\t\tst[l] = '\\0';\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\twhile (elem->width > l) {\n\t\t\t\t\t\tst[l++] = ' ';\n\t\t\t\t\t}\n\t\t\t\t\tst[l] = '\\0';\n\t\t\t\t}\n\t\t\t}\n\t\t\telem->scr_width = l * 8;\n\n\t\t\tif (!Hud_TranslateCoords(elem, &x, &y)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (elem->flags & (HUD_BLINK_B | HUD_BLINK_F)) {\n\t\t\t\ttblink = fmod(cls.realtime, elem->blink) / elem->blink;\n\t\t\t}\n\n\t\t\tif (!(elem->flags & HUD_BLINK_B) || tblink < 0.5) {\n\t\t\t\tif (elem->coords[3]) {\n\t\t\t\t\tif (elem->alpha < 1) {\n\t\t\t\t\t\tDraw_AlphaFill(x, y, elem->scr_width, elem->scr_height, (unsigned char)elem->coords[3], elem->alpha);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tDraw_Fill(x, y, elem->scr_width, elem->scr_height, (unsigned char)elem->coords[3]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!(elem->flags & HUD_BLINK_F) || tblink < 0.5) {\n\t\t\t\tif (!(elem->flags & HUD_IMAGE)) {\n\t\t\t\t\t/*\n\t\t\t\t\tint std_charset = char_textures[0];\n\t\t\t\t\tif (elem->charset) {\n\t\t\t\t\t\tchar_textures[0] = elem->charset;\n\t\t\t\t\t}*/\n\t\t\t\t\tif (elem->alpha < 1) {\n\t\t\t\t\t\tDraw_AlphaString(x, y, st, elem->alpha);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tDraw_String(x, y, st);\n\t\t\t\t\t}\n\t\t\t\t\t//char_textures[0] = std_charset;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (elem->alpha < 1) {\n\t\t\t\t\t\tDraw_AlphaPic(x, y, img, elem->alpha);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tDraw_Pic(x, y, img);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// QW262 -->\nvoid Hud_262Init(void)\n{\n\t//\n\t// register hud commands\n\t//\n\tCmd_AddCommand(\"hud262_add\", Hud_Add_f);\n\tCmd_AddCommand(\"hud262_remove\", Hud_Remove_f);\n\tCmd_AddCommand(\"hud262_position\", Hud_Position_f);\n\tCmd_AddCommand(\"hud262_bg\", Hud_Bg_f);\n\tCmd_AddCommand(\"hud262_move\", Hud262_Move_f);\n\tCmd_AddCommand(\"hud262_width\", Hud_Width_f);\n\tCmd_AddCommand(\"hud262_alpha\", Hud_Alpha_f);\n\tCmd_AddCommand(\"hud262_blink\", Hud_Blink_f);\n\tCmd_AddCommand(\"hud262_disable\", Hud_Disable_f);\n\tCmd_AddCommand(\"hud262_enable\", Hud_Enable_f);\n\tCmd_AddCommand(\"hud262_list\", Hud_List_f);\n\tCmd_AddCommand(\"hud262_bringtofront\", Hud_BringToFront_f);\n}\n\nvoid Hud_262CatchStringsOnLoad(char *line)\n{\n\tchar *tmpbuff;\n\n\tif (Utils_RegExpMatch(\"^((\\\\s+)?(?i)hud262_(add|alpha|bg|blink|disable|enable|position|width))\", line)) {\n\t\tif (hud262_load_buff == NULL) {\n\t\t\thud262_load_buff = (char*)Q_malloc((strlen(line) + 2) * sizeof(char));\n\t\t\tsnprintf(hud262_load_buff, strlen(line) + 2, \"%s\\n\", line);\n\t\t}\n\t\telse {\n\t\t\ttmpbuff = (char *)Q_malloc(strlen(hud262_load_buff) + 1);\n\t\t\tstrcpy(tmpbuff, hud262_load_buff);\n\t\t\thud262_load_buff = (char *)Q_realloc(hud262_load_buff, (strlen(tmpbuff) + strlen(line) + 2) * sizeof(char));\n\t\t\tsnprintf(hud262_load_buff, strlen(tmpbuff) + strlen(line) + 2, \"%s%s\\n\", tmpbuff, line);\n\t\t\tQ_free(tmpbuff);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/hud_ammo.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n//\n// common HUD elements\n// like clock etc..\n//\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"fonts.h\"\n\nextern cvar_t hud_tp_need;\nextern cvar_t tp_need_shells, tp_need_nails, tp_need_rockets, tp_need_cells;\n\nstatic int TP_TeamFortressEngineerSpanner(void)\n{\n\tif (cl.teamfortress) {\n\t\t// MEAG: Fixme!\n\t\tchar *player_skin = Info_ValueForKey(cl.players[cl.playernum].userinfo, \"skin\");\n\t\tchar *model_name = cl.model_precache[CL_WeaponModelForView()->current.modelindex]->name;\n\t\tif (player_skin && (strcasecmp(player_skin, \"tf_eng\") == 0) && model_name && (strcasecmp(model_name, \"progs/v_span.mdl\") == 0)) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic int State_AmmoNumForWeapon(int weapon)\n{\t// returns ammo number (shells = 1, nails = 2, rox = 3, cells = 4) for given weapon\n\tswitch (weapon) {\n\t\tcase 2: case 3: return 1;\n\t\tcase 4: case 5: return 2;\n\t\tcase 6: case 7: return 3;\n\t\tcase 8: return 4;\n\t\tdefault: return 0;\n\t}\n}\n\nstatic int State_AmmoForWeapon(int weapon)\n{\t// returns ammo amount for given weapon\n\tint ammon = State_AmmoNumForWeapon(weapon);\n\n\tif (ammon)\n\t\treturn cl.stats[STAT_SHELLS + ammon - 1];\n\telse\n\t\treturn 0;\n}\n\nstatic int TP_IsAmmoLow(int weapon)\n{\n\tint ammo = State_AmmoForWeapon(weapon);\n\tswitch (weapon) {\n\t\tcase 2:\n\t\tcase 3:  return ammo <= tp_need_shells.value;\n\t\tcase 4:\n\t\tcase 5:  return ammo <= tp_need_nails.value;\n\t\tcase 6:\n\t\tcase 7:  return ammo <= tp_need_rockets.value;\n\t\tcase 8:  return ammo <= tp_need_cells.value;\n\t\tdefault: return 0;\n\t}\n}\n\nint HUD_AmmoLowByWeapon(int weapon)\n{\n\tif (hud_tp_need.value) {\n\t\treturn TP_IsAmmoLow(weapon);\n\t}\n\telse {\n\t\tint a;\n\t\tswitch (weapon) {\n\t\t\tcase 2:\n\t\t\tcase 3:\n\t\t\t\ta = STAT_SHELLS; break;\n\t\t\tcase 4:\n\t\t\tcase 5:\n\t\t\t\ta = STAT_NAILS; break;\n\t\t\tcase 6:\n\t\t\tcase 7:\n\t\t\t\ta = STAT_ROCKETS; break;\n\t\t\tcase 8:\n\t\t\t\ta = STAT_CELLS; break;\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t\treturn (HUD_Stats(a) <= 10);\n\t}\n}\n\nstatic void SCR_HUD_DrawAmmo(\n\thud_t *hud, int num, float scale, int style, int digits, char *s_align, qbool proportional, qbool always,\n\tbyte *text_color_low, byte *text_color_normal\n)\n{\n\textern mpic_t sb_ib_ammo[4];\n\tint value, num_old;\n\tqbool low;\n\n\tnum_old = num;\n\tif (num < 1 || num > 4) {\t// draw 'current' ammo, which one is it?\n\t\tif (ShowPreselectedWeap()) {\n\t\t\t// using weapon pre-selection so show info for current best pre-selected weapon ammo\n\t\t\tnum = State_AmmoNumForWeapon(IN_BestWeapon(true));\n\t\t}\n\t\telse {\n\t\t\t// not using weapon pre-selection or player is dead so show current selected ammo\n\t\t\tif (HUD_Stats(STAT_ITEMS) & IT_SHELLS) {\n\t\t\t\tnum = 1;\n\t\t\t}\n\t\t\telse if (HUD_Stats(STAT_ITEMS) & IT_NAILS) {\n\t\t\t\tnum = 2;\n\t\t\t}\n\t\t\telse if (HUD_Stats(STAT_ITEMS) & IT_ROCKETS) {\n\t\t\t\tnum = 3;\n\t\t\t}\n\t\t\telse if (HUD_Stats(STAT_ITEMS) & IT_CELLS) {\n\t\t\t\tnum = 4;\n\t\t\t}\n\t\t\telse if (TP_TeamFortressEngineerSpanner()) {\n\t\t\t\tnum = 4;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tnum = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\t// 'New HUD' used to just return - instead draw blank space so other objects can be placed\n\t// If user has specified 'show_always', carry on and show current value of STAT_AMMO\n\tif (!num && !always) {\n\t\tif (style < 2 || style == 3) {\n\t\t\t// use this to calculate sizes, but draw_content is false\n\t\t\tSCR_HUD_DrawNum2(hud, 0, false, scale, style, digits, s_align, proportional, false, text_color_low, text_color_normal);\n\t\t}\n\t\telse {\n\t\t\tint x_, y;\n\n\t\t\t// calculate sizes but draw nothing\n\t\t\tHUD_PrepareDraw(hud, 42 * scale, 11 * scale, &x_, &y);\n\t\t}\n\t\treturn;\n\t}\n\n\tlow = HUD_AmmoLowByWeapon(num * 2);\n\tif (num < 1 || num > 4 || (num_old == 0 && (!ShowPreselectedWeap() || cl.standby))) {\n\t\t// this check is here to display a feature from KTPRO/KTX where you can see received damage in prewar\n\t\t// also we make sure this applies only to 'ammo' element\n\t\t// weapon preselection must always use HUD_Stats()\n\t\tvalue = cl.stats[STAT_AMMO];\n\t}\n\telse {\n\t\tvalue = HUD_Stats(STAT_SHELLS + num - 1);\n\t}\n\n\tif (style < 2 || style == 3) {\n\t\t// simply draw number\n\t\tSCR_HUD_DrawNum2(hud, value, style == 3 ? false : low, scale, style, digits, s_align, proportional, true, text_color_low, text_color_normal);\n\t}\n\telse {\n\t\t// else - draw classic ammo-count box with background\n\t\tchar buf[8];\n\t\tint  x_, y;\n\t\tfloat x;\n\t\ttext_alignment_t align;\n\n\t\talign = text_align_right;\n\t\tswitch (tolower(s_align[0])) {\n\t\t\tcase 'l':   // 'l'eft\n\t\t\t\talign = text_align_left; break;\n\t\t\tcase 'c':   // 'c'enter\n\t\t\t\talign = text_align_center; break;\n\t\t\tdefault:\n\t\t\tcase 'r':   // 'r'ight\n\t\t\t\talign = text_align_right; break;\n\t\t}\n\n\t\tscale = max(scale, 0.01);\n\t\tif (!HUD_PrepareDraw(hud, 42 * scale, 11 * scale, &x_, &y)) {\n\t\t\treturn;\n\t\t}\n\n\t\tx = x_;\n\t\tif (num >= 1 && num <= sizeof(sb_ib_ammo) / sizeof(sb_ib_ammo[0]) && R_TextureReferenceIsValid(sb_ib_ammo[num - 1].texnum)) {\n\t\t\tDraw_SPic(x, y, &sb_ib_ammo[num - 1], scale);\n\t\t}\n\n\t\tsnprintf(buf, sizeof(buf), \"%d\", value);\n\n\t\t// convert to nicer numbers\n\t\tbuf[0] = buf[0] >= '0' ? 18 + buf[0] - '0' : buf[0];\n\t\tbuf[1] = buf[1] >= '0' ? 18 + buf[1] - '0' : buf[1];\n\t\tbuf[2] = buf[2] >= '0' ? 18 + buf[2] - '0' : buf[2];\n\n\t\tDraw_SStringAligned(x, y, buf, scale, 1.0f, proportional, align, x + 30 * scale);\n\t}\n}\n\nvoid SCR_HUD_DrawAmmoCurrent(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *digits, *align, *proportional, *always, *text_color_low, *text_color_normal;\n\tif (scale == NULL)  // first time called\n\t{\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tdigits = HUD_FindVar(hud, \"digits\");\n\t\talign = HUD_FindVar(hud, \"align\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t\talways = HUD_FindVar(hud, \"show_always\");\n\t\ttext_color_low = HUD_FindInitTextColorVar(hud, \"text_color_low\");\n\t\ttext_color_normal = HUD_FindInitTextColorVar(hud, \"text_color_normal\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawAmmo(hud, 0, scale->value, style->value, digits->value, align->string,\n\t\t\tproportional->integer, always->integer,\n\t\t\tstrlen(text_color_low->string) == 0 ? NULL : text_color_low->color,\n\t\t\tstrlen(text_color_normal->string) == 0 ? NULL : text_color_normal->color);\n\t}\n}\n\nvoid SCR_HUD_DrawAmmo1(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *digits, *align, *proportional, *text_color_low, *text_color_normal;\n\tif (scale == NULL)  // first time called\n\t{\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tdigits = HUD_FindVar(hud, \"digits\");\n\t\talign = HUD_FindVar(hud, \"align\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t\ttext_color_low = HUD_FindInitTextColorVar(hud, \"text_color_low\");\n\t\ttext_color_normal = HUD_FindInitTextColorVar(hud, \"text_color_normal\");\n\t}\n\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawAmmo(hud, 1, scale->value, style->value, digits->value, align->string,\n\t\t\tproportional->integer, true,\n\t\t\tstrlen(text_color_low->string) == 0 ? NULL : text_color_low->color,\n\t\t\tstrlen(text_color_normal->string) == 0 ? NULL : text_color_normal->color);\n\t}\n}\n\nvoid SCR_HUD_DrawAmmo2(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *digits, *align, *proportional, *text_color_low, *text_color_normal;\n\tif (scale == NULL)  // first time called\n\t{\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tdigits = HUD_FindVar(hud, \"digits\");\n\t\talign = HUD_FindVar(hud, \"align\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t\ttext_color_low = HUD_FindInitTextColorVar(hud, \"text_color_low\");\n\t\ttext_color_normal = HUD_FindInitTextColorVar(hud, \"text_color_normal\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawAmmo(hud, 2, scale->value, style->value, digits->value, align->string,\n\t\t\tproportional->integer, true,\n\t\t\tstrlen(text_color_low->string) == 0 ? NULL : text_color_low->color,\n\t\t\tstrlen(text_color_normal->string) == 0 ? NULL : text_color_normal->color);\n\t}\n}\n\nvoid SCR_HUD_DrawAmmo3(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *digits, *align, *proportional, *text_color_low, *text_color_normal;\n\tif (scale == NULL)  // first time called\n\t{\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tdigits = HUD_FindVar(hud, \"digits\");\n\t\talign = HUD_FindVar(hud, \"align\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t\ttext_color_low = HUD_FindInitTextColorVar(hud, \"text_color_low\");\n\t\ttext_color_normal = HUD_FindInitTextColorVar(hud, \"text_color_normal\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawAmmo(hud, 3, scale->value, style->value, digits->value, align->string,\n\t\t\tproportional->integer, true,\n\t\t\tstrlen(text_color_low->string) == 0 ? NULL : text_color_low->color,\n\t\t\tstrlen(text_color_normal->string) == 0 ? NULL : text_color_normal->color);\n\t}\n}\n\nvoid SCR_HUD_DrawAmmo4(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *digits, *align, *proportional, *text_color_low, *text_color_normal;\n\tif (scale == NULL)  // first time called\n\t{\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tdigits = HUD_FindVar(hud, \"digits\");\n\t\talign = HUD_FindVar(hud, \"align\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t\ttext_color_low = HUD_FindInitTextColorVar(hud, \"text_color_low\");\n\t\ttext_color_normal = HUD_FindInitTextColorVar(hud, \"text_color_normal\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawAmmo(hud, 4, scale->value, style->value, digits->value, align->string,\n\t\t\tproportional->integer, true,\n\t\t\tstrlen(text_color_low->string) == 0 ? NULL : text_color_low->color,\n\t\t\tstrlen(text_color_normal->string) == 0 ? NULL : text_color_normal->color);\n\t}\n}\n\n// icons - active ammo, armor, face etc..\nvoid SCR_HUD_DrawAmmoIcon(hud_t *hud, int num, float scale, int style)\n{\n\textern mpic_t *sb_ammo[4];\n\tint   x, y, width, height;\n\n\tscale = max(scale, 0.01);\n\n\twidth = height = (style ? 8 : 24) * scale;\n\n\tif (cl.spectator == cl.autocam) {\n\t\tif (!HUD_PrepareDraw(hud, width, height, &x, &y) || num == 0)\n\t\t\treturn;\n\n\t\tif (style) {\n\t\t\tswitch (num) {\n\t\t\t\tcase 1: Draw_SAlt_String(x, y, \"s\", scale, false); break;\n\t\t\t\tcase 2: Draw_SAlt_String(x, y, \"n\", scale, false); break;\n\t\t\t\tcase 3: Draw_SAlt_String(x, y, \"r\", scale, false); break;\n\t\t\t\tcase 4: Draw_SAlt_String(x, y, \"c\", scale, false); break;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tDraw_SPic(x, y, sb_ammo[num - 1], scale);\n\t\t}\n\t}\n}\n\nvoid SCR_HUD_DrawAmmoIconCurrent(hud_t *hud)\n{\n\tint num;\n\tstatic cvar_t *scale = NULL, *style;\n\n\tif (scale == NULL)  // first time called\n\t{\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t}\n\n\tif (ShowPreselectedWeap()) {\n\t\t// using weapon pre-selection so show info for current best pre-selected weapon ammo\n\t\tnum = State_AmmoNumForWeapon(IN_BestWeapon(true));\n\t}\n\telse {\n\t\t// not using weapon pre-selection or player is dead so show current selected ammo\n\t\tif (HUD_Stats(STAT_ITEMS) & IT_SHELLS)\n\t\t\tnum = 1;\n\t\telse if (HUD_Stats(STAT_ITEMS) & IT_NAILS)\n\t\t\tnum = 2;\n\t\telse if (HUD_Stats(STAT_ITEMS) & IT_ROCKETS)\n\t\t\tnum = 3;\n\t\telse if (HUD_Stats(STAT_ITEMS) & IT_CELLS)\n\t\t\tnum = 4;\n\t\telse if (TP_TeamFortressEngineerSpanner())\n\t\t\tnum = 4;\n\t\telse\n\t\t\tnum = 0;\n\t}\n\n\tSCR_HUD_DrawAmmoIcon(hud, num, scale->value, style->value);\n}\n\nvoid SCR_HUD_DrawAmmoIcon1(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style;\n\tif (scale == NULL)  // first time called\n\t{\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawAmmoIcon(hud, 1, scale->value, style->value);\n\t}\n}\n\nvoid SCR_HUD_DrawAmmoIcon2(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style;\n\tif (scale == NULL)  // first time called\n\t{\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawAmmoIcon(hud, 2, scale->value, style->value);\n\t}\n}\n\nvoid SCR_HUD_DrawAmmoIcon3(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style;\n\tif (scale == NULL)  // first time called\n\t{\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawAmmoIcon(hud, 3, scale->value, style->value);\n\t}\n}\n\nvoid SCR_HUD_DrawAmmoIcon4(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style;\n\tif (scale == NULL)  // first time called\n\t{\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawAmmoIcon(hud, 4, scale->value, style->value);\n\t}\n}\n\nvoid Ammo_HudInit(void)\n{\n\t// ammo/s\n\tHUD_Register(\n\t\t\"ammo\", NULL, \"Part of your inventory - ammo for active weapon.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawAmmoCurrent,\n\t\t\"1\", \"health\", \"after\", \"center\", \"32\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"align\", \"right\",\n\t\t\"digits\", \"3\",\n\t\t\"proportional\", \"0\",\n\t\t\"show_always\", \"0\",\n\t\t\"text_color_low\", \"\",\n\t\t\"text_color_normal\", \"\",\n\t\t NULL\n\t);\n\n\tHUD_Register(\"ammo1\", NULL, \"Part of your inventory - ammo - shells.\",\n\t\t\t\t HUD_INVENTORY, ca_active, 0, SCR_HUD_DrawAmmo1,\n\t\t\t\t \"0\", \"ibar\", \"left\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\t\t \"style\", \"0\",\n\t\t\t\t \"scale\", \"1\",\n\t\t\t\t \"align\", \"right\",\n\t\t\t\t \"digits\", \"3\",\n\t\t\t\t \"proportional\", \"0\",\n\t\t\t\t \"text_color_low\", \"\",\n\t\t\t\t \"text_color_normal\", \"\",\n\t\t\t\t NULL);\n\tHUD_Register(\"ammo2\", NULL, \"Part of your inventory - ammo - nails.\",\n\t\t\t\t HUD_INVENTORY, ca_active, 0, SCR_HUD_DrawAmmo2,\n\t\t\t\t \"0\", \"ammo1\", \"after\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\t\t \"style\", \"0\",\n\t\t\t\t \"scale\", \"1\",\n\t\t\t\t \"align\", \"right\",\n\t\t\t\t \"digits\", \"3\",\n\t\t\t\t \"proportional\", \"0\",\n\t\t\t\t \"text_color_low\", \"\",\n\t\t\t\t \"text_color_normal\", \"\",\n\t\t\t\t NULL);\n\tHUD_Register(\"ammo3\", NULL, \"Part of your inventory - ammo - rockets.\",\n\t\t\t\t HUD_INVENTORY, ca_active, 0, SCR_HUD_DrawAmmo3,\n\t\t\t\t \"0\", \"ammo2\", \"after\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\t\t \"style\", \"0\",\n\t\t\t\t \"scale\", \"1\",\n\t\t\t\t \"align\", \"right\",\n\t\t\t\t \"digits\", \"3\",\n\t\t\t\t \"proportional\", \"0\",\n\t\t\t\t \"text_color_low\", \"\",\n\t\t\t\t \"text_color_normal\", \"\",\n\t\t\t\t NULL);\n\tHUD_Register(\"ammo4\", NULL, \"Part of your inventory - ammo - cells.\",\n\t\t\t\t HUD_INVENTORY, ca_active, 0, SCR_HUD_DrawAmmo4,\n\t\t\t\t \"0\", \"ammo3\", \"after\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\t\t \"style\", \"0\",\n\t\t\t\t \"scale\", \"1\",\n\t\t\t\t \"align\", \"right\",\n\t\t\t\t \"digits\", \"3\",\n\t\t\t\t \"proportional\", \"0\",\n\t\t\t\t \"text_color_low\", \"\",\n\t\t\t\t \"text_color_normal\", \"\",\n\t\t\t\t NULL);\n\n\t// ammo icon/s\n\tHUD_Register(\n\t\t\"iammo\", NULL, \"Part of your inventory - ammo icon.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawAmmoIconCurrent,\n\t\t\"1\", \"ammo\", \"before\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"show_always\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\"iammo1\", NULL, \"Part of your inventory - ammo icon.\",\n\t\t\t\t HUD_INVENTORY, ca_active, 0, SCR_HUD_DrawAmmoIcon1,\n\t\t\t\t \"0\", \"ibar\", \"left\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\t\t \"style\", \"2\",\n\t\t\t\t \"scale\", \"1\",\n\t\t\t\t NULL);\n\tHUD_Register(\"iammo2\", NULL, \"Part of your inventory - ammo icon.\",\n\t\t\t\t HUD_INVENTORY, ca_active, 0, SCR_HUD_DrawAmmoIcon2,\n\t\t\t\t \"0\", \"iammo1\", \"after\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\t\t \"style\", \"2\",\n\t\t\t\t \"scale\", \"1\",\n\t\t\t\t NULL);\n\tHUD_Register(\"iammo3\", NULL, \"Part of your inventory - ammo icon.\",\n\t\t\t\t HUD_INVENTORY, ca_active, 0, SCR_HUD_DrawAmmoIcon3,\n\t\t\t\t \"0\", \"iammo2\", \"after\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\t\t \"style\", \"2\",\n\t\t\t\t \"scale\", \"1\",\n\t\t\t\t NULL);\n\tHUD_Register(\"iammo4\", NULL, \"Part of your inventory - ammo icon.\",\n\t\t\t\t HUD_INVENTORY, ca_active, 0, SCR_HUD_DrawAmmoIcon4,\n\t\t\t\t \"0\", \"iammo3\", \"after\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\t\t \"style\", \"2\",\n\t\t\t\t \"scale\", \"1\",\n\t\t\t\t NULL);\n}"
  },
  {
    "path": "src/hud_armor.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"fonts.h\"\n#include \"teamplay.h\"\n#include \"vx_stuff.h\"\n\nstatic int TP_IsArmorLow(void)\n{\n\textern cvar_t tp_need_ra, tp_need_ya, tp_need_ga;\n\n\tif ((cl.stats[STAT_ARMOR] > 0) && (cl.stats[STAT_ITEMS] & IT_ARMOR3)) {\n\t\treturn cl.stats[STAT_ARMOR] <= tp_need_ra.value;\n\t}\n\tif ((cl.stats[STAT_ARMOR] > 0) && (cl.stats[STAT_ITEMS] & IT_ARMOR2)) {\n\t\treturn cl.stats[STAT_ARMOR] <= tp_need_ya.value;\n\t}\n\tif ((cl.stats[STAT_ARMOR] > 0) && (cl.stats[STAT_ITEMS] & IT_ARMOR1)) {\n\t\treturn cl.stats[STAT_ARMOR] <= tp_need_ga.value;\n\t}\n\treturn 1;\n}\n\nstatic qbool HUD_ArmorLow(void)\n{\n\textern cvar_t hud_tp_need;\n\n\tif (hud_tp_need.value) {\n\t\treturn (TP_IsArmorLow());\n\t}\n\telse {\n\t\treturn (HUD_Stats(STAT_ARMOR) <= 25);\n\t}\n}\n\nstatic void SCR_HUD_DrawArmor(hud_t *hud)\n{\n\tint level;\n\tqbool low;\n\tstatic cvar_t *scale = NULL, *style, *digits, *align, *pent_666, *proportional, *hidezero;\n\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tdigits = HUD_FindVar(hud, \"digits\");\n\t\talign = HUD_FindVar(hud, \"align\");\n\t\tpent_666 = HUD_FindVar(hud, \"pent_666\"); // Show 666 or armor value when carrying pentagram\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t\thidezero = HUD_FindVar(hud, \"hidezero\"); //Hide armor number if zero\n\t}\n\n\tif (HUD_Stats(STAT_HEALTH) > 0) {\n\t\tif ((HUD_Stats(STAT_ITEMS) & IT_INVULNERABILITY) && pent_666->integer) {\n\t\t\tlevel = 666;\n\t\t\tlow = true;\n\t\t}\n\t\telse {\n\t\t\tlevel = HUD_Stats(STAT_ARMOR);\n\t\t\tlow = HUD_ArmorLow();\n\t\t}\n\t}\n\telse {\n\t\tlevel = 0;\n\t\tlow = true;\n\t}\n\tif (level == 0 && hidezero->integer == 1) return;\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawNum(hud, level, low, scale->value, style->value, digits->value, align->string, proportional->integer);\n\t}\n}\n\nstatic void SCR_HUD_DrawArmorIcon(hud_t *hud)\n{\n\textern mpic_t  *sb_armor[3];\n\textern mpic_t  *draw_disc;\n\tint   x, y, height;\n\n\tint style;\n\tfloat scale;\n\n\tstatic cvar_t *v_scale = NULL, *v_style, *v_proportional;\n\tif (v_scale == NULL) {\n\t\t// first time called\n\t\tv_scale = HUD_FindVar(hud, \"scale\");\n\t\tv_style = HUD_FindVar(hud, \"style\");\n\t\tv_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tscale = max(v_scale->value, 0.01);\n\tstyle = (int)(v_style->value);\n\n\theight = (style ? 8 : 24) * scale;\n\n\tif (cl.spectator == cl.autocam) {\n\t\tif (style) {\n\t\t\tint c;\n\n\t\t\tif (!HUD_PrepareDraw(hud, FontFixedWidth(1, scale, 0, v_proportional->integer), height, &x, &y)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (HUD_Stats(STAT_ITEMS) & IT_INVULNERABILITY) {\n\t\t\t\tc = '@';\n\t\t\t}\n\t\t\telse  if (HUD_Stats(STAT_ITEMS) & IT_ARMOR3) {\n\t\t\t\tc = 'r';\n\t\t\t}\n\t\t\telse if (HUD_Stats(STAT_ITEMS) & IT_ARMOR2) {\n\t\t\t\tc = 'y';\n\t\t\t}\n\t\t\telse if (HUD_Stats(STAT_ITEMS) & IT_ARMOR1) {\n\t\t\t\tc = 'g';\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tc += 128;\n\n\t\t\tDraw_SCharacterP(x, y, c, scale, v_proportional->integer);\n\t\t}\n\t\telse {\n\t\t\tmpic_t* pic;\n\n\t\t\tif (!HUD_PrepareDraw(hud, 24 * scale, height, &x, &y)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (HUD_Stats(STAT_ITEMS) & IT_INVULNERABILITY) {\n\t\t\t\tpic = draw_disc;\n\t\t\t}\n\t\t\telse  if (HUD_Stats(STAT_ITEMS) & IT_ARMOR3) {\n\t\t\t\tpic = sb_armor[2];\n\t\t\t}\n\t\t\telse if (HUD_Stats(STAT_ITEMS) & IT_ARMOR2) {\n\t\t\t\tpic = sb_armor[1];\n\t\t\t}\n\t\t\telse if (HUD_Stats(STAT_ITEMS) & IT_ARMOR1) {\n\t\t\t\tpic = sb_armor[0];\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tDraw_SPic(x, y, pic, scale);\n\t\t}\n\t}\n}\n\nstatic void SCR_HUD_DrawArmorDamage(hud_t *hud)\n{\n\tDraw_AMFStatLoss(STAT_ARMOR, hud);\n}\n\nvoid SCR_HUD_DrawBarArmor(hud_t *hud)\n{\n\tstatic\tcvar_t *width = NULL, *height, *direction, *color_noarmor, *color_ga, *color_ga_over, *color_ya, *color_ra, *color_unnatural;\n\tint\t\tx, y;\n\tint\t\tarmor = HUD_Stats(STAT_ARMOR);\n\tqbool\talive = cl.stats[STAT_HEALTH] > 0;\n\n\tif (width == NULL) {\n\t\t// first time called\n\t\twidth = HUD_FindVar(hud, \"width\");\n\t\theight = HUD_FindVar(hud, \"height\");\n\t\tdirection = HUD_FindVar(hud, \"direction\");\n\t\tcolor_noarmor = HUD_FindVar(hud, \"color_noarmor\");\n\t\tcolor_ga_over = HUD_FindVar(hud, \"color_ga_over\");\n\t\tcolor_ga = HUD_FindVar(hud, \"color_ga\");\n\t\tcolor_ya = HUD_FindVar(hud, \"color_ya\");\n\t\tcolor_ra = HUD_FindVar(hud, \"color_ra\");\n\t\tcolor_unnatural = HUD_FindVar(hud, \"color_unnatural\");\n\t}\n\n\tif (HUD_PrepareDraw(hud, width->integer, height->integer, &x, &y) && (cl.spectator == cl.autocam)) {\n\t\tif (!width->integer || !height->integer) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (HUD_Stats(STAT_ITEMS) & IT_INVULNERABILITY && alive) {\n\t\t\tSCR_HUD_DrawBar(direction->integer, 100, 100.0, color_unnatural->color, x, y, width->integer, height->integer);\n\t\t}\n\t\telse if (HUD_Stats(STAT_ITEMS) & IT_ARMOR3 && alive) {\n\t\t\tSCR_HUD_DrawBar(direction->integer, 100, 100.0, color_noarmor->color, x, y, width->integer, height->integer);\n\t\t\tSCR_HUD_DrawBar(direction->integer, armor, 200.0, color_ra->color, x, y, width->integer, height->integer);\n\t\t}\n\t\telse if (HUD_Stats(STAT_ITEMS) & IT_ARMOR2 && alive) {\n\t\t\tSCR_HUD_DrawBar(direction->integer, 100, 100.0, color_noarmor->color, x, y, width->integer, height->integer);\n\t\t\tSCR_HUD_DrawBar(direction->integer, armor, 150.0, color_ya->color, x, y, width->integer, height->integer);\n\t\t}\n\t\telse if (HUD_Stats(STAT_ITEMS) & IT_ARMOR1 && alive) {\n\t\t\tif (armor > 100) {\n\t\t\t\tSCR_HUD_DrawBar(direction->integer, 100, 100.0, color_ga->color, x, y, width->integer, height->integer);\n\t\t\t\tSCR_HUD_DrawBar(direction->integer, armor - 100.0, 100.0, color_ga_over->color, x, y, width->integer, height->integer);\n\t\t\t} else {\n\t\t\t\tSCR_HUD_DrawBar(direction->integer, 100, 100.0, color_noarmor->color, x, y, width->integer, height->integer);\n\t\t\t\tSCR_HUD_DrawBar(direction->integer, armor, 100.0, color_ga->color, x, y, width->integer, height->integer);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tSCR_HUD_DrawBar(direction->integer, 100, 100.0, color_noarmor->color, x, y, width->integer, height->integer);\n\t\t}\n\t}\n}\n\nvoid Armor_HudInit(void)\n{\n\t// armor count\n\tHUD_Register(\n\t\t\"armor\", NULL, \"Part of your inventory - armor level.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawArmor,\n\t\t\"1\", \"face\", \"before\", \"center\", \"-32\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"align\", \"right\",\n\t\t\"digits\", \"3\",\n\t\t\"pent_666\", \"1\",  // Show 666 instead of armor value\n\t\t\"proportional\", \"0\",\n\t\t\"hidezero\", \"0\", // Hide armor number if 0\n\t\tNULL\n\t);\n\n\t// armor icon\n\tHUD_Register(\n\t\t\"iarmor\", NULL, \"Part of your inventory - armor icon.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawArmorIcon,\n\t\t\"1\", \"armor\", \"before\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\t// armordamage\n\tHUD_Register(\n\t\t\"armordamage\", NULL, \"Shows amount of damage done to your armour.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawArmorDamage,\n\t\t\"0\", \"armor\", \"left\", \"before\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"align\", \"right\",\n\t\t\"digits\", \"3\",\n\t\t\"duration\", \"0.8\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\n\t\t\"bar_armor\", NULL, \"Armor bar.\",\n\t\tHUD_PLUSMINUS, ca_active, 0, SCR_HUD_DrawBarArmor,\n\t\t\"0\", \"armor\", \"left\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"height\", \"16\",\n\t\t\"width\", \"64\",\n\t\t\"direction\", \"1\",\n\t\t\"color_noarmor\", \"128 128 128 64\",\n\t\t\"color_ga_over\", \"48 160 0 128\",\n\t\t\"color_ga\", \"32 128 0 128\",\n\t\t\"color_ya\", \"192 128 0 128\",\n\t\t\"color_ra\", \"128 0 0 128\",\n\t\t\"color_unnatural\", \"255 255 255 128\",\n\t\tNULL\n\t);\n}\n"
  },
  {
    "path": "src/hud_autoid.c",
    "content": "/*\nCopyright (C) 1996-2003 Id Software, Inc., A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: cl_screen.c,v 1.156 2007-10-29 00:56:47 qqshka Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"fonts.h\"\n#include \"r_matrix.h\"\n#include \"r_local.h\"\n\n/*********************************** AUTOID ***********************************/\n\n// Not static so it can be toggled in menu\ncvar_t scr_autoid                                  = { \"scr_autoid\", \"5\" };\nstatic cvar_t scr_autoid_weapons                   = { \"scr_autoid_weapons\", \"2\" };\nstatic cvar_t scr_autoid_namelength                = { \"scr_autoid_namelength\", \"0\" };\nstatic cvar_t scr_autoid_barlength                 = { \"scr_autoid_barlength\", \"16\" };\nstatic cvar_t scr_autoid_weaponicon                = { \"scr_autoid_weaponicon\", \"1\" };\nstatic cvar_t scr_autoid_scale                     = { \"scr_autoid_scale\", \"1\" };\nstatic cvar_t scr_autoid_proportional              = { \"scr_autoid_proportional\", \"0\" };\n\n// These aren't static as they're also used for multiview hud\ncvar_t scr_autoid_healthbar_bg_color               = { \"scr_autoid_healthbar_bg_color\", \"180 115 115 100\", CVAR_COLOR };\ncvar_t scr_autoid_healthbar_normal_color           = { \"scr_autoid_healthbar_normal_color\", \"80 0 0 255\", CVAR_COLOR };\ncvar_t scr_autoid_healthbar_mega_color             = { \"scr_autoid_healthbar_mega_color\", \"255 0 0 255\", CVAR_COLOR };\ncvar_t scr_autoid_healthbar_two_mega_color         = { \"scr_autoid_healthbar_two_mega_color\", \"255 100 0 255\", CVAR_COLOR };\ncvar_t scr_autoid_healthbar_unnatural_color        = { \"scr_autoid_healthbar_unnatural_color\", \"255 255 255 255\", CVAR_COLOR };\n\nstatic cvar_t scr_autoid_armorbar_green_armor      = { \"scr_autoid_armorbar_green_armor\", \"25 170 0 255\", CVAR_COLOR };\nstatic cvar_t scr_autoid_armorbar_yellow_armor     = { \"scr_autoid_armorbar_yellow_armor\", \"255 220 0 255\", CVAR_COLOR };\nstatic cvar_t scr_autoid_armorbar_red_armor        = { \"scr_autoid_armorbar_red_armor\", \"255 0 0 255\", CVAR_COLOR };\n\ntypedef struct player_autoid_s {\n\tfloat x, y;\n\tplayer_info_t* player;\n\n\t// antilag indicators (debugging antilag queries)\n\tfloat rewind_x1, rewind_y1; // as rewound by server\n\tfloat rewind_x2, rewind_y2; // current position, for rewound line\n\tfloat client_x1, client_y1; // as claimed by the client\n\tfloat client_x2, client_y2; // current position, for client line\n\tqbool rewind_valid, client_valid;\n} autoid_player_t;\n\n// For 2-pass multiview... [<view-num>][<other-player>]\nstatic autoid_player_t saved_autoids[4][MAX_CLIENTS];\nstatic int saved_autoid_count[4];\n\nstatic autoid_player_t autoids[MAX_CLIENTS];\nstatic int autoid_count;\n\n#define AUTOID_HEALTHBAR_OFFSET_Y\t\t\t16\n\n#define AUTOID_ARMORBAR_OFFSET_Y\t\t\t(AUTOID_HEALTHBAR_OFFSET_Y + 5)\n#define AUTOID_ARMORNAME_OFFSET_Y\t\t\t(AUTOID_ARMORBAR_OFFSET_Y + 8 + 2)\n#define AUTOID_ARMORNAME_OFFSET_X\t\t\t8\n\n#define AUTOID_WEAPON_OFFSET_Y\t\t\t\tAUTOID_HEALTHBAR_OFFSET_Y\n#define AUTOID_WEAPON_OFFSET_X\t\t\t\t2\n\nvoid SCR_SaveAutoID(void)\n{\n\tint view_num = CL_MultiviewCurrentView() - 1;\n\n\tif (view_num >= 0 && view_num < 4) {\n\t\tmemcpy(saved_autoids[view_num], autoids, sizeof(saved_autoids[view_num]));\n\t\tsaved_autoid_count[view_num] = autoid_count;\n\t}\n}\n\nvoid SCR_RestoreAutoID(void)\n{\n\tint view_num = CL_MultiviewCurrentView() - 1;\n\n\tif (view_num >= 0 && view_num < 4) {\n\t\tmemcpy(autoids, saved_autoids[view_num], sizeof(autoids));\n\t\tautoid_count = saved_autoid_count[view_num];\n\t}\n}\n\nvoid SCR_SetupAutoID(void)\n{\n\tint j, tracknum = -1;\n\tfloat winz, *origin;\n\tplayer_state_t *state;\n\tplayer_info_t *info;\n\tcentity_t *cent;\n\titem_vis_t visitem;\n\tautoid_player_t *id;\n\n\tautoid_count = 0;\n\n\tif (!scr_autoid.value) {\n\t\treturn;\n\t}\n\n\tif (cls.state != ca_active || !cl.validsequence || cl.intermission) {\n\t\treturn;\n\t}\n\n\tif (!cls.demoplayback && !cl.spectator) {\n\t\treturn;\n\t}\n\n\tif (cl.spectator) {\n\t\ttracknum = Cam_TrackNum();\n\t}\n\n\tVectorCopy(vpn, visitem.forward);\n\tVectorCopy(vright, visitem.right);\n\tVectorCopy(vup, visitem.up);\n\tVectorCopy(r_origin, visitem.vieworg);\n\n\tstate = cl.frames[cl.parsecount & UPDATE_MASK].playerstate;\n\tinfo = cl.players;\n\tcent = &cl_entities[1];\n\n\tfor (j = 0; j < MAX_CLIENTS; j++, info++, state++, cent++) {\n\t\tif (state->messagenum != cl.parsecount || j == cl.playernum || j == tracknum || info->spectator) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ((state->modelindex == cl_modelindices[mi_player] && ISDEAD(state->frame)) ||\n\t\t\tstate->modelindex == cl_modelindices[mi_h_player]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\torigin = cent->lerp_origin;\n\n\t\t// FIXME: In multiview, this will detect some players being outside of the view even though\n\t\t// he's visible on screen, this only happens in some cases.\n\t\tif (R_CullSphere(origin, 0)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tVectorCopy(origin, visitem.entorg);\n\t\tvisitem.entorg[2] += 27;\n\t\tVectorSubtract(visitem.entorg, visitem.vieworg, visitem.dir);\n\t\tvisitem.dist = DotProduct(visitem.dir, visitem.forward);\n\t\tvisitem.radius = 25;\n\n\t\tif (!TP_IsItemVisible(&visitem)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tid = &autoids[autoid_count];\n\t\tid->player = info;\n\t\tif (R_Project3DCoordinates(origin[0], origin[1], origin[2] + 28, &id->x, &id->y, &winz)) {\n\t\t\tautoid_count++;\n\n\t\t\tid->rewind_valid = id->client_valid = false;\n\t\t\tif (state->antilag_flags & dbg_antilag_rewind_present) {\n\t\t\t\tid->rewind_valid = R_Project3DCoordinates(state->rewind_origin[0], state->rewind_origin[1], state->rewind_origin[2] + 16, &id->rewind_x1, &id->rewind_y1, &winz);\n\t\t\t\tid->rewind_valid &= R_Project3DCoordinates(origin[0], origin[1], origin[2] + 16, &id->rewind_x2, &id->rewind_y2, &winz);\n\t\t\t}\n\t\t\tif (state->antilag_flags & dbg_antilag_client_present) {\n\t\t\t\tid->client_valid = R_Project3DCoordinates(state->client_origin[0], state->client_origin[1], state->client_origin[2] + 18, &id->client_x1, &id->client_y1, &winz);\n\t\t\t\tid->client_valid &= R_Project3DCoordinates(origin[0], origin[1], origin[2] + 18, &id->client_x2, &id->client_y2, &winz);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void SCR_DrawAutoIDStatus(autoid_player_t *autoid_p, int x, int y, float scale, qbool proportional)\n{\n\tchar armor_name[20];\n\tchar weapon_name[20];\n\tint bar_length;\n\n\tif (scr_autoid_barlength.integer > 0) {\n\t\tbar_length = scr_autoid_barlength.integer;\n\t}\n\telse if (scr_autoid_namelength.integer >= 1) {\n\t\tfloat fixed = FontFixedWidth(scr_autoid_namelength.integer, false, scale, proportional);\n\t\tfloat particular = Draw_StringLength(autoid_p->player->name, -1, scale, proportional);\n\n\t\tbar_length = min(fixed, particular) * 0.5;\n\t}\n\telse {\n\t\tbar_length = Draw_StringLength(autoid_p->player->name, -1, scale, proportional) * 0.5;\n\t}\n\n\t// Draw health above the name.\n\tif (scr_autoid.integer >= 2) {\n\t\tint health;\n\t\tint health_length;\n\n\t\thealth = autoid_p->player->stats[STAT_HEALTH];\n\t\thealth = min(100, health);\n\t\thealth_length = Q_rint((bar_length / 100.0) * health);\n\n\t\t// Normal health.\n\t\tDraw_AlphaFillRGB(x - bar_length * scale, y - AUTOID_HEALTHBAR_OFFSET_Y * scale, bar_length * 2 * scale, 4 * scale, RGBAVECT_TO_COLOR(scr_autoid_healthbar_bg_color.color));\n\t\tDraw_AlphaFillRGB(x - bar_length * scale, y - AUTOID_HEALTHBAR_OFFSET_Y * scale, health_length * 2 * scale, 4 * scale, RGBAVECT_TO_COLOR(scr_autoid_healthbar_normal_color.color));\n\n\t\thealth = autoid_p->player->stats[STAT_HEALTH];\n\n\t\t// Mega health\n\t\tif (health > 100 && health <= 200) {\n\t\t\thealth_length = Q_rint((bar_length / 100.0) * (health - 100));\n\t\t\tDraw_AlphaFillRGB(x - bar_length * scale, y - AUTOID_HEALTHBAR_OFFSET_Y * scale, health_length * 2 * scale, 4 * scale, RGBAVECT_TO_COLOR(scr_autoid_healthbar_mega_color.color));\n\t\t}\n\t\telse if (health > 200 && health <= 250) {\n\t\t\t// Super health.\n\t\t\thealth_length = Q_rint((bar_length / 100.0) * (health - 200));\n\t\t\tDraw_AlphaFillRGB(x - bar_length * scale, y - AUTOID_HEALTHBAR_OFFSET_Y * scale, bar_length * 2 * scale, 4 * scale, RGBAVECT_TO_COLOR(scr_autoid_healthbar_mega_color.color));\n\t\t\tDraw_AlphaFillRGB(x - bar_length * scale, y - AUTOID_HEALTHBAR_OFFSET_Y * scale, health_length * 2 * scale, 4 * scale, RGBAVECT_TO_COLOR(scr_autoid_healthbar_two_mega_color.color));\n\t\t}\n\t\telse if (health > 250) {\n\t\t\t// Crazy health.\n\t\t\t// This will never happen during a normal game.\n\t\t\tDraw_AlphaFillRGB(x - bar_length * scale, y - AUTOID_HEALTHBAR_OFFSET_Y * scale, bar_length * 2 * scale, 4 * scale, RGBAVECT_TO_COLOR(scr_autoid_healthbar_unnatural_color.color));\n\t\t}\n\t}\n\n\t// Draw armor.\n\tif (scr_autoid.integer >= 2) {\n\t\tint armor;\n\t\tint armor_length;\n\t\tcvar_t* armor_color = NULL;\n\n\t\tarmor = autoid_p->player->stats[STAT_ARMOR];\n\t\tarmor = min(100, armor);\n\t\tarmor_length = Q_rint((bar_length / 100.0) * armor);\n\n\t\tif (autoid_p->player->stats[STAT_ITEMS] & IT_ARMOR1) {\n\t\t\tarmor_color = &scr_autoid_armorbar_green_armor;\n\t\t}\n\t\telse if (autoid_p->player->stats[STAT_ITEMS] & IT_ARMOR2) {\n\t\t\tarmor_color = &scr_autoid_armorbar_yellow_armor;\n\t\t}\n\t\telse if (autoid_p->player->stats[STAT_ITEMS] & IT_ARMOR3) {\n\t\t\tarmor_color = &scr_autoid_armorbar_red_armor;\n\t\t}\n\n\t\tif (armor_color != NULL) {\n\t\t\tcolor_t background = RGBA_TO_COLOR(armor_color->color[0], armor_color->color[1], armor_color->color[2], 50);\n\t\t\tcolor_t foreground = RGBAVECT_TO_COLOR(armor_color->color);\n\n\t\t\tDraw_AlphaFillRGB(x - bar_length * scale, y - AUTOID_ARMORBAR_OFFSET_Y * scale, bar_length * 2 * scale, 4 * scale, background);\n\t\t\tDraw_AlphaFillRGB(x - bar_length * scale, y - AUTOID_ARMORBAR_OFFSET_Y * scale, armor_length * 2 * scale, 4 * scale, foreground);\n\t\t}\n\t}\n\n\t// Draw the name of the armor type.\n\tif (scr_autoid.integer >= 3 && scr_autoid.integer < 6 && autoid_p->player->stats[STAT_ITEMS] & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) {\n\t\tif (autoid_p->player->stats[STAT_ITEMS] & IT_ARMOR1) {\n\t\t\tstrlcpy(armor_name, \"&c0f0GA\", sizeof(armor_name));\n\t\t}\n\t\telse if (autoid_p->player->stats[STAT_ITEMS] & IT_ARMOR2) {\n\t\t\tstrlcpy(armor_name, \"&cff0YA\", sizeof(armor_name));\n\t\t}\n\t\telse {\n\t\t\tstrlcpy(armor_name, \"&cf00RA\", sizeof(armor_name));\n\t\t}\n\n\t\tDraw_SColoredStringBasic(\n\t\t\tx - AUTOID_ARMORNAME_OFFSET_X * scale,\n\t\t\ty - AUTOID_ARMORNAME_OFFSET_Y * scale,\n\t\t\tarmor_name, 0, scale, proportional\n\t\t);\n\t}\n\n\tif (scr_autoid_weapons.integer > 0 && (scr_autoid.integer >= 4)) {\n\t\t// Draw the players weapon.\n\t\tint best_weapon = -1;\n\t\textern mpic_t *sb_weapons[7][8];\n\t\tmpic_t *weapon_pic = NULL;\n\n\t\tbest_weapon = BestWeaponFromStatItems(autoid_p->player->stats[STAT_ITEMS]);\n\n\t\tswitch (best_weapon) {\n\t\t\tcase IT_SHOTGUN:\n\t\t\t\tweapon_pic = sb_weapons[0][0];\n\t\t\t\tstrlcpy(weapon_name, \"SG\", sizeof(weapon_name));\n\t\t\t\tbreak;\n\t\t\tcase IT_SUPER_SHOTGUN:\n\t\t\t\tweapon_pic = sb_weapons[0][1];\n\t\t\t\tstrlcpy(weapon_name, \"BS\", sizeof(weapon_name));\n\t\t\t\tbreak;\n\t\t\tcase IT_NAILGUN:\n\t\t\t\tweapon_pic = sb_weapons[0][2];\n\t\t\t\tstrlcpy(weapon_name, \"NG\", sizeof(weapon_name));\n\t\t\t\tbreak;\n\t\t\tcase IT_SUPER_NAILGUN:\n\t\t\t\tweapon_pic = sb_weapons[0][3];\n\t\t\t\tstrlcpy(weapon_name, \"SN\", sizeof(weapon_name));\n\t\t\t\tbreak;\n\t\t\tcase IT_GRENADE_LAUNCHER:\n\t\t\t\tweapon_pic = sb_weapons[0][4];\n\t\t\t\tstrlcpy(weapon_name, \"GL\", sizeof(weapon_name));\n\t\t\t\tbreak;\n\t\t\tcase IT_ROCKET_LAUNCHER:\n\t\t\t\tweapon_pic = sb_weapons[0][5];\n\t\t\t\tstrlcpy(weapon_name, \"RL\", sizeof(weapon_name));\n\t\t\t\tbreak;\n\t\t\tcase IT_LIGHTNING:\n\t\t\t\tweapon_pic = sb_weapons[0][6];\n\t\t\t\tstrlcpy(weapon_name, \"LG\", sizeof(weapon_name));\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t// No weapon.\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (weapon_pic != NULL && best_weapon > 0 && ((scr_autoid.integer == 4 && best_weapon == 7) || (scr_autoid.integer > 4 && best_weapon >= scr_autoid_weapons.integer))) {\n\t\t\tif (scr_autoid_weaponicon.value) {\n\t\t\t\tDraw_SSubPic(\n\t\t\t\t\tx - (bar_length + weapon_pic->width + AUTOID_WEAPON_OFFSET_X) * scale,\n\t\t\t\t\ty - (AUTOID_HEALTHBAR_OFFSET_Y + Q_rint((weapon_pic->height / 2.0))) * scale,\n\t\t\t\t\tweapon_pic,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t\tweapon_pic->width,\n\t\t\t\t\tweapon_pic->height,\n\t\t\t\t\tscale\n\t\t\t\t);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SColoredStringBasic(\n\t\t\t\t\tx - (bar_length + 16 + AUTOID_WEAPON_OFFSET_X) * scale,\n\t\t\t\t\ty - (AUTOID_HEALTHBAR_OFFSET_Y + 4) * scale,\n\t\t\t\t\tweapon_name, 1, scale, proportional\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid SCR_DrawAntilagIndicators(void)\n{\n\tint i;\n\textern cvar_t cl_debug_antilag_lines;\n\textern cvar_t cl_debug_antilag_view;\n\n\tif (!cl_debug_antilag_lines.integer || (!cls.demoplayback && !cl.spectator) || cl.intermission) {\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < autoid_count; ++i) {\n\t\tcolor_t r_color = RGBA_TO_COLOR(255, 0, 0, 255);\n\t\tcolor_t c_color = RGBA_TO_COLOR(0, 255, 0, 255);\n\t\tcolor_t rc_color = RGBA_TO_COLOR(0, 0, 255, 255);\n\t\tfloat r_x1 = autoids[i].rewind_x1 * vid.width / glwidth;\n\t\tfloat r_y1 = (glheight - autoids[i].rewind_y1) * vid.height / glheight;\n\t\tfloat r_x2 = autoids[i].rewind_x2 * vid.width / glwidth;\n\t\tfloat r_y2 = (glheight - autoids[i].rewind_y2) * vid.height / glheight;\n\t\tfloat c_x1 = autoids[i].client_x1 * vid.width / glwidth;\n\t\tfloat c_y1 = (glheight - autoids[i].client_y1) * vid.height / glheight;\n\t\tfloat c_x2 = autoids[i].client_x2 * vid.width / glwidth;\n\t\tfloat c_y2 = (glheight - autoids[i].client_y2) * vid.height / glheight;\n\n\t\tif (cl_debug_antilag_view.integer == 0) {\n\t\t\t// player shown in current server position, draw from rewound & client\n\t\t\tif (autoids[i].rewind_valid) {\n\t\t\t\tDraw_AlphaLineRGB(r_x1, r_y1, r_x2, r_y2, 2, r_color);\n\t\t\t}\n\t\t\tif (autoids[i].client_valid) {\n\t\t\t\tDraw_AlphaLineRGB(c_x1, c_y1, c_x2, c_y2, 2, c_color);\n\t\t\t}\n\t\t}\n\t\telse if (cl_debug_antilag_view.integer == 1) {\n\t\t\t// player shown in rewound position, draw from current & client\n\t\t\tif (autoids[i].rewind_valid) {\n\t\t\t\tDraw_AlphaLineRGB(r_x1, r_y1, r_x2, r_y2, 2, r_color);\n\t\t\t}\n\t\t\tif (autoids[i].rewind_valid && autoids[i].client_valid) {\n\t\t\t\tDraw_AlphaLineRGB(c_x1, c_y1, r_x1, r_y1, 2, rc_color);\n\t\t\t}\n\t\t}\n\t\telse if (cl_debug_antilag_view.integer == 2) {\n\t\t\t// player shown in client position, draw to current & rewound\n\t\t\tif (autoids[i].rewind_valid && autoids[i].client_valid) {\n\t\t\t\tDraw_AlphaLineRGB(r_x1, r_y1, c_x1, c_y1, 2, rc_color);\n\t\t\t}\n\t\t\tif (autoids[i].client_valid) {\n\t\t\t\tDraw_AlphaLineRGB(c_x1, c_y1, c_x2, c_y2, 2, c_color);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid SCR_DrawAutoID(void)\n{\n\tint i, x, y;\n\tfloat scale;\n\tqbool proportional = scr_autoid_proportional.integer;\n\n\tif (!scr_autoid.value || (!cls.demoplayback && !cl.spectator) || cl.intermission) {\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < autoid_count; i++) {\n\t\tx = autoids[i].x * vid.width / glwidth;\n\t\ty = (glheight - autoids[i].y) * vid.height / glheight;\n\t\tscale = (scr_autoid_scale.value > 0 ? scr_autoid_scale.value : 1.0);\n\n\t\tif (scr_autoid.integer) {\n\t\t\tif (scr_autoid_namelength.integer >= 1 && scr_autoid_namelength.integer < MAX_SCOREBOARDNAME) {\n\t\t\t\tchar name[MAX_SCOREBOARDNAME];\n\n\t\t\t\tstrlcpy(name, autoids[i].player->name, sizeof(name));\n\t\t\t\tname[scr_autoid_namelength.integer] = 0;\n\t\t\t\tDraw_SString(x - Draw_StringLength(name, -1, 0.5 * scale, proportional), y - 8 * scale, name, scale, proportional);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SString(x - Draw_StringLength(autoids[i].player->name, -1, 0.5 * scale, proportional), y - 8 * scale, autoids[i].player->name, scale, proportional);\n\t\t\t}\n\t\t}\n\n\t\t// We only have health/armor info for all players when in demo playback.\n\t\tif (cls.mvdplayback && scr_autoid.value >= 2) {\n\t\t\tSCR_DrawAutoIDStatus(&autoids[i], x, y, scale, proportional);\n\t\t}\n\t}\n}\n\nvoid SCR_RegisterAutoIDCvars(void)\n{\n\tif (!host_initialized) {\n\t\tCvar_Register(&scr_autoid);\n\t\tCvar_Register(&scr_autoid_weapons);\n\t\tCvar_Register(&scr_autoid_namelength);\n\t\tCvar_Register(&scr_autoid_barlength);\n\t\tCvar_Register(&scr_autoid_weaponicon);\n\t\tCvar_Register(&scr_autoid_scale);\n\t\tCvar_Register(&scr_autoid_healthbar_bg_color);\n\t\tCvar_Register(&scr_autoid_healthbar_normal_color);\n\t\tCvar_Register(&scr_autoid_healthbar_mega_color);\n\t\tCvar_Register(&scr_autoid_healthbar_two_mega_color);\n\t\tCvar_Register(&scr_autoid_healthbar_unnatural_color);\n\t\tCvar_Register(&scr_autoid_proportional);\n\n\t\tCvar_Register(&scr_autoid_armorbar_green_armor);\n\t\tCvar_Register(&scr_autoid_armorbar_yellow_armor);\n\t\tCvar_Register(&scr_autoid_armorbar_red_armor);\n\t}\n}\n"
  },
  {
    "path": "src/hud_centerprint.c",
    "content": "/*\nCopyright (C) 1996-2003 Id Software, Inc., A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: cl_screen.c,v 1.156 2007-10-29 00:56:47 qqshka Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"keys.h\"\n#include \"menu.h\"\n#include \"hud.h\"\n\nstatic cvar_t scr_centertime  = { \"scr_centertime\",  \"2\" };\nstatic cvar_t scr_centershift = { \"scr_centershift\", \"0\" };\nstatic cvar_t scr_centerspeed = { \"scr_centerspeed\", \"8\" };\n\n/**************************** CENTER PRINTING ********************************/\n\nstatic char\t scr_centerstring_lines[1024][41];\n\nstatic float scr_centertime_start;   // for slow victory printing\nstatic float scr_centertime_off;\nstatic int   scr_center_lines;\nstatic int   scr_erase_lines;\nstatic int   scr_erase_center;\n\nvoid SCR_CenterPrint_Clear(void)\n{\n\t// Make sure no centerprint messages are left from previous level.\n\tscr_centertime_off = 0;\n\tmemset(scr_centerstring_lines, 0, sizeof(scr_centerstring_lines));\n}\n\nvoid SCR_CenterPrint_Init(void)\n{\n\tif (!host_initialized) {\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\t\tCvar_Register(&scr_centertime);\n\t\tCvar_Register(&scr_centershift);\n\t\tCvar_Register(&scr_centerspeed);\n\t\tCvar_ResetCurrentGroup();\n\n\t\tCmd_AddLegacyCommand(\"scr_printspeed\", \"scr_centerspeed\");\n\t}\n}\n\n// Called for important messages that should stay in the center of the screen for a few moments\nvoid SCR_CenterPrint(const char *str)\n{\n\tscr_centertime_off = scr_centertime.value;\n\tscr_centertime_start = cl.time;\n\tmemset(scr_centerstring_lines, 0, sizeof(scr_centerstring_lines));\n\n\t// count the number of lines for centering\n\tscr_center_lines = 0;\n\twhile (*str) {\n\t\tconst char* endl = strchr(str, '\\n');\n\t\tif (!endl) {\n\t\t\tstrlcpy(scr_centerstring_lines[scr_center_lines], str, sizeof(scr_centerstring_lines[scr_center_lines]));\n\t\t\t++scr_center_lines;\n\t\t\tbreak;\n\t\t}\n\t\telse {\n\t\t\tint len = endl - str;\n\t\t\tlen = min(len, sizeof(scr_centerstring_lines[scr_center_lines]) - 1);\n\t\t\tstrlcpy(scr_centerstring_lines[scr_center_lines], str, sizeof(scr_centerstring_lines[scr_center_lines]));\n\t\t\tscr_centerstring_lines[scr_center_lines][len] = '\\0';\n\t\t\t++scr_center_lines;\n\n\t\t\tstr = endl + 1;\n\t\t}\n\t}\n}\n\nstatic void SCR_DrawCenterString(float x, float y, float scale, qbool proportional, float speed)\n{\n\t// the finale prints the characters one at a time\n\tint remaining = cl.intermission ? speed * (cl.time - scr_centertime_start) : -1;\n\tint l;\n\tfloat max_width = (sizeof(scr_centerstring_lines[l]) - 1) * 8 * scale;\n\n\tscale = max(scale, 0.1);\n\tscr_erase_center = 0;\n\tif (remaining == 0) {\n\t\treturn;\n\t}\n\n\tfor (l = 0; l < scr_center_lines; ++l) {\n\t\tif (remaining >= 0 && remaining < strlen(scr_centerstring_lines[l])) {\n\t\t\t// Can't use standard centering here... we center to the full line, we might print less than that...\n\t\t\tchar temp[1024];\n\t\t\tint len;\n\n\t\t\tstrlcpy(temp, scr_centerstring_lines[l], sizeof(temp));\n\t\t\ttemp[remaining] = '\\0';\n\t\t\tlen = Draw_StringLength(scr_centerstring_lines[l], -1, scale, proportional);\n\n\t\t\tDraw_SString(x + (max_width - len) / 2, y, temp, scale, proportional);\n\t\t}\n\t\telse {\n\t\t\tDraw_SStringAligned(x, y, scr_centerstring_lines[l], scale, 1.0f, proportional, text_align_center, x + max_width);\n\t\t}\n\n\t\tif (remaining >= 0) {\n\t\t\tremaining -= strlen(scr_centerstring_lines[l]);\n\t\t\tif (remaining <= 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\ty += 8 * scale;\n\t}\n}\n\nstatic qbool SCR_CheckDrawCenterString(void)\n{\n\tscr_copytop = 1;\n\tif (scr_center_lines > scr_erase_lines) {\n\t\tscr_erase_lines = scr_center_lines;\n\t}\n\n\tif (!scr_center_lines) {\n\t\treturn false;\n\t}\n\n\tscr_centertime_off -= cls.frametime;\n\tif (scr_centertime_off <= 0 && !cl.intermission) {\n\t\treturn false;\n\t}\n\n\t// condition says: \"Draw center string only when in game or in proxy menu, otherwise leave.\"\n\tif (key_dest != key_game && ((key_dest != key_menu) || (m_state != m_proxy))) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid SCR_CenterString_Draw(void)\n{\n\tfloat y;\n\tfloat max_width;\n\tfloat scale = 1.0f;\n\tqbool proportional = false;\n\textern cvar_t scr_newHud;\n\tstatic cvar_t* hud_draw = NULL;\n\n\tif (!SCR_CheckDrawCenterString()) {\n\t\treturn;\n\t}\n\n\tif (hud_draw == NULL) {\n\t\thud_draw = Cvar_Find(\"hud_centerprint_show\");\n\t}\n\n\tif (scr_newHud.integer > 0 && hud_draw && hud_draw->integer) {\n\t\treturn;\n\t}\n\n\t// shift all centerprint but not proxy menu - more user-friendly way\n\ty = ((scr_center_lines <= 4) ? vid.height * 0.35 : 48);\n\tif (m_state != m_proxy) {\n\t\ty += scr_centershift.value * 8 * scale;\n\t}\n\tmax_width = (sizeof(scr_centerstring_lines[0]) - 1) * 8 * scale;\n\n\tSCR_DrawCenterString((vid.width - max_width) / 2, y, scale, proportional, max(scr_centerspeed.value, 1));\n}\n\nvoid SCR_EraseCenterString(void)\n{\n\tint y;\n\n\tif (scr_erase_center++ > vid.numpages) {\n\t\tscr_erase_lines = 0;\n\t\treturn;\n\t}\n\n\ty = (scr_center_lines <= 4) ? vid.height * 0.35 : 48;\n\n\tscr_copytop = 1;\n\tDraw_TileClear(0, y, vid.width, min(8 * scr_erase_lines, vid.height - y - 1));\n}\n\nstatic void SCR_HUD_DrawCenterPrint(hud_t* hud)\n{\n\tint x = 0, y = 0;\n\tfloat width = 0, height = 0;\n\n\tstatic cvar_t\n\t\t*hud_scale = NULL,\n\t\t*hud_proportional,\n\t\t*hud_speed;\n\n\tif (!SCR_CheckDrawCenterString()) {\n\t\treturn;\n\t}\n\n\tif (!hud_scale) {\n\t\thud_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_proportional = HUD_FindVar(hud, \"proportional\");\n\t\thud_speed = HUD_FindVar(hud, \"speed\");\n\t}\n\n\twidth = 40 * 8 * hud_scale->value;\n\theight = 12 * 8 * hud_scale->value;\n\n\tif (height > 0 && width > 0 && HUD_PrepareDraw(hud, ceil(width), ceil(height), &x, &y)) {\n\t\tSCR_DrawCenterString(x, y, hud_scale->value, hud_proportional->value, max(hud_speed->integer, 1));\n\t}\n}\n\nvoid CenterPrint_HudInit(void)\n{\n\tHUD_Register(\n\t\t\"centerprint\", NULL, \"Shows alerts from server, countdowns etc.\",\n\t\tHUD_PLUSMINUS | HUD_ON_FINALE, ca_active, 0, SCR_HUD_DrawCenterPrint,\n\t\t\"0\", \"screen\", \"center\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\t\"speed\", \"8\",\n\t\tNULL\n\t);\n}\n"
  },
  {
    "path": "src/hud_clock.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// HUD: clock, democlock, gameclock etc\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"utils.h\"\n#include \"fonts.h\"\n\nvoid SCR_DrawBigClock(int x, int y, int style, int blink, float scale, const char *t);\nvoid SCR_DrawSmallClock(int x, int y, int style, int blink, float scale, const char *t, qbool proportional);\n\nextern cvar_t scr_newHud;\n\nstatic const char *SCR_HUD_ClockFormat(int format)\n{\n\tswitch (format) {\n\t\tcase 1: return \"%I:%M %p\";\n\t\tcase 2: return \"%I:%M:%S %p\";\n\t\tcase 3: return \"%H:%M\";\n\t\tdefault: case 0: return \"%H:%M:%S\";\n\t}\n}\n\n//---------------------\n//\n// draw HUD clock\n//\nstatic void SCR_HUD_DrawClock(hud_t *hud)\n{\n\tint width, height;\n\tint x, y;\n\tconst char *t;\n\n\tstatic cvar_t\n\t\t*hud_clock_big = NULL,\n\t\t*hud_clock_style,\n\t\t*hud_clock_blink,\n\t\t*hud_clock_scale,\n\t\t*hud_clock_format,\n\t\t*hud_clock_proportional,\n\t\t*hud_clock_content;\n\n\tif (hud_clock_big == NULL) {\n\t\t// first time\n\t\thud_clock_big = HUD_FindVar(hud, \"big\");\n\t\thud_clock_style = HUD_FindVar(hud, \"style\");\n\t\thud_clock_blink = HUD_FindVar(hud, \"blink\");\n\t\thud_clock_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_clock_format = HUD_FindVar(hud, \"format\");\n\t\thud_clock_proportional = HUD_FindVar(hud, \"proportional\");\n\t\thud_clock_content = HUD_FindVar(hud, \"content\");\n\t}\n\n\tif (hud_clock_content->integer == 1) {\n\t\tt = SCR_GetTimeString(TIMETYPE_HOSTCLOCK, SCR_HUD_ClockFormat(hud_clock_format->integer));\n\t}\n\telse if (hud_clock_content->integer == 2) {\n\t\tt = SCR_GetTimeString(TIMETYPE_CONNECTEDCLOCK, SCR_HUD_ClockFormat(hud_clock_format->integer));\n\t}\n\telse {\n\t\tt = SCR_GetTimeString(TIMETYPE_CLOCK, SCR_HUD_ClockFormat(hud_clock_format->integer));\n\t}\n\twidth = SCR_GetClockStringWidth(t, hud_clock_big->integer, hud_clock_scale->value, hud_clock_proportional->integer);\n\theight = SCR_GetClockStringHeight(hud_clock_big->integer, hud_clock_scale->value);\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tif (hud_clock_big->value) {\n\t\t\tSCR_DrawBigClock(x, y, hud_clock_style->value, hud_clock_blink->value, hud_clock_scale->value, t);\n\t\t}\n\t\telse {\n\t\t\tSCR_DrawSmallClock(x, y, hud_clock_style->value, hud_clock_blink->value, hud_clock_scale->value, t, hud_clock_proportional->integer);\n\t\t}\n\t}\n}\n\n//---------------------\n//\n// draw HUD gameclock\n//\nstatic void SCR_HUD_DrawGameClock(hud_t *hud)\n{\n\tint width, height;\n\tint x, y;\n\tint timetype;\n\tconst char *t;\n\n\tstatic cvar_t\n\t\t*hud_gameclock_big = NULL,\n\t\t*hud_gameclock_style,\n\t\t*hud_gameclock_blink,\n\t\t*hud_gameclock_countdown,\n\t\t*hud_gameclock_scale,\n\t\t*hud_gameclock_offset,\n\t\t*hud_gameclock_proportional;\n\n\tif (hud_gameclock_big == NULL)    // first time\n\t{\n\t\thud_gameclock_big = HUD_FindVar(hud, \"big\");\n\t\thud_gameclock_style = HUD_FindVar(hud, \"style\");\n\t\thud_gameclock_blink = HUD_FindVar(hud, \"blink\");\n\t\thud_gameclock_countdown = HUD_FindVar(hud, \"countdown\");\n\t\thud_gameclock_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_gameclock_offset = HUD_FindVar(hud, \"offset\");\n\t\thud_gameclock_proportional = HUD_FindVar(hud, \"proportional\");\n\t\tgameclockoffset = &hud_gameclock_offset->integer;\n\t}\n\n\ttimetype = (hud_gameclock_countdown->value) ? TIMETYPE_GAMECLOCKINV : TIMETYPE_GAMECLOCK;\n\tt = SCR_GetTimeString(timetype, NULL);\n\twidth = SCR_GetClockStringWidth(t, hud_gameclock_big->integer, hud_gameclock_scale->value, hud_gameclock_proportional->integer);\n\theight = SCR_GetClockStringHeight(hud_gameclock_big->integer, hud_gameclock_scale->value);\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tif (hud_gameclock_big->value) {\n\t\t\tSCR_DrawBigClock(x, y, hud_gameclock_style->value, hud_gameclock_blink->value, hud_gameclock_scale->value, t);\n\t\t}\n\t\telse {\n\t\t\tSCR_DrawSmallClock(x, y, hud_gameclock_style->value, hud_gameclock_blink->value, hud_gameclock_scale->value, t, hud_gameclock_proportional->integer);\n\t\t}\n\t}\n}\n\n//---------------------\n//\n// draw HUD democlock\n//\nstatic void SCR_HUD_DrawDemoClock(hud_t *hud)\n{\n\tint width = 0;\n\tint height = 0;\n\tint x = 0;\n\tint y = 0;\n\tconst char *t;\n\tstatic cvar_t\n\t\t*hud_democlock_big = NULL,\n\t\t*hud_democlock_style,\n\t\t*hud_democlock_blink,\n\t\t*hud_democlock_scale,\n\t\t*hud_democlock_proportional;\n\n\tif (!cls.demoplayback || cls.mvdplayback == QTV_PLAYBACK) {\n\t\tHUD_PrepareDraw(hud, width, height, &x, &y);\n\t\treturn;\n\t}\n\n\tif (hud_democlock_big == NULL) {\n\t\t// first time\n\t\thud_democlock_big = HUD_FindVar(hud, \"big\");\n\t\thud_democlock_style = HUD_FindVar(hud, \"style\");\n\t\thud_democlock_blink = HUD_FindVar(hud, \"blink\");\n\t\thud_democlock_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_democlock_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tt = SCR_GetTimeString(TIMETYPE_DEMOCLOCK, NULL);\n\twidth = SCR_GetClockStringWidth(t, hud_democlock_big->integer, hud_democlock_scale->value, hud_democlock_proportional->integer);\n\theight = SCR_GetClockStringHeight(hud_democlock_big->integer, hud_democlock_scale->value);\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tif (hud_democlock_big->value) {\n\t\t\tSCR_DrawBigClock(x, y, hud_democlock_style->value, hud_democlock_blink->value, hud_democlock_scale->value, t);\n\t\t}\n\t\telse {\n\t\t\tSCR_DrawSmallClock(x, y, hud_democlock_style->value, hud_democlock_blink->value, hud_democlock_scale->value, t, hud_democlock_proportional->integer);\n\t\t}\n\t}\n}\n\nstatic void SCR_HUD_DrawScoreClock(hud_t *hud) {\n\tint width, height;\n\tint x, y;\n\tstatic cvar_t\n\t\t\t*hud_scoreclock_format = NULL,\n\t\t\t*hud_scoreclock_scale = NULL,\n\t\t\t*hud_scoreclock_proportional = NULL;\n\n\tif (hud_scoreclock_format == NULL) {\n\t\thud_scoreclock_format = HUD_FindVar(hud, \"format\");\n\t\thud_scoreclock_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_scoreclock_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tif(!hud_scoreclock_format->string)\n\t\treturn;\n\n\ttime_t t;\n\tstruct tm *ptm;\n\tchar datestr[256] = \"bad date\";\n\ttime(&t);\n\tif ((ptm = localtime(&t)))\n\t\tstrftime(datestr, sizeof(datestr) - 1, hud_scoreclock_format->string, ptm);\n\n\twidth = Draw_StringLengthColors(datestr, -1, hud_scoreclock_scale->value, hud_scoreclock_proportional->integer);\n\theight = SCR_GetClockStringHeight(false, hud_scoreclock_scale->value);\n\n\tif(!width)\n\t\treturn;\n\n\tif (!HUD_PrepareDraw(hud, width, height, &x, &y))\n\t\treturn;\n\n\tDraw_SString(x, y, datestr, hud_scoreclock_scale->value, hud_scoreclock_proportional->integer);\n}\n\n///\n/// cl_screen.c clock module: to be merged\n///\n// non-static for menu\ncvar_t scr_clock = { \"cl_clock\", \"0\" };\nstatic cvar_t scr_clock_format = { \"cl_clock_format\", \"0\" };\nstatic cvar_t scr_clock_x = { \"cl_clock_x\", \"0\" };\nstatic cvar_t scr_clock_y = { \"cl_clock_y\", \"-1\" };\n\n// non-static for menu\ncvar_t scr_gameclock = { \"cl_gameclock\", \"0\" };\nstatic cvar_t scr_gameclock_x = { \"cl_gameclock_x\", \"0\" };\nstatic cvar_t scr_gameclock_y = { \"cl_gameclock_y\", \"-3\" };\nstatic cvar_t scr_gameclock_offset = { \"cl_gameclock_offset\", \"0\" };\nstatic cvar_t scr_gameclock_style = { \"cl_gameclock_style\", \"0\" };\n\nstatic cvar_t scr_democlock = { \"cl_democlock\", \"0\" };\nstatic cvar_t scr_democlock_x = { \"cl_democlock_x\", \"0\" };\nstatic cvar_t scr_democlock_y = { \"cl_democlock_y\", \"-2\" };\n\nstatic void SCR_DrawClock(void)\n{\n\tint x, y;\n\tchar str[64];\n\n\tif (!scr_clock.integer || scr_newHud.integer == 1) {\n\t\t// newHud has its own clock\n\t\treturn;\n\t}\n\n\tif (scr_clock.integer == 2) {\n\t\tstrlcpy(str, SCR_GetTimeString(TIMETYPE_CLOCK, SCR_HUD_ClockFormat(scr_clock_format.integer)), sizeof(str));\n\t}\n\telse if (scr_clock.integer == 3) {\n\t\tstrlcpy(str, SCR_GetTimeString(TIMETYPE_HOSTCLOCK, SCR_HUD_ClockFormat(scr_clock_format.integer)), sizeof(str));\n\t}\n\telse {\n\t\tstrlcpy(str, SCR_GetTimeString(TIMETYPE_CONNECTEDCLOCK, SCR_HUD_ClockFormat(scr_clock_format.integer)), sizeof(str));\n\t}\n\n\tx = ELEMENT_X_COORD(scr_clock);\n\ty = ELEMENT_Y_COORD(scr_clock);\n\tDraw_String(x, y, str);\n}\n\nstatic void SCR_DrawGameClock(void)\n{\n\tint x, y, i, style = scr_gameclock_style.integer;\n\tchar str[80], *s;\n\tfloat timelimit;\n\n\tif (!scr_gameclock.value || scr_newHud.value == 1) {\n\t\t// newHud has its own gameclock\n\t\treturn;\n\t}\n\n\tif (scr_gameclock.value == 2 || scr_gameclock.value == 4) {\n\t\ttimelimit = 60 * cl.timelimit + 1;\n\t}\n\telse {\n\t\ttimelimit = 0;\n\t}\n\n\tif (cl.countdown || cl.standby) {\n\t\tstrlcpy(str, SecondsToHourString(timelimit), sizeof(str));\n\t}\n\telse {\n\t\tstrlcpy(str, SecondsToHourString((int)fabs(timelimit - cl.gametime + scr_gameclock_offset.value)), sizeof(str));\n\t}\n\n\tif ((scr_gameclock.value == 3 || scr_gameclock.value == 4) && (s = strchr(str, ':'))) {\n\t\t// or just use SecondsToMinutesString() ...\n\t\ts++;\n\t}\n\telse {\n\t\ts = str;\n\t}\n\n\tif (style) {\n\t\tfor (i = 0; i < strlen(s); i++) {\n\t\t\t// only recolor ':' for red numbers\n\t\t\tif (style != 1 && s[i] == ':')\n\t\t\t\tcontinue;\n\n\t\t\tswitch (style) {\n\t\t\tcase 1:\n\t\t\t\t// red numbers\n\t\t\t\ts[i] += 128;\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\t// high bit green numbers\n\t\t\t\ts[i] += 98;\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\t// low bit green numbers\n\t\t\t\ts[i] -= 30;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tx = ELEMENT_X_COORD(scr_gameclock);\n\ty = ELEMENT_Y_COORD(scr_gameclock);\n\tDraw_String(x, y, s);\n}\n\nstatic void SCR_DrawDemoClock(void)\n{\n\tint x, y;\n\tchar str[80];\n\n\tif (!cls.demoplayback || cls.mvdplayback == QTV_PLAYBACK || !scr_democlock.value || scr_newHud.value == 1) {\n\t\t// newHud has its own democlock\n\t\treturn;\n\t}\n\n\tif (scr_democlock.value == 2) {\n\t\tstrlcpy(str, SecondsToHourString((int)(cls.demotime)), sizeof(str));\n\t}\n\telse {\n\t\tstrlcpy(str, SecondsToHourString((int)(cls.demotime - demostarttime)), sizeof(str));\n\t}\n\n\tx = ELEMENT_X_COORD(scr_democlock);\n\ty = ELEMENT_Y_COORD(scr_democlock);\n\tDraw_String(x, y, str);\n}\n\nvoid SCR_DrawClocks(void)\n{\n\tSCR_DrawClock();\n\tSCR_DrawGameClock();\n\tSCR_DrawDemoClock();\n}\n\n/// Initialisation\nvoid Clock_HudInit(void)\n{\n\tCvar_Register(&scr_clock_x);\n\tCvar_Register(&scr_clock_y);\n\tCvar_Register(&scr_clock_format);\n\tCvar_Register(&scr_clock);\n\n\tCvar_Register(&scr_gameclock_style);\n\tCvar_Register(&scr_gameclock_offset);\n\tCvar_Register(&scr_gameclock_x);\n\tCvar_Register(&scr_gameclock_y);\n\tCvar_Register(&scr_gameclock);\n\n\tCvar_Register(&scr_democlock_x);\n\tCvar_Register(&scr_democlock_y);\n\tCvar_Register(&scr_democlock);\n\n\t// init clock\n\tHUD_Register(\n\t\t\"clock\", NULL, \"Shows current local time (hh:mm:ss).\",\n\t\tHUD_PLUSMINUS, ca_disconnected, 8, SCR_HUD_DrawClock,\n\t\t\"0\", \"top\", \"right\", \"console\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"big\", \"1\",\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"blink\", \"1\",\n\t\t\"format\", \"0\",\n\t\t\"proportional\", \"0\",\n\t\t\"content\", \"0\",\n\t\tNULL\n\t);\n\n\t// init democlock\n\tHUD_Register(\n\t\t\"democlock\", NULL, \"Shows current demo time (hh:mm:ss).\",\n\t\tHUD_PLUSMINUS, ca_disconnected, 7, SCR_HUD_DrawDemoClock,\n\t\t\"1\", \"top\", \"right\", \"console\", \"0\", \"8\", \"0\", \"0 0 0\", NULL,\n\t\t\"big\", \"0\",\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"blink\", \"0\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\t// init gameclock\n\tHUD_Register(\n\t\t\"gameclock\", NULL, \"Shows current game time (hh:mm:ss).\",\n\t\tHUD_PLUSMINUS, ca_disconnected, 8, SCR_HUD_DrawGameClock,\n\t\t\"1\", \"top\", \"right\", \"console\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"big\", \"1\",\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"blink\", \"1\",\n\t\t\"countdown\", \"0\",\n\t\t\"offset\", \"0\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\t// init scoreclock\n\tHUD_Register(\n\t\t\t\"scoreclock\", NULL, \"Shows current date and time on the scoreboard\",\n\t\t\tHUD_NO_DRAW | HUD_ON_INTERMISSION | HUD_ON_SCORES | HUD_ON_FINALE, ca_disconnected, 8, SCR_HUD_DrawScoreClock,\n\t\t\t\"1\", \"screen\", \"center\", \"bottom\", \"0\", \"-10\", \"0\", \"0 0 0\", NULL,\n\t\t\t\"scale\", \"1\",\n\t\t\t\"format\", \"%d-%m-%Y %H:%M:%S\",\n\t\t\t\"proportional\", \"0\",\n\t\t\tNULL\n\t);\n}\n\n//\n// ------------------\n// draw BIG clock\n// style:\n//  0 - normal\n//  1 - red\nvoid SCR_DrawBigClock(int x, int y, int style, int blink, float scale, const char *t)\n{\n\textern  mpic_t  *sb_nums[2][11];\n\textern  mpic_t  *sb_colon/*, *sb_slash*/;\n\tqbool lblink = blink && ((int)(curtime * 10)) % 10 < 5;\n\n\tstyle = bound(0, style, 1);\n\n\twhile (*t) {\n\t\tif (*t >= '0'  &&  *t <= '9') {\n\t\t\tDraw_STransPic(x, y, sb_nums[style][*t - '0'], scale);\n\t\t\tx += 24 * scale;\n\t\t}\n\t\telse if (*t == ':') {\n\t\t\tif (lblink || !blink) {\n\t\t\t\tDraw_STransPic(x, y, sb_colon, scale);\n\t\t\t}\n\n\t\t\tx += 16 * scale;\n\t\t}\n\t\telse {\n\t\t\tDraw_SCharacter(x, y, *t + (style ? 128 : 0), 3 * scale);\n\t\t\tx += 24 * scale;\n\t\t}\n\t\tt++;\n\t}\n}\n\n// ------------------\n// draw SMALL clock\n// style:\n//  0 - small white\n//  1 - small red\n//  2 - small yellow/white\n//  3 - small yellow/red\nvoid SCR_DrawSmallClock(int x, int y, int style, int blink, float scale, const char *t, qbool proportional)\n{\n\tqbool lblink = blink && ((int)(curtime * 10)) % 10 < 5;\n\tint c;\n\n\tstyle = bound(0, style, 3);\n\n\twhile (*t) {\n\t\tc = (int)*t;\n\t\tif (c >= '0'  &&  c <= '9') {\n\t\t\tif (style == 1) {\n\t\t\t\tc += 128;\n\t\t\t}\n\t\t\telse if (style == 2 || style == 3) {\n\t\t\t\tc -= 30;\n\t\t\t}\n\t\t}\n\t\telse if (c == ':') {\n\t\t\tif (style == 1 || style == 3) {\n\t\t\t\tc += 128;\n\t\t\t}\n\t\t\tif (lblink || !blink) {\n\t\t\t\t;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tc = ' ';\n\t\t\t}\n\t\t}\n\t\tx += Draw_SCharacterP(x, y, c, scale, proportional);\n\t\tt++;\n\t}\n}\n"
  },
  {
    "path": "src/hud_common.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n//\n// common HUD elements\n// like clock etc..\n//\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#ifdef WITH_PNG\n#include <png.h>\n#endif\n#include \"image.h\"\n#include \"stats_grid.h\"\n#include \"vx_stuff.h\"\n#include \"gl_model.h\"\n#include \"tr_types.h\"\n#include \"rulesets.h\"\n#include \"utils.h\"\n#include \"sbar.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"Ctrl.h\"\n#include \"console.h\"\n#include \"teamplay.h\"\n#include \"mvd_utils.h\"\n#include \"mvd_utils_common.h\"\n#include \"fonts.h\"\n\nvoid TeamHold_DrawPercentageBar(\n\tint x, int y, int width, int height,\n\tfloat team1_percent, float team2_percent,\n\tint team1_color, int team2_color,\n\tint show_text, int vertical,\n\tint vertical_text, float opacity, float scale,\n\tqbool proportional\n);\n\n#ifndef STAT_MINUS\n#define STAT_MINUS\t\t10\n#endif\n\nvoid SCR_HUD_DrawTracker(hud_t* hud);\nvoid SCR_HUD_WeaponStats(hud_t *hud);\nvoid WeaponStats_HUDInit(void);\nvoid TeamInfo_HudInit(void);\nvoid Speed_HudInit(void);\nvoid TeamHold_HudInit(void);\nvoid Clock_HudInit(void);\nvoid Ammo_HudInit(void);\nvoid Items_HudInit(void);\nvoid Net_HudInit(void);\nvoid Guns_HudInit(void);\nvoid Groups_HudInit(void);\nvoid Armor_HudInit(void);\nvoid Health_HudInit(void);\nvoid GameSummary_HudInit(void);\nvoid Performance_HudInit(void);\nvoid Scores_HudInit(void);\nvoid Face_HudInit(void);\nvoid Frags_HudInit(void);\nvoid Tracking_HudInit(void);\nvoid CenterPrint_HudInit(void);\nvoid Qtv_HudInit(void);\n\nhud_t *hud_netgraph = NULL;\n\n// Items to be filtered out\nstatic int itemsclock_filter = 0;\n\n// ----------------\n// HUD planning\n//\n\nstruct\n{\n\t// this is temporary storage place for some of user's settings\n\t// hud_* values will be dumped into config file\n\tint old_multiview;\n\tint old_fov;\n\tint old_newhud;\n\n\tqbool active;\n} autohud;\n\nvoid OnAutoHudChange(cvar_t *var, char *value, qbool *cancel);\nstatic void OnRemovePrefixesChange(cvar_t* var, char* value, qbool* cancel);\nqbool autohud_loaded = false;\ncvar_t hud_planmode = {\"hud_planmode\",   \"0\"};\ncvar_t mvd_autohud = {\"mvd_autohud\", \"0\", 0, OnAutoHudChange};\ncvar_t hud_digits_trim = {\"hud_digits_trim\", \"1\"};\nstatic cvar_t hud_name_remove_prefixes = { \"hud_name_remove_prefixes\", \"\", 0, OnRemovePrefixesChange };\n\nsort_players_info_t\t\tsorted_players_by_frags[MAX_CLIENTS];\nsort_players_info_t\t\tsorted_players[MAX_CLIENTS];\nsort_teams_info_t\t\tsorted_teams[MAX_CLIENTS];\nint       n_teams;\nint       n_players;\nint       n_spectators;\nint       active_player_position = -1;\nint       active_team_position = -1;\n\nint hud_stats[MAX_CL_STATS];\n\nextern void DumpHUD(char *);\nextern char *Macro_MatchType(void);\n\nint HUD_Stats(int stat_num)\n{\n\tif (hud_planmode.value)\n\t\treturn hud_stats[stat_num];\n\telse\n\t\treturn cl.stats[stat_num];\n}\n\n// ----------------\n// HUD low levels\n//\n\ncvar_t hud_tp_need = {\"hud_tp_need\",   \"0\"};\n\n/* tp need levels\n   int TP_IsHealthLow(void);\n   int TP_IsArmorLow(void);\n   int TP_IsAmmoLow(int weapon); */\nextern cvar_t tp_need_health, tp_need_ra, tp_need_ya, tp_need_ga,\n       tp_weapon_order, tp_need_weapon, tp_need_shells,\n       tp_need_nails, tp_need_rockets, tp_need_cells;\n\nint TP_IsWeaponLow(void)\n{\n\tchar *s = tp_weapon_order.string;\n\twhile (*s  &&  *s != tp_need_weapon.string[0])\n\t{\n\t\tif (cl.stats[STAT_ITEMS] & (IT_SHOTGUN << (*s-'0'-2)))\n\t\t\treturn false;\n\t\ts++;\n\t}\n\treturn true;\n}\n\nvoid R_MQW_NetGraph(int outgoing_sequence, int incoming_sequence, int *packet_latency,\n\t\tint lost, int minping, int avgping, int maxping, int devping,\n\t\tint posx, int posy, int width, int height, int revx, int revy);\n// ----------------\n// Netgraph\nvoid SCR_HUD_Netgraph(hud_t *hud)\n{\n\tstatic cvar_t\n\t\t*par_width = NULL, *par_height,\n\t\t*par_swap_x, *par_swap_y,\n\t\t*par_ploss;\n\n\tif (par_width == NULL)  // first time\n\t{\n\t\tpar_width  = HUD_FindVar(hud, \"width\");\n\t\tpar_height = HUD_FindVar(hud, \"height\");\n\t\tpar_swap_x = HUD_FindVar(hud, \"swap_x\");\n\t\tpar_swap_y = HUD_FindVar(hud, \"swap_y\");\n\t\tpar_ploss  = HUD_FindVar(hud, \"ploss\");\n\t}\n\n\tR_MQW_NetGraph(cls.netchan.outgoing_sequence, cls.netchan.incoming_sequence,\n\t\t\tpacket_latency, par_ploss->value ? CL_CalcNet() : -1, -1, -1, -1, -1, -1,\n\t\t\t-1, (int)par_width->value, (int)par_height->value,\n\t\t\t(int)par_swap_x->value, (int)par_swap_y->value);\n}\n\n//---------------------\n//\n// draw HUD notify\n//\n\nvoid SCR_HUD_DrawNotify(hud_t* hud)\n{\n\tstatic cvar_t* hud_notify_rows = NULL;\n\tstatic cvar_t* hud_notify_scale;\n\tstatic cvar_t* hud_notify_time;\n\tstatic cvar_t* hud_notify_cols;\n\tstatic cvar_t* hud_notify_proportional;\n\n\tint x;\n\tint y;\n\tint width;\n\tint height;\n\tint chars_per_line;\n\n\tif (hud_notify_rows == NULL) // First time.\n\t{\n\t\thud_notify_rows  = HUD_FindVar(hud, \"rows\");\n\t\thud_notify_cols  = HUD_FindVar(hud, \"cols\");\n\t\thud_notify_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_notify_time  = HUD_FindVar(hud, \"time\");\n\t\thud_notify_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\t// Deliberately leaving this alone for the moment, not doing FontFixedWidth()...\n\tchars_per_line = (hud_notify_cols->integer > 0 ? hud_notify_cols->integer : con_linewidth);\n\theight = Q_rint ((con_linewidth / chars_per_line) * hud_notify_rows->integer * 8 * hud_notify_scale->value);\n\twidth = 8 * chars_per_line * hud_notify_scale->value;\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tSCR_DrawNotify(x, y, hud_notify_scale->value, hud_notify_time->integer, hud_notify_rows->integer, chars_per_line, hud_notify_proportional->integer);\n\t}\n}\n\n// =======================================================\n//\n//  s t a t u s   b a r   e l e m e n t s\n//\n//\n\n// status numbers\nvoid SCR_HUD_DrawNum2(\n\thud_t *hud, int num, qbool low,\n\tfloat scale, int style, int digits, char *s_align, qbool proportional, qbool draw_content,\n\tbyte *text_color_low, byte *text_color_normal\n)\n{\n\textern mpic_t *sb_nums[2][11];\n\n\tint  i;\n\tchar buf[sizeof(int) * 3]; // each byte need <= 3 chars\n\tint  len;\n\n\tint width, height, x, y;\n\tint size;\n\tint align;\n\n\tclrinfo_t clr = { .i = 0 };\n\n\tclamp(num, -99999, 999999);\n\tscale = max(scale, 0.01);\n\tif (digits > 0) {\n\t\tclamp(digits, 1, 6);\n\t}\n\telse {\n\t\tdigits = 0; // auto-resize\n\t}\n\n\talign = 2;\n\tswitch (tolower(s_align[0])) {\n\t\tdefault:\n\t\tcase 'l':   // 'l'eft\n\t\t\talign = 0;\n\t\t\tbreak;\n\t\tcase 'c':   // 'c'enter\n\t\t\talign = 1;\n\t\t\tbreak;\n\t\tcase 'r':   // 'r'ight\n\t\t\talign = 2;\n\t\t\tbreak;\n\t}\n\n\tsnprintf(buf, sizeof (buf), \"%d\", (style == 2 || style == 3) ? num : abs(num));\n\tif (digits) {\n\t\tswitch (hud_digits_trim.integer) {\n\t\t\tcase 0: // 10030 -> 999\n\t\t\t\tlen = strlen(buf);\n\t\t\t\tif (len > digits) {\n\t\t\t\t\tchar *p = buf;\n\t\t\t\t\tif (num < 0) {\n\t\t\t\t\t\t*p++ = '-';\n\t\t\t\t\t}\n\t\t\t\t\tfor (i = (num < 0) ? 1 : 0; i < digits; i++) {\n\t\t\t\t\t\t*p++ = '9';\n\t\t\t\t\t}\n\t\t\t\t\t*p = 0;\n\t\t\t\t\tlen = digits;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\tcase 1: // 10030 -> 030\n\t\t\t\tlen = strlen(buf);\n\t\t\t\tif(len > digits) {\n\t\t\t\t\tchar *p = buf;\n\t\t\t\t\tmemmove(p, p + (len - digits), digits);\n\t\t\t\t\tbuf[digits] = '\\0';\n\t\t\t\t\tlen = strlen(buf);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 2: // 10030 -> 100\n\t\t\t\tbuf[digits] = '\\0';\n\t\t\t\tlen = strlen(buf);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\telse {\n\t\tlen = strlen(buf);\n\t}\n\n\tswitch (style)\n\t{\n\t\tcase 1:\n\t\tcase 3:\n\t\t\tsize = 8;\n\t\t\tbreak;\n\t\tcase 0:\n\t\tcase 2:\n\t\tdefault:\n\t\t\tsize = 24;\n\t\t\tbreak;\n\t}\n\n\tif (digits) {\n\t\tif (size == 8) {\n\t\t\twidth = FontFixedWidth(digits, scale, true, proportional);\n\t\t}\n\t\telse {\n\t\t\twidth = digits * size * scale;\n\t\t}\n\t}\n\telse {\n\t\tif (size == 8) {\n\t\t\twidth = FontFixedWidth(len, scale, true, proportional);\n\t\t}\n\t\telse {\n\t\t\twidth = len * size * scale;\n\t\t}\n\t}\n\n\theight = size * scale;\n\n\tswitch (style)\n\t{\n\t\tcase 1:\n\t\tcase 3:\n\t\t\tif (!HUD_PrepareDraw(hud, width, height, &x, &y) || !draw_content) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tswitch (align)\n\t\t\t{\n\t\t\t\tcase 0: break;\n\t\t\t\tcase 1: x += width / 2 - Draw_StringLength(buf, -1, scale, proportional) / 2; break;\n\t\t\t\tcase 2: x += width - Draw_StringLength(buf, -1, scale, proportional); break;\n\t\t\t}\n\n\t\t\tif (low) {\n\t\t\t\tif (text_color_low != NULL)\n\t\t\t\t{\n\t\t\t\t\tclr.c = RGBAVECT_TO_COLOR(text_color_low);\n\t\t\t\t\tDraw_SColoredAlphaString(x, y, buf, &clr, 1, 0, scale, 1.0, proportional);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tDraw_SAlt_String(x, y, buf, scale, proportional);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif(style == 3) {\n\t\t\t\t\t// golden numbers\n\t\t\t\t\tfor(i = 0; i < len; i++) {\n\t\t\t\t\t\tif(isdigit(buf[i])) {\n\t\t\t\t\t\t\tbuf[i] = 18 + buf[i] - '0';\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (text_color_normal != NULL)\n\t\t\t\t{\n\t\t\t\t\tclr.c = RGBAVECT_TO_COLOR(text_color_normal);\n\t\t\t\t\tDraw_SColoredAlphaString(x, y, buf, &clr, 1, 0, scale, 1.0, proportional);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tDraw_SString(x, y, buf, scale, proportional);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 0:\n\t\tcase 2:\n\t\tdefault:\n\t\t\tif (!HUD_PrepareDraw(hud, width, height, &x, &y) || !draw_content) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tswitch (align) {\n\t\t\t\tcase 0: break;\n\t\t\t\tcase 1: x += (width - size * len * scale) / 2; break;\n\t\t\t\tcase 2: x += (width - size * len * scale); break;\n\t\t\t}\n\n\t\t\tfor (i = 0; i < len; i++) {\n\t\t\t\tif(buf[i] == '-' && style == 2) {\n\t\t\t\t\tDraw_STransPic (x, y, sb_nums[low ? 1 : 0][STAT_MINUS], scale);\n\t\t\t\t\tx += 24 * scale;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tDraw_STransPic (x, y, sb_nums[low ? 1 : 0][buf[i] - '0'], scale);\n\t\t\t\t\tx += 24 * scale;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t}\n}\n\nvoid SCR_HUD_DrawNum(\n\thud_t* hud, int num, qbool low,\n\tfloat scale, int style, int digits, char* s_align, qbool proportional\n)\n{\n\tSCR_HUD_DrawNum2(hud, num, low, scale, style, digits, s_align, proportional, true, NULL, NULL);\n}\n\n#define TEMPHUD_NAME \"_temphud\"\n#define TEMPHUD_FULLPATH \"configs/\"TEMPHUD_NAME\".cfg\"\n\n// will check if user wants to un/load external MVD HUD automatically\nvoid HUD_AutoLoad_MVD(int autoload) {\n\tchar *cfg_suffix = \"custom\";\n\textern cvar_t scr_fov;\n\textern cvar_t scr_newHud;\n\textern void Cmd_Exec_f (void);\n\textern void DumpConfig(char *name);\n\n\tif (autoload && cls.mvdplayback) {\n\t\t// Turn autohud ON here\n\n\t\tCom_DPrintf(\"Loading MVD Hud\\n\");\n\t\t// Store current settings.\n\t\tif (!autohud.active)\n\t\t{\n\t\t\textern cvar_t cfg_save_cmdline, cfg_save_cvars, cfg_save_cmds, cfg_save_aliases, cfg_save_binds;\n\n\t\t\t// Save old cfg_save values so that we don't screw the users\n\t\t\t// settings when saving the temp config.\n\t\t\tint old_cmdline = cfg_save_cmdline.value;\n\t\t\tint old_cvars\t= cfg_save_cvars.value;\n\t\t\tint old_cmds\t= cfg_save_cmds.value;\n\t\t\tint old_aliases = cfg_save_aliases.value;\n\t\t\tint old_binds\t= cfg_save_binds.value;\n\n\t\t\tautohud.old_fov = (int) scr_fov.value;\n\t\t\tautohud.old_multiview = (int) cl_multiview.value;\n\t\t\tautohud.old_newhud = (int) scr_newHud.value;\n\n\t\t\t// Make sure everything current settings are saved.\n\t\t\tCvar_SetValue(&cfg_save_cmdline,\t1);\n\t\t\tCvar_SetValue(&cfg_save_cvars,\t\t1);\n\t\t\tCvar_SetValue(&cfg_save_cmds,\t\t1);\n\t\t\tCvar_SetValue(&cfg_save_aliases,\t1);\n\t\t\tCvar_SetValue(&cfg_save_binds,\t\t1);\n\n\t\t\t// Save a temporary config.\n\t\t\tDumpConfig(TEMPHUD_NAME\".cfg\");\n\n\t\t\tCvar_SetValue(&cfg_save_cmdline,\told_cmdline);\n\t\t\tCvar_SetValue(&cfg_save_cvars,\t\told_cvars);\n\t\t\tCvar_SetValue(&cfg_save_cmds,\t\told_cmds);\n\t\t\tCvar_SetValue(&cfg_save_aliases,\told_aliases);\n\t\t\tCvar_SetValue(&cfg_save_binds,\t\told_binds);\n\t\t}\n\n\t\t// load MVD HUD config\n\t\tswitch ((int) autoload) {\n\t\t\tcase 1: // load 1on1 or 4on4 or custom according to $matchtype\n\t\t\t\tif (!strncmp(Macro_MatchType(), \"duel\", 4)) {\n\t\t\t\t\tcfg_suffix = \"1on1\";\n\t\t\t\t} else if (!strncmp(Macro_MatchType(), \"4on4\", 4)) {\n\t\t\t\t\tcfg_suffix = \"4on4\";\n\t\t\t\t} else {\n\t\t\t\t\tcfg_suffix = \"custom\";\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\tcase 2:\n\t\t\t\tcfg_suffix = \"custom\";\n\t\t\t\tbreak;\n\t\t}\n\n\t\tCbuf_AddText(va(\"exec cfg/mvdhud_%s.cfg\\n\", cfg_suffix));\n\n\t\tautohud.active = true;\n\t\treturn;\n\t}\n\n\tif ((!cls.mvdplayback || !autoload) && autohud.active) {\n\t\t// either user decided to turn mvd autohud off or mvd playback is over\n\t\t// -> Turn autohud OFF here\n\t\tFILE *tempfile;\n\t\tchar *fullname = va(\"%s/ezquake/\"TEMPHUD_FULLPATH, com_basedir);\n\n\t\tCom_DPrintf(\"Unloading MVD Hud\\n\");\n\t\t// load stored settings\n\t\tCvar_SetValue(&scr_fov, autohud.old_fov);\n\t\tCvar_SetValue(&cl_multiview, autohud.old_multiview);\n\t\tCvar_SetValue(&scr_newHud, autohud.old_newhud);\n\t\t//Cmd_TokenizeString(\"exec \"TEMPHUD_FULLPATH);\n\t\tCmd_TokenizeString(\"cfg_load \"TEMPHUD_FULLPATH);\n\t\tCmd_Exec_f();\n\n\t\t// delete temp config with hud_* settings\n\t\tif ((tempfile = fopen(fullname, \"rb\")) && (fclose(tempfile) != EOF))\n\t\t\tunlink(fullname);\n\n\t\tautohud.active = false;\n\t\treturn;\n\t}\n}\n\nvoid OnAutoHudChange(cvar_t *var, char *value, qbool *cancel) {\n\tHUD_AutoLoad_MVD(Q_atoi(value));\n}\n\n// Is run when a new map is loaded.\nvoid HUD_NewMap(void) {\n#if defined(WITH_PNG)\n\tHUD_NewRadarMap();\n#endif // WITH_PNG\n\n\tautohud_loaded = false;\n}\n\nqbool HUD_ShowInDemoplayback(int val)\n{\n\tif(!cl.teamplay && val == HUD_SHOW_ONLY_IN_TEAMPLAY)\n\t{\n\t\treturn false;\n\t}\n\telse if(!cls.demoplayback && val == HUD_SHOW_ONLY_IN_DEMOPLAYBACK)\n\t{\n\t\treturn false;\n\t}\n\telse if(!cl.teamplay && !cls.demoplayback\n\t\t\t&& val == HUD_SHOW_ONLY_IN_TEAMPLAY + HUD_SHOW_ONLY_IN_DEMOPLAYBACK)\n\t{\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid SCR_Hud_StackBar(hud_t* hud)\n{\n\tint x, y;\n\tint height = 8;\n\tint width = 0;\n\n\tstatic cvar_t\n\t\t*hud_stackbar_style = NULL,\n\t\t*hud_stackbar_opacity,\n\t\t*hud_stackbar_width,\n\t\t*hud_stackbar_height,\n\t\t*hud_stackbar_vertical,\n\t\t*hud_stackbar_show_text,\n\t\t*hud_stackbar_vertical_text,\n\t\t*hud_stackbar_onlytp,\n\t\t*hud_stackbar_scale,\n\t\t*hud_stackbar_proportional;\n\n\tif (hud_stackbar_style == NULL)    // first time\n\t{\n\t\thud_stackbar_style               = HUD_FindVar(hud, \"style\");\n\t\thud_stackbar_opacity             = HUD_FindVar(hud, \"opacity\");\n\t\thud_stackbar_width               = HUD_FindVar(hud, \"width\");\n\t\thud_stackbar_height              = HUD_FindVar(hud, \"height\");\n\t\thud_stackbar_vertical            = HUD_FindVar(hud, \"vertical\");\n\t\thud_stackbar_show_text           = HUD_FindVar(hud, \"show_text\");\n\t\thud_stackbar_vertical_text       = HUD_FindVar(hud, \"vertical_text\");\n\t\thud_stackbar_onlytp              = HUD_FindVar(hud, \"onlytp\");\n\t\thud_stackbar_scale               = HUD_FindVar(hud, \"scale\");\n\t\thud_stackbar_proportional        = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\theight = max(1, hud_stackbar_height->value);\n\twidth = max(0, hud_stackbar_width->value);\n\n\t// Don't show when not in teamplay/demoplayback.\n\tif (!HUD_ShowInDemoplayback(hud_stackbar_onlytp->value)) {\n\t\tHUD_PrepareDraw(hud, width , height, &x, &y);\n\t\treturn;\n\t}\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\t// Get stack for each team, convert to %\n\t\tint stack1 = sorted_teams[0].stack;\n\t\tint stack2 = sorted_teams[1].stack;\n\n\t\tif (stack1 + stack2 > 0) {\n\t\t\tfloat percent1 = (stack1 * 1.0f) / (stack1 + stack2);\n\n\t\t\tTeamHold_DrawPercentageBar(\n\t\t\t\tx, y, width, height,\n\t\t\t\tpercent1, 1.0f - percent1,\n\t\t\t\tsorted_teams[0].bottom,\n\t\t\t\tsorted_teams[1].bottom,\n\t\t\t\thud_stackbar_show_text->value,\n\t\t\t\thud_stackbar_vertical->value,\n\t\t\t\thud_stackbar_vertical_text->value,\n\t\t\t\thud_stackbar_opacity->value,\n\t\t\t\thud_stackbar_scale->value,\n\t\t\t\thud_stackbar_proportional->integer\n\t\t\t);\n\t\t}\n\t\telse {\n\t\t\tDraw_AlphaFill(x, y, hud_stackbar_width->value, height, 0, hud_stackbar_opacity->value * 0.5);\n\t\t}\n\t}\n}\n\nvoid ItemsClock_OnChangeItemFilter(cvar_t* var, char *s, qbool *cancel)\n{\n\tint new_filter = 0;\n\n\tnew_filter |= Utils_RegExpMatch(\"RL\", s) ? IT_ROCKET_LAUNCHER : 0;\n\tnew_filter |= Utils_RegExpMatch(\"QUAD\",\ts) ? IT_QUAD : 0;\n\tnew_filter |= Utils_RegExpMatch(\"RING\",\ts) ? IT_INVISIBILITY : 0;\n\tnew_filter |= Utils_RegExpMatch(\"PENT\",\ts) ? IT_INVULNERABILITY : 0;\n\tnew_filter |= Utils_RegExpMatch(\"SUIT\", s) ? IT_SUIT : 0;\n\tnew_filter |= Utils_RegExpMatch(\"LG\", s) ? IT_LIGHTNING : 0;\n\tnew_filter |= Utils_RegExpMatch(\"GL\", s) ? IT_GRENADE_LAUNCHER : 0;\n\tnew_filter |= Utils_RegExpMatch(\"SNG\", s) ? IT_SUPER_NAILGUN : 0;\n\tnew_filter |= Utils_RegExpMatch(\"MH\", s) ? IT_SUPERHEALTH : 0;\n\tnew_filter |= Utils_RegExpMatch(\"RA\", s) ? IT_ARMOR3 : 0;\n\tnew_filter |= Utils_RegExpMatch(\"YA\", s) ? IT_ARMOR2 : 0;\n\tnew_filter |= Utils_RegExpMatch(\"GA\", s) ? IT_ARMOR1 : 0;\n\n\titemsclock_filter = new_filter;\n}\n\nvoid SCR_HUD_DrawItemsClock(hud_t *hud)\n{\n\textern qbool hud_editor;\n\tint width, height;\n\tint x, y;\n\tstatic cvar_t\n\t\t*hud_itemsclock_timelimit = NULL,\n\t\t*hud_itemsclock_style = NULL,\n\t\t*hud_itemsclock_scale = NULL,\n\t\t*hud_itemsclock_filter = NULL,\n\t\t*hud_itemsclock_proportional = NULL,\n\t\t*hud_itemsclock_backpacks = NULL;\n\n\tif (hud_itemsclock_timelimit == NULL) {\n\t\tchar val[256];\n\n\t\thud_itemsclock_timelimit = HUD_FindVar(hud, \"timelimit\");\n\t\thud_itemsclock_style = HUD_FindVar(hud, \"style\");\n\t\thud_itemsclock_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_itemsclock_filter = HUD_FindVar(hud, \"filter\");\n\t\thud_itemsclock_proportional = HUD_FindVar(hud, \"proportional\");\n\t\thud_itemsclock_backpacks = HUD_FindVar(hud, \"backpacks\");\n\n\t\t// Unecessary to parse the item filter string on each frame.\n\t\thud_itemsclock_filter->OnChange = ItemsClock_OnChangeItemFilter;\n\n\t\t// Parse the item filter the first time (trigger the OnChange function above).\n\t\tstrlcpy(val, hud_itemsclock_filter->string, sizeof(val));\n\t\tCvar_Set(hud_itemsclock_filter, val);\n\t}\n\n\tMVD_ClockList_TopItems_DimensionsGet(hud_itemsclock_timelimit->value, hud_itemsclock_style->integer, &width, &height, hud_itemsclock_scale->value, hud_itemsclock_backpacks->integer, hud_itemsclock_proportional->integer);\n\n\tif (hud_editor)\n\t\tHUD_PrepareDraw(hud, width, LETTERHEIGHT * hud_itemsclock_scale->value, &x, &y);\n\n\tif (!height)\n\t\treturn;\n\n\tif (!HUD_PrepareDraw(hud, width, height, &x, &y))\n\t\treturn;\n\n\tMVD_ClockList_TopItems_Draw(hud_itemsclock_timelimit->value, hud_itemsclock_style->integer, x, y, hud_itemsclock_scale->value, itemsclock_filter, hud_itemsclock_backpacks->integer, hud_itemsclock_proportional->integer);\n}\n\n#ifdef WITH_PNG\n\nvoid SCR_HUD_DrawKeys(hud_t *hud)\n{\n\tchar line1[4], line2[4];\n\tint width, height, x, y;\n\tusermainbuttons_t b;\n\tint i;\n\tstatic cvar_t* vscale = NULL;\n\tstatic cvar_t* player = NULL;\n\tfloat scale;\n\tint player_slot = -1;\n\n\tif (!vscale) {\n\t\tvscale = HUD_FindVar(hud, \"scale\");\n\t\tplayer = HUD_FindVar(hud, \"player\");\n\t}\n\n\tif (cls.demoplayback && !cls.nqdemoplayback && !cls.mvdplayback && player->string[0]) {\n\t\tplayer_slot = Player_GetSlot(player->string, false);\n\t}\n\telse if (cls.mvdplayback) {\n\t\tif (player->string[0]) {\n\t\t\tplayer_slot = Player_GetSlot(player->string, false);\n\t\t}\n\t\telse {\n\t\t\t// returns -1 if not tracking\n\t\t\tplayer_slot = Cam_TrackNum();\n\t\t}\n\t}\n\n\tb = CL_GetLastCmd(player_slot);\n\n\tscale = vscale->value;\n\tscale = max(0, scale);\n\n\ti = 0;\n\tline1[i++] = b.attack  ? (char)('x' + 128) : 'x';\n\tline1[i++] = b.forward ? (char)('^' + 128) : '^';\n\tline1[i++] = b.jump    ? (char)('J' + 128) : 'J';\n\tline1[i++] = '\\0';\n\ti = 0;\n\tline2[i++] = b.left    ? (char)('<' + 128) : '<';\n\tline2[i++] = b.back    ? (char)('_' + 128) : '_';\n\tline2[i++] = b.right   ? (char)('>' + 128) : '>';\n\tline2[i++] = '\\0';\n\n\twidth = LETTERWIDTH * strlen(line1) * scale;\n\theight = LETTERHEIGHT * 2 * scale;\n\n\tif (!HUD_PrepareDraw(hud, width ,height, &x, &y))\n\t\treturn;\n\n\tDraw_SString(x, y, line1, scale, false);\n\tDraw_SString(x, y + LETTERHEIGHT*scale, line2, scale, false);\n}\n\n#endif // WITH_PNG\n\n//---------------------\n//\n// draw HUD static text\n//\nvoid SCR_HUD_DrawStaticText(hud_t *hud)\n{\n\tconst char *in;\n\tint alignment = 0;\n\n\tstatic cvar_t\n\t\t*hud_statictext_big = NULL,\n\t\t*hud_statictext_scale,\n\t\t*hud_statictext_text,\n\t\t*hud_statictext_textalign,\n\t\t*hud_statictext_proportional;\n\n\tif (hud_statictext_big == NULL) {\n\t\t// first time\n\t\thud_statictext_big = HUD_FindVar(hud, \"big\");\n\t\thud_statictext_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_statictext_text = HUD_FindVar(hud, \"text\");\n\t\thud_statictext_textalign = HUD_FindVar(hud, \"textalign\");\n\t\thud_statictext_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\t// Static text valid for demos/qtv only\n\tif (!cls.demoplayback) {\n\t\tint x, y;\n\t\tHUD_PrepareDraw(hud, 0, 0, &x, &y);\n\t\treturn;\n\t}\n\n\talignment = 0;\n\tif (!strcmp(hud_statictext_textalign->string, \"right\")) {\n\t\talignment = 1;\n\t}\n\telse if (!strcmp(hud_statictext_textalign->string, \"center\")) {\n\t\talignment = 2;\n\t}\n\n\t// convert special characters\n\tin = hud_statictext_text->string;\n\tif (strlen(in) >= MAX_MACRO_STRING) {\n\t\tin = \"error: input string too long\";\n\t}\n\tin = TP_ParseFunChars(in, false);\n\n\tSCR_HUD_MultiLineString(hud, in, hud_statictext_big->integer, alignment, hud_statictext_scale->value, hud_statictext_proportional->integer);\n}\n\nvoid SCR_HUD_MultiLineString(hud_t* hud, const char* in, qbool large_font, int alignment, float scale, qbool proportional)\n{\n\t// find carriage returns\n\tint x, y;\n\tconst char *line_start = in;\n\tint max_length = Draw_StringLengthColorsByTerminator(line_start, -1, scale, proportional, '\\r');\n\tchar* line_end;\n\tint lines = 0;\n\tint character_height;\n\n\twhile ((line_end = strchr(line_start, '\\r'))) {\n\t\tint line_length;\n\n\t\tline_start = line_end + 1;\n\t\tline_length = Draw_StringLengthColorsByTerminator(line_start, -1, scale, proportional, '\\r');\n\t\tmax_length = max(max_length, line_length);\n\n\t\t++lines;\n\t}\n\n\tif (*line_start) {\n\t\t++lines;\n\t}\n\n\t// collapse to invisible if nothing to display\n\tif (max_length == 0) {\n\t\tlines = 0;\n\t}\n\n\tcharacter_height = 8 * scale;\n\tif (large_font) {\n\t\tcharacter_height = 24 * scale;\n\t}\n\n\tif (HUD_PrepareDraw(hud, max_length, lines * character_height, &x, &y)) {\n\t\tfor (line_start = in; *line_start; line_start = line_end + 1) {\n\t\t\tint line_x = x;\n\t\t\tline_end = strchr(line_start, '\\r');\n\n\t\t\tif (line_end) {\n\t\t\t\t*line_end = '\\0';\n\t\t\t}\n\n\t\t\tif (alignment == 1) {\n\t\t\t\t// Right-justified\n\t\t\t\tline_x += (max_length - Draw_StringLengthColors(line_start, -1, scale, proportional));\n\t\t\t}\n\t\t\telse if (alignment == 2) {\n\t\t\t\t// Centered\n\t\t\t\tline_x += (max_length - Draw_StringLengthColors(line_start, -1, scale, proportional)) / 2;\n\t\t\t}\n\n\t\t\tif (large_font) {\n\t\t\t\tSCR_DrawWadString(line_x, y, scale, line_start); \n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SString(line_x, y, line_start, scale, proportional);\n\t\t\t}\n\n\t\t\ty += character_height;\n\t\t\tif (!line_end)\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nfloat SCR_HUD_TotalStrength(float health, float armorValue, float armorType)\n{\n\treturn max(0, min(\n\t\thealth / (1 - armorType),\n\t\thealth + armorValue\n\t));\n}\n\nfloat SCR_HUD_ArmorType(int items)\n{\n\tif (items & IT_INVULNERABILITY) {\n\t\treturn 0.99f;\n\t}\n\telse if (items & IT_ARMOR3) {\n\t\treturn 0.8f;\n\t}\n\telse if (items & IT_ARMOR2) {\n\t\treturn 0.6f;\n\t}\n\telse if (items & IT_ARMOR1) {\n\t\treturn 0.3f;\n\t}\n\treturn 0.0f;\n}\n\n//\n// Run before HUD elements are drawn.\n// Place stuff that is common for HUD elements here.\n//\nvoid HUD_BeforeDraw(void)\n{\n\t// Only sort once per draw.\n\tHUD_Sort_Scoreboard (HUD_SCOREBOARD_ALL);\n}\n\n//\n// Run after HUD elements are drawn.\n// Place stuff that is common for HUD elements here.\n//\nvoid HUD_AfterDraw(void)\n{\n}\n\n// ----------------\n// Init\n// and add some common elements to hud (clock etc)\n//\n\nvoid CommonDraw_Init(void)\n{\n\tint i;\n\n\t// variables\n\tCvar_SetCurrentGroup(CVAR_GROUP_HUD);\n\tCvar_Register(&hud_planmode);\n\tCvar_Register(&hud_tp_need);\n\tCvar_Register(&hud_digits_trim);\n\tCvar_Register(&hud_name_remove_prefixes);\n\tCvar_ResetCurrentGroup();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_MVD);\n\tCvar_Register (&mvd_autohud);\n\tCvar_ResetCurrentGroup();\n\n\t// init HUD STAT table\n\tfor (i=0; i < MAX_CL_STATS; i++)\n\t\thud_stats[i] = 0;\n\thud_stats[STAT_HEALTH]  = 200;\n\thud_stats[STAT_AMMO]    = 100;\n\thud_stats[STAT_ARMOR]   = 200;\n\thud_stats[STAT_SHELLS]  = 200;\n\thud_stats[STAT_NAILS]   = 200;\n\thud_stats[STAT_ROCKETS] = 100;\n\thud_stats[STAT_CELLS]   = 100;\n\thud_stats[STAT_ACTIVEWEAPON] = 32;\n\thud_stats[STAT_ITEMS] = 0xffffffff - IT_ARMOR2 - IT_ARMOR1;\n\n\tautohud.active = 0;\n\n\tHUD_Register(\n\t\t\"tracker\", NULL, \"Shows deaths, frag streaks etc\",\n\t\t0, ca_active, 0, SCR_HUD_DrawTracker,\n\t\t\"0\", \"screen\", \"left\", \"top\", \"0\", \"48\", \"0\", \"0 0 0\", NULL,\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\t\"name_width\", \"0\",\n\t\t\"align_right\", \"0\",\n\t\t\"image_scale\", \"1\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\n\t\t\"notify\", NULL, \"Shows last console lines\",\n\t\tHUD_PLUSMINUS, ca_disconnected, 8, SCR_HUD_DrawNotify,\n\t\t\"0\", \"top\", \"left\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"rows\", \"4\",\n\t\t\"cols\", \"30\",\n\t\t\"scale\", \"1\",\n\t\t\"time\", \"4\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n#ifdef WITH_PNG\n\tHUD_Register(\n\t\t\"keys\", NULL, \"Shows which keys user does press at the moment\",\n\t\t0, ca_active, 1, SCR_HUD_DrawKeys,\n\t\t\"0\", \"screen\", \"right\", \"center\", \"0\", \"0\", \"0.5\", \"20 20 20\", NULL,\n\t\t\"scale\", \"2\", \"player\", \"\",\n\t\tNULL\n\t);\n#endif\n\n\tHUD_Register(\n\t\t\"itemsclock\", NULL, \"Displays upcoming item respawns\",\n\t\t0, ca_active, 1, SCR_HUD_DrawItemsClock,\n\t\t\"0\", \"screen\", \"right\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"timelimit\", \"5\",\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"filter\", \"\",\n\t\t\"proportional\", \"0\",\n\t\t\"backpacks\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\n\t\t\"static_text\", NULL, \"Static text (demos only).\",\n\t\t0, ca_active, 0, SCR_HUD_DrawStaticText,\n\t\t\"0\", \"screen\", \"left\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"big\", \"0\",\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"text\", \"\",\n\t\t\"textalign\", \"left\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\n\t\t\"teamstackbar\", NULL, \"Shows relative stacks of each team.\",\n\t\tHUD_PLUSMINUS, ca_active, 0, SCR_Hud_StackBar,\n\t\t\"0\", \"top\", \"left\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"opacity\", \"0.8\",\n\t\t\"width\", \"200\",\n\t\t\"height\", \"8\",\n\t\t\"vertical\", \"0\",\n\t\t\"vertical_text\", \"0\",\n\t\t\"show_text\", \"1\",\n\t\t\"onlytp\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"simpleitems\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tPerformance_HudInit();\n\tGuns_HudInit();\n\tGroups_HudInit();\n\tItems_HudInit();\n\tAmmo_HudInit();\n\tArmor_HudInit();\n\tScores_HudInit();\n\tFace_HudInit();\n\tGameSummary_HudInit();\n\tNet_HudInit();\n\tClock_HudInit();\n\tSpeed_HudInit();\n\tRadar_HudInit();\n\tWeaponStats_HUDInit();\n\tTeamInfo_HudInit();\n\tTeamHold_HudInit();\n\tHealth_HudInit();\n\tFrags_HudInit();\n\tTracking_HudInit();\n\tCenterPrint_HudInit();\n\tQtv_HudInit();\n}\n\nconst char* HUD_FirstTeam(void)\n{\n\tif (!cl.teamlock1_teamname[0] && n_teams) {\n\t\t// setting for the first time when we have teams\n\t\tstrlcpy(cl.teamlock1_teamname, sorted_teams[0].name, sizeof(cl.teamlock1_teamname));\n\t}\n\telse if (n_teams) {\n\t\t// check that the team still exists\n\t\tint i;\n\t\tfor (i = 0; i < n_teams; ++i) {\n\t\t\tif (!strcmp(sorted_teams[i].name, cl.teamlock1_teamname)) {\n\t\t\t\treturn cl.teamlock1_teamname; // found\n\t\t\t}\n\t\t}\n\t\t// not found, reset to first time\n\t\tstrlcpy(cl.teamlock1_teamname, sorted_teams[0].name, sizeof(cl.teamlock1_teamname));\n\t}\n\telse {\n\t\t// no teams, clear and set again in the future\n\t\tmemset(cl.teamlock1_teamname, 0, sizeof(cl.teamlock1_teamname));\n\t}\n\treturn cl.teamlock1_teamname;\n}\n\nvoid CL_RemovePrefixFromName(int player)\n{\n\tsize_t skip;\n\tchar* prefixes, * prefix, * name;\n\tchar normalized_list[256];\n\tchar normalized_name[MAX_SCOREBOARDNAME];\n\tconst char* prefixes_list = hud_name_remove_prefixes.string;\n\n\tstrlcpy(cl.players[player].shortname, cl.players[player].name, sizeof(cl.players[player].shortname));\n\n\tif (!prefixes_list || !prefixes_list[0]) {\n\t\treturn;\n\t}\n\n\tskip = 0;\n\n\tstrlcpy(normalized_list, prefixes_list, sizeof(normalized_list));\n\tprefixes = Q_normalizetext(normalized_list);\n\tprefix = strtok(prefixes, \" \");\n\tstrlcpy(normalized_name, cl.players[player].name, sizeof(normalized_name));\n\tname = Q_normalizetext(normalized_name);\n\n\twhile (prefix != NULL) {\n\t\tif (strlen(prefix) > skip && strlen(name) > strlen(prefix) && strncasecmp(prefix, name, strlen(prefix)) == 0) {\n\t\t\tskip = strlen(prefix);\n\t\t\t// remove spaces from the new start of the name\n\t\t\twhile (name[skip] == ' ') {\n\t\t\t\tskip++;\n\t\t\t}\n\t\t\t// if it would skip the whole name, just use the whole name\n\t\t\tif (name[skip] == 0) {\n\t\t\t\tskip = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tprefix = strtok(NULL, \" \");\n\t}\n\n\tstrlcpy(cl.players[player].shortname, cl.players[player].name + skip, sizeof(cl.players[player].shortname));\n}\n\nstatic void OnRemovePrefixesChange(cvar_t* var, char* value, qbool* cancel)\n{\n\tint i;\n\n\tCvar_SetIgnoreCallback(var, value);\n\n\tfor (i = 0; i < sizeof(cl.players) / sizeof(cl.players[0]); ++i) {\n\t\tCL_RemovePrefixFromName(i);\n\t}\n}\n"
  },
  {
    "path": "src/hud_common.h",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n//\n// common HUD elements\n// like clock etc..\n//\n\n#ifndef __HUD_COMMON__H__\n#define __HUD_COMMON__H__\n\nextern hud_t *hud_netgraph;\n\nvoid SCR_HUD_Netgraph(hud_t *hud);\nvoid SCR_HUD_DrawNetStats(hud_t *hud);\n\nvoid HUD_NewMap(void);\nvoid HUD_NewRadarMap(void);\nvoid SCR_HUD_DrawRadar(hud_t *hud);\n\nvoid HudCommon_Init(void);\nvoid SCR_HUD_DrawNum(hud_t *hud, int num, qbool low, float scale, int style, int digits, char *s_align, qbool proportional);\nvoid SCR_HUD_DrawNum2(hud_t* hud, int num, qbool low, float scale, int style, int digits, char* s_align, qbool proportional, qbool draw_content, byte *text_color_low, byte *text_color_normal);\n\nextern qbool autohud_loaded;\nextern cvar_t mvd_autohud;\nvoid HUD_AutoLoad_MVD(int autoload);\n\n// player sorting\n// for frags and players\ntypedef struct sort_teams_info_s {\n\tchar *name;\n\tint  frags;\n\tint  min_ping;\n\tint  avg_ping;\n\tint  max_ping;\n\tint  nplayers;\n\tint  top, bottom;   // leader colours\n\tint  rlcount;       // Number of RLs present in the team. (Cokeman 2006-05-27)\n\tint  lgcount;       // number of LGs present in the team\n\tint  weapcount;     // number of players with weapons (RLs | LGs) in the team\n\tint  ra_taken;      // Total red armors taken\n\tint  ya_taken;      // Total yellow armors taken\n\tint  ga_taken;      // Total green armors taken\n\tint  mh_taken;      // Total megahealths taken\n\tint  quads_taken;   // Total quads taken\n\tint  pents_taken;   // Total pents taken\n\tint  rings_taken;   // Total rings taken\n\tint  stack;         // Total damage the team can take\n\tfloat ra_lasttime;  // last time ra taken\n\tfloat ya_lasttime;  // last time ya taken\n\tfloat ga_lasttime;  // last time ga taken\n\tfloat mh_lasttime;  // last time mh taken\n\tfloat q_lasttime;   // last time quad taken\n\tfloat p_lasttime;   // last time pent taken\n\tfloat r_lasttime;   // last time ring taken\n}\nsort_teams_info_t;\n\ntypedef struct sort_players_info_s {\n\tint playernum;\n\tsort_teams_info_t *team;\n}\nsort_players_info_t;\n\nextern sort_players_info_t sorted_players_by_frags[MAX_CLIENTS];\nextern sort_players_info_t sorted_players[MAX_CLIENTS];\nextern sort_teams_info_t   sorted_teams[MAX_CLIENTS];\nextern int n_teams;\nextern int n_players;\nextern int n_spectators;\n\nint HUD_Stats(int stat_num);\nextern cvar_t cl_weaponpreselect;\n\n#define HUD_SCOREBOARD_ALL\t\t\t0xffffffff\n#define HUD_SCOREBOARD_SORT_TEAMS\t(1 << 0)\n#define HUD_SCOREBOARD_SORT_PLAYERS\t(1 << 1)\n#define HUD_SCOREBOARD_UPDATE\t\t(1 << 2)\n#define HUD_SCOREBOARD_AVG_PING\t\t(1 << 3)\nvoid HUD_Sort_Scoreboard(int flags);\n\nextern int active_player_position;\nextern int active_team_position;\n\nvoid SCR_HUD_MultiLineString(hud_t* hud, const char* in, qbool large_font, int alignment, float scale, qbool proportional);\n\nfloat SCR_HUD_TotalStrength(float health, float armorValue, float armorType);\n\nfloat SCR_HUD_ArmorType(int items);\n\n#endif // __HUD_COMMON__H__\n"
  },
  {
    "path": "src/hud_editor.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/**\n\tHUD Editor module\n\n\tInitial concept code jogihoogi, rewritten by Cokeman, Feb 2007\n*/\n\n#include \"quakedef.h\"\n#include \"qsound.h\"\n#include \"utils.h\"\n#include \"menu.h\"\n#include \"keys.h\"\n#include \"Ctrl.h\"\n#include \"ez_controls.h\"\n#include \"ez_button.h\"\n#include \"ez_label.h\"\n#include \"ez_scrollbar.h\"\n#include \"ez_scrollpane.h\"\n#include \"ez_slider.h\"\n#include \"ez_listview.h\"\n#include \"ez_window.h\"\n#include \"hud.h\"\n#include \"hud_editor.h\"\n\n// Returns true if a point is on the same side of a line as a third point\nstatic qbool PointSameSide(vec3_t test_point, vec3_t a, vec3_t b, vec3_t control_point)\n{\n\tvec3_t a_b, a_test, a_control;\n\tvec3_t cp1, cp2;\n\n\tVectorSubtract(b, a, a_b);\n\tVectorSubtract(test_point, a, a_test);\n\tVectorSubtract(control_point, a, a_control);\n\n\tCrossProduct(a_b, a_test, cp1);\n\tCrossProduct(a_b, a_control, cp2);\n\n\treturn DotProduct(cp1, cp2) >= 0;\n}\n\n// Returns true if point is in a polygon\n// Replaced previous version in mathlib.c as this will cope with being passed a triangle strip\nstatic qbool IsPointInPolygon(int npol, vec3_t *v, float x, float y)\n{\n\tint i;\n\tvec3_t test_point = { x, y, 0 };\n\n\tfor (i = 2; i < npol; ++i) {\n\t\tfloat* a = v[i - 2];\n\t\tfloat* b = v[i - 1];\n\t\tfloat* c = v[i];\n\n\t\tif (PointSameSide(test_point, a, b, c) && PointSameSide(test_point, b, c, a) && PointSameSide(test_point, c, a, b)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nez_tree_t help_control_tree;\n\nextern mpic_t\t\t*scr_cursor_icon;\t// cl_screen.c\nextern cvar_t\t\thud_planmode;\t\t// hud_common.c\n\nmpic_t\t\t\t\t*hud_editor_move_icon\t= NULL;\nmpic_t\t\t\t\t*hud_editor_resize_icon\t= NULL;\nmpic_t\t\t\t\t*hud_editor_align_icon\t= NULL;\nmpic_t\t\t\t\t*hud_editor_place_icon\t= NULL;\n\ncvar_t\t\t\t\thud_editor_allowresize\t= {\"hud_editor_allowresize\", \"1\"};\t// Show resize handles / allow resizing.\ncvar_t\t\t\t\thud_editor_allowmove\t= {\"hud_editor_allowmove\", \"1\"};\t// Allow moving...\ncvar_t\t\t\t\thud_editor_allowplace\t= {\"hud_editor_allowplace\", \"1\"};\t// Allow placing HUDs.\ncvar_t\t\t\t\thud_editor_allowalign\t= {\"hud_editor_allowalign\", \"1\"};\t// Allow aligning HUDs.\n\nextern hud_t\t\t*hud_huds;\t\t\t\t\t\t\t\t\t\t// The list of HUDs..\nhud_t\t\t\t\t*selected_hud\t\t\t= NULL;\t\t\t\t\t// The currently selected HUD.\n\nqbool\t\t\t\thud_editor\t\t\t\t= false;\t\t\t\t// If we're in HUD editor mode or not.\nhud_editor_mode_t\thud_editor_mode\t\t\t= hud_editmode_off;\t\t// The current mode the HUD editor is in.\nhud_editor_mode_t\thud_editor_prevmode\t\t= hud_editmode_off;\t\t// The previous mode the HUD editor was in.\n\nqbool\t\t\t\thud_editor_showhelp\t\t= false;\t\t\t\t// Show the help plaque or not?\nqbool\t\t\t\thud_editor_showoutlines\t= true;\t\t\t\t\t// Show guidelines around the HUD elements.\n\nfloat\t\t\t\thud_mouse_x;\t\t\t\t\t\t\t\t\t// The screen coordinates of the mouse cursor.\nfloat\t\t\t\thud_mouse_y;\n\n// Macros for what mouse buttons are clicked.\n#define MOUSEDOWN\t\t\t( keydown[K_MOUSE1] ||  keydown[K_MOUSE2]|| keydown[K_MOUSE3])\n#define MOUSEDOWN_1_2\t\t( keydown[K_MOUSE1] &&  keydown[K_MOUSE2])\n#define MOUSEDOWN_1_3\t\t( keydown[K_MOUSE1] &&  keydown[K_MOUSE3])\n#define MOUSEDOWN_1_2_3\t\t( keydown[K_MOUSE1] &&  keydown[K_MOUSE2] &&  keydown[K_MOUSE3])\n#define MOUSEDOWN_1_ONLY\t( keydown[K_MOUSE1] && !keydown[K_MOUSE2] && !keydown[K_MOUSE3])\n#define MOUSEDOWN_2_ONLY\t(!keydown[K_MOUSE1] &&  keydown[K_MOUSE2] && !keydown[K_MOUSE3])\n#define MOUSEDOWN_3_ONLY\t(!keydown[K_MOUSE1] && !keydown[K_MOUSE2] &&  keydown[K_MOUSE3])\n#define MOUSEDOWN_1_2_ONLY\t( keydown[K_MOUSE1] &&  keydown[K_MOUSE2] && !keydown[K_MOUSE3])\n#define MOUSEDOWN_1_3_ONLY\t( keydown[K_MOUSE1] && !keydown[K_MOUSE2] &&  keydown[K_MOUSE3])\n#define MOUSEDOWN_2_3_ONLY\t(!keydown[K_MOUSE1] &&  keydown[K_MOUSE2] &&  keydown[K_MOUSE3])\n#define MOUSEDOWN_NONE\t\t(!keydown[K_MOUSE1] && !keydown[K_MOUSE2] && !keydown[K_MOUSE3])\n\n#define HUD_CENTER_X(h)\t\t((h)->lx + (h)->lw / 2)\t// Gets the coordinates for the center point of the specified hud.\n#define HUD_CENTER_Y(h)\t\t((h)->ly + (h)->lh / 2)\n\n//\n// HUD align polygons.\n//\n#define HUD_ALIGN_POLYCOUNT_CORNER\t5\n#define HUD_ALIGN_POLYCOUNT_EDGE\t4\n#define HUD_ALIGN_POLYCOUNT_CENTER\t8\n#define HUD_ALIGN_POLYCOUNT_CONSOLE\t4\n\nvec3_t *hud_align_current_poly = NULL;\t// The current alignment polygon when in alignment mode.\nint hud_align_current_polycount = 0;\t// Number of vertices in teh polygon.\n\nvec3_t hud_align_topright_poly[HUD_ALIGN_POLYCOUNT_CORNER];\nvec3_t hud_align_top_poly[HUD_ALIGN_POLYCOUNT_EDGE];\nvec3_t hud_align_topleft_poly[HUD_ALIGN_POLYCOUNT_CORNER];\nvec3_t hud_align_left_poly[HUD_ALIGN_POLYCOUNT_EDGE];\nvec3_t hud_align_bottomleft_poly[HUD_ALIGN_POLYCOUNT_CORNER];\nvec3_t hud_align_bottom_poly[HUD_ALIGN_POLYCOUNT_EDGE];\nvec3_t hud_align_bottomright_poly[HUD_ALIGN_POLYCOUNT_CORNER];\nvec3_t hud_align_right_poly[HUD_ALIGN_POLYCOUNT_EDGE];\nvec3_t hud_align_center_poly[HUD_ALIGN_POLYCOUNT_CENTER];\n\nvec3_t hud_align_consoleleft_poly[HUD_ALIGN_POLYCOUNT_CONSOLE];\nvec3_t hud_align_console_poly[HUD_ALIGN_POLYCOUNT_CONSOLE];\nvec3_t hud_align_consoleright_poly[HUD_ALIGN_POLYCOUNT_CONSOLE];\n\ntypedef enum hud_alignmode_s\n{\n\thud_align_center,\n\thud_align_top,\n\thud_align_topleft,\n\thud_align_left,\n\thud_align_bottomleft,\n\thud_align_bottom,\n\thud_align_bottomright,\n\thud_align_right,\n\thud_align_topright,\n\thud_align_consoleleft,\n\thud_align_consoleright,\n\thud_align_console\n\t// Not including before/after for now.\n} hud_alignmode_t;\n\nhud_alignmode_t\thud_alignmode = hud_align_center;\n\n// Possible positions for a grep handle.\ntypedef enum hud_greppos_s\n{\n\tpos_visible,\t// On screen.\n\tpos_top,\n\tpos_left,\n\tpos_bottom,\n\tpos_right\n} hud_greppos_t;\n\n// A Grep handle for when a HUD goes offscreen.\ntypedef struct hud_grephandle_s\n{\n\thud_t\t\t\t\t\t*hud;\t\t\t// HUD associated with this grep handle.\n\tint\t\t\t\t\t\tx;\t\t\t\t// The position in screen coordinates for the grephandle.\n\tint\t\t\t\t\t\ty;\n\tint\t\t\t\t\t\twidth;\n\tint\t\t\t\t\t\theight;\n\tqbool\t\t\t\t\thighlighted;\t// Should this grephandle be drawn highlighted?\n\thud_greppos_t\t\t\tpos;\t\t\t// The offscreen position of the HUD associated with this handle.\n\tstruct hud_grephandle_s\t*next;\n\tstruct hud_grephandle_s\t*previous;\n} hud_grephandle_t;\n\nhud_grephandle_t\t*hud_greps = NULL;\t\t\t\t\t// The list of \"grep handles\" that are shown if a HUD element is moved offscreen.\n\nhud_grephandle_t\t*hud_hoverlist = NULL;\t\t\t\t// The list of HUDs the mouse is hovering.\nint\t\t\t\t\thud_hoverlist_count = 0;\t\t\t// The number of hovered HUDs...\nqbool\t\t\t\thud_hoverlist_pos_is_set = false;\t// Has the coordinates been set for the hoverlist yet?\nfloat\t\t\t\thud_hoverlist_x;\t\t\t\t\t// The x position of the hover list.\nfloat\t\t\t\thud_hoverlist_y;\t\t\t\t\t// The y position of the hover list.\nhud_grephandle_t\thud_containers[MAX_HUD_ELEMENTS];\t// List of HUDs which have been hovered.\n\n//\n// Change in cursor's X position since last frame\n//\nstatic float HUD_CursorDeltaX(void)\n{\n\treturn (scr_pointer_state.x - scr_pointer_state.x_old);\n}\n\n//\n// Change in cursor's Y position since last frame\n//\nstatic float HUD_CursorDeltaY(void)\n{\n\treturn (scr_pointer_state.y - scr_pointer_state.y_old);\n}\n\n//\n// Adds a new HUD to the list of HUDs that are being hovered.\n//\nstatic void HUD_Editor_AddHoverHud(hud_grephandle_t *hud_container)\n{\n\t// Nothing to add.\n\tif(!hud_container)\n\t{\n\t\treturn;\n\t}\n\n\thud_container->next = hud_hoverlist;\n\tif(hud_hoverlist)\n\t{\n\t\thud_hoverlist->previous = hud_container;\n\t}\n\n\thud_hoverlist = hud_container;\n\n\thud_hoverlist_count++;\n}\n\n//\n// Associates (\"Creates\") a HUD with a HUD container.\n//\nstatic hud_grephandle_t *HUD_Editor_CreateHoverHud(hud_t *hud)\n{\n\tstatic int i = 0;\n\tint j = 0;\n\n\tif(i >= MAX_HUD_ELEMENTS - 1)\n\t{\n\t\treturn NULL;\n\t}\n\n\t// Check if the HUD already exists and use that one if that's the case...\n\tfor(j = 0; j < MAX_HUD_ELEMENTS && hud_containers[j].hud; j++)\n\t{\n\t\tif(hud_containers[j].hud == hud)\n\t\t{\n\t\t\thud_containers[j].next = NULL;\n\t\t\thud_containers[j].previous = NULL;\n\t\t\treturn &hud_containers[j];\n\t\t}\n\t}\n\n\t// The HUD wasn't found so add it to the end of the list.\n\thud_containers[i].hud = hud;\n\thud_containers[i].next = NULL;\n\thud_containers[i].previous = NULL;\n\ti++;\n\n\treturn &hud_containers[i - 1];\n}\n\n//\n// Find the HUD that is being selected in the hover list.\n//\nstatic hud_t *HUD_Editor_FindHoverListSelection(hud_grephandle_t *list)\n{\n\thud_t *match = NULL;\n\thud_grephandle_t *hud_iter = list;\n\n\twhile(hud_iter)\n\t{\n\t\tif(hud_iter->hud\n\t\t\t&& hud_mouse_x > hud_iter->x\n\t\t\t&& hud_mouse_x < (hud_iter->x + hud_iter->width)\n\t\t\t&& hud_mouse_y > hud_iter->y\n\t\t\t&& hud_mouse_y < (hud_iter->y + hud_iter->height))\n\t\t{\n\t\t\thud_iter->highlighted = true;\n\t\t\tmatch = hud_iter->hud;\n\t\t}\n\t\telse\n\t\t{\n\t\t\thud_iter->highlighted = false;\n\t\t}\n\n\t\thud_iter = hud_iter->next;\n\t}\n\n\treturn match;\n}\n\n//\n// Draw the hover list...\n//\nstatic qbool HUD_Editor_DrawHoverList(int x, int y, hud_grephandle_t *list)\n{\n\t#define PADDING 5\n\t#define LINEGAP\t1\n\tint width = 0;\n\tint height = 0;\n\tint i = 0;\n\tclrinfo_t color, highlight;\n\thud_grephandle_t *hud_iter = list;\n\n\t// No point in showing a list if there's only one hud under the cursor\n\t// or if the user hasn't right-clicked.\n\tif (hud_hoverlist_count <= 1 || hud_editor_mode != hud_editmode_hoverlist)\n\t{\n\t\treturn false;\n\t}\n\n\t// Get the width of to draw with.\n\twhile (hud_iter)\n\t{\n\t\twidth = max(width, (PADDING + 2 + strlen(hud_iter->hud->name)) * 8);\n\t\thud_iter = hud_iter->next;\n\t}\n\n\t// Draw the background fill.\n\theight = ((hud_hoverlist_count - 1) * (8 + LINEGAP)) + (2 * PADDING);\n\tDraw_AlphaFillRGB(x, y - height, width, height, RGBA_TO_COLOR(0, 0, 0, 255));\n\n\thud_iter = list;\n\n\t// Highlight color (yellow)...\n\thighlight.c = RGBA_TO_COLOR(0, 255, 0, 255);\n\thighlight.i = 0;\n\n\t// Orange.\n\tcolor.c = RGBA_TO_COLOR(255, 150, 0, 255);\n\tcolor.i = 0;\n\n\t// Draw the list items.\n\twhile(hud_iter)\n\t{\n\t\t// Calculate the size and position of the HUD in the list.\n\t\thud_iter->x = x;\n\t\thud_iter->y = y - height + (i * 8) + LINEGAP;\n\t\thud_iter->width = width;\n\t\thud_iter->height = 8;\n\n\t\t// Draw the z-order + name of the HUD.\n\t\tDraw_ColoredString3(hud_iter->x, hud_iter->y,\n\t\t\tva(\"%2d - %s\", hud_iter->hud->order->integer, hud_iter->hud->name),\n\t\t\t(hud_iter->highlighted ? &highlight : &color), 1, 0);\n\n\t\thud_iter = hud_iter->next;\n\t\ti++;\n\t}\n\n\treturn true;\n}\n\n//\n// Sets a new HUD Editor mode (and saves the previous one).\n//\nstatic void HUD_Editor_SetMode(hud_editor_mode_t newmode)\n{\n\thud_editor_prevmode = hud_editor_mode;\n\thud_editor_mode = newmode;\n}\n\n//\n// Draws a tool tip with a background next to the cursor.\n//\nstatic void HUD_Editor_DrawTooltip(int x, int y, char *string, color_t color) //float r, float g, float b, float a)\n{\n\tint len = strlen(string) * 8;\n\n\ty -= 9;\n\n\t// Make sure we're drawing within the screen.\n\tif (x + len > vid.width)\n\t{\n\t\tx -= len;\n\t}\n\n\tif (y + 9 > vid.height)\n\t{\n\t\ty -= 9;\n\t}\n\n\tDraw_AlphaFillRGB(x, y, len, 8, color);\n\tDraw_String(x, y, string);\n}\n\n//\n// Gets an alignment string for a specified alignmode enum.\n//\nstatic char *HUD_Editor_GetAlignmentString(hud_alignmode_t align)\n{\n\tswitch(hud_alignmode)\n\t{\n\t\tcase hud_align_center :\t\treturn \"center center\";\n\t\tcase hud_align_top :\t\treturn \"center top\";\n\t\tcase hud_align_topleft :\treturn \"left top\";\n\t\tcase hud_align_left :\t\treturn \"left center\";\n\t\tcase hud_align_bottomleft :\treturn \"left bottom\";\n\t\tcase hud_align_bottom :\t\treturn \"center bottom\";\n\t\tcase hud_align_bottomright :return \"right bottom\";\n\t\tcase hud_align_right :\t\treturn \"right center\";\n\t\tcase hud_align_topright :\treturn \"right top\";\n\t\tcase hud_align_consoleleft :return \"left console\";\n\t\tcase hud_align_console :\treturn \"center console\";\n\t\tcase hud_align_consoleright:return \"right console\";\n\t\tdefault :\t\t\t\t\treturn \"\";\n\t}\n}\n\n//\n// Returns an alignment enum based on a specified alignment string.\n//\nstatic hud_alignmode_t HUD_Editor_GetAlignmentFromString(char *alignstr)\n{\n\tif(!strcmp(alignstr, \"center center\"))\n\t{\n\t\treturn hud_align_center;\n\t}\n\telse if(!strcmp(alignstr, \"center top\"))\n\t{\n\t\treturn hud_align_top;\n\t}\n\telse if(!strcmp(alignstr, \"left top\"))\n\t{\n\t\treturn hud_align_topleft;\n\t}\n\telse if(!strcmp(alignstr, \"left center\"))\n\t{\n\t\treturn hud_align_left;\n\t}\n\telse if(!strcmp(alignstr, \"left bottom\"))\n\t{\n\t\treturn hud_align_bottomleft;\n\t}\n\telse if(!strcmp(alignstr, \"center bottom\"))\n\t{\n\t\treturn hud_align_bottom;\n\t}\n\telse if(!strcmp(alignstr, \"right bottom\"))\n\t{\n\t\treturn hud_align_bottomright;\n\t}\n\telse if(!strcmp(alignstr, \"right center\"))\n\t{\n\t\treturn hud_align_right;\n\t}\n\telse if(!strcmp(alignstr, \"right top\"))\n\t{\n\t\treturn hud_align_topright;\n\t}\n\telse if(!strcmp(alignstr, \"left console\"))\n\t{\n\t\treturn hud_align_consoleleft;\n\t}\n\telse if(!strcmp(alignstr, \"center console\"))\n\t{\n\t\treturn hud_align_console;\n\t}\n\telse if(!strcmp(alignstr, \"right console\"))\n\t{\n\t\treturn hud_align_consoleright;\n\t}\n\n\treturn hud_align_center;\n}\n\n//\n// Returns the alignment we are trying to align the selected HUD to at it's placement\n// when in alignment mode.\n//\nstatic hud_alignmode_t HUD_Editor_GetAlignment(int x, int y, hud_t *hud_element)\n{\n\t// For less clutter.\n\tfloat\tmid_x\t\t= 0.0;\n\tfloat\tmid_y\t\t= 0.0;\n\tint\t\toffset_x\t= 0;\n\tint\t\toffset_y\t= 0;\n\n\tif(hud_element)\n\t{\n\t\t// Aligns for HUD elements.\n\t\toffset_x = hud_element->lx;\n\t\toffset_y = hud_element->ly;\n\t\tmid_x = hud_element->lw / 2.0;\n\t\tmid_y = hud_element->lh / 2.0;\n\t}\n\telse\n\t{\n\t\t// Aligns for Screen.\n\t\toffset_x = 0;\n\t\toffset_y = (int)(vid.height * 0.05);\n\t\tmid_x = vid.width / 2.0;\n\t\tmid_y = (vid.height + offset_y) / 2.0;\n\t}\n\n\tif(!hud_element)\n\t{\n\t\t// Console left.\n\t\t{\n\t\t\thud_align_consoleleft_poly[0][0] = 0;\n\t\t\thud_align_consoleleft_poly[0][1] = 0;\n\t\t\thud_align_consoleleft_poly[0][2] = 0;\n\n\t\t\thud_align_consoleleft_poly[1][0] = 0;\n\t\t\thud_align_consoleleft_poly[1][1] = offset_y;\n\t\t\thud_align_consoleleft_poly[1][2] = 0;\n\n\t\t\thud_align_consoleleft_poly[2][0] = mid_x / 2.0;\n\t\t\thud_align_consoleleft_poly[2][1] = 0;\n\t\t\thud_align_consoleleft_poly[2][2] = 0;\n\n\t\t\thud_align_consoleleft_poly[3][0] = mid_x / 2.0;\n\t\t\thud_align_consoleleft_poly[3][1] = offset_y;\n\t\t\thud_align_consoleleft_poly[3][2] = 0;\n\n\t\t\tif (IsPointInPolygon(HUD_ALIGN_POLYCOUNT_CONSOLE, hud_align_consoleleft_poly, x, y))\n\t\t\t{\n\t\t\t\thud_align_current_poly = hud_align_consoleleft_poly;\n\t\t\t\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_CONSOLE;\n\t\t\t\treturn hud_align_consoleleft;\n\t\t\t}\n\t\t}\n\n\t\t// Console.\n\t\t{\n\t\t\thud_align_console_poly[0][0] = mid_x / 2.0;\n\t\t\thud_align_console_poly[0][1] = 0;\n\t\t\thud_align_console_poly[0][2] = 0;\n\n\t\t\thud_align_console_poly[1][0] = mid_x / 2.0;\n\t\t\thud_align_console_poly[1][1] = offset_y;\n\t\t\thud_align_console_poly[1][2] = 0;\n\n\t\t\thud_align_console_poly[2][0] = mid_x + (mid_x / 2.0);\n\t\t\thud_align_console_poly[2][1] = 0;\n\t\t\thud_align_console_poly[2][2] = 0;\n\n\t\t\thud_align_console_poly[3][0] = mid_x + (mid_x / 2.0);\n\t\t\thud_align_console_poly[3][1] = offset_y;\n\t\t\thud_align_console_poly[3][2] = 0;\n\n\t\t\tif (IsPointInPolygon(HUD_ALIGN_POLYCOUNT_CONSOLE, hud_align_console_poly, x, y))\n\t\t\t{\n\t\t\t\thud_align_current_poly = hud_align_console_poly;\n\t\t\t\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_CONSOLE;\n\t\t\t\treturn hud_align_console;\n\t\t\t}\n\t\t}\n\n\t\t// Console right.\n\t\t{\n\t\t\thud_align_consoleright_poly[0][0] = mid_x + (mid_x / 2.0);\n\t\t\thud_align_consoleright_poly[0][1] = 0;\n\t\t\thud_align_consoleright_poly[0][2] = 0;\n\n\t\t\thud_align_consoleright_poly[1][0] = mid_x + (mid_x / 2.0);\n\t\t\thud_align_consoleright_poly[1][1] = offset_y;\n\t\t\thud_align_consoleright_poly[1][2] = 0;\n\n\t\t\thud_align_consoleright_poly[2][0] = 2 * mid_x;\n\t\t\thud_align_consoleright_poly[2][1] = 0;\n\t\t\thud_align_consoleright_poly[2][2] = 0;\n\n\t\t\thud_align_consoleright_poly[3][0] = 2 * mid_x;\n\t\t\thud_align_consoleright_poly[3][1] = offset_y;\n\t\t\thud_align_consoleright_poly[3][2] = 0;\n\n\t\t\tif (IsPointInPolygon(HUD_ALIGN_POLYCOUNT_CONSOLE, hud_align_consoleright_poly, x, y))\n\t\t\t{\n\t\t\t\thud_align_current_poly = hud_align_consoleright_poly;\n\t\t\t\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_CONSOLE;\n\t\t\t\treturn hud_align_consoleright;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Top right.\n\t{\n\t\thud_align_topright_poly[0][0] = mid_x + (mid_x / 4.0);\n\t\thud_align_topright_poly[0][1] = mid_y - (mid_y / 2.0);\n\t\thud_align_topright_poly[0][2] = 0;\n\n\t\thud_align_topright_poly[1][0] = mid_x + (mid_x / 2.0);\n\t\thud_align_topright_poly[1][1] = mid_y - (mid_y / 4.0);\n\t\thud_align_topright_poly[1][2] = 0;\n\n\t\thud_align_topright_poly[2][0] = mid_x + (mid_x / 2.0);\n\t\thud_align_topright_poly[2][1] = 0;\n\t\thud_align_topright_poly[2][2] = 0;\n\n\t\thud_align_topright_poly[3][0] = 2 * mid_x;\n\t\thud_align_topright_poly[3][1] = mid_y - (mid_y / 2.0);\n\t\thud_align_topright_poly[3][2] = 0;\n\n\t\thud_align_topright_poly[4][0] = 2 * mid_x;\n\t\thud_align_topright_poly[4][1] = 0;\n\t\thud_align_topright_poly[4][2] = 0;\n\n\t\tif (IsPointInPolygon(HUD_ALIGN_POLYCOUNT_CORNER, hud_align_topright_poly, x - offset_x, y - offset_y))\n\t\t{\n\t\t\thud_align_current_poly = hud_align_topright_poly;\n\t\t\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_CORNER;\n\t\t\treturn hud_align_topright;\n\t\t}\n\t}\n\n\t// Top.\n\t{\n\t\thud_align_top_poly[0][0] = mid_x / 2.0;\n\t\thud_align_top_poly[0][1] = 0;\n\t\thud_align_top_poly[0][2] = 0;\n\n\t\thud_align_top_poly[1][0] = mid_x - (mid_x / 4.0);\n\t\thud_align_top_poly[1][1] = mid_y - (mid_y / 2.0);\n\t\thud_align_top_poly[1][2] = 0;\n\n\t\thud_align_top_poly[2][0] = mid_x + (mid_x / 2.0);\n\t\thud_align_top_poly[2][1] = 0;\n\t\thud_align_top_poly[2][2] = 0;\n\n\t\thud_align_top_poly[3][0] = mid_x + (mid_x / 4.0);\n\t\thud_align_top_poly[3][1] = mid_y - (mid_y / 2.0);\n\t\thud_align_top_poly[3][2] = 0;\n\n\t\tif (IsPointInPolygon(HUD_ALIGN_POLYCOUNT_EDGE, hud_align_top_poly, x - offset_x, y - offset_y))\n\t\t{\n\t\t\thud_align_current_poly = hud_align_top_poly;\n\t\t\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_EDGE;\n\t\t\treturn hud_align_top;\n\t\t}\n\t}\n\n\t// Top Left.\n\t{\n\t\thud_align_topleft_poly[0][0] = 0;\n\t\thud_align_topleft_poly[0][1] = mid_y - (mid_y / 2.0);\n\t\thud_align_topleft_poly[0][2] = 0;\n\n\t\thud_align_topleft_poly[1][0] = 0;\n\t\thud_align_topleft_poly[1][1] = 0;\n\t\thud_align_topleft_poly[1][2] = 0;\n\n\t\thud_align_topleft_poly[2][0] = mid_x - (mid_x / 2.0);\n\t\thud_align_topleft_poly[2][1] = mid_y - (mid_y / 4.0);\n\t\thud_align_topleft_poly[2][2] = 0;\n\n\t\thud_align_topleft_poly[3][0] = mid_x - (mid_x / 2.0);\n\t\thud_align_topleft_poly[3][1] = 0;\n\t\thud_align_topleft_poly[3][2] = 0;\n\n\t\thud_align_topleft_poly[4][0] = mid_x - (mid_x / 4.0);\n\t\thud_align_topleft_poly[4][1] = mid_y - (mid_y / 2.0);\n\t\thud_align_topleft_poly[4][2] = 0;\n\n\t\tif (IsPointInPolygon(HUD_ALIGN_POLYCOUNT_CORNER, hud_align_topleft_poly, x - offset_x, y - offset_y))\n\t\t{\n\t\t\thud_align_current_poly = hud_align_topleft_poly;\n\t\t\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_CORNER;\n\t\t\treturn hud_align_topleft;\n\t\t}\n\t}\n\n\t// Left.\n\t{\n\t\thud_align_left_poly[0][0] = 0;\n\t\thud_align_left_poly[0][1] = mid_y / 2.0;\n\t\thud_align_left_poly[0][2] = 0;\n\n\t\thud_align_left_poly[1][0] = 0;\n\t\thud_align_left_poly[1][1] = mid_y + (mid_y / 2.0);\n\t\thud_align_left_poly[1][2] = 0;\n\n\t\thud_align_left_poly[2][0] = mid_x / 2.0;\n\t\thud_align_left_poly[2][1] = mid_y - (mid_y / 4.0);\n\t\thud_align_left_poly[2][2] = 0;\n\n\t\thud_align_left_poly[3][0] = mid_x / 2.0;\n\t\thud_align_left_poly[3][1] = mid_y + (mid_y / 4.0);\n\t\thud_align_left_poly[3][2] = 0;\n\n\t\tif (IsPointInPolygon(HUD_ALIGN_POLYCOUNT_EDGE, hud_align_left_poly, x - offset_x, y - offset_y))\n\t\t{\n\t\t\thud_align_current_poly = hud_align_left_poly;\n\t\t\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_EDGE;\n\t\t\treturn hud_align_left;\n\t\t}\n\t}\n\n\t// Bottom Left.\n\t{\n\t\thud_align_bottomleft_poly[0][0] = 0;\n\t\thud_align_bottomleft_poly[0][1] = 2 * mid_y;\n\t\thud_align_bottomleft_poly[0][2] = 0;\n\n\t\thud_align_bottomleft_poly[1][0] = 0;\n\t\thud_align_bottomleft_poly[1][1] = mid_y + (mid_y / 2.0);\n\t\thud_align_bottomleft_poly[1][2] = 0;\n\n\t\thud_align_bottomleft_poly[2][0] = mid_x / 2.0;\n\t\thud_align_bottomleft_poly[2][1] = 2 * mid_y;\n\t\thud_align_bottomleft_poly[2][2] = 0;\n\n\t\thud_align_bottomleft_poly[3][0] = mid_x / 2.0;\n\t\thud_align_bottomleft_poly[3][1] = mid_y + (mid_y / 4.0);\n\t\thud_align_bottomleft_poly[3][2] = 0;\n\n\t\thud_align_bottomleft_poly[4][0] = mid_x - (mid_x / 4.0);\n\t\thud_align_bottomleft_poly[4][1] = mid_y + (mid_y / 2.0);\n\t\thud_align_bottomleft_poly[4][2] = 0;\n\n\t\tif (IsPointInPolygon(HUD_ALIGN_POLYCOUNT_CORNER, hud_align_bottomleft_poly, x - offset_x, y - offset_y))\n\t\t{\n\t\t\thud_align_current_poly = hud_align_bottomleft_poly;\n\t\t\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_CORNER;\n\t\t\treturn hud_align_bottomleft;\n\t\t}\n\t}\n\n\t// Bottom.\n\t{\n\t\thud_align_bottom_poly[0][0] = mid_x - (mid_x / 4.0);\n\t\thud_align_bottom_poly[0][1] = mid_y + (mid_y / 2.0);\n\t\thud_align_bottom_poly[0][2] = 0;\n\n\t\thud_align_bottom_poly[1][0] = mid_x - (mid_x / 2.0);\n\t\thud_align_bottom_poly[1][1] = 2 * mid_y;\n\t\thud_align_bottom_poly[1][2] = 0;\n\n\t\thud_align_bottom_poly[2][0] = mid_x + (mid_x / 4.0);\n\t\thud_align_bottom_poly[2][1] = mid_y + (mid_y / 2.0);\n\t\thud_align_bottom_poly[2][2] = 0;\n\n\t\thud_align_bottom_poly[3][0] = mid_x + (mid_x / 2.0);\n\t\thud_align_bottom_poly[3][1] = 2 * mid_y;\n\t\thud_align_bottom_poly[3][2] = 0;\n\n\t\tif (IsPointInPolygon(HUD_ALIGN_POLYCOUNT_EDGE, hud_align_bottom_poly, x - offset_x, y - offset_y))\n\t\t{\n\t\t\thud_align_current_poly = hud_align_bottom_poly;\n\t\t\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_EDGE;\n\t\t\treturn hud_align_bottom;\n\t\t}\n\t}\n\n\t// Bottom Right.\n\t{\n\t\thud_align_bottomright_poly[0][0] = 2 * mid_x;\n\t\thud_align_bottomright_poly[0][1] = 2 * mid_y;\n\t\thud_align_bottomright_poly[0][2] = 0;\n\n\t\thud_align_bottomright_poly[1][0] = mid_x + (mid_x / 2.0);\n\t\thud_align_bottomright_poly[1][1] = 2 * mid_y;\n\t\thud_align_bottomright_poly[1][2] = 0;\n\n\t\thud_align_bottomright_poly[2][0] = 2 * mid_x;\n\t\thud_align_bottomright_poly[2][1] = mid_y + (mid_y / 2.0);\n\t\thud_align_bottomright_poly[2][2] = 0;\n\n\t\thud_align_bottomright_poly[3][0] = mid_x + (mid_x / 4.0);\n\t\thud_align_bottomright_poly[3][1] = mid_y + (mid_y / 2.0);\n\t\thud_align_bottomright_poly[3][2] = 0;\n\n\t\thud_align_bottomright_poly[4][0] = mid_x + (mid_x / 2.0);\n\t\thud_align_bottomright_poly[4][1] = mid_y + (mid_y / 4.0);\n\t\thud_align_bottomright_poly[4][2] = 0;\n\n\t\tif (IsPointInPolygon(HUD_ALIGN_POLYCOUNT_CORNER, hud_align_bottomright_poly, x - offset_x, y - offset_y))\n\t\t{\n\t\t\thud_align_current_poly = hud_align_bottomright_poly;\n\t\t\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_CORNER;\n\t\t\treturn hud_align_bottomright;\n\t\t}\n\t}\n\n\t// Right.\n\t{\n\t\thud_align_right_poly[0][0] = mid_x + (mid_x / 2.0);\n\t\thud_align_right_poly[0][1] = mid_y - (mid_y / 4.0);\n\t\thud_align_right_poly[0][2] = 0;\n\n\t\thud_align_right_poly[1][0] = mid_x + (mid_x / 2.0);\n\t\thud_align_right_poly[1][1] = mid_y + (mid_y / 4.0);\n\t\thud_align_right_poly[1][2] = 0;\n\n\t\thud_align_right_poly[2][0] = 2 * mid_x;\n\t\thud_align_right_poly[2][1] = mid_y / 2.0;\n\t\thud_align_right_poly[2][2] = 0;\n\n\t\thud_align_right_poly[3][0] = 2 * mid_x;\n\t\thud_align_right_poly[3][1] = mid_y + (mid_y / 2.0);\n\t\thud_align_right_poly[3][2] = 0;\n\n\t\tif (IsPointInPolygon(HUD_ALIGN_POLYCOUNT_EDGE, hud_align_right_poly, x - offset_x, y - offset_y))\n\t\t{\n\t\t\thud_align_current_poly = hud_align_right_poly;\n\t\t\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_EDGE;\n\t\t\treturn hud_align_right;\n\t\t}\n\t}\n\n\t// Center.\n\t{\n\t\tfloat x0 = mid_x / 2.0;\n\t\tfloat x1 = mid_x - (mid_x / 4.0);\n\t\tfloat x2 = mid_x + (mid_x / 4.0);\n\t\tfloat x3 = mid_x + (mid_x / 2.0);\n\t\tfloat y0 = mid_y / 2.0;\n\t\tfloat y1 = mid_y - (mid_y / 4.0);\n\t\tfloat y2 = mid_y + (mid_y / 4.0);\n\t\tfloat y3 = mid_y + (mid_y / 2.0);\n\n\t\thud_align_center_poly[0][0] = x0;\n\t\thud_align_center_poly[0][1] = y1;\n\t\thud_align_center_poly[0][2] = 0;\n\n\t\thud_align_center_poly[1][0] = x0;\n\t\thud_align_center_poly[1][1] = y2;\n\t\thud_align_center_poly[1][2] = 0;\n\n\t\thud_align_center_poly[2][0] = x1;\n\t\thud_align_center_poly[2][1] = y0;\n\t\thud_align_center_poly[2][2] = 0;\n\n\t\thud_align_center_poly[3][0] = x1;\n\t\thud_align_center_poly[3][1] = y3;\n\t\thud_align_center_poly[3][2] = 0;\n\n\t\thud_align_center_poly[4][0] = x2;\n\t\thud_align_center_poly[4][1] = y0;\n\t\thud_align_center_poly[4][2] = 0;\n\n\t\thud_align_center_poly[5][0] = x2;\n\t\thud_align_center_poly[5][1] = y3;\n\t\thud_align_center_poly[5][2] = 0;\n\n\t\thud_align_center_poly[6][0] = x3;\n\t\thud_align_center_poly[6][1] = y1;\n\t\thud_align_center_poly[6][2] = 0;\n\n\t\thud_align_center_poly[7][0] = x3;\n\t\thud_align_center_poly[7][1] = y2;\n\t\thud_align_center_poly[7][2] = 0;\n\n\t\tif (IsPointInPolygon(HUD_ALIGN_POLYCOUNT_CENTER, hud_align_center_poly, x - offset_x, y - offset_y))\n\t\t{\n\t\t\thud_align_current_poly = hud_align_center_poly;\n\t\t\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_CENTER;\n\t\t\treturn hud_align_center;\n\t\t}\n\t}\n\n\t// Default to center.\n\thud_align_current_poly = hud_align_center_poly;\n\thud_align_current_polycount = HUD_ALIGN_POLYCOUNT_CENTER;\n\treturn hud_align_center;\n}\n\n//\n// Finds the next child of the specified HUD element.\n//\nstatic hud_t *HUD_Editor_FindNextChild(hud_t *hud_element)\n{\n\tstatic hud_t\t*hud_it = NULL;\n\tstatic hud_t\t*parent = NULL;\n\thud_t\t\t\t*hud_result = NULL;\n\n\t// Reset everything if we're given a NULL argument.\n\tif(!hud_element)\n\t{\n\t\thud_it = NULL;\n\t\tparent = NULL;\n\t\treturn NULL;\n\t}\n\n\t// There's a new parent so start searching from the beginning.\n\tif(!parent || strcmp(parent->name, hud_element->name))\n\t{\n\t\tparent = hud_element;\n\t\thud_it = hud_huds;\n\t}\n\n\twhile(hud_it)\n\t{\n\t\t// Check if this HUD is placed at the parent, if so we've found a child.\n\t\tif(hud_it->place_hud && !strcmp(hud_it->place_hud->name, parent->name))\n\t\t{\n\t\t\thud_result = hud_it;\n\t\t\thud_it = hud_it->next;\n\t\t\treturn hud_result;\n\t\t}\n\n\t\thud_it = hud_it->next;\n\t}\n\n\tparent = NULL;\n\n\t// No children found.\n\treturn NULL;\n}\n\n//\n// Moves a HUD element.\n//\nstatic void HUD_Editor_Move(float dx, float dy, hud_t *hud_element)\n{\n\tif(!hud_element)\n\t{\n\t\treturn;\n\t}\n\n\tCvar_SetValue(hud_element->pos_x, hud_element->pos_x->value + dx);\n\tCvar_SetValue(hud_element->pos_y, hud_element->pos_y->value + dy);\n}\n\n//\n// Draw the current alignment.\n//\nstatic void HUD_Editor_DrawAlignment(hud_t *hud_parent)\n{\n\textern void Draw_Polygon(int x, int y, vec3_t *vertices, int num_vertices, color_t color);\n\tcolor_t c = RGBA_TO_COLOR(255, 255, 0, 50);\n\n\tif (hud_parent) {\n\t\tDraw_Polygon(hud_parent->lx, hud_parent->ly, hud_align_current_poly, hud_align_current_polycount, c);\n\t}\n\telse {\n\t\tDraw_Polygon(0, 0, hud_align_current_poly, hud_align_current_polycount, c);\n\t}\n}\n\ntypedef struct hud_resize_handle_s\n{\n\tint x;\n\tint y;\n\tint width;\n\tint height;\n} hud_resize_handle_t;\n\n#define HUD_RESIZEHANDLE_THICKNESS\t\t5\n#define HUD_RESIZEHANDLE_SIZEFACTOR\t\t0.3\n#define HUD_RESIZEHANDLE_COUNT\t\t\t8\n#define HUD_RESIZE_MAGICSCALE\t\t\t200 // HACK : This seems to work nice though...\n\n#define HUD_RESIZEHANDLE_NONE\t\t\t-1\n#define HUD_RESIZEHANDLE_TOPLEFT\t\t0\n#define HUD_RESIZEHANDLE_TOP\t\t\t1\n#define HUD_RESIZEHANDLE_TOPRIGHT\t\t2\n#define HUD_RESIZEHANDLE_RIGHT\t\t\t3\n#define HUD_RESIZEHANDLE_BOTTOMRIGHT\t4\n#define HUD_RESIZEHANDLE_BOTTOM\t\t\t5\n#define HUD_RESIZEHANDLE_BOTTOMLEFT\t\t6\n#define HUD_RESIZEHANDLE_LEFT\t\t\t7\n\n//\n// Resizes the height/width of a HUD element by a delta size and alignment.\n//\nstatic void HUD_Editor_ResizeDelta(cvar_t *size, float delta_size, hud_alignmode_t alignment)\n{\n\tif(!size)\n\t{\n\t\treturn;\n\t}\n\n\tswitch(alignment)\n\t{\n\t\tcase hud_align_right :\n\t\tcase hud_align_bottom :\n\t\t\tCvar_SetValue(size, size->value + delta_size);\n\t\t\tbreak;\n\t\tcase hud_align_left :\n\t\tcase hud_align_top :\n\t\t\tCvar_SetValue(size, size->value - delta_size);\n\t\t\tbreak;\n\t\t// unhandled\n\t\tcase hud_align_center:\n\t\tcase hud_align_topleft:\n\t\tcase hud_align_bottomleft:\n\t\tcase hud_align_bottomright:\n\t\tcase hud_align_topright:\n\t\tcase hud_align_consoleleft:\n\t\tcase hud_align_consoleright:\n\t\tcase hud_align_console:\n\t\t\tbreak;\n\t}\n}\n\n//\n// Scales a HUD element.\n//\nstatic void HUD_EditorScaleDelta(cvar_t *scale, float delta_scale, hud_alignmode_t alignment)\n{\n\tif(!scale)\n\t{\n\t\treturn;\n\t}\n\n\tswitch(alignment)\n\t{\n\t\tcase hud_align_topleft :\n\t\tcase hud_align_bottomleft :\n\t\t\tCvar_SetValue(scale, scale->value - delta_scale);\n\t\t\tbreak;\n\t\tcase hud_align_topright :\n\t\tcase hud_align_bottomright :\n\t\t\tCvar_SetValue(scale, scale->value + delta_scale);\n\t\t\tbreak;\n\t\t// unhandled\n\t\tcase hud_align_center:\n\t\tcase hud_align_top:\n\t\tcase hud_align_left:\n\t\tcase hud_align_bottom:\n\t\tcase hud_align_right:\n\t\tcase hud_align_consoleleft:\n\t\tcase hud_align_consoleright:\n\t\tcase hud_align_console:\n\t\t\tbreak;\n\t}\n\n\tif(scale->value < 0)\n\t{\n\t\tCvar_SetValue(scale, 0);\n\t}\n}\n\n//\n// Checks if the HUD element we're hovering has any dimension variables\n// and handles resizing for it if it does by drawing resize handles\n// that the user can click.\n//\nstatic qbool HUD_Editor_Resizing(hud_t *hud_hover)\n{\n\tint i\t\t\t\t\t= 0;\n\tqbool found_handle\t\t= false;\t// Did we find any handle under the cursor?\n\tstatic cvar_t *width\t= NULL;\n\tstatic cvar_t *height\t= NULL;\n\tstatic cvar_t *scale\t= NULL;\n\thud_resize_handle_t *resize_handles[HUD_RESIZEHANDLE_COUNT];\n\tstatic int last_resize_handle = HUD_RESIZEHANDLE_NONE;\n\n\tfloat mouse_x = HUD_CursorDeltaX();\n\tfloat mouse_y = HUD_CursorDeltaY();\n\n\t// Is this mode turned on?\n\tif(!hud_editor_allowresize.value)\n\t{\n\t\treturn false;\n\t}\n\n\t// Select a HUD if it hasn't already been done.\n\tif(hud_hover && hud_editor_mode == hud_editmode_move_resize\n\t\t&& !selected_hud && hud_hover)\n\t{\n\t\tselected_hud = hud_hover;\n\t\treturn true;\n\t}\n\n\tmemset(resize_handles, 0, sizeof(resize_handles));\n\n\t// Try getting any available dimension variables\n\t// that are available for this HUD element, these\n\t// aren't mandatory for all HUD elements, so only\n\t// some will have them.\n\tif(hud_hover)\n\t{\n\t\twidth\t= HUD_FindVar(hud_hover, \"width\");\n\t\theight\t= HUD_FindVar(hud_hover, \"height\");\n\t\tscale\t= HUD_FindVar(hud_hover, \"scale\");\n\n\t\tif(width)\n\t\t{\n\t\t\t// Right & left.\n\t\t\tstatic hud_resize_handle_t right;\n\t\t\tstatic hud_resize_handle_t left;\n\n\t\t\tright.width\t\t= HUD_RESIZEHANDLE_THICKNESS;\n\t\t\tright.height\t= hud_hover->lh * HUD_RESIZEHANDLE_SIZEFACTOR;\n\t\t\tright.x\t\t\t= hud_hover->lw - right.width;\n\t\t\tright.y\t\t\t= (hud_hover->lh - right.height) / 2;\n\t\t\tresize_handles[HUD_RESIZEHANDLE_RIGHT] = &right;\n\n\t\t\tleft.width\t\t= HUD_RESIZEHANDLE_THICKNESS;\n\t\t\tleft.height\t\t= hud_hover->lh * HUD_RESIZEHANDLE_SIZEFACTOR;\n\t\t\tleft.x\t\t\t= 0;\n\t\t\tleft.y\t\t\t= (hud_hover->lh - left.height) / 2;\n\t\t\tresize_handles[HUD_RESIZEHANDLE_LEFT] = &left;\n\t\t}\n\n\t\tif(height)\n\t\t{\n\t\t\t// Top & bottom\n\t\t\tstatic hud_resize_handle_t top;\n\t\t\tstatic hud_resize_handle_t bottom;\n\n\t\t\ttop.width\t\t= hud_hover->lw * HUD_RESIZEHANDLE_SIZEFACTOR;\n\t\t\ttop.height\t\t= HUD_RESIZEHANDLE_THICKNESS;\n\t\t\ttop.x\t\t\t= (hud_hover->lw - top.width) / 2;\n\t\t\ttop.y\t\t\t= 0;\n\t\t\tresize_handles[HUD_RESIZEHANDLE_TOP] = &top;\n\n\t\t\tbottom.width\t\t= hud_hover->lw * HUD_RESIZEHANDLE_SIZEFACTOR;\n\t\t\tbottom.height\t\t= HUD_RESIZEHANDLE_THICKNESS;\n\t\t\tbottom.x\t\t\t= (hud_hover->lw - bottom.width) / 2;\n\t\t\tbottom.y\t\t\t= hud_hover->lh - bottom.height;\n\t\t\tresize_handles[HUD_RESIZEHANDLE_BOTTOM] = &bottom;\n\t\t}\n\n\t\tif(scale)\n\t\t{\n\t\t\t// Top left, top right, bottom left & bottom right.\n\t\t\tstatic hud_resize_handle_t topleft;\n\t\t\tstatic hud_resize_handle_t bottomleft;\n\t\t\tstatic hud_resize_handle_t topright;\n\t\t\tstatic hud_resize_handle_t bottomright;\n\n\t\t\ttopleft.width\t\t= HUD_RESIZEHANDLE_THICKNESS;\n\t\t\ttopleft.height\t\t= HUD_RESIZEHANDLE_THICKNESS;\n\t\t\ttopleft.x\t\t\t= 0;\n\t\t\ttopleft.y\t\t\t= 0;\n\t\t\tresize_handles[HUD_RESIZEHANDLE_TOPLEFT] = &topleft;\n\n\t\t\tbottomleft.width\t= HUD_RESIZEHANDLE_THICKNESS;\n\t\t\tbottomleft.height\t= HUD_RESIZEHANDLE_THICKNESS;\n\t\t\tbottomleft.x\t\t= 0;\n\t\t\tbottomleft.y\t\t= hud_hover->lh - bottomleft.height;\n\t\t\tresize_handles[HUD_RESIZEHANDLE_BOTTOMLEFT] = &bottomleft;\n\n\t\t\ttopright.width\t\t= HUD_RESIZEHANDLE_THICKNESS;\n\t\t\ttopright.height\t\t= HUD_RESIZEHANDLE_THICKNESS;\n\t\t\ttopright.x\t\t\t= hud_hover->lw - topright.width;\n\t\t\ttopright.y\t\t\t= 0;\n\t\t\tresize_handles[HUD_RESIZEHANDLE_TOPRIGHT] = &topright;\n\n\t\t\tbottomright.width\t= HUD_RESIZEHANDLE_THICKNESS;\n\t\t\tbottomright.height\t= HUD_RESIZEHANDLE_THICKNESS;\n\t\t\tbottomright.x\t\t= hud_hover->lw - bottomright.width;\n\t\t\tbottomright.y\t\t= hud_hover->lh - bottomright.height;\n\t\t\tresize_handles[HUD_RESIZEHANDLE_BOTTOMRIGHT] = &bottomright;\n\t\t}\n\t}\n\n\tfor(i = 0; i < HUD_RESIZEHANDLE_COUNT; i++)\n\t{\n\t\t// Non existant for this HUD element.\n\t\tif(!resize_handles[i])\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tif(hud_editor_mode == hud_editmode_move_resize)\n\t\t{\n\t\t\t// We're in resize mode, so check if we're clicking any of the\n\t\t\t// resize handles.\n\n\t\t\tif((selected_hud && (last_resize_handle == i)) ||\n\t\t\t\t  (hud_mouse_x >= (selected_hud->lx + resize_handles[i]->x)\n\t\t\t\t&& (hud_mouse_x <= (selected_hud->lx + resize_handles[i]->x + resize_handles[i]->width))\n\t\t\t\t&& (hud_mouse_y >= (selected_hud->ly + resize_handles[i]->y))\n\t\t\t\t&& (hud_mouse_y <= (selected_hud->ly + resize_handles[i]->y + resize_handles[i]->height))))\n\t\t\t{\n\t\t\t\t// Keep track of which resize handle we're grabbing\n\t\t\t\t// so that it doesn't matter if the mouse \"slips\" outside\n\t\t\t\t// it's bounding box as long as the mouse is still pressed.\n\t\t\t\tif(last_resize_handle < 0 || last_resize_handle != i)\n\t\t\t\t{\n\t\t\t\t\tlast_resize_handle = i;\n\t\t\t\t}\n\n\t\t\t\tfound_handle = true;\n\n\t\t\t\t// Draw the resize handle highlighted.\n\t\t\t\tDraw_AlphaFillRGB(\n\t\t\t\t\tselected_hud->lx + resize_handles[last_resize_handle]->x,\n\t\t\t\t\tselected_hud->ly + resize_handles[last_resize_handle]->y,\n\t\t\t\t\tresize_handles[last_resize_handle]->width,\n\t\t\t\t\tresize_handles[last_resize_handle]->height,\n\t\t\t\t\tRGBA_TO_COLOR(255, 255, 0, 125));\n\n\t\t\t\t// Check which resize handle that has been selected\n\t\t\t\t// and resize the HUD element accordingly.\n\t\t\t\tswitch(i)\n\t\t\t\t{\n\t\t\t\t\tcase HUD_RESIZEHANDLE_RIGHT :\n\t\t\t\t\t\tHUD_Editor_ResizeDelta(width, mouse_x, hud_align_right);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase HUD_RESIZEHANDLE_LEFT :\n\t\t\t\t\t\tHUD_Editor_ResizeDelta(width, mouse_x, hud_align_left);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase HUD_RESIZEHANDLE_TOP :\n\t\t\t\t\t\tHUD_Editor_ResizeDelta(height, mouse_y, hud_align_top);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase HUD_RESIZEHANDLE_BOTTOM :\n\t\t\t\t\t\tHUD_Editor_ResizeDelta(height, mouse_y, hud_align_bottom);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase HUD_RESIZEHANDLE_TOPRIGHT :\n\t\t\t\t\t\t// HACK : Dividing by 200 seems to work fine, but this could probably be done a lot nicer.\n\t\t\t\t\t\tHUD_EditorScaleDelta(scale, (mouse_x - mouse_y) / HUD_RESIZE_MAGICSCALE, hud_align_topright);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase HUD_RESIZEHANDLE_BOTTOMRIGHT :\n\t\t\t\t\t\tHUD_EditorScaleDelta(scale, (mouse_x + mouse_y) / HUD_RESIZE_MAGICSCALE, hud_align_bottomright);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase HUD_RESIZEHANDLE_TOPLEFT :\n\t\t\t\t\t\tHUD_EditorScaleDelta(scale, (mouse_x + mouse_y) / HUD_RESIZE_MAGICSCALE, hud_align_topleft);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase HUD_RESIZEHANDLE_BOTTOMLEFT :\n\t\t\t\t\t\tHUD_EditorScaleDelta(scale, (mouse_x - mouse_y) / HUD_RESIZE_MAGICSCALE, hud_align_bottomleft);\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Recalculate all HUD elements.\n\t\t\t\tHUD_Recalculate();\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\telse if(hud_editor_prevmode == hud_editmode_move_resize)\n\t\t{\n\t\t\t// We left resize mode.\n\t\t\tselected_hud = NULL;\n\t\t\tlast_resize_handle = HUD_RESIZEHANDLE_NONE;\n\t\t}\n\t\telse if(hud_hover)\n\t\t{\n\t\t\t// If we're hovering a HUD, always draw all it's resize handles.\n\t\t\tif((hud_mouse_x >= (hud_hover->lx + resize_handles[i]->x)\n\t\t\t\t&& hud_mouse_x <= (hud_hover->lx + resize_handles[i]->x + resize_handles[i]->width)\n\t\t\t\t&& hud_mouse_y >= (hud_hover->ly + resize_handles[i]->y)\n\t\t\t\t&& hud_mouse_y <= (hud_hover->ly + resize_handles[i]->y + resize_handles[i]->height)))\n\t\t\t{\n\t\t\t\t// Highlight the resize handle under the cursor.\n\t\t\t\tDraw_AlphaFillRGB(hud_hover->lx + resize_handles[i]->x, hud_hover->ly + resize_handles[i]->y,\n\t\t\t\t\tresize_handles[i]->width, resize_handles[i]->height,\n\t\t\t\t\tRGBA_TO_COLOR(255, 255, 0, 125));\n\n\t\t\t\t// Set the resize cursor icon since we're hovering a resize handle.\n\t\t\t\tscr_cursor_icon = hud_editor_resize_icon;\n\t\t\t\tfound_handle = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Normal resize handle.\n\t\t\t\tDraw_AlphaFillRGB(hud_hover->lx + resize_handles[i]->x, hud_hover->ly + resize_handles[i]->y,\n\t\t\t\tresize_handles[i]->width, resize_handles[i]->height,\n\t\t\t\tRGBA_TO_COLOR(255, 255, 0, 50));\n\t\t\t}\n\t\t}\n\t}\n\n\t// We didn't hover any resize handle so show no icon.\n\tif(!found_handle)\n\t{\n\t\tscr_cursor_icon = NULL;\n\t}\n\n\t// We didn't perform any action, so let others try.\n\treturn false;\n}\n\nstatic qbool hud_editor_locked_axis_is_x = true;\t// Are we locking X- or Y-axis movement?\nstatic qbool hud_editor_lockaxis_found= false;\t\t// Have we found what axist to lock on to?\nstatic qbool hud_editor_finding_lockaxis = false;\t// Are we in the process of finding the lock axis?\n\n//\n// Check if we're supposed to be moving anything.\n//\nstatic qbool HUD_Editor_Moving(hud_t *hud_hover)\n{\n\t// Mouse delta\n\tfloat mouse_x = HUD_CursorDeltaX();\n\tfloat mouse_y = HUD_CursorDeltaY();\n\n\t// Is this mode allowed?\n\tif(!hud_editor_allowmove.value)\n\t{\n\t\treturn false;\n\t}\n\n\t// Set the move cursor icon if we're hovering a HUD element\n\t// unless it's not already set (resize may have set it if\n\t// a resize handle is being hovered).\n\tif(hud_hover && !scr_cursor_icon)\n\t{\n\t\tscr_cursor_icon = hud_editor_move_icon;\n\t}\n\telse if(!hud_hover)\n\t{\n\t\t// Not hovering anything so show no cursor.\n\t\tscr_cursor_icon = NULL;\n\t}\n\n\t// Left mousebutton down = lets move it !\n\tif (hud_editor_mode == hud_editmode_move_resize || hud_editor_mode == hud_editmode_move_lockedaxis)\n\t{\n\t\t// If we just entered movement mode, nothing is selected\n\t\t// so select the hud we're hovering to start with.\n\t\tif(!selected_hud && hud_hover)\n\t\t{\n\t\t\tselected_hud = hud_hover;\n\t\t\treturn true;\n\t\t}\n\n\t\tif(selected_hud)\n\t\t{\n\t\t\t// Move using the mouse delta instead of the absolute\n\t\t\t// mouse cursor coordinates on the screen.\n\n\t\t\t// Lock the movement to the axis that the user starts\n\t\t\t// dragging the HUD element along if we're in locked-axis mode.\n\t\t\tif(hud_editor_mode == hud_editmode_move_lockedaxis)\n\t\t\t{\n\t\t\t\t// We haven't found the axis.\n\t\t\t\tif(!hud_editor_lockaxis_found)\n\t\t\t\t{\n\t\t\t\t\tstatic float last_mouse_x = 0.0;\n\t\t\t\t\tstatic float last_mouse_y = 0.0;\n\n\t\t\t\t\tif(!hud_editor_finding_lockaxis)\n\t\t\t\t\t{\n\t\t\t\t\t\t// Start trying to find the lock axis.\n\t\t\t\t\t\thud_editor_finding_lockaxis = true;\n\t\t\t\t\t\tlast_mouse_x = 0;\n\t\t\t\t\t\tlast_mouse_y = 0;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// Get the delta of the mouse movement from when the\n\t\t\t\t\t\t// user clicked the mouse button and started dragging.\n\t\t\t\t\t\tfloat mouse_delta_x = fabs(last_mouse_x - mouse_x);\n\t\t\t\t\t\tfloat mouse_delta_y = fabs(last_mouse_y - mouse_y);\n\n\t\t\t\t\t\t// Increment the mouse movement since last frame.\n\t\t\t\t\t\tlast_mouse_x += mouse_x;\n\t\t\t\t\t\tlast_mouse_y += mouse_y;\n\n\t\t\t\t\t\t// If the mouse has moved more than 5 pixels in any\n\t\t\t\t\t\t// direction we decide to lock onto that axis.\n\t\t\t\t\t\tif(mouse_delta_x > 5 || mouse_delta_y > 5)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\thud_editor_locked_axis_is_x = (mouse_delta_x > mouse_delta_y);\n\t\t\t\t\t\t\thud_editor_lockaxis_found = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Move while locked to the axis.\n\t\t\t\tif(hud_editor_locked_axis_is_x)\n\t\t\t\t{\n\t\t\t\t\t// Move only along X-axis.\n\t\t\t\t\tHUD_Editor_Move(mouse_x, 0, hud_hover);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// Move only along Y-axis.\n\t\t\t\t\tHUD_Editor_Move(0, mouse_y, hud_hover);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Ordinary move.\n\t\t\t\tHUD_Editor_Move(mouse_x, mouse_y, hud_hover);\n\t\t\t}\n\n\t\t\tHUD_Recalculate();\n\t\t\treturn true;\n\t\t}\n\t}\n\telse if((hud_editor_prevmode == hud_editmode_move_resize && hud_editor_mode != hud_editmode_move_lockedaxis)\n\t\t || (hud_editor_prevmode == hud_editmode_move_lockedaxis && hud_editor_mode != hud_editmode_move_resize))\n\t{\n\t\t// Only deselect if we're going from a move mode to a non-move mode (Such as align/place).\n\n\t\t// We've left move mode, so deselect.\n\t\tselected_hud = NULL;\n\t\thud_editor_lockaxis_found = false;\n\t\thud_editor_finding_lockaxis = false;\n\t}\n\n\t// We haven't handled the input.\n\treturn false;\n}\n\n//\n// Handles input from mouse if in alignment mode.\n//\nstatic qbool HUD_Editor_Aligning(hud_t *hud_hover)\n{\n\t// Is this mode allowed?\n\tif(!hud_editor_allowalign.value)\n\t{\n\t\treturn false;\n\t}\n\n\tif(hud_editor_mode == hud_editmode_align)\n\t{\n\t\t// If we just entered alignment mode, nothing is selected\n\t\t// so select the hud we're hovering to start with.\n\t\tif(!selected_hud && hud_hover)\n\t\t{\n\t\t\tselected_hud = hud_hover;\n\t\t\treturn true;\n\t\t}\n\n\t\t// Set the align icon for the cursor.\n\t\tscr_cursor_icon = hud_editor_align_icon;\n\n\t\t// We have something selected so show some visual\n\t\t// feedback for when aligning to the user.\n\t\tif(selected_hud)\n\t\t{\n\t\t\tif(selected_hud->place_hud)\n\t\t\t{\n\t\t\t\t// Placed at another HUD, so align onto that.\n\t\t\t\thud_alignmode = HUD_Editor_GetAlignment(hud_mouse_x, hud_mouse_y, selected_hud->place_hud);\n\t\t\t\tHUD_Editor_DrawAlignment(selected_hud->place_hud);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Placed on the screen.\n\t\t\t\thud_alignmode = HUD_Editor_GetAlignment(hud_mouse_x, hud_mouse_y, NULL);\n\t\t\t\tHUD_Editor_DrawAlignment(NULL);\n\t\t\t}\n\t\t}\n\t}\n\telse if(hud_editor_prevmode == hud_editmode_align && keydown[K_ALT])\n\t{\n\t\t// The user just released the mousebutton but is still holding\n\t\t// down ALT. If the user releases ALT before the mouse button\n\t\t// the operation will be cancelled. So commit the users actions.\n\n\t\t// We must have something to align.\n\t\tif(selected_hud)\n\t\t{\n\t\t\t// Reset position.\n\t\t\tCvar_Set(selected_hud->pos_x, \"0\");\n\t\t\tCvar_Set(selected_hud->pos_y, \"0\");\n\n\t\t\t// Align to the area the mouse is placed over (previously set in hud_alignmode).\n\t\t\tCbuf_AddText(va(\"align %s %s\\n\", selected_hud->name, HUD_Editor_GetAlignmentString(hud_alignmode)));\n\t\t\tHUD_Recalculate();\n\n\t\t\t// Free selection.\n\t\t\tselected_hud = NULL;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n//\n// Handles feedback/commiting of actions when in placement mode.\n//\nstatic qbool HUD_Editor_Placing(hud_t *hud_hover)\n{\n\t// Is this mode allowed?\n\tif(!hud_editor_allowplace.value)\n\t{\n\t\treturn false;\n\t}\n\n\t// Show the place icon at the cursor if ctrl is pressed\n\t// while hovering a HUD element.\n\tif(hud_hover && keydown[K_CTRL])\n\t{\n\t\t// Set the place cursor icon.\n\t\tscr_cursor_icon = hud_editor_place_icon;\n\t}\n\telse if(!hud_hover)\n\t{\n\t\tscr_cursor_icon = NULL;\n\t}\n\n\tif(hud_editor_mode == hud_editmode_place)\n\t{\n\t\t// If we just entered placement mode, nothing is selected\n\t\t// so select the hud we're hovering to start with.\n\t\tif(!selected_hud && hud_hover)\n\t\t{\n\t\t\tselected_hud = hud_hover;\n\t\t\treturn true;\n\t\t}\n\n\t\t// Set the place cursor icon.\n\t\tscr_cursor_icon = hud_editor_place_icon;\n\n\t\tif(selected_hud)\n\t\t{\n\t\t\t// Find the center of the selected HUD so we know where\n\t\t\t// to draw the line from.\n\n\t\t\tif(hud_hover)\n\t\t\t{\n\t\t\t\t// We're trying to place the HUD on itself or on the HUD it's already placed at.\n\t\t\t\tif(hud_hover == selected_hud || (selected_hud->place_hud && selected_hud->place_hud == hud_hover))\n\t\t\t\t{\n\t\t\t\t\t// Red \"not allowed\".\n\t\t\t\t\tDraw_AlphaRectangleRGB(hud_hover->lx, hud_hover->ly, hud_hover->lw, hud_hover->lh, 1, true, RGBA_TO_COLOR(255, 0, 0, 25));\n\t\t\t\t\tDraw_AlphaRectangleRGB(hud_hover->lx, hud_hover->ly, hud_hover->lw, hud_hover->lh, 1, false, RGBA_TO_COLOR(255, 0, 0, 255));\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// Green \"allowed\" placement.\n\t\t\t\t\tDraw_AlphaRectangleRGB(hud_hover->lx, hud_hover->ly, hud_hover->lw, hud_hover->lh, 1, true, RGBA_TO_COLOR(0, 255, 0, 25));\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Placement at the screen (We don't care about stuff like IFREE, HBAR, SBAR and so on).\n\t\t\tif(!strcmp(selected_hud->place->string, \"screen\"))\n\t\t\t{\n\t\t\t\t// Not allowed, already placed there.\n\t\t\t\tDraw_AlphaRectangleRGB(0, 0, vid.width, vid.height, 1, true, RGBA_TO_COLOR(255, 0, 0, 25));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Allowed.\n\t\t\t\tDraw_AlphaRectangleRGB(0, 0, vid.width, vid.height, 1, true, RGBA_TO_COLOR(0, 255, 0, 25));\n\t\t\t}\n\t\t}\n\t}\n\telse if(hud_editor_prevmode == hud_editmode_place && keydown[K_CTRL])\n\t{\n\t\t// We've just exited placement mode, but control is still pressed,\n\t\t// that means we should place the selected_hud.\n\t\t// (If you release ctrl before you release Mouse 1, you cancel the place operation).\n\n\t\tif(selected_hud)\n\t\t{\n\t\t\t// If we're hovering a HUD place it there.\n\t\t\tif(hud_hover)\n\t\t\t{\n\t\t\t\t// We're trying to place the HUD on itself or on the HUD it's already placed at so do nothing.\n\t\t\t\tif(hud_hover == selected_hud || (selected_hud->place_hud && selected_hud->place_hud == hud_hover))\n\t\t\t\t{\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\t// Place at other HUD.\n\t\t\t\tCvar_Set(selected_hud->align_x, \"center\");\n\t\t\t\tCvar_Set(selected_hud->align_y, \"center\");\n\t\t\t\tCvar_Set(selected_hud->pos_x, \"0\");\n\t\t\t\tCvar_Set(selected_hud->pos_y, \"0\");\n\t\t\t\tCvar_Set(selected_hud->place, hud_hover->name);\n\n\t\t\t\t// Make sure the child has a higher order.\n\t\t\t\tif((int)selected_hud->order->value <= (int)hud_hover->order->value)\n\t\t\t\t{\n\t\t\t\t\tCvar_SetValue(selected_hud->order, hud_hover->order->value + 1);\n\t\t\t\t}\n\n\t\t\t\tHUD_Recalculate();\n\n\t\t\t\t// Free selection.\n\t\t\t\tselected_hud = NULL;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Mouse button was released on on the \"screen\".\n\n\t\t\t\tif(!strcmp(selected_hud->place->string, \"screen\"))\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Place at other HUD.\n\t\t\t\tCvar_Set(selected_hud->align_x, \"center\");\n\t\t\t\tCvar_Set(selected_hud->align_y, \"center\");\n\t\t\t\tCvar_Set(selected_hud->pos_x, \"0\");\n\t\t\t\tCvar_Set(selected_hud->pos_y, \"0\");\n\t\t\t\tCvar_Set(selected_hud->place, \"screen\");\n\t\t\t\tHUD_Recalculate();\n\n\t\t\t\t// Free selection.\n\t\t\t\tselected_hud = NULL;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\t// We weren't placing something so check other states also.\n\treturn false;\n}\n\n// =============================================================================\n//\t\t\t\t\t\t\tHUD Editor Grep Handles.\n// =============================================================================\n// These show up when a HUD element is moved completly offscreen (by accident\n// most likely), so that the user can still grab it and move it back onto the\n// screen again.\n// =============================================================================\n\n//\n// Finds a HUD grephandle (if the hud element has gone offscreen).\n//\nstatic hud_grephandle_t *HUD_Editor_FindGrep(hud_t *hud_element)\n{\n\thud_grephandle_t *greps_it = NULL;\n\tgreps_it = hud_greps;\n\n\twhile(greps_it)\n\t{\n\t\tif(!strcmp(greps_it->hud->name, hud_element->name))\n\t\t{\n\t\t\treturn greps_it;\n\t\t}\n\n\t\tgreps_it = greps_it->next;\n\t}\n\n\treturn NULL;\n}\n\n//\n// Returns an \"offscreen\" arrow in the correct direction based on where\n// offscreen the HUD element is located.\n//\nstatic char *HUD_Editor_GetGrepArrow(hud_grephandle_t *grep)\n{\n\tswitch(grep->pos)\n\t{\n\t\tcase pos_top :\t\treturn \"/\\\\\";\n\t\tcase pos_bottom :\treturn \"\\\\/\";\n\t\tcase pos_left :\t\treturn \"<<<\";\n\t\tcase pos_right :\treturn \">>>\";\n\t\tdefault :\t\t\treturn \"\";\n\t}\n}\n\n//\n// Draws the grephandles.\n//\nstatic void HUD_Editor_DrawGreps(void)\n{\n\tclrinfo_t color, highlight;\n\thud_grephandle_t *greps_it = NULL;\n\tgreps_it = hud_greps;\n\n\t// Highlight color (yellow).\n\thighlight.c = RGBA_TO_COLOR(0, 255, 0, 255);\n\thighlight.i = 0;\n\n\t// Orange.\n\tcolor.c = RGBA_TO_COLOR(255, 150, 0, 255);\n\tcolor.i = 0;\n\n\twhile(greps_it)\n\t{\n\t\tDraw_ColoredString3(greps_it->x, greps_it->y,\n\t\t\tva(\"%s %s\", HUD_Editor_GetGrepArrow(greps_it), greps_it->hud->name),\n\t\t\t(greps_it->highlighted ? &highlight : &color), 1, 0);\n\n\t\tgreps_it = greps_it->next;\n\t}\n}\n\n//\n// Get's the position offscreen for a HUD element\n// left/right/top/bottom or visible if it's not offscreen.\n//\nstatic hud_greppos_t HUD_Editor_GetHudGrepPosition(hud_t *hud)\n{\n\tif(hud->lx + hud->lw <= 0)\n\t{\n\t\treturn pos_left;\n\t}\n\telse if(hud->lx >= (signed)vid.width)\n\t{\n\t\treturn pos_right;\n\t}\n\telse if(hud->ly + hud->lh <= 0)\n\t{\n\t\treturn pos_top;\n\t}\n\telse if(hud->ly >= (signed)vid.height)\n\t{\n\t\treturn pos_bottom;\n\t}\n\n\treturn pos_visible;\n}\n\n//\n// Positions a grephandle based on it's position and where the HUD element\n// it's associated with is located.\n//\nstatic void HUD_Editor_PositionGrep(hud_t *hud_element, hud_grephandle_t *grep)\n{\n\t// Get the position of the grephandle.\n\tgrep->pos = HUD_Editor_GetHudGrepPosition(hud_element);\n\n\t// Position the grephandle on screen.\n\tswitch(grep->pos)\n\t{\n\t\tcase pos_top :\n\t\t\tgrep->x\t= hud_element->lx;\n\t\t\tgrep->y\t= 5;\n\t\t\tbreak;\n\t\tcase pos_bottom :\n\t\t\tgrep->x\t= hud_element->lx;\n\t\t\tgrep->y\t= vid.height - grep->height - 5;\n\t\t\tbreak;\n\t\tcase pos_left :\n\t\t\tgrep->x = 5;\n\t\t\tgrep->y = hud_element->ly;\n\t\t\tbreak;\n\t\tcase pos_right :\n\t\t\tgrep->x = vid.width - grep->width - 5;\n\t\t\tgrep->y = hud_element->ly;\n\t\t\tbreak;\n\t\tdefault :\n\t\t\tgrep->x = hud_element->lx;\n\t\t\tgrep->y = hud_element->ly;\n\t\t\tbreak;\n\t}\n}\n\n//\n// Creates a new grephandle and associates it with a HUD element that is offscreen.\n//\nstatic hud_grephandle_t *HUD_Editor_CreateGrep(hud_t *hud_element)\n{\n\thud_grephandle_t *grep = NULL;\n\n\tgrep\t\t\t= Q_malloc(sizeof(hud_grephandle_t));\n\tmemset(grep, 0, sizeof(*grep));\n\n\tgrep->width\t\t= 8 * (4 + strlen(hud_element->name));\n\tgrep->height\t= 8;\n\tgrep->hud\t\t= hud_element;\n\tgrep->previous\t= NULL;\n\tgrep->next\t\t= hud_greps;\n\tif(hud_greps)\n\t{\n\t\thud_greps->previous = grep;\n\t}\n\n\thud_greps = grep;\n\n\tHUD_Editor_PositionGrep(hud_element, grep);\n\n\treturn grep;\n}\n\n//\n// Destroys a grephandle (called if it's no longer offscreen).\n//\nstatic void HUD_Editor_DestroyGrep(hud_grephandle_t *grep)\n{\n\t// Already destroyed.\n\tif(!grep)\n\t{\n\t\treturn;\n\t}\n\n\t// Relink any neighbours in the list.\n\tif(grep->next && grep->previous)\n\t{\n\t\tgrep->previous->next = grep->next;\n\t\tgrep->next->previous = grep->previous;\n\t}\n\telse if(grep->next)\n\t{\n\t\tgrep->next->previous = NULL;\n\t\thud_greps = grep->next;\n\t}\n\telse if(grep->previous)\n\t{\n\t\tgrep->previous->next = NULL;\n\t}\n\telse\n\t{\n\t\thud_greps = NULL;\n\t}\n\n\tmemset(grep, 0, sizeof(*grep));\n\n\tQ_free(grep);\n}\n\n//\n// Finds a HUD element associated with the grephandle under the mouse cursor.\n//\nstatic hud_t *HUD_Editor_FindHudByGrep(void)\n{\n\thud_grephandle_t *greps_it = NULL;\n\tgreps_it = hud_greps;\n\n\twhile(greps_it)\n\t{\n\t\tif (greps_it->hud\n\t\t\t&& hud_mouse_x >= greps_it->x\n\t\t\t&& hud_mouse_x <= (greps_it->x + greps_it->width)\n\t\t\t&& hud_mouse_y >= greps_it->y\n\t\t\t&& hud_mouse_y <= (greps_it->y + greps_it->height))\n\t\t{\n\t\t\tgreps_it->highlighted = true;\n\t\t\treturn greps_it->hud;\n\t\t}\n\n\t\tgreps_it->highlighted = false;\n\n\t\tgreps_it = greps_it->next;\n\t}\n\n\treturn NULL;\n}\n\n//\n// Draws the outline of the visible HUD elements.\n//\nstatic void HUD_Editor_DrawOutlines(void)\n{\n\thud_t *temp_hud\t= hud_huds;\n\n\tif(!temp_hud || !hud_editor_showoutlines)\n\t{\n\t\treturn;\n\t}\n\n\twhile(temp_hud->next)\n\t{\n\t\t// Check if the item is visible.\n\t\tif (!temp_hud->show->value || (temp_hud->place_hud && !temp_hud->place_hud->show->value))\n\t\t{\n\t\t\ttemp_hud = temp_hud->next;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Draw an outline for all hud elements (faint).\n\t\tDraw_AlphaRectangleRGB(temp_hud->lx, temp_hud->ly, temp_hud->lw, temp_hud->lh, 1, false, RGBA_TO_COLOR(0, 255, 0, 25));\n\n\t\ttemp_hud = temp_hud->next;\n\t}\n}\n\n//\n// Finds if there's any HUD under the cursor and draws outlines for all HUD elements.\n//\nstatic qbool HUD_Editor_FindHudUnderCursor(hud_t **hud)\n{\n\thud_grephandle_t\t*grep\t\t= NULL;\n\thud_greppos_t\t\tpos\t\t\t= pos_visible;\n\thud_t\t\t\t\t*temp_hud\t= hud_huds;\n\tqbool\t\t\t\tfound\t\t= false;\n\n\tif(!temp_hud)\n\t{\n\t\treturn false;\n\t}\n\n\t// Check if we already had something selected since last time and was moving.\n\tif(selected_hud && (hud_editor_mode == hud_editmode_move_resize || hud_editor_mode == hud_editmode_move_lockedaxis))\n\t{\n\t\tfound = true;\n\t\t(*hud) = selected_hud;\n\t}\n\n\t// If the hover list is being showed, only look for HUDs in that\n\t// not the HUD's that are below the cursor.\n\tif(hud_editor_mode == hud_editmode_hoverlist && hud_hoverlist_count > 0)\n\t{\n\t\t(*hud) = HUD_Editor_FindHoverListSelection(hud_hoverlist);\n\t\treturn (hud != NULL);\n\t}\n\n\t// Reset the hoverlist since we might be hovering something new.\n\thud_hoverlist_count = 0;\n\thud_hoverlist = NULL;\n\n\twhile(temp_hud)\n\t{\n\t\t// Not visible.\n\t\tif (!temp_hud->show->value || (temp_hud->place_hud && !temp_hud->place_hud->show->value))\n\t\t{\n\t\t\ttemp_hud = temp_hud->next;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// We found one.\n\t\tif (hud_mouse_x >= temp_hud->lx\n\t\t\t&& hud_mouse_x <= (temp_hud->lx + temp_hud->lw)\n\t\t\t&& hud_mouse_y >= temp_hud->ly\n\t\t\t&& hud_mouse_y <= (temp_hud->ly + temp_hud->lh))\n\t\t{\n\t\t\t// If we're moving/resizing something only continue checking for\n\t\t\t// more HUD elements we have the mouse over if we haven't already\n\t\t\t// found one. If we don't do this when you drag a HUD element\n\t\t\t// over another HUD element that has a greater Z-order the selection\n\t\t\t// will jump to that HUD element, and you'll start moving that instead.\n\t\t\t//\n\t\t\t// Vice versa if we would skip any HUD item after we've found one\n\t\t\t// when not already moving an item, it would mean that we could only\n\t\t\t// select HUD elements that are topmost in the Z-order, so an item\n\t\t\t// placed within another item would not be selectable.\n\t\t\tif(((hud_editor_mode == hud_editmode_move_resize || hud_editor_mode == hud_editmode_move_lockedaxis) && !found)\n\t\t\t\t|| (hud_editor_mode != hud_editmode_move_resize && hud_editor_mode != hud_editmode_move_lockedaxis))\n\t\t\t{\n\t\t\t\tfound = true;\n\t\t\t\t(*hud) = temp_hud;\n\n\t\t\t\t// Add this HUD to the list of HUDs under the cursor.\n\t\t\t\tHUD_Editor_AddHoverHud(HUD_Editor_CreateHoverHud(temp_hud));\n\t\t\t}\n\t\t}\n\n\t\t// Check if the hud element is offscreen and\n\t\t// if there's any grep handle for this hud element\n\t\t{\n\t\t\tpos = HUD_Editor_GetHudGrepPosition(temp_hud);\n\t\t\tgrep = HUD_Editor_FindGrep(temp_hud);\n\n\t\t\tif(pos != pos_visible)\n\t\t\t{\n\t\t\t\t// We didn't find any grep handle so create one.\n\t\t\t\tif(!grep)\n\t\t\t\t{\n\t\t\t\t\tgrep = HUD_Editor_CreateGrep(temp_hud);\n\t\t\t\t}\n\n\t\t\t\t// Position the grep if we got one.\n\t\t\t\tif(grep)\n\t\t\t\t{\n\t\t\t\t\tHUD_Editor_PositionGrep(temp_hud, grep);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// The HUD element is visbile, so no need for a grep handle for it.\n\t\t\t\tHUD_Editor_DestroyGrep(grep);\n\t\t\t}\n\t\t}\n\n\t\ttemp_hud = temp_hud->next;\n\t}\n\n\t// We didn't find any HUD's under the cursor, but\n\t// what about \"grep handles\" (for offscreen HUDs).\n\tif(!found)\n\t{\n\t\ttemp_hud = HUD_Editor_FindHudByGrep();\n\n\t\tif(temp_hud)\n\t\t{\n\t\t\tfound = true;\n\t\t\t(*hud) = temp_hud;\n\t\t}\n\t}\n\n\t// Nothing found, make sure result is NULL.\n\tif (!found)\n\t{\n\t\t(*hud) = NULL;\n\t}\n\n\treturn found;\n}\n\n//\n// Returns the point a HUD is aligned to on it's parent in screen coordinates.\n//\nstatic void HUD_Editor_GetAlignmentPoint(hud_t *hud, int *x, int *y)\n{\n\textern float scr_con_current; // Console height. console.c\n\tint parent_x = 0;\n\tint parent_y = 0;\n\tint parent_w = 0;\n\tint parent_h = 0;\n\thud_alignmode_t alignmode = HUD_Editor_GetAlignmentFromString(va(\"%s %s\", hud->align_x->string, hud->align_y->string));\n\n\tif(hud->place_hud)\n\t{\n\t\t// Placed at another HUD.\n\t\tparent_x = hud->place_hud->lx;\n\t\tparent_y = hud->place_hud->ly;\n\t\tparent_w = hud->place_hud->lw;\n\t\tparent_h = hud->place_hud->lh;\n\n\t\t(*x) = HUD_CENTER_X(hud->place_hud);\n\t\t(*y) = HUD_CENTER_Y(hud->place_hud);\n\t}\n\telse\n\t{\n\t\t// Placed at \"screen\".\n\t\tparent_x = 0;\n\t\tparent_y = 0;\n\t\tparent_w = vid.width;\n\t\tparent_h = vid.height;\n\n\t\t(*x) = vid.width / 2;\n\t\t(*y) = vid.height / 2;\n\t}\n\n\tswitch(alignmode)\n\t{\n\t\tdefault:\n\t\tcase hud_align_center:\n\t\t\t// Already set.\n\t\t\tbreak;\n\t\tcase hud_align_right:\n\t\t\t(*x) = parent_x + parent_w;\n\t\t\t(*y) = parent_y + (parent_h / 2);\n\t\t\tbreak;\n\t\tcase hud_align_topright:\n\t\t\t(*x) = parent_x + parent_w;\n\t\t\t(*y) = parent_y;\n\t\t\tbreak;\n\t\tcase hud_align_top:\n\t\t\t(*x) = parent_x + (parent_w / 2);\n\t\t\t(*y) = parent_y;\n\t\t\tbreak;\n\t\tcase hud_align_topleft:\n\t\t\t(*x) = parent_x;\n\t\t\t(*y) = parent_y;\n\t\t\tbreak;\n\t\tcase hud_align_left:\n\t\t\t(*x) = parent_x;\n\t\t\t(*y) = parent_y + (parent_h / 2);\n\t\t\tbreak;\n\t\tcase hud_align_bottomleft:\n\t\t\t(*x) = parent_x;\n\t\t\t(*y) = parent_y + parent_h;\n\t\t\tbreak;\n\t\tcase hud_align_bottom:\n\t\t\t(*x) = parent_x + (parent_w / 2);\n\t\t\t(*y) = parent_y + parent_h;\n\t\t\tbreak;\n\t\tcase hud_align_bottomright:\n\t\t\t(*x) = parent_x + parent_w;\n\t\t\t(*y) = parent_y + parent_h;\n\t\t\tbreak;\n\t\tcase hud_align_consoleleft:\n\t\t\t(*x) = parent_x;\n\t\t\t(*y) = scr_con_current;\n\t\t\tbreak;\n\t\tcase hud_align_console:\n\t\t\t(*x) = parent_x + (parent_w / 2);\n\t\t\t(*y) = scr_con_current;\n\t\t\tbreak;\n\t\tcase hud_align_consoleright:\n\t\t\t(*x) = parent_x + parent_w;\n\t\t\t(*y) = scr_con_current;\n\t\t\tbreak;\n\t}\n}\n\n//\n// Draws a green line to each corner of a HUD element from a specified point.\n//\n/*\nstatic void HUD_Editor_DrawLinesToEachCorner(hud_t *hud, int x, int y)\n{\n\tDraw_AlphaLineRGB(hud->lx, hud->ly,\t\t\t\t\t\tx, y, 1, RGBA_TO_COLOR(0, 255, 0, 25));\n\tDraw_AlphaLineRGB(hud->lx, hud->ly + hud->lh,\t\t\tx, y, 1, RGBA_TO_COLOR(0, 255, 0, 25));\n\tDraw_AlphaLineRGB(hud->lx + hud->lw, hud->ly,\t\t\tx, y, 1, RGBA_TO_COLOR(0, 255, 0, 25));\n\tDraw_AlphaLineRGB(hud->lx + hud->lw, hud->ly + hud->lh, x, y, 1, RGBA_TO_COLOR(0, 255, 0, 25));\n}\n*/\n\n//\n// Draws connections to/from a HUD element.\n//\nstatic void HUD_Editor_DrawConnections(hud_t *hud_hover)\n{\n\thud_t *child = NULL;\n\tint align_x = 0.0;\n\tint align_y = 0.0;\n\n\tif (!hud_hover || !hud_editor_showoutlines)\n\t{\n\t\treturn;\n\t}\n\n\t// Get the alignment point at the parent in screen coordinates.\n\tHUD_Editor_GetAlignmentPoint(hud_hover, &align_x, &align_y);\n\n\t// Draw a line to the parent of the HUD we're hovering.\n\tDraw_AlphaLineRGB(HUD_CENTER_X(hud_hover), HUD_CENTER_Y(hud_hover), align_x, align_y, 1, RGBA_TO_COLOR(0, 255, 0, 25));\n\n\t// Draw a line to all children of the HUD we're hovering.\n\twhile((child = HUD_Editor_FindNextChild(hud_hover)))\n\t{\n\t\t// Don't bother with hidden children.\n\t\tif(!child->show->value)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Draw a line from the alignment point on the parent to the child.\n\t\tHUD_Editor_GetAlignmentPoint(child, &align_x, &align_y);\n\t\tDraw_AlphaLineRGB(align_x, align_y, HUD_CENTER_X(child), HUD_CENTER_Y(child), 1, RGBA_TO_COLOR(0, 255, 0, 25));\n\t}\n}\n\n//\n// Evaluates the current mouse/keyboard state and sets the appropriate mode.\n//\nstatic void HUD_Editor_EvaluateState(hud_t *hud_hover)\n{\n\t// Mouse 1\t\t\t= Move + Resize\n\t// Mouse 2\t\t\t= Toggle menu\n\t// Ctrl  + Mouse 1\t= Place\n\t// Alt\t + Mouse 1\t= Align\n\t// Shift + Mouse 1\t= Lock moving to one axis (If you start dragging along x-axis, it will stick to that)\n\n\tif (MOUSEDOWN)\n\t{\n\t\t// Turn of help on mouse click.\n\t\thud_editor_showhelp = false;\n\t}\n\n\tif (hud_editor_mode == hud_editmode_hoverlist)\n\t{\n\t\tif (!MOUSEDOWN)\n\t\t{\n\t\t\t// Stay in hoverlist mode until the user clicks something.\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (hud_hover && MOUSEDOWN_1_ONLY && keydown[K_SHIFT])\n\t{\n\t\t// Move (Locked to an axis).\n\t\tHUD_Editor_SetMode(hud_editmode_move_lockedaxis);\n\t}\n\telse if ((hud_hover || hud_editor_prevmode == hud_editmode_place) && MOUSEDOWN_1_ONLY && keydown[K_CTRL])\n\t{\n\t\t// Place.\n\t\tHUD_Editor_SetMode(hud_editmode_place);\n\t}\n\telse if ((hud_hover || hud_editor_prevmode == hud_editmode_align) && MOUSEDOWN_1_ONLY && keydown[K_ALT])\n\t{\n\t\t// Align.\n\t\tHUD_Editor_SetMode(hud_editmode_align);\n\t}\n\telse if ((hud_hover || selected_hud) && MOUSEDOWN_1_ONLY)\n\t{\n\t\t// Move + resize.\n\t\tHUD_Editor_SetMode(hud_editmode_move_resize);\n\t}\n\telse if (hud_hoverlist_count > 1 && MOUSEDOWN_2_ONLY)\n\t{\n\t\t// Hover list for when hovering more than one HUD.\n\t\tHUD_Editor_SetMode(hud_editmode_hoverlist);\n\t}\n\telse if (hud_hover && MOUSEDOWN_2_ONLY)\n\t{\n\t\t// HUD element menu for the HUD element we have the mouse over.\n\t\tHUD_Editor_SetMode(hud_editmode_hudmenu);\n\t}\n\telse if (MOUSEDOWN_2_ONLY)\n\t{\n\t\t// Main menu for adding HUDs if we right click non-occupied space.\n\t\tHUD_Editor_SetMode(hud_editmode_menu);\n\t}\n\telse\n\t{\n\t\t// Nothing special happening.\n\t\tHUD_Editor_SetMode(hud_editmode_normal);\n\t}\n}\n\n//\n// Draws the tooltips for a HUD element based on the state we're in.\n//\nstatic void HUD_Editor_DrawTooltips(hud_t *hud_hover)\n{\n\tchar *message = NULL;\n\tbyte color[4] = {0, 0, 0, 0};\n\n\tif(!hud_hover)\n\t{\n\t\treturn;\n\t}\n\n\tif (selected_hud)\n\t{\n\t\tswitch(hud_editor_mode)\n\t\t{\n\t\t\tcase hud_editmode_move_lockedaxis :\n\t\t\tcase hud_editmode_move_resize :\n\t\t\t{\n\t\t\t\tmessage = va(\"(%d, %d) moving %s\", (int)selected_hud->pos_x->value, (int)selected_hud->pos_y->value, selected_hud->name);\n\t\t\t\tcolor[0] = 255;\n\t\t\t\tcolor[3] = 125;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase hud_editmode_align :\n\t\t\t{\n\t\t\t\tchar *align = NULL;\n\n\t\t\t\talign = HUD_Editor_GetAlignmentString(hud_alignmode);\n\n\t\t\t\tmessage = va(\"align %s to %s\", selected_hud->name, align);\n\t\t\t\tcolor[1] = 255;\n\t\t\t\tcolor[2] = 255;\n\t\t\t\tcolor[3] = 125;\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase hud_editmode_place :\n\t\t\t{\n\t\t\t\tmessage = va(\"placing %s\", selected_hud->name);\n\t\t\t\tcolor[0] = 255;\n\t\t\t\tcolor[3] = 125;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase hud_editmode_normal :\n\t\t\t{\n\t\t\t\tmessage = hud_hover->name;\n\t\t\t\tcolor[2] = 255;\n\t\t\t\tcolor[3] = 125;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// unhandled\n\t\t\tcase hud_editmode_off:\n\t\t\tcase hud_editmode_resize:\n\t\t\tcase hud_editmode_hudmenu:\n\t\t\tcase hud_editmode_menu:\n\t\t\tcase hud_editmode_hoverlist:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif(!message)\n\t{\n\t\tmessage = hud_hover->name;\n\t\tcolor[2] = 255;\n\t\tcolor[3] = 125;\n\t}\n\n\tHUD_Editor_DrawTooltip(hud_mouse_x, hud_mouse_y, message, RGBA_TO_COLOR(color[0], color[1], color[2], color[3]));\n}\n\n//\n// Draws a help window.\n//\nstatic void HUD_Editor_DrawHelp(void)\n{\n\t#define HUD_EDITOR_HELP_BORDER\t32\n\t#define HUD_EDITOR_HELP_WIDTH\tmin(vid.conwidth - (2 * HUD_EDITOR_HELP_BORDER), 500)\n\t#define HUD_EDITOR_HELP_HEIGHT\t(vid.conheight - (2 * HUD_EDITOR_HELP_BORDER))\n\t#define HUD_EDITOR_HELP_X\t\t((vid.conwidth - HUD_EDITOR_HELP_WIDTH) / 2)\n\t#define HUD_EDITOR_HELP_Y\t\tHUD_EDITOR_HELP_BORDER\n\t#define HUD_EDITOR_HELP_TITLE\t\"&cfd0HUD EDITOR HELP\"\n\n\tDraw_TextBox(HUD_EDITOR_HELP_X, HUD_EDITOR_HELP_Y, HUD_EDITOR_HELP_WIDTH / 8, HUD_EDITOR_HELP_HEIGHT / 8);\n\n\tDraw_ColoredString(\n\t\tHUD_EDITOR_HELP_X + ((HUD_EDITOR_HELP_WIDTH - strlen(HUD_EDITOR_HELP_TITLE) * 8) / 2),\n\t\tHUD_EDITOR_HELP_Y + 10,\n\t\tHUD_EDITOR_HELP_TITLE, 1, false);\n\n\tUI_PrintTextBlock(\n\t\tHUD_EDITOR_HELP_X + 10,\n\t\tHUD_EDITOR_HELP_Y + 30,\n\t\tHUD_EDITOR_HELP_WIDTH,\n\t\tHUD_EDITOR_HELP_HEIGHT - 30,\n\t\t\"The HUD Editor helps you to customize your Heads Up Display. \"\n\t\t\"When you move the cursor over a HUD element it will be \"\n\t\t\"highlighted and it's name will be shown. When hovering a HUD \"\n\t\t\"you can perform the following actions:\\n\"\n\t\t\"\\n\"\n\t\t\"&cfd0MOVE&r relative to the HUD elements parent/alignment by \"\n\t\t\"holding down &c0dfMOUSE 1&r and &c0dfdragging&r.\\n\"\n\t\t\"(Lock movement to a specific axis by holding down &c0dfSHIFT&r).\\n\"\n\t\t\"\\n\"\n\t\t\"&cfd0RESIZE&r the HUD element by clicking on one of the \"\n\t\t\"&c0dfresize handles&r that appears when hovering and item and \"\n\t\t\"&c0dfdragging&r. (Not all HUD elements are resizeable/scaleable).\\n\"\n\t\t\"\\n\"\n\t\t\"&cfd0PLACE&r the HUD element at another HUD element or a location \"\n\t\t\"such as the screen/console by holding down &c0dfCTRL&r when \"\n\t\t\"dragging. The target that the element will be placed in \"\n\t\t\"will turn green if you can place it there, red otherwise.\\n\"\n\t\t\"\\n\"\n\t\t\"&cfd0ALIGN&r the HUD element in different ways to it's parent by \"\n\t\t\"holding down &c0dfALT&r when dragging. Doing this will show \"\n\t\t\"yellow highlights at the position you're about to align to.\\n\"\n\t\t\"\\n\"\n\t\t\"  &cfd0Keyboard shortcuts:\\n\"\n\t\t\"  &c0dfP&r      Toggle HUD planmode on/off (default on).\\n\"\n\t\t\"  &c0dfH&r      Toggle this help.\\n\"\n\t\t\"  &c0dfF1&r     Toggle if moving should be allowed.\\n\"\n\t\t\"  &c0dfF2&r     Toggle resizing.\\n\"\n\t\t\"  &c0dfF3&r     Toggle aligning.\\n\"\n\t\t\"  &c0dfF4&r     Toggle placing.\\n\"\n\t\t\"  &c0dfSPACE&r  Toggle outlines/guidelines.\\n\",\n\t\t0);\n}\n\nint Test_OnGotFocus(ez_control_t *self, void *payload, void *ext_event_info)\n{\n\tEZ_control_SetBackgroundColor(self, self->background_color[0], self->background_color[1], self->background_color[2], 200);\n\treturn 0;\n}\n\nint Test_OnLostFocus(ez_control_t *self, void *payload, void *ext_event_info)\n{\n\tEZ_control_SetBackgroundColor(self, self->background_color[0], self->background_color[1], self->background_color[2], 100);\n\treturn 0;\n}\n\nez_control_t *root = NULL;\nez_control_t *child1 = NULL;\nez_control_t *child2 = NULL;\nez_button_t *button = NULL;\nez_label_t *label = NULL;\nez_label_t *label2 = NULL;\nez_slider_t *slider = NULL;\nez_scrollbar_t *scrollbar = NULL;\nez_scrollpane_t *scrollpane = NULL;\nez_listview_t *listview = NULL;\nez_window_t *window = NULL;\n\nint Test_OnButtonDraw(ez_control_t *self, void *payload, void *ext_event_info)\n{\n\tint x, y;\n\tEZ_control_GetDrawingPosition(self, &x, &y);\n\n\treturn 0;\n}\n\nint Test_OnSliderPositionChanged(ez_control_t *self, void *payload, void *ext_event_info)\n{\n\tez_slider_t *slider = (ez_slider_t *)self;\n\n\tint slider_pos = EZ_slider_GetPosition(slider);\n\n\tEZ_label_SetText(label2, va(\"%i\", slider_pos));\n\n\treturn 0;\n}\n\nint Test_OnControlDraw(ez_control_t *self, void *payload, void *ext_event_info)\n{\n\tint x, y; //, i;\n\tEZ_control_GetDrawingPosition(self, &x, &y);\n\n\t/*for (i = 0; i < 30; i++)\n\t{\n\t\tDraw_String(x, y + i*8, va(\"%d%d%d%d\", i, i, i, i));\n\t}*/\n\n\t/*\n\t{\n\t\tchar str[] = \"Hello this is a sentence that's supposed to fit within a box of stuff, I hope this works...\";\n\t\tchar line[1024];\n\t\tint last_index = 0;\n\t\tint i = 0;\n\n\t\twhile (Util_GetNextWordwrapString(str, line, last_index, &last_index, sizeof(str) / sizeof(char), self->width, 8))\n\t\t{\n\t\t\tDraw_String(x, y + i*8, line);\n\t\t\ti++;\n\t\t}\n\t}\n\t*/\n\n\t/*{\n\t\tclrinfo_t color;\n\t\tcolor.i = 0;\n\t\tcolor.c = RGBA_TO_COLOR(0, 255, 0, 255);\n\t\tDraw_BigString(x, y, \"Hej\", &color, 1, 1, 1, 0);\n\t}*/\n\n\treturn 0;\n}\n\nstatic hud_t *hud_hover = NULL;\n\n//\n// Main HUD Editor function.\n//\nstatic void HUD_Editor(void)\n{\n\tqbool found = false;\n\n\t// If we just entered hoverlist mode we want to keep the mouse coordinates\n\t// so we know where to draw the list until the user has picked a HUD.\n\tif (!hud_hoverlist_pos_is_set\n\t\t&& hud_editor_mode == hud_editmode_hoverlist\n\t\t&& hud_editor_prevmode != hud_editmode_hoverlist)\n\t{\n\t\thud_hoverlist_x = cursor_x;\n\t\thud_hoverlist_y = cursor_y;\n\t\thud_hoverlist_pos_is_set = true;\n\t}\n\telse if (hud_editor_mode != hud_editmode_hoverlist)\n\t{\n\t\thud_hoverlist_pos_is_set = false;\n\t}\n\n\t// Find the HUD we're moving or have the cursor over.\n\tfound = HUD_Editor_FindHudUnderCursor(&hud_hover);\n\n\t// Draw faint outlines for all visible hud elements.\n\tHUD_Editor_DrawOutlines();\n\n\t// Draw the \"grep handles\" for offscreen HUDs.\n\tHUD_Editor_DrawGreps();\n\n\t// Draw a rectangle around the currently active HUD element.\n\tif(found && hud_hover)\n\t{\n\t\tDraw_AlphaRectangleRGB(hud_hover->lx, hud_hover->ly, hud_hover->lw, hud_hover->lh, 1, false, RGBA_TO_COLOR(0, 255, 0, 255));\n\t}\n\n\t// If we are realigning draw a green outline for the selected hud element.\n\tif (selected_hud)\n\t{\n\t\tDraw_AlphaRectangleRGB(selected_hud->lx, selected_hud->ly, selected_hud->lw, selected_hud->lh, 2, false, RGBA_TO_COLOR(0, 255, 0, 255));\n\t}\n\n\t// Check the mouse/keyboard states and if we're hovering above a hud or not.\n\tHUD_Editor_EvaluateState(hud_hover);\n\n\t// Draw the child/parent connections the hud we're hovering has.\n\tHUD_Editor_DrawConnections(hud_hover);\n\n\t// Draw a red line from selected hud to cursor.\n\tif (selected_hud)\n\t{\n\t\tDraw_AlphaLineRGB(hud_mouse_x, hud_mouse_y, HUD_CENTER_X(selected_hud), HUD_CENTER_Y(selected_hud), 1, RGBA_TO_COLOR(255, 0, 0, 255));\n\t}\n\n\t// Check if we're performing any action.\n\t// (Only perform one at any given time).\n\t(void)\n\t(HUD_Editor_DrawHoverList(hud_hoverlist_x, hud_hoverlist_y, hud_hoverlist)\n\t\t || HUD_Editor_Resizing(hud_hover)\n\t\t || HUD_Editor_Moving(hud_hover)\n\t\t || HUD_Editor_Placing(hud_hover)\n\t\t || HUD_Editor_Aligning(hud_hover));\n\n\t// Draw tooltips for the HUD.\n\tHUD_Editor_DrawTooltips(hud_hover);\n\n\t// Show the help window?\n\tif(hud_editor_showhelp)\n\t{\n\t\tHUD_Editor_DrawHelp();\n\t}\n\n\tEZ_tree_EventLoop(&help_control_tree);\n}\n\n//\n// Toggles the HUD Editor on or off.\n//\nvoid HUD_Editor_Toggle_f(void)\n{\n\textern cvar_t scr_newHud;\n\t// static keydest_t key_dest_prev = key_game;\n\tstatic int old_hud_planmode = 0;\n\n\tif (cls.state != ca_active)\n\t{\n\t\t// We can't turn on the hud editor when disconnected.\n\t\tif(!hud_editor)\n\t\t{\n\t\t\tCom_Printf(\"You need to be in game to use the HUD editor.\\n\");\n\t\t}\n\n\t\t// If the hud editor managed to still be on while disconnected.\n\t\thud_editor = false;\n\t}\n\telse if (!scr_newHud.value)\n\t{\n\t\tCom_Printf(\"You have to have scr_newHud turned on to use the HUD editor.\\n\");\n\t\thud_editor = false;\n\t}\n\telse\n\t{\n\t\t// Toggle.\n\t\thud_editor = !hud_editor;\n\t\tS_LocalSound(\"misc/basekey.wav\");\n\t}\n\n\tif (hud_editor)\n\t{\n\t\t// Start HUD Editor.\n\n\t\tkey_dest = key_hudeditor;\n\t\tHUD_Editor_SetMode(hud_editmode_normal);\n\n\t\t// Set planmode by default.\n\t\told_hud_planmode = hud_planmode.value;\n\t\tCvar_SetValue(&hud_planmode, 1.0);\n\n\t\t// Start showing the help plaque so the user learns the controls.\n\t\thud_editor_showhelp = true;\n\t}\n\telse\n\t{\n\t\t// Exit the HUD Editor.\n\n\t\tkey_dest = key_game;\n\t\tkey_dest_beforecon = key_game;\n\t\tHUD_Editor_SetMode(hud_editmode_off);\n\t\tscr_cursor_icon = NULL;\n\n\t\t// Reset to the old value for HUD planmode.\n\t\tCvar_SetValue(&hud_planmode, old_hud_planmode);\n\t}\n}\n\n//\n// Handles mouse events sent to the HUD editor.\n//\nqbool HUD_Editor_MouseEvent(mouse_state_t *ms)\n{\n\t// Updating cursor location.\n\tif(hud_editor_mode == hud_editmode_move_lockedaxis)\n\t{\n\t\t// Don't update the HUD Editor cursor if the axis is locked\n\t\t// so that we avoid explicit checks for that.\n\t\t// The cursor will still move around the screen properly.\n\t\tif(hud_editor_locked_axis_is_x)\n\t\t{\n\t\t\thud_mouse_x = ms->x;\n\n\t\t\t// Draw a line that indicates that the movement is locked to the X-axis.\n\t\t\tif (selected_hud)\n\t\t\t{\n\t\t\t\tDraw_AlphaLineRGB(0, HUD_CENTER_Y(selected_hud), vid.width, HUD_CENTER_Y(selected_hud), 1, RGBA_TO_COLOR(255, 0, 0, 75));\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\thud_mouse_y = ms->y;\n\n\t\t\tif (selected_hud)\n\t\t\t{\n\t\t\t\tDraw_AlphaLineRGB(HUD_CENTER_X(selected_hud), 0, HUD_CENTER_X(selected_hud), vid.height, 1, RGBA_TO_COLOR(255, 0, 0, 75));\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\t// Normal operation, always update cursor.\n\t\thud_mouse_x = ms->x;\n\t\thud_mouse_y = ms->y;\n\t}\n\n\treturn EZ_tree_MouseEvent(&help_control_tree, ms);\n}\n\n//\n// Handles key events sent to the HUD editor.\n//\nvoid HUD_Editor_Key(int key, int unichar, qbool down)\n{\n\tstatic int planmode = 1;\n\tint togglekeys[2];\n\n\tEZ_tree_KeyEvent(&help_control_tree, key, unichar, down);\n\n\tM_FindKeysForCommand(\"toggleconsole\", togglekeys);\n\tif (Key_IsConsoleToggle(key) || (key == togglekeys[0]) || (key == togglekeys[1])) {\n\t\tHUD_Editor_Toggle_f();\n\t\tCon_ToggleConsole_f();\n\t\treturn;\n\t}\n\n\tif (down)\n\t{\n\t\tswitch (key)\n\t\t{\n\t\t\tcase K_ESCAPE:\n\t\t\t\tHUD_Editor_Toggle_f();\n\t\t\t\tbreak;\n\t\t\tcase 'p' :\n\t\t\t\t// Toggle HUD plan mode.\n\t\t\t\tplanmode = !planmode;\n\t\t\t\tCvar_SetValue(&hud_planmode, planmode);\n\t\t\t\tbreak;\n\t\t\tcase 'h' :\n\t\t\t\t// Toggle the help window.\n\t\t\t\thud_editor_showhelp = !hud_editor_showhelp;\n\t\t\t\tbreak;\n\t\t\tcase K_SPACE :\n\t\t\t\t// Toggle hud element outlines.\n\t\t\t\thud_editor_showoutlines = !hud_editor_showoutlines;\n\t\t\t\tbreak;\n\t\t\tcase K_F1 :\n\t\t\t\t// Toggle moving.\n\t\t\t\tCvar_SetValue(&hud_editor_allowmove, !hud_editor_allowmove.value);\n\t\t\t\tbreak;\n\t\t\tcase K_F2 :\n\t\t\t\t// Toggle resizing.\n\t\t\t\tCvar_SetValue(&hud_editor_allowresize, !hud_editor_allowresize.value);\n\t\t\t\tbreak;\n\t\t\tcase K_F3 :\n\t\t\t\t// Toggle aligning.\n\t\t\t\tCvar_SetValue(&hud_editor_allowalign, !hud_editor_allowalign.value);\n\t\t\t\tbreak;\n\t\t\tcase K_F4 :\n\t\t\t\t// Toggle placing.\n\t\t\t\tCvar_SetValue(&hud_editor_allowplace, !hud_editor_allowplace.value);\n\t\t\t\tbreak;\n\t\t\tcase K_UPARROW :\n\t\t\t\t// TODO : Add \"nudging\" in hud editor.\n\t\t\t\tbreak;\n\t\t\tcase K_DOWNARROW :\n\t\t\t\t// TODO : Add \"nudging\" in hud editor.\n\t\t\t\tbreak;\n\t\t\tcase K_LEFTARROW :\n\t\t\t\t// TODO : Add \"nudging\" in hud editor.\n\t\t\t\tbreak;\n\t\t\tcase K_RIGHTARROW :\n\t\t\t\t// TODO : Add \"nudging\" in hud editor.\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n//\n// Inits HUD Editor.\n//\nvoid HUD_Editor_Init(void)\n{\n\textern mpic_t *SCR_LoadCursorImage(char *cursorimage);\n\n#if 0\n\tclrinfo_t color;\n\n\tcolor.c = RGBA_TO_COLOR(255, 255, 255, 255);\n\tcolor.i = 0;\n\n\t// Root\n\t{\n\t\troot = EZ_control_Create(&help_control_tree, NULL, \"Test window\", \"Test\", 50, 50, 400, 400, control_focusable | control_movable | control_resize_h | control_resize_v);\n\t\tEZ_control_SetBackgroundColor(root, 0, 0, 0, 100);\n\t\tEZ_control_SetSize(root, 400, 400);\n\t}\n\n\t// Child 1\n\t{\n\t\tchild1 = EZ_control_Create(&help_control_tree, root, \"Child 1\", \"Test\", 10, 10, 50, 50, control_focusable | control_movable | control_contained | control_scrollable | control_resizeable);\n\n\t\tEZ_control_AddOnGotFocus(child1, Test_OnGotFocus, NULL);\n\t\tEZ_control_AddOnLostFocus(child1, Test_OnLostFocus, NULL);\n\t\tEZ_control_AddOnDraw(child1, Test_OnControlDraw, NULL);\n\n\t\tEZ_control_SetMinVirtualSize(child1, child1->width * 3, child1->height * 3);\n\t\t//EZ_control_SetVirtualSize(child1, child1->width * 4, child1->height * 2);\n\n\t\tEZ_control_SetBackgroundColor(child1, 150, 150, 0, 100);\n\t}\n\n\t// Child 2\n\t{\n\t\tchild2 = EZ_control_Create(&help_control_tree, root, \"Child 2\", \"Test\", 30, 50, 50, 20, control_focusable | control_contained);\n\n\t\tEZ_control_AddOnGotFocus(child2, Test_OnGotFocus, NULL);\n\t\tEZ_control_AddOnLostFocus(child2, Test_OnLostFocus, NULL);\n\n\t\tEZ_control_SetBackgroundColor(child2, 150, 150, 200, 100);\n\t}\n\n\t// Button.\n\t{\n\t\tbutton = EZ_button_Create(&help_control_tree, child1, \"button\", \"A crazy button!\", 15, -15, 80, 60, control_contained | control_resizeable);\n\t\tEZ_control_AddOnDraw((ez_control_t *)button, Test_OnButtonDraw, NULL);\n\n\t\tEZ_button_SetFocusedColor(button, 255, 0, 0, 255);\n\t\tEZ_button_SetNormalColor(button, 255, 255, 0, 100);\n\t\tEZ_button_SetPressedColor(button, 255, 255, 0, 255);\n\t\tEZ_button_SetHoverColor(button, 255, 0, 0, 150);\n\n\t\tEZ_button_SetToggleable(button, true);\n\n\t\tEZ_button_SetText(button, \"Button\");\n\t\tEZ_button_SetTextAlignment(button, middle_center);\n\n\t\tEZ_control_SetAnchor((ez_control_t *)button, (anchor_left | anchor_right | anchor_bottom));\n\t}\n\n\t// Label.\n\t{\n\t\tlabel = EZ_label_Create(&help_control_tree, root,\n\t\t\t\"label\", \"A crazy label!\", 200, 200, 250, 80,\n\t\t\tcontrol_focusable | control_contained | control_resizeable | control_scrollable /*| control_movable */ | control_resize_h | control_resize_v,\n\t\t\tlabel_wraptext | label_autosize,\n\t\t\t\"Hello\\nthis is a test are you fine because I am bla bla bla this is a very long string and it's plenty of fun haha!\");\n\n\t\tEZ_label_SetTextScale(label, 2.0);\n\t\t//EZ_label_SetTextFlags(label, LABEL_READONLY);\n\n\t\tEZ_control_SetBackgroundColor((ez_control_t *)label, 150, 150, 0, 50);\n\t\t//EZ_control_SetAnchor((ez_control_t *)label, anchor_top | anchor_right | anchor_bottom);\n\t}\n\n\t// Label 2.\n\t{\n\t\tlabel2 = EZ_label_Create(&help_control_tree, root,\n\t\t\t\"label2\", \"A crazy label!\", 100, 50, 32, 16,\n\t\t\tcontrol_focusable | control_contained | control_resizeable,\n\t\t\t0, \"\");\n\t}\n\n\t// Slider.\n\t{\n\t\tslider = EZ_slider_Create(&help_control_tree, root,\n\t\t\t\"slider\", \"Slider omg\", 50, 100, 150, 8, control_focusable | control_contained | control_resizeable);\n\n\t\tEZ_control_SetAnchor((ez_control_t *)slider, anchor_left | anchor_right);\n\n\t\tEZ_slider_SetMax(slider, 100);\n\t\tEZ_slider_SetMin(slider, 50);\n\t\tEZ_slider_SetPosition(slider, 5);\n\t\tEZ_slider_SetScale(slider, 1.0);\n\n\t\tEZ_slider_AddOnSliderPositionChanged(slider, Test_OnSliderPositionChanged, NULL);\n\t}\n\n\t/*\n\t// Scrollbar.\n\t{\n\t\tez_control_t *label_ctrl = (ez_control_t *)label;\n\n\t\tscrollbar = EZ_scrollbar_Create(&help_control_tree, root, \"Scrollbar\", \"\",\n\t\t\t30, 150, 10, 150, control_anchor_viewport);\n\n\t\tEZ_scrollbar_SetTargetParent(scrollbar, false);\n\t\t//EZ_control_SetContained((ez_control_t *)scrollbar, false);\n\t\tEZ_control_SetAnchor((ez_control_t *)scrollbar, anchor_right | anchor_top | anchor_bottom);\n\t\tEZ_control_SetMovable((ez_control_t *)scrollbar, false);\n\t}\n\t*/\n\n\t// Listview\n\t{\n\t\tlistview = EZ_listview_Create(&help_control_tree, root, \"Listview\", \"\", 50, 50, 200, 200,\n\t\t\tcontrol_resize_h | control_resize_v | control_resizeable);\n\n\t\tEZ_listview_SetHeaderText(listview, 0, \"Hej\");\n\t\tEZ_listview_SetHeaderText(listview, 1, \"Hej 2\");\n\n\t\tEZ_listview_SetColumnWidth(listview, 0, 80);\n\t\tEZ_listview_SetColumnWidth(listview, 1, 50);\n\t}\n\n\t// Scrollpane\n\t{\n\t\tscrollpane = EZ_scrollpane_Create(&help_control_tree, root, \"Scrollpane\", \"\", -10, -20, 150, 150,\n\t\t\tcontrol_resize_h | control_resize_v | control_resizeable);\n\n\t\tEZ_control_SetBackgroundColor((ez_control_t *)scrollpane, 255, 0, 0, 100);\n\n\t\t//EZ_scrollpane_SetTarget(scrollpane, child1);\n\t\tEZ_scrollpane_SetTarget(scrollpane, (ez_control_t *)listview);\n\t}\n\n\t// Window.\n\t{\n\t\twindow = EZ_window_Create(&help_control_tree, root, \"Window\", NULL, 20, 20, 150, 150,\n\t\t\tcontrol_movable | control_focusable | control_resize_h | control_resize_v | control_contained);\n\n\t\tEZ_control_SetBackgroundColor((ez_control_t *)window, 0, 100, 0, 100);\n\n\t\tEZ_window_SetWindowAreaMinVirtualSize(window, 200, 200);\n\n\t\t//EZ_window_AddChild(window, (ez_control_t *)scrollpane);\n\t}\n\n\t/*\n\t// Test.\n\t{\n\t\tez_control_t *c = EZ_control_Create(&help_control_tree, root, \"C test 1\", \"Test\", 10, 10, 150, 150,\n\t\t\tcontrol_resize_h | control_focusable | control_movable | control_contained | control_scrollable | control_resizeable);\n\n\t\tez_control_t *c2 = EZ_control_Create(&help_control_tree, c, \"C test 1\", \"Test\", 0, 10, 30, 10,\n\t\t\tcontrol_focusable | control_movable | control_contained | control_scrollable | control_resizeable);\n\n\t\tEZ_control_SetAnchor(c2, anchor_top | anchor_right);\n\t\tEZ_control_SetBackgroundColor(c2, 150, 0, 20, 100);\n\n\t\tEZ_control_SetBackgroundColor(c, 50, 40, 50, 100);\n\t}\n\t*/\n\n\tEZ_tree_Refresh(&help_control_tree);\n\n#endif\n\n\t// Register commands.\n\tCmd_AddCommand(\"hud_editor\", HUD_Editor_Toggle_f);\n\n\t// Register variables.\n\tCvar_SetCurrentGroup(CVAR_GROUP_HUD);\n\tCvar_Register(&hud_editor_allowresize);\n\tCvar_Register(&hud_editor_allowmove);\n\tCvar_Register(&hud_editor_allowplace);\n\tCvar_Register(&hud_editor_allowalign);\n    Cvar_ResetCurrentGroup();\n\n\t// Load HUD editor cursor icons.\n\thud_editor_move_icon = SCR_LoadCursorImage(\"gfx/hud_move_icon\");\n\thud_editor_resize_icon = SCR_LoadCursorImage(\"gfx/hud_resize_icon\");\n\thud_editor_align_icon = SCR_LoadCursorImage(\"gfx/hud_align_icon\");\n\thud_editor_place_icon = SCR_LoadCursorImage(\"gfx/hud_place_icon\");\n\n\thud_editor = false;\n\tHUD_Editor_SetMode(hud_editmode_off);\n}\n\n//\n// Draws the HUD Editor if it's on.\n//\nvoid HUD_Editor_Draw(void)\n{\n\tif (!hud_editor)\n\t\treturn;\n\n\tHUD_Editor();\n}\n\n//\n// Should this HUD element be fully drawn or not when in align mode\n// when using the HUD editor?\n//\nqbool HUD_Editor_ConfirmDraw(hud_t *hud)\n{\n\tif(hud_editor_mode == hud_editmode_align || hud_editor_mode == hud_editmode_place)\n\t{\n\t\t// If this is the selected hud, or the parent of the selected hud then draw it.\n\t\tif((selected_hud && !strcmp(selected_hud->name, hud->name))\n\t\t\t|| (selected_hud && hud->place_hud && !strcmp(selected_hud->name, hud->place_hud->name)))\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n"
  },
  {
    "path": "src/hud_editor.h",
    "content": "/*\nHUD Editor module\n\nCopyright (C) 2007 Cokeman\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef __HUD_EDITOR_H__\n#define __HUD_EDITOR_H__\n\n// hud editor drawing function\nvoid HUD_Editor_Draw(void);\n\n// hud editor initialization\nvoid HUD_Editor_Init(void);\n\n// Mouse processing.\nqbool HUD_Editor_MouseEvent (mouse_state_t *ms);\n\n// Key press processing function.\nvoid HUD_Editor_Key(int key, int unichar, qbool down);\n\n//\n// Should this HUD element be fully drawn or not when in align mode\n// when using the HUD editor.\n//\nqbool HUD_Editor_ConfirmDraw(hud_t *hud);\n\ntypedef enum\n{\n\thud_editmode_off,\n\thud_editmode_align,\n\thud_editmode_place,\n\thud_editmode_move_resize,\n\thud_editmode_resize,\n\thud_editmode_move_lockedaxis,\n\thud_editmode_hudmenu,\n\thud_editmode_menu,\n\thud_editmode_hoverlist,\n\thud_editmode_normal\n} hud_editor_mode_t;\n\nextern hud_editor_mode_t\thud_editor_mode;\nextern hud_t\t\t\t\t*selected_hud;\n\n#endif // __HUD_EDITOR_H__\n"
  },
  {
    "path": "src/hud_face.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n\n// face\nvoid SCR_HUD_DrawFace(hud_t *hud)\n{\n\textern mpic_t  *sb_faces[5][2]; // 0 is dead, 1-4 are alive\n\t\t\t\t\t\t\t\t\t// 0 is static, 1 is temporary animation\n\textern mpic_t  *sb_face_invis;\n\textern mpic_t  *sb_face_quad;\n\textern mpic_t  *sb_face_invuln;\n\textern mpic_t  *sb_face_invis_invuln;\n\n\tint     f, anim;\n\tint     x, y;\n\tfloat   scale;\n\n\tstatic cvar_t *v_scale = NULL;\n\tif (v_scale == NULL)  // first time called\n\t{\n\t\tv_scale = HUD_FindVar(hud, \"scale\");\n\t}\n\n\tscale = max(v_scale->value, 0.01);\n\n\tif (cl.spectator != cl.autocam)\n\t\treturn;\n\t\n\tif (!HUD_PrepareDraw(hud, 24 * scale, 24 * scale, &x, &y))\n\t\treturn;\n\n\tif ((HUD_Stats(STAT_ITEMS) & (IT_INVISIBILITY | IT_INVULNERABILITY))\n\t\t== (IT_INVISIBILITY | IT_INVULNERABILITY)) {\n\t\tDraw_SPic(x, y, sb_face_invis_invuln, scale);\n\t\treturn;\n\t}\n\tif (HUD_Stats(STAT_ITEMS) & IT_QUAD) {\n\t\tDraw_SPic(x, y, sb_face_quad, scale);\n\t\treturn;\n\t}\n\tif (HUD_Stats(STAT_ITEMS) & IT_INVISIBILITY) {\n\t\tDraw_SPic(x, y, sb_face_invis, scale);\n\t\treturn;\n\t}\n\tif (HUD_Stats(STAT_ITEMS) & IT_INVULNERABILITY) {\n\t\tDraw_SPic(x, y, sb_face_invuln, scale);\n\t\treturn;\n\t}\n\n\tif (HUD_Stats(STAT_HEALTH) >= 100)\n\t\tf = 4;\n\telse\n\t\tf = max(0, HUD_Stats(STAT_HEALTH)) / 20;\n\n\tif (cl.time <= cl.faceanimtime)\n\t\tanim = 1;\n\telse\n\t\tanim = 0;\n\tDraw_SPic(x, y, sb_faces[f][anim], scale);\n}\n\nvoid Face_HudInit(void)\n{\n\t// player face (health indicator)\n\tHUD_Register(\n\t\t\"face\", NULL, \"Your bloody face.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawFace,\n\t\t\"1\", \"screen\", \"center\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"scale\", \"1\",\n\t\tNULL\n\t);\n}\n"
  },
  {
    "path": "src/hud_frags.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"utils.h\"\n#include \"sbar.h\"\n#include \"fonts.h\"\n\n#define MAX_FRAGS_NAME 32\n#define FRAGS_HEALTH_SPACING 1\n\n#define\tTEAMFRAGS_EXTRA_SPEC_NONE\t0\n#define TEAMFRAGS_EXTRA_SPEC_BEFORE\t1\n#define\tTEAMFRAGS_EXTRA_SPEC_ONTOP\t2\n#define TEAMFRAGS_EXTRA_SPEC_NOICON 3\n#define TEAMFRAGS_EXTRA_SPEC_RLTEXT 4\n\n#define FRAGS_HEALTHBAR_NORMAL_COLOR\t75\n#define FRAGS_HEALTHBAR_MEGA_COLOR\t\t251\n#define\tFRAGS_HEALTHBAR_TWO_MEGA_COLOR\t238\n#define\tFRAGS_HEALTHBAR_UNNATURAL_COLOR\t144\n\nstatic qbool hud_frags_extra_spec_info = true;\nstatic qbool hud_frags_show_rl = true;\nstatic qbool hud_frags_show_lg = true;\nstatic qbool hud_frags_show_armor = true;\nstatic qbool hud_frags_show_health = true;\nstatic qbool hud_frags_show_powerup = true;\nstatic qbool hud_frags_textonly = false;\nstatic qbool hud_frags_horiz_health = false;\nstatic qbool hud_frags_horiz_power = false;\n\nstatic void Frags_DrawHorizontalHealthBar(player_info_t* info, int x, int y, int width, int height, qbool flip, qbool use_power)\n{\n\tstatic cvar_t* health_color_nohealth = NULL;\n\tstatic cvar_t* health_color_normal = NULL;\n\tstatic cvar_t* health_color_mega = NULL;\n\tstatic cvar_t* health_color_twomega = NULL;\n\tstatic cvar_t* health_color_unnatural = NULL;\n\tstatic cvar_t* armor_color_noarmor = NULL;\n\tstatic cvar_t* armor_color_ga_over = NULL;\n\tstatic cvar_t* armor_color_ga = NULL;\n\tstatic cvar_t* armor_color_ya = NULL;\n\tstatic cvar_t* armor_color_ra = NULL;\n\tstatic cvar_t* armor_color_unnatural = NULL;\n\n\tint armor = info->stats[STAT_ARMOR];\n\tint items = info->stats[STAT_ITEMS];\n\tint health;\n\tint true_health = info->stats[STAT_HEALTH];\n\tfloat max_health = (use_power ? 450 : 250);\n\tfloat max_armor = 200;\n\tfloat armorType;\n\tfloat health_width, armor_width;\n\tfloat max_health_width, max_health_value;\n\tint health_height = (height * 3) / 4;\n\tint armor_height = height - health_height;\n\tcolor_t armor_color = 0;\n\tcolor_t health_color = 0;\n\tdouble now = (cls.mvdplayback ? cls.demotime : cl.time);\n\tqbool prewar = cl.standby || cl.countdown;\n\n\tfloat drop_wait_time = 3.0f;\n\tfloat drop_speed = 300.0f;\n\n\tbyte* bk;\n\tcolor_t border_color;\n\n\t// Use other hud elements to get range of colours\n\tif (health_color_nohealth == NULL) {\n\t\thealth_color_nohealth = Cvar_Find(\"hud_bar_health_color_nohealth\");\n\t\thealth_color_normal = Cvar_Find(\"hud_bar_health_color_normal\");\n\t\thealth_color_mega = Cvar_Find(\"hud_bar_health_color_mega\");\n\t\thealth_color_twomega = Cvar_Find(\"hud_bar_health_color_twomega\");\n\t\thealth_color_unnatural = Cvar_Find(\"hud_bar_health_color_unnatural\");\n\n\t\tarmor_color_noarmor = Cvar_Find(\"hud_bar_armor_color_noarmor\");\n\t\tarmor_color_ga_over = Cvar_Find(\"hud_bar_armor_color_ga_over\");\n\t\tarmor_color_ga = Cvar_Find(\"hud_bar_armor_color_ga\");\n\t\tarmor_color_ya = Cvar_Find(\"hud_bar_armor_color_ya\");\n\t\tarmor_color_ra = Cvar_Find(\"hud_bar_armor_color_ra\");\n\t\tarmor_color_unnatural = Cvar_Find(\"hud_bar_armor_color_unnatural\");\n\t}\n\n\tbk = (byte*)&health_color_nohealth->color;\n\tborder_color = RGBA_TO_COLOR(\n\t\tmax(bk[0], 24) - 24,\n\t\tmax(bk[1], 24) - 24,\n\t\tmax(bk[2], 24) - 24,\n\t\tmin(bk[3], 128) + 127\n\t);\n\n\tif (health_color_nohealth == NULL || armor_color_noarmor == NULL) {\n\t\treturn;\n\t}\n\n\t// work out colours\n\tarmorType = true_health >= 1 ? SCR_HUD_ArmorType(items) : 0;\n\tif (items & IT_INVULNERABILITY && true_health >= 1) {\n\t\tarmor_color = RGBAVECT_TO_COLOR(armor_color_unnatural->color);\n\t}\n\telse if (items & IT_ARMOR3 && true_health >= 1) {\n\t\tarmor_color = RGBAVECT_TO_COLOR(armor_color_ra->color);\n\t\tmax_armor = 200;\n\t}\n\telse if (items & IT_ARMOR2 && true_health >= 1) {\n\t\tarmor_color = RGBAVECT_TO_COLOR(armor_color_ya->color);\n\t\tmax_armor = 150;\n\t}\n\telse if (items & IT_ARMOR1 && true_health >= 1) {\n\t\tif (armor > 100) {\n\t\t\tarmor_color = RGBAVECT_TO_COLOR(armor_color_ga_over->color);\n\t\t} else {\n\t\t\tarmor_color = RGBAVECT_TO_COLOR(armor_color_ga->color);\n\t\t}\n\t\tmax_armor = 100;\n\t}\n\n\tif (use_power) {\n\t\thealth = prewar ? 0 : SCR_HUD_TotalStrength(true_health, armor, armorType);\n\t\tif (health <= 110) {\n\t\t\thealth_color = RGBAVECT_TO_COLOR(health_color_normal->color);\n\t\t}\n\t\telse if (health <= 220) {\n\t\t\thealth_color = RGBAVECT_TO_COLOR(health_color_mega->color);\n\t\t}\n\t\telse if (health <= 450) {\n\t\t\thealth_color = RGBAVECT_TO_COLOR(health_color_twomega->color);\n\t\t}\n\t\telse {\n\t\t\thealth_color = RGBAVECT_TO_COLOR(health_color_unnatural->color);\n\t\t}\n\t}\n\telse {\n\t\thealth = prewar ? 0 : true_health;\n\t\tif (health <= 100) {\n\t\t\thealth_color = RGBAVECT_TO_COLOR(health_color_normal->color);\n\t\t}\n\t\telse if (health <= 200) {\n\t\t\thealth_color = RGBAVECT_TO_COLOR(health_color_mega->color);\n\t\t}\n\t\telse if (health <= 250) {\n\t\t\thealth_color = RGBAVECT_TO_COLOR(health_color_twomega->color);\n\t\t}\n\t\telse {\n\t\t\thealth_color = RGBAVECT_TO_COLOR(health_color_unnatural->color);\n\t\t}\n\t}\n\n\t// Background\n\tDraw_AlphaRectangleRGB(x, y, width, health_height, 1.0, false, border_color);\n\tDraw_AlphaFillRGB(x, y + health_height, width, armor_height, RGBAVECT_TO_COLOR(armor_color_noarmor->color));\n\tx += 1;\n\twidth -= 2;\n\ty += 1;\n\thealth_height -= 2;\n\tDraw_AlphaFillRGB(x, y, width, health_height, RGBAVECT_TO_COLOR(health_color_nohealth->color));\n\n\t// Calculate figures\n\thealth = min(health, max_health);\n\tif (info->prev_health > health && info->prev_health > 0) {\n\t\t// Decrease health bar to current health\n\t\tdouble dropTime = now - info->prev_health_last_set;\n\t\tint new_health = health;\n\n\t\thealth = max(health, info->prev_health - dropTime * drop_speed);\n\t\tif (health == new_health) {\n\t\t\tinfo->prev_health = health;\n\t\t\tinfo->prev_health_last_set = now;\n\t\t}\n\t}\n\telse if (info->prev_health < health && info->prev_health > 0) {\n\t\t// Increase health bar to current health\n\t\tdouble dropTime = now - info->prev_health_last_set;\n\t\tint new_health = health;\n\n\t\thealth = min(health, info->prev_health + dropTime * drop_speed);\n\t\tif (health == new_health) {\n\t\t\tinfo->prev_health = health;\n\t\t\tinfo->prev_health_last_set = now;\n\t\t}\n\t}\n\telse {\n\t\tinfo->prev_health = (prewar && health == max_health ? 0 : health);\n\t\tinfo->prev_health_last_set = now;\n\t}\n\n\t// Decrease the maximum health\n\tmax_health_value = min(info->max_health, max_health);\n\tif (info->max_health_last_set && info->max_health_last_set < now - drop_wait_time) {\n\t\tdouble dropTime = now - info->max_health_last_set - drop_wait_time;\n\n\t\tif (health < info->max_health && info->prev_health > 0) {\n\t\t\tmax_health_value = max(health, info->max_health - dropTime * drop_speed);\n\t\t\tif (max_health_value == health) {\n\t\t\t\tinfo->max_health = max_health_value = health;\n\t\t\t\tinfo->max_health_last_set = 0;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tinfo->max_health = max_health_value = (prewar ? 0 : health);\n\t\t\tinfo->max_health_last_set = 0;\n\t\t}\n\t}\n\telse if (health >= info->max_health) {\n\t\tinfo->max_health = (prewar ? 0 : health);\n\t\tinfo->max_health_last_set = 0;\n\t}\n\telse if (health < info->prev_health && !cls.mvdplayback) {\n\t\tinfo->max_health_last_set = now;\n\t}\n\telse if (prewar) {\n\t\tinfo->max_health = max_health_value = 0;\n\t}\n\n\t// Draw a health bar.\n\thealth_width = prewar ? width : Q_rint((width / max_health) * health);\n\thealth_width = (health_width > 0.0 && health_width < 1.0) ? 1 : health_width;\n\thealth_width = max(health_width, 0);\n\n\tarmor = min(armor, max_armor);\n\tarmor_width = Q_rint((width / max_armor) * armor);\n\tarmor_width = (armor_width > 0.0 && armor_width < 1.0) ? 1 : armor_width;\n\tarmor_width = max(armor_width, 0);\n\n\tmax_health_width = 0;\n\tif (info->max_health_last_set && health < max_health_value) {\n\t\tmax_health_width = Q_rint((width / max_health) * max_health_value);\n\t\tmax_health_width -= health_width;\n\t\tmax_health_width = (max_health_width > 0.0 && max_health_width < 1.0) ? 1 : max_health_width;\n\t\tmax_health_width = max(max_health_width, 0);\n\t}\n\n\tif (flip) {\n\t\tDraw_AlphaFillRGB(x + width - (int)health_width, y, health_width, health_height, health_color);\n\t\tif (max_health_width) {\n\t\t\tDraw_AlphaFillRGB(x + width - (int)health_width - (int)max_health_width, y, max_health_width, health_height, RGBA_TO_COLOR(255, 0, 0, 128));\n\t\t}\n\n\t\tDraw_AlphaFillRGB(x, y + health_height, width, armor_height, RGBAVECT_TO_COLOR(armor_color_noarmor->color));\n\t\tif (armor_width > 0 && health > 0) {\n\t\t\tDraw_AlphaFillRGB(x + width - (int)armor_width, y + health_height, armor_width, armor_height, armor_color);\n\t\t}\n\t}\n\telse {\n\t\tDraw_AlphaFillRGB(x, y, health_width, health_height, health_color);\n\t\tif (max_health_width) {\n\t\t\tDraw_AlphaFillRGB(x + health_width, y, max_health_width, health_height, RGBA_TO_COLOR(255, 0, 0, 128));\n\t\t}\n\n\t\tDraw_AlphaFillRGB(x, y + health_height, width, armor_height, RGBAVECT_TO_COLOR(armor_color_noarmor->color));\n\t\tif (armor_width > 0 && health > 0) {\n\t\t\tDraw_AlphaFillRGB(x, y + health_height, armor_width, armor_height, armor_color);\n\t\t}\n\t}\n}\n\nstatic void Frags_DrawColors(\n\tint x, int y, int width, int height,\n\tint top_color, int bottom_color, float color_alpha,\n\tint frags, int drawBrackets, int style,\n\tfloat bignum, float scale, qbool proportional, \n\tint wipeout, int hidefrags, int isdead, int timetospawn\n)\n{\n\tchar buf[32];\n\tclrinfo_t color;\n\tint posy = 0;\n\tint char_size = (bignum > 0) ? Q_rint(24 * bignum) : 8 * scale;\n\tqbool use_wipeout = (check_ktx_ca_wo() && wipeout);\n\tqbool norespawn = (use_wipeout && (isdead == 2));\n\tfloat wo_alpha = (use_wipeout && (isdead == 1)) ? 0.5 : 1.0;\n\tfloat bignum_alpha = (use_wipeout && isdead) ? 0.25 : 1.0;\n\n\tDraw_AlphaFill(x, y, width, height / 2, norespawn ? 3 : top_color, color_alpha * wo_alpha);\n\tDraw_AlphaFill(x, y + height / 2, width, height - height / 2, norespawn ? 3 : bottom_color, color_alpha * wo_alpha);\n\n\tposy = y + (height - char_size) / 2;\n\tcolor.i = 0;\n\n\tif ((bignum > 0) && !hidefrags) {\n\t\t//\n\t\t// Scaled big numbers for frags.\n\t\t//\n\t\textern  mpic_t *sb_nums[2][11];\n\t\tchar *t = buf;\n\t\tint char_x;\n\t\tint char_y;\n\t\tsnprintf(buf, sizeof(buf), \"%d\", frags);\n\t\t\n\t\tchar_x = max(x, x + (width - (int)strlen(buf) * char_size) / 2);\n\t\tchar_y = max(y, posy);\n\n\t\twhile (*t) {\n\t\t\tif (*t >= '0' && *t <= '9') {\n\t\t\t\tDraw_SAlphaPic(char_x, char_y, sb_nums[0][*t - '0'], bignum_alpha, bignum);\n\t\t\t\tchar_x += char_size;\n\t\t\t}\n\t\t\telse if (*t == '-') {\n\t\t\t\tDraw_SAlphaPic(char_x, char_y, sb_nums[0][STAT_MINUS], bignum_alpha, bignum);\n\t\t\t\tchar_x += char_size;\n\t\t\t}\n\n\t\t\tt++;\n\t\t}\n\t}\n\telse {\n\t\tif (use_wipeout) {\n\t\t\tif (isdead == 1 && timetospawn > 0 && timetospawn < 999) {\n\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xFF, 0x00, (byte)(1 * 255));\n\t\t\t} \n\t\t\telse if (isdead == 2) {\n\t\t\t\tcolor.c = RGBA_TO_COLOR(0x33, 0x33, 0x33, (byte)(1 * 255));\n\t\t\t} \n\t\t\telse {\n\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xFF, 0xFF, (byte)(1 * 255));\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xFF, 0xFF, (byte)(1 * 255));\n\t\t}\n\n\t\t// Determine snprintf content\n\t\tif (use_wipeout && isdead == 1 && timetospawn > 0 && timetospawn < 999) {\n\t\t\tsnprintf(buf, sizeof(buf), \"%d\", timetospawn);\n\t\t} \n\t\telse if (use_wipeout && hidefrags) {\n\t\t\tsnprintf(buf, sizeof(buf), \"%s\", \" \");\n\t\t} \n\t\telse {\n\t\t\tsnprintf(buf, sizeof(buf), \"%d\", frags);\n\t\t}\n\n\t\t// Normal text size. (meag: why -3?)\n\t\tDraw_SColoredAlphaString(x + (width - Draw_StringLength(buf, -1, scale, proportional)) / 2, posy, buf, &color, 1, 0, scale, 1, proportional);\n\t}\n\n\tif (drawBrackets && width) {\n\t\t// Brackets [] are not available scaled, so use normal size even\n\t\t// if we're drawing big frag nums.\n\t\tint brack_posy = y + (height - 8 * scale) / 2;\n\t\tint d = (width >= 32) ? 0 : 1;\n\n\t\tswitch (style) {\n\t\t\tcase 1:\n\t\t\t\tDraw_SCharacterP(x - FontCharacterWidth(13, scale, proportional) * 0.5f, posy, 13, scale, proportional);\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\t// Red outline.\n\t\t\t\tDraw_Fill(x, y - 1, width, 1, 0x4f);\n\t\t\t\tDraw_Fill(x, y - 1, 1, height + 2, 0x4f);\n\t\t\t\tDraw_Fill(x + width - 1, y - 1, 1, height + 2, 0x4f);\n\t\t\t\tDraw_Fill(x, y + height, width, 1, 0x4f);\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\t// Draw nothing\n\t\t\t\tbreak;\n\t\t\tcase 0:\n\t\t\tdefault:\n\t\t\t\tDraw_SCharacterP(x - 2 - 2 * d, brack_posy, 16, scale, proportional); // [\n\t\t\t\tDraw_SCharacterP(x + width - 8 + 1 + d, brack_posy, 17, scale, proportional); // ]\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic void Frags_DrawHealthBar(int original_health, int x, int y, int height, int width, qbool horizontal, qbool flip)\n{\n\tint health;\n\n\t// Get the health.\n\thealth = original_health;\n\thealth = min(100, health);\n\n\t// Draw a health bar.\n\tif (horizontal) {\n\t\tfloat health_width = Q_rint((width / 100.0) * health);\n\t\thealth_width = (health_width > 0.0 && health_width < 1.0) ? 1 : health_width;\n\t\thealth_width = (health_width < 0.0) ? 0.0 : health_width;\n\n\t\tif (flip) {\n\t\t\tDraw_Fill(x + width - (int)health_width, y, health_width, (height * 3) / 4, FRAGS_HEALTHBAR_NORMAL_COLOR);\n\t\t}\n\t\telse {\n\t\t\tDraw_Fill(x, y, x + (int)health_width, (height * 3) / 4, FRAGS_HEALTHBAR_NORMAL_COLOR);\n\t\t}\n\t}\n\telse {\n\t\tfloat health_height = Q_rint((height / 100.0) * health);\n\t\thealth_height = (health_height > 0.0 && health_height < 1.0) ? 1 : health_height;\n\t\thealth_height = (health_height < 0.0) ? 0.0 : health_height;\n\t\tDraw_Fill(x, y + height - (int)health_height, 3, (int)health_height, FRAGS_HEALTHBAR_NORMAL_COLOR);\n\n\t\t// Get the health again to check if health is more than 100.\n\t\thealth = original_health;\n\t\tif (health > 100 && health <= 200) {\n\t\t\tfloat health_height = (int)Q_rint((height / 100.0) * (health - 100));\n\t\t\tDraw_Fill(x, y + height - health_height, width, health_height, FRAGS_HEALTHBAR_MEGA_COLOR);\n\t\t}\n\t\telse if (health > 200 && health <= 250) {\n\t\t\tfloat health_height = (int)Q_rint((height / 100.0) * (health - 200));\n\t\t\tDraw_Fill(x, y, width, height, FRAGS_HEALTHBAR_MEGA_COLOR);\n\t\t\tDraw_Fill(x, y + height - health_height, width, health_height, FRAGS_HEALTHBAR_TWO_MEGA_COLOR);\n\t\t}\n\t\telse if (health > 250) {\n\t\t\t// This will never happen during a normal game.\n\t\t\tDraw_Fill(x, y, width, health_height, FRAGS_HEALTHBAR_UNNATURAL_COLOR);\n\t\t}\n\t}\n}\n\nstatic int TeamFrags_DrawExtraSpecInfo(int num, int px, int py, int width, int height, int style)\n{\n\textern mpic_t *sb_weapons[7][8]; // sbar.c\n\tmpic_t rl_picture = *sb_weapons[0][5];\n\n\t// Only allow this for spectators.\n\tif (!(cls.demoplayback || cl.spectator)\n\t\t|| style > TEAMFRAGS_EXTRA_SPEC_RLTEXT\n\t\t|| style <= TEAMFRAGS_EXTRA_SPEC_NONE\n\t\t|| !style) {\n\t\treturn px;\n\t}\n\n\t// Check if the team has any RL's.\n\tif (sorted_teams[num].rlcount > 0) {\n\t\tint y_pos = py;\n\n\t\t//\n\t\t// Draw the RL + count depending on style.\n\t\t//\n\n\t\tif ((style == TEAMFRAGS_EXTRA_SPEC_BEFORE || style == TEAMFRAGS_EXTRA_SPEC_NOICON)\n\t\t\t&& style != TEAMFRAGS_EXTRA_SPEC_RLTEXT) {\n\t\t\ty_pos = Q_rint(py + (height / 2.0) - 4);\n\t\t\tDraw_ColoredString(px, y_pos, va(\"%d\", sorted_teams[num].rlcount), 0, false);\n\t\t\tpx += 8 + 1;\n\t\t}\n\n\t\tif (style != TEAMFRAGS_EXTRA_SPEC_NOICON && style != TEAMFRAGS_EXTRA_SPEC_RLTEXT) {\n\t\t\ty_pos = Q_rint(py + (height / 2.0) - (rl_picture.height / 2.0));\n\t\t\tDraw_SSubPic(px, y_pos, &rl_picture, 0, 0, rl_picture.width, rl_picture.height, 1);\n\t\t\tpx += rl_picture.width + 1;\n\t\t}\n\n\t\tif (style == TEAMFRAGS_EXTRA_SPEC_ONTOP && style != TEAMFRAGS_EXTRA_SPEC_RLTEXT) {\n\t\t\ty_pos = Q_rint(py + (height / 2.0) - 4);\n\t\t\tDraw_ColoredString(px - 14, y_pos, va(\"%d\", sorted_teams[num].rlcount), 0, false);\n\t\t}\n\n\t\tif (style == TEAMFRAGS_EXTRA_SPEC_RLTEXT) {\n\t\t\ty_pos = Q_rint(py + (height / 2.0) - 4);\n\t\t\tDraw_ColoredString(px, y_pos, va(\"&ce00RL&cfff%d\", sorted_teams[num].rlcount), 0, false);\n\t\t\tpx += 8 * 3 + 1;\n\t\t}\n\t}\n\telse {\n\t\t// If the team has no RL's just pad with nothing.\n\t\tif (style == TEAMFRAGS_EXTRA_SPEC_BEFORE) {\n\t\t\t// Draw the rl count before the rl icon.\n\t\t\tpx += rl_picture.width + 8 + 1 + 1;\n\t\t}\n\t\telse if (style == TEAMFRAGS_EXTRA_SPEC_ONTOP) {\n\t\t\t// Draw the rl count on top of the RL instead of infront.\n\t\t\tpx += rl_picture.width + 1;\n\t\t}\n\t\telse if (style == TEAMFRAGS_EXTRA_SPEC_NOICON) {\n\t\t\t// Only draw the rl count.\n\t\t\tpx += 8 + 1;\n\t\t}\n\t\telse if (style == TEAMFRAGS_EXTRA_SPEC_RLTEXT) {\n\t\t\tpx += 8 * 3 + 1;\n\t\t}\n\t}\n\n\treturn px;\n}\n\nstatic void Frags_OnChangeExtraSpecInfo(cvar_t *var, char *s, qbool *cancel)\n{\n\t// Parse the extra spec info.\n\thud_frags_show_rl = Utils_RegExpMatch(\"RL|ALL\", s);\n\thud_frags_show_lg = Utils_RegExpMatch(\"LG|ALL\", s);\n\thud_frags_show_armor = Utils_RegExpMatch(\"ARMOR|ALL\", s);\n\thud_frags_show_health = Utils_RegExpMatch(\"HEALTH|ALL\", s);\n\thud_frags_show_powerup = Utils_RegExpMatch(\"POWERUP|ALL\", s);\n\thud_frags_textonly = Utils_RegExpMatch(\"TEXT\", s);\n\thud_frags_horiz_health = Utils_RegExpMatch(\"HMETER\", s);\n\thud_frags_horiz_power = Utils_RegExpMatch(\"PMETER\", s);\n\n\thud_frags_extra_spec_info = (hud_frags_show_rl || hud_frags_show_lg || hud_frags_show_armor || hud_frags_show_health || hud_frags_show_powerup);\n}\n\nstatic int Frags_DrawExtraSpecInfo(player_info_t *info,\n\tint px, int py,\n\tint cell_width, int cell_height,\n\tint space_x, int space_y, int flip\n)\n{\n\textern mpic_t *sb_weapons[7][8];\t\t// sbar.c ... Used for displaying the RL.\n\tmpic_t *rl_picture = sb_weapons[0][5];\t// Picture of RL.\n\tmpic_t *lg_picture = sb_weapons[0][6];\t// Picture of LG.\n\n\tfloat\tarmor_height = 0.0;\n\tint\t\tarmor = 0;\n\tint\t\tarmor_bg_color = 0;\n\tfloat\tarmor_bg_power = 0;\n\tint\t\tweapon_width = 0;\n\n\t// Only allow this for spectators.\n\tif (!(cls.demoplayback || cl.spectator)) {\n\t\treturn px;\n\t}\n\n\t// Set width based on text or picture.\n\tif (hud_frags_show_armor || hud_frags_show_rl || hud_frags_show_lg || hud_frags_show_powerup) {\n\t\tif (hud_frags_show_rl && hud_frags_show_lg) {\n\t\t\tweapon_width = (!hud_frags_textonly ? rl_picture->width + lg_picture->width : 24 * 2);\n\t\t}\n\t\telse if (hud_frags_show_rl) {\n\t\t\tweapon_width = (!hud_frags_textonly ? rl_picture->width : 24);\n\t\t}\n\t\telse {\n\t\t\tweapon_width = (!hud_frags_textonly ? lg_picture->width : 24);\n\t\t}\n\t}\n\n\t// Draw health bar. (flipped)\n\tif (flip && hud_frags_show_health) {\n\t\tFrags_DrawHealthBar(info->stats[STAT_HEALTH], px, py, cell_height, 3, false, false);\n\t\tpx += 3 + FRAGS_HEALTH_SPACING;\n\t}\n\n\tarmor = info->stats[STAT_ARMOR];\n\n\t// If the player has any armor, draw it in the appropriate color.\n\tif (info->stats[STAT_ITEMS] & IT_ARMOR1) {\n\t\tarmor_bg_power = 100;\n\t\tarmor_bg_color = 178; // Green armor.\n\t}\n\telse if (info->stats[STAT_ITEMS] & IT_ARMOR2) {\n\t\tarmor_bg_power = 150;\n\t\tarmor_bg_color = 111; // Yellow armor.\n\t}\n\telse if (info->stats[STAT_ITEMS] & IT_ARMOR3) {\n\t\tarmor_bg_power = 200;\n\t\tarmor_bg_color = 79; // Red armor.\n\t}\n\n\t// Only draw the armor if the current player has one and if the style allows it.\n\tif (armor_bg_power && hud_frags_show_armor) {\n\t\tarmor_height = Q_rint((cell_height / armor_bg_power) * armor);\n\n\t\tDraw_AlphaFill(px,\t\t\t\t\t\t\t\t\t\t\t\t// x\n\t\t\tpy + cell_height - (int)armor_height,\t\t\t// y (draw from bottom up)\n\t\t\tweapon_width,\t\t\t\t\t\t\t\t\t// width\n\t\t\t(int)armor_height,\t\t\t\t\t\t\t\t// height\n\t\t\tarmor_bg_color,\t\t\t\t\t\t\t\t\t// color\n\t\t\t0.3);\t\t\t\t\t\t\t\t\t\t\t// alpha\n\t}\n\n\t// Draw the rl if the current player has it and the style allows it.\n\tif (info->stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER && hud_frags_show_rl) {\n\t\tif (!hud_frags_textonly) {\n\t\t\t// Draw the rl-pic.\n\t\t\tDraw_SSubPic(\n\t\t\t\tpx,\n\t\t\t\tpy + Q_rint((cell_height / 2.0)) - (rl_picture->height / 2.0),\n\t\t\t\trl_picture, 0, 0,\n\t\t\t\trl_picture->width,\n\t\t\t\trl_picture->height, 1\n\t\t\t);\n\t\t}\n\t\telse {\n\t\t\t// Just print \"RL\" instead.\n\t\t\tDraw_String(px + 12 - 8, py + Q_rint((cell_height / 2.0)) - 4, \"RL\");\n\t\t}\n\t}\n\n\t// Draw the lg if the current player has it and the style allows it.\n\tif ((info->stats[STAT_ITEMS] & IT_LIGHTNING) && hud_frags_show_lg) {\n\t\tif (!hud_frags_textonly) {\n\t\t\t// Draw the lg-pic.\n\t\t\tDraw_SSubPic(\n\t\t\t\thud_frags_show_rl ? px + rl_picture->width + 24 : px,\n\t\t\t\tpy + Q_rint((cell_height / 2.0)) - (lg_picture->height / 2.0),\n\t\t\t\tlg_picture, 0, 0,\n\t\t\t\tlg_picture->width,\n\t\t\t\tlg_picture->height, 1\n\t\t\t);\n\t\t}\n\t\telse {\n\t\t\t// Just print \"LG\" instead.\n\t\t\tDraw_String(hud_frags_show_rl ? px + 8 + 24 : px + 12 - 8, py + Q_rint((cell_height / 2.0)) - 4, \"LG\");\n\t\t}\n\t}\n\n\t// Only draw powerups if the current player has one and the style allows it.\n\tif (hud_frags_show_powerup) {\n\t\t//float powerups_x = px + (spec_extra_weapon_w / 2.0);\n\t\tfloat powerups_x = px + (weapon_width / 2.0);\n\n\t\tif (info->stats[STAT_ITEMS] & IT_INVULNERABILITY\n\t\t\t&& info->stats[STAT_ITEMS] & IT_INVISIBILITY\n\t\t\t&& info->stats[STAT_ITEMS] & IT_QUAD) {\n\t\t\tDraw_ColoredString(Q_rint(powerups_x - 10), py, \"&c0ffQ&cf00P&cff0R\", 0, false);\n\t\t}\n\t\telse if (info->stats[STAT_ITEMS] & IT_QUAD\n\t\t\t&& info->stats[STAT_ITEMS] & IT_INVULNERABILITY) {\n\t\t\tDraw_ColoredString(Q_rint(powerups_x - 8), py, \"&c0ffQ&cf00P\", 0, false);\n\t\t}\n\t\telse if (info->stats[STAT_ITEMS] & IT_QUAD\n\t\t\t&& info->stats[STAT_ITEMS] & IT_INVISIBILITY) {\n\t\t\tDraw_ColoredString(Q_rint(powerups_x - 8), py, \"&c0ffQ&cff0R\", 0, false);\n\t\t}\n\t\telse if (info->stats[STAT_ITEMS] & IT_INVULNERABILITY\n\t\t\t&& info->stats[STAT_ITEMS] & IT_INVISIBILITY) {\n\t\t\tDraw_ColoredString(Q_rint(powerups_x - 8), py, \"&cf00P&cff0R\", 0, false);\n\t\t}\n\t\telse if (info->stats[STAT_ITEMS] & IT_QUAD) {\n\t\t\tDraw_ColoredString(Q_rint(powerups_x - 4), py, \"&c0ffQ\", 0, false);\n\t\t}\n\t\telse if (info->stats[STAT_ITEMS] & IT_INVULNERABILITY) {\n\t\t\tDraw_ColoredString(Q_rint(powerups_x - 4), py, \"&cf00P\", 0, false);\n\t\t}\n\t\telse if (info->stats[STAT_ITEMS] & IT_INVISIBILITY) {\n\t\t\tDraw_ColoredString(Q_rint(powerups_x - 4), py, \"&cff0R\", 0, false);\n\t\t}\n\t}\n\n\tpx += weapon_width + FRAGS_HEALTH_SPACING;\n\n\t// Draw health bar. (not flipped)\n\tif (!flip && hud_frags_show_health) {\n\t\tFrags_DrawHealthBar(info->stats[STAT_HEALTH], px, py, cell_height, 3, false, false);\n\t\tpx += 3 + FRAGS_HEALTH_SPACING;\n\t}\n\n\treturn px;\n}\n\nstatic void Frags_DrawBackground(\n\tint px, int py, int cell_width, int cell_height,\n\tint space_x, int space_y, int max_name_length, int max_team_length,\n\tint bg_color, int shownames, int showteams, int drawBrackets, int style\n)\n{\n\tint bg_width = cell_width + space_x;\n\t//int bg_color = Sbar_BottomColor(info);\n\tfloat bg_alpha = 0.3;\n\n\tif (style == 4\n\t\t|| style == 6\n\t\t|| style == 8)\n\t\tbg_alpha = 0;\n\n\tif (shownames)\n\t\tbg_width += max_name_length * 8 + space_x;\n\n\tif (showteams)\n\t\tbg_width += max_team_length * 8 + space_x;\n\n\tif (drawBrackets)\n\t\tbg_alpha = 0.7;\n\n\tif (style == 7 || style == 8)\n\t\tbg_color = 0x4f;\n\n\tDraw_AlphaFill(px - 1, py - space_y / 2, bg_width, cell_height + space_y, bg_color, bg_alpha);\n\n\tif (drawBrackets && (style == 5 || style == 6)) {\n\t\tDraw_Fill(px - 1, py - 1 - space_y / 2, bg_width, 1, 0x4f);\n\n\t\tDraw_Fill(px - 1, py - space_y / 2, 1, cell_height + space_y, 0x4f);\n\t\tDraw_Fill(px + bg_width - 1, py - 1 - space_y / 2, 1, cell_height + 1 + space_y, 0x4f);\n\n\t\tDraw_Fill(px - 1, py + cell_height + space_y / 2, bg_width + 1, 1, 0x4f);\n\t}\n}\n\nstatic int Frags_DrawText(\n\tint px, int py,\n\tint cell_width, int cell_height,\n\tint space_x, int space_y,\n\tint max_name_length, int max_team_length,\n\tint flip, int pad,\n\tint shownames, int showteams,\n\tchar* name, char* team, float scale, qbool proportional,\n\tint wipeout, int isdead, int timetospawn\n)\n{\n\tchar _name[MAX_FRAGS_NAME + 1];\n\tchar _team[MAX_FRAGS_NAME + 1];\n\tfloat team_length = 0;\n\tint name_length = 0;\n\tfloat char_size = 8 * scale;\n\tint y_pos;\n\tclrinfo_t color;\n\tqbool use_wipeout = (check_ktx_ca_wo() && wipeout);\n\n\tcolor.i = 0;\n\ty_pos = Q_rint(py + (cell_height / 2.0) - 4 * scale);\n\n\tif (use_wipeout && (isdead == 1) && (timetospawn > 0) && (timetospawn < 999)){\n\t\tcolor.c = RGBA_TO_COLOR(0x55, 0x55, 0x55, (byte)(1 * 255));\n\t}\n\telse if (use_wipeout && (isdead == 2)){\n\t\tcolor.c = RGBA_TO_COLOR(0x33, 0x33, 0x33, (byte)(1 * 255));\n\t}\n\telse {\n\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xFF, 0xFF, (byte)(1 * 255));\n\t}\n\n\t// Draw team\n\tif (showteams && cl.teamplay) {\n\t\tstrlcpy(_team, team, clamp(max_team_length, 0, sizeof(_team)));\n\t\tteam_length = Draw_StringLength(_team, -1, scale, proportional);\n\n\t\tif (!flip) {\n\t\t\tpx += space_x;\n\t\t}\n\n\t\tif (pad && flip) {\n\t\t\tDraw_SColoredAlphaString(px + max_team_length * char_size - team_length, y_pos, _team, &color, 1, 0, scale, 1, proportional);\n\t\t\tpx += max_team_length * char_size;\n\t\t}\n\t\telse if (pad) {\n\t\t\tDraw_SColoredAlphaString(px, y_pos, _team, &color, 1, 0, scale, 1, proportional);\n\t\t\tpx += max_team_length * char_size;\n\t\t}\n\t\telse {\n\t\t\tpx += Draw_SColoredAlphaString(px, y_pos, _team, &color, 1, 0, scale, 1, proportional);\n\t\t}\n\n\t\tif (flip) {\n\t\t\tpx += space_x;\n\t\t}\n\t}\n\n\tif (shownames) {\n\t\t// Draw name\n\t\tstrlcpy(_name, name, clamp(max_name_length, 0, sizeof(_name)));\n\t\tname_length = Draw_StringLength(_name, -1, scale, proportional);\n\n\t\tif (flip && pad) {\n\t\t\tDraw_SColoredAlphaString(px + max_name_length * char_size - name_length, y_pos, _name, &color, 1, 0, scale, 1, proportional);\n\t\t\tpx += max_name_length * char_size;\n\t\t}\n\t\telse if (pad) {\n\t\t\tDraw_SColoredAlphaString(px, y_pos, _name, &color, 1, 0, scale, 1, proportional);\n\t\t\tpx += max_name_length * char_size;\n\t\t}\n\t\telse {\n\t\t\tpx += Draw_SColoredAlphaString(px, y_pos, _name, &color, 1, 0, scale, 1, proportional);\n\t\t}\n\n\t\tpx += space_x;\n\t}\n\n\treturn px;\n}\n\nstatic void SCR_HUD_DrawFrags(hud_t *hud)\n{\n\tint width = 0, height = 0;\n\tint x, y;\n\tint max_team_length = 0;\n\tint max_name_length = 0;\n\n\tint rows, cols, cell_width, cell_height, space_x, space_y;\n\tint a_rows, a_cols; // actual\n\tqbool any_labels_or_info;\n\tqbool two_lines;\n\n\textern ti_player_t ti_clients[MAX_CLIENTS];\n\n\tstatic cvar_t\n\t\t*hud_frags_cell_width = NULL,\n\t\t*hud_frags_cell_height,\n\t\t*hud_frags_rows,\n\t\t*hud_frags_cols,\n\t\t*hud_frags_space_x,\n\t\t*hud_frags_space_y,\n\t\t*hud_frags_vertical,\n\t\t*hud_frags_strip,\n\t\t*hud_frags_shownames,\n\t\t*hud_frags_hidefrags,\n\t\t*hud_frags_wipeout,\n\t\t*hud_frags_teams,\n\t\t*hud_frags_padtext,\n\t\t*hud_frags_extra_spec,\n\t\t*hud_frags_fliptext,\n\t\t*hud_frags_style,\n\t\t*hud_frags_bignum,\n\t\t*hud_frags_colors_alpha,\n\t\t*hud_frags_maxname,\n\t\t*hud_frags_notintp,\n\t\t*hud_frags_fixedwidth,\n\t\t*hud_frags_scale,\n\t\t*hud_frags_proportional;\n\n\textern mpic_t *sb_weapons[7][8]; // sbar.c ... Used for displaying the RL.\n\tmpic_t *rl_picture;\t\t\t\t // Picture of RL.\n\trl_picture = sb_weapons[0][5];\n\n\tif (hud_frags_cell_width == NULL) {   // first time\n\t\tchar specval[256];\n\n\t\thud_frags_cell_width = HUD_FindVar(hud, \"cell_width\");\n\t\thud_frags_cell_height = HUD_FindVar(hud, \"cell_height\");\n\t\thud_frags_rows = HUD_FindVar(hud, \"rows\");\n\t\thud_frags_cols = HUD_FindVar(hud, \"cols\");\n\t\thud_frags_space_x = HUD_FindVar(hud, \"space_x\");\n\t\thud_frags_space_y = HUD_FindVar(hud, \"space_y\");\n\t\thud_frags_strip = HUD_FindVar(hud, \"strip\");\n\t\thud_frags_vertical = HUD_FindVar(hud, \"vertical\");\n\t\thud_frags_shownames = HUD_FindVar(hud, \"shownames\");\n\t\thud_frags_hidefrags = HUD_FindVar(hud, \"hidefrags\");\n\t\thud_frags_wipeout = HUD_FindVar(hud, \"wipeout\");\n\t\thud_frags_teams = HUD_FindVar(hud, \"showteams\");\n\t\thud_frags_padtext = HUD_FindVar(hud, \"padtext\");\n\t\thud_frags_extra_spec = HUD_FindVar(hud, \"extra_spec_info\");\n\t\thud_frags_fliptext = HUD_FindVar(hud, \"fliptext\");\n\t\thud_frags_style = HUD_FindVar(hud, \"style\");\n\t\thud_frags_bignum = HUD_FindVar(hud, \"bignum\");\n\t\thud_frags_colors_alpha = HUD_FindVar(hud, \"colors_alpha\");\n\t\thud_frags_maxname = HUD_FindVar(hud, \"maxname\");\n\t\thud_frags_notintp = HUD_FindVar(hud, \"notintp\");\n\t\thud_frags_fixedwidth = HUD_FindVar(hud, \"fixedwidth\");\n\t\thud_frags_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_frags_proportional = HUD_FindVar(hud, \"proportional\");\n\n\t\t// Set the OnChange function for extra spec info.\n\t\thud_frags_extra_spec->OnChange = Frags_OnChangeExtraSpecInfo;\n\t\tstrlcpy(specval, hud_frags_extra_spec->string, sizeof(specval));\n\t\tCvar_Set(hud_frags_extra_spec, specval);\n\t}\n\n\t// Don't draw the frags if we're in teamplay.\n\tif (hud_frags_notintp->value && cl.teamplay) {\n\t\tHUD_PrepareDraw(hud, width, height, &x, &y);\n\t\treturn;\n\t}\n\n\t//\n\t// Clamp values to be \"sane\".\n\t//\n\t{\n\t\trows = hud_frags_rows->value;\n\t\tclamp(rows, 1, MAX_CLIENTS);\n\n\t\tcols = hud_frags_cols->value;\n\t\tclamp(cols, 1, MAX_CLIENTS);\n\n\t\t// Some users doesn't want to show the actual frags, just\n\t\t// extra_spec_info stuff + names.\n\t\tcell_width = hud_frags_cell_width->value * hud_frags_scale->value;\n\t\tclamp(cell_width, 0, 128);\n\n\t\tcell_height = hud_frags_cell_height->value * hud_frags_scale->value;\n\t\tclamp(cell_height, 7, 32);\n\n\t\tspace_x = hud_frags_space_x->value * hud_frags_scale->value;\n\t\tclamp(space_x, 0, 128);\n\n\t\tspace_y = hud_frags_space_y->value * hud_frags_scale->value;\n\t\tclamp(space_y, 0, 128);\n\t}\n\n\tif (hud_frags_strip->integer) {\n\t\t// Auto set the number of rows / cols based on the number of players.\n\t\t// (This is kinda fucked up, but I won't mess with it for the sake of backwards compability).\n\t\tif (hud_frags_vertical->value == 1) {\n\t\t\ta_cols = min((n_players + rows - 1) / rows, cols);\n\t\t\ta_rows = min(rows, n_players);\n\t\t}\n\t\telse if (hud_frags_vertical->value == 2) {\n\t\t\ta_cols = min(n_teams, cols);\n\t\t\ta_rows = min(rows, ((n_players % a_cols) ? n_players/a_cols + 1 : n_players/a_cols));\n\t\t}\n\t\telse {\n\t\t\ta_rows = min((n_players + cols - 1) / cols, rows);\n\t\t\ta_cols = min(cols, n_players);\n\t\t}\n\t}\n\telse {\n\t\ta_rows = rows;\n\t\ta_cols = cols;\n\t}\n\n\twidth = (a_cols * cell_width) + ((a_cols + 1) * space_x);\n\theight = (a_rows * cell_height) + ((a_rows + 1) * space_y);\n\n\t// Get the longest name/team name for padding.\n\tif (hud_frags_shownames->value || hud_frags_teams->value) {\n\t\tint cur_length = 0;\n\t\tint n;\n\n\t\t// If the user has set a limit on how many chars that\n\t\t// are allowed to be shown for a name/teamname.\n\t\tif (hud_frags_fixedwidth->value) {\n\t\t\tmax_name_length = min(max(0, (int)hud_frags_maxname->value) + 1, MAX_FRAGS_NAME);\n\t\t\tmax_team_length = min(max(0, (int)hud_frags_maxname->value) + 1, MAX_FRAGS_NAME);\n\t\t}\n\t\telse {\n\t\t\tfor (n = 0; n < n_players; n++) {\n\t\t\t\tplayer_info_t *info = &cl.players[sorted_players[n].playernum];\n\t\t\t\tcur_length = strlen(info->name);\n\n\t\t\t\t// Name.\n\t\t\t\tif (cur_length >= max_name_length) {\n\t\t\t\t\tmax_name_length = cur_length + 1;\n\t\t\t\t}\n\n\t\t\t\tcur_length = strlen(info->team);\n\n\t\t\t\t// Team name.\n\t\t\t\tif (cur_length >= max_team_length) {\n\t\t\t\t\tmax_team_length = cur_length + 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmax_name_length = min(max(0, (int)hud_frags_maxname->value), max_name_length) + 1;\n\t\t\tmax_team_length = min(max(0, (int)hud_frags_maxname->value), max_team_length) + 1;\n\t\t}\n\n\t\t// We need a wider box to draw in if we show the names.\n\t\tif (hud_frags_shownames->value) {\n\t\t\twidth += (a_cols * FontFixedWidth(max_name_length, hud_frags_scale->value, false, hud_frags_proportional->integer)) + ((a_cols + 1) * space_x);\n\t\t}\n\n\t\tif (cl.teamplay && hud_frags_teams->value) {\n\t\t\twidth += (a_cols * FontFixedWidth(max_team_length, hud_frags_scale->value, false, hud_frags_proportional->integer)) + ((a_cols + 1) * space_x);\n\t\t}\n\t}\n\n\t// Make room for the extra spectator stuff.\n\tif (hud_frags_extra_spec_info && (cls.demoplayback || cl.spectator)) {\n\t\tif (hud_frags_show_health) {\n\t\t\twidth += a_cols * (3 + FRAGS_HEALTH_SPACING);\n\t\t}\n\t\tif (hud_frags_show_armor || hud_frags_show_powerup || hud_frags_show_rl) {\n\t\t\twidth += a_cols * (!hud_frags_textonly ? rl_picture->width : 24 * hud_frags_scale->value);\n\t\t}\n\t}\n\n\tany_labels_or_info = hud_frags_shownames->value || hud_frags_teams->value || hud_frags_extra_spec_info;\n\ttwo_lines = (cls.demoplayback || cl.spectator) && any_labels_or_info && (hud_frags_horiz_health || hud_frags_horiz_power);\n\n\tif (two_lines) {\n\t\theight *= 2;\n\t}\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tint i = 0;\n\t\tint player_x = 0;\n\t\tint player_y = 0;\n\t\tint num = 0;\n\t\tint drawBrackets = 0;\n\n\t\t// The number of players that are to be visible.\n\t\tint limit = min(n_players, a_rows * a_cols);\n\n\t\tnum = 0;\n\t\tnum = (num <= limit || num >= n_players) ? 0 : num;\n\n\t\t//\n\t\t// Loop through all the positions that should be drawn (columns * rows or number of players).\n\t\t//\n\t\t// Start drawing player \"num\", usually the first player in the array, but if\n\t\t// showself_always is set this might be someone else (since we need to make sure the current\n\t\t// player is always shown).\n\t\t//\n\t\tfor (i = 0; i < limit; i++) {\n\t\t\tplayer_info_t *info;\n\t\t\tti_player_t *ti_cl;\n\n\t\t\t// Always include the current player's position\n\t\t\tif (active_player_position >= 0 && i == limit - 1 && num < active_player_position) {\n\t\t\t\tnum = active_player_position;\n\t\t\t}\n\n\t\t\tinfo = &cl.players[sorted_players[num].playernum]; // FIXME! johnnycz; causes crashed on some demos\n\t\t\tti_cl = &ti_clients[sorted_players[num].playernum];\n\n\t\t\t//\n\t\t\t// Set the coordinates where to draw the next element.\n\t\t\t//\n\t\t\tif (hud_frags_vertical->value) {\n\t\t\t\tif (i % a_rows == 0) {\n\t\t\t\t\t// We're drawing a new column.\n\t\t\t\t\tint element_width = cell_width + space_x;\n\n\t\t\t\t\t// Get the width of all the stuff that is shown, the name, frag cell and so on.\n\t\t\t\t\tif (hud_frags_shownames->value) {\n\t\t\t\t\t\telement_width += (max_name_length) * 8 * hud_frags_scale->value;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (hud_frags_teams->value) {\n\t\t\t\t\t\telement_width += (max_team_length) * 8 * hud_frags_scale->value;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (hud_frags_extra_spec_info && (cls.demoplayback || cl.spectator)) {\n\t\t\t\t\t\telement_width += (!hud_frags_textonly ? rl_picture->width : 24 * hud_frags_scale->value);\n\t\t\t\t\t}\n\n\t\t\t\t\tplayer_x = x + space_x + ((i / a_rows) * element_width);\n\n\t\t\t\t\t// New column.\n\t\t\t\t\tplayer_y = y + space_y;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (i % a_cols == 0) {\n\t\t\t\t// Drawing new row.\n\t\t\t\tplayer_x = x + space_x;\n\t\t\t\tplayer_y = y + space_y + (i / a_cols) * (cell_height * (two_lines ? 2 : 1) + space_y);\n\t\t\t}\n\n\t\t\tdrawBrackets = 0;\n\n\t\t\t// Bug fix. Before the wrong player would be higlighted\n\t\t\t// during qwd-playback, since you ARE the player that you're\n\t\t\t// being spectated (you're not a spectator).\n\t\t\tif (cls.demoplayback && !cl.spectator && !cls.mvdplayback) {\n\t\t\t\tdrawBrackets = (sorted_players[num].playernum == cl.playernum);\n\t\t\t}\n\t\t\telse if (cls.demoplayback || cl.spectator) {\n\t\t\t\tdrawBrackets = (cl.spec_track == sorted_players[num].playernum && Cam_TrackNum() >= 0);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tdrawBrackets = (sorted_players[num].playernum == cl.playernum);\n\t\t\t}\n\n\t\t\t// Don't draw any brackets in multiview since we're tracking several players.\n\t\t\tif (CL_MultiviewEnabled() && !CL_MultiviewInsetEnabled()) {\n\t\t\t\tdrawBrackets = 0;\n\t\t\t}\n\n\t\t\tif (any_labels_or_info) {\n\t\t\t\t// Relative x coordinate where we draw the subitems.\n\t\t\t\tint rel_player_x = player_x;\n\t\t\t\tqbool odd_column = (hud_frags_vertical->value ? (i / a_rows) : (i % a_cols));\n\t\t\t\tqbool fliptext = hud_frags_fliptext->integer == 1;\n\n\t\t\t\tfliptext |= hud_frags_fliptext->integer == 2 && !odd_column;\n\t\t\t\tfliptext |= hud_frags_fliptext->integer == 3 && odd_column;\n\n\t\t\t\tif (hud_frags_style->value >= 4 && hud_frags_style->value <= 8) {\n\t\t\t\t\t// Draw background based on the style.\n\t\t\t\t\tFrags_DrawBackground(\n\t\t\t\t\t\tplayer_x, player_y, cell_width, cell_height, space_x, space_y,\n\t\t\t\t\t\tmax_name_length, max_team_length, Sbar_BottomColor(info),\n\t\t\t\t\t\thud_frags_shownames->value, hud_frags_teams->value, drawBrackets,\n\t\t\t\t\t\thud_frags_style->value\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (fliptext) {\n\t\t\t\t\t//\n\t\t\t\t\t// Flip the text\n\t\t\t\t\t// NAME | TEAM | FRAGS | EXTRA_SPEC_INFO\n\t\t\t\t\t//\n\t\t\t\t\tif (two_lines) {\n\t\t\t\t\t\tFrags_DrawHorizontalHealthBar(info, rel_player_x, player_y, max_name_length * 8 * hud_frags_scale->value, cell_height, fliptext, hud_frags_horiz_power);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Draw name.\n\t\t\t\t\trel_player_x = Frags_DrawText(\n\t\t\t\t\t\trel_player_x, player_y + (two_lines ? cell_height : 0), cell_width, cell_height,\n\t\t\t\t\t\tspace_x, space_y, max_name_length, max_team_length,\n\t\t\t\t\t\tfliptext, hud_frags_padtext->value,\n\t\t\t\t\t\thud_frags_shownames->value, 0,\n\t\t\t\t\t\tinfo->name, info->team, hud_frags_scale->value, hud_frags_proportional->integer,\n\t\t\t\t\t\thud_frags_wipeout->value,\n\t\t\t\t\t\tti_cl->isdead,\n\t\t\t\t\t\tti_cl->timetospawn\n\t\t\t\t\t);\n\n\t\t\t\t\t// Draw team.\n\t\t\t\t\trel_player_x = Frags_DrawText(\n\t\t\t\t\t\trel_player_x, player_y + (two_lines ? cell_height : 0), cell_width, cell_height,\n\t\t\t\t\t\tspace_x, space_y, max_name_length, max_team_length,\n\t\t\t\t\t\tfliptext, hud_frags_padtext->value,\n\t\t\t\t\t\t0, hud_frags_teams->value,\n\t\t\t\t\t\tinfo->name, info->team, hud_frags_scale->value, hud_frags_proportional->integer,\n\t\t\t\t\t\thud_frags_wipeout->value,\n\t\t\t\t\t\tti_cl->isdead,\n\t\t\t\t\t\tti_cl->timetospawn\n\t\t\t\t\t);\n\n\t\t\t\t\tFrags_DrawColors(\n\t\t\t\t\t\trel_player_x, player_y, cell_width, cell_height * (two_lines ? 2 : 1),\n\t\t\t\t\t\tSbar_TopColor(info), Sbar_BottomColor(info), hud_frags_colors_alpha->value,\n\t\t\t\t\t\tinfo->frags,\n\t\t\t\t\t\tdrawBrackets,\n\t\t\t\t\t\thud_frags_style->value,\n\t\t\t\t\t\thud_frags_bignum->value,\n\t\t\t\t\t\thud_frags_scale->value,\n\t\t\t\t\t\thud_frags_proportional->integer,\n\t\t\t\t\t\thud_frags_wipeout->value,\n\t\t\t\t\t\thud_frags_hidefrags->value,\n\t\t\t\t\t\tti_cl->isdead,\n\t\t\t\t\t\tti_cl->timetospawn\n\t\t\t\t\t);\n\n\t\t\t\t\trel_player_x += cell_width + space_x;\n\n\t\t\t\t\t// Show extra information about all the players if spectating:\n\t\t\t\t\t// - What armor they have.\n\t\t\t\t\t// - How much health.\n\t\t\t\t\t// - If they have RL or not.\n\t\t\t\t\trel_player_x = Frags_DrawExtraSpecInfo(info, rel_player_x, player_y, cell_width, cell_height,\n\t\t\t\t\t\tspace_x, space_y,\n\t\t\t\t\t\tfliptext\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t//\n\t\t\t\t\t// Don't flip the text\n\t\t\t\t\t// EXTRA_SPEC_INFO | FRAGS | TEAM | NAME\n\t\t\t\t\t//\n\t\t\t\t\trel_player_x = Frags_DrawExtraSpecInfo(info, rel_player_x, player_y, cell_width, cell_height,\n\t\t\t\t\t\tspace_x, space_y,\n\t\t\t\t\t\tfliptext\n\t\t\t\t\t);\n\n\t\t\t\t\tFrags_DrawColors(rel_player_x, player_y, cell_width, cell_height * (two_lines ? 2 : 1),\n\t\t\t\t\t\tSbar_TopColor(info), Sbar_BottomColor(info), hud_frags_colors_alpha->value,\n\t\t\t\t\t\tinfo->frags,\n\t\t\t\t\t\tdrawBrackets,\n\t\t\t\t\t\thud_frags_style->value,\n\t\t\t\t\t\thud_frags_bignum->value,\n\t\t\t\t\t\thud_frags_scale->value,\n\t\t\t\t\t\thud_frags_proportional->integer,\n\t\t\t\t\t\thud_frags_wipeout->value,\n\t\t\t\t\t\thud_frags_hidefrags->value,\n\t\t\t\t\t\tti_cl->isdead,\n\t\t\t\t\t\tti_cl->timetospawn\n\t\t\t\t\t);\n\n\t\t\t\t\trel_player_x += cell_width + space_x;\n\n\t\t\t\t\tif (two_lines) {\n\t\t\t\t\t\tFrags_DrawHorizontalHealthBar(info, rel_player_x, player_y, max_name_length * 8 * hud_frags_scale->value, cell_height, fliptext, hud_frags_horiz_power);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Draw team.\n\t\t\t\t\trel_player_x = Frags_DrawText(rel_player_x, player_y + (two_lines ? cell_height : 0), cell_width, cell_height,\n\t\t\t\t\t\tspace_x, space_y, max_name_length, max_team_length,\n\t\t\t\t\t\tfliptext, hud_frags_padtext->value,\n\t\t\t\t\t\t0, hud_frags_teams->value,\n\t\t\t\t\t\tinfo->name, info->team, hud_frags_scale->value, hud_frags_proportional->integer,\n\t\t\t\t\t\thud_frags_wipeout->value,\n\t\t\t\t\t\tti_cl->isdead,\n\t\t\t\t\t\tti_cl->timetospawn\n\t\t\t\t\t);\n\n\t\t\t\t\t// Draw name.\n\t\t\t\t\trel_player_x = Frags_DrawText(\n\t\t\t\t\t\trel_player_x, player_y + (two_lines ? cell_height : 0), cell_width, cell_height,\n\t\t\t\t\t\tspace_x, space_y, max_name_length, max_team_length,\n\t\t\t\t\t\tfliptext, hud_frags_padtext->value,\n\t\t\t\t\t\thud_frags_shownames->value, 0,\n\t\t\t\t\t\tinfo->name, info->team, hud_frags_scale->value, hud_frags_proportional->integer,\n\t\t\t\t\t\thud_frags_wipeout->value,\n\t\t\t\t\t\tti_cl->isdead,\n\t\t\t\t\t\tti_cl->timetospawn\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (hud_frags_vertical->value) {\n\t\t\t\t\t// Next row.\n\t\t\t\t\tplayer_y += cell_height * (two_lines ? 2 : 1) + space_y;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Next column.\n\t\t\t\t\tplayer_x = rel_player_x + space_x;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Only showing the frags, no names or extra spec info.\n\n\t\t\t\tFrags_DrawColors(\n\t\t\t\t\tplayer_x, player_y, cell_width, cell_height,\n\t\t\t\t\tSbar_TopColor(info), Sbar_BottomColor(info), hud_frags_colors_alpha->value,\n\t\t\t\t\tinfo->frags,\n\t\t\t\t\tdrawBrackets,\n\t\t\t\t\thud_frags_style->value,\n\t\t\t\t\thud_frags_bignum->value,\n\t\t\t\t\thud_frags_scale->value,\n\t\t\t\t\thud_frags_proportional->integer,\n\t\t\t\t\thud_frags_wipeout->value,\n\t\t\t\t\thud_frags_hidefrags->value,\n\t\t\t\t\tti_cl->isdead,\n\t\t\t\t\tti_cl->timetospawn\n\t\t\t\t);\n\n\t\t\t\tif (hud_frags_vertical->value) {\n\t\t\t\t\t// Next row.\n\t\t\t\t\tplayer_y += cell_height + space_y;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Next column.\n\t\t\t\t\tplayer_x += cell_width + space_x;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Next player.\n\t\t\tnum++;\n\t\t}\n\t}\n}\n\nstatic void SCR_HUD_DrawTeamFrags(hud_t *hud)\n{\n\tint width = 0, height = 0;\n\tint x, y;\n\tint max_team_length = 0, num = 0;\n\tint rows, cols, cell_width, cell_height, space_x, space_y;\n\tint a_rows, a_cols; // actual\n\n\tstatic cvar_t\n\t\t*hud_teamfrags_cell_width,\n\t\t*hud_teamfrags_cell_height,\n\t\t*hud_teamfrags_rows,\n\t\t*hud_teamfrags_cols,\n\t\t*hud_teamfrags_space_x,\n\t\t*hud_teamfrags_space_y,\n\t\t*hud_teamfrags_vertical,\n\t\t*hud_teamfrags_strip,\n\t\t*hud_teamfrags_shownames,\n\t\t*hud_teamfrags_fliptext,\n\t\t*hud_teamfrags_padtext,\n\t\t*hud_teamfrags_style,\n\t\t*hud_teamfrags_extra_spec,\n\t\t*hud_teamfrags_onlytp,\n\t\t*hud_teamfrags_bignum,\n\t\t*hud_teamfrags_colors_alpha,\n\t\t*hud_teamfrags_scale,\n\t\t*hud_teamfrags_proportional;\n\n\textern mpic_t *sb_weapons[7][8]; // sbar.c\n\tmpic_t rl_picture = *sb_weapons[0][5];\n\n\tif (hud_teamfrags_cell_width == 0)    // first time\n\t{\n\t\thud_teamfrags_cell_width = HUD_FindVar(hud, \"cell_width\");\n\t\thud_teamfrags_cell_height = HUD_FindVar(hud, \"cell_height\");\n\t\thud_teamfrags_rows = HUD_FindVar(hud, \"rows\");\n\t\thud_teamfrags_cols = HUD_FindVar(hud, \"cols\");\n\t\thud_teamfrags_space_x = HUD_FindVar(hud, \"space_x\");\n\t\thud_teamfrags_space_y = HUD_FindVar(hud, \"space_y\");\n\t\thud_teamfrags_strip = HUD_FindVar(hud, \"strip\");\n\t\thud_teamfrags_vertical = HUD_FindVar(hud, \"vertical\");\n\t\thud_teamfrags_shownames = HUD_FindVar(hud, \"shownames\");\n\t\thud_teamfrags_fliptext = HUD_FindVar(hud, \"fliptext\");\n\t\thud_teamfrags_padtext = HUD_FindVar(hud, \"padtext\");\n\t\thud_teamfrags_style = HUD_FindVar(hud, \"style\");\n\t\thud_teamfrags_extra_spec = HUD_FindVar(hud, \"extra_spec_info\");\n\t\thud_teamfrags_onlytp = HUD_FindVar(hud, \"onlytp\");\n\t\thud_teamfrags_bignum = HUD_FindVar(hud, \"bignum\");\n\t\thud_teamfrags_colors_alpha = HUD_FindVar(hud, \"colors_alpha\");\n\t\thud_teamfrags_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_teamfrags_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\t// Don't draw the frags if we're not in teamplay.\n\tif (hud_teamfrags_onlytp->value && !cl.teamplay) {\n\t\tHUD_PrepareDraw(hud, width, height, &x, &y);\n\t\treturn;\n\t}\n\n\trows = hud_teamfrags_rows->value;\n\tclamp(rows, 1, MAX_CLIENTS);\n\tcols = hud_teamfrags_cols->value;\n\tclamp(cols, 1, MAX_CLIENTS);\n\tcell_width = hud_teamfrags_cell_width->value;\n\tclamp(cell_width, 28, 128);\n\tcell_height = hud_teamfrags_cell_height->value;\n\tclamp(cell_height, 7, 32);\n\tspace_x = hud_teamfrags_space_x->value;\n\tclamp(space_x, 0, 128);\n\tspace_y = hud_teamfrags_space_y->value;\n\tclamp(space_y, 0, 128);\n\n\tif (hud_teamfrags_strip->value) {\n\t\tif (hud_teamfrags_vertical->value) {\n\t\t\ta_cols = min((n_teams + rows - 1) / rows, cols);\n\t\t\ta_rows = min(rows, n_teams);\n\t\t}\n\t\telse {\n\t\t\ta_rows = min((n_teams + cols - 1) / cols, rows);\n\t\t\ta_cols = min(cols, n_teams);\n\t\t}\n\t}\n\telse {\n\t\ta_rows = rows;\n\t\ta_cols = cols;\n\t}\n\n\twidth = a_cols * cell_width + (a_cols + 1)*space_x;\n\theight = a_rows * cell_height + (a_rows + 1)*space_y;\n\n\t// Get the longest team name for padding.\n\tif (hud_teamfrags_shownames->value || hud_teamfrags_extra_spec->value) {\n\t\tint rlcount_width = 0;\n\n\t\tint cur_length = 0;\n\t\tint n;\n\n\t\tfor (n = 0; n < n_teams; n++) {\n\t\t\tif (hud_teamfrags_shownames->value) {\n\t\t\t\tcur_length = strlen(sorted_teams[n].name);\n\n\t\t\t\t// Team name\n\t\t\t\tif (cur_length >= max_team_length) {\n\t\t\t\t\tmax_team_length = cur_length + 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Calculate the length of the extra spec info.\n\t\tif (hud_teamfrags_extra_spec->value && (cls.demoplayback || cl.spectator)) {\n\t\t\tif (hud_teamfrags_extra_spec->value == TEAMFRAGS_EXTRA_SPEC_BEFORE) {\n\t\t\t\t// Draw the rl count before the rl icon.\n\t\t\t\trlcount_width = rl_picture.width + 8 + 1 + 1;\n\t\t\t}\n\t\t\telse if (hud_teamfrags_extra_spec->value == TEAMFRAGS_EXTRA_SPEC_ONTOP) {\n\t\t\t\t// Draw the rl count on top of the RL instead of infront.\n\t\t\t\trlcount_width = rl_picture.width + 1;\n\t\t\t}\n\t\t\telse if (hud_teamfrags_extra_spec->value == TEAMFRAGS_EXTRA_SPEC_NOICON) {\n\t\t\t\t// Only draw the rl count.\n\t\t\t\trlcount_width = 8 + 1;\n\t\t\t}\n\t\t\telse if (hud_teamfrags_extra_spec->value == TEAMFRAGS_EXTRA_SPEC_RLTEXT) {\n\t\t\t\trlcount_width = 8 * 3 + 1;\n\t\t\t}\n\t\t}\n\n\t\twidth += a_cols * max_team_length * 8 + (a_cols + 1) * space_x + a_cols * rlcount_width;\n\t}\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tint i;\n\t\tint px = 0;\n\t\tint py = 0;\n\t\tint drawBrackets;\n\t\tint limit = min(n_teams, a_rows*a_cols);\n\n\t\tfor (i = 0; i < limit; i++) {\n\t\t\tif (hud_teamfrags_vertical->value) {\n\t\t\t\tif (i % a_rows == 0) {\n\t\t\t\t\tpx = x + space_x + (i / a_rows) * (cell_width + space_x);\n\t\t\t\t\tpy = y + space_y;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (i % a_cols == 0) {\n\t\t\t\t\tpx = x + space_x;\n\t\t\t\t\tpy = y + space_y + (i / a_cols) * (cell_height + space_y);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdrawBrackets = 0;\n\n\t\t\t// Bug fix. Before the wrong player would be higlighted\n\t\t\t// during qwd-playback, since you ARE the player that you're\n\t\t\t// being spectated.\n\t\t\tif (cls.demoplayback && !cl.spectator && !cls.mvdplayback) {\n\t\t\t\t// QWD Playback.\n\t\t\t\tif (!strcmp(sorted_teams[num].name, cl.players[cl.playernum].team)) {\n\t\t\t\t\tdrawBrackets = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (cls.demoplayback || cl.spectator) {\n\t\t\t\t// MVD playback / spectating.\n\t\t\t\tif (!strcmp(cl.players[cl.spec_track].team, sorted_teams[num].name) && Cam_TrackNum() >= 0) {\n\t\t\t\t\tdrawBrackets = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Normal player.\n\t\t\t\tif (!strcmp(sorted_teams[num].name, cl.players[cl.playernum].team)) {\n\t\t\t\t\tdrawBrackets = 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (hud_teamfrags_shownames->value || hud_teamfrags_extra_spec->value) {\n\t\t\t\tint _px = px;\n\t\t\t\tqbool text_lhs = hud_teamfrags_fliptext->integer == 1;\n\t\t\t\ttext_lhs |= hud_teamfrags_fliptext->integer == 2 && ((i % a_cols) % 2 == 0);\n\t\t\t\ttext_lhs |= hud_teamfrags_fliptext->integer == 3 && ((i % a_cols) % 2 == 1);\n\n\t\t\t\t// Draw a background if the style tells us to.\n\t\t\t\tif (hud_teamfrags_style->value >= 4 && hud_teamfrags_style->value <= 8) {\n\t\t\t\t\tFrags_DrawBackground(\n\t\t\t\t\t\tpx, py, cell_width, cell_height, space_x, space_y,\n\t\t\t\t\t\t0, max_team_length, sorted_teams[num].bottom,\n\t\t\t\t\t\t0, hud_teamfrags_shownames->value, drawBrackets,\n\t\t\t\t\t\thud_teamfrags_style->value\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Draw the text on the left or right side of the score?\n\t\t\t\tif (text_lhs) {\n\t\t\t\t\t// Draw team.\n\t\t\t\t\t_px = Frags_DrawText(\n\t\t\t\t\t\t_px, py, cell_width, cell_height,\n\t\t\t\t\t\tspace_x, space_y, 0, max_team_length,\n\t\t\t\t\t\ttrue, hud_teamfrags_padtext->value,\n\t\t\t\t\t\t0, hud_teamfrags_shownames->value,\n\t\t\t\t\t\t\"\", sorted_teams[num].name, hud_teamfrags_scale->value, hud_teamfrags_proportional->integer,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t\t);\n\n\t\t\t\t\tFrags_DrawColors(\n\t\t\t\t\t\t_px, py, cell_width, cell_height,\n\t\t\t\t\t\tsorted_teams[num].top,\n\t\t\t\t\t\tsorted_teams[num].bottom,\n\t\t\t\t\t\thud_teamfrags_colors_alpha->value,\n\t\t\t\t\t\tsorted_teams[num].frags,\n\t\t\t\t\t\tdrawBrackets,\n\t\t\t\t\t\thud_teamfrags_style->value,\n\t\t\t\t\t\thud_teamfrags_bignum->value,\n\t\t\t\t\t\thud_teamfrags_scale->value,\n\t\t\t\t\t\thud_teamfrags_proportional->integer,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t\t);\n\n\t\t\t\t\t_px += cell_width + space_x;\n\n\t\t\t\t\t// Draw the rl if the current player has it and the style allows it.\n\t\t\t\t\t_px = TeamFrags_DrawExtraSpecInfo(num, _px, py, cell_width, cell_height, hud_teamfrags_extra_spec->value);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Draw the rl if the current player has it and the style allows it.\n\t\t\t\t\t_px = TeamFrags_DrawExtraSpecInfo(num, _px, py, cell_width, cell_height, hud_teamfrags_extra_spec->value);\n\n\t\t\t\t\tFrags_DrawColors(\n\t\t\t\t\t\t_px, py, cell_width, cell_height,\n\t\t\t\t\t\tsorted_teams[num].top,\n\t\t\t\t\t\tsorted_teams[num].bottom,\n\t\t\t\t\t\thud_teamfrags_colors_alpha->value,\n\t\t\t\t\t\tsorted_teams[num].frags,\n\t\t\t\t\t\tdrawBrackets,\n\t\t\t\t\t\thud_teamfrags_style->value,\n\t\t\t\t\t\thud_teamfrags_bignum->value,\n\t\t\t\t\t\thud_teamfrags_scale->value,\n\t\t\t\t\t\thud_teamfrags_proportional->integer,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t\t);\n\n\t\t\t\t\t_px += cell_width + space_x;\n\n\t\t\t\t\t// Draw team.\n\t\t\t\t\t_px = Frags_DrawText(\n\t\t\t\t\t\t_px, py, cell_width, cell_height,\n\t\t\t\t\t\tspace_x, space_y, 0, max_team_length,\n\t\t\t\t\t\tfalse, hud_teamfrags_padtext->value,\n\t\t\t\t\t\t0, hud_teamfrags_shownames->value,\n\t\t\t\t\t\t\"\", sorted_teams[num].name,\n\t\t\t\t\t\thud_teamfrags_scale->value,\n\t\t\t\t\t\thud_teamfrags_proportional->integer,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (hud_teamfrags_vertical->value) {\n\t\t\t\t\tpy += cell_height + space_y;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tpx = _px + space_x;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tFrags_DrawColors(\n\t\t\t\t\tpx, py, cell_width, cell_height,\n\t\t\t\t\tsorted_teams[num].top,\n\t\t\t\t\tsorted_teams[num].bottom,\n\t\t\t\t\thud_teamfrags_colors_alpha->value,\n\t\t\t\t\tsorted_teams[num].frags,\n\t\t\t\t\tdrawBrackets,\n\t\t\t\t\thud_teamfrags_style->value,\n\t\t\t\t\thud_teamfrags_bignum->value,\n\t\t\t\t\thud_teamfrags_scale->value,\n\t\t\t\t\thud_teamfrags_proportional->integer,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t\t0\n\t\t\t\t);\n\n\t\t\t\tif (hud_teamfrags_vertical->value) {\n\t\t\t\t\tpy += cell_height + space_y;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tpx += cell_width + space_x;\n\t\t\t\t}\n\t\t\t}\n\t\t\tnum++;\n\t\t}\n\t}\n}\n\nvoid Frags_HudInit(void)\n{\n\tHUD_Register(\n\t\t\"frags\", NULL, \"Show list of player frags in short form.\",\n\t\t0, ca_active, 0, SCR_HUD_DrawFrags,\n\t\t\"0\", \"top\", \"right\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"cell_width\", \"32\",\n\t\t\"cell_height\", \"8\",\n\t\t\"rows\", \"1\",\n\t\t\"cols\", \"4\",\n\t\t\"space_x\", \"1\",\n\t\t\"space_y\", \"1\",\n\t\t\"teamsort\", \"0\",\n\t\t\"strip\", \"1\",\n\t\t\"vertical\", \"0\",\n\t\t\"hidefrags\", \"0\",\n\t\t\"shownames\", \"0\",\n\t\t\"showteams\", \"0\",\n\t\t\"wipeout\", \"1\",\n\t\t\"padtext\", \"1\",\n\t\t\"showself_always\", \"1\",\n\t\t\"extra_spec_info\", \"ALL\",\n\t\t\"fliptext\", \"0\",\n\t\t\"style\", \"0\",\n\t\t\"bignum\", \"0\",\n\t\t\"colors_alpha\", \"1.0\",\n\t\t\"maxname\", \"16\",\n\t\t\"notintp\", \"0\",\n\t\t\"fixedwidth\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\n\t\t\"teamfrags\", NULL, \"Show list of team frags in short form.\",\n\t\t0, ca_active, 0, SCR_HUD_DrawTeamFrags,\n\t\t\"1\", \"ibar\", \"center\", \"before\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"cell_width\", \"32\",\n\t\t\"cell_height\", \"8\",\n\t\t\"rows\", \"1\",\n\t\t\"cols\", \"2\",\n\t\t\"space_x\", \"1\",\n\t\t\"space_y\", \"1\",\n\t\t\"strip\", \"1\",\n\t\t\"vertical\", \"0\",\n\t\t\"shownames\", \"0\",\n\t\t\"padtext\", \"1\",\n\t\t\"fliptext\", \"1\",\n\t\t\"style\", \"0\",\n\t\t\"extra_spec_info\", \"1\",\n\t\t\"onlytp\", \"0\",\n\t\t\"bignum\", \"0\",\n\t\t\"colors_alpha\", \"1.0\",\n\t\t\"maxname\", \"16\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n}\n"
  },
  {
    "path": "src/hud_gamesummary.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"gl_model.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n\nstatic void SCR_Hud_GameSummary(hud_t* hud)\n{\n\tint x, y;\n\tint height = 8;\n\tint width = 0;\n\tchar* format_string;\n\tint icon_size = 32;\n\n\tstatic cvar_t\n\t\t*hud_gamesummary_style = NULL,\n\t\t*hud_gamesummary_onlytp,\n\t\t*hud_gamesummary_scale,\n\t\t*hud_gamesummary_format,\n\t\t*hud_gamesummary_circles,\n\t\t*hud_gamesummary_ratio,\n\t\t*hud_gamesummary_flash,\n\t\t*hud_gamesummary_proportional;\n\n\tif (hud_gamesummary_style == NULL) {\n\t\t// first time\n\t\thud_gamesummary_style = HUD_FindVar(hud, \"style\");\n\t\thud_gamesummary_onlytp = HUD_FindVar(hud, \"onlytp\");\n\t\thud_gamesummary_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_gamesummary_format = HUD_FindVar(hud, \"format\");\n\t\thud_gamesummary_circles = HUD_FindVar(hud, \"circles\");\n\t\thud_gamesummary_ratio = HUD_FindVar(hud, \"ratio\");\n\t\thud_gamesummary_flash = HUD_FindVar(hud, \"flash\");\n\t\thud_gamesummary_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\t// Don't show when not in teamplay/demoplayback.\n\tif (!cls.mvdplayback || !HUD_ShowInDemoplayback(hud_gamesummary_onlytp->value)) {\n\t\tHUD_PrepareDraw(hud, 0, 0, &x, &y);\n\t\treturn;\n\t}\n\n\t// Measure for width\n\ticon_size = 8 * bound(1, hud_gamesummary_ratio->value, 8);\n\twidth = strlen(hud_gamesummary_format->string) * icon_size * hud_gamesummary_scale->value;\n\theight = (icon_size + (hud_gamesummary_style->integer ? 8 : 0)) * hud_gamesummary_scale->value;\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tint text_offset = hud_gamesummary_scale->value * (icon_size / 2 - 4);\n\t\tint icon_offset = 0;\n\n\t\tif (hud_gamesummary_style->integer == 1) {\n\t\t\ttext_offset = icon_size * hud_gamesummary_scale->value;\n\t\t}\n\t\telse if (hud_gamesummary_style->integer == 2) {\n\t\t\ttext_offset = 0;\n\t\t\ticon_offset = 8 * hud_gamesummary_scale->value;\n\t\t}\n\n\t\tfor (format_string = hud_gamesummary_format->string; *format_string; ++format_string) {\n\t\t\tint team = isupper(format_string[0]) ? 0 : 1;\n\t\t\tmpic_t* background_tex = NULL;\n\t\t\tint value = 0;\n\t\t\tfloat alpha = 1.0f;\n\t\t\tfloat last_taken = 0.0f;\n\t\t\tbyte* background_color = NULL;\n\t\t\tbyte mega_color[] = { 0xFF, 0x8C, 0x00 };\n\t\t\tbyte ra_color[] = { 0xFF, 0x00, 0x00 };\n\t\t\tbyte ya_color[] = { 0xFF, 0xFF, 0x00 };\n\t\t\tbyte ga_color[] = { 0x00, 0xBF, 0x00 };\n\t\t\tbyte rl_color[] = { 0xFF, 0x1F, 0x3F };\n\t\t\tbyte lg_color[] = { 0x2F, 0xAA, 0xAA };\n\t\t\tbyte quad_color[] = { 0x00, 0x5F, 0xFF };\n\t\t\tbyte pent_color[] = { 0xFF, 0x00, 0x00 };\n\t\t\tbyte ring_color[] = { 0xFF, 0xFF, 0x00 };\n\n\t\t\tswitch (tolower(format_string[0])) {\n\t\t\t\tcase 'm':\n\t\t\t\t\tbackground_tex = Mod_SimpleTextureForHint(MOD_MEGAHEALTH, 0);\n\t\t\t\t\tvalue = sorted_teams[team].mh_taken;\n\t\t\t\t\tbackground_color = mega_color;\n\t\t\t\t\tlast_taken = sorted_teams[team].mh_lasttime;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'r':\n\t\t\t\t\tbackground_tex = Mod_SimpleTextureForHint(MOD_ARMOR, 2);\n\t\t\t\t\tvalue = sorted_teams[team].ra_taken;\n\t\t\t\t\tbackground_color = ra_color;\n\t\t\t\t\tlast_taken = sorted_teams[team].ra_lasttime;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'y':\n\t\t\t\t\tbackground_tex = Mod_SimpleTextureForHint(MOD_ARMOR, 1);\n\t\t\t\t\tvalue = sorted_teams[team].ya_taken;\n\t\t\t\t\tbackground_color = ya_color;\n\t\t\t\t\tlast_taken = sorted_teams[team].ya_lasttime;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'g':\n\t\t\t\t\tbackground_tex = Mod_SimpleTextureForHint(MOD_ARMOR, 0);\n\t\t\t\t\tvalue = sorted_teams[team].ga_taken;\n\t\t\t\t\tbackground_color = ga_color;\n\t\t\t\t\tlast_taken = sorted_teams[team].ga_lasttime;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'o':\n\t\t\t\t\tbackground_tex = Mod_SimpleTextureForHint(MOD_ROCKETLAUNCHER, 0);\n\t\t\t\t\tvalue = sorted_teams[team].rlcount;\n\t\t\t\t\tbackground_color = rl_color;\n\t\t\t\t\tlast_taken = value ? -1 : 0;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'l':\n\t\t\t\t\tbackground_tex = Mod_SimpleTextureForHint(MOD_LIGHTNINGGUN, 0);\n\t\t\t\t\tvalue = sorted_teams[team].lgcount;\n\t\t\t\t\tbackground_color = lg_color;\n\t\t\t\t\tlast_taken = value ? -1 : 0;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'w':\n\t\t\t\t\tbackground_tex = Mod_SimpleTextureForHint(MOD_ROCKETLAUNCHER, 0);\n\t\t\t\t\tvalue = sorted_teams[team].weapcount;\n\t\t\t\t\tbackground_color = rl_color;\n\t\t\t\t\tlast_taken = value ? -1 : 0;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'q':\n\t\t\t\t\tbackground_tex = Mod_SimpleTextureForHint(MOD_QUAD, 0);\n\t\t\t\t\tvalue = sorted_teams[team].quads_taken;\n\t\t\t\t\tbackground_color = quad_color;\n\t\t\t\t\tlast_taken = sorted_teams[team].q_lasttime;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'p':\n\t\t\t\t\tbackground_tex = Mod_SimpleTextureForHint(MOD_PENT, 0);\n\t\t\t\t\tvalue = sorted_teams[team].pents_taken;\n\t\t\t\t\tbackground_color = pent_color;\n\t\t\t\t\tlast_taken = sorted_teams[team].p_lasttime;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'e':\n\t\t\t\t\tbackground_tex = Mod_SimpleTextureForHint(MOD_RING, 0);\n\t\t\t\t\tvalue = sorted_teams[team].rings_taken;\n\t\t\t\t\tbackground_color = ring_color;\n\t\t\t\t\tlast_taken = sorted_teams[team].r_lasttime;\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\talpha = last_taken == -1 ? 1.0f : 0.3f;\n\t\t\tif (last_taken > 0 && cls.demotime >= last_taken) {\n\t\t\t\tif (hud_gamesummary_flash->integer && cls.demotime - last_taken <= 1.0f) {\n\t\t\t\t\t// Flash for first second\n\t\t\t\t\talpha = (int)((cls.demotime - last_taken) * 10) % 4 >= 2 ? 0.5f : 1.0f;\n\t\t\t\t}\n\t\t\t\telse if (cls.demotime - last_taken <= 2.3f) {\n\t\t\t\t\t// Then solid colour\n\t\t\t\t\talpha = 1.0f;\n\t\t\t\t}\n\t\t\t\telse if (cls.demotime - last_taken <= 3.0f) {\n\t\t\t\t\t// Then fade back to normal\n\t\t\t\t\talpha = bound(0.3f, 1.0f - (cls.demotime - last_taken - 2.3f), 1.0f);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t{\n\t\t\t\tchar buffer[10];\n\n\t\t\t\tsnprintf(buffer, sizeof(buffer), \"%d\", value);\n\n\t\t\t\tif (hud_gamesummary_circles->integer && background_color) {\n\t\t\t\t\tfloat half_width = icon_size * 0.5f * hud_gamesummary_scale->value;\n\n\t\t\t\t\talpha /= 2;\n\n\t\t\t\t\tDraw_AlphaCircleFillRGB(x + half_width, y + icon_offset + half_width, half_width, RGBA_TO_COLOR(background_color[0] * alpha, background_color[1] * alpha, background_color[2] * alpha, alpha * 255));\n\t\t\t\t}\n\t\t\t\telse if (background_tex && R_TextureReferenceIsValid(background_tex->texnum)) {\n\t\t\t\t\tDraw_FitPicAlpha(x, y + icon_offset, icon_size * hud_gamesummary_scale->value, icon_size * hud_gamesummary_scale->value, background_tex, alpha);\n\t\t\t\t}\n\n\t\t\t\tif (value) {\n\t\t\t\t\tDraw_SStringAligned(x, y + text_offset, buffer, hud_gamesummary_scale->value, 1.0f, hud_gamesummary_proportional->integer, text_align_center, x + icon_size * hud_gamesummary_scale->value);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tx += icon_size * hud_gamesummary_scale->value;\n\t\t}\n\t}\n}\n\nstatic void SCR_HUD_DrawScoreMapName(hud_t *hud) {\n\tstatic cvar_t *proportional = NULL, *scale, *style;\n\tint w, h, x, y;\n\tchar str[256];\n\n\tif (proportional == NULL)\n\t{\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t}\n\n\tswitch (style->integer)\n\t{\n\t\tcase 1:\n\t\t\tsnprintf(str, sizeof(str), \"%s\", cl.levelname);\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tsnprintf(str, sizeof(str), \"%s\", host_mapname.string);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tsnprintf(str, sizeof(str), \"%s (%s)\", cl.levelname, host_mapname.string);\n\t\t\tbreak;\n\t}\n\n\tw = Draw_StringLengthColors(str, -1, scale->value, proportional->integer);\n\th = scale->value * 8;\n\n\tif (!HUD_PrepareDraw(hud, w, h, &x, &y))\n\t\treturn;\n\n\tDraw_SString(x, y, str, scale->value, proportional->integer);\n}\n\nvoid GameSummary_HudInit(void)\n{\n\tHUD_Register(\n\t\t\"gamesummary\", NULL, \"Shows total pickups & current weapons.\",\n\t\tHUD_PLUSMINUS, ca_active, 0, SCR_Hud_GameSummary,\n\t\t\"0\", \"top\", \"left\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"onlytp\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"format\", \"RYM myr\",\n\t\t\"circles\", \"0\",\n\t\t\"ratio\", \"4\",\n\t\t\"flash\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\n\t\t\"scoremapname\", NULL, \"Shows map name on the scoreboard\",\n\t\tHUD_NO_DRAW | HUD_ON_INTERMISSION | HUD_ON_SCORES | HUD_ON_FINALE, ca_disconnected, 8, SCR_HUD_DrawScoreMapName,\n\t\t\"0\", \"screen\", \"right\", \"bottom\", \"0\", \"-10\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"proportional\", \"0\",\n\t\t\"scale\", \"1\",\n\t\tNULL\n\t);\n}\n"
  },
  {
    "path": "src/hud_groups.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"rulesets.h\"\n\n// ============================================================================0\n// Groups\n// ============================================================================0\n#define HUD_NUM_GROUPS 9\n#define HUD_GROUP_SCALEMODE_TILE\t\t1\n#define HUD_GROUP_SCALEMODE_STRETCH\t\t2\n#define HUD_GROUP_SCALEMODE_GROW\t\t3\n#define HUD_GROUP_SCALEMODE_CENTER\t\t4\n\n#define HUD_GROUP_PIC_BASEPATH\t\"gfx/%s\"\n\nstatic mpic_t *hud_group_pics[HUD_NUM_GROUPS];\nstatic qbool hud_group_pics_is_loaded[HUD_NUM_GROUPS];\n\n/* Workaround to reload pics on vid_restart\n* since cache (where they are stored) is flushed\n*/\nvoid HUD_Common_Reset_Group_Pics(void)\n{\n\tmemset(hud_group_pics, 0, sizeof(hud_group_pics));\n\tmemset(hud_group_pics_is_loaded, 0, sizeof(hud_group_pics_is_loaded));\n}\n\nstatic void SCR_HUD_DrawGroup(hud_t *hud, int width, int height, mpic_t *pic, int pic_scalemode, float pic_alpha)\n{\n\tint x, y;\n\n\tclamp(width, 1, 99999);\n\tclamp(height, 1, 99999);\n\n\t// Set it to this, because 1.0 will make the colors\n\t// completly saturated, and no semi-transparency will show.\n\tpic_alpha = (pic_alpha) >= 1.0 ? 1 : pic_alpha;\n\n\t// Grow the group if necessary.\n\tif (pic_scalemode == HUD_GROUP_SCALEMODE_GROW\n\t\t&& pic != NULL && pic->height > 0 && pic->width > 0) {\n\t\twidth = max(pic->width, width);\n\t\theight = max(pic->height, height);\n\t}\n\n\tif (!HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\treturn;\n\t}\n\n\t// Draw the picture if it's set.\n\tif (pic != NULL && pic->height > 0) {\n\t\tint pw, ph;\n\n\t\tif (pic_scalemode == HUD_GROUP_SCALEMODE_TILE) {\n\t\t\t// Tile.\n\t\t\tint cx = 0, cy = 0;\n\t\t\twhile (cy < height) {\n\t\t\t\twhile (cx < width) {\n\t\t\t\t\tpw = min(pic->width, width - cx);\n\t\t\t\t\tph = min(pic->height, height - cy);\n\n\t\t\t\t\tif (pw >= pic->width  &&  ph >= pic->height) {\n\t\t\t\t\t\tDraw_AlphaPic(x + cx, y + cy, pic, pic_alpha);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tDraw_AlphaSubPic(x + cx, y + cy, pic, 0, 0, pw, ph, pic_alpha);\n\t\t\t\t\t}\n\n\t\t\t\t\tcx += pic->width;\n\t\t\t\t}\n\n\t\t\t\tcx = 0;\n\t\t\t\tcy += pic->height;\n\t\t\t}\n\t\t}\n\t\telse if (pic_scalemode == HUD_GROUP_SCALEMODE_STRETCH) {\n\t\t\t// Stretch or shrink the picture to fit.\n\t\t\tfloat scale_x = (float)width / pic->width;\n\t\t\tfloat scale_y = (float)height / pic->height;\n\n\t\t\tDraw_SAlphaSubPic2(x, y, pic, 0, 0, pic->width, pic->height, scale_x, scale_y, pic_alpha);\n\t\t}\n\t\telse if (pic_scalemode == HUD_GROUP_SCALEMODE_CENTER) {\n\t\t\t// Center the picture in the group.\n\t\t\tint pic_x = x + (width - pic->width) / 2;\n\t\t\tint pic_y = y + (height - pic->height) / 2;\n\n\t\t\tint src_x = 0;\n\t\t\tint src_y = 0;\n\n\t\t\tif (x > pic_x) {\n\t\t\t\tsrc_x = x - pic_x;\n\t\t\t\tpic_x = x;\n\t\t\t}\n\n\t\t\tif (y > pic_y) {\n\t\t\t\tsrc_y = y - pic_y;\n\t\t\t\tpic_y = y;\n\t\t\t}\n\n\t\t\tDraw_AlphaSubPic(pic_x, pic_y, pic, src_x, src_y, min(width, pic->width), min(height, pic->height), pic_alpha);\n\t\t}\n\t\telse {\n\t\t\t// Normal. Draw in the top left corner.\n\t\t\tDraw_AlphaSubPic(x, y, pic, 0, 0, min(width, pic->width), min(height, pic->height), pic_alpha);\n\t\t}\n\t}\n}\n\nstatic qbool SCR_HUD_LoadGroupPic(cvar_t *var, unsigned int grp_id, char *newpic)\n{\n\tchar pic_path[MAX_PATH];\n\tmpic_t *tmp_pic = NULL;\n\n\tif (newpic && strlen(newpic) > 0) {\n\t\t/* Create path for new pic */\n\t\tsnprintf(pic_path, sizeof(pic_path), HUD_GROUP_PIC_BASEPATH, newpic);\n\n\t\t// Try loading the pic.\n\t\tif (!(tmp_pic = Draw_CachePicSafe(pic_path, false, true))) {\n\t\t\tCom_Printf(\"Couldn't load picture %s for '%s'\\n\", newpic, var->name);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\thud_group_pics[grp_id] = tmp_pic;\n\n\treturn true;\n}\n\nstatic void SCR_HUD_OnChangePic_GroupX(cvar_t *var, char *newpic, qbool *cancel)\n{\n\tint idx;\n\tint res;\n\tres = sscanf(var->name, \"hud_group%d_picture\", &idx);\n\tif (res == 0 || res == EOF) {\n\t\tCom_Printf(\"Failed to parse group number (OnChange func)\\n\");\n\t\t*cancel = true;\n\t}\n\telse {\n\t\tidx--; /* group1 is on index 0 etc */\n\t\t*cancel = Ruleset_BlockHudPicChange() || !SCR_HUD_LoadGroupPic(var, idx, newpic);\n\t}\n}\n\nstatic void SCR_HUD_Groups_Draw(hud_t *hud)\n{\n\t/* FIXME: Perhaps make some nice structs... */\n\tstatic cvar_t *width[HUD_NUM_GROUPS];\n\tstatic cvar_t *height[HUD_NUM_GROUPS];\n\tstatic cvar_t *picture[HUD_NUM_GROUPS];\n\tstatic cvar_t *pic_alpha[HUD_NUM_GROUPS];\n\tstatic cvar_t *pic_scalemode[HUD_NUM_GROUPS];\n\tint res;\n\tint idx;\n\n\tres = sscanf(hud->name, \"group%d\", &idx);\n\tif (res == 0 || res == EOF) {\n\t\tCom_Printf(\"Failed to parse group number from \\\"%s\\\"\\n\", hud->name);\n\t\treturn;\n\t}\n\tidx--;\n\n\t/* First init */\n\tif (width[idx] == NULL) { // first time called\n\t\twidth[idx] = HUD_FindVar(hud, \"width\");\n\t\theight[idx] = HUD_FindVar(hud, \"height\");\n\t\tpicture[idx] = HUD_FindVar(hud, \"picture\");\n\t\tpic_alpha[idx] = HUD_FindVar(hud, \"pic_alpha\");\n\t\tpic_scalemode[idx] = HUD_FindVar(hud, \"pic_scalemode\");\n\n\t\tpicture[idx]->OnChange = SCR_HUD_OnChangePic_GroupX;\n\t}\n\n\tif (!hud_group_pics_is_loaded[idx]) {\n\t\tSCR_HUD_LoadGroupPic(picture[idx], idx, picture[idx]->string);\n\t\thud_group_pics_is_loaded[idx] = true;\n\t}\n\n\tSCR_HUD_DrawGroup(hud, width[idx]->value, height[idx]->value, hud_group_pics[idx], pic_scalemode[idx]->value, pic_alpha[idx]->value);\n}\n\nvoid Groups_HudInit(void)\n{\n\t// groups\n\tHUD_Register(\n\t\t\"group1\", NULL, \"Group element.\",\n\t\tHUD_NO_GROW, ca_disconnected, 0, SCR_HUD_Groups_Draw,\n\t\t\"0\", \"screen\", \"left\", \"top\", \"0\", \"0\", \".5\", \"0 0 0\", NULL,\n\t\t\"name\", \"group1\",\n\t\t\"width\", \"64\",\n\t\t\"height\", \"64\",\n\t\t\"picture\", \"\",\n\t\t\"pic_alpha\", \"1.0\",\n\t\t\"pic_scalemode\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"group2\", NULL, \"Group element.\",\n\t\tHUD_NO_GROW, ca_disconnected, 0, SCR_HUD_Groups_Draw,\n\t\t\"0\", \"screen\", \"center\", \"top\", \"0\", \"0\", \".5\", \"0 0 0\", NULL,\n\t\t\"name\", \"group2\",\n\t\t\"width\", \"64\",\n\t\t\"height\", \"64\",\n\t\t\"picture\", \"\",\n\t\t\"pic_alpha\", \"1.0\",\n\t\t\"pic_scalemode\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"group3\", NULL, \"Group element.\",\n\t\tHUD_NO_GROW, ca_disconnected, 0, SCR_HUD_Groups_Draw,\n\t\t\"0\", \"screen\", \"right\", \"top\", \"0\", \"0\", \".5\", \"0 0 0\", NULL,\n\t\t\"name\", \"group3\",\n\t\t\"width\", \"64\",\n\t\t\"height\", \"64\",\n\t\t\"picture\", \"\",\n\t\t\"pic_alpha\", \"1.0\",\n\t\t\"pic_scalemode\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"group4\", NULL, \"Group element.\",\n\t\tHUD_NO_GROW, ca_disconnected, 0, SCR_HUD_Groups_Draw,\n\t\t\"0\", \"screen\", \"left\", \"center\", \"0\", \"0\", \".5\", \"0 0 0\", NULL,\n\t\t\"name\", \"group4\",\n\t\t\"width\", \"64\",\n\t\t\"height\", \"64\",\n\t\t\"picture\", \"\",\n\t\t\"pic_alpha\", \"1.0\",\n\t\t\"pic_scalemode\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"group5\", NULL, \"Group element.\",\n\t\tHUD_NO_GROW, ca_disconnected, 0, SCR_HUD_Groups_Draw,\n\t\t\"0\", \"screen\", \"center\", \"center\", \"0\", \"0\", \".5\", \"0 0 0\", NULL,\n\t\t\"name\", \"group5\",\n\t\t\"width\", \"64\",\n\t\t\"height\", \"64\",\n\t\t\"picture\", \"\",\n\t\t\"pic_alpha\", \"1.0\",\n\t\t\"pic_scalemode\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"group6\", NULL, \"Group element.\",\n\t\tHUD_NO_GROW, ca_disconnected, 0, SCR_HUD_Groups_Draw,\n\t\t\"0\", \"screen\", \"right\", \"center\", \"0\", \"0\", \".5\", \"0 0 0\", NULL,\n\t\t\"name\", \"group6\",\n\t\t\"width\", \"64\",\n\t\t\"height\", \"64\",\n\t\t\"picture\", \"\",\n\t\t\"pic_alpha\", \"1.0\",\n\t\t\"pic_scalemode\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"group7\", NULL, \"Group element.\",\n\t\tHUD_NO_GROW, ca_disconnected, 0, SCR_HUD_Groups_Draw,\n\t\t\"0\", \"screen\", \"left\", \"bottom\", \"0\", \"0\", \".5\", \"0 0 0\", NULL,\n\t\t\"name\", \"group7\",\n\t\t\"width\", \"64\",\n\t\t\"height\", \"64\",\n\t\t\"picture\", \"\",\n\t\t\"pic_alpha\", \"1.0\",\n\t\t\"pic_scalemode\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"group8\", NULL, \"Group element.\",\n\t\tHUD_NO_GROW, ca_disconnected, 0, SCR_HUD_Groups_Draw,\n\t\t\"0\", \"screen\", \"center\", \"bottom\", \"0\", \"0\", \".5\", \"0 0 0\", NULL,\n\t\t\"name\", \"group8\",\n\t\t\"width\", \"64\",\n\t\t\"height\", \"64\",\n\t\t\"picture\", \"\",\n\t\t\"pic_alpha\", \"1.0\",\n\t\t\"pic_scalemode\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"group9\", NULL, \"Group element.\",\n\t\tHUD_NO_GROW, ca_disconnected, 0, SCR_HUD_Groups_Draw,\n\t\t\"0\", \"screen\", \"right\", \"bottom\", \"0\", \"0\", \".5\", \"0 0 0\", NULL,\n\t\t\"name\", \"group9\",\n\t\t\"width\", \"64\",\n\t\t\"height\", \"64\",\n\t\t\"picture\", \"\",\n\t\t\"pic_alpha\", \"1.0\",\n\t\t\"pic_scalemode\", \"0\",\n\t\tNULL\n\t);\n}\n"
  },
  {
    "path": "src/hud_guns.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"fonts.h\"\n#include \"teamplay.h\"\n#include \"utils.h\"\n\n// -----------\n// gunz\n//\nstatic void SCR_HUD_DrawGunByNum(hud_t *hud, int num, float scale, int style, int wide, qbool proportional)\n{\n\textern mpic_t *sb_weapons[7][8];  // sbar.c\n\tint i = num - 2;\n\tint width, height;\n\tint x, y;\n\tchar *tmp;\n\n\tscale = max(scale, 0.01);\n\n\tswitch (style) {\n\t\tcase 3: // opposite colors of case 1\n\t\tcase 1:     // text, gold inactive, white active\n\t\t\twidth = FontFixedWidth(2, scale, false, proportional);\n\t\t\theight = 8 * scale;\n\t\t\tif (!HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (HUD_Stats(STAT_ITEMS) & (IT_SHOTGUN << i)) {\n\t\t\t\tqbool weapon_active = (HUD_Stats(STAT_ACTIVEWEAPON) == (IT_SHOTGUN << i));\n\n\t\t\t\tswitch (num) {\n\t\t\t\t\tcase 2: tmp = \"sg\"; break;\n\t\t\t\t\tcase 3: tmp = \"bs\"; break;\n\t\t\t\t\tcase 4: tmp = \"ng\"; break;\n\t\t\t\t\tcase 5: tmp = \"sn\"; break;\n\t\t\t\t\tcase 6: tmp = \"gl\"; break;\n\t\t\t\t\tcase 7: tmp = \"rl\"; break;\n\t\t\t\t\tcase 8: tmp = \"lg\"; break;\n\t\t\t\t\tdefault: tmp = \"\";\n\t\t\t\t}\n\n\t\t\t\tif ((weapon_active && style == 1) || (!weapon_active && style == 3)) {\n\t\t\t\t\tDraw_SString(x, y, tmp, scale, proportional);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tDraw_SAlt_String(x, y, tmp, scale, proportional);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 4: // opposite colors of case 2\n\t\tcase 2: // numbers, gold inactive, white active\n\t\t\twidth = FontFixedWidth(1, scale, true, proportional);;\n\t\t\theight = 8 * scale;\n\t\t\tif (!HUD_PrepareDraw(hud, width, height, &x, &y))\n\t\t\t\treturn;\n\t\t\tif (HUD_Stats(STAT_ITEMS) & (IT_SHOTGUN << i)) {\n\t\t\t\tif (HUD_Stats(STAT_ACTIVEWEAPON) == (IT_SHOTGUN << i)) {\n\t\t\t\t\tnum += '0' + (style == 4 ? 128 : 0);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tnum += '0' + (style == 4 ? 0 : 128);\n\t\t\t\t}\n\t\t\t\tDraw_SCharacterP(x, y, num, scale, proportional);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 5: // COLOR active, gold inactive\n\t\tcase 7: // COLOR active, white inactive\n\t\tcase 6: // white active, COLOR inactive\n\t\tcase 8: // gold active, COLOR inactive\n\t\t\twidth = FontFixedWidth(2, scale, false, proportional);\n\t\t\theight = 8 * scale;\n\n\t\t\tif (!HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (HUD_Stats(STAT_ITEMS) & (IT_SHOTGUN << i)) {\n\t\t\t\tif (HUD_Stats(STAT_ACTIVEWEAPON) == (IT_SHOTGUN << i)) {\n\t\t\t\t\tif ((style == 5) || (style == 7)) { // strip {}\n\t\t\t\t\t\tchar *weap_str = TP_ItemName((IT_SHOTGUN << i));\n\t\t\t\t\t\tchar weap_white_stripped[32];\n\t\t\t\t\t\tUtil_SkipChars(weap_str, \"{}\", weap_white_stripped, 32);\n\t\t\t\t\t\tDraw_SString(x, y, weap_white_stripped, scale, false);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\t//Strip both &cRGB and {}\n\t\t\t\t\t\tchar inactive_weapon_buf[16];\n\t\t\t\t\t\tchar inactive_weapon_buf_nowhite[16];\n\t\t\t\t\t\tUtil_SkipEZColors(inactive_weapon_buf, TP_ItemName(IT_SHOTGUN << i), sizeof(inactive_weapon_buf));\n\t\t\t\t\t\tUtil_SkipChars(inactive_weapon_buf, \"{}\", inactive_weapon_buf_nowhite, sizeof(inactive_weapon_buf_nowhite));\n\n\t\t\t\t\t\tif (style == 8) {\n\t\t\t\t\t\t\t// gold active\n\t\t\t\t\t\t\tDraw_SAlt_String(x, y, inactive_weapon_buf_nowhite, scale, proportional);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if (style == 6) {\n\t\t\t\t\t\t\t// white active\n\t\t\t\t\t\t\tDraw_SString(x, y, inactive_weapon_buf_nowhite, scale, proportional);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif ((style == 5) || (style == 7)) { //Strip both &cRGB and {}\n\t\t\t\t\t\tchar inactive_weapon_buf[16];\n\t\t\t\t\t\tchar inactive_weapon_buf_nowhite[16];\n\t\t\t\t\t\tUtil_SkipEZColors(inactive_weapon_buf, TP_ItemName(IT_SHOTGUN << i), sizeof(inactive_weapon_buf));\n\t\t\t\t\t\tUtil_SkipChars(inactive_weapon_buf, \"{}\", inactive_weapon_buf_nowhite, sizeof(inactive_weapon_buf_nowhite));\n\n\t\t\t\t\t\tif (style == 5) {\n\t\t\t\t\t\t\t// gold inactive\n\t\t\t\t\t\t\tDraw_SAlt_String(x, y, inactive_weapon_buf_nowhite, scale, proportional);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if (style == 7) {\n\t\t\t\t\t\t\t// white inactive\n\t\t\t\t\t\t\tDraw_SString(x, y, inactive_weapon_buf_nowhite, scale, proportional);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse if ((style == 6) || (style == 8)) { // strip only {}\n\t\t\t\t\t\tchar *weap_str = TP_ItemName((IT_SHOTGUN << i));\n\t\t\t\t\t\tchar weap_white_stripped[32];\n\t\t\t\t\t\tUtil_SkipChars(weap_str, \"{}\", weap_white_stripped, 32);\n\t\t\t\t\t\tDraw_SString(x, y, weap_white_stripped, scale, proportional);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:    // classic - pictures\n\t\t\twidth = scale * (wide ? 48 : 24);\n\t\t\theight = scale * 16;\n\n\t\t\tif (!HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (HUD_Stats(STAT_ITEMS) & (IT_SHOTGUN << i)) {\n\t\t\t\tfloat   time;\n\t\t\t\tint     flashon;\n\n\t\t\t\ttime = cl.item_gettime[i];\n\t\t\t\tflashon = (int)((cl.time - time) * 10);\n\t\t\t\tif (flashon < 0) {\n\t\t\t\t\tflashon = 0;\n\t\t\t\t}\n\t\t\t\tif (flashon >= 10) {\n\t\t\t\t\tflashon = (HUD_Stats(STAT_ACTIVEWEAPON) == (IT_SHOTGUN << i)) ? 1 : 0;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tflashon = (flashon % 5) + 2;\n\t\t\t\t}\n\n\t\t\t\tif (wide || num != 8) {\n\t\t\t\t\tDraw_SPic(x, y, sb_weapons[flashon][i], scale);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tDraw_SSubPic(x, y, sb_weapons[flashon][i], 0, 0, 24, 16, scale);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t}\n}\n\nstatic void SCR_HUD_DrawGun2(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional, *frame_hide;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawGunByNum(hud, 2, scale->value, style->value, 0, proportional->integer);\n\t}\n\n\tframe_hide = HUD_FindVar(hud, \"frame_hide\");\n\thud->frame_hide = frame_hide->value && !(HUD_Stats(STAT_ITEMS) & IT_SHOTGUN)\n\t\t? true\n\t\t: false;\n}\n\nstatic void SCR_HUD_DrawGun3(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional, *frame_hide;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawGunByNum(hud, 3, scale->value, style->value, 0, proportional->integer);\n\t}\n\n\tframe_hide = HUD_FindVar(hud, \"frame_hide\");\n\thud->frame_hide = frame_hide->value && !(HUD_Stats(STAT_ITEMS) & IT_SUPER_SHOTGUN)\n\t\t? true\n\t\t: false;\n}\n\nstatic void SCR_HUD_DrawGun4(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional, *frame_hide;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawGunByNum(hud, 4, scale->value, style->value, 0, proportional->integer);\n\t}\n\n\tframe_hide = HUD_FindVar(hud, \"frame_hide\");\n\thud->frame_hide = frame_hide->value && !(HUD_Stats(STAT_ITEMS) & IT_NAILGUN)\n\t\t? true\n\t\t: false;\n}\n\nstatic void SCR_HUD_DrawGun5(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional, *frame_hide;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawGunByNum(hud, 5, scale->value, style->value, 0, proportional->integer);\n\t}\n\n\tframe_hide = HUD_FindVar(hud, \"frame_hide\");\n\thud->frame_hide = frame_hide->value && !(HUD_Stats(STAT_ITEMS) & IT_SUPER_NAILGUN)\n\t\t? true\n\t\t: false;\n}\n\nstatic void SCR_HUD_DrawGun6(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional, *frame_hide;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawGunByNum(hud, 6, scale->value, style->value, 0, proportional->integer);\n\t}\n\n\tframe_hide = HUD_FindVar(hud, \"frame_hide\");\n\thud->frame_hide = frame_hide->value && !(HUD_Stats(STAT_ITEMS) & IT_GRENADE_LAUNCHER)\n\t\t? true\n\t\t: false;\n}\n\nstatic void SCR_HUD_DrawGun7(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional, *frame_hide;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawGunByNum(hud, 7, scale->value, style->value, 0, proportional->integer);\n\t}\n\n\tframe_hide = HUD_FindVar(hud, \"frame_hide\");\n\thud->frame_hide = frame_hide->value && !(HUD_Stats(STAT_ITEMS) & IT_ROCKET_LAUNCHER)\n\t\t? true\n\t\t: false;\n}\n\nstatic void SCR_HUD_DrawGun8(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *wide, *proportional, *frame_hide;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\twide = HUD_FindVar(hud, \"wide\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawGunByNum(hud, 8, scale->value, style->value, wide->value, proportional->integer);\n\t}\n\n\tframe_hide = HUD_FindVar(hud, \"frame_hide\");\n\thud->frame_hide = frame_hide->value && !(HUD_Stats(STAT_ITEMS) & IT_LIGHTNING)\n\t\t? true\n\t\t: false;\n}\n\nstatic void SCR_HUD_DrawGunCurrent(hud_t *hud)\n{\n\tint gun;\n\tstatic cvar_t *scale = NULL, *style, *wide, *proportional;\n\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\twide = HUD_FindVar(hud, \"wide\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tif (ShowPreselectedWeap()) {\n\t\t// using weapon pre-selection so show info for current best pre-selected weapon\n\t\tgun = IN_BestWeapon(true);\n\t\tif (gun < 2) {\n\t\t\treturn;\n\t\t}\n\t}\n\telse {\n\t\t// not using weapon pre-selection or player is dead so show current selected weapon\n\t\tswitch (HUD_Stats(STAT_ACTIVEWEAPON)) {\n\t\t\tcase IT_SHOTGUN << 0:   gun = 2; break;\n\t\t\tcase IT_SHOTGUN << 1:   gun = 3; break;\n\t\t\tcase IT_SHOTGUN << 2:   gun = 4; break;\n\t\t\tcase IT_SHOTGUN << 3:   gun = 5; break;\n\t\t\tcase IT_SHOTGUN << 4:   gun = 6; break;\n\t\t\tcase IT_SHOTGUN << 5:   gun = 7; break;\n\t\t\tcase IT_SHOTGUN << 6:   gun = 8; break;\n\t\t\tdefault: return;\n\t\t}\n\t}\n\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawGunByNum(hud, gun, scale->value, style->value, wide->value, proportional->integer);\n\t}\n}\n\nvoid Guns_HudInit(void)\n{\n\t// init guns\n\tHUD_Register(\"gun\", NULL, \"Part of your inventory - current weapon.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawGunCurrent,\n\t\t\"0\", \"ibar\", \"center\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"wide\", \"0\",\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\t\"frame_hide\", \"0\",\n\t\tNULL);\n\tHUD_Register(\"gun2\", NULL, \"Part of your inventory - shotgun.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawGun2,\n\t\t\"1\", \"ibar\", \"left\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\t\"frame_hide\", \"0\",\n\t\tNULL);\n\tHUD_Register(\"gun3\", NULL, \"Part of your inventory - super shotgun.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawGun3,\n\t\t\"1\", \"gun2\", \"after\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\t\"frame_hide\", \"0\",\n\t\tNULL);\n\tHUD_Register(\"gun4\", NULL, \"Part of your inventory - nailgun.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawGun4,\n\t\t\"1\", \"gun3\", \"after\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\t\"frame_hide\", \"0\",\n\t\tNULL);\n\tHUD_Register(\"gun5\", NULL, \"Part of your inventory - super nailgun.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawGun5,\n\t\t\"1\", \"gun4\", \"after\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\t\"frame_hide\", \"0\",\n\t\tNULL);\n\tHUD_Register(\"gun6\", NULL, \"Part of your inventory - grenade launcher.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawGun6,\n\t\t\"1\", \"gun5\", \"after\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\t\"frame_hide\", \"0\",\n\t\tNULL);\n\tHUD_Register(\"gun7\", NULL, \"Part of your inventory - rocket launcher.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawGun7,\n\t\t\"1\", \"gun6\", \"after\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\t\"frame_hide\", \"0\",\n\t\tNULL);\n\tHUD_Register(\"gun8\", NULL, \"Part of your inventory - thunderbolt.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawGun8,\n\t\t\"1\", \"gun7\", \"after\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"wide\", \"0\",\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\t\"frame_hide\", \"0\",\n\t\tNULL);\n}\n"
  },
  {
    "path": "src/hud_health.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"vx_stuff.h\"\n\nstatic int TP_IsHealthLow(void)\n{\n\textern cvar_t tp_need_health;\n\n\treturn cl.stats[STAT_HEALTH] <= tp_need_health.value;\n}\n\nstatic qbool HUD_HealthLow(void)\n{\n\textern cvar_t hud_tp_need;\n\n\tif (hud_tp_need.value) {\n\t\treturn TP_IsHealthLow();\n\t}\n\telse {\n\t\treturn HUD_Stats(STAT_HEALTH) <= 25;\n\t}\n}\n\nstatic void SCR_HUD_DrawHealth(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *digits, *align, *proportional;\n\tstatic int value;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tdigits = HUD_FindVar(hud, \"digits\");\n\t\talign = HUD_FindVar(hud, \"align\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tvalue = HUD_Stats(STAT_HEALTH);\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawNum(hud, (value < 0 ? 0 : value), HUD_HealthLow(), scale->value, style->value, digits->value, align->string, proportional->integer);\n\t}\n}\n\nstatic void SCR_HUD_DrawHealthDamage(hud_t *hud)\n{\n\tDraw_AMFStatLoss(STAT_HEALTH, hud);\n\n\tif (HUD_Stats(STAT_HEALTH) <= 0) {\n\t\tAmf_Reset_DamageStats();\n\t}\n}\n\nstatic void SCR_HUD_DrawBarHealth(hud_t *hud)\n{\n\tstatic\tcvar_t *width = NULL, *height, *direction, *color_nohealth, *color_normal, *color_mega, *color_twomega, *color_unnatural;\n\tint\t\tx, y;\n\tint\t\thealth = cl.stats[STAT_HEALTH];\n\n\tif (width == NULL) {\n\t\t// first time called\n\t\twidth = HUD_FindVar(hud, \"width\");\n\t\theight = HUD_FindVar(hud, \"height\");\n\t\tdirection = HUD_FindVar(hud, \"direction\");\n\t\tcolor_nohealth = HUD_FindVar(hud, \"color_nohealth\");\n\t\tcolor_normal = HUD_FindVar(hud, \"color_normal\");\n\t\tcolor_mega = HUD_FindVar(hud, \"color_mega\");\n\t\tcolor_twomega = HUD_FindVar(hud, \"color_twomega\");\n\t\tcolor_unnatural = HUD_FindVar(hud, \"color_unnatural\");\n\t}\n\n\tif (HUD_PrepareDraw(hud, width->integer, height->integer, &x, &y) && (cl.spectator == cl.autocam)) {\n\t\tif (!width->integer || !height->integer) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (health > 250) {\n\t\t\tSCR_HUD_DrawBar(direction->integer, 100, 100.0, color_unnatural->color, x, y, width->integer, height->integer);\n\t\t}\n\t\telse if (health > 200) {\n\t\t\tSCR_HUD_DrawBar(direction->integer, 100, 100.0, color_normal->color, x, y, width->integer, height->integer);\n\t\t\tSCR_HUD_DrawBar(direction->integer, 100, 100.0, color_mega->color, x, y, width->integer, height->integer);\n\t\t\tSCR_HUD_DrawBar(direction->integer, health - 200, 100.0, color_twomega->color, x, y, width->integer, height->integer);\n\t\t}\n\t\telse if (health > 100) {\n\t\t\tSCR_HUD_DrawBar(direction->integer, 100, 100.0, color_normal->color, x, y, width->integer, height->integer);\n\t\t\tSCR_HUD_DrawBar(direction->integer, health - 100, 100.0, color_mega->color, x, y, width->integer, height->integer);\n\t\t}\n\t\telse if (health > 0) {\n\t\t\tSCR_HUD_DrawBar(direction->integer, 100, 100.0, color_nohealth->color, x, y, width->integer, height->integer);\n\t\t\tSCR_HUD_DrawBar(direction->integer, health, 100.0, color_normal->color, x, y, width->integer, height->integer);\n\t\t}\n\t\telse {\n\t\t\tSCR_HUD_DrawBar(direction->integer, 100, 100.0, color_nohealth->color, x, y, width->integer, height->integer);\n\t\t}\n\t}\n}\n\nvoid Health_HudInit(void)\n{\n\tHUD_Register(\n\t\t\"bar_health\", NULL, \"Health bar.\",\n\t\tHUD_PLUSMINUS, ca_active, 0, SCR_HUD_DrawBarHealth,\n\t\t\"0\", \"health\", \"right\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"height\", \"16\",\n\t\t\"width\", \"64\",\n\t\t\"direction\", \"0\",\n\t\t\"color_nohealth\", \"128 128 128 64\",\n\t\t\"color_normal\", \"32 64 128 128\",\n\t\t\"color_mega\", \"64 96 128 128\",\n\t\t\"color_twomega\", \"128 128 255 128\",\n\t\t\"color_unnatural\", \"255 255 255 128\",\n\t\tNULL\n\t);\n\n\t// health\n\tHUD_Register(\n\t\t\"health\", NULL, \"Part of your status - health level.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawHealth,\n\t\t\"1\", \"face\", \"after\", \"center\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"align\", \"right\",\n\t\t\"digits\", \"3\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\t// healthdamage\n\tHUD_Register(\n\t\t\"healthdamage\", NULL, \"Shows amount of damage done to your health.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawHealthDamage,\n\t\t\"0\", \"health\", \"left\", \"before\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"align\", \"right\",\n\t\t\"digits\", \"3\",\n\t\t\"duration\", \"0.8\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n}\n"
  },
  {
    "path": "src/hud_items.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"fonts.h\"\n\n// ----------------\n// powerzz\n//\nstatic void SCR_HUD_DrawPowerup(hud_t *hud, int num, float scale, int style, qbool proportional)\n{\n\textern mpic_t *sb_items[32];\n\tint    x, y, width, height;\n\tint    c;\n\n\tscale = max(scale, 0.01);\n\n\tif (cl.spectator == cl.autocam) {\n\t\tswitch (style) {\n\t\tcase 1:     // letter\n\t\t\twidth = FontFixedWidth(1, scale, false, proportional);\n\t\t\theight = 8 * scale;\n\t\t\tif (!HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (HUD_Stats(STAT_ITEMS) & (1 << (17 + num))) {\n\t\t\t\tswitch (num) {\n\t\t\t\t\tcase 0: c = '1'; break;\n\t\t\t\t\tcase 1: c = '2'; break;\n\t\t\t\t\tcase 2: c = 'r'; break;\n\t\t\t\t\tcase 3: c = 'p'; break;\n\t\t\t\t\tcase 4: c = 's'; break;\n\t\t\t\t\tcase 5: c = 'q'; break;\n\t\t\t\t\tdefault: c = '?';\n\t\t\t\t}\n\t\t\t\tDraw_SCharacterP(x, y, c, scale, proportional);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:    // classic - pics\n\t\t\twidth = height = scale * 16;\n\t\t\tif (!HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (HUD_Stats(STAT_ITEMS) & (1 << (17 + num))) {\n\t\t\t\tDraw_SPic(x, y, sb_items[num], scale);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic void SCR_HUD_DrawKey1(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawPowerup(hud, 0, scale->value, style->value, proportional->integer);\n\t}\n}\n\nstatic void SCR_HUD_DrawKey2(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawPowerup(hud, 1, scale->value, style->value, proportional->integer);\n\t}\n}\n\nstatic void SCR_HUD_DrawRing(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawPowerup(hud, 2, scale->value, style->value, proportional->integer);\n\t}\n}\n\nstatic void SCR_HUD_DrawPent(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawPowerup(hud, 3, scale->value, style->value, proportional->integer);\n\t}\n}\n\nstatic void SCR_HUD_DrawSuit(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawPowerup(hud, 4, scale->value, style->value, proportional->integer);\n\t}\n}\n\nstatic void SCR_HUD_DrawQuad(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawPowerup(hud, 5, scale->value, style->value, proportional->integer);\n\t}\n}\n\n// -----------\n// sigils\n//\nstatic void SCR_HUD_DrawSigil(hud_t *hud, int num, float scale, int style, qbool proportional)\n{\n\textern mpic_t *sb_sigil[4];\n\tint     x, y;\n\n\tscale = max(scale, 0.01);\n\tif (cl.spectator == cl.autocam) {\n\t\tswitch (style) {\n\t\tcase 1:     // sigil number\n\t\t\tif (!HUD_PrepareDraw(hud, 8 * scale, 8 * scale, &x, &y)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (HUD_Stats(STAT_ITEMS) & (1 << (28 + num))) {\n\t\t\t\tDraw_SCharacterP(x, y, num + '0' + 1, scale, proportional);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:    // classic - picture\n\t\t\tif (!HUD_PrepareDraw(hud, 8 * scale, 16 * scale, &x, &y)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (HUD_Stats(STAT_ITEMS) & (1 << (28 + num))) {\n\t\t\t\tDraw_SPic(x, y, sb_sigil[num], scale);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic void SCR_HUD_DrawSigil1(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawSigil(hud, 0, scale->value, style->value, proportional->integer);\n\t}\n}\n\nvoid SCR_HUD_DrawSigil2(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawSigil(hud, 1, scale->value, style->value, proportional->integer);\n\t}\n}\n\nstatic void SCR_HUD_DrawSigil3(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawSigil(hud, 2, scale->value, style->value, proportional->integer);\n\t}\n}\n\nstatic void SCR_HUD_DrawSigil4(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *proportional;\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\tif (cl.spectator == cl.autocam) {\n\t\tSCR_HUD_DrawSigil(hud, 3, scale->value, style->value, proportional->integer);\n\t}\n}\n\nvoid Items_HudInit(void)\n{\n\t// init powerzz\n\tHUD_Register(\n\t\t\"key1\", NULL, \"Part of your inventory - silver key.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawKey1,\n\t\t\"1\", \"ibar\", \"top\", \"left\", \"0\", \"64\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"key2\", NULL, \"Part of your inventory - gold key.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawKey2,\n\t\t\"1\", \"key1\", \"left\", \"after\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"ring\", NULL, \"Part of your inventory - invisibility.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawRing,\n\t\t\"1\", \"key2\", \"left\", \"after\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"pent\", NULL, \"Part of your inventory - invulnerability.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawPent,\n\t\t\"1\", \"ring\", \"left\", \"after\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"suit\", NULL, \"Part of your inventory - biosuit.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawSuit,\n\t\t\"1\", \"pent\", \"left\", \"after\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"quad\", NULL, \"Part of your inventory - quad damage.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawQuad,\n\t\t\"1\", \"suit\", \"left\", \"after\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\t// sigilzz\n\tHUD_Register(\n\t\t\"sigil1\", NULL, \"Part of your inventory - sigil 1.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawSigil1,\n\t\t\"0\", \"ibar\", \"left\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"sigil2\", NULL, \"Part of your inventory - sigil 2.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawSigil2,\n\t\t\"0\", \"sigil1\", \"after\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"sigil3\", NULL, \"Part of your inventory - sigil 3.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawSigil3,\n\t\t\"0\", \"sigil2\", \"after\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\tHUD_Register(\n\t\t\"sigil4\", NULL, \"Part of your inventory - sigil 4.\",\n\t\tHUD_INVENTORY, ca_active, 0, SCR_HUD_DrawSigil4,\n\t\t\"0\", \"sigil3\", \"after\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n}\n"
  },
  {
    "path": "src/hud_net.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"fonts.h\"\n\n//---------------------\n//\n// network statistics\n//\nvoid SCR_HUD_DrawNetStats(hud_t *hud)\n{\n\tint width, height;\n\tint x, y;\n\n\tstatic cvar_t *hud_net_period = NULL;\n\tstatic cvar_t *hud_net_proportional = NULL;\n\tstatic cvar_t *hud_net_scale = NULL;\n\n\tif (hud_net_period == NULL) {\n\t\t// first time\n\t\thud_net_period = HUD_FindVar(hud, \"period\");\n\t\thud_net_proportional = HUD_FindVar(hud, \"proportional\");\n\t\thud_net_scale = HUD_FindVar(hud, \"scale\");\n\t}\n\n\twidth = FontFixedWidth(16, hud_net_scale->value, false, hud_net_proportional->integer);\n\theight = (12 + 8 + 8 + 8 + 8 + 16 + 8 + 8 + 8 + 8 + 16 + 8 + 8 + 8) * hud_net_scale->value;\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tfloat period = hud_net_period->value;\n\t\tchar line[128];\n\t\tdouble t;\n\n\t\t// static data\n\t\tstatic double last_calculated;\n\t\tstatic int    ping_min, ping_max, ping_avg;\n\t\tstatic float  ping_dev;\n\t\tstatic float  f_min, f_max, f_avg;\n\t\tstatic int    lost_lost, lost_delta, lost_rate, lost_total;\n\t\tstatic int    size_all, size_in, size_out;\n\t\tstatic int    bandwidth_all, bandwidth_in, bandwidth_out;\n\t\tstatic int    with_delta;\n\n\t\tif (cls.state != ca_active) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (period < 0) {\n\t\t\tperiod = 0;\n\t\t}\n\n\t\tt = curtime;\n\t\tif (t - last_calculated > period) {\n\t\t\t// recalculate\n\t\t\tnet_stat_result_t result;\n\n\t\t\tlast_calculated = t;\n\n\t\t\tCL_CalcNetStatistics(\n\t\t\t\tperiod,             // period of time\n\t\t\t\tnetwork_stats,      // samples table\n\t\t\t\tNETWORK_STATS_SIZE, // number of samples in table\n\t\t\t\t&result             // results\n\t\t\t);\n\n\t\t\tif (result.samples == 0) {\n\t\t\t\treturn; // error calculating net\n\t\t\t}\n\n\t\t\tping_avg = (int)(result.ping_avg + 0.5);\n\t\t\tping_min = (int)(result.ping_min + 0.5);\n\t\t\tping_max = (int)(result.ping_max + 0.5);\n\t\t\tping_dev = result.ping_dev;\n\n\t\t\tclamp(ping_avg, 0, 999);\n\t\t\tclamp(ping_min, 0, 999);\n\t\t\tclamp(ping_max, 0, 999);\n\t\t\tclamp(ping_dev, 0, 99.9);\n\n\t\t\tf_avg = (int)(result.ping_f_avg + 0.5);\n\t\t\tf_min = (int)(result.ping_f_min + 0.5);\n\t\t\tf_max = (int)(result.ping_f_max + 0.5);\n\n\t\t\tclamp(f_avg, 0, 99.9);\n\t\t\tclamp(f_min, 0, 99.9);\n\t\t\tclamp(f_max, 0, 99.9);\n\n\t\t\tlost_lost = (int)(result.lost_lost + 0.5);\n\t\t\tlost_rate = (int)(result.lost_rate + 0.5);\n\t\t\tlost_delta = (int)(result.lost_delta + 0.5);\n\t\t\tlost_total = (int)(result.lost_lost + result.lost_rate + result.lost_delta + 0.5);\n\n\t\t\tclamp(lost_lost, 0, 100);\n\t\t\tclamp(lost_rate, 0, 100);\n\t\t\tclamp(lost_delta, 0, 100);\n\t\t\tclamp(lost_total, 0, 100);\n\n\t\t\tsize_in = (int)(result.size_in + 0.5);\n\t\t\tsize_out = (int)(result.size_out + 0.5);\n\t\t\tsize_all = (int)(result.size_in + result.size_out + 0.5);\n\n\t\t\tbandwidth_in = (int)(result.bandwidth_in + 0.5);\n\t\t\tbandwidth_out = (int)(result.bandwidth_out + 0.5);\n\t\t\tbandwidth_all = (int)(result.bandwidth_in + result.bandwidth_out + 0.5);\n\n\t\t\tclamp(size_in, 0, 999);\n\t\t\tclamp(size_out, 0, 999);\n\t\t\tclamp(size_all, 0, 999);\n\t\t\tclamp(bandwidth_in, 0, 99999);\n\t\t\tclamp(bandwidth_out, 0, 99999);\n\t\t\tclamp(bandwidth_all, 0, 99999);\n\n\t\t\twith_delta = result.delta;\n\t\t}\n\n\t\tDraw_Alt_String(x + (width - Draw_StringLength(\"latency\", 7, hud_net_scale->value, hud_net_proportional->integer)) / 2, y, \"latency\", hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 12 * hud_net_scale->value;\n\n\t\tDraw_SString(x, y, \"min\", hud_net_scale->value, hud_net_proportional->integer);\n\t\tsnprintf(line, sizeof(line), \"%4.1f %3d ms\", f_min, ping_min);\n\t\tDraw_SString(x + width - Draw_StringLength(line, -1, hud_net_scale->value, hud_net_proportional->integer), y, line, hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 8 * hud_net_scale->value;\n\n\t\tDraw_SString(x, y, \"avg\", hud_net_scale->value, hud_net_proportional->integer);\n\t\tsnprintf(line, sizeof(line), \"%4.1f %3d ms\", f_avg, ping_avg);\n\t\tDraw_SString(x + width - Draw_StringLength(line, -1, hud_net_scale->value, hud_net_proportional->integer), y, line, hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 8 * hud_net_scale->value;\n\n\t\tDraw_SString(x, y, \"max\", hud_net_scale->value, hud_net_proportional->integer);\n\t\tsnprintf(line, sizeof(line), \"%4.1f %3d ms\", f_max, ping_max);\n\t\tDraw_SString(x + width - Draw_StringLength(line, -1, hud_net_scale->value, hud_net_proportional->integer), y, line, hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 8 * hud_net_scale->value;\n\n\t\tDraw_SString(x, y, \"dev\", hud_net_scale->value, hud_net_proportional->integer);\n\t\tsnprintf(line, sizeof(line), \"%5.2f ms\", ping_dev);\n\t\tDraw_SString(x + width - Draw_StringLength(line, -1, hud_net_scale->value, hud_net_proportional->integer), y, line, hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 12 * hud_net_scale->value;\n\n\t\tDraw_Alt_String(x + (width - Draw_StringLength(\"packet loss\", 11, hud_net_scale->value, hud_net_proportional->integer)) / 2, y, \"packet loss\", hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 12 * hud_net_scale->value;\n\n\t\tDraw_SString(x, y, \"lost\", hud_net_scale->value, hud_net_proportional->integer);\n\t\tsnprintf(line, sizeof(line), \"%3d %%\", lost_lost);\n\t\tDraw_SString(x + width - Draw_StringLength(line, -1, hud_net_scale->value, hud_net_proportional->integer), y, line, hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 8 * hud_net_scale->value;\n\n\t\tDraw_SString(x, y, \"rate cut\", hud_net_scale->value, hud_net_proportional->integer);\n\t\tsnprintf(line, sizeof(line), \"%3d %%\", lost_rate);\n\t\tDraw_SString(x + width - Draw_StringLength(line, -1, hud_net_scale->value, hud_net_proportional->integer), y, line, hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 8 * hud_net_scale->value;\n\n\t\tif (with_delta) {\n\t\t\tDraw_SString(x, y, \"bad delta\", hud_net_scale->value, hud_net_proportional->integer);\n\t\t\tsnprintf(line, sizeof(line), \"%3d %%\", lost_delta);\n\t\t\tDraw_SString(x + width - Draw_StringLength(line, -1, hud_net_scale->value, hud_net_proportional->integer), y, line, hud_net_scale->value, hud_net_proportional->integer);\n\t\t}\n\t\telse {\n\t\t\tDraw_SString(x, y, \"no delta compr\", hud_net_scale->value, hud_net_proportional->integer);\n\t\t}\n\t\ty += 8 * hud_net_scale->value;\n\n\t\tDraw_SString(x, y, \"total\", hud_net_scale->value, hud_net_proportional->integer);\n\t\tsnprintf(line, sizeof(line), \"%3d %%\", lost_total);\n\t\tDraw_SString(x + width - Draw_StringLength(line, -1, hud_net_scale->value, hud_net_proportional->integer), y, line, hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 12 * hud_net_scale->value;\n\n\t\tDraw_Alt_String(x + (width - Draw_StringLength(\"packet size/BPS\", 15, hud_net_scale->value, hud_net_proportional->integer)) / 2, y, \"packet size/BPS\", hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 12 * hud_net_scale->value;\n\n\t\tDraw_SString(x, y, \"out\", hud_net_scale->value, hud_net_proportional->integer);\n\t\tsnprintf(line, sizeof(line), \"%3d %5d\", size_out, bandwidth_out);\n\t\tDraw_SString(x + width - Draw_StringLength(line, -1, hud_net_scale->value, hud_net_proportional->integer), y, line, hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 8 * hud_net_scale->value;\n\n\t\tDraw_SString(x, y, \"in\", hud_net_scale->value, hud_net_proportional->integer);\n\t\tsnprintf(line, sizeof(line), \"%3d %5d\", size_in, bandwidth_in);\n\t\tDraw_SString(x + width - Draw_StringLength(line, -1, hud_net_scale->value, hud_net_proportional->integer), y, line, hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 8 * hud_net_scale->value;\n\n\t\tDraw_SString(x, y, \"total\", hud_net_scale->value, hud_net_proportional->integer);\n\t\tsnprintf(line, sizeof(line), \"%3d %5d\", size_all, bandwidth_all);\n\t\tDraw_SString(x + width - Draw_StringLength(line, -1, hud_net_scale->value, hud_net_proportional->integer), y, line, hud_net_scale->value, hud_net_proportional->integer);\n\t\ty += 8 * hud_net_scale->value;\n\t}\n}\n\n// Problem icon, Net\nstatic void SCR_HUD_NetProblem(hud_t *hud)\n{\n\textern mpic_t *scr_net;\n\tstatic cvar_t *scale = NULL;\n\tint x, y;\n\textern qbool hud_editor;\n\n\tif (scale == NULL) {\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t}\n\n\tif ((cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged < UPDATE_BACKUP - 1) || cls.demoplayback) {\n\t\tif (hud_editor)\n\t\t\tHUD_PrepareDraw(hud, scr_net->width, scr_net->height, &x, &y);\n\t\treturn;\n\t}\n\n\tif (!HUD_PrepareDraw(hud, scr_net->width, scr_net->height, &x, &y))\n\t\treturn;\n\n\tDraw_SPic(x, y, scr_net, scale->value);\n}\n\n//---------------------\n//\n// draw HUD ping\n//\nstatic void SCR_HUD_DrawPing(hud_t *hud)\n{\n\tdouble t;\n\tstatic double last_calculated;\n\tstatic int ping_avg, pl, ping_min, ping_max;\n\tstatic float ping_dev;\n\n\tint width, height;\n\tint x, y;\n\tchar buf[512];\n\n\tstatic cvar_t\n\t\t*hud_ping_period = NULL,\n\t\t*hud_ping_show_pl,\n\t\t*hud_ping_show_dev,\n\t\t*hud_ping_show_min,\n\t\t*hud_ping_show_max,\n\t\t*hud_ping_style,\n\t\t*hud_ping_blink,\n\t\t*hud_ping_scale,\n\t\t*hud_ping_proportional;\n\n\tif (hud_ping_period == NULL)    // first time\n\t{\n\t\thud_ping_period = HUD_FindVar(hud, \"period\");\n\t\thud_ping_show_pl = HUD_FindVar(hud, \"show_pl\");\n\t\thud_ping_show_dev = HUD_FindVar(hud, \"show_dev\");\n\t\thud_ping_show_min = HUD_FindVar(hud, \"show_min\");\n\t\thud_ping_show_max = HUD_FindVar(hud, \"show_max\");\n\t\thud_ping_style = HUD_FindVar(hud, \"style\");\n\t\thud_ping_blink = HUD_FindVar(hud, \"blink\");\n\t\thud_ping_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_ping_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tt = curtime;\n\tif (t - last_calculated > hud_ping_period->value) {\n\t\t// recalculate\n\t\tnet_stat_result_t result;\n\t\tfloat period;\n\n\t\tlast_calculated = t;\n\n\t\tperiod = max(hud_ping_period->value, 0);\n\n\t\tCL_CalcNetStatistics(\n\t\t\tperiod,             // period of time\n\t\t\tnetwork_stats,      // samples table\n\t\t\tNETWORK_STATS_SIZE, // number of samples in table\n\t\t\t&result);           // results\n\n\t\tif (result.samples == 0)\n\t\t\treturn; // error calculating net\n\n\t\tping_avg = (int)(result.ping_avg + 0.5);\n\t\tping_min = (int)(result.ping_min + 0.5);\n\t\tping_max = (int)(result.ping_max + 0.5);\n\t\tping_dev = result.ping_dev;\n\t\tpl = result.lost_lost;\n\n\t\tclamp(ping_avg, 0, 999);\n\t\tclamp(ping_min, 0, 999);\n\t\tclamp(ping_max, 0, 999);\n\t\tclamp(ping_dev, 0, 99.9);\n\t\tclamp(pl, 0, 100);\n\t}\n\n\tbuf[0] = 0;\n\n\t// blink\n\tif (hud_ping_blink->value)   // add dot\n\t\tstrlcat(buf, (last_calculated + hud_ping_period->value / 2 > cls.realtime) ? \"\\x8f\" : \" \", sizeof(buf));\n\n\t// min ping\n\tif (hud_ping_show_min->value)\n\t\tstrlcat(buf, va(\"%d\\xf\", ping_min), sizeof(buf));\n\n\t// ping\n\tstrlcat(buf, va(\"%d\", ping_avg), sizeof(buf));\n\n\t// max ping\n\tif (hud_ping_show_max->value)\n\t\tstrlcat(buf, va(\"\\xf%d\", ping_max), sizeof(buf));\n\n\t// unit\n\tstrlcat(buf, \" ms\", sizeof(buf));\n\n\t// standard deviation\n\tif (hud_ping_show_dev->value)\n\t\tstrlcat(buf, va(\" (%.1f)\", ping_dev), sizeof(buf));\n\n\t// pl\n\tif (hud_ping_show_pl->value)\n\t\tstrlcat(buf, va(\" \\x8f %d%%\", pl), sizeof(buf));\n\n\t// display that on screen\n\twidth = Draw_StringLength(buf, -1, hud_ping_scale->value, hud_ping_proportional->integer);\n\theight = 8 * hud_ping_scale->value;\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tif (hud_ping_style->value) {\n\t\t\tDraw_SAlt_String(x, y, buf, hud_ping_scale->value, hud_ping_proportional->integer);\n\t\t}\n\t\telse {\n\t\t\tDraw_SString(x, y, buf, hud_ping_scale->value, hud_ping_proportional->integer);\n\t\t}\n\t}\n}\n\nvoid Net_HudInit(void)\n{\n\t// init net\n\tHUD_Register(\n\t\t\"net\", NULL, \"Shows network statistics, like latency, packet loss, average packet sizes and bandwidth. Shown only when you are connected to a server.\",\n\t\tHUD_PLUSMINUS, ca_active, 7, SCR_HUD_DrawNetStats,\n\t\t\"0\", \"top\", \"left\", \"center\", \"0\", \"0\", \"0.2\", \"0 0 0\", NULL,\n\t\t\"period\", \"1\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\t// netproblem icon\n\tHUD_Register(\n\t\t\"netproblem\", NULL, \"Shows an icon if you are experiencing network problems\",\n\t\tHUD_NO_FRAME, ca_active, 0, SCR_HUD_NetProblem,\n\t\t\"1\", \"top\", \"left\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"scale\", \"1\",\n\t\tNULL\n\t);\n\n\t// init ping\n\tHUD_Register(\n\t\t\"ping\", NULL, \"Shows most important net conditions, like ping and pl. Shown only when you are connected to a server.\",\n\t\tHUD_PLUSMINUS, ca_active, 9, SCR_HUD_DrawPing,\n\t\t\"0\", \"screen\", \"left\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"period\", \"1\",\n\t\t\"show_pl\", \"1\",\n\t\t\"show_min\", \"0\",\n\t\t\"show_max\", \"0\",\n\t\t\"show_dev\", \"0\",\n\t\t\"style\", \"0\",\n\t\t\"blink\", \"1\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n}\n"
  },
  {
    "path": "src/hud_performance.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"tr_types.h\"\n#include \"r_framestats.h\"\n#include \"screen.h\"\n#include \"vx_stuff.h\"\n\ncvar_t\tshow_fps = { \"show_fps\", \"0\" };\nstatic cvar_t\tshow_fps_x = { \"show_fps_x\", \"-5\" };\nstatic cvar_t\tshow_fps_y = { \"show_fps_y\", \"-1\" };\n\nr_frame_stats_t prevFrameStats;\nr_frame_stats_t frameStats;\n\n// ----------------\n// DrawFPS\nstatic void SCR_HUD_DrawFPS(hud_t *hud)\n{\n\tint x, y;\n\tchar st[128];\n\tqbool drop_triggered = false;\n\textern cvar_t cl_maxfps;\n\n\tstatic cvar_t\n\t\t*hud_fps_show_min = NULL,\n\t\t*hud_fps_style,\n\t\t*hud_fps_title,\n\t\t*hud_fps_drop,\n\t\t*hud_fps_scale,\n\t\t*hud_fps_proportional;\n\n\tif (hud_fps_show_min == NULL) {\n\t\t// first time called\n\t\thud_fps_show_min = HUD_FindVar(hud, \"show_min\");\n\t\thud_fps_style = HUD_FindVar(hud, \"style\");\n\t\thud_fps_title = HUD_FindVar(hud, \"title\");\n\t\thud_fps_drop = HUD_FindVar(hud, \"drop\");\n\t\thud_fps_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_fps_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tdrop_triggered = (hud_fps_drop->integer > 0 && (hud_fps_drop->value) >= cls.fps);\n\tdrop_triggered |= (hud_fps_drop->integer < 0 && cl_maxfps.integer && (cl_maxfps.integer + hud_fps_drop->value) >= cls.fps);\n\n\tif (hud_fps_show_min->value) {\n\t\tsnprintf(st, sizeof(st), \"%3d\\xf%3d\", (int)(cls.min_fps + 0.25), (int)(cls.fps + 0.25));\n\t}\n\telse {\n\t\tsnprintf(st, sizeof(st), \"%3d\", (int)(cls.fps + 0.25));\n\t}\n\n\tif (hud_fps_title->value) {\n\t\tstrlcat(st, \" fps\", sizeof(st));\n\t}\n\n\tif (HUD_PrepareDraw(hud, Draw_StringLength(st, -1, hud_fps_scale->value, hud_fps_proportional->integer), 8 * hud_fps_scale->value, &x, &y)) {\n\t\tswitch (hud_fps_style->integer) {\n\t\t\tcase 1:\n\t\t\t\tDraw_SAlt_String(x, y, st, hud_fps_scale->value, hud_fps_proportional->integer);;\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\t// if fps is less than a user-set value, then show it\n\t\t\t\tif (drop_triggered) {\n\t\t\t\t\tDraw_SString(x, y, st, hud_fps_scale->value, hud_fps_proportional->integer);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\t// if fps is less than a user-set value, then show it\n\t\t\t\tif (drop_triggered) {\n\t\t\t\t\tDraw_SAlt_String(x, y, st, hud_fps_scale->value, hud_fps_proportional->integer);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tDraw_SString(x, y, st, hud_fps_scale->value, hud_fps_proportional->integer);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n// DrawFrameTime\nstatic void SCR_HUD_DrawFrameTime(hud_t* hud)\n{\n\tint x, y;\n\tchar st[128];\n\n\tstatic cvar_t\n\t\t* hud_frametime_show_max = NULL,\n\t\t* hud_frametime_style,\n\t\t* hud_frametime_title,\n\t\t* hud_frametime_spike,\n\t\t* hud_frametime_scale,\n\t\t* hud_frametime_proportional;\n\n\tif (hud_frametime_show_max == NULL) {\n\t\t// first time called\n\t\thud_frametime_show_max = HUD_FindVar(hud, \"show_max\");\n\t\thud_frametime_style = HUD_FindVar(hud, \"style\");\n\t\thud_frametime_title = HUD_FindVar(hud, \"title\");\n\t\thud_frametime_spike = HUD_FindVar(hud, \"spike\");\n\t\thud_frametime_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_frametime_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tif (hud_frametime_show_max->value) {\n\t\tsnprintf(st, sizeof(st), \"%3.1f\\xf%3.1f\", cls.max_frametime * 1000, cls.avg_frametime * 1000);\n\t}\n\telse {\n\t\tsnprintf(st, sizeof(st), \"%3.1f\", cls.avg_frametime * 1000);\n\t}\n\n\tif (hud_frametime_title->value) {\n\t\tstrlcat(st, \" ms\", sizeof(st));\n\t}\n\n\tif (HUD_PrepareDraw(hud, Draw_StringLength(st, -1, hud_frametime_scale->value, hud_frametime_proportional->integer), 8 * hud_frametime_scale->value, &x, &y)) {\n\t\tswitch (hud_frametime_style->integer) {\n\t\tcase 1:\n\t\t\tDraw_SAlt_String(x, y, st, hud_frametime_scale->value, hud_frametime_proportional->integer);;\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\t// if frametime is greater than a user-set value, then show it\n\t\t\tif ((hud_frametime_spike->value) <= cls.max_frametime * 1000) {\n\t\t\t\tDraw_SString(x, y, st, hud_frametime_scale->value, hud_frametime_proportional->integer);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\t// if frametime is greater than a user-set value, then show it\n\t\t\tif ((hud_frametime_spike->value) <= cls.max_frametime * 1000) {\n\t\t\t\tDraw_SAlt_String(x, y, st, hud_frametime_scale->value, hud_frametime_proportional->integer);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tDraw_SString(x, y, st, hud_frametime_scale->value, hud_frametime_proportional->integer);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic void SCR_HUD_DrawVidLag(hud_t *hud)\n{\n\tint x, y;\n\tchar st[128];\n\tstatic cvar_t *hud_vidlag_style = NULL;\n\tstatic cvar_t *hud_vidlag_scale = NULL;\n\tstatic cvar_t *hud_vidlag_proportional = NULL;\n\n\textern qbool VID_VSyncIsOn(void);\n\textern double vid_vsync_lag;\n\tstatic double old_lag;\n\n\tif (VID_VSyncIsOn() || glConfig.displayFrequency) {\n\t\t// take the average of last two values, otherwise it\n\t\t// changes very fast and is hard to read\n\t\tdouble current, avg;\n\t\tif (VID_VSyncIsOn()) {\n\t\t\tcurrent = vid_vsync_lag;\n\t\t}\n\t\telse {\n\t\t\tcurrent = min(cls.trueframetime, 1.0 / glConfig.displayFrequency) * 0.5;\n\t\t}\n\t\tavg = (current + old_lag) * 0.5;\n\t\told_lag = current;\n\t\tsnprintf(st, sizeof(st), \"%2.1f\", avg * 1000);\n\t}\n\telse {\n\t\tstrcpy(st, \"?\");\n\t}\n\n\tif (hud_vidlag_style == NULL) {\n\t\t// first time called\n\t\thud_vidlag_style = HUD_FindVar(hud, \"style\");\n\t\thud_vidlag_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_vidlag_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tstrlcat(st, \" ms\", sizeof(st));\n\n\tif (HUD_PrepareDraw(hud, Draw_StringLength(st, -1, hud_vidlag_scale->value, hud_vidlag_proportional->integer), 8 * hud_vidlag_scale->value, &x, &y)) {\n\t\tif (hud_vidlag_style->value) {\n\t\t\tDraw_SAlt_String(x, y, st, hud_vidlag_scale->value, hud_vidlag_proportional->integer);\n\t\t}\n\t\telse {\n\t\t\tDraw_SString(x, y, st, hud_vidlag_scale->value, hud_vidlag_proportional->integer);\n\t\t}\n\t}\n}\n\n#define MAX_FRAMESTATS_LINES 32\nstatic char content[MAX_FRAMESTATS_LINES][2][64];\n\nstatic void FrameStats_AddLine(int* line, const char* name, int value)\n{\n\tif (*line < 0 || *line >= MAX_FRAMESTATS_LINES) {\n\t\treturn;\n\t}\n\n\tstrlcpy(content[*line][0], name, sizeof(content[*line][0]));\n\tif (name[0]) {\n\t\tsnprintf(content[*line][1], sizeof(content[*line][1]), \"%4d\", value);\n\t}\n\telse {\n\t\tmemset(content[*line][1], 0, sizeof(content[*line][1]));\n\t}\n\t++(*line);\n}\n\nstatic void FrameStats_DrawElement(hud_t *hud)\n{\n\tstatic cvar_t\n\t\t*hud_frameStats_style = NULL,\n\t\t*hud_frameStats_scale,\n\t\t*hud_frameStats_proportional,\n\t\t*hud_frameStats_amfstats;\n\n\tint height = 8;\n\tint width = 0;\n\tint x = 0;\n\tint y = 0;\n\tint lines = 0;\n\tint i;\n\tint max_field_length;\n\tint max_value_length;\n\tint value_length[MAX_FRAMESTATS_LINES];\n\n\tif (hud_frameStats_style == NULL) {\n\t\t// first time\n\t\thud_frameStats_style = HUD_FindVar(hud, \"style\");\n\t\thud_frameStats_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_frameStats_proportional = HUD_FindVar(hud, \"proportional\");\n\t\thud_frameStats_amfstats = HUD_FindVar(hud, \"amfstats\");\n\t}\n\n\tFrameStats_AddLine(&lines, \"Draw calls:\", prevFrameStats.draw_calls);\n\tFrameStats_AddLine(&lines, \"Sub-draw calls:\", prevFrameStats.subdraw_calls);\n\tFrameStats_AddLine(&lines, \"Texture switches:\", prevFrameStats.texture_binds);\n\tFrameStats_AddLine(&lines, \"Lightmap uploads:\", prevFrameStats.lightmap_updates);\n\tif (frameStats.classic.polycount[polyTypeWorldModel]) {\n\t\tFrameStats_AddLine(&lines, \"\", 0);\n\t\tFrameStats_AddLine(&lines, \"World-model polys:\", frameStats.classic.polycount[polyTypeWorldModel]);\n\t\tif (cl.standby || com_serveractive) {\n\t\t\tFrameStats_AddLine(&lines, \"Alias-model polys:\", frameStats.classic.polycount[polyTypeAliasModel]);\n\t\t\tFrameStats_AddLine(&lines, \"Brush-model polys:\", frameStats.classic.polycount[polyTypeBrushModel]);\n\t\t}\n\t}\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tFrameStats_AddLine(&lines, \"Mallocs:\", prevFrameStats.mallocs);\n\tFrameStats_AddLine(&lines, \"HotMallocs:\", prevFrameStats.hotloop_mallocs);\n#endif\n\n\tFrameStats_AddLine(&lines, \"Particles:\", prevFrameStats.particle_count);\n\tif (hud_frameStats_amfstats->integer) {\n\t\tFrameStats_AddLine(&lines, \"\", 0);\n\t\tFrameStats_AddLine(&lines, \"AMF particle count:\", ParticleCount);\n\t\tFrameStats_AddLine(&lines, \"AMF particle peak:\", ParticleCountHigh);\n\t\tFrameStats_AddLine(&lines, \"Corona count:\", CoronaCount);\n\t\tFrameStats_AddLine(&lines, \"Corona peak:\", CoronaCountHigh);\n\t}\n\n\theight = lines * 8 * hud_frameStats_scale->value;\n\tmax_field_length = max_value_length = 0;\n\tfor (i = 0; i < lines; ++i) {\n\t\tint name_length = Draw_StringLength(content[i][0], -1, hud_frameStats_scale->value, hud_frameStats_proportional->integer);\n\t\tvalue_length[i] = Draw_StringLength(content[i][1], -1, hud_frameStats_scale->value, hud_frameStats_proportional->integer);\n\n\t\tmax_field_length = max(max_field_length, name_length);\n\t\tmax_value_length = max(max_value_length, value_length[i]);\n\t}\n\n\twidth = max_field_length + 8 * hud_frameStats_scale->value + max_value_length;\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tfor (i = 0; i < lines; ++i, y += 8 * hud_frameStats_scale->value) {\n\t\t\tif (content[i][0][0]) {\n\t\t\t\tDraw_SString(x, y, content[i][0], hud_frameStats_scale->value, hud_frameStats_proportional->integer);\n\t\t\t\tDraw_SString(x + width - value_length[i], y, content[i][1], hud_frameStats_scale->value, hud_frameStats_proportional->integer);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Performance_HudInit(void)\n{\n\t// fps\n\tHUD_Register(\n\t\t\"fps\", NULL,\n\t\t\"Shows your current framerate in frames per second (fps).\"\n\t\t\"This can also show the minimum framerate that occured in the last measured period.\",\n\t\tHUD_PLUSMINUS, ca_active, 9, SCR_HUD_DrawFPS,\n\t\t\"1\", \"gameclock\", \"center\", \"after\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"show_min\", \"0\",\n\t\t\"style\", \"0\",\n\t\t\"title\", \"1\",\n\t\t\"scale\", \"1\",\n\t\t\"drop\", \"70\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\t// frametime\n\tHUD_Register(\n\t\t\"frametime\", NULL,\n\t\t\"Shows your current frametime in ms.\"\n\t\t\"This can also show the maximum frametime that occured in the last measured period.\",\n\t\tHUD_PLUSMINUS, ca_active, 9, SCR_HUD_DrawFrameTime,\n\t\t\"0\", \"fps\", \"center\", \"after\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"show_max\", \"1\",\n\t\t\"style\", \"0\",\n\t\t\"title\", \"1\",\n\t\t\"scale\", \"1\",\n\t\t\"spike\", \"10\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\n\t\t\"vidlag\", NULL,\n\t\t\"Shows the delay between the time a frame is rendered and the time it's displayed.\",\n\t\tHUD_PLUSMINUS, ca_active, 9, SCR_HUD_DrawVidLag,\n\t\t\"0\", \"top\", \"right\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\n\t\t\"framestats\", NULL,\n\t\t\"Shows information about the renderer's status & workload.\",\n\t\tHUD_PLUSMINUS, ca_active, 0, FrameStats_DrawElement,\n\t\t\"0\", \"top\", \"left\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\t\"amfstats\", \"0\",\n\t\tNULL\n\t);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\tCvar_Register(&show_fps);\n\tCvar_Register(&show_fps_x);\n\tCvar_Register(&show_fps_y);\n\tCvar_ResetCurrentGroup();\n}\n\nvoid SCR_DrawFPS(void)\n{\n\tint x, y;\n\tchar str[80];\n\textern cvar_t scr_newHud;\n\n\tif (!show_fps.integer || scr_newHud.integer == 1) {\n\t\t// HUD -> hexum - newHud has its own fps\n\t\treturn;\n\t}\n\n\tsnprintf(str, sizeof(str), \"%3.1f\", cls.fps + 0.05);\n\n\tx = ELEMENT_X_COORD(show_fps);\n\ty = ELEMENT_Y_COORD(show_fps);\n\tDraw_String(x, y, str);\n}\n"
  },
  {
    "path": "src/hud_qtv.c",
    "content": "/*\nCopyright (C) 1996-2003 Id Software, Inc., A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: cl_screen.c,v 1.156 2007-10-29 00:56:47 qqshka Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"hud.h\"\n#include \"qtv.h\"\n\nstatic cvar_t scr_qtvbuffer   = { \"scr_qtvbuffer\",        \"0\" };\nstatic cvar_t scr_qtvbuffer_x = { \"scr_qtvbuffer_x\",      \"0\" };\nstatic cvar_t scr_qtvbuffer_y = { \"scr_qtvbuffer_y\",    \"-10\" };\n\nvoid SCR_DrawQTVBuffer(void)\n{\n\textern double Demo_GetSpeed(void);\n\n\tint x, y;\n\tint ms, len;\n\tchar str[64];\n\n\tif (!SCR_QTVBufferToBeDrawn(scr_qtvbuffer.integer)) {\n\t\treturn;\n\t}\n\n\tlen = Demo_BufferSize(&ms);\n\tsnprintf(str, sizeof(str), \"%6dms %5db %2.3f\", ms, len, Demo_GetSpeed());\n\n\tx = ELEMENT_X_COORD(scr_qtvbuffer);\n\ty = ELEMENT_Y_COORD(scr_qtvbuffer);\n\tDraw_String(x, y, str);\n}\n\nstatic void SCR_HUD_DrawQTVBuffer(hud_t* hud)\n{\n\textern double Demo_GetSpeed(void);\n\n\tint x, y;\n\tint ms, len;\n\tchar str[64];\n\tfloat draw_len;\n\n\tstatic cvar_t *hud_scale, *hud_proportional;\n\n\tif (hud_scale == NULL) {\n\t\thud_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tif (!SCR_QTVBufferToBeDrawn(hud->show->integer)) {\n\t\treturn;\n\t}\n\n\tlen = Demo_BufferSize(&ms);\n\tsnprintf(str, sizeof(str), \"%6dms %5db %2.3f\", ms, len, Demo_GetSpeed());\n\t\n\tdraw_len = Draw_StringLength(str, -1, hud_scale->value, hud_proportional->integer);\n\tif (HUD_PrepareDraw(hud, draw_len, 8 * hud_scale->value, &x, &y)) {\n\t\tDraw_SString(x, y, str, hud_scale->value, hud_proportional->integer);\n\t}\n}\n\nvoid Qtv_HudInit(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\tCvar_Register(&scr_qtvbuffer_x);\n\tCvar_Register(&scr_qtvbuffer_y);\n\tCvar_Register(&scr_qtvbuffer);\n\tCvar_ResetCurrentGroup();\n\n\tHUD_Register(\n\t\t\"qtv_buffer\", NULL, \"QTV buffering status.\",\n\t\t0, ca_active, 0, SCR_HUD_DrawQTVBuffer,\n\t\t\"0\", \"screen\", \"left\", \"top\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n}\n"
  },
  {
    "path": "src/hud_radar.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"stats_grid.h\"\n#include \"utils.h\"\n#include \"hud.h\"\n#include \"gl_model.h\"\n#include \"image.h\"\n#if defined(WITH_PNG)\n#include <png.h>\n#endif\n#include \"sbar.h\"\n#include \"r_texture.h\"\n#include \"r_renderer.h\"\n\n// What stats to draw.\n#define HUD_RADAR_STATS_NONE\t\t\t\t0\n#define HUD_RADAR_STATS_BOTH_TEAMS_HOLD\t\t1\n#define HUD_RADAR_STATS_TEAM1_HOLD\t\t\t2\n#define HUD_RADAR_STATS_TEAM2_HOLD\t\t\t3\n#define HUD_RADAR_STATS_BOTH_TEAMS_DEATHS\t4\n#define HUD_RADAR_STATS_TEAM1_DEATHS\t\t5\n#define HUD_RADAR_STATS_TEAM2_DEATHS\t\t6\n\n// The skinnum property in the entity_s structure is used\n// for determening what type of armor to draw on the radar.\n#define HUD_RADAR_GA\t\t\t\t\t0\n#define HUD_RADAR_YA\t\t\t\t\t1\n#define HUD_RADAR_RA\t\t\t\t\t2\n\n#define HUD_RADAR_HIGHLIGHT_NONE\t\t\t0\n#define HUD_RADAR_HIGHLIGHT_TEXT_ONLY\t\t1\n#define HUD_RADAR_HIGHLIGHT_OUTLINE\t\t\t2\n#define HUD_RADAR_HIGHLIGHT_FIXED_OUTLINE\t3\n#define HUD_RADAR_HIGHLIGHT_CIRCLE\t\t\t4\n#define HUD_RADAR_HIGHLIGHT_FIXED_CIRCLE\t5\n#define HUD_RADAR_HIGHLIGHT_ARROW_BOTTOM\t6\n#define HUD_RADAR_HIGHLIGHT_ARROW_CENTER\t7\n#define HUD_RADAR_HIGHLIGHT_ARROW_TOP\t\t8\n#define HUD_RADAR_HIGHLIGHT_CROSS_CORNERS\t9\n#define HUD_RADAR_HIGHLIGHT_BRIGHT_VIEW    10\n\n// Radar filters.\n#define RADAR_SHOW_WEAPONS (radar_show_ssg || radar_show_ng || radar_show_sng || radar_show_gl || radar_show_rl || radar_show_lg)\nstatic qbool radar_show_ssg = false;\nstatic qbool radar_show_ng = false;\nstatic qbool radar_show_sng = false;\nstatic qbool radar_show_gl = false;\nstatic qbool radar_show_rl = false;\nstatic qbool radar_show_lg = false;\n\n#define RADAR_SHOW_ITEMS (radar_show_backpacks || radar_show_health || radar_show_ra || radar_show_ya || radar_show_ga || radar_show_rockets || radar_show_nails || radar_show_cells || radar_show_shells || radar_show_quad || radar_show_pent || radar_show_ring || radar_show_suit)\nstatic qbool radar_show_backpacks = false;\nstatic qbool radar_show_health = false;\nstatic qbool radar_show_ra = false;\nstatic qbool radar_show_ya = false;\nstatic qbool radar_show_ga = false;\nstatic qbool radar_show_rockets = false;\nstatic qbool radar_show_nails = false;\nstatic qbool radar_show_cells = false;\nstatic qbool radar_show_shells = false;\nstatic qbool radar_show_quad = false;\nstatic qbool radar_show_pent = false;\nstatic qbool radar_show_ring = false;\nstatic qbool radar_show_suit = false;\nstatic qbool radar_show_mega = false;\n\n#define RADAR_SHOW_OTHER (radar_show_gibs || radar_show_explosions || radar_show_nails_p || radar_show_rockets_p || radar_show_shaft_p || radar_show_teleport || radar_show_shotgun)\nstatic qbool radar_show_nails_p = false;\nstatic qbool radar_show_rockets_p = false;\nstatic qbool radar_show_shaft_p = false;\nstatic qbool radar_show_gibs = false;\nstatic qbool radar_show_explosions = false;\nstatic qbool radar_show_teleport = false;\nstatic qbool radar_show_shotgun = false;\n\n#define HUD_COLOR_DEFAULT_TRANSPARENCY\t75\n\nbyte hud_radar_highlight_color[4] = { 255, 255, 0, HUD_COLOR_DEFAULT_TRANSPARENCY };\n\nextern temp_entity_list_t temp_entities;\n\n#ifdef WITH_PNG\n\n// Map picture to draw for the mapoverview hud control.\nmpic_t radar_pic;\nstatic qbool radar_pic_found = false;\n\n// The conversion formula used for converting from quake coordinates to pixel coordinates\n// when drawing on the map overview.\nstatic float map_x_slope;\nstatic float map_x_intercept;\nstatic float map_y_slope;\nstatic float map_y_intercept;\nstatic qbool conversion_formula_found = false;\n\n// Used for drawing the height of the player.\nstatic float map_height_diff = 0.0;\n\n#define RADAR_BASE_PATH_FORMAT\t\"radars/%s.png\"\n\n//\n// Is run when a new map is loaded.\n//\nvoid HUD_NewRadarMap(void)\n{\n\tint i = 0;\n\tint len = 0;\n\tint n_textcount = 0;\n\tmpic_t *radar_pic_p = NULL;\n\tpng_textp txt = NULL;\n\tchar *radar_filename = NULL;\n\n\tif (!cl.worldmodel)\n\t\treturn; // seems we are not ready to do that\n\n\t// Reset the radar pic status.\n\tmemset (&radar_pic, 0, sizeof(radar_pic));\n\tradar_pic_found = false;\n\tconversion_formula_found = false;\n\n\t// Allocate a string for the path to the radar image.\n\tlen = strlen (RADAR_BASE_PATH_FORMAT) + strlen (host_mapname.string);\n\tradar_filename = Q_calloc (len, sizeof(char));\n\tsnprintf (radar_filename, len, RADAR_BASE_PATH_FORMAT, host_mapname.string);\n\n\t// Load the map picture.\n\tif ((radar_pic_p = R_LoadPicImage (radar_filename, host_mapname.string, 0, 0, TEX_ALPHA)) != NULL) {\n\t\tradar_pic = *radar_pic_p;\n\t\tradar_pic_found = true;\n\n\t\trenderer.TextureWrapModeClamp(radar_pic.texnum);\n\n\t\t// Calculate the height of the map.\n\t\tmap_height_diff = fabs((float)(cl.worldmodel->maxs[2] - cl.worldmodel->mins[2]));\n\n\t\t// Get the comments from the PNG.\n\t\ttxt = Image_LoadPNG_Comments(radar_filename, &n_textcount);\n\n\t\t// Check if we found any comments.\n\t\tif(txt != NULL)\n\t\t{\n\t\t\tint found_count = 0;\n\n\t\t\t// Find the conversion formula in the comments found in the PNG.\n\t\t\tfor(i = 0; i < n_textcount; i++)\n\t\t\t{\n\t\t\t\tif(!strcmp(txt[i].key, \"QWLMConversionSlopeX\"))\n\t\t\t\t{\n\t\t\t\t\tmap_x_slope = atof(txt[i].text);\n\t\t\t\t\tfound_count++;\n\t\t\t\t}\n\t\t\t\telse if(!strcmp(txt[i].key, \"QWLMConversionInterceptX\"))\n\t\t\t\t{\n\t\t\t\t\tmap_x_intercept = atof(txt[i].text);\n\t\t\t\t\tfound_count++;\n\t\t\t\t}\n\t\t\t\telse if(!strcmp(txt[i].key, \"QWLMConversionSlopeY\"))\n\t\t\t\t{\n\t\t\t\t\tmap_y_slope = atof(txt[i].text);\n\t\t\t\t\tfound_count++;\n\t\t\t\t}\n\t\t\t\telse if(!strcmp(txt[i].key, \"QWLMConversionInterceptY\"))\n\t\t\t\t{\n\t\t\t\t\tmap_y_intercept = atof(txt[i].text);\n\t\t\t\t\tfound_count++;\n\t\t\t\t}\n\n\t\t\t\tQ_free(txt[i].key);\n\t\t\t\tQ_free(txt[i].text);\n\n\t\t\t\tconversion_formula_found = (found_count == 4);\n\t\t\t}\n\n\t\t\t// Free the text chunks.\n\t\t\tQ_free(txt);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tconversion_formula_found = false;\n\t\t\tCon_Printf(\"No conversion formula found\\n\");\n\t\t}\n\t}\n\telse\n\t{\n\t\t// No radar pic found.\n\t\tmemset (&radar_pic, 0, sizeof(radar_pic));\n\t\tradar_pic_found = false;\n\t\tconversion_formula_found = false;\n\t}\n\n\t// Free the path string to the radar png.\n\tQ_free (radar_filename);\n}\n\nstatic void Radar_DrawGrid(stats_weight_grid_t *grid, int x, int y, float scale, int pic_width, int pic_height, int style)\n{\n\tint row, col;\n\n\t// Don't try to draw anything if we got no data.\n\tif(grid == NULL || grid->cells == NULL || style == HUD_RADAR_STATS_NONE)\n\t{\n\t\treturn;\n\t}\n\n\t// Go through all the cells and draw them based on their weight.\n\tfor(row = 0; row < grid->row_count; row++)\n\t{\n\t\t// Just to be safe if something went wrong with the allocation.\n\t\tif(grid->cells[row] == NULL)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor(col = 0; col < grid->col_count; col++)\n\t\t{\n\t\t\tfloat weight = 0.0;\n\t\t\tint color = 0;\n\n\t\t\tfloat tl_x, tl_y;\t\t\t// The pixel coordinate of the top left corner of a grid cell.\n\t\t\tfloat p_cell_length_x;\t\t// The pixel length of a cell.\n\t\t\tfloat p_cell_length_y;\t\t// The pixel \"length\" on the Y-axis. We calculate this\n\t\t\t\t\t\t\t\t\t\t// seperatly because we'll get errors when converting from\n\t\t\t\t\t\t\t\t\t\t// quake coordinates -> pixel coordinates.\n\n\t\t\t// Calculate the pixel coordinates of the top left corner of the current cell.\n\t\t\t// (This is times 8 because the conversion formula was calculated from a .loc-file)\n\t\t\ttl_x = (map_x_slope * (8.0 * grid->cells[row][col].tl_x) + map_x_intercept) * scale;\n\t\t\ttl_y = (map_y_slope * (8.0 * grid->cells[row][col].tl_y) + map_y_intercept) * scale;\n\n\t\t\t// Calculate the cell length in pixel length.\n\t\t\tp_cell_length_x = map_x_slope*(8.0 * grid->cell_length) * scale;\n\t\t\tp_cell_length_y = map_y_slope*(8.0 * grid->cell_length) * scale;\n\n\t\t\t// Add rounding errors (so that we don't get weird gaps in the grid).\n\t\t\tp_cell_length_x += tl_x - Q_rint(tl_x);\n\t\t\tp_cell_length_y += tl_y - Q_rint(tl_y);\n\n\t\t\t// Don't draw the stats stuff outside the picture.\n\t\t\tif(tl_x + p_cell_length_x > pic_width || tl_y + p_cell_length_y > pic_height || x + tl_x < x || y + tl_y < y)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t//\n\t\t\t// Death stats.\n\t\t\t//\n\t\t\tif(grid->cells[row][col].teams[STATS_TEAM1].death_weight + grid->cells[row][col].teams[STATS_TEAM2].death_weight > 0)\n\t\t\t{\n\t\t\t\tweight = 0;\n\n\t\t\t\tif(style == HUD_RADAR_STATS_BOTH_TEAMS_DEATHS || style == HUD_RADAR_STATS_TEAM1_DEATHS)\n\t\t\t\t{\n\t\t\t\t\tweight = grid->cells[row][col].teams[STATS_TEAM1].death_weight;\n\t\t\t\t}\n\n\t\t\t\tif(style == HUD_RADAR_STATS_BOTH_TEAMS_DEATHS || style == HUD_RADAR_STATS_TEAM2_DEATHS)\n\t\t\t\t{\n\t\t\t\t\tweight += grid->cells[row][col].teams[STATS_TEAM2].death_weight;\n\t\t\t\t}\n\n\t\t\t\tcolor = 79;\n\t\t\t}\n\n\t\t\t//\n\t\t\t// Team stats.\n\t\t\t//\n\t\t\t{\n\t\t\t\t// No point in drawing if we have no weight.\n\t\t\t\tif(grid->cells[row][col].teams[STATS_TEAM1].weight + grid->cells[row][col].teams[STATS_TEAM2].weight <= 0\n\t\t\t\t\t&& (style == HUD_RADAR_STATS_BOTH_TEAMS_HOLD\n\t\t\t\t\t\t||\tstyle == HUD_RADAR_STATS_TEAM1_HOLD\n\t\t\t\t\t\t||\tstyle == HUD_RADAR_STATS_TEAM2_HOLD))\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Get the team with the highest weight for this cell.\n\t\t\t\tif(grid->cells[row][col].teams[STATS_TEAM1].weight > grid->cells[row][col].teams[STATS_TEAM2].weight\n\t\t\t\t\t&& (style == HUD_RADAR_STATS_BOTH_TEAMS_HOLD\n\t\t\t\t\t\t||\tstyle == HUD_RADAR_STATS_TEAM1_HOLD))\n\t\t\t\t{\n\t\t\t\t\tweight = grid->cells[row][col].teams[STATS_TEAM1].weight;\n\t\t\t\t\tcolor = stats_grid->teams[STATS_TEAM1].color;\n\t\t\t\t}\n\t\t\t\telse if(style == HUD_RADAR_STATS_BOTH_TEAMS_HOLD ||\tstyle == HUD_RADAR_STATS_TEAM2_HOLD)\n\t\t\t\t{\n\t\t\t\t\tweight = grid->cells[row][col].teams[STATS_TEAM2].weight;\n\t\t\t\t\tcolor = stats_grid->teams[STATS_TEAM2].color;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Draw the cell in the color of the team with the\n\t\t\t// biggest weight for this cell. Or draw deaths.\n\t\t\tDraw_AlphaFill(\n\t\t\t\tx + Q_rint(tl_x),\t\t\t// X.\n\t\t\t\ty + Q_rint(tl_y),\t\t\t// Y.\n\t\t\t\tQ_rint(p_cell_length_x),\t\t// Width.\n\t\t\t\tQ_rint(p_cell_length_y),\t\t// Height.\n\t\t\t\tcolor,\t\t\t\t\t\t// Color.\n\t\t\t\tweight);\t\t\t\t\t// Alpha.\n\t\t}\n\t}\n}\n\nstatic void Radar_OnChangeWeaponFilter(cvar_t *var, char *newval, qbool *cancel)\n{\n\t// Parse the weapon filter.\n\tradar_show_ssg\t\t= Utils_RegExpMatch(\"SSG|SUPERSHOTGUN|ALL\",\t\t\tnewval);\n\tradar_show_ng\t\t= Utils_RegExpMatch(\"([^S]|^)NG|NAILGUN|ALL\",\t\tnewval); // Yes very ugly, but we don't want to match SNG.\n\tradar_show_sng\t\t= Utils_RegExpMatch(\"SNG|SUPERNAILGUN|ALL\",\t\t\tnewval);\n\tradar_show_rl\t\t= Utils_RegExpMatch(\"RL|ROCKETLAUNCHER|ALL\",\t\tnewval);\n\tradar_show_gl\t\t= Utils_RegExpMatch(\"GL|GRENADELAUNCHER|ALL\",\t\tnewval);\n\tradar_show_lg\t\t= Utils_RegExpMatch(\"LG|SHAFT|LIGHTNING|ALL\",\t\tnewval);\n}\n\nstatic void Radar_OnChangeItemFilter(cvar_t *var, char *newval, qbool *cancel)\n{\n\t// Parse the item filter.\n\tradar_show_backpacks\t\t= Utils_RegExpMatch(\"BP|BACKPACK|ALL\",\t\t\t\t\tnewval);\n\tradar_show_health\t\t\t= Utils_RegExpMatch(\"HP|HEALTH|ALL\",\t\t\t\t\tnewval);\n\tradar_show_ra\t\t\t\t= Utils_RegExpMatch(\"RA|REDARMOR|ARMOR|ALL\",\t\t\tnewval);\n\tradar_show_ya\t\t\t\t= Utils_RegExpMatch(\"YA|YELLOWARMOR|ARMOR|ALL\",\t\t\tnewval);\n\tradar_show_ga\t\t\t\t= Utils_RegExpMatch(\"GA|GREENARMOR|ARMOR|ALL\",\t\t\tnewval);\n\tradar_show_rockets\t\t\t= Utils_RegExpMatch(\"ROCKETS|ROCKS|AMMO|ALL\",\t\t\tnewval);\n\tradar_show_nails\t\t\t= Utils_RegExpMatch(\"NAILS|SPIKES|AMMO|ALL\",\t\t\tnewval);\n\tradar_show_cells\t\t\t= Utils_RegExpMatch(\"CELLS|BATTERY|AMMO|ALL\",\t\t\tnewval);\n\tradar_show_shells\t\t\t= Utils_RegExpMatch(\"SHELLS|AMMO|ALL\",\t\t\t\t\tnewval);\n\tradar_show_quad\t\t\t\t= Utils_RegExpMatch(\"QUAD|POWERUPS|ALL\",\t\t\t\tnewval);\n\tradar_show_pent\t\t\t\t= Utils_RegExpMatch(\"PENT|PENTAGRAM|666|POWERUPS|ALL\",\tnewval);\n\tradar_show_ring\t\t\t\t= Utils_RegExpMatch(\"RING|INVISIBLE|EYES|POWERUPS|ALL\",\tnewval);\n\tradar_show_suit\t\t\t\t= Utils_RegExpMatch(\"SUIT|POWERUPS|ALL\",\t\t\t\tnewval);\n\tradar_show_mega\t\t\t\t= Utils_RegExpMatch(\"MH|MEGA|MEGAHEALTH|100\\\\+|ALL\",\tnewval);\n}\n\nstatic void Radar_OnChangeOtherFilter(cvar_t *var, char *newval, qbool *cancel)\n{\n\t// Parse the \"other\" filter.\n\tradar_show_nails_p\t\t\t= Utils_RegExpMatch(\"NAILS|PROJECTILES|ALL\",\tnewval);\n\tradar_show_rockets_p\t\t= Utils_RegExpMatch(\"ROCKETS|PROJECTILES|ALL\",\tnewval);\n\tradar_show_shaft_p\t\t\t= Utils_RegExpMatch(\"SHAFT|PROJECTILES|ALL\",\tnewval);\n\tradar_show_gibs\t\t\t\t= Utils_RegExpMatch(\"GIBS|ALL\",\t\t\t\t\tnewval);\n\tradar_show_explosions\t\t= Utils_RegExpMatch(\"EXPLOSIONS|ALL\",\t\t\tnewval);\n\tradar_show_teleport\t\t\t= Utils_RegExpMatch(\"TELE|ALL\",\t\t\t\t\tnewval);\n\tradar_show_shotgun\t\t\t= Utils_RegExpMatch(\"SHOTGUN|SG|BUCK|ALL\",\t\tnewval);\n}\n\nstatic void Radar_OnChangeHighlightColor(cvar_t *var, char *newval, qbool *cancel)\n{\n\tchar *new_color;\n\tchar buf[MAX_COM_TOKEN];\n\n\t// Translate a colors name to RGB values.\n\tnew_color = ColorNameToRGBString(newval);\n\n\t// Parse the colors.\n\t//color = StringToRGB(new_color);\n\tstrlcpy(buf,new_color,sizeof(buf));\n\tmemcpy(hud_radar_highlight_color, StringToRGB(buf), sizeof(byte) * 4);\n\n\t// Set the cvar to contain the new color string\n\t// (if the user entered \"red\" it will be \"255 0 0\").\n\tCvar_Set(var, new_color);\n}\n\nstatic void Radar_DrawEntities(int x, int y, float scale, float player_size, int show_hold_areas, float text_scale, qbool simple_items, qbool proportional)\n{\n\tint i;\n\n\t// Entities (weapons and such). cl_main.c\n\textern visentlist_t cl_visents;\n\n\t// Go through all the entities and draw the ones we're supposed to.\n\tfor (i = 0; i < cl_visents.count; i++) {\n\t\tentity_t* currententity = &cl_visents.list[i].ent;\n\t\ttexture_ref simpletexture;\n\t\tqbool simple_valid;\n\n\t\tint entity_q_x = 0;\n\t\tint entity_q_y = 0;\n\t\tint entity_p_x = 0;\n\t\tint entity_p_y = 0;\n\n\t\t// Find simple texture for entity\n\t\tif (currententity->skinnum < 0 || currententity->skinnum >= MAX_SIMPLE_TEXTURES) {\n\t\t\tsimpletexture = currententity->model->simpletexture[0]; // ah...\n\t\t}\n\t\telse {\n\t\t\tsimpletexture = currententity->model->simpletexture[currententity->skinnum];\n\t\t}\n\t\tsimple_valid = R_TextureReferenceIsValid(simpletexture);\n\n\t\t// Get quake coordinates (times 8 to get them in the same format as .locs).\n\t\tentity_q_x = currententity->origin[0] * 8;\n\t\tentity_q_y = currententity->origin[1] * 8;\n\n\t\t// Convert from quake coordiantes -> pixel coordinates.\n\t\tentity_p_x = x + Q_rint((map_x_slope*entity_q_x + map_x_intercept) * scale);\n\t\tentity_p_y = y + Q_rint((map_y_slope*entity_q_y + map_y_intercept) * scale);\n\n\t\t// TODO: Replace all model name comparison below with MOD_HINT's instead for less comparisons (create new ones in Mod_LoadAliasModel() in r_model.c and gl_model.c/.h for the ones that don't have one already).\n\n\t\t//\n\t\t// Powerups.\n\t\t//\n\n\t\tif (radar_show_pent && currententity->model->modhint == MOD_PENT) {\n\t\t\t// Pentagram.\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SColoredStringBasic(entity_p_x, entity_p_y, \"&cf00P\", 0, text_scale, proportional);\n\t\t\t}\n\t\t}\n\t\telse if (radar_show_quad && currententity->model->modhint == MOD_QUAD) {\n\t\t\t// Quad.\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SColoredStringBasic(entity_p_x, entity_p_y, \"&c0ffQ\", 0, text_scale, proportional);\n\t\t\t}\n\t\t}\n\t\telse if (radar_show_ring && currententity->model->modhint == MOD_RING) {\n\t\t\t// Ring.\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SColoredStringBasic(entity_p_x, entity_p_y, \"&cff0R\", 0, text_scale, proportional);\n\t\t\t}\n\t\t}\n\t\telse if (radar_show_suit && currententity->model->modhint == MOD_SUIT) {\n\t\t\t// Suit.\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SColoredStringBasic(entity_p_x, entity_p_y, \"&c0f0S\", 0, text_scale, proportional);\n\t\t\t}\n\t\t}\n\n\t\t//\n\t\t// Show RL, LG and backpacks.\n\t\t//\n\t\tif (radar_show_rl && currententity->model->modhint == MOD_ROCKETLAUNCHER) {\n\t\t\t// RL.\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SString(entity_p_x - (2 * 8) / 2, entity_p_y - 4, \"RL\", text_scale, proportional);\n\t\t\t}\n\t\t}\n\t\telse if (radar_show_lg && currententity->model->modhint == MOD_LIGHTNINGGUN) {\n\t\t\t// LG.\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SString(entity_p_x - (2 * 8) / 2, entity_p_y - 4, \"LG\", text_scale, proportional);\n\t\t\t}\n\t\t}\n\t\telse if (radar_show_backpacks && currententity->model->modhint == MOD_BACKPACK) {\n\t\t\t// Back packs.\n\t\t\tfloat back_pack_size = 0;\n\n\t\t\tback_pack_size = max(player_size * 0.5, 0.05);\n\n\t\t\tDraw_AlphaCircleFill(entity_p_x, entity_p_y, back_pack_size, 114, 1);\n\t\t\tDraw_AlphaCircleOutline(entity_p_x, entity_p_y, back_pack_size, 1.0, 0, 1);\n\t\t}\n\n\t\tif (currententity->model->modhint == MOD_ARMOR) {\n\t\t\t//\n\t\t\t// Show armors.\n\t\t\t//\n\t\t\tif (radar_show_ga && currententity->skinnum == HUD_RADAR_GA) {\n\t\t\t\t// GA.\n\t\t\t\tif (simple_valid && simple_items) {\n\t\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tDraw_AlphaCircleFill(entity_p_x, entity_p_y, 3.0, 178, 1.0);\n\t\t\t\t\tDraw_AlphaCircleOutline(entity_p_x, entity_p_y, 3.0, 1.0, 0, 1.0);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (radar_show_ya && currententity->skinnum == HUD_RADAR_YA) {\n\t\t\t\t// YA.\n\t\t\t\tif (simple_valid && simple_items) {\n\t\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tDraw_AlphaCircleFill(entity_p_x, entity_p_y, 3.0, 192, 1.0);\n\t\t\t\t\tDraw_AlphaCircleOutline(entity_p_x, entity_p_y, 3.0, 1.0, 0, 1.0);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (radar_show_ra && currententity->skinnum == HUD_RADAR_RA) {\n\t\t\t\t// RA.\n\t\t\t\tif (simple_valid && simple_items) {\n\t\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tDraw_AlphaCircleFill(entity_p_x, entity_p_y, 3.0, 251, 1.0);\n\t\t\t\t\tDraw_AlphaCircleOutline(entity_p_x, entity_p_y, 3.0, 1.0, 0, 1.0);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (radar_show_mega && currententity->model->modhint == MOD_MEGAHEALTH) {\n\t\t\t//\n\t\t\t// Show megahealth.\n\t\t\t//\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Draw a red border around the cross.\n\t\t\t\tDraw_AlphaRectangleRGB(entity_p_x - 3, entity_p_y - 3, 8, 8, 1, false, RGBA_TO_COLOR(200, 0, 0, 200));\n\n\t\t\t\t// Draw a black outline cross.\n\t\t\t\tDraw_AlphaFill(entity_p_x - 3, entity_p_y - 1, 8, 4, 0, 1);\n\t\t\t\tDraw_AlphaFill(entity_p_x - 1, entity_p_y - 3, 4, 8, 0, 1);\n\n\t\t\t\t// Draw a 2 pixel cross.\n\t\t\t\tDraw_AlphaFill(entity_p_x - 2, entity_p_y, 6, 2, 79, 1);\n\t\t\t\tDraw_AlphaFill(entity_p_x, entity_p_y - 2, 2, 6, 79, 1);\n\t\t\t}\n\t\t}\n\n\t\tif (radar_show_ssg && !strcmp(currententity->model->name, \"progs/g_shot.mdl\")) {\n\t\t\t// SSG.\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SString(entity_p_x - (3 * 8) / 2, entity_p_y - 4, \"SSG\", text_scale, proportional);\n\t\t\t}\n\t\t}\n\t\telse if (radar_show_ng && !strcmp(currententity->model->name, \"progs/g_nail.mdl\")) {\n\t\t\t// NG.\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SString(entity_p_x - (2 * 8) / 2, entity_p_y - 4, \"NG\", text_scale, proportional);\n\t\t\t}\n\t\t}\n\t\telse if (radar_show_sng && !strcmp(currententity->model->name, \"progs/g_nail2.mdl\")) {\n\t\t\t// SNG.\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SString(entity_p_x - (3 * 8) / 2, entity_p_y - 4, \"SNG\", text_scale, proportional);\n\t\t\t}\n\t\t}\n\t\telse if (radar_show_gl && currententity->model->modhint == MOD_GRENADELAUNCHER) {\n\t\t\t// GL.\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SString(entity_p_x - (2 * 8) / 2, entity_p_y - 4, \"GL\", text_scale, proportional);\n\t\t\t}\n\t\t}\n\n\t\tif (radar_show_gibs && currententity->model->modhint == MOD_GIB) {\n\t\t\t//\n\t\t\t// Gibs.\n\t\t\t//\n\n\t\t\tDraw_AlphaCircleFill(entity_p_x, entity_p_y, 2.0, 251, 1);\n\t\t}\n\n\t\tif (radar_show_health\n\t\t\t&& (!strcmp(currententity->model->name, \"maps/b_bh25.bsp\")\n\t\t\t|| !strcmp(currententity->model->name, \"maps/b_bh10.bsp\"))) {\n\t\t\t//\n\t\t\t// Health.\n\t\t\t//\n\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Draw a black outline cross.\n\t\t\t\tDraw_AlphaFill(entity_p_x - 3, entity_p_y - 1, 7, 3, 0, 1);\n\t\t\t\tDraw_AlphaFill(entity_p_x - 1, entity_p_y - 3, 3, 7, 0, 1);\n\n\t\t\t\t// Draw a cross.\n\t\t\t\tDraw_AlphaFill(entity_p_x - 2, entity_p_y, 5, 1, 79, 1);\n\t\t\t\tDraw_AlphaFill(entity_p_x, entity_p_y - 2, 1, 5, 79, 1);\n\t\t\t}\n\t\t}\n\n\t\t//\n\t\t// Ammo.\n\t\t//\n\t\tif (radar_show_rockets\n\t\t\t&& (!strcmp(currententity->model->name, \"maps/b_rock0.bsp\")\n\t\t\t|| !strcmp(currententity->model->name, \"maps/b_rock1.bsp\"))) {\n\t\t\t//\n\t\t\t// Rockets.\n\t\t\t//\n\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Draw a black outline.\n\t\t\t\tDraw_AlphaFill(entity_p_x - 1, entity_p_y - 6, 3, 5, 0, 1);\n\t\t\t\tDraw_AlphaFill(entity_p_x - 2, entity_p_y - 1, 5, 5, 0, 1);\n\n\t\t\t\t// The brown rocket.\n\t\t\t\tDraw_AlphaFill(entity_p_x, entity_p_y - 5, 1, 5, 120, 1);\n\t\t\t\tDraw_AlphaFill(entity_p_x - 1, entity_p_y, 1, 3, 120, 1);\n\t\t\t\tDraw_AlphaFill(entity_p_x + 1, entity_p_y, 1, 3, 120, 1);\n\t\t\t}\n\t\t}\n\n\t\tif (radar_show_cells\n\t\t\t&& (!strcmp(currententity->model->name, \"maps/b_batt0.bsp\")\n\t\t\t|| !strcmp(currententity->model->name, \"maps/b_batt1.bsp\"))) {\n\t\t\t//\n\t\t\t// Cells.\n\t\t\t//\n\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Draw a black outline.\n\t\t\t\tDraw_AlphaLine(entity_p_x - 3, entity_p_y, entity_p_x + 4, entity_p_y - 5, 3, 0, 1);\n\t\t\t\tDraw_AlphaLine(entity_p_x - 3, entity_p_y, entity_p_x + 3, entity_p_y, 3, 0, 1);\n\t\t\t\tDraw_AlphaLine(entity_p_x + 3, entity_p_y, entity_p_x - 3, entity_p_y + 4, 3, 0, 1);\n\n\t\t\t\t// Draw a yellow lightning!\n\t\t\t\tDraw_AlphaLine(entity_p_x - 2, entity_p_y, entity_p_x + 3, entity_p_y - 4, 1, 111, 1);\n\t\t\t\tDraw_AlphaLine(entity_p_x - 2, entity_p_y, entity_p_x + 2, entity_p_y, 1, 111, 1);\n\t\t\t\tDraw_AlphaLine(entity_p_x + 2, entity_p_y, entity_p_x - 2, entity_p_y + 3, 1, 111, 1);\n\t\t\t}\n\t\t}\n\n\t\tif (radar_show_nails\n\t\t\t&& (!strcmp(currententity->model->name, \"maps/b_nail0.bsp\")\n\t\t\t|| !strcmp(currententity->model->name, \"maps/b_nail1.bsp\"))) {\n\t\t\t//\n\t\t\t// Nails.\n\t\t\t//\n\n\t\t\t// Draw a black outline.\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_AlphaFill(entity_p_x - 3, entity_p_y - 3, 7, 3, 0, 1);\n\t\t\t\tDraw_AlphaFill(entity_p_x - 2, entity_p_y - 2, 5, 3, 0, 0.5);\n\t\t\t\tDraw_AlphaFill(entity_p_x - 1, entity_p_y, 3, 3, 0, 1);\n\t\t\t\tDraw_AlphaFill(entity_p_x - 1, entity_p_y + 3, 1, 1, 0, 0.5);\n\t\t\t\tDraw_AlphaFill(entity_p_x + 1, entity_p_y + 3, 1, 1, 0, 0.5);\n\t\t\t\tDraw_AlphaFill(entity_p_x, entity_p_y + 4, 1, 1, 0, 1);\n\n\t\t\t\tDraw_AlphaFill(entity_p_x - 2, entity_p_y - 2, 5, 1, 6, 1);\n\t\t\t\tDraw_AlphaFill(entity_p_x - 1, entity_p_y - 1, 3, 1, 6, 0.5);\n\t\t\t\tDraw_AlphaFill(entity_p_x, entity_p_y, 1, 4, 6, 1);\n\t\t\t}\n\t\t}\n\n\t\tif (radar_show_shells\n\t\t\t&& (!strcmp(currententity->model->name, \"maps/b_shell0.bsp\")\n\t\t\t|| !strcmp(currententity->model->name, \"maps/b_shell1.bsp\"))) {\n\t\t\t//\n\t\t\t// Shells.\n\t\t\t//\n\n\t\t\tif (simple_valid && simple_items) {\n\t\t\t\tDraw_2dAlphaTexture(entity_p_x - 3.0, entity_p_y - 3.0, 6.0, 6.0, simpletexture, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Draw a black outline.\n\t\t\t\tDraw_AlphaFill(entity_p_x - 2, entity_p_y - 3, 5, 9, 0, 1);\n\n\t\t\t\t// Draw 2 shotgun shells.\n\t\t\t\tDraw_AlphaFill(entity_p_x - 1, entity_p_y - 2, 1, 4, 73, 1);\n\t\t\t\tDraw_AlphaFill(entity_p_x - 1, entity_p_y - 2 + 5, 1, 2, 104, 1);\n\n\t\t\t\tDraw_AlphaFill(entity_p_x + 1, entity_p_y - 2, 1, 4, 73, 1);\n\t\t\t\tDraw_AlphaFill(entity_p_x + 1, entity_p_y - 2 + 5, 1, 2, 104, 1);\n\t\t\t}\n\t\t}\n\n\t\t//\n\t\t// Show projectiles (rockets, grenades, nails, shaft).\n\t\t//\n\n\t\tif (radar_show_nails_p && currententity->model->modhint == MOD_SPIKE) {\n\t\t\t//\n\t\t\t// Spikes from SNG and NG.\n\t\t\t//\n\n\t\t\tDraw_AlphaFill(entity_p_x, entity_p_y, 1, 1, 254, 1);\n\t\t}\n\t\telse if (radar_show_rockets_p &&\n\t\t\t(currententity->model->modhint == MOD_ROCKET || currententity->model->modhint == MOD_GRENADE)\n\t\t\t) {\n\t\t\t//\n\t\t\t// Rockets and grenades.\n\t\t\t//\n\n\t\t\tfloat entity_angle = 0;\n\t\t\tint x_line_end = 0;\n\t\t\tint y_line_end = 0;\n\n\t\t\t// Get the entity angle in radians.\n\t\t\tentity_angle = DEG2RAD(currententity->angles[1]);\n\n\t\t\tx_line_end = entity_p_x + 5 * cos(entity_angle) * scale;\n\t\t\ty_line_end = entity_p_y - 5 * sin(entity_angle) * scale;\n\n\t\t\t// Draw the rocket/grenade showing it's angle also.\n\t\t\tDraw_AlphaLine(entity_p_x, entity_p_y, x_line_end, y_line_end, 1.0, 254, 1);\n\t\t}\n\t\telse if (radar_show_shaft_p && currententity->model->modhint == MOD_THUNDERBOLT) {\n\t\t\t//\n\t\t\t// Shaft beam.\n\t\t\t//\n\n\t\t\tfloat entity_angle = 0;\n\t\t\tfloat shaft_length = 0;\n\t\t\tfloat x_line_end = 0;\n\t\t\tfloat y_line_end = 0;\n\n\t\t\t// Get the length and angle of the shaft.\n\t\t\tshaft_length = currententity->model->maxs[1];\n\t\t\tentity_angle = (currententity->angles[1] * M_PI) / 180;\n\n\t\t\t// Calculate where the shaft beam's ending point.\n\t\t\tx_line_end = entity_p_x + shaft_length * cos(entity_angle);\n\t\t\ty_line_end = entity_p_y - shaft_length * sin(entity_angle);\n\n\t\t\t// Draw the shaft beam.\n\t\t\tDraw_AlphaLine(entity_p_x, entity_p_y, x_line_end, y_line_end, 1.0, 254, 1);\n\t\t}\n\t}\n\n\t// Show corpses (cl_deadbodyfilter might mean they aren't in visible entity list)\n\t{\n\t\tint entity_q_x = 0;\n\t\tint entity_q_y = 0;\n\t\tint entity_p_x = 0;\n\t\tint entity_p_y = 0;\n\t\tpacket_entities_t *pack = &cl.frames[cl.validsequence & UPDATE_MASK].packet_entities;\n\t\tchar buffer[MAX_SCOREBOARDNAME + 8];\n\n\t\tfor (i = 0; i < pack->num_entities; ++i) {\n\t\t\tentity_state_t *state = &pack->entities[i];\n\n\t\t\t// must be body or gibbed head\n\t\t\tif (state->modelindex != cl_modelindices[mi_player] && state->modelindex != cl_modelindices[mi_h_player]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (state->colormap <= 0 || state->colormap > MAX_CLIENTS) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Get quake coordinates (times 8 to get them in the same format as .locs).\n\t\t\tentity_q_x = state->origin[0] * 8;\n\t\t\tentity_q_y = state->origin[1] * 8;\n\n\t\t\t// Convert from quake coordiantes -> pixel coordinates.\n\t\t\tentity_p_x = x + Q_rint((map_x_slope * entity_q_x + map_x_intercept) * scale);\n\t\t\tentity_p_y = y + Q_rint((map_y_slope * entity_q_y + map_y_intercept) * scale);\n\n\t\t\t{\n\t\t\t\tint player_color = Sbar_BottomColor(&cl.players[state->colormap - 1]);\n\n\t\t\t\tbyte red = host_basepal[player_color * 3];\n\t\t\t\tbyte green = host_basepal[player_color * 3 + 1];\n\t\t\t\tbyte blue = host_basepal[player_color * 3 + 2];\n\n\t\t\t\tsnprintf(buffer, sizeof(buffer), \"&c%x%x%x%s\",\n\t\t\t\t\t(unsigned int)(red / 16),\n\t\t\t\t\t(unsigned int)(green / 16),\n\t\t\t\t\t(unsigned int)(blue / 16),\n\t\t\t\t\t\"X\"\n\t\t\t\t);\n\n\t\t\t\tDraw_SColoredStringBasic(entity_p_x - 4, entity_p_y - 4, buffer, 0, 0.75f, proportional);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Draw a circle around \"hold areas\", the grid cells within this circle\n\t// are the ones that are counted for that particular hold area. The team\n\t// that has the most percentage of these cells is considered to hold that area.\n\tif (show_hold_areas && stats_important_ents != NULL && stats_important_ents->list != NULL) {\n\t\tint entity_p_x = 0;\n\t\tint entity_p_y = 0;\n\n\t\tfor (i = 0; i < stats_important_ents->count; i++) {\n\t\t\tentity_p_x = x + Q_rint((map_x_slope * 8 * stats_important_ents->list[i].origin[0] + map_x_intercept) * scale);\n\t\t\tentity_p_y = y + Q_rint((map_y_slope * 8 * stats_important_ents->list[i].origin[1] + map_y_intercept) * scale);\n\n\t\t\tDraw_SColoredStringBasic(entity_p_x - Draw_StringLength(stats_important_ents->list[i].name, -1, text_scale, proportional) / 2.0, entity_p_y - 4,\n\t\t\t\tva(\"&c55f%s\", stats_important_ents->list[i].name), 0, text_scale, proportional);\n\n\t\t\tDraw_AlphaCircleOutline(entity_p_x, entity_p_y, map_x_slope * 8 * stats_important_ents->hold_radius * scale, 1.0, 15, 0.2);\n\t\t}\n\t}\n\n\t//\n\t// Draw temp entities (explosions, blood, teleport effects).\n\t//\n\tfor (i = 0; i < MAX_TEMP_ENTITIES; i++) {\n\t\tfloat time_diff = 0.0;\n\n\t\tint entity_q_x = 0;\n\t\tint entity_q_y = 0;\n\t\tint entity_p_x = 0;\n\t\tint entity_p_y = 0;\n\n\t\t// Get the time since the entity spawned.\n\t\tif (cls.demoplayback) {\n\t\t\ttime_diff = cls.demotime - temp_entities.list[i].time;\n\t\t}\n\t\telse {\n\t\t\ttime_diff = cls.realtime - temp_entities.list[i].time;\n\t\t}\n\n\t\t// Don't show temp entities for long.\n\t\tif (time_diff < 0.25) {\n\t\t\tfloat radius = 0.0;\n\t\t\tradius = (time_diff < 0.125) ? (time_diff * 32.0) : (time_diff * 32.0) - time_diff;\n\t\t\tradius *= scale;\n\t\t\tradius = min(max(radius, 0), 200);\n\n\t\t\t// Get quake coordinates (times 8 to get them in the same format as .locs).\n\t\t\tentity_q_x = temp_entities.list[i].pos[0] * 8;\n\t\t\tentity_q_y = temp_entities.list[i].pos[1] * 8;\n\n\t\t\tentity_p_x = x + Q_rint((map_x_slope*entity_q_x + map_x_intercept) * scale);\n\t\t\tentity_p_y = y + Q_rint((map_y_slope*entity_q_y + map_y_intercept) * scale);\n\n\t\t\tif (radar_show_explosions\n\t\t\t\t&& (temp_entities.list[i].type == TE_EXPLOSION\n\t\t\t\t|| temp_entities.list[i].type == TE_TAREXPLOSION)) {\n\t\t\t\t//\n\t\t\t\t// Explosions.\n\t\t\t\t//\n\n\t\t\t\tDraw_AlphaCircleFill(entity_p_x, entity_p_y, radius, 235, 0.8);\n\t\t\t}\n\t\t\telse if (radar_show_teleport && temp_entities.list[i].type == TE_TELEPORT) {\n\t\t\t\t//\n\t\t\t\t// Teleport effect.\n\t\t\t\t//\n\n\t\t\t\tradius *= 1.5;\n\t\t\t\tDraw_AlphaCircleFill(entity_p_x, entity_p_y, radius, 244, 0.8);\n\t\t\t}\n\t\t\telse if (radar_show_shotgun && temp_entities.list[i].type == TE_GUNSHOT) {\n\t\t\t\t//\n\t\t\t\t// Shotgun fire.\n\t\t\t\t//\n\n#define SHOTGUN_SPREAD 10\n\t\t\t\tint spread_x = 0;\n\t\t\t\tint spread_y = 0;\n\t\t\t\tint n = 0;\n\n\t\t\t\tfor (n = 0; n < 10; n++) {\n\t\t\t\t\tspread_x = (int)(rand() / (((double)RAND_MAX + 1) / SHOTGUN_SPREAD));\n\t\t\t\t\tspread_y = (int)(rand() / (((double)RAND_MAX + 1) / SHOTGUN_SPREAD));\n\n\t\t\t\t\tDraw_AlphaFill(entity_p_x + spread_x - (SHOTGUN_SPREAD / 2), entity_p_y + spread_y - (SHOTGUN_SPREAD / 2), 1, 1, 8, 0.9);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Radar_DrawPlayers(int x, int y, int width, int height, float scale,\n\tfloat show_height, float show_powerups,\n\tfloat player_size, float show_names,\n\tfloat fade_players, float highlight,\n\tchar *highlight_color, float text_scale, qbool team_colours_for_name, qbool proportional)\n{\n\tint i;\n\tplayer_state_t *state;\n\tplayer_info_t *info;\n\n\t// Get player state so we can know where he is (or on rare occassions, she).\n\tstate = cl.frames[cl.parsecount & UPDATE_MASK].playerstate;\n\n\t// Get the info for the player.\n\tinfo = cl.players;\n\n\t//\n\t// Draw the players.\n\t//\n\tfor (i = 0; i < MAX_CLIENTS; i++, info++, state++)\n\t{\n\t\t// Players quake coordinates\n\t\t// (these are multiplied by 8, since the conversion formula was\n\t\t// calculated using the coordinates in a .loc-file, which are in\n\t\t// the format quake-coordainte*8).\n\t\tint player_q_x = 0;\n\t\tint player_q_y = 0;\n\n\t\t// The height of the player.\n\t\tfloat player_z = 1.0;\n\t\tfloat player_z_relative = 1.0;\n\n\t\t// Players pixel coordinates.\n\t\tint player_p_x = 0;\n\t\tint player_p_y = 0;\n\n\t\t// Used for drawing the the direction the\n\t\t// player is looking at.\n\t\tfloat player_angle = 0;\n\t\tint x_line_start = 0;\n\t\tint y_line_start = 0;\n\t\tint x_line_end = 0;\n\t\tint y_line_end = 0;\n\n\t\t// Color and opacity of the player.\n\t\tint player_color = 0;\n\t\tfloat player_alpha = 1.0;\n\t\tqbool player_cross = false;\n\t\tfloat player_outline = 1.0f;\n\t\tqbool has_weapon = false;\n\n\t\t// Make sure we're not drawing any ghosts.\n\t\tif(!info->name[0])\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (state->messagenum == cl.parsecount)\n\t\t{\n\t\t\t// TODO: Implement lerping to get smoother drawing.\n\n\t\t\t// Get the quake coordinates. Multiply by 8 since\n\t\t\t// the conversion formula has been calculated using\n\t\t\t// a .loc-file which is in that format.\n\t\t\tplayer_q_x = state->origin[0]*8;\n\t\t\tplayer_q_y = state->origin[1]*8;\n\n\t\t\t// Get the players view angle.\n\t\t\tplayer_angle = cls.demoplayback ? state->viewangles[1] : cl.simangles[1];\n\n\t\t\t// Convert from quake coordiantes -> pixel coordinates.\n\t\t\tplayer_p_x = Q_rint((map_x_slope*player_q_x + map_x_intercept) * scale);\n\t\t\tplayer_p_y = Q_rint((map_y_slope*player_q_y + map_y_intercept) * scale);\n\n\t\t\tplayer_color = Sbar_BottomColor(info);\n\n\t\t\thas_weapon = (info->stats[STAT_ITEMS] & (IT_ROCKET_LAUNCHER | IT_LIGHTNING));\n\n\t\t\t// Calculate the height of the player.\n\t\t\tif(show_height)\n\t\t\t{\n\t\t\t\tplayer_z = state->origin[2];\n\t\t\t\tplayer_z += (player_z >= 0) ? fabs(cl.worldmodel->mins[2]) : fabs(cl.worldmodel->maxs[2]);\n\t\t\t\tplayer_z_relative = min(fabs(player_z / map_height_diff), 1.0);\n\t\t\t\tplayer_z_relative = max(player_z_relative, 0.2);\n\t\t\t}\n\n\t\t\t// Make the players fade out as they get less armor/health.\n\t\t\tif (fade_players == 1 || fade_players == 3) {\n\t\t\t\tint stack = bound(0, SCR_HUD_TotalStrength(info->stats[STAT_HEALTH], info->stats[STAT_ARMOR], SCR_HUD_ArmorType(info->stats[STAT_ITEMS])), 220);\n\n\t\t\t\t// Don't let the players get completly transparent so add 0.2 to the final value.\n\t\t\t\tplayer_alpha = bound(0, 0.3f + (stack / 315.0f), 1.0f);\n\t\t\t}\n\t\t\telse if (fade_players == 2) {\n\t\t\t\t// Players are faded if they don't have weapon\n\t\t\t\tplayer_alpha = has_weapon ? 1.0f : 0.3f;\n\t\t\t}\n\n\t\t\t// Turn dead people into crosses.\n\t\t\tif (info->stats[STAT_HEALTH] <= 0) {\n\t\t\t\tplayer_alpha = 1.0;\n\t\t\t\tplayer_cross = true;\n\t\t\t}\n\n\t\t\tif (fade_players == 1 && has_weapon) {\n\t\t\t\tplayer_outline = 2.5f;\n\t\t\t}\n\t\t\telse if (fade_players == 2) {\n\t\t\t\tfloat armorType = SCR_HUD_ArmorType(info->stats[STAT_ITEMS]);\n\t\t\t\tint stack = bound(0, SCR_HUD_TotalStrength(info->stats[STAT_HEALTH], info->stats[STAT_ARMOR], armorType), 360);\n\n\t\t\t\tplayer_outline = stack < 80 ? 0.0f : stack < 110 ? 1.0f : stack < 200 ? 2.0f : stack < 300 ? 3.0f : 4.0f;\n\t\t\t}\n\n\t\t\t// Draw a ring around players with powerups if it's enabled.\n\t\t\tif (show_powerups) {\n\t\t\t\tif (info->stats[STAT_ITEMS] & IT_INVISIBILITY) {\n\t\t\t\t\tDraw_AlphaCircleFill (x + player_p_x, y + player_p_y, player_size*2*player_z_relative, 161, 0.2);\n\t\t\t\t}\n\n\t\t\t\tif (info->stats[STAT_ITEMS] & IT_INVULNERABILITY) {\n\t\t\t\t\tDraw_AlphaCircleFill (x + player_p_x, y + player_p_y, player_size*2*player_z_relative, 79, 0.5);\n\t\t\t\t}\n\n\t\t\t\tif (info->stats[STAT_ITEMS] & IT_QUAD) {\n\t\t\t\t\tDraw_AlphaCircleFill (x + player_p_x, y + player_p_y, player_size*2*player_z_relative, 244, 0.2);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Draw a circle around the tracked player.\n\t\t\tif (highlight != HUD_RADAR_HIGHLIGHT_NONE && Cam_TrackNum() >= 0 && info->userid == cl.players[Cam_TrackNum()].userid)\n\t\t\t{\n\t\t\t\tcolor_t higlight_color = RGBAVECT_TO_COLOR(hud_radar_highlight_color);\n\n\t\t\t\t// Draw the highlight.\n\t\t\t\tswitch ((int)highlight)\n\t\t\t\t{\n\t\t\t\tcase HUD_RADAR_HIGHLIGHT_CROSS_CORNERS :\n\t\t\t\t{\n\t\t\t\t\t// Top left\n\t\t\t\t\tDraw_AlphaLineRGB (x, y, x + player_p_x, y + player_p_y, 2, higlight_color);\n\n\t\t\t\t\t// Top right.\n\t\t\t\t\tDraw_AlphaLineRGB (x + width, y, x + player_p_x, y + player_p_y, 2, higlight_color);\n\n\t\t\t\t\t// Bottom left.\n\t\t\t\t\tDraw_AlphaLineRGB (x, y + height, x + player_p_x, y + player_p_y, 2, higlight_color);\n\n\t\t\t\t\t// Bottom right.\n\t\t\t\t\tDraw_AlphaLineRGB (x + width, y + height, x + player_p_x, y + player_p_y, 2, higlight_color);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase HUD_RADAR_HIGHLIGHT_ARROW_TOP :\n\t\t\t\t{\n\t\t\t\t\t// Top center.\n\t\t\t\t\tDraw_AlphaLineRGB (x + width / 2, y, x + player_p_x, y + player_p_y, 2, higlight_color);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase HUD_RADAR_HIGHLIGHT_ARROW_CENTER :\n\t\t\t\t{\n\t\t\t\t\t// Center.\n\t\t\t\t\tDraw_AlphaLineRGB (x + width / 2, y + height / 2, x + player_p_x, y + player_p_y, 2, higlight_color);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase HUD_RADAR_HIGHLIGHT_ARROW_BOTTOM :\n\t\t\t\t{\n\t\t\t\t\t// Bottom center.\n\t\t\t\t\tDraw_AlphaLineRGB (x + width / 2, y + height, x + player_p_x, y + player_p_y, 2, higlight_color);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase HUD_RADAR_HIGHLIGHT_FIXED_CIRCLE :\n\t\t\t\t{\n\t\t\t\t\tDraw_AlphaCircleRGB (x + player_p_x, y + player_p_y, player_size * 1.5, 1.0, true, higlight_color);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase HUD_RADAR_HIGHLIGHT_CIRCLE :\n\t\t\t\t{\n\t\t\t\t\tDraw_AlphaCircleRGB (x + player_p_x, y + player_p_y, player_size * player_z_relative * 2.0, 1.0, true, higlight_color);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase HUD_RADAR_HIGHLIGHT_FIXED_OUTLINE :\n\t\t\t\t{\n\t\t\t\t\tDraw_AlphaCircleOutlineRGB (x + player_p_x, y + player_p_y, player_size * 1.5, 1.0, higlight_color);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase HUD_RADAR_HIGHLIGHT_OUTLINE :\n\t\t\t\t{\n\t\t\t\t\tDraw_AlphaCircleOutlineRGB (x + player_p_x, y + player_p_y, player_size * player_z_relative * 2.0, 1.0, higlight_color);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase HUD_RADAR_HIGHLIGHT_TEXT_ONLY :\n\t\t\t\tdefault :\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Draw the actual player and a line showing what direction the player is looking in.\n\t\t\tif (!player_cross) {\n\t\t\t\tfloat lhs_angle = DEG2RAD(player_angle - 45);\n\t\t\t\tfloat rhs_angle = DEG2RAD(player_angle + 45);\n\n\t\t\t\tqbool highlight_player = highlight == HUD_RADAR_HIGHLIGHT_BRIGHT_VIEW && Cam_TrackNum() >= 0 && info->userid == cl.players[Cam_TrackNum()].userid;\n\n\t\t\t\tx_line_start = x + player_p_x;\n\t\t\t\ty_line_start = y + player_p_y;\n\n\t\t\t\tDraw_AlphaPieSlice(x_line_start, y_line_start, player_size * 2 * player_z_relative + 1, lhs_angle, rhs_angle, 1.0f, true, 15, highlight_player ? 0.4f : 0.1f);\n\n\t\t\t\t// Draw the player on the map.\n\t\t\t\tDraw_AlphaCircleFill(x + player_p_x, y + player_p_y, player_size * player_z_relative + player_outline, 0, 1.0f);\n\t\t\t\tDraw_AlphaCircleFill(x + player_p_x, y + player_p_y, player_size * player_z_relative, player_color, 1.0f);\n\t\t\t\tDraw_AlphaCircleFill(x + player_p_x, y + player_p_y, player_size * player_z_relative, 0, 1.0f - player_alpha);\n\n\t\t\t\tif (fade_players == 3 && has_weapon) {\n\t\t\t\t\tDraw_AlphaCircleFill(x + player_p_x, y + player_p_y, player_size * player_z_relative * 0.5f, 0, 1.0f);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfloat size = (player_size * player_z_relative);\n\n\t\t\t\tx_line_start = x + player_p_x - size;\n\t\t\t\ty_line_start = y + player_p_y - size;\n\n\t\t\t\tx_line_end = x_line_start + 2 * size;\n\t\t\t\ty_line_end = y_line_start + 2 * size;\n\n\t\t\t\tDraw_AlphaLine(x_line_start, y_line_start, x_line_end, y_line_end, 3.0, player_color, player_alpha);\n\t\t\t\tDraw_AlphaLine(x_line_start, y_line_end, x_line_end, y_line_start, 3.0, player_color, player_alpha);\n\t\t\t}\n\n\t\t\t// Draw the players name.\n\t\t\tif (show_names)\n\t\t\t{\n\t\t\t\tint name_x = 0;\n\t\t\t\tint name_y = 0;\n\t\t\t\tint text_length = Draw_StringLength(info->name, -1, text_scale, proportional);\n\n\t\t\t\tname_x = x + player_p_x;\n\t\t\t\tname_y = y + player_p_y;\n\n\t\t\t\t// Make sure we're not too far right.\n\t\t\t\tif (name_x + text_length > x + width) {\n\t\t\t\t\tname_x -= (name_x + text_length - (x + width));\n\t\t\t\t}\n\n\t\t\t\t// Make sure we're not outside the radar to the left.\n\t\t\t\tname_x = max(name_x, x);\n\n\t\t\t\t// Draw the name.\n\t\t\t\tif (highlight >= HUD_RADAR_HIGHLIGHT_TEXT_ONLY && info->userid == cl.players[Cam_TrackNum()].userid) {\n\t\t\t\t\tchar buffer[MAX_SCOREBOARDNAME + 8];\n\n\t\t\t\t\tsnprintf(buffer, sizeof(buffer), \"&c%x%x%x%s\",\n\t\t\t\t\t\t(unsigned int)(hud_radar_highlight_color[0] / 16),\n\t\t\t\t\t\t(unsigned int)(hud_radar_highlight_color[1] / 16),\n\t\t\t\t\t\t(unsigned int)(hud_radar_highlight_color[2] / 16),\n\t\t\t\t\t\tinfo->name\n\t\t\t\t\t);\n\n\t\t\t\t\t// Draw the tracked players name in the user specified color.\n\t\t\t\t\tDraw_SColoredStringBasic(name_x, name_y, buffer, 0, text_scale, proportional);\n\t\t\t\t}\n\t\t\t\telse if (team_colours_for_name) {\n\t\t\t\t\tchar buffer[MAX_SCOREBOARDNAME + 8];\n\n\t\t\t\t\tbyte red = host_basepal[player_color * 3];\n\t\t\t\t\tbyte green = host_basepal[player_color * 3 + 1];\n\t\t\t\t\tbyte blue = host_basepal[player_color * 3 + 2];\n\n\t\t\t\t\tsnprintf(buffer, sizeof(buffer), \"&c%x%x%x%s\",\n\t\t\t\t\t\t(unsigned int)(red / 16),\n\t\t\t\t\t\t(unsigned int)(green / 16),\n\t\t\t\t\t\t(unsigned int)(blue / 16),\n\t\t\t\t\t\tinfo->name\n\t\t\t\t\t);\n\n\t\t\t\t\t// Draw the tracked players name in the user specified color.\n\t\t\t\t\tDraw_SColoredStringBasic(name_x, name_y, buffer, 0, text_scale, proportional);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Draw other players in normal character color.\n\t\t\t\t\tDraw_SString (name_x, name_y, info->name, text_scale, proportional);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Show if a person lost an RL-pack.\n\t\t\tif (info->stats[STAT_HEALTH] <= 0 && info->stats[STAT_ACTIVEWEAPON] == IT_ROCKET_LAUNCHER)\n\t\t\t{\n\t\t\t\tDraw_AlphaCircleOutline (x + player_p_x, y + player_p_y, player_size*player_z_relative*2, 1.0, 254, player_alpha);\n\t\t\t\tDraw_SColoredStringBasic (x + player_p_x, y + player_p_y, va(\"&cf00PACK!\"), 1, text_scale, proportional);\n\t\t\t}\n\t\t}\n\t}\n}\n\n//\n// Draws a map of the current level and plots player movements on it.\n//\nvoid SCR_HUD_DrawRadar(hud_t *hud)\n{\n\tint width, height, x, y;\n\tfloat width_limit, height_limit;\n\tfloat scale;\n\tfloat x_scale;\n\tfloat y_scale;\n\n\tstatic cvar_t\n\t\t*hud_radar_opacity = NULL,\n\t\t*hud_radar_width,\n\t\t*hud_radar_height,\n\t\t*hud_radar_autosize,\n\t\t*hud_radar_fade_players,\n\t\t*hud_radar_show_powerups,\n\t\t*hud_radar_show_names,\n\t\t*hud_radar_highlight,\n\t\t*hud_radar_highlight_color,\n\t\t*hud_radar_player_size,\n\t\t*hud_radar_show_height,\n\t\t*hud_radar_show_stats,\n\t\t*hud_radar_show_hold,\n\t\t*hud_radar_weaponfilter,\n\t\t*hud_radar_itemfilter,\n\t\t*hud_radar_onlytp,\n\t\t*hud_radar_otherfilter,\n\t\t*hud_radar_scale,\n\t\t*hud_radar_simple,\n\t\t*hud_radar_colour_names,\n\t\t*hud_radar_proportional;\n\n\tif (hud_radar_opacity == NULL)    // first time\n\t{\n\t\tchar checkval[256];\n\n\t\thud_radar_opacity\t\t\t= HUD_FindVar(hud, \"opacity\");\n\t\thud_radar_width\t\t\t\t= HUD_FindVar(hud, \"width\");\n\t\thud_radar_height\t\t\t= HUD_FindVar(hud, \"height\");\n\t\thud_radar_autosize\t\t\t= HUD_FindVar(hud, \"autosize\");\n\t\thud_radar_fade_players\t\t= HUD_FindVar(hud, \"fade_players\");\n\t\thud_radar_show_powerups\t\t= HUD_FindVar(hud, \"show_powerups\");\n\t\thud_radar_show_names\t\t= HUD_FindVar(hud, \"show_names\");\n\t\thud_radar_player_size\t\t= HUD_FindVar(hud, \"player_size\");\n\t\thud_radar_show_height\t\t= HUD_FindVar(hud, \"show_height\");\n\t\thud_radar_show_stats\t\t= HUD_FindVar(hud, \"show_stats\");\n\t\thud_radar_show_hold\t\t\t= HUD_FindVar(hud, \"show_hold\");\n\t\thud_radar_weaponfilter\t\t= HUD_FindVar(hud, \"weaponfilter\");\n\t\thud_radar_itemfilter\t\t= HUD_FindVar(hud, \"itemfilter\");\n\t\thud_radar_otherfilter\t\t= HUD_FindVar(hud, \"otherfilter\");\n\t\thud_radar_onlytp\t\t\t= HUD_FindVar(hud, \"onlytp\");\n\t\thud_radar_highlight\t\t\t= HUD_FindVar(hud, \"highlight\");\n\t\thud_radar_highlight_color\t= HUD_FindVar(hud, \"highlight_color\");\n\t\thud_radar_scale             = HUD_FindVar(hud, \"scale\");\n\t\thud_radar_simple            = HUD_FindVar(hud, \"simpleitems\");\n\t\thud_radar_colour_names      = HUD_FindVar(hud, \"colornames\");\n\t\thud_radar_proportional      = HUD_FindVar(hud, \"proportional\");\n\n\t\t//\n\t\t// Only parse the the filters when they change, not on each frame.\n\t\t//\n\n\t\t// Weapon filter.\n\t\thud_radar_weaponfilter->OnChange = Radar_OnChangeWeaponFilter;\n\t\tstrlcpy(checkval, hud_radar_weaponfilter->string, sizeof(checkval));\n\t\tCvar_Set(hud_radar_weaponfilter, checkval);\n\n\t\t// Item filter.\n\t\thud_radar_itemfilter->OnChange = Radar_OnChangeItemFilter;\n\t\tstrlcpy(checkval, hud_radar_itemfilter->string, sizeof(checkval));\n\t\tCvar_Set(hud_radar_itemfilter, checkval);\n\n\t\t// Other filter.\n\t\thud_radar_otherfilter->OnChange = Radar_OnChangeOtherFilter;\n\t\tstrlcpy(checkval, hud_radar_otherfilter->string, sizeof(checkval));\n\t\tCvar_Set(hud_radar_otherfilter, checkval);\n\n\t\t// Highlight color.\n\t\thud_radar_highlight_color->OnChange = Radar_OnChangeHighlightColor;\n\t\tstrlcpy(checkval, hud_radar_highlight_color->string, sizeof(checkval));\n\t\tCvar_Set(hud_radar_highlight_color, checkval);\n\t}\n\n\t// Don't show anything if it's a normal player.\n\tif(!(cls.demoplayback || cl.spectator))\n\t{\n\t\tHUD_PrepareDraw(hud, hud_radar_width->value, hud_radar_height->value, &x, &y);\n\t\treturn;\n\t}\n\n\t// Don't show when not in teamplay/demoplayback.\n\tif(!HUD_ShowInDemoplayback(hud_radar_onlytp->value))\n\t{\n\t\tHUD_PrepareDraw(hud, hud_radar_width->value, hud_radar_height->value, &x, &y);\n\t\treturn;\n\t}\n\n\t// Save the width and height of the HUD. We're using these because\n\t// if autosize is on these will be altered and we don't want to change\n\t// the settings that the user set, if we try, and the user turns off\n\t// autosize again the size of the HUD will remain \"autosized\" until the user\n\t// resets it by hand again.\n\twidth_limit = hud_radar_width->value;\n\theight_limit = hud_radar_height->value;\n\n\t// we support also sizes specified as a percentage of total screen width/height\n\tif (strchr(hud_radar_width->string, '%')) {\n\t\twidth_limit = width_limit * vid.conwidth / 100.0;\n\t}\n\tif (strchr(hud_radar_height->string, '%')) {\n\t\theight_limit = hud_radar_height->value * vid.conheight / 100.0;\n\t}\n\n\t// This map doesn't have a map pic.\n\tif (!radar_pic_found) {\n\t\tif (HUD_PrepareDraw(hud, Q_rint(width_limit), Q_rint(height_limit), &x, &y)) {\n\t\t\tDraw_SString(x, y, \"No radar picture found!\", hud_radar_scale->value, hud_radar_proportional->integer);\n\t\t}\n\t\treturn;\n\t}\n\n\t// Make sure we can translate the coordinates.\n\tif (!conversion_formula_found) {\n\t\tif (HUD_PrepareDraw(hud, Q_rint(width_limit), Q_rint(height_limit), &x, &y)) {\n\t\t\tDraw_SString(x, y, \"No conversion formula found!\", hud_radar_scale->value, hud_radar_proportional->integer);\n\t\t}\n\t\treturn;\n\t}\n\n\tx = 0;\n\ty = 0;\n\n\tscale = 1;\n\n\tif (hud_radar_autosize->value) {\n\t\t//\n\t\t// Autosize the hud element based on the size of the radar picture.\n\t\t//\n\n\t\twidth = width_limit = radar_pic.width;\n\t\theight = height_limit = radar_pic.height;\n\t}\n\telse {\n\t\t//\n\t\t// Size the picture so that it fits inside the hud element.\n\t\t//\n\n\t\t// Set the scaling based on the picture dimensions.\n\t\tx_scale = (width_limit / radar_pic.width);\n\t\ty_scale = (height_limit / radar_pic.height);\n\n\t\tscale = (x_scale < y_scale) ? x_scale : y_scale;\n\n\t\twidth = radar_pic.width * scale;\n\t\theight = radar_pic.height * scale;\n\t}\n\n\tif (HUD_PrepareDraw(hud, Q_rint(width_limit), Q_rint(height_limit), &x, &y)) {\n\t\tfloat player_size = 1.0;\n\t\tstatic int lastframecount = -1;\n\n\t\t// Place the map picture in the center of the HUD element.\n\t\tx += Q_rint((width_limit / 2.0) - (width / 2.0));\n\t\tx = max(0, x);\n\t\tx = min(x + width, x);\n\n\t\ty += Q_rint((height_limit / 2.0) - (height / 2.0));\n\t\ty = max(0, y);\n\t\ty = min(y + height, y);\n\n\t\t// Draw the radar background.\n\t\tDraw_SAlphaPic(x, y, &radar_pic, hud_radar_opacity->value, scale);\n\n\t\t// Only draw once per frame.\n\t\tif (cls.framecount == lastframecount) {\n\t\t\treturn;\n\t\t}\n\t\tlastframecount = cls.framecount;\n\n\t\tif (!cl.oldparsecount || !cl.parsecount || cls.state < ca_active) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Scale the player size after the size of the radar.\n\t\tplayer_size = hud_radar_player_size->value * scale;\n\n\t\t// Draw team stats.\n\t\tif (hud_radar_show_stats->value) {\n\t\t\tRadar_DrawGrid(stats_grid, x, y, scale, width, height, hud_radar_show_stats->value);\n\t\t}\n\n\t\t// Draw entities such as powerups, weapons and backpacks.\n\t\tif (RADAR_SHOW_WEAPONS || RADAR_SHOW_ITEMS || RADAR_SHOW_OTHER) {\n\t\t\tRadar_DrawEntities(\n\t\t\t\tx, y, scale, player_size,\n\t\t\t\thud_radar_show_hold->value, hud_radar_scale->value, hud_radar_simple->value, hud_radar_proportional->integer\n\t\t\t);\n\t\t}\n\n\t\t// Draw the players.\n\t\tRadar_DrawPlayers(\n\t\t\tx, y, width, height, scale,\n\t\t\thud_radar_show_height->value,\n\t\t\thud_radar_show_powerups->value,\n\t\t\tplayer_size,\n\t\t\thud_radar_show_names->value,\n\t\t\thud_radar_fade_players->value,\n\t\t\thud_radar_highlight->value,\n\t\t\thud_radar_highlight_color->string,\n\t\t\thud_radar_scale->value,\n\t\t\thud_radar_colour_names->integer,\n\t\t\thud_radar_proportional->integer\n\t\t);\n\t}\n}\n\n#endif // WITH_PNG\n\nvoid Radar_HudInit(void)\n{\n#ifdef WITH_PNG\n\tHUD_Register(\"radar\", NULL, \"Plots the players on a picture of the map. (Only when watching MVD's or QTV).\",\n\t\tHUD_PLUSMINUS, ca_active, 0, SCR_HUD_DrawRadar,\n\t\t\"0\", \"top\", \"left\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"opacity\", \"0.5\",\n\t\t\"width\", \"30%\",\n\t\t\"height\", \"25%\",\n\t\t\"autosize\", \"0\",\n\t\t\"show_powerups\", \"1\",\n\t\t\"show_names\", \"0\",\n\t\t\"highlight_color\", \"yellow\",\n\t\t\"highlight\", \"0\",\n\t\t\"player_size\", \"10\",\n\t\t\"show_height\", \"1\",\n\t\t\"show_stats\", \"1\",\n\t\t\"fade_players\", \"1\",\n\t\t\"show_hold\", \"0\",\n\t\t\"weaponfilter\", \"gl rl lg\",\n\t\t\"itemfilter\", \"backpack quad pent armor mega\",\n\t\t\"otherfilter\", \"projectiles gibs explosions shotgun\",\n\t\t\"onlytp\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"simpleitems\", \"1\",\n\t\t\"colornames\", \"0\",\n\t\t\"proportional\", \"0\",\n\t\tNULL);\n#endif\n}\n\n"
  },
  {
    "path": "src/hud_scores.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"teamplay.h\"\n#include \"Ctrl.h\"\n#include \"vx_stuff.h\"\n#include \"mvd_utils_common.h\"\n#include \"sbar.h\"\n\nqbool TP_ReadMacroFormat(char* s, int* fixed_width, int* alignment, char** new_s);\nchar* TP_AlignMacroText(char* text, int fixed_width, int alignment);\n\nstatic cvar_t hud_sortrules_teamsort = { \"hud_sortrules_teamsort\", \"0\" };\nstatic cvar_t hud_sortrules_playersort = { \"hud_sortrules_playersort\", \"1\" };\nstatic cvar_t hud_sortrules_includeself = { \"hud_sortrules_includeself\", \"1\" };\n\nstatic int playersort_rules;\nstatic int teamsort_rules;\n\nstatic int HUD_ComparePlayers(const void *vp1, const void *vp2)\n{\n\tconst sort_players_info_t *p1 = vp1;\n\tconst sort_players_info_t *p2 = vp2;\n\n\tint r = 0;\n\tplayer_info_t *i1 = &cl.players[p1->playernum];\n\tplayer_info_t *i2 = &cl.players[p2->playernum];\n\n\tif (i1->spectator && !i2->spectator) {\n\t\tr = -1;\n\t}\n\telse if (!i1->spectator && i2->spectator) {\n\t\tr = 1;\n\t}\n\telse if (i1->spectator && i2->spectator) {\n\t\tr = Q_strcmp2(i1->name, i2->name);\n\t}\n\telse {\n\t\t//\n\t\t// Both are players.\n\t\t//\n\t\tif ((playersort_rules & 2) && cl.teamplay && p1->team && p2->team) {\n\t\t\t// Leading team on top, sort players inside of the teams.\n\n\t\t\t// Teamsort 1, first sort on team frags.\n\t\t\tif (teamsort_rules == 1) {\n\t\t\t\tr = p1->team->frags - p2->team->frags;\n\t\t\t}\n\n\t\t\t// sort on team name only.\n\t\t\tr = (r == 0) ? -Q_strcmp2(p1->team->name, p2->team->name) : r;\n\t\t}\n\n\t\tif (playersort_rules & 1) {\n\t\t\tr = (r == 0) ? i1->frags - i2->frags : r;\n\t\t}\n\t\tr = (r == 0) ? -Q_strcmp2(i1->name, i2->name) : r;\n\t}\n\n\tr = (r == 0) ? (p1->playernum - p2->playernum) : r;\n\n\t// qsort() sorts ascending by default, we want descending.\n\t// So negate the result.\n\treturn -r;\n}\n\nstatic int HUD_CompareTeams(const void *vt1, const void *vt2)\n{\n\tint r = 0;\n\tconst sort_teams_info_t *t1 = vt1;\n\tconst sort_teams_info_t *t2 = vt2;\n\n\tif (hud_sortrules_teamsort.integer == 1) {\n\t\tr = (t1->frags - t2->frags);\n\t}\n\tr = !r ? -Q_strcmp2(t1->name, t2->name) : r;\n\n\t// qsort() sorts ascending by default, we want descending.\n\t// So negate the result.\n\treturn -r;\n}\n\nvoid HUD_Sort_Scoreboard(int flags)\n{\n\tint i;\n\tint team;\n\tint active_player = -1;\n\tqbool common_fragcounts = cl.teamfortress && cl.teamplay && cl.scoring_system == SCORING_SYSTEM_DEFAULT;\n\n\tactive_player_position = -1;\n\tactive_team_position = -1;\n\n\tn_teams = 0;\n\tn_players = 0;\n\tn_spectators = 0;\n\n\tplayersort_rules = hud_sortrules_playersort.integer;\n\tteamsort_rules = hud_sortrules_teamsort.integer;\n\n\t// This taken from score_bar logic\n\tif (cls.demoplayback && !cl.spectator && !cls.mvdplayback) {\n\t\tactive_player = cl.playernum;\n\t}\n\telse if ((cls.demoplayback || cl.spectator) && Cam_TrackNum() >= 0) {\n\t\tactive_player = cl.spec_track;\n\t}\n\telse {\n\t\tactive_player = cl.playernum;\n\t}\n\n\t// Set team properties.\n\tif (flags & HUD_SCOREBOARD_UPDATE) {\n\t\tmemset(sorted_teams, 0, sizeof(sorted_teams));\n\n\t\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\t\tif (cl.players[i].name[0] && !cl.players[i].spectator) {\n\t\t\t\tif (cl.players[i].frags == 0 && cl.players[i].team[0] == '\\0' && !strcmp(cl.players[i].name, \"[ServeMe]\")) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Find players team\n\t\t\t\tfor (team = 0; team < n_teams; team++) {\n\t\t\t\t\tif (cl.teamplay && !strcmp(cl.players[i].team, sorted_teams[team].name) && sorted_teams[team].name[0]) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (!cl.teamplay && !strcmp(cl.players[i].name, sorted_teams[team].name) && sorted_teams[team].name[0]) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// The team wasn't found in the list of existing teams\n\t\t\t\t// so add a new team.\n\t\t\t\tif (team == n_teams) {\n\t\t\t\t\tteam = n_teams++;\n\t\t\t\t\tsorted_teams[team].min_ping = 999;\n\t\t\t\t\tsorted_teams[team].top = Sbar_TopColor(&cl.players[i]);\n\t\t\t\t\tsorted_teams[team].bottom = Sbar_BottomColor(&cl.players[i]);\n\t\t\t\t\tif (cl.teamplay) {\n\t\t\t\t\t\tsorted_teams[team].name = cl.players[i].team;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tsorted_teams[team].name = cl.players[i].name;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (sorted_teams[team].nplayers >= 1) {\n\t\t\t\t\tcommon_fragcounts &= (sorted_teams[team].frags / sorted_teams[team].nplayers == cl.players[i].frags);\n\t\t\t\t}\n\n\t\t\t\tsorted_teams[team].nplayers++;\n\t\t\t\tif (cl.scoring_system == SCORING_SYSTEM_TEAMFRAGS) {\n\t\t\t\t\tif (sorted_teams[team].nplayers == 1) {\n\t\t\t\t\t\tsorted_teams[team].frags = cl.players[i].frags;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tsorted_teams[team].frags = max(cl.players[i].frags, sorted_teams[team].frags);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tsorted_teams[team].frags += cl.players[i].frags;\n\t\t\t\t}\n\t\t\t\tsorted_teams[team].avg_ping += cl.players[i].ping;\n\t\t\t\tsorted_teams[team].min_ping = min(sorted_teams[team].min_ping, cl.players[i].ping);\n\t\t\t\tsorted_teams[team].max_ping = max(sorted_teams[team].max_ping, cl.players[i].ping);\n\t\t\t\tsorted_teams[team].stack += SCR_HUD_TotalStrength(cl.players[i].stats[STAT_HEALTH], cl.players[i].stats[STAT_ARMOR], SCR_HUD_ArmorType(cl.players[i].stats[STAT_ITEMS]));\n\n\t\t\t\t// The total RL count for the players team.\n\t\t\t\tif (cl.players[i].stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER) {\n\t\t\t\t\tsorted_teams[team].rlcount++;\n\t\t\t\t}\n\t\t\t\tif (cl.players[i].stats[STAT_ITEMS] & IT_LIGHTNING) {\n\t\t\t\t\tsorted_teams[team].lgcount++;\n\t\t\t\t}\n\t\t\t\tif (cl.players[i].stats[STAT_ITEMS] & (IT_ROCKET_LAUNCHER | IT_LIGHTNING)) {\n\t\t\t\t\tsorted_teams[team].weapcount++;\n\t\t\t\t}\n\n\t\t\t\tif (cls.mvdplayback) {\n\t\t\t\t\tmvd_new_info_t* stats = MVD_StatsForPlayer(&cl.players[i]);\n\t\t\t\t\tsort_teams_info_t* t = &sorted_teams[team];\n\n\t\t\t\t\tif (stats) {\n\t\t\t\t\t\tt->quads_taken += stats->mvdinfo.itemstats[QUAD_INFO].count;\n\t\t\t\t\t\tt->pents_taken += stats->mvdinfo.itemstats[PENT_INFO].count;\n\t\t\t\t\t\tt->rings_taken += stats->mvdinfo.itemstats[RING_INFO].count;\n\t\t\t\t\t\tt->ga_taken += stats->mvdinfo.itemstats[GA_INFO].count;\n\t\t\t\t\t\tt->ya_taken += stats->mvdinfo.itemstats[YA_INFO].count;\n\t\t\t\t\t\tt->ra_taken += stats->mvdinfo.itemstats[RA_INFO].count;\n\t\t\t\t\t\tt->mh_taken += stats->mvdinfo.itemstats[MH_INFO].count;\n\t\t\t\t\t\tt->q_lasttime = max(t->q_lasttime, stats->mvdinfo.itemstats[QUAD_INFO].starttime);\n\t\t\t\t\t\tt->p_lasttime = max(t->p_lasttime, stats->mvdinfo.itemstats[PENT_INFO].starttime);\n\t\t\t\t\t\tt->r_lasttime = max(t->r_lasttime, stats->mvdinfo.itemstats[RING_INFO].starttime);\n\t\t\t\t\t\tt->ga_lasttime = max(t->ga_lasttime, stats->mvdinfo.itemstats[GA_INFO].starttime);\n\t\t\t\t\t\tt->ya_lasttime = max(t->ya_lasttime, stats->mvdinfo.itemstats[YA_INFO].starttime);\n\t\t\t\t\t\tt->ra_lasttime = max(t->ra_lasttime, stats->mvdinfo.itemstats[RA_INFO].starttime);\n\t\t\t\t\t\tt->mh_lasttime = max(t->mh_lasttime, stats->mvdinfo.itemstats[MH_INFO].starttime);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Set player data.\n\t\t\t\tsorted_players[n_players + n_spectators].playernum = i;\n\t\t\t\tif (i == active_player && !cl.players[i].spectator) {\n\t\t\t\t\tactive_player_position = n_players + n_spectators;\n\t\t\t\t}\n\n\t\t\t\t// Increase the count.\n\t\t\t\tif (cl.players[i].spectator) {\n\t\t\t\t\tn_spectators++;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tn_players++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Calc avg ping.\n\tif (flags & HUD_SCOREBOARD_AVG_PING) {\n\t\tfor (team = 0; team < n_teams; team++) {\n\t\t\tsorted_teams[team].avg_ping /= sorted_teams[team].nplayers;\n\t\t}\n\t}\n\n\t// Adjust team scores for team fortress scoring (required so it matches +showteamscores)\n\tif (cl.teamfortress && common_fragcounts) {\n\t\tfor (team = 0; team < n_teams; ++team) {\n\t\t\tif (sorted_teams[team].nplayers) {\n\t\t\t\tsorted_teams[team].frags /= sorted_teams[team].nplayers;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sort teams.\n\tif (flags & HUD_SCOREBOARD_SORT_TEAMS) {\n\t\tactive_team_position = -1;\n\n\t\tqsort(sorted_teams, n_teams, sizeof(sort_teams_info_t), HUD_CompareTeams);\n\n\t\t// if set, need to make sure that the player's team is first or second position\n\t\tif (hud_sortrules_includeself.integer && active_player_position >= 0) {\n\t\t\tplayer_info_t *player = &cl.players[sorted_players[active_player_position].playernum];\n\t\t\tsorted_players[active_player_position].team = NULL;\n\n\t\t\t// Find players team.\n\t\t\tfor (team = 0; team < n_teams; team++) {\n\t\t\t\tconst char* team_name = (cl.teamplay ? player->team : player->name);\n\t\t\t\tif (!strcmp(team_name, sorted_teams[team].name) && sorted_teams[team].name[0]) {\n\t\t\t\t\tif (hud_sortrules_includeself.integer == 1 && team > 0) {\n\t\t\t\t\t\tsort_teams_info_t temp = sorted_teams[0];\n\t\t\t\t\t\tsorted_teams[0] = sorted_teams[team];\n\t\t\t\t\t\tsorted_teams[team] = temp;\n\t\t\t\t\t}\n\t\t\t\t\telse if (hud_sortrules_includeself.integer == 2 && team > 1) {\n\t\t\t\t\t\tsort_teams_info_t temp = sorted_teams[1];\n\t\t\t\t\t\tsorted_teams[1] = sorted_teams[team];\n\t\t\t\t\t\tsorted_teams[team] = temp;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// BUGFIX, this needs to happen AFTER the team array has been sorted, otherwise the\n\t\t// players might be pointing to the incorrect team address.\n\t\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\t\tplayer_info_t *player = &cl.players[sorted_players[i].playernum];\n\t\t\tsorted_players[i].team = NULL;\n\n\t\t\t// Find players team.\n\t\t\tfor (team = 0; team < n_teams; team++) {\n\t\t\t\tif (!strcmp(player->team, sorted_teams[team].name) && sorted_teams[team].name[0]) {\n\t\t\t\t\tsorted_players[i].team = &sorted_teams[team];\n\t\t\t\t\tif (sorted_players[i].playernum == active_player) {\n\t\t\t\t\t\tactive_team_position = team;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sort players by frags only\n\tif (flags & HUD_SCOREBOARD_SORT_PLAYERS) {\n\t\tint oldrules = playersort_rules;\n\n\t\tplayersort_rules = 1;\n\t\tqsort(sorted_players, n_players + n_spectators, sizeof(sort_players_info_t), HUD_ComparePlayers);\n\t\tplayersort_rules = oldrules;\n\n\t\tmemcpy(sorted_players_by_frags, sorted_players, sizeof(sorted_players_by_frags));\n\t}\n\n\t// Sort players.\n\tif (flags & HUD_SCOREBOARD_SORT_PLAYERS) {\n\t\tqsort(sorted_players, n_players + n_spectators, sizeof(sort_players_info_t), HUD_ComparePlayers);\n\n\t\tif (!cl.teamplay) {\n\t\t\t// Re-find player\n\t\t\tactive_player_position = -1;\n\t\t\tfor (i = 0; i < n_players + n_spectators; ++i) {\n\t\t\t\tif (sorted_players[i].playernum == active_player) {\n\t\t\t\t\tactive_player_position = i;\n\t\t\t\t\tif (hud_sortrules_includeself.integer == 1 && i > 0) {\n\t\t\t\t\t\tsort_players_info_t temp = sorted_players[0];\n\t\t\t\t\t\tsorted_players[0] = sorted_players[i];\n\t\t\t\t\t\tsorted_players[i] = temp;\n\t\t\t\t\t\tactive_player_position = 0;\n\t\t\t\t\t}\n\t\t\t\t\telse if (hud_sortrules_includeself.integer == 2 && i > 1) {\n\t\t\t\t\t\tsort_players_info_t temp = sorted_players[1];\n\t\t\t\t\t\tsorted_players[1] = sorted_players[i];\n\t\t\t\t\t\tsorted_players[i] = temp;\n\t\t\t\t\t\tactive_player_position = 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic qbool SCR_Hud_GetScores(int* team, int* enemy, char** teamName, char** enemyName)\n{\n\tqbool swap = false;\n\n\t*team = *enemy = 0;\n\t*teamName = *enemyName = NULL;\n\n\tif (cl.teamplay) {\n\t\t*team = sorted_teams[0].frags;\n\t\t*teamName = sorted_teams[0].name;\n\n\t\tif (n_teams > 1) {\n\t\t\tif (hud_sortrules_includeself.integer >= 1 && active_team_position > 1) {\n\t\t\t\t*enemy = sorted_teams[active_team_position].frags;\n\t\t\t\t*enemyName = sorted_teams[active_team_position].name;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t*enemy = sorted_teams[1].frags;\n\t\t\t\t*enemyName = sorted_teams[1].name;\n\t\t\t}\n\n\t\t\tswap = (hud_sortrules_includeself.integer == 1 && active_team_position > 0);\n\t\t}\n\t}\n\telse if (cl.deathmatch) {\n\t\t*team = cl.players[sorted_players[0].playernum].frags;\n\t\t*teamName = cl.players[sorted_players[0].playernum].name;\n\n\t\tif (n_players > 1) {\n\t\t\tif (hud_sortrules_includeself.integer >= 1 && active_player_position > 1) {\n\t\t\t\t*enemy = cl.players[sorted_players[active_player_position].playernum].frags;\n\t\t\t\t*enemyName = cl.players[sorted_players[active_player_position].playernum].name;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t*enemy = cl.players[sorted_players[1].playernum].frags;\n\t\t\t\t*enemyName = cl.players[sorted_players[1].playernum].name;\n\t\t\t}\n\n\t\t\tswap = (hud_sortrules_includeself.integer == 1 && active_player_position > 0);\n\t\t}\n\t}\n\n\tif (!*teamName) {\n\t\t*teamName = \"T\";\n\t}\n\tif (!*enemyName) {\n\t\t*enemyName = \"E\";\n\t}\n\n\tif (swap) {\n\t\tint temp_frags = *team;\n\t\tchar* temp_name = *teamName;\n\n\t\t*team = *enemy;\n\t\t*teamName = *enemyName;\n\t\t*enemy = temp_frags;\n\t\t*enemyName = temp_name;\n\t}\n\n\treturn swap;\n}\n\n//\n// TODO: decide what to do in freefly mode (and how to catch it?!), now all score_* hud elements just draws \"0\"\n//\nstatic void SCR_HUD_DrawScoresTeam(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *digits, *align, *colorize, *proportional;\n\tint teamFrags = 0, enemyFrags = 0;\n\tchar* teamName = 0, *enemyName = 0;\n\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tdigits = HUD_FindVar(hud, \"digits\");\n\t\talign = HUD_FindVar(hud, \"align\");\n\t\tcolorize = HUD_FindVar(hud, \"colorize\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tSCR_Hud_GetScores(&teamFrags, &enemyFrags, &teamName, &enemyName);\n\n\tSCR_HUD_DrawNum(hud, teamFrags, (colorize->integer) ? (teamFrags < 0 || colorize->integer > 1) : false, scale->value, style->value, digits->value, align->string, proportional->integer);\n}\n\nstatic void SCR_HUD_DrawScoresEnemy(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *digits, *align, *colorize, *proportional;\n\tint teamFrags = 0, enemyFrags = 0;\n\tchar* teamName = 0, *enemyName = 0;\n\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tdigits = HUD_FindVar(hud, \"digits\");\n\t\talign = HUD_FindVar(hud, \"align\");\n\t\tcolorize = HUD_FindVar(hud, \"colorize\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tSCR_Hud_GetScores(&teamFrags, &enemyFrags, &teamName, &enemyName);\n\n\tif (!cl.teamplay) {\n\t\t// Ignore enemyFrags now, and go by highest opponent that isn't current player\n\t\tif (n_players > 1) {\n\t\t\tif (!strcmp(cl.players[sorted_players_by_frags[0].playernum].name, teamName)) {\n\t\t\t\tenemyFrags = cl.players[sorted_players_by_frags[1].playernum].frags;\n\t\t\t\tenemyName = cl.players[sorted_players_by_frags[1].playernum].name;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tenemyFrags = cl.players[sorted_players_by_frags[0].playernum].frags;\n\t\t\t\tenemyName = cl.players[sorted_players_by_frags[0].playernum].name;\n\t\t\t}\n\t\t}\n\t}\n\n\tSCR_HUD_DrawNum(hud, enemyFrags, (colorize->integer) ? (enemyFrags < 0 || colorize->integer > 1) : false, scale->value, style->value, digits->value, align->string, proportional->integer);\n}\n\nstatic void SCR_HUD_DrawScoresDifference(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *digits, *align, *colorize, *proportional;\n\tint teamFrags = 0, enemyFrags = 0;\n\tchar* teamName = 0, *enemyName = 0;\n\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tdigits = HUD_FindVar(hud, \"digits\");\n\t\talign = HUD_FindVar(hud, \"align\");\n\t\tcolorize = HUD_FindVar(hud, \"colorize\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tSCR_Hud_GetScores(&teamFrags, &enemyFrags, &teamName, &enemyName);\n\n\tif (!cl.teamplay) {\n\t\t// Ignore enemyFrags now, and go by highest opponent that isn't current player\n\t\tif (n_players > 1) {\n\t\t\tif (!strcmp(cl.players[sorted_players_by_frags[0].playernum].name, teamName)) {\n\t\t\t\tenemyFrags = cl.players[sorted_players_by_frags[1].playernum].frags;\n\t\t\t\tenemyName = cl.players[sorted_players_by_frags[1].playernum].name;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tenemyFrags = cl.players[sorted_players_by_frags[0].playernum].frags;\n\t\t\t\tenemyName = cl.players[sorted_players_by_frags[0].playernum].name;\n\t\t\t}\n\t\t}\n\t}\n\n\tSCR_HUD_DrawNum(hud, teamFrags - enemyFrags, (colorize->integer) ? ((teamFrags - enemyFrags) < 0 || colorize->integer > 1) : false, scale->value, style->value, digits->value, align->string, proportional->integer);\n}\n\nstatic void SCR_HUD_DrawScoresPosition(hud_t *hud)\n{\n\tstatic cvar_t *scale = NULL, *style, *digits, *align, *colorize, *proportional;\n\tint teamFrags = 0, enemyFrags = 0, position = 0, i = 0;\n\tchar* teamName = 0, *enemyName = 0;\n\n\tif (scale == NULL) {\n\t\t// first time called\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tdigits = HUD_FindVar(hud, \"digits\");\n\t\talign = HUD_FindVar(hud, \"align\");\n\t\tcolorize = HUD_FindVar(hud, \"colorize\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tSCR_Hud_GetScores(&teamFrags, &enemyFrags, &teamName, &enemyName);\n\n\tposition = 1;\n\tif (cl.teamplay) {\n\t\tfor (i = 0; i < n_teams; ++i) {\n\t\t\tif (sorted_teams[i].frags > teamFrags) {\n\t\t\t\t++position;\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\tfor (i = 0; i < n_players + n_spectators; ++i) {\n\t\t\tif (cl.players[sorted_players[i].playernum].frags > teamFrags) {\n\t\t\t\t++position;\n\t\t\t}\n\t\t}\n\t}\n\n\tSCR_HUD_DrawNum(hud, position, (colorize->integer) ? (position != 1 || colorize->integer > 1) : false, scale->value, style->value, digits->value, align->string, proportional->integer);\n}\n\n/*\n\tezQuake's analogue of +scores of KTX\n\t( t:x e:x [x] )\n*/\nstatic void SCR_HUD_DrawScoresBar(hud_t *hud)\n{\n\tstatic\tcvar_t *scale = NULL, *style, *format_big, *format_small, *frag_length, *reversed_big, *reversed_small, *proportional;\n\tint\t\twidth = 0, height = 0, x, y;\n\tint\t\ti = 0;\n\n\tint\t\ts_team = 0, s_enemy = 0, s_difference = 0;\n\tchar\t*n_team = \"T\", *n_enemy = \"E\";\n\tqbool   swappedOrder = false;\n\n\tchar\tbuf[MAX_MACRO_STRING];\n\tchar\tc, *out, *temp, *in;\n\tint     frag_digits = 1;\n\n\tif (scale == NULL)  // first time called\n\t{\n\t\tscale = HUD_FindVar(hud, \"scale\");\n\t\tstyle = HUD_FindVar(hud, \"style\");\n\t\tformat_big = HUD_FindVar(hud, \"format_big\");\n\t\tformat_small = HUD_FindVar(hud, \"format_small\");\n\t\tfrag_length = HUD_FindVar(hud, \"frag_length\");\n\t\treversed_big = HUD_FindVar(hud, \"format_reversed_big\");\n\t\treversed_small = HUD_FindVar(hud, \"format_reversed_small\");\n\t\tproportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tfrag_digits = max(1, min(frag_length->value, 4));\n\n\tswappedOrder = SCR_Hud_GetScores(&s_team, &s_enemy, &n_team, &n_enemy);\n\ts_difference = s_team - s_enemy;\n\n\t// two pots of delicious customized copypasta from math_tools.c\n\tswitch (style->integer) {\n\t\t// Big\n\t\tcase 1:\n\t\t\tin = TP_ParseFunChars(swappedOrder && reversed_big->string[0] ? reversed_big->string : format_big->string, false);\n\t\t\tbuf[0] = 0;\n\t\t\tout = buf;\n\n\t\t\twhile ((c = *in++) && (out - buf < MAX_MACRO_STRING - 1)) {\n\t\t\t\tif ((c == '%') && *in) {\n\t\t\t\t\tswitch ((c = *in++)) {\n\t\t\t\t\t\t// c = colorize, r = reset\n\t\t\t\t\t\tcase 'd':\n\t\t\t\t\t\t\ttemp = va(\"%d\", s_difference);\n\t\t\t\t\t\t\twidth += (s_difference >= 0) ? strlen(temp) * 24 : ((strlen(temp) - 1) * 24) + 16;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'D':\n\t\t\t\t\t\t\ttemp = va(\"c%dr\", s_difference);\n\t\t\t\t\t\t\twidth += (s_difference >= 0) ? (strlen(temp) - 2) * 24 : ((strlen(temp) - 3) * 24) + 16;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'e':\n\t\t\t\t\t\t\ttemp = va(\"%d\", s_enemy);\n\t\t\t\t\t\t\twidth += (s_enemy >= 0) ? strlen(temp) * 24 : ((strlen(temp) - 1) * 24) + 16;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'E':\n\t\t\t\t\t\t\ttemp = va(\"c%dr\", s_enemy);\n\t\t\t\t\t\t\twidth += (s_enemy >= 0) ? (strlen(temp) - 2) * 24 : ((strlen(temp) - 3) * 24) + 16;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'p':\n\t\t\t\t\t\t\ttemp = va(\"%d\", i + 1);\n\t\t\t\t\t\t\twidth += 24;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 't':\n\t\t\t\t\t\t\ttemp = va(\"%d\", s_team);\n\t\t\t\t\t\t\twidth += (s_team >= 0) ? strlen(temp) * 24 : ((strlen(temp) - 1) * 24) + 16;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'T':\n\t\t\t\t\t\t\ttemp = va(\"c%dr\", s_team);\n\t\t\t\t\t\t\twidth += (s_team >= 0) ? (strlen(temp) - 2) * 24 : ((strlen(temp) - 3) * 24) + 16;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'z':\n\t\t\t\t\t\t\tif (s_difference >= 0) {\n\t\t\t\t\t\t\t\ttemp = va(\"%d\", s_difference);\n\t\t\t\t\t\t\t\twidth += strlen(temp) * 24;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\ttemp = va(\"c%dr\", -(s_difference));\n\t\t\t\t\t\t\t\twidth += (strlen(temp) - 2) * 24;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'Z':\n\t\t\t\t\t\t\tif (s_difference >= 0) {\n\t\t\t\t\t\t\t\ttemp = va(\"c%dr\", s_difference);\n\t\t\t\t\t\t\t\twidth += (strlen(temp) - 2) * 24;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\ttemp = va(\"%d\", -(s_difference));\n\t\t\t\t\t\t\t\twidth += strlen(temp) * 24;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\ttemp = NULL;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (temp != NULL) {\n\t\t\t\t\t\tstrlcpy(out, temp, sizeof(buf) - (out - buf));\n\t\t\t\t\t\tout += strlen(temp);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (c == ':' || c == '/' || c == '-' || c == ' ') {\n\t\t\t\t\twidth += 16;\n\t\t\t\t\t*out++ = c;\n\t\t\t\t}\n\t\t\t}\n\t\t\t*out = 0;\n\t\t\tbreak;\n\n\t\t\t// Small\n\t\tcase 0:\n\t\tdefault:\n\t\t\tin = TP_ParseFunChars(swappedOrder && reversed_small->string[0] ? reversed_small->string : format_small->string, false);\n\t\t\tbuf[0] = 0;\n\t\t\tout = buf;\n\n\t\t\twhile ((c = *in++) && (out - buf < MAX_MACRO_STRING - 1)) {\n\t\t\t\tif ((c == '%') && *in) {\n\t\t\t\t\tint fixed_width = 0, alignment = 0;\n\n\t\t\t\t\tif (in[0] == '<') {\n\t\t\t\t\t\t--in;\n\t\t\t\t\t\tTP_ReadMacroFormat(in, &fixed_width, &alignment, &in);\n\t\t\t\t\t\t++in;\n\t\t\t\t\t}\n\n\t\t\t\t\tswitch ((c = *in++)) {\n\t\t\t\t\t\tcase '%':\n\t\t\t\t\t\t\ttemp = \"%\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 't':\n\t\t\t\t\t\t\ttemp = va(\"%*d\", frag_digits, s_team);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'e':\n\t\t\t\t\t\t\ttemp = va(\"%*d\", frag_digits, s_enemy);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'd':\n\t\t\t\t\t\t\ttemp = va(\"%d\", s_difference);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'p':\n\t\t\t\t\t\t\ttemp = va(\"%d\", i + 1);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'T':\n\t\t\t\t\t\t\ttemp = n_team;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'E':\n\t\t\t\t\t\t\ttemp = n_enemy;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'D':\n\t\t\t\t\t\t\ttemp = va(\"%+d\", s_difference);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\ttemp = va(\"%%%c\", c);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (fixed_width) {\n\t\t\t\t\t\ttemp = TP_AlignMacroText(temp, fixed_width, alignment);\n\t\t\t\t\t}\n\t\t\t\t\tstrlcpy(out, temp, sizeof(buf) - (out - buf));\n\t\t\t\t\tout += strlen(temp);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t*out++ = c;\n\t\t\t\t}\n\t\t\t}\n\t\t\t*out = 0;\n\t\t\tbreak;\n\t}\n\n\tswitch (style->integer) {\n\t\t// Big\n\t\tcase 1:\n\t\t\twidth *= scale->value;\n\t\t\theight = 24 * scale->value;\n\n\t\t\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\t\t\tSCR_DrawWadString(x, y, scale->value, buf);\n\t\t\t}\n\t\t\tbreak;\n\n\t\t\t// Small\n\t\tcase 0:\n\t\tdefault:\n\t\t\twidth = Draw_StringLengthColors(buf, -1, scale->value, proportional->integer);\n\t\t\theight = 8 * scale->value;\n\n\t\t\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\t\t\tDraw_SString(x, y, buf, scale->value, proportional->integer);\n\t\t\t}\n\t\t\tbreak;\n\t}\n}\n\nstatic void SCR_HUD_DrawOwnFrags(hud_t *hud)\n{\n\t// not implemented yet: scale, color\n\t// fixme: add appropriate opengl functions that will add alpha, scale and color\n\tint width;\n\tint height = LETTERHEIGHT;\n\tint x, y;\n\tdouble alpha;\n\tstatic cvar_t\n\t\t*hud_ownfrags_timeout = NULL,\n\t\t*hud_ownfrags_scale = NULL,\n\t\t*hud_ownfrags_proportional = NULL;\n\textern qbool hud_editor;\n\n\tif (hud_ownfrags_timeout == NULL) {\n\t\t// first time\n\t\thud_ownfrags_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_ownfrags_timeout = HUD_FindVar(hud, \"timeout\");\n\t\thud_ownfrags_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tif (hud_editor) {\n\t\tHUD_PrepareDraw(hud, 10 * LETTERWIDTH, height * hud_ownfrags_scale->value, &x, &y);\n\t}\n\n\twidth = VX_OwnFragTextLen(hud_ownfrags_scale->value, hud_ownfrags_proportional->integer);\n\tif (!width) {\n\t\treturn;\n\t}\n\n\tif (VX_OwnFragTime() > hud_ownfrags_timeout->value) {\n\t\treturn;\n\t}\n\n\theight *= hud_ownfrags_scale->value;\n\n\talpha = 2 - hud_ownfrags_timeout->value / VX_OwnFragTime() * 2;\n\talpha = bound(0, alpha, 1);\n\n\tif (!HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\treturn;\n\t}\n\n\tif (VX_OwnFragTime() < hud_ownfrags_timeout->value) {\n\t\tDraw_SString(x, y, VX_OwnFragText(), hud_ownfrags_scale->value, hud_ownfrags_proportional->integer);\n\t}\n}\n\nvoid Scores_HudInit(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_HUD);\n\tCvar_Register(&hud_sortrules_playersort);\n\tCvar_Register(&hud_sortrules_teamsort);\n\tCvar_Register(&hud_sortrules_includeself);\n\tCvar_ResetCurrentGroup();\n\n\tHUD_Register(\n\t\t\"score_team\", NULL, \"Own scores or team scores.\",\n\t\t0, ca_active, 0, SCR_HUD_DrawScoresTeam,\n\t\t\"0\", \"screen\", \"left\", \"bottom\", \"0\", \"0\", \"0.5\", \"4 8 32\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"align\", \"right\",\n\t\t\"digits\", \"0\",\n\t\t\"colorize\", \"0\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\n\t\t\"score_enemy\", NULL, \"Scores of enemy or enemy team.\",\n\t\t0, ca_active, 0, SCR_HUD_DrawScoresEnemy,\n\t\t\"0\", \"score_team\", \"after\", \"bottom\", \"0\", \"0\", \"0.5\", \"32 4 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"align\", \"right\",\n\t\t\"digits\", \"0\",\n\t\t\"colorize\", \"0\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\n\t\t\"score_difference\", NULL, \"Difference between teamscores and enemyscores.\",\n\t\t0, ca_active, 0, SCR_HUD_DrawScoresDifference,\n\t\t\"0\", \"score_enemy\", \"after\", \"bottom\", \"0\", \"0\", \"0.5\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"align\", \"right\",\n\t\t\"digits\", \"0\",\n\t\t\"colorize\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\"score_position\", NULL, \"Position on scoreboard.\",\n\t\t0, ca_active, 0, SCR_HUD_DrawScoresPosition,\n\t\t\"0\", \"score_difference\", \"after\", \"bottom\", \"0\", \"0\", \"0.5\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"align\", \"right\",\n\t\t\"digits\", \"0\",\n\t\t\"colorize\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\n\t\t\"score_bar\", NULL, \"Team, enemy, and difference scores together.\",\n\t\tHUD_PLUSMINUS, ca_active, 0, SCR_HUD_DrawScoresBar,\n\t\t\"0\", \"screen\", \"center\", \"console\", \"0\", \"0\", \"0.5\", \"0 0 0\", NULL,\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"format_small\", \"&c69f%T&r:%t &cf10%E&r:%e $[%D$]\",\n\t\t\"format_big\", \"%t:%e:%Z\",\n\t\t\"format_reversed_big\", \"\",\n\t\t\"format_reversed_small\", \"\",\n\t\t\"frag_length\", \"0\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tHUD_Register(\n\t\t\"ownfrags\" /* jeez someone give me a better name please */,\n\t\tNULL, \"Highlights your own frags\",\n\t\t0, ca_active, 1, SCR_HUD_DrawOwnFrags,\n\t\t\"1\", \"screen\", \"center\", \"top\", \"0\", \"50\", \"0.2\", \"0 0 100\", NULL,\n\t\t/*\n\t\t\"color\", \"255 255 255\",\n\t\t*/\n\t\t\"timeout\", \"3\",\n\t\t\"scale\", \"1.5\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n}\n"
  },
  {
    "path": "src/hud_speed.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"hud.h\"\n\nstatic cvar_t show_speed   = { \"show_speed\", \"0\" };\nstatic cvar_t show_speed_x = { \"show_speed_x\", \"-1\" };\nstatic cvar_t show_speed_y = { \"show_speed_y\", \"1\" };\n\n#define SPEED_GREEN\t\t\t\t\"52\"\n#define SPEED_BROWN_RED\t\t\t\"100\"\n#define SPEED_DARK_RED\t\t\t\"72\"\n#define SPEED_BLUE\t\t\t\t\"216\"\n#define SPEED_RED\t\t\t\t\"229\"\n\n#define\tSPEED_STOPPED\t\t\tSPEED_GREEN\n#define\tSPEED_NORMAL\t\t\tSPEED_BROWN_RED\n#define\tSPEED_FAST\t\t\t\tSPEED_DARK_RED\n#define\tSPEED_FASTEST\t\t\tSPEED_BLUE\n#define\tSPEED_INSANE\t\t\tSPEED_RED\n\n#define SPEED_TAG_LENGTH\t\t2\n#define SPEED_OUTLINE_SPACING\tSPEED_TAG_LENGTH\n#define SPEED_FILL_SPACING\t\tSPEED_OUTLINE_SPACING + 1\n#define SPEED_WHITE\t\t\t\t10\n#define SPEED_TEXT_ONLY\t\t\t1\n\n#define\tSPEED_TEXT_ALIGN_NONE\t0\n#define SPEED_TEXT_ALIGN_CLOSE\t1\n#define SPEED_TEXT_ALIGN_CENTER\t2\n#define SPEED_TEXT_ALIGN_FAR\t3\n\n#define\tHUD_SPEED2_ORIENTATION_UP\t\t0\n#define\tHUD_SPEED2_ORIENTATION_DOWN\t\t1\n#define\tHUD_SPEED2_ORIENTATION_RIGHT\t2\n#define\tHUD_SPEED2_ORIENTATION_LEFT\t\t3\n\n// 'speed' hud element\nstatic void SCR_DrawHUDSpeed(\n\tint x, int y, int width, int height,\n\tint type,\n\tfloat tick_spacing,\n\tfloat opacity,\n\tint vertical,\n\tint vertical_text,\n\tint text_align,\n\tbyte color_stopped,\n\tbyte color_normal,\n\tbyte color_fast,\n\tbyte color_fastest,\n\tbyte color_insane,\n\tint style,\n\tfloat scale,\n\tqbool proportional\n)\n{\n\tbyte color_offset;\n\tbyte color1, color2;\n\tint player_speed;\n\tvec_t *velocity;\n\tqbool split;\n\tchar speed_text[20];\n\n\tif (scr_con_current == vid.height) {\n\t\treturn;     // console is full screen\n\t}\n\n\tsplit = (style == 2 || style == 3);\n\tstyle = style & 1;\n\n\t// Get the velocity.\n\tif (cl.players[cl.playernum].spectator && Cam_TrackNum() >= 0) {\n\t\tvelocity = cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK].playerstate[Cam_TrackNum()].velocity;\n\t}\n\telse {\n\t\tvelocity = cl.simvel;\n\t}\n\n\t// Calculate the speed\n\tif (!type) {\n\t\t// Based on XY.\n\t\tplayer_speed = sqrt(velocity[0] * velocity[0]\n\t\t\t\t\t\t\t+ velocity[1] * velocity[1]);\n\t}\n\telse {\n\t\t// Based on XYZ.\n\t\tplayer_speed = sqrt(velocity[0] * velocity[0]\n\t\t\t\t\t\t\t+ velocity[1] * velocity[1]\n\t\t\t\t\t\t\t+ velocity[2] * velocity[2]);\n\t}\n\n\t// Calculate the color offset for the \"background color\".\n\tif (vertical) {\n\t\tcolor_offset = height * (player_speed % 500) / 500;\n\t}\n\telse {\n\t\tcolor_offset = width * (player_speed % 500) / 500;\n\t}\n\n\t// Set the color based on the speed.\n\tswitch (player_speed / 500) {\n\t\tcase 0:\n\t\t\tcolor1 = color_stopped;\n\t\t\tcolor2 = color_normal;\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tcolor1 = color_normal;\n\t\t\tcolor2 = color_fast;\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tcolor1 = color_fast;\n\t\t\tcolor2 = color_fastest;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tcolor1 = color_fastest;\n\t\t\tcolor2 = color_insane;\n\t\t\tbreak;\n\t}\n\n\t// Draw tag marks.\n\tif (tick_spacing > 0.0 && style != SPEED_TEXT_ONLY) {\n\t\tfloat f;\n\n\t\tfor (f = tick_spacing; f < 1.0; f += tick_spacing) {\n\t\t\tif (vertical) {\n\t\t\t\t// Left.\n\t\t\t\tDraw_AlphaFill(\n\t\t\t\t\tx           ,           // x\n\t\t\t\t\ty + (int)(f * height),  // y\n\t\t\t\t\tSPEED_TAG_LENGTH,       // Width\n\t\t\t\t\t1,                      // Height\n\t\t\t\t\tSPEED_WHITE,            // Color\n\t\t\t\t\topacity                 // Opacity\n\t\t\t\t);\n\n\t\t\t\t// Right.\n\t\t\t\tDraw_AlphaFill(\n\t\t\t\t\tx + width - SPEED_TAG_LENGTH + 1,\n\t\t\t\t\ty + (int)(f * height),\n\t\t\t\t\tSPEED_TAG_LENGTH,\n\t\t\t\t\t1,\n\t\t\t\t\tSPEED_WHITE,\n\t\t\t\t\topacity\n\t\t\t\t);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Above.\n\t\t\t\tDraw_AlphaFill(\n\t\t\t\t\tx + (int)(f * width),\n\t\t\t\t\ty,\n\t\t\t\t\t1,\n\t\t\t\t\tSPEED_TAG_LENGTH,\n\t\t\t\t\tSPEED_WHITE,\n\t\t\t\t\topacity\n\t\t\t\t);\n\n\t\t\t\t// Below.\n\t\t\t\tDraw_AlphaFill(\n\t\t\t\t\tx + (int)(f * width),\n\t\t\t\t\ty + height - SPEED_TAG_LENGTH + 1,\n\t\t\t\t\t1,\n\t\t\t\t\tSPEED_TAG_LENGTH,\n\t\t\t\t\tSPEED_WHITE,\n\t\t\t\t\topacity\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t//\n\t// Draw outline.\n\t//\n\tif (style != SPEED_TEXT_ONLY) {\n\t\tif (vertical) {\n\t\t\t// Left.\n\t\t\tDraw_AlphaFill(\n\t\t\t\tx + SPEED_OUTLINE_SPACING,\n\t\t\t\ty,\n\t\t\t\t1,\n\t\t\t\theight,\n\t\t\t\tSPEED_WHITE,\n\t\t\t\topacity\n\t\t\t);\n\n\t\t\t// Right.\n\t\t\tDraw_AlphaFill(\n\t\t\t\tx + width - SPEED_OUTLINE_SPACING,\n\t\t\t\ty,\n\t\t\t\t1,\n\t\t\t\theight,\n\t\t\t\tSPEED_WHITE,\n\t\t\t\topacity\n\t\t\t);\n\t\t}\n\t\telse {\n\t\t\t// Above.\n\t\t\tDraw_AlphaFill(\n\t\t\t\tx,\n\t\t\t\ty + SPEED_OUTLINE_SPACING,\n\t\t\t\twidth,\n\t\t\t\t1,\n\t\t\t\tSPEED_WHITE,\n\t\t\t\topacity\n\t\t\t);\n\n\t\t\t// Below.\n\t\t\tDraw_AlphaFill(\n\t\t\t\tx,\n\t\t\t\ty + height - SPEED_OUTLINE_SPACING,\n\t\t\t\twidth,\n\t\t\t\t1,\n\t\t\t\tSPEED_WHITE,\n\t\t\t\topacity\n\t\t\t);\n\t\t}\n\t}\n\n\t//\n\t// Draw fill.\n\t//\n\tif (style != SPEED_TEXT_ONLY) {\n\t\tif (vertical) {\n\t\t\t// Draw the right color (slower).\n\t\t\tDraw_AlphaFill(\n\t\t\t\tx + SPEED_FILL_SPACING,\n\t\t\t\ty,\n\t\t\t\twidth - (2 * SPEED_FILL_SPACING),\n\t\t\t\theight - color_offset,\n\t\t\t\tcolor1,\n\t\t\t\topacity\n\t\t\t);\n\n\t\t\t// Draw the left color (faster).\n\t\t\tDraw_AlphaFill(\n\t\t\t\tx + SPEED_FILL_SPACING,\n\t\t\t\ty + height - color_offset,\n\t\t\t\twidth - (2 * SPEED_FILL_SPACING),\n\t\t\t\tcolor_offset,\n\t\t\t\tcolor2,\n\t\t\t\topacity\n\t\t\t);\n\t\t}\n\t\telse {\n\t\t\t// Draw the right color (slower).\n\t\t\tDraw_AlphaFill(\n\t\t\t\tx + color_offset,\n\t\t\t\ty + SPEED_FILL_SPACING,\n\t\t\t\twidth - color_offset,\n\t\t\t\theight - (2 * SPEED_FILL_SPACING),\n\t\t\t\tcolor1,\n\t\t\t\topacity\n\t\t\t);\n\n\t\t\t// Draw the left color (faster).\n\t\t\tDraw_AlphaFill(\n\t\t\t\tx,\n\t\t\t\ty + SPEED_FILL_SPACING,\n\t\t\t\tcolor_offset,\n\t\t\t\theight - (2 * SPEED_FILL_SPACING),\n\t\t\t\tcolor2,\n\t\t\t\topacity\n\t\t\t);\n\t\t}\n\t}\n\n\tif (text_align == SPEED_TEXT_ALIGN_NONE) {\n\t\treturn;\n\t}\n\n\tif (split) {\n\t\tsnprintf(speed_text, sizeof(speed_text), \"%4d %4d %4d\", (int)velocity[0], (int)velocity[1], (int)velocity[2]);\n\t}\n\telse {\n\t\tsnprintf(speed_text, sizeof(speed_text), \"%4d\", player_speed);\n\t}\n\n\t// Draw the speed text.\n\tif (vertical && vertical_text) {\n\t\tint i = 1;\n\t\tint len = 0;\n\n\t\t// Align the text accordingly.\n\t\tswitch (text_align) {\n\t\t\tcase SPEED_TEXT_ALIGN_NONE:\t\treturn;\n\t\t\tcase SPEED_TEXT_ALIGN_FAR:\t\ty = y + height - 4 * 8; break;\n\t\t\tcase SPEED_TEXT_ALIGN_CENTER:\ty = Q_rint(y + height / 2.0 - 16); break;\n\t\t\tcase SPEED_TEXT_ALIGN_CLOSE:\n\t\t\tdefault: break;\n\t\t}\n\n\t\tlen = strlen(va(\"%d\", player_speed));\n\n\t\t// 10^len\n\t\twhile (len > 0) {\n\t\t\ti *= 10;\n\t\t\tlen--;\n\t\t}\n\n\t\t// Write one number per row.\n\t\tfor (; i > 1; i /= 10) {\n\t\t\tint next;\n\t\t\tnext = (i / 10);\n\n\t\t\t// Really make sure we don't try division by zero :)\n\t\t\tif (next <= 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tDraw_SString(Q_rint(x + width / 2.0 - 4 * scale), y, va(\"%1d\", (player_speed % i) / next), scale, proportional);\n\t\t\ty += 8;\n\t\t}\n\t}\n\telse {\n\t\ttext_alignment_t alignment = text_align_left;\n\n\t\tif (text_align == SPEED_TEXT_ALIGN_FAR) {\n\t\t\talignment = text_align_right;\n\t\t}\n\t\telse if (text_align == SPEED_TEXT_ALIGN_CENTER) {\n\t\t\talignment = text_align_center;\n\t\t}\n\n\t\tDraw_SStringAligned(x, y + height / 2.0 - 4 * scale, speed_text, scale, 1.0f, proportional, alignment, x + width);\n\t}\n}\n\nstatic void SCR_HUD_DrawSpeed(hud_t *hud)\n{\n\tint width, height;\n\tint x, y;\n\n\tstatic cvar_t *hud_speed_xyz = NULL,\n\t\t*hud_speed_width,\n\t\t*hud_speed_height,\n\t\t*hud_speed_tick_spacing,\n\t\t*hud_speed_opacity,\n\t\t*hud_speed_color_stopped,\n\t\t*hud_speed_color_normal,\n\t\t*hud_speed_color_fast,\n\t\t*hud_speed_color_fastest,\n\t\t*hud_speed_color_insane,\n\t\t*hud_speed_vertical,\n\t\t*hud_speed_vertical_text,\n\t\t*hud_speed_text_align,\n\t\t*hud_speed_style,\n\t\t*hud_speed_scale,\n\t\t*hud_speed_proportional;\n\n\tif (hud_speed_xyz == NULL)    // first time\n\t{\n\t\thud_speed_xyz = HUD_FindVar(hud, \"xyz\");\n\t\thud_speed_width = HUD_FindVar(hud, \"width\");\n\t\thud_speed_height = HUD_FindVar(hud, \"height\");\n\t\thud_speed_tick_spacing = HUD_FindVar(hud, \"tick_spacing\");\n\t\thud_speed_opacity = HUD_FindVar(hud, \"opacity\");\n\t\thud_speed_color_stopped = HUD_FindVar(hud, \"color_stopped\");\n\t\thud_speed_color_normal = HUD_FindVar(hud, \"color_normal\");\n\t\thud_speed_color_fast = HUD_FindVar(hud, \"color_fast\");\n\t\thud_speed_color_fastest = HUD_FindVar(hud, \"color_fastest\");\n\t\thud_speed_color_insane = HUD_FindVar(hud, \"color_insane\");\n\t\thud_speed_vertical = HUD_FindVar(hud, \"vertical\");\n\t\thud_speed_vertical_text = HUD_FindVar(hud, \"vertical_text\");\n\t\thud_speed_text_align = HUD_FindVar(hud, \"text_align\");\n\t\thud_speed_style = HUD_FindVar(hud, \"style\");\n\t\thud_speed_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_speed_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\twidth = max(0, hud_speed_width->value) * hud_speed_scale->value;\n\theight = max(0, hud_speed_height->value) * hud_speed_scale->value;\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tSCR_DrawHUDSpeed(\n\t\t\tx, y, width, height,\n\t\t\thud_speed_xyz->value,\n\t\t\thud_speed_tick_spacing->value,\n\t\t\thud_speed_opacity->value,\n\t\t\thud_speed_vertical->value,\n\t\t\thud_speed_vertical_text->value,\n\t\t\thud_speed_text_align->value,\n\t\t\thud_speed_color_stopped->value,\n\t\t\thud_speed_color_normal->value,\n\t\t\thud_speed_color_fast->value,\n\t\t\thud_speed_color_fastest->value,\n\t\t\thud_speed_color_insane->value,\n\t\t\thud_speed_style->integer,\n\t\t\thud_speed_scale->value,\n\t\t\thud_speed_proportional->integer\n\t\t);\n\t}\n}\n\n// speed2\nstatic void SCR_HUD_DrawSpeed2(hud_t *hud)\n{\n\tint width, height;\n\tint x, y;\n\tchar player_speed_text[20];\n\tfloat text_length;\n\n\tstatic cvar_t *hud_speed2_xyz = NULL,\n\t\t*hud_speed2_opacity,\n\t\t*hud_speed2_color_stopped,\n\t\t*hud_speed2_color_normal,\n\t\t*hud_speed2_color_fast,\n\t\t*hud_speed2_color_fastest,\n\t\t*hud_speed2_color_insane,\n\t\t*hud_speed2_radius,\n\t\t*hud_speed2_wrapspeed,\n\t\t*hud_speed2_orientation,\n\t\t*hud_speed2_scale,\n\t\t*hud_speed2_proportional;\n\n\tif (hud_speed2_xyz == NULL)    // first time\n\t{\n\t\thud_speed2_xyz = HUD_FindVar(hud, \"xyz\");\n\t\thud_speed2_opacity = HUD_FindVar(hud, \"opacity\");\n\t\thud_speed2_color_stopped = HUD_FindVar(hud, \"color_stopped\");\n\t\thud_speed2_color_normal = HUD_FindVar(hud, \"color_normal\");\n\t\thud_speed2_color_fast = HUD_FindVar(hud, \"color_fast\");\n\t\thud_speed2_color_fastest = HUD_FindVar(hud, \"color_fastest\");\n\t\thud_speed2_color_insane = HUD_FindVar(hud, \"color_insane\");\n\t\thud_speed2_radius = HUD_FindVar(hud, \"radius\");\n\t\thud_speed2_wrapspeed = HUD_FindVar(hud, \"wrapspeed\");\n\t\thud_speed2_orientation = HUD_FindVar(hud, \"orientation\");\n\t\thud_speed2_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_speed2_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\t// Calculate the height and width based on the radius.\n\tswitch ((int)hud_speed2_orientation->value) {\n\t\tcase HUD_SPEED2_ORIENTATION_LEFT:\n\t\tcase HUD_SPEED2_ORIENTATION_RIGHT:\n\t\t\theight = max(0, 2 * hud_speed2_radius->value);\n\t\t\twidth = max(0, (hud_speed2_radius->value));\n\t\t\tbreak;\n\t\tcase HUD_SPEED2_ORIENTATION_DOWN:\n\t\tcase HUD_SPEED2_ORIENTATION_UP:\n\t\tdefault:\n\t\t\t// Include the height of the speed text in the height.\n\t\t\theight = max(0, (hud_speed2_radius->value));\n\t\t\twidth = max(0, 2 * hud_speed2_radius->value);\n\t\t\tbreak;\n\t}\n\n\twidth *= hud_speed2_scale->value;\n\theight *= hud_speed2_scale->value;\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tfloat radius;\n\t\tint player_speed;\n\t\tint arc_length;\n\t\tint color1, color2;\n\t\tint text_x = x;\n\t\tint text_y = y;\n\t\tvec_t *velocity;\n\n\t\t// Start and end points for the needle\n\t\tint needle_start_x = 0;\n\t\tint needle_start_y = 0;\n\t\tint needle_end_x = 0;\n\t\tint needle_end_y = 0;\n\n\t\t// The length of the arc between the zero point\n\t\t// and where the needle is pointing at.\n\t\tint needle_offset = 0;\n\n\t\t// The angle between the zero point and the position\n\t\t// that the needle is drawn on.\n\t\tfloat needle_angle = 0.0;\n\n\t\t// The angle where to start drawing the half circle and where to end.\n\t\t// This depends on the orientation of the circle (left, right, up, down).\n\t\tfloat circle_startangle = 0.0;\n\t\tfloat circle_endangle = 0.0;\n\n\t\t// Avoid divison by zero.\n\t\tif (hud_speed2_radius->value <= 0) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Get the velocity.\n\t\tif (cl.players[cl.playernum].spectator && Cam_TrackNum() >= 0) {\n\t\t\tvelocity = cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK].playerstate[Cam_TrackNum()].velocity;\n\t\t}\n\t\telse {\n\t\t\tvelocity = cl.simvel;\n\t\t}\n\n\t\t// Calculate the speed\n\t\tif (!hud_speed2_xyz->value) {\n\t\t\t// Based on XY.\n\t\t\tplayer_speed = sqrt(velocity[0] * velocity[0]\n\t\t\t\t\t\t\t\t+ velocity[1] * velocity[1]);\n\t\t}\n\t\telse {\n\t\t\t// Based on XYZ.\n\t\t\tplayer_speed = sqrt(velocity[0] * velocity[0]\n\t\t\t\t\t\t\t\t+ velocity[1] * velocity[1]\n\t\t\t\t\t\t\t\t+ velocity[2] * velocity[2]);\n\t\t}\n\n\t\tsnprintf(player_speed_text, sizeof(player_speed_text), \"%d\", player_speed);\n\t\ttext_length = Draw_StringLength(player_speed_text, -1, hud_speed2_scale->value, hud_speed2_proportional->integer);\n\n\t\t// Set the color based on the wrap speed.\n\t\tswitch ((int)(player_speed / hud_speed2_wrapspeed->value)) {\n\t\t\tcase 0:\n\t\t\t\tcolor1 = hud_speed2_color_stopped->integer;\n\t\t\t\tcolor2 = hud_speed2_color_normal->integer;\n\t\t\t\tbreak;\n\t\t\tcase 1:\n\t\t\t\tcolor1 = hud_speed2_color_normal->integer;\n\t\t\t\tcolor2 = hud_speed2_color_fast->integer;\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tcolor1 = hud_speed2_color_fast->integer;\n\t\t\t\tcolor2 = hud_speed2_color_fastest->integer;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tcolor1 = hud_speed2_color_fastest->integer;\n\t\t\t\tcolor2 = hud_speed2_color_insane->integer;\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// Set some properties how to draw the half circle, needle and text\n\t\t// based on the orientation of the hud item.\n\t\tswitch ((int)hud_speed2_orientation->value) {\n\t\t\tcase HUD_SPEED2_ORIENTATION_LEFT:\n\t\t\t{\n\t\t\t\tx += width;\n\t\t\t\ty += height / 2;\n\t\t\t\tcircle_startangle = M_PI / 2.0;\n\t\t\t\tcircle_endangle = (3 * M_PI) / 2.0;\n\n\t\t\t\ttext_x = x - text_length;\n\t\t\t\ttext_y = y - 8 * hud_speed2_scale->value * 0.5;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase HUD_SPEED2_ORIENTATION_RIGHT:\n\t\t\t{\n\t\t\t\ty += height / 2;\n\t\t\t\tcircle_startangle = (3 * M_PI) / 2.0;\n\t\t\t\tcircle_endangle = (5 * M_PI) / 2.0;\n\t\t\t\tneedle_end_y = y + hud_speed2_radius->value * sin(needle_angle);\n\n\t\t\t\ttext_x = x;\n\t\t\t\ttext_y = y - 8 * hud_speed2_scale->value * 0.5;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase HUD_SPEED2_ORIENTATION_DOWN:\n\t\t\t{\n\t\t\t\tx += width / 2;\n\t\t\t\tcircle_startangle = M_PI;\n\t\t\t\tcircle_endangle = 2 * M_PI;\n\t\t\t\tneedle_end_y = y + hud_speed2_radius->value * sin(needle_angle);\n\n\t\t\t\ttext_x = x - 0.5 * text_length;\n\t\t\t\ttext_y = y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase HUD_SPEED2_ORIENTATION_UP:\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tx += width / 2;\n\t\t\t\ty += height;\n\t\t\t\tcircle_startangle = 0;\n\t\t\t\tcircle_endangle = M_PI;\n\t\t\t\tneedle_end_y = y - hud_speed2_radius->value * sin(needle_angle);\n\n\t\t\t\ttext_x = x - 0.5 * text_length;\n\t\t\t\ttext_y = y - 8 * hud_speed2_scale->value;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t//\n\t\t// Calculate the offsets and angles.\n\t\t//\n\t\t{\n\t\t\t// Calculate the arc length of the half circle background.\n\t\t\tarc_length = fabs((circle_endangle - circle_startangle) * hud_speed2_radius->value);\n\n\t\t\t// Calculate the angle where the speed needle should point.\n\t\t\tneedle_offset = arc_length * (player_speed % Q_rint(hud_speed2_wrapspeed->value)) / Q_rint(hud_speed2_wrapspeed->value);\n\t\t\tneedle_angle = needle_offset / hud_speed2_radius->value;\n\n\t\t\t// Draw from the center of the half circle.\n\t\t\tneedle_start_x = x;\n\t\t\tneedle_start_y = y;\n\t\t}\n\n\t\tradius = hud_speed2_radius->value * hud_speed2_scale->value;\n\n\t\t// Set the needle end point depending on the orientation of the hud item.\n\t\tswitch ((int)hud_speed2_orientation->value) {\n\t\t\tcase HUD_SPEED2_ORIENTATION_LEFT:\n\t\t\t{\n\t\t\t\tneedle_end_x = x - radius * sin(needle_angle);\n\t\t\t\tneedle_end_y = y + radius * cos(needle_angle);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase HUD_SPEED2_ORIENTATION_RIGHT:\n\t\t\t{\n\t\t\t\tneedle_end_x = x + radius * sin(needle_angle);\n\t\t\t\tneedle_end_y = y - radius * cos(needle_angle);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase HUD_SPEED2_ORIENTATION_DOWN:\n\t\t\t{\n\t\t\t\tneedle_end_x = x + radius * cos(needle_angle);\n\t\t\t\tneedle_end_y = y + radius * sin(needle_angle);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase HUD_SPEED2_ORIENTATION_UP:\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tneedle_end_x = x - radius * cos(needle_angle);\n\t\t\t\tneedle_end_y = y - radius * sin(needle_angle);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Draw the speed-o-meter background.\n\t\tDraw_AlphaPieSlice(\n\t\t\tx, y,                            // Position\n\t\t\tradius,                          // Radius\n\t\t\tcircle_startangle,               // Start angle\n\t\t\tcircle_endangle - needle_angle,  // End angle\n\t\t\t1,                               // Thickness\n\t\t\ttrue,                            // Fill\n\t\t\tcolor1,                          // Color\n\t\t\thud_speed2_opacity->value        // Opacity\n\t\t);\n\n\t\t// Draw a pie slice that shows the \"color\" of the speed.\n\t\tDraw_AlphaPieSlice(\n\t\t\tx, y,                               // Position\n\t\t\tradius,                             // Radius\n\t\t\tcircle_endangle - needle_angle,     // Start angle\n\t\t\tcircle_endangle,                    // End angle\n\t\t\t1,                                  // Thickness\n\t\t\ttrue,                               // Fill\n\t\t\tcolor2,                             // Color\n\t\t\thud_speed2_opacity->value           // Opacity\n\t\t);\n\n\t\t// Draw the \"needle attachment\" circle.\n\t\tDraw_AlphaCircle(x, y, 2.0, 1, true, 15, hud_speed2_opacity->value);\n\n\t\t// Draw the speed needle.\n\t\tDraw_AlphaLineRGB(needle_start_x, needle_start_y, needle_end_x, needle_end_y, 1, RGBA_TO_COLOR(250, 250, 250, 255 * hud_speed2_opacity->value));\n\n\t\t// Draw the speed.\n\t\tDraw_SString(text_x, text_y, player_speed_text, hud_speed2_scale->value, hud_speed2_proportional->integer);\n\t}\n}\n\nvoid Speed_HudInit(void)\n{\n\t// init speed\n\tHUD_Register(\"speed\", NULL, \"Shows your current running speed. It is measured over XY or XYZ axis depending on \\'xyz\\' property.\",\n\t\tHUD_PLUSMINUS, ca_active, 7, SCR_HUD_DrawSpeed,\n\t\t\"0\", \"top\", \"center\", \"bottom\", \"0\", \"-5\", \"0\", \"0 0 0\", NULL,\n\t\t\"xyz\", \"0\",\n\t\t\"width\", \"160\",\n\t\t\"height\", \"15\",\n\t\t\"opacity\", \"1.0\",\n\t\t\"tick_spacing\", \"0.2\",\n\t\t\"color_stopped\", SPEED_STOPPED,\n\t\t\"color_normal\", SPEED_NORMAL,\n\t\t\"color_fast\", SPEED_FAST,\n\t\t\"color_fastest\", SPEED_FASTEST,\n\t\t\"color_insane\", SPEED_INSANE,\n\t\t\"vertical\", \"0\",\n\t\t\"vertical_text\", \"1\",\n\t\t\"text_align\", \"1\",\n\t\t\"style\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\t// Init speed2 (half circle thingie).\n\tHUD_Register(\"speed2\", NULL, \"Shows your current running speed. It is measured over XY or XYZ axis depending on \\'xyz\\' property.\",\n\t\tHUD_PLUSMINUS, ca_active, 7, SCR_HUD_DrawSpeed2,\n\t\t\"0\", \"top\", \"center\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"xyz\", \"0\",\n\t\t\"opacity\", \"1.0\",\n\t\t\"color_stopped\", SPEED_STOPPED,\n\t\t\"color_normal\", SPEED_NORMAL,\n\t\t\"color_fast\", SPEED_FAST,\n\t\t\"color_fastest\", SPEED_FASTEST,\n\t\t\"color_insane\", SPEED_INSANE,\n\t\t\"radius\", \"50.0\",\n\t\t\"wrapspeed\", \"500\",\n\t\t\"orientation\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\tCvar_Register(&show_speed);\n\tCvar_Register(&show_speed_x);\n\tCvar_Register(&show_speed_y);\n\tCvar_ResetCurrentGroup();\n}\n\nvoid SCR_DrawSpeed(void)\n{\n\tdouble t;\n\tint x, y, mynum;\n\tchar str[80];\n\tvec3_t vel;\n\tfloat speed;\n\tstatic float maxspeed = 0, display_speed = -1;\n\tstatic double lastframetime = 0;\n\tstatic int lastmynum = -1;\n\textern cvar_t scr_newHud;\n\n\tif (!show_speed.integer || scr_newHud.integer == 1) {\n\t\t// newHud has its own speed\n\t\treturn;\n\t}\n\n\tt = curtime;\n\tif (!cl.spectator || (mynum = Cam_TrackNum()) == -1) {\n\t\tmynum = cl.playernum;\n\t}\n\n\tif (mynum != lastmynum) {\n\t\tlastmynum = mynum;\n\t\tlastframetime = t;\n\t\tdisplay_speed = -1;\n\t\tmaxspeed = 0;\n\t}\n\n\tif (!cl.spectator || cls.demoplayback || mynum == cl.playernum) {\n\t\tVectorCopy(cl.simvel, vel);\n\t}\n\telse {\n\t\tVectorCopy(cl.frames[cl.validsequence & UPDATE_MASK].playerstate[mynum].velocity, vel);\n\t}\n\n\tvel[2] = 0;\n\tspeed = VectorLength(vel);\n\n\tmaxspeed = max(maxspeed, speed);\n\n\tif (display_speed >= 0) {\n\t\tsnprintf(str, sizeof(str), \"%3d%s\", (int)display_speed, show_speed.value == 2 ? \" SPD\" : \"\");\n\t\tx = ELEMENT_X_COORD(show_speed);\n\t\ty = ELEMENT_Y_COORD(show_speed);\n\t\tDraw_String(x, y, str);\n\t}\n\n\tif (t - lastframetime >= 0.1) {\n\t\tlastframetime = t;\n\t\tdisplay_speed = maxspeed;\n\t\tmaxspeed = 0;\n\t}\n}\n"
  },
  {
    "path": "src/hud_teaminfo.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// hud_teaminfo.c\n\n#include \"quakedef.h\"\n#include \"hud.h\"\n#include \"teamplay.h\"\n#include \"hud_common.h\"\n#include \"vx_stuff.h\"\n#include \"utils.h\"\n// Get rid of these once we remove matrix scaling from cl_screen.c version...\n#include \"gl_model.h\"\n#include \"fonts.h\"\n\n#define FONT_WIDTH 8\n\nstatic void Update_TeamInfo(void);\nmpic_t* SCR_GetWeaponIconByFlag(int flag);\n\nstatic cvar_t scr_shownick_order           = { \"scr_shownick_order\", \"%p%n %a/%H %w\" };\nstatic cvar_t scr_shownick_frame_color     = { \"scr_shownick_frame_color\", \"10 0 0 120\", CVAR_COLOR };\nstatic cvar_t scr_shownick_scale           = { \"scr_shownick_scale\",\t\t\"1\" };\nstatic cvar_t scr_shownick_y               = { \"scr_shownick_y\",\t\t\t\"0\" };\nstatic cvar_t scr_shownick_x               = { \"scr_shownick_x\",\t\t\t\"0\" };\nstatic cvar_t scr_shownick_name_width      = { \"scr_shownick_name_width\",\t\"6\" };\nstatic cvar_t scr_shownick_time            = { \"scr_shownick_time\",\t\t\"0.8\" };\nstatic cvar_t scr_shownick_proportional    = { \"scr_shownick_proportional\", \"0\" };\nstatic cvar_t scr_shownick_show_ammo\t   = { \"scr_shownick_show_ammo\", \"0\" };\n\nstatic cvar_t scr_teaminfo_order           = { \"scr_teaminfo_order\", \"%p%n $x10%l$x11 %a/%H %w\", CVAR_NONE };\nstatic cvar_t scr_teaminfo_align_right     = { \"scr_teaminfo_align_right\", \"1\" };\nstatic cvar_t scr_teaminfo_frame_color     = { \"scr_teaminfo_frame_color\", \"10 0 0 120\", CVAR_COLOR };\nstatic cvar_t scr_teaminfo_scale           = { \"scr_teaminfo_scale\",       \"1\" };\nstatic cvar_t scr_teaminfo_y               = { \"scr_teaminfo_y\",           \"0\" };\nstatic cvar_t scr_teaminfo_x               = { \"scr_teaminfo_x\",           \"0\" };\nstatic cvar_t scr_teaminfo_loc_width       = { \"scr_teaminfo_loc_width\",   \"5\" };\nstatic cvar_t scr_teaminfo_name_width      = { \"scr_teaminfo_name_width\",  \"6\" };\nstatic cvar_t scr_teaminfo_low_health      = { \"scr_teaminfo_low_health\",  \"25\" };\nstatic cvar_t scr_teaminfo_armor_style     = { \"scr_teaminfo_armor_style\", \"3\" };\nstatic cvar_t scr_teaminfo_powerup_style   = { \"scr_teaminfo_powerup_style\", \"1\" };\nstatic cvar_t scr_teaminfo_flag_style      = { \"scr_teaminfo_flag_style\",  \"1\" };\nstatic cvar_t scr_teaminfo_weapon_style    = { \"scr_teaminfo_weapon_style\",\"1\" };\nstatic cvar_t scr_teaminfo_show_ammo\t   = { \"scr_teaminfo_show_ammo\",\"0\" };\nstatic cvar_t scr_teaminfo_show_countdown  = { \"scr_teaminfo_show_countdown\",\"1\" };\nstatic cvar_t scr_teaminfo_show_enemies    = { \"scr_teaminfo_show_enemies\",\"0\" };\nstatic cvar_t scr_teaminfo_show_self       = { \"scr_teaminfo_show_self\",   \"2\" };\nstatic cvar_t scr_teaminfo_proportional    = { \"scr_teaminfo_proportional\", \"0\"};\ncvar_t scr_teaminfo                        = { \"scr_teaminfo\",             \"1\" };   // non-static for menu\n\nstatic int SCR_HudDrawTeamInfoPlayer(ti_player_t *ti_cl, float x, int y, int maxname, int maxloc, qbool width_only, float scale, const char* layout, int weapon_style, int show_ammo, int show_countdown, int armor_style, int powerup_style, int flag_style, int low_health, qbool proportional);\n\nstatic int HUD_CompareTeamInfoSlots(const void* lhs_, const void* rhs_)\n{\n\tint lhs = *(const int*)lhs_;\n\tint rhs = *(const int*)rhs_;\n\tint lhs_pos = -1;\n\tint rhs_pos = -1;\n\tint i;\n\n\tfor (i = 0; i < n_players; ++i) {\n\t\tif (sorted_players[i].playernum == lhs) {\n\t\t\tlhs_pos = i;\n\t\t}\n\t\tif (sorted_players[i].playernum == rhs) {\n\t\t\trhs_pos = i;\n\t\t}\n\t}\n\n\treturn lhs_pos - rhs_pos;\n}\n\nvoid SCR_HUD_DrawTeamInfo(hud_t *hud)\n{\n\tint x, y, _y, width, height;\n\tint i, j, k, slots[MAX_CLIENTS], slots_num, maxname, maxloc;\n\tchar tmp[1024], *nick;\n\tfloat header_spacing;\n\n\t// Used for hud_teaminfo, data is collected in screen.c / scr_teaminfo\n\textern ti_player_t ti_clients[MAX_CLIENTS];\n\n\textern qbool hud_editor;\n\n\tstatic cvar_t\n\t\t*hud_teaminfo_weapon_style = NULL,\n\t\t*hud_teaminfo_align_right,\n\t\t*hud_teaminfo_loc_width,\n\t\t*hud_teaminfo_name_width,\n\t\t*hud_teaminfo_show_ammo,\n\t\t*hud_teaminfo_show_countdown,\n\t\t*hud_teaminfo_show_enemies,\n\t\t*hud_teaminfo_show_self,\n\t\t*hud_teaminfo_show_headers,\n\t\t*hud_teaminfo_scale,\n\t\t*hud_teaminfo_armor_style,\n\t\t*hud_teaminfo_powerup_style,\n\t\t*hud_teaminfo_flag_style,\n\t\t*hud_teaminfo_low_health,\n\t\t*hud_teaminfo_layout,\n\t\t*hud_teaminfo_proportional,\n\t\t*hud_teaminfo_header_spacing;\n\n\tif (hud_teaminfo_weapon_style == NULL) {\n\t\t// first time\n\t\thud_teaminfo_weapon_style = HUD_FindVar(hud, \"weapon_style\");\n\t\thud_teaminfo_align_right = HUD_FindVar(hud, \"align_right\");\n\t\thud_teaminfo_loc_width = HUD_FindVar(hud, \"loc_width\");\n\t\thud_teaminfo_name_width = HUD_FindVar(hud, \"name_width\");\n\t\thud_teaminfo_show_ammo = HUD_FindVar(hud, \"show_ammo\");\n\t\thud_teaminfo_show_enemies = HUD_FindVar(hud, \"show_enemies\");\n\t\thud_teaminfo_show_self = HUD_FindVar(hud, \"show_self\");\n\t\thud_teaminfo_show_headers = HUD_FindVar(hud, \"show_headers\");\n\t\thud_teaminfo_show_countdown = HUD_FindVar(hud, \"show_countdown\");\n\t\thud_teaminfo_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_teaminfo_armor_style = HUD_FindVar(hud, \"armor_style\");\n\t\thud_teaminfo_powerup_style = HUD_FindVar(hud, \"powerup_style\");\n\t\thud_teaminfo_flag_style = HUD_FindVar(hud, \"flag_style\");\n\t\thud_teaminfo_low_health = HUD_FindVar(hud, \"low_health\");\n\t\thud_teaminfo_layout = HUD_FindVar(hud, \"layout\");\n\t\thud_teaminfo_proportional = HUD_FindVar(hud, \"proportional\");\n\t\thud_teaminfo_header_spacing = HUD_FindVar(hud, \"header_spacing\");\n\t}\n\n\t// Don't update hud item unless first view is beeing displayed\n\tif (CL_MultiviewCurrentView() != 1 && CL_MultiviewCurrentView() != 0) {\n\t\treturn;\n\t}\n\n\tif (cls.mvdplayback) {\n\t\tUpdate_TeamInfo();\n\t}\n\n\t// fill data we require to draw teaminfo\n\tfor (maxloc = maxname = slots_num = i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!cl.players[i].name[0] || cl.players[i].spectator || !ti_clients[i].time || ti_clients[i].time + 5 < r_refdef2.time) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// do not show enemy players unless it's MVD and user wishes to show them\n\t\tif (VX_TrackerIsEnemy(i) && (!cls.mvdplayback || !hud_teaminfo_show_enemies->integer)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// do not show tracked player to spectator\n\t\tif ((cl.spectator && Cam_TrackNum() == i) && hud_teaminfo_show_self->integer == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// dynamically guess max length of name/location\n\t\tnick = (ti_clients[i].nick[0] ? ti_clients[i].nick : cl.players[i].name); // we use nick or name\n\t\tmaxname = max(maxname, strlen(TP_ParseFunChars(nick, false)));\n\n\t\tstrlcpy(tmp, TP_LocationName(ti_clients[i].org), sizeof(tmp));\n\t\tmaxloc = max(maxloc, strlen(TP_ParseFunChars(tmp, false)));\n\n\t\tslots[slots_num++] = i;\n\t}\n\n\tqsort(slots, slots_num, sizeof(slots[0]), HUD_CompareTeamInfoSlots);\n\n\t// well, better use fixed loc length\n\tmaxloc = bound(0, hud_teaminfo_loc_width->integer, 100);\n\t// limit name length\n\tmaxname = bound(0, maxname, hud_teaminfo_name_width->integer);\n\n\theader_spacing = max(0, hud_teaminfo_header_spacing->value);\n\n\t// this doesn't draw anything, just calculate width\n\twidth = SCR_HudDrawTeamInfoPlayer(&ti_clients[0], 0, 0, maxname, maxloc, true, hud_teaminfo_scale->value, hud_teaminfo_layout->string, hud_teaminfo_weapon_style->integer, hud_teaminfo_show_ammo->integer, hud_teaminfo_show_countdown->integer, hud_teaminfo_armor_style->integer, hud_teaminfo_powerup_style->integer, hud_teaminfo_flag_style->integer, hud_teaminfo_low_health->integer, hud_teaminfo_proportional->integer);\n\theight = FONTWIDTH * hud_teaminfo_scale->value * (hud_teaminfo_show_enemies->integer && hud_teaminfo_show_headers->integer ? slots_num + max(2 * n_teams - 1, 0) * header_spacing : slots_num);\n\n\tif (hud_editor) {\n\t\tHUD_PrepareDraw(hud, width, FONTWIDTH, &x, &y);\n\t}\n\n\tif (!slots_num) {\n\t\treturn;\n\t}\n\n\tif (!cl.teamplay) {\n\t\t// non teamplay mode\n\t\treturn;\n\t}\n\n\tif (!HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\treturn;\n\t}\n\n\t_y = y;\n\tx = (hud_teaminfo_align_right->value ? x - (width * (FONTWIDTH * hud_teaminfo_scale->value)) : x);\n\n\t// If multiple teams are displayed then sort the display and print team header on overlay\n\tk = 0;\n\tif (hud_teaminfo_show_enemies->integer) {\n\t\twhile (sorted_teams[k].name) {\n\t\t\t// hmx : different name/scores alignment options are possible in the header\n\t\t\t// in which case, make sure to differentiate name width vs teaminfo width\n\t\t\t// i.e int name_width = Draw_SString()\n\t\t\tif (hud_teaminfo_show_headers->integer) {\n\t\t\t\tif (k > 0) { // separator between teams\n\t\t\t\t\t_y += FONTWIDTH * hud_teaminfo_scale->value * header_spacing;\n\t\t\t\t}\n\n\t\t\t\tDraw_SString(x, _y, sorted_teams[k].name, hud_teaminfo_scale->value, hud_teaminfo_proportional->integer);\n\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s %4i\", TP_ParseFunChars(\"$.\", false), sorted_teams[k].frags);\n\t\t\t\tDraw_SStringAligned(x, _y, tmp, hud_teaminfo_scale->value, 1.0f, hud_teaminfo_proportional->integer, text_align_right, x + width);\n\t\t\t\t_y += FONTWIDTH * hud_teaminfo_scale->value;\n\t\t\t}\n\t\t\tfor (j = 0; j < slots_num; j++) {\n\t\t\t\ti = slots[j];\n\t\t\t\tif (!strcmp(cl.players[i].team, sorted_teams[k].name)) {\n\t\t\t\t\tSCR_HudDrawTeamInfoPlayer(&ti_clients[i], x, _y, maxname, maxloc, false, hud_teaminfo_scale->value, hud_teaminfo_layout->string, hud_teaminfo_weapon_style->integer, hud_teaminfo_show_ammo->integer, hud_teaminfo_show_countdown->integer, hud_teaminfo_armor_style->integer, hud_teaminfo_powerup_style->integer, hud_teaminfo_flag_style->integer, hud_teaminfo_low_health->integer, hud_teaminfo_proportional->integer);\n\t\t\t\t\t_y += FONTWIDTH * hud_teaminfo_scale->value;\n\t\t\t\t}\n\t\t\t}\n\t\t\tk++;\n\t\t}\n\t}\n\telse {\n\t\tfor (j = 0; j < slots_num; j++) {\n\t\t\ti = slots[j];\n\t\t\tSCR_HudDrawTeamInfoPlayer(&ti_clients[i], x, _y, maxname, maxloc, false, hud_teaminfo_scale->value, hud_teaminfo_layout->string, hud_teaminfo_weapon_style->integer, hud_teaminfo_show_ammo->integer, hud_teaminfo_show_countdown->integer, hud_teaminfo_armor_style->integer, hud_teaminfo_powerup_style->integer, hud_teaminfo_flag_style->integer, hud_teaminfo_low_health->integer, hud_teaminfo_proportional->integer);\n\t\t\t_y += FONTWIDTH * hud_teaminfo_scale->value;\n\t\t}\n\t}\n}\n\nqbool Has_Both_RL_and_LG(int flags)\n{\n\treturn (flags & IT_ROCKET_LAUNCHER) && (flags & IT_LIGHTNING);\n}\n\nstatic int SCR_HudDrawTeamInfoPlayer(ti_player_t *ti_cl, float x, int y, int maxname, int maxloc, qbool width_only, float scale, const char* layout, int weapon_style, int show_ammo, int show_countdown, int armor_style, int powerup_style, int flag_style, int low_health, qbool proportional)\n{\n\textern cvar_t tp_name_rlg;\n\tchar *s, *loc, tmp[1024], tmp2[MAX_MACRO_STRING], *aclr, *txtclr;\n\tfloat x_in = x; // save x\n\tint i, a;\n\tqbool isDeadCA, isRespawning;\n\tmpic_t *pic;\n\tfloat width;\n\tfloat font_width = scale * FONT_WIDTH;\n\tfloat alpha;\n\n\textern mpic_t *sb_face_invis, *sb_face_quad, *sb_face_invuln;\n\textern mpic_t *sb_armor[3];\n\textern mpic_t *sb_items[32];\n\n\tif (!ti_cl) {\n\t\treturn 0;\n\t}\n\n\ttxtclr = \"&cfff\";\n\tisDeadCA = ti_cl->isdead;\n\tisRespawning = isDeadCA && ti_cl->timetospawn > 0 && ti_cl->timetospawn < 999;\n\t\n\tif (isDeadCA) {\n\t\talpha = 0.25;\n\t}\n\telse {\n\t\talpha = 1.0;\n\t}\n\n\ti = ti_cl->client;\n\tif (i < 0 || i >= MAX_CLIENTS) {\n\t\tCom_DPrintf(\"SCR_Draw_TeamInfoPlayer: wrong client %d\\n\", i);\n\t\treturn 0;\n\t}\n\n\t// this limit len of string because TP_ParseFunChars() do not check overflow\n\tstrlcpy(tmp2, layout, sizeof(tmp2));\n\tstrlcpy(tmp2, TP_ParseFunChars(tmp2, false), sizeof(tmp2));\n\ts = tmp2;\n\n\tswitch (BestWeaponFromStatItems(ti_cl->items)){\n\t\tcase IT_LIGHTNING:\n\t\t\ta = ti_cl->cells;\n\t\t\tbreak;\n\t\tcase IT_ROCKET_LAUNCHER:\n\t\tcase IT_GRENADE_LAUNCHER:\n\t\t\ta = ti_cl->rockets;\n\t\t\tbreak;\n\t\tcase IT_SUPER_NAILGUN:\n\t\tcase IT_NAILGUN:\n\t\t\ta = ti_cl->nails;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\ta = ti_cl->shells;\n\t\t\tbreak;\n\t}\n\n\t//\n\t// parse/draw string like this \"%n %h:%a %l %p %w\"\n\t//\n\tfor (; *s; s++) {\n\t\tswitch ((int)s[0]) {\n\t\t\tcase '%':\n\t\t\t\ts++; // advance\n\n\t\t\t\tswitch ((int)s[0]) {\n\t\t\t\t\tcase 'n': // draw name\n\t\t\t\t\t\twidth = maxname * font_width;\n\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\tchar *nick = TP_ParseFunChars(ti_cl->nick[0] ? ti_cl->nick : cl.players[i].shortname, false);\n\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s%s\", txtclr, nick);\n\t\t\t\t\t\t\tDraw_SStringAligned(x, y, tmp, scale, alpha, proportional, text_align_right, x + width);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tx += width;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'w': // draw \"best\" weapon icon/name\n\t\t\t\t\tcase 'W': // draw \"best\" weapon icon/name\n\t\t\t\t\t\tswitch (weapon_style) {\n\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\t\tchar *weap_str;\n\t\t\t\t\t\t\t\t\tif (Has_Both_RL_and_LG(ti_cl->items)) {\n\t\t\t\t\t\t\t\t\t\tweap_str = tp_name_rlg.string;\n\t\t\t\t\t\t\t\t\t} \n\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\tweap_str = TP_ItemName(BestWeaponFromStatItems(ti_cl->items));\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tchar weap_white_stripped[32];\n\t\t\t\t\t\t\t\t\tUtil_SkipChars(weap_str, \"{}\", weap_white_stripped, 32);\n\t\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s%s\", txtclr, isDeadCA ? \"--\" : weap_white_stripped);\n\t\t\t\t\t\t\t\t\tDraw_SStringAligned(x, y, tmp, scale, alpha, proportional, ((s[0] == 'W' || show_ammo) ? text_align_right : text_align_left), x + 3 * font_width);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tx += 3 * font_width;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault: // draw image by default\n\t\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\t\tif (!isDeadCA && (pic = SCR_GetWeaponIconByFlag(BestWeaponFromStatItems(ti_cl->items)))) {\n\t\t\t\t\t\t\t\t\t\tDraw_SAlphaPic(x, y, pic, alpha, 0.5 * scale);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tx += 2 * font_width;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (show_ammo) {\n\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\tif (isDeadCA){\n\t\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s\", txtclr);\n\t\t\t\t\t\t\t\t} \n\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s:%d\", txtclr, a);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tDraw_SStringAligned(x, y, tmp, scale, alpha, proportional, text_align_left, x + 4 * font_width);\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tx += 4 * font_width;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'k': // draw ammo\n\t\t\t\t\tcase 'K': // draw ammo\n\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s%d\", txtclr, a);\n\t\t\t\t\t\t\tDraw_SStringAligned(x, y, tmp, scale, alpha, proportional, (s[0] == 'C' ? text_align_right : text_align_left), x + 3 * font_width);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tx += 3 * font_width;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'r': // draw respawn time\n\t\t\t\t\tcase 'R': // draw respawn time\n\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\tif (isRespawning)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"&cfa0%d\", ti_cl->timetospawn);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s\", \" \");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tDraw_SStringAligned(x, y, tmp, scale * 0.75, 1.0, proportional, (s[0] == 'r' ? text_align_right : text_align_left), x + 2 * font_width);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tx += 2 * font_width;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'h': // draw health, padding with space on left side\n\t\t\t\t\tcase 'H': // draw health, padding with space on right side\n\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\tif (isDeadCA){\n\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s--\", txtclr); // print dashes if dead\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s%d\", (ti_cl->health < low_health ? \"&cf00\" : txtclr), ti_cl->health);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tDraw_SStringAligned(x, y, tmp, scale, alpha, proportional, (s[0] == 'h' ? text_align_right : text_align_left), x + 3 * font_width);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tx += 3 * font_width;\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 'f': // draw frags, space on left side\n\t\t\t\t\tcase 'F': // draw frags, space on right side\n\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), (s[0] == 'f' ? \"%s%3d\" : \"%s%-3d\"), txtclr, cl.players[i].frags);\n\t\t\t\t\t\t\tDraw_SStringAligned(x, y, tmp, scale, alpha, proportional, (s[0] == 'f' ? text_align_right : text_align_left), x + 3 * font_width);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tx += 3 * FONTWIDTH * scale;\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 'a': // draw armor, padded with space on left side\n\t\t\t\t\tcase 'A': // draw armor, padded with space on right side\n\t\t\t\t\t\t//\n\t\t\t\t\t\t// different styles of armor\n\t\t\t\t\t\t//\n\t\t\t\t\t\taclr = txtclr;\n\t\t\t\t\t\tswitch (armor_style) {\n\t\t\t\t\t\t\tcase 1: // image prefixed armor value\n\t\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\t\tif (ti_cl->items & IT_ARMOR3)\n\t\t\t\t\t\t\t\t\t\tDraw_SAlphaPic(x, y, sb_armor[2], alpha, 1.0 / 3 * scale);\n\t\t\t\t\t\t\t\t\telse if (ti_cl->items & IT_ARMOR2)\n\t\t\t\t\t\t\t\t\t\tDraw_SAlphaPic(x, y, sb_armor[1], alpha, 1.0 / 3 * scale);\n\t\t\t\t\t\t\t\t\telse if (ti_cl->items & IT_ARMOR1)\n\t\t\t\t\t\t\t\t\t\tDraw_SAlphaPic(x, y, sb_armor[0], alpha, 1.0 / 3 * scale);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase 2: // colored background of armor value\n\t\t\t\t\t\t\t\t/*\n\t\t\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\t\t\tbyte col[4] = {255, 255, 255, 0};\n\n\t\t\t\t\t\t\t\t\t\tif (ti_cl->items & IT_ARMOR3) {\n\t\t\t\t\t\t\t\t\t\t\tcol[0] = 255; col[1] =   0; col[2] =   0; col[3] = 255;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse if (ti_cl->items & IT_ARMOR2) {\n\t\t\t\t\t\t\t\t\t\t\tcol[0] = 255; col[1] = 255; col[2] =   0; col[3] = 255;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse if (ti_cl->items & IT_ARMOR1) {\n\t\t\t\t\t\t\t\t\t\t\tcol[0] =   0; col[1] = 255; col[2] =   0; col[3] = 255;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t*/\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase 3: // colored armor value\n\t\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\t\tif (ti_cl->items & IT_ARMOR3) {\n\t\t\t\t\t\t\t\t\t\taclr = \"&cf00\";\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\telse if (ti_cl->items & IT_ARMOR2) {\n\t\t\t\t\t\t\t\t\t\taclr = \"&cff0\";\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\telse if (ti_cl->items & IT_ARMOR1) {\n\t\t\t\t\t\t\t\t\t\taclr = \"&c0f0\";\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase 4: // armor value prefixed with letter\n\t\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\t\tif (ti_cl->items & IT_ARMOR3)\n\t\t\t\t\t\t\t\t\t\tDraw_SStringAlpha(x, y, \"r\", scale, alpha, proportional);\n\t\t\t\t\t\t\t\t\telse if (ti_cl->items & IT_ARMOR2)\n\t\t\t\t\t\t\t\t\t\tDraw_SStringAlpha(x, y, \"y\", scale, alpha, proportional);\n\t\t\t\t\t\t\t\t\telse if (ti_cl->items & IT_ARMOR1)\n\t\t\t\t\t\t\t\t\t\tDraw_SStringAlpha(x, y, \"g\", scale, alpha, proportional);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t// value drawn no matter which style\n\t\t\t\t\t\t\tif (isDeadCA){\n\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s--\", txtclr); // print dashes if dead\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s%d\", aclr, ti_cl->armor);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tDraw_SStringAligned(x, y, tmp, scale, alpha, proportional, s[0] == 'A' ? text_align_left : text_align_right, x + 3 * font_width);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tx += 3 * font_width;\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 'l': // draw location\n\t\t\t\t\t\twidth = maxloc * font_width;\n\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\tloc = TP_LocationName(ti_cl->org);\n\t\t\t\t\t\t\tif (!loc[0]) {\n\t\t\t\t\t\t\t\tloc = \"unknown\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tloc = TP_ParseFunChars(loc, false);\n\t\t\t\t\t\t\tif (isDeadCA){\n\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%sdead\", txtclr);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s%s\", txtclr, loc);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tDraw_SStringAligned(x, y, tmp, scale, alpha, proportional, text_align_right, x + width);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tx += width;\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 'p': // draw powerups\n\t\t\t\t\t\tif (show_countdown && check_ktx_ca_wo())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\tif (isRespawning)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"&cfa0%d \", ti_cl->timetospawn);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s\", \" \");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tDraw_SStringAligned(x, y, tmp, scale * 0.75, 1.0, proportional, text_align_right, x + 3 * font_width);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tx += 3 * font_width;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tswitch (powerup_style) {\n\t\t\t\t\t\t\t\tcase 1: // quad/pent/ring image\n\t\t\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\t\t\tif (ti_cl->items & IT_QUAD) {\n\t\t\t\t\t\t\t\t\t\t\tDraw_SPic(x, y, sb_items[5], scale / 2);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\t\t\t\t\tif (ti_cl->items & IT_INVULNERABILITY) {\n\t\t\t\t\t\t\t\t\t\t\tDraw_SPic(x, y, sb_items[3], scale / 2);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\t\t\t\t\tif (ti_cl->items & IT_INVISIBILITY) {\n\t\t\t\t\t\t\t\t\t\t\tDraw_SPic(x, y, sb_items[2], scale / 2);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\telse { \n\t\t\t\t\t\t\t\t\t\tx += 3 * font_width;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\t\t\tcase 2: // player powerup face\n\t\t\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\t\t\tif (sb_face_quad && (ti_cl->items & IT_QUAD)) {\n\t\t\t\t\t\t\t\t\t\t\tDraw_SPic(x, y, sb_face_quad, scale / 3);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\t\t\t\t\tif (sb_face_invuln && (ti_cl->items & IT_INVULNERABILITY)) {\n\t\t\t\t\t\t\t\t\t\t\tDraw_SPic(x, y, sb_face_invuln, scale / 3);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\t\t\t\t\tif (sb_face_invis && (ti_cl->items & IT_INVISIBILITY)) {\n\t\t\t\t\t\t\t\t\t\t\tDraw_SPic(x, y, sb_face_invis, scale / 3);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\tx += 3 * font_width;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\t\t\tcase 3: // colored font (QPR)\n\t\t\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\t\t\tif (ti_cl->items & IT_QUAD) {\n\t\t\t\t\t\t\t\t\t\t\tDraw_SString(x, y, \"&c03fQ\", scale, proportional);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\t\t\t\t\tif (ti_cl->items & IT_INVULNERABILITY) {\n\t\t\t\t\t\t\t\t\t\t\tDraw_SString(x, y, \"&cf00P\", scale, proportional);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\t\t\t\t\tif (ti_cl->items & IT_INVISIBILITY) {\n\t\t\t\t\t\t\t\t\t\t\tDraw_SString(x, y, \"&cff0R\", scale, proportional);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\tx += 3 * font_width;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 't':\n\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\tsprintf(tmp, \"%s%i\", txtclr, Player_GetTrackId(cl.players[ti_cl->client].userid));\n\t\t\t\t\t\t\tDraw_SStringAlpha(x, y, tmp, scale, alpha, proportional);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tx += (Player_GetTrackId(cl.players[ti_cl->client].userid) >= 10 ? 2 : 1) * font_width;\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase 'c':\n\t\t\t\t\t\tswitch (flag_style) {\n\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\t\tif (ti_cl->items & IT_KEY1) {\n\t\t\t\t\t\t\t\t\t\tDraw_SPic(x, y, sb_items[0], scale / 2);\n\t\t\t\t\t\t\t\t\t} else if (ti_cl->items & IT_KEY2) {\n\t\t\t\t\t\t\t\t\t\tDraw_SPic(x, y, sb_items[1], scale / 2);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\t\t\tif (ti_cl->items & IT_KEY1) {\n\t\t\t\t\t\t\t\t\t\tDraw_SString(x, y, \"&cf00B\", scale, proportional);\n\t\t\t\t\t\t\t\t\t} else if (ti_cl->items & IT_KEY2) {\n\t\t\t\t\t\t\t\t\t\tDraw_SString(x, y, \"&c00fR\", scale, proportional);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase '%': // wow, %% result in one %, how smart\n\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s%s\", txtclr, \"%\");\n\t\t\t\t\t\t\tDraw_SStringAlpha(x, y, tmp, scale, alpha, proportional);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tx += font_width;\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tdefault: // print %x - that mean sequence unknown\n\t\t\t\t\t\tif (!width_only) {\n\t\t\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%%%c\", s[0]);\n\t\t\t\t\t\t\tDraw_SStringAlpha(x, y, tmp, scale, alpha, proportional);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tx += (s[0] ? 2 : 1) * font_width;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\n\t\t\tdefault: // print x\n\t\t\t\tif (!width_only) {\n\t\t\t\t\tsnprintf(tmp, sizeof(tmp), \"%s%c\", txtclr, s[0]);\n\t\t\t\t\tif (s[0] != ' ') {\n\t\t\t\t\t\t// inhuman smart optimization, do not print space!\n\t\t\t\t\t\tDraw_SStringAlpha(x, y, tmp, scale, alpha, proportional);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tx += font_width;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn (x - x_in); // return width\n}\n\n\n// ORIGINAL teaminfo (cl_screen.c)\n\n// scr_teaminfo \n// Variable ti_clients and related functions also used by hud_teaminfo in hud_common.c\nti_player_t ti_clients[MAX_CLIENTS];\n\nvoid SCR_ClearTeamInfo(void)\n{\n\tmemset(ti_clients, 0, sizeof(ti_clients));\n}\n\nvoid SCR_Draw_TeamInfo(void)\n{\n\tint x, y, w, h;\n\tint i, j, slots[MAX_CLIENTS], slots_num, maxname, maxloc;\n\tchar tmp[1024], *nick;\n\n\tfloat\tscale = bound(0.1, scr_teaminfo_scale.value, 10);\n\n\tif (!cl.teamplay || !scr_teaminfo.integer) {\n\t\t// non teamplay mode\n\t\treturn;\n\t}\n\n\tif (cls.mvdplayback) {\n\t\tUpdate_TeamInfo();\n\t}\n\n\t// fill data we require to draw teaminfo\n\tfor (maxloc = maxname = slots_num = i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!cl.players[i].name[0] || cl.players[i].spectator\n\t\t\t|| !ti_clients[i].time || ti_clients[i].time + TI_TIMEOUT < r_refdef2.time\n\t\t\t)\n\t\t\tcontinue;\n\n\t\t// do not show enemy players unless it's MVD and user wishes to show them\n\t\tif (VX_TrackerIsEnemy(i) && (!cls.mvdplayback || !scr_teaminfo_show_enemies.integer))\n\t\t\tcontinue;\n\n\t\t// do not show tracked player to spectator\n\t\tif ((cl.spectator && Cam_TrackNum() == i) && !TEAMINFO_SHOWSELF())\n\t\t\tcontinue;\n\n\t\t// dynamically guess max length of name/location\n\t\tnick = (ti_clients[i].nick[0] ? ti_clients[i].nick : cl.players[i].name); // we use nick or name\n\t\tmaxname = max(maxname, strlen(TP_ParseFunChars(nick, false)));\n\n\t\tstrlcpy(tmp, TP_LocationName(ti_clients[i].org), sizeof(tmp));\n\t\tmaxloc = max(maxloc, strlen(TP_ParseFunChars(tmp, false)));\n\n\t\tslots[slots_num++] = i;\n\t}\n\n\t// well, better use fixed loc length\n\tmaxloc = bound(0, scr_teaminfo_loc_width.integer, 100);\n\t// limit name length\n\tmaxname = bound(0, maxname, scr_teaminfo_name_width.integer);\n\n\tif (!slots_num) {\n\t\treturn;\n\t}\n\n\ty = vid.height * 0.6 + scr_teaminfo_y.value;\n\n\t// this does't draw anything, just calculate width\n\tw = SCR_HudDrawTeamInfoPlayer(&ti_clients[0], 0, 0, maxname, maxloc, true, scr_teaminfo_scale.value, scr_teaminfo_order.string, scr_teaminfo_weapon_style.integer, scr_teaminfo_show_ammo.integer, scr_teaminfo_show_countdown.integer, scr_teaminfo_armor_style.integer, scr_teaminfo_powerup_style.integer, scr_teaminfo_flag_style.integer, scr_teaminfo_low_health.integer, scr_teaminfo_proportional.integer);\n\th = slots_num * scale;\n\n\tfor (j = 0; j < slots_num; j++) {\n\t\ti = slots[j];\n\n\t\tx = (scr_teaminfo_align_right.value ? (vid.width - w) - FONTWIDTH : FONTWIDTH);\n\t\tx += scr_teaminfo_x.value;\n\n\t\tif (!j) { // draw frame\n\t\t\tbyte\t*col = scr_teaminfo_frame_color.color;\n\n\t\t\tDraw_AlphaRectangleRGB(x, y, w, h * FONTWIDTH, 0, true, RGBAVECT_TO_COLOR(col));\n\t\t}\n\n\t\tSCR_HudDrawTeamInfoPlayer(&ti_clients[i], x, y, maxname, maxloc, false, scr_teaminfo_scale.value, scr_teaminfo_order.string, scr_teaminfo_weapon_style.integer, scr_teaminfo_show_ammo.integer, scr_teaminfo_show_countdown.integer, scr_teaminfo_armor_style.integer, scr_teaminfo_powerup_style.integer, scr_teaminfo_flag_style.integer, scr_teaminfo_low_health.integer, scr_teaminfo_proportional.integer);\n\n\t\ty += FONTWIDTH * scale;\n\t}\n}\n\n#define FLAGS_RUNES_MASK (IT_SIGIL1 | IT_SIGIL2 | IT_SIGIL3 | IT_SIGIL4 | IT_KEY1 | IT_KEY2)\n\nstatic int Filter_FlagsAndRunes(int client, int stats)\n{\n\tif (!Stats_IsFlagsParsed()) {\n\t\treturn stats;\n\t}\n\treturn (ti_clients[client].items & FLAGS_RUNES_MASK) | (stats & ~FLAGS_RUNES_MASK);\n}\n\nvoid Parse_TeamInfo(char *s)\n{\n\tint\t\tclient;\n\n\tCmd_TokenizeString(s);\n\n\tclient = atoi(Cmd_Argv(1));\n\n\tif (client < 0 || client >= MAX_CLIENTS) {\n\t\tCom_DPrintf(\"Parse_TeamInfo: wrong client %d\\n\", client);\n\t\treturn;\n\t}\n\n\tti_clients[client].client = client; // no, its not stupid\n\n\tti_clients[client].time = r_refdef2.time;\n\n\tti_clients[client].org[0] = atoi(Cmd_Argv(2));\n\tti_clients[client].org[1] = atoi(Cmd_Argv(3));\n\tti_clients[client].org[2] = atoi(Cmd_Argv(4));\n\tti_clients[client].health = atoi(Cmd_Argv(5));\n\tti_clients[client].armor = atoi(Cmd_Argv(6));\n\tti_clients[client].items = Filter_FlagsAndRunes(client, atoi(Cmd_Argv(7)));\n\tstrlcpy(ti_clients[client].nick, Cmd_Argv(8), TEAMINFO_NICKLEN); // nick is optional\n\tti_clients[client].shells = atoi(Cmd_Argv(9));\n\tti_clients[client].nails = atoi(Cmd_Argv(10));\n\tti_clients[client].rockets = atoi(Cmd_Argv(11));\n\tti_clients[client].cells = atoi(Cmd_Argv(12));\n}\n\nvoid Parse_CAInfo(char *s)\n{\n\tint\t\tclient;\n\n\tCmd_TokenizeString(s);\n\tclient = atoi(Cmd_Argv(1));\n\n\tif (client < 0 || client >= MAX_CLIENTS) {\n\t\tCom_DPrintf(\"Parse_CAInfo: wrong client %d\\n\", client);\n\t\treturn;\n\t}\n\n\tif (!cls.mvdplayback) {\n\t\tti_clients[client].client = client; // no, its not stupid\n\t\tti_clients[client].time = r_refdef2.time;\n\t\tti_clients[client].org[0] = atoi(Cmd_Argv(2));\n\t\tti_clients[client].org[1] = atoi(Cmd_Argv(3));\n\t\tti_clients[client].org[2] = atoi(Cmd_Argv(4));\n\t\tti_clients[client].health = atoi(Cmd_Argv(5));\n\t\tti_clients[client].armor = atoi(Cmd_Argv(6));\n\t\tti_clients[client].items = atoi(Cmd_Argv(7));\n\t\tstrlcpy(ti_clients[client].nick, Cmd_Argv(8), TEAMINFO_NICKLEN); // nick is optional\n\t\tti_clients[client].shells = atoi(Cmd_Argv(9));\n\t\tti_clients[client].nails = atoi(Cmd_Argv(10));\n\t\tti_clients[client].rockets = atoi(Cmd_Argv(11));\n\t\tti_clients[client].cells = atoi(Cmd_Argv(12));\n\t}\n\t\n\tti_clients[client].camode = atoi(Cmd_Argv(13));\n\tti_clients[client].isdead = atoi(Cmd_Argv(14));\n\tti_clients[client].timetospawn = atoi(Cmd_Argv(15));\n\tti_clients[client].round_kills = atoi(Cmd_Argv(16));\n\tti_clients[client].round_deaths = atoi(Cmd_Argv(17));\n}\n\nvoid Update_FlagStatus(int player_num, char *team, qbool got_flag)\n{\n\tint flag = IT_KEY1 | IT_KEY2;\n\tif (!strcmp(team, \"blue\")) {\n\t\tflag = IT_KEY2;\n\t} else if (!strcmp(team, \"red\")) {\n\t\tflag = IT_KEY1;\n\t}\n\n\tif (got_flag) {\n\t\tti_clients[player_num].items |= flag;\n\t} else {\n\t\tti_clients[player_num].items &= ~flag;\n\t}\n}\n\nstatic void Update_TeamInfo(void)\n{\n\tint\t\ti;\n\tint\t\t*st;\n\n\tstatic double lastupdate = 0;\n\n\tif (!cls.mvdplayback) {\n\t\treturn;\n\t}\n\n\t// don't update each frame - it's less disturbing\n\tif (cls.realtime - lastupdate < 1)\n\t\treturn;\n\n\tlastupdate = cls.realtime;\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (cl.players[i].spectator || !cl.players[i].name[0])\n\t\t\tcontinue;\n\n\t\tst = cl.players[i].stats;\n\n\t\tti_clients[i].client = i; // no, its not stupid\n\n\t\tti_clients[i].time = r_refdef2.time;\n\n\t\tVectorCopy(cl.frames[cl.parsecount & UPDATE_MASK].playerstate[i].origin, ti_clients[i].org);\n\n\t\tti_clients[i].health = bound(0, st[STAT_HEALTH], 999);\n\t\tti_clients[i].armor = bound(0, st[STAT_ARMOR], 999);\n\t\tti_clients[i].items = Filter_FlagsAndRunes(i, st[STAT_ITEMS]);\n\t\tti_clients[i].shells = bound(0, st[STAT_SHELLS], 999);\n\t\tti_clients[i].nails = bound(0, st[STAT_NAILS], 999);\n\t\tti_clients[i].rockets = bound(0, st[STAT_ROCKETS], 999);\n\t\tti_clients[i].cells = bound(0, st[STAT_CELLS], 999);\n\t\tti_clients[i].nick[0] = 0; // sad, we don't have nick, will use name\n\t}\n}\n\nvoid TeamInfo_HudInit(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\tCvar_Register(&scr_shownick_order);\n\tCvar_Register(&scr_shownick_frame_color);\n\tCvar_Register(&scr_shownick_scale);\n\tCvar_Register(&scr_shownick_y);\n\tCvar_Register(&scr_shownick_x);\n\tCvar_Register(&scr_shownick_name_width);\n\tCvar_Register(&scr_shownick_time);\n\tCvar_Register(&scr_shownick_proportional);\n\tCvar_Register(&scr_shownick_show_ammo);\n\n\tCvar_Register(&scr_teaminfo_order);\n\tCvar_Register(&scr_teaminfo_align_right);\n\tCvar_Register(&scr_teaminfo_frame_color);\n\tCvar_Register(&scr_teaminfo_scale);\n\tCvar_Register(&scr_teaminfo_y);\n\tCvar_Register(&scr_teaminfo_x);\n\tCvar_Register(&scr_teaminfo_loc_width);\n\tCvar_Register(&scr_teaminfo_name_width);\n\tCvar_Register(&scr_teaminfo_low_health);\n\tCvar_Register(&scr_teaminfo_armor_style);\n\tCvar_Register(&scr_teaminfo_powerup_style);\n\tCvar_Register(&scr_teaminfo_flag_style);\n\tCvar_Register(&scr_teaminfo_weapon_style);\n\tCvar_Register(&scr_teaminfo_show_ammo);\n\tCvar_Register(&scr_teaminfo_show_countdown);\n\tCvar_Register(&scr_teaminfo_show_enemies);\n\tCvar_Register(&scr_teaminfo_show_self);\n\tCvar_Register(&scr_teaminfo_proportional);\n\tCvar_Register(&scr_teaminfo);\n\n\tHUD_Register(\n\t\t\"teaminfo\", NULL, \"Show information about your team in short form.\",\n\t\t0, ca_active, 0, SCR_HUD_DrawTeamInfo,\n\t\t\"0\", \"\", \"right\", \"center\", \"0\", \"0\", \"0.2\", \"20 20 20\", NULL,\n\t\t\"layout\", \"%p%n $x10%l$x11 %a/%H %w\",\n\t\t\"align_right\", \"0\",\n\t\t\"loc_width\", \"5\",\n\t\t\"name_width\", \"6\",\n\t\t\"low_health\", \"25\",\n\t\t\"armor_style\", \"3\",\n\t\t\"weapon_style\", \"0\",\n\t\t\"show_ammo\", \"0\",\n\t\t\"show_countdown\", \"1\",\n\t\t\"show_enemies\", \"0\",\n\t\t\"show_self\", \"1\",\n\t\t\"show_headers\", \"1\",\n\t\t\"scale\", \"1\",\n\t\t\"powerup_style\", \"1\",\n\t\t\"flag_style\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\t\"header_spacing\", \"1\",\n\t\tNULL\n\t);\n}\n\n\n\n// SHOWNICK\n\n\n/***************************** customizeable shownick *************************/\n\nstatic ti_player_t shownick;\n\nvoid SCR_ClearShownick(void)\n{\n\tmemset(&shownick, 0, sizeof(shownick));\n}\n\nvoid Parse_Shownick(char *s)\n{\n\tint\t\tclient, version, arg;\n\n\tCmd_TokenizeString(s);\n\n\targ = 1;\n\n\tversion = atoi(Cmd_Argv(arg++));\n\n\tswitch (version) {\n\t\tcase 1:\n\t\t{\n\t\t\tclient = atoi(Cmd_Argv(arg++));\n\n\t\t\tif (client < 0 || client >= MAX_CLIENTS) {\n\t\t\t\tCom_DPrintf(\"Parse_Shownick: wrong client %d\\n\", client);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tshownick.client = client;\n\n\t\t\tshownick.time = r_refdef2.time;\n\n\t\t\tshownick.org[0] = atoi(Cmd_Argv(arg++));\n\t\t\tshownick.org[1] = atoi(Cmd_Argv(arg++));\n\t\t\tshownick.org[2] = atoi(Cmd_Argv(arg++));\n\t\t\tshownick.health = atoi(Cmd_Argv(arg++));\n\t\t\tshownick.armor = atoi(Cmd_Argv(arg++));\n\t\t\tshownick.items = atoi(Cmd_Argv(arg++));\n\t\t\tstrlcpy(shownick.nick, Cmd_Argv(arg++), TEAMINFO_NICKLEN); // nick is optional\n\t\t\tshownick.shells = atoi(Cmd_Argv(arg++));\n\t\t\tshownick.nails = atoi(Cmd_Argv(arg++));\n\t\t\tshownick.rockets = atoi(Cmd_Argv(arg++));\n\t\t\tshownick.cells = atoi(Cmd_Argv(arg++));\n\n\t\t\treturn;\n\t\t}\n\n\t\tdefault:\n\t\t\tCom_DPrintf(\"Parse_Shownick: unsupported version %d\\n\", version);\n\t\t\treturn;\n\t}\n}\n\nstatic void Update_Shownick(void)\n{\n\tint\t\ti;\n\tint\t\t*st;\n\n\tif (!cls.mvdplayback) {\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (cl.players[i].spectator || !cl.players[i].name[0])\n\t\t\tcontinue;\n\n\t\tif (i != shownick.client)\n\t\t\tcontinue;\n\t\t\t\n\t\tst = cl.players[i].stats;\n\n\t\tshownick.client = i; // no, its not stupid\n\n\t\tVectorCopy(cl.frames[cl.parsecount & UPDATE_MASK].playerstate[i].origin, shownick.org);\n\n\t\tshownick.health = bound(0, st[STAT_HEALTH], 999);\n\t\tshownick.armor = bound(0, st[STAT_ARMOR], 999);\n\t\tshownick.items = st[STAT_ITEMS];\n\t\tshownick.shells = bound(0, st[STAT_SHELLS], 999);\n\t\tshownick.nails = bound(0, st[STAT_NAILS], 999);\n\t\tshownick.rockets = bound(0, st[STAT_ROCKETS], 999);\n\t\tshownick.cells = bound(0, st[STAT_CELLS], 999);\n\t\tshownick.nick[0] = 0; // sad, we don't have nick, will use name\n\t}\n}\n\nvoid SCR_Draw_ShowNick(void)\n{\n\tqbool\tscr_shownick_align_right = false;\n\tint\t\tx, y, w, h;\n\tint\t\tmaxname, maxloc;\n\tbyte\t*col;\n\tfloat\tscale = bound(0.1, scr_shownick_scale.value, 10);\n\n\t// check do we have something do draw\n\tif (!shownick.time || shownick.time + bound(0.1, scr_shownick_time.value, 3) < r_refdef2.time) {\n\t\treturn;\n\t}\n\n\tif (cls.mvdplayback) {\n\t\tUpdate_Shownick();\n\t}\n\n\t// loc is unused\n\tmaxloc = 0;\n\n\t// limit name length\n\tmaxname = 999;\n\tmaxname = bound(0, maxname, scr_shownick_name_width.integer);\n\n\ty = vid.height * 0.6 + scr_shownick_y.value;\n\n\t// this does't draw anything, just calculate width\n\tw = SCR_HudDrawTeamInfoPlayer(&shownick, 0, 0, maxname, maxloc, true, scale, scr_shownick_order.string, scr_teaminfo_weapon_style.integer, scr_shownick_show_ammo.integer, 0, scr_teaminfo_armor_style.integer, scr_teaminfo_powerup_style.integer, scr_teaminfo_flag_style.integer, scr_teaminfo_low_health.integer, scr_shownick_proportional.integer);\n\th = FONTWIDTH * scale;\n\n\tx = (scr_shownick_align_right ? (vid.width - w) - FONTWIDTH : FONTWIDTH);\n\tx += scr_shownick_x.value;\n\n\t// draw frame\n\tcol = scr_shownick_frame_color.color;\n\n\tDraw_AlphaRectangleRGB(x, y, w, h, 0, true, RGBAVECT_TO_COLOR(col));\n\n\t// draw shownick\n\tSCR_HudDrawTeamInfoPlayer(&shownick, x, y, maxname, maxloc, false, scale, scr_shownick_order.string, scr_teaminfo_weapon_style.integer, scr_shownick_show_ammo.integer, 0, scr_teaminfo_armor_style.integer, scr_teaminfo_powerup_style.integer, scr_teaminfo_flag_style.integer, scr_teaminfo_low_health.integer, scr_shownick_proportional.integer);\n}\n"
  },
  {
    "path": "src/hud_tracking.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"sbar.h\"\n#include \"utils.h\"\n\n// Tracking text.\n// \"Tracking: [team] name, JUMP for next\", \"Tracking:\" and \"JUMP\" are brown. default: \"Tracking %t %n, [JUMP] for next\"\nstatic cvar_t scr_tracking         = { \"scr_tracking\", \"\\xD4\\xF2\\xE1\\xE3\\xEB\\xE9\\xEE\\xE7\\xBA %t %n, \\xCA\\xD5\\xCD\\xD0 for next\" };\nstatic cvar_t scr_spectatorMessage = { \"scr_spectatorMessage\", \"1\" };\n\n#define MAX_TRACKING_STRING\t\t512\n\nstatic void SCR_HUD_DrawTracking(hud_t *hud)\n{\n\tint x = 0, y = 0, width = 0, height = 0;\n\tchar track_string[MAX_TRACKING_STRING];\n\tint player = cl.spec_track;\n\n\tstatic cvar_t\n\t\t*hud_tracking_format = NULL,\n\t\t*hud_tracking_scale,\n\t\t*hud_tracking_proportional;\n\n\tif (!hud_tracking_format) {\n\t\thud_tracking_format = HUD_FindVar(hud, \"format\");\n\t\thud_tracking_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_tracking_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tstrlcpy(track_string, hud_tracking_format->string, sizeof(track_string));\n\tReplace_In_String(track_string, sizeof(track_string), '%', 2,\n\t\t\"n\", cl.players[player].name,\t\t\t\t\t\t// Replace %n with player name.\n\t\t\"t\", cl.teamplay ? cl.players[player].team : \"\");\t// Replace %t with player team if teamplay is on.\n\theight = 8 * hud_tracking_scale->value;\n\twidth = Draw_StringLength(track_string, -1, hud_tracking_scale->value, hud_tracking_proportional->integer);\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tif (cl.spectator && cl.autocam == CAM_TRACK) {\n\t\t\t// Normal\n\t\t\tDraw_SString(x, y, track_string, hud_tracking_scale->value, hud_tracking_proportional->integer);\n\t\t}\n\t}\n}\n\nvoid Tracking_HudInit(void)\n{\n\t// Tracking JohnNy_cz (Contains name of the player who's player we're watching at the moment)\n\tHUD_Register(\n\t\t\"tracking\", NULL, \"Shows the name of tracked player.\",\n\t\tHUD_PLUSMINUS, ca_active, 9, SCR_HUD_DrawTracking,\n\t\t\"1\", \"face\", \"center\", \"before\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"format\", \"\\xD4\\xF2\\xE1\\xE3\\xEB\\xE9\\xEE\\xE7\\xBA %t %n, \\xCA\\xD5\\xCD\\xD0 for next\", //\"Tracking: team name, JUMP for next\", \"Tracking:\" and \"JUMP\" are brown. default: \"Tracking %t %n, [JUMP] for next\"\n\t\t\"scale\", \"1\", \"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\tCvar_Register(&scr_tracking);\n\tCvar_Register(&scr_spectatorMessage);\n\tCvar_ResetCurrentGroup();\n}\n\nvoid Sbar_DrawSpectatorMessage(void)\n{\n\textern mpic_t* sb_scorebar;\n\n\tif (scr_spectatorMessage.value != 0) {\n\t\tSbar_DrawPic(0, 0, sb_scorebar);\n\t\tSbar_DrawString(160 - 7 * 8, 4, \"SPECTATOR MODE\");\n\t\tSbar_DrawString(160 - 14 * 8 + 4, 12, \"Press [ATTACK] for AutoCamera\");\n\t}\n}\n\nvoid Sbar_DrawTrackingString(void)\n{\n\tchar st[512];\n\textern cvar_t scr_tracking, scr_newHud;\n\n\t// If showing ammo on top of status, display higher up\n\tint y_coordinate = (Sbar_IsStandardBar() ? SBAR_HEIGHT - sb_lines : 0) - 8;\n\n\tif (sb_lines > 0 && scr_newHud.value != 1 && cl.spectator && cl.autocam == CAM_TRACK) {\n\t\tstrlcpy(st, scr_tracking.string, sizeof(st));\n\n\t\tReplace_In_String(st, sizeof(st), '%', 2, \"n\", cl.players[cl.spec_track].name, \"t\", cl.teamplay ? cl.players[cl.spec_track].team : \"\");\n\n\t\t// Multiview\n\t\t// Fix displaying \"tracking ..\" for both players with inset on\n\t\tif (cl_multiview.value != 2 || !cls.mvdplayback) {\n\t\t\tSbar_DrawString(0, y_coordinate, st);\n\t\t}\n\t\telse if (CL_MultiviewCurrentView() == 1 && cl_mvinset.value) {\n\t\t\tSbar_DrawString(0, y_coordinate, st);\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "src/hud_weapon_stats.c",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"teamplay.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n\ntypedef enum\n{\n\twpNONE = 0,\n\twpAXE,\n\twpSG,\n\twpSSG,\n\twpNG,\n\twpSNG,\n\twpGL,\n\twpRL,\n\twpLG,\n\twpMAX\n} weaponName_t;\n\ntypedef struct wpType_s {\n\tint hits;\t\t\t// hits with this weapon, for SG and SSG this is count of bullets\n\tint attacks;\t\t// all attacks with this weapon, for SG and SSG this is count of bullets\n} wpType_t;\n\ntypedef struct ws_player_s {\n\tint \t\tclient;\n\n\twpType_t\twpn[wpMAX];\n} ws_player_t;\n\nstatic ws_player_t ws_clients[MAX_CLIENTS];\n\nstatic weaponName_t WS_NameToNum(const char *name)\n{\n\tif ( !strcmp(name, \"axe\") )\n\t\treturn wpAXE;\n\tif ( !strcmp(name, \"sg\") )\n\t\treturn wpSG;\n\tif ( !strcmp(name, \"ssg\") )\n\t\treturn wpSSG;\n\tif ( !strcmp(name, \"ng\") )\n\t\treturn wpNG;\n\tif ( !strcmp(name, \"sng\") )\n\t\treturn wpSNG;\n\tif ( !strcmp(name, \"gl\") )\n\t\treturn wpGL;\n\tif ( !strcmp(name, \"rl\") )\n\t\treturn wpRL;\n\tif ( !strcmp(name, \"lg\") )\n\t\treturn wpLG;\n\n\treturn wpNONE;\n}\n\n// reads weapon stats string from server/demo\nvoid Parse_WeaponStats(char *s)\n{\n\tint\t\tclient, arg;\n\tweaponName_t wp;\n\n\tCmd_TokenizeString( s );\n\n\targ = 1;\n\n\tclient = atoi( Cmd_Argv( arg++ ) );\n\n\tif (client < 0 || client >= MAX_CLIENTS)\n\t{\n\t\tCom_DPrintf(\"Parse_WeaponStats: wrong client %d\\n\", client);\n\t\treturn;\n\t}\n\n\tws_clients[ client ].client = client; // no, its not stupid\n\n\twp = WS_NameToNum( Cmd_Argv( arg++ ) );\n\n\tif ( wp == wpNONE )\n\t{\n\t\tCom_DPrintf(\"Parse_WeaponStats: wrong weapon\\n\");\n\t\treturn;\n\t}\n\n\tws_clients[ client ].wpn[wp].attacks = atoi( Cmd_Argv( arg++ ) );\n\tws_clients[ client ].wpn[wp].hits    = atoi( Cmd_Argv( arg++ ) );\n}\n\n// called when new map spawned\nvoid SCR_ClearWeaponStats(void)\n{\n\tmemset(ws_clients, 0, sizeof(ws_clients));\n}\n\n// \nstatic void SCR_CreateWeaponStatsPlayerText(ws_player_t *ws_cl, char* input_string, char* output_string, int max_length)\n{\n\tchar *s, tmp2[MAX_MACRO_STRING];\n\tint new_length = 0;\n\n\t*output_string = '\\0';\n\tif (!ws_cl) {\n\t\treturn;\n\t}\n\n\t// this limit len of string because TP_ParseFunChars() do not check overflow\n\tstrlcpy(tmp2, input_string , sizeof(tmp2));\n\tstrlcpy(tmp2, TP_ParseFunChars(tmp2, false), sizeof(tmp2));\n\n\tfor (s = tmp2; *s && new_length < max_length - 1; ++s) {\n\t\tint wp;\n\t\tqbool percentage = (*s == '%');\n\n\t\tif (*s != '%' && *s != '#') {\n\t\t\toutput_string[new_length++] = *s;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (*s == s[1]) {\n\t\t\toutput_string[new_length++] = *s;\n\t\t\t++s;\n\t\t\tcontinue;\n\t\t}\n\n\t\t++s;\n\t\twp = (int)s[0] - '0'; \n\t\tif (wp <= wpNONE || wp >= wpMAX) {\n\t\t\tcontinue; // unsupported weapon\n\t\t}\n\n\t\tif (percentage) {\n\t\t\tfloat accuracy = (ws_cl->wpn[wp].attacks ? 100.0f * ws_cl->wpn[wp].hits / ws_cl->wpn[wp].attacks : 0);\n\n\t\t\tnew_length += snprintf(output_string + new_length, max_length - new_length - 1, \"%.1f\", accuracy);\n\t\t}\n\t\telse {\n\t\t\tnew_length += snprintf(output_string + new_length, max_length - new_length - 1, \"%d\", ws_cl->wpn[wp].hits);\n\t\t}\n\t}\n\toutput_string[new_length] = '\\0';\n}\n\nvoid SCR_HUD_WeaponStats(hud_t *hud)\n{\n\tchar content[128];\n\tint x, y;\n\tint i;\n\tint alignment = 0;\n\n\tstatic cvar_t\n\t\t*hud_weaponstats_format = NULL,\n\t\t*hud_weaponstats_textalign,\n\t\t*hud_weaponstats_scale,\n\t\t*hud_weaponstats_proportional;\n\n\tif (hud_weaponstats_format == NULL) {\n\t\t// first time\n\t\thud_weaponstats_format = HUD_FindVar(hud, \"format\");\n\t\thud_weaponstats_textalign = HUD_FindVar(hud, \"textalign\");\n\t\thud_weaponstats_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_weaponstats_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\tif (!strcmp(hud_weaponstats_textalign->string, \"right\")) {\n\t\talignment = 1;\n\t}\n\telse if (!strcmp(hud_weaponstats_textalign->string, \"center\")) {\n\t\talignment = 2;\n\t}\n\n\ti = (cl.spectator ? Cam_TrackNum() : cl.playernum);\n\tif (i < 0 || i >= MAX_CLIENTS) {\n\t\tHUD_PrepareDraw(hud, 0, 0, &x, &y);\n\t\treturn;\n\t}\n\n\tSCR_CreateWeaponStatsPlayerText(&ws_clients[i], hud_weaponstats_format->string, content, sizeof(content));\n\n\tSCR_HUD_MultiLineString(hud, content, false, alignment, hud_weaponstats_scale->value, hud_weaponstats_proportional->integer);\n}\n\nvoid OnChange_scr_weaponstats (cvar_t *var, char *value, qbool *cancel)\n{\n\textern void CL_UserinfoChanged (char *key, char *value);\n\n\t// do not allow set \"wpsx\" to \"0\" instead set it to \"\"\n\tCL_UserinfoChanged(\"wpsx\", strcmp(value, \"0\") ? value : \"\");\n}\n\nstatic void SCR_MvdWeaponStatsOn_f(void)\n{\n\tcvar_t* show = Cvar_Find(\"hud_weaponstats_show\");\n\tif (show) {\n\t\tCvar_Set (show, \"1\");\n\t}\n}\n\nstatic void SCR_MvdWeaponStatsOff_f(void)\n{\n\tcvar_t* show = Cvar_Find(\"hud_weaponstats_show\");\n\tif (show) {\n\t\tCvar_Set (show, \"0\");\n\t}\n}\n\nvoid WeaponStats_HUDInit(void)\n{\n\tcvar_t* show_cvar;\n\n\tHUD_Register(\n\t\t\"weaponstats\", NULL, \"Weapon stats\",\n\t\t0, ca_active, 0, SCR_HUD_WeaponStats,\n\t\t\"0\", \"screen\", \"center\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"format\", \"&c990sg&r:%2 &c099ssg&r:%3 &c900rl&r:#7 &c009lg&r:%8\",\n\t\t\"textalign\", \"center\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tshow_cvar = Cvar_Find(\"hud_weaponstats_show\");\n\tif (show_cvar) {\n\t\tshow_cvar->OnChange = OnChange_scr_weaponstats;\n\t}\n}\n\nvoid WeaponStats_CommandInit(void)\n{\n\tif (!host_initialized) {\n\t\tCmd_AddCommand(\"+cl_wp_stats\", SCR_MvdWeaponStatsOn_f);\n\t\tCmd_AddCommand(\"-cl_wp_stats\", SCR_MvdWeaponStatsOff_f);\n\t}\n}\n"
  },
  {
    "path": "src/ignore.c",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: ignore.c,v 1.7 2007-03-11 06:01:40 disconn3ct Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"ignore.h\"\n#include \"utils.h\"\n#include \"teamplay.h\"\n\n\n#define MAX_TEAMIGNORELIST\t4\n#define\tFLOODLIST_SIZE\t\t10\n\ncvar_t\t\tignore_spec\t\t\t\t= {\"ignore_spec\", \"0\"};\t\t\ncvar_t\t\tignore_qizmo_spec\t\t= {\"ignore_qizmo_spec\", \"0\"};\ncvar_t      ignore_qtv              = {\"ignore_qtv\", \"0\"};\ncvar_t\t\tignore_mode\t\t\t\t= {\"ignore_mode\", \"0\"};\ncvar_t\t\tignore_flood_duration\t= {\"ignore_flood_duration\", \"4\"};\ncvar_t\t\tignore_flood\t\t\t= {\"ignore_flood\", \"0\"};\t\t\ncvar_t\t\tignore_opponents\t\t= {\"ignore_opponents\", \"0\"};\ncvar_t\t\tignore_no_weapon\t\t= {\"ignore_no_weapon\", \"0\"};\ncvar_t\t\tignore_not_enough_ammo\t\t= {\"ignore_not_enough_ammo\", \"0\"};\n\nchar ignoreteamlist[MAX_TEAMIGNORELIST][16 + 1];\n\ntypedef struct flood_s {\n\tchar data[2048];\n\tfloat time;\n} flood_t;\n\nstatic flood_t floodlist[FLOODLIST_SIZE];\nstatic int\t\tfloodindex;\n\nextern void PaddedPrint (char *s);\n\nstatic qbool IsIgnored(int slot) {\n\treturn cl.players[slot].ignored;\n}\n\nstatic void Display_Ignorelist(void) {\n\tint i;\n\n\tCom_Printf (\"\\x02\" \"User Ignore List:\\n\");\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t\tif (cl.players[i].name[0] && cl.players[i].ignored)\n\t\t\tPaddedPrint(cl.players[i].name);\n\tif (con.x)\n\t\tCom_Printf (\"\\n\");\n\n#ifdef FTE_PEXT2_VOICECHAT\n\tCom_Printf (\"\\x02\" \"VOIP Ignore List:\\n\");\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t\tif (cl.players[i].name[0] && cl.players[i].vignored && !cl.players[i].ignored)\n\t\t\tPaddedPrint(cl.players[i].name);\n\tif (con.x)\n\t\tCom_Printf (\"\\n\");\n#endif\n\n\tCom_Printf (\"\\x02\" \"Team Ignore List:\\n\");\n\tfor (i = 0; i < MAX_TEAMIGNORELIST && ignoreteamlist[i][0]; i++)\n\t\tPaddedPrint(ignoreteamlist[i]);\n\tif (con.x)\n\t\tCom_Printf (\"\\n\");\n\n\tif (ignore_opponents.value)\n\t\tCom_Printf(\"\\x02\" \"Opponents are Ignored\\n\");\n\n\tif (ignore_spec.value == 2 || (ignore_spec.value == 1 && !cl.spectator))\n\t\tCom_Printf (\"\\x02\" \"Spectators are Ignored\\n\");\n\n\tif (ignore_qizmo_spec.value)\n\t\tCom_Printf(\"\\x02\" \"Qizmo spectators are Ignored\\n\");\n\n\tif (ignore_qtv.integer)\n\t\tCom_Printf(\"\\x02\" \"QuakeTV observers are Ignored\\n\");\n\n\tCom_Printf(\"\\n\");\n}\n\nstatic qbool Ignorelist_Add(int slot) {\n\tif (IsIgnored(slot))\n\t\treturn false;\n\n\tcl.players[slot].ignored = true;\n#ifdef FTE_PEXT2_VOICECHAT\n\tcl.players[slot].vignored = true;\n\tS_Voip_Ignore(slot, true);\n#endif\n\treturn true;\n}\n\nstatic qbool Ignorelist_Del (int slot)\n{\n#ifdef FTE_PEXT2_VOICECHAT\n\tif (cl.players[slot].vignored) {\n\t\tcl.players[slot].vignored = false;\n\t\tS_Voip_Ignore (slot, false);\n\t}\n#endif\n\n\tif (cl.players[slot].ignored == false)\n\t\treturn false;\n\n\tcl.players[slot].ignored = false;\n\treturn true;\n}\n\nstatic void Ignore_f(void) {\n\tint c, slot;\n\n\tif ((c = Cmd_Argc()) == 1) {\n\t\tDisplay_Ignorelist();\n\t\treturn;\n\t} else if (c != 2) {\n\t\tCom_Printf(\"Usage: %s [userid | name]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif ((slot = Player_StringtoSlot(Cmd_Argv(1), true, true)) == PLAYER_ID_NOMATCH) {\n\t\tCom_Printf(\"%s : no player with userid %d\\n\", Cmd_Argv(0), Q_atoi(Cmd_Argv(1)));\n\t}\n\telse if (slot == PLAYER_NAME_NOMATCH) {\n\t\tCom_Printf(\"%s : no player with name %s\\n\", Cmd_Argv(0), Cmd_Argv(1));\n\t}\n\telse if (Ignorelist_Add (slot)) {\n\t\tCom_Printf (\"Added user %s to ignore list\\n\", cl.players[slot].name);\n\t}\n\telse {\n\t\tCom_Printf (\"User %s is already ignored\\n\", cl.players[slot].name);\n\t}\n}\n\nstatic void IgnoreList_f(void) {\n\tif (Cmd_Argc() != 1)\n\t\tCom_Printf(\"%s : no arguments expected\\n\", Cmd_Argv(0));\n\telse\n\t\tDisplay_Ignorelist();\n}\n\nstatic void Ignore_ID_f(void) {\n\tint c, userid, i, slot;\n\tchar *arg;\n\n\tif ((c = Cmd_Argc()) == 1) {\n\t\tDisplay_Ignorelist();\n\t\treturn;\n\t} else if (c != 2) {\n\t\tCom_Printf(\"Usage: %s [userid]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\targ = Cmd_Argv(1);\n\tfor (i = 0; arg[i]; i++) {\n\t\tif (!isdigit(arg[i])) {\n\t\t\tCom_Printf(\"Usage: %s [userid]\\n\", Cmd_Argv(0));\n\t\t\treturn;\n\t\t}\n\t}\n\tuserid = Q_atoi(arg);\n\tif ((slot = Player_IdtoSlot(userid)) == PLAYER_ID_NOMATCH) {\n\t\tCom_Printf(\"%s : no player with userid %d\\n\", Cmd_Argv(0), userid);\n\t\treturn;\n\t}\n\tif (Ignorelist_Add(slot))\n\t\tCom_Printf(\"Added user %s to ignore list\\n\", cl.players[slot].name);\n\telse\n\t\tCom_Printf (\"User %s is already ignored\\n\", cl.players[slot].name);\n}\n\nstatic void Unignore_f(void) {\n\tint c, slot;\n\n\tif ((c = Cmd_Argc()) == 1) {\n\t\tDisplay_Ignorelist();\n\t\treturn;\n\t} else if (c != 2) {\n\t\tCom_Printf(\"Usage: %s [userid | name]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif ((slot = Player_StringtoSlot(Cmd_Argv(1), true, true)) == PLAYER_ID_NOMATCH) {\n\t\tCom_Printf(\"%s : no player with userid %d\\n\", Cmd_Argv(0), Q_atoi(Cmd_Argv(1)));\n\t} else if (slot == PLAYER_NAME_NOMATCH) {\n\t\tCom_Printf(\"%s : no player with name %s\\n\", Cmd_Argv(0), Cmd_Argv(1));\n\t} else {\t\t\n\t\tif (Ignorelist_Del(slot))\n\t\t\tCom_Printf(\"Removed user %s from ignore list\\n\", cl.players[slot].name);\n\t\telse\n\t\t\tCom_Printf(\"User %s is not being ignored\\n\", cl.players[slot].name);\n\t}\n}\n\nstatic void Unignore_ID_f(void) {\n\tint c, i, userid, slot;\n\tchar *arg;\n\n\tif ((c = Cmd_Argc()) == 1) {\n\t\tDisplay_Ignorelist();\n\t\treturn;\n\t} else if (c != 2) {\n\t\tCom_Printf(\"Usage: %s [userid]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\targ = Cmd_Argv(1);\n\tfor (i = 0; arg[i]; i++) {\n\t\tif (!isdigit(arg[i])) {\n\t\t\tCom_Printf(\"Usage: %s [userid]\\n\", Cmd_Argv(0));\n\t\t\treturn;\n\t\t}\n\t}\n\tuserid = Q_atoi(arg);\n\tif ((slot = Player_IdtoSlot(userid)) == PLAYER_ID_NOMATCH) {\n\t\tCom_Printf(\"%s : no player with userid %d\\n\", Cmd_Argv(0), userid);\n\t\treturn;\n\t}\n\tif (Ignorelist_Del(slot))\n\t\tCom_Printf(\"Removed user %s from ignore list\\n\", cl.players[slot].name);\n\telse\n\t\tCom_Printf(\"User %s is not being ignored\\n\", cl.players[slot].name);\n}\n\nstatic void Ignoreteam_f(void) {\n\tint c, i, j;\n\tchar *arg;\n\n\tc = Cmd_Argc();\n\tif (c == 1) {\n\t\tDisplay_Ignorelist();\n\t\treturn;\n\t} else if (c != 2) {\n\t\tCom_Printf(\"Usage: %s [team]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\targ = Cmd_Argv(1);\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (cl.players[i].name[0] && !cl.players[i].spectator && !strcmp(arg, cl.players[i].team)) {\n\t\t\tfor (j = 0; j < MAX_TEAMIGNORELIST && ignoreteamlist[j][0]; j++) {\n\t\t\t\tif (!strncmp(arg, ignoreteamlist[j], sizeof(ignoreteamlist[j]) - 1)) {\n\t\t\t\t\tCom_Printf (\"Team %s is already ignored\\n\", arg);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (j == MAX_TEAMIGNORELIST)\n\t\t\t\tCom_Printf(\"You cannot ignore more than %d teams\\n\", MAX_TEAMIGNORELIST);\n\t\t\tstrlcpy(ignoreteamlist[j], arg, sizeof(ignoreteamlist[j]));\n\t\t\tif (j + 1 < MAX_TEAMIGNORELIST)\n\t\t\t\tignoreteamlist[j + 1][0] = 0;\t\t\t\n\t\t\tCom_Printf(\"Added team %s to ignore list\\n\", arg);\n\t\t\treturn;\n\t\t}\n\t}\n\tCom_Printf(\"%s : no team with name %s\\n\", Cmd_Argv(0), arg);\n}\n\nstatic void Unignoreteam_f(void) {\n\tint i, c, j;\n\tchar *arg;\n\n\tc = Cmd_Argc();\n\tif (c == 1) {\n\t\tDisplay_Ignorelist();\n\t\treturn;\n\t} else if (c != 2) {\n\t\tCom_Printf(\"Usage: %s [team]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\t\n\targ = Cmd_Argv(1);\n\tfor (i = 0; i < MAX_TEAMIGNORELIST && ignoreteamlist[i][0]; i++) {\n\t\tif (!strncmp(arg, ignoreteamlist[i], sizeof(ignoreteamlist[i]) - 1)) {\n\t\t\tfor (j = i; j < MAX_TEAMIGNORELIST && ignoreteamlist[j][0]; j++) \n\t\t\t\t;\n\t\t\tif ( --j >  i)\n\t\t\t\tstrlcpy(ignoreteamlist[i], ignoreteamlist[j], sizeof(ignoreteamlist[i]));\n\t\t\tignoreteamlist[j][0] = 0;\t\t\t\n\t\t\tCom_Printf(\"Removed team %s from ignore list\\n\", arg);\n\t\t\treturn;\n\t\t}\n\t}\n\tCom_Printf(\"Team %s is not being ignored\\n\", arg);\n}\n\nstatic void UnignoreAll_f (void) {\n\tint i;\n\n\tif (Cmd_Argc() != 1) {\n\t\tCom_Printf(\"%s : no arguments expected\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t\tcl.players[i].ignored = false;\n\tCom_Printf(\"User ignore list cleared\\n\");\n}\n\nstatic void UnignoreteamAll_f (void) {\n\tif (Cmd_Argc() != 1) {\n\t\tCom_Printf(\"%s : no arguments expected\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\tignoreteamlist[0][0] = 0;\n\tCom_Printf(\"Team ignore list cleared\\n\");\n}\n\nchar Ignore_Check_Flood(char *s, int flags, int offset) {\n\tint i, p, q, len;\n\tchar name[MAX_INFO_STRING];\n\n\tif ( !(  \n\t ( (ignore_flood.value == 1 && (flags == msgtype_normal || flags == msgtype_spec)) ||\n\t (ignore_flood.value == 2 && flags != msgtype_unknown) )\n\t   )  )\n\t\treturn NO_IGNORE_NO_ADD;\n\n\tif (flags == msgtype_normal || flags == msgtype_spec) {\n\t\tp = 0;\n\t\tq = offset - 3;\n\t} else if (flags == msgtype_team) {\n\t\tp = 1;\n\t\tq = offset - 4;\n\t} else if (flags == msgtype_specteam) {\n\t\tp = 7;\n\t\tq = offset -3;\n\t} else\n\t\treturn NO_IGNORE_NO_ADD;\n\n\tlen = bound (0, q - p + 1, MAX_INFO_STRING - 1);\n\n\tstrlcpy(name, s + p, len + 1);\n\tif (!cls.demoplayback && !strcmp(name, Player_MyName())) {\n\t\treturn NO_IGNORE_NO_ADD;\n\t}\n\tfor (i = 0; i < FLOODLIST_SIZE; i++) {\n\t\tif (floodlist[i].data[0] && !strncmp(floodlist[i].data, s, sizeof(floodlist[i].data) - 1) &&\n\t\t\tcls.realtime - floodlist[i].time < ignore_flood_duration.value) {\n\t\t\treturn IGNORE_NO_ADD;\n\t\t}\n\t}\n\treturn NO_IGNORE_ADD;\n}\n\nvoid Ignore_Flood_Add(char *s) {\n\n\tfloodlist[floodindex].data[0] = 0;\n\tstrlcpy(floodlist[floodindex].data, s, sizeof(floodlist[floodindex].data));\n\tfloodlist[floodindex].time = cls.realtime;\n\tfloodindex++;\n\tif (floodindex == FLOODLIST_SIZE)\n\t\tfloodindex = 0;\n}\n\n\nqbool Ignore_Message(char *s, int flags, int offset) {\n\tint slot, i, p, q, len;\t\n\tchar name[32];\n\n\tif (!ignore_mode.value && (flags & msgtype_team))\n\t\treturn false;\t\t\n\n\n\tif (ignore_spec.value == 2 && (flags == msgtype_spec || flags == msgtype_specteam))\n\t\treturn true;\n\telse if (ignore_spec.value == 1 && (flags == msgtype_spec) && !cl.spectator)\n\t\treturn true;\n\telse if (ignore_qtv.integer && (flags & msgtype_qtv))\n\t\treturn true;\n\n\tif (flags == msgtype_normal || flags == msgtype_spec) {\n\t\tp = 0;\n\t\tq = offset - 3;\n\t} else if (flags == msgtype_team) {\n\t\tp = 1;\n\t\tq = offset - 4;\n\t} else if (flags == msgtype_specteam) {\n\t\tp = 7;\n\t\tq = offset - 3;\n\t} else {\n\t\treturn false;\n\t}\n\n\tlen = bound (0, q - p + 1, sizeof(name) - 1);\n\tstrlcpy(name, s + p, len + 1);\n\n\tif ((slot = Player_NametoSlot(name)) == PLAYER_NAME_NOMATCH)\n\t\treturn false;\t\n\n\tif (cl.players[slot].ignored)\n\t\treturn true;\n\t\n\n\tif (ignore_opponents.value && (\n\t\t\t(int) ignore_opponents.value == 1 ||\n\t\t\t(cls.state >= ca_connected && !cl.standby && !cls.demoplayback && !cl.spectator) // match?\n\t\t\t) && \n\t\tflags == msgtype_normal && !cl.spectator && slot != cl.playernum &&\n\t\t(!cl.teamplay || strcmp(cl.players[slot].team, cl.players[cl.playernum].team))\n\t\t)\n\t\treturn true;\n\n\n\tif (!cl.teamplay)\n\t\treturn false;\n\n\tif (cl.players[slot].spectator || !strcmp(Player_MyName(), name))\n\t\treturn false;\t\n\n\tfor (i = 0; i < MAX_TEAMIGNORELIST && ignoreteamlist[i][0]; i++) {\n\t\tif (!strncmp(cl.players[slot].team, ignoreteamlist[i], sizeof(ignoreteamlist[i]) - 1))\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid Ignore_ResetFloodList(void) {\n\tint i;\n\n\tfor (i = 0; i < FLOODLIST_SIZE; i++)\n\t\tfloodlist[i].data[0] = 0;\n\tfloodindex = 0;\n}\n\n#ifdef FTE_PEXT2_VOICECHAT\nstatic qbool Ignorelist_VAdd(int slot)\n{\n\tif (cl.players[slot].vignored)\n\t\treturn false;\n\n\tcl.players[slot].vignored = true;\n\tS_Voip_Ignore(slot, true);\n\treturn true;\n}\n\nstatic qbool IgnoreList_VDel (int slot)\n{\n\tif (!cl.players[slot].vignored)\n\t\treturn false;\n\n\tcl.players[slot].vignored = false;\n\tS_Voip_Ignore(slot, false);\n\treturn true;\n}\n\nstatic void VIgnoreToggleCommand (qbool ignore)\n{\n\tint c, slot;\n\n\tif ((c = Cmd_Argc()) == 1) {\n\t\tDisplay_Ignorelist();\n\t\treturn;\n\t}\n\telse if (c != 2) {\n\t\tCon_Printf(\"Usage: %s [userid | name]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif ((slot = Player_StringtoSlot(Cmd_Argv(1), true, true)) == PLAYER_ID_NOMATCH) {\n\t\tCon_Printf(\"%s : no player with userid %d\\n\", Cmd_Argv(0), Q_atoi(Cmd_Argv(1)));\n\t}\n\telse if (slot == PLAYER_NAME_NOMATCH) {\n\t\tCon_Printf(\"%s : no player with name %s\\n\", Cmd_Argv(0), Cmd_Argv(1));\n\t}\n\telse if (ignore ? (Ignorelist_VAdd (slot)) : (IgnoreList_VDel (slot))) {\n\t\tCon_Printf (\"%s user %s %s ignore list\\n\", ignore ? \"Added\" : \"Removed\", cl.players[slot].name, ignore ? \"to\" : \"from\");\n\t}\n\telse {\n\t\tCon_Printf (\"User %s is %s ignored\\n\", cl.players[slot].name, ignore ? \"already\" : \"not\");\n\t}\n}\n\nstatic void VIgnore_f(void)\n{\n\tVIgnoreToggleCommand (true);\n}\n\nstatic void VUnignore_f (void)\n{\n\tVIgnoreToggleCommand (false);\n}\n#endif\n\nvoid Ignore_Init(void) {\n\tint i;\n\n\tfor (i = 0; i < MAX_TEAMIGNORELIST; i++)\n\t\tignoreteamlist[i][0] = 0;\n\tIgnore_ResetFloodList();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_CHAT);\n\tCvar_Register (&ignore_flood_duration);\n\tCvar_Register (&ignore_flood);\n\tCvar_Register (&ignore_spec);\n\tCvar_Register (&ignore_qizmo_spec);\n\tCvar_Register (&ignore_qtv);\n\tCvar_Register (&ignore_mode);\n\tCvar_Register (&ignore_opponents);\n\tCvar_Register (&ignore_no_weapon);\n\tCvar_Register (&ignore_not_enough_ammo);\n\n\tCvar_ResetCurrentGroup();\n\n#ifdef FTE_PEXT2_VOICECHAT\n\tCmd_AddCommand (\"ignore_voip\", VIgnore_f);\n\tCmd_AddCommand (\"unignore_voip\", VUnignore_f);\n#endif\n\tCmd_AddCommand (\"ignore\", Ignore_f);\t\t\t\t\t\n\tCmd_AddCommand (\"ignorelist\", IgnoreList_f);\t\t\t\n\tCmd_AddCommand (\"unignore\", Unignore_f);\t\t\t\t\n\tCmd_AddCommand (\"ignore_team\", Ignoreteam_f);\t\t\n\tCmd_AddCommand (\"unignore_team\", Unignoreteam_f);\t\n\tCmd_AddCommand (\"unignoreAll\", UnignoreAll_f);\t\t\t\n\tCmd_AddCommand (\"unignoreAll_team\", UnignoreteamAll_f);\t\n\tCmd_AddCommand (\"unignore_id\", Unignore_ID_f);\t\t\n\tCmd_AddCommand (\"ignore_id\", Ignore_ID_f);\t\t\t\n}\n"
  },
  {
    "path": "src/ignore.h",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef __IGNORE_H_\n\n#define __IGNORE_H_\t\n\n\n#define NO_IGNORE_NO_ADD\t0\n#define\tNO_IGNORE_ADD\t\t1\n#define\tIGNORE_NO_ADD\t\t2\n\nvoid Ignore_Init(void);\nqbool Ignore_Message(char *s, int flags, int offset);\nchar Ignore_Check_Flood(char *s, int flags, int offset);\nvoid Ignore_Flood_Add(char *s);\nvoid Ignore_ResetFloodList(void);\n\n#endif\n"
  },
  {
    "path": "src/image.c",
    "content": "/*\nCopyright (C) 1996-2003 A Nourai, Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef __FreeBSD__\n#include <dlfcn.h>\n#endif\n#include \"quakedef.h\"\n#include \"image.h\"\n\n#ifdef WITH_PNG\n#include \"png.h\"\n/*#ifdef _WIN32\n#pragma comment(lib, \"libs/libpng.lib\")\n#endif*/\n#endif\n\n#ifdef WITH_JPEG\n#if defined(_MSC_VER)\n#pragma warning(disable: 4005)\n#endif\n#include <jpeglib.h>\n#include <jerror.h>\n#if defined(_MSC_VER)\n#pragma warning(default: 4005)\n#endif\n/*#ifdef _WIN32\n#pragma comment(lib, \"libs/libjpeg.lib\")\n#endif*/\n#endif\n\n#define IMAGE_MAX_DIMENSIONS 4096\n\ncvar_t image_png_compression_level = {\"image_png_compression_level\", \"1\"};\ncvar_t image_jpeg_quality_level = {\"image_jpeg_quality_level\", \"75\"};\n\n/***************************** IMAGE RESAMPLING ******************************/\n\nstatic void Image_Resample32LerpLine (byte *in, byte *out, int inwidth, int outwidth) \n{\n\tint j, xi, oldx = 0, f, fstep, endx, lerp;\n\n\tfstep = (int) (inwidth * 65536.0f / outwidth);\n\tendx = (inwidth - 1);\n\tfor (j = 0, f = 0; j < outwidth; j++, f += fstep) {\n\t\txi = (int) f >> 16;\n\t\tif (xi != oldx) {\n\t\t\tin += (xi - oldx) * 4;\n\t\t\toldx = xi;\n\t\t}\n\t\tif (xi < endx) {\n\t\t\tlerp = f & 0xFFFF;\n\t\t\t*out++ = (byte) ((((in[4] - in[0]) * lerp) >> 16) + in[0]);\n\t\t\t*out++ = (byte) ((((in[5] - in[1]) * lerp) >> 16) + in[1]);\n\t\t\t*out++ = (byte) ((((in[6] - in[2]) * lerp) >> 16) + in[2]);\n\t\t\t*out++ = (byte) ((((in[7] - in[3]) * lerp) >> 16) + in[3]);\n\t\t} else  {\n\t\t\t*out++ = in[0];\n\t\t\t*out++ = in[1];\n\t\t\t*out++ = in[2];\n\t\t\t*out++ = in[3];\n\t\t}\n\t}\n}\n\nstatic void Image_Resample24LerpLine (byte *in, byte *out, int inwidth, int outwidth) \n{\n\tint j, xi, oldx = 0, f, fstep, endx, lerp;\n\n\tfstep = (int) (inwidth * 65536.0f / outwidth);\n\tendx = (inwidth - 1);\n\tfor (j = 0, f = 0; j < outwidth; j++, f += fstep) {\n\t\txi = (int) f >> 16;\n\t\tif (xi != oldx) {\n\t\t\tin += (xi - oldx) * 3;\n\t\t\toldx = xi;\n\t\t}\n\t\tif (xi < endx) {\n\t\t\tlerp = f & 0xFFFF;\n\t\t\t*out++ = (byte) ((((in[3] - in[0]) * lerp) >> 16) + in[0]);\n\t\t\t*out++ = (byte) ((((in[4] - in[1]) * lerp) >> 16) + in[1]);\n\t\t\t*out++ = (byte) ((((in[5] - in[2]) * lerp) >> 16) + in[2]);\n\t\t} else  {\n\t\t\t*out++ = in[0];\n\t\t\t*out++ = in[1];\n\t\t\t*out++ = in[2];\n\t\t}\n\t}\n}\n\n#define LERPBYTE(i) r = row1[i]; out[i] = (byte) ((((row2[i] - r) * lerp) >> 16) + r)\n#define NOLERPBYTE(i) *out++ = inrow[f + i]\n\nstatic void Image_Resample32 (void *indata, int inwidth, int inheight,\n\t\t\t\t\t\t\t\tvoid *outdata, int outwidth, int outheight, int quality) \n{\n\tif (quality) \n\t{\n\t\tint i, j, r, yi, oldy, f, fstep, endy = (inheight - 1), lerp;\n\t\tint inwidth4 = inwidth * 4, outwidth4 = outwidth * 4;\n\t\tbyte *inrow, *out, *row1, *row2, *memalloc;\n\n\t\tout = outdata;\n\t\tfstep = (int) (inheight * 65536.0f / outheight);\n\n\t\tmemalloc = (byte *) Q_malloc(2 * outwidth4);\n\t\trow1 = memalloc;\n\t\trow2 = memalloc + outwidth4;\n\t\tinrow = (byte *) indata;\n\t\toldy = 0;\n\t\tImage_Resample32LerpLine (inrow, row1, inwidth, outwidth);\n\t\tImage_Resample32LerpLine (inrow + inwidth4, row2, inwidth, outwidth);\n\t\t\n\t\tfor (i = 0, f = 0; i < outheight; i++, f += fstep)\t\n\t\t{\n\t\t\tyi = f >> 16;\n\n\t\t\tif (yi < endy) \n\t\t\t{\n\t\t\t\tlerp = f & 0xFFFF;\n\n\t\t\t\tif (yi != oldy)\n\t\t\t\t{\n\t\t\t\t\tinrow = (byte *) indata + inwidth4 * yi;\n\t\t\t\t\tif (yi == oldy + 1)\n\t\t\t\t\t\tmemcpy(row1, row2, outwidth4);\n\t\t\t\t\telse\n\t\t\t\t\t\tImage_Resample32LerpLine (inrow, row1, inwidth, outwidth);\n\t\t\t\t\tImage_Resample32LerpLine (inrow + inwidth4, row2, inwidth, outwidth);\n\t\t\t\t\toldy = yi;\n\t\t\t\t}\n\n\t\t\t\tj = outwidth - 4;\n\n\t\t\t\twhile(j >= 0)\n\t\t\t\t{\n\t\t\t\t\tLERPBYTE(0); LERPBYTE(1); LERPBYTE(2); LERPBYTE(3);\n\t\t\t\t\tLERPBYTE(4); LERPBYTE(5); LERPBYTE(6); LERPBYTE(7);\n\t\t\t\t\tLERPBYTE(8); LERPBYTE(9); LERPBYTE(10); LERPBYTE(11);\n\t\t\t\t\tLERPBYTE(12); LERPBYTE(13); LERPBYTE(14); LERPBYTE(15);\n\t\t\t\t\tout += 16;\n\t\t\t\t\trow1 += 16;\n\t\t\t\t\trow2 += 16;\n\t\t\t\t\tj -= 4;\n\t\t\t\t}\n\n\t\t\t\tif (j & 2) \n\t\t\t\t{\n\t\t\t\t\tLERPBYTE(0); LERPBYTE(1); LERPBYTE(2); LERPBYTE(3);\n\t\t\t\t\tLERPBYTE(4); LERPBYTE(5); LERPBYTE(6); LERPBYTE(7);\n\t\t\t\t\tout += 8;\n\t\t\t\t\trow1 += 8;\n\t\t\t\t\trow2 += 8;\n\t\t\t\t}\n\n\t\t\t\tif (j & 1)\n\t\t\t\t{\n\t\t\t\t\tLERPBYTE(0); LERPBYTE(1); LERPBYTE(2); LERPBYTE(3);\n\t\t\t\t\tout += 4;\n\t\t\t\t\trow1 += 4;\n\t\t\t\t\trow2 += 4;\n\t\t\t\t}\n\t\t\t\trow1 -= outwidth4;\n\t\t\t\trow2 -= outwidth4;\n\t\t\t} \n\t\t\telse \n\t\t\t{\n\t\t\t\tif (yi != oldy) \n\t\t\t\t{\n\t\t\t\t\tinrow = (byte *) indata + inwidth4 * yi;\n\t\t\t\t\tif (yi == oldy+1)\n\t\t\t\t\t\tmemcpy(row1, row2, outwidth4);\n\t\t\t\t\telse\n\t\t\t\t\t\tImage_Resample32LerpLine (inrow, row1, inwidth, outwidth);\n\t\t\t\t\toldy = yi;\n\t\t\t\t}\n\t\t\t\tmemcpy(out, row1, outwidth4);\n\t\t\t}\n\t\t}\n\n\t\tQ_free(memalloc);\n\t}\n\telse \n\t{\n\t\tint i, j;\n\t\tunsigned int frac, fracstep, *inrow, *out;\n\n\t\tout = outdata;\n\n\t\tfracstep = inwidth * 0x10000 / outwidth;\n\t\tfor (i = 0; i < outheight; i++)\n\t\t{\n\t\t\tinrow = (unsigned int *) indata + inwidth * (i * inheight / outheight);\n\t\t\tfrac = fracstep >> 1;\n\t\t\tj = outwidth - 4;\n\t\t\n\t\t\twhile (j >= 0)\n\t\t\t{\n\t\t\t\tout[0] = inrow[frac >> 16]; frac += fracstep;\n\t\t\t\tout[1] = inrow[frac >> 16]; frac += fracstep;\n\t\t\t\tout[2] = inrow[frac >> 16]; frac += fracstep;\n\t\t\t\tout[3] = inrow[frac >> 16]; frac += fracstep;\n\t\t\t\tout += 4;\n\t\t\t\tj -= 4;\n\t\t\t}\n\n\t\t\tif (j & 2)\n\t\t\t{\n\t\t\t\tout[0] = inrow[frac >> 16]; frac += fracstep;\n\t\t\t\tout[1] = inrow[frac >> 16]; frac += fracstep;\n\t\t\t\tout += 2;\n\t\t\t}\n\n\t\t\tif (j & 1) \n\t\t\t{\n\t\t\t\tout[0] = inrow[frac >> 16]; frac += fracstep;\n\t\t\t\tout += 1;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void Image_Resample24 (void *indata, int inwidth, int inheight,\n\t\t\t\t\t void *outdata, int outwidth, int outheight, int quality)\n{\n\tif (quality)\n\t{\n\t\tint i, j, r, yi, oldy, f, fstep, endy = (inheight - 1), lerp;\n\t\tint inwidth3 = inwidth * 3, outwidth3 = outwidth * 3;\n\t\tbyte *inrow, *out, *row1, *row2, *memalloc;\n\n\t\tout = outdata;\n\t\tfstep = (int) (inheight * 65536.0f / outheight);\n\n\t\tmemalloc = (byte *) Q_malloc(2 * outwidth3);\n\t\trow1 = memalloc;\n\t\trow2 = memalloc + outwidth3;\n\t\tinrow = (byte *) indata;\n\t\toldy = 0;\n\t\tImage_Resample24LerpLine (inrow, row1, inwidth, outwidth);\n\t\tImage_Resample24LerpLine (inrow + inwidth3, row2, inwidth, outwidth);\n\t\t\n\t\tfor (i = 0, f = 0; i < outheight; i++, f += fstep)\t\n\t\t{\n\t\t\tyi = f >> 16;\n\t\t\tif (yi < endy) {\n\t\t\t\tlerp = f & 0xFFFF;\n\t\t\t\tif (yi != oldy)\n\t\t\t\t{\n\t\t\t\t\tinrow = (byte *) indata + inwidth3 * yi;\n\t\t\t\t\tif (yi == oldy + 1)\n\t\t\t\t\t\tmemcpy(row1, row2, outwidth3);\n\t\t\t\t\telse\n\t\t\t\t\t\tImage_Resample24LerpLine (inrow, row1, inwidth, outwidth);\n\t\t\t\t\tImage_Resample24LerpLine (inrow + inwidth3, row2, inwidth, outwidth);\n\t\t\t\t\toldy = yi;\n\t\t\t\t}\n\n\t\t\t\tj = outwidth - 4;\n\n\t\t\t\twhile(j >= 0)\n\t\t\t\t{\n\t\t\t\t\tLERPBYTE(0); LERPBYTE(1); LERPBYTE(2);\n\t\t\t\t\tLERPBYTE(3); LERPBYTE(4); LERPBYTE(5);\n\t\t\t\t\tLERPBYTE(6); LERPBYTE(7); LERPBYTE(8);\n\t\t\t\t\tLERPBYTE(9); LERPBYTE(10); LERPBYTE(11);\n\t\t\t\t\tout += 12;\n\t\t\t\t\trow1 += 12;\n\t\t\t\t\trow2 += 12;\n\t\t\t\t\tj -= 4;\n\t\t\t\t}\n\n\t\t\t\tif (j & 2) \n\t\t\t\t{\n\t\t\t\t\tLERPBYTE(0); LERPBYTE(1); LERPBYTE(2);\n\t\t\t\t\tLERPBYTE(3); LERPBYTE(4); LERPBYTE(5);\n\t\t\t\t\tout += 6;\n\t\t\t\t\trow1 += 6;\n\t\t\t\t\trow2 += 6;\n\t\t\t\t}\n\n\t\t\t\tif (j & 1) \n\t\t\t\t{\n\t\t\t\t\tLERPBYTE(0); LERPBYTE(1); LERPBYTE(2);\n\t\t\t\t\tout += 3;\n\t\t\t\t\trow1 += 3;\n\t\t\t\t\trow2 += 3;\n\t\t\t\t}\n\t\t\t\trow1 -= outwidth3;\n\t\t\t\trow2 -= outwidth3;\n\t\t\t} \n\t\t\telse\n\t\t\t{\n\t\t\t\tif (yi != oldy) \n\t\t\t\t{\n\t\t\t\t\tinrow = (byte *) indata + inwidth3 * yi;\n\t\t\t\t\tif (yi == oldy+1)\n\t\t\t\t\t\tmemcpy(row1, row2, outwidth3);\n\t\t\t\t\telse\n\t\t\t\t\t\tImage_Resample24LerpLine (inrow, row1, inwidth, outwidth);\n\t\t\t\t\toldy = yi;\n\t\t\t\t}\n\n\t\t\t\tmemcpy(out, row1, outwidth3);\n\t\t\t}\n\t\t}\n\n\t\tQ_free(memalloc);\n\t} \n\telse \n\t{\n\t\tint i, j, f;\n\t\tunsigned int frac, fracstep, inwidth3 = inwidth * 3;\n\t\tbyte *inrow, *out;\n\n\t\tout = outdata;\n\n\t\tfracstep = inwidth * 0x10000 / outwidth;\n\t\tfor (i = 0; i < outheight; i++) \n\t\t{\n\t\t\tinrow = (byte *) indata + inwidth3 * (i * inheight / outheight);\n\t\t\tfrac = fracstep >> 1;\n\t\t\tj = outwidth - 4;\n\n\t\t\twhile (j >= 0) \n\t\t\t{\n\t\t\t\tf = (frac >> 16) * 3; NOLERPBYTE(0); NOLERPBYTE(1); NOLERPBYTE(2); frac += fracstep;\n\t\t\t\tf = (frac >> 16) * 3; NOLERPBYTE(0); NOLERPBYTE(1); NOLERPBYTE(2); frac += fracstep;\n\t\t\t\tf = (frac >> 16) * 3; NOLERPBYTE(0); NOLERPBYTE(1); NOLERPBYTE(2); frac += fracstep;\n\t\t\t\tf = (frac >> 16) * 3; NOLERPBYTE(0); NOLERPBYTE(1); NOLERPBYTE(2); frac += fracstep;\n\t\t\t\tj -= 4;\n\t\t\t}\n\n\t\t\tif (j & 2) \n\t\t\t{\n\t\t\t\tf = (frac >> 16) * 3; NOLERPBYTE(0); NOLERPBYTE(1); NOLERPBYTE(2); frac += fracstep;\n\t\t\t\tf = (frac >> 16) * 3; NOLERPBYTE(0); NOLERPBYTE(1); NOLERPBYTE(2); frac += fracstep;\n\t\t\t\tout += 2;\n\t\t\t}\n\n\t\t\tif (j & 1)\n\t\t\t{\n\t\t\t\tf = (frac >> 16) * 3; NOLERPBYTE(0); NOLERPBYTE(1); NOLERPBYTE(2); frac += fracstep;\n\t\t\t\tout += 1;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Image_Resample (void *indata, int inwidth, int inheight,\n\t\t\t\t\t void *outdata, int outwidth, int outheight, int bpp, int quality) \n{\n\tif (bpp == 4)\n\t\tImage_Resample32(indata, inwidth, inheight, outdata, outwidth, outheight, quality);\n\telse if (bpp == 3)\n\t\tImage_Resample24(indata, inwidth, inheight, outdata, outwidth, outheight, quality);\n\telse\n\t\tSys_Error(\"Image_Resample: unsupported bpp (%d)\", bpp);\n}\n\nvoid Image_MipReduce (const byte *in, byte *out, int *width, int *height, int bpp) \n{\n\tconst byte *inrow;\n\tint x, y, nextrow;\n\n\tinrow = in;\n\tnextrow = *width * bpp;\n\n\tif (*width > 1) \n\t{\n\t\t*width >>= 1;\n\n\t\tif (*height > 1) \n\t\t{\n\t\t\t// reduce both (width and height)\n\t\t\t*height >>= 1;\n\t\t\n\t\t\tif (bpp == 4)\n\t\t\t{\n\t\t\t\tfor (y = 0; y < *height; y++, inrow += nextrow * 2)\n\t\t\t\t{\n\t\t\t\t\tfor (in = inrow, x = 0; x < *width; x++)\n\t\t\t\t\t{\n\t\t\t\t\t\tout[0] = (byte) ((in[0] + in[4] + in[nextrow] + in[nextrow + 4]) >> 2);\n\t\t\t\t\t\tout[1] = (byte) ((in[1] + in[5] + in[nextrow + 1] + in[nextrow + 5]) >> 2);\n\t\t\t\t\t\tout[2] = (byte) ((in[2] + in[6] + in[nextrow + 2] + in[nextrow + 6]) >> 2);\n\t\t\t\t\t\tout[3] = (byte) ((in[3] + in[7] + in[nextrow + 3] + in[nextrow + 7]) >> 2);\n\t\t\t\t\t\tout += 4;\n\t\t\t\t\t\tin += 8;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} \n\t\t\telse if (bpp == 3) \n\t\t\t{\n\t\t\t\tfor (y = 0; y < *height; y++, inrow += nextrow * 2)\n\t\t\t\t{\n\t\t\t\t\tfor (in = inrow, x = 0; x < *width; x++)\n\t\t\t\t\t{\n\t\t\t\t\t\tout[0] = (byte) ((in[0] + in[3] + in[nextrow] + in[nextrow + 3]) >> 2);\n\t\t\t\t\t\tout[1] = (byte) ((in[1] + in[4] + in[nextrow + 1] + in[nextrow + 4]) >> 2);\n\t\t\t\t\t\tout[2] = (byte) ((in[2] + in[5] + in[nextrow + 2] + in[nextrow + 5]) >> 2);\n\t\t\t\t\t\tout += 3;\n\t\t\t\t\t\tin += 6;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} \n\t\t\telse \n\t\t\t{\n\t\t\t\tSys_Error(\"Image_MipReduce: unsupported bpp (%d)\", bpp);\n\t\t\t}\n\t\t} \n\t\telse \n\t\t{\n\t\t\t// reduce width\n\t\t\tif (bpp == 4)\n\t\t\t{\n\t\t\t\tfor (y = 0; y < *height; y++, inrow += nextrow)\n\t\t\t\t{\n\t\t\t\t\tfor (in = inrow, x = 0; x < *width; x++)\n\t\t\t\t\t{\n\t\t\t\t\t\tout[0] = (byte) ((in[0] + in[4]) >> 1);\n\t\t\t\t\t\tout[1] = (byte) ((in[1] + in[5]) >> 1);\n\t\t\t\t\t\tout[2] = (byte) ((in[2] + in[6]) >> 1);\n\t\t\t\t\t\tout[3] = (byte) ((in[3] + in[7]) >> 1);\n\t\t\t\t\t\tout += 4;\n\t\t\t\t\t\tin += 8;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} \n\t\t\telse if (bpp == 3) \n\t\t\t{\n\t\t\t\tfor (y = 0; y < *height; y++, inrow += nextrow)\n\t\t\t\t{\n\t\t\t\t\tfor (in = inrow, x = 0; x < *width; x++)\n\t\t\t\t\t{\n\t\t\t\t\t\tout[0] = (byte) ((in[0] + in[3]) >> 1);\n\t\t\t\t\t\tout[1] = (byte) ((in[1] + in[4]) >> 1);\n\t\t\t\t\t\tout[2] = (byte) ((in[2] + in[5]) >> 1);\n\t\t\t\t\t\tout += 3;\n\t\t\t\t\t\tin += 6;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} \n\t\t\telse \n\t\t\t{\n\t\t\t\tSys_Error(\"Image_MipReduce: unsupported bpp (%d)\", bpp);\n\t\t\t}\n\t\t}\n\t}\n\telse if (*height > 1)\n\t{\n\t\t// reduce height\n\t\t*height >>= 1;\n\n\t\tif (bpp == 4) \n\t\t{\n\t\t\tfor (y = 0; y < *height; y++, inrow += nextrow * 2)\n\t\t\t{\n\t\t\t\tfor (in = inrow, x = 0; x < *width; x++)\n\t\t\t\t{\n\t\t\t\t\tout[0] = (byte) ((in[0] + in[nextrow]) >> 1);\n\t\t\t\t\tout[1] = (byte) ((in[1] + in[nextrow + 1]) >> 1);\n\t\t\t\t\tout[2] = (byte) ((in[2] + in[nextrow + 2]) >> 1);\n\t\t\t\t\tout[3] = (byte) ((in[3] + in[nextrow + 3]) >> 1);\n\t\t\t\t\tout += 4;\n\t\t\t\t\tin += 4;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (bpp == 3) \n\t\t{\n\t\t\tfor (y = 0; y < *height; y++, inrow += nextrow * 2)\n\t\t\t{\n\t\t\t\tfor (in = inrow, x = 0; x < *width; x++)\n\t\t\t\t{\n\t\t\t\t\tout[0] = (byte) ((in[0] + in[nextrow]) >> 1);\n\t\t\t\t\tout[1] = (byte) ((in[1] + in[nextrow + 1]) >> 1);\n\t\t\t\t\tout[2] = (byte) ((in[2] + in[nextrow + 2]) >> 1);\n\t\t\t\t\tout += 3;\n\t\t\t\t\tin += 3;\n\t\t\t\t}\n\t\t\t}\n\t\t} \n\t\telse \n\t\t{\n\t\t\tSys_Error(\"Image_MipReduce: unsupported bpp (%d)\", bpp);\n\t\t}\n\t} \n\telse\n\t{\n\t\tSys_Error(\"Image_MipReduce: Input texture has dimensions %dx%d\", width, height);\n\t}\n}\n\n/************************************ PNG ************************************/\n#ifdef WITH_PNG\n\n#ifdef WITH_APNG\n#undef WITH_APNG\n#endif\n\n#ifdef PNG_IO_STATE_SUPPORTED\n#define WITH_APNG\n#endif\n\n#ifdef WITH_APNG\nstatic vfsfile_t* apng_fp;\nstatic png_structp apng_ptr;\nstatic png_infop apng_info_ptr;\nstatic int apng_framenumber;\nstatic int apng_compression;\n#endif\n\nstatic void PNG_IO_user_read_data(png_structp png_ptr, png_bytep data, png_size_t length) {\n\tvfsfile_t *v = (vfsfile_t *) png_get_io_ptr(png_ptr);\n\tvfserrno_t err;\n\tVFS_READ(v, data, (int)length, &err);\n}\n\nstatic void PNG_IO_user_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {\n\tvfsfile_t *v = (vfsfile_t *) png_get_io_ptr(png_ptr);\n\n\tVFS_WRITE(v, data, (int)length);\n}\n\n#ifdef WITH_APNG\nstatic byte* apng_data = NULL;\nstatic size_t apng_data_limit = 0;\nstatic size_t apng_data_length = 0;\n\nstatic void PNG_IO_user_write_data_apng_discard(png_structp png_ptr, png_bytep data, png_size_t length)\n{\n\t// do nothing\n}\n\nstatic void PNG_IO_user_flush_data_apng_discard(png_structp png_ptr)\n{\n\t// do nothing\n}\n\nstatic void PNG_IO_user_write_data_apng(png_structp png_ptr, png_bytep data, png_size_t length)\n{\n\tif ((png_get_io_state(png_ptr) & PNG_IO_MASK_LOC) == PNG_IO_CHUNK_DATA) {\n\t\tif (!apng_data) {\n\t\t\tapng_data_limit = 256 * 1024;\n\t\t\tapng_data_length = 0;\n\t\t\tapng_data = Q_malloc(apng_data_limit);\n\t\t}\n\n\t\tif (apng_data_length + length > apng_data_limit) {\n\t\t\tapng_data_limit += max(length + 4, 64 * 1024);\n\t\t\tapng_data = Q_realloc(apng_data, apng_data_limit);\n\t\t}\n\n\t\tif (apng_data_length == 0) {\n\t\t\t*(unsigned int*)apng_data = htonl(apng_framenumber);\n\t\t\tapng_data_length += 4;\n\t\t}\n\n\t\tmemcpy(apng_data + apng_data_length, data, length);\n\t\tapng_data_length += length;\n\t}\n}\n#endif\n\nstatic void PNG_IO_user_flush_data(png_structp png_ptr)\n{\n\tvfsfile_t *v = (vfsfile_t *) png_get_io_ptr(png_ptr);\n\tVFS_FLUSH(v);\n}\n\n#define PNG_HEADER_LENGTH\t8\n#define PNG_LOAD_TEXT\t\t1\n#define PNG_LOAD_DATA\t\t2\n\nstatic qbool PNG_HasHeader (vfsfile_t *fin)\n{\n\tbyte header[PNG_HEADER_LENGTH];\n\tvfserrno_t err;\n\n\tVFS_READ(fin, header, PNG_HEADER_LENGTH, &err);\n\tif (png_sig_cmp(header, 0, PNG_HEADER_LENGTH)) \n\t{\n\t\tVFS_CLOSE(fin);\n\t\tfin = NULL;\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic void Image_PngErrorHandler(png_structp png_ptr, png_const_charp error_msg)\n{\n\tconst char* filename = (const char*)png_get_error_ptr(png_ptr);\n\n\tif (filename == NULL || !filename[0]) {\n\t\tfilename = \"(unknown path)\";\n\t}\n\tif (error_msg == NULL || !error_msg[0]) {\n\t\terror_msg = \"unknown error\";\n\t}\n\n\tSys_Error(\"Invalid PNG detected: %s (%s)\\n\", filename, error_msg);\n}\n\nstatic void Image_PngWarningHandler(png_structp png_ptr, png_const_charp error_msg)\n{\n\tconst char* filename = (const char*)png_get_error_ptr(png_ptr);\n\n\tif (filename == NULL || !filename[0]) {\n\t\tfilename = \"(unknown path)\";\n\t}\n\tif (error_msg == NULL || !error_msg[0]) {\n\t\terror_msg = \"unknown error\";\n\t}\n\tif (strstr(error_msg, \"known incorrect sRGB profile\")) {\n\t\treturn; // only matters if we would subsequently save the .png\n\t}\n\n\tCon_Printf(\"&cdd0libpng&r: %s (%s)\\n\", filename, error_msg);\n}\n\npng_data *Image_LoadPNG_All (vfsfile_t *fin, const char *filename, int matchwidth, int matchheight, int loadflag, int *real_width, int *real_height)\n{\n\tbyte **rowpointers = NULL;\n\tbyte *data = NULL;\n\tpng_structp png_ptr = NULL;\n\tpng_infop pnginfo = NULL;\t\t\t\n\tpng_textp textchunks = NULL;\t\t// Actual text chunks that will be returned.\n\tpng_textp png_text_ptr = NULL;\t\t// Text chunks are read into this (will be scrapped by libpng).\n\tpng_data *png_return_val = NULL;\t// Return struct containing data + text chunks.\n\tint n_textcount = 0;\t\t\t\t// Number of text chunks.\n\tint y, width, height, bitdepth, colortype, interlace, compression, filter, bytesperpixel;\n\tpng_size_t rowbytes;\n\n\t// Check if we were given a non-null file pointer\n\t// if so then use it, otherwise try to open the specified filename.\n\tif (!fin && !(fin = FS_OpenVFS(filename, \"rb\", FS_ANY)))\n\t{\n\t\treturn NULL;\n\t}\n\n\t// Check if the loaded file contains a PNG header.\n\tif (!PNG_HasHeader (fin))\n\t{\n\t\tCom_DPrintf (\"Invalid PNG image %s\\n\", COM_SkipPath(filename));\n\t\treturn NULL;\n\t}\n\n\t// Try creating a PNG structure for reading the file.\n\tif (!(png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (void*)filename, Image_PngErrorHandler, Image_PngWarningHandler))) \n\t{\n\t\tVFS_CLOSE(fin);\n\t\tfin = NULL;\n\t\treturn NULL;\n\t}\n\n\t// Create a structure for reading the characteristics of the image.\n\tif (!(pnginfo = png_create_info_struct(png_ptr))) \n\t{\n\t\tpng_destroy_read_struct(&png_ptr, &pnginfo, NULL);\n\t\tVFS_CLOSE(fin);\n\t\tfin = NULL;\n\t\treturn NULL;\n\t}\n\n\t// Set the return address that PNGLib should return to if\n\t// an error occurs during reading.\n#if 0\n\tif (setjmp(png_ptr->jmpbuf)) \n\t{\n\t\tpng_destroy_read_struct(&png_ptr, &pnginfo, NULL);\n\t\tVFS_CLOSE(fin);\n\t\tfin = NULL;\n\t\treturn NULL;\n\t}\n#endif\n\n\t// Set the read function that should be used.\n    png_set_read_fn(png_ptr, fin, PNG_IO_user_read_data);\n\n\t// Tell PNG-lib that we have already handled the first <num_bytes> magic bytes.\n\t// Handling more than 8 bytes from the beginning of the file is an error.\n\tpng_set_sig_bytes(png_ptr, PNG_HEADER_LENGTH);\n\n\t// Read all the file information until the actual image data.\n\tpng_read_info(png_ptr, pnginfo);\n\n\t// Get the IHDR info and set transformations based on it's contents\n\t// suc as pallete type / bit depth.\n\t//\n\t// The IHDR chunk contains the width, height, bpp, colortype, \n\t// filter method and compression type used.\n\t// This must be the first chunk in the file.\n\t{\n\t\tpng_get_IHDR(png_ptr, pnginfo, (png_uint_32 *) &width, (png_uint_32 *) &height, &bitdepth,\n\t\t\t&colortype, &interlace, &compression, &filter);\n\n\t\t// Return the width and height.\n\t\tif (real_width)\n\t\t\t(*real_width) = width;\n\n\t\tif (real_height)\n\t\t\t(*real_height) = height;\n\n\t\t// Too big?\n\t\tif (width > IMAGE_MAX_DIMENSIONS || height > IMAGE_MAX_DIMENSIONS) \n\t\t{\n\t\t\tCom_DPrintf (\"PNG image %s exceeds maximum supported dimensions\\n\", COM_SkipPath(filename));\n\t\t\tpng_destroy_read_struct(&png_ptr, &pnginfo, NULL);\n\t\t\tVFS_CLOSE(fin);\n\t\t\tfin = NULL;\n\t\t\treturn NULL;\n\t\t}\n\n\t\t// If the caller wanted the image to be a specific size\n\t\t// make sure it is that size, otherwise cleanup and return.\n\t\tif ((matchwidth && width != matchwidth) || (matchheight && height != matchheight)) \n\t\t{\n\t\t\tpng_destroy_read_struct(&png_ptr, &pnginfo, NULL);\n\t\t\tVFS_CLOSE(fin);\n\t\t\tfin = NULL;\n\t\t\treturn NULL;\n\t\t}\n\n\t\tif (colortype == PNG_COLOR_TYPE_PALETTE) \n\t\t{\n\t\t\tpng_set_palette_to_rgb(png_ptr);\n\t\t\tpng_set_filler(png_ptr, 255, PNG_FILLER_AFTER);\t\t\n\t\t}\n\n\t\tif (colortype == PNG_COLOR_TYPE_GRAY && bitdepth < 8) \n\t\t{\n#if PNG_LIBPNG_VER >= 10209\n\t\t\tpng_set_expand_gray_1_2_4_to_8(png_ptr);\n#else\n\t\t\tpng_set_gray_1_2_4_to_8(png_ptr);\n#endif\n\t\t}\n\t\t\n\t\tif (png_get_valid(png_ptr, pnginfo, PNG_INFO_tRNS))\t\n\t\t{\n\t\t\tpng_set_tRNS_to_alpha(png_ptr);\n\t\t}\n\n\t\tif (colortype == PNG_COLOR_TYPE_GRAY || colortype == PNG_COLOR_TYPE_GRAY_ALPHA)\n\t\t{\n\t\t\tpng_set_gray_to_rgb(png_ptr);\n\t\t}\n\n\t\tif (colortype != PNG_COLOR_TYPE_RGBA)\n\t\t{\n\t\t\tpng_set_filler(png_ptr, 255, PNG_FILLER_AFTER);\n\t\t}\n\n\t\tif (bitdepth < 8)\n\t\t{\n\t\t\tpng_set_expand (png_ptr);\n\t\t}\n\t\telse if (bitdepth == 16)\n\t\t{\n\t\t\tpng_set_strip_16(png_ptr);\n\t\t}\n\n\t\t// Update the pnginfo structure with our transformation changes.\n\t\tpng_read_update_info(png_ptr, pnginfo);\n\t}\n\n\t//\n\t// Read the text chunks before the image data.\n\t//\n\tif (loadflag & PNG_LOAD_TEXT)\n\t{\n\t\t// Get the text chunks found before the image data.\n\t\tn_textcount = 0;\n\t\tpng_get_text(png_ptr, pnginfo, &png_text_ptr, &n_textcount);\n\t}\n\n\t//\n\t// Read the image data.\n\t//\n\t{\n\t\t// Get the number of bytes a row will use / number of channels / bitdepth.\n\t\trowbytes = png_get_rowbytes(png_ptr, pnginfo);\n\t\tbytesperpixel = png_get_channels(png_ptr, pnginfo);\n\t\tbitdepth = png_get_bit_depth(png_ptr, pnginfo);\n\n\t\t// We don't support some formats.\n\t\tif (bitdepth != 8 || (bytesperpixel != 4 && bytesperpixel != 1)) \n\t\t{\n\t\t\tCom_DPrintf (\"Unsupported PNG image %s: Bad color depth and/or bpp\\n\", COM_SkipPath(filename));\n\t\t\tpng_destroy_read_struct(&png_ptr, &pnginfo, NULL);\n\t\t\tVFS_CLOSE(fin);\n\t\t\tfin = NULL;\n\t\t\treturn NULL;\n\t\t}\n\n\t\t// Allocate a byte array for the actual image data.\n\t\tdata = (byte *) Q_malloc_named(height * rowbytes, filename);\n\n\t\t// Even though we just allocated the memory for all the image data\n\t\t// PNG lib wants this in the form of rowpointers.\n\t\trowpointers = (byte **) Q_malloc(height * sizeof(*rowpointers));\n\t\tfor (y = 0; y < height; y++)\n\t\t{\n\t\t\trowpointers[y] = data + y * rowbytes;\n\t\t}\n\n\t\t// Read the actual image data.\n\t\tpng_read_image(png_ptr, rowpointers);\n\t\tpng_read_end(png_ptr, pnginfo);\n\n\t\tQ_free(rowpointers);\n\t}\n\t\n\t//\n\t// Read the text chunks after the image data.\n\t//\n\tif (loadflag & PNG_LOAD_TEXT)\n\t{\n\t\t// Read text chunks after the image data also.\n\t\tpng_get_text(png_ptr, pnginfo, &png_text_ptr, &n_textcount);\n\t\n\t\t// Return the text chunks if we found any.\n\t\tif(n_textcount > 0)\n\t\t{\n\t\t\tint i = 0;\n\t\t\tint len = 0;\n\t\t\ttextchunks = (png_textp)Q_calloc(n_textcount, sizeof(png_text));\n\n\t\t\tfor(i = 0; i < n_textcount; i++)\n\t\t\t{\n\t\t\t\tlen = strlen(png_text_ptr[i].key);\n\n\t\t\t\ttextchunks[i].key = (char *)Q_calloc(len + 1, sizeof(char));\n\t\t\t\ttextchunks[i].text = (char *)Q_calloc(png_text_ptr[i].text_length + 1, sizeof(char));\n\n\t\t\t\tstrlcpy (textchunks[i].key, png_text_ptr[i].key, len + 1);\n\t\t\t\tstrlcpy (textchunks[i].text, png_text_ptr[i].text, png_text_ptr[i].text_length + 1);\n\t\t\t\t\n\t\t\t\ttextchunks[i].text_length = png_text_ptr[i].text_length;\n\t\t\t\ttextchunks[i].compression = png_text_ptr[i].compression;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttextchunks = NULL;\n\t\t}\n\t}\n\n\t// Clean up.\n\tpng_destroy_read_struct (&png_ptr, &pnginfo, NULL);\n\tVFS_CLOSE(fin);\n\tfin = NULL;\n\n\t// If we don't care about the data.\n\tif (!(loadflag & PNG_LOAD_DATA))\n\t{\n\t\tQ_free (data);\n\t}\n\n\t// Gather up the return data.\n\tpng_return_val = (png_data *)Q_malloc(sizeof(png_data));\t\n\tpng_return_val->data = data;\n\tpng_return_val->textchunks = textchunks;\n\tpng_return_val->text_count = n_textcount;\n\n\treturn png_return_val;\n}\n\npng_textp Image_LoadPNG_Comments (char *filename, int *text_count)\n{\n\tpng_textp textchunks = NULL;\n\tpng_data *pdata = NULL;\n\n\tpdata = Image_LoadPNG_All (NULL, filename, 0, 0, PNG_LOAD_TEXT, NULL, NULL);\n\n\tif (pdata)\n\t{\t\t\n\t\t// Return text chunks + count.\n\t\t(*text_count) = (int)pdata->text_count;\n\t\ttextchunks = pdata->textchunks;\n\n\t\tQ_free(pdata->data);\n\t\tQ_free(pdata);\n\t}\n\n\treturn textchunks;\n}\n\nbyte *Image_LoadPNG (vfsfile_t *fin, const char *filename, int matchwidth, int matchheight, int *real_width, int *real_height) \n{\n\tbyte *data = NULL;\n\tpng_data *pdata;\n\t\n\t// Load the actual image.\n\tpdata = Image_LoadPNG_All (fin, filename, matchwidth, matchheight, PNG_LOAD_DATA, real_width, real_height);\n\t\n\tif (pdata)\n\t{\n\t\t// Save the data and free the rest.\n\t\tdata = pdata->data;\n\t\tQ_free(pdata);\n\t}\n\t\n\treturn data;\n}\n\nint Image_WritePNG(char *filename, int compression, byte *pixels, size_t width, size_t height)\n{\n\tchar name[MAX_PATH];\n\tint i, bpp = 3, pngformat;\n\tvfsfile_t *fp;\n\n\tpng_structp png_ptr;\n\tpng_infop info_ptr;\n\tpng_byte **rowpointers;\n\tsnprintf (name, sizeof(name), \"%s\", filename);\n\n\tif (!(fp = FS_OpenVFS(name, \"wb\", FS_NONE_OS))) {\n\t\tFS_CreatePath (name);\n\t\tif (!(fp = FS_OpenVFS(name, \"wb\", FS_NONE_OS)))\n\t\t\treturn false;\n\t}\n\n\tif (!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))) {\n\t\tVFS_CLOSE(fp);\n\t\treturn false;\n\t}\n\n\tif (!(info_ptr = png_create_info_struct(png_ptr))) {\n\t\tpng_destroy_write_struct(&png_ptr, (png_infopp) NULL);\n\t\tVFS_CLOSE(fp);\n\t\treturn false;\n\t}\n\n#if 0\n\tif (setjmp(png_ptr->jmpbuf)) {\n\t\tpng_destroy_write_struct(&png_ptr, &info_ptr);\n\t\tVFS_CLOSE(fp);\n\t\treturn false;\n\t}\n#endif\n\n    png_set_write_fn(png_ptr, fp, PNG_IO_user_write_data, PNG_IO_user_flush_data);\n\tpng_set_compression_level(png_ptr, bound(Z_NO_COMPRESSION, compression, Z_BEST_COMPRESSION));\n\n\tpngformat = (bpp == 4) ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB;\n\tpng_set_IHDR(png_ptr, info_ptr, (png_uint_32)width, (png_uint_32)height, 8, pngformat,\n\t\tPNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);\n\n\tpng_write_info(png_ptr, info_ptr);\n\n\trowpointers = (png_byte **) Q_malloc (height * sizeof(*rowpointers));\n\tfor (i = 0; i < height; i++) {\n\t\trowpointers[i] = pixels + (height - i - 1) * width * bpp;\n\t}\n\tpng_write_image(png_ptr, rowpointers);\n\tpng_write_end(png_ptr, info_ptr);\n\tQ_free(rowpointers);\n\tpng_destroy_write_struct(&png_ptr, &info_ptr);\n\tVFS_CLOSE(fp);\n\treturn true;\n}\n\n#ifdef WITH_APNG\nqbool Image_OpenAPNG(char* filename, int compression, int width, int height, int frames)\n{\n\tchar name[MAX_PATH];\n\tint bpp = 3, pngformat;\n\n\tsnprintf(name, sizeof(name), \"%s\", filename);\n\n\twidth = abs(width);\n\n\tif (!(apng_fp = FS_OpenVFS(name, \"wb\", FS_NONE_OS))) {\n\t\tFS_CreatePath(name);\n\t\tif (!(apng_fp = FS_OpenVFS(name, \"wb\", FS_NONE_OS))) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (!(apng_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))) {\n\t\tVFS_CLOSE(apng_fp);\n\t\tapng_fp = NULL;\n\t\treturn false;\n\t}\n\n\tif (!(apng_info_ptr = png_create_info_struct(apng_ptr))) {\n\t\tpng_destroy_write_struct(&apng_ptr, (png_infopp)NULL);\n\t\tVFS_CLOSE(apng_fp);\n\t\tapng_fp = NULL;\n\t\treturn false;\n\t}\n\n\tpng_set_write_fn(apng_ptr, apng_fp, PNG_IO_user_write_data, PNG_IO_user_flush_data);\n\tpng_set_compression_level(apng_ptr, bound(Z_NO_COMPRESSION, compression, Z_BEST_COMPRESSION));\n\tpngformat = (bpp == 4) ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB;\n\tpng_set_IHDR(apng_ptr, apng_info_ptr, width, height, 8, pngformat, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);\n\tpng_write_info(apng_ptr, apng_info_ptr);\n\n\t// write acTL block\n\t{\n\t\tbyte acTL[] = { 'a', 'c', 'T', 'L' };\n\t\tunsigned int actldata[2] = { htonl(frames), htonl(0) };\n\n\t\tpng_write_chunk(apng_ptr, acTL, (png_const_bytep) actldata, sizeof(actldata));\n\t}\n\n\tapng_framenumber = 0;\n\tapng_compression = compression;\n\treturn true;\n}\n\nqbool Image_WriteAPNGFrame(byte* pixels, size_t width, size_t height, int fps)\n{\n\tpng_byte **rowpointers = (png_byte **)Q_malloc(height * sizeof(*rowpointers));\n\tint i, bpp = 3;\n\n\t// Write fcTL chunk\n\t{\n\t\tstruct {\n\t\t\tunsigned int sequence_number;\n\t\t\tunsigned int width;\n\t\t\tunsigned int height;\n\t\t\tunsigned int x_offset;\n\t\t\tunsigned int y_offset;\n\t\t\tunsigned short delay_num;\n\t\t\tunsigned short delay_den;\n\t\t\tbyte dispose_op;\n\t\t\tbyte blend_op;\n\t\t} fcTL_chunk;\n\t\tbyte header[4] = { 'f', 'c', 'T', 'L' };\n\n\t\tfcTL_chunk.sequence_number = htonl(apng_framenumber);\n\t\tfcTL_chunk.width = htonl((u_long)width);\n\t\tfcTL_chunk.height = htonl((u_long)height);\n\t\tfcTL_chunk.x_offset = htonl(0);\n\t\tfcTL_chunk.y_offset = htonl(0);\n\t\tfcTL_chunk.delay_num = htons(1);\n\t\tfcTL_chunk.delay_den = htons(fps);\n\t\tfcTL_chunk.dispose_op = 0;       // APNG_DISPOSE_OP_NONE\n\t\tfcTL_chunk.blend_op = 0;         // APNG_BLEND_OP_SOURCE\n\n\t\tpng_write_chunk(apng_ptr, header, (png_const_bytep)&fcTL_chunk, 26);\n\n\t\t++apng_framenumber;\n\t}\n\n\tfor (i = 0; i < height; i++) {\n\t\trowpointers[i] = pixels + (height - i - 1) * width * bpp;\n\t}\n\tif (apng_framenumber >= 2) {\n\t\t// Create a pretend 'new' .png so the IDAT is correct\n\t\tbyte fdAT[4] = { 'f', 'd', 'A', 'T' };\n\t\t{\n\t\t\tpng_structp fake_apng_ptr;\n\t\t\tpng_infop fake_apng_info_ptr;\n\n\t\t\tfake_apng_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);\n\t\t\tfake_apng_info_ptr = png_create_info_struct(fake_apng_ptr);\n\n\t\t\tpng_set_write_fn(fake_apng_ptr, NULL, PNG_IO_user_write_data_apng_discard, PNG_IO_user_flush_data_apng_discard);\n\t\t\tpng_set_compression_level(fake_apng_ptr, bound(Z_NO_COMPRESSION, apng_compression, Z_BEST_COMPRESSION));\n\t\t\tpng_set_IHDR(fake_apng_ptr, fake_apng_info_ptr, (png_uint_32)width, (png_uint_32)height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);\n\t\t\tpng_write_info(fake_apng_ptr, fake_apng_info_ptr);\n\n\t\t\tpng_write_flush(fake_apng_ptr);\n\t\t\tpng_set_write_fn(fake_apng_ptr, NULL, PNG_IO_user_write_data_apng, PNG_IO_user_flush_data);\n\t\t\tapng_data_length = 0;\n\t\t\tpng_write_image(fake_apng_ptr, rowpointers);\n\t\t\tpng_write_flush(fake_apng_ptr);\n\t\t\tpng_destroy_write_struct(&fake_apng_ptr, &fake_apng_info_ptr);\n\t\t}\n\n\t\tpng_write_chunk(apng_ptr, fdAT, apng_data, apng_data_length);\n\n\t\t++apng_framenumber;\n\t}\n\telse {\n\t\tbyte IDAT[4] = { 'I', 'D', 'A', 'T' };\n\n\t\tpng_write_flush(apng_ptr);\n\t\tpng_set_write_fn(apng_ptr, apng_fp, PNG_IO_user_write_data_apng, PNG_IO_user_flush_data);\n\t\tapng_data_length = 0;\n\t\tpng_write_image(apng_ptr, rowpointers);\n\t\tpng_write_flush(apng_ptr);\n\t\tpng_set_write_fn(apng_ptr, apng_fp, PNG_IO_user_write_data, PNG_IO_user_flush_data);\n\n\t\t// Skip first four bytes, contains our sequence number\n\t\tpng_write_chunk(apng_ptr, IDAT, apng_data + 4, apng_data_length - 4);\n\t}\n\n\tapng_data_length = 0;\n\treturn true;\n}\n\nqbool Image_CloseAPNG(void)\n{\n\tpng_write_end(apng_ptr, apng_info_ptr);\n\tpng_destroy_write_struct(&apng_ptr, &apng_info_ptr);\n\tVFS_CLOSE(apng_fp);\n\tapng_fp = NULL;\n\tQ_free(apng_data);\n\tapng_data_limit = apng_data_length = 0;\n\n\treturn true;\n}\n#endif // WITH_APNG\n#endif // WITH_PNG\n\n#ifndef WITH_APNG\n// This might be because IO-state isn't supported, or we're building without PNG support\nqbool Image_OpenAPNG(char* filename, int compression, int width, int height, int frames)\n{\n\treturn false;\n}\n\nqbool Image_WriteAPNGFrame(byte* pixels, size_t width, size_t height, int fps)\n{\n\treturn false;\n}\n\nqbool Image_CloseAPNG(void)\n{\n\treturn false;\n}\n#endif\n\n/************************************ TGA ************************************/\n\n// Definitions for image types\n#define TGA_MAPPED\t\t1\t// Uncompressed, color-mapped images\n#define TGA_MAPPED_RLE\t9\t// Runlength encoded color-mapped images\n#define TGA_RGB\t\t\t2\t// Uncompressed, RGB images\n#define TGA_RGB_RLE\t\t10\t// Runlength encoded RGB images\n#define TGA_MONO\t\t3\t// Uncompressed, black and white images\n#define TGA_MONO_RLE\t11\t// Compressed, black and white images\n\n// Custom definitions to simplify code\n#define MYTGA_MAPPED\t80\n#define MYTGA_RGB15\t\t81\n#define MYTGA_RGB24\t\t82\n#define MYTGA_RGB32\t\t83\n#define MYTGA_MONO8\t\t84\n#define MYTGA_MONO16\t85\n\ntypedef struct TGAHeader_s \n{\n\tbyte\t\t\tidLength, colormapType, imageType;\n\tunsigned short\tcolormapIndex, colormapLength;\n\tbyte\t\t\tcolormapSize;\n\tunsigned short\txOrigin, yOrigin, width, height;\n\tbyte\t\t\tpixelSize, attributes;\n} TGAHeader_t;\n\n\nstatic void TGA_upsample15(byte *dest, byte *src, qbool alpha) \n{\n\tdest[2] = (byte) ((src[0] & 0x1F) << 3);\n\tdest[1] = (byte) ((((src[1] & 0x03) << 3) + ((src[0] & 0xE0) >> 5)) << 3);\n\tdest[0] = (byte) (((src[1] & 0x7C) >> 2) << 3);\n\tdest[3] = (alpha && !(src[1] & 0x80)) ? 0 : 255;\n}\n\nstatic void TGA_upsample24(byte *dest, byte *src) \n{\n\tdest[2] = src[0];\n\tdest[1] = src[1];\n\tdest[0] = src[2];\n\tdest[3] = 255;\n}\n\nstatic void TGA_upsample32(byte *dest, byte *src) \n{\n\tdest[2] = src[0];\n\tdest[1] = src[1];\n\tdest[0] = src[2];\n\tdest[3] = src[3];\n}\n\n\n#define TGA_ERROR(msg)\t{if (msg) {Com_DPrintf((msg), COM_SkipPath(filename));} Q_free(fileBuffer); return NULL;}\n\nbyte *Image_LoadTGA(vfsfile_t *fin, const char *filename, int matchwidth, int matchheight, int *real_width, int *real_height) \n{\n\tTGAHeader_t header;\n\tint i, x, y, bpp, alphabits, compressed, mytype, row_inc, runlen, readpixelcount;\n\tint image_width = -1, image_height = -1;\n\tbyte *fileBuffer, *in, *out, *data, *enddata, rgba[4], palette[256 * 4];\n\tint filesize;\n\n\tif (!fin && !(fin = FS_OpenVFS(filename, \"rb\", FS_ANY)))\n\t\treturn NULL;\n\tfilesize = VFS_GETLEN(fin);\n\tfileBuffer = (byte *) Q_malloc(filesize);\n\n\tVFS_READ(fin, fileBuffer, filesize, NULL);\n\tVFS_CLOSE(fin);\n\n\tif (filesize < 19)\n\t\tTGA_ERROR(NULL);\n\n\theader.idLength = fileBuffer[0];\n\theader.colormapType = fileBuffer[1];\n\theader.imageType = fileBuffer[2];\n\n\theader.colormapIndex = BuffLittleShort(fileBuffer + 3);\n\theader.colormapLength = BuffLittleShort(fileBuffer + 5);\n\theader.colormapSize = fileBuffer[7];\n\theader.xOrigin = BuffLittleShort(fileBuffer + 8);\n\theader.yOrigin = BuffLittleShort(fileBuffer + 10);\n\theader.width = image_width = BuffLittleShort(fileBuffer + 12);\n\theader.height = image_height = BuffLittleShort(fileBuffer + 14);\n\theader.pixelSize = fileBuffer[16];\n\theader.attributes = fileBuffer[17];\n\n\t// Return the width and height.\n\tif (real_width)\n\t\t(*real_width) = image_width;\n\n\tif (real_height)\n\t\t(*real_height) = image_height;\n\n\tif (image_width > IMAGE_MAX_DIMENSIONS || image_height > IMAGE_MAX_DIMENSIONS || image_width <= 0 || image_height <= 0)\n\t\tTGA_ERROR(NULL);\n\tif ((matchwidth && image_width != matchwidth) || (matchheight && image_height != matchheight))\n\t\tTGA_ERROR(NULL);\n\n\tbpp = (header.pixelSize + 7) >> 3;\n\talphabits = (header.attributes & 0x0F);\n\tcompressed = (header.imageType & 0x08);\n\n\tin = fileBuffer + 18 + header.idLength;\n\tenddata = fileBuffer + filesize;\n\n\t// error check the image type's pixel size\n\tif (header.imageType == TGA_RGB || header.imageType == TGA_RGB_RLE) {\n\t\tif (!(header.pixelSize == 15 || header.pixelSize == 16 || header.pixelSize == 24 || header.pixelSize == 32))\n\t\t\tTGA_ERROR(\"Unsupported TGA image %s: Bad pixel size for RGB image\\n\");\n\t\tmytype = (header.pixelSize == 24) ? MYTGA_RGB24 : (header.pixelSize == 32) ? MYTGA_RGB32 : MYTGA_RGB15;\n\t} else if (header.imageType == TGA_MAPPED || header.imageType == TGA_MAPPED_RLE) {\n\t\tif (header.pixelSize != 8)\n\t\t\tTGA_ERROR(\"Unsupported TGA image %s: Bad pixel size for color-mapped image.\\n\");\n\t\tif (!(header.colormapSize == 15 || header.colormapSize == 16 || header.colormapSize == 24 || header.colormapSize == 32))\n\t\t\tTGA_ERROR(\"Unsupported TGA image %s: Bad colormap size.\\n\");\n\t\tif (header.colormapType != 1 || header.colormapLength * 4 > sizeof(palette))\n\t\t\tTGA_ERROR(\"Unsupported TGA image %s: Bad colormap type and/or length for color-mapped image.\\n\");\n\n\t\t// read in the palette\n\t\tif (header.colormapSize == 15 || header.colormapSize == 16) {\n\t\t\tfor (i = 0, out = palette; i < header.colormapLength; i++, in += 2, out += 4)\n\t\t\t\tTGA_upsample15(out, in, alphabits == 1);\n\t\t} else if (header.colormapSize == 24) {\n\t\t\tfor (i = 0, out = palette; i < header.colormapLength; i++, in += 3, out += 4)\n\t\t\t\tTGA_upsample24(out, in);\n\t\t} else if (header.colormapSize == 32) {\n\t\t\tfor (i = 0, out = palette; i < header.colormapLength; i++, in += 4, out += 4)\n\t\t\t\tTGA_upsample32(out, in);\n\t\t}\n\t\tmytype = MYTGA_MAPPED;\n\t} else if (header.imageType == TGA_MONO || header.imageType == TGA_MONO_RLE) {\n\t\tif (!(header.pixelSize == 8 || (header.pixelSize == 16 && alphabits == 8)))\n\t\t\tTGA_ERROR(\"Unsupported TGA image %s: Bad pixel size for grayscale image.\\n\");\n\t\tmytype = (header.pixelSize == 8) ? MYTGA_MONO8 : MYTGA_MONO16;\n\t} else {\n\t\tTGA_ERROR(\"Unsupported TGA image %s: Bad image type.\\n\");\n\t}\n\n\tif (header.attributes & 0x10)\n\t\tTGA_ERROR(\"Unsupported TGA image %s: Pixel data spans right to left.\\n\");\n\n\tdata = (byte *) Q_malloc(image_width * image_height * 4);\n\n\t// if bit 5 of attributes isn't set, the image has been stored from bottom to top\n\tif ((header.attributes & 0x20)) {\n\t\tout = data;\n\t\trow_inc = 0;\n\t} else {\n\t\tout = data + (image_height - 1) * image_width * 4;\n\t\trow_inc = -image_width * 4 * 2;\n\t}\n\n\tx = y = 0;\n\trgba[0] = rgba[1] = rgba[2] = rgba[3] = 255;\n\n\twhile (y < image_height) {\n\t\t// decoder is mostly the same whether it's compressed or not\n\t\treadpixelcount = runlen = 0x7FFFFFFF;\n\t\tif (compressed && in < enddata) {\n\t\t\trunlen = *in++;\n\t\t\t// high bit indicates this is an RLE compressed run\n\t\t\tif (runlen & 0x80)\n\t\t\t\treadpixelcount = 1;\n\t\t\trunlen = 1 + (runlen & 0x7F);\n\t\t}\n\n\t\twhile (runlen-- && y < image_height) {\n\t\t\tif (readpixelcount > 0) {\n\t\t\t\treadpixelcount--;\n\t\t\t\trgba[0] = rgba[1] = rgba[2] = rgba[3] = 255;\n\n\t\t\t\tif (in + bpp <= enddata) {\n\t\t\t\t\tswitch(mytype) {\n\t\t\t\t\tcase MYTGA_MAPPED:\n\t\t\t\t\t\tfor (i = 0; i < 4; i++)\n\t\t\t\t\t\t\trgba[i] = palette[in[0] * 4 + i];\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase MYTGA_RGB15:\n\t\t\t\t\t\tTGA_upsample15(rgba, in, alphabits == 1);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase MYTGA_RGB24:\n\t\t\t\t\t\tTGA_upsample24(rgba, in);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase MYTGA_RGB32:\n\t\t\t\t\t\tTGA_upsample32(rgba, in);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase MYTGA_MONO8:\n\t\t\t\t\t\trgba[0] = rgba[1] = rgba[2] = in[0];\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase MYTGA_MONO16:\n\t\t\t\t\t\trgba[0] = rgba[1] = rgba[2] = in[0];\n\t\t\t\t\t\trgba[3] = in[1];\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tin += bpp;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (i = 0; i < 4; i++)\n\t\t\t\t*out++ = rgba[i];\n\t\t\tif (++x == image_width) {\n\t\t\t\t// end of line, advance to next\n\t\t\t\tx = 0;\n\t\t\t\ty++;\n\t\t\t\tout += row_inc;\n\t\t\t}\n\t\t}\n\t}\n\n\tQ_free(fileBuffer);\n\treturn data;\n}\n\nint Image_WriteTGA (char *filename, byte *pixels, size_t width, size_t height)\n{\n\tchar name[MAX_PATH];\n\tbyte buffer[18] = { 0 };\n\tvfsfile_t *outfile;\n\n\tbuffer[2] = 2;          // uncompressed type\n\tbuffer[12] = width & 255;\n\tbuffer[13] = (width >> 8) & 0xFF;\n\tbuffer[14] = height & 255;\n\tbuffer[15] = (height >> 8) & 0xFF;\n\tbuffer[16] = 24;\n\n\tsnprintf (name, sizeof(name), \"%s\", filename);\n\tif (!(outfile = FS_OpenVFS(filename, \"wb\", FS_NONE_OS))) {\n\t\tFS_CreatePath (name);\n\t\tif (!(outfile = FS_OpenVFS(name, \"wb\", FS_NONE_OS))) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tVFS_WRITE(outfile, buffer, sizeof(buffer));\n\tVFS_WRITE(outfile, pixels, (int)(width * height * 3));\n\tVFS_CLOSE(outfile);\n\treturn true;\n}\n\n/*********************************** JPEG ************************************/\n\n#ifdef WITH_JPEG\n#ifndef jpeg_create_compress\n#define jpeg_create_compress(cinfo) \\\n    jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, (size_t) sizeof(struct jpeg_compress_struct))\n#endif\n\ntypedef struct \n{\n  struct jpeg_destination_mgr pub; \n  vfsfile_t *outfile;\n  JOCTET *buffer;\n} my_destination_mgr;\n\ntypedef my_destination_mgr *my_dest_ptr;\n\n#define JPEG_OUTPUT_BUF_SIZE  4096\n\nstatic qbool jpeg_in_error = false;\n\nstatic void JPEG_IO_init_destination(j_compress_ptr cinfo)\n{\n\tmy_dest_ptr dest = (my_dest_ptr) cinfo->dest;\n\tdest->buffer = (JOCTET *) (cinfo->mem->alloc_small)\n\t\t((j_common_ptr) cinfo, JPOOL_IMAGE, JPEG_OUTPUT_BUF_SIZE * sizeof(JOCTET));\n\tdest->pub.next_output_byte = dest->buffer;\n\tdest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE;\n}\n\nstatic boolean JPEG_IO_empty_output_buffer (j_compress_ptr cinfo) \n{\n\tmy_dest_ptr dest = (my_dest_ptr) cinfo->dest;\n\n\tif (VFS_WRITE(dest->outfile, dest->buffer, JPEG_OUTPUT_BUF_SIZE) != JPEG_OUTPUT_BUF_SIZE)\n\t{\n\t\tjpeg_in_error = true;\n\t\treturn (boolean)false;\n\t}\n\tdest->pub.next_output_byte = dest->buffer;\n\tdest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE;\n\treturn (boolean)true;\n}\n\nstatic void JPEG_IO_term_destination (j_compress_ptr cinfo)\n{\n\tmy_dest_ptr dest = (my_dest_ptr) cinfo->dest;\n\tsize_t datacount = JPEG_OUTPUT_BUF_SIZE - dest->pub.free_in_buffer;\n\n\tif (datacount > 0) {\n\t\tif (((size_t) VFS_WRITE(dest->outfile, dest->buffer, (int)datacount)) != datacount)\n\t\t{\n\t\t\tjpeg_in_error = true;\n\t\t\treturn;\n\t\t}\n\t}\n\tVFS_FLUSH(dest->outfile);\n}\n\nstatic void JPEG_IO_set_dest (j_compress_ptr cinfo, vfsfile_t *outfile)\n{\n\tmy_dest_ptr dest;\n\n\tif (!cinfo->dest) {\n\t\tcinfo->dest = (struct jpeg_destination_mgr *) (cinfo->mem->alloc_small)(\n\t\t\t\t\t\t\t(j_common_ptr) cinfo,JPOOL_PERMANENT, sizeof(my_destination_mgr));\n\t}\n\n\tdest = (my_dest_ptr) cinfo->dest;\n\tdest->pub.init_destination = JPEG_IO_init_destination;\n\tdest->pub.empty_output_buffer = JPEG_IO_empty_output_buffer;\n\tdest->pub.term_destination = JPEG_IO_term_destination;\n\tdest->outfile = outfile;\n}\n\ntypedef struct my_error_mgr \n{\n\tstruct jpeg_error_mgr pub;\n\tjmp_buf setjmp_buffer;\n} jpeg_error_mgr_wrapper;\n\nvoid jpeg_error_exit (j_common_ptr cinfo)\n{\t\n\tlongjmp(((jpeg_error_mgr_wrapper *) cinfo->err)->setjmp_buffer, 1);\n}\n\nint Image_WriteJPEG(char *filename, int quality, byte *pixels, int width, int height) \n{\n\tchar name[MAX_PATH];\n\tvfsfile_t *outfile;\n\n\tjpeg_error_mgr_wrapper jerr;\n\tstruct jpeg_compress_struct cinfo;\n\tJSAMPROW row_pointer[1];\n\n\tsnprintf (name, sizeof(name), \"%s\", filename);  \n\tif (!(outfile = FS_OpenVFS(name, \"wb\", FS_NONE_OS))) {\n\t\tFS_CreatePath (name);\n\t\tif (!(outfile = FS_OpenVFS(name, \"wb\", FS_NONE_OS)))\n\t\t\treturn false;\n\t}\n\tcinfo.err = jpeg_std_error(&jerr.pub);\n\tjerr.pub.error_exit = jpeg_error_exit;\n\tif (setjmp(jerr.setjmp_buffer)) {\n\t\tVFS_CLOSE(outfile);\n\t\treturn false;\n\t}\n\tjpeg_create_compress(&cinfo);\n\n\tjpeg_in_error = false;\n\tJPEG_IO_set_dest(&cinfo, outfile);\n\n\tcinfo.image_width = abs(width); \t\n\tcinfo.image_height = height;\n\tcinfo.input_components = 3;\n\tcinfo.in_color_space = JCS_RGB;\n\tjpeg_set_defaults(&cinfo);\n\tjpeg_set_quality (&cinfo, bound(0, quality, 100), (boolean)true);\n\tjpeg_start_compress(&cinfo, (boolean)true);\n\n\twhile (cinfo.next_scanline < height) {\n\t    *row_pointer = &pixels[(int)cinfo.next_scanline * width * 3];\n\t    jpeg_write_scanlines(&cinfo, row_pointer, 1);\n\t\tif (jpeg_in_error)\n\t\t\tbreak;\n\t}\n\n\tjpeg_finish_compress(&cinfo);\n\tVFS_CLOSE(outfile);\n\tjpeg_destroy_compress(&cinfo);\n\treturn true;\n}\n\n\n//\n// jpeg loading\n// stolen from Spikes FTE\n//\n\ntypedef struct my_error_mgr * my_error_ptr;\n\n// Here's the routine that will replace the standard error_exit method:\nMETHODDEF(void)\nmy_error_exit (j_common_ptr cinfo)\n{\n\t// cinfo->err really points to a my_error_mgr struct, so coerce pointer\n\tmy_error_ptr myerr = (my_error_ptr) cinfo->err;\n\n\t// Always display the message.\n\t// We could postpone this until after returning, if we chose.\n\t(*cinfo->err->output_message) (cinfo);\n\n\t// Return control to the setjmp point \n\tlongjmp(myerr->setjmp_buffer, 1);\n}\n\n//\n// Sample routine for JPEG decompression.  We assume that the source file name\n// is passed in.  We want to return 1 on success, 0 on error.\n//\n\n// Expanded data source object for stdio input \n\ntypedef struct \n{\n\tstruct jpeg_source_mgr pub;\t// Public fields.\n\n\tbyte * infile;\t\t\t\t// Source stream.\n\tint currentpos;\n\tint maxlen;\n\tJOCTET * buffer;\t\t\t// Start of buffer.\n\tboolean start_of_file;\t\t// Have we gotten any data yet?\n} my_source_mgr;\n\ntypedef my_source_mgr * my_src_ptr;\n\n#define INPUT_BUF_SIZE  4096\t// Choose an efficiently fread'able size.\n\n/*METHODDEF(void)\ninit_source (j_decompress_ptr cinfo)\n{\n  my_src_ptr src = (my_src_ptr) cinfo->src;\n\n  src->start_of_file = TRUE;\n}\n\nMETHODDEF(boolean)\nfill_input_buffer (j_decompress_ptr cinfo)\n{\n\tmy_source_mgr *src = (my_source_mgr*) cinfo->src;\n\tsize_t nbytes;\n  \n\tnbytes = src->maxlen - src->currentpos;\n\tif (nbytes > INPUT_BUF_SIZE)\n\t\tnbytes = INPUT_BUF_SIZE;\n\tmemcpy(src->buffer, &src->infile[src->currentpos], nbytes);\n\tsrc->currentpos+=nbytes;\n\n\tif (nbytes <= 0) \n\t{\n\t\tif (src->start_of_file)\t// Treat empty input file as fatal error.\n\t\t\tERREXIT(cinfo, JERR_INPUT_EMPTY);\n\t\t\n\t\tWARNMS(cinfo, JWRN_JPEG_EOF);\n\t\t\n\t\t// Insert a fake EOI marker.\n\t\tsrc->buffer[0] = (JOCTET) 0xFF;\n\t\tsrc->buffer[1] = (JOCTET) JPEG_EOI;\n\t\tnbytes = 2;\n\t}\n\n\tsrc->pub.next_input_byte = src->buffer;\n\tsrc->pub.bytes_in_buffer = nbytes;\n\tsrc->start_of_file = FALSE;\n\n\treturn TRUE;\n}\n\n\nMETHODDEF(void)\nskip_input_data (j_decompress_ptr cinfo, long num_bytes)\n{\n\tmy_source_mgr *src = (my_source_mgr*) cinfo->src;\n\n\tif (num_bytes > 0) \n\t{\n\t\twhile (num_bytes > (long) src->pub.bytes_in_buffer) \n\t\t{\n\t\t\tnum_bytes -= (long) src->pub.bytes_in_buffer;\n\t\t\t(void) fill_input_buffer(cinfo);\n\t\t}\n\t\tsrc->pub.next_input_byte += (size_t) num_bytes;\n\t\tsrc->pub.bytes_in_buffer -= (size_t) num_bytes;\n\t}\n}\n\nMETHODDEF(void)\nterm_source (j_decompress_ptr cinfo)\n{\n}\n\nGLOBAL(void)\njpeg_mem_src (j_decompress_ptr cinfo, byte * infile, int maxlen)\n{\n\tmy_source_mgr *src;\n\n\tif (cinfo->src == NULL) \n\t{\t\n\t\t// First time for this JPEG object?\n\t\tcinfo->src = (struct jpeg_source_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(my_source_mgr));\n\t\t\n\t\tsrc = (my_source_mgr*) cinfo->src;\n\t\t\n\t\tsrc->buffer = (JOCTET *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, INPUT_BUF_SIZE * sizeof(JOCTET));\n\t}\n\n\tsrc = (my_source_mgr*) cinfo->src;\n\tsrc->pub.init_source = init_source;\n\tsrc->pub.fill_input_buffer = fill_input_buffer;\n\tsrc->pub.skip_input_data = skip_input_data;\n\tsrc->pub.resync_to_restart = jpeg_resync_to_restart; // Use default method.\n\tsrc->pub.term_source = term_source;\n\tsrc->infile = infile;\n\tsrc->pub.bytes_in_buffer = 0; // Forces fill_input_buffer on first read.\n\tsrc->pub.next_input_byte = NULL; // until buffer loaded.\n\n\tsrc->currentpos = 0;\n\tsrc->maxlen = maxlen;\n}\n*/\n\nbyte *Image_LoadJPEG(vfsfile_t *fin, const char *filename, int matchwidth, int matchheight, int *real_width, int *real_height)\n{\n\tbyte *mem = NULL, *in, *out;\n\tint i, image_width, image_height;\n\n\tbyte *infile = NULL;\n\tint length;\n\tint filesize;\n\n\t// This struct contains the JPEG decompression parameters and pointers to\n\t// working space (which is allocated as needed by the JPEG library).\n\tstruct jpeg_decompress_struct cinfo;\n\t\n\t// We use our private extension JPEG error handler.\n\t// Note that this struct must live as long as the main JPEG parameter\n\t// struct, to avoid dangling-pointer problems.\n\tstruct my_error_mgr jerr;\n\t\n\t// More stuff \n\tJSAMPARRAY buffer;\t\t// Output row buffer.\n\tint size_stride;\t\t// physical row width in output buffer.\n\n\tif (!fin && !(fin = FS_OpenVFS(filename, \"rb\", FS_ANY)))\n\t\treturn NULL;\n\tfilesize = VFS_GETLEN(fin);\n\n\tinfile = (byte *) Q_malloc(length = filesize);\n\tif (VFS_READ(fin, infile, filesize, NULL) != filesize) \n\t{\n\t\tCom_DPrintf (\"Image_LoadJPEG: fread() failed on %s\\n\", COM_SkipPath(filename));\n\t\tVFS_CLOSE(fin);\n\t\tQ_free(infile);\n\t\treturn NULL;\n\t}\n\n\tVFS_CLOSE(fin);\n\n\t// Step 1: allocate and initialize JPEG decompression object.\n\n\t// We set up the normal JPEG error routines, then override error_exit. \n\tcinfo.err = jpeg_std_error(&jerr.pub);\n\tjerr.pub.error_exit = my_error_exit;\n\n\t// Establish the setjmp return context for my_error_exit to use. */\n\tif (setjmp(jerr.setjmp_buffer)) \n\t{\n\t\t// If we get here, the JPEG code has signaled an error.\n\nbadjpeg:\n\n\t\tjpeg_destroy_decompress(&cinfo);    \n\n\t\tQ_free(infile);\n\t\tQ_free(mem);\n\t\tCom_DPrintf (\"Image_LoadJPEG: badjpeg %s, len %d\\n\", COM_SkipPath(filename), length);\n\t\treturn 0;\n\t}\n\n\tjpeg_create_decompress(&cinfo);\n\n\tjpeg_mem_src(&cinfo, infile, length);\n\n\t(void) jpeg_read_header(&cinfo, TRUE);  \n\n\t(void) jpeg_start_decompress(&cinfo);\n\n\timage_width  = cinfo.output_width;\n\timage_height = cinfo.output_height;\n\n\t// Return the width and height.\n\tif (real_width)\n\t\t(*real_width) = image_width;\n\n\tif (real_height)\n\t\t(*real_height) = image_height;\n\n\tif (image_width > IMAGE_MAX_DIMENSIONS || image_height > IMAGE_MAX_DIMENSIONS || image_width <= 0 || image_height <= 0)\n\t{\n\t\tCom_Printf(\"Bad actual dimensions %dx%d in jpeg %s\\n\", image_width, image_height, COM_SkipPath(filename));\n\t\tgoto badjpeg;\n\t}\n\n\tif ((matchwidth && image_width != matchwidth) || (matchheight && image_height != matchheight))\n\t{\n\t\tCom_Printf(\"Bad match dimensions %dx%d vs %dx%d in jpeg %s\\n\", image_width, image_height, matchwidth, matchheight, COM_SkipPath(filename));\n\t\tgoto badjpeg; \n\t}\n\n\tif (cinfo.output_components!=3)\n\t{\n\t\tCom_Printf(\"Bad number of componants in jpeg %s\\n\", COM_SkipPath(filename));\n\t\tgoto badjpeg;\n\t}\n\n\tsize_stride = cinfo.output_width * cinfo.output_components;\n\t// Make a one-row-high sample array that will go away when done with image.\n\tbuffer = (*cinfo.mem->alloc_sarray)\n\t\t((j_common_ptr) &cinfo, JPOOL_IMAGE, size_stride, 1);\n\n\tout = mem = Q_malloc(cinfo.output_height*cinfo.output_width*4);\n\tmemset(out, 0, cinfo.output_height*cinfo.output_width*4);\n\n\twhile (cinfo.output_scanline < cinfo.output_height) \n\t{\n\t\t(void) jpeg_read_scanlines(&cinfo, buffer, 1);    \n\n\t\tin = buffer[0];\n\t\tfor (i = 0; i < cinfo.output_width; i++)\n\t\t{\n\t\t\t// RGB to RGBA\n\t\t\t*out++ = *in++;\n\t\t\t*out++ = *in++;\n\t\t\t*out++ = *in++;\n\t\t\t*out++ = 255;\t\n\t\t}\t\n\t}\n\n\t(void) jpeg_finish_decompress(&cinfo);\n\n\tjpeg_destroy_decompress(&cinfo);\n\n\tQ_free(infile);\n\treturn mem;\n}\n#endif // WITH_JPEG\n\n/************************************ PCX ************************************/\n\ntypedef struct pcx_s \n{\n    char\t\t\tmanufacturer;\n    char\t\t\tversion;\n    char\t\t\tencoding;\n    char\t\t\tbits_per_pixel;\n    unsigned short\txmin,ymin,xmax,ymax;\n    unsigned short\thres,vres;\n    byte\t\t\tpalette[48];\n    char\t\t\treserved;\n    char\t\t\tcolor_planes;\n    unsigned short\tbytes_per_line;\n    unsigned short\tpalette_type;\n    char\t\t\tfiller[58];\n    byte\t\t\tdata;\t\t\n} pcx_t;\n\nbyte *Image_LoadPCX (vfsfile_t *fin, const char *filename, int matchwidth, int matchheight, int *real_width, int *real_height) \n{\n\tpcx_t *pcx;\n\tbyte *pcxbuf, *data, *out, *pix;\n\tint x, y, dataByte, runLength, width, height;\n\tint filesize;\n\n\tif (!fin && !(fin = FS_OpenVFS(filename, \"rb\", FS_ANY)))\n\t\treturn NULL;\n\tfilesize = VFS_GETLEN(fin);\n\n\tpcxbuf = (byte *) Q_malloc(filesize);\n\tif (VFS_READ(fin, pcxbuf, filesize, NULL) != filesize) \n\t{\n\t\tCom_DPrintf (\"Image_LoadPCX: fread() failed on %s\\n\", COM_SkipPath(filename));\n\t\tVFS_CLOSE(fin);\n\t\tQ_free(pcxbuf);\n\t\treturn NULL;\n\t}\n\tVFS_CLOSE(fin);\n\n\tpcx = (pcx_t *) pcxbuf;\n\tpcx->xmax = LittleShort (pcx->xmax);\n\tpcx->xmin = LittleShort (pcx->xmin);\n\tpcx->ymax = LittleShort (pcx->ymax);\n\tpcx->ymin = LittleShort (pcx->ymin);\n\tpcx->hres = LittleShort (pcx->hres);\n\tpcx->vres = LittleShort (pcx->vres);\n\tpcx->bytes_per_line = LittleShort (pcx->bytes_per_line);\n\tpcx->palette_type = LittleShort (pcx->palette_type);\n\n\tpix = &pcx->data;\n\n\tif (pcx->manufacturer != 0x0a || pcx->version != 5 || pcx->encoding != 1 || pcx->bits_per_pixel != 8) \n\t{\n\t\tCom_DPrintf (\"Invalid PCX image %s\\n\", COM_SkipPath(filename));\n\t\tQ_free(pcxbuf);\n\t\treturn NULL;\n\t}\n\n\twidth = pcx->xmax + 1;\n\theight = pcx->ymax + 1;\n\n\t// Return the width and height.\n\tif (real_width)\n\t\t(*real_width) = width;\n\n\tif (real_height)\n\t\t(*real_height) = height;\n\n\tif (width > IMAGE_MAX_DIMENSIONS || height > IMAGE_MAX_DIMENSIONS)\n\t{\n\t\tCom_DPrintf (\"PCX image %s exceeds maximum supported dimensions\\n\", COM_SkipPath(filename));\n\t\tQ_free(pcxbuf);\n\t\treturn NULL;\n\t}\n\n\tif ((matchwidth && width != matchwidth) || (matchheight && height != matchheight))\n\t{\n\t\tQ_free(pcxbuf);\n\t\treturn NULL;\n\t}\n \n\tdata = out = (byte *) Q_malloc (width * height);\n\n\tfor (y = 0; y < height; y++, out += width)\n\t{\n\t\tfor (x = 0; x < width; ) \n\t\t{\n\t\t\tif (pix - (byte *) pcx > filesize) \n\t\t\t{\n\t\t\t\tCom_DPrintf (\"Malformed PCX image %s\\n\", COM_SkipPath(filename));\n\t\t\t\tQ_free(pcxbuf);\n\t\t\t\tQ_free(data);\n\t\t\t\treturn NULL;\n\t\t\t}\n\n\t\t\tdataByte = *pix++;\n\n\t\t\tif ((dataByte & 0xC0) == 0xC0)\n\t\t\t{\n\t\t\t\trunLength = dataByte & 0x3F;\n\t\t\t\tif (pix - (byte *) pcx > filesize)\n\t\t\t\t{\n\t\t\t\t\tCom_DPrintf (\"Malformed PCX image %s\\n\", COM_SkipPath(filename));\n\t\t\t\t\tQ_free(pcxbuf);\n\t\t\t\t\tQ_free(data);\n\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\t\t\t\tdataByte = *pix++;\n\t\t\t}\n\t\t\telse \n\t\t\t{\n\t\t\t\trunLength = 1;\n\t\t\t}\n\n\t\t\tif (runLength + x > width + 1) \n\t\t\t{\n\t\t\t\tCom_DPrintf (\"Malformed PCX image %s\\n\", COM_SkipPath(filename));\n\t\t\t\tQ_free(pcxbuf);\n\t\t\t\tQ_free(data);\n\t\t\t\treturn NULL;\n\t\t\t}\n\n\t\t\twhile (runLength-- > 0)\n\t\t\t\tout[x++] = dataByte;\n\t\t}\n\t}\n\n\tif (pix - (byte *) pcx > filesize) \n\t{\n\t\tCom_DPrintf (\"Malformed PCX image %s\\n\", COM_SkipPath(filename));\n\t\tQ_free(pcxbuf);\n\t\tQ_free(data);\n\t\treturn NULL;\n\t}\n\n\tQ_free(pcxbuf);\n\treturn data;\n}\n\n// This does't load 32bit pcx, just convert 8bit color buffer to 32bit buffer, so we can make from this texture.\nbyte *Image_LoadPCX_As32Bit (vfsfile_t *fin, const char *filename, int matchwidth, int matchheight, int *real_width, int *real_height)\n{\n\tint image_width, image_height;\n\tunsigned *out;\n\tint size, i;\n\tbyte *pix = Image_LoadPCX (fin, filename, matchwidth, matchheight, &image_width, &image_height);\n\n\tif (!pix)\n\t\treturn NULL;\n\n\tif (real_width)\n\t\t(*real_width) = image_width;\n\n\tif (real_height)\n\t\t(*real_height) = image_height;\n\n\tsize = image_width * image_height;\n\tout = Q_malloc(size * sizeof(unsigned));\n\n\tfor (i = 0; i < size; i++)\n\t\tout[i] = d_8to24table[pix[i]];\n\n\tQ_free(pix);\n\n\treturn (byte*) out;\n}\n\nint Image_WritePCX (char *filename, byte *data, int width, int height, byte *palette)\n{\n\tint rowbytes = width;\n\t\n\tint i, j, length;\n\tbyte *pack;\n\tpcx_t *pcx;\n\n\tif (!(pcx = (pcx_t *) Q_malloc (width * height * 2 + 1000)))\n\t\treturn false;\n\n\tpcx->manufacturer = 0x0a;\n\tpcx->version = 5;\t\t\n \tpcx->encoding = 1;\t\t\n\tpcx->bits_per_pixel = 8;\n\tpcx->xmin = 0;\n\tpcx->ymin = 0;\n\tpcx->xmax = LittleShort((short) (width - 1));\n\tpcx->ymax = LittleShort((short) (height - 1));\n\tpcx->hres = LittleShort((short) width);\n\tpcx->vres = LittleShort((short) height);\n\tmemset (pcx->palette, 0, sizeof(pcx->palette));\n\tpcx->color_planes = 1;\t\t\n\tpcx->bytes_per_line = LittleShort((short) width);\n\tpcx->palette_type = LittleShort(1);\t\t\n\tmemset (pcx->filler, 0, sizeof(pcx->filler));\n\n\tpack = &pcx->data;\n\n\tfor (i = 0; i < height; i++) \n\t{\n\t\tfor (j = 0; j < width; j++) \n\t\t{\n\t\t\tif ((*data & 0xc0) != 0xc0)\n\t\t\t\t*pack++ = *data++;\n\t\t\telse {\n\t\t\t\t*pack++ = 0xc1;\n\t\t\t\t*pack++ = *data++;\n\t\t\t}\n\t\t}\n\t\tdata += rowbytes - width;\n\t}\n\n\t*pack++ = 0x0c;\t\n\tfor (i = 0; i < 768; i++)\n\t\t*pack++ = *palette++;\n\n\tlength = pack - (byte *) pcx;\n\tif (!(FS_WriteFile_2 (filename, pcx, length)))\n\t{\n\t\tQ_free(pcx);\n\t\treturn false;\n\t}\n\n\tQ_free(pcx);\n\treturn true;\n}\n\n/*********************************** INIT ************************************/\n\nvoid Image_Init(void) \n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREENSHOTS);\n\n\t#ifdef WITH_PNG\n\tCvar_Register (&image_png_compression_level);\n\t#endif // WITH_PNG\n\n\t#ifdef WITH_JPEG\n\tCvar_Register (&image_jpeg_quality_level);\n\t#endif // WITH_JPEG\n\n\tCvar_ResetCurrentGroup();\n}\n\n\n"
  },
  {
    "path": "src/image.h",
    "content": "/*\n\nCopyright (C) 1996-2003 A Nourai, Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef _IMAGE_H\n\n#define _IMAGE_H\n\n#if defined(WITH_PNG) && !defined(WITH_ZLIB)\n#error WITH_PNG requires WITH_ZLIB\n#endif\n\nvoid Image_Init(void);\n\nvoid Image_Resample (void *indata, int inwidth, int inheight,\n\t\t\t\t\t void *outdata, int outwidth, int outheight, int bpp, int quality);\nvoid Image_MipReduce (const byte *in, byte *out, int *width, int *height, int bpp);\n\n#if defined(WITH_PNG)\n#include <png.h>\n\ntypedef struct\n{\n\tbyte *data;\n\tpng_textp textchunks;\n\tsize_t text_count;\n} png_data;\n\npng_textp Image_LoadPNG_Comments (char *filename, int *text_count);\npng_data *Image_LoadPNG_All (vfsfile_t *vin, const char *filename, int matchwidth, int matchheight, int loadflag, int *real_width, int *real_height);\n#endif\n\nbyte *Image_LoadPNG (vfsfile_t *v, const char *path, int matchwidth, int matchheight, int *real_width, int *real_height);\nbyte *Image_LoadTGA (vfsfile_t *v, const char *path, int matchwidth, int matchheight, int *real_width, int *real_height);\nbyte *Image_LoadPCX (vfsfile_t *v, const char *path, int matchwidth, int matchheight, int *real_width, int *real_height);\nbyte *Image_LoadJPEG(vfsfile_t *v, const char *path, int matchwidth, int matchheight, int *real_width, int *real_height);\n// this does't load 32bit pcx, just convert 8bit color buffer to 32bit buffer, so we can make from this texture\nbyte *Image_LoadPCX_As32Bit (vfsfile_t *v, const char *path, int matchwidth, int matchheight, int *real_width, int *real_height);\n\nint Image_WritePNG(char *filename, int compression, byte *pixels, size_t width, size_t height);\nint Image_WriteTGA(char *filename, byte *pixels, size_t width, size_t height);\nint Image_WriteJPEG(char *filename, int quality, byte *pixels, int width, int height);\nint Image_WritePCX(char *filename, byte *data, int width, int height, byte *palette);\n\nqbool Image_OpenAPNG(char* filename, int compression, int width, int height, int frames);\nqbool Image_WriteAPNGFrame(byte* pixels, size_t width, size_t height, int fps);\nqbool Image_CloseAPNG(void);\n\nextern cvar_t image_jpeg_quality_level, image_png_compression_level;\n\n#endif\t//_IMAGE_H\n\n"
  },
  {
    "path": "src/in_osx.h",
    "content": "/*\n Copyright (C) 2011 Florian Zwoch\n Copyright (C) 2011 Mark Olsen\n \n This program is free software; you can redistribute it and/or\n modify it under the terms of the GNU General Public License\n as published by the Free Software Foundation; either version 2\n of the License, or (at your option) any later version.\n \n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n \n See the GNU General Public License for more details.\n \n You should have received a copy of the GNU General Public License\n along with this program; if not, write to the Free Software\n Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n */\n\nint OSX_Mouse_Init(void);\nvoid OSX_Mouse_Shutdown(void);\nvoid OSX_Mouse_GetMouseMovement(int *mouse_x, int *mouse_y);\n"
  },
  {
    "path": "src/in_osx.m",
    "content": "/*\nCopyright (C) 2011 Florian Zwoch\nCopyright (C) 2011 Mark Olsen\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#import <SDL.h>\n#import <GameController/GameController.h>\n#import \"common.h\"\n\nstatic id mouseAddedObserver = nil;\nstatic id mouseRemovedObserver = nil;\nstatic SDL_mutex *mouse_mutex = NULL;\nstatic float mouse_x = 0;\nstatic float mouse_y = 0;\n\nstatic void OSX_RegisterMouseMovedHandler(GCMouse *mouse)\n{\n\tmouse.mouseInput.mouseMovedHandler = ^(GCMouseInput *input, float deltaX, float deltaY) {\n\t\tSDL_LockMutex(mouse_mutex);\n\t\tmouse_x += deltaX;\n\t\tmouse_y += deltaY;\n\t\tSDL_UnlockMutex(mouse_mutex);\n\t};\n}\n\nstatic void OSX_CreateMouseObserver(void)\n{\n\tmouseAddedObserver = [[NSNotificationCenter defaultCenter]\n\t\t\t\t\t addObserverForName:GCMouseDidConnectNotification\n\t\t\t\t\t\t\t\t object:nil\n\t\t\t\t\t\t\t\t  queue:nil\n\t\t\t\t\t\t\t usingBlock:^(NSNotification *note) {\n\t\t\t\t\t\t\t\t OSX_RegisterMouseMovedHandler(note.object);\n\t\t\t\t\t\t\t }\n\t];\n\tmouseRemovedObserver = [[NSNotificationCenter defaultCenter]\n\t\t\taddObserverForName:GCMouseDidDisconnectNotification\n\t\t\t\t\t\tobject:nil\n\t\t\t\t\t\t queue:nil\n\t\t\t\t\tusingBlock:^(NSNotification *note) {\n\t\t\t\t\t\tGCMouse *mouse = note.object;\n\t\t\t\t\t\tmouse.mouseInput.mouseMovedHandler = nil;\n\t\t\t\t\t}\n\t];\n}\n\nint OSX_Mouse_Init(void)\n{\n\tmouse_x = 0;\n\tmouse_y = 0;\n\n\tif (mouse_mutex != NULL) {\n\t\treturn 0;\n\t}\n\n\tmouse_mutex = SDL_CreateMutex();\n\tOSX_CreateMouseObserver();\n\n\treturn 0;\n}\n\nvoid OSX_Mouse_Shutdown(void)\n{\n\tif (mouse_mutex == NULL) {\n\t\treturn;\n\t}\n\n\t[[NSNotificationCenter defaultCenter] removeObserver:mouseAddedObserver];\n\tmouseAddedObserver = nil;\n\t[[NSNotificationCenter defaultCenter] removeObserver:mouseRemovedObserver];\n\tmouseRemovedObserver = nil;\n\n\tSDL_DestroyMutex(mouse_mutex);\n\tmouse_mutex = NULL;\n}\n\nvoid OSX_Mouse_GetMouseMovement(int *m_x, int *m_y)\n{\n\tSDL_LockMutex(mouse_mutex);\n\n\t*m_x = (int)mouse_x;\n\t*m_y = (int)mouse_y;\n\n\tmouse_x = 0;\n\tmouse_y = 0;\n\n\tSDL_UnlockMutex(mouse_mutex);\n}\n"
  },
  {
    "path": "src/in_sdl2.c",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include <stdint.h>\n\n#include \"SDL.h\"\n#include \"SDL_joystick.h\"\n#include \"common.h\"\n#include \"cvar.h\"\n#include \"quakedef.h\"\n#include \"input.h\"\n#include \"keys.h\"\n#include \"movie.h\"\n\n#ifndef _WIN32\n#include <sys/time.h>\n#endif\n\n#ifdef __APPLE__\n#include \"in_osx.h\"\n#endif\n\nstatic void in_joystick_callback(cvar_t *var, char *value, qbool *cancel);\n\ncvar_t\tm_filter        = {\"m_filter\",       \"0\", CVAR_SILENT};\ncvar_t\tcl_keypad       = {\"cl_keypad\",      \"1\", CVAR_SILENT};\n\n/*  Joystick cvars.  */\ncvar_t\tin_joystick\t\t= {\"joystick\",\t\t\t\"0\",\tCVAR_SILENT, in_joystick_callback };\ncvar_t\tjoy_index\t\t= {\"joyindex\",\t\t\t\"0\",\tCVAR_SILENT };\ncvar_t\tjoy_name\t\t= {\"joyname\",\t\t\t\"joystick\", CVAR_SILENT };\ncvar_t\tjoy_advanced\t\t= {\"joyadvanced\",\t\t\"0\",\tCVAR_SILENT };\ncvar_t\tjoy_advaxisx\t\t= {\"joyadvaxisx\",\t\t\"0\",\tCVAR_SILENT };\ncvar_t\tjoy_advaxisy\t\t= {\"joyadvaxisy\",\t\t\"0\",\tCVAR_SILENT };\ncvar_t\tjoy_advaxisz\t\t= {\"joyadvaxisz\",\t\t\"0\",\tCVAR_SILENT };\ncvar_t\tjoy_advaxisr\t\t= {\"joyadvaxisr\",\t\t\"0\",\tCVAR_SILENT };\ncvar_t\tjoy_advaxisu\t\t= {\"joyadvaxisu\",\t\t\"0\",\tCVAR_SILENT };\ncvar_t\tjoy_advaxisv\t\t= {\"joyadvaxisv\",\t\t\"0\",\tCVAR_SILENT };\ncvar_t\tjoy_forwardthreshold\t= {\"joyforwardthreshold\",\t\"0.15\",\tCVAR_SILENT };\ncvar_t\tjoy_sidethreshold\t= {\"joysidethreshold\",\t\t\"0.15\",\tCVAR_SILENT };\ncvar_t\tjoy_flythreshold\t= {\"joyflythreshold\",\t\t\"0.15\",\tCVAR_SILENT };\ncvar_t\tjoy_pitchthreshold\t= {\"joypitchthreshold\",\t\t\"0.15\",\tCVAR_SILENT };\ncvar_t\tjoy_yawthreshold\t= {\"joyyawthreshold\",\t\t\"0.15\",\tCVAR_SILENT };\ncvar_t\tjoy_flysensitivity\t= {\"joyflysensitivity\",\t\t\"-1.0\",\tCVAR_SILENT };\ncvar_t\tjoy_forwardsensitivity\t= {\"joyforwardsensitivity\",\t\"-1.0\",\tCVAR_SILENT };\ncvar_t\tjoy_sidesensitivity\t= {\"joysidesensitivity\",\t\"-1.0\",\tCVAR_SILENT };\ncvar_t\tjoy_pitchsensitivity\t= {\"joypitchsensitivity\",\t\"1.0\",\tCVAR_SILENT };\ncvar_t\tjoy_yawsensitivity\t= {\"joyyawsensitivity\",\t\t\"-1.0\",\tCVAR_SILENT };\n\n\nextern cvar_t\tmovie_steadycam, movie_fps;\nextern int\tmx, my;\nextern qbool\tmouseinitialized;\n\nextern void IN_StartupMouse(void);\nextern void IN_DeactivateMouse(void);\nextern void IN_Restart_f(void);\n\n\n/***************************************************************************\n * Mouse-y things.\n */\nvoid IN_MouseMove (usercmd_t *cmd)\n{\n\tstatic int old_mouse_x = 0, old_mouse_y = 0;\n\n\tif (!mouseinitialized)\n\t\treturn;\n\n\t//\n\t// Do not move the player if we're in HUD editor or menu mode.\n\t// And don't apply ingame sensitivity, since that will make movements jerky.\n\t//\n\tif (! IN_MouseTrackingRequired())\n\t{\n\t\t// Normal game mode.\n\t\tfloat mouse_x, mouse_y;\n\n\t\tif (m_filter.value) {\n\t\t\tfloat filterfrac = bound (0.0f, m_filter.value, 1.0f) / 2.0f;\n\t\t\tmouse_x = (mx * (1.0f - filterfrac) + old_mouse_x * filterfrac);\n\t\t\tmouse_y = (my * (1.0f - filterfrac) + old_mouse_y * filterfrac);\n\t\t} else {\n\t\t\tmouse_x = mx;\n\t\t\tmouse_y = my;\n\t\t}\n\n\t\told_mouse_x = mx;\n\t\told_mouse_y = my;\n\n\t\tif (m_accel.value > 0.0f) {\n\t\t\tfloat accelsens = sensitivity.value;\n\t\t\tfloat mousespeed = (sqrt (mx * mx + my * my)) / (1000.0f * (float) cls.trueframetime);\n\n\t\t\tmousespeed -= m_accel_offset.value;\n\t\t\tif (mousespeed > 0) {\n\t\t\t\tmousespeed *= m_accel.value;\n\t\t\t\tif (m_accel_power.value > 1) {\n\t\t\t\t\taccelsens += exp((m_accel_power.value - 1) * log(mousespeed));\n\t\t\t\t} else {\n\t\t\t\t\taccelsens = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (m_accel_senscap.value > 0 && accelsens > m_accel_senscap.value) {\n\t\t\t\taccelsens = m_accel_senscap.value;\n\t\t\t}\n\n\t\t\tmouse_x *= accelsens;\n\t\t\tmouse_y *= accelsens;\n\t\t} else {\n\t\t\tmouse_x *= sensitivity.value;\n\t\t\tmouse_y *= sensitivity.value;\n\t\t}\n\n\t\t// add mouse X/Y movement to cmd\n\t\tif ((in_strafe.state & 1) || (lookstrafe.value && mlook_active))\n\t\t\tcmd->sidemove += m_side.value * mouse_x;\n\t\telse if (!cl.paused || cls.demoplayback || cl.spectator)\n\t\t\tcl.viewangles[YAW] -= m_yaw.value * mouse_x;\n\n\t\tif (mlook_active)\n\t\t\tV_StopPitchDrift ();\n\n\t\tif (mlook_active && !(in_strafe.state & 1))\n\t\t{\n\t\t\tif (!cl.paused || cls.demoplayback || cl.spectator) {\n\t\t\t\tcl.viewangles[PITCH] += m_pitch.value * mouse_y;\n\t\t\t}\n\t\t\tif (cl.viewangles[PITCH] > cl.maxpitch)\n\t\t\t\tcl.viewangles[PITCH] = cl.maxpitch;\n\t\t\tif (cl.viewangles[PITCH] < cl.minpitch)\n\t\t\t\tcl.viewangles[PITCH] = cl.minpitch;\n\t\t} else {\n\t\t\tcmd->forwardmove -= m_forward.value * mouse_y;\n\t\t}\n\t}\n\telse {\n\t\told_mouse_x = mx * cursor_sensitivity.value;\n\t\told_mouse_y = my * cursor_sensitivity.value;\n\t}\n\n\tmx = my = 0; // clear for next update\n}\n\n\n/***************************************************************************\n * Stick-y things.\n */\nenum joy_axes {\n\tJOY_AXIS_X = 0,\n\tJOY_AXIS_Y,\n\tJOY_AXIS_Z,\n\tJOY_AXIS_R,\n\tJOY_AXIS_U,\n\tJOY_AXIS_V,\n\tJOY_MAX_AXES,\n\n\tJOY_ABSOLUTE_AXIS = 0x0000,\t\t// control like a joystick\n\tJOY_RELATIVE_AXIS = 0x0100,\t\t// control like a mouse, spinner, trackball\n};\n\nenum _ControlList {\n\tAXIS__NONE = 0,\n\tAXIS_FORWARD,\n\tAXIS_LOOK,\n\tAXIS_SIDE,\n\tAXIS_TURN,\n\tAXIS_FLY,\n};\n\nstatic SDL_Joystick\t*joy_dev;\nstatic uint32_t\t\tjoy_prevbuttons;\nstatic uint16_t\t\taxis_map[JOY_MAX_AXES];\nstatic uint16_t\t\tcontrol_map[JOY_MAX_AXES];\nstatic uint16_t\t\tjoy_numbuttons,\n\t\t\tjoy_numaxes;\nstatic int16_t\t\tjoy_devidx = -1;\nstatic uint8_t\t\tjoy_prevpov;\nstatic qbool\t\tjoy_avail, joy_haspov, joy_advancedinit;\n\nstatic void in_joystick_callback(cvar_t *var, char *value, qbool *cancel)\n{\n  if ((var == &in_joystick) && (atoi(value) != in_joystick.value)) {\n    Cvar_SetValue(&in_joystick, atoi(value));\n    IN_Restart_f();\n  }\n}\n\n/*\n * The user needs to invoke this command after changing the 'joyadv*' cvars.\n */\nstatic void\nJoy_AdvancedUpdate_f (void)\n{\n\t// Called once by IN_ReadJoystick and by user whenever an update is needed\n\t// cvars are now available.\n\tint\t\ti;\n\tuint16_t\tval;\n\n\t// initialize all the maps\n\tfor (i = 0;  i < JOY_MAX_AXES;  ++i) {\n\t\taxis_map[i]\t= AXIS__NONE;\n\t\tcontrol_map[i]\t= JOY_ABSOLUTE_AXIS;\n\t\t//pdwRawValue[i] = RawValuePointer(i);\n\t}\n\n\tif (!joy_advanced.integer) {\n\t\t// Default joystick initialization\n\t\t// 2 axes only with joystick control.\n\t\taxis_map[JOY_AXIS_X] = AXIS_TURN;\n\t\t// dwControlMap[JOY_AXIS_X] = JOY_ABSOLUTE_AXIS;\n\t\taxis_map[JOY_AXIS_Y] = AXIS_FORWARD;\n\t\t// dwControlMap[JOY_AXIS_Y] = JOY_ABSOLUTE_AXIS;\n\t}\n\telse\n\t{\n\t\tif (strcmp (joy_name.string, \"joystick\")) {\n\t\t\t// Notify user of advanced controller.\n\t\t\tCom_Printf (\"\\n%s configured\\n\\n\", joy_name.string);\n\t\t}\n\n\t\t// Advanced initialization here\n\t\t// data supplied by user via joy_axisn cvars.\n\t\t// Axis index in bits 7:0.  Relative/absolute flag\n\t\t// is bit 8.\n\t\tval = joy_advaxisx.integer;\n\t\taxis_map[JOY_AXIS_X] = val & 0x00ff;\n\t\tcontrol_map[JOY_AXIS_X] = val & JOY_RELATIVE_AXIS;\n\t\tval = joy_advaxisy.integer;\n\t\taxis_map[JOY_AXIS_Y] = val & 0x00ff;\n\t\tcontrol_map[JOY_AXIS_Y] = val & JOY_RELATIVE_AXIS;\n\t\tval = joy_advaxisz.integer;\n\t\taxis_map[JOY_AXIS_Z] = val & 0x00ff;\n\t\tcontrol_map[JOY_AXIS_Z] = val & JOY_RELATIVE_AXIS;\n\t\tval = joy_advaxisr.integer;\n\t\taxis_map[JOY_AXIS_R] = val & 0x00ff;\n\t\tcontrol_map[JOY_AXIS_R] = val & JOY_RELATIVE_AXIS;\n\t\tval = joy_advaxisu.integer;\n\t\taxis_map[JOY_AXIS_U] = val & 0x00ff;\n\t\tcontrol_map[JOY_AXIS_U] = val & JOY_RELATIVE_AXIS;\n\t\tval = joy_advaxisv.integer;\n\t\taxis_map[JOY_AXIS_V] = val & 0x00ff;\n\t\tcontrol_map[JOY_AXIS_V] = val & JOY_RELATIVE_AXIS;\n\t}\n\n\t/*\n\t * If the requested joystick device index has changed, try opening it.\n\t */\n\tif (joy_devidx != joy_index.integer) {\n\t\tSDL_Joystick *newdev;\n\n\t\tnewdev = SDL_JoystickOpen (joy_index.integer);\n\t\tif (newdev) {\n\t\t\tif (joy_dev) {\n\t\t\t\t/*  Close the old one.  */\n\t\t\t\tSDL_JoystickClose (joy_dev);\n\t\t\t}\n\t\t\tjoy_dev\t\t= newdev;\n\t\t\tjoy_devidx\t= joy_index.integer;\n\t\t\tjoy_numaxes\t= SDL_JoystickNumAxes (joy_dev);\n\t\t\tjoy_numbuttons\t= SDL_JoystickNumButtons (joy_dev);\n\t\t\tjoy_haspov\t= (SDL_JoystickNumHats (joy_dev) > 0);\n\t\t\tif (joy_numbuttons > sizeof (joy_prevbuttons) * 8) {\n\t\t\t\t/*  We can't handle more than 32 buttons.  */\n\t\t\t\tjoy_numbuttons = sizeof (joy_prevbuttons) * 8;\n\t\t\t}\n\t\t\tCon_Printf (\"Opened joystick index %d: \\\"%s\\\"\\n\",\n\t\t\t            joy_index.integer, SDL_JoystickName (joy_dev));\n\t\t} else {\n\t\t\tCon_Printf (\"Failed to open joystick index %d\\n\", joy_index.integer);\n\t\t}\n\t}\n}\n\nvoid\nIN_Commands (void)\n{\n\tuint32_t\tbuttonstate, buttonmask;\n\tint\t\ti, base_key;\n\n\tif (!joy_avail  ||  !joy_dev  ||  !in_joystick.integer)\n\t\treturn;\n\n\t/*\n\t * Loop through the joystick buttons\n\t * First four buttons are mapped to K_JOY[1-4].  Buttons after that are\n\t * mapped to K_AUX1+.  POV directions are mapped to K_JOYPOV{UP,RT,DN,LT}.\n\t */\n\tbuttonstate = 0;\n\tfor (i = 0;  i < joy_numbuttons;  ++i) {\n\t\tuint8_t val = SDL_JoystickGetButton (joy_dev, i);\n\n\t\tbuttonstate |= val << i;\n\t\tbuttonmask = 1 << i;\n\n\t\tif ((buttonstate ^ joy_prevbuttons) & buttonmask) {\n\t\t\tbase_key = i < 4 ?  K_JOY1 :  K_AUX1 - 4;\n\t\t\tKey_Event (base_key + i, val != 0);\n\t\t}\n\t}\n\tjoy_prevbuttons = buttonstate;\n\n\tif (joy_haspov) {\n\t\t/*\n\t\t * SDL_JoystickGetHat() returns a bitmask of the four cardinal\n\t\t * directions in up/right/down/left order from bit 0.  Only\n\t\t * the first POV hat is used.\n\t\t */\n\t\tuint8_t povstate = SDL_JoystickGetHat (joy_dev, 0);\n\t\tuint8_t povmask;\n\n\t\tfor (i = 0;  i < 4;  ++i) {\n\t\t\tpovmask = 1 << i;\n\n\t\t\tif ((povstate ^ joy_prevpov) & povmask) {\n\t\t\t\tKey_Event (K_JOYPOVUP + i, !!(povstate & povmask));\n\t\t\t}\n\t\t}\n\t\tjoy_prevpov = povstate;\n\t}\n}\n\nstatic void\nIN_JoyMove (usercmd_t *cmd)\n{\n\tfloat\tspeed, aspeed, frametime;\n\tfloat\taxisval;\n\tint\ti;\n\n\tif (!in_joystick.integer) return;\n\n\tif (Movie_IsCapturing()  &&  movie_steadycam.value) {\n\t\tframetime = movie_fps.value > 0 ?  1.0 / movie_fps.value\n\t\t                                :  1.0 / 30.0;\n\t} else {\n\t\tframetime = cls.trueframetime;\n\t}\n\n\t// Complete initialization if first time in\n\t// this is needed as cvars are not available at initialization time.\n\tif (!joy_advancedinit)\n\t{\n\t\tJoy_AdvancedUpdate_f();\n\t\tjoy_advancedinit = true;\n\t}\n\n\t// Verify joystick is available and that the user wants to use it.\n\tif (!joy_avail  ||  !joy_dev)\n\t\treturn;\n\n\tspeed = (in_speed.state & 1) ? cl_movespeedkey.value : 1.0;\n\taspeed = speed * frametime;\n\n\t// Loop through the axes.\n\tfor (i = 0;  i < JOY_MAX_AXES;  ++i)\n\t{\n\t\tif (axis_map[i] == AXIS__NONE  ||  i >= joy_numaxes) {\n\t\t\tcontinue;\n\t\t}\n\t\taxisval = SDL_JoystickGetAxis (joy_dev, i);\n\n\t\t// Convert range from -32768..32767 to -1..1\n\t\taxisval /= 32768.0;\n\n\t\tswitch (axis_map[i]) {\n\t\tcase AXIS_FORWARD:\n\t\t\tif (joy_advanced.integer == 0  &&  mlook_active)\n\t\t\t{\n\t\t\t\t// user wants forward control to become look control\n\t\t\t\tif (fabs (axisval) > joy_pitchthreshold.value) {\n\t\t\t\t\t// if mouse invert is on, invert the joystick pitch value\n\t\t\t\t\t// only absolute control support here (joy_advanced is false)\n\t\t\t\t\tif (m_pitch.value < 0.0)\n\t\t\t\t\t\tcl.viewangles[PITCH] -= (axisval * joy_pitchsensitivity.value) * aspeed * cl_pitchspeed.value;\n\t\t\t\t\telse\n\t\t\t\t\t\tcl.viewangles[PITCH] += (axisval * joy_pitchsensitivity.value) * aspeed * cl_pitchspeed.value;\n\t\t\t\t\tV_StopPitchDrift();\n\t\t\t\t} else {\n\t\t\t\t\t// no pitch movement\n\t\t\t\t\t// disable pitch return-to-center unless requested by user\n\t\t\t\t\t// *** this code can be removed when the lookspring bug is fixed\n\t\t\t\t\t// *** the bug always has the lookspring feature on\n\t\t\t\t\tif (lookspring.value == 0.0)\n\t\t\t\t\t\tV_StopPitchDrift();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// user wants forward control to be forward control\n\t\t\t\tif (fabs (axisval) > joy_forwardthreshold.value)\n\t\t\t\t\tcmd->forwardmove += (axisval * joy_forwardsensitivity.value) * speed * cl_forwardspeed.value;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase AXIS_SIDE:\n\t\t\tif (fabs (axisval) > joy_sidethreshold.value)\n\t\t\t\tcmd->sidemove += (axisval * joy_sidesensitivity.value) * speed * cl_sidespeed.value;\n\t\t\tbreak;\n\n\t\tcase AXIS_FLY:\n\t\t\tif (fabs (axisval) > joy_flythreshold.value)\n\t\t\t\tcmd->upmove += (axisval * joy_flysensitivity.value) * speed * cl_upspeed.value;\n\t\t\tbreak;\n\n\t\tcase AXIS_TURN:\n\t\t\tif ((in_strafe.state & 1)  ||  (lookstrafe.value  &&  mlook_active)) {\n\t\t\t\t// user wants turn control to become side control\n\t\t\t\tif (fabs (axisval) > joy_sidethreshold.value)\n\t\t\t\t\tcmd->sidemove -= (axisval * joy_sidesensitivity.value) * speed * cl_sidespeed.value;\n\t\t\t} else {\n\t\t\t\t// user wants turn control to be turn control\n\t\t\t\tif (fabs (axisval) > joy_yawthreshold.value) {\n\t\t\t\t\tif (control_map[i] == JOY_ABSOLUTE_AXIS)\n\t\t\t\t\t\tcl.viewangles[YAW] += (axisval * joy_yawsensitivity.value) * aspeed * cl_yawspeed.value;\n\t\t\t\t\telse\n\t\t\t\t\t\tcl.viewangles[YAW] += (axisval * joy_yawsensitivity.value) * speed * 180.0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase AXIS_LOOK:\n\t\t\tif (mlook_active)\n\t\t\t{\n\t\t\t\tif (fabs (axisval) > joy_pitchthreshold.value)\n\t\t\t\t{\n\t\t\t\t\t// pitch movement detected and pitch movement desired by user\n\t\t\t\t\tif (control_map[i] == JOY_ABSOLUTE_AXIS)\n\t\t\t\t\t\tcl.viewangles[PITCH] += (axisval * joy_pitchsensitivity.value) * aspeed * cl_pitchspeed.value;\n\t\t\t\t\telse\n\t\t\t\t\t\tcl.viewangles[PITCH] += (axisval * joy_pitchsensitivity.value) * speed * 180.0;\n\t\t\t\t\tV_StopPitchDrift();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// no pitch movement\n\t\t\t\t\t// disable pitch return-to-center unless requested by user\n\t\t\t\t\t// *** this code can be removed when the lookspring bug is fixed\n\t\t\t\t\t// *** the bug always has the lookspring feature on\n\t\t\t\t\tif (lookspring.value == 0.0)\n\t\t\t\t\t\tV_StopPitchDrift();\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Bounds check pitch\n\t//cl.viewangles[PITCH] = bound (-70, cl.viewangles[PITCH], 80);\n\tif (cl.viewangles[PITCH] > cl.maxpitch)\n\t\tcl.viewangles[PITCH] = cl.maxpitch;\n\tif (cl.viewangles[PITCH] < cl.minpitch)\n\t\tcl.viewangles[PITCH] = cl.minpitch;\n}\n\nstatic void\nIN_StartupJoystick (void)\n{\n\tSDL_Joystick\t*jdev;\n\tint\t\tnumdevs, i;\n\n\t//COM_CheckParm (\"-joystick\");\n\t\n\tif (!host_initialized) {\n\t\tCvar_SetCurrentGroup (CVAR_GROUP_INPUT_JOYSTICK);\n\t\tCvar_Register (&in_joystick);\n\t\tCvar_Register (&joy_index);\n\t\tCvar_Register (&joy_name);\n\t\tCvar_Register (&joy_advanced);\n\t\tCvar_Register (&joy_advaxisx);\n\t\tCvar_Register (&joy_advaxisy);\n\t\tCvar_Register (&joy_advaxisz);\n\t\tCvar_Register (&joy_advaxisr);\n\t\tCvar_Register (&joy_advaxisu);\n\t\tCvar_Register (&joy_advaxisv);\n\t\tCvar_Register (&joy_forwardthreshold);\n\t\tCvar_Register (&joy_sidethreshold);\n\t\tCvar_Register (&joy_flythreshold);\n\t\tCvar_Register (&joy_pitchthreshold);\n\t\tCvar_Register (&joy_yawthreshold);\n\t\tCvar_Register (&joy_flysensitivity);\n\t\tCvar_Register (&joy_forwardsensitivity);\n\t\tCvar_Register (&joy_sidesensitivity);\n\t\tCvar_Register (&joy_pitchsensitivity);\n\t\tCvar_Register (&joy_yawsensitivity);\n\t\tCvar_ResetCurrentGroup();\n\n\t\tCmd_AddCommand (\"joyadvancedupdate\", Joy_AdvancedUpdate_f);\n\t}\n\n\tif (!in_joystick.value) return;\n\n\tif (SDL_WasInit (SDL_INIT_JOYSTICK) == 0) {\n\t\tint ret = SDL_InitSubSystem (SDL_INIT_JOYSTICK);\n\t\tif (ret == -1) {\n\t\t\tCom_Printf (\"\\nSDL joystick subsystem init failed.\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\t/*  See if there are any joysticks at all.  */\n\tnumdevs = SDL_NumJoysticks();\n\tif (!numdevs) {\n\t\tCom_Printf (\"\\nno joysticks detected by SDL\\n\\n\");\n\t\treturn;\n\t}\n\n\t/*  Check if we can open any of them.  */\n\tfor (i = 0;  i < numdevs;  ++i) {\n\t\tjdev = SDL_JoystickOpen (i);\n\t\tif (jdev) {\n\t\t\tCom_Printf (\"Detected joystick %d: %d axes, %d buttons: \\\"%s\\\"\\n\",\n\t\t\t            i,\n\t\t\t            SDL_JoystickNumAxes (jdev),\n\t\t\t            SDL_JoystickNumButtons (jdev),\n\t\t\t            SDL_JoystickName (jdev));\n\t\t\tSDL_JoystickClose (jdev);\n\t\t\tjoy_avail = true;\n\t\t}\n\t}\n}\n\nvoid\nIN_DeactivateJoystick (void)\n{\n\t// Close the device here.\n\treturn;\n}\n\nvoid IN_Move (usercmd_t *cmd)\n{\n\tIN_MouseMove (cmd);\n\tIN_JoyMove (cmd);\n}\n\nvoid IN_Init (void)\n{\n\tCvar_SetCurrentGroup (CVAR_GROUP_INPUT_MOUSE);\n\tCvar_Register (&m_filter);\n\n\tCvar_SetCurrentGroup (CVAR_GROUP_INPUT_KEYBOARD);\n\tCvar_Register (&cl_keypad);\n\tCvar_ResetCurrentGroup ();\n\n\tif (!host_initialized) {\n\t\tCmd_AddCommand(\"in_restart\", IN_Restart_f);\n\t}\n\n\tIN_StartupMouse ();\n\tIN_StartupJoystick();\n}\n\nvoid IN_Shutdown(void)\n{\n\tIN_DeactivateMouse(); // btw we trying de init this in video shutdown too...\n\n#ifdef __APPLE__\n\tOSX_Mouse_Shutdown(); // Safe to call, will just return if it's not running\n#endif\n\n\tmouseinitialized = false;\n}\n\n"
  },
  {
    "path": "src/input.h",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n// input.h -- external (non-keyboard) input devices\n\n#ifndef __INPUT_H__\n#define __INPUT_H__\n\nvoid IN_Init (void);\nvoid IN_Shutdown (void);\nvoid IN_Commands (void); // oportunity for devices to stick commands on the script buffer\nvoid IN_Move (usercmd_t *cmd); // add additional movement on top of the keyboard move cmd\nvoid IN_ClearProtectedKeys (void); // reset player movement\n\n//\n// cl_input.c\n//\ntypedef struct\n{\n\tint\t\tdown[2];\t\t// key nums holding it down\n\tint\t\tstate;\t\t\t// low bit is down state\n\tdouble\tdowntime;\t\t// when KeyDown() last time called for that button\n\tdouble\tuptime;\t\t\t// when KeyUp() last time called for that button\n} kbutton_t;\n\nextern kbutton_t in_mlook, in_klook;\nextern kbutton_t in_strafe;\nextern kbutton_t in_speed;\n\nvoid CL_InitInput (void);\nvoid CL_SendClientCommand(qbool reliable, char *format, ...);\nvoid CL_SendCmd (void);\nvoid CL_BaseMove (usercmd_t *cmd);\nfloat CL_KeyState (kbutton_t *key, qbool lookbutton);\nqbool Key_TryMovementProtected(const char *cmd, qbool down, int key);\n\nextern cvar_t\tallow_scripts;\n\nextern cvar_t\tcl_upspeed;\nextern cvar_t\tcl_forwardspeed;\nextern cvar_t\tcl_backspeed;\nextern cvar_t\tcl_sidespeed;\nextern cvar_t\tcl_movespeedkey;\nextern cvar_t\tcl_anglespeedkey;\nextern cvar_t\tcl_yawspeed;\nextern cvar_t\tcl_pitchspeed;\nextern cvar_t\tcl_keypad;\n\nextern cvar_t\tfreelook;\nextern cvar_t\tsensitivity;\nextern cvar_t\tcursor_sensitivity;\nextern cvar_t\tlookspring;\nextern cvar_t\tlookstrafe;\n\nextern cvar_t\tm_pitch;\nextern cvar_t\tm_yaw;\nextern cvar_t\tm_forward;\nextern cvar_t\tm_side;\nextern cvar_t\tm_accel;\nextern cvar_t\tm_accel_power;\nextern cvar_t\tm_accel_senscap;\nextern cvar_t\tm_accel_offset;\nextern cvar_t\tm_filter;\nextern cvar_t\t_windowed_mouse;\n\n#define mlook_active\t(freelook.value || (in_mlook.state&1))\n\n#endif /* __INPUT_H__ */\n"
  },
  {
    "path": "src/irc.c",
    "content": "/*\r\nCopyright (C) 2011 johnnycz\r\n\r\nThis program is free software; you can redistribute it and/or\r\nmodify it under the terms of the GNU General Public License\r\nas published by the Free Software Foundation; either version 2\r\nof the License, or (at your option) any later version.\r\n\r\nThis program is distributed in the hope that it will be useful,\r\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\r\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\r\n\r\nSee the GNU General Public License for more details.\r\n\r\nYou should have received a copy of the GNU General Public License\r\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\r\n*/\r\n\r\n#ifdef WITH_IRC\r\n\r\n#include \"quakedef.h\"\r\n#include \"libircclient.h\"\r\n#include \"libirc_rfcnumeric.h\"\r\n#include \"version.h\"\r\n#include \"textencoding.h\"\r\n#include \"irc_filter.h\"\r\n\r\n// because of MAX_MACRO_STRING\r\n#include \"teamplay.h\"\r\n\r\ncvar_t irc_server_address = {\"irc_server_address\", \"194.124.229.59\"}; // quakenet\r\ncvar_t irc_server_port = {\"irc_server_port\", \"6667\"};\r\ncvar_t irc_server_password = {\"irc_server_password\", \"\"};\r\ncvar_t irc_user_nick = {\"irc_user_nick\", \"\"};\r\ncvar_t irc_user_username = {\"irc_user_username\", \"ezQuake\"};\r\ncvar_t irc_user_realname = {\"irc_user_realname\", \"\"};\r\n\r\n#define MAX_TARGET_LEN 128\r\n#define MAX_CHANS 100\r\n\r\ntypedef struct\r\n{\r\n\tunsigned int size;\r\n\tunsigned int current;\r\n\tchar *list[MAX_CHANS];\r\n} irc_chanlist;\r\n\r\ntypedef struct\r\n{\r\n\tirc_chanlist chanlist;\r\n\tchar nick[MAX_TARGET_LEN];\r\n} irc_ctx_t;\r\n\r\nextern cvar_t name;\r\n\r\nstatic irc_callbacks_t callbacks;\r\nstatic irc_session_t * irc_singlesession;\r\nstatic irc_ctx_t irc_ctx;\r\nstatic fd_set irc_fd_in, irc_fd_out;\r\nstatic int irc_maxfd = 0;\r\n\r\n#define MAXPRINTMSG     4096 // should be moved from common.c to common.h\r\n\r\nint utf8ToWc(char* str, wchar* c) {\r\n        int i, n;\r\n\t\tunsigned char c1, c2;\r\n        unsigned char c0 = (unsigned char)str[0];\r\n        *c = '_';\r\n        if (c0 & 0x80) {                                                                 // 1xxx xxxx\r\n                if(c0 & 0x40) {                                                         // 11xx xxxx\r\n                        if(c0 & 0x20) {                                                 // 111x xxxx\r\n                                if(c0 & 0x10) {                                         // 1111 xxxx\r\n                                        n = 4;\r\n                                        if(c0 & 0x08) {                                 // 1111 1xxx\r\n                                                n = 5;\r\n                                                if(c0 & 0x04) {                         // 1111 11xx\r\n                                                        if(c0 & 0x02) {                 // 1111 111x\r\n                                                                return 1;\r\n                                                        }\r\n                                                        n = 6;\r\n                                                }\r\n                                        }\r\n                                        i = 1;\r\n                                        while(i < n && (str[i] & 0x80) == 0x80)\r\n                                                ++i;\r\n                                        return i;\r\n                                } else {                // 1110xxxx\r\n                                        c1 = (unsigned char)str[1];\r\n                                        if((c1 & (0x80 | 0x40)) != 0x80)\r\n                                                return 1;\r\n\r\n                                        c2 = (unsigned char)str[2];\r\n                                        if((c2 & (0x80 | 0x40)) != 0x80)\r\n                                                return 2;\r\n\r\n                                        *c = (((wchar)c0 & 0x0f) << 12) |\r\n                                                (((wchar)c1 & 0x3f) << 6) |\r\n                                                ((wchar)c2 & 0x3f);\r\n\r\n                                        // Ugly utf-16 surrogate catch (D800-DFFF)\r\n                                        if ((unsigned short)(c - 0xD800) < 0x800) {\r\n                                                *c = '_';\r\n                                        }\r\n\r\n                                        return 3;\r\n                                }\r\n                        } else {                                // 110xxxxx\r\n                                c1 = (unsigned char)str[1];\r\n                                if((c1 & (0x80 | 0x40)) != 0x80)\r\n                                        return 1;\r\n\r\n                                *c = (((wchar)c0 & 0x1f) << 6) |\r\n                                        ((wchar)c1 & 0x3f);\r\n                                return 2;\r\n                        }\r\n                } else {                                        // 10xxxxxx\r\n                        return 1;\r\n                }\r\n        } else {                                                // 0xxxxxxx\r\n                *c = c0;\r\n                return 1;\r\n        }\r\n}\r\n\r\nint wcToUtf8(wchar c, char *dst) {\r\n        if(c >= 0x0800) {\r\n                dst[0] = (char)(0x80 | 0x40 | 0x20  | (c >> 12));\r\n                dst[1] = (char)(0x80 | ((c >> 6) & 0x3f));\r\n                dst[2] = (char)(0x80 | (c & 0x3f));\r\n\t\t\t\treturn 3;\r\n        } else if(c >= 0x0080) {\r\n                dst[0] = (char)(0x80 | 0x40 | (c >> 6));\r\n                dst[1] = (char)(0x80 | (c & 0x3f));\r\n\t\t\t\treturn 2;\r\n        } else {\r\n                dst[0] = (char)c;\r\n\t\t\t\treturn 1;\r\n        }\r\n}\r\n\r\nchar* encode_utf8(wchar *wmsg)\r\n{\r\n\tstatic char buffer[MAXPRINTMSG];\r\n\tint src, dst;\r\n\tfor (src = dst = 0; wmsg[src]; src++) {\r\n\t\tdst += wcToUtf8(wmsg[src], &(buffer[dst]));\r\n\t}\r\n\tbuffer[dst] = 0;\r\n\treturn buffer;\r\n}\r\n\r\nvoid IRC_Printf(char *fmt, ...)\r\n{\r\n\twchar *wmsg;\r\n\tchar utf8_ok = 1;\r\n\tsize_t dst, src, len;\r\n\r\n\tva_list argptr;\r\n\tchar msg[MAXPRINTMSG];\r\n\r\n\tva_start (argptr, fmt);\r\n\tvsnprintf (msg, sizeof(msg), fmt, argptr);\r\n\tva_end (argptr);\r\n\t\r\n\tlen = strlen(msg);\r\n\twmsg = (wchar*) Q_malloc((len+1) * sizeof(wchar));\r\n\tfor (src = dst = 0; src < len; dst++) {\r\n\t\tint numc = utf8ToWc(&(msg[src]), &(wmsg[dst]));\r\n\t\tif (wmsg[dst] == '_' && msg[src] != '_') utf8_ok = 0;\r\n\t\tsrc += numc;\r\n\t}\r\n\twmsg[dst] = 0;\r\n\r\n#ifdef _WIN32 // todo: fixme. don't know to convert from single-byte system codepage. sorry, *nix: you will read only utf-8\r\n\tif (!utf8_ok) {\r\n\t\t// fix some cp1251/etc bastard\r\n\t\tMultiByteToWideChar(CP_ACP, 0, msg, len, wmsg, len);\r\n\t}\r\n#endif\r\n\tCon_PrintW(wmsg);\r\n\tQ_free(wmsg);\r\n}\r\n\r\nvoid IRC_Chanlist_Add(irc_chanlist *list, const char* chan)\r\n{\r\n\tint i;\r\n\r\n\tfor (i = 0; i < list->size; ++i) {\r\n\t\tif (strcmp(list->list[i], chan) == 0) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t}\r\n\r\n\tif (list->size < MAX_CHANS) {\r\n\t\tlist->list[list->size] = Q_strdup(chan);\r\n\t\tlist->current = list->size++;\r\n\t}\r\n\telse {\r\n\t\tCom_Printf(\"Maximum amount of IRC channels reached\\n\");\r\n\t}\r\n}\r\n\r\nqbool IRC_Chanlist_Remove(irc_chanlist *list, const char* chan)\r\n{\r\n\tint i;\r\n\r\n\tfor (i = 0; i < list->size; i++) {\r\n\t\tif (strcmp(list->list[i], chan) == 0) {\r\n\t\t\tQ_free(list->list[i]);\r\n\t\t\tif (list->size > 1) {\r\n\t\t\t\tlist->list[i] = list->list[list->size-1];\r\n\t\t\t}\r\n\t\t\tlist->size--;\r\n\r\n\t\t\tif (list->current >= list->size) {\r\n\t\t\t\t// don't select the next window as the removed one was the last one\r\n\t\t\t\t// so select the previous one, or none if list is empty\r\n\t\t\t\tlist->current = list->size ? list->size - 1 : 0;\r\n\t\t\t}\r\n\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\r\n\treturn false;\r\n}\r\n\r\nstatic char *IRC_Chanlist_GetCurrent(irc_chanlist *list)\r\n{\r\n\tif (list->size) {\r\n\t\treturn list->list[list->current];\r\n\t}\r\n\telse {\r\n\t\treturn \"\";\r\n\t}\r\n}\r\n\r\nchar* IRC_GetCurrentChan(void)\r\n{\r\n\treturn IRC_Chanlist_GetCurrent(&irc_ctx.chanlist);\r\n}\r\n\r\nstatic void IRC_Chanlist_ChooseNext(irc_chanlist *list)\r\n{\r\n\tlist->current++;\r\n\tif (list->current >= list->size) {\r\n\t\tlist->current = 0;\r\n\t}\r\n}\r\n\r\nvoid IRC_NextChan(void) {\r\n\tIRC_Chanlist_ChooseNext(&irc_ctx.chanlist);\r\n}\r\n\r\nstatic void IRC_Chanlist_ChoosePrev(irc_chanlist *list)\r\n{\r\n\tif (list->current) {\r\n\t\tlist->current--;\r\n\t}\r\n\telse {\r\n\t\tlist->current = list->size - 1;\r\n\t}\r\n}\r\n\r\nvoid IRC_PrevChan(void) {\r\n\tIRC_Chanlist_ChoosePrev(&irc_ctx.chanlist);\r\n}\r\n\r\nchar *Cmd_ArgLine(unsigned int starting_from_arg)\r\n{\r\n\tstatic char args[MAX_MACRO_STRING];\r\n\tint i;\r\n\r\n\targs[0] = '\\0';\r\n\r\n\tfor (i = starting_from_arg; i < Cmd_Argc(); i++) {\r\n\t\tif (i > starting_from_arg)\r\n\t\t\tstrlcat (args, \" \", MAX_MACRO_STRING);\r\n\r\n\t\tstrlcat (args, Cmd_Argv(i), MAX_MACRO_STRING);\r\n\t}\r\n\r\n\treturn args;\r\n}\r\n\r\nchar* IRC_mask_to_nick(const char* mask)\r\n{\r\n\tstatic char masktonickbuf[255];\r\n\tirc_target_get_nick(mask, masktonickbuf, sizeof(masktonickbuf));\r\n\treturn masktonickbuf;\r\n}\r\n\r\nvoid IRC_event_ctcp_req(irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n\tIRC_Printf(\"IRC: CTCP Request %s from %s\\n\", params[0], IRC_mask_to_nick(origin));\r\n\r\n\tif (strcmp(params[0], \"VERSION\") == 0) {\r\n\t\tirc_cmd_ctcp_reply(session, IRC_mask_to_nick(origin), \"VERSION ezQuake \" VERSION_NUMBER);\r\n\t\tCom_Printf(\"- replied\\n\");\r\n\t}\r\n\tCom_Printf(\"- ignored\\n\");\r\n}\r\n\r\nvoid IRC_event_notice(irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n    if (!IRC_filter_show_notice_messages())\r\n        return;\r\n    \r\n\tIRC_Printf(\"IRC: *%s* (%s): %s\\n\", origin, params[0], count > 1 ? params[1] : \"\");\r\n}\r\n\r\nvoid IRC_event_topic(irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n    if (!IRC_filter_show_chanop_messages())\r\n        return;\r\n    \r\n\tif (count > 1) {\r\n\t\tIRC_Printf(\"IRC: %s changes topic on %s to %s\\n\", origin, params[0], params[1]);\r\n\t}\r\n\telse {\r\n\t\tIRC_Printf(\"IRC: %s changes topic on %s\\n\", origin, params[0]);\r\n\t}\r\n}\r\n\r\nvoid IRC_event_mode(irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n    if (!IRC_filter_show_chanop_messages())\r\n        return;\r\n    \r\n    IRC_Printf(\"IRC: %s (%s) sets mode %s%s%s\\n\", IRC_mask_to_nick(origin), params[0], params[1],\r\n               (count > 2 ? \" \" : \"\"), (count > 2 ? params[3] : \"\"));\r\n}\r\n\r\nvoid IRC_event_kick(irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n    if (!IRC_filter_show_chanop_messages())\r\n        return;\r\n    \r\n    IRC_Printf(\"IRC: %s has been kicked from %s by %s (%s)\\n\", params[1], params[0], IRC_mask_to_nick(origin), params[2]);\r\n}\r\n\r\nvoid IRC_event_privmsg(irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n\tirc_ctx_t *ctx = (irc_ctx_t *) irc_get_ctx(session);\r\n\r\n\tif (count > 1) {\r\n        if (IRC_filter_show_private_messages()) {\r\n            IRC_Printf(\"IRC: (Privmsg) <%s> %s\\n\", IRC_mask_to_nick(origin), params[1]);\r\n        }\r\n\t\tIRC_Chanlist_Add(&ctx->chanlist, IRC_mask_to_nick(origin));\r\n\t}\r\n}\r\n\r\nvoid IRC_event_channel (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n    if (!IRC_filter_show_chanop_messages())\r\n        return;\r\n        \r\n\tif (count > 1) {\r\n\t\tIRC_Printf(\"IRC: (%s) <%s> %s\\n\", params[0], IRC_mask_to_nick(origin), params[1]);\r\n\t}\r\n}\r\n\r\nvoid IRC_event_join (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n\tirc_ctx_t *ctx = (irc_ctx_t *) irc_get_ctx(session);\r\n\t\r\n\tif ( !origin )\r\n\t\treturn;\r\n\r\n\t// We need to know whether WE are joining the channel, or someone else.\r\n\t// To do this, we compare the origin with our nick.\r\n    // Note that we have set LIBIRC_OPTION_STRIPNICKS to obtain 'parsed' nicks.\r\n\tif ( !strcmp(IRC_mask_to_nick(origin), ctx->nick)) {\r\n\t\tIRC_Printf(\"IRC: You've joined %s\\n\", params[0]);\r\n\t\tIRC_Chanlist_Add(&ctx->chanlist, params[0]);\t\t\r\n\t}\r\n\telse {\r\n        if (IRC_filter_show_chanop_messages()) {\r\n            IRC_Printf(\"IRC: %s &c0f0joined&cfff %s\\n\", IRC_mask_to_nick(origin), params[0]);\r\n        }\r\n\t}\r\n}\r\n\r\nvoid IRC_event_part (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n\tirc_ctx_t *ctx = (irc_ctx_t *) irc_get_ctx(session);\r\n\r\n\tif (strcmp(IRC_mask_to_nick(origin), ctx->nick) == 0) {\r\n\t\tIRC_Chanlist_Remove(&ctx->chanlist, params[0]);\r\n\t\tIRC_Printf(\"IRC: You have left %s\\n\", params[0]);\r\n\t}\r\n\telse {\r\n    if (IRC_filter_show_chanop_messages()) {\r\n      IRC_Printf(\"IRC: %s has &c888left&cfff %s\\n\", IRC_mask_to_nick(origin), params[0]);\r\n    }\r\n\t}\r\n}\r\n\r\nvoid IRC_event_quit (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n    if (!IRC_filter_show_connection_messages())\r\n        return;\r\n    \r\n    IRC_Printf(\"IRC: %s Quit: %s\\n\", IRC_mask_to_nick(origin), count > 1 ? params[0] : \"-\");\r\n}\r\n\r\nvoid IRC_event_nick (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n    if (!IRC_filter_show_connection_messages())\r\n        return;\r\n\r\n    Com_Printf(\"IRC: Nick event\\n\");\r\n}\r\n\r\nvoid IRC_event_connect (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n\tCom_Printf(\"IRC: Connected\\n\");\r\n}\r\n\r\nvoid IRC_event_numeric (irc_session_t * session, unsigned int event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n\tif ( event > 400 )\r\n\t{\r\n\t\tIRC_Printf (\"IRC ERROR %d: %s: %s %s %s %s\\n\", \r\n\t\t\t\tevent,\r\n\t\t\t\torigin ? origin : \"unknown\",\r\n\t\t\t\tparams[0],\r\n\t\t\t\tcount > 1 ? params[1] : \"\",\r\n\t\t\t\tcount > 2 ? params[2] : \"\",\r\n\t\t\t\tcount > 3 ? params[3] : \"\");\r\n\t}\r\n\telse {\r\n\t\tint i;\r\n\r\n\t\tswitch (event) {\r\n\t\t\tcase LIBIRC_RFC_RPL_WELCOME:\r\n\t\t\t\tIRC_Printf(\"IRC: Welcome to the Internet Relay Network %s\\n\", params[0]);\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase LIBIRC_RFC_RPL_MOTDSTART:\r\n\t\t\t\tCom_Printf(\"IRC: Message of the day:\\n\");\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase LIBIRC_RFC_RPL_MOTD:\r\n\t\t\t\tIRC_Printf(\"IRC: %s\\n\", params[1]);\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase LIBIRC_RFC_RPL_ENDOFMOTD:\r\n\t\t\t\tCom_Printf(\"IRC: End of MOTD\\n\");\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase LIBIRC_RFC_RPL_NAMREPLY:\r\n\t\t\t\tCom_Printf(\"IRC: Names:\\n\");\r\n\t\t\t\tfor (i = 2; i < count; ++i) {\r\n\t\t\t\t\tIRC_Printf(\" %s\", params[i]);\r\n\t\t\t\t}\r\n\t\t\t\tCom_Printf(\"\\n\");\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase LIBIRC_RFC_RPL_TOPIC:\r\n\t\t\t\tCom_Printf(\"IRC: Topic:\\n\");\r\n\t\t\t\tfor (i = 2; i < count; ++i) {\r\n\t\t\t\t\tIRC_Printf(\" %s\", params[i]);\r\n\t\t\t\t}\r\n\t\t\t\tCom_Printf(\"\\n\");\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tdefault:\r\n\t\t\t\tCom_Printf(\"IRC: Event %d\\n\", event);\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nvoid IRC_event_universal (irc_session_t * session, const char * event, const char * origin, const char ** params, unsigned int count)\r\n{\r\n\tchar buf[512];\r\n\tint cnt;\r\n\r\n\tbuf[0] = '\\0';\r\n\r\n\tfor ( cnt = 0; cnt < count; cnt++ )\r\n\t{\r\n\t\tif ( cnt )\r\n\t\t\tstrcat (buf, \"|\");\r\n\r\n\t\tstrcat (buf, params[cnt]);\r\n\t}\r\n\r\n\r\n\tIRC_Printf (\"Event \\\"%s\\\", origin: \\\"%s\\\", params: %d [%s]\\n\", event, origin ? origin : \"NULL\", cnt, buf);\r\n}\r\n\r\nvoid normalise_channel_name(char * channel, char * output)\r\n{\r\n    if (channel[0] == '#') {\r\n        strcpy(output, channel);\r\n    } else {\r\n        strcpy(output, \"#\");\r\n        strcat(output, channel);\r\n    }\r\n}\r\n\r\nstatic void IRC_irc_f(void)\r\n{\r\n\tirc_ctx_t *ctx = (irc_ctx_t *) irc_get_ctx(irc_singlesession);\r\n\r\n\tif (Cmd_Argc() < 2) {\r\n\t\tCom_Printf(\"Usage: irc <command>\\n\");\r\n\t\treturn;\r\n\t}\r\n\r\n\tif (strcmp(Cmd_Argv(1), \"connect\") == 0) {\r\n\t\t// use quake name by default\r\n\t\tconst char *nick = irc_user_nick.string[0] ? irc_user_nick.string : name.string;\r\n\r\n\t\tif (irc_connect (irc_singlesession, irc_server_address.string, irc_server_port.integer,\r\n\t\t\tirc_server_password.string[0] ? irc_server_password.string : NULL,\r\n\t\t\tnick, irc_user_username.string, irc_user_realname.string)\r\n\t\t) {\r\n\t\t\tIRC_Printf (\"IRC: Could not connect: %s\\n\", irc_strerror (irc_errno(irc_singlesession)));\r\n\t\t}\r\n\t\telse {\r\n\t\t\tstrlcpy(ctx->nick, nick, MAX_TARGET_LEN);\r\n\t\t\t\r\n\t\t\t// multithreading is not good for us (lots of things are not thread-safe)\r\n\t\t\t// we use single-threaded variant\r\n\t\t\t// Sys_CreateThread(IRC_Run, NULL);\r\n\r\n\t\t\tCom_Printf(\"IRC: Connecting...\\n\");\r\n\t\t}\r\n\t}\r\n\telse if (strcmp(Cmd_Argv(1), \"disconnect\") == 0) {\r\n\t\tirc_disconnect(irc_singlesession);\r\n\t}\r\n\telse if (strcmp(Cmd_Argv(1), \"say\") == 0) {\r\n\t\tchar *chan = IRC_Chanlist_GetCurrent(&ctx->chanlist);\r\n\t\tif (chan) {\r\n\t\t\tchar *msg = Cmd_ArgLine(2);\r\n\t\t\twchar *wmsg = decode_string(msg);\r\n\t\t\tirc_cmd_msg(irc_singlesession, chan, encode_utf8(wmsg));\r\n\t\t\tCom_Printf(\"IRC: (%s) <%s> \", chan, ctx->nick);\r\n\t\t\tCon_PrintW(wmsg);\r\n\t\t\tCom_Printf(\"\\n\");\r\n\t\t}\r\n\t\telse {\r\n\t\t\tCom_Printf(\"No current channel selected\\n\");\r\n\t\t}\r\n\t}\r\n\telse if (strcmp(Cmd_Argv(1), \"query\") == 0) {\r\n\t\tif (Cmd_Argc() < 3) {\r\n\t\t\tCom_Printf(\"Too few arguments\\n\");\r\n\t\t}\r\n\t\telse {\r\n\t\t\tchar *msg = Cmd_ArgLine(3);\r\n\t\t\tchar *targetnick = Cmd_Argv(2);\r\n\r\n\t\t\tIRC_Chanlist_Add(&ctx->chanlist, targetnick);\r\n\t\t\tirc_cmd_msg(irc_singlesession, targetnick, msg);\r\n\t\t\tCom_Printf(\"IRC: (%s) <%s> %s\\n\", targetnick, ctx->nick, msg);\r\n\t\t}\r\n\t}\r\n\telse if (strcmp(Cmd_Argv(1), \"window\") == 0) {\r\n\t\tif (Cmd_Argc() < 3) {\r\n\t\t}\r\n\t\telse {\r\n\t\t\tif (strcmp(Cmd_Argv(2), \"next\") == 0) {\r\n\t\t\t\tIRC_NextChan();\r\n\t\t\t}\r\n\t\t\telse if (strcmp(Cmd_Argv(2), \"prev\") == 0) {\r\n\t\t\t\tIRC_PrevChan();\r\n\t\t\t}\r\n\t\t\telse if (strcmp(Cmd_Argv(2), \"close\") == 0) {\r\n\t\t\t\tIRC_Chanlist_Remove(&ctx->chanlist, IRC_GetCurrentChan());\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tCom_Printf(\"Uknown argument. Valid arguments: next, prev, close\\n\");\r\n\t\t\t}\r\n\t\t}\r\n\t\tIRC_Printf(\"IRC: Current window: %s\\n\", IRC_GetCurrentChan());\r\n\t}\r\n\telse if (strcmp(Cmd_Argv(1), \"nick\") == 0) {\r\n\t\tif (Cmd_Argc() >= 3) {\r\n\t\t\tirc_cmd_nick(irc_singlesession, Cmd_Argv(2));\r\n\t\t\t\r\n\t\t\t// fixme\r\n\t\t\t// we presume here that the change will succeed\r\n\t\t\t// that may cause lots of troubles\r\n\t\t\tstrlcpy(ctx->nick, Cmd_Argv(2), MAX_TARGET_LEN);\r\n\t\t}\r\n\t}\r\n    else if ((strcmp(Cmd_Argv(1), \"join\") == 0) ||\r\n             (strcmp(Cmd_Argv(1), \"part\") == 0)) {\r\n        char *normalised_channel = malloc(strlen(Cmd_Argv(2) + 1));\r\n        normalise_channel_name(Cmd_Argv(2), normalised_channel);\r\n        \r\n        if (strcmp(Cmd_Argv(1), \"join\") == 0) {\r\n            irc_cmd_join(irc_singlesession, normalised_channel, Cmd_Argv(3));\r\n        } else {\r\n            irc_cmd_part(irc_singlesession, normalised_channel);\r\n        }\r\n        \r\n        free(normalised_channel);\r\n    }\r\n\telse {\r\n\t\tchar *cmd = Cmd_ArgLine(1);\r\n\t\twchar *wcmd = decode_string(cmd);\r\n\t\tirc_send_raw(irc_singlesession, \"%s\", encode_utf8(wcmd));\r\n\t\tCom_Printf(\"IRC: \");\r\n\t\tCon_PrintW(wcmd);\r\n\t\tCom_Printf(\"\\n\");\r\n\t}\r\n}\r\n\r\nvoid IRC_Update(void)\r\n{\r\n\tstruct timeval timeout = { 0, 0 };\r\n\tint r;\r\n\r\n\tif (irc_singlesession && irc_is_connected(irc_singlesession)) {\r\n\t\tFD_ZERO(&irc_fd_in);\r\n\t\tFD_ZERO(&irc_fd_out);\r\n\t\tr = irc_add_select_descriptors(irc_singlesession, &irc_fd_in, &irc_fd_out, &irc_maxfd);\r\n\t\tif (r != 0) {\r\n\t\t\tCom_Printf(\"Could not set IRC socket descriptors:\\n\");\r\n\t\t\tIRC_Printf(\"- %s\\n\", irc_strerror(r));\r\n\t\t}\r\n\r\n\t\tr = select(irc_maxfd+1, &irc_fd_in, &irc_fd_out, 0, &timeout);\r\n\t\tif (r == -1) {\r\n\t\t\tCom_Printf(\"IRC: select() failed\\n\");\r\n\t\t} else if (r == 0) {\r\n\t\t\t// no data ready\r\n\t\t}\r\n\t\telse {\r\n\t\t\tr = irc_process_select_descriptors(irc_singlesession, &irc_fd_in, &irc_fd_out);\r\n\t\t\tif (r) {\r\n\t\t\t\tCom_Printf(\"IRC: Error processing select descriptors:\\n\");\r\n\t\t\t\tIRC_Printf(\"- %s\\n\", irc_strerror(r));\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nvoid IRC_Init(void)\r\n{\r\n\tcallbacks.event_connect = IRC_event_connect;\r\n\tcallbacks.event_join = IRC_event_join;\r\n\tcallbacks.event_numeric = IRC_event_numeric;\r\n\tcallbacks.event_ctcp_req = IRC_event_ctcp_req;\r\n\tcallbacks.event_privmsg = IRC_event_privmsg;\r\n\tcallbacks.event_channel = IRC_event_channel;\r\n\tcallbacks.event_nick = IRC_event_nick;\r\n\tcallbacks.event_part = IRC_event_part;\r\n\tcallbacks.event_ctcp_action = IRC_event_universal;\r\n\tcallbacks.event_ctcp_rep = IRC_event_universal;\r\n\t// callbacks.event_dcc_chat_req = IRC_event_universal;\r\n\t// callbacks.event_dcc_send_req = IRC_event_universal;\r\n\tcallbacks.event_invite = IRC_event_universal;\r\n\tcallbacks.event_kick = IRC_event_kick;\r\n\tcallbacks.event_mode = IRC_event_mode;\r\n\tcallbacks.event_notice = IRC_event_notice;\r\n\tcallbacks.event_quit = IRC_event_quit;\r\n\tcallbacks.event_umode = IRC_event_universal;\r\n\tcallbacks.event_unknown = IRC_event_universal;\r\n\tcallbacks.event_topic = IRC_event_topic;\r\n\r\n\tirc_singlesession = irc_create_session (&callbacks);\r\n\tif (!irc_singlesession) {\r\n\t\tCom_Printf (\"Could not create IRC session\\n\");\r\n\t\treturn;\r\n\t}\r\n\r\n\tirc_ctx.chanlist.size = 0;\r\n\tirc_ctx.chanlist.current = 0;\r\n\tirc_set_ctx (irc_singlesession, &irc_ctx);\r\n\r\n\tCmd_AddCommand(\"irc\", IRC_irc_f);\r\n\r\n\tCvar_SetCurrentGroup(CVAR_GROUP_CHAT);\r\n\tCvar_Register(&irc_server_address);\r\n\tCvar_Register(&irc_server_port);\r\n\tCvar_Register(&irc_server_password);\r\n\tCvar_Register(&irc_user_nick);\r\n\tCvar_Register(&irc_user_username);\r\n\tCvar_Register(&irc_user_realname);\r\n  IRC_filter_register_cvars();\r\n\r\n\tCvar_ResetCurrentGroup();\r\n}\r\n\r\nvoid IRC_Deinit(void)\r\n{\r\n\tirc_destroy_session(irc_singlesession);\r\n}\r\n#endif\r\n\r\n"
  },
  {
    "path": "src/irc.h",
    "content": "/*\nInternet Relay Chat Support\n\nCopyright (C) 2009 johnnycz\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifdef WITH_IRC\n/// initializes the IRC module, creates libircclient session \nvoid IRC_Init(void);\n\n/// process new data waiting on the socket\nvoid IRC_Update(void);\n\n/// return the name of the active (currently selected) channel from the channel list\nchar* IRC_GetCurrentChan(void);\n\n/// select following channel from the channels list as the current channel\nvoid IRC_NextChan(void);\n\n/// select previous channel from the channels list as the current channel\nvoid IRC_PrevChan(void);\n#endif\n\n"
  },
  {
    "path": "src/irc_filter.c",
    "content": "/*\n Internet Relay Chat filtering methods\n \n Copyright (C) 2011 Andrew 'dies-el' Donaldson\n \n This program is free software; you can redistribute it and/or\n modify it under the terms of the GNU General Public License\n as published by the Free Software Foundation; either version 2\n of the License, or (at your option) any later version.\n \n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n \n See the GNU General Public License for more details.\n \n You should have received a copy of the GNU General Public License\n along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifdef WITH_IRC\n\n#include \"quakedef.h\"\n\ncvar_t irc_filter_join_part_messages = {\"irc_filter_join_part_messages\", \"0\"};\ncvar_t irc_filter_quit_messages = {\"irc_filter_quit_messages\", \"0\"};\ncvar_t irc_filter_private_messages = {\"irc_filter_private_messages\", \"0\"};\ncvar_t irc_filter_notice_messages = {\"irc_filter_notice_messages\", \"0\"};\n \nvoid IRC_filter_register_cvars() {\n  Cvar_Register(&irc_filter_join_part_messages);\n  Cvar_Register(&irc_filter_quit_messages);\n  Cvar_Register(&irc_filter_private_messages);\n  Cvar_Register(&irc_filter_notice_messages);\n}\n\n// These methods are deliberately a lot broader than the cvars above.\n\nint IRC_filter_show_chanop_messages() {\n  return (irc_filter_join_part_messages.integer == 0);\n}\n\nint IRC_filter_show_connection_messages() {\n  return (irc_filter_quit_messages.integer == 0);\n}\n\nint IRC_filter_show_private_messages() {\n  return (irc_filter_private_messages.integer == 0);\n}\n\nint IRC_filter_show_notice_messages() {\n  return (irc_filter_notice_messages.integer == 0);\n}\n\n#endif\n"
  },
  {
    "path": "src/irc_filter.h",
    "content": "/*\n Internet Relay Chat filtering methods\n \n Copyright (C) 2011 Andrew 'dies-el' Donaldson\n \n This program is free software; you can redistribute it and/or\n modify it under the terms of the GNU General Public License\n as published by the Free Software Foundation; either version 2\n of the License, or (at your option) any later version.\n \n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n \n See the GNU General Public License for more details.\n \n You should have received a copy of the GNU General Public License\n along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifdef WITH_IRC\n\n// Registers the cvars that these filters rely on.\nvoid IRC_filter_register_cvars(void);\n\n// Returns true if chanop messages (join/parts/etc) are to be shown\nint IRC_filter_show_chanop_messages(void);\n\n// Returns true if connection messages (quits) are to be shown\nint IRC_filter_show_connection_messages(void);\n\n// Returns true if private messages are to be shown\nint IRC_filter_show_private_messages(void);\n\n// Returns true if notices are to be shown\nint IRC_filter_show_notice_messages(void);\n\n#endif\n"
  },
  {
    "path": "src/keys.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: keys.c,v 1.80 2007-09-26 13:53:42 tonik Exp $\n\n*/\n\n#include <wchar.h>\n#include \"quakedef.h\"\n#include \"textencoding.h\"\n#include \"menu.h\"\n#include \"keys.h\"\n#include \"input.h\"\n\n#ifdef _WIN32\n#include <windows.h>\n#endif\n\n#include \"gl_model.h\"\n#include \"tr_types.h\"\n\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"hud_editor.h\"\n#include \"demo_controls.h\"\n#include \"irc.h\"\n#include \"qtv.h\"\n#include \"utils.h\"\n\nextern cvar_t sys_disable_alt_enter;\n\n//key up events are sent even if in console mode\n\ncvar_t cl_chatmode                  = {\"cl_chatmode\", \"2\"};\ncvar_t con_funchars_mode            = {\"con_funchars_mode\", \"0\"};\ncvar_t con_tilde_mode               = {\"con_tilde_mode\", \"0\"};\n// values: old style, current + default values , current only, default only, current + default if changed, none.\ncvar_t con_completion_format        = {\"con_completion_format\", \"2\"};\ncvar_t con_hide_chat_input          = {\"con_hide_chat_input\", \"1\"};\ncvar_t con_completion_changed_mark  = {\"con_completion_changed_mark\", \"1\"};\ncvar_t con_bindphysical             = {\"con_bindphysical\", \"0\"};\ncvar_t in_builtinkeymap             = {\"in_builtinkeymap\", \"0\"};\n\nchar* escape_regex(char* string);\nvoid OnChange_con_prompt_charcode(cvar_t *var, char *string, qbool *cancel);\nvoid OnChange_con_completion_color(cvar_t *var, char *string, qbool *cancel);\ncvar_t con_prompt_charcode\t\t\t\t\t= {\"con_prompt_charcode\", \"93\", CVAR_NONE, OnChange_con_prompt_charcode};\ncvar_t con_completion_color_name\t\t\t= {\"con_completion_color_name\", \"8ff\", CVAR_NONE, OnChange_con_completion_color};\ncvar_t con_completion_color_value_current\t= {\"con_completion_color_value_current\", \"fff\", CVAR_NONE, OnChange_con_completion_color};\ncvar_t con_completion_color_value_default\t= {\"con_completion_color_value_default\", \"fff\", CVAR_NONE, OnChange_con_completion_color};\ncvar_t con_completion_color_quotes_current\t= {\"con_completion_color_quotes_current\", \"ff8\", CVAR_NONE, OnChange_con_completion_color};\ncvar_t con_completion_color_quotes_default\t= {\"con_completion_color_quotes_default\", \"ff8\", CVAR_NONE, OnChange_con_completion_color};\ncvar_t con_completion_color_colon\t\t\t= {\"con_completion_color_colon\", \"fff\", CVAR_NONE, OnChange_con_completion_color};\ncvar_t con_completion_color_title\t\t\t= {\"con_completion_color_title\", \"ff3\", CVAR_NONE, OnChange_con_completion_color};\ncvar_t con_completion_color_changed_mark\t= {\"con_completion_color_changed_mark\", \"f30\", CVAR_NONE, OnChange_con_completion_color};\ncvar_t con_completion_padding\t\t\t\t= {\"con_completion_padding\", \"2\"};\n\ncvar_t\tcl_savehistory = {\"cl_savehistory\", \"1\"};\n#define\t\tHISTORY_FILE_NAME\t\"ezquake/.ezquake_history\"\n\nwchar\tkey_lines[CMDLINES][MAXCMDLINE];\nint\t\tkey_linepos;\nint\t\tkey_lastpress;\n\nint\t\tedit_line=0;\nint\t\thistory_line=0;\n\nint del_removes;\nint key_lineposorig;\nint old_keyline_length;\nint last_cmd_length = 0;\nint called_second = 0;\nint try = 0;\nint count = 0;\nint count_cmd = 0;\nint count_cvar = 0;\nint count_alias = 0;\n\nkeydest_t\tkey_dest, key_dest_beforemm, key_dest_beforecon;\n\nchar\t*keybindings[UNKNOWN + 256];\nqbool\tconsolekeys[UNKNOWN + 256];\t// if true, can't be rebound while in console\nqbool\thudeditorkeys[UNKNOWN + 256];\t// if true, can't be rebound while in hud editor\nqbool\tdemocontrolskey[UNKNOWN + 256];\nint\t\tkeyshift[UNKNOWN + 256];\t\t// key to map to if shift held down in console\nint\t\tkey_repeats[UNKNOWN + 256];\t// if > 1, it is autorepeating\nqbool\tkeydown[UNKNOWN + 256];\nqbool\tkeyactive[UNKNOWN + 256];\n\n// SDL2 sends a KEYDOWN event and then an optional TEXTINPUT event for the actual character generated\n// This stores the last quake key (K_*) from the KEYDOWN, or 0 if any subsequent TEXTINPUT event should be ignored.\nstatic int lastKeyDown = 0;\nstatic qbool lastKeyGeneratedCharacter = false;\n\ntypedef struct\n{\n\tchar *name;\n\tchar *type;\n} jogi_avail_complete_t;\n\n\ntypedef struct {\n\tchar\t*name;\n\tint\t\tkeynum;\n} keyname_t;\n\nkeyname_t keynames[] = {\n\t{\"TAB\", K_TAB},\n\t{\"ENTER\", K_ENTER},\n\t{\"ESCAPE\", K_ESCAPE},\n\t{\"SPACE\", K_SPACE},\n\t{\"BACKSPACE\", K_BACKSPACE},\n\n\t{\"CAPSLOCK\",K_CAPSLOCK},\n\t{\"PRINTSCR\", K_PRINTSCR},\n\t{\"PRINTSCRN\", K_PRINTSCR},\n\t{\"PRINTSCREEN\", K_PRINTSCR},\n\t{\"SCRLCK\", K_SCRLCK},\n\t{\"SCROLLLOCK\", K_SCRLCK},\n\t{\"SCROLLOCK\", K_SCRLCK},\t// misspelled; kept for compatibility\n\n#ifdef __APPLE__\n\t{\"COMMAND\", K_CMD},\n\t{\"PARA\", K_PARA},\n\t{\"F13\", K_F13},\n\t{\"F14\", K_F14},\n\t{\"F15\", K_F15},\n\t{\"KP_EQUAL\", KP_EQUAL},\n#endif\n\n\t{\"PAUSE\", K_PAUSE},\n\n\t{\"UPARROW\", K_UPARROW},\n\t{\"DOWNARROW\", K_DOWNARROW},\n\t{\"LEFTARROW\", K_LEFTARROW},\n\t{\"RIGHTARROW\", K_RIGHTARROW},\n\n\t{\"ALT\", K_ALT},\n\t{\"LALT\", K_LALT},\n\t{\"RALT\", K_RALT},\n\n\t{\"ALTGR\", K_RALT},\n\t{\"ALTCHAR\", K_RALT},\n\n\t{\"CTRL\", K_CTRL},\n\t{\"LCTRL\", K_LCTRL},\n\t{\"RCTRL\", K_RCTRL},\n\t{\"SHIFT\", K_SHIFT},\n\t{\"LSHIFT\", K_LSHIFT},\n\t{\"RSHIFT\", K_RSHIFT},\n\n\t{\"WINKEY\", K_WIN},\n\t{\"LWINKEY\", K_LWIN},\n\t{\"RWINKEY\", K_RWIN},\n\t{\"POPUPMENU\", K_MENU}, //???\n\n\t// special keys\n\t{\"WIN\", K_WIN},\n\t{\"LWIN\", K_LWIN},\n\t{\"RWIN\", K_RWIN},\n\t{\"SUPER\", K_WIN},\n\t{\"LSUPER\", K_LWIN},\n\t{\"RSUPER\", K_RWIN},\n\t{\"MENU\", K_MENU},\n\t{\"ISO\", K_ISO},\n\n\t// Keypad stuff..\n\n\t{\"NUMLOCK\", KP_NUMLOCK},\n\t{\"KP_NUMLCK\", KP_NUMLOCK},\n\t{\"KP_NUMLOCK\", KP_NUMLOCK},\n\t{\"KP_SLASH\", KP_SLASH},\n\t{\"KP_DIVIDE\", KP_SLASH},\n\t{\"KP_STAR\", KP_STAR},\n\t{\"KP_MULTIPLY\", KP_STAR},\n\n\t{\"KP_MINUS\", KP_MINUS},\n\n\t{\"KP_HOME\", KP_HOME},\n\t{\"KP_7\", KP_HOME},\n\t{\"KP_UPARROW\", KP_UPARROW},\n\t{\"KP_8\", KP_UPARROW},\n\t{\"KP_PGUP\", KP_PGUP},\n\t{\"KP_9\", KP_PGUP},\n\t{\"KP_PLUS\", KP_PLUS},\n\n\t{\"KP_LEFTARROW\", KP_LEFTARROW},\n\t{\"KP_4\", KP_LEFTARROW},\n\t{\"KP_5\", KP_5},\n\t{\"KP_RIGHTARROW\", KP_RIGHTARROW},\n\t{\"KP_6\", KP_RIGHTARROW},\n\n\t{\"KP_END\", KP_END},\n\t{\"KP_1\", KP_END},\n\t{\"KP_DOWNARROW\", KP_DOWNARROW},\n\t{\"KP_2\", KP_DOWNARROW},\n\t{\"KP_PGDN\", KP_PGDN},\n\t{\"KP_3\", KP_PGDN},\n\n\t{\"KP_INS\", KP_INS},\n\t{\"KP_0\", KP_INS},\n\t{\"KP_DEL\", KP_DEL},\n\t{\"KP_ENTER\", KP_ENTER},\n\n\t{\"F1\", K_F1},\n\t{\"F2\", K_F2},\n\t{\"F3\", K_F3},\n\t{\"F4\", K_F4},\n\t{\"F5\", K_F5},\n\t{\"F6\", K_F6},\n\t{\"F7\", K_F7},\n\t{\"F8\", K_F8},\n\t{\"F9\", K_F9},\n\t{\"F10\", K_F10},\n\t{\"F11\", K_F11},\n\t{\"F12\", K_F12},\n\n\t{\"INS\", K_INS},\n\t{\"DEL\", K_DEL},\n\t{\"PGDN\", K_PGDN},\n\t{\"PGUP\", K_PGUP},\n\t{\"HOME\", K_HOME},\n\t{\"END\", K_END},\n\n\t{\"MOUSE1\", K_MOUSE1},\n\t{\"MOUSE2\", K_MOUSE2},\n\t{\"MOUSE3\", K_MOUSE3},\n\t{\"MOUSE4\", K_MOUSE4},\n\t{\"MOUSE5\", K_MOUSE5},\n\t{\"MOUSE6\", K_MOUSE6},\n\t{\"MOUSE7\", K_MOUSE7},\n\t{\"MOUSE8\", K_MOUSE8},\n\n\t{\"JOY1\", K_JOY1},\n\t{\"JOY2\", K_JOY2},\n\t{\"JOY3\", K_JOY3},\n\t{\"JOY4\", K_JOY4},\n\n\t{\"JOYPOVUP\", K_JOYPOVUP},\n\t{\"JOYPOVRT\", K_JOYPOVRT},\n\t{\"JOYPOVDN\", K_JOYPOVDN},\n\t{\"JOYPOVLT\", K_JOYPOVLT},\n\n\t{\"AUX1\", K_AUX1},\n\t{\"AUX2\", K_AUX2},\n\t{\"AUX3\", K_AUX3},\n\t{\"AUX4\", K_AUX4},\n\t{\"AUX5\", K_AUX5},\n\t{\"AUX6\", K_AUX6},\n\t{\"AUX7\", K_AUX7},\n\t{\"AUX8\", K_AUX8},\n\t{\"AUX9\", K_AUX9},\n\t{\"AUX10\", K_AUX10},\n\t{\"AUX11\", K_AUX11},\n\t{\"AUX12\", K_AUX12},\n\t{\"AUX13\", K_AUX13},\n\t{\"AUX14\", K_AUX14},\n\t{\"AUX15\", K_AUX15},\n\t{\"AUX16\", K_AUX16},\n\t{\"AUX17\", K_AUX17},\n\t{\"AUX18\", K_AUX18},\n\t{\"AUX19\", K_AUX19},\n\t{\"AUX20\", K_AUX20},\n\t{\"AUX21\", K_AUX21},\n\t{\"AUX22\", K_AUX22},\n\t{\"AUX23\", K_AUX23},\n\t{\"AUX24\", K_AUX24},\n\t{\"AUX25\", K_AUX25},\n\t{\"AUX26\", K_AUX26},\n\t{\"AUX27\", K_AUX27},\n\t{\"AUX28\", K_AUX28},\n\n\t{\"MWHEELUP\", K_MWHEELUP},\n\t{\"MWHEELDOWN\", K_MWHEELDOWN},\n\n\t{\"SEMICOLON\", ';'},\t// because a raw semicolon separates commands\n\n\t{\"SECTION\", K_SECTION},\n\t{\"ACUTE\", K_ACUTE},\n\t{\"DIAERESIS\", K_DIAERESIS},\n\t{\"ARING\", K_ARING},\n\t{\"ADIAERESIS\", K_ADIAERESIS},\n\t{\"ODIAERESIS\", K_ODIAERESIS},\n\t{\"WAKE_UP\", K_WAKE_UP},\n\n\t{NULL,0}\n};\n\nmouse_state_t scr_pointer_state;\n\n/*\n   ==============================================================================\n   Flushing my array\n   ==============================================================================\n   */\nvoid CompleteCommandNew_Reset (void)\n{\n\tif ((del_removes) || (key_linepos == key_lineposorig))\n\t{\n\t\tdel_removes = 0;\n\t\tcalled_second = 0;\n\t\ttry = 0;\n\t}\n}\n\n/*\n   ==============================================================================\n   LINE TYPING INTO THE CONSOLE\n   ==============================================================================\n   */\n\nqbool CheckForCommand (void) \n{\n\tchar command[256], *s;\n\n\tstrlcpy(command, wcs2str(key_lines[edit_line] + 1), sizeof(command));\n\tfor (s = command; *s > ' '; s++)\n\t\t;\n\t*s = 0;\n\n\treturn command[0] && (Cvar_Find(command) || Cmd_FindCommand(command) || Cmd_FindAlias(command) || Cmd_IsLegacyCommand(command));\n}\n\n//===================================================================\n//  Advanced command completion\n\n#define COLUMNWIDTH 20\n#define MINCOLUMNWIDTH 18\t// the last column may be slightly smaller\n\n\nvoid PaddedPrint (char *s) \n{\n\textern int con_linewidth;\n\textern char *str_repeat (char *str, int amount);\n\tchar *pad = NULL;\n\tint nextcolx = 0;\n\n\tif (con_completion_format.integer)  // plain list\n\t{\n\t\tpad = str_repeat(\" \", con_completion_padding.integer);\n\t\tCom_Printf (\"%s&c%s%s&r\\n\", pad, con_completion_color_name.string, s);\n\t\tQ_free(pad);\n\t}\n\telse\n\t{\n\t\tif (con.x)\n\t\t\tnextcolx = (int)((con.x + COLUMNWIDTH)/COLUMNWIDTH)*COLUMNWIDTH;\n\n\t\tif (nextcolx > con_linewidth - MINCOLUMNWIDTH || (con.x && nextcolx + strlen(s) >= con_linewidth))\n\t\t\tCom_Printf (\"\\n\");\n\n\t\tif (con.x)\n\t\t\tCom_Printf (\" \");\n\t\twhile (con.x % COLUMNWIDTH)\n\t\t\tCom_Printf (\" \");\n\t\tCom_Printf (\"%s\", s);\n\t}\n}\n\nvoid PaddedPrintMarkAndPad (qbool mark)\n{\n\textern char *str_repeat (char *str, int amount);\n\n\tchar* s;\n\tint padding = con_completion_padding.integer;\n\n\tif (mark)\n\t{\n\t\tCom_Printf (\"&c%s*\", con_completion_color_changed_mark.string);\n\t\tpadding -= 1;\n\t}\n\n\ts = str_repeat(\" \", padding);\n\tCom_Printf (\"%s\", s);\n\tQ_free(s);\n}\n\nvoid PaddedPrintValue (char *s, char *v, char *dv)  // name, value, default value\n{\n\tqbool changed = (strcmp(s, dv) != 0) && (strcmp(dv, v) != 0);\n\tqbool add_mark = (con_completion_changed_mark.integer > 0) && changed;\n\n\tswitch (con_completion_format.integer)\n\t{\n\t\tcase 1:\t// current + default\n\t\t\tif (strcmp(s, dv))\n\t\t\t{\n\t\t\t\tPaddedPrintMarkAndPad (add_mark);\n\t\t\t\tCom_Printf (\"&c%s%s &c%s:&r &c%s\\\"&c%s%s&c%s\\\"&r &c%s:&r &c%s\\\"&c%s%s&c%s\\\"&r\\n\",\n\t\t\t\t\t\tcon_completion_color_name.string, s , con_completion_color_colon.string,\n\t\t\t\t\t\tcon_completion_color_quotes_current.string, con_completion_color_value_current.string, v,\n\t\t\t\t\t\tcon_completion_color_quotes_current.string, con_completion_color_colon.string,\n\t\t\t\t\t\tcon_completion_color_quotes_default.string, con_completion_color_value_default.string, dv,\n\t\t\t\t\t\tcon_completion_color_quotes_default.string);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tPaddedPrintMarkAndPad (false);\n\t\t\t\tCom_Printf (\"&c%s%s &c%s: &c%s\\\"&c%s%s&c%s\\\"&r\\n\",\n\t\t\t\t\t\tcon_completion_color_name.string, s , con_completion_color_colon.string,\n\t\t\t\t\t\tcon_completion_color_quotes_current.string, con_completion_color_value_current.string, v,\n\t\t\t\t\t\tcon_completion_color_quotes_current.string);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 2:\t// current only\n\t\t\tPaddedPrintMarkAndPad (add_mark);\n\t\t\tCom_Printf (\"&c%s%s &c%s: &c%s\\\"&c%s%s&c%s\\\"&r\\n\",\n\t\t\t\t\tcon_completion_color_name.string, s , con_completion_color_colon.string,\n\t\t\t\t\tcon_completion_color_quotes_current.string, con_completion_color_value_current.string, v,\n\t\t\t\t\tcon_completion_color_quotes_current.string);\n\t\t\tbreak;\n\n\t\tcase 3:\t// default only\n\t\t\tPaddedPrintMarkAndPad (add_mark);\n\t\t\tCom_Printf (\"&c%s%s &c%s:&r &c%s\\\"&c%s%s&c%s\\\"&r\\n\",\n\t\t\t\t\tcon_completion_color_name.string, s, con_completion_color_colon.string,\n\t\t\t\t\tcon_completion_color_quotes_default.string, con_completion_color_value_default.string,\n\t\t\t\t\tdv, con_completion_color_quotes_default.string);\n\t\t\tbreak;\n\n\t\tcase 4:\t// current + default if changed\n\t\t\tif(changed)\n\t\t\t{\n\t\t\t\tif (strcmp(s, dv))\n\t\t\t\t{\n\t\t\t\t\tPaddedPrintMarkAndPad (add_mark);\n\t\t\t\t\tCom_Printf (\"&c%s%s &c%s:&r &c%s\\\"&c%s%s&c%s\\\"&r &c%s:&r &c%s\\\"&c%s%s&c%s\\\"&r\\n\",\n\t\t\t\t\t\t\tcon_completion_color_name.string, s , con_completion_color_colon.string,\n\t\t\t\t\t\t\tcon_completion_color_quotes_current.string, con_completion_color_value_current.string, v,\n\t\t\t\t\t\t\tcon_completion_color_quotes_current.string, con_completion_color_colon.string,\n\t\t\t\t\t\t\tcon_completion_color_quotes_default.string, con_completion_color_value_default.string, dv,\n\t\t\t\t\t\t\tcon_completion_color_quotes_default.string);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tPaddedPrintMarkAndPad (false);\n\t\t\t\t\tCom_Printf (\"&c%s%s &c%s: &c%s\\\"&c%s%s&c%s\\\"&r\\n\",\n\t\t\t\t\t\t\tcon_completion_color_name.string, s , con_completion_color_colon.string,\n\t\t\t\t\t\t\tcon_completion_color_quotes_current.string, con_completion_color_value_current.string, v,\n\t\t\t\t\t\t\tcon_completion_color_quotes_current.string);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tPaddedPrintMarkAndPad (add_mark);\n\t\t\t\tCom_Printf (\"&c%s%s &c%s: &c%s\\\"&c%s%s&c%s\\\"&r\\n\",\n\t\t\t\t\t\tcon_completion_color_name.string, s , con_completion_color_colon.string,\n\t\t\t\t\t\tcon_completion_color_quotes_current.string, con_completion_color_value_current.string, v,\n\t\t\t\t\t\tcon_completion_color_quotes_current.string);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 5:\t// none\n\t\t\tPaddedPrintMarkAndPad (add_mark);\n\t\t\tCom_Printf (\"&c%s%s&r\\n\",\n\t\t\t\t\tcon_completion_color_name.string, s);\n\t\t\tbreak;\n\n\t\tdefault: // old completion\n\t\t\tPaddedPrint(s);\n\t\t\tbreak;\n\t}\n}\n\nstatic char\tcompl_common[64];\nstatic int\tcompl_clen;\nstatic int\tcompl_len;\n\nstatic void FindCommonSubString (char *s) \n{\n\tif (!compl_clen)\n\t{\n\t\tstrlcpy (compl_common, s, sizeof(compl_common));\n\t\tcompl_clen = strlen (compl_common);\n\t} \n\telse\n\t{\n\t\twhile (compl_clen > compl_len && strncasecmp(s, compl_common, compl_clen))\n\t\t\tcompl_clen--;\n\t}\n}\n\nint Cmd_CompleteCountPossible (char *partial);\nint Cmd_AliasCompleteCountPossible (char *partial);\nint Cvar_CompleteCountPossible (char *partial);\nvoid CompleteName(void);\n\nextern cmd_function_t *cmd_functions;\nextern cmd_alias_t *cmd_alias;\nextern cvar_t *cvar_vars;\n\nvoid CompleteCommandNew (void)\n{\n\tchar *cmd, token[MAXCMDLINE], *s, *escaped;\n\twchar temp[MAXCMDLINE];\n\tint c, a, v, start, end, i, diff_len, size, my_string_length;\n\n\tchar completebuff[MAXCMDLINE];\n\tcmd_alias_t *s_a;\n\tcmd_function_t *s_c;\n\tcvar_t *s_v;\n\tint s_count;\n\n\tstatic cmd_alias_t *sorted_aliases[4096];\n\tstatic cmd_function_t *sorted_cmds[4096];\n\tstatic cvar_t *sorted_cvars[4096];\n\tstatic jogi_avail_complete_t jogi_avail_complete[4096];\n\n#define MAX_SORTED_ALIASES (sizeof(sorted_aliases) / sizeof(sorted_aliases[0]))\n#define MAX_SORTED_CVARS (sizeof (sorted_cvars) / sizeof (sorted_cvars[0]))\n#define MAX_SORTED_CMDS (sizeof (sorted_cmds) / sizeof (sorted_cmds[0]))\n\n\textern int Cmd_AliasCompare (const void *,const void *);\n\textern int Cmd_CommandCompare (const void *, const void *);\n\textern int Cvar_CvarCompare (const void *, const void *);\n\n\tif (!\n\t\t\t(key_linepos < 2\n\t\t\t || isspace (key_lines[edit_line][key_linepos - 1]))\n\t\t\t&&  !called_second)\n\t{\n\n\t\tcount = 0;\n\t\tcount_cmd = 0;\n\t\tcount_cvar = 0;\n\t\tcount_alias = 0;\n\t\tmemset(jogi_avail_complete, 0, sizeof(jogi_avail_complete));\n\n\t\tcalled_second = 1;\n\t\tif (key_linepos < 2 || isspace (key_lines[edit_line][key_linepos - 1]))\n\t\t\treturn;\n\n\t\tfor (start = key_linepos - 1;\n\t\t\t\tstart >= 1 && !isspace (key_lines[edit_line][start]);\n\t\t\t\tstart--)\n\t\t\t;\n\t\tif (start == 0)\n\t\t\tstart = 1;\n\t\tif (isspace (key_lines[edit_line][start]))\n\t\t\tstart++;\n\t\tend = key_linepos - 1;\n\n\t\tsize = min (end - start + 1, sizeof (token) - 1);\n\t\tmemcpy (token, wcs2str(&key_lines[edit_line][start]), size);\n\t\ttoken[size] = 0;\n\n\t\ts = token;\n\t\tif (*s == '\\\\' || *s == '/' || *s == '$')\n\t\t{\n\t\t\ts++;\n\t\t\tstart++;\n\t\t}\n\t\tif (start > end)\n\t\t\treturn;\n\n\t\tcompl_len = strlen (s);\n\t\tcompl_clen = 0;\n\n\t\tc = Cmd_CompleteCountPossible (s);\n\t\ta = Cmd_AliasCompleteCountPossible (s);\n\t\tv = Cvar_CompleteCountPossible (s);\n\n\t\tif (c + a + v > 1)\n\t\t{\n\t\t\tCom_Printf (\"\\n\");\n\n\t\t\tif (c)\n\t\t\t{\n\t\t\t\tlegacycmd_t* lcmd;\n\n\t\t\t\tif (con_completion_format.integer) {\n\t\t\t\t\tCom_Printf(\"&c%sCommands (%d):&r\\n\", con_completion_color_title.string, c);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tCom_Printf(\"\\x02\" \"Commands (%d):\\n\", c);\n\t\t\t\t}\n\n\t\t\t\tescaped = escape_regex(s);\n\t\t\t\tsnprintf(completebuff, sizeof(completebuff), \"^((?i)%s)\", escaped);\n\t\t\t\tQ_free(escaped);\n\n\t\t\t\tfor (s_c = cmd_functions, s_count = 0; s_c && s_count < MAX_SORTED_CMDS; s_c = s_c->next) {\n\t\t\t\t\tif (Utils_RegExpMatch(completebuff, s_c->name)) {\n\t\t\t\t\t\tsorted_cmds[s_count++] = s_c;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Legacy commands\n\t\t\t\tfor (lcmd = legacycmds; lcmd; lcmd = lcmd->next) {\n\t\t\t\t\tif (Utils_RegExpMatch(completebuff, lcmd->dummy_cmd.name)) {\n\t\t\t\t\t\tsorted_cmds[s_count++] = &lcmd->dummy_cmd;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tqsort(sorted_cmds, s_count, sizeof (cmd_function_t *), Cmd_CommandCompare);\n\n\t\t\t\tfor (i = 0; i < s_count; i++) {\n\t\t\t\t\tPaddedPrint (sorted_cmds[i]->name);\n\t\t\t\t\tFindCommonSubString (sorted_cmds[i]->name);\n\t\t\t\t\tjogi_avail_complete[count].name = sorted_cmds[i]->name;\n\t\t\t\t\tjogi_avail_complete[count].type = \"command\";\n\t\t\t\t\tcount++;\n\t\t\t\t\tcount_cmd++;\n\t\t\t\t}\n\n\t\t\t\tif (con.x)\n\t\t\t\t\tCom_Printf (\"\\n\");\n\t\t\t}\n\n\t\t\tif (v)\n\t\t\t{\n\t\t\t\tif (con_completion_format.integer)\n\t\t\t\t\tCom_Printf (\"&c%sVariables (%d):&r\\n\", con_completion_color_title.string, v);\n\t\t\t\telse\n\t\t\t\t\tCom_Printf (\"\\x02\" \"Variables (%d):\\n\", v);\n\n\t\t\t\tescaped = escape_regex(s);\n\t\t\t\tsnprintf(completebuff, sizeof(completebuff), \"^((?i)%s)\", escaped);\n\t\t\t\tQ_free(escaped);\n\n\t\t\t\tfor (s_v = cvar_vars, s_count = 0; s_v && s_count < MAX_SORTED_CVARS; s_v = s_v->next) {\n\t\t\t\t\tif (Utils_RegExpMatch(completebuff, s_v->name)) {\n\t\t\t\t\t\tsorted_cvars[s_count++] = s_v;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tqsort(sorted_cvars, s_count, sizeof (cvar_t *), Cvar_CvarCompare);\n\n\t\t\t\tfor (i = 0; i < s_count; i++) {\n\t\t\t\t\tPaddedPrintValue (sorted_cvars[i]->name, sorted_cvars[i]->string, sorted_cvars[i]->defaultvalue);\n\t\t\t\t\tFindCommonSubString (sorted_cvars[i]->name);\n\t\t\t\t\tjogi_avail_complete[count].name = sorted_cvars[i]->name;\n\t\t\t\t\tjogi_avail_complete[count].type = \"variable\";\n\t\t\t\t\tcount++;\n\t\t\t\t\tcount_cvar++;\n\t\t\t\t}\n\n\t\t\t\tif (con.x)\n\t\t\t\t\tCom_Printf (\"\\n\");\n\t\t\t}\n\n\t\t\tif (a)\n\t\t\t{\n\t\t\t\tif (con_completion_format.integer)\n\t\t\t\t\tCom_Printf (\"&c%sAliases (%d):&r\\n\", con_completion_color_title.string, a);\n\t\t\t\telse\n\t\t\t\t\tCom_Printf (\"\\x02\" \"Aliases (%d):\\n\", a);\n\n\t\t\t\tescaped = escape_regex(s);\n\t\t\t\tsnprintf(completebuff, sizeof(completebuff), \"^((?i)%s)\", escaped);\n\t\t\t\tQ_free(escaped);\n\n\t\t\t\tfor (s_a = cmd_alias, s_count = 0; s_a && s_count < MAX_SORTED_ALIASES; s_a = s_a->next) {\n\t\t\t\t\tif (Utils_RegExpMatch(completebuff, s_a->name)) {\n\t\t\t\t\t\tsorted_aliases[s_count++] = s_a;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tqsort(sorted_aliases, s_count, sizeof (cmd_alias_t *), Cmd_AliasCompare);\t\t\t\t\n\n\t\t\t\tfor (i = 0; i < s_count; i++) {\n\t\t\t\t\tPaddedPrintValue(sorted_aliases[i]->name, sorted_aliases[i]->value, sorted_aliases[i]->name);\n\t\t\t\t\tFindCommonSubString(sorted_aliases[i]->name);\n\t\t\t\t\tjogi_avail_complete[count].name = sorted_aliases[i]->name;\n\t\t\t\t\tjogi_avail_complete[count].type = \"alias\";\n\t\t\t\t\tcount++;\n\t\t\t\t\tcount_alias++;\n\t\t\t\t}\n\n\t\t\t\tif (con.x) {\n\t\t\t\t\tCom_Printf(\"\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (c + a + v == 1)\n\t\t{\n\t\t\tcmd = Cmd_CompleteCommand (s);\n\t\t\tif (!cmd)\n\t\t\t\tcmd = Cvar_CompleteVariable (s);\n\t\t\tif (!cmd)\n\t\t\t\treturn;\t// this should never happen\n\t\t}\n\t\telse if (compl_clen)\n\t\t{\n\t\t\tcompl_common[compl_clen] = 0;\n\t\t\tcmd = compl_common;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCompleteName ();\n\t\t\treturn;\n\t\t}\n\n\t\tdiff_len = strlen (cmd) - (end - start + 1);\n\t\tqwcslcpy (temp, key_lines[edit_line] + end + 1, sizeof(temp)/sizeof(temp[0]));\n\t\tqwcslcpy (key_lines[edit_line] + end + 1 + diff_len, temp, MAXCMDLINE - (end + 1 + diff_len));\n\n\t\tfor (i = 0; start + i < MAXCMDLINE && i < strlen (cmd); i++)\n\t\t{\n\t\t\tkey_lines[edit_line][start + i] = char2wc(cmd[i]);\n\t\t}\n\n\t\tkey_linepos += diff_len;\n\t\tkey_lines[edit_line][min\n\t\t\t(key_linepos + qwcslen(temp),\n\t\t\t MAXCMDLINE - 1)] = 0;\n\t\tif (start == 1 && key_linepos + qwcslen(temp) < MAXCMDLINE - 1)\n\t\t{\n\t\t\tfor (i = key_linepos + qwcslen(temp); i > 0; i--)\n\t\t\t{\n\t\t\t\tkey_lines[edit_line][i + 1] = key_lines[edit_line][i];\n\t\t\t}\n\t\t\tkey_lines[edit_line][1] = '/';\n\t\t\tkey_linepos++;\n\t\t}\n\t\tif (c + a + v == 1 && !key_lines[edit_line][key_linepos] && key_linepos < MAXCMDLINE - 1)\n\t\t{\n\t\t\tkey_lines[edit_line][key_linepos] = ' ';\n\t\t\tkey_lines[edit_line][++key_linepos] = 0;\n\t\t}\n\n\t\twhile (!(isspace (key_lines[edit_line][key_linepos])) && (key_lines[edit_line][key_linepos] != '\\0'))\n\t\t{\n\t\t\tqwcscpy (key_lines[edit_line] + key_linepos,\n\t\t\t\t\tkey_lines[edit_line] + key_linepos + 1);\n\t\t}\n\n\t\tkey_lineposorig = key_linepos;\n\t\ttry = 0;\n\t\tlast_cmd_length = 0;\n\t\told_keyline_length = qwcslen( key_lines[edit_line] );\n\t}\n\telse if ((key_linepos >= 2\n\t\t\t\t|| isspace (key_lines[edit_line][key_linepos - 1]))\n\t\t\t&&  called_second\n\t\t\t&& (key_linepos == key_lineposorig) && (old_keyline_length == (int) qwcslen(key_lines[edit_line])))\n\t{\n\t\tif (count != try)\n\t\t{\n\t\t\tint len;\n\t\t\tchar text[50];\n\t\t\tint test;\n\t\t\tint testvar;\n\t\t\ttry++;\n\t\t\t//Com_Printf(\"%i\\n\",try);\n\t\t\ttest = try - 1;\n\t\t\ttestvar = key_linepos;\n\n\t\t\twhile ((testvar != 0)\n\t\t\t\t\t&& !(isspace (key_lines[edit_line][testvar]))\n\t\t\t\t\t&& (key_lines[edit_line][testvar] != '\\\\')\n\t\t\t\t\t&& (key_lines[edit_line][testvar] != '$')\n\t\t\t\t\t&& (key_lines[edit_line][testvar] != '/'))\n\t\t\t{\n\t\t\t\ttestvar--;\n\t\t\t}\n\n\t\t\ttestvar = key_linepos - testvar;\n\n\t\t\tmy_string_length =\tstrlen (jogi_avail_complete[test].name);\n\n\t\t\tmemset(text, 0, sizeof(text));\n\n\t\t\tfor (i = 0; i < bound(0, my_string_length-testvar+1, sizeof(text)); i++)\n\t\t\t{\n\t\t\t\ttext[i] = jogi_avail_complete[test].name[i + testvar -1 ];\n\t\t\t}\n\n\t\t\tlen = strlen (text);\n\n\t\t\tmemmove (key_lines[edit_line] + key_linepos + len,\n\t\t\t\t\tkey_lines[edit_line] + key_linepos +\n\t\t\t\t\tlast_cmd_length,\n\t\t\t\t\t(MAXCMDLINE - key_linepos + 1 -\n\t\t\t\t\t last_cmd_length)*sizeof(wchar));\n\t\t\tmemcpy (key_lines[edit_line] + key_linepos, str2wcs(text),\n\t\t\t\t\tlen * sizeof(wchar));\n\n\t\t\tdel_removes = 1;\n\t\t\tlast_cmd_length = strlen (text);\n\t\t}\n\t\telse if (count == try)\n\t\t{\n\t\t\ttry = 0;\n\t\t}\n\t\told_keyline_length = qwcslen( key_lines[edit_line] );\n\t}\n\telse if ((key_linepos >= 2\n\t\t\t\t|| isspace (key_lines[edit_line][key_linepos - 1]))\n\t\t\t&& called_second\n\t\t\t&& (key_linepos != key_lineposorig))\n\t{\n\t\ttry = 0;\n\t\tcalled_second = 0;\n\t\tdel_removes = 0;\n\t\tCompleteCommandNew ();\n\t}\n}\n\nstatic void CompleteName_InsertNick(const char *name, wchar *s, wchar *p, wchar *q)\n{\n\tint i, diff;\n\twchar t[MAXCMDLINE];\n\n\tqwcslcpy(t, str2wcs(name), sizeof(t)/sizeof(t[0]));\n\n\tfor (i = 0; t[i]; i++)\n\t{\n\t\tif ((127 & t[i]) == ' ')\n\t\t{\n\t\t\tint k;\n\n\t\t\tif ((k = qwcslen(t)) < MAXCMDLINE - 2)\n\t\t\t{\n\t\t\t\tmemmove(t + 1, t, (k + 1)*sizeof(wchar));\n\t\t\t\tt[k + 2] = 0;\n\t\t\t\tt[k + 1] = t[0] = '\\\"';\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\tdiff = qwcslen(t) - qwcslen(s);\n\n\tmemmove(q + diff, q, (qwcslen(q) + 1)*sizeof(wchar));\n\tmemmove(p, t, qwcslen(t)*sizeof(wchar));\n\tkey_linepos += diff;\n\tif (!key_lines[edit_line][key_linepos] && key_linepos < MAXCMDLINE - 1)\n\t{\n\t\tkey_lines[edit_line][key_linepos] = ' ';\n\t\tkey_lines[edit_line][++key_linepos] = 0;\n\t}\n}\n\n\nvoid CompleteName(void)\n{\n\textern const char disallowed_in_nick[]; // utils.c\n\n\twchar s[MAXCMDLINE], *p, *q;\n\tchar name[128] = {0};\n\n\tp = q = key_lines[edit_line] + key_linepos;\n\twhile (--p >= key_lines[edit_line] + 1)\n\t\tif (!(  (*(signed short *)p >= 32) && !strchr(disallowed_in_nick, wc2char(*p)) ))\n\t\t\tbreak;\n\tp++;\n\tif (q - p <= 0)\n\t\treturn;\n\n\tqwcslcpy (s, p, q - p + 1);\n\n\tif (FindBestNick(wcs2str(s), 0, name, sizeof(name)))\n\t\tCompleteName_InsertNick(name, s, p, q);\n}\n\n//===================================================================\n\nstatic void AdjustConsoleHeight (int delta) {\n\textern cvar_t scr_consize;\n\tint height;\n\n\tif (cls.state != ca_active && !cl.intermission)\n\t\treturn;\n\theight = (scr_consize.value * vid.height + delta + 5) / 10;\n\theight *= 10;\n\tif (delta < 0 && height < 30)\n\t\theight = 30;\n\tif (delta > 0 && height > vid.height - 10)\n\t\theight = vid.height - 10;\n\tCvar_SetValue (&scr_consize, (float)height / vid.height);\n}\n\nstatic qbool yellowchars = false;\nqbool con_redchars    = false;\n\n\n// 1) clear any typing\n// 2) put what was typed in console to the history if any\nvoid Key_ClearTyping (void)\n{\n\t//if new input is the same as previous one or the line is empty\n\t// do not increment edit_line\n\tif((wcscmp ((wchar_t*)key_lines[edit_line], (wchar_t*)key_lines[(edit_line - 1) & (CMDLINES - 1)]))\n\t\t\t&& key_lines[edit_line][1])\n\t\tedit_line = (edit_line + 1) & (CMDLINES - 1);\n\n\thistory_line = edit_line;\n\tkey_lines[edit_line][0] = con_prompt_charcode.integer;\n\tkey_lines[edit_line][1] = 0;\n\tkey_linepos = 1;\n}\n\nvoid Key_Console_Backspace(void)\n{\n\tif (key_linepos > 1)\n\t{\n\t\tqwcscpy(key_lines[edit_line] + key_linepos - 1, key_lines[edit_line] + key_linepos);\n\t\tkey_linepos--;\n\t}\n}\n\n//\n// Interactive line editing and console scrollback.\n//\nvoid Key_Console (int key, int unichar)\n{\n\tint i, len;\n\tqbool print_in_console = true;\n\n\tswitch (key)\n\t{\n\t\tcase 'M': case 'm': case 'J': case 'j':\t\t//^M,^J = Enter\n\t\t\t{\n\t\t\t\tif (!keydown[K_CTRL])\n\t\t\t\t\tbreak;\n\t\t\t\t// Fall through.\n\t\t\t}\n\t\tcase K_ENTER:\n\t\t\t{\n\t\t\t\tCompleteCommandNew_Reset ();\n\t\t\t\tcon_redchars = false;\n\n\t\t\t\t// Backslash text are commands.\n\t\t\t\tif (key_lines[edit_line][1] != '/' || key_lines[edit_line][2] != '/')\n\t\t\t\t{\n\t\t\t\t\tqbool no_lf = true;\n\n\t\t\t\t\t// If Ctrl+enter was pressed, regard the chat as a team message.\n\t\t\t\t\tif (((keydown[K_CTRL] && key == K_ENTER) || keydown[K_SHIFT]) && cls.state >= ca_connected)\n\t\t\t\t\t{\n\t\t\t\t\t\t// Do we have something to say?\n\t\t\t\t\t\tif (*(key_lines[edit_line] + 1)) \n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tCbuf_AddText ((keydown[K_CTRL] && key == K_ENTER) ? \"say_team \\\"\" : \"say \\\"\");\n\t\t\t\t\t\t\tCbuf_AddText (encode_say(key_lines[edit_line] + 1));\n\t\t\t\t\t\t\tCbuf_AddText (\"\\\"\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tno_lf = false;\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tif (key_lines[edit_line][1] == '\\\\' || key_lines[edit_line][1] == '/')\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tCbuf_AddText (encode_say(key_lines[edit_line] + 2));\t// skip the ]/\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Check if it's a chat message or a command.\n\t\t\t\t\t\t\tif (cls.state == ca_disconnected || (cl_chatmode.value != 1 && CheckForCommand()))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tCbuf_AddText (encode_say(key_lines[edit_line] + 1));\t// Valid command.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// It's a chat message.\n\t\t\t\t\t\t\t\tif (cls.state >= ca_connected)\t// Can happen if cl_chatmode is 1\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tif (cl_chatmode.value == 2 || cl_chatmode.value == 1)\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tif (*(key_lines[edit_line] + 1)) // Do we have something to say?\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tCbuf_AddText (\"say \\\"\");\n\t\t\t\t\t\t\t\t\t\t\tCbuf_AddText (encode_say(key_lines[edit_line] + 1));\n\t\t\t\t\t\t\t\t\t\t\tCbuf_AddText (\"\\\"\");\n\n\t\t\t\t\t\t\t\t\t\t\tif (con_hide_chat_input.integer)\n\t\t\t\t\t\t\t\t\t\t\t\tprint_in_console = false;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tno_lf = false;\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tCbuf_AddText (encode_say(key_lines[edit_line] + 1));\t// skip the ]\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tno_lf = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (no_lf)\n\t\t\t\t\t{\n\t\t\t\t\t\tCbuf_AddText (\"\\n\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// cut'n'paste rocks!\n\t\t\t\tif (!print_in_console)\n\t\t\t\t\tPrint_flags[Print_current] |= PR_SKIP;\n\n\t\t\t\tCon_PrintW (key_lines[edit_line]);\t// FIXME logging\n\n\t\t\t\t// cut'n'paste rocks!\n\t\t\t\tif (!print_in_console)\n\t\t\t\t\tPrint_flags[Print_current] |= PR_SKIP;\n\n\t\t\t\tCon_PrintW (str2wcs(\"\\n\"));\n\n\t\t\t\tKey_ClearTyping ();\n\n\t\t\t\tif (cls.state == ca_disconnected)\n\t\t\t\t\tSCR_UpdateScreen ();\t// force an update, because the command\n\t\t\t\t// may take some time\n\t\t\t\treturn;\n\t\t\t}\n\t\tcase K_TAB:\n\t\t\t{\n\t\t\t\t// Command completion\n\t\t\t\tif (!(keydown[K_SHIFT]) && keydown[K_CTRL])\n\t\t\t\t{\n\t\t\t\t\tCompleteName ();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tCompleteCommandNew ();\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\tcase 'H': case 'h':\t\t// ^H = BACKSPACE\n\t\t\t{\n\t\t\t\tif (!keydown[K_CTRL] || keydown[K_ALT])\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\tcase K_BACKSPACE:\n\t\t\t{\n\t\t\t\t// added by jogi start\n\t\t\t\tCompleteCommandNew_Reset();\n\t\t\t\t// added by jogi stop\n\n\t\t\t\t// removes word before cursor\n\t\t\t\tif(keydown[K_CTRL])\n\t\t\t\t{\n\t\t\t\t\tlen = 0;\n\n\t\t\t\t\twhile(key_linepos > 1 && key_lines[edit_line][key_linepos - 1] == ' ')\n\t\t\t\t\t{\n\t\t\t\t\t\tkey_linepos--;\n\t\t\t\t\t\tlen++;\n\t\t\t\t\t}\n\n\t\t\t\t\twhile(key_linepos > 1 && key_lines[edit_line][key_linepos - 1] != ' ')\n\t\t\t\t\t{\n\t\t\t\t\t\tkey_linepos--;\n\t\t\t\t\t\tlen++;\n\t\t\t\t\t}\n\n\t\t\t\t\t// remove spaces after this word leaving only last one\n\t\t\t\t\twhile(key_linepos < qwcslen(key_lines[edit_line]) && key_lines[edit_line][key_linepos - 1] == ' ' && key_lines[edit_line][key_linepos - 2] == ' ')\n\t\t\t\t\t{\n\t\t\t\t\t\tkey_linepos--;\n\t\t\t\t\t\tlen++;\n\t\t\t\t\t}\n\n\t\t\t\t\tqwcscpy(key_lines[edit_line] + key_linepos, key_lines[edit_line] + key_linepos + len);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tKey_Console_Backspace();\n\t\t\t\t}\n\n\t\t\t\t// disable red chars mode if the last character was deleted\n\t\t\t\tif(key_linepos == 1 && qwcslen(key_lines[edit_line]) == 1)\n\t\t\t\t\tcon_redchars = false;\n\n\t\t\t\treturn;\n\t\t\t}\n\t\tcase K_DEL:\n\t\t\t{\n\t\t\t\t// added by jogi start\n\t\t\t\tif((del_removes) && (key_linepos == key_lineposorig))\n\t\t\t\t{\n\t\t\t\t\tqwcscpy(key_lines[edit_line] + key_linepos, key_lines[edit_line] + key_linepos + last_cmd_length);\n\n\t\t\t\t\tdel_removes = 0;\n\t\t\t\t\tcalled_second = 0;\n\t\t\t\t\ttry = 0;\n\t\t\t\t}\n\t\t\t\t// added by jogi stopp\n\n\t\t\t\t// removes word after cursor\n\t\t\t\tif(keydown[K_CTRL])\n\t\t\t\t{\n\t\t\t\t\ti = key_linepos;\n\t\t\t\t\tlen = 0;\t\t\t\t\n\n\t\t\t\t\twhile(key_linepos < qwcslen(key_lines[edit_line]) && key_lines[edit_line][i] == ' ')\n\t\t\t\t\t{\n\t\t\t\t\t\ti++;\n\t\t\t\t\t\tlen++;\n\t\t\t\t\t}\n\n\t\t\t\t\t// don't remove regular chars if we removed spaces before\n\t\t\t\t\tif(len == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\twhile(key_linepos < qwcslen(key_lines[edit_line]) && key_lines[edit_line][i] != ' ' && key_lines[edit_line][i] != 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ti++;\n\t\t\t\t\t\t\tlen++;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// remove spaces after this word\n\t\t\t\t\t\twhile(key_linepos < qwcslen(key_lines[edit_line]) && key_lines[edit_line][i] == ' ')\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ti++;\n\t\t\t\t\t\t\tlen++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tqwcscpy(key_lines[edit_line] + key_linepos, key_lines[edit_line] + key_linepos + len);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif(key_linepos < qwcslen(key_lines[edit_line]))\n\t\t\t\t\t{\n\t\t\t\t\t\tqwcscpy(key_lines[edit_line] + key_linepos, key_lines[edit_line] + key_linepos + 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// disable red chars mode if the last character was deleted\n\t\t\t\tif (key_linepos == 1 && qwcslen(key_lines[edit_line]) == 1)\n\t\t\t\t\tcon_redchars = false;\n\n\t\t\t\treturn;\n\t\t\t}\n\t\tcase K_RIGHTARROW:\n\t\t\t{\n\t\t\t\tif (keydown[K_CTRL])\n\t\t\t\t{\n\t\t\t\t\t// word right\n\t\t\t\t\ti = qwcslen(key_lines[edit_line]);\n\t\t\t\t\twhile (key_linepos < i\n\t\t\t\t\t\t\t&& key_lines[edit_line][key_linepos] != ' ')\n\t\t\t\t\t\tkey_linepos++;\n\t\t\t\t\twhile (key_linepos < i\n\t\t\t\t\t\t\t&& key_lines[edit_line][key_linepos] == ' ')\n\t\t\t\t\t\tkey_linepos++;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// added by jogi start\n\t\t\t\tCompleteCommandNew_Reset();\n\t\t\t\t// added by jogi stop\n\t\t\t\tif (key_linepos < qwcslen(key_lines[edit_line]))\n\t\t\t\t\tkey_linepos++;\n\t\t\t\treturn;\n\t\t\t}\n\t\tcase K_LEFTARROW:\n\t\t\t{\n\t\t\t\tif (keydown[K_CTRL])\n\t\t\t\t{\n\t\t\t\t\t// word left\n\t\t\t\t\twhile (key_linepos > 1\n\t\t\t\t\t\t\t&& key_lines[edit_line][key_linepos - 1] ==\n\t\t\t\t\t\t\t' ')\n\t\t\t\t\t\tkey_linepos--;\n\t\t\t\t\twhile (key_linepos > 1\n\t\t\t\t\t\t\t&& key_lines[edit_line][key_linepos - 1] !=\n\t\t\t\t\t\t\t' ')\n\t\t\t\t\t\tkey_linepos--;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// addeded by jogi start\n\t\t\t\tCompleteCommandNew_Reset();\n\t\t\t\t// addeded by jogi start\n\n\t\t\t\tif (key_linepos > 1)\n\t\t\t\t\tkey_linepos--;\n\t\t\t\treturn;\n\t\t\t}\n\t\tcase 'P': case 'p':\t\t// ^P = back in history\n\t\t\t{\n\t\t\t\tif (!keydown[K_CTRL] || keydown[K_ALT])\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\tcase K_UPARROW:\n\t\t\t{\n\t\t\t\tif (key == K_UPARROW && keydown[K_CTRL])\n\t\t\t\t{\n\t\t\t\t\tAdjustConsoleHeight (-10);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\thistory_line = (history_line - 1) & (CMDLINES - 1);\n\t\t\t\t} while (history_line != edit_line && !key_lines[history_line][1]);\n\n\t\t\t\tif (history_line == edit_line)\n\t\t\t\t{\n\t\t\t\t\thistory_line = (edit_line + 1) & (CMDLINES - 1);\n\t\t\t\t}\n\n\t\t\t\tqwcscpy(key_lines[edit_line], key_lines[history_line]);\n\t\t\t\tkey_linepos = qwcslen(key_lines[edit_line]);\n\t\t\t\treturn;\n\t\t\t}\n\t\tcase 'N': case 'n':\t\t// ^N = forward in history\n\t\t\t{\n\t\t\t\tif (!keydown[K_CTRL] || keydown[K_ALT])\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\tcase K_DOWNARROW:\n\t\t\t{\n\t\t\t\tif (key == K_DOWNARROW && keydown[K_CTRL])\n\t\t\t\t{\n\t\t\t\t\tAdjustConsoleHeight (10);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (history_line == edit_line)\n\t\t\t\t{\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\thistory_line = (history_line + 1) & (CMDLINES - 1);\n\t\t\t\t} while (history_line != edit_line && !key_lines[history_line][1]);\n\n\t\t\t\tif (history_line == edit_line)\n\t\t\t\t{\n\t\t\t\t\tkey_lines[edit_line][0] = con_prompt_charcode.integer;\n\t\t\t\t\tkey_lines[edit_line][1] = 0;\n\t\t\t\t\tkey_linepos = 1;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tqwcscpy(key_lines[edit_line], key_lines[history_line]);\n\t\t\t\t\tkey_linepos = qwcslen(key_lines[edit_line]);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\tcase K_PGUP:\n\t\tcase K_MWHEELUP:\n\t\t\t{\n\t\t\t\tif (keydown[K_CTRL] && key == K_PGUP)\n\t\t\t\t{\n\t\t\t\t\tcon.display -= ((int)scr_conlines - 22) >> 3;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tcon.display -= 2;\n\t\t\t\t}\n\n\t\t\t\tif (con.display - con.current + con.numlines < 0)\n\t\t\t\t{\n\t\t\t\t\tcon.display = con.current - con.numlines;\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\tcase K_MWHEELDOWN:\n\t\tcase K_PGDN:\n\t\t\t{\n\t\t\t\tif (keydown[K_CTRL] && key == K_PGDN)\n\t\t\t\t{\n\t\t\t\t\tcon.display += ((int)scr_conlines - 22) >> 3;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tcon.display += 2;\n\t\t\t\t}\n\n\t\t\t\tif (con.display - con.current > 0)\n\t\t\t\t{\n\t\t\t\t\tcon.display = con.current;\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\tcase K_HOME:\n\t\t\t{\n\t\t\t\tif (keydown[K_CTRL])\n\t\t\t\t{\n\t\t\t\t\tcon.display = con.current - con.numlines;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tkey_linepos = 1;\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\tcase K_END:\n\t\t\t{\n\t\t\t\tif (keydown[K_CTRL])\n\t\t\t\t{\n\t\t\t\t\tcon.display = con.current;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tkey_linepos = qwcslen(key_lines[edit_line]);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t}\n\n\tif (((key == 'V' || key == 'v') && \n#ifndef __APPLE__\n\t\t\t\tkeydown[K_CTRL]\n#else\n\t\t\t\tkeydown[K_CMD]\n#endif\n\t\t\t\t&& !keydown[K_ALT])\n\t\t\t|| ((key == K_INS || key == KP_INS) && keydown[K_SHIFT]))\n\t{\n\t\twchar *clipText;\n\n\t\tif ((clipText = Sys_GetClipboardTextW()))\n\t\t{\n\t\t\tlen = qwcslen(clipText);\n\t\t\tif (len + qwcslen(key_lines[edit_line]) > MAXCMDLINE - 1)\n\t\t\t{\n\t\t\t\tlen = MAXCMDLINE - 1 - qwcslen(key_lines[edit_line]);\n\t\t\t}\n\n\t\t\tif (len > 0)\n\t\t\t{\n\t\t\t\t// Insert the string.\n\t\t\t\tmemmove (key_lines[edit_line] + key_linepos + len,\n\t\t\t\t\t\tkey_lines[edit_line] + key_linepos, (qwcslen(key_lines[edit_line]) - key_linepos + 1)*sizeof(wchar));\n\t\t\t\tmemcpy (key_lines[edit_line] + key_linepos, clipText, len*sizeof(wchar));\n\t\t\t\tkey_linepos += len;\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\tif (key == 'u' && keydown[K_CTRL] && !keydown[K_ALT])\n\t{\n\t\tif (key_linepos > 1)\n\t\t{\n\t\t\tqwcscpy(key_lines[edit_line] + 1, key_lines[edit_line] + key_linepos);\n\t\t\tkey_linepos = 1;\n\t\t}\n\t\treturn;\n\t}\n\n\tif (!unichar)\n\t\treturn;\t// non printable\n\n\tif (con_funchars_mode.value)\n\t{\n\t\t// CTRL+y toggles yellowchars\n\t\tif (keydown[K_CTRL] && key == 'y' && !keydown[K_RALT] && !keydown[K_ALT]) {\n\t\t\tyellowchars = !yellowchars;\n\t\t\tcon_redchars    = false;\n\t\t\tCom_Printf( \"input of yellow numbers is now o%s!\\n\", yellowchars ? \"n\" : \"ff\" );\n\t\t\treturn;\n\t\t}\n\n\t\t// CTRL+r toggles red chars\n\t\tif (keydown[K_CTRL] && key == 'r' && !keydown[K_RALT] && !keydown[K_ALT]) {\n\t\t\tcon_redchars    = !con_redchars;\n\t\t\tif ( yellowchars )\n\t\t\t{\n\t\t\t\tCom_Printf( \"input of yellow numbers is now off!\\n\" );\n\t\t\t}\n\t\t\tyellowchars = false;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif ( yellowchars || (keydown[K_CTRL] && !(con_funchars_mode.value)))\n\t{\n\t\tif (unichar >= '0' && unichar <= '9')\n\t\t{\n\t\t\tunichar = unichar - '0' + 0x12;\t// yellow number\n\t\t}\n\t\telse\n\t\t{\n\t\t\tswitch (unichar)\n\t\t\t{\n\t\t\t\tcase '[': unichar = 0x10; break;\n\t\t\t\tcase ']': unichar = 0x11; break;\n\t\t\t\tcase 'g': unichar = 0x86; break; // ctrl+g green led\n\t\t\t\tcase 'r': unichar = 0x87; break; // ctrl+r red led\n\t\t\t\tcase 'y': unichar = 0x88; break; // ctrl+y yellow led\n\t\t\t\tcase 'b': unichar = 0x89; break; // ctrl+b blue led\n\t\t\t\tcase 'w': unichar = 0x84; break; // ctrl+w white led\n\t\t\t\tcase '(': unichar = 0x80; break;\n\t\t\t\tcase '=': unichar = 0x81; break;\n\t\t\t\tcase ')': unichar = 0x82; break;\n\t\t\t\tcase 'a': unichar = 0x83; break;\n\t\t\t\tcase '<': unichar = 0x1d; break;\n\t\t\t\tcase '-': unichar = 0x1e; break;\n\t\t\t\tcase '>': unichar = 0x1f; break;\n\t\t\t\tcase ',': unichar = 0x1c; break;\n\t\t\t\tcase '.': unichar = 0x9c; break;\n\t\t\t\tcase 'B': unichar = 0x8b; break;\n\t\t\t\tcase 'C': unichar = 0x8d; break;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (con_redchars || (keydown[K_ALT] && !(con_funchars_mode.value)))\n\t{\n\t\tunichar |= 128;\t\t// red char\n\t}\n\n\ti = qwcslen(key_lines[edit_line]);\n\tif (i >= MAXCMDLINE-1)\n\t{\n\t\treturn;\n\t}\n\n\t// This also moves the ending \\0\n\tmemmove (key_lines[edit_line]+key_linepos+1, key_lines[edit_line]+key_linepos, (i-key_linepos+1)*sizeof(wchar));\n\tkey_lines[edit_line][key_linepos] = unichar;\n\tkey_linepos++;\n\tCompleteCommandNew_Reset ();\n\tlastKeyGeneratedCharacter = true;\n}\n\n//============================================================================\n\nchat_type chat_team;\n\nqbool chat_observers;\t// added by jogi\nqbool chat_server;\t\t// added by jogi\nwchar\t\tchat_buffer[MAXCMDLINE];\nint\t\t\tchat_linepos = 0;\n\nvoid Key_Message (int key, wchar unichar) {\n\tint len;\n\n\tswitch (key)\n\t{\n\t\tcase K_ENTER:\n\t\t\tif (chat_buffer[0])\n\t\t\t{\n\t\t\t\tqbool irccommand = false;\n\t\t\t\tswitch (chat_team) {\n\t\t\t\t\tcase chat_mm2: Cbuf_AddText(\"say_team \\\"\"); break;\n\t\t\t\t\tcase chat_qtvtogame: \n\t\t\t\t\t\t       if (cls.mvdplayback == QTV_PLAYBACK) {\n\t\t\t\t\t\t\t       Cbuf_AddText(\"say \\\"say_game \");  // QTV parses the text, no say_game command has been implemented\n\t\t\t\t\t\t       } else {\n\t\t\t\t\t\t\t       Cbuf_AddText(\"// \"); // silence output to remove \"unknown command\" error message\n\t\t\t\t\t\t\t       Com_Printf(\"&cf00Error&r: &c090messagemodeqtvtogame&r requires you to be connected to a &c666QTV&r server\\n\");\n\t\t\t\t\t\t       }\n\t\t\t\t\t\t       break;\n#ifdef WITH_IRC\n\t\t\t\t\tcase chat_irc:\n\t\t\t\t\t\t       if (chat_buffer[0] == '/') {\n\t\t\t\t\t\t\t       irccommand = true;\n\t\t\t\t\t\t\t       Cbuf_AddText(\"irc \");\t\n\t\t\t\t\t\t       }\n\t\t\t\t\t\t       else {\n\t\t\t\t\t\t\t       Cbuf_AddText(\"irc say \\\"\");\n\t\t\t\t\t\t       }\n\t\t\t\t\t\t       break;\n#endif\n\n\t\t\t\t\tdefault:\n\t\t\t\t\tcase chat_mm1: Cbuf_AddText(\"say \\\"\"); break;\n\t\t\t\t}\n\n\t\t\t\tif (irccommand) {\n\t\t\t\t\tCbuf_AddText(encode_say(chat_buffer+1));\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tCbuf_AddText(encode_say(chat_buffer));\n\t\t\t\t\tCbuf_AddText(\"\\\"\\n\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (key_dest_beforemm != key_message && key_dest_beforemm != key_console)\n\t\t\t\tkey_dest = key_dest_beforemm;\n\t\t\telse\n\t\t\t\tkey_dest = key_game;\n\n\t\t\tchat_linepos = 0;\n\t\t\tchat_buffer[0] = 0;\n\t\t\treturn;\n\n\t\tcase K_ESCAPE:\n\t\t\tif (key_dest_beforemm != key_message && key_dest_beforemm != key_console)\n\t\t\t\tkey_dest = key_dest_beforemm;\n\t\t\telse\n\t\t\t\tkey_dest = key_game;\n\n\t\t\tchat_buffer[0] = 0;\n\t\t\tchat_linepos = 0;\n\t\t\treturn;\n\n\t\tcase K_HOME:\n\t\t\tchat_linepos = 0;\n\t\t\treturn;\n\n\t\tcase K_END:\n\t\t\tchat_linepos = qwcslen(chat_buffer);\n\t\t\treturn;\n\n\t\tcase K_LEFTARROW:\n\t\t\tif (chat_linepos > 0)\n\t\t\t\tchat_linepos--;\n\t\t\treturn;\n\n\t\tcase K_RIGHTARROW:\n\t\t\tif (chat_linepos < qwcslen(chat_buffer))\n\t\t\t\tchat_linepos++;\n\t\t\treturn;\n\n\t\tcase K_BACKSPACE:\n\t\t\tif (chat_linepos > 0)\n\t\t\t{\n\t\t\t\tqwcscpy(chat_buffer + chat_linepos - 1, chat_buffer + chat_linepos);\n\t\t\t\tchat_linepos--;\n\t\t\t}\n\t\t\treturn;\n\n\t\tcase K_DEL:\n\t\t\tif (chat_buffer[chat_linepos])\n\t\t\t\tqwcscpy(chat_buffer + chat_linepos, chat_buffer + chat_linepos + 1);\n\t\t\treturn;\n\n\t\tcase K_PGDN:\n#ifdef WITH_IRC\n\t\t\tif (chat_team == chat_irc) {\n\t\t\t\tIRC_NextChan();\n\t\t\t}\n#endif\n\t\t\tbreak;\n\n\t\tcase K_PGUP:\n#ifdef WITH_IRC\n\t\t\tif (chat_team == chat_irc) {\n\t\t\t\tIRC_PrevChan();\n\t\t\t}\n#endif\n\t\t\tbreak;\n\t}\n\n\tif (((key == 'V' || key == 'v') && keydown[K_CTRL] && !keydown[K_ALT])\n\t\t\t|| ((key == K_INS || key == KP_INS) && keydown[K_SHIFT]))\n\t{\n\t\twchar *clipText;\n\n\t\tif ((clipText = Sys_GetClipboardTextW()))\n\t\t{\n\t\t\tlen = qwcslen(clipText);\n\t\t\tif (len + qwcslen(chat_buffer) > MAXCMDLINE - 1)\n\t\t\t\tlen = MAXCMDLINE - 1 - qwcslen(chat_buffer);\n\t\t\tif (len > 0)\n\t\t\t{\n\t\t\t\t// insert the string\n\t\t\t\tmemmove (chat_buffer + chat_linepos + len,\n\t\t\t\t\t\tchat_buffer + chat_linepos, (qwcslen(chat_buffer) - chat_linepos + 1)*sizeof(wchar));\n\t\t\t\tmemcpy (chat_buffer + chat_linepos, clipText, len * sizeof(wchar));\n\t\t\t\tchat_linepos += len;\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\tif (!unichar)\n\t\treturn;\t// non printable\n\n\tlen = qwcslen(chat_buffer);\n\n\tif (len >= sizeof(chat_buffer)/sizeof(wchar) - 1)\n\t\treturn; // all full\n\n\t// This also moves the ending \\0\n\tmemmove (chat_buffer+chat_linepos+1, chat_buffer+chat_linepos, (len - chat_linepos + 1)*sizeof(wchar));\n\tchat_buffer[chat_linepos++] = unichar;\n\tlastKeyGeneratedCharacter = true;\n}\n\n//============================================================================\n\n//Returns a key number to be used to index keybindings[] by looking at the given string.\n//Single ascii characters return themselves, while the K_* names are matched up.\n#define UNKNOWN_S \"UNKNOWN\"\n\nbyte Key_CharacterToQuakeCode(char ch);\n\nint Key_StringToKeynum (const char *str)\n{\n\tkeyname_t *kn;\n\n\tif (!str || !str[0])\n\t\treturn -1;\n\n\tif (!str[1] && !con_bindphysical.value)\n\t{\n\t\tint value = Key_CharacterToQuakeCode(str[0]);\n\n\t\tif (value != 0)\n\t\t\treturn value;\n\t}\n\n\tif (!str[1])\n\t\treturn (int)(unsigned char)str[0];\n\n\tfor (kn = keynames; kn->name; kn++) {\n\t\tif (!strcasecmp (str,kn->name))\n\t\t\treturn kn->keynum;\n\t}\n\n\treturn -1;\n}\n\n//FIXME: handle quote special (general escape sequence?)\nchar *Key_KeynumToString (int keynum) {\n\tkeyname_t *kn;\n\tstatic char tinystr[2];\n\n\tif (keynum == -1)\n\t\treturn \"<KEY NOT FOUND>\";\n\tif (keynum > 32 && keynum < 127) {\t// printable ascii\n\t\ttinystr[0] = keynum;\n\t\ttinystr[1] = 0;\n\t\treturn tinystr;\n\t}\n\n\tfor (kn=keynames ; kn->name ; kn++)\n\t\tif (keynum == kn->keynum)\n\t\t\treturn kn->name;\n\n\treturn \"<UNKNOWN KEYNUM>\";\n}\n\nvoid Key_SetBinding (int keynum, const char *binding) {\n\tif (keynum == -1)\n\t\treturn;\n\n#ifndef __APPLE__\n\tif (keynum == K_CTRL || keynum == K_ALT || keynum == K_SHIFT || keynum == K_WIN) {\n\t\tKey_SetBinding(keynum + 1, binding);\n\t\tKey_SetBinding(keynum + 2, binding);\n\t\treturn;\n\t}\n#endif\n\n\t// free (and hence Q_free) is safe to call with a NULL argument\n\tQ_free (keybindings[keynum]);\n\tkeybindings[keynum] = Q_strdup(binding);\n}\n\nvoid Key_Unbind (int keynum) {\n\tif (keynum == -1)\n\t\treturn;\n\n\tif (keynum == K_CTRL || keynum == K_ALT || keynum == K_SHIFT || keynum == K_WIN) {\n\t\tKey_Unbind(keynum + 1);\n\t\tKey_Unbind(keynum + 2);\n\t\treturn;\n\t}\n\n\t// free (and hence Q_free) is safe to call with a NULL argument\n\tQ_free (keybindings[keynum]);\n\tkeybindings[keynum] = NULL;\n}\n\nvoid Key_Unbind_f (void) {\n\tint b;\n\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf (\"Usage:  %s <key> : remove commands from a key\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tb = Key_StringToKeynum (Cmd_Argv(1));\n\tif (b == -1) {\n\t\tCom_Printf (\"\\\"%s\\\" isn't a valid key\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tKey_Unbind (b);\n}\n\nvoid Key_Unbindall_f (void) {\n\tint i;\n\n\tfor (i = 0; i < sizeof(keybindings) / sizeof(*keybindings); i++)\n\t\tif (keybindings[i])\n\t\t\tKey_Unbind (i);\n}\n\nstatic void Key_PrintBindInfo(int keynum, char *keyname) {\n\tif (!keyname)\n\t\tkeyname = Key_KeynumToString(keynum);\n\n\tif (keynum == -1) {\n\t\tCom_Printf (\"\\\"%s\\\" isn't a valid key\\n\", keyname);\n\t\treturn;\n\t}\n\n\tif (keybindings[keynum])\n\t\tCom_Printf (\"\\\"%s\\\" = \\\"%s\\\"\\n\", keyname, keybindings[keynum]);\n\telse\n\t\tCom_Printf (\"\\\"%s\\\" is not bound\\n\", keyname);\n}\n\n//checks if LCTRL and RCTRL are both bound and bound to the same thing\nqbool Key_IsLeftRightSameBind(int b) {\n\tif (b < 0 || b >= (sizeof(keybindings) / sizeof(*keybindings)) - 2)\n\t\treturn false;\n\n\treturn\t(b == K_CTRL || b == K_ALT || b == K_SHIFT || b == K_WIN) &&\n\t\t(keybindings[b + 1] && keybindings[b + 2] && !strcmp(keybindings[b + 1], keybindings[b + 2]));\n}\n\nvoid Key_Bind_f (void) {\n\tint c, b;\n\n\tc = Cmd_Argc();\n\n\tif (c < 2) {\n\t\tCom_Printf (\"Usage: %s <key> [command] : attach a command to a key\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\tb = Key_StringToKeynum (Cmd_Argv(1));\n\tif (b == -1) {\n\t\tCom_Printf (\"\\\"%s\\\" isn't a valid key\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\tif (c == 2) {\n#ifndef __APPLE__\n\t\tif ((b == K_CTRL || b == K_ALT || b == K_SHIFT || b == K_WIN) && (keybindings[b + 1] || keybindings[b + 2])) {\n\n\t\t\tif (keybindings[b + 1] && keybindings[b + 2] && !strcmp(keybindings[b + 1], keybindings[b + 2])) {\n\t\t\t\tCom_Printf (\"\\\"%s\\\" = \\\"%s\\\"\\n\", Cmd_Argv(1), keybindings[b + 1]);\n\t\t\t} else {\n\t\t\t\tKey_PrintBindInfo(b + 1, NULL);\n\t\t\t\tKey_PrintBindInfo(b + 2, NULL);\n\t\t\t}\n\t\t} else\n#endif\n\t\t{\n\n\t\t\t//\t\tand the following should print \"ctrl (etc) is not bound\" since K_CTRL cannot be bound\n\t\t\tKey_PrintBindInfo(b, Cmd_Argv(1));\n\t\t}\n\t\treturn;\n\t}\n\n\t// copy the rest of the command line\n\tKey_SetBinding (b, Cmd_MakeArgs(2));\n}\n\nvoid Key_BindList_f (void) {\n\tint i;\n\n\tfor (i = 0; i < (sizeof(keybindings) / sizeof(*keybindings)); i++) {\n\t\tif (Key_IsLeftRightSameBind(i)) {\n\t\t\tCom_Printf (\"%s \\\"%s\\\"\\n\", Key_KeynumToString(i), keybindings[i + 1]);\n\t\t\ti += 2;\n\t\t} else {\n\t\t\tif (keybindings[i])\n\t\t\t\tCom_Printf (\"%s \\\"%s\\\"\\n\", Key_KeynumToString(i), keybindings[i]);\n\t\t}\n\t}\n}\n\nvoid Key_BindEdit_f(void) {\n\tchar *keybinding, final_string[MAXCMDLINE - 1];\n\tint keynum, argc = Cmd_Argc();\n\n\tif(argc < 2) {\n\t\tCom_Printf(\"%s <key> : modify a bind\\n\", Cmd_Argv(0));\n\t\tCom_Printf(\"bindlist : list all binds\\n\");\n\t\treturn;\n\t}\n\n\tkeynum = Key_StringToKeynum(Cmd_Argv(1));\n\tif(keynum == -1) {\n\t\tCom_Printf(\"\\\"%s\\\" isn't a valid key\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tkeybinding = keybindings[keynum] ? keybindings[keynum] : \"\";\n\tstrlcpy(final_string, \"/bind \\\"\", sizeof(final_string));\n\tstrlcat(final_string, Cmd_Argv(1), sizeof(final_string));\n\tstrlcat(final_string, \"\\\" \\\"\", sizeof(final_string));\n\tstrlcat(final_string, keybinding, sizeof(final_string));\n\tstrlcat(final_string, \"\\\"\", sizeof(final_string));\n\tKey_ClearTyping();\n\tkey_linepos = 8 + (int)strlen(Cmd_Argv(1)) + 3; // move to where the commands are in the bind\n\tmemcpy(key_lines[edit_line] + 1, str2wcs(final_string), (strlen(final_string) + 1) * sizeof(wchar));\n}\n\nvoid History_Init (void)\n{\n\tint i, c;\n\tFILE *hf;\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_CONSOLE);\n\tCvar_Register (&cl_savehistory);\n\tCvar_ResetCurrentGroup();\n\n\tfor (i = 0; i < CMDLINES; i++) \n\t{\n\t\tkey_lines[i][0] = con_prompt_charcode.integer;\n\t\tkey_lines[i][1] = 0;\n\t}\n\tkey_linepos = 1;\n\n\tif (cl_savehistory.value)\n\t{\n\t\tchar filename[MAX_OSPATH] = {0};\n\n\t\tsnprintf(&filename[0], sizeof(filename), \"%s/\" HISTORY_FILE_NAME, com_basedir);\n\t\tif ((hf = fopen(filename, \"rt\")))\n\t\t{\n\t\t\tdo\n\t\t\t{\n\t\t\t\ti = 1;\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tc = fgetc(hf);\n\t\t\t\t\tkey_lines[edit_line][i++] = c;\n\t\t\t\t} while (c != '\\n' && c != EOF && i < MAXCMDLINE);\n\t\t\t\tkey_lines[edit_line][i - 1] = 0;\n\t\t\t\tedit_line = (edit_line + 1) & (CMDLINES - 1);\n\t\t\t} while (c != EOF && edit_line < CMDLINES);\n\t\t\tfclose(hf);\n\n\t\t\thistory_line = edit_line = (edit_line - 1) & (CMDLINES - 1);\n\t\t\tkey_lines[edit_line][0] = con_prompt_charcode.integer;\n\t\t\tkey_lines[edit_line][1] = 0;\n\t\t}\n\t}\n}\n\nvoid History_Shutdown (void)\n{\n\tint i;\n\tFILE *hf;\n\n\tif (cl_savehistory.value)\n\t{\n\t\tchar filename[MAX_OSPATH] = {0};\n\n\t\tsnprintf(&filename[0], sizeof(filename), \"%s/\" HISTORY_FILE_NAME, com_basedir);\n\t\tif ((hf = fopen(filename, \"wt\")))\n\t\t{\n\t\t\ti = edit_line;\n\t\t\tdo\n\t\t\t{\n\t\t\t\ti = (i + 1) & (CMDLINES - 1);\n\t\t\t} while (i != edit_line && !key_lines[i][1]);\n\n\t\t\tdo\n\t\t\t{\n\t\t\t\tfprintf(hf, \"%s\\n\", /*wcs2str*/encode_say(key_lines[i] + 1));\n\t\t\t\ti = (i + 1) & (CMDLINES - 1);\n\t\t\t} while (i != edit_line && key_lines[i][1]);\n\t\t\tfclose(hf);\n\t\t}\n\t}\n}\n// } Added by VVD\n\nvoid Key_Init (void) {\n\tint i;\n\n\t// init ascii characters in console mode\n\tfor (i = 32; i < 128; i++)\n\t\tconsolekeys[i] = true;\n\tconsolekeys[K_ENTER] = true;\n\tconsolekeys[K_TAB] = true;\n\tconsolekeys[K_LEFTARROW] = true;\n\tconsolekeys[K_RIGHTARROW] = true;\n\tconsolekeys[K_UPARROW] = true;\n\tconsolekeys[K_DOWNARROW] = true;\n\tconsolekeys[K_BACKSPACE] = true;\n\tconsolekeys[K_INS] = true;\n\tconsolekeys[K_DEL] = true;\n\tconsolekeys[K_HOME] = true;\n\tconsolekeys[K_END] = true;\n\tconsolekeys[K_PGUP] = true;\n\tconsolekeys[K_PGDN] = true;\n\tconsolekeys[K_ALT] = true;\n\tconsolekeys[K_LALT] = true;\n\tconsolekeys[K_RALT] = true;\n\tconsolekeys[K_CTRL] = true;\n\tconsolekeys[K_LCTRL] = true;\n\tconsolekeys[K_RCTRL] = true;\n\tconsolekeys[K_SHIFT] = true;\n\tconsolekeys[K_LSHIFT] = true;\n\tconsolekeys[K_RSHIFT] = true;\n#ifdef __APPLE__\n\tconsolekeys[K_CMD] = true;\n#endif\n\tconsolekeys[K_MWHEELUP] = true;\n\tconsolekeys[K_MWHEELDOWN] = true;\n\tconsolekeys[K_WIN] = true;\n\tconsolekeys[K_LWIN] = true;\n\tconsolekeys[K_RWIN] = true;\n\tconsolekeys[K_MENU] = true;\n\tconsolekeys['`'] = false;\n\tconsolekeys['~'] = false;\n\n\tfor (i = 0; i < sizeof(keyshift) / sizeof(*keyshift); i++)\n\t\tkeyshift[i] = i;\n\tfor (i = 'a'; i <= 'z'; i++)\n\t\tkeyshift[i] = i - 'a' + 'A';\n\tkeyshift['1'] = '!';\n\tkeyshift['2'] = '@';\n\tkeyshift['3'] = '#';\n\tkeyshift['4'] = '$';\n\tkeyshift['5'] = '%';\n\tkeyshift['6'] = '^';\n\tkeyshift['7'] = '&';\n\tkeyshift['8'] = '*';\n\tkeyshift['9'] = '(';\n\tkeyshift['0'] = ')';\n\tkeyshift['-'] = '_';\n\tkeyshift['='] = '+';\n\tkeyshift[','] = '<';\n\tkeyshift['.'] = '>';\n\tkeyshift['/'] = '?';\n\tkeyshift[';'] = ':';\n\tkeyshift['\\''] = '\"';\n\tkeyshift['['] = '{';\n\tkeyshift[']'] = '}';\n\tkeyshift['`'] = '~';\n\tkeyshift['\\\\'] = '|';\n\n\tmemset(hudeditorkeys, true, 512 * sizeof(qbool));\n\thudeditorkeys[K_ALT] = true;\n\thudeditorkeys[K_LALT] = true;\n\thudeditorkeys[K_SHIFT] = true;\n\thudeditorkeys[K_LSHIFT] = true;\n\thudeditorkeys[K_CTRL] = true;\n\thudeditorkeys[K_LCTRL] = true;\n\thudeditorkeys['h'] = true;\n\thudeditorkeys['p'] = true;\n\thudeditorkeys[K_SPACE] = true;\n\thudeditorkeys[K_F1] = true;\n\thudeditorkeys[K_F2] = true;\n\thudeditorkeys[K_F3] = true;\n\thudeditorkeys[K_F4] = true;\n\thudeditorkeys[K_MOUSE1] = true;\n\thudeditorkeys[K_MOUSE2] = true;\n\thudeditorkeys[K_MOUSE3] = true;\n\n\tmemset(&scr_pointer_state, 0, sizeof(mouse_state_t));\n\n\t// register our functions\n\tCmd_AddCommand(\"bindlist\",Key_BindList_f);\n\tCmd_AddCommand(\"bind\",Key_Bind_f);\n\tCmd_AddCommand(\"bindedit\", Key_BindEdit_f);\n\tCmd_AddCommand(\"unbind\",Key_Unbind_f);\n\tCmd_AddCommand(\"unbindall\",Key_Unbindall_f);\n\tCvar_SetCurrentGroup(CVAR_GROUP_CONSOLE);\n\tCvar_Register(&cl_chatmode);\n\tCvar_Register(&con_funchars_mode);\n\tCvar_Register(&con_tilde_mode);\n\tCvar_Register(&con_completion_format);\n\tCvar_Register(&con_completion_color_value_current);\n\tCvar_Register(&con_completion_color_value_default);\n\tCvar_Register(&con_completion_color_name);\n\tCvar_Register(&con_completion_color_quotes_current);\n\tCvar_Register(&con_completion_color_quotes_default);\n\tCvar_Register(&con_completion_color_colon);\n\tCvar_Register(&con_completion_color_changed_mark);\n\tCvar_Register(&con_prompt_charcode);\n\tCvar_Register(&con_hide_chat_input);\n\tCvar_Register(&con_completion_padding);\n\tCvar_Register(&con_completion_color_title);\n\tCvar_Register(&con_completion_changed_mark);\n\tCvar_Register(&con_bindphysical);\n\tCvar_Register(&in_builtinkeymap);\n\n\tCvar_ResetCurrentGroup();\n}\n\n// sends new mouse state message to active module and it's windows\n// returns:\n//   true: message was received and handled\n//   false: message wasn't handled by any window\nstatic qbool Mouse_EventDispatch(void)\n{\n\tqbool mouse_handled = false;\n\n\t// Send mouse cursor status to appropriate windows\n\tswitch (key_dest) \n\t{\n\t\tcase key_menu: \n\t\t\tmouse_handled = Menu_Mouse_Event(&scr_pointer_state);\n\t\t\tbreak;\n\t\tcase key_hudeditor: \n\t\t\tmouse_handled = HUD_Editor_MouseEvent(&scr_pointer_state);\n\t\t\tbreak;\n\t\tcase key_demo_controls:\n\t\t\tmouse_handled = DemoControls_MouseEvent(&scr_pointer_state);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t\t// unhandled\n\t\tcase key_game:\n\t\tcase key_console:\n\t\tcase key_message:\n\t\t\tbreak;\n\t}\n\n\treturn mouse_handled;\n}\n\n// called by Key_Event, updates button states\nqbool Mouse_ButtonEvent(int key, qbool down)\n{\n\tif (key >= K_MOUSE1 && key <= K_MOUSE8)\n\t{\t// in this case we convert the button number to the range 1..8\n\t\tkey = key - K_MOUSE1 + 1;   // get the button number, starting from 1\n\t\tkey = bound(1, key, 8);\n\t\tscr_pointer_state.buttons[key] = down;\n\t}\n\telse if (key != K_MWHEELDOWN && key != K_MWHEELUP)\n\t{\n\t\t// not a mouse button received\n\t\treturn false;\n\t}\n\n\tscr_pointer_state.button_down = down ? key : 0;\n\tscr_pointer_state.button_up   = down ? 0 : key;\n\n\t// report if the button event has been handled or not\n\treturn Mouse_EventDispatch();\n}\n\n// called by cl_screen.c each time it figures out that the mouse has moved\nvoid Mouse_MoveEvent(void)\n{\n\t// no button has been pressed\n\tscr_pointer_state.button_down = 0;\n\tscr_pointer_state.button_up = 0;\n\n\t// the rest of scr_pointer_state has already been updated by cl_screen module\n\n\tMouse_EventDispatch();  // so just dispatch the message with new state\n}\n\nqbool Key_IsConsoleToggle (int key)\n{\n\treturn (key == '`' || key == '~');\n}\n\n// Will tell if the key should be currently translated into a command bound to it\n// or send it to some client module.\nstatic qbool Key_ConsoleKey(int key)\n{\n\t// This makes it possible to type chars under tilde key into the console.\n\tqbool con_toggle = Key_IsConsoleToggle(key);\n\tqbool con_key = (con_tilde_mode.integer && con_toggle && (con_tilde_mode.integer == 1 || !CONSOLE_LINE_EMPTY())) ? true : consolekeys[key];\n\tqbool hud_key = (con_tilde_mode.integer && con_toggle) ? true : hudeditorkeys[key];\n\tqbool demo_controls_key = (con_tilde_mode.integer && con_toggle) ? true : democontrolskey[key];\n\tqbool con_keypad_key = cl_keypad.integer && key >= KP_HOME && key <= KP_ENTER;\n\n\tif (key_dest == key_menu && Menu_ExecuteKey(key))\n\t\treturn false;\n\n\tif ((key_dest == key_console || key_dest == key_message) && !con_key && !con_keypad_key)\n\t\treturn false;\n\n\tif (key_dest == key_game && (cls.state == ca_active || !con_key))\n\t\treturn false;\n\n\tif (key_dest == key_hudeditor && !hud_key)\n\t\treturn false;\n\n\tif (key_dest == key_demo_controls && !demo_controls_key)\n\t\treturn false;\n\n\treturn true;\n}\n\n// Called by the system between frames for both key up and key down events Should NOT be called during an interrupt!\nvoid Key_EventEx (int key, wchar unichar, qbool down)\n{\n\tchar *kb, cmd[1024];\n\n\t// FIXME: disconnect: really FIXME CTRL+r or CTRL+[ with in_builinkeymap 1 cause to unichar < 32 \n\tif (/*unichar < 32 ||*/ (unichar > 127 && unichar <= 256))\n\t\tunichar = 0;\n\n\tif (key == K_LALT || key == K_RALT)\n\t\tKey_Event (K_ALT, down);\n\telse if (key == K_LCTRL || key == K_RCTRL)\n\t\tKey_Event (K_CTRL, down);\n\telse if (key == K_LSHIFT || key == K_RSHIFT)\n\t\tKey_Event (K_SHIFT, down);\n\telse if (key == K_LWIN || key == K_RWIN)\n\t\tKey_Event (K_WIN, down);\n\n\tkeydown[key] = down;\n\n\tif (!down)\n\t\tkey_repeats[key] = 0;\n\n\tkey_lastpress = key;\n\n\t// update auto-repeat status\n\tif (down) \n\t{\n\t\tkey_repeats[key]++;\n\n\t\tif (key_repeats[key] > 1) \n\t\t{\n\t\t\tif ((key != K_BACKSPACE && key != K_DEL\n\t\t\t\t\t\t&& key != K_LEFTARROW && key != K_RIGHTARROW\n\t\t\t\t\t\t&& key != K_UPARROW && key != K_DOWNARROW\n\t\t\t\t\t\t&& key != K_PGUP && key != K_PGDN && (key < 32 || key > 126 || key == '`'))\n\t\t\t\t\t|| (key_dest == key_game && cls.state == ca_active))\n\t\t\t{\n\t\t\t\treturn;\t// ignore most autorepeats\n\t\t\t}\n\t\t}\n\t}\n\n\t// Handle escape specialy, so the user can never unbind it.\n\tif (key == K_ESCAPE)\n\t{\n\t\tif (!down)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tif (KeyDestStartupDemo(key_dest)) {\n\t\t\tCbuf_AddText(\"togglemenu\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tswitch (key_dest) \n\t\t{\n\t\t\tcase key_message:\n\t\t\t\tKey_Message (key, unichar);\n\t\t\t\tbreak;\n\t\t\tcase key_menu:\n\t\t\t\tM_Keydown (key, unichar);\n\t\t\t\tbreak;\n\t\t\tcase key_game:\n\t\t\t\tM_ToggleMenu_f ();\n\t\t\t\tbreak;\n\t\t\tcase key_console:\n\t\t\t\tif (!SCR_NEED_CONSOLE_BACKGROUND) {\n\t\t\t\t\tCon_ToggleConsole_f();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tM_ToggleMenu_f();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase key_hudeditor:\n\t\t\t\tHUD_Editor_Key(key, unichar, down);\n\t\t\t\tbreak;\n\t\t\tcase key_demo_controls:\n\t\t\t\tDemoControls_KeyEvent(key, unichar, down);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tassert(!\"Bad key_dest\");\n\t\t}\n\n\t\treturn;\n\t}\n\n\t// Special case for accessing the menu via mouse when in console in disconnected mode:\n\tif (key == K_MOUSE2 && key_dest == key_game && cls.state == ca_disconnected)\n\t{\n\t\tM_ToggleMenu_f();\n\t}\n\n\tif (!down)\n\t{\n\t\t// Key up event.\n\t\tif (key_dest == key_hudeditor) \n\t\t{\n\t\t\tHUD_Editor_Key(key, unichar, down);\n\t\t}\n\t\telse if (key_dest == key_demo_controls)\n\t\t{\n\t\t\tDemoControls_KeyEvent(key, unichar, down);\n\t\t}\n\n\t\t// Key up events only generate commands if the game key binding is a button command (leading + sign).\n\t\t// These will occur even in console mode, to keep the character from continuing an action started before a\n\t\t// console switch.  Button commands include the kenum as a parameter, so multiple downs can be matched with ups\n\t\t{\n\t\t\tkb = keybindings[key];\n\t\t\tif (kb)\n\t\t\t{\n\t\t\t\tif (Key_TryMovementProtected(kb, down, key)) {\n\t\t\t\t\t// this was a protected movement binding and was executed\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (kb[0] == '+' && keyactive[key]) \n\t\t\t\t{\n\t\t\t\t\tsnprintf (cmd, sizeof (cmd), \"-%s %i\\n\", kb+1, key);\n\t\t\t\t\tCbuf_AddText (cmd);\n\t\t\t\t\tkeyactive[key] = false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (keyshift[key] != key) \n\t\t\t{\n\t\t\t\tkb = keybindings[keyshift[key]];\n\t\t\t\tif (kb && kb[0] == '+' && keyactive[keyshift[key]]) \n\t\t\t\t{\n\t\t\t\t\tsnprintf (cmd, sizeof (cmd), \"-%s %i\\n\", kb+1, key);\n\t\t\t\t\tCbuf_AddText (cmd);\n\t\t\t\t\tkeyactive[keyshift[key]] = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn;\n\t}\n\n\t// switch windowed<->fullscreen if pressed alt+enter, I succeed only with left alt, dunno why...\n\tif (key == K_ENTER && !sys_disable_alt_enter.integer && keydown[K_ALT] && (key_dest == key_console || key_dest == key_game))\n\t{\n\t\tKey_ClearStates(); // Zzzz\n\t\tcon_suppress = true;\n\t\tCvar_SetValue( &r_fullscreen, !r_fullscreen.integer );\n\t\tCbuf_AddText( \"vid_restart\\n\" );\n\t\tCbuf_Execute();\n\t\tcon_suppress = false;\n\t\treturn;\n\t}\n\n\t// if not a consolekey, send to the interpreter no matter what mode is\n\tif (!Key_ConsoleKey(key)) \n\t{\n\t\tkb = keybindings[key];\n\n\t\tif (kb) \n\t\t{\n\t\t\tif (Key_TryMovementProtected(kb, down, key)) {\n\t\t\t\t// this was a protected movement binding and was executed\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (kb[0] == '+')\n\t\t\t{\t\n\t\t\t\t// Button commands add keynum as a parm.\n\t\t\t\tsnprintf (cmd, sizeof (cmd), \"%s %i\\n\", kb, key);\n\t\t\t\tCbuf_AddText (cmd);\n\t\t\t\tkeyactive[key] = true;\n\t\t\t} \n\t\t\telse \n\t\t\t{\n\t\t\t\tCbuf_AddText (kb);\n\t\t\t\tCbuf_AddText (\"\\n\");\n\t\t\t}\n\t\t}\n\n\t\treturn;\n\t}\n\n\tif (!down) {\n\t\treturn;\t // Other systems only care about key down events.\n\t}\n\n#ifdef _WIN32\n\t{\n\t\textern cvar_t con_toggle_deadkey;\n\n\t\tif (Key_IsConsoleToggle (key) && con_toggle_deadkey.integer) {\n\t\t\tSys_CancelDeadKey ();\n\t\t}\n\t}\n#endif\n\n\tif (keydown[K_SHIFT])\n\t\tkey = keyshift[key];\n\n\t// Key down event.\n\tswitch (key_dest) \n\t{\n\t\tcase key_message:\n\t\t\tKey_Message (key, unichar);\n\t\t\tbreak;\n\n\t\tcase key_menu:\n\t\t\tM_Keydown (key, unichar);\n\t\t\tbreak;\n\n\t\tcase key_game:\n\t\tcase key_console:\n\t\t\tKey_Console (key, unichar);\n\t\t\tbreak;\n\n\t\tcase key_hudeditor:\n\t\t\tHUD_Editor_Key(key, unichar, down);\n\t\t\tbreak;\n\n\t\tcase key_demo_controls:\n\t\t\tDemoControls_KeyEvent(key, unichar, down);\n\t\t\tbreak;\n\n\t\tcase key_startupdemo_browser:\n\t\t\tCbuf_AddText(\"menu_slist\\n\");\n\t\t\tbreak;\n\t\tcase key_startupdemo_console:\n\t\tcase key_startupdemo_game:\n\t\t\tkey_dest = key_console;\n\t\t\tbreak;\n\t\tcase key_startupdemo_menu:\n\t\t\tCbuf_AddText(\"togglemenu\\n\");\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tassert(!\"Bad key_dest\");\n\t}\n}\n\nvoid Key_Event (int key, qbool down)\n{\n\tqbool processTextInput = (key_dest == key_console || key_dest == key_message);\n\tqbool consoleToggle = (key == '`' || key == '~') && !con_tilde_mode.integer;\n\twchar unichar = 0;\n\n\tassert (key >= 0 && key <= 255);\n\n\tif (key >= K_MOUSE1 && key <= K_MOUSE8)\n\t{\n\t\t// if the Mouse_ButtonEvent return true means that the window which received\n\t\t// a mouse click handled it and we do not have to send old\n\t\t// K_MOUSE* key event\n\t\tif (Mouse_ButtonEvent(key, down)) \n\t\t\treturn;\n\t}\n\n\tif (key == K_MWHEELDOWN || key == K_MWHEELUP) {\n\t\t// same logic applies here as for handling K_MOUSE1..8 buttons\n\t\tif (Mouse_ButtonEvent(key, down)) \n\t\t\treturn;\n\t}\n\n\t// Caps lock shouldn't trigger bind in chat/console\n\tif (key == K_CAPSLOCK && processTextInput) {\n\t\treturn;\n\t}\n\n\tlastKeyGeneratedCharacter = false;\n\tunichar = keydown[K_SHIFT] ? keyshift[key] : key;\n\tif (unichar < 32 || unichar > 127)\n\t\tunichar = 0;\n\n\tKey_EventEx (key, unichar, down);\n\n\t// Store this as we may need it for subsequent SDL_TextInput event\n\tlastKeyDown = 0;\n\tif (down && processTextInput && !consoleToggle)\n\t\tlastKeyDown = key;\n}\n\nvoid Key_Event_TextInput(wchar unichar)\n{\n\tif (in_builtinkeymap.value)\n\t\treturn;\n\tif (!lastKeyDown)\n\t\treturn;\n\n\tif (key_dest == key_message)\n\t{\n\t\tif (lastKeyGeneratedCharacter)\n\t\t\tKey_Message(K_BACKSPACE, K_BACKSPACE);\n\t\tKey_Message(lastKeyDown, unichar);\n\t}\n\telse if (key_dest == key_console)\n\t{\n\t\tif (lastKeyGeneratedCharacter)\n\t\t\tKey_Console_Backspace();\n\t\tKey_Console(lastKeyDown, unichar);\n\t}\n}\n\nvoid Key_ClearStates (void) \n{\n\tint\t\ti;\n\n\tfor (i = 0; i < sizeof(keydown) / sizeof(*keydown); i++) \n\t{\n\t\tif (keydown[i])\n\t\t\tKey_Event(i, false);\n\n\t\tkeydown[i] = false;\n\t\tkey_repeats[i] = false;\n\t}\n}\n\nvoid OnChange_con_prompt_charcode (cvar_t *var, char *string, qbool *cancel)\n{\t\n\tint i, charcode = Q_atoi(string);\n\t*cancel = true;\n\n\tif ((charcode > 31) && (charcode <= 255) && (charcode != con_prompt_charcode.integer))\n\t{\n\t\t// changes prompt in current line\n\t\tkey_lines[edit_line][0] = charcode;\n\n\t\t// changes prompt in all lines\n\t\tfor (i = 0; i < CMDLINES; i++)\n\t\t{\n\t\t\tif (qwcslen(key_lines[i]) > 1)\n\t\t\t\tkey_lines[i][0] = charcode;\n\t\t}\n\n\t\t*cancel = false;\n\t}\n}\n\nvoid OnChange_con_completion_color (cvar_t *var, char *string, qbool *cancel)\n{\n\tif (!Utils_RegExpMatch(\"^[0-9a-fA-F]{3}$\", string))\n\t\t*cancel = true;\n}\n\nchar* escape_regex(char* string)\n{\n\t// TODO: Rename and move this to a more appropriate place so other functions may use it (utils.c ?)\n\tint i, j, len;\n\tchar *out = \"\";\n\n\tlen = strlen(string);\n\tout = (char*) Q_malloc((len * 2 + 1) * sizeof(char));\n\n\tfor(i = 0, j = 0; i < len; i++)\n\t{\n\t\tswitch(string[i])\n\t\t{\n\t\t\tcase '+':\n\t\t\tcase '.':\n\t\t\tcase '[':\n\t\t\tcase ']':\n\t\t\t\tout[j++] = '\\\\';\n\t\t\t\tout[j++] = string[i];\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tout[j++] = string[i];\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tout[j++] = '\\0';\n\n\treturn out;\n}\n\nvoid Key_Shutdown(void)\n{\n\tint i;\n\n\tfor (i = 0; i < sizeof(keybindings) / sizeof(keybindings[0]); ++i) {\n\t\tQ_free(keybindings[i]);\n\t}\n}\n"
  },
  {
    "path": "src/keys.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: keys.h,v 1.16 2007-05-28 10:47:34 johnnycz Exp $\n\n*/\n\n// these are the key numbers that should be passed to Key_Event\n\n#ifndef _KEYS_H_\n#define _KEYS_H_\n \ntypedef enum {\n\tK_TAB = 9,\n\tK_ENTER = 13,\n\tK_ESCAPE = 27,\n\tK_SPACE\t= 32,\n\n// normal keys should be passed as lowercased ascii\n\n\tK_BACKSPACE = 127,\n\n\tK_CAPSLOCK,\n\tK_PRINTSCR,\n\tK_SCRLCK,\t\t//130\n\tK_PAUSE,\n\n\tK_UPARROW,\n\tK_DOWNARROW,\n\tK_LEFTARROW,\n\tK_RIGHTARROW,\n\n\t\n\tK_ALT,\n\tK_LALT,\n\tK_RALT,\n\tK_CTRL,\t\t\t//140\n\tK_LCTRL,\n\tK_RCTRL,\n\tK_SHIFT,\n\tK_LSHIFT,\n\tK_RSHIFT,\n#ifdef __APPLE__\n\tK_CMD,\n\tK_PARA,\n#endif\n\tK_F1,\n\tK_F2,\n\tK_F3,\n\tK_F4,\n\tK_F5,\t\t\t//150\n\tK_F6,\n\tK_F7,\n\tK_F8,\n\tK_F9,\n\tK_F10,\n\tK_F11,\n\tK_F12,\n#ifdef __APPLE__\n\tK_F13,\n\tK_F14,\n\tK_F15,\n#endif\n\tK_INS,\n\tK_DEL,\n\tK_PGDN,\t\t\t//160\n\tK_PGUP,\n\tK_HOME,\n\tK_END,\n\n\tK_WIN,\n\tK_LWIN,\n\tK_RWIN,\n\tK_MENU,\n\tK_ISO,\n\n//\n// Keypad stuff..\n//\n\n\tKP_NUMLOCK,\n\tKP_SLASH,\n\tKP_STAR,\t\t//170\n\n\tKP_HOME,\n\tKP_UPARROW,\n\tKP_PGUP,\n\tKP_MINUS,\n\n\tKP_LEFTARROW,\n\tKP_5,\n\tKP_RIGHTARROW,\n\tKP_PLUS,\n\n\tKP_END,\n\tKP_DOWNARROW,\t//180\n\tKP_PGDN,\n\n\tKP_INS,\n\tKP_DEL,\n\tKP_ENTER,\n\n#ifdef __APPLE__\n\t// macintosh keypad\n\tKP_EQUAL,\n\n\tKP_7=KP_HOME,\n\tKP_8=KP_UPARROW,\n\tKP_9=KP_PGUP,\n\n\tKP_4=KP_LEFTARROW,\n\tKP_6=KP_RIGHTARROW,\n\n\tKP_1=KP_END,\n\tKP_2=KP_DOWNARROW,\n\tKP_3=KP_PGDN,\n\n\tKP_0=KP_INS,\n\tKP_DOT=KP_DEL,\n#endif\n\n\tK_SECTION,\n\tK_ACUTE,\n\tK_DIAERESIS,\n\tK_ARING,\n\tK_ADIAERESIS,\n\tK_ODIAERESIS,\n\tK_WAKE_UP,\t\t//191\n\n//\n// mouse buttons generate virtual keys\n//\n\tK_MOUSE1 = 200,\n\tK_MOUSE2,\n\tK_MOUSE3,\n\tK_MOUSE4,\n\tK_MOUSE5,\n\tK_MOUSE6,\n\tK_MOUSE7,\n\tK_MOUSE8,\n\n//\n// joystick generic buttons\n//\n\tK_JOY1,\n\tK_JOY2,\n\tK_JOY3,\t\t\t//210\n\tK_JOY4,\n\n//\n// joystick POV/hat buttons\n//\n\tK_JOYPOVUP,\n\tK_JOYPOVRT,\n\tK_JOYPOVDN,\n\tK_JOYPOVLT,\n\n//\n// aux keys are for multi-buttoned joysticks to generate so they can use\n// the normal binding process\n//\n\tK_AUX1,\n\tK_AUX2,\n\tK_AUX3,\n\tK_AUX4,\n\tK_AUX5,\t\t\t//220\n\tK_AUX6,\n\tK_AUX7,\n\tK_AUX8,\n\tK_AUX9,\n\tK_AUX10,\n\tK_AUX11,\n\tK_AUX12,\n\tK_AUX13,\n\tK_AUX14,\n\tK_AUX15,\t\t//230\n\tK_AUX16,\n\tK_AUX17,\n\tK_AUX18,\n\tK_AUX19,\n\tK_AUX20,\n\tK_AUX21,\n\tK_AUX22,\n\tK_AUX23,\n\tK_AUX24,\n\tK_AUX25,\t\t//240\n\tK_AUX26,\n\tK_AUX27,\n\tK_AUX28,\n\n// JACK: Intellimouse(c) Mouse Wheel Support\n\n\tK_MWHEELUP = 244,\n\tK_MWHEELDOWN,\t//245\n\n\tUNKNOWN = 256\n\n} keynum_t;\n\n\ntypedef enum {\n\tkey_game,\n\tkey_console,\n\tkey_message,\n\tkey_menu,\n\tkey_hudeditor,\n\tkey_demo_controls,\n\tkey_startupdemo_menu,\n\tkey_startupdemo_console,\n\tkey_startupdemo_game,\n\tkey_startupdemo_browser\n} keydest_t;\n\n#define KeyDestStartupDemo(x) ((x) >= key_startupdemo_menu && (x) <= key_startupdemo_browser)\n\nextern keydest_t\tkey_dest, key_dest_beforemm, key_dest_beforecon;\nextern char \t*keybindings[UNKNOWN + 256];\nextern int\t\tkey_repeats[UNKNOWN + 256];\nextern qbool\tkeydown[UNKNOWN + 256];\nextern int\t\tkey_lastpress;\n\n#ifdef WITH_IRC\n\ttypedef enum { chat_mm1, chat_mm2, chat_irc, chat_qtvtogame }\tchat_type;\n#else\n\ttypedef enum { chat_mm1, chat_mm2, chat_qtvtogame }\tchat_type;\n#endif\n\nextern wchar \tchat_buffer[];\nextern int \t\tchat_linepos;\nextern chat_type chat_team;\n\n\n// this is message type sent across windows that accept mouse pointer\ntypedef struct mouse_state_s {\n\tdouble x;           // current mouse pointer horisontal position\n\tdouble y;           // current mouse pointer vertical position\n\tdouble x_old;\t    // previous mouse pointer positions\n\tdouble y_old;\n    qbool buttons[9];   // button states .. omit button 0\n    int button_down;    // number of the button that just has been pressed down\n    int button_up;      // number of the button that just has been released\n} mouse_state_t;\n\n// exported only to be changed in the cl_screen.c module where mouse position gets updated\n// do not access this variable anywhere else, prefer \"window-messages\" system\nextern mouse_state_t scr_pointer_state;\n\n// used in cl_screen module which is responsible for updating the mouse pointer position\nvoid Mouse_MoveEvent(void);\n\nvoid History_Init (void);\nvoid History_Shutdown (void);\n\n// this will clear any typping in console\nvoid Key_ClearTyping (void);\n\nvoid Key_Event (int key, qbool down);\nvoid Key_EventEx (int key, wchar unichar, qbool down);\nvoid Key_Init (void);\nvoid Key_Shutdown(void);\nvoid Key_SetBinding (int keynum, const char *binding);\nvoid Key_Unbind (int keynum);\nvoid Key_ClearStates (void);\nint\t Key_StringToKeynum (const char *str);\nchar *Key_KeynumToString (int keynum);\nvoid Key_Unbindall_f (void);\n\n// should not be public actually but...\n// {\n#define\t\tCMDLINES\t(1<<8)\n#define\t\tMAXCMDLINE\t256\n\nextern wchar\tkey_lines[CMDLINES][MAXCMDLINE];\nextern int\t\tkey_linepos;\nextern int\t\tedit_line;\nextern qbool\tcon_redchars;\n\nextern cvar_t\tcon_bindphysical;\n\n#define CONSOLE_LINE_EMPTY() (!key_lines[edit_line][1])\n\n// }\n\n#if defined(_WIN32) && !defined(WITHOUT_WINKEYHOOK)\n#define WINDOWS_LWINDOWSKEY (1 << 0)\n#define WINDOWS_RWINDOWSKEY (1 << 1)\n#define WINDOWS_MENU        (1 << 2)\n#define WINDOWS_PRINTSCREEN (1 << 3)\n#define WINDOWS_CAPSLOCK    (1 << 4)\n\nextern unsigned int windows_keys_down, windows_keys_up;\n#endif\n\nqbool Key_IsConsoleToggle(int key);\n\n#endif // _KEYS_H_ \n"
  },
  {
    "path": "src/linux_signals.c",
    "content": "/*\n===========================================================================\nCopyright (C) 1999-2005 Id Software, Inc.\n\nThis file is part of Quake III Arena source code.\n\nQuake III Arena source code is free software; you can redistribute it\nand/or modify it under the terms of the GNU General Public License as\npublished by the Free Software Foundation; either version 2 of the License,\nor (at your option) any later version.\n\nQuake III Arena source code is distributed in the hope that it will be\nuseful, but WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Foobar; if not, write to the Free Software\nFoundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n===========================================================================\n\n    $Id: linux_signals.c,v 1.4 2007-06-15 12:26:07 johnnycz Exp $\n\n*/\n#include <signal.h>\n\n#include \"quakedef.h\"\n#include \"input.h\"\n\nstatic qbool signalcaught = false;\n\nstatic void signal_handler(int sig) // bk010104 - replace this... (NOTE TTimo huh?)\n{\n\tif (signalcaught)\n\t{\n\t\tprintf(\"DOUBLE SIGNAL FAULT: Received signal %d, exiting...\\n\", sig);\n\t\tSys_Quit();\n\t\texit(0);\n\t}\n\n\tsignalcaught = true;\n\tprintf(\"Received signal %d, exiting...\\n\", sig);\n\n//\n// client related things\n//\n\tVID_Shutdown(false);  // bk010104 - shouldn't this be CL_Shutdown\n\n\tSys_Quit();\n\texit(0);\n}\n\nvoid InitSig(void)\n{\n\tsignal(SIGHUP,  signal_handler);\n\tsignal(SIGINT,  signal_handler); // btw, q3 do not have this signal handling\n\tsignal(SIGQUIT, signal_handler);\n\tsignal(SIGILL,  signal_handler);\n\tsignal(SIGTRAP, signal_handler);\n\tsignal(SIGIOT,  signal_handler);\n\tsignal(SIGBUS,  signal_handler);\n\tsignal(SIGFPE,  signal_handler);\n\tsignal(SIGSEGV, signal_handler);\n\tsignal(SIGTERM, signal_handler);\n}\n"
  },
  {
    "path": "src/localtime.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef __LOCALTIME_H__\n#define __LOCALTIME_H__\n\n#ifdef _WIN32\n\n#include <windows.h> // FIXME: we should include it only at winquake.h\n#include <time.h>\n\n#else\n\n#include <time.h>\n\ntypedef struct SYSTEMTIME_s\n{\n    int wYear;\n    int wMonth;\n    int wDay;\n    int wDayOfWeek;\n    int wHour;\n    int wMinute;\n    int wSecond;\n    int wMilliseconds;\n} SYSTEMTIME;\n\nvoid GetLocalTime(SYSTEMTIME *);\n\n#endif\n\n\nint  GetFileLocalTime(char *, SYSTEMTIME *);\nvoid UnixtimeToWintime(SYSTEMTIME *, struct tm *);\nint  SYSTEMTIMEcmp(const SYSTEMTIME *, const SYSTEMTIME *);\n\n#endif // __LOCALTIME_H__\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "src/localtime_posix.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include <time.h>\n#include <sys/stat.h>\n#include \"localtime.h\"\n\n// helper\nvoid UnixtimeToWintime(SYSTEMTIME *wintime, struct tm* time)\n{\n    wintime->wYear      = time->tm_year + 1900;\n    wintime->wMonth     = time->tm_mon + 1;\n    wintime->wDay       = time->tm_mday;\n    wintime->wDayOfWeek = time->tm_wday;\n    wintime->wHour      = time->tm_hour;\n    wintime->wMinute    = time->tm_min;\n    wintime->wSecond    = time->tm_sec;\n}\n\n\nvoid GetLocalTime(SYSTEMTIME *wintime)\n{\n    struct tm * lintime;\n    time_t t;\n    \n    time(&t);\n    lintime = localtime(&t);\n\n    UnixtimeToWintime(wintime, lintime);    \n}\n\n\nint  GetFileLocalTime(char *path, SYSTEMTIME *wintime)\n{\n    struct stat statbuf;\n    struct tm * lintime;\n    \n    if (stat(path, &statbuf) != 0)\n        return 0;\n    lintime = localtime(&statbuf.st_mtime);\n\n    UnixtimeToWintime(wintime, lintime);\n\n    return 1;\n}\n\nstatic int intcmp(int i1, int i2)\n{\n    if (i1 > i2)\n    return 1;\n    if (i1 < i2)\n    return -1;\n    return 0;\n}\n\nint SYSTEMTIMEcmp(const SYSTEMTIME *t1, const SYSTEMTIME *t2)\n{\n    return intcmp( t1->wSecond, t2->wSecond )\n    +  intcmp( t1->wMinute, t2->wMinute ) * 2\n    +  intcmp( t1->wHour,   t2->wHour   ) * 4\n    +  intcmp( t1->wDay,    t2->wDay    ) * 8\n    +  intcmp( t1->wMonth,  t2->wMonth  ) * 16\n    +  intcmp( t1->wYear,   t2->wYear   ) * 32;\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "src/localtime_win.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include <windows.h>\n#include \"localtime.h\"\n\n// localtime built-in\n// void GetLocalTime(SYSTEMTIME *wintime);\n\n\n// local file time\nint  GetFileLocalTime(char *path, SYSTEMTIME *wintime)\n{\n    // update time\n    HANDLE hFile;\n//    OFSTRUCT of;\n    FILETIME ft1, ft2;\n\n    hFile = CreateFile(path, 0, FILE_SHARE_READ,\n        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);\n    if (hFile == (HANDLE)ERROR_INVALID_HANDLE)\n        return 0;\n        \n    GetFileTime(hFile, NULL, NULL, &ft1);\n    FileTimeToLocalFileTime(&ft1, &ft2);\n    FileTimeToSystemTime(&ft2, wintime);\n    CloseHandle(hFile);\n    return 1;\n}\n\nint intcmp(int i1, int i2)\n{\n    if (i1 > i2)\n\treturn 1;\n    if (i1 < i2)\n\treturn -1;\n    return 0;\n}\n\nint SYSTEMTIMEcmp(const SYSTEMTIME *t1, const SYSTEMTIME *t2)\n{\n    return intcmp( t1->wSecond, t2->wSecond ) * 2\n        +  intcmp( t1->wMinute, t2->wMinute ) * 4\n        +  intcmp( t1->wHour,   t2->wHour   ) * 8\n        +  intcmp( t1->wDay,    t2->wDay    ) * 16\n        +  intcmp( t1->wMonth,  t2->wMonth  ) * 32\n        +  intcmp( t1->wYear,   t2->wYear   ) * 64;\n}\n"
  },
  {
    "path": "src/logging.c",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: logging.c,v 1.13 2007-10-11 05:55:47 dkure Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"logging.h\"\n#include \"utils.h\"\n#include \"hash.h\"\n#include \"vfs.h\"\n#include <curl/curl.h>\n\n\nstatic void OnChange_log_dir(cvar_t *var, char *string, qbool *cancel);\n\ncvar_t\t\tlog_dir\t\t\t= {\"log_dir\", \"\", 0, OnChange_log_dir};\ncvar_t\t\tlog_readable\t= {\"log_readable\", \"1\"};\n\n#define\t\t\tLOG_FILENAME_MAXSIZE\t(MAX_PATH)\n#define\t\t\tLOGFILEBUFFER\t\t\t(128*1024)\n\nstatic FILE       *logfile;     // opened file with log, writing is not performed until logging is stopped to avoid fps spikes\nstatic vfsfile_t  *memlogfile;  // allocated memory where the log is being written\nstatic char       logfilename[LOG_FILENAME_MAXSIZE];\n\nstatic qbool autologging = false;\n\nconst char *MT_Challenge_GetToken(void);\nconst char *MT_Challenge_GetLadderId(void);\nconst char *MT_Challenge_GetHash(void);\nqbool MT_Challenge_IsOn(void);\nvoid Log_UploadTemp(void);\n\nqbool Log_IsLogging(void) {\n\treturn logfile ? true : false;\n}\n\nstatic char *Log_LogDirectory(void) {\n\tstatic char dir[LOG_FILENAME_MAXSIZE];\n\n\tstrlcpy(dir, FS_LegacyDir(log_dir.string), sizeof(dir));\n\treturn dir;\n}\n\nstatic void Log_Stop(void) {\n\tint read;\n\tvfserrno_t err;\n\tchar buf[1024];\n\tint size;\n\n\tif (!Log_IsLogging())\n\t\treturn;\n\n\tsize = (int) VFS_TELL(memlogfile);\n\n\tVFS_SEEK(memlogfile, 0, SEEK_SET);\n\twhile (size > 0 && (read = VFS_READ(memlogfile, buf, 1024, &err))) {\n\t\tfwrite(buf, 1, min(read, size), logfile);\n\t\tsize -= read;\n\t}\n\n\tVFS_CLOSE(memlogfile);\n\tfclose(logfile);\n\n\tlogfile = NULL;\n\tmemlogfile = NULL;\n}\n\nstatic void OnChange_log_dir(cvar_t *var, char *string, qbool *cancel) {\n\tif (!string[0])\n\t\treturn;\n\n\tUtil_Process_FilenameEx(string, cl_mediaroot.integer == 2);\n\n\tif (!Util_Is_Valid_FilenameEx(string, cl_mediaroot.integer == 2)) {\n\t\tCom_Printf(Util_Invalid_Filename_Msg(var->name));\n\t\t*cancel = true;\n\t\treturn;\n\t}\n}\n\nstatic void Log_log_f(void) {\n\tchar *fulllogname;\n\tFILE *templog;\n\tvoid *buf;\n\n\tswitch (Cmd_Argc()) {\n\tcase 1:\n\t\tif (autologging)\n\t\t\tCom_Printf(\"Auto console logging is in progress\\n\");\n\t\telse if (Log_IsLogging())\n\t\t\tCom_Printf(\"Logging to %s\\n\", logfilename);\n\t\telse\n\t\t\tCom_Printf(\"Not logging\\n\");\n\t\treturn;\n\tcase 2:\n\t\tif (!strcasecmp(Cmd_Argv(1), \"stop\")) {\n\t\t\tif (autologging) {\n\t\t\t\tLog_AutoLogging_StopMatch();\n\t\t\t} else {\n\t\t\t\tif (Log_IsLogging()) {\n\t\t\t\t\tLog_Stop();\n\t\t\t\t\tCom_Printf(\"Stopped logging to %s\\n\", logfilename);\n\t\t\t\t} else {\n\t\t\t\t\tCom_Printf(\"Not logging\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (autologging) {\n\t\t\tCom_Printf(\"Auto console logging must be stopped first!\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (Log_IsLogging()) {\n\t\t\tLog_Stop();\n\t\t\tCom_Printf(\"Stopped logging to %s\\n\", logfilename);\n\t\t}\n\n\t\tstrlcpy(logfilename, Cmd_Argv(1), sizeof(logfilename) - 4);\n\t\tUtil_Process_Filename(logfilename);\n\t\tif (!Util_Is_Valid_Filename(logfilename)) {\n\t\t\tCom_Printf(Util_Invalid_Filename_Msg(\"filename\"));\n\t\t\treturn;\n\t\t}\n\t\tCOM_ForceExtensionEx (logfilename, \".log\", sizeof (logfilename));\n\t\tfulllogname = va(\"%s/%s\", Log_LogDirectory(), logfilename);\n\t\tif (!(templog = fopen (fulllogname, log_readable.value ? \"w\" : \"wb\"))) {\n\t\t\tFS_CreatePath(fulllogname);\n\t\t\tif (!(templog = fopen (fulllogname, log_readable.value ? \"w\" : \"wb\"))) {\n\t\t\t\tCom_Printf(\"Error: Couldn't open %s\\n\", logfilename);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tbuf = Q_calloc(1, LOGFILEBUFFER);\n\t\tif (!buf) {\n\t\t\tCom_Printf(\"Not enough memory to allocate log buffer\\n\");\n\t\t\tfclose(templog);\n\t\t\treturn;\n\t\t}\n\t\tmemlogfile = FSMMAP_OpenVFS(buf, LOGFILEBUFFER);\n\t\tCom_Printf(\"Logging to %s\\n\", logfilename);\n\t\tlogfile = templog;\n\t\tbreak;\n\tdefault:\n\t\tCom_Printf(\"Usage: %s [filename | stop]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n}\n\nvoid Log_Init(void) {\n\tCvar_SetCurrentGroup(CVAR_GROUP_CONSOLE);\n\tCvar_Register (&log_dir);\n\tCvar_Register (&log_readable);\n\n\tCvar_ResetCurrentGroup();\n\n\tCmd_AddCommand(\"log\", Log_log_f);\n}\n\nvoid Log_Shutdown(void) {\n\tif (Log_IsLogging())\t\n\t\tLog_Stop();\n}\n\nvoid Log_Write(char *s) {\n\tif (!Log_IsLogging())\n\t\treturn;\n\t\n\tVFS_WRITE(memlogfile, s, strlen(s));\n\t// fprintf(logfile, \"%s\", s);\n}\n\n//=============================================================================\n//\t\t\t\t\t\t\tAUTO CONSOLE LOGGING\n//=============================================================================\n\n\n\nstatic char\tauto_matchname[2 * MAX_OSPATH];\nstatic qbool temp_log_ready = false;\nstatic float auto_starttime;\n\nchar *MT_TempDirectory(void);\n\nextern cvar_t match_auto_logconsole, match_auto_minlength;\n\nvoid Log_AutoLogging_StopMatch(void) {\n\tif (!autologging) {\n\t\treturn;\n\t}\n\n\tautologging = false;\n\tLog_Stop();\n\ttemp_log_ready = true;\n\n\tif (match_auto_logconsole.value == 2)\n\t\tLog_AutoLogging_SaveMatch(true);\n\telse {\n\t\tCom_Printf (\"Auto console logging completed\\n\");\n\t\tif (MT_Challenge_Log_IsUploadAllowed()) {\n\t\t\tLog_UploadTemp();\n\t\t}\n\t}\n}\n\n\nvoid Log_AutoLogging_CancelMatch(void) {\n\tif (!autologging)\n\t\treturn;\n\n\tautologging = false;\n\tLog_Stop();\n\ttemp_log_ready = true;\n\n\tif (match_auto_logconsole.value == 2) {\n\t\tif (cls.realtime - auto_starttime > match_auto_minlength.value) {\n\t\t\tLog_AutoLogging_SaveMatch(true);\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"Auto console logging cancelled\\n\");\n\t\t}\n\t} else if (match_auto_logconsole.integer == 1 || MT_ChallengeSpecified()) {\n\t\tCom_Printf (\"Auto console logging completed\\n\");\n\t\tif (MT_Challenge_Log_IsUploadAllowed()) {\n\t\t\tLog_UploadTemp();\n\t\t}\n\t} else {\n\t\tCom_Printf (\"Auto console logging completed\\n\");\n\t}\n}\n\n\nvoid Log_AutoLogging_StartMatch(char *logname) {\n\tchar extendedname[MAX_OSPATH * 2], *fullname;\n\tFILE *templog;\n\tvoid *buf;\n\n\ttemp_log_ready = false;\n\n\tif (!match_auto_logconsole.integer && !MT_ChallengeSpecified()) {\n\t\treturn;\n\t}\n\n\tif (Log_IsLogging()) {\n\t\tif (autologging) {\t\t\n\t\t\tautologging = false;\n\t\t\tLog_Stop();\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"Auto console logging skipped (already logging)\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\tstrlcpy(auto_matchname, logname, sizeof(auto_matchname));\n\n\tstrlcpy(extendedname, TEMP_LOG_NAME, sizeof(extendedname));\n\tCOM_ForceExtensionEx (extendedname, \".log\", sizeof (extendedname));\n\tfullname = va(\"%s/%s\", MT_TempDirectory(), extendedname);\n\n\tif (!(templog = fopen (fullname, log_readable.value ? \"w\" : \"wb\"))) {\n\t\tFS_CreatePath(fullname);\n\t\tif (!(templog = fopen (fullname, log_readable.value ? \"w\" : \"wb\"))) {\n\t\t\tCom_Printf(\"Error: Couldn't open %s\\n\", fullname);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tbuf = Q_calloc(1, LOGFILEBUFFER);\n\tif (!buf) {\n\t\tCom_Printf(\"Not enough memory to allocate log buffer\\n\");\n\t\tfclose(templog);\n\t\treturn;\n\t}\n\tmemlogfile = FSMMAP_OpenVFS(buf, LOGFILEBUFFER);\n\n\tCom_Printf (\"Auto console logging commenced\\n\");\n\n\tlogfile = templog;\n\tautologging = true;\n\tauto_starttime = cls.realtime;\n}\n\nqbool Log_AutoLogging_Status(void) {\n\treturn temp_log_ready ? 2 : autologging ? 1 : 0;\n}\n\nvoid Log_AutoLogging_SaveMatch(qbool allow_upload) {\n\tint error, num;\n\tFILE *f;\n\tchar *dir, *tempname, savedname[2 * MAX_OSPATH], *fullsavedname, *exts[] = {\"log\", NULL};\n\n\tif (!temp_log_ready) {\n\t\treturn;\n\t}\n\n\tif (Log_TempLogUploadPending()) {\n\t\tCom_Printf(\"Error: Can't save the log. Log upload is still pending.\\n\");\n\t\treturn;\n\t}\n\n\ttemp_log_ready = false;\n\n\tdir = Log_LogDirectory();\n\ttempname = va(\"%s/%s\", MT_TempDirectory(), TEMP_LOG_NAME);\n\n\tfullsavedname = va(\"%s/%s\", dir, auto_matchname);\n\tif ((num = Util_Extend_Filename(fullsavedname, exts)) == -1) {\n\t\tCom_Printf(\"Error: no available filenames\\n\");\n\t\treturn;\n\t}\n\tsnprintf (savedname, sizeof(savedname), \"%s_%03i.log\", auto_matchname, num);\n\n\tfullsavedname = va(\"%s/%s\", dir, savedname);\n\n\t\n\tif (!(f = fopen(tempname, \"rb\")))\n\t\treturn;\n\tfclose(f);\n\n\tif ((error = rename(tempname, fullsavedname))) {\n\t\tFS_CreatePath(fullsavedname);\n\t\terror = rename(tempname, fullsavedname);\n\t}\n\n\tif (!error) {\n\t\tCom_Printf(\"Match console log saved to %s\\n\", savedname);\n\t\tif (allow_upload && MT_Challenge_Log_IsUploadAllowed()) {\n\t\t\t// note: we allow the client to be a spectator, so that spectators\n\t\t\t// can submit logs for matches they spec in case players don't do it\n\t\t\tLog_AutoLogging_Upload(fullsavedname);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/logging.h",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef __LOGGING_H_\n\n#define __LOGGING_H_\n\nqbool Log_IsLogging(void);\nvoid Log_Init(void);\nvoid Log_Shutdown(void);\nvoid Log_Write(char *s);\n\nvoid Log_AutoLogging_StopMatch(void);\nvoid Log_AutoLogging_CancelMatch(void);\nvoid Log_AutoLogging_StartMatch(char *logname);\nqbool Log_AutoLogging_Status(void);\nvoid Log_AutoLogging_SaveMatch(qbool allow_upload);\nqbool Log_TempLogUploadPending(void);\nqbool MT_Challenge_Log_IsUploadAllowed(void);\nvoid Log_AutoLogging_Upload(const char *filename);\nqbool MT_ChallengeSpecified(void);\n\n#define TEMP_LOG_NAME \"_!_temp_!_.log\"\n\nextern cvar_t log_readable;\nextern cvar_t log_dir;\n\n#endif\n"
  },
  {
    "path": "src/macro_definitions.h",
    "content": "\n#ifndef EZQUAKE_MACRO_DEFINITIONS_H\n#define EZQUAKE_MACRO_DEFINITIONS_H\n\n#define MACRO_DEF(x) macro_ ## x\n\ntypedef enum {\n#include \"macro_ids.h\"\n\tnum_macros\n} macro_id;\n\n#undef MACRO_DEF\n\n#endif // EZQUAKE_MACRO_DEFINITIONS_H\n"
  },
  {
    "path": "src/macro_ids.h",
    "content": "\nMACRO_DEF(health),\nMACRO_DEF(armortype),\nMACRO_DEF(armor),\nMACRO_DEF(colored_armor),\nMACRO_DEF(colored_powerups),\nMACRO_DEF(colored_short_powerups),\nMACRO_DEF(tp_powerups),\nMACRO_DEF(shells),\nMACRO_DEF(nails),\nMACRO_DEF(rockets),\nMACRO_DEF(cells),\nMACRO_DEF(weapons),\nMACRO_DEF(ammo),\nMACRO_DEF(bestweapon),\nMACRO_DEF(bestammo),\nMACRO_DEF(powerups),\nMACRO_DEF(location),\nMACRO_DEF(deathloc),\nMACRO_DEF(tookatloc),\nMACRO_DEF(tookloc),\nMACRO_DEF(took),\nMACRO_DEF(pointatloc),\nMACRO_DEF(pointloc),\nMACRO_DEF(point),\nMACRO_DEF(need),\nMACRO_DEF(droploc),\nMACRO_DEF(droptime),\nMACRO_DEF(ledpoint),\nMACRO_DEF(ledstatus),\nMACRO_DEF(lastloc),\nMACRO_DEF(lastpowerup),\nMACRO_DEF(cam_pos_x),\nMACRO_DEF(cam_pos_y),\nMACRO_DEF(cam_pos_z),\nMACRO_DEF(cam_pos),\nMACRO_DEF(cam_angles_pitch),\nMACRO_DEF(cam_angles_yaw),\nMACRO_DEF(cam_angles_roll),\nMACRO_DEF(cam_angles),\nMACRO_DEF(demoname),\nMACRO_DEF(demolength),\nMACRO_DEF(connectiontype),\nMACRO_DEF(demoplayback),\nMACRO_DEF(demotime),\nMACRO_DEF(rand),\nMACRO_DEF(matchstatus),\nMACRO_DEF(serverip),\nMACRO_DEF(conwidth),\nMACRO_DEF(conheight),\nMACRO_DEF(matchname),\nMACRO_DEF(matchtype),\nMACRO_DEF(mp3info),\nMACRO_DEF(mp3_volume),\nMACRO_DEF(lastip),\nMACRO_DEF(qt),\nMACRO_DEF(latency),\nMACRO_DEF(ping),\nMACRO_DEF(time),\nMACRO_DEF(timestamp),\nMACRO_DEF(date),\nMACRO_DEF(dateiso),\nMACRO_DEF(weaponnum),\nMACRO_DEF(weapon),\nMACRO_DEF(tf_skin),\nMACRO_DEF(gamedir),\nMACRO_DEF(triggermatch),\nMACRO_DEF(team1),\nMACRO_DEF(team2),\n\n// leave trailing ,\n"
  },
  {
    "path": "src/match_tools.c",
    "content": "/*\n\nCopyright (C) 2002-2003       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: match_tools.c,v 1.32 2007-10-13 01:59:54 himan Exp $\n*/\n\n\n#include \"quakedef.h\"\n#include <time.h>\n#include \"logging.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"utils.h\"\n#include <curl/curl.h>\n#include \"sha1.h\"\n#include <time.h>\n\nvoid MT_Challenge_StartMatch(void);\nvoid MT_Challenge_Cancel(void);\nvoid MT_Challenge_InitCvars(void);\n\n#define MAX_STATIC_STRING 1024\n\ncvar_t match_format_solo = {\"match_format_solo\", \"solo/%n - [%M]\"};\ncvar_t match_format_coop = {\"match_format_coop\", \"coop/%n - [%C_player_coop] - [%M]\"};\ncvar_t match_format_race = {\"match_format_race\", \"race/%n - [race] - [%M]\"};\n\ncvar_t match_format_duel = {\"match_format_duel\", \"duel/%n - %p%v%e - [dmm%D] - [%M]\"};\ncvar_t match_format_ffa = {\"match_format_ffa\", \"ffa/%n - [%C_player_ffa] - [%M]\"};\ncvar_t match_format_2on2 = {\"match_format_2on2\", \"2on2/%n - [%k%v%l] - [%M]\"};\ncvar_t match_format_3on3 = {\"match_format_3on3\", \"tdm/%n - [%Oon%E_%t%v%e] - [%M]\"};\ncvar_t match_format_4on4 = {\"match_format_4on4\", \"tdm/%n - [%Oon%E_%t%v%e] - [%M]\"};\ncvar_t match_format_tdm = {\"match_format_tdm\", \"tdm/%n - [%Oon%E_%t%v%e] - [%M]\"};\ncvar_t match_format_multiteam = {\"match_format_multiteam\", \"tdm/%n - [%a_%b] - [%M]\"};\n\ncvar_t match_format_arena = {\"match_format_arena\", \"arena/%n - %p%v%e - [%F_frags] - [%M]\"};\ncvar_t match_format_tf_duel = {\"match_format_tf_duel\", \"tfduel/%n - %p%v%e [%M]\"};\ncvar_t match_format_tf_clanwar = {\"match_format_tf_clanwar\", \"tfwar/%n - [%Oon%E_%t%v%e] - [%M]\"};\n\ncvar_t match_name_and = {\"match_name_and\", \"_&_\"};\t\t\t\ncvar_t match_name_versus = {\"match_name_versus\", \"_vs_\"};\t\ncvar_t match_name_on = {\"match_name_on\", \"on\"};\t\t\t\t\ncvar_t match_name_nick = {\"match_name_nick\", \"\"};\t\t\t\ncvar_t match_name_spec = {\"match_name_spec\", \"(SPEC)\"};\t\t\n\nchar *CL_DemoDirectory(void);\n\nstatic char *MT_CleanString(char *string, qbool allow_spaces_and_slashes) {\n\tbyte *in, *out, c, d, *disallowed;\n\tstatic byte buf[MAX_STATIC_STRING], badchars[] = {' ', '\\\\', '/', '?', '*', ':', '<', '>', '\"', '|', '\\0'};\n\textern char readableChars[];\n\n\tdisallowed = allow_spaces_and_slashes ? badchars + 3 : badchars;\n\n\t#define CLEANCHAR(c) \\\n\t\t((readableChars[(byte) c] < ' ' || strchr((char *)disallowed, readableChars[(byte) c])) ? '_' : readableChars[(byte) c])\n\n\tin = (byte *) string;\n\tout = buf;\n\n\twhile ((c = *in++) && out - buf < sizeof(buf) - 1) {\n\t\td = CLEANCHAR(c);\n\t\t*out++ = d;\n\t\tif (d == '_') {\n\t\t\tfor ( ; *in && CLEANCHAR(*in) == '_'; in++)\n\t\t\t\t;\n\t\t}\n\t}\n\n\t#undef CLEANCHAR\n\n\t*out = 0;\n\tUtil_Process_Filename((char *) buf);\n\treturn (char *) buf;\n}\n\nchar* MT_PlayerName(void)\n{\n\treturn TP_PlayerName();\n}\n\nchar* MT_PlayerTeam(void)\n{\n\treturn TP_PlayerTeam();\n}\n\nchar* MT_EnemyName(void)\n{\n\tint i;\n\tchar *myname, *name;\n\tstatic char\tenemyname[MAX_INFO_STRING];\n\n\tenemyname[0] = 0;\n\tmyname = MT_PlayerName ();\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (cl.players[i].name[0] && !cl.players[i].spectator) {\n\t\t\tname = Info_ValueForKey(cl.players[i].userinfo, \"name\");\n\t\t\tif (strcmp(name, myname)) {\n\t\t\t\tstrlcpy(enemyname, name, sizeof (enemyname));\n\t\t\t\treturn enemyname;\n\t\t\t}\n\t\t}\n\t}\n\treturn enemyname;\n}\n\nstatic char *MT_EnemyTeam(void) {\n\tint i;\n\tchar *myteam, *team;\n\tstatic char\tenemyteam[MAX_INFO_STRING];\n\n\tenemyteam[0] = 0;\n\tmyteam = MT_PlayerTeam();\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (cl.players[i].name[0] && !cl.players[i].spectator) {\n\t\t\tteam = cl.players[i].team;\n\t\t\tif (team[0] && strcmp(team, myteam)) {\n\t\t\t\tstrlcpy (enemyteam, team, sizeof (enemyteam));\n\t\t\t\treturn enemyteam;\n\t\t\t}\n\t\t}\n\t}\n\treturn enemyteam;\n}\n\nint MT_CountPlayers(void)\n{\n\treturn TP_CountPlayers();\n}\n\nstatic int MT_CountTeamMembers(char *team)\n{\n\tint i, count = 0;\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!cl.players[i].name[0] || cl.players[i].spectator)\n\t\t\tcontinue;\n\t\tif (!strcmp(cl.players[i].team, team))\n\t\t\tcount++;\n\t}\n\treturn count;\n}\n\nstatic char *MT_NameAndClean_TeamMembers(char *team) {\n\tstatic char namebuf[MAX_STATIC_STRING];\n\tint i;\n\n\tnamebuf[0] = 0;\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!cl.players[i].name[0] || cl.players[i].spectator)\n\t\t\tcontinue;\n\t\tif (!strcmp(cl.players[i].team, team)) {\n\t\t\tif (namebuf[0])\n\t\t\t\tstrlcat(namebuf, match_name_and.string, sizeof (namebuf) - strlen (namebuf));\n\n\t\t\tstrlcat(namebuf, MT_CleanString(cl.players[i].name, false), sizeof (namebuf) - strlen (namebuf));\n\t\t}\n\t}\n\treturn namebuf;\n}\n\nstatic char *MT_MapName(void) {\n\tstatic char buf[MAX_OSPATH];\n\n\tstrlcpy(buf, TP_MapName(), sizeof(buf));\n\treturn buf;\n}\n\nstatic void MT_GetPlayerNames(char *name1, char *name2) {\n\tint i;\n\tchar *s1 = NULL, *s2 = NULL;\n\n\tname1[0] = name2[0] = 0;\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!cl.players[i].name[0] || cl.players[i].spectator)\n\t\t\tcontinue;\n\t\tif (!s1) {\n\t\t\ts1 = cl.players[i].name;\n\t\t} else {\n\t\t\ts2 = cl.players[i].name;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (s1)\n\t\tstrcpy(name1, s1);\n\tif (s2)\n\t\tstrcpy(name2, s2);\n}\n\nstatic int MT_GetTeamNames(char teams[][MAX_INFO_STRING], int max) {\n\tint i, j, count = 0;\n\n\tmemset(teams, 0, sizeof(*teams));\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!cl.players[i].name[0] || cl.players[i].spectator)\n\t\t\tcontinue;\n\n\t\tfor (j = 0; j < i; j++) {\n\t\t\tif (!cl.players[j].name[0] || cl.players[j].spectator)\n\t\t\t\tcontinue;\n\n\t\t\tif (!strcmp(cl.players[i].team, cl.players[j].team))\n\t\t\t\tbreak;\n\t\t}\n\t\tif (j == i) {\n\t\t\tstrlcpy (teams[count], cl.players[i].team, MAX_INFO_STRING);\n\t\t\tcount++;\n\t\t}\n\t\tif (count == max)\n\t\t\tbreak;\n\t}\n\treturn count;\n}\n\nstatic char *MT_Serverinfo_Race(void) {\n\tstatic char buf[MAX_OSPATH];\n\n\tstrlcpy(buf, Info_ValueForKey(cl.serverinfo, \"race\"), sizeof(buf));\n\treturn buf;\n}\n\n\ntypedef enum {\n\tmt_duel, mt_ffa,\n\tmt_2on2, mt_3on3, mt_4on4, mt_tdm,\n\tmt_multiteam,\n\tmt_arena,\n\tmt_tf_duel, mt_tf_clanwar,\n\tmt_solo,\t\t\n\tmt_coop,\t\t\n\tmt_race,\t\t\n\tmt_empty,\t\t\n\tmt_unknown_gamedir,\n\tmt_unknown,\n\tmt_numtypes,\n} matchtype_t;\n\ntypedef struct matchcvar_s {\n\tmatchtype_t matchtype;\n\tchar\t\t*nickname;\n\tcvar_t\t\t*format;\n\tqbool\tautosshot;\n} matchcvar_t;\n\nstatic matchcvar_t matchcvars[mt_numtypes] = {\n\t{mt_duel, \"duel\", &match_format_duel, true},\n\t{mt_ffa, \"ffa\", &match_format_ffa, true},\n\t{mt_2on2, \"2on2\", &match_format_2on2, true},\n\t{mt_3on3, \"3on3\", &match_format_3on3, true},\n\t{mt_4on4, \"4on4\", &match_format_4on4, true},\n\t{mt_tdm, \"tdm\", &match_format_tdm, true},\n\t{mt_multiteam, \"tdm\", &match_format_multiteam, true},\n\t{mt_arena, \"arena\", &match_format_arena, true},\n\t{mt_tf_duel, \"tfduel\", &match_format_tf_duel, true},\n\t{mt_tf_clanwar, \"tfclanwar\", &match_format_tf_clanwar, true},\n\t{mt_solo, \"solo\", &match_format_solo, false},\n\t{mt_coop, \"coop\", &match_format_coop, false},\n\t{mt_race, \"race\", &match_format_race, false},\n\t{mt_empty, \"empty\", NULL, false},\n\t{mt_unknown_gamedir, \"unknown\", NULL, false},\n\t{mt_unknown, \"unknown\", NULL, false},\n};\n\ntypedef struct matchinfo_s {\n\tqbool spectator;\n\tchar myname[MAX_INFO_STRING];\n\tchar player1[MAX_INFO_STRING];\n\tchar player2[MAX_INFO_STRING];\n\tchar team1[MAX_INFO_STRING];\n\tchar team2[MAX_INFO_STRING];\n\tint team1count;\n\tint team2count;\n\tint numteams;\n\tchar team1names[MAX_STATIC_STRING];\t\n\tchar team2names[MAX_STATIC_STRING];\t\n\tchar multiteamnames[MAX_STATIC_STRING];\t\n\tchar multiteamcounts[128];\t\t\t\t\n\tint maxteamsize;\n\tint numplayers;\n\tint timelimit;\n\tint fraglimit;\n\tint teamplay;\n\tint maxclients;\n\tint deathmatch;\n\tchar mapname[MAX_OSPATH];\n\tchar gamedir[MAX_OSPATH];\n\tmatchtype_t matchtype;\n\tchar day[8];\n\tchar month[8];\n\tchar year[8];\n\tchar bigyear[8];\n\tchar hour[8];\n\tchar minute[8];\n\tchar second[8];\n} matchinfo_t;\n\nstatic matchtype_t MT_GetMatchType(matchinfo_t *matchinfo) {\n\n\tif (matchinfo->numplayers < 1)\n\t\treturn mt_empty;\n\n\n\tif (!strcasecmp(matchinfo->gamedir, \"qw\") && !strcmp(MT_Serverinfo_Race(), matchinfo->mapname))\n\t\treturn mt_race;\n\n\n\tif (matchinfo->numplayers < 2)\n\t\treturn mt_solo;\n\n\n\tif (cl.teamfortress) {\n\t\treturn\t(matchinfo->numplayers == 2) ? mt_tf_duel : \n\t\t\t\t(matchinfo->numteams < 2) ? mt_unknown : mt_tf_clanwar;\n\t}\n\n\tif (strstr(matchinfo->gamedir, \"arena\"))\n\t\treturn mt_arena;\n\n\tif (!matchinfo->deathmatch)\n\t\treturn mt_coop;\n\n\n\tif (matchinfo->numplayers == 2)\n\t\treturn (!matchinfo->teamplay || matchinfo->numteams == 2) ? mt_duel : mt_unknown;\n\n\tif (!matchinfo->teamplay || matchinfo->maxteamsize <= 1)\n\t\treturn mt_ffa;\n\n\n\tif (matchinfo->numteams !=  2)\n\t\treturn (matchinfo->numteams > 2) ? mt_multiteam : mt_unknown;\n\n\tswitch (matchinfo->maxteamsize) {\n\t\tcase 2: return mt_2on2;\n\t\tcase 3: return mt_3on3;\n\t\tcase 4: return mt_4on4;\n\t\tdefault: return mt_tdm;\n\t}\n}\n\nstatic matchinfo_t *MT_GetMatchInfo(void) {\n\tstatic matchinfo_t matchinfo;\n\tchar teamnames[MAX_CLIENTS][MAX_INFO_STRING];\n\tint i, numteams, maxteamsize, teamsize;\n\ttime_t t;\n\tstruct tm *ptm;\n\n\tmemset(&matchinfo, 0, sizeof(matchinfo));\n\tnumteams = MT_GetTeamNames(teamnames, MAX_CLIENTS);\n\n\tmatchinfo.spectator = cl.spectator;\n\tstrlcpy(matchinfo.myname, MT_PlayerName(), sizeof(matchinfo.myname));\n\n\tif (cl.spectator) {\n\t\tMT_GetPlayerNames(matchinfo.player1, matchinfo.player2);\n\t\tstrlcpy(matchinfo.team1, teamnames[0], sizeof(matchinfo.team1));\n\t\tstrlcpy(matchinfo.team2, teamnames[1], sizeof(matchinfo.team2));\n\t} else {\n\t\tstrlcpy(matchinfo.player1, MT_PlayerName(), sizeof(matchinfo.player1));\n\t\tstrlcpy(matchinfo.player2, MT_EnemyName(), sizeof(matchinfo.player2));\n\t\tstrlcpy(matchinfo.team1, MT_PlayerTeam(), sizeof(matchinfo.team1));\n\t\tstrlcpy(matchinfo.team2, MT_EnemyTeam(), sizeof(matchinfo.team2));\n\t}\n\n\n\tmatchinfo.team1count = MT_CountTeamMembers(matchinfo.team1);\n\tmatchinfo.team2count = MT_CountTeamMembers(matchinfo.team2);\n\tmatchinfo.numteams = numteams;\n\tstrlcpy(matchinfo.team1names, MT_NameAndClean_TeamMembers(matchinfo.team1), sizeof(matchinfo.team1names));\n\tstrlcpy(matchinfo.team2names, MT_NameAndClean_TeamMembers(matchinfo.team2), sizeof(matchinfo.team2names));\n\n\n#define CLEANFIELD(x) strlcpy(matchinfo.x, MT_CleanString(matchinfo.x, false), sizeof(matchinfo.x));\t\n\tCLEANFIELD(myname);\n\tCLEANFIELD(player1);\n\tCLEANFIELD(player2);\n\tCLEANFIELD(team1);\n\tCLEANFIELD(team2);\n#undef CLEANFIELD\n\n#define BUF matchinfo.multiteamnames\n\tfor (i = 0; i < numteams; i++) {\n\t\tstrlcat (BUF, MT_CleanString(teamnames[i], false), sizeof (BUF) - strlen (BUF));\n\t\tif (i < numteams - 1)\n\t\t\tstrlcat (BUF, match_name_versus.string, sizeof (BUF) - strlen (BUF));\n\t}\n#undef BUF\n\n\tmaxteamsize = 0;\n#define BUF matchinfo.multiteamcounts\n\tfor (i = 0; i < numteams; i++) {\n\t\tteamsize = MT_CountTeamMembers(teamnames[i]);\n\t\tif (*teamnames[i])\n\t\t\tmaxteamsize = max(maxteamsize, teamsize);\n\t\tstrlcat (BUF, va(\"%d\", teamsize), sizeof (BUF) - strlen (BUF));\n\t\tif (i < numteams - 1)\n\t\t\tstrlcat(BUF, match_name_on.string, sizeof (BUF) - strlen (BUF));\n\t}\n\tmatchinfo.maxteamsize = maxteamsize;\n#undef BUF\n\n\tmatchinfo.numplayers = MT_CountPlayers();\n\n\tmatchinfo.timelimit = Q_atoi(Info_ValueForKey(cl.serverinfo, \"timelimit\"));\n\tmatchinfo.fraglimit = Q_atoi(Info_ValueForKey(cl.serverinfo, \"fraglimit\"));\n\tmatchinfo.teamplay = Q_atoi(Info_ValueForKey(cl.serverinfo, \"teamplay\"));\n\tmatchinfo.maxclients = Q_atoi(Info_ValueForKey(cl.serverinfo, \"maxclients\"));\n\tmatchinfo.deathmatch = cl.deathmatch;\n\n\tstrlcpy(matchinfo.mapname, MT_MapName(), sizeof(matchinfo.mapname));\n\tstrlcpy(matchinfo.gamedir, cls.gamedirfile, sizeof(matchinfo.gamedir));\n\n\tmatchinfo.matchtype = MT_GetMatchType(&matchinfo);\n\n\ttime(&t);\n\tif ((ptm = localtime(&t))) {\n\t\tstrftime (matchinfo.day, sizeof(matchinfo.day) - 1, \"%d\", ptm);\n\t\tstrftime (matchinfo.month, sizeof(matchinfo.month) - 1, \"%m\", ptm);\n\t\tstrftime (matchinfo.year, sizeof(matchinfo.year) - 1, \"%y\", ptm);\n\t\tstrftime (matchinfo.bigyear, sizeof(matchinfo.bigyear) - 1, \"%Y\", ptm);\n\t\tstrftime (matchinfo.hour, sizeof(matchinfo.hour) - 1, \"%H\", ptm);\n\t\tstrftime (matchinfo.minute, sizeof(matchinfo.minute) - 1, \"%M\", ptm);\n\t\tstrftime (matchinfo.second, sizeof(matchinfo.second) - 1, \"%S\", ptm);\n\t}\n\n\treturn &matchinfo;\n}\n\nvoid MT_Macrolist_f(void) {\n\tint argc;\n\n\tswitch((argc = Cmd_Argc())) {\n\tcase 1:\n\t\tCom_Printf(\"\\x02The following macros can be used to name your matches\\n\");\n\t\tCom_Printf(\"\\x02The square brackets apply when spectating a match\\n\\n\");\n\t\tCom_Printf(\"\\x02%%n\"); Com_Printf(\" - your nick [followed by match_name_spec]\\n\");\n\t\tCom_Printf(\"\\x02%%p\"); Com_Printf(\" - your name [player1's name]\\n\");\n\t\tCom_Printf(\"\\x02%%t\"); Com_Printf(\" - your team [team1's name]\\n\");\n\t\tCom_Printf(\"\\x02%%e\"); Com_Printf(\" - enemy nick in duels, enemy team in tp [player2/team2]\\n\");\n\n\t\tCom_Printf(\"\\x02%%k\"); Com_Printf(\" - names of players on your team [team1]\\n\");\n\t\tCom_Printf(\"\\x02%%l\"); Com_Printf(\" - names of players on enemy team [team2]\\n\");\n\n\t\tCom_Printf(\"\\x02%%O\"); Com_Printf(\" - number of teammates [number on team1]\\n\");\n\t\tCom_Printf(\"\\x02%%E\"); Com_Printf(\" - number of enemies [number on team2]\\n\");\n\t\tCom_Printf(\"\\x02%%C\"); Com_Printf(\" - number of players on the server\\n\");\n\n\t\tCom_Printf(\"\\x02%%a\"); Com_Printf(\" - team counts separated by match_name_on (eg 4on3on4)\\n\");\n\t\tCom_Printf(\"\\x02%%b\"); Com_Printf(\" - team names separated by match_name_versus\\n\");\n\t\tCom_Printf(\"\\x02%%v\"); Com_Printf(\" - shortcut for $match_name_versus\\n\");\n\n\t\tCom_Printf(\"\\x02%%T\"); Com_Printf(\" - timelimit on the server\\n\");\n\t\tCom_Printf(\"\\x02%%F\"); Com_Printf(\" - fraglimit n the server\\n\");\n\t\tCom_Printf(\"\\x02%%p\"); Com_Printf(\" - teamplay setting on the server\\n\");\n\t\tCom_Printf(\"\\x02%%D\"); Com_Printf(\" - deathmatch mode on the server\\n\");\n\n\t\tCom_Printf(\"\\x02%%M\"); Com_Printf(\" - mapname\\n\");\n\t\tCom_Printf(\"\\x02%%G\"); Com_Printf(\" - gamedir (eg. qw, fortress, arena, etc)\\n\");\n\n\t\tCom_Printf(\"\\x02%%d\"); Com_Printf(\" - day\\n\");\n\t\tCom_Printf(\"\\x02%%m\"); Com_Printf(\" - month\\n\");\n\t\tCom_Printf(\"\\x02%%y\"); Com_Printf(\" - year (without century)\\n\");\n\t\tCom_Printf(\"\\x02%%Y\"); Com_Printf(\" - year (with century)\\n\");\n\n\t\tCom_Printf(\"\\x02%%H\"); Com_Printf(\" - hour\\n\");\n\t\tCom_Printf(\"\\x02%%Q\"); Com_Printf(\" - minute\\n\");\n\t\tCom_Printf(\"\\x02%%S\"); Com_Printf(\" - second\\n\");\n\n\t\tbreak;\n\tdefault:\n\t\tCom_Printf(\"%s : no arguments expected\\n\", Cmd_Argv(0));\n\t\tbreak;\n\t}\n}\n\nstatic char *MT_ParseFormat(char *format, matchinfo_t *matchinfo) {\n\tstatic char buf[MAX_STATIC_STRING];\n\tchar c, *out, *in, *temp;\n\n\tbuf[0] = 0;\n\tout = buf;\n\tin = format;\n\n\twhile ((c = *in++) && (out - buf < MAX_STATIC_STRING - 1)) {\n\t\tif ((c == '%') && *in) {\n\t\t\tswitch((c = *in++)) {\n\t\t\t\tcase '%':\n\t\t\t\t\ttemp = \"%\"; break;\n\t\t\t\tcase 'n':\n\t\t\t\t\ttemp = match_name_nick.string[0] ? match_name_nick.string : matchinfo->myname;\n\t\t\t\t\ttemp = cl.spectator ? va(\"%s%s\", temp, match_name_spec.string) : temp;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'p':\n\t\t\t\t\ttemp = matchinfo->player1; break;\n\t\t\t\tcase 't':\n\t\t\t\t\ttemp = matchinfo->team1; break;\n\t\t\t\tcase 'e':\n\t\t\t\t\ttemp = matchinfo->numplayers == 2 ? matchinfo->player2 : matchinfo->team2; break;\n\t\t\t\tcase 'k':\n\t\t\t\t\ttemp = matchinfo->team1names; break;\n\t\t\t\tcase 'l':\n\t\t\t\t\ttemp = matchinfo->team2names; break;\n\t\t\t\tcase 'O':\n\t\t\t\t\ttemp = matchinfo->teamplay ? va(\"%d\", matchinfo->team1count) : \"1\"; break;\n\t\t\t\tcase 'E':\n\t\t\t\t\ttemp = va(\"%d\", matchinfo->teamplay ? matchinfo->team2count : matchinfo->numplayers - 1); break;\n\t\t\t\tcase 'C':\n\t\t\t\t\ttemp = va(\"%d\", matchinfo->numplayers); break;\n\t\t\t\tcase 'a':\n\t\t\t\t\ttemp = matchinfo->multiteamcounts; break;\n\t\t\t\tcase 'b':\n\t\t\t\t\ttemp = matchinfo->multiteamnames; break;\n\t\t\t\tcase 'v':\n\t\t\t\t\ttemp = match_name_versus.string; break;\n\t\t\t\tcase 'T':\n\t\t\t\t\ttemp = va(\"%d\", matchinfo->timelimit); break;\n\t\t\t\tcase 'F':\n\t\t\t\t\ttemp = va(\"%d\", matchinfo->fraglimit); break;\n\t\t\t\tcase 'N':\n\t\t\t\t\ttemp = va(\"%d\", matchinfo->teamplay); break;\n\t\t\t\tcase 'D':\n\t\t\t\t\ttemp = va(\"%d\", matchinfo->deathmatch); break;\n\t\t\t\tcase 'M':\n\t\t\t\t\ttemp = matchinfo->mapname; break;\n\t\t\t\tcase 'G':\n\t\t\t\t\ttemp = matchinfo->gamedir; break;\n\t\t\t\tcase 'd':\n\t\t\t\t\ttemp = matchinfo->day; break;\n\t\t\t\tcase 'm':\n\t\t\t\t\ttemp =  matchinfo->month; break;\n\t\t\t\tcase 'y':\n\t\t\t\t\ttemp = matchinfo->year; break;\n\t\t\t\tcase 'Y':\n\t\t\t\t\ttemp = matchinfo->bigyear; break;\n\t\t\t\tcase 'H':\n\t\t\t\t\ttemp = matchinfo->hour; break;\n\t\t\t\tcase 'Q':\n\t\t\t\t\ttemp = matchinfo->minute; break;\n\t\t\t\tcase 'S':\n\t\t\t\t\ttemp = matchinfo->second; break;\n\t\t\t\tdefault:\n\t\t\t\t\ttemp = va(\"%%%c\", c); break;\n\t\t\t}\n\t\t\tstrlcpy(out, temp, sizeof(buf) - (out - buf));\n\t\t\tout += strlen(temp);\n\t\t} else {\n\t\t\t*out++ = c;\n\t\t}\n\t}\n\n\t*out = 0;\n\treturn buf;\n}\n\nstatic char *MT_NameForMatchInfo(matchinfo_t *matchinfo) {\n\tchar *format = NULL;\n\n\tswitch (matchinfo->matchtype) {\n\tcase mt_empty:\n\t\tformat = \"%n - [%M]\"; break;\n\tcase mt_unknown_gamedir:\n\t\tformat = \"%n - [gamedir - %G, %C players] - [%M]\"; break;\n\tcase mt_unknown:\n\t\tformat = \"%n - [Unknown Game, gamedir - %G, %C players] - [%M]\"; break;\n\tdefault:\n\t\tif (matchinfo->matchtype < mt_numtypes) {\n\t\t\tformat = matchcvars[matchinfo->matchtype].format->string;\n\t\t} else {\n\t\t\tSys_Error(\"Macro_Matchdesc : Unknown match type %d\", matchinfo->matchtype);\n\t\t}\n\t\tbreak;\n\t}\n\tif (!format)\n\t\tSys_Error(\"MT_NameForMatchInfo: NULL format\");\n\n\treturn MT_CleanString(MT_ParseFormat(format, matchinfo), true);\n}\n\nchar *Macro_MatchName(void) {\n\treturn (cls.state < ca_active) ? \"No match in progress\" : MT_NameForMatchInfo(MT_GetMatchInfo());\n}\n\nchar *MT_MatchName(void) {\n\tstatic char buf[MAX_STATIC_STRING];\n\n\tstrlcpy(buf, Macro_MatchName(), sizeof(buf));\n\treturn buf;\n}\n\nchar *MT_ShortStatus(void)\n{\n\tint maxclients = Q_atoi(Info_ValueForKey(cl.serverinfo, \"maxclients\"));\n\tchar *mapname = TP_MapName();\n\n\treturn va(\"%d/%d - %s\", TP_CountPlayers(), maxclients, mapname);\n}\n\n#define MT_SCOREBOARD_SHOWIME\t4\n\nvoid MT_TakeScreenshot(void);\n\ncvar_t match_auto_record = {\"match_auto_record\", \"0\"};\ncvar_t match_auto_logconsole = {\"match_auto_logconsole\", \"1\"};\ncvar_t match_auto_sshot = {\"match_auto_sshot\", \"0\"};\ncvar_t match_auto_minlength = {\"match_auto_minlength\", \"30\"};\ncvar_t match_auto_spectating = {\"match_auto_spectating\", \"0\"};\ncvar_t match_auto_unminimize = {\"match_auto_unminimize\", \"1\"};\n\ntypedef struct mt_matchtstate_s {\n\tqbool standby;\n\tint intermission;\n\tint status;\n\tfloat starttime;\n\tfloat endtime;\n\tchar matchname[2 * MAX_OSPATH];\n\tmatchtype_t matchtype;\n} mt_matchstate_t;\n\nstatic mt_matchstate_t matchstate;\n\nstatic void MT_Delayed_EndMatch(void) {\n\tmatchstate.endtime = 0;\n\n\tLog_AutoLogging_StopMatch();\n\tCL_AutoRecord_StopMatch();\n}\n\nstatic void MT_EndMatch(void) {\n\tMT_TakeScreenshot();\n\n\tif (!matchstate.status)\n\t\treturn;\n\n\tmatchstate.starttime = 0;\n\tmatchstate.status = 0;\n \tmatchstate.endtime = cls.realtime;\n}\n\nstatic void MT_CancelMatch(void) {\n\tif (matchstate.endtime) {\n\t\tMT_Delayed_EndMatch();\n\t}\n\n\tif (!matchstate.status)\n\t\treturn;\n\n\tmatchstate.starttime = 0;\n\tmatchstate.status = 0;\n\tmatchstate.endtime = 0;\t\n\n\tLog_AutoLogging_CancelMatch();\n\tCL_AutoRecord_CancelMatch();\n\tMT_Challenge_Cancel();\n}\n\nstatic void MT_StartMatch(void)\n{\n\tif (matchstate.endtime) {\n\t\tMT_Delayed_EndMatch();\n\t}\n\n\tif (matchstate.status) {\n\t\tMT_CancelMatch();\n\t}\n\n\tmatchstate.status = 1;\n\tmatchstate.starttime = cls.realtime;\n\tmatchstate.endtime = 0;\n\n\t// disconnect: match_forcestart resets gameclock\n\tcl.standby = false;\n\tcl.countdown = false;\n\tcl.gametime = 0;\n\tcl.gamestarttime = Sys_DoubleTime();\n\n\tif (cls.state < ca_active) {\n\t\tmatchstate.matchtype = mt_empty;\n\t\tstrlcpy(matchstate.matchname, \"No match in progress\", sizeof(matchstate.matchname));\n\t} else {\n\t\tmatchinfo_t *matchinfo = MT_GetMatchInfo();\n\t\tmatchstate.matchtype = matchinfo->matchtype;\n\t\tstrlcpy(matchstate.matchname, MT_NameForMatchInfo(matchinfo), sizeof(matchstate.matchname));\n\t}\n\n\tMT_Challenge_StartMatch();\n\n\tCL_AutoRecord_StartMatch(matchstate.matchname);\n\tLog_AutoLogging_StartMatch(matchstate.matchname);\n\n\tStats_Reset();\n}\n\nstatic void MT_ClearClientState(void)\n{\n\tmemset(&matchstate, 0, sizeof(matchstate));\n\tcl.gamestarttime = Sys_DoubleTime();\n\tcl.gamepausetime = 0;\n}\n\nvoid MT_Frame(void)\n{\n\tif (matchstate.endtime && cls.realtime >= matchstate.endtime + MT_SCOREBOARD_SHOWIME) {\n\t\tMT_Delayed_EndMatch();\n\t}\n\n\tif (cls.state != ca_active || cls.demoplayback) {\n\t\treturn;\n\t}\n\n\tif (matchstate.standby && !cl.standby && !cl.intermission) {\n\t\tif (!cl.spectator || match_auto_spectating.value) {\n\t\t\tMT_StartMatch();\n\t\t}\n\n\t\tif (match_auto_unminimize.integer == 2 || (match_auto_unminimize.integer == 1 && !cl.spectator)) {\n\t\t\tVID_Restore();\n\t\t}\n\t}\n\n\tif (!matchstate.intermission && cl.intermission) {\n\t\tMT_EndMatch();\n\t}\n\telse if (matchstate.status && !matchstate.standby && cl.standby) {\n\t\tMT_CancelMatch();\n\t}\n\n\tmatchstate.standby = cl.standby;\n\tmatchstate.intermission = cl.intermission;\n}\n\nvoid MT_NewMap(void)\n{\n\tMT_CancelMatch();\n\tMT_ClearClientState();\n}\n\nvoid MT_Disconnect(void)\n{\n\tMT_CancelMatch();\n\tMT_ClearClientState();\n}\n\nchar* MT_TempDirectory(void)\n{\n\tstatic char dir[MAX_OSPATH * 2] = {0};\n\n\tif (!dir[0]) {\n\t\tsnprintf(dir, sizeof(dir), \"%s/temp\", com_homedir);\n\t}\n\treturn dir;\n}\n\nchar *MT_TempDemoDirectory(void)\n{\n\tstatic char dir[MAX_OSPATH * 2] = {0};\n\n\tif (!dir[0])\n\t\tsnprintf(dir, sizeof(dir), \"%s/temp\", CL_DemoDirectory());\n\treturn dir;\n}\n\nvoid MT_SaveMatch_f(void)\n{\n\tint demo_status, log_status;\n\n\tdemo_status = CL_AutoRecord_Status();\n\tlog_status = Log_AutoLogging_Status();\n\n\tif (Log_TempLogUploadPending()) {\n\t\tCom_Printf(\"Log upload is still in progress\\n\");\n\t\treturn;\n\t}\n\n\tif ((demo_status & 2) || (log_status & 2)) {\n\t\tCom_Printf(\"\\x02Saving match...\\n\");\n\t\tCL_AutoRecord_SaveMatch();\n\t\tLog_AutoLogging_SaveMatch(false);\n\t}\n\telse {\n\t\tif ((demo_status & 1) || (log_status & 1)) {\n\t\t\tif ((demo_status & 1) && (log_status & 1)) {\n\t\t\t\tCom_Printf(\"Auto demo recording and console logging still in progress\\n\");\n\t\t\t}\n\t\t\telse if ((demo_status & 1)) {\n\t\t\t\tCom_Printf(\"Auto demo recording still in progress\\n\");\n\t\t\t}\n\t\t\telse if ((log_status & 1)) {\n\t\t\t\tCom_Printf(\"Auto console logging still in progress\\n\");\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"Nothing to save!\\n\");\n\t\t}\n\t}\n}\n\nvoid MT_Match_ForceStart_f(void)\n{\n\tswitch (Cmd_Argc()) {\n\tcase 1:\n\t\tif (cls.state != ca_active || cls.demoplayback) {\n\t\t\tCom_Printf(\"You must be connected to a server before using \\\"%s\\\"\\n\", Cmd_Argv(0));\n\t\t\treturn;\n\t\t}\n\t\tMT_StartMatch();\n\t\tbreak;\n\tdefault:\n\t\tCom_Printf(\"%s : no arguments expected\\n\", Cmd_Argv(0));\n\t\tbreak;\n\t}\n}\n\nvoid MT_TakeScreenshot(void)\n{\n\tint i;\n\tqbool have_opponent;\n\n\tif (!match_auto_sshot.value || (cl.spectator && !match_auto_spectating.value))\n\t\treturn;\n\n\t//don't bother screen-shotting solo games etc\n\tif (!matchcvars[matchstate.matchtype].autosshot)\n\t\treturn;\n\n\t//make sure there are actually some frags on the board, and somebody besides us\n\thave_opponent = false;\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!cl.players[i].name[0] || cl.players[i].spectator)\n\t\t\tcontinue;\n\t\tif (i != cl.playernum)\n\t\t\thave_opponent = true;\t\t\n\t}\n\tif (!have_opponent) {\n\t\tCom_Printf(\"Auto screenshot cancelled\\n\");\n\t\treturn;\n\t}\n\n\tif (!matchstate.status) {\n\t\tmatchinfo_t *matchinfo = MT_GetMatchInfo();\n\t\tmatchstate.matchtype = matchinfo->matchtype;\n\t\tstrlcpy(matchstate.matchname, MT_NameForMatchInfo(matchinfo), sizeof(matchstate.matchname));\n\t}\n\tSCR_AutoScreenshot(matchstate.matchname);\n}\n\n#define MAX_GROUP_MEMBERS\t36\n\ntypedef struct mapgroup_s {\n\tchar groupname[MAX_QPATH];\n\tchar members[MAX_GROUP_MEMBERS][MAX_QPATH];\n\tstruct mapgroup_s *next, *prev;\n\tqbool system;\n\tint nummembers;\n} mapgroup_t;\n\nstatic mapgroup_t *mapgroups = NULL;\t\nstatic mapgroup_t *last_system_mapgroup = NULL;\nstatic qbool mapgroups_init = false;\t\n\n#define FIRSTUSERGROUP (last_system_mapgroup ? last_system_mapgroup->next : mapgroups)\n\nstatic mapgroup_t *GetGroupWithName(char *groupname) {\n\tmapgroup_t *node;\n\n\tfor (node = mapgroups; node; node = node->next) {\n\t\tif (!strcasecmp(node->groupname, groupname))\n\t\t\treturn node;\n\t}\n\treturn NULL;\n}\n\nstatic mapgroup_t *GetGroupWithMember(char *member) {\n\tint j;\n\tmapgroup_t *node;\n\n\tfor (node = mapgroups; node; node = node->next) {\n\t\tfor (j = 0; j < node->nummembers; j++) {\n\t\t\tif (!strcasecmp(node->members[j], member))\n\t\t\t\treturn node;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nstatic void DeleteMapGroup(mapgroup_t *group) {\n\tif (!group)\n\t\treturn;\n\n\tif (group->prev)\n\t\tgroup->prev->next = group->next;\n\tif (group->next)\n\t\tgroup->next->prev = group->prev;\n\n\t\n\tif (group == mapgroups) {\n\t\tmapgroups = mapgroups->next;\t\t\n\t\tif (group == last_system_mapgroup)\n\t\t\tlast_system_mapgroup = NULL;\n\t} else if (group == last_system_mapgroup) {\n\t\tlast_system_mapgroup = last_system_mapgroup->prev;\n\t}\n\n\tQ_free(group);\n}\n\nstatic void ResetGroupMembers(mapgroup_t *group) {\n\tint i;\n\n\tif (!group)\n\t\treturn;\n\n\tfor (i = 0; i < group->nummembers; i++)\n\t\tgroup->members[i][0] = 0;\n\n\tgroup->nummembers = 0;\n}\n\nstatic void DeleteGroupMember(mapgroup_t *group, char *member) {\n\tint i;\n\n\tif (!group)\n\t\treturn;\n\n\tfor (i = 0; i < group->nummembers; i++) {\n\t\tif (!strcasecmp(member, group->members[i]))\n\t\t\tbreak;\n\t}\n\n\tif (i == group->nummembers)\t\n\t\treturn;\n\n\tif (i < group->nummembers - 1)\n\t\tmemmove(group->members[i], group->members[i + 1], (group->nummembers - 1 - i) * sizeof(group->members[0]));\n\n\tgroup->nummembers--;\n}\n\nstatic void AddGroupMember(mapgroup_t *group, char *member) {\n\tint i;\n\n\tif (!group || group->nummembers == MAX_GROUP_MEMBERS)\n\t\treturn;\n\n\tfor (i = 0; i < group->nummembers; i++) {\t\t\n\t\tif (!strcasecmp(member, group->members[i]))\n\t\t\treturn;\n\t}\n\n\tstrlcpy(group->members[group->nummembers], member, sizeof(group->members[group->nummembers]));\n\tgroup->nummembers++;\n}\n\nvoid MT_MapGroup_f(void) {\n\tint i, c, j;\n\tqbool removeflag = false;\n\tmapgroup_t *node, *group, *tempnode;\n\tchar *groupname, *member;\n\n\tif ((c = Cmd_Argc()) == 1) {\t\t\n\t\tif (!FIRSTUSERGROUP) {\n\t\t\tCom_Printf(\"No map groups defined\\n\");\n\t\t} else {\n\t\t\tfor (node = FIRSTUSERGROUP; node; node = node->next) {\n\t\t\t\tCom_Printf(\"\\x02%s: \", node->groupname);\n\t\t\t\tfor (j = 0; j < node->nummembers; j++)\n\t\t\t\t\tCom_Printf(\"%s \", node->members[j]);\n\t\t\t\tCom_Printf(\"\\n\");\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\tgroupname = Cmd_Argv(1);\n\n\tif (c == 2 && !strcasecmp(groupname, \"clear\")) {\t\n\t\tfor (node = FIRSTUSERGROUP; node; node = tempnode) {\n\t\t\ttempnode = node->next;\n\t\t\tDeleteMapGroup(node);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (Util_Is_Valid_Filename(groupname) == false) {\n\t\tCom_Printf(\"Error: %s is not a valid map group name\\n\", groupname);\n\t\treturn;\n\t}\n\n\tgroup = GetGroupWithName(groupname);\n\n\tif (c == 2) {\t\n\t\tif (!group) {\n\t\t\tCom_Printf(\"No map group named \\\"%s\\\"\\n\", groupname);\n\t\t} else {\n\t\t\tCom_Printf(\"\\x02%s: \", groupname);\n\t\t\tfor (j = 0; j < group->nummembers; j++)\n\t\t\t\tCom_Printf(\"%s \", group->members[j]);\n\t\t\tCom_Printf(\"\\n\");\n\t\t}\n\t\treturn;\n\t}\n\n\tif (group && group->system) {\n\t\tCom_Printf(\"Cannot modify system group \\\"%s\\\"\\n\", groupname);\n\t\treturn;\n\t}\n\n\tif (c == 3 && !strcasecmp(Cmd_Argv(2), \"clear\")) {\t\n\t\tif (!group)\n\t\t\tCom_Printf(\"\\\"%s\\\" is not a map group name\\n\", groupname);\n\t\telse\n\t\t\tDeleteMapGroup(group);\n\t\treturn;\n\t}\n\n\tif (!group) {\t\n\t\tgroup = (mapgroup_t *) Q_calloc(1, sizeof(mapgroup_t));\n\t\tstrlcpy(group->groupname, groupname, sizeof(group->groupname));\n\t\tgroup->system = !mapgroups_init;\n\t\tif (mapgroups) {\t\n\t\t\tfor (tempnode = mapgroups; tempnode->next; tempnode = tempnode->next)\n\t\t\t\t;\n\t\t\ttempnode->next = group;\n\t\t\tgroup->prev = tempnode;\n\t\t} else {\n\t\t\tmapgroups = group;\n\t\t}\n\t} else {\t\t\n\t\tmember = Cmd_Argv(2);\n\t\tif (member[0] != '+' && member[0] != '-')\n\t\t\tResetGroupMembers(group);\n\t}\n\n\tfor (i = 2; i < c; i++) {\n\t\tmember = Cmd_Argv(i);\n\t\tif (member[0] == '+') {\n\t\t\tremoveflag = false;\n\t\t\tmember++;\n\t\t} else if (member[0] == '-') {\n\t\t\tremoveflag = true;\n\t\t\tmember++;\n\t\t}\n\n\t\tif (!removeflag && (tempnode = GetGroupWithMember(member)) && tempnode != group) {\n\t\t\tif (cl_warncmd.integer || developer.integer)\n\t\t\t\tCom_Printf(\"Warning: \\\"%s\\\" is already a member of group \\\"%s\\\"...ignoring\\n\", member, tempnode->groupname);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (removeflag)\n\t\t\tDeleteGroupMember(group, member);\n\t\telse\n\t\t\tAddGroupMember(group, member);\n\t}\n\n\tif (!group->nummembers)\t\n\t\tDeleteMapGroup(group);\n}\n\nvoid MT_AddMapGroups(void) {\n\tchar exmy_group[256] = {0}, exmy_map[6] = {'e', 0, 'm', 0, ' ', 0};\n\tint i, j;\n\tmapgroup_t *tempnode;\n\n\tstrlcat (exmy_group, \"mapgroup exmy start \", sizeof (exmy_group));\n\tfor (i = 1; i <= 4; i++) {\n\t\tfor (j = 1; j <= 8; j++) {\t\t\n\t\t\texmy_map[1] = i + '0';\n\t\t\texmy_map[3] = j + '0';\n\t\t\tstrlcat(exmy_group, exmy_map, sizeof (exmy_group));\n\t\t}\n\t}\n\tCmd_TokenizeString(exmy_group);\n\tMT_MapGroup_f();\n\tfor (tempnode = mapgroups; tempnode->next; tempnode = tempnode->next)\n\t\t;\n\tlast_system_mapgroup = tempnode;\n\tmapgroups_init = true;\n}\n\nchar *MT_GetMapGroupName(char *mapname, qbool *system) {\n\tmapgroup_t *group;\n\n\tgroup = GetGroupWithMember(mapname);\n\tif (group && strcmp(mapname, group->groupname)) {\n\t\tif (system)\n\t\t\t*system = group->system;\n\t\treturn group->groupname;\n\t} else {\n\t\treturn NULL;\n\t}\n}\n\nvoid DumpMapGroups(FILE *f) {\n\tmapgroup_t *node;\n\tint j;\n\n\tif (!FIRSTUSERGROUP) {\n\t\tfprintf(f, \"mapgroup clear\\n\");\n\t\treturn;\n\t}\n\tfor (node = FIRSTUSERGROUP; node; node = node->next) {\n\t\tfprintf(f, \"mapgroup %s \", node->groupname);\n\t\tfor (j = 0; j < node->nummembers; j++)\n\t\t\tfprintf(f, \"%s \", node->members[j]);\n\t\tfprintf(f, \"\\n\");\n\t}\n}\n\nchar *Macro_MatchType(void) {\n\tmatchinfo_t *matchinfo;\n\n\tif (cls.state < ca_active)\n\t\treturn \"No match in progress\";\n\n\tmatchinfo = MT_GetMatchInfo();\n\n\treturn matchcvars[matchinfo->matchtype].nickname;\n}\n\nvoid MT_Init(void)\n{\n\tchar tmp_path[MAX_OSPATH] = {0};\n\n\tsnprintf(&tmp_path[0], sizeof(tmp_path), \"%s/ezquake/temp\", com_basedir);\n\tSys_mkdir(tmp_path);\n\n\tMT_ClearClientState();\n\n\tCmd_AddMacro(macro_matchname, Macro_MatchName);\n\tCmd_AddMacro(macro_matchtype, Macro_MatchType);\n\n\tCmd_AddCommand(\"match_format_macrolist\", MT_Macrolist_f);\n\tCmd_AddCommand(\"match_forcestart\", MT_Match_ForceStart_f);\n\tCmd_AddCommand(\"match_save\", MT_SaveMatch_f);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_MATCH_TOOLS);\n\n\tCvar_Register(&match_format_solo);\n\tCvar_Register(&match_format_coop);\n\tCvar_Register(&match_format_race);\n\tCvar_Register(&match_format_duel);\n\tCvar_Register(&match_format_ffa);\n\tCvar_Register(&match_format_2on2);\n\tCvar_Register(&match_format_3on3);\n\tCvar_Register(&match_format_4on4);\n\tCvar_Register(&match_format_tdm);\n\tCvar_Register(&match_format_multiteam);\n\tCvar_Register(&match_format_arena);\n\tCvar_Register(&match_format_tf_duel);\n\tCvar_Register(&match_format_tf_clanwar);\n\n\tCvar_Register(&match_name_and);\n\tCvar_Register(&match_name_versus);\n\tCvar_Register(&match_name_on);\n\n\tCvar_Register(&match_name_nick);\n\tCvar_Register(&match_name_spec);\n\n\tCvar_Register(&match_auto_record);\n\tCvar_Register(&match_auto_logconsole);\n\tCvar_Register(&match_auto_sshot);\n\tCvar_Register(&match_auto_minlength);\n\tCvar_Register(&match_auto_spectating);\n\tCvar_Register(&match_auto_unminimize);\n\n\tCvar_ResetCurrentGroup();\n\n\tMT_Challenge_InitCvars();\n}\n\nvoid MT_Shutdown(void)\n{\n\tmapgroup_t* group = mapgroups;\n\n\twhile (group) {\n\t\tmapgroup_t* next = group->next;\n\t\tQ_free(group);\n\t\tgroup = next;\n\t}\n}\n"
  },
  {
    "path": "src/match_tools_challenge.c",
    "content": "/*\nCopyright (C) 2002-2003       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"teamplay.h\"\n#include \"utils.h\"\n#include <curl/curl.h>\n#include \"sha1.h\"\n#include <time.h>\n#include \"logging.h\"\n\nchar* MT_PlayerName(void);\nchar* MT_PlayerTeam(void);\nchar* MT_EnemyName(void);\nint MT_CountPlayers(void);\nchar* MT_TempDirectory(void);\n\nstatic qbool temp_log_upload_pending = false;\n\nvoid MT_ChallengeMode_Change(cvar_t *cvar, char *value, qbool *cancel)\n{\n\tif (cls.state != ca_disconnected && !cl.standby && !cl.spectator) {\n\t\t*cancel = true;\n\t\tCom_Printf(\"Challenge mode cannot be toggled during the match\\n\");\n\t\treturn;\n\t}\n}\n\nstatic cvar_t match_auto_logupload       = { \"match_auto_logupload\", \"0\" };\nstatic cvar_t match_auto_logupload_token = { \"match_auto_logupload_token\", \"\", 0, MT_ChallengeMode_Change };\nstatic cvar_t match_auto_logurl          = { \"match_auto_logurl\", \"http://stats.quakeworld.nu/logupload\" };\nstatic cvar_t match_challenge            = { \"match_challenge\", \"0\", 0, MT_ChallengeMode_Change };\nstatic cvar_t match_challenge_url        = { \"match_challenge_url\", \"http://stats.quakeworld.nu/post-challenge\", 0, MT_ChallengeMode_Change };\nstatic cvar_t match_ladder_id            = { \"match_ladder_id\", \"1\" };\n\ntypedef enum challenge_status_e {\n\tchallenge_start,\n\tchallenge_end\n} challenge_status_e;\n\ntypedef struct challenge_data_s {\n\tchallenge_status_e status;\n\tchar *ladderid;\n\tint players_count;\n\tchar *token;\n\tchar *player1;\n\tchar *player2;\n\tchar *server;\n\tchar *map;\n\tchar *url;\n\tchar *hash;\n} challenge_data_t;\n\nstatic challenge_data_t *last_challenge = NULL;\n\nqbool MT_Challenge_IsOn(void)\n{\n\treturn last_challenge != NULL;\n}\n\nconst char *MT_Challenge_GetLadderId(void)\n{\n\tif (last_challenge == NULL) {\n\t\treturn \"\";\n\t}\n\n\treturn last_challenge->ladderid;\n}\n\nconst char *MT_Challenge_GetHash(void)\n{\n\tif (last_challenge == NULL) {\n\t\treturn \"\";\n\t}\n\n\treturn last_challenge->hash;\n}\n\nconst char *MT_Challenge_GetToken(void)\n{\n\tif (last_challenge == NULL) {\n\t\treturn \"\";\n\t}\n\n\treturn last_challenge->token;\n}\n\nconst char* MT_Challenge_StatusName(challenge_status_e status)\n{\n\tswitch (status) {\n\t\tcase challenge_start: return \"start\";\n\t\tcase challenge_end: return \"end\";\n\t\tdefault:\n\t\t\tCom_Printf(\"ERROR: MT_Challenge_StatusName: Unknown challenge status\");\n\t\t\treturn \"\";\n\t}\n}\n\nsize_t MT_Curl_Write_Void(void *ptr, size_t size, size_t nmemb, void *userdata)\n{\n\treturn size * nmemb;\n}\n\nstatic const char *MT_Challenge_GenerateHash(void)\n{\n\tstatic unsigned char hash[DIGEST_SIZE];\n\tSHA1_CTX context;\n\tchar* hostname = Info_ValueForKey(cl.serverinfo, \"hostname\");\n\tdouble curtime = Sys_DoubleTime();\n\n\tSHA1Init(&context);\n\n\tSHA1Update(&context, (unsigned char *)match_ladder_id.string, strlen(match_ladder_id.string));\n\tSHA1Update(&context, (unsigned char *)match_auto_logupload_token.string, strlen(match_auto_logupload_token.string));\n\tSHA1Update(&context, (unsigned char *)MT_PlayerName(), strlen(MT_PlayerName()));\n\tSHA1Update(&context, (unsigned char *)MT_EnemyName(), strlen(MT_EnemyName()));\n\tSHA1Update(&context, (unsigned char *)hostname, strlen(hostname));\n\tSHA1Update(&context, (unsigned char *)host_mapname.string, strlen(host_mapname.string));\n\tSHA1Update(&context, (unsigned char *)match_challenge_url.string, strlen(match_challenge_url.string));\n\tSHA1Update(&context, (unsigned char *)&curtime, sizeof(double));\n\n\tSHA1Final(hash, &context);\n\n\treturn bin2hex(hash);\n}\n\nchallenge_data_t *MT_Challenge_Create(challenge_status_e status, int players_count,\n\tconst char *ladderid, const char *token, const char *player1, const char *player2,\n\tconst char *server, const char *map, const char *url, const char *hash)\n{\n\tchallenge_data_t *retval = (challenge_data_t *)Q_malloc(sizeof(challenge_data_t));\n\tretval->status = status;\n\tretval->players_count = players_count;\n\tretval->ladderid = Q_strdup(ladderid);\n\tretval->token = Q_strdup(token);\n\tretval->player1 = Q_strdup(player1);\n\tretval->player2 = Q_strdup(player2);\n\tretval->server = Q_strdup(server);\n\tretval->map = Q_strdup(map);\n\tretval->url = Q_strdup(url);\n\tretval->hash = Q_strdup(hash);\n\n\treturn retval;\n}\n\nvoid MT_Challenge_Destroy(challenge_data_t *challenge)\n{\n\tQ_free(challenge->ladderid);\n\tQ_free(challenge->token);\n\tQ_free(challenge->player1);\n\tQ_free(challenge->player2);\n\tQ_free(challenge->server);\n\tQ_free(challenge->map);\n\tQ_free(challenge->url);\n\tQ_free(challenge->hash);\n\tQ_free(challenge);\n}\n\nchallenge_data_t *MT_Challenge_Init(challenge_status_e status)\n{\n\treturn MT_Challenge_Create(status, MT_CountPlayers(), match_ladder_id.string, match_auto_logupload_token.string,\n\t\tMT_PlayerName(), MT_EnemyName(), Info_ValueForKey(cl.serverinfo, \"hostname\"), host_mapname.string,\n\t\tmatch_challenge_url.string, MT_Challenge_GenerateHash());\n}\n\nchallenge_data_t *MT_Challenge_Copy(const challenge_data_t *orig)\n{\n\treturn MT_Challenge_Create(orig->status, orig->players_count, orig->ladderid, orig->token, orig->player1, orig->player2,\n\t\torig->server, orig->map, orig->url, orig->hash);\n}\n\nint MT_Challenge_StartSend_Thread(void *arg)\n{\n\tchallenge_data_t *challenge_data = (challenge_data_t *)arg;\n\n\tCURL *curl;\n\tCURLcode res;\n\tstruct curl_httppost *post = NULL;\n\tstruct curl_httppost *last = NULL;\n\tstruct curl_slist *headers = NULL;\n\tchar errorbuffer[CURL_ERROR_SIZE] = \"\";\n\n\tcurl = curl_easy_init();\n\n\theaders = curl_slist_append(headers, \"Content-Type: text/plain\");\n\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"status\",\n\t\tCURLFORM_COPYCONTENTS, MT_Challenge_StatusName(challenge_data->status),\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"ladderid\",\n\t\tCURLFORM_COPYCONTENTS, challenge_data->ladderid,\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"hash\",\n\t\tCURLFORM_COPYCONTENTS, challenge_data->hash,\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"token\",\n\t\tCURLFORM_COPYCONTENTS, challenge_data->token,\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"players_count\",\n\t\tCURLFORM_COPYCONTENTS, va(\"%d\", challenge_data->players_count),\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"player1\",\n\t\tCURLFORM_COPYCONTENTS, challenge_data->player1,\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"player2\",\n\t\tCURLFORM_COPYCONTENTS, challenge_data->player2,\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"server\",\n\t\tCURLFORM_COPYCONTENTS, challenge_data->server,\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"map\",\n\t\tCURLFORM_COPYCONTENTS, challenge_data->map,\n\t\tCURLFORM_END);\n\n\tcurl_easy_setopt(curl, CURLOPT_HTTPPOST, post);\n\tcurl_easy_setopt(curl, CURLOPT_URL, challenge_data->url);\n\tcurl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorbuffer);\n\tcurl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, MT_Curl_Write_Void);\n\n\tres = curl_easy_perform(curl); /* post away! */\n\n\tcurl_formfree(post);\n\tcurl_easy_cleanup(curl);\n\n\tif (res != CURLE_OK) {\n\t\tCom_Printf(\"Challenge announcement upload failed:\\n%s\\n\", curl_easy_strerror(res));\n\t\tCom_Printf(\"%s\\n\", errorbuffer);\n\t}\n\telse {\n\t\tCom_Printf(\"Challenge %s announced\\n\", MT_Challenge_StatusName(challenge_data->status));\n\t}\n\n\tMT_Challenge_Destroy(challenge_data);\n\n\treturn 0;\n}\n\nstatic void MT_Challenge_BreakSend(void)\n{\n\tchallenge_data_t *thread_data;\n\n\tif (last_challenge != NULL) {\n\t\t// normal situation - reuse the challenge we announced on start\n\t\tthread_data = MT_Challenge_Copy(last_challenge);\n\t\tthread_data->status = challenge_end;\n\t\tthread_data->players_count = MT_CountPlayers();\n\n\t\tMT_Challenge_Destroy(last_challenge);\n\t\tlast_challenge = NULL;\n\t}\n\telse {\n\t\t// weird situation, we didn't send the start of the challenge (perhaps late-join of the match)\n\t\t// but we now want to send the break of the challenge.. we will do our best-effort here\n\t\t// - simply generate new challenge and say we break it; it is up to the challenge server\n\t\t// what it will do with this message\n\t\tthread_data = MT_Challenge_Init(challenge_end);\n\t}\n\n\tif (Sys_CreateDetachedThread(MT_Challenge_StartSend_Thread, thread_data) < 0) {\n\t\tCom_Printf(\"Failed to create MT Challenge BreakSend thread\\n\");\n\t}\n}\n\nstatic void MT_Challenge_StartSend(void)\n{\n\tchallenge_data_t *thread_data;\n\n\tlast_challenge = MT_Challenge_Init(challenge_start);\n\n\tthread_data = MT_Challenge_Copy(last_challenge);\n\n\tif (Sys_CreateDetachedThread(MT_Challenge_StartSend_Thread, thread_data) < 0) {\n\t\tCom_Printf(\"Failed to create MT Challenge StartSend thread\\n\");\n\t}\n}\n\nvoid MT_Challenge_Cancel(void)\n{\n\tif (match_challenge.integer) {\n\t\tMT_Challenge_BreakSend();\n\t}\n}\n\nvoid MT_Challenge_StartMatch(void)\n{\n\tif (last_challenge != NULL) {\n\t\tMT_Challenge_Destroy(last_challenge);\n\t\tlast_challenge = NULL;\n\t}\n\n\tif (match_challenge.integer) {\n\t\tCbuf_AddText(\"play items/protect.wav\\n\");\n\t\tCbuf_AddText(\"say Challenge mode: on\\n\");\n\t\tMT_Challenge_StartSend();\n\t}\n}\n\nvoid MT_Challenge_InitCvars(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_MATCH_TOOLS);\n\tCvar_Register(&match_auto_logupload);\n\tCvar_Register(&match_auto_logurl);\n\tCvar_Register(&match_auto_logupload_token);\n\tCvar_Register(&match_challenge);\n\tCvar_Register(&match_challenge_url);\n\tCvar_Register(&match_ladder_id);\n\tCvar_ResetCurrentGroup();\n}\n\nqbool MT_Challenge_Log_IsUploadAllowed(void)\n{\n\textern cvar_t match_auto_logconsole;\n\n\treturn (match_challenge.integer || (match_auto_logupload.integer && match_auto_logconsole.integer))\n\t\t&& !cls.demoplayback\n\t\t&& cls.server_adr.type != NA_LOOPBACK;\n}\n\ntypedef struct log_upload_job_s {\n\tqbool challenge_mode;\n\tchar *challenge_hash;\n\tchar *player_name;\n\tchar *token;\n\tchar *hostname;\n\tchar *filename;\n\tchar *url;\n\tchar *mapname;\n\tchar *ladderid;\n} log_upload_job_t;\n\nstatic log_upload_job_t* Log_Upload_Job_Prepare(qbool challenge_mode, const char *challenge_hash,\n\tconst char *filename, const char *hostname,\n\tconst char* player_name, const char *token, const char *url,\n\tconst char *mapname, const char *ladderid)\n{\n\tlog_upload_job_t *job = (log_upload_job_t *)Q_malloc(sizeof(log_upload_job_t));\n\n\tjob->challenge_mode = challenge_mode;\n\tjob->challenge_hash = Q_strdup(challenge_hash);\n\tjob->filename = Q_strdup(filename);\n\tjob->hostname = Q_strdup(hostname);\n\tjob->player_name = Q_strdup(player_name);\n\tjob->token = Q_strdup(token);\n\tjob->url = Q_strdup(url);\n\tjob->mapname = Q_strdup(mapname);\n\tjob->ladderid = Q_strdup(ladderid);\n\n\treturn job;\n}\n\nstatic void Log_Upload_Job_Free(log_upload_job_t *job)\n{\n\tQ_free(job->filename);\n\tQ_free(job->hostname);\n\tQ_free(job->challenge_hash);\n\tQ_free(job->player_name);\n\tQ_free(job->token);\n\tQ_free(job->url);\n\tQ_free(job->mapname);\n\tQ_free(job);\n}\n\nstatic size_t Log_Curl_Write_Void(void *ptr, size_t size, size_t nmemb, void *userdata)\n{\n\treturn size * nmemb;\n}\n\nint Log_AutoLogging_Upload_Thread(void *vjob)\n{\n\tlog_upload_job_t *job = (log_upload_job_t *)vjob;\n\tCURL *curl;\n\tCURLcode res;\n\tstruct curl_httppost *post = NULL;\n\tstruct curl_httppost *last = NULL;\n\tstruct curl_slist *headers = NULL;\n\tchar errorbuffer[CURL_ERROR_SIZE] = \"\";\n\n\tcurl = curl_easy_init();\n\n\theaders = curl_slist_append(headers, \"Content-Type: text/plain\");\n\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"name\",\n\t\tCURLFORM_COPYCONTENTS, job->player_name,\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"challenge\",\n\t\tCURLFORM_COPYCONTENTS, job->challenge_mode ? \"1\" : \"0\",\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"challenge_hash\",\n\t\tCURLFORM_COPYCONTENTS, job->challenge_hash,\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"token\",\n\t\tCURLFORM_COPYCONTENTS, job->token,\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"host\",\n\t\tCURLFORM_COPYCONTENTS, job->hostname,\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"map\",\n\t\tCURLFORM_COPYCONTENTS, job->mapname,\n\t\tCURLFORM_END);\n\tcurl_formadd(&post, &last,\n\t\tCURLFORM_COPYNAME, \"log\",\n\t\tCURLFORM_FILE, job->filename,\n\t\tCURLFORM_CONTENTHEADER, headers,\n\t\tCURLFORM_END);\n\n\tcurl_easy_setopt(curl, CURLOPT_HTTPPOST, post);\n\tcurl_easy_setopt(curl, CURLOPT_URL, job->url);\n\tcurl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorbuffer);\n\tcurl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Log_Curl_Write_Void);\n\n\tres = curl_easy_perform(curl); /* post away! */\n\n\tcurl_formfree(post);\n\tcurl_easy_cleanup(curl);\n\n\tif (res != CURLE_OK) {\n\t\tCom_Printf(\"Uploading of log failed:\\n%s\\n\", curl_easy_strerror(res));\n\t\tCom_Printf(\"%s\\n\", errorbuffer);\n\t}\n\telse {\n\t\tCom_Printf(\"Match log uploaded\\n\");\n\t}\n\n\tLog_Upload_Job_Free(job);\n\ttemp_log_upload_pending = false;\n\treturn 0;\n}\n\nvoid Log_AutoLogging_Upload(const char *filename)\n{\n\tlog_upload_job_t *job = Log_Upload_Job_Prepare(\n\t\tMT_Challenge_IsOn(),\n\t\tMT_Challenge_GetHash(),\n\t\tfilename,\n\t\tInfo_ValueForKey(cl.serverinfo, \"hostname\"),\n\t\tcl.players[cl.playernum].name,\n\t\tMT_Challenge_GetToken(),\n\t\tmatch_auto_logurl.string,\n\t\thost_mapname.string,\n\t\tMT_Challenge_GetLadderId());\n\n\tCom_Printf(\"Uploading match log...\\n\");\n\tif (Sys_CreateDetachedThread(Log_AutoLogging_Upload_Thread, (void *)job) < 0) {\n\t\tCom_Printf(\"Failed to create AutoLogging_Upload thread\\n\");\n\t}\n}\n\nqbool Log_TempLogUploadPending(void)\n{\n\treturn temp_log_upload_pending;\n}\n\nvoid Log_UploadTemp(void)\n{\n\tchar *tempname = va(\"%s/%s\", MT_TempDirectory(), TEMP_LOG_NAME);\n\ttemp_log_upload_pending = true;\n\tLog_AutoLogging_Upload(tempname);\n}\n\nvoid CL_QWURL_ProcessChallenge(const char *parameters)\n{\n\textern cvar_t match_auto_logupload_token;\n\textern cvar_t match_challenge;\n\n\t// parameters is expected to be of the format \"?token=<string>&otherparam=<string>&...\"\n\tchar info_buf[1024];\n\tchar *wp = info_buf;\n\tconst char *rp = parameters;\n\tsize_t write_len = 0;\n\tctxinfo_t ctx;\n\tchar *token;\n\tmemset(&ctx, 0, sizeof(ctxinfo_t));\n\tctx.max = 20;\n\n\twhile (*rp && write_len < 1022) {\n\t\tchar c = *rp++;\n\t\tif (c == '?') {\n\t\t\tc = '\\\\';\n\t\t}\n\t\telse if (c == '&') {\n\t\t\tc = '\\\\';\n\t\t}\n\t\telse if (c == '=') {\n\t\t\tc = '\\\\';\n\t\t}\n\n\t\t*wp++ = c;\n\t\twrite_len++;\n\t}\n\n\t*wp++ = '\\0';\n\n\tInfo_Convert(&ctx, info_buf);\n\n\ttoken = Info_Get(&ctx, \"token\");\n\n\tInfo_RemoveAll(&ctx);\n\n\tif (*token) {\n\t\tCvar_Set(&match_auto_logupload_token, token);\n\t\tCvar_Set(&match_challenge, \"1\");\n\t\tCom_Printf(\"Joining challenge ...\\n\");\n\t}\n\telse {\n\t\tCom_Printf(\"Challenge token not found in the URL\\n\");\n\t}\n}\n\nqbool MT_ChallengeSpecified(void)\n{\n\textern cvar_t match_challenge;\n\n\treturn match_challenge.integer != 0;\n}\n"
  },
  {
    "path": "src/mathlib.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\n*/\n// mathlib.c -- math primitives\n\n#ifdef SERVERONLY\n#include \"qwsvdef.h\"\n#else\n#include \"common.h\"\n#endif\n\nvec3_t vec3_origin = { 0, 0, 0 };\n\nint  _mathlib_temp_int1, _mathlib_temp_int2, _mathlib_temp_int3;\nfloat _mathlib_temp_float1, _mathlib_temp_float2, _mathlib_temp_float3;\nvec3_t _mathlib_temp_vec1, _mathlib_temp_vec2, _mathlib_temp_vec3;\n\nvoid ProjectPointOnPlane(vec3_t dst, const vec3_t p, const vec3_t normal) {\n\tfloat d, inv_denom;\n\tvec3_t n;\n\n\tinv_denom = 1.0F / DotProduct(normal, normal);\n\n\td = DotProduct(normal, p) * inv_denom;\n\n\tVectorScale(normal, inv_denom, n);\n\tVectorMA(p, -d, n, dst);\n}\n\nvoid PerpendicularVector(vec3_t dst, const vec3_t src) {\n\tif (!src[0]) {\n\t\tVectorSet(dst, 1, 0, 0);\n\t}\n\telse if (!src[1]) {\n\t\tVectorSet(dst, 0, 1, 0);\n\t}\n\telse if (!src[2]) {\n\t\tVectorSet(dst, 0, 0, 1);\n\t}\n\telse {\n\t\tVectorSet(dst, -src[1], src[0], 0);\n\t\tVectorNormalizeFast(dst);\n\t}\n}\n\nvoid VectorVectors(vec3_t forward, vec3_t right, vec3_t up) {\n\tPerpendicularVector(right, forward);\n\tCrossProduct(right, forward, up);\n}\n\nvoid MakeNormalVectors (/* in */ vec3_t forward, /* out */ vec3_t right, vec3_t up)\n{\n\tfloat d;\n\n\t// this rotate and negate guarantees a vector\n\t// not colinear with the original\n\tright[1] = -forward[0];\n\tright[2] = forward[1];\n\tright[0] = forward[2];\n\n\td = DotProduct (right, forward);\n\tVectorMA (right, -d, forward, right);\n\tVectorNormalize (right);\n\tCrossProduct (right, forward, up);\n}\n\nvoid RotatePointAroundVector(vec3_t dst, const vec3_t dir, const vec3_t point, float degrees) {\n\tfloat t0, t1, angle, c, s;\n\tvec3_t vr, vu, vf;\n\n\tangle = DEG2RAD(degrees);\n\tc = cos(angle);\n\ts = sin(angle);\n\tVectorCopy(dir, vf);\n\tVectorVectors(vf, vr, vu);\n\n\tt0 = vr[0] *  c + vu[0] * -s;\n\tt1 = vr[0] *  s + vu[0] *  c;\n\tdst[0] = (t0 * vr[0] + t1 * vu[0] + vf[0] * vf[0]) * point[0]\n\t\t+ (t0 * vr[1] + t1 * vu[1] + vf[0] * vf[1]) * point[1]\n\t\t+ (t0 * vr[2] + t1 * vu[2] + vf[0] * vf[2]) * point[2];\n\n\tt0 = vr[1] *  c + vu[1] * -s;\n\tt1 = vr[1] *  s + vu[1] *  c;\n\tdst[1] = (t0 * vr[0] + t1 * vu[0] + vf[1] * vf[0]) * point[0]\n\t\t+ (t0 * vr[1] + t1 * vu[1] + vf[1] * vf[1]) * point[1]\n\t\t+ (t0 * vr[2] + t1 * vu[2] + vf[1] * vf[2]) * point[2];\n\n\tt0 = vr[2] *  c + vu[2] * -s;\n\tt1 = vr[2] *  s + vu[2] *  c;\n\tdst[2] = (t0 * vr[0] + t1 * vu[0] + vf[2] * vf[0]) * point[0]\n\t\t+ (t0 * vr[1] + t1 * vu[1] + vf[2] * vf[1]) * point[1]\n\t\t+ (t0 * vr[2] + t1 * vu[2] + vf[2] * vf[2]) * point[2];\n}\n\nvoid BOPS_Error (void)\n{\n\tSys_Error (\"BoxOnPlaneSide:  Bad signbits\");\n}\n\n//Returns 1, 2, or 1 + 2\nint BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct mplane_s *p) {\n\t//the following optimisation is performed by BOX_ON_PLANE_SIDE macro\n\t//if (p->type < 3)\n\t//\treturn ((emaxs[p->type] >= p->dist) | ((emins[p->type] < p->dist) << 1));\n\tswitch(p->signbits) {\n\tdefault:\n\tcase 0:\n\t\treturn  (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) >= p->dist) |\n\t\t        (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) < p->dist) << 1));\n\tcase 1:\n\t\treturn  (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) >= p->dist) |\n\t\t        (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) < p->dist) << 1));\n\tcase 2:\n\t\treturn  (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) >= p->dist) |\n\t\t        (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) < p->dist) << 1));\n\tcase 3:\n\t\treturn  (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) >= p->dist) |\n\t\t        (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) < p->dist) << 1));\n\tcase 4:\n\t\treturn  (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) >= p->dist) |\n\t\t        (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) < p->dist) << 1));\n\tcase 5:\n\t\treturn  (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) >= p->dist) |\n\t\t        (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) < p->dist) << 1));\n\tcase 6:\n\t\treturn  (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) >= p->dist) |\n\t\t        (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) < p->dist) << 1));\n\tcase 7:\n\t\treturn  (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) >= p->dist) |\n\t\t        (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) < p->dist) << 1));\n\t}\n}\n\nvoid AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up)\n{\n\tfloat angle, sr, sp, sy, cr, cp, cy, temp;\n\n\tif (angles[YAW]) {\n\t\tangle = DEG2RAD(angles[YAW]);\n\t\tsy = sin(angle);\n\t\tcy = cos(angle);\n\t} else {\n\t\tsy = 0;\n\t\tcy = 1;\n\t}\n\n\tif (angles[PITCH]) {\n\t\tangle = DEG2RAD(angles[PITCH]);\n\t\tsp = sin(angle);\n\t\tcp = cos(angle);\n\t} else {\n\t\tsp = 0;\n\t\tcp = 1;\n\t}\n\n\tif (forward) {\n\t\tforward[0] = cp * cy;\n\t\tforward[1] = cp * sy;\n\t\tforward[2] = -sp;\n\t}\n\n\tif (right || up) {\n\t\tif (angles[ROLL]) {\n\t\t\tangle = DEG2RAD(angles[ROLL]);\n\t\t\tsr = sin(angle);\n\t\t\tcr = cos(angle);\n\n\t\t\tif (right) {\n\t\t\t\ttemp = sr * sp;\n\t\t\t\tright[0] = -1 * temp * cy + cr * sy;\n\t\t\t\tright[1] = -1 * temp * sy - cr * cy;\n\t\t\t\tright[2] = -1 * sr * cp;\n\t\t\t}\n\n\t\t\tif (up) {\n\t\t\t\ttemp = cr * sp;\n\t\t\t\tup[0] = (temp * cy + sr * sy);\n\t\t\t\tup[1] = (temp * sy - sr * cy);\n\t\t\t\tup[2] = cr * cp;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (right) {\n\t\t\t\tright[0] = sy;\n\t\t\t\tright[1] = -cy;\n\t\t\t\tright[2] = 0;\n\t\t\t}\n\n\t\t\tif (up) {\n\t\t\t\tup[0] = sp * cy ;\n\t\t\t\tup[1] = sp * sy;\n\t\t\t\tup[2] = cp;\n\t\t\t}\n\t\t}\n\t}\n}\n\n//VULT COLLISION\nvoid AngleVectorsFLU (const vec3_t angles, vec3_t forward, vec3_t left, vec3_t up)\n{\n\tdouble angle, sr, sp, sy, cr, cp, cy;\n\n\tangle = angles[YAW] * (M_PI*2 / 360);\n\tsy = sin(angle);\n\tcy = cos(angle);\n\tangle = angles[PITCH] * (M_PI*2 / 360);\n\tsp = sin(angle);\n\tcp = cos(angle);\n\tif (forward)\n\t{\n\t\tforward[0] = cp*cy;\n\t\tforward[1] = cp*sy;\n\t\tforward[2] = -sp;\n\t}\n\tif (left || up)\n\t{\n\t\tangle = angles[ROLL] * (M_PI*2 / 360);\n\t\tsr = sin(angle);\n\t\tcr = cos(angle);\n\t\tif (left)\n\t\t{\n\t\t\tleft[0] = sr*sp*cy+cr*-sy;\n\t\t\tleft[1] = sr*sp*sy+cr*cy;\n\t\t\tleft[2] = sr*cp;\n\t\t}\n\t\tif (up)\n\t\t{\n\t\t\tup[0] = cr*sp*cy+-sr*-sy;\n\t\t\tup[1] = cr*sp*sy+-sr*cy;\n\t\t\tup[2] = cr*cp;\n\t\t}\n\t}\n}\n\nvec_t VectorLength (vec3_t v) {\n\tfloat length;\n\n\tlength = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];\n\treturn sqrt(length);\n}\n\nfloat VectorNormalize (vec3_t v) {\n\tfloat length;\n\n\tlength = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];\n\tlength = sqrt (length);\n\n\tif (length) {\n\t\tVectorScale(v, 1 / length, v);\n\t}\n\n\treturn length;\n}\n\nvoid R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) {\n\tout[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +\tin1[0][2] * in2[2][0];\n\tout[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +\tin1[0][2] * in2[2][1];\n\tout[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +\tin1[0][2] * in2[2][2];\n\tout[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +\tin1[1][2] * in2[2][0];\n\tout[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +\tin1[1][2] * in2[2][1];\n\tout[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +\tin1[1][2] * in2[2][2];\n\tout[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +\tin1[2][2] * in2[2][0];\n\tout[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +\tin1[2][2] * in2[2][1];\n\tout[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +\tin1[2][2] * in2[2][2];\n}\n\nvoid R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]) {\n\tout[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +\tin1[0][2] * in2[2][0];\n\tout[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +\tin1[0][2] * in2[2][1];\n\tout[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +\tin1[0][2] * in2[2][2];\n\tout[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] +\tin1[0][2] * in2[2][3] + in1[0][3];\n\tout[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +\tin1[1][2] * in2[2][0];\n\tout[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +\tin1[1][2] * in2[2][1];\n\tout[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +\tin1[1][2] * in2[2][2];\n\tout[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] +\tin1[1][2] * in2[2][3] + in1[1][3];\n\tout[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +\tin1[2][2] * in2[2][0];\n\tout[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +\tin1[2][2] * in2[2][1];\n\tout[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +\tin1[2][2] * in2[2][2];\n\tout[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] +\tin1[2][2] * in2[2][3] + in1[2][3];\n}\n\n//Returns mathematically correct (floor-based) quotient and remainder for numer and denom, both of\n//which should contain no fractional part. The quotient must fit in 32 bits.\nvoid FloorDivMod (double numer, double denom, int *quotient, int *rem)\n{\n\tint q, r;\n\tdouble x;\n\n#ifndef PARANOID\n\tif (denom <= 0.0) {\n\t\tSys_Error(\"FloorDivMod: bad denominator %d\", denom);\n\t}\n#endif\n\n\tif (numer >= 0.0) {\n\t\tx = floor(numer / denom);\n\t\tq = (int) x;\n\t\tr = (int) floor(numer - (x * denom));\n\t}\n\telse {\n\t\t// perform operations with positive values, and fix mod to make floor-based\n\t\tx = floor(-numer / denom);\n\t\tq = -(int)x;\n\t\tr = (int)floor(-numer - (x * denom));\n\t\tif (r != 0) {\n\t\t\tq--;\n\t\t\tr = (int)denom - r;\n\t\t}\n\t}\n\t*quotient = q;\n\t*rem = r;\n}\n\nint GreatestCommonDivisor (int i1, int i2)\n{\n\tif (i1 > i2) {\n\t\tif (i2 == 0)\n\t\t\treturn (i1);\n\t\treturn GreatestCommonDivisor (i2, i1 % i2);\n\t}\n\telse {\n\t\tif (i1 == 0)\n\t\t\treturn (i2);\n\t\treturn GreatestCommonDivisor (i1, i2 % i1);\n\t}\n}\n\n//\n// From: http://www.cse.ucsc.edu/~pang/160/f98/Gems/GemsIV/centroid.c\n// polyCentroid: Calculates the centroid (xCentroid, yCentroid) and area\n// of a polygon, given its vertices (x[0], y[0]) ... (x[n-1], y[n-1]). It\n// is assumed that the contour is closed, i.e., that the vertex following\n// (x[n-1], y[n-1]) is (x[0], y[0]).  The algebraic sign of the area is\n// positive for counterclockwise ordering of vertices in x-y plane;\n// otherwise negative.\n//\n// Returned values:  0 for normal execution;  1 if the polygon is\n// degenerate (number of vertices < 3);  and 2 if area = 0 (and the centroid is undefined).\nint GetPolyCentroid(vec3_t *v, int n, float *xCentroid, float *yCentroid, float *area)\n{\n\tregister int i, j;\n\tfloat ai, atmp = 0, xtmp = 0, ytmp = 0;\n\n\tif (n < 3)\n\t{\n\t\treturn 1;\n\t}\n\n\tfor (i = n - 1, j = 0; j < n; i = j, j++)\n\t{\n\t\tai = v[i][0] * v[j][1] - v[j][0] * v[i][1];\n\t\tatmp += ai;\n\t\txtmp += (v[j][0] + v[i][0]) * ai;\n\t\tytmp += (v[j][1] + v[i][1]) * ai;\n\t}\n\n\t*area = atmp / 2;\n\n\tif (atmp != 0)\n\t{\n\t\t*xCentroid =\txtmp / (3 * atmp);\n\t\t*yCentroid =\tytmp / (3 * atmp);\n\t\treturn 0;\n\t}\n\treturn 2;\n}\n\n//Inverts an 8.24 value to a 16.16 value\nfixed16_t Invert24To16(fixed16_t val)\n{\n\tif (val < 256) {\n\t\treturn (0xFFFFFFFF);\n\t}\n\n\treturn (fixed16_t) (((double) 0x10000 * (double) 0x1000000 / (double) val) + 0.5);\n}\n\n/*\nInit rotation matrix 'out', 'angle' in radians, 'v' should be normilized vector.\n*/\nvoid Matrix3x3_CreateRotate (matrix3x3_t out, float angle, const vec3_t v)\n{\n\tfloat c = cos(angle);\n\tfloat s = sin(angle);\n\n\tout[0][0] = v[0] * v[0] + c * (1 - v[0] * v[0]);\n\tout[1][0] = v[0] * v[1] * (1 - c) + v[2] * s;\n\tout[2][0] = v[2] * v[0] * (1 - c) - v[1] * s;\n\n\tout[0][1] = v[0] * v[1] * (1 - c) - v[2] * s;\n\tout[1][1] = v[1] * v[1] + c * (1 - v[1] * v[1]);\n\tout[2][1] = v[1] * v[2] * (1 - c) + v[0] * s;\n\n\tout[0][2] = v[2] * v[0] * (1 - c) + v[1] * s;\n\tout[1][2] = v[1] * v[2] * (1 - c) - v[0] * s;\n\tout[2][2] = v[2] * v[2] + c * (1 - v[2] * v[2]);\n}\n\n/*\nMultiply matrix 'in' by vector 'v', note what 'out' is vector.\n*/\nvoid Matrix3x3_MultiplyByVector (vec3_t out, const matrix3x3_t in, const vec3_t v)\n{\n\tout[0] = in[0][0] * v[0] + in[0][1] * v[1] + in[0][2] * v[2];\n\tout[1] = in[1][0] * v[0] + in[1][1] * v[1] + in[1][2] * v[2];\n\tout[2] = in[2][0] * v[0] + in[2][1] * v[1] + in[2][2] * v[2];\n}\n\nfloat VectorDistance(const vec3_t x, const vec3_t y)\n{\n\tvec3_t diff = { x[0] - y[0], x[1] - y[1], x[2] - y[2] };\n\n\treturn VectorLength(diff);\n}\n\nfloat VectorDistanceQuick(const vec3_t x, const vec3_t y)\n{\n\tvec3_t diff = { x[0] - y[0], x[1] - y[1], x[2] - y[2] };\n\n\treturn diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2];\n}\n"
  },
  {
    "path": "src/mathlib.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n// mathlib.h\n#ifndef __MATHLIB_H__\n#define __MATHLIB_H__\n\n#define\tPITCH   0 // up / down\n#define\tYAW     1 // left / right\n#define\tROLL    2 // fall over\n\ntypedef float vec_t;\ntypedef vec_t vec3_t[3];\ntypedef vec_t vec5_t[5];\n\ntypedef\tint\tfixed4_t;\ntypedef\tint\tfixed8_t;\ntypedef\tint\tfixed16_t;\n\ntypedef vec_t matrix3x3_t[3][3];\n\nstruct mplane_s;\n\n#ifndef M_PI\n#define M_PI 3.14159265358979323846  // matches value in gcc v2 math.h\n#endif\n\n#define DEG2RAD(a) (((a) * M_PI) / 180.0F)\n#define RAD2DEG(a) (((a) * 180.0F) / M_PI)\n\n#define NANMASK   (255<<23)\n#define IS_NAN(x) (((*(int *)&x)&NANMASK)==NANMASK)\n\n#ifndef Q_rint\n#define Q_rint(x) ((x) > 0 ? (int)((x) + 0.5) : (int)((x) - 0.5))\n#endif\n\n#define DotProduct(x,y)         ((x)[0] * (y)[0] + (x)[1] * (y)[1] + (x)[2] * (y)[2])\n#define VectorSubtract(a,b,c)   ((c)[0] = (a)[0] - (b)[0], (c)[1] = (a)[1] - (b)[1], (c)[2] = (a)[2] - (b)[2])\n#define VectorAdd(a,b,c)        ((c)[0] = (a)[0] + (b)[0], (c)[1] = (a)[1] + (b)[1], (c)[2] = (a)[2] + (b)[2])\n#define VectorCopy(a,b)         ((b)[0] = (a)[0], (b)[1] = (a)[1], (b)[2] = (a)[2])\n#define VectorClear(a)          ((a)[0] = (a)[1] = (a)[2] = 0)\n#define VectorNegate(a,b)       ((b)[0] = -(a)[0], (b)[1] = -(a)[1], (b)[2] = -(a)[2])\n#define VectorSet(v, x, y, z)   ((v)[0] = (x), (v)[1] = (y), (v)[2] = (z))\n\n#define CrossProduct(v1, v2, x)\t\t\t\t\t\t\t\\\n\t((x)[0] = (v1)[1] * (v2)[2] - (v1)[2] * (v2)[1],\t\\\n\t(x)[1] = (v1)[2] * (v2)[0] - (v1)[0] * (v2)[2],\t\t\\\n\t(x)[2] = (v1)[0] * (v2)[1] - (v1)[1] * (v2)[0])\n\n#define VectorSupCompare(v, w, m)\t\t\t\t\t\t\\\n\t(_mathlib_temp_float1 = m,\t\t\t\t\t\t\t\\\n\t(v)[0] - (w)[0] > -_mathlib_temp_float1 && (v)[0] - (w)[0] < _mathlib_temp_float1 &&\t\\\n\t(v)[1] - (w)[1] > -_mathlib_temp_float1 && (v)[1] - (w)[1] < _mathlib_temp_float1 &&\t\\\n\t(v)[2] - (w)[2] > -_mathlib_temp_float1 && (v)[2] - (w)[2] < _mathlib_temp_float1)\n\n#define VectorL2Compare(v, w, m)\t\t\t\t\t\t\\\n\t(_mathlib_temp_float1 = (m) * (m),\t\t\t\t\t\t\\\n\t_mathlib_temp_vec1[0] = (v)[0] - (w)[0], _mathlib_temp_vec1[1] = (v)[1] - (w)[1], _mathlib_temp_vec1[2] = (v)[2] - (w)[2],\t\\\n\t_mathlib_temp_vec1[0] * _mathlib_temp_vec1[0] +\t\t\\\n\t_mathlib_temp_vec1[1] * _mathlib_temp_vec1[1] +\t\t\\\n\t_mathlib_temp_vec1[2] * _mathlib_temp_vec1[2] < _mathlib_temp_float1)\n\n#define VectorCompare(v, w)\t((v)[0] == (w)[0] && (v)[1] == (w)[1] && (v)[2] == (w)[2])\n\n#define VectorMA(a, _f, b, c)\t\t\t\t\t\t\t\\\ndo {\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t_mathlib_temp_float1 = (_f);\t\t\t\t\t\t\\\n\t(c)[0] = (a)[0] + _mathlib_temp_float1 * (b)[0];\t\\\n\t(c)[1] = (a)[1] + _mathlib_temp_float1 * (b)[1];\t\\\n\t(c)[2] = (a)[2] + _mathlib_temp_float1 * (b)[2];\t\\\n} while(0);\n\n#define VectorScale(in, _scale, out)\t\t\\\ndo {\t\t\t\t\t\t\t\t\t\t\\\n\tfloat scale = (_scale);\t\t\t\t\t\\\n\t(out)[0] = (in)[0] * (scale); (out)[1] = (in)[1] * (scale); (out)[2] = (in)[2] * (scale);\t\\\n} while (0);\n\n#define anglemod(a)\t((360.0 / 65536) * ((int) ((a) * (65536 / 360.0)) & 65535))\n\n#define VectorNormalizeFast(_v)\t\t\\\ndo {\t\t\t\t\t\t\t\t\\\n\t_mathlib_temp_float1 = DotProduct((_v), (_v));\t\t\t\t\t\t\\\n\tif (_mathlib_temp_float1) {\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t_mathlib_temp_float2 = 0.5f * _mathlib_temp_float1;\t\t\t\t\\\n\t\t_mathlib_temp_int1 = *((int *) &_mathlib_temp_float1);\t\t\t\\\n\t\t_mathlib_temp_int1 = 0x5f375a86 - (_mathlib_temp_int1 >> 1);\t\\\n\t\t_mathlib_temp_float1 = *((float *) &_mathlib_temp_int1);\t\t\\\n\t\t_mathlib_temp_float1 = _mathlib_temp_float1 * (1.5f - _mathlib_temp_float2 * _mathlib_temp_float1 * _mathlib_temp_float1);\t\\\n\t\tVectorScale((_v), _mathlib_temp_float1, (_v))\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n} while (0);\n\n#define BOX_ON_PLANE_SIDE(emins, emaxs, p)\t\t\\\n\t(((p)->type < 3) ?\t\t\t\t\t\t\t\\\n\t(\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t((p)->dist <= (emins)[(p)->type]) ?\t\t\\\n\t\t\t1\t\t\t\t\t\t\t\t\t\\\n\t\t:\t\t\t\t\t\t\t\t\t\t\\\n\t\t(\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t((p)->dist >= (emaxs)[(p)->type]) ?\t\\\n\t\t\t\t2\t\t\t\t\t\t\t\t\\\n\t\t\t:\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t3\t\t\t\t\t\t\t\t\\\n\t\t)\t\t\t\t\t\t\t\t\t\t\\\n\t)\t\t\t\t\t\t\t\t\t\t\t\\\n\t:\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tBoxOnPlaneSide((emins), (emaxs), (p)))\n\n#define VectorInterpolate(v1, _frac, v2, v)\t\t\t\t\t\t\t\\\ndo {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t_mathlib_temp_float1 = _frac;\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t(v)[0] = (v1)[0] + _mathlib_temp_float1 * ((v2)[0] - (v1)[0]);\t\\\n\t(v)[1] = (v1)[1] + _mathlib_temp_float1 * ((v2)[1] - (v1)[1]);\t\\\n\t(v)[2] = (v1)[2] + _mathlib_temp_float1 * ((v2)[2] - (v1)[2]);\t\\\n} while (0);\n\n#define AngleInterpolate(v1, _frac, v2, v)\t\t\t\t\t\t\t\t\t\\\ndo {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t_mathlib_temp_float1 = _frac;\t\t\t\t\t\t\t\t\t\t\t\\\n\tVectorSubtract((v2), (v1), _mathlib_temp_vec1);\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tif (_mathlib_temp_vec1[0] > 180) _mathlib_temp_vec1[0] -= 360;\t\t\t\\\n\telse if (_mathlib_temp_vec1[0] < -180) _mathlib_temp_vec1[0] += 360;\t\\\n\tif (_mathlib_temp_vec1[1] > 180) _mathlib_temp_vec1[1] -= 360;\t\t\t\\\n\telse if (_mathlib_temp_vec1[1] < -180) _mathlib_temp_vec1[1] += 360;\t\\\n\tif (_mathlib_temp_vec1[2] > 180) _mathlib_temp_vec1[2] -= 360;\t\t\t\\\n\telse if (_mathlib_temp_vec1[2] < -180) _mathlib_temp_vec1[2] += 360;\t\\\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t(v)[0] = (v1)[0] + _mathlib_temp_float1 * _mathlib_temp_vec1[0];\t\t\\\n\t(v)[1] = (v1)[1] + _mathlib_temp_float1 * _mathlib_temp_vec1[1];\t\t\\\n\t(v)[2] = (v1)[2] + _mathlib_temp_float1 * _mathlib_temp_vec1[2];\t\t\\\n} while (0);\n\n#define FloatInterpolate(f1, _frac, f2)\t\t\t\\\n\t(_mathlib_temp_float1 = _frac,\t\t\t\t\\\n\t(f1) + _mathlib_temp_float1 * ((f2) - (f1)))\n\n#define PlaneDist(point, plane) (\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t(plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)\t\\\n)\n\n#define PlaneDiff(point, plane) (\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t(((plane)->type < 3) ? (point)[(plane)->type] - (plane)->dist: DotProduct((point), (plane)->normal) - (plane)->dist) \t\\\n)\n\nvoid VectorVectors(vec3_t forward, vec3_t right, vec3_t up);\nvoid MakeNormalVectors (vec3_t forward, vec3_t right, vec3_t up);\nvec_t VectorLength (vec3_t v);\nfloat VectorNormalize (vec3_t v);\t\t// returns vector length\n\nvoid R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]);\nvoid R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]);\n\nvoid FloorDivMod (double numer, double denom, int *quotient, int *rem);\nfixed16_t Invert24To16(fixed16_t val);\nint GreatestCommonDivisor (int i1, int i2);\n\nvoid AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);\n//VULT COLLISION\nvoid AngleVectorsFLU (const vec3_t angles, vec3_t forward, vec3_t left, vec3_t up);\nint BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct mplane_s *plane);\n\nvoid ProjectPointOnPlane(vec3_t dst, const vec3_t p, const vec3_t normal);\nvoid PerpendicularVector(vec3_t dst, const vec3_t src);\nvoid RotatePointAroundVector(vec3_t dst, const vec3_t dir, const vec3_t point, float degrees);\n\nint GetPolyCentroid(vec3_t *v, int n, float *xCentroid, float *yCentroid, float *area);\n\nextern vec3_t vec3_origin;\nextern int _mathlib_temp_int1, _mathlib_temp_int2, _mathlib_temp_int3;\nextern float _mathlib_temp_float1, _mathlib_temp_float2, _mathlib_temp_float3;\nextern vec3_t _mathlib_temp_vec1, _mathlib_temp_vec2, _mathlib_temp_vec3;\n\n#define Q_ROUND_POWER2(in, out) {\t\t\t\t\t\t\\\n\t_mathlib_temp_int1 = in;\t\t\t\t\t\t\t\\\n\tfor (out = 1; out < _mathlib_temp_int1; out <<= 1)\t\\\n\t;\t\t\t\t\t\t\t\t\t\t\t\t\\\n}\n\n\n#define clamp(a,b,c) (a = min(max(a, b), c))\n#define sgn(x) ((x < 0) ? -1 : ((x > 0) ? 1 : 0))\n\n#define MinI(a,b) min(a,b)\n#define MinD(a,b) min(a,b)\n#define MaxI(a,b) max(a,b)\n#define MaxD(a,b) max(a,b)\n\n// Minimal matrix math for rotating point around vector.\n// We have RotatePointAroundVector(), but matrix math allow me rotate few points faster than this function.\n\n/*\nInit rotation matrix 'out', 'angle' in radians, 'v' should be normilized vector.\n*/\nvoid Matrix3x3_CreateRotate(matrix3x3_t out, float angle, const vec3_t v);\n\n/*\nMultiply matrix 'in' by vector 'v', note what 'out' is vector.\n*/\nvoid Matrix3x3_MultiplyByVector(vec3_t out, const matrix3x3_t in, const vec3_t v);\n\nfloat VectorDistance(const vec3_t x, const vec3_t y);\nfloat VectorDistanceQuick(const vec3_t x, const vec3_t y);\n\n#endif /* !__MATHLIB_H__ */\n"
  },
  {
    "path": "src/md4.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n\n*/\n/* GLOBAL.H - RSAREF types and constants */\n\n#include <string.h>\n\n/* POINTER defines a generic pointer type */\ntypedef unsigned char *POINTER;\n\n/* UINT2 defines a two byte word */\ntypedef unsigned short int UINT2;\n\n/* UINT4 defines a four byte word */\n#if defined(__alpha__) || defined(_LP64) || defined(__x86_64)\ntypedef unsigned int UINT4;\n#else\ntypedef unsigned long int UINT4;\n#endif\n\n\n/* MD4.H - header file for MD4C.C */\n\n/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991.\n\nAll rights reserved.\n\nLicense to copy and use this software is granted provided that it is identified as the 'RSA Data Security, Inc. MD4 Message-Digest Algorithm' in all material mentioning or referencing this software or this function.\nLicense is also granted to make and use derivative works provided that such works are identified as 'derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm' in all material mentioning or referencing the derived work.\nRSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided 'as is' without express or implied warranty of any kind.\n\nThese notices must be retained in any copies of any part of this documentation and/or software. */\n\n/* MD4 context. */\ntypedef struct {\n    UINT4 state[4];             /* state (ABCD) */\n    UINT4 count[2];             /* number of bits, modulo 2^64 (lsb first) */\n    unsigned char buffer[64];           /* input buffer */\n} MD4_CTX;\n\nvoid MD4Init (MD4_CTX *);\nvoid MD4Update (MD4_CTX *, unsigned char *, unsigned int);\nvoid MD4Final (unsigned char [16], MD4_CTX *);\n\n\n\n/* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm */\n/* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved.\n\nLicense to copy and use this software is granted provided that it is identified as the\nRSA Data Security, Inc. MD4 Message-Digest Algorithm\n in all material mentioning or referencing this software or this function.\nLicense is also granted to make and use derivative works provided that such works are identified as\nderived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm\nin all material mentioning or referencing the derived work.\nRSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided\nas is without express or implied warranty of any kind.\n\nThese notices must be retained in any copies of any part of this documentation and/or software. */\n\n/* Constants for MD4Transform routine.  */\n#define S11 3\n#define S12 7\n#define S13 11\n#define S14 19\n#define S21 3\n#define S22 5\n#define S23 9\n#define S24 13\n#define S31 3\n#define S32 9\n#define S33 11\n#define S34 15\n\nstatic void MD4Transform (UINT4 [4], unsigned char [64]);\nstatic void Encode (unsigned char *, UINT4 *, unsigned int);\nstatic void Decode (UINT4 *, unsigned char *, unsigned int);\n\nstatic unsigned char PADDING[64] = {\n0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n};\n\n/* F, G and H are basic MD4 functions. */\n#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))\n#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))\n#define H(x, y, z) ((x) ^ (y) ^ (z))\n\n/* ROTATE_LEFT rotates x left n bits. */\n#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))\n\n/* FF, GG and HH are transformations for rounds 1, 2 and 3 */\n/* Rotation is separate from addition to prevent recomputation */\n#define FF(a, b, c, d, x, s) {(a) += F ((b), (c), (d)) + (x); (a) = ROTATE_LEFT ((a), (s));}\n\n#define GG(a, b, c, d, x, s) {(a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; (a) = ROTATE_LEFT ((a), (s));}\n\n#define HH(a, b, c, d, x, s) {(a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; (a) = ROTATE_LEFT ((a), (s));}\n\n\n/* MD4 initialization. Begins an MD4 operation, writing a new context. */\nvoid MD4Init (MD4_CTX *context)\n{\n    context->count[0] = context->count[1] = 0;\n\n/* Load magic initialization constants.*/\ncontext->state[0] = 0x67452301;\ncontext->state[1] = 0xefcdab89;\ncontext->state[2] = 0x98badcfe;\ncontext->state[3] = 0x10325476;\n}\n\n/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */\nvoid MD4Update (MD4_CTX *context, unsigned char *input, unsigned int inputLen)\n{\n    unsigned int i, index, partLen;\n\n    /* Compute number of bytes mod 64 */\n    index = (unsigned int)((context->count[0] >> 3) & 0x3F);\n\n    /* Update number of bits */\n    if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3))\n        context->count[1]++;\n\n    context->count[1] += ((UINT4)inputLen >> 29);\n\n    partLen = 64 - index;\n\n    /* Transform as many times as possible.*/\n    if (inputLen >= partLen)\n    {\n        memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen);\n        MD4Transform (context->state, context->buffer);\n\n        for (i = partLen; i + 63 < inputLen; i += 64)\n            MD4Transform (context->state, &input[i]);\n\n        index = 0;\n    }\n    else\n        i = 0;\n\n    /* Buffer remaining input */\n    memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i);\n}\n\n\n/* MD4 finalization. Ends an MD4 message-digest operation, writing the the message digest and zeroizing the context. */\nvoid MD4Final (unsigned char digest[16], MD4_CTX *context)\n{\n    unsigned char bits[8];\n    unsigned int index, padLen;\n\n    /* Save number of bits */\n    Encode (bits, context->count, 8);\n\n    /* Pad out to 56 mod 64.*/\n    index = (unsigned int)((context->count[0] >> 3) & 0x3f);\n    padLen = (index < 56) ? (56 - index) : (120 - index);\n    MD4Update (context, PADDING, padLen);\n\n    /* Append length (before padding) */\n    MD4Update (context, bits, 8);\n\n    /* Store state in digest */\n    Encode (digest, context->state, 16);\n\n    /* Zeroize sensitive information.*/\n    memset ((POINTER)context, 0, sizeof (*context));\n}\n\n\n/* MD4 basic transformation. Transforms state based on block. */\nstatic void MD4Transform (UINT4 state[4], unsigned char block[64])\n{\n    UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];\n\n    Decode (x, block, 64);\n\n/* Round 1 */\nFF (a, b, c, d, x[ 0], S11);                /* 1 */\nFF (d, a, b, c, x[ 1], S12);                /* 2 */\nFF (c, d, a, b, x[ 2], S13);                /* 3 */\nFF (b, c, d, a, x[ 3], S14);                /* 4 */\nFF (a, b, c, d, x[ 4], S11);                /* 5 */\nFF (d, a, b, c, x[ 5], S12);                /* 6 */\nFF (c, d, a, b, x[ 6], S13);                /* 7 */\nFF (b, c, d, a, x[ 7], S14);                /* 8 */\nFF (a, b, c, d, x[ 8], S11);                /* 9 */\nFF (d, a, b, c, x[ 9], S12);                /* 10 */\nFF (c, d, a, b, x[10], S13);            /* 11 */\nFF (b, c, d, a, x[11], S14);            /* 12 */\nFF (a, b, c, d, x[12], S11);            /* 13 */\nFF (d, a, b, c, x[13], S12);            /* 14 */\nFF (c, d, a, b, x[14], S13);            /* 15 */\nFF (b, c, d, a, x[15], S14);            /* 16 */\n\n/* Round 2 */\nGG (a, b, c, d, x[ 0], S21);            /* 17 */\nGG (d, a, b, c, x[ 4], S22);            /* 18 */\nGG (c, d, a, b, x[ 8], S23);            /* 19 */\nGG (b, c, d, a, x[12], S24);            /* 20 */\nGG (a, b, c, d, x[ 1], S21);            /* 21 */\nGG (d, a, b, c, x[ 5], S22);            /* 22 */\nGG (c, d, a, b, x[ 9], S23);            /* 23 */\nGG (b, c, d, a, x[13], S24);            /* 24 */\nGG (a, b, c, d, x[ 2], S21);            /* 25 */\nGG (d, a, b, c, x[ 6], S22);            /* 26 */\nGG (c, d, a, b, x[10], S23);            /* 27 */\nGG (b, c, d, a, x[14], S24);            /* 28 */\nGG (a, b, c, d, x[ 3], S21);            /* 29 */\nGG (d, a, b, c, x[ 7], S22);            /* 30 */\nGG (c, d, a, b, x[11], S23);            /* 31 */\nGG (b, c, d, a, x[15], S24);            /* 32 */\n\n/* Round 3 */\nHH (a, b, c, d, x[ 0], S31);                /* 33 */\nHH (d, a, b, c, x[ 8], S32);            /* 34 */\nHH (c, d, a, b, x[ 4], S33);            /* 35 */\nHH (b, c, d, a, x[12], S34);            /* 36 */\nHH (a, b, c, d, x[ 2], S31);            /* 37 */\nHH (d, a, b, c, x[10], S32);            /* 38 */\nHH (c, d, a, b, x[ 6], S33);            /* 39 */\nHH (b, c, d, a, x[14], S34);            /* 40 */\nHH (a, b, c, d, x[ 1], S31);            /* 41 */\nHH (d, a, b, c, x[ 9], S32);            /* 42 */\nHH (c, d, a, b, x[ 5], S33);            /* 43 */\nHH (b, c, d, a, x[13], S34);            /* 44 */\nHH (a, b, c, d, x[ 3], S31);            /* 45 */\nHH (d, a, b, c, x[11], S32);            /* 46 */\nHH (c, d, a, b, x[ 7], S33);            /* 47 */\nHH (b, c, d, a, x[15], S34);            /* 48 */\n\nstate[0] += a;\nstate[1] += b;\nstate[2] += c;\nstate[3] += d;\n\n    /* Zeroize sensitive information.*/\n    memset ((POINTER)x, 0, sizeof (x));\n}\n\n\n/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */\nstatic void Encode (unsigned char *output, UINT4 *input, unsigned int len)\n{\n    unsigned int i, j;\n\n    for (i = 0, j = 0; j < len; i++, j += 4) {\n        output[j] = (unsigned char)(input[i] & 0xff);\n        output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);\n        output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);\n        output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);\n    }\n}\n\n\n/* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */\nstatic void Decode (UINT4 *output, unsigned char *input, unsigned int len)\n{\nunsigned int i, j;\n\nfor (i = 0, j = 0; j < len; i++, j += 4)\n    output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);\n}\n\n//===================================================================\n\nunsigned Com_BlockChecksum (void *buffer, int length)\n{\n    int         digest[4];\n    unsigned    val;\n    MD4_CTX     ctx;\n\n    MD4Init (&ctx);\n    MD4Update (&ctx, (unsigned char *)buffer, length);\n    MD4Final ( (unsigned char *)digest, &ctx);\n\n    val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3];\n\n    return val;\n}\n\nvoid Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf)\n{\n    MD4_CTX     ctx;\n\n    MD4Init (&ctx);\n    MD4Update (&ctx, (unsigned char *)buffer, len);\n    MD4Final ( outbuf, &ctx);\n}\n"
  },
  {
    "path": "src/menu.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef _WIN32\n#include <dirent.h>\n#include <sys/stat.h>\n#endif\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#ifndef CLIENTONLY\n#include \"server.h\"\n#endif\n#include \"menu.h\"\n#include \"EX_browser.h\"\n#include \"Ctrl_Tab.h\"\n#include \"menu_demo.h\"\n#include \"menu_proxy.h\"\n#include \"menu_options.h\"\n#include \"menu_ingame.h\"\n#include \"menu_multiplayer.h\"\n#include \"EX_FileList.h\"\n#include \"help.h\"\n#include \"utils.h\"\n#include \"qsound.h\"\n#include \"keys.h\"\n#include \"common_draw.h\"\n#include \"r_matrix.h\"\n\nqbool vid_windowedmouse = true;\nvoid (*vid_menudrawfn)(void);\nvoid (*vid_menukeyfn)(int key);\nvoid CL_Disconnect_f(void);\n\nextern cvar_t con_shift, scr_menualpha;\n\nvoid M_Menu_Main_f (void);\n\tvoid M_Menu_SinglePlayer_f (void);\n\t\tvoid M_Menu_Load_f (void);\n\t\tvoid M_Menu_Save_f (void);\n\tvoid M_Menu_Browser_f (void);\n\tvoid M_Menu_MultiPlayer_f (void);\n\t\t\tvoid M_Menu_SEdit_f (void);\n\t\tvoid M_Menu_Demos_f (void);\n\t\tvoid M_Menu_GameOptions_f (void);\n\tvoid M_Menu_Options_f (void);\n\tvoid M_Menu_Quit_f (void);\n\nvoid M_Main_Draw (void);\n\tvoid M_SinglePlayer_Draw (void);\n\t\tvoid M_Load_Draw (void);\n\t\tvoid M_Save_Draw (void);\n\tvoid M_MultiPlayer_Draw (void);\n\t\tvoid M_ServerList_Draw (void);\n\t\t\tvoid M_SEdit_Draw (void);\n\t\tvoid M_Demo_Draw (void);\n\t\tvoid M_GameOptions_Draw (void);\n\t\tvoid M_Proxy_Draw (void);\n\tvoid M_Options_Draw (void);\n\tvoid M_Help_Draw (void);\n\tvoid M_Quit_Draw (void);\n\nvoid M_Main_Key (int key);\n\tvoid M_SinglePlayer_Key (int key);\n\t\tvoid M_Load_Key (int key);\n\t\tvoid M_Save_Key (int key);\n\tvoid M_MultiPlayerSub_Key (int key);\n\t\tvoid M_ServerList_Key (int key);\n\t\t\tvoid M_SEdit_Key (int key);\n\t\tvoid M_Demo_Key (int key);\n\t\tqbool M_GameOptions_Key (int key);\n\t\tvoid M_Proxy_Key (int key);\n\tvoid M_Options_Key (int key, wchar unichar);\n\tvoid M_Help_Key (int key);\n\tvoid M_Quit_Key (int key);\n\nvoid M_Menu_Help_f (void);\n\nm_state_t m_state;\n\nqbool    m_entersound;          // play after drawing a frame, so caching\n                                // won't disrupt the sound\nint            m_topmenu;       // set if a submenu was entered via a\n                                // menu_* command\n#define    SLIDER_RANGE    10\n\ntypedef struct menu_window_s {\n\tint x;\n\tint y;\n\tint w;\n\tint h;\n} menu_window_t;\n\n//=============================================================================\n/* Support Routines */\n\ncvar_t     scr_scaleMenu = {\"scr_scaleMenu\",\"2\"};\nint        menuwidth = 320;\nint        menuheight = 240;\n\ncvar_t     scr_centerMenu = {\"scr_centerMenu\",\"1\"};\ncvar_t     menu_ingame = {\"menu_ingame\", \"1\"};\ncvar_t     menu_botmatch_gamedir = { \"menu_botmatch_gamedir\", \"fbca\" };\ncvar_t     menu_botmatch_mod_old = { \"menu_botmatch_mod_old\", \"1\" };\nint        m_yofs = 0;\n\nvoid M_DrawCharacter (int cx, int line, int num) {\n\tDraw_Character (cx + ((menuwidth - 320)>>1), line + m_yofs, num);\n}\n\nvoid M_Print_GetPoint(int cx, int cy, int *rx, int *ry, const char *str, qbool red) {\n\tcx += ((menuwidth - 320)>>1);\n\tcy += m_yofs;\n\t*rx = cx;\n\t*ry = cy;\n\tif (red) {\n\t\tDraw_Alt_String(cx, cy, str, 1, false);\n\t}\n\telse {\n\t\tDraw_String(cx, cy, str);\n\t}\n}\n\nvoid M_Print (int cx, int cy, char *str) {\n\tint rx, ry;\n\tM_Print_GetPoint(cx, cy, &rx, &ry, str, true);\n}\n\nvoid M_PrintWhite (int cx, int cy, char *str) {\n\tDraw_String (cx + ((menuwidth - 320)>>1), cy + m_yofs, str);\n}\n\n// replacement of M_DrawTransPic - sometimes we need the real coordinate of the pic\nstatic void M_DrawTransPic_GetPoint (int x, int y, int *rx, int *ry, mpic_t *pic)\n{\n\t*rx = x + ((menuwidth - 320)>>1);\n\t*ry = y + m_yofs;\n\tDraw_TransPic (x + ((menuwidth - 320)>>1), y + m_yofs, pic);\n}\n\nvoid M_DrawTransPic (int x, int y, mpic_t *pic) {\n\tint tx, ty;\n\tM_DrawTransPic_GetPoint(x, y, &tx, &ty, pic);\n}\n\nvoid M_DrawPic (int x, int y, mpic_t *pic) {\n\tDraw_Pic (x + ((menuwidth - 320)>>1), y + m_yofs, pic);\n}\n\nvoid M_DrawTextBox (int x, int y, int width, int lines) {\n\tDraw_TextBox (x + ((menuwidth - 320) >> 1), y + m_yofs, width, lines);\n}\n\nvoid M_DrawSlider (int x, int y, float range) {\n\tint    i;\n\n\trange = bound(0, range, 1);\n\tM_DrawCharacter (x-8, y, 128);\n\tfor (i=0 ; i<SLIDER_RANGE ; i++)\n\t\tM_DrawCharacter (x + i*8, y, 129);\n\tM_DrawCharacter (x+i*8, y, 130);\n\tM_DrawCharacter (x + (SLIDER_RANGE-1)*8 * range, y, 131);\n}\n\nqbool Key_IsLeftRightSameBind(int b);\n\nvoid M_FindKeysForCommand (const char *command, int *twokeys) {\n\tint count, j, l;\n\tchar *b;\n\n\ttwokeys[0] = twokeys[1] = -1;\n\tl = strlen(command) + 1;\n\tcount = 0;\n\n\tfor (j = 0 ; j < (sizeof(keybindings) / sizeof(*keybindings)); j++) {\n\t\tb = keybindings[j];\n\t\tif (!b)\n\t\t\tcontinue;\n\t\tif (!strncmp (b, command, l)) {\n\t\t\tif (count) {\n\t\t\t\tif (j == twokeys[0] + 1 && (twokeys[0] == K_LCTRL || twokeys[0] == K_LSHIFT || twokeys[0] == K_LALT)) {\n\n\t\t\t\t\ttwokeys[0]--;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\ttwokeys[count] = j;\n\t\t\tcount++;\n\t\t\tif (count == 2) {\n\n\t\t\t\tif (Key_IsLeftRightSameBind(twokeys[1]))\n\t\t\t\t\ttwokeys[1]++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid M_Unscale_Menu(void)\n{\n\t// do not scale this menu\n\tif (scr_scaleMenu.value) \n\t{\n\t\tmenuwidth = vid.width;\n\t\tmenuheight = vid.height;\n\t\tR_OrthographicProjection(0, menuwidth, menuheight, 0, -99999, 99999);\n\t}\n}\n\n// will apply menu scaling effect for given window region\n// scr_scaleMenu 1 uses glOrtho function and we use the same algorithm in here\nstatic void M_Window_Adjust(const menu_window_t *original, menu_window_t *scaled)\n{\n\tdouble sc_x, sc_y; // scale factors\n\n\tif (scr_scaleMenu.value)\n\t{\n\t\tsc_x = (double) vid.width / (double) menuwidth;\n\t\tsc_y = (double) vid.height / (double) menuheight;\n\t\tscaled->x = original->x * sc_x;\n\t\tscaled->y = original->y * sc_y;\n\t\tscaled->w = original->w * sc_x;\n\t\tscaled->h = original->h * sc_y;\n\t}\n\telse\n\t{\n\t\tmemcpy(scaled, original, sizeof(menu_window_t));\n\t}\n}\n\n// this function will look at window borders and current mouse cursor position\n// and will change which item in the window should be selected\n// we presume that all entries have same height and occupy the whole window\n// 1st par: input, window position & size\n// 2nd par: input, mouse position\n// 3rd par: input, how many entries does the window have\n// 4th par: output, newly selected entry, first entry is 0, second 1, ...\n// return value: does the cursor belong to this window? yes/no\nstatic qbool M_Mouse_Select(const menu_window_t *uw, const mouse_state_t *m, int entries, int *newentry)\n{\n\tdouble entryheight;\n\tdouble nentry;\n\tmenu_window_t rw;\n\tmenu_window_t *w = &rw; // just a language \"shortcut\"\n\t\n\tM_Window_Adjust(uw, w);\n\n\t// window is invisible\n\tif (!(w->h > 0) || !(w->w > 0)) return false;\n\n\t// check if the pointer is inside of the window\n\tif (m->x < w->x || m->y < w->y || m->x > w->x + w->w || m->y > w->y + w->h)\n\t\treturn false; // no, it's not\n\n\tentryheight = w->h / entries;\n\tnentry = (int) (m->y - w->y) / (int) entryheight;\n\t\n\t*newentry = bound(0, nentry, entries-1);\n\n\treturn true;\n}\n\n//=============================================================================\n\nvoid M_EnterMenu (int state) {\n\tif (key_dest != key_menu) {\n\t\tm_topmenu = state;\n\t\tif (state != m_proxy) {\n\t\t\tCon_ClearNotify ();\n\t\t\t// hide the console\n\t\t\tscr_conlines = 0;\n\t\t\tscr_con_current = 0;\n\t\t}\n\t} else {\n\t\tm_topmenu = m_none;\n\t}\n\n\tkey_dest = key_menu;\n\tm_state = state;\n\tm_entersound = true;\n}\n\nstatic void M_ToggleHeadMenus(int type)\n{\n\tm_entersound = true;\n\n\t// do not ever use ingame menu, if user doesn't wish so\n\tif (!menu_ingame.integer) {\n\t\ttype = m_main;\n\t}\n\n\tif (key_dest == key_menu) {\n\t\tif (m_state != type) {\n\t\t\tM_EnterMenu(type);\n\t\t\treturn;\n\t\t}\n\t\tkey_dest = key_game;\n\t\tm_state = m_none;\n\t} else {\n\t\tM_EnterMenu(type);\n\t}\n}\n\nvoid M_ToggleMenu_f (void) {\n\tif (cls.state == ca_active) {\n\t\tM_ToggleHeadMenus(m_ingame);\n\t}\n\telse M_ToggleHeadMenus(m_main);\n}\n\nvoid M_EnterProxyMenu (void) {\n\tM_EnterMenu(m_proxy);\n}\n\nvoid M_LeaveMenu (int parent) {\n\tif (m_topmenu == m_state) {\n\t\tm_state = m_none;\n\t\tkey_dest = key_game;\n\t} else {\n\t\tm_state = parent;\n\t\tm_entersound = true;\n\t}\n}\n\n// dunno how to call this function\n// must leave all menus instead of calling few functions in row\nvoid M_LeaveMenus (void) {\n//\tm_entersound = true; // fixme: which value we must set ???\n\n\tm_topmenu = m_none;\n\tm_state   = m_none;\n\tkey_dest  = key_game;\n}\n\nvoid M_ToggleProxyMenu_f (void) {\n\t// this is what user has bound to a key - that means \n\t// when in proxy menu -> turn it off; and vice versa\n\tm_entersound = true;\n\n\tif ((key_dest == key_menu) && (m_state == m_proxy)) {\n\t\tM_LeaveMenus();\n\t\tMenu_Proxy_Toggle();\n\t} else {\n\t\tM_EnterMenu (m_proxy);\n\t\tMenu_Proxy_Toggle();\n\t}\n}\n\n//=============================================================================\n/* MAIN MENU */\n\nint    m_main_cursor;\nstatic qbool\tnewmainmenu = false;\nmenu_window_t m_main_window;\n\nvoid M_Menu_Main_f (void) {\n\tM_EnterMenu (m_main);\n}\n\n#define BIGLETTER_WIDTH\t64\n#define BIGLETTER_HEIGHT\t64\n#define BIGMENU_LEFT\t\t\t\t72\n#define BIGMENU_TOP\t\t\t\t\t32\n#define BIGMENU_ITEMS_SCALE\t\t\t0.3\n#define\tBIGMENU_LETTER_SPACING\t\t-2\n#define BIGMENU_VERTICAL_PADDING\t2\n\ntypedef struct bigmenu_items_s {\n\tconst char *label;\n\tvoid (* enter_handler) (void);\n} bigmenu_items_t;\n\n#define BIGMENU_ITEMS_COUNT(x) (sizeof(x) / sizeof(bigmenu_items_t))\n\nbigmenu_items_t mainmenu_items[] = {\n\t{\"Single Player\", M_Menu_SinglePlayer_f},\n\t{\"Multiplayer\", M_Menu_MultiPlayer_f},\n\t{\"Options\", M_Menu_Options_f},\n\t{\"Demos\", M_Menu_Demos_f},\n\t{\"Help\", M_Menu_Help_f},\n\t{\"Quit\", M_Menu_Quit_f}\n};\n\n#define    MAIN_ITEMS    (newmainmenu ? BIGMENU_ITEMS_COUNT(mainmenu_items) : 5)\n\n// mcharset must be supported in this point\nstatic void M_BigMenu_DrawItems(bigmenu_items_t *menuitems, const unsigned int items, int left_corner, int top_corner, int *width, int *height)\n{\n\tint i;\n\tint mheight = 0;\n\tint mwidth = 0;\n\tint x = left_corner;\n\tint y = top_corner;\n\n\tfor (i = 0; i < items; i++) {\n\t\tint thiswidth = strlen(menuitems[i].label)*BIGMENU_ITEMS_SCALE*BIGLETTER_WIDTH;\n\t\tmheight += BIGMENU_ITEMS_SCALE*BIGLETTER_HEIGHT + BIGMENU_VERTICAL_PADDING;\n\t\tmwidth = max(mwidth, thiswidth);\n\t\tDraw_BigString(x, y, menuitems[i].label, NULL, 0, \n\t\t\tBIGMENU_ITEMS_SCALE, 1, BIGMENU_LETTER_SPACING);\n\t\ty += BIGMENU_ITEMS_SCALE*BIGLETTER_HEIGHT + BIGMENU_VERTICAL_PADDING;\n\t}\n\n\t*width = mwidth;\n\t*height = mheight;\n}\n\nvoid M_Main_Draw (void) {\n\tint f = (int) (curtime * 10) % 6;\n\tmpic_t *p;\n\tint itemheight;\n\n\tM_DrawTransPic (16, BIGMENU_TOP, Draw_CachePic (CACHEPIC_QPLAQUE) );\n\n\t// the Main Manu heading\n\tp = Draw_CachePic (CACHEPIC_TTL_MAIN);\n\tM_DrawPic ( (320-p->width)/2, 4, p);\n\n\t// Main Menu items\n\tif (Draw_BigFontAvailable()) {\n\t\tnewmainmenu = true;\n\t\tm_main_window.x = BIGMENU_LEFT + (menuwidth - 320)/2;\n\t\tm_main_window.y = BIGMENU_TOP + m_yofs;\n\t\tM_BigMenu_DrawItems(mainmenu_items, BIGMENU_ITEMS_COUNT(mainmenu_items), m_main_window.x, m_main_window.y,\n\t\t\t\t\t\t &m_main_window.w, &m_main_window.h);\n\t\titemheight = m_main_window.h / BIGMENU_ITEMS_COUNT(mainmenu_items);\n\t}\n\telse {\n\t\tnewmainmenu = false;\n\t\tp = Draw_CachePic (CACHEPIC_MAINMENU);\n\t\tm_main_window.w = p->width;\n\t\tm_main_window.h = p->height;\n\t\tM_DrawTransPic_GetPoint (72, 32, &m_main_window.x, &m_main_window.y, p);\n\t\t\n\t\t// main menu specific correction, mainmenu.lmp|png have some useless extra space at the bottom\n\t\t// that makes the mouse pointer position calculation imperfect\n\t\tm_main_window.h *= 0.9;\n\n\t\titemheight = 20;\n\t}\t\n\n\tM_DrawTransPic (54, BIGMENU_TOP + m_main_cursor * itemheight,\n\t\tDraw_CachePic(CACHEPIC_MENUDOT1 + f)\n\t);\n}\n\nstatic void M_Main_Enter(const unsigned int entry)\n{\n\tif (newmainmenu) {\n\t\tmainmenu_items[entry].enter_handler();\n\t}\n\telse {\n\t\tswitch (entry) {\n\t\tcase 0: M_Menu_SinglePlayer_f (); break;\n\t\tcase 1:\tM_Menu_MultiPlayer_f (); break;\n\t\tcase 2: M_Menu_Options_f (); break;\n\t\tcase 4: M_Menu_Quit_f (); break;\n\t\t}\n\t}\n}\n\nvoid M_Main_Key (int key) {\n\tswitch (key) {\n\tcase K_ESCAPE:\n\tcase K_MOUSE2:\n\t\tif (cls.state < ca_active)\n\t\t\tkey_dest = key_console;\n\t\telse \n\t\t\tkey_dest = key_game;\n\t\tm_state = m_none;\n\t\tbreak;\n\n\tcase '`':\n\tcase '~':\n\t\tkey_dest = key_console;\n\t\tm_state = m_none;\n\t\tbreak;\n\n\tcase K_UPARROW:\n\tcase K_MWHEELUP:\n\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\tif (--m_main_cursor < 0)\n\t\t\tm_main_cursor = MAIN_ITEMS - 1;\n\t\tbreak;\n\n\tcase K_DOWNARROW:\n\tcase K_MWHEELDOWN:\n\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\tif (++m_main_cursor >= MAIN_ITEMS)\n\t\t\tm_main_cursor = 0;\n\t\tbreak;\n\n\tcase K_HOME:\n\tcase K_PGUP:\n\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\tm_main_cursor = 0;\n\t\tbreak;\n\n\tcase K_END:\n\tcase K_PGDN:\n\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\tm_main_cursor = MAIN_ITEMS - 1;\n\t\tbreak;\n\n\tcase K_ENTER:\n\tcase K_MOUSE1:\n\t\tm_entersound = true;\n\t\tM_Main_Enter((unsigned) m_main_cursor);\n\t}\n}\n\nstatic qbool M_Main_Mouse_Event(const mouse_state_t* ms)\n{\n\tM_Mouse_Select(&m_main_window, ms, MAIN_ITEMS, &m_main_cursor);\n    \n    if (ms->button_up == 1) M_Main_Key(K_MOUSE1);\n    if (ms->button_up == 2) M_Main_Key(K_MOUSE2);\n\n    return true;\n}\n\n//=============================================================================\n/* OPTIONS MENU */\n\nvoid M_Menu_Options_f (void) {\n\tM_EnterMenu (m_options);\n}\n\nvoid M_Options_Key (int key, wchar unichar) {\n\tMenu_Options_Key(key, unichar); // menu_options module\n}\n\nvoid M_Options_Draw (void) {\n\tMenu_Options_Draw();\t// menu_options module\n}\n\n//=============================================================================\n/* PROXY MENU */\n\n// key press in demos menu\nvoid M_Proxy_Key (int key) {\n\tint togglekeys[2];\n\n\t// the menu_proxy module doesn't know which key user has bound to toggleproxymenu action\n\tM_FindKeysForCommand(\"toggleproxymenu\", togglekeys);\n\tif ((key == togglekeys[0]) || (key == togglekeys[1])) {\n\t\tMenu_Proxy_Toggle(); \n\t\tM_LeaveMenus();\n\t\treturn;\n\t}\n\n\t// ppl are used to access console even when in qizmo menu\n\tM_FindKeysForCommand(\"toggleconsole\", togglekeys);\n\tif ((key == togglekeys[0]) || (key == togglekeys[1])) {\n\t\tCon_ToggleConsole_f();\n\t\treturn;\n\t}\n\n\tMenu_Proxy_Key(key); // menu_proxy module\n}\n\n\n//=============================================================================\n/* HELP MENU */\n\nint        help_page;\n#define    NUM_HELP_PAGES    6\n\nvoid M_Menu_Help_f (void) {\n\tM_EnterMenu (m_help);\n}\n\nvoid M_Help_Draw (void) {\n\tint x, y, w, h;\n\n\tM_Unscale_Menu();\n\n    // this will add top, left and bottom padding\n    // right padding is not added because it causes annoying scrollbar behaviour\n    // when mouse gets off the scrollbar to the right side of it\n\tw = vid.width - OPTPADDING; // here used to be a limit to 512x... size\n\th = vid.height - OPTPADDING*2;\n\tx = OPTPADDING;\n\ty = OPTPADDING;\n\n\tMenu_Help_Draw (x, y, w, h);\n}\n\n//=============================================================================\n/* QUIT MENU */\n\nint        msgNumber;\nint        m_quit_prevstate;\nqbool    wasInMenus;\n\nvoid M_Menu_Quit_f (void) {\n\textern cvar_t cl_confirmquit;\n\n\tif (!cl_confirmquit.integer) Host_Quit();\n\n\tif (m_state == m_quit)\n\t\treturn;\n\twasInMenus = (key_dest == key_menu);\n\tm_quit_prevstate = m_state;\n\tmsgNumber = rand()&7;\n\tM_EnterMenu (m_quit);\n}\n\nvoid M_Quit_Key (int key) {\n\tswitch (key) {\n\t\tcase K_MOUSE2:\n\t\tcase K_ESCAPE:\n\t\tcase 'n':\n\t\tcase 'N':\n\t\t\tif (wasInMenus) {\n\t\t\t\tm_state = m_quit_prevstate;\n\t\t\t\tm_entersound = true;\n\t\t\t} else {\n\t\t\t\tkey_dest = key_game;\n\t\t\t\tm_state = m_none;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase '`':\n\t\tcase '~':\n\t\t\tkey_dest = key_console;\n\t\t\tm_state = m_none;\n\t\t\tbreak;\n\n\t\tcase K_MOUSE1:\n\t\tcase K_ENTER:\n\t\tcase 'Y':\n\t\tcase 'y':\n\t\t\tkey_dest = key_console;\n\t\t\tHost_Quit ();\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nvoid M_Quit_Draw (void) { // Quit screen text.\n\tM_DrawTextBox (0, 0, 38, 26);\n\tM_PrintWhite (16, 12,  \"  Quake version 1.09 by id Software\\n\\n\");\n\tM_PrintWhite (16, 28,  \"Programming        Art \\n\");\n\tM_Print (16, 36,  \" John Carmack       Adrian Carmack\\n\");\n\tM_Print (16, 44,  \" Michael Abrash     Kevin Cloud\\n\");\n\tM_Print (16, 52,  \" John Cash          Paul Steed\\n\");\n\tM_Print (16, 60,  \" Dave 'Zoid' Kirsch\\n\");\n\tM_PrintWhite (16, 68,  \"Design             Biz\\n\");\n\tM_Print (16, 76,  \" John Romero        Jay Wilbur\\n\");\n\tM_Print (16, 84,  \" Sandy Petersen     Mike Wilson\\n\");\n\tM_Print (16, 92,  \" American McGee     Donna Jackson\\n\");\n\tM_Print (16, 100,  \" Tim Willits        Todd Hollenshead\\n\");\n\tM_PrintWhite (16, 108, \"Support            Projects\\n\");\n\tM_Print (16, 116, \" Barrett Alexander  Shawn Green\\n\");\n\tM_PrintWhite (16, 124, \"Sound Effects\\n\");\n\tM_Print (16, 132, \" Trent Reznor and Nine Inch Nails\\n\\n\");\n\tM_PrintWhite (16, 148, \"Quake is a trademark of Id Software,\\n\");\n\tM_PrintWhite (16, 156, \"inc., (c)1996 Id Software, inc. All\\n\");\n\tM_PrintWhite (16, 164, \"rights reserved. NIN logo is a\\n\");\n\tM_PrintWhite (16, 172, \"registered trademark licensed to\\n\");\n\tM_PrintWhite (16, 180, \"Nothing Interactive, Inc. All rights\\n\");\n\tM_PrintWhite (16, 188, \"reserved.\\n\\n\");\n\tM_Print (16, 204, \"          Press y to exit\\n\");\n}\n\n//=============================================================================\n/* SINGLE PLAYER MENU */\n\n#ifndef CLIENTONLY\n\n#define    SINGLEPLAYER_ITEMS    3\nint    m_singleplayer_cursor;\nqbool m_singleplayer_confirm;\nqbool m_singleplayer_notavail;\nmenu_window_t m_singleplayer_window;\nstatic qbool m_singleplayer_big = false;\nstatic bigmenu_items_t m_singleplayer_items[] = {\n\t{\"New Game\", NULL},\n\t{\"Load\", NULL}, \n\t{\"Save\", NULL}\n};\n\nextern    cvar_t    maxclients;\n\nvoid M_Menu_SinglePlayer_f (void) {\n\tM_EnterMenu (m_singleplayer);\n\tm_singleplayer_confirm = false;\n\tm_singleplayer_notavail = false;\n}\n\nvoid M_SinglePlayer_Draw (void) {\n\tint f = (int)(curtime * 10)%6;\n\tmpic_t *p;\n\tint itemheight;\n\n#ifndef WITH_NQPROGS\n\tif (m_singleplayer_notavail) {\n\t\tp = Draw_CachePic (\"gfx/ttl_sgl.lmp\");\n\t\tM_DrawPic ( (320-p->width)/2, 4, p);\n\t\tM_DrawTextBox (60, 10*8, 24, 4);\n\t\tM_PrintWhite (80, 12*8, \" Cannot start a game\");\n\t\tM_PrintWhite (80, 13*8, \"spprogs.dat not found\");\n\t\treturn;\n\t}\n#endif\n\n\tif (m_singleplayer_confirm) {\n\t\tM_PrintWhite (64, 11*8, \"Are you sure you want to\");\n\t\tM_PrintWhite (64, 12*8, \"    start a new game?\");\n\t\treturn;\n\t}\n\n\tM_DrawTransPic (16, BIGMENU_TOP, Draw_CachePic (CACHEPIC_QPLAQUE) );\n\tp = Draw_CachePic (CACHEPIC_TTL_SGL);\n\tM_DrawPic ( (320-p->width)/2, 4, p);\n\n\tif (Draw_BigFontAvailable()) {\n\t\tm_singleplayer_big = true;\n\t\tm_singleplayer_window.x = BIGMENU_LEFT + ((menuwidth - 320)>>1);\n\t\tm_singleplayer_window.y = BIGMENU_TOP + m_yofs;\n\t\tM_BigMenu_DrawItems(m_singleplayer_items, BIGMENU_ITEMS_COUNT(m_singleplayer_items),\n\t\t\tm_singleplayer_window.x, m_singleplayer_window.y, &m_singleplayer_window.w,\n\t\t\t&m_singleplayer_window.h);\n\t\titemheight = m_singleplayer_window.h / BIGMENU_ITEMS_COUNT(m_singleplayer_items);\n\t}\n\telse {\n\t\tm_singleplayer_big = false;\n\t\tp = Draw_CachePic (CACHEPIC_SP_MENU);\n\t\tm_singleplayer_window.w = p->width;\n\t\tm_singleplayer_window.h = p->height;\n\t\tM_DrawTransPic_GetPoint(72, 32, &m_singleplayer_window.x, &m_singleplayer_window.y, p);\n\t\titemheight = 20;\n\t}\n\n\tM_DrawTransPic (54, BIGMENU_TOP + m_singleplayer_cursor * itemheight,\n\t\tDraw_CachePic(CACHEPIC_MENUDOT1 + f)\n\t);\n}\n\n#ifndef WITH_NQPROGS\nstatic void CheckSPGame (void) {\n\tvfsfile_t *v;\n\tif ((v = FS_OpenVFS(\"spprogs.dat\", \"rb\", FS_ANY))) {\n\t\tVFS_CLOSE(v);\n\t\tm_singleplayer_notavail = false;\n\t} else {\n\t\tm_singleplayer_notavail = true;\n\t}\n}\n#endif\t// !WITH_NQPROGS\n\nstatic void StartNewGame(void)\n{\n\textern cvar_t sv_progtype;\n\n\tkey_dest = key_game;\n\tm_state = m_none;\n\tCvar_Set(&maxclients, \"1\");\n\tCvar_Set(&teamplay, \"0\");\n\tCvar_Set(&deathmatch, \"0\");\n\tCvar_Set(&coop, \"0\");\n\n\tCvar_Set(&sv_progsname, \"spprogs\"); // force progsname\n#ifdef USE_PR2\n\tCvar_Set(&sv_progtype, \"0\"); // force .dat\n#endif\n\n\tif (com_serveractive) {\n\t\tCbuf_AddText(\"disconnect\\n\");\n\t}\n\n\tCbuf_AddText(\"map start\\n\");\n}\n\nvoid M_SinglePlayer_Key (int key) {\n#ifndef WITH_NQPROGS\n\tif (m_singleplayer_notavail) {\n\t\tswitch (key) {\n\t\t\tcase K_BACKSPACE:\n\t\t\tcase K_ESCAPE:\n\t\t\tcase K_ENTER:\n\t\t\t\tm_singleplayer_notavail = false;\n\t\t\t\tbreak;\n\t\t}\n\t\treturn;\n\t}\n#endif\n\n\tif (m_singleplayer_confirm) {\n\t\tif (key == K_ESCAPE || key == 'n' || key == K_MOUSE2) {\n\t\t\tm_singleplayer_confirm = false;\n\t\t\tm_entersound = true;\n\t\t} else if (key == 'y' || key == K_ENTER || key == K_MOUSE1) {\n\t\t\tStartNewGame ();\n\t\t}\n\t\treturn;\n\t}\n\n\tswitch (key) {\n\t\tcase K_BACKSPACE:\n\t\t\tm_topmenu = m_none;    // intentional fallthrough\n\t\tcase K_MOUSE2:\n\t\tcase K_ESCAPE:\n\t\t\tM_LeaveMenu (m_main);\n\t\t\tbreak;\n\n\t\tcase '`':\n\t\tcase '~':\n\t\t\tkey_dest = key_console;\n\t\t\tm_state = m_none;\n\t\t\tbreak;\n\n\t\tcase K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\tif (++m_singleplayer_cursor >= SINGLEPLAYER_ITEMS)\n\t\t\t\tm_singleplayer_cursor = 0;\n\t\t\tbreak;\n\n\t\tcase K_UPARROW:\n\t\tcase K_MWHEELUP:\n\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\tif (--m_singleplayer_cursor < 0)\n\t\t\t\tm_singleplayer_cursor = SINGLEPLAYER_ITEMS - 1;\n\t\t\tbreak;\n\n\t\tcase K_HOME:\n\t\tcase K_PGUP:\n\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\tm_singleplayer_cursor = 0;\n\t\t\tbreak;\n\n\t\tcase K_END:\n\t\tcase K_PGDN:\n\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\tm_singleplayer_cursor = SINGLEPLAYER_ITEMS - 1;\n\t\t\tbreak;\n\n\t\tcase K_ENTER:\n\t\tcase K_MOUSE1:\n\t\t\tswitch (m_singleplayer_cursor) {\n\t\t\t\tcase 0:\n#ifndef WITH_NQPROGS\n\t\t\t\t\tCheckSPGame ();\n\t\t\t\t\tif (m_singleplayer_notavail) {\n\t\t\t\t\t\tm_entersound = true;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n#endif\n\t\t\t\t\tif (com_serveractive) {\n\t\t\t\t\t\t// bring up confirmation dialog\n\t\t\t\t\t\tm_singleplayer_confirm = true;\n\t\t\t\t\t\tm_entersound = true;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tStartNewGame ();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 1:\n\t\t\t\t\tM_Menu_Load_f ();\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 2:\n\t\t\t\t\tM_Menu_Save_f ();\n\t\t\t\t\tbreak;\n\t\t\t}\n\t}\n}\n\nqbool M_SinglePlayer_Mouse_Event(const mouse_state_t* ms)\n{\n\tM_Mouse_Select(&m_singleplayer_window, ms, SINGLEPLAYER_ITEMS, &m_singleplayer_cursor);\n\n    if (ms->button_up == 1) M_SinglePlayer_Key(K_MOUSE1);\n    if (ms->button_up == 2) M_SinglePlayer_Key(K_MOUSE2);\n\n    return true;\n}\n\n#else    // !CLIENTONLY\n\nvoid M_Menu_SinglePlayer_f (void) {\n\tM_EnterMenu (m_singleplayer);\n}\n\nvoid M_SinglePlayer_Draw (void) {\n\tmpic_t *p;\n\n\tM_DrawTransPic (16, 4, Draw_CachePic (CACHEPIC_QPLAQUE) );\n\tp = Draw_CachePic (CACHEPIC_TTL_SGL);\n\tM_DrawPic ( (320-p->width)/2, 4, p);\n\t//    M_DrawTransPic (72, 32, Draw_CachePic (CACHEPIC_SP_MENU) );\n\n\tM_DrawTextBox (60, 10*8, 23, 4);\n\tM_PrintWhite (88, 12*8, \"This client is for\");\n\tM_PrintWhite (88, 13*8, \"Internet play only\");\n}\n\nvoid M_SinglePlayer_Key (int key) {\n\tswitch (key) {\n\t\tcase K_BACKSPACE:\n\t\t\tm_topmenu = m_none;    // intentional fallthrough\n\t\tcase K_ESCAPE:\n\t\tcase K_ENTER:\n\t\t\tM_LeaveMenu (m_main);\n\t\t\tbreak;\n\n\t\tcase '`':\n\t\tcase '~':\n\t\t\tkey_dest = key_console;\n\t\t\tm_state = m_none;\n\t\t\tbreak;\n\t}\n}\n#endif    // CLIENTONLY\n\n\n//=============================================================================\n/* LOAD/SAVE MENU */\n\n#ifndef CLIENTONLY\n\n#define    MAX_SAVEGAMES        12\n\nconst char* save_filenames[] = {\n\t\"s0.sav\", \"s1.sav\", \"s2.sav\", \"s3.sav\", \"s4.sav\", \"s5.sav\", \"s6.sav\", \"s7.sav\", \"s8.sav\", \"s9.sav\", \"s10.sav\", \"s11.sav\"\n};\n#ifdef C_ASSERT\nC_ASSERT(sizeof(save_filenames) / sizeof(save_filenames[0]) == MAX_SAVEGAMES);\n#endif\n\nint        load_cursor;        // 0 < load_cursor < MAX_SAVEGAMES\nchar    m_filenames[MAX_SAVEGAMES][SAVEGAME_COMMENT_LENGTH + 1];\nint        loadable[MAX_SAVEGAMES];\nmenu_window_t load_window, save_window;\n\nvoid M_ScanSaves(char* sp_gamedir)\n{\n\tint i, j;\n\tchar name[MAX_OSPATH];\n\tvfsfile_t* f;\n\n\tfor (i = 0; i < MAX_SAVEGAMES; i++) {\n\t\tloadable[i] = false;\n\t\tstrlcpy(m_filenames[i], \"--- UNUSED SLOT ---\", SAVEGAME_COMMENT_LENGTH + 1);\n\n\t\tFS_SaveGameDirectory(name, sizeof(name));\n\t\tstrlcat(name, save_filenames[i], sizeof(name));\n\t\tif (!(f = FS_OpenVFS(name, \"rb\", FS_NONE_OS))) {\n\t\t\tcontinue;\n\t\t}\n\t\tVFS_GETS(f, name, sizeof(name));\n\t\tVFS_GETS(f, name, sizeof(name));\n\t\tstrlcpy(m_filenames[i], name, sizeof(m_filenames[i]));\n\n\t\t// change _ back to space\n\t\tfor (j = 0; j < SAVEGAME_COMMENT_LENGTH; j++) {\n\t\t\tif (m_filenames[i][j] == '_') {\n\t\t\t\tm_filenames[i][j] = ' ';\n\t\t\t}\n\t\t}\n\t\tloadable[i] = true;\n\t\tVFS_CLOSE(f);\n\t}\n}\n\nvoid M_Menu_Load_f (void) {\n\tvfsfile_t *f;\n\n\tif (!(f = FS_OpenVFS(\"spprogs.dat\", \"rb\", FS_ANY)))\n\t\treturn;\n\n\tM_EnterMenu (m_load);\n\t// VFS-FIXME: file_from_gamedir is not set in FS_OpenVFS\n\tM_ScanSaves (!file_from_gamedir ? \"qw\" : com_gamedir);\n}\n\nvoid M_Menu_Save_f (void) {\n\tif (sv.state != ss_active)\n\t\treturn;\n\tif (cl.intermission)\n\t\treturn;\n\n\tM_EnterMenu (m_save);\n\tM_ScanSaves (com_gamedir);\n}\n\nvoid M_Load_Draw (void) {\n\tint i;\n\tmpic_t *p;\n\tint lx = 0, ly = 0;\t// lower bounds of the window\n\n\tp = Draw_CachePic (CACHEPIC_P_LOAD);\n\tM_DrawPic ( (320 - p->width) >> 1, 4, p);\n\n\tfor (i = 0; i < MAX_SAVEGAMES; i++)\n\t{\n\t\tif (i == 0)\n\t\t\tM_Print_GetPoint (16, 32 + 8*i, &load_window.x, &load_window.y, m_filenames[i], load_cursor == 0);\n\t\telse \n\t\t\tM_Print_GetPoint (16, 32 + 8*i, &lx, &ly, m_filenames[i], load_cursor == i);\n\t}\n\n\tload_window.w = SAVEGAME_COMMENT_LENGTH*8; // presume 8 pixels for each letter\n\tload_window.h = ly - load_window.y + 8;\n\n\t// line cursor\n\tM_DrawCharacter (8, 32 + load_cursor * 8, FLASHINGARROW());\n}\n\nvoid M_Save_Draw (void) {\n\tint i;\n\tmpic_t *p;\n\tint lx = 0, ly = 0;\t// lower bounds of the window\n\n\tp = Draw_CachePic (CACHEPIC_P_SAVE);\n\tM_DrawPic ( (320 - p->width) >> 1, 4, p);\n\n\tfor (i = 0; i < MAX_SAVEGAMES; i++)\n\t{\n\t\tif (i == 0)\n\t\t\tM_Print_GetPoint (16, 32 + 8 * i, &save_window.x, &save_window.y, m_filenames[i], load_cursor == 0);\n\t\telse\n\t\t\tM_Print_GetPoint (16, 32 + 8 * i, &lx, &ly, m_filenames[i], load_cursor == i);\n\t}\n\n\tsave_window.w = SAVEGAME_COMMENT_LENGTH*8; // presume 8 pixels for each letter\n\tsave_window.h = ly - save_window.y + 8;\n\n\t// line cursor\n\tM_DrawCharacter (8, 32 + load_cursor * 8, FLASHINGARROW());\n}\n\nvoid M_Load_Key (int key) {\n\tswitch (key) {\n\t\tcase K_BACKSPACE:\n\t\t\tm_topmenu = m_none;    // intentional fallthrough\n\t\tcase K_MOUSE2:\n\t\tcase K_ESCAPE:\n\t\t\tM_LeaveMenu (m_singleplayer);\n\t\t\tbreak;\n\n\t\tcase '`':\n\t\tcase '~':\n\t\t\tkey_dest = key_console;\n\t\t\tm_state = m_none;\n\t\t\tbreak;\n\n\t\tcase K_ENTER:\n\t\tcase K_MOUSE1:\n\t\t\tS_LocalSound (\"misc/menu2.wav\");\n\t\t\tif (!loadable[load_cursor])\n\t\t\t\treturn;\n\t\t\tm_state = m_none;\n\t\t\tkey_dest = key_game;\n\n\t\t\t// issue the load command\n\t\t\tCbuf_AddText(va(\"load s%i\\n\", load_cursor));\n\t\t\treturn;\n\n\t\tcase K_UPARROW:\n\t\tcase K_MWHEELUP:\n\t\tcase K_LEFTARROW:\n\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\tload_cursor--;\n\t\t\tif (load_cursor < 0)\n\t\t\t\tload_cursor = MAX_SAVEGAMES - 1;\n\t\t\tbreak;\n\n\t\tcase K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n\t\tcase K_RIGHTARROW:\n\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\tload_cursor++;\n\t\t\tif (load_cursor >= MAX_SAVEGAMES)\n\t\t\t\tload_cursor = 0;\n\t\t\tbreak;\n\t}\n}\n\nvoid M_Save_Key (int key) {\n\tswitch (key) {\n\t\tcase K_BACKSPACE:\n\t\t\tm_topmenu = m_none;    // intentional fallthrough\n\t\tcase K_MOUSE2:\n\t\tcase K_ESCAPE:\n\t\t\tM_LeaveMenu (m_singleplayer);\n\t\t\tbreak;\n\n\t\tcase '`':\n\t\tcase '~':\n\t\t\tkey_dest = key_console;\n\t\t\tm_state = m_none;\n\t\t\tbreak;\n\n\t\tcase K_ENTER:\n\t\tcase K_MOUSE1:\n\t\t\tm_state = m_none;\n\t\t\tkey_dest = key_game;\n\t\t\tCbuf_AddText (va(\"save s%i\\n\", load_cursor));\n\t\t\treturn;\n\n\t\tcase K_UPARROW:\n\t\tcase K_MWHEELUP:\n\t\tcase K_LEFTARROW:\n\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\tload_cursor--;\n\t\t\tif (load_cursor < 0)\n\t\t\t\tload_cursor = MAX_SAVEGAMES-1;\n\t\t\tbreak;\n\n\t\tcase K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n\t\tcase K_RIGHTARROW:\n\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\tload_cursor++;\n\t\t\tif (load_cursor >= MAX_SAVEGAMES)\n\t\t\t\tload_cursor = 0;\n\t\t\tbreak;\n\t}\n}\n\nqbool M_Save_Mouse_Event(const mouse_state_t *ms)\n{\n\tM_Mouse_Select(&save_window, ms, MAX_SAVEGAMES, &load_cursor);\n\n    if (ms->button_up == 1) M_Save_Key(K_MOUSE1);\n    if (ms->button_up == 2) M_Save_Key(K_MOUSE2);\n\n\treturn true;\n}\n\nqbool M_Load_Mouse_Event(const mouse_state_t *ms)\n{\n\tM_Mouse_Select(&load_window, ms, MAX_SAVEGAMES, &load_cursor);\n\n    if (ms->button_up == 1) M_Load_Key(K_MOUSE1);\n    if (ms->button_up == 2) M_Load_Key(K_MOUSE2);\n\n    return true;\n}\n\n#endif\n\n\n// ================================\n// Multiplayer submenu\n// used only if mcharset is not available making demo player menu inaccesible from mainmenu\n\nint    m_multiplayer_cursor;\n#define    MULTIPLAYER_ITEMS    2\nmenu_window_t m_multiplayer_window;\n\nvoid M_MultiPlayerSub_Draw (void) {\n\tmpic_t    *p;\n\tint lx, ly;\n\n\tM_DrawTransPic (16, 4, Draw_CachePic (CACHEPIC_QPLAQUE) );\n\tp = Draw_CachePic (CACHEPIC_P_MULTI);\n\tM_DrawPic ( (320-p->width)/2, 4, p);\n\tM_Print_GetPoint (80, 40, &m_multiplayer_window.x, &m_multiplayer_window.y, \"Join Game\", m_multiplayer_cursor == 0);\n\tm_multiplayer_window.h = 8;\n\tM_Print_GetPoint (80, 48, &lx, &ly, \"Demos\", m_multiplayer_cursor == MULTIPLAYER_ITEMS - 1);\n\tm_multiplayer_window.h += 8;\n\tm_multiplayer_window.w = 20 * 8; // presume 20 letters long word and 8 pixels for a letter\n\t\n\t// cursor\n\tM_DrawCharacter (64, 40 + m_multiplayer_cursor * 8, FLASHINGARROW());\n}\n\nvoid M_MultiPlayerSub_Key (int key) {\n\tswitch (key) {\n\t\tcase K_BACKSPACE:\n\t\t\tm_topmenu = m_none;    // intentional fallthrough\n\t\tcase K_MOUSE2:\n\t\tcase K_ESCAPE:\n\t\t\tM_LeaveMenu (m_main);\n\t\t\tbreak;\n\n\t\tcase '`':\n\t\tcase '~':\n\t\t\tkey_dest = key_console;\n\t\t\tm_state = m_none;\n\t\t\tbreak;\n\n\t\tcase K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\tif (++m_multiplayer_cursor >= MULTIPLAYER_ITEMS)\n\t\t\t\tm_multiplayer_cursor = 0;\n\t\t\tbreak;\n\n\t\tcase K_UPARROW:\n\t\tcase K_MWHEELUP:\n\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\tif (--m_multiplayer_cursor < 0)\n\t\t\t\tm_multiplayer_cursor = MULTIPLAYER_ITEMS - 1;\n\t\t\tbreak;\n\n\t\tcase K_HOME:\n\t\tcase K_PGUP:\n\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\tm_multiplayer_cursor = 0;\n\t\t\tbreak;\n\n\t\tcase K_END:\n\t\tcase K_PGDN:\n\t\t\tS_LocalSound (\"misc/menu1.wav\");\n\t\t\tm_multiplayer_cursor = MULTIPLAYER_ITEMS - 1;\n\t\t\tbreak;\n\n\t\tcase K_ENTER:\n\t\tcase K_MOUSE1:\n\t\t\tm_entersound = true;\n\t\t\tswitch (m_multiplayer_cursor) {\n\t\t\t\tcase 0:\n\t\t\t\t\tM_EnterMenu(m_multiplayer);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 1:\n\t\t\t\t\tM_Menu_Demos_f ();\n\t\t\t\t\tbreak;\n\t\t\t}\n\t}\n}\n\nqbool M_MultiPlayerSub_Mouse_Event(const mouse_state_t *ms)\n{\n\tM_Mouse_Select(&m_multiplayer_window, ms, MULTIPLAYER_ITEMS, &m_multiplayer_cursor);\n\n    if (ms->button_up == 1) M_MultiPlayerSub_Key(K_MOUSE1);\n    if (ms->button_up == 2) M_MultiPlayerSub_Key(K_MOUSE2);\n    \n    return true;\n}\n\nvoid M_Menu_Browser_f (void) {\n\tM_EnterMenu(m_multiplayer);\n}\n\nvoid M_Menu_MultiPlayer_f (void)\n{\n\tif (Draw_BigFontAvailable()) {\n\t\tM_EnterMenu(m_multiplayer);\n\t}\n\telse {\n\t\t// demos entry is not accessible with old main menu\n\t\t// so we need to create a submenu in the multiplayer menu\n\t\tM_EnterMenu(m_multiplayer_submenu);\n\t}\n}\n\nvoid M_Menu_Demos_f (void)\n{\n\tM_EnterMenu(m_demos); // switches client state\n}\n\n//=============================================================================\n/* Menu Subsystem */\n\nvoid M_Init (void) {\n\textern cvar_t menu_marked_bgcolor;\n\textern cvar_t menu_marked_fade;\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_MENU);\n\tCvar_Register(&scr_centerMenu);\n\tCvar_Register(&menu_ingame);\n\tCvar_Register(&scr_scaleMenu);\n\tCvar_Register(&menu_marked_fade);\n\tCvar_Register(&menu_botmatch_gamedir);\n\tCvar_Register(&menu_botmatch_mod_old);\n\n\tCvar_Register(&menu_marked_bgcolor);\n\tBrowser_Init();\n\tCvar_ResetCurrentGroup();\n\tMenu_Help_Init();\t// help_files module\n\tMenu_Demo_Init();\t// menu_demo module\n\tMenu_Options_Init(); // menu_options module\n\tMenu_Ingame_Init();\n\tMenu_MultiPlayer_Init(); // menu_multiplayer.h\n\n\tCmd_AddCommand (\"togglemenu\", M_ToggleMenu_f);\n\tCmd_AddCommand (\"toggleproxymenu\", M_ToggleProxyMenu_f);\n\n\tCmd_AddCommand (\"menu_main\", M_Menu_Main_f);\n#ifndef CLIENTONLY\n\tCmd_AddCommand (\"menu_singleplayer\", M_Menu_SinglePlayer_f);\n\tCmd_AddCommand (\"menu_load\", M_Menu_Load_f);\n\tCmd_AddCommand (\"menu_save\", M_Menu_Save_f);\n#endif\n\tCmd_AddCommand (\"menu_multiplayer\", M_Menu_MultiPlayer_f);\n\tCmd_AddCommand (\"menu_slist\", M_Menu_Browser_f);\n\tCmd_AddCommand (\"menu_demos\", M_Menu_Demos_f);\n\tCmd_AddCommand (\"menu_options\", M_Menu_Options_f);\n\tCmd_AddCommand (\"help\", M_Menu_Help_f);\n\tCmd_AddCommand (\"menu_help\", M_Menu_Help_f);\n\tCmd_AddCommand (\"menu_quit\", M_Menu_Quit_f);\n}\n\nvoid M_Shutdown(void)\n{\n\tMenu_Help_Shutdown();\n\tMenu_Demo_Shutdown();\n\tMenu_Options_Shutdown();\n\tMenu_Ingame_Shutdown();\n\tMenu_MultiPlayer_Shutdown();\n}\n\nvoid M_Draw(void)\n{\n\tif (m_state == m_none || key_dest != key_menu || m_state == m_proxy) {\n\t\treturn;\n\t}\n\n\tscr_copyeverything = 1;\n\n\tif (SCR_NEED_CONSOLE_BACKGROUND) {\n\t\tDraw_ConsoleBackground(scr_con_current);\n\t}\n\telse {\n\t\t// if you don't like fade in ingame menu, uncomment this\n\t\t// if (m_state != m_ingame && m_state != m_democtrl)\n\t\tDraw_FadeScreen(scr_menualpha.value);\n\t}\n\n\tscr_fullupdate = 0;\n\n\tif (scr_scaleMenu.value) {\n\t\tif (vid.aspect > 1.0) {\n\t\t\tmenuheight = bound(240, (int)((vid.height / scr_scaleMenu.value) + 0.5f), 960);\n\t\t\tmenuwidth = (int)((menuheight * vid.aspect) + 0.5f);\n\t\t}\n\t\telse {\n\t\t\tmenuwidth = bound(320, (int)((vid.width / scr_scaleMenu.value) + 0.5f), 960);\n\t\t\tmenuheight = (int)((menuwidth / vid.aspect) + 0.5f);\n\t\t}\n\n\t\tR_OrthographicProjection(0, menuwidth, menuheight, 0, -99999, 99999);\n\t}\n\telse {\n\t\tmenuwidth = vid.width;\n\t\tmenuheight = vid.height;\n\t}\n\n\tif (scr_centerMenu.value) {\n\t\tm_yofs = (menuheight - 200) / 2;\n\t}\n\telse {\n\t\tm_yofs = 0;\n\t}\n\n\tswitch (m_state) {\n\t\tcase m_none: break;\n\t\tcase m_main:\t\t\tM_Main_Draw(); break;\n\t\tcase m_singleplayer:\tM_SinglePlayer_Draw(); break;\n#ifndef CLIENTONLY\n\t\tcase m_load:\t\t\tM_Load_Draw(); break;\n\t\tcase m_save:\t\t\tM_Save_Draw(); break;\n#else\n\t\t\t// keeps gcc happy\n\t\tcase m_load:\n\t\tcase m_save:\n\t\t\tbreak;\n#endif\n\t\tcase m_multiplayer:\t\tMenu_MultiPlayer_Draw(); break;\n\t\tcase m_multiplayer_submenu: M_MultiPlayerSub_Draw(); break;\n\t\tcase m_options:\t\t\tM_Options_Draw(); break;\n\t\tcase m_proxy:\t\t\tMenu_Proxy_Draw(); break;\n\t\tcase m_ingame:\t\t\tM_Ingame_Draw(); break;\n\t\tcase m_help:\t\t\tM_Help_Draw(); break;\n\t\tcase m_quit:\t\t\tM_Quit_Draw(); break;\n\t\tcase m_demos:\t\t\tMenu_Demo_Draw(); break;\n\t}\n\n\tif (scr_scaleMenu.value) {\n\t\tR_OrthographicProjection(0, vid.width, vid.height, 0, -99999, 99999);\n\t}\n\n\tif (m_entersound) {\n\t\tS_LocalSound(\"misc/menu2.wav\");\n\t\tm_entersound = false;\n\t}\n}\n\n// Return true if the system should execute the key, false if we want it to pass to M_Keydown\nqbool Menu_ExecuteKey (int key) {\n\t// This always turns into /togglemenu anyway\n\tif (key == K_ESCAPE) {\n\t\treturn true;\n\t}\n\n\t// Capture all keypresses when binding\n\tif (Menu_Options_IsBindingKey ()) {\n\t\treturn false;\n\t}\n\n\t// Capture F1 in menus that have help boxes\n\tif (key == K_F1 && m_state != m_ingame) {\n\t\treturn false;\n\t}\n\n\t// Other function keys should execute\n\tif (key >= K_F1 && key <= K_F12)\n\t\treturn true;\n\n\t// Capture everything else\n\treturn false;\n}\n\nvoid M_Keydown (int key, wchar unichar) {\n\tswitch (m_state) {\n\t\tcase m_none: return;\n\t\tcase m_main:\t\t\tM_Main_Key(key); return;\n\t\tcase m_singleplayer:\tM_SinglePlayer_Key(key); return;\n#ifndef CLIENTONLY\n\t\tcase m_load:\t\t\tM_Load_Key(key); return;\n\t\tcase m_save:\t\t\tM_Save_Key(key); return;\n#else\n\t\tcase m_load:\n\t\tcase m_save:\n\t\t\tbreak;\n#endif\n\t\tcase m_multiplayer:\t\tMenu_MultiPlayer_Key(key, unichar); return;\n\t\tcase m_multiplayer_submenu: M_MultiPlayerSub_Key(key); return;\n\t\tcase m_options: \t\tM_Options_Key(key, unichar); return;\n\t\tcase m_proxy:\t\t\tM_Proxy_Key(key); return;\n\t\tcase m_ingame:\t\t\tM_Ingame_Key(key); return;\n\t\tcase m_help:\t\t\tMenu_Help_Key(key, unichar); return;\n\t\tcase m_quit:\t\t\tM_Quit_Key(key); return;\n\t\tcase m_demos:\t\t\tMenu_Demo_Key(key, unichar); break;\n\t}\n}\n\nqbool Menu_Mouse_Event(const mouse_state_t* ms)\n{\n\tif (ms->button_down == K_MWHEELDOWN || ms->button_up == K_MWHEELDOWN ||\n\t\tms->button_down == K_MWHEELUP   || ms->button_up == K_MWHEELUP)\n\t{\n\t\t// menus do not handle this type of mouse wheel event, they accept it as a key event\t\n\t\treturn false;\n\t}\n\n\t// send the mouse state to appropriate modules here\n    // functions should report if they handled the event or not\n    switch (m_state) {\n\tcase m_main:\t\t\treturn M_Main_Mouse_Event(ms);\n#ifndef CLIENTONLY\n\tcase m_singleplayer:\treturn M_SinglePlayer_Mouse_Event(ms);\n#endif\n\tcase m_multiplayer:\t\treturn Menu_MultiPlayer_Mouse_Event(ms);\n\tcase m_multiplayer_submenu: return M_MultiPlayerSub_Mouse_Event(ms);\n#ifndef CLIENTONLY\n\tcase m_load:\t\t\treturn M_Load_Mouse_Event(ms);\n\tcase m_save:\t\t\treturn M_Save_Mouse_Event(ms);\n#endif\n\tcase m_options:\t\t\treturn Menu_Options_Mouse_Event(ms);\n\tcase m_demos:\t\t\treturn Menu_Demo_Mouse_Event(ms);\n\tcase m_ingame:\t\t\treturn Menu_Ingame_Mouse_Event(ms);\n\tcase m_help:\t\t\treturn Menu_Help_Mouse_Event(ms);\n\tcase m_none: default:\treturn false;\n\t}\n}\n"
  },
  {
    "path": "src/menu.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: menu.h,v 1.22 2007-10-17 17:08:26 dkure Exp $\n\n*/\n\n#ifndef __MENU_H__\n#define __MENU_H__\n\n#include \"keys.h\"\n\n#define OPTPADDING 4\n\n//\n// menus\n//\nvoid M_Init (void);\nvoid M_Keydown (int key, wchar unichar);\nvoid M_Draw (void);\nvoid M_ToggleMenu_f (void);\nvoid M_LeaveMenus (void);\nvoid M_LeaveMenu (int parent);\nvoid M_EnterMenu (int state);\nvoid M_Menu_Main_f (void);\nvoid M_EnterProxyMenu (void);\nvoid M_DrawTextBox (int x, int y, int width, int lines);\nvoid M_Menu_Quit_f (void);\nvoid M_Demos_Playlist_stop_f (void);\nvoid M_Menu_ServerList_f (void);\nvoid M_DrawTransPic (int x, int y, mpic_t *pic);\nvoid M_DrawPic (int x, int y, mpic_t *pic);\nvoid M_PrintWhite (int cx, int cy, char *str);\nvoid M_Print (int cx, int cy, char *str);\nvoid M_DrawCharacter (int cx, int line, int num);\nvoid M_DrawSlider (int x, int y, float range);\nvoid M_FindKeysForCommand (const char *command, int *twokeys);\nvoid M_BuildTranslationTable(int top, int bottom);\nvoid M_Unscale_Menu(void);\nqbool Menu_Mouse_Event(const mouse_state_t* ms);\nqbool Menu_ExecuteKey(int key);\n\nextern int m_yofs;\n\n#define FLASHINGARROW() (12+((int)(curtime*4)&1))\n#define FLASHINGCURSOR() (10+((int)(curtime*4)&1))\n\ntypedef enum {\n    m_none, m_main, m_proxy, m_singleplayer, m_load, m_save,\n\tm_multiplayer, m_demos, m_multiplayer_submenu,\n    m_options,\n\tm_help,\n\tm_quit, m_ingame, \n} m_state_t;\n\nextern m_state_t m_state;\n\nvoid M_Shutdown(void);\n\n#endif // __MENU_H_\n"
  },
  {
    "path": "src/menu_demo.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/*\n\tDemo Menu module\n\n\tStands between menu.c and Ctrl_Tab.c\n\n\tNaming convention:\n\tfunction mask | usage    | purpose\n\t--------------|----------|-----------------------------\n\tMenu_Demo_*   | external | interface for menu.c\n\tCT_Demo_*     | external | interface for Ctrl_Tab.c\n\tCL_Demo_*_f   | external | interface for cl_demo.c\n\tM_Demo_*_f    | external | commands issued by the user\n\tDemo_         | internal | internal (static) functions\n\n\tmade by:\n\t\tjohnnycz, Dec 2006\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"settings.h\"\n#include \"settings_page.h\"\n#include \"EX_FileList.h\"\n#include \"Ctrl.h\"\n#include \"Ctrl_Tab.h\"\n#include \"menu.h\"\n#include \"keys.h\"\n\n#include \"hash.h\"\n#include \"fs.h\"\n#include \"vfs.h\"\n\n#ifdef _WIN32\n#define DEMO_TIME                 FILETIME\n#else\n#define DEMO_TIME                 time_t\n#endif\n\n//unused\n//#define MAX_DEMO_NAME           MAX_OSPATH\n//#define MAX_DEMO_FILES          2048\n#define DEMO_MAXLINES             17\n\n#define DEMO_PLAYLIST_NAME_MAX    16\n#define DEMO_PLAYLIST_OPTIONS_MAX 5\n#define\tDEMO_PLAYLIST_MAX         256\n#define DEMO_PLAYLIST_TAB_MAIN    0\n\n#define DEMO_OPTIONS_MAX          2\n\n#define DEMO_TAB_MAIN             0\n#define DEMO_TAB_PLAYLIST         1\n#define DEMO_TAB_OPTIONS          2\n#define DEMO_TAB_MAX              2\n\nextern int mvd_demo_track_run;\n\ntypedef enum\n{\n    DEMOPG_BROWSER,\t\t// Browse page\n\tDEMOPG_PLAYLIST,\t// Playlist page\n\tDEMOPG_ENTRY,\t\t// Entry page\n\tDEMOPG_OPTIONS\t\t// Options page\n}\tdemo_tab_t;\n\ntypedef struct demo_playlist_s\n{\n\tchar name[MAX_PATH];\n\tchar path[MAX_PATH];\n\tchar trackname[DEMO_PLAYLIST_NAME_MAX];\n} demo_playlist_t;\n\nextern cvar_t     scr_scaleMenu;\nextern int        menuwidth;\nextern int        menuheight;\n\n// Demo browser container\nfilelist_t demo_filelist;\n\n// Demo menu container\nCTab_t demo_tab;\n\n// Playlist structures\ndemo_playlist_t demo_playlist[DEMO_PLAYLIST_MAX]; // TODO: A play list probably shouldn't be tied to a GUI, put this in cl_demo.c instead and allow adding demos from the console also.\n\nchar track_name[DEMO_PLAYLIST_NAME_MAX];\nchar default_track[DEMO_PLAYLIST_NAME_MAX];\nqbool demo_playlist_started = false;\n\ncvar_t    demo_playlist_loop = {\"demo_playlist_loop\",\"0\"};\ncvar_t    demo_playlist_track_name = {\"demo_playlist_track_name\",\"\"};\n\n// demo playlist options\nsettings_page demoplsett;\nsetting demoplsett_arr[] = {\n\tADDSET_SEPARATOR(\"Playlist options\"),\n\tADDSET_BOOL(\"looping\", demo_playlist_loop),\n\tADDSET_STRING(\"default track\", demo_playlist_track_name)\n};\n\nstatic int demo_playlist_base = 0;\nstatic int demo_playlist_current_played = 0;\nstatic int demo_playlist_cursor = 0;\nstatic int demo_playlist_num = 0;\nstatic int demo_playlist_opt_cursor = 0;\nstatic int demo_playlist_started_test = 0;\n\nchar demo_track[DEMO_PLAYLIST_NAME_MAX];\n\n// Demo Playlist Functions\nstatic void M_Demo_Playlist_stop_f (void)\n{\n\tif (!demo_playlist_started_test)\n\t{\n\t\tdemo_playlist_started = false;\n\t\tdemo_playlist_started_test = 0;\n\t}\n}\n\nstatic void Demo_Playlist_Start (int i)\n{\n\tkey_dest = key_game;\n\tm_state = m_none;\n\tdemo_playlist_current_played = i;\n\tdemo_playlist_started_test = 0 ;\n\n\tif (cls.demoplayback)\n\t{\n\t\tCL_Disconnect_f();\n\t}\n\n\tdemo_playlist_started_test = 0 ;\n\tdemo_playlist_started = true;\n\tstrlcpy(track_name, demo_playlist[demo_playlist_current_played].trackname, sizeof(track_name));\n\tCbuf_AddText (va(\"playdemo \\\"%s\\\"\\n\", demo_playlist[demo_playlist_current_played].path));\n}\n\nvoid CL_Demo_Playlist_f (void)\n{\n\tdemo_playlist_current_played++;\n\n\tstrlcpy (track_name, demo_playlist[demo_playlist_current_played].trackname, sizeof(track_name));\n\n\tif (demo_playlist_current_played == demo_playlist_num  && demo_playlist_loop.value )\n\t{\n\t\tdemo_playlist_current_played = 0;\n\t\tCbuf_AddText (va(\"playdemo \\\"%s\\\"\\n\", demo_playlist[demo_playlist_current_played].path));\n\t}\n\telse if (demo_playlist_current_played == demo_playlist_num )\n\t{\n\t\tCom_Printf(\"End of demo playlist.\\n\");\n\t\tdemo_playlist_started = false;\n\t\tdemo_playlist_current_played = 0;\n\t}\n\telse\n\t{\n\t\tCbuf_AddText (va(\"playdemo \\\"%s\\\"\\n\", demo_playlist[demo_playlist_current_played].path));\n\t}\n}\n\nvoid M_Demo_Playlist_Next_f (void)\n{\n\tint tmp;\n\n\tif (!demo_playlist_started)\n\t{\n\t\treturn;\n\t}\n\n\ttmp = demo_playlist_current_played + 1 ;\n\n\tif (tmp > demo_playlist_num - 1)\n\t{\n\t\ttmp = 0 ;\n\t}\n\n\tDemo_Playlist_Start(tmp);\n}\n\nvoid M_Demo_Playlist_Prev_f (void)\n{\n\tint tmp;\n\tif (!demo_playlist_started)\n\t{\n\t\treturn;\n\t}\n\n\ttmp = demo_playlist_current_played - 1 ;\n\n\tif (tmp < 0)\n\t{\n\t\ttmp = demo_playlist_num - 1 ;\n\t}\n\n\tCom_Printf(\"Prev %i\\n\", tmp);\n\tDemo_Playlist_Start(tmp);\n}\n\nvoid M_Demo_Playlist_Clear_f (void)\n{\n\tif (demo_playlist_num == 0)\n\t{\n\t\treturn;\n\t}\n\n\tmemset (&demo_playlist, 0, sizeof(demo_playlist_t) * demo_playlist_num);\n\n\tdemo_playlist_num = 0;\n\tdemo_playlist_started = false;\n}\n\nvoid M_Demo_Playlist_Stop_f (void)\n{\n\tM_Demo_Playlist_stop_f();\n\tCbuf_AddText(\"disconnect\\n\");\n}\n\nstatic void Demo_Playlist_Setup_f (void)\n{\n\tstrlcpy (demo_track, demo_playlist[demo_playlist_cursor + demo_playlist_base].trackname, sizeof(demo_track));\n\tstrlcpy (default_track, demo_playlist_track_name.string, min(16, sizeof(default_track)));\n}\n\n//\n// Delete the current entry from the playlist.\n//\nstatic void Demo_Playlist_Del(int i)\n{\n\tint y;\n\n\tif (i >= DEMO_PLAYLIST_MAX) {\n\t\tCom_Printf(\"Error: demo playlist item %d out of range (%d)\\n\", i, DEMO_PLAYLIST_MAX);\n\t\treturn;\n\t}\n\n\t// Remove the playlist item.\n\tmemset (&demo_playlist[i], 0, sizeof(demo_playlist[i]));\n\n\tfor (y = i; y < (DEMO_PLAYLIST_MAX - 1) && demo_playlist[y+1].name[0] != '\\0'; y++ )\n\t{\n\t\tmemmove (&demo_playlist[y], &demo_playlist[y+1], sizeof(demo_playlist[y]));\n\t\tmemset (&demo_playlist[y+1], 0, sizeof(demo_playlist[y+1]));\n\t}\n\n\tdemo_playlist_num--;\n\tdemo_playlist_num = max(0, demo_playlist_num);\n\n\tdemo_playlist_cursor = bound(0, demo_playlist_cursor, demo_playlist_num - 1);\n}\n\n//\n// Move the selected entry up a step in the playlist.\n//\nstatic void Demo_Playlist_Move_Up (int i)\n{\n\tdemo_playlist_t tmp;\n\n\tif (i == 0)\n\t{\n\t\treturn;\n\t}\n\n\tmemcpy (&tmp, &demo_playlist[i-1], sizeof(tmp));\n\tmemcpy (&demo_playlist[i-1], &demo_playlist[i], sizeof(demo_playlist[i-1]));\n\tmemcpy (&demo_playlist[i], &tmp, sizeof(demo_playlist[i]));\n}\n\n//\n// Move the selected entry down a step in the playlist.\n//\nstatic void Demo_Playlist_Move_Down (int i)\n{\n\tdemo_playlist_t tmp;\n\n\tif(i + 1 == demo_playlist_num)\n\t{\n\t\treturn;\n\t}\n\n\tmemcpy (&tmp, &demo_playlist[i+1], sizeof(tmp));\n\tmemcpy (&demo_playlist[i+1], &demo_playlist[i], sizeof(demo_playlist[i+1]));\n\tmemcpy (&demo_playlist[i], &tmp, sizeof(demo_playlist[i]));\n}\n\n//\n// Select the previous entry in the playlist.\n//\nstatic void Demo_Playlist_SelectPrev(void)\n{\n\tdemo_playlist_cursor -= DEMO_MAXLINES - 1;\n\tif (demo_playlist_cursor < 0)\n\t{\n\t\tdemo_playlist_base += demo_playlist_cursor;\n\t\tdemo_playlist_base = max (0, demo_playlist_base);\n\t\tdemo_playlist_cursor = 0;\n\t}\n\n\tDemo_Playlist_Setup_f();\n}\n\nstatic void Demo_Playlist_SelectNext(void)\n{\n\tdemo_playlist_cursor += DEMO_MAXLINES - 1;\n\n\tif (demo_playlist_base + demo_playlist_cursor >= demo_playlist_num)\n\t{\n\t\tdemo_playlist_cursor = demo_playlist_num - demo_playlist_base - 1;\n\t}\n\n\tif (demo_playlist_cursor >= DEMO_MAXLINES)\n\t{\n\t\tdemo_playlist_base += demo_playlist_cursor - (DEMO_MAXLINES - 1);\n\t\tdemo_playlist_cursor = DEMO_MAXLINES - 1;\n\n\t\tif (demo_playlist_base + demo_playlist_cursor >= demo_playlist_num)\n\t\t{\n\t\t\tdemo_playlist_base = demo_playlist_num - demo_playlist_cursor - 1;\n\t\t}\n\t}\n\n\tDemo_Playlist_Setup_f();\n}\n\n/*\nstatic void Demo_FormatSize (char *t) {\n\tchar *s;\n\n\tfor (s = t; *s; s++) {\n\t\tif (*s >= '0' && *s <= '9')\n\t\t\t*s = *s - '0' + 18;\n\t\telse\n\t\t\t*s |= 128;\n\t}\n}\n*/\n\n// ============\n// <draw pages>\n\nvoid CT_Demo_Browser_Draw(int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n    FL_Draw(&demo_filelist, x, y, w, h);\n}\n\n\nvoid CT_Demo_Playlist_Draw(int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n\tint i, y2;\n\n\tif(demo_playlist_num == 0)\n\t{\n\t\tUI_Print_Center(x, y + 16, w, \"Playlist is empty\", false);\n\t\tUI_Print_Center(x, y + 32, w, \"Use [Insert] or [Ctrl]+[Enter]\", true);\n\t\tUI_Print_Center(x, y + 40, w, \"in the demo browser to add a demo\", true);\n\t\t// UI_Print_Center(x, y + 40, w, \"to add demo to the playlist\", true);\n\t}\n\telse\n\t{\n\t\ty = y - 48;\n\t\tfor (i = 0; i <= demo_playlist_num - demo_playlist_base && i < DEMO_MAXLINES; i++)\n\t\t{\n\t\t\ty2 = 32 + i * 8 ;\n\n\t\t\tif (demo_playlist_cursor == i)\n\t\t\t\tM_Print (24, y + y2, demo_playlist[demo_playlist_base + i].name);\n\t\t\telse\n\t\t\t\tM_PrintWhite (24, y + y2, demo_playlist[demo_playlist_base + i].name);\n\t\t}\n\n\t\tM_DrawCharacter (8, y + 32 + demo_playlist_cursor * 8, FLASHINGARROW());\n\t}\n}\n\nvoid CT_Demo_Entry_Draw(int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n\tint z;\n\ty = y - 32;\n\n\tif (demo_playlist_started && cls.demoplayback)\n\t{\n\t\tM_Print (24, y + 32, \"Currently playing:\");\n\t\tM_PrintWhite (24, y + 40, demo_playlist[demo_playlist_current_played].name);\n\t}\n\telse\n\t{\n\t\tM_Print (24, y + 32, \"Not playing anything\");\n\t}\n\n\tM_Print (24, y + 56, \"Next     demo\");\n\tM_Print (24, y + 64, \"Previous demo\");\n\tM_Print (24, y + 72, \"Stop  playlist\");\n\tM_Print (24, y + 80, \"Clear playlist\");\n\n\tif (demo_playlist_num > 0)\n\t{\n\t\tM_Print (24, y + 96, \"Currently selected:\");\n\t\tM_Print (24, y + 104, demo_playlist[demo_playlist_cursor].name);\n\t}\n\telse\n\t{\n\t\tM_Print (24, y + 96, \"No demo in playlist\");\n\t}\n\n\tif (strcasecmp(demo_playlist[demo_playlist_cursor].name + strlen(demo_playlist[demo_playlist_cursor].name) - 4, \".mvd\"))\n\t{\n\t\tM_Print (24, y + 120, \"Tracking only available with mvds\");\n\t}\n\telse\n\t{\n\t\tM_Print (24, y + 120, \"Track\");\n\t\tM_DrawTextBox (160, y + 112, 16, 1);\n\t\tM_PrintWhite (168, y + 120, demo_track);\n\t\tif (demo_playlist_opt_cursor == 4 && demo_playlist_num > 0)\n\t\t\tM_DrawCharacter (168 + 8*strlen(demo_track), 120 + y, FLASHINGCURSOR());\n\t}\n\n\tz = demo_playlist_opt_cursor + (demo_playlist_opt_cursor >= 4 ? 4 : 0);\n\tz = y + 56 + z * 8;\n\tM_DrawCharacter (8, z, FLASHINGARROW());\n}\n\nvoid CT_Demo_Options_Draw(int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n\tSettings_Draw(x, y, w, h, &demoplsett); \n}\n\n#define DEMOPAGEPADDING 4\n\n// in the end leads calls one of the four functions above\nvoid Menu_Demo_Draw (void)\n{\n\textern void Demo_Draw(int, int, int, int);\n\n\tint x, y, w, h;\n\n\tM_Unscale_Menu();\n\n    // don't add padding on the right side so the scrolling is friendly\n\tw = vid.width - DEMOPAGEPADDING; // here used to be a limit to 512x... size, we've considered it useless\n\th = vid.height - DEMOPAGEPADDING*2;\n\tx = DEMOPAGEPADDING;\n\ty = DEMOPAGEPADDING;\n\n\tCTab_Draw(&demo_tab, x, y, w, h);\n}\n// </draw pages>\n// =============\n\nstatic void Demo_AddDemoToPlaylist(const char* display_name, const char* path)\n{\n\tif (demo_playlist_num >= DEMO_PLAYLIST_MAX) {\n\t\tCom_Printf(\"Playlist is full, cannot add \\\"%s\\\" to it. Max allowed demos in playlist is %d\\n\", display_name, DEMO_PLAYLIST_MAX);\n\t\treturn;\n\t}\n\n\tsnprintf(demo_playlist[demo_playlist_num].name, sizeof((*demo_playlist).name), \"%s\", display_name);\n\tsnprintf(demo_playlist[demo_playlist_num].path, sizeof((*demo_playlist).path), \"%s\", path);\n\tdemo_playlist_num++;\n}\n\nvoid Demo_AddDirToPlaylist (char *dir_path)\n{\n\textern void FL_ReadDir(filelist_t *fl);\n\tint i;\n\tfilelist_t dir_filelist;\n\n\tif (!dir_path) {\n\t\treturn;\n\t}\n\n\t// Bit of a hack, but we need values for cvars and what not for the FL_ functions to work\n\t// so borrow them from demo_filelist.\n\tmemcpy (&dir_filelist, &demo_filelist, sizeof(dir_filelist));\n\n\tFL_SetCurrentDir (&dir_filelist, dir_path);\n\tFL_ReadDir (&dir_filelist);\n\n\t// Find the demos and add them to the playlist.\n\tfor (i = 0; i < dir_filelist.num_entries; i++)\n\t{\n\t\tfiledesc_t *f = &dir_filelist.entries[i];\n\n\t\t// Don't bother with zips and directories.\n\t\tif (f->is_directory\n#ifdef WITH_ZIP\n\t\t\t|| f->is_archive\n#endif\n\t\t\t)\n\t\t{\n\t\t\t// TODO: Make this recursive for dirs?\n\t\t\tcontinue;\n\t\t}\n\n\t\tDemo_AddDemoToPlaylist (f->display, f->name);\n\t}\n}\n\n#ifdef WITH_ZIP\nvoid Demo_AddZipToPlaylist(const char *zip_path)\n{\n\tunz_global_info global_info;\n\tunzFile         zip_file;\n\tchar            filename[MAX_PATH_LENGTH];\n\tunz_file_info   file_info;\n\tchar            full_queued_path[MAX_PATH_LENGTH];\n\n\t// Unpack the files to a temp path.\n\tzip_file = FS_ZipUnpackOpenFile(zip_path);\n\n\t// Get the number of files in the zip archive.\n\tif (unzGetGlobalInfo(zip_file, &global_info) == UNZ_OK) {\n\t\tint i;\n\n\t\tfor (i = 0; i < global_info.number_entry; ++i) {\n\t\t\tif (i && unzGoToNextFile(zip_file) != UNZ_OK) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (unzGetCurrentFileInfo(zip_file, &file_info, filename, sizeof(filename), NULL, 0, NULL, 0) != UNZ_OK) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (!CL_DemoExtensionMatch(filename)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tstrlcpy(full_queued_path, zip_path, sizeof(full_queued_path));\n\t\t\tstrlcat(full_queued_path, \"/\", sizeof(full_queued_path));\n\t\t\tstrlcat(full_queued_path, filename, sizeof(full_queued_path));\n\t\t\tDemo_AddDemoToPlaylist(COM_SkipPath(filename), filename);\n\t\t}\n\t}\n}\n#endif // WITH_ZIP\n\n// ==============================\n// <key processing for each page>\n\nint CT_Demo_Browser_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n\textern void M_ToggleMenu_f (void);\n\textern void M_LeaveMenu (int);\n    qbool processed = false;\n\n\t// Special case for adding zips/dirs to playlist.\n\tif (key == K_INS || (key == K_ENTER && keydown[K_CTRL]))\n\t{\n\t\t#ifdef WITH_ZIP\n\t\tif (FS_IsArchive (FL_GetCurrentPath(&demo_filelist)))\n\t\t{\n\t\t\t// Zip.\n\t\t\tDemo_AddZipToPlaylist (FL_GetCurrentPath(&demo_filelist));\n\t\t\treturn true;\n\t\t}\n\t\telse\n\t\t#endif // WITH_ZIP\n\t\t{\n\t\t\tif (FL_IsCurrentDir (&demo_filelist))\n\t\t\t{\n\t\t\t\t// Dir.\n\t\t\t\tDemo_AddDirToPlaylist (FL_GetCurrentPath(&demo_filelist));\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\t// See if the main filebrowser functions wants to\n\t// do something first, like enter a dir/zip.\n    processed = FL_Key(&demo_filelist, key);\n\n    if (!processed)\n    {\n\t\tif (key == K_INS)\n\t\t{\n\t\t\t// Add the selected demo to the playlist.\n\t\t\tDemo_AddDemoToPlaylist (FL_GetCurrentDisplay (&demo_filelist), FL_GetCurrentPath (&demo_filelist));\n\t\t}\n        else if (key == K_ENTER || key == K_MOUSE1)\n        {\n\t\t\tif (keydown[K_CTRL])\n\t\t\t{\n\t\t\t\t// Add the selected demo to the playlist.\n\t\t\t\tDemo_AddDemoToPlaylist (FL_GetCurrentDisplay (&demo_filelist), FL_GetCurrentPath (&demo_filelist));\n\t\t\t}\n\t\t\telse if (keydown[K_SHIFT])\n\t\t\t{\n\t\t\t\tM_LeaveMenus();\n\t\t\t\tCbuf_AddText (va(\"timedemo \\\"%s\\\"\\n\", FL_GetCurrentPath(&demo_filelist)));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tM_LeaveMenus();\n\t\t\t\tCbuf_AddText(va(\"playdemo \\\"%s\\\"\\n\", FL_GetCurrentPath(&demo_filelist)));\n\t\t\t\tprocessed = true;\n\t\t\t}\n        }\n    }\n\n    return processed;\n}\n\nint CT_Demo_Playlist_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n\tswitch (key)\n\t{\n\t\tcase K_UPARROW:\n\t\tcase K_MWHEELUP:\n\t\t{\n\t\t\tif (keydown[K_CTRL] && demo_playlist_cursor + demo_playlist_base > 0)\n\t\t\t\tDemo_Playlist_Move_Up(demo_playlist_cursor + demo_playlist_base);\n\n\t\t\tif (demo_playlist_cursor > 0)\n\t\t\t{\n\t\t\t\tdemo_playlist_cursor--;\n\t\t\t}\n\t\t\telse if (demo_playlist_base > 0)\n\t\t\t{\n\t\t\t\tdemo_playlist_base--;\n\t\t\t}\n\n\t\t\tDemo_Playlist_Setup_f();\n\t\t\tbreak;\n\t\t}\n\t\tcase K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n\t\t{\n\t\t\tif (keydown[K_CTRL] && demo_playlist_cursor + demo_playlist_base < demo_playlist_num)\n\t\t\t\tDemo_Playlist_Move_Down(demo_playlist_cursor + demo_playlist_base);\n\n\t\t\tif (demo_playlist_cursor + demo_playlist_base < demo_playlist_num - 1)\n\t\t\t{\n\t\t\t\tif (demo_playlist_cursor < DEMO_MAXLINES - 1)\n\t\t\t\t\tdemo_playlist_cursor++;\n\t\t\t\telse\n\t\t\t\t\tdemo_playlist_base++;\n\t\t\t}\n\t\t\tDemo_Playlist_Setup_f();\n\t\t\tbreak;\n\t\t}\n\t\tcase K_HOME:\n\t\t{\n\t\t\tdemo_playlist_cursor = 0;\n\t\t\tdemo_playlist_base = 0;\n\t\t\tDemo_Playlist_Setup_f();\n\t\t\tbreak;\n\t\t}\n\t\tcase K_END:\n\t\t{\n\t\t\tif (demo_playlist_num > DEMO_PLAYLIST_OPTIONS_MAX)\n\t\t\t{\n\t\t\t\tdemo_playlist_cursor = DEMO_PLAYLIST_OPTIONS_MAX - 1;\n\t\t\t\tdemo_playlist_base = demo_playlist_num - demo_playlist_cursor - 1;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tdemo_playlist_base = 0;\n\t\t\t\tdemo_playlist_cursor = demo_playlist_num - 1;\n\t\t\t}\n\t\t\tDemo_Playlist_Setup_f();\n\t\t\tbreak;\n\t\t}\n\t\tcase K_PGUP:\n\t\t{\n\t\t\tDemo_Playlist_SelectPrev();\n\t\t\tbreak;\n\t\t}\n\t\tcase K_PGDN:\n\t\t{\n\t\t\tDemo_Playlist_SelectNext();\n\t\t\tbreak;\n\t\t}\n\t\tcase K_ENTER:\n\t\t{\n\t\t\tDemo_Playlist_Start(demo_playlist_cursor + demo_playlist_base);\n\t\t\tbreak;\n\t\t}\n\t\tcase K_DEL:\n\t\t{\n\t\t\tDemo_Playlist_Del(demo_playlist_cursor + demo_playlist_base);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nint CT_Demo_Entry_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n\tint l;\n\n\tswitch (key)\n\t{\n\t\tcase K_LEFTARROW: return false;\n\t\tcase K_RIGHTARROW: return false;\n\t\tcase K_UPARROW:\n\t\tcase K_MWHEELUP:\n\t\t{\n\t\t\tdemo_playlist_opt_cursor = demo_playlist_opt_cursor ? demo_playlist_opt_cursor - 1 : DEMO_PLAYLIST_OPTIONS_MAX - 1;\n\t\t\tbreak;\n\t\t}\n\t\tcase K_DOWNARROW:\n\t\tcase K_MWHEELDOWN:\n\t\t{\n\t\t\tdemo_playlist_opt_cursor++;\n\t\t\tdemo_playlist_opt_cursor = demo_playlist_opt_cursor % DEMO_PLAYLIST_OPTIONS_MAX;\n\t\t\tbreak;\n\t\t}\n\t\tcase K_PGUP:\n\t\t{\n\t\t\tDemo_Playlist_SelectPrev();\n\t\t\tbreak;\n\t\t}\n\t\tcase K_PGDN:\n\t\t{\n\t\t\tDemo_Playlist_SelectNext();\n\t\t\tbreak;\n\t\t}\n\t\tcase K_ENTER:\n\t\t{\n\t\t\tif (demo_playlist_opt_cursor == 0)\n\t\t\t\tM_Demo_Playlist_Next_f();\n\t\t\telse if (demo_playlist_opt_cursor == 1)\n\t\t\t\tM_Demo_Playlist_Prev_f();\n\t\t\telse if (demo_playlist_opt_cursor == 2)\n\t\t\t\tM_Demo_Playlist_Stop_f();\n\t\t\telse if (demo_playlist_opt_cursor == 3)\n\t\t\t\tM_Demo_Playlist_Clear_f();\n\t\t\tbreak;\n\t\t}\n\t\tcase K_BACKSPACE:\n\t\t{\n\t\t\tif (demo_playlist_opt_cursor == 4)\n\t\t\t{\n\t\t\t\tif (strlen(demo_track))\n\t\t\t\t\tdemo_track[strlen(demo_track)-1] = 0;\n\t\t\t\tstrlcpy(demo_playlist[demo_playlist_cursor + demo_playlist_base].trackname,demo_track,sizeof(demo_playlist->trackname));\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t{\n\t\t\tif (key < 32 || key > 127)\n\t\t\t\treturn false;\n\n\t\t\tif (demo_playlist_opt_cursor == 4)\n\t\t\t{\n\t\t\t\tl = strlen(demo_track);\n\t\t\t\tif (l < 15)\n\t\t\t\t{\n\t\t\t\t\tdemo_track[l+1] = 0;\n\t\t\t\t\tdemo_track[l] = key;\n\t\t\t\t\tstrlcpy(demo_playlist[demo_playlist_cursor + demo_playlist_base].trackname,demo_track,sizeof(demo_playlist->trackname));\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\nint CT_Demo_Options_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n\treturn Settings_Key(&demoplsett, key, unichar);\n}\n\nqbool CT_Demo_Browser_Mouse_Event(const mouse_state_t *ms)\n{\n    if (FL_Mouse_Event(&demo_filelist, ms)) {\n        return true;\n    } else if (ms->button_up >= 1 && ms->button_up <= 2) {\n        CT_Demo_Browser_Key(K_MOUSE1 - 1 + ms->button_up, 0, &demo_tab, demo_tab.pages + DEMOPG_BROWSER);\n        return true;\n    }\n\n    // this specially \"eats\" button_up event, there is no reason to process other events anyway\n\treturn true;\n}\n\nqbool CT_Demo_Options_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn Settings_Mouse_Event(&demoplsett, ms);\n}\n\n// will lead to call of one of the 4 functions above\nvoid Menu_Demo_Key(int key, wchar unichar)\n{\n\textern void M_Menu_Main_f (void);\n\n    int handled = CTab_Key(&demo_tab, key, unichar);\n\n    if (!handled)\n    {\n\t\tif (key == K_ESCAPE || key == K_MOUSE2)\n        {\n            M_Menu_Main_f();\n        }\n    }\n}\n// </key processing for each page>\n\nqbool Menu_Demo_Mouse_Event(const mouse_state_t *ms)\n{\n\tmouse_state_t nms = *ms;\n\n    if (ms->button_up == 2) {\n        Menu_Demo_Key(K_MOUSE2, 0);\n        return true;\n    }\n\n\tnms.x -= DEMOPAGEPADDING;\n\tnms.y -= DEMOPAGEPADDING;\n\tnms.x_old -= DEMOPAGEPADDING;\n\tnms.y_old -= DEMOPAGEPADDING;\n\treturn CTab_Mouse_Event(&demo_tab, &nms);\n}\n\nCTabPage_Handlers_t demo_browser_handlers = {\n\tCT_Demo_Browser_Draw,\n\tCT_Demo_Browser_Key,\n\tNULL,\n\tCT_Demo_Browser_Mouse_Event\n};\n\nCTabPage_Handlers_t demo_playlist_handlers = {\n\tCT_Demo_Playlist_Draw,\n\tCT_Demo_Playlist_Key\n};\n\nCTabPage_Handlers_t demo_entry_handlers = {\n\tCT_Demo_Entry_Draw,\n\tCT_Demo_Entry_Key\n};\n\nCTabPage_Handlers_t demo_options_handlers = {   \n\tCT_Demo_Options_Draw,\n\tCT_Demo_Options_Key,\n\tNULL,\n\tCT_Demo_Options_Mouse_Event\n};\n\n// set new initial dir\nvoid Menu_Demo_NewHome(const char *homedir)\n{\n\tFL_SetCurrentDir(&demo_filelist, homedir);\n}\n\nvoid Menu_Demo_Init(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\tCvar_Register (&demo_playlist_loop);\n\tCvar_Register (&demo_playlist_track_name);\n\tCvar_ResetCurrentGroup();\n\n\tCmd_AddCommand (\"demo_playlist_stop\", M_Demo_Playlist_Stop_f);\n\tCmd_AddCommand (\"demo_playlist_next\", M_Demo_Playlist_Next_f);\n\tCmd_AddCommand (\"demo_playlist_prev\", M_Demo_Playlist_Prev_f);\n\tCmd_AddCommand (\"demo_playlist_clear\", M_Demo_Playlist_Clear_f);\n\n\tFL_Init(&demo_filelist, \"./qw\");\n    FL_AddFileType(&demo_filelist, 0, \".qwd\");\n\tFL_AddFileType(&demo_filelist, 1, \".qwz\");\n\tFL_AddFileType(&demo_filelist, 2, \".mvd\");\n\tFL_AddFileType(&demo_filelist, 3, \".dem\");\n\t#ifdef WITH_ZLIB\n\tFL_AddFileType(&demo_filelist, 4, \".gz\");\n\t#endif // WITH_ZLIB\n\t#ifdef WITH_ZIP\n\tFL_AddFileType(&demo_filelist, 4, \".zip\");\n\tFL_AddFileType(&demo_filelist, 4, \".pk3\");\n\t#endif // WITH_ZIP\n\n\tSettings_Page_Init(demoplsett, demoplsett_arr);\n\n\t// initialize tab control\n    CTab_Init(&demo_tab);\n\tCTab_AddPage(&demo_tab, \"Browser\", DEMOPG_BROWSER, &demo_browser_handlers);\n\tCTab_AddPage(&demo_tab, \"Playlist\", DEMOPG_PLAYLIST, &demo_playlist_handlers);\n\tCTab_AddPage(&demo_tab, \"Entry\", DEMOPG_ENTRY, &demo_entry_handlers);\n\tCTab_AddPage(&demo_tab, \"Options\", DEMOPG_OPTIONS, &demo_options_handlers);\n\tCTab_SetCurrentId(&demo_tab, DEMOPG_BROWSER);\n}\n\nvoid Menu_Demo_Shutdown(void)\n{\n\tFL_Shutdown(&demo_filelist);\n\tSettings_Shutdown(&demoplsett);\n}\n\nvoid CL_Demo_NextInPlaylist(void)\n{\n\tif (demo_playlist_started) {\n\t\tCL_Demo_Playlist_f();\n\t\tmvd_demo_track_run = 0;\n\t}\n}\n\nvoid CL_Demo_Disconnected(void)\n{\n\tdemo_playlist_started = false;\n\tmvd_demo_track_run = 0;\n}\n"
  },
  {
    "path": "src/menu_demo.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/*\n\tDemo Menu module\n\t$Id: menu_demo.h,v 1.8 2007-03-19 13:23:20 johnnycz Exp $\n*/\n\n#include \"keys.h\"\n\n// <interface for menu.c>\n// initializes the \"Demo\" menu on client startup\nvoid Menu_Demo_Init(void);\n\n// process key press that belongs to demo menu\nvoid Menu_Demo_Key(int key, wchar unichar);\n\n// process request to draw the demo menu\nvoid Menu_Demo_Draw (void);\n\n// process mouse move event\nqbool Menu_Demo_Mouse_Event(const mouse_state_t *);\n\n// sets new starting dir\nvoid Menu_Demo_NewHome(const char *);\n// </interface>\n\n\n// <interface for cl_demo.c>\nvoid CL_Demo_Playlist_f(void);\nvoid CL_Demo_NextInPlaylist(void);\nvoid CL_Demo_Disconnected(void);\n// </interface>\n"
  },
  {
    "path": "src/menu_ingame.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/**\n\t\\file\n\n\t\\brief\n\tIn-game menu\n\n\t\\author\n\tjohnnycz\n**/\n\n#include \"quakedef.h\"\n#include \"keys.h\"\n#include \"menu.h\"\n#include \"Ctrl.h\"\n#include \"settings.h\"\n#include \"settings_page.h\"\n#include \"server.h\"\n#include \"version.h\"\n\n#define TOPMARGIN (6*LETTERWIDTH)\n\nsettings_page single_menu;\nsettings_page ingame_menu;\nsettings_page democtrl_menu;\nsettings_page botmatch_menu;\nsettings_page qtv_menu;\n\n#define MENU_ALIAS(func,command,leavem) static void func(void) { Cbuf_AddText(command \"\\n\"); if (leavem) M_LeaveMenus(); }\n\nvoid MIng_MainMenu(void)\t\t{ M_Menu_Main_f(); }\nvoid MIng_Back(void)\t\t\t{ M_LeaveMenus(); }\n\nMENU_ALIAS(MIng_ServerBrowser,\"menu_slist\",false);\nMENU_ALIAS(MIng_Options,\"menu_options\",false);\nMENU_ALIAS(MIng_Join,\"join\",true);\nMENU_ALIAS(MIng_Observe,\"observe\",true);\nMENU_ALIAS(MIng_Disconnect,\"disconnect\",true);\nMENU_ALIAS(MIng_Ready, \"ready\",true);\nMENU_ALIAS(MIng_Break, \"break\",true);\nMENU_ALIAS(MIng_SkillUp, \"skillup\",false);\nMENU_ALIAS(MIng_SkillDown, \"skilldown\",false);\nMENU_ALIAS(MIng_AddBot, \"addbot\",false);\nMENU_ALIAS(MIng_RemoveBot, \"removebot\",false);\nMENU_ALIAS(MIng_TeamBlue, \"team blue;color 13\",true);\nMENU_ALIAS(MIng_TeamRed, \"team red;color 4\",true);\nMENU_ALIAS(MDemoCtrl_DemoBrowser,\"menu_demos\",false);\nMENU_ALIAS(MDemoCtrl_DemoControls, \"demo_controls\",true);\nMENU_ALIAS(MSP_Load, \"menu_load\", false);\nMENU_ALIAS(MSP_Save, \"menu_save\", false);\nMENU_ALIAS(MQTV_Autotrack, \"autotrack\", true);\nMENU_ALIAS(MQTV_Lastscores, \"lastscores\", true);\nMENU_ALIAS(MQTV_Observers, \"qtvusers\", true);\nMENU_ALIAS(MQTV_Reconnect, \"qtvreconnect\", true);\n\nsetting single_menu_entries[] = {\n\tADDSET_SEPARATOR(\"In-game Menu\"),\n\tADDSET_ACTION(\"Load Game\", MSP_Load, \"\"),\n\tADDSET_ACTION(\"Save Game\", MSP_Save, \"\"),\n\tADDSET_BLANK(),\n\tADDSET_ACTION(\"Options\", MIng_Options, \"\"),\n\tADDSET_ACTION(\"Main Menu\", MIng_MainMenu, \"\"),\n\tADDSET_BLANK(),\n\tADDSET_ACTION(\"Return To Game\", MIng_Back, \"\"),\n};\n\nsetting ingame_menu_entries[] = {\n\tADDSET_SEPARATOR(\"In-game Menu\"),\n\tADDSET_ACTION(\"Ready\", MIng_Ready, \"\"),\n\tADDSET_ACTION(\"Break\", MIng_Break, \"\"),\n\tADDSET_ACTION(\"Join\", MIng_Join, \"\"),\n\tADDSET_ACTION(\"Observe\", MIng_Observe, \"\"),\n\tADDSET_ACTION(\"Disconnect\", MIng_Disconnect, \"\"),\n\tADDSET_BLANK(),\n\tADDSET_ACTION(\"Server Browser\", MIng_ServerBrowser, \"\"),\n\tADDSET_ACTION(\"Options\", MIng_Options, \"\"),\n\tADDSET_ACTION(\"Main Menu\", MIng_MainMenu, \"\"),\n\tADDSET_BLANK(),\n\tADDSET_ACTION(\"Return To Game\", MIng_Back, \"\"),\n};\n\nsetting democtrl_menu_entries[] = {\n\tADDSET_SEPARATOR(\"Demo Control Menu\"),\n\tADDSET_ACTION(\"Toggle autotrack\", MQTV_Autotrack, \"\"),\n\tADDSET_ACTION(\"Demo controls\", MDemoCtrl_DemoControls, \"\"),\n\tADDSET_BLANK(),\n\tADDSET_ACTION(\"Disconnect\", MIng_Disconnect, \"\"),\n\tADDSET_BLANK(),\n\tADDSET_ACTION(\"Demo Browser\", MDemoCtrl_DemoBrowser, \"\"),\n\tADDSET_ACTION(\"Options\", MIng_Options, \"\"),\n\tADDSET_ACTION(\"Main Menu\", MIng_MainMenu, \"\"),\n\tADDSET_BLANK(),\n\tADDSET_ACTION(\"Return To Demo\", MIng_Back, \"\"),\n};\n\nsetting qtv_menu_entries[] = {\n\tADDSET_SEPARATOR(\"QuakeTV Menu\"),\n\tADDSET_ACTION(\"Toggle autotrack\", MQTV_Autotrack, \"\"),\n\tADDSET_ACTION(\"Last scores\", MQTV_Lastscores, \"\"),\n\tADDSET_ACTION(\"List observers\", MQTV_Observers, \"\"),\n\tADDSET_BLANK(),\n\tADDSET_ACTION(\"Reconnect\", MQTV_Reconnect, \"\"),\n\tADDSET_ACTION(\"Disconnect\", MIng_Disconnect, \"\"),\n\tADDSET_BLANK(),\n\tADDSET_ACTION(\"Options\", MIng_Options, \"\"),\n\tADDSET_ACTION(\"Main Menu\", MIng_MainMenu, \"\"),\n\tADDSET_BLANK(),\n\tADDSET_ACTION(\"Return To Game\", MIng_Back, \"\"),\n};\n\nsetting botmatch_menu_entries[] = {\n\tADDSET_SEPARATOR(\"Botmatch Menu\"),\n\tADDSET_ACTION(\"Ready\", MIng_Ready, \"\"),\n\tADDSET_ACTION(\"Break\", MIng_Break, \"\"),\n\tADDSET_ACTION(\"Team Blue\", MIng_TeamBlue, \"\"),\n\tADDSET_ACTION(\"Team Red\", MIng_TeamRed, \"\"),\n\tADDSET_ACTION(\"Add Bot\", MIng_AddBot, \"\"),\n\tADDSET_ACTION(\"Remove Bot\", MIng_RemoveBot, \"\"),\n\tADDSET_ACTION(\"Increase Bot Skill\", MIng_SkillUp, \"\"),\n\tADDSET_ACTION(\"Decrease Bot Skill\", MIng_SkillDown, \"\"),\n\tADDSET_ACTION(\"Disconnect\", MIng_Disconnect, \"\"),\n\tADDSET_BLANK(),\n\tADDSET_ACTION(\"Options\", MIng_Options, \"\"),\n\tADDSET_ACTION(\"Main Menu\", MIng_MainMenu, \"\"),\n\tADDSET_BLANK(),\n\tADDSET_ACTION(\"Return To Game\", MIng_Back, \"\"),\n};\n\n#define DEMOPLAYBACK() (cls.demoplayback && cls.mvdplayback != QTV_PLAYBACK)\n#define BOTMATCH() (!strcmp(cls.gamedirfile, \"fbca\"))\n#ifndef CLIENTONLY\n#define SINGLEPLAYER() (com_serveractive && cls.state == ca_active && !cl.deathmatch && maxclients.value == 1)\n#endif\n#define QTVPLAYBACK() (cls.mvdplayback == QTV_PLAYBACK)\n\nstatic settings_page *M_Ingame_Current(void) {\n\tif (DEMOPLAYBACK()) {\n\t\treturn &democtrl_menu;\n\t}\n\telse if (BOTMATCH()) {\n\t\treturn &botmatch_menu;\n\t}\n#ifndef CLIENTONLY\n\telse if (SINGLEPLAYER()) {\n\t\treturn &single_menu;\n\t}\n#endif\n\telse if (QTVPLAYBACK()) {\n\t\treturn &qtv_menu;\n\t}\n\telse {\n\t\treturn &ingame_menu;\n\t}\n}\n\nvoid M_Ingame_Draw(void) {\n\tchar version[VERSION_MAX_LEN] = { 0 };\n\tqbool outdated;\n\n\tM_Unscale_Menu();\n\tSettings_Draw(0, TOPMARGIN, vid.width, vid.height - TOPMARGIN, M_Ingame_Current());\n\n\toutdated = VersionCheck_GetLatest(version);\n\tif (outdated)\n\t{\n\t\tchar message[4096] = { 0 };\n\t\tsnprintf(message, sizeof(message), \"Outdated client %s, latest version is %s\", VERSION_NUMBER, version);\n\t\tUI_Print_Center(0, 32, vid.width, message, 0);\n\t}\n}\n\nvoid M_Ingame_Key(int key) {\n\tif (Settings_Key(M_Ingame_Current(), key, 0)) return;\n\n\tswitch (key) {\n\tcase K_MOUSE2:\n\tcase K_ESCAPE: M_LeaveMenus(); break;\n\t}\n}\n\nqbool Menu_Ingame_Mouse_Event(const mouse_state_t *ms) {\n\tmouse_state_t m = *ms;\n\tm.y -= TOPMARGIN;\n\treturn Settings_Mouse_Event(M_Ingame_Current(), &m);\n}\n\nvoid Menu_Ingame_Init(void)\n{\n\tSettings_Page_Init(single_menu, single_menu_entries);\n\tSettings_Page_SetMinit(single_menu);\n\tSettings_Page_Init(ingame_menu, ingame_menu_entries);\n\tSettings_Page_SetMinit(ingame_menu);\n\tSettings_Page_Init(democtrl_menu, democtrl_menu_entries);\n\tSettings_Page_SetMinit(democtrl_menu);\n\tSettings_Page_Init(botmatch_menu, botmatch_menu_entries);\n\tSettings_Page_SetMinit(botmatch_menu);\n\tSettings_Page_Init(qtv_menu, qtv_menu_entries);\n\tSettings_Page_SetMinit(qtv_menu);\n}\n\nvoid Menu_Ingame_Shutdown(void)\n{\n\tSettings_Shutdown(&single_menu);\n\tSettings_Shutdown(&ingame_menu);\n\tSettings_Shutdown(&democtrl_menu);\n\tSettings_Shutdown(&botmatch_menu);\n\tSettings_Shutdown(&qtv_menu);\n}\n"
  },
  {
    "path": "src/menu_ingame.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/**\n\n\tIn-game menu\n\n\tmade by johnnycz, June 2007\n\tlast edit:\n\t$Id: menu_ingame.h,v 1.2 2007-07-15 09:50:44 disconn3ct Exp $\n\n*/\n\n#ifndef __MENU_INGAME_H__\n#define __MENU_INGAME_H__\n\nextern void M_Ingame_Draw(void);\nextern void M_Democtrl_Draw(void);\n\nextern void M_Ingame_Key(int);\nextern void M_Democtrl_Key(int);\n\nextern qbool Menu_Ingame_Mouse_Event(const mouse_state_t *ms);\nextern qbool Menu_Democtrl_Mouse_Event(const mouse_state_t *ms);\n\nextern void Menu_Ingame_Init(void);\n\n#endif // __MENU_INGAME_H__\n"
  },
  {
    "path": "src/menu_multiplayer.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"keys.h\"\n#include \"EX_browser.h\"\n#include \"settings.h\"\n#include \"settings_page.h\"\n#include \"Ctrl.h\"\n#include \"Ctrl_Tab.h\"\n#include \"menu.h\"\n#include \"gl_model.h\"\n#include \"menu_multiplayer.h\"\n\n#define BROWSERPADDING 4\n\nCTab_t sb_tab;\n\nextern cvar_t scr_scaleMenu;\nextern int menuwidth;\nextern int menuheight;\n\nconst char* sb_showproxies_labels[] = { \"hide\", \"show\", \"exclusively\" };\n\nstatic settings_page sbsettings;\nstatic setting sbsettings_arr[] = {\n\tADDSET_SEPARATOR(\"Server Filters\"),\n\tADDSET_BOOL\t\t(\"Hide Empty\", sb_hideempty),\n\tADDSET_BOOL\t\t(\"Hide Full\", sb_hidefull),\n\tADDSET_BOOL\t\t(\"Hide Not Empty\", sb_hidenotempty),\n\tADDSET_BOOL\t\t(\"Hide Dead\", sb_hidedead),\n\tADDSET_BOOL     (\"Hide High Ping\", sb_hidehighping),\n\tADDSET_NAMED    (\"Show Proxies\", sb_showproxies, sb_showproxies_labels),\n\n\tADDSET_SEPARATOR(\"Display Columns\"),\n\tADDSET_BOOL\t\t(\"Show Ping\", sb_showping),\n\tADDSET_BOOL\t\t(\"Show Map\", sb_showmap),\n\tADDSET_BOOL\t\t(\"Show Gamedir\", sb_showgamedir),\n\tADDSET_BOOL\t\t(\"Show Players\", sb_showplayers),\n\tADDSET_BOOL\t\t(\"Show Timelimit\", sb_showtimelimit),\n\tADDSET_BOOL\t\t(\"Show Fraglimit\", sb_showfraglimit),\n\tADDSET_BOOL\t\t(\"Show Server Address\", sb_showaddress),\n\n\tADDSET_SEPARATOR(\"Display\"),\n\tADDSET_BOOL\t\t(\"Server Status\", sb_status),\n\n\tADDSET_SEPARATOR(\"Network Filters\"),\n\tADDSET_NUMBER\t(\"Ping Timeout\", sb_pingtimeout, 50, 1000, 50),\n\tADDSET_NUMBER\t(\"Pings Per Server\", sb_pings, 1, 5, 1),\n\tADDSET_NUMBER\t(\"Pings Per Second\", sb_pingspersec, 10, 300, 10),\n\tADDSET_NUMBER\t(\"Master Timeout\", sb_mastertimeout, 50, 1000, 50),\n\tADDSET_NUMBER\t(\"Master Retries\", sb_masterretries, 1, 5, 1),\n\tADDSET_NUMBER\t(\"Info Timeout\", sb_infotimeout, 50, 1000, 50),\n\tADDSET_NUMBER\t(\"Info Retries\", sb_inforetries, 0, 4, 1),\n\tADDSET_NUMBER\t(\"Infos Per Second\", sb_infospersec, 10, 1000, 10),\n\tADDSET_BOOL     (\"Find best routes\", sb_findroutes)\n};\n\n/* generates a toggle function for custom enum in ADDSET_CUSTOM setting type */\n#define GENERATE_ENUM_TOGGLE_PROC(basename,var,upperbound)\t\\\n\tstatic void M_CG_##basename##_t(qbool back) {\t\t\\\n\t\t(var) += back ? -1 : 1;\t\t\t\\\n\t\tif ((var) >= upperbound) {\t\t\\\n\t\t\t(var) = 0;\t\t\t\t\t\\\n\t\t} else if ((var) < 0) {\t\t\t\\\n\t\t\t(var) = upperbound - 1;\t\t\\\n\t\t}\t\t\t\t\t\t\t\t\\\n\t}\n\n/* generates a reading function for custom enum in ADDSET_CUSTOM setting type */\n#define GENERATE_ENUM_READ_PROC(basename,var,ubound,descarray)\t\\\n\tstatic const char* M_CG_##basename##_r(void) {\t\\\n\t\tif (((var) > 0) && ((var) < ubound)) {\t\t\t\\\n\t\t\treturn descarray[var];\t\t\t\t\t\t\\\n\t\t} else {\t\t\t\t\t\t\t\t\t\t\\\n\t\t\treturn descarray[0];\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t}\n\n// spawns a variable of given enum type and it's reading and writing menu function\n#define GENERATE_ENUM_MENU_FUNC(menu_enum) \\\n\tstatic menu_enum ## _t menu_enum ## _var; \\\n\tGENERATE_ENUM_READ_PROC(menu_enum, menu_enum ## _var, menu_enum ## _max, menu_enum ## _desc) \\\n\tGENERATE_ENUM_TOGGLE_PROC(menu_enum, menu_enum ## _var, menu_enum ## _max)\n\ntypedef struct {\n\tchar    *name;\n\tchar    *description;\n} level_t;\n\nlevel_t        levels[] = {\n\t{\"start\", \"Entrance\"},    // 0\n\n\t{\"e1m1\", \"Slipgate Complex\"},                // 1\n\t{\"e1m2\", \"Castle of the Damned\"},\n\t{\"e1m3\", \"The Necropolis\"},\n\t{\"e1m4\", \"The Grisly Grotto\"},\n\t{\"e1m5\", \"Gloom Keep\"},\n\t{\"e1m6\", \"The Door To Chthon\"},\n\t{\"e1m7\", \"The House of Chthon\"},\n\t{\"e1m8\", \"Ziggurat Vertigo\"},\n\n\t{\"e2m1\", \"The Installation\"},                // 9\n\t{\"e2m2\", \"Ogre Citadel\"},\n\t{\"e2m3\", \"Crypt of Decay\"},\n\t{\"e2m4\", \"The Ebon Fortress\"},\n\t{\"e2m5\", \"The Wizard's Manse\"},\n\t{\"e2m6\", \"The Dismal Oubliette\"},\n\t{\"e2m7\", \"Underearth\"},\n\n\t{\"e3m1\", \"Termination Central\"},            // 16\n\t{\"e3m2\", \"The Vaults of Zin\"},\n\t{\"e3m3\", \"The Tomb of Terror\"},\n\t{\"e3m4\", \"Satan's Dark Delight\"},\n\t{\"e3m5\", \"Wind Tunnels\"},\n\t{\"e3m6\", \"Chambers of Torment\"},\n\t{\"e3m7\", \"The Haunted Halls\"},\n\n\t{\"e4m1\", \"The Sewage System\"},                // 23\n\t{\"e4m2\", \"The Tower of Despair\"},\n\t{\"e4m3\", \"The Elder God Shrine\"},\n\t{\"e4m4\", \"The Palace of Hate\"},\n\t{\"e4m5\", \"Hell's Atrium\"},\n\t{\"e4m6\", \"The Pain Maze\"},\n\t{\"e4m7\", \"Azure Agony\"},\n\t{\"e4m8\", \"The Nameless City\"},\n\n\t{\"end\", \"Shub-Niggurath's Pit\"},            // 31\n\n\t{\"dm1\", \"Place of Two Deaths\"},                // 32\n\t{\"dm2\", \"Claustrophobopolis\"},\n\t{\"dm3\", \"The Abandoned Base\"},\n\t{\"dm4\", \"The Bad Place\"},\n\t{\"dm5\", \"The Cistern\"},\n\t{\"dm6\", \"The Dark Zone\"}\n};\n\ntypedef struct {\n\tchar    *description;\n\tint        firstLevel;\n\tint        levels;\n} episode_t;\n\nepisode_t    episodes[] = {\n\t{\"Welcome to Quake\", 0, 1},\n\t{\"Doomed Dimension\", 1, 8},\n\t{\"Realm of Black Magic\", 9, 7},\n\t{\"Netherworld\", 16, 7},\n\t{\"The Elder World\", 23, 8},\n\t{\"Final Level\", 31, 1},\n\t{\"Deathmatch Arena\", 32, 6}\n};\n\n/* bot match type */\n\ttypedef enum bm_type_e {\n\t\tbmt_arena, bmt_clarena, bmt_duel, bmt_ffa, bmt_team, bm_type_max\n\t} bm_type_t;\n\tstatic const char *bm_type_desc[bm_type_max] = {\n\t\t\"Arena\", \"Clan Arena\", \"Duel\", \"Free For All\", \"Teamplay\"\n\t};\n\tGENERATE_ENUM_MENU_FUNC(bm_type);\n\n/* bot match map */\n\ttypedef enum bm_map_e {\n\t\tbmm_dm3, bmm_dm4, bmm_dm6, bmm_ztndm3, bmm_e1m2, bmm_aerowalk, bmm_spinev2,\n\t\tbmm_pkeg1, bmm_ultrav, bmm_frobodm2, bmm_amphi2, bmm_povdmm4, bmm_dranzdm8,\n\t\tbm_map_max\n\t} bm_map_t;\n\tstatic const char *bm_map_desc[bm_map_max] = {\n\t\t\"dm3\", \"dm4\", \"dm6\", \"ztndm3\", \"e1m2\", \"aerowalk\", \"spinev2\", \"pkeg1\",\n\t\t\"ultrav\", \"frobodm2\", \"amphi2\", \"povdmm4\", \"dranzdm8\"\n\t};\n\tGENERATE_ENUM_MENU_FUNC(bm_map);\n\n/* bot match starter */\n\tstatic void M_CG_BotMatch_Start(void) {\n\t\tconst char *cfg;\n\t\tconst char *map = bm_map_desc[bm_map_var];\n\t\textern cvar_t menu_botmatch_gamedir, menu_botmatch_mod_old;\n\n\t\tswitch (bm_type_var) {\n\t\t\tcase bmt_arena:\t\tcfg = \"arena\"; break;\n\t\t\tcase bmt_clarena:\tcfg = \"clarena\"; break;\n\t\t\tcase bmt_duel:\t\tcfg = \"duel\"; break;\n\t\t\tcase bmt_ffa:\t\tcfg = \"ffa\"; break;\n\t\t\tcase bmt_team:\t\tcfg = \"team\"; break;\n\t\t\tdefault:\t\t\tcfg = \"ffa\"; break;\n\t\t}\n\n\t\tCbuf_AddText(\"disconnect;sv_progsname qwprogs;\");\n\t\tif (menu_botmatch_gamedir.string[0]) {\n\t\t\tCbuf_AddText(va(\"gamedir \\\"%s\\\";\", menu_botmatch_gamedir.string));\n\t\t}\n\t\tif (menu_botmatch_mod_old.integer) {\n\t\t\tCbuf_AddText(\"cl_sv_packetsync 0\\n\");\n\t\t\tCbuf_AddText(\"cl_pext_floatcoords 0\\n\");\n\t\t\tCbuf_AddText(\"sv_bigcoords 0\\n\");\n\t\t}\n\t\tCbuf_AddText(va(\"exec configs/%s.cfg;map %s\\n\",cfg,map));\n\t\tM_LeaveMenus();\n\t}\n\n\n/* coop game difficulty */\n\ttypedef enum game_skill_e {\n\t\tGSKILL_EASY = 0,\n\t\tGSKILL_NORMAL,\n\t\tGSKILL_HARD,\n\t\tGSKILL_NIGHTMARE,\n\t\tgame_skill_max\n\t} game_skill_t;\n\tstatic const char* game_skill_desc[game_skill_max] = {\n\t\t\"Easy\", \"Normal\", \"Hard\", \"Nightmare\"\n\t};\n\tGENERATE_ENUM_MENU_FUNC(game_skill);\n\n/* coop game team damage */\n\ttypedef enum cg_teamdamage_e {\n\t\tTD_OFF, TD_ON, cg_teamdamage_max\n\t} cg_teamdamage_t;\n\tstatic const char* cg_teamdamage_desc[cg_teamdamage_max] = {\n\t\t\"off\", \"on\"\n\t};\n\tGENERATE_ENUM_MENU_FUNC(cg_teamdamage);\n\n/* deathmatch map */\n\ttypedef enum game_map_group_e {\n\t\tGMG_EPISODE1,\t\n\t\tGMG_EPISODE2,\n\t\tGMG_EPISODE3,\n\t\tGMG_EPISODE4,\n\t\tGMG_CUSTOM,\n\t\tgame_map_group_max\n\t} game_map_group_t;\n\tstatic const char* game_map_group_desc[game_map_group_max] = {\n\t\t\"Doomed Dimension\", \"Realm of Black Magic\", \"Netherworld\", \"The Elder World\", \"Custom\"\n\t};\n\tGENERATE_ENUM_MENU_FUNC(game_map_group);\n\n/* deathmatch game mode */\n\ttypedef enum DM_game_mode_e {\n\t\tDMGM_ffa, DMGM_duel, DMGM_tp, DMGM_arena, DM_game_mode_max\n\t} DM_game_mode_t;\n\tstatic const char* DM_game_mode_desc[DM_game_mode_max] = {\n\t\t\"Free For All\", \"Duel\", \"Teamplay\", \"Arena\"\n\t};\n\tGENERATE_ENUM_MENU_FUNC(DM_game_mode);\n\nstatic int coopmaxplayers = 2;\n\nstatic int dm_maxplayers = 2;\nstatic int dm_maxspectators = 8;\nstatic int dm_timelimit = 10;\nstatic int dm_fraglimit = 0;\n\nvoid Menu_CG_DM_StartGame(void)\n{\n\tint dm;\n\tint tp;\n\n\tCbuf_AddText(\"disconnect; gamedir qw; coop 0; sv_progsname qwprogs;\\n\");\n\tswitch (DM_game_mode_var) {\n\t\tcase DMGM_ffa: tp = 0; dm = 3; break;\n\t\tcase DMGM_duel: tp = 0; dm = 3; break;\n\t\tcase DMGM_tp: tp = 2; dm = 1; break;\n\t\tcase DMGM_arena: tp = 0; dm = 4; break;\n\t\tdefault: tp = 0; dm = 3; break;\n\t}\n\tCbuf_AddText(va(\"deathmatch %d;teamplay %d\\n\",dm,tp));\n\tCbuf_AddText(va(\"maxclients %d;maxspectators %d\\n\",dm_maxplayers,dm_maxspectators));\n\tCbuf_AddText(va(\"timelimit %d;fraglimit %d\\n\",dm_timelimit,dm_fraglimit));\n\t// todo: finish map choosing\n\tCbuf_AddText(\"map dm4\\n\");\n\tM_LeaveMenus();\n}\n\nvoid Menu_CG_Coop_StartGame(void)\n{\n\tint tp = (cg_teamdamage_var == TD_OFF) ? 1 : 2;\n\tint skill = game_skill_var;\n\n\tCbuf_AddText(\"disconnect; sv_progsname spprogs; gamedir qw; coop 1; deathmatch 0\\n\");\n\tCbuf_AddText(va(\"teamplay %d; skill %d;maxclients %d;map start\",\n\t\t\t\t\ttp,skill,coopmaxplayers));\n\tM_LeaveMenus();\n}\n\nstatic settings_page create_game_options;\nstatic setting create_game_options_arr[] = {\n\tADDSET_SEPARATOR(\"Bot Match\"),\n\tADDSET_CUSTOM\t(\"Game Mode\", M_CG_bm_type_r, M_CG_bm_type_t,\n\t\t\t\t\t \"Choose one of the three available game types\"),\n\tADDSET_CUSTOM\t(\"Map\", M_CG_bm_map_r, M_CG_bm_map_t, \"Bots support only limit set of maps\"),\n\tADDSET_ACTION\t(\"Start Bot Match\", M_CG_BotMatch_Start, \"Start the bot match\"),\n\n\tADDSET_SEPARATOR(\"Deathmatch\"),\n\tADDSET_INTNUMBER(\"Player slots\", dm_maxplayers, 1, 32, 1),\n\tADDSET_INTNUMBER(\"Spectator slots\", dm_maxspectators, 0, 16, 1),\n\tADDSET_CUSTOM\t(\"Game Mode\", M_CG_DM_game_mode_r, M_CG_DM_game_mode_t, \"Game Mode\"),\n\tADDSET_INTNUMBER(\"Timelimit\", dm_timelimit, 0, 60, 5),\n\tADDSET_INTNUMBER(\"Fraglimit\", dm_fraglimit, 0, 100, 10),\n\tADDSET_CUSTOM\t(\"Map Group\", M_CG_game_map_group_r, M_CG_game_map_group_t, \"\"),\n\t/* ADDSET_CUSTOM\t(\"Map\", Menu_CG_map_r, Menu_CG_map_t, \"\"), */\n\tADDSET_ACTION\t(\"Start Deathmatch\", Menu_CG_DM_StartGame,\n\t\t\t\t\t \"Start game with given parameters\"),\n\n\tADDSET_SEPARATOR(\"Cooperative\"),\n\tADDSET_CUSTOM\t(\"Difficulty\", M_CG_game_skill_r, M_CG_game_skill_t, \n\t\t\t\t\t \"Monsters skill level\"),\n\tADDSET_CUSTOM\t(\"Teamplay Damage\", M_CG_cg_teamdamage_r, M_CG_cg_teamdamage_t,\n\t\t\t\t\t \"Whether Give damage to teammates if you shoot them\"),\n\tADDSET_INTNUMBER(\"Player Slots\", coopmaxplayers, 1, 32, 1),\n\tADDSET_ACTION\t(\"Start Coop Game\", Menu_CG_Coop_StartGame,\n\t\t\t\t\t \"Start game with given parameters\"),\n};\n\nstatic void Servers_Draw (int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n\tSB_Servers_Draw(x,y,w,h);\n}\n\nstatic int Servers_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n\treturn SB_Servers_Key(key);\n}\n\nstatic void Servers_OnShow(void)\n{\n\tSB_Servers_OnShow();\n}\n\nstatic qbool Servers_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn SB_Servers_Mouse_Event(ms);\n}\n\nstatic void Sources_Draw (int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n\tSB_Sources_Draw(x,y,w,h);\n}\n\nstatic int Sources_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n\treturn SB_Sources_Key(key);\n}\n\nstatic qbool Sources_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn SB_Sources_Mouse_Event(ms);\n}\n\nstatic void Players_Draw(int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n\tSB_Players_Draw(x, y, w, h);\n}\n\nstatic int Players_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n\treturn SB_Players_Key(key);\n}\nstatic qbool Players_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn SB_Players_Mouse_Event(ms);\n}\n\nstatic qbool Options_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn Settings_Mouse_Event(&sbsettings, ms);\n}\n\nstatic int Options_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n\treturn Settings_Key(&sbsettings, key, unichar);\n}\n\nstatic void Options_Draw(int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n\tSettings_Draw(x, y, w, h, &sbsettings);\n}\n\nvoid Menu_MultiPlayer_Draw (void)\n{\n\tint x, y, w, h;\n\n\tM_Unscale_Menu();\n\n\tw = min(640, vid.width) - BROWSERPADDING*2;\n\th = min(480, vid.height) - BROWSERPADDING*2;\n\tx = (vid.width - w) / 2;\n\ty = (vid.height - h) / 2;\n\n\tBrowser_window.x = x;\n\tBrowser_window.y = y;\n\tBrowser_window.w = w;\n\tBrowser_window.h = h;\n\n\tCTab_Draw(&sb_tab, x, y, w, h);\n\n\tSB_Specials_Draw();\n}\n\nvoid Menu_MultiPlayer_Key(int key, wchar unichar)\n{\n\tif (SB_Specials_Key(key, unichar)) return;\n\t\n\tCTab_Key(&sb_tab, key, unichar);\n}\n\nqbool Menu_MultiPlayer_Mouse_Event(const mouse_state_t *ms)\n{\n\tmouse_state_t nms = *ms;\n\n    if (ms->button_up == 2) {\n        Menu_MultiPlayer_Key(K_MOUSE2, 0);\n        return true;\n    }\n\n\tnms.x -= Browser_window.x;\n\tnms.y -= Browser_window.y;\n\tnms.x_old -= Browser_window.x;\n\tnms.y_old -= Browser_window.y;\n\n\treturn CTab_Mouse_Event(&sb_tab, &nms);\n}\n\nstatic qbool CreateGame_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn Settings_Mouse_Event(&create_game_options, ms);\n}\n\nstatic int CreateGame_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n\treturn Settings_Key(&create_game_options, key, unichar);\n}\n\nstatic void CreateGame_Draw(int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n\tSettings_Draw(x,y,w,h,&create_game_options);\n}\n\nCTabPage_Handlers_t sb_servers_handlers = {\n\tServers_Draw,\n\tServers_Key,\n\tServers_OnShow,\n\tServers_Mouse_Event\n};\n\nCTabPage_Handlers_t sb_sources_handlers = {\n\tSources_Draw,\n\tSources_Key,\n\tNULL,\n\tSources_Mouse_Event\n};\n\nCTabPage_Handlers_t sb_players_handlers = {\n\tPlayers_Draw,\n\tPlayers_Key,\n\tNULL,\n\tPlayers_Mouse_Event\n};\n\nCTabPage_Handlers_t sb_options_handlers = {\n\tOptions_Draw,\n\tOptions_Key,\n\tNULL,\n\tOptions_Mouse_Event\n};\n\nCTabPage_Handlers_t sb_creategame_handlers = {\n\tCreateGame_Draw,\n\tCreateGame_Key,\n\tNULL,\n\tCreateGame_Mouse_Event\n};\n\nvoid Menu_MultiPlayer_Init(void)\n{\n\tSettings_Page_Init(sbsettings, sbsettings_arr);\n\tSettings_Page_Init(create_game_options, create_game_options_arr);\n\n\tCTab_Init(&sb_tab);\n\tCTab_AddPage(&sb_tab, \"Servers\", SBPG_SERVERS, &sb_servers_handlers);\n\tCTab_AddPage(&sb_tab, \"Sources\", SBPG_SOURCES, &sb_sources_handlers);\n\tCTab_AddPage(&sb_tab, \"Players\", SBPG_PLAYERS, &sb_players_handlers);\n\tCTab_AddPage(&sb_tab, \"Options\", SBPG_OPTIONS, &sb_options_handlers);\n\tCTab_AddPage(&sb_tab, \"Create\", SBPG_CREATEGAME, &sb_creategame_handlers);\n\tCTab_SetCurrentId(&sb_tab, SBPG_SERVERS);\n}\n\nvoid Menu_MultiPlayer_Shutdown(void)\n{\n\tSettings_Shutdown(&sbsettings);\n\tSettings_Shutdown(&create_game_options);\n}\n\nvoid Menu_MultiPlayer_SwitchToServersTab(void)\n{\n\tM_EnterMenu(m_multiplayer);\n\tCTab_SetCurrentId(&sb_tab, SBPG_SERVERS);\n}\n"
  },
  {
    "path": "src/menu_multiplayer.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// initialize the multiplayer menu\nvoid Menu_MultiPlayer_Init(void);\n\nvoid Menu_MultiPlayer_Draw (void);\n\nvoid Menu_MultiPlayer_Key(int key, wchar unichar);\n\nqbool Menu_MultiPlayer_Mouse_Event(const mouse_state_t *ms);\n\nvoid Menu_MultiPlayer_SwitchToServersTab(void);\n\ntypedef enum\n{\n\tSBPG_SERVERS,\t// Servers page\n\tSBPG_SOURCES,\t// Sources page\n\tSBPG_PLAYERS,\t// Players page\n\tSBPG_OPTIONS,\t// Options page\n\tSBPG_CREATEGAME   // Host Game page\n} sb_tab_t;\nextern CTab_t sb_tab;\n\n"
  },
  {
    "path": "src/menu_options.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/*\n\n\tOptions Menu module\n\n\tUses Ctrl_Tab and Settings modules\n\n\tNaming convention:\n\tfunction mask | usage    | purpose\n\t--------------|----------|-----------------------------\n\tMenu_Opt_*    | external | interface for menu.c\n\tCT_Opt_*      | external | interface for Ctrl_Tab.c\n\n\tmade by:\n\t\tjohnnycz, Jan 2006\n\n*/\n\n#include \"quakedef.h\"\n#include \"settings.h\"\n#include \"settings_page.h\"\n#include \"Ctrl_EditBox.h\"\n#include \"vx_stuff.h\"\n#include \"vx_tracker.h\"\n#include \"gl_model.h\"\n#include \"tr_types.h\"\n#include \"teamplay.h\"\n#include \"EX_FileList.h\"\n#include \"Ctrl.h\"\n#include \"Ctrl_Tab.h\"\n#include \"input.h\"\n#include \"qsound.h\"\n#include \"menu.h\"\n#include \"keys.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"r_local.h\"\n\nextern cvar_t r_farclip, gl_max_size, gl_miptexLevel;\nextern cvar_t r_bloom;\nextern cvar_t gl_flashblend, r_dynamic, gl_lightmode, gl_modulate;\n\nextern cvar_t vid_framebuffer, vid_framebuffer_hdr, vid_framebuffer_hdr_tonemap, vid_framebuffer_scale, vid_framebuffer_multisample, vid_framebuffer_fxaa;\n\nextern cvar_t vid_software_palette;\n\n#ifdef EZ_MULTIPLE_RENDERERS\nextern cvar_t vid_renderer;\n#endif\n\ntypedef enum {\n\tOPTPG_MISC,\n\tOPTPG_PLAYER,\n\tOPTPG_FPS,\n\tOPTPG_HUD,\n\tOPTPG_DEMO_SPEC,\n\tOPTPG_BINDS,\n\tOPTPG_SYSTEM,\n\tOPTPG_CONFIG,\n}\toptions_tab_t;\n\nCTab_t options_tab;\nint options_unichar;\t// variable local to this module\n\nextern cvar_t     scr_scaleMenu;\nextern int        menuwidth;\nextern int        menuheight;\n\nextern qbool    m_entersound; // todo - put into menu.h\nvoid M_Menu_Help_f (void);\t// todo - put into menu.h\nextern cvar_t scr_scaleMenu;\nqbool OnMenuAdvancedChange(cvar_t*, char*);\ncvar_t menu_advanced = {\"menu_advanced\", \"0\"};\n\n//=============================================================================\n// <SETTINGS>\n\nenum {mode_fastest, mode_faithful, mode_higheyecandy, mode_eyecandy, mode_undef} fps_mode = mode_undef;\n\nextern cvar_t scr_fov, scr_newHud, cl_staticsounds, r_fullbrightSkins, cl_deadbodyfilter, cl_muzzleflash, r_fullbright;\nextern cvar_t scr_sshot_format;\nvoid ResetConfigs_f(void);\n\nstatic qbool AlwaysRun(void) { return cl_forwardspeed.value > 200; }\nconst char* AlwaysRunRead(void) { return AlwaysRun() ? \"on\" : \"off\"; }\nvoid AlwaysRunToggle(qbool back) {\n\tif (AlwaysRun()) {\n\t\tCvar_SetValue (&cl_forwardspeed, 200);\n\t\tCvar_SetValue (&cl_backspeed, 200);\n\t\tCvar_SetValue (&cl_sidespeed, 200);\n\t} else {\n\t\tCvar_SetValue (&cl_forwardspeed, 400);\n\t\tCvar_SetValue (&cl_backspeed, 400);\n\t\tCvar_SetValue (&cl_sidespeed, 400);\n\t}\n}\n\nstatic qbool InvertMouse(void) { return m_pitch.value < 0; }\nconst char* InvertMouseRead(void) { return InvertMouse() ? \"on\" : \"off\"; }\nvoid InvertMouseToggle(qbool back) { Cvar_SetValue(&m_pitch, -m_pitch.value); }\n\nstatic qbool AutoSW(void) { return (w_switch.value > 2) || (!w_switch.value) || (b_switch.value > 2) || (!b_switch.value); }\nconst char* AutoSWRead(void) { return AutoSW() ? \"on\" : \"off\"; }\nvoid AutoSWToggle(qbool back) {\n\tif (AutoSW()) {\n\t\tCvar_SetValue (&w_switch, 1);\n\t\tCvar_SetValue (&b_switch, 1);\n\t} else {\n\t\tCvar_SetValue (&w_switch, 8);\n\t\tCvar_SetValue (&b_switch, 8);\n\t}\n}\n\nstatic int GFXPreset(void) {\n\tif (fps_mode == mode_undef) {\n\t\tswitch ((int) cl_deadbodyfilter.value) {\n\t\tcase 0: fps_mode = mode_eyecandy; break;\n\t\tcase 1: fps_mode = cl_muzzleflash.value ? mode_faithful : mode_eyecandy; break;\n\t\tdefault: fps_mode = mode_fastest; break;\n\t\t}\n\t}\n\treturn fps_mode;\n}\nconst char* GFXPresetRead(void) {\n\tswitch (GFXPreset()) {\n\tcase mode_fastest: return \"fastest\";\n\tcase mode_higheyecandy: return \"high eyecandy\";\n\tcase mode_faithful: return \"faithful\";\n\tdefault: return \"eyecandy\";\n\t}\n}\nvoid GFXPresetToggle(qbool back) {\n\tif (back) {\n\t\tif (fps_mode <= 0) {\n\t\t\tfps_mode = mode_undef;\n\t\t}\n\t\t--fps_mode;\n\t}\n\telse {\n\t\t++fps_mode;\n\t\tif (fps_mode >= mode_undef) {\n\t\t\tfps_mode = 0;\n\t\t}\n\t}\n\n\tswitch (GFXPreset()) {\n\tcase mode_fastest: Cbuf_AddText (\"exec cfg/gfx_gl_fast.cfg\\n\"); return;\n\tcase mode_higheyecandy: Cbuf_AddText (\"exec cfg/gfx_gl_higheyecandy.cfg\\n\"); return;\n\tcase mode_faithful: Cbuf_AddText (\"exec cfg/gfx_gl_faithful.cfg\\n\"); return;\n\tcase mode_eyecandy: Cbuf_AddText (\"exec cfg/gfx_gl_eyecandy.cfg\\n\"); return;\n\t}\n}\n\nconst char* amf_tracker_frags_enum[] = {\"off\", \"related\", \"all\" };\n\nconst char* mvdautohud_enum[] = { \"off\", \"simple\", \"customizable\" };\nconst char* mvdautotrack_enum[] = { \"off\", \"auto\", \"custom\", \"multitrack\", \"simple\" };\nconst char* funcharsmode_enum[] = { \"ctrl+key\", \"ctrl+y\" };\nconst char* ignoreopponents_enum[] = { \"off\", \"always\", \"on match\" };\nconst char* msgfilter_enum[] = { \"off\", \"say+spec\", \"team\", \"say+team+spec\" };\nconst char* allowscripts_enum[] = { \"off\", \"simple\", \"all\" };\nconst char* scrautoid_enum[] = { \"off\", \"nick\", \"health+armor\", \"health+armor+type\", \"all (rl)\", \"all (best gun)\" };\nconst char* coloredtext_enum[] = { \"off\", \"simple\", \"frag messages\" };\nconst char* autorecord_enum[] = { \"off\", \"don't save\", \"auto save\" };\nconst char* hud_enum[] = { \"classic\", \"new\", \"combined\" };\nconst char* ignorespec_enum[] = { \"off\", \"on (as player)\", \"on (always)\" };\nconst char* gender_enum[] = { \"Male\", \"m\", \"Female\", \"f\", \"Neutral\", \"n\", \"Not specified\", \"\" };\nconst char* lighting_enum[] = { \"Off\", \"0\", \"On (CPU)\", \"1\", \"On (GPU - GLSL only)\", \"2\" };\n\nconst char* scr_sshot_format_enum[] = {\n\t\"JPG\", \"jpg\", \"PNG\", \"png\", \"TGA\", \"tga\" };\n\nextern cvar_t mvd_autotrack, mvd_moreinfo, mvd_status, cl_weaponpreselect, cl_weaponhide, con_funchars_mode, con_notifytime, scr_consize, ignore_opponents, _con_notifylines,\n\tignore_qizmo_spec, ignore_spec, msg_filter, crosshair, crosshairsize, cl_smartjump, scr_coloredText,\n\tcl_rollangle, cl_rollspeed, v_gunkick, v_kickpitch, v_kickroll, v_kicktime, v_viewheight, match_auto_sshot, match_auto_record, match_auto_logconsole,\n\tr_fastturb, r_grenadetrail, cl_drawgun, r_viewmodelsize, r_viewmodeloffset, gl_outline, scr_clock, scr_gameclock, show_fps, rate, cl_c2sImpulseBackup,\n\tname, team, skin, topcolor, bottomcolor, cl_teamtopcolor, cl_teambottomcolor, cl_teamquadskin, cl_teampentskin, cl_teambothskin, /*cl_enemytopcolor, cl_enemybottomcolor, */\n\tcl_enemyquadskin, cl_enemypentskin, cl_enemybothskin, demo_dir, qizmo_dir, qwdtools_dir, cl_fakename, cl_fakename_suffix,\n\tcl_chatsound, con_sound_mm1_volume, con_sound_mm2_volume, con_sound_spec_volume, con_sound_other_volume, s_khz, s_desiredsamples,\n\truleset, scr_sshot_dir, log_dir, cl_nolerp, cl_confirmquit, log_readable, ignore_flood, ignore_flood_duration, con_timestamps, scr_consize, scr_conspeed, cl_chatmode, cl_chasecam,\n\tenemyforceskins, teamforceskins, vid_vsync_lag_fix, cl_sayfilter_coloredtext, cl_sayfilter_sendboth,\n\tmvd_autotrack_lockteam, qtv_adjustbuffer, cl_earlypackets, cl_useimagesinfraglog, con_completion_format, menu_ingame, sys_inactivesound\n;\n\n#ifdef _WIN32\nextern cvar_t demo_format, sys_highpriority, cl_window_caption, vid_flashonactivity;\nvoid Sys_RegisterQWURLProtocol_f(void);\n#endif\nextern cvar_t scr_autoid, crosshairalpha, amf_hidenails, amf_hiderockets, gl_anisotropy, gl_lumatextures, gl_textureless, gl_colorlights, scr_conalpha, scr_conback, gl_clear, gl_powerupshells,\n\tscr_teaminfo\n;\n\nvoid CL_Autotrack_f(void);\n\nconst char* bandwidth_enum[] = { \n\t\"Modem (33k)\", \"3800\", \"Modem (56k)\", \"5670\", \n\t\"ISDN (112k)\", \"9856\", \"Cable (128k)\", \"14336\",\n\t\"ADSL (> 256k)\", \"30000\" };\n\nconst char* cl_c2sImpulseBackup_enum[] = {\n\t\"Perfect\", \"0\", \"Low\", \"2\", \"Medium\", \"4\",\n\t\"High\", \"6\" };\n\t\n\nconst char* ignore_flood_enum[] = {\n\t\"Off\", \"0\", \"say/spec\", \"1\", \"say/say_team/spec\", \"2\" };\n\n#ifdef _WIN32\nconst char* demoformat_enum[] = { \"QuakeWorld Demo\", \"qwd\", \"Qizmo compressed QWD\", \"qwz\", \"MultiViewDemo\", \"mvd\" };\n#endif\n\nconst char* cl_chatmode_enum[] = {\n\t\"All Commands\", \"All Chat\", \"First Word\" };\n\nconst char* con_completion_format_enum[] = {\n\t\"Old\", \"New:Current+Default\", \"New:Current\", \"New:Default\", \"New:Current+Default,if changed\", \"New:Without values\"\n};\n\t\nconst char* scr_conback_enum[] = {\n\t\"Off\", \"On Load\", \"Always\", };\n\nconst char* s_khz_enum[] = {\n\t\"11 kHz\", \"11\", \"22 kHz\", \"22\", \"44 kHz\", \"44\"\n//FIXME probably works on all supported platforms by now?\n#if defined(__linux__) || defined(__FreeBSD__) || defined(_WIN32)\n\t,\"48 kHz\", \"48\"\n#endif\n};\nconst char* s_desired_samples_enum[] = {\n\t\"(default)\", \"0\", \"128\", \"128\", \"256\", \"256\", \"512\", \"512\", \"1024\", \"1024\"\n};\n\nconst char* cl_nolerp_enum[] = {\"on\", \"off\"};\nconst char* ruleset_enum[] = { \"ezQuake default\", \"default\", \"Smackdown\", \"smackdown\", \"Thunderdome\", \"thunderdome\", \"Moscow TF League\", \"mtfl\", \"QuakeCon\", \"qcon\", \"Smackdrive\", \"smackdrive\" };\nconst char *mediaroot_enum[] = { \"relative to exe\", \"relative to home\", \"full path\" };\nconst char *teamforceskins_enum[] = { \"off\", \"use player's name\", \"use player's userid\", \"set t1, t2, t3, ...\" };\nconst char *enemyforceskins_enum[] = { \"off\", \"use player's name\", \"use player's userid\", \"set e1, e2, e3, ...\" };\n\n#ifdef _WIN32\nconst char *priority_enum[] = { \"low\", \"-1\", \"normal\", \"0\", \"high\", \"1\" };\n#endif\n\n\nvoid DefaultConfig(void) { Cbuf_AddText(\"cfg_reset full\\n\"); }\n\nsettings_page settmisc;\n\nvoid CT_Opt_Settings_Draw (int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page) {\n\tSettings_Draw(x, y, w, h, &settmisc);\n}\n\nint CT_Opt_Settings_Key (int k, wchar unichar, CTab_t *tab, CTabPage_t *page) {\n\treturn Settings_Key(&settmisc, k, unichar);\n}\n\nvoid OnShow_SettMain(void) { Settings_OnShow(&settmisc); }\n\nqbool CT_Opt_Settings_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn Settings_Mouse_Event(&settmisc, ms);\n}\n\n// </SETTINGS>\n//=============================================================================\n\nsettings_page settview;\n\nvoid CT_Opt_View_Draw (int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page) {\n\tSettings_Draw(x, y, w, h, &settview);\n}\n\nint CT_Opt_View_Key (int k, wchar unichar, CTab_t *tab, CTabPage_t *page) {\n\treturn Settings_Key(&settview, k, unichar);\n}\n\nvoid OnShow_SettView(void) { Settings_OnShow(&settview); }\n\nqbool CT_Opt_View_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn Settings_Mouse_Event(&settview, ms);\n}\nsettings_page settplayer;\nvoid CT_Opt_Player_Draw (int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page) {\n\tSettings_Draw(x, y, w, h, &settplayer);\n}\n\nint CT_Opt_Player_Key (int k, wchar unichar, CTab_t *tab, CTabPage_t *page) {\n\treturn Settings_Key(&settplayer, k, unichar);\n}\n\nvoid OnShow_SettPlayer(void) { Settings_OnShow(&settplayer); }\n\nqbool CT_Opt_Player_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn Settings_Mouse_Event(&settplayer, ms);\n}\n\n\n//=============================================================================\n// <BINDS>\n\nextern cvar_t in_raw, in_m_smooth, m_rate, in_m_os_parameters;\nconst char* in_raw_enum[] = { \"off\", \"on\" };\nconst char* in_m_os_parameters_enum[] = { \"off\", \"Keep accel settings\", \"Keep speed settings\", \"Keep all settings\" };\n\nvoid Menu_Input_Restart(void) { Cbuf_AddText(\"in_restart\\n\"); }\n\nsettings_page settbinds;\n\nvoid CT_Opt_Binds_Draw (int x2, int y2, int w, int h, CTab_t *tab, CTabPage_t *page) {\n\tSettings_Draw(x2, y2, w, h, &settbinds);\n}\n\nint CT_Opt_Binds_Key (int k, wchar unichar, CTab_t *tab, CTabPage_t *page) {\n\treturn Settings_Key(&settbinds, k, unichar);\n}\n\nvoid OnShow_SettBinds(void) { Settings_OnShow(&settbinds); }\n\nqbool CT_Opt_Binds_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn Settings_Mouse_Event(&settbinds, ms);\n}\n\n// </BINDS>\n//=============================================================================\n\n\n\n//=============================================================================\n// <FPS>\nextern cvar_t v_bonusflash;\nextern cvar_t cl_rocket2grenade;\nextern cvar_t v_damagecshift;\nextern cvar_t r_fastsky;\nextern cvar_t r_drawflame;\nextern cvar_t gl_simpleitems;\nextern cvar_t gl_simpleitems_orientation;\nextern cvar_t gl_part_inferno;\nextern cvar_t amf_lightning;\nextern cvar_t r_drawflat;\n\nconst char* explosiontype_enum[] =\n{ \"fire + sparks\", \"fire only\", \"teleport\", \"blood\", \"big blood\", \"dbl gunshot\", \"blob effect\", \"big explosion\", \"plasma\", \"sparks\", \"off\" };\n\nconst char* muzzleflashes_enum[] =\n{ \"off\", \"on\", \"own off\" };\n\nconst char* outline_enum[] =\n{ \"off\", \"models\", \"world\", \"models+world\" };\n\nconst char* simpleitemsorientation_enum[] =\n{\"Parallel upright\", \"Facing upright\", \"Parallel\", \"Oriented\"};\n\nconst char* deadbodyfilter_enum[] =\n{ \"off\", \"fast\", \"instant\", \"DM off, TF on\" };\n\nconst char* rocketmodel_enum[] =\n{ \"rocket\", \"grenade\" };\n\nconst char* rockettrail_enum[] =\n{ \"off\", \"normal\", \"grenade\", \"alt normal\", \"slight blood\", \"big blood\", \"tracer 1\", \"tracer 2\", \"plasma\", \"lavaball\", \"fuel rod\", \"plasma 2\", \"alt normal 2\" };\n\nconst char* powerupglow_enum[] =\n{ \"off\", \"on\", \"own off\" };\n\nconst char* grenadetrail_enum[] =\n{ \"off\", \"normal\", \"grenade\", \"alt normal\", \"slight blood\", \"big blood\", \"tracer 1\", \"tracer 2\", \"plasma\", \"lavaball\", \"fuel rod\", \"plasma 2\", \"alt normal 2\" };\n\nextern cvar_t cl_maxfps;\nconst char* FpslimitRead(void) {\n\tswitch ((int) cl_maxfps.value) {\n\tcase 0: return \"no limit\";\n\tcase 72: return \"72 (old std)\";\n\tcase 77: return \"77 (standard)\";\n\tdefault: return \"custom\";\n\t}\n}\nvoid FpslimitToggle(qbool back) {\n\tif (back) {\n\t\tswitch ((int) cl_maxfps.value) {\n\t\tcase 0: Cvar_SetValue(&cl_maxfps, 77); return;\n\t\tcase 72: Cvar_SetValue(&cl_maxfps, 0); return;\n\t\tcase 77: Cvar_SetValue(&cl_maxfps, 72); return;\n\t\t}\n\t} else {\n\t\tswitch ((int) cl_maxfps.value) {\n\t\tcase 0: Cvar_SetValue(&cl_maxfps, 72); return;\n\t\tcase 72: Cvar_SetValue(&cl_maxfps, 77); return;\n\t\tcase 77: Cvar_SetValue(&cl_maxfps, 0); return;\n\t\t}\n\t}\n}\n\nconst char* gl_max_size_enum[] = {\n\t\"low\", \"1\", \"medium\", \"8\", \"high\", \"256\", \"max\", \"2048\"\n};\n\nextern cvar_t gl_texturemode;\nconst char* gl_texturemode_enum[] = {\n\t\"very low\", \"GL_NEAREST_MIPMAP_NEAREST\",\n\t\"low\", \"GL_LINEAR\",\n\t\"medium\", \"GL_LINEAR_MIPMAP_NEAREST\",\n\t\"high\", \"GL_LINEAR_MIPMAP_LINEAR\",\n\t\"very high\", \"GL_NEAREST\"\n};\n\nconst char* vid_software_palette_enum[] = {\n\t\"hardware\", \"0\",\n\t\"shader\", \"1\"\n};\n\n#ifdef EZ_MULTIPLE_RENDERERS\nconst char* vid_renderer_enum[] = {\n\t\"classic\", \"0\",\n\t\"modern\", \"1\"\n};\n#endif\n\nconst char* vid_framebuffer_enum[] = {\n\t\"disabled\", \"0\",\n\t\"enabled\", \"1\",\n\t\"enabled (separate scene & hud)\", \"2\"\n};\n\nsettings_page settfps;\n\nvoid CT_Opt_FPS_Draw (int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n\tSettings_Draw(x, y, w, h, &settfps);\n}\n\nint CT_Opt_FPS_Key (int k, wchar unichar, CTab_t *tab, CTabPage_t *page) {\n\treturn Settings_Key(&settfps, k, unichar);\n}\n\nvoid OnShow_SettFPS(void) { Settings_OnShow(&settfps); }\n\nqbool CT_Opt_FPS_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn Settings_Mouse_Event(&settfps, ms);\n}\n\n\n// </FPS>\n//=============================================================================\n\n\n//=============================================================================\n// <SYSTEM>\n\n// these variables serve as a temporary storage for user selected settings\n// they get initialized with current settings when the page is showed\ntypedef struct menu_video_settings_s {\n\tint res;\n\tint bpp;\n\tqbool usedesktopres;\n\tqbool fullscreen;\n} menu_system_settings_t;\nqbool mss_askmode = false;\n\n// here we store the configuration that user selected in menu\nmenu_system_settings_t mss_selected;\n\n// here we store the current video config in case the new video settings weren't successfull\nmenu_system_settings_t mss_previous;\n\n// will apply given video settings\nstatic void ApplyVideoSettings(const menu_system_settings_t *s)\n{\n\tconst SDL_DisplayMode *current;\n\n\tcurrent = VID_GetDisplayMode(s->res);\n\tif (current == NULL) {\n\t\treturn;\n\t}\n\n\tCvar_SetValue(&vid_width, current->w);\n\tCvar_SetValue(&vid_height, current->h);\n\tCvar_SetValue(&r_displayRefresh,current->refresh_rate);\n\tCvar_SetValue(&r_colorbits, s->bpp);\n\tCvar_SetValue(&r_fullscreen, s->fullscreen);\n\tCbuf_AddText(\"vid_restart\\n\");\n\tCom_DPrintf(\"askmode: %s\\n\", mss_askmode ? \"on\" : \"off\");\n}\n\n// will store current video settings into the given structure\nstatic void StoreCurrentVideoSettings(menu_system_settings_t *out)\n{\n\tout->usedesktopres = vid_usedesktopres.integer ? true : false;\n\tout->res = VID_GetCurrentModeIndex();\n\tout->bpp = (int) r_colorbits.value;\n\tout->fullscreen = (int) r_fullscreen.value;\n}\n\n// performed when user hits the \"apply\" button\nvoid VideoApplySettings (void)\n{\n\tStoreCurrentVideoSettings(&mss_previous);\n\n\tApplyVideoSettings(&mss_selected);\n\n\tmss_askmode = true;\n}\n\n// performed when user hits the \"apply\" button\nvoid RendererRestart (void)\n{\n\tCbuf_AddText(\"vid_restart\\n\");\n}\n\n// two possible results of the \"keep these video settings?\" dialogue\nstatic void KeepNewVideoSettings (void) { mss_askmode = false; }\nstatic void CancelNewVideoSettings (void) {\n\tmss_askmode = false;\n\tApplyVideoSettings(&mss_previous);\n}\n\n\nconst char* BitDepthRead(void)\n{\n\treturn mss_selected.bpp == 32 ? \"32 bit\" : mss_selected.bpp == 16 ? \"16 bit\" : \"use desktop settings\";\n}\n\nconst char* ResolutionRead(void)\n{\n\tstatic char buf[64];\n\tconst SDL_DisplayMode *mode;\n\n\tmode = VID_GetDisplayMode(mss_selected.res);\n\tif (mode == NULL) {\n\t\treturn \"\";\n\t}\n\n\tsnprintf(buf, sizeof(buf), \"%dx%d@%dHz\", mode->w, mode->h, mode->refresh_rate);\n\t\t\n\treturn buf;\n}\n\nconst char* FullScreenRead(void)\n{\n\treturn mss_selected.fullscreen ? \"on\" : \"off\";\n}\n\nvoid ResolutionToggle(qbool back)\n{\n\tint count = VID_GetModeIndexCount();\n\tif (back) {\n\t\tmss_selected.res--;\n\t} else {\n\t\t mss_selected.res++;\n\t}\n\n\tmss_selected.res = (mss_selected.res + count) % count;\n}\n\nvoid BitDepthToggle(qbool back) {\n\tif (back) {\n\t\tswitch (mss_selected.bpp) {\n\t\tcase 0: mss_selected.bpp = 32; return;\n\t\tcase 16: mss_selected.bpp = 0; return;\n\t\tdefault: mss_selected.bpp = 16; return;\n\t\t}\n\t} else {\n\t\tswitch (mss_selected.bpp) {\n\t\tcase 0: mss_selected.bpp = 16; return;\n\t\tcase 16: mss_selected.bpp = 32; return;\n\t\tcase 32: mss_selected.bpp = 0; return;\n\t\t}\n\t}\n}\nvoid FullScreenToggle(qbool back) { mss_selected.fullscreen = mss_selected.fullscreen ? 0 : 1; }\n\nsettings_page settsystem;\n\nvoid CT_Opt_System_Draw (int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n\t#define ASKBOXWIDTH 300\n\tif(mss_askmode)\n\t{\n\t\tUI_DrawBox((w-ASKBOXWIDTH)/2, h/2 - 16, ASKBOXWIDTH, 32);\n\t\tUI_Print_Center((w-ASKBOXWIDTH)/2, h/2 - 8, ASKBOXWIDTH, \"Do you wish to keep these settings?\", false);\n\t\tUI_Print_Center((w-ASKBOXWIDTH)/2, h/2, ASKBOXWIDTH, \"(y/n)\", true);\n\t}\n\telse\n\t{\n\t\tSettings_Draw(x,y,w,h, &settsystem);\n\t}\n}\n\nint CT_Opt_System_Key (int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n\tif (mss_askmode)\n\t{\n\n\t\tif (key == 'y' || key == K_ENTER)\n\t\t{\n\t\t\tKeepNewVideoSettings();\n\t\t}\n\t\telse if(key == 'n' || key == K_ESCAPE)\n\t\t{\n\t\t\tCancelNewVideoSettings();\n\t\t}\n\t\treturn true;\n\t}\n\telse\n\t{\n\t\treturn Settings_Key(&settsystem, key, unichar);\n\t}\n}\n\nvoid OnShow_SettSystem(void)\n{\n\tStoreCurrentVideoSettings(&mss_selected);\n\tSettings_OnShow(&settsystem);\n}\n\nqbool CT_Opt_System_Mouse_Event(const mouse_state_t *ms)\n{\n\treturn Settings_Mouse_Event(&settsystem, ms);\n}\n\n// </SYSTEM>\n\n// *********\n// <CONFIG>\n\n// Menu Options Config Page Mode\n\nfilelist_t configs_filelist;\nCEditBox filenameeb;\n\nenum { MOCPM_SETTINGS, MOCPM_CHOOSECONFIG, MOCPM_CHOOSESCRIPT, MOCPM_ENTERFILENAME } MOpt_configpage_mode = MOCPM_SETTINGS;\n\nextern cvar_t cfg_backup, cfg_save_aliases, cfg_save_binds, cfg_save_cmdline,\n\tcfg_save_cmds, cfg_save_cvars, cfg_save_unchanged, cfg_save_userinfo, cfg_use_home, cfg_save_onquit, cfg_use_gamedir, cfg_legacy_exec;\n\nvoid MOpt_ImportConfig(void) {\n\tMOpt_configpage_mode = MOCPM_CHOOSECONFIG;\n\t\n\t// hope few doubled trinary operator won't hurt your brains\n\tif (cfg_use_home.integer)\n\t\tFL_SetCurrentDir(&configs_filelist, (cfg_use_gamedir.integer) ? va(\"%s/%s\", com_homedir, (strcmp(com_gamedirfile, \"qw\") == 0) ? \"\" : com_gamedirfile) : com_homedir);\n    else\n\t\tFL_SetCurrentDir(&configs_filelist, (cfg_use_gamedir.integer) ? va(\"%s/%s/configs\", com_basedir, (strcmp(com_gamedirfile, \"qw\") == 0) ? \"ezquake\" : com_gamedirfile) : va(\"%s/ezquake/configs\", com_basedir));\n}\nvoid MOpt_ExportConfig(void) {\n\tMOpt_configpage_mode = MOCPM_ENTERFILENAME;\n\tfilenameeb.text[0] = 0;\n\tfilenameeb.pos = 0;\n}\n\nvoid MOpt_LoadScript(void) {\n\tMOpt_configpage_mode = MOCPM_CHOOSESCRIPT;\n\tFL_SetCurrentDir(&configs_filelist, \"./ezquake/cfg\");\n}\n\nvoid MOpt_CfgSaveAllOn(void) {\n\tS_LocalSound(\"misc/basekey.wav\");\n\tCvar_SetValue(&cfg_backup, 1);\n\tCvar_SetValue(&cfg_legacy_exec, 1);\n\tCvar_SetValue(&cfg_save_aliases, 1);\n\tCvar_SetValue(&cfg_save_binds, 1);\n\tCvar_SetValue(&cfg_save_cmdline, 1);\n\tCvar_SetValue(&cfg_save_cmds, 1);\n\tCvar_SetValue(&cfg_save_cvars, 1);\n\tCvar_SetValue(&cfg_save_unchanged, 1);\n\tCvar_SetValue(&cfg_save_userinfo, 2);\n}\n\nconst char* MOpt_legacywrite_enum[] = { \"off\", \"non-qw dir frontend.cfg\", \"also config.cfg\", \"non-qw config.cfg\" };\nconst char* MOpt_userinfo_enum[] = { \"off\", \"all but player\", \"all\" };\n\nvoid MOpt_LoadCfg(void) {\n\tS_LocalSound(\"misc/basekey.wav\");\n\tCbuf_AddText(\"cfg_load\\n\");\n}\nvoid MOpt_SaveCfg(void) { \n\tS_LocalSound(\"doors/runeuse.wav\");\n\tCbuf_AddText(\"cfg_save\\n\");\n}\n\nsettings_page settconfig;\n\n#define INPUTBOXWIDTH 300\n#define INPUTBOXHEIGHT 48\n\nvoid MOpt_FilenameInputBoxDraw(int x, int y, int w, int h)\n{\n\tUI_DrawBox(x + (w-INPUTBOXWIDTH) / 2, y + (h-INPUTBOXHEIGHT) / 2, INPUTBOXWIDTH, INPUTBOXHEIGHT);\n\tUI_Print_Center(x, y + h/2 - 8, w, \"Enter the config file name\", false);\n\tCEditBox_Draw(&filenameeb, x + (w-INPUTBOXWIDTH)/2 + 16, y + h/2 + 8, true);\n}\n\nqbool MOpt_FileNameInputBoxKey(int key)\n{\n\tCEditBox_Key(&filenameeb, key, key);\n\treturn true;\n}\n\nchar *MOpt_FileNameInputBoxGetText(void)\n{\n\treturn filenameeb.text;\n}\n\nvoid CT_Opt_Config_Draw(int x, int y, int w, int h, CTab_t *tab, CTabPage_t *page)\n{\n\tswitch (MOpt_configpage_mode) {\n\tcase MOCPM_SETTINGS:\n\t\tSettings_Draw(x,y,w,h,&settconfig);\n\t\tbreak;\n\n\tcase MOCPM_CHOOSESCRIPT:\n\tcase MOCPM_CHOOSECONFIG:\n\t\tFL_Draw(&configs_filelist, x,y,w,h);\n\t\tbreak;\n\n\tcase MOCPM_ENTERFILENAME:\n\t\tMOpt_FilenameInputBoxDraw(x,y,w,h);\n\t\tbreak;\n\t}\n}\n\nint CT_Opt_Config_Key(int key, wchar unichar, CTab_t *tab, CTabPage_t *page)\n{\n\tswitch (MOpt_configpage_mode) {\n\tcase MOCPM_SETTINGS:\n\t\treturn Settings_Key(&settconfig, key, unichar);\n\t\tbreak;\n\n\tcase MOCPM_CHOOSECONFIG:\n\t\tif (key == K_ENTER || key == K_MOUSE1) {\n\t\t\tCbuf_AddText(va(\"cfg_load \\\"%s\\\"\\n\", COM_SkipPath(FL_GetCurrentEntry(&configs_filelist)->name)));\n\t\t\tMOpt_configpage_mode = MOCPM_SETTINGS;\n\t\t\treturn true;\n\t\t} else if (key == K_ESCAPE || key == K_MOUSE2) {\n\t\t\tMOpt_configpage_mode = MOCPM_SETTINGS;\n\t\t\treturn true;\n\t\t} else return FL_Key(&configs_filelist, key);\n\n\tcase MOCPM_CHOOSESCRIPT:\n\t\tif (key == K_ENTER || key == K_MOUSE1) {\n\t\t\tCbuf_AddText(va(\"exec \\\"cfg/%s\\\"\\n\", COM_SkipPath(FL_GetCurrentEntry(&configs_filelist)->name)));\n\t\t\tMOpt_configpage_mode = MOCPM_SETTINGS;\n\t\t\treturn true;\n\t\t} else if (key == K_ESCAPE || key == K_MOUSE2) {\n\t\t\tMOpt_configpage_mode = MOCPM_SETTINGS;\n\t\t\treturn true;\n\t\t} else return FL_Key(&configs_filelist, key);\n\n\tcase MOCPM_ENTERFILENAME:\n\t\tif (key == K_ENTER || key == K_MOUSE1) {\n\t\t\tCbuf_AddText(va(\"cfg_save \\\"%s\\\"\\n\", MOpt_FileNameInputBoxGetText()));\n\t\t\tMOpt_configpage_mode = MOCPM_SETTINGS;\n\t\t\treturn true;\n        } else if (key == K_ESCAPE || key == K_MOUSE2) {\n\t\t\tMOpt_configpage_mode = MOCPM_SETTINGS;\n\t\t\treturn true;\n\t\t} else return MOpt_FileNameInputBoxKey(key);\n\t}\n\n\treturn false;\n}\n\nvoid OnShow_SettConfig(void) { Settings_OnShow(&settconfig); }\n\nqbool CT_Opt_Config_Mouse_Event(const mouse_state_t *ms)\n{\n    if (MOpt_configpage_mode == MOCPM_CHOOSECONFIG || MOpt_configpage_mode == MOCPM_CHOOSESCRIPT) {\n        if (FL_Mouse_Event(&configs_filelist, ms))\n            return true;\n        else if (ms->button_up == 1 || ms->button_up == 2)\n            return CT_Opt_Config_Key(K_MOUSE1 - 1 + ms->button_up, 0, &options_tab, options_tab.pages + OPTPG_CONFIG);\n\n        return true;\n    }\n    else\n    {\n        return Settings_Mouse_Event(&settconfig, ms);\n    }\n}\n\n\n// </CONFIG>\n// *********\n\nCTabPage_Handlers_t options_misc_handlers = {\n\tCT_Opt_Settings_Draw,\n\tCT_Opt_Settings_Key,\n\tOnShow_SettMain,\n\tCT_Opt_Settings_Mouse_Event\n};\n\nCTabPage_Handlers_t options_player_handlers = {\n\tCT_Opt_Player_Draw,\n\tCT_Opt_Player_Key,\n\tOnShow_SettPlayer,\n\tCT_Opt_Player_Mouse_Event\n};\n\nCTabPage_Handlers_t options_graphics_handlers = {\n\tCT_Opt_FPS_Draw,\n\tCT_Opt_FPS_Key,\n\tOnShow_SettFPS,\n\tCT_Opt_FPS_Mouse_Event\n};\n\nCTabPage_Handlers_t options_view_handlers = {\n\tCT_Opt_View_Draw,\n\tCT_Opt_View_Key,\n\tOnShow_SettView,\n\tCT_Opt_View_Mouse_Event\n};\n\nCTabPage_Handlers_t options_controls_handlers = {\n\tCT_Opt_Binds_Draw,\n\tCT_Opt_Binds_Key,\n\tOnShow_SettBinds,\n\tCT_Opt_Binds_Mouse_Event\n};\n\nCTabPage_Handlers_t options_system_handlers = {\n\tCT_Opt_System_Draw,\n\tCT_Opt_System_Key,\n\tOnShow_SettSystem,\n\tCT_Opt_System_Mouse_Event\n};\n\nCTabPage_Handlers_t options_config_handlers = {\n\tCT_Opt_Config_Draw,\n\tCT_Opt_Config_Key,\n\tOnShow_SettConfig,\n\tCT_Opt_Config_Mouse_Event\n};\n\nvoid Menu_Options_Key(int key, wchar unichar) {\n    int handled = CTab_Key(&options_tab, key, unichar);\n\toptions_unichar = unichar;\n\n\tif (!handled && (key == K_ESCAPE || key == K_MOUSE2))\n\t\tM_Menu_Main_f();\n}\n\nvoid Menu_Options_Draw(void) {\n\tint x, y, w, h;\n\n\tM_Unscale_Menu();\n\n    // this will add top, left and bottom padding\n    // right padding is not added because it causes annoying scrollbar behaviour\n    // when mouse gets off the scrollbar to the right side of it\n\tw = vid.width - OPTPADDING; // here used to be a limit to 512x... size\n\th = vid.height - OPTPADDING*2;\n\tx = OPTPADDING;\n\ty = OPTPADDING;\n\n\tCTab_Draw(&options_tab, x, y, w, h);\n}\n\n\n// PLAYER TAB\nsetting settplayer_arr[] = {\n\tADDSET_BOOL\t\t(\"Advanced Options\", menu_advanced),\n\tADDSET_SEPARATOR(\"Player Settings\"),\n\tADDSET_STRING\t(\"Name\", name),\n\tADDSET_STRING\t(\"Teamchat Name\", cl_fakename),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_STRING\t(\"Teamchat Name Suffix\", cl_fakename_suffix),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_ENUM     (\"Gender\", gender, gender_enum),\n\tADDSET_STRING\t(\"Team\", team),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_SKIN\t\t(\"Skin\", skin),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_COLOR\t(\"Shirt Color\", topcolor),\n\tADDSET_COLOR\t(\"Pants Color\", bottomcolor),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_NUMBER\t(\"Fullbright Skins\", r_fullbrightSkins, 0, 1, 0.05),\n\tADDSET_ENUM    \t(\"Ruleset\", ruleset, ruleset_enum),\n\tADDSET_BASIC_SECTION(),\n\t\n\tADDSET_SEPARATOR(\"Weapon Handling\"),\n\tADDSET_CUSTOM\t(\"Gun Autoswitch\", AutoSWRead, AutoSWToggle, \"Switches to the weapon picked up if it is more powerful than what you're currently holding.\"),\n\tADDSET_BOOL\t\t(\"Gun Preselect\", cl_weaponpreselect),\n\tADDSET_BOOL\t\t(\"Gun Auto Hide\", cl_weaponhide),\n\t\n    ADDSET_SEPARATOR(\"Movement\"),\n\tADDSET_CUSTOM\t(\"Always Run\", AlwaysRunRead, AlwaysRunToggle, \"Maximum forward speed at all times.\"),\n\tADDSET_ADVANCED_SECTION(),\n    ADDSET_BOOL\t\t(\"Smart Jump\", cl_smartjump),\n\tADDSET_NAMED\t(\"Movement Scripts\", allow_scripts, allowscripts_enum),\n\tADDSET_BASIC_SECTION(),\n\t\n\tADDSET_SEPARATOR(\"Team Skin & Colors\"),\n\tADDSET_COLOR\t(\"Shirt Color\", cl_teamtopcolor),\n\tADDSET_COLOR\t(\"Pants Color\", cl_teambottomcolor),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_SKIN\t\t(\"Skin\", cl_teamskin),\n\tADDSET_NAMED\t(\"Force Skins\", teamforceskins, teamforceskins_enum),\n\tADDSET_SKIN\t\t(\"Quad Skin\", cl_teamquadskin),\n\tADDSET_SKIN\t\t(\"Pent Skin\", cl_teampentskin),\n\tADDSET_SKIN\t\t(\"Quad+Pent Skin\", cl_teambothskin),\n\tADDSET_BASIC_SECTION(),\n\t\n\tADDSET_SEPARATOR(\"Enemy Skin & Colors\"),\n\tADDSET_COLOR\t(\"Shirt Color\", cl_enemytopcolor),\n\tADDSET_COLOR\t(\"Pants Color\", cl_enemybottomcolor),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_SKIN\t\t(\"Skin\", cl_enemyskin),\n\tADDSET_NAMED\t(\"Force Skins\", enemyforceskins, enemyforceskins_enum),\n\tADDSET_SKIN\t\t(\"Quad Skin\", cl_enemyquadskin),\n\tADDSET_SKIN\t\t(\"Pent Skin\", cl_enemypentskin),\n\tADDSET_SKIN\t\t(\"Quad+Pent Skin\", cl_enemybothskin),\n\tADDSET_BASIC_SECTION(),\n};\n\n// GRAPHICS TAB\nsetting settfps_arr[] = {\n\tADDSET_BOOL\t\t(\"Advanced Options\", menu_advanced),\n\t\n\tADDSET_SEPARATOR(\"Presets\"),\n\tADDSET_CUSTOM\t(\"GFX Preset\", GFXPresetRead, GFXPresetToggle, \"Select different graphic presets.\"),\n\n\tADDSET_SEPARATOR(\"Field Of View\"),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_NUMBER(\"Draw Distance\", r_farclip, 4096, 8192, 4096),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_NUMBER\t(\"View Size (fov)\", scr_fov, 40, 140, 2),\n\tADDSET_NUMBER\t(\"Screen Size\", scr_viewsize, 30, 120, 5),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_NUMBER\t(\"Rollangle\", cl_rollangle, 0, 10, 1),\n\tADDSET_NUMBER\t(\"Rollspeed\", cl_rollspeed, 0, 30, 2),\n\tADDSET_BOOL\t\t(\"Gun Kick\", v_gunkick),\n\tADDSET_NUMBER\t(\"Kick Pitch\", v_kickpitch, 0, 10, 0.5),\n\tADDSET_NUMBER\t(\"Kick Roll\", v_kickroll, 0, 10, 0.5),\n\tADDSET_NUMBER\t(\"Kick Time\", v_kicktime, 0, 10, 0.5),\n\tADDSET_NUMBER\t(\"View Height\", v_viewheight, -7, 4, 0.5),\n\tADDSET_BASIC_SECTION(),\n\n\tADDSET_ADVANCED_SECTION(),\t\n\tADDSET_SEPARATOR(\"Textures\"),\n\tADDSET_BOOL\t\t(\"Luma\", gl_lumatextures),\n\tADDSET_ENUM \t(\"Detail\", gl_max_size, gl_max_size_enum),\n\tADDSET_NUMBER\t(\"Miptex\", gl_miptexLevel, 0, 3, 1),\n\tADDSET_BOOL\t\t(\"No Textures\", gl_textureless),\n\tADDSET_BASIC_SECTION(),\n\t\n\tADDSET_SEPARATOR(\"Player & Weapon Model\"),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL\t\t(\"Powerup Luma\", gl_powerupshells),\n\tADDSET_NUMBER\t(\"Weapon Opacity\", cl_drawgun, 0, 1, 0.05),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_NUMBER\t(\"Weapon Size\", r_viewmodelsize, 0.1, 1, 0.05),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_NUMBER\t(\"Weapon Shift\", r_viewmodeloffset, -10, 10, 1),\n\tADDSET_NAMED\t(\"Weapon Muzzleflashes\", cl_muzzleflash, muzzleflashes_enum),\n\tADDSET_NAMED\t(\"Outline\", gl_outline, outline_enum),\n\tADDSET_BASIC_SECTION(),\n\t\n\tADDSET_SEPARATOR(\"Environment\"),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL\t\t(\"Fullbright World\", r_fullbright),\n\tADDSET_BOOL\t\t(\"Simple Sky\", r_fastsky),\n\tADDSET_BOOL\t\t(\"Simple Walls\", r_drawflat),\n\tADDSET_BOOL\t\t(\"Simple Turbs\", r_fastturb),\n\tADDSET_BOOL\t\t(\"Simple Items\", gl_simpleitems),\n\tADDSET_NAMED\t(\"Simple Items Orientation\", gl_simpleitems_orientation, simpleitemsorientation_enum),\n\tADDSET_BOOL\t\t(\"Draw Flame\", r_drawflame),\n\tADDSET_BOOL\t\t(\"Backpack Filter\", cl_backpackfilter),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_BOOL\t\t(\"Gib Filter\", cl_gibfilter),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_NAMED\t(\"Dead Body Filter\", cl_deadbodyfilter, deadbodyfilter_enum),\n\tADDSET_BASIC_SECTION(),\n\t\n\tADDSET_SEPARATOR(\"Projectiles\"),\n\tADDSET_NAMED\t(\"Explosion Type\", r_explosiontype, explosiontype_enum),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_NAMED\t(\"Rocket Model\", cl_rocket2grenade, rocketmodel_enum),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_NAMED\t(\"Rocket Trail\", r_rockettrail, rockettrail_enum),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_NUMBER\t(\"Rocket Light\", r_rocketlight, 0, 1, 0.05),\n\tADDSET_NAMED\t(\"Grenade Trail\", r_grenadetrail, grenadetrail_enum),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_NUMBER\t(\"Fakeshaft\", cl_fakeshaft, 0, 1, 0.05),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL\t\t(\"Hide Nails\", amf_hidenails),\n\tADDSET_BOOL\t\t(\"Hide Rockets\", amf_hiderockets),\n\tADDSET_BASIC_SECTION(),\n\n\tADDSET_SEPARATOR(\"Lighting\"),\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tADDSET_BOOL\t\t(\"GL Bloom\", r_bloom),\n#endif\n\tADDSET_NAMED\t(\"Powerup Glow\", r_powerupglow, powerupglow_enum),\n\tADDSET_NUMBER\t(\"Damage Flash\", v_damagecshift, 0, 1, 0.1),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL\t\t(\"Pickup Flash\", v_bonusflash),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_BOOL\t\t(\"Colored Lights\", gl_colorlights),\n\tADDSET_BOOL\t\t(\"Fast Lights\", gl_flashblend),\n\tADDSET_ENUM     (\"Dynamic Lights\", r_dynamic, lighting_enum),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL\t\t(\"Darken Map\", gl_lightmode),\n\tADDSET_BOOL\t\t(\"Particle Shaft\", amf_lightning),\n\tADDSET_BASIC_SECTION(),\n};\n\n// VIEW TAB\nsetting settview_arr[] = {\n\tADDSET_BOOL\t\t(\"Advanced Options\", menu_advanced),\n\tADDSET_SEPARATOR(\"Head Up Display\"),\n\tADDSET_NAMED\t(\"HUD Type\", scr_newHud, hud_enum),\n\tADDSET_NUMBER\t(\"Crosshair\", crosshair, 0, 7, 1),\n\tADDSET_NUMBER\t(\"Crosshair Size\", crosshairsize, 0.2, 3, 0.2),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_NUMBER\t(\"Crosshair Alpha\", crosshairalpha, 0.1, 1, 0.1),\n\tADDSET_NAMED\t(\"Overhead Name\", scr_autoid, scrautoid_enum),\n\tADDSET_BASIC_SECTION(),\n\n\tADDSET_SEPARATOR(\"New HUD\"),\n\tADDSET_BOOLLATE\t(\"Gameclock\", hud_gameclock_show),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOLLATE (\"Big Gameclock\", hud_gameclock_big),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_BOOL\t\t(\"Teaminfo Table\", scr_teaminfo),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOLLATE (\"Own Frags Announcer\", hud_ownfrags_show),\n\tADDSET_BOOLLATE (\"Teamholdbar\", hud_teamholdbar_show),\n\tADDSET_BOOLLATE (\"Teamholdinfo\", hud_teamholdinfo_show),\n\tADDSET_BOOLLATE (\"Clock\", hud_clock_show),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_BOOLLATE (\"FPS\", hud_fps_show),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOLLATE (\"Radar\", hud_radar_show),\n\tADDSET_BASIC_SECTION(),\n\n\tADDSET_SEPARATOR(\"Quake Classic HUD\"),\n\tADDSET_BOOL\t\t(\"Status Bar\", cl_sbar),\n\tADDSET_BOOL\t\t(\"HUD Left\", cl_hudswap),\n\tADDSET_BOOL\t\t(\"Show FPS\", show_fps),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL\t\t(\"Show Clock\", scr_clock),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_BOOL\t\t(\"Show Gameclock\", scr_gameclock),\n\n\tADDSET_SEPARATOR(\"Tracker Messages\"),\n\tADDSET_NUMBER\t(\"Messages\", amf_tracker_messages, 0, 10, 1),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL\t\t(\"Flags\", amf_tracker_flags),\n\tADDSET_NAMED\t(\"Frags\", amf_tracker_frags, amf_tracker_frags_enum),\n\tADDSET_BOOL\t\t(\"Streaks\", amf_tracker_streaks),\n\tADDSET_NUMBER\t(\"Time\", amf_tracker_time, 0.5, 6, 0.5),\n\tADDSET_NUMBER\t(\"Scale\", amf_tracker_scale, 0.1, 2, 0.1),\n\tADDSET_BOOL\t\t(\"Use Images\", cl_useimagesinfraglog),\n\tADDSET_BOOL\t\t(\"Align Right\", amf_tracker_align_right),\n\tADDSET_BASIC_SECTION(),\n\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_SEPARATOR(\"Message Filtering\"),\n\tADDSET_NAMED\t(\"Ignore Opponents\", ignore_opponents, ignoreopponents_enum),\n\tADDSET_NAMED\t(\"Ignore Spectators\", ignore_spec, ignorespec_enum),\n\tADDSET_BOOL\t\t(\"Ignore Qizmo Observers\", ignore_qizmo_spec),\n\tADDSET_ENUM \t(\"Ignore Flood\", ignore_flood, ignore_flood_enum),\n\tADDSET_NUMBER \t(\"Ignore Flood Duration\", ignore_flood_duration, 0, 10, 1),\n\tADDSET_NAMED\t(\"Message Filtering\", msg_filter, msgfilter_enum),\n\n\tADDSET_SEPARATOR(\"Outgoing Filtering\"),\n\tADDSET_BOOL\t\t(\"Filter Colored Text\", cl_sayfilter_coloredtext),\n\tADDSET_BOOL\t\t(\"Send #u/#c Versions\", cl_sayfilter_sendboth),\n\n\tADDSET_SEPARATOR(\"Console Options\"),\n\tADDSET_BOOL\t\t(\"Timestamps\", con_timestamps),\n\tADDSET_NAMED\t(\"Chat Mode\", cl_chatmode, cl_chatmode_enum),\n\tADDSET_NAMED\t(\"Completion Format\", con_completion_format, con_completion_format_enum),\n\tADDSET_NUMBER\t(\"Size\", scr_consize, 0, 1, 0.1),\n\tADDSET_NUMBER\t(\"Speed\", scr_conspeed, 1000, 9999, 1000),\n\tADDSET_NUMBER\t(\"Alpha\", scr_conalpha, 0, 1, 0.1),\n\tADDSET_NAMED\t(\"Map Preview\", scr_conback, scr_conback_enum),\n\tADDSET_NAMED\t(\"Fun Chars Mode\", con_funchars_mode, funcharsmode_enum),\n\tADDSET_NAMED\t(\"Colored Text\", scr_coloredText, coloredtext_enum),\n\tADDSET_NUMBER\t(\"Notify Time\", con_notifytime, 0.5, 16, 0.5),\n\tADDSET_NUMBER\t(\"Notify Lines\", _con_notifylines, 0, 16, 1),\n\tADDSET_BOOL\t\t(\"Confirm Quit\", cl_confirmquit),\n\n\tADDSET_SEPARATOR(\"Demo & Observing\"),\n\tADDSET_BOOL\t\t(\"Chasecam\", cl_chasecam),\n\n\tADDSET_BASIC_SECTION(),\n\tADDSET_SEPARATOR(\"Demo Playback\"),\n\tADDSET_NUMBER\t(\"Multiview\", cl_multiview, 0, 4, 1),\n\tADDSET_BOOL\t\t(\"Display HUD\", cl_mvdisplayhud),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL\t\t(\"HUD Flip\", cl_mvhudflip),\n\tADDSET_BOOL\t\t(\"HUD Vertical\", cl_mvhudvertical),\n\tADDSET_BOOL\t\t(\"Inset View\", cl_mvinset),\n\tADDSET_BOOL\t\t(\"Inset HUD\", cl_mvinsethud),\n\tADDSET_BOOL\t\t(\"Inset Cross\", cl_mvinsetcrosshair),\n\tADDSET_BASIC_SECTION(),\n\t\n\tADDSET_SEPARATOR(\"Multiview Demos\"),\n\tADDSET_ACTION\t(\"Toggle Autotrack\", CL_Autotrack_f, \"Toggle auto-tracking of the best player\"),\n\tADDSET_NAMED\t(\"Autohud\", mvd_autohud, mvdautohud_enum),\n\tADDSET_NAMED\t(\"Autotrack Type\", mvd_autotrack, mvdautotrack_enum),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL\t\t(\"Autotrack Lock Team\", mvd_autotrack_lockteam),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_BOOL\t\t(\"Moreinfo\", mvd_moreinfo),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL     (\"Status\", mvd_status),\n\tADDSET_BASIC_SECTION(),\n};\n\n// CONTROLS TAB\n// please try to put mostly binds in here\nsetting settbinds_arr[] = {\n\tADDSET_BOOL\t\t(\"Advanced Options\", menu_advanced),\n\t\n\tADDSET_SEPARATOR(\"Mouse Settings\"),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL\t\t(\"Freelook\", freelook),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_NUMBER\t(\"Sensitivity\", sensitivity, 1, 20, 0.25), // My sens is 16, so maybe some people have it up to 20?\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_NUMBER\t(\"Menu Mouse Sensitivity\", cursor_sensitivity, 0.10 , 3, 0.10),\n\tADDSET_NUMBER\t(\"Acceleration\", m_accel, 0, 1, 0.1),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_CUSTOM\t(\"Invert Mouse\", InvertMouseRead, InvertMouseToggle, \"Inverts the Y axis.\"),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_STRING   (\"X-axis Sensitivity\", m_yaw),\n\tADDSET_STRING   (\"Y-axis Sensitivity\", m_pitch),\n\tADDSET_NAMED    (\"Raw Mouse Input\", in_raw, in_raw_enum),\n\tADDSET_BASIC_SECTION(),\n\n\tADDSET_SEPARATOR(\"Movement\"),\n\tADDSET_BIND(\"Attack\", \"+attack\"),\n\tADDSET_BIND(\"Jump/Swim up\", \"+jump\"),\n\tADDSET_BIND(\"Move Forward\", \"+forward\"),\n\tADDSET_BIND(\"Move Backward\", \"+back\"),\n\tADDSET_BIND(\"Strafe Left\", \"+moveleft\"),\n\tADDSET_BIND(\"Strafe Right\", \"+moveright\"),\n\tADDSET_BIND(\"Turn Left\", \"+left\"),\n\tADDSET_BIND(\"Turn Right\", \"+right\"),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BIND(\"Swim Up\", \"+moveup\"),\n\tADDSET_BIND(\"Swim Down\", \"+movedown\"),\n\tADDSET_BIND(\"Zoom In/Out\", \"+zoom\"),\n\tADDSET_BASIC_SECTION(),\n\n\tADDSET_SEPARATOR(\"Weapons\"),\n\tADDSET_BIND(\"Previous Weapon\", \"weapon 12\"),\n\tADDSET_BIND(\"Next Weapon\", \"weapon 10\"),\n\tADDSET_BIND(\"Axe\", \"weapon 1\"),\n\tADDSET_BIND(\"Shotgun\", \"weapon 2\"),\n\tADDSET_BIND(\"Super Shotgun\", \"weapon 3\"),\n\tADDSET_BIND(\"Nailgun\", \"weapon 4\"),\n\tADDSET_BIND(\"Super Nailgun\", \"weapon 5\"),\n\tADDSET_BIND(\"Grenade Launcher\", \"weapon 6\"),\n\tADDSET_BIND(\"Rocket Launcher\", \"weapon 7\"),\n\tADDSET_BIND(\"Thunderbolt\", \"weapon 8\"),\n\n\tADDSET_SEPARATOR(\"Chat settings\"),\n\tADDSET_BIND\t(\"Chat\", \"messagemode\"),\n\tADDSET_BIND\t(\"Teamchat\", \"messagemode2\"),\n\n\tADDSET_SEPARATOR(\"Miscellaneous\"),\n\tADDSET_BIND(\"Show Scores\", \"+showscores\"),\n\tADDSET_BIND(\"Screenshot\", \"screenshot\"),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BIND(\"Pause\", \"pause\"),\n\tADDSET_BIND(\"Quit\", \"quit\"),\n\tADDSET_BIND(\"Proxy Menu\", \"toggleproxymenu\"),\n\tADDSET_BASIC_SECTION(),\n\n\tADDSET_SEPARATOR(\"Demo & Spec\"),\n\tADDSET_BIND(\"Demo Controls\", \"demo_controls\"),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BIND(\"Autotrack\", \"autotrack\"),\n\tADDSET_BIND(\"Play\", \"cl_demospeed 1;echo Playing demo.\"),\n\tADDSET_BIND(\"Stop\", \"disconnect\"),\n\tADDSET_BIND(\"Pause\", \"cl_demospeed 0;echo Demo paused.\"),\n\tADDSET_BIND(\"Fast Forward\", \"cl_demospeed 5;echo Demo paused.\"),\n\tADDSET_BASIC_SECTION(),\n\n\tADDSET_SEPARATOR(\"Teamplay\"),\n\tADDSET_BIND(\"Report Status\", \"tp_msgreport\"),\n\tADDSET_BIND(\"Lost Location\", \"tp_msglost\"),\n\tADDSET_BIND(\"Location Safe\", \"tp_msgsafe\"),\n\tADDSET_BIND(\"Point At Item\", \"tp_msgpoint\"),\n\tADDSET_BIND(\"Took Item\", \"tp_msgtook\"),\n\tADDSET_BIND(\"Need Items\", \"tp_msgneed\"),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BIND(\"Yes/Ok\", \"tp_msgyesok\"),\n\tADDSET_BIND(\"Coming From Location\", \"tp_msgcoming\"),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_BIND(\"Help Location\", \"tp_msghelp\"),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BIND(\"Get Quad\", \"tp_msggetquad\"),\n\tADDSET_BIND(\"Get Pent\", \"tp_msggetpent\"),\n\tADDSET_BIND(\"Enemy Quad Dead\", \"tp_msgquaddead\"),\n\tADDSET_BIND(\"Enemy Has Powerup\", \"tp_msgenemypwr\"),\n\tADDSET_BIND(\"Trick At Location\", \"tp_msgtrick\"),\n\tADDSET_BIND(\"Replace At Location\", \"tp_msgreplace\"),\n\tADDSET_BIND(\"You Take Item\", \"tp_msgutake\"),\n\tADDSET_BIND(\"Waiting\", \"tp_msgwaiting\"),\n\tADDSET_BIND(\"Enemy Slipped\", \"tp_msgslipped\"),\n\tADDSET_BASIC_SECTION(),\n};\n\n// MISC TAB\nsetting settmisc_arr[] = {\n\tADDSET_BOOL\t\t(\"Advanced Options\", menu_advanced),\n\n\t//Match Tools\n\tADDSET_SEPARATOR(\"Match Tools\"),\n\tADDSET_BOOL\t\t(\"Auto Screenshot\", match_auto_sshot),\n\tADDSET_ENUM \t(\"Screenshot Format\", scr_sshot_format, scr_sshot_format_enum),\n\tADDSET_NAMED\t(\"Auto Record Demo\", match_auto_record, autorecord_enum),\n#ifdef _WIN32\n\tADDSET_ENUM     (\"Demo Format\", demo_format, demoformat_enum),\n#endif\n\tADDSET_NAMED\t(\"Auto Log Match\", match_auto_logconsole, autorecord_enum),\n\tADDSET_BOOL\t\t(\"Log Readable\", log_readable),\n\n\t//Paths\n\tADDSET_SEPARATOR(\"Paths\"),\n\tADDSET_NAMED    (\"Media Paths Type\", cl_mediaroot, mediaroot_enum),\n\tADDSET_STRING   (\"Screenshots Path\", scr_sshot_dir),\n\tADDSET_STRING\t(\"Demos Path\", demo_dir),\n\tADDSET_STRING   (\"Logs Path\", log_dir),\n\tADDSET_STRING\t(\"Qizmo Path\", qizmo_dir),\n\tADDSET_STRING\t(\"QWDTools Path\", qwdtools_dir),\n#ifdef _WIN32\n\tADDSET_ACTION\t(\"Set qw:// assoc.\", Sys_RegisterQWURLProtocol_f,\n\t\t\"Sets this application as the handler of qw:// URLs, so by double-clicking such links in your operating system, this client will open and connect to given address\"),\n#endif\n\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_SEPARATOR\t(\"Miscellaneous\"),\n\tADDSET_NAMED\t\t(\"Linear Interpolation\", cl_nolerp, cl_nolerp_enum),\n\tADDSET_BOOL\t\t\t(\"In-Game Menu\", menu_ingame),\n\tADDSET_BASIC_SECTION(),\n};\n\n// SYSTEM TAB\nsetting settsystem_arr[] = {\n\tADDSET_BOOL\t\t(\"Advanced Options\", menu_advanced),\n\n\t//Video\n\tADDSET_SEPARATOR(\"Video\"),\n\tADDSET_NUMBER\t(\"Gamma\", v_gamma, 0.3, 3.0, 0.1),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_ENUM\t\t(\"Gamma control\", vid_software_palette, vid_software_palette_enum),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_NUMBER\t(\"Contrast\", v_contrast, 1, 5, 0.1),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_NUMBER\t(\"Lightmap Intensity\", gl_modulate, 0.5, 3.0, 0.1),\n\tADDSET_BOOL\t\t(\"Clear Video Buffer\", gl_clear),\n\tADDSET_NUMBER\t(\"Anisotropy Filter\", gl_anisotropy, 0, 16, 1),\n\tADDSET_ENUM\t\t(\"Quality Mode\", gl_texturemode, gl_texturemode_enum),\n\tADDSET_BASIC_SECTION(),\n\n\tADDSET_SEPARATOR(\"Screen Settings\"),\n\tADDSET_BOOL(\"Use desktop resolution\", vid_usedesktopres),\n\tADDSET_CUSTOM(\"Resolution\", ResolutionRead, ResolutionToggle, \"Change your screen resolution.\"),\n\tADDSET_BOOL(\"Vertical Sync\", r_swapInterval),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL(\"Vsync Lag Fix\", vid_vsync_lag_fix),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_CUSTOM(\"Bit Depth\", BitDepthRead, BitDepthToggle, \"Choose 16bit or 32bit color mode for your screen.\"),\n\tADDSET_CUSTOM(\"Fullscreen\", FullScreenRead, FullScreenToggle, \"Toggle between fullscreen and windowed mode.\"),\n\tADDSET_ACTION(\"Apply Changes\", VideoApplySettings, \"Restarts the renderer and applies the selected resolution.\"),\n\n#ifdef EZ_MULTIPLE_RENDERERS\n\tADDSET_SEPARATOR(\"Renderer\"),\n\tADDSET_ENUM(\"Mode\", vid_renderer, vid_renderer_enum),\n\tADDSET_ACTION(\"Apply Changes\", RendererRestart, \"Restarts the renderer.\"),\n#endif\n\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_SEPARATOR(\"Framebuffer\"),\n\tADDSET_ENUM(\"Mode\", vid_framebuffer, vid_framebuffer_enum),\n\tADDSET_BOOL(\"HDR\", vid_framebuffer_hdr),\n\tADDSET_BOOL(\"HDR Tonemap\", vid_framebuffer_hdr_tonemap),\n\tADDSET_NUMBER(\"Scale\", vid_framebuffer_scale, 0.25, 2.0, 0.25),\n\tADDSET_NUMBER(\"Multisample\", vid_framebuffer_multisample, 0, 16, 1),\n\tADDSET_NUMBER(\"FXAA\", vid_framebuffer_fxaa, 0, 17, 1),\n\tADDSET_ACTION(\"Apply Changes\", RendererRestart, \"Restarts the renderer.\"),\n\tADDSET_BASIC_SECTION(),\n\n\t//Font\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_SEPARATOR(\"Font\"),\n\tADDSET_NUMBER(\"Width\", r_conwidth, 320, 2048, 8),\n\tADDSET_NUMBER(\"Height\", r_conheight, 240, 1538, 4),\n\tADDSET_BASIC_SECTION(),\n\n\t//Sound & Volume\n\tADDSET_SEPARATOR(\"Sound & Volume\"),\n\tADDSET_NUMBER\t(\"Primary Volume\", s_volume, 0, 1, 0.05),\n\tADDSET_BIND(\"Mute/Unmute\", \"mutesound\"),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL\t\t(\"Self Volume Levels\", cl_chatsound),\n\tADDSET_NUMBER\t(\"Chat Volume\", con_sound_mm1_volume, 0, 1, 0.1),\n\tADDSET_NUMBER\t(\"Team Chat Volume\", con_sound_mm2_volume, 0, 1, 0.1),\n\tADDSET_NUMBER\t(\"Spectator Volume\", con_sound_spec_volume, 0, 1, 0.1),\n\tADDSET_NUMBER\t(\"Other Volume\", con_sound_other_volume, 0, 1, 0.1),\n\tADDSET_BOOL\t\t(\"Static Sounds\", cl_staticsounds),\n\tADDSET_BOOL\t\t(\"Sounds When Minimized\", sys_inactivesound),\n\tADDSET_BASIC_SECTION(),\n\tADDSET_ENUM \t(\"Quality\", s_khz, s_khz_enum),\n\tADDSET_ENUM \t(\"Buffer size\", s_desiredsamples, s_desired_samples_enum),\n\n\t//Connection\n\tADDSET_SEPARATOR(\"Connection\"),\n\tADDSET_ENUM \t(\"Bandwidth Limit\", rate, bandwidth_enum),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL\t\t(\"Early Packets\", cl_earlypackets),\n\tADDSET_ENUM\t\t(\"Packetloss\", cl_c2sImpulseBackup, cl_c2sImpulseBackup_enum),\n\tADDSET_BOOL\t\t(\"QTV Buffer Adjusting\", qtv_adjustbuffer),\n\tADDSET_BASIC_SECTION(),\n\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_SEPARATOR(\"Miscellaneous\"),\n\tADDSET_CUSTOM\t(\"FPS Limit\", FpslimitRead, FpslimitToggle, \"Limits the amount of frames rendered per second. May help with lag; best to consult forums about the best value for your setup.\"),\n#ifdef _WIN32\n\tADDSET_ENUM\t\t(\"Process Priority\", sys_highpriority, priority_enum),\n\tADDSET_BOOL\t\t(\"Taskbar Flash\", vid_flashonactivity),\n\tADDSET_BOOL\t\t(\"Taskbar Name\", cl_window_caption),\n#endif\n\tADDSET_BASIC_SECTION(),\n};\n\n// CONFIG TAB\nsetting settconfig_arr[] = {\n\tADDSET_BOOL\t\t(\"Advanced Options\", menu_advanced),\n\tADDSET_ACTION\t(\"Go To Console\", Con_ToggleConsole_f, \"Opens the console.\"),\n\n    ADDSET_SEPARATOR(\"Load & Save\"),\n    ADDSET_ACTION(\"Reload Settings\", MOpt_LoadCfg, \"Reset the settings to last saved configuration.\"),\n    ADDSET_ACTION(\"Save Settings\", MOpt_SaveCfg, \"Save the settings\"),\n\tADDSET_ADVANCED_SECTION(),\n    ADDSET_BOOL(\"Save To Profile Dir\", cfg_use_home),\n\tADDSET_BOOL(\"Save To Mod Directory\", cfg_use_gamedir),\n    ADDSET_BASIC_SECTION(),\n\tADDSET_ACTION(\"Reset To Defaults\", DefaultConfig, \"Reset all settings to defaults\"),\n\n    \n\tADDSET_SEPARATOR(\"Export & Import\"),\n\tADDSET_ACTION(\"Load Script\", MOpt_LoadScript, \"Find and load scripts here.\"),\n\tADDSET_ACTION(\"Import config ...\", MOpt_ImportConfig, \"You can load a configuration file from here.\"),\n\tADDSET_ACTION(\"Export config ...\", MOpt_ExportConfig, \"Will export your current configuration to a file.\"),\n\t\n\tADDSET_SEPARATOR(\"Config Saving Options\"),\n    ADDSET_ACTION(\"Reset Saving Options\", MOpt_CfgSaveAllOn, \"Configuration saving settings will be reset to defaults.\"),\n    ADDSET_BOOL(\"Auto Save On Quit\", cfg_save_onquit),\n\tADDSET_BOOL(\"Save Unchanged Opt.\", cfg_save_unchanged),\n\tADDSET_ADVANCED_SECTION(),\n\tADDSET_BOOL(\"Backup Old File\", cfg_backup),\n\tADDSET_NAMED(\"Load Legacy\", cfg_legacy_exec, MOpt_legacywrite_enum),\n\tADDSET_BOOL(\"Aliases\", cfg_save_aliases),\n\tADDSET_BOOL(\"Binds\", cfg_save_binds),\n\tADDSET_BOOL(\"Cmdline\", cfg_save_cmdline),\n\tADDSET_BOOL(\"Init Commands\", cfg_save_cmds),\n\tADDSET_BOOL(\"Variables\", cfg_save_cvars),\n\tADDSET_NAMED(\"Userinfo\", cfg_save_userinfo, MOpt_userinfo_enum)\n};\n\nqbool Menu_Options_Mouse_Event(const mouse_state_t *ms)\n{\n\tmouse_state_t nms = *ms;\n\n    if (ms->button_up == 2) {\n        Menu_Options_Key(K_MOUSE2, 0);\n        return true;\n    }\n\n\t// we are sending relative coordinates\n\tnms.x -= OPTPADDING;\n\tnms.y -= OPTPADDING;\n\tnms.x_old -= OPTPADDING;\n\tnms.y_old -= OPTPADDING;\n\n\tif (nms.x < 0 || nms.y < 0) return false;\n\n\treturn CTab_Mouse_Event(&options_tab, &nms);\n}\nvoid Menu_Options_Init(void) {\n\tSettings_MainInit();\n\n\tSettings_Page_Init(settmisc, settmisc_arr);\n\tSettings_Page_Init(settfps, settfps_arr);\n\tSettings_Page_Init(settview, settview_arr);\n\tSettings_Page_Init(settplayer, settplayer_arr);\n\tSettings_Page_Init(settbinds, settbinds_arr);\n\tSettings_Page_Init(settsystem, settsystem_arr);\n\tSettings_Page_Init(settconfig, settconfig_arr);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_MENU);\n\tCvar_Register(&menu_advanced);\n\tCvar_ResetCurrentGroup();\n\n\tFL_Init(&configs_filelist, \"./ezquake/configs\");\n\tFL_SetDirUpOption(&configs_filelist, false);\n\tFL_SetDirsOption(&configs_filelist, false);\n\tFL_AddFileType(&configs_filelist, 0, \".cfg\");\n\tFL_AddFileType(&configs_filelist, 1, \".txt\");\n\n\tCEditBox_Init(&filenameeb, 32, 64);\n\n\tCTab_Init(&options_tab);\n\tCTab_AddPage(&options_tab, \"Player\", OPTPG_PLAYER, &options_player_handlers);\n\tCTab_AddPage(&options_tab, \"Graphics\", OPTPG_FPS, &options_graphics_handlers);\n\tCTab_AddPage(&options_tab, \"View\", OPTPG_HUD, &options_view_handlers);\n\tCTab_AddPage(&options_tab, \"Controls\", OPTPG_BINDS, &options_controls_handlers);\n\tCTab_AddPage(&options_tab, \"Misc\", OPTPG_MISC, &options_misc_handlers);\n\tCTab_AddPage(&options_tab, \"System\", OPTPG_SYSTEM, &options_system_handlers);\n\tCTab_AddPage(&options_tab, \"Config\", OPTPG_CONFIG, &options_config_handlers);\n\tCTab_SetCurrentId(&options_tab, OPTPG_PLAYER);\n}\n\nvoid Menu_Options_Shutdown(void)\n{\n\tFL_Shutdown(&configs_filelist);\n\tSettings_Shutdown(&settmisc);\n\tSettings_Shutdown(&settfps);\n\tSettings_Shutdown(&settview);\n\tSettings_Shutdown(&settplayer);\n\tSettings_Shutdown(&settbinds);\n\tSettings_Shutdown(&settsystem);\n\tSettings_Shutdown(&settconfig);\n\tSettings_MainShutdown();\n}\n\nqbool Menu_Options_IsBindingKey (void)\n{\n\t// Options/Binds, and waiting for a keypress\n\treturn m_state == m_options && (\n\t\t(CTab_GetCurrentId (&options_tab) == OPTPG_BINDS && settbinds.mode == SPM_BINDING) ||\n\t\t(CTab_GetCurrentId (&options_tab) == OPTPG_SYSTEM && settsystem.mode == SPM_BINDING)\n\t);\n}\n"
  },
  {
    "path": "src/menu_options.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/*\n\tOptions Menu module\n\t$Id: menu_options.h,v 1.4 2007-03-19 13:23:20 johnnycz Exp $\n*/\n\n// <interface for menu.c>\n// initializes the \"Demo\" menu on client startup\nvoid Menu_Options_Init(void);\n\n// process key press that belongs to demo menu\nvoid Menu_Options_Key(int key, wchar unichar);\n\n// process request to draw the demo menu\nvoid Menu_Options_Draw (void);\n\nqbool Menu_Options_IsBindingKey (void);\n\n// process mouse move\nqbool Menu_Options_Mouse_Event(const mouse_state_t *);\n// </interface>\n"
  },
  {
    "path": "src/menu_proxy.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/*\n\n\tProxy Menu module\n\n\tThis stands rather apart from the Main Menu hierarchy,\n\tbut we still use the menu structures.\n\n\tmade by:\n\t\tjohnnycz, Jan 2007\n\tlast edit:\n\t\t$Id: menu_proxy.c,v 1.2 2007-03-11 06:01:41 disconn3ct Exp $\n\n*/\n\n#include \"quakedef.h\"\n#include \"menu.h\"\n#include \"keys.h\"\n\n\n#define PROXYACCESS \"say proxy:menu\"\n#define ProxyMenuToggle() Cbuf_AddText(PROXYACCESS \"\\n\");\n#define ProxyMenuCmd(x) Cbuf_AddText(PROXYACCESS \" \" x \"\\n\")\n\n// disconnection from proxy should make us leave menus\nqbool CheckProxyConnection(void) {\n\tif (!CL_ConnectedToProxy()) {\n\t\tCom_Printf(\"You are not connected to a proxy.\\n\");\n\t\tM_LeaveMenus();\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid Menu_Proxy_Toggle(void)\n{\n\tif (!CheckProxyConnection())\n\t\treturn;\n\n\tProxyMenuToggle();\n}\n\nvoid Menu_Proxy_Key(int key)\n{\n\tif (!CheckProxyConnection())\n\t\treturn;\n\n\tswitch (key) {\n\tcase K_ESCAPE: \n\t\tProxyMenuToggle(); \n\t\tM_LeaveMenus();\n\t\tbreak;\n\tcase K_MOUSE1: case K_ENTER:\n\t\tProxyMenuCmd(\"select\"); break;\n\tcase K_BACKSPACE: ProxyMenuCmd(\"back\"); break;\n\n\tcase K_UPARROW: ProxyMenuCmd(\"up\"); break;\n\tcase K_DOWNARROW: ProxyMenuCmd(\"down\"); break;\n\tcase K_LEFTARROW: ProxyMenuCmd(\"left\"); break;\n\tcase K_RIGHTARROW: ProxyMenuCmd(\"right\"); break;\n\n\tcase K_PGDN: ProxyMenuCmd(\"pgdn\"); break;\n\tcase K_PGUP: ProxyMenuCmd(\"pgup\"); break;\n\tcase K_HOME: ProxyMenuCmd(\"home\"); break;\n\tcase K_END: ProxyMenuCmd(\"end\"); break;\n\tcase K_DEL: ProxyMenuCmd(\"delete\"); break;\n\tcase K_INS: ProxyMenuCmd(\"help\"); break;\n\t}\n}\n\nvoid Menu_Proxy_Draw(void) { \n// proxy menu is usually drawn by centerprint messages\n// but ofc this can be changed later .. don't forget to hack SCR_CheckDrawCenterString() then again\n} \n"
  },
  {
    "path": "src/menu_proxy.h",
    "content": "/*\nProxy Menu module\n\nCopyright (C) 2011 johnnycz\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// catches key presses sent to proxy\nvoid Menu_Proxy_Key(int key);\n\n// main proxy menu drawing function\nvoid Menu_Proxy_Draw(void);\n\n// send signal to the proxy to toggle the menu\nvoid Menu_Proxy_Toggle(void);\n"
  },
  {
    "path": "src/modelgen.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n//\n// modelgen.h: header file for model generation program\n//\n\n// *********************************************************\n// * This file must be identical in the modelgen directory *\n// * and in the Quake directory, because it's used to      *\n// * pass data from one to the other via model files.      *\n// *********************************************************\n\n#ifdef INCLUDELIBS\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <math.h>\n#include <string.h>\n\n#include \"cmdlib.h\"\n#include \"scriplib.h\"\n#include \"trilib.h\"\n#include \"lbmlib.h\"\n#include \"mathlib.h\"\n\n#endif\n\n#define ALIAS_VERSION\t6\n\n#define ALIAS_ONSEAM\t\t\t\t0x0020\n\n// must match definition in spritegn.h\n#ifndef SYNCTYPE_T\n#define SYNCTYPE_T\ntypedef enum {ST_SYNC=0, ST_RAND } synctype_t;\n#endif\n\ntypedef enum { ALIAS_SINGLE=0, ALIAS_GROUP } aliasframetype_t;\n\ntypedef enum { ALIAS_SKIN_SINGLE=0, ALIAS_SKIN_GROUP } aliasskintype_t;\n\ntypedef struct {\n\tint\t\t\tident;\n\tint\t\t\tversion;\n\tvec3_t\t\tscale;\n\tvec3_t\t\tscale_origin;\n\tfloat\t\tboundingradius;\n\tvec3_t\t\teyeposition;\n\tint\t\t\tnumskins;\n\tint\t\t\tskinwidth;\n\tint\t\t\tskinheight;\n\tint\t\t\tnumverts;\n\tint\t\t\tnumtris;\n\tint\t\t\tnumframes;\n\tsynctype_t\tsynctype;\n\tint\t\t\tflags;\n\tfloat\t\tsize;\n} mdl_t;\n\n// TODO: could be shorts\n\ntypedef struct {\n\tint\t\tonseam;\n\tint\t\ts;\n\tint\t\tt;\n} stvert_t;\n\ntypedef struct dtriangle_s {\n\tint\t\t\t\t\tfacesfront;\n\tint\t\t\t\t\tvertindex[3];\n} dtriangle_t;\n\n#define DT_FACES_FRONT\t\t\t\t0x0010\n\n// This mirrors trivert_t in trilib.h, is present so Quake knows how to\n// load this data\n\ntypedef struct {\n\tbyte\tv[3];\n\tbyte\tlightnormalindex;\n} trivertx_t;\n\ntypedef struct {\n\tvec3_t  v;\n\tvec3_t  normal;\n\tbyte\tlightnormalindex;\n} ez_trivertx_t;\n\ntypedef struct {\n\ttrivertx_t\tbboxmin;\t// lightnormal isn't used\n\ttrivertx_t\tbboxmax;\t// lightnormal isn't used\n\tchar\t\tname[16];\t// frame name from grabbing\n} daliasframe_t;\n\ntypedef struct {\n\tint\t\t\tnumframes;\n\ttrivertx_t\tbboxmin;\t// lightnormal isn't used\n\ttrivertx_t\tbboxmax;\t// lightnormal isn't used\n} daliasgroup_t;\n\ntypedef struct {\n\tint\t\t\tnumskins;\n} daliasskingroup_t;\n\ntypedef struct {\n\tfloat\tinterval;\n} daliasinterval_t;\n\ntypedef struct {\n\tfloat\tinterval;\n} daliasskininterval_t;\n\ntypedef struct {\n\taliasframetype_t\ttype;\n} daliasframetype_t;\n\ntypedef struct {\n\taliasskintype_t\ttype;\n} daliasskintype_t;\n\n#define IDPOLYHEADER\t(('O'<<24)+('P'<<16)+('D'<<8)+'I')\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// little-endian \"IDPO\"\n\n"
  },
  {
    "path": "src/movie.c",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: movie.c,v 1.26 2007-10-11 06:46:12 borisu Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"utils.h\"\n#include \"qsound.h\"\n#include \"image.h\"\n#ifdef _WIN32\n#include \"movie_avi.h\"\t//joe: capturing to avi\n#include <windows.h>\n#else\n\t#include <time.h>\n#endif\n\nstatic void OnChange_movie_dir(cvar_t *var, char *string, qbool *cancel);\nstatic void WAVCaptureStop (void);\nstatic void WAVCaptureStart (void);\nvoid SCR_Movieshot (char *);\t//joe: capturing to avi\n\n//joe: capturing audio\n// Variables for buffering audio\nstatic short capture_audio_samples[44100];\t// big enough buffer for 1fps at 44100Hz\nstatic int captured_audio_samples;\nstatic qbool frame_has_sound = false;\n\n#ifdef _WIN32\nvoid OnChange_movie_codec(cvar_t *var, char *string, qbool *cancel);\nLONG Movie_CurrentLength(void);\n\nstatic char movie_avi_filename[MAX_OSPATH]; // Stores the user's requested filename\nstatic void Movie_Start_AVI_Capture(void);\nstatic int avi_number = 0;\n\n//joe: capturing to avi\nstatic qbool movie_is_avi = false;\nqbool movie_avi_loaded = false, movie_acm_loaded = false;\nstatic char avipath[256];\nstatic FILE *avifile = NULL;\ncvar_t   movie_codec      = {\"demo_capture_codec\", \"0\", 0, OnChange_movie_codec };\t// Capturing to avi\ncvar_t   movie_mp3        = {\"demo_capture_mp3\", \"0\"};\ncvar_t   movie_mp3_kbps   = {\"demo_capture_mp3_kbps\", \"128\"};\ncvar_t   movie_vid_maxlen = {\"demo_capture_vid_maxlen\", \"0\"};\n#endif\n\ncvar_t          movie_fps                = {\"demo_capture_fps\", \"30.0\"};\nstatic cvar_t   movie_dir                = {\"demo_capture_dir\",  \"capture\", 0, OnChange_movie_dir};\ncvar_t          movie_steadycam          = {\"demo_capture_steadycam\", \"0\"};\nstatic cvar_t   movie_background_threads = {\"demo_capture_background_threads\", \"0\"};\n\nextern cvar_t scr_sshot_type;\n\nstatic unsigned char aviSoundBuffer[4096]; // Static buffer for mixing\n\nstatic double movie_real_start_time;\nstatic volatile qbool movie_is_capturing = false;\nstatic double movie_start_time;\nstatic double movie_len;\nstatic int movie_frame_count;\nstatic char image_ext[4];\nstatic qbool capturing_apng;\nstatic int apng_expected_frames;\n\n\n#ifdef _WIN32\n\tstatic SYSTEMTIME movie_start_date;\n#else\n\tstruct tm movie_start_date;\n#endif\n\nqbool Movie_IsCapturing(void)\n{\n\treturn cls.demoplayback && !cls.timedemo && movie_is_capturing;\n}\n\ndouble Movie_Frametime(void)\n{\n\tdouble time = (double)(movie_fps.value > 0 ? 1.0 / movie_fps.value : 1 / 30.0);\n\n\treturn bound(1.0 / 1000, time, 1.0);;\n}\n\ndouble Movie_InputFrametime(void)\n{\n\tif (movie_steadycam.value) {\n\t\treturn movie_fps.value > 0 ? 1.0 / movie_fps.value : 1 / 30.0;\n\t}\n\n\treturn cls.trueframetime;\n}\n\nstatic void Movie_Start(double _time) \n{\n\textern cvar_t scr_sshot_format;\n\n\t#ifndef _WIN32\n\ttime_t t;\n\tt = time(NULL);\n\tlocaltime_r(&t, &movie_start_date);\n\t#else\n\tGetLocalTime(&movie_start_date);\n\t#endif\n\t#ifdef _WIN32\n\tmovie_is_avi = !!avifile; //joe: capturing to avi\n\t#endif\n\tmovie_len = _time;\n\tmovie_start_time = cls.realtime;\n\n\tmovie_frame_count = 0;\n\n\t#ifdef _WIN32\n\tif (movie_is_avi)\t//joe: capturing to avi\n\t{\n\t\tmovie_is_capturing = Capture_Open (avipath);\n\t}\n\telse\n\t#endif\n\t{\n\t\t// DEFAULT_SSHOT_FORMAT\n\t\tif (!strcmp(scr_sshot_format.string, \"tga\")\n\t\t || !strcmp(scr_sshot_format.string, \"jpeg\")\n\t\t || !strcmp(scr_sshot_format.string, \"jpg\")\n\t\t || !strcmp(scr_sshot_format.string, \"png\"))\n\t\t{\n\t\t\tstrlcpy(image_ext, scr_sshot_format.string, sizeof(image_ext));\t\t\n\t\t}\n\t\telse if (!strcmp(scr_sshot_format.string, \"apng\")) {\n\t\t\tstrlcpy(image_ext, \"png\", sizeof(image_ext));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstrlcpy (image_ext, \"tga\", sizeof (image_ext));\n\t\t}\n\t\tmovie_is_capturing = true;\n\t\tWAVCaptureStart ();\n\t}\n\tmovie_real_start_time = Sys_DoubleTime ();\n}\n\nvoid Movie_Stop(qbool restarting)\n{\n\tif (Movie_AnimatedPNG()) {\n\t\tImage_CloseAPNG();\n\t}\n#ifdef _WIN32\n\tif (movie_is_avi) { //joe: capturing to avi\n\t\tCapture_Close ();\n\t\tfclose (avifile);\n\t\tavifile = NULL;\n\t}\n\tif (!restarting) {\n\t\tS_StopAllSounds();\n\t\tMovie_BackgroundShutdown();\n\n\t\tCom_Printf(\"Captured %d frames (%.2fs).\\n\", movie_frame_count, (float) (cls.realtime - movie_start_time));\n\t\tCom_Printf(\"  Time: %5.1f seconds\\n\", Sys_DoubleTime() - movie_real_start_time);\n\t}\n#endif\n\tWAVCaptureStop ();\n\tmovie_is_capturing = restarting;\n}\n\nvoid Movie_Demo_Capture_f(void)\n{\n\tint argc;\n\tdouble duration;\n\tchar *error;\n\textern cvar_t scr_sshot_format;\n\n#ifdef _WIN32\n\terror = va(\"Usage: %s (\\\"start\\\" time [avifile]) | \\\"stop\\\"\\n\", Cmd_Argv(0));\n\tif ((argc = Cmd_Argc()) != 2 && argc != 3 && argc != 4) {\n#else\n\terror = va(\"Usage: %s (\\\"start\\\" time) | \\\"stop\\\"\\n\", Cmd_Argv(0));\n\tif ((argc = Cmd_Argc()) != 2 && argc != 3) {\n#endif\n\t\tCom_Printf(error);\n\t\treturn;\n\t}\n\tif (argc == 2) {\n\t\tif (strncasecmp(\"stop\", Cmd_Argv(1), 4))\n\t\t\tCom_Printf(error);\n\t\telse if (Movie_IsCapturing()) \n\t\t\tMovie_Stop(false);\n\t\telse\n\t\t\tCom_Printf(\"%s : Not capturing\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\tif (strncasecmp(\"start\", Cmd_Argv(1), 5)) {\n\t\tCom_Printf(error);\n\t\treturn;\n\t} else if (Movie_IsCapturing()) {\n\t\tCom_Printf(\"%s : Already capturing\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\tif (!cls.demoplayback || cls.timedemo) {\n\t\tCom_Printf(\"%s : Must be playing a demo to capture\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\tif ((duration = Q_atof(Cmd_Argv(2))) <= 0) {\n\t\tCom_Printf(\"%s : Time argument must be positive\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\tcapturing_apng = false;\n#ifdef _WIN32\n\t//joe: capturing to avi\n\tif (argc == 4) {\n\t\tavi_number = 0;\n\n\t\tstrlcpy(movie_avi_filename, Cmd_Argv(3), sizeof(movie_avi_filename)-10);\t\t// Store user's requested filename\n\t\tif (!movie_avi_loaded) {\n\t\t\tCom_Printf_State (PRINT_FAIL, \"Avi capturing not initialized\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tMovie_Start_AVI_Capture();\n\t}\n\telse\n#endif\n\tif (!strcasecmp(scr_sshot_format.string, \"apng\")) {\n\t\tchar fname[MAX_OSPATH];\n\t\textern cvar_t image_png_compression_level;\n\t\textern int glwidth, glheight;\n#ifndef _WIN32\n\t\ttime_t t;\n\t\tt = time(NULL);\n\t\tlocaltime_r(&t, &movie_start_date);\n\n\t\tsnprintf(fname, sizeof(fname), \"%s/capture_%02d-%02d-%04d_%02d-%02d-%02d/capture.png\",\n\t\t\tmovie_dir.string, movie_start_date.tm_mday, movie_start_date.tm_mon, movie_start_date.tm_year,\n\t\t\tmovie_start_date.tm_hour, movie_start_date.tm_min, movie_start_date.tm_sec);\n#else\n\t\tGetLocalTime(&movie_start_date);\n\n\t\tsnprintf(fname, sizeof(fname), \"%s/capture_%02d-%02d-%04d_%02d-%02d-%02d/capture.png\",\n\t\t\tmovie_dir.string, movie_start_date.wDay, movie_start_date.wMonth, movie_start_date.wYear,\n\t\t\tmovie_start_date.wHour, movie_start_date.wMinute, movie_start_date.wSecond);\n#endif\n\n\t\tapng_expected_frames = duration * movie_fps.integer;\n\t\tcapturing_apng = Image_OpenAPNG(fname, image_png_compression_level.integer, glwidth, glheight, apng_expected_frames);\n\n\t\tif (!capturing_apng) {\n\t\t\tMovie_BackgroundInitialise();\n\t\t}\n\t}\n\telse {\n\t\tMovie_BackgroundInitialise();\n\t}\n\tMovie_Start(duration);\n}\n\n#ifdef _WIN32\nstatic void Movie_Start_AVI_Capture(void)\n{\n\t++avi_number;\n\n\t// If we're going to break up the movie, append number\n\tchar aviname[MAX_OSPATH];\n\tif (avi_number > 1) {\n\t\tsnprintf (aviname, sizeof (aviname), \"%s-%03d\", movie_avi_filename, avi_number);\n\t}\n\telse {\n\t\tstrlcpy (aviname, movie_avi_filename, sizeof (aviname));\n\t}\n\n\tif (!(Util_Is_Valid_Filename(aviname))) {\n\t\tCom_Printf(Util_Invalid_Filename_Msg(aviname));\n\t\treturn;\n\t}\n\tCOM_ForceExtensionEx (aviname, \".avi\", sizeof (aviname));\n\tsnprintf (avipath, sizeof(avipath), \"%s/%s/%s\", com_basedir, movie_dir.string, aviname);\n\tif (!(avifile = fopen(avipath, \"wb\"))) {\n\t\tFS_CreatePath (avipath);\n\t\tif (!(avifile = fopen(avipath, \"wb\"))) {\n\t\t\tCom_Printf(\"Error: Couldn't open %s\\n\", aviname);\n\t\t\treturn;\n\t\t}\n\t}\n}\n#endif\n\nvoid Movie_Init(void) {\n\tCvar_SetCurrentGroup(CVAR_GROUP_DEMO);\n\tCvar_Register(&movie_fps);\n\tCvar_Register(&movie_dir);\n\tCvar_Register(&movie_background_threads);\n\tCvar_Register(&movie_steadycam);\n\n\tCvar_ResetCurrentGroup();\n\n\tCmd_AddCommand(\"demo_capture\", Movie_Demo_Capture_f);\n\n#ifdef _WIN32\n\tCapture_InitAVI ();\t\t//joe: capturing to avi\n\tif (!movie_avi_loaded)\n\t\treturn;\n\n\tcaptured_audio_samples = 0;\n\tCvar_SetCurrentGroup(CVAR_GROUP_DEMO);\n\tCvar_Register(&movie_codec);\n\tCvar_Register(&movie_vid_maxlen);\n\n\tCvar_ResetCurrentGroup();\n\n\tCapture_InitACM ();\n\tif (!movie_acm_loaded)\n\t\treturn;\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_DEMO);\n\tCvar_Register(&movie_mp3);\n\tCvar_Register(&movie_mp3_kbps);\n\n\tCvar_ResetCurrentGroup();\n#endif\n}\n\nvoid Movie_StartFrame(void) \n{\n\tif (Cmd_FindAlias(\"f_captureframe\")) {\n\t\tCbuf_AddTextEx (&cbuf_main, \"f_captureframe\\n\");\n\t}\n}\n\nvoid Movie_FinishFrame(void) \n{\n\tchar fname[128];\n\tif (!Movie_IsCapturing())\n\t\treturn;\n\n\t#ifdef _WIN32\n\tif (!movie_is_avi) \n\t{\n\t\tsnprintf(fname, sizeof(fname), \"%s/capture_%02d-%02d-%04d_%02d-%02d-%02d/shot-%06d.%s\",\n\t\t\tmovie_dir.string, movie_start_date.wDay, movie_start_date.wMonth, movie_start_date.wYear,\n\t\t\tmovie_start_date.wHour,\tmovie_start_date.wMinute, movie_start_date.wSecond, movie_frame_count, image_ext);\n\n\t\tcon_suppress = true;\n\t}\n\telse \n\t{\n\t\tfname[0] = '\\0';\n\t\t// Split up if we're over the time limit for each segment\n\t\tif (movie_vid_maxlen.integer && Movie_CurrentLength() >= movie_vid_maxlen.value * 1024 * 1024) \n\t\t{\n\t\t\tdouble original_start_time = movie_start_time;\n\n\t\t\t// Close existing, and start again\n\t\t\tMovie_Stop(true);\n\t\t\tMovie_Start_AVI_Capture();\n\t\t\tMovie_Start(movie_len);\n\n\t\t\t// keep track of original start time so we know when to stop for good\n\t\t\tmovie_start_time = original_start_time;\n\t\t}\n\t}\n\t#else\n\tsnprintf(fname, sizeof(fname), \"%s/capture_%02d-%02d-%04d_%02d-%02d-%02d/shot-%06d.%s\",\n\t\tmovie_dir.string, movie_start_date.tm_mday, 1 + movie_start_date.tm_mon, 1900 + movie_start_date.tm_year,\n\t\tmovie_start_date.tm_hour, movie_start_date.tm_min, movie_start_date.tm_sec, movie_frame_count, image_ext);\n\n\tcon_suppress = true;\n\t#endif // _WIN32\n\n\tSCR_Movieshot (fname);\n\tmovie_frame_count++;\n\n#ifdef _WIN32\n\tif (!movie_is_avi)\n#endif\n\t{\n\t\tcon_suppress = false;\n\t}\n\n\tif (Movie_AnimatedPNG()) {\n\t\tif (movie_frame_count >= apng_expected_frames) {\n\t\t\tMovie_Stop(false);\n\t\t}\n\t}\n\telse if (cls.realtime >= movie_start_time + movie_len) {\n\t\tMovie_Stop (false);\n\t}\n}\n\nqbool Movie_IsCapturingAVI(void) {\n#ifdef _WIN32\n\treturn movie_is_avi && Movie_IsCapturing();\n#else\n\treturn false;\n#endif\n}\n\nstatic vfsfile_t *wav_output = NULL;\nstatic int wav_sample_count = 0;\n\nstatic void WAVCaptureStart (void)\n{\n\t// WAV file format details taken from http://soundfile.sapp.org/doc/WaveFormat/\n\tchar fname[MAX_PATH];\n\n\tint chunkSize = 0;  // We write 0 to start with, need to replace once we know number of samples\n\tint fmtBlockSize = LittleLong (16);\n\tshort fmtFormat = LittleShort (1); // PCM, uncompressed\n\tshort fmtNumberChannels = LittleShort (shw->numchannels);\n\tint fmtSampleRate = LittleLong (shw->khz);\n\tint fmtBitsPerSample = 16;\n\tint fmtByteRate = shw->khz * fmtNumberChannels * (fmtBitsPerSample / 8);  // 16 = 16 bit sound\n\tint fmtBlockAlign = (fmtBitsPerSample / 8) * shw->numchannels; // 16 bit sound\n\n#ifdef _WIN32\n\tsnprintf (fname, sizeof (fname), \"%s/capture_%02d-%02d-%04d_%02d-%02d-%02d/audio.wav\",\n\t\tmovie_dir.string, movie_start_date.wDay, movie_start_date.wMonth, movie_start_date.wYear,\n\t\tmovie_start_date.wHour, movie_start_date.wMinute, movie_start_date.wSecond);\n#else\n\tsnprintf (fname, sizeof (fname), \"%s/capture_%02d-%02d-%04d_%02d-%02d-%02d/audio.wav\",\n\t\tmovie_dir.string, movie_start_date.tm_mday, 1 + movie_start_date.tm_mon, 1900 + movie_start_date.tm_year,\n\t\tmovie_start_date.tm_hour, movie_start_date.tm_min, movie_start_date.tm_sec);\n#endif\n\tif (!(wav_output = FS_OpenVFS (fname, \"wb\", FS_NONE_OS))) {\n\t\tFS_CreatePath (fname);\n\t\tif (!(wav_output = FS_OpenVFS (fname, \"wb\", FS_NONE_OS)))\n\t\t\treturn;\n\t}\n\n\t// RIFF header\n\tVFS_WRITE (wav_output, \"RIFF\", 4);\n\tVFS_WRITE (wav_output, &chunkSize, 4);\n\tVFS_WRITE (wav_output, \"WAVE\", 4);\n\n\t// \"fmt \" chunk (24 bytes total)\n\tVFS_WRITE (wav_output, \"fmt \", 4);\n\tVFS_WRITE (wav_output, &fmtBlockSize, 4);\n\tVFS_WRITE (wav_output, &fmtFormat, 2);\n\tVFS_WRITE (wav_output, &fmtNumberChannels, 2);\n\tVFS_WRITE (wav_output, &fmtSampleRate, 4);\n\tVFS_WRITE (wav_output, &fmtByteRate, 4);\n\tVFS_WRITE (wav_output, &fmtBlockAlign, 2);\n\tVFS_WRITE (wav_output, &fmtBitsPerSample, 2);\n\n\t// \"data\" chunk\n\tVFS_WRITE (wav_output, \"data\", 4);\n\tVFS_WRITE (wav_output, &chunkSize, 4);\n\n\t// actual data will be written as audio mixed each frame\n\twav_sample_count = 0;\n}\n\nstatic void WAVCaptureFrame (int samples, byte *sample_buffer)\n{\n\tint i;\n\n\tif (wav_output) {\n\t\tfor (i = 0; i < samples; ++i) {\n\t\t\tunsigned long original = LittleLong (*(unsigned long*)sample_buffer);\n\n\t\t\tVFS_WRITE (wav_output, &original, 4);\n\t\t\tsample_buffer += 4;\n\t\t}\n\n\t\twav_sample_count += samples;\n\t}\n}\n\nstatic void WAVCaptureStop (void)\n{\n\tif (wav_output) {\n\t\t// Now we know how many samples, so can fill in the blocks in the file\n\t\tint dataSize = wav_sample_count * 2 * shw->numchannels; // 16 bit sound, stereo\n\t\tint riffChunkSize = 36 + dataSize; // 36 = 4[format field] + (8 + chunk1size)[fmt block] + 8 [subchunk2Id + size fields]\n\n\t\tVFS_SEEK (wav_output, 4, SEEK_SET);\n\t\tVFS_WRITE (wav_output, &riffChunkSize, 4);\n\t\tVFS_SEEK (wav_output, 40, SEEK_SET);   // RIFF header = 12, fmt chunk 24, \"data\" 4\n\t\tVFS_WRITE (wav_output, &dataSize, 4);\n\t\tVFS_CLOSE (wav_output);\n\n\t\twav_output = 0;\n\t\twav_sample_count = 0;\n\t}\n}\n\nvoid Movie_MixFrameSound (void (*mixFunction)(void))\n{\n\tint samples_required = (int)(0.5 + Movie_Frametime() * shw->khz) * shw->numchannels - (captured_audio_samples << 1);\n\n\tmemset(aviSoundBuffer, 0, sizeof(aviSoundBuffer));\n\tshw->buffer = (unsigned char*)aviSoundBuffer;\n\tshw->samples = min(samples_required, sizeof(aviSoundBuffer) / 2);\n\tframe_has_sound = false;\n\n\tdo {\n\t\tmixFunction();\n\t} while (! frame_has_sound);\n}\n\nvoid Movie_TransferSound(void* data, int snd_linear_count)\n{\n\tint samples_per_frame = (int)(0.5 + Movie_Frametime() * shw->khz);\n\n\t// Write some sound\n\tmemcpy(capture_audio_samples + (captured_audio_samples << 1), data, snd_linear_count * shw->numchannels);\n\tcaptured_audio_samples += (snd_linear_count >> 1);\n\tshw->snd_sent += snd_linear_count * shw->numchannels;\n\n\tif (captured_audio_samples >= samples_per_frame) {\n\t\t// We have enough audio samples to match one frame of video\n#ifdef _WIN32\n\t\tif (movie_is_avi) {\n\t\t\tCapture_WriteAudio (samples_per_frame, (byte *)capture_audio_samples);\n\t\t}\n\t\telse {\n\t\t\tWAVCaptureFrame (samples_per_frame, (byte *)capture_audio_samples);\n\t\t}\n#else\n\t\tWAVCaptureFrame (samples_per_frame, (byte *)capture_audio_samples);\n#endif\n\t\tmemcpy (capture_audio_samples, capture_audio_samples + (samples_per_frame << 1), (captured_audio_samples - samples_per_frame) * 2 * shw->numchannels);\n\t\tcaptured_audio_samples -= samples_per_frame;\n\n\t\tframe_has_sound = true;\n\t}\n}\n\nstatic void OnChange_movie_dir(cvar_t *var, char *string, qbool *cancel) {\n\tif (Movie_IsCapturing()) {\n\t\tCom_Printf(\"Cannot change demo_capture_dir whilst capturing.  Use 'demo_capture stop' to cease capturing first.\\n\");\n\t\t*cancel = true;\n\t\treturn;\n\t} else if (strlen(string) > 31) {\n\t\tCom_Printf(\"demo_capture_dir can only contain a maximum of 31 characters\\n\");\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\tUtil_Process_Filename(string);\n\tif (!(Util_Is_Valid_Filename(string))) {\n\t\tCom_Printf(Util_Invalid_Filename_Msg(\"demo_capture_dir\"));\n\t\t*cancel = true;\n\t\treturn;\n\t}\n}\n\ntypedef struct background_thread_s {\n\tSDL_Thread* thread;            // thread handle\n\tSDL_mutex* mutex;              // mutex\n\tSDL_sem* finished;             // signals that the background thread should terminate (set by main)\n\tSDL_sem* signal;               // queue length\n\tscr_sshot_target_t* data;      // queue data\n\tbyte* buffer;                  // screenshot data\n} background_thread_t;\n\n#define MAX_SCREENSHOT_THREADS           8\n\nstatic background_thread_t threads[MAX_SCREENSHOT_THREADS] = { { 0 } };\nstatic byte* tempBuffer = 0;\nstatic int background_threads = 0;\nstatic int next_thread = 0;\nstatic size_t movie_width = 0;\nstatic size_t movie_height = 0;\n\nstatic int Movie_BackgroundThread(void* thread_data)\n{\n\tbackground_thread_t* thread = (background_thread_t*) thread_data;\n\twhile (true) {\n\t\t// Wait to be woken up\n\t\tSDL_SemWait(thread->signal);\n\n\t\tSDL_LockMutex(thread->mutex);\n\t\tif (SDL_SemTryWait(thread->finished) == SDL_MUTEX_TIMEDOUT) {\n\t\t\tSCR_ScreenshotWrite(thread->data);\n\t\t\tSDL_UnlockMutex(thread->mutex);\n\t\t}\n\t\telse {\n\t\t\tSDL_UnlockMutex(thread->mutex);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nqbool Movie_BackgroundInitialise(void)\n{\n\textern int glwidth, glheight;\n\tint i;\n\n\tbackground_threads = (int) bound(0, movie_background_threads.integer, MAX_SCREENSHOT_THREADS);\n\tif (background_threads) {\n\t\tmemset(&threads, 0, sizeof(threads));\n\t\tnext_thread = 0;\n\n\t\t// Create background threads in background\n\t\tfor (i = 0; i < background_threads; ++i) {\n\t\t\tthreads[i].mutex = SDL_CreateMutex();\n\t\t\tthreads[i].signal = SDL_CreateSemaphore(0);\n\t\t\tthreads[i].finished = SDL_CreateSemaphore(0);\n\t\t\tthreads[i].buffer = Q_malloc(glwidth * glheight * 3);\n\t\t\tthreads[i].thread = SDL_CreateThread(Movie_BackgroundThread, NULL, (void*)&threads[i]);\n\t\t}\n\t}\n\n\tmovie_height = glheight;\n\tmovie_width = glwidth;\n\ttempBuffer = Q_malloc(movie_width * movie_height * 3);\n\n\treturn true;\n}\n\nvoid Movie_BackgroundShutdown(void)\n{\n\tint i;\n\n\tfor (i = 0; i < background_threads; ++i) {\n\t\tSDL_SemPost(threads[i].finished);\n\t\tSDL_SemPost(threads[i].signal);\n\t}\n\n\tfor (i = 0; i < background_threads; ++i) {\n\t\tSDL_WaitThread(threads[i].thread, NULL);\n\t\tSDL_DestroySemaphore(threads[i].finished);\n\t\tSDL_DestroySemaphore(threads[i].signal);\n\t\tSDL_DestroyMutex(threads[i].mutex);\n\n\t\tQ_free(threads[i].buffer);\n\t}\n\n\tQ_free(tempBuffer);\n\n}\n\nbyte* Movie_TempBuffer(size_t width, size_t height)\n{\n\tif (width != movie_width || height != movie_height) {\n\t\treturn NULL;\n\t}\n\n\treturn tempBuffer;\n}\n\nqbool Movie_BackgroundCapture(scr_sshot_target_t* params)\n{\n\tif (params->buffer != tempBuffer) {\n\t\treturn false;\n\t}\n\n\tif (Movie_IsCapturing() && ! Movie_IsCapturingAVI() && background_threads) {\n\t\tbackground_thread_t* thread = &threads[next_thread];\n\n\t\tSDL_LockMutex(thread->mutex);\n\t\tmemcpy(thread->buffer, params->buffer, params->width * params->height * 3);\n\t\tparams->buffer = thread->buffer;\n\t\tthread->data = params;\n\t\tSDL_UnlockMutex(thread->mutex);\n\t\tSDL_SemPost(thread->signal);\n\n\t\tnext_thread = (next_thread + 1) % background_threads;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nqbool Movie_AnimatedPNG(void)\n{\n\treturn Movie_IsCapturing() && capturing_apng;\n}\n"
  },
  {
    "path": "src/movie.h",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef __MOVIE_H_\n\n#define __MOVIE_H_\n\nvoid Movie_Init(void);\nvoid Movie_StartFrame(void);\nvoid Movie_FinishFrame(void);\nqbool Movie_IsCapturing(void);\nqbool Movie_IsCapturingAVI(void);\nvoid Movie_Stop(qbool restarting);\ndouble Movie_Frametime(void);\ndouble Movie_InputFrametime(void);\nqbool Movie_TransferSound(void* data, int snd_linear_count);\nvoid Movie_MixFrameSound(void (*mixFunction)(void));\n\n#endif\n"
  },
  {
    "path": "src/movie_avi.c",
    "content": "/*\nCopyright (C) 2001 Quake done Quick\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// movie_avi.c\n\n#include \"quakedef.h\"\n#include <windows.h>\n#include <vfw.h>\n#include <msacm.h>\n#include <mmreg.h>\n#include <mmsystem.h>\n#include \"movie_avi.h\"\n#include \"qsound.h\"\n#include \"gl_model.h\"\n\n#ifndef ACMAPI // mingw hax\n#define ACMAPI WINAPI\n#endif\n\nstatic void (CALLBACK *qAVIFileInit)(void);\nstatic HRESULT (CALLBACK *qAVIFileOpen)(PAVIFILE *, LPCTSTR, UINT, LPCLSID);\nstatic HRESULT (CALLBACK *qAVIFileCreateStream)(PAVIFILE, PAVISTREAM *, AVISTREAMINFO *);\nstatic HRESULT (CALLBACK *qAVIMakeCompressedStream)(PAVISTREAM *, PAVISTREAM, AVICOMPRESSOPTIONS *, CLSID *);\nstatic HRESULT (CALLBACK *qAVIStreamSetFormat)(PAVISTREAM, LONG, LPVOID, LONG);\nstatic HRESULT (CALLBACK *qAVIStreamWrite)(PAVISTREAM, LONG, LONG, LPVOID, LONG, DWORD, LONG *, LONG *);\nstatic ULONG (CALLBACK *qAVIStreamRelease)(PAVISTREAM);\nstatic ULONG (CALLBACK *qAVIFileRelease)(PAVIFILE);\nstatic void (CALLBACK *qAVIFileExit)(void);\n\nstatic MMRESULT (ACMAPI *qacmDriverOpen)(LPHACMDRIVER, HACMDRIVERID, DWORD);\nstatic MMRESULT (ACMAPI *qacmDriverDetails)(HACMDRIVERID, LPACMDRIVERDETAILS, DWORD);\nstatic MMRESULT (ACMAPI *qacmDriverEnum)(ACMDRIVERENUMCB, DWORD, DWORD);\nstatic MMRESULT (ACMAPI *qacmFormatTagDetails)(HACMDRIVER, LPACMFORMATTAGDETAILS, DWORD);\nstatic MMRESULT (ACMAPI *qacmStreamOpen)(LPHACMSTREAM, HACMDRIVER, LPWAVEFORMATEX, LPWAVEFORMATEX, LPWAVEFILTER, DWORD, DWORD, DWORD);\nstatic MMRESULT (ACMAPI *qacmStreamSize)(HACMSTREAM, DWORD, LPDWORD, DWORD);\nstatic MMRESULT (ACMAPI *qacmStreamPrepareHeader)(HACMSTREAM, LPACMSTREAMHEADER, DWORD);\nstatic MMRESULT (ACMAPI *qacmStreamUnprepareHeader)(HACMSTREAM, LPACMSTREAMHEADER, DWORD);\nstatic MMRESULT (ACMAPI *qacmStreamConvert)(HACMSTREAM, LPACMSTREAMHEADER, DWORD);\nstatic MMRESULT (ACMAPI *qacmStreamClose)(HACMSTREAM, DWORD);\nstatic MMRESULT (ACMAPI *qacmDriverClose)(HACMDRIVER, DWORD);\n\nstatic HINSTANCE handle_avi = NULL, handle_acm = NULL;\n\nPAVIFILE\tm_file;\nPAVISTREAM\tm_uncompressed_video_stream;\nPAVISTREAM\tm_compressed_video_stream;\nPAVISTREAM\tm_audio_stream;\n\nunsigned long\tm_codec_fourcc;\nint\t\tm_video_frame_counter;\nint\t\tm_video_frame_size;\n\nqbool\tm_audio_is_mp3;\nint\t\tm_audio_frame_counter;\nWAVEFORMATEX\tm_wave_format;\nMPEGLAYER3WAVEFORMAT mp3_format;\nqbool\tmp3_driver;\nHACMDRIVER\thad;\nHACMSTREAM\thstr;\nACMSTREAMHEADER\tstrhdr;\nLONG bytesWritten;\n\n\nextern qbool movie_avi_loaded, movie_acm_loaded;\nextern\tcvar_t\tmovie_codec, movie_fps, movie_mp3, movie_mp3_kbps;\n\n#define AVI_GETFUNC(f) (qAVI##f = (void *)GetProcAddress(handle_avi, \"AVI\" #f))\n#define ACM_GETFUNC(f) (qacm##f = (void *)GetProcAddress(handle_acm, \"acm\" #f))\n\nvoid Capture_InitAVI (void)\n{\n\tmovie_avi_loaded = false;\n\n\tif (!(handle_avi = LoadLibrary(\"avifil32.dll\")))\n\t{\n\t\tCom_Printf (\"\\x02\" \"Avi capturing module not found\\n\");\n\t\tgoto fail;\n\t}\n\n\tAVI_GETFUNC(FileInit);\n\tAVI_GETFUNC(FileOpen);\n\tAVI_GETFUNC(FileCreateStream);\n\tAVI_GETFUNC(MakeCompressedStream);\n\tAVI_GETFUNC(StreamSetFormat);\n\tAVI_GETFUNC(StreamWrite);\n\tAVI_GETFUNC(StreamRelease);\n\tAVI_GETFUNC(FileRelease);\n\tAVI_GETFUNC(FileExit);\n\n\tmovie_avi_loaded = qAVIFileInit && qAVIFileOpen && qAVIFileCreateStream && \n\t\t\tqAVIMakeCompressedStream && qAVIStreamSetFormat && qAVIStreamWrite && \n\t\t\tqAVIStreamRelease && qAVIFileRelease && qAVIFileExit;\n\n\tif (!movie_avi_loaded)\n\t{\n\t\tCom_Printf_State (PRINT_FAIL, \"Avi capturing module not initialized\\n\");\n\t\tgoto fail;\n\t}\n\n\tCom_Printf_State (PRINT_OK, \"Avi capturing module initialized\\n\");\n\treturn;\n\nfail:\n\tif (handle_avi)\n\t{\n\t\tFreeLibrary (handle_avi);\n\t\thandle_avi = NULL;\n\t}\n}\n\nvoid Capture_InitACM (void)\n{\n\tmovie_acm_loaded = false;\n\n\tif (!(handle_acm = LoadLibrary(\"msacm32.dll\")))\n\t{\n\t\tCom_Printf (\"\\x02\" \"ACM module not found\\n\");\n\t\tgoto fail;\n\t}\n\n\tACM_GETFUNC(DriverOpen);\n\tACM_GETFUNC(DriverEnum);\n\tACM_GETFUNC(StreamOpen);\n\tACM_GETFUNC(StreamSize);\n\tACM_GETFUNC(StreamPrepareHeader);\n\tACM_GETFUNC(StreamUnprepareHeader);\n\tACM_GETFUNC(StreamConvert);\n\tACM_GETFUNC(StreamClose);\n\tACM_GETFUNC(DriverClose);\n\tqacmDriverDetails = (void *)GetProcAddress (handle_acm, \"acmDriverDetailsA\");\n\tqacmFormatTagDetails = (void *)GetProcAddress (handle_acm, \"acmFormatTagDetailsA\");\n\n\tmovie_acm_loaded = qacmDriverOpen && qacmDriverDetails && qacmDriverEnum && \n\t\t\tqacmFormatTagDetails && qacmStreamOpen && qacmStreamSize && \n\t\t\tqacmStreamPrepareHeader && qacmStreamUnprepareHeader && \n\t\t\tqacmStreamConvert && qacmStreamClose && qacmDriverClose;\n\n\tif (!movie_acm_loaded)\n\t{\n\t\tCom_Printf_State (PRINT_FAIL, \"ACM module not initialized\\n\");\n\t\tgoto fail;\n\t}\n\n\tCom_Printf_State (PRINT_OK, \"ACM module initialized\\n\");\n\treturn;\n\nfail:\n\tif (handle_acm)\n\t{\n\t\tFreeLibrary (handle_acm);\n\t\thandle_acm = NULL;\n\t}\n}\n\nPAVISTREAM Capture_VideoStream (void)\n{\n\treturn m_codec_fourcc ? m_compressed_video_stream : m_uncompressed_video_stream;\n}\n\n#ifndef ACMDRIVERDETAILS_SUPPORTF_CODEC\n#define ACMDRIVERDETAILS_SUPPORTF_CODEC 0x00000001L\n#endif\n#ifndef ACM_FORMATTAGDETAILSF_INDEX\n#define ACM_FORMATTAGDETAILSF_INDEX         0x00000000L\n#endif\n#ifndef MPEGLAYER3_WFX_EXTRA_BYTES\n#define MPEGLAYER3_WFX_EXTRA_BYTES   12\n#define MPEGLAYER3_ID_UNKNOWN            0\n#define MPEGLAYER3_ID_MPEG               1\n#define MPEGLAYER3_ID_CONSTANTFRAMESIZE  2\n\n#define MPEGLAYER3_FLAG_PADDING_ISO      0x00000000\n#define MPEGLAYER3_FLAG_PADDING_ON       0x00000001\n#define MPEGLAYER3_FLAG_PADDING_OFF      0x00000002\n#endif\n#ifndef ACMERR_BASE\n#define ACMERR_BASE         (512)\n#define ACMERR_NOTPOSSIBLE  (ACMERR_BASE + 0)\n#define ACMERR_BUSY         (ACMERR_BASE + 1)\n#define ACMERR_UNPREPARED   (ACMERR_BASE + 2)\n#define ACMERR_CANCELED     (ACMERR_BASE + 3)\n#endif\n\nBOOL CALLBACK acmDriverEnumCallback (HACMDRIVERID hadid, DWORD_PTR dwInstance, DWORD fdwSupport)\n{\n\tif (fdwSupport & ACMDRIVERDETAILS_SUPPORTF_CODEC)\n\t{\n\t\tint\ti;\n\t\tACMDRIVERDETAILS details;\n\n\t\tdetails.cbStruct = sizeof(details);\n\t\tqacmDriverDetails (hadid, &details, 0);\n\t\tqacmDriverOpen (&had, hadid, 0);\n\n\t\tfor (i = 0 ; i < details.cFormatTags ; i++)\n\t\t{\n\t\t\tACMFORMATTAGDETAILS\tfmtDetails;\n\n\t\t\tmemset (&fmtDetails, 0, sizeof(fmtDetails));\n\t\t\tfmtDetails.cbStruct = sizeof(fmtDetails);\n\t\t\tfmtDetails.dwFormatTagIndex = i;\n\t\t\tqacmFormatTagDetails (had, &fmtDetails, ACM_FORMATTAGDETAILSF_INDEX);\n\t\t\tif (fmtDetails.dwFormatTag == WAVE_FORMAT_MPEGLAYER3)\n\t\t\t{\n\t\t\t\tCom_DPrintf (\"MP3-capable ACM codec found: %s\\n\", details.szLongName);\n\t\t\t\tmp3_driver = true;\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tqacmDriverClose (had, 0);\n\t}\n\n\treturn true;\n}\n\nqbool Capture_Open (char *filename)\n{\n\tHRESULT\t\t\t\thr;\n\tBITMAPINFOHEADER\tbitmap_info_header;\n\tAVISTREAMINFO\t\tstream_header;\n\tchar\t\t\t\t*fourcc;\n\n\tbytesWritten = 0;\n\tm_video_frame_counter = m_audio_frame_counter = 0;\n\tm_file = NULL;\n\tm_codec_fourcc = 0;\n\tm_compressed_video_stream = m_uncompressed_video_stream = m_audio_stream = NULL;\n\tm_audio_is_mp3 = (qbool)movie_mp3.value;\n\n\tif (*(fourcc = movie_codec.string) != '0')\t// codec fourcc supplied\n\t{\n\t\tm_codec_fourcc = mmioFOURCC (fourcc[0], fourcc[1], fourcc[2], fourcc[3]);\n\t}\n\n\tqAVIFileInit ();\n\thr = qAVIFileOpen (&m_file, filename, OF_WRITE | OF_CREATE, NULL);\n\tif (FAILED(hr))\n\t{\n\t\tCom_Printf (\"ERROR: Couldn't open AVI file\\n\");\n\t\tCapture_Close ();\n\t\treturn false;\n\t}\n\n\t// initialize video data\n\tm_video_frame_size = glwidth * glheight * 3;\n\n\tmemset (&bitmap_info_header, 0, sizeof(bitmap_info_header));\n\tbitmap_info_header.biSize = sizeof(BITMAPINFOHEADER);\n\tbitmap_info_header.biWidth = glwidth;\n\tbitmap_info_header.biHeight = glheight;\n\tbitmap_info_header.biPlanes = 1;\n\tbitmap_info_header.biBitCount = 24;\n\tbitmap_info_header.biCompression = BI_RGB;\n\tbitmap_info_header.biSizeImage = m_video_frame_size;\n\n\tmemset (&stream_header, 0, sizeof(stream_header));\n\tstream_header.fccType = streamtypeVIDEO;\n\tstream_header.fccHandler = m_codec_fourcc;\n\tstream_header.dwScale = 1;\n\tstream_header.dwRate = (unsigned long)(0.5 + movie_fps.value);\n\tstream_header.dwSuggestedBufferSize = bitmap_info_header.biSizeImage;\n\tSetRect (&stream_header.rcFrame, 0, 0, bitmap_info_header.biWidth, bitmap_info_header.biHeight);\n\n\thr = qAVIFileCreateStream (m_file, &m_uncompressed_video_stream, &stream_header);\n\tif (FAILED(hr))\n\t{\n\t\tCom_Printf (\"ERROR: Couldn't create video stream\\n\");\n\t\tCapture_Close ();\n\t\treturn false;\n\t}\n\n\tif (m_codec_fourcc)\n\t{\n\t\tAVICOMPRESSOPTIONS\topts;\n\n\t\tmemset (&opts, 0, sizeof(opts));\n\t\topts.fccType = stream_header.fccType;\n\t\topts.fccHandler = m_codec_fourcc;\n\n\t\t// Make the stream according to compression\n\t\thr = qAVIMakeCompressedStream (&m_compressed_video_stream, m_uncompressed_video_stream, &opts, NULL);\n\t\tif (FAILED(hr))\n\t\t{\n\t\t\tCom_Printf (\"ERROR: Couldn't make compressed video stream\\n\");\n\t\t\tCapture_Close ();\n\t\t\treturn false;\n\t\t}\n\t}\n\n\thr = qAVIStreamSetFormat (Capture_VideoStream(), 0, &bitmap_info_header, bitmap_info_header.biSize);\n\tif (FAILED(hr))\n\t{\n\t\tCom_Printf (\"ERROR: Couldn't set video stream format\\n\");\n\t\tCapture_Close ();\n\t\treturn false;\n\t}\n\n\t// initialize audio data\n\tmemset (&m_wave_format, 0, sizeof(m_wave_format));\n\tm_wave_format.wFormatTag = WAVE_FORMAT_PCM;\n\tm_wave_format.nChannels = 2; // always stereo in Quake sound engine\n\tm_wave_format.nSamplesPerSec = shw ? shw->khz : 0;\n\tm_wave_format.wBitsPerSample = 16; // always 16bit in Quake sound engine\n\tm_wave_format.nBlockAlign = m_wave_format.wBitsPerSample/8 * m_wave_format.nChannels;\n\tm_wave_format.nAvgBytesPerSec = m_wave_format.nSamplesPerSec * m_wave_format.nBlockAlign;\n\n\tmemset (&stream_header, 0, sizeof(stream_header));\n\tstream_header.fccType = streamtypeAUDIO;\n\tstream_header.dwScale = m_wave_format.nBlockAlign;\n\tstream_header.dwRate = stream_header.dwScale * (unsigned long)m_wave_format.nSamplesPerSec;\n\tstream_header.dwSampleSize = m_wave_format.nBlockAlign;\n\n\thr = qAVIFileCreateStream (m_file, &m_audio_stream, &stream_header);\n\tif (FAILED(hr))\n\t{\n\t\tCom_Printf (\"ERROR: Couldn't create audio stream\\n\");\n\t\tCapture_Close ();\n\t\treturn false;\n\t}\n\n\tif (m_audio_is_mp3)\n\t{\n\t\tMMRESULT\tmmr;\n\n\t\t// try to find an MP3 codec\n\t\thad = NULL;\n\t\tmp3_driver = false;\n\t\tqacmDriverEnum (acmDriverEnumCallback, 0, 0);\n\t\tif (!mp3_driver)\n\t\t{\n\t\t\tCom_Printf (\"ERROR: Couldn't find any MP3 decoder\\n\");\n\t\t\tCapture_Close ();\n\t\t\treturn false;\n\t\t}\n\n\t\tmemset (&mp3_format, 0, sizeof(mp3_format));\n\t\tmp3_format.wfx.wFormatTag = WAVE_FORMAT_MPEGLAYER3;\n\t\tmp3_format.wfx.nChannels = 2;\n\t\tmp3_format.wfx.nSamplesPerSec = shw->khz;\n\t\tmp3_format.wfx.wBitsPerSample = 0;\n\t\tmp3_format.wfx.nBlockAlign = 1;\n\t\tmp3_format.wfx.nAvgBytesPerSec = movie_mp3_kbps.value * 125;\n\t\tmp3_format.wfx.cbSize = MPEGLAYER3_WFX_EXTRA_BYTES;\n\t\tmp3_format.wID = MPEGLAYER3_ID_MPEG;\n\t\tmp3_format.fdwFlags = MPEGLAYER3_FLAG_PADDING_OFF;\n\t\tmp3_format.nBlockSize = mp3_format.wfx.nAvgBytesPerSec / movie_fps.value;\n\t\tmp3_format.nFramesPerBlock = 1;\n\t\tmp3_format.nCodecDelay = 1393;\n\n\t\thstr = NULL;\n\t\tif ((mmr = qacmStreamOpen(&hstr, had, &m_wave_format, &mp3_format.wfx, NULL, 0, 0, 0)))\n\t\t{\n\t\t\tswitch (mmr)\n\t\t\t{\n\t\t\tcase MMSYSERR_INVALPARAM:\n\t\t\t\tCom_Printf (\"ERROR: Invalid parameters passed to acmStreamOpen\\n\");\n\t\t\t\tCapture_Close ();\n\t\t\t\treturn false;\n\n\t\t\tcase ACMERR_NOTPOSSIBLE:\n\t\t\t\tCom_Printf (\"ERROR: No ACM filter found capable of decoding MP3\\n\");\n\t\t\t\tCapture_Close ();\n\t\t\t\treturn false;\n\n\t\t\tdefault:\n\t\t\t\tCom_Printf (\"ERROR: Couldn't open ACM decoding stream\\n\");\n\t\t\t\tCapture_Close ();\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\thr = qAVIStreamSetFormat (m_audio_stream, 0, &mp3_format, sizeof(MPEGLAYER3WAVEFORMAT));\n\t\tif (FAILED(hr))\n\t\t{\n\t\t\tCom_Printf (\"ERROR: Couldn't set audio stream format\\n\");\n\t\t\tCapture_Close ();\n\t\t\treturn false;\n\t\t}\n\t}\n\telse\n\t{\n\t\thr = qAVIStreamSetFormat (m_audio_stream, 0, &m_wave_format, sizeof(WAVEFORMATEX));\n\t\tif (FAILED(hr))\n\t\t{\n\t\t\tCom_Printf (\"ERROR: Couldn't set audio stream format\\n\");\n\t\t\tCapture_Close ();\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nvoid Capture_Close (void)\n{\n\tif (m_uncompressed_video_stream) {\n\t\tqAVIStreamRelease (m_uncompressed_video_stream);\n\t\tm_uncompressed_video_stream = 0;\n\t}\n\tif (m_compressed_video_stream) {\n\t\tqAVIStreamRelease (m_compressed_video_stream);\n\t\tm_compressed_video_stream = 0;\n\t}\n\tif (m_audio_stream) {\n\t\tqAVIStreamRelease (m_audio_stream);\n\t\tm_audio_stream = 0;\n\t}\n\tif (m_audio_is_mp3) {\n\t\tqacmStreamClose (hstr, 0);\n\t\tqacmDriverClose (had, 0);\n\t\thstr = 0;\n\t\thad = 0;\n\t}\n\tif (m_file) {\n\t\tqAVIFileRelease (m_file);\n\t\tm_file = 0;\n\t}\n\n\tbytesWritten = 0;\n\tqAVIFileExit ();\n}\n\nvoid Capture_WriteVideo (byte *pixel_buffer, int size)\n{\n\tHRESULT\thr;\n\tLONG frameBytesWritten;\n\n\t// Check frame size (TODO: other things too?) hasn't changed\n\tif (m_video_frame_size != size)\n\t{\n\t\tCom_Printf (\"ERROR: Frame size changed\\n\");\n\t\treturn;\n\t}\n\n\tif (!Capture_VideoStream())\n\t{\n\t\tCom_Printf (\"ERROR: Video stream is NULL\\n\");\n\t\treturn;\n\t}\n\n\t// Write the pixel buffer to to the AVIFile, one sample/frame at the time\n\t// set each frame to be a keyframe (it doesn't depend on previous frames).\n\thr = qAVIStreamWrite (Capture_VideoStream(), m_video_frame_counter++, 1, pixel_buffer, m_video_frame_size, AVIIF_KEYFRAME, NULL, &frameBytesWritten);\n\tif (FAILED(hr))\n\t{\n\t\tCom_Printf (\"ERROR: Couldn't write to AVI file\\n\");\n\t\treturn;\n\t}\n\n\tbytesWritten += frameBytesWritten;\n}\n\n#ifndef ACM_STREAMSIZEF_SOURCE\n#define ACM_STREAMSIZEF_SOURCE          0x00000000L\n#endif\n#ifndef ACM_STREAMCONVERTF_BLOCKALIGN\n#define ACM_STREAMCONVERTF_BLOCKALIGN   0x00000004\n#define ACM_STREAMCONVERTF_START        0x00000010\n#define ACM_STREAMCONVERTF_END          0x00000020\n#endif\n\nvoid Capture_WriteAudio (int samples, byte *sample_buffer)\n{\n\tHRESULT        hr = E_UNEXPECTED;\n\tLONG           frameBytesWritten;\n\tunsigned long  sample_bufsize;\n\n\tif (!m_audio_stream) {\n\t\tCom_Printf (\"ERROR: Audio stream is NULL\\n\");\n\t\treturn;\n\t}\n\n\tsample_bufsize = samples * m_wave_format.nBlockAlign;\n\tif (m_audio_is_mp3) {\n\t\tMMRESULT\tmmr;\n\t\tbyte\t\t*mp3_buffer;\n\t\tunsigned long\tmp3_bufsize;\n\n\t\tif ((mmr = qacmStreamSize (hstr, sample_bufsize, &mp3_bufsize, ACM_STREAMSIZEF_SOURCE))) {\n\t\t\tCom_Printf (\"ERROR: Couldn't get mp3bufsize\\n\");\n\t\t\treturn;\n\t\t}\n\t\tif (!mp3_bufsize) {\n\t\t\tCom_Printf (\"ERROR: mp3bufsize is zero\\n\");\n\t\t\treturn;\n\t\t}\n\t\tmp3_buffer = (byte *)Q_calloc (mp3_bufsize, 1);\n\n\t\tmemset (&strhdr, 0, sizeof (strhdr));\n\t\tstrhdr.cbStruct = sizeof (strhdr);\n\t\tstrhdr.pbSrc = sample_buffer;\n\t\tstrhdr.cbSrcLength = sample_bufsize;\n\t\tstrhdr.pbDst = mp3_buffer;\n\t\tstrhdr.cbDstLength = mp3_bufsize;\n\n\t\tif ((mmr = qacmStreamPrepareHeader (hstr, &strhdr, 0))) {\n\t\t\tCom_Printf (\"ERROR: Couldn't prepare header\\n\");\n\t\t\tQ_free (mp3_buffer);\n\t\t\treturn;\n\t\t}\n\n\t\tif ((mmr = qacmStreamConvert (hstr, &strhdr, ACM_STREAMCONVERTF_BLOCKALIGN))) {\n\t\t\tCom_Printf (\"ERROR: Couldn't convert audio stream\\n\");\n\t\t\tgoto clean;\n\t\t}\n\n\t\thr = qAVIStreamWrite (m_audio_stream, m_audio_frame_counter++, 1, mp3_buffer, strhdr.cbDstLengthUsed, AVIIF_KEYFRAME, NULL, &frameBytesWritten);\n\t\tbytesWritten += frameBytesWritten;\n\n\tclean:\n\t\tif ((mmr = qacmStreamUnprepareHeader (hstr, &strhdr, 0))) {\n\t\t\tCom_Printf (\"ERROR: Couldn't unprepare header\\n\");\n\t\t\tQ_free (mp3_buffer);\n\t\t\treturn;\n\t\t}\n\n\t\tQ_free (mp3_buffer);\n\t}\n\telse {\n\t\t// The audio is not in MP3 format, just write the WAV data to the avi.\n\t\thr = qAVIStreamWrite (m_audio_stream, m_audio_frame_counter++, 1, sample_buffer, samples * m_wave_format.nBlockAlign, AVIIF_KEYFRAME, NULL, &frameBytesWritten);\n\t\tbytesWritten += frameBytesWritten;\n\t}\n\n\tif (FAILED (hr)) {\n\t\tCom_Printf (\"ERROR: Couldn't write to AVI file\\n\");\n\t\treturn;\n\t}\n}\n\nLONG Movie_CurrentLength (void)\n{\n\treturn bytesWritten;\n}\n\nqbool ValidateMovieCodec (char* name)\n{\n\tHKEY registryKey;\n\tint valueIndex;\n\tchar valueName[64];\n\tDWORD valueLength = sizeof (valueName);\n\tDWORD lookupLength;\n\n\t// Uncompressed stream\n\tif (name[0] == 0 || !strcmp (name, \"0\")) {\n\t\treturn true;\n\t}\n\n\t// Open / Create the key.\n\tif (RegCreateKeyEx (HKEY_LOCAL_MACHINE, \"SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Drivers32\",\n\t\t0, NULL, REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE, NULL, &registryKey, NULL)) {\n\t\tCom_Printf_State (PRINT_WARNING, \"Could not read list of valid codecs\\n\");\n\t\treturn true; // assume ok, will find out later\n\t}\n\n\tstrlcpy (valueName, \"vidc.\", sizeof (valueName));\n\tstrlcat (valueName, name, sizeof (valueName));\n\tif (RegQueryValueEx(registryKey, valueName, NULL, NULL, NULL, &lookupLength) == ERROR_SUCCESS) {\n\t\tRegCloseKey (registryKey);\n\t\treturn true;\n\t}\n\n\tCon_Printf (\"Warning: '%s' not found in list of valid codecs.\\n\", name);\n\tCon_Printf (\"The following codecs are installed:\\n\", name);\n\tfor (valueIndex = 0; RegEnumValue (registryKey, valueIndex, valueName, &valueLength, NULL, NULL, NULL, NULL) == ERROR_SUCCESS; valueIndex++, valueLength = sizeof (valueName)) {\n\t\tif (! strncmp (valueName, \"vidc.\", 5)) {\n\t\t\tCon_Printf (\"  %s\\n\", valueName + 5);\n\t\t}\n\t}\n\n\tRegCloseKey (registryKey);\n\treturn false;\n}\n\nvoid OnChange_movie_codec (cvar_t *var, char *string, qbool *cancel)\n{\n\t// Print warning if codec specified isn't installed\n\tValidateMovieCodec (string);\n}\n"
  },
  {
    "path": "src/movie_avi.h",
    "content": "/*\nCopyright (C) 2002 Quake done Quick\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifndef __MOVIE_AVI_H_\n\n#define __MOVIE_AVI_H_\n\nvoid Capture_InitAVI (void);\nvoid Capture_InitACM (void);\nqbool Capture_Open (char *filename);\nvoid Capture_WriteVideo (byte *pixel_buffer, int size);\nvoid Capture_WriteAudio (int samples, byte *sample_buffer);\nvoid Capture_Close (void);\n\n#endif\n"
  },
  {
    "path": "src/mvd_autotrack.c",
    "content": "/*\nCopyright (C) 2001-2002 jogihoogi\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: mvd_autotrack.c,v 1.6 2007-09-27 23:18:03 johnnycz Exp $\n*/\n\n// MultiView Demo Autotrack system\n// when observing a demo or QTV playback, will switch to best players automatically\n\n#include \"quakedef.h\"\n#include \"parser.h\"\n#include \"localtime.h\"\n#include \"mvd_utils_common.h\"\n\n// mvd_autotrack cvars\ncvar_t mvd_autotrack = {\"mvd_autotrack\", \"0\"};\ncvar_t mvd_autotrack_instant = {\"mvd_autotrack_instant\", \"0\"};\ncvar_t mvd_autotrack_1on1 = {\"mvd_autotrack_1on1\", \"%a * %A + 50 * %W + %p + %f\"};\ncvar_t mvd_autotrack_1on1_values = {\"mvd_autotrack_1on1_values\", \"1 2 3 2 3 5 8 8 1 2 3 0 0 0\"};\ncvar_t mvd_autotrack_2on2 = {\"mvd_autotrack_2on2\", \"%a * %A + 50 * %W + %p + %f\"};\ncvar_t mvd_autotrack_2on2_values = {\"mvd_autotrack_2on2_values\", \"1 2 3 2 3 5 8 8 1 2 3 500 900 1000\"};\ncvar_t mvd_autotrack_4on4 = {\"mvd_autotrack_4on4\", \"%a * %A + 50 * %W + %p + %f\"};\ncvar_t mvd_autotrack_4on4_values = {\"mvd_autotrack_4on4_values\", \"1 2 4 2 4 6 10 10 1 2 3 500 900 1000\"};\ncvar_t mvd_autotrack_custom = {\"mvd_autotrack_custom\", \"%a * %A + 50 * %W + %p + %f\"};\ncvar_t mvd_autotrack_custom_values = {\"mvd_autotrack_custom_values\", \"1 2 3 2 3 4 6 6 1 2 3 500 900 1000\"};\ncvar_t mvd_autotrack_lockteam = {\"mvd_autotrack_lockteam\", \"0\"};\n\ncvar_t mvd_multitrack_1 = {\"mvd_multitrack_1\", \"%f\"};\ncvar_t mvd_multitrack_1_values = {\"mvd_multitrack_1_values\", \"1 2 3 2 3 5 8 8 1 2 3 0 0 0\"};\ncvar_t mvd_multitrack_2 = {\"mvd_multitrack_2\", \"%W\"};\ncvar_t mvd_multitrack_2_values = {\"mvd_multitrack_2_values\", \"1 2 3 2 3 5 8 8 1 2 3 0 0 0\"};\ncvar_t mvd_multitrack_3 = {\"mvd_multitrack_3\", \"%h\"};\ncvar_t mvd_multitrack_3_values = {\"mvd_multitrack_3_values\", \"1 2 3 2 3 5 8 8 1 2 3 0 0 0\"};\ncvar_t mvd_multitrack_4 = {\"mvd_multitrack_4\", \"%A\"};\ncvar_t mvd_multitrack_4_values = {\"mvd_multitrack_4_values\", \"1 2 3 2 3 5 8 8 1 2 3 0 0 0\"};\n\n#define PL_VALUES_COUNT 14\nstatic float pl_values[PL_VALUES_COUNT];\n#define axe_val     (pl_values[0])\n#define sg_val      (pl_values[1])\n#define ssg_val     (pl_values[2])\n#define ng_val      (pl_values[3])\n#define sng_val     (pl_values[4])\n#define gl_val      (pl_values[5])\n#define rl_val      (pl_values[6])\n#define lg_val      (pl_values[7])\n#define ga_val      (pl_values[8])\n#define ya_val      (pl_values[9])\n#define ra_val      (pl_values[10])\n#define ring_val    (pl_values[11])\n#define quad_val    (pl_values[12])\n#define pent_val    (pl_values[13])\n\nint multitrack_id_1 = 0;\nint multitrack_id_2 = 0;\nint multitrack_id_3 = 0;\nint multitrack_id_4 = 0;\n\nchar *multitrack_val ;\nchar *multitrack_str ;\nint last_track = 0;\n\nstatic int currentplayer_num;\n\nstatic int MVD_AutoTrackBW(int i){\n\textern cvar_t tp_weapon_order;\n\tint j;\n\tchar *t[] = {tp_weapon_order.string, \"78654321\", NULL}, **s;\n\n\tfor (s = t; *s; s++) {\n\t\tfor (j = 0 ; j < strlen(*s) ; j++) {\n\t\t\tswitch ((*s)[j]) {\n\t\t\t\tcase '1': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_AXE) return axe_val; break;\n\t\t\t\tcase '2': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_SHOTGUN) return sg_val; break;\n\t\t\t\tcase '3': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_SUPER_SHOTGUN) return ssg_val; break;\n\t\t\t\tcase '4': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_NAILGUN) return ng_val; break;\n\t\t\t\tcase '5': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_SUPER_NAILGUN) return sng_val; break;\n\t\t\t\tcase '6': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_GRENADE_LAUNCHER) return gl_val; break;\n\t\t\t\tcase '7': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER) return rl_val; break;\n\t\t\t\tcase '8': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_LIGHTNING) return lg_val; break;\n\t\t\t}\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic expr_val MVD_Var_Vals(const char *n)\n{\n\tint i = currentplayer_num;\n    int stats = mvd_new_info[i].p_info->stats[STAT_ITEMS];\n\tdouble bp_at, bp_pw;\n\n\t// get armortype\n\tif      (stats & IT_ARMOR1) bp_at = ga_val;\n\telse if (stats & IT_ARMOR2) bp_at = ya_val;\n\telse if (stats & IT_ARMOR3)\tbp_at = ra_val;\n\telse                        bp_at = 0;\n\n\t// get powerup type\n\tbp_pw=0;\n\tif (stats & IT_QUAD)            bp_pw += quad_val;\n\tif (stats & IT_INVULNERABILITY)\tbp_pw += pent_val;\n\tif (stats & IT_INVISIBILITY)    bp_pw += ring_val;\n\n\t// not implemented yet:\n\t// D - average quad run time\n\t// e - average quad run frags\n\t// E - average quad run teamfrags\n\t// u - average pent run frags\n\t// U - average pent run teamfrags\n\t// q - Axe kills\n\t// Q - Shotgun kills\n\t// r - Supershotgun kills\n\t// R - Nailgun kills\n\t// s - Supernailgun kills\n\t// S - Grenadelauncher kills\n\t// t - Rocketlauncher kills\n\t// T - Lightninggun kills\n\n\tswitch (*n) {\n\t\t/// armor ammount\n\t\tcase 'a': return Get_Expr_Double(mvd_new_info[i].p_info->stats[STAT_ARMOR]);\n\t\t\t  /// armor type\n\t\tcase 'A': return Get_Expr_Double(bp_at);\n\t\t\t  /// current run time\n\t\tcase 'c': return Get_Expr_Double(mvd_new_info[i].mvdinfo.runs[mvd_new_info[i].mvdinfo.run].time);\n\t\t\t  /// current run frags\n\t\tcase 'C': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.runs[mvd_new_info[i].mvdinfo.run].frags);\n\t\t\t  /// current run teamfrags\n\t\tcase 'd': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.runs[mvd_new_info[i].mvdinfo.run].teamfrags);\n\n\t\t\t  /// frags\n\t\tcase 'f': return Get_Expr_Integer(mvd_new_info[i].p_info->frags);\n\n\t\t\t  /// teamfrags\n\t\tcase 'F': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.teamfrags);\n\n\t\t\t  /// deaths\n\t\tcase 'g': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.das.deathcount);\n\t\t\t  /// health\n\t\tcase 'h': return Get_Expr_Integer(mvd_new_info[i].p_info->stats[STAT_HEALTH]);\n\n\t\t\t  /// taken super-shotguns\n\t\tcase 'I': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[SSG_INFO].count);\n\t\t\t  /// taken nailguns\n\t\tcase 'j': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[NG_INFO].count);\n\t\t\t  /// taken super-nailguns\n\t\tcase 'J': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[SNG_INFO].count);\n\t\t\t  /// taken grenade launchers\n\t\tcase 'k': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[GL_INFO].count);\n\t\t\t  /// # of lost grenade launchers\n\t\tcase 'K': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[GL_INFO].lost);\n\t\t\t  /// # of taken rocket launchers\n\t\tcase 'l': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[RL_INFO].count);\n\t\t\t  /// # of lost rocket launchers\n\t\tcase 'L': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[RL_INFO].lost);\n\t\tcase 'm': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[LG_INFO].count);\n\t\tcase 'M': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[LG_INFO].lost);\n\t\tcase 'n': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[MH_INFO].count);\n\t\tcase 'N': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[GA_INFO].count);\n\t\tcase 'o': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[YA_INFO].count);\n\t\tcase 'O': return Get_Expr_Integer(mvd_new_info[i].mvdinfo.itemstats[RA_INFO].count);\n\t\tcase 'p': return Get_Expr_Double(bp_pw);\n\n\t\t\t  // id, so that player are always distinguishable by something\n\t\tcase 'u': return Get_Expr_Integer(i);\n\t\t\t  // v - average run time\n\t\tcase 'v': return Get_Expr_Double(mvd_new_info[i].mvdinfo.run_stats.all.avg_time);\n\t\t\t  // V - average run frags\n\t\tcase 'V': return Get_Expr_Double(mvd_new_info[i].mvdinfo.run_stats.all.avg_frags);\n\t\t\t  // w - average run teamfrags\n\t\tcase 'w': return Get_Expr_Double(mvd_new_info[i].mvdinfo.run_stats.all.avg_teamfrags);\n\n\t\tcase 'W': return Get_Expr_Integer(MVD_AutoTrackBW(i));\n\t\tcase 'x': return Get_Expr_Double(mvd_new_info[i].mvdinfo.itemstats[RING_INFO].count);\n\t\tcase 'X': return Get_Expr_Double(mvd_new_info[i].mvdinfo.itemstats[RING_INFO].lost);\n\t\tcase 'y': return Get_Expr_Double(mvd_new_info[i].mvdinfo.itemstats[QUAD_INFO].count);\n\t\tcase 'Y': return Get_Expr_Double(mvd_new_info[i].mvdinfo.itemstats[QUAD_INFO].lost);\n\t\tcase 'z': return Get_Expr_Double(mvd_new_info[i].mvdinfo.itemstats[PENT_INFO].count);\n\t}\n\treturn Get_Expr_Integer(0);\n}\n\nstatic const parser_extra mvd_pars_extra = { MVD_Var_Vals, NULL };\n\nstatic void MVD_GetValuesAndEquation(const char** v, const char** e) {\n#define RET(x) { *v = mvd_autotrack_##x##_values.string; *e = mvd_autotrack_##x.string; }\n\tif (mvd_autotrack.integer == 1) {\n\t\tif      (mvd_cg_info.gametype == 0) RET(1on1)\n\t\telse if (mvd_cg_info.gametype == 1) RET(2on2)\n\t\telse if (mvd_cg_info.gametype == 3) RET(4on4)\n\t\telse\t\t\t\t\t\t\t\tRET(4on4)\n\t}\n\telse if (mvd_autotrack.integer == 2)\tRET(custom)\n\telse if (mvd_autotrack.integer == 3 && cl_multiview.value) {\n\t\t*v = multitrack_val; *e = multitrack_str; \n\t} else\t\t\t\t\t\t\t\t\tRET(4on4)\n#undef RET\n}\n\nstatic void MVD_UpdatePlayerValues(void)\n{\n\tint eval_error, i;\n    const char* eq;\n\tconst char* vals;\n    double value;\n\n\tMVD_GetValuesAndEquation(&vals, &eq);\n\n    // will extract user string \"1 5 8 100.4 ...\" into pl_values array which is\n    // later accessed via macros like ra_val, quad_val, rl_val, ...\n    if (COM_GetFloatTokens(vals, pl_values, PL_VALUES_COUNT) != PL_VALUES_COUNT)\n    {\n\t    Com_Printf(\"mvd_autotrack aborting due to wrong use of mvd_autotrack_*_value\\n\");\n\t\tCvar_SetValue(&mvd_autotrack,0);\n\t\treturn;\n    }\n\n\tfor ( i=0; i<mvd_cg_info.pcount ; i++ )\n    {\n\t\t// store global variable which is used in MVD_Var_Vals\n\t\tcurrentplayer_num = i;\n\t\teval_error = Expr_Eval_Double(eq, &mvd_pars_extra, &value);\n\n\t\tif (eval_error != EXPR_EVAL_SUCCESS) {\n\t\t\tCom_Printf(\"Expression evaluation error: %s\\n\", Parser_Error_Description(eval_error));\n\t\t\tCvar_SetValue(&mvd_autotrack,0);\n\t\t\treturn;\n\t\t}\n\n\t\tmvd_new_info[i].value = value;\n\t}\n}\n\nstatic qbool MVD_LockTeamIgnore(player_info_t* plr)\n{\n\tint tracked;\n\n\tif (!mvd_autotrack_lockteam.integer || !cl.teamplay) {\n\t\treturn false;\n\t}\n\n\ttracked = CL_MultiviewAutotrackSlot();\n\n\treturn plr == NULL || (tracked >= 0 && tracked < 31 && strcmp(plr->team, cl.players[tracked].team));\n}\n\nstatic int MVD_FindId_By_TrackId(int track_id)\n{\n\tint i;\n\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\n\t\tif (mvd_new_info[i].id == track_id) {\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int MVD_GetBestPlayer(void)\n{\n\tint initial_track, initial_id, i, bp_id;\n\tfloat bp_val = -1000;\n\tqbool candidate_found = false;\n\n\tinitial_track = 0;\n\tif (last_track >= 0 && last_track < mvd_cg_info.pcount && mvd_new_info[last_track].p_info) {\n\t\tinitial_track = last_track;\n\t}\n\n\tinitial_id = MVD_FindId_By_TrackId(initial_track);\n\n\tif (!MVD_LockTeamIgnore(mvd_new_info[initial_track].p_info)) {\n\t\tbp_val = mvd_new_info[initial_id].value;\n\t\tcandidate_found = true;\n\t}\n\tbp_id = mvd_new_info[initial_id].id;\n\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\n\t\tif (MVD_LockTeamIgnore(mvd_new_info[i].p_info)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!candidate_found || bp_val < mvd_new_info[i].value) {\n\t\t\tbp_val = mvd_new_info[i].value;\n\t\t\tbp_id = mvd_new_info[i].id;\n\t\t\tcandidate_found = true;\n\t\t}\n\t}\n\treturn bp_id;\n}\n\nstatic int MVD_FindBestPlayer(void)\n{\n\tMVD_UpdatePlayerValues();\n\treturn MVD_GetBestPlayer();\n}\n\n#define HASRL(x) (x & IT_ROCKET_LAUNCHER)\n#define HASPENT(x) (x & IT_INVULNERABILITY)\n#define HASQUAD(x) (x & IT_QUAD)\n#define HASWEAPON(x) ((x & IT_ROCKET_LAUNCHER ) || (x & IT_LIGHTNING))\nint MVD_GetBetterPlayerSimple(int a, int b)\n{\n\tint sa = cl.players[a].stats[STAT_ITEMS];\n\tint sb = cl.players[b].stats[STAT_ITEMS];\n\n\tif (HASPENT(sa) && HASRL(sa)) return a;\n\tif (HASPENT(sb) && HASRL(sb)) return b;\n\tif (HASQUAD(sa) && HASWEAPON(sa)) return a;\n\tif (HASQUAD(sb) && HASWEAPON(sb)) return b;\n\tif (HASPENT(sa)) return a;\n\tif (HASPENT(sb)) return b;\n\tif (HASQUAD(sa)) return a;\n\tif (HASQUAD(sb)) return b;\n\tif (HASWEAPON(sa)) return a;\n\tif (HASWEAPON(sb)) return b;\n\n\treturn a;\n}\n\nstatic int MVD_FindBestPlayerSimple(void)\n{\n\tint i, b;\n\tint tracked = CL_MultiviewAutotrackSlot();\n\n\tb = tracked;\n\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\n\t\tif (MVD_LockTeamIgnore(mvd_new_info[i].p_info)) {\n\t\t\tcontinue;\n\t\t}\n\t\tb = MVD_GetBetterPlayerSimple(b, mvd_new_info[i].id);\n\t}\n\n\treturn b;\n}\n\nstatic qbool MVD_TrackedHasNoWeapon(int pov) {\n\tint stat = cl.players[pov].stats[STAT_ITEMS];\n\tif ((stat & IT_ROCKET_LAUNCHER) || (stat & IT_LIGHTNING)) {\n\t\treturn false;\n\t}\n\telse {\n\t\treturn true;\n\t}\n}\n\nstatic qbool MVD_SomeoneHasWeapon(void) {\n\tint i, stats;\n\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\n\t\tif (MVD_LockTeamIgnore(mvd_new_info[i].p_info)) {\n\t\t\tcontinue;\n\t\t}\n\t\tstats = mvd_new_info[i].p_info->stats[STAT_ITEMS];\n\t\tif ((stats & IT_ROCKET_LAUNCHER) || (stats & IT_LIGHTNING)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nstatic qbool MVD_TrackedHasNoPowerup(int pov) {\n\tint stats = cl.players[pov].stats[STAT_ITEMS];\n\tif ((stats & IT_QUAD) || (stats & IT_INVULNERABILITY)) {\n\t\treturn false;\n\t}\n\telse {\n\t\treturn true;\n\t}\n}\n\nstatic qbool MVD_SomeoneHasPowerup(void) {\n\tint i, stats;\n\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\n\t\tif (MVD_LockTeamIgnore(mvd_new_info[i].p_info)) {\n\t\t\tcontinue;\n\t\t}\n\t\tstats = mvd_new_info[i].p_info->stats[STAT_ITEMS];\n\t\tif ((stats & IT_QUAD) || (stats & IT_INVULNERABILITY)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nstatic qbool MVD_SomeoneHasPentWithRL(void) {\n\tint i, stats;\n\n\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\n\t\tif (MVD_LockTeamIgnore(mvd_new_info[i].p_info)) {\n\t\t\tcontinue;\n\t\t}\n\t\tstats = mvd_new_info[i].p_info->stats[STAT_ITEMS];\n\t\tif ((stats & IT_ROCKET_LAUNCHER) && (stats & IT_INVULNERABILITY)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nstatic qbool MVD_SwitchMoment(void) {\n\tint pov = CL_MultiviewAutotrackSlot ();\n\n\tif (MVD_TrackedHasNoWeapon(pov) && MVD_SomeoneHasWeapon()) {\n\t\treturn true;\n\t}\n\telse if (MVD_TrackedHasNoPowerup(pov) && MVD_SomeoneHasPowerup()) {\n\t\treturn true;\n\t}\n\telse if (MVD_SomeoneHasPentWithRL()) {\n\t\treturn true;\n\t}\n\telse {\n\t\treturn false;\n\t}\n}\n\nvoid MVD_AutoTrack(void) {\n\tchar arg[64];\n\tint id;\n\tstatic double lastupdate = 0;\n\n\t#ifdef DEBUG\n\tprintf(\"MVD_AutoTrack Started\\n\");\n\t#endif\n\n\tif (!mvd_autotrack.value)\n\t\treturn;\n\n\tif (cl.standby || cl.countdown)\n\t\treturn;\n\n\t// no need to recalculate the values in every frame\n\tif (cls.realtime - lastupdate < 0.5)\n\t\treturn;\n\n\tlastupdate = cls.realtime;\n\n\tif (mvd_autotrack.value == 3 && cl_multiview.value > 0){\n\t\tif (cl_multiview.value >= 1 ){\n\t\t\tmultitrack_str = mvd_multitrack_1.string;\n\t\t\tmultitrack_val = mvd_multitrack_1_values.string;\n\t\t\tid = MVD_FindBestPlayer();\n\t\t\tif (id != multitrack_id_1){\n\t\t\t\tsnprintf(arg, sizeof (arg), \"track1 %i \\n\",id);\n\t\t\t\tCbuf_AddText(arg);\n\t\t\t\tmultitrack_id_1 = id;\n\t\t\t}\n\t\t}\n\t\tif (cl_multiview.value >= 2 ){\n\t\t\tmultitrack_str = mvd_multitrack_2.string;\n\t\t\tmultitrack_val = mvd_multitrack_2_values.string;\n\t\t\tid = MVD_FindBestPlayer();\n\t\t\tif (id != multitrack_id_2){\n\t\t\t\tsnprintf(arg, sizeof (arg), \"track2 %i \\n\",id);\n\t\t\t\tCbuf_AddText(arg);\n\t\t\t\tmultitrack_id_2 = id;\n\t\t\t}\n\t\t}\n\t\tif (cl_multiview.value >= 3 ){\n\t\t\tmultitrack_str = mvd_multitrack_3.string;\n\t\t\tmultitrack_val = mvd_multitrack_3_values.string;\n\t\t\tid = MVD_FindBestPlayer();\n\t\t\tif (id != multitrack_id_3){\n\t\t\t\tsnprintf(arg, sizeof (arg), \"track3 %i \\n\",id);\n\t\t\t\tCbuf_AddText(arg);\n\t\t\t\tmultitrack_id_3 = id;\n\t\t\t}\n\t\t}\n\t\tif (cl_multiview.value >= 4 ){\n\t\t\tmultitrack_str = mvd_multitrack_4.string;\n\t \t\tmultitrack_val = mvd_multitrack_4_values.string;\n\t\t\tid = MVD_FindBestPlayer();\n\t\t\tif (id != multitrack_id_4){\n\t\t\t\tsnprintf(arg, sizeof (arg), \"track4 %i \\n\",id);\n\t\t\t\tCbuf_AddText(arg);\n\t\t\t\tmultitrack_id_4 = id;\n\t\t\t}\n\t\t}\n\t}\n\telse if (mvd_autotrack_instant.integer || MVD_SwitchMoment())// mvd_autotrack is 1 or 2 or 3\n\t{\n\t\tif (mvd_autotrack.integer == 4) {\n\t\t\tid = MVD_FindBestPlayerSimple();\n\t\t}\n\t\telse {\n\t\t\tid = MVD_FindBestPlayer();\n\t\t}\n\n\t\tif (id != last_track || CL_MultiviewAutotrackSlot() != id) {\n\t\t\tsnprintf(arg, sizeof (arg), \"track \\\"%s\\\"\\n\", cl.players[id].name);\n\t\t\tCbuf_AddText(arg);\n\t\t\tlast_track = id;\n\t\t}\n\t}\n\t#ifdef DEBUG\n\tprintf(\"MVD_AutoTrack Stopped\\n\");\n\t#endif\n}\n\nvoid MVD_AutoTrack_Init(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_MVD);\n\tCvar_Register (&mvd_autotrack);\n\tCvar_Register (&mvd_autotrack_instant);\n\tCvar_Register (&mvd_autotrack_1on1);\n\tCvar_Register (&mvd_autotrack_1on1_values);\n\tCvar_Register (&mvd_autotrack_2on2);\n\tCvar_Register (&mvd_autotrack_2on2_values);\n\tCvar_Register (&mvd_autotrack_4on4);\n\tCvar_Register (&mvd_autotrack_4on4_values);\n\tCvar_Register (&mvd_autotrack_custom);\n\tCvar_Register (&mvd_autotrack_custom_values);\n\tCvar_Register (&mvd_autotrack_lockteam);\n\n\tCvar_Register (&mvd_multitrack_1);\n\tCvar_Register (&mvd_multitrack_1_values);\n\tCvar_Register (&mvd_multitrack_2);\n\tCvar_Register (&mvd_multitrack_2_values);\n\tCvar_Register (&mvd_multitrack_3);\n\tCvar_Register (&mvd_multitrack_3_values);\n\tCvar_Register (&mvd_multitrack_4);\n\tCvar_Register (&mvd_multitrack_4_values);\n\tCvar_ResetCurrentGroup();\n}\n"
  },
  {
    "path": "src/mvd_utils.c",
    "content": "/*\r\nCopyright (C) 2001-2002 jogihoogi\r\n\r\nThis program is free software; you can redistribute it and/or\r\nmodify it under the terms of the GNU General Public License\r\nas published by the Free Software Foundation; either version 2\r\nof the License, or (at your option) any later version.\r\n\r\nThis program is distributed in the hope that it will be useful,\r\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\r\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\r\n\r\nSee the included (GNU.txt) GNU General Public License for more details.\r\n\r\nYou should have received a copy of the GNU General Public License\r\nalong with this program; if not, write to the Free Software\r\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\r\n\r\n$Id: mvd_utils.c,v 1.57 2007-10-11 17:56:47 johnnycz Exp $\r\n*/\r\n\r\n// core module of the group of MVD tools: mvd_utils, mvd_xmlstats, mvd_autotrack\r\n\r\n#include \"quakedef.h\"\r\n#include <math.h>\r\n#include \"parser.h\"\r\n#include \"localtime.h\"\r\n#include \"gl_model.h\"\r\n#include \"teamplay.h\"\r\n#include \"utils.h\"\r\n#include \"mvd_utils_common.h\"\r\n#include \"Ctrl.h\"\r\n#include \"sbar.h\"\r\n#include \"vx_tracker.h\"\r\n\r\n#define MENTION_PICKED_UP_ITEM  1\r\n#define MENTION_PICKED_UP_PACK  2\r\n#define MENTION_DIED_WITH_ITEM -1\r\n#define MENTION_ITEM_RAN_OUT   -2\r\n#define MENTION_DROPPED_ITEM   -3\r\n\r\n#define MAX_ANNOUNCER_LINES 16\r\n#define MAX_ANNOUNCER_LINE_TIME 10\r\n#define MAX_ANNOUNCER_LINE_TIME_FADE 8\r\nstatic char announcer_line_strings[MAX_ANNOUNCER_LINES][256];\r\nstatic double announcer_line_times[MAX_ANNOUNCER_LINES];\r\nstatic int announcer_lines;\r\n\r\nstatic const char* MVD_AnnouncerTeamPlayerName(player_info_t* info);\r\n\r\n// Can contain 'pack'\r\n#define MVD_ANNOUNCER_ITEM_LENGTH 9\r\n\r\nextern cvar_t tp_name_quad, tp_name_pent, tp_name_ring, tp_name_separator, tp_name_backpack, tp_name_suit;\r\nextern cvar_t tp_name_axe, tp_name_sg, tp_name_ssg, tp_name_ng, tp_name_sng, tp_name_gl, tp_name_rl, tp_name_lg, tp_name_ga, tp_name_ya, tp_name_ra, tp_name_mh;\r\n\r\nmvd_gt_info_t mvd_gt_info[mvd_gt_types] = {\r\n\t{gt_1on1,\"duel\"},\r\n\t{gt_2on2,\"2on2\"},\r\n\t{gt_3on3,\"3on3\"},\r\n\t{gt_4on4,\"4on4\"},\r\n\t{gt_unknown,\"unknown\"},\r\n};\r\n\r\nmvd_cg_info_s mvd_cg_info;\r\n\r\nmvd_wp_info_t mvd_wp_info[mvd_info_types] = {\r\n\t{AXE_INFO,  \"axe\",  IT_AXE,              \"axe\",         0,                  0, 0xDA, 0xDA, 0xDA, NULL,          \"&cf0f\", \"axe\",             MVD_ADDCLOCK_NEVER, false },\r\n\t{SG_INFO,   \"sg\",   IT_SHOTGUN,          \"sg\",          0,                  0, 0xDA, 0xDA, 0xDA, NULL,          \"&cf0f\", \"sg\",              MVD_ADDCLOCK_NEVER, false },\r\n\t{SSG_INFO,  \"ssg\",  IT_SUPER_SHOTGUN,    \"&cf0fssg&r\",  0,                  0, 0xDA, 0xDA, 0xDA, &tp_name_ssg,  \"&cf0f\", \"&cf0fssg pack&r\", MVD_ADDCLOCK_NEVER, false },\r\n\t{NG_INFO,   \"ng\",   IT_NAILGUN,          \"&cf0fng&r\",   0,                  0, 0xDA, 0xDA, 0xDA, &tp_name_ng,   \"&cf0f\", \"&cf0fng pack&r\",  MVD_ADDCLOCK_NEVER, false },\r\n\t{SNG_INFO,  \"sng\",  IT_SUPER_NAILGUN,    \"&cf0fsng&r\",  0,                  0, 0xDA, 0xDA, 0xDA, &tp_name_sng,  \"&cf0f\", \"&cf0fsng pack&r\", MVD_ADDCLOCK_NEVER, false },\r\n\t{GL_INFO,   \"gl\",   IT_GRENADE_LAUNCHER, \"&cf0fgl&r\",   0,                  0, 0xDA, 0xDA, 0xDA, &tp_name_gl,   \"&cf0f\", \"&cf0fgl pack&r\",  MVD_ADDCLOCK_NEVER, false },\r\n\t{RL_INFO,   \"rl\",   IT_ROCKET_LAUNCHER,  \"&cf0frl&r\",   MOD_ROCKETLAUNCHER, 0, 0xDA, 0xDA, 0xDA, &tp_name_rl,   \"&cf0f\", \"&cf0frl pack&r\",  MVD_ADDCLOCK_DMM1,  false },\r\n\t{LG_INFO,   \"lg\",   IT_LIGHTNING,        \"&cf0flg&r\",   MOD_LIGHTNINGGUN,   0, 0xDA, 0xDA, 0xDA, &tp_name_lg,   \"&cf0f\", \"&cf0flg pack&r\",  MVD_ADDCLOCK_DMM1,  false },\r\n\t{RING_INFO, \"rg\",   IT_INVISIBILITY,     \"&cff0ring&r\", MOD_RING,           0, 0xA6, 0xA6, 0x00, &tp_name_ring, \"&cff0\", \"\",                MVD_ADDCLOCK_ALWAYS,  true  },\r\n\t{QUAD_INFO, \"qd\",   IT_QUAD,             \"&c00fquad&r\", MOD_QUAD,           0, 0x4D, 0x45, 0xC9, &tp_name_quad, \"&c00f\", \"\",                MVD_ADDCLOCK_ALWAYS,  true  },\r\n\t{PENT_INFO, \"pt\",   IT_INVULNERABILITY,  \"&cf00pent&r\", MOD_PENT,           0, 0x91, 0x01, 0x01, &tp_name_pent, \"&cf00\", \"\",                MVD_ADDCLOCK_ALWAYS,  true  },\r\n\t{GA_INFO,   \"ga\",   IT_ARMOR1,           \"&c0f0ga&r\",   MOD_ARMOR,          0, 0x00, 0x72, 0x36, &tp_name_ga,   \"&c0f0\", \"\",                MVD_ADDCLOCK_ALWAYS,  false },\r\n\t{YA_INFO,   \"ya\",   IT_ARMOR2,           \"&cff0ya&r\",   MOD_ARMOR,          1, 0xA6, 0xA6, 0x00, &tp_name_ya,   \"&cff0\", \"\",                MVD_ADDCLOCK_ALWAYS,  false },\r\n\t{RA_INFO,   \"ra\",   IT_ARMOR3,           \"&cf00ra&r\",   MOD_ARMOR,          2, 0x91, 0x01, 0x01, &tp_name_ra,   \"&cf00\", \"\",                MVD_ADDCLOCK_ALWAYS,  false },\r\n\t{MH_INFO,   \"mh\",   IT_SUPERHEALTH,      \"&c00fmh&r\",   MOD_MEGAHEALTH,     0, 0xAD, 0x54, 0x2A, &tp_name_mh,   \"&c00f\", \"\",                MVD_ADDCLOCK_ALWAYS,  false },\r\n};\r\nstatic int item_counts[mvd_info_types];\r\n\r\n#define MVDCLOCK_PERSISTENT        1\r\n#define MVDCLOCK_BACKPACK          2\r\n#define MVDCLOCK_BACKPACK_REMOVED  4\r\n#define MVDCLOCK_NEVERSPAWNED      8    // we add baseline ents, so might include quad in duel... use this to hide\r\n#define MVDCLOCK_HIDDEN           16    // don't show in itemslist\r\n\r\n#define ITEMSCLOCK_TAKEN_PAUSE     4    // in seconds\r\n\r\ntypedef struct mvd_clock_t {\r\n\tint itemtype;                       // RA, Quad, RL, ...\r\n\tdouble clockval;                    // time when the clock expires\r\n\tchar location[MAX_MACRO_STRING];    // Player location when the object was picked up\r\n\tint flags;                          // flags\r\n\tint entity;                         // entity number\r\n\tint last_taken_by;                  // player entity item was last taken by (0 for unknown)\r\n\tint dropped_by;                     // player entity who dropped it (0 for unknown)\r\n\tdouble last_taken;                  // time when item was last taken\r\n\tdouble old_clockval;                // used to briefly keep items in same order as they're taken\r\n\tfloat hold_clockval;                // time when the player's hold-time will expire\r\n\tint order;                          // for static ordering\r\n\r\n\tstruct mvd_clock_t* next;           // next item in the linked list\r\n\tstruct mvd_clock_t* prev;           // prev item in the linked list\r\n} mvd_clock_t;\r\n\r\n// points to the first (earliest) item of the clock list\r\nstatic mvd_clock_t* mvd_clocklist = NULL;\r\n\r\ntypedef struct quad_cams_s {\r\n\tvec3_t\torg;\r\n\tvec3_t\tangles;\r\n} quad_cams_t;\r\n\r\n// Warning: this is a bitmask, pent & quad values are subtracted\r\ntypedef enum {\r\n\tpowerup_cam_inactive = 0,\r\n\tpowerup_cam_quad_active = 1,\r\n\tpowerup_cam_pent_active = 2,\r\n\tpowerup_cam_quadpent_active = 3\r\n} powerup_cam_status_id;\r\n\r\ntypedef struct cam_id_s {\r\n\tquad_cams_t cam;\r\n\tchar* tag;\r\n\tpowerup_cam_status_id filter;\r\n} cam_id_t;\r\n\r\nquad_cams_t quad_cams[3];\r\nquad_cams_t pent_cams[3];\r\n\r\ncam_id_t cam_id[7];\r\n\r\n// NEW VERSION\r\n\r\ntypedef struct runs_s {\r\n\tdouble starttime;\r\n\tdouble endtime;\r\n} runs_t;\r\n\r\ntypedef struct kill_s {\r\n\tint\t\ttype;\t//0 - kill, 1 - selfkill\r\n\tdouble\ttime;\r\n\tvec3_t\tlocation;\r\n\tint\t\tlwf;\r\n} kill_t;\r\n\r\ntypedef struct death_s {\r\n\tdouble time;\r\n\tvec3_t location;\r\n\tint id;\r\n} death_t;\r\n\r\ntypedef struct spawn_s {\r\n\tdouble time;\r\n\tvec3_t location;\r\n} spawn_t;\r\n\r\ntypedef struct mvd_new_info_cg_s {\r\n\tdouble game_starttime;\r\n} mvd_new_info_cg_t; // mvd_new_info_cg;\r\n\r\ntypedef struct mvd_player_s {\r\n\tplayer_state_t* p_state;\r\n\tplayer_info_t* p_info;\r\n} mvd_player_t;\r\n\r\ntypedef struct mvd_gameinfo_s {\r\n\tdouble starttime;\r\n\tchar mapname[1024];\r\n\tchar team1[1024];\r\n\tchar team2[1024];\r\n\tchar hostname[1024];\r\n\tint gametype;\r\n\tint timelimit;\r\n\tint pcount;\r\n\tint deathmatch;\r\n} mvd_gameinfo_t;\r\n\r\nextern\tcentity_t\t\tcl_entities[CL_MAX_EDICTS];\r\nextern\tentity_t\t\tcl_static_entities[MAX_STATIC_ENTITIES];\r\ndouble lasttime1, lasttime2;\r\ndouble lasttime = 0;\r\ndouble gamestart_time;\r\n\r\ndouble quad_time = 0;\r\ndouble pent_time = 0;\r\nqbool quad_is_active = false;\r\nqbool pent_is_active = false;\r\npowerup_cam_status_id powerup_cam_status = powerup_cam_inactive;\r\nqbool powerup_cam_active[4];\r\n\r\nstatic qbool was_standby = true;\r\nstatic int fixed_ordering = 0;\r\n\r\n\r\nextern cvar_t tp_name_none, tp_weapon_order;\r\nchar mvd_info_best_weapon[20];\r\n\r\nextern qbool TP_LoadLocFile(char* path, qbool quiet);\r\nextern char* TP_LocationName(vec3_t location);\r\n\r\n//matchinfo_t *MT_GetMatchInfo(void);\r\n\r\n//char Mention_Win_Buf[80][1024];\r\n\r\nmvd_new_info_t mvd_new_info[MAX_CLIENTS];\r\n\r\nint mvd_demo_track_run = 0;\r\n\r\n// mvd_info cvars\r\ncvar_t\t\t\tmvd_info = { \"mvd_info\", \"0\" };\r\ncvar_t\t\t\tmvd_info_show_header = { \"mvd_info_show_header\", \"0\" };\r\ncvar_t\t\t\tmvd_info_setup = { \"mvd_info_setup\", \"%p%n \\x10%l\\x11 %h/%a %w\" }; // FIXME: non-ascii chars\r\ncvar_t\t\t\tmvd_info_x = { \"mvd_info_x\", \"0\" };\r\ncvar_t\t\t\tmvd_info_y = { \"mvd_info_y\", \"0\" };\r\n\r\n// mvd_stats cvars\r\ncvar_t\t\t\tmvd_status = { \"mvd_status\",\"0\" };\r\ncvar_t\t\t\tmvd_status_x = { \"mvd_status_x\",\"0\" };\r\ncvar_t\t\t\tmvd_status_y = { \"mvd_status_y\",\"0\" };\r\n\r\n// Powerup cams\r\nstatic cvar_t mvd_powerup_cam = { \"mvd_powerup_cam\", \"0\" };\r\n\r\nstatic cvar_t mvd_pc_quad_1 = { \"mvd_pc_quad_1\", \"\" };\r\nstatic cvar_t mvd_pc_quad_2 = { \"mvd_pc_quad_2\", \"\" };\r\nstatic cvar_t mvd_pc_quad_3 = { \"mvd_pc_quad_3\", \"\" };\r\n\r\nstatic cvar_t mvd_pc_pent_1 = { \"mvd_pc_pent_1\", \"\" };\r\nstatic cvar_t mvd_pc_pent_2 = { \"mvd_pc_pent_2\", \"\" };\r\nstatic cvar_t mvd_pc_pent_3 = { \"mvd_pc_pent_3\", \"\" };\r\n\r\nstatic cvar_t powerup_cam_cvars[4] = {\r\n\t{ \"mvd_pc_view_1\", \"\" }, \r\n\t{ \"mvd_pc_view_2\", \"\" },\r\n\t{ \"mvd_pc_view_3\", \"\" },\r\n\t{ \"mvd_pc_view_4\", \"\" }\r\n};\r\n\r\ncvar_t mvd_moreinfo = { \"mvd_moreinfo\",\"0\" };\r\ncvar_t mvd_autoadd_items = { \"mvd_autoadd_items\", \"0\" };\r\ncvar_t mvd_sortitems = { \"mvd_sortitems\", \"1\" };\r\n\r\ntypedef struct bp_var_s {\r\n\tint id;\r\n\tint val;\r\n} bp_var_t;\r\n\r\nbp_var_t bp_var[MAX_CLIENTS];\r\n\r\nchar* Make_Red(char* s, int i) {\r\n\tstatic char buf[1024];\r\n\tchar* p, * ret;\r\n\tbuf[0] = 0;\r\n\tret = buf;\r\n\tfor (p = s; *p; p++) {\r\n\t\tif (!strspn(p, \"1234567890.\") || !(i))\r\n\t\t\t*ret++ = *p | 128;\r\n\t\telse\r\n\t\t\t*ret++ = *p;\r\n\t}\r\n\t*ret = 0;\r\n\treturn buf;\r\n}\r\n\r\nvoid MVD_Init_Info(int player_slot)\r\n{\r\n\tint i;\r\n\tint z;\r\n\r\n\tfor (z = 0, i = 0; i < MAX_CLIENTS; i++) {\r\n\t\tif (!cl.players[i].name[0] || cl.players[i].spectator == 1)\r\n\t\t\tcontinue;\r\n\t\tmvd_new_info[z].id = i;\r\n\t\tif (player_slot == i || player_slot == MAX_CLIENTS) {\r\n\t\t\tmvd_new_info[z].mvdinfo.initialized = false;\r\n\t\t}\r\n\t\t// memset(mvd_new_info[z].item_info, 0, sizeof(mvd_new_info[z].item_info));\r\n\t\tmvd_new_info[z++].p_info = &cl.players[i];\r\n\t}\r\n\r\n\tstrlcpy(mvd_cg_info.mapname, TP_MapName(), sizeof(mvd_cg_info.mapname));\r\n\tmvd_cg_info.timelimit = cl.timelimit;\r\n\r\n\tstrlcpy(mvd_cg_info.team1, (z ? mvd_new_info[0].p_info->team : \"\"), sizeof(mvd_cg_info.team1));\r\n\tfor (i = 0; i < z; i++) {\r\n\t\tif (strcmp(mvd_new_info[i].p_info->team, mvd_cg_info.team1)) {\r\n\t\t\tstrlcpy(mvd_cg_info.team2, mvd_new_info[i].p_info->team, sizeof(mvd_cg_info.team2));\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\tif (z == 2)\r\n\t\tmvd_cg_info.gametype = 0;\r\n\telse if (z == 4)\r\n\t\tmvd_cg_info.gametype = 1;\r\n\telse if (z == 6)\r\n\t\tmvd_cg_info.gametype = 2;\r\n\telse if (z == 8)\r\n\t\tmvd_cg_info.gametype = 3;\r\n\telse\r\n\t\tmvd_cg_info.gametype = 4;\r\n\r\n\tstrlcpy(mvd_cg_info.hostname, Info_ValueForKey(cl.serverinfo, \"hostname\"), sizeof(mvd_cg_info.hostname));\r\n\tmvd_cg_info.deathmatch = cl.deathmatch;\r\n\r\n\tmvd_cg_info.pcount = z;\r\n\r\n\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\r\n\t\tmvd_new_info[i].p_state = &cl.frames[cl.parsecount & UPDATE_MASK].playerstate[mvd_new_info[i].id];\r\n\t}\r\n\r\n\tmemset(item_counts, 0, sizeof(item_counts));\r\n\tfor (i = 0; i < sizeof(item_counts) / sizeof(item_counts[0]); ++i) {\r\n\t\tint item_model_hint = mvd_wp_info[i].model_hint;\r\n\t\tint item_skin_number = mvd_wp_info[i].skin_number;\r\n\r\n\t\tif (item_model_hint) {\r\n\t\t\tint j;\r\n\r\n\t\t\tfor (j = 0; j < CL_MAX_EDICTS; j++) {\r\n\t\t\t\tint modindex = cl_entities[j].baseline.modelindex;\r\n\t\t\t\tint skin = cl_entities[j].baseline.skinnum;\r\n\r\n\t\t\t\tif (modindex >= 0 && modindex < sizeof(cl.model_precache) / sizeof(cl.model_precache[0]) && cl.model_precache[modindex]) {\r\n\t\t\t\t\tif (cl.model_precache[modindex]->modhint == item_model_hint && skin == item_skin_number) {\r\n\t\t\t\t\t\t++item_counts[i];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif (player_slot == MAX_CLIENTS) {\r\n\t\tmemset(announcer_line_strings, 0, sizeof(announcer_line_strings));\r\n\t\tmemset(announcer_line_times, 0, sizeof(announcer_line_times));\r\n\t\tannouncer_lines = 0;\r\n\t}\r\n}\r\n\r\ndouble MVD_RespawnTimeGet(int itemtype)\r\n{\r\n\tswitch (itemtype) {\r\n\tcase RA_INFO:\r\n\tcase YA_INFO:\r\n\tcase GA_INFO:\r\n\tcase MH_INFO:\r\n\t\treturn 20.0;\r\n\r\n\tcase SSG_INFO:\r\n\tcase NG_INFO:\r\n\tcase SNG_INFO:\r\n\tcase GL_INFO:\r\n\tcase RL_INFO:\r\n\tcase LG_INFO:\r\n\t\treturn 30.0;\r\n\r\n\tcase QUAD_INFO:\r\n\t\treturn 60.0;\r\n\r\n\tcase RING_INFO:\r\n\tcase PENT_INFO:\r\n\t\treturn 300.0;\r\n\r\n\tdefault:\r\n\t\tCom_DPrintf(\"Warning in MVD_RespawnTimeGet(): unknown item type %d\\n\", itemtype);\r\n\t\treturn 0.0;\r\n\t}\r\n}\r\n\r\nvoid MVD_ClockList_Insert(mvd_clock_t* newclock)\r\n{\r\n\tCom_DPrintf(\"MVD_ClockList_Insert type=%d\\n\", newclock->itemtype);\r\n\tif (mvd_clocklist == NULL) {\r\n\t\tmvd_clocklist = newclock;\r\n\t\tnewclock->next = NULL;\r\n\t\tnewclock->prev = NULL;\r\n\t}\r\n\telse {\r\n\t\tmvd_clock_t* current = mvd_clocklist;\r\n\t\tmvd_clock_t* last = NULL;\r\n\t\twhile (current && current->clockval < newclock->clockval) {\r\n\t\t\tlast = current;\r\n\t\t\tcurrent = current->next;\r\n\t\t}\r\n\t\tif (current) {\r\n\t\t\tnewclock->next = current;\r\n\t\t\tnewclock->prev = current->prev;\r\n\t\t\tif (current->prev) {\r\n\t\t\t\tcurrent->prev->next = newclock;\r\n\t\t\t}\r\n\t\t\tcurrent->prev = newclock;\r\n\t\t\tif (last == NULL) {\r\n\t\t\t\tmvd_clocklist = newclock;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse {\r\n\t\t\tif (last) {\r\n\t\t\t\tlast->next = newclock;\r\n\t\t\t\tnewclock->prev = last;\r\n\t\t\t\tnewclock->next = NULL;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tnewclock->prev = NULL;\r\n\t\t\t\tnewclock->next = mvd_clocklist;\r\n\t\t\t\tmvd_clocklist = newclock;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nmvd_clock_t* MVD_ClockList_Remove(mvd_clock_t* item)\r\n{\r\n\tmvd_clock_t* ret = NULL;\r\n\r\n\tif (item == mvd_clocklist) {\r\n\t\tmvd_clocklist = item->next;\r\n\t\tif (mvd_clocklist) {\r\n\t\t\tmvd_clocklist->prev = NULL;\r\n\t\t}\r\n\t\tQ_free(item);\r\n\t\treturn mvd_clocklist;\r\n\t}\r\n\r\n\tret = item->next;\r\n\t// item->prev is not null\r\n\tif (item->next) {\r\n\t\titem->next->prev = item->prev;\r\n\t\titem->prev->next = item->next;\r\n\t}\r\n\telse {\r\n\t\titem->prev->next = NULL;\r\n\t}\r\n\tQ_free(item);\r\n\treturn ret;\r\n}\r\n\r\nstatic void MVD_ClockStart(int itemtype, vec3_t origin)\r\n{\r\n\tmvd_clock_t* newclock = (mvd_clock_t*)Q_malloc(sizeof(mvd_clock_t));\r\n\tnewclock->clockval = cls.demopackettime + MVD_RespawnTimeGet(itemtype);\r\n\tnewclock->itemtype = itemtype;\r\n\tif (origin) {\r\n\t\tstrlcpy(newclock->location, TP_LocationName(origin), sizeof(newclock->location));\r\n\t}\r\n\tnewclock->order = 0;\r\n\tMVD_ClockList_Insert(newclock);\r\n}\r\n\r\nstatic mvd_clock_t* MVD_ClockStartEntity(int entity, int itemtype, int flags)\r\n{\r\n\tmvd_clock_t* newclock = (mvd_clock_t*)Q_malloc(sizeof(mvd_clock_t));\r\n\tnewclock->clockval = 0;\r\n\tnewclock->itemtype = itemtype;\r\n\tstrlcpy(newclock->location, TP_LocationName(cl_entities[entity].baseline.origin), sizeof(newclock->location));\r\n\tnewclock->flags = flags;\r\n\tnewclock->entity = entity;\r\n\tnewclock->order = ++fixed_ordering;\r\n\tMVD_ClockList_Insert(newclock);\r\n\treturn newclock;\r\n}\r\n\r\nstatic mvd_clock_t* MVD_ClockFindEntity(int entity)\r\n{\r\n\tmvd_clock_t* current;\r\n\r\n\tfor (current = mvd_clocklist; current; current = current->next) {\r\n\t\tif (current->entity == entity) {\r\n\t\t\treturn current;\r\n\t\t}\r\n\t}\r\n\r\n\treturn NULL;\r\n}\r\n\r\nvoid MVD_ClockList_RemoveExpired(void)\r\n{\r\n\tmvd_clock_t* current;\r\n\r\n\tfor (current = mvd_clocklist; current; ) {\r\n\t\t// We don't remove persistent counters\r\n\t\tif (!(current->flags & MVDCLOCK_PERSISTENT)) {\r\n\t\t\t// Expired\r\n\t\t\tif (current->clockval + 1 < cls.demopackettime) {\r\n\t\t\t\tcurrent = MVD_ClockList_Remove(current);\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\t//\r\n\t\t\tif (current->entity && !(current->flags & MVDCLOCK_BACKPACK_REMOVED)) {\r\n\t\t\t\tint mod = cl_entities[current->entity].current.modelindex;\r\n\t\t\t\tif (mod <= 0 || mod >= sizeof(cl.model_precache) / sizeof(cl.model_precache[0])) {\r\n\t\t\t\t\tif (current->last_taken) {\r\n\t\t\t\t\t\t// Backpack has been picked up, disconnect from entity\r\n\t\t\t\t\t\tcurrent->flags &= ~(MVDCLOCK_BACKPACK);\r\n\t\t\t\t\t\tcurrent->flags |= MVDCLOCK_BACKPACK_REMOVED;\r\n\t\t\t\t\t\tcurrent->entity = 0;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tcurrent = MVD_ClockList_Remove(current);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (cl_entities[current->entity].sequence < cl.validsequence) {\r\n\t\t\t\t\t// Backpack has been picked up or disappeared, disconnect from entity and let expire\r\n\t\t\t\t\tcurrent->flags &= ~(MVDCLOCK_BACKPACK);\r\n\t\t\t\t\tcurrent->flags |= MVDCLOCK_BACKPACK_REMOVED;\r\n\t\t\t\t\tcurrent->entity = 0;\r\n\t\t\t\t}\r\n\t\t\t\telse if (cl.model_precache[mod] == NULL || cl.model_precache[mod]->modhint != MOD_BACKPACK) {\r\n\t\t\t\t\t// No longer a backpack, remove\r\n\t\t\t\t\tcurrent = MVD_ClockList_Remove(current);\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tcurrent = current->next;\r\n\t}\r\n}\r\n\r\nint MVD_ClockList_GetLongestName(void)\r\n{\r\n\tint i, current, longest = 0;\r\n\tint items[] = {\r\n\t\tIT_AXE, IT_SHOTGUN, IT_SUPER_SHOTGUN, IT_NAILGUN, IT_SUPER_NAILGUN,\r\n\t\tIT_GRENADE_LAUNCHER, IT_ROCKET_LAUNCHER, IT_LIGHTNING, IT_INVISIBILITY,\r\n\t\tIT_QUAD, IT_INVULNERABILITY, IT_ARMOR1, IT_ARMOR2, IT_ARMOR3, IT_SUPERHEALTH\r\n\t};\r\n\r\n\tfor (i = 0; i < (sizeof(items) / sizeof(*items)); i++) {\r\n\t\tcurrent = strlen_color(TP_ItemName(items[i]));\r\n\t\tif (longest < current)\r\n\t\t\tlongest = current;\r\n\t}\r\n\treturn longest;\r\n}\r\n\r\nstatic double MVD_ClockList_SortTime(mvd_clock_t* c)\r\n{\r\n\tdouble t = c->clockval;\r\n\r\n\tif (c->last_taken && cls.demopackettime - c->last_taken < ITEMSCLOCK_TAKEN_PAUSE) {\r\n\t\t// item has just been taken, keep to the start of the list\r\n\t\tt = -1 - c->old_clockval;\r\n\t}\r\n\telse if (c->last_taken && t == -1) {\r\n\t\t// megahealth still held by player, put to end of list\r\n\t\tt = cls.demopackettime + c->last_taken + 1000;\r\n\t}\r\n\r\n\treturn t;\r\n}\r\n\r\n// Moves persistent\r\nstatic int MVD_ClockList_Compare(mvd_clock_t* c, mvd_clock_t* n)\r\n{\r\n\tdouble c_time = MVD_ClockList_SortTime(c);\r\n\tdouble n_time = MVD_ClockList_SortTime(n);\r\n\tqbool c_persistent = c->flags & MVDCLOCK_PERSISTENT;\r\n\tqbool n_persistent = n->flags & MVDCLOCK_PERSISTENT;\r\n\r\n\tif (c_persistent && !n_persistent) {\r\n\t\treturn 1;\r\n\t}\r\n\tif (!c_persistent && n_persistent) {\r\n\t\treturn -1;\r\n\t}\r\n\r\n\tif (mvd_sortitems.integer) {\r\n\t\treturn c_time - n_time;\r\n\t}\r\n\telse {\r\n\t\treturn c->order - n->order;\r\n\t}\r\n}\r\n\r\nstatic void MVD_ClockList_Sort(void)\r\n{\r\n\tqbool any_change = true;\r\n\r\n\twhile (any_change) {\r\n\t\tmvd_clock_t* c = mvd_clocklist;\r\n\r\n\t\tany_change = false;\r\n\t\twhile (c) {\r\n\t\t\tmvd_clock_t* n = c->next;\r\n\t\t\tint comparison;\r\n\r\n\t\t\tif (!n) {\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\r\n\t\t\tcomparison = MVD_ClockList_Compare(c, n);\r\n\t\t\tif (comparison > 0) {\r\n\t\t\t\tc->next = n->next;\r\n\t\t\t\tn->prev = c->prev;\r\n\t\t\t\tc->prev = n;\r\n\t\t\t\tn->next = c;\r\n\r\n\t\t\t\tif (n->prev) {\r\n\t\t\t\t\tn->prev->next = n;\r\n\t\t\t\t}\r\n\t\t\t\tif (c->next) {\r\n\t\t\t\t\tc->next->prev = c;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (c == mvd_clocklist) {\r\n\t\t\t\t\tmvd_clocklist = n;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tany_change = true;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tc = n;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// MEAG: Deliberately not taking 'proportional' argument into account here, don't think we should measure by item type either...\r\nvoid MVD_ClockList_TopItems_DimensionsGet(double time_limit, int style, int* width, int* height, float scale, qbool backpacks, qbool proportional)\r\n{\r\n\tint lines = 0;\r\n\tmvd_clock_t* current = mvd_clocklist;\r\n\tint persistent = 0, temporary = 0;\r\n\r\n\tif (style == 5) {\r\n\t\tMVD_ClockList_Sort();\r\n\r\n\t\tcurrent = mvd_clocklist;\r\n\t}\r\n\r\n\twhile (current) {\r\n\t\tint time = (int)((current->clockval - cls.demopackettime) + 1);\r\n\r\n\t\t// Skip if it's a backpack and the config turns these off\r\n\t\tif (!backpacks && (current->flags & (MVDCLOCK_BACKPACK | MVDCLOCK_BACKPACK_REMOVED))) {\r\n\t\t\tcurrent = current->next;\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\tif (current->flags & MVDCLOCK_PERSISTENT) {\r\n\t\t\t// Skip if player has manually removed\r\n\t\t\tif (current->flags & MVDCLOCK_HIDDEN) {\r\n\t\t\t\tcurrent = current->next;\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif (current->flags & MVDCLOCK_NEVERSPAWNED) {\r\n\t\t\t\t// Skip auto-added items that haven't spawned and have never spawned\r\n\t\t\t\tif (cl_entities[current->entity].current.modelindex == 0 && time <= 0) {\r\n\t\t\t\t\tcurrent = current->next;\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tcurrent->flags &= ~(MVDCLOCK_NEVERSPAWNED);\r\n\t\t\t}\r\n\r\n\t\t}\r\n\r\n\t\tif (current->entity || current->clockval - cls.demopackettime < time_limit) {\r\n\t\t\tint time = (int)((current->clockval - cls.demopackettime) + 1);\r\n\r\n\t\t\tif (current->flags & MVDCLOCK_PERSISTENT) {\r\n\t\t\t\tif (cl_entities[current->entity].current.modelindex != 0 || time >= 0) {\r\n\t\t\t\t\t++persistent;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\t++temporary;\r\n\t\t\t}\r\n\t\t\tlines++;\r\n\t\t}\r\n\t\tcurrent = current->next;\r\n\t}\r\n\r\n\t// the longest possible string\r\n\tif (style == 1) {\r\n\t\t*width = LETTERWIDTH * (MVD_ClockList_GetLongestName() + sizeof(\" spawn\") - 1) * scale;\r\n\t}\r\n\telse if (style == 3) {\r\n\t\t*width = LETTERWIDTH * (2 + sizeof(\" spawn\") - 1) * scale;\r\n\t}\r\n\telse if (style == 4) {\r\n\t\t*width = LETTERWIDTH * (2 + sizeof(\" spawn\") - 1) * scale;\r\n\t}\r\n\telse {\r\n\t\t*width = LETTERWIDTH * (sizeof(\"QUAD spawn\") - 1) * scale;\r\n\t}\r\n\r\n\tif (persistent && temporary) {\r\n\t\tlines++;\r\n\t}\r\n\r\n\t*height = LETTERHEIGHT * lines * scale * (style == 3 ? 2 : 1);\r\n}\r\n\r\nstatic qbool MVD_ClockIsHeld(mvd_clock_t* current, qbool test_held, float* alpha)\r\n{\r\n\tdouble time_since;\r\n\r\n\tif (alpha) {\r\n\t\t*alpha = 1.0f;\r\n\t}\r\n\r\n\tif (!current->entity || (test_held && current->hold_clockval <= cls.demopackettime)) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tif (current->last_taken_by <= 0 || current->last_taken_by > MAX_CLIENTS || !cl.players[current->last_taken_by - 1].name[0]) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tif (test_held && (cl.players[current->last_taken_by - 1].stats[STAT_ITEMS] & mvd_wp_info[current->itemtype].it)) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\tif (!current->last_taken || current->last_taken > cls.demopackettime) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\ttime_since = (cls.demopackettime - current->last_taken);\r\n\tif (alpha) {\r\n\t\tif (time_since > ITEMSCLOCK_TAKEN_PAUSE - 1 && time_since < ITEMSCLOCK_TAKEN_PAUSE) {\r\n\t\t\t*alpha = (ITEMSCLOCK_TAKEN_PAUSE - time_since);\r\n\t\t}\r\n\t\telse if (time_since >= ITEMSCLOCK_TAKEN_PAUSE && time_since < ITEMSCLOCK_TAKEN_PAUSE + 1) {\r\n\t\t\t*alpha = (time_since - ITEMSCLOCK_TAKEN_PAUSE);\r\n\t\t}\r\n\t}\r\n\treturn time_since < ITEMSCLOCK_TAKEN_PAUSE;\r\n}\r\n\r\nvoid MVD_ClockList_TopItems_Draw(double time_limit, int style, int x, int y, float scale, int filter, qbool backpacks, qbool proportional)\r\n{\r\n\tmvd_clock_t* current = mvd_clocklist;\r\n\tchar clockitem[128];\r\n\tchar temp[128];\r\n\tint base_x = x;\r\n\tqbool was_persistent = true;\r\n\tint barWidth;\r\n\r\n\twhile (current) {\r\n\t\tfloat alpha = 1.0f;\r\n\t\tx = base_x;\r\n\r\n\t\t// Skip backpacks\r\n\t\tif (!backpacks && (current->flags & (MVDCLOCK_BACKPACK | MVDCLOCK_BACKPACK_REMOVED))) {\r\n\t\t\tcurrent = current->next;\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\t// Skip auto-added items that have never spawned or manually removed\r\n\t\tif (current->flags & (MVDCLOCK_NEVERSPAWNED | MVDCLOCK_HIDDEN)) {\r\n\t\t\tcurrent = current->next;\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\tif (current->entity || current->clockval - cls.demopackettime < time_limit) {\r\n\t\t\tint time = (int)((current->clockval - cls.demopackettime) + 1);\r\n\t\t\tmpic_t* texture = Mod_SimpleTextureForHint(mvd_wp_info[current->itemtype].model_hint, mvd_wp_info[current->itemtype].skin_number);\r\n\r\n\t\t\tif (filter & mvd_wp_info[current->itemtype].it) {\r\n\t\t\t\tcurrent = current->next;\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif ((current->flags & MVDCLOCK_PERSISTENT) && !was_persistent) {\r\n\t\t\t\ty += LETTERHEIGHT * scale;\r\n\t\t\t}\r\n\t\t\twas_persistent = (current->flags & MVDCLOCK_PERSISTENT);\r\n\r\n\t\t\tif (style == 1) {\r\n\t\t\t\t// tp_name_*\r\n\t\t\t\tstrlcpy(clockitem, TP_ItemName(mvd_wp_info[current->itemtype].it), sizeof(clockitem));\r\n\t\t\t}\r\n\t\t\telse if (style == 2) {\r\n\t\t\t\t// brown + white\r\n\t\t\t\tstrlcpy(clockitem, mvd_wp_info[current->itemtype].name, sizeof(clockitem));\r\n\t\t\t\tCharsToBrown(clockitem, clockitem + strlen(clockitem));\r\n\t\t\t}\r\n\t\t\telse if (style == 3 && texture && R_TextureReferenceIsValid(texture->texnum)) {\r\n\t\t\t\t// simpleitem\r\n\t\t\t\tDraw_FitPic(x, y, 2 * LETTERWIDTH * scale, 2 * LETTERHEIGHT * scale, texture);\r\n\t\t\t\tx += 2 * LETTERWIDTH * scale;\r\n\t\t\t\tclockitem[0] = '\\0';\r\n\t\t\t\ty += LETTERHEIGHT * scale / 2;\r\n\t\t\t}\r\n\t\t\telse if (style == 4) {\r\n\t\t\t\tchar item_name[MAX_MACRO_STRING];\r\n\r\n\t\t\t\tif (mvd_wp_info[current->itemtype].name_cvar && mvd_wp_info[current->itemtype].name_cvar->string[0]) {\r\n\t\t\t\t\tUtil_SkipChars(mvd_wp_info[current->itemtype].name_cvar->string, \"{}\", item_name, sizeof(item_name));\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tstrlcpy(item_name, mvd_wp_info[current->itemtype].colored_name, sizeof(item_name));\r\n\t\t\t\t}\r\n\r\n\t\t\t\tsnprintf(clockitem, sizeof(clockitem), \"%*s\", MVD_ANNOUNCER_ITEM_LENGTH + ((int)strlen(item_name) - strlen_color(item_name)), item_name);\r\n\r\n\t\t\t\tstrlcat(clockitem, \" \\034\", sizeof(clockitem));\r\n\t\t\t}\r\n\t\t\telse if (style == 5) {\r\n\t\t\t\tchar item_name[MAX_MACRO_STRING];\r\n\r\n\t\t\t\tif (current->flags & (MVDCLOCK_BACKPACK | MVDCLOCK_BACKPACK_REMOVED)) {\r\n\t\t\t\t\tconst char* name = mvd_wp_info[current->itemtype].colored_packname;\r\n\r\n\t\t\t\t\tsnprintf(item_name, sizeof(item_name) - 1, \"%*s\", MVD_ANNOUNCER_ITEM_LENGTH + ((int)strlen(name) - strlen_color(name)), name);\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tconst char* color = mvd_wp_info[current->itemtype].color_string;\r\n\r\n\t\t\t\t\tsnprintf(item_name, sizeof(item_name) - 1, \"%s%*s&r\", color ? color : \"\", MVD_ANNOUNCER_ITEM_LENGTH + ((int)strlen(current->location) - strlen_color(current->location)), current->location);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tstrlcpy(clockitem, item_name, sizeof(clockitem));\r\n\t\t\t\tstrlcat(clockitem, \" \\034\", sizeof(clockitem));\r\n\t\t\t\tDraw_SString(x, y, clockitem, scale, proportional);\r\n\r\n\t\t\t\tx += strlen_color(clockitem) * 8 * scale;\r\n\t\t\t\tclockitem[0] = '\\0';\r\n\t\t\t}\r\n\t\t\telse if (style == 6) {\r\n\t\t\t\t// progress bar countdown\r\n\t\t\t\tstrlcpy(clockitem, mvd_wp_info[current->itemtype].name, sizeof(clockitem));\r\n\r\n\t\t\t\tbarWidth = (round(67 * scale) / time_limit) * (time_limit - time + 1);\r\n\t\t\t\tif (time == 0) {\r\n\t\t\t\t\tbarWidth = (round(67 * scale) / time_limit) * time_limit;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tDraw_AlphaFillRGB(x - 1, y, barWidth, 10 * scale,\r\n\t\t\t\t\tRGBA_TO_COLOR(mvd_wp_info[current->itemtype].Rcolor,\r\n\t\t\t\t\t\tmvd_wp_info[current->itemtype].Gcolor,\r\n\t\t\t\t\t\tmvd_wp_info[current->itemtype].Bcolor, 128));\r\n\t\t\t}\r\n\t\t\telse if (style == 7) {\r\n\t\t\t\t// progress bar countdown, but itemname not in bar\r\n\t\t\t\tstrlcpy(clockitem, mvd_wp_info[current->itemtype].name, sizeof(clockitem));\r\n\r\n\t\t\t\tbarWidth = (round(44 * scale) / time_limit) * (time_limit - time + 1);\r\n\t\t\t\tif (time == 0) {\r\n\t\t\t\t\tbarWidth = (round(44 * scale) / time_limit) * time_limit;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tDraw_AlphaFillRGB(x + (22 * scale), y, barWidth, 10 * scale,\r\n\t\t\t\t\tRGBA_TO_COLOR(mvd_wp_info[current->itemtype].Rcolor,\r\n\t\t\t\t\t\tmvd_wp_info[current->itemtype].Gcolor,\r\n\t\t\t\t\t\tmvd_wp_info[current->itemtype].Bcolor, 128));\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\t// built-in color(GL) or simple white (software)\r\n\t\t\t\tif (mvd_wp_info[current->itemtype].name_cvar) {\r\n\t\t\t\t\tstrlcpy(clockitem, mvd_wp_info[current->itemtype].colored_name, sizeof(clockitem));\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tstrlcpy(clockitem, mvd_wp_info[current->itemtype].colored_name, sizeof(clockitem));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (current->flags & MVDCLOCK_BACKPACK_REMOVED) {\r\n\t\t\t\tif (current->location[0]) {\r\n\t\t\t\t\tstrlcat(clockitem, \" \", sizeof(clockitem));\r\n\t\t\t\t\tstrlcat(clockitem, current->location, sizeof(clockitem));\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (current->dropped_by && current->last_taken_by) {\r\n\t\t\t\t\tstrlcat(clockitem, \" (\", sizeof(clockitem));\r\n\t\t\t\t\tstrlcat(clockitem, MVD_AnnouncerTeamPlayerName(&cl.players[current->dropped_by - 1]), sizeof(clockitem));\r\n\t\t\t\t\tstrlcat(clockitem, \" \\015 \", sizeof(clockitem));\r\n\t\t\t\t\tstrlcat(clockitem, MVD_AnnouncerTeamPlayerName(&cl.players[current->last_taken_by - 1]), sizeof(clockitem));\r\n\t\t\t\t\tstrlcat(clockitem, \")\", sizeof(clockitem));\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tcurrent = MVD_ClockList_Remove(current);\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (time > 0) {\r\n\t\t\t\tif (current->flags & MVDCLOCK_BACKPACK) {\r\n\t\t\t\t\tstrlcpy(current->location, TP_LocationName(cl_entities[current->entity].current.origin), sizeof(current->location));\r\n\r\n\t\t\t\t\tsnprintf(temp, sizeof(temp), \" %s\", current->location);\r\n\r\n\t\t\t\t\tif (current->dropped_by) {\r\n\t\t\t\t\t\tstrlcat(temp, \" (\", sizeof(temp));\r\n\t\t\t\t\t\tstrlcat(temp, MVD_AnnouncerTeamPlayerName(&cl.players[current->dropped_by - 1]), sizeof(temp));\r\n\t\t\t\t\t\tstrlcat(temp, \")\", sizeof(temp));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (MVD_ClockIsHeld(current, mvd_wp_info[current->itemtype].show_held, &alpha)) {\r\n\t\t\t\t\tsnprintf(temp, sizeof(temp), \" %s\", MVD_AnnouncerTeamPlayerName(&cl.players[current->last_taken_by - 1]));\r\n\t\t\t\t}\r\n\t\t\t\telse if (style == 5) {\r\n\t\t\t\t\tif (time >= 60) {\r\n\t\t\t\t\t\tsnprintf(temp, sizeof(temp), \" %d:%02d\", time / 60, time % 60);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tsnprintf(temp, sizeof(temp), \" %d\", time);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tsnprintf(temp, sizeof(temp), \" %*d\", time_limit >= 10 ? 2 : 1, time);\r\n\t\t\t\t}\r\n\t\t\t\tstrlcat(clockitem, temp, sizeof(clockitem));\r\n\t\t\t}\r\n\t\t\telse if (current->flags & MVDCLOCK_PERSISTENT) {\r\n\t\t\t\tstrlcpy(temp, \" up\", sizeof(temp));\r\n\r\n\t\t\t\t// If a player is holding the item, use their name instead\r\n\t\t\t\tif (MVD_ClockIsHeld(current, true, NULL)) {\r\n\t\t\t\t\tsnprintf(temp, sizeof(temp), \" %s\", MVD_AnnouncerTeamPlayerName(&cl.players[current->last_taken_by - 1]));\r\n\t\t\t\t}\r\n\r\n\t\t\t\tstrlcat(clockitem, temp, sizeof(clockitem));\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tstrlcat(clockitem, \" spawn\", sizeof(clockitem));\r\n\t\t\t}\r\n\r\n\t\t\tif (style == 4 && item_counts[current->itemtype] != 1 && current->location[0]) {\r\n\t\t\t\tstrlcat(clockitem, \" \\020\", sizeof(clockitem));\r\n\t\t\t\tstrlcat(clockitem, current->location, sizeof(clockitem));\r\n\t\t\t\tstrlcat(clockitem, \"\\021\", sizeof(clockitem));\r\n\t\t\t}\r\n\r\n\t\t\tDraw_SStringAlpha(x, y, clockitem, scale, alpha, proportional);\r\n\r\n\t\t\ty += LETTERHEIGHT * scale;\r\n\t\t\tif (style == 3) {\r\n\t\t\t\ty += LETTERHEIGHT * scale / 2;\r\n\t\t\t}\r\n\t\t\telse if ((style == 6) || (style == 7)) {\r\n\t\t\t\ty += round(4 * scale);\r\n\t\t\t}\r\n\t\t}\r\n\t\tcurrent = current->next;\r\n\t}\r\n}\r\n\r\nstatic void MVD_Took(int player, int item, qbool addclock)\r\n{\r\n\tif (mvd_new_info[player].mvdinfo.initialized && !cl.mvd_ktx_markers) {\r\n\t\tif (addclock) {\r\n\t\t\tMVD_ClockStart(item, mvd_new_info[player].p_state ? mvd_new_info[player].p_state->origin : NULL);\r\n\t\t}\r\n\t\tmvd_new_info[player].mvdinfo.itemstats[item].mention = addclock ? MENTION_PICKED_UP_ITEM : MENTION_PICKED_UP_PACK;\r\n\t}\r\n}\r\n\r\n// this steps in action if the user has created a demo playlist and has specified\r\n// which player should be prefered in the demos (so that he doesn't have to switch\r\n// to that player at the start of each demo manually)\r\nvoid MVD_Demo_Track(void) {\r\n\textern char track_name[16];\r\n\textern cvar_t demo_playlist_track_name;\r\n\tchar track_player[128] = { 0 };\r\n\r\n#ifdef DEBUG\r\n\tprintf(\"MVD_Demo_Track Started\\n\");\r\n#endif\r\n\r\n\ttrack_name[15] = '\\0';\r\n\tif (strlen(track_name))\r\n\t{\r\n\t\tif (FindBestNick(track_name, FBN_IGNORE_SPECS | FBN_IGNORE_QTVSPECS, track_player, sizeof(track_player)))\r\n\t\t\tCbuf_AddText(va(\"track \\\"%s\\\"\\n\", track_player));\r\n\t}\r\n\telse if (strlen(demo_playlist_track_name.string))\r\n\t{\r\n\t\tif (FindBestNick(demo_playlist_track_name.string, FBN_IGNORE_SPECS | FBN_IGNORE_QTVSPECS, track_player, sizeof(track_player)))\r\n\t\t\tCbuf_AddText(va(\"track \\\"%s\\\"\\n\", track_player));\r\n\t}\r\n\r\n\tmvd_demo_track_run = 1;\r\n#ifdef DEBUG\r\n\tprintf(\"MVD_Demo_Track Stopped\\n\");\r\n#endif\r\n}\r\n\r\n\r\nint MVD_BestWeapon(int i) {\r\n\tint x;\r\n\tchar* t[] = { tp_weapon_order.string, \"78654321\", NULL }, ** s;\r\n\tfor (s = t; *s; s++) {\r\n\t\tfor (x = 0; x < strlen(*s); x++) {\r\n\t\t\tswitch ((*s)[x]) {\r\n\t\t\tcase '1': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_AXE) return IT_AXE; break;\r\n\t\t\tcase '2': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_SHOTGUN) return IT_SHOTGUN; break;\r\n\t\t\tcase '3': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_SUPER_SHOTGUN) return IT_SUPER_SHOTGUN; break;\r\n\t\t\tcase '4': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_NAILGUN) return IT_NAILGUN; break;\r\n\t\t\tcase '5': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_SUPER_NAILGUN) return IT_SUPER_NAILGUN; break;\r\n\t\t\tcase '6': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_GRENADE_LAUNCHER) return IT_GRENADE_LAUNCHER; break;\r\n\t\t\tcase '7': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER) return IT_ROCKET_LAUNCHER; break;\r\n\t\t\tcase '8': if (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_LIGHTNING) return IT_LIGHTNING; break;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn 0;\r\n}\r\n\r\nchar* MVD_BestWeapon_strings(int i) {\r\n\treturn TP_ItemName(MVD_BestWeapon(i));\r\n}\r\n\r\nint MVD_Weapon_LWF(int i) {\r\n\tswitch (i) {\r\n\tcase IT_AXE: return 0;\r\n\tcase IT_SHOTGUN: return 1;\r\n\tcase IT_SUPER_SHOTGUN: return 2;\r\n\tcase IT_NAILGUN: return 3;\r\n\tcase IT_SUPER_NAILGUN: return 4;\r\n\tcase IT_GRENADE_LAUNCHER: return 5;\r\n\tcase IT_ROCKET_LAUNCHER: return 6;\r\n\tcase IT_LIGHTNING: return 7;\r\n\tdefault: return 666;\r\n\t}\r\n}\r\n\r\nchar* MVD_BestAmmo(int i) {\r\n\r\n\tswitch (MVD_BestWeapon(i)) {\r\n\tcase IT_SHOTGUN: case IT_SUPER_SHOTGUN:\r\n\t\treturn va(\"%i\", mvd_new_info[i].p_info->stats[STAT_SHELLS]);\r\n\r\n\tcase IT_NAILGUN: case IT_SUPER_NAILGUN:\r\n\t\treturn va(\"%i\", mvd_new_info[i].p_info->stats[STAT_NAILS]);\r\n\r\n\tcase IT_GRENADE_LAUNCHER: case IT_ROCKET_LAUNCHER:\r\n\t\treturn va(\"%i\", mvd_new_info[i].p_info->stats[STAT_ROCKETS]);\r\n\r\n\tcase IT_LIGHTNING:\r\n\t\treturn va(\"%i\", mvd_new_info[i].p_info->stats[STAT_CELLS]);\r\n\r\n\tdefault: return \"0\";\r\n\t}\r\n}\r\n\r\nvoid MVD_Info(void) {\r\n\tchar str[1024];\r\n\tchar mvd_info_final_string[1024], mvd_info_powerups[20], mvd_info_header_string[1024];\r\n\tint x, y, z, i;\r\n\r\n\r\n\r\n#ifdef DEBUG\r\n\tprintf(\"MVD_Info Started\\n\");\r\n#endif\r\n\r\n\tz = 1;\r\n\r\n\tif (!mvd_info.value)\r\n\t\treturn;\r\n\r\n\tif (!cls.mvdplayback)\r\n\t\treturn;\r\n\r\n\tif (mvd_info_show_header.value) {\r\n\t\tstrlcpy(mvd_info_header_string, mvd_info_setup.string, sizeof(mvd_info_header_string));\r\n\t\tReplace_In_String(mvd_info_header_string, sizeof(mvd_info_header_string), '%', \\\r\n\t\t\t10, \\\r\n\t\t\t\"a\", \"Armor\", \\\r\n\t\t\t\"f\", \"Frags\", \\\r\n\t\t\t\"h\", \"Health\", \\\r\n\t\t\t\"l\", \"Location\", \\\r\n\t\t\t\"n\", \"Nick\", \\\r\n\t\t\t\"P\", \"Ping\", \\\r\n\t\t\t\"p\", \"Powerup\", \\\r\n\t\t\t\"v\", \"Value\", \\\r\n\t\t\t\"w\", \"Cur.Weap.\", \\\r\n\t\t\t\"W\", \"Best Weap.\");\r\n\t\tstrlcpy(mvd_info_header_string, Make_Red(mvd_info_header_string, 0), sizeof(mvd_info_header_string));\r\n\t\tstrlcpy(str, mvd_info_final_string, sizeof(str));\r\n\t\tx = ELEMENT_X_COORD(mvd_info);\r\n\t\ty = ELEMENT_Y_COORD(mvd_info);\r\n\t\tDraw_String(x, y + ((z++) * 8), mvd_info_header_string);\r\n\t}\r\n\r\n\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\r\n\r\n\t\tmvd_info_powerups[0] = 0;\r\n\t\tif (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_QUAD)\r\n\t\t\t//strlcpy(mvd_info_powerups, tp_name_quad.string, sizeof(mvd_info_powerups));\r\n\r\n\t\t\tif (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_INVULNERABILITY) {\r\n\t\t\t\t//if (mvd_info_powerups[0])\r\n\t\t\t\t//\tstrlcat(mvd_info_powerups, tp_name_separator.string, sizeof(mvd_info_powerups));\r\n\t\t\t\t//strlcat(mvd_info_powerups, tp_name_pent.string, sizeof(mvd_info_powerups));\r\n\t\t\t}\r\n\r\n\t\tif (mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_INVISIBILITY) {\r\n\t\t\t//if (mvd_info_powerups[0])\r\n\t\t\t//\tstrlcat(mvd_info_powerups, tp_name_separator.string, sizeof(mvd_info_powerups));\r\n\t\t\t//strlcat(mvd_info_powerups, tp_name_ring.string, sizeof(mvd_info_powerups));\r\n\t\t}\r\n\r\n\t\tstrlcpy(mvd_info_final_string, mvd_info_setup.string, sizeof(mvd_info_final_string));\r\n\t\tReplace_In_String(mvd_info_final_string, sizeof(mvd_info_final_string), '%', \\\r\n\t\t\t10, \\\r\n\t\t\t\"w\", va(\"%s:%i\", TP_ItemName(mvd_new_info[i].p_info->stats[STAT_ACTIVEWEAPON]), mvd_new_info[i].p_info->stats[STAT_AMMO]), \\\r\n\t\t\t\"W\", va(\"%s:%s\", MVD_BestWeapon_strings(i), MVD_BestAmmo(i)), \\\r\n\t\t\t\"a\", va(\"%i\", mvd_new_info[i].p_info->stats[STAT_ARMOR]), \\\r\n\t\t\t\"f\", va(\"%i\", mvd_new_info[i].p_info->frags), \\\r\n\t\t\t\"h\", va(\"%i\", mvd_new_info[i].p_info->stats[STAT_HEALTH]), \\\r\n\t\t\t\"l\", TP_LocationName(mvd_new_info[i].p_state->origin), \\\r\n\t\t\t\"n\", mvd_new_info[i].p_info->name, \\\r\n\t\t\t\"P\", va(\"%i\", mvd_new_info[i].p_info->ping), \\\r\n\t\t\t\"p\", mvd_info_powerups, \\\r\n\t\t\t\"v\", va(\"%f\", mvd_new_info[i].value));\r\n\t\tstrlcpy(str, mvd_info_final_string, sizeof(str));\r\n\t\tx = ELEMENT_X_COORD(mvd_info);\r\n\t\ty = ELEMENT_Y_COORD(mvd_info);\r\n\t\tDraw_String(x, y + ((z++) * 8), str);\r\n\r\n#ifdef DEBUG\r\n\t\tprintf(\"MVD_Info Stopped\\n\");\r\n#endif\r\n\t}\r\n}\r\n\r\nstatic void MVD_AddString(const char* line)\r\n{\r\n\tVX_TrackerPickupText(line);\r\n\r\n\tif (announcer_lines == MAX_ANNOUNCER_LINES) {\r\n\t\tmemmove(&announcer_line_strings[0], &announcer_line_strings[1], sizeof(announcer_line_strings[0]) * (MAX_ANNOUNCER_LINES - 1));\r\n\t\tmemmove(&announcer_line_times[0], &announcer_line_times[1], sizeof(announcer_line_times[0]) * (MAX_ANNOUNCER_LINES - 1));\r\n\t\tannouncer_lines = MAX_ANNOUNCER_LINES - 1;\r\n\t}\r\n\tstrlcpy(announcer_line_strings[announcer_lines], line, sizeof(announcer_line_strings[announcer_lines]));\r\n\tannouncer_line_times[announcer_lines] = cl.time;\r\n\t++announcer_lines;\r\n}\r\n\r\nconst char* MVD_AnnouncerString(int line, int total, float* alpha)\r\n{\r\n\tint index = announcer_lines - total + line;\r\n\tif (index >= 0 && index < announcer_lines && announcer_line_times[index] > cl.time - MAX_ANNOUNCER_LINE_TIME) {\r\n\t\tif (cl.time - announcer_line_times[index] < MAX_ANNOUNCER_LINE_TIME_FADE) {\r\n\t\t\t*alpha = 1;\r\n\t\t}\r\n\t\telse {\r\n\t\t\t*alpha = (1 - (cl.time - announcer_line_times[index] - MAX_ANNOUNCER_LINE_TIME_FADE) / (MAX_ANNOUNCER_LINE_TIME - MAX_ANNOUNCER_LINE_TIME_FADE));\r\n\t\t}\r\n\t\treturn announcer_line_strings[index];\r\n\t}\r\n\telse {\r\n\t\t*alpha = 0;\r\n\t\treturn \"\";\r\n\t}\r\n}\r\n\r\nstatic const char* MVD_AnnouncerTeamPlayerName(player_info_t* info)\r\n{\r\n\tif (cl.teamplay) {\r\n\t\tint player_color = Sbar_BottomColor(info);\r\n\r\n\t\tbyte red = host_basepal[player_color * 3];\r\n\t\tbyte green = host_basepal[player_color * 3 + 1];\r\n\t\tbyte blue = host_basepal[player_color * 3 + 2];\r\n\t\tchar* buf;\r\n\r\n\t\tif (info->bottomcolor == 3) {\r\n\t\t\tred = 99;\r\n\t\t\tgreen = 255;\r\n\t\t\tblue = 120;\r\n\t\t}\r\n\t\telse if (info->bottomcolor == 12) {\r\n\t\t\tred = green = 255;\r\n\t\t\tblue = 0;\r\n\t\t}\r\n\r\n\t\tbuf = va(\"&c%x%x%x%s&r\", (unsigned int)(red / 16), (unsigned int)(green / 16), (unsigned int)(blue / 16), info->name);\r\n\t\tCharsToWhite(buf, buf + strlen(buf));\r\n\t\treturn buf;\r\n\t}\r\n\telse {\r\n\t\treturn info->name;\r\n\t}\r\n}\r\n\r\nstatic const char* MVD_AnnouncerPlayerName(int i)\r\n{\r\n\treturn MVD_AnnouncerTeamPlayerName(mvd_new_info[i].p_info);\r\n}\r\n\r\nvoid MVD_Status_Announcer(int i, int z) {\r\n\t//char *pn = mvd_new_info[i].p_info->name;\r\n\tvec3_t* pl = &mvd_new_info[i].p_state->origin;\r\n\r\n\tif (cl.mvd_ktx_markers) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tif (mvd_new_info[i].mvdinfo.itemstats[z].mention > 0) {\r\n\t\tint mention = mvd_new_info[i].mvdinfo.itemstats[z].mention;\r\n\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[z].mention = 0;\r\n\r\n\t\tif (!mvd_moreinfo.integer) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif (z >= 2 && z < sizeof(mvd_wp_info) / sizeof(mvd_wp_info[0]) && z != GA_INFO && z != GL_INFO) {\r\n\t\t\tchar location_name[MAX_MACRO_STRING];\r\n\t\t\tchar item_name[MAX_MACRO_STRING];\r\n\t\t\tchar simple_item_name[MAX_MACRO_STRING];\r\n\r\n\t\t\tif (mvd_wp_info[z].name_cvar && mvd_wp_info[z].name_cvar->string && mvd_wp_info[z].name_cvar->string[0]) {\r\n\t\t\t\tUtil_SkipChars(mvd_wp_info[z].name_cvar->string, \"{}\", item_name, sizeof(item_name));\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tstrlcpy(item_name, mvd_wp_info[z].colored_name, sizeof(item_name));\r\n\t\t\t}\r\n\t\t\tUtil_SkipEZColors(simple_item_name, item_name, sizeof(simple_item_name));\r\n\t\t\tstrlcpy(location_name, TP_ParseFunChars(TP_LocationName(*pl), false), sizeof(location_name));\r\n\t\t\tif (mention == MENTION_PICKED_UP_ITEM) {\r\n\t\t\t\tqbool names_match = !strcmp(location_name, simple_item_name);\r\n\r\n\t\t\t\tif (item_counts[z] == 1 || names_match) {\r\n\t\t\t\t\tMVD_AddString(va(\"%*s \\015 %s\\n\", MVD_ANNOUNCER_ITEM_LENGTH + (strlen(item_name) - strlen_color(item_name)), item_name, MVD_AnnouncerPlayerName(i)));\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tMVD_AddString(va(\"%*s \\015 %s \\020%s\\021\\n\", MVD_ANNOUNCER_ITEM_LENGTH + (strlen(item_name) - strlen_color(item_name)), item_name, MVD_AnnouncerPlayerName(i), location_name));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (mention == MENTION_PICKED_UP_PACK) {\r\n\t\t\t\tif (tp_name_backpack.string[0]) {\r\n\t\t\t\t\tchar pack_name[MAX_MACRO_STRING];\r\n\r\n\t\t\t\t\tUtil_SkipChars(tp_name_backpack.string, \"{}\", pack_name, sizeof(pack_name));\r\n\r\n\t\t\t\t\tstrlcat(item_name, \" \", sizeof(item_name));\r\n\t\t\t\t\tstrlcat(item_name, pack_name, sizeof(item_name));\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tstrlcat(item_name, \" pack\", sizeof(item_name));\r\n\t\t\t\t}\r\n\r\n\t\t\t\tMVD_AddString(va(\"%*s \\015 %s \\020%s\\021\\n\", MVD_ANNOUNCER_ITEM_LENGTH + (strlen(item_name) - strlen_color(item_name)), item_name, MVD_AnnouncerPlayerName(i), location_name));\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse if (mvd_new_info[i].mvdinfo.itemstats[z].mention < 0) {\r\n\t\tint mention = mvd_new_info[i].mvdinfo.itemstats[z].mention;\r\n\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[z].mention = 0;\r\n\r\n\t\tif (!mvd_moreinfo.integer) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif (z >= 5 && z < sizeof(mvd_wp_info) / sizeof(mvd_wp_info[0])) {\r\n\t\t\tchar location_name[MAX_MACRO_STRING];\r\n\t\t\tchar item_name[MAX_MACRO_STRING];\r\n\t\t\tconst char* pn = MVD_AnnouncerPlayerName(i);\r\n\r\n\t\t\tif (mvd_wp_info[z].name_cvar && mvd_wp_info[z].name_cvar->string && mvd_wp_info[z].name_cvar->string[0]) {\r\n\t\t\t\tUtil_SkipChars(mvd_wp_info[z].name_cvar->string, \"{}\", item_name, sizeof(item_name));\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tstrlcpy(item_name, mvd_wp_info[z].colored_name, sizeof(item_name));\r\n\t\t\t}\r\n\t\t\tstrlcpy(location_name, TP_ParseFunChars(TP_LocationName(*pl), false), sizeof(location_name));\r\n\t\t\tif (mention == MENTION_DIED_WITH_ITEM) {\r\n\t\t\t\t// Weapon tracking should probably be added back in\r\n\t\t\t\tif (cl.deathmatch == 1 && (z == RING_INFO || z == QUAD_INFO)) {\r\n\t\t\t\t\tstrlcat(item_name, \" &cf00dead&r\", sizeof(item_name));\r\n\r\n\t\t\t\t\tMVD_AddString(va(\"%*s   (%s)\\n\", MVD_ANNOUNCER_ITEM_LENGTH + (strlen(item_name) - strlen_color(item_name)), item_name, pn));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (mention == MENTION_ITEM_RAN_OUT) {\r\n\t\t\t\tstrlcat(item_name, \" over\", sizeof(item_name));\r\n\r\n\t\t\t\tMVD_AddString(va(\"%*s   (%s)\\n\", MVD_ANNOUNCER_ITEM_LENGTH + (strlen(item_name) - strlen_color(item_name)), item_name, pn));\r\n\t\t\t}\r\n\t\t\telse if (mention == MENTION_DROPPED_ITEM) {\r\n\t\t\t\tif (cl.deathmatch == 1) {\r\n\t\t\t\t\tstrlcat(item_name, \" &cf00drop&r\", sizeof(item_name));\r\n\r\n\t\t\t\t\tMVD_AddString(va(\"%*s \\015 %s \\020%s\\021\\n\", MVD_ANNOUNCER_ITEM_LENGTH + (strlen(item_name) - strlen_color(item_name)), item_name, MVD_AnnouncerPlayerName(i), location_name));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nvoid MVD_Status_WP(int i, int* taken) {\r\n\tint j, k;\r\n\tfor (k = j = SSG_INFO; j <= LG_INFO; j++, k = k * 2) {\r\n\t\tif (!mvd_new_info[i].mvdinfo.itemstats[j].has && mvd_new_info[i].p_info->stats[STAT_ITEMS] & k) {\r\n\t\t\tif (j >= GL_INFO && cl.deathmatch == 1) {\r\n\t\t\t\t*taken |= (1 << j);\r\n\t\t\t}\r\n\t\t\tmvd_new_info[i].mvdinfo.itemstats[j].has = 1;\r\n\t\t\tmvd_new_info[i].mvdinfo.itemstats[j].count++;\r\n\t\t}\r\n\t}\r\n\r\n}\r\n\r\nvoid MVD_Stats_Cleanup(void)\r\n{\r\n\tquad_is_active = false;\r\n\tpent_is_active = false;\r\n\tpowerup_cam_status = powerup_cam_inactive;\r\n\tmemset(powerup_cam_active, 0, sizeof(powerup_cam_active));\r\n\tpent_time = quad_time = 0;\r\n\twas_standby = true;\r\n\twhile (mvd_clocklist) {\r\n\t\tMVD_ClockList_Remove(mvd_clocklist);\r\n\t}\r\n\r\n\tmemset(mvd_new_info, 0, sizeof(mvd_new_info));\r\n\tmemset(&mvd_cg_info, 0, sizeof(mvd_cg_info));\r\n\tfixed_ordering = 0;\r\n}\r\n\r\nvoid MVD_Set_Armor_Stats(int z, int i) {\r\n\tswitch (z) {\r\n\tcase GA_INFO:\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[YA_INFO].has = 0;\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[RA_INFO].has = 0;\r\n\t\tbreak;\r\n\tcase YA_INFO:\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[GA_INFO].has = 0;\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[RA_INFO].has = 0;\r\n\t\tbreak;\r\n\tcase RA_INFO:\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[GA_INFO].has = 0;\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[YA_INFO].has = 0;\r\n\t\tbreak;\r\n\r\n\t}\r\n}\r\n\r\n// calculates the average values of run statistics\r\nvoid MVD_Stats_CalcAvgRuns(void)\r\n{\r\n\tint i;\r\n\tstatic double lastupdate = 0;\r\n\r\n\t// no need to recalculate the values in every frame\r\n\tif (cls.demopackettime - lastupdate < 0.5) {\r\n\t\treturn;\r\n\t}\r\n\telse {\r\n\t\tlastupdate = cls.demopackettime;\r\n\t}\r\n\r\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\r\n\t\tmvd_info_t* pi = &mvd_new_info[i].mvdinfo;\r\n\t\t//\t\tint r = mvd_new_info[i].mvdinfo.run;\r\n\t\tint tf, ttf, tt;\r\n\t\tint j;\r\n\t\ttf = ttf = tt = 0;\r\n\r\n\t\tfor (j = 0; j < pi->run; j++) {\r\n\t\t\ttf += pi->runs[j].frags;\r\n\t\t\tttf += pi->runs[j].teamfrags;\r\n\t\t\ttt += pi->runs[j].time;\r\n\t\t}\r\n\r\n\t\tif (pi->run) {\r\n\t\t\tpi->run_stats.all.avg_frags = tf / (double)pi->run;\r\n\t\t\tpi->run_stats.all.avg_teamfrags = ttf / (double)pi->run;\r\n\t\t\tpi->run_stats.all.avg_time = tt / (double)pi->run;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nstatic qbool MVD_Weapon_From_Backpack(int weapon, int taken, int* ammotaken)\r\n{\r\n\t// Things that signalize backpack took:\r\n\t// a) some taken ammo, different than weapon pickup gives, or\r\n\t// b) more than one weapon was taken\r\n\r\n\t// From the info we have the process is undeterministic.\r\n\t// The current semantics is \"paranoid\":\r\n\t// When not sure, expect it was a backpack pickup.\r\n\t// Howver, it is still impossible to distinguish some backpack pickups.\r\n\r\n\t// Why?\r\n\t// For example when player has sg and 199 shells and his status\r\n\t// changes to ssg + 200 shells, it's impossible to tell whether\r\n\t// he picked up ssg from pack or regular ssg spawn.\r\n\t// Or when he has 0 rockets and then picks rl pack with 5 rockets,\r\n\t// it looks like he picked regular rl.\r\n\t// Also there's the thing which is dependent on deathmatch setting when you\r\n\t// get extra rockets that even weren't in the rl pack originally\r\n\t// but you get them anyway if you have no rox.\r\n\r\n\t// Also worth mentioning this does not reflect custom mods\r\n\t// like tf, ctf and others...\r\n\r\n\t// Possible approach would be to check player coordinates\r\n\t// and check whether on given bsp some of the spawnpoints\r\n\t// of given item is close enough to the player.\r\n\r\n\t// Another possible approach would be to extend the protocol with\r\n\t// proper pickup info delivery.\r\n\r\n\tint i;\r\n\r\n\tif (weapon == SSG_INFO) {\r\n\t\tif (ammotaken[0] != 5 || ammotaken[1] > 0 || ammotaken[2] > 0 || ammotaken[3] > 0) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\tif (weapon == NG_INFO) {\r\n\t\tif (ammotaken[0] > 0 || ammotaken[1] != 30 || ammotaken[2] > 0 || ammotaken[3] > 0) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\tif (weapon == SNG_INFO) {\r\n\t\tif (ammotaken[0] > 0 || ammotaken[1] != 30 || ammotaken[2] > 0 || ammotaken[3] > 0) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\tif (weapon == GL_INFO) {\r\n\t\tif (ammotaken[0] > 0 || ammotaken[1] > 0 || ammotaken[2] != 5 || ammotaken[3] > 0) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\tif (weapon == RL_INFO) {\r\n\t\tif (ammotaken[0] > 0 || ammotaken[1] > 0 || ammotaken[2] != 5 || ammotaken[3] > 0) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\tif (weapon == LG_INFO) {\r\n\t\tif (ammotaken[0] > 0 || ammotaken[1] > 0 || ammotaken[2] > 0 || ammotaken[3] != 15) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\r\n\tfor (i = SSG_INFO; i <= LG_INFO; i++) {\r\n\t\tif ((taken & (1 << i)) && i != weapon) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\r\n\treturn false;\r\n}\r\n\r\nstatic void MVD_Stats_Gather_AlivePlayer(int player_index)\r\n{\r\n\tint x; // item index\r\n\tint z; // item index\r\n\tint i = player_index;\r\n\tint killdiff;\r\n\tint taken = 0;\r\n\tint ammotaken[AMMO_TYPES] = { 0, 0, 0, 0 };\r\n\tqbool had_mega;\r\n\tqbool has_mega;\r\n\r\n\tfor (x = GA_INFO; x <= RA_INFO && mvd_cg_info.deathmatch != 4; x++) {\r\n\t\tif (mvd_new_info[i].p_info->stats[STAT_ITEMS] & mvd_wp_info[x].it) {\r\n\r\n\t\t\tif (!mvd_new_info[i].mvdinfo.itemstats[x].has) {\r\n\t\t\t\ttaken |= (1 << x);\r\n\t\t\t\tMVD_Set_Armor_Stats(x, i);\r\n\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].count++;\r\n\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].lost = mvd_new_info[i].p_info->stats[STAT_ARMOR];\r\n\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].has = 1;\r\n\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].starttime = cls.demopackettime;\r\n\t\t\t}\r\n\r\n\t\t\tif (mvd_new_info[i].mvdinfo.itemstats[x].lost < mvd_new_info[i].p_info->stats[STAT_ARMOR]) {\r\n\t\t\t\ttaken |= (1 << x);\r\n\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].count++;\r\n\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].starttime = cls.demopackettime;\r\n\t\t\t}\r\n\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].lost = mvd_new_info[i].p_info->stats[STAT_ARMOR];\r\n\t\t}\r\n\t}\r\n\r\n\tfor (x = RING_INFO; x <= PENT_INFO && mvd_cg_info.deathmatch != 4; x++) {\r\n\t\tmvd_pw_t* item = &mvd_new_info[i].mvdinfo.itemstats[x];\r\n\r\n\t\tif (!item->has && (mvd_new_info[i].p_info->stats[STAT_ITEMS] & mvd_wp_info[x].it)) {\r\n\t\t\ttaken |= (1 << x);\r\n\t\t\titem->has = 1;\r\n\r\n\t\t\tif (x == PENT_INFO && (powerup_cam_status == powerup_cam_quadpent_active || powerup_cam_status == powerup_cam_pent_active)) {\r\n\t\t\t\tpent_is_active = true;\r\n\t\t\t\tpowerup_cam_status -= powerup_cam_pent_active;\r\n\t\t\t\tpent_time = cls.demopackettime;\r\n\t\t\t}\r\n\t\t\tif (x == QUAD_INFO && (powerup_cam_status == powerup_cam_quadpent_active || powerup_cam_status == powerup_cam_quad_active)) {\r\n\t\t\t\tquad_is_active = true;\r\n\t\t\t\tpowerup_cam_status -= powerup_cam_quad_active;\r\n\t\t\t\tquad_time = cls.demopackettime;\r\n\t\t\t}\r\n\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].starttime = cls.demopackettime;\r\n\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].count++;\r\n\t\t}\r\n\t\tif (mvd_new_info[i].mvdinfo.itemstats[x].has && !(mvd_new_info[i].p_info->stats[STAT_ITEMS] & mvd_wp_info[x].it)) {\r\n\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].has = 0;\r\n\t\t\tif (x == QUAD_INFO && quad_is_active) {\r\n\t\t\t\tquad_is_active = false;\r\n\t\t\t}\r\n\t\t\tif (x == PENT_INFO && pent_is_active) {\r\n\t\t\t\tpent_is_active = false;\r\n\t\t\t}\r\n\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].runs[mvd_new_info[i].mvdinfo.itemstats[x].run].starttime = mvd_new_info[i].mvdinfo.itemstats[x].starttime;\r\n\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].runs[mvd_new_info[i].mvdinfo.itemstats[x].run].time = cls.demopackettime - mvd_new_info[i].mvdinfo.itemstats[x].starttime;\r\n\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].run++;\r\n\t\t}\r\n\t}\r\n\r\n\thad_mega = mvd_new_info[i].mvdinfo.itemstats[MH_INFO].has > 0;\r\n\thas_mega = mvd_new_info[i].p_info->stats[STAT_ITEMS] & IT_SUPERHEALTH;\r\n\tif (!had_mega && has_mega) {\r\n\t\t// Picked up mega\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[MH_INFO].mention = MENTION_PICKED_UP_ITEM;\r\n\t\tVectorCopy(mvd_new_info[i].p_state->origin, mvd_new_info[i].mega_locations[0]);\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[MH_INFO].has = 1;\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[MH_INFO].count++;\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[MH_INFO].starttime = cls.demopackettime;\r\n\t\tMVD_Status_Announcer(i, MH_INFO);\r\n\t}\r\n\telse if (has_mega && mvd_new_info[i].mvdinfo.itemstats[MH_INFO].lost < mvd_new_info[i].p_info->stats[STAT_HEALTH] && mvd_new_info[i].p_info->stats[STAT_HEALTH] > 100) {\r\n\t\t// They already had mega health but health increased - must have been another mega\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[MH_INFO].mention = MENTION_PICKED_UP_ITEM;\r\n\t\tif (mvd_new_info[i].mvdinfo.itemstats[MH_INFO].has < MAX_MEGAS_PER_PLAYER) {\r\n\t\t\tVectorCopy(mvd_new_info[i].p_state->origin, mvd_new_info[i].mega_locations[mvd_new_info[i].mvdinfo.itemstats[MH_INFO].has]);\r\n\t\t}\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[MH_INFO].has++;\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[MH_INFO].count++;\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[MH_INFO].starttime = cls.demopackettime;\r\n\t\tMVD_Status_Announcer(i, MH_INFO);\r\n\t}\r\n\tmvd_new_info[i].mvdinfo.itemstats[MH_INFO].lost = mvd_new_info[i].p_info->stats[STAT_HEALTH];\r\n\r\n\tif (had_mega && !has_mega) {\r\n\t\t// Might have had two megas ticking down, create as many clocks as necessary\r\n\t\tint megas = mvd_new_info[i].mvdinfo.itemstats[MH_INFO].has;\r\n\t\twhile (megas > 0) {\r\n\t\t\t--megas;\r\n\t\t\tif (!cl.mvd_ktx_markers) {\r\n\t\t\t\tMVD_ClockStart(MH_INFO, megas < MAX_MEGAS_PER_PLAYER ? mvd_new_info[i].mega_locations[megas] : NULL);\r\n\t\t\t}\r\n\t\t}\r\n\t\tmvd_new_info[i].mvdinfo.itemstats[MH_INFO].has = 0;\r\n\t}\r\n\r\n\tfor (z = RING_INFO; z <= PENT_INFO; z++) {\r\n\t\tif (mvd_new_info[i].mvdinfo.itemstats[z].has == 1) {\r\n\t\t\tmvd_new_info[i].mvdinfo.itemstats[z].runs[mvd_new_info[i].mvdinfo.itemstats[z].run].starttime = mvd_new_info[i].mvdinfo.itemstats[z].starttime;\r\n\t\t\tmvd_new_info[i].mvdinfo.itemstats[z].runs[mvd_new_info[i].mvdinfo.itemstats[z].run].time = cls.demopackettime - mvd_new_info[i].mvdinfo.itemstats[z].starttime;\r\n\t\t}\r\n\t}\r\n\r\n\tif (mvd_new_info[i].mvdinfo.lastfrags != mvd_new_info[i].p_info->frags) {\r\n\t\tif (mvd_new_info[i].mvdinfo.lastfrags < mvd_new_info[i].p_info->frags) {\r\n\t\t\tkilldiff = mvd_new_info[i].p_info->frags - mvd_new_info[i].mvdinfo.lastfrags;\r\n\t\t\tfor (z = 0; z < 8; z++) {\r\n\t\t\t\tif (z == MVD_Weapon_LWF(mvd_new_info[i].mvdinfo.lfw))\r\n\t\t\t\t\tmvd_new_info[i].mvdinfo.killstats.normal[z].kills += killdiff;\r\n\t\t\t}\r\n\t\t\tif (mvd_new_info[i].mvdinfo.lfw == -1)\r\n\t\t\t\tmvd_new_info[i].mvdinfo.spawntelefrags += killdiff;\r\n\t\t\tfor (z = 8; z < 11; z++) {\r\n\t\t\t\tif (mvd_new_info[i].mvdinfo.itemstats[z].has)\r\n\t\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[z].runs[mvd_new_info[i].mvdinfo.itemstats[z].run].frags += killdiff;\r\n\t\t\t}\r\n\t\t\tmvd_new_info[i].mvdinfo.runs[mvd_new_info[i].mvdinfo.run].frags++;\r\n\t\t}\r\n\t\telse if (mvd_new_info[i].mvdinfo.lastfrags > mvd_new_info[i].p_info->frags) {\r\n\t\t\tkilldiff = mvd_new_info[i].mvdinfo.lastfrags - mvd_new_info[i].p_info->frags;\r\n\t\t\tfor (z = AXE_INFO; z <= LG_INFO; z++) {\r\n\t\t\t\tif (z == MVD_Weapon_LWF(mvd_new_info[i].mvdinfo.lfw))\r\n\t\t\t\t\tmvd_new_info[i].mvdinfo.killstats.normal[z].teamkills += killdiff;\r\n\t\t\t}\r\n\t\t\tif (mvd_new_info[i].mvdinfo.lfw == -1) {\r\n\t\t\t\tmvd_new_info[i].mvdinfo.teamspawntelefrags += killdiff;\r\n\r\n\t\t\t}\r\n\t\t\tfor (z = 8; z < 11; z++) {\r\n\t\t\t\tif (mvd_new_info[i].mvdinfo.itemstats[z].has)\r\n\t\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[z].runs[mvd_new_info[i].mvdinfo.itemstats[z].run].teamfrags += killdiff;\r\n\t\t\t}\r\n\t\t\tmvd_new_info[i].mvdinfo.runs[mvd_new_info[i].mvdinfo.run].teamfrags++;\r\n\t\t}\r\n\r\n\r\n\t\tmvd_new_info[i].mvdinfo.lastfrags = mvd_new_info[i].p_info->frags;\r\n\t}\r\n\r\n\tmvd_new_info[i].mvdinfo.runs[mvd_new_info[i].mvdinfo.run].time = cls.demopackettime - mvd_new_info[i].mvdinfo.das.alivetimestart;\r\n\r\n\tif (mvd_new_info[i].mvdinfo.lfw == -1) {\r\n\t\tif (mvd_new_info[i].mvdinfo.lastfrags > mvd_new_info[i].p_info->frags) {\r\n\t\t\tmvd_new_info[i].mvdinfo.teamspawntelefrags += mvd_new_info[i].p_info->frags - mvd_new_info[i].mvdinfo.lastfrags;\r\n\t\t}\r\n\t\telse if (mvd_new_info[i].mvdinfo.lastfrags < mvd_new_info[i].p_info->frags) {\r\n\t\t\tmvd_new_info[i].mvdinfo.spawntelefrags += mvd_new_info[i].p_info->frags - mvd_new_info[i].mvdinfo.lastfrags;\r\n\t\t}\r\n\t\tmvd_new_info[i].mvdinfo.lastfrags = mvd_new_info[i].p_info->frags;\r\n\t}\r\n\r\n\tif (mvd_new_info[i].p_state->weaponframe > 0)\r\n\t\tmvd_new_info[i].mvdinfo.lfw = mvd_new_info[i].p_info->stats[STAT_ACTIVEWEAPON];\r\n\tif (mvd_cg_info.deathmatch != 4) {\r\n\t\tMVD_Status_WP(i, &taken);\r\n\t\tfor (z = SSG_INFO; z <= RA_INFO; z++) {\r\n\t\t\tMVD_Status_Announcer(i, z);\r\n\t\t}\r\n\t}\r\n\r\n\tfor (x = 0; x < AMMO_TYPES; x++) {\r\n\t\tint ammo_new = mvd_new_info[i].p_info->stats[STAT_SHELLS + x];\r\n\t\tint ammo_old = mvd_new_info[i].mvdinfo.ammostats[x];\r\n\t\tif (mvd_new_info[i].mvdinfo.initialized && ammo_new > ammo_old) {\r\n\t\t\tint diff = ammo_new - ammo_old;\r\n\t\t\tammotaken[x] = diff;\r\n\t\t}\r\n\t\tmvd_new_info[i].mvdinfo.ammostats[x] =\r\n\t\t\tmvd_new_info[i].p_info->stats[STAT_SHELLS + x];\r\n\t}\r\n\r\n\tfor (x = 0; x < mvd_info_types; x++) {\r\n\t\tif (taken & (1 << x)) {\r\n\t\t\tqbool weapon_from_backpack =\r\n\t\t\t\tIS_WEAPON(x) && MVD_Weapon_From_Backpack(x, taken, ammotaken);\r\n\t\t\t// don't start clock if item was from backpack\r\n\t\t\tqbool add_clock = !weapon_from_backpack;\r\n\t\t\tCom_DPrintf(\"player %i took %i, weapon from backpack: %s\\n\",\r\n\t\t\t\ti, x, weapon_from_backpack ? \"yes\" : \"no\");\r\n\r\n\t\t\tMVD_Took(i, x, add_clock);\r\n\t\t}\r\n\t}\r\n}\r\n\r\nint MVD_Stats_Gather(void)\r\n{\r\n\tint death_stats = 0;\r\n\tint x, i;\r\n\r\n\tif (cl.countdown || cl.standby) {\r\n\t\treturn 0;\r\n\t}\r\n\r\n\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\r\n\t\tmvd_new_info[i].p_state = &cl.frames[cl.parsecount & UPDATE_MASK].playerstate[mvd_new_info[i].id];\r\n\t}\r\n\r\n\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\r\n\t\tif (pent_time == 0 && quad_time == 0 && !mvd_new_info[i].mvdinfo.firstrun) {\r\n\t\t\tpowerup_cam_status = powerup_cam_quadpent_active;\r\n\t\t\tquad_time = pent_time = cls.demopackettime;\r\n\t\t}\r\n\r\n\t\tif (mvd_new_info[i].mvdinfo.firstrun == 0) {\r\n\t\t\tmvd_new_info[i].mvdinfo.das.alivetimestart = cls.demopackettime;\r\n\t\t\tgamestart_time = cls.demopackettime;\r\n\t\t\tmvd_new_info[i].mvdinfo.firstrun = 1;\r\n\t\t\tmvd_new_info[i].mvdinfo.lfw = -1;\r\n\t\t}\r\n\r\n\t\t// death alive stats\r\n\t\tif (mvd_new_info[i].p_info->stats[STAT_HEALTH] > 0 && mvd_new_info[i].mvdinfo.das.isdead == 1) {\r\n\t\t\tmvd_new_info[i].mvdinfo.das.isdead = 0;\r\n\t\t\tmvd_new_info[i].mvdinfo.das.alivetimestart = cls.demopackettime;\r\n\t\t\tmvd_new_info[i].mvdinfo.lfw = -1;\r\n\t\t}\r\n\r\n\t\tmvd_new_info[i].mvdinfo.das.alivetime = cls.demopackettime - mvd_new_info[i].mvdinfo.das.alivetimestart;\r\n\t\tif (mvd_new_info[i].p_info->stats[STAT_HEALTH] <= 0 && mvd_new_info[i].mvdinfo.das.isdead != 1) {\r\n\t\t\tmvd_new_info[i].mvdinfo.das.isdead = 1;\r\n\t\t\tmvd_new_info[i].mvdinfo.das.deathcount++;\r\n\t\t\tdeath_stats = 1;\r\n\t\t}\r\n\r\n\t\tif (death_stats) {\r\n\t\t\tdeath_stats = 0;\r\n\t\t\tmvd_new_info[i].mvdinfo.run++;\r\n\r\n\t\t\tfor (x = 0; x < mvd_info_types; x++) {\r\n\t\t\t\tif (mvd_new_info[i].p_info && mvd_new_info[i].p_info->stats[STAT_ACTIVEWEAPON] == mvd_wp_info[x].it) {\r\n\t\t\t\t\tif (mvd_wp_info[x].it == IT_ROCKET_LAUNCHER || mvd_wp_info[x].it == IT_LIGHTNING) {\r\n\t\t\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].mention = MENTION_DROPPED_ITEM;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].lost++;\r\n\t\t\t\t}\r\n\t\t\t\telse if (mvd_new_info[i].p_info && (mvd_new_info[i].p_info->stats[STAT_ITEMS] & mvd_wp_info[x].it)) {\r\n\t\t\t\t\tif (mvd_wp_info[x].it == IT_ROCKET_LAUNCHER || mvd_wp_info[x].it == IT_LIGHTNING || mvd_wp_info[x].it == IT_QUAD || mvd_wp_info[x].it == IT_INVISIBILITY) {\r\n\t\t\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].mention = MENTION_DIED_WITH_ITEM;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t/*if (x == MVD_Weapon_LWF(mvd_new_info[i].mvdinfo.lfw)) {\r\n\t\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].mention = MENTION_DIED_WITH_ITEM;\r\n\t\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].lost++;\r\n\t\t\t\t}*/\r\n\r\n\t\t\t\tif (x == QUAD_INFO && mvd_new_info[i].mvdinfo.itemstats[QUAD_INFO].has) {\r\n\t\t\t\t\tif (mvd_new_info[i].mvdinfo.itemstats[x].starttime - cls.demopackettime < 30) {\r\n\t\t\t\t\t\tquad_is_active = false;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].run++;\r\n\t\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].lost++;\r\n\t\t\t\t}\r\n\t\t\t\tmvd_new_info[i].mvdinfo.itemstats[x].has = 0;\r\n\t\t\t}\r\n\t\t\tmvd_new_info[i].mvdinfo.lfw = -1;\r\n\t\t}\r\n\r\n\t\tif (!mvd_new_info[i].mvdinfo.das.isdead) {\r\n\t\t\tMVD_Stats_Gather_AlivePlayer(i);\r\n\t\t}\r\n\t\telse {\r\n\t\t\t// Report dropped items immediately\r\n\t\t\tint z;\r\n\t\t\tfor (z = SSG_INFO; z <= RA_INFO; z++) {\r\n\t\t\t\tMVD_Status_Announcer(i, z);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ((((pent_time + 300) - cls.demopackettime) < 5) && !pent_is_active) {\r\n\t\t\tpowerup_cam_status |= powerup_cam_pent_active;\r\n\t\t}\r\n\t\tif ((((quad_time + 60) - cls.demopackettime) < 5) && !quad_is_active) {\r\n\t\t\tpowerup_cam_status |= powerup_cam_quad_active;\r\n\t\t}\r\n\t\tmvd_new_info[i].mvdinfo.initialized = true;\r\n\t}\r\n\r\n\treturn 1;\r\n}\r\n\r\nvoid MVD_Status(void)\r\n{\r\n\tint x, y, p;\r\n\tchar str[1024];\r\n\tint i;\r\n\tint id = 0;\r\n\tint z = 0;\r\n\tdouble av_f = 0;\r\n\tdouble av_t = 0;\r\n\tdouble av_tk = 0;\r\n\r\n\tif (!mvd_status.value)\r\n\t\treturn;\r\n\r\n\tif (!cls.mvdplayback)\r\n\t\treturn;\r\n\r\n\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\r\n\t\tif (mvd_new_info[i].id == cl.spec_track) {\r\n\t\t\tid = i;\r\n\t\t}\r\n\t}\r\n\r\n\tx = ELEMENT_X_COORD(mvd_status);\r\n\ty = ELEMENT_Y_COORD(mvd_status);\r\n\tif (mvd_new_info[id].p_info) {\r\n\t\tstrlcpy(str, mvd_new_info[id].p_info->name, sizeof(str));\r\n\t}\r\n\telse {\r\n\t\tstr[0] = '\\0';\r\n\t}\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\tstrlcpy(str, \"&cf40Took\", sizeof(str));\r\n\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, va(\"RL: %i LG: %i GL: %i RA: %i YA: %i GA:%i\", \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[RL_INFO].count, \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[LG_INFO].count, \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[GL_INFO].count, \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[RA_INFO].count, \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[YA_INFO].count, \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[GA_INFO].count), sizeof(str));\r\n\tstrlcpy(str, Make_Red(str, 1), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\tstrlcpy(str, va(\"Ring: %i Quad: %i Pent: %i MH: %i\", \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[RING_INFO].count, \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[QUAD_INFO].count, \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[PENT_INFO].count, \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[MH_INFO].count), sizeof(str));\r\n\tstrlcpy(str, Make_Red(str, 1), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\t//\tCom_Printf(\"%f %f %f \\n\",lasttime,mvd_new_info[id].mvdinfo.das.alivetimestart, mvd_new_info[id].mvdinfo.das.alivetime);\r\n\tif (cls.demopackettime >= lasttime + .1) {\r\n\t\tlasttime = cls.demopackettime;\r\n\t\tlasttime1 = mvd_new_info[id].mvdinfo.das.alivetime;\r\n\t}\r\n\r\n\tstrlcpy(str, va(\"Deaths: %i\", mvd_new_info[id].mvdinfo.das.deathcount), sizeof(str));\r\n\tstrlcpy(str, Make_Red(str, 1), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, \"Average Run:\", sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, \"Time      Frags TKS\", sizeof(str));\r\n\tstrlcpy(str, Make_Red(str, 1), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tfor (p = 0; p <= mvd_new_info[id].mvdinfo.run; p++) {\r\n\t\tav_t += mvd_new_info[id].mvdinfo.runs[p].time;\r\n\t\tav_f += mvd_new_info[id].mvdinfo.runs[p].frags;\r\n\t\tav_tk += mvd_new_info[id].mvdinfo.runs[p].teamfrags;\r\n\t}\r\n\tif (av_t > 0) {\r\n\t\tav_t = av_t / (mvd_new_info[id].mvdinfo.run + 1);\r\n\t\tav_f = av_f / (mvd_new_info[id].mvdinfo.run + 1);\r\n\t\tav_tk = av_tk / (mvd_new_info[id].mvdinfo.run + 1);\r\n\t}\r\n\r\n\tstrlcpy(str, va(\"%9.3f %3.3f %3.3f\", av_t, av_f, av_tk), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\r\n\tstrlcpy(str, \"Last 3 Runs:\", sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, \"No. Time      Frags TKS\", sizeof(str));\r\n\tstrlcpy(str, Make_Red(str, 1), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tp = mvd_new_info[id].mvdinfo.run - 3;\r\n\tif (p < 0)\r\n\t\tp = 0;\r\n\t//&& mvd_new_info[id].mvdinfo.runs[p].time\r\n\tfor (; p <= mvd_new_info[id].mvdinfo.run; p++) {\r\n\t\tstrlcpy(str, va(\"%3i %9.3f %5i %3i\", p + 1, mvd_new_info[id].mvdinfo.runs[p].time, mvd_new_info[id].mvdinfo.runs[p].frags, mvd_new_info[id].mvdinfo.runs[p].teamfrags), sizeof(str));\r\n\t\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\t}\r\n\tstrlcpy(str, va(\"Last Fired Weapon: %s\", TP_ItemName(mvd_new_info[id].mvdinfo.lfw)), sizeof(str));\r\n\tstrlcpy(str, Make_Red(str, 1), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, \"&cf40Lost\", sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, va(\"RL: %i LG: %i GL: %i QUAD: %i\", \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[RL_INFO].lost, \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[LG_INFO].lost, \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[GL_INFO].lost, \\\r\n\t\tmvd_new_info[id].mvdinfo.itemstats[QUAD_INFO].lost), sizeof(str));\r\n\tstrlcpy(str, Make_Red(str, 1), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, \"&cf40Kills\", sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, va(\"RL: %i LG: %i GL: %i SNG: %i NG: %i SSG: %i SG: %i AXE: %i\", \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[RL_INFO].kills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[LG_INFO].kills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[GL_INFO].kills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[SNG_INFO].kills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[NG_INFO].kills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[SSG_INFO].kills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[SG_INFO].kills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[AXE_INFO].kills), sizeof(str));\r\n\tstrlcpy(str, Make_Red(str, 1), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, va(\"SPAWN: %i\", \\\r\n\t\tmvd_new_info[id].mvdinfo.spawntelefrags), sizeof(str));\r\n\tstrlcpy(str, Make_Red(str, 1), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, \"&cf40Teamkills\", sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, va(\"RL: %i LG: %i GL: %i SNG: %i NG: %i SSG: %i SG: %i AXE: %i\", \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[RL_INFO].teamkills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[LG_INFO].teamkills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[GL_INFO].teamkills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[SNG_INFO].teamkills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[NG_INFO].teamkills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[SSG_INFO].teamkills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[SG_INFO].teamkills, \\\r\n\t\tmvd_new_info[id].mvdinfo.killstats.normal[AXE_INFO].teamkills), sizeof(str));\r\n\tstrlcpy(str, Make_Red(str, 1), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\tstrlcpy(str, va(\"SPAWN: %i\", \\\r\n\t\tmvd_new_info[id].mvdinfo.teamspawntelefrags), sizeof(str));\r\n\tstrlcpy(str, Make_Red(str, 1), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, \"Last 3 Quad Runs:\", sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tstrlcpy(str, \"No. Time      Frags TKS\", sizeof(str));\r\n\tstrlcpy(str, Make_Red(str, 0), sizeof(str));\r\n\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\r\n\tp = mvd_new_info[id].mvdinfo.itemstats[QUAD_INFO].run - 3;\r\n\tif (p < 0)\r\n\t\tp = 0;\r\n\tfor (; p <= mvd_new_info[id].mvdinfo.itemstats[QUAD_INFO].run && mvd_new_info[id].mvdinfo.itemstats[QUAD_INFO].runs[p].time; p++) {\r\n\t\tstrlcpy(str, va(\"%3i %9.3f %5i %3i\", p + 1, mvd_new_info[id].mvdinfo.itemstats[QUAD_INFO].runs[p].time, mvd_new_info[id].mvdinfo.itemstats[QUAD_INFO].runs[p].frags, mvd_new_info[id].mvdinfo.itemstats[QUAD_INFO].runs[p].teamfrags), sizeof(str));\r\n\t\tDraw_ColoredString(x, y + ((z++) * 8), str, 1, false);\r\n\t}\r\n}\r\n\r\nqbool MVD_MatchStarted(void) {\r\n\tif (was_standby && !cl.standby) {\r\n\t\twas_standby = false;\r\n\t\treturn true;\r\n\t}\r\n\twas_standby = cl.standby;\r\n\treturn false;\r\n}\r\n\r\nvoid MVD_Mainhook(void) {\r\n\tif (MVD_MatchStarted()) {\r\n\t\tMVD_Init_Info(MAX_CLIENTS);\r\n\t}\r\n\r\n\tMVD_Stats_Gather();\r\n\tMVD_Stats_CalcAvgRuns();\r\n\tMVD_AutoTrack();\r\n\tMVD_ClockList_RemoveExpired();\r\n\tif (cls.mvdplayback && mvd_demo_track_run == 0) {\r\n\t\tMVD_Demo_Track();\r\n\t}\r\n}\r\n\r\nstatic void MVD_PowerupCam_GetCoords(void)\r\n{\r\n\tchar val[1024];\r\n\r\n\tstrlcpy(val, mvd_pc_quad_1.string, sizeof(val));\r\n\tcam_id[0].cam.org[0] = (float)atof(strtok(val, \" \"));\r\n\tcam_id[0].cam.org[1] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[0].cam.org[2] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[0].cam.angles[0] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[0].cam.angles[1] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[0].tag = \"q1\";\r\n\tcam_id[0].filter = powerup_cam_quad_active;\r\n\r\n\tstrlcpy(val, mvd_pc_quad_2.string, sizeof(val));\r\n\tcam_id[1].cam.org[0] = (float)atof(strtok(val, \" \"));\r\n\tcam_id[1].cam.org[1] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[1].cam.org[2] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[1].cam.angles[0] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[1].cam.angles[1] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[1].tag = \"q2\";\r\n\tcam_id[1].filter = powerup_cam_quad_active;\r\n\r\n\tstrlcpy(val, mvd_pc_quad_3.string, sizeof(val));\r\n\tcam_id[2].cam.org[0] = (float)atof(strtok(val, \" \"));\r\n\tcam_id[2].cam.org[1] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[2].cam.org[2] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[2].cam.angles[0] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[2].cam.angles[1] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[2].tag = \"q3\";\r\n\tcam_id[2].filter = powerup_cam_quad_active;\r\n\r\n\tstrlcpy(val, mvd_pc_pent_1.string, sizeof(val));\r\n\tcam_id[3].cam.org[0] = (float)atof(strtok(val, \" \"));\r\n\tcam_id[3].cam.org[1] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[3].cam.org[2] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[3].cam.angles[0] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[3].cam.angles[1] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[3].tag = \"p1\";\r\n\tcam_id[3].filter = powerup_cam_pent_active;\r\n\r\n\tstrlcpy(val, mvd_pc_pent_2.string, sizeof(val));\r\n\tcam_id[4].cam.org[0] = (float)atof(strtok(val, \" \"));\r\n\tcam_id[4].cam.org[1] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[4].cam.org[2] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[4].cam.angles[0] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[4].cam.angles[1] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[4].tag = \"p2\";\r\n\tcam_id[4].filter = powerup_cam_pent_active;\r\n\r\n\tstrlcpy(val, mvd_pc_pent_3.string, sizeof(val));\r\n\tcam_id[5].cam.org[0] = (float)atof(strtok(val, \" \"));\r\n\tcam_id[5].cam.org[1] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[5].cam.org[2] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[5].cam.angles[0] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[5].cam.angles[1] = (float)atof(strtok(NULL, \" \"));\r\n\tcam_id[5].tag = \"p3\";\r\n\tcam_id[5].filter = powerup_cam_pent_active;\r\n}\r\n\r\nstatic qbool MVD_PowerupCam_Process(cvar_t* cvar)\r\n{\r\n\tqbool active = false;\r\n\r\n\tif (strlen(cvar->string)) {\r\n\t\tint i;\r\n\t\tqbool found = false;\r\n\r\n\t\tfor (i = 0; i < 6; i++) {\r\n\t\t\tif (!strcmp(cvar->string, cam_id[i].tag)) {\r\n\t\t\t\tfound = true;\r\n\r\n\t\t\t\tif (cam_id[i].filter & powerup_cam_status) {\r\n\t\t\t\t\tVectorCopy(cam_id[i].cam.angles, r_refdef.viewangles);\r\n\t\t\t\t\tVectorCopy(cam_id[i].cam.org, r_refdef.vieworg);\r\n\t\t\t\t\tactive = true;\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (!found) {\r\n\t\t\tCvar_Set(cvar, \"\");\r\n\t\t\tCom_Printf(\"wrong tag for %s\\n\", cvar->name);\r\n\t\t}\r\n\t}\r\n\r\n\treturn active;\r\n}\r\n\r\n// If powerup cam configured but not currently enabled\r\nqbool MVD_PowerupCam_Hidden(void)\r\n{\r\n\tint view_number = CL_MultiviewCurrentView();\r\n\r\n\tif (view_number < 1 || view_number > sizeof(powerup_cam_active) / sizeof(powerup_cam_active[0])) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\t--view_number;\r\n\treturn powerup_cam_cvars[view_number].string && powerup_cam_cvars[view_number].string[0] && !(powerup_cam_status && powerup_cam_active[view_number]);\r\n}\r\n\r\nint MVD_PowerupCams_Enabled(void)\r\n{\r\n\tint i, count = 0;\r\n\r\n\tfor (i = 0; i < sizeof(powerup_cam_active) / sizeof(powerup_cam_active[0]) && i < cl_multiview.integer; ++i) {\r\n\t\tif (powerup_cam_active[i]) {\r\n\t\t\t++count;\r\n\t\t}\r\n\t}\r\n\r\n\treturn count;\r\n}\r\n\r\nqbool MVD_PowerupCam_Enabled(void)\r\n{\r\n\tint view_number = CL_MultiviewCurrentView();\r\n\r\n\tif (view_number < 1 || view_number > sizeof(powerup_cam_active) / sizeof(powerup_cam_active[0])) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\t--view_number;\r\n\treturn powerup_cam_cvars[view_number].string && powerup_cam_cvars[view_number].string[0] && powerup_cam_status && powerup_cam_active[view_number];\r\n}\r\n\r\nvoid MVD_PowerupCam_Frame(void)\r\n{\r\n\tint view_number = CL_MultiviewCurrentView();\r\n\r\n\tif (view_number < 1 || view_number > sizeof(powerup_cam_active) / sizeof(powerup_cam_active[0])) {\r\n\t\treturn;\r\n\t}\r\n\r\n\t--view_number;\r\n\tif (!mvd_powerup_cam.integer || powerup_cam_status == powerup_cam_inactive) {\r\n\t\tmemset(powerup_cam_active, 0, sizeof(powerup_cam_active));\r\n\t\treturn;\r\n\t}\r\n\r\n\tMVD_PowerupCam_GetCoords();\r\n\tpowerup_cam_active[view_number] = MVD_PowerupCam_Process(&powerup_cam_cvars[view_number]);\r\n}\r\n\r\nstatic void MVDAnnouncer_HelpListItems(void)\r\n{\r\n\tint i;\r\n\r\n\tCon_Printf(\"Valid types: \");\r\n\tfor (i = 0; i < sizeof(mvd_wp_info) / sizeof(mvd_wp_info[0]); ++i) {\r\n\t\tif (i) {\r\n\t\t\tCon_Printf(\",\");\r\n\t\t}\r\n\t\tCon_Printf(\"%s\", mvd_wp_info[i].name);\r\n\t}\r\n\tCon_Printf(\"\\n\");\r\n}\r\n\r\nstatic int MVDAnnouncer_FindEntity(const vec3_t pos, int type)\r\n{\r\n\tint j;\r\n\r\n\tfor (j = 0; j < CL_MAX_EDICTS; j++) {\r\n\t\tint modindex = cl_entities[j].baseline.modelindex;\r\n\t\tint skin = cl_entities[j].baseline.skinnum;\r\n\r\n\t\tif (modindex >= 0 && modindex < sizeof(cl.model_precache) / sizeof(cl.model_precache[0]) && cl.model_precache[modindex]) {\r\n\t\t\tif (cl.model_precache[modindex]->modhint == mvd_wp_info[type].model_hint && skin == mvd_wp_info[type].skin_number) {\r\n\t\t\t\tvec3_t distance;\r\n\t\t\t\tVectorSubtract(cl_entities[j].baseline.origin, pos, distance);\r\n\t\t\t\tif (VectorLength(distance) < 50) {\r\n\t\t\t\t\treturn j;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn 0;\r\n}\r\n\r\nstatic void MVDAnnouncer_RemoveItem(void)\r\n{\r\n\tvec3_t pos;\r\n\tconst char* type;\r\n\tint i;\r\n\tmvd_clock_t* clock_entry;\r\n\r\n\tif (Cmd_Argc() < 5) {\r\n\t\tCon_Printf(\"Removes a persistent item clock for item at given position\\n\");\r\n\t\tCon_Printf(\"Usage: %s <x> <y> <z> <type>\\n\", Cmd_Argv(0));\r\n\t\tMVDAnnouncer_HelpListItems();\r\n\t\treturn;\r\n\t}\r\n\r\n\tpos[0] = atof(Cmd_Argv(1));\r\n\tpos[1] = atof(Cmd_Argv(2));\r\n\tpos[2] = atof(Cmd_Argv(3));\r\n\r\n\ttype = Cmd_Argv(4);\r\n\r\n\tfor (i = 0; i < sizeof(mvd_wp_info) / sizeof(mvd_wp_info[0]); ++i) {\r\n\t\tif (!strcmp(type, mvd_wp_info[i].name)) {\r\n\t\t\tint ent = MVDAnnouncer_FindEntity(pos, i);\r\n\r\n\t\t\tif (ent) {\r\n\t\t\t\tfor (clock_entry = mvd_clocklist; clock_entry; clock_entry = clock_entry->next) {\r\n\t\t\t\t\tif ((clock_entry->flags & MVDCLOCK_PERSISTENT) && clock_entry->entity == ent) {\r\n\t\t\t\t\t\tclock_entry->flags |= MVDCLOCK_HIDDEN;\r\n\t\t\t\t\t\treturn;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Create it and mark it as hidden to stop it re-appearing\r\n\t\t\t\tMVD_ClockStartEntity(ent, mvd_wp_info[i].id, MVDCLOCK_PERSISTENT | MVDCLOCK_HIDDEN | MVDCLOCK_NEVERSPAWNED);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tCon_Printf(\"No item found at specified location\\n\");\r\n\t\t\treturn;\r\n\t\t}\r\n\t}\r\n\r\n\tCon_Printf(\"Invalid type specified\\n\");\r\n\tMVDAnnouncer_HelpListItems();\r\n}\r\n\r\nstatic void MVDAnnouncer_ListItems(void)\r\n{\r\n\tmvd_clock_t* clock_entry;\r\n\r\n\tfor (clock_entry = mvd_clocklist; clock_entry; clock_entry = clock_entry->next) {\r\n\t\tif ((clock_entry->flags & MVDCLOCK_PERSISTENT) && clock_entry->entity) {\r\n\t\t\tCon_Printf(\"mvd_name_item %d %d %d %s \\\"%s\\\" // entity %d\\n\",\r\n\t\t\t\t(int)cl_entities[clock_entry->entity].baseline.origin[0],\r\n\t\t\t\t(int)cl_entities[clock_entry->entity].baseline.origin[1],\r\n\t\t\t\t(int)cl_entities[clock_entry->entity].baseline.origin[2],\r\n\t\t\t\tmvd_wp_info[clock_entry->itemtype].name,\r\n\t\t\t\tclock_entry->location,\r\n\t\t\t\tclock_entry->entity\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n}\r\n\r\nstatic void MVDAnnouncer_NameItem(void)\r\n{\r\n\tvec3_t pos;\r\n\tconst char* type;\r\n\tconst char* label;\r\n\tint i;\r\n\r\n\tif (Cmd_Argc() < 6) {\r\n\t\tCon_Printf(\"Creates a persistent item clock for item at given position\\n\");\r\n\t\tCon_Printf(\"Usage: %s <x> <y> <z> <type> <label>\\n\", Cmd_Argv(0));\r\n\t\tMVDAnnouncer_HelpListItems();\r\n\t\treturn;\r\n\t}\r\n\r\n\tpos[0] = atof(Cmd_Argv(1));\r\n\tpos[1] = atof(Cmd_Argv(2));\r\n\tpos[2] = atof(Cmd_Argv(3));\r\n\r\n\ttype = Cmd_Argv(4);\r\n\tlabel = Cmd_Argv(5);\r\n\r\n\tfor (i = 0; i < sizeof(mvd_wp_info) / sizeof(mvd_wp_info[0]); ++i) {\r\n\t\tif (!strcmp(type, mvd_wp_info[i].name)) {\r\n\t\t\tint ent = MVDAnnouncer_FindEntity(pos, i);\r\n\t\t\tif (ent) {\r\n\t\t\t\tmvd_clock_t* existing = MVD_ClockFindEntity(ent);\r\n\t\t\t\tif (existing) {\r\n\t\t\t\t\tstrlcpy(existing->location, label, sizeof(existing->location));\r\n\t\t\t\t\t// We move to bottom of the list to allow the user to list every item and dictate order\r\n\t\t\t\t\texisting->order = ++fixed_ordering;\r\n\t\t\t\t\texisting->flags &= ~(MVDCLOCK_HIDDEN);\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tMVD_ClockStartEntity(ent, mvd_wp_info[i].id, MVDCLOCK_PERSISTENT | MVDCLOCK_NEVERSPAWNED);\r\n\t\t\t\t}\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tCon_Printf(\"No entity of type %s found near %d %d %d\\n\", type, (int)pos[0], (int)pos[1], (int)pos[2]);\r\n\t\t\treturn;\r\n\t\t}\r\n\t}\r\n\r\n\tCon_Printf(\"Invalid type specified '%s'\\n\", type);\r\n\tMVDAnnouncer_HelpListItems();\r\n}\r\n\r\nvoid MVD_Utils_Init(void)\r\n{\r\n\tint i;\r\n\r\n\tMVD_AutoTrack_Init();\r\n\tMVD_XMLStats_Init();\r\n\r\n\tCvar_SetCurrentGroup(CVAR_GROUP_MVD);\r\n\tCvar_Register(&mvd_info);\r\n\tCvar_Register(&mvd_info_show_header);\r\n\tCvar_Register(&mvd_info_setup);\r\n\tCvar_Register(&mvd_info_x);\r\n\tCvar_Register(&mvd_info_y);\r\n\r\n\tCvar_Register(&mvd_status);\r\n\tCvar_Register(&mvd_status_x);\r\n\tCvar_Register(&mvd_status_y);\r\n\r\n\tCvar_Register(&mvd_powerup_cam);\r\n\r\n\tCvar_Register(&mvd_pc_quad_1);\r\n\tCvar_Register(&mvd_pc_quad_2);\r\n\tCvar_Register(&mvd_pc_quad_3);\r\n\r\n\tCvar_Register(&mvd_pc_pent_1);\r\n\tCvar_Register(&mvd_pc_pent_2);\r\n\tCvar_Register(&mvd_pc_pent_3);\r\n\r\n\tfor (i = 0; i < sizeof(powerup_cam_cvars) / sizeof(powerup_cam_cvars[0]); ++i) {\r\n\t\tCvar_Register(&powerup_cam_cvars[i]);\r\n\t}\r\n\r\n\tCvar_Register(&mvd_moreinfo);\r\n\tCvar_Register(&mvd_autoadd_items);\r\n\tCvar_Register(&mvd_sortitems);\r\n\tCmd_AddCommand(\"mvd_name_item\", MVDAnnouncer_NameItem);\r\n\tCmd_AddCommand(\"mvd_remove_item\", MVDAnnouncer_RemoveItem);\r\n\tCmd_AddCommand(\"mvd_list_items\", MVDAnnouncer_ListItems);\r\n\r\n\tCvar_ResetCurrentGroup();\r\n}\r\n\r\nvoid MVD_Screen(void) {\r\n\tMVD_Info();\r\n\tMVD_Status();\r\n}\r\n\r\nvoid MVD_FlushUserCommands(void)\r\n{\r\n\tint i;\r\n\tfloat targettime = cls.demopackettime + cl.mvd_time_offset;\r\n\r\n\tfor (i = 1; i < sizeof(cl.mvd_user_cmd) / sizeof(cl.mvd_user_cmd[0]); ++i) {\r\n\t\tif (cl.mvd_user_cmd_time[i] && cl.mvd_user_cmd_time[i] <= targettime) {\r\n\t\t\tcl.mvd_user_cmd_time[0] = cl.mvd_user_cmd_time[i];\r\n\t\t\tcl.mvd_user_cmd[0] = cl.mvd_user_cmd[i];\r\n\t\t\tcl.mvd_user_cmd_time[i] = 0;\r\n\t\t\tcl.mvd_user_cmd[i] = 0;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nvoid MVD_ParseUserCommand(const char* s)\r\n{\r\n\tfloat time;\r\n\tint command;\r\n\tint i;\r\n\tint plr;\r\n\r\n\tif (Cam_TrackNum() == -1) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tCmd_TokenizeString((char*)s);\r\n\r\n\tif (Cmd_Argc() < 2) {\r\n\t\treturn;\r\n\t}\r\n\r\n\ttime = atof(Cmd_Argv(0));\r\n\tcommand = atoi(Cmd_Argv(1));\r\n\tplr = Cmd_Argc() >= 3 ? atoi(Cmd_Argv(2)) : 0;\r\n\r\n\tif (plr != 0 && cl.spec_track != plr - 1) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tif (!cl.mvd_time_offset) {\r\n\t\tcl.mvd_time_offset = time - cls.demopackettime;\r\n\t}\r\n\r\n\tMVD_FlushUserCommands();\r\n\tfor (i = 0; i < sizeof(cl.mvd_user_cmd) / sizeof(cl.mvd_user_cmd[0]); ++i) {\r\n\t\tif (cl.mvd_user_cmd_time[i] == 0) {\r\n\t\t\tcl.mvd_user_cmd_time[i] = time;\r\n\t\t\tcl.mvd_user_cmd[i] = command;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nmvd_new_info_t* MVD_StatsForPlayer(player_info_t* info)\r\n{\r\n\tint i;\r\n\tfor (i = 0; i < MAX_CLIENTS; ++i) {\r\n\t\tif (mvd_new_info[i].p_info == info) {\r\n\t\t\treturn &mvd_new_info[i];\r\n\t\t}\r\n\t}\r\n\treturn NULL;\r\n}\r\n\r\nvoid MVD_Initialise(void)\r\n{\r\n\tmemset(mvd_new_info, 0, sizeof(mvd_new_info));\r\n\tmemset(&mvd_cg_info, 0, sizeof(mvd_cg_info));\r\n\tcl.mvd_ktx_markers = false;\r\n}\r\n\r\nvoid MVD_GameStart(void)\r\n{\r\n\tint i;\r\n\r\n\tif (!cl.mvd_ktx_markers) {\r\n\t\tMVD_Initialise();\r\n\t}\r\n\r\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\r\n\t\tif (!cl.players[i].name[0] || cl.players[i].spectator == 1) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\tMVD_Init_Info(i);\r\n\t}\r\n}\r\n\r\nint MVD_ItemTypeForEntity(int ent)\r\n{\r\n\tint modindex = cl_entities[ent].baseline.modelindex;\r\n\tint skin = cl_entities[ent].baseline.skinnum;\r\n\r\n\tif (modindex >= 0 && modindex < sizeof(cl.model_precache) / sizeof(cl.model_precache[0]) && cl.model_precache[modindex]) {\r\n\t\tint j;\r\n\r\n\t\tfor (j = 0; j < sizeof(mvd_wp_info) / sizeof(mvd_wp_info[0]); ++j) {\r\n\t\t\tif (mvd_wp_info[j].add_clock == MVD_ADDCLOCK_ALWAYS || (mvd_wp_info[j].add_clock == MVD_ADDCLOCK_DMM1 && cl.deathmatch == 1)) {\r\n\t\t\t\tif (cl.model_precache[modindex]->modhint == mvd_wp_info[j].model_hint && skin == mvd_wp_info[j].skin_number) {\r\n\t\t\t\t\treturn mvd_wp_info[j].id;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn 0;\r\n}\r\n\r\n// ktx matchstart (no args)\r\nvoid MVDAnnouncer_MatchStart(void)\r\n{\r\n\tint i;\r\n\tint j;\r\n\r\n\tMVD_Stats_Cleanup();\r\n\tMVD_Init_Info(MAX_CLIENTS);\r\n\tMVD_GameStart();\r\n\tcl.mvd_ktx_markers = true;\r\n\r\n\t// Clocklist should start as persistent timers from entity baselines\r\n\tif (mvd_autoadd_items.integer) {\r\n\t\tfor (i = 0; i < CL_MAX_EDICTS; i++) {\r\n\t\t\tint modindex = cl_entities[i].baseline.modelindex;\r\n\t\t\tint skin = cl_entities[i].baseline.skinnum;\r\n\r\n\t\t\tif (modindex >= 0 && modindex < sizeof(cl.model_precache) / sizeof(cl.model_precache[0]) && cl.model_precache[modindex]) {\r\n\t\t\t\tfor (j = 0; j < sizeof(mvd_wp_info) / sizeof(mvd_wp_info[0]); ++j) {\r\n\t\t\t\t\tif (mvd_wp_info[j].add_clock == MVD_ADDCLOCK_ALWAYS || (mvd_wp_info[j].add_clock == MVD_ADDCLOCK_DMM1 && cl.deathmatch == 1)) {\r\n\t\t\t\t\t\tif (cl.model_precache[modindex]->modhint == mvd_wp_info[j].model_hint && skin == mvd_wp_info[j].skin_number) {\r\n\t\t\t\t\t\t\tMVD_ClockStartEntity(i, mvd_wp_info[j].id, MVDCLOCK_PERSISTENT | MVDCLOCK_NEVERSPAWNED);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tTP_ExecTrigger(\"f_demomatchstart\");\r\n\r\n\twas_standby = false;\r\n}\r\n\r\n// Signifies an item has been taken\r\n// ktx took <entity-num> <respawn-period> <plr-entnum>\r\nvoid MVDAnnouncer_ItemTaken(const char* s)\r\n{\r\n\tint entity, respawn, player_ent;\r\n\tmvd_clock_t* clock_entry;\r\n\r\n\tCmd_TokenizeString((char*)s);\r\n\r\n\tif (Cmd_Argc() < 5) {\r\n\t\tCom_DPrintf(\"//ktx took: expected 5 args, found %d\\n\", Cmd_Argc());\r\n\t\treturn;\r\n\t}\r\n\r\n\tcl.mvd_ktx_markers = true;\r\n\r\n\tentity = atoi(Cmd_Argv(2));\r\n\trespawn = atoi(Cmd_Argv(3));\r\n\tplayer_ent = atoi(Cmd_Argv(4));\r\n\r\n\tclock_entry = MVD_ClockFindEntity(entity);\r\n\tif (clock_entry) {\r\n\t\tclock_entry->last_taken = cls.demopackettime;\r\n\t\tclock_entry->old_clockval = clock_entry->clockval;\r\n\t\tif (respawn > 0) {\r\n\t\t\tclock_entry->clockval = cls.demopackettime + respawn;\r\n\t\t\tif (clock_entry->itemtype == QUAD_INFO || clock_entry->itemtype == PENT_INFO || clock_entry->itemtype == RING_INFO) {\r\n\t\t\t\tclock_entry->hold_clockval = cls.demopackettime + 30;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tclock_entry->hold_clockval = 0;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse {\r\n\t\t\t// Hold indefinitely\r\n\t\t\tclock_entry->clockval = -1;\r\n\t\t\tclock_entry->hold_clockval = HUGE_VAL;\r\n\t\t}\r\n\r\n\t\tif (player_ent >= 1 && player_ent <= MAX_CLIENTS) {\r\n\t\t\tclock_entry->last_taken_by = player_ent;\r\n\t\t}\r\n\t}\r\n\telse if (respawn) {\r\n\t\tint item = MVD_ItemTypeForEntity(entity);\r\n\r\n\t\tif (item) {\r\n\t\t\tMVD_ClockStart(item, cl_entities[entity].baseline.origin);\r\n\t\t\tmvd_new_info[player_ent - 1].mvdinfo.itemstats[item].mention = MENTION_PICKED_UP_ITEM;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// Used to manually start a countdown if it was indefinitely held in the past (e.g. mega-health worn off)\r\n// ktx timer <entity-num> <respawn-period>\r\nvoid MVDAnnouncer_StartTimer(const char* s)\r\n{\r\n\tint entity, respawn;\r\n\tmvd_clock_t* clock_entry;\r\n\r\n\tCmd_TokenizeString((char*)s);\r\n\r\n\tif (Cmd_Argc() < 4) {\r\n\t\tCom_DPrintf(\"//ktx timer: expected 4 args, found %d\\n\", Cmd_Argc());\r\n\t\treturn;\r\n\t}\r\n\r\n\tcl.mvd_ktx_markers = true;\r\n\tentity = atoi(Cmd_Argv(2));\r\n\trespawn = atoi(Cmd_Argv(3));\r\n\r\n\tclock_entry = MVD_ClockFindEntity(entity);\r\n\tif (clock_entry) {\r\n\t\tclock_entry->old_clockval = clock_entry->clockval;\r\n\t\tclock_entry->clockval = cls.demopackettime + respawn;\r\n\t\tclock_entry->last_taken_by = 0;\r\n\t\tclock_entry->last_taken = 0;\r\n\t}\r\n\telse {\r\n\t\tint item = MVD_ItemTypeForEntity(entity);\r\n\r\n\t\tif (item) {\r\n\t\t\tMVD_ClockStart(item, cl_entities[entity].baseline.origin);\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// ktx drop <entity-num> <weapon-it> <plr-entnum>\r\nvoid MVDAnnouncer_PackDropped(const char* s)\r\n{\r\n\tint entity, weapon, player_ent, i;\r\n\tmvd_clock_t* clock_entry;\r\n\r\n\tCmd_TokenizeString((char*)s);\r\n\r\n\tif (Cmd_Argc() < 5) {\r\n\t\tCom_DPrintf(\"//ktx drop: expected 5 args, found %d\\n\", Cmd_Argc());\r\n\t\treturn;\r\n\t}\r\n\r\n\tcl.mvd_ktx_markers = true;\r\n\tentity = atoi(Cmd_Argv(2));\r\n\tweapon = atoi(Cmd_Argv(3));\r\n\tplayer_ent = atoi(Cmd_Argv(4));\r\n\r\n\tif (entity >= 0 && entity < sizeof(cl_entities) / sizeof(cl_entities[0])) {\r\n\t\tcl_entities[entity].contents = weapon;\r\n\r\n\t\tclock_entry = MVD_ClockFindEntity(entity);\r\n\t\tif (clock_entry) {\r\n\t\t\tMVD_ClockList_Remove(clock_entry);\r\n\t\t}\r\n\r\n\t\tfor (i = 0; i < sizeof(mvd_wp_info) / sizeof(mvd_wp_info[0]); ++i) {\r\n\t\t\tif (mvd_wp_info[i].it == weapon) {\r\n\t\t\t\tclock_entry = MVD_ClockStartEntity(entity, i, MVDCLOCK_BACKPACK);\r\n\t\t\t\tclock_entry->clockval = cls.demopackettime + 120;\r\n\t\t\t\tclock_entry->dropped_by = player_ent;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// ktx expire <entity-num>\r\nvoid MVDAnnouncer_Expired(const char* s)\r\n{\r\n\tint entity;\r\n\tmvd_clock_t* clock_entry;\r\n\r\n\tCmd_TokenizeString((char*)s);\r\n\r\n\tif (Cmd_Argc() < 3) {\r\n\t\tCom_DPrintf(\"//ktx expire: expected 3 args, found %d\\n\", Cmd_Argc());\r\n\t\treturn;\r\n\t}\r\n\r\n\tcl.mvd_ktx_markers = true;\r\n\tentity = atoi(Cmd_Argv(2));\r\n\tif (entity >= 0 && entity < sizeof(cl_entities) / sizeof(cl_entities[0])) {\r\n\t\tcl_entities[entity].contents = 0;\r\n\r\n\t\tclock_entry = MVD_ClockFindEntity(entity);\r\n\t\tif (clock_entry) {\r\n\t\t\tMVD_ClockList_Remove(clock_entry);\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// ktx bp <entnum> <plr-entnum>\r\nvoid MVDAnnouncer_BackpackPickup(const char* s)\r\n{\r\n\tint entity, player_ent;\r\n\tmvd_clock_t* clock_entry;\r\n\r\n\tCmd_TokenizeString((char*)s);\r\n\tif (Cmd_Argc() < 4) {\r\n\t\tCom_DPrintf(\"//ktx bp: expected 4 args, found %d\\n\", Cmd_Argc());\r\n\t\treturn;\r\n\t}\r\n\r\n\tcl.mvd_ktx_markers = true;\r\n\tentity = atoi(Cmd_Argv(2));\r\n\tplayer_ent = atoi(Cmd_Argv(3));\r\n\r\n\tclock_entry = MVD_ClockFindEntity(entity);\r\n\tif (clock_entry) {\r\n\t\tclock_entry->last_taken_by = player_ent;\r\n\t\tclock_entry->clockval = cls.demopackettime + ITEMSCLOCK_TAKEN_PAUSE;\r\n\t\tclock_entry->last_taken = cls.demopackettime;\r\n\t}\r\n\r\n\tif (entity >= 0 && entity < sizeof(cl_entities) / sizeof(cl_entities[0])) {\r\n\t\tcl_entities[entity].contents = 0;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/mvd_utils.h",
    "content": "/*\nCopyright (C) 2001-2002 jogihoogi\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: mvd_utils.h,v 1.5 2007-09-24 21:41:17 johnnycz Exp $\n*/\n\n#ifndef MVDUTILS_HEADER\n#define MVDUTILS_HEADER\n\n// main header for a group of MVD tools: mvd_utils, mvd_xmlstats, mvd_autotrack\nvoid MVD_Screen (void);\n\n// initialize the module, add variables and commands\nvoid MVD_Utils_Init(void); \nvoid MVD_Mainhook(void);\nvoid MVD_Stats_Cleanup(void);\nvoid MVD_ClockList_TopItems_Draw(double time_limit, int style, int x, int y, float scale, int filter, qbool backpacks, qbool proportional);\nvoid MVD_ClockList_TopItems_DimensionsGet(double time_limit, int style, int *width, int *height, float scale, qbool backpacks, qbool proportional);\n\n// update match info structures\nvoid MVD_Init_Info(int player_slot);\nvoid MVD_GameStart(void);\nvoid MVD_Initialise(void);\n\n// //ktx event notifications\nvoid MVDAnnouncer_MatchStart(void);\nvoid MVDAnnouncer_ItemTaken(const char* s);\nvoid MVDAnnouncer_StartTimer(const char* s);\nvoid MVDAnnouncer_PackDropped(const char* s);\nvoid MVDAnnouncer_Expired(const char* s);\nvoid MVDAnnouncer_BackpackPickup(const char* s);\nvoid CL_ReadKtxDamageIndicatorString(const char* s);\n\n// Powerup cams\nqbool MVD_PowerupCam_Enabled(void);\nint MVD_PowerupCams_Enabled(void);\nvoid MVD_PowerupCam_Frame(void);\n\n// JSON stats embedded in mvd\ntypedef struct {\n\tint frags;\n\tint deaths;\n\tint teamkills;\n\tint spawnfrags;\n\tint kills;\n\tint suicides;\n} mvd_player_basic_stats;\n\ntypedef struct {\n\tint taken;\n\tint given;\n\tint team;\n\tint self;\n\tint team_weapons;\n\tint enemy_weapons;\n\tint taken_to_die;\n} mvd_player_dmg_stats;\n\ntypedef struct {\n\tint max;\n\tint quad;\n} mvd_player_spree_stats;\n\ntypedef struct {\n\tqbool present;\n\tfloat max;\n\tfloat avg;\n} mvd_player_speed_stats;\n\ntypedef struct {\n\tint attacks;\n\tint hits;\n\tint real;\n\tint virtual;\n} mvd_player_weapon_accuracy_stats;\n\ntypedef struct {\n\tint total;\n\tint team;\n\tint enemy;\n\tint self;\n} mvd_player_weapon_kill_stats;\n\ntypedef struct {\n\tint enemy;\n\tint team;\n} mvd_player_weapon_dmg_stats;\n\ntypedef struct {\n\tchar name[16];\n\tmvd_player_weapon_accuracy_stats accuracy;\n\tmvd_player_weapon_kill_stats kills;\n\tint deaths;\n\tint pickups;\n\tint dropped;\n\tint taken;\n\tint total_taken;\n\tint spawn_taken;\n\tint spawn_total_taken;\n\tmvd_player_weapon_dmg_stats damage;\n} mvd_player_weapon_stats;\n\ntypedef struct {\n\tchar name[64];\n\tint taken;\n\tint time;\n} mvd_player_item_stats;\n\ntypedef struct {\n\tqbool present;\n\tint points;\n\tint flag_caps;\n\tint defends;\n\tint carrier_defends;\n\tint carrier_frags;\n\tint pickups;\n\tint returns;\n\tint runes[4];\n} mvd_player_ctf_stats;\n\ntypedef struct {\n\tint coilgun;\n\tint axe;\n\tint stomp;\n\tint multi;\n\tint air;\n\tint best_multi;\n} mvd_player_instagib_gib_stats;\n\ntypedef struct {\n\tqbool present;\n\tint avg_height;\n\tint max_height;\n\tint rings;\n\tmvd_player_instagib_gib_stats gibs;\n} mvd_player_instagib_stats;\n\ntypedef struct {\n\tint bronze;\n\tint silver;\n\tint gold;\n\tint platinum;\n} mvd_player_midair_type_stats;\n\ntypedef struct {\n\tfloat total;\n\tfloat max;\n\tfloat avg;\n} mvd_player_midair_height_stats;\n\ntypedef struct {\n\tqbool present;\n\tint stomps;\n\tmvd_player_midair_type_stats midairs;\n\tint total;\n\tint bonus;\n\tmvd_player_midair_height_stats heights;\n} mvd_player_midair_stats;\n\ntypedef struct {\n\tqbool present;\n\tint wins;\n\tint losses;\n} mvd_player_rocket_arena_stats;\n\n#define HOONYMODE_MAXROUNDS 64\n\ntypedef struct {\n\tqbool present;\n\tint classic_roundcount;\n\tchar classic_rounds[HOONYMODE_MAXROUNDS];\n\tint blitz_roundcount;\n\tint blitz_roundfrags[HOONYMODE_MAXROUNDS];\n} mvd_player_hoonymode_stats;\n\n#define LGCMODE_DISTANCE_BUCKETS 32\n\ntypedef struct {\n\tqbool present;\n\tint under;\n\tint over;\n\tint hit_buckets;\n\tint hits[LGCMODE_DISTANCE_BUCKETS];\n\tint misses[LGCMODE_DISTANCE_BUCKETS];\n} mvd_player_lgc_stats;\n\ntypedef struct {\n\tqbool present;\n\tint skill;\n\tqbool customised;\n} mvd_player_bot_stats;\n\ntypedef struct {\n\tint topcolor;\n\tint bottomcolor;\n\tint ping;\n\tchar login[64];\n\tchar team[64];\n\tmvd_player_basic_stats stats;\n\tmvd_player_dmg_stats dmg;\n\tint transferred_packs;\n\tmvd_player_spree_stats spree;\n\tfloat control;\n\tmvd_player_speed_stats speed;\n\tint handicap;\n\tmvd_player_weapon_stats weapons[16];\n\tmvd_player_item_stats items[32];\n\tmvd_player_ctf_stats ctf;\n\tmvd_player_instagib_stats instagib;\n\tmvd_player_midair_stats midair;\n\tmvd_player_rocket_arena_stats rocket_arena;\n\tmvd_player_hoonymode_stats hoonymode;\n\tmvd_player_lgc_stats lgc;\n\tmvd_player_bot_stats bot;\n} mvd_player_stats;\n\ntypedef struct {\n\tint version;\n\ttime_t date;\n\tchar map[32];\n\tchar hostname[32];\n\tchar ip[64];\n\tint port;\n\tchar matchtag[64];\n\tchar mode[16];\n\tint timelimit;\n\tint fraglimit;\n\tint dmm;\n\tint teamplay;\n\tint duration;\n\tchar demoname[128];\n\n\tmvd_player_stats players;\n} mvd_game_stats;\n\n#endif // MVDUTILS_HEADER\n"
  },
  {
    "path": "src/mvd_utils_common.h",
    "content": "/*\nCopyright (C) 2001-2002 jogihoogi\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: mvd_utils_common.h,v 1.2 2007-09-24 21:34:51 johnnycz Exp $\n*/\n\n// Shared declarations for the whole mvd group of tools\n// declarations from mvd_utils.c and mvd_autotrack.c\n\n#define MH_INFO\t\t14\n#define RA_INFO\t\t13\n#define YA_INFO\t\t12\n#define GA_INFO\t\t11\n#define PENT_INFO\t10\n#define QUAD_INFO\t9\n#define RING_INFO\t8\n#define LG_INFO\t\t7\n#define RL_INFO\t\t6\n#define GL_INFO\t\t5\n#define SNG_INFO\t4\n#define NG_INFO\t\t3\n#define SSG_INFO\t2\n#define SG_INFO\t\t1\n#define AXE_INFO\t0\n#define mvd_info_types 15\n\n#define IS_WEAPON(x) ((x) >= SSG_INFO && (x) <= LG_INFO)\n#define MAX_MEGAS_PER_PLAYER 4\n\n#define AMMO_TYPES 4\n\n// killstats structures\ntypedef struct mvd_ks_w_s {\n\tint kills;\n\tint teamkills;\n\tint lastkills;\n} mvd_ks_w_t;\n\ntypedef struct mvd_ks_s {\n\tmvd_ks_w_t normal[8];\t// weapon kills axe - lg\n\tmvd_ks_w_t q[7];\n\tmvd_ks_w_t p[7];\n\tmvd_ks_w_t r[7];\n\tmvd_ks_w_t qp[7];\n\tmvd_ks_w_t qr[7];\n\tmvd_ks_w_t pr[7];\n\tmvd_ks_w_t qpr[7];\n} mvd_ks_t;\n\ntypedef struct  mvd_ds_s {\n\tint isdead;\n\tint deathcount;\n\tdouble alivetimestart;\n\tdouble alivetime;\n\n} mvd_ds_t ;\n\n// keeps runstats from spawn to death\ntypedef struct mvd_runs_s {\n\tdouble time;\n\tint frags;\n\tint teamfrags;\n\tdouble starttime;\n\n} mvd_runs_t ;\n\n// average run statistics\ntypedef struct mvd_avgruns_s {\n\tdouble avg_time;\n\tdouble avg_frags;\n\tdouble avg_teamfrags;\n} mvd_avgruns_t;\n\n// average run statistics\ntypedef struct mvd_avgruns_all_s {\n\tmvd_avgruns_t all;\n\t// following 3 are not implemented yet\n\tmvd_avgruns_t quad;\n\tmvd_avgruns_t pent;\n\tmvd_avgruns_t ring;\n} mvd_avgruns_all_t;\n\n// item stuff\ntypedef struct mvd_pw_s {\n\tint has;\n\tdouble starttime;\n\tdouble ctime;\n\tint count ;\n\tint lost ;\n\tint mention;\n\tmvd_runs_t runs[512];\n\tint run;\n} mvd_pw_t;\n\ntypedef struct mvd_info_s {\n    float value;\n    int lfw;\t\t\t\t//last fired weapon\n\tmvd_ds_t das;\t\t\t//dead alive stats\n\tmvd_pw_t itemstats[mvd_info_types];\t\t//item stats\n\tunsigned short ammostats[AMMO_TYPES];\n\tmvd_runs_t runs[512];\t//\n\tmvd_avgruns_all_t run_stats;\t// calculated from other items\n\tmvd_ks_t killstats;\t\t// killstats\n\tint spawntelefrags;\n\tint teamspawntelefrags;\n\tint teamfrags;\t\t\t// frags are in cl.players structure\n\tint lastfrags;\n\tint run;\n\tint firstrun;\n\tqbool initialized;\n} mvd_info_t;\n\ntypedef struct items_s {\n\tint took;\n\tint lost;\n} items_t;\n\ntypedef struct mvd_event_s {\n\tint\t\t\ttype;\t\t\t// what happened ?\t0-spawn,1-death,2-kill,3-teamkill,4-took,5-powerup_end\n\tdouble\t\ttime;\t\t// when did it happen ?\n\tvec3_t\t\tloc;\t\t// where did it happen ?\n\tint\t\t\tinfo;\t\t// for item on tooks\n\t\t\t\t\t\t\t// lfw on deaths\n\t\t\t\t\t\t\t// lfw on kills 0-7 axe-lg,-1 spawn\n\t\t\t\t\t\t\t//\n\tint\t\t\tk_id;\t\t// userid of the guy we killed\n} mvd_event_t;\n\ntypedef struct mvd_new_info_s {\n\tint id;\t\t\t\t\t\t\t//id cl.players[id]\n\tfloat value;\t\t\t\t\t\t//mvd_info/mvd_autotrack value\n\n\tmvd_event_t\t\t*event;\n\tplayer_state_t\t*p_state ;\n\tplayer_info_t\t*p_info;\n\tint lwf;\t\t\t\t\t\t// last weapon fired\n\tmvd_info_t mvdinfo;\n\tvec3_t mega_locations[MAX_MEGAS_PER_PLAYER];\n} mvd_new_info_t;// mvd_new_info;\n\nextern mvd_new_info_t mvd_new_info[MAX_CLIENTS];\n\ntypedef struct mvd_cg_info_s {\n\tchar mapname[1024];\n\tchar team1[1024];\n\tchar team2[1024];\n\tchar hostname[1024];\n\tint gametype;\n\tint timelimit;\n\tint pcount;\n\tint deathmatch;\n} mvd_cg_info_s;\n\nextern mvd_cg_info_s mvd_cg_info;\n\ntypedef struct mvd_gt_info_s {\n\tint id;\n\tchar *name;\n} mvd_gt_info_t;\n\n#define gt_unknown\t3\n#define gt_4on4\t3\n#define gt_3on3\t2\n#define gt_2on2\t1\n#define gt_1on1\t0\n#define mvd_gt_types 5\n\nextern mvd_gt_info_t mvd_gt_info[mvd_gt_types];\n\n#define MVD_ADDCLOCK_NEVER  0\n#define MVD_ADDCLOCK_ALWAYS 1\n#define MVD_ADDCLOCK_DMM1   2\n\ntypedef struct mvd_wp_info_s {\n\tint           id;\n\tchar*         name;\n\tint           it;\n\tconst char*   colored_name;\n\tint           model_hint;\n\tint           skin_number;\n\tbyte          Rcolor;\n\tbyte          Gcolor;\n\tbyte          Bcolor;\n\tconst cvar_t* name_cvar;\n\tconst char*   color_string;\n\tconst char*   colored_packname;\n\tint           add_clock;\n\tqbool         show_held;\n} mvd_wp_info_t;\n\nextern mvd_wp_info_t mvd_wp_info[mvd_info_types];\n\n// mvd_autotrack:\nvoid MVD_AutoTrack(void);\nvoid MVD_AutoTrack_Init(void);\n\n// mvd_xmlstats:\nvoid MVD_XMLStats_Init(void);\n\nmvd_new_info_t* MVD_StatsForPlayer(player_info_t* info);\n"
  },
  {
    "path": "src/mvd_xmlstats.c",
    "content": "/*\nCopyright (C) 2001-2002 jogihoogi\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: mvd_xmlstats.c,v 1.1 2007-09-24 21:34:52 johnnycz Exp $\n*/\n\n// MultiView Demo Stats Export System\n// after a match is over, user can export stats in xml format to a file\n\n#include \"quakedef.h\"\n#include \"mvd_utils_common.h\"\n\nstatic char *mvd_name_to_xml(char *s){\n\tstatic char buf[1024];\n\tchar *p;\n\tint i;\n\tbuf[0]=0;\n\tfor(p=s;*p;p++){\n\t\ti=(int)*p;\n\t\tif(i<0)\n\t\t\ti+=256;\n\t\tif (strlen(buf)>0 ){\n\t\t\tsnprintf(buf, sizeof (buf), \"%s\",va(\"%s\t\t\t<char>%i</char>\\n\",buf,i));\n\t\t}else\n\t\t\tsnprintf(buf, sizeof (buf), \"%s\",va(\"\\n\t\t\t<char>%i</char>\\n\",i));\n\t}\n\treturn buf;\n\n}\n\nstatic void mvd_s_h (FILE *f){\n\tfprintf(f,\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n\");\n\tfprintf(f,\"<?xml-stylesheet type=\\\"text/xsl\\\" href=\\\"mvdstats.xsl\\\"?>\\n\");\n\tfprintf(f,\"<mvdstats>\\n\");\n\tfprintf(f,\"\\n\");\n\tfprintf(f,\"\\n\");\n\tfprintf(f,\"<demoinfos>\\n\");\n\tfprintf(f,\"\t\t<map>%s</map>\\n\",mvd_cg_info.mapname);\n\tfprintf(f,\"\t\t<gametype>%s</gametype>\\n\",mvd_gt_info[mvd_cg_info.gametype].name);\n\tfprintf(f,\"\t\t<hostname>%s</hostname>\\n\",mvd_cg_info.hostname);\n\tif (mvd_cg_info.gametype != 0 && mvd_cg_info.gametype != 4){\n\t\tfprintf(f,\"\t\t<team1>%s</team1>\\n\",mvd_name_to_xml(mvd_cg_info.team1));\n\t\tfprintf(f,\"\t\t<team2>%s</team2>\\n\",mvd_name_to_xml(mvd_cg_info.team2));\n\t}\n\tfprintf(f,\"\t\t<timelimit>%i</timelimit>\\n\",mvd_cg_info.timelimit);\n\tfprintf(f,\"</demoinfos>\\n\");\n}\n\nstatic void mvd_s_e (FILE *f){\n\tfprintf(f,\"</mvdstats>\\n\");\n\n}\n\nstatic void mvd_s_p (FILE *f,int i,int k){\n\tint x,y,z;\n\n\t\tfprintf(f,\"\t<player id=\\\"%i\\\">\\n\",k);\n\t\tfprintf(f,\"\t\t<nick>%s\t\t</nick>\\n\",mvd_name_to_xml(mvd_new_info[i].p_info->name));\n\t\tif (mvd_cg_info.gametype != 0 && mvd_cg_info.gametype != 4)\n\t\t\tfprintf(f,\"\t\t<team>%s</team>\\n\",mvd_name_to_xml(mvd_new_info[i].p_info->team));\n\t\tfprintf(f,\"\t\t<kills>\\n\");\n\t\tfor (z=0,x=AXE_INFO;x<=LG_INFO;x++){\n\t\t\tfprintf(f,\"\t\t\t<%s>%i</%s>\\n\",mvd_wp_info[x].name,mvd_new_info[i].mvdinfo.killstats.normal[x].kills,mvd_wp_info[x].name);\n\t\t\tz+=mvd_new_info[i].mvdinfo.killstats.normal[x].kills;\n\t\t}\n\t\tfprintf(f,\"\t\t\t<spawn>%i</spawn>\\n\",mvd_new_info[i].mvdinfo.spawntelefrags);\n\t\tz+=mvd_new_info[i].mvdinfo.spawntelefrags;\n\t\t\tfprintf(f,\"\t\t\t<all>%i</all>\\n\",z);\n\t\tfprintf(f,\"\t\t</kills>\\n\");\n\t\tfprintf(f,\"\t\t<teamkills>\\n\");\n\t\tfor (z=0,x=AXE_INFO;x<=LG_INFO;x++){\n\t\t\tfprintf(f,\"\t\t\t<%s>%i</%s>\\n\",mvd_wp_info[x].name,mvd_new_info[i].mvdinfo.killstats.normal[x].teamkills,mvd_wp_info[x].name);\n\t\t\tz+=mvd_new_info[i].mvdinfo.killstats.normal[x].teamkills;\n\t\t}\n\t\tfprintf(f,\"\t\t\t<spawn>%i</spawn>\\n\",mvd_new_info[i].mvdinfo.teamspawntelefrags);\n\t\tz+=mvd_new_info[i].mvdinfo.teamspawntelefrags;\n\t\t\tfprintf(f,\"\t\t\t<all>%i</all>\\n\",z);\n\n\t\tfprintf(f,\"\t\t</teamkills>\\n\");\n\t\tfprintf(f,\"\t\t<deaths>%i</deaths>\\n\",mvd_new_info[i].mvdinfo.das.deathcount);\n\t\tfprintf(f,\"\t\t<took>\\n\");\n\t\tfor (x=SSG_INFO;x<=MH_INFO;x++){\n\t\t\tfprintf(f,\"\t\t\t<%s>%i</%s>\\n\",mvd_wp_info[x].name,mvd_new_info[i].mvdinfo.itemstats[x].count,mvd_wp_info[x].name);\n\t\t}\n\t\tfprintf(f,\"\t\t</took>\\n\");\n\t\tfprintf(f,\"\t\t<lost>\\n\");\n\t\tfor (x=SSG_INFO;x<=MH_INFO;x++){\n\t\t\tfprintf(f,\"\t\t\t<%s>%i</%s>\\n\",mvd_wp_info[x].name,mvd_new_info[i].mvdinfo.itemstats[x].lost,mvd_wp_info[x].name);\n\t\t}\n\t\tfprintf(f,\"\t\t</lost>\\n\");\n\n\t\tfprintf(f,\"\t<runs>\\n\");\n\t\tfor(x=0;x<mvd_new_info[i].mvdinfo.run;x++){\n\t\t\tfprintf(f,\"\t\t<run id=\\\"%i\\\">\\n\",x);\n\t\t\tfprintf(f,\"\t\t\t<time>%9.3f</time>\\n\",mvd_new_info[i].mvdinfo.runs[x].time);\n\t\t\tfprintf(f,\"\t\t\t<frags>%i</frags>\\n\",mvd_new_info[i].mvdinfo.runs[x].frags);\n\t\t\tfprintf(f,\"\t\t\t<teamfrags>%i</teamfrags>\\n\",mvd_new_info[i].mvdinfo.runs[x].teamfrags);\n\t\t\tfprintf(f,\"\t\t</run>\\n\");\n\t\t}\n\t\tfprintf(f,\"\t</runs>\\n\");\n\n\t\tfor(y=RING_INFO;y<=PENT_INFO ;y++){\n\t\t\tif (mvd_new_info[i].mvdinfo.itemstats[y].run == 0)\n\t\t\t\tcontinue;\n\t\t\tfprintf(f,\"\t<%s_runs>\\n\",mvd_wp_info[y].name);\n\n\t\t\tfor(x=0;x<mvd_new_info[i].mvdinfo.itemstats[y].run;x++){\n\t\t\tfprintf(f,\"\t\t<run id=\\\"%i\\\">\\n\",x);\n\t\t\tfprintf(f,\"\t\t\t<time>%9.3f</time>\\n\",mvd_new_info[i].mvdinfo.itemstats[y].runs[x].time);\n\t\t\tfprintf(f,\"\t\t\t<frags>%i</frags>\\n\",mvd_new_info[i].mvdinfo.itemstats[y].runs[x].frags);\n\t\t\tfprintf(f,\"\t\t\t<teamfrags>%i</teamfrags>\\n\",mvd_new_info[i].mvdinfo.itemstats[y].runs[x].teamfrags);;\n\t\t\tfprintf(f,\"\t\t</run>\\n\");\n\t\t\t}\n\t\t\tfprintf(f,\"\t</%s_runs>\\n\",mvd_wp_info[y].name);\n\t\t}\n\n\t\tfprintf(f,\"\t</player>\\n\\n\");\n}\n\nstatic void mvd_s_t (FILE *f){\n\tint i,z;\n\tint static count;\n\n\tif (mvd_cg_info.gametype == 0 || mvd_cg_info.gametype == 4)\n\t\treturn;\n\tfprintf(f,\"<Teamstats>\\n\");\n\n\tfprintf(f,\"\t\t<team1>\\n\");\n\tfprintf(f,\"\t\t\t<name>%s</name>\\n\",mvd_name_to_xml(mvd_cg_info.team1));\n\tfprintf(f,\"\t\t\t<players>\\n\");\n\tfor (i = 0,z=0; i < mvd_cg_info.pcount; i++) {\n\t\tif (strcmp(mvd_new_info[i].p_info->team,mvd_cg_info.team1))\n\t\t\tcontinue;\n\t\tmvd_s_p(f,i,z);\n\t\tz++;\n\t}\n\n\tfprintf(f,\"\t\t\t</players>\\n\");\n\n\tfprintf(f,\"\t\t<took>\\n\");\n\tfor(z=SSG_INFO;z<=MH_INFO;z++,count=0){\n\t\tfor (i = 0; i < mvd_cg_info.gametype; i++) {\n\t\t\tif (strcmp(mvd_new_info[i].p_info->team,mvd_cg_info.team1))\n\t\t\t\tcontinue;\n\t\t\tcount+=mvd_new_info[i].mvdinfo.itemstats[z].count;\n\n\t\t}\n\t\tfprintf(f,\"\t\t\t<%s>%i</%s>\\n\",mvd_wp_info[z].name,count,mvd_wp_info[z].name);\n\t}\n\tfprintf(f,\"\t\t</took>\\n\");\n\n\tfprintf(f,\"\t\t<lost>\\n\");\n\tfor(z=SSG_INFO;z<=QUAD_INFO;z++,count=0){\n\t\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\n\t\t\tif (!strcmp(mvd_new_info[i].p_info->team,mvd_cg_info.team1))\n\t\t\t\tcontinue;\n\t\t\tcount+=mvd_new_info[i].mvdinfo.itemstats[z].lost;\n\n\t\t}\n\t\tfprintf(f,\"\t\t\t<%s>%i</%s>\\n\",mvd_wp_info[z].name,count,mvd_wp_info[z].name);\n\t}\n\n\tfprintf(f,\"\t\t</lost>\\n\");\n\n\tfprintf(f,\"\t\t<kills>\\n\");\n\tfor(z=AXE_INFO;z<=LG_INFO;z++,count=0){\n\t\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\n\t\t\tif (strcmp(mvd_new_info[i].p_info->team,mvd_cg_info.team1))\n\t\t\t\tcontinue;\n\t\t\tcount+=mvd_new_info[i].mvdinfo.killstats.normal[z].kills;\n\n\t\t}\n\t\tfprintf(f,\"\t\t\t<%s>%i</%s>\\n\",mvd_wp_info[z].name,count,mvd_wp_info[z].name);\n\t\tcount=0;\n\t}\n\n\tfprintf(f,\"\t\t</kills>\\n\");\n\n\tfprintf(f,\"\t\t</team1>\\n\");\n\n\tfprintf(f,\"\t\t<team2>\\n\");\n\t\tfprintf(f,\"\t\t\t<name>%s</name>\\n\",mvd_name_to_xml(mvd_cg_info.team2));\n\t\tfprintf(f,\"\t\t\t<players>\\n\");\n\t\tfor (i = 0,z=0; i < mvd_cg_info.pcount; i++) {\n\t\t\tif (strcmp(mvd_new_info[i].p_info->team,mvd_cg_info.team2))\n\t\t\t\tcontinue;\n\t\t\tmvd_s_p(f,i,z);\n\t\t\tz++;\n\t\t}\n\t\tfprintf(f,\"\t\t\t</players>\\n\");\n\n\t\tfprintf(f,\"\t\t<took>\\n\");\n\t\tfor(z=SSG_INFO;z<=MH_INFO;z++,count=0){\n\t\t\tfor (i = 0; i < mvd_cg_info.gametype; i++) {\n\t\t\t\tif (strcmp(mvd_new_info[i].p_info->team,mvd_cg_info.team2))\n\t\t\t\t\tcontinue;\n\t\t\t\tcount+=mvd_new_info[i].mvdinfo.itemstats[z].count;\n\n\t\t\t}\n\t\t\tfprintf(f,\"\t\t\t<%s>%i</%s>\\n\",mvd_wp_info[z].name,count,mvd_wp_info[z].name);\n\n\t\t}\n\t\tfprintf(f,\"\t\t</took>\\n\");\n\n\t\tfprintf(f,\"\t\t<lost>\\n\");\n\t\tfor(z=SSG_INFO;z<=QUAD_INFO;z++,count=0){\n\t\t\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\n\t\t\t\tif (strcmp(mvd_new_info[i].p_info->team,mvd_cg_info.team2))\n\t\t\t\t\tcontinue;\n\t\t\t\tcount+=mvd_new_info[i].mvdinfo.itemstats[z].lost;\n\n\t\t\t}\n\t\t\tfprintf(f,\"\t\t\t<%s>%i</%s>\\n\",mvd_wp_info[z].name,count,mvd_wp_info[z].name);\n\t\t}\n\n\t\tfprintf(f,\"\t\t</lost>\\n\");\n\n\t\tfprintf(f,\"\t\t<kills>\\n\");\n\t\tfor(z=AXE_INFO;z<=LG_INFO;z++,count=0){\n\t\t\tfor (i = 0; i < mvd_cg_info.pcount; i++) {\n\t\t\t\tif (strcmp(mvd_new_info[i].p_info->team,mvd_cg_info.team2))\n\t\t\t\t\tcontinue;\n\t\t\t\tcount+=mvd_new_info[i].mvdinfo.killstats.normal[z].kills;\n\n\t\t\t}\n\t\t\tfprintf(f,\"\t\t\t<%s>%i</%s>\\n\",mvd_wp_info[z].name,count,mvd_wp_info[z].name);\n\n\t\t}\n\n\t\tfprintf(f,\"\t\t</kills>\\n\");\n\n\t\tfprintf(f,\"\t\t</team2>\\n\");\n\n\tfprintf(f,\"</Teamstats>\\n\");\n}\n\nstatic void MVD_Status_Xml (void){\n\tFILE *f;\n\tint i;\n\tchar* filename;\n\n\t// todo: add match name from match tools\n\n\tfilename = \"stats.xml\";\n\n\tf=fopen(filename,\"wb\");\n\tif (!f) {\n\t\tCom_Printf(\"Can't open %s\\n\", filename);\n\t\treturn;\n\t}\n\tCom_Printf(\"Dumping XML stats to %s\\n\",filename);\n\n\tmvd_s_h(f);\n\tif (mvd_cg_info.gametype!=0 && mvd_cg_info.gametype!=4 ){\n\t\tmvd_s_t(f);\n\t} else {\n\t\tfor (i=0;i<mvd_cg_info.pcount;i++)\n\t\t\tmvd_s_p(f,i,i);\n\t}\n\tmvd_s_e(f);\n\tfclose(f);\n}\n\nvoid MVD_XMLStats_Init(void)\n{\n\tCmd_AddCommand (\"mvd_dumpstats\",MVD_Status_Xml);\n}\n"
  },
  {
    "path": "src/net.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\n*/\n// net.c\n\n#ifdef SERVERONLY\n#include \"qwsvdef.h\"\n#else\n#include \"quakedef.h\"\n#include \"server.h\"\n#include \"utils.h\"\n#define MAX_STRINGS 16 // well, this used not only for va, anyway, static buffers is evil...\n#endif\n\n#ifdef _WIN32\nWSADATA winsockdata;\n#define EZ_TCP_WOULDBLOCK (WSAEWOULDBLOCK)\n#else\n#define EZ_TCP_WOULDBLOCK (EINPROGRESS)\n#endif\n\n\n#ifndef SERVERONLY\nnetadr_t\tnet_local_cl_ipadr;\n\nvoid NET_CloseClient (void);\n\nstatic void cl_net_clientport_changed(cvar_t* var, char* value, qbool* cancel);\nstatic cvar_t cl_net_clientport = { \"cl_net_clientport\", \"27001\", CVAR_AUTO, cl_net_clientport_changed };  // Was PORT_CLIENT in protocol.h\n\ntypedef struct {\n\tint port;\n\tdouble rtt;\n} portpingprobe_t;\n\ntypedef struct {\n\tint thread_id;\n\tint num_ports;\n\tstruct sockaddr_in *addr;\n\tportpingprobe_t *results;\n} portpingprobe_worker_args_t;\n\ntypedef struct {\n\tchar *target_addr;\n\tchar *original_addr;\n} portpingprobe_orchestrator_args_t;\n\nstatic SDL_atomic_t portpingprobe_status;\nstatic SDL_atomic_t portpingprobe_progress;\n\nstatic void cl_portpingprobe_probes_changed(cvar_t *var, char *val, qbool *cancel);\nstatic void cl_portpingprobe_port_probes_changed(cvar_t *var, char *val, qbool *cancel);\nstatic cvar_t cl_portpingprobe_probes      = {\"cl_portpingprobe_probes\", \"500\", 0, cl_portpingprobe_probes_changed};\nstatic cvar_t cl_portpingprobe_port_probes = {\"cl_portpingprobe_port_probes\", \"1\", 0, cl_portpingprobe_port_probes_changed};\nstatic cvar_t cl_portpingprobe_enable      = {\"cl_portpingprobe_enable\", \"0\"};\nstatic cvar_t cl_portpingprobe_delay       = {\"cl_portpingprobe_delay\",  \"0\"};\n\nstatic double NET_PortPing(const struct sockaddr_in *srv_adr, const int probe_port);\nstatic int NET_PortPingProbeWorker(void *data);\nstatic SDL_Thread **NET_PortPingProbeInitWorkers(struct sockaddr_in *addr, int num_threads, portpingprobe_t *results);\nstatic int NET_PortPingProbeOrchestrator(void *data);\n\n#define MIN_TCP_TIMEOUT  500\n#define MAX_TCP_TIMEOUT 5000\n\nstatic cvar_t net_tcp_timeout = { \"net_tcp_timeout\", \"2000\" };\n#endif\n\n#ifndef CLIENTONLY\nnetadr_t\tnet_local_sv_ipadr;\nnetadr_t\tnet_local_sv_tcpipadr;\n\ncvar_t\t\tsv_local_addr = {\"sv_local_addr\", \"\", CVAR_ROM};\n#endif\n\nnetadr_t\tnet_from;\nsizebuf_t\tnet_message;\n\nstatic byte net_message_buffer[MSG_BUF_SIZE];\n\n// forward definition.\nqbool NET_GetPacketEx (netsrc_t netsrc, qbool delay);\nvoid NET_SendPacketEx (netsrc_t netsrc, int length, void *data, netadr_t to, qbool delay);\n\n#ifdef SERVERONLY\n#define TCP_LISTEN_BACKLOG 2\n#else\n#define TCP_LISTEN_BACKLOG 1\n#ifndef _WIN32\nextern qbool stdin_ready;\nextern int do_stdin;\n#endif\n#endif\n\n//=============================================================================\n//\n// LOOPBACK defs.\n//\n\n#define MAX_LOOPBACK 4 // must be a power of two\n\ntypedef struct {\n\tbyte\tdata[MAX_UDP_PACKET];\n\tint\t\tdatalen;\n} loopmsg_t;\n\ntypedef struct {\n\tloopmsg_t\tmsgs[MAX_LOOPBACK];\n\tunsigned int\tget, send;\n} loopback_t;\n\nloopback_t\tloopbacks[2];\n\n//=============================================================================\n//\n// Delayed packets, CLIENT ONLY.\n//\n\n#ifndef SERVERONLY\n\ntypedef struct packet_queue_s {\n\tcl_delayed_packet_t packets[CL_MAX_DELAYED_PACKETS];\n\tint head;\n\tint tail;\n\tqbool outgoing;\n} packet_queue_t;\n\nstatic packet_queue_t delay_queue_get;\nstatic packet_queue_t delay_queue_send;\n\nstatic inline void NET_PacketQueueSetNextIndex(int* index)\n{\n\t*index = (*index + 1) % CL_MAX_DELAYED_PACKETS;\n}\n\nstatic qbool NET_PacketQueueRemove(packet_queue_t* queue, sizebuf_t* buffer, netadr_t* from_address)\n{\n\tcl_delayed_packet_t* next = &queue->packets[queue->head];\n\tdouble time;\n\n\t// Empty queue\n\tif (!next->time) {\n\t\treturn false;\n\t}\n\n\t// Not time yet\n\ttime = Sys_DoubleTime();\n\tif (next->time > time) {\n\t\treturn false;\n\t}\n\n\tSZ_Clear(buffer);\n\tSZ_Write(buffer, next->data, next->length);\n\t*from_address = next->addr;\n\tnext->time = 0;\n\n\tNET_PacketQueueSetNextIndex(&queue->head);\n\treturn true;\n}\n\nstatic qbool NET_PacketQueueAdd(packet_queue_t* queue, byte* data, int size, netadr_t addr)\n{\n\tcl_delayed_packet_t* next = &queue->packets[queue->tail];\n\tfloat deviation = 0;\n\tfloat ms_delay;\n\n\tif (cl_delay_packet_dev.integer) {\n\t\tdeviation = f_rnd(-bound(0, cl_delay_packet_dev.integer, CL_MAX_PACKET_DELAY_DEVIATION), bound(0, cl_delay_packet_dev.integer, CL_MAX_PACKET_DELAY_DEVIATION));\n\t}\n\n\t// If buffer is full, can't prevent packet loss - drop this packet\n\tif (next->time && queue->head == queue->tail) {\n\t\treturn false;\n\t}\n\n\t// calculate delay based on settings\n\tif (cls.state != ca_active) {\n\t\t// not yet connected, go as fast as possible\n\t\tms_delay = 0;\n\t}\n\telse if (cl_delay_packet_target.integer && *(int*)data != -1) {\n\t\tif (!queue->outgoing) {\n\t\t\t// dynamically change delay to target a particular latency\n\t\t\tint sequence_ack;\n\t\t\tint sequencemod;\n\t\t\tdouble expected_latency;\n\n\t\t\tMSG_BeginReading();\n\t\t\tMSG_ReadLong(); // sequence =\n\t\t\tsequence_ack = MSG_ReadLong();\n\t\t\tsequence_ack &= ~(1 << 31);\n\n\t\t\tsequencemod = sequence_ack & UPDATE_MASK;\n\t\t\texpected_latency = (cls.realtime - cl.frames[sequencemod].senttime) * 1000.0;\n\n\t\t\tms_delay = max(0, cl_delay_packet_target.value - expected_latency + deviation);\n\t\t}\n\t\telse {\n\t\t\t// push some of the delay onto outgoing\n\t\t\tms_delay = cls.latency / 2;\n\t\t}\n\t}\n\telse {\n\t\t// delay by constant amount\n\t\tms_delay = bound(0, 0.5 * cl_delay_packet.integer + deviation, CL_MAX_PACKET_DELAY);\n\t}\n\n\tmemmove(next->data, data, size);\n\tnext->length = size;\n\tnext->addr = addr;\n\tnext->time = Sys_DoubleTime() + 0.001 * ms_delay;\n\n\tNET_PacketQueueSetNextIndex(&queue->tail);\n\treturn true;\n}\n\nstatic cl_delayed_packet_t* NET_PacketQueuePeek(packet_queue_t* queue)\n{\n\tcl_delayed_packet_t* next = &queue->packets[queue->head];\n\n\t// Empty queue\n\tif (!next->time)\n\t\treturn NULL;\n\n\treturn next;\n}\n\nstatic void NET_PacketQueueAdvance(packet_queue_t* queue)\n{\n\tcl_delayed_packet_t* head = NET_PacketQueuePeek(queue);\n\n\tif (head != NULL)\n\t{\n\t\thead->time = 0;\n\n\t\tNET_PacketQueueSetNextIndex(&queue->head);\n\t}\n}\n\n#endif\n\n//=============================================================================\n//\n// Geters.\n//\n#ifndef CLIENTONLY\nint NET_UDPSVPort (void)\n{\n\treturn ntohs(net_local_sv_ipadr.port);\n}\n#endif\n\nint NET_GetSocket(netsrc_t netsrc, qbool tcp)\n{\n\tif (netsrc == NS_SERVER)\n\t{\n#ifdef CLIENTONLY\n\t\tSys_Error(\"NET_GetPacket: Bad netsrc\");\n\t\treturn INVALID_SOCKET;\n#else\n\t\treturn tcp ? svs.sockettcp : svs.socketip;\n#endif\n\t}\n\telse\n\t{\n#ifdef SERVERONLY\n\t\tSys_Error(\"NET_GetPacket: Bad netsrc\");\n\t\treturn INVALID_SOCKET;\n#else\n\t\treturn tcp ? cls.sockettcp : cls.socketip;\n#endif\n\t}\n}\n\n//=============================================================================\n//\n// Converters.\n//\n\nvoid NetadrToSockadr (const netadr_t *a, struct sockaddr_storage *s)\n{\n\tmemset (s, 0, sizeof(struct sockaddr_in));\n\t((struct sockaddr_in*)s)->sin_family = AF_INET;\n\n\t((struct sockaddr_in*)s)->sin_addr.s_addr = *(int *)&a->ip;\n\t((struct sockaddr_in*)s)->sin_port = a->port;\n}\n\nvoid SockadrToNetadr (const struct sockaddr_storage *s, netadr_t *a)\n{\n\ta->type = NA_IP;\n\t*(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr;\n\ta->port = ((struct sockaddr_in *)s)->sin_port;\n\treturn;\n}\n\n//=============================================================================\n//\n// Comparators.\n//\n\nqbool NET_CompareBaseAdr (const netadr_t a, const netadr_t b)\n{\n#ifndef SERVERONLY\n\tif (a.type == NA_LOOPBACK && b.type == NA_LOOPBACK)\n\t\treturn true;\n#endif\n\n\t// FIXME: Should we check a.type == b.type here ???\n\n\tif (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3])\n\t\treturn true;\n\treturn false;\n}\n\nqbool NET_CompareAdr (const netadr_t a, const netadr_t b)\n{\n#ifndef SERVERONLY\n\tif (a.type == NA_LOOPBACK && b.type == NA_LOOPBACK)\n\t\treturn true;\n#endif\n\n\t// FIXME: Should we check a.type == b.type here ???\n\n\tif (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] && a.port == b.port)\n\t\treturn true;\n\treturn false;\n}\n\n//=============================================================================\n//\n// Printors.\n//\n\nchar *NET_AdrToString (const netadr_t a)\n{\n\tstatic char s[MAX_STRINGS][32]; // 22 should be OK too\n\tstatic int idx = 0;\n\n\tidx %= MAX_STRINGS;\n\n#ifndef SERVERONLY\n\tif (a.type == NA_LOOPBACK) {\n\t\treturn \"loopback\";\n\t}\n#endif\n\n\tsnprintf (s[idx], sizeof(s[0]), \"%i.%i.%i.%i:%i\", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs(a.port));\n\treturn s[idx++];\n}\n\nchar *NET_BaseAdrToString (const netadr_t a)\n{\n\tstatic char s[MAX_STRINGS][32]; // 22 should be OK too\n\tstatic int idx = 0;\n\n\tidx %= MAX_STRINGS;\n\n#ifndef SERVERONLY\n\tif (a.type == NA_LOOPBACK) {\n\t\treturn \"loopback\";\n\t}\n#endif\n\n\tsnprintf (s[idx], sizeof(s[0]), \"%i.%i.%i.%i\", a.ip[0], a.ip[1], a.ip[2], a.ip[3]);\n\treturn s[idx++];\n}\n\nstatic qbool NET_StringToSockaddr (const char *s, struct sockaddr_storage *sadr)\n{\n\tstruct hostent\t*h;\n\tchar\t*colon;\n\tchar\tcopy[128];\n\n\tif (!(*s))\n\t\treturn false;\n\n\tmemset (sadr, 0, sizeof(*sadr));\n\n\t((struct sockaddr_in *)sadr)->sin_family = AF_INET;\n\t((struct sockaddr_in *)sadr)->sin_port = 0;\n\n\t// can't resolve IP by hostname if hostname was truncated\n\tif (strlcpy (copy, s, sizeof(copy)) >= sizeof(copy))\n\t\treturn false;\n\n\t// strip off a trailing :port if present\n\tfor (colon = copy ; *colon ; colon++)\n\t{\n\t\tif (*colon == ':') {\n\t\t\t*colon = 0;\n\t\t\t((struct sockaddr_in *)sadr)->sin_port = htons((short)atoi(colon+1));\n\t\t}\n\t}\n\n\t//this is the wrong way to test. a server name may start with a number.\n\tif (copy[0] >= '0' && copy[0] <= '9')\n\t{\n\t\t//this is the wrong way to test. a server name may start with a number.\n\t\t*(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr(copy);\n\t}\n\telse {\n\t\tif (!(h = gethostbyname(copy))) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (h->h_addrtype != AF_INET) {\n\t\t\treturn false;\n\t\t}\n\n\t\t*(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0];\n\t}\n\n\treturn true;\n}\n\nqbool NET_StringToAdr (const char *s, netadr_t *a)\n{\n\tstruct sockaddr_storage sadr;\n\n#ifndef SERVERONLY\n\tif (!strcmp(s, \"local\")) {\n\t\tmemset(a, 0, sizeof(*a));\n\t\ta->type = NA_LOOPBACK;\n\t\treturn true;\n\t}\n#endif\n\n\tif (!NET_StringToSockaddr (s, &sadr))\n\t\treturn false;\n\n\tSockadrToNetadr (&sadr, a);\n\n\treturn true;\n}\n\n/*\n=============================================================================\nLOOPBACK BUFFERS FOR LOCAL PLAYER\n=============================================================================\n*/\n\nqbool NET_GetLoopPacket (netsrc_t netsrc, netadr_t *from, sizebuf_t *message)\n{\n\tint i;\n\tloopback_t *loop;\n\n\tloop = &loopbacks[netsrc];\n\n\tif (loop->send - loop->get > MAX_LOOPBACK)\n\t\tloop->get = loop->send - MAX_LOOPBACK;\n\n\tif (loop->get >= loop->send)\n\t\treturn false;\n\n\ti = loop->get & (MAX_LOOPBACK-1);\n\tloop->get++;\n\n\tif (message->maxsize < loop->msgs[i].datalen)\n\t\tSys_Error(\"NET_SendLoopPacket: Loopback buffer was too big\");\n\n\tmemcpy (message->data, loop->msgs[i].data, loop->msgs[i].datalen);\n\tmessage->cursize = loop->msgs[i].datalen;\n\tmemset (from, 0, sizeof(*from));\n\tfrom->type = NA_LOOPBACK;\n\treturn true;\n}\n\nvoid NET_SendLoopPacket (netsrc_t netsrc, int length, void *data, netadr_t to)\n{\n\tint i;\n\tloopback_t *loop;\n\n\tloop = &loopbacks[netsrc ^ 1];\n\n\ti = loop->send & (MAX_LOOPBACK - 1);\n\tloop->send++;\n\n\tif (length > (int) sizeof(loop->msgs[i].data))\n\t\tSys_Error (\"NET_SendLoopPacket: length > %d\", (int) sizeof(loop->msgs[i].data));\n\n\tmemcpy (loop->msgs[i].data, data, length);\n\tloop->msgs[i].datalen = length;\n}\n\nvoid NET_ClearLoopback (void)\n{\n\tloopbacks[0].send = loopbacks[0].get = 0;\n\tloopbacks[1].send = loopbacks[1].get = 0;\n}\n\n#ifndef CLIENTONLY\n//=============================================================================\n//\n// SV TCP connection.\n//\n\n// allocate, may link it in, if requested\nsvtcpstream_t *sv_tcp_connection_new(int sock, netadr_t from, char *buf, int buf_len, qbool link)\n{\n\tsvtcpstream_t *st = NULL;\n\n\tst = Q_malloc(sizeof(svtcpstream_t));\n\tst->waitingforprotocolconfirmation = true;\n\tst->socketnum = sock;\n\tst->remoteaddr = from;\n\tif (buf_len > 0 && buf_len < sizeof(st->inbuffer))\n\t{\n\t\tmemmove(st->inbuffer, buf, buf_len);\n\t\tst->inlen = buf_len;\n\t}\n\telse\n\t\tst->drop = true; // yeah, funny\n\n\t// link it in if requested\n\tif (link)\n\t{\n\t\tst->next = svs.tcpstreams;\n\t\tsvs.tcpstreams = st;\n\t}\n\n\treturn st;\n}\n\n// free data, may unlink it out if requested\nstatic void sv_tcp_connection_free(svtcpstream_t *drop, qbool unlink)\n{\n\tif (!drop)\n\t\treturn; // someone kidding us\n\n\t// unlink if requested\n\tif (unlink)\n\t{\n\t\tif (svs.tcpstreams == drop)\n\t\t{\n\t\t\tsvs.tcpstreams = svs.tcpstreams->next;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsvtcpstream_t *st = NULL;\n\n\t\t\tfor (st = svs.tcpstreams; st; st = st->next)\n\t\t\t{\n\t\t\t\tif (st->next == drop)\n\t\t\t\t{\n\t\t\t\t\tst->next = st->next->next;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// well, think socket may be zero, but most of the time zero is stdin fd, so better not close it\n\tif (drop->socketnum && drop->socketnum != INVALID_SOCKET)\n\t\tclosesocket(drop->socketnum);\n\n\tQ_free(drop);\n}\n\nint sv_tcp_connection_count(void)\n{\n\tsvtcpstream_t *st = NULL;\n\tint cnt = 0;\n\n\tfor (st = svs.tcpstreams; st; st = st->next)\n\t\tcnt++;\n\n\treturn cnt;\n}\n#endif\n\n//=============================================================================\n\nqbool NET_GetUDPPacket (netsrc_t netsrc, netadr_t *from_adr, sizebuf_t *message)\n{\n\tint ret, err;\n\tstruct sockaddr_storage from = {0};\n\tsocklen_t fromlen;\n\tint socket = NET_GetSocket(netsrc, false);\n\n\tif (socket == INVALID_SOCKET)\n\t\treturn false;\n\n\tfromlen = sizeof(from);\n\tret = recvfrom (socket, (char *)message->data, message->maxsize, 0, (struct sockaddr *)&from, &fromlen);\n\tSockadrToNetadr (&from, from_adr);\n\n\tif (ret == -1)\n\t{\n\t\terr = qerrno;\n\n\t\tif (err == EWOULDBLOCK)\n\t\t\treturn false; // common error, does not spam in logs.\n\n\t\tif (err == EMSGSIZE)\n\t\t{\n\t\t\tCon_DPrintf (\"Warning: Oversize packet from %s\\n\", NET_AdrToString (*from_adr));\n\t\t\treturn false;\n\t\t}\n\n\t\tif (err == ECONNABORTED || err == ECONNRESET)\n\t\t{\n\t\t\tCon_DPrintf (\"Connection lost or aborted\\n\");\n\t\t\treturn false;\n\t\t}\n\n\t\tCon_Printf (\"NET_GetPacket: recvfrom: (%i): %s\\n\", err, strerror(err));\n\t\treturn false;\n\t}\n\n\tif (ret >= message->maxsize)\n\t{\n\t\tCon_Printf (\"Oversize packet from %s\\n\", NET_AdrToString (*from_adr));\n\t\treturn false;\n\t}\n\n\tmessage->cursize = ret;\n\n\treturn ret;\n}\n\n#ifndef SERVERONLY\nqbool NET_GetTCPPacket_CL (netsrc_t netsrc, netadr_t *from, sizebuf_t *message)\n{\n\tint ret, err;\n\n\tif (netsrc != NS_CLIENT || cls.sockettcp == INVALID_SOCKET)\n\t\treturn false;\n\n\tret = recv(cls.sockettcp, (char *) cls.tcpinbuffer + cls.tcpinlen, sizeof(cls.tcpinbuffer) - cls.tcpinlen, 0);\n\n\t// FIXME: should we check for ret == 0  for disconnect ???\n\n\tif (ret == -1)\n\t{\n\t\terr = qerrno;\n\n\t\tif (err == EWOULDBLOCK)\n\t\t{\n\t\t\tret = 0; // hint for code below that it was not cricial error.\n\t\t}\n\t\telse if (err == ECONNABORTED || err == ECONNRESET)\n\t\t{\n\t\t\tCon_Printf (\"Connection lost or aborted\\n\"); //server died/connection lost.\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf (\"NET_GetPacket: Error (%i): %s\\n\", err, strerror(err));\n\t\t}\n\n\t\tif (ret)\n\t\t{\n\t\t\t// error detected, close socket then.\n\t\t\tclosesocket(cls.sockettcp);\n\t\t\tcls.sockettcp = INVALID_SOCKET;\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tcls.tcpinlen += ret;\n\n\tif (cls.tcpinlen < 2)\n\t\treturn false;\n\n\tmessage->cursize = BigShort(*(short*)cls.tcpinbuffer);\n\tif (message->cursize >= message->maxsize)\n\t{\n\t\tclosesocket(cls.sockettcp);\n\t\tcls.sockettcp = INVALID_SOCKET;\n\t\tCon_Printf (\"Warning: Oversize packet from %s\\n\", NET_AdrToString (cls.sockettcpdest));\n\t\treturn false;\n\t}\n\n\tif (message->cursize + 2 > cls.tcpinlen)\n\t{\n\t\t//not enough buffered to read a packet out of it.\n\t\treturn false;\n\t}\n\n\tmemcpy(message->data, cls.tcpinbuffer + 2, message->cursize);\n\tmemmove(cls.tcpinbuffer, cls.tcpinbuffer + message->cursize + 2, cls.tcpinlen - (message->cursize + 2));\n\tcls.tcpinlen -= message->cursize + 2;\n\n\t*from = cls.sockettcpdest;\n\n\treturn true;\n}\n#endif\n\n#ifndef CLIENTONLY\nqbool NET_GetTCPPacket_SV (netsrc_t netsrc, netadr_t *from, sizebuf_t *message)\n{\n\tint ret;\n\tfloat timeval = Sys_DoubleTime();\n\tsvtcpstream_t *st = NULL, *next = NULL;\n\n\tif (netsrc != NS_SERVER)\n\t\treturn false;\n\n\tfor (st = svs.tcpstreams; st; st = next)\n\t{\n\t\tnext = st->next;\n\n\t\t*from = st->remoteaddr;\n\n\t\tif (st->socketnum == INVALID_SOCKET || st->drop)\n\t\t{\n\t\t\tsv_tcp_connection_free(st, true); // free and unlink\n\t\t\tcontinue;\n\t\t}\n\n\t\t//due to the above checks about invalid sockets, the socket is always open for st below.\n\n\t\t// check for client timeout\n\t\tif (st->timeouttime < timeval)\n\t\t{\n\t\t\tst->drop = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tret = recv(st->socketnum, st->inbuffer+st->inlen, sizeof(st->inbuffer)-st->inlen, 0);\n\t\tif (ret == 0)\n\t\t{\n\t\t\t// connection closed\n\t\t\tst->drop = true;\n\t\t\tcontinue;\n\t\t}\n\t\telse if (ret == -1)\n\t\t{\n\t\t\tint err = qerrno;\n\n\t\t\tif (err == EWOULDBLOCK)\n\t\t\t{\n\t\t\t\tret = 0; // it's OK\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (err == ECONNABORTED || err == ECONNRESET)\n\t\t\t\t{\n\t\t\t\t\tCon_DPrintf (\"Connection lost or aborted\\n\"); //server died/connection lost.\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tCon_DPrintf (\"NET_GetPacket: Error (%i): %s\\n\", err, strerror(err));\n\t\t\t\t}\n\n\t\t\t\tst->drop = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// update timeout\n\t\t\tst->timeouttime = Sys_DoubleTime() + 10;\n\t\t}\n\n\t\tst->inlen += ret;\n\n\t\tif (st->waitingforprotocolconfirmation)\n\t\t{\n\t\t\t// not enough data\n\t\t\tif (st->inlen < 6)\n\t\t\t\tcontinue;\n\n\t\t\tif (strncmp(st->inbuffer, \"qizmo\\n\", 6))\n\t\t\t{\n\t\t\t\tCon_Printf (\"Unknown TCP client\\n\");\n\t\t\t\tst->drop = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// remove leading 6 bytes\n\t\t\tmemmove(st->inbuffer, st->inbuffer+6, st->inlen - (6));\n\t\t\tst->inlen -= 6;\n\t\t\t// confirmed\n\t\t\tst->waitingforprotocolconfirmation = false;\n\t\t}\n\n\t\t// need two bytes for packet len\n\t\tif (st->inlen < 2)\n\t\t\tcontinue;\n\n\t\tmessage->cursize = BigShort(*(short*)st->inbuffer);\n\t\tif (message->cursize < 0)\n\t\t{\n\t\t\tmessage->cursize = 0;\n\t\t\tCon_Printf (\"Warning: malformed message from %s\\n\", NET_AdrToString (*from));\n\t\t\tst->drop = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (message->cursize >= message->maxsize)\n\t\t{\n\t\t\tCon_Printf (\"Warning: Oversize packet from %s\\n\", NET_AdrToString (*from));\n\t\t\tst->drop = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (message->cursize + 2 > st->inlen)\n\t\t{\n\t\t\t//not enough buffered to read a packet out of it.\n\t\t\tcontinue;\n\t\t}\n\n\t\tmemcpy(message->data, st->inbuffer + 2, message->cursize);\n\t\tmemmove(st->inbuffer, st->inbuffer + message->cursize + 2, st->inlen - (message->cursize + 2));\n\t\tst->inlen -= message->cursize + 2;\n\n\t\treturn true; // we got packet!\n\t}\n\n\treturn false; // no packet received.\n}\n#endif\n\nqbool NET_GetPacketEx (netsrc_t netsrc, qbool delay)\n{\n#ifndef SERVERONLY\n\tif (delay)\n\t\treturn NET_PacketQueueRemove(&delay_queue_get, &net_message, &net_from);\n#endif\n\n#ifndef SERVERONLY\n\tif (NET_GetLoopPacket(netsrc, &net_from, &net_message))\n\t\treturn true;\n#endif\n\n\tif (NET_GetUDPPacket(netsrc, &net_from, &net_message))\n\t\treturn true;\n\n// TCPCONNECT -->\n#ifndef SERVERONLY\n\tif (netsrc == NS_CLIENT && cls.sockettcp != INVALID_SOCKET && NET_GetTCPPacket_CL(netsrc, &net_from, &net_message))\n\t\treturn true;\n#endif\n\n#ifndef CLIENTONLY\n\tif (netsrc == NS_SERVER && svs.tcpstreams && NET_GetTCPPacket_SV(netsrc, &net_from, &net_message))\n\t\treturn true;\n#endif\n// <--TCPCONNECT\n\n\treturn false;\n}\n\nqbool NET_GetPacket (netsrc_t netsrc)\n{\n#ifdef SERVERONLY\n\tqbool delay = false;\n#else\n\tqbool delay = (netsrc == NS_CLIENT && (cl_delay_packet.integer || cl_delay_packet_target.integer));\n#endif\n\n\treturn NET_GetPacketEx (netsrc, delay);\n}\n\n//=============================================================================\n\n#ifndef SERVERONLY\nqbool NET_SendTCPPacket_CL (netsrc_t netsrc, int length, void *data, netadr_t to)\n{\n\tunsigned short slen;\n\n\tif (netsrc != NS_CLIENT || cls.sockettcp == INVALID_SOCKET)\n\t\treturn false;\n\n\tif (!NET_CompareAdr(to, cls.sockettcpdest))\n\t\treturn false;\n\n\t// this goes to the server so send it via TCP.\n\tslen = BigShort((unsigned short)length);\n\t// FIXME: CHECK send() result, we use NON BLOCKIN MODE, FFS!\n\tsend(cls.sockettcp, (char*)&slen, sizeof(slen), 0);\n\tsend(cls.sockettcp, data, length, 0);\n\n\treturn true;\n}\n#endif\n\n#ifndef CLIENTONLY\nqbool NET_SendTCPPacket_SV (netsrc_t netsrc, int length, void *data, netadr_t to)\n{\n\tsvtcpstream_t *st;\n\n\tif (netsrc != NS_SERVER)\n\t\treturn false;\n\n\tfor (st = svs.tcpstreams; st; st = st->next)\n\t{\n\t\tif (st->socketnum == INVALID_SOCKET)\n\t\t\tcontinue;\n\n\t\tif (NET_CompareAdr(to, st->remoteaddr))\n\t\t{\n\t\t\tint sent;\n\t\t\tunsigned short slen = BigShort((unsigned short)length);\n\n\t\t\tif (st->outlen + length + sizeof(slen) >= sizeof(st->outbuffer))\n\t\t\t{\n\t\t\t\t// not enough space, we overflowed\n\t\t\t\tbreak; // well, quake should resist to some packet lost.. so we just drop that packet.\n\t\t\t}\n\n\t\t\t// put data in buffer\n\t\t\tmemmove(st->outbuffer + st->outlen, (char*)&slen, sizeof(slen));\n\t\t\tst->outlen += sizeof(slen);\n\t\t\tmemmove(st->outbuffer + st->outlen, data, length);\n\t\t\tst->outlen += length;\n\n\t\t\tsent = send(st->socketnum, st->outbuffer, st->outlen, 0);\n\n\t\t\tif (sent == 0)\n\t\t\t{\n\t\t\t\t// think it's OK\n\t\t\t}\n\t\t\telse if (sent > 0) //we put some data through\n\t\t\t{ //move up the buffer\n\t\t\t\tst->outlen -= sent;\n\t\t\t\tmemmove(st->outbuffer, st->outbuffer + sent, st->outlen);\n\t\t\t}\n\t\t\telse\n\t\t\t{ //error of some kind. would block or something\n\t\t\t\tif (qerrno != EWOULDBLOCK && qerrno != EAGAIN)\n\t\t\t\t{\n\t\t\t\t\tst->drop = true; // something cricial, drop than\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// 'st' will be not zero, if we found 'to' in 'svs.tcpstreams'.\n\t// That does not mean we actualy send packet, since there case of overflow, but who cares,\n\t// all is matter that we found such 'to' and tried to send packet.\n\treturn !!st;\n}\n#endif\n\nqbool NET_SendUDPPacket (netsrc_t netsrc, int length, void *data, netadr_t to)\n{\n\tstruct sockaddr_storage addr;\n\tint ret;\n\tint socket = NET_GetSocket(netsrc, false);\n\n\tif (socket == INVALID_SOCKET)\n\t\treturn false;\n\n\tNetadrToSockadr (&to, &addr);\n\n\tret = sendto (socket, data, length, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));\n\tif (ret == -1)\n\t{\n\t\tint err = qerrno;\n\n\t\tif (err == EWOULDBLOCK || err == ECONNREFUSED || err == EADDRNOTAVAIL)\n\t\t\t; // nothing\n\t\telse\n\t\t\tCon_Printf (\"NET_SendPacket: sendto: (%i): %s %i\\n\", err, strerror(err), socket);\n\t}\n\n\treturn true;\n}\n\nvoid NET_SendPacketEx (netsrc_t netsrc, int length, void *data, netadr_t to, qbool delay)\n{\n#ifndef SERVERONLY\n\tif (delay)\n\t{\n\t\tNET_PacketQueueAdd (&delay_queue_send, data, length, to);\n\t\treturn;\n\t}\n#endif\n\n#ifndef SERVERONLY\n\tif (to.type == NA_LOOPBACK)\n\t{\n\t\tNET_SendLoopPacket (netsrc, length, data, to);\n\t\treturn;\n\t}\n#endif\n\n// TCPCONNECT -->\n#ifndef SERVERONLY\n\tif (netsrc == NS_CLIENT && cls.sockettcp != INVALID_SOCKET && NET_SendTCPPacket_CL(netsrc, length, data, to))\n\t\treturn;\n#endif\n\n#ifndef CLIENTONLY\n\tif (netsrc == NS_SERVER && svs.tcpstreams && NET_SendTCPPacket_SV(netsrc, length, data, to))\n\t\treturn;\n#endif\n// <--TCPCONNECT\n\n\tNET_SendUDPPacket(netsrc, length, data, to);\n}\n\nvoid NET_SendPacket (netsrc_t netsrc, int length, void *data, netadr_t to)\n{\n#ifdef SERVERONLY\n\tqbool delay = false;\n#else\n\tqbool delay = (netsrc == NS_CLIENT && cl_delay_packet.integer);\n#endif\n\n\tNET_SendPacketEx (netsrc, length, data, to, delay);\n}\n\n#ifndef SERVERONLY\nqbool CL_UnqueOutputPacket(qbool sendall)\n{\n\tdouble time = 0;\n\tcl_delayed_packet_t* packet = NULL;\n\tqbool released = false;\n\n\twhile ((packet = NET_PacketQueuePeek(&delay_queue_send)))\n\t{\n\t\tif (!time) {\n\t\t\ttime = Sys_DoubleTime();\n\t\t}\n\n\t\t// Not yet ready to send\n\t\tif (packet->time > time && !sendall)\n\t\t\tbreak;\n\n\t\t// ok, send it\n\t\tNET_SendPacketEx(NS_CLIENT, packet->length, packet->data, packet->addr, false);\n\n\t\t// mark as unused slot\n\t\tNET_PacketQueueAdvance(&delay_queue_send);\n\n\t\treleased = true;\n\t}\n\n\treturn released;\n}\n#endif\n\n//=============================================================================\n\nqbool TCP_Set_KEEPALIVE(int sock)\n{\n\tint\t\tiOptVal = 1;\n\n\tif (sock == INVALID_SOCKET) {\n\t\tCon_Printf(\"TCP_Set_KEEPALIVE: invalid socket\\n\");\n\t\treturn false;\n\t}\n\n\tif (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void*)&iOptVal, sizeof(iOptVal)) == SOCKET_ERROR) {\n\t\tCon_Printf (\"TCP_Set_KEEPALIVE: setsockopt: (%i): %s\\n\", qerrno, strerror (qerrno));\n\t\treturn false;\n\t}\n\n#if defined(__linux__)\n\n//\tThe time (in seconds) the connection needs to remain idle before TCP starts sending keepalive probes, \n//  if the socket option SO_KEEPALIVE has been set on this socket.\n\n\tiOptVal = 60;\n\n\tif (setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, (void*)&iOptVal, sizeof(iOptVal)) == -1) {\n\t\tCon_Printf (\"TCP_Set_KEEPALIVE: setsockopt TCP_KEEPIDLE: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\treturn false;\n\t}\n\n//  The time (in seconds) between individual keepalive probes.\n\tiOptVal = 30;\n\n\tif (setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, (void*)&iOptVal, sizeof(iOptVal)) == -1) {\n\t\tCon_Printf (\"TCP_Set_KEEPALIVE: setsockopt TCP_KEEPINTVL: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\treturn false;\n\t}\n\n//  The maximum number of keepalive probes TCP should send before dropping the connection. \n\tiOptVal = 6;\n\n\tif (setsockopt(sock, SOL_TCP, TCP_KEEPCNT, (void*)&iOptVal, sizeof(iOptVal)) == -1) {\n\t\tCon_Printf (\"TCP_Set_KEEPALIVE: setsockopt TCP_KEEPCNT: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\treturn false;\n\t}\n#else\n\t// FIXME: windows, bsd etc...\n#endif\n\n\treturn true;\n}\n\nint TCP_OpenStream(netadr_t remoteaddr)\n{\n\tunsigned long _true = true;\n\tint newsocket;\n\tint temp;\n\tstruct sockaddr_storage qs;\n\n\tNetadrToSockadr(&remoteaddr, &qs);\n\ttemp = sizeof(struct sockaddr_in);\n\n\tif ((newsocket = socket(((struct sockaddr_in*)&qs)->sin_family, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {\n\t\tCon_Printf(\"TCP_OpenStream: socket: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\treturn INVALID_SOCKET;\n\t}\n\n\t// Set socket to non-blocking\n#if !defined(_WIN32)\n\tif ((fcntl(newsocket, F_SETFL, O_NONBLOCK)) == -1) { // O'Rly?! @@@\n\t\tCon_Printf(\"TCP_OpenStream: fcntl: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\tclosesocket(newsocket);\n\t\treturn INVALID_SOCKET;\n\t}\n#endif\n\n\tif (ioctlsocket(newsocket, FIONBIO, &_true) == -1) { // make asynchronous\n\t\tCon_Printf(\"TCP_OpenStream: ioctl: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\tclosesocket(newsocket);\n\t\treturn INVALID_SOCKET;\n\t}\n\n\tif (connect (newsocket, (struct sockaddr *)&qs, temp) == INVALID_SOCKET) {\n\t\t// Socket is non-blocking, so check if the error is just because it would block\n\t\tif (qerrno == EZ_TCP_WOULDBLOCK) {\n\t\t\tstruct timeval t;\n\t\t\tfd_set socket_set;\n\n\t\t\tt.tv_sec = bound(MIN_TCP_TIMEOUT, net_tcp_timeout.integer, MAX_TCP_TIMEOUT) / 1000;\n\t\t\tt.tv_usec = (bound(MIN_TCP_TIMEOUT, net_tcp_timeout.integer, MAX_TCP_TIMEOUT) % 1000) * 1000;\n\n\t\t\tFD_ZERO(&socket_set);\n\t\t\tFD_SET(newsocket, &socket_set);\n\n\t\t\tif (select(newsocket + 1, NULL, &socket_set, NULL, &t) <= 0) {\n\t\t\t\tCon_Printf(\"TCP_OpenStream: connection timeout\\n\");\n\t\t\t\tclosesocket(newsocket);\n\t\t\t\treturn INVALID_SOCKET;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tint error = SOCKET_ERROR;\n#ifdef _WIN32\n\t\t\t\tint len = sizeof(error);\n#else\n\t\t\t\tsocklen_t len = sizeof(error);\n#endif\n\n\t\t\t\tgetsockopt(newsocket, SOL_SOCKET, SO_ERROR, (char*)&error, &len);\n\t\t\t\tif (error != 0) {\n\t\t\t\t\tCon_Printf(\"TCP_OpenStream: connect: (%i): %s\\n\", error, strerror(error));\n\t\t\t\t\tclosesocket(newsocket);\n\t\t\t\t\treturn INVALID_SOCKET;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tCon_Printf(\"TCP_OpenStream: connect: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\t\tclosesocket(newsocket);\n\t\t\treturn INVALID_SOCKET;\n\t\t}\n\t}\n\n\treturn newsocket;\n}\n\nint TCP_OpenListenSocket (unsigned short int port)\n{\n\tint newsocket;\n\tstruct sockaddr_in address = { 0 };\n\tunsigned long nonblocking = true;\n\tint i;\n\n\tif ((newsocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {\n\t\tCon_Printf (\"TCP_OpenListenSocket: socket: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\treturn INVALID_SOCKET;\n\t}\n\n#ifndef _WIN32\n\tif ((fcntl (newsocket, F_SETFL, O_NONBLOCK)) == -1) { // O'Rly?! @@@\n\t\tCon_Printf (\"TCP_OpenListenSocket: fcntl: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\tclosesocket(newsocket);\n\t\treturn INVALID_SOCKET;\n\t}\n#endif\n\n\tif (ioctlsocket (newsocket, FIONBIO, &nonblocking) == -1) { // make asynchronous\n\t\tCon_Printf (\"TCP_OpenListenSocket: ioctl: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\tclosesocket(newsocket);\n\t\treturn INVALID_SOCKET;\n\t}\n\n#ifdef __APPLE__\n\taddress.sin_len = sizeof(address); // apple are special...\n#endif\n\taddress.sin_family = AF_INET;\n\n\t// check for interface binding option\n\tif ((i = COM_CheckParm(cmdline_param_net_ipaddress)) != 0 && i < COM_Argc()) {\n\t\taddress.sin_addr.s_addr = inet_addr(COM_Argv(i+1));\n\t\tCon_DPrintf (\"Binding to IP Interface Address of %s\\n\", inet_ntoa(address.sin_addr));\n\t}\n\telse {\n\t\taddress.sin_addr.s_addr = INADDR_ANY;\n\t}\n\t\n\tif (port == PORT_ANY) {\n\t\taddress.sin_port = 0;\n\t}\n\telse {\n\t\taddress.sin_port = htons(port);\n\t}\n\n\tif (bind (newsocket, (void *)&address, sizeof(address)) == -1) {\n\t\tCon_Printf (\"TCP_OpenListenSocket: bind: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\tclosesocket(newsocket);\n\t\treturn INVALID_SOCKET;\n\t}\n\n\tif (listen (newsocket, TCP_LISTEN_BACKLOG) == INVALID_SOCKET) {\n\t\tCon_Printf (\"TCP_OpenListenSocket: listen: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\tclosesocket(newsocket);\n\t\treturn INVALID_SOCKET;\n\t}\n\n\tif (!TCP_Set_KEEPALIVE(newsocket)) {\n\t\tCon_Printf (\"TCP_OpenListenSocket: TCP_Set_KEEPALIVE: failed\\n\");\n\t\tclosesocket(newsocket);\n\t\treturn INVALID_SOCKET;\n\t}\n\n\treturn newsocket;\n}\n\nint UDP_OpenSocket (unsigned short int port)\n{\n\tint newsocket;\n\tstruct sockaddr_in address;\n\tunsigned long _true = true;\n\tint i;\n\n\tif ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {\n\t\tCon_Printf (\"UDP_OpenSocket: socket: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\treturn INVALID_SOCKET;\n\t}\n\n#ifndef _WIN32\n\tif ((fcntl (newsocket, F_SETFL, O_NONBLOCK)) == -1) { // O'Rly?! @@@\n\t\tCon_Printf (\"UDP_OpenSocket: fcntl: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\tclosesocket(newsocket);\n\t\treturn INVALID_SOCKET;\n\t}\n#endif\n\n\tif (ioctlsocket (newsocket, FIONBIO, &_true) == -1) { // make asynchronous\n\t\tCon_Printf (\"UDP_OpenSocket: ioctl: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\tclosesocket(newsocket);\n\t\treturn INVALID_SOCKET;\n\t}\n\n\taddress.sin_family = AF_INET;\n\n\t// check for interface binding option\n\tif ((i = COM_CheckParm(cmdline_param_net_ipaddress)) != 0 && i < COM_Argc()) {\n\t\taddress.sin_addr.s_addr = inet_addr(COM_Argv(i+1));\n\t\tCon_DPrintf (\"Binding to IP Interface Address of %s\\n\", inet_ntoa(address.sin_addr));\n\t}\n\telse {\n\t\taddress.sin_addr.s_addr = INADDR_ANY;\n\t}\n\n\tif (port == PORT_ANY) {\n\t\taddress.sin_port = 0;\n\t}\n\telse {\n\t\taddress.sin_port = htons(port);\n\t}\n\n\tif (bind (newsocket, (void *)&address, sizeof(address)) == -1) {\n\t\tCon_Printf (\"UDP_OpenSocket: bind: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\tclosesocket(newsocket);\n\t\treturn INVALID_SOCKET;\n\t}\n\n\treturn newsocket;\n}\n\nqbool NET_Sleep(int msec, qbool stdinissocket)\n{\n\tstruct timeval\ttimeout;\n\tfd_set\t\t\tfdset;\n\tqbool\t\t\tstdin_ready = false;\n\tint\t\t\t\tmaxfd = 0;\n\n\tFD_ZERO (&fdset);\n\n\tif (stdinissocket) {\n\t\tFD_SET (0, &fdset); // stdin is processed too (tends to be socket 0)\n\t\tmaxfd = max(0, maxfd);\n\t}\n\n#ifndef CLIENTONLY\n\tif (svs.socketip != INVALID_SOCKET) {\n\t\tFD_SET(svs.socketip, &fdset); // network socket\n\t\tmaxfd = max(svs.socketip, maxfd);\n\t}\n#endif\n\n\ttimeout.tv_sec = msec/1000;\n\ttimeout.tv_usec = (msec%1000)*1000;\n\tswitch (select(maxfd + 1, &fdset, NULL, NULL, &timeout))\n\t{\n\t\tcase -1:\n\t\tcase  0:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tif (stdinissocket) {\n\t\t\t\tstdin_ready = FD_ISSET(0, &fdset);\n\t\t\t}\n\t\t\tbreak;\n\t}\n\n\treturn stdin_ready;\n}\n\nvoid NET_GetLocalAddress (int socket, netadr_t *out)\n{\n\tchar buff[512];\n\tstruct sockaddr_storage address;\n\tsize_t namelen;\n\tnetadr_t adr = {0};\n\tqbool notvalid = false;\n\n\tstrlcpy (buff, \"localhost\", sizeof (buff));\n\tgethostname (buff, sizeof (buff));\n\tbuff[sizeof(buff) - 1] = 0;\n\n\tif (!NET_StringToAdr (buff, &adr))\t//urm\n\t\tNET_StringToAdr (\"127.0.0.1\", &adr);\n\n\tnamelen = sizeof(address);\n\tif (getsockname (socket, (struct sockaddr *)&address, (socklen_t *)&namelen) == -1) {\n\t\tnotvalid = true;\n\t\tNET_StringToSockaddr(\"0.0.0.0\", (struct sockaddr_storage *)&address);\n//\t\tSys_Error (\"NET_Init: getsockname:\", strerror(qerrno));\n\t}\n\n\tSockadrToNetadr(&address, out);\n\tif (!*(int*)out->ip)\t//socket was set to auto\n\t\t*(int *)out->ip = *(int *)adr.ip;\t//change it to what the machine says it is, rather than the socket.\n\n#ifndef SERVERONLY\n\tif (notvalid)\n\t\tCom_Printf_State (PRINT_FAIL, \"Couldn't detect local ip\\n\");\n\telse\n\t\tCom_Printf_State (PRINT_OK, \"IP address %s\\n\", NET_AdrToString (*out));\n#endif\n}\n\nvoid NET_Init (void)\n{\n#ifdef _WIN32\n\tWORD wVersionRequested;\n\tint r;\n\n\twVersionRequested = MAKEWORD(1, 1);\n\tr = WSAStartup (wVersionRequested, &winsockdata);\n\tif (r)\n\t\tSys_Error (\"Winsock initialization failed.\");\n#endif\n\n\t// init the message buffer\n\tSZ_Init (&net_message, net_message_buffer, sizeof(net_message_buffer));\n\n\tCon_DPrintf(\"UDP Initialized\\n\");\n\n#ifndef SERVERONLY\n\tcls.socketip = INVALID_SOCKET;\n// TCPCONNECT -->\n\tcls.sockettcp = INVALID_SOCKET;\n// <--TCPCONNECT\n\n\tCvar_Register(&cl_net_clientport);\n\tCvar_Register(&net_tcp_timeout);\n\n\tCvar_Register(&cl_portpingprobe_delay);\n\tCvar_Register(&cl_portpingprobe_enable);\n\tCvar_Register(&cl_portpingprobe_probes);\n\tCvar_Register(&cl_portpingprobe_port_probes);\n\tNET_SetPortPingProbeStatus(PORTPINGPROBE_READY);\n\n\tdelay_queue_send.outgoing = true;\n#endif\n\n#ifndef CLIENTONLY\n\tCvar_Register (&sv_local_addr);\n\n\tsvs.socketip = INVALID_SOCKET;\n// TCPCONNECT -->\n\tsvs.sockettcp = INVALID_SOCKET;\n// <--TCPCONNECT\n#endif\n\n#ifdef SERVERONLY\n\t// As client+server we init it in SV_SpawnServer().\n\t// As serveronly we do it here.\n\tNET_InitServer();\n#endif\n}\n\nvoid NET_Shutdown (void)\n{\n#ifndef CLIENTONLY\n\tNET_CloseServer();\n#endif\n\n#ifndef SERVERONLY\n\tNET_CloseClient();\n#endif\n\n#ifdef _WIN32\n\tWSACleanup ();\n#endif\n}\n\n#ifndef SERVERONLY\nstatic void cl_net_clientport_changed(cvar_t* var, char* value, qbool* cancel)\n{\n\tint new_socket = INVALID_SOCKET;\n\tint new_port = atoi(value);\n\tqbool set_auto = false;\n\n\t// No change\n\tif (new_port == var->integer) {\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n#ifndef CLIENTONLY\n\t// FIXME: Technically this could be changed on the command line or dynamically allocated\n\t//        no damage done if they do this when disconnected \n\tif (new_port == PORT_SERVER) {\n\t\t*cancel = true;\n\t\tCon_Printf(\"Port %i is reserved for the internal server.\\n\", new_port);\n\t\treturn;\n\t}\n#endif\n\n\t// In theory you could be connected to mvd...\n\tif (cls.state != ca_disconnected) {\n\t\tCon_Printf(\"You must be disconnected to change %s.\\n\", var->name);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tif (new_port > 0) {\n\t\tnew_socket = UDP_OpenSocket(new_port);\n\n\t\tif (new_socket == INVALID_SOCKET) {\n\t\t\tCon_Printf(\"Unable to open new socket on port %d\\n\", new_port);\n\t\t\t*cancel = true;\n\t\t\treturn;\n\t\t}\n\t}\n\tif (new_socket == INVALID_SOCKET) {\n\t\tnew_socket = UDP_OpenSocket(PORT_ANY);\n\t\tset_auto = true;\n\t}\n\t\n\tif (new_socket == INVALID_SOCKET) {\n\t\tCon_Printf(\"Unable to open new socket\\n\");\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tif (cls.socketip != INVALID_SOCKET) {\n\t\tclosesocket(cls.socketip);\n\t}\n\n\tcls.socketip = new_socket;\n\tNET_GetLocalAddress(cls.socketip, &net_local_cl_ipadr);\n\tif (set_auto) {\n\t\tCom_Printf(\"Client port allocated: %i\\n\", ntohs(net_local_cl_ipadr.port));\n\t\tCvar_AutoSetInt(var, ntohs(net_local_cl_ipadr.port));\n\t}\n\telse {\n\t\tCvar_AutoReset(var);\n\t}\n}\n\n// This is called after config loaded\nvoid NET_InitClient(void)\n{\n\tint port = cl_net_clientport.integer;\n\tqbool set_auto = false;\n\tqbool set = (cls.socketip == INVALID_SOCKET);\n\tint p;\n\n\t// Allow user to override the config file\n\tp = COM_CheckParm(cmdline_param_net_clientport);\n\tif (p && p < COM_Argc()) {\n\t\tport = atoi(COM_Argv(p + 1));\n\t\tset_auto = true;\n\t}\n\n\tif (cls.socketip == INVALID_SOCKET && port > 0)\n\t\tcls.socketip = UDP_OpenSocket(port);\n\n\tif (cls.socketip == INVALID_SOCKET) {\n\t\tcls.socketip = UDP_OpenSocket(PORT_ANY); // any dynamic port\n\t\tset_auto = true;\n\t}\n\n\tif (cls.socketip == INVALID_SOCKET) {\n\t\tSys_Error(\"Couldn't allocate client socket\");\n\t\treturn;\n\t}\n\n\t// init the message buffer\n\tSZ_Init(&net_message, net_message_buffer, sizeof(net_message_buffer));\n\n\t// determine my name & address\n\tNET_GetLocalAddress(cls.socketip, &net_local_cl_ipadr);\n\tif (set) {\n\t\tif (set_auto) {\n\t\t\tCvar_AutoSetInt(&cl_net_clientport, ntohs(net_local_cl_ipadr.port));\n\t\t}\n\t\telse {\n\t\t\tCvar_AutoReset(&cl_net_clientport);\n\t\t}\n\t}\n\n\tCom_Printf_State(PRINT_OK, \"Client port initialized: %i\\n\", ntohs(net_local_cl_ipadr.port));\n}\n\nvoid NET_CloseClient (void)\n{\n\tif (cls.socketip != INVALID_SOCKET) {\n\t\tclosesocket(cls.socketip);\n\t\tcls.socketip = INVALID_SOCKET;\n\t}\n\n// TCPCONNECT -->\n\t// FIXME: is it OK? Probably we should send disconnect?\n\tif (cls.sockettcp != INVALID_SOCKET) {\n\t\tclosesocket(cls.sockettcp);\n\t\tcls.sockettcp = INVALID_SOCKET;\n\t}\n// <--TCPCONNECT\n}\n\nqbool CL_QueInputPacket(void)\n{\n\tif (!NET_GetPacketEx(NS_CLIENT, false))\n\t\treturn false;\n\n\treturn NET_PacketQueueAdd(&delay_queue_get, net_message.data, net_message.cursize, net_from);\n}\n\nvoid CL_ClearQueuedPackets(void)\n{\n\tmemset(&delay_queue_get, 0, sizeof(delay_queue_get));\n\tmemset(&delay_queue_send, 0, sizeof(delay_queue_send));\n\tdelay_queue_send.outgoing = true;\n}\n#endif\n\n#ifndef CLIENTONLY\n//\n// Open server TCP port.\n// NOTE: Zero port will actually close already opened port.\n//\nvoid NET_InitServer_TCP(unsigned short int port)\n{\n\t// close socket first.\n\tif (svs.sockettcp != INVALID_SOCKET)\n\t{\n\t\tCon_Printf(\"Server TCP port closed\\n\");\n\t\tclosesocket(svs.sockettcp);\n\t\tsvs.sockettcp = INVALID_SOCKET;\n\t\tnet_local_sv_tcpipadr.type = NA_INVALID;\n\t}\n\n\tif (port)\n\t{\n\t\tsvs.sockettcp = TCP_OpenListenSocket (port);\n\n\t\tif (svs.sockettcp != INVALID_SOCKET)\n\t\t{\n\t\t\t// get local address.\n\t\t\tNET_GetLocalAddress (svs.sockettcp, &net_local_sv_tcpipadr);\n\t\t\tCon_Printf(\"Opening server TCP port %u\\n\", (unsigned int)port);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf(\"Failed to open server TCP port %u\\n\", (unsigned int)port);\n\t\t}\n\t}\n}\n\nvoid NET_InitServer (void)\n{\n\tint port = PORT_SERVER;\n\tint p;\n\n\tp = COM_CheckParm (cmdline_param_net_serverport);\n\tif (p && p < COM_Argc()) {\n\t\tport = atoi(COM_Argv(p+1));\n\t}\n\n\tif (svs.socketip == INVALID_SOCKET) {\n\t\tsvs.socketip = UDP_OpenSocket (port);\n\t}\n\n\tif (svs.socketip != INVALID_SOCKET) {\n\t\tNET_GetLocalAddress (svs.socketip, &net_local_sv_ipadr);\n\t\tCvar_SetROM (&sv_local_addr, NET_AdrToString (net_local_sv_ipadr));\n\t}\n\telse {\n\t\t// FIXME: is it right???\n\t\tCvar_SetROM (&sv_local_addr, \"\");\n\t}\n\n\tif (svs.socketip == INVALID_SOCKET) {\n#ifdef SERVERONLY\n\t\tSys_Error\n#else\n\t\tCon_Printf\n#endif\n\t\t\t(\"WARNING: Couldn't allocate server socket\\n\");\n\t}\n\n#ifndef SERVERONLY\n\t// init the message buffer\n\tSZ_Init (&net_message, net_message_buffer, sizeof(net_message_buffer));\n#endif\n}\n\nvoid NET_CloseServer (void)\n{\n\tif (svs.socketip != INVALID_SOCKET) {\n\t\tclosesocket(svs.socketip);\n\t\tsvs.socketip = INVALID_SOCKET;\n\t}\n\n\tnet_local_sv_ipadr.type = NA_LOOPBACK; // FIXME: why not NA_INVALID?\n\n// TCPCONNECT -->\n\tNET_InitServer_TCP(0);\n// <--TCPCONNECT\n}\n#endif\n\nstatic void cl_portpingprobe_probes_changed(cvar_t *var, char *val, qbool *cancel)\n{\n\tint probes = Q_atoi(val);\n\n\tif (probes < 1 || probes > 1000)\n\t{\n\t\tCom_Printf(\"The number of probes needs to be between 1 and 1000.\\n\");\n\t\t*cancel = true;\n\t}\n}\n\nstatic void cl_portpingprobe_port_probes_changed(cvar_t *var, char *val, qbool *cancel)\n{\n\tint probes = Q_atoi(val);\n\n\tif (probes < 1 || probes > 5)\n\t{\n\t\tCom_Printf(\"The number of port probes needs to be between 1 and 5.\\n\");\n\t\t*cancel = true;\n\t}\n}\n\nstatic double NET_PortPing(const struct sockaddr_in *srv_adr, const int probe_port) {\n\tstatic char payload[] = {0xff, 0xff, 0xff, 0xff, 'p', 'i', 'n', 'g'};\n\tstatic struct timeval timeout = {1, 0};\n\tstruct sockaddr_in cli_addr;\n\tdouble start;\n\tchar buf;\n\tint sock, ret = -1;\n#ifdef _WIN32\n\tstatic int timeout_ms = -1;\n\n\tif (timeout_ms == -1)\n\t{\n\t\ttimeout_ms = timeout.tv_sec * 1000 + timeout.tv_usec / 1000;\n\t}\n#endif\n\n\tif ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)\n\t{\n\t\tCom_Printf(\"NET_PortPing: Unable to initialize socket\\n\");\n\t\treturn -1;\n\t}\n\n#ifdef _WIN32\n\tif (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout_ms, sizeof(timeout_ms)) < 0)\n#else\n\tif (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0)\n#endif\n\t{\n\t\tCom_Printf(\"NET_PortPing: Unable to set timeout on socket\\n\");\n\t\tgoto cleanup;\n\t}\n\n\tmemset(&cli_addr, 0, sizeof(cli_addr));\n\tcli_addr.sin_family = AF_INET;\n\tcli_addr.sin_addr.s_addr = INADDR_ANY;\n\tcli_addr.sin_port = htons(probe_port);\n\n\tif (bind(sock, (struct sockaddr *)&cli_addr, sizeof(cli_addr)) < 0)\n\t{\n\t\tCom_DPrintf(\"NET_PortPing: Port is already in use: %d\\n\", probe_port);\n\t\tgoto cleanup;\n\t}\n\n\tstart = Sys_DoubleTime();\n\n\tif ((ret = sendto(sock, payload, sizeof(payload), 0, (struct sockaddr *)srv_adr, sizeof(*srv_adr))) < 0)\n\t{\n\t\tCom_Printf(\"NET_PortPing: Unable to send data to the server from port %d\\n\", probe_port);\n\t\tgoto cleanup;\n\t}\n\n\t// If this fails, it is an indication that we are being rate-limited by\n\t// a router along the way, so we'll pause for a while before proceeding.\n\tif ((ret = recvfrom(sock, &buf, sizeof(buf), 0, NULL, NULL)) <= 0)\n\t{\n\t\tCom_DPrintf(\"NET_PortPing: No data received from the server\\n\");\n\t\tSys_MSleep(100);\n\t\tret = -1;\n\t}\n\ncleanup:\n\tclosesocket(sock);\n\n\treturn ret == -1 ? ret : Sys_DoubleTime() - start;\n}\n\nstatic int NET_PortPingProbeWorker(void *data)\n{\n\tportpingprobe_worker_args_t *args = (portpingprobe_worker_args_t *)data;\n\tdouble rtt;\n\tint i, port;\n\tint *ports = Q_malloc(args->num_ports * sizeof(int));\n\tint num_probes = args->num_ports * cl_portpingprobe_port_probes.integer;\n\n\tCom_DPrintf(\"[%d]: Generating %d random ports\\n\", args->thread_id, args->num_ports);\n\tfor (i = 0; i < args->num_ports; i++)\n\t{\n\t\tports[i] = rand() % (65536 - 1024) + 1024;\n\t}\n\n\tCom_DPrintf(\"[%d]: Launching %d probes\\n\", args->thread_id, num_probes);\n\tfor (i = 0; i < num_probes; i++)\n\t{\n\t\tport = ports[i % args->num_ports];\n\t\trtt = NET_PortPing(args->addr, port);\n\t\targs->results[i].port = port;\n\t\targs->results[i].rtt = rtt;\n\n\t\t// If the client issues a new connection while a probe is in\n\t\t// progress or disconnects, we'll abort and exit gracefully.\n\t\tif (NET_GetPortPingProbeStatus() == PORTPINGPROBE_ABORT)\n\t\t{\n\t\t\tgoto cleanup;\n\t\t}\n\n\t\t// If set, we'll sleep for delay number of ms before moving on\n\t\t// to the next iteration.\n\t\tif (cl_portpingprobe_delay.integer > 0)\n\t\t{\n\t\t\tSys_MSleep(cl_portpingprobe_delay.integer);\n\t\t}\n\n\t\tCom_DPrintf(\"[%d]: Probing port %d: RTT = %f ms\\n\", args->thread_id, port, rtt*1000.0);\n\n\t\t// Increment the progress counter.\n\t\tSDL_AtomicIncRef(&portpingprobe_progress);\n\t}\n\ncleanup:\n\tfree(args);\n\tfree(ports);\n\n\treturn 0;\n}\n\nstatic SDL_Thread **NET_PortPingProbeInitWorkers(struct sockaddr_in *addr, int num_threads, portpingprobe_t *results)\n{\n\tSDL_Thread **workers = Q_malloc(sizeof(SDL_Thread *) * num_threads);\n\tportpingprobe_worker_args_t *args;\n\tint i, j, num_probes, remaining_probes;\n\n\t// Calculate the number of probes each thread should perform.\n\tnum_probes = num_threads == 1\n\t\t? cl_portpingprobe_probes.integer\n\t\t: cl_portpingprobe_probes.integer / num_threads;\n\tremaining_probes = num_threads == 1 ? 0 : cl_portpingprobe_probes.integer % num_threads;\n\n\tfor (i = 0; i < num_threads; i++)\n\t{\n\t\t// The arguments we allocate here will be freed by the\n\t\t// NET_PortPingProbeWorker when it is finished. If we can't\n\t\t// create the thread, we'll free the current arguments here and\n\t\t// let the cleanup handle the previously created workers.\n\t\targs = Q_malloc(sizeof(portpingprobe_worker_args_t));\n\t\targs->addr = addr;\n\t\targs->thread_id = i;\n\t\targs->results = &results[i * num_probes * cl_portpingprobe_port_probes.integer];\n\t\targs->num_ports = num_probes + (i == num_threads - 1 ? remaining_probes : 0);\n\n\t\tworkers[i] = Sys_CreateThread(NET_PortPingProbeWorker, args);\n\t\tif (!workers[i])\n\t\t{\n\t\t\tCom_Printf(\"NET_PortPingProbeInitWorkers: Failed to create worker thread %d\\n\", i);\n\t\t\tQ_free(args);\n\t\t\tgoto cleanup;\n\t\t}\n\t}\n\n\treturn workers;\n\ncleanup:\n\t// If we've started some threads, we'll need to push the ABORT signal so\n\t// they are exited before we proceed with further cleanup.\n\tif (i > 0)\n\t{\n\t\tNET_SetPortPingProbeStatus(PORTPINGPROBE_ABORT);\n\n\t\tfor (j = 0; j < i; j++)\n\t\t{\n\t\t\tSDL_WaitThread(workers[j], NULL);\n\t\t}\n\t}\n\n\tQ_free(workers);\n\n\treturn NULL;\n}\n\nstatic int NET_PortPingProbeOrchestrator(void *data)\n{\n\tportpingprobe_orchestrator_args_t *args = (portpingprobe_orchestrator_args_t *)data;\n\tportpingprobe_status_t status = PORTPINGPROBE_READY;\n\tportpingprobe_t *results = NULL;\n\tSDL_Thread **workers = NULL;\n\tstruct sockaddr_in addr;\n\tnetadr_t net_addr;\n\tdouble best_rtt = 100000;\n\tint best_port = cl_net_clientport.integer;\n\tint num_cores = SDL_GetCPUCount();\n\tint num_threads = num_cores - 4;\n\tint i;\n\n\tif (num_threads < 1)\n\t{\n\t\tnum_threads = 1;\n\t}\n\n\t// Ensure that the target address is valid before we proceed.\n\tif (!NET_StringToAdr(args->target_addr, &net_addr))\n\t{\n\t\tCom_Printf(\"NET_PortPingProbeOrchestrator: Invalid address %s\\n\", args->target_addr);\n\t\tgoto cleanup;\n\t}\n\tmemset(&addr, 0, sizeof(addr));\n\taddr.sin_family = AF_INET;\n\taddr.sin_port = net_addr.port == 0 ? htons(27500) : net_addr.port;\n\tmemcpy(&addr.sin_addr.s_addr, net_addr.ip, 4);\n\n\t// Allocate memory for the probe results.\n\tresults = Q_malloc(sizeof(portpingprobe_t) * cl_portpingprobe_probes.integer * cl_portpingprobe_port_probes.integer);\n\n\t// Initialize the worker threads.\n\tworkers = NET_PortPingProbeInitWorkers(&addr, num_threads, results);\n\tif (workers == NULL)\n\t{\n\t\tgoto cleanup;\n\t}\n\n\t// Reset the progress counter. The worker threads will increment the\n\t// counter during the probing, and the results will be rendered using\n\t// the progress bar from console.c\n\tSDL_AtomicSet(&portpingprobe_progress, 0);\n\n\tCom_Printf(\"Probing %s to find the best source port (%d probes, %d threads, %d %s per port)\\n\",\n\t\targs->target_addr, cl_portpingprobe_probes.integer, num_threads,\n\t\tcl_portpingprobe_port_probes.integer,\n\t\tcl_portpingprobe_port_probes.integer == 1 ? \"probe\" : \"probes\");\n\n\t// Wait for the worker threads to finish; they will either finish when\n\t// all probes have been completed or if they are aborted by the user.\n\tfor (i = 0; i < num_threads; i++)\n\t{\n\t\tSDL_WaitThread(workers[i], NULL);\n\t}\n\n\t// The probe was aborted. Let's clean up immediately and end the\n\t// orchestrator.\n\tif (NET_GetPortPingProbeStatus() == PORTPINGPROBE_ABORT)\n\t{\n\t\tCom_Printf(\"Port ping probe aborted\\n\");\n\t\tgoto cleanup;\n\t}\n\n\t// Iterate over the results and select the port with the lowest latency.\n\tfor (i = 0; i < cl_portpingprobe_probes.integer * cl_portpingprobe_port_probes.integer; i++)\n\t{\n\t\tCom_DPrintf(\"Results: index: %d, port: %d RTT: %f\\n\",\n\t\t\ti, results[i].port, results[i].rtt);\n\n\t\tif (results[i].rtt != -1 && results[i].rtt < best_rtt)\n\t\t{\n\t\t\tbest_port = results[i].port;\n\t\t\tbest_rtt = results[i].rtt;\n\t\t}\n\t}\n\n\tCom_Printf(\"Connecting to %s using source port %d (%.2f ms)\\n\",\n\t\targs->target_addr, best_port, best_rtt*1000.0);\n\tCvar_SetValue(&cl_net_clientport, best_port);\n\tCbuf_AddText(va(\"connect %s\\n\", args->original_addr));\n\n\tstatus = PORTPINGPROBE_COMPLETED;\n\ncleanup:\n\tQ_free(workers);\n\tQ_free(results);\n\tQ_free(args->target_addr);\n\tQ_free(args->original_addr);\n\tQ_free(args);\n\n\tNET_SetPortPingProbeStatus(status);\n\n\treturn 0;\n}\n\nqbool IsPortPingProbeEnabled(void)\n{\n\treturn cl_portpingprobe_enable.integer;\n}\n\nvoid NET_PortPingProbe(const char *target_addr, const char *original_addr)\n{\n\tportpingprobe_orchestrator_args_t *args;\n\n\t// Ensure we are ready to start a new probe before we proceed.\n\tif (NET_GetPortPingProbeStatus() != PORTPINGPROBE_READY)\n\t{\n\t\treturn;\n\t}\n\n\t// Set status to PROBING to ensure we aren't launching any more probes\n\t// when we're already working on one.\n\tNET_SetPortPingProbeStatus(PORTPINGPROBE_PROBING);\n\n\t// We need to allocate and copy the target and original addresses since\n\t// they will go out of scope as soon as the connect function ends. We'll\n\t// free the allocations once the worker function completes.\n\targs = Q_malloc(sizeof(portpingprobe_orchestrator_args_t));\n\targs->target_addr = Q_strdup(target_addr);\n\targs->original_addr = Q_strdup(original_addr);\n\n\tif (Sys_CreateDetachedThread(NET_PortPingProbeOrchestrator, (void *)args) < 0)\n\t{\n\t\tCom_Printf(\"NET_PortPingProbe: Unable to launch orchestrator thread\\n\");\n\t}\n}\n\nvoid NET_SetPortPingProbeStatus(const portpingprobe_status_t status)\n{\n\tSDL_AtomicSet(&portpingprobe_status, (int)status);\n}\n\nportpingprobe_status_t NET_GetPortPingProbeStatus(void)\n{\n\treturn (portpingprobe_status_t)SDL_AtomicGet(&portpingprobe_status);\n}\n\nint NET_GetPortPingProbeProgress(void)\n{\n\treturn ((float)SDL_AtomicGet(&portpingprobe_progress) / (cl_portpingprobe_probes.integer * cl_portpingprobe_port_probes.integer)) * 100;\n}\n"
  },
  {
    "path": "src/net.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\n*/\n\n// net.h -- quake's interface to the networking layer\n\n#ifndef __NET_H__\n#define __NET_H__\n\n#include <errno.h>\n\n#define MAX_DUPLICATE_PACKETS (3)\n\n#ifdef _WIN32\n\n//\n// OS specific includes.\n//\n\n#include <winsock2.h>\n#include <ws2tcpip.h>\n\n//\n// OS specific types definition.\n//\n\ntypedef int socklen_t;\ntypedef SOCKET socket_t;\n\n//\n// OS specific definitions.\n//\n\n#ifdef EWOULDBLOCK\n#undef EWOULDBLOCK\n#endif\n#ifdef EMSGSIZE\n#undef EMSGSIZE\n#endif\n#ifdef ECONNRESET\n#undef ECONNRESET\n#endif\n#ifdef ECONNABORTED\n#undef ECONNABORTED\n#endif\n#ifdef ECONNREFUSED\n#undef ECONNREFUSED\n#endif\n#ifdef EADDRNOTAVAIL\n#undef EADDRNOTAVAIL\n#endif\n#ifdef EAFNOSUPPORT\n#undef EAFNOSUPPORT\n#endif\n\n#define EWOULDBLOCK     WSAEWOULDBLOCK\n#define EMSGSIZE        WSAEMSGSIZE\n#define ECONNRESET      WSAECONNRESET\n#define ECONNABORTED    WSAECONNABORTED\n#define ECONNREFUSED    WSAECONNREFUSED\n#define EADDRNOTAVAIL   WSAEADDRNOTAVAIL\n#define EAFNOSUPPORT    WSAEAFNOSUPPORT\n#define qerrno          WSAGetLastError()\n#else //_WIN32\n\n//\n// OS specific includes.\n//\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netinet/tcp.h>\n#include <netdb.h>\n#include <sys/ioctl.h>\n#include <sys/uio.h>\n#include <arpa/inet.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#ifdef __sun__\n#include <sys/filio.h>\n#endif //__sun__\n\n#ifdef NeXT\n#include <libc.h>\n#endif //NeXT\n\n//\n// OS specific types definition.\n//\n\ntypedef int socket_t;\n\n//\n// OS specific definitions.\n//\n\n#define qerrno \t\t\terrno\n\n#define closesocket close\n#define ioctlsocket ioctl\n\n#endif //_WIN32\n\n//\n// common definitions.\n//\n\n// winsock2.h defines INVALID_SOCKET as (SOCKET)(~0)\n// SOCKET is 64 bits on x86_64 while int may still be 32 bit wide\n#if defined _WIN32 && defined INVALID_SOCKET\n#undef INVALID_SOCKET\n#endif\n\n#ifndef INVALID_SOCKET\n#define INVALID_SOCKET  -1\n#endif\n\n#ifndef SOCKET_ERROR\n#define SOCKET_ERROR    -1\n#endif\n\n#define PORT_ANY ((unsigned short int)0xFFFF)\n\ntypedef enum {NA_INVALID, NA_LOOPBACK, NA_IP} netadrtype_t;\n\ntypedef enum {NS_CLIENT, NS_SERVER} netsrc_t;\n\ntypedef struct {\n\tnetadrtype_t    type;\n\n\tbyte            ip[4];\n\n\tunsigned short  port;\n} netadr_t;\n\nextern\tnetadr_t\tnet_local_sv_ipadr;\nextern\tnetadr_t\tnet_local_sv_tcpipadr;\nextern\tnetadr_t\tnet_local_cl_ipadr;\n\nextern\tnetadr_t\tnet_from; // address of who sent the packet\nextern\tsizebuf_t\tnet_message;\n\n#define MAX_UDP_PACKET (MAX_MSGLEN*2) // one more than msg + header\n\n// convert netadrt_t to sockaddr_storage.\nvoid\tNetadrToSockadr (const netadr_t *a, struct sockaddr_storage *s);\n// convert sockaddr_storage to netadrt_t.\nvoid\tSockadrToNetadr (const struct sockaddr_storage *s, netadr_t *a);\n\n// compare netart_t.\nqbool\tNET_CompareAdr (const netadr_t a, const netadr_t b);\n// compare netart_t, ignore port.\nqbool\tNET_CompareBaseAdr (const netadr_t a, const netadr_t b);\n// print netadr_t as string, xxx.xxx.xxx.xxx:xxxxx notation.\nchar\t*NET_AdrToString (const netadr_t a);\n// print netadr_t as string, port skipped, xxx.xxx.xxx.xxx notation.\nchar\t*NET_BaseAdrToString (const netadr_t a);\n// convert/resolve IP/DNS to netadr_t.\nqbool\tNET_StringToAdr (const char *s, netadr_t *a);\n\nvoid\tNET_Init (void);\nvoid\tNET_Shutdown (void);\nvoid\tNET_InitClient (void);\nvoid\tNET_InitServer (void);\nvoid\tNET_CloseServer (void);\nqbool\tNET_GetPacket (netsrc_t sock);\nvoid\tNET_SendPacket (netsrc_t sock, int length, void *data, netadr_t to);\n\nvoid\tNET_GetLocalAddress (int socket, netadr_t *out);\n\nvoid\tNET_ClearLoopback (void);\nqbool\tNET_Sleep(int msec, qbool stdinissocket);\n\n// GETER: return port of UDP server socket.\nint\t\tNET_UDPSVPort (void);\n\n// GETER: return client/server UDP/TCP socket.\nint\t\tNET_GetSocket(netsrc_t netsrc, qbool tcp);\n\n// open server TCP socket.\nvoid\tNET_InitServer_TCP(unsigned short int port);\n\n// UTILITY: set KEEPALIVE option on TCP socket (useful for faster timeout detection).\nqbool \tTCP_Set_KEEPALIVE(int sock);\n// UTILITY: open TCP socket for remove address (useful for client connection).\nint\t\tTCP_OpenStream (netadr_t remoteaddr);\n// UTILITY: open TCP listen socket (useful for server).\nint\t\tTCP_OpenListenSocket (unsigned short int port);\n// UTILITY: open UDP listen socket (useful for server).\nint\t\tUDP_OpenSocket (unsigned short int port);\n//============================================================================\n\n//\n// netchan related.\n//\n\n#define\tOLD_AVG\t\t0.99\t\t// total = oldtotal*OLD_AVG + new*(1-OLD_AVG)\n\n#define\tMAX_LATENT\t32\n\ntypedef struct {\n\tnetsrc_t\tsock;\n\n\tqbool\t\tfatal_error;\n\n\tint\t\t\tdropped;\t\t\t// between last packet and previous\n\n\tfloat\t\tlast_received;\t\t// for timeouts\n\n\t// the statistics are cleared at each client begin, because\n\t// the server connecting process gives a bogus picture of the data\n\tfloat\t\tframe_latency;\t\t// rolling average\n\tfloat\t\tframe_rate;\n\n\tint\t\t\tdrop_count;\t\t\t// dropped packets, cleared each level\n\tint\t\t\tgood_count;\t\t\t// cleared each level\n\n\tnetadr_t\tremote_address;\n\tint\t\t\tqport;\n\n\t// bandwidth estimator\n\tdouble\t\tcleartime;\t\t\t// if curtime > nc->cleartime, free to go\n\tdouble\t\trate;\t\t\t\t// seconds / byte\n\n\tint         dupe;               // duplicate packets to send (0 = no duplicates, as normal)\n\n\t// sequencing variables\n\tint\t\t\tincoming_sequence;\n\tint\t\t\tincoming_acknowledged;\n\tint\t\t\tincoming_reliable_acknowledged; // single bit\n\n\tint\t\t\tincoming_reliable_sequence; // single bit, maintained local\n\n\tint\t\t\toutgoing_sequence;\n\tint\t\t\treliable_sequence;\t// single bit\n\tint\t\t\tlast_reliable_sequence; // sequence number of last send\n\n\t// reliable staging and holding areas\n\tsizebuf_t\tmessage;\t\t\t// writing buffer to send to server\n\tbyte\t\tmessage_buf[MAX_MSGLEN];\n\n\tint\t\t\treliable_length;\n\tbyte\t\treliable_buf[MAX_MSGLEN]; // unacked reliable message\n\n\t// time and size data to calculate bandwidth\n\tint\t\t\toutgoing_size[MAX_LATENT];\n\tdouble\t\toutgoing_time[MAX_LATENT];\n} netchan_t;\n\nvoid Netchan_Init (void);\nvoid Netchan_Transmit (netchan_t *chan, int length, byte *data);\nvoid Netchan_OutOfBand (netsrc_t sock, netadr_t adr, int length, byte *data);\nvoid Netchan_OutOfBandPrint (netsrc_t sock, netadr_t adr, char *format, ...);\nqbool Netchan_Process (netchan_t *chan);\nvoid Netchan_Setup (netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int mtu);\n\nqbool Netchan_CanPacket (netchan_t *chan);\nqbool Netchan_CanReliable (netchan_t *chan);\n\ntypedef enum {\n\tPORTPINGPROBE_READY,\n\tPORTPINGPROBE_PROBING,\n\tPORTPINGPROBE_COMPLETED,\n\tPORTPINGPROBE_ABORT\n} portpingprobe_status_t;\n\nqbool IsPortPingProbeEnabled(void);\nvoid NET_PortPingProbe(const char *target_addr, const char *original_addr);\nvoid NET_SetPortPingProbeStatus(const portpingprobe_status_t status);\nportpingprobe_status_t NET_GetPortPingProbeStatus(void);\nint NET_GetPortPingProbeProgress(void);\n\n#endif /* !__NET_H__ */\n"
  },
  {
    "path": "src/net_chan.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\n*/\n\n#ifdef SERVERONLY\n#include \"qwsvdef.h\"\n#else\n#include <time.h>\n#include \"quakedef.h\"\n#include \"server.h\"\n#endif\n\n#define\tPACKET_HEADER 8\n\n/*\n\npacket header\n-------------\n31\tsequence\n1\tdoes this message contain a reliable payload\n31\tacknowledge sequence\n1\tacknowledge receipt of even/odd message\n16  qport\n\nThe remote connection never knows if it missed a reliable message, the\nlocal side detects that it has been dropped by seeing a sequence acknowledge\nhigher than the last reliable sequence, but without the correct even/odd\nbit for the reliable set.\n\nIf the sender notices that a reliable message has been dropped, it will be\nretransmitted.  It will not be retransmitted again until a message after\nthe retransmit has been acknowledged and the reliable still failed to get there.\n\nIf the sequence number is -1, the packet should be handled without a netcon.\n\nThe reliable message can be added to at any time by doing\nMSG_Write* (&netchan->message, <data>).\n\nIf the message buffer is overflowed, either by a single message, or by\nmultiple frames worth piling up while the last reliable transmit goes\nunacknowledged, the netchan signals a fatal error.\n\nReliable messages are always placed first in a packet, then the unreliable\nmessage is included if there is sufficient room.\n\nTo the receiver, there is no distinction between the reliable and unreliable\nparts of the message, they are just processed out as a single larger message.\n\nIllogical packet sequence numbers cause the packet to be dropped, but do\nnot kill the connection.  This, combined with the tight window of valid\nreliable acknowledgement numbers provides protection against malicious\naddress spoofing.\n\nThe qport field is a workaround for bad address translating routers that\nsometimes remap the client's source port on a packet during gameplay.\n\nIf the base part of the net address matches and the qport matches, then the\nchannel matches even if the IP port differs.  The IP port should be updated\nto the new value before sending out any replies.\n\n*/\n\ncvar_t  showpackets    = {\"showpackets\", \"0\"};\ncvar_t  showdrop       = {\"showdrop\", \"0\"};\n#ifndef SERVERONLY\ncvar_t  qport          = {\"qport\", \"0\"};\n#ifndef CLIENTONLY\ncvar_t  sv_showpackets = {\"sv_showpackets\", \"0\"};\ncvar_t  sv_showdrop    = {\"sv_showdrop\", \"0\"};\n#endif\n#endif\n\n#ifdef SERVERONLY\n#define CHAN_IDENTIFIER(sockType) \" [s]\"\n#define ShowPacket(src,packet_type) (showpackets.integer == 1 || showpackets.integer == packet_type)\n#define ShowDrop(src) (showdrop.integer)\n#elif CLIENTONLY\n#define CHAN_IDENTIFIER(sockType) \" [c]\"\n#define ShowPacket(src,packet_type) (showpackets.integer == 1 || showpackets.integer == packet_type)\n#define ShowDrop(src) (showdrop.integer)\n#else\n#define CHAN_IDENTIFIER(sockType) (sockType == NS_SERVER ? \" [s]\" : \" [c]\")\n\n// Use the sv_ options for internal server\nstatic qbool ShowPacket(netsrc_t src, int packet_type)\n{\n\tif (src == NS_SERVER) {\n\t\treturn sv_showpackets.integer == 1 || sv_showpackets.integer == packet_type;\n\t}\n\telse {\n\t\treturn showpackets.integer == 1 || showpackets.integer == packet_type;\n\t}\n}\n\nstatic qbool ShowDrop(netsrc_t src)\n{\n\tif (src == NS_SERVER) {\n\t\treturn sv_showdrop.integer;\n\t}\n\telse {\n\t\treturn showdrop.integer;\n\t}\n}\n#endif\n\n#define PACKET_SENDING 2\n#define PACKET_RECEIVING 3\n\n/*\n===============\nNetchan_Init\n\n===============\n*/\nvoid Netchan_Init(void)\n{\n#ifndef SERVERONLY\n\tint\t\tport = 0xffff;\n\n\tsrand((unsigned)time(NULL));\n\tport &= rand();\n#endif // SERVERONLY\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\n\tCvar_Register(&showpackets);\n\tCvar_Register(&showdrop);\n\n#ifndef SERVERONLY\n\tCvar_SetCurrentGroup(CVAR_GROUP_NO_GROUP);\n\tCvar_Register(&qport);\n\tCvar_SetValue(&qport, port);\n\n#ifndef CLIENTONLY\n\tCvar_Register(&sv_showpackets);\n\tCvar_Register(&sv_showdrop);\n#endif\n#endif\n\n\tCvar_ResetCurrentGroup();\n}\n\n/*\n===============\nNetchan_OutOfBand\n\nSends an out-of-band datagram\n================\n*/\nvoid Netchan_OutOfBand (netsrc_t sock, netadr_t adr, int length, byte *data)\n{\n\tsizebuf_t send;\n\tbyte send_buf[MAX_MSGLEN + PACKET_HEADER];\n\n\t// write the packet header\n\tSZ_Init (&send, send_buf, sizeof(send_buf));\n\n\tMSG_WriteLong (&send, -1);\t// -1 sequence means out of band\n\tSZ_Write (&send, data, length);\n\n\t// send the datagram\n#ifndef SERVERONLY\n\t//zoid, no input in demo playback mode\n\tif (!cls.demoplayback)\n#endif\n\t\tNET_SendPacket (sock, send.cursize, send.data, adr);\n}\n\n/*\n===============\nNetchan_OutOfBandPrint\n\nSends a text message in an out-of-band datagram\n================\n*/\nvoid Netchan_OutOfBandPrint (netsrc_t sock, netadr_t adr, char *format, ...)\n{\n\tva_list argptr;\n\tchar string[8192];\n\n\tva_start (argptr, format);\n\tvsnprintf (string, sizeof(string), format, argptr);\n\tva_end (argptr);\n\n\tNetchan_OutOfBand (sock, adr, strlen(string), (byte *)string);\n}\n\n/*\n==============\nNetchan_Setup\n\ncalled to open a channel to a remote system\n==============\n*/\nvoid Netchan_Setup (netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int mtu)\n{\n\tif (mtu < 1) {\n\t\tmtu = (int)sizeof(chan->message_buf); // OLD way, backward compatibility.\n\t}\n\telse {\n\t\tmtu -= PACKET_HEADER; // new way.\n\t}\n\n\tmemset (chan, 0, sizeof (*chan));\n\n\tchan->sock = sock;\n\tchan->remote_address = adr;\n\tchan->qport = qport;\n\n\tchan->last_received = curtime;\n\tchan->rate = 1.0/2500;\n\n\tSZ_InitEx (&chan->message, chan->message_buf, bound(min(MIN_MTU, (int)sizeof(chan->message_buf)), mtu, (int)sizeof(chan->message_buf)), true);\n}\n\n/*\n===============\nNetchan_CanPacket\n\nReturns true if the bandwidth choke isn't active\n================\n*/\n#ifndef SERVERONLY\n#define MAX_BACKUP  200\n#else\n#define\tMAX_BACKUP\t400\n#endif\nqbool Netchan_CanPacket (netchan_t *chan)\n{\n#ifndef SERVERONLY\n\tif (chan->remote_address.type == NA_LOOPBACK)\n\t\treturn true; // unlimited bandwidth for local client\n#endif\n\n\treturn (chan->cleartime < curtime + MAX_BACKUP * chan->rate) ? true : false;\n}\n\n/*\n===============\nNetchan_CanReliable\n\nReturns true if the bandwidth choke isn't \n================\n*/\nqbool Netchan_CanReliable (netchan_t *chan)\n{\n\tif (chan->reliable_length)\n\t\treturn false; // waiting for ack\n\treturn Netchan_CanPacket (chan);\n}\n\n/*\n===============\nNetchan_Transmit\n\ntries to send an unreliable message to a connection, and handles the\ntransmition / retransmition of the reliable messages.\n\nA 0 length will still generate a packet and deal with the reliable messages.\n================\n*/\nvoid Netchan_Transmit (netchan_t *chan, int length, byte *data)\n{\n\tsizebuf_t send;\n\tbyte send_buf[MAX_MSGLEN + PACKET_HEADER];\n\tqbool send_reliable;\n\tunsigned w1, w2;\n\tint i;\n\tstatic double last_error_time = 0;\n\n\t// check for message overflow\n\tif (chan->message.overflowed) {\n\t\tchan->fatal_error = true; //FIXME: THIS DOES NOTHING\n\t\tif (last_error_time - curtime > 5 || developer.value) {\n\t\t\tCon_Printf (\"%s:Outgoing message overflow\\n\", NET_AdrToString (chan->remote_address));\n\t\t\tlast_error_time = curtime;\n\t\t}\n\t\treturn;\n\t}\n\n\t// if the remote side dropped the last reliable message, resend it\n\tsend_reliable = false;\n\n\tif (chan->incoming_acknowledged > chan->last_reliable_sequence && chan->incoming_reliable_acknowledged != chan->reliable_sequence)\n\t\tsend_reliable = true;\n\n\t// if the reliable transmit buffer is empty, copy the current message out\n\tif (!chan->reliable_length && chan->message.cursize) {\n\t\tmemcpy (chan->reliable_buf, chan->message_buf, chan->message.cursize);\n\t\tchan->reliable_length = chan->message.cursize;\n\t\tchan->message.cursize = 0;\n\t\tchan->reliable_sequence ^= 1;\n\t\tsend_reliable = true;\n\t}\n\n\t// write the packet header\n\tSZ_Init (&send, send_buf, min(chan->message.maxsize + PACKET_HEADER, (int)sizeof(send_buf)));\n\n\tw1 = chan->outgoing_sequence | (send_reliable<<31);\n\tw2 = chan->incoming_sequence | (chan->incoming_reliable_sequence<<31);\n\n\tchan->outgoing_sequence++;\n\n\tMSG_WriteLong (&send, w1);\n\tMSG_WriteLong (&send, w2);\n\n#ifndef SERVERONLY\n\t// send the qport if we are a client\n\tif (chan->sock == NS_CLIENT)\n\t\tMSG_WriteShort (&send, chan->qport);\n#endif\n\n\t// copy the reliable message to the packet first\n\tif (send_reliable) {\n\t\tSZ_Write (&send, chan->reliable_buf, chan->reliable_length);\n\t\tchan->last_reliable_sequence = chan->outgoing_sequence;\n\t}\n\n\t// add the unreliable part if space is available\n\tif (send.maxsize - send.cursize >= length) {\n\t\tSZ_Write(&send, data, length);\n\t}\n\n\t// send the datagram\n\ti = chan->outgoing_sequence & (MAX_LATENT-1);\n\tchan->outgoing_size[i] = send.cursize;\n\tchan->outgoing_time[i] = curtime;\n\n#ifndef SERVERONLY\n\t//zoid, no input in demo playback mode\n\tif (!cls.demoplayback)\n#endif\n\t{\n\t\tfor (i = 0; i <= chan->dupe; ++i) {\n\t\t\tNET_SendPacket(chan->sock, send.cursize, send.data, chan->remote_address);\n\t\t}\n\t}\n\n\tif (chan->cleartime < curtime) {\n\t\tchan->cleartime = curtime + send.cursize * i * chan->rate;\n\t}\n\telse {\n\t\tchan->cleartime += send.cursize * i * chan->rate;\n\t}\n\n#ifndef CLIENTONLY\n\tif (chan->sock == NS_SERVER && sv.paused)\n\t\tchan->cleartime = curtime;\n#endif\n\n\tif (ShowPacket(chan->sock, PACKET_SENDING)) {\n#ifndef SERVERONLY\n\t\tPrint_flags[Print_current] |= PR_TR_SKIP;\n#endif\n\t\tCon_Printf (\"%.1f --> s=%i(%i) a=%i(%i) %i%s\\n\"\n\t\t            , cls.demopackettime * 1000, chan->outgoing_sequence\n\t\t            , send_reliable\n\t\t            , chan->incoming_sequence\n\t\t            , chan->incoming_reliable_sequence\n\t\t            , send.cursize, CHAN_IDENTIFIER(chan->sock)\n\t\t);\n\t}\n\n}\n\n/*\n=================\nNetchan_Process\n\ncalled when the current net_message is from remote_address\nmodifies net_message so that it points to the packet payload\n=================\n*/\nqbool Netchan_Process (netchan_t *chan)\n{\n\tunsigned sequence, sequence_ack;\n\tunsigned reliable_ack, reliable_message;\n\n#ifdef SERVERONLY\n\tif (!NET_CompareAdr (net_from, chan->remote_address))\n\t\treturn false;\n#endif\n\n\t// get sequence numbers\n\tMSG_BeginReading ();\n\tsequence = MSG_ReadLong ();\n\tsequence_ack = MSG_ReadLong ();\n\n\t// read the qport if we are a server\n\tif (chan->sock == NS_SERVER)\n\t\tMSG_ReadShort ();\n\n\treliable_message = sequence >> 31;\n\treliable_ack = sequence_ack >> 31;\n\n\tsequence &= ~(1 << 31);\n\tsequence_ack &= ~(1 << 31);\n\n\tif (ShowPacket(chan->sock, PACKET_RECEIVING)) {\n#ifndef SERVERONLY\n\t\tPrint_flags[Print_current] |= PR_TR_SKIP;\n#endif\n\t\tCon_Printf (\"%.1f <-- s=%i(%i) a=%i(%i) %i%s\\n\"\n\t\t            , cls.demopackettime * 1000, sequence\n\t\t            , reliable_message\n\t\t            , sequence_ack\n\t\t            , reliable_ack\n\t\t            , net_message.cursize, CHAN_IDENTIFIER(chan->sock)\n\t\t);\n\t}\n\n\t// discard stale or duplicated packets\n\tif (sequence <= (unsigned)chan->incoming_sequence) {\n\t\tif (ShowDrop(chan->sock)) {\n#ifndef SERVERONLY\n\t\t\tPrint_flags[Print_current] |= PR_TR_SKIP;\n#endif\n\t\t\tCon_Printf (\"%s:Out of order packet %i at %i\\n\"\n\t\t\t            , NET_AdrToString (chan->remote_address)\n\t\t\t            , sequence\n\t\t\t            , chan->incoming_sequence);\n\t\t}\n\t\treturn false;\n\t}\n\n\t// dropped packets don't keep the message from being used\n\tchan->dropped = sequence - (chan->incoming_sequence+1);\n\tif (chan->dropped > 0) {\n\t\tchan->drop_count += 1;\n\n\t\tif (ShowDrop(chan->sock)) {\n#ifndef SERVERONLY\n\t\t\tPrint_flags[Print_current] |= PR_TR_SKIP;\n#endif\n\t\t\tCon_Printf (\"%s:Dropped %i packets at %i\\n\"\n\t\t\t            , NET_AdrToString (chan->remote_address)\n\t\t\t            , chan->dropped\n\t\t\t            , sequence);\n\t\t}\n\t}\n\n\t// if the current outgoing reliable message has been acknowledged\n\t// clear the buffer to make way for the next\n\tif (reliable_ack == (unsigned)chan->reliable_sequence) {\n\t\tchan->reliable_length = 0;\t// it has been received\n\t}\n\n\t// if this message contains a reliable message, bump incoming_reliable_sequence\n\tchan->incoming_sequence = sequence;\n\tchan->incoming_acknowledged = sequence_ack;\n\tchan->incoming_reliable_acknowledged = reliable_ack;\n\tif (reliable_message) {\n\t\tchan->incoming_reliable_sequence ^= 1;\n\t}\n\n\t// the message can now be read from the current message pointer\n\t// update statistics counters\n\tchan->frame_latency = chan->frame_latency * OLD_AVG +\n\t\t(chan->outgoing_sequence - sequence_ack) * (1.0 - OLD_AVG);\n\tchan->frame_rate = chan->frame_rate * OLD_AVG +\n\t\t(curtime - chan->last_received) * (1.0 - OLD_AVG);\n\tchan->good_count += 1;\n\n\tchan->last_received = curtime;\n\n\treturn true;\n}\n"
  },
  {
    "path": "src/parser.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/**\n\t\\file\n\n\t\\brief\n\tArithmetic expression evaluator\n    \n\t\\author johnnycz\n**/\n\n// fixme: most probably there are some memory leaks when working with strings\n\n#include <stdlib.h>\n#include <math.h>\n#include <ctype.h>\n#include <string.h>\n#include <wctype.h>\n#include \"quakedef.h\"\n#include \"q_shared.h\"\n#include \"utils.h\"\n#include \"pcre2.h\"\n#include \"parser.h\"\n\n#undef Q_malloc\n#undef Q_free\n#undef Q_strdup\n#define Q_malloc malloc\n#define Q_free free\n#define Q_strdup strdup\n\n#define GLOBAL /* */\n#define LOCAL static\n\n#define REAL_COMPARE_EPSILON\t0.00000001\n#define MAX_NUMBER_LENGTH\t\t32\n#define BOOL_TRUE\t1\n#define BOOL_FALSE\t0\n\n#define ERR_INVALID_TOKEN   1\n#define ERR_UNEXPECTED_CHAR 2\n#define ERR_TYPE_MISMATCH\t3\n#define ERR_ZERO_DIV\t\t4\n#define ERR_NOTIMPL\t\t\t5\n#define ERR_MALFORMED_NUM   6\n#define ERR_OUT_OF_MEM\t\t7\n#define ERR_INTERNAL        8\n#define ERR_REGEXP\t\t\t9\n#define ERR_INVALID_ARG     10\n\n//\n// Tokens\n//\n#define TK_EOF      0\n#define TK_INVALID  1\n// double, integer\n#define TK_DOUBLE   100\n#define TK_INTEGER\t101\n// +, -\n#define TK_PLUS     200\n#define TK_MINUS    201\n// *, /, mod\n#define TK_ASTERISK 250\n#define TK_SLASH    251\n#define TK_MOD\t\t252\n#define TK_XOR      253\n#define TK_DIV      254\n// parentheses\n#define TK_BR_O     300\n#define TK_BR_C     301\n// variable\n#define TK_VAR      500\n#define TK_STR      501\n// comparisions\n#define TK_LT\t\t600\n#define TK_LE\t\t601\n#define TK_EQ       602\n#define TK_EQ2\t\t603\n#define TK_GE       604\n#define TK_GT       605\n#define TK_NE       606\n#define TK_ISIN     607\n#define TK_NISIN    608\n#define TK_REEQ     609\n#define TK_RENE     610\n// logic\n#define TK_AND      700\n#define TK_OR       701\n// string\n#define TK_STRLEN   800\n#define TK_INT      801\n#define TK_SUBSTR   802\n#define TK_POS      804\n#define TK_TOBROWN  805\n#define TK_TOWHITE  806\n// misc\n#define TK_COMMA    900\n\ntypedef struct {\n    const char *string;\t\t\t// input string\n    int pos;\t\t\t\t\t// current reading position in the input string\n    int lookahead;\t\t\t\t// next token in the string\n    int error;\t\t\t\t\t// first error\n\tint warning;\t\t\t\t// last warning\n\tvariable_val_fnc varfnc;\t// pointer to a function returning variable values\n\tsubpatterns_report_fnc re_patfnc;\n\t\t\t\t\t\t\t\t// a function where we report regexp-found subpatterns\n} expr_parser_t, *EParser;\n\n#define CURCHAR(p) (p->string[p->pos])\n\nLOCAL void SetError(EParser p, int error)\n{\n\tif (p->error == EXPR_EVAL_SUCCESS)\n\t{\n\t\tp->error = error;\n\t} // else keep the previous error\n}\n\nLOCAL void FreeIfStr(const expr_val *e)\n{\n\tif (e->type == ET_STR) {\n\t\tQ_free(e->s_val);\n\t}\n}\n\nLOCAL double Get_Double(const expr_val e)\n{\n\tswitch (e.type) {\n\tcase ET_INT: return e.i_val;\n\tcase ET_DBL: return e.d_val;\n\tcase ET_BOOL: return e.b_val;\n\tcase ET_STR: default: return 0;\n\t}\n}\n\nLOCAL int Get_Integer(const expr_val e)\n{\n\tswitch (e.type) {\n\tcase ET_INT: return e.i_val;\n\tcase ET_DBL: return (int) e.d_val;\n\tcase ET_BOOL:return e.b_val;\n\tcase ET_STR: default: return 0;\n\t}\n}\n\nLOCAL int Get_Bool(const expr_val e)\n{\n\tswitch (e.type) {\n\tcase ET_INT: return e.i_val;\n\tcase ET_DBL: return (int) e.d_val;\n\tcase ET_BOOL:return e.b_val;\n\tcase ET_STR: {\n\t\tint retval = (*e.s_val) ? BOOL_TRUE : BOOL_FALSE;\n\t\tQ_free(e.s_val);\n\t\treturn retval;\n\t\t}\n\tdefault: assert(false); return 0;\n\t}\n}\n\nGLOBAL expr_val Get_Expr_Double(double v)\n{\n\texpr_val t = {0};\n\tt.type = ET_DBL;\n\tt.d_val = v;\n\treturn t;\n}\n\nGLOBAL expr_val Get_Expr_String(const char *v)\n{\n\texpr_val t = {0};\n\tt.type = ET_STR;\n\tt.s_val = Q_strdup(v);\n\treturn t;\n}\n\nGLOBAL expr_val Get_Expr_Integer(int v)\n{\n\texpr_val t = {0};\n\tt.type = ET_INT;\n\tt.i_val = v;\n\treturn t;\n}\n\nGLOBAL expr_val Get_Expr_Bool(int v)\n{\n\texpr_val t = {0};\n\tt.type = ET_BOOL;\n\tt.b_val = v ? BOOL_TRUE : BOOL_FALSE;\n\treturn t;\n}\n\nGLOBAL expr_val Get_Expr_Dummy(void)\n{\n\texpr_val t = {0};\n\tt.type = ET_INT;\n\tt.i_val = 0;\n\treturn t;\n}\n\nLOCAL int Compare_Double(double a, double b)\n{\n\tdouble diff = fabs(a-b);\n\tif (diff < REAL_COMPARE_EPSILON)\n\t\treturn 0; // a == b\n\telse if (a > b)\n\t\treturn 1;\n\telse\n\t\treturn -1;\n}\n\nLOCAL expr_val ToString(EParser p, const expr_val e)\n{\n\texpr_val r = {0};\n\tswitch (e.type) {\n\tcase ET_STR: r = e; break;\n\tcase ET_INT:\n\t\tr.s_val = (char *) malloc(MAX_NUMBER_LENGTH);\n\t\tif (!r.s_val) { SetError(p, ERR_OUT_OF_MEM); return Get_Expr_Dummy(); }\n\t\tsnprintf(r.s_val, MAX_NUMBER_LENGTH, \"%i\", e.i_val);\n\t\tbreak;\n\n\tcase ET_DBL:\n\t\tr.s_val = (char *) malloc(MAX_NUMBER_LENGTH);\n\t\tif (!r.s_val) { SetError(p, ERR_OUT_OF_MEM); return Get_Expr_Dummy(); }\n\t\tsnprintf(r.s_val, MAX_NUMBER_LENGTH, \"%f\", e.d_val);\n\t\tbreak;\n\n\tcase ET_BOOL:\n\t\tr.s_val = (char *) malloc(6);\n\t\tif (!r.s_val) { SetError(p, ERR_OUT_OF_MEM); return Get_Expr_Dummy(); }\n\t\tsnprintf(r.s_val, 6, \"%s\", e.b_val ? \"true\" : \"false\");\n\t\tbreak;\n\t}\n\n\treturn r;\n}\n\nLOCAL expr_val Concat(EParser p, const expr_val e1, const expr_val e2)\n{\n\tsize_t len;\n\texpr_val ret = {0};\n\tif (e1.type != ET_STR || e2.type != ET_STR) {\n\t\tSetError(p, ERR_INTERNAL);\n\t\treturn Get_Expr_Dummy();\n\t}\n\n\tlen = strlen(e1.s_val) + strlen(e2.s_val) + 1;\n\tret.type = ET_STR;\n\tret.s_val = malloc(len);\n\tif (!ret.s_val) {\n\t\tSetError(p, ERR_OUT_OF_MEM);\n\t\tFreeIfStr(&e1);\n\t\tFreeIfStr(&e2);\n\t\treturn Get_Expr_Dummy();\n\t}\n\tstrlcpy(ret.s_val, e1.s_val, len);\n\tstrlcat(ret.s_val, e2.s_val, len);\n\tQ_free(e1.s_val);\n\tQ_free(e2.s_val);\n\n\treturn ret;\n}\n\nLOCAL expr_val operator_plus(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val ret = {0};\n\n\tswitch (e1.type) {\n\tcase ET_INT: switch (e2.type) {\n\t\tcase ET_INT:\tret.type = ET_INT; ret.i_val = e1.i_val + e2.i_val; break;\n\t\tcase ET_DBL:\tret.type = ET_DBL; ret.d_val = e1.i_val + e2.d_val; break;\n\t\tcase ET_BOOL:\tret.type = ET_INT; ret.i_val = e1.i_val + e2.b_val; break;\n\t\tcase ET_STR:\tSetError(p, ERR_TYPE_MISMATCH); break;\n\t\t} break;\n\n\tcase ET_DBL: switch (e2.type) {\n\t\tcase ET_INT:\tret.type = ET_DBL; ret.d_val = e1.d_val + e2.i_val; break;\n\t\tcase ET_DBL:\tret.type = ET_DBL; ret.d_val = e1.d_val + e2.d_val; break;\n\t\tcase ET_BOOL:\tret.type = ET_DBL; ret.d_val = e1.d_val + e2.b_val; break;\n\t\tcase ET_STR:\tSetError(p, ERR_TYPE_MISMATCH); break;\n\t\t} break;\n\n\tcase ET_BOOL: switch (e2.type) {\n\t\tcase ET_INT:\tret.type = ET_INT; ret.i_val = e1.b_val + e2.i_val; break;\n\t\tcase ET_DBL:\tret.type = ET_DBL; ret.d_val = e1.b_val + e2.d_val; break;\n\t\tcase ET_BOOL:\tret.type = ET_BOOL;ret.b_val = e1.b_val + e2.b_val; break;\n\t\tcase ET_STR:\tSetError(p, ERR_TYPE_MISMATCH); break;\n        } break;\n\n\tcase ET_STR: switch (e2.type) {\n\t\tcase ET_STR:\tret = Concat(p, e1, e2); break;\n\t\tdefault:\t\tSetError(p, ERR_TYPE_MISMATCH); break;\n\t\t} break;\n\t}\n\n\treturn ret;\n}\n\nLOCAL expr_val operator_minus (EParser p, const expr_val e1)\n{\n\texpr_val ret = {0};\n\n\tswitch (e1.type) {\n\tcase ET_INT: ret.type = ET_INT; ret.i_val = -e1.i_val; break;\n\tcase ET_DBL: ret.type = ET_DBL; ret.d_val = -e1.d_val; break;\n\tcase ET_BOOL: ret.type = ET_BOOL; ret.b_val = -e1.b_val; break;\n\tcase ET_STR: SetError(p, ERR_TYPE_MISMATCH); break;\n\t}\n\n\treturn ret;\n}\n\nLOCAL expr_val operator_multiply(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val ret = {0};\n\n\tswitch (e1.type) {\n\tcase ET_INT: switch (e2.type) {\n\t\tcase ET_INT: ret.type = ET_INT; ret.i_val = e1.i_val * e2.i_val; break;\n\t\tcase ET_DBL: ret.type = ET_DBL; ret.d_val = e1.i_val * e2.d_val; break;\n\t\tcase ET_BOOL:ret.type = ET_BOOL;ret.b_val = e1.i_val * e2.b_val; break;\n\t\tcase ET_STR: SetError(p, ERR_TYPE_MISMATCH); break;\n\t\t} break;\n\n\tcase ET_DBL: switch (e2.type) {\n\t\tcase ET_INT: ret.type = ET_DBL; ret.d_val = e1.d_val * e2.i_val; break;\n\t\tcase ET_DBL: ret.type = ET_DBL; ret.d_val = e1.d_val * e2.d_val; break;\n\t\tcase ET_BOOL:ret.type = ET_DBL; ret.d_val = e1.d_val * e2.b_val; break;\n\t\tcase ET_STR: SetError(p, ERR_TYPE_MISMATCH); break;\n\t\t} break;\n\n\tcase ET_BOOL: switch (e2.type) {\n\t\tcase ET_INT: ret.type = ET_INT; ret.i_val = e1.b_val * e2.i_val; break;\n\t\tcase ET_DBL: ret.type = ET_DBL; ret.d_val = e1.b_val * e2.d_val; break;\n\t\tcase ET_BOOL:ret.type = ET_BOOL;ret.b_val = e1.b_val * e2.b_val; break;\n\t\tcase ET_STR: SetError(p, ERR_TYPE_MISMATCH); break;\n\t\t} break;\n\n\tcase ET_STR: default: SetError(p, ERR_TYPE_MISMATCH); break;\n\t}\n\n\treturn ret;\n}\n\nLOCAL expr_val operator_modulo(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val ret;\n\n\tif (e1.type == ET_INT && e2.type == ET_INT) {\n\t\tret.type = ET_INT;\n\t\tret.i_val = e1.i_val % e2.i_val;\n\t}\n\telse {\n\t\tSetError(p, ERR_TYPE_MISMATCH);\n\t\tret = Get_Expr_Dummy();\n\t}\n\n\treturn ret;\n}\n\nLOCAL expr_val operator_xor(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val ret;\n\n\tif (e1.type == ET_INT && e2.type == ET_INT) {\n\t\tret.type = ET_INT;\n\t\tret.i_val = e1.i_val ^ e2.i_val;\n\t}\n\telse if (e1.type == ET_BOOL && e2.type == ET_BOOL) {\n\t\tret.type = ET_BOOL;\n\t\tret.b_val = ((e1.b_val ? 1 : 0) ^ (e2.b_val ? 1 : 0)) ? BOOL_TRUE : BOOL_FALSE;\n\t}\n\telse {\n\t\tSetError(p, ERR_TYPE_MISMATCH);\n\t\tret = Get_Expr_Dummy();\n\t}\n\n\treturn ret;\n}\n\nLOCAL expr_val operator_divint(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val ret;\n\n\tif (e1.type == ET_INT && e2.type == ET_INT) {\n\t\tret.type = ET_INT;\n\t\tret.i_val = e1.i_val / e2.i_val;\n\t}\n\telse {\n\t\tSetError(p, ERR_TYPE_MISMATCH);\n\t\tret = Get_Expr_Dummy();\n\t}\n\n\treturn ret;\n}\n\nLOCAL expr_val operator_divide(EParser p, const expr_val e1)\n{\n\tdouble d;\n\texpr_val ret;\n\n\tswitch (e1.type) {\n\tcase ET_INT:\td = e1.i_val; break;\n\tcase ET_DBL:\td = e1.d_val; break;\n\tcase ET_BOOL:\td = e1.b_val; break;\n\tcase ET_STR: \n\tdefault:\n\t\tSetError(p, ERR_TYPE_MISMATCH); \n\t\treturn e1;\n\t}\n\n\tif (d) {\n\t\tret.type = ET_DBL;\n\t\tret.d_val = 1/d;\n\t} else \t{\n\t\tSetError(p, ERR_ZERO_DIV);\n\t\tret = Get_Expr_Dummy();\n\t}\n\n\treturn ret;\n}\n\nLOCAL expr_val operator_strlen(EParser p, const expr_val arg1)\n{\n\texpr_val ret;\n\tret.type = ET_INT;\n\t\n\tif (arg1.type == ET_STR) {\n\t\tret.i_val = strlen(arg1.s_val);\n\t\tQ_free(arg1.s_val);\n\t}\n\telse {\n\t\tSetError(p, ERR_TYPE_MISMATCH);\n\t}\n\n\treturn ret;\n}\n\nLOCAL expr_val operator_substr(EParser p, const expr_val arg1, const expr_val arg2,\n                               const expr_val arg3)\n{\n\texpr_val ret;\n\t\n\tif (arg1.type == ET_STR && arg2.type == ET_INT && arg3.type == ET_INT) {\n\t\tconst char *str = arg1.s_val;\n\t\tsize_t arglen = strlen(str);\n\t\tsize_t pos = arg2.i_val;\n\t\tsize_t len = arg3.i_val;\n\n\t\tif (pos >= 0 && len >= 0) {\n\t\t\tif (len == 0 || pos >= arglen) {\n\t\t\t\tret.type = ET_STR;\n\t\t\t\tret.s_val = Q_strdup(\"\");\n\t\t\t}\n\t\t\telse { // len > 0 && pos < arglen\n\t\t\t\tchar *buf;\n\n\t\t\t\tif (pos + len > arglen) {\n\t\t\t\t\tlen = arglen - pos;\n\t\t\t\t}\n\n\t\t\t\tbuf = (char *) malloc(len + 1);\n\n\t\t\t\tif (buf) {\n\t\t\t\t\tstrlcpy(buf, str + pos, len + 1);\n\t\t\t\t\tret.type = ET_STR;\n\t\t\t\t\tret.s_val = buf;\n\t\t\t\t}\n\t\t\t\telse { // malloc fail\n\t\t\t\t\tSetError(p, ERR_OUT_OF_MEM);\n\t\t\t\t\tret = Get_Expr_Dummy();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse { // pos < 0 || len < 0\n\t\t\tSetError(p, ERR_INVALID_ARG);\n\t\t\tret = Get_Expr_Dummy();\n\t\t}\n\t}\n\telse { // args are not string, int, int\n\t\tSetError(p, ERR_TYPE_MISMATCH);\n\t\tret = Get_Expr_Dummy();\n\t}\n\n\tFreeIfStr(&arg1);\n\tFreeIfStr(&arg2);\n\tFreeIfStr(&arg3);\n\n\treturn ret;\n}\n\nLOCAL expr_val operator_pos(EParser p, const expr_val arg1, const expr_val arg2)\n{\n\texpr_val ret;\n\t\n\tif (arg1.type == ET_STR && arg2.type == ET_STR) {\n\t\tchar *haystack = arg1.s_val;\n\t\tchar *needle = arg2.s_val;\n\t\t\n\t\tchar *find = strstr(haystack, needle);\n\n\t\tret.type = ET_INT;\n\t\tif (find) {\n\t\t\tret.i_val = (int) (find - arg1.s_val);\n\t\t}\n\t\telse {\n\t\t\tret.i_val = -1;\n\t\t}\n\n\t\tQ_free(arg1.s_val);\n\t\tQ_free(arg2.s_val);\n\t}\n\telse {\n\t\tSetError(p, ERR_TYPE_MISMATCH);\n\t\tFreeIfStr(&arg1);\n\t\tFreeIfStr(&arg2);\n\t\tret = Get_Expr_Dummy();\n\t}\n\n\treturn ret;\n}\n\nLOCAL expr_val operator_int(EParser p, const expr_val arg1)\n{\n\texpr_val ret;\n\tret.type = ET_INT;\n\n\tswitch (arg1.type) {\n\t\tcase ET_INT:\n\t\t\tret.i_val = arg1.i_val;\n\t\t\tbreak;\n\t\tcase ET_DBL:\n\t\t\tret.i_val = (int) arg1.d_val;\n\t\t\tbreak;\n\t\tcase ET_STR:\n\t\t\tret.i_val = atoi(arg1.s_val);\n\t\t\tQ_free(arg1.s_val);\n\t\t\tbreak;\n\t\tcase ET_BOOL:\n\t\t\tret.i_val = arg1.b_val ? 1 : 0;\n\t\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\nLOCAL expr_val operator_tobrown(EParser p, const expr_val arg1)\n{\n\texpr_val ret;\n\n\tif (arg1.type == ET_STR) {\n\t\tret.s_val = Q_strdup(arg1.s_val);\n\t\tret.type = ET_STR;\n\t\tCharsToBrown(ret.s_val, ret.s_val + strlen(ret.s_val));\n\t}\n\telse {\n\t\tSetError(p, ERR_TYPE_MISMATCH);\n\t\tret = Get_Expr_Dummy();\n\t}\n\n\tFreeIfStr(&arg1);\n\treturn ret;\n}\n\nLOCAL expr_val operator_towhite(EParser p, const expr_val arg1)\n{\n\texpr_val ret;\n\n\tif (arg1.type == ET_STR) {\n\t\tret.s_val = Q_strdup(arg1.s_val);\n\t\tret.type = ET_STR;\n\t\tCharsToWhite(ret.s_val, ret.s_val + strlen(ret.s_val));\n\t}\n\telse {\n\t\tSetError(p, ERR_TYPE_MISMATCH);\n\t\tret = Get_Expr_Dummy();\n\t}\n\n\tFreeIfStr(&arg1);\n\n\treturn ret;\n}\n\n\ntypedef enum {\n\tCMPRESULT_lt,\n\tCMPRESULT_le,\n\tCMPRESULT_eq,\n\tCMPRESULT_ne,\n\tCMPRESULT_ge,\n\tCMPRESULT_gt\n} cmp_type;\n\n// this will be called if one of the expressions is string\n// and we want to do a comparision on them\nLOCAL expr_val string_check_cmp(EParser p, const expr_val e1, const expr_val e2, cmp_type ctype)\n{\n\texpr_val r = {0};\n\tr.type = ET_BOOL;\n\tr.b_val = BOOL_FALSE;\n\n\tif (e1.type != ET_STR && e2.type != ET_STR)\t{\n\t\tSetError(p, ERR_INTERNAL);\n\t} else if (e1.type != ET_STR) {\n\t\tQ_free(e2.s_val);\n\t\tSetError(p, ERR_TYPE_MISMATCH);\n\t} else if (e2.type != ET_STR) {\n\t\tQ_free(e1.s_val);\n\t\tSetError(p, ERR_TYPE_MISMATCH);\n\t} else {\t// both STR\n\t\tint a = strcmp(e1.s_val, e2.s_val);\n\t\tswitch(ctype) {\n\t\tcase CMPRESULT_lt: r.b_val = a <  0; break;\n\t\tcase CMPRESULT_le: r.b_val = a <= 0; break;\n\t\tcase CMPRESULT_eq: r.b_val = a == 0; break;\n\t\tcase CMPRESULT_ne: r.b_val = a != 0; break;\n\t\tcase CMPRESULT_ge: r.b_val = a >= 0; break;\n\t\tcase CMPRESULT_gt: r.b_val = a >  0; break;\n\t\t}\n\t\tQ_free(e1.s_val);\n\t\tQ_free(e2.s_val);\n\t}\n\treturn r;\n}\n\n// test: \"1==1.0\", \"2==2.0\", \"5/2==2.5\"\nLOCAL expr_val operator_eq(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val ret = {0};\n\tret.type = ET_BOOL;\n\n\tif (e1.type == ET_STR || e2.type == ET_STR)\n\t\treturn string_check_cmp(p, e1, e2, CMPRESULT_eq);\n\n\tswitch(e1.type) {\n\tcase ET_INT: switch (e2.type) {\n\t\tcase ET_INT: ret.b_val = e1.i_val == e2.i_val; break;\n\t\tcase ET_DBL: ret.b_val = !Compare_Double(e1.i_val, e2.d_val); break;\n\t\tcase ET_BOOL: ret.b_val = e2.b_val ? e1.i_val == 1 : e1.i_val == 0; break;\n\t\t// handled above, avoid gcc warning\n\t\tcase ET_STR: break;\n\t\t} break;\n\tcase ET_DBL: switch (e2.type) {\n\t\tcase ET_INT: ret.b_val = !Compare_Double(e1.d_val, e2.i_val); break;\n\t\tcase ET_DBL: ret.b_val = !Compare_Double(e1.d_val, e2.d_val); break;\n\t\tcase ET_BOOL: SetError(p, ERR_TYPE_MISMATCH); break;\n\t\t// handled above, avoid gcc warning\n\t\tcase ET_STR: break;\n\t\t} break;\n\tcase ET_BOOL: switch (e2.type) {\n\t\tcase ET_INT: ret.b_val = e1.b_val ? e2.i_val == 1 : e2.i_val == 0; break;\n\t\tcase ET_DBL: SetError(p, ERR_TYPE_MISMATCH); break;\n\t\tcase ET_BOOL: ret.b_val = e1.b_val == e2.b_val; break;\n\t\t// handled above, avoid gcc warning\n\t\tcase ET_STR: break;\n\t    } break;\n\t// handled above, avoid gcc warning\n\tcase ET_STR: break;\n\t}\n\n\treturn ret;\n}\n\n// test: \"1<1.0\", ...\nLOCAL expr_val operator_lt(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val ret = {0};\n\tret.type = ET_BOOL;\n\n\tif (e1.type == ET_STR || e2.type == ET_STR)\n\t\treturn string_check_cmp(p, e1, e2, CMPRESULT_lt);\n\n\tswitch(e1.type) {\n\tcase ET_INT: switch (e2.type) {\n\t\tcase ET_INT: ret.b_val = e1.i_val < e2.i_val; break;\n\t\tcase ET_DBL: ret.b_val = Compare_Double(e1.i_val, e2.d_val) == -1; break;\n\t\tcase ET_BOOL: SetError(p, ERR_TYPE_MISMATCH); break;\n\t\t// handled above, avoid gcc warning\n\t\tcase ET_STR: break;\n\t\t} break;\n\tcase ET_DBL: switch (e2.type) {\n\t\tcase ET_INT: ret.b_val = Compare_Double(e1.d_val, e2.i_val) == -1; break;\n\t\tcase ET_DBL: ret.b_val = Compare_Double(e1.d_val, e2.d_val) == -1; break;\n\t\tcase ET_BOOL: SetError(p, ERR_TYPE_MISMATCH); break;\n\t\t// handled above, avoid gcc warning\n\t\tcase ET_STR: break;\n\t\t} break;\n\tcase ET_BOOL: switch (e2.type) {\n\t\tcase ET_BOOL: ret.b_val = !e1.b_val && e2.b_val; break;\n\t\tdefault: SetError(p, ERR_TYPE_MISMATCH); break;\n\t    } break;\n\t// handled above, avoid gcc warning\n\tcase ET_STR: break;\n\t}\n\n\treturn ret;\n}\n\n// this obscurity here just defines operators \"<=\", \">\", \"!=\" and \">=\"\n// by using operators \"<\" and \"==\"\n#define LT_VAL() (operator_lt(p,e1,e2).b_val)\n#define EQ_VAL() (operator_eq(p,e1,e2).b_val)\n#define ADDOP(name,cond)\t\t\t\t\t\t\t\t\t\t\\\nLOCAL expr_val operator_##name (EParser p, const expr_val e1, const expr_val e2) {\t\\\n\texpr_val ret = {0}; ret.type = ET_BOOL;\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tif (e1.type == ET_STR || e2.type == ET_STR)\t\t\t\t\t\t\t\t\t\t\\\n\t\treturn string_check_cmp(p, e1, e2, CMPRESULT_##name);\t\t\t\t\t\t\\\n\telse ret.b_val = (cond); return ret;\t\t\t\t\t\t\t\t\t\t\t\\\n}\n\nADDOP(le, LT_VAL() || EQ_VAL());\nADDOP(gt, !LT_VAL() && !EQ_VAL());\nADDOP(ne, !EQ_VAL());\nADDOP(ge, !LT_VAL());\n\n#undef ADDOP\n#undef LT_VAL\n#undef EQ_VAL\n\nLOCAL expr_val operator_isin(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val s1 = ToString(p, e1);\n\texpr_val s2 = ToString(p, e2);\n\texpr_val r = {0}; r.type = ET_BOOL;\n\n\tif (!(*s1.s_val)) {\n\t\tr.b_val = BOOL_FALSE;\n\t} else {\n\t\tr.b_val = strstr(s2.s_val, s1.s_val) ? BOOL_TRUE : BOOL_FALSE;\n\t}\n\n\tQ_free(s1.s_val);\n\tQ_free(s2.s_val);\n\treturn r;\n}\n\nLOCAL expr_val operator_nisin(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val r = operator_isin(p, e1, e2);\n\tr.b_val = !r.b_val;\n\treturn r;\n}\n\nLOCAL expr_val operator_reeq(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val r = {0};\n\texpr_val strr = ToString(p, e1);\n\texpr_val mask = ToString(p, e2);\n\t// this makes sense for \"111 =~ 1.1\", however we are doing str->double->str conversion\n\t// and it can happen that the result string won't be the same as the source\n\tpcre2_code       *regexp;\n\tint              error;\n\tPCRE2_SIZE       error_offset;\n\tpcre2_match_data *match_data = NULL;\n\tPCRE2_SIZE       *offsets;\n\tint              rc;\n\n\tr.type = ET_BOOL;\n\n\tregexp = pcre2_compile ((PCRE2_SPTR)mask.s_val, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL);\n\tif (!regexp) {\n\t\tSetError(p, ERR_REGEXP);\n\t\treturn Get_Expr_Dummy();\n\t}\n\tmatch_data = pcre2_match_data_create_from_pattern(regexp, NULL);\n\trc = pcre2_match (regexp, (PCRE2_SPTR)strr.s_val, strlen(strr.s_val),\n\t                0, 0, match_data, NULL);\n\tif (rc >= 0) {\n\t\toffsets = pcre2_get_ovector_pointer(match_data);\n\t\tif (p->re_patfnc)\n\t\t\tp->re_patfnc(strr.s_val, offsets, rc > 99 ? 99 : rc);\n\t\tr.b_val = BOOL_TRUE;\n\t} else\n\t\tr.b_val = BOOL_FALSE;\n\n\tQ_free(e1.s_val);\n\tQ_free(e2.s_val);\n\n\tpcre2_match_data_free (match_data);\n\tpcre2_code_free (regexp);\n\treturn r;\n}\n\nLOCAL expr_val operator_rene(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val r = operator_reeq(p, e1, e2);\n\tr.b_val = !r.b_val;\n\treturn r;\n}\n\nLOCAL expr_val operator_and(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val ret = {0};\n\tret.type = ET_BOOL;\n\n\tswitch (e1.type) {\n\tcase ET_INT: switch (e2.type) {\n\t\tcase ET_INT: ret.b_val = e1.i_val && e2.i_val; break;\n\t\tcase ET_DBL: ret.b_val = e1.i_val && e2.d_val; break;\n\t\tcase ET_BOOL: ret.b_val = e1.i_val && e2.b_val; break;\n\t\tcase ET_STR: ret.i_val = e1.i_val && *e2.s_val; break;\n\t    } break;\n\tcase ET_DBL: switch (e2.type) {\n\t    case ET_INT: ret.b_val = e1.d_val && e2.i_val; break;\n\t\tcase ET_DBL: ret.b_val = e1.d_val && e2.d_val; break;\n\t\tcase ET_BOOL: ret.b_val = e1.d_val && e2.b_val; break;\n\t\tcase ET_STR: ret.b_val = e1.d_val && *e2.s_val; break;\n\t\t} break;\n\tcase ET_BOOL: switch (e2.type) {\n\t\tcase ET_INT: ret.b_val = e1.b_val && e2.i_val; break;\n\t\tcase ET_DBL: ret.b_val = e1.b_val && e2.d_val; break;\n\t\tcase ET_BOOL: ret.b_val = e1.b_val && e2.b_val; break;\n\t\tcase ET_STR: ret.b_val = e1.b_val && *e2.s_val; break;\n\t\t} break;\n\tcase ET_STR: switch (e2.type) {\n\t\tcase ET_INT: ret.b_val = *e1.s_val && e2.i_val; break;\n\t\tcase ET_DBL: ret.b_val = *e1.s_val && e2.d_val; break;\n\t\tcase ET_BOOL: ret.b_val = *e1.s_val && e2.b_val; break;\n\t\tcase ET_STR: ret.b_val = *e1.s_val && *e2.s_val; break;\n\t\t} break;\n\t}\n\n\tFreeIfStr(&e1);\n\tFreeIfStr(&e2);\n\n\treturn ret;\n}\n\n// a copy-paste of operator_and, I couldn't think of better way, limited C :(\nLOCAL expr_val operator_or(EParser p, const expr_val e1, const expr_val e2)\n{\n\texpr_val ret = {0};\n\tret.type = ET_BOOL;\n\n\tswitch (e1.type) {\n\tcase ET_INT: switch (e2.type) {\n\t\tcase ET_INT: ret.b_val = e1.i_val || e2.i_val; break;\n\t\tcase ET_DBL: ret.b_val = e1.i_val || e2.d_val; break;\n\t\tcase ET_BOOL: ret.b_val = e1.i_val || e2.b_val; break;\n\t\tcase ET_STR: ret.i_val = e1.i_val || *e2.s_val; break;\n\t    } break;\n\tcase ET_DBL: switch (e2.type) {\n\t    case ET_INT: ret.b_val = e1.d_val || e2.i_val; break;\n\t\tcase ET_DBL: ret.b_val = e1.d_val || e2.d_val; break;\n\t\tcase ET_BOOL: ret.b_val = e1.d_val || e2.b_val; break;\n\t\tcase ET_STR: ret.b_val = e1.d_val || *e2.s_val; break;\n\t\t} break;\n\tcase ET_BOOL: switch (e2.type) {\n\t\tcase ET_INT: ret.b_val = e1.b_val || e2.i_val; break;\n\t\tcase ET_DBL: ret.b_val = e1.b_val || e2.d_val; break;\n\t\tcase ET_BOOL: ret.b_val = e1.b_val || e2.b_val; break;\n\t\tcase ET_STR: ret.b_val = e1.b_val || *e2.s_val; break;\n\t\t} break;\n\tcase ET_STR: switch (e2.type) {\n\t\tcase ET_INT: ret.b_val = *e1.s_val || e2.i_val; break;\n\t\tcase ET_DBL: ret.b_val = *e1.s_val || e2.d_val; break;\n\t\tcase ET_BOOL: ret.b_val = *e1.s_val || e2.b_val; break;\n\t\tcase ET_STR: ret.b_val = *e1.s_val || *e2.s_val; break;\n\t\t} break;\n\t}\n\n\tFreeIfStr(&e1);\n\tFreeIfStr(&e2);\n\n\treturn ret;\n}\n\n\n// does the input string contain str as the very first string?\nLOCAL int Follows(EParser p, const char *str)\n{\n\tconst char* c1 = p->string + p->pos;\n\tconst char* c2 = str;\n\n\twhile (*c1 && *c2 && *c1 == *c2) { c1++; c2++; }\n\n\tif (!*c2) return 1;\n\telse      return 0;\n}\n\n// update the lookahead based on what we see next in the input string\nLOCAL void Next_Token(EParser p)\n{\n    char c;\n\n    while(c = CURCHAR(p), c && c == ' ') p->pos++;\n\n    if (!c)                     { p->lookahead = TK_EOF; }\n\telse if (Follows(p, \"isin\")) { p->lookahead = TK_ISIN; }\n\telse if (Follows(p, \"!isin\")) { p->lookahead = TK_NISIN; }\n\telse if (Follows(p, \"mod\")) { p->lookahead = TK_MOD; }\n\telse if (Follows(p, \"xor\")) { p->lookahead = TK_XOR; }\n\telse if (Follows(p, \"div\")) { p->lookahead = TK_DIV; }\n\telse if (Follows(p, \"and\") || Follows(p, \"AND\") || Follows(p, \"&&\"))\n\t{ p->lookahead = TK_AND; }\n\telse if (Follows(p, \"or\") || Follows(p, \"OR\") || Follows(p, \"||\"))\n\t{ p->lookahead = TK_OR; }\n\telse if (Follows(p, \"strlen\"))    { p->lookahead = TK_STRLEN; }\n\telse if (Follows(p, \"int\"))       { p->lookahead = TK_INT; }\n\telse if (Follows(p, \"substr\"))    { p->lookahead = TK_SUBSTR; }\n\telse if (Follows(p, \"pos\"))       { p->lookahead = TK_POS; }\n\telse if (Follows(p, \"tobrown\"))   { p->lookahead = TK_TOBROWN; }\n\telse if (Follows(p, \"towhite\"))   { p->lookahead = TK_TOWHITE; }\n\telse if (Follows(p, \"<=\"))  { p->lookahead = TK_LE; }\n\telse if (Follows(p, \"==\"))  { p->lookahead = TK_EQ2; }\n\telse if (Follows(p, \"!=\"))  { p->lookahead = TK_NE; }\n\telse if (Follows(p, \">=\"))  { p->lookahead = TK_GE; }\n\telse if (Follows(p, \"=~\"))  { p->lookahead = TK_REEQ; }\n\telse if (Follows(p, \"!~\"))  { p->lookahead = TK_RENE; }\n\telse if (c == '=')          { p->lookahead = TK_EQ; }\n\telse if (c == '<')          { p->lookahead = TK_LT; }\n\telse if (c == '>')          { p->lookahead = TK_GT; }\n    else if (c == '+')          { p->lookahead = TK_PLUS; }\n    else if (c == '*')          { p->lookahead = TK_ASTERISK; }\n    else if (c == '-')          { p->lookahead = TK_MINUS; }\n\telse if (c == '/')\t\t\t{ p->lookahead = TK_SLASH; }\n    else if (c == '(')          { p->lookahead = TK_BR_O; }\n    else if (c == ')')          { p->lookahead = TK_BR_C; }\n\telse if (c == ',')          { p->lookahead = TK_COMMA; }\n    else if ((c >= '0' && c <= '9') || c == '.') {  // isdigit screamed with debug warnings here in some occasions\n\t\tint dbl = 0;\n\t\tconst char *cp = p->string + p->pos;\n\t\twhile (isdigit(*cp) || *cp == '.') {\n\t\t\tif (*cp++ == '.') dbl++;\n\t\t}\n\n\t\tif (dbl == 0)\t\t{ p->lookahead = TK_INTEGER; }\n\t\telse if (dbl == 1)\t{ p->lookahead = TK_DOUBLE; }\n\t\telse\t\t\t\t{ SetError(p, ERR_MALFORMED_NUM); }\n\t}\n    else if (c == '%') { p->lookahead = TK_VAR; }\n\telse p->lookahead = TK_STR;\n}\n\nLOCAL expr_val Match_Var(EParser p)\n{\n\tchar varname[MAX_VAR_NAME];\n\tchar *wp = varname;\n\tsize_t vnlen = 0;\n\n\t// var name can start with % sign\n\tif (p->string[p->pos] == '%' || p->string[p->pos] == '$') p->pos++;\n\n\twhile (p->string[p->pos] && (isalpha(p->string[p->pos]) ||  p->string[p->pos] == '_') && vnlen < sizeof(varname))\n\t{\n\t\t*wp++ = *(p->string + p->pos++);\n\t\tvnlen++;\n\t}\n\t*wp = '\\0';\n\n    Next_Token(p);\n\n\treturn p->varfnc ? p->varfnc(varname) : Get_Expr_Dummy();\n}\n\nLOCAL expr_val Match_String(EParser p)\n{\n\texpr_val ret = {0};\n\tsize_t len = 0;\n\tint startpos = p->pos;\n\tchar firstc = p->string[startpos];\n\n\tif (firstc == '\\'') {\n\t\tp->pos++; startpos++;\n\t\twhile (p->string[p->pos] && p->string[p->pos] != '\\'') {\n\t\t\tlen++; p->pos++;\n\t\t}\n\t\tp->pos++;\t// skip the '\n\t} else if (firstc == '\\\"') {\n\t\tp->pos++; startpos++;\n\t\twhile (p->string[p->pos] && p->string[p->pos] != '\\\"') {\n\t\t\tlen++; p->pos++;\n\t\t}\n\t\tp->pos++;\t// skip the \"\n\t} else {\n\t\t/*\n\t\t * FIXME is iswspace behaves same on Windows, Linux, MacOSX and FreeBSD?\n\t\t */\n\t\twhile (p->string[p->pos] && !iswspace(p->string[p->pos])) {\n\t\t\tlen++; p->pos++;\n\t\t}\n\t}\n\n\tret.type = ET_STR;\n\tret.s_val = (char *) malloc(len+1);\n\tif (!ret.s_val) {\n\t\tSetError(p, ERR_OUT_OF_MEM);\n\t\treturn Get_Expr_Dummy();\n\t}\n\tstrlcpy(ret.s_val, p->string + startpos, len+1);\n\n\tNext_Token(p);\n\n\treturn ret;\n}\n\n// check if we are reading string we expected, return it's value (if any)\n// and move the reading position\nLOCAL expr_val Match(EParser p, int token)\n{\n#define UNEXP_CHAR(p) { \\\n    SetError(p, ERR_UNEXPECTED_CHAR); \\\n    return ret; \\\n}\n    char c;\n    expr_val ret = Get_Expr_Dummy();\n\n    switch (token)\n    {\n    case TK_DOUBLE:\n        // add error check here\n\t\tret.type = ET_DBL;\n\t\tret.d_val = atof(p->string + p->pos);\n        while (c = CURCHAR(p), c && (isdigit(c) || c == '.')) p->pos++;\n        Next_Token(p);\n        break;\n\n\tcase TK_INTEGER:\n\t\tret.type = ET_INT;\n\t\tret.i_val = atoi(p->string + p->pos);\n\t\twhile (c = CURCHAR(p), c && isdigit(c)) p->pos++;\n\t\tNext_Token(p);\n\t\tbreak;\n\n    case TK_VAR:\n        // add error check here\n        return Match_Var(p);\n\n\tcase TK_STR:\n\t\treturn Match_String(p);\n\n\tcase TK_AND:\n\t\tif\t\t(Follows(p, \"and\")) { p->pos += 3; Next_Token(p); }\n\t\telse if (Follows(p, \"AND\")) { p->pos += 3; Next_Token(p); }\n\t\telse if (Follows(p, \"&&\"))  { p->pos += 2; Next_Token(p); }\n\t\telse UNEXP_CHAR(p);\n\t\tbreak;\n\n\tcase TK_OR:\n\t\tif\t\t(Follows(p, \"or\")) { p->pos += 2; Next_Token(p); }\n\t\telse if (Follows(p, \"OR\")) { p->pos += 2; Next_Token(p); }\n\t\telse if (Follows(p, \"||\"))  { p->pos += 2; Next_Token(p); }\n\t\telse UNEXP_CHAR(p);\n\t\tbreak;\n\n    case TK_BR_O:\n        if (!Follows(p, \"(\")) UNEXP_CHAR(p);\n        p->pos++;\n        Next_Token(p);\n        break;\n\n    case TK_BR_C:\n        if (!Follows(p, \")\")) UNEXP_CHAR(p);\n        p->pos++;\n        Next_Token(p);\n        break;\n\n    case TK_PLUS:\n        if (!Follows(p, \"+\")) UNEXP_CHAR(p);\n        p->pos++;\n        Next_Token(p);\n        break;\n\n    case TK_ASTERISK:\n        if (!Follows(p, \"*\")) UNEXP_CHAR(p);\n        p->pos++;\n        Next_Token(p);\n        break;\n\n\tcase TK_SLASH:\n\t\tif (!Follows(p, \"/\")) UNEXP_CHAR(p);\n\t\tp->pos++;\n\t\tNext_Token(p);\n\t\tbreak;\n\n    case TK_MINUS:\n        if (!Follows(p, \"-\")) UNEXP_CHAR(p);\n        p->pos++;\n        Next_Token(p);\n        break;\n\n\tcase TK_LT:\n\t\tif (!Follows(p, \"<\")) UNEXP_CHAR(p);\n\t\tp->pos++; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_LE:\n\t\tif (!Follows(p, \"<=\")) UNEXP_CHAR(p);\n\t\tp->pos += 2; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_EQ:\n\t\tif (!Follows(p, \"=\")) UNEXP_CHAR(p);\n\t\tp->pos++; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_EQ2:\n\t\tif (!Follows(p, \"==\")) UNEXP_CHAR(p);\n\t\tp->pos += 2; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_NE:\n\t\tif (!Follows(p, \"!=\") && !Follows(p, \"<>\")) UNEXP_CHAR(p);\n\t\tp->pos += 2; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_GE:\n\t\tif (!Follows(p, \">=\")) UNEXP_CHAR(p);\n\t\tp->pos += 2; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_GT:\n\t\tif (!Follows(p, \">\")) UNEXP_CHAR(p);\n\t\tp->pos++; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_ISIN:\n\t\tif (!Follows(p, \"isin\")) UNEXP_CHAR(p);\n\t\tp->pos += 4; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_NISIN:\n\t\tif (!Follows(p, \"!isin\")) UNEXP_CHAR(p);\n\t\tp->pos += 5; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_REEQ:\n\t\tif (!Follows(p, \"=~\")) UNEXP_CHAR(p);\n\t\tp->pos += 2; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_RENE:\n\t\tif (!Follows(p, \"!~\")) UNEXP_CHAR(p);\n\t\tp->pos += 2; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_MOD:\n\t\tif (!Follows(p, \"mod\")) UNEXP_CHAR(p);\n\t\tp->pos += 3; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_XOR:\n\t\tif (!Follows(p, \"xor\")) UNEXP_CHAR(p);\n\t\tp->pos += 3; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_DIV:\n\t\tif (!Follows(p, \"div\")) UNEXP_CHAR(p);\n\t\tp->pos += 3; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_STRLEN:\n\t\tif (!Follows(p, \"strlen\")) UNEXP_CHAR(p);\n\t\tp->pos += 6; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_INT:\n\t\tif (!Follows(p, \"int\")) UNEXP_CHAR(p);\n\t\tp->pos += 3; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_SUBSTR:\n\t\tif (!Follows(p, \"substr\")) UNEXP_CHAR(p);\n\t\tp->pos += 6; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_POS:\n\t\tif (!Follows(p, \"pos\")) UNEXP_CHAR(p);\n\t\tp->pos += 3; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_TOBROWN:\n\t\tif (!Follows(p, \"tobrown\")) UNEXP_CHAR(p);\n\t\tp->pos += 7; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_TOWHITE:\n\t\tif (!Follows(p, \"towhite\")) UNEXP_CHAR(p);\n\t\tp->pos += 7; Next_Token(p);\n\t\tbreak;\n\n\tcase TK_COMMA:\n\t\tif (!Follows(p, \",\")) UNEXP_CHAR(p);\n\t\tp->pos += 1; Next_Token(p);\n\t\tbreak;\n    }\n\n    return ret;\n}\n\n\n/*\n\nFollowing code represents this grammar\n\n S -> C\n C -> BC'\n C'-> orBC'\n C'-> lambda\n B -> EB'\n B'-> <EB'\n B'-> lambda\n E -> TE'\n E'-> +TE'\n E'-> lambda\n T -> FT'\n T'-> *FT'\n T'-> lambda\n F -> (C)\n F -> var\n F -> number\n F -> fnc(C)\n\n which came from following grammar after eliminating left recursion\n C -> C or B\n C -> B\n B -> B < E\n B -> E\n E -> E + T\n E -> T\n T -> T * F\n T -> F\n F -> (C)\n F -> var\n F -> number\n F -> fnc(C)\n\n*/\n\nLOCAL expr_val C(EParser p);\n\nLOCAL expr_val F(EParser p)\n{\n\tswitch (p->lookahead)\n    {\n\tcase TK_BR_O: {\n\t\texpr_val m;\n        Match(p, TK_BR_O); m = C(p); Match(p, TK_BR_C);\n        return m;\n\t\t}\n\tcase TK_VAR:\n\t\treturn Match(p, TK_VAR);\n\tcase TK_STR:\n\t\treturn Match(p, TK_STR);\n    case TK_DOUBLE:\n\t\treturn Match(p, TK_DOUBLE);\n\tcase TK_INTEGER:\n\t\treturn Match(p, TK_INTEGER);\n    case TK_MINUS:\n\t\tMatch(p, TK_MINUS);\n\t\treturn operator_minus(p, F(p));\n\tcase TK_STRLEN:\n\t\tMatch(p, TK_STRLEN);\n\t\treturn operator_strlen(p, F(p));\n\tcase TK_SUBSTR: {\n\t\texpr_val arg1, arg2, arg3;\n\t\tMatch(p, TK_SUBSTR);\n\t\tMatch(p, TK_BR_O);\n\t\targ1 = F(p);\n\t\tMatch(p, TK_COMMA);\n\t\targ2 = F(p);\n\t\tMatch(p, TK_COMMA);\n\t\targ3 = F(p);\n\t\tMatch(p, TK_BR_C);\n\t\treturn operator_substr(p, arg1, arg2, arg3);\n\t\t}\n\tcase TK_POS: {\n\t\texpr_val arg1, arg2;\n\t\tMatch(p, TK_POS);\n\t\tMatch(p, TK_BR_O);\n\t\targ1 = F(p);\n\t\tMatch(p, TK_COMMA);\n\t\targ2 = F(p);\n\t\tMatch(p, TK_BR_C);\n\t\treturn operator_pos(p, arg1, arg2);\n\t\t}\n\tcase TK_INT:\n\t\tMatch(p, TK_INT);\n\t\treturn operator_int(p, F(p));\n\tcase TK_TOBROWN:\n\t\tMatch(p, TK_TOBROWN);\n\t\treturn operator_tobrown(p, F(p));\n\tcase TK_TOWHITE:\n\t\tMatch(p, TK_TOWHITE);\n\t\treturn operator_towhite(p, F(p));\n\tdefault:\n\t\tSetError(p, ERR_INVALID_TOKEN);\n\t\treturn Get_Expr_Dummy();\n    }\n}\n\nLOCAL expr_val Tap(EParser p, expr_val v)\n{\n\tswitch (p->lookahead) {\n\tcase TK_ASTERISK: Match(p, TK_ASTERISK);\n        return operator_multiply(p, v, Tap(p, F(p)));\n\tcase TK_SLASH: Match(p, TK_SLASH);\n\t\treturn operator_multiply(p, v, Tap(p, operator_divide(p, F(p))));\n\tcase TK_MOD: Match(p, TK_MOD);\n\t\treturn operator_modulo(p, v, Tap(p, F(p)));\n\tcase TK_XOR: Match(p, TK_XOR);\n\t\treturn operator_xor(p, v, Tap(p, F(p)));\n\tcase TK_DIV: Match(p, TK_DIV);\n\t\treturn operator_divint(p, v, Tap(p, F(p)));\n\tdefault: return v;\n\t}\n}\n\nLOCAL expr_val T(EParser p) { return Tap(p, F(p)); }\n\nLOCAL expr_val Eap(EParser p, expr_val v)\n{\n\tswitch (p->lookahead) {\n\tcase TK_PLUS: Match(p, TK_PLUS);\n\t\treturn operator_plus(p, v, Eap(p,T(p)));\n\tcase TK_MINUS: Match(p, TK_MINUS);\n\t\treturn operator_plus(p, v, Eap(p, operator_minus(p, T(p))));\n\tdefault: return v;\n\t}\n}\n\nLOCAL expr_val E(EParser p) { return Eap(p, T(p)); }\n\nLOCAL expr_val Bap(EParser p, expr_val v)\n{\n\tswitch (p->lookahead) {\n\tcase TK_LT: Match(p, TK_LT); return operator_lt(p, v, Bap(p, E(p)));\n\tcase TK_LE: Match(p, TK_LE); return operator_le(p, v, Bap(p, E(p)));\n\tcase TK_EQ: Match(p, TK_EQ); return operator_eq(p, v, Bap(p, E(p)));\n\tcase TK_EQ2: Match(p, TK_EQ2); return operator_eq(p, v, Bap(p, E(p)));\n\tcase TK_NE: Match(p, TK_NE); return operator_ne(p, v, Bap(p, E(p)));\n\tcase TK_GE: Match(p, TK_GE); return operator_ge(p, v, Bap(p, E(p)));\n\tcase TK_GT: Match(p, TK_GT); return operator_gt(p, v, Bap(p, E(p)));\n\tcase TK_ISIN: Match(p, TK_ISIN); return operator_isin(p, v, Bap(p, E(p)));\n\tcase TK_NISIN: Match(p, TK_NISIN); return operator_nisin(p, v, Bap(p, E(p)));\n\tcase TK_REEQ: Match(p, TK_REEQ); return operator_reeq(p, v, Bap(p, E(p)));\n\tcase TK_RENE: Match(p, TK_RENE); return operator_rene(p, v, Bap(p, E(p)));\n\tdefault: return v;\n\t}\n}\n\nLOCAL expr_val B(EParser p) { return Bap(p, E(p)); }\n\nLOCAL expr_val Cap(EParser p, expr_val v)\n{\n\tswitch (p->lookahead) {\n\tcase TK_AND: Match(p, TK_AND);\n\t\treturn operator_and(p, v, Cap(p, B(p)));\n\tcase TK_OR: Match(p, TK_OR);\n\t\treturn operator_or(p, v, Cap(p, B(p)));\n\tdefault: return v;\n\t}\n}\n\nLOCAL expr_val C(EParser p) { return Cap(p, B(p)); }\n\nLOCAL expr_val S(EParser p) { return C(p); }\n\n// -- grammar end --\n\nGLOBAL const char* Parser_Error_Description(int error)\n{\n\tswitch (error) {\n\tcase EXPR_EVAL_SUCCESS:\t\treturn \"Evaluation succeeded\";\n\tcase ERR_INVALID_TOKEN:\t\treturn \"Invalid token found\";\n\tcase ERR_UNEXPECTED_CHAR:\treturn \"Unexpected character\";\n\tcase ERR_TYPE_MISMATCH:\t\treturn \"Type mismatch\";\n\tcase ERR_ZERO_DIV:\t\t\treturn \"Division by zero\";\n\tcase ERR_NOTIMPL:\t\t\treturn \"Operation not implemented\";\n\tcase ERR_MALFORMED_NUM:\t\treturn \"Malformed number found\";\n\tcase ERR_OUT_OF_MEM:\t\treturn \"Out of memory\";\n\tcase ERR_INTERNAL:\t\t\treturn \"Internal error\";\n\tcase ERR_REGEXP:\t\t\treturn \"Regexp error\";\n\tcase ERR_INVALID_ARG:       return \"Invalid argument\";\n\tdefault:\t\t\t\t\treturn \"Unknown error\";\n\t}\n}\n\nLOCAL void Init_Parser(EParser p, const parser_extra* f, const char *str)\n{\n    p->pos = 0;\n    p->error = EXPR_EVAL_SUCCESS;\n    p->string = str;\n\tp->varfnc = f ? f->var2val_fnc : 0;\n\tp->re_patfnc = f ? f->subpatt_fnc : 0;\n    Next_Token(p);\n}\n\n/// Evaluates an expression represented by a string\n///\n/// \\param[in]\tstr\t\texpression to be evaluated\n/// \\param[in]\tf\t\textra parser options\n/// \\param[out]\terror\terror level\n/// \\return Returns the result of the evaluation of the expression, it's type and value\nGLOBAL expr_val Expr_Eval(const char *str, const parser_extra* f, int *error)\n{\n    expr_parser_t p;\n\texpr_val e;\n    Init_Parser(&p, f, str);\n\te = S(&p);\n\t*error = p.error;\n\treturn e;\n}\n\nGLOBAL int Expr_Eval_Int(const char *str, const parser_extra* f, int *result)\n{\n\tint err;\n\texpr_val e;\n\n\te = Expr_Eval(str, f, &err);\n\tif (err == EXPR_EVAL_SUCCESS) *result = Get_Integer(e);\n\n\treturn err;\n}\n\nGLOBAL int Expr_Eval_Double(const char *str, const parser_extra* f, double *result)\n{\n\tint err;\n\texpr_val e;\n\n\te = Expr_Eval(str, f, &err);\n\tif (err == EXPR_EVAL_SUCCESS) *result = Get_Double(e);\n\n    return err;\n}\n\nGLOBAL int Expr_Eval_Bool(const char* str, const parser_extra* f, int *result)\n{\n\tint err;\n\texpr_val e;\n\n\te = Expr_Eval(str, f, &err);\n\tif (err == EXPR_EVAL_SUCCESS) *result = Get_Bool(e);\n\n    return err;\n}\n\nLOCAL int Expr_Run_Test_Core(const char *str, const expr_val expected_result, int expected_error)\n{\n\tint real_error;\n\texpr_val real_result = Expr_Eval(str, NULL, &real_error);\n\tif (expected_error == real_error) {\n\t\tif (expected_error == EXPR_EVAL_SUCCESS) {\n\t\t\tif (expected_result.type == real_result.type) {\n\t\t\t\tswitch (expected_result.type) {\n\t\t\t\t\tcase ET_INT: return (expected_result.i_val == real_result.i_val) ? 0 : -1;\n\t\t\t\t\tcase ET_DBL: return ((fabs(expected_result.d_val - real_result.d_val) < 0.000001) ? 0 : -1);\n\t\t\t\t\tcase ET_BOOL: return (expected_result.b_val == real_result.b_val) ? 0 : -1;\n\t\t\t\t\tcase ET_STR: {\n\t\t\t\t\t\tint equal = (strcmp(expected_result.s_val, real_result.s_val) == 0);\n\t\t\t\t\t\tQ_free(real_result.s_val);\n\n\t\t\t\t\t\treturn (equal ? 0 : -1);\n\t\t\t\t\t}\n\t\t\t\t\tdefault: return -4;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn -2;\n\t\t\t}\n\t\t}\n\t\telse { // expected error happened\n\t\t\treturn 0;\n\t\t}\n\t}\n\telse {\n\t\treturn -3;\n\t}\n}\n\nLOCAL int Expr_Run_Test(const char *str, const expr_val expected_result, int expected_error)\n{\n\tvoid Com_Printf (char *fmt, ...);\n\tint res = Expr_Run_Test_Core(str, expected_result, expected_error);\n\tif (res != 0) {\n\t\tCom_Printf(\"Test '%s' failed with error %d\\n\", str, res);\n\t\tres = 1;\n\t}\n\treturn res;\n}\n\nLOCAL int Expr_Run_Test_Int(const char *str, int val)\n{\n\treturn Expr_Run_Test(str, Get_Expr_Integer(val), EXPR_EVAL_SUCCESS);\n}\n\nLOCAL int Expr_Run_Test_Bool(const char *str, int val)\n{\n\treturn Expr_Run_Test(str, Get_Expr_Bool(val), EXPR_EVAL_SUCCESS);\n}\n\nLOCAL int Expr_Run_Test_Double(const char *str, double val)\n{\n\treturn Expr_Run_Test(str, Get_Expr_Double(val), EXPR_EVAL_SUCCESS);\n}\n\nLOCAL int Expr_Run_Test_Error(const char *str, int error)\n{\n\treturn Expr_Run_Test(str, Get_Expr_Dummy(), error);\n}\n\nLOCAL int Expr_Run_Test_Str(const char *str, const char *val)\n{\n\texpr_val eval = Get_Expr_String(val);\n\tint res = Expr_Run_Test(str, eval, EXPR_EVAL_SUCCESS);\n\tQ_free(eval.s_val);\n\treturn res;\n}\n\nGLOBAL int Expr_Run_Unit_Tests(void)\n{\n\tint errors = 0;\n\n\terrors += Expr_Run_Test_Int(\"0\", 0);\n\terrors += Expr_Run_Test_Int(\"-1\", -1);\n\terrors += Expr_Run_Test_Int(\"   100 \", 100);\n\terrors += Expr_Run_Test_Int(\"1+2+3+4+5+6\", 1+2+3+4+5+6);\n\terrors += Expr_Run_Test_Double(\"4+2/6+9.1-4*(4*7+2.5-4/2*(42/9+2.075)-8-3-12)-9+21*4\",\n\t\t112.366667);\n\t\n\terrors += Expr_Run_Test_Error(\"2+4/(3-1-0.5-0.5-0.25-0.75)\", ERR_ZERO_DIV);\n\n\terrors += Expr_Run_Test_Double(\"3.33333\", 3.33333);\n\terrors += Expr_Run_Test_Double(\"-0.0001\", -0.0001);\n\terrors += Expr_Run_Test_Double(\"10.0/3.0\", 10.0/3.0);\n\n\terrors += Expr_Run_Test_Bool(\"0+1 > 1+0\", 0);\n\terrors += Expr_Run_Test_Bool(\"0+1 >= 1+0\", 1);\n\terrors += Expr_Run_Test_Bool(\"0+1 < 1+0\", 0);\n\terrors += Expr_Run_Test_Bool(\"0+1+2 <= 2+1+0\", 1);\n\terrors += Expr_Run_Test_Bool(\"123==123\", 1);\n\terrors += Expr_Run_Test_Bool(\"1051=1052\", 0);\n\terrors += Expr_Run_Test_Bool(\"1051!=1052\", 1);\n\n\terrors += Expr_Run_Test_Bool(\"\\\"x\\\" isin \\\"x\\\"\", 1);\n\terrors += Expr_Run_Test_Bool(\"\\\"x\\\" isin \\\"xaaa\\\"\", 1);\n\terrors += Expr_Run_Test_Bool(\"\\\"x\\\" isin \\\"aaax\\\"\", 1);\n\terrors += Expr_Run_Test_Bool(\"\\\"x\\\" isin \\\"aaa\\\"\", 0);\n\terrors += Expr_Run_Test_Bool(\"\\\"x\\\" !isin \\\"aaa\\\"\", 1);\n\n\terrors += Expr_Run_Test_Int(\"10 mod 3\", 10 % 3);\n\terrors += Expr_Run_Test_Int(\"12643 xor 18061\", 12643 ^ 18061);\n\terrors += Expr_Run_Test_Int(\"123 div 11\", 123/11);\n\n\terrors += Expr_Run_Test_Int(\"strlen \\\"1234\\\"\", 4);\n\terrors += Expr_Run_Test_Int(\"int 4.4\", 4);\n\terrors += Expr_Run_Test_Int(\"int 4.6\", 4);\n\terrors += Expr_Run_Test_Str(\"substr(\\\"abcd\\\", 3, 30)\", \"d\");\n\terrors += Expr_Run_Test_Str(\"substr(\\\"abcdef\\\", 0, 2)\", \"ab\");\n\terrors += Expr_Run_Test_Str(\"substr(\\\"abcdef\\\", 2, 1)\", \"c\");\n\n\terrors += Expr_Run_Test_Bool(\"(1 and 0) or (0 and 2) or (0 xor 0)\", 0);\n\terrors += Expr_Run_Test_Bool(\"(1 and 0) or (0 and 2) or (0 xor 1)\", 1);\n\terrors += Expr_Run_Test_Bool(\"0 or 0 or 0 or 0 or 1 or 0 or 0 or 0\", 1);\n\terrors += Expr_Run_Test_Bool(\"1 and 1 and 1 and 1 and 0 and 1 and 1\", 0);\n\terrors += Expr_Run_Test_Bool(\"1 and 1 and 1 and 1 and 2 and 1 and 1\", 1);\n\terrors += Expr_Run_Test_Bool(\"1<2 && 3<4 && 5<=5 && 6<=7 && 8>=8 && 10>9\", 1);\n\n\terrors += Expr_Run_Test_Bool(\"(abc isin dabcd or (x !isin xxxx and 3 < 5)) and 'a'+'bc' isin 'dab'+'cd'\", 1);\n\n\t// todo: tobrown, towhite\n\n\treturn errors;\n}\n\n"
  },
  {
    "path": "src/parser.h",
    "content": "/*\nCopyright (C) 2011 johnnycz\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/**\n\t\\file\n\n\t\\brief\n\tThis module allows you to evaluate string representation\n\tof an arithmetic expression into a number\n\n\tSupports:\n\t- parentheses (, )\n\t- binary operators:\n          - +, -, *, / (arithmetic, also + for string concatenation)\n\t\t  - <, <=, =, ==, >=, >, =~, !~, isin, !isin\n\t\t  - and, AND, &&, or, OR, ||\n\t- unary operators: -\n\t- types: double, integer, bool,\n\t    string (wrapped in quotes or apostrophes or surrounded by white-space chars)\n\t- variables: optionally preceded with '%'\n\n\t\\author\tjohnnycz\n**/\n\n#ifndef __PARSER_H__\n#define __PARSER_H__\n\n#define MAX_VAR_NAME\t\t32\n\n#define EXPR_EVAL_SUCCESS\t0\n\n/// type of an expression\ntypedef enum { \n\tET_INT,\t\t///< integer\n\tET_DBL,\t\t///< double\n\tET_BOOL,\t///< boolean\n\tET_STR\t\t///< string\n} expr_type;\n\n/// expression value and type\ntypedef struct expr_val {\n\texpr_type type;\t///< type of an expression\n\tint i_val;\t\t///< integer value\n\tdouble d_val;\t///< double value\n\tint b_val;\t\t///< boolean value\n\tchar *s_val;\t///< string value\n} expr_val;\n\n/// \\brief Gets value of an user variable found in an expression during the evaluation\n///\n/// \\param[in] varname name of the user variable\n/// \\return Value of the user variable\ntypedef expr_val (* variable_val_fnc) (const char* varname);\n\n/// \\brief Regexp match handler\n///\n/// Gets called when the expression parser does a regexp match\n///\n/// \\param[in] str\t\tsource string\n/// \\param[in] offsets\tarray of offset pairs (start1,end1,start2,end2,...)\n/// \\param[in] matches\tnumber of captured groups\ntypedef void (* subpatterns_report_fnc) (const char* str, size_t* offsets, int matches);\n\n/// expression parser's extra options\ntypedef struct {\n\t/// pointer to the function that returns values of the user variables\n\tvariable_val_fnc\t\tvar2val_fnc;\n\n\t/// pointer to the function that handles regexp matches\n\tsubpatterns_report_fnc\tsubpatt_fnc;\n} parser_extra;\n\n// conversion functions\nextern expr_val Get_Expr_Double(double v);\nextern expr_val Get_Expr_Integer(int v);\nextern expr_val Get_Expr_Dummy(void);\n\n// given the number of the error, you'll get brief description of that error\nextern const char* Parser_Error_Description(int error);\n\n// evaluate str, write the result and return error level\nextern expr_val Expr_Eval(const char* str, const parser_extra*, int* error);\nextern int Expr_Eval_Int(const char *str, const parser_extra*, int *result);\nextern int Expr_Eval_Double(const char *str, const parser_extra*, double *result);\nextern int Expr_Eval_Bool(const char* std, const parser_extra*, int *result);\n\n// run unit tests, return number of errors\nint Expr_Run_Unit_Tests(void);\n\n#endif // __PARSER_H__\n"
  },
  {
    "path": "src/particles_classic.h",
    "content": "\n#ifndef PARTICLES_CLASSIC_HEADER\n#define PARTICLES_CLASSIC_HEADER\n\ntypedef enum {\n\tpt_static, pt_grav, pt_slowgrav, pt_fire, pt_explode, pt_explode2, pt_blob, pt_blob2, pt_rail\n} ptype_t;\n\ntypedef struct qparticle_s {\n\tvec3_t      org;\n\tfloat       color;\n\tvec3_t      vel;\n\tfloat       ramp;\n\tfloat       die;\n\tptype_t     type;\n\t//struct particle_s *next;\n} qparticle_t;\n\n//#define DEFAULT_NUM_PARTICLES\t2048\n#define ABSOLUTE_MIN_PARTICLES\t512\n#define ABSOLUTE_MAX_PARTICLES\t8192\n\n#endif // PARTICLES_CLASSIC_HEADER\n"
  },
  {
    "path": "src/pmove.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n\n#ifdef SERVERONLY\n#include \"qwsvdef.h\"\n#else\n#include \"quakedef.h\"\n#include \"pmove.h\"\n#endif\n\nmovevars_t      movevars;\nplayermove_t    pmove;\n\nstatic float\tpm_frametime;\n\nstatic vec3_t\tpm_forward, pm_right;\n\nstatic vec3_t   groundnormal;\n\nvec3_t\tplayer_mins = {-16, -16, -24};\nvec3_t\tplayer_maxs = {16, 16, 32};\n\n#define STEPSIZE        18\n\n#define pm_flyfriction\t4\n\n#define BLOCKED_FLOOR\t1\n#define BLOCKED_STEP\t2\n#define BLOCKED_OTHER\t4\n#define BLOCKED_ANY\t\t7\n\n#define MAX_JUMPFIX_DOTPRODUCT -0.1\n\n// Add an entity to touch list, discarding duplicates\nstatic void PM_AddTouchedEnt (int num)\n{\n\tint i;\n\n\tif (pmove.numtouch == sizeof(pmove.touchindex)/sizeof(pmove.touchindex[0]))\n\t\treturn;\n\n\tfor (i = 0; i < pmove.numtouch; i++)\n\t\tif (pmove.touchindex[i] == num)\n\t\t\treturn; // already added\n\n\tpmove.touchindex[pmove.numtouch] = num;\n\tpmove.numtouch++;\n}\n\n//Slide off of the impacting object\n//returns the blocked flags (1 = floor, 2 = step / wall)\n#define STOP_EPSILON 0.1\nstatic void PM_ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce)\n{\n\tfloat backoff, change;\n\tint i;\n\n\tbackoff = DotProduct (in, normal) * overbounce;\n\n\tfor (i = 0; i < 3; i++) {\n\t\tchange = normal[i] * backoff;\n\t\tout[i] = in[i] - change;\n\t\tif (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)\n\t\t\tout[i] = 0;\n\t}\n}\n\n//The basic solid body movement clip that slides along multiple planes\n#define\tMAX_CLIP_PLANES 5\nstatic int PM_SlideMove (void)\n{\n\tvec3_t dir, planes[MAX_CLIP_PLANES], primal_velocity, original_velocity, end;\n\tint bumpcount, numbumps, i, j, blocked, numplanes;\n\tfloat d, time_left;\n\ttrace_t trace;\n\n\tnumbumps = 4;\n\tblocked = 0;\n\tVectorCopy (pmove.velocity, original_velocity);\n\tVectorCopy (pmove.velocity, primal_velocity);\n\tnumplanes = 0;\n\n\ttime_left = pm_frametime;\n\n\tfor (bumpcount = 0; bumpcount < numbumps; bumpcount++) {\n\t\tVectorMA(pmove.origin, time_left, pmove.velocity, end);\n\t\ttrace = PM_PlayerTrace (pmove.origin, end);\n\n\t\tif (trace.startsolid || trace.allsolid) {\n\t\t\t// entity is trapped in another solid\n\t\t\tVectorClear (pmove.velocity);\n\t\t\treturn 3;\n\t\t}\n\n\t\tif (trace.fraction > 0) {\n\t\t\t// actually covered some distance\n\t\t\tVectorCopy (trace.endpos, pmove.origin);\n\t\t\tnumplanes = 0;\n\t\t}\n\n\t\tif (trace.fraction == 1) {\n\t\t\tbreak; // moved the entire distance\n\t\t}\n\n\t\t// save entity for contact\n\t\tPM_AddTouchedEnt (trace.e.entnum);\n\n\t\tif (trace.plane.normal[2] >= MIN_STEP_NORMAL)\n\t\t\tblocked |= BLOCKED_FLOOR;\n\t\telse if (!trace.plane.normal[2])\n\t\t\tblocked |= BLOCKED_STEP;\n\t\telse\n\t\t\tblocked |= BLOCKED_OTHER;\n\n\t\ttime_left -= time_left * trace.fraction;\n\n\t\t// cliped to another plane\n\t\tif (numplanes >= MAX_CLIP_PLANES) {\n\t\t\t// this shouldn't really happen\n\t\t\tVectorClear (pmove.velocity);\n\t\t\tbreak;\n\t\t}\n\n\t\tVectorCopy (trace.plane.normal, planes[numplanes]);\n\t\tnumplanes++;\n\n\t\t// modify original_velocity so it parallels all of the clip planes\n\t\tfor (i = 0; i < numplanes; i++) {\n\t\t\tPM_ClipVelocity (original_velocity, planes[i], pmove.velocity, 1);\n\t\t\tfor (j = 0; j < numplanes; j++) {\n\t\t\t\tif (j != i) {\n\t\t\t\t\tif (DotProduct(pmove.velocity, planes[j]) < 0) {\n\t\t\t\t\t\tbreak; // not ok\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (j == numplanes) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (i != numplanes) {\n\t\t\t// go along this plane\n\t\t}\n\t\telse {\n\t\t\t// go along the crease\n\t\t\tif (numplanes != 2) {\n\t\t\t\tVectorClear (pmove.velocity);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tCrossProduct (planes[0], planes[1], dir);\n\t\t\td = DotProduct (dir, pmove.velocity);\n\t\t\tVectorScale (dir, d, pmove.velocity);\n\t\t}\n\n\t\t// if velocity is against the original velocity, stop dead\n\t\t// to avoid tiny occilations in sloping corners\n\t\tif (DotProduct (pmove.velocity, primal_velocity) <= 0) {\n\t\t\tVectorClear (pmove.velocity);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (pmove.waterjumptime) {\n\t\tVectorCopy(primal_velocity, pmove.velocity);\n\t}\n\n\treturn blocked;\n}\n\n//Each intersection will try to step over the obstruction instead of sliding along it.\nstatic int PM_StepSlideMove (qbool in_air)\n{\n\tvec3_t original, originalvel, down, up, downvel, dest;\n\tfloat downdist, updist, stepsize;\n\ttrace_t trace;\n\tint blocked;\n\n\t// try sliding forward both on ground and up 16 pixels\n\t// take the move that goes farthest\n\tVectorCopy (pmove.origin, original);\n\tVectorCopy (pmove.velocity, originalvel);\n\n\tblocked = PM_SlideMove ();\n\n\tif (!blocked) {\n\t\treturn blocked; // moved the entire distance\n\t}\n\n\tif (in_air) {\n\t\t// don't let us step up unless it's indeed a step we bumped in\n\t\t// (that is, there's solid ground below)\n\t\tfloat *org;\n\n\t\tif (!(blocked & BLOCKED_STEP)) {\n\t\t\treturn blocked;\n\t\t}\n\n\t\torg = (originalvel[2] < 0) ? pmove.origin : original;\n\t\tVectorCopy (org, dest);\n\t\tdest[2] -= STEPSIZE;\n\t\ttrace = PM_PlayerTrace (org, dest);\n\t\tif (trace.fraction == 1 || trace.plane.normal[2] < MIN_STEP_NORMAL) {\n\t\t\treturn blocked;\n\t\t}\n\n\t\t// adjust stepsize, otherwise it would be possible to walk up a\n\t\t// a step higher than STEPSIZE\n\t\tstepsize = STEPSIZE - (org[2] - trace.endpos[2]);\n\t}\n\telse {\n\t\tstepsize = STEPSIZE;\n\t}\n\n\tVectorCopy (pmove.origin, down);\n\tVectorCopy (pmove.velocity, downvel);\n\n\tVectorCopy (original, pmove.origin);\n\tVectorCopy (originalvel, pmove.velocity);\n\n\t// move up a stair height\n\tVectorCopy (pmove.origin, dest);\n\tdest[2] += stepsize;\n\ttrace = PM_PlayerTrace (pmove.origin, dest);\n\tif (!trace.startsolid && !trace.allsolid) {\n\t\tVectorCopy(trace.endpos, pmove.origin);\n\t}\n\n\tif (in_air && originalvel[2] < 0) {\n\t\tpmove.velocity[2] = 0;\n\t}\n\n\tPM_SlideMove ();\n\n\t// press down the stepheight\n\tVectorCopy (pmove.origin, dest);\n\tdest[2] -= stepsize;\n\ttrace = PM_PlayerTrace (pmove.origin, dest);\n\tif (trace.fraction != 1 && trace.plane.normal[2] < MIN_STEP_NORMAL) {\n\t\tgoto usedown;\n\t}\n\tif (!trace.startsolid && !trace.allsolid) {\n\t\tVectorCopy(trace.endpos, pmove.origin);\n\t}\n\n\tif (pmove.origin[2] < original[2]) {\n\t\tgoto usedown;\n\t}\n\n\tVectorCopy (pmove.origin, up);\n\n\t// decide which one went farther\n\tdowndist = (down[0] - original[0]) * (down[0] - original[0])\n\t\t+ (down[1] - original[1]) * (down[1] - original[1]);\n\tupdist = (up[0] - original[0]) * (up[0] - original[0])\n\t\t+ (up[1] - original[1]) * (up[1] - original[1]);\n\n\tif (downdist >= updist) {\nusedown:\n\t\tVectorCopy (down, pmove.origin);\n\t\tVectorCopy (downvel, pmove.velocity);\n\t\treturn blocked;\n\t}\n\n\t// copy z value from slide move\n\tpmove.velocity[2] = downvel[2];\n\n\tif (!pmove.onground && pmove.waterlevel < 2 && (blocked & BLOCKED_STEP)) {\n\t\tfloat scale;\n\t\t// in pm_airstep mode, walking up a 16 unit high step\n\t\t// will kill 16% of horizontal velocity\n\t\tscale = 1 - 0.01*(pmove.origin[2] - original[2]);\n\t\tpmove.velocity[0] *= scale;\n\t\tpmove.velocity[1] *= scale;\n\t}\n\n\treturn blocked;\n}\n\n//Handles both ground friction and water friction\nstatic void PM_Friction(void)\n{\n\tfloat speed, newspeed, control, friction, drop;\n\tvec3_t start, stop;\n\ttrace_t trace;\n\n\tif (pmove.waterjumptime)\n\t\treturn;\n\n\tspeed = VectorLength(pmove.velocity);\n\tif (speed < 1) {\n\t\tpmove.velocity[0] = pmove.velocity[1] = 0;\n\t\tif (pmove.pm_type == PM_FLY) {\n\t\t\tpmove.velocity[2] = 0;\n\t\t}\n\t\treturn;\n\t}\n\n\tif (pmove.waterlevel >= 2) {\n\t\t// apply water friction, even if in fly mode\n\t\tdrop = speed * movevars.waterfriction * pmove.waterlevel * pm_frametime;\n\t}\n\telse if (pmove.pm_type == PM_FLY) {\n\t\t// apply flymode friction\n\t\tdrop = speed * pm_flyfriction * pm_frametime;\n\t}\n\telse if (pmove.onground) {\n\t\t// apply ground friction\n\t\tfriction = movevars.friction;\n\n\t\t// if the leading edge is over a dropoff, increase friction\n\t\tstart[0] = stop[0] = pmove.origin[0] + pmove.velocity[0] / speed * 16;\n\t\tstart[1] = stop[1] = pmove.origin[1] + pmove.velocity[1] / speed * 16;\n\t\tstart[2] = pmove.origin[2] + player_mins[2];\n\t\tstop[2] = start[2] - 34;\n\t\ttrace = PM_PlayerTrace(start, stop);\n\t\tif (trace.fraction == 1) {\n\t\t\tfriction *= 2;\n\t\t}\n\n\t\tcontrol = speed < movevars.stopspeed ? movevars.stopspeed : speed;\n\t\tdrop = control * friction * pm_frametime;\n\t}\n\telse {\n\t\treturn; // in air, no friction\n\t}\n\n\t// scale the velocity\n\tnewspeed = speed - drop;\n\tnewspeed = max(newspeed, 0);\n\tnewspeed /= speed;\n\n\tVectorScale(pmove.velocity, newspeed, pmove.velocity);\n}\n\nstatic void PM_Accelerate(vec3_t wishdir, float wishspeed, float accel)\n{\n\tfloat addspeed, accelspeed, currentspeed;\n\n\tif (pmove.pm_type == PM_DEAD)\n\t\treturn;\n\tif (pmove.waterjumptime)\n\t\treturn;\n\n\tcurrentspeed = DotProduct(pmove.velocity, wishdir);\n\taddspeed = wishspeed - currentspeed;\n\tif (addspeed <= 0)\n\t\treturn;\n\taccelspeed = accel * pm_frametime * wishspeed;\n\tif (accelspeed > addspeed)\n\t\taccelspeed = addspeed;\n\n\tVectorMA(pmove.velocity, accelspeed, wishdir, pmove.velocity);\n}\n\n#ifndef SERVERONLY\n#ifdef EXPERIMENTAL_SHOW_ACCELERATION\nqbool player_in_air = false;\nfloat cosinus_val = 0.f;\nqbool flag_player_pmove;\n#endif\n#endif\n\nstatic void PM_AirAccelerate(vec3_t wishdir, float wishspeed, float accel)\n{\n\tfloat addspeed, accelspeed, currentspeed, wishspd = wishspeed;\n\tfloat originalspeed = 0.0, newspeed = 0.0, speedcap = 0.0;\n\n\tif (pmove.pm_type == PM_DEAD)\n\t\treturn;\n\tif (pmove.waterjumptime)\n\t\treturn;\n\n\tif (movevars.bunnyspeedcap > 0)\n\t\toriginalspeed = sqrt(pmove.velocity[0] * pmove.velocity[0] + pmove.velocity[1] * pmove.velocity[1]);\n\n\twishspd = min(wishspd, 30);\n\tcurrentspeed = DotProduct(pmove.velocity, wishdir);\n\taddspeed = wishspd - currentspeed;\n\n#ifdef EXPERIMENTAL_SHOW_ACCELERATION\n\tif (flag_player_pmove) {\n\t\tcosinus_val = 0.f;\n\t\toriginalspeed = sqrt(pmove.velocity[0] * pmove.velocity[0] + pmove.velocity[1] * pmove.velocity[1]);\n\t\tif (originalspeed > 1.f) {\n\t\t\tcosinus_val = currentspeed / originalspeed;\n\t\t}\n\n\t\tplayer_in_air = true;\n\t}\n#endif\n\n\tif (addspeed <= 0)\n\t\treturn;\n\taccelspeed = accel * wishspeed * pm_frametime;\n\taccelspeed = min(accelspeed, addspeed);\n\n\tVectorMA(pmove.velocity, accelspeed, wishdir, pmove.velocity);\n\n\tif (movevars.bunnyspeedcap > 0) {\n\t\tnewspeed = sqrt(pmove.velocity[0] * pmove.velocity[0] + pmove.velocity[1] * pmove.velocity[1]);\n\t\tif (newspeed > originalspeed) {\n\t\t\tspeedcap = movevars.maxspeed * movevars.bunnyspeedcap;\n\t\t\tif (newspeed > speedcap) {\n\t\t\t\tif (originalspeed < speedcap)\n\t\t\t\t\toriginalspeed = speedcap;\n\t\t\t\tpmove.velocity[0] *= originalspeed / newspeed;\n\t\t\t\tpmove.velocity[1] *= originalspeed / newspeed;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic int PM_WaterMove(void)\n{\n\tvec3_t wishvel, wishdir;\n\tfloat wishspeed;\n\tint i;\n\n\t// user intentions\n\tfor (i = 0; i < 3; i++)\n\t\twishvel[i] = pm_forward[i] * pmove.cmd.forwardmove + pm_right[i] * pmove.cmd.sidemove;\n\n\tif (pmove.pm_type != PM_FLY && !pmove.cmd.forwardmove && !pmove.cmd.sidemove && !pmove.cmd.upmove)\n\t\twishvel[2] -= 60; // drift towards bottom\n\telse\n\t\twishvel[2] += pmove.cmd.upmove;\n\n\tVectorCopy(wishvel, wishdir);\n\twishspeed = VectorNormalize(wishdir);\n\n\tif (wishspeed > movevars.maxspeed) {\n\t\tVectorScale(wishvel, movevars.maxspeed / wishspeed, wishvel);\n\t\twishspeed = movevars.maxspeed;\n\t}\n\twishspeed *= 0.7;\n\n\t// water acceleration\n\tPM_Accelerate(wishdir, wishspeed, movevars.wateraccelerate);\n\n\treturn PM_StepSlideMove(false);\n}\n\nstatic int PM_FlyMove(void)\n{\n\tvec3_t wishvel, wishdir;\n\tfloat wishspeed;\n\tint i;\n\n\tfor (i = 0; i < 3; i++)\n\t\twishvel[i] = pm_forward[i] * pmove.cmd.forwardmove + pm_right[i] * pmove.cmd.sidemove;\n\n\twishvel[2] += pmove.cmd.upmove;\n\n\tVectorCopy(wishvel, wishdir);\n\twishspeed = VectorNormalize(wishdir);\n\n\tif (wishspeed > movevars.maxspeed) {\n\t\tVectorScale(wishvel, movevars.maxspeed / wishspeed, wishvel);\n\t\twishspeed = movevars.maxspeed;\n\t}\n\n\tPM_Accelerate(wishdir, wishspeed, movevars.accelerate);\n\treturn PM_StepSlideMove(false);\n}\n\nstatic int PM_AirMove(void)\n{\n\tfloat fmove, smove, wishspeed;\n\tvec3_t wishvel, wishdir;\n\tint i;\n\n\tfmove = pmove.cmd.forwardmove;\n\tsmove = pmove.cmd.sidemove;\n\n\tpm_forward[2] = 0;\n\tpm_right[2] = 0;\n\tVectorNormalize(pm_forward);\n\tVectorNormalize(pm_right);\n\n\tfor (i = 0; i < 2; i++)\n\t\twishvel[i] = pm_forward[i] * fmove + pm_right[i] * smove;\n\twishvel[2] = 0;\n\n\tVectorCopy(wishvel, wishdir);\n\twishspeed = VectorNormalize(wishdir);\n\n\t// clamp to server defined max speed\n\tif (wishspeed > movevars.maxspeed) {\n\t\tVectorScale(wishvel, movevars.maxspeed / wishspeed, wishvel);\n\t\twishspeed = movevars.maxspeed;\n\t}\n\n\tif (pmove.onground) {\n\t\tif (movevars.slidefix) {\n\t\t\tpmove.velocity[2] = min(pmove.velocity[2], 0); // bound above by 0\n\t\t\tPM_Accelerate(wishdir, wishspeed, movevars.accelerate);\n\t\t\t// add gravity\n\t\t\tpmove.velocity[2] -= movevars.entgravity * movevars.gravity * pm_frametime;\n\t\t}\n\t\telse {\n\t\t\tpmove.velocity[2] = 0;\n\t\t\tPM_Accelerate(wishdir, wishspeed, movevars.accelerate);\n\t\t}\n\n\t\tif (!pmove.velocity[0] && !pmove.velocity[1]) {\n\t\t\tpmove.velocity[2] = 0;\n\t\t\treturn 0;\n\t\t}\n\n\t\treturn PM_StepSlideMove(false);\n\t}\n\telse {\n\t\tint blocked;\n\t\t// not on ground, so little effect on velocity\n\t\tPM_AirAccelerate(wishdir, wishspeed, movevars.accelerate);\n\n\t\t// add gravity\n\t\tpmove.velocity[2] -= movevars.entgravity * movevars.gravity * pm_frametime;\n\n\t\tif (movevars.airstep)\n\t\t\tblocked = PM_StepSlideMove(true);\n\t\telse\n\t\t\tblocked = PM_SlideMove();\n\n\t\tif (movevars.pground) {\n\t\t\tif (blocked & BLOCKED_FLOOR) {\n\t\t\t\tpmove.onground = true;\n\t\t\t}\n\t\t}\n\n\t\treturn blocked;\n\t}\n}\n\n#define MAXGROUNDSPEED_DEFAULT 180\n#define MAXGROUNDSPEED_MAXIMUM 240\n\nstatic void PM_RampEdgeAdjustNormal(vec3_t normal, int flags)\n{\n\tint i;\n\n\tfor (i = 0; i < 3; ++i) {\n\t\tif (flags & (PHYSICSNORMAL_FLIPX << i)) {\n\t\t\tif (pmove.velocity[i] < 0) {\n\t\t\t\tnormal[i] = -normal[i];\n\t\t\t}\n\t\t\telse if (pmove.velocity[0] == 0) {\n\t\t\t\tnormal[i] = 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\n#define PM_FarFromGround(trace) (((trace).fraction == 1 || (trace).plane.normal[2] < MIN_STEP_NORMAL))\n\nstatic trace_t PM_CategorizePositionRunTrace(vec3_t point)\n{\n\ttrace_t trace = { 0 };\n\n\ttrace = PM_PlayerTrace(pmove.origin, point);\n\tif (!PM_FarFromGround(trace)) {\n\t\tVectorCopy(trace.plane.normal, groundnormal);\n\t}\n\n\treturn trace;\n}\n\nvoid PM_CategorizePosition(void)\n{\n\ttrace_t trace = { 0 };\n\tvec3_t point;\n\tint cont;\n\tmphysicsnormal_t ground;\n\n\tpmove.maxgroundspeed = MAXGROUNDSPEED_DEFAULT;\n\n\t// if the player hull point one unit down is solid, the player is on ground\n\t// see if standing on something solid\n\tpoint[0] = pmove.origin[0];\n\tpoint[1] = pmove.origin[1];\n\tpoint[2] = pmove.origin[2] - 1;\n\n\tif (movevars.rampjump) {\n\t\t// Increase speed limit for player as steepness of the floor increases\n\t\ttrace = PM_CategorizePositionRunTrace(point);\n\t\tground = CM_PhysicsNormal(trace.physicsnormal);\n\n\t\tif (ground.flags & PHYSICSNORMAL_SET) {\n\t\t\tVectorCopy(ground.normal, groundnormal);\n\t\t\tPM_RampEdgeAdjustNormal(groundnormal, ground.flags);\n\t\t\tVectorNormalize(groundnormal);\n\n\t\t\tif (movevars.rampjump && !PM_FarFromGround(trace) && trace.e.entnum == 0 && groundnormal[2] > MIN_STEP_NORMAL && groundnormal[2] < 1 && DotProduct(groundnormal, pmove.velocity) < MAX_JUMPFIX_DOTPRODUCT) {\n\t\t\t\t// They are moving up a ramp, increase maxspeed to check if we keep them on it\n\t\t\t\tfloat range = 1.0 - asin(groundnormal[2]) * 2 / M_PI; // asin() returns 0...PI/2, so range is [1...0]\n\n\t\t\t\t// Max out at 45 degree ramps...\n\t\t\t\trange = min(range, 0.5f) * 2;\n\n\t\t\t\tpmove.maxgroundspeed += (MAXGROUNDSPEED_MAXIMUM - MAXGROUNDSPEED_DEFAULT) * range;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (pmove.velocity[2] > pmove.maxgroundspeed) {\n\t\tpmove.onground = false;\n\t}\n\telse if (!movevars.pground || pmove.onground) {\n\t\tif (!movevars.rampjump) {\n\t\t\ttrace = PM_CategorizePositionRunTrace(point);\n\t\t}\n\t\tif (PM_FarFromGround(trace)) {\n\t\t\tpmove.onground = false;\n\t\t}\n\t\telse {\n\t\t\tpmove.onground = true;\n\t\t\tpmove.groundent = trace.e.entnum;\n\t\t\tpmove.waterjumptime = 0;\n\t\t}\n\n\t\t// standing on an entity other than the world\n\t\tif (trace.e.entnum > 0) {\n\t\t\tPM_AddTouchedEnt(trace.e.entnum);\n\t\t}\n\t}\n\n\t// get waterlevel\n\tpmove.waterlevel = 0;\n\tpmove.watertype = CONTENTS_EMPTY;\n\n\tpoint[2] = pmove.origin[2] + player_mins[2] + 1;\n\tcont = PM_PointContents (point);\n\tif (cont <= CONTENTS_WATER) {\n\t\tpmove.watertype = cont;\n\t\tpmove.waterlevel = 1;\n\t\tpoint[2] = pmove.origin[2] + (player_mins[2] + player_maxs[2]) * 0.5;\n\t\tcont = PM_PointContents (point);\n\t\tif (cont <= CONTENTS_WATER) {\n\t\t\tpmove.waterlevel = 2;\n\t\t\tpoint[2] = pmove.origin[2] + 22;\n\t\t\tcont = PM_PointContents (point);\n\t\t\tif (cont <= CONTENTS_WATER) {\n\t\t\t\tpmove.waterlevel = 3;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!movevars.pground) {\n\t\tif (pmove.onground && pmove.pm_type != PM_FLY && pmove.waterlevel < 2) {\n\t\t\t// snap to ground so that we can't jump higher than we're supposed to\n\t\t\tif (!trace.startsolid && !trace.allsolid) {\n\t\t\t\tVectorCopy(trace.endpos, pmove.origin);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void PM_CheckJump (void)\n{\n\tif (pmove.pm_type == PM_FLY)\n\t\treturn;\n\n\tif (pmove.pm_type == PM_DEAD) {\n\t\tpmove.jump_held = true; // don't jump on respawn\n\t\treturn;\n\t}\n\n\tif (!(pmove.cmd.buttons & BUTTON_JUMP)) {\n\t\tpmove.jump_held = false;\n\t\treturn;\n\t}\n\n\tif (pmove.waterjumptime) {\n\t\treturn;\n\t}\n\n\tif (pmove.waterlevel >= 2) {\n\t\t// swimming, not jumping\n\t\tpmove.onground = false;\n\n\t\tif (pmove.watertype == CONTENTS_WATER)\n\t\t\tpmove.velocity[2] = 100;\n\t\telse if (pmove.watertype == CONTENTS_SLIME)\n\t\t\tpmove.velocity[2] = 80;\n\t\telse\n\t\t\tpmove.velocity[2] = 50;\n\t\treturn;\n\t}\n\n\tif (!pmove.onground)\n\t\treturn; // in air, so no effect\n\n\tif (pmove.jump_held && !pmove.jump_msec)\n\t\treturn; // don't pogo stick\n\n\tif (!movevars.pground) {\n\t\t// check for jump bug\n\t\t// groundplane normal was set in the call to PM_CategorizePosition\n\t\tif ((movevars.rampjump || pmove.velocity[2] < 0) && DotProduct(pmove.velocity, groundnormal) < MAX_JUMPFIX_DOTPRODUCT) {\n\t\t\t// pmove.velocity is pointing into the ground, clip it\n\t\t\tPM_ClipVelocity(pmove.velocity, groundnormal, pmove.velocity, 1);\n\t\t}\n\t}\n\n\tpmove.onground = false;\n\tif (pmove.maxgroundspeed > MAXGROUNDSPEED_DEFAULT && pmove.velocity[2] > MAXGROUNDSPEED_DEFAULT) {\n\t\t// we adjusted maxspeed to keep them on ground, need to reduce velocity here so they can't jump too high\n\t\tpmove.velocity[2] = MAXGROUNDSPEED_DEFAULT;\n\t}\n\tpmove.velocity[2] += 270;\n\n\tif (movevars.ktjump > 0) {\n\t\t// meag: pmove.velocity[2] = max(pmove.velocity[2], 270); (?)\n\t\tif (movevars.ktjump > 1)\n\t\t\tmovevars.ktjump = 1;\n\t\tif (pmove.velocity[2] < 270)\n\t\t\tpmove.velocity[2] = pmove.velocity[2] * (1 - movevars.ktjump) + 270 * movevars.ktjump;\n\t}\n\n\tpmove.jump_held = true; // don't jump again until released\n\tpmove.jump_msec = pmove.cmd.msec;\n}\n\nstatic void PM_CheckWaterJump (void)\n{\n\tvec3_t flatforward;\n\tvec3_t spot;\n\tint cont;\n\n\tif (pmove.waterjumptime)\n\t\treturn;\n\n\t// don't hop out if we just jumped in\n\tif (pmove.velocity[2] < -180)\n\t\treturn;\n\n\t// see if near an edge\n\tflatforward[0] = pm_forward[0];\n\tflatforward[1] = pm_forward[1];\n\tflatforward[2] = 0;\n\tVectorNormalize (flatforward);\n\n\tVectorMA (pmove.origin, 24, flatforward, spot);\n\tspot[2] += 8;\n\tcont = PM_PointContents_AllBSPs (spot);\n\tif (cont != CONTENTS_SOLID)\n\t\treturn;\n\tspot[2] += 24;\n\tcont = PM_PointContents_AllBSPs (spot);\n\tif (cont != CONTENTS_EMPTY)\n\t\treturn;\n\t// jump out of water\n\tVectorScale (flatforward, 50, pmove.velocity);\n\tpmove.velocity[2] = 310;\n\tpmove.waterjumptime = 2; // safety net\n\tpmove.jump_held = true; // don't jump again until released\n}\n\n//If pmove.origin is in a solid position,\n//try nudging slightly on all axis to\n//allow for the cut precision of the net coordinates\nstatic void PM_NudgePosition (void)\n{\n\tstatic int sign[3] = {0, -1, 1};\n\tint x, y, z, i;\n\tvec3_t base;\n\n\tVectorCopy (pmove.origin, base);\n\n\tfor (i = 0; i < 3; i++)\n\t\tpmove.origin[i] = ((int) (pmove.origin[i] * 8)) * 0.125;\n\n\tfor (z = 0; z <= 2; z++) {\n\t\tfor (y = 0; y <= 2; y++) {\n\t\t\tfor (x = 0; x <= 2; x++) {\n\t\t\t\tpmove.origin[0] = base[0] + (sign[x] * 0.125);\n\t\t\t\tpmove.origin[1] = base[1] + (sign[y] * 0.125);\n\t\t\t\tpmove.origin[2] = base[2] + (sign[z] * 0.125);\n\t\t\t\tif (PM_TestPlayerPosition (pmove.origin))\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t// some maps spawn the player several units into the ground\n\tfor (z = 1; z <= 18; z++) {\n\t\tpmove.origin[0] = base[0];\n\t\tpmove.origin[1] = base[1];\n\t\tpmove.origin[2] = base[2] + z;\n\t\tif (PM_TestPlayerPosition(pmove.origin))\n\t\t\treturn;\n\t}\n\n\tVectorCopy (base, pmove.origin);\n}\n\nstatic void PM_SpectatorMove(void)\n{\n\tfloat newspeed, currentspeed, addspeed, accelspeed, wishspeed;\n\tfloat speed, drop, friction, control, fmove, smove;\n\tvec3_t wishvel, wishdir;\n\tint i;\n\n\t// friction\n\tspeed = VectorLength(pmove.velocity);\n\tif (speed < 1) {\n\t\tVectorClear(pmove.velocity);\n\t}\n\telse {\n\t\tfriction = movevars.friction * 1.5; // extra friction\n\t\tcontrol = speed < movevars.stopspeed ? movevars.stopspeed : speed;\n\t\tdrop = control * friction * pm_frametime;\n\n\t\t// scale the velocity\n\t\tnewspeed = speed - drop;\n\t\tif (newspeed < 0) {\n\t\t\tnewspeed = 0;\n\t\t}\n\t\tnewspeed /= speed;\n\n\t\tVectorScale(pmove.velocity, newspeed, pmove.velocity);\n\t}\n\n\t// accelerate\n\tfmove = pmove.cmd.forwardmove;\n\tsmove = pmove.cmd.sidemove;\n\n\tVectorNormalize(pm_forward);\n\tVectorNormalize(pm_right);\n\n\tfor (i = 0; i < 3; i++) {\n\t\twishvel[i] = pm_forward[i] * fmove + pm_right[i] * smove;\n\t}\n\twishvel[2] += pmove.cmd.upmove;\n\n\tVectorCopy(wishvel, wishdir);\n\twishspeed = VectorNormalize(wishdir);\n\n\t// clamp to server defined max speed\n\tif (wishspeed > movevars.spectatormaxspeed) {\n\t\tVectorScale(wishvel, movevars.spectatormaxspeed / wishspeed, wishvel);\n\t\twishspeed = movevars.spectatormaxspeed;\n\t}\n\n\tcurrentspeed = DotProduct(pmove.velocity, wishdir);\n\taddspeed = wishspeed - currentspeed;\n\n\t// Buggy QW spectator mode, kept for compatibility\n\tif (pmove.pm_type == PM_OLD_SPECTATOR) {\n\t\tif (addspeed <= 0) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (addspeed > 0) {\n\t\taccelspeed = movevars.accelerate * pm_frametime * wishspeed;\n\t\taccelspeed = min(accelspeed, addspeed);\n\t\tVectorMA(pmove.velocity, accelspeed, wishdir, pmove.velocity);\n\t}\n\n\t// move\n\tVectorMA(pmove.origin, pm_frametime, pmove.velocity, pmove.origin);\n}\n\n//Returns with origin, angles, and velocity modified in place.\n//Numtouch and touchindex[] will be set if any of the physents were contacted during the move.\nint PM_PlayerMove(void)\n{\n\tint blocked = 0;\n\n#ifndef SERVERONLY\n#ifdef EXPERIMENTAL_SHOW_ACCELERATION\n\tif (flag_player_pmove) player_in_air = false;\n#endif\n#endif\n\n\tpm_frametime = pmove.cmd.msec * 0.001;\n\tpmove.numtouch = 0;\n\n\tif (pmove.pm_type == PM_NONE || pmove.pm_type == PM_LOCK) {\n\t\tPM_CategorizePosition();\n\t\treturn 0;\n\t}\n\n\t// take angles directly from command\n\tVectorCopy(pmove.cmd.angles, pmove.angles);\n\tAngleVectors(pmove.angles, pm_forward, pm_right, NULL);\n\n\tif (pmove.pm_type == PM_SPECTATOR || pmove.pm_type == PM_OLD_SPECTATOR) {\n\t\tPM_SpectatorMove();\n\t\tpmove.onground = false;\n\t\treturn 0;\n\t}\n\n\tPM_NudgePosition();\n\n\t// set onground, watertype, and waterlevel\n\tPM_CategorizePosition();\n\n\tif (pmove.waterlevel == 2 && pmove.pm_type != PM_FLY)\n\t\tPM_CheckWaterJump();\n\n\tif (pmove.velocity[2] < 0 || pmove.pm_type == PM_DEAD)\n\t\tpmove.waterjumptime = 0;\n\n\tif (pmove.waterjumptime) {\n\t\tpmove.waterjumptime -= pm_frametime;\n\t\tif (pmove.waterjumptime < 0)\n\t\t\tpmove.waterjumptime = 0;\n\t}\n\n\tif (pmove.jump_msec) {\n\t\tpmove.jump_msec += pmove.cmd.msec;\n\t\tif (pmove.jump_msec > 50)\n\t\t\tpmove.jump_msec = 0;\n\t}\n\n\tPM_CheckJump();\n\n\tPM_Friction();\n\n\tif (pmove.waterlevel >= 2)\n\t\tblocked = PM_WaterMove();\n\telse if (pmove.pm_type == PM_FLY)\n\t\tblocked = PM_FlyMove();\n\telse\n\t\tblocked = PM_AirMove();\n\n\t// set onground, watertype, and waterlevel for final spot\n\tPM_CategorizePosition();\n\n\tif (!movevars.pground) {\n\t\t// this is to make sure landing sound is not played twice\n\t\t// and falling damage is calculated correctly\n\t\tif (pmove.onground && pmove.velocity[2] < -300) {\n\t\t\tif (DotProduct(pmove.velocity, groundnormal) < MAX_JUMPFIX_DOTPRODUCT) {\n\t\t\t\tPM_ClipVelocity(pmove.velocity, groundnormal, pmove.velocity, 1);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn blocked;\n}\n"
  },
  {
    "path": "src/pmove.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n\n#ifndef __PMOVE_H__\n#define __PMOVE_H__\n\n#define\tMAX_PHYSENTS 64 \n\ntypedef struct {\n\tvec3_t\t\torigin;\n\tcmodel_t\t*model;\t\t// only for bsp models\n\tvec3_t\t\tmins, maxs;\t// only for non-bsp models\n\tint\t\t\tinfo;\t\t// for client or server to identify\n#if defined(FTE_PEXT_TRANS)\n\tqbool\t\tis_transparent;\n#endif\n} physent_t;\n\ntypedef enum {\n\tPM_NORMAL,              // normal ground movement\n\tPM_OLD_SPECTATOR,       // fly, no clip to world (QW bug)\n\tPM_SPECTATOR,           // fly, no clip to world\n\tPM_DEAD,                // no acceleration\n\tPM_FLY,                 // fly, bump into walls\n\tPM_NONE,                // can't move\n\tPM_LOCK                 // server controls origin and view angles\n} pmtype_t;\n\ntypedef struct {\n\t// player state\n\tvec3_t\t\torigin;\n\tvec3_t\t\tangles;\n\tvec3_t\t\tvelocity;\n\tqbool\t\tjump_held;\n\tint\t\t\tjump_msec; // msec since last jump\n\tfloat\t\twaterjumptime;\n\tint\t\t\tpm_type;\n\n\t// world state\n\tint\t\t\tnumphysent;\n\tphysent_t\tphysents[MAX_PHYSENTS]; // 0 should be the world\n\n\t// input\n\tusercmd_t\tcmd;\n\n\t// results\n\tint\t\t\tnumtouch;\n\tint\t\t\ttouchindex[MAX_PHYSENTS];\n\tqbool\t\tonground;\n\tint\t\t\tgroundent; // index in physents array, only valid when onground is true\n\tint\t\t\twaterlevel;\n\tint\t\t\twatertype;\n\n\tint         maxgroundspeed;\n} playermove_t;\n\ntypedef struct {\n\tfloat\tgravity;\n\tfloat\tstopspeed;\n\tfloat\tmaxspeed;\n\tfloat\tspectatormaxspeed;\n\tfloat\taccelerate;\n\tfloat\tairaccelerate;\n\tfloat\twateraccelerate;\n\tfloat\tfriction;\n\tfloat\twaterfriction;\n\tfloat\tentgravity;\n\tfloat\tbunnyspeedcap;\n\tfloat\tktjump;\n\tqbool\tslidefix; // NQ-style movement down ramps\n\tqbool\tairstep;\n\tqbool\tpground; // NQ-style \"onground\" flag handling.\n\tint     rampjump; // if set, all vertical velocity clipped by groundplane during jump frame.  If 0, only when falling (standard jumpfix)\n\tint     safestrafe; // sv_safestrafe value from server\n} movevars_t;\n\nextern movevars_t movevars;\nextern playermove_t pmove;\n\nint PM_PlayerMove (void);\n\nint PM_PointContents (vec3_t point);\nint PM_PointContents_AllBSPs (vec3_t p);\nvoid PM_CategorizePosition (void);\nqbool PM_TestPlayerPosition (vec3_t point);\ntrace_t PM_PlayerTrace (vec3_t start, vec3_t end);\ntrace_t PM_TraceLine (vec3_t start, vec3_t end);\n\n#define MIN_STEP_NORMAL 0.7 // roughly 45 degrees\n\n#endif /* !__PMOVE_H__ */\n"
  },
  {
    "path": "src/pmovetst.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n\n#ifdef SERVERONLY\n#include \"qwsvdef.h\"\n#else\n#include \"quakedef.h\"\n#include \"pmove.h\"\n#endif\n\nextern\tvec3_t player_mins;\nextern\tvec3_t player_maxs;\n\n\nstatic void PM_TraceBounds (vec3_t start, vec3_t end, vec3_t boxmins, vec3_t boxmaxs)\n{\n\tint i;\n\n\tfor (i = 0; i < 3; i++) {\n\t\tif (end[i] > start[i]) {\n\t\t\tboxmins[i] = start[i] - 1;\n\t\t\tboxmaxs[i] = end[i] + 1;\n\t\t} else {\n\t\t\tboxmins[i] = end[i] - 1;\n\t\t\tboxmaxs[i] = start[i] + 1;\n\t\t}\n\t}\n}\n\nstatic qbool PM_CullTraceBox(vec3_t mins, vec3_t maxs, vec3_t offset, vec3_t emins, vec3_t emaxs, vec3_t hullmins, vec3_t hullmaxs) {\n\treturn\n\t\t(\tmins[0] + hullmins[0] > offset[0] + emaxs[0] || maxs[0] + hullmaxs[0] < offset[0] + emins[0] ||\n\t\t\tmins[1] + hullmins[1] > offset[1] + emaxs[1] || maxs[1] + hullmaxs[1] < offset[1] + emins[1] ||\n\t\t\tmins[2] + hullmins[2] > offset[2] + emaxs[2] || maxs[2] + hullmaxs[2] < offset[2] + emins[2]\n\t\t);\n}\n\n/*\n==================\nPM_PointContents\n==================\n*/\nint PM_PointContents (vec3_t p)\n{\n\thull_t *hull;\n\t\n\t// Safety check for NULL model in case entity updates arrive out of order\n\tif (!pmove.physents[0].model)\n\t\treturn CONTENTS_EMPTY;\n\t\n\thull = &pmove.physents[0].model->hulls[0];\n\treturn CM_HullPointContents (hull, hull->firstclipnode, p);\n}\n\n/*\n==================\nPM_PointContents_AllBSPs\n\nChecks world and bsp entities, but not bboxes (like traceline with nomonsters set)\nFor waterjump test\n==================\n*/\nint PM_PointContents_AllBSPs (vec3_t p)\n{\n\tint         i;\n\tphysent_t   *pe;\n\thull_t      *hull;\n\tvec3_t      test;\n\tint         result, final;\n\n\tfinal = CONTENTS_EMPTY;\n\tfor (i = 0; i < pmove.numphysent; i++)\n\t{\n\t\tpe = &pmove.physents[i];\n\t\tif (!pe->model)\n\t\t\tcontinue;\t// ignore non-bsp\n\t\thull = &pmove.physents[i].model->hulls[0];\n\t\tVectorSubtract (p, pe->origin, test);\n\t\tresult = CM_HullPointContents (hull, hull->firstclipnode, test);\n\t\tif (result == CONTENTS_SOLID)\n\t\t\treturn CONTENTS_SOLID;\n\t\tif (final == CONTENTS_EMPTY)\n\t\t\tfinal = result;\n\t}\n\n\treturn final;\n}\n\n/*\n================\nPM_TestPlayerPosition\n\nReturns false if the given player position is not valid (in solid)\n================\n*/\nqbool PM_TestPlayerPosition (vec3_t pos)\n{\n\tint       i;\n\tphysent_t *pe;\n\tvec3_t    mins, maxs, offset, test;\n\thull_t    *hull;\n\n\tfor (i = 0; i < pmove.numphysent; i++) {\n\t\tpe = &pmove.physents[i];\n\t\t// get the clipping hull\n\t\tif (pe->model) {\n\t\t\thull = &pmove.physents[i].model->hulls[1];\n\t\t\tVectorSubtract(hull->clip_mins, player_mins, offset);\n\t\t\tVectorAdd(offset, pe->origin, offset);\n\t\t}\n\t\telse {\n\t\t\tVectorSubtract(pe->mins, player_maxs, mins);\n\t\t\tVectorSubtract(pe->maxs, player_mins, maxs);\n\t\t\thull = CM_HullForBox(mins, maxs);\n\t\t\tVectorCopy(pe->origin, offset);\n\t\t}\n\n\t\tVectorSubtract(pos, offset, test);\n\n\t\tif (CM_HullPointContents(hull, hull->firstclipnode, test) == CONTENTS_SOLID) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n================\nPM_PlayerTrace\n================\n*/\ntrace_t PM_PlayerTrace (vec3_t start, vec3_t end)\n{\n\ttrace_t   trace, total;\n\tvec3_t    offset;\n\tvec3_t    start_l, end_l;\n\thull_t    *hull;\n\tint       i;\n\tphysent_t *pe;\n\tvec3_t    mins, maxs, tracemins, tracemaxs;\n\n\t// fill in a default trace\n\tmemset (&total, 0, sizeof(trace_t));\n\ttotal.fraction = 1;\n\ttotal.e.entnum = -1;\n\tVectorCopy (end, total.endpos);\n\n\tPM_TraceBounds(start, end, tracemins, tracemaxs);\n\n\tfor (i = 0; i < pmove.numphysent; i++) {\n\t\tpe = &pmove.physents[i];\n\n\t\t// get the clipping hull\n\t\tif (pe->model) {\n\t\t\thull = &pmove.physents[i].model->hulls[1];\n\n\t\t\tif (i > 0 && PM_CullTraceBox(tracemins, tracemaxs, pe->origin, pe->model->mins, pe->model->maxs, hull->clip_mins, hull->clip_maxs)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tVectorSubtract(hull->clip_mins, player_mins, offset);\n\t\t\tVectorAdd(offset, pe->origin, offset);\n\t\t}\n\t\telse {\n\t\t\tVectorSubtract(pe->mins, player_maxs, mins);\n\t\t\tVectorSubtract(pe->maxs, player_mins, maxs);\n\n\t\t\tif (PM_CullTraceBox(tracemins, tracemaxs, pe->origin, mins, maxs, vec3_origin, vec3_origin)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\thull = CM_HullForBox(mins, maxs);\n\t\t\tVectorCopy(pe->origin, offset);\n\t\t}\n\n\t\tVectorSubtract(start, offset, start_l);\n\t\tVectorSubtract(end, offset, end_l);\n\n\t\t// trace a line through the appropriate clipping hull\n\t\ttrace = CM_HullTrace(hull, start_l, end_l);\n\n\t\t// fix trace up by the offset\n\t\tVectorAdd(trace.endpos, offset, trace.endpos);\n\n\t\tif (trace.allsolid) {\n\t\t\ttrace.startsolid = true;\n\t\t}\n\t\tif (trace.startsolid) {\n\t\t\ttrace.fraction = 0;\n\t\t}\n\n\t\t// did we clip the move?\n\t\tif (trace.fraction < total.fraction) {\n\t\t\ttotal = trace;\n\t\t\ttotal.e.entnum = i;\n\t\t}\n\t}\n\n\treturn total;\n}\n\n/*\n================\nPM_TraceLine\n================\n*/\ntrace_t PM_TraceLine (vec3_t start, vec3_t end)\n{\n\tint i;\n\thull_t *hull;\n\tphysent_t *pe;\n\tvec3_t offset, start_l, end_l;\n\ttrace_t trace, total;\n\n\t// fill in a default trace\n\tmemset (&total, 0, sizeof(trace_t));\n\ttotal.fraction = 1;\n\ttotal.e.entnum = -1;\n\tVectorCopy (end, total.endpos);\n\n\tfor (i = 0; i < pmove.numphysent; i++) {\n\t\tpe = &pmove.physents[i];\n\n#if defined(FTE_PEXT_TRANS)\n\t\tif (cls.fteprotocolextensions & FTE_PEXT_TRANS && pe->is_transparent) {\n\t\t\tcontinue;\n\t\t}\n#endif\n\n\t\t// get the clipping hull\n\t\thull = (pe->model) ? (&pmove.physents[i].model->hulls[0]) : (CM_HullForBox(pe->mins, pe->maxs));\n\n\t\t// PM_HullForEntity (ent, mins, maxs, offset);\n\t\tVectorCopy(pe->origin, offset);\n\n\t\tVectorSubtract(start, offset, start_l);\n\t\tVectorSubtract(end, offset, end_l);\n\n\t\t// trace a line through the appropriate clipping hull\n\t\ttrace = CM_HullTrace(hull, start_l, end_l);\n\n\t\t// fix trace up by the offset\n\t\tVectorAdd(trace.endpos, offset, trace.endpos);\n\n\t\tif (trace.allsolid) {\n\t\t\ttrace.startsolid = true;\n\t\t}\n\t\tif (trace.startsolid) {\n\t\t\ttrace.fraction = 0;\n\t\t}\n\n\t\t// did we clip the move?\n\t\tif (trace.fraction < total.fraction) {\n\t\t\ttotal = trace;\n\t\t\ttotal.e.entnum = i;\n\t\t}\n\t}\n\n\treturn total;\n}\n"
  },
  {
    "path": "src/pr2.h",
    "content": "/*\n *  QW262\n *  Copyright (C) 2004  [sd] angel\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by\n *  the Free Software Foundation; either version 2 of the License, or\n *  (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n *\n *\n */\n\n#ifndef __PR2_H__\n#define __PR2_H__\n\n\nintptr_t PR2_GameSystemCalls( intptr_t *args );\nextern cvar_t sv_progtype;\nextern vm_t* sv_vm;\n\n\nvoid\t\tPR2_Init(void);\n#define PR_Init PR2_Init\nvoid\t\tPR2_UnLoadProgs(void);\n#define PR_UnLoadProgs PR2_UnLoadProgs\nvoid\t\tPR2_LoadProgs(void);\n#define PR_LoadProgs PR2_LoadProgs\nvoid\t\tPR2_GameStartFrame(qbool isBotFrame);\n#define PR_GameStartFrame PR2_GameStartFrame\nvoid\t\tPR2_LoadEnts(char *data);\n#define PR_LoadEnts PR2_LoadEnts\nvoid\t\tPR2_GameClientConnect(int spec);\n#define PR_GameClientConnect PR2_GameClientConnect\nvoid\t\tPR2_GamePutClientInServer(int spec);\n#define PR_GamePutClientInServer PR2_GamePutClientInServer\nvoid\t\tPR2_GameClientDisconnect(int spec);\n#define PR_GameClientDisconnect PR2_GameClientDisconnect\nvoid\t\tPR2_GameClientPreThink(int spec);\n#define PR_GameClientPreThink PR2_GameClientPreThink\nvoid\t\tPR2_GameClientPostThink(int spec);\n#define PR_GameClientPostThink PR2_GameClientPostThink\nqbool\t\tPR2_ClientCmd(void);\n#define PR_ClientCmd PR2_ClientCmd\nvoid\t\tPR2_ClientKill(void);\n#define PR_ClientKill PR2_ClientKill\nqbool\t\tPR2_ClientSay(int isTeamSay, char *message);\n#define PR_ClientSay PR2_ClientSay\nvoid\t\tPR2_GameSetNewParms(void);\n#define PR_GameSetNewParms PR2_GameSetNewParms\nvoid\t\tPR2_GameSetChangeParms(void);\n#define PR_GameSetChangeParms PR2_GameSetChangeParms\nvoid\t\tPR2_EdictTouch(func_t f);\n#define PR_EdictTouch PR2_EdictTouch\nvoid\t\tPR2_EdictThink(func_t f);\n#define PR_EdictThink PR2_EdictThink\nvoid\t\tPR2_EdictBlocked(func_t f);\n#define PR_EdictBlocked PR2_EdictBlocked\nqbool \t\tPR2_UserInfoChanged(int after);\n#define PR_UserInfoChanged PR2_UserInfoChanged\nvoid \t\tPR2_GameShutDown(void);\n#define PR_GameShutDown PR2_GameShutDown\nvoid \t\tPR2_GameConsoleCommand(void);\nvoid\t\tPR2_PausedTic(float duration);\n#define PR_PausedTic PR2_PausedTic\n\nchar*\t\tPR2_GetString(intptr_t reference);\n//#define\t\tPR_GetString PR2_GetString\nchar*       PR2_GetEntityString(string_t reference);\n#define     PR_GetEntityString PR2_GetEntityString\nvoid        PR2_SetEntityString(edict_t* ed, string_t* target, char* value);\nvoid        PR2_SetGlobalString(string_t* target, char* value);\n#define     PR_SetEntityString(entity, address, value) PR2_SetEntityString(entity, &address, value)\n#define     PR_SetGlobalString(address, value) PR2_SetGlobalString(&address, value)\nvoid\t\tPR2_RunError(char *error, ...);\neval_t*\t\tPR2_GetEdictFieldValue(edict_t *ed, char *field);\n#define PR_GetEdictFieldValue PR2_GetEdictFieldValue\nint\t\t\tED2_FindFieldOffset(char *field);\n#define ED_FindFieldOffset ED2_FindFieldOffset\nvoid \t\tPR2_InitProg(void);\n#define PR_InitProg PR2_InitProg\nvoid        PR2_ClearEdict(edict_t* e);\n#define PR_ClearEdict PR2_ClearEdict\n\n#endif /* !__PR2_H__ */\n"
  },
  {
    "path": "src/pr2_cmds.c",
    "content": "/*\n *  QW262\n *  Copyright (C) 2004  [sd] angel\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by\n *  the Free Software Foundation; either version 2 of the License, or\n *  (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n *\n *\n *  \n */\n\n#ifndef CLIENTONLY\n#ifdef USE_PR2\n\n#include \"qwsvdef.h\"\n#include \"vm.h\"\n#include \"vm_local.h\"\n\n#define SETUSERINFO_STAR          (1<<0) // allow set star keys\n\n#ifdef SERVERONLY\n#define Cbuf_AddTextEx(x, y) Cbuf_AddText(y)\n#define Cbuf_ExecuteEx(x) Cbuf_Execute()\n#endif\n\nconst char *pr2_ent_data_ptr;\nvm_t *sv_vm = NULL;\nextern gameData_t gamedata;\n\nstatic int PASSFLOAT(float f)\n{\n\tfloatint_t fi;\n\tfi.f = f;\n\treturn fi.i;\n}\n\n#if 0 // Provided for completness.\nstatic float GETFLOAT(int i)\n{\n\tfloatint_t fi;\n\tfi.i = i;\n\treturn fi.f;\n}\n#endif\n\ntypedef intptr_t (*ext_syscall_t)(intptr_t *arg);\nstatic intptr_t EXT_MapExtFieldPtr(intptr_t *args);\nstatic intptr_t EXT_SetExtFieldPtr(intptr_t *args);\nstatic intptr_t EXT_GetExtFieldPtr(intptr_t *args);\nstruct\n{\n\tchar *extname;\n\text_syscall_t fun;\n} ext_syscalls[] =\n{\n\t{\"MapExtFieldPtr\",\tEXT_MapExtFieldPtr},\n\t{\"SetExtFieldPtr\",\tEXT_SetExtFieldPtr},\n\t{\"GetExtFieldPtr\",\tEXT_GetExtFieldPtr},\n};\next_syscall_t ext_syscall_tbl[256];\n\nint NUM_FOR_GAME_EDICT(byte *e)\n{\n\tint b;\n\n\tb = (byte *)e - (byte *)sv.game_edicts;\n\tb /= pr_edict_size;\n\n\tif (b < 0 || b >= sv.num_edicts)\n\t\tSV_Error(\"NUM_FOR_GAME_EDICT: bad pointer\");\n\n\treturn b;\n}\n\nintptr_t PR2_EntityStringLocation(string_t offset, int max_size);\nvoid static PR2_SetEntityString_model(edict_t *ed, string_t *target, char *s) {\n\tif (!sv_vm) {\n\t\tPR1_SetString(target, s);\n\t\treturn;\n\t}\n\n\tswitch (sv_vm->type) {\n\tcase VMI_NONE:\n\t\tPR1_SetString(target, s);\n\t\treturn;\n\n\tcase VMI_NATIVE:\n\t\tif (sv_vm->pr2_references) {\n\t\t\tchar **location =\n\t\t\t\t\t(char **)PR2_EntityStringLocation(*target, sizeof(char *));\n\t\t\tif (location) {\n\t\t\t\t*location = s;\n\t\t\t}\n\t\t}\n#ifndef idx64\n\t\telse if (target) {\n\t\t\t*target = (string_t)s;\n\t\t}\n#endif\n\t\treturn;\n\tcase VMI_BYTECODE:\n\tcase VMI_COMPILED: {\n\t\tint off = VM_ExplicitPtr2VM(sv_vm, (byte *)s);\n\n\t\tif (sv_vm->pr2_references) {\n\t\t\tstring_t *location =\n\t\t\t\t\t(string_t *)PR2_EntityStringLocation(*target, sizeof(string_t));\n\n\t\t\tif (location) {\n\t\t\t\t*location = off;\n\t\t\t}\n\t\t} else {\n\t\t\t*target = off;\n\t\t}\n\t}\n\t\treturn;\n\t}\n\n\t*target = 0;\n}\n\n/*\n============\nPR2_RunError\n \nAborts the currently executing function\n============\n*/\nvoid PR2_RunError(char *error, ...)\n{\n\tva_list\t\targptr;\n\tchar\t\tstring[1024];\n\n\tva_start(argptr, error);\n\tvsnprintf(string, sizeof(string), error, argptr);\n\tva_end(argptr);\n\n\tsv_error = true;\n\n\tCon_Printf(\"%s\\n\", string);\n\n\tSV_Error(\"Program error: %s\", string);\n}\n\nvoid PR2_CheckEmptyString(char *s)\n{\n\tif (!s || s[0] <= ' ')\n\t\tPR2_RunError(\"Bad string\");\n}\n\nvoid PF2_precache_sound(char *s)\n{\n\tint i;\n\n\tif (sv.state != ss_loading)\n\t\tPR2_RunError(\"PF_Precache_*: Precache can only be done in spawn \"\n\t\t             \"functions\");\n\tPR2_CheckEmptyString(s);\n\n\tfor (i = 0; i < MAX_SOUNDS; i++)\n\t{\n\t\tif (!sv.sound_precache[i])\n\t\t{\n\t\t\tsv.sound_precache[i] = s;\n\t\t\treturn;\n\t\t}\n\t\tif (!strcmp(sv.sound_precache[i], s))\n\t\t\treturn;\n\t}\n\n\tPR2_RunError (\"PF_precache_sound: overflow\");\n}\n\nvoid PF2_precache_model(char *s)\n{\n\tint \ti;\n\n\tif (sv.state != ss_loading)\n\t\tPR2_RunError(\"PF_Precache_*: Precache can only be done in spawn \"\n\t\t             \"functions\");\n\n\tPR2_CheckEmptyString(s);\n\n\tfor (i = 0; i < MAX_MODELS; i++)\n\t{\n\t\tif (!sv.model_precache[i])\n\t\t{\n\t\t\tsv.model_precache[i] = s;\n\t\t\treturn;\n\t\t}\n\t\tif (!strcmp(sv.model_precache[i], s))\n\t\t\treturn;\n\t}\n\n\tPR2_RunError (\"PF_precache_model: overflow\");\n}\n\nintptr_t PF2_precache_vwep_model(char *s)\n{\n\tint \ti;\n\n\tif (sv.state != ss_loading)\n\t\tPR2_RunError(\"PF_Precache_*: Precache can only be done in spawn \"\n\t\t             \"functions\");\n\n\tPR2_CheckEmptyString(s);\n\n\t// the strings are transferred via the stufftext mechanism, hence the stringency\n\tif (strchr(s, '\"') || strchr(s, ';') || strchr(s, '\\n'  ) || strchr(s, '\\t') || strchr(s, ' '))\n\t\tPR2_RunError (\"Bad string\\n\");\n\n\tfor (i = 0; i < MAX_VWEP_MODELS; i++)\n\t{\n\t\tif (!sv.vw_model_name[i]) {\n\t\t\tsv.vw_model_name[i] = s;\n\t\t\treturn i;\n\t\t}\n\t}\n\tPR2_RunError (\"PF_precache_vwep_model: overflow\");\n\treturn 0;\n}\n\n/*\n=================\nPF2_setorigin\n \nThis is the only valid way to move an object without using the physics of the world (setting velocity and waiting).  Directly changing origin will not set internal links correctly, so clipping would be messed up.  This should be called when an object is spawned, and then only if it is teleported.\n \nsetorigin (entity, origin)\n=================\n*/\n\nvoid PF2_setorigin(edict_t *e, float x, float y, float z)\n{\n\tvec3_t origin;\n\n\torigin[0] = x;\n\torigin[1] = y;\n\torigin[2] = z;\n\n\tVectorCopy(origin, e->v->origin);\n\tSV_AntilagReset(e);\n\tSV_LinkEdict(e, false);\n}\n\n/*\n=================\nPF2_setsize\n \nthe size box is rotated by the current angle\n \nsetsize (entity, minvector, maxvector)\n=================\n*/\nvoid PF2_setsize(edict_t *e, float x1, float y1, float z1, float x2, float y2, float z2)\n{\n\t// vec3_t min, max;\n\te->v->mins[0] = x1;\n\te->v->mins[1] = y1;\n\te->v->mins[2] = z1;\n\n\te->v->maxs[0] = x2;\n\te->v->maxs[1] = y2;\n\te->v->maxs[2] = z2;\n\n\tVectorSubtract(e->v->maxs, e->v->mins, e->v->size);\n\n\tSV_LinkEdict(e, false);\n}\n\n/*\n=================\nPF2_setmodel\n \nsetmodel(entity, model)\nAlso sets size, mins, and maxs for inline bmodels\n=================\n*/\nvoid PF2_setmodel(edict_t *e, char *m)\n{\n\tchar\t\t**check;\n\tint\t\ti;\n\tcmodel_t\t*mod;\n\n\tif (!m)\n\t\tm = \"\";\n\t// check to see if model was properly precached\n\tfor (i = 0, check = sv.model_precache; *check; i++, check++)\n\t\tif (!strcmp(*check, m))\n\t\t\tbreak;\n\n\tif (!*check)\n\t\tPR2_RunError(\"no precache: %s\\n\", m);\n\n\tPR2_SetEntityString_model(e, &e->v->model, m);\n\te->v->modelindex = i;\n\n\t// if it is an inline model, get the size information for it\n\tif (m[0] == '*')\n\t{\n\t\tmod = CM_InlineModel (m);\n\t\tVectorCopy(mod->mins, e->v->mins);\n\t\tVectorCopy(mod->maxs, e->v->maxs);\n\t\tVectorSubtract(mod->maxs, mod->mins, e->v->size);\n\t\tSV_LinkEdict(e, false);\n\t}\n\n}\n\n/*\n=================\nPF2_sprint\n \nsingle print to a specific client\n \nsprint(clientent, value)\n=================\n*/\n\n// trap_SPrint() flags\n#define SPRINT_IGNOREINDEMO   (   1<<0) // do not put such message in mvd demo\n\nvoid PF2_sprint(int entnum, int level, char *s, int flags)\n{\n\tclient_t *client, *cl;\n\tint i;\n\n\tif (gamedata.APIversion < 15)\n\t\tflags = 0;\n\tif (entnum < 1 || entnum > MAX_CLIENTS)\n\t{\n\t\tCon_Printf(\"tried to sprint to a non-client %d (%s)\\n\", entnum, s);\n\t\treturn;\n\t}\n\n\tclient = &svs.clients[entnum - 1];\n\n\t// do not print to client in such state\n\tif (client->state < cs_connected)\n\t\treturn;\n\n\tif (flags & SPRINT_IGNOREINDEMO)\n\t\tSV_ClientPrintf2 (client, level, \"%s\", s); // this does't go to mvd demo\n\telse\n\t\tSV_ClientPrintf  (client, level, \"%s\", s); // this will be in mvd demo too\n\n\t//bliP: spectator print ->\n\tif ((int)sv_specprint.value & SPECPRINT_SPRINT)\n\t{\n\t\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t\t{\n\t\t\tif (!cl->state || !cl->spectator)\n\t\t\t\tcontinue;\n\n\t\t\tif ((cl->spec_track == entnum) && (cl->spec_print & SPECPRINT_SPRINT))\n\t\t\t{\n\t\t\t\tif (flags & SPRINT_IGNOREINDEMO)\n\t\t\t\t\tSV_ClientPrintf2 (cl, level, \"%s\", s); // this does't go to mvd demo\n\t\t\t\telse\n\t\t\t\t\tSV_ClientPrintf  (cl, level, \"%s\", s); // this will be in mvd demo too\n\t\t\t}\n\t\t}\n\t}\n\t//<-\n}\n\n/*\n=================\nPF2_centerprint\n\nsingle print to a specific client\n\ncenterprint(clientent, value)\n=================\n*/\nvoid PF2_centerprint(int entnum, char *s)\n{\n\tclient_t *cl, *spec;\n\tint i;\n\n\tif (entnum < 1 || entnum > MAX_CLIENTS)\n\t{\n\t\tCon_Printf(\"tried to centerprint to a non-client %d \\n\", entnum);\n\t\treturn;\n\t}\n\n\tcl = &svs.clients[entnum - 1];\n\n\tClientReliableWrite_Begin(cl, svc_centerprint, 2 + strlen(s));\n\tClientReliableWrite_String(cl, s);\n\n\tif (sv.mvdrecording)\n\t{\n\t\tif (MVDWrite_Begin(dem_single, entnum - 1, 2 + strlen(s)))\n\t\t{\n\t\t\tMVD_MSG_WriteByte(svc_centerprint);\n\t\t\tMVD_MSG_WriteString(s);\n\t\t}\n\t}\n\n\t//bliP: spectator print ->\n\tif ((int)sv_specprint.value & SPECPRINT_CENTERPRINT)\n\t{\n\t\tfor (i = 0, spec = svs.clients; i < MAX_CLIENTS; i++, spec++)\n\t\t{\n\t\t\tif (!cl->state || !spec->spectator)\n\t\t\t\tcontinue;\n\n\t\t\tif ((spec->spec_track == entnum) && (cl->spec_print & SPECPRINT_CENTERPRINT))\n\t\t\t{\n\t\t\t\tClientReliableWrite_Begin (spec, svc_centerprint, 2 + strlen(s));\n\t\t\t\tClientReliableWrite_String (spec, s);\n\t\t\t}\n\t\t}\n\t}\n\t//<-\n\n}\n\n/*\n=================\nPF2_ambientsound\n \n=================\n*/\nvoid PF2_ambientsound(float x, float y, float z, char *samp, float vol,\tfloat attenuation)\n{\n\tchar\t**check;\n\tint\t\ti, soundnum;\n\tvec3_t \tpos;\n\n\tpos[0] = x;\n\tpos[1] = y;\n\tpos[2] = z;\n\n\tif( !samp )\n\t\tsamp = \"\";\n\n\t// check to see if samp was properly precached\n\tfor (soundnum = 0, check = sv.sound_precache; *check; check++, soundnum++)\n\t\tif (!strcmp(*check, samp))\n\t\t\tbreak;\n\n\tif (!*check)\n\t{\n\t\tCon_Printf(\"no precache: %s\\n\", samp);\n\t\treturn;\n\t}\n\n\t// add an svc_spawnambient command to the level signon packet\n\tMSG_WriteByte(&sv.signon, svc_spawnstaticsound);\n\tfor (i = 0; i < 3; i++)\n\t\tMSG_WriteCoord(&sv.signon, pos[i]);\n\n\tMSG_WriteByte(&sv.signon, soundnum);\n\n\tMSG_WriteByte(&sv.signon, vol * 255);\n\tMSG_WriteByte(&sv.signon, attenuation * 64);\n\n}\n\n/*\n=================\nPF2_traceline\n \nUsed for use tracing and shot targeting\nTraces are blocked by bbox and exact bsp entityes, and also slide box entities\nif the tryents flag is set.\n \ntraceline (vector1, vector2, tryents)\n=================\n*/\nvoid PF2_traceline(float v1_x, float v1_y, float v1_z,\n\t\t\t\t\tfloat v2_x, float v2_y, float v2_z,\n\t\t\t\t\tint nomonsters, int entnum)\n{\n\ttrace_t\ttrace;\n\tedict_t\t*ent;\n\tvec3_t v1, v2;\n\n\tent = EDICT_NUM(entnum);\n\tv1[0] = v1_x;\n\tv1[1] = v1_y;\n\tv1[2] = v1_z;\n\n\tv2[0] = v2_x;\n\tv2[1] = v2_y;\n\tv2[2] = v2_z;\n\n\tif (sv_antilag.value == 2)\n\t{\n\t\tif (!(entnum >= 1 && entnum <= MAX_CLIENTS && svs.clients[entnum - 1].isBot)) {\n\t\t\tnomonsters |= MOVE_LAGGED;\n\t\t}\n\t}\n\n\ttrace = SV_Trace(v1, vec3_origin, vec3_origin, v2, nomonsters, ent);\n\n\tpr_global_struct->trace_allsolid = trace.allsolid;\n\tpr_global_struct->trace_startsolid = trace.startsolid;\n\tpr_global_struct->trace_fraction = trace.fraction;\n\tpr_global_struct->trace_inwater = trace.inwater;\n\tpr_global_struct->trace_inopen = trace.inopen;\n\tVectorCopy (trace.endpos, pr_global_struct->trace_endpos);\n\tVectorCopy (trace.plane.normal, pr_global_struct->trace_plane_normal);\n\tpr_global_struct->trace_plane_dist = trace.plane.dist;\n\n\tif (trace.e.ent)\n\t\tpr_global_struct->trace_ent = EDICT_TO_PROG(trace.e.ent);\n\telse\n\t\tpr_global_struct->trace_ent = EDICT_TO_PROG(sv.edicts);\n}\n\n/*\n=================\nPF2_TraceCapsule\n \nUsed for use tracing and shot targeting\nTraces are blocked by bbox and exact bsp entityes, and also slide box entities\nif the tryents flag is set.\n=================\n*/\nvoid PF2_TraceCapsule(float v1_x, float v1_y, float v1_z,\n\t\t\t\t\tfloat v2_x,\tfloat v2_y, float v2_z,\n\t\t\t\t\tint nomonsters, edict_t *ent,\n\t\t\t\t\tfloat min_x, float min_y, float min_z,\n\t\t\t\t\tfloat max_x, float max_y, float max_z)\n{\n\ttrace_t\ttrace;\n\tvec3_t v1, v2, v3, v4;\n\n\tv1[0] = v1_x;\n\tv1[1] = v1_y;\n\tv1[2] = v1_z;\n\n\tv2[0] = v2_x;\n\tv2[1] = v2_y;\n\tv2[2] = v2_z;\n\n\tv3[0] = min_x;\n\tv3[1] = min_y;\n\tv3[2] = min_z;\n\n\tv4[0] = max_x;\n\tv4[1] = max_y;\n\tv4[2] = max_z;\n\n\ttrace = SV_Trace(v1, v3, v4, v2, nomonsters, ent);\n\n\tpr_global_struct->trace_allsolid = trace.allsolid;\n\tpr_global_struct->trace_startsolid = trace.startsolid;\n\tpr_global_struct->trace_fraction = trace.fraction;\n\tpr_global_struct->trace_inwater = trace.inwater;\n\tpr_global_struct->trace_inopen = trace.inopen;\n\tVectorCopy (trace.endpos, pr_global_struct->trace_endpos);\n\tVectorCopy (trace.plane.normal, pr_global_struct->trace_plane_normal);\n\tpr_global_struct->trace_plane_dist = trace.plane.dist;\n\n\tif (trace.e.ent)\n\t\tpr_global_struct->trace_ent = EDICT_TO_PROG(trace.e.ent);\n\telse\n\t\tpr_global_struct->trace_ent = EDICT_TO_PROG(sv.edicts);\n}\n\n/*\n=================\nPF2_checkclient\n \nReturns a client (or object that has a client enemy) that would be a\nvalid target.\n \nIf there are more than one valid options, they are cycled each frame\n \nIf (self.origin + self.viewofs) is not in the PVS of the current target,\nit is not returned at all.\n \nname checkclient ()\n=================\n*/\n\nstatic byte\t*checkpvs;\n\nint PF2_newcheckclient(int check)\n{\n\tint\t\ti;\n\tedict_t\t*ent;\n\tvec3_t\torg;\n\n\t// cycle to the next one\n\tif (check < 1)\n\t\tcheck = 1;\n\tif (check > MAX_CLIENTS)\n\t\tcheck = MAX_CLIENTS;\n\n\tif (check == MAX_CLIENTS)\n\t\ti = 1;\n\telse\n\t\ti = check + 1;\n\n\tfor ( ; ; i++)\n\t{\n\t\tif (i == MAX_CLIENTS + 1)\n\t\t\ti = 1;\n\n\t\tent = EDICT_NUM(i);\n\n\t\tif (i == check)\n\t\t\tbreak;\t// didn't find anything else\n\n\t\tif (ent->e.free)\n\t\t\tcontinue;\n\t\tif (ent->v->health <= 0)\n\t\t\tcontinue;\n\t\tif ((int) ent->v->flags & FL_NOTARGET)\n\t\t\tcontinue;\n\n\t\t// anything that is a client, or has a client as an enemy\n\t\tbreak;\n\t}\n\n\t// get the PVS for the entity\n\tVectorAdd (ent->v->origin, ent->v->view_ofs, org);\n\tcheckpvs = CM_LeafPVS (CM_PointInLeaf(org));\n\n\treturn i;\n}\n\n#define\tMAX_CHECK\t16\n\nintptr_t PF2_checkclient(void)\n{\n\tedict_t\t*ent, *self;\n\tint\t\tl;\n\tvec3_t\tview;\n\n\t// find a new check if on a new frame\n\tif (sv.time - sv.lastchecktime >= 0.1)\n\t{\n\t\tsv.lastcheck = PF2_newcheckclient(sv.lastcheck);\n\t\tsv.lastchecktime = sv.time;\n\t}\n\n\t// return check if it might be visible\n\tent = EDICT_NUM(sv.lastcheck);\n\tif (ent->e.free || ent->v->health <= 0)\n\t{\n\t\t// RETURN_EDICT(sv.edicts);\n\t\treturn NUM_FOR_EDICT(sv.edicts);\n\t}\n\n\t// if current entity can't possibly see the check entity, return 0\n\tself = PROG_TO_EDICT(pr_global_struct->self);\n\tVectorAdd(self->v->origin, self->v->view_ofs, view);\n\tl = CM_Leafnum(CM_PointInLeaf(view)) - 1;\n\tif ((l < 0) || !(checkpvs[l >> 3] & (1 << (l & 7))))\n\t{\n\t\treturn NUM_FOR_EDICT(sv.edicts);\n\t}\n\n\treturn NUM_FOR_EDICT(ent);\n}\n\n//============================================================================\n// modified by Tonik\n/*\n=================\nPF2_stuffcmd\n\nSends text over to the client's execution buffer\n\nstuffcmd (clientent, value)\n=================\n*/\n\n// trap_stuffcmd() flags\n#define STUFFCMD_IGNOREINDEMO   (   1<<0) // do not put in mvd demo\n#define STUFFCMD_DEMOONLY       (   1<<1) // put in mvd demo only\n\nvoid PF2_stuffcmd(int entnum, char *str, int flags)\n{\n\tchar *buf = NULL;\n\tclient_t *cl, *spec;\n\tint j;\n\n\tif (gamedata.APIversion < 15)\n\t\tflags = 0;\n\tif( !str )\n\t\tPR2_RunError(\"PF2_stuffcmd: NULL pointer\");\n\n\t// put in mvd demo only\n\tif (flags & STUFFCMD_DEMOONLY)\n\t{\n\t\tif (strchr( str, '\\n' )) // we have \\n trail\n\t\t{\n\t\t\tif (sv.mvdrecording)\n\t\t\t{\n\t\t\t\tif (MVDWrite_Begin(dem_all, 0, 2 + strlen(str)))\n\t\t\t\t{\n\t\t\t\t\tMVD_MSG_WriteByte(svc_stufftext);\n\t\t\t\t\tMVD_MSG_WriteString(str);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn; // do not send to client in any case\n\t}\n\n\tif (entnum < 1 || entnum > MAX_CLIENTS)\n\t\tPR2_RunError(\"Parm 0 not a client\");\n\n\n\tcl = &svs.clients[entnum - 1];\n\tif (!strncmp(str, \"disconnect\\n\", MAX_STUFFTEXT))\n\t{\n\t\t// so long and thanks for all the fish\n\t\tcl->drop = true;\n\t\treturn;\n\t}\n\t\n\tbuf = cl->stufftext_buf;\n\tif (strlen(buf) + strlen(str) >= MAX_STUFFTEXT)\n\t\tPR2_RunError(\"stufftext buffer overflow\");\n\tstrlcat (buf, str, MAX_STUFFTEXT);\n\n\tif( strchr( buf, '\\n' ) )\n\t{\n\t\tClientReliableWrite_Begin(cl, svc_stufftext, 2 + strlen(buf));\n\t\tClientReliableWrite_String(cl, buf);\n\n\t\tif (!(flags & STUFFCMD_IGNOREINDEMO)) // STUFFCMD_IGNOREINDEMO flag is NOT set\n\t\t{\n\t\t\tif (sv.mvdrecording)\n\t\t\t{\n\t\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 2 + strlen(buf)))\n\t\t\t\t{\n\t\t\t\t\tMVD_MSG_WriteByte(svc_stufftext);\n\t\t\t\t\tMVD_MSG_WriteString(buf);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t//bliP: spectator print ->\n\t\tif ((int)sv_specprint.value & SPECPRINT_STUFFCMD)\n\t\t{\n\t\t\tfor (j = 0, spec = svs.clients; j < MAX_CLIENTS; j++, spec++)\n\t\t\t{\n\t\t\t\tif (!cl->state || !spec->spectator)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif ((spec->spec_track == entnum) && (cl->spec_print & SPECPRINT_STUFFCMD))\n\t\t\t\t{\n\t\t\t\t\tClientReliableWrite_Begin (spec, svc_stufftext, 2+strlen(buf));\n\t\t\t\t\tClientReliableWrite_String (spec, buf);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tbuf[0] = 0;\n\t}\n\n}\n\n/*\n=================\nPF2_executecmd\n=================\n*/\nvoid PF2_executecmd(void)\n{\n\tint old_other, old_self; // mod_consolecmd will be executed, so we need to store this\n\n\told_self = pr_global_struct->self;\n\told_other = pr_global_struct->other;\n\n\tCbuf_ExecuteEx(&cbuf_server);\n\n\tpr_global_struct->self = old_self;\n\tpr_global_struct->other = old_other;\n}\n\n/*\n=================\nPF2_readcmd\n \nvoid readmcmd (string str,string buff, int sizeofbuff)\n=================\n*/\n\nvoid PF2_readcmd(char *str, char *buf, int sizebuff)\n{\n\textern char outputbuf[];\n\textern \tredirect_t sv_redirected;\n\tredirect_t old;\n\n\tCbuf_ExecuteEx(&cbuf_server);\n\tCbuf_AddTextEx(&cbuf_server, str);\n\n\told = sv_redirected;\n\n\tif (old != RD_NONE)\n\t\tSV_EndRedirect();\n\n\tSV_BeginRedirect(RD_MOD);\n\tCbuf_ExecuteEx(&cbuf_server);\n\n\tstrlcpy(buf, outputbuf, sizebuff);\n\n\tSV_EndRedirect();\n\n\tif (old != RD_NONE)\n\t\tSV_BeginRedirect(old);\n\n}\n\n/*\n=================\nPF2_redirectcmd\n \nvoid redirectcmd (entity to, string str)\n=================\n*/\n\nvoid PF2_redirectcmd(int entnum, char *str)\n{\n\n\textern redirect_t sv_redirected;\n\n\tif (sv_redirected) {\n\t\tCbuf_AddTextEx(&cbuf_server, str);\n\t\tCbuf_ExecuteEx(&cbuf_server);\n\t\treturn;\n\t}\n\n\tif (entnum < 1 || entnum > MAX_CLIENTS) {\n\t\tPR2_RunError(\"Parm 0 not a client\");\n\t}\n\n\tSV_BeginRedirect((redirect_t)(RD_MOD + entnum));\n\tCbuf_AddTextEx(&cbuf_server, str);\n\tCbuf_ExecuteEx(&cbuf_server);\n\tSV_EndRedirect();\n}\n\n/*\n=================\nPF2_FindRadius\n\nReturns a chain of entities that have origins within a spherical area\ngedict_t *findradius( gedict_t * start, vec3_t org, float rad );\n=================\n*/\n\nintptr_t PF2_FindRadius(int e, float *org, float rad)\n{\n\tint j;\n\n\tedict_t *ed;\n\tvec3_t\teorg;\n\n\tfor ( e++; e < sv.num_edicts; e++ )\n\t{\n\t\ted = EDICT_NUM( e );\n\n\t\tif (ed->e.free)\n\t\t\tcontinue;\n\t\tif (ed->v->solid == SOLID_NOT)\n\t\t\tcontinue;\n\t\tfor (j=0 ; j<3 ; j++)\n\t\t\teorg[j] = org[j] - (ed->v->origin[j] + (ed->v->mins[j] + ed->v->maxs[j])*0.5);\n\t\tif (VectorLength(eorg) > rad)\n\t\t\tcontinue;\n\t\treturn VM_Ptr2VM((byte *)ed->v);\n\t}\n\treturn 0;\n}\n\n/*\n===============\nPF2_walkmove\n \nfloat(float yaw, float dist) walkmove\n===============\n*/\nint PF2_walkmove(edict_t *ent, float yaw, float dist)\n{\n\tvec3_t\t\tmove;\n\tint\t\t\toldself;\n\tint\t\t\tret;\n\n\tif (!((int) ent->v->flags & (FL_ONGROUND | FL_FLY | FL_SWIM)))\n\t{\n\t\treturn 0;\n\t}\n\n\tyaw = yaw * M_PI * 2 / 360;\n\n\tmove[0] = cos(yaw) * dist;\n\tmove[1] = sin(yaw) * dist;\n\tmove[2] = 0;\n\n\t// save program state, because SV_movestep may call other progs\n\t//\toldf = pr_xfunction;\n\toldself = pr_global_struct->self;\n\n\tret = SV_movestep(ent, move, true);\n\n\t// restore program state\n\t//\tpr_xfunction = oldf;\n\tpr_global_struct->self = oldself;\n\treturn ret;\n}\n\n/*\n===============\nPF2_MoveToGoal\n \nfloat(float dist) PF2_MoveToGoal\n===============\n*/\n\nvoid PF2_MoveToGoal(float dist)\n{\n\tedict_t\t\t*ent, *goal;\n\n\t//\tdfunction_t\t*oldf;\n\tint\t\t\toldself;\n\n\tent  = PROG_TO_EDICT(pr_global_struct->self);\n\tgoal = PROG_TO_EDICT(ent->v->goalentity);\n\n\tif ( !( (int)ent->v->flags & (FL_ONGROUND|FL_FLY|FL_SWIM) ) )\n\t{\n\t\treturn;\n\t}\n\n\t// if the next step hits the enemy, return immediately\n\tif ( PROG_TO_EDICT(ent->v->enemy) != sv.edicts && SV_CloseEnough (ent, goal, dist) )\n\t\treturn;\n\n\t// save program state, because SV_movestep may call other progs\n\t//\toldf = pr_xfunction;\n\toldself = pr_global_struct->self;\n\n\t// bump around...\n\tif ( (rand()&3)==1 || !SV_StepDirection (ent, ent->v->ideal_yaw, dist))\n\t{\n\t\tSV_NewChaseDir (ent, goal, dist);\n\t}\n\n\t// restore program state\n\t//\tpr_xfunction = oldf;\n\tpr_global_struct->self = oldself;\n}\n\n/*\n===============\nPF2_droptofloor\n \nvoid(entnum) droptofloor\n===============\n*/\nint PF2_droptofloor(edict_t *ent)\n{\n\tvec3_t\t\tend;\n\ttrace_t\t\ttrace;\n\n\tVectorCopy(ent->v->origin, end);\n\tend[2] -= 256;\n\n\ttrace = SV_Trace(ent->v->origin, ent->v->mins, ent->v->maxs, end, false, ent);\n\n\tif (trace.fraction == 1 || trace.allsolid)\n\t{\n\t\treturn 0;\n\t}\n\telse\n\t{\n\t\tVectorCopy(trace.endpos, ent->v->origin);\n\t\tSV_LinkEdict(ent, false);\n\t\tent->v->flags = (int) ent->v->flags | FL_ONGROUND;\n\t\tent->v->groundentity = EDICT_TO_PROG(trace.e.ent);\n\t\treturn 1;\n\t}\n}\n\n/*\n===============\nPF2_lightstyle\n \nvoid(int style, string value) lightstyle\n===============\n*/\nvoid PF2_lightstyle(int style, char *val)\n{\n\tclient_t\t*client;\n\tint\t\t\tj;\n\n\t// change the string in sv\n\tsv.lightstyles[style] = val;\n\n\t// send message to all clients on this server\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\tfor (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)\n\t\tif (client->state == cs_spawned)\n\t\t{\n\t\t\tClientReliableWrite_Begin(client, svc_lightstyle, strlen(val) + 3);\n\t\t\tClientReliableWrite_Char(client, style);\n\t\t\tClientReliableWrite_String(client, val);\n\t\t}\n\n\tif (sv.mvdrecording)\n\t{\n\t\tif (MVDWrite_Begin(dem_all, 0, strlen(val) + 3))\n\t\t{\n\t\t\tMVD_MSG_WriteByte(svc_lightstyle);\n\t\t\tMVD_MSG_WriteChar(style);\n\t\t\tMVD_MSG_WriteString(val);\n\t\t}\n\t}\n}\n\n/*\n=============\nPF2_pointcontents\n=============\n*/\nint PF2_pointcontents(float x, float y, float z)\n{\n\tvec3_t origin;\n\n\torigin[0] = x;\n\torigin[1] = y;\n\torigin[2] = z;\n\n\treturn SV_PointContents(origin);\n}\n\n/*\n=============\nPF2_nextent\n\nentity nextent(entity)\n=============\n*/\nintptr_t PF2_nextent(int i)\n{\n\tedict_t\t*ent;\n\n\twhile (1)\n\t{\n\t\ti++;\n\t\tif (i >= sv.num_edicts)\n\t\t{\n\t\t\treturn 0;\n\t\t}\n\t\tent = EDICT_NUM(i);\n\t\tif (!ent->e.free)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n}\n\n/*\n=============\nPF2_nextclient\n\nfast walk over spawned clients\n \nentity nextclient(entity)\n=============\n*/\nintptr_t PF2_nextclient(int i)\n{\n\tedict_t\t*ent;\n\n\twhile (1)\n\t{\n\t\ti++;\n\t\tif (i < 1 || i > MAX_CLIENTS)\n\t\t{\n\t\t\treturn 0;\n\t\t}\n\t\tent = EDICT_NUM(i);\n\t\tif (!ent->e.free) // actually that always true for clients edicts\n\t\t{\n\t\t\tif (svs.clients[i - 1].state == cs_spawned) // client in game\n\t\t\t{\n\t\t\t\treturn VM_Ptr2VM((byte *)ent->v);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n=============\nPF2_find\n\nentity find(start,fieldoff,str)\n=============\n*/\nintptr_t PF2_Find(int e, int fofs, char *str)\n{\n\tchar *t;\n\tedict_t\t*ed;\n\n\tif(!str)\n\t\tPR2_RunError (\"PF2_Find: bad search string\");\n\n\tfor (e++ ; e < sv.num_edicts ; e++)\n\t{\n\t\ted = EDICT_NUM(e);\n\t\tif (ed->e.free)\n\t\t\tcontinue;\n\n\t\tif (!(intptr_t *)((byte *)ed->v + fofs))\n\t\t\tcontinue;\n\n\t\tt = VM_ArgPtr(*(intptr_t *)((char *)ed->v + fofs));\n\n\t\tif (!t)\n\t\t\tcontinue;\n\n\t\tif (!strcmp(t,str))\n\t\t{\n\t\t\treturn VM_Ptr2VM((byte *)ed->v);\n\t\t}\n\t}\n\treturn 0;\n}\n\n/*\n=============\nPF2_aim ??????\n \nPick a vector for the player to shoot along\nvector aim(entity, missilespeed)\n=============\n*/\n/*\n==============\nPF2_changeyaw ???\n \nThis was a major timewaster in progs, so it was converted to C\n==============\n*/\n\n/*\n===============================================================================\n \nMESSAGE WRITING\n \n===============================================================================\n*/\n\n\n#define\tMSG_BROADCAST\t0\t\t// unreliable to all\n#define\tMSG_ONE\t\t\t1\t\t// reliable to one (msg_entity)\n#define\tMSG_ALL\t\t\t2\t\t// reliable to all\n#define\tMSG_INIT\t\t3\t\t// write to the init string\n#define\tMSG_MULTICAST\t4\t\t// for multicast()\n\n\nsizebuf_t *WriteDest2(int dest)\n{\n\t//\tint\t\tentnum;\n\t//\tint\t\tdest;\n\t//\tedict_t\t*ent;\n\n\t//dest = G_FLOAT(OFS_PARM0);\n\tswitch (dest)\n\t{\n\tcase MSG_BROADCAST:\n\t\treturn &sv.datagram;\n\n\tcase MSG_ONE:\n\t\tSV_Error(\"Shouldn't be at MSG_ONE\");\n#if 0\n\t\tent = PROG_TO_EDICT(pr_global_struct->msg_entity);\n\t\tentnum = NUM_FOR_EDICT(ent);\n\t\tif (entnum < 1 || entnum > MAX_CLIENTS)\n\t\t\tPR2_RunError(\"WriteDest: not a client\");\n\t\treturn &svs.clients[entnum - 1].netchan.message;\n#endif\n\n\tcase MSG_ALL:\n\t\treturn &sv.reliable_datagram;\n\n\tcase MSG_INIT:\n\t\tif (sv.state != ss_loading)\n\t\t\tPR2_RunError(\"PF_Write_*: MSG_INIT can only be written in spawn \"\n\t\t\t             \"functions\");\n\t\treturn &sv.signon;\n\n\tcase MSG_MULTICAST:\n\t\treturn &sv.multicast;\n\n\tdefault:\n\t\tPR2_RunError (\"WriteDest: bad destination\");\n\t\tbreak;\n\t}\n\n\treturn NULL;\n}\n\nstatic client_t *Write_GetClient(void)\n{\n\tint\t\tentnum;\n\tedict_t\t*ent;\n\n\tent = PROG_TO_EDICT(pr_global_struct->msg_entity);\n\tentnum = NUM_FOR_EDICT(ent);\n\tif (entnum < 1 || entnum > MAX_CLIENTS)\n\t\tPR2_RunError(\"WriteDest: not a client\");\n\treturn &svs.clients[entnum - 1];\n}\n\nvoid PF2_WriteByte(int to, int data)\n{\n\tif (to == MSG_ONE)\n\t{\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, 1);\n\t\tClientReliableWrite_Byte(cl, data);\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 1))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteByte(data);\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteByte(WriteDest2(to), data);\n}\n\nvoid PF2_WriteChar(int to, int data)\n{\n\tif (to == MSG_ONE)\n\t{\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, 1);\n\t\tClientReliableWrite_Char(cl, data);\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 1))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteByte(data);\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteChar(WriteDest2(to), data);\n}\n\nvoid PF2_WriteShort(int to, int data)\n{\n\tif (to == MSG_ONE)\n\t{\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, 2);\n\t\tClientReliableWrite_Short(cl, data);\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 2))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteShort(data);\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteShort(WriteDest2(to), data);\n}\n\nvoid PF2_WriteLong(int to, int data)\n{\n\tif (to == MSG_ONE)\n\t{\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, 4);\n\t\tClientReliableWrite_Long(cl, data);\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 4))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteLong(data);\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteLong(WriteDest2(to), data);\n}\n\nvoid PF2_WriteAngle(int to, float data)\n{\n\tif (to == MSG_ONE)\n\t{\n#ifdef FTE_PEXT_FLOATCOORDS\n\t\tint size = msg_anglesize;\n#else\n\t\tint size = 1;\n#endif\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, size);\n\t\tClientReliableWrite_Angle(cl, data);\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, size))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteAngle(data);\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteAngle(WriteDest2(to), data);\n}\n\nvoid PF2_WriteCoord(int to, float data)\n{\n\tif (to == MSG_ONE)\n\t{\n#ifdef FTE_PEXT_FLOATCOORDS\n\t\tint size = msg_coordsize;\n#else\n\t\tint size = 2;\n#endif\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, size);\n\t\tClientReliableWrite_Coord(cl, data);\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, size))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteCoord(data);\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteCoord(WriteDest2(to), data);\n}\n\nvoid PF2_WriteString(int to, char *data)\n{\n\tif (to == MSG_ONE)\n\t{\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, 1 + strlen(data));\n\t\tClientReliableWrite_String(cl, data);\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 1 + strlen(data)))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteString(data);\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteString(WriteDest2(to), data);\n}\n\nvoid PF2_WriteEntity(int to, int data)\n{\n\tif (to == MSG_ONE)\n\t{\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, 2);\n\t\tClientReliableWrite_Short(cl,data );//G_EDICTNUM(OFS_PARM1)\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 2))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteShort(data);\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteShort(WriteDest2(to), data);\n}\n\n//=============================================================================\n\nint SV_ModelIndex(char *name);\n\n/*\n==================\nPF2_makestatic \n \n==================\n*/\nvoid PF2_makestatic(edict_t *ent)\n{\n\tentity_state_t *s;\n\n\tif (sv.static_entity_count >= sizeof(sv.static_entities) / sizeof(sv.static_entities[0])) {\n\t\tED_Free (ent);\n\t\treturn;\n\t}\n\n\ts = &sv.static_entities[sv.static_entity_count];\n\tmemset(s, 0, sizeof(sv.static_entities[0]));\n\ts->number = sv.static_entity_count + 1;\n\ts->modelindex = SV_ModelIndex(PR_GetEntityString(ent->v->model));\n\tif (!s->modelindex) {\n\t\tED_Free (ent);\n\t\treturn;\n\t}\n\ts->frame = ent->v->frame;\n\ts->colormap = ent->v->colormap;\n\ts->skinnum = ent->v->skin;\n\tVectorCopy(ent->v->origin, s->origin);\n\tVectorCopy(ent->v->angles, s->angles);\n#ifdef FTE_PEXT_TRANS\n\ts->trans = ent->xv.alpha >= 1.0f ? 0 : bound(0, (byte)(ent->xv.alpha * 254.0), 254);\n#endif\n#ifdef FTE_PEXT_COLOURMOD\n\tif (ent->xv.colourmod[0] != 1.0f && ent->xv.colourmod[1] != 1.0f && ent->xv.colourmod[2] != 1.0f)\n\t{\n\t\ts->colourmod[0] = bound(0, ent->xv.colourmod[0] * (256.0f / 8.0f), 255);\n\t\ts->colourmod[1] = bound(0, ent->xv.colourmod[1] * (256.0f / 8.0f), 255);\n\t\ts->colourmod[2] = bound(0, ent->xv.colourmod[2] * (256.0f / 8.0f), 255);\n\t}\n#endif\n\t++sv.static_entity_count;\n\n\t// throw the entity away now\n\tED_Free(ent);\n}\n\n//=============================================================================\n\n/*\n==============\nPF2_setspawnparms\n==============\n*/\nvoid PF2_setspawnparms(int entnum)\n{\n\tint\t\t\ti;\n\tclient_t\t*client;\n\n\tif (entnum < 1 || entnum > MAX_CLIENTS)\n\t\tPR2_RunError(\"Entity is not a client\");\n\n\t// copy spawn parms out of the client_t\n\tclient = svs.clients + (entnum - 1);\n\n\tfor (i = 0; i < NUM_SPAWN_PARMS; i++)\n\t\t(&pr_global_struct->parm1)[i] = client->spawn_parms[i];\n}\n\n/*\n==============\nPF2_changelevel\n==============\n*/\nvoid PF2_changelevel(char *s, char *entfile)\n{\n\tstatic int last_spawncount;\n\tchar expanded[MAX_QPATH];\n\n\tif (gamedata.APIversion < 15)\n\t\tentfile = \"\";\n\t// check to make sure the level exists.\n\t// this is work around for bellow check about two changelevels,\n\t// which lock server in one map if we trying switch to map which does't exist\n\tsnprintf(expanded, MAX_QPATH, \"maps/%s.bsp\", s);\n\tif (!FS_FLocateFile(expanded, FSLFRT_IFFOUND, NULL))\n\t{\n\t\tSys_Printf (\"Can't find %s\\n\", expanded);\n\t\treturn;\n\t}\n\n\t// make sure we don't issue two changelevels\n\t// this check is evil and cause lock on one map, if /map command fail for some reason\n\tif (svs.spawncount == last_spawncount)\n\t\treturn;\n\tlast_spawncount = svs.spawncount;\n\n\tif (entfile && *entfile) {\n\t\tCbuf_AddTextEx(&cbuf_server, va(\"map %s %s\\n\", s, entfile));\n\t}\n\telse {\n\t\tCbuf_AddTextEx(&cbuf_server, va(\"map %s\\n\", s));\n\t}\n}\n\n/*\n==============\nPF2_logfrag\n \nlogfrag (killer, killee)\n==============\n*/\nvoid PF2_logfrag(int e1, int e2)\n{\n\tchar *s;\n\t// -> scream\n\ttime_t\t\tt;\n\tstruct tm\t*tblock;\n\n\tif (e1 < 1 || e1 > MAX_CLIENTS || e2 < 1 || e2 > MAX_CLIENTS)\n\t\treturn;\n\n\t// -> scream\n\tt = time (NULL);\n\ttblock = localtime (&t);\n\n\t//bliP: date check ->\n\tif (!tblock)\n\t\ts = va(\"%s\\n\", \"#bad date#\");\n\telse\n\t\tif ((int)frag_log_type.value) // need for old-style frag log file\n\t\t\ts = va(\"\\\\frag\\\\%s\\\\%s\\\\%s\\\\%s\\\\%d-%d-%d %d:%d:%d\\\\\\n\",\n\t\t\t       svs.clients[e1-1].name, svs.clients[e2-1].name,\n\t\t\t       svs.clients[e1-1].team, svs.clients[e2-1].team,\n\t\t\t       tblock->tm_year + 1900, tblock->tm_mon + 1, tblock->tm_mday,\n\t\t\t       tblock->tm_hour, tblock->tm_min, tblock->tm_sec);\n\t\telse\n\t\t\ts = va(\"\\\\%s\\\\%s\\\\\\n\",svs.clients[e1-1].name, svs.clients[e2-1].name);\n\t// <-\n\n\tSZ_Print(&svs.log[svs.logsequence & 1], s);\n\tSV_Write_Log(FRAG_LOG, 1, s);\n}\n/*\n==============\nPF2_getinfokey\n \nstring(entity e, string key) infokey\n==============\n*/\nvoid PF2_infokey(int e1, char *key, char *valbuff, int sizebuff)\n//(int e1, char *key, char *valbuff, int sizebuff)\n{\n\tstatic char ov[256];\n\tchar\t\t*value;\n\n\tvalue = ov;\n\n\tif (e1 == 0)\n\t{\n\t\tif (key && key[0] == '\\\\') { // so we can check is server support such \"hacks\" via infokey(world, \"\\\\realip\") f.e.\n\t\t\tkey++;\n\n\t\t\tvalue = \"no\";\n\n\t\t\tif (   !strcmp(key, \"date_str\")\n\t\t\t\t|| !strcmp(key, \"ip\") || !strncmp(key, \"realip\", 7) || !strncmp(key, \"download\", 9)\n\t\t\t\t|| !strcmp(key, \"ping\") || !strcmp(key, \"*userid\") || !strncmp(key, \"login\", 6)\n\t\t\t\t|| !strcmp(key, \"*VIP\") || !strcmp(key, \"*state\")\n\t\t\t\t|| !strcmp(key, \"netname\")\n\t\t\t\t|| !strcmp(key, \"mapname\") || !strcmp(key, \"modelname\")\n\t\t\t\t|| !strcmp(key, \"version\") || !strcmp(key, \"servername\")\n\t\t\t   )\n\t\t\t\tvalue = \"yes\";\n\t\t}\n\t\telse if (!strcmp(key, \"date_str\")) { // qqshka - qvm does't have any time builtin support, so add this\n\t\t\tdate_t date;\n\n\t\t\tSV_TimeOfDay(&date, \"%a %b %d, %H:%M:%S %Y\");\n\t\t\tsnprintf(ov, sizeof(ov), \"%s\", date.str);\n\t\t}\n\t\telse if (!strcmp(key, \"mapname\")) {\n\t\t\tvalue = sv.mapname;\n\t\t}\n\t\telse if (!strcmp(key, \"modelname\")) {\n\t\t\tvalue = sv.modelname;\n\t\t}\n\t\telse if (!strcmp(key, \"version\")) {\n\t\t\tvalue = VersionStringFull();\n\t\t}\n\t\telse if (!strcmp(key, \"servername\")) {\n\t\t\tvalue = SERVER_NAME;\n\t\t}\n\t\telse if ((value = Info_ValueForKey(svs.info, key)) == NULL || !*value)\n\t\t\tvalue = Info_Get(&_localinfo_, key);\n\t}\n\telse if (e1 > 0 && e1 <= MAX_CLIENTS)\n\t{\n\t\tclient_t *cl = &svs.clients[e1-1];\n\n\t\tif (!strcmp(key, \"ip\"))\n\t\t\tstrlcpy(ov, NET_BaseAdrToString(cl->netchan.remote_address), sizeof(ov));\n\t\telse if (!strncmp(key, \"realip\", 7))\n\t\t\tstrlcpy(ov, NET_BaseAdrToString (cl->realip), sizeof(ov));\n\t\telse if (!strncmp(key, \"download\", 9))\n\t\t\tsnprintf(ov, sizeof(ov), \"%d\", cl->file_percent ? cl->file_percent : -1); //bliP: file percent\n\t\telse if (!strcmp(key, \"ping\"))\n\t\t\tsnprintf(ov, sizeof(ov), \"%d\", (int)SV_CalcPing(cl));\n\t\telse if (!strcmp(key, \"*userid\"))\n\t\t\tsnprintf(ov, sizeof(ov), \"%d\", cl->userid);\n\t\telse if (!strncmp(key, \"login\", 6))\n\t\t\tvalue = cl->login;\n\t\telse if (!strcmp(key, \"*VIP\")) // qqshka: also located in userinfo, but this is more safe/secure way, imo\n\t\t\tsnprintf(ov, sizeof(ov), \"%d\", cl->vip);\n\t\telse if (!strcmp(key, \"netname\"))\n\t\t\tvalue = cl->name;\n\t\telse if (!strcmp(key, \"*state\"))\n\t\t{\n\t\t\tswitch (cl->state)\n\t\t\t{\n\t\t\t\tcase cs_free:         value = \"free\"; break;\n\t\t\t\tcase cs_zombie:       value = \"zombie\"; break;\n\t\t\t\tcase cs_preconnected: value = \"preconnected\"; break;\n\t\t\t\tcase cs_connected:    value = \"connected\"; break;\n\t\t\t\tcase cs_spawned:      value = \"spawned\"; break;\n\n\t\t\t\tdefault: value = \"unknown\"; break;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\tvalue = Info_Get(&cl->_userinfo_ctx_, key);\n\t}\n\telse\n\t\tvalue = \"\";\n\n\tif ((int) strlen(value) > sizebuff)\n\t\tCon_DPrintf(\"PR2_infokey: buffer size too small\\n\");\n\n\tstrlcpy(valbuff, value, sizebuff);\n\t//\tRETURN_STRING(value);\n}\n\n/*\n==============\nPF2_multicast\n \nvoid(vector where, float set) multicast\n==============\n*/\nvoid PF2_multicast(float x, float y, float z, int to)\n//(vec3_t o, int to)\n{\n\tvec3_t o;\n\n\to[0] = x;\n\to[1] = y;\n\to[2] = z;\n\tSV_Multicast(o, to);\n}\n\n/*\n==============\nPF2_disable_updates\n \nvoid(entiny whom, float time) disable_updates\n==============\n*/\nvoid PF2_disable_updates(int entnum, float time1)\n//(int entnum, float time)\n{\n\tclient_t *client;\n\n\t//\tentnum = G_EDICTNUM(OFS_PARM0);\n\t//\ttime1 = G_FLOAT(OFS_PARM1);\n\n\tif (entnum < 1 || entnum > MAX_CLIENTS)\n\t{\n\t\tCon_Printf(\"tried to disable_updates to a non-client\\n\");\n\t\treturn;\n\t}\n\n\tclient = &svs.clients[entnum - 1];\n\n\tclient->disable_updates_stop = realtime + time1;\n}\n\n#define MAX_PR2_FILES 8\n\ntypedef struct\n{\n\tchar name[256];\n\tvfsfile_t  *handle;\n\tfsMode_t accessmode;\n}\npr2_fopen_files_t;\n\npr2_fopen_files_t pr2_fopen_files[MAX_PR2_FILES];\nint pr2_num_open_files = 0;\n\nchar* cmodes[]={\"rb\",\"r\",\"wb\",\"w\",\"ab\",\"a\"};\n// FIXME: read from paks\nint PF2_FS_OpenFile(char *name, fileHandle_t *handle, fsMode_t fmode)\n{\n\tint i, ret = -1;\n\n\tif(pr2_num_open_files >= MAX_PR2_FILES)\n\t{\n\t\treturn -1;\n\t}\n\n\t*handle = 0;\n\tfor (i = 0; i < MAX_PR2_FILES; i++)\n\t\tif (!pr2_fopen_files[i].handle)\n\t\t\tbreak;\n\tif (i == MAX_PR2_FILES)\t//too many already open\n\t{\n\t\treturn -1;\n\t}\n\n\tif (FS_UnsafeFilename(name)) {\n\t\t// someone tried to be clever.\n\t\treturn -1;\n\t}\n\tstrlcpy(pr2_fopen_files[i].name, name, sizeof(pr2_fopen_files[i].name));\n\tpr2_fopen_files[i].accessmode = fmode;\n\tswitch(fmode)\n\t{\n\tcase FS_READ_BIN:\n\tcase FS_READ_TXT:\n#ifndef SERVERONLY\n\t\tpr2_fopen_files[i].handle = FS_OpenVFS(name, cmodes[fmode], FS_ANY);\n#else\n\t\tpr2_fopen_files[i].handle = FS_OpenVFS(name, cmodes[fmode], FS_GAME);\n#endif\n\n\t\tif(!pr2_fopen_files[i].handle)\n\t\t{\n\t\t\treturn -1;\n\t\t}\n\n\t\tCon_DPrintf( \"PF2_FS_OpenFile %s\\n\", name );\n\n\t\tret = VFS_GETLEN(pr2_fopen_files[i].handle);\n\n\t\tbreak;\n\tcase FS_WRITE_BIN:\n\tcase FS_WRITE_TXT:\n\tcase FS_APPEND_BIN:\n\tcase FS_APPEND_TXT:\n\t\t// well, perhaps we we should create path...\n\t\t//\t\tFS_CreatePathRelative(name, FS_GAME_OS);\n\t\tpr2_fopen_files[i].handle = FS_OpenVFS(name, cmodes[fmode], FS_GAME_OS);\n\t\tif ( !pr2_fopen_files[i].handle )\n\t\t{\n\t\t\treturn -1;\n\t\t}\n\t\tCon_DPrintf( \"PF2_FS_OpenFile %s\\n\", name );\n\t\tret = VFS_TELL(pr2_fopen_files[i].handle);\n\n\t\tbreak;\n\tdefault:\n\t\treturn -1;\n\t}\n\n\t*handle = i+1;\n\tpr2_num_open_files++;\n\treturn ret;\n}\n\nvoid PF2_FS_CloseFile(fileHandle_t fnum)\n{\n\tfnum--;\n\tif (fnum < 0 || fnum >= MAX_PR2_FILES)\n\t\treturn;\t//out of range\n\tif(!pr2_num_open_files)\n\t\treturn;\n\tif(!(pr2_fopen_files[fnum].handle))\n\t\treturn;\n\n\tVFS_CLOSE(pr2_fopen_files[fnum].handle);\n\n\tpr2_fopen_files[fnum].handle = NULL;\n\tpr2_num_open_files--;\n}\n\nint seek_origin[]={SEEK_CUR,SEEK_END,SEEK_SET};\n\nintptr_t PF2_FS_SeekFile(fileHandle_t fnum, intptr_t offset, fsOrigin_t type)\n{\n\tfnum--;\n\n\tif (fnum < 0 || fnum >= MAX_PR2_FILES)\n\t\treturn 0;//out of range\n\n\tif(!pr2_num_open_files)\n\t\treturn 0;\n\n\tif(!(pr2_fopen_files[fnum].handle))\n\t\treturn 0;\n\tif(type < 0 || type >= sizeof(seek_origin) / sizeof(seek_origin[0]))\n\t\treturn 0;\n\n\treturn VFS_SEEK(pr2_fopen_files[fnum].handle, offset, seek_origin[type]);\n}\n\nintptr_t PF2_FS_TellFile(fileHandle_t fnum)\n{\n\tfnum--;\n\n\tif (fnum < 0 || fnum >= MAX_PR2_FILES)\n\t\treturn 0;//out of range\n\n\tif(!pr2_num_open_files)\n\t\treturn 0;\n\n\tif(!(pr2_fopen_files[fnum].handle))\n\t\treturn 0;\n\n\treturn VFS_TELL(pr2_fopen_files[fnum].handle);\n}\n\nintptr_t PF2_FS_WriteFile(char *dest, intptr_t quantity, fileHandle_t fnum)\n{\n\tfnum--;\n\tif (fnum < 0 || fnum >= MAX_PR2_FILES)\n\t\treturn 0;//out of range\n\n\tif(!pr2_num_open_files)\n\t\treturn 0;\n\n\tif(!(pr2_fopen_files[fnum].handle))\n\t\treturn 0;\n\n\treturn VFS_WRITE(pr2_fopen_files[fnum].handle, dest, quantity);\n}\n\nintptr_t PF2_FS_ReadFile(char *dest, intptr_t quantity, fileHandle_t fnum)\n{\n\tfnum--;\n\tif (fnum < 0 || fnum >= MAX_PR2_FILES)\n\t\treturn 0;//out of range\n\n\tif(!pr2_num_open_files)\n\t\treturn 0;\n\n\tif(!(pr2_fopen_files[fnum].handle))\n\t\treturn 0;\n\n\treturn VFS_READ(pr2_fopen_files[fnum].handle, dest, quantity, NULL);\n}\n\nvoid PR2_FS_Restart(void)\n{\n\tint i;\n\n\tif(pr2_num_open_files)\n\t{\n\t\tfor (i = 0; i < MAX_PR2_FILES; i++)\n\t\t{\n\t\t\tif(pr2_fopen_files[i].handle)\n\t\t\t{\n\t\t\t\tVFS_CLOSE(pr2_fopen_files[i].handle);\n\t\t\t\tpr2_num_open_files--;\n\t\t\t\tpr2_fopen_files[i].handle = NULL;\n\t\t\t}\n\t\t}\n\t}\n\tif(pr2_num_open_files)\n\t\tSys_Error(\"PR2_fcloseall: pr2_num_open_files != 0\");\n\tpr2_num_open_files = 0;\n\tmemset(pr2_fopen_files,0,sizeof(pr2_fopen_files));\n}\n\nstatic int GetFileList_Compare (const void *p1, const void *p2)\n{\n\treturn strcmp (*((char**)p1), *((char**)p2));\n}\n\n#define FILELIST_GAMEDIR_ONLY\t(1<<0) // if set then search in gamedir only\n#define FILELIST_WITH_PATH\t\t(1<<1) // include path to file\n#define FILELIST_WITH_EXT\t\t(1<<2) // include extension of file\n\nintptr_t PF2_FS_GetFileList(char *path, char *ext,\n\t\t\tchar *listbuff, intptr_t buffsize, intptr_t flags)\n{\n//\textern\tsearchpath_t *com_searchpaths; // evil, because this must be used in fs.c only...\n\tchar\t*gpath = NULL;\n\n\tchar \t*list[MAX_DIRFILES];\n\tconst \tint\tlist_cnt = sizeof(list) / sizeof(list[0]);\n\n\tdir_t\tdir;\n\n//\tsearchpath_t *search;\n\tchar\tnetpath[MAX_OSPATH], *fullname;\n\n\tchar\t*dirptr;\n\n\tint numfiles = 0;\n\tint i, j;\n\n\tif (gamedata.APIversion < 15)\n\t\tflags = 0;\n\n\tmemset(list, 0, sizeof(list));\n\n\tdirptr   = listbuff;\n\t*dirptr  = 0;\n\n\tif (strstr( path, \"..\" ) || strstr( path, \"::\" ))\n\t\treturn 0; // do not allow relative paths\n\n\t// search through the path, one element at a time\n\tfor (i = 0, gpath = NULL; i < list_cnt && ( gpath = FS_NextPath( gpath ) ); )\n\t{\n\t\t// if FILELIST_GAMEDIR_ONLY set then search in gamedir only\n\t\tif ((flags & FILELIST_GAMEDIR_ONLY) && strcmp(gpath, fs_gamedir))\n\t\t\tcontinue;\n\n\t\tsnprintf (netpath, sizeof (netpath), \"%s/%s\", gpath, path);\n\n\t\t// reg exp search...\n\t\tdir = Sys_listdir(netpath, ext, SORT_NO);\n\n\t\tfor (j = 0; i < list_cnt && dir.files[j].name[0]; j++, i++)\n\t\t{\n\t\t\tif (flags & FILELIST_WITH_PATH)\n\t\t\t{\n\t\t\t\t// with path\n\t\t\t\tsnprintf (netpath, sizeof (netpath), \"%s/%s\", gpath, dir.files[j].name);\n\t\t\t\tfullname = netpath;\n\n\t\t\t\t// skip \"./\" prefix\n\t\t\t\tif (!strncmp(fullname, \"./\", sizeof(\"./\") - 1))\n\t\t\t\t\tfullname += 2;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// just name\n\t\t\t\tfullname = dir.files[j].name;\n\t\t\t}\n\n\t\t\t// skip file extension\n\t\t\tif (!(flags & FILELIST_WITH_EXT)) {\n#ifndef SERVERONLY\n\t\t\t\tCOM_StripExtension(fullname, fullname, sizeof(fullname));\n#else\n\t\t\t\tCOM_StripExtension(fullname);\n#endif\n\t\t\t}\n\n\t\t\tlist[i] = Q_strdup(fullname); // a bit below we will free it\n\t\t}\n\t}\n\n\t// sort it, this will help exclude duplicates a bit below\n\tqsort (list, i, sizeof(list[0]), GetFileList_Compare);\n\n\t// copy\n\tfor (i = 0; i < list_cnt && list[i]; i++)\n\t{\n\t\tsize_t namelen = strlen(list[i]) + 1;\n\n\t\tif(dirptr + namelen > listbuff + buffsize)\n\t\t\tbreak;\n\n\t\tif (!*list[i])\n\t\t\tcontinue; // hrm, empty\n\n\t\t// simple way to exclude duplicates, since we sorted it above!\n\t\tif (i && !strcmp(list[i-1], list[i]))\n\t\t\tcontinue;\n\n//\t\tCon_Printf(\"%4d %s\\n\", i, list[i]);\n\n\t\tstrlcpy(dirptr, list[i], namelen);\n\t\tdirptr += namelen;\n\n\t\tnumfiles++;\n\t}\n\n\t// free allocated mem\n\tfor (i = 0; i < list_cnt; i++)\n\t\tQ_free(list[i]);\n\treturn numfiles;\n}\n\n// To prevent mods from hardcoding field offsets which would cause engine incompatibilities.\nstatic uint32_t GetExtFieldCookie(void)\n{\n\tstatic uint32_t cookie = 0;\n\twhile (cookie == 0)\n\t{\n\t\tcookie = ((uint32_t)(rand() & 0xFFFF)) << 16;\n\t}\n\treturn cookie;\n}\n\nstatic qbool ValidateExtFieldToken(uint32_t token, uint32_t *offset)\n{\n\tuint32_t cookie = GetExtFieldCookie();\n\t*offset = token & ~cookie;\n\treturn (token & cookie) == cookie;\n}\n\nstatic intptr_t EXT_SetExtFieldPtr(intptr_t *args)\n{\n\tuint32_t field_ref;\n\tedict_t *e;\n\tsize_t size;\n\n\tif (!ValidateExtFieldToken(args[2], &field_ref))\n\t{\n\t\tCon_Printf(\"SetExtFieldPtr: Corrupt field reference!\\n\");\n\t\treturn 0;\n\t}\n\n\tsize = args[4];\n\n\tif ((field_ref + size) > sizeof(ext_entvars_t))\n\t{\n\t\tCon_Printf(\"SetExtFieldPtr: Field reference out of bounds!\\n\");\n\t\treturn 0;\n\t}\n\n\te = &sv.edicts[NUM_FOR_GAME_EDICT(VM_ArgPtr(args[1]))];\n\tmemcpy((byte*)&e->xv + field_ref, VM_ArgPtr(args[3]), size);\n\n\treturn 1;\n}\n\nstatic intptr_t EXT_GetExtFieldPtr(intptr_t *args)\n{\n\tuint32_t field_ref;\n\tedict_t *e;\n\tsize_t size;\n\n\tif (!ValidateExtFieldToken(args[2], &field_ref))\n\t{\n\t\tCon_Printf(\"GetExtFieldPtr: Corrupt field reference!\\n\");\n\t\treturn 0;\n\t}\n\n\tsize = args[4];\n\n\tif ((field_ref + size) > sizeof(ext_entvars_t))\n\t{\n\t\tCon_Printf(\"GetExtFieldPtr: Field reference out of bounds!\\n\");\n\t\treturn 0;\n\t}\n\n\te = &sv.edicts[NUM_FOR_GAME_EDICT(VM_ArgPtr(args[1]))];\n\tmemcpy(VM_ArgPtr(args[3]), (byte*)&e->xv + field_ref, size);\n\n\treturn 1;\n}\n\nstatic intptr_t EXT_MapExtFieldPtr(intptr_t *args)\n{\n\tchar *key = VM_ArgPtr(args[1]);\n\tif (key)\n\t{\n\t\tif (!strcmp(key, \"alpha\"))\n\t\t{\n\t\t\treturn offsetof(ext_entvars_t, alpha) | GetExtFieldCookie();\n\t\t}\n\t\tif (!strcmp(key, \"colormod\"))\n\t\t{\n\t\t\treturn offsetof(ext_entvars_t, colourmod) | GetExtFieldCookie();\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/*\n  int trap_Map_Extension( const char* ext_name, int mapto)\n  return:\n    0 \tsuccess maping\n   -1\tnot found\n   -2\tcannot map\n*/\nintptr_t PF2_Map_Extension(char *name, int mapto)\n{\n\tint i;\n\n\tif ((mapto - G_EXTENSIONS_FIRST) >= ARRAY_LEN(ext_syscall_tbl))\n\t{\n\t\treturn -2;\n\t}\n\n\tif (!name)\n\t{\n\t\tif (mapto < _G__LASTAPI)\n\t\t{\n\t\t\treturn -2;\n\t\t}\n\t\treturn -1;\n\t}\n\tfor (i = 0; i < ARRAY_LEN(ext_syscalls); i++)\n\t{\n\t\tif (!strcmp(ext_syscalls[i].extname, name))\n\t\t{\n\t\t\text_syscall_tbl[mapto - G_EXTENSIONS_FIRST] = ext_syscalls[i].fun;\n\t\t\treturn mapto;\n\t\t}\n\t}\n\n\treturn -1;\n}\n/////////Bot Functions\nextern cvar_t maxclients, maxspectators;\nint PF2_Add_Bot(char *name, int bottomcolor, int topcolor, char *skin)\n{\n\tclient_t *cl, *newcl = NULL;\n\tint     edictnum;\n\tint     clients, i;\n\textern char *shortinfotbl[];\n\tchar   *s;\n\tedict_t *ent;\n\teval_t *val;\n\tint old_self;\n\tchar info[MAX_EXT_INFO_STRING];\n\n\t// count up the clients and spectators\n\tclients = 0;\n\tfor ( i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++ )\n\t{\n\t\tif ( cl->state == cs_free )\n\t\t\tcontinue;\n\t\tif ( !cl->spectator )\n\t\t\tclients++;\n\t}\n\n\t// if at server limits, refuse connection\n\tif ((int)maxclients.value > MAX_CLIENTS )\n\t\tCvar_SetValue( &maxclients, MAX_CLIENTS );\n\tif ((int)maxspectators.value > MAX_CLIENTS )\n\t\tCvar_SetValue( &maxspectators, MAX_CLIENTS );\n\tif ((int)maxspectators.value + maxclients.value > MAX_CLIENTS )\n\t\tCvar_SetValue( &maxspectators, MAX_CLIENTS - (int)maxclients.value );\n\n\tif ( clients >= ( int ) maxclients.value )\n\t{\n\t\treturn 0;\n\t}\n\tfor ( i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++ )\n\t{\n\t\tif ( cl->state == cs_free )\n\t\t{\n\t\t\tnewcl = cl;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif ( !newcl )\n\t{\n\t\treturn 0;\n\t}\n\n\tmemset(newcl, 0, sizeof(*newcl));\n\tedictnum = (newcl - svs.clients) + 1;\n\tent = EDICT_NUM(edictnum);\n\tED_ClearEdict(ent);\n\n\tmemset(&newcl->_userinfo_ctx_, 0, sizeof(newcl->_userinfo_ctx_));\n\tmemset(&newcl->_userinfoshort_ctx_, 0, sizeof(newcl->_userinfoshort_ctx_));\n\n\tsnprintf( info, sizeof( info ),\n\t          \"\\\\name\\\\%s\\\\topcolor\\\\%d\\\\bottomcolor\\\\%d\\\\emodel\\\\6967\\\\pmodel\\\\13845\\\\skin\\\\%s\\\\*bot\\\\1\",\n\t          name, topcolor, bottomcolor, skin );\n\n\tnewcl->_userinfo_ctx_.max      = MAX_CLIENT_INFOS;\n\tnewcl->_userinfoshort_ctx_.max = MAX_CLIENT_INFOS;\n\tInfo_Convert(&newcl->_userinfo_ctx_, info);\n\n\tnewcl->state = cs_spawned;\n\tnewcl->userid = SV_GenerateUserID();\n\tnewcl->datagram.allowoverflow = true;\n\tnewcl->datagram.data = newcl->datagram_buf;\n\tnewcl->datagram.maxsize = sizeof( newcl->datagram_buf );\n\tnewcl->spectator = 0;\n\tnewcl->isBot = 1;\n\tSV_SetClientConnectionTime(newcl);\n\tstrlcpy(newcl->name, name, sizeof(newcl->name));\n\n\tnewcl->entgravity = 1.0;\n\tval = PR2_GetEdictFieldValue( ent, \"gravity\" ); // FIXME: do it similar to maxspeed\n\tif ( val )\n\t\tval->_float = 1.0;\n\tsv_client->maxspeed = sv_maxspeed.value;\n\n\tif (fofs_maxspeed)\n\t\tEdictFieldFloat(ent, fofs_maxspeed) = sv_maxspeed.value;\n\n\tnewcl->edict = ent;\n\tent->v->colormap = edictnum;\n\tval = PR2_GetEdictFieldValue(ent, \"isBot\"); // FIXME: do it similar to maxspeed\n\tif( val )\n\t\tval->_int = 1;\n\n\t// restore client name.\n\tPR_SetEntityString(ent, ent->v->netname, newcl->name);\n\n\tmemset( newcl->stats, 0, sizeof( newcl->stats ) );\n\tSZ_InitEx (&newcl->netchan.message, newcl->netchan.message_buf, (int)sizeof(newcl->netchan.message_buf), true);\n\tSZ_Clear( &newcl->netchan.message );\n\tnewcl->netchan.drop_count = 0;\n\tnewcl->netchan.incoming_sequence = 1;\n\n\t// copy the most important userinfo into userinfoshort\n\t// {\n\tSV_ExtractFromUserinfo( newcl, true );\n\n\tfor ( i = 0; shortinfotbl[i] != NULL; i++ )\n\t{\n\t\ts = Info_Get( &newcl->_userinfo_ctx_, shortinfotbl[i] );\n\t\tInfo_SetStar( &newcl->_userinfoshort_ctx_, shortinfotbl[i], s );\n\t}\n\n\t// move star keys to infoshort\n\tInfo_CopyStar( &newcl->_userinfo_ctx_, &newcl->_userinfoshort_ctx_ );\n\n\t// }\n\n\tnewcl->disable_updates_stop = -1.0;\t// Vladis\n\n\tSV_FullClientUpdate( newcl, &sv.reliable_datagram );\n\n\told_self = pr_global_struct->self;\n\tpr_global_struct->time = sv.time;\n\tpr_global_struct->self = EDICT_TO_PROG(newcl->edict);\n\n\tPR2_GameClientConnect(0);\n\tPR2_GamePutClientInServer(0);\n\n\tpr_global_struct->self = old_self;\n\treturn edictnum;\n}\n\nvoid RemoveBot(client_t *cl)\n{\n\n\tif( !cl->isBot )\n\t\treturn;\n\n\tpr_global_struct->self = EDICT_TO_PROG(cl->edict);\n\tif ( sv_vm )\n\t\tPR2_GameClientDisconnect(0);\n\n\tcl->old_frags = 0;\n\tcl->edict->v->frags = 0.0;\n\tcl->name[0] = 0;\n\tcl->state = cs_free;\n\n\tInfo_RemoveAll(&cl->_userinfo_ctx_);\n\tInfo_RemoveAll(&cl->_userinfoshort_ctx_);\n\n\tSV_FullClientUpdate( cl, &sv.reliable_datagram );\n\tcl->isBot = 0;\n}\n\nvoid PF2_Remove_Bot(int entnum)\n{\n\tclient_t *cl;\n\tint old_self;\n\n\tif ( entnum < 1 || entnum > MAX_CLIENTS )\n\t{\n\t\tCon_Printf( \"tried to remove a non-botclient %d \\n\", entnum );\n\t\treturn;\n\t}\n\tcl = &svs.clients[entnum - 1];\n\tif ( !cl->isBot )\n\t{\n\t\tCon_Printf( \"tried to remove a non-botclient %d \\n\", entnum );\n\t\treturn;\n\t}\n\told_self = pr_global_struct->self; //save self\n\n\tpr_global_struct->self = entnum;\n\tRemoveBot(cl);\n\tpr_global_struct->self = old_self;\n\n}\n\n// FIXME: Why PR2_UserInfoChanged is not called here? Like for normal players.\n//        Why we need this special handling in the first place?\nvoid PF2_SetBotUserInfo(int entnum, char *key, char *value, int flags)\n{\n\tclient_t *cl;\n\tint     i;\n\textern char *shortinfotbl[];\n\n\tif (gamedata.APIversion < 15)\n\t\tflags = 0;\n\tif (strstr(key, \"&c\") || strstr(key, \"&r\") || strstr(value, \"&c\") || strstr(value, \"&r\"))\n\t\treturn;\n\n\tif ( entnum < 1 || entnum > MAX_CLIENTS )\n\t{\n\t\tCon_Printf( \"tried to change userinfo a non-botclient %d \\n\", entnum );\n\t\treturn;\n\t}\n\tcl = &svs.clients[entnum - 1];\n\tif ( !cl->isBot )\n\t{\n\t\tCon_Printf( \"tried to change userinfo a non-botclient %d \\n\", entnum );\n\t\treturn;\n\t}\n\n\tif ( flags & SETUSERINFO_STAR )\n\t\tInfo_SetStar( &cl->_userinfo_ctx_, key, value );\n\telse\n\t\tInfo_Set( &cl->_userinfo_ctx_, key, value );\n\tSV_ExtractFromUserinfo( cl, !strcmp( key, \"name\" ) );\n\n\tfor ( i = 0; shortinfotbl[i] != NULL; i++ )\n\t{\n\t\tif ( key[0] == '_' || !strcmp( key, shortinfotbl[i] ) )\n\t\t{\n\t\t\tchar *nuw = Info_Get( &cl->_userinfo_ctx_, key );\n\n\t\t\tInfo_Set( &cl->_userinfoshort_ctx_, key, nuw );\n\n\t\t\ti = cl - svs.clients;\n\t\t\tMSG_WriteByte( &sv.reliable_datagram, svc_setinfo );\n\t\t\tMSG_WriteByte( &sv.reliable_datagram, i );\n\t\t\tMSG_WriteString( &sv.reliable_datagram, key );\n\t\t\tMSG_WriteString( &sv.reliable_datagram, nuw );\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nvoid PF2_SetBotCMD(int entnum, int msec, float a1, float a2, float a3,\n\t\t\t\tint forwardmove, int sidemove, int upmove, int buttons, int impulse)\n{\n\tclient_t *cl;\n\n\tif ( entnum < 1 || entnum > MAX_CLIENTS )\n\t{\n\t\tCon_Printf( \"tried to set cmd a non-botclient %d \\n\", entnum );\n\t\treturn;\n\t}\n\tcl = &svs.clients[entnum - 1];\n\tif ( !cl->isBot )\n\t{\n\t\tCon_Printf( \"tried to set cmd a non-botclient %d \\n\", entnum );\n\t\treturn;\n\t}\n\tcl->botcmd.msec = msec;\n\tcl->botcmd.angles[0] = a1;\n\tcl->botcmd.angles[1] = a2;\n\tcl->botcmd.angles[2] = a3;\n\tcl->botcmd.forwardmove = forwardmove;\n\tcl->botcmd.sidemove = sidemove;\n\tcl->botcmd.upmove = upmove;\n\tcl->botcmd.buttons = buttons;\n\tcl->botcmd.impulse = impulse;\n\n\tif (cl->edict->v->fixangle)\n\t{\n\t\tVectorCopy(cl->edict->v->angles, cl->botcmd.angles);\n\t\tcl->botcmd.angles[PITCH] *= -3;\n\t\tcl->edict->v->fixangle = 0;\n\t}\n}\n\n//=========================================\n// some time support in QVM\n//=========================================\n\n/*\n==============\nPF2_QVMstrftime\n==============\n*/\nint PF2_QVMstrftime(char *valbuff, int sizebuff, char *fmt, int offset)\n{\n\tstruct tm *newtime;\n\ttime_t long_time;\n\tint ret;\n\n\tif (sizebuff <= 0 || !valbuff) {\n\t\tCon_DPrintf(\"PF2_QVMstrftime: wrong buffer\\n\");\n\t\treturn 0;\n\t}\n\n\ttime(&long_time);\n\tlong_time += offset;\n\tnewtime = localtime(&long_time);\n\n\tif (!newtime)\n\t{\n\t\tvalbuff[0] = 0; // or may be better set to \"#bad date#\" ?\n\t\treturn 0;\n\t}\n\n\tret = strftime(valbuff, sizebuff-1, fmt, newtime);\n\n\tif (!ret) {\n\t\tvalbuff[0] = 0; // or may be better set to \"#bad date#\" ?\n\t\tCon_DPrintf(\"PF2_QVMstrftime: buffer size too small\\n\");\n\t\treturn 0;\n\t}\n\treturn ret;\n}\n\n// a la the ZQ_PAUSE QC extension\nvoid PF2_setpause(int pause)\n{\n\tif (pause != (sv.paused & 1))\n\t\tSV_TogglePause (NULL, 1);\n}\n\nvoid PF2_SetUserInfo(int entnum, char *k, char *v, int flags)\n{\n\tclient_t *cl;\n\tchar   key[MAX_KEY_STRING];\n\tchar   value[MAX_KEY_STRING];\n\tchar   s[MAX_KEY_STRING * 4];\n\tint     i;\n\textern char *shortinfotbl[];\n\n\tif (strstr(k, \"&c\") || strstr(k, \"&r\") || strstr(v, \"&c\") || strstr(v, \"&r\"))\n\t\treturn;\n\n\tif ( entnum < 1 || entnum > MAX_CLIENTS )\n\t{\n\t\tCon_Printf( \"tried to change userinfo a non-client %d \\n\", entnum );\n\t\treturn;\n\t}\n\n\tcl = &svs.clients[entnum - 1];\n\n\t// well, our API is weird\n\tif ( cl->isBot )\n\t{\n\t\tPF2_SetBotUserInfo(entnum, k, v, flags);\n\t\treturn;\n\t}\n\n\t// tokenize\n\n\tsnprintf( s, sizeof(s), \"PF2_SetUserInfo \\\"%-.*s\\\" \\\"%-.*s\\\"\", (int)sizeof(key), k, (int)sizeof(value), v );\n\n\tCmd_TokenizeString( s );\n\n\t// well, PR2_UserInfoChanged() may call PF2_SetUserInfo() again, so we better save thouse\n\tstrlcpy( key, Cmd_Argv(1), sizeof(key) );\n\tstrlcpy( value, Cmd_Argv(2), sizeof(value) );\n\n\tif( sv_vm )\n\t{\n\t\tpr_global_struct->time = sv.time;\n\t\tpr_global_struct->self = EDICT_TO_PROG(cl->edict);\n\n\t\tif (PR2_UserInfoChanged(0))\n\t\t\treturn;\n\t}\n\n\tif ( flags & SETUSERINFO_STAR )\n\t\tInfo_SetStar( &cl->_userinfo_ctx_, key, value );\n\telse\n\t\tInfo_Set( &cl->_userinfo_ctx_, key, value );\n\n\tSV_ExtractFromUserinfo( cl, !strcmp( key, \"name\" ) );\n\tPR2_UserInfoChanged(1);\n\n\tfor ( i = 0; shortinfotbl[i] != NULL; i++ )\n\t{\n\t\tif ( !strcmp( key, shortinfotbl[i] ) )\n\t\t{\n\t\t\tchar *nuw = Info_Get( &cl->_userinfo_ctx_, key );\n\n\t\t\t// well, here we do not have if ( flags & SETUSERINFO_STAR ) because shortinfotbl[] does't have any star key\n\n\t\t\tInfo_Set( &cl->_userinfoshort_ctx_, key, nuw );\n\n\t\t\ti = cl - svs.clients;\n\t\t\tMSG_WriteByte( &sv.reliable_datagram, svc_setinfo );\n\t\t\tMSG_WriteByte( &sv.reliable_datagram, i );\n\t\t\tMSG_WriteString( &sv.reliable_datagram, key );\n\t\t\tMSG_WriteString( &sv.reliable_datagram, nuw );\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nvoid PF2_VisibleTo(int viewer, int first, int len, byte *visible)\n{\n\tint e, last = first + len;\n\tedict_t *ent;\n\tedict_t *viewer_ent = EDICT_NUM(viewer);\n\tvec3_t org;\n\tbyte *pvs;\n\n\tif (last > sv.num_edicts)\n\t\tlast = sv.num_edicts;\n\n\tVectorAdd(viewer_ent->v->origin, viewer_ent->v->view_ofs, org);\n\tpvs = CM_FatPVS(org);\n\n\tfor (e = first, ent = EDICT_NUM(e); e < last; e++, ent = NEXT_EDICT(ent))\n\t{\n\t\tint i;\n\t\tif (ent->e.num_leafs < 0 || ent->e.free\n\t\t\t|| (e >= 1 && e <= MAX_CLIENTS && svs.clients[e - 1].state != cs_spawned)) {\n\t\t\tcontinue; // Ignore free edicts or not active client.\n\t\t}\n\t\tfor (i = 0; i < ent->e.num_leafs; i++) {\n\t\t\tif (pvs[ent->e.leafnums[i] >> 3] & (1 << (ent->e.leafnums[i]&7))) {\n\t\t\t\tvisible[e - first] = true; // seems to be visible\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\n//===========================================================================\n// SysCalls\n//===========================================================================\n\n#define VMV(x) _vmf(args[x]), _vmf(args[(x) + 1]), _vmf(args[(x) + 2])\n#define VME(x) EDICT_NUM(args[x])\nintptr_t PR2_GameSystemCalls(intptr_t *args) {\n\tswitch (args[0]) {\n\tcase G_GETAPIVERSION:\n\t\treturn GAME_API_VERSION;\n\tcase G_DPRINT:\n\t\tCon_DPrintf(\"%s\", (const char *)VMA(1));\n\t\treturn 0;\n\tcase G_ERROR:\n\t\tPR2_RunError(VMA(1));\n\t\treturn 0;\n\tcase G_GetEntityToken:\n\t\tVM_CheckBounds(sv_vm, args[1], args[2]);\n\t\tpr2_ent_data_ptr = COM_Parse(pr2_ent_data_ptr);\n\t\tstrlcpy(VMA(1), com_token, args[2]);\n\t\treturn pr2_ent_data_ptr != NULL;\n\tcase G_SPAWN_ENT:\n\t\treturn NUM_FOR_EDICT(ED_Alloc());\n\tcase G_REMOVE_ENT:\n\t\tED_Free(VME(1));\n\t\treturn 0;\n\tcase G_PRECACHE_SOUND:\n\t\tPF2_precache_sound(VMA(1));\n\t\treturn 0;\n\tcase G_PRECACHE_MODEL:\n\t\tPF2_precache_model(VMA(1));\n\t\treturn 0;\n\tcase G_LIGHTSTYLE:\n\t\tPF2_lightstyle(args[1], VMA(2));\n\t\treturn 0;\n\tcase G_SETORIGIN:\n\t\tPF2_setorigin(VME(1), VMV(2));\n\t\treturn 0;\n\tcase G_SETSIZE:\n\t\tPF2_setsize(VME(1), VMV(2), VMV(5));\n\t\treturn 0;\n\tcase G_SETMODEL:\n\t\tPF2_setmodel(VME(1), VMA(2));\n\t\treturn 0;\n\tcase G_BPRINT: {\n\t\tint flags = args[3];\n\t\tif (gamedata.APIversion < 15)\n\t\t\tflags = 0;\n\t\tSV_BroadcastPrintfEx(args[1], flags, \"%s\", VMA(2));\n\t}\n\t\treturn 0;\n\tcase G_SPRINT:\n\t\tPF2_sprint(args[1], args[2], VMA(3), args[4]);\n\t\treturn 0;\n\tcase G_CENTERPRINT:\n\t\tPF2_centerprint(args[1], VMA(2));\n\t\treturn 0;\n\tcase G_AMBIENTSOUND:\n\t\tPF2_ambientsound(VMV(1), VMA(4), VMF(5), VMF(6));\n\t\treturn 0;\n\tcase G_SOUND:\n\t\t/*\n\t\t=================\n\t\tPF2_sound\n\n\t\tEach entity can have eight independant sound sources, like voice,\n\t\tweapon, feet, etc.\n\n\t\tChannel 0 is an auto-allocate channel, the others override anything\n\t\talready running on that entity/channel pair.\n\n\t\tAn attenuation of 0 will play full volume everywhere in the level.\n\t\tLarger attenuations will drop off.\n\t\tvoid sound( gedict_t * ed, int channel, char *samp, float vol, float att )\n\t\t=================\n\t\t*/\n\t\tSV_StartSound(VME(1), args[2], VMA(3), VMF(4) * 255, VMF(5));\n\t\treturn 0;\n\tcase G_TRACELINE:\n\t\tPF2_traceline(VMV(1), VMV(4), args[7], args[8]);\n\t\treturn 0;\n\tcase G_CHECKCLIENT:\n\t\treturn PF2_checkclient();\n\tcase G_STUFFCMD:\n\t\tPF2_stuffcmd(args[1], VMA(2), args[3]);\n\t\treturn 0;\n\tcase G_LOCALCMD:\n\t\t/* =================\n\t\tSends text over to the server's execution buffer\n\n\t\tlocalcmd (string)\n\t\t================= */\n\t\tCbuf_AddTextEx(&cbuf_server, VMA(1));\n\t\treturn 0;\n\tcase G_CVAR:\n\t\treturn PASSFLOAT(Cvar_Value(VMA(1)));\n\tcase G_CVAR_SET:\n\t\tCvar_SetByName(VMA(1), VMA(2));\n\t\treturn 0;\n\tcase G_FINDRADIUS:\n\t\treturn PF2_FindRadius(NUM_FOR_GAME_EDICT(VMA(1)), (float *)VMA(2), VMF(3));\n\tcase G_WALKMOVE:\n\t\treturn PF2_walkmove(VME(1), VMF(2), VMF(3));\n\tcase G_DROPTOFLOOR:\n\t\treturn PF2_droptofloor(VME(1));\n\tcase G_CHECKBOTTOM:\n\t\treturn SV_CheckBottom(VME(1));\n\tcase G_POINTCONTENTS:\n\t\treturn PF2_pointcontents(VMV(1));\n\tcase G_NEXTENT:\n\t\treturn PF2_nextent(args[1]);\n\tcase G_AIM:\n\t\treturn 0;\n\tcase G_MAKESTATIC:\n\t\tPF2_makestatic(VME(1));\n\t\treturn 0;\n\tcase G_SETSPAWNPARAMS:\n\t\tPF2_setspawnparms(args[1]);\n\t\treturn 0;\n\tcase G_CHANGELEVEL:\n\t\tPF2_changelevel(VMA(1), VMA(2));\n\t\treturn 0;\n\tcase G_LOGFRAG:\n\t\tPF2_logfrag(args[1], args[2]);\n\t\treturn 0;\n\tcase G_GETINFOKEY:\n\t\tVM_CheckBounds(sv_vm, args[3], args[4]);\n\t\tPF2_infokey(args[1], VMA(2), VMA(3), args[4]);\n\t\treturn 0;\n\tcase G_MULTICAST:\n\t\tPF2_multicast(VMV(1), args[4]);\n\t\treturn 0;\n\tcase G_DISABLEUPDATES:\n\t\tPF2_disable_updates(args[1], VMF(2));\n\t\treturn 0;\n\tcase G_WRITEBYTE:\n\t\tPF2_WriteByte(args[1], args[2]);\n\t\treturn 0;\n\tcase G_WRITECHAR:\n\t\tPF2_WriteChar(args[1], args[2]);\n\t\treturn 0;\n\tcase G_WRITESHORT:\n\t\tPF2_WriteShort(args[1], args[2]);\n\t\treturn 0;\n\tcase G_WRITELONG:\n\t\tPF2_WriteLong(args[1], args[2]);\n\t\treturn 0;\n\tcase G_WRITEANGLE:\n\t\tPF2_WriteAngle(args[1], VMF(2));\n\t\treturn 0;\n\tcase G_WRITECOORD:\n\t\tPF2_WriteCoord(args[1], VMF(2));\n\t\treturn 0;\n\tcase G_WRITESTRING:\n\t\tPF2_WriteString(args[1], VMA(2));\n\t\treturn 0;\n\tcase G_WRITEENTITY:\n\t\tPF2_WriteEntity(args[1], args[2]);\n\t\treturn 0;\n\tcase G_FLUSHSIGNON:\n\t\tSV_FlushSignon();\n\t\treturn 0;\n\tcase g_memset:\n\t\tVM_CheckBounds(sv_vm, args[1], args[3]);\n\t\tmemset(VMA(1), args[2], args[3]);\n\t\treturn args[1];\n\tcase g_memcpy:\n\t\tVM_CheckBounds2(sv_vm, args[1], args[2], args[3]);\n\t\tmemcpy(VMA(1), VMA(2), args[3]);\n\t\treturn args[1];\n\tcase g_strncpy:\n\t\tVM_CheckBounds2(sv_vm, args[1], args[2], args[3]);\n\t\tstrncpy(VMA(1), VMA(2), args[3]);\n\t\treturn args[1];\n\tcase g_sin:\n\t\treturn PASSFLOAT(sin(VMF(1)));\n\tcase g_cos:\n\t\treturn PASSFLOAT(cos(VMF(1)));\n\tcase g_atan2:\n\t\treturn PASSFLOAT(atan2(VMF(1), VMF(2)));\n\tcase g_sqrt:\n\t\treturn PASSFLOAT(sqrt(VMF(1)));\n\tcase g_floor:\n\t\treturn PASSFLOAT(floor(VMF(1)));\n\tcase g_ceil:\n\t\treturn PASSFLOAT(ceil(VMF(1)));\n\tcase g_acos:\n\t\treturn PASSFLOAT(acos(VMF(1)));\n\tcase G_CMD_ARGC:\n\t\treturn Cmd_Argc();\n\tcase G_CMD_ARGV:\n\t\tVM_CheckBounds(sv_vm, args[2], args[3]);\n\t\tstrlcpy(VMA(2), Cmd_Argv(args[1]), args[3]);\n\t\treturn 0;\n\tcase G_TraceCapsule:\n\t\tPF2_TraceCapsule(VMV(1), VMV(4), args[7], VME(8), VMV(9), VMV(12));\n\t\treturn 0;\n\tcase G_FSOpenFile:\n\t\treturn PF2_FS_OpenFile(VMA(1), (fileHandle_t *)VMA(2), (fsMode_t)args[3]);\n\tcase G_FSCloseFile:\n\t\tPF2_FS_CloseFile((fileHandle_t)args[1]);\n\t\treturn 0;\n\tcase G_FSReadFile:\n\t\tVM_CheckBounds(sv_vm, args[1], args[2]);\n\t\treturn PF2_FS_ReadFile(VMA(1), args[2], (fileHandle_t)args[3]);\n\tcase G_FSWriteFile:\n\t\tVM_CheckBounds(sv_vm, args[1], args[2]);\n\t\treturn PF2_FS_WriteFile(VMA(1), args[2], (fileHandle_t)args[3]);\n\tcase G_FSSeekFile:\n\t\treturn PF2_FS_SeekFile((fileHandle_t)args[1], args[2], (fsOrigin_t)args[3]);\n\tcase G_FSTellFile:\n\t\treturn PF2_FS_TellFile((fileHandle_t)args[1]);\n\tcase G_FSGetFileList:\n\t\tVM_CheckBounds(sv_vm, args[3], args[4]);\n\t\treturn PF2_FS_GetFileList(VMA(1), VMA(2), VMA(3), args[4], args[5]);\n\tcase G_CVAR_SET_FLOAT:\n\t\tCvar_SetValueByName(VMA(1), VMF(2));\n\t\treturn 0;\n\tcase G_CVAR_STRING:\n\t\tVM_CheckBounds(sv_vm, args[2], args[3]);\n\t\tstrlcpy(VMA(2), Cvar_String(VMA(1)), args[3]);\n\t\treturn 0;\n\tcase G_Map_Extension:\n\t\treturn PF2_Map_Extension(VMA(1), args[2]);\n\tcase G_strcmp:\n\t\treturn strcmp(VMA(1), VMA(2));\n\tcase G_strncmp:\n\t\treturn strncmp(VMA(1), VMA(2), args[3]);\n\tcase G_stricmp:\n\t\treturn strcasecmp(VMA(1), VMA(2));\n\tcase G_strnicmp:\n\t\treturn strncasecmp(VMA(1), VMA(2), args[3]);\n\tcase G_Find:\n\t\treturn PF2_Find(NUM_FOR_GAME_EDICT(VMA(1)), args[2], VMA(3));\n\tcase G_executecmd:\n\t\tPF2_executecmd();\n\t\treturn 0;\n\tcase G_conprint:\n\t\tSys_Printf(\"%s\", VMA(1));\n\t\treturn 0;\n\tcase G_readcmd:\n\t\tVM_CheckBounds(sv_vm, args[2], args[3]);\n\t\tPF2_readcmd(VMA(1), VMA(2), args[3]);\n\t\treturn 0;\n\tcase G_redirectcmd:\n\t\tPF2_redirectcmd(NUM_FOR_GAME_EDICT(VMA(1)), VMA(2));\n\t\treturn 0;\n\tcase G_Add_Bot:\n\t\treturn PF2_Add_Bot(VMA(1), args[2], args[3], VMA(4));\n\tcase G_Remove_Bot:\n\t\tPF2_Remove_Bot(args[1]);\n\t\treturn 0;\n\tcase G_SetBotUserInfo:\n\t\tPF2_SetBotUserInfo(args[1], VMA(2), VMA(3), args[4]);\n\t\treturn 0;\n\tcase G_SetBotCMD:\n\t\tPF2_SetBotCMD(args[1], args[2], VMV(3), args[6], args[7], args[8], args[9], args[10]);\n\t\treturn 0;\n\tcase G_QVMstrftime:\n\t\tVM_CheckBounds(sv_vm, args[1], args[2]);\n\t\treturn PF2_QVMstrftime(VMA(1), args[2], VMA(3), args[4]);\n\tcase G_CMD_ARGS:\n\t\tVM_CheckBounds(sv_vm, args[1], args[2]);\n\t\tstrlcpy(VMA(1), Cmd_Args(), args[2]);\n\t\treturn 0;\n\tcase G_CMD_TOKENIZE:\n\t\tCmd_TokenizeString(VMA(1));\n\t\treturn 0;\n\tcase g_strlcpy:\n\t\tVM_CheckBounds(sv_vm, args[1], args[3]);\n\t\treturn strlcpy(VMA(1), VMA(2), args[3]);\n\tcase g_strlcat:\n\t\tVM_CheckBounds(sv_vm, args[1], args[3]);\n\t\treturn strlcat(VMA(1), VMA(2), args[3]);\n\tcase G_MAKEVECTORS:\n\t\tAngleVectors(VMA(1), pr_global_struct->v_forward, pr_global_struct->v_right,\n\t\t\t\t\t\t\t\t pr_global_struct->v_up);\n\t\treturn 0;\n\tcase G_NEXTCLIENT:\n\t\treturn PF2_nextclient(NUM_FOR_GAME_EDICT(VMA(1)));\n\tcase G_PRECACHE_VWEP_MODEL:\n\t\treturn PF2_precache_vwep_model(VMA(1));\n\tcase G_SETPAUSE:\n\t\tPF2_setpause(args[1]);\n\t\treturn 0;\n\tcase G_SETUSERINFO:\n\t\tPF2_SetUserInfo(args[1], VMA(2), VMA(3), args[4]);\n\t\treturn 0;\n\tcase G_MOVETOGOAL:\n\t\tPF2_MoveToGoal(VMF(1));\n\t\treturn 0;\n\tcase G_VISIBLETO:\n\t\tVM_CheckBounds(sv_vm, args[4], args[3]);\n\t\tmemset(VMA(4), 0, args[3]); // Ensure same memory state on each run.\n\t\tPF2_VisibleTo(args[1], args[2], args[3], VMA(4));\n\t\treturn 0;\n\tdefault:\n\t\tif (args[0] >= _G__LASTAPI && ext_syscall_tbl[args[0] - G_EXTENSIONS_FIRST])\n\t\t{\n\t\t\treturn ext_syscall_tbl[args[0] - G_EXTENSIONS_FIRST](args);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tSV_Error(\"Bad game system trap: %ld\", (long int)args[0]);\n\t\t}\n\t}\n\treturn 0;\n}\n\n#endif /* USE_PR2 */\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/pr2_edict.c",
    "content": "/*\n *  QW262\n *  Copyright (C) 2004  [sd] angel\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by\n *  the Free Software Foundation; either version 2 of the License, or\n *  (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n *\n *\n *  \n */\n\n#ifndef CLIENTONLY\n#ifdef USE_PR2\n\n#include \"qwsvdef.h\"\n\nfield_t *fields;\n\neval_t *PR2_GetEdictFieldValue(edict_t *ed, char *field)\n{\n\tchar *s;\n\tfield_t\t*f;\n\n\tif (!sv_vm)\n\t\treturn PR1_GetEdictFieldValue(ed, field);\n\n\tfor (f = fields; (s = PR2_GetString(f->name)) && *s; f++)\n\t\tif (!strcasecmp(PR2_GetString(f->name), field))\n\t\t\treturn (eval_t *)((char *)ed->v + f->ofs);\n\n\treturn NULL;\n}\n\nint ED2_FindFieldOffset (char *field)\n{\n\tchar *s;\n\tfield_t\t*f;\n\n\tif (!sv_vm)\n\t\treturn ED1_FindFieldOffset(field);\n\n\tfor (f = fields; (s = PR2_GetString(f->name)) && *s; f++)\n\t\tif (!strcasecmp(PR2_GetString(f->name), field))\n\t\t\treturn f->ofs;\n\n\treturn 0;\n}\n\n/*\n=============\nED2_PrintEdict_f\n \nFor debugging, prints a single edicy\n=============\n*/\nvoid ED2_PrintEdict_f (void)\n{\n\textern void ED_PrintEdict_f (void);\n\n\tif(!sv_vm)\n\t\tED_PrintEdict_f();\n}\n\n/*\n=============\nED2_PrintEdicts\n \nFor debugging, prints all the entities in the current server\n=============\n*/\nvoid ED2_PrintEdicts (void)\n{\n\textern void ED_PrintEdicts (void);\n\n\tif(!sv_vm)\n\t\tED_PrintEdicts();\n}\n\n#endif /* USE_PR2 */\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/pr2_exec.c",
    "content": "/*\n *  QW262\n *  Copyright (C) 2004  [sd] angel\n *\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by\n *  the Free Software Foundation; either version 2 of the License, or\n *  (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n *\n *\n *  \n */\n\n#ifndef CLIENTONLY\n#ifdef USE_PR2\n\n#include \"qwsvdef.h\"\n#include \"vm_local.h\"\n\ngameData_t gamedata;\nextern field_t *fields;\n\n// 0 = pr1 (qwprogs.dat etc), 1 = native (.so/.dll), 2 = q3vm (.qvm), 3 = q3vm (.qvm) with JIT\ncvar_t sv_progtype = { \"sv_progtype\",\"0\" };\n\n// 0 = standard, 1 = pr2 mods set string_t fields as byte offsets to location of actual strings\ncvar_t sv_pr2references = {\"sv_pr2references\", \"0\"};\n\nvoid ED2_PrintEdicts (void);\nvoid PR2_Profile_f (void);\nvoid ED2_PrintEdict_f (void);\nvoid ED_Count (void);\nvoid VM_VmInfo_f( void );\n\nvoid PR2_Init(void)\n{\n\tint p;\n\tint usedll;\n\tCvar_Register(&sv_progtype);\n\tCvar_Register(&sv_progsname);\n\tCvar_Register(&sv_pr2references);\n\tCvar_Register(&vm_rtChecks);\n#ifdef WITH_NQPROGS\n\tCvar_Register(&sv_forcenqprogs);\n#endif\n\n\tp = SV_CommandLineProgTypeArgument();\n\n\tif (p && p < COM_Argc())\n\t{\n\t\tusedll = Q_atoi(COM_Argv(p + 1));\n\n\t\tif (usedll > VMI_COMPILED || usedll < VMI_NONE)\n\t\t\tusedll = VMI_NONE;\n\t\tCvar_SetValue(&sv_progtype,usedll);\n\t}\n\n\tCmd_AddCommand (\"edict\", ED2_PrintEdict_f);\n\tCmd_AddCommand (\"edicts\", ED2_PrintEdicts);\n\tCmd_AddCommand (\"edictcount\", ED_Count);\n\tCmd_AddCommand (\"profile\", PR2_Profile_f);\n\tCmd_AddCommand (\"mod\", PR2_GameConsoleCommand);\n\n\tCmd_AddCommand (\"vminfo\", VM_VmInfo_f);\n\tmemset(pr_newstrtbl, 0, sizeof(pr_newstrtbl));\n}\n\nvoid PR2_Profile_f(void)\n{\n\tif(!sv_vm)\n\t{\n\t\tPR_Profile_f();\n\t\treturn;\n\t}\n}\n\n//===========================================================================\n// PR2_GetString: only called to get direct addresses now\n//===========================================================================\nchar *PR2_GetString(intptr_t num)\n{\n\tif(!sv_vm)\n\t\treturn PR1_GetString(num);\n\n\tswitch (sv_vm->type)\n\t{\n\tcase VMI_NONE:\n\t\treturn PR1_GetString(num);\n\n\tcase VMI_NATIVE:\n\t\tif (num) {\n\t\t\treturn (char *)num;\n\t\t}\n\t\telse {\n\t\t\treturn \"\";\n\t\t}\n\n\tcase VMI_BYTECODE:\n\tcase VMI_COMPILED:\n\t\tif (num <= 0)\n\t\t\treturn \"\";\n\t\treturn VM_ExplicitArgPtr(sv_vm, num);\n\t}\n\n\treturn NULL;\n}\n\nintptr_t PR2_EntityStringLocation(string_t offset, int max_size)\n{\n\tif (offset > 0 && offset < pr_edict_size * sv.max_edicts - max_size) {\n\t\treturn ((intptr_t)sv.game_edicts + offset);\n\t}\n\n\treturn 0;\n}\n\nintptr_t PR2_GlobalStringLocation(string_t offset)\n{\n\t// FIXME: the mod has allocated this memory, don't have max size\n\tif (offset > 0) {\n\t\treturn ((intptr_t)pr_global_struct + offset);\n\t}\n\n\treturn 0;\n}\n\nchar *PR2_GetEntityString(string_t num)\n{\n\n\tif(!sv_vm)\n\t\treturn PR1_GetString(num);\n\n\tswitch (sv_vm->type)\n\t{\n\tcase VMI_NONE:\n\t\treturn PR1_GetString(num);\n\n\tcase VMI_NATIVE:\n\t\tif (num) {\n\t\t\tif (sv_vm->pr2_references) {\n\t\t\t\tchar** location = (char**)PR2_EntityStringLocation(num, sizeof(char*));\n\t\t\t\tif (location && *location) {\n\t\t\t\t\treturn *location;\n\t\t\t\t}\n\t\t\t}\n#ifndef idx64\n\t\t\telse {\n\t\t\t\treturn (char *) (num);\n\t\t\t}\n#endif\n\t\t}\n\t\treturn \"\";\n\n\tcase VMI_BYTECODE:\n\tcase VMI_COMPILED:\n\t\tif (num <= 0)\n\t\t\treturn \"\";\n\t\tif (sv_vm->pr2_references) {\n\t\t\tnum = *(string_t*)PR2_EntityStringLocation(num, sizeof(string_t));\n\t\t}\n\t\treturn VM_ExplicitArgPtr(sv_vm, num);\n\t}\n\n\treturn NULL;\n}\n\n//===========================================================================\n// PR2_SetString\n// !!!!IMPOTANT!!!!\n// Server change string pointers in mod memory only in trapcall(strings passed from mod, and placed in mod memory).\n// Never pass pointers outside of the mod memory to mod, this does not work in QVM in 64 bit server.\n//===========================================================================\nvoid PR2_SetEntityString(edict_t* ed, string_t* target, char* s)\n{\n\tif (!sv_vm) {\n\t\tPR1_SetString(target, s);\n\t\treturn;\n\t}\n}\nvoid PR2_SetGlobalString(string_t* target, char* s)\n{\n\tif (!sv_vm) {\n\t\tPR1_SetString(target, s);\n\t\treturn;\n\t}\n}\n\n/*\n=================\nPR2_LoadEnts\n=================\n*/\nextern const char *pr2_ent_data_ptr;\n\nvoid PR2_LoadEnts(char *data)\n{\n\tif (sv_vm)\n\t{\n\t\tpr2_ent_data_ptr = data;\n\n\t\t//Init parse\n\t\tVM_Call(sv_vm, 0, GAME_LOADENTS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\t}\n\telse\n\t{\n\t\tPR1_LoadEnts(data);\n\t}\n}\n\n//===========================================================================\n// GameStartFrame\n//===========================================================================\nvoid PR2_GameStartFrame(qbool isBotFrame)\n{\n\tif (isBotFrame && (!sv_vm || sv_vm->type == VMI_NONE || gamedata.APIversion < 15)) {\n\t\treturn;\n\t}\n\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 2, GAME_START_FRAME, (int) (sv.time * 1000), (int)isBotFrame, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\tPR1_GameStartFrame();\n}\n\n//===========================================================================\n// GameClientConnect\n//===========================================================================\nvoid PR2_GameClientConnect(int spec)\n{\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 1, GAME_CLIENT_CONNECT, spec, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\tPR1_GameClientConnect(spec);\n}\n\n//===========================================================================\n// GamePutClientInServer\n//===========================================================================\nvoid PR2_GamePutClientInServer(int spec)\n{\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 1, GAME_PUT_CLIENT_IN_SERVER, spec, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\tPR1_GamePutClientInServer(spec);\n}\n\n//===========================================================================\n// GameClientDisconnect\n//===========================================================================\nvoid PR2_GameClientDisconnect(int spec)\n{\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 1, GAME_CLIENT_DISCONNECT, spec, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\tPR1_GameClientDisconnect(spec);\n}\n\n//===========================================================================\n// GameClientPreThink\n//===========================================================================\nvoid PR2_GameClientPreThink(int spec)\n{\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 1, GAME_CLIENT_PRETHINK, spec, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\tPR1_GameClientPreThink(spec);\n}\n\n//===========================================================================\n// GameClientPostThink\n//===========================================================================\nvoid PR2_GameClientPostThink(int spec)\n{\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 1, GAME_CLIENT_POSTTHINK, spec, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\tPR1_GameClientPostThink(spec);\n}\n\n//===========================================================================\n// ClientCmd return false on unknown command\n//===========================================================================\nqbool PR2_ClientCmd(void)\n{\n\tif (sv_vm)\n\t\treturn VM_Call(sv_vm, 0, GAME_CLIENT_COMMAND, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\treturn PR1_ClientCmd();\n}\n\n//===========================================================================\n// ClientKill\n//===========================================================================\nvoid PR2_ClientKill(void)\n{\n\tif (sv_vm)\n\t\tPR2_ClientCmd(); // PR2 have some universal way for command execution unlike QC based mods.\n\telse\n\t\tPR1_ClientKill();\n}\n\n//===========================================================================\n// ClientSay return false if say unhandled by mod\n//===========================================================================\nqbool PR2_ClientSay(int isTeamSay, char *message)\n{\n\t//\n\t// message - used for QC based mods only.\n\t// PR2 mods get it from Cmd_Args() and such.\n\t//\n\n\tif (sv_vm)\n\t\treturn VM_Call(sv_vm, 1, GAME_CLIENT_SAY, isTeamSay, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\treturn PR1_ClientSay(isTeamSay, message);\n}\n\n//===========================================================================\n// GameSetNewParms\n//===========================================================================\nvoid PR2_GameSetNewParms(void)\n{\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 0, GAME_SETNEWPARMS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\tPR1_GameSetNewParms();\n}\n\n//===========================================================================\n// GameSetNewParms\n//===========================================================================\nvoid PR2_GameSetChangeParms(void)\n{\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 0, GAME_SETCHANGEPARMS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t{\n\t\tPR1_GameSetChangeParms();\n\t}\n}\n\n//===========================================================================\n// EdictTouch\n//===========================================================================\nvoid PR2_EdictTouch(func_t f)\n{\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 0, GAME_EDICT_TOUCH, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\tPR1_EdictTouch(f);\n}\n\n//===========================================================================\n// EdictThink\n//===========================================================================\nvoid PR2_EdictThink(func_t f)\n{\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 0, GAME_EDICT_THINK, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\tPR1_EdictThink(f);\n}\n\n//===========================================================================\n// EdictBlocked\n//===========================================================================\nvoid PR2_EdictBlocked(func_t f)\n{\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 0, GAME_EDICT_BLOCKED, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\tPR1_EdictBlocked(f);\n}\n\n//===========================================================================\n// UserInfoChanged\n//===========================================================================\nqbool PR2_UserInfoChanged(int after)\n{\n\tif (sv_vm)\n\t\treturn VM_Call(sv_vm, 1, GAME_CLIENT_USERINFO_CHANGED, after, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\treturn PR1_UserInfoChanged(after);\n}\n\n//===========================================================================\n// GameShutDown\n//===========================================================================\nvoid PR2_GameShutDown(void)\n{\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 0, GAME_SHUTDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\tPR1_GameShutDown();\n}\n\n//===========================================================================\n// UnLoadProgs\n//===========================================================================\nvoid PR2_UnLoadProgs(void)\n{\n\tif (sv_vm)\n\t{\n\t\tVM_Free( sv_vm );\n\t\tsv_vm = NULL;\n\t}\n\telse\n\t{\n\t\tPR1_UnLoadProgs();\n\t}\n}\n\n//===========================================================================\n// LoadProgs\n//===========================================================================\nvoid PR2_LoadProgs(void)\n{\n\tsv_vm = VM_Create(VM_GAME, sv_progsname.string, PR2_GameSystemCalls, sv_progtype.value );\n\n\tif ( sv_vm )\n\t{\n\t\t; // nothing.\n\t}\n\telse\n\t{\n\t\tPR1_LoadProgs ();\n\t}\n}\n\n//===========================================================================\n// GameConsoleCommand\n//===========================================================================\nvoid PR2_GameConsoleCommand(void)\n{\n\tint     old_other, old_self;\n\tclient_t\t*cl;\n\tint\t\t\ti;\n\n\tif( sv_vm )\n\t{\n\t\told_self = pr_global_struct->self;\n\t\told_other = pr_global_struct->other;\n\t\tpr_global_struct->other = 0; //sv_cmd = SV_CMD_CONSOLE;\n\t\tpr_global_struct->self = 0;\n\n\t\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t\t{\n\t\t\tif (!cl->state)\n\t\t\t\tcontinue;\n\t\t\tif ( cl->isBot )\n\t\t\t\tcontinue;\n\n\t\t\tif (NET_CompareAdr(cl->netchan.remote_address, net_from))\n\t\t\t{\n\t\t\t\tpr_global_struct->self = EDICT_TO_PROG(cl->edict);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tVM_Call(sv_vm, 0, GAME_CONSOLE_COMMAND, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\t\tpr_global_struct->self = old_self;\n\t\tpr_global_struct->other = old_other;\n\t}\n}\n\n//===========================================================================\n// PausedTic\n//===========================================================================\nvoid PR2_PausedTic(float duration)\n{\n\tif (sv_vm)\n\t\tVM_Call(sv_vm, 1, GAME_PAUSED_TIC, (int)(duration*1000), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\telse\n\t\tPR1_PausedTic(duration);\n}\n\nvoid PR2_ClearEdict(edict_t* e)\n{\n\tif (sv_vm && sv_vm->pr2_references && (sv_vm->type == VMI_NATIVE || sv_vm->type == VMI_BYTECODE || sv_vm->type == VMI_COMPILED)) {\n\t\tint old_self = pr_global_struct->self;\n\t\tpr_global_struct->self = EDICT_TO_PROG(e);\n\t\tVM_Call(sv_vm, 0, GAME_CLEAR_EDICT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\t\tpr_global_struct->self = old_self;\n\t}\n}\n\n//===========================================================================\n// InitProgs\n//===========================================================================\n\n#define GAME_API_VERSION_MIN 16\n\nvoid LoadGameData(intptr_t gamedata_ptr)\n{\n#ifdef idx64\n\tgameData_vm_t* gamedata_vm;\n\n\tif (sv_vm->type == VMI_BYTECODE || sv_vm->type == VMI_COMPILED)\n\t{\n\t\tgamedata_vm = (gameData_vm_t *)PR2_GetString(gamedata_ptr);\n\t\tgamedata.ents = (intptr_t)gamedata_vm->ents_p;\n\t\tgamedata.global = (intptr_t)gamedata_vm->global_p;\n\t\tgamedata.fields = (intptr_t)gamedata_vm->fields_p;\n\t\tgamedata.APIversion = gamedata_vm->APIversion;\n\t\tgamedata.sizeofent = gamedata_vm->sizeofent;\n\t\tgamedata.maxentities = gamedata_vm->maxentities;\n\t\treturn;\n\t}\n#endif\n\tgamedata = *(gameData_t *)PR2_GetString(gamedata_ptr);\n}\n\nvoid LoadFields(void)\n{\n#ifdef idx64\n\tif (sv_vm->type == VMI_BYTECODE || sv_vm->type == VMI_COMPILED)\n\t{\n\t\tfield_vm_t *fieldvm_p;\n\t\tfield_t\t*f;\n\t\tint num = 0;\n\t\tfieldvm_p = (field_vm_t*)PR2_GetString((intptr_t)gamedata.fields);\n\t\twhile (fieldvm_p[num].name) {\n\t\t\tnum++;\n\t\t}\n\t\tf = fields = (field_t *)Hunk_AllocName(sizeof(field_t) * (num + 1), \"edfields\");\n\t\twhile (fieldvm_p->name){\n\t\t\tf->name = (stringptr_t)fieldvm_p->name;\n\t\t\tf->ofs =  fieldvm_p->ofs;\n\t\t\tf->type = (fieldtype_t)fieldvm_p->type;\n\t\t\tf++;\n\t\t\tfieldvm_p++;\n\t\t}\n\t\tf->name = 0;\n\t\treturn;\n\t}\n#endif\n\tfields = (field_t*)PR2_GetString((intptr_t)gamedata.fields);\n}\n\nextern void PR2_FS_Restart(void);\n\nvoid PR2_InitProg(void)\n{\n\textern cvar_t sv_pr2references;\n\n\tintptr_t gamedata_ptr;\n\n\tCvar_SetValue(&sv_pr2references, 0.0f);\n\n\tif (!sv_vm) {\n\t\tPR1_InitProg();\n\t\treturn;\n\t}\n\n\tPR2_FS_Restart();\n\n\tgamedata.APIversion = 0;\n\tgamedata_ptr = (intptr_t) VM_Call(sv_vm, 2, GAME_INIT, (int)(sv.time * 1000), (int)(Sys_DoubleTime() * 100000), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);\n\tif (!gamedata_ptr) {\n\t\tSV_Error(\"PR2_InitProg: gamedata == NULL\");\n\t}\n\n\tLoadGameData(gamedata_ptr);\n\tif (gamedata.APIversion < GAME_API_VERSION_MIN || gamedata.APIversion > GAME_API_VERSION) {\n\t\tif (GAME_API_VERSION_MIN == GAME_API_VERSION) {\n\t\t\tSV_Error(\"PR2_InitProg: Incorrect API version (%i should be %i)\", gamedata.APIversion, GAME_API_VERSION);\n\t\t}\n\t\telse {\n\t\t\tSV_Error(\"PR2_InitProg: Incorrect API version (%i should be between %i and %i)\", gamedata.APIversion, GAME_API_VERSION_MIN, GAME_API_VERSION);\n\t\t}\n\t}\n\n\tsv_vm->pr2_references = gamedata.APIversion >= 15 && (int)sv_pr2references.value;\n#ifdef idx64\n\tif (sv_vm->type == VMI_NATIVE && (!sv_vm->pr2_references || gamedata.APIversion < 15))\n\t\tSV_Error(\"PR2_InitProg: Native prog must support sv_pr2references for 64bit mode (mod API version (%i should be 15+))\", gamedata.APIversion);\n#endif\n\tpr_edict_size = gamedata.sizeofent;\n\tCon_DPrintf(\"edict size %d\\n\", pr_edict_size);\n\tsv.game_edicts = (entvars_t *)(PR2_GetString((intptr_t)gamedata.ents));\n\tpr_global_struct = (globalvars_t*)PR2_GetString((intptr_t)gamedata.global);\n\tpr_globals = (float *)pr_global_struct;\n\tLoadFields();\n\n\tsv.max_edicts = MAX_EDICTS;\n\tif (gamedata.APIversion >= 14) {\n\t\tsv.max_edicts = min(sv.max_edicts, gamedata.maxentities);\n\t}\n\telse {\n\t\tsv.max_edicts = min(sv.max_edicts, 512);\n\t}\n}\n\n#endif /* USE_PR2 */\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/pr_cmds.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\nstatic tokenizecontext_t pr1_tokencontext;\n\n#define\tRETURN_EDICT(e) (((int *)pr_globals)[OFS_RETURN] = EDICT_TO_PROG(e))\n#define\tRETURN_STRING(s) (PR1_SetString(&((int *)pr_globals)[OFS_RETURN], s))\n\n/*\n===============================================================================\n \n\t\t\t\t\t\tBUILT-IN FUNCTIONS\n \n===============================================================================\n*/\n\nchar *PF_VarString (int\tfirst)\n{\n\tint\t\ti;\n\tstatic char out[2048];\n\n\tout[0] = 0;\n\tfor (i=first ; i<pr_argc ; i++)\n\t{\n\t\tstrlcat (out, G_STRING((OFS_PARM0+i*3)), sizeof(out));\n\t}\n\treturn out;\n}\n\n\n/*\n=================\nPF_errror\n \nThis is a TERMINAL error, which will kill off the entire server.\nDumps self.\n \nerror(value)\n=================\n*/\nvoid PF_error (void)\n{\n\tchar\t*s;\n\tedict_t\t*ed;\n\n\ts = PF_VarString(0);\n\tCon_Printf (\"======SERVER ERROR in %s:\\n%s\\n\", PR1_GetString(pr_xfunction->s_name) ,s);\n\ted = PROG_TO_EDICT(pr_global_struct->self);\n\tED_Print (ed);\n\n\tSV_Error (\"Program error (PF_error)\");\n}\n\n/*\n=================\nPF_objerror\n \nDumps out self, then an error message.  The program is aborted and self is\nremoved, but the level can continue.\n \nobjerror(value)\n=================\n*/\nvoid PF_objerror (void)\n{\n\tchar\t*s;\n\tedict_t\t*ed;\n\n\ts = PF_VarString(0);\n\tCon_Printf (\"======OBJECT ERROR in %s:\\n%s\\n\", PR1_GetString(pr_xfunction->s_name),s);\n\ted = PROG_TO_EDICT(pr_global_struct->self);\n\tED_Print (ed);\n\tED_Free (ed);\n\n\tSV_Error (\"Program error (PF_objerror)\");\n}\n\n\n\n/*\n==============\nPF_makevectors\n \nWrites new values for v_forward, v_up, and v_right based on angles\nmakevectors(vector)\n==============\n*/\nvoid PF_makevectors (void)\n{\n\tAngleVectors (G_VECTOR(OFS_PARM0), PR_GLOBAL(v_forward), PR_GLOBAL(v_right), PR_GLOBAL(v_up));\n}\n\n/*\n=================\nPF_setorigin\n \nThis is the only valid way to move an object without using the physics of the world (setting velocity and waiting).  Directly changing origin will not set internal links correctly, so clipping would be messed up.  This should be called when an object is spawned, and then only if it is teleported.\n \nsetorigin (entity, origin)\n=================\n*/\nvoid PF_setorigin (void)\n{\n\tedict_t\t*e;\n\tfloat\t*org;\n\n\te = G_EDICT(OFS_PARM0);\n\torg = G_VECTOR(OFS_PARM1);\n\tVectorCopy (org, e->v->origin);\n\tSV_AntilagReset (e);\n\tSV_LinkEdict (e, false);\n}\n\n\n/*\n=================\nPF_setsize\n \nthe size box is rotated by the current angle\n \nsetsize (entity, minvector, maxvector)\n=================\n*/\nvoid PF_setsize (void)\n{\n\tedict_t\t*e;\n\tfloat\t*min, *max;\n\n\te = G_EDICT(OFS_PARM0);\n\tmin = G_VECTOR(OFS_PARM1);\n\tmax = G_VECTOR(OFS_PARM2);\n\tVectorCopy (min, e->v->mins);\n\tVectorCopy (max, e->v->maxs);\n\tVectorSubtract (max, min, e->v->size);\n\tSV_LinkEdict (e, false);\n}\n\n\n/*\n=================\nPF_setmodel\n \nsetmodel(entity, model)\nAlso sets size, mins, and maxs for inline bmodels\n=================\n*/\nstatic void PF_setmodel (void)\n{\n\tint\t\t\ti;\n\tedict_t\t\t*e;\n\tchar\t\t*m, **check;\n\tcmodel_t\t*mod;\n\n\te = G_EDICT(OFS_PARM0);\n\tm = G_STRING(OFS_PARM1);\n\n// check to see if model was properly precached\n\tfor (i = 0, check = sv.model_precache; i < MAX_MODELS && *check ; i++, check++)\n\t\tif (!strcmp(*check, m))\n\t\t\tgoto ok;\n\tPR_RunError (\"PF_setmodel: no precache: %s\\n\", m);\nok:\n\n\te->v->model = G_INT(OFS_PARM1);\n\te->v->modelindex = i;\n\n// if it is an inline model, get the size information for it\n\tif (m[0] == '*')\n\t{\n\t\tmod = CM_InlineModel (m);\n\t\tVectorCopy (mod->mins, e->v->mins);\n\t\tVectorCopy (mod->maxs, e->v->maxs);\n\t\tVectorSubtract (mod->maxs, mod->mins, e->v->size);\n\t\tSV_LinkEdict (e, false);\n\t}\n\telse if (pr_nqprogs)\n\t{\n\t\t// hacks to make NQ progs happy\n\t\tif (!strcmp(PR1_GetString(e->v->model), \"maps/b_explob.bsp\"))\n\t\t{\n\t\t\tVectorClear (e->v->mins);\n\t\t\tVectorSet (e->v->maxs, 32, 32, 64);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// FTE does this, so we do, too; I'm not sure if it makes a difference\n\t\t\tVectorSet (e->v->mins, -16, -16, -16);\n\t\t\tVectorSet (e->v->maxs, 16, 16, 16);\n\t\t}\n\t\tVectorSubtract (e->v->maxs, e->v->mins, e->v->size);\n\t\tSV_LinkEdict (e, false);\n\t}\n}\n\n/*\n=================\nPF_bprint\n \nbroadcast print to everyone on server\n \nbprint(value)\n=================\n*/\nvoid PF_bprint (void)\n{\n\tchar\t\t*s;\n\tint\t\t\tlevel;\n\n\tif (pr_nqprogs)\n\t{\n\t\tlevel = PRINT_HIGH;\n\t\ts = PF_VarString(0);\n\t}\n\telse\n\t{\n\t\tlevel = G_FLOAT(OFS_PARM0);\n\t\ts = PF_VarString(1);\n\t}\n\n\tSV_BroadcastPrintf (level, \"%s\", s);\n}\n\n#define SPECPRINT_CENTERPRINT\t0x1\n#define SPECPRINT_SPRINT\t0x2\n#define SPECPRINT_STUFFCMD\t0x4\n/*\n=================\nPF_sprint\n \nsingle print to a specific client\n \nsprint(clientent, value)\n=================\n*/\nvoid PF_sprint (void)\n{\n\tchar\t\t*s;\n\tclient_t\t*client, *cl;\n\tint\t\t\tentnum;\n\tint\t\t\tlevel;\n\tint\t\t\ti;\n\n\tentnum = G_EDICTNUM(OFS_PARM0);\n\tif (pr_nqprogs)\n\t{\n\t\tlevel = PRINT_HIGH;\n\t\ts = PF_VarString(1);\n\t}\n\telse\n\t{\n\t\tlevel = G_FLOAT(OFS_PARM1);\n\t\ts = PF_VarString(2);\n\t}\n\n\tif (entnum < 1 || entnum > MAX_CLIENTS)\n\t{\n\t\tCon_Printf (\"tried to sprint to a non-client\\n\");\n\t\treturn;\n\t}\n\n\tclient = &svs.clients[entnum-1];\n\n\tSV_ClientPrintf (client, level, \"%s\", s);\n\n\t//bliP: spectator print ->\n\tif ((int)sv_specprint.value & SPECPRINT_SPRINT)\n\t{\n\t\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t\t{\n\t\t\tif (!cl->state || !cl->spectator)\n\t\t\t\tcontinue;\n\n\t\t\tif ((cl->spec_track == entnum) && (cl->spec_print & SPECPRINT_SPRINT))\n\t\t\t\tSV_ClientPrintf (cl, level, \"%s\", s);\n\t\t}\n\t}\n\t//<-\n}\n\n\n\n/*\n=================\nPF_centerprint\n \nsingle print to a specific client\n \ncenterprint(clientent, value)\n=================\n*/\nvoid PF_centerprint (void)\n{\n\tchar\t\t*s;\n\tint\t\t\tentnum;\n\tclient_t\t*cl, *spec;\n\tint\t\t\ti;\n\n\tentnum = G_EDICTNUM(OFS_PARM0);\n\ts = PF_VarString(1);\n\n\tif (entnum < 1 || entnum > MAX_CLIENTS)\n\t{\n\t\tCon_Printf (\"tried to sprint to a non-client\\n\");\n\t\treturn;\n\t}\n\n\tcl = &svs.clients[entnum-1];\n\n\tClientReliableWrite_Begin (cl, svc_centerprint, 2 + strlen(s));\n\tClientReliableWrite_String (cl, s);\n\n\tif (sv.mvdrecording)\n\t{\n\t\tif (MVDWrite_Begin (dem_single, entnum - 1, 2 + strlen(s)))\n\t\t{\n\t\t\tMVD_MSG_WriteByte (svc_centerprint);\n\t\t\tMVD_MSG_WriteString (s);\n\t\t}\n\t}\n\n\t//bliP: spectator print ->\n\tif ((int)sv_specprint.value & SPECPRINT_CENTERPRINT)\n\t{\n\t\tfor (i = 0, spec = svs.clients; i < MAX_CLIENTS; i++, spec++)\n\t\t{\n\t\t\tif (!cl->state || !spec->spectator)\n\t\t\t\tcontinue;\n\n\t\t\tif ((spec->spec_track == entnum) && (cl->spec_print & SPECPRINT_CENTERPRINT))\n\t\t\t{\n\t\t\t\tClientReliableWrite_Begin (spec, svc_centerprint, 2 + strlen(s));\n\t\t\t\tClientReliableWrite_String (spec, s);\n\t\t\t}\n\t\t}\n\t}\n\t//<-\n}\n\n\n/*\n=================\nPF_normalize\n \nvector normalize(vector)\n=================\n*/\nvoid PF_normalize (void)\n{\n\tfloat\t*value1;\n\tvec3_t\tnewvalue;\n\tfloat\tnuw;\n\n\tvalue1 = G_VECTOR(OFS_PARM0);\n\n\tnuw = value1[0] * value1[0] + value1[1] * value1[1] + value1[2]*value1[2];\n\tnuw = sqrt(nuw);\n\n\tif (nuw == 0)\n\t\tnewvalue[0] = newvalue[1] = newvalue[2] = 0;\n\telse\n\t{\n\t\tnuw = 1/nuw;\n\t\tnewvalue[0] = value1[0] * nuw;\n\t\tnewvalue[1] = value1[1] * nuw;\n\t\tnewvalue[2] = value1[2] * nuw;\n\t}\n\n\tVectorCopy (newvalue, G_VECTOR(OFS_RETURN));\n}\n\n/*\n=================\nPF_vlen\n \nscalar vlen(vector)\n=================\n*/\nvoid PF_vlen (void)\n{\n\tfloat\t*value1;\n\tfloat\tnuw;\n\n\tvalue1 = G_VECTOR(OFS_PARM0);\n\n\tnuw = value1[0] * value1[0] + value1[1] * value1[1] + value1[2]*value1[2];\n\tnuw = sqrt(nuw);\n\n\tG_FLOAT(OFS_RETURN) = nuw;\n}\n\n/*\n=================\nPF_vectoyaw\n \nfloat vectoyaw(vector)\n=================\n*/\nvoid PF_vectoyaw (void)\n{\n\tfloat\t*value1;\n\tfloat\tyaw;\n\n\tvalue1 = G_VECTOR(OFS_PARM0);\n\n\tif (value1[1] == 0 && value1[0] == 0)\n\t\tyaw = 0;\n\telse\n\t{\n\t\tyaw = /*(int)*/ (atan2(value1[1], value1[0]) * 180 / M_PI);\n\t\tif (yaw < 0)\n\t\t\tyaw += 360;\n\t}\n\n\tG_FLOAT(OFS_RETURN) = yaw;\n}\n\n\n/*\n=================\nPF_vectoangles\n \nvector vectoangles(vector)\n=================\n*/\nvoid PF_vectoangles (void)\n{\n\tfloat\t*value1;\n\tfloat\tforward;\n\tfloat\tyaw, pitch;\n\n\tvalue1 = G_VECTOR(OFS_PARM0);\n\n\tif (value1[1] == 0 && value1[0] == 0)\n\t{\n\t\tyaw = 0;\n\t\tif (value1[2] > 0)\n\t\t\tpitch = 90;\n\t\telse\n\t\t\tpitch = 270;\n\t}\n\telse\n\t{\n\t\tyaw = /*(int)*/ (atan2(value1[1], value1[0]) * 180 / M_PI);\n\t\tif (yaw < 0)\n\t\t\tyaw += 360;\n\n\t\tforward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]);\n\t\tpitch = /*(int)*/ (atan2(value1[2], forward) * 180 / M_PI);\n\t\tif (pitch < 0)\n\t\t\tpitch += 360;\n\t}\n\n\tG_FLOAT(OFS_RETURN+0) = pitch;\n\tG_FLOAT(OFS_RETURN+1) = yaw;\n\tG_FLOAT(OFS_RETURN+2) = 0;\n}\n\n/*\n=================\nPF_Random\n \nReturns a number from 0<= num < 1\n \nrandom()\n=================\n*/\nvoid PF_random (void)\n{\n\tfloat\t\tnum;\n\n\tnum = (rand ()&0x7fff) / ((float)0x7fff);\n\n\tG_FLOAT(OFS_RETURN) = num;\n}\n\n\n/*\n=================\nPF_particle\n\nparticle(origin, dir, color, count [,replacement_te [,replacement_count]]) = #48\nFor NQ progs (or as a QW extension)\n=================\n*/\nstatic void PF_particle (void)\n{\n\tfloat\t*org, *dir;\n\tfloat\tcolor, count;\n\tint\t\treplacement_te;\n\tint\t\treplacement_count = 0 /* suppress compiler warning */;\n\t\t\t\n\torg = G_VECTOR(OFS_PARM0);\n\tdir = G_VECTOR(OFS_PARM1);\n\tcolor = G_FLOAT(OFS_PARM2);\n\tcount = G_FLOAT(OFS_PARM3);\n\n\t// Progs should provide a tempentity code and particle count for the case\n\t// when a client doesn't support svc_particle\n\tif (pr_argc >= 5)\n\t{\n\t\treplacement_te = G_FLOAT(OFS_PARM4);\n\t\treplacement_count = (pr_argc >= 6) ? G_FLOAT(OFS_PARM5) : 1;\n\t}\n\telse\n\t{\n\t\t// To aid porting of NQ mods, if the extra arguments are not provided, try\n\t\t// to figure out what progs want by inspecting color and count\n\t\tif (count == 255)\n\t\t{\n\t\t\treplacement_te = TE_EXPLOSION;\t\t// count is not used\n\t\t}\n\t\telse if (color == 73)\n\t\t{\n\t\t\treplacement_te = TE_BLOOD;\n\t\t\treplacement_count = 1;\t// FIXME: use count / <some value>?\n\t\t}\n\t\telse if (color == 225)\n\t\t{\n\t\t\treplacement_te = TE_LIGHTNINGBLOOD;\t// count is not used\n\t\t}\n\t\telse\n\t\t{\n\t\t\treplacement_te = 0;\t\t// don't send anything\n\t\t}\n\t}\n\n\tSV_StartParticle (org, dir, color, count, replacement_te, replacement_count);\n}\n\n/*\n=================\nPF_ambientsound\n \n=================\n*/\nvoid PF_ambientsound (void)\n{\n\tchar\t\t**check;\n\tchar\t\t*samp;\n\tfloat\t\t*pos;\n\tfloat \t\tvol, attenuation;\n\tint\t\t\ti, soundnum;\n\n\tpos = G_VECTOR (OFS_PARM0);\n\tsamp = G_STRING(OFS_PARM1);\n\tvol = G_FLOAT(OFS_PARM2);\n\tattenuation = G_FLOAT(OFS_PARM3);\n\n\t// check to see if samp was properly precached\n\tfor (soundnum=0, check = sv.sound_precache ; *check ; check++, soundnum++)\n\t\tif (!strcmp(*check,samp))\n\t\t\tbreak;\n\n\tif (!*check)\n\t{\n\t\tCon_Printf (\"no precache: %s\\n\", samp);\n\t\treturn;\n\t}\n\n\t// add an svc_spawnambient command to the level signon packet\n\n\tMSG_WriteByte (&sv.signon,svc_spawnstaticsound);\n\tfor (i=0 ; i<3 ; i++)\n\t\tMSG_WriteCoord(&sv.signon, pos[i]);\n\n\tMSG_WriteByte (&sv.signon, soundnum);\n\n\tMSG_WriteByte (&sv.signon, vol*255);\n\tMSG_WriteByte (&sv.signon, attenuation*64);\n\n}\n\n/*\n=================\nPF_sound\n \nEach entity can have eight independant sound sources, like voice,\nweapon, feet, etc.\n \nChannel 0 is an auto-allocate channel, the others override anything\nalready running on that entity/channel pair.\n \nAn attenuation of 0 will play full volume everywhere in the level.\nLarger attenuations will drop off.\n \n=================\n*/\nvoid PF_sound (void)\n{\n\tchar\t\t*sample;\n\tint\t\t\tchannel;\n\tedict_t\t\t*entity;\n\tint \t\tvolume;\n\tfloat attenuation;\n\n\tentity = G_EDICT(OFS_PARM0);\n\tchannel = G_FLOAT(OFS_PARM1);\n\tsample = G_STRING(OFS_PARM2);\n\tvolume = G_FLOAT(OFS_PARM3) * 255;\n\tattenuation = G_FLOAT(OFS_PARM4);\n\n\tSV_StartSound (entity, channel, sample, volume, attenuation);\n}\n\n/*\n=================\nPF_break\n \nbreak()\n=================\n*/\nvoid PF_break (void)\n{\n\tCon_Printf (\"break statement\\n\");\n\t*(int *)-4 = 0;\t// dump to debugger\n\t//\tPR_RunError (\"break statement\");\n}\n\n/*\n=================\nPF_traceline\n \nUsed for use tracing and shot targeting\nTraces are blocked by bbox and exact bsp entityes, and also slide box entities\nif the tryents flag is set.\n \ntraceline (vector1, vector2, tryents)\n=================\n*/\nvoid PF_traceline (void)\n{\n\tfloat\t*v1, *v2;\n\ttrace_t\ttrace;\n\tint\t\tnomonsters;\n\tedict_t\t*ent;\n\n\tv1 = G_VECTOR(OFS_PARM0);\n\tv2 = G_VECTOR(OFS_PARM1);\n\tnomonsters = G_FLOAT(OFS_PARM2);\n\tent = G_EDICT(OFS_PARM3);\n\n\tif (sv_antilag.value == 2)\n\t\tnomonsters |= MOVE_LAGGED;\n\n\ttrace = SV_Trace (v1, vec3_origin, vec3_origin, v2, nomonsters, ent);\n\n\tPR_GLOBAL(trace_allsolid) = trace.allsolid;\n\tPR_GLOBAL(trace_startsolid) = trace.startsolid;\n\tPR_GLOBAL(trace_fraction) = trace.fraction;\n\tPR_GLOBAL(trace_inwater) = trace.inwater;\n\tPR_GLOBAL(trace_inopen) = trace.inopen;\n\tVectorCopy (trace.endpos, PR_GLOBAL(trace_endpos));\n\tVectorCopy (trace.plane.normal, PR_GLOBAL(trace_plane_normal));\n\tPR_GLOBAL(trace_plane_dist) =  trace.plane.dist;\t\n\tif (trace.e.ent)\n\t\tPR_GLOBAL(trace_ent) = EDICT_TO_PROG(trace.e.ent);\n\telse\n\t\tPR_GLOBAL(trace_ent) = EDICT_TO_PROG(sv.edicts);\n}\n\n/*\n=================\nPF_checkpos\n \nReturns true if the given entity can move to the given position from it's\ncurrent position by walking or rolling.\nFIXME: make work...\nscalar checkpos (entity, vector)\n=================\n*/\nvoid PF_checkpos (void)\n{}\n\n//============================================================================\n\n// Unlike Quake's Mod_LeafPVS, CM_LeafPVS returns a pointer to static data\n// uncompressed at load time, so it's safe to store for future use\nstatic byte\t*checkpvs;\n\nint PF_newcheckclient (int check)\n{\n\tint\t\ti;\n\tedict_t\t*ent;\n\tvec3_t\torg;\n\n// cycle to the next one\n\n\tif (check < 1)\n\t\tcheck = 1;\n\tif (check > MAX_CLIENTS)\n\t\tcheck = MAX_CLIENTS;\n\n\tif (check == MAX_CLIENTS)\n\t\ti = 1;\n\telse\n\t\ti = check + 1;\n\n\tfor ( ;  ; i++)\n\t{\n\t\tif (i == MAX_CLIENTS+1)\n\t\t\ti = 1;\n\n\t\tent = EDICT_NUM(i);\n\n\t\tif (i == check)\n\t\t\tbreak;\t// didn't find anything else\n\n\t\tif (ent->e.free)\n\t\t\tcontinue;\n\t\tif (ent->v->health <= 0)\n\t\t\tcontinue;\n\t\tif ((int)ent->v->flags & FL_NOTARGET)\n\t\t\tcontinue;\n\n\t// anything that is a client, or has a client as an enemy\n\t\tbreak;\n\t}\n\n// get the PVS for the entity\n\tVectorAdd (ent->v->origin, ent->v->view_ofs, org);\n\tcheckpvs = CM_LeafPVS (CM_PointInLeaf(org));\n\n\treturn i;\n}\n\n\n/*\n=================\nPF_checkclient\n\nReturns a client (or object that has a client enemy) that would be a\nvalid target.\n\nIf there are more than one valid options, they are cycled each frame\n\nIf (self.origin + self.viewofs) is not in the PVS of the current target,\nit is not returned at all.\n\nentity checkclient() = #17\n=================\n*/\n#define\tMAX_CHECK\t16\nstatic void PF_checkclient (void)\n{\n\tedict_t\t*ent, *self;\n\tint\t\tl;\n\tvec3_t\tvieworg;\n\t\n// find a new check if on a new frame\n\tif (sv.time - sv.lastchecktime >= 0.1)\n\t{\n\t\tsv.lastcheck = PF_newcheckclient (sv.lastcheck);\n\t\tsv.lastchecktime = sv.time;\n\t}\n\n// return check if it might be visible\t\n\tent = EDICT_NUM(sv.lastcheck);\n\tif (ent->e.free || ent->v->health <= 0)\n\t{\n\t\tRETURN_EDICT(sv.edicts);\n\t\treturn;\n\t}\n\n// if current entity can't possibly see the check entity, return 0\n\tself = PROG_TO_EDICT(pr_global_struct->self);\n\tVectorAdd (self->v->origin, self->v->view_ofs, vieworg);\n\tl = CM_Leafnum(CM_PointInLeaf(vieworg)) - 1;\n\tif ( (l<0) || !(checkpvs[l>>3] & (1<<(l&7)) ) )\n\t{\n\t\tRETURN_EDICT(sv.edicts);\n\t\treturn;\n\t}\n\n// might be able to see it\n\tRETURN_EDICT(ent);\n}\n\n//============================================================================\n\n\n/*\n=================\nPF_stuffcmd\n \nSends text over to the client's execution buffer\n \nstuffcmd (clientent, value)\n=================\n*/\nvoid PF_stuffcmd (void)\n{\n\tint\t\tentnum;\n\tchar\t*str;\n\tclient_t\t*cl, *spec;\n\tchar\t*buf;\n\tint j;\n\n\tentnum = G_EDICTNUM(OFS_PARM0);\n\tif (entnum < 1 || entnum > MAX_CLIENTS)\n\t\tPR_RunError (\"Parm 0 not a client\");\n\tstr = G_STRING(OFS_PARM1);\n\n\tcl = &svs.clients[entnum-1];\n\n\tif (!strncmp(str, \"disconnect\\n\", MAX_STUFFTEXT))\n\t{\n\t\t// so long and thanks for all the fish\n\t\tcl->drop = true;\n\t\treturn;\n\t}\n\n\tbuf = cl->stufftext_buf;\n\tif (strlen(buf) + strlen(str) >= MAX_STUFFTEXT)\n\t\tPR_RunError (\"stufftext buffer overflow\");\n\tstrlcat (buf, str, MAX_STUFFTEXT);\n\n\tif( strchr( buf, '\\n' ) )\n\t{\n\t\tClientReliableWrite_Begin (cl, svc_stufftext, 2+strlen(buf));\n\t\tClientReliableWrite_String (cl, buf);\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin ( dem_single, cl - svs.clients, 2+strlen(buf)))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteByte (svc_stufftext);\n\t\t\t\tMVD_MSG_WriteString (buf);\n\t\t\t}\n\t\t}\n\n\t\t//bliP: spectator print ->\n\t\tif ((int)sv_specprint.value & SPECPRINT_STUFFCMD)\n\t\t{\n\t\t\tfor (j = 0, spec = svs.clients; j < MAX_CLIENTS; j++, spec++)\n\t\t\t{\n\t\t\t\tif (!cl->state || !spec->spectator)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif ((spec->spec_track == entnum) && (cl->spec_print & SPECPRINT_STUFFCMD))\n\t\t\t\t{\n\t\t\t\t\tClientReliableWrite_Begin (spec, svc_stufftext, 2+strlen(buf));\n\t\t\t\t\tClientReliableWrite_String (spec, buf);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t//<-\n\t\n\t\tbuf[0] = 0;\n\t}\n}\n\n/*\n=================\nPF_localcmd\n \nSends text over to the client's execution buffer\n \nlocalcmd (string)\n=================\n*/\nvoid PF_localcmd (void)\n{\n\tchar\t*str;\n\n\tstr = G_STRING(OFS_PARM0);\n\n\tif (pr_nqprogs && !strcmp(str, \"restart\\n\"))\n\t{\n\t\tCbuf_AddText (va(\"map %s\\n\", sv.mapname));\n\t\treturn;\n\t}\n\n\tCbuf_AddText (str);\n}\n\n#define MAX_PR_STRING_SIZE\t2048\n#define MAX_PR_STRINGS\t\t64\n\nint\t\tpr_string_index = 0;\nchar\tpr_string_buf[MAX_PR_STRINGS][MAX_PR_STRING_SIZE];\nchar\t*pr_string_temp = pr_string_buf[0];\n\nvoid PF_SetTempString(void)\n{\n\tpr_string_temp = pr_string_buf[(++pr_string_index) & (MAX_PR_STRINGS - 1)];\n}\n\n\n/*\n=================\nPF_tokenize\n \nfloat tokenize(string)\n=================\n*/\n\nvoid PF_tokenize (void)\n{\n\tconst char *str;\n\n\tstr = G_STRING(OFS_PARM0);\n\tCmd_TokenizeStringEx(&pr1_tokencontext, str);\n\tG_FLOAT(OFS_RETURN) = Cmd_ArgcEx(&pr1_tokencontext);\n}\n\n/*\n=================\nPF_argc\n \nreturns number of tokens (must be executed after PF_Tokanize!)\n \nfloat argc(void)\n=================\n*/\n\nvoid PF_argc (void)\n{\n\tG_FLOAT(OFS_RETURN) = (float) Cmd_ArgcEx(&pr1_tokencontext);\n}\n\n/*\n=================\nPF_argv\n \nreturns token requested by user (must be executed after PF_Tokanize!)\n \nstring argc(float)\n=================\n*/\n\nvoid PF_argv (void)\n{\n\tint num;\n\n\tnum = (int) G_FLOAT(OFS_PARM0);\n\n//\tif (num < 0 ) num = 0;\n//\tif (num > Cmd_Argc()-1) num = Cmd_Argc()-1;\n\n\tif (num < 0 || num >= Cmd_ArgcEx(&pr1_tokencontext)) {\n\t\tRETURN_STRING(\"\");\n\t}\n\telse {\n\t\tsnprintf(pr_string_temp, MAX_PR_STRING_SIZE, \"%s\", Cmd_ArgvEx(&pr1_tokencontext, num));\n\t\tRETURN_STRING(pr_string_temp);\n\t\tPF_SetTempString();\n\t}\n}\n\n/*\n=================\nPF_substr\n \nstring substr(string str, float start, float len)\n=================\n*/\n\nvoid PF_substr (void)\n{\n\tchar *s;\n\tint start, len, l;\n\n\ts = G_STRING(OFS_PARM0);\n\tstart = (int) G_FLOAT(OFS_PARM1);\n\tlen = (int) G_FLOAT(OFS_PARM2);\n\tl = strlen(s);\n\n\tif (start >= l || !len || !*s)\n\t{\n\t\tPR_SetTmpString(&G_INT(OFS_RETURN), \"\");\n\t\treturn;\n\t}\n\n\ts += start;\n\tl -= start;\n\n\tif (len > l + 1)\n\t\tlen = l + 1;\n\n\tstrlcpy(pr_string_temp, s, len + 1);\n\n\tPR1_SetString(&G_INT(OFS_RETURN), pr_string_temp);\n\n\tPF_SetTempString();\n}\n\n/*\n=================\nPF_strcat\n \nstring strcat(string str1, string str2)\n=================\n*/\n\nvoid PF_strcat (void)\n{\n\tstrlcpy(pr_string_temp, PF_VarString(0), MAX_PR_STRING_SIZE);\n\tPR1_SetString(&G_INT(OFS_RETURN), pr_string_temp);\n\n\tPF_SetTempString();\n}\n\n/*\n=================\nPF_strlen\n \nfloat strlen(string str)\n=================\n*/\n\nvoid PF_strlen (void)\n{\n\tG_FLOAT(OFS_RETURN) = (float) strlen(G_STRING(OFS_PARM0));\n}\n\n/*\n=================\nPF_strzone\n\nZQ_QC_STRINGS\nstring newstr (string str [, float size])\n\nThe 'size' parameter is not QSG but an MVDSV extension\n=================\n*/\n\nvoid PF_strzone (void)\n{\n\tchar *s;\n\tint i, size;\n\n\ts = G_STRING(OFS_PARM0);\n\n\tfor (i = 0; i < MAX_PRSTR; i++)\n\t{\n\t\tif (!pr_newstrtbl[i] || pr_newstrtbl[i] == pr_strings)\n\t\t\tbreak;\n\t}\n\n\tif (i == MAX_PRSTR)\n\t\tPR_RunError(\"strzone: out of string memory\");\n\n\tsize = strlen(s) + 1;\n\tif (pr_argc == 2 && (int) G_FLOAT(OFS_PARM1) > size)\n\t\tsize = (int) G_FLOAT(OFS_PARM1);\n\n\tpr_newstrtbl[i] = (char *) Q_malloc(size);\n\tstrlcpy(pr_newstrtbl[i], s, size);\n\n\tG_INT(OFS_RETURN) = -(i+MAX_PRSTR);\n}\n\n/*\n=================\nPF_strunzone\n\nZQ_QC_STRINGS\nvoid strunzone (string str)\n=================\n*/\n\nvoid PF_strunzone (void)\n{\n\tint num;\n\n\tnum = G_INT(OFS_PARM0);\n\tif (num > - MAX_PRSTR)\n\t\tPR_RunError(\"strunzone: not a dynamic string\");\n\n\tif (num <= -(MAX_PRSTR * 2))\n\t\tPR_RunError (\"strunzone: bad string\");\n\n\tnum = - (num + MAX_PRSTR);\n\n\tif (pr_newstrtbl[num] == pr_strings)\n\t\treturn;\t// allow multiple strunzone on the same string (like free in C)\n\n\tQ_free(pr_newstrtbl[num]);\n\tpr_newstrtbl[num] = pr_strings;\n}\n\nvoid PF_clear_strtbl(void)\n{\n\tint i;\n\n\tfor (i = 0; i < MAX_PRSTR; i++)\n\t{\n\t\tif (pr_newstrtbl[i] && pr_newstrtbl[i] != pr_strings)\n\t\t{\n\t\t\tQ_free(pr_newstrtbl[i]);\n\t\t\tpr_newstrtbl[i] = NULL;\n\t\t}\n\t}\n}\n\n//FTE_CALLTIMEOFDAY\nvoid PF_calltimeofday (void)\n{\n\textern func_t ED_FindFunctionOffset (char *name);\n\tfunc_t f;\n\n\tif ((f = ED_FindFunctionOffset (\"timeofday\")))\n\t{\n\t\tdate_t date;\n\n\t\tSV_TimeOfDay(&date, \"%a %b %d, %H:%M:%S %Y\");\n\n\t\tG_FLOAT(OFS_PARM0) = (float)date.sec;\n\t\tG_FLOAT(OFS_PARM1) = (float)date.min;\n\t\tG_FLOAT(OFS_PARM2) = (float)date.hour;\n\t\tG_FLOAT(OFS_PARM3) = (float)date.day;\n\t\tG_FLOAT(OFS_PARM4) = (float)date.mon;\n\t\tG_FLOAT(OFS_PARM5) = (float)date.year;\n\t\tPR_SetTmpString(&G_INT(OFS_PARM6), date.str);\n\n\t\tPR_ExecuteProgram(f);\n\t}\n}\n\n/*\n=================\nPF_cvar\n\nfloat cvar (string)\n=================\n*/\nvoid PF_cvar (void)\n{\n\tchar\t*str;\n\n\tstr = G_STRING(OFS_PARM0);\n\n\tif (!strcasecmp(str, \"pr_checkextension\")) {\n\t\t// we do support PF_checkextension\n\t\tG_FLOAT(OFS_RETURN) = 1.0;\n\t\treturn;\n\t}\n\n\tif (pr_nqprogs && !pr_globals[35]/* deathmatch */\n\t\t&& (!strcmp(str, \"timelimit\") || !strcmp(str, \"samelevel\"))\n\t)\n\t{\n\t\t// workaround for NQ progs bug: timelimit and samelevel are checked in SP/coop\n\t\tG_FLOAT(OFS_RETURN) = 0.0;\n\t\treturn;\n\t}\n\n\tG_FLOAT(OFS_RETURN) = Cvar_Value (str);\n}\n\n/*\n=================\nPF_cvar_set\n \nfloat cvar (string)\n=================\n*/\nvoid PF_cvar_set (void)\n{\n\tchar\t*var_name, *val;\n\tcvar_t\t*var;\n\n\tvar_name = G_STRING(OFS_PARM0);\n\tval = G_STRING(OFS_PARM1);\n\n\tvar = Cvar_Find(var_name);\n\tif (!var)\n\t{\n\t\tCon_Printf (\"PF_cvar_set: variable %s not found\\n\", var_name);\n\t\treturn;\n\t}\n\n\tCvar_Set (var, val);\n}\n\n/*\n=================\nPF_findradius\n \nReturns a chain of entities that have origins within a spherical area\n \nfindradius (origin, radius)\n=================\n*/\nstatic void PF_findradius (void)\n{\n\tint\t\t\ti, j, numtouch;\n\tedict_t\t\t*touchlist[MAX_EDICTS], *ent, *chain;\n\tfloat\t\trad, rad_2, *org;\n\tvec3_t\t\tmins, maxs, eorg;\n\n\torg = G_VECTOR(OFS_PARM0);\n\trad = G_FLOAT(OFS_PARM1);\n\trad_2 = rad * rad;\n\n\tfor (i = 0; i < 3; i++)\n\t{\n\t\tmins[i] = org[i] - rad - 1;\t\t// enlarge the bbox a bit\n\t\tmaxs[i] = org[i] + rad + 1;\n\t}\n\n\tnumtouch = SV_AreaEdicts (mins, maxs, touchlist, sv.max_edicts, AREA_SOLID);\n\tnumtouch += SV_AreaEdicts (mins, maxs, &touchlist[numtouch], sv.max_edicts - numtouch, AREA_TRIGGERS);\n\n\tchain = (edict_t *)sv.edicts;\n\n// touch linked edicts\n\tfor (i = 0; i < numtouch; i++)\n\t{\n\t\tent = touchlist[i];\n\t\tif (ent->v->solid == SOLID_NOT)\n\t\t\tcontinue;\t// FIXME?\n\n\t\tfor (j = 0; j < 3; j++)\n\t\t\teorg[j] = org[j] - (ent->v->origin[j] + (ent->v->mins[j] + ent->v->maxs[j]) * 0.5);\n\t\tif (DotProduct(eorg, eorg) > rad_2)\n\t\t\tcontinue;\n\n\t\tent->v->chain = EDICT_TO_PROG(chain);\n\t\tchain = ent;\n\t}\n\n\tRETURN_EDICT(chain);\n}\n\n\n/*\n=========\nPF_dprint\n=========\n*/\nvoid PF_dprint (void)\n{\n\tCon_Printf (\"%s\",PF_VarString(0));\n}\n\nvoid PF_ftos (void)\n{\n\tfloat\tv;\n\tv = G_FLOAT(OFS_PARM0);\n\n\tif (v == (int)v)\n\t\tsnprintf (pr_string_temp, MAX_PR_STRING_SIZE, \"%d\",(int)v);\n\telse\n\t\tsnprintf (pr_string_temp, MAX_PR_STRING_SIZE, \"%5.1f\",v);\n\tPR1_SetString(&G_INT(OFS_RETURN), pr_string_temp);\n\tPF_SetTempString();\n}\n\nvoid PF_fabs (void)\n{\n\tfloat\tv;\n\tv = G_FLOAT(OFS_PARM0);\n\tG_FLOAT(OFS_RETURN) = fabs(v);\n}\n\nvoid PF_vtos (void)\n{\n\tsnprintf (pr_string_temp, MAX_PR_STRING_SIZE, \"'%5.1f %5.1f %5.1f'\", G_VECTOR(OFS_PARM0)[0], G_VECTOR(OFS_PARM0)[1], G_VECTOR(OFS_PARM0)[2]);\n\tPR1_SetString(&G_INT(OFS_RETURN), pr_string_temp);\n\n\tPF_SetTempString();\n}\n\nvoid PF_Spawn (void)\n{\n\tedict_t\t*ed;\n\ted = ED_Alloc();\n\tRETURN_EDICT(ed);\n}\n\nvoid PF_Remove (void)\n{\n\tedict_t\t*ed;\n\n\ted = G_EDICT(OFS_PARM0);\n\tED_Free (ed);\n}\n\n\n// entity (entity start, .string field, string match) find = #5;\nvoid PF_Find (void)\n{\n\tint\t\te;\n\tint\t\tf;\n\tchar\t*s, *t;\n\tedict_t\t*ed;\n\n\te = G_EDICTNUM(OFS_PARM0);\n\tf = G_INT(OFS_PARM1);\n\ts = G_STRING(OFS_PARM2);\n\tif (!s)\n\t\tPR_RunError (\"PF_Find: bad search string\");\n\n\tfor (e++ ; e < sv.num_edicts ; e++)\n\t{\n\t\ted = EDICT_NUM(e);\n\t\tif (ed->e.free)\n\t\t\tcontinue;\n\t\tt = E_STRING(ed,f);\n\t\tif (!t)\n\t\t\tcontinue;\n\t\tif (!strcmp(t,s))\n\t\t{\n\t\t\tRETURN_EDICT(ed);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tRETURN_EDICT(sv.edicts);\n}\n\nvoid PR_CheckEmptyString (char *s)\n{\n\tif (s[0] <= ' ')\n\t\tPR_RunError (\"Bad string\");\n}\n\nvoid PF_precache_file (void)\n{\t// precache_file is only used to copy files with qcc, it does nothing\n\tG_INT(OFS_RETURN) = G_INT(OFS_PARM0);\n}\n\nvoid PF_precache_sound (void)\n{\n\tchar\t*s;\n\tint\t\ti;\n\n\tif (sv.state != ss_loading)\n\t\tPR_RunError (\"PF_Precache_*: Precache can only be done in spawn functions\");\n\n\ts = G_STRING(OFS_PARM0);\n\tG_INT(OFS_RETURN) = G_INT(OFS_PARM0);\n\tPR_CheckEmptyString (s);\n\n\tfor (i=0 ; i<MAX_SOUNDS ; i++)\n\t{\n\t\tif (!sv.sound_precache[i])\n\t\t{\n\t\t\tsv.sound_precache[i] = s;\n\t\t\treturn;\n\t\t}\n\t\tif (!strcmp(sv.sound_precache[i], s))\n\t\t\treturn;\n\t}\n\tPR_RunError (\"PF_precache_sound: overflow\");\n}\n\nvoid PF_precache_model (void)\n{\n\tchar\t*s;\n\tint\t\ti;\n\n\tif (sv.state != ss_loading)\n\t\tPR_RunError (\"PF_Precache_*: Precache can only be done in spawn functions\");\n\n\ts = G_STRING(OFS_PARM0);\n\tG_INT(OFS_RETURN) = G_INT(OFS_PARM0);\n\tPR_CheckEmptyString (s);\n\n\tfor (i=0 ; i<MAX_MODELS ; i++)\n\t{\n\t\tif (!sv.model_precache[i])\n\t\t{\n\t\t\tsv.model_precache[i] = s;\n\t\t\treturn;\n\t\t}\n\t\tif (!strcmp(sv.model_precache[i], s))\n\t\t\treturn;\n\t}\n\tPR_RunError (\"PF_precache_model: overflow\");\n}\n\nstatic void PF_precache_vwep_model (void)\n{\n\tchar\t*s;\n\tint\t\ti;\n\t\n\tif (sv.state != ss_loading)\n\t\tPR_RunError (\"PF_Precache_*: Precache can only be done in spawn functions\");\n\t\t\n\ts = G_STRING(OFS_PARM0);\n\tPR_CheckEmptyString (s);\n\n\t// the strings are transferred via the stufftext mechanism, hence the stringency\n\tif (strchr(s, '\"') || strchr(s, ';') || strchr(s, '\\n'  ) || strchr(s, '\\t') || strchr(s, ' '))\n\t\tPR_RunError (\"Bad string\\n\");\n\n\tfor (i = 0; i < MAX_VWEP_MODELS; i++)\n\t{\n\t\tif (!sv.vw_model_name[i]) {\n\t\t\tsv.vw_model_name[i] = s;\n\t\t\tG_INT(OFS_RETURN) = i;\n\t\t\treturn;\n\t\t}\n\t}\n\tPR_RunError (\"PF_precache_vwep_model: overflow\");\n}\n\n\nvoid PF_coredump (void)\n{\n\tED_PrintEdicts ();\n}\n\nvoid PF_traceon (void)\n{\n\tpr_trace = true;\n}\n\nvoid PF_traceoff (void)\n{\n\tpr_trace = false;\n}\n\nvoid PF_eprint (void)\n{\n\tED_PrintNum (G_EDICTNUM(OFS_PARM0));\n}\n\n/*\n===============\nPF_walkmove\n \nfloat(float yaw, float dist) walkmove\n===============\n*/\nvoid PF_walkmove (void)\n{\n\tedict_t\t*ent;\n\tfloat\tyaw, dist;\n\tvec3_t\tmove;\n\tdfunction_t\t*oldf;\n\tint \toldself;\n\n\tent = PROG_TO_EDICT(pr_global_struct->self);\n\tyaw = G_FLOAT(OFS_PARM0);\n\tdist = G_FLOAT(OFS_PARM1);\n\n\tif ( !( (int)ent->v->flags & (FL_ONGROUND|FL_FLY|FL_SWIM) ) )\n\t{\n\t\tG_FLOAT(OFS_RETURN) = 0;\n\t\treturn;\n\t}\n\n\tyaw = yaw*M_PI*2 / 360;\n\n\tmove[0] = cos(yaw)*dist;\n\tmove[1] = sin(yaw)*dist;\n\tmove[2] = 0;\n\n\t// save program state, because SV_movestep may call other progs\n\toldf = pr_xfunction;\n\toldself = pr_global_struct->self;\n\n\tG_FLOAT(OFS_RETURN) = SV_movestep(ent, move, true);\n\n\n\t// restore program state\n\tpr_xfunction = oldf;\n\tpr_global_struct->self = oldself;\n}\n\n/*\n===============\nPF_droptofloor\n \nvoid() droptofloor\n===============\n*/\nvoid PF_droptofloor (void)\n{\n\tedict_t\t\t*ent;\n\tvec3_t\t\tend;\n\ttrace_t\t\ttrace;\n\n\tent = PROG_TO_EDICT(pr_global_struct->self);\n\n\tVectorCopy (ent->v->origin, end);\n\tend[2] -= 256;\n\n\ttrace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, end, false, ent);\n\n\tif (trace.fraction == 1 || trace.allsolid)\n\t\tG_FLOAT(OFS_RETURN) = 0;\n\telse\n\t{\n\t\tVectorCopy (trace.endpos, ent->v->origin);\n\t\tSV_LinkEdict (ent, false);\n\t\tent->v->flags = (int)ent->v->flags | FL_ONGROUND;\n\t\tent->v->groundentity = EDICT_TO_PROG(trace.e.ent);\n\t\tG_FLOAT(OFS_RETURN) = 1;\n\t}\n}\n\n/*\n===============\nPF_lightstyle\n \nvoid(float style, string value) lightstyle\n===============\n*/\nvoid PF_lightstyle (void)\n{\n\tint\t\tstyle;\n\tchar\t*val;\n\tclient_t\t*client;\n\tint\t\t\tj;\n\n\tstyle = G_FLOAT(OFS_PARM0);\n\tval = G_STRING(OFS_PARM1);\n\n\t// change the string in sv\n\tsv.lightstyles[style] = val;\n\n\t// send message to all clients on this server\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\tfor (j=0, client = svs.clients ; j<MAX_CLIENTS ; j++, client++)\n\t\tif ( client->state == cs_spawned )\n\t\t{\n\t\t\tClientReliableWrite_Begin (client, svc_lightstyle, strlen(val)+3);\n\t\t\tClientReliableWrite_Char (client, style);\n\t\t\tClientReliableWrite_String (client, val);\n\t\t}\n\tif (sv.mvdrecording)\n\t{\n\t\tif (MVDWrite_Begin( dem_all, 0, strlen(val)+3))\n\t\t{\n\t\t\tMVD_MSG_WriteByte(svc_lightstyle);\n\t\t\tMVD_MSG_WriteChar(style);\n\t\t\tMVD_MSG_WriteString(val);\n\t\t}\n\t}\n}\n\nvoid PF_rint (void)\n{\n\tfloat\tf;\n\tf = G_FLOAT(OFS_PARM0);\n\tif (f > 0)\n\t\tG_FLOAT(OFS_RETURN) = (int)(f + 0.5);\n\telse\n\t\tG_FLOAT(OFS_RETURN) = (int)(f - 0.5);\n}\nvoid PF_floor (void)\n{\n\tG_FLOAT(OFS_RETURN) = floor(G_FLOAT(OFS_PARM0));\n}\nvoid PF_ceil (void)\n{\n\tG_FLOAT(OFS_RETURN) = ceil(G_FLOAT(OFS_PARM0));\n}\n\n\n/*\n=============\nPF_checkbottom\n=============\n*/\nvoid PF_checkbottom (void)\n{\n\tedict_t\t*ent;\n\n\tent = G_EDICT(OFS_PARM0);\n\n\tG_FLOAT(OFS_RETURN) = SV_CheckBottom (ent);\n}\n\n/*\n=============\nPF_pointcontents\n=============\n*/\nvoid PF_pointcontents (void)\n{\n\tfloat\t*v;\n\n\tv = G_VECTOR(OFS_PARM0);\n\n\tG_FLOAT(OFS_RETURN) = SV_PointContents (v);\n}\n\n/*\n=============\nPF_nextent\n \nentity nextent(entity)\n=============\n*/\nvoid PF_nextent (void)\n{\n\tint\t\ti;\n\tedict_t\t*ent;\n\n\ti = G_EDICTNUM(OFS_PARM0);\n\twhile (1)\n\t{\n\t\ti++;\n\t\tif (i == sv.num_edicts)\n\t\t{\n\t\t\tRETURN_EDICT(sv.edicts);\n\t\t\treturn;\n\t\t}\n\t\tent = EDICT_NUM(i);\n\t\tif (!ent->e.free)\n\t\t{\n\t\t\tRETURN_EDICT(ent);\n\t\t\treturn;\n\t\t}\n\t}\n}\n\n/*\n=============\nPF_aim\n \nPick a vector for the player to shoot along\nvector aim(entity, missilespeed)\n=============\n*/\nvoid PF_aim (void)\n{\n\tVectorCopy (PR_GLOBAL(v_forward), G_VECTOR(OFS_RETURN));\n}\n\n/*\n==============\nPF_changeyaw\n \nThis was a major timewaster in progs, so it was converted to C\n==============\n*/\nvoid PF_changeyaw (void)\n{\n\tedict_t\t\t*ent;\n\tfloat\t\tideal, current, move, speed;\n\n\tent = PROG_TO_EDICT(pr_global_struct->self);\n\tcurrent = anglemod( ent->v->angles[1] );\n\tideal = ent->v->ideal_yaw;\n\tspeed = ent->v->yaw_speed;\n\n\tif (current == ideal)\n\t\treturn;\n\tmove = ideal - current;\n\tif (ideal > current)\n\t{\n\t\tif (move >= 180)\n\t\t\tmove = move - 360;\n\t}\n\telse\n\t{\n\t\tif (move <= -180)\n\t\t\tmove = move + 360;\n\t}\n\tif (move > 0)\n\t{\n\t\tif (move > speed)\n\t\t\tmove = speed;\n\t}\n\telse\n\t{\n\t\tif (move < -speed)\n\t\t\tmove = -speed;\n\t}\n\n\tent->v->angles[1] = anglemod (current + move);\n}\n\n/*\n===============================================================================\n \nMESSAGE WRITING\n \n===============================================================================\n*/\n\n#define\tMSG_BROADCAST\t0\t\t// unreliable to all\n#define\tMSG_ONE\t\t\t1\t\t// reliable to one (msg_entity)\n#define\tMSG_ALL\t\t\t2\t\t// reliable to all\n#define\tMSG_INIT\t\t3\t\t// write to the init string\n#define\tMSG_MULTICAST\t4\t\t// for multicast()\n\nsizebuf_t *WriteDest (void)\n{\n\tint\t\tdest;\n\t//\tint\t\tentnum;\n\t//\tedict_t\t*ent;\n\n\tdest = G_FLOAT(OFS_PARM0);\n\tswitch (dest)\n\t{\n\tcase MSG_BROADCAST:\n\t\treturn &sv.datagram;\n\n\tcase MSG_ONE:\n\t\tSV_Error(\"Shouldn't be at MSG_ONE\");\n#if 0\n\t\tent = PROG_TO_EDICT(PR_GLOBAL(msg_entity));\n\t\tentnum = NUM_FOR_EDICT(ent);\n\t\tif (entnum < 1 || entnum > MAX_CLIENTS)\n\t\t\tPR_RunError (\"WriteDest: not a client\");\n\t\treturn &svs.clients[entnum-1].netchan.message;\n#endif\n\n\tcase MSG_ALL:\n\t\treturn &sv.reliable_datagram;\n\n\tcase MSG_INIT:\n\t\tif (sv.state != ss_loading)\n\t\t\tPR_RunError (\"PF_Write_*: MSG_INIT can only be written in spawn functions\");\n\t\treturn &sv.signon;\n\n\tcase MSG_MULTICAST:\n\t\treturn &sv.multicast;\n\n\tdefault:\n\t\tPR_RunError (\"WriteDest: bad destination\");\n\t\tbreak;\n\t}\n\n\treturn NULL;\n}\n\nstatic client_t *Write_GetClient(void)\n{\n\tint\t\tentnum;\n\tedict_t\t*ent;\n\n\tent = PROG_TO_EDICT(PR_GLOBAL(msg_entity));\n\tentnum = NUM_FOR_EDICT(ent);\n\tif (entnum < 1 || entnum > MAX_CLIENTS)\n\t\tPR_RunError (\"WriteDest: not a client\");\n\treturn &svs.clients[entnum-1];\n}\n\n\n#ifdef WITH_NQPROGS\nstatic byte nqp_buf_data[1024] /* must be large enough for svc_finale text */;\nstatic sizebuf_t nqp_buf;\nstatic qbool nqp_ignore_this_frame;\nstatic int nqp_expect;\n\nvoid NQP_Reset (void)\n{\n\tnqp_ignore_this_frame = false;\n\tnqp_expect = 0;\n\tSZ_Init (&nqp_buf, nqp_buf_data, sizeof(nqp_buf_data));\n}\n\nstatic void NQP_Flush (int count)\n{\n// FIXME, we make no distinction reliable or not\n\tassert (count <= nqp_buf.cursize);\n\tSZ_Write (&sv.reliable_datagram, nqp_buf_data, count);\n\tmemmove (nqp_buf_data, nqp_buf_data + count, nqp_buf.cursize - count);\n\tnqp_buf.cursize -= count;\n}\n\nstatic void NQP_Skip (int count)\n{\n\tassert (count <= nqp_buf.cursize);\n\tmemmove (nqp_buf_data, nqp_buf_data + count, nqp_buf.cursize - count);\n\tnqp_buf.cursize -= count;\n}\n\nstatic void NQP_Process (void)\n{\n\tint cmd;\n\n\tif (nqp_ignore_this_frame) {\n\t\tSZ_Clear (&nqp_buf);\n\t\treturn;\n\t}\n\n\twhile (1) {\n\t\tif (nqp_expect) {\n\t\t\tif (nqp_buf.cursize >= nqp_expect) {\n\t\t\t\tNQP_Flush (nqp_expect);\n\t\t\t\tnqp_expect = 0;\n\t\t\t}\n\t\t\telse\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (!nqp_buf.cursize)\n\t\t\tbreak;\n\n\t\tnqp_expect = 0;\n\n\t\tcmd = nqp_buf_data[0];\n\t\tif (cmd == svc_killedmonster || cmd == svc_foundsecret || cmd == svc_sellscreen)\n\t\t\tnqp_expect = 1;\n\t\telse if (cmd == svc_updatestat) {\n\t\t\tNQP_Skip (1);\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, svc_updatestatlong);\n\t\t\tnqp_expect = 5;\n\t\t}\n\t\telse if (cmd == svc_print) {\n\t\t\tbyte *p = (byte *)memchr (nqp_buf_data + 1, 0, nqp_buf.cursize - 1);\n\t\t\tif (!p)\n\t\t\t\tgoto waitformore;\n\t\t\tMSG_WriteByte(&sv.reliable_datagram, svc_print);\n\t\t\tMSG_WriteByte(&sv.reliable_datagram, PRINT_HIGH);\n\t\t\tMSG_WriteString(&sv.reliable_datagram, (char *)(nqp_buf_data + 1));\n\t\t\tNQP_Skip(p - nqp_buf_data + 1);\n\t\t}\n\t\telse if (cmd == nq_svc_setview) {\n\t\t\tif (nqp_buf.cursize < 3)\n\t\t\t\tgoto waitformore;\n\t\t\tNQP_Skip (3);\t\t// TODO: make an extension for this\n\t\t}\n\t\telse if (cmd == svc_updatefrags)\n\t\t\tnqp_expect = 4;\n\t\telse if (cmd == nq_svc_updatecolors) {\n\t\t\tif (nqp_buf.cursize < 3)\n\t\t\t\tgoto waitformore;\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, svc_setinfo);\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, nqp_buf_data[1]);\n\t\t\tMSG_WriteString (&sv.reliable_datagram, \"topcolor\");\n\t\t\tMSG_WriteString (&sv.reliable_datagram, va(\"%i\", min(nqp_buf_data[2] & 15, 13)));\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, svc_setinfo);\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, nqp_buf_data[1]);\n\t\t\tMSG_WriteString (&sv.reliable_datagram, \"bottomcolor\");\n\t\t\tMSG_WriteString (&sv.reliable_datagram, va(\"%i\", min((nqp_buf_data[2] >> 4)& 15, 13)));\n\t\t\tNQP_Skip (3);\n\t\t}\n\t\telse if (cmd == nq_svc_updatename) {\n\t\t\tint slot;\n\t\t\tbyte *p;\n\t\t\tif (nqp_buf.cursize < 3)\n\t\t\t\tgoto waitformore;\n\t\t\tslot = nqp_buf_data[1];\n\t\t\tp = (byte *)memchr (nqp_buf_data + 2, 0, nqp_buf.cursize - 2);\n\t\t\tif (!p)\n\t\t\t\tgoto waitformore;\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, svc_setinfo);\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, slot);\n\t\t\tMSG_WriteString (&sv.reliable_datagram, \"name\");\n\t\t\tMSG_WriteString (&sv.reliable_datagram, (char *)nqp_buf_data + 2);\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, svc_updateping);\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, slot);\n\t\t\tMSG_WriteShort (&sv.reliable_datagram, 0);\n\t\t\t// We expect bots to set their name when they enter the game and never change it\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, svc_updateentertime);\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, slot);\n\t\t\tMSG_WriteFloat (&sv.reliable_datagram, 0);\n\t\t\tNQP_Skip ((p - nqp_buf_data) + 1);\n\t\t}\n\t\telse if (cmd == svc_cdtrack) {\n\t\t\tif (nqp_buf.cursize < 3)\n\t\t\t\tgoto waitformore;\n\t\t\tNQP_Flush (2);\n\t\t\tNQP_Skip (1);\n\t\t}\n\t\telse if (cmd == svc_finale) {\n\t\t\tbyte *p = (byte *)memchr (nqp_buf_data + 1, 0, nqp_buf.cursize - 1);\n\t\t\tif (!p)\n\t\t\t\tgoto waitformore;\n\t\t\tnqp_expect = (p - nqp_buf_data) + 1;\n\t\t}\n\t\telse if (cmd == svc_intermission) {\n\t\t\tint i;\n\t\t\tNQP_Flush (1);\n\t\t\tfor (i = 0; i < 3; i++)\n\t\t\t\tMSG_WriteCoord (&sv.reliable_datagram, svs.clients[0].edict->v->origin[i]);\n\t\t\tfor (i = 0; i < 3; i++)\n\t\t\t\tMSG_WriteAngle (&sv.reliable_datagram, svs.clients[0].edict->v->angles[i]);\n\t\t}\n\t\telse if (cmd == nq_svc_cutscene) {\n\t\t\tbyte *p = (byte *)memchr (nqp_buf_data + 1, 0, nqp_buf.cursize - 1);\n\t\t\tif (!p)\n\t\t\t\tgoto waitformore;\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, svc_stufftext);\n\t\t\tMSG_WriteString (&sv.reliable_datagram, \"//cutscene\\n\"); // ZQ extension\n\t\t\tNQP_Skip (p - nqp_buf_data + 1);\n\t\t}\n\t\telse if (nqp_buf_data[0] == svc_temp_entity) {\n\t\t\tif (nqp_buf.cursize < 2)\n\t\t\t\tbreak;\n\nswitch (nqp_buf_data[1]) {\n  case TE_SPIKE:\n  case TE_SUPERSPIKE:\n  case TE_EXPLOSION:\n  case TE_TAREXPLOSION:\n  case TE_WIZSPIKE:\n  case TE_KNIGHTSPIKE:\n  case TE_LAVASPLASH:\n  case TE_TELEPORT:\n\t\tnqp_expect = 8;\n\t\tbreak;\n  case TE_GUNSHOT:\n\t\tif (nqp_buf.cursize < 8)\n\t\t\tgoto waitformore;\n\t\tNQP_Flush (2);\n\t\tMSG_WriteByte (&sv.reliable_datagram, 1);\n\t\tNQP_Flush (6);\n\t\tbreak;\n\n  case TE_LIGHTNING1:\n  case TE_LIGHTNING2:\n  case TE_LIGHTNING3:\n\t\tnqp_expect = 16;\n\t  break;\n  case NQ_TE_BEAM:\n  {\t\tint entnum;\n\t\tif (nqp_buf.cursize < 16)\n\t\t\tgoto waitformore;\n\t\tMSG_WriteByte (&sv.reliable_datagram, svc_temp_entity);\n\t\tMSG_WriteByte (&sv.reliable_datagram, TE_LIGHTNING1);\n\t\tentnum = nqp_buf_data[2] + nqp_buf_data[3]*256;\n\t\tif ((unsigned int)entnum > 1023)\n\t\t\tentnum = 0;\n\t\tMSG_WriteShort (&sv.reliable_datagram, (short)(entnum - 1288));\n\t\tNQP_Skip (4);\n\t\tnqp_expect = 12;\n\t\tbreak;\n  }\n\n  case NQ_TE_EXPLOSION2:\n\t\tnqp_expect = 10;\n\t\tbreak;\n  default:\n\t\tCon_Printf (\"WARNING: progs.dat sent an unsupported svc_temp_entity: %i\\n\", nqp_buf_data[1]);\n\t    goto ignore;\n}\n\n\t\t}\n\t\telse {\n\t\t\tCon_Printf (\"WARNING: progs.dat sent an unsupported svc: %i\\n\", cmd);\nignore:\n\t\t\tnqp_ignore_this_frame = true;\n\t\t\tbreak;\n\t\t}\n\t}\nwaitformore:;\n}\n\n#else // !WITH_NQPROGS\n#define NQP_Process()\nsizebuf_t nqp_buf;\t// dummy\n#endif\n\n\nvoid PF_WriteByte (void)\n{\n\tif (pr_nqprogs)\n\t{\n\t\tif (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT)\n\t\t\treturn;\t// we don't support this\n\t\tMSG_WriteByte (&nqp_buf, G_FLOAT(OFS_PARM1));\n\t\tNQP_Process ();\n\t\treturn;\n\t}\n\n\tif (G_FLOAT(OFS_PARM0) == MSG_ONE)\n\t{\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, 1);\n\t\tClientReliableWrite_Byte(cl, G_FLOAT(OFS_PARM1));\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 1))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteByte(G_FLOAT(OFS_PARM1));\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteByte (WriteDest(), G_FLOAT(OFS_PARM1));\n}\n\nvoid PF_WriteChar (void)\n{\n\tif (pr_nqprogs)\n\t{\n\t\tif (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT)\n\t\t\treturn;\t// we don't support this\n\t\tMSG_WriteByte (&nqp_buf, G_FLOAT(OFS_PARM1));\n\t\tNQP_Process ();\n\t\treturn;\n\t}\n\n\tif (G_FLOAT(OFS_PARM0) == MSG_ONE)\n\t{\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, 1);\n\t\tClientReliableWrite_Char(cl, G_FLOAT(OFS_PARM1));\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 1))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteByte(G_FLOAT(OFS_PARM1));\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteChar (WriteDest(), G_FLOAT(OFS_PARM1));\n}\n\nvoid PF_WriteShort (void)\n{\n\tif (pr_nqprogs)\n\t{\n\t\tif (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT)\n\t\t\treturn;\t// we don't support this\n\t\tMSG_WriteShort (&nqp_buf, G_FLOAT(OFS_PARM1));\n\t\tNQP_Process ();\n\t\treturn;\n\t}\n\n\tif (G_FLOAT(OFS_PARM0) == MSG_ONE)\n\t{\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, 2);\n\t\tClientReliableWrite_Short(cl, G_FLOAT(OFS_PARM1));\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 2))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteShort(G_FLOAT(OFS_PARM1));\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteShort (WriteDest(), G_FLOAT(OFS_PARM1));\n}\n\nvoid PF_WriteLong (void)\n{\n\tif (pr_nqprogs)\n\t{\n\t\tif (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT)\n\t\t\treturn;\t// we don't support this\n\t\tMSG_WriteLong (&nqp_buf, G_FLOAT(OFS_PARM1));\n\t\tNQP_Process ();\n\t\treturn;\n\t}\n\n\tif (G_FLOAT(OFS_PARM0) == MSG_ONE)\n\t{\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, 4);\n\t\tClientReliableWrite_Long(cl, G_FLOAT(OFS_PARM1));\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 4))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteLong(G_FLOAT(OFS_PARM1));\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteLong (WriteDest(), G_FLOAT(OFS_PARM1));\n}\n\nvoid PF_WriteAngle (void)\n{\n\tif (pr_nqprogs)\n\t{\n\t\tif (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT)\n\t\t\treturn;\t// we don't support this\n\t\tMSG_WriteAngle (&nqp_buf, G_FLOAT(OFS_PARM1));\n\t\tNQP_Process ();\n\t\treturn;\n\t}\n\n\tif (G_FLOAT(OFS_PARM0) == MSG_ONE)\n\t{\n#ifdef FTE_PEXT_FLOATCOORDS\n\t\tint size = msg_anglesize;\n#else\n\t\tint size = 1;\n#endif\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, size);\n\t\tClientReliableWrite_Angle(cl, G_FLOAT(OFS_PARM1));\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, size))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteAngle(G_FLOAT(OFS_PARM1));\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteAngle (WriteDest(), G_FLOAT(OFS_PARM1));\n}\n\nvoid PF_WriteCoord (void)\n{\n\tif (pr_nqprogs)\n\t{\n\t\tif (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT)\n\t\t\treturn;\t// we don't support this\n\t\tMSG_WriteCoord (&nqp_buf, G_FLOAT(OFS_PARM1));\n\t\tNQP_Process ();\n\t\treturn;\n\t}\n\n\tif (G_FLOAT(OFS_PARM0) == MSG_ONE)\n\t{\n#ifdef FTE_PEXT_FLOATCOORDS\n\t\tint size = msg_coordsize;\n#else\n\t\tint size = 2;\n#endif\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, size);\n\t\tClientReliableWrite_Coord(cl, G_FLOAT(OFS_PARM1));\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, size))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteCoord(G_FLOAT(OFS_PARM1));\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteCoord (WriteDest(), G_FLOAT(OFS_PARM1));\n}\n\nvoid PF_WriteString (void)\n{\n\tif (pr_nqprogs)\n\t{\n\t\tif (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT)\n\t\t\treturn;\t// we don't support this\n\t\tMSG_WriteString (&nqp_buf, G_STRING(OFS_PARM1));\n\t\tNQP_Process ();\n\t\treturn;\n\t}\n\n\tif (G_FLOAT(OFS_PARM0) == MSG_ONE)\n\t{\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, 1+strlen(G_STRING(OFS_PARM1)));\n\t\tClientReliableWrite_String(cl, G_STRING(OFS_PARM1));\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 1 + strlen(G_STRING(OFS_PARM1))))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteString(G_STRING(OFS_PARM1));\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteString (WriteDest(), G_STRING(OFS_PARM1));\n}\n\n\nvoid PF_WriteEntity (void)\n{\n\tif (pr_nqprogs)\n\t{\n\t\tif (G_FLOAT(OFS_PARM0) == MSG_ONE || G_FLOAT(OFS_PARM0) == MSG_INIT)\n\t\t\treturn;\t// we don't support this\n\t\tMSG_WriteShort (&nqp_buf, G_EDICTNUM(OFS_PARM1));\n\t\tNQP_Process ();\n\t\treturn;\n\t}\n\n\tif (G_FLOAT(OFS_PARM0) == MSG_ONE)\n\t{\n\t\tclient_t *cl = Write_GetClient();\n\t\tClientReliableCheckBlock(cl, 2);\n\t\tClientReliableWrite_Short(cl, G_EDICTNUM(OFS_PARM1));\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_single, cl - svs.clients, 2))\n\t\t\t{\n\t\t\t\tMVD_MSG_WriteShort(G_EDICTNUM(OFS_PARM1));\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t\tMSG_WriteShort (WriteDest(), G_EDICTNUM(OFS_PARM1));\n}\n\n//=============================================================================\n\nint SV_ModelIndex (char *name);\n\nvoid PF_makestatic (void)\n{\n\tentity_state_t* s;\n\tedict_t\t*ent;\n\n\tent = G_EDICT(OFS_PARM0);\n\tif (sv.static_entity_count >= sizeof(sv.static_entities) / sizeof(sv.static_entities[0])) {\n\t\tED_Free (ent);\n\t\treturn;\n\t}\n\n\ts = &sv.static_entities[sv.static_entity_count];\n\tmemset(s, 0, sizeof(sv.static_entities[0]));\n\ts->number = sv.static_entity_count + 1;\n\ts->modelindex = SV_ModelIndex(PR_GetEntityString(ent->v->model));\n\tif (!s->modelindex) {\n\t\tED_Free (ent);\n\t\treturn;\n\t}\n\ts->frame = ent->v->frame;\n\ts->colormap = ent->v->colormap;\n\ts->skinnum = ent->v->skin;\n\tVectorCopy(ent->v->origin, s->origin);\n\tVectorCopy(ent->v->angles, s->angles);\n#ifdef FTE_PEXT_TRANS\n\ts->trans = ent->xv.alpha >= 1.0f ? 0 : bound(0, (byte)(ent->xv.alpha * 254.0), 254);\n#endif\n#ifdef FTE_PEXT_COLOURMOD\n\tif (ent->xv.colourmod[0] != 1.0f && ent->xv.colourmod[1] != 1.0f && ent->xv.colourmod[2] != 1.0f)\n\t{\n\t\ts->colourmod[0] = bound(0, ent->xv.colourmod[0] * (256.0f / 8.0f), 255);\n\t\ts->colourmod[1] = bound(0, ent->xv.colourmod[1] * (256.0f / 8.0f), 255);\n\t\ts->colourmod[2] = bound(0, ent->xv.colourmod[2] * (256.0f / 8.0f), 255);\n\t}\n#endif\n\t++sv.static_entity_count;\n\n\t// throw the entity away now\n\tED_Free (ent);\n}\n\n//=============================================================================\n\n/*\n==============\nPF_setspawnparms\n==============\n*/\nvoid PF_setspawnparms (void)\n{\n\tedict_t\t*ent;\n\tint\t\ti;\n\tclient_t\t*client;\n\n\tent = G_EDICT(OFS_PARM0);\n\ti = NUM_FOR_EDICT(ent);\n\tif (i < 1 || i > MAX_CLIENTS)\n\t\tPR_RunError (\"Entity is not a client\");\n\n\t// copy spawn parms out of the client_t\n\tclient = svs.clients + (i-1);\n\n\tfor (i=0 ; i< NUM_SPAWN_PARMS ; i++)\n\t\t(&PR_GLOBAL(parm1))[i] = client->spawn_parms[i];\n}\n\n/*\n==============\nPF_changelevel\n==============\n*/\nvoid PF_changelevel (void)\n{\n\tchar\t*s;\n\tstatic\tint\tlast_spawncount;\n\n\t// make sure we don't issue two changelevels\n\tif (svs.spawncount == last_spawncount)\n\t\treturn;\n\tlast_spawncount = svs.spawncount;\n\n\ts = G_STRING(OFS_PARM0);\n\tCbuf_AddText (va(\"map %s\\n\",s));\n}\n\n\n/*\n==============\nPF_logfrag\n \nlogfrag (killer, killee)\n==============\n*/\nvoid PF_logfrag (void)\n{\n\tedict_t\t*ent1, *ent2;\n\tint\t\te1, e2;\n\tchar\t*s;\n\t// -> scream\n\ttime_t\t\tt;\n\tstruct tm\t*tblock;\n\t// <-\n\n\tent1 = G_EDICT(OFS_PARM0);\n\tent2 = G_EDICT(OFS_PARM1);\n\n\te1 = NUM_FOR_EDICT(ent1);\n\te2 = NUM_FOR_EDICT(ent2);\n\n\tif (e1 < 1 || e1 > MAX_CLIENTS || e2 < 1 || e2 > MAX_CLIENTS)\n\t\treturn;\n\t// -> scream\n\tt = time (NULL);\n\ttblock = localtime (&t);\n\n\t//bliP: date check ->\n\tif (!tblock)\n\t\ts = va(\"%s\\n\", \"#bad date#\");\n\telse\n\t\tif ((int)frag_log_type.value) // need for old-style frag log file\n\t\t\ts = va(\"\\\\frag\\\\%s\\\\%s\\\\%s\\\\%s\\\\%d-%d-%d %d:%d:%d\\\\\\n\",\n\t\t\t       svs.clients[e1-1].name, svs.clients[e2-1].name,\n\t\t\t       svs.clients[e1-1].team, svs.clients[e2-1].team,\n\t\t\t       tblock->tm_year + 1900, tblock->tm_mon + 1, tblock->tm_mday,\n\t\t\t       tblock->tm_hour, tblock->tm_min, tblock->tm_sec);\n\t\telse\n\t\t\ts = va(\"\\\\%s\\\\%s\\\\\\n\",svs.clients[e1-1].name, svs.clients[e2-1].name);\n\t// <-\n\tSZ_Print (&svs.log[svs.logsequence&1], s);\n\tSV_Write_Log(FRAG_LOG, 1, s);\n\t//\tSV_Write_Log(MOD_FRAG_LOG, 1, \"\\n==== PF_logfrag ===={\\n\");\n\t//\tSV_Write_Log(MOD_FRAG_LOG, 1, va(\"%d\\n\", time(NULL)));\n\t//\tSV_Write_Log(MOD_FRAG_LOG, 1, s);\n\t//\tSV_Write_Log(MOD_FRAG_LOG, 1, \"}====================\\n\");\n}\n\n/*\n==============\nPF_infokey\n\nstring(entity e, string key) infokey\n==============\n*/\nvoid PF_infokey (void)\n{\n\tedict_t\t*e;\n\tint\t\te1;\n\tchar\t*value;\n\tchar\t*key;\n\tstatic\tchar ov[256];\n\tclient_t *cl;\n\n\te = G_EDICT(OFS_PARM0);\n\te1 = NUM_FOR_EDICT(e);\n\tkey = G_STRING(OFS_PARM1);\n\tcl = &svs.clients[e1-1];\n\n\tif (e1 == 0)\n\t{\n\t\tif ((value = Info_ValueForKey (svs.info, key)) == NULL || !*value)\n\t\t\tvalue = Info_Get(&_localinfo_, key);\n\t}\n\telse if (e1 <= MAX_CLIENTS)\n\t{\n\t\tvalue = ov;\n\t\tif (!strncmp(key, \"ip\", 3))\n\t\t\tstrlcpy(ov, NET_BaseAdrToString (cl->netchan.remote_address), sizeof(ov));\n\t\telse if (!strncmp(key, \"realip\", 7))\n\t\t\tstrlcpy(ov, NET_BaseAdrToString (cl->realip), sizeof(ov));\n\t\telse if (!strncmp(key, \"download\", 9))\n\t\t\t//snprintf(ov, sizeof(ov), \"%d\", cl->download != NULL ? (int)(100*cl->downloadcount/cl->downloadsize) : -1);\n\t\t\tsnprintf(ov, sizeof(ov), \"%d\", cl->file_percent ? cl->file_percent : -1); //bliP: file percent\n\t\telse if (!strncmp(key, \"ping\", 5))\n\t\t\tsnprintf(ov, sizeof(ov), \"%d\", SV_CalcPing (cl));\n\t\telse if (!strncmp(key, \"*userid\", 8))\n\t\t\tsnprintf(ov, sizeof(ov), \"%d\", svs.clients[e1 - 1].userid);\n\t\telse if (!strncmp(key, \"login\", 6))\n\t\t\tvalue = cl->login;\n\t\telse\n\t\t\tvalue = Info_Get (&cl->_userinfo_ctx_, key);\n\t}\n\telse\n\t\tvalue = \"\";\n\n\tstrlcpy(pr_string_temp, value, MAX_PR_STRING_SIZE);\n\tRETURN_STRING(pr_string_temp);\n\tPF_SetTempString();\n}\n\n/*\n==============\nPF_stof\n \nfloat(string s) stof\n==============\n*/\nvoid PF_stof (void)\n{\n\tchar\t*s;\n\n\ts = G_STRING(OFS_PARM0);\n\n\tG_FLOAT(OFS_RETURN) = Q_atof(s);\n}\n\n\n/*\n==============\nPF_multicast\n \nvoid(vector where, float set) multicast\n==============\n*/\nvoid PF_multicast (void)\n{\n\tfloat\t*o;\n\tint\t\tto;\n\n\to = G_VECTOR(OFS_PARM0);\n\tto = G_FLOAT(OFS_PARM1);\n\n\tSV_Multicast (o, to);\n}\n\n//DP_QC_SINCOSSQRTPOW\n//float sin(float x) = #60\nvoid PF_sin (void)\n{\n\tG_FLOAT(OFS_RETURN) = sin(G_FLOAT(OFS_PARM0));\n}\n\n//DP_QC_SINCOSSQRTPOW\n//float cos(float x) = #61\nvoid PF_cos (void)\n{\n\tG_FLOAT(OFS_RETURN) = cos(G_FLOAT(OFS_PARM0));\n}\n\n//DP_QC_SINCOSSQRTPOW\n//float sqrt(float x) = #62\nvoid PF_sqrt (void)\n{\n\tG_FLOAT(OFS_RETURN) = sqrt(G_FLOAT(OFS_PARM0));\n}\n\n//DP_QC_SINCOSSQRTPOW\n//float pow(float x, float y) = #97;\nstatic void PF_pow (void)\n{\n\tG_FLOAT(OFS_RETURN) = pow(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1));\n}\n\n//DP_QC_MINMAXBOUND\n//float min(float a, float b, ...) = #94\nvoid PF_min (void)\n{\n\t// LordHavoc: 3+ argument enhancement suggested by FrikaC\n\tif (pr_argc == 2)\n\t\tG_FLOAT(OFS_RETURN) = min(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1));\n\telse if (pr_argc >= 3)\n\t{\n\t\tint i;\n\t\tfloat f = G_FLOAT(OFS_PARM0);\n\t\tfor (i = 1;i < pr_argc;i++)\n\t\t\tif (G_FLOAT((OFS_PARM0+i*3)) < f)\n\t\t\t\tf = G_FLOAT((OFS_PARM0+i*3));\n\t\tG_FLOAT(OFS_RETURN) = f;\n\t}\n\telse\n\t\tSys_Error(\"min: must supply at least 2 floats\");\n}\n\n//DP_QC_MINMAXBOUND\n//float max(float a, float b, ...) = #95\nvoid PF_max (void)\n{\n\t// LordHavoc: 3+ argument enhancement suggested by FrikaC\n\tif (pr_argc == 2)\n\t\tG_FLOAT(OFS_RETURN) = max(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1));\n\telse if (pr_argc >= 3)\n\t{\n\t\tint i;\n\t\tfloat f = G_FLOAT(OFS_PARM0);\n\t\tfor (i = 1;i < pr_argc;i++)\n\t\t\tif (G_FLOAT((OFS_PARM0+i*3)) > f)\n\t\t\t\tf = G_FLOAT((OFS_PARM0+i*3));\n\t\tG_FLOAT(OFS_RETURN) = f;\n\t}\n\telse\n\t\tSys_Error(\"max: must supply at least 2 floats\");\n}\n\n//DP_QC_MINMAXBOUND\n//float bound(float min, float value, float max) = #96\nvoid PF_bound (void)\n{\n\tG_FLOAT(OFS_RETURN) = bound(G_FLOAT(OFS_PARM0), G_FLOAT(OFS_PARM1), G_FLOAT(OFS_PARM2));\n}\n\n/*\n=================\nPF_tracebox\n\nLike traceline but traces a box of the size specified\n(NOTE: currently the hull size can only be one of the sizes used in the map\nfor bmodel collisions, entity collisions will pay attention to the exact size\nspecified however, this is a collision code limitation in quake itself,\nand will be fixed eventually).\n\nDP_QC_TRACEBOX\n\nvoid(vector v1, vector mins, vector maxs, vector v2, float nomonsters, entity ignore) tracebox = #90;\n=================\n*/\nstatic void PF_tracebox (void)\n{\n        float       *v1, *v2, *mins, *maxs;\n        edict_t     *ent;\n        int          nomonsters;\n        trace_t      trace;\n\n        v1 = G_VECTOR(OFS_PARM0);\n        mins = G_VECTOR(OFS_PARM1);\n        maxs = G_VECTOR(OFS_PARM2);\n        v2 = G_VECTOR(OFS_PARM3);\n        nomonsters = G_FLOAT(OFS_PARM4);\n        ent = G_EDICT(OFS_PARM5);\n\n        trace = SV_Trace (v1, mins, maxs, v2, nomonsters, ent);\n\n        PR_GLOBAL(trace_allsolid) = trace.allsolid;\n        PR_GLOBAL(trace_startsolid) = trace.startsolid;\n        PR_GLOBAL(trace_fraction) = trace.fraction;\n        PR_GLOBAL(trace_inwater) = trace.inwater;\n        PR_GLOBAL(trace_inopen) = trace.inopen;\n        VectorCopy (trace.endpos, PR_GLOBAL(trace_endpos));\n        VectorCopy (trace.plane.normal, PR_GLOBAL(trace_plane_normal));\n        PR_GLOBAL(trace_plane_dist) =  trace.plane.dist;\n        if (trace.e.ent)\n                PR_GLOBAL(trace_ent) = EDICT_TO_PROG(trace.e.ent);\n        else\n                PR_GLOBAL(trace_ent) = EDICT_TO_PROG(sv.edicts);\n}\n\n/*\n=================\nPF_randomvec\n\nDP_QC_RANDOMVEC\nvector randomvec() = #91\n=================\n*/\nstatic void PF_randomvec (void)\n{\n\tvec3_t temp;\n\n\tdo {\n\t\ttemp[0] = (rand() & 0x7fff) * (2.0 / 0x7fff) - 1.0;\n\t\ttemp[1] = (rand() & 0x7fff) * (2.0 / 0x7fff) - 1.0;\n\t\ttemp[2] = (rand() & 0x7fff) * (2.0 / 0x7fff) - 1.0;\n\t} while (DotProduct(temp, temp) >= 1);\n\n\tVectorCopy (temp, G_VECTOR(OFS_RETURN));\n}\n\n/*\n=================\nPF_cvar_string\n\nQSG_CVARSTRING DP_QC_CVAR_STRING\nstring cvar_string(string varname) = #103;\n=================\n*/\nstatic void PF_cvar_string (void)\n{\n\tchar\t*str;\n\tcvar_t\t*var;\n\n\tstr = G_STRING(OFS_PARM0);\n\tvar = Cvar_Find(str);\n\tif (!var) {\n\t\tG_INT(OFS_RETURN) = 0;\n\t\treturn;\n\t}\n\n\tstrlcpy (pr_string_temp, var->string, MAX_PR_STRING_SIZE);\n\tRETURN_STRING(pr_string_temp);\n\tPF_SetTempString();\n}\n\n// DP_REGISTERCVAR\n// float(string name, string value) registercvar = #93;\n// DarkPlaces implementation has an undocumented feature where you can add cvar flags\n// as a third parameter;  I don't see how that can be useful so it's not implemented\nvoid PF_registercvar (void)\n{\n\tchar *name, *value;\n\n\tname = G_STRING(OFS_PARM0);\n\tvalue = G_STRING(OFS_PARM0);\n\n\tif (Cvar_Find(name)) {\n\t\tG_INT(OFS_RETURN) = 0;\n\t\treturn;\n\t}\n\n\tCvar_Create (name, value, 0);\n\tG_INT(OFS_RETURN) = 1;\n}\n\n// ZQ_PAUSE\n// void(float pause) setpause = #531;\nvoid PF_setpause (void)\n{\n\tint pause;\n\n\tpause = G_FLOAT(OFS_PARM0) ? 1 : 0;\n\n\tif (pause != (sv.paused & 1))\n\t\tSV_TogglePause (NULL, 1);\n}\n\n\n/*\n==============\nPF_checkextension\n\nfloat checkextension(string extension) = #99;\n==============\n*/\nstatic void PF_checkextension (void)\n{\n\tstatic char *supported_extensions[] = {\n\t\t\"DP_CON_SET\",               // http://wiki.quakesrc.org/index.php/DP_CON_SET\n\t\t\"DP_QC_CVAR_STRING\",\t\t// http://wiki.quakesrc.org/index.php/DP_QC_CVAR_STRING\n\t\t\"DP_QC_MINMAXBOUND\",        // http://wiki.quakesrc.org/index.php/DP_QC_MINMAXBOUND\n\t\t\"DP_QC_RANDOMVEC\",\t\t\t// http://wiki.quakesrc.org/index.php/DP_QC_RANDOMVEC\n\t\t\"DP_QC_SINCOSSQRTPOW\",      // http://wiki.quakesrc.org/index.php/DP_QC_SINCOSSQRTPOW\n\t\t\"DP_QC_TRACEBOX\",\t\t\t// http://wiki.quakesrc.org/index.php/DP_QC_TRACEBOX\n\t\t\"DP_REGISTERCVAR\",\t\t\t// http://wiki.quakesrc.org/index.php/DP_REGISTERCVAR\n\t\t\"FTE_CALLTIMEOFDAY\",        // http://wiki.quakesrc.org/index.php/FTE_CALLTIMEOFDAY\n\t\t\"QSG_CVARSTRING\",\t\t\t// http://wiki.quakesrc.org/index.php/QSG_CVARSTRING\n\t\t\"ZQ_CLIENTCOMMAND\",\t\t\t// http://wiki.quakesrc.org/index.php/ZQ_CLIENTCOMMAND\n\t\t\"ZQ_ITEMS2\",                // http://wiki.quakesrc.org/index.php/ZQ_ITEMS2\n\t\t\"ZQ_MOVETYPE_NOCLIP\",       // http://wiki.quakesrc.org/index.php/ZQ_MOVETYPE_NOCLIP\n\t\t\"ZQ_MOVETYPE_FLY\",          // http://wiki.quakesrc.org/index.php/ZQ_MOVETYPE_FLY\n\t\t\"ZQ_MOVETYPE_NONE\",         // http://wiki.quakesrc.org/index.php/ZQ_MOVETYPE_NONE\n\t\t\"ZQ_PAUSE\",\t\t\t\t\t// http://wiki.quakesrc.org/index.php/ZQ_PAUSE\n\t\t\"ZQ_QC_STRINGS\",\t\t\t// http://wiki.quakesrc.org/index.php/ZQ_QC_STRINGS\n\t\t\"ZQ_QC_TOKENIZE\",           // http://wiki.quakesrc.org/index.php/ZQ_QC_TOKENIZE\n\t\t\"ZQ_VWEP\",\n\t\tNULL\n\t};\n\tchar **pstr, *extension;\n\textension = G_STRING(OFS_PARM0);\n\n\tfor (pstr = supported_extensions; *pstr; pstr++) {\n\t\tif (!strcasecmp(*pstr, extension)) {\n\t\t\tG_FLOAT(OFS_RETURN) = 1.0;\t// supported\n\t\t\treturn;\n\t\t}\n\t}\n\n\tG_FLOAT(OFS_RETURN) = 0.0;\t// not supported\n}\n\n\n\nvoid PF_Fixme (void)\n{\n\tPR_RunError (\"unimplemented bulitin\");\n}\n\n\n\nstatic builtin_t std_builtins[] =\n{\nPF_Fixme,\t\t//#0\nPF_makevectors,\t// void(entity e)\tmakevectors \t\t= #1;\nPF_setorigin,\t// void(entity e, vector o) setorigin\t= #2;\nPF_setmodel,\t// void(entity e, string m) setmodel\t= #3;\nPF_setsize,\t// void(entity e, vector min, vector max) setsize = #4;\nPF_Fixme,\t// void(entity e, vector min, vector max) setabssize = #5;\nPF_break,\t// void() break\t\t\t\t\t\t= #6;\nPF_random,\t// float() random\t\t\t\t\t\t= #7;\nPF_sound,\t// void(entity e, float chan, string samp) sound = #8;\nPF_normalize,\t// vector(vector v) normalize\t\t\t= #9;\nPF_error,\t// void(string e) error\t\t\t\t= #10;\nPF_objerror,\t// void(string e) objerror\t\t\t\t= #11;\nPF_vlen,\t// float(vector v) vlen\t\t\t\t= #12;\nPF_vectoyaw,\t// float(vector v) vectoyaw\t\t= #13;\nPF_Spawn,\t// entity() spawn\t\t\t\t\t\t= #14;\nPF_Remove,\t// void(entity e) remove\t\t\t\t= #15;\nPF_traceline,\t// float(vector v1, vector v2, float tryents) traceline = #16;\nPF_checkclient,\t// entity() clientlist\t\t\t\t\t= #17;\nPF_Find,\t// entity(entity start, .string fld, string match) find = #18;\nPF_precache_sound,\t// void(string s) precache_sound\t\t= #19;\nPF_precache_model,\t// void(string s) precache_model\t\t= #20;\nPF_stuffcmd,\t// void(entity client, string s)stuffcmd = #21;\nPF_findradius,\t// entity(vector org, float rad) findradius = #22;\nPF_bprint,\t// void(string s) bprint\t\t\t\t= #23;\nPF_sprint,\t// void(entity client, string s) sprint = #24;\nPF_dprint,\t// void(string s) dprint\t\t\t\t= #25;\nPF_ftos,\t// void(string s) ftos\t\t\t\t= #26;\nPF_vtos,\t// void(string s) vtos\t\t\t\t= #27;\nPF_coredump,\nPF_traceon,\nPF_traceoff,\t\t//#30\nPF_eprint,\t// void(entity e) debug print an entire entity\nPF_walkmove, // float(float yaw, float dist) walkmove\nPF_Fixme, // float(float yaw, float dist) walkmove\nPF_droptofloor,\nPF_lightstyle,\nPF_rint,\nPF_floor,\nPF_ceil,\nPF_Fixme,\nPF_checkbottom,\t\t//#40\nPF_pointcontents,\nPF_Fixme,\nPF_fabs,\nPF_aim,\nPF_cvar,\nPF_localcmd,\nPF_nextent,\nPF_particle,\nPF_changeyaw,\nPF_Fixme,\t\t//#50\nPF_vectoangles,\n\nPF_WriteByte,\nPF_WriteChar,\nPF_WriteShort,\nPF_WriteLong,\nPF_WriteCoord,\nPF_WriteAngle,\nPF_WriteString,\nPF_WriteEntity,\t\t//#59\n\nPF_Fixme,\nPF_Fixme,\nPF_Fixme,\nPF_Fixme,\nPF_Fixme,\nPF_Fixme,\nPF_Fixme,\n\nSV_MoveToGoal,\nPF_precache_file,\nPF_makestatic,\n\nPF_changelevel,\t\t//#70\nPF_Fixme,\n\nPF_cvar_set,\nPF_centerprint,\n\nPF_ambientsound,\n\nPF_precache_model,\nPF_precache_sound,\t\t// precache_sound2 is different only for qcc\nPF_precache_file,\n\nPF_setspawnparms,\n\nPF_logfrag,\n\nPF_infokey,\t\t//#80\nPF_stof,\nPF_multicast,\n};\n\n#define num_std_builtins (sizeof(std_builtins)/sizeof(std_builtins[0]))\n\nstatic struct { int num; builtin_t func; } ext_builtins[] =\n{\n{60, PF_sin},\t\t\t//float(float f) sin = #60;\n{61, PF_cos},\t\t\t//float(float f) cos = #61;\n{62, PF_sqrt},\t\t\t//float(float f) sqrt = #62;\n\n{84, PF_tokenize},\t\t// float(string s) tokenize\n{85, PF_argc},\t\t\t// float() argc\n{86, PF_argv},\t\t\t// string(float n) argv\n\n{90, PF_tracebox},\t\t// void (vector v1, vector mins, vector maxs, vector v2, float nomonsters, entity ignore) tracebox\n{91, PF_randomvec},\t\t// vector() randomvec\n////\n{93, PF_registercvar},\t// float(string name, string value) registercvar\n{94, PF_min},\t\t\t// float(float a, float b, ...) min\n{95, PF_max},\t\t\t// float(float a, float b, ...) max\n{96, PF_bound},\t\t\t// float(float min, float value, float max) bound\n{97, PF_pow},\t\t\t// float(float x, float y) pow\n////\n{99, PF_checkextension},// float(string name) checkextension\n////\n{103, PF_cvar_string},\t// string(string varname) cvar_string\n////\n{114, PF_strlen},\t\t// float(string s) strlen\n{115, PF_strcat},\t\t// string(string s1, string s2, ...) strcat\n{116, PF_substr},\t\t// string(string s, float start, float count) substr\n//{117, PF_stov},\t\t\t// vector(string s) stov\n{118, PF_strzone},\t\t// string(string s) strzone\n{119, PF_strunzone},\t// void(string s) strunzone\n{231, PF_calltimeofday},// void() calltimeofday\n{448, PF_cvar_string},\t// string(string varname) cvar_string\n{531, PF_setpause},\t\t//void(float pause) setpause\n{532, PF_precache_vwep_model},\t// float(string model) precache_vwep_model = #532;\n};\n\n#define num_ext_builtins (sizeof(ext_builtins)/sizeof(ext_builtins[0]))\n\nbuiltin_t *pr_builtins = NULL;\nint pr_numbuiltins = 0;\n\nvoid PR_InitBuiltins (void)\n{\n\tint i;\n\n\tif (pr_builtins)\n\t\treturn; // We don't need reinit it.\n\n\t// Free old array.\n\tQ_free (pr_builtins);\n\t// We have at least iD builtins.\n\tpr_numbuiltins = num_std_builtins;\n\t// Find highest builtin number to see how much space we actually need.\n\tfor (i = 0; i < num_ext_builtins; i++)\n\t\tpr_numbuiltins = max(ext_builtins[i].num + 1, pr_numbuiltins);\n\t// Allocate builtins array.\n\tpr_builtins = (builtin_t *) Q_malloc(pr_numbuiltins * sizeof(builtin_t));\n\t// Init new array to PF_Fixme().\n\tfor (i = 0; i < pr_numbuiltins; i++)\n\t\tpr_builtins[i] = PF_Fixme;\n\t// Copy iD builtins in new array.\n\tmemcpy (pr_builtins, std_builtins, num_std_builtins * sizeof(builtin_t));\n\t// Add QSG builtins or, probably, overwrite iD ones.\n\tfor (i = 0; i < num_ext_builtins; i++)\n\t{\n\t\tassert (ext_builtins[i].num >= 0);\n\t\tpr_builtins[ext_builtins[i].num] = ext_builtins[i].func;\n\t}\n}\n\n#endif // CLIENTONLY"
  },
  {
    "path": "src/pr_comp.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifndef __PR_COMP_H__\n#define __PR_COMP_H__\n\n// this file is shared by quake and qcc\n\ntypedef int func_t;\ntypedef int string_t;\ntypedef intptr_t stringptr_t;\n\ntypedef enum {ev_void, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_pointer} etype_t;\n\n\n#define\tOFS_NULL\t\t0\n#define\tOFS_RETURN\t\t1\n#define\tOFS_PARM0\t\t4\t\t// leave 3 ofs for each parm to hold vectors\n#define\tOFS_PARM1\t\t7\n#define\tOFS_PARM2\t\t10\n#define\tOFS_PARM3\t\t13\n#define\tOFS_PARM4\t\t16\n#define\tOFS_PARM5\t\t19\n#define\tOFS_PARM6\t\t22\n#define\tOFS_PARM7\t\t25\n#define\tRESERVED_OFS\t28\n\n\nenum {\n\tOP_DONE,\n\tOP_MUL_F,\n\tOP_MUL_V,\n\tOP_MUL_FV,\n\tOP_MUL_VF,\n\tOP_DIV_F,\n\tOP_ADD_F,\n\tOP_ADD_V,\n\tOP_SUB_F,\n\tOP_SUB_V,\n\t\n\tOP_EQ_F,\n\tOP_EQ_V,\n\tOP_EQ_S,\n\tOP_EQ_E,\n\tOP_EQ_FNC,\n\t\n\tOP_NE_F,\n\tOP_NE_V,\n\tOP_NE_S,\n\tOP_NE_E,\n\tOP_NE_FNC,\n\t\n\tOP_LE,\n\tOP_GE,\n\tOP_LT,\n\tOP_GT,\n\n\tOP_LOAD_F,\n\tOP_LOAD_V,\n\tOP_LOAD_S,\n\tOP_LOAD_ENT,\n\tOP_LOAD_FLD,\n\tOP_LOAD_FNC,\n\n\tOP_ADDRESS,\n\n\tOP_STORE_F,\n\tOP_STORE_V,\n\tOP_STORE_S,\n\tOP_STORE_ENT,\n\tOP_STORE_FLD,\n\tOP_STORE_FNC,\n\n\tOP_STOREP_F,\n\tOP_STOREP_V,\n\tOP_STOREP_S,\n\tOP_STOREP_ENT,\n\tOP_STOREP_FLD,\n\tOP_STOREP_FNC,\n\n\tOP_RETURN,\n\tOP_NOT_F,\n\tOP_NOT_V,\n\tOP_NOT_S,\n\tOP_NOT_ENT,\n\tOP_NOT_FNC,\n\tOP_IF,\n\tOP_IFNOT,\n\tOP_CALL0,\n\tOP_CALL1,\n\tOP_CALL2,\n\tOP_CALL3,\n\tOP_CALL4,\n\tOP_CALL5,\n\tOP_CALL6,\n\tOP_CALL7,\n\tOP_CALL8,\n\tOP_STATE,\n\tOP_GOTO,\n\tOP_AND,\n\tOP_OR,\n\t\n\tOP_BITAND,\n\tOP_BITOR\n};\n\n\ntypedef struct statement_s\n{\n\tunsigned short\top;\n\tshort\ta,b,c;\n} dstatement_t;\n\ntypedef struct\n{\n\tunsigned short\ttype;\t\t// if DEF_SAVEGLOBGAL bit is set\n\t\t\t\t\t\t\t\t// the variable needs to be saved in savegames\n\tunsigned short\tofs;\n\tint\t\t\ts_name;\n} ddef_t;\n#define\tDEF_SAVEGLOBAL\t(1<<15)\n\n#define\tMAX_PARMS\t8\n\ntypedef struct\n{\n\tint\t\tfirst_statement;\t// negative numbers are builtins\n\tint\t\tparm_start;\n\tint\t\tlocals;\t\t\t\t// total ints of parms + locals\n\t\n\tint\t\tprofile;\t\t// runtime\n\t\n\tint\t\ts_name;\n\tint\t\ts_file;\t\t\t// source file defined in\n\t\n\tint\t\tnumparms;\n\tbyte\tparm_size[MAX_PARMS];\n} dfunction_t;\n\n\n#define\tPROG_VERSION\t6\ntypedef struct\n{\n\tint\t\tversion;\n\tint\t\tcrc;\t\t\t// check of header file\n\t\n\tint\t\tofs_statements;\n\tint\t\tnumstatements;\t// statement 0 is an error\n\n\tint\t\tofs_globaldefs;\n\tint\t\tnumglobaldefs;\n\t\n\tint\t\tofs_fielddefs;\n\tint\t\tnumfielddefs;\n\t\n\tint\t\tofs_functions;\n\tint\t\tnumfunctions;\t// function 0 is an empty\n\t\n\tint\t\tofs_strings;\n\tint\t\tnumstrings;\t\t// first string is a null string\n\n\tint\t\tofs_globals;\n\tint\t\tnumglobals;\n\t\n\tint\t\tentityfields;\n} dprograms_t;\n\n#endif /* !__PR_COMP_H__ */\n"
  },
  {
    "path": "src/pr_edict.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n// sv_edict.c -- entity dictionary\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\ndprograms_t\t\t*progs;\ndfunction_t\t\t*pr_functions;\nchar\t\t\t*pr_strings;\nddef_t\t\t\t*pr_fielddefs;\nddef_t\t\t\t*pr_globaldefs;\ndstatement_t\t*pr_statements;\nglobalvars_t\t*pr_global_struct;\nfloat\t\t\t*pr_globals;\t\t\t// same as pr_global_struct\nint\t\t\t\tpr_edict_size;\t// in bytes\n\n#define NQ_PROGHEADER_CRC 5927\n\n#ifdef WITH_NQPROGS\nqbool pr_nqprogs;\nint pr_fieldoffsetpatch[106];\nint pr_globaloffsetpatch[62];\nstatic int pr_globaloffsetpatch_nq[62] = {0,0,0,0,0,666,-4,-4,8,8,\n8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8, \n8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8, 8,8};\n#endif\n\nstatic int type_size[8] =\n{\n\t1,                  // void\n\t1,                  // string_t\n\t1,                  // float\n\t3,                  // vector\n\t1,                  // entity\n\t1,                  // field\n\t1,                  // func_t\n\t1                   // pointer (its an int index)\n};\n\nddef_t *ED_FieldAtOfs (int ofs);\nqbool ED_ParseEpair (void *base, ddef_t *key, char *s);\n\n#define\tMAX_FIELD_LEN\t64\n#define GEFV_CACHESIZE\t2\n\ntypedef struct\n{\n\tddef_t\t*pcache;\n\tchar\tfield[MAX_FIELD_LEN];\n}\ngefv_cache;\n\nstatic gefv_cache\tgefvCache[GEFV_CACHESIZE] = {{NULL, \"\"}, {NULL, \"\"}};\n\nfunc_t mod_SpectatorConnect, mod_SpectatorThink, mod_SpectatorDisconnect;\nfunc_t GE_ClientCommand, GE_PausedTic, GE_ShouldPause;\n\nfunc_t mod_ConsoleCmd, mod_UserCmd;\nfunc_t mod_UserInfo_Changed, mod_localinfoChanged;\nfunc_t mod_ChatMessage;\n\ncvar_t\tsv_progsname = {\"sv_progsname\", \"qwprogs\"};\n#ifdef WITH_NQPROGS\ncvar_t  sv_forcenqprogs = {\"sv_forcenqprogs\", \"0\"};\n#endif\n\n/*\n=================\nED_ClearEdict\n \nSets everything to NULL\n=================\n*/\nvoid ED_ClearEdict (edict_t *e)\n{\n\tmemset(e->v, 0, pr_edict_size);\n\tmemset(&e->xv, 0, sizeof(ext_entvars_t));\n\te->e.lastruntime = 0;\n\te->e.free = false;\n\tPR_ClearEdict(e);\n}\n\n/*\n=================\nED_Alloc\n \nEither finds a free edict, or allocates a new one.\nTry to avoid reusing an entity that was recently freed, because it\ncan cause the client to think the entity morphed into something else\ninstead of being removed and recreated, which can cause interpolated\nangles and bad trails.\n=================\n*/\nedict_t *ED_Alloc (void)\n{\n\tint\t\t\ti;\n\tedict_t\t\t*e;\n\n\tfor (i = MAX_CLIENTS + 1; i < sv.num_edicts; i++)\n\t{\n\t\te = EDICT_NUM(i);\n\t\t// the first couple seconds of server time can involve a lot of\n\t\t// freeing and allocating, so relax the replacement policy\n\t\tif (e->e.free && (e->e.freetime < 2 || sv.time - e->e.freetime > 0.5))\n\t\t{\n\t\t\tED_ClearEdict(e);\n\t\t\treturn e;\n\t\t}\n\t}\n\n\tif (i == sv.max_edicts)\n\t{\n\t\tCon_Printf (\"WARNING: ED_Alloc: no free edicts [%d]\\n\", sv.max_edicts);\n\t\ti--;\t// step on whatever is the last edict\n\t\te = EDICT_NUM(i);\n\t\tSV_UnlinkEdict(e);\n\t}\n\telse\n\t{\n\t\tsv.num_edicts++;\n\t\te = EDICT_NUM(i);\n\t}\n\n\tED_ClearEdict(e);\n\n\treturn e;\n}\n\n/*\n=================\nED_Free\n \nMarks the edict as free\nFIXME: walk all entities and NULL out references to this entity\n=================\n*/\nvoid ED_Free (edict_t *ed)\n{\n\tSV_UnlinkEdict (ed);\t\t// unlink from world bsp\n\tmemset(&ed->xv, 0, sizeof(ext_entvars_t));\n\ted->e.free = true;\n\ted->v->model = 0;\n\ted->v->takedamage = 0;\n\ted->v->modelindex = 0;\n\ted->v->colormap = 0;\n\ted->v->skin = 0;\n\ted->v->frame = 0;\n\ted->v->health = 0;\n\ted->v->classname = 0;\n\tVectorClear (ed->v->origin);\n\tVectorClear (ed->v->angles);\n\ted->v->nextthink = -1;\n\ted->v->solid = 0;\n\n\ted->e.freetime = sv.time;\n}\n\n//===========================================================================\n\n/*\n============\nED_GlobalAtOfs\n============\n*/\nddef_t *ED_GlobalAtOfs (int ofs)\n{\n\tddef_t\t\t*def;\n\tint\t\t\ti;\n\n\tfor (i=0 ; i<progs->numglobaldefs ; i++)\n\t{\n\t\tdef = &pr_globaldefs[i];\n\t\tif (def->ofs == ofs)\n\t\t\treturn def;\n\t}\n\treturn NULL;\n}\n\n/*\n============\nED_FieldAtOfs\n============\n*/\nddef_t *ED_FieldAtOfs (int ofs)\n{\n\tddef_t\t\t*def;\n\tint\t\t\ti;\n\n\tfor (i=0 ; i<progs->numfielddefs ; i++)\n\t{\n\t\tdef = &pr_fielddefs[i];\n\t\tif (def->ofs == ofs)\n\t\t\treturn def;\n\t}\n\treturn NULL;\n}\n\n/*\n============\nED_FindField\n============\n*/\nddef_t *ED_FindField (char *name)\n{\n\tddef_t\t\t*def;\n\tint\t\t\ti;\n\n\tfor (i=0 ; i<progs->numfielddefs ; i++)\n\t{\n\t\tdef = &pr_fielddefs[i];\n\t\tif (!strcmp(PR1_GetString(def->s_name),name) )\n\t\t\treturn def;\n\t}\n\treturn NULL;\n}\n\n\n/*\n============\nED_FindGlobal\n============\n*/\nddef_t *ED_FindGlobal (char *name)\n{\n\tddef_t\t\t*def;\n\tint\t\t\ti;\n\n\tfor (i=0 ; i<progs->numglobaldefs ; i++)\n\t{\n\t\tdef = &pr_globaldefs[i];\n\t\tif (!strcmp(PR1_GetString(def->s_name),name) )\n\t\t\treturn def;\n\t}\n\treturn NULL;\n}\n\n/*\n============\nED1_FindFieldOffset\n============\n*/\nint ED1_FindFieldOffset (char *field)\n{\n\tddef_t *d;\n\td = ED_FindField(field);\n\tif (!d)\n\t\treturn 0;\n\treturn d->ofs*4;\n}\n\n/*\n============\nED_FindFunction\n============\n*/\ndfunction_t *ED_FindFunction (char *name)\n{\n\tregister dfunction_t\t\t*func;\n\tregister int\t\t\t\ti;\n\n\tif (!progs)\n\t\treturn NULL;\n\n\tfor (i=0 ; i<progs->numfunctions ; i++)\n\t{\n\t\tfunc = &pr_functions[i];\n\t\tif (!strcmp(PR1_GetString(func->s_name), name))\n\t\t\treturn func;\n\t}\n\treturn NULL;\n}\n\nfunc_t ED_FindFunctionOffset (char *name)\n{\n\tdfunction_t *func;\n\n\tfunc = ED_FindFunction (name);\n\treturn func ? (func_t)(func - pr_functions) : 0;\n}\n\neval_t *PR1_GetEdictFieldValue(edict_t *ed, char *field)\n{\n\tddef_t\t\t\t*def = NULL;\n\tint\t\t\t\ti;\n\tstatic int\t\trep = 0;\n\n\tfor (i=0 ; i<GEFV_CACHESIZE ; i++)\n\t{\n\t\tif (!strcmp(field, gefvCache[i].field))\n\t\t{\n\t\t\tdef = gefvCache[i].pcache;\n\t\t\tgoto Done;\n\t\t}\n\t}\n\n\tdef = ED_FindField (field);\n\n\tif (strlen(field) < MAX_FIELD_LEN)\n\t{\n\t\tgefvCache[rep].pcache = def;\n\t\tstrlcpy (gefvCache[rep].field, field, MAX_FIELD_LEN);\n\t\trep ^= 1;\n\t}\n\nDone:\n\tif (!def)\n\t\treturn NULL;\n\n\treturn (eval_t *)((char *)ed->v + def->ofs*4);\n}\n\n/*\n============\nPR_ValueString\n \nReturns a string describing *data in a type specific manner\n=============\n*/\nchar *PR_ValueString (etype_t type, eval_t *val)\n{\n\tstatic char\tline[256];\n\tddef_t\t\t*def;\n\tdfunction_t\t*f;\n\n\ttype = (etype_t) (type & ~DEF_SAVEGLOBAL);\n\n\tswitch (type)\n\t{\n\tcase ev_string:\n\t\tsnprintf (line, sizeof(line), \"%s\", PR1_GetString(val->string));\n\t\tbreak;\n\tcase ev_entity:\n\t\tsnprintf (line, sizeof(line), \"entity %i\", NUM_FOR_EDICT(PROG_TO_EDICT(val->edict)) );\n\t\tbreak;\n\tcase ev_function:\n\t\tf = pr_functions + val->function;\n\t\tsnprintf (line, sizeof(line), \"%s()\", PR1_GetString(f->s_name));\n\t\tbreak;\n\tcase ev_field:\n\t\tdef = ED_FieldAtOfs ( val->_int );\n\t\tsnprintf (line, sizeof(line), \".%s\", PR1_GetString(def->s_name));\n\t\tbreak;\n\tcase ev_void:\n\t\tsnprintf (line, sizeof(line), \"void\");\n\t\tbreak;\n\tcase ev_float:\n\t\tsnprintf (line, sizeof(line), \"%5.1f\", val->_float);\n\t\tbreak;\n\tcase ev_vector:\n\t\tsnprintf (line, sizeof(line), \"'%5.1f %5.1f %5.1f'\", val->vector[0], val->vector[1], val->vector[2]);\n\t\tbreak;\n\tcase ev_pointer:\n\t\tsnprintf (line, sizeof(line), \"pointer\");\n\t\tbreak;\n\tdefault:\n\t\tsnprintf (line, sizeof(line), \"bad type %i\", type);\n\t\tbreak;\n\t}\n\n\treturn line;\n}\n\n/*\n============\nPR_UglyValueString\n \nReturns a string describing *data in a type specific manner\nEasier to parse than PR_ValueString\n=============\n*/\nchar *PR_UglyValueString (etype_t type, eval_t *val)\n{\n\tstatic char\tline[256];\n\tddef_t\t\t*def;\n\tdfunction_t\t*f;\n\n\ttype = (etype_t) (type & ~DEF_SAVEGLOBAL);\n\n\tswitch (type)\n\t{\n\tcase ev_string:\n\t\tsnprintf (line, sizeof(line), \"%s\", PR1_GetString(val->string));\n\t\tbreak;\n\tcase ev_entity:\n\t\tsnprintf (line, sizeof(line), \"%i\", NUM_FOR_EDICT(PROG_TO_EDICT(val->edict)));\n\t\tbreak;\n\tcase ev_function:\n\t\tf = pr_functions + val->function;\n\t\tsnprintf (line, sizeof(line), \"%s\", PR1_GetString(f->s_name));\n\t\tbreak;\n\tcase ev_field:\n\t\tdef = ED_FieldAtOfs ( val->_int );\n\t\tsnprintf (line, sizeof(line), \"%s\", PR1_GetString(def->s_name));\n\t\tbreak;\n\tcase ev_void:\n\t\tsnprintf (line, sizeof(line), \"void\");\n\t\tbreak;\n\tcase ev_float:\n\t\tsnprintf (line, sizeof(line), \"%f\", val->_float);\n\t\tbreak;\n\tcase ev_vector:\n\t\tsnprintf (line, sizeof(line), \"%f %f %f\", val->vector[0], val->vector[1], val->vector[2]);\n\t\tbreak;\n\tdefault:\n\t\tsnprintf (line, sizeof(line), \"bad type %i\", type);\n\t\tbreak;\n\t}\n\n\treturn line;\n}\n\n/*\n============\nPR_GlobalString\n \nReturns a string with a description and the contents of a global,\npadded to 20 field width\n============\n*/\nchar *PR_GlobalString (int ofs)\n{\n\tchar\t*s;\n\tint\t\ti;\n\tddef_t\t*def;\n\tvoid\t*val;\n\tstatic char\tline[128];\n\n\tval = (void *)&pr_globals[ofs];\n\tdef = ED_GlobalAtOfs(ofs);\n\tif (!def)\n\t{\n\t\tsnprintf (line, sizeof(line), \"%i(?\"\"?\"\"?)\", ofs); // separate the ?'s to shut up gcc\n\t}\n\telse\n\t{\n\t\ts = PR_ValueString ((etype_t)def->type, (eval_t *) val);\n\t\tsnprintf (line, sizeof(line), \"%i(%s)%s\", ofs, PR1_GetString(def->s_name), s);\n\t}\n\n\ti = strlen(line);\n\tfor ( ; i<20 ; i++)\n\t\tstrlcat (line, \" \", sizeof(line));\n\tstrlcat (line, \" \", sizeof(line));\n\n\treturn line;\n}\n\nchar *PR_GlobalStringNoContents (int ofs)\n{\n\tint\t\ti;\n\tddef_t\t*def;\n\tstatic char\tline[128];\n\n\tdef = ED_GlobalAtOfs(ofs);\n\tif (!def)\n\t\tsnprintf (line, sizeof(line), \"%i(?\"\"?\"\"?)\", ofs); // separate the ?'s to shut up gcc\n\telse\n\t\tsnprintf (line, sizeof(line), \"%i(%s)\", ofs, PR1_GetString(def->s_name));\n\n\ti = strlen(line);\n\tfor ( ; i<20 ; i++)\n\t\tstrlcat (line, \" \", sizeof(line));\n\tstrlcat (line, \" \", sizeof(line));\n\n\treturn line;\n}\n\n\n/*\n=============\nED_Print\n \nFor debugging\n=============\n*/\nvoid ED_Print (edict_t *ed)\n{\n\tint\t\tl;\n\tddef_t\t*d;\n\tint\t\t*v;\n\tint\t\ti, j;\n\tchar\t*name;\n\tint\t\ttype;\n\n\tif (ed->e.free)\n\t{\n\t\tCon_Printf (\"FREE\\n\");\n\t\treturn;\n\t}\n\n\tfor (i=1 ; i<progs->numfielddefs ; i++)\n\t{\n\t\td = &pr_fielddefs[i];\n\t\tname = PR1_GetString(d->s_name);\n\t\tif (name[strlen(name)-2] == '_')\n\t\t\tcontinue;\t// skip _x, _y, _z vars\n\n\t\tv = (int *)((char *)ed->v + d->ofs*4);\n\n\t\t// if the value is still all 0, skip the field\n\t\ttype = d->type & ~DEF_SAVEGLOBAL;\n\n\t\tfor (j = 0; j < type_size[type]; j++) {\n\t\t\tif (v[j]) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (j == type_size[type]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tCon_Printf (\"%s\",name);\n\t\tl = strlen (name);\n\t\twhile (l++ < 15) {\n\t\t\tCon_Printf(\" \");\n\t\t}\n\n\t\tCon_Printf (\"%s\\n\", PR_ValueString((etype_t)d->type, (eval_t *)v));\n\t}\n}\n\n/*\n=============\nED_Write\n \nFor savegames\n=============\n*/\nvoid ED_Write (FILE *f, edict_t *ed)\n{\n\tddef_t\t*d;\n\tint\t\t*v;\n\tint\t\ti, j;\n\tchar\t*name;\n\tint\t\ttype;\n\n\tfprintf (f, \"{\\n\");\n\n\tif (ed->e.free)\n\t{\n\t\tfprintf (f, \"}\\n\");\n\t\treturn;\n\t}\n\n\tfor (i=1 ; i<progs->numfielddefs ; i++)\n\t{\n\t\td = &pr_fielddefs[i];\n\t\tname = PR1_GetString(d->s_name);\n\t\tif (name[strlen(name)-2] == '_')\n\t\t\tcontinue;\t// skip _x, _y, _z vars\n\n\t\tv = (int *)((char *)ed->v + d->ofs*4);\n\n\t\t// if the value is still all 0, skip the field\n\t\ttype = d->type & ~DEF_SAVEGLOBAL;\n\t\tfor (j=0 ; j<type_size[type] ; j++)\n\t\t\tif (v[j])\n\t\t\t\tbreak;\n\t\tif (j == type_size[type])\n\t\t\tcontinue;\n\n\t\tfprintf (f,\"\\\"%s\\\" \",name);\n\t\tfprintf (f,\"\\\"%s\\\"\\n\", PR_UglyValueString((etype_t)d->type, (eval_t *)v));\n\t}\n\n\tfprintf (f, \"}\\n\");\n}\n\nvoid ED_PrintNum (int ent)\n{\n\tED_Print (EDICT_NUM(ent));\n}\n\n/*\n=============\nED_PrintEdicts\n \nFor debugging, prints all the entities in the current server\n=============\n*/\nvoid ED_PrintEdicts (void)\n{\n\tint\t\ti;\n\n\tCon_Printf (\"%i entities\\n\", sv.num_edicts);\n\tfor (i=0 ; i<sv.num_edicts ; i++)\n\t{\n\t\tCon_Printf (\"\\nEDICT %i:\\n\",i);\n\t\tED_PrintNum (i);\n\t}\n}\n\n/*\n=============\nED_PrintEdict_f\n \nFor debugging, prints a single edicy\n=============\n*/\nvoid ED_PrintEdict_f (void)\n{\n\tint\t\ti;\n\n\tif (Cmd_Argc () != 2)\n\t{\n\t\tCon_Printf (\"\\nUsage:\\nedict [num]\\n\");\n\t\treturn;\n\t}\n\n\ti = Q_atoi (Cmd_Argv(1));\n\tif(i < 0 || i >= sv.num_edicts)\n\t{\n\t\tCon_Printf (\"\\nNo such edict: %i\\n\", i);\n\t\treturn;\n\t}\n\t\n\tCon_Printf (\"\\n EDICT %i:\\n\",i);\n\tED_PrintNum (i);\n}\n\n/*\n=============\nED_Count\n \nFor debugging\n=============\n*/\nvoid ED_Count (void)\n{\n\tint\t\ti;\n\tedict_t\t*ent;\n\tint\t\tactive, models, solid, step;\n\n\tactive = models = solid = step = 0;\n\tfor (i=0 ; i<sv.num_edicts ; i++)\n\t{\n\t\tent = EDICT_NUM(i);\n\t\tif (ent->e.free)\n\t\t\tcontinue;\n\t\tactive++;\n\t\tif (ent->v->solid)\n\t\t\tsolid++;\n\t\tif (ent->v->model)\n\t\t\tmodels++;\n\t\tif (ent->v->movetype == MOVETYPE_STEP)\n\t\t\tstep++;\n\t}\n\n\tCon_Printf (\"num_edicts:%3i\\n\", sv.num_edicts);\n\tCon_Printf (\"active    :%3i\\n\", active);\n\tCon_Printf (\"view      :%3i\\n\", models);\n\tCon_Printf (\"touch     :%3i\\n\", solid);\n\tCon_Printf (\"step      :%3i\\n\", step);\n\n}\n\n/*\n==============================================================================\n \n\t\t\t\t\tARCHIVING GLOBALS\n \nFIXME: need to tag constants, doesn't really work\n==============================================================================\n*/\n\n/*\n=============\nED_WriteGlobals\n=============\n*/\nvoid ED_WriteGlobals (FILE *f)\n{\n\tddef_t\t\t*def;\n\tint\t\t\ti;\n\tchar\t\t*name;\n\tint\t\t\ttype;\n\n\tfprintf (f,\"{\\n\");\n\tfor (i=0 ; i<progs->numglobaldefs ; i++)\n\t{\n\t\tdef = &pr_globaldefs[i];\n\t\ttype = def->type;\n\t\tif ( !(def->type & DEF_SAVEGLOBAL) )\n\t\t\tcontinue;\n\t\ttype &= ~DEF_SAVEGLOBAL;\n\n\t\tif (type != ev_string\n\t\t        && type != ev_float\n\t\t        && type != ev_entity)\n\t\t\tcontinue;\n\n\t\tname = PR1_GetString(def->s_name);\n\t\tfprintf (f,\"\\\"%s\\\" \", name);\n\t\tfprintf (f,\"\\\"%s\\\"\\n\", PR_UglyValueString((etype_t)type, (eval_t *)&pr_globals[def->ofs]));\n\t}\n\tfprintf (f,\"}\\n\");\n}\n\n/*\n=============\nED_ParseGlobals\n=============\n*/\nvoid ED_ParseGlobals (const char *data)\n{\n\tchar\tkeyname[64];\n\tddef_t\t*key;\n\n\twhile (1)\n\t{\n\t\t// parse key\n\t\tdata = COM_Parse (data);\n\t\tif (com_token[0] == '}')\n\t\t\tbreak;\n\t\tif (!data)\n\t\t\tSV_Error (\"ED_ParseEntity: EOF without closing brace\");\n\n\t\tstrlcpy (keyname, com_token, sizeof(keyname));\n\n\t\t// parse value\n\t\tdata = COM_Parse (data);\n\t\tif (!data)\n\t\t\tSV_Error (\"ED_ParseEntity: EOF without closing brace\");\n\n\t\tif (com_token[0] == '}')\n\t\t\tSV_Error (\"ED_ParseEntity: closing brace without data\");\n\n\t\tkey = ED_FindGlobal (keyname);\n\t\tif (!key)\n\t\t{\n\t\t\tCon_Printf (\"%s is not a global\\n\", keyname);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!ED_ParseEpair ((void *)pr_globals, key, com_token))\n\t\t\tSV_Error (\"ED_ParseGlobals: parse error\");\n\t}\n}\n\n//============================================================================\n\n\n/*\n=============\nED_NewString\n=============\n*/\nchar *ED_NewString (char *string)\n{\n\tchar\t*nuw, *new_p;\n\tint\t\ti,l;\n\n\tl = strlen(string) + 1;\n\tnuw = (char *) Hunk_AllocName (l, \"edstring\");\n\tnew_p = nuw;\n\n\tfor (i=0 ; i< l ; i++)\n\t{\n\t\tif (string[i] == '\\\\' && i < l-1)\n\t\t{\n\t\t\ti++;\n\t\t\tif (string[i] == 'n')\n\t\t\t\t*new_p++ = '\\n';\n\t\t\telse\n\t\t\t\t*new_p++ = '\\\\';\n\t\t}\n\t\telse\n\t\t\t*new_p++ = string[i];\n\t}\n\n\treturn nuw;\n}\n\n\n/*\n=============\nED_ParseEval\n \nCan parse either fields or globals\nreturns false if error\n=============\n*/\nqbool ED_ParseEpair (void *base, ddef_t *key, char *s)\n{\n\tint\t\ti;\n\tchar\tstring[128];\n\tddef_t\t*def;\n\tchar\t*v, *w;\n\tvoid\t*d;\n\tdfunction_t\t*func;\n\n\td = (void *)((int *)base + key->ofs);\n\n\tswitch (key->type & ~DEF_SAVEGLOBAL)\n\t{\n\tcase ev_string:\n\t\tPR1_SetString((string_t *)d, ED_NewString (s));\n\t\tbreak;\n\n\tcase ev_float:\n\t\t*(float *)d = Q_atof (s);\n\t\tbreak;\n\n\tcase ev_vector:\n\t\tstrlcpy (string, s, sizeof(string));\n\t\tv = string;\n\t\tw = string;\n\t\tfor (i=0 ; i<3 ; i++)\n\t\t{\n\t\t\twhile (*v && *v != ' ')\n\t\t\t\tv++;\n\t\t\t*v = 0;\n\t\t\t((float *)d)[i] = Q_atof (w);\n\t\t\tw = v = v+1;\n\t\t}\n\t\tbreak;\n\n\tcase ev_entity:\n\t\t*(int *)d = EDICT_TO_PROG(EDICT_NUM(Q_atoi (s)));\n\t\tbreak;\n\n\tcase ev_field:\n\t\tdef = ED_FindField (s);\n\t\tif (!def)\n\t\t{\n\t\t\tCon_Printf (\"Can't find field %s\\n\", s);\n\t\t\treturn false;\n\t\t}\n\t\t*(int *)d = G_INT(def->ofs);\n\t\tbreak;\n\n\tcase ev_function:\n\t\tfunc = ED_FindFunction (s);\n\t\tif (!func)\n\t\t{\n\t\t\tCon_Printf (\"Can't find function %s\\n\", s);\n\t\t\treturn false;\n\t\t}\n\t\t*(func_t *)d = func - pr_functions;\n\t\tbreak;\n\n\tdefault:\n\t\tbreak;\n\t}\n\treturn true;\n}\n\n/*\n====================\nED_ParseEdict\n \nParses an edict out of the given string, returning the new position\ned should be a properly initialized empty edict.\nUsed for initial level load and for savegames.\n====================\n*/\nconst char *ED_ParseEdict (const char *data, edict_t *ent)\n{\n\tddef_t\t\t*key;\n\tqbool\t\tanglehack;\n\tqbool\t\tinit;\n\tchar\t\tkeyname[256];\n\n\tinit = false;\n\n\t// go through all the dictionary pairs\n\twhile (1)\n\t{\n\t\t// parse key\n\t\tdata = COM_Parse (data);\n\t\tif (com_token[0] == '}')\n\t\t\tbreak;\n\t\tif (!data)\n\t\t\tSV_Error (\"ED_ParseEntity: EOF without closing brace\");\n\n\t\t// anglehack is to allow QuakeEd to write single scalar angles\n\t\t// and allow them to be turned into vectors. (FIXME...)\n\t\tif (!strcmp(com_token, \"angle\"))\n\t\t{\n\t\t\tstrlcpy (com_token, \"angles\", MAX_COM_TOKEN);\n\t\t\tanglehack = true;\n\t\t}\n\t\telse\n\t\t\tanglehack = false;\n\n\t\t// FIXME: change light to _light to get rid of this hack\n\t\tif (!strcmp(com_token, \"light\"))\n\t\t\tstrlcpy (com_token, \"light_lev\", MAX_COM_TOKEN);\t// hack for single light def\n\n\t\tstrlcpy (keyname, com_token, sizeof(keyname));\n\n\t\t// parse value\n\t\tdata = COM_Parse (data);\n\t\tif (!data)\n\t\t\tSV_Error (\"ED_ParseEntity: EOF without closing brace\");\n\n\t\tif (com_token[0] == '}')\n\t\t\tSV_Error (\"ED_ParseEntity: closing brace without data\");\n\n\t\tinit = true;\n\n\t\t// keynames with a leading underscore are used for utility comments,\n\t\t// and are immediately discarded by quake\n\t\tif (keyname[0] == '_')\n\t\t\tcontinue;\n\n\t\tif (!strcmp (keyname, \"alpha\"))\n\t\t{\n\t\t\tent->xv.alpha = bound(0.0f, atof (com_token), 1.0f);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!strcmp(keyname, \"colormod\"))\n\t\t{\n\t\t\tfloat v[3];\n\t\t\tint ret = sscanf(com_token, \"%f %f %f\", &v[0], &v[1], &v[2]);\n\t\t\tif (ret == 3 && v[0] > 0.0f && v[1] > 0.0f && v[2] > 0.0f)\n\t\t\t{\n\t\t\t\tent->xv.colourmod[0] = max(0.0f, v[0]);\n\t\t\t\tent->xv.colourmod[1] = max(0.0f, v[1]);\n\t\t\t\tent->xv.colourmod[2] = max(0.0f, v[2]);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tkey = ED_FindField (keyname);\n\t\tif (!key)\n\t\t{\n\t\t\tCon_Printf (\"%s is not a field\\n\", keyname);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (anglehack)\n\t\t{\n\t\t\tchar\ttemp[32];\n\t\t\tstrlcpy (temp, com_token, sizeof(temp));\n\t\t\tsnprintf (com_token, MAX_COM_TOKEN, \"0 %s 0\", temp);\n\t\t}\n\n\t\tif (!ED_ParseEpair ((void *)ent->v, key, com_token))\n\t\t\tSV_Error (\"ED_ParseEdict: parse error\");\n\t}\n\n\tif (!init)\n\t\tent->e.free = true;\n\n\treturn data;\n}\n\n\n/*\n================\nED_LoadFromFile\n \nThe entities are directly placed in the array, rather than allocated with\nED_Alloc, because otherwise an error loading the map would have entity\nnumber references out of order.\n \nCreates a server's entity / program execution context by\nparsing textual entity definitions out of an ent file.\n \nUsed for both fresh maps and savegame loads.  A fresh map would also need\nto call ED_CallSpawnFunctions () to let the objects initialize themselves.\n================\n*/\nvoid ED_LoadFromFile (const char *data)\n{\n\tedict_t\t\t*ent;\n\tint\t\t\tinhibit;\n\tdfunction_t\t*func;\n\n\tent = NULL;\n\tinhibit = 0;\n\tpr_global_struct->time = sv.time;\n\n\t// parse ents\n\twhile (1)\n\t{\n\t\t// parse the opening brace\n\t\tdata = COM_Parse (data);\n\t\tif (!data)\n\t\t\tbreak;\n\t\tif (com_token[0] != '{')\n\t\t\tSV_Error (\"ED_LoadFromFile: found %s when expecting {\",com_token);\n\n\t\tif (!ent)\n\t\t\tent = EDICT_NUM(0);\n\t\telse\n\t\t\tent = ED_Alloc ();\n\t\tdata = ED_ParseEdict (data, ent);\n\n\t\t// remove things from different skill levels or deathmatch\n\t\tif ((int)deathmatch.value)\n\t\t{\n\t\t\tif (((int)ent->v->spawnflags & SPAWNFLAG_NOT_DEATHMATCH))\n\t\t\t{\n\t\t\t\tED_Free (ent);\n\t\t\t\tinhibit++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\telse if ((current_skill == 0 && ((int)ent->v->spawnflags & SPAWNFLAG_NOT_EASY))\n\t\t         || (current_skill == 1 && ((int)ent->v->spawnflags & SPAWNFLAG_NOT_MEDIUM))\n\t\t         || (current_skill >= 2 && ((int)ent->v->spawnflags & SPAWNFLAG_NOT_HARD)) )\n\t\t{\n\t\t\tED_Free (ent);\n\t\t\tinhibit++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t//\n\t\t// immediately call spawn function\n\t\t//\n\t\tif (!ent->v->classname)\n\t\t{\n\t\t\tCon_Printf (\"No classname for:\\n\");\n\t\t\tED_Print (ent);\n\t\t\tED_Free (ent);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// look for the spawn function\n\t\tfunc = ED_FindFunction ( PR1_GetString(ent->v->classname) );\n\n\t\tif (!func)\n\t\t{\n\t\t\tCon_Printf (\"No spawn function for:\\n\");\n\t\t\tED_Print (ent);\n\t\t\tED_Free (ent);\n\t\t\tcontinue;\n\t\t}\n\n\t\tpr_global_struct->self = EDICT_TO_PROG(ent);\n\t\tPR_ExecuteProgram (func - pr_functions);\n\t\tSV_FlushSignon();\n\t}\n\n\tCon_DPrintf (\"%i entities inhibited\\n\", inhibit);\n}\nextern redirect_t\tsv_redirected;\nqbool PR_ConsoleCmd(void)\n{\n\tif (mod_ConsoleCmd)\n\t{\n\t\tif (sv_redirected != RD_MOD)\n\t\t{\n\t\t\tpr_global_struct->time = sv.time;\n\t\t\tpr_global_struct->self = 0;\n\t\t}\n\t\tPR_ExecuteProgram (mod_ConsoleCmd);\n\t\treturn (int) G_FLOAT(OFS_RETURN);\n\t}\n\n\treturn false;\n}\n\nqbool PR1_ClientCmd(void)\n{\n\t// ZQ_CLIENTCOMMAND extension\n\tif (GE_ClientCommand)\n\t{\n\t\tstatic char cmd_copy[128], args_copy[1024] /* Ouch! */;\n\t\tstrlcpy (cmd_copy, Cmd_Argv(0), sizeof(cmd_copy));\n\t\tstrlcpy (args_copy, Cmd_Args(), sizeof(args_copy));\n\t\tPR1_SetString (&((int *)pr_globals)[OFS_PARM0], cmd_copy);\n\t\tPR1_SetString (&((int *)pr_globals)[OFS_PARM1], args_copy);\n\t\tPR_ExecuteProgram (GE_ClientCommand);\n\t\treturn G_FLOAT(OFS_RETURN) ? true : false;\n\t}\n\n\tif (mod_UserCmd)\n\t{\n\t\tstatic char cmd_copy[128];\n\t\tstrlcpy (cmd_copy, Cmd_Argv(0), sizeof(cmd_copy));\n\t\tPR1_SetString (&((int *)pr_globals)[OFS_PARM0], cmd_copy);\n\n\t\tPR_ExecuteProgram (mod_UserCmd);\n\t\treturn G_FLOAT(OFS_RETURN) ? true : false;\n\t}\n\n\treturn false;\n}\n\n\n/*\n===============\nPR1_LoadProgs\n===============\n*/\nvoid PF_clear_strtbl(void);\n\n#ifdef WITH_NQPROGS\nvoid PR_InitPatchTables (void)\n{\n\tint i;\n\n\tif (pr_nqprogs)\n\t{\n\t\tmemcpy (pr_globaloffsetpatch, pr_globaloffsetpatch_nq, sizeof(pr_globaloffsetpatch));\n\t\tfor (i = 0; i < 106; i++)\n\t\t{\n\t\t\tpr_fieldoffsetpatch[i] = (i < 8) ? i : (i < 25) ? i + 1 :\n\t\t\t\t(i < 28) ? i + (102 - 25) : (i < 73) ? i - 2 :\n\t\t\t\t(i < 74) ? i + (105 - 73) : (i < 105) ? i - 3 : /* (i == 105) */ 8;\n\t\t}\n\n\t\tfor (i=0 ; i<progs->numfielddefs ; i++)\n\t\t\tpr_fielddefs[i].ofs = PR_FIELDOFS(pr_fielddefs[i].ofs);\n\t}\n\telse\n\t{\n\t\tmemset (pr_globaloffsetpatch, 0, sizeof(pr_globaloffsetpatch));\n\n\t\tfor (i = 0; i < 106; i++)\n\t\t\tpr_fieldoffsetpatch[i] = i;\n\t}\n}\n#endif\n\nvoid PR1_LoadProgs (void)\n{\n\tint\ti;\n\tchar num[32];\n\tint filesize;\n\n\t// flush the non-C variable lookup cache\n\tfor (i = 0; i < GEFV_CACHESIZE; i++)\n\t\tgefvCache[i].field[0] = 0;\n\n\t// clear pr_newstrtbl\n\tPF_clear_strtbl();\n\n\tprogs = NULL;\n#ifdef WITH_NQPROGS\n\tpr_nqprogs = false;\n\n\t// forced load of NQ progs.\n\tif (!progs && Cvar_Value(\"sv_forcenqprogs\") && (progs = (dprograms_t *)FS_LoadHunkFile (\"progs.dat\", &filesize)))\n\t\tpr_nqprogs = true;\n#endif\n\n\tif (!progs)\n\t{\n\t\tchar name[MAX_OSPATH];\n\t\tsnprintf(name, sizeof(name), \"%s.dat\", sv_progsname.string);\n\t\tprogs = (dprograms_t *)FS_LoadHunkFile (name, &filesize);\n\t}\n\tif (!progs)\n\t\tprogs = (dprograms_t *)FS_LoadHunkFile (\"qwprogs.dat\", &filesize);\n\tif (!progs)\n\t\tprogs = (dprograms_t *)FS_LoadHunkFile (\"spprogs.dat\", &filesize);\n#ifdef WITH_NQPROGS\n\t// low priority load of NQ progs.\n\tif (!progs && (progs = (dprograms_t *)FS_LoadHunkFile (\"progs.dat\", &filesize)))\n\t\tpr_nqprogs = true;\n#endif\n\n\tif (!progs)\n\t\tSV_Error (\"PR1_LoadProgs: couldn't load progs.dat\");\n\tCon_DPrintf (\"Programs occupy %iK.\\n\", filesize/1024);\n\n#ifdef WITH_NQPROGS\n\tif (pr_nqprogs)\n\t\tCon_DPrintf (\"NQ progs.\\n\");\n#endif\n\n\t// add prog crc to the serverinfo\n\tsnprintf (num, sizeof(num), \"%i\", CRC_Block ((byte *)progs, filesize));\n\tInfo_SetValueForStarKey (svs.info, \"*progs\", num, MAX_SERVERINFO_STRING);\n\n\t// byte swap the header\n\tfor (i = 0; i < (int) sizeof(*progs) / 4 ; i++)\n\t\t((int *)progs)[i] = LittleLong ( ((int *)progs)[i] );\n\n\tif (progs->version != PROG_VERSION)\n\t\tSV_Error (\"qwprogs.dat has wrong version number (%i should be %i)\", progs->version, PROG_VERSION);\n\tif (progs->crc != (pr_nqprogs ? NQ_PROGHEADER_CRC : PROGHEADER_CRC))\n\t\tSV_Error (\"You must have the qwprogs.dat from QuakeWorld installed\");\n\n\tpr_functions = (dfunction_t *)((byte *)progs + progs->ofs_functions);\n\tpr_strings = (char *)progs + progs->ofs_strings;\n\tpr_globaldefs = (ddef_t *)((byte *)progs + progs->ofs_globaldefs);\n\tpr_fielddefs = (ddef_t *)((byte *)progs + progs->ofs_fielddefs);\n\tpr_statements = (dstatement_t *)((byte *)progs + progs->ofs_statements);\n\n\tnum_prstr = 0;\n\n\tpr_global_struct = (globalvars_t *)((byte *)progs + progs->ofs_globals);\n\tpr_globals = (float *)pr_global_struct;\n\n\tpr_edict_size = progs->entityfields * 4;\n\n\t// byte swap the lumps\n\tfor (i = 0; i < progs->numstatements; i++)\n\t{\n\t\tpr_statements[i].op = LittleShort(pr_statements[i].op);\n\t\tpr_statements[i].a = LittleShort(pr_statements[i].a);\n\t\tpr_statements[i].b = LittleShort(pr_statements[i].b);\n\t\tpr_statements[i].c = LittleShort(pr_statements[i].c);\n\t}\n\n\tfor (i = 0; i < progs->numfunctions; i++)\n\t{\n\t\tpr_functions[i].first_statement = LittleLong (pr_functions[i].first_statement);\n\t\tpr_functions[i].parm_start = LittleLong (pr_functions[i].parm_start);\n\t\tpr_functions[i].s_name = LittleLong (pr_functions[i].s_name);\n\t\tpr_functions[i].s_file = LittleLong (pr_functions[i].s_file);\n\t\tpr_functions[i].numparms = LittleLong (pr_functions[i].numparms);\n\t\tpr_functions[i].locals = LittleLong (pr_functions[i].locals);\n\t}\n\n\tfor (i = 0; i < progs->numglobaldefs; i++)\n\t{\n\t\tpr_globaldefs[i].type = LittleShort (pr_globaldefs[i].type);\n\t\tpr_globaldefs[i].ofs = LittleShort (pr_globaldefs[i].ofs);\n\t\tpr_globaldefs[i].s_name = LittleLong (pr_globaldefs[i].s_name);\n\t}\n\n\tfor (i = 0; i < progs->numfielddefs; i++)\n\t{\n\t\tpr_fielddefs[i].type = LittleShort (pr_fielddefs[i].type);\n\t\tif (pr_fielddefs[i].type & DEF_SAVEGLOBAL)\n\t\t\tSV_Error (\"PR1_LoadProgs: pr_fielddefs[i].type & DEF_SAVEGLOBAL\");\n\t\tpr_fielddefs[i].ofs = LittleShort (pr_fielddefs[i].ofs);\n\t\tpr_fielddefs[i].s_name = LittleLong (pr_fielddefs[i].s_name);\n\t}\n\n\tfor (i = 0; i < progs->numglobals; i++)\n\t\t((int *)pr_globals)[i] = LittleLong (((int *)pr_globals)[i]);\n\n\tPR_InitBuiltins();\n}\n\nvoid PR1_InitProg(void)\n{\n\tsv.game_edicts = (entvars_t*) Hunk_AllocName (MAX_EDICTS * pr_edict_size, \"edicts\");\n\tsv.max_edicts = MAX_EDICTS;\n}\n\n/*\n===============\nPR1_Init\n===============\n*/\nvoid PR1_Init (void)\n{\n\tCvar_Register(&sv_progsname);\n#ifdef WITH_NQPROGS\n\tCvar_Register(&sv_forcenqprogs);\n#endif\n\n\tCmd_AddCommand (\"edict\", ED_PrintEdict_f);\n\tCmd_AddCommand (\"edicts\", ED_PrintEdicts);\n\tCmd_AddCommand (\"edictcount\", ED_Count);\n\tCmd_AddCommand (\"profile\", PR_Profile_f);\n\n\tmemset(pr_newstrtbl, 0, sizeof(pr_newstrtbl));\n}\n\nedict_t *EDICT_NUM(int n)\n{\n\tif (n < 0 || n >= sv.max_edicts)\n\t\tSV_Error (\"EDICT_NUM: bad number %i\", n);\n\treturn &sv.edicts[n];\n}\n\nint NUM_FOR_EDICT(edict_t *e)\n{\n\tint\t\tb;\n\n\tb = e->e.entnum;\n\n\tif (b < 0 || b >= sv.num_edicts)\n\t\tSV_Error (\"NUM_FOR_EDICT: bad pointer\");\n\n\treturn b;\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/pr_exec.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n   \n*/\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n#include <limits.h>\n\ntypedef struct prstack_s\n{\n\tint s;\n\tdfunction_t *f;\n} prstack_t;\n\n#define\tMAX_STACK_DEPTH 32\nprstack_t\tpr_stack[MAX_STACK_DEPTH];\nint\t\t\tpr_depth;\n\n#define\tLOCALSTACK_SIZE 2048\nint\t\t\tlocalstack[LOCALSTACK_SIZE];\nint\t\t\tlocalstack_used;\n\n\nqbool\t\tpr_trace;\ndfunction_t\t*pr_xfunction;\nint\t\t\tpr_xstatement;\n\n\nint\t\t\tpr_argc;\n\nchar *pr_opnames[] =\n    {\n        \"DONE\",\n\n        \"MUL_F\",\n        \"MUL_V\",\n        \"MUL_FV\",\n        \"MUL_VF\",\n\n        \"DIV\",\n\n        \"ADD_F\",\n        \"ADD_V\",\n\n        \"SUB_F\",\n        \"SUB_V\",\n\n        \"EQ_F\",\n        \"EQ_V\",\n        \"EQ_S\",\n        \"EQ_E\",\n        \"EQ_FNC\",\n\n        \"NE_F\",\n        \"NE_V\",\n        \"NE_S\",\n        \"NE_E\",\n        \"NE_FNC\",\n\n        \"LE\",\n        \"GE\",\n        \"LT\",\n        \"GT\",\n\n        \"INDIRECT\",\n        \"INDIRECT\",\n        \"INDIRECT\",\n        \"INDIRECT\",\n        \"INDIRECT\",\n        \"INDIRECT\",\n\n        \"ADDRESS\",\n\n        \"STORE_F\",\n        \"STORE_V\",\n        \"STORE_S\",\n        \"STORE_ENT\",\n        \"STORE_FLD\",\n        \"STORE_FNC\",\n\n        \"STOREP_F\",\n        \"STOREP_V\",\n        \"STOREP_S\",\n        \"STOREP_ENT\",\n        \"STOREP_FLD\",\n        \"STOREP_FNC\",\n\n        \"RETURN\",\n\n        \"NOT_F\",\n        \"NOT_V\",\n        \"NOT_S\",\n        \"NOT_ENT\",\n        \"NOT_FNC\",\n\n        \"IF\",\n        \"IFNOT\",\n\n        \"CALL0\",\n        \"CALL1\",\n        \"CALL2\",\n        \"CALL3\",\n        \"CALL4\",\n        \"CALL5\",\n        \"CALL6\",\n        \"CALL7\",\n        \"CALL8\",\n\n        \"STATE\",\n\n        \"GOTO\",\n\n        \"AND\",\n        \"OR\",\n\n        \"BITAND\",\n        \"BITOR\"\n    };\n\nchar *PR_GlobalString (int ofs);\nchar *PR_GlobalStringNoContents (int ofs);\n\n\n//=============================================================================\n\n/*\n=================\nPR_PrintStatement\n=================\n*/\nvoid PR_PrintStatement (dstatement_t *s)\n{\n\tint i;\n\n\tif ( (unsigned)s->op < sizeof(pr_opnames)/sizeof(pr_opnames[0]))\n\t{\n\n\t\tCon_Printf (\"%s \",  pr_opnames[s->op]);\n\n\t\ti = strlen(pr_opnames[s->op]);\n\t\tfor ( ; i<10 ; i++)\n\t\t\tCon_Printf (\" \");\n\t}\n\n\tif (s->op == OP_IF || s->op == OP_IFNOT)\n\t\tCon_Printf (\"%sbranch %i\",PR_GlobalString(s->a),s->b);\n\telse if (s->op == OP_GOTO)\n\t{\n\t\tCon_Printf (\"branch %i\",s->a);\n\t}\n\telse if ( (unsigned)(s->op - OP_STORE_F) < 6)\n\t{\n\t\tCon_Printf (\"%s\",PR_GlobalString(s->a));\n\t\tCon_Printf (\"%s\", PR_GlobalStringNoContents(s->b));\n\t}\n\telse\n\t{\n\t\tif (s->a)\n\t\t\tCon_Printf (\"%s\",PR_GlobalString(s->a));\n\t\tif (s->b)\n\t\t\tCon_Printf (\"%s\",PR_GlobalString(s->b));\n\t\tif (s->c)\n\t\t\tCon_Printf (\"%s\", PR_GlobalStringNoContents(s->c));\n\t}\n\tCon_Printf (\"\\n\");\n}\n\n/*\n============\nPR_StackTrace\n============\n*/\nvoid PR_StackTrace (void)\n{\n\tdfunction_t *f;\n\tint i;\n\n\tif (pr_depth == 0)\n\t{\n\t\tCon_Printf (\"<NO STACK>\\n\");\n\t\treturn;\n\t}\n\n\tpr_stack[pr_depth].f = pr_xfunction;\n\tfor (i=pr_depth ; i>0 ; i--)\n\t{\n\t\tf = pr_stack[i].f;\n\n\t\tif (!f)\n\t\t\tCon_Printf (\"<NO FUNCTION>\\n\");\n\t\telse\n\t\t\tCon_Printf (\"%12s : %s\\n\", PR1_GetString(f->s_file), PR1_GetString(f->s_name));\n\t}\n}\n\n\n/*\n============\nPR_Profile_f\n\n============\n*/\nvoid PR_Profile_f (void)\n{\n\tdfunction_t\t*f, *best;\n\tint max;\n\tint num;\n\tint i;\n\n\tif (sv.state != ss_active)\n\t\treturn;\t\n\n\tnum = 0;\n\tdo\n\t{\n\t\tmax = 0;\n\t\tbest = NULL;\n\t\tfor (i=0 ; i<progs->numfunctions ; i++)\n\t\t{\n\t\t\tf = &pr_functions[i];\n\t\t\tif (f->profile > max)\n\t\t\t{\n\t\t\t\tmax = f->profile;\n\t\t\t\tbest = f;\n\t\t\t}\n\t\t}\n\t\tif (best)\n\t\t{\n\t\t\tif (num < 10)\n\t\t\t\tCon_Printf (\"%7i %s\\n\", best->profile, PR1_GetString(best->s_name));\n\t\t\tnum++;\n\t\t\tbest->profile = 0;\n\t\t}\n\t}\n\twhile (best);\n}\n\n\n/*\n============\nPR_RunError\n\nAborts the currently executing function\n============\n*/\nvoid PR_RunError (char *error, ...)\n{\n\tva_list argptr;\n\tchar string[1024];\n\n\tva_start (argptr,error);\n\tvsnprintf (string, sizeof(string), error, argptr);\n\tva_end (argptr);\n\n\n\tsv_error = true;\n\n\tPR_PrintStatement (pr_statements + pr_xstatement);\n\tPR_StackTrace ();\n\tCon_Printf (\"%s\\n\", string);\n\n\tpr_depth = 0; // dump the stack so SV_Error can shutdown functions\n\n\tSV_Error (\"Program error (PR_RunError)\");\n}\n\n/*\n====================\nPR_EnterFunction\n\nReturns the new program statement counter\n====================\n*/\nint PR_EnterFunction (dfunction_t *f)\n{\n\tint i, j, c, o;\n\n\tpr_stack[pr_depth].s = pr_xstatement;\n\tpr_stack[pr_depth].f = pr_xfunction;\n\tpr_depth++;\n\tif (pr_depth >= MAX_STACK_DEPTH)\n\t\tPR_RunError (\"stack overflow\");\n\n\t// save off any locals that the new function steps on\n\tc = f->locals;\n\tif (localstack_used + c > LOCALSTACK_SIZE)\n\t\tPR_RunError (\"PR_ExecuteProgram: locals stack overflow\\n\");\n\n\tfor (i=0 ; i < c ; i++)\n\t\tlocalstack[localstack_used+i] = ((int *)pr_globals)[f->parm_start + i];\n\tlocalstack_used += c;\n\n\t// copy parameters\n\to = f->parm_start;\n\tfor (i=0 ; i<f->numparms ; i++)\n\t{\n\t\tfor (j=0 ; j<f->parm_size[i] ; j++)\n\t\t{\n\t\t\t((int *)pr_globals)[o] = ((int *)pr_globals)[OFS_PARM0+i*3+j];\n\t\t\to++;\n\t\t}\n\t}\n\n\tpr_xfunction = f;\n\treturn f->first_statement - 1; // offset the s++\n}\n\n/*\n====================\nPR_LeaveFunction\n====================\n*/\nint PR_LeaveFunction (void)\n{\n\tint i, c;\n\n\tif (pr_depth <= 0)\n\t\tSV_Error (\"prog stack underflow\");\n\n\t// restore locals from the stack\n\tc = pr_xfunction->locals;\n\tlocalstack_used -= c;\n\tif (localstack_used < 0)\n\t\tPR_RunError (\"PR_ExecuteProgram: locals stack underflow\\n\");\n\n\tfor (i=0 ; i < c ; i++)\n\t\t((int *)pr_globals)[pr_xfunction->parm_start + i] = localstack[localstack_used+i];\n\n\t// up stack\n\tpr_depth--;\n\tpr_xfunction = pr_stack[pr_depth].f;\n\treturn pr_stack[pr_depth].s;\n}\n\n/*\n============================================================================\nPR_ExecuteProgram\n\nThe interpretation main loop\n============================================================================\n*/\nvoid PR_ExecuteProgram (func_t fnum)\n{\n\teval_t *a = NULL, *b = NULL, *c = NULL;\n\tint s;\n\tdstatement_t *st = NULL;\n\tdfunction_t *f, *newf;\n\tint runaway;\n\tint i;\n\tedict_t *ed;\n\tint exitdepth;\n\teval_t *ptr;\n\n\tif (!fnum || fnum >= progs->numfunctions)\n\t{\n\t\tif (pr_global_struct->self)\n\t\t\tED_Print (PROG_TO_EDICT(pr_global_struct->self));\n\t\tSV_Error (\"PR_ExecuteProgram: NULL function\");\n\t}\n\n\tf = &pr_functions[fnum];\n\n\trunaway = 100000;\n\tpr_trace = false;\n\n\t// make a stack frame\n\texitdepth = pr_depth;\n\n\ts = PR_EnterFunction (f);\n\n\twhile (1)\n\t{\n\t\ts++; // next statement\n\n\t\tst = &pr_statements[s];\n\t\ta = (eval_t *)&pr_globals[st->a];\n\t\tb = (eval_t *)&pr_globals[st->b];\n\t\tc = (eval_t *)&pr_globals[st->c];\n\n\t\tif (--runaway == 0)\n\t\t\tPR_RunError (\"runaway loop error\");\n\n\t\tpr_xfunction->profile++;\n\t\tpr_xstatement = s;\n\n\t\tif (pr_trace)\n\t\t\tPR_PrintStatement (st);\n\n\t\tswitch (st->op)\n\t\t{\n\t\tcase OP_ADD_F:\n\t\t\tc->_float = a->_float + b->_float;\n\t\t\tbreak;\n\t\tcase OP_ADD_V:\n\t\t\tc->vector[0] = a->vector[0] + b->vector[0];\n\t\t\tc->vector[1] = a->vector[1] + b->vector[1];\n\t\t\tc->vector[2] = a->vector[2] + b->vector[2];\n\t\t\tbreak;\n\n\t\tcase OP_SUB_F:\n\t\t\tc->_float = a->_float - b->_float;\n\t\t\tbreak;\n\t\tcase OP_SUB_V:\n\t\t\tc->vector[0] = a->vector[0] - b->vector[0];\n\t\t\tc->vector[1] = a->vector[1] - b->vector[1];\n\t\t\tc->vector[2] = a->vector[2] - b->vector[2];\n\t\t\tbreak;\n\n\t\tcase OP_MUL_F:\n\t\t\tc->_float = a->_float * b->_float;\n\t\t\tbreak;\n\t\tcase OP_MUL_V:\n\t\t\tc->_float = a->vector[0]*b->vector[0]\n\t\t\t            + a->vector[1]*b->vector[1]\n\t\t\t            + a->vector[2]*b->vector[2];\n\t\t\tbreak;\n\t\tcase OP_MUL_FV:\n\t\t\tc->vector[0] = a->_float * b->vector[0];\n\t\t\tc->vector[1] = a->_float * b->vector[1];\n\t\t\tc->vector[2] = a->_float * b->vector[2];\n\t\t\tbreak;\n\t\tcase OP_MUL_VF:\n\t\t\tc->vector[0] = b->_float * a->vector[0];\n\t\t\tc->vector[1] = b->_float * a->vector[1];\n\t\t\tc->vector[2] = b->_float * a->vector[2];\n\t\t\tbreak;\n\n\t\tcase OP_DIV_F:\n\t\t\tc->_float = a->_float / b->_float;\n\t\t\tbreak;\n\n\t\tcase OP_BITAND:\n\t\t\tc->_float = (int)a->_float & (int)b->_float;\n\t\t\tbreak;\n\n\t\tcase OP_BITOR:\n\t\t\tc->_float = (int)a->_float | (int)b->_float;\n\t\t\tbreak;\n\n\n\t\tcase OP_GE:\n\t\t\tc->_float = a->_float >= b->_float;\n\t\t\tbreak;\n\t\tcase OP_LE:\n\t\t\tc->_float = a->_float <= b->_float;\n\t\t\tbreak;\n\t\tcase OP_GT:\n\t\t\tc->_float = a->_float > b->_float;\n\t\t\tbreak;\n\t\tcase OP_LT:\n\t\t\tc->_float = a->_float < b->_float;\n\t\t\tbreak;\n\t\tcase OP_AND:\n\t\t\tc->_float = a->_float && b->_float;\n\t\t\tbreak;\n\t\tcase OP_OR:\n\t\t\tc->_float = a->_float || b->_float;\n\t\t\tbreak;\n\n\t\tcase OP_NOT_F:\n\t\t\tc->_float = !a->_float;\n\t\t\tbreak;\n\t\tcase OP_NOT_V:\n\t\t\tc->_float = !a->vector[0] && !a->vector[1] && !a->vector[2];\n\t\t\tbreak;\n\t\tcase OP_NOT_S:\n\t\t\tc->_float = !a->string || !*PR1_GetString(a->string);\n\t\t\tbreak;\n\t\tcase OP_NOT_FNC:\n\t\t\tc->_float = !a->function;\n\t\t\tbreak;\n\t\tcase OP_NOT_ENT:\n\t\t\tc->_float = (PROG_TO_EDICT(a->edict) == sv.edicts);\n\t\t\tbreak;\n\n\t\tcase OP_EQ_F:\n\t\t\tc->_float = a->_float == b->_float;\n\t\t\tbreak;\n\t\tcase OP_EQ_V:\n\t\t\tc->_float = (a->vector[0] == b->vector[0]) &&\n\t\t\t            (a->vector[1] == b->vector[1]) &&\n\t\t\t            (a->vector[2] == b->vector[2]);\n\t\t\tbreak;\n\t\tcase OP_EQ_S:\n\t\t\tc->_float = !strcmp(PR1_GetString(a->string), PR1_GetString(b->string));\n\t\t\tbreak;\n\t\tcase OP_EQ_E:\n\t\t\tc->_float = a->_int == b->_int;\n\t\t\tbreak;\n\t\tcase OP_EQ_FNC:\n\t\t\tc->_float = a->function == b->function;\n\t\t\tbreak;\n\n\n\t\tcase OP_NE_F:\n\t\t\tc->_float = a->_float != b->_float;\n\t\t\tbreak;\n\t\tcase OP_NE_V:\n\t\t\tc->_float = (a->vector[0] != b->vector[0]) ||\n\t\t\t            (a->vector[1] != b->vector[1]) ||\n\t\t\t            (a->vector[2] != b->vector[2]);\n\t\t\tbreak;\n\t\tcase OP_NE_S:\n\t\t\tc->_float = strcmp(PR1_GetString(a->string), PR1_GetString(b->string));\n\t\t\tbreak;\n\t\tcase OP_NE_E:\n\t\t\tc->_float = a->_int != b->_int;\n\t\t\tbreak;\n\t\tcase OP_NE_FNC:\n\t\t\tc->_float = a->function != b->function;\n\t\t\tbreak;\n\n\t\t\t//==================\n\t\tcase OP_STORE_F:\n\t\tcase OP_STORE_ENT:\n\t\tcase OP_STORE_FLD:\t\t// integers\n\t\tcase OP_STORE_S:\n\t\tcase OP_STORE_FNC:\t\t// pointers\n\t\t\tb->_int = a->_int;\n\t\t\tbreak;\n\t\tcase OP_STORE_V:\n\t\t\tb->vector[0] = a->vector[0];\n\t\t\tb->vector[1] = a->vector[1];\n\t\t\tb->vector[2] = a->vector[2];\n\t\t\tbreak;\n\n\t\tcase OP_STOREP_F:\n\t\tcase OP_STOREP_ENT:\n\t\tcase OP_STOREP_FLD:\t\t// integers\n\t\tcase OP_STOREP_S:\n\t\tcase OP_STOREP_FNC:\t\t// pointers\n\t\t\tptr = (eval_t *)((byte *)sv.game_edicts + b->_int);\n\t\t\tptr->_int = a->_int;\n\t\t\tbreak;\n\t\tcase OP_STOREP_V:\n\t\t\tptr = (eval_t *)((byte *)sv.game_edicts + b->_int);\n\t\t\tptr->vector[0] = a->vector[0];\n\t\t\tptr->vector[1] = a->vector[1];\n\t\t\tptr->vector[2] = a->vector[2];\n\t\t\tbreak;\n\n\t\tcase OP_ADDRESS:\n\t\t\ted = PROG_TO_EDICT(a->edict);\n#ifdef PARANOID\n\t\t\tNUM_FOR_EDICT(ed);\t\t// make sure it's in range\n#endif\n\t\t\tif (ed == (edict_t *)sv.edicts && sv.state == ss_active)\n\t\t\t\tPR_RunError (\"assignment to world entity\");\n\t\t\tc->_int = (byte *)((int *)ed->v + PR_FIELDOFS(b->_int)) - (byte *)sv.game_edicts;\n\t\t\tbreak;\n\n\t\tcase OP_LOAD_F:\n\t\tcase OP_LOAD_FLD:\n\t\tcase OP_LOAD_ENT:\n\t\tcase OP_LOAD_S:\n\t\tcase OP_LOAD_FNC:\n\t\t\ted = PROG_TO_EDICT(a->edict);\n#ifdef PARANOID\n\t\t\tNUM_FOR_EDICT(ed);\t\t// make sure it's in range\n#endif\n\t\t\t//need for checking 'cmd mmode player N', if N >= 0x10000000 =(signed)=> negative\n\t\t\tif (b->_int >= 0)\n\t\t\t{\n\t\t\t\ta = (eval_t *)((int *)ed->v + PR_FIELDOFS(b->_int));\n\t\t\t\tc->_int = a->_int;\n\t\t\t}\n\t\t\telse\n\t\t\t\tc->_int = 0;\n\t\t\tbreak;\n\n\t\tcase OP_LOAD_V:\n\t\t\ted = PROG_TO_EDICT(a->edict);\n#ifdef PARANOID\n\t\t\tNUM_FOR_EDICT(ed);\t\t// make sure it's in range\n#endif\n\t\t\ta = (eval_t *)((int *)ed->v + PR_FIELDOFS(b->_int));\n\t\t\tc->vector[0] = a->vector[0];\n\t\t\tc->vector[1] = a->vector[1];\n\t\t\tc->vector[2] = a->vector[2];\n\t\t\tbreak;\n\n\t\t\t//==================\n\n\t\tcase OP_IFNOT:\n\t\t\tif (!a->_int)\n\t\t\t\ts += st->b - 1;\t// offset the s++\n\t\t\tbreak;\n\n\t\tcase OP_IF:\n\t\t\tif (a->_int)\n\t\t\t\ts += st->b - 1;\t// offset the s++\n\t\t\tbreak;\n\n\t\tcase OP_GOTO:\n\t\t\ts += st->a - 1;\t// offset the s++\n\t\t\tbreak;\n\n\t\tcase OP_CALL0:\n\t\tcase OP_CALL1:\n\t\tcase OP_CALL2:\n\t\tcase OP_CALL3:\n\t\tcase OP_CALL4:\n\t\tcase OP_CALL5:\n\t\tcase OP_CALL6:\n\t\tcase OP_CALL7:\n\t\tcase OP_CALL8:\n\t\t\tpr_argc = st->op - OP_CALL0;\n\t\t\tif (!a->function)\n\t\t\t\tPR_RunError (\"NULL function\");\n\n\t\t\tnewf = &pr_functions[a->function];\n\n\t\t\tif (newf->first_statement < 0)\n\t\t\t{\t// negative statements are built in functions\n\t\t\t\ti = -newf->first_statement;\n\t\t\t\tif (i >= pr_numbuiltins)\n\t\t\t\t\tPR_RunError (\"Bad builtin call number\");\n\t\t\t\tpr_builtins[i] ();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\ts = PR_EnterFunction (newf);\n\n\t\t\tbreak;\n\n\t\tcase OP_DONE:\n\t\tcase OP_RETURN:\n\t\t\tpr_globals[OFS_RETURN] = pr_globals[st->a];\n\t\t\tpr_globals[OFS_RETURN+1] = pr_globals[st->a+1];\n\t\t\tpr_globals[OFS_RETURN+2] = pr_globals[st->a+2];\n\n\t\t\ts = PR_LeaveFunction ();\n\t\t\tif (pr_depth == exitdepth)\n\t\t\t\treturn;\t\t// all done\n\t\t\tbreak;\n\n\t\tcase OP_STATE:\n\t\t\ted = PROG_TO_EDICT(pr_global_struct->self);\n\t\t\ted->v->nextthink = pr_global_struct->time + 0.1;\n\t\t\tif (a->_float != ed->v->frame)\n\t\t\t{\n\t\t\t\ted->v->frame = a->_float;\n\t\t\t}\n\t\t\ted->v->think = b->function;\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tPR_RunError (\"Bad opcode %i\", st->op);\n\t\t}\n\t}\n\n}\n\n//=============================================================================\n\nchar *pr_newstrtbl[MAX_PRSTR];\nchar *pr_strtbl[MAX_PRSTR];\nint num_prstr;\n\nchar *PR1_GetString(int num)\n{\n\tif (num < 0)\n\t{\n\t\t//Con_DPrintf(\"GET:%d == %s\\n\", num, pr_strtbl[-num]);\n\t\tnum = -num;\n\t\tif (num >= 2 * MAX_PRSTR)\n\t\t{\n\t\t\tCon_Printf(\"PR1_GetString: num = %d\\n\", num);// May be will be better to generate PR_RunError?\n\t\t\treturn NULL;\n\t\t}\n\t\tif (num >= MAX_PRSTR)\n\t\t\treturn pr_newstrtbl[num - MAX_PRSTR];\n\n\t\treturn pr_strtbl[num];\n\t}\n\treturn pr_strings + num;\n}\n\nvoid PR1_SetString(string_t* address, char* s)\n{\n\tint i;\n\n\tif (!address) {\n\t\treturn;\n\t}\n\n\tif (!s || !s[0]) {\n\t\t*address = 0;\n\t\treturn;\n\t}\n\n\tif (s - pr_strings < 0 || s - pr_strings > INT_MAX) {\n\t\tfor (i = 0; i < num_prstr; i++) {\n\t\t\tif (pr_strtbl[i] == s) {\n\t\t\t\t*address = -i;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif (num_prstr + 1 >= MAX_PRSTR) {\n\t\t\tSys_Error(\"MAX_PRSTR\");\n\t\t}\n\n\t\tpr_strtbl[++num_prstr] = s;\n\t\t//Con_DPrintf(\"SET:%d == %s\\n\", -num_prstr, s);\n\t\t*address = -num_prstr;\n\t}\n\telse {\n\t\t*address = (int)(s - pr_strings);\n\t}\n}\n\n/*\n==============\nPR_SetTmpString\n\ntemp strings are used for qc function parameters\nmany calls to function could cause strtbl overflow\n==============\n*/\n\nvoid PR_SetTmpString(string_t* target, const char *s)\n{\n\tstatic int index1;\n\tstatic char tmp[8][2048];\n\n\tindex1 = (index1 + 1) & 7;\n\n\tstrlcpy(tmp[index1], s, sizeof(tmp[index1]));\n\tPR1_SetString(target, tmp[index1]);\n}\n\n//=============================================================================\n\nvoid PR1_GameClientDisconnect(int spec)\n{\n\tif (spec)\n\t{\n\t\tif (mod_SpectatorDisconnect)\n\t\t\tPR_ExecuteProgram(mod_SpectatorDisconnect);\n\t}\n\telse\n\t{\n\t\tPR_ExecuteProgram(PR_GLOBAL(ClientDisconnect));\n\t}\n}\n\n//=============================================================================\n\nvoid PR1_GameClientConnect(int spec)\n{\n\tif (spec)\n\t{\n\t\tif (mod_SpectatorConnect)\n\t\t\tPR_ExecuteProgram(mod_SpectatorConnect);\n\t}\n\telse\n\t{\n\t\tPR_ExecuteProgram(PR_GLOBAL(ClientConnect));\n\t}\n}\n\n//=============================================================================\n\nvoid PR1_GamePutClientInServer(int spec)\n{\n\tif (spec)\n\t{\n\t\t// none...\n\t}\n\telse\n\t{\n\t\tPR_ExecuteProgram(PR_GLOBAL(PutClientInServer));\n\t}\n}\n\n//=============================================================================\n\nvoid PR1_GameClientPreThink(int spec)\n{\n\tif (spec)\n\t{\n\t\t// none...\n\t}\n\telse\n\t{\n\t\tPR_ExecuteProgram(PR_GLOBAL(PlayerPreThink));\n\t}\n}\n\n//=============================================================================\n\nvoid PR1_GameClientPostThink(int spec)\n{\n\tif (spec)\n\t{\n\t\tif (mod_SpectatorThink)\n\t\t\tPR_ExecuteProgram(mod_SpectatorThink);\n\t}\n\telse\n\t{\n\t\tPR_ExecuteProgram(PR_GLOBAL(PlayerPostThink));\n\t}\n}\n\n//=============================================================================\n\nqbool PR1_ClientSay(int isTeamSay, char *message)\n{\n\tqbool ret = false;\n\n\tif (mod_ChatMessage)\n\t{\n\t\tint j;\n\n\t\t// remove surrounding \" if any.\n\t\tif (message[0] == '\"' && (j = (int)strlen(message)) > 2 && message[j-1] == '\"')\n\t\t{\n\t\t\tmessage++;  // skip opening \".\n\t\t\tmessage[max(0,(int)strlen(message)-1)] = 0;   // truncate closing \".\n\t\t}\n\n\t\tPR_SetTmpString(&G_INT(OFS_PARM0), message);\n\t\tG_FLOAT(OFS_PARM1) = (float)isTeamSay;\n\n\t\tPR_ExecuteProgram(mod_ChatMessage);\n\n\t\tret = !!G_FLOAT(OFS_RETURN);\n\t}\n\n\treturn ret;\n}\n\n//=============================================================================\n\nvoid PR1_PausedTic(float duration)\n{\n\tif (GE_PausedTic)\n\t{\n\t\tG_FLOAT(OFS_PARM0) = duration;\n\t\tPR_ExecuteProgram (GE_PausedTic);\n\t}\n}\n\n//=============================================================================\n\nvoid PR1_UnLoadProgs(void)\n{\n\tif (progs)\n\t{\n\t\t// FIXME: There should be done alot of variables reseting...\n\n#ifdef WITH_NQPROGS\n\t\tpr_nqprogs = false;\n#endif\n\t\tprogs = NULL;\n\t}\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/progdefs.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifndef __PROGDEFS_H__\n#define __PROGDEFS_H__\n\n/* file generated by qcc, do not modify */\n\ntypedef struct\n{\tint\tpad[28];\n\tint\tself;\n\tint\tother;\n\tint\tworld;\n\tfloat\ttime;\n\tfloat\tframetime;\n\tint\tnewmis;\n\tfloat\tforce_retouch;\n\tstring_t\tmapname;\n\tfloat\tserverflags;\n\tfloat\ttotal_secrets;\n\tfloat\ttotal_monsters;\n\tfloat\tfound_secrets;\n\tfloat\tkilled_monsters;\n\tfloat\tparm1;\n\tfloat\tparm2;\n\tfloat\tparm3;\n\tfloat\tparm4;\n\tfloat\tparm5;\n\tfloat\tparm6;\n\tfloat\tparm7;\n\tfloat\tparm8;\n\tfloat\tparm9;\n\tfloat\tparm10;\n\tfloat\tparm11;\n\tfloat\tparm12;\n\tfloat\tparm13;\n\tfloat\tparm14;\n\tfloat\tparm15;\n\tfloat\tparm16;\n\tvec3_t\tv_forward;\n\tvec3_t\tv_up;\n\tvec3_t\tv_right;\n\tfloat\ttrace_allsolid;\n\tfloat\ttrace_startsolid;\n\tfloat\ttrace_fraction;\n\tvec3_t\ttrace_endpos;\n\tvec3_t\ttrace_plane_normal;\n\tfloat\ttrace_plane_dist;\n\tint\ttrace_ent;\n\tfloat\ttrace_inopen;\n\tfloat\ttrace_inwater;\n\tint\tmsg_entity;\n\tfunc_t\tmain;\n\tfunc_t\tStartFrame;\n\tfunc_t\tPlayerPreThink;\n\tfunc_t\tPlayerPostThink;\n\tfunc_t\tClientKill;\n\tfunc_t\tClientConnect;\n\tfunc_t\tPutClientInServer;\n\tfunc_t\tClientDisconnect;\n\tfunc_t\tSetNewParms;\n\tfunc_t\tSetChangeParms;\n} globalvars_t;\n\ntypedef struct\n{\n\tfloat\tmodelindex;\n\tvec3_t\tabsmin;\n\tvec3_t\tabsmax;\n\tfloat\tltime;\n\tfloat\tlastruntime;\n\tfloat\tmovetype;\n\tfloat\tsolid;\n\tvec3_t\torigin;\n\tvec3_t\toldorigin;\n\tvec3_t\tvelocity;\n\tvec3_t\tangles;\n\tvec3_t\tavelocity;\n\tstring_t\tclassname;\n\tstring_t\tmodel;\n\tfloat\tframe;\n\tfloat\tskin;\n\tfloat\teffects;\n\tvec3_t\tmins;\n\tvec3_t\tmaxs;\n\tvec3_t\tsize;\n\tfunc_t\ttouch;\n\tfunc_t\tuse;\n\tfunc_t\tthink;\n\tfunc_t\tblocked;\n\tfloat\tnextthink;\n\tint\tgroundentity;\n\tfloat\thealth;\n\tfloat\tfrags;\n\tfloat\tweapon;\n\tstring_t\tweaponmodel;\n\tfloat\tweaponframe;\n\tfloat\tcurrentammo;\n\tfloat\tammo_shells;\n\tfloat\tammo_nails;\n\tfloat\tammo_rockets;\n\tfloat\tammo_cells;\n\tfloat\titems;\n\tfloat\ttakedamage;\n\tint\tchain;\n\tfloat\tdeadflag;\n\tvec3_t\tview_ofs;\n\tfloat\tbutton0;\n\tfloat\tbutton1;\n\tfloat\tbutton2;\n\tfloat\timpulse;\n\tfloat\tfixangle;\n\tvec3_t\tv_angle;\n\tstring_t\tnetname;\n\tint\tenemy;\n\tfloat\tflags;\n\tfloat\tcolormap;\n\tfloat\tteam;\n\tfloat\tmax_health;\n\tfloat\tteleport_time;\n\tfloat\tarmortype;\n\tfloat\tarmorvalue;\n\tfloat\twaterlevel;\n\tfloat\twatertype;\n\tfloat\tideal_yaw;\n\tfloat\tyaw_speed;\n\tint\taiment;\n\tint\tgoalentity;\n\tfloat\tspawnflags;\n\tstring_t\ttarget;\n\tstring_t\ttargetname;\n\tfloat\tdmg_take;\n\tfloat\tdmg_save;\n\tint\tdmg_inflictor;\n\tint\towner;\n\tvec3_t\tmovedir;\n\tstring_t\tmessage;\n\tfloat\tsounds;\n\tstring_t\tnoise;\n\tstring_t\tnoise1;\n\tstring_t\tnoise2;\n\tstring_t\tnoise3;\n} entvars_t;\n\n#define PROGHEADER_CRC 54730\n\n#endif /* !__PROGDEFS_H__ */\n"
  },
  {
    "path": "src/progs.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifndef __PROGS_H__\n#define __PROGS_H__\n\n#include \"pr_comp.h\" // defs shared with qcc\n#include \"progdefs.h\" // generated by program cdefs\n\ntypedef union eval_s\n{\n\tstring_t\tstring;\n\tfloat\t\t_float;\n\tfloat\t\tvector[3];\n\tfunc_t\t\tfunction;\n\tint\t\t_int;\n\tint\t\tedict;\n} eval_t;\n\nstruct edict_s; // forward referecnce for link_t\n\ntypedef struct link_s\n{\n\tstruct edict_s *ed;\n\n\tstruct link_s\t*prev, *next;\n} link_t;\n\n#define\tEDICT_FROM_AREA(l)\t((l)->ed)\n\n#define\tMAX_ENT_LEAFS\t16\n\ntypedef struct sv_edict_s\n{\n\tqbool\t\tfree;\n\tlink_t\t\tarea;\t\t\t// linked to a division node or leaf\n\n\tint         entnum;\n\n\tint\t\t\tnum_leafs;\n\tshort\t\tleafnums[MAX_ENT_LEAFS];\n\n\tentity_state_t\tbaseline;\n\n\tfloat\t\tfreetime;\t\t// sv.time when the object was freed\n\tdouble\t\tlastruntime;\t// sv.time when SV_RunEntity was last called for this edict (Tonik)\n} sv_edict_t;\n\ntypedef struct\n{\n\tfloat\talpha;\t\t\t// 0 = opaque, 1 = opaque, 0 < x < 1 translucent\n\tfloat\tcolourmod[3];\t// r,g,b [0.0 .. 1.0], > 1 overbright\n} ext_entvars_t;\n\ntypedef struct edict_s\n{\n\tsv_edict_t\te;\t\t\t// server side part of the edict_t\n\text_entvars_t\txv;\n\tentvars_t\t*v;\t\t\t// C exported fields from progs\n} edict_t;\n\n//============================================================================\n\nextern\tdprograms_t\t*progs;\nextern\tdfunction_t\t*pr_functions;\nextern\tchar\t\t*pr_strings;\nextern\tddef_t\t\t*pr_globaldefs;\nextern\tddef_t\t\t*pr_fielddefs;\nextern\tdstatement_t\t*pr_statements;\nextern\tglobalvars_t\t*pr_global_struct;\nextern\tfloat\t\t*pr_globals;\t// same as pr_global_struct\n\nextern\tint         pr_edict_size;\t// in bytes\nextern\tcvar_t      sv_progsname; \n#ifdef WITH_NQPROGS\nextern\tcvar_t      sv_forcenqprogs;\n#endif\n\n//============================================================================\n\n#ifdef WITH_NQPROGS\n\nextern\tqbool\t\t\tpr_nqprogs;\n\nextern\tint pr_fieldoffsetpatch[106];\nextern\tint pr_globaloffsetpatch[62];\n\n#define PR_FIELDOFS(i) ((unsigned int)(i) > 105 ? (i) : pr_fieldoffsetpatch[i])\n#define PR_GLOBAL(field) (((globalvars_t *)((byte *)pr_global_struct + \\\n\tpr_globaloffsetpatch[((int *)&((globalvars_t *)0)->field - (int *)0) - 28]))->field)\n\nvoid NQP_Reset (void);\n\n#else\t// !WITH_NQPROGS\n\n#define pr_nqprogs 0\n#define PR_FIELDOFS(i) (i)\n#define PR_GLOBAL(field) pr_global_struct->field\n#define NQP_Reset()\n\n#endif\n\n//============================================================================\n\nvoid PR_Init (void);\n\nvoid PR_ExecuteProgram (func_t fnum);\nvoid PR_InitPatchTables (void);\t// NQ progs support\n\nvoid PR_Profile_f (void);\n\nvoid ED_ClearEdict (edict_t *e);\nedict_t *ED_Alloc (void);\nvoid ED_Free (edict_t *ed);\n\nchar *ED_NewString (char *string);\n// returns a copy of the string allocated from the server's string heap\n\nvoid ED_Print (edict_t *ed);\nvoid ED_Write (FILE *f, edict_t *ed);\nconst char *ED_ParseEdict (const char *data, edict_t *ent);\n\nvoid ED_WriteGlobals (FILE *f);\nvoid ED_ParseGlobals (const char *data);\n\nvoid ED_LoadFromFile (const char *data);\n\nedict_t *EDICT_NUM(int n);\nint NUM_FOR_EDICT(edict_t *e);\n\n#define\tNEXT_EDICT(e) ((edict_t *)((byte *)(e) + sizeof(edict_t)))\n\n#define\tEDICT_TO_PROG(e) ((byte *)(e)->v - (byte *)sv.game_edicts)\n#define PROG_TO_EDICT(e) (&sv.edicts[(e)/pr_edict_size])\n\n//============================================================================\n\n#define\tG_FLOAT(o) (pr_globals[o])\n#define\tG_INT(o) (*(int *)&pr_globals[o])\n#define\tG_EDICT(o) (&sv.edicts[(*(int *)&pr_globals[o])/pr_edict_size])\n#define G_EDICTNUM(o) NUM_FOR_EDICT(G_EDICT(o))\n#define\tG_VECTOR(o) (&pr_globals[o])\n#define\tG_STRING(o) (PR1_GetString(*(string_t *)&pr_globals[o]))\n#define\tG_FUNCTION(o) (*(func_t *)&pr_globals[o])\n\n#define\tE_FLOAT(e,o) (((float*)e->v)[o])\n#define\tE_INT(e,o) (*(int *)&((float*)e->v)[o])\n#define\tE_VECTOR(e,o) (&((float*)e->v)[o])\n#define\tE_STRING(e,o) (PR1_GetString(*(string_t *)&((float*)e->v)[PR_FIELDOFS(o)]))\n\ntypedef void\t\t(*builtin_t) (void);\nextern\tbuiltin_t\t*pr_builtins;\nextern\tint\t\tpr_numbuiltins;\n\nextern\tint\t\tpr_argc;\n\nextern\tqbool\tpr_trace;\nextern\tdfunction_t\t*pr_xfunction;\nextern\tint\t\tpr_xstatement;\n\nextern func_t mod_ConsoleCmd, mod_UserCmd;\nextern func_t mod_UserInfo_Changed, mod_localinfoChanged;\nextern func_t mod_ChatMessage;\nextern func_t mod_SpectatorConnect, mod_SpectatorDisconnect, mod_SpectatorThink;\nextern func_t GE_ClientCommand, GE_PausedTic, GE_ShouldPause;\n\nextern int fofs_items2; // ZQ_ITEMS2 extension\nextern int fofs_vw_index;\t// ZQ_VWEP\nextern int fofs_movement;\nextern int fofs_gravity, fofs_maxspeed;\nextern int fofs_hideentity;\nextern int fofs_trackent;\nextern int fofs_visibility;\nextern int fofs_hide_players;\nextern int fofs_teleported;\n\n#define EdictFieldFloat(ed, fieldoffset) ((eval_t *)((byte *)(ed)->v + (fieldoffset)))->_float\n#define EdictFieldVector(ed, fieldoffset) ((eval_t *)((byte *)(ed)->v + (fieldoffset)))->vector\n\nvoid PR_RunError (char *error, ...);\n\nvoid ED_PrintEdicts (void);\nvoid ED_PrintNum (int ent);\n\neval_t *PR1_GetEdictFieldValue(edict_t *ed, char *field);\n\nint ED1_FindFieldOffset (char *field);\n\n//\n// PR Strings stuff\n//\n#define MAX_PRSTR 1024\n\nextern char *pr_strtbl[MAX_PRSTR];\nextern char *pr_newstrtbl[MAX_PRSTR];\nextern int num_prstr;\n\nchar *PR1_GetString(int num);\nvoid PR1_SetString(string_t* address, char* s);\nvoid PR_SetTmpString(string_t* address, const char *s);\n\nvoid PR1_LoadProgs (void);\nvoid PR1_InitProg(void);\nvoid PR1_Init(void);\n\n#define PR1_GameShutDown()\t// PR1 does not really have it.\nvoid PR1_UnLoadProgs(void);\n\nvoid PR1_GameClientDisconnect(int spec);\nvoid PR1_GameClientConnect(int spec);\nvoid PR1_GamePutClientInServer(int spec);\nvoid PR1_GameClientPreThink(int spec);\nvoid PR1_GameClientPostThink(int spec);\nqbool PR1_ClientSay(int isTeamSay, char *message);\nvoid PR1_PausedTic(float duration);\nqbool PR1_ClientCmd(void);\n\n#define PR1_GameSetChangeParms() PR_ExecuteProgram(PR_GLOBAL(SetChangeParms))\n#define PR1_GameSetNewParms() PR_ExecuteProgram(PR_GLOBAL(SetNewParms))\n#define PR1_GameStartFrame() PR_ExecuteProgram (PR_GLOBAL(StartFrame))\n#define PR1_ClientKill() PR_ExecuteProgram (PR_GLOBAL(ClientKill))\n#define PR1_UserInfoChanged(after) (0) // PR1 does not really have it,\n                                  // we have mod_UserInfo_Changed but it is slightly different.\n#define PR1_LoadEnts ED_LoadFromFile\n#define PR1_EdictThink PR_ExecuteProgram\n#define PR1_EdictTouch PR_ExecuteProgram\n#define PR1_EdictBlocked PR_ExecuteProgram\n\n#ifndef USE_PR2\n\t#define PR_LoadProgs PR1_LoadProgs\n\t#define PR_InitProg PR1_InitProg\n\t#define PR_GameShutDown PR1_GameShutDown\n\t#define PR_UnLoadProgs PR1_UnLoadProgs\n\n\t#define PR_Init PR1_Init\n\t//#define PR_GetString PR1_GetString\n\t//#define PR_SetString PR1_SetString\n\t#define PR_GetEntityString PR1_GetString\n\t#define PR_SetEntityString(ent, target, value) PR1_SetString(&target, value)\n\t#define PR_SetGlobalString(target, value) PR1_SetString(&target, value)\n\t#define ED_FindFieldOffset ED1_FindFieldOffset\n\t#define PR_GetEdictFieldValue PR1_GetEdictFieldValue\n\n\t#define PR_GameClientDisconnect PR1_GameClientDisconnect\n\t#define PR_GameClientConnect PR1_GameClientConnect\n\t#define PR_GamePutClientInServer PR1_GamePutClientInServer\n\t#define PR_GameClientPreThink PR1_GameClientPreThink\n\t#define PR_GameClientPostThink PR1_GameClientPostThink\n\t#define PR_ClientSay PR1_ClientSay\n\t#define PR_PausedTic PR1_PausedTic\n\t#define PR_ClientCmd PR1_ClientCmd\n\n\t#define PR_GameSetChangeParms PR1_GameSetChangeParms\n\t#define PR_GameSetNewParms PR1_GameSetNewParms\n\t#define PR_GameStartFrame(isBotFrame) { if (!isBotFrame) { PR1_GameStartFrame(); } }\n\t#define PR_ClientKill PR1_ClientKill\n\t#define PR_UserInfoChanged PR1_UserInfoChanged\n\t#define PR_LoadEnts PR1_LoadEnts\n\t#define PR_EdictThink PR1_EdictThink\n\t#define PR_EdictTouch PR1_EdictTouch\n\t#define PR_EdictBlocked PR1_EdictBlocked\n\n\t#define PR_ClearEdict(ent)\n#endif\n\n// pr_cmds.c\nvoid PR_InitBuiltins (void);\n\n#endif /* !__PROGS_H__ */\n"
  },
  {
    "path": "src/q_platform.h",
    "content": "/*\r\n===========================================================================\r\nCopyright (C) 1999-2005 Id Software, Inc.\r\n\r\nThis file is part of Quake III Arena source code.\r\n\r\nQuake III Arena source code is free software; you can redistribute it\r\nand/or modify it under the terms of the GNU General Public License as\r\npublished by the Free Software Foundation; either version 2 of the License,\r\nor (at your option) any later version.\r\n\r\nQuake III Arena source code is distributed in the hope that it will be\r\nuseful, but WITHOUT ANY WARRANTY; without even the implied warranty of\r\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\nGNU General Public License for more details.\r\n\r\nYou should have received a copy of the GNU General Public License\r\nalong with Quake III Arena source code; if not, write to the Free Software\r\nFoundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\r\n===========================================================================\r\n*/\r\n\r\n#ifndef __Q_PLATFORM_H\r\n#define __Q_PLATFORM_H\r\n\r\n// this is for determining if we have an asm version of a C function\r\n#define idx64 0\r\n\r\n#ifdef Q3_VM\r\n\r\n#define idx386 0\r\n#define idppc 0\r\n#define idppc_altivec 0\r\n#define idsparc 0\r\n\r\n#else\r\n\r\n#if (defined _M_IX86 || defined __i386__) && !defined(C_ONLY)\r\n#define idx386 1\r\n#else\r\n#define idx386 0\r\n#endif\r\n\r\n#if (defined(powerc) || defined(powerpc) || defined(ppc) || \\\r\n\tdefined(__ppc) || defined(__ppc__)) && !defined(C_ONLY)\r\n#define idppc 1\r\n#if defined(__VEC__)\r\n#define idppc_altivec 1\r\n#ifdef MACOS_X  // Apple's GCC does this differently than the FSF.\r\n#define VECCONST_UINT8(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \\\r\n\t(vector unsigned char) (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)\r\n#else\r\n#define VECCONST_UINT8(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \\\r\n\t(vector unsigned char) {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}\r\n#endif\r\n#else\r\n#define idppc_altivec 0\r\n#endif\r\n#else\r\n#define idppc 0\r\n#define idppc_altivec 0\r\n#endif\r\n\r\n#if defined(__sparc__) && !defined(C_ONLY)\r\n#define idsparc 1\r\n#else\r\n#define idsparc 0\r\n#endif\r\n\r\n#endif\r\n\r\n#ifndef __ASM_I386__ // don't include the C bits if included from qasm.h\r\n\r\n// for windows fastcall option\r\n#define QDECL\r\n\r\n//================================================================= WIN64/32 ===\r\n\r\n#if defined(_WIN64) || defined(__WIN64__)\r\n\r\n#undef idx64\r\n#define idx64 1\r\n\r\n#undef QDECL\r\n#define QDECL __cdecl\r\n\r\n#if defined( _MSC_VER )\r\n#define OS_STRING \"win_msvc64\"\r\n#elif defined __MINGW64__\r\n#define OS_STRING \"win_mingw64\"\r\n#else\r\n#define OS_STRING \"win64\"\r\n#endif\r\n\r\n#define ID_INLINE static __inline\r\n#define PATH_SEPARATOR \"\\\\\"\r\n\r\n#if defined( __WIN64__ ) \r\n#define ARCH_STRING \"x86_64\"\r\n#elif defined _M_ALPHA\r\n#define ARCH_STRING \"AXP\"\r\n#endif\r\n\r\n#define Q3_LITTLE_ENDIAN\r\n\r\n#define DLEXT \"dll\"\r\n\r\n#elif defined(_WIN32) || defined(__WIN32__)\r\n\r\n#undef QDECL\r\n#define QDECL __cdecl\r\n\r\n#if defined( _MSC_VER )\r\n#define OS_STRING \"win_msvc\"\r\n#elif defined __MINGW32__\r\n#define OS_STRING \"win_mingw\"\r\n#endif\r\n\r\n#define ID_INLINE static __inline\r\n#define PATH_SEPARATOR \"\\\\\"\r\n\r\n#if defined( _M_IX86 ) || defined( __i386__ )\r\n#define ARCH_STRING \"x86\"\r\n#elif defined _M_ALPHA\r\n#define ARCH_STRING \"AXP\"\r\n#endif\r\n\r\n#define Q3_LITTLE_ENDIAN\r\n\r\n#define DLEXT \"dll\"\r\n\r\n#endif\r\n\r\n//============================================================== MAC OS X ===\r\n\r\n#if defined(MACOS_X) || defined(__APPLE_CC__)\r\n\r\n// make sure this is defined, just for sanity's sake...\r\n#ifndef MACOS_X\r\n#define MACOS_X\r\n#endif\r\n\r\n#define OS_STRING \"macosx\"\r\n#define ID_INLINE static inline\r\n#define PATH_SEPARATOR \"/\"\r\n\r\n#ifdef __ppc__\r\n#define ARCH_STRING \"ppc\"\r\n#define Q3_BIG_ENDIAN\r\n#elif defined __i386__\r\n#define ARCH_STRING \"i386\"\r\n#define Q3_LITTLE_ENDIAN\r\n#elif defined __x86_64__\r\n#undef idx64\r\n#define idx64 1\r\n#define ARCH_STRING \"x86_64\"\r\n#define Q3_LITTLE_ENDIAN\r\n#endif\r\n\r\n#define DLEXT \"dylib\"\r\n\r\n#endif\r\n\r\n//================================================================= LINUX ===\r\n\r\n#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(ANDROID) || defined(__ANDROID__)\r\n\r\n#include <endian.h>\r\n\r\n#if defined(ANDROID) || defined(__ANDROID__)\r\n#define OS_STRING \"android\"\r\n#elif defined(__linux__)\r\n#define OS_STRING \"linux\"\r\n#else\r\n#define OS_STRING \"kFreeBSD\"\r\n#endif\r\n\r\n#define ID_INLINE static inline\r\n#define PATH_SEPARATOR \"/\"\r\n\r\n#if defined __i386__\r\n#define ARCH_STRING \"i386\"\r\n#elif defined __x86_64__\r\n#undef idx64\r\n#define idx64 1\r\n#define ARCH_STRING \"x86_64\"\r\n#elif defined __powerpc64__\r\n#define ARCH_STRING \"ppc64\"\r\n#elif defined __powerpc__\r\n#define ARCH_STRING \"ppc\"\r\n#elif defined __s390__\r\n#define ARCH_STRING \"s390\"\r\n#elif defined __s390x__\r\n#define ARCH_STRING \"s390x\"\r\n#elif defined __ia64__\r\n#define ARCH_STRING \"ia64\"\r\n#elif defined __alpha__\r\n#define ARCH_STRING \"alpha\"\r\n#elif defined __sparc__\r\n#define ARCH_STRING \"sparc\"\r\n#elif defined __arm__\r\n#define ARCH_STRING \"arm\"\r\n#elif defined __cris__\r\n#define ARCH_STRING \"cris\"\r\n#elif defined __hppa__\r\n#define ARCH_STRING \"hppa\"\r\n#elif defined __mips__\r\n#define ARCH_STRING \"mips\"\r\n#elif defined __sh__\r\n#define ARCH_STRING \"sh\"\r\n#endif\r\n\r\n#if __FLOAT_WORD_ORDER == __BIG_ENDIAN\r\n#define Q3_BIG_ENDIAN\r\n#else\r\n#define Q3_LITTLE_ENDIAN\r\n#endif\r\n\r\n#define DLEXT \"so\"\r\n\r\n#endif\r\n\r\n//=================================================================== BSD ===\r\n\r\n#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)\r\n\r\n#include <sys/types.h>\r\n#include <machine/endian.h>\r\n\r\n#ifndef __BSD__\r\n  #define __BSD__\r\n#endif\r\n\r\n#if defined(__FreeBSD__)\r\n#define OS_STRING \"freebsd\"\r\n#elif defined(__OpenBSD__)\r\n#define OS_STRING \"openbsd\"\r\n#elif defined(__NetBSD__)\r\n#define OS_STRING \"netbsd\"\r\n#endif\r\n\r\n#define ID_INLINE static inline\r\n#define PATH_SEPARATOR \"/\"\r\n\r\n#ifdef __i386__\r\n#define ARCH_STRING \"i386\"\r\n#elif defined __amd64__\r\n#undef idx64\r\n#define idx64 1\r\n#define ARCH_STRING \"amd64\"\r\n#elif defined __axp__\r\n#define ARCH_STRING \"alpha\"\r\n#endif\r\n\r\n#if BYTE_ORDER == BIG_ENDIAN\r\n#define Q3_BIG_ENDIAN\r\n#else\r\n#define Q3_LITTLE_ENDIAN\r\n#endif\r\n\r\n#define DLEXT \"so\"\r\n\r\n#endif\r\n\r\n//================================================================= SUNOS ===\r\n\r\n#ifdef __sun\r\n\r\n#include <stdint.h>\r\n#include <sys/byteorder.h>\r\n\r\n#define OS_STRING \"solaris\"\r\n#define ID_INLINE static inline\r\n#define PATH_SEPARATOR \"/\"\r\n\r\n#ifdef __i386__\r\n#define ARCH_STRING \"i386\"\r\n#elif defined __sparc\r\n#define ARCH_STRING \"sparc\"\r\n#endif\r\n\r\n#if defined( _BIG_ENDIAN )\r\n#define Q3_BIG_ENDIAN\r\n#elif defined( _LITTLE_ENDIAN )\r\n#define Q3_LITTLE_ENDIAN\r\n#endif\r\n\r\n#define DLEXT \"so\"\r\n\r\n#endif\r\n\r\n//================================================================== IRIX ===\r\n\r\n#ifdef __sgi\r\n\r\n#define OS_STRING \"irix\"\r\n#define ID_INLINE static __inline\r\n#define PATH_SEPARATOR \"/\"\r\n\r\n#define ARCH_STRING \"mips\"\r\n\r\n#define Q3_BIG_ENDIAN // SGI's MIPS are always big endian\r\n\r\n#define DLEXT \"so\"\r\n\r\n#endif\r\n\r\n//=============================================================== MORPHOS ===\r\n\r\n#ifdef __MORPHOS__\r\n\r\n#define OS_STRING \"morphos\"\r\n#define ID_INLINE static inline\r\n#define PATH_SEPARATOR \"/\"\r\n\r\n#define ARCH_STRING \"ppc\"\r\n\r\n#define Q3_BIG_ENDIAN\r\n\r\n#define DLEXT \"so\"\r\n\r\n#endif\r\n\r\n#ifdef __CYGWIN__\r\n#define OS_STRING \"cygwin\"\r\n#define ID_INLINE static inline\r\n#define PATH_SEPARATOR \"/\"\r\n\r\n#define ARCH_STRING \"x86\"\r\n\r\n#define Q3_LITTLE_ENDIAN\r\n\r\n#define DLEXT \"dll\"\r\n\r\n#endif\r\n\r\n#ifdef __DJGPP__\r\n#define OS_STRING \"msdos\"\r\n#define ID_INLINE static inline\r\n#define PATH_SEPARATOR \"/\"\r\n\r\n#define ARCH_STRING \"dos\"\r\n\r\n#define Q3_LITTLE_ENDIAN\r\n\r\n#define DLEXT \"dll\"\r\n#endif\r\n\r\n\r\n#ifdef FTE_TARGET_WEB\r\n#define OS_STRING \"emscripten\"\r\n#define ID_INLINE static inline\r\n#define PATH_SEPARATOR \"/\"\r\n\r\n#define ARCH_STRING \"web\"\r\n\r\n#define Q3_LITTLE_ENDIAN\r\n\r\n#define DLEXT \"so\"\r\n#endif\r\n\r\n#ifdef NACL\r\n#define OS_STRING \"nacl\"\r\n#define ID_INLINE static inline\r\n#define PATH_SEPARATOR \"/\"\r\n\r\n#define ARCH_STRING \"web\"\r\n\r\n#define Q3_LITTLE_ENDIAN\r\n\r\n#define DLEXT \"so\"\r\n#endif\r\n\r\n//================================================================== Q3VM ===\r\n\r\n#ifdef Q3_VM\r\n\r\n#define OS_STRING \"q3vm\"\r\n#define ID_INLINE static\r\n#define PATH_SEPARATOR \"/\"\r\n\r\n#define ARCH_STRING \"bytecode\"\r\n\r\n#define DLEXT \"qvm\"\r\n\r\n#endif\r\n\r\n//===========================================================================\r\n\r\n//catch missing defines in above blocks\r\n#if !defined( OS_STRING )\r\n#define ARCH_STRING \"unknown\"\r\n//#error \"Operating system not supported\"\r\n#endif\r\n\r\n#if !defined( ARCH_STRING )\r\n#define ARCH_STRING \"unk\"\r\n//#error \"Architecture not supported\"\r\n#endif\r\n\r\n#ifndef ID_INLINE\r\n#define ID_INLINE static\r\n//#error \"ID_INLINE not defined\"\r\n#endif\r\n\r\n#ifndef PATH_SEPARATOR\r\n#define PATH_SEPARATOR \"/\"\r\n//#error \"PATH_SEPARATOR not defined\"\r\n#endif\r\n\r\n#ifndef DLEXT\r\n#define DLEXT \"so\"\r\n//#error \"DLEXT not defined\"\r\n#endif\r\n\r\n/*\r\n//endianness\r\nshort ShortSwap (short l);\r\nint LongSwap (int l);\r\nfloat FloatSwap (const float *f);\r\n\r\n#if defined( Q3_BIG_ENDIAN ) && defined( Q3_LITTLE_ENDIAN )\r\n#error \"Endianness defined as both big and little\"\r\n#elif defined( Q3_BIG_ENDIAN )\r\n\r\n#define LittleShort(x) ShortSwap(x)\r\n#define LittleLong(x) LongSwap(x)\r\n#define LittleFloat(x) FloatSwap(&x)\r\n#define BigShort\r\n#define BigLong\r\n#define BigFloat\r\n\r\n#elif defined( Q3_LITTLE_ENDIAN )\r\n\r\n#define LittleShort\r\n#define LittleLong\r\n#define LittleFloat\r\n#define BigShort(x) ShortSwap(x)\r\n#define BigLong(x) LongSwap(x)\r\n#define BigFloat(x) FloatSwap(&x)\r\n\r\n#elif defined( Q3_VM )\r\n\r\n#define LittleShort\r\n#define LittleLong\r\n#define LittleFloat\r\n#define BigShort\r\n#define BigLong\r\n#define BigFloat\r\n\r\n#else\r\n#error \"Endianness not defined\"\r\n#endif\r\n*/\r\n\r\n\r\n//platform string\r\n#ifdef NDEBUG\r\n#define PLATFORM_STRING OS_STRING \"-\" ARCH_STRING\r\n#else\r\n#define PLATFORM_STRING OS_STRING \"-\" ARCH_STRING \"-debug\"\r\n#endif\r\n\r\n#endif\r\n\r\n#endif\r\n"
  },
  {
    "path": "src/q_shared.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\nCopyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: q_shared.c,v 1.27 2007-09-15 21:00:32 disconn3ct Exp $\n\n*/\n// q_shared.c -- functions shared by all subsystems\n\n#include \"quakedef.h\"\n#include \"q_shared.h\"\n#include \"r_framestats.h\"\n\n/*\n============================================================================\n                        LIBRARY REPLACEMENT FUNCTIONS\n============================================================================\n*/\n\nint Q_atoi (const char *str)\n{\n\tint val, sign, c;\n\n\tif (!str) return 0;\n\n\tfor (; *str && *str <= ' '; str++);\n\n\tif (*str == '-') {\n\t\tsign = -1;\n\t\tstr++;\n\t} else {\n\t\tif (*str == '+')\n\t\t\tstr++;\n\n\t\tsign = 1;\n\t}\n\n\tval = 0;\n\n\t// check for hex\n\tif (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) {\n\t\tstr += 2;\n\t\twhile (1) {\n\t\t\tc = *str++;\n\t\t\tif (c >= '0' && c <= '9')\n\t\t\t\tval = (val << 4) + c - '0';\n\t\t\telse if (c >= 'a' && c <= 'f')\n\t\t\t\tval = (val << 4) + c - 'a' + 10;\n\t\t\telse if (c >= 'A' && c <= 'F')\n\t\t\t\tval = (val << 4) + c - 'A' + 10;\n\t\t\telse\n\t\t\t\treturn val*sign;\n\t\t}\n\t}\n\n\t// check for character\n\tif (str[0] == '\\'')\n\t\treturn sign * str[1];\n\n\t// assume decimal\n\twhile (1) {\n\t\tc = *str++;\n\t\tif (c <'0' || c > '9')\n\t\t\treturn val*sign;\n\t\tval = val * 10 + c - '0';\n\t}\n\n\treturn 0;\n}\n\nfloat Q_atof (const char *str)\n{\n\tdouble val;\n\tint sign, c, decimal, total;\n\n\tif (!str) return 0;\n\n\tfor (; *str && *str <= ' '; str++); // VVD: from MVDSV :-)\n/*\n\t//R00k - qbism// 1999-12-27 ATOF problems with leading spaces fix by Maddes\n\twhile ((*str) && (*str<=' '))\n\t{\n\t\tstr++;\n\t}\n\t//R00k - qbism// 1999-12-27 ATOF problems with leading spaces fix by Maddes\n*/\n\tif (*str == '-') {\n\t\tsign = -1;\n\t\tstr++;\n\t} else {\n\t\tif (*str == '+')\n\t\t\tstr++;\n\n\t\tsign = 1;\n\t}\n\n\tval = 0;\n\n\t// check for hex\n\tif (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) {\n\t\tstr += 2;\n\t\twhile (1) {\n\t\t\tc = *str++;\n\t\t\tif (c >= '0' && c <= '9')\n\t\t\t\tval = (val * 16) + c - '0';\n\t\t\telse if (c >= 'a' && c <= 'f')\n\t\t\t\tval = (val * 16) + c - 'a' + 10;\n\t\t\telse if (c >= 'A' && c <= 'F')\n\t\t\t\tval = (val * 16) + c - 'A' + 10;\n\t\t\telse\n\t\t\t\treturn val*sign;\n\t\t}\n\t}\n\n\t// check for character\n\tif (str[0] == '\\'')\n\t\treturn sign * str[1];\n\n\t// assume decimal\n\tdecimal = -1;\n\ttotal = 0;\n\twhile (1) {\n\t\tc = *str++;\n\t\tif (c == '.') {\n\t\t\tdecimal = total;\n\t\t\tcontinue;\n\t\t}\n\t\tif (c <'0' || c > '9')\n\t\t\tbreak;\n\t\tval = val*10 + c - '0';\n\t\ttotal++;\n\t}\n\n\tif (decimal == -1)\n\t\treturn val * sign;\n\twhile (total > decimal) {\n\t\tval /= 10;\n\t\ttotal--;\n\t}\n\n\treturn val * sign;\n}\n\n// removes trailing zeros\nchar *Q_ftos (float value)\n{\n\tstatic char str[128];\n\tint\ti;\n\n\tsnprintf (str, sizeof(str), \"%f\", value);\n\n\tfor (i=strlen(str)-1 ; i>0 && str[i]=='0' ; i--)\n\t\tstr[i] = 0;\n\tif (str[i] == '.')\n\t\tstr[i] = 0;\n\n\treturn str;\n}\n\n// like strcpy, but allow overlapping strings\nchar *Q_strcpy( char *to, char *from )\n{\n\tint i;\n\n\tif (to < from) {\n\t\tfor (i = 0; from[i] != 0; i++) {\n\t\t\tto[i] = from[i];\n\t\t}\n\t\tto[i] = from[i];\n\t} else {\n\t\tfor (i = strlen(from); i >= 0; i--) {\n\t\t\tto[i] = from[i];\n\t\t}\n\t}\n\n\treturn to;\n}\n\nchar* Q_strlwr(char* s1) {\n\tchar* s;\n\n\ts = s1;\n\twhile (*s) {\n\t\t*s = tolower(*s);\n\t\ts++;\n\t}\n\treturn s1;\n}\n\nchar* Q_strupr(char* s1) {\n\tchar* s;\n\n\ts = s1;\n\twhile (*s) {\n\t\t*s = toupper(*s);\n\t\ts++;\n\t}\n\treturn s1;\n}\n\n// Added by VVD {\n#ifdef _WIN32\nint qsnprintf(char *buffer, size_t count, char const *format, ...)\n{\n\tint ret;\n\tva_list argptr;\n\tif (!count) return 0;\n\tva_start(argptr, format);\n\tret = _vsnprintf(buffer, count, format, argptr);\n\tbuffer[count - 1] = 0;\n\tva_end(argptr);\n\treturn ret;\n}\nint qvsnprintf(char *buffer, size_t count, const char *format, va_list argptr)\n{\n\tint ret;\n\tif (!count) return 0;\n\tret = _vsnprintf(buffer, count, format, argptr);\n\tbuffer[count - 1] = 0;\n\treturn ret;\n}\n#endif\n\n#if defined(__linux__) || defined(_WIN32)\n\nsize_t strlcpy(char *dst, const char *src, size_t siz)\n{\n\tregister char *d = dst;\n\tregister const char *s = src;\n\tregister size_t n = siz;\n\n\t/* Copy as many bytes as will fit */\n\tif (n != 0 && --n != 0) {\n\t\tdo {\n\t\t\tif ((*d++ = *s++) == 0)\n\t\t\t\tbreak;\n\t\t} while (--n != 0);\n\t}\n\n\t/* Not enough room in dst, add NUL and traverse rest of src */\n\tif (n == 0) {\n\t\tif (siz != 0)\n\t\t\t*d = '\\0'; /* NUL-terminate dst */\n\t\twhile (*s++)\n\t\t\t;\n\t}\n\n\treturn(s - src - 1);\t/* count does not include NUL */\n}\n\nsize_t strlcat(char *dst, const char *src, size_t siz)\n{\n\tregister char *d = dst;\n\tregister const char *s = src;\n\tregister size_t n = siz;\n\tsize_t dlen;\n\n\t/* Find the end of dst and adjust bytes left but don't go past end */\n\twhile (n-- != 0 && *d != '\\0')\n\t\td++;\n\tdlen = d - dst;\n\tn = siz - dlen;\n\n\tif (n == 0)\n\t\treturn(dlen + strlen(s));\n\twhile (*s != '\\0') {\n\t\tif (n != 1) {\n\t\t\t*d++ = *s;\n\t\t\tn--;\n\t\t}\n\t\ts++;\n\t}\n\t*d = '\\0';\n\n\treturn(dlen + (s - src));       /* count does not include NUL */\n}\n\nchar *strnstr(const char *s, const char *find, size_t slen)\n{\n\tchar c, sc;\n\tsize_t len;\n\n\tif ((c = *find++) != '\\0') \n\t{\n\t\tlen = strlen(find);\n\n\t\tdo \n\t\t{\n\t\t\tdo \n\t\t\t{\n\t\t\t\tif ((sc = *s++) == '\\0' || slen-- < 1)\n\t\t\t\t\treturn (NULL);\n\t\t\t} \n\t\t\twhile (sc != c);\n\t\t\t\n\t\t\tif (len > slen)\n\t\t\t\treturn (NULL);\n\n\t\t} while (strncmp(s, find, len) != 0);\n\t\ts--;\n\t}\n\treturn ((char *)s);\n}\n#endif\n// A Case-insensitive strstr.\nchar *strstri(const char *text, const char *find)\n{\n\tchar *s = (char *)text;\n\tint findlen = strlen(find);\n\n\t// Empty substring, return input (like strstr).\n\tif (findlen == 0)\n\t{\n\t\treturn s;\n\t}\n\n\twhile (*s)\n\t{\n\t\t// Check if we can find the substring.\n\t\tif (!strncasecmp(s, find, findlen))\n\t\t{\n\t\t\treturn s;\n\t\t}\n\n\t\ts++;\n\t}\n\n\treturn NULL;\n}\n\n// Added by VVD }\n\n//\n// Finds the first occurance of a char in a string starting from the end.\n// \nchar *strchrrev(char *str, char chr)\n{\n\tchar *firstchar = str;\n\tfor (str = str + strlen(str)-1; str >= firstchar; str--)\n\t\tif (*str == chr)\n\t\t\treturn str;\n\n\treturn NULL;\n}\n\n// D-Kure: added for fte vfs\nint wildcmp(char *wild, char *string)\n{\n\tchar *cp=NULL, *mp=NULL;\n\n\twhile ((*string) && (*wild != '*'))\n\t{\n\t\tif ((*wild != *string) && (*wild != '?'))\n\t\t{\n\t\t\treturn 0;\n\t\t}\n\t\twild++;\n\t\tstring++;\n\t}\n\n\twhile (*string)\n\t{\n\t\tif (*wild == '*')\n\t\t{\n\t\t\tif (!*++wild)   //a * at the end of the wild string matches anything the checked string has\n\t\t\t{\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tmp = wild;\n\t\t\tcp = string+1;\n\t\t}\n\t\telse if ((*wild == *string) || (*wild == '?'))\n\t\t{\n\t\t\twild++;\n\t\t\tstring++;\n\t\t}\n\t\telse\n\t\t{\n\t\t\twild = mp;\n\t\t\tstring = cp++;\n\t\t}\n\t}\n\n\twhile (*wild == '*')\n\t{\n\t\twild++;\n\t}\n\treturn !*wild;\n}\n\nwchar char2wc (char c)\n{\n\treturn (wchar)(unsigned char)c;\n}\n\nchar wc2char (wchar wc)\n{\n\treturn (wc <= 255) ? (char)wc : '?';\n}\n\nwchar *str2wcs (const char *s)\n{\n\tstatic wchar buf[65536]; //ouch! ouch!\n\tsize_t i;\n\n\tfor (i = 0; i < (sizeof(buf)/sizeof(buf[0])) - 1; i++) {\n\t\tif (s[i] == 0)\n\t\t\tbreak;\n\t\tbuf[i] = (short)(unsigned char)s[i];\n\t}\n\tbuf[i] = 0;\n\treturn buf;\n}\n\nchar *wcs2str (const wchar *ws)\n{\n\tstatic char buf[65536];\t//ouch! ouch!\n\tsize_t i;\n\n\tfor (i = 0; i < sizeof(buf) - 1; i++) {\n\t\tif (ws[i] == 0)\n\t\t\tbreak;\n\t\tbuf[i] = ws[i] <= 255 ? (char)ws[i] : '?';\n\t}\n\tbuf[i] = 0;\n\treturn buf;\n}\n\n#ifndef _WIN32\n// disconnect: We assume both str and strSearch are NULL-terminated\nwchar *qwcsstr (const wchar *str, const wchar *strSearch)\n{\n\tsize_t i, j, search;\n\tsize_t str_len;\n\tsize_t strSearch_len;\n\n\tstr_len = qwcslen (str);\n\tstrSearch_len = qwcslen (strSearch);\n\tsearch = 0;\n\n\tif (str_len && strSearch_len) { // paranoid check\n\t\tfor (i = 0; i < str_len - 1; i++) { // -1 because last wchar is NULL\n\t\t\tfor (j = 0; j <  strSearch_len - 1; j++) { // -1 because last wchar is NULL\n\t\t\t\tif (str [j + i] != strSearch[j]) {\n\t\t\t\t\tsearch = 0;\n\t\t\t\t\tbreak;\n\t\t\t\t} else {\n\t\t\t\t\tsearch = i;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (search)\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn (wchar *)(str + search);\n}\n\nsize_t qwcslen (const wchar *ws)\n{\n\tsize_t i = 0;\n\twhile (*ws++)\n\t\ti++;\n\treturn i;\n}\n\nwchar *qwcscpy (wchar *dest, const wchar *src)\n{\n\twhile (*src)\n\t\t*dest++ = *src++;\n\t*dest = 0;\n\treturn dest;\n}\n#endif\n\n// NOTE: size is not the number of bytes to copy, but the number of characters. sizeof(dest) / sizeof(wchar) should be used.\nsize_t qwcslcpy (wchar *dst, const wchar *src, size_t size)\n{\n\tsize_t len = qwcslen (src);\n\n\tif (len < size) {\n\t\t// it'll fit\n\t\tmemmove(dst, src, (len + 1) * sizeof(wchar));\n\t\treturn len;\n\t}\n\n\tif (size == 0)\n\t\treturn len;\n\n\tassert (size >= 0);\t// if a negative size was passed, then we're fucked\n\n\tmemcpy (dst, src, (size - 1) * sizeof(wchar));\n\tdst[size - 1] = 0;\n\n\treturn len;\n}\n\nsize_t qwcslcat (wchar *dst, const wchar *src, size_t size)\n{\n\tsize_t dstlen = qwcslen (dst);\n\tsize_t srclen = qwcslen (src);\n\tsize_t len = dstlen + srclen;\n\n\tif (len < size) {\n\t\t// it'll fit\n\t\tmemcpy (dst + dstlen, src, (srclen + 1) * sizeof(wchar));\n\t\treturn len;\n\t}\n\n\tif (dstlen >= size - 1)\n\t\treturn srclen + size;\n\n\tif (size == 0)\n\t\treturn srclen;\n\n\tassert (size >= 0);\t// if a negative size was passed, then we're fucked\n\n\tmemcpy (dst + dstlen, src, (size - 1 - dstlen)*sizeof(wchar));\n\tdst[size - 1] = 0;\n\n\treturn len;\n}\n\n#ifndef qwcschr\nwchar *qwcschr (const wchar *ws, wchar wc)\n{\n\twhile (*ws) {\n\t\tif (*ws == wc)\n\t\t\treturn (wchar *)ws;\n\t\tws++;\n\t}\n\treturn NULL;\n}\n#endif\n\n#ifndef qwcschr\nwchar *qwcsrchr (const wchar *ws, wchar wc)\n{\n\twchar *p = NULL;\n\twhile (*ws) {\n\t\tif (*ws == wc)\n\t\t\tp = (wchar *)ws;\n\t\tws++;\n\t}\n\treturn p;\n}\n#endif\n\nwchar *Q_wcsdup (const wchar *src)\n{\n\twchar *out;\n\tsize_t size = (qwcslen(src) + 1) * sizeof(wchar);\n\tout = Q_malloc (size);\n\tmemcpy (out, src, size);\n\treturn out;\n}\n\n\nstatic qbool Q_glob_match_after_star (const char *pattern, const char *text)\n{\n\tchar c, c1;\n\tconst char *p = pattern, *t = text;\n\n\twhile ((c = *p++) == '?' || c == '*') {\n\t\tif (c == '?' && *t++ == '\\0')\n\t\t\treturn false;\n\t}\n\n\tif (c == '\\0')\n\t\treturn true;\n\n\tfor (c1 = ((c == '\\\\') ? *p : c); ; ) {\n\t\tif (tolower(*t) == c1 && Q_glob_match (p - 1, t))\n\t\t\treturn true;\n\t\tif (*t++ == '\\0')\n\t\t\treturn false;\n\t}\n}\n\n/*\nMatch a pattern against a string.\nBased on Vic's Q_WildCmp, which is based on Linux glob_match.\nWorks like glob_match, except that sets ([]) are not supported.\n\nA match means the entire string TEXT is used up in matching.\n\nIn the pattern string, `*' matches any sequence of characters,\n`?' matches any character. Any other character in the pattern\nmust be matched exactly.\n\nTo suppress the special syntactic significance of any of `*?\\'\nand match the character exactly, precede it with a `\\'.\n*/\nqbool Q_glob_match (const char *pattern, const char *text)\n{\n\tchar c;\n\n\twhile ((c = *pattern++) != '\\0') {\n\t\tswitch (c) {\n\t\t\tcase '?':\n\t\t\t\tif (*text++ == '\\0')\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tcase '\\\\':\n\t\t\t\tif (tolower(*pattern++) != tolower(*text++))\n\t\t\t\t\treturn false;\n\t\t\t\tbreak;\n\t\t\tcase '*':\n\t\t\t\treturn Q_glob_match_after_star(pattern, text);\n\t\t\tdefault:\n\t\t\t\tif (tolower(c) != tolower(*text++))\n\t\t\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn (*text == '\\0');\n}\n\nextern void Com_Printf (char *fmt, ...);\nunsigned int Com_HashKey (const char *str) {\n\tunsigned int hash = 0;\n\tint c;\n\n\tif (!str) {\n\t\tCom_Printf(\"warning: Com_HashKey called with NULL argument\\n\");\n\t\treturn 0;\n\t}\n\n\t// the (c&~32) makes it case-insensitive\n\t// hash function known as sdbm, used in gawk\n\twhile ((c = *str++))\n        hash = (c &~ 32) + (hash << 6) + (hash << 16) - hash;\n\n    return hash;\n}\n\n\n/*\n============================================================================\n                         BYTE ORDER FUNCTIONS\n============================================================================\n*/\n\n/*short ShortSwap (short l) {\n\tbyte b1, b2;\n\n\tb1 = l & 255;\n\tb2 = (l >> 8) & 255;\n\n\treturn (b1 << 8) + b2;\n}\n\nint LongSwap (int l) {\n\tbyte b1, b2, b3, b4;\n\n\tb1 = l & 255;\n\tb2 = (l >> 8) & 255;\n\tb3 = (l >> 16) & 255;\n\tb4 = (l >> 24) & 255;\n\n\treturn ((int) b1 << 24) + ((int) b2 << 16) + ((int) b3 << 8) + b4;\n}\n\nfloat FloatSwap (float f) {\n\tunion {\n\t\tfloat\tf;\n\t\tbyte\tb[4];\n\t} dat1, dat2;\n\n\tdat1.f = f;\n\tdat2.b[0] = dat1.b[3];\n\tdat2.b[1] = dat1.b[2];\n\tdat2.b[2] = dat1.b[1];\n\tdat2.b[3] = dat1.b[0];\n\treturn dat2.f;\n}*/\n\n#ifndef ShortSwap\nshort ShortSwap (short s)\n{\n\tunion\n\t{\n\t\tshort\ts;\n\t\tbyte\tb[2];\n\t} dat1, dat2;\n\tdat1.s = s;\n\tdat2.b[0] = dat1.b[1];\n\tdat2.b[1] = dat1.b[0];\n\treturn dat2.s;\n}\n#endif\n\n#ifndef LongSwap\nint LongSwap (int l)\n{\n\tunion\n\t{\n\t\tint\t\tl;\n\t\tbyte\tb[4];\n\t} dat1, dat2;\n\tdat1.l = l;\n\tdat2.b[0] = dat1.b[3];\n\tdat2.b[1] = dat1.b[2];\n\tdat2.b[2] = dat1.b[1];\n\tdat2.b[3] = dat1.b[0];\n\treturn dat2.l;\n}\n#endif\n\nfloat FloatSwap (float f)\n{\n\tunion\n\t{\n\t\tfloat\tf;\n\t\tbyte\tb[4];\n\t} dat1, dat2;\n\tdat1.f = f;\n\tdat2.b[0] = dat1.b[3];\n\tdat2.b[1] = dat1.b[2];\n\tdat2.b[2] = dat1.b[1];\n\tdat2.b[3] = dat1.b[0];\n\treturn dat2.f;\n}\n\n#ifdef __PDP_ENDIAN\nint LongSwapPDP2Big (int l)\n{\n\tunion\n\t{\n\t\tint\t\tl;\n\t\tbyte\tb[4];\n\t} dat1, dat2;\n\tdat1.l = l;\n\tdat2.b[0] = dat1.b[1];\n\tdat2.b[1] = dat1.b[0];\n\tdat2.b[2] = dat1.b[3];\n\tdat2.b[3] = dat1.b[2];\n\treturn dat2.l;\n}\n\nint LongSwapPDP2Lit (int l)\n{\n\tunion\n\t{\n\t\tint\t\tl;\n\t\tshort\ts[2];\n\t} dat1, dat2;\n\tdat1.l = l;\n\tdat2.s[0] = dat1.s[1];\n\tdat2.s[1] = dat1.s[0];\n\treturn dat2.l;\n}\n\nfloat FloatSwapPDP2Big (float f)\n{\n\tunion\n\t{\n\t\tfloat\tf;\n\t\tbyte\tb[4];\n\t} dat1, dat2;\n\tdat1.f = f;\n\tdat2.b[0] = dat1.b[1];\n\tdat2.b[1] = dat1.b[0];\n\tdat2.b[2] = dat1.b[3];\n\tdat2.b[3] = dat1.b[2];\n\treturn dat2.f;\n}\n\nfloat FloatSwapPDP2Lit (float f)\n{\n\tunion\n\t{\n\t\tfloat\tf;\n\t\tshort\ts[2];\n\t} dat1, dat2;\n\tdat1.f = f;\n\tdat2.s[0] = dat1.s[1];\n\tdat2.s[1] = dat1.s[0];\n\treturn dat2.f;\n}\n#endif\n\n// Extract integers from buffers\nunsigned int BuffBigLong (const unsigned char *buffer)\n{\n\treturn (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];\n}\n\nunsigned short BuffBigShort (const unsigned char *buffer)\n{\n\treturn (buffer[0] << 8) | buffer[1];\n}\n\nunsigned int BuffLittleLong (const unsigned char *buffer)\n{\n\treturn (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0];\n}\n\nunsigned short BuffLittleShort (const unsigned char *buffer)\n{\n\treturn (buffer[1] << 8) | buffer[0];\n}\n\n//===========================================================================\n\nvoid SZ_InitEx2(sizebuf_t* buf, byte* data, int length, qbool allowoverflow, sizebuf_overflow_handler_func_t overflow_handler)\n{\n\tmemset(buf, 0, sizeof(*buf));\n\tbuf->data = data;\n\tbuf->maxsize = length;\n\tbuf->allowoverflow = allowoverflow;\n\tbuf->overflow_handler = overflow_handler;\n}\n\nvoid SZ_InitEx (sizebuf_t *buf, byte *data, int length, qbool allowoverflow)\n{\n\tSZ_InitEx2(buf, data, length, allowoverflow, NULL);\n}\n\nvoid SZ_Init (sizebuf_t *buf, byte *data, int length)\n{\n\tSZ_InitEx (buf, data, length, false);\n}\n\nvoid SZ_Clear (sizebuf_t *buf) {\n\tbuf->cursize = 0;\n\tbuf->overflowed = false;\n}\n\nvoid *SZ_GetSpace(sizebuf_t *buf, int length)\n{\n\tvoid *data;\n\n\tif (buf->cursize + length > buf->maxsize && buf->overflow_handler) {\n\t\tbuf->overflow_handler(buf, length);\n\t}\n\n\tif (buf->cursize + length > buf->maxsize) {\n\t\tif (!buf->allowoverflow)\n\t\t\tSys_Error (\"SZ_GetSpace: overflow without allowoverflow set (%i/%i/%i)\", buf->cursize, length, buf->maxsize);\n\n\t\tif (length > buf->maxsize)\n\t\t\tSys_Error (\"SZ_GetSpace: %i/%i is > full buffer size\", length, buf->maxsize);\n\n\t\tSys_Printf (\"SZ_GetSpace: overflow\\n\");\t// because Com_Printf may be redirected\n\t\tSZ_Clear (buf);\n\t\tbuf->overflowed = true;\n\t}\n\n\tdata = buf->data + buf->cursize;\n\tbuf->cursize += length;\n\n\treturn data;\n}\n\nvoid SZ_Write(sizebuf_t *buf, const void *data, int length)\n{\n\tbyte* dest = SZ_GetSpace(buf, length);\n\n\tmemcpy(dest, data, length);\n}\n\nvoid SZ_Print(sizebuf_t *buf, char *data)\n{\n\tint len = strlen(data) + 1;\n\n\t// Remove trailing '\\0'\n\tif (buf->cursize && !buf->data[buf->cursize - 1]) {\n\t\t--buf->cursize;\n\t}\n\n\tSZ_Write(buf, data, len);\n}\n\n//============================================================================\n\n#ifdef DEBUG_MEMORY_ALLOCATIONS\nstatic unsigned int allocation_number = 0;\n\ntypedef struct ezquake_memory_block_s {\n\tchar filename[MAX_OSPATH];\n\tchar label[64];\n\tint line_number;\n\tsize_t size;\n\tunsigned int allocation_number;\n} ezquake_memory_block_t;\n\n#define MEMORY_BLOCK_FOR_PTR(ptr) ((ezquake_memory_block_t*) (((intptr_t)ptr) - sizeof(ezquake_memory_block_t)));\n#define PTR_FOR_MEMORY_BLOCK(block) (void*)(((intptr_t)block) + sizeof(ezquake_memory_block_t))\n\nstatic void Q_malloc_register(const char* file, int line)\n{\n\tif (frameStats.hotloop) {\n\t\t++frameStats.hotloop_mallocs;\n\t\tif (developer.integer) {\n\t\t\tframeStats.hotloop = false;\n\t\t\tCom_Printf(\"HotMalloc: %s[%d]\\n\", file, line);\n\t\t\tframeStats.hotloop = true;\n\t\t}\n\t}\n\t++frameStats.mallocs;\n}\n\n#endif\n\n/*\n** Q_malloc\n**\n** Use it instead of malloc so that if memory allocation fails,\n** the program exits with a message saying there's not enough memory\n** instead of crashing after trying to use a NULL pointer\n*/\n#ifdef DEBUG_MEMORY_ALLOCATIONS\nvoid* Q_malloc_debug(size_t size, const char* file, int line, const char* label)\n#else\nvoid* Q_malloc(size_t size)\n#endif\n{\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tvoid *p;\n\tezquake_memory_block_t* block = malloc(sizeof(ezquake_memory_block_t) + size);\n\n\tSys_Printf(\"\\nmemory,alloc,%u,%s,%d,%u,%s\\n\", allocation_number, file, line, size, label ? label : \"\");\n\n\tif (!block) {\n\t\tSys_Error(\"Q_malloc: Not enough memory free; check disk space\\n\");\n\t\treturn NULL;\n\t}\n\n\tstrlcpy(block->filename, file, sizeof(block->filename));\n\tif (label) {\n\t\tstrlcpy(block->label, label, sizeof(block->label));\n\t}\n\tblock->line_number = line;\n\tblock->size = size;\n\tblock->allocation_number = allocation_number++;\n\n\tQ_malloc_register(file, line);\n\n\tp = PTR_FOR_MEMORY_BLOCK(block);\n#else\n\tvoid *p = malloc(size);\n\n\tif (!p) {\n\t\tSys_Error(\"Q_malloc: Not enough memory free; check disk space\\n\");\n\t}\n#endif\n\n//#ifndef _DEBUG\n\tmemset(p, 0, size);\n//#endif\n\n\treturn p;\n}\n\n#ifdef DEBUG_MEMORY_ALLOCATIONS\nvoid *Q_calloc_debug(size_t n, size_t size, const char* file, int line, const char* label)\n#else\nvoid *Q_calloc(size_t n, size_t size)\n#endif\n{\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tvoid *p = Q_malloc_debug(n * size, file, line, label);\n#else\n\tvoid *p = calloc(n, size);\n\n\tif (!p) {\n\t\tSys_Error(\"Q_calloc: Not enough memory free; check disk space\\n\");\n\t}\n#endif\n\n\treturn p;\n}\n\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n#define EZQUAKE_SAFE_REALLOC\nvoid* Q_realloc_debug(void* p, size_t newsize, const char* file, int line, const char* label)\n{\n\tezquake_memory_block_t* block = NULL;\n\tsize_t old_size = 0;\n\n#ifdef EZQUAKE_SAFE_REALLOC\n\tif (p && old_size)\n\t{\n\t\tvoid* new_buffer;\n\n\t\t// To be sure we're not assuming the memory block is extended, allocate and re-allocate (can catch some bugs)\n\t\tblock = MEMORY_BLOCK_FOR_PTR(p);\n\t\tnew_buffer = Q_malloc_debug(newsize + sizeof(ezquake_memory_block_t), file, line, label);\n\t\tif (!new_buffer) {\n\t\t\treturn NULL;\n\t\t}\n\t\tmemcpy(new_buffer, p, block->size);\n\t\tQ_free_debug(p, file, line);\n\t\treturn new_buffer;\n\t}\n#endif\n\n\tif (p) {\n\t\tblock = MEMORY_BLOCK_FOR_PTR(p);\n\t\tSys_Printf(\"\\nmemory,free(realloc),%u,%s,%d,%u,%s,%d\\n\", block->allocation_number, block->filename, block->line_number, block->size, file, line);\n\t}\n\tSys_Printf(\"\\nmemory,alloc(realloc),%u,%s,%d,%u,%s\\n\", allocation_number, file, line, newsize, label ? label : \"\");\n\n\tblock = p = realloc(block, newsize + sizeof(ezquake_memory_block_t));\n\n\tstrlcpy(block->filename, file, sizeof(block->filename));\n\tblock->line_number = line;\n\tblock->size = newsize;\n\tblock->allocation_number = allocation_number++;\n\tif (label) {\n\t\tstrlcpy(block->label, label, sizeof(block->label));\n\t}\n\tQ_malloc_register(file, line);\n\n\treturn (void*)(((intptr_t)p) + sizeof(ezquake_memory_block_t));\n}\n#else\nvoid *Q_realloc(void *p, size_t newsize)\n{\n\tif (!(p = realloc(p, newsize))) {\n\t\tSys_Error(\"Q_realloc: Not enough memory free; check disk space\\n\");\n\t}\n\n\treturn p;\n}\n#endif // !DEBUG_MEMORY_ALLOCATIONS\n\n#ifdef DEBUG_MEMORY_ALLOCATIONS\nvoid Q_free_debug(void* ptr, const char* file, int line)\n{\n\tif (ptr) {\n\t\tezquake_memory_block_t* block = MEMORY_BLOCK_FOR_PTR(ptr);\n\n\t\tSys_Printf(\"\\nmemory,free,%u,%s,%d,%u,%s,%d\\n\", block->allocation_number, block->filename, block->line_number, block->size, file, line);\n\n\t\tfree(block);\n\t}\n}\n\nchar *Q_strdup_debug(const char *src, const char* file, int line, const char* label)\n{\n\tif (src) {\n\t\tsize_t size = strlen(src) + 1;\n\t\tchar *p = Q_malloc_debug(size, file, line, label);\n\n\t\tif (!p) {\n\t\t\tSys_Error(\"Q_strdup: Not enough memory free; check disk space\\n\");\n\t\t}\n\n\t\tstrlcpy(p, src, size);\n\n\t\treturn p;\n\t}\n\treturn NULL;\n}\n\n#else\nchar *Q_strdup(const char *src)\n{\n\tif (src) {\n\t\tchar *p = strdup(src);\n\n\t\tif (!p) {\n\t\t\tSys_Error(\"Q_strdup: Not enough memory free; check disk space\\n\");\n\t\t}\n\t\treturn p;\n\t}\n\treturn NULL;\n}\n#endif\n\n// PLZ free returned string after it no longer need!!!\n#ifdef DEBUG_MEMORY_ALLOCATIONS\nchar *Q_wcs2str_malloc_debug(const wchar *ws, const char* file, int line)\n#else\nchar *Q_wcs2str_malloc(const wchar *ws)\n#endif\n{\n\tsize_t i;\n\tsize_t len = qwcslen(ws);\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tchar *buf = (char *)Q_malloc_debug(len + 1, file, line, NULL);\n\tQ_malloc_register(file, line);\n#else\n\tchar *buf = (char *)Q_malloc(len + 1);\n#endif\n\n\tfor (i = 0; i < len; i++) {\n\t\tif (ws[i] == 0)\n\t\t\tbreak;\n\t\tbuf[i] = ws[i] <= 255 ? (char)ws[i] : '?';\n\t}\n\tbuf[i] = 0;\n\treturn buf;\n}\n\n#ifdef _WIN64\n#undef strlen\nint Q_strlen(const char* s)\n{\n\treturn (int)strlen(s);\n}\n#endif\n\n// case insensitive and red-insensitive compare\nint Q_strcmp2(const char * s1, const char * s2)\n{\n\tif (s1 == NULL && s2 == NULL)\n\t\treturn 0;\n\n\tif (s1 == NULL)\n\t\treturn -1;\n\n\tif (s2 == NULL)\n\t\treturn 1;\n\n\twhile (*s1 || *s2) {\n\t\tif (tolower(*s1 & 0x7f) != tolower(*s2 & 0x7f)) {\n\t\t\treturn tolower(*s1 & 0x7f) - tolower(*s2 & 0x7f);\n\t\t}\n\t\ts1++;\n\t\ts2++;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/q_shared.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: q_shared.h,v 1.36 2007-10-03 14:00:04 dkure Exp $\n\n*/\n// q_shared.h -- functions shared by all subsystems\n\n#ifndef __Q_SHARED_H__\n#define __Q_SHARED_H__\n\n#include <math.h>\n#include <string.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <ctype.h>\n#include <assert.h>\n\n#define wchar unsigned short\t// 16-bit Unicode char\n\n#undef true\n#undef false\ntypedef enum {false, true} qbool;\n\n#include \"mathlib.h\"\n#include \"sys.h\"\n\n#if defined(_MSC_VER) | defined (__INTEL_COMPILER)\n#define unlink _unlink\n#define strdup _strdup\n#endif\n\n#ifdef _MSC_VER\n#pragma warning( disable : 4244 4127 4201 4214 4514 4305 4115 4018 4996)\n#endif\n\n#ifdef __INTEL_COMPILER\n#pragma warning( disable : 188)\n#endif\n\ntypedef unsigned char byte;\n\n#ifndef NULL\n#define NULL ((void *) 0)\n#endif\n\n\n#ifdef _WIN32\n#define IS_SLASH(c) ((c) == '/' || (c) == '\\\\')\n#else\n#define IS_SLASH(c) ((c) == '/')\n#endif\n\n#ifndef min\n#define min(a,b) ((a) < (b) ? (a) : (b))\n#endif\n#ifndef max\n#define max(a,b) ((a) > (b) ? (a) : (b))\n#endif\n\n//#define bound(a,b,c) (max((a), min((b), (c))))\n#define bound(a,b,c) ((a) >= (c) ? (a) : \\\n\t\t\t\t\t(b) < (a) ? (a) : (b) > (c) ? (c) : (b))\n\n#define isspace2(c) ((c) == 0x09 || (c) == 0x0D || (c) == 0x0A || (c) == 0x20)\n\n//============================================================================\n\n#define\tMINIMUM_MEMORY\t\t0x550000\n\n#define\tMAX_QPATH\t\t\t64\t\t// max length of a quake game pathname\n#define\tMAX_OSPATH\t\t\t260\t\t// max length of a filesystem pathname\n\n#define\tON_EPSILON\t\t\t0.1\t\t// point on plane side epsilon\n\n//============================================================================\n\nstruct sizebuf_s;\n\ntypedef void (*sizebuf_overflow_handler_func_t)(struct sizebuf_s*, int);\n\ntypedef struct sizebuf_s {\n\tqbool\tallowoverflow;\t// if false, do a Sys_Error\n\tqbool\toverflowed;\t\t// set to true if the buffer size failed\n\tbyte\t*data;\n\tint\t\tmaxsize;\n\tint\t\tcursize;\n\tsizebuf_overflow_handler_func_t overflow_handler;\n} sizebuf_t;\n\n#define MSG_HasOverflowHandler(m) ((m)->overflow_handler != NULL)\n\nextern char *com_args_original;\n\nvoid SZ_Init(sizebuf_t *buf, byte *data, int length);\nvoid SZ_InitEx(sizebuf_t *buf, byte *data, int length, qbool allowoverflow);\nvoid SZ_InitEx2(sizebuf_t* buf, byte* data, int length, qbool allowoverflow, sizebuf_overflow_handler_func_t overflow_handler);\nvoid SZ_Clear(sizebuf_t *buf);\nvoid *SZ_GetSpace(sizebuf_t *buf, int length);\nvoid SZ_Write(sizebuf_t *buf, const void *data, int length);\nvoid SZ_Print(sizebuf_t *buf, char *data);\t// strcats onto the sizebuf\n\n//============================================================================\n\nshort\tShortSwap (short l);\nint\t\tLongSwap (int l);\nfloat\tFloatSwap (float f);\nint\t\tLongSwapPDP2Big (int l);\nint\t\tLongSwapPDP2Lit (int l);\nfloat\tFloatSwapPDP2Big (float f);\nfloat\tFloatSwapPDP2Lit (float f);\n\n#if defined(_MSC_VER) && _MSC_VER >= 1400\n// vc++ 2005 and up has intrinsics for the BSWAP instruction.\n#define ShortSwap _byteswap_ushort\n#define LongSwap _byteswap_ulong\n#endif\n\n//======================= ENDIAN DECTECTION ==================================\n//======================= WIN32 DEFINES ======================================\n#ifdef _WIN32\n#define __LITTLE_ENDIAN__\n#endif\n\n//======================= LINUX DEFINES ======================================\n#ifdef __linux__\n\n#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && !defined(__PDP_ENDIAN__)\n\n#if __FLOAT_WORD_ORDER == __BIG_ENDIAN\n#define __BIG_ENDIAN__\n#elif __BYTE_ORDER == __ORDER_BIG_ENDIAN\n#define __BIG_ENDIAN__\n#elif __FLOAT_WORD_ORDER == __LITTLE_ENDIAN\n#define __LITTLE_ENDIAN__\n#elif __BYTE_ORDER == __ORDER_LITTLE_ENDIAN\n#define __LITTLE_ENDIAN__\n#elif __FLOAT_WORD_ORDER == __PDP_ENDIAN\n#define __PDP_ENDIAN__\n#endif\n\n#endif\n\n#endif\n\n//======================= FreeBSD/OpenBSD/NetBSD DEFINES ====================================\n#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)\n\n#include <machine/endian.h>\n#if BYTE_ORDER == BIG_ENDIAN\n#ifndef __BIG_ENDIAN__\n#define __BIG_ENDIAN__\n#endif\n#elif BYTE_ORDER == LITTLE_ENDIAN\n#ifndef __LITTLE_ENDIAN__\n#define __LITTLE_ENDIAN__\n#endif\n#elif BYTE_ORDER == PDP_ENDIAN\n#ifndef __PDP_ENDIAN__\n#define __PDP_ENDIAN__\n#endif\n#endif\n\n#endif\n\n//======================= BYTE SWAPS =========================================\n#if defined __BIG_ENDIAN__\n#define BigShort(x)\t\t(x)\n#define BigLong(x)\t\t(x)\n#define BigFloat(x)\t\t(x)\n#define LittleShort(x)\tShortSwap(x)\n#define LittleLong(x)\tLongSwap(x)\n#define LittleFloat(x)\tFloatSwap(x)\n\n#elif defined __LITTLE_ENDIAN__\n#define BigShort(x)\t\tShortSwap(x)\n#define BigLong(x)\t\tLongSwap(x)\n#define BigFloat(x)\t\tFloatSwap(x)\n#define LittleShort(x)\t(x)\n#define LittleLong(x)\t(x)\n#define LittleFloat(x)\t(x)\n\n#elif defined __PDP_ENDIAN__\n#define BigShort(x)\t\tShortSwap(x)\n#define BigLong(x)\t\tLongSwapPDP2Big(x)\n#define BigFloat(x)\t\tFloatSwapPDP2Big(x)\n#define LittleShort(x)\t(x)\n#define LittleLong(x)\tLongSwapPDP2Lit(x)\n#define LittleFloat(x)\tFloatSwapPDP2Lit(x)\n\n#else\n#error Unknown byte order type!\n#endif\n\nunsigned int BuffBigLong (const unsigned char *buffer);\nunsigned short BuffBigShort (const unsigned char *buffer);\nunsigned int BuffLittleLong (const unsigned char *buffer);\nunsigned short BuffLittleShort (const unsigned char *buffer);\n/* johnnycz: VVD's change broke e.g. TGA loading (crosshairimage, tom)\nVVD: fixed by changing from \"int\" to \"short\" in 2nd string - stupid copy&paste bug :-(\n#define\tBuffLittleLong(buffer)\tLittleLong(*(int*)buffer)\n#define\tBuffLittleShort(buffer)\tLittleShort(*(short*)buffer)\n*/\n\n//============================================================================\n\nint Q_atoi (const char *str);\nfloat Q_atof (const char *str);\nchar *Q_ftos (float value); // removes trailing zero chars\n\nchar* Q_strcpy(char *to, char *from);\nchar* Q_strupr(char* s1);\nchar* Q_strlwr(char *s1);\nint Q_strcmp2(const char * s1, const char * s2);\n\n// Added by VVD {\n#ifdef _MSC_VER\n#define strcasecmp(s1, s2)\t_stricmp  ((s1),   (s2))\n#define strncasecmp(s1, s2, n)\t_strnicmp ((s1),   (s2),   (n))\n// vc++ snprintf and vsnprintf are non-standard and not compatible with C99.\nint qsnprintf(char *str, size_t n, char const *fmt, ...);\nint qvsnprintf(char *buffer, size_t count, const char *format, va_list argptr);\n#if _MSC_VER < 1900\n#define snprintf qsnprintf\n#define vsnprintf qvsnprintf\n#endif // _MSC_VER < 1900 // Visual Studio 15\n#endif\n\nchar *strstri(const char *text, const char *find); // Case insensitive strstr.\n\n#if defined(__linux__) || defined(_WIN32)\nsize_t strlcpy (char *dst, const char *src, size_t siz);\nsize_t strlcat (char *dst, const char *src, size_t siz);\nchar  *strnstr (const char *s, const char *find, size_t slen);\n#endif\n// Added by VVD }\n\nchar *strchrrev(char *str, char chr);\nint wildcmp(char *wild, char *string);\n\nwchar char2wc (char c);\nchar wc2char (wchar wc);\nwchar *str2wcs (const char *str);\nchar *wcs2str (const wchar *ws);\n\n#ifdef _WIN32\n#ifdef _WIN64\n#define qwcscpy wcscpy\n#define qwcschr wcschr\n#define qwcsrchr wcsrchr\n#define qwcslen (int)wcslen\n#define qwcsstr wcsstr\n#else\n#define qwcscpy wcscpy\n#define qwcschr wcschr\n#define qwcsrchr wcsrchr\n#define qwcslen wcslen\n#define qwcsstr wcsstr\n#endif\n#else\nwchar *qwcscpy (wchar *dest, const wchar *src);\nwchar *qwcschr (const wchar *ws, wchar wc);\nwchar *qwcsrchr (const wchar *ws, wchar wc);\nsize_t qwcslen (const wchar *s);\nwchar *qwcsstr (const wchar *str, const wchar *strSearch);\n#endif\n\n// NOTE: size is not the number of bytes to copy, but the number of characters. sizeof(dest) / sizeof(wchar) should be used.\nsize_t qwcslcpy (wchar *dst, const wchar *src, size_t size);\nsize_t qwcslcat (wchar *dst, const wchar *src, size_t size);\nwchar *Q_wcsdup(const wchar *src);\n\nqbool Q_glob_match (const char *pattern, const char *text);\n\nunsigned int Com_HashKey (const char *name);\n\n//============================================================================\n\n// memory management\n#ifdef DEBUG_MEMORY_ALLOCATIONS\nvoid *Q_malloc_debug(size_t size, const char* file, int line, const char* label);\nvoid *Q_calloc_debug(size_t n, size_t size, const char* file, int line, const char* label);\nvoid *Q_realloc_debug(void *p, size_t newsize, const char* file, int line, const char* label);\nchar *Q_strdup_debug(const char *src, const char* file, int line, const char* label);\nvoid Q_free_debug(void* ptr, const char* file, int line);\nchar *Q_wcs2str_malloc_debug(const wchar *ws, const char* file, int line); // you must freed returned string after it no longer need!!!\n#define Q_malloc_named(size, name) (Q_malloc_debug((size), __FILE__, __LINE__, name))\n#define Q_malloc(size) (Q_malloc_debug((size), __FILE__, __LINE__, NULL))\n#define Q_calloc(n, size) (Q_calloc_debug((n), (size), __FILE__, __LINE__, NULL))\n#define Q_calloc_named(n, size, name) (Q_calloc_debug((n), (size), __FILE__, __LINE__, name))\n#define Q_realloc(p, newsize) (Q_realloc_debug((p), (newsize), __FILE__, __LINE__, NULL))\n#define Q_realloc_named(p, newsize, name) (Q_realloc_debug((p), (newsize), __FILE__, __LINE__, name))\n#define Q_strdup(src) (Q_strdup_debug((src), __FILE__, __LINE__, NULL))\n#define Q_strdup_named(src, name) (Q_strdup_debug((src), __FILE__, __LINE__, (name)))\n#define Q_free(ptr) { Q_free_debug(ptr, __FILE__, __LINE__); ptr = NULL; }\n#define Q_wcs2str_malloc(ws) (Q_wcs2str_malloc_debug(ws, __FILE__, __LINE__))\n#else\n#define Q_malloc_named(size, name) (Q_malloc(size))\n#define Q_calloc_named(n, size, name) (Q_calloc(n, size))\n#define Q_realloc_named(p, newsize, name) (Q_realloc(p, newsize))\n#define Q_strdup_named(size, name) (Q_strdup(size))\nvoid *Q_malloc(size_t size);\nvoid *Q_calloc(size_t n, size_t size);\nvoid *Q_realloc(void *p, size_t newsize);\n#define Q_free(ptr) if(ptr) { free(ptr); ptr = NULL; }\nchar *Q_strdup(const char *src);\nchar *Q_wcs2str_malloc(const wchar *ws); // you must freed returned string after it no longer need!!!\n#endif\n#define Q_calloc_untracked(n, size) (calloc(n, size))\n#define Q_free_untracked(ptr) if(ptr) { free(ptr); ptr = NULL; }\n\n//============================================================================\n// chat icons flags \n// used now by client code only, but may be used in future by server code too, so put here\n#define CIF_CHAT  (1<<0) /* set this flag if user in console, mm1, mm2 etc but not in game */\n#define CIF_AFK   (1<<1) /* set this flag if app lose focus, ie alt+tab */\n\n//============================================================================\n\n#define\tMAX_MSGLEN\t\t\t1450\t\t// max length of a reliable message\n#define\tMAX_DATAGRAM\t\t1450\t\t// max length of unreliable message\n#define\tMSG_BUF_SIZE\t\t8192\t\t// max length of msg buf; MVD demo need it\n#define\tFILE_TRANSFER_BUF_SIZE\t(MAX_MSGLEN - 100)\n#define MIN_MTU             1350        // since user can specifie MTU it is a good idea to limit it at some \"sane\" value.\n\n// qqshka: Its all messy.\n// For example ezquake (and FTE?) expect maximum message is MSG_BUF_SIZE == 8192 with mvd header which have not fixed size,\n// however fuhquake uses less msg size as I recall.\n// mvd header max size is 10 bytes.\n// \n// MAX_MVD_SIZE - max size of single mvd message _WITHOUT_ header\n#define\tMAX_MVD_SIZE\t\t\t(MSG_BUF_SIZE - 100)\n\n#endif /* __Q_SHARED_H__ */\n"
  },
  {
    "path": "src/qmb_particles.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_QMB_PARTICLES_HEADER\n#define EZQUAKE_QMB_PARTICLES_HEADER\n\n//====================================================\nvoid QMB_InitParticles(void);\nvoid QMB_ClearParticles(void);\nvoid QMB_CalculateParticles(void);\nvoid QMB_DrawParticles(void);\nvoid QMB_ShutdownParticles(void);\n\nvoid QMB_RunParticleEffect(vec3_t org, vec3_t dir, int color, int count);\nvoid QMB_ParticleTrail(vec3_t start, vec3_t end, trail_type_t type);\nvoid QMB_EntityParticleTrail(centity_t* cent, trail_type_t type);\nvoid QMB_ParticleRailTrail(vec3_t start, vec3_t end, int color_num);\nvoid QMB_BlobExplosion(vec3_t org);\nvoid QMB_ParticleExplosion(vec3_t org);\nvoid QMB_LavaSplash(vec3_t org);\nvoid QMB_TeleportSplash(vec3_t org);\n\nvoid QMB_DetpackExplosion(vec3_t org);\n\nvoid QMB_InfernoFlame(vec3_t org);\nvoid QMB_StaticBubble(entity_t *ent);\n\nextern qbool qmb_initialized;\nextern float particle_time;\n\n#define MIN_ENTITY_PARTICLE_FRAMETIME (0.1)\n#define ONE_FRAME_ONLY\t(0.0001)\n\n#define\tINIT_NEW_PARTICLE(_pt, _p, _color, _size, _time) \\\n{ \\\n\t_p = free_particles;\t\t\t\t\t\t\t\t\\\n\tfree_particles = _p->next;\t\t\t\t\t\t\t\\\n\t_p->next = _pt->start;\t\t\t\t\t\t\t\t\\\n\t_pt->start = _p;\t\t\t\t\t\t\t\t\t\\\n\t_p->size = _size;\t\t\t\t\t\t\t\t\t\\\n\t_p->hit = 0;\t\t\t\t\t\t\t\t\t\t\\\n\t_p->start = r_refdef2.time;\t\t\t\t\t\t\t\\\n\t_p->die = _p->start + _time;\t\t\t\t\t\t\\\n\t_p->growth = 0;\t\t\t\t\t\t\t\t\t\t\\\n\t_p->rotspeed = 0;\t\t\t\t\t\t\t\t\t\\\n\t_p->texindex = (rand() % particle_textures[_pt->texture].components);\t\\\n\t_p->bounces = 0;\t\t\t\t\t\t\t\t\t\\\n\t_p->initial_alpha = _pt->startalpha;                \\\n\tmemcpy(_p->color, _color, sizeof(_p->color));\t\t\\\n\t_p->cached_contents = 0;                            \\\n\t_p->cached_distance = 0;                            \\\n\tVectorClear(_p->cached_movement);                   \\\n\t_p->entity_ref = 0;                                 \\\n\t_p->entity_trailnumber = 0;                         \\\n\tParticleStats(1); \\\n}\n\n#endif\n"
  },
  {
    "path": "src/qsound.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n// qsound.h -- client sound i/o functions\n\n#ifndef __QSOUND_H__\n#define __QSOUND_H__\n\n#include \"q_shared.h\"\n#include \"zone.h\"\n#include \"cvar.h\"\n\ntypedef struct sfx_s {\n\tchar  name[MAX_QPATH];\n\tvoid *buf;\n} sfx_t;\n\n// FIXME: REMOVE ME PLZ\ntypedef struct snd_format_s {\n\tunsigned int speed;\n\tunsigned int width;\n\tunsigned int channels;\n} snd_format_t;\n\ntypedef struct sfxcache_s {\n\tsnd_format_t\tformat;\n\tunsigned int \ttotal_length;\n\tint \t\tloopstart;\n\tunsigned char\tdata[1];\n} sfxcache_t;\n\ntypedef struct soundhw_s {\n\tunsigned int  numchannels;\n\tunsigned int  samples;\n\tunsigned int  samplepos;\n\tunsigned int  samplebits;\n\tunsigned int  khz;\n\tunsigned long snd_sent;\n\tunsigned char *buffer;\n\t/* qw related */\n\tint paintedtime;\n\tint oldsamplepos;\n\tint numwraps;\n\tint queuelen;\n} soundhw_t;\n\nextern soundhw_t *shw;\n\ntypedef struct channel_s {\n\tsfx_t\t\t*sfx;\t\t\t// sfx number\n\tint\t\tleftvol;\t\t// 0-255 volume\n\tint\t\trightvol;\t\t// 0-255 volume\n\tint\t\tend;\t\t\t// end time in global paintsamples\n\tint \t\tpos;\t\t\t// sample position in sfx\n\tint\t\tlooping;\t\t// where to loop, -1 = no looping\n\tint\t\tentnum;\t\t\t// to allow overriding a specific sound\n\tint\t\tentchannel;\t\t//\n\tvec3_t\t\torigin;\t\t\t// origin of sound effect\n\tvec_t\t\tdist_mult;\t\t// distance multiplier (attenuation/clipK)\n\tint\t\tmaster_vol;\t\t// 0-255 master volume\n\tint\t\tflags;\n} channel_t;\n\n#define CHANNEL_FLAG_VOICE (1 << 0)\n\ntypedef struct wavinfo_s {\n\tint\t\trate;\n\tint\t\twidth;\n\tint\t\tchannels;\n\tint\t\tloopstart;\n\tint\t\tsamples;\n\tint\t\tdataofs;\t\t// chunk starts this many bytes from file start\n} wavinfo_t;\n\nvoid S_Init (void);\nvoid S_Shutdown (void);\nvoid S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol,  float attenuation);\nvoid S_StaticSound (sfx_t *sfx, vec3_t origin, float vol, float attenuation);\nvoid S_StopSound (int entnum, int entchannel);\nvoid S_StopAllSounds(void);\nvoid S_Update (vec3_t origin, vec3_t v_forward, vec3_t v_right, vec3_t v_up);\n\nsfx_t *S_PrecacheSound (char *sample);\nvoid S_PaintChannels(int endtime);\n\nvoid S_LocalSound (char *s);\nvoid S_LocalSoundWithVol(char *sound, float volume);\nsfxcache_t *S_LoadSound (sfx_t *s);\n#define RAW_SOURCE_QIZMO_VOICE MAX_CLIENTS\nvoid S_RawAudio(int sourceid, byte *data, unsigned int speed, unsigned int samples, unsigned int channelsnum, unsigned int width);\nvoid S_QizmoVoice_PlayFrame(int sequence, int voice_id, const byte *data, int bytes);\n\nvoid SND_InitScaletable (void);\nint SND_Rate(int rate);\n\nvoid SND_ResampleStream(void *in, int inrate, int inwidth, int inchannels, int insamps,\n\t\t\t\t\t\tvoid *out, int outrate, int outwidth, int outchannels, int resampstyle);\n\n// ====================================================================\n// User-setable variables\n// ====================================================================\n\n#define MAX_CHANNELS 512\n#define MAX_DYNAMIC_CHANNELS 32\n\n\nextern channel_t\tchannels[MAX_CHANNELS];\n// 0 to MAX_DYNAMIC_CHANNELS - 1 = normal entity sounds\n// MAX_DYNAMIC_CHANNELS to MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS - 1 = water, etc\n// MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS to total_channels = static sounds\n\nextern unsigned int\ttotal_channels;\n\nextern qbool\t\tsnd_initialized;\nextern qbool\t\tsnd_started;\n\nextern int\t\tsoundtime;\n\nextern cvar_t\t\ts_loadas8bit;\nextern cvar_t\t\ts_khz;\nextern cvar_t\t\ts_volume;\nextern cvar_t\t\ts_raw_volume;\nextern cvar_t\t\ts_swapstereo;\nextern cvar_t\t\tbgmvolume;\n\n#endif\n"
  },
  {
    "path": "src/qtv.c",
    "content": "/*\nCopyright (C) 2011 qqshka\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/*\n\tSupport for FTE QuakeTV\n\n\t$Id: qtv.c,v 1.20 2007-10-28 02:45:19 qqshka Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"qtv.h\"\n#include \"input.h\"\n#include \"teamplay.h\"\n#include \"fs.h\"\n#include \"utils.h\"\n\ncvar_t  qtv_buffertime       = { \"qtv_buffertime\",       \"0.5\" };\ncvar_t  qtv_prebuffertime    = { \"qtv_prebuffertime\",    \"0\" };\ncvar_t  qtv_chatprefix       = { \"qtv_chatprefix\",       \"$[{QTV}$] \" };\ncvar_t  qtv_gamechatprefix   = { \"qtv_gamechatprefix\",   \"$[{QTV>game}$] \" };\ncvar_t  qtv_skipchained      = { \"qtv_skipchained\",      \"1\" };\ncvar_t  qtv_adjustbuffer     = { \"qtv_adjustbuffer\",     \"1\" };\ncvar_t  qtv_adjustminspeed   = { \"qtv_adjustminspeed\",   \"0\" };\ncvar_t  qtv_adjustmaxspeed   = { \"qtv_adjustmaxspeed\",   \"999\" };\ncvar_t  qtv_adjustlowstart   = { \"qtv_adjustlowstart\",   \"0.3\" };\ncvar_t  qtv_adjusthighstart  = { \"qtv_adjusthighstart\",  \"1\" };\ncvar_t  qtv_say_team         = { \"qtv_say_team\",         \"0\" };\ncvar_t  qtv_allow_pause      = { \"qtv_allow_pause\",      \"0\" }; // ignore cl_demospeed during QTV playback by default\n\ncvar_t  qtv_event_join       = { \"qtv_event_join\",        \" &c2F2joined&r\"};\ncvar_t  qtv_event_leave      = { \"qtv_event_leave\",       \" &cF22left&r\"};\ncvar_t  qtv_event_changename = { \"qtv_event_changename\",  \" &cFF0changed name to&r \"};\ncvar_t  qtv_event_msglevel   = { \"qtv_event_msglevel\",    \"2\" };\n\nextern qbool qtv_playback_paused;\n\nvoid Qtvusers_f (void);\nvoid QtvStartDelay_f(void);\nvoid QtvEndDelay_f(void);\n\nvoid QTV_Init(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_QTV);\n\n\tCvar_Register(&qtv_buffertime);\n\tCvar_Register(&qtv_prebuffertime);\n\tCvar_Register(&qtv_chatprefix);\n\tCvar_Register(&qtv_gamechatprefix);\n\tCvar_Register(&qtv_skipchained);\n\tCvar_Register(&qtv_adjustbuffer);\n\tCvar_Register(&qtv_adjustminspeed);\n\tCvar_Register(&qtv_adjustmaxspeed);\n\tCvar_Register(&qtv_adjustlowstart);\n\tCvar_Register(&qtv_adjusthighstart);\n\tCvar_Register(&qtv_say_team);\n\tCvar_Register(&qtv_allow_pause);\n\n\tCvar_Register(&qtv_event_join);\n\tCvar_Register(&qtv_event_leave);\n\tCvar_Register(&qtv_event_changename);\n\tCvar_Register(&qtv_event_msglevel);\n\n\tCvar_ResetCurrentGroup();\n\n\tCmd_AddCommand(\"qtvusers\", Qtvusers_f);\n\tCmd_AddCommand(\"+qtv_delay\", QtvStartDelay_f);\n\tCmd_AddCommand(\"-qtv_delay\", QtvEndDelay_f);\n}\n\n//=================================================\n\nchar *QTV_CL_HEADER(float qtv_ver, int qtv_ezquake_ext)\n{\n\tstatic char header[1024];\n\n\tsnprintf(header, sizeof(header), \"QTV\\n\" \"VERSION: %g\\n\" QTV_EZQUAKE_EXT \": %d\\n\", qtv_ver, qtv_ezquake_ext);\n\n\treturn header;\n}\n\n//=================================================\n\n// ripped from FTEQTV, original name is SV_ConsistantMVDData\n// return non zero if we have at least one message\n// ms - will contain ms\nint ConsistantMVDDataEx(unsigned char *buffer, int remaining, int *ms, int max_messages)\n{\n\tqbool warn = true;\n\tint lengthofs;\n\tint length;\n\tint available = 0;\n\n\tif (ms) {\n\t\tms[0] = 0;\n\t}\n\n\twhile ( 1 ) {\n\t\tif (remaining < 2) {\n\t\t\treturn available;\n\t\t}\n\n\t\t//buffer[0] is time\n\n\t\tswitch (buffer[1]&dem_mask)\n\t\t{\n\t\tcase dem_set:\n\t\t\tlength = 10;\n\t\t\tgoto gottotallength;\n\t\tcase dem_multiple:\n\t\t\tlengthofs = 6;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tlengthofs = 2;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (lengthofs+4 > remaining) {\n\t\t\treturn available;\n\t\t}\n\n\t\tlength = (buffer[lengthofs]<<0) + (buffer[lengthofs+1]<<8) + (buffer[lengthofs+2]<<16) + (buffer[lengthofs+3]<<24);\n\n\t\tif (length > MAX_MVD_SIZE && warn) {\n\t\t\tCom_Printf(\"Corrupt mvd, length: %d\\n\", length);\n\t\t\twarn = false;\n\t\t}\n\n\t\tlength += lengthofs+4;\n\ngottotallength:\n\t\tif (remaining < length) {\n\t\t\treturn available;\n\t\t}\n\n\t\tif (ms) {\n\t\t\tms[0] += buffer[0];\n\t\t}\n\t\t\t\n\t\tremaining -= length;\n\t\tavailable += length;\n\t\tbuffer    += length;\n\n\t\tif (max_messages && available >= max_messages) {\n\t\t\treturn available;\n\t\t}\n\t}\n}\n\nint ConsistantMVDData(unsigned char *buffer, int remaining, int max_packets)\n{\n\treturn ConsistantMVDDataEx(buffer, remaining, NULL, max_packets);\n}\n\n//=================================================\n\nextern vfsfile_t *playbackfile;\n\nvoid QTV_ForwardToServerEx (qbool skip_if_no_params, qbool use_first_argument)\n{\n\tchar data[1024 + 100] = {0}, text[1024], *s;\n\tsizebuf_t buf;\n\n\tif (    cls.mvdplayback != QTV_PLAYBACK\n\t\t|| !playbackfile /* || cls.qtv_ezquake_ext & QTV_EZQUAKE_EXT_CLC_STRINGCMD ???*/\n\t   )\n\t\treturn;\n\n\tif (skip_if_no_params)\n\t\tif (Cmd_Argc() < 2)\n\t\t\treturn;\n\n\t// lowercase command\n\tfor (s = Cmd_Argv(0); *s; s++)\n\t\t*s = (char) tolower(*s);\n\n\tif (cls.state == ca_disconnected) {\n\t\tCom_Printf (\"Can't \\\"%s\\\", not connected\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif (strcmp(Cmd_Argv(0), \"say_team\") == 0 && !qtv_say_team.integer) {\n\t\tCom_Printf(\"Cannot send team messages. Use qtv_say_team 1 to override.\\n\");\n\t\treturn;\n\t}\n\n\tSZ_Init(&buf, (byte*) data, sizeof(data));\n\n\ts = TP_ParseMacroString (Cmd_Args());\n\ts = TP_ParseFunChars (s, true);\n\n\ttext[0] = 0; // *cat is dangerous, ensure we empty buffer before use it\n\n\tif (use_first_argument)\n\t\tstrlcat(text, Cmd_Argv(0), sizeof(text));\n\n\tif (s[0])\n\t{\n\t\tstrlcat(text, \" \", sizeof(text));\n\t\tstrlcat(text, s,   sizeof(text));\n\t}\n\n\tMSG_WriteShort  (&buf, 2 + 1 + strlen(text) + 1); // short + byte + null terminated string\n\tMSG_WriteByte   (&buf, qtv_clc_stringcmd);\n\tMSG_WriteString (&buf, text);\n\n\tVFS_WRITE(playbackfile, buf.data, buf.cursize);\n}\n\n#if defined(_MSC_VER) && !defined(__clang__)\n#pragma optimize( \"\", off )\n#endif\nvoid QTV_Say_f (void)\n{\n\ttokenizecontext_t tmpcontext;\n\n\t// save context, so we can later restore it\n\tCmd_SaveContext(&tmpcontext);\n\n\t// in our last tests, this check was not necessary\n\t// and even lead to issues\n\t// so we are disabling it temporarily to see if everything works ok without it\n#if 0\n\t// get rid of quotes, if any\n\tchar *s = Cmd_Args();\n\tif (0 && s[0] == '\\\"' && s[(len = strlen(s))-1] == '\\\"' && len > 2)\n\t{\n\t\tint len;\n\t\tchar text[1024] = {0};\n\t\tsnprintf(text, sizeof(text), \"%s %s\", Cmd_Argv(0), s + 1);\n\t\tif ((len = strlen(text)))\n\t\t\ttext[len - 1] = 0;\n\t\tCmd_TokenizeString(text);\n\t}\n#endif\n\n\tQTV_ForwardToServerEx (true, true);\n\n\t// restore\n\tCmd_RestoreContext(&tmpcontext);\n}\n#if defined(_MSC_VER) && !defined(__clang__)\n#pragma optimize( \"\", on )\n#endif\n\nvoid QTV_Cmd_ForwardToServer (void)\n{\n\tQTV_ForwardToServerEx (false, true);\n}\n\n// don't forward the first argument\nvoid QTV_Cl_ForwardToServer_f (void)\n{\n\tQTV_ForwardToServerEx (false, false);\n}\n\nvoid QTV_Cmd_Printf(int qtv_ext, char *fmt, ...)\n{\n\tva_list argptr;\n\tchar msg[1024] = {0};\n\ttokenizecontext_t tmpcontext;\n\n\tif (cls.mvdplayback != QTV_PLAYBACK || (qtv_ext & cls.qtv_ezquake_ext) != qtv_ext)\n\t\treturn; // no point for this, since it not qtv playback or qtv server do not support it\n\n\tva_start (argptr, fmt);\n\tvsnprintf (msg, sizeof(msg), fmt, argptr);\n\tva_end (argptr);\n\n\t// save context, so we can later restore it\n\tCmd_SaveContext(&tmpcontext);\n\n\tCmd_TokenizeString(msg);\n\tQTV_Cmd_ForwardToServer ();\n\n\t// restore\n\tCmd_RestoreContext(&tmpcontext);\n}\n\n//=================================================\n\nqtvuser_t *qtvuserlist = NULL;\n\nstatic qtvuser_t *QTV_UserById(int id)\n{\n\tqtvuser_t *current;\n\n\tfor (current = qtvuserlist; current; current = current->next)\n\t\tif (current->id == id)\n\t\t\treturn current;\n\n\treturn NULL;\n}\n\nstatic void QTV_SetUser(qtvuser_t *to, qtvuser_t *from)\n{\n\t*to = *from;\n}\n\n// allocate data and set fields, perform linkage to qtvuserlist\n// Well, instead of QTV_NewUser(int id, char *name, ...) I pass params with single qtvuser_t *user struct, well its OK for current struct.\nstatic qtvuser_t *QTV_NewUser(qtvuser_t *user)\n{\n\t// check, may be user alredy exist, so reuse it\n\tqtvuser_t *newuser = QTV_UserById(user->id);\n\n\tif (!newuser)\n\t{\n\t\t// user does't exist, alloc data\n\t\tnewuser = Q_malloc(sizeof(*newuser));\n\n\t\tQTV_SetUser(newuser, user);\n\n\t\t// perform linkage\n\t\tnewuser->next = qtvuserlist;\n\t\tqtvuserlist = newuser;\n\t}\n\telse\n\t{\n\t\t// we do not need linkage, just save current\n\t\tqtvuser_t *oldnext = newuser->next; // we need save this before assign all fields\n\n\t\tQTV_SetUser(newuser, user);\n\n\t\tnewuser->next = oldnext;\n\t}\n\n\treturn newuser;\n}\n\n// free data, perform unlink if requested\nstatic void QTV_FreeUser(qtvuser_t *user, qbool unlink)\n{\n\tif (!user)\n\t\treturn;\n\n\tif (unlink)\n\t{\n\t\tqtvuser_t *next, *prev, *current;\n\n\t\tprev = NULL;\n\t\tcurrent = qtvuserlist;\n\n\t\tfor ( ; current; )\n\t\t{\n\t\t\tnext = current->next;\n\n\t\t\tif (user == current)\n\t\t\t{\n\t\t\t\tif (prev)\n\t\t\t\t\tprev->next = next;\n\t\t\t\telse\n\t\t\t\t\tqtvuserlist = next;\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tprev = current;\n\t\t\tcurrent = next;\n\t\t}\n\t}\n\n\tQ_free(user);\n}\n\n// free whole qtvuserlist\nvoid QTV_FreeUserList(void)\n{\n\tqtvuser_t *next, *current;\n\n\tcurrent = qtvuserlist;\n\n\tfor ( ; current; current = next)\n\t{\n\t\tnext = current->next;\n\t\tQTV_FreeUser(current, false);\n\t}\n\n\tqtvuserlist = NULL;\n}\n\n#define QTV_EVENT_PREFIX \"QTV: \"\n\n// user join qtv\nvoid QTV_JoinEvent(qtvuser_t *user, qtvuserlist_t event)\n{\n\t// make it optional message, or if QUL_INIT, just return since we don't\n\t// need to spam which users are already on the QTV.\n\tif (!qtv_event_join.string[0] || qtv_event_msglevel.integer == 0 || event == QUL_INIT)\n\t\treturn;\n\n\t// do not show \"user joined\" at moment of connection to QTV, it mostly QTV just spammed userlist to us.\n\tif (cls.state <= ca_demostart)\n\t\treturn;\n\n\tif (QTV_UserById(user->id))\n\t{\n\t\t// we alredy have this user, do not double trigger\n\t\treturn;\n\t}\n\n\tif (qtv_event_msglevel.integer == 1 ||\n\t\t(qtv_event_msglevel.integer == 2 && cls.state == ca_active && (cls.demoplayback || cl.standby)))\n\n\t{\n\t\tCom_Printf(\"%s%s%s\\n\", QTV_EVENT_PREFIX, user->name, qtv_event_join.string);\n\t}\n}\n\n// user leaved/left qtv\nvoid QTV_LeaveEvent(qtvuser_t *user)\n{\n\tqtvuser_t *olduser;\n\n\t// make it optional message\n\tif (!qtv_event_leave.string[0] || qtv_event_msglevel.integer == 0)\n\t\treturn;\n\n\tif (!(olduser = QTV_UserById(user->id)))\n\t{\n\t\t// we do not have this user\n\t\treturn;\n\t}\n\n\tif (qtv_event_msglevel.integer == 1 ||\n\t\t(qtv_event_msglevel.integer == 2 && cls.state == ca_active && (cls.demoplayback || cl.standby)))\n\t{\n\t\tCom_Printf(\"%s%s%s\\n\", QTV_EVENT_PREFIX, olduser->name, qtv_event_leave.string);\n\t}\n}\n\n// user changed name on qtv\nvoid QTV_ChangeEvent(qtvuser_t *user)\n{\n\tqtvuser_t *olduser;\n\n\t// well, too spammy, make it as option\n\tif (!qtv_event_changename.string[0] || qtv_event_msglevel.integer == 0)\n\t\treturn;\n\n\tif (!(olduser = QTV_UserById(user->id)))\n\t{\n\t\t// we do not have this user yet\n\t\tCom_DPrintf(\"qtv: change event without olduser\\n\");\n\t\treturn;\n\t}\n\n\tif (qtv_event_msglevel.integer == 1 ||\n\t\t(qtv_event_msglevel.integer == 2 && cls.state == ca_active && (cls.demoplayback || cl.standby)))\n\t{\n\t\tCom_Printf(\"%s%s%s%s\\n\", QTV_EVENT_PREFIX, olduser->name, qtv_event_changename.string, user->name);\n\t}\n}\n\nvoid Parse_QtvUserList(char *s)\n{\n\tqtvuser_t\t\ttmpuser;\n\tqtvuserlist_t\taction;\n\tint\t\t\t\tcnt = 1;\n\t\n\tmemset(&tmpuser, 0, sizeof(tmpuser));\n\n\t// action id [\\\"name\\\"]\n\n\tCmd_TokenizeString( s );\n\n\taction \t\t= atoi( Cmd_Argv( cnt++ ) );\n\ttmpuser.id\t= atoi( Cmd_Argv( cnt++ ) );\n\tstrlcpy(tmpuser.name, Cmd_Argv( cnt++ ), sizeof(tmpuser.name)); // name is optional in some cases\n\n\tswitch ( action )\n\t{\n\t\tcase QUL_ADD:\n\t\tcase QUL_INIT:\n\t\t\tQTV_JoinEvent(&tmpuser, action);\n\t\t\tQTV_NewUser(&tmpuser);\n\n\t\tbreak;\n\t\t\n\t\tcase QUL_CHANGE:\n\t\t\tQTV_ChangeEvent(&tmpuser);\n\t\t\tQTV_NewUser(&tmpuser);\n\n\t\tbreak;\n\n\t\tcase QUL_DEL:\n\t\t\tQTV_LeaveEvent(&tmpuser);\n\t\t\tQTV_FreeUser(QTV_UserById(tmpuser.id), true);\n\n\t\tbreak;\n\n\t\tdefault:\n\t\t\tCom_Printf(\"Parse_QtvUserList: unknown action %d\\n\", action);\n\n\t\treturn;\n\t}\n}\n\n// FIXME: make sexy GUI instead!\nvoid Qtvusers_f (void)\n{\n\tqtvuser_t *current;\n\tint c;\n\n\tif (cls.state == ca_disconnected)\n\t{\n\t\tCom_Printf (\"Can't \\\"%s\\\", not connected\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif (cls.mvdplayback != QTV_PLAYBACK)\n\t{\n\t\tCL_SendClientCommand(true, \"%s %s\", Cmd_Argv(0), Cmd_Args());\n\t\treturn;\n\t}\n\n\tc = 0;\n\tCom_Printf (\"userid name\\n\");\n\tCom_Printf (\"------ ----\\n\");\n\n\tfor (current = qtvuserlist; current; current = current->next)\n\t{\n\t\tCom_Printf (\"%6i %s\\n\", current->id, current->name);\n\t\tc++;\n\t}\n\n\tCom_Printf (\"%i total users\\n\", c);\n}\n\nqbool QTV_FindBestNick (const char *nick, char *result, size_t result_len)\n{\n\tint best = 999999;\n\tchar name[128], *match;\n\n\tqtvuser_t *current = NULL, *bestplayer = NULL;\n\n\tresult[0] = 0;\n\n\tfor (current = qtvuserlist; current; current = current->next)\n\t{\n\t\tif (!current->name[0])\n\t\t\tcontinue;\n\n\t\tstrlcpy(name, current->name, sizeof(name));\n\t\tRemoveColors(name, sizeof (name));\n\t\tfor (match = name; match[0]; match++)\n\t\t\tmatch[0] = tolower(match[0]);\n\n\t\tif (!name[0])\n\t\t\tcontinue;\n\n\t\tif ((match = strstr(name, nick)) && match - name < best)\n\t\t{\n\t\t\tbest = match - name;\n\t\t\tbestplayer = current;\n\t\t}\n\t}\n\n\tif (bestplayer)\n\t{\n\t\tstrlcpy(result, bestplayer->name, result_len);\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid QtvStartDelay_f(void)\n{\n\tif (cls.mvdplayback != QTV_PLAYBACK) {\n\t\tCon_Printf(\"Can only be used during QTV playback.\\n\");\n\t\treturn;\n\t}\n\n\tqtv_playback_paused = true;\n}\n\nvoid QtvEndDelay_f(void)\n{\n\tint ms;\n\n\tif (Demo_BufferSize(&ms)) {\n\t\tCvar_SetValue(&qtv_buffertime, ms / 1000.0f);\n\t\tCon_Printf(\"QTV delay set to %3.1fs.\\n\", qtv_buffertime.value);\n\t}\n\telse {\n\t\tCon_Printf(\"No QTV data in buffer: no delay set.\\n\");\n\t}\n\n\tqtv_playback_paused = false;\n}\n\n"
  },
  {
    "path": "src/qtv.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// qtv.h - qtv related stuff\n\n#ifndef __QTV_H__\n#define __QTV_H__\n\n//======================================\n\n#define QTVPREBUFFERTIME (qtv_prebuffertime.value > 0 ? qtv_prebuffertime.value : bound(0.1, qtv_buffertime.value, 10))\n#define QTVBUFFERTIME bound(0.1, qtv_buffertime.value, 30)\n\n//======================================\n\n#define QTV_VERSION\t\t\t1.0f\t\t// we are support up to this QTV version\n\n// { QTV_EZQUAKE_EXT\n\n#define QTV_EZQUAKE_EXT\t\t\"QTV_EZQUAKE_EXT\"\n\n#define QTV_EZQUAKE_EXT_DOWNLOAD\t(1<<0)\t\t// well, this is not just download, but also different connection process\n#define QTV_EZQUAKE_EXT_SETINFO\t\t(1<<1)\t\t// does't qtv server/client support setinfo\n#define QTV_EZQUAKE_EXT_QTVUSERLIST\t(1<<2)\t\t// does't qtv server/client support qtvuserlist command\n\n#define QTV_EZQUAKE_EXT_NUM ( QTV_EZQUAKE_EXT_DOWNLOAD | QTV_EZQUAKE_EXT_SETINFO | QTV_EZQUAKE_EXT_QTVUSERLIST )\n\n// }\n\n// this just can't be done as macro, so I wrote function\nchar *QTV_CL_HEADER(float qtv_ver, int qtv_ezquake_ext);\n\n// qqshka: It's all messy.\n// For example ezquake (and FTE?) expect maximum message is MSG_BUF_SIZE == 8192 with mvd header which have not fixed size,\n// however fuhquake uses less msg size as I recall.\n// mvd header max size is 10 bytes.\n// \n// MAX_MVD_SIZE - max size of single mvd message _WITHOUT_ header\n#define\tMAX_MVD_SIZE\t\t\t(MSG_BUF_SIZE - 100)\n//======================================\n\ntypedef enum {\n\tQUL_NONE = 0,\t//\n\tQUL_ADD,\t\t// user joined\n\tQUL_CHANGE,\t\t// user changed something like name or something\n\tQUL_DEL,\t\t// user dropped\n\tQUL_INIT\t\t// user init\n} qtvuserlist_t;\n\ntypedef struct qtvuser_s {\n\tint\t\t\t\t\tid;\t\t\t\t\t\t\t\t// unique user id\n\tchar\t\t\t\tname[MAX_INFO_KEY];\t\t\t\t// client name, well must be unique too\n\n\tstruct qtvuser_s\t*next;\t\t\t\t\t\t\t// next qtvuser_s struct in our list\n} qtvuser_t;\n\nvoid\t\tQTV_FreeUserList(void);\nvoid\t\tParse_QtvUserList(char *s);\n\nqbool\t\tQTV_FindBestNick (const char *nick, char *result, size_t result_len);\n\n//======================================\n\nextern cvar_t qtv_buffertime;\nextern cvar_t qtv_prebuffertime;\nextern cvar_t qtv_chatprefix;\nextern cvar_t qtv_gamechatprefix;\nextern cvar_t qtv_skipchained;\nextern cvar_t qtv_adjustbuffer;\nextern cvar_t qtv_adjustminspeed;\nextern cvar_t qtv_adjustmaxspeed;\nextern cvar_t qtv_adjustlowstart;\nextern cvar_t qtv_adjusthighstart;\nextern cvar_t qtv_allow_pause;\n\nextern cvar_t qtv_event_join;\nextern cvar_t qtv_event_leave;\nextern cvar_t qtv_event_changename;\n\nvoid QTV_Init(void);\n\n//======================================\n\n#define\t\tdem_mask\t(7)\n\nint\t\t\tConsistantMVDDataEx(unsigned char *buffer, int remaining, int *ms, int max_packets);\nint\t\t\tConsistantMVDData(unsigned char *buffer, int remaining, int max_packets);\n\n//======================================\n// qtv clc list\n//\n\n#define qtv_clc_stringcmd    1\n\n//======================================\n\nvoid\t\tQTV_ForwardToServerEx (qbool skip_if_no_params, qbool use_first_argument);\nvoid\t\tQTV_Say_f (void);\nvoid\t\tQTV_Cmd_ForwardToServer (void);\nvoid\t\tQTV_Cl_ForwardToServer_f (void);\nvoid\t\tQTV_Cmd_Printf(int qtv_ext, char *fmt, ...);\n\n#endif // __QTV_H__\n\n"
  },
  {
    "path": "src/quakedef.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// quakedef.h -- primary header for client\n\n\n//define PARANOID // speed sapping error checking\n\n#ifndef  __QUAKEDEF_H__\n#define  __QUAKEDEF_H__\n\n#include \"common.h\"\n\n#include \"vid.h\"\n#include \"screen.h\"\n#include \"render.h\"\n#include \"draw.h\"\n#include \"console.h\"\n#include \"cl_view.h\"\n#include \"fs.h\"\n\n#include \"client.h\"\n\n// particles\ntypedef enum trail_type_s {\n\tROCKET_TRAIL, GRENADE_TRAIL, ALT_ROCKET_TRAIL, BLOOD_TRAIL, BIG_BLOOD_TRAIL,\n\tTRACER1_TRAIL, TRACER2_TRAIL, VOOR_TRAIL,\n\t//VULT PARTICLES\n\tRAIL_TRAIL,\n\tRAIL_TRAIL2,\n\tLAVA_TRAIL,\n\tAMF_ROCKET_TRAIL,\n\tBLEEDING_TRAIL,\n\tBLEEDING_TRAIL2,\n} trail_type_t;\n\nvoid R_InitParticles(void);\nvoid R_ClearParticles(void);\nvoid R_DrawParticles(void);\nvoid R_ParticleFrame(void);\nvoid R_ParticleEndFrame(void);\nvoid R_ReadPointFile_f(void);\nvoid R_RunParticleEffect(vec3_t, vec3_t, int, int);\nvoid R_ParticleTrail(vec3_t start, vec3_t end, trail_type_t type);\nvoid R_EntityParticleTrail(centity_t* cent, trail_type_t type);\nvoid R_BlobExplosion(vec3_t);\nvoid R_ParticleExplosion(vec3_t);\nvoid R_LavaSplash(vec3_t);\nvoid R_TeleportSplash(vec3_t);\nvoid Classic_InitParticles(void);\nvoid Classic_ClearParticles(void);\nvoid Classic_RunParticleEffect(vec3_t org, vec3_t dir, int color, int count);\nvoid Classic_ParticleTrail(vec3_t start, vec3_t end, vec3_t *, trail_type_t type);\nvoid Classic_ParticleRailTrail(vec3_t start, vec3_t end, int color);\nvoid Classic_BlobExplosion(vec3_t org);\nvoid Classic_ParticleExplosion(vec3_t org);\nvoid Classic_LavaSplash(vec3_t org);\nvoid Classic_TeleportSplash(vec3_t org);\n\ntypedef enum {\n\ttexture_type_2d,\n\ttexture_type_2d_array,\n\ttexture_type_cubemap,\n\ttexture_type_2d_multisampled,\n\ttexture_type_count\n} r_texture_type_id;\n\ntypedef enum {\n\ttexture_minification_nearest,\n\ttexture_minification_linear,\n\ttexture_minification_nearest_mipmap_nearest,\n\ttexture_minification_linear_mipmap_nearest,\n\ttexture_minification_nearest_mipmap_linear,\n\ttexture_minification_linear_mipmap_linear,\n\n\ttexture_minification_count\n} texture_minification_id;\n\ntypedef enum {\n\ttexture_magnification_nearest,\n\ttexture_magnification_linear,\n\n\ttexture_magnification_count\n} texture_magnification_id;\n\n#if defined(_WIN32) && defined(_DEBUG)\n#define DebugOutput(text) \\\n\tif (IsDebuggerPresent()) { \\\n\t\tOutputDebugString(text); \\\n\t}\n#else\n#define DebugOutput(text)\n#endif\n\n#endif /* !__QUAKEDEF_H__ */\n"
  },
  {
    "path": "src/qwsvdef.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// qwsvdef.h -- primary header for server\n\n\n//define\tPARANOID\t\t\t// speed sapping error checking\n\n#include <time.h>\n#include \"common.h\"\n#include \"fs.h\"\n#include \"gl_model.h\"\n#include \"server.h\"\n\n#include \"net.h\"\n#include \"crc.h\"\n#include \"sha1.h\"\n#include \"sha3.h\"\n#include \"pmove.h\"\n#include \"version.h\"\n#include \"sv_log.h\"\n#include \"sv_world.h\"\n#include \"vfs.h\"\n"
  },
  {
    "path": "src/r_aliasmodel.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// Alias model (.mdl) processing\n// Most code taken from gl_rmain.c\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"vx_stuff.h\"\n#include \"vx_vertexlights.h\"\n#include \"utils.h\"\n#include \"qsound.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"rulesets.h\"\n#include \"teamplay.h\"\n#include \"r_aliasmodel.h\"\n#include \"crc.h\"\n#include \"qmb_particles.h\"\n#include \"r_matrix.h\"\n#include \"r_local.h\"\n#include \"r_framestats.h\"\n#include \"r_trace.h\"\n#include \"r_lighting.h\"\n#include \"r_renderer.h\"\n\ntexture_ref shelltexture;\nmodel_t* cl_custommodels[custom_model_count];\n\n// precalculated dot products for quantized angles\nbyte      r_avertexnormal_dots[SHADEDOT_QUANT][NUMVERTEXNORMALS] =\n#include \"anorm_dots.h\"\n;\nfloat     r_avertexnormals[NUMVERTEXNORMALS][3] = {\n#include \"anorms.h\"\n};\nbyte      *shadedots = r_avertexnormal_dots[0];\n\n/*\n==============================================================================\nALIAS MODELS\n==============================================================================\n*/\n\naliashdr_t\t*pheader;\n\nstvert_t\tstverts[MAXALIASVERTS];\nmtriangle_t\ttriangles[MAXALIASTRIS];\n\n// a pose is a single set of vertexes.  a frame may be\n// an animating sequence of poses\ntrivertx_t\t*poseverts[MAXALIASFRAMES];\nint\t\t\tposenum;\n\nstatic void* Mod_LoadAliasFrame(void* pin, maliasframedesc_t *frame, int* posenum);\nstatic void* Mod_LoadAliasGroup(void* pin, maliasframedesc_t *frame, int* posenum);\nvoid* Mod_LoadAllSkins(model_t* loadmodel, int numskins, daliasskintype_t *pskintype);\n\nstatic cvar_t    gl_shaftlight = { \"gl_shaftlight\", \"1\" };\ncvar_t    r_lerpmuzzlehack = { \"r_lerpmuzzlehack\", \"1\" };\ncvar_t    gl_powerupshells_effect1level = { \"gl_powerupshells_effect1level\", \"0.75\" };\ncvar_t    gl_powerupshells_base1level = { \"gl_powerupshells_base1level\", \"0.05\" };\ncvar_t    gl_powerupshells_effect2level = { \"gl_powerupshells_effect2level\", \"0.4\" };\ncvar_t    gl_powerupshells_base2level = { \"gl_powerupshells_base2level\", \"0.1\" };\ncvar_t    gl_custom_grenade_tf = { \"gl_custom_grenade_tf\", \"1\" };\n\nfloat     r_framelerp;\n\nextern float     bubblecolor[NUM_DLIGHTTYPES][4];\n\nextern cvar_t    r_lerpframes;\nextern cvar_t    gl_outline;\n\nstatic custom_model_color_t custom_model_colors[] = {\n\t// LG beam\n\t{\n\t\t{ \"gl_custom_lg_color\", \"\", CVAR_COLOR },\n\t\t{ \"gl_custom_lg_fullbright\", \"1\" },\n\t\t&amf_lightning,\n\t\tMOD_THUNDERBOLT,\n\t\t0,\n\t\ttrue\n\t},\n\t// Rockets\n\t{\n\t\t{ \"gl_custom_rocket_color\", \"\", CVAR_COLOR },\n\t\t{ \"gl_custom_rocket_fullbright\", \"1\" },\n\t\tNULL,\n\t\tMOD_ROCKET,\n\t\t0,\n\t\ttrue\n\t},\n\t// Grenades\n\t{\n\t\t{ \"gl_custom_grenade_color\", \"\", CVAR_COLOR },\n\t\t{ \"gl_custom_grenade_fullbright\", \"1\" },\n\t\tNULL,\n\t\tMOD_GRENADE,\n\t\t0,\n\t\ttrue\n\t},\n\t// Spikes\n\t{\n\t\t{ \"gl_custom_spike_color\", \"\", CVAR_COLOR },\n\t\t{ \"gl_custom_spike_fullbright\", \"1\" },\n\t\t&amf_part_spikes,\n\t\tMOD_SPIKE,\n\t\t0,\n\t\ttrue\n\t},\n\t// Backpacks with RL inside\n\t{\n\t\t{ \"gl_custom_rlpack_color\", \"255 64 64\", CVAR_COLOR },\n\t\t{ \"\", \"0\" },\n\t\tNULL,\n\t\tMOD_BACKPACK,\n\t\tRF_ROCKETPACK,\n\t\tfalse\n\t},\n\t// Backpacks with LG inside\n\t{\n\t\t{ \"gl_custom_lgpack_color\", \"64 64 255\", CVAR_COLOR },\n\t\t{ \"\", \"0\" },\n\t\tNULL,\n\t\tMOD_BACKPACK,\n\t\tRF_LGPACK,\n\t\tfalse\n\t}\n};\n\nstatic qbool IsFlameModel(model_t* model)\n{\n\treturn model->modhint == MOD_FLAME || model->modhint == MOD_FLAME0 || model->modhint == MOD_FLAME3;\n}\n\nstatic void R_RenderAliasModelEntity(\n\tentity_t* ent, aliashdr_t *paliashdr, byte *color32bit,\n\ttexture_ref texture, texture_ref fb_texture, maliasframedesc_t* oldframe, maliasframedesc_t* frame,\n\tqbool outline, int effects\n)\n{\n\tint i;\n\tmodel_t* model = ent->model;\n\n\tif (!(ent->renderfx & RF_FORCECOLOURMOD))\n\t\tent->r_modelcolor[0] = -1;  // by default no solid fill color for model, using texture\n\tif (color32bit) {\n\t\t// force some color for such model\n\t\tfor (i = 0; i < 3; i++) {\n\t\t\tent->r_modelcolor[i] = color32bit[i] / 255.0;\n\t\t\tent->r_modelcolor[i] = bound(0, ent->r_modelcolor[i], 1);\n\t\t}\n\n\t\tfb_texture = null_texture_reference;\n\t}\n\n\tR_SetupAliasFrame(ent, model, oldframe, frame, outline, texture, fb_texture, effects, ent->renderfx);\n}\n\nqbool R_CullAliasModel(entity_t* ent, maliasframedesc_t* oldframe, maliasframedesc_t* frame)\n{\n\tvec3_t mins, maxs;\n\n\t//culling\n\tif (!(ent->renderfx & RF_WEAPONMODEL)) {\n\t\tif (ent->angles[0] || ent->angles[1] || ent->angles[2]) {\n\t\t\tif (R_CullSphere(ent->origin, max(oldframe->radius, frame->radius))) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (r_framelerp == 1) {\n\t\t\t\tVectorAdd(ent->origin, frame->bboxmin, mins);\n\t\t\t\tVectorAdd(ent->origin, frame->bboxmax, maxs);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tint i;\n\t\t\t\tfor (i = 0; i < 3; i++) {\n\t\t\t\t\tmins[i] = ent->origin[i] + min(oldframe->bboxmin[i], frame->bboxmin[i]);\n\t\t\t\t\tmaxs[i] = ent->origin[i] + max(oldframe->bboxmax[i], frame->bboxmax[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (R_CullBox(mins, maxs)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n\n// FIXME: Move filtering options to cl_ents.c\nqbool R_FilterEntity(entity_t* ent)\n{\n\t// VULT NAILTRAIL - Hidenails\n\tif (amf_hidenails.value && ent->model->modhint == MOD_SPIKE) {\n\t\treturn true;\n\t}\n\n\t// VULT ROCKETTRAILS - Hide rockets\n\tif (amf_hiderockets.value && (ent->model->flags & EF_ROCKET)) {\n\t\treturn true;;\n\t}\n\n\t// VULT CAMERAS - Show/Hide playermodel\n\tif (ent->alpha == -1) {\n\t\tif (cameratype == C_NORMAL) {\n\t\t\treturn true;\n\t\t}\n\t\tent->alpha = 1;\n\t\treturn false;\n\t}\n\telse if (ent->alpha < 0) {\n\t\t// VULT MOTION TRAILS\n\t\treturn true;\n\t}\n\n\t// Handle flame/flame0 model changes\n\tif (qmb_initialized) {\n\t\tif (!amf_part_fire.integer && ent->model->modhint == MOD_FLAME0) {\n\t\t\tent->model = cl.model_precache[cl_modelindices[mi_flame]];\n\t\t}\n\t\telse if (amf_part_fire.integer) {\n\t\t\tif (ent->model->modhint == MOD_FLAME0) {\n\t\t\t\tif (!ISPAUSED) {\n\t\t\t\t\tParticleTorchFire(ent);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (ent->model->modhint == MOD_FLAME && cl_custommodels[custom_model_flame0]) {\n\t\t\t\t// do we have progs/flame0.mdl?\n\t\t\t\tif (!ISPAUSED) {\n\t\t\t\t\tParticleTorchFire(ent);\n\t\t\t\t}\n\t\t\t\tent->model = cl_custommodels[custom_model_flame0];\n\t\t\t}\n\t\t\telse if (ent->model->modhint == MOD_FLAME2 || ent->model->modhint == MOD_FLAME3) {\n\t\t\t\tif (!ISPAUSED) {\n\t\t\t\t\tParticleTorchFire(ent);\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n\nvoid R_OverrideModelTextures(entity_t* ent, texture_ref* texture, texture_ref* fb_texture, byte** color32bit)\n{\n\tint playernum = -1;\n\n\tif (ent->scoreboard && (ent->renderfx & RF_VWEPMODEL) == 0) {\n\t\tplayernum = ent->scoreboard - cl.players;\n\t}\n\n\tif (playernum >= 0 && playernum < MAX_CLIENTS) {\n\t\tR_SetSkinForPlayerEntity(ent, texture, fb_texture, color32bit);\n\t}\n\t// TODO: Can we move the custom_model logic to here?  If fullbright, nullify textures and set color?\n\n\tif (ent->full_light || !gl_fb_models.integer) {\n\t\t*fb_texture = null_texture_reference;\n\t}\n}\n\nstatic qbool R_CanDrawModelShadow(entity_t* ent)\n{\n\treturn (r_shadows.integer && !ent->full_light && !(ent->renderfx & RF_NOSHADOW)) && !ent->alpha;\n}\n\nvoid R_DrawAliasModel(entity_t *ent, qbool outline)\n{\n\tint anim, skinnum;\n\ttexture_ref texture, fb_texture;\n\taliashdr_t* paliashdr;\n\tmaliasframedesc_t *oldframe, *frame;\n\tbyte *color32bit = NULL;\n\tfloat oldMatrix[16];\n\n\tif (R_FilterEntity(ent)) {\n\t\treturn;\n\t}\n\n\t// Meag: Do not move this above R_FilterEntity(), it might change the model... :(\n\tpaliashdr = (aliashdr_t *)Mod_Extradata(ent->model); // locate the proper data\n\n\t//VULT CORONAS\n\tif (amf_coronas.integer) {\n\t\tif (IsFlameModel(ent->model)) {\n\t\t\t//FIXME: This is slow and pathetic as hell, really we should just check the entity\n\t\t\t//alternatively add some kind of permanent client side TE for the torch\n\t\t\tNewStaticLightCorona(C_FIRE, ent->origin, ent->entity_id);\n\t\t}\n\t\telse if (ent->model->modhint == MOD_TELEPORTDESTINATION) {\n\t\t\tNewStaticLightCorona(C_LIGHTNING, ent->origin, ent->entity_id);\n\t\t}\n\t}\n\n\tent->frame = bound(0, ent->frame, paliashdr->numframes - 1);\n\tent->oldframe = bound(0, ent->oldframe, paliashdr->numframes - 1);\n\n\tframe = &paliashdr->frames[ent->frame];\n\toldframe = &paliashdr->frames[ent->oldframe];\n\n\tr_framelerp = 1.0;\n\tif (r_lerpframes.integer && ent->framelerp >= 0 && ent->oldframe != ent->frame) {\n\t\tr_framelerp = min(ent->framelerp, 1);\n\t}\n\n\tif (R_CullAliasModel(ent, oldframe, frame)) {\n\t\treturn;\n\t}\n\n\tframeStats.classic.polycount[polyTypeAliasModel] += paliashdr->numtris;\n\n\tR_TraceEnterRegion(va(\"%s(%s)\", __func__, ent->model->name), true);\n\tR_PushModelviewMatrix(oldMatrix);\n\tR_StateBeginDrawAliasModel(ent, paliashdr);\n\n\t//get lighting information\n\tR_AliasSetupLighting(ent);\n\tshadedots = r_avertexnormal_dots[((int)(ent->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1)];\n\tent->r_modelalpha = (ent->alpha ? ent->alpha : 1);\n\n\tif (ent->r_modelalpha != 1 && outline) {\n\t\t// todo\n\t\tR_PopModelviewMatrix(oldMatrix);\n\t\tR_TraceLeaveRegion(true);\n\t\treturn;\n\t}\n\n\tanim = (int)(r_refdef2.time * 10) & 3;\n\tskinnum = ent->skinnum;\n\tif (skinnum >= paliashdr->numskins || skinnum < 0) {\n\t\tCom_DPrintf(\"R_DrawAliasModel: no such skin # %d\\n\", skinnum);\n\t\tskinnum = 0;\n\t}\n\n\ttexture = paliashdr->gl_texturenum[skinnum][anim];\n\tfb_texture = paliashdr->glc_fb_texturenum[skinnum][anim];\n\n\tR_OverrideModelTextures(ent, &texture, &fb_texture, &color32bit);\n\tR_RenderAliasModelEntity(ent, paliashdr, color32bit, texture, fb_texture, oldframe, frame, outline, ent->effects);\n\tR_PopModelviewMatrix(oldMatrix);\n\n\tif (R_CanDrawModelShadow(ent)) {\n\t\trenderer.DrawAliasModelShadow(ent);\n\t}\n\n\tR_TraceLeaveRegion(true);\n\n\treturn;\n}\n\nint R_AliasFramePose(const maliasframedesc_t* frame)\n{\n\tint pose, numposes;\n\tfloat interval;\n\n\tpose = frame->firstpose;\n\tnumposes = frame->numposes;\n\tif (numposes > 1) {\n\t\tinterval = frame->interval;\n\t\tpose += (int)(r_refdef2.time / interval) % numposes;\n\t}\n\n\treturn pose;\n}\n\nvoid R_SetupAliasFrame(\n\tentity_t* ent,\n\tmodel_t* model,\n\tmaliasframedesc_t *oldframe, maliasframedesc_t *frame,\n\tqbool outline,\n\ttexture_ref texture, texture_ref fb_texture,\n\tint effects, int render_effects\n)\n{\n\textern cvar_t gl_lumatextures;\n\tint oldpose, pose;\n\tfloat lerp;\n\n\tR_AliasModelDeterminePoses(oldframe, frame, &oldpose, &pose, &lerp);\n\n\tif (!gl_lumatextures.integer) {\n\t\tR_TextureReferenceInvalidate(fb_texture);\n\t}\n\n\trenderer.DrawAliasFrame(ent, model, oldpose, pose, texture, fb_texture, outline, effects, render_effects, lerp);\n}\n\nvoid R_AliasModelDeterminePoses(const maliasframedesc_t* oldframe, const maliasframedesc_t* frame, int* prevpose, int* nextpose, float* lerpfrac)\n{\n\tint oldpose = R_AliasFramePose(oldframe);\n\tint pose = R_AliasFramePose(frame);\n\tfloat lerp = 0;\n\n\tif (oldframe->nextpose == pose) {\n\t\tlerp = r_framelerp;\n\t}\n\telse if (frame->nextpose == oldpose) {\n\t\tint temp = pose;\n\n\t\tpose = oldpose;\n\t\toldpose = temp;\n\t\tlerp = (1 - r_framelerp);\n\t}\n\telse {\n\t\tlerp = 1;\n\t}\n\n\tif (lerp == 1) {\n\t\toldpose = pose;\n\t\tlerp = 0;\n\t}\n\telse if (lerp == 0) {\n\t\tpose = oldpose;\n\t}\n\n\t*prevpose = oldpose;\n\t*nextpose = pose;\n\t*lerpfrac = lerp;\n}\n\nstatic void R_AliasModelScaleLight(entity_t* ent)\n{\n\tfloat max_component;\n\n\tmax_component = max(ent->lightcolor[0], ent->lightcolor[1]);\n\tmax_component = max(max_component, ent->lightcolor[2]);\n\n\tif (max_component >= 256) {\n\t\tVectorScale(ent->lightcolor, 255 / max_component, ent->lightcolor);\n\t}\n\telse if (max_component < cl.minlight) {\n\t\tent->lightcolor[0] += cl.minlight;\n\t\tent->lightcolor[1] += cl.minlight;\n\t\tent->lightcolor[2] += cl.minlight;\n\t}\n}\n\nstatic void R_AliasModelColoredLighting(entity_t* ent)\n{\n\tint i, j, k, lnum;\n\tvec3_t dist;\n\tfloat add, added = 0;\n\n\t/* FIXME: dimman... cache opt from fod */\n\t//VULT COLOURED MODEL LIGHTS\n\tent->bestlight = -1;\n\tfor (i = 0; i < MAX_DLIGHTS / 32; i++) {\n\t\tif (!cl_dlight_active[i]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (j = 0; j < 32; j++) {\n\t\t\tif ((cl_dlight_active[i] & (1 << j)) && i * 32 + j < MAX_DLIGHTS) {\n\t\t\t\tlnum = i * 32 + j;\n\n\t\t\t\tVectorSubtract(ent->origin, cl_dlights[lnum].origin, dist);\n\t\t\t\tadd = cl_dlights[lnum].radius - VectorLength(dist);\n\n\t\t\t\tif (add > 0) {\n\t\t\t\t\tvec3_t dlight_color;\n\n\t\t\t\t\tif (amf_lighting_vertex.integer && (ent->bestlight < 0 || cl_dlights[lnum].radius > cl_dlights[ent->bestlight].radius)) {\n\t\t\t\t\t\tent->bestlight = lnum;\n\t\t\t\t\t}\n\t\t\t\t\tadded += add;\n\n\t\t\t\t\tif (cl_dlights[lnum].type == lt_custom) {\n\t\t\t\t\t\tVectorCopy(cl_dlights[lnum].color, dlight_color);\n\t\t\t\t\t\tVectorScale(dlight_color, (1.0 / 255), dlight_color); // convert color from byte to float\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tVectorCopy(bubblecolor[cl_dlights[lnum].type], dlight_color);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (k = 0; k < 3; k++) {\n\t\t\t\t\t\tent->lightcolor[k] += (dlight_color[k] * add) * 2;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tent->ambientlight += added;\n\tR_AliasModelScaleLight(ent);\n}\n\nstatic void R_AliasModelStandardLighting(entity_t* ent)\n{\n\tint i, j, lnum;\n\tvec3_t dist;\n\tfloat add, added = 0;\n\n\t/* FIXME: dimman... cache opt from fod */\n\tent->bestlight = -1;\n\tfor (i = 0; i < MAX_DLIGHTS / 32; i++) {\n\t\tif (cl_dlight_active[i]) {\n\t\t\tfor (j = 0; j < 32; j++) {\n\t\t\t\tif ((cl_dlight_active[i] & (1 << j)) && i * 32 + j < MAX_DLIGHTS) {\n\t\t\t\t\tlnum = i * 32 + j;\n\n\t\t\t\t\tVectorSubtract(ent->origin, cl_dlights[lnum].origin, dist);\n\t\t\t\t\tadd = cl_dlights[lnum].radius - VectorLength(dist);\n\n\t\t\t\t\tif (add > 0) {\n\t\t\t\t\t\tif (amf_lighting_vertex.integer && (ent->bestlight < 0 || cl_dlights[lnum].radius > cl_dlights[ent->bestlight].radius)) {\n\t\t\t\t\t\t\tent->bestlight = lnum;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tadded += add;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tent->ambientlight += added;\n\tent->lightcolor[0] += added;\n\tent->lightcolor[1] += added;\n\tent->lightcolor[2] += added;\n}\n\nvoid R_AliasSetupLighting(entity_t *ent)\n{\n\tfloat fbskins = 0;\n\tunsigned int i;\n\tmodel_t* clmodel = ent->model;\n\tqbool player_model = (clmodel->modhint == MOD_PLAYER || ent->renderfx & RF_PLAYERMODEL);\n\tqbool calculate_lighting = true;\n\n\t//VULT COLOURED MODEL LIGHTING\n\tent->custom_model = NULL;\n\tent->ambientlight = ent->shadelight = 0;\n\tfor (i = 0; i < sizeof(custom_model_colors) / sizeof(custom_model_colors[0]); ++i) {\n\t\tcustom_model_color_t* test = &custom_model_colors[i];\n\t\tif (test->model_hint && test->model_hint == clmodel->modhint && (test->renderfx == 0 || test->renderfx == ent->renderfx)) {\n\t\t\tif (test->color_cvar.string[0] && (test->amf_cvar == NULL || test->amf_cvar->integer == 0)) {\n\t\t\t\tent->custom_model = &custom_model_colors[i];\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (clmodel->modhint == MOD_GRENADE && !gl_custom_grenade_tf.integer && cl.teamfortress) {\n\t\tent->custom_model = NULL;\n\t}\n\n\tif (ent->custom_model && ent->custom_model->fullbright_cvar.integer) {\n\t\tent->ambientlight = 4096;\n\t\tent->shadelight = 0;\n\t\tent->full_light = true;\n\t\treturn;\n\t}\n\n\t// make thunderbolt and torches full light\n\tif (clmodel->modhint == MOD_THUNDERBOLT) {\n\t\tent->ambientlight = 60 + 150 * bound(0, gl_shaftlight.value, 1);\n\t\tent->shadelight = 0;\n\t\tent->full_light = true;\n\t\treturn;\n\t}\n\telse if (clmodel->modhint == MOD_FLAME || clmodel->modhint == MOD_FLAME2) {\n\t\tent->ambientlight = 255;\n\t\tent->shadelight = 0;\n\t\tent->full_light = true;\n\t\treturn;\n\t}\n\n\t//normal lighting\n\tent->full_light = false;\n\tif (player_model) {\n\t\tfbskins = bound(0, r_fullbrightSkins.value, r_refdef2.max_fbskins);\n\t\tif (fbskins >= 1 && gl_fb_models.integer == 1) {\n\t\t\tent->ambientlight = ent->shadelight = 4096;\n\t\t\tent->full_light = true;\n\t\t\tcalculate_lighting = false;\n\t\t}\n\t\telse {\n\t\t\tent->full_light = fbskins > 0;\n\t\t\tcalculate_lighting = true;\n\t\t}\n\t}\n\telse if (Rulesets_FullbrightModel(clmodel)) {\n\t\tent->ambientlight = ent->shadelight = 4096;\n\t\tcalculate_lighting = (r_shadows.integer);\n\t}\n\n\tif (calculate_lighting) {\n\t\tR_LightEntity(ent);\n\n\t\tif (amf_lighting_colour.integer) {\n\t\t\tR_AliasModelColoredLighting(ent);\n\t\t}\n\t\telse {\n\t\t\tR_AliasModelStandardLighting(ent);\n\t\t}\n\n\t\tif (player_model) {\n\t\t\tent->ambientlight = max(ent->ambientlight, 8 + fbskins * 120);\n\t\t\tent->shadelight = max(ent->shadelight, 8 + fbskins * 120);\n\t\t}\n\t\telse {\n\t\t\t// clamp lighting so it doesn't overbright as much\n\t\t\tent->ambientlight = min(ent->ambientlight, 128);\n\t\t\tif (ent->ambientlight + ent->shadelight > 192) {\n\t\t\t\tent->shadelight = 192 - ent->ambientlight;\n\t\t\t}\n\t\t}\n\t}\n\n\t// always give the gun some light\n\tif ((ent->renderfx & RF_WEAPONMODEL) && ent->ambientlight < 24) {\n\t\tent->ambientlight = ent->shadelight = 24;\n\t}\n\n\t// never allow players to go totally black\n\tif (player_model && ent->ambientlight < 8) {\n\t\tent->ambientlight = ent->shadelight = 8;\n\t}\n\n\tif (ent->ambientlight < cl.minlight) {\n\t\tent->ambientlight = ent->shadelight = cl.minlight;\n\t}\n}\n\nvoid R_DrawViewModel(void)\n{\n\textern cvar_t cl_drawgun;\n\tcentity_t *cent = CL_WeaponModelForView();\n\tstatic entity_t gun;\n\n\t//VULT CAMERA - Don't draw gun in external camera\n\tif (cameratype != C_NORMAL) {\n\t\treturn;\n\t}\n\n\tif (!r_drawentities.value || !cent->current.modelindex || cl_drawgun.value <= 0) {\n\t\treturn;\n\t}\n\n\tmemset(&gun, 0, sizeof(gun));\n\n\tif (!(gun.model = cl.model_precache[cent->current.modelindex])) {\n\t\tHost_Error(\"R_DrawViewModel: bad modelindex\");\n\t}\n\n\tVectorCopy(cent->current.origin, gun.origin);\n\tVectorCopy(cent->current.angles, gun.angles);\n\tgun.colormap = vid.colormap;\n\tgun.renderfx = RF_WEAPONMODEL | RF_NOSHADOW | gun.model->renderfx;\n\n\tgun.effects |= (cl.stats[STAT_ITEMS] & IT_QUAD) ? EF_BLUE : 0;\n\tgun.effects |= (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) ? EF_RED : 0;\n\tgun.effects |= (cl.stats[STAT_ITEMS] & IT_SUIT) ? EF_GREEN : 0;\n\n\tgun.frame = cent->current.frame;\n\tif (cent->frametime >= 0 && cent->frametime <= r_refdef2.time) {\n\t\tgun.oldframe = cent->oldframe;\n\t\tgun.framelerp = (r_refdef2.time - cent->frametime) * 10;\n\t}\n\telse {\n\t\tgun.oldframe = gun.frame;\n\t\tgun.framelerp = -1;\n\t}\n\t//Con_Printf(\"Lerping %d=>%d\\n\", gun.oldframe, gun.frame);\n\n\tgun.alpha = bound(0, cl_drawgun.value, 1);\n\n\tif (R_UseImmediateOpenGL() && !gl_mtexable) {\n\t\tgun.alpha = 0;\n\t}\n\n\tswitch (gun.model->type) {\n\t\tcase mod_alias:\n\t\t\t// don't support outlining weaponmodel just yet\n\t\t\tR_DrawAliasModel(&gun, false);\n\t\t\tif (gun.effects) {\n\t\t\t\trenderer.DrawAliasModelPowerupShell(&gun);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase mod_alias3:\n\t\t\trenderer.DrawAlias3Model(&gun, false, false);\n\t\t\tif (gun.effects) {\n\t\t\t\trenderer.DrawAlias3ModelPowerupShell(&gun);\n\t\t\t}\n\t\t\trenderer.DrawAlias3Model(&gun, false, true);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tCom_Printf(\"Not drawing view model of type %i\\n\", gun.model->type);\n\t\t\tbreak;\n\t}\n}\n\nvoid R_InitAliasModelCvars(void)\n{\n\tint i;\n\n\tfor (i = 0; i < sizeof(custom_model_colors) / sizeof(custom_model_colors[0]); ++i) {\n\t\tCvar_Register(&custom_model_colors[i].color_cvar);\n\t\tCvar_Register(&custom_model_colors[i].fullbright_cvar);\n\t}\n\n\tCvar_Register(&r_lerpmuzzlehack);\n\tCvar_Register(&gl_shaftlight);\n\n\tCvar_Register(&gl_powerupshells_base1level);\n\tCvar_Register(&gl_powerupshells_effect1level);\n\tCvar_Register(&gl_powerupshells_base2level);\n\tCvar_Register(&gl_powerupshells_effect2level);\n\tCvar_Register(&gl_custom_grenade_tf);\n}\n\nvoid Mod_LoadAliasModel(model_t *mod, void *buffer, int filesize, const char* loadname)\n{\n\tint i, j, version, numframes, size, start, end, total;\n\tmdl_t *pinmodel;\n\tstvert_t *pinstverts;\n\tdtriangle_t *pintriangles;\n\tdaliasframetype_t *pframetype;\n\tdaliasskintype_t *pskintype;\n\taliasframetype_t frametype;\n\tint posenum;\n\n\t//VULT MODELS\n\tMod_AddModelFlags(mod);\n\n\tif (mod->modhint == MOD_PLAYER || mod->modhint == MOD_EYES) {\n\t\tmod->crc = CRC_Block(buffer, filesize);\n\t}\n\n\tstart = Hunk_LowMark();\n\n\tpinmodel = (mdl_t *)buffer;\n\n\tversion = LittleLong(pinmodel->version);\n\n\tif (version != ALIAS_VERSION) {\n\t\tHunk_FreeToLowMark(start);\n\t\tHost_Error(\"Mod_LoadAliasModel: %s has wrong version number (%i should be %i)\\n\", mod->name, version, ALIAS_VERSION);\n\t\treturn;\n\t}\n\n\t// allocate space for a working header, plus all the data except the frames, skin and group info\n\tsize = sizeof(aliashdr_t) + (LittleLong(pinmodel->numframes) - 1) * sizeof(pheader->frames[0]);\n\tpheader = (aliashdr_t *)Hunk_AllocName(size, loadname);\n\n\tmod->flags = LittleLong(pinmodel->flags);\n\n\t// endian-adjust and copy the data, starting with the alias model header\n\tpheader->boundingradius = LittleFloat(pinmodel->boundingradius);\n\tpheader->numskins = LittleLong(pinmodel->numskins);\n\tpheader->skinwidth = LittleLong(pinmodel->skinwidth);\n\tpheader->skinheight = LittleLong(pinmodel->skinheight);\n\n\tif (pheader->skinheight > MAX_LBM_HEIGHT) {\n\t\tHost_Error(\"Mod_LoadAliasModel: model %s has a skin taller than %d\", mod->name, MAX_LBM_HEIGHT);\n\t}\n\n\tpheader->numverts = LittleLong(pinmodel->numverts);\n\n\tif (pheader->numverts <= 0) {\n\t\tHost_Error(\"Mod_LoadAliasModel: model %s has no vertices\", mod->name);\n\t}\n\n\tif (pheader->numverts > MAXALIASVERTS) {\n\t\tHost_Error(\"Mod_LoadAliasModel: model %s has too many vertices\", mod->name);\n\t}\n\n\tpheader->numtris = LittleLong(pinmodel->numtris);\n\n\tif (pheader->numtris <= 0) {\n\t\tHost_Error(\"Mod_LoadAliasModel: model %s has no triangles\", mod->name);\n\t}\n\n\tpheader->numframes = LittleLong(pinmodel->numframes);\n\tnumframes = pheader->numframes;\n\tif (numframes < 1) {\n\t\tHost_Error(\"Mod_LoadAliasModel: model %s has invalid # of frames: %d\\n\", mod->name, numframes);\n\t}\n\n\tpheader->size = LittleFloat(pinmodel->size) * ALIAS_BASE_SIZE_RATIO;\n\tmod->synctype = LittleLong(pinmodel->synctype);\n\tmod->numframes = pheader->numframes;\n\n\tfor (i = 0; i < 3; i++) {\n\t\tpheader->scale[i] = LittleFloat(pinmodel->scale[i]);\n\t\tpheader->scale_origin[i] = LittleFloat(pinmodel->scale_origin[i]);\n\t\tpheader->eyeposition[i] = LittleFloat(pinmodel->eyeposition[i]);\n\t}\n\n\t// load the skins\n\tpskintype = (daliasskintype_t *)&pinmodel[1];\n\tpskintype = Mod_LoadAllSkins(mod, pheader->numskins, pskintype);\n\n\t// load base s and t vertices\n\tpinstverts = (stvert_t *)pskintype;\n\n\tfor (i = 0; i < pheader->numverts; i++) {\n\t\tstverts[i].onseam = LittleLong(pinstverts[i].onseam);\n\t\tstverts[i].s = LittleLong(pinstverts[i].s);\n\t\tstverts[i].t = LittleLong(pinstverts[i].t);\n\t}\n\n\t// load triangle lists\n\tpintriangles = (dtriangle_t *)&pinstverts[pheader->numverts];\n\n\tfor (i = 0; i < pheader->numtris; i++) {\n\t\ttriangles[i].facesfront = LittleLong(pintriangles[i].facesfront);\n\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tint index = triangles[i].vertindex[j] = LittleLong(pintriangles[i].vertindex[j]);\n\t\t\tif (index < 0 || index >= pheader->numverts) {\n\t\t\t\tHost_Error(\"Mod_LoadAliasModel: model %s has invalid vertex ref (%d/%d)\\n\", mod->name, index, pheader->numverts);\n\t\t\t}\n\t\t}\n\t}\n\n\t// load the frames\n\tposenum = 0;\n\tpframetype = (daliasframetype_t *)&pintriangles[pheader->numtris];\n\n\tmod->mins[0] = mod->mins[1] = mod->mins[2] = 255;\n\tmod->maxs[0] = mod->maxs[1] = mod->maxs[2] = 0;\n\n\tfor (i = 0; i < numframes; i++) {\n\t\tframetype = LittleLong(pframetype->type);\n\n\t\tif (frametype == ALIAS_SINGLE) {\n\t\t\tpframetype = (daliasframetype_t *)Mod_LoadAliasFrame(pframetype + 1, &pheader->frames[i], &posenum);\n\t\t}\n\t\telse {\n\t\t\tpframetype = (daliasframetype_t *)Mod_LoadAliasGroup(pframetype + 1, &pheader->frames[i], &posenum);\n\t\t}\n\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tmod->mins[j] = min(mod->mins[j], pheader->frames[i].bboxmin[j]);\n\t\t\tmod->maxs[j] = max(mod->maxs[j], pheader->frames[i].bboxmax[j]);\n\t\t}\n\t}\n\n\tmod->radius = RadiusFromBounds(mod->mins, mod->maxs);\n\n\tpheader->numposes = posenum;\n\n\tmod->type = mod_alias;\n\n\t// build the draw lists\n\trenderer.PrepareAliasModel(mod, pheader);\n\n\t// move the complete, relocatable alias model to the cache\n\tend = Hunk_LowMark();\n\ttotal = end - start;\n\n\tmod->cached_data = Q_malloc(total);\n\tmemcpy(mod->cached_data, pheader, total);\n\n\t// try load simple textures\n\tmemset(mod->simpletexture, 0, sizeof(mod->simpletexture));\n\tfor (i = 0; i < MAX_SIMPLE_TEXTURES && i < pheader->numskins; i++) {\n\t\tmod->simpletexture[i] = Mod_LoadSimpleTexture(mod, i);\n\t}\n\n\tHunk_FreeToLowMark(start);\n}\n\nstatic void* Mod_LoadAliasFrame(void * pin, maliasframedesc_t *frame, int* posenum)\n{\n\ttrivertx_t *pinframe;\n\tint i, len;\n\tdaliasframe_t *pdaliasframe;\n\n\tpdaliasframe = (daliasframe_t *)pin;\n\n\tstrlcpy(frame->name, pdaliasframe->name, sizeof(frame->name));\n\tstrlcpy(frame->groupname, frame->name, sizeof(frame->groupname));\n\tframe->firstpose = *posenum;\n\tframe->numposes = 1;\n\tframe->groupnumber = 0;\n\n\tfor (len = strlen(frame->groupname); len > 0 && isdigit(frame->groupname[len - 1]); --len) {\n\t\tframe->groupnumber *= 10;\n\t\tframe->groupnumber += (frame->groupname[len - 1] - '0');\n\n\t\tframe->groupname[len - 1] = '\\0';\n\t}\n\n\tfor (i = 0; i < 3; i++) {\n\t\t// these are byte values, so we don't have to worry about endianness\n\t\tframe->bboxmin[i] = pdaliasframe->bboxmin.v[i] * pheader->scale[i] + pheader->scale_origin[i];\n\t\tframe->bboxmax[i] = pdaliasframe->bboxmax.v[i] * pheader->scale[i] + pheader->scale_origin[i];\n\t}\n\tframe->radius = RadiusFromBounds(frame->bboxmin, frame->bboxmax);\n\n\tpinframe = (trivertx_t *)(pdaliasframe + 1);\n\n\tposeverts[*posenum] = pinframe;\n\t(*posenum)++;\n\n\tpinframe += pheader->numverts;\n\n\treturn (void *)pinframe;\n}\n\nstatic void* Mod_LoadAliasGroup(void * pin, maliasframedesc_t *frame, int* posenum)\n{\n\tdaliasgroup_t *pingroup;\n\tint i, numframes;\n\tdaliasinterval_t *pin_intervals;\n\tvoid *ptemp;\n\n\tpingroup = (daliasgroup_t *)pin;\n\n\tnumframes = LittleLong(pingroup->numframes);\n\n\tframe->firstpose = *posenum;\n\tframe->numposes = numframes;\n\n\tfor (i = 0; i < 3; i++) {\n\t\t// these are byte values, so we don't have to worry about endianness\n\t\tframe->bboxmin[i] = pingroup->bboxmin.v[i] * pheader->scale[i] + pheader->scale_origin[i];\n\t\tframe->bboxmax[i] = pingroup->bboxmax.v[i] * pheader->scale[i] + pheader->scale_origin[i];\n\t}\n\tframe->radius = RadiusFromBounds(frame->bboxmin, frame->bboxmax);\n\n\tpin_intervals = (daliasinterval_t *)(pingroup + 1);\n\tframe->interval = LittleFloat(pin_intervals->interval);\n\tpin_intervals += numframes;\n\n\tptemp = (void *)pin_intervals;\n\tfor (i = 0; i < numframes; i++) {\n\t\tposeverts[*posenum] = (trivertx_t *)((daliasframe_t *)ptemp + 1);\n\t\t(*posenum)++;\n\n\t\tptemp = (trivertx_t *)((daliasframe_t *)ptemp + 1) + pheader->numverts;\n\t}\n\n\treturn ptemp;\n}\n\nmaliasframedesc_t* R_AliasModelFindFrame(aliashdr_t* hdr, const char* framename, int framenumber)\n{\n\tint f;\n\n\tfor (f = 0; f < hdr->numframes; ++f) {\n\t\tmaliasframedesc_t* frame = &hdr->frames[f];\n\n\t\tif (frame->groupnumber == framenumber && !strcmp(frame->groupname, framename)) {\n\t\t\treturn frame;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nvoid R_AliasModelColor(const entity_t* ent, float* color, qbool* invalidate_texture)\n{\n\t*invalidate_texture = false;\n\n\tif (ent->custom_model == NULL) {\n\t\tif (ent->r_modelcolor[0] < 0) {\n\t\t\t// normal color\n\t\t\tVectorSet(color, 1, 1, 1);\n\t\t}\n\t\telse {\n\t\t\tVectorCopy(ent->r_modelcolor, color);\n\t\t}\n\t}\n\telse {\n\t\tVectorScale(ent->custom_model->color_cvar.color, 1.0f / 255, color);\n\n\t\t*invalidate_texture = (ent->custom_model->fullbright_cvar.integer || ent->custom_model->disable_texturing);\n\t}\n\tVectorScale(color, ent->r_modelalpha, color);\n\tcolor[3] = ent->r_modelalpha;\n}\n\nvoid R_AliasModelPrepare(entity_t* ent, int framecount, int* frame1_, int* frame2_, float* lerpfrac, qbool* outline)\n{\n\textern cvar_t r_viewmodelsize, cl_drawgun;\n\tint frame1 = bound(0, ent->oldframe, framecount - 1);\n\tint frame2 = bound(0, ent->frame, framecount - 1);\n\tint expected1 = Mod_ExpectedNextFrame(ent->model, frame1, framecount);\n\tint expected2 = Mod_ExpectedNextFrame(ent->model, frame2, framecount);\n\n\tR_RotateForEntity(ent);\n\tif ((ent->renderfx & RF_WEAPONMODEL) && r_viewmodelsize.value < 1) {\n\t\t// perform scalling for r_viewmodelsize\n\t\tR_ScaleModelview(0.5 + bound(0, r_viewmodelsize.value, 1) / 2, 1, 1);\n\t}\n\n\t//\n\tent->r_modelalpha = ((ent->renderfx & RF_WEAPONMODEL) && gl_mtexable) ? bound(0, cl_drawgun.value, 1) : 1;\n\tent->r_modelalpha = (ent->alpha ? ent->alpha : ent->r_modelalpha);\n\tif (!(ent->renderfx & RF_FORCECOLOURMOD))\n\t\tent->r_modelcolor[0] = -1;  // by default no solid fill color for model, using texture\n\n\tR_AliasSetupLighting(ent);\n\n\tif (!r_lerpframes.value || ent->framelerp < 0 || frame1 == frame2 || (frame2 != expected1 && frame1 != expected2)) {\n\t\t*lerpfrac = 1.0f;\n\t}\n\telse {\n\t\t*lerpfrac = min(ent->framelerp, 1.0f);\n\t}\n\n\tif (frame2 != expected1 && frame1 == expected2) {\n\t\tint temp = frame1;\n\t\tframe1 = frame2;\n\t\tframe2 = temp;\n\t\t*lerpfrac = 1.0f - *lerpfrac;\n\t}\n\n\tif (*lerpfrac == 1) {\n\t\t*lerpfrac = 0;\n\t\tframe1 = frame2;\n\t}\n\n\t*frame1_ = frame1;\n\t*frame2_ = frame2;\n\n\t// We don't support outline for transparent models,\n\t*outline &= ent->r_modelalpha == 1;\n}\n"
  },
  {
    "path": "src/r_aliasmodel.h",
    "content": "\n#ifndef EZQUAKE_R_ALIASMODEL_HEADER\n#define EZQUAKE_R_ALIASMODEL_HEADER\n\n#include \"r_buffers.h\"\n\nextern byte      *shadedots;\n\nvoid R_AliasSetupLighting(entity_t *ent);\n\nvoid GLM_DrawAliasModelFrame(\n\tentity_t* ent, model_t* model, int poseVertIndex, int poseVertIndex2, int vertsPerPose,\n\ttexture_ref texture, qbool outline, int effects, int render_effects, float lerp_fraction\n);\n\nvoid* Mod_LoadAllSkins(model_t* loadmodel, int numskins, daliasskintype_t* pskintype);\n\nextern cvar_t gl_powerupshells_base1level, gl_powerupshells_base2level;\nextern cvar_t gl_powerupshells_effect1level, gl_powerupshells_effect2level;\nextern cvar_t gl_fb_models, r_shadows, r_fullbrightSkins, r_drawentities;\n\nvoid R_SetupAliasFrame(\n\tentity_t* ent,\n\tmodel_t* model,\n\tmaliasframedesc_t *oldframe, maliasframedesc_t *frame,\n\tqbool outline, texture_ref texture, texture_ref fb_texture,\n\tint effects, int render_effects\n);\n\n#define ALIAS_BASE_SIZE_RATIO\t\t(1.0 / 11.0)\n// normalizing factor so player model works out to about\n//  1 pixel per triangle\n#define\tMAX_LBM_HEIGHT\t\t480\n\n// FIXME: Get rid\nextern qbool gl_mtexable;\n\nextern texture_ref shelltexture;\n\nvoid GLC_StateBeginMD3Draw(float alpha, qbool textured, qbool weapon, qbool additive_pass);\nvoid R_StateBeginDrawAliasModel(const entity_t* e, aliashdr_t* paliashdr);\n\n// gl_mesh.c\nvoid R_AliasModelPopulateVBO(model_t* mod, vbo_model_vert_t* aliasModelBuffer, int position);\nvoid R_CreateAliasModelVBO(void);\n\nvoid GLC_StateBeginAliasPowerupShell(qbool weapon);\nvoid GLC_AllocateAliasPoseBuffer(void);\n\nvoid GLC_BeginCausticsTextureMatrix(void);\nvoid GLC_EndCausticsTextureMatrix(void);\n\nvoid R_DrawAliasModel(entity_t *ent, qbool outline);\n\nqbool R_FilterEntity(entity_t* ent);\nqbool R_CullAliasModel(entity_t* ent, maliasframedesc_t* oldframe, maliasframedesc_t* frame);\n\nvoid R_AliasModelPrepare(entity_t* ent, int framecount, int* frame1, int* frame2, float* lerpfrac, qbool* outline);\nint R_AliasFramePose(const maliasframedesc_t* frame);\nmaliasframedesc_t* R_AliasModelFindFrame(aliashdr_t* hdr, const char* framename, int framenumber);\nvoid R_AliasModelColor(const entity_t* ent, float* color, qbool* invalidate_texture);\n\n// Previous constant was 135, pre-scaling... this is in world coordinates now,\n//   so picked a new constant that stopped the nailgun moving \n#define ALIASMODEL_MAX_LERP_DISTANCE (6.3)\n\nvoid R_SetSkinForPlayerEntity(entity_t* ent, texture_ref* texture, texture_ref* fb_texture, byte** color32bit);\nvoid R_AliasModelDeterminePoses(const maliasframedesc_t* oldframe, const maliasframedesc_t* frame, int* prevpose, int* nextpose, float* lerpfrac);\n\n// FIXME: not really GL_, probably Vulkan too?\nvoid GL_PrepareAliasModel(model_t* m, aliashdr_t* hdr);\n\n#endif // EZQUAKE_R\n"
  },
  {
    "path": "src/r_aliasmodel_md3.c",
    "content": "/*\nCopyright (C) 2011 fuh and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Code to load MD3 files\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_aliasmodel_md3.h\"\n#include \"vx_vertexlights.h\" \n#include \"r_texture.h\"\n#include \"r_buffers.h\"\n#include \"r_local.h\"\n\nvoid GLM_MakeAlias3DisplayLists(model_t* model);\n\ntypedef float m3by3_t[3][3];\n\nstatic int Mod_ReadFlagsFromMD1(char *name, int md3version)\n{\n\tmdl_t* pinmodel;\n\tchar fname[MAX_QPATH];\n\tCOM_StripExtension(name, fname, sizeof(fname));\n\tCOM_DefaultExtension(fname, \".md3\", sizeof(fname));\n\n\tif (!strcmp(name, fname)) {\n\t\t//md3 renamed as mdl\n\t\tCOM_StripExtension(name, fname, sizeof(fname));\t//seeing as the md3 is named over the mdl,\n\t\tCOM_DefaultExtension(fname, \".md1\", sizeof(fname));//read from a file with md1 (one, not an ell)\n\t}\n\telse {\n\t\tCOM_StripExtension(name, fname, sizeof(fname));\n\t\tCOM_DefaultExtension(fname, \".mdl\", sizeof(fname));\n\t}\n\n\tpinmodel = (mdl_t *)FS_LoadTempFile(fname, NULL);\n\n\tif (!pinmodel) {\n\t\t//not found\n\t\treturn 0;\n\t}\n\n\tif (pinmodel->ident != IDPOLYHEADER) {\n\t\treturn 0;\n\t}\n\tif (pinmodel->version != ALIAS_VERSION) {\n\t\treturn 0;\n\t}\n\treturn pinmodel->flags;\n}\n\ntypedef struct md3_skin_list_entry_s {\n\tsurfinf_t surface_info;\n\tint surface_number;\n\tint skin_number;\n\n\tstruct md3_skin_list_entry_s* next;\n} md3_skin_list_entry_t;\n\nstatic void Mod_MD3LoadSkins(model_t* mod, md3Header_t* header, md3model_t* model)\n{\n\tconst char* alternative_names[] = { \"default\", \"blue\", \"red\", \"green\", \"yellow\" };\n\tchar skinfileskinname[128];\n\tchar strippedmodelname[128];\n\tint found_skins = 0;\n\tint surface = 0;\n\tmd3Surface_t* surf;\n\tmd3_skin_list_entry_t* skin_list = NULL;\n\n\tstrlcpy(strippedmodelname, mod->name, sizeof(strippedmodelname));\n\tCOM_StripExtension(strippedmodelname, strippedmodelname, sizeof(strippedmodelname));\n\n\t*skinfileskinname = '\\0';\n\n\t// Load as many external skins as we can find\n\twhile (true) {\n\t\tchar *sfile, *sfilestart, *nl;\n\t\tchar skinfile[128] = { 0 };\n\t\tint skinfile_length;\n\n\t\tsnprintf(skinfile, sizeof(skinfile), \"%s_%d.skin\", strippedmodelname, found_skins);\n\t\tsfile = sfilestart = (char *)FS_LoadHeapFile(skinfile, &skinfile_length);\n\n\t\tif (!sfile) {\n\t\t\t// Try alternate name if we have one\n\t\t\tif (found_skins < sizeof(alternative_names) / sizeof(alternative_names[0])) {\n\t\t\t\tsnprintf(skinfile, sizeof(skinfile), \"%s_%s.skin\", strippedmodelname, alternative_names[found_skins]);\n\t\t\t\tsfile = sfilestart = (char *)FS_LoadHeapFile(skinfile, &skinfile_length);\n\t\t\t}\n\t\t\t\n\t\t\tif (!sfile) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Scan the file for the textures to use for each surface\n\t\tMD3_ForEachSurface(header, surf, surface) {\n\t\t\tint name_length = strlen(surf->name);\n\t\t\tmd3_skin_list_entry_t* new_skin = Q_malloc(sizeof(md3_skin_list_entry_t));\n\n\t\t\tnew_skin->next = skin_list;\n\t\t\tskin_list = new_skin;\n\t\t\tnew_skin->surface_number = surface;\n\t\t\tnew_skin->skin_number = found_skins;\n\n\t\t\tstrlcpy(new_skin->surface_info.name, \"textures/\", sizeof(new_skin->surface_info.name));\n\t\t\tstrlcat(new_skin->surface_info.name, surf->name, sizeof(new_skin->surface_info.name));\n\n\t\t\t// Search through the .skin file for the mapping from surface => texture\n\t\t\tfor (sfile = sfilestart; sfile < sfilestart + skinfile_length && sfile[0]; sfile = nl + 1) {\n\t\t\t\tnl = strchr(sfile, '\\n');\n\t\t\t\tif (!nl) {\n\t\t\t\t\tnl = sfile + skinfile_length;\n\t\t\t\t}\n\t\t\t\tif (sfile[name_length] == ',' && !strncasecmp(surf->name, sfile, name_length)) {\n\t\t\t\t\tstrlcpy(new_skin->surface_info.name, sfile + name_length + 1, sizeof(new_skin->surface_info.name));\n\t\t\t\t\tnew_skin->surface_info.name[min(sizeof(new_skin->surface_info.name) - 1, nl - (sfile + name_length + 1))] = '\\0';\n\t\t\t\t\tnl = strchr(new_skin->surface_info.name, '\\r');\n\t\t\t\t\tif (nl) {\n\t\t\t\t\t\t*nl = '\\0';\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tsfile = nl + 1;\n\t\t\t}\n\t\t}\n\t\tQ_free(sfilestart);\n\n\t\t++found_skins;\n\t}\n\n\t// End: make sure we allocate at least as many skins as defined in the model\n\twhile (found_skins < max(header->numSkins, 1)) {\n\t\tMD3_ForEachSurface(header, surf, surface) {\n\t\t\tmd3_skin_list_entry_t* new_skin = Q_malloc(sizeof(md3_skin_list_entry_t));\n\t\t\tnew_skin->next = skin_list;\n\t\t\tskin_list = new_skin;\n\t\t\tstrlcpy(new_skin->surface_info.name, \"textures/\", sizeof(new_skin->surface_info.name));\n\t\t\tstrlcat(new_skin->surface_info.name, COM_SkipPath(surf->name), sizeof(new_skin->surface_info.name));\n\t\t\t++found_skins;\n\t\t}\n\t}\n\n\theader->numSkins = found_skins;\n\n\t// Convert linked list to array so it's moveable & easier to access\n\tsurfinf_t* surface_info = Hunk_AllocName(sizeof(surfinf_t) * header->numSkins * header->numSurfaces, \"md3surfinfo\");\n\tmodel->surfinf = (int)((intptr_t)surface_info - (intptr_t)model);\n\twhile (skin_list) {\n\t\tmd3_skin_list_entry_t* next = skin_list->next;\n\t\tint surf_info_index = skin_list->skin_number * header->numSurfaces + skin_list->surface_number;\n\n\t\tsurface_info[surf_info_index] = skin_list->surface_info;\n\n\t\tQ_free(skin_list);\n\t\tskin_list = next;\n\t}\n}\n\nvoid Mod_LoadAlias3Model(model_t *mod, void *buffer, int filesize)\n{\n\tint start, end, total;\n\tint numsurfs;\n\tint surfn;\n\n\tmd3model_t *pheader;\n\tmd3Header_t *mem;\n\tsurfinf_t *sinf;\n\tmd3Surface_t *surf;\n\tmd3Shader_t *sshad;\n\tmd3Frame_t *mFrame;\n\tint fr, i, j;\n\tmd3St_t *st;\n\tezMd3XyzNormal_t* output_vert;\n\n\tstart = Hunk_LowMark();\n\n\tmod->type = mod_alias3;\n\tMod_AddModelFlags(mod);\n\n\tnumsurfs = LittleLong(((md3Header_t *)buffer)->numSurfaces);\n\n\tpheader = (md3model_t *)Hunk_AllocName(sizeof(md3model_t), \"md3model\");\n\tmem = (md3Header_t *)Hunk_AllocName(filesize, \"md3header\");\n\tpheader->md3model = (char *)mem - (char *)pheader;\n\tmemcpy(mem, buffer, filesize);\t//casually load the entire thing. As you do.\n\n\tmem->ident = LittleLong(mem->ident);\n\tmem->version = LittleLong(mem->version);\n\n\tmem->flags = LittleLong(mem->flags);\t//Does anyone know what these are?\n\n\tmem->numFrames = LittleLong(mem->numFrames);\n\tmem->numTags = LittleLong(mem->numTags);\n\tmem->numSurfaces = LittleLong(mem->numSurfaces);\n\n\tmem->numSkins = LittleLong(mem->numSkins);\n\n\tmem->ofsFrames = LittleLong(mem->ofsFrames);\n\tmem->ofsTags = LittleLong(mem->ofsTags);\n\tmem->ofsSurfaces = LittleLong(mem->ofsSurfaces);\n\tmem->ofsEnd = LittleLong(mem->ofsEnd);\n\n\tpheader->numframes = mem->numFrames;\n\tpheader->numtags = mem->numTags;\n\tpheader->ofstags = pheader->md3model + mem->ofsTags;\n\tfor (fr = 0; fr < mem->numFrames; fr++) {\n\t\tmFrame = ((md3Frame_t *)((char *)mem + mem->ofsFrames)) + fr;\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tmFrame->bounds[0][j] = LittleFloat(mFrame->bounds[0][j]);\n\t\t\tmFrame->bounds[1][j] = LittleFloat(mFrame->bounds[1][j]);\n\t\t\tmFrame->localOrigin[j] = LittleFloat(mFrame->localOrigin[j]);\n\t\t}\n\t\tmFrame->radius = LittleLong(mFrame->radius);\n\t}\n\n\tMod_MD3LoadSkins(mod, mem, pheader);\n\t{\n\t\tsinf = (surfinf_t*)((char *)pheader + pheader->surfinf);\n\t\tsurf = (md3Surface_t *)((char *)mem + mem->ofsSurfaces);\n\t\tfor (surfn = 0; surfn < numsurfs; surfn++) {\n\t\t\tmd3Triangle_t\t*tris;\n\t\t\tmd3XyzNormal_t\t*vert;\n\n\t\t\tsurf->ident = LittleLong(surf->ident);\n\n\t\t\tsurf->flags = LittleLong(surf->flags);\n\t\t\tsurf->numFrames = LittleLong(surf->numFrames);\n\n\t\t\tsurf->numShaders = LittleLong(surf->numShaders);\n\t\t\tsurf->numVerts = LittleLong(surf->numVerts);\n\n\t\t\tsurf->numTriangles = LittleLong(surf->numTriangles);\n\t\t\tsurf->ofsTriangles = LittleLong(surf->ofsTriangles);\n\n\t\t\tsurf->ofsShaders = LittleLong(surf->ofsShaders);\n\t\t\tsurf->ofsSt = LittleLong(surf->ofsSt);\n\t\t\tsurf->ofsXyzNormals = LittleLong(surf->ofsXyzNormals);\n\n\t\t\tsurf->ofsEnd = LittleLong(surf->ofsEnd);\n\n\t\t\tst = (md3St_t *)((char *)surf + surf->ofsSt);\t//skin texture coords.\n\t\t\tfor (i = 0; i < surf->numVerts; i++) {\n\t\t\t\tst[i].s = LittleFloat(st[i].s);\n\t\t\t\tst[i].t = LittleFloat(st[i].t);\n\t\t\t}\n\n\t\t\t// swap all the triangles\n\t\t\ttris = (md3Triangle_t *)((char *)surf + surf->ofsTriangles);\n\t\t\tfor (j = 0; j < surf->numTriangles; j++) {\n\t\t\t\ttris[j].indexes[0] = LittleLong(tris[j].indexes[0]);\n\t\t\t\ttris[j].indexes[1] = LittleLong(tris[j].indexes[1]);\n\t\t\t\ttris[j].indexes[2] = LittleLong(tris[j].indexes[2]);\n\t\t\t}\n\n\t\t\t// swap all the vertices, convert to our format\n\t\t\tvert = (md3XyzNormal_t *)((char *)surf + surf->ofsXyzNormals);\n\t\t\toutput_vert = (ezMd3XyzNormal_t*)Hunk_AllocName(surf->numVerts * surf->numFrames * sizeof(ezMd3XyzNormal_t), \"md3normal\");\n\t\t\tfor (j = 0; j < surf->numVerts * surf->numFrames; j++) {\n\t\t\t\tvert[j].xyz[0] = LittleShort(vert[j].xyz[0]);\n\t\t\t\tvert[j].xyz[1] = LittleShort(vert[j].xyz[1]);\n\t\t\t\tvert[j].xyz[2] = LittleShort(vert[j].xyz[2]);\n\t\t\t\tvert[j].normal = LittleShort(vert[j].normal);\n\n\t\t\t\tVectorScale(vert[j].xyz, MD3_XYZ_SCALE, output_vert[j].xyz);\n\t\t\t\t{\n\t\t\t\t\toutput_vert[j].normal_lat = ((vert[j].normal >> 8) & 255) * (2.0 * M_PI) / 255.0;\n\t\t\t\t\toutput_vert[j].normal_lng = (vert[j].normal & 255) * (2.0 * M_PI) / 255.0;\n\n\t\t\t\t\toutput_vert[j].normal[0] = cos(output_vert[j].normal_lat) * sin(output_vert[j].normal_lng);\n\t\t\t\t\toutput_vert[j].normal[1] = sin(output_vert[j].normal_lat) * sin(output_vert[j].normal_lng);\n\t\t\t\t\toutput_vert[j].normal[2] = cos(output_vert[j].normal_lng);\n\t\t\t\t}\n\t\t\t}\n\t\t\tsurf->ofsXyzNormals = (char*)output_vert - (char*)surf;\n\n\t\t\tsshad = (md3Shader_t *)((char *)surf + surf->ofsShaders);\n\t\t\tsshad->shaderIndex = LittleLong(sshad->shaderIndex);\n\n\t\t\tfor (i = 0; i < mem->numSkins; ++i) {\n\t\t\t\tchar specifiedskinnameinfolder[128] = { 0 };\n\t\t\t\tchar tenebraeskinname[128] = { 0 };\n\n\t\t\t\t// MEAG: Changed the load order here, tenebraeskinname can be over-written by the .skin file\n\t\t\t\tconst char* potential_textures[] = {\n\t\t\t\t\tsinf->name,\n\t\t\t\t\tsshad->name,\n\t\t\t\t\tspecifiedskinnameinfolder,\n\t\t\t\t\ttenebraeskinname\n\t\t\t\t};\n\n\t\t\t\tstrlcpy(specifiedskinnameinfolder, \"textures/\", sizeof(specifiedskinnameinfolder));\n\t\t\t\tstrlcat(specifiedskinnameinfolder, sshad->name, sizeof(specifiedskinnameinfolder));\n\n\t\t\t\tif (sshad->name[0]) {\n\t\t\t\t\tstrlcpy(tenebraeskinname, mod->name, sizeof(tenebraeskinname)); //backup\n\t\t\t\t\tCOM_SkipPathWritable(tenebraeskinname)[0] = '\\0';\n\t\t\t\t\tstrlcat(tenebraeskinname, sshad->name, sizeof(tenebraeskinname));\n\t\t\t\t}\n\n\t\t\t\t// Try and load\n\t\t\t\tfor (j = 0; j < sizeof(potential_textures) / sizeof(potential_textures[0]); ++j) {\n\t\t\t\t\tif (potential_textures[j][0] && R_TextureReferenceIsValid(sinf->texnum = R_LoadTextureImage(potential_textures[j], potential_textures[j], 0, 0, 0))) {\n\t\t\t\t\t\tif (potential_textures[j] != sinf->name) {\n\t\t\t\t\t\t\tstrlcpy(sinf->name, potential_textures[j], sizeof(sinf->name));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!R_TextureReferenceIsValid(sinf->texnum)) {\n\t\t\t\t\tsinf->texnum = R_LoadTextureImage(\"dummy\", \"dummy\", 0, 0, 0);\n\t\t\t\t\tstrlcpy(sinf->name, \"dummy\", sizeof(sinf->name));\n\t\t\t\t}\n\t\t\t\t++sinf;\n\t\t\t}\n\n\t\t\tsurf = (md3Surface_t *)((char *)surf + surf->ofsEnd);\n\t\t}\n\t}\n\n\tend = Hunk_LowMark();\n\ttotal = end - start;\n\n\tmod->cached_data = Q_malloc(total);\n\tif (!mod->cached_data) {\n\t\treturn;\n\t}\n\tmemcpy(mod->cached_data, pheader, total);\n\n\t// try load simple textures\n\tmemset(mod->simpletexture, 0, sizeof(mod->simpletexture));\n\tfor (i = 0; i < sizeof(mod->simpletexture) / sizeof(mod->simpletexture[0]); ++i) {\n\t\tmod->simpletexture[i] = Mod_LoadSimpleTexture(mod, i);\n\t\tif (!R_TextureReferenceIsValid(mod->simpletexture[i])) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tHunk_FreeToLowMark(start);\n\n\tmod->flags = Mod_ReadFlagsFromMD1(mod->name, mem->flags);\n\n\tif (buffers.supported) {\n\t\tGLM_MakeAlias3DisplayLists(mod);\n\t}\n}\n\n/*\n// Commented out for the moment, isn't used...\n// if anyone is interested that is...\nint GetTag(model_t *mod, char *tagname, int frame, float **org, m3by3_t **ang)\n{\n\tmd3model_t *mhead;\n\tmd3tag_t *tag;\n\tint tnum;\n\tfloat bad[3] = { 0 };\n\tm3by3_t badm = { {0} };\n\n\t*org = bad;\n\t*ang = &badm;\n\n\tif (mod->type != mod_alias3) {\n\t\treturn false;\n\t}\n\n\tmhead = (md3model_t *)Mod_Extradata(mod);\n\tif (frame > mhead->numframes) {\n\t\tframe = 0;\n\t}\n\n\ttag = (md3tag_t *)((char *)mhead + mhead->ofstags);\n\ttag += frame*mhead->numtags;\n\n\tfor (tnum = 0;tnum < mhead->numtags; tnum++) {\n\t\tif (!strcasecmp(tag->name, tagname)) {\n\t\t\t*org = tag->org;\n\t\t\t*ang = &tag->ang;\n\t\t\treturn true;\n\t\t}\n\t\ttag++;\n\t}\n\n\treturn false;\n}*/\n\n// Helper functions to simplify logic\nmd3Surface_t* MD3_NextSurface(md3Surface_t* surf)\n{\n\treturn (md3Surface_t *)((uintptr_t)surf + surf->ofsEnd);\n}\n\nmd3Surface_t* MD3_FirstSurface(md3Header_t* header)\n{\n\treturn (md3Surface_t *)((uintptr_t)header + header->ofsSurfaces);\n}\n\nmd3St_t* MD3_SurfaceTextureCoords(md3Surface_t* surface)\n{\n\treturn (md3St_t *)((uintptr_t)surface + surface->ofsSt);\n}\n\nezMd3XyzNormal_t* MD3_SurfaceVertices(md3Surface_t* surface)\n{\n\treturn (ezMd3XyzNormal_t *)((uintptr_t)surface + surface->ofsXyzNormals);\n}\n\nmd3Triangle_t* MD3_SurfaceTriangles(md3Surface_t* surface)\n{\n\treturn (md3Triangle_t*)((uintptr_t)surface + surface->ofsTriangles);\n}\n\nsurfinf_t* MD3_ExtraSurfaceInfoForModel(md3model_t* model)\n{\n\treturn (surfinf_t *)((uintptr_t)model + model->surfinf);\n}\n\nmd3Header_t* MD3_HeaderForModel(md3model_t* model)\n{\n\treturn (md3Header_t *)((uintptr_t)model + model->md3model);\n}\n"
  },
  {
    "path": "src/r_aliasmodel_md3.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef _GL_MD3_H_\n#define _GL_MD3_H_\n\n#define MD3_IDENT\t\t(('3'<<24)+('P'<<16)+('D'<<8)+'I')\n\n#define\tMD3_XYZ_SCALE\t(1.0 / 64)\n\nvoid Mod_LoadAlias3Model (model_t *mod, void *buffer, int filesize);\nstruct entity_s;\t//whoops\n\n//structures from Tenebrae\ntypedef struct {\n\tint\t\t\tident;\n\tint\t\t\tversion;\n\n\tchar\t\tname[MAX_QPATH];\n\n\tint\t\t\tflags;\t//Does anyone know what these are?\n\n\tint\t\t\tnumFrames;\n\tint\t\t\tnumTags;\t\t\t\n\tint\t\t\tnumSurfaces;\n\n\tint\t\t\tnumSkins;\n\n\tint\t\t\tofsFrames;\n\tint\t\t\tofsTags;\n\tint\t\t\tofsSurfaces;\n\tint\t\t\tofsEnd;\n} md3Header_t;\n\n//then has header->numFrames of these at header->ofs_Frames\ntypedef struct md3Frame_s {\n\tvec3_t\t\tbounds[2];\n\tvec3_t\t\tlocalOrigin;\n\tfloat\t\tradius;\n\tchar\t\tname[16];\n} md3Frame_t;\n\n//there are header->numSurfaces of these at header->ofsSurfaces, following from ofsEnd\ntypedef struct {\n\tint\t\tident;\t\t\t\t// \n\n\tchar\tname[MAX_QPATH];\t// polyset name\n\n\tint\t\tflags;\n\tint\t\tnumFrames;\t\t\t// all surfaces in a model should have the same\n\n\tint\t\tnumShaders;\t\t\t// all surfaces in a model should have the same\n\tint\t\tnumVerts;\n\n\tint\t\tnumTriangles;\n\tint\t\tofsTriangles;\n\n\tint\t\tofsShaders;\t\t\t// offset from start of md3Surface_t\n\tint\t\tofsSt;\t\t\t\t// texture coords are common for all frames\n\tint\t\tofsXyzNormals;\t\t// numVerts * numFrames (after loading this now points to ezMd3NormalXyz_t)\n\n\tint\t\tofsEnd;\t\t\t\t// next surface follows\n} md3Surface_t;\n\n//at surf+surf->ofsXyzNormals (on initial load only, then that points to ezMd3NormalXyz_t)\ntypedef struct {\n\tshort\t\txyz[3];\n\tunsigned short normal;\n} md3XyzNormal_t;\n\ntypedef struct {\n\tvec3_t      xyz;\n\tvec3_t      normal;\n\tfloat       normal_lat;\n\tfloat       normal_lng;\n} ezMd3XyzNormal_t;\n\n//surf->numTriangles at surf+surf->ofsTriangles\ntypedef struct {\n\tint\t\t\tindexes[3];\n} md3Triangle_t;\n\n//surf->numVerts at surf+surf->ofsSt\ntypedef struct {\n\tfloat\t\ts;\n\tfloat\t\tt;\n} md3St_t;\n\ntypedef struct {\n\tchar\t\t\tname[MAX_QPATH];\n\tint\t\t\t\tshaderIndex;\n} md3Shader_t;\n//End of Tenebrae 'assistance'\n\ntypedef struct {\n\tchar name[MAX_QPATH];\n\tvec3_t org;\n\tfloat ang[3][3];\n} md3tag_t;\n\n//extra surfinfo\ntypedef struct {\n\tchar name[MAX_QPATH];\t//FIXME: pointer to shader. Requires model recaching when shader info is wiped though.\n\ttexture_ref texnum;\n} surfinf_t;\n\ntypedef struct {\n\tint surfinf;\t\t//ofs, surfs*skins\n\tint md3model;\t//ofs md3Header_t\n\n\tint ofstags;\n\tint numtags;\n\tint numframes;\n} md3model_t;\n\nmd3Surface_t* MD3_NextSurface(md3Surface_t* surf);\nmd3Surface_t* MD3_FirstSurface(md3Header_t* header);\nmd3St_t* MD3_SurfaceTextureCoords(md3Surface_t* surface);\nezMd3XyzNormal_t* MD3_SurfaceVertices(md3Surface_t* surface);\nmd3Triangle_t* MD3_SurfaceTriangles(md3Surface_t* surface);\nsurfinf_t* MD3_ExtraSurfaceInfoForModel(md3model_t* model);\nmd3Header_t* MD3_HeaderForModel(md3model_t* model);\n\n#define MD3_ForEachSurface(header, surface, surfnum) \\\n\tfor ((surfnum) = 0, (surface) = MD3_FirstSurface(header); (surfnum) < (header)->numSurfaces; ++(surfnum), (surface) = MD3_NextSurface(surface))\n\n#endif\n"
  },
  {
    "path": "src/r_aliasmodel_mesh.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// gl_mesh.c: triangle model functions\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_local.h\"\n#include \"r_aliasmodel.h\"\n#include \"glsl/constants.glsl\"\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\nvoid GLM_CreateAliasModelVAO(void);\n#endif\n\n// Also used by .md3\nvoid GL_AliasModelSetVertexDirection(int num_triangles, vbo_model_vert_t* vbo_buffer, int v1, int v2, qbool limit_lerp, int key_pose)\n{\n\tint j, k;\n\n\tfor (j = 0; j < num_triangles; ++j) {\n\t\tfor (k = 0; k < 3; ++k, ++v1, ++v2) {\n\t\t\tVectorSubtract(vbo_buffer[v2].position, vbo_buffer[v1].position, vbo_buffer[v1].direction);\n\n\t\t\tvbo_buffer[v1].flags = 0;\n\t\t\tif (limit_lerp) {\n\t\t\t\tif ((vbo_buffer[v1].position[0] > 0) != (vbo_buffer[v2].position[0] > 0)) {\n\t\t\t\t\tvbo_buffer[v1].flags |= AM_VERTEX_NOLERP;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Also used by .md3\nvoid GL_AliasModelFixNormals(vbo_model_vert_t* vbo_buffer, int v, int vertsPerPose)\n{\n\tint j, k;\n\tvec3_t new_normal;  // 2020...\n\tint matches;\n\n\tfor (j = 0; j < vertsPerPose; ++j) {\n\t\tif (vbo_buffer[v + j].flags & AM_VERTEX_NORMALFIXED) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tVectorCopy(vbo_buffer[v + j].normal, new_normal);\n\t\tmatches = 1;\n\n\t\tfor (k = j + 1; k < vertsPerPose; ++k) {\n\t\t\tif (VectorCompare(vbo_buffer[v + j].position, vbo_buffer[v + k].position)) {\n\t\t\t\tVectorAdd(new_normal, vbo_buffer[v + k].normal, new_normal);\n\t\t\t\t++matches;\n\t\t\t}\n\t\t}\n\n\t\tif (matches > 1) {\n\t\t\tVectorScale(new_normal, 1.0f / matches, new_normal);\n\n\t\t\tVectorCopy(new_normal, vbo_buffer[v + j].normal);\n\t\t\tfor (k = j + 1; k < vertsPerPose; ++k) {\n\t\t\t\tif (VectorCompare(vbo_buffer[v + j].position, vbo_buffer[v + k].position)) {\n\t\t\t\t\tVectorCopy(new_normal, vbo_buffer[v + k].normal);\n\t\t\t\t\tvbo_buffer[v + k].flags |= AM_VERTEX_NORMALFIXED;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid GL_PrepareAliasModel(model_t* m, aliashdr_t* hdr)\n{\n\t// \n\textern float r_avertexnormals[NUMVERTEXNORMALS][3];\n\tvbo_model_vert_t* vbo_buffer;\n\tint pose, j, k, v;\n\tint f;\n\n\tv = 0;\n\thdr->poseverts = hdr->vertsPerPose = 3 * hdr->numtris;\n\tm->vertsInVBO = hdr->vertsPerPose * hdr->numposes;\n\tm->temp_vbo_buffer = vbo_buffer = Q_malloc_named(m->vertsInVBO * sizeof(vbo_model_vert_t), m->name);\n\n\t// \n\tfor (pose = 0; pose < hdr->numposes; ++pose) {\n\t\tv = pose * hdr->vertsPerPose;\n\t\tfor (j = 0; j < hdr->numtris; ++j) {\n\t\t\tfor (k = 0; k < 3; ++k, ++v) {\n\t\t\t\ttrivertx_t* src;\n\t\t\t\tfloat x, y, z, s, t;\n\t\t\t\tint l;\n\t\t\t\tint vert = triangles[j].vertindex[k];\n\n\t\t\t\tsrc = &poseverts[pose][vert];\n\n\t\t\t\tl = src->lightnormalindex;\n\t\t\t\tx = src->v[0] * hdr->scale[0] + hdr->scale_origin[0];\n\t\t\t\ty = src->v[1] * hdr->scale[1] + hdr->scale_origin[1];\n\t\t\t\tz = src->v[2] * hdr->scale[2] + hdr->scale_origin[2];\n\t\t\t\tif (m->modhint == MOD_EYES) {\n\t\t\t\t\tx *= 2;\n\t\t\t\t\ty *= 2;\n\t\t\t\t\tz *= 2;\n\t\t\t\t\tz -= 30;\n\t\t\t\t}\n\t\t\t\ts = stverts[vert].s;\n\t\t\t\tt = stverts[vert].t;\n\n\t\t\t\tif (!triangles[j].facesfront && stverts[vert].onseam) {\n\t\t\t\t\ts += pheader->skinwidth / 2;\t// on back side\n\t\t\t\t}\n\t\t\t\ts = (s + 0.5) / pheader->skinwidth;\n\t\t\t\tt = (t + 0.5) / pheader->skinheight;\n\n\t\t\t\tmemset(&vbo_buffer[v], 0, sizeof(vbo_buffer[v]));\n\t\t\t\tVectorSet(vbo_buffer[v].position, x, y, z);\n\t\t\t\tvbo_buffer[v].texture_coords[0] = s;\n\t\t\t\tvbo_buffer[v].texture_coords[1] = t;\n\t\t\t\tVectorCopy(r_avertexnormals[l], vbo_buffer[v].normal);\n\t\t\t\tvbo_buffer[v].flags = 0;\n\t\t\t\tvbo_buffer[v].lightnormalindex = l;\n\t\t\t}\n\t\t}\n\n\t\tGL_AliasModelFixNormals(vbo_buffer, pose * hdr->vertsPerPose, hdr->vertsPerPose);\n\t}\n\n\t// Go back through and set directions\n\tfor (f = 0; f < hdr->numframes; ++f) {\n\t\tmaliasframedesc_t* frame = &hdr->frames[f];\n\n\t\tif (frame->numposes > 1) {\n\t\t\t// This frame has animated poses, so link them all together\n\t\t\tfor (pose = 0; pose < frame->numposes - 1; ++pose) {\n\t\t\t\tGL_AliasModelSetVertexDirection(hdr->numtris, vbo_buffer, hdr->vertsPerPose * (frame->firstpose + pose), hdr->vertsPerPose * (frame->firstpose + pose + 1), m->renderfx & RF_LIMITLERP, 0);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t// Find next frame's pose\n\t\t\tmaliasframedesc_t* frame2 = R_AliasModelFindFrame(hdr, frame->groupname, frame->groupnumber + 1);\n\t\t\tif (!frame2) {\n\t\t\t\tframe2 = R_AliasModelFindFrame(hdr, frame->groupname, 1 + Mod_ExpectedNextFrame(m, frame->groupnumber, hdr->numframes));\n\t\t\t}\n\n\t\t\tif (frame2) {\n\t\t\t\tGL_AliasModelSetVertexDirection(hdr->numtris, vbo_buffer, hdr->vertsPerPose * frame->firstpose, hdr->vertsPerPose * frame2->firstpose, m->renderfx & RF_LIMITLERP, 0);\n\t\t\t\tframe->nextpose = frame2->firstpose;\n\t\t\t}\n\t\t}\n\t}\n}\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\nextern cvar_t gl_program_aliasmodels;\n#define R_GLSLAliasModelRendering() ((!R_UseImmediateOpenGL()) || gl_program_aliasmodels.integer)\n#else\n#define R_GLSLAliasModelRendering() (1)\n#endif\n\nvoid R_AliasModelPopulateVBO(model_t* mod, vbo_model_vert_t* aliasModelBuffer, int position)\n{\n\t// Don't delete if using immediate mode as we loop over them ourselves\n\tif (mod->temp_vbo_buffer && R_GLSLAliasModelRendering()) {\n\t\tmemcpy(aliasModelBuffer + position, mod->temp_vbo_buffer, mod->vertsInVBO * sizeof(vbo_model_vert_t));\n\n\t\tmod->vbo_start = position;\n\t\tQ_free(mod->temp_vbo_buffer);\n\t}\n}\n\nstatic void R_ImportSpriteCoordsToVBO(vbo_model_vert_t* verts, int* position)\n{\n\tverts = &verts[*position];\n\n\tmemset(verts, 0, sizeof(vbo_model_vert_t) * 4);\n\n\tVectorSet(verts[0].position, 0, -1, -1);\n\tverts[0].texture_coords[0] = 1;\n\tverts[0].texture_coords[1] = 1;\n\tverts[0].flags = 0;\n\n\tVectorSet(verts[1].position, 0, -1, 1);\n\tverts[1].texture_coords[0] = 1;\n\tverts[1].texture_coords[1] = 0;\n\tverts[1].flags = 1;\n\n\tVectorSet(verts[2].position, 0, 1, 1);\n\tverts[2].texture_coords[0] = 0;\n\tverts[2].texture_coords[1] = 0;\n\tverts[2].flags = 2;\n\n\tVectorSet(verts[3].position, 0, 1, -1);\n\tverts[3].texture_coords[0] = 0;\n\tverts[3].texture_coords[1] = 1;\n\tverts[3].flags = 3;\n\n\t*position += 4;\n}\n\nstatic void R_ImportModelToVBO(model_t* mod, vbo_model_vert_t* aliasmodel_data, int* new_vbo_position)\n{\n\tif (mod->type == mod_alias || mod->type == mod_alias3) {\n\t\tR_AliasModelPopulateVBO(mod, aliasmodel_data, *new_vbo_position);\n\t\t*new_vbo_position += mod->vertsInVBO;\n\t}\n\telse if (mod->type == mod_sprite) {\n\t\tmod->vbo_start = 0;\n\t}\n\telse if (mod->type == mod_brush) {\n\t\tmod->vbo_start = 0;\n\t}\n}\n\nvoid R_CreateAliasModelVBO(void)\n{\n\tvbo_model_vert_t* aliasModelData;\n\tint new_vbo_position = 0;\n\tint required_vbo_length = 4;\n\tint i;\n\n\tfor (i = 1; i < MAX_MODELS; ++i) {\n\t\tmodel_t* mod = cl.model_precache[i];\n\n\t\tif (mod && (mod->type == mod_alias || mod->type == mod_alias3)) {\n\t\t\trequired_vbo_length += mod->vertsInVBO;\n\t\t}\n\t}\n\n\tfor (i = 0; i < MAX_VWEP_MODELS; i++) {\n\t\tmodel_t* mod = cl.vw_model_precache[i];\n\n\t\tif (mod && (mod->type == mod_alias || mod->type == mod_alias3)) {\n\t\t\trequired_vbo_length += mod->vertsInVBO;\n\t\t}\n\t}\n\n\t// custom models are explicitly loaded by client, not notified by server\n\tfor (i = 0; i < custom_model_count; ++i) {\n\t\tmodel_t* mod = Mod_CustomModel(i, false);\n\n\t\tif (mod && (mod->type == mod_alias || mod->type == mod_alias3)) {\n\t\t\trequired_vbo_length += mod->vertsInVBO;\n\t\t}\n\t}\n\n\t// Go back through all models, populating VBO content\n\taliasModelData = Q_malloc(required_vbo_length * sizeof(vbo_model_vert_t));\n\n\t// VBO starts with simple-model/sprite vertices\n\tR_ImportSpriteCoordsToVBO(aliasModelData, &new_vbo_position);\n\n\tfor (i = 1; i < MAX_MODELS; ++i) {\n\t\tmodel_t* mod = cl.model_precache[i];\n\n\t\tif (mod) {\n\t\t\tR_ImportModelToVBO(mod, aliasModelData, &new_vbo_position);\n\t\t}\n\t}\n\n\tfor (i = 0; i < MAX_VWEP_MODELS; i++) {\n\t\tmodel_t* mod = cl.vw_model_precache[i];\n\n\t\tif (mod) {\n\t\t\tR_ImportModelToVBO(mod, aliasModelData, &new_vbo_position);\n\t\t}\n\t}\n\n\tfor (i = 0; i < custom_model_count; ++i) {\n\t\tmodel_t* mod = Mod_CustomModel(i, false);\n\n\t\tif (mod) {\n\t\t\tR_ImportModelToVBO(mod, aliasModelData, &new_vbo_position);\n\t\t}\n\t}\n\n\tbuffers.Create(r_buffer_aliasmodel_vertex_data, buffertype_vertex, \"aliasmodel-vertex-data\", required_vbo_length * sizeof(vbo_model_vert_t), aliasModelData, bufferusage_reuse_many_frames);\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\tif (R_UseModernOpenGL()) {\n\t\tGLM_CreateAliasModelVAO();\n#ifdef EZQ_GL_BINDINGPOINT_ALIASMODEL_SSBO\n\t\taliasModel_ssbo = buffers.Create(buffertype_storage, \"aliasmodel-vertex-ssbo\", required_vbo_length * sizeof(vbo_model_vert_t), aliasModelData, bufferusage_constant_data);\n\t\tbuffers.BindBase(aliasModel_ssbo, EZQ_GL_BINDINGPOINT_ALIASMODEL_SSBO);\n#endif\n\t}\n#endif\n\tQ_free(aliasModelData);\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_AllocateAliasPoseBuffer();\n\t}\n#endif\n}\n"
  },
  {
    "path": "src/r_aliasmodel_skins.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// Alias model (.mdl) skin processing\n// Most code taken from gl_rmain.c\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"rulesets.h\"\n#include \"r_texture.h\"\n#include \"r_local.h\"\n#include \"image.h\"\n\nstatic void Mod_FloodFillSkin(byte *skin, int skinwidth, int skinheight);\n\n//byte player_8bit_texels[320 * 200];\nbyte player_8bit_texels[256*256]; // Workaround for new player model, isn't proper for \"real\" quake skins\n\nvoid R_CompressFullbrightTexture(byte* skin_pixels, int skin_width, int skin_height, byte* fb_pixels, int fb_width, int fb_height)\n{\n\tint x, y;\n\n\t// adjust fb-texture to add alpha (this is from (TEX_FULLBRIGHT | TEX_LUMA) processing)\n\tfor (x = 0; x < skin_width; ++x) {\n\t\tfor (y = 0; y < skin_height; ++y) {\n\t\t\tint base = (x + y * skin_width) * 4;\n\t\t\textern cvar_t gl_wicked_luma_level;\n\n\t\t\t// For luma textures that don't have alpha component (or are just incorrect)\n\t\t\tif (fb_pixels[base] < gl_wicked_luma_level.integer && fb_pixels[base + 1] < gl_wicked_luma_level.integer && fb_pixels[base + 2] < gl_wicked_luma_level.integer) {\n\t\t\t\tfb_pixels[base] = fb_pixels[base + 1] = fb_pixels[base + 2] = fb_pixels[base + 3] = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\t// re-scale fb texture to match underlying skin\n\tR_TextureRescaleOverlay(&fb_pixels, &fb_width, &fb_height, skin_width, skin_height);\n\n\t// Merge the fb texture with the original\n\tfor (x = 0; x < skin_width; ++x) {\n\t\tfor (y = 0; y < skin_height; ++y) {\n\t\t\tint base = (x + y * skin_width) * 4;\n\n\t\t\tskin_pixels[base + 3] = 255;\n\t\t\tif (fb_pixels[base + 3]) {\n\t\t\t\tfloat a = fb_pixels[base + 3] / 255.0f;\n\t\t\t\tint orig = skin_pixels[base];\n\n\t\t\t\tskin_pixels[base] = (byte)bound(0, (int)orig * (1 - a) + (int)fb_pixels[base] * a, 255);\n\t\t\t\tskin_pixels[base + 1] = (byte)bound(0, (int)orig * (1 - a) + (int)fb_pixels[base + 1] * a, 255);\n\t\t\t\tskin_pixels[base + 2] = (byte)bound(0, (int)orig * (1 - a) + (int)fb_pixels[base + 2] * a, 255);\n\t\t\t\tskin_pixels[base + 3] = (1 - a) * 255;\n\t\t\t}\n\t\t}\n\t}\n\n\tQ_free(fb_pixels);\n}\n\nstatic texture_ref Mod_LoadExternalSkin(model_t* loadmodel, char *identifier, texture_ref *fb_texnum)\n{\n\tchar loadpath[MAX_OSPATH] = {0};\n\tint texmode = 0, luma_texmode;\n\ttexture_ref texnum;\n\tqbool luma_allowed = Ruleset_IsLumaAllowed(loadmodel);\n\tint skin_width = 0, skin_height = 0;\n\tint luma_width = 0, luma_height = 0;\n\tbyte* skin_pixels = NULL;\n\tbyte* luma_pixels = NULL;\n\n\tR_TextureReferenceInvalidate(texnum);\n\tR_TextureReferenceInvalidate(*fb_texnum);\n\n\tif (gl_no24bit.integer || RuleSets_DisallowExternalTexture(loadmodel)) {\n\t\treturn texnum;\n\t}\n\n\ttexmode |= TEX_MIPMAP;\n\ttexmode |= (loadmodel->modhint == MOD_VMODEL ? TEX_VIEWMODEL : 0);\n\ttexmode |= (!gl_scaleModelTextures.value ? TEX_NOSCALE : 0);\n\tluma_texmode = texmode | TEX_FULLBRIGHT | TEX_ALPHA | TEX_LUMA;\n\n\t// try \"textures/models/...\" path\n\tstrlcpy(loadpath, \"textures/models/\", sizeof(loadpath));\n\tstrlcat(loadpath, identifier, sizeof(loadpath));\n\tskin_pixels = R_LoadImagePixels(loadpath, 0, 0, texmode, &skin_width, &skin_height);\n\n\tif (!skin_pixels) {\n\t\t// try \"textures/...\" path\n\t\tstrlcpy(loadpath, \"textures/\", sizeof(loadpath));\n\t\tstrlcat(loadpath, identifier, sizeof(loadpath));\n\n\t\tskin_pixels = R_LoadImagePixels(loadpath, 0, 0, texmode, &skin_width, &skin_height);\n\t}\n\n\tif (skin_pixels && luma_allowed) {\n\t\t// not a luma actually, but which suffix use then? _fb or what?\n\t\tstrlcat(loadpath, \"_luma\", sizeof(loadpath));\n\n\t\tluma_pixels = R_LoadImagePixels(loadpath, 0, 0, luma_texmode, &luma_width, &luma_height);\n\t}\n\n\tif (R_CompressFullbrightTextures() && skin_pixels && luma_pixels) {\n\t\tR_CompressFullbrightTexture(skin_pixels, skin_width, skin_height, luma_pixels, luma_width, luma_height);\n\n\t\ttexmode |= (TEX_ALPHA | TEX_MERGED_LUMA);\n\t\tluma_pixels = NULL;\n\t}\n\n\tif (skin_pixels) {\n\t\ttexnum = R_LoadTexturePixels(skin_pixels, identifier, skin_width, skin_height, texmode);\n\t\tif (luma_pixels) {\n\t\t\t*fb_texnum = R_LoadTexturePixels(luma_pixels, va(\"@fb_%s\", identifier), luma_width, luma_height, luma_texmode);\n\t\t\tQ_free(luma_pixels);\n\t\t}\n\n\t\tQ_free(skin_pixels);\n\t}\n\n\treturn texnum;\n}\n\nvoid* Mod_LoadAllSkins(model_t* loadmodel, int numskins, daliasskintype_t* pskintype)\n{\n\tint i, j, k, s, groupskins, texmode = 0;\n\ttexture_ref gl_texnum, fb_texnum;\n\tchar basename[64], identifier[64];\n\tbyte *skin;\n\tdaliasskingroup_t *pinskingroup;\n\tdaliasskininterval_t *pinskinintervals;\n\n\tskin = (byte *)(pskintype + 1);\n\n\tif (numskins < 1 || numskins > MAX_SKINS) {\n\t\tHost_Error(\"Mod_LoadAllSkins: Invalid # of skins: %d (model %s)\\n\", numskins, loadmodel->name);\n\t}\n\n\ts = pheader->skinwidth * pheader->skinheight;\n\n\tCOM_StripExtension(COM_SkipPath(loadmodel->name), basename, sizeof(basename));\n\n\ttexmode |= TEX_MIPMAP;\n\ttexmode |= (loadmodel->modhint == MOD_VMODEL ? TEX_VIEWMODEL : 0);\n\ttexmode |= (!gl_scaleModelTextures.value && !loadmodel->isworldmodel) ? TEX_NOSCALE : 0;\n\n\tfor (i = 0; i < numskins; i++) {\n\t\tif (pskintype->type == ALIAS_SKIN_SINGLE) {\n\t\t\tMod_FloodFillSkin(skin, pheader->skinwidth, pheader->skinheight);\n\n\t\t\t// save 8 bit texels for the player model to remap\n\t\t\tif (loadmodel->modhint == MOD_PLAYER) {\n\t\t\t\tif (s > sizeof(player_8bit_texels)) {\n\t\t\t\t\tHost_Error(\"Mod_LoadAllSkins: Player skin too large (model %s)\", loadmodel->name);\n\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\t\t\t\tmemcpy(player_8bit_texels, (byte *)(pskintype + 1), s);\n\t\t\t}\n\n\t\t\tsnprintf(identifier, sizeof(identifier), \"%s_%i\", basename, i);\n\n\t\t\tR_TextureReferenceInvalidate(gl_texnum);\n\t\t\tR_TextureReferenceInvalidate(fb_texnum);\n\n\t\t\tgl_texnum = Mod_LoadExternalSkin(loadmodel, identifier, &fb_texnum);\n\t\t\tif (!R_TextureReferenceIsValid(gl_texnum)) {\n\t\t\t\tgl_texnum = R_LoadTexture(identifier, pheader->skinwidth, pheader->skinheight, (byte *)(pskintype + 1), texmode, 1);\n\n\t\t\t\tif (Img_HasFullbrights((byte *)(pskintype + 1), pheader->skinwidth * pheader->skinheight)) {\n\t\t\t\t\tfb_texnum = R_LoadTexture(va(\"@fb_%s\", identifier), pheader->skinwidth, pheader->skinheight, (byte *)(pskintype + 1), texmode | TEX_FULLBRIGHT, 1);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpheader->gl_texturenum[i][0] = pheader->gl_texturenum[i][1] = pheader->gl_texturenum[i][2] = pheader->gl_texturenum[i][3] = gl_texnum;\n\t\t\tpheader->glc_fb_texturenum[i][0] = pheader->glc_fb_texturenum[i][1] = pheader->glc_fb_texturenum[i][2] = pheader->glc_fb_texturenum[i][3] = fb_texnum;\n\n\t\t\tpskintype = (daliasskintype_t *)((byte *)(pskintype + 1) + s);\n\t\t}\n\t\telse {\n\t\t\t// animating skin group.  yuck.\n\t\t\tpskintype++;\n\t\t\tpinskingroup = (daliasskingroup_t *)pskintype;\n\t\t\tgroupskins = LittleLong(pinskingroup->numskins);\n\t\t\tpinskinintervals = (daliasskininterval_t *)(pinskingroup + 1);\n\n\t\t\tpskintype = (void *)(pinskinintervals + groupskins);\n\n\t\t\tfor (j = 0; j < groupskins; j++) {\n\t\t\t\tMod_FloodFillSkin(skin, pheader->skinwidth, pheader->skinheight);\n\n\t\t\t\tsnprintf(identifier, sizeof(identifier), \"%s_%i_%i\", basename, i, j);\n\n\t\t\t\tR_TextureReferenceInvalidate(gl_texnum);\n\t\t\t\tR_TextureReferenceInvalidate(fb_texnum);\n\n\t\t\t\tgl_texnum = Mod_LoadExternalSkin(loadmodel, identifier, &fb_texnum);\n\t\t\t\tif (!R_TextureReferenceIsValid(gl_texnum)) {\n\t\t\t\t\tgl_texnum = R_LoadTexture(identifier, pheader->skinwidth, pheader->skinheight, (byte *)(pskintype), texmode, 1);\n\n\t\t\t\t\tif (Img_HasFullbrights((byte *)(pskintype), pheader->skinwidth*pheader->skinheight)) {\n\t\t\t\t\t\tfb_texnum = R_LoadTexture(va(\"@fb_%s\", identifier), pheader->skinwidth, pheader->skinheight, (byte *)(pskintype), texmode | TEX_FULLBRIGHT, 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tpheader->gl_texturenum[i][j & 3] = gl_texnum;\n\t\t\t\tpheader->glc_fb_texturenum[i][j & 3] = fb_texnum;\n\n\t\t\t\tpskintype = (daliasskintype_t *)((byte *)(pskintype)+s);\n\t\t\t}\n\n\t\t\tfor (k = j; j < 4; j++) {\n\t\t\t\tpheader->gl_texturenum[i][j & 3] = pheader->gl_texturenum[i][j - k];\n\t\t\t\tpheader->glc_fb_texturenum[i][j & 3] = pheader->glc_fb_texturenum[i][j - k];\n\t\t\t}\n\t\t}\n\t}\n\treturn pskintype;\n}\n\ntypedef struct {\n\tshort x, y;\n} floodfill_t;\n\nextern unsigned d_8to24table[];\n\n// must be a power of 2\n#define FLOODFILL_FIFO_SIZE 0x1000\n#define FLOODFILL_FIFO_MASK (FLOODFILL_FIFO_SIZE - 1)\n\n#define FLOODFILL_STEP( off, dx, dy ) \\\n{ \\\n\tif (pos[off] == fillcolor) \\\n\t{ \\\n\t\tpos[off] = 255; \\\n\t\tfifo[inpt].x = x + (dx), fifo[inpt].y = y + (dy); \\\n\t\tinpt = (inpt + 1) & FLOODFILL_FIFO_MASK; \\\n\t} \\\n\telse if (pos[off] != 255) fdc = pos[off]; \\\n}\n\n//Fill background pixels so mipmapping doesn't have haloes - Ed\nstatic void Mod_FloodFillSkin(byte *skin, int skinwidth, int skinheight)\n{\n\tbyte fillcolor = *skin; // assume this is the pixel to fill\n\tfloodfill_t fifo[FLOODFILL_FIFO_SIZE];\n\tint inpt = 0, outpt = 0, filledcolor = -1, i;\n\n\tif (filledcolor == -1) {\n\t\tfilledcolor = 0;\n\t\t// attempt to find opaque black\n\t\tfor (i = 0; i < 256; ++i) {\n\t\t\tif (d_8to24table[i] == (255 << 0)) { // alpha 1.0\n\t\t\t\tfilledcolor = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// can't fill to filled color or to transparent color (used as visited marker)\n\tif ((fillcolor == filledcolor) || (fillcolor == 255)) {\n\t\t//printf( \"not filling skin from %d to %d\\n\", fillcolor, filledcolor );\n\t\treturn;\n\t}\n\n\tfifo[inpt].x = 0, fifo[inpt].y = 0;\n\tinpt = (inpt + 1) & FLOODFILL_FIFO_MASK;\n\n\twhile (outpt != inpt) {\n\t\tint x = fifo[outpt].x, y = fifo[outpt].y, fdc = filledcolor;\n\t\tbyte *pos = &skin[x + skinwidth * y];\n\n\t\toutpt = (outpt + 1) & FLOODFILL_FIFO_MASK;\n\n\t\tif (x > 0)\t\t\t\tFLOODFILL_STEP(-1, -1, 0);\n\t\tif (x < skinwidth - 1)\tFLOODFILL_STEP(1, 1, 0);\n\t\tif (y > 0)\t\t\t\tFLOODFILL_STEP(-skinwidth, 0, -1);\n\t\tif (y < skinheight - 1)\tFLOODFILL_STEP(skinwidth, 0, 1);\n\t\tskin[x + skinwidth * y] = fdc;\n\t}\n}\n"
  },
  {
    "path": "src/r_atlas.c",
    "content": "/*\nModule for creating 2d atlas for UI elements\n\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"gl_model.h\"\n#include \"tr_types.h\"\n#include \"r_texture.h\"\n#include \"r_local.h\"\n#include \"r_trace.h\"\n#include \"r_renderer.h\"\n\n#define MAXIMUM_ATLAS_TEXTURE_WIDTH  4096\n#define MAXIMUM_ATLAS_TEXTURE_HEIGHT 4096\n\ntypedef struct deleteable_texture_s {\n\ttexture_ref original;\n\tqbool moved_to_atlas;\n} deleteable_texture_t;\n\nstatic byte* atlas_texels;\nstatic byte* buffer;\nstatic byte* prev_atlas_texels;\nstatic deleteable_texture_t atlas_deletable_textures[WADPIC_PIC_COUNT + NUMCROSSHAIRS + 2 + MAX_CHARSETS];\nstatic int atlas_allocated[MAXIMUM_ATLAS_TEXTURE_WIDTH];\nstatic int atlas_delete_count = 0;\nstatic int atlas_texture_width = 0;\nstatic int atlas_texture_height = 0;\nstatic int atlas_block_width = 0;\nstatic int atlas_block_height = 0;\nstatic int atlas_chunk_size = 64;\nstatic qbool atlas_dirty;\n#define ATLAS_SIZE_IN_BYTES (atlas_texture_width * atlas_texture_height * 4)\n\nstatic cachepic_node_t wadpics[WADPIC_PIC_COUNT];\nstatic cachepic_node_t charsetpics[MAX_CHARSETS * 256];\nstatic cachepic_node_t crosshairpics[NUMCROSSHAIRS + 2];\n#ifdef EZ_FREETYPE_SUPPORT\nstatic cachepic_node_t fontpics[MAX_CHARSETS * 256];\n#endif\n\nstatic float solid_s;\nstatic float solid_t;\nstatic texture_ref atlas_texnum;\nstatic texture_ref solid_texnum;\nstatic qbool atlas_refresh = false;\n\nstatic void AddTextureToDeleteList(texture_ref tex)\n{\n\tif (R_TextureReferenceIsValid(tex) && atlas_delete_count < sizeof(atlas_deletable_textures) / sizeof(atlas_deletable_textures[0])) {\n\t\tatlas_deletable_textures[atlas_delete_count].original = tex;\n\t\tatlas_deletable_textures[atlas_delete_count].moved_to_atlas = false;\n\t\tR_TraceAPI(\"[atlas] adding texture to delete list: %d [%s], pos %d\", tex.index, R_TextureIdentifier(tex), atlas_delete_count);\n\t\tatlas_delete_count++;\n\t}\n\telse if (R_TextureReferenceIsValid(tex)) {\n\t\tR_TraceAPI(\"[atlas] !! attempted to add invalid texture reference to delete list\");\n\t}\n\telse {\n\t\tR_TraceAPI(\"[atlas] !! failed to add texture to delete list (overflow) %d [%s]\", tex.index, R_TextureIdentifier(tex));\n\t}\n}\n\nstatic void AddToDeleteList(mpic_t* src)\n{\n\tif (!R_TextureReferenceEqual(atlas_texnum, src->texnum) && src->sl == 0 && src->tl == 0 && src->th == 1 && src->sh == 1) {\n\t\tAddTextureToDeleteList(src->texnum);\n\t}\n\telse if (!R_TextureReferenceEqual(atlas_texnum, src->texnum)) {\n\t\tR_TraceAPI(\"[atlas] !! not adding %d [%s] to delete list (incomplete texture): %.2f %.2f %.2f %.2f\", src->texnum.index, R_TextureIdentifier(src->texnum), src->sl, src->sh, src->tl, src->th);\n\t}\n}\n\nstatic void ConfirmDeleteTexture(texture_ref tex)\n{\n\tint i;\n\tfor (i = 0; i < atlas_delete_count; ++i) {\n\t\tif (R_TextureReferenceEqual(atlas_deletable_textures[i].original, tex)) {\n\t\t\tatlas_deletable_textures[i].moved_to_atlas = true;\n\t\t\tR_TraceAPI(\"[atlas] marking deletable %d (%d/%s) as moved to atlas\", i, atlas_deletable_textures[i].original.index, R_TextureIdentifier(atlas_deletable_textures[i].original));\n\t\t}\n\t}\n}\n\nstatic void DeleteOldTextures(void)\n{\n\tint i;\n\n\tR_TraceEnterFunctionRegion;\n\tfor (i = 0; i < atlas_delete_count; ++i) {\n\t\tif (atlas_deletable_textures[i].moved_to_atlas) {\n\t\t\tR_TraceAPI(\"[atlas] deleting %d (%d/%s)\", i, atlas_deletable_textures[i].original.index, R_TextureIdentifier(atlas_deletable_textures[i].original));\n\t\t\tR_DeleteTexture(&atlas_deletable_textures[i].original);\n\t\t}\n\t}\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid Atlas_SolidTextureCoordinates(texture_ref* ref, float* s, float* t)\n{\n\tif (ref) {\n\t\t*ref = solid_texnum;\n\t}\n\t*s = solid_s;\n\t*t = solid_t;\n}\n\n// Returns false if allocation failed.\nstatic qbool Atlas_AllocBlock(int w, int h, int *x, int *y)\n{\n\tint i, j, best, best2;\n\n\tw = (w + (atlas_chunk_size - 1)) / atlas_chunk_size;\n\th = (h + (atlas_chunk_size - 1)) / atlas_chunk_size;\n\tbest = atlas_block_height;\n\n\tfor (i = 0; i < atlas_block_width - w; i++) {\n\t\tbest2 = 0;\n\n\t\tfor (j = 0; j < w; j++) {\n\t\t\tif (atlas_allocated[i + j] >= best) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (atlas_allocated[i + j] > best2) {\n\t\t\t\tbest2 = atlas_allocated[i + j];\n\t\t\t}\n\t\t}\n\n\t\tif (j == w) {\n\t\t\t// This is a valid spot.\n\t\t\t*x = i;\n\t\t\t*y = best = best2;\n\t\t}\n\t}\n\n\tif (best + h > atlas_block_height) {\n\t\t*x = *y = 0;\n\t\treturn false;\n\t}\n\n\tfor (i = 0; i < w; i++) {\n\t\tatlas_allocated[*x + i] = best + h;\n\t}\n\n\tatlas_dirty = true;\n\t*x *= atlas_chunk_size;\n\t*y *= atlas_chunk_size;\n\n\treturn true;\n}\n\nstatic void CachePics_AllocateSolidTexture(void)\n{\n\tint x_pos, y_pos, y;\n\n\tif (!Atlas_AllocBlock(atlas_chunk_size, atlas_chunk_size, &x_pos, &y_pos)) {\n\t\tsolid_texnum = solidwhite_texture;\n\t\tsolid_s = 0.5;\n\t\tsolid_t = 0.5;\n\t\treturn;\n\t}\n\n\tfor (y = 0; y < atlas_chunk_size; ++y) {\n\t\tmemset(atlas_texels + (x_pos + (y_pos + y) * atlas_texture_width) * 4, 0xFF, atlas_chunk_size * 4);\n\t}\n\n\tsolid_s = (x_pos + atlas_chunk_size / 2) * 1.0f / atlas_texture_width;\n\tsolid_t = (y_pos + atlas_chunk_size / 2) * 1.0f / atlas_texture_height;\n\tsolid_texnum = atlas_texnum;\n}\n\nstatic void CachePics_CopyToBuffer(mpic_t* pic, int x_pos, int y_pos, int new_texture_width, const byte* input, byte* output)\n{\n\tint texWidth = R_TextureWidth(pic->texnum);\n\tint texHeight = R_TextureHeight(pic->texnum);\n\tint height = (pic->th - pic->tl) * texHeight;\n\tint width = (pic->sh - pic->sl) * texWidth;\n\tint yOffset;\n\n\tfor (yOffset = 0; yOffset < height; ++yOffset) {\n\t\tint y = y_pos + yOffset;\n\t\tint base = (x_pos + y * new_texture_width) * 4;\n\t\tint srcBase = (pic->sl * texWidth + (yOffset + pic->tl * texHeight) * texWidth) * 4;\n\n\t\tmemcpy(output + base, input + srcBase, 4 * width);\n\t}\n}\n\nstatic int CachePics_AddToAtlas(mpic_t* pic)\n{\n\tint width = pic->width, height = pic->height;\n\tint texWidth = 0, texHeight = 0;\n\tint x_pos, y_pos;\n\tint padding = 1;\n\n\t// Find size of the source\n\ttexWidth = R_TextureWidth(pic->texnum);\n\ttexHeight = R_TextureHeight(pic->texnum);\n\n\twidth = (pic->sh - pic->sl) * texWidth;\n\theight = (pic->th - pic->tl) * texHeight;\n\n\tif (width > atlas_texture_width || height > atlas_texture_height) {\n\t\treturn -1;\n\t}\n\n\t// Allocate space in an atlas texture\n\tif (Atlas_AllocBlock(width + (width == atlas_texture_width ? 0 : padding), height + (height == atlas_texture_height ? 0 : padding), &x_pos, &y_pos)) {\n\t\tbyte* input_image = NULL;\n\n\t\t// Copy texture image\n\t\tif (R_TextureReferenceEqual(pic->texnum, atlas_texnum)) {\n\t\t\tinput_image = prev_atlas_texels;\n\t\t}\n\t\telse {\n\t\t\trenderer.TextureGet(pic->texnum, ATLAS_SIZE_IN_BYTES, buffer, 4);\n\n\t\t\tinput_image = buffer;\n\t\t}\n\n\t\tCachePics_CopyToBuffer(pic, x_pos, y_pos, atlas_texture_width, input_image, atlas_texels);\n\n\t\tR_TraceAPI(\"  moved to atlas: %d,%d => %d,%d\", x_pos, y_pos, x_pos + width, y_pos + height);\n\t\tpic->sl = (x_pos) / (float)atlas_texture_width;\n\t\tpic->sh = (x_pos + width) / (float)atlas_texture_width;\n\t\tpic->tl = (y_pos) / (float)atlas_texture_height;\n\t\tpic->th = (y_pos + height) / (float)atlas_texture_height;\n\t\tpic->texnum = atlas_texnum;\n\n\t\treturn 0;\n\t}\n\telse if (R_TextureReferenceEqual(atlas_texnum, pic->texnum)) {\n\t\t// Was on texture but no longer fits - need to create new texture\n\t\tCachePics_CopyToBuffer(pic, 0, 0, width, prev_atlas_texels, buffer);\n\t\tR_TraceAPI(\"  !moved from atlas: %d,%d => %d,%d\", x_pos, y_pos, x_pos + width, y_pos + height);\n\n\t\tpic->tl = pic->sl = 0;\n\t\tpic->th = pic->sh = 1;\n\t\tpic->texnum = R_LoadTexturePixels(buffer, \"\", width, height, TEX_ALPHA);\n\t}\n\telse {\n\t\tR_TraceAPI(\"  !unable to move to atlas\");\n\t}\n\n\treturn -1;\n}\n\nvoid CachePics_AtlasFrame(void)\n{\n\t// cls.state != active should be safe, vid_restart builds atlas directly via GFX_Init()\n\tif (atlas_refresh && cls.state != ca_active) {\n\t\tCachePics_CreateAtlas();\n\t}\n}\n\nvoid CachePics_AtlasUpload(void)\n{\n\tif (atlas_dirty) {\n\t\tR_TraceEnterFunctionRegion;\n\t\tatlas_texnum = R_LoadTexture(\"cachepics:atlas\", atlas_texture_width, atlas_texture_height, atlas_texels, TEX_ALPHA | TEX_NOSCALE, 4);\n\t\trenderer.TextureSetFiltering(atlas_texnum, texture_minification_linear, texture_magnification_linear);\n\t\trenderer.TextureWrapModeClamp(atlas_texnum);\n\t\tR_TraceLeaveFunctionRegion;\n\t}\n\tatlas_dirty = false;\n}\n\nvoid CachePics_Init(void)\n{\n\tatlas_dirty = true;\n\tatlas_texture_width = atlas_texture_height = min(glConfig.gl_max_size_default, MAXIMUM_ATLAS_TEXTURE_WIDTH);\n\n\tCachePics_AtlasUpload();\n}\n\nstatic void CachePics_InsertBySize(cachepic_node_t** sized_list, cachepic_node_t* node)\n{\n\tint size_node;\n\tcachepic_node_t* current = *sized_list;\n\tint size_this;\n\n\tnode->width = R_TextureWidth(node->data.pic->texnum);\n\tnode->height = R_TextureHeight(node->data.pic->texnum);\n\n\tnode->width *= (node->data.pic->sh - node->data.pic->sl);\n\tnode->height *= (node->data.pic->th - node->data.pic->tl);\n\n\tsize_node = node->width * node->height;\n\n\twhile (current) {\n\t\tsize_this = current->width * current->height;\n\t\tif (size_this > size_node) {\n\t\t\tsized_list = &current->size_order;\n\t\t\tcurrent = *sized_list;\n\t\t\tcontinue;\n\t\t}\n\n\t\tbreak;\n\t}\n\n\tnode->size_order = current;\n\t*sized_list = node;\n}\n\nvoid CachePics_LoadAmmoPics(mpic_t* ibar)\n{\n\textern mpic_t sb_ib_ammo[4];\n\tmpic_t* targPic;\n\tint i;\n\tint texWidth, texHeight;\n\tfloat sRatio = (ibar->sh - ibar->sl) / (float)ibar->width;\n\tfloat tRatio = (ibar->th - ibar->tl) / (float)ibar->height;\n\tbyte* source;\n\tbyte* target;\n\tint realwidth, realheight;\n\tfloat newsh, newsl;\n\tfloat newth, newtl;\n\n\t// Find size of the source\n\ttexWidth = R_TextureWidth(ibar->texnum);\n\ttexHeight = R_TextureHeight(ibar->texnum);\n\n\tsource = Q_malloc(texWidth * texHeight * 4);\n\ttarget = Q_malloc(texWidth * texHeight * 4);\n\n\trenderer.TextureGet(ibar->texnum, texWidth * texHeight * 4, source, 4);\n\tfor (i = WADPIC_SB_IBAR_AMMO1; i <= WADPIC_SB_IBAR_AMMO4; ++i) {\n\t\tint num = i - WADPIC_SB_IBAR_AMMO1;\n\t\tint y;\n\t\tint x_src, y_src;\n\t\tchar name[16];\n\t\tint xcoord = (3 + num * 48);\n\t\tint xwidth = 42;\n\t\tint ycoord = 0;\n\t\tint yheight = 11;\n\n\t\tnewsl = ibar->sl + xcoord * sRatio;\n\t\tnewsh = newsl + xwidth * sRatio;\n\t\tnewtl = ibar->tl + ycoord * tRatio;\n\t\tnewth = newtl + yheight * tRatio;\n\n\t\tx_src = texWidth * newsl;\n\t\ty_src = texHeight * newtl;\n\n\t\trealwidth = (newsh - newsl) * texWidth;\n\t\trealheight = (newth - newtl) * texHeight;\n\n\t\t// Cope with 1x1 transparent png files...(#571)\n\t\trealwidth = max(realwidth, 1);\n\t\trealheight = max(realheight, 1);\n\t\tx_src = min(x_src, texWidth - 1);\n\t\ty_src = min(y_src, texHeight - 1);\n\n\t\tsnprintf(name, sizeof(name), \"hud_ammo_%d\", i - WADPIC_SB_IBAR_AMMO1);\n\n\t\tfor (y = 0; y < realheight; ++y) {\n\t\t\tmemcpy(target + (y * realwidth) * 4, &source[(x_src + (y_src + y) * texWidth) * 4], realwidth * 4);\n\t\t}\n\n\t\ttargPic = wad_pictures[i].pic = &sb_ib_ammo[num];\n\t\ttargPic->texnum = R_LoadTexture(name, realwidth, realheight, target, TEX_NOCOMPRESS | TEX_ALPHA | TEX_NOSCALE | TEX_NO_TEXTUREMODE, 4);\n\t\ttargPic->sl = 0;\n\t\ttargPic->sh = 1;\n\t\ttargPic->tl = 0;\n\t\ttargPic->th = 1;\n\t\ttargPic->width = 42;\n\t\ttargPic->height = 11;\n\t}\n\n\tQ_free(target);\n\tQ_free(source);\n\n\tAddToDeleteList(ibar);\n}\n\nstatic void DeleteCharsetTextures(void)\n{\n\tint i;\n\n\tR_TraceEnterFunctionRegion;\n\tfor (i = 0; i < MAX_CHARSETS; ++i) {\n\t\tcharset_t* charset = &char_textures[i];\n\t\tint j;\n\t\tqbool all_on_atlas = true;\n\n\t\tif (!R_TextureReferenceIsValid(charset->master)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tR_TraceAPI(\"Checking normal charset %03d is on atlas...\", i);\n\t\tfor (j = 0; j < 256 && all_on_atlas; ++j) {\n\t\t\ttexture_ref glyph_tex = charset->glyphs[j].texnum;\n\n\t\t\tall_on_atlas &= (!R_TextureReferenceIsValid(glyph_tex) || R_TextureReferenceEqual(glyph_tex, atlas_texnum));\n\t\t}\n\t\tif (all_on_atlas && R_TextureReferenceIsValid(charset->master)) {\n\t\t\tR_TraceAPI(\"- all on atlas, deleting...\", i);\n\t\t\tR_DeleteTexture(&charset->master);\n\t\t\tR_TextureReferenceInvalidate(charset->master);\n\t\t}\n\t\telse {\n\t\t\tR_TraceAPI(\"- some glyphs not on atlas\", i);\n\t\t}\n\t}\n\n#ifdef EZ_FREETYPE_SUPPORT\n\tfor (i = 0; i < MAX_CHARSETS; ++i) {\n\t\textern charset_t proportional_fonts[MAX_CHARSETS];\n\t\tcharset_t* charset = &proportional_fonts[i];\n\t\tint j;\n\t\tqbool all_on_atlas = true;\n\n\t\tif (!R_TextureReferenceIsValid(charset->master)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tR_TraceAPI(\"Checking proportional charset %03d is on atlas...\", i);\n\t\tfor (j = 0; j < 256 && all_on_atlas; ++j) {\n\t\t\tall_on_atlas &= R_TextureReferenceEqual(charset->glyphs[j].texnum, atlas_texnum);\n\t\t}\n\t\tif (all_on_atlas && R_TextureReferenceIsValid(charset->master)) {\n\t\t\tR_TraceAPI(\"- all on atlas, deleting...\", i);\n\t\t\tR_DeleteTexture(&charset->master);\n\t\t}\n\t\telse {\n\t\t\tR_TraceAPI(\"- some glyphs not on atlas\", i);\n\t\t}\n\t}\n#endif\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid CachePics_CreateAtlas(void)\n{\n\tcachepic_node_t* sized_list = NULL;\n\tcachepic_node_t* cur;\n\tcachepic_node_t simple_items[MOD_NUMBER_HINTS * MAX_SIMPLE_TEXTURES];\n\tint i, j;\n\tdouble start_time = Sys_DoubleTime();\n\n\tif (COM_CheckParm(cmdline_param_client_noatlas)) {\n\t\tatlas_refresh = false;\n\t\treturn;\n\t}\n\n\tR_TraceEnterFunctionRegion;\n\n\t// Delete old atlas textures\n\tatlas_texels = Q_malloc(ATLAS_SIZE_IN_BYTES);\n\tprev_atlas_texels = Q_malloc(ATLAS_SIZE_IN_BYTES);\n\tif (R_TextureReferenceIsValid(atlas_texnum)) {\n\t\trenderer.TextureGet(atlas_texnum, ATLAS_SIZE_IN_BYTES, prev_atlas_texels, 4);\n\t}\n\tmemset(atlas_allocated, 0, sizeof(atlas_allocated));\n\tmemset(wadpics, 0, sizeof(wadpics));\n\tatlas_delete_count = 0;\n\n\tatlas_chunk_size = atlas_texture_width >= 4096 ? 64 : atlas_texture_width >= 2048 ? 32 : 16;\n\tatlas_block_width = atlas_texture_width / atlas_chunk_size;\n\tatlas_block_height = atlas_texture_height / atlas_chunk_size;\n\n\t// Copy wadpic data over\n\tfor (i = 0; i < WADPIC_PIC_COUNT; ++i) {\n\t\textern wadpic_t wad_pictures[WADPIC_PIC_COUNT];\n\t\tmpic_t* src = wad_pictures[i].pic;\n\n\t\t// This tiles, so don't include in atlas\n\t\tif (i == WADPIC_BACKTILE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (src) {\n\t\t\twadpics[i].data.pic = src;\n\n\t\t\tif (i != WADPIC_SB_IBAR) {\n\t\t\t\tCachePics_InsertBySize(&sized_list, &wadpics[i]);\n\n\t\t\t\tAddToDeleteList(src);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (i = 0; i < MAX_CHARSETS; ++i) {\n\t\tcharset_t* charset = &char_textures[i];\n\n\t\tfor (j = 0; j < 256; ++j) {\n\t\t\tif (R_TextureReferenceIsValid(charset->glyphs[j].texnum)) {\n\t\t\t\tcharsetpics[i * 256 + j].data.pic = &charset->glyphs[j];\n\n\t\t\t\tCachePics_InsertBySize(&sized_list, &charsetpics[i * 256 + j]);\n\t\t\t}\n\t\t}\n\t}\n\n#ifdef EZ_FREETYPE_SUPPORT\n\tfor (i = 0; i < MAX_CHARSETS; ++i) {\n\t\textern charset_t proportional_fonts[MAX_CHARSETS];\n\t\tcharset_t* charset = &proportional_fonts[i];\n\n\t\tfor (j = 0; j < 256; ++j) {\n\t\t\tif (R_TextureReferenceIsValid(charset->glyphs[j].texnum)) {\n\t\t\t\tfontpics[i * 256 + j].data.pic = &charset->glyphs[j];\n\n\t\t\t\tCachePics_InsertBySize(&sized_list, &fontpics[i * 256 + j]);\n\t\t\t}\n\t\t}\n\t}\n#endif\n\n\t// Copy crosshairs\n\t{\n\t\textern mpic_t crosshairtexture_txt;\n\t\textern mpic_t crosshairpic;\n\t\textern mpic_t crosshairs_builtin[NUMCROSSHAIRS];\n\n\t\tif (R_TextureReferenceIsValid(crosshairtexture_txt.texnum)) {\n\t\t\tcrosshairpics[0].data.pic = &crosshairtexture_txt;\n\t\t\tCachePics_InsertBySize(&sized_list, &crosshairpics[0]);\n\n\t\t\tAddToDeleteList(&crosshairtexture_txt);\n\t\t}\n\n\t\tif (R_TextureReferenceIsValid(crosshairpic.texnum)) {\n\t\t\tcrosshairpics[1].data.pic = &crosshairpic;\n\t\t\tCachePics_InsertBySize(&sized_list, &crosshairpics[1]);\n\n\t\t\tAddToDeleteList(&crosshairpic);\n\t\t}\n\n\t\tfor (i = 0; i < NUMCROSSHAIRS; ++i) {\n\t\t\tif (R_TextureReferenceIsValid(crosshairs_builtin[i].texnum)) {\n\t\t\t\tcrosshairpics[i + 2].data.pic = &crosshairs_builtin[i];\n\t\t\t\tCachePics_InsertBySize(&sized_list, &crosshairpics[i + 2]);\n\n\t\t\t\tAddToDeleteList(&crosshairs_builtin[i]);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add simple item textures (meag: these should be very low priority)\n\tfor (i = 0; i < MOD_NUMBER_HINTS; ++i) {\n\t\tfor (j = 0; j < MAX_SIMPLE_TEXTURES; ++j) {\n\t\t\tmpic_t* pic = Mod_SimpleTextureForHint(i, j);\n\t\t\tcachepic_node_t* node = &simple_items[i * MAX_SIMPLE_TEXTURES + j];\n\n\t\t\tnode->data.pic = pic;\n\n\t\t\tif (pic && R_TextureReferenceIsValid(pic->texnum)) {\n\t\t\t\tCachePics_InsertBySize(&sized_list, node);\n\n\t\t\t\t// Don't delete these, used for 3d sprites\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add cached picture images\n\tfor (i = 0; i < CACHED_PICS_HDSIZE; ++i) {\n\t\textern cachepic_node_t *cachepics[CACHED_PICS_HDSIZE];\n\t\tcachepic_node_t *cur;\n\n\t\tfor (cur = cachepics[i]; cur; cur = cur->next) {\n\t\t\tif (!Draw_IsConsoleBackground(cur->data.pic) && !Draw_KeepOffAtlas(cur->data.name)) {\n\t\t\t\tCachePics_InsertBySize(&sized_list, cur);\n\n\t\t\t\tAddToDeleteList(cur->data.pic);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Actually copy to the atlas texture\n\tbuffer = Q_malloc(ATLAS_SIZE_IN_BYTES);\n\tfor (cur = sized_list; cur; cur = cur->size_order) {\n\t\ttexture_ref original = cur->data.pic->texnum;\n\t\tR_TraceAPI(\"[atlas] attempting to add %d/%s to atlas\", original.index, R_TextureIdentifier(original));\n\t\tif (CachePics_AddToAtlas(cur->data.pic) >= 0) {\n\t\t\tConfirmDeleteTexture(original);\n\t\t}\n\t}\n\tCachePics_AllocateSolidTexture();\n\n\t// Upload atlas texture\n\tCachePics_AtlasUpload();\n\n\tDeleteOldTextures();\n\tDeleteCharsetTextures();\n\n\tQ_free(atlas_texels);\n\tQ_free(prev_atlas_texels);\n\tQ_free(buffer);\n\tCon_DPrintf(\"Atlas build time: %f\\n\", Sys_DoubleTime() - start_time);\n\n\t// Make sure we don't reference any old textures\n\tR_EmptyImageQueue();\n\n\tR_TraceLeaveFunctionRegion;\n\n\tatlas_refresh = false;\n}\n\nvoid CachePics_MarkAtlasDirty(void)\n{\n\tatlas_refresh = true;\n}\n"
  },
  {
    "path": "src/r_bloom.c",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#include \"quakedef.h\"\n#include \"r_local.h\"\n\nvoid GLC_BloomBlend(void);\nvoid GLC_InitBloomTextures(void);\n\nvoid R_BloomBlend(void)\n{\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_BloomBlend();\n\t}\n#endif\n}\n\nvoid R_InitBloomTextures(void)\n{\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_InitBloomTextures();\n\t}\n#endif\n}\n"
  },
  {
    "path": "src/r_brushmodel.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_vao.h\"\n#include \"r_renderer.h\"\n#include \"r_state.h\"\n#include \"tr_types.h\"\n#include \"rulesets.h\"\n#include \"r_trace.h\"\n#include \"r_matrix.h\"\n#include \"r_brushmodel.h\"\n#include \"r_lightmaps.h\"\n#include \"r_lighting.h\"\n#include \"glc_state.h\"\n\nunsigned int* modelIndexes;\nunsigned int modelIndexMaximum;\n\nstatic int R_BrushModelMeasureVBOSize(model_t* m);\nstatic int R_BrushModelPopulateVBO(model_t* m, void* vbo_buffer, int vbo_pos);\nvoid R_BrushModelClearTextureChains(model_t *clmodel);\n\nstatic int R_BrushModelMeasureIndexSize(model_t* m)\n{\n\tint j, total_surf_verts = 0, total_surfaces = 0;\n\n\tfor (j = 0; j < m->nummodelsurfaces; ++j) {\n\t\tmsurface_t* surf = m->surfaces + m->firstmodelsurface + j;\n\t\tglpoly_t* poly;\n\n\t\tfor (poly = surf->polys; poly; poly = poly->next) {\n\t\t\ttotal_surf_verts += poly->numverts;\n\t\t\t++total_surfaces;\n\t\t}\n\t}\n\n\tif (total_surf_verts <= 0 || total_surfaces < 1) {\n\t\treturn 0;\n\t}\n\n\t// Every vert in every surface, + surface terminator\n\treturn (total_surf_verts)+(total_surfaces - 1);\n}\n\nvoid R_BrushModelCreateVBO(void)\n{\n\tint i;\n\tint size = 0;\n\tint position = 0;\n\tint indexes = 0;\n\tint max_entity_indexes = 0;\n\tvoid* buffer = NULL;\n\tunsigned int buffer_size;\n\n\tfor (i = 1; i < MAX_MODELS; ++i) {\n\t\tmodel_t* mod = cl.model_precache[i];\n\t\tif (mod && mod->type == mod_brush) {\n\t\t\tint index_count = R_BrushModelMeasureIndexSize(mod);\n\n\t\t\tsize += R_BrushModelMeasureVBOSize(mod);\n\t\t\tif (mod->isworldmodel) {\n\t\t\t\tindexes += index_count;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tmax_entity_indexes = max(max_entity_indexes, index_count);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (i = 0; i < MAX_VWEP_MODELS; i++) {\n\t\tmodel_t* mod = cl.vw_model_precache[i];\n\t\tif (mod && mod->type == mod_brush) {\n\t\t\tint index_count = R_BrushModelMeasureIndexSize(mod);\n\n\t\t\tsize += R_BrushModelMeasureVBOSize(mod);\n\t\t\tif (mod->isworldmodel) {\n\t\t\t\tindexes += index_count;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tmax_entity_indexes = max(max_entity_indexes, index_count);\n\t\t\t}\n\t\t}\n\t}\n\n\tindexes += max_entity_indexes * MAX_STANDARD_ENTITIES;\n\tif (!R_BufferReferenceIsValid(r_buffer_brushmodel_index_data) || indexes > buffers.Size(r_buffer_brushmodel_index_data) / sizeof(modelIndexes[0])) {\n\t\tQ_free(modelIndexes);\n\t\tmodelIndexMaximum = indexes;\n\t\tmodelIndexes = Q_malloc(sizeof(*modelIndexes) * modelIndexMaximum);\n\n\t\tR_BindVertexArray(vao_none);\n\t\tif (R_BufferReferenceIsValid(r_buffer_brushmodel_index_data)) {\n\t\t\tbuffers.Resize(r_buffer_brushmodel_index_data, modelIndexMaximum * sizeof(modelIndexes[0]), NULL);\n\t\t}\n\t\telse {\n\t\t\tbuffers.Create(r_buffer_brushmodel_index_data, buffertype_index, \"brushmodel-elements\", modelIndexMaximum * sizeof(modelIndexes[0]), NULL, bufferusage_once_per_frame);\n\t\t}\n\t}\n\n\t// Create vbo buffer\n\tbuffer_size = size * (R_UseModernOpenGL() ? sizeof(vbo_world_vert_t) : sizeof(glc_vbo_world_vert_t));\n\tbuffer = Q_malloc(buffer_size);\n\n\t// Copy data into buffer\n\tfor (i = 1; i < MAX_MODELS; ++i) {\n\t\tmodel_t* mod = cl.model_precache[i];\n\t\tif (mod && mod->type == mod_brush) {\n\t\t\tposition = R_BrushModelPopulateVBO(mod, buffer, position);\n\t\t}\n\t}\n\n\tfor (i = 0; i < MAX_VWEP_MODELS; i++) {\n\t\tmodel_t* mod = cl.vw_model_precache[i];\n\t\tif (mod && mod->type == mod_brush) {\n\t\t\tposition = R_BrushModelPopulateVBO(mod, buffer, position);\n\t\t}\n\t}\n\n\t// Copy VBO buffer across\n\tbuffers.Create(r_buffer_brushmodel_vertex_data, buffertype_vertex, \"brushmodel-vbo\", buffer_size, buffer, bufferusage_reuse_many_frames);\n\n\tQ_free(buffer);\n}\n\nstatic int R_BrushModelMeasureVBOSize(model_t* m)\n{\n\tint j, total_surf_verts = 0, total_surfaces = 0;\n\n\tfor (j = 0; j < m->nummodelsurfaces; ++j) {\n\t\tmsurface_t* surf = m->surfaces + m->firstmodelsurface + j;\n\t\tglpoly_t* poly;\n\n\t\tif (!(surf->flags & (SURF_DRAWTURB | SURF_DRAWSKY))) {\n\t\t\tif (surf->texinfo->flags & TEX_SPECIAL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tif (!m->textures[surf->texinfo->miptex]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (poly = surf->polys; poly; poly = poly->next) {\n\t\t\ttotal_surf_verts += poly->numverts;\n\t\t\t++total_surfaces;\n\t\t}\n\t}\n\n\tif (total_surf_verts <= 0 || total_surfaces < 1) {\n\t\treturn 0;\n\t}\n\n\treturn (total_surf_verts);// +2 * (total_surfaces - 1));\n}\n\n// Create VBO, ordering by texture array\nstatic int R_BrushModelPopulateVBO(model_t* m, void* vbo_buffer, int vbo_pos)\n{\n\tint i, j;\n\n\t// Order vertices in the VBO by texture & lightmap\n\tfor (i = 0; i < m->numtextures; ++i) {\n\t\tqbool has_fb = false, has_luma = false;\n\n\t\tif (!m->textures[i]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\thas_fb = R_TextureReferenceIsValid(m->textures[i]->fb_texturenum);\n\t\thas_luma = has_fb & m->textures[i]->isLumaTexture;\n\t\tfor (j = 0; j < m->nummodelsurfaces; ++j) {\n\t\t\tmsurface_t* surf = m->surfaces + m->firstmodelsurface + j;\n\t\t\tqbool isTurbOrSky = surf->flags & (SURF_DRAWTURB | SURF_DRAWSKY);\n\t\t\tqbool isLitTurb = surf->texinfo->texture->isLitTurb;\n\t\t\tint lightmap = isTurbOrSky && !isLitTurb ? -1 : surf->lightmaptexturenum;\n\t\t\tglpoly_t* poly;\n\n\t\t\tif (surf->texinfo->miptex != i) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// copy verts into buffer (alternate to turn fan into triangle strip)\n\t\t\tfor (poly = surf->polys; poly; poly = poly->next) {\n\t\t\t\tint k = 0;\n\t\t\t\tint material = i;\n\t\t\t\tfloat scaleS = m->textures[i]->gl_texture_scaleS;\n\t\t\t\tfloat scaleT = m->textures[i]->gl_texture_scaleT;\n\n\t\t\t\tif (!poly->numverts) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Store position for drawing individual polys\n\t\t\t\tpoly->vbo_start = vbo_pos;\n\t\t\t\tfor (k = 0; k < poly->numverts; ++k) {\n\t\t\t\t\tvbo_pos = renderer.BrushModelCopyVertToBuffer(m, vbo_buffer, vbo_pos, poly->verts[k], lightmap, material, scaleS, scaleT, surf, has_fb, has_luma);\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\t}\n\n\treturn vbo_pos;\n}\n\nvoid R_BrushModelFreeMemory(void)\n{\n\tmodelIndexMaximum = 0;\n\tQ_free(modelIndexes);\n}\n\nvoid R_BrushModelDrawEntity(entity_t *e)\n{\n\tint k;\n\tunsigned int li;\n\tunsigned int lj;\n\tvec3_t mins, maxs;\n\tmodel_t *clmodel;\n\tqbool rotated;\n\tfloat oldMatrix[16];\n\textern cvar_t gl_brush_polygonoffset;\n\tqbool caustics = false;\n\textern cvar_t gl_flashblend;\n\textern msurface_t* skychain;\n\n\t// Get rid of Z-fighting for textures by offsetting the\n\t// drawing of entity models compared to normal polygons.\n\t// dimman: disabled for qcon\n\tqbool polygonOffset = gl_brush_polygonoffset.value > 0 && Ruleset_AllowPolygonOffset(e);\n\n\tclmodel = e->model;\n\tif (!clmodel->nummodelsurfaces) {\n\t\treturn;\n\t}\n\n\tif (e->angles[0] || e->angles[1] || e->angles[2]) {\n\t\trotated = true;\n\t\tif (R_CullSphere(e->origin, clmodel->radius)) {\n\t\t\treturn;\n\t\t}\n\t}\n\telse {\n\t\trotated = false;\n\t\tVectorAdd(e->origin, clmodel->mins, mins);\n\t\tVectorAdd(e->origin, clmodel->maxs, maxs);\n\n\t\tif (R_CullBox(mins, maxs)) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\tR_TraceEnterRegion(va(\"%s(%s)\", __func__, e->model->name), true);\n\n\tVectorSubtract(r_refdef.vieworg, e->origin, modelorg);\n\tif (rotated) {\n\t\tvec3_t\ttemp;\n\t\tvec3_t\tforward, right, up;\n\n\t\tVectorCopy(modelorg, temp);\n\t\tAngleVectors(e->angles, forward, right, up);\n\t\tmodelorg[0] = DotProduct(temp, forward);\n\t\tmodelorg[1] = -DotProduct(temp, right);\n\t\tmodelorg[2] = DotProduct(temp, up);\n\t}\n\n\t// calculate dynamic lighting for bmodel if it's not an instanced model\n\tif (clmodel->firstmodelsurface) {\n\t\tfor (li = 0; li < MAX_DLIGHTS / 32; li++) {\n\t\t\tif (cl_dlight_active[li]) {\n\t\t\t\tfor (lj = 0; lj < 32; lj++) {\n\t\t\t\t\tif ((cl_dlight_active[li] & (1 << lj)) && li * 32 + lj < MAX_DLIGHTS) {\n\t\t\t\t\t\tk = li * 32 + lj;\n\n\t\t\t\t\t\tif (!gl_flashblend.integer || (cl_dlights[k].bubble && gl_flashblend.integer != 2)) {\n\t\t\t\t\t\t\tR_MarkLights(&cl_dlights[k], 1 << k, clmodel->nodes + clmodel->firstnode);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tR_BrushModelClearTextureChains(clmodel);\n\trenderer.ChainBrushModelSurfaces(clmodel, e);\n\n\tif (clmodel->last_texture_chained >= 0 || clmodel->drawflat_todo || skychain) {\n\t\tR_PushModelviewMatrix(oldMatrix);\n\t\tR_RotateForEntity(e);\n\n\t\t// START shaman FIX for no simple textures on world brush models {\n\t\t//draw the textures chains for the model\n\t\tif (clmodel->last_texture_chained >= 0 || clmodel->drawflat_todo) {\n\t\t\tif (clmodel->firstmodelsurface) {\n\t\t\t\tR_RenderAllDynamicLightmaps(clmodel);\n\t\t\t}\n\n\t\t\t//R00k added contents point for underwater bmodels\n\t\t\tif (r_refdef2.drawCaustics) {\n\t\t\t\tif (clmodel->isworldmodel) {\n\t\t\t\t\tvec3_t midpoint;\n\n\t\t\t\t\tVectorAdd(clmodel->mins, clmodel->maxs, midpoint);\n\t\t\t\t\tVectorScale(midpoint, 0.5f, midpoint);\n\t\t\t\t\tVectorAdd(midpoint, e->origin, midpoint);\n\n\t\t\t\t\tcaustics = R_PointIsUnderwater(midpoint);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcaustics = R_PointIsUnderwater(e->origin);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\trenderer.DrawBrushModel(e, polygonOffset, caustics);\n\t\t// } END shaman FIX for no simple textures on world brush models\n\n\t\tR_PopModelviewMatrix(oldMatrix);\n\t}\n\n\tR_TraceLeaveRegion(true);\n}\n\n// Convert ordering of verts for convex polygons to triangle strip\nvoid R_BrushModelPolygonToTriangleStrip(glpoly_t* poly)\n{\n\tfloat tempVert[VERTEXSIZE];\n\tint i;\n\n\t// Apart from first vertex, replace odd verts with vert at end of list\n\tfor (i = 2; i < poly->numverts - 1; i += 2) {\n\t\tmemcpy(tempVert, poly->verts[poly->numverts - 1], sizeof(tempVert));\n\t\tmemmove(poly->verts[i + 1], poly->verts[i], sizeof(tempVert) * (poly->numverts - i - 1));\n\t\tmemcpy(poly->verts[i], tempVert, sizeof(tempVert));\n\t}\n}\n"
  },
  {
    "path": "src/r_brushmodel.h",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef R_BRUSHMODEL_HEADER\n#define R_BRUSHMODEL_HEADER\n\n#include \"r_buffers.h\"\n#include \"r_framestats.h\"\n\nqbool R_PointIsUnderwater(vec3_t point);\n\nvoid GLC_StateBeginWaterSurfaces(void);\nvoid GLC_StateBeginAlphaChain(void);\nvoid GLC_StateBeginAlphaChainSurface(msurface_t* s);\n\n// gl_rsurf.c\nvoid GLC_EmitDetailPolys_GLSL(void);\nvoid GLC_EmitDetailPolys(qbool use_vbo);\nvoid R_BrushModelDrawEntity(entity_t *e);\nvoid R_DrawWorld(void);\nvoid GLC_DrawAlphaChain(msurface_t* alphachain, frameStatsPolyType polyType);\n\n// gl_warp.c\nvoid R_TurbSurfacesSubdivide(msurface_t *fa);\nvoid R_SkySurfacesBuildPolys(msurface_t *fa);\nvoid R_InitSky(texture_t *mt);\t// called at level load\nvoid R_DrawSky(void);\nqbool R_DrawWorldOutlines(void);\n\n// internal\n#define CHAIN_SURF_B2F(surf, chain) \t\t\t\\\n\t{\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t(surf)->texturechain = (chain);\t\t\t\\\n\t\t(chain) = (surf);\t\t\t\t\t\t\\\n\t}\nvoid chain_surfaces_simple(msurface_t** chain_head, msurface_t* surf);\nvoid chain_surfaces_simple_drawflat(msurface_t** chain_head, msurface_t* surf);\nvoid chain_surfaces_by_lightmap(msurface_t** chain_head, msurface_t* surf);\nextern unsigned int* modelIndexes;\nextern unsigned int modelIndexMaximum;\nvoid R_BrushModelCreateVBO(void);\n\n#define BRUSHMODEL_MAX_SURFACE_EXTENTS +999999999\n#define BRUSHMODEL_MIN_SURFACE_EXTENTS -999999999\n\n#endif // R_BRUSHMODEL_HEADER\n"
  },
  {
    "path": "src/r_brushmodel_bspx.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n//============================================================\n// BSPX loading\n\n// This code has been moved to cmodel.c, as there are physics lumps (rampjump)\n//   as well as rendering-only (colored lighting)\n// Move code to do with rendering lumps here?\n"
  },
  {
    "path": "src/r_brushmodel_load.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: gl_model.c,v 1.41 2007-10-07 08:06:33 tonik Exp $\n*/\n// gl_brushmodel.c  -- model loading and caching of brush models\n\n#include \"quakedef.h\"\n#include <limits.h>\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"rulesets.h\"\n#include \"wad.h\"\n#include \"crc.h\"\n#include \"fmod.h\"\n#include \"utils.h\"\n#include \"glsl/constants.glsl\"\n#include \"r_brushmodel_sky.h\"\n#include \"r_local.h\"\n#include \"r_texture.h\"\n#include \"r_matrix.h\"\n#include \"r_lighting.h\"\n#include \"r_lightmaps.h\"\n#include \"r_framestats.h\"\n#include \"r_brushmodel.h\"\n#include \"r_trace.h\"\n#include \"r_renderer.h\"\n#include \"r_state.h\"\n#include \"tr_types.h\"\n\nvec3_t modelorg;\n\nextern msurface_t* skychain;\nextern msurface_t* alphachain;\nchar* TranslateTextureName(texture_t *tx);\nqbool Mod_LoadExternalTexture(model_t* loadmodel, texture_t *tx, int mode, int brighten_flag);\n\nmodel_t* Mod_FindName(const char *name);\n\nstatic void SetTextureFlags(model_t* mod, msurface_t* out, int surfnum)\n{\n\tout->texinfo->surfaces++;\n\n\t// set the drawing flags flag\n\t// sky, turb and alpha should be mutually exclusive\n\tif (Mod_IsSkyTextureName(mod, out->texinfo->texture->name)) {\t// sky\n\t\tout->flags |= (SURF_DRAWSKY | SURF_DRAWTILED);\n\t\tR_SkySurfacesBuildPolys(out);\t// build gl polys\n\t\tout->texinfo->skippable = false;\n\t\treturn;\n\t}\n\n\tif (Mod_IsTurbTextureName(mod, out->texinfo->texture->name)) {\t// turbulent\n\t\tout->flags |= SURF_DRAWTURB;\n\t\tout->texinfo->skippable = false;\n\n\t\t// check if texture should receive no lighting.\n\t\tif (out->texinfo->flags & TEX_SPECIAL) {\n\t\t\tout->flags |= SURF_DRAWTILED;\n\t\t} else {\n\t\t\tout->texinfo->texture->isLitTurb = true;\n\t\t}\n\t\tR_TurbSurfacesSubdivide(out);\t// cut up polygon for warps\n\t\treturn;\n\t}\n\n\tif (Mod_IsAlphaTextureName(mod, out->texinfo->texture->name)) {\n\t\tout->flags |= SURF_DRAWALPHA;\n\t\tout->texinfo->skippable = false;\n\t\tout->texinfo->texture->isAlphaTested = true;\n\t}\n}\n\nstatic byte* LoadColoredLighting(char *name, char **litfilename, int *filesize)\n{\n\tqbool system;\n\tbyte *data;\n\tchar *groupname, *mapname;\n\textern cvar_t gl_loadlitfiles;\n\n\tif (!(gl_loadlitfiles.integer == 1 || gl_loadlitfiles.integer == 2)) {\n\t\treturn NULL;\n\t}\n\n\tmapname = TP_MapName();\n\tgroupname = TP_GetMapGroupName(mapname, &system);\n\n\tif (strcmp(name, va(\"maps/%s.bsp\", mapname))) {\n\t\treturn NULL;\n\t}\n\n\t*litfilename = va(\"maps/lits/%s.lit\", mapname);\n\tdata = FS_LoadHunkFile (*litfilename, filesize);\n\n\tif (!data) {\n\t\t*litfilename = va(\"maps/%s.lit\", mapname);\n\t\tdata = FS_LoadHunkFile (*litfilename, filesize);\n\t}\n\n\tif (!data) {\n\t\t*litfilename = va(\"lits/%s.lit\", mapname);\n\t\tdata = FS_LoadHunkFile (*litfilename, filesize);\n\t}\n\n\tif (!data && groupname && !system) {\n\t\t*litfilename = va(\"maps/%s.lit\", groupname);\n\t\tdata = FS_LoadHunkFile (*litfilename, filesize);\n\t}\n\n\tif (!data && groupname && !system) {\n\t\t*litfilename = va(\"lits/%s.lit\", groupname);\n\t\tdata = FS_LoadHunkFile (*litfilename, filesize);\n\t}\n\n\treturn data;\n}\n\nstatic void SetSurfaceLighting(model_t* loadmodel, msurface_t* out, byte* styles, int lightofs)\n{\n\tint i;\n\tfor (i = 0; i < MAXLIGHTMAPS; i++) {\n\t\tout->styles[i] = styles[i];\n\t}\n\ti = LittleLong(lightofs);\n\tif (i == -1 || loadmodel->lightdata == NULL) {\n\t\tout->samples = NULL;\n\t}\n\telse {\n\t\tout->samples = loadmodel->lightdata + (loadmodel->bspversion == HL_BSPVERSION ? i : i * loadmodel->lightdatasamplesize);\n\t}\n}\n\n//Fills in s->texturemins[] and s->extents[]\nstatic void CalcSurfaceExtents(model_t* loadmodel, msurface_t *s) {\n\tfloat mins[2], maxs[2], val;\n\tint i,j, e, bmins[2], bmaxs[2];\n\tmvertex_t *v;\n\tmtexinfo_t *tex;\n\n\tmins[0] = mins[1] = BRUSHMODEL_MAX_SURFACE_EXTENTS;\n\tmaxs[0] = maxs[1] = BRUSHMODEL_MIN_SURFACE_EXTENTS;\n\n\ttex = s->texinfo;\n\n\tfor (i = 0; i < s->numedges; i++) {\n\t\te = loadmodel->surfedges[s->firstedge+i];\n\t\tif (e >= 0) {\n\t\t\tv = &loadmodel->vertexes[loadmodel->edges[e].v[0]];\n\t\t}\n\t\telse {\n\t\t\tv = &loadmodel->vertexes[loadmodel->edges[-e].v[1]];\n\t\t}\n\n\t\tfor (j = 0; j < 2; j++) {\n\t\t\t// The following calculation is sensitive to floating-point\n\t\t\t// precision. It needs to produce the same result that the\n\t\t\t// light compiler does, because R_BuildLightMap uses surf->\n\t\t\t// extents to know the width/height of a surface's lightmap,\n\t\t\t// and incorrect rounding here manifests itself as patches\n\t\t\t// of \"corrupted\" looking lightmaps.\n\t\t\t// Most light compilers are win32 executables, so they use\n\t\t\t// x87 floating point. This means the multiplies and adds\n\t\t\t// are done at 80-bit precision, and the result is rounded\n\t\t\t// down to 32-bits and stored in val.\n\t\t\t// Adding the casts to double seems to be good enough to fix\n\t\t\t// lighting glitches when Quakespasm is compiled as x86_64\n\t\t\t// and using SSE2 floating-point. A potential trouble spot\n\t\t\t// is the hallway at the beginning of mfxsp17. -- ericw\n\t\t\tval = (double) v->position[0] * (double) tex->vecs[j][0] +\n\t\t\t\t  (double) v->position[1] * (double) tex->vecs[j][1] +\n\t\t\t\t  (double) v->position[2] * (double) tex->vecs[j][2] +\n\t\t\t\t  (double) tex->vecs[j][3];\n\t\t\tif (i == 0 || val < mins[j]) {\n\t\t\t\tmins[j] = val;\n\t\t\t}\n\t\t\tif (i == 0 || val > maxs[j]) {\n\t\t\t\tmaxs[j] = val;\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (i = 0; i < 2; i++) {\n\t\tbmins[i] = floor(mins[i]/16);\n\t\tbmaxs[i] = ceil(maxs[i]/16);\n\n\t\ts->texturemins[i] = bmins[i] * 16;\n\t\ts->extents[i] = (bmaxs[i] - bmins[i]) * 16;\n\t\tif (!(tex->flags & TEX_SPECIAL) && s->extents[i] > 512 /* 256 */) {\n\t\t\tHost_Error(\"CalcSurfaceExtents: Bad surface extents\");\n\t\t}\n\t}\n}\n\nstatic void Mod_SetParent(mnode_t *node, mnode_t *parent)\n{\n\tnode->parent = parent;\n\tif (node->contents < 0) {\n\t\treturn;\n\t}\n\tMod_SetParent(node->children[0], node);\n\tMod_SetParent(node->children[1], node);\n}\n\nstatic void Mod_LoadLighting(model_t* loadmodel, lump_t* l, byte* mod_base, bspx_header_t* bspx_header)\n{\n\tint i, lit_ver, mark;\n\tbyte *in, *out, *data;\n\tchar *litfilename;\n\tint filesize;\n\textern cvar_t gl_loadlitfiles;\n\tqbool load_inline;\n\n\n\tloadmodel->lightdata = NULL;\n\n\tif (loadmodel->bspversion == HL_BSPVERSION && l->filelen > 0) {\n\t\tloadmodel->lightdata = (byte *) Hunk_AllocName(l->filelen, loadmodel->name);\n\t\tmemcpy (loadmodel->lightdata, mod_base + l->fileofs, l->filelen);\n\t\treturn;\n\t}\n\n\tload_inline = gl_loadlitfiles.integer == 1 || gl_loadlitfiles.integer == 3;\n\tif (!load_inline && (l->filelen <= 0 && !R_FullBrightAllowed())) {\n\t\tCon_Printf(\"No vanilla lighting, using inline to satisfy ruleset.\\n\");\n\t\tload_inline = true;\n\t}\n\n\tif (load_inline) {\n\t\tint threshold = (lightmode == 1 ? 255 : lightmode == 2 ? 170 : 128);\n\t\tint lumpsize;\n\t\tbyte *rgb;\n\n\t\trgb = Mod_BSPX_FindLump(bspx_header, \"LIGHTING_E5BGR9\", &lumpsize, mod_base);\n\t\tif (rgb && lumpsize % 4 == 0 && (lumpsize == l->filelen * 4 || l->filelen <= 0)) {\n\t\t\tloadmodel->lightdata = (byte *) Hunk_AllocName (lumpsize, loadmodel->name);\n\t\t\tloadmodel->lightdatasamplesize = 4;\n\t\t\tmemcpy(loadmodel->lightdata, rgb, lumpsize);\n\t\t\tloadmodel->flags |= MOD_HDRLIGHTING;\n\t\t\tfor (i = 0; i < lumpsize / 4; i++) { //native endian...\n\t\t\t\t((int*)loadmodel->lightdata)[i] = LittleLong(((int*)loadmodel->lightdata)[i]);\n\t\t\t}\n\t\t} else {\n\t\t\trgb = Mod_BSPX_FindLump(bspx_header, \"RGBLIGHTING\", &lumpsize, mod_base);\n\t\t\t// Sanity-check size if vanilla lit exists\n\t\t\tif (rgb && lumpsize % 3 == 0 && (lumpsize == l->filelen * 3 || l->filelen <= 0)) {\n\t\t\t\tloadmodel->lightdata = (byte *) Hunk_AllocName(lumpsize, loadmodel->name);\n\t\t\t\tloadmodel->lightdatasamplesize = 3;\n\t\t\t\tmemcpy(loadmodel->lightdata, rgb, lumpsize);\n\t\t\t\t// we trust the inline RGB data to be bug free so we don't check it against the mono lightmap\n\t\t\t\t// what we do though is prevent color wash-out in brightly lit areas\n\t\t\t\t// (one day we may do it in R_BuildLightMap instead)\n\t\t\t\tout = loadmodel->lightdata;\n\t\t\t\tfor (i = 0; i < lumpsize / 3; i++, out += 3) {\n\t\t\t\t\tint m = max(out[0], max(out[1], out[2]));\n\t\t\t\t\tif (m > threshold) {\n\t\t\t\t\t\tout[0] = out[0] * threshold / m;\n\t\t\t\t\t\tout[1] = out[1] * threshold / m;\n\t\t\t\t\t\tout[2] = out[2] * threshold / m;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// all done, but we let them override it with a .lit\n\t\t\t}\n\t\t}\n\t}\n\n\t// Missing or corrupt vanilla lighting needed for .lit files, bail.\n\tif (l->filelen <= 0 || l->filelen >= INT_MAX / 3) {\n\t\treturn;\n\t}\n\n\t//check for a .lit file\n\tmark = Hunk_LowMark();\n\tdata = LoadColoredLighting(loadmodel->name, &litfilename, &filesize);\n\tif (data) {\n\t\tif (filesize < 8 || strncmp((char *)data, \"QLIT\", 4)) {\n\t\t\tCom_Printf(\"Corrupt .lit file (%s)...ignoring\\n\", COM_SkipPath(litfilename));\n\t\t} else if (l->filelen * 3 + 8 != filesize) {\n\t\t\tCom_Printf(\"Warning: .lit file (%s) has incorrect size\\n\", COM_SkipPath(litfilename));\n\t\t} else if ((lit_ver = LittleLong(((int *)data)[1])) != 1) {\n\t\t\tCom_Printf(\"Unknown .lit file version (v%d)\\n\", lit_ver);\n\t\t} else {\n\t\t\textern cvar_t gl_oldlitscaling;\n\t\t\tif (developer.integer || cl_warncmd.integer) {\n\t\t\t\tCom_Printf(\"Static coloured lighting loaded\\n\");\n\t\t\t}\n\t\t\tloadmodel->lightdata = data + 8;\n\t\t\tloadmodel->lightdatasamplesize = 3;\n\n\t\t\tin = mod_base + l->fileofs;\n\t\t\tout = loadmodel->lightdata;\n\t\t\tif (gl_oldlitscaling.integer) {\n\t\t\t\t// old way (makes colored areas too dark)\n\t\t\t\tfor (i = 0; i < l->filelen; i++, in++, out+=3) {\n\t\t\t\t\tfloat m, s;\n\n\t\t\t\t\tm = max(out[0], max(out[1], out[2]));\n\t\t\t\t\tif (!m) {\n\t\t\t\t\t\tout[0] = out[1] = out[2] = *in;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts = *in / m;\n\t\t\t\t\t\tout[0] = (int) (s * out[0]);\n\t\t\t\t\t\tout[1] = (int) (s * out[1]);\n\t\t\t\t\t\tout[2] = (int) (s * out[2]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// new way\n\t\t\t\tfloat threshold = (lightmode == 1 ? 255 : lightmode == 2 ? 170 : 128);\n\t\t\t\tfor (i = 0; i < l->filelen; i++, in++, out+=3) {\n\t\t\t\t\tfloat r, g, b, m, p, s;\n\t\t\t\t\tif (!out[0] && !out[1] && !out[2]) {\n\t\t\t\t\t\tout[0] = out[1] = out[2] = *in;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// calculate perceived brightness of the color sample\n\t\t\t\t\t// kudos to Darel Rex Finley for his HSP color model\n\t\t\t\t\tp = sqrt(out[0]*out[0]*0.241 + out[1]*out[1]*0.691 + out[2]*out[2]*0.068);\n\t\t\t\t\t// scale to match perceived brightness of monochrome sample\n\t\t\t\t\ts = *in / p;\n\t\t\t\t\tr = s * out[0];\n\t\t\t\t\tg = s * out[1];\n\t\t\t\t\tb = s * out[2];\n\t\t\t\t\tm = max(r, max(g, b));\n\t\t\t\t\tif (m > threshold) {\n\t\t\t\t\t\t// scale down to avoid color washout\n\t\t\t\t\t\tr *= threshold/m;\n\t\t\t\t\t\tg *= threshold/m;\n\t\t\t\t\t\tb *= threshold/m;\n\t\t\t\t\t}\n\t\t\t\t\tout[0] = (int) (r + 0.5);\n\t\t\t\t\tout[1] = (int) (g + 0.5);\n\t\t\t\t\tout[2] = (int) (b + 0.5);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tHunk_FreeToLowMark (mark);\n\t}\n\n\tif (loadmodel->lightdata) {\n\t\treturn;\t\t// we have loaded inline RGB data\n\t}\n\n\t//no .lit found, expand the white lighting data to color\n\tloadmodel->lightdata = (byte *) Hunk_AllocName (l->filelen * 3, va(\"%s_@lightdata\", loadmodel->name));\n\tloadmodel->lightdatasamplesize = 3;\n\tin = mod_base + l->fileofs;\n\tout = loadmodel->lightdata;\n\tfor (i = 0; i < l->filelen; i++, out += 3) {\n\t\tout[0] = out[1] = out[2] = *in++;\n\t}\n}\n\nstatic qbool Mod_LoadExternalSkyTexture(texture_t *tx)\n{\n\tchar *altname, *mapname;\n\tchar solidname[MAX_QPATH], alphaname[MAX_QPATH];\n\tchar altsolidname[MAX_QPATH], altalphaname[MAX_QPATH];\n\tbyte alphapixel = 255;\n\tint flags = TEX_MIPMAP | (!gl_scaleskytextures.integer ? TEX_NOSCALE : 0);\n\n\tif (!R_ExternalTexturesEnabled(true)) {\n\t\treturn false;\n\t}\n\n\taltname = TranslateTextureName (tx);\n\tmapname = Cvar_String(\"mapname\");\n\tsnprintf (solidname, sizeof(solidname), \"%s_solid\", tx->name);\n\tsnprintf (alphaname, sizeof(alphaname), \"%s_alpha\", tx->name);\n\n\tsolidskytexture = R_LoadTextureImage (va(\"textures/%s/%s\", mapname, solidname), solidname, 0, 0, flags);\n\tif (!R_TextureReferenceIsValid(solidskytexture) && altname) {\n\t\tsnprintf(altsolidname, sizeof(altsolidname), \"%s_solid\", altname);\n\t\tsolidskytexture = R_LoadTextureImage (va(\"textures/%s\", altsolidname), altsolidname, 0, 0, flags);\n\t}\n\tif (!R_TextureReferenceIsValid(solidskytexture)) {\n\t\tsolidskytexture = R_LoadTextureImage(va(\"textures/%s\", solidname), solidname, 0, 0, flags);\n\t}\n\tif (!R_TextureReferenceIsValid(solidskytexture)) {\n\t\treturn false;\n\t}\n\n\talphaskytexture = R_LoadTextureImage (va(\"textures/%s/%s\", mapname, alphaname), alphaname, 0, 0, TEX_ALPHA | TEX_PREMUL_ALPHA | flags);\n\tif (!R_TextureReferenceIsValid(alphaskytexture) && altname) {\n\t\tsnprintf (altalphaname, sizeof(altalphaname), \"%s_alpha\", altname);\n\t\talphaskytexture = R_LoadTextureImage (va(\"textures/%s\", altalphaname), altalphaname, 0, 0, TEX_ALPHA | TEX_PREMUL_ALPHA | flags);\n\t}\n\tif (!R_TextureReferenceIsValid(alphaskytexture)) {\n\t\talphaskytexture = R_LoadTextureImage(va(\"textures/%s\", alphaname), alphaname, 0, 0, TEX_ALPHA | TEX_PREMUL_ALPHA | flags);\n\t}\n\tif (!R_TextureReferenceIsValid(alphaskytexture)) {\n\t\t// Load a texture consisting of a single transparent pixel\n\t\talphaskytexture = R_LoadTexture(alphaname, 1, 1, &alphapixel, TEX_ALPHA | TEX_PREMUL_ALPHA | flags, 1);\n\t}\n\treturn true;\n}\n\nstatic void Mod_LoadVisibility(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tif (l->filelen <= 0) {\n\t\tloadmodel->visdata = NULL;\n\t\treturn;\n\t}\n\tloadmodel->visdata = (byte*)Hunk_AllocName(l->filelen, loadmodel->name);\n\tloadmodel->visdata_length = l->filelen;\n\tmemcpy(loadmodel->visdata, mod_base + l->fileofs, l->filelen);\n}\n\nstatic void Mod_LoadSubmodels(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tdmodel_t *in, *out;\n\tint i, j, count;\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadSubmodels: funny lump size in %s\", loadmodel->name);\n\t\treturn;\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count > MAX_MODELS) {\n\t\tHost_Error(\"Mod_LoadSubmodels : count > MAX_MODELS\");\n\t\treturn;\n\t}\n\tif (count > INT_MAX / sizeof(*out)) {\n\t\tHost_Error(\"Mod_LoadSubmodels : count > %d\", INT_MAX);\n\t\treturn;\n\t}\n\tout = (dmodel_t *) Hunk_AllocName ( count*sizeof(*out), loadmodel->name);\n\n\tloadmodel->submodels = out;\n\tloadmodel->numsubmodels = count;\n\n\tfor (i = 0; i < count; i++, in++, out++)\t{\n\t\tfor (j = 0; j < 3; j++) {\t// spread the mins / maxs by a pixel\n\t\t\tout->mins[j] = LittleFloat (in->mins[j]) - 1;\n\t\t\tout->maxs[j] = LittleFloat (in->maxs[j]) + 1;\n\t\t\tout->origin[j] = LittleFloat (in->origin[j]);\n\t\t}\n\t\tfor (j = 0; j < MAX_MAP_HULLS; j++) {\n\t\t\tout->headnode[j] = LittleLong(in->headnode[j]);\n\t\t}\n\t\tout->visleafs = LittleLong (in->visleafs);\n\t\tout->firstface = LittleLong (in->firstface);\n\t\tout->numfaces = LittleLong (in->numfaces);\n\t}\n}\n\nstatic void Mod_LoadTextures(model_t* mod, lump_t *l, byte* mod_base)\n{\n\tint i, j, num, max, altmax, pixels;\n\tmiptex_t *mt;\n\ttexture_t *tx, *tx2, *anims[10], *altanims[10];\n\tdmiptexlump_t *m;\n\n\tif (l->filelen <= 0) {\n\t\tmod->textures = NULL;\n\t\treturn;\n\t}\n\n\tm = (dmiptexlump_t *) (mod_base + l->fileofs);\n\tm->nummiptex = LittleLong (m->nummiptex);\n\tif (m->nummiptex < 0 || m->nummiptex > INT_MAX / sizeof(*mod->textures)) {\n\t\tHost_Error(\"Mod_LoadTextures: nummiptex %d (0-%d)\", m->nummiptex, INT_MAX / sizeof(*mod->textures));\n\t\treturn;\n\t}\n\tmod->numtextures = m->nummiptex;\n\tmod->textures = (texture_t **) Hunk_AllocName(m->nummiptex * sizeof(*mod->textures), mod->name);\n\n\tfor (i = 0; i < m->nummiptex; i++) {\n\t\tm->dataofs[i] = LittleLong(m->dataofs[i]);\n\t\tif (m->dataofs[i] == -1) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (m->dataofs[i] < 0 || m->dataofs[i] > l->fileofs + l->filelen - sizeof(miptex_t)) {\n\t\t\tHost_Error(\"Miptex %d has data offset %d (-1 to %d)\", i, m->dataofs[i], l->fileofs + l->filelen - sizeof(miptex_t));\n\t\t}\n\n\t\tmt = (miptex_t *)((byte *)m + m->dataofs[i]);\n\t\tif ((uintptr_t)mt > (uintptr_t)mod_base + l->fileofs + l->filelen - sizeof(miptex_t)) {\n\t\t\tHost_Error(\"Texture %s data offset > lump (%d + %d vs %d)\", mt->name, mod_base, l->fileofs, l->filelen);\n\t\t}\n\t\tmt->width  = LittleLong(mt->width);\n\t\tmt->height = LittleLong(mt->height);\n\t\tfor (j = 0; j < MIPLEVELS; j++) {\n\t\t\tmt->offsets[j] = LittleLong(mt->offsets[j]);\n\t\t}\n\n\t\tif ((mt->width & 15) || (mt->height & 15)) {\n\t\t\tHost_Error(\"Texture %s is not 16 aligned\", mt->name);\n\t\t}\n\n\t\tpixels = mt->width * mt->height / 64 * 85;// some magic numbers?\n\t\tif (mod->bspversion == HL_BSPVERSION) {\n\t\t\tpixels += 2 + 256 * 3;\t/* palette and unknown two bytes */\n\t\t}\n\t\t// Some sanity checking (improve!)\n\t\tif (pixels < 0 || m->dataofs[i] > (l->fileofs + l->filelen - pixels) || pixels > INT_MAX - sizeof(texture_t)) {\n\t\t\tHost_Error(\"Texture %s: offset %d pixelsize %d, lumpsize %d\", mt->name, m->dataofs[i], pixels, l->filelen);\n\t\t}\n\t\ttx = (texture_t *)Hunk_AllocName(sizeof(texture_t) + pixels, mod->name);\n\t\tmod->textures[i] = tx;\n\n\t\tmemcpy(tx->name, mt->name, sizeof(tx->name));\n\t\tif (!tx->name[0]) {\n\t\t\tsnprintf(tx->name, sizeof(tx->name), \"unnamed%d\", i);\n\t\t\tCom_DPrintf(\"Warning: unnamed texture in %s, renaming to %s\\n\", mod->name, tx->name);\n\t\t}\n\n\t\ttx->width  = mt->width;\n\t\ttx->height = mt->height;\n\t\ttx->index = i;\n\t\ttx->loaded = false; // so texture will be reloaded\n\n\t\tif (tx->width < 0 || tx->height < 0) {\n\t\t\tHost_Error(\"Texture %s negative dimensions: %dx%d\", tx->name, tx->width, tx->height);\n\t\t}\n\t\tif (tx->width == 0 || tx->height == 0) {\n\t\t\tCon_Printf(\"Warning: Skipping zero size texture %s in %s\\n\", tx->name, mod->name);\n\t\t\tcontinue;\n\t\t}\n\t\tif (tx->width > INT_MAX / tx->height / 3) {\n\t\t\tHost_Error(\"Texture %s excessive size: %dx%d\", tx->name, tx->width, tx->height);\n\t\t}\n\n\t\tif (mod->bspversion == HL_BSPVERSION) {\n\t\t\tif (!mt->offsets[0]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// the pixels immediately follow the structures\n\t\t\tmemcpy(tx + 1, mt + 1, pixels);\n\t\t\tfor (j = 0; j < MIPLEVELS; j++) {\n\t\t\t\ttx->offsets[j] = mt->offsets[j] + sizeof(texture_t) - sizeof(miptex_t);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (mt->offsets[0]) {\n\t\t\t// the pixels immediately follow the structures\n\t\t\tmemcpy(tx+1, mt+1, pixels);\n\n\t\t\tfor (j = 0; j < MIPLEVELS; j++) {\n\t\t\t\ttx->offsets[j] = mt->offsets[j] + sizeof(texture_t) - sizeof(miptex_t);\n\t\t\t}\n\n\t\t\t// HACK HACK HACK\n\t\t\tif (!strcmp(mt->name, \"shot1sid\") && mt->width==32 && mt->height==32\n\t\t\t\t&& CRC_Block((byte*)(mt+1), mt->width*mt->height) == 65393)\n\t\t\t{\t// This texture in b_shell1.bsp has some of the first 32 pixels painted white.\n\t\t\t\t// They are invisible in software, but look really ugly in GL. So we just copy\n\t\t\t\t// 32 pixels from the bottom to make it look nice.\n\t\t\t\tmemcpy (tx+1, (byte *)(tx+1) + 32*31, 32);\n\t\t\t}\n\n\t\t\t// just for r_fastturb's sake\n\t\t\t{\n\t\t\t\tbyte *data = (byte *) &d_8to24table[*((byte *) mt + mt->offsets[0] + ((mt->height * mt->width) >> 1))];\n\t\t\t\ttx->flatcolor3ub = (255 << 24) + (data[0] << 0) + (data[1] << 8) + (data[2] << 16);\n\n\t\t\t\tif (strstr(tx->name, \"water\") || strstr(tx->name, \"mwat\")) {\n\t\t\t\t\ttx->turbType = TEXTURE_TURB_WATER;\n\t\t\t\t}\n\t\t\t\telse if (strstr(tx->name, \"slime\")) {\n\t\t\t\t\ttx->turbType = TEXTURE_TURB_SLIME;\n\t\t\t\t}\n\t\t\t\telse if (strstr(tx->name, \"lava\")) {\n\t\t\t\t\ttx->turbType = TEXTURE_TURB_LAVA;\n\t\t\t\t}\n\t\t\t\telse if (strstr(tx->name, \"tele\")) {\n\t\t\t\t\ttx->turbType = TEXTURE_TURB_TELE;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tR_LoadBrushModelTextures(mod);\n\n\t// sequence the animations\n\tfor (i = 0; i < m->nummiptex; i++) {\n\t\ttx = mod->textures[i];\n\t\tif (!tx || tx->name[0] != '+') {\n\t\t\tcontinue;\n\t\t}\n\t\tif (tx->anim_next) {\n\t\t\tcontinue; // already sequenced\n\t\t}\n\n\t\t// find the number of frames in the animation\n\t\tmemset (anims, 0, sizeof(anims));\n\t\tmemset (altanims, 0, sizeof(altanims));\n\n\t\tmax = tx->name[1];\n\t\taltmax = 0;\n\t\tif (max >= 'a' && max <= 'z') {\n\t\t\tmax -= 'a' - 'A';\n\t\t}\n\t\tif (max >= '0' && max <= '9') {\n\t\t\tmax -= '0';\n\t\t\taltmax = 0;\n\t\t\tanims[max] = tx;\n\t\t\tmax++;\n\t\t}\n\t\telse if (max >= 'A' && max <= 'J') {\n\t\t\taltmax = max - 'A';\n\t\t\tmax = 0;\n\t\t\taltanims[altmax] = tx;\n\t\t\taltmax++;\n\t\t}\n\t\telse {\n\t\t\tHost_Error (\"Mod_LoadTextures: Bad animating texture %s\", tx->name);\n\t\t}\n\n\t\tfor (j = i + 1; j < m->nummiptex; j++) {\n\t\t\ttx2 = mod->textures[j];\n\t\t\tif (!tx2 || tx2->name[0] != '+') {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (strcmp(tx2->name + 2, tx->name + 2)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tnum = tx2->name[1];\n\t\t\tif (num >= 'a' && num <= 'z') {\n\t\t\t\tnum -= 'a' - 'A';\n\t\t\t}\n\t\t\tif (num >= '0' && num <= '9') {\n\t\t\t\tnum -= '0';\n\t\t\t\tanims[num] = tx2;\n\t\t\t\tif (num + 1 > max) {\n\t\t\t\t\tmax = num + 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (num >= 'A' && num <= 'J') {\n\t\t\t\tnum = num - 'A';\n\t\t\t\taltanims[num] = tx2;\n\t\t\t\tif (num + 1 > altmax) {\n\t\t\t\t\taltmax = num + 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tHost_Error (\"Mod_LoadTextures: Bad animating texture %s\", tx->name);\n\t\t\t}\n\t\t}\n\n#define\tANIM_CYCLE\t2\n\t\t// link them all together\n\t\tfor (j = 0; j < max && j < sizeof(anims) / sizeof(anims[0]); j++) {\n\t\t\ttx2 = anims[j];\n\t\t\tif (!tx2) {\n\t\t\t\tHost_Error(\"Mod_LoadTextures: Missing frame %i of %s\", j, tx->name);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttx2->anim_total = max * ANIM_CYCLE;\n\t\t\ttx2->anim_min = j * ANIM_CYCLE;\n\t\t\ttx2->anim_max = (j + 1) * ANIM_CYCLE;\n\t\t\ttx2->anim_next = anims[(j + 1) % max];\n\t\t\tif (altmax) {\n\t\t\t\ttx2->alternate_anims = altanims[0];\n\t\t\t}\n\t\t}\n\t\tfor (j = 0; j < altmax && j < sizeof(altanims) / sizeof(altanims[0]); j++) {\n\t\t\ttx2 = altanims[j];\n\t\t\tif (!tx2) {\n\t\t\t\tHost_Error(\"Mod_LoadTextures: Missing frame %i of %s\", j, tx->name);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttx2->anim_total = altmax * ANIM_CYCLE;\n\t\t\ttx2->anim_min = j * ANIM_CYCLE;\n\t\t\ttx2->anim_max = (j + 1) * ANIM_CYCLE;\n\t\t\ttx2->anim_next = altanims[ (j + 1) % altmax ];\n\t\t\tif (max) {\n\t\t\t\ttx2->alternate_anims = anims[0];\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void Mod_LoadVertexes(model_t* mod, lump_t *l, byte* mod_base)\n{\n\tdvertex_t *in;\n\tmvertex_t *out;\n\tint i, count;\n\tint max_vertices = (INT_MAX / sizeof(*out));\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen <= 0) {\n\t\tHost_Error(\"Mod_LoadVertexes: lump size <= 0 in %s\", mod->name);\n\t\treturn;\n\t}\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadVertexes: funny lump size in %s\", mod->name);\n\t\treturn;\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count > max_vertices) {\n\t\tHost_Error(\"Mod_LoadVertexes: invalid vertex count (%d vs 0-%d)\", count, max_vertices);\n\t\treturn;\n\t}\n\tout = (mvertex_t *)Hunk_AllocName(count * sizeof(*out), mod->name);\n\n\tmod->vertexes = out;\n\tmod->numvertexes = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tout->position[0] = LittleFloat(in->point[0]);\n\t\tout->position[1] = LittleFloat(in->point[1]);\n\t\tout->position[2] = LittleFloat(in->point[2]);\n\t}\n}\n\nstatic void Mod_LoadEdges(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tdedge_t *in;\n\tmedge_t *out;\n\tint i, count;\n\tint max_edges = (INT_MAX / sizeof(*out));\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadEdges: funny lump size in %s\", loadmodel->name);\n\t\treturn;\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count < 0 || count + 1 >= max_edges) {\n\t\tHost_Error(\"Mod_LoadEdges: invalid edge count (%d vs 0-%d)\", count, max_edges);\n\t\treturn;\n\t}\n\tout = (medge_t *) Hunk_AllocName ( (count + 1) * sizeof(*out), loadmodel->name);\n\n\tloadmodel->edges = out;\n\tloadmodel->numedges = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tout->v[0] = (unsigned short) LittleShort(in->v[0]);\n\t\tout->v[1] = (unsigned short) LittleShort(in->v[1]);\n\t}\n}\n\nstatic void Mod_LoadEdgesBSP2(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tdedge29a_t *in;\n\tmedge_t *out;\n\tint i, count;\n\tint max_edges = (INT_MAX / sizeof(*out));\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in))\n\t\tHost_Error (\"Mod_LoadEdges: funny lump size in %s\", loadmodel->name);\n\tcount = l->filelen / sizeof(*in);\n\tif (count < 0 || count + 1 >= max_edges) {\n\t\tHost_Error(\"Mod_LoadEdges: invalid edge count (%d vs 0-%d)\", count, max_edges);\n\t\treturn;\n\t}\n\tout = (medge_t *) Hunk_AllocName ( (count + 1) * sizeof(*out), loadmodel->name);\n\n\tloadmodel->edges = out;\n\tloadmodel->numedges = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tout->v[0] = LittleLong(in->v[0]);\n\t\tout->v[1] = LittleLong(in->v[1]);\n\t}\n}\n\nstatic void Mod_LoadSurfedges(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tint i, count, *in, *out;\n\tint max_edges = (INT_MAX / sizeof(*out));\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in))\n\t\tHost_Error (\"Mod_LoadSurfedges: funny lump size in %s\", loadmodel->name);\n\tcount = l->filelen / sizeof(*in);\n\tif (count < 0 || count + 1 >= max_edges) {\n\t\tHost_Error(\"Mod_LoadSurfEdges: invalid edge count (%d vs 0-%d)\", count, max_edges);\n\t\treturn;\n\t}\n\tout = (int *) Hunk_AllocName ( count*sizeof(*out), loadmodel->name);\n\n\tloadmodel->surfedges = out;\n\tloadmodel->numsurfedges = count;\n\n\tfor (i = 0; i < count; i++) {\n\t\tout[i] = LittleLong(in[i]);\n\t}\n}\n\nstatic void Mod_LoadPlanes(model_t* model, lump_t* l, byte* mod_base)\n{\n\tint i, j, count, bits;\n\tmplane_t *out;\n\tdplane_t *in;\n\tint max_planes = (INT_MAX / sizeof(*out));\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadPlanes: funny lump size in %s\", model->name);\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count < 0 || count >= max_planes) {\n\t\tHost_Error(\"Mod_LoadPlanes: invalid plane count (%d vs 0-%d)\", count, max_planes);\n\t\treturn;\n\t}\n\tout = (mplane_t *) Hunk_AllocName ( count*sizeof(*out), model->name);\n\n\tmodel->planes = out;\n\tmodel->numplanes = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tbits = 0;\n\t\tfor (j = 0; j < 3 ; j++) {\n\t\t\tout->normal[j] = LittleFloat (in->normal[j]);\n\t\t\tif (out->normal[j] < 0) {\n\t\t\t\tbits |= 1 << j;\n\t\t\t}\n\t\t}\n\n\t\tout->dist = LittleFloat (in->dist);\n\t\tout->type = LittleLong (in->type);\n\t\tout->signbits = bits;\n\t}\n}\n\nstatic void Mod_LoadMarksurfacesBSP2(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tint i, count;\n\tunsigned int j;\n\tint *in;\n\tmsurface_t **out;\n\tint max_surfaces = (INT_MAX / sizeof(*out));\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadMarksurfaces: funny lump size in %s\", loadmodel->name);\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count < 0 || count >= max_surfaces) {\n\t\tHost_Error(\"Mod_LoadMarksurfaces: invalid marksurface count (%d vs 0-%d)\", count, max_surfaces);\n\t\treturn;\n\t}\n\tout = (msurface_t **)Hunk_AllocName(count * sizeof(*out), loadmodel->name);\n\n\tloadmodel->marksurfaces = out;\n\tloadmodel->nummarksurfaces = count;\n\n\tfor (i = 0; i < count; i++) {\n\t\t// Surface indices are unsigned\n\t\tj = (unsigned int) LittleLong(in[i]);\n\t\tif (j >= loadmodel->numsurfaces) {\n\t\t\tHost_Error(\"Mod_LoadMarksurfaces: bad surface number\");\n\t\t}\n\t\tout[i] = loadmodel->surfaces + j;\n\t}\n}\n\nstatic void Mod_LoadMarksurfaces(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tint i, count;\n\tunsigned short j;\n\tshort *in;\n\tmsurface_t **out;\n\tint max_surfaces = (INT_MAX / sizeof(*out));\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadMarksurfaces: funny lump size in %s\", loadmodel->name);\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count < 0 || count >= max_surfaces) {\n\t\tHost_Error(\"Mod_LoadMarksurfaces: invalid marksurface count (%d vs 0-%d)\", count, max_surfaces);\n\t\treturn;\n\t}\n\tout = (msurface_t **) Hunk_AllocName ( count*sizeof(*out), loadmodel->name);\n\n\tloadmodel->marksurfaces = out;\n\tloadmodel->nummarksurfaces = count;\n\n\tfor (i = 0; i < count; i++) {\n\t\t// Surface indices are unsigned\n\t\tj = (unsigned short) LittleShort(in[i]);\n\t\tif (j >= loadmodel->numsurfaces) {\n\t\t\tHost_Error(\"Mod_LoadMarksurfaces: bad surface number\");\n\t\t}\n\t\tout[i] = loadmodel->surfaces + j;\n\t}\n}\n\nstatic void Mod_ParseWadsFromEntityLump(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tconst char *data;\n\tchar *s, key[1024], value[1024];\n\tint i, j, k;\n\n\tif (l->filelen <= 0) {\n\t\treturn;\n\t}\n\n\tdata = (char *)(mod_base + l->fileofs);\n\tdata = COM_Parse(data);\n\tif (!data) {\n\t\treturn;\n\t}\n\n\tif (com_token[0] != '{') {\n\t\treturn; // error\n\t}\n\n\twhile (1) {\n\t\tif (!(data = COM_Parse(data))) {\n\t\t\treturn; // error\n\t\t}\n\n\t\tif (com_token[0] == '}') {\n\t\t\tbreak; // end of worldspawn\n\t\t}\n\n\t\tstrlcpy(key, (com_token[0] == '_') ? com_token + 1 : com_token, sizeof(key));\n\t\tfor (s = key + strlen(key) - 1; s >= key && *s == ' '; s--) {\n\t\t\t// remove trailing spaces\n\t\t\t*s = 0;\n\t\t}\n\n\t\tif (!(data = COM_Parse(data))) {\n\t\t\treturn; // error\n\t\t}\n\n\t\tstrlcpy(value, com_token, sizeof(value));\n\t\tif (!strcmp(\"sky\", key) || !strcmp(\"skyname\", key)) {\n\t\t\tCvar_Set(&r_skyname, value);\n\t\t}\n\n\t\tif (!strcmp(\"wad\", key)) {\n\t\t\tj = 0;\n\t\t\tfor (i = 0; i < strlen(value); i++) {\n\t\t\t\tif (value[i] != ';' && value[i] != '\\\\' && value[i] != '/' && value[i] != ':') {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!value[i]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor ( ; i < sizeof(value); i++) {\n\t\t\t\t// ignore path - the \\\\ check is for HalfLife\n\t\t\t\tif (value[i] == '\\\\' || value[i] == '/' || value[i] == ':') {\n\t\t\t\t\tj = i + 1;\n\t\t\t\t}\n\t\t\t\telse if (value[i] == ';' || value[i] == 0) {\n\t\t\t\t\tk = value[i];\n\t\t\t\t\tvalue[i] = 0;\n\t\t\t\t\tif (value[j]) {\n\t\t\t\t\t\tWAD3_LoadWadFile(value + j);\n\t\t\t\t\t}\n\t\t\t\t\tj = i + 1;\n\t\t\t\t\tif (!k) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void Mod_LoadTexinfo(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\ttexinfo_t *in;\n\tmtexinfo_t *out;\n\tint i, j, k, count;\n\tint max_texinfo = (INT_MAX / sizeof(*out));\n\n\tin = (void *) (mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadTexinfo: funny lump size in %s\", loadmodel->name);\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count > max_texinfo) {\n\t\tHost_Error(\"Mod_LoadTexinfo: invalid texinfo count (%d vs 0-%d)\", count, max_texinfo);\n\t\treturn;\n\t}\n\tout = (mtexinfo_t *) Hunk_AllocName (count*sizeof(*out), loadmodel->name);\n\n\tloadmodel->texinfo = out;\n\tloadmodel->numtexinfo = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tfor (j = 0; j < 2; j++) {\n\t\t\tfor (k = 0; k < 4; k++) {\n\t\t\t\tout->vecs[j][k] = LittleFloat(in->vecs[j][k]);\n\t\t\t}\n\t\t}\n\n\t\tout->miptex = LittleLong (in->miptex);\n\t\tout->flags = LittleLong (in->flags);\n\n\t\t// Skip texture unless surface flags say otherwise\n\t\tout->skippable = out->flags & TEX_SPECIAL;\n\n\t\tif (!loadmodel->textures) {\n\t\t\tout->texture = r_notexture_mip;\t// checkerboard texture\n\t\t\tout->flags = 0;\n\t\t}\n\t\telse {\n\t\t\tif (out->miptex >= loadmodel->numtextures) {\n\t\t\t\tHost_Error(\"Mod_LoadTexinfo: %d miptex %d >= loadmodel->numtextures\", i, out->miptex);\n\t\t\t}\n\t\t\tif (out->miptex < 0) {\n\t\t\t\tHost_Error(\"Mod_LoadTexinfo: %d miptex %d < 0\", i, out->miptex);\n\t\t\t}\n\t\t\tout->texture = loadmodel->textures[out->miptex];\n\t\t\tif (!out->texture) {\n\t\t\t\tout->texture = r_notexture_mip; // texture not found\n\t\t\t\tout->flags = 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Extension to support lightmaps that aren't tied to texture scale.\nstatic int Mod_LoadDecoupledLM(dlminfo_t* lminfos, int surfnum, msurface_t *out)\n{\n\tdlminfo_t *lminfo;\n\tunsigned short lmwidth, lmheight;\n\tint i, j;\n\n\tif (lminfos == NULL) {\n\t\treturn -1;\n\t}\n\n\tlminfo = lminfos + surfnum;\n\n\tlmwidth = LittleShort(lminfo->lmwidth);\n\tlmheight = LittleShort(lminfo->lmheight);\n\n\tif (lmwidth <= 0 || lmheight <= 0) {\n\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < 2; i++) {\n\t\tfor (j = 0; j < 4; j++) {\n\t\t\tout->lmvecs[i][j] = LittleFloat(lminfo->vecs[i][j]);\n\t\t}\n\t}\n\n\tout->extents[0] = (short)(lmwidth - 1);\n\tout->extents[1] = (short)(lmheight - 1);\n\tout->lmshift = 0;\n\tout->texturemins[0] = 0;\n\tout->texturemins[1] = 0;\n\n\tfloat v0 = VectorLength(out->lmvecs[0]);\n\tout->lmvlen[0] = v0 > 0.0f ? 1.0f / v0 : 0.0f;\n\n\tfloat v1 = VectorLength(out->lmvecs[1]);\n\tout->lmvlen[1] = v1 > 0.0f ? 1.0f / v1 : 0.0f;\n\n\treturn LittleLong(lminfo->lightofs);\n}\n\nstatic void Mod_LoadFaces(model_t* loadmodel, lump_t* l, byte* mod_base, bspx_header_t *bspx_header)\n{\n\tdlminfo_t *lminfos;\n\tdface_t *in;\n\tmsurface_t *out;\n\tint count, surfnum, planenum, side, lminfosize, lightofs;\n\tint max_faces = INT_MAX / sizeof(*out);\n\tint texinfo;\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in))\n\t\tHost_Error (\"Mod_LoadFaces: funny lump size in %s\", loadmodel->name);\n\tcount = l->filelen / sizeof(*in);\n\tif (count <= 0 || count > max_faces) {\n\t\tHost_Error(\"Mod_LoadFaces: invalid surface count (%d vs 0-%d)\", count, max_faces);\n\t\treturn;\n\t}\n\tout = (msurface_t *) Hunk_AllocName ( count*sizeof(*out), loadmodel->name);\n\n\tloadmodel->surfaces = out;\n\tloadmodel->numsurfaces = count;\n\n\tlminfos = Mod_BSPX_FindLump(bspx_header, \"DECOUPLED_LM\", &lminfosize, mod_base);\n\tif (lminfos != NULL && lminfosize / sizeof(dlminfo_t) != loadmodel->numsurfaces) {\n\t\tCom_Printf(\"[%s] decoupled_lm size %d does not match surface count %d\\n\", loadmodel->name, lminfosize / sizeof(dlminfo_t), loadmodel->numsurfaces);\n\t\tlminfos = NULL;\n\t}\n\n\tfor (surfnum = 0; surfnum < count; surfnum++, in++, out++) {\n\t\tout->firstedge = LittleLong(in->firstedge);\n\t\tout->numedges = LittleShort(in->numedges);\n\t\tout->flags = 0;\n\n\t\ttexinfo = LittleShort(in->texinfo);\n\n\t\tif (out->firstedge < 0 || out->firstedge >= loadmodel->numsurfedges || out->numedges > loadmodel->numsurfedges - out->firstedge) {\n\t\t\tHost_Error(\"[%s] surface %d references invalid edge range [%d length %d, vs %d]\", loadmodel->name, surfnum, out->firstedge, out->numedges, loadmodel->numsurfedges);\n\t\t}\n\n\t\tplanenum = LittleShort(in->planenum);\n\t\tif (planenum < 0 || planenum >= loadmodel->numplanes) {\n\t\t\tHost_Error(\"[%s] surface %d references invalid plane %d/%d\", loadmodel->name, surfnum, planenum, loadmodel->numplanes);\n\t\t}\n\n\t\tside = LittleShort(in->side);\n\t\tif (side) {\n\t\t\tout->flags |= SURF_PLANEBACK;\n\t\t}\n\n\t\tif (texinfo < 0 || texinfo >= loadmodel->numtexinfo) {\n\t\t\tHost_Error(\"[%s] surface %d references invalid texinfo %d/%d\", loadmodel->name, surfnum, texinfo, loadmodel->numtexinfo);\n\t\t}\n\t\tout->plane = loadmodel->planes + planenum;\n\t\tout->texinfo = loadmodel->texinfo + texinfo;\n\n\t\tlightofs = Mod_LoadDecoupledLM(lminfos, surfnum, out);\n\t\tif (lightofs < 0) {\n\t\t\tmemcpy(out->lmvecs, out->texinfo->vecs, sizeof(out->lmvecs));\n\t\t\tout->lmshift = DEFAULT_LMSHIFT;\n\t\t\tout->lmvlen[0] = 1.0f;\n\t\t\tout->lmvlen[1] = 1.0f;\n\n\t\t\tCalcSurfaceExtents(loadmodel, out);\n\n\t\t\tlightofs = in->lightofs;\n\t\t}\n\n\t\t// lighting info\n\t\tSetSurfaceLighting(loadmodel, out, in->styles, lightofs);\n\n\t\tSetTextureFlags(loadmodel, out, surfnum);\n\t}\n}\n\nstatic void Mod_LoadFacesBSP2(model_t* loadmodel, lump_t* l, byte* mod_base, bspx_header_t *bspx_header)\n{\n\tdlminfo_t *lminfos;\n\tdface29a_t *in;\n\tmsurface_t *out;\n\tint count, surfnum, planenum, side, lminfosize, lightofs;\n\tint max_faces = INT_MAX / sizeof(*out);\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadFaces: funny lump size in %s\", loadmodel->name);\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count <= 0 || count > max_faces) {\n\t\tHost_Error(\"Mod_LoadFaces: invalid surface count (%d vs 0-%d)\", count, max_faces);\n\t\treturn;\n\t}\n\tout = (msurface_t *) Hunk_AllocName ( count*sizeof(*out), loadmodel->name);\n\n\tloadmodel->surfaces = out;\n\tloadmodel->numsurfaces = count;\n\n\tlminfos = Mod_BSPX_FindLump(bspx_header, \"DECOUPLED_LM\", &lminfosize, mod_base);\n\tif (lminfos != NULL && lminfosize / sizeof(dlminfo_t) != loadmodel->numsurfaces) {\n\t\tCom_Printf(\"[%s] decoupled_lm size %d does not match surface count %d\\n\", loadmodel->name, lminfosize / sizeof(dlminfo_t), loadmodel->numsurfaces);\n\t\tlminfos = NULL;\n\t}\n\n\tfor (surfnum = 0; surfnum < count; surfnum++, in++, out++) {\n\t\tout->firstedge = LittleLong(in->firstedge);\n\t\tout->numedges = LittleLong(in->numedges);\n\t\tout->flags = 0;\n\n\t\tplanenum = LittleLong(in->planenum);\n\t\tside = LittleLong(in->side);\n\t\tif (side) {\n\t\t\tout->flags |= SURF_PLANEBACK;\n\t\t}\n\n\t\tout->plane = loadmodel->planes + planenum;\n\t\tout->texinfo = loadmodel->texinfo + LittleLong(in->texinfo);\n\n\t\tlightofs = Mod_LoadDecoupledLM(lminfos, surfnum, out);\n\t\tif (lightofs < 0) {\n\t\t\tmemcpy(out->lmvecs, out->texinfo->vecs, sizeof(out->lmvecs));\n\t\t\tout->lmshift = DEFAULT_LMSHIFT;\n\t\t\tout->lmvlen[0] = 1.0f;\n\t\t\tout->lmvlen[1] = 1.0f;\n\n\t\t\tCalcSurfaceExtents(loadmodel, out);\n\n\t\t\tlightofs = in->lightofs;\n\t\t}\n\n\t\t// lighting info\n\t\tSetSurfaceLighting(loadmodel, out, in->styles, lightofs);\n\n\t\tSetTextureFlags(loadmodel, out, surfnum);\n\t}\n}\n\nstatic void Mod_LoadNodes(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tint i, j, count, p;\n\tdnode_t *in;\n\tmnode_t *out;\n\tint max_nodes = (INT_MAX / sizeof(*out));\n\n\tin = (void *) (mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadNodes: funny lump size in %s\", loadmodel->name);\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count <= 0 || count > max_nodes) {\n\t\tHost_Error(\"Mod_LoadNodes: invalid node count (%d vs 0-%d)\", count, max_nodes);\n\t\treturn;\n\t}\n\tout = (mnode_t *) Hunk_AllocName (count * sizeof(*out), loadmodel->name);\n\n\tloadmodel->nodes = out;\n\tloadmodel->numnodes = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tout->minmaxs[j] = LittleShort (in->mins[j]);\n\t\t\tout->minmaxs[3 + j] = LittleShort (in->maxs[j]);\n\t\t}\n\n\t\tp = LittleLong(in->planenum);\n\t\tout->plane = loadmodel->planes + p;\n\n\t\tout->firstsurface = LittleShort (in->firstface);\n\t\tout->numsurfaces = LittleShort (in->numfaces);\n\n\t\tfor (j = 0; j < 2; j++) {\n\t\t\tp = LittleShort (in->children[j]);\n\t\t\tif (p >= 0) {\n\t\t\t\tout->children[j] = loadmodel->nodes + p;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tout->children[j] = (mnode_t *)(loadmodel->leafs + (-1 - p));\n\t\t\t}\n\t\t}\n\t}\n\n\tMod_SetParent (loadmodel->nodes, NULL);\t// sets nodes and leafs\n}\n\nstatic void Mod_LoadNodes29a(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tint i, j, count, p;\n\tdnode29a_t *in;\n\tmnode_t *out;\n\tint max_nodes = (INT_MAX / sizeof(*out));\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadNodes: funny lump size in %s\", loadmodel->name);\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count <= 0 || count > max_nodes) {\n\t\tHost_Error(\"Mod_LoadNodes: invalid node count (%d vs 0-%d)\", count, max_nodes);\n\t\treturn;\n\t}\n\tout = (mnode_t *) Hunk_AllocName (count * sizeof(*out), loadmodel->name);\n\n\tloadmodel->nodes = out;\n\tloadmodel->numnodes = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tout->minmaxs[j] = LittleShort (in->mins[j]);\n\t\t\tout->minmaxs[3 + j] = LittleShort (in->maxs[j]);\n\t\t}\n\n\t\tp = LittleLong(in->planenum);\n\t\tout->plane = loadmodel->planes + p;\n\n\t\tout->firstsurface = LittleLong (in->firstface);\n\t\tout->numsurfaces = LittleLong (in->numfaces);\n\n\t\tfor (j = 0; j < 2; j++) {\n\t\t\tp = LittleLong (in->children[j]);\n\t\t\tif (p >= 0) {\n\t\t\t\tout->children[j] = loadmodel->nodes + p;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tout->children[j] = (mnode_t *)(loadmodel->leafs + (-1 - p));\n\t\t\t}\n\t\t}\n\t}\n\n\tMod_SetParent (loadmodel->nodes, NULL);\t// sets nodes and leafs\n}\n\nstatic void Mod_LoadNodesBSP2(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tint i, j, count, p;\n\tdnode_bsp2_t *in;\n\tmnode_t *out;\n\tint max_nodes = (INT_MAX / sizeof(*out));\n\n\tin = (void *) (mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadNodes: funny lump size in %s\", loadmodel->name);\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count <= 0 || count > max_nodes) {\n\t\tHost_Error(\"Mod_LoadNodes: invalid node count (%d vs 0-%d)\", count, max_nodes);\n\t\treturn;\n\t}\n\tout = (mnode_t *) Hunk_AllocName (count * sizeof(*out), loadmodel->name);\n\n\tloadmodel->nodes = out;\n\tloadmodel->numnodes = count;\n\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tout->minmaxs[j] = LittleFloat (in->mins[j]);\n\t\t\tout->minmaxs[3 + j] = LittleFloat (in->maxs[j]);\n\t\t}\n\n\t\tp = LittleLong(in->planenum);\n\t\tout->plane = loadmodel->planes + p;\n\n\t\tout->firstsurface = LittleLong (in->firstface);\n\t\tout->numsurfaces = LittleLong (in->numfaces);\n\n\t\tfor (j = 0; j < 2; j++) {\n\t\t\tp = LittleLong (in->children[j]);\n\t\t\tif (p >= 0) {\n\t\t\t\tout->children[j] = loadmodel->nodes + p;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tout->children[j] = (mnode_t*)(loadmodel->leafs + (-1 - p));\n\t\t\t}\n\t\t}\n\t}\n\n\tMod_SetParent (loadmodel->nodes, NULL);\t// sets nodes and leafs\n}\n\nstatic qbool ContentsIsLiquid(int contents)\n{\n\tswitch (contents) {\n\tcase CONTENTS_WATER:\n\tcase CONTENTS_SLIME:\n\tcase CONTENTS_LAVA:\n\t\treturn true;\n\tdefault:\n\t\treturn false;\n\t}\n}\n\nstatic void Mod_LoadLeafs(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tdleaf_t *in;\n\tmleaf_t *out;\n\tint i, j, count, p;\n\tint max_leafs = (INT_MAX / sizeof(*out));\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in))\n\t\tHost_Error (\"Mod_LoadLeafs: funny lump size in %s\", loadmodel->name);\n\tcount = l->filelen / sizeof(*in);\n\tif (count <= 0 || count > max_leafs) {\n\t\tHost_Error(\"Mod_LoadNodes: invalid node count (%d vs 0-%d)\", count, max_leafs);\n\t\treturn;\n\t}\n\tout = (mleaf_t *) Hunk_AllocName ( count*sizeof(*out), loadmodel->name);\n\n\tloadmodel->leafs = out;\n\tloadmodel->numleafs = count;\n\tfor (i = 0; i < count; i++, in++, out++) {\n\t\tint first_marksurface, num_marksurfaces;\n\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tout->minmaxs[j] = LittleShort (in->mins[j]);\n\t\t\tout->minmaxs[3 + j] = LittleShort (in->maxs[j]);\n\t\t}\n\n\t\tp = LittleLong(in->contents);\n\t\tout->contents = p;\n\n\t\tfirst_marksurface = LittleShort(in->firstmarksurface);\n\t\tnum_marksurfaces = LittleShort(in->nummarksurfaces);\n\n\t\tif (first_marksurface < 0 || first_marksurface > loadmodel->nummarksurfaces) {\n\t\t\tHost_Error(\"Mod_LoadLeafs: first mark surface invalid (%d vs 0-%d)\", first_marksurface, loadmodel->nummarksurfaces);\n\t\t}\n\t\tif (num_marksurfaces < 0 || num_marksurfaces > loadmodel->nummarksurfaces - first_marksurface) {\n\t\t\tHost_Error(\"Mod_LoadLeafs: num_marksurfaces invalid (%d + %d vs 0-%d)\", first_marksurface, num_marksurfaces, loadmodel->nummarksurfaces);\n\t\t}\n\n\t\tout->firstmarksurface = loadmodel->marksurfaces + first_marksurface;\n\t\tout->nummarksurfaces = num_marksurfaces;\n\n\t\tp = LittleLong(in->visofs);\n\t\tout->compressed_vis = (p == -1 || loadmodel->visdata == NULL || p >= loadmodel->visdata_length) ? NULL : loadmodel->visdata + p;\n\t\tout->efrags = NULL;\n\n\t\tif (ContentsIsLiquid(out->contents)) {\n\t\t\tfor (j = 0; j < out->nummarksurfaces; j++)\n\t\t\t\tout->firstmarksurface[j]->flags |= SURF_UNDERWATER;\n\t\t}\n\t}\n}\n\nstatic void Mod_LoadLeafs29a(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tdleaf29a_t *in;\n\tmleaf_t *out;\n\tint i, j, count, p;\n\tint max_leafs = (INT_MAX / sizeof(*out));\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadLeafs: funny lump size in %s\", loadmodel->name);\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count <= 0 || count > max_leafs) {\n\t\tHost_Error(\"Mod_LoadNodes: invalid node count (%d vs 0-%d)\", count, max_leafs);\n\t\treturn;\n\t}\n\tout = (mleaf_t *) Hunk_AllocName (count*sizeof(*out), loadmodel->name);\n\n\tloadmodel->leafs = out;\n\tloadmodel->numleafs = count;\n\tfor (i = 0; i < count; i++, in++, out++)\t{\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tout->minmaxs[j] = LittleShort (in->mins[j]);\n\t\t\tout->minmaxs[3 + j] = LittleShort (in->maxs[j]);\n\t\t}\n\n\t\tp = LittleLong(in->contents);\n\t\tout->contents = p;\n\n\t\tout->firstmarksurface = loadmodel->marksurfaces +\n\t\t\tLittleLong(in->firstmarksurface);\n\t\tout->nummarksurfaces = LittleLong(in->nummarksurfaces);\n\n\t\tp = LittleLong(in->visofs);\n\t\tout->compressed_vis = (p == -1 || loadmodel->visdata == NULL || p >= loadmodel->visdata_length) ? NULL : loadmodel->visdata + p;\n\t\tout->efrags = NULL;\n\n\t\tif (ContentsIsLiquid(out->contents)) {\n\t\t\tfor (j = 0; j < out->nummarksurfaces; j++)\n\t\t\t\tout->firstmarksurface[j]->flags |= SURF_UNDERWATER;\n\t\t}\n\t}\n}\n\nstatic void Mod_LoadLeafsBSP2(model_t* loadmodel, lump_t* l, byte* mod_base)\n{\n\tdleaf_bsp2_t *in;\n\tmleaf_t *out;\n\tint i, j, count, p;\n\tint max_leafs = (INT_MAX / sizeof(*out));\n\n\tin = (void *)(mod_base + l->fileofs);\n\tif (l->filelen % sizeof(*in)) {\n\t\tHost_Error(\"Mod_LoadLeafs: funny lump size in %s\", loadmodel->name);\n\t}\n\tcount = l->filelen / sizeof(*in);\n\tif (count <= 0 || count > max_leafs) {\n\t\tHost_Error(\"Mod_LoadNodes: invalid node count (%d vs 0-%d)\", count, max_leafs);\n\t\treturn;\n\t}\n\tout = (mleaf_t *) Hunk_AllocName ( count*sizeof(*out), loadmodel->name);\n\n\tloadmodel->leafs = out;\n\tloadmodel->numleafs = count;\n\n\tfor (i = 0; i < count; i++, in++, out++)\t{\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tout->minmaxs[j] = LittleFloat (in->mins[j]);\n\t\t\tout->minmaxs[3 + j] = LittleFloat (in->maxs[j]);\n\t\t}\n\n\t\tp = LittleLong(in->contents);\n\t\tout->contents = p;\n\n\t\tout->firstmarksurface = loadmodel->marksurfaces +\n\t\t\tLittleLong(in->firstmarksurface);\n\t\tout->nummarksurfaces = LittleLong(in->nummarksurfaces);\n\n\t\tp = LittleLong(in->visofs);\n\t\tout->compressed_vis = (p == -1 || loadmodel->visdata == NULL || p >= loadmodel->visdata_length) ? NULL : loadmodel->visdata + p;\n\t\tout->efrags = NULL;\n\n\t\tif (ContentsIsLiquid(out->contents)) {\n\t\t\tfor (j = 0; j < out->nummarksurfaces; j++) {\n\t\t\t\tout->firstmarksurface[j]->flags |= SURF_UNDERWATER;\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Called from Mod_LoadModel()\nvoid Mod_LoadBrushModel(model_t *mod, void *buffer, int filesize)\n{\n\tint i;\n\tdheader_t *header;\n\tdmodel_t *bm;\n\tvec3_t normal;\n\tbspx_header_t* bspx_header;\n\n\tmod->type = mod_brush;\n\n\theader = (dheader_t *)buffer;\n\n\tmod->bspversion = LittleLong(header->version);\n\n\tif (mod->bspversion != Q1_BSPVERSION && mod->bspversion != HL_BSPVERSION && mod->bspversion != Q1_BSPVERSION2 && mod->bspversion != Q1_BSPVERSION29a) {\n\t\tHost_Error(\"Mod_LoadBrushModel: %s has wrong version number (%i should be %i (Quake), %i (HalfLife), %i (BSP2) or %i (2PSB))\", mod->name, mod->bspversion, Q1_BSPVERSION, HL_BSPVERSION, Q1_BSPVERSION2, Q1_BSPVERSION29a);\n\t}\n\tmod->isworldmodel = !strcmp(mod->name, va(\"maps/%s.bsp\", host_mapname.string));\n\n\t// swap all the lumps\n\tfor (i = 0; i < sizeof(dheader_t) / 4; i++) {\n\t\t((int *)header)[i] = LittleLong(((int *)header)[i]);\n\t}\n\n\t// Check lump sizes\n\tfor (i = 0; i < sizeof(header->lumps) / sizeof(header->lumps[0]); ++i) {\n\t\tif (header->lumps[i].fileofs > filesize) {\n\t\t\tHost_Error(\"Mod_LoadBrushModel(%s): lump %d has offset beyond file (%d vs %d)\", mod->name, i, header->lumps[i].fileofs, filesize);\n\t\t\treturn;\n\t\t}\n\t\tif (header->lumps[i].filelen < 0) {\n\t\t\tHost_Error(\"Mod_LoadBrushModel(%s): lump %d has negative lump length (%d)\", mod->name, i, header->lumps[i].filelen);\n\t\t\treturn;\n\t\t}\n\t\tif (header->lumps[i].filelen > filesize - header->lumps[i].fileofs) {\n\t\t\tHost_Error(\"Mod_LoadBrushModel(%s): lump %d has length beyond file (%d + %d vs %d)\", mod->name, i, header->lumps[i].fileofs, header->lumps[i].filelen, filesize);\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// check for BSPX extensions\n\tbspx_header = Mod_LoadBSPX(filesize, (byte*)header);\n\n\t// load into heap\n\tMod_LoadVertexes(mod, &header->lumps[LUMP_VERTEXES], (byte*)header);\n\tif (mod->bspversion == Q1_BSPVERSION2 || mod->bspversion == Q1_BSPVERSION29a) {\n\t\tMod_LoadEdgesBSP2(mod, &header->lumps[LUMP_EDGES], (byte*)header);\n\t}\n\telse {\n\t\tMod_LoadEdges(mod, &header->lumps[LUMP_EDGES], (byte*)header);\n\t}\n\tMod_LoadSurfedges(mod, &header->lumps[LUMP_SURFEDGES], (byte*)header);\n\tif (mod->bspversion == HL_BSPVERSION) {\n\t\tMod_ParseWadsFromEntityLump(mod, &header->lumps[LUMP_ENTITIES], (byte*)header);\n\t}\n\tMod_LoadTextures(mod, &header->lumps[LUMP_TEXTURES], (byte*)header);\n\tMod_LoadLighting(mod, &header->lumps[LUMP_LIGHTING], (byte*)header, bspx_header);\n\tMod_LoadPlanes(mod, &header->lumps[LUMP_PLANES], (byte*)header);\n\tMod_LoadTexinfo(mod, &header->lumps[LUMP_TEXINFO], (byte*)header);\n\tif (mod->bspversion == Q1_BSPVERSION2 || mod->bspversion == Q1_BSPVERSION29a) {\n\t\tMod_LoadFacesBSP2(mod, &header->lumps[LUMP_FACES], (byte*)header, bspx_header);\n\t\tMod_LoadMarksurfacesBSP2(mod, &header->lumps[LUMP_MARKSURFACES], (byte*)header);\n\t}\n\telse {\n\t\tMod_LoadFaces(mod, &header->lumps[LUMP_FACES], (byte*)header, bspx_header);\n\t\tMod_LoadMarksurfaces(mod, &header->lumps[LUMP_MARKSURFACES], (byte*)header);\n\t}\n\tMod_LoadVisibility(mod, &header->lumps[LUMP_VISIBILITY], (byte*)header);\n\tif (mod->bspversion == Q1_BSPVERSION29a) {\n\t\tMod_LoadLeafs29a(mod, &header->lumps[LUMP_LEAFS], (byte*)header);\n\t\tMod_LoadNodes29a(mod, &header->lumps[LUMP_NODES], (byte*)header);\n\t}\n\telse if (mod->bspversion == Q1_BSPVERSION2) {\n\t\tMod_LoadLeafsBSP2(mod, &header->lumps[LUMP_LEAFS], (byte*)header);\n\t\tMod_LoadNodesBSP2(mod, &header->lumps[LUMP_NODES], (byte*)header);\n\t}\n\telse {\n\t\tMod_LoadLeafs(mod, &header->lumps[LUMP_LEAFS], (byte*)header);\n\t\tMod_LoadNodes(mod, &header->lumps[LUMP_NODES], (byte*)header);\n\t}\n\tMod_LoadSubmodels(mod, &header->lumps[LUMP_MODELS], (byte*)header);\n\n\t// regular and alternate animation\n\tmod->numframes = 2;\n\n\t// set up the submodels (FIXME: this is confusing)\n\tfor (i = 0; i < mod->numsubmodels; i++) {\n\t\tbm = &mod->submodels[i];\n\n\t\tmod->firstmodelsurface = bm->firstface;\n\t\tmod->nummodelsurfaces = bm->numfaces;\n\n\t\tmod->firstnode = bm->headnode[0];\n\t\tif ((unsigned)mod->firstnode > mod->numnodes) {\n\t\t\tHost_Error(\"Inline model %i has bad firstnode\", i);\n\t\t}\n\n\t\tVectorCopy(bm->maxs, mod->maxs);\n\t\tVectorCopy(bm->mins, mod->mins);\n\n\t\tmod->radius = RadiusFromBounds(mod->mins, mod->maxs);\n\n\t\tmod->numleafs = bm->visleafs;\n\n\t\tif (i < mod->numsubmodels - 1) {\n\t\t\t// duplicate the basic information\n\t\t\tchar name[16];\n\t\t\tmodel_t* submodel;\n\n\t\t\tsnprintf(name, sizeof(name), \"*%i\", i + 1);\n\t\t\tsubmodel = Mod_FindName(name);\n\t\t\t*submodel = *mod;\n\t\t\tstrlcpy(submodel->name, name, sizeof(submodel->name));\n\t\t\tmod = submodel;\n\t\t}\n\t}\n\n\t// set wall/ceiling drawflat\n\tfor (i = 0; i < mod->numsurfaces; ++i) {\n\t\tmsurface_t* s = &mod->surfaces[i];\n\n\t\tVectorCopy(s->plane->normal, normal);\n\t\tVectorNormalize(normal);\n\n\t\tif (normal[2] < -0.5 || normal[2] > 0.5) {\n\t\t\t// floor or ceiling\n\t\t\ts->flags |= SURF_DRAWFLAT_FLOOR;\n\t\t}\n\t}\n}\n\n// this is initial load, or callback from VID after a vid_restart\nvoid R_LoadBrushModelTextures(model_t *m)\n{\n\tchar\t\t*texname;\n\ttexture_t\t*tx;\n\tint\t\t\ti, texmode, noscale_flag, alpha_flag, brighten_flag, mipTexLevel;\n\tbyte\t\t*data;\n\tint\t\t\twidth, height;\n\n\t// try load simple textures\n\tMod_AddModelFlags(m);\n\tmemset(m->simpletexture, 0, sizeof(m->simpletexture));\n\tm->simpletexture[0] = Mod_LoadSimpleTexture(m, 0);\n\n\tif (!m->textures) {\n\t\treturn;\n\t}\n\n\t//\tCom_Printf(\"lm %d %s\\n\", lightmode, loadmodel->name);\n\n\tfor (i = 0; i < m->numtextures; i++)\n\t{\n\t\ttx = m->textures[i];\n\t\tif (!tx) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (tx->loaded) {\n\t\t\tcontinue; // seems already loaded\n\t\t}\n\n\t\tR_TextureReferenceInvalidate(tx->gl_texturenum);\n\t\tR_TextureReferenceInvalidate(tx->fb_texturenum);\n\n\t\tif (m->isworldmodel && m->bspversion != HL_BSPVERSION && Mod_IsSkyTextureName(m, tx->name)) {\n\t\t\tif (!Mod_LoadExternalSkyTexture(tx)) {\n\t\t\t\tR_InitSky(tx);\n\t\t\t}\n\t\t\ttx->loaded = true;\n\t\t\tcontinue; // mark as loaded\n\t\t}\n\n\t\tnoscale_flag = 0;\n\t\tnoscale_flag = (!gl_scaleModelTextures.value && !m->isworldmodel) ? TEX_NOSCALE : noscale_flag;\n\t\tnoscale_flag = (!gl_scaleTurbTextures.value  && Mod_IsTurbTextureName(m, tx->name)) ? TEX_NOSCALE : noscale_flag;\n\t\tnoscale_flag = (!gl_scaleAlphaTextures.value  && Mod_IsAlphaTextureName(m, tx->name)) ? TEX_NOSCALE : noscale_flag;\n\n\t\tmipTexLevel  = noscale_flag ? 0 : gl_miptexLevel.value;\n\n\t\ttexmode = TEX_MIPMAP | noscale_flag;\n\t\tbrighten_flag = (!Mod_IsTurbTextureName(m, tx->name) && (lightmode == 2)) ? TEX_BRIGHTEN : 0;\n\t\talpha_flag = Mod_IsAlphaTextureName(m, tx->name) ? TEX_ALPHA : 0;\n\n\t\tif (Mod_LoadExternalTexture(m, tx, texmode | alpha_flag, brighten_flag)) {\n\t\t\ttx->loaded = true; // mark as loaded\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (m->bspversion == HL_BSPVERSION) {\n\t\t\tif ((data = WAD3_LoadTexture(tx))) {\n\t\t\t\tfs_netpath[0] = 0;\n\t\t\t\ttx->gl_texturenum = R_LoadTexturePixels(data, tx->name, tx->width, tx->height, texmode | alpha_flag);\n\t\t\t\tQ_free(data);\n\t\t\t\ttx->loaded = true; // mark as loaded\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttx->offsets[0] = 0; // this mean use r_notexture_mip, any better solution?\n\t\t}\n\n\t\tif (tx->offsets[0]) {\n\t\t\ttexname = tx->name;\n\t\t\twidth   = tx->width  >> mipTexLevel;\n\t\t\theight  = tx->height >> mipTexLevel;\n\t\t\tdata    = (byte *) tx + tx->offsets[mipTexLevel];\n\t\t}\n\t\telse {\n\t\t\ttexname = r_notexture_mip->name;\n\t\t\twidth   = r_notexture_mip->width  >> mipTexLevel;\n\t\t\theight  = r_notexture_mip->height >> mipTexLevel;\n\t\t\tdata     = (byte *) r_notexture_mip + r_notexture_mip->offsets[mipTexLevel];\n\t\t}\n\n\t\ttx->gl_texturenum = R_LoadTexture(texname, width, height, data, texmode | brighten_flag | alpha_flag, 1);\n\t\tif (!Mod_IsTurbTextureName(m, tx->name) && Img_HasFullbrights(data, width * height)) {\n\t\t\ttx->fb_texturenum = R_LoadTexture(va(\"@fb_%s\", texname), width, height, data, texmode | TEX_FULLBRIGHT | alpha_flag, 1);\n\t\t}\n\t\ttx->loaded = true; // mark as loaded\n\t}\n}\n"
  },
  {
    "path": "src/r_brushmodel_sky.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// gl_sky.c -- sky polygons (was previously in gl_warp.c)\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"r_brushmodel_sky.h\"\n#include \"r_texture.h\"\n#include \"r_local.h\"\n#include \"r_trace.h\"\n#include \"r_renderer.h\"\n#include \"tr_types.h\"\n\nstatic qbool Sky_LoadSkyboxTextures(const char* skyname);\n\ntexture_ref solidskytexture, alphaskytexture;\ntexture_ref skyboxtextures[MAX_SKYBOXTEXTURES];\ntexture_ref skybox_cubeMap;\n\nqbool r_skyboxloaded;\n\nextern cvar_t r_skywind;\n\nstatic float skywind_dist;\nstatic float skywind_yaw;\nstatic float skywind_pitch;\nstatic float skywind_period;\n\nstatic char active_skyname[MAX_QPATH];\n\n#define SKYWIND_CFG\t\t\t\"_wind.cfg\"\n\nstatic void Skywind_Load_f(void);\nstatic void Skywind_Clear(void);\n\n//A sky texture is 256 * 128, with the right side being a masked overlay\nvoid R_InitSky (texture_t *mt) {\n\tint i, j, p, r, g, b;\n\tbyte *src;\n\tunsigned trans[128 * 128], transpix, *rgba;\n\tint flags = TEX_MIPMAP | (gl_scaleskytextures.integer ? 0 : TEX_NOSCALE);\n\n\tsrc = (byte *) mt + mt->offsets[0];\n\n\t// make an average value for the back to avoid a fringe on the top level\n\tr = g = b = 0;\n\tfor (i = 0; i < 128; i++) {\n\t\tfor (j = 0; j < 128; j++) {\n\t\t\tp = src[i * 256 + j + 128];\n\t\t\trgba = &d_8to24table[p];\n\t\t\ttrans[(i * 128) + j] = *rgba;\n\t\t\tr += ((byte *) rgba)[0];\n\t\t\tg += ((byte *) rgba)[1];\n\t\t\tb += ((byte *) rgba)[2];\n\t\t}\n\t}\n\n\t((byte *) &transpix)[0] = r / (128 * 128);\n\t((byte *) &transpix)[1] = g / (128 * 128);\n\t((byte *) &transpix)[2] = b / (128 * 128);\n\t((byte *) &transpix)[3] = 0;\n\n\tsolidskytexture = R_LoadTexture (\"***solidskytexture***\", 128, 128, (byte *)trans, flags, 4);\n\n\tfor (i = 0; i < 128; i++) {\n\t\tfor (j = 0; j < 128; j++) {\n\t\t\tp = src[i * 256 + j];\n\t\t\ttrans[(i * 128) + j] = p ? d_8to24table[p] : transpix;\n\t\t}\n\t}\n\n\talphaskytexture = R_LoadTexture (\"***alphaskytexture***\", 128, 128, (byte *)trans, TEX_ALPHA | flags, 4);\n}\n\nint R_SetSky(char *skyname)\n{\n\tchar *groupname;\n\n\tmemset(active_skyname, 0, sizeof(active_skyname));\n\tr_skyboxloaded = false;\n\tSkywind_Clear();\n\n\t// set skyname to groupname if any\n\tskyname\t= (groupname = TP_GetSkyGroupName(TP_MapName(), NULL)) ? groupname : skyname;\n\n\tif (!skyname || !skyname[0] || strchr(skyname, '.'))\n\t{\n\t\t// Empty name or contain dot(dot causing troubles with skipping part of the name as file extenson),\n\t\t// so do nothing.\n\t\treturn 1;\n\t}\n\n\tif (!Sky_LoadSkyboxTextures(skyname)) {\n\t\treturn 1;\n\t}\n\n\t// everything was OK\n\tr_skyboxloaded = true;\n\n\tstrlcpy(active_skyname, skyname, sizeof(active_skyname));\n\n\tif (r_skywind.integer) {\n\t\tSkywind_Load_f();\n\t}\n\n\treturn 0;\n}\n\nvoid OnChange_r_skyname (cvar_t *v, char *skyname, qbool* cancel)\n{\n\tif (!skyname[0]) {\n\t\tmemset(active_skyname, 0, sizeof(active_skyname));\n\t\tr_skyboxloaded = false;\n\t\treturn;\n\t}\n\n\t*cancel = R_SetSky(skyname);\n}\n\nstatic void R_LoadSky_f(void)\n{\n\tswitch (Cmd_Argc()) {\n\tcase 1:\n\t\tif (r_skyboxloaded) {\n\t\t\tCom_Printf(\"Current skybox is \\\"%s\\\"\\n\", r_skyname.string);\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"No skybox has been set\\n\");\n\t\t}\n\t\tbreak;\n\tcase 2:\n\t\tif (!strcasecmp(Cmd_Argv(1), \"none\")) {\n\t\t\tCvar_Set(&r_skyname, \"\");\n\t\t}\n\t\telse {\n\t\t\tCvar_Set(&r_skyname, Cmd_Argv(1));\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tCom_Printf(\"Usage: %s <skybox>\\n\", Cmd_Argv(0));\n\t\tbreak;\n\t}\n}\n\n/*\n==============\nR_DrawSky\n\nDraw either the classic cloudy quake sky or a skybox\n==============\n*/\nvoid R_DrawSky (void)\n{\n\tif (!skychain) {\n\t\treturn;\n\t}\n\n\tR_TraceEnterNamedRegion(\"R_DrawSky\");\n\trenderer.DrawSky();\n\tR_TraceLeaveNamedRegion();\n\n\tskychain = NULL;\n\tskychain_tail = &skychain;\n}\n\nstatic qbool R_LoadSkyTexturePixels(r_cubemap_direction_id dir, const char* skyname, byte** data, int* width, int* height)\n{\n\tstatic const char *skybox_ext[r_cubemap_direction_count] = { \"rt\", \"bk\", \"lf\", \"ft\", \"up\", \"dn\" };\n\tstatic const char* search_paths[][2] = { { \"env/\", \"\" }, { \"gfx/env/\", \"\" }, { \"env/\", \"_\" }, { \"gfx/env/\", \"_\" } };\n\tint j, flags = TEX_NOCOMPRESS | TEX_MIPMAP | (gl_scaleskytextures.integer ? 0 : TEX_NOSCALE);\n\n\tif (dir < 0 || dir >= r_cubemap_direction_count) {\n\t\treturn false;\n\t}\n\n\tfor (j = 0; j < sizeof(search_paths) / sizeof(search_paths[0]); ++j) {\n\t\tchar path[MAX_PATH];\n\n\t\tstrlcpy(path, search_paths[j][0], sizeof(path));\n\t\tstrlcat(path, skyname, sizeof(path));\n\t\tstrlcat(path, search_paths[j][1], sizeof(path));\n\t\tstrlcat(path, skybox_ext[dir], sizeof(path));\n\n\t\t*data = R_LoadImagePixels(path, 0, 0, flags, width, height);\n\t\tif (*data) {\n\t\t\treturn *data != NULL;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nstatic qbool Sky_LoadSkyboxTextures(const char* skyname)\n{\n\tstatic int skydirection[] = { 4, 1, 5, 0, 2, 3 };\n\n\tr_cubemap_direction_id i;\n\tbyte* data;\n\tint fixed_size = 0;\n\tint width, height;\n\n\tR_DeleteTexture(&skybox_cubeMap);\n\tfor (i = 0; i < MAX_SKYBOXTEXTURES; i++) {\n\t\tR_DeleteTexture(&skyboxtextures[i]);\n\t}\n\n\tfor (i = 0; i < MAX_SKYBOXTEXTURES; i++) {\n\t\tif (R_LoadSkyTexturePixels(i, skyname, &data, &width, &height)) {\n\t\t\tif (R_UseCubeMapForSkyBox()) {\n\t\t\t\tint size = (i != 0 ? fixed_size : min(width, height));\n\n\t\t\t\tR_TextureRescaleOverlay(&data, &width, &height, size, size);\n\n\t\t\t\tfixed_size = size;\n\n\t\t\t\tif (i == 0) {\n\t\t\t\t\tskybox_cubeMap = R_CreateCubeMap(\"***skybox***\", size, size, TEX_NOCOMPRESS | TEX_MIPMAP | TEX_NOSCALE | TEX_ALPHA);\n\t\t\t\t\tif (!R_TextureReferenceIsValid(skybox_cubeMap)) {\n\t\t\t\t\t\tQ_free(data);\n\t\t\t\t\t\tCom_Printf(\"Couldn't load skybox \\\"%s\\\"\\n\", skyname);\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\trenderer.TextureLoadCubemapFace(skybox_cubeMap, skydirection[i], data, size, size);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tchar id[16];\n\n\t\t\t\tsnprintf(id, sizeof(id) - 1, \"***skybox%d***\", i);\n\n\t\t\t\tskyboxtextures[i] = R_LoadTexture(id, width, height, data, TEX_NOCOMPRESS | TEX_MIPMAP | TEX_NOSCALE, 4);\n\t\t\t}\n\n\t\t\t// we should free data from R_LoadImagePixels()\n\t\t\tQ_free(data);\n\t\t}\n\t\telse {\n\t\t\tint j;\n\n\t\t\tCom_Printf(\"Couldn't load skybox \\\"%s\\\"\\n\", skyname);\n\t\t\tif (R_UseCubeMapForSkyBox()) {\n\t\t\t\tR_DeleteCubeMap(&skybox_cubeMap);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfor (j = 0; j < i; ++j) {\n\t\t\t\t\tR_DeleteTexture(&skyboxtextures[j]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (R_TextureReferenceIsValid(skybox_cubeMap)) {\n\t\trenderer.TextureMipmapGenerate(skybox_cubeMap);\n\t}\n\n\treturn true;\n}\n\nvoid R_ClearSkyTextures(void)\n{\n\tR_TextureReferenceInvalidate(solidskytexture);\n\tR_TextureReferenceInvalidate(alphaskytexture);\n}\n\nstatic void Skywind_Clear(void)\n{\n\tif (!r_skyboxloaded)\n\t\treturn;\n\tskywind_dist = 0.f;\n\tskywind_yaw = 45.f;\n\tskywind_pitch = 0.f;\n\tskywind_period = 30.f;\n}\n\nstatic void Skywind_Load_f(void)\n{\n\tchar relname[MAX_QPATH];\n\tchar *buf;\n\tconst char *data;\n\n\tif (!r_skyboxloaded)\n\t{\n\t\tCon_Printf (\"No skybox loaded\\n\");\n\t\treturn;\n\t}\n\n\tsnprintf(relname, sizeof(relname), \"gfx/env/%s\" SKYWIND_CFG, active_skyname);\n\tbuf = (char *)FS_LoadTempFile(relname, NULL);\n\tif (!buf)\n\t{\n\t\tCon_DPrintf (\"Sky wind config not found '%s'.\\n\", relname);\n\t\treturn;\n\t}\n\n\tdata = COM_Parse(buf);\n\tif (!data)\n\t{\n\t\treturn;\n\t}\n\n\tif (strcmp(com_token, \"skywind\") != 0)\n\t{\n\t\tCon_Printf(\"Skywind_Load_f: first token must be 'skywind'.\\n\");\n\t\treturn;\n\t}\n\n\tSkywind_Clear();\n\n\tif ((data = COM_Parse(data)) != NULL)\n\t{\n\t\tskywind_dist = bound(-2.0f, atof(com_token), 2.0f);\n\t}\n\n\tif ((data = COM_Parse(data)) != NULL)\n\t{\n\t\tskywind_yaw = fmodf(atof(com_token), 360.0f);\n\t}\n\n\tif ((data = COM_Parse(data)) != NULL)\n\t{\n\t\tskywind_period = atof(com_token);\n\t}\n\n\tif ((data = COM_Parse(data)) != NULL)\n\t{\n\t\tskywind_pitch = fmodf(atof(com_token) + 90.0f, 180.0f) - 90.0f;\n\t}\n}\n\nstatic void Skywind_Save_f (void)\n{\n\tchar relname[MAX_QPATH];\n\tchar path[MAX_OSPATH];\n\tFILE *f;\n\n\tif (!r_skyboxloaded)\n\t{\n\t\tCon_Printf(\"No skybox loaded\\n\");\n\t\treturn;\n\t}\n\n\tsnprintf(relname, sizeof(relname), \"gfx/env/%s\" SKYWIND_CFG, active_skyname);\n\tsnprintf(path, sizeof(path), \"%s/%s\", com_gamedir, relname);\n\tf = fopen(path, \"wt\");\n\tif (!f)\n\t{\n\t\tCon_Printf(\"Couldn't write '%s'.\\n\", relname);\n\t\treturn;\n\t}\n\n\tfprintf(f,\n\t\t\t\"// distance yaw period pitch\\n\"\n\t\t\t\"skywind %g %g %g %g\\n\",\n\t\t\tskywind_dist,\n\t\t\tskywind_yaw,\n\t\t\tskywind_period,\n\t\t\tskywind_pitch\n\t);\n\n\tfclose(f);\n\n\tCon_Printf(\"Wrote %s\\n\", relname);\n}\n\nstatic void Skywind_LookDir_f (void)\n{\n\tif (cls.state != ca_active)\n\t{\n\t\treturn;\n\t}\n\n\tif (!r_skyboxloaded)\n\t{\n\t\tCon_Printf(\"No skybox loaded\\n\");\n\t\treturn;\n\t}\n\n\t// invert view direction so that clouds move towards the player, not away from them\n\tskywind_yaw = fmodf(cl.viewangles[YAW] + 180.0f, 360.0f);\n\tskywind_pitch = -cl.viewangles[PITCH];\n\n\t// first argument, if present, overrides the loop duration (default: 30 seconds)\n\tif (Cmd_Argc() >= 2)\n\t{\n\t\tskywind_period = atof(Cmd_Argv(1));\n\t}\n\telse if (!skywind_period)\n\t{\n\t\tskywind_period = 30.f;\n\t}\n\n\t// second argument, if present, overrides the amplitude of the movement (default: 1.0)\n\tif (Cmd_Argc() >= 3)\n\t{\n\t\tskywind_dist = bound(-2.0f, atof(Cmd_Argv(2)), 2.0f);\n\t}\n\telse if (!skywind_dist)\n\t{\n\t\tskywind_dist = 1.0f;\n\t}\n}\n\nstatic void Skywind_Rotate_f(void)\n{\n\tif (cls.state != ca_active)\n\t{\n\t\treturn;\n\t}\n\n\tif (!r_skyboxloaded)\n\t{\n\t\tCon_Printf(\"No skybox loaded\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tCon_Printf(\n\t\t\t\t\"usage:\\n\"\n\t\t\t\t\"   %s <yawdelta> [pitchdelta]\\n\",\n\t\t\t\tCmd_Argv (0)\n\t\t);\n\t\treturn;\n\t}\n\n\tskywind_yaw = fmodf(skywind_yaw + atof(Cmd_Argv(1)), 360.0f);\n\tif (Cmd_Argc() >= 3)\n\t{\n\t\tskywind_pitch = fmodf(skywind_pitch + atof(Cmd_Argv(2)) + 90.0f, 180.0f) - 90.0f;\n\t}\n}\n\nstatic void Skywind_f (void)\n{\n\tif (cls.state != ca_active)\n\t{\n\t\treturn;\n\t}\n\n\tif (!r_skyboxloaded)\n\t{\n\t\tCon_Printf(\"No skybox loaded\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tCon_Printf (\n\t\t\t\t\"usage:\\n\"\n\t\t\t\t\"   %s [distance] [yaw] [period] [pitch]\\n\"\n\t\t\t\t\"current values:\\n\"\n\t\t\t\t\"   \\\"distance\\\" is \\\"%g\\\"\\n\"\n\t\t\t\t\"   \\\"yaw\\\"      is \\\"%g\\\"\\n\"\n\t\t\t\t\"   \\\"period\\\"   is \\\"%g\\\"\\n\"\n\t\t\t\t\"   \\\"pitch\\\"    is \\\"%g\\\"\\n\",\n\t\t\t\tCmd_Argv(0),\n\t\t\t\tskywind_dist,\n\t\t\t\tskywind_yaw,\n\t\t\t\tskywind_period,\n\t\t\t\tskywind_pitch\n\t\t);\n\t\treturn;\n\t}\n\n\tskywind_dist = bound(-2.0f, atof(Cmd_Argv (1)), 2.0f);\n\tif (Cmd_Argc () >= 3)\n\t{\n\t\tskywind_yaw = fmodf(atof(Cmd_Argv(2)), 360.0f);\n\t}\n\tif (Cmd_Argc () >= 4)\n\t{\n\t\tskywind_period = atof(Cmd_Argv(3));\n\t}\n\tif (Cmd_Argc () >= 5)\n\t{\n\t\tskywind_pitch = fmodf(atof(Cmd_Argv(4)) + 90.0f, 180.0f) - 90.0f;\n\t}\n}\n\nqbool Skywind_Active(void)\n{\n\treturn r_skyboxloaded && skywind_dist > 0.0f;\n}\n\nqbool Skywind_GetDirectionAndPhase(float wind_dir[3], float *wind_phase)\n{\n\tfloat yaw, pitch, sy, sp, cy, cp, dist, period;\n\tdouble phase;\n\n\tif (!Skywind_Active())\n\t{\n\t\treturn false;\n\t}\n\n\tyaw = DEG2RAD(skywind_yaw);\n\tpitch = DEG2RAD(skywind_pitch);\n\tsy = sinf(yaw);\n\tsp = sinf(pitch);\n\tcy = cosf(yaw);\n\tcp = cosf(pitch);\n\tdist = bound(-2.f, skywind_dist, 2.f);\n\tperiod = skywind_period / r_skywind.value;\n\tphase = period ? cl.time * 0.5 / period : 0.5;\n\n\tphase -= floor(phase) + 0.5; // [-0.5, 0.5)\n\twind_dir[0] = dist * cp * sy;\n\twind_dir[1] = dist * sp;\n\twind_dir[2] = -dist * cp * cy;\n\t*wind_phase = (float) phase;\n\n\treturn true;\n}\n\nvoid R_SkyRegisterCvars(void)\n{\n\tCmd_AddCommand(\"loadsky\", R_LoadSky_f);\n\tCmd_AddCommand(\"skywind\",Skywind_f);\n\tCmd_AddCommand(\"skywind_save\",Skywind_Save_f);\n\tCmd_AddCommand(\"skywind_load\",Skywind_Load_f);\n\tCmd_AddCommand(\"skywind_lookdir\",Skywind_LookDir_f);\n\tCmd_AddCommand(\"skywind_rotate\",Skywind_Rotate_f);\n}"
  },
  {
    "path": "src/r_brushmodel_sky.h",
    "content": "\n#ifndef EZQUAKE_R_BRUSHMODEL_SKY_HEADER\n#define EZQUAKE_R_BRUSHMODEL_SKY_HEADER\n\nvoid GLC_SkyDrawChainedSurfaces(void);\n\nextern msurface_t *skychain;\nextern msurface_t **skychain_tail;\nextern texture_ref solidskytexture, alphaskytexture;\nextern cvar_t r_skyname;\n\n#define MAX_SKYBOXTEXTURES 6\nextern texture_ref skyboxtextures[MAX_SKYBOXTEXTURES];\n\nvoid R_ClearSkyTextures(void);\nvoid R_SkyRegisterCvars(void);\nextern qbool r_skyboxloaded;\n\nqbool Skywind_Active(void);\nqbool Skywind_GetDirectionAndPhase(float wind_dir[3], float *wind_phase);\n\n#endif // EZQUAKE_R_BRUSHMODEL_SKY_HEADER\n"
  },
  {
    "path": "src/r_brushmodel_surfaces.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// r_surf.c: surface-related refresh code\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"rulesets.h\"\n#include \"utils.h\"\n#include \"r_performance.h\"\n#include \"r_local.h\"\n#include \"r_texture.h\"\n#include \"r_lighting.h\"\n#include \"r_brushmodel.h\"\n#include \"r_brushmodel_sky.h\"\n#include \"r_lightmaps.h\"\n#include \"r_trace.h\"\n#include \"r_renderer.h\"\n\n// Move to API\nvoid GLC_ClearTextureChains(void);\n\n// gl_refrag.c\nvoid R_StoreEfrags(efrag_t **ppefrag);\n\nstatic void R_TurbSurfacesEmitParticleEffects(msurface_t* s);\n\nmsurface_t\t*skychain = NULL;\nmsurface_t\t**skychain_tail = &skychain;\n\nmsurface_t\t*waterchain = NULL;\n\nmsurface_t\t*alphachain = NULL;\nmsurface_t\t**alphachain_tail = &alphachain;\n\ntypedef void(*chain_surf_func)(msurface_t** chain_head, msurface_t* surf);\n\nvoid chain_surfaces_simple(msurface_t** chain_head, msurface_t* surf)\n{\n\tsurf->texturechain = *chain_head;\n\t*chain_head = surf;\n}\n\nvoid chain_surfaces_simple_drawflat(msurface_t** chain_head, msurface_t* surf)\n{\n\tsurf->drawflatchain = *chain_head;\n\t*chain_head = surf;\n}\n\nvoid chain_surfaces_by_lightmap(msurface_t** chain_head, msurface_t* surf)\n{\n\tmsurface_t* current = *chain_head;\n\tqbool alphaSurface = surf->flags & SURF_DRAWALPHA;\n\n\twhile (current) {\n\t\tif ((alphaSurface && !(current->flags & SURF_DRAWALPHA)) || (!alphaSurface && surf->lightmaptexturenum > current->lightmaptexturenum)) {\n\t\t\tchain_head = &(current->texturechain);\n\t\t\tcurrent = *chain_head;\n\t\t\tcontinue;\n\t\t}\n\n\t\tbreak;\n\t}\n\n\tsurf->texturechain = current;\n\t*chain_head = surf;\n}\n\n#define CHAIN_SURF_F2B(surf, chain_tail)\t\t\\\n\t{\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t*(chain_tail) = (surf);\t\t\t\t\t\\\n\t\t(chain_tail) = &(surf)->texturechain;\t\\\n\t\t(surf)->texturechain = NULL;\t\t\t\\\n\t}\n\n#define CHAIN_SURF_B2F(surf, chain) \t\t\t\\\n\t{\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t(surf)->texturechain = (chain);\t\t\t\\\n\t\t(chain) = (surf);\t\t\t\t\t\t\\\n\t}\n\n#define CHAIN_RESET(chain)\t\t\t\\\n{\t\t\t\t\t\t\t\t\\\n\tchain = NULL;\t\t\t\t\\\n\tchain##_tail = &chain;\t\t\\\n}\n\n//Returns the proper texture for a given time and base texture\ntexture_t *R_TextureAnimation(entity_t* ent, texture_t *base)\n{\n\tint relative, count;\n\n\tif (ent && ent->frame && base->alternate_anims) {\n\t\tbase = base->alternate_anims;\n\t}\n\n\tif (!base->anim_total) {\n\t\treturn base;\n\t}\n\n\trelative = (int) (r_refdef2.time * 10) % base->anim_total;\n\n\tcount = 0;\n\twhile (base->anim_min > relative || base->anim_max <= relative) {\n\t\tbase = base->anim_next;\n\t\tif (!base) {\n\t\t\tHost_Error(\"R_TextureAnimation: broken cycle\");\n\t\t}\n\t\tif (++count > 100) {\n\t\t\tHost_Error(\"R_TextureAnimation: infinite cycle\");\n\t\t}\n\t}\n\n\treturn base;\n}\n\nvoid R_BrushModelClearTextureChains(model_t *clmodel)\n{\n\tint i;\n\ttexture_t *texture;\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_ClearTextureChains();\n\t}\n#endif\n\n\tclmodel->texturechains_have_lumas = false;\n\tfor (i = 0; i < clmodel->numtextures; i++) {\n\t\tif ((texture = clmodel->textures[i])) {\n\t\t\ttexture->texturechain = NULL;\n\t\t\ttexture->texturechain_tail = &texture->texturechain;\n\t\t}\n\t}\n\tclmodel->drawflat_chain = NULL;\n\tclmodel->drawflat_todo = false;\n\tclmodel->alphapass_todo = false;\n\tclmodel->first_texture_chained = clmodel->numtextures;\n\tclmodel->last_texture_chained = -1;\n\n\tr_notexture_mip->texturechain = NULL;\n\tr_notexture_mip->texturechain_tail = &r_notexture_mip->texturechain;\n\n\tCHAIN_RESET(skychain);\n\tif (clmodel == cl.worldmodel) {\n\t\twaterchain = NULL;\n\t}\n\tCHAIN_RESET(alphachain);\n}\n\nvoid OnChange_r_drawflat (cvar_t *var, char *value, qbool *cancel) {\n\tchar *p;\n\tqbool progress = false;\n\n\n\tp = Info_ValueForKey (cl.serverinfo, \"status\");\n\tprogress = (strstr (p, \"left\")) ? true : false;\n\n\tif (cls.state >= ca_connected && progress && !r_refdef2.allow_cheats && !cl.spectator) {\n\t\tCom_Printf (\"%s changes are not allowed during the match.\\n\", var->name);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n}\n\nvoid R_RecursiveWorldNode(mnode_t *node, int clipflags)\n{\n\textern cvar_t r_fastturb, r_fastsky;\n\tmodel_t* clmodel = cl.worldmodel;\n\n\tint c, side, clipped;\n\tmplane_t *plane, *clipplane;\n\tmsurface_t *surf, **mark;\n\tmleaf_t *pleaf;\n\tfloat dot;\n\n\tif (node->contents == CONTENTS_SOLID || node->visframe != r_visframecount) {\n\t\treturn;\n\t}\n\tfor (c = 0, clipplane = frustum; c < 4; c++, clipplane++) {\n\t\tif (!(clipflags & (1 << c))) {\n\t\t\tcontinue;\t// don't need to clip against it\n\t\t}\n\n\t\tclipped = BOX_ON_PLANE_SIDE(node->minmaxs, node->minmaxs + 3, clipplane);\n\t\tif (clipped == 2) {\n\t\t\treturn;\n\t\t}\n\t\telse if (clipped == 1) {\n\t\t\tclipflags &= ~(1 << c);\t// node is entirely on screen\n\t\t}\n\t}\n\n\t// if a leaf node, draw stuff\n\tif (node->contents < 0) {\n\t\tpleaf = (mleaf_t *)node;\n\n\t\tmark = pleaf->firstmarksurface;\n\t\tc = pleaf->nummarksurfaces;\n\n\t\tif (c) {\n\t\t\tdo {\n\t\t\t\t(*mark)->visframe = r_framecount;\n\t\t\t\tmark++;\n\t\t\t} while (--c);\n\t\t}\n\n\t\t// deal with model fragments in this leaf\n\t\tif (pleaf->efrags) {\n\t\t\tR_StoreEfrags(&pleaf->efrags);\n\t\t}\n\n\t\treturn;\n\t}\n\n\t// node is just a decision point, so go down the apropriate sides\n\n\t// find which side of the node we are on\n\tplane = node->plane;\n\n\tdot = PlaneDiff(modelorg, plane);\n\tside = (dot >= 0) ? 0 : 1;\n\n\t// recurse down the children, front side first\n\tR_RecursiveWorldNode(node->children[side], clipflags);\n\n\t// draw stuff\n\tc = node->numsurfaces;\n\n\tif (c) {\n\t\tqbool turbSurface;\n\t\tqbool alphaSurface;\n\n\t\tsurf = cl.worldmodel->surfaces + node->firstsurface;\n\n\t\tif (dot < -BACKFACE_EPSILON) {\n\t\t\tside = SURF_PLANEBACK;\n\t\t}\n\t\telse if (dot > BACKFACE_EPSILON) {\n\t\t\tside = 0;\n\t\t}\n\n\t\tfor (; c; c--, surf++) {\n\t\t\tif (surf->visframe != r_framecount) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ((dot < 0) ^ !!(surf->flags & SURF_PLANEBACK)) {\n\t\t\t\tcontinue;\t\t// wrong side\n\t\t\t}\n\n\t\t\t// add surf to the right chain\n\t\t\tcl.worldmodel->first_texture_chained = min(cl.worldmodel->first_texture_chained, surf->texinfo->miptex);\n\t\t\tcl.worldmodel->last_texture_chained = max(cl.worldmodel->last_texture_chained, surf->texinfo->miptex);\n\n\t\t\tturbSurface = (surf->flags & SURF_DRAWTURB);\n\t\t\talphaSurface = (surf->flags & SURF_DRAWALPHA);\n\t\t\tif (surf->flags & SURF_DRAWSKY) {\n\t\t\t\tif (r_fastsky.integer || R_UseModernOpenGL()) {\n\t\t\t\t\tchain_surfaces_simple_drawflat(&cl.worldmodel->drawflat_chain, surf);\n\t\t\t\t\tcl.worldmodel->drawflat_todo = true;\n\t\t\t\t}\n\t\t\t\telse if (!r_fastsky.integer) {\n\t\t\t\t\tchain_surfaces_simple(&skychain, surf);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (turbSurface) {\n\t\t\t\tif (r_fastturb.integer && r_refdef2.wateralpha == 1) {\n\t\t\t\t\tchain_surfaces_simple_drawflat(&cl.worldmodel->drawflat_chain, surf);\n\t\t\t\t\tcl.worldmodel->drawflat_todo = true;\n\t\t\t\t}\n\t\t\t\telse if (r_refdef2.solidTexTurb && R_UseModernOpenGL()) {\n\t\t\t\t\tchain_surfaces_simple(&surf->texinfo->texture->texturechain, surf);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tchain_surfaces_simple(&waterchain, surf);\n\t\t\t\t}\n\t\t\t\tR_TurbSurfacesEmitParticleEffects(surf);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (!alphaSurface && r_refdef2.drawFlatFloors && (surf->flags & SURF_DRAWFLAT_FLOOR)) {\n\t\t\t\t\tif (R_UseImmediateOpenGL()) {\n\t\t\t\t\t\tR_AddDrawflatChainSurface(surf, true);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tchain_surfaces_simple_drawflat(&cl.worldmodel->drawflat_chain, surf);\n\t\t\t\t\t}\n\t\t\t\t\tcl.worldmodel->drawflat_todo = true;\n\t\t\t\t}\n\t\t\t\telse if (!alphaSurface && r_refdef2.drawFlatWalls && !(surf->flags & SURF_DRAWFLAT_FLOOR)) {\n\t\t\t\t\tif (R_UseImmediateOpenGL()) {\n\t\t\t\t\t\tR_AddDrawflatChainSurface(surf, false);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tchain_surfaces_simple_drawflat(&cl.worldmodel->drawflat_chain, surf);\n\t\t\t\t\t}\n\t\t\t\t\tcl.worldmodel->drawflat_todo = true;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tclmodel->texturechains_have_lumas |= R_TextureAnimation(NULL, surf->texinfo->texture)->isLumaTexture;\n\t\t\t\t\tclmodel->alphapass_todo |= alphaSurface;\n\n\t\t\t\t\tif (R_UseImmediateOpenGL()) {\n\t\t\t\t\t\tif (alphaSurface) {\n\t\t\t\t\t\t\tCHAIN_SURF_B2F(surf, surf->texinfo->texture->texturechain);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tchain_surfaces_by_lightmap(&surf->texinfo->texture->texturechain, surf);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tchain_surfaces_simple(&surf->texinfo->texture->texturechain, surf);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// recurse down the back side\n\tR_RecursiveWorldNode(node->children[!side], clipflags);\n}\n\nvoid R_CreateWorldTextureChains(void)\n{\n\textern cvar_t r_drawworld;\n\n\tif (cl.worldmodel && (!cls.timedemo || r_drawworld.integer)) {\n\t\tR_BrushModelClearTextureChains(cl.worldmodel);\n\n\t\tVectorCopy(r_refdef.vieworg, modelorg);\n\n\t\t//set up texture chains for the world\n\t\tR_RecursiveWorldNode(cl.worldmodel->nodes, 15);\n\n\t\t// these are rendered later now, so we can process earlier\n\t\tR_RenderDlights();\n\n\t\tR_RenderAllDynamicLightmaps(cl.worldmodel);\n\t}\n}\n\nvoid R_DrawWorld(void)\n{\n\tR_TraceEnterNamedRegion(\"R_DrawWorld\");\n\tVectorCopy(r_refdef.vieworg, modelorg);\n\n\t//draw the world sky\n\tR_DrawSky();\n\n\trenderer.DrawWorld();\n\n\tif (r_refdef2.wateralpha == 1) {\n\t\trenderer.DrawWaterSurfaces();\n\t}\n\tR_TraceLeaveNamedRegion();\n}\n\nvoid R_MarkLeaves(void)\n{\n\tbyte *vis;\n\tmnode_t *node;\n\tint i;\n\tbyte solid[MAX_MAP_LEAFS / 8];\n\textern cvar_t r_novis;\n\n\tif (!r_novis.value && r_oldviewleaf == r_viewleaf && r_oldviewleaf2 == r_viewleaf2) {\n\t\t// watervis hack\n\t\treturn;\n\t}\n\n\tr_visframecount++;\n\tr_oldviewleaf = r_viewleaf;\n\n\tif (r_novis.value) {\n\t\tvis = solid;\n\t\tmemset(solid, 0xff, (cl.worldmodel->numleafs + 7) >> 3);\n\t}\n\telse {\n\t\tvis = Mod_LeafPVS(r_viewleaf, cl.worldmodel);\n\n\t\tif (r_viewleaf2) {\n\t\t\tint\t\t\ti, count;\n\t\t\tunsigned\t*src, *dest;\n\n\t\t\t// merge visibility data for two leafs\n\t\t\tcount = (cl.worldmodel->numleafs + 7) >> 3;\n\t\t\tmemcpy(solid, vis, count);\n\t\t\tsrc = (unsigned *)Mod_LeafPVS(r_viewleaf2, cl.worldmodel);\n\t\t\tdest = (unsigned *)solid;\n\t\t\tcount = (count + 3) >> 2;\n\t\t\tfor (i = 0; i < count; i++)\n\t\t\t\t*dest++ |= *src++;\n\t\t\tvis = solid;\n\t\t}\n\t}\n\n\tfor (i = 0; i < cl.worldmodel->numleafs; i++) {\n\t\tif (vis[i >> 3] & (1 << (i & 7))) {\n\t\t\tnode = (mnode_t *)&cl.worldmodel->leafs[i + 1];\n\t\t\tdo {\n\t\t\t\tif (node->visframe == r_visframecount)\n\t\t\t\t\tbreak;\n\t\t\t\tnode->visframe = r_visframecount;\n\t\t\t\tnode = node->parent;\n\t\t\t} while (node);\n\t\t}\n\t}\n}\n\nstatic void R_TurbSurfacesEmitParticleEffects(msurface_t* s)\n{\n#define ESHADER(eshadername)  extern void eshadername (vec3_t org)\n\textern void EmitParticleEffect(msurface_t *fa, void (*fun)(vec3_t nv));\n\textern cvar_t tei_lavafire, tei_slime;\n\n\tESHADER(FuelRodExplosion);//green mushroom explosion\n\tESHADER(ParticleFire);//torch fire\n\tESHADER(ParticleFirePool);//lavapool alike fire\n\tESHADER(VX_DeathEffect);//big white spark explosion\n\tESHADER(VX_GibEffect);//huge red blood cloud\n\tESHADER(VX_DetpackExplosion);//cool huge explosion\n\tESHADER(VX_Implosion);//TODO\n\tESHADER(VX_TeslaCharge);\n\tESHADER(ParticleSlime);\n\tESHADER(ParticleSlimeHarcore);\n\tESHADER(ParticleBloodPool);\n\tESHADER(ParticleSlimeBubbles); //HyperNewbie particles init\n\tESHADER(ParticleSlimeGlow);\n\tESHADER(ParticleSmallerFirePool);\n\tESHADER(ParticleLavaSmokePool);\n\n\tif (!tei_lavafire.integer && !tei_slime.integer) {\n\t\treturn;\n\t}\n\n\t//Tei \"eshaders\".\n\tif (s->texinfo && s->texinfo->texture && s->texinfo->texture->name[0]) {\n\t\tswitch (s->texinfo->texture->name[1]) {\n\t\t\t//Lava\n\t\tcase 'l':\n\t\tcase 'L':\n\t\t{\n\t\t\tswitch (tei_lavafire.integer) {\n\t\t\tcase 1:\n\t\t\t\t//Tei lavafire, normal\n\t\t\t\tEmitParticleEffect(s, ParticleFire);\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\t//Tei lavafire HARDCORE\n\t\t\t\tEmitParticleEffect(s, ParticleFirePool);\n\t\t\t\t//Tei redblood smoke\n\t\t\t\tEmitParticleEffect(s, ParticleBloodPool);\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\t//HyperNewbie's smokefire\n\t\t\t\tEmitParticleEffect(s, ParticleSmallerFirePool);\n\t\t\t\tEmitParticleEffect(s, ParticleLavaSmokePool);\n\t\t\t\tbreak;\n\t\t\tcase 4:\n\t\t\t\tEmitParticleEffect(s, ParticleSmallerFirePool);\n\t\t\t\tEmitParticleEffect(s, ParticleLavaSmokePool);\n\t\t\t\tEmitParticleEffect(s, ParticleLavaSmokePool);\n\t\t\t\tEmitParticleEffect(s, ParticleLavaSmokePool);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 't':\n\t\t\t//Teleport\n\t\t\t//TODO: a cool implosion subtel fx\n\t\t\t//\t\tEmitParticleEffect(s,VX_Implosion);\n\t\t\tbreak;\n\t\tcase 's':\n\t\t{\n\t\t\tswitch (tei_slime.integer) {\n\t\t\tcase 1:\n\t\t\t\tEmitParticleEffect(s, ParticleSlime);\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tEmitParticleEffect(s, ParticleSlimeHarcore);\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\tif (!(rand() % 40)) {\n\t\t\t\t\tEmitParticleEffect(s, ParticleSlimeGlow);\n\t\t\t\t}\n\t\t\t\tif (!(rand() % 40)) {\n\t\t\t\t\tEmitParticleEffect(s, ParticleSlimeBubbles);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 4:\n\t\t\t\tif (!(rand() % 10)) {\n\t\t\t\t\tEmitParticleEffect(s, ParticleSlimeGlow);\n\t\t\t\t}\n\t\t\t\tif (!(rand() % 10)) {\n\t\t\t\t\tEmitParticleEffect(s, ParticleSlimeBubbles);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'w':\n\t\t\t//\tEmitParticleEffect(s,VX_TeslaCharge);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nqbool R_DrawWorldOutlines(void)\n{\n\textern cvar_t gl_outline;\n\n\treturn (gl_outline.integer & 2) && !RuleSets_DisallowModelOutline(NULL);\n}\n"
  },
  {
    "path": "src/r_brushmodel_textures.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// gl_brushmodel_textures.c  -- external texture loading for brush models\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"r_texture.h\"\n#include \"r_brushmodel.h\"\n#include \"image.h\"\n#include \"r_renderer.h\"\n\n/* Some id maps have textures with identical names but different looks.\nWe hardcode a list of names, checksums and alternative names to provide a way\nfor external texture packs to differentiate them. */\nstatic struct {\n\tint md4;\n\tchar *origname, *newname;\n} translate_names[] = {\n\t//\t{ 0x8a010dc0, \"sky4\", \"sky4\" },\n\t{ 0xde688b77, \"sky4\", \"sky1\" },\n\t{ 0x45d110ec, \"metal5_2\", \"metal5_2_arc\" },\n\t{ 0x0d275f87, \"metal5_2\", \"metal5_2_x\" },\n\t{ 0xf8e27da8, \"metal5_4\", \"metal5_4_arc\" },\n\t{ 0xa301c52e, \"metal5_4\", \"metal5_4_double\" },\n\t{ 0xfaa8bf77, \"metal5_8\", \"metal5_8_back\" },\n\t{ 0x88792923, \"metal5_8\", \"metal5_8_rune\" },\n\t{ 0xfe4f9f5a, \"plat_top1\", \"plat_top1_bolt\" },\n\t{ 0x9ac3fccf, \"plat_top1\", \"plat_top1_cable\" },\n\t{ 0, NULL, NULL },\n};\n\nchar *TranslateTextureName(texture_t *tx)\n{\n\tint i;\n\tint checksum = 0;\n\tqbool checksum_done = false;\n\n\tfor (i = 0; translate_names[i].origname; i++) {\n\t\tif (strcmp(tx->name, translate_names[i].origname)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!checksum_done) {\n\t\t\tchecksum = Com_BlockChecksum(tx+1, tx->width*tx->height);\n\t\t\tchecksum_done = true;\n\t\t\t//Com_DPrintf (\"checksum(\\\"%s\\\") = 0x%x\\n\", tx->name, checksum);\n\t\t}\n\t\tif (translate_names[i].md4 == checksum) {\n\t\t\t//Com_DPrintf (\"Translating %s --> %s\\n\", tx->name, translate_names[i].newname);\n\t\t\tassert (strlen(translate_names[i].newname) < sizeof(tx->name));\n\t\t\treturn translate_names[i].newname;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nqbool Mod_LoadExternalTexture(model_t* loadmodel, texture_t *tx, int mode, int brighten_flag)\n{\n\tchar *name, *altname, *mapname, *groupname;\n\tint luma_mode = TEX_LUMA;\n\tint material_width = 0;\n\tint material_height = 0;\n\tint luma_width = 0;\n\tint luma_height = 0;\n\tbyte* material_pixels = NULL;\n\tbyte* luma_pixels = NULL;\n\tchar texture_path[MAX_OSPATH];\n\n\tif (!R_ExternalTexturesEnabled(loadmodel->isworldmodel)) {\n\t\treturn false;\n\t}\n\n\tname = tx->name;\n\taltname = TranslateTextureName(tx);\n\tmapname = TP_MapName();\n\tgroupname = TP_GetMapGroupName(mapname, NULL);\n\ttexture_path[0] = '\\0';\n\n\tif (loadmodel->isworldmodel) {\n\t\tstrlcpy(texture_path, \"textures/\", sizeof(texture_path));\n\t\tstrlcat(texture_path, mapname, sizeof(texture_path));\n\t\tstrlcat(texture_path, \"/\", sizeof(texture_path));\n\t\tstrlcat(texture_path, name, sizeof(texture_path));\n\n\t\tmaterial_pixels = R_LoadImagePixels(texture_path, 0, 0, mode | brighten_flag, &material_width, &material_height);\n\t\tif (!material_pixels && groupname) {\n\t\t\tstrlcpy(texture_path, \"textures/\", sizeof(texture_path));\n\t\t\tstrlcat(texture_path, groupname, sizeof(texture_path));\n\t\t\tstrlcat(texture_path, \"/\", sizeof(texture_path));\n\t\t\tstrlcat(texture_path, name, sizeof(texture_path));\n\n\t\t\tmaterial_pixels = R_LoadImagePixels(texture_path, 0, 0, mode | brighten_flag, &material_width, &material_height);\n\t\t}\n\t}\n\telse {\n\t\tstrlcpy(texture_path, \"textures/bmodels/\", sizeof(texture_path));\n\t\tstrlcat(texture_path, name, sizeof(texture_path));\n\n\t\tmaterial_pixels = R_LoadImagePixels(texture_path, 0, 0, mode | brighten_flag, &material_width, &material_height);\n\t}\n\n\tif (!material_pixels && altname) {\n\t\tstrlcpy(texture_path, \"textures/\", sizeof(texture_path));\n\t\tstrlcat(texture_path, altname, sizeof(texture_path));\n\n\t\tmaterial_pixels = R_LoadImagePixels(texture_path, 0, 0, mode | brighten_flag, &material_width, &material_height);\n\t}\n\n\tif (!material_pixels) {\n\t\tstrlcpy(texture_path, \"textures/\", sizeof(texture_path));\n\t\tstrlcat(texture_path, name, sizeof(texture_path));\n\n\t\tmaterial_pixels = R_LoadImagePixels(texture_path, 0, 0, mode | brighten_flag, &material_width, &material_height);\n\t}\n\n\t// Try and load the corresponding luma\n\tif (material_pixels && !Mod_IsTurbTextureName(loadmodel, name)) {\n\t\tstrlcat(texture_path, \"_luma\", sizeof(texture_path));\n\n\t\tluma_pixels = R_LoadImagePixels(texture_path, 0, 0, mode | luma_mode, &luma_width, &luma_height);\n\t}\n\n\t// resize luma if dimensions don't match material\n\tif (R_LumaTexturesMustMatchDimensions() && material_pixels && luma_pixels) {\n\t\t// make sure sizes match (so they fit in array): the shader still has to read from both alpha-material & luma\n\t\tR_TextureRescaleOverlay(&luma_pixels, &luma_width, &luma_height, material_width, material_height);\n\t}\n\n\t// Load into renderer\n\tif (material_pixels) {\n\t\ttx->gl_texturenum = R_LoadTexturePixels(material_pixels, name, material_width, material_height, mode);\n\t\tif (luma_pixels) {\n\t\t\ttx->fb_texturenum = R_LoadTexturePixels(luma_pixels, va(\"@fb_%s\", name), luma_width, luma_height, mode | luma_mode);\n\t\t\tQ_free(luma_pixels);\n\t\t}\n\n\t\tQ_free(material_pixels);\n\t}\n\n\ttx->isLumaTexture = R_TextureReferenceIsValid(tx->fb_texturenum);\n\n\treturn R_TextureReferenceIsValid(tx->gl_texturenum);\n}\n\nqbool Mod_IsTurbTextureName(model_t* model, const char* name)\n{\n\tif (model->bspversion != HL_BSPVERSION && name[0] == '*') {\n\t\treturn true;\n\t}\n\n\treturn (model->bspversion == HL_BSPVERSION && name[0] == '!');\n}\n\nqbool Mod_IsSkyTextureName(model_t* model, const char* name)\n{\n\treturn (tolower(name[0]) == 's' && tolower(name[1]) == 'k' && tolower(name[2]) == 'y');\n}\n\nqbool Mod_IsAlphaTextureName(model_t* model, const char* name)\n{\n\treturn (name[0] == '{');\n}\n"
  },
  {
    "path": "src/r_brushmodel_warpsurfaces.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// gl_warp.c -- water polygons\n\n#include \"quakedef.h\"\n#include \"vx_stuff.h\"\n#include \"gl_model.h\"\n#include \"glsl/constants.glsl\"\n#include \"r_local.h\"\n#include \"r_brushmodel.h\"\n#include \"utils.h\"\n\nextern model_t *loadmodel;\nstatic msurface_t *warpface;\n\nstatic void R_WarpSurfaceBoundPoly(int numverts, float *verts, vec3_t mins, vec3_t maxs)\n{\n\tint i, j;\n\tfloat *v;\n\n\tmins[0] = mins[1] = mins[2] = BRUSHMODEL_MAX_SURFACE_EXTENTS;\n\tmaxs[0] = maxs[1] = maxs[2] = BRUSHMODEL_MIN_SURFACE_EXTENTS;\n\tv = verts;\n\tfor (i = 0; i < numverts; i++) {\n\t\tfor (j = 0; j < 3; j++, v++) {\n\t\t\tif (i == 0 || *v < mins[j]) {\n\t\t\t\tmins[j] = *v;\n\t\t\t}\n\t\t\tif (i == 0 || *v > maxs[j]) {\n\t\t\t\tmaxs[j] = *v;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void R_WarpSurfaceSubdividePolygon(int numverts, float *verts)\n{\n\tint i, j, k, f, b;\n\tvec3_t mins, maxs, front[64], back[64];\n\tfloat m, *v, dist[64], frac, s, t;\n\tglpoly_t *poly;\n\tfloat subdivide_size;\n\textern cvar_t gl_subdivide_size;\n\n\tif (numverts > 60) {\n\t\tSys_Error(\"numverts = %i\", numverts);\n\t}\n\n\tsubdivide_size = max(1, gl_subdivide_size.value);\n\tR_WarpSurfaceBoundPoly(numverts, verts, mins, maxs);\n\n\tfor (i = 0; i < 3; i++) {\n\t\tm = (mins[i] + maxs[i]) * 0.5;\n\t\tm = subdivide_size * floor(m / subdivide_size + 0.5);\n\t\tif (maxs[i] - m < 8) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (m - mins[i] < 8) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// cut it\n\t\tv = verts + i;\n\t\tfor (j = 0; j < numverts; j++, v += 3) {\n\t\t\tdist[j] = *v - m;\n\t\t}\n\n\t\t// wrap cases\n\t\tdist[j] = dist[0];\n\t\tv -= i;\n\t\tVectorCopy(verts, v);\n\n\t\tf = b = 0;\n\t\tv = verts;\n\t\tfor (j = 0; j < numverts; j++, v += 3) {\n\t\t\tif (dist[j] >= 0) {\n\t\t\t\tVectorCopy(v, front[f]);\n\t\t\t\tf++;\n\t\t\t}\n\t\t\tif (dist[j] <= 0) {\n\t\t\t\tVectorCopy(v, back[b]);\n\t\t\t\tb++;\n\t\t\t}\n\t\t\tif (dist[j] == 0 || dist[j + 1] == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ((dist[j] > 0) != (dist[j + 1] > 0)) {\n\t\t\t\t// clip point\n\t\t\t\tfrac = dist[j] / (dist[j] - dist[j + 1]);\n\t\t\t\tfor (k = 0; k < 3; k++) {\n\t\t\t\t\tfront[f][k] = back[b][k] = v[k] + frac * (v[3 + k] - v[k]);\n\t\t\t\t}\n\t\t\t\tf++;\n\t\t\t\tb++;\n\t\t\t}\n\t\t}\n\t\tR_WarpSurfaceSubdividePolygon(f, front[0]);\n\t\tR_WarpSurfaceSubdividePolygon(b, back[0]);\n\t\treturn;\n\t}\n\n\tpoly = (glpoly_t *)Hunk_AllocName(sizeof(glpoly_t) + (numverts - 4) * VERTEXSIZE * sizeof(float), \"subdivpoly\");\n\tpoly->next = warpface->subdivided;\n\twarpface->subdivided = poly;\n\tpoly->numverts = numverts;\n\tfor (i = 0; i < numverts; i++, verts += 3) {\n\t\tVectorCopy(verts, poly->verts[i]);\n\t\ts = DotProduct(verts, warpface->texinfo->vecs[0]);\n\t\tt = DotProduct(verts, warpface->texinfo->vecs[1]);\n\t\tpoly->verts[i][3] = s / 64.0f;\n\t\tpoly->verts[i][4] = t / 64.0f;\n\t\tpoly->verts[i][5] = 8 * sin(2 * t) / 64.0f;\n\t\tpoly->verts[i][6] = 8 * sin(2 * s) / 64.0f;\n\t\tpoly->verts[i][7] = 8 * cos(2 * t) / 64.0f;\n\t\tpoly->verts[i][8] = 8 * cos(2 * s) / 64.0f;\n\t}\n\tR_BrushModelPolygonToTriangleStrip(poly);\n}\n\n// Breaks a polygon up along axial 64 unit boundaries so that turbulent and sky warps can be done reasonably.\nvoid R_TurbSurfacesSubdivide(msurface_t *fa)\n{\n\tvec3_t verts[64];\n\tint numverts, i, lindex;\n\tfloat *vec;\n\n\twarpface = fa;\n\n\t// convert edges back to a normal polygon\n\tnumverts = 0;\n\tfor (i = 0; i < fa->numedges && numverts < 64; i++) {\n\t\tlindex = loadmodel->surfedges[fa->firstedge + i];\n\n\t\tif (lindex > 0) {\n\t\t\tvec = loadmodel->vertexes[loadmodel->edges[lindex].v[0]].position;\n\t\t}\n\t\telse {\n\t\t\tvec = loadmodel->vertexes[loadmodel->edges[-lindex].v[1]].position;\n\t\t}\n\t\tVectorCopy(vec, verts[numverts]);\n\t\tnumverts++;\n\t}\n\n\tR_WarpSurfaceSubdividePolygon(numverts, verts[0]);\n}\n\n// Just build the gl polys, don't subdivide\nvoid R_SkySurfacesBuildPolys(msurface_t *fa)\n{\n\tvec3_t\t\tverts[64];\n\tint\t\t\tnumverts;\n\tint\t\t\ti;\n\tint\t\t\tlindex;\n\tfloat\t\t*vec;\n\tglpoly_t\t*poly;\n\tfloat\t\t*vert;\n\n\t//\n\t// convert edges back to a normal polygon\n\t//\n\tnumverts = 0;\n\tfor (i = 0; i < fa->numedges; i++) {\n\t\tlindex = loadmodel->surfedges[fa->firstedge + i];\n\n\t\tif (lindex > 0) {\n\t\t\tvec = loadmodel->vertexes[loadmodel->edges[lindex].v[0]].position;\n\t\t}\n\t\telse {\n\t\t\tvec = loadmodel->vertexes[loadmodel->edges[-lindex].v[1]].position;\n\t\t}\n\t\tVectorCopy(vec, verts[numverts]);\n\t\tnumverts++;\n\t}\n\n\tpoly = Hunk_AllocName(sizeof(glpoly_t) + (numverts - 4) * VERTEXSIZE * sizeof(float), \"subdivpoly\");\n\tpoly->next = NULL;\n\tfa->polys = poly;\n\tpoly->numverts = numverts;\n\tvert = verts[0];\n\tfor (i = 0; i < numverts; i++, vert += 3) {\n\t\tVectorCopy(vert, poly->verts[i]);\n\t}\n\tR_BrushModelPolygonToTriangleStrip(poly);\n}\n\nbyte* SurfaceFlatTurbColor(texture_t* texture)\n{\n\textern cvar_t r_telecolor, r_watercolor, r_slimecolor, r_lavacolor, r_skycolor;\n\n\tswitch (texture->turbType)\n\t{\n\tcase TEXTURE_TURB_WATER:\n\t\treturn r_watercolor.color;\n\tcase TEXTURE_TURB_SLIME:\n\t\treturn r_slimecolor.color;\n\tcase TEXTURE_TURB_LAVA:\n\t\treturn r_lavacolor.color;\n\tcase TEXTURE_TURB_TELE:\n\t\treturn r_telecolor.color;\n\tcase TEXTURE_TURB_SKY:\n\t\treturn r_skycolor.color;\n\t}\n\n\treturn (byte *)&texture->flatcolor3ub;\n}\n\n//Tei, add fire to lava\nvoid EmitParticleEffect(msurface_t *fa, void(*fun)(vec3_t nv))\n{\n\tglpoly_t *p;\n\tfloat *v;\n\tint i;\n\tvec3_t nv;\n\n\tfor (p = fa->subdivided; p; p = p->next) {\n\t\tfloat min_x, min_y, max_x, max_y;\n\t\tfor (i = 0, v = p->verts[0]; i < p->numverts; i++, v += VERTEXSIZE) {\n\t\t\tmin_x = i == 0 ? v[0] : min(min_x, v[0]);\n\t\t\tmin_y = i == 0 ? v[1] : min(min_y, v[1]);\n\t\t\tmax_x = i == 0 ? v[0] : max(max_x, v[0]);\n\t\t\tmax_y = i == 0 ? v[1] : max(max_y, v[1]);\n\t\t}\n\n\t\tfor (i = 0, v = p->verts[0]; i < p->numverts; i++, v += VERTEXSIZE) {\n\t\t\tif (rand() % 100 <= (cls.frametime / 0.00416) * 4) {\n\t\t\t\tVectorCopy(v, nv);\n\n\t\t\t\tnv[0] += f_rnd(min_x - v[0], max_x - v[0]);\n\t\t\t\tnv[1] += f_rnd(min_y - v[1], max_y - v[1]);\n\n\t\t\t\tfun(nv);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/r_brushmodel_warpsurfaces_sin.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n0.000000, 0.098172, 0.196330, 0.294458, 0.392541, 0.490566, 0.588517, 0.686378, \n0.784137, 0.881778, 0.979285, 1.076646, 1.173844, 1.270865, 1.367695, 1.464319, \n1.560723, 1.656891, 1.752810, 1.848465, 1.943841, 2.038925, 2.133702, 2.228158, \n2.322277, 2.416048, 2.509454, 2.602482, 2.695119, 2.787349, 2.879160, 2.970538, \n3.061467, 3.151936, 3.241931, 3.331436, 3.420441, 3.508930, 3.596891, 3.684310, \n3.771174, 3.857470, 3.943186, 4.028307, 4.112822, 4.196717, 4.279981, 4.362600, \n4.444562, 4.525854, 4.606466, 4.686383, 4.765594, 4.844088, 4.921853, 4.998876, \n5.075146, 5.150652, 5.225383, 5.299326, 5.372472, 5.444808, 5.516324, 5.587010, \n5.656854, 5.725847, 5.793977, 5.861234, 5.927609, 5.993091, 6.057671, 6.121338, \n6.184084, 6.245898, 6.306771, 6.366695, 6.425660, 6.483658, 6.540679, 6.596714, \n6.651757, 6.705798, 6.758829, 6.810842, 6.861829, 6.911783, 6.960696, 7.008561, \n7.055370, 7.101117, 7.145794, 7.189396, 7.231914, 7.273344, 7.313678, 7.352911, \n7.391036, 7.428049, 7.463942, 7.498712, 7.532353, 7.564859, 7.596225, 7.626448, \n7.655523, 7.683444, 7.710209, 7.735812, 7.760250, 7.783520, 7.805617, 7.826539, \n7.846282, 7.864844, 7.882221, 7.898411, 7.913412, 7.927221, 7.939836, 7.951256, \n7.961478, 7.970501, 7.978324, 7.984945, 7.990364, 7.994579, 7.997591, 7.999398, \n8.000000, 7.999398, 7.997591, 7.994579, 7.990364, 7.984945, 7.978324, 7.970501, \n7.961478, 7.951256, 7.939836, 7.927221, 7.913412, 7.898411, 7.882221, 7.864844, \n7.846282, 7.826539, 7.805617, 7.783520, 7.760250, 7.735812, 7.710209, 7.683444, \n7.655523, 7.626448, 7.596225, 7.564859, 7.532353, 7.498712, 7.463942, 7.428049, \n7.391036, 7.352911, 7.313678, 7.273344, 7.231914, 7.189396, 7.145794, 7.101117, \n7.055370, 7.008561, 6.960696, 6.911783, 6.861829, 6.810842, 6.758829, 6.705798, \n6.651757, 6.596714, 6.540679, 6.483658, 6.425660, 6.366695, 6.306771, 6.245898, \n6.184084, 6.121338, 6.057671, 5.993091, 5.927609, 5.861234, 5.793977, 5.725847, \n5.656854, 5.587010, 5.516324, 5.444808, 5.372472, 5.299326, 5.225383, 5.150652, \n5.075146, 4.998876, 4.921853, 4.844088, 4.765594, 4.686383, 4.606466, 4.525854, \n4.444562, 4.362600, 4.279981, 4.196717, 4.112822, 4.028307, 3.943186, 3.857470, \n3.771174, 3.684310, 3.596891, 3.508930, 3.420441, 3.331436, 3.241931, 3.151936, \n3.061467, 2.970538, 2.879160, 2.787349, 2.695119, 2.602482, 2.509454, 2.416048, \n2.322277, 2.228158, 2.133702, 2.038925, 1.943841, 1.848465, 1.752810, 1.656891, \n1.560723, 1.464319, 1.367695, 1.270865, 1.173844, 1.076646, 0.979285, 0.881778, \n0.784137, 0.686378, 0.588517, 0.490566, 0.392541, 0.294458, 0.196330, 0.098172, \n0.000000, -0.098172, -0.196330, -0.294458, -0.392541, -0.490566, -0.588517, -0.686378, \n-0.784137, -0.881778, -0.979285, -1.076646, -1.173844, -1.270865, -1.367695, -1.464319, \n-1.560723, -1.656891, -1.752810, -1.848465, -1.943841, -2.038925, -2.133702, -2.228158, \n-2.322277, -2.416048, -2.509454, -2.602482, -2.695119, -2.787349, -2.879160, -2.970538, \n-3.061467, -3.151936, -3.241931, -3.331436, -3.420441, -3.508930, -3.596891, -3.684310, \n-3.771174, -3.857470, -3.943186, -4.028307, -4.112822, -4.196717, -4.279981, -4.362600, \n-4.444562, -4.525854, -4.606466, -4.686383, -4.765594, -4.844088, -4.921853, -4.998876, \n-5.075146, -5.150652, -5.225383, -5.299326, -5.372472, -5.444808, -5.516324, -5.587010, \n-5.656854, -5.725847, -5.793977, -5.861234, -5.927609, -5.993091, -6.057671, -6.121338, \n-6.184084, -6.245898, -6.306771, -6.366695, -6.425660, -6.483658, -6.540679, -6.596714, \n-6.651757, -6.705798, -6.758829, -6.810842, -6.861829, -6.911783, -6.960696, -7.008561, \n-7.055370, -7.101117, -7.145794, -7.189396, -7.231914, -7.273344, -7.313678, -7.352911, \n-7.391036, -7.428049, -7.463942, -7.498712, -7.532353, -7.564859, -7.596225, -7.626448, \n-7.655523, -7.683444, -7.710209, -7.735812, -7.760250, -7.783520, -7.805617, -7.826539, \n-7.846282, -7.864844, -7.882221, -7.898411, -7.913412, -7.927221, -7.939836, -7.951256, \n-7.961478, -7.970501, -7.978324, -7.984945, -7.990364, -7.994579, -7.997591, -7.999398, \n-8.000000, -7.999398, -7.997591, -7.994579, -7.990364, -7.984945, -7.978324, -7.970501, \n-7.961478, -7.951256, -7.939836, -7.927221, -7.913412, -7.898411, -7.882221, -7.864844, \n-7.846282, -7.826539, -7.805617, -7.783520, -7.760250, -7.735812, -7.710209, -7.683444, \n-7.655523, -7.626448, -7.596225, -7.564859, -7.532353, -7.498712, -7.463942, -7.428049, \n-7.391036, -7.352911, -7.313678, -7.273344, -7.231914, -7.189396, -7.145794, -7.101117, \n-7.055370, -7.008561, -6.960696, -6.911783, -6.861829, -6.810842, -6.758829, -6.705798, \n-6.651757, -6.596714, -6.540679, -6.483658, -6.425660, -6.366695, -6.306771, -6.245898, \n-6.184084, -6.121338, -6.057671, -5.993091, -5.927609, -5.861234, -5.793977, -5.725847, \n-5.656854, -5.587010, -5.516324, -5.444808, -5.372472, -5.299326, -5.225383, -5.150652, \n-5.075146, -4.998876, -4.921853, -4.844088, -4.765594, -4.686383, -4.606466, -4.525854, \n-4.444562, -4.362600, -4.279981, -4.196717, -4.112822, -4.028307, -3.943186, -3.857470, \n-3.771174, -3.684310, -3.596891, -3.508930, -3.420441, -3.331436, -3.241931, -3.151936, \n-3.061467, -2.970538, -2.879160, -2.787349, -2.695119, -2.602482, -2.509454, -2.416048, \n-2.322277, -2.228158, -2.133702, -2.038925, -1.943841, -1.848465, -1.752810, -1.656891, \n-1.560723, -1.464319, -1.367695, -1.270865, -1.173844, -1.076646, -0.979285, -0.881778, \n-0.784137, -0.686378, -0.588517, -0.490566, -0.392541, -0.294458, -0.196330, -0.098172, "
  },
  {
    "path": "src/r_buffers.c",
    "content": "\n#include \"quakedef.h\"\n#include \"r_local.h\"\n#include \"r_buffers.h\"\n\napi_buffers_t buffers;\n\nvoid R_CreateInstanceVBO(void)\n{\n\tunsigned int values[MAX_STANDARD_ENTITIES];\n\tint i;\n\n\tfor (i = 0; i < MAX_STANDARD_ENTITIES; ++i) {\n\t\tvalues[i] = i;\n\t}\n\n\tbuffers.Create(r_buffer_instance_number, buffertype_vertex, \"instance#\", sizeof(values), values, bufferusage_reuse_many_frames);\n}\n"
  },
  {
    "path": "src/r_buffers.h",
    "content": "\n#ifndef EZQUAKE_R_BUFFERS_HEADER\n#define EZQUAKE_R_BUFFERS_HEADER\n\ntypedef enum {\n\tr_buffer_none,\n\tr_buffer_aliasmodel_vertex_data,\n\tr_buffer_aliasmodel_vertex_ssbo,\n\tr_buffer_aliasmodel_glc_pose_data,\n\tr_buffer_aliasmodel_drawcall_indirect,\n\tr_buffer_aliasmodel_model_data,\n\tr_buffer_brushmodel_vertex_data,\n\tr_buffer_brushmodel_index_data,\n\tr_buffer_brushmodel_surface_data,\n\tr_buffer_brushmodel_lightstyles_ssbo,\n\tr_buffer_brushmodel_surfacestolight_ssbo,\n\tr_buffer_brushmodel_worldsamplers_ssbo,\n\tr_buffer_brushmodel_drawcall_indirect,\n\tr_buffer_brushmodel_drawcall_data,\n\tr_buffer_sprite_vertex_data,\n\tr_buffer_sprite_index_data,\n\tr_buffer_instance_number,\n\tr_buffer_hud_image_vertex_data,\n\tr_buffer_hud_image_index_data,\n\tr_buffer_hud_circle_vertex_data,\n\tr_buffer_postprocess_vertex_data,\n\tr_buffer_frame_constants,\n\n\tr_buffer_count\n} r_buffer_id;\n\n#define R_BufferReferenceIsValid(x) (buffers.IsValid(x))\n#define R_BufferReferencesEqual(x,y) ((x) == (y))\n\n// Buffers\ntypedef enum {\n\tbufferusage_unknown,\n\tbufferusage_once_per_frame,     // filled & used once per frame\n\tbufferusage_reuse_per_frame,    // filled & used many times per frame\n\tbufferusage_reuse_many_frames,  // filled once, expect to use many times over subsequent frames\n\tbufferusage_constant_data,       // filled once, never updated again\n\n\tbufferusage_count\n} bufferusage_t;\n\ntypedef enum {\n\tbuffertype_unknown,\n\tbuffertype_vertex,\n\tbuffertype_index,\n\tbuffertype_storage,\n\tbuffertype_uniform,\n\tbuffertype_indirect,\n\n\tbuffertype_count\n} buffertype_t;\n\ntypedef struct api_buffers_t {\n\tvoid (*InitialiseState)(void);\n\n\tvoid (*StartFrame)(void);\n\tvoid (*EndFrame)(void);\n\tqbool (*FrameReady)(void);\n\n\tsize_t (*Size)(r_buffer_id id);\n\tqbool (*Create)(r_buffer_id id, buffertype_t type, const char* name, int size, void* data, bufferusage_t usage);\n\tuintptr_t (*BufferOffset)(r_buffer_id id);\n\n\tvoid (*Bind)(r_buffer_id id);\n\tvoid (*BindBase)(r_buffer_id id, unsigned int index);\n\tvoid (*BindRange)(r_buffer_id id, unsigned int index, ptrdiff_t offset, int size);\n\tvoid (*UnBind)(buffertype_t type);\n\tvoid (*Update)(r_buffer_id id, int size, void* data);\n\tvoid (*UpdateSection)(r_buffer_id id, ptrdiff_t offset, int size, const void* data);\n\tvoid (*Resize)(r_buffer_id id, int size, void* data);\n\tvoid (*EnsureSize)(r_buffer_id id, int size);\n\n\tqbool (*IsValid)(r_buffer_id id);\n\tvoid (*SetElementArray)(r_buffer_id id);\n\tvoid (*Shutdown)(void);\n\n#ifdef WITH_RENDERING_TRACE\n\tvoid (*PrintState)(FILE* debug_frame_out, int debug_frame_depth);\n#endif\n\n\tqbool supported;\n} api_buffers_t;\n\nextern api_buffers_t buffers;\n\nvoid R_CreateInstanceVBO(void);\n\n#endif // EZQUAKE_R_BUFFERS_HEADER\n"
  },
  {
    "path": "src/r_chaticons.c",
    "content": "/*\nCopyright (C) 1996-2003 Id Software, Inc., A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"glm_texture_arrays.h\"\n#include \"r_sprite3d.h\"\n#include \"r_texture.h\"\n#include \"r_chaticons.h\"\n#include \"r_state.h\"\n#include \"r_matrix.h\"\n\n// Chat icons\ntypedef byte col_t[4]; // FIXME: why 4?\n\ntypedef struct ci_player_s {\n\tvec3_t\t\torg;\n\tcol_t\t\tcolor;\n\tfloat\t\trotangle;\n\tfloat\t\tsize;\n\tbyte\t\ttexindex;\n\tint\t\t\tflags;\n\tfloat       distance;\n\n\tplayer_info_t *player;\n} ci_player_t;\n\ntypedef enum {\n\tcitex_chat,\n\tcitex_afk,\n\tcitex_chat_afk,\n\tnum_citextures,\n} ci_tex_t;\n\n#define\tMAX_CITEX_COMPONENTS\t\t8\ntypedef struct ci_texture_s {\n\ttexture_ref  texnum;\n\ttexture_ref  tex_array;\n\tint          tex_index;\n\tint          components;\n\tfloat        coords[MAX_CITEX_COMPONENTS][4];\n\tfloat        originalCoords[MAX_CITEX_COMPONENTS][4];\n} ci_texture_t;\n\n#define TEXTURE_DETAILS(x) (R_UseModernOpenGL() ? x.tex_array : x.texnum),(x.tex_index)\n\nextern cvar_t r_chaticons_alpha;\n\n/**************************************** chat icon *****************************/\n// qqshka: code is a mixture of autoid and particle engine\n\nstatic ci_player_t ci_clients[MAX_CLIENTS];\nstatic int ci_count;\nstatic ci_texture_t ci_textures[num_citextures];\nstatic qbool ci_initialized = false;\n\n#define FONT_SIZE (256.0)\n\n#define ADD_CICON_TEXTURE(_ptex, _texnum, _texindex, _components, _s1, _t1, _s2, _t2)\t\\\n\tdo {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tci_textures[_ptex].texnum = _texnum;\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tci_textures[_ptex].components = _components;\t\t\t\t\t\t\t\t\t\t\\\n\t\tci_textures[_ptex].coords[_texindex][0] = (_s1 + 1) / FONT_SIZE;\t\t\t\t\t\\\n\t\tci_textures[_ptex].coords[_texindex][1] = (_t1 + 1) / FONT_SIZE;\t\t\t\t\t\\\n\t\tci_textures[_ptex].coords[_texindex][2] = (_s2 - 1) / FONT_SIZE;\t\t\t\t\t\\\n\t\tci_textures[_ptex].coords[_texindex][3] = (_t2 - 1) / FONT_SIZE;\t\t\t\t\t\\\n\t\tmemcpy(ci_textures[_ptex].originalCoords, ci_textures[_ptex].coords, sizeof(ci_textures[_ptex].originalCoords)); \\\n\t} while(0);\n\n// FIXME: Almost duplicate of QMB_LoadTextureSubImage, extracting sprites from an atlas\n//        This version works on different enumeration, and doesn't double-up for pre-multiplied alpha\nstatic void R_LoadChatIconTextureSubImage(ci_tex_t tex, const char* id, const byte* pixels, byte* temp_buffer, int full_width, int full_height, int texIndex, int components, int sub_x, int sub_y, int sub_x2, int sub_y2)\n{\n\tconst int mode = TEX_ALPHA | TEX_COMPLAIN | TEX_NOSCALE;// | TEX_MIPMAP;\n\ttexture_ref tex_ref;\n\tint y, x;\n\tint width = sub_x2 - sub_x;\n\tint height = sub_y2 - sub_y;\n\n\tfor (y = 0; y < height; ++y) {\n\t\tconst byte* source = pixels + ((sub_y + y) * full_width + sub_x) * 4;\n\t\tbyte* target = temp_buffer + y * width * 4;\n\n\t\tfor (x = 0; x < width; ++x, source += 4, target += 4) {\n\t\t\ttarget[0] = (byte)(((int)source[0] * (int)source[3]) / 255.0);\n\t\t\ttarget[1] = (byte)(((int)source[1] * (int)source[3]) / 255.0);\n\t\t\ttarget[2] = (byte)(((int)source[2] * (int)source[3]) / 255.0);\n\t\t}\n\t}\n\n\ttex_ref = R_LoadTexture(id, width, height, temp_buffer, mode, 4);\n\n\tADD_CICON_TEXTURE(tex, tex_ref, 0, 1, 0, 0, FONT_SIZE, FONT_SIZE);\n}\n\nstatic void R_DrawChatIconBillboard(sprite3d_batch_id batch, ci_texture_t* _ptex, ci_player_t* _p, vec3_t _coord[4])\n{\n\tfloat coordinates[4][4];\n\tr_sprite3d_vert_t* vert;\n\tint i;\n\n\tfor (i = 0; i < 4; ++i) {\n\t\tVectorScale(_coord[i], _p->size, coordinates[i]);\n\t\tif (_p->rotangle) {\n\t\t\tR_RotateVector(coordinates[i], _p->rotangle, vpn[0], vpn[1], vpn[2]);\n\t\t}\n\t\tVectorAdd(coordinates[i], _p->org, coordinates[i]);\n\t}\n\n\tvert = R_Sprite3DAddEntry(batch, 4);\n\tif (vert) {\n\t\tR_Sprite3DSetVert(vert++, coordinates[0][0], coordinates[0][1], coordinates[0][2], _ptex->coords[_p->texindex][0], _ptex->coords[_p->texindex][3], _p->color, _ptex->tex_index);\n\t\tR_Sprite3DSetVert(vert++, coordinates[1][0], coordinates[1][1], coordinates[1][2], _ptex->coords[_p->texindex][0], _ptex->coords[_p->texindex][1], _p->color, _ptex->tex_index);\n\t\tR_Sprite3DSetVert(vert++, coordinates[3][0], coordinates[3][1], coordinates[3][2], _ptex->coords[_p->texindex][2], _ptex->coords[_p->texindex][3], _p->color, _ptex->tex_index);\n\t\tR_Sprite3DSetVert(vert++, coordinates[2][0], coordinates[2][1], coordinates[2][2], _ptex->coords[_p->texindex][2], _ptex->coords[_p->texindex][1], _p->color, _ptex->tex_index);\n\t}\n}\n\nstatic int CmpCI_Order(const void *p1, const void *p2)\n{\n\tconst ci_player_t\t*a1 = (ci_player_t *)p1;\n\tconst ci_player_t\t*a2 = (ci_player_t *)p2;\n\tint l1 = a1->distance;\n\tint l2 = a2->distance;\n\n\tif (l1 > l2)\n\t\treturn -1;\n\tif (l1 < l2)\n\t\treturn  1;\n\n\treturn 0;\n}\n\nvoid R_SetupChatIcons(void)\n{\n\tint j, tracknum = -1;\n\tplayer_state_t *state;\n\tplayer_info_t *info;\n\tci_player_t *id;\n\tcentity_t *cent;\n\n\tci_count = 0;\n\n\tif (!bound(0, r_chaticons_alpha.value, 1)) {\n\t\treturn;\n\t}\n\n\tif (cls.state != ca_active || !cl.validsequence || cl.intermission) {\n\t\treturn;\n\t}\n\n\tif (cl.spectator) {\n\t\ttracknum = Cam_TrackNum();\n\t}\n\n\tstate = cl.frames[cl.parsecount & UPDATE_MASK].playerstate;\n\tinfo = cl.players;\n\tcent = &cl_entities[1];\n\n\tfor (j = 0; j < MAX_CLIENTS; j++, info++, state++, cent++) {\n\t\tvec3_t diff;\n\t\tfloat fade = 1;\n\n\t\tif (state->messagenum != cl.parsecount || j == cl.playernum || j == tracknum || info->spectator) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!info->chatflag) {\n\t\t\tcontinue; // user not chatting, so ignore\n\t\t}\n\n\t\tid = &ci_clients[ci_count];\n\t\tid->texindex = 0;\n\t\tid->player = info;\n\n\t\tid->org[0] = cent->lerp_origin[0];\n\t\tid->org[1] = cent->lerp_origin[1];\n\t\tid->org[2] = cent->lerp_origin[2] + 33; // move balloon up a bit\n\t\tVectorSubtract(id->org, r_refdef.vieworg, diff);\n\t\tid->distance = VectorLength(diff);\n\n\t\tif ((!cls.mvdplayback || Cam_TrackNum() >= 0) && cl.racing) {\n\t\t\tif (id->distance < KTX_RACING_PLAYER_MIN_DISTANCE) {\n\t\t\t\tcontinue; // too close, hide indicators\n\t\t\t}\n\t\t\tfade = min(id->distance, KTX_RACING_PLAYER_MAX_DISTANCE) / KTX_RACING_PLAYER_ALPHA_SCALE;\n\t\t}\n\n\t\tid->size = 8; // scale baloon\n\t\tid->rotangle = 5 * sin(2 * r_refdef2.time); // may be set to 0, if u dislike rolling\n\t\tid->color[0] = id->color[1] = id->color[2] = id->color[3] = 255 * bound(0, r_chaticons_alpha.value, 1) * fade; // pre-multiplied alpha\n\n\t\tid->flags = info->chatflag & (CIF_CHAT | CIF_AFK); // get known flags\n\t\tid->flags = (id->flags ? id->flags : CIF_CHAT); // use chat as default if we got some unknown \"chat\" value\n\n\t\tci_count++;\n\t}\n\n\tif (ci_count) {\n\t\t// sort icons so we draw most far to you first\n\t\tqsort((void *)ci_clients, ci_count, sizeof(ci_clients[0]), CmpCI_Order);\n\t}\n}\n\nvoid R_InitChatIcons(void)\n{\n\tint texmode = TEX_ALPHA | TEX_COMPLAIN | TEX_NOSCALE | TEX_MIPMAP;\n\n\tci_initialized = false;\n\n\t{\n\t\tint real_width, real_height;\n\t\tbyte* original;\n\t\tbyte* temp_buffer;\n\n\t\toriginal = R_LoadImagePixels(\"textures/chaticons\", 0, 0, texmode, &real_width, &real_height);\n\t\tif (!original) {\n\t\t\treturn;\n\t\t}\n\n\t\ttemp_buffer = Q_malloc(real_width * real_height * 4);\n\t\tR_LoadChatIconTextureSubImage(citex_chat, \"ci:chat\", original, temp_buffer, real_width, real_height, 0, 1, 0, 0, real_width / 4, real_height / 4);\n\t\tR_LoadChatIconTextureSubImage(citex_afk, \"ci:afk\", original, temp_buffer, real_width, real_height, 0, 1, real_width / 4, 0, real_width / 2, real_height / 4);\n\t\tR_LoadChatIconTextureSubImage(citex_chat_afk, \"ci:chat-afk\", original, temp_buffer, real_width, real_height, 0, 1, 0, 0, real_width / 2, real_height / 4);\n\t\tQ_free(temp_buffer);\n\t\tQ_free(original);\n\t}\n\n\tci_initialized = true;\n}\n\nvoid R_DrawChatIcons(void)\n{\n\tint\ti, flags;\n\tvec3_t billboard[4], billboard2[4], vright_tmp;\n\tci_player_t *p;\n\n\tif (!ci_initialized) {\n\t\treturn;\n\t}\n\n\tif (!bound(0, r_chaticons_alpha.value, 1) || ci_count < 1) {\n\t\treturn;\n\t}\n\n\tVectorAdd(vup, vright, billboard[2]);\n\tVectorSubtract(vright, vup, billboard[3]);\n\tVectorNegate(billboard[2], billboard[0]);\n\tVectorNegate(billboard[3], billboard[1]);\n\n\tVectorScale(vright, 2, vright_tmp);\n\tVectorAdd(vup, vright_tmp, billboard2[2]);\n\tVectorSubtract(vright_tmp, vup, billboard2[3]);\n\tVectorNegate(billboard2[2], billboard2[0]);\n\tVectorNegate(billboard2[3], billboard2[1]);\n\n\tR_Sprite3DInitialiseBatch(SPRITE3D_CHATICON_AFK_CHAT, r_state_chaticon, TEXTURE_DETAILS(ci_textures[citex_chat_afk]), r_primitive_triangle_strip);\n\tR_Sprite3DInitialiseBatch(SPRITE3D_CHATICON_AFK, r_state_chaticon, TEXTURE_DETAILS(ci_textures[citex_afk]), r_primitive_triangle_strip);\n\tR_Sprite3DInitialiseBatch(SPRITE3D_CHATICON_CHAT, r_state_chaticon, TEXTURE_DETAILS(ci_textures[citex_chat]), r_primitive_triangle_strip);\n\n\tfor (i = 0; i < ci_count; i++) {\n\t\tp = &ci_clients[i];\n\t\tflags = p->flags;\n\n\t\tif ((flags & CIF_CHAT) && (flags & CIF_AFK)) {\n\t\t\tR_DrawChatIconBillboard(SPRITE3D_CHATICON_AFK_CHAT, &ci_textures[citex_chat_afk], p, billboard2);\n\t\t}\n\t\telse if (flags & CIF_CHAT) {\n\t\t\tR_DrawChatIconBillboard(SPRITE3D_CHATICON_CHAT, &ci_textures[citex_chat], p, billboard);\n\t\t}\n\t\telse if (flags & CIF_AFK) {\n\t\t\tR_DrawChatIconBillboard(SPRITE3D_CHATICON_AFK, &ci_textures[citex_afk], p, billboard);\n\t\t}\n\t}\n}\n\nvoid R_ImportChatIconTextureArrayReferences(texture_flag_t* texture_flags)\n{\n\tci_tex_t tex;\n\tint i;\n\n\tfor (tex = 0; tex < num_citextures; ++tex) {\n\t\tif (R_TextureReferenceIsValid(ci_textures[tex].texnum)) {\n\t\t\ttexture_array_ref_t* array_ref = &texture_flags[ci_textures[tex].texnum.index].array_ref[TEXTURETYPES_SPRITES];\n\n\t\t\tci_textures[tex].tex_array = array_ref->ref;\n\t\t\tci_textures[tex].tex_index = array_ref->index;\n\t\t\tfor (i = 0; i < ci_textures[tex].components; ++i) {\n\t\t\t\tci_textures[tex].coords[i][0] *= array_ref->scale_s;\n\t\t\t\tci_textures[tex].coords[i][2] *= array_ref->scale_s;\n\t\t\t\tci_textures[tex].coords[i][1] *= array_ref->scale_t;\n\t\t\t\tci_textures[tex].coords[i][3] *= array_ref->scale_t;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid R_FlagChatIconTexturesForArray(texture_flag_t* texture_flags)\n{\n\tci_tex_t tex;\n\n\tfor (tex = 0; tex < num_citextures; ++tex) {\n\t\tif (R_TextureReferenceIsValid(ci_textures[tex].texnum)) {\n\t\t\ttexture_flags[ci_textures[tex].texnum.index].flags |= (1 << TEXTURETYPES_SPRITES);\n\t\t\tmemcpy(ci_textures[tex].coords, ci_textures[tex].originalCoords, sizeof(ci_textures[tex].coords));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/r_chaticons.h",
    "content": "/*\nCopyright (C) 1996-2003 Id Software, Inc., A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifndef EZQUAKE_R_CHATICONS_HEADER\n#define EZQUAKE_R_CHATICONS_HEADER\n\nvoid R_InitChatIcons(void);\nvoid R_SetupChatIcons(void);\nvoid R_DrawChatIcons(void);\n\n#endif // EZQUAKE_R_CHATICONS_HEADER\n"
  },
  {
    "path": "src/r_draw.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: gl_draw.c,v 1.104 2007-10-18 05:28:23 dkure Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"wad.h\"\n#include \"stats_grid.h\"\n#include \"utils.h\"\n#include \"sbar.h\"\n#include \"common_draw.h\"\n#include \"tr_types.h\"\n#include \"gl_framebuffer.h\"\n#include \"r_texture.h\"\n#include \"r_matrix.h\"\n#include \"r_local.h\"\n#include \"r_draw.h\"\n#include \"r_state.h\"\n#include \"r_trace.h\"\n#include \"r_renderer.h\"\n\nvoid CachePics_Init(void);\nvoid Draw_InitCharset(void);\nvoid CachePics_LoadAmmoPics(mpic_t* ibar);\nvoid Draw_SetCrosshairTextMode(qbool enabled);\n\nvoid GLC_DrawDisc(void);\n\nextern cvar_t crosshair, cl_crossx, cl_crossy, crosshaircolor, crosshairsize;\nextern cvar_t con_shift, hud_faderankings;\n\ncvar_t\tscr_conalpha\t\t= {\"scr_conalpha\", \"0.8\"};\ncvar_t\tscr_conback\t\t\t= {\"scr_conback\", \"1\"};\nvoid OnChange_scr_conpicture(cvar_t *, char *, qbool *);\ncvar_t  scr_conpicture      = {\"scr_conpicture\", \"conback\", 0, OnChange_scr_conpicture};\ncvar_t\tscr_menualpha\t\t= {\"scr_menualpha\", \"0.7\"};\ncvar_t\tscr_menudrawhud\t\t= {\"scr_menudrawhud\", \"0\"};\n\ncvar_t  r_smoothtext        = { \"r_smoothtext\",      \"1\" };\ncvar_t  r_smoothcrosshair   = { \"r_smoothcrosshair\", \"1\" };\ncvar_t  r_smoothimages      = { \"r_smoothimages\",    \"1\" };\ncvar_t  r_smoothalphahack   = { \"r_smoothalphahack\", \"0\" };\n\nvoid OnChange_crosshairimage(cvar_t *, char *, qbool *);\nstatic cvar_t crosshairimage          = {\"crosshairimage\", \"\", 0, OnChange_crosshairimage};\n\ncvar_t crosshairalpha                 = {\"crosshairalpha\", \"1\"};\n\nstatic cvar_t crosshairscalemethod         = {\"crosshairscalemethod\", \"0\"};\nstatic cvar_t crosshairscale               = {\"crosshairscale\", \"0\"};\nstatic int    current_crosshair_pixel_size = 0;\n\nmpic_t\t\t\t*draw_disc;\nstatic mpic_t\t*draw_backtile;\n\nstatic mpic_t\tconback;\n\nmpic_t      crosshairtexture_txt;\nmpic_t      crosshairpic;\nmpic_t      crosshairs_builtin[NUMCROSSHAIRS];\n\nstatic byte customcrosshairdata[64];\n\n#define CROSSHAIR_NONE\t0\n#define CROSSHAIR_TXT\t1\n#define CROSSHAIR_IMAGE\t2\nstatic int customcrosshair_loaded = CROSSHAIR_NONE;\n\n#define CH_POINT(x,y,size) ((x) + (y) * size)\n\nstatic void CreateBuiltinCrosshair(byte* data, int size, int format)\n{\n\tint i = 0;\n\tint middle = (size / 2) - 1;\n\n\tmemset(data, 0xff, size * size);\n\n\tswitch (format) {\n\tcase 2:\n\t\t// simple cross, alternating stipple effect\n\t\tfor (i = 0; i < size; i += 2) {\n\t\t\t// vertical\n\t\t\tdata[CH_POINT(middle, i, size)] = 0xfe;\n\n\t\t\t// horizontal\n\t\t\tdata[CH_POINT(i, middle, size)] = 0xfe;\n\t\t}\n\t\tbreak;\n\tcase 3:\n\t\t// small cross in center\n\t\t{\n\t\t\tint length = max(0, size / 8);\n\n\t\t\tdata[CH_POINT(middle, middle, size)] = 0xfe;\n\t\t\tfor (i = 1; i <= length; ++i) {\n\t\t\t\tint p1 = CH_POINT(middle - i, middle, size);\n\t\t\t\tint p2 = CH_POINT(middle + i, middle, size);\n\t\t\t\tint p3 = CH_POINT(middle, middle - i, size);\n\t\t\t\tint p4 = CH_POINT(middle, middle + i, size);\n\n\t\t\t\tdata[p1] = 0xfe;\n\t\t\t\tdata[p2] = 0xfe;\n\t\t\t\tdata[p3] = 0xfe;\n\t\t\t\tdata[p4] = 0xfe;\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase 4:\n\t\t// just a dot (scale to make square then circle?)\n\t\tdata[CH_POINT(middle, middle, size)] = 0xfe;\n\t\tbreak;\n\tcase 5:\n\t\t// diagonals (middle missing)\n\t\tfor (i = 0; i < size; ++i) {\n\t\t\tif (i >= middle - 1 && i <= middle + 1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tdata[CH_POINT(i, i, size)] = 0xfe;\n\t\t\tdata[CH_POINT(size - 1 - i, i, size)] = 0xfe;\n\t\t}\n\t\tbreak;\n\tcase 6:\n\t\t// Supposed to be a smiley face, not converting that...\n\tcase 7:\n\t\t// Square with dot in centre\n\t\tdata[CH_POINT(middle, middle, size)] = 0xfe;\n\t\tfor (i = 2; i <= size - 4; ++i) {\n\t\t\tdata[CH_POINT(i, 0, size)] = 0xfe;\n\t\t\tdata[CH_POINT(0, i, size)] = 0xfe;\n\t\t\tdata[CH_POINT(i, size - 2, size)] = 0xfe;\n\t\t\tdata[CH_POINT(size - 2, i, size)] = 0xfe;\n\t\t}\n\t\tbreak;\n\t}\n}\n\n/*\n* Draw_CopyMPICKeepSize\n* Copy data from src to dst but keep unchanged dst->width and dst->height\n*/\nstatic void Draw_CopyMPICKeepSize(mpic_t *dst, mpic_t *src)\n{\n\tbyte width[sizeof(dst->width)];\n\tbyte height[sizeof(dst->height)];\n\n\t// remember particular fields\n\tmemcpy(width, (byte*)&dst->width, sizeof(width));\n\tmemcpy(height, (byte*)&dst->height, sizeof(height));\n\n\t// bit by bit copy\n\t*dst = *src;\n\n\t// restore fields\n\tmemcpy((byte*)&dst->width, width, sizeof(width));\n\tmemcpy((byte*)&dst->height, height, sizeof(height));\n}\n\nvoid OnChange_scr_conpicture(cvar_t *v, char *s, qbool *cancel)\n{\n\tmpic_t *pic_24bit;\n\n\tif (!s[0])\n\t\treturn;\n\n\tif (!(pic_24bit = R_LoadPicImage(va(\"gfx/%s\", s), \"conback\", 0, 0, 0)))\n\t{\n\t\tCom_Printf(\"Couldn't load image gfx/%s\\n\", s);\n\t\treturn;\n\t}\n\n\tDraw_CopyMPICKeepSize(&conback, pic_24bit);\n\tDraw_AdjustConback();\n}\n\nvoid OnChange_crosshairimage(cvar_t *v, char *s, qbool *cancel)\n{\n\tmpic_t *pic;\n\n\tcustomcrosshair_loaded &= ~CROSSHAIR_IMAGE;\n\n\tif (!s[0])\n\t\treturn;\n\n\tif (!(pic = Draw_CachePicSafe(va(\"crosshairs/%s\", s), false, true)))\n\t{\n\t\tCom_Printf(\"Couldn't load image %s\\n\", s);\n\t\treturn;\n\t}\n\n\tcrosshairpic = *pic;\n\tcustomcrosshair_loaded |= CROSSHAIR_IMAGE;\n\tCachePics_MarkAtlasDirty();\n}\n\nvoid customCrosshair_Init(void)\n{\n\tchar ch;\n\tvfsfile_t *f;\n\tvfserrno_t err;\n\tint i = 0, c;\n\n\tcustomcrosshair_loaded = CROSSHAIR_NONE;\n\tR_TextureReferenceInvalidate(crosshairtexture_txt.texnum);\n\n\tif (!(f = FS_OpenVFS(\"crosshairs/crosshair.txt\", \"rb\", FS_ANY))) {\n\t\treturn;\n\t}\n\n\twhile (i < 64) {\n\t\tVFS_READ(f, &ch, sizeof(char), &err);\n\t\tif (err == VFSERR_EOF) {\n\t\t\tCom_Printf(\"Invalid format in crosshair.txt (Need 64 X's and O's)\\n\");\n\t\t\tVFS_CLOSE(f);\n\t\t\treturn;\n\t\t}\n\t\tc = ch;\n\n\t\tif (isspace(c))\n\t\t\tcontinue;\n\n\t\tif (tolower(c) != 'x' && tolower(c) != 'o') {\n\t\t\tCom_Printf(\"Invalid format in crosshair.txt (Only X's and O's and whitespace permitted)\\n\");\n\t\t\tVFS_CLOSE(f);\n\t\t\treturn;\n\t\t}\n\t\tcustomcrosshairdata[i++] = (c == 'x' || c == 'X') ? 0xfe : 0xff;\n\t}\n\n\tVFS_CLOSE(f);\n\tcrosshairtexture_txt.texnum = R_LoadTexture(\"cross:custom\", 8, 8, customcrosshairdata, TEX_ALPHA, 1);\n\tcrosshairtexture_txt.sl = crosshairtexture_txt.tl = 0;\n\tcrosshairtexture_txt.sh = crosshairtexture_txt.th = 1;\n\tcustomcrosshair_loaded |= CROSSHAIR_TXT;\n\tCachePics_MarkAtlasDirty();\n}\n\nstatic int CrosshairPixelSize(void)\n{\n\t// 0 = 8, 1 = 16 etc\n\treturn pow(2, 3 + (int)bound(0, crosshairscale.integer, 5));\n}\n\nstatic void BuildBuiltinCrosshairs(void)\n{\n\tint i;\n\tchar str[256] = {0};\n\tint crosshair_size = CrosshairPixelSize();\n\tbyte* crosshair_buffer = (byte*)Q_malloc(crosshair_size * crosshair_size);\n\n\tif (!(customcrosshair_loaded & CROSSHAIR_IMAGE)) {\n\t\tmemset(&crosshairpic, 0, sizeof(crosshairpic));\n\t}\n\tfor (i = 0; i < NUMCROSSHAIRS; i++) {\n\t\tsnprintf(str, sizeof(str), \"cross:hardcoded%d\", i);\n\t\tCreateBuiltinCrosshair(crosshair_buffer, crosshair_size, i + 2);\n\t\tcrosshairs_builtin[i].texnum = R_LoadTexture (str, crosshair_size, crosshair_size, crosshair_buffer, TEX_ALPHA, 1);\n\t\tcrosshairs_builtin[i].sl = crosshairs_builtin[i].tl = 0;\n\t\tcrosshairs_builtin[i].sh = crosshairs_builtin[i].th = 1;\n\t\tcrosshairs_builtin[i].height = crosshairs_builtin[i].width = 16;\n\n\t\trenderer.TextureWrapModeClamp(crosshairs_builtin[i].texnum);\n\t}\n\tQ_free(crosshair_buffer);\n\tcurrent_crosshair_pixel_size = crosshair_size;\n\tCachePics_MarkAtlasDirty();\n}\n\nvoid Draw_InitCrosshairs(void)\n{\n\tchar str[256] = {0};\n\n\tBuildBuiltinCrosshairs();\n\tcustomCrosshair_Init(); // safe re-init\n\n\tsnprintf(str, sizeof(str), \"%s\", crosshairimage.string);\n\tCvar_Set(&crosshairimage, str);\n}\n\nfloat overall_alpha = 1.0;\n\nvoid Draw_SetOverallAlpha(float alpha)\n{\n\tclamp(alpha, 0.0, 1.0);\n\toverall_alpha = alpha;\n}\n\nfloat Draw_MultiplyOverallAlpha(float alpha)\n{\n\tfloat old = overall_alpha;\n\n\toverall_alpha *= alpha;\n\n\treturn old;\n}\n\nvoid Draw_EnableScissorRectangle(float x, float y, float width, float height)\n{\n\tfloat resdif_w = (glwidth / (float)vid.conwidth);\n\tfloat resdif_h = (glheight / (float)vid.conheight);\n\n\tR_EnableScissorTest(\n\t\tQ_rint(x * resdif_w),\n\t\tQ_rint((vid.conheight - (y + height)) * resdif_h),\n\t\tQ_rint(width * resdif_w),\n\t\tQ_rint(height * resdif_h)\n\t);\n}\n\nvoid Draw_EnableScissor(float left, float right, float top, float bottom)\n{\n\tDraw_EnableScissorRectangle(left, top, (right - left), (bottom - top));\n}\n\nvoid Draw_DisableScissor(void)\n{\n\tR_DisableScissorTest();\n}\n\n//=============================================================================\n// Support Routines\nwadpic_t wad_pictures[WADPIC_PIC_COUNT];\n\nmpic_t *Draw_CacheWadPic(char *name, int code)\n{\n\tqpic_t *p;\n\tmpic_t *pic, *pic_24bit;\n\twadpic_t* wadpic = NULL;\n\n\tif (code >= 0 && code < WADPIC_PIC_COUNT) {\n\t\twadpic = &wad_pictures[code];\n\t}\n\n\tp = W_GetLumpName (name);\n\tpic = (mpic_t *)p;\n\tif (wadpic) {\n\t\tstrlcpy(wadpic->name, name, sizeof(wadpic->name));\n\t\twadpic->pic = pic;\n\t}\n\n\tif ((pic_24bit = R_LoadPicImage(va(\"textures/wad/%s\", name), name, 0, 0, TEX_ALPHA)) ||\n\t\t(pic_24bit = R_LoadPicImage(va(\"gfx/%s\", name), name, 0, 0, TEX_ALPHA)))\n\t{\n\t\t// Only keep the size info from the lump. The other stuff is copied from the 24 bit image.\n\t\tpic->sh\t\t= pic_24bit->sh;\n\t\tpic->sl\t\t= pic_24bit->sl;\n\t\tpic->texnum = pic_24bit->texnum;\n\t\tpic->th\t\t= pic_24bit->th;\n\t\tpic->tl\t\t= pic_24bit->tl;\n\n\t\tif (code == WADPIC_SB_IBAR) {\n\t\t\tCachePics_LoadAmmoPics(pic);\n\t\t}\n\n\t\treturn pic;\n\t}\n\n\tR_LoadPicTexture(name, pic, p->data);\n\n\tif (code == WADPIC_SB_IBAR) {\n\t\tCachePics_LoadAmmoPics(pic);\n\t}\n\n\treturn pic;\n}\n\n//\n// Loads an image into the cache.\n// Variables:\n//\t\tcrash - Crash the client if loading fails.\n//\t\tonly24bit - Don't fall back to loading the normal 8-bit texture if\n//\t\t\t\t\tloading the 24-bit version fails.\n//\nmpic_t *Draw_CachePicSafe(const char *path, qbool crash, qbool only24bit)\n{\n\tchar stripped_path[MAX_PATH];\n\tchar lmp_path[MAX_PATH];\n\tmpic_t *fpic;\n\tmpic_t *pic_24bit;\n\tqbool lmp_found = false;\n\tqpic_t *dat = NULL;\n\tvfsfile_t *v = NULL;\n\n\t// Check if the picture was already cached, if so inc refcount.\n\tif ((fpic = CachePic_Find(path, true))) {\n\t\treturn fpic;\n\t}\n\n\t// Get the filename without extension.\n\tCOM_StripExtension(path, stripped_path, sizeof(stripped_path));\n\tsnprintf(lmp_path, MAX_PATH, \"%s.lmp\", stripped_path);\n\n\t// Try loading the pic from disk.\n\n\t// Only load the 24-bit version of the picture.\n\tif (only24bit) {\n\t\tif (!(pic_24bit = R_LoadPicImage(path, NULL, 0, 0, TEX_ALPHA))) {\n\t\t\tif(crash) {\n\t\t\t\tSys_Error (\"Draw_CachePicSafe: failed to load %s\", path);\n\t\t\t}\n\t\t\treturn NULL;\n\t\t}\n\n\t\t/* This will make a copy of the pic struct */\n\t\treturn CachePic_Add(path, pic_24bit);\n\t}\n\n\t// Load the \".lmp\" file.\n\tif ((v = FS_OpenVFS(lmp_path, \"rb\", FS_ANY))) {\n\t\tVFS_CLOSE(v);\n\n\t\tif (!(dat = (qpic_t *)FS_LoadTempFile(lmp_path, NULL))) {\n\t\t\tif(crash) {\n\t\t\t\tSys_Error (\"Draw_CachePicSafe: failed to load %s\", lmp_path);\n\t\t\t}\n\t\t\treturn NULL;\n\t\t}\n\t\tlmp_found = true;\n\n\t\t// Make sure the width and height are correct.\n\t\tSwapPic (dat);\n\t}\n\n\t// Try loading the 24-bit picture.\n\t// If that fails load the data for the lmp instead.\n\tif ((pic_24bit = R_LoadPicImage(path, NULL, 0, 0, TEX_ALPHA))) {\n\t\t// Only use the lmp-data if there was one.\n\t\tif (lmp_found) {\n\t\t\tpic_24bit->width = dat->width;\n\t\t\tpic_24bit->height = dat->height;\n\t\t}\n\t\treturn CachePic_Add(path, pic_24bit);\n\t}\n\telse if (dat) {\n\t\tmpic_t tmp = {0};\n\t\ttmp.width = dat->width;\n\t\ttmp.height = dat->height;\n\t\tR_LoadPicTexture(path, &tmp, dat->data);\n\t\treturn CachePic_Add(path, &tmp);\n\t}\n\telse {\n\t\tif(crash) {\n\t\t\tSys_Error (\"Draw_CachePicSafe: failed to load %s\", path);\n\t\t}\n\t\treturn NULL;\n\t}\n}\n\nstatic const char* cache_pic_paths[] = {\n\t\"gfx/pause.lmp\",\n\t\"gfx/loading.lmp\",\n\t\"gfx/box_tl.lmp\",\n\t\"gfx/box_ml.lmp\",\n\t\"gfx/box_bl.lmp\",\n\t\"gfx/box_tm.lmp\",\n\t\"gfx/box_mm.lmp\",\n\t\"gfx/box_mm2.lmp\",\n\t\"gfx/box_bm.lmp\",\n\t\"gfx/box_tr.lmp\",\n\t\"gfx/box_mr.lmp\",\n\t\"gfx/box_br.lmp\",\n\t\"gfx/ttl_main.lmp\",\n\t\"gfx/mainmenu.lmp\",\n\t\"gfx/menudot1.lmp\",\n\t\"gfx/menudot2.lmp\",\n\t\"gfx/menudot3.lmp\",\n\t\"gfx/menudot4.lmp\",\n\t\"gfx/menudot5.lmp\",\n\t\"gfx/menudot6.lmp\",\n\t\"gfx/ttl_sgl.lmp\",\n\t\"gfx/qplaque.lmp\",\n\t\"gfx/sp_menu.lmp\",\n\t\"gfx/p_load.lmp\",\n\t\"gfx/p_save.lmp\",\n\t\"gfx/p_multi.lmp\",\n\t\"gfx/ranking.lmp\",\n\t\"gfx/complete.lmp\",\n\t\"gfx/inter.lmp\",\n\t\"gfx/finale.lmp\"\n};\n\nqbool Draw_KeepOffAtlas(const char* path)\n{\n\tint i;\n\tqbool result = false;\n\n\t// Tiled backgrounds: atlas not suitable for tiling, so keep off atlas\n\tfor (i = CACHEPIC_BOX_TL; i <= CACHEPIC_BOX_BR && !result; ++i) {\n\t\tresult |= !strcmp(path, cache_pic_paths[i]);\n\t}\n\n\t// Single-player & main menu items - take up too much space for no high-performance path\n\tfor (i = CACHEPIC_TTL_MAIN; i <= CACHEPIC_P_MULTI && !result; ++i) {\n\t\tresult |= !strcmp(path, cache_pic_paths[i]);\n\t}\n\n\t// Single-player intermission titles\n\tfor (i = CACHEPIC_COMPLETE; i <= CACHEPIC_FINALE && !result; ++i) {\n\t\tresult |= !strcmp(path, cache_pic_paths[i]);\n\t}\n\n\tR_TraceAPI(\"Draw_KeepOffAtlas(%s) = %s\\n\", path, result ? \"true\" : \"false\");\n\treturn result;\n}\n\nmpic_t *Draw_CachePic(cache_pic_id_t id)\n{\n\tif (id < 0 || id >= CACHEPIC_NUM_OF_PICS) {\n\t\tSys_Error(\"Draw_CachePic(%d) - out of range\", id);\n\t}\n\n\treturn Draw_CachePicSafe(cache_pic_paths[id], true, false);\n}\n\nstatic void Draw_Precache(void)\n{\n\tint i;\n\tfor (i = 0; i < CACHEPIC_NUM_OF_PICS; ++i) {\n\t\tDraw_CachePic(i);\n\t}\n}\n\nvoid Draw_InitConback (void);\n\nvoid Draw_Shutdown(void)\n{\n\tW_FreeWadFile();\n}\n\nvoid Draw_Init (void)\n{\n\textern void HUD_Common_Reset_Group_Pics(void);\n\textern void Draw_Charset_Init(void);\n\n\tDraw_Charset_Init();\n\n\tif (!host_initialized) {\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_CONSOLE);\n\t\tCvar_Register(&scr_conalpha);\n\t\tCvar_Register(&scr_conback);\n\t\tCvar_Register(&scr_conpicture);\n\t\tCvar_Register(&r_smoothtext);\n\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\t\tCvar_Register(&scr_menualpha);\n\t\tCvar_Register(&scr_menudrawhud);\n\t\tCvar_Register(&r_smoothimages);\n\t\tCvar_Register(&r_smoothalphahack);\n\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_CROSSHAIR);\n\t\tCvar_Register(&crosshairimage);\n\t\tCvar_Register(&crosshairalpha);\n\t\tCvar_Register(&crosshairscale);\n\t\tCvar_Register(&crosshairscalemethod);\n\t\tCvar_Register(&r_smoothcrosshair);\n\n\t\tCvar_ResetCurrentGroup();\n\t}\n\n\tdraw_disc = draw_backtile = NULL;\n\n\tW_LoadWadFile(\"gfx.wad\"); // Safe re-init.\n\tCachePics_Shutdown();\n\tHUD_Common_Reset_Group_Pics();\n\n\tR_Texture_Init();  // Probably safe to re-init now.\n\n\t// Clear the scrap, should be called ASAP after textures initialization\n\tCachePics_Init();\n\n\t// Load the console background and the charset by hand, because we need to write the version\n\t// string into the background before turning it into a texture.\n\tDraw_InitCharset(); // Safe re-init.\n\tDraw_InitConback(); // Safe re-init.\n\n\t// Load the crosshair pics\n\tDraw_InitCrosshairs();\n\n\t// Get the other pics we need.\n\tdraw_disc     = Draw_CacheWadPic(\"disc\", WADPIC_DISC);\n\tdraw_backtile = Draw_CacheWadPic(\"backtile\", WADPIC_BACKTILE);\n\n\tDraw_Precache();\n}\n\nqbool CL_MultiviewGetCrosshairCoordinates(qbool use_screen_coords, float* cross_x, float* cross_y, qbool* half_size);\n\nvoid Draw_Crosshair (void)\n{\n\tfloat x = 0.0, y = 0.0, ofs1, ofs2, sh, th, sl, tl;\n\tbyte col[4];\n\textern vrect_t scr_vrect;\n\tfloat crosshair_scale = (crosshairscalemethod.integer ? 1 : ((float)glwidth / 320));\n\tint crosshair_pixel_size = CrosshairPixelSize();\n\tqbool half_size = false;\n\n\tif (current_crosshair_pixel_size != crosshair_pixel_size) {\n\t\tBuildBuiltinCrosshairs();\n\t}\n\n\tif ((crosshair.value >= 2 && crosshair.value <= NUMCROSSHAIRS + 1) ||\n\t\t((customcrosshair_loaded & CROSSHAIR_TXT) && crosshair.value == 1) ||\n\t\t(customcrosshair_loaded & CROSSHAIR_IMAGE)) {\n\t\ttexture_ref texnum;\n\t\tint width2d = VID_RenderWidth2D();\n\t\tint height2d = VID_RenderHeight2D();\n\n\t\tif (!crosshairalpha.value) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!CL_MultiviewGetCrosshairCoordinates(true, &x, &y, &half_size)) {\n\t\t\treturn;\n\t\t}\n\n\t\tR_OrthographicProjection(0, width2d, height2d, 0, -99999, 99999);\n\n\t\tx += (crosshairscalemethod.integer ? 1 : (float)width2d / vid.width) * cl_crossx.value;\n\t\ty += (crosshairscalemethod.integer ? 1 : (float)height2d / vid.height) * cl_crossy.value;\n\n\t\tcol[0] = crosshaircolor.color[0];\n\t\tcol[1] = crosshaircolor.color[1];\n\t\tcol[2] = crosshaircolor.color[2];\n\t\tcol[3] = bound(0, crosshairalpha.value, 1) * 255;\n\n\t\tif (customcrosshair_loaded & CROSSHAIR_IMAGE) {\n\t\t\ttexnum = crosshairpic.texnum;\n\t\t\tofs1 = (crosshairpic.width * 0.5f - 0.5f) * bound(0, crosshairsize.value, 20);\n\t\t\tofs2 = (crosshairpic.height * 0.5f + 0.5f) * bound(0, crosshairsize.value, 20);\n\n\t\t\tsh = crosshairpic.sh;\n\t\t\tsl = crosshairpic.sl;\n\t\t\tth = crosshairpic.th;\n\t\t\ttl = crosshairpic.tl;\n\t\t}\n\t\telse {\n\t\t\tmpic_t* pic = ((crosshair.value >= 2) ? &crosshairs_builtin[(int)crosshair.value - 2] : &crosshairtexture_txt);\n\n\t\t\ttexnum = pic->texnum;\n\t\t\tofs1 = (crosshair_pixel_size * 0.5f - 0.5f) * crosshair_scale * bound(0, crosshairsize.value, 20);\n\t\t\tofs2 = (crosshair_pixel_size * 0.5f + 0.5f) * crosshair_scale * bound(0, crosshairsize.value, 20);\n\n\t\t\tsh = pic->sh;\n\t\t\tsl = pic->sl;\n\t\t\tth = pic->th;\n\t\t\ttl = pic->tl;\n\t\t}\n\n\t\tif (half_size) {\n\t\t\tofs1 *= 0.5f;\n\t\t\tofs2 *= 0.5f;\n\t\t}\n\n\t\tR_DrawImage(x - ofs1, y - ofs1, ofs1 + ofs2, ofs1 + ofs2, sl, tl, sh - sl, th - tl, col, false, texnum, false, true);\n\n\t\tR_OrthographicProjection(0, vid.width, vid.height, 0, -99999, 99999);\n\t}\n\telse if (crosshair.value) {\n\t\t// Multiview\n\t\tDraw_SetCrosshairTextMode(true);\n\t\tif (CL_MultiviewInsetEnabled()) {\n\t\t\tif (CL_MultiviewInsetView()) {\n\t\t\t\tint width2d = VID_RenderWidth2D();\n\t\t\t\tint height2d = VID_RenderHeight2D();\n\n\t\t\t\tif (!CL_MultiviewGetCrosshairCoordinates(true, &x, &y, &half_size)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// convert from 3d to 2d\n\t\t\t\tx = (x * vid.width) / width2d;\n\t\t\t\ty = (y * vid.height) / height2d;\n\n\t\t\t\t// x = vid.width - (vid.width / 3) / 2 - 4\n\t\t\t\t// y = (vid.height / 3) / 2 - 2,\n\t\t\t\tDraw_Character(x - 4, y - 4, '+');\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_Character(scr_vrect.x + scr_vrect.width / 2 - 4 + cl_crossx.value, scr_vrect.y + scr_vrect.height / 2 - 4 + cl_crossy.value, '+');\n\t\t\t}\n\t\t}\n\t\telse if (CL_MultiviewActiveViews() == 2) {\n\t\t\tDraw_Character(vid.width / 2 - 4, vid.height * 3 / 4 - 2, '+');\n\t\t\tDraw_Character(vid.width / 2 - 4, vid.height / 4 - 2, '+');\n\t\t}\n\t\telse if (CL_MultiviewActiveViews() == 3) {\n\t\t\tDraw_Character(vid.width / 2 - 4, vid.height / 4 - 2, '+');\n\t\t\tDraw_Character(vid.width / 4 - 4, vid.height / 2 + vid.height / 4 - 2, '+');\n\t\t\tDraw_Character(vid.width / 2 + vid.width / 4 - 4, vid.height / 2 + vid.height / 4 - 2, '+');\n\t\t}\n\t\telse if (CL_MultiviewActiveViews() == 4) {\n\t\t\tDraw_Character(vid.width / 4 - 4, vid.height / 4 - 2, '+');\n\t\t\tDraw_Character(vid.width / 2 + vid.width / 4 - 4, vid.height / 4 - 2, '+');\n\t\t\tDraw_Character(vid.width / 4 - 4, vid.height / 2 + vid.height / 4 - 2, '+');\n\t\t\tDraw_Character(vid.width / 2 + vid.width / 4 - 4, vid.height / 2 + vid.height / 4 - 2, '+');\n\t\t}\n\t\telse {\n\t\t\tDraw_Character(scr_vrect.x + scr_vrect.width / 2 - 4 + cl_crossx.value, scr_vrect.y + scr_vrect.height / 2 - 4 + cl_crossy.value, '+');\n\t\t}\n\t\tDraw_SetCrosshairTextMode(false);\n\t}\n}\n\nvoid Draw_TextBox(float x, float y, int width, int lines)\n{\n\tmpic_t *p;\n\tint cx, cy, n;\n\n\t// draw left side\n\tcx = x;\n\tcy = y;\n\tp = Draw_CachePic (CACHEPIC_BOX_TL);\n\tDraw_TransPic (cx, cy, p);\n\tp = Draw_CachePic (CACHEPIC_BOX_ML);\n\tfor (n = 0; n < lines; n++)\n\t{\n\t\tcy += 8;\n\t\tDraw_TransPic (cx, cy, p);\n\t}\n\n\tp = Draw_CachePic (CACHEPIC_BOX_BL);\n\tDraw_TransPic (cx, cy+8, p);\n\n\t// Draw middle.\n\tcx += 8;\n\twhile (width > 0)\n\t{\n\t\tcy = y;\n\t\tp = Draw_CachePic (CACHEPIC_BOX_TM);\n\t\tDraw_TransPic (cx, cy, p);\n\t\tp = Draw_CachePic (CACHEPIC_BOX_MM);\n\n\t\tfor (n = 0; n < lines; n++)\n\t\t{\n\t\t\tcy += 8;\n\t\t\tif (n == 1)\n\t\t\t\tp = Draw_CachePic (CACHEPIC_BOX_MM2);\n\t\t\tDraw_TransPic (cx, cy, p);\n\t\t}\n\n\t\tp = Draw_CachePic (CACHEPIC_BOX_BM);\n\t\tDraw_TransPic (cx, cy+8, p);\n\t\twidth -= 2;\n\t\tcx += 16;\n\t}\n\n\t// Draw right side.\n\tcy = y;\n\tp = Draw_CachePic (CACHEPIC_BOX_TR);\n\tDraw_TransPic (cx, cy, p);\n\tp = Draw_CachePic (CACHEPIC_BOX_MR);\n\n\tfor (n = 0; n < lines; n++)\n\t{\n\t\tcy += 8;\n\t\tDraw_TransPic (cx, cy, p);\n\t}\n\n\tp = Draw_CachePic (CACHEPIC_BOX_BR);\n\tDraw_TransPic (cx, cy+8, p);\n}\n\n// This repeats a 64 * 64 tile graphic to fill the screen around a sized down refresh window.\nvoid Draw_TileClear(int x, int y, int w, int h)\n{\n\tbyte white[4] = { 255, 255, 255, 255 };\n\n\tR_DrawImage(x, y, w, h, x / 64.0, y / 64.0, w / 64.0, h / 64.0, white, false, draw_backtile->texnum, false, false);\n}\n\nvoid Draw_AlphaRectangleRGB(float x, float y, float w, float h, float thickness, qbool fill, color_t color)\n{\n\tbyte bytecolor[4];\n\n\t// Is alpha 0?\n\tif ((byte)(color >> 24 & 0xFF) == 0) {\n\t\treturn;\n\t}\n\n\tCOLOR_TO_RGBA(color, bytecolor);\n\tthickness = max(0, thickness);\n\n\tR_DrawAlphaRectangleRGB(x, y, w, h, thickness, fill, bytecolor);\n}\n\nvoid Draw_AlphaRectangle (int x, int y, int w, int h, byte c, float thickness, qbool fill, float alpha)\n{\n\tDraw_AlphaRectangleRGB(x, y, w, h, thickness, fill,\n\t\tRGBA_TO_COLOR(host_basepal[c * 3], host_basepal[c * 3 + 1], host_basepal[c * 3 + 2], (byte)(alpha * 255)));\n}\n\nvoid Draw_AlphaFillRGB(float x, float y, float w, float h, color_t color)\n{\n\tDraw_AlphaRectangleRGB(x, y, w, h, 1, true, color);\n}\n\nvoid Draw_AlphaFill(float x, float y, float w, float h, byte c, float alpha)\n{\n\tDraw_AlphaRectangle(x, y, w, h, c, 1, true, alpha);\n}\n\nvoid Draw_Fill (int x, int y, int w, int h, byte c)\n{\n\tDraw_AlphaFill(x, y, w, h, c, 1);\n}\n\nvoid Draw_AlphaLineRGB (float x_start, float y_start, float x_end, float y_end, float thickness, color_t color)\n{\n\tbyte bytecolor[4];\n\n\tCOLOR_TO_RGBA_PREMULT(color, bytecolor);\n\n\tR_Draw_LineRGB(thickness, bytecolor, x_start, y_start, x_end, y_end);\n}\n\nvoid Draw_AlphaLine (float x_start, float y_start, float x_end, float y_end, float thickness, byte c, float alpha)\n{\n\tDraw_AlphaLineRGB (x_start, y_start, x_end, y_end, thickness,\n\t\tRGBA_TO_COLOR(host_basepal[c * 3], host_basepal[c * 3 + 1], host_basepal[c * 3 + 2], 255));\n}\n\nvoid Draw_Polygon(int x, int y, vec3_t *vertices, int num_vertices, color_t color)\n{\n\tR_Draw_Polygon(x, y, vertices, num_vertices, color);\n}\n\nstatic void Draw_AlphaPieSliceRGB (int x, int y, float radius, float startangle, float endangle, float thickness, qbool fill, color_t color)\n{\n\tR_Draw_AlphaPieSliceRGB(x, y, radius, startangle, endangle, thickness, fill, color);\n}\n\nvoid Draw_AlphaPieSlice (int x, int y, float radius, float startangle, float endangle, float thickness, qbool fill, byte c, float alpha)\n{\n\tDraw_AlphaPieSliceRGB (x, y, radius, startangle, endangle, thickness, fill,\n\t\tRGBA_TO_COLOR(host_basepal[c * 3], host_basepal[c * 3 + 1], host_basepal[c * 3 + 2], (byte)Q_rint(255 * alpha)));\n}\n\nvoid Draw_AlphaCircleRGB(float x, float y, float radius, float thickness, qbool fill, color_t color)\n{\n\tDraw_AlphaPieSliceRGB (x, y, radius, 0, 2 * M_PI, thickness, fill, color);\n}\n\nvoid Draw_AlphaCircle(float x, float y, float radius, float thickness, qbool fill, byte c, float alpha)\n{\n\tDraw_AlphaPieSlice (x, y, radius, 0, 2 * M_PI, thickness, fill, c, alpha);\n}\n\nvoid Draw_AlphaCircleOutlineRGB(float x, float y, float radius, float thickness, color_t color)\n{\n\tDraw_AlphaCircleRGB(x, y, radius, thickness, false, color);\n}\n\nvoid Draw_AlphaCircleOutline(float x, float y, float radius, float thickness, byte color, float alpha)\n{\n\tDraw_AlphaCircle(x, y, radius, thickness, false, color, alpha);\n}\n\nvoid Draw_AlphaCircleFillRGB(float x, float y, float radius, color_t color)\n{\n\tDraw_AlphaCircleRGB(x, y, radius, 1.0, true, color);\n}\n\nvoid Draw_AlphaCircleFill(float x, float y, float radius, byte color, float alpha)\n{\n\tDraw_AlphaCircle(x, y, radius, 1.0, true, color, alpha);\n}\n\n//\n// SCALE versions of some functions\n//\n\n//=============================================================================\n// Draw picture functions\n//=============================================================================\nvoid Draw_SAlphaSubPic2(float x, float y, mpic_t *pic, int src_x, int src_y, int src_width, int src_height, float scale_x, float scale_y, float alpha)\n{\n\tfloat newsl, newtl, newsh, newth;\n    float oldglwidth, oldglheight;\n\n    oldglwidth = pic->sh - pic->sl;\n    oldglheight = pic->th - pic->tl;\n\n    newsl = pic->sl + (src_x * oldglwidth) / (float)pic->width;\n    newsh = newsl + (src_width * oldglwidth) / (float)pic->width;\n\n    newtl = pic->tl + (src_y * oldglheight) / (float)pic->height;\n    newth = newtl + (src_height * oldglheight) / (float)pic->height;\n\n\talpha *= overall_alpha;\n\n\tR_Draw_SAlphaSubPic2(x, y, pic, src_width, src_height, newsl, newtl, newsh, newth, scale_x, scale_y, alpha);\n}\n\nvoid Draw_SAlphaSubPic(float x, float y, mpic_t *pic, int src_x, int src_y, int src_width, int src_height, float scale, float alpha)\n{\n\tDraw_SAlphaSubPic2(x, y, pic, src_x, src_y, src_width, src_height, scale, scale, alpha);\n}\n\nvoid Draw_SSubPic(float x, float y, mpic_t *gl, int srcx, int srcy, int width, int height, float scale)\n{\n\tDraw_SAlphaSubPic(x, y, gl, srcx, srcy, width, height, scale, 1);\n}\n\nvoid Draw_AlphaSubPic(float x, float y, mpic_t *pic, int srcx, int srcy, int width, int height, float alpha)\n{\n\tDraw_SAlphaSubPic(x, y, pic, srcx, srcy, width, height, 1, alpha);\n}\n\nvoid Draw_SubPic(float x, float y, mpic_t *pic, int srcx, int srcy, int width, int height)\n{\n\tDraw_SAlphaSubPic(x, y, pic, srcx, srcy, width, height, 1, 1);\n}\n\nvoid Draw_AlphaPic(float x, float y, mpic_t *pic, float alpha)\n{\n\tDraw_SAlphaSubPic(x , y, pic, 0, 0, pic->width, pic->height, 1, alpha);\n}\n\nvoid Draw_SAlphaPic(float x, float y, mpic_t *gl, float alpha, float scale)\n{\n\tDraw_SAlphaSubPic(x ,y , gl, 0, 0, gl->width, gl->height, scale, alpha);\n}\n\nvoid Draw_SPic(float x, float y, mpic_t *gl, float scale)\n{\n\tDraw_SAlphaSubPic (x, y, gl, 0, 0, gl->width, gl->height, scale, 1.0);\n}\n\nvoid Draw_FitPic(float x, float y, int fit_width, int fit_height, mpic_t *gl)\n{\n    float sw, sh;\n    sw = (float) fit_width / (float) gl->width;\n    sh = (float) fit_height / (float) gl->height;\n    Draw_SPic(x, y, gl, min(sw, sh));\n}\n\nvoid Draw_FitPicAlpha(float x, float y, int fit_width, int fit_height, mpic_t *gl, float alpha)\n{\n\tfloat sw, sh;\n\tsw = (float) fit_width / (float) gl->width;\n\tsh = (float) fit_height / (float) gl->height;\n\tDraw_SAlphaPic(x, y, gl, alpha, min(sw, sh));\n}\n\nvoid Draw_FitPicAlphaCenter(float x, float y, int fit_width, int fit_height, mpic_t* gl, float alpha)\n{\n\tfloat sw, sh, scale;\n\tsw = (float)fit_width / (float)gl->width;\n\tsh = (float)fit_height / (float)gl->height;\n\tscale = min(sw, sh);\n\tDraw_SAlphaPic(x + (fit_width - scale * gl->width) / 2.0f, y + (fit_height - scale * gl->height) / 2.0f, gl, alpha, scale);\n}\n\nvoid Draw_STransPic(float x, float y, mpic_t *pic, float scale)\n{\n    Draw_SPic(x, y, pic, scale);\n}\n\nvoid Draw_Pic(float x, float y, mpic_t *pic)\n{\n\tDraw_SAlphaSubPic(x, y, pic, 0, 0, pic->width, pic->height, 1, 1);\n}\n\nvoid Draw_TransPic(float x, float y, mpic_t *pic)\n{\n\tDraw_Pic(x, y, pic);\n}\n\nstatic char last_mapname[MAX_QPATH] = {0};\nstatic mpic_t *last_lvlshot = NULL;\n\n// If conwidth or conheight changes, adjust conback sizes too.\nvoid Draw_AdjustConback(void)\n{\n\tconback.width  = vid.conwidth;\n\tconback.height = vid.conheight;\n\n\tif (last_lvlshot) {\n\t\t// Resize.\n\t\tlast_lvlshot->width = conback.width;\n\t\tlast_lvlshot->height = conback.height;\n\t}\n}\n\nstatic void Draw_DeleteOldLevelshot(mpic_t* pic)\n{\n\tif (pic && R_TextureReferenceIsValid(pic->texnum)) {\n\t\tR_DeleteTexture(&pic->texnum);\n\t\tif (!CachePic_RemoveByPic(pic)) {\n\t\t\tR_TextureReferenceInvalidate(pic->texnum);\n\t\t}\n\t}\n}\n\nvoid Draw_ClearConback(void)\n{\n\tlast_lvlshot = NULL;\n\tlast_mapname[0] = 0;\n}\n\nvoid Draw_InitConback(void)\n{\n\tqpic_t *cb;\n\tmpic_t *pic_24bit;\n\n\t// Level shots init. It's cache based so don't free!\n\t// Expect the cache to be wiped thus render the old data invalid\n\tDraw_DeleteOldLevelshot(last_lvlshot);\n\tDraw_ClearConback();\n\n\tif (!glConfig.initialized) {\n\t\treturn;\n\t}\n\n\tif (!(cb = (qpic_t *)FS_LoadHeapFile(\"gfx/conback.lmp\", NULL))) {\n\t\tSys_Error(\"Couldn't load gfx/conback.lmp\");\n\t\treturn;\n\t}\n\tSwapPic (cb);\n\n\tif (cb->width != 320 || cb->height != 200) {\n\t\tSys_Error(\"Draw_InitConback: conback.lmp size is not 320x200\");\n\t}\n\n\tif ((pic_24bit = R_LoadPicImage(va(\"gfx/%s\", scr_conpicture.string), \"conback\", 0, 0, TEX_ALPHA))) {\n\t\tDraw_CopyMPICKeepSize(&conback, pic_24bit);\n\t}\n\telse {\n\t\tconback.width = cb->width;\n\t\tconback.height = cb->height;\n\t\tR_LoadPicTexture(\"conback\", &conback, cb->data);\n\t}\n\n\tDraw_AdjustConback();\n\n\t// Free loaded console.\n\tQ_free(cb);\n}\n\nvoid Draw_ConsoleBackground(int lines)\n{\n\tmpic_t *lvlshot = NULL;\n\tfloat alpha = (SCR_NEED_CONSOLE_BACKGROUND ? 1 : bound(0, scr_conalpha.value, 1)) * overall_alpha;\n\n\tif (host_mapname.string[0]\t\t\t\t\t\t\t\t\t\t\t// We have mapname.\n\t\t && (    scr_conback.value == 2\t\t\t\t\t\t\t\t\t// Always per level conback.\n\t\t\t || (scr_conback.value == 1 && SCR_NEED_CONSOLE_BACKGROUND) // Only at load time.\n\t\t\t))\n\t{\n\t\t// Here we limit call Draw_CachePicSafe() once per level,\n\t\t// because if image not found Draw_CachePicSafe() will try open image again each frame, that cause HDD lag.\n\t\tif (strncmp(host_mapname.string, last_mapname, sizeof(last_mapname))) {\n\t\t\tchar name[MAX_QPATH];\n\t\t\tmpic_t* old_levelshot = last_lvlshot;\n\n\t\t\tsnprintf(name, sizeof(name), \"textures/levelshots/%s.xxx\", host_mapname.string);\n\t\t\tif ((last_lvlshot = Draw_CachePicSafe(name, false, true))) {\n\t\t\t\t// Resize.\n\t\t\t\tlast_lvlshot->width  = conback.width;\n\t\t\t\tlast_lvlshot->height = conback.height;\n\t\t\t}\n\t\t\tif (last_lvlshot != old_levelshot) {\n\t\t\t\tDraw_DeleteOldLevelshot(old_levelshot);\n\t\t\t}\n\n\t\t\tstrlcpy(last_mapname, host_mapname.string, sizeof(last_mapname)); // Save.\n\t\t}\n\n\t\tlvlshot = last_lvlshot;\n\t}\n\n\tif (alpha) {\n\t\tint con_shift_value = cls.state == ca_active ? con_shift.value : 0;\n\n\t\tDraw_AlphaPic(0, (lines - vid.height) + con_shift_value, lvlshot ? lvlshot : &conback, alpha);\n\t}\n}\n\nvoid Draw_FadeScreen(float alpha)\n{\n\talpha = bound(0, alpha, 1) * overall_alpha;\n\tif (!alpha) {\n\t\treturn;\n\t}\n\n\tR_Draw_FadeScreen(alpha);\n\n\tSbar_Changed();\n}\n\n//=============================================================================\n// Draws the little blue disc in the corner of the screen.\n// Call before beginning any disc IO.\nvoid Draw_BeginDisc (void)\n{\n\textern cvar_t r_drawdisc;\n\n\tif (!draw_disc || !r_drawdisc.integer) {\n\t\treturn;\n\t}\n\n\t// Intel cards, most notably Intel 915GM/910GML has problems with\n\t// writing directly to the front buffer and then flipping the back buffer,\n\t// so don't draw the I/O disc on those cards, it will cause the console\n\t// to flicker.\n\t//\n\t// From Intels dev network:\n\t// \"Using two dimensional data within a 3D scene is sometimes used to render\n\t// objects like scoreboards and road signs. When that blit request is sent \n\t// to or from a buffer, the data contained within must be updated, causing \n\t// a pipeline flush and disabling Zone Rendering. One easy way to generate \n\t// the same effect is to use a quad or a billboard that is aligned to the \n\t// view frustrum. Similarly, a flip operation while rendering to a back buffer \n\t// will cause serialization. Be sure you are done altering the back buffer\n\t// before you flip.\n#ifndef __APPLE__\n\tif (glConfig.hardwareType == GLHW_INTEL) {\n\t\treturn;\n\t}\n#endif\n\n\trenderer.DrawDisc();\n}\n\n// Erases the disc icon.\n// Call after completing any disc IO\nvoid Draw_EndDisc(void)\n{\n}\n\n//\n// Changes the projection to orthogonal (2D drawing).\n//\nvoid R_Set2D(void)\n{\n\trenderer.Begin2DRendering();\n\tR_IdentityModelView();\n\tR_OrthographicProjection(0, vid.width, vid.height, 0, -99999, 99999);\n\tR_TraceResetRegion(false);\n}\n\nvoid Draw_2dAlphaTexture(float x, float y, float width, float height, texture_ref texture_num, float alpha)\n{\n\tmpic_t pic;\n\n\tpic.height = height;\n\tpic.width = width;\n\tpic.th = 1;\n\tpic.tl = 0;\n\tpic.sh = 1;\n\tpic.sl = 0;\n\tpic.texnum = texture_num;\n\n\tDraw_AlphaPic(x, y, &pic, alpha);\n}\n\nqbool Draw_IsConsoleBackground(mpic_t* pic)\n{\n\treturn pic == &conback || pic == last_lvlshot;\n}\n"
  },
  {
    "path": "src/r_draw.h",
    "content": "\n#ifndef EZQUAKE_R_DRAW_HEADER\n#define EZQUAKE_R_DRAW_HEADER\n\nvoid R_Draw_SAlphaSubPic2(float x, float y, mpic_t *pic, int src_width, int src_height, float newsl, float newtl, float newsh, float newth, float scale_x, float scale_y, float alpha);\nvoid R_Draw_AlphaPieSliceRGB(float x, float y, float radius, float startangle, float endangle, float thickness, qbool fill, color_t color);\nvoid R_Draw_LineRGB(float thickness, byte* color, float x_start, float y_start, float x_end, float y_end);\nvoid R_DrawImage(float x, float y, float width, float height, float tex_s, float tex_t, float tex_width, float tex_height, byte* color, qbool alpha_test, texture_ref texnum, qbool isText, qbool isCrosshair);\nvoid R_DrawAlphaRectangleRGB(int x, int y, int w, int h, float thickness, qbool fill, byte* bytecolor);\nvoid R_Draw_FadeScreen(float alpha);\nfloat R_Draw_CharacterBase(float x, float y, wchar num, float scale, qbool apply_overall_alpha, qbool bigchar, qbool gl_statechange, qbool proportional);\nvoid R_Draw_ResetCharGLState(void);\nvoid R_Draw_SetColor(byte* rgba);\nvoid R_Draw_StringBase_StartString(float x, float y, float scale);\nvoid R_Cache2DMatrix(void);\nvoid R_UndoLastCharacter(void);\nvoid R_Draw_Polygon(float x, float y, vec3_t *vertices, int num_vertices, color_t color);\n\n#endif // EZQUAKE_R_DRAW_HEADER\n"
  },
  {
    "path": "src/r_draw_charset.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: gl_draw.c,v 1.104 2007-10-18 05:28:23 dkure Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"wad.h\"\n#include \"stats_grid.h\"\n#include \"utils.h\"\n#include \"sbar.h\"\n#include \"common_draw.h\"\n#include \"fonts.h\"\n#include \"r_texture.h\"\n#include \"r_draw.h\"\n#include \"r_trace.h\"\n\nstatic void OnChange_gl_consolefont(cvar_t *, char *, qbool *);\nvoid Draw_InitFont(void);\n\ncvar_t gl_alphafont    = { \"gl_alphafont\", \"1\" };\ncvar_t gl_consolefont  = { \"gl_consolefont\", \"povo5\", CVAR_AUTO, OnChange_gl_consolefont };\ncvar_t scr_coloredText = { \"scr_coloredText\", \"1\" };\ncvar_t gl_charsets_min = { \"gl_charsets_min\", \"1\" };\n\nstatic byte *draw_chars; // 8*8 graphic characters\n\ncharset_t char_textures[MAX_CHARSETS];\nint char_mapping[256]; // need to expand in future...\n\nstatic byte cache_currentColor[4];\nstatic qbool nextCharacterIsCrosshair = false;\n\nstatic const byte color_white[4] = { 255, 255, 255, 255 };\n\n/*\n* Load_LMP_Charset\n*/\nstatic qbool Load_LMP_Charset(char *name, int flags, charset_t* charset)\n{\n\tint i;\n\tbyte\tbuf[256 * 256];\n\tbyte\t*data;\n\tbyte\t*src, *dest;\n\tint filesize;\n\ttexture_ref tex;\n\n\t// We expect an .lmp to be in QPIC format, but it's ok if it's just raw data.\n\tif (!strcasecmp(name, \"charset\")) {\n\t\t// work around for original charset\n\t\tdata = draw_chars;\n\t\tfilesize = 128 * 128;\n\t}\n\telse {\n\t\tdata = FS_LoadTempFile(va(\"gfx/%s.lmp\", name), &filesize);\n\t}\n\n\tif (!data)\n\t\treturn 0;\n\n\tif (filesize == 128 * 128) {\n\t\t// Raw data.\n\t}\n\telse if (filesize == 128 * 128 + 8) {\n\t\tqpic_t *p = (qpic_t *)data;\n\t\tSwapPic(p);\n\t\tif (p->width != 128 || p->height != 128)\n\t\t\treturn 0;\n\t\tdata += 8;\n\t}\n\telse {\n\t\treturn 0;\n\t}\n\n\tfor (i = 0; i < (256 * 64); i++) {\n\t\tif (data[i] == 0) {\n\t\t\tdata[i] = 255;\t// Proper transparent color.\n\t\t}\n\t}\n\n\t// Convert the 128*128 conchars texture to 256*256 leaving\n\t// empty space between rows so that chars don't stumble on\n\t// each other because of texture smoothing.\n\tmemset(buf, 255, sizeof(buf));\n\tsrc = data;\n\tdest = buf;\n\n\tfor (i = 0; i < 128; i++) {\n\t\tint j;\n\n\t\tif (i % 8 == 0 && i) {\n\t\t\tdest += 256 * 8;\n\t\t}\n\t\tfor (j = 0; j < 16; ++j) {\n\t\t\tmemcpy(dest, src, 8);\n\t\t\tsrc += 8;\n\t\t\tdest += 8 * 2;\n\t\t}\n\t}\n\n\ttex = R_LoadTexture(va(\"pic:%s\", name), 256, 256, buf, flags, 1);\n\tif (R_TextureReferenceIsValid(tex)) {\n\t\tfor (i = 0; i < 256; ++i) {\n\t\t\tcharset->glyphs[i].texnum = tex;\n\t\t\tcharset->glyphs[i].width = 128 / 16;\n\t\t\tcharset->glyphs[i].height = 128 / 16;\n\t\t\tcharset->glyphs[i].sl = 2 * (i & 0x0F) * (1.0f / 32);\n\t\t\tcharset->glyphs[i].sh = charset->glyphs[i].sl + 8.0f / 256;\n\t\t\tcharset->glyphs[i].tl = 2 * (i / 0x10) * (1.0f / 32);\n\t\t\tcharset->glyphs[i].th = charset->glyphs[i].tl + 8.0f / 256;\n\t\t}\n\n\t\tcharset->master = tex;\n\t\tcharset->custom_scale_x = charset->custom_scale_y = 1;\n\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/*\n* Load_Locale_Charset\n*/\nstatic qbool Load_Locale_Charset(const char *name, const char *locale, unsigned int num, int flags)\n{\n\tchar texture[1024], id[256], lmp[256], basename[MAX_QPATH];\n\n\tif (num >= MAX_CHARSETS) {\n\t\treturn 0;\n\t}\n\n\tR_TraceEnterFunctionRegion;\n\tmemset(&char_textures[num], 0, sizeof(char_textures[num]));\n\n\tCOM_StripExtension(name, basename, sizeof(basename));\n\tsnprintf(texture, sizeof(texture), \"textures/charsets/%s-%s\", basename, locale);\n\tsnprintf(id, sizeof(id), \"pic:charset-%s\", locale);\n\tsnprintf(lmp, sizeof(lmp), \"conchars-%s\", locale);\n\n\t// try first 24 bit, then 8 bit\n\tchar_mapping[num] = 0;\n\tif (R_LoadCharsetImage(texture, id, flags, &char_textures[num])) {\n\t\tchar_mapping[num] = num;\n\t}\n\telse if (Load_LMP_Charset(lmp, flags, &char_textures[num])) {\n\t\tchar_mapping[num] = num;\n\t}\n\tR_TraceLeaveFunctionRegion;\n\n\treturn char_mapping[num];\n}\n\nstatic int Draw_LoadCharset(const char *name)\n{\n\tint flags = TEX_ALPHA | TEX_NOCOMPRESS | TEX_NOSCALE | TEX_NO_TEXTUREMODE;\n\tint i = 0;\n\tqbool loaded = false;\n\n\t//\n\t// NOTE: we trying to not change char_textures[0] if we can't load charset.\n\t//\t\tThis way user still have some charset and can fix issue.\n\t//\n\tif (!strcasecmp(name, \"original\")) {\n\t\tloaded = Load_LMP_Charset(\"charset\", flags, &char_textures[0]);\n\t}\n\telse {\n\t\tloaded = R_LoadCharsetImage(va(\"textures/charsets/%s\", name), \"pic:charset\", flags, &char_textures[0]);\n\t}\n\n\tif (!loaded) {\n\t\tCom_Printf(\"Couldn't load charset \\\"%s\\\"\\n\", name);\n\t\treturn 1;\n\t}\n\n\t// Load alternate charsets if available\n\tif (gl_charsets_min.integer) {\n\t\tLoad_Locale_Charset(name, \"cyr\", 4, flags);\n\t}\n\telse {\n\t\tfor (i = 1; i < MAX_USER_CHARSETS; ++i) {\n\t\t\tchar charsetName[10];\n\n\t\t\tsnprintf(charsetName, sizeof(charsetName), \"%03d\", i);\n\n\t\t\tloaded = Load_Locale_Charset(name, charsetName, i, flags);\n\n\t\t\t// 2.2 only supported -cyr suffix\n\t\t\tif (i == 4 && !loaded) {\n\t\t\t\tLoad_Locale_Charset(name, \"cyr\", 4, flags);\n\t\t\t}\n\t\t}\n\t}\n\n\tCachePics_MarkAtlasDirty();\n\n\treturn 0;\n}\n\nstatic void OnChange_gl_consolefont(cvar_t *var, char *string, qbool *cancel)\n{\n\t*cancel = Draw_LoadCharset(string);\n}\n\nstatic void Draw_LoadCharset_f(void)\n{\n\tswitch (Cmd_Argc()) {\n\tcase 1:\n\t\tCom_Printf(\"Current charset is \\\"%s\\\"\\n\", gl_consolefont.string);\n\t\tbreak;\n\tcase 2:\n\t\tCvar_Set(&gl_consolefont, Cmd_Argv(1));\n\t\tbreak;\n\tdefault:\n\t\tCom_Printf(\"Usage: %s <charset>\\n\", Cmd_Argv(0));\n\t}\n}\n\n// Called when textencoding, if we can't display a font it can replace with alternative\nqbool R_CharAvailable(wchar num)\n{\n\tif (num == (num & 0xff)) {\n\t\treturn true;\n\t}\n\n\treturn (char_mapping[(num >> 8) & 0xff]);\n}\n\n// x, y\t\t\t\t\t= Pixel position of char.\n// num\t\t\t\t\t= The character to draw.\n// scale\t\t\t\t= The scale of the character.\n// apply_overall_alpha\t= Should the overall alpha for all drawing apply to this char?\n// color\t\t\t\t= Color!\n// bigchar\t\t\t\t= Draw this char using the big character charset.\n// gl_statechange\t\t= Change the gl state before drawing?\nstatic float Draw_CharacterBaseW(float x, float y, wchar num, float scale, qbool apply_overall_alpha, qbool bigchar, qbool gl_statechange, qbool proportional)\n{\n\tint original_x = x;\n\n\tif (FontAlterCharCoordsWide(&x, &y, num, bigchar, scale, proportional)) {\n\t\treturn R_Draw_CharacterBase(x, y, num, scale, apply_overall_alpha, bigchar, gl_statechange, proportional);\n\t}\n\treturn x - original_x;\n}\n\nstatic void Draw_ResetCharGLState(void)\n{\n\tR_Draw_ResetCharGLState();\n}\n\nvoid Draw_SCharacter(float x, float y, int num, float scale)\n{\n\tDraw_CharacterBaseW(x, y, char2wc(num), scale, true, false, true, false);\n\tDraw_ResetCharGLState();\n}\n\nfloat Draw_SCharacterP(float x, float y, int num, float scale, qbool proportional)\n{\n\tfloat new_x = Draw_CharacterBaseW(x, y, char2wc(num), scale, true, false, true, proportional);\n\tDraw_ResetCharGLState();\n\treturn new_x;\n}\n\nfloat Draw_CharacterWSP(float x, float y, wchar num, float scale, qbool proportional)\n{\n\tfloat new_x = Draw_CharacterBaseW(x, y, num, scale, true, false, true, proportional);\n\tDraw_ResetCharGLState();\n\treturn new_x;\n}\n\nvoid Draw_CharacterW(float x, float y, wchar num)\n{\n\tDraw_CharacterBaseW(x, y, num, 1, true, false, true, false);\n\tDraw_ResetCharGLState();\n}\n\nvoid Draw_Character(float x, float y, int num)\n{\n\tDraw_CharacterBaseW(x, y, char2wc(num), 1, true, false, true, false);\n\tDraw_ResetCharGLState();\n}\n\nvoid Draw_SetColor(byte *rgba)\n{\n\tR_Draw_SetColor(rgba);\n}\n\nstatic float Draw_StringBase(float x, float y, const char *text, clrinfo_t *color, int color_count, int red, float scale, float alpha, qbool bigchar, int char_gap, qbool proportional, float max_x)\n{\n\tbyte rgba[4];\n\tqbool color_is_white = true;\n\tint i, r, g, b;\n\tint curr_char;\n\tint color_index = 0;\n\tcolor_t last_color = COLOR_WHITE;\n\tfloat original_x = x;\n\tfloat char_width = 0;\n\n\t// Nothing to draw.\n\tif (!*text) {\n\t\treturn 0;\n\t}\n\n\t// Make sure we set the color from scratch so that the \n\t// overall opacity is applied properly.\n\trgba[0] = rgba[1] = rgba[2] = rgba[3] = 255;\n\tif (scr_coloredText.integer && color_count > 0) {\n\t\tCOLOR_TO_RGBA(color[0].c, rgba);\n\t}\n\n\t// Draw the string.\n\tR_Draw_StringBase_StartString(x, y, scale);\n\tfor (i = 0; text[i]; i++) {\n\t\t// If we didn't get a color array, check for color codes in the text instead.\n\t\tif (!color) {\n\t\t\tif (text[i] == '&') {\n\t\t\t\tif (text[i + 1] == 'c' && text[i + 2] && text[i + 3] && text[i + 4]) {\n\t\t\t\t\tr = HexToInt(text[i + 2]);\n\t\t\t\t\tg = HexToInt(text[i + 3]);\n\t\t\t\t\tb = HexToInt(text[i + 4]);\n\n\t\t\t\t\tif (r >= 0 && g >= 0 && b >= 0) {\n\t\t\t\t\t\tif (scr_coloredText.value) {\n\t\t\t\t\t\t\trgba[0] = (r * 16);\n\t\t\t\t\t\t\trgba[1] = (g * 16);\n\t\t\t\t\t\t\trgba[2] = (b * 16);\n\t\t\t\t\t\t\trgba[3] = 255;\n\t\t\t\t\t\t\tcolor_is_white = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcolor_count++; // Keep track on how many colors we're using.\n\n\t\t\t\t\t\trgba[3] = 255 * alpha;\n\t\t\t\t\t\tDraw_SetColor(rgba);\n\n\t\t\t\t\t\ti += 4;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (text[i + 1] == 'r') {\n\t\t\t\t\tif (!color_is_white) {\n\t\t\t\t\t\trgba[0] = rgba[1] = rgba[2] = 255;\n\t\t\t\t\t\trgba[3] = 255 * alpha;\n\t\t\t\t\t\tcolor_is_white = true;\n\t\t\t\t\t\tDraw_SetColor(rgba);\n\t\t\t\t\t}\n\n\t\t\t\t\ti++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (scr_coloredText.value && (color_index < color_count) && (i == color[color_index].i)) {\n\t\t\t// Change color if the color array tells us this index should have a new color.\n\n\t\t\t// Set the new color if it's not the same as the last.\n\t\t\tif (color[color_index].c != last_color) {\n\t\t\t\tlast_color = color[color_index].c;\n\t\t\t\tCOLOR_TO_RGBA(color[color_index].c, rgba);\n\t\t\t\trgba[3] = 255 * alpha;\n\t\t\t\tDraw_SetColor(rgba);\n\t\t\t}\n\n\t\t\tcolor_index++; // Goto next color.\n\t\t}\n\t\tcurr_char = (unsigned char)text[i];\n\n\t\t// Do not convert the character to red if we're applying color to the text.\n\t\tif (red && color_count <= 0) {\n\t\t\tcurr_char |= 128;\n\t\t}\n\n\t\t// Draw the character but don't apply overall opacity, we've already done that\n\t\t// And don't update the glstate, we've done that also!\n\t\tchar_width = Draw_CharacterBaseW(x, y, curr_char, scale, false, bigchar, false, proportional);\n\n\t\tif (max_x && x + char_width > max_x) {\n\t\t\tR_UndoLastCharacter();\n\t\t\tbreak;\n\t\t}\n\n\t\tx += char_width;\n\t}\n\tDraw_ResetCharGLState();\n\treturn ceil(x - original_x);\n}\n\nvoid Draw_BigString(float x, float y, const char *text, clrinfo_t *color, int color_count, float scale, float alpha, int char_gap)\n{\n\tDraw_StringBase(x, y, text, color, color_count, false, scale, alpha, true, char_gap, false, 0);\n}\n\nfloat Draw_SColoredAlphaString(float x, float y, const char *text, clrinfo_t *color, int color_count, int red, float scale, float alpha, qbool proportional)\n{\n\treturn Draw_StringBase(x, y, text, color, color_count, red, scale, alpha, false, 0, proportional, 0);\n}\n\nvoid Draw_SColoredStringAligned(int x, int y, const char *text, clrinfo_t* color, int color_count, float scale, float alpha, qbool proportional, text_alignment_t align, float max_x)\n{\n\tint initial_position, final_position;\n\tfloat width;\n\n\tinitial_position = Draw_ImagePosition();\n\twidth = Draw_StringBase(x, y, text, color, color_count, false, scale, alpha, false, 0, proportional, max_x);\n\tfinal_position = Draw_ImagePosition();\n\n\tif (align == text_align_center) {\n\t\tDraw_AdjustImages(initial_position, final_position, (max_x - x - width) / 2);\n\t}\n\telse if (align == text_align_right) {\n\t\tDraw_AdjustImages(initial_position, final_position, max_x - x - width);\n\t}\n}\n\nvoid Draw_SStringAligned(int x, int y, const char *text, float scale, float alpha, qbool proportional, text_alignment_t align, float max_x)\n{\n\tint initial_position, final_position;\n\tfloat width;\n\n\tinitial_position = Draw_ImagePosition();\n\twidth = Draw_StringBase(x, y, text, NULL, 0, false, scale, alpha, false, 0, proportional, max_x);\n\tfinal_position = Draw_ImagePosition();\n\n\tif (align == text_align_center) {\n\t\tDraw_AdjustImages(initial_position, final_position, (max_x - x - width) / 2);\n\t}\n\telse if (align == text_align_right) {\n\t\tDraw_AdjustImages(initial_position, final_position, max_x - x - width);\n\t}\n}\n\nfloat Draw_SString(float x, float y, const char *text, float scale, qbool proportional)\n{\n\treturn Draw_StringBase(x, y, text, NULL, 0, false, scale, 1, false, 0, proportional, 0);\n}\n\nfloat Draw_SStringAlpha(int x, int y, const char* text, float scale, float alpha, qbool proportional)\n{\n\treturn Draw_StringBase(x, y, text, NULL, 0, false, scale, alpha, false, 0, proportional, 0);\n}\n\nvoid Draw_SAlt_String(float x, float y, const char *text, float scale, qbool proportional)\n{\n\tDraw_StringBase(x, y, text, NULL, 0, true, scale, 1, false, 0, proportional, 0);\n}\n\nint Draw_ConsoleString(float x, float y, const wchar *text, clrinfo_t *color, int text_length, int red, float scale, qbool proportional)\n{\n\tconst qbool bigchar = false;\n\tbyte rgba[4];\n\tqbool color_is_white = true;\n\tint i, r, g, b;\n\tint curr_char;\n\tint color_index = 0;\n\tcolor_t last_color = COLOR_WHITE;\n\tint color_count = color ? text_length : 0;\n\n\t// Nothing to draw.\n\tif (!*text) {\n\t\treturn 0;\n\t}\n\n\t// Make sure we set the color from scratch so that the \n\t// overall opacity is applied properly.\n\trgba[0] = rgba[1] = rgba[2] = rgba[3] = 255;\n\tif (scr_coloredText.integer && color_count > 0) {\n\t\tCOLOR_TO_RGBA(color[color_index].c, rgba);\n\t}\n\n\t// Draw the string.\n\tR_Draw_StringBase_StartString(x, y, scale);\n\tfor (i = 0; text[i] != 0 && (text_length <= 0 || i < text_length); i++) {\n\t\t// If we didn't get a color array, check for color codes in the text instead.\n\t\tif (!color) {\n\t\t\tif (text[i] == '&') {\n\t\t\t\tif ((text_length == 0 || i + 4 < text_length) && text[i + 1] == 'c' && text[i + 2] && text[i + 3] && text[i + 4]) {\n\t\t\t\t\tr = HexToInt(text[i + 2]);\n\t\t\t\t\tg = HexToInt(text[i + 3]);\n\t\t\t\t\tb = HexToInt(text[i + 4]);\n\n\t\t\t\t\tif (r >= 0 && g >= 0 && b >= 0) {\n\t\t\t\t\t\tif (scr_coloredText.value) {\n\t\t\t\t\t\t\trgba[0] = (r * 16);\n\t\t\t\t\t\t\trgba[1] = (g * 16);\n\t\t\t\t\t\t\trgba[2] = (b * 16);\n\t\t\t\t\t\t\trgba[3] = 255;\n\t\t\t\t\t\t\tcolor_is_white = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcolor_count++; // Keep track on how many colors we're using.\n\n\t\t\t\t\t\trgba[3] = 255;\n\t\t\t\t\t\tDraw_SetColor(rgba);\n\n\t\t\t\t\t\ti += 4;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ((text_length == 0 || i + 1 < text_length) && text[i + 1] == 'r') {\n\t\t\t\t\tif (!color_is_white) {\n\t\t\t\t\t\trgba[0] = rgba[1] = rgba[2] = 255;\n\t\t\t\t\t\trgba[3] = 255;\n\t\t\t\t\t\tcolor_is_white = true;\n\t\t\t\t\t\tDraw_SetColor(rgba);\n\t\t\t\t\t}\n\n\t\t\t\t\ti++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (scr_coloredText.value) {\n\t\t\t// Change color if the color array tells us this index should have a new color.\n\n\t\t\t// Set the new color if it's not the same as the last.\n\t\t\tif (color[color_index].c != last_color) {\n\t\t\t\tlast_color = color[color_index].c;\n\t\t\t\tCOLOR_TO_RGBA(color[color_index].c, rgba);\n\t\t\t\trgba[3] = 255;\n\t\t\t\tDraw_SetColor(rgba);\n\t\t\t}\n\n\t\t\tcolor_index++; // Goto next color.\n\t\t}\n\t\tcurr_char = text[i];\n\n\t\t// Do not convert the character to red if we're applying color to the text.\n\t\tif (red && color_count <= 0) {\n\t\t\tcurr_char |= 128;\n\t\t}\n\n\t\t// Draw the character but don't apply overall opacity, we've already done that\n\t\t// And don't update the glstate, we've done that also!\n\t\tx += Draw_CharacterBaseW(x, y, curr_char, scale, false, bigchar, false, proportional);\n\t}\n\tDraw_ResetCharGLState();\n\n\treturn i;\n}\n\nvoid Draw_ColoredString3(float x, float y, const char *text, clrinfo_t *color, int color_count, int red)\n{\n\tDraw_StringBase(x, y, text, color, color_count, red, 1, 1, false, 0, false, 0);\n}\n\nvoid Draw_ColoredString(float x, float y, const char *text, int red, qbool proportional)\n{\n\tDraw_StringBase(x, y, text, NULL, 0, red, 1, 1, false, 0, proportional, 0);\n}\n\nvoid Draw_SColoredStringBasic(float x, float y, const char *text, int red, float scale, qbool proportional)\n{\n\tDraw_StringBase(x, y, text, NULL, 0, red, scale, 1, false, 0, proportional, 0);\n}\n\n// FIXME: Replace with Draw_ColoredString()\nvoid Draw_Alt_String(float x, float y, const char *text, float scale, qbool proportional)\n{\n\tDraw_StringBase(x, y, text, NULL, 0, true, scale, 1, false, 0, proportional, 0);\n}\n\n// TODO: proportional\nvoid Draw_AlphaString(float x, float y, const char *text, float alpha)\n{\n\tDraw_StringBase(x, y, text, NULL, 0, false, 1, alpha, false, 0, false, 0);\n}\n\nvoid Draw_String(float x, float y, const char *text)\n{\n\tDraw_StringBase(x, y, text, NULL, 0, false, 1, 1, false, 0, false, 0);\n}\n\nfloat Draw_StringLengthW(const wchar *text, int length, float scale, qbool proportional)\n{\n\tint i;\n\tfloat x = 0;\n\n\tfor (i = 0; text[i] && (length == -1 || i < length); i++) {\n\t\tx += FontCharacterWidthWide(text[i], scale, proportional);\n\t}\n\n\treturn x;\n}\n\nfloat Draw_StringLength(const char *text, int length, float scale, qbool proportional)\n{\n\tif (!proportional) {\n\t\tif (length < 0) {\n\t\t\tlength = strlen(text);\n\t\t}\n\t\treturn length * scale * 8;\n\t}\n\telse {\n\t\tint i;\n\t\tfloat x = 0;\n\n\t\tfor (i = 0; text[i] && (length == -1 || i < length); i++) {\n\t\t\tx += FontCharacterWidth(text[i], scale, proportional);\n\t\t}\n\n\t\treturn x;\n\t}\n}\n\nfloat Draw_StringLengthColors(const char *text, int length, float scale, qbool proportional)\n{\n\treturn Draw_StringLengthColorsByTerminator(text, length, scale, proportional, 0);\n}\n\nfloat Draw_StringLengthColorsByTerminator(const char *text, int length, float scale, qbool proportional, char terminator)\n{\n\tif (!proportional) {\n\t\tif (length < 0) {\n\t\t\tlength = strlen_color_by_terminator(text, terminator);\n\t\t}\n\t\treturn length * scale * 8;\n\t}\n\telse {\n\t\tint i;\n\t\tfloat x = 0;\n\n\t\tfor (i = 0; text[i] && text[i] != terminator && (length == -1 || i < length); i++) {\n\t\t\tif (text[i] == '&') {\n\t\t\t\tif (text[i + 1] == 'c' && HexToInt(text[i + 2]) >= 0 && HexToInt(text[i + 3]) >= 0 && HexToInt(text[i + 4]) >= 0) {\n\t\t\t\t\ti += 4; // skip \"&cRGB\"\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\telse if (text[i + 1] == 'r') {\n\t\t\t\t\ti += 1; // skip \"&r\"\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tx += FontCharacterWidthWide(text[i], scale, proportional);\n\t\t}\n\n\t\treturn x;\n\t}\n}\n\nint Draw_CharacterFit(const char* text, float width, float scale, qbool proportional)\n{\n\tint fit = 0;\n\n\tif (!proportional) {\n\t\treturn width / (8 * scale);\n\t}\n\n\twhile (*text) {\n\t\tfloat next_char_width = FontCharacterWidthWide(*text, scale, proportional);\n\t\tif (next_char_width > width) {\n\t\t\tbreak;\n\t\t}\n\n\t\twidth -= next_char_width;\n\t\t++fit;\n\t\t++text;\n\t}\n\n\treturn fit;\n}\n\n// Called during initialisation, before R_Texture_Init\nvoid Draw_Charset_Init(void)\n{\n\tif (!host_initialized) {\n\t\tCmd_AddCommand(\"loadcharset\", Draw_LoadCharset_f);\n#ifdef EZ_FREETYPE_SUPPORT\n\t\tFontInitialise();\n#endif\n\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_CONSOLE);\n\t\tCvar_Register(&gl_consolefont);\n\t\tCvar_Register(&gl_alphafont);\n\t\tCvar_Register(&gl_charsets_min);\n\t\tCvar_Register(&scr_coloredText);\n\t\tCvar_ResetCurrentGroup();\n\t}\n\n\tdraw_chars = NULL;\n}\n\n// Called during initialisation, _after_ R_Texture_Init, wad file loaded etc\nvoid Draw_InitCharset(void)\n{\n\tint i;\n\n\tmemset(char_textures, 0, sizeof(char_textures));\n\tmemset(char_mapping, 0, sizeof(char_mapping));\n\n\tdraw_chars = W_GetLumpName(\"conchars\");\n\tfor (i = 0; i < 256 * 64; i++) {\n\t\tif (draw_chars[i] == 0) {\n\t\t\tdraw_chars[i] = 255;\n\t\t}\n\t}\n\n\tCvar_AutoReset(&gl_consolefont);\n\tDraw_LoadCharset(gl_consolefont.string);\n\n\tif (!R_TextureReferenceIsValid(char_textures[0].glyphs[0].texnum)) {\n\t\tif (Draw_LoadCharset(\"original\") == 0) {\n\t\t\tCvar_AutoSet(&gl_consolefont, \"original\");\n\t\t}\n\t}\n\n\tif (!R_TextureReferenceIsValid(char_textures[0].glyphs[0].texnum)) {\n\t\tSys_Error(\"Draw_InitCharset: Couldn't load charset\");\n\t}\n\n\tDraw_InitFont();\n}\n\nvoid Draw_SetCrosshairTextMode(qbool enabled)\n{\n\tnextCharacterIsCrosshair = enabled;\n}\n\nstatic void Draw_TextCacheInit(float x, float y, float scale)\n{\n\tmemcpy(cache_currentColor, color_white, sizeof(cache_currentColor));\n}\n\nstatic void Draw_TextCacheSetColor(const byte* color)\n{\n\tmemcpy(cache_currentColor, color, sizeof(cache_currentColor));\n}\n\nstatic float Draw_TextCacheAddCharacter(float x, float y, wchar ch, float scale, qbool proportional)\n{\n\tint new_charset = (ch >> 8) & 0xFF;\n\tcharset_t* texture = &char_textures[0];\n\tmpic_t* pic;\n\n#ifdef EZ_FREETYPE_SUPPORT\n\tif (proportional) {\n\t\textern charset_t proportional_fonts[MAX_CHARSETS];\n\n\t\tproportional &= R_TextureReferenceIsValid(proportional_fonts[new_charset].glyphs[ch & 0xFF].texnum);\n\t\tif (proportional) {\n\t\t\ttexture = &proportional_fonts[new_charset];\n\t\t}\n\t}\n#else\n\tproportional = false;\n#endif\n\n\tif (!proportional) {\n\t\tint slot = new_charset;\n\n\t\tif (slot) {\n\t\t\tif (slot < 0xE0 && !char_mapping[slot]) {\n\t\t\t\tslot = 0;\n\t\t\t\tch = '?';\n\t\t\t}\n\t\t}\n\n\t\ttexture = &char_textures[slot];\n\t}\n\n\tpic = &texture->glyphs[ch & 0xFF];\n\tif (!R_TextureReferenceIsValid(pic->texnum)) {\n\t\ttexture = &char_textures[0];\n\t\tpic = &texture->glyphs['*'];\n\t}\n\tR_TraceAPI(\"R_DrawChar(%d %c = %d[%d], pic[%d/%s] %0.3f %0.3f, %0.3f\", ch, (ch < 256 ? ch : '?'), new_charset, ch & 0xFF, pic->texnum.index, R_TextureIdentifier(pic->texnum), texture->custom_scale_x, texture->custom_scale_y, scale);\n\tR_TraceAPI(\n\t\t\"-> %f,%f %f,%f [%d,%d %d,%d]\",\n\t\tpic->sl, pic->tl, pic->sh - pic->sl, pic->th - pic->tl,\n\t\t(int)(pic->sl * R_TextureWidth(pic->texnum)),\n\t\t(int)(pic->tl * R_TextureHeight(pic->texnum)),\n\t\t(int)((pic->sh - pic->sl) * R_TextureWidth(pic->texnum)),\n\t\t(int)((pic->th - pic->tl) * R_TextureHeight(pic->texnum))\n\t);\n\tR_DrawImage(x, y, texture->custom_scale_x * scale * 8, texture->custom_scale_y * scale * 8, pic->sl, pic->tl, pic->sh - pic->sl, pic->th - pic->tl, cache_currentColor, false, pic->texnum, true, nextCharacterIsCrosshair);\n\treturn FontCharacterWidthWide(ch, scale, proportional);\n}\n\n// x, y\t\t\t\t\t= Pixel position of char.\n// num\t\t\t\t\t= The character to draw.\n// scale\t\t\t\t= The scale of the character.\n// apply_overall_alpha\t= Should the overall alpha for all drawing apply to this char?\n// bigchar\t\t\t\t= Draw this char using the big character charset.\n// gl_statechange\t\t= Change the gl state before drawing?\nfloat R_Draw_CharacterBase(float x, float y, wchar num, float scale, qbool apply_overall_alpha, qbool bigchar, qbool gl_statechange, qbool proportional)\n{\n\tint char_size = (bigchar ? 64 : 8);\n\n\tif (bigchar) {\n\t\tmpic_t *p = Draw_CachePicSafe(MCHARSET_PATH, false, true);\n\n\t\tif (p) {\n\t\t\tint sx = 0;\n\t\t\tint sy = 0;\n\t\t\tint char_width = (p->width / 8);\n\t\t\tint char_height = (p->height / 8);\n\t\t\tchar c = (char)(num & 0xFF);\n\n\t\t\tDraw_GetBigfontSourceCoords(c, char_width, char_height, &sx, &sy);\n\n\t\t\tif (sx >= 0) {\n\t\t\t\t// Don't apply alpha here, since we already applied it above.\n\t\t\t\tDraw_SAlphaSubPic(x, y, p, sx, sy, char_width, char_height, (((float)char_size / char_width) * scale), 1);\n\t\t\t}\n\n\t\t\treturn 64 * scale;\n\t\t}\n\n\t\t// TODO : Force players to have mcharset.png or fallback to overscaling normal font? :s\n\t}\n\n\treturn Draw_TextCacheAddCharacter(x, y, num, scale, proportional);\n}\n\nvoid R_Draw_ResetCharGLState(void)\n{\n\tDraw_TextCacheSetColor(color_white);\n}\n\nvoid R_Draw_SetColor(byte* rgba)\n{\n\textern cvar_t scr_coloredText;\n\n\tif (scr_coloredText.integer) {\n\t\tDraw_TextCacheSetColor(rgba);\n\t}\n}\n\nvoid R_Draw_StringBase_StartString(float x, float y, float scale)\n{\n\tDraw_TextCacheInit(x, y, scale);\n}\n"
  },
  {
    "path": "src/r_draw_circle.c",
    "content": "/*\nCopyright (C) 2017-2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"utils.h\"\n#include \"common_draw.h\"\n#include \"glm_draw.h\"\n#include \"glm_vao.h\"\n#include \"r_state.h\"\n#include \"r_matrix.h\"\n#include \"r_buffers.h\"\n#include \"r_renderer.h\"\n\nglm_circle_framedata_t circleData;\n\nextern float overall_alpha;\n\nstatic void SetCirclePoint(float* points, float x, float y)\n{\n\tfloat v[4] = { x, y, 0, 1 };\n\textern float cachedMatrix[16];\n\n\tR_MultiplyVector(cachedMatrix, v, v);\n\n\tpoints[0] = v[0];\n\tpoints[1] = v[1];\n}\n\nvoid R_Draw_AlphaPieSliceRGB(float x, float y, float radius, float startangle, float endangle, float thickness, qbool fill, color_t color)\n{\n\tfloat* pointData;\n\tdouble angle;\n\tbyte color_bytes[4];\n\tint i;\n\tint start;\n\tint end;\n\tint points;\n\n\tif (circleData.circleCount >= CIRCLES_PER_FRAME) {\n\t\treturn;\n\t}\n\tif (!R_LogCustomImageType(imagetype_circle, circleData.circleCount)) {\n\t\treturn;\n\t}\n\n\t// Get the vertex index where to start and stop drawing.\n\tstart = Q_rint((startangle * CIRCLE_LINE_COUNT) / (2 * M_PI));\n\tend = Q_rint((endangle * CIRCLE_LINE_COUNT) / (2 * M_PI));\n\n\t// If the end is less than the start, increase the index so that\n\t// we start on a \"new\" circle.\n\tif (end < start) {\n\t\tend = end + CIRCLE_LINE_COUNT;\n\t}\n\n\tpoints = 0;\n\tpointData = circleData.drawCirclePointData + (FLOATS_PER_CIRCLE * circleData.circleCount);\n\tCOLOR_TO_RGBA(color, color_bytes);\n\tcircleData.drawCircleColors[circleData.circleCount][0] = (color_bytes[0] / 255.0f) * overall_alpha;\n\tcircleData.drawCircleColors[circleData.circleCount][1] = (color_bytes[1] / 255.0f) * overall_alpha;\n\tcircleData.drawCircleColors[circleData.circleCount][2] = (color_bytes[2] / 255.0f) * overall_alpha;\n\tcircleData.drawCircleColors[circleData.circleCount][3] = (color_bytes[3] / 255.0f) * overall_alpha;\n\tcircleData.drawCircleFill[circleData.circleCount] = fill;\n\tcircleData.drawCircleThickness[circleData.circleCount] = thickness;\n\t++circleData.circleCount;\n\n\t// Create a vertex at the exact position specified by the start angle.\n\tSetCirclePoint(&pointData[points * 2], x + radius * cos(startangle), y - radius * sin(startangle));\n\t++points;\n\n\t// TODO: Use lookup table for sin/cos?\n\tfor (i = start; i < end; i++) {\n\t\tangle = (i * 2 * M_PI) / CIRCLE_LINE_COUNT;\n\t\tSetCirclePoint(&pointData[points * 2], x + radius * cos(angle), y - radius * sin(angle));\n\t\t++points;\n\n\t\t// When filling we're drawing triangles so we need to\n\t\t// create a vertex in the middle of the circle to fill\n\t\t// the entire pie slice/circle.\n\t\tif (fill) {\n\t\t\tSetCirclePoint(&pointData[points * 2], x, y);\n\t\t\t++points;\n\t\t}\n\t}\n\n\tSetCirclePoint(&pointData[points * 2], x + radius * cos(endangle), y - radius * sin(endangle));\n\t++points;\n\n\t// Create a vertex for the middle point if we're not drawing a complete circle.\n\tif (endangle - startangle < 2 * M_PI) {\n\t\tSetCirclePoint(&pointData[points * 2], x, y);\n\t\t++points;\n\t}\n\n\tcircleData.drawCirclePoints[circleData.circleCount - 1] = points;\n}\n"
  },
  {
    "path": "src/r_draw_image.c",
    "content": "/*\nCopyright (C) 2017-2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"glm_draw.h\"\n#include \"tr_types.h\"\n#include \"glsl/constants.glsl\"\n#include \"r_matrix.h\"\n#include \"glm_vao.h\"\n#include \"r_state.h\"\n#include \"glc_vao.h\"\n#include \"r_buffers.h\"\n#include \"r_renderer.h\"\n\nextern cvar_t r_smoothtext, r_smoothcrosshair, r_smoothimages;\nfloat cachedMatrix[16];\n\nglm_image_framedata_t imageData;\n\nint Draw_ImagePosition(void)\n{\n\treturn imageData.imageCount;\n}\n\nvoid Draw_AdjustImages(int first, int last, float x_offset)\n{\n\tfloat v1[4] = { x_offset, 0, 0, 1 };\n\tfloat v2[4] = { 0, 0, 0, 1 };\n\n\tR_MultiplyVector(cachedMatrix, v1, v1);\n\tR_MultiplyVector(cachedMatrix, v2, v2);\n\n\tx_offset = v1[0] - v2[0];\n\n\trenderer.AdjustImages(first, last, x_offset);\n}\n\nvoid R_DrawImage(float x, float y, float width, float height, float tex_s, float tex_t, float tex_width, float tex_height, byte* color, qbool alpha_test, texture_ref texnum, qbool isText, qbool isCrosshair)\n{\n\tint flags = IMAGEPROG_FLAGS_TEXTURE;\n\n\tif (imageData.imageCount >= MAX_MULTI_IMAGE_BATCH) {\n\t\treturn;\n\t}\n\tif (!R_LogCustomImageTypeWithTexture(imagetype_image, imageData.imageCount, texnum)) {\n\t\treturn;\n\t}\n\n\tflags |= (alpha_test ? IMAGEPROG_FLAGS_ALPHATEST : 0);\n\tif (isCrosshair) {\n\t\tif (!r_smoothcrosshair.integer) {\n\t\t\tflags |= IMAGEPROG_FLAGS_NEAREST;\n\t\t}\n\t}\n\telse if (isText) {\n\t\tflags |= IMAGEPROG_FLAGS_TEXT;\n\t\tif (!r_smoothtext.integer) {\n\t\t\tflags |= IMAGEPROG_FLAGS_NEAREST;\n\t\t}\n\t}\n\telse {\n\t\tif (!r_smoothimages.integer) {\n\t\t\tflags |= IMAGEPROG_FLAGS_NEAREST;\n\t\t}\n\t}\n\n\trenderer.DrawImage(x, y, width, height, tex_s, tex_t, tex_width, tex_height, color, flags);\n}\n\nvoid R_DrawRectangle(float x, float y, float width, float height, byte* color)\n{\n\trenderer.DrawRectangle(x, y, width, height, color);\n}\n\nvoid R_Cache2DMatrix(void)\n{\n\tR_MultiplyMatrix(R_ProjectionMatrix(), R_ModelviewMatrix(), cachedMatrix);\n}\n\nvoid R_UndoLastCharacter(void)\n{\n\tif (imageData.imageCount) {\n\t\t--imageData.imageCount;\n\t\tR_HudUndoLastElement();\n\t}\n}\n"
  },
  {
    "path": "src/r_draw_line.c",
    "content": "/*\nCopyright (C) 2017-2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"glm_draw.h\"\n#include \"glm_vao.h\"\n#include \"r_state.h\"\n#include \"r_matrix.h\"\n#include \"r_buffers.h\"\n\nglm_line_framedata_t lineData;\n\nvoid GLM_Draw_Line3D(byte* color, vec3_t start, vec3_t end)\n{\n\tif (lineData.lineCount >= MAX_LINES_PER_FRAME) {\n\t\treturn;\n\t}\n}\n\nvoid R_Draw_LineRGB(float thickness, byte* color, float x_start, float y_start, float x_end, float y_end)\n{\n\textern float cachedMatrix[16];\n\tfloat v1[4] = { x_start, y_start, 0, 1 };\n\tfloat v2[4] = { x_end, y_end, 0, 1 };\n\tfloat texCoords[2];\n\ttexture_ref texture;\n\tglm_image_t* img;\n\n\tif (lineData.lineCount >= MAX_LINES_PER_FRAME) {\n\t\treturn;\n\t}\n\tif (imageData.imageCount + 2 >= MAX_MULTI_IMAGE_BATCH) {\n\t\treturn;\n\t}\n\n\tAtlas_SolidTextureCoordinates(&texture, &texCoords[0], &texCoords[1]);\n\n\tif (!R_LogCustomImageTypeWithTexture(imagetype_line, lineData.lineCount, texture)) {\n\t\treturn;\n\t}\n\n\tR_MultiplyVector(cachedMatrix, v1, v1);\n\tR_MultiplyVector(cachedMatrix, v2, v2);\n\n\timg = &imageData.images[imageData.imageCount * 4];\n\timg[0].pos[0] = v1[0];\n\timg[0].pos[1] = v1[1];\n\tmemcpy(img[0].colour, color, sizeof(img[0].colour));\n\timg[1].pos[0] = v2[0];\n\timg[1].pos[1] = v2[1];\n\tmemcpy(img[1].colour, color, sizeof(img[1].colour));\n\timg[0].tex[0] = img[1].tex[0] = texCoords[0];\n\timg[0].tex[1] = img[1].tex[1] = texCoords[1];\n\timg[0].tex[2] = img[1].tex[2] = 0;\n\timg[0].tex[3] = img[1].tex[3] = 1;\n\tlineData.line_thickness[lineData.lineCount] = thickness;\n\tlineData.imageIndex[lineData.lineCount] = imageData.imageCount * 4;\n\t++imageData.imageCount;\n\t++lineData.lineCount;\n}\n"
  },
  {
    "path": "src/r_draw_polygon.c",
    "content": "/*\nCopyright (C) 2017-2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"glm_draw.h\"\n#include \"glm_vao.h\"\n#include \"r_state.h\"\n#include \"r_matrix.h\"\n#include \"r_buffers.h\"\n\nglm_polygon_framedata_t polygonData;\n\nvoid R_Draw_Polygon(float x, float y, vec3_t *vertices, int num_vertices, color_t color)\n{\n\tint i;\n\ttexture_ref texture;\n\tfloat texCoords[2];\n\n\tif (polygonData.polygonCount >= MAX_POLYGONS_PER_FRAME) {\n\t\treturn;\n\t}\n\tif (num_vertices <= 2 || num_vertices >= 4 * (MAX_MULTI_IMAGE_BATCH - imageData.imageCount)) {\n\t\treturn;\n\t}\n\n\tAtlas_SolidTextureCoordinates(&texture, &texCoords[0], &texCoords[1]);\n\n\tif (!R_LogCustomImageTypeWithTexture(imagetype_polygon, polygonData.polygonCount, texture)) {\n\t\treturn;\n\t}\n\n\tpolygonData.polygonVerts[polygonData.polygonCount] = num_vertices;\n\tpolygonData.polygonImageIndexes[polygonData.polygonCount] = imageData.imageCount * 4;\n\n\tfor (i = 0; i < num_vertices; ++i) {\n\t\textern float cachedMatrix[16];\n\t\tfloat v1[4] = { vertices[i][0], vertices[i][1], vertices[i][2], 1 };\n\t\tglm_image_t* img = &imageData.images[imageData.imageCount * 4 + i];\n\n\t\tR_MultiplyVector(cachedMatrix, v1, v1);\n\n\t\tCOLOR_TO_RGBA_PREMULT(color, img->colour);\n\t\timg->tex[0] = texCoords[0];\n\t\timg->tex[1] = texCoords[1];\n\t\timg->pos[0] = v1[0];\n\t\timg->pos[1] = v1[1];\n\t}\n\timageData.imageCount += (num_vertices + 3) / 4;\n\t++polygonData.polygonCount;\n}\n"
  },
  {
    "path": "src/r_framestats.h",
    "content": "\n#ifndef EZQUAKE_R_FRAMESTATS_HEADER\n#define EZQUAKE_R_FRAMESTATS_HEADER\n\ntypedef enum {\n\tpolyTypeWorldModel,\n\tpolyTypeAliasModel,\n\tpolyTypeBrushModel,\n\n\tpolyTypeMaximum\n} frameStatsPolyType;\n\ntypedef struct r_frame_stats_classic_s {\n\tint polycount[polyTypeMaximum];\n} r_frame_stats_classic_t;\n\ntypedef struct r_frame_stats_modern_s {\n\tint world_batches;\n\tint buffer_uploads;\n\tint multidraw_calls;\n} r_frame_stats_modern_t;\n\ntypedef struct r_frame_stats_s {\n\tr_frame_stats_classic_t classic;\n\tr_frame_stats_modern_t modern;\n\n\tqbool hotloop;\n\tint hotloop_mallocs;\n\tint mallocs;\n\tint hotloop_stringops;\n\tint stringops;\n\n\tint texture_binds;\n\tunsigned int lightmap_min_changed;\n\tunsigned int lightmap_max_changed;\n\tint lightmap_updates;\n\tint draw_calls;\n\tint subdraw_calls;\n\tint particle_count;\n\n\tdouble start_time;\n\tdouble end_time;\n} r_frame_stats_t;\n\nextern r_frame_stats_t frameStats, prevFrameStats;\nextern cvar_t r_speeds;\n\n#endif // EZQUAKE_R_FRAMESTATS_HEADER\n"
  },
  {
    "path": "src/r_hud.c",
    "content": "/*\nCopyright (C) 2017-2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"glm_draw.h\"\n#include \"r_draw.h\"\n#include \"r_local.h\"\n#include \"tr_types.h\"\n#include \"r_matrix.h\"\n\n#define MAX_2D_ELEMENTS 4096\n\ntypedef struct draw_hud_element_s {\n\tr_image_type_t type;\n\ttexture_ref texture;\n\tint index;\n} draw_hud_element_t;\n\ntypedef struct hud_api_s {\n\tstruct {\n\t\tvoid(*Prepare)(void);\n\t\tvoid(*Draw)(texture_ref texture, int start, int end);\n\t} types[imagetype_count];\n\tvoid(*OnComplete)(void);\n\t//void(*DrawAccelBar)(int x, int y, int length, int charsize, int pos);\n\n\tdraw_hud_element_t elements[MAX_2D_ELEMENTS];\n\tint count;\n} hud_api_t;\n\nstatic hud_api_t hud;\n\n#define HudSetFunctionPointers(prefix) \\\n{ \\\n\textern void prefix ## _HudDrawCircles(texture_ref texture, int start, int end); \\\n\textern void prefix ## _HudDrawLines(texture_ref texture, int start, int end); \\\n\textern void prefix ## _HudDrawPolygons(texture_ref texture, int start, int end); \\\n\textern void prefix ## _HudDrawImages(texture_ref texture, int start, int length); \\\n\textern void prefix ## _HudPrepareCircles(void); \\\n\textern void prefix ## _HudPrepareImages(void); \\\n\textern void prefix ## _HudDrawComplete(void); \\\n\\\n\thud.types[imagetype_image].Draw = prefix ## _HudDrawImages; \\\n\thud.types[imagetype_image].Prepare = prefix ## _HudPrepareImages; \\\n\thud.types[imagetype_circle].Draw = prefix ## _HudDrawCircles; \\\n\thud.types[imagetype_circle].Prepare = prefix ## _HudPrepareCircles; \\\n\thud.types[imagetype_line].Draw = prefix ## _HudDrawLines; \\\n\thud.types[imagetype_line].Prepare = NULL; \\\n\thud.types[imagetype_polygon].Draw = prefix ## _HudDrawPolygons; \\\n\thud.types[imagetype_polygon].Prepare = NULL; \\\n\thud.OnComplete = prefix ## _HudDrawComplete; \\\n}\n\nvoid R_Hud_Initialise(void)\n{\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\tif (R_UseModernOpenGL()) {\n\t\tHudSetFunctionPointers(GLM);\n\t}\n#endif\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tHudSetFunctionPointers(GLC);\n\t}\n#endif\n#ifdef RENDERER_OPTION_VULKAN\n\tif (R_UseVulkan()) {\n\t\tHudSetFunctionPointers(VK);\n\t}\n#endif\n}\n\nstatic void R_PrepareImageDraw(void)\n{\n\thud.types[imagetype_image].Prepare();\n\thud.types[imagetype_circle].Prepare();\n\n\tR_IdentityModelView();\n\tR_IdentityProjectionView();\n}\n\nvoid R_DrawRectangle(float x, float y, float width, float height, byte* color);\n\nvoid R_DrawAlphaRectangleRGB(int x, int y, int w, int h, float thickness, qbool fill, byte* bytecolor)\n{\n\tif (fill) {\n\t\tR_DrawRectangle(x, y, w, h, bytecolor);\n\t}\n\telse {\n\t\tR_DrawRectangle(x, y, w, thickness, bytecolor);\n\t\tR_DrawRectangle(x, y + thickness, thickness, h - thickness, bytecolor);\n\t\tR_DrawRectangle(x + w - thickness, y + thickness, thickness, h - thickness, bytecolor);\n\t\tR_DrawRectangle(x, y + h - thickness, w, thickness, bytecolor);\n\t}\n}\n\nvoid R_Draw_SAlphaSubPic2(float x, float y, mpic_t *pic, int src_width, int src_height, float newsl, float newtl, float newsh, float newth, float scale_x, float scale_y, float alpha)\n{\n\tbyte color[] = { 255, 255, 255, alpha * 255 };\n\n\tR_DrawImage(x, y, scale_x * src_width, scale_y * src_height, newsl, newtl, newsh - newsl, newth - newtl, color, false, pic->texnum, false, false);\n}\n\nvoid R_Draw_FadeScreen(float alpha)\n{\n\tDraw_AlphaRectangleRGB(0, 0, vid.width, vid.height, 0.0f, true, RGBA_TO_COLOR(0, 0, 0, (alpha < 1 ? alpha * 255 : 255)));\n}\n\nvoid R_EmptyImageQueue(void)\n{\n\thud.count = imageData.imageCount = circleData.circleCount = lineData.lineCount = polygonData.polygonCount = 0;\n}\n\nvoid R_FlushImageDraw(void)\n{\n\tR_PrepareImageDraw();\n\n\tif (hud.count > 0 && glConfig.initialized) {\n\t\ttexture_ref currentTexture = null_texture_reference;\n\t\tr_image_type_t type = imagetype_image;\n\t\tint start = 0;\n\t\tint i;\n\n\t\tfor (i = 0; i < hud.count; ++i) {\n\t\t\tqbool texture_changed = (R_TextureReferenceIsValid(currentTexture) && R_TextureReferenceIsValid(hud.elements[i].texture) && !R_TextureReferenceEqual(currentTexture, hud.elements[i].texture));\n\n\t\t\tif (i && (hud.elements[i].type != type || texture_changed)) {\n\t\t\t\thud.types[type].Draw(currentTexture, hud.elements[start].index, hud.elements[i - 1].index);\n\n\t\t\t\tstart = i;\n\t\t\t}\n\n\t\t\tif (R_TextureReferenceIsValid(hud.elements[i].texture)) {\n\t\t\t\tcurrentTexture = hud.elements[i].texture;\n\t\t\t}\n\t\t\ttype = hud.elements[i].type;\n\t\t}\n\n\t\thud.types[type].Draw(currentTexture, hud.elements[start].index, hud.elements[hud.count - 1].index);\n\t}\n\n\tR_EmptyImageQueue();\n\n\thud.OnComplete();\n}\n\nqbool R_LogCustomImageType(r_image_type_t type, int index)\n{\n\tif (hud.count >= MAX_2D_ELEMENTS) {\n\t\treturn false;\n\t}\n\n\thud.elements[hud.count].type = type;\n\thud.elements[hud.count].index = index;\n\thud.elements[hud.count].texture = null_texture_reference;\n\thud.count++;\n\treturn true;\n}\n\nqbool R_LogCustomImageTypeWithTexture(r_image_type_t type, int index, texture_ref texture)\n{\n\tif (hud.count >= MAX_2D_ELEMENTS) {\n\t\treturn false;\n\t}\n\n\thud.elements[hud.count].type = type;\n\thud.elements[hud.count].index = index;\n\thud.elements[hud.count].texture = texture;\n\thud.count++;\n\treturn true;\n}\n\nvoid R_HudUndoLastElement(void)\n{\n\tif (hud.count) {\n\t\t--hud.count;\n\t}\n}\n"
  },
  {
    "path": "src/r_lighting.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_R_LIGHTING_HEADER\n#define EZQUAKE_R_LIGHTING_HEADER\n\nextern cvar_t       gl_lightmode;\nextern cvar_t       gl_modulate;\nextern int          lightmode;              // set to gl_lightmode on mapchange\n\nextern cvar_t       gl_flashblend;\nextern cvar_t       gl_rl_globe;\n\n// gl_rlight.c\nvoid R_MarkLights(dlight_t *light, int bit, mnode_t *node);\nvoid R_AnimateLight(void);\nvoid R_RenderDlights(void);\nvoid R_LightEntity(entity_t* ent);\n\nextern unsigned int d_lightstylevalue[256];\t// 8.8 fraction of base light value\nstatic const float rgb9e5tab[32] = {\n\t//multipliers for the 9-bit mantissa, according to the biased mantissa\n\t//aka: pow(2, biasedexponent - bias-bits) where bias is 15 and bits is 9\n\t1.0/(1<<24), 1.0/(1<<23), 1.0/(1<<22), 1.0/(1<<21), 1.0/(1<<20), 1.0/(1<<19), 1.0/(1<<18), 1.0/(1<<17),\n\t1.0/(1<<16), 1.0/(1<<15), 1.0/(1<<14), 1.0/(1<<13), 1.0/(1<<12), 1.0/(1<<11), 1.0/(1<<10), 1.0/(1<<9),\n\t1.0/(1<<8),  1.0/(1<<7),  1.0/(1<<6),  1.0/(1<<5),  1.0/(1<<4),  1.0/(1<<3),  1.0/(1<<2),  1.0/(1<<1),\n\t1.0,         1.0*(1<<1),  1.0*(1<<2),  1.0*(1<<3),  1.0*(1<<4),  1.0*(1<<5),  1.0*(1<<6),  1.0*(1<<7),\n};\n\nextern cvar_t r_dynamic;\n#define R_NoLighting()       (r_dynamic.integer == 0)\n#define R_HardwareLighting() (r_dynamic.integer == 2 && R_UseModernOpenGL())\n#define R_SoftwareLighting() (r_dynamic.integer && !R_HardwareLighting())\n\n#endif // EZQUAKE_R_LOCAL_HEADER\n"
  },
  {
    "path": "src/r_lightmaps.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// gl_lightmaps.c: lightmap-related code\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"rulesets.h\"\n#include \"utils.h\"\n#include \"r_local.h\"\n#include \"r_texture.h\"\n#include \"r_lightmaps.h\"\n#include \"r_lighting.h\"\n#include \"r_buffers.h\"\n#include \"r_framestats.h\"\n#include \"r_lightmaps_internal.h\"\n#include \"r_trace.h\"\n#include \"r_renderer.h\"\n#include \"tr_types.h\"\n\ntypedef struct dlightinfo_s {\n\tint local[2];\n\tint rad;\n\tint minlight;\t// rad - minlight\n\tint lnum; // reference to cl_dlights[]\n\tint color[3];\n} dlightinfo_t;\n\nstatic unsigned int blocklights[MAX_LIGHTMAP_SIZE * 3];\n\nlightmap_data_t* lightmaps;\nstatic unsigned int last_lightmap_updated;\nunsigned int lightmap_array_size;\n\nstatic qbool gl_invlightmaps = true;\n\nstatic dlightinfo_t dlightlist[MAX_DLIGHTS];\nstatic int numdlights;\n\n// funny, but this colors differ from bubblecolor[NUM_DLIGHTTYPES][4]\nint dlightcolor[NUM_DLIGHTTYPES][3] = {\n\t{ 100,  90,  80 },\t// dimlight or brightlight\n\t{ 100,  50,  10 },\t// muzzleflash\n\t{ 100,  50,  10 },\t// explosion\n\t{  90,  60,   7 },\t// rocket\n\t{ 128,   0,   0 },\t// red\n\t{   0,   0, 128 },\t// blue\n\t{ 128,   0, 128 },\t// red + blue\n\t{   0, 128,   0 },\t// green\n\t{ 128, 128,   0 }, \t// red + green\n\t{   0, 128, 128 }, \t// blue + green\n\t{ 128, 128, 128 },\t// white\n\t{ 128, 128, 128 },\t// custom\n};\n\nstatic void R_BuildDlightList (msurface_t *surf)\n{\n\tfloat dist;\n\tvec3_t impact;\n\tint lnum, i, smax, tmax, irad, iminlight, local[2], tdmin, sdmin, distmin;\n\tunsigned int dlightbits;\n\tdlightinfo_t *light;\n\n\tnumdlights = 0;\n\n\tsmax = (surf->extents[0] >> surf->lmshift) + 1;\n\ttmax = (surf->extents[1] >> surf->lmshift) + 1;\n\tdlightbits = surf->dlightbits;\n\n\tfor (lnum = 0; lnum < MAX_DLIGHTS && dlightbits; lnum++) {\n\t\tif (!(surf->dlightbits & (1 << lnum))) {\n\t\t\tcontinue;\t\t// not lit by this light\n\t\t}\n\n\t\tdlightbits &= ~(1<<lnum);\n\n\t\tdist = PlaneDiff(cl_dlights[lnum].origin, surf->plane);\n\t\tirad = (cl_dlights[lnum].radius - fabs(dist)) * 256;\n\t\timinlight = cl_dlights[lnum].minlight * 256;\n\t\tif (irad < iminlight) {\n\t\t\tcontinue;\n\t\t}\n\n\t\timinlight = irad - iminlight;\n\n\t\tfor (i = 0; i < 3; i++) {\n\t\t\timpact[i] = cl_dlights[lnum].origin[i] - surf->plane->normal[i] * dist;\n\t\t}\n\n\t\tlocal[0] = DotProduct (impact, surf->lmvecs[0]) + surf->lmvecs[0][3] - surf->texturemins[0];\n\t\tlocal[1] = DotProduct (impact, surf->lmvecs[1]) + surf->lmvecs[1][3] - surf->texturemins[1];\n\n\t\t// check if this dlight will touch the surface\n\t\tif (local[1] > 0) {\n\t\t\ttdmin = local[1] - (tmax << surf->lmshift);\n\t\t\tif (tdmin < 0) {\n\t\t\t\ttdmin = 0;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\ttdmin = -local[1];\n\t\t}\n\n\t\tif (local[0] > 0) {\n\t\t\tsdmin = local[0] - (smax << surf->lmshift);\n\t\t\tif (sdmin < 0)\n\t\t\t\tsdmin = 0;\n\t\t} else {\n\t\t\tsdmin = -local[0];\n\t\t}\n\n\t\tif (sdmin > tdmin)\n\t\t\tdistmin = (sdmin << 8) + (tdmin << 7);\n\t\telse\n\t\t\tdistmin = (tdmin << 8) + (sdmin << 7);\n\n\t\tif (distmin < iminlight) {\n\t\t\textern cvar_t gl_colorlights;\n\n\t\t\t// save dlight info\n\t\t\tlight = &dlightlist[numdlights];\n\t\t\tlight->minlight = iminlight >> 7;\n\t\t\tlight->rad = irad >> 7;\n\t\t\tlight->local[0] = local[0];\n\t\t\tlight->local[1] = local[1];\n\t\t\tlight->lnum = lnum;\n\n\t\t\tif (gl_colorlights.integer) {\n\t\t\t\tif (cl_dlights[light->lnum].type == lt_custom) {\n\t\t\t\t\tVectorCopy(cl_dlights[light->lnum].color, light->color);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tVectorCopy(dlightcolor[cl_dlights[light->lnum].type], light->color);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tVectorSet(light->color, 128, 128, 128);\n\t\t\t}\n\t\t\tnumdlights++;\n\t\t}\n\t}\n}\n\n//R_BuildDlightList must be called first!\nstatic void R_AddDynamicLights(msurface_t *surf)\n{\n\tint i, smax, tmax, s, t, sd, td, _sd, _td, irad, idist, iminlight, tmp;\n\tdlightinfo_t *light;\n\tunsigned *dest;\n\n\tsmax = (surf->extents[0] >> surf->lmshift) + 1;\n\ttmax = (surf->extents[1] >> surf->lmshift) + 1;\n\n\tfor (i = 0, light = dlightlist; i < numdlights; i++, light++) {\n\t\tirad = light->rad;\n\t\timinlight = light->minlight;\n\n\t\tfor (t = 0, _td = light->local[1]; t < tmax; t++, _td -= (1 << surf->lmshift)) {\n\t\t\ttd = _td < 0 ? -_td : _td;\n\t\t\ttd *= surf->lmvlen[1];\n\n\t\t\tif (td < irad) {\n\t\t\t\tdest = blocklights + t * smax * 3;\n\t\t\t\tfor (s = 0, _sd = light->local[0]; s < smax; s++, _sd -= (1 << surf->lmshift), dest += 3) {\n\t\t\t\t\tsd = _sd < 0 ? -_sd : _sd;\n\t\t\t\t\tsd *= surf->lmvlen[0];\n\n\t\t\t\t\tif (sd + td < iminlight) {\n\t\t\t\t\t\tidist = sd + td;\n\t\t\t\t\t\tif (sd > td) {\n\t\t\t\t\t\t\tidist += sd;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tidist += td;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (idist < iminlight) {\n\t\t\t\t\t\t\ttmp = irad - idist;\n\t\t\t\t\t\t\tdest[0] += tmp * light->color[0];\n\t\t\t\t\t\t\tdest[1] += tmp * light->color[1];\n\t\t\t\t\t\t\tdest[2] += tmp * light->color[2];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse if (_sd < 0) {\n\t\t\t\t\t\ts = smax;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (_td < 0) {\n\t\t\t\tt = tmax;\n\t\t\t}\n\t\t}\n\t}\n}\n\n//Combine and scale multiple lightmaps into the 8.8 format in blocklights\nstatic void R_BuildLightMap(msurface_t *surf, byte *dest, int stride, uint32_t flags)\n{\n\tint smax, tmax, i, j, size, blocksize, maps;\n\tunsigned scale, *bl;\n\tqbool fullbright = false;\n\n\tR_TraceEnterRegion(va(\"R_BuildLightMap(%d)\", surf->surfacenum), true);\n\n\tsurf->cached_dlight = !!numdlights;\n\n\tsmax = (surf->extents[0] >> surf->lmshift) + 1;\n\ttmax = (surf->extents[1] >> surf->lmshift) + 1;\n\tsize = smax * tmax;\n\tblocksize = size * 3;\n\n\t// check for full bright or no light data\n\tfullbright = (R_FullBrightAllowed() || !cl.worldmodel || !cl.worldmodel->lightdata);\n\n\tif (fullbright) {\t// set to full bright\n\t\tfor (i = 0; i < blocksize; i++) {\n\t\t\tblocklights[i] = 255 << 8;\n\t\t}\n\t}\n\telse {\n\t\t// clear to no light\n\t\tmemset(blocklights, 0, blocksize * sizeof(int));\n\t}\n\n\t// add all the lightmaps\n\tif (flags & MOD_HDRLIGHTING) {\n\t\tuint32_t *lightmap = (uint32_t *)surf->samples;\n\t\tfor (maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255; maps++) {\n\t\t\tscale = d_lightstylevalue[surf->styles[maps]];\n\t\t\tsurf->cached_light[maps] = scale;\t// 8.8 fraction\n\t\t\t// it sucks that blocklights is an int array. we can still massively\n\t\t\t// overbright though, just not underbright quite as accurately\n\t\t\t// (still quite a bit more than rgb8 precision there).\n\t\t\tbl = blocklights;\n\t\t\tfor (i=0 ; i<size ; i++) {\n\t\t\t\tuint32_t e5bgr9 = *lightmap++;\n\t\t\t\t//we're converting to a scale that holds overbrights, so 1->128, its 2->255ish\n\t\t\t\tfloat e = rgb9e5tab[e5bgr9>>27] * (1<<7) * scale;\n\t\t\t\t*bl++ += e*((e5bgr9>> 0)&0x1ff); //red\n\t\t\t\t*bl++ += e*((e5bgr9>> 9)&0x1ff); //green\n\t\t\t\t*bl++ += e*((e5bgr9>>18)&0x1ff); //blue\n\t\t\t}\n\t\t}\n\t} else {\n\t\tbyte *lightmap = surf->samples;\n\t\tfor (maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255; maps++) {\n\t\t\tscale = d_lightstylevalue[surf->styles[maps]];\n\t\t\tsurf->cached_light[maps] = scale;\t// 8.8 fraction\n\n\t\t\tif (!fullbright && lightmap) {\n\t\t\t\tbl = blocklights;\n\t\t\t\tfor (i = 0; i < blocksize; i++) {\n\t\t\t\t\t*bl++ += lightmap[i] * scale;\n\t\t\t\t}\n\t\t\t\tlightmap += blocksize;\t\t// skip to next lightmap\n\t\t\t}\n\t\t}\n\t}\n\n\t// add all the dynamic lights\n\tif (!fullbright && numdlights) {\n\t\tR_AddDynamicLights(surf);\n\t}\n\n\t// bound, invert, and shift\n\tbl = blocklights;\n\tstride -= smax * 4;\n\n\tscale = (lightmode == 2) ? (int)(256 * 1.5) : 256 * 2;\n\tscale *= bound(0.5, gl_modulate.value, 3);\n\tfor (i = 0; i < tmax; i++, dest += stride) {\n\t\tfor (j = smax; j; j--) {\n\t\t\tunsigned r, g, b, m;\n\t\t\tr = bl[0] * scale;\n\t\t\tg = bl[1] * scale;\n\t\t\tb = bl[2] * scale;\n\t\t\tm = max(r, g);\n\t\t\tm = max(m, b);\n\t\t\tif (m > ((255 << 16) + (1 << 15))) {\n\t\t\t\tunsigned s = (((255 << 16) + (1 << 15)) << 8) / m;\n\t\t\t\tr = (r >> 8) * s;\n\t\t\t\tg = (g >> 8) * s;\n\t\t\t\tb = (b >> 8) * s;\n\t\t\t}\n\t\t\tif (gl_invlightmaps) {\n\t\t\t\tif (GL_Supported(R_SUPPORT_BGRA_LIGHTMAPS)) {\n\t\t\t\t\tdest[2] = 255 - (r >> 16);\n\t\t\t\t\tdest[1] = 255 - (g >> 16);\n\t\t\t\t\tdest[0] = 255 - (b >> 16);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tdest[0] = 255 - (r >> 16);\n\t\t\t\t\tdest[1] = 255 - (g >> 16);\n\t\t\t\t\tdest[2] = 255 - (b >> 16);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (GL_Supported(R_SUPPORT_BGRA_LIGHTMAPS)) {\n\t\t\t\t\tdest[2] = r >> 16;\n\t\t\t\t\tdest[1] = g >> 16;\n\t\t\t\t\tdest[0] = b >> 16;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tdest[0] = r >> 16;\n\t\t\t\t\tdest[1] = g >> 16;\n\t\t\t\t\tdest[2] = b >> 16;\n\t\t\t\t}\n\t\t\t}\n\t\t\tdest[3] = 255;\n\t\t\tbl += 3;\n\t\t\tdest += 4;\n\t\t}\n\t}\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid R_UploadLightMap(int textureUnit, int lightmapnum)\n{\n\tlightmap_data_t* lm = &lightmaps[lightmapnum];\n\n\tif (lm->modified) {\n\t\tR_TraceEnterFunctionRegion;\n\t\tlm->modified = false;\n\t\trenderer.UploadLightmap(textureUnit, lightmapnum);\n\t\tlm->change_area.l = LIGHTMAP_WIDTH;\n\t\tlm->change_area.t = LIGHTMAP_HEIGHT;\n\t\tlm->change_area.h = 0;\n\t\tlm->change_area.w = 0;\n\t\t++frameStats.lightmap_updates;\n\t\tR_TraceLeaveFunctionRegion;\n\t}\n}\n\nvoid R_RenderDynamicLightmaps(msurface_t *fa, qbool world)\n{\n\tbyte *base;\n\tint maps, smax, tmax;\n\tglRect_t *theRect;\n\tqbool lightstyle_modified = false;\n\tlightmap_data_t* lm;\n\n\tif (!R_SoftwareLighting() && !fa->cached_dlight) {\n\t\treturn;\n\t}\n\tif (fa->lightmaptexturenum < 0) {\n\t\treturn;\n\t}\n\n\t// check for lightmap modification\n\tfor (maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++) {\n\t\tif (d_lightstylevalue[fa->styles[maps]] != fa->cached_light[maps]) {\n\t\t\tlightstyle_modified = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (R_SoftwareLighting()) {\n\t\tif (fa->dlightframe == r_framecount) {\n\t\t\tR_BuildDlightList(fa);\n\t\t}\n\t\telse {\n\t\t\tnumdlights = 0;\n\t\t}\n\n\t\tif (numdlights == 0 && !fa->cached_dlight && !lightstyle_modified) {\n\t\t\treturn;\n\t\t}\n\t}\n\telse {\n\t\tnumdlights = 0;\n\t}\n\n\tlm = &lightmaps[fa->lightmaptexturenum];\n\tlm->modified = true;\n\ttheRect = &lm->change_area;\n\tif (fa->light_t < theRect->t) {\n\t\tif (theRect->h) {\n\t\t\ttheRect->h += theRect->t - fa->light_t;\n\t\t}\n\t\ttheRect->t = fa->light_t;\n\t}\n\tif (fa->light_s < theRect->l) {\n\t\tif (theRect->w) {\n\t\t\ttheRect->w += theRect->l - fa->light_s;\n\t\t}\n\t\ttheRect->l = fa->light_s;\n\t}\n\tsmax = (fa->extents[0] >> fa->lmshift) + 1;\n\ttmax = (fa->extents[1] >> fa->lmshift) + 1;\n\tif (theRect->w + theRect->l < fa->light_s + smax) {\n\t\ttheRect->w = fa->light_s - theRect->l + smax;\n\t}\n\tif (theRect->h + theRect->t < fa->light_t + tmax) {\n\t\ttheRect->h = fa->light_t - theRect->t + tmax;\n\t}\n\tbase = lm->rawdata + (fa->light_t * LIGHTMAP_WIDTH + fa->light_s) * 4;\n\tR_BuildLightMap (fa, base, LIGHTMAP_WIDTH * 4, world ? cl.worldmodel->flags : 0);\n}\n\nvoid R_LightmapFrameInit(void)\n{\n\tframeStats.lightmap_min_changed = lightmap_array_size;\n\tframeStats.lightmap_max_changed = 0;\n\n\trenderer.LightmapFrameInit();\n}\n\nstatic void R_RenderAllDynamicLightmapsForChain(msurface_t* surface, qbool world)\n{\n\tint k;\n\tmsurface_t* s;\n\tframeStatsPolyType polyType;\n\n\tif (!surface) {\n\t\treturn;\n\t}\n\n\tR_TraceEnterFunctionRegion;\n\n\tpolyType = world ? polyTypeWorldModel : polyTypeBrushModel;\n\tfor (s = surface; s; s = s->texturechain) {\n\t\tk = s->lightmaptexturenum;\n\n\t\t++frameStats.classic.polycount[polyType];\n\n\t\tif (k >= 0 && !(s->flags & (SURF_DRAWTURB | SURF_DRAWSKY))) {\n\t\t\trenderer.RenderDynamicLightmaps(s, world);\n\n\t\t\tif (lightmaps[k].modified) {\n\t\t\t\tframeStats.lightmap_min_changed = min(k, frameStats.lightmap_min_changed);\n\t\t\t\tframeStats.lightmap_max_changed = max(k, frameStats.lightmap_max_changed);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (s = surface->drawflatchain; s; s = s->drawflatchain) {\n\t\tk = s->lightmaptexturenum;\n\n\t\t++frameStats.classic.polycount[polyType];\n\n\t\tif (k >= 0 && !(s->flags & (SURF_DRAWTURB | SURF_DRAWSKY))) {\n\t\t\trenderer.RenderDynamicLightmaps(s, world);\n\n\t\t\tif (lightmaps[k].modified) {\n\t\t\t\tframeStats.lightmap_min_changed = min(k, frameStats.lightmap_min_changed);\n\t\t\t\tframeStats.lightmap_max_changed = max(k, frameStats.lightmap_max_changed);\n\t\t\t}\n\t\t}\n\t}\n\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid R_UploadChangedLightmaps(void)\n{\n\tif (r_lightmap_lateupload.integer == 1) {\n\t\treturn;\n\t}\n\n\tR_TraceEnterNamedRegion(__func__);\n\tif (R_HardwareLighting()) {\n\t\tif (R_FullBrightAllowed() || !cl.worldmodel || !cl.worldmodel->lightdata) {\n\t\t\tint i;\n\n\t\t\tfor (i = frameStats.lightmap_min_changed; i <= frameStats.lightmap_max_changed; ++i) {\n\t\t\t\tif (lightmaps[i].modified) {\n\t\t\t\t\tmemset(lightmaps[i].rawdata, 255, sizeof(lightmaps[i].rawdata));\n\t\t\t\t\tR_UploadLightMap(0, i);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tframeStats.lightmap_min_changed = lightmap_array_size;\n\t\t\tframeStats.lightmap_max_changed = 0;\n\t\t}\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\t\telse if (R_UseModernOpenGL()) {\n\t\t\tGLM_ComputeLightmaps();\n\t\t}\n#endif\n\t}\n\telse if (frameStats.lightmap_min_changed < lightmap_array_size) {\n\t\tunsigned int i;\n\n\t\tfor (i = frameStats.lightmap_min_changed; i <= frameStats.lightmap_max_changed; ++i) {\n\t\t\tif (lightmaps[i].modified) {\n\t\t\t\tR_UploadLightMap(0, i);\n\t\t\t}\n\t\t}\n\n\t\tframeStats.lightmap_min_changed = lightmap_array_size;\n\t\tframeStats.lightmap_max_changed = 0;\n\t}\n\tR_TraceLeaveNamedRegion();\n}\n\nvoid R_RenderAllDynamicLightmaps(model_t *model)\n{\n\tunsigned int i;\n\n\tR_TraceEnterFunctionRegion;\n\n\tfor (i = 0; i < model->numtextures; i++) {\n\t\tif (!model->textures[i]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tR_TraceEnterRegion(model->textures[i]->name, true);\n\t\tR_RenderAllDynamicLightmapsForChain(model->textures[i]->texturechain, model->isworldmodel);\n\t\tR_TraceLeaveRegion(true);\n\t}\n\n\tR_TraceEnterRegion(\"drawflat_chain\", true);\n\tR_RenderAllDynamicLightmapsForChain(model->drawflat_chain, model->isworldmodel);\n\tR_TraceLeaveRegion(true);\n\n\tfor (i = 0; i < R_LightmapCount(); ++i) {\n\t\tmsurface_t* surf = R_DrawflatLightmapChain(i);\n\n\t\tif (surf) {\n\t\t\tR_TraceEnterRegion(va(\"lightmapChain[%d]\", i), true);\n\t\t\tR_RenderAllDynamicLightmapsForChain(surf, model->isworldmodel);\n\t\t\tR_TraceLeaveRegion(true);\n\t\t}\n\t}\n\n\tif (R_UseImmediateOpenGL()) {\n\t\tR_UploadChangedLightmaps();\n\t}\n\n\tR_TraceLeaveFunctionRegion;\n}\n\n// returns a lightmap number and the position inside it\nstatic int LightmapAllocBlock(int w, int h, int *x, int *y)\n{\n\tint i, j, best, best2;\n\tint texnum;\n\n\tif (w < 1 || w > LIGHTMAP_WIDTH || h < 1 || h > LIGHTMAP_HEIGHT) {\n\t\tSys_Error(\"AllocBlock: Bad dimensions (w: %d, h: %d)\", w, h);\n\t}\n\n\tfor (texnum = last_lightmap_updated; texnum < lightmap_array_size; texnum++, last_lightmap_updated++) {\n\t\tbest = LIGHTMAP_HEIGHT + 1;\n\n\t\tfor (i = 0; i < LIGHTMAP_WIDTH - w; i++) {\n\t\t\tbest2 = 0;\n\n\t\t\tfor (j = i; j < i + w; j++) {\n\t\t\t\tif (lightmaps[texnum].allocated[j] >= best) {\n\t\t\t\t\ti = j;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (lightmaps[texnum].allocated[j] > best2) {\n\t\t\t\t\tbest2 = lightmaps[texnum].allocated[j];\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (j == i + w) {\n\t\t\t\t// this is a valid spot\n\t\t\t\t*x = i;\n\t\t\t\t*y = best = best2;\n\t\t\t}\n\t\t}\n\n\t\tif (best + h > LIGHTMAP_HEIGHT) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (i = 0; i < w; i++) {\n\t\t\tlightmaps[texnum].allocated[*x + i] = best + h;\n\t\t}\n\n\t\treturn texnum;\n\t}\n\n\t// Dynamically increase array\n\t{\n\t\tunsigned int new_size = lightmap_array_size + LIGHTMAP_ARRAY_GROWTH;\n\t\tlightmaps = Q_realloc(lightmaps, sizeof(lightmaps[0]) * new_size);\n\t\tif (!lightmaps) {\n\t\t\tSys_Error(\"AllocBlock: full\");\n\t\t\treturn 0;\n\t\t}\n\t\tmemset(lightmaps + lightmap_array_size, 0, sizeof(lightmaps[0]) * LIGHTMAP_ARRAY_GROWTH);\n\t\tlightmap_array_size = new_size;\n\n\t\t// Memory pointers might now be invalid afer realloc()\n\t\tfor (i = 0; i < new_size; ++i) {\n\t\t\tlightmaps[i].drawflat_chain_tail = &lightmaps[i].drawflat_chain;\n\t\t}\n\t}\n\treturn LightmapAllocBlock(w, h, x, y);\n}\n\n#define EPSILON 1e-6\n\n// Check if triangle has a ~zero area\n// https://en.wikipedia.org/wiki/Collinearity\nstatic qbool R_ArePointsColinear(const vec3_t v1, const vec3_t v2, const vec3_t v3)\n{\n\tvec3_t d0, d1, cross;\n\n\tVectorSubtract(v2, v1, d0);\n\tVectorSubtract(v3, v2, d1);\n\n\tCrossProduct(d0, d1, cross);\n\n\treturn DotProduct(cross, cross) < EPSILON;\n}\n\nstatic void R_RemoveColinearVertices(glpoly_t *poly, float new_verts[][VERTEXSIZE])\n{\n\tint i, v1_index, v2_index, v3_index, new_numverts = 0;\n\tint numverts = poly->numverts;\n\n\tv1_index = numverts - 1;\n\tv2_index = 0;\n\tv3_index = 1;\n\n\tfor (i = 0; i < numverts; i++) {\n\t\tfloat *v1 = poly->verts[v1_index];\n\t\tfloat *v2 = poly->verts[v2_index];\n\t\tfloat *v3 = poly->verts[v3_index];\n\n\t\tif (!R_ArePointsColinear(v1, v2, v3)) {\n\t\t\tmemcpy(new_verts[new_numverts], v2, sizeof(float) * VERTEXSIZE);\n\t\t\tnew_numverts++;\n\t\t}\n\n\t\tv1_index = v2_index;\n\t\tv2_index = v3_index;\n\t\tv3_index = (v3_index + 1) % numverts;\n\t}\n\n\tif (new_numverts > 0) {\n\t\tmemcpy(poly->verts, new_verts, new_numverts * sizeof(float) * VERTEXSIZE);\n\t\tpoly->numverts = new_numverts;\n\t}\n}\n\n// \nstatic void R_BuildSurfaceDisplayList(model_t* currentmodel, msurface_t *fa)\n{\n\textern cvar_t r_remove_collinear_vertices;\n\tint i, lindex, lnumverts;\n\tmedge_t *pedges, *r_pedge;\n\tfloat *vec, s, t;\n\tglpoly_t *poly;\n\tqbool isTurb = (fa->flags & SURF_DRAWTURB);\n\n\t// reconstruct the polygon\n\tpedges = currentmodel->edges;\n\tlnumverts = fa->numedges;\n\n\t// draw texture\n\tif (!fa->polys) { // seems map loaded first time, so light maps loaded first time too\n\t\tpoly = (glpoly_t *)Hunk_AllocName(sizeof(glpoly_t) + (lnumverts - 4) * VERTEXSIZE * sizeof(float), \"lmpoly\");\n\t\tpoly->next = fa->polys;\n\t\tfa->polys = poly;\n\t\tpoly->numverts = lnumverts;\n\t}\n\telse { // seems vid_restart issued, so do not allocate memory, we alredy done it, I hope\n\t\tpoly = fa->polys;\n\t}\n\n\tfor (i = 0; i < lnumverts; i++) {\n\t\tlindex = currentmodel->surfedges[fa->firstedge + i];\n\n\t\tif (lindex > 0) {\n\t\t\tr_pedge = &pedges[lindex];\n\t\t\tvec = currentmodel->vertexes[r_pedge->v[0]].position;\n\t\t}\n\t\telse {\n\t\t\tr_pedge = &pedges[-lindex];\n\t\t\tvec = currentmodel->vertexes[r_pedge->v[1]].position;\n\t\t}\n\n\t\ts = DotProduct(vec, fa->texinfo->vecs[0]);\n\t\tif (!isTurb) {\n\t\t\ts += fa->texinfo->vecs[0][3];\n\t\t\ts /= fa->texinfo->texture->width;\n\t\t}\n\t\telse {\n\t\t\ts /= 64;\n\t\t}\n\n\t\tt = DotProduct(vec, fa->texinfo->vecs[1]);\n\t\tif (!isTurb) {\n\t\t\tt += fa->texinfo->vecs[1][3];\n\t\t\tt /= fa->texinfo->texture->height;\n\t\t}\n\t\telse {\n\t\t\tt /= 64;\n\t\t}\n\n\t\tVectorCopy(vec, poly->verts[i]);\n\n\t\t// material\n\t\tpoly->verts[i][3] = s;\n\t\tpoly->verts[i][4] = t;\n\n\t\t// lightmap texture coordinates\n\t\tif (isTurb && !fa->texinfo->texture->isLitTurb) {\n\t\t\tpoly->verts[i][5] = poly->verts[i][6] = 0;\n\t\t\tpoly->verts[i][7] = poly->verts[i][8] = 0;\n\t\t}\n\t\telse {\n\t\t\ts = DotProduct(vec, fa->lmvecs[0]) + fa->lmvecs[0][3];\n\t\t\ts -= fa->texturemins[0];\n\t\t\ts += fa->light_s * (1 << fa->lmshift);\n\t\t\ts += (1 << fa->lmshift) * 0.5;\n\t\t\ts /= LIGHTMAP_WIDTH * (1 << fa->lmshift);\n\n\t\t\tt = DotProduct(vec, fa->lmvecs[1]) + fa->lmvecs[1][3];\n\t\t\tt -= fa->texturemins[1];\n\t\t\tt += fa->light_t * (1 << fa->lmshift);\n\t\t\tt += (1 << fa->lmshift) * 0.5;\n\t\t\tt /= LIGHTMAP_HEIGHT * (1 << fa->lmshift);\n\n\t\t\tpoly->verts[i][5] = s;\n\t\t\tpoly->verts[i][6] = t;\n\n\t\t\ts = DotProduct(vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3];\n\t\t\ts /= 128;\n\n\t\t\tt = DotProduct(vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3];\n\t\t\tt /= 128;\n\n\t\t\t// Detail textures\n\t\t\tpoly->verts[i][7] = 18 * s;\n\t\t\tpoly->verts[i][8] = 18 * t;\n\t\t}\n\t}\n\tpoly->numverts = lnumverts;\n\n\t// Some GPUs misbehave if fed triangles of empty size.\n\tif (r_remove_collinear_vertices.value) {\n\t\tif (poly->numverts > 4) {\n\t\t\tfloat (*new_verts)[VERTEXSIZE] = Q_malloc(poly->numverts * sizeof(float[VERTEXSIZE]));\n\t\t\tR_RemoveColinearVertices(poly, new_verts);\n\t\t\tQ_free(new_verts);\n\t\t} else {\n\t\t\tfloat new_verts[4][VERTEXSIZE];\n\t\t\tR_RemoveColinearVertices(poly, new_verts);\n\t\t}\n\t}\n\n\tR_BrushModelPolygonToTriangleStrip(poly);\n}\n\nstatic void R_BuildLightmapData(msurface_t* surf, int surfnum)\n{\n\tlightmap_data_t* lm = &lightmaps[surf->lightmaptexturenum];\n\tunsigned int smax = (surf->extents[0] >> surf->lmshift) + 1;\n\tunsigned int tmax = (surf->extents[1] >> surf->lmshift) + 1;\n\tunsigned int lightmap_flags;\n\tint s, t, maps;\n\n\tlightmap_flags = 0xFFFFFFFF;\n\tfor (maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255; maps++) {\n\t\tlightmap_flags &= ~(0xFF << (8 * maps));\n\t\tlightmap_flags |= surf->styles[maps] << (8 * maps);\n\t}\n\n\tfor (t = 0; t < tmax; ++t) {\n\t\tsize_t offset = ((surf->light_t + t) * LIGHTMAP_WIDTH + surf->light_s) * 4;\n\t\tint* data = &lm->computeData[offset];\n\t\tunsigned int* source = &lm->sourcedata[offset];\n\n\t\tfor (s = 0; s < smax; ++s, data += 4, source += 4) {\n\t\t\tdata[0] = surfnum;\n\t\t\tdata[1] = (s * (1 << surf->lmshift) + surf->texturemins[0] - surf->lmvecs[0][3]);\n\t\t\tdata[2] = (t * (1 << surf->lmshift) + surf->texturemins[1] - surf->lmvecs[1][3]);\n\t\t\tdata[3] = 0;\n\n\t\t\tsource[0] = source[1] = source[2] = 0;\n\t\t\tsource[3] = lightmap_flags;\n\n\t\t\tif (surf->samples) {\n\t\t\t\tif (surfnum != -1 && cl.worldmodel->flags & MOD_HDRLIGHTING) {\n\t\t\t\t\tuint32_t* lightmap = (uint32_t *)surf->samples;\n\t\t\t\t\tfor (maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255; maps++) {\n\t\t\t\t\t\tsize_t lightmap_index = (maps * smax * tmax + t * smax + s);\n\t\t\t\t\t\tuint32_t e5bgr9 = lightmap[lightmap_index];\n\t\t\t\t\t\t//we're converting to a scale that holds overbrights, so 1->128, its 2->255ish\n\t\t\t\t\t\tfloat e = rgb9e5tab[e5bgr9>>27] * (1<<7);\n\t\t\t\t\t\t// should not clamp here, but changing the light compute shader can be done later\n\t\t\t\t\t\tsource[0] += (unsigned int)bound(0, e*((e5bgr9>> 0)&0x1ff), 0xff) << (8 * maps);\n\t\t\t\t\t\tsource[1] += (unsigned int)bound(0, e*((e5bgr9>> 9)&0x1ff), 0xff) << (8 * maps);\n\t\t\t\t\t\tsource[2] += (unsigned int)bound(0, e*((e5bgr9>>18)&0x1ff), 0xff) << (8 * maps);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tbyte* lightmap = surf->samples;\n\t\t\t\t\tfor (maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255; maps++) {\n\t\t\t\t\t\tsize_t lightmap_index = (maps * smax * tmax + t * smax + s) * 3;\n\n\t\t\t\t\t\tsource[0] |= ((unsigned int)lightmap[lightmap_index + 0]) << (8 * maps);\n\t\t\t\t\t\tsource[1] |= ((unsigned int)lightmap[lightmap_index + 1]) << (8 * maps);\n\t\t\t\t\t\tsource[2] |= ((unsigned int)lightmap[lightmap_index + 2]) << (8 * maps);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void R_LightmapCreateForSurface(msurface_t *surf, int surfnum, uint32_t flags)\n{\n\tint smax, tmax;\n\tbyte *base;\n\n\tsmax = (surf->extents[0] >> surf->lmshift) + 1;\n\ttmax = (surf->extents[1] >> surf->lmshift) + 1;\n\n\tif (smax > LIGHTMAP_WIDTH) {\n\t\tHost_Error(\"%s: smax = %d > BLOCK_WIDTH\", __func__, smax);\n\t}\n\tif (tmax > LIGHTMAP_HEIGHT) {\n\t\tHost_Error(\"%s: tmax = %d > BLOCK_HEIGHT\", __func__, tmax);\n\t}\n\tif (smax * tmax > MAX_LIGHTMAP_SIZE) {\n\t\tHost_Error(\"%s: smax * tmax = %d > MAX_LIGHTMAP_SIZE\", __func__, smax * tmax);\n\t}\n\n\tsurf->lightmaptexturenum = LightmapAllocBlock(smax, tmax, &surf->light_s, &surf->light_t);\n\n\tbase = lightmaps[surf->lightmaptexturenum].rawdata + (surf->light_t * LIGHTMAP_WIDTH + surf->light_s) * 4;\n\tnumdlights = 0;\n\tR_BuildLightmapData(surf, surfnum);\n\tR_BuildLightMap(surf, base, LIGHTMAP_WIDTH * 4, flags);\n}\n\nstatic int R_LightmapSurfaceSortFunction(const void* lhs_, const void* rhs_)\n{\n\tconst msurface_t* lhs = *(const msurface_t**)lhs_;\n\tconst msurface_t* rhs = *(const msurface_t**)rhs_;\n\n\tif (r_lightmap_packbytexture.integer == 1) {\n\t\tint lhs_size = ((lhs->extents[0] >> lhs->lmshift) + 1) * ((lhs->extents[1] >> lhs->lmshift) + 1);\n\t\tint rhs_size = ((rhs->extents[0] >> rhs->lmshift) + 1) * ((rhs->extents[1] >> rhs->lmshift) + 1);\n\n\t\treturn rhs_size - lhs_size;\n\t}\n\telse if (r_lightmap_packbytexture.integer == 2) {\n\t\tint lhs_width = ((lhs->extents[0] >> lhs->lmshift) + 1);\n\t\tint lhs_height = ((lhs->extents[1] >> lhs->lmshift) + 1);\n\t\tint rhs_width = ((rhs->extents[0] >> rhs->lmshift) + 1);\n\t\tint rhs_height = ((rhs->extents[1] >> rhs->lmshift) + 1);\n\n\t\tint height_diff = rhs_height - lhs_height;\n\t\tint width_diff = rhs_width - lhs_width;\n\n\t\tif (height_diff) {\n\t\t\treturn height_diff;\n\t\t}\n\t\treturn width_diff;\n\t}\n\telse {\n\t\treturn 0;\n\t}\n}\n\n//Builds the lightmap texture with all the surfaces from all brush models\n//Only called when map is initially loaded\nvoid R_BuildLightmaps(void)\n{\n\tint i, j, t;\n\tmodel_t\t*m;\n\n\tif (lightmaps) {\n\t\tfor (i = 0; i < lightmap_array_size; ++i) {\n\t\t\tlightmaps[i].drawflat_chain = NULL;\n\t\t\tlightmaps[i].drawflat_chain_tail = &lightmaps[i].drawflat_chain;\n\t\t\tmemset(lightmaps[i].allocated, 0, sizeof(lightmaps[i].allocated));\n\t\t}\n\t}\n\telse {\n\t\tlightmaps = Q_malloc(sizeof(*lightmaps) * LIGHTMAP_ARRAY_GROWTH);\n\t\tlightmap_array_size = LIGHTMAP_ARRAY_GROWTH;\n\t\tfor (i = 0; i < lightmap_array_size; ++i) {\n\t\t\tlightmaps[i].drawflat_chain_tail = &lightmaps[i].drawflat_chain;\n\t\t}\n\t}\n\tlast_lightmap_updated = 0;\n\n\tgl_invlightmaps = R_UseImmediateOpenGL();\n\n\tr_framecount = 1;\t\t// no dlightcache\n\tfor (j = 1; j < MAX_MODELS; j++) {\n\t\tif (!(m = cl.model_precache[j])) {\n\t\t\tbreak;\n\t\t}\n\t\tif (m->name[0] == '*') {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// mark all surfaces as needing lightmap\n\t\tfor (i = 0; i < m->numsurfaces; i++) {\n\t\t\tm->surfaces[i].surfacenum = i;\n\t\t\tm->surfaces[i].lightmaptexturenum = -1;\n\t\t}\n\n\t\t// assign in order based on texture and then by size\n\t\tif (r_lightmap_packbytexture.integer) {\n\t\t\tmsurface_t** surfaces = Q_malloc(m->numsurfaces * sizeof(msurface_t*));\n\n\t\t\tfor (t = 0; t < m->numtextures; ++t) {\n\t\t\t\tint surface_count = 0;\n\n\t\t\t\t// create list of surfaces that need processed for this texture\n\t\t\t\tfor (i = 0; i < m->numsurfaces; i++) {\n\t\t\t\t\tqbool isTurb = (m->surfaces[i].flags & SURF_DRAWTURB);\n\t\t\t\t\tqbool isSky = (m->surfaces[i].flags & SURF_DRAWSKY);\n\n\t\t\t\t\tif (m->surfaces[i].texinfo->miptex != t) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (isSky || m->surfaces[i].lightmaptexturenum >= 0) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (!isTurb && (m->surfaces[i].texinfo->flags & TEX_SPECIAL)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tsurfaces[surface_count++] = &m->surfaces[i];\n\t\t\t\t}\n\n\t\t\t\t// sort, biggest first\n\t\t\t\tqsort(surfaces, surface_count, sizeof(surfaces[0]), R_LightmapSurfaceSortFunction);\n\n\t\t\t\t//\n\t\t\t\tfor (i = 0; i < surface_count; ++i) {\n\t\t\t\t\tqbool isTurb = (surfaces[i]->flags & SURF_DRAWTURB);\n\n\t\t\t\t\tif (!isTurb) {\n\t\t\t\t\t\tR_LightmapCreateForSurface(surfaces[i], m->isworldmodel ? surfaces[i]->surfacenum : -1, m->flags);\n\t\t\t\t\t}\n\t\t\t\t\tR_BuildSurfaceDisplayList(m, surfaces[i]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tQ_free(surfaces);\n\t\t}\n\n\t\t// Double-check: go through all textures again\n\t\tfor (i = 0; i < m->numsurfaces; i++) {\n\t\t\tqbool isTurb = (m->surfaces[i].flags & SURF_DRAWTURB);\n\t\t\tqbool isSky = (m->surfaces[i].flags & SURF_DRAWSKY);\n\n\t\t\tif (isSky || m->surfaces[i].lightmaptexturenum >= 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isTurb && (m->surfaces[i].texinfo->flags & TEX_SPECIAL)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!isTurb || !(m->surfaces[i].texinfo->flags & TEX_SPECIAL)) {\n\t\t\t\tR_LightmapCreateForSurface(m->surfaces + i, m->isworldmodel ? m->surfaces[i].surfacenum : -1, m->flags);\n\t\t\t}\n\t\t\tR_BuildSurfaceDisplayList(m, m->surfaces + i);\n\t\t}\n\t}\n\n\t// upload all lightmaps that were filled\n\trenderer.CreateLightmapTextures();\n\n\tfor (i = 0; i < lightmap_array_size; i++) {\n\t\tif (!lightmaps[i].allocated[0]) {\n\t\t\tbreak;\t\t// not used anymore\n\t\t}\n\t\tlightmaps[i].modified = false;\n\t\tlightmaps[i].change_area.l = LIGHTMAP_WIDTH;\n\t\tlightmaps[i].change_area.t = LIGHTMAP_HEIGHT;\n\t\tlightmaps[i].change_area.w = 0;\n\t\tlightmaps[i].change_area.h = 0;\n\t\trenderer.BuildLightmap(i);\n\t}\n}\n\nvoid R_InvalidateLightmapTextures(void)\n{\n\tint i;\n\n\tif (renderer.InvalidateLightmapTextures) {\n\t\trenderer.InvalidateLightmapTextures();\n\t}\n\n\tfor (i = 0; i < lightmap_array_size; ++i) {\n\t\tR_TextureReferenceInvalidate(lightmaps[i].gl_texref);\n\t}\n\n\tQ_free(lightmaps);\n\tlightmap_array_size = 0;\n\tlast_lightmap_updated = 0;\n}\n\nvoid R_LightmapShutdown(void)\n{\n\tQ_free(lightmaps);\n\tlightmap_array_size = 0;\n\n\tif (renderer.LightmapShutdown) {\n\t\trenderer.LightmapShutdown();\n\t}\n}\n\n// mark all surfaces so ALL light maps will reload in R_RenderDynamicLightmaps()\nvoid R_ForceReloadLightMaps(void)\n{\n\tmodel_t\t*m;\n\tint i, j;\n\n\tCom_DPrintf(\"forcing of reloading all light maps!\\n\");\n\n\tfor (j = 1; j < MAX_MODELS; j++) {\n\t\tif (!(m = cl.model_precache[j])) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (m->name[0] == '*') {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (i = 0; i < m->numsurfaces; i++) {\n\t\t\tm->surfaces[i].cached_dlight = true; // kinda hack, so we force reload light map\n\t\t}\n\t}\n}\n\nqbool R_FullBrightAllowed(void)\n{\n\textern cvar_t r_fullbright;\n\n\treturn r_fullbright.value && r_refdef2.allow_cheats;\n}\n\nvoid R_CheckReloadLightmaps(void)\n{\n\tstatic qbool allowed;\n\tstatic qbool hardware_lighting;\n\tstatic float modulate;\n\n\t// not changed, nothing to do\n\tif (allowed == R_FullBrightAllowed() && hardware_lighting == R_HardwareLighting() && modulate == gl_modulate.value) {\n\t\treturn;\n\t}\n\n\t// ok, it changed, lets update all our light maps...\n\tallowed = R_FullBrightAllowed();\n\thardware_lighting = R_HardwareLighting();\n\tmodulate = gl_modulate.value;\n\n\tR_ForceReloadLightMaps();\n}\n\nunsigned int R_LightmapCount(void)\n{\n\treturn lightmap_array_size;\n}\n\nstruct msurface_s* R_DrawflatLightmapChain(int i)\n{\n\treturn lightmaps[i].drawflat_chain;\n}\n\nvoid R_ClearDrawflatLightmapChain(int i)\n{\n\tlightmaps[i].drawflat_chain = NULL;\n\tlightmaps[i].drawflat_chain_tail = &lightmaps[i].drawflat_chain;\n}\n\nvoid R_AddDrawflatChainSurface(struct msurface_s* surf, qbool floor)\n{\n\tlightmap_data_t* lm = &lightmaps[surf->lightmaptexturenum];\n\n\tif (!lm->drawflat_chain_tail) {\n\t\tlm->drawflat_chain_tail = &lm->drawflat_chain;\n\t}\n\n\tif (floor) {\n\t\t*lm->drawflat_chain_tail = surf;\n\t\tlm->drawflat_chain_tail = &surf->drawflatchain;\n\t\tsurf->drawflatchain = NULL;\n\t}\n\telse {\n\t\tsurf->drawflatchain = lm->drawflat_chain;\n\t\tif (lm->drawflat_chain_tail == &lm->drawflat_chain) {\n\t\t\tlm->drawflat_chain_tail = &surf->drawflatchain;\n\t\t}\n\t\tlm->drawflat_chain = surf;\n\t}\n}\n"
  },
  {
    "path": "src/r_lightmaps.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_R_LIGHTMAPS_HEADER\n#define EZQUAKE_R_LIGHTMAPS_HEADER\n\nvoid R_BuildLightmaps(void);\nqbool R_FullBrightAllowed(void);\nvoid R_CheckReloadLightmaps(void);\nvoid R_RenderAllDynamicLightmaps(struct model_s* model);\nvoid R_LightmapFrameInit(void);\nvoid R_ForceReloadLightMaps(void);\nunsigned int R_LightmapCount(void);\nstruct msurface_s* R_DrawflatLightmapChain(int i);\nvoid R_ClearDrawflatLightmapChain(int i);\nvoid R_AddDrawflatChainSurface(struct msurface_s* surf, qbool floor);\n\n#endif // EZQUAKE_R_LIGHTMAPS_HEADER\n"
  },
  {
    "path": "src/r_lightmaps_internal.h",
    "content": "\n#ifndef EZQUAKE_R_LIGHTMAPS_INTERNAL_HEADER\n#define EZQUAKE_R_LIGHTMAPS_INTERNAL_HEADER\n\n// Lightmap size\n#define\tLIGHTMAP_WIDTH  128\n#define\tLIGHTMAP_HEIGHT 128\n\n#define MAX_LIGHTMAP_SIZE (64 * 64)\n#define LIGHTMAP_ARRAY_GROWTH 4\n\ntypedef struct glRect_s {\n\tunsigned int l, t, w, h;\n} glRect_t;\n\ntypedef struct lightmap_data_s {\n\tbyte rawdata[4 * LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT];\n\tint computeData[4 * LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT];\n\tunsigned int sourcedata[4 * LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT];\n\tint allocated[LIGHTMAP_WIDTH];\n\n\ttexture_ref gl_texref;\n\tglpoly_t* poly_chain;\n\tmsurface_t* drawflat_chain;\n\tmsurface_t** drawflat_chain_tail;\n\tglRect_t change_area;\n\tqbool modified;\n} lightmap_data_t;\n\nextern lightmap_data_t* lightmaps;\nextern unsigned int lightmap_array_size;\n\nvoid GLM_LightmapFrameInit(void);\nvoid GLM_RenderDynamicLightmaps(msurface_t* surface, qbool world);\nvoid GLM_ComputeLightmaps(void);\n\n#endif // EZQUAKE_R_LIGHTMAPS_INTERNAL_HEADER\n"
  },
  {
    "path": "src/r_local.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_R_LOCAL_HEADER\n#define EZQUAKE_R_LOCAL_HEADER\n\n#include \"r_buffers.h\"\n\nextern int glx, gly, glwidth, glheight;\n\nvoid R_BeginRendering(int *x, int *y, int *width, int *height);\nvoid R_EndRendering(void);\nvoid R_Set2D(void);\nvoid R_PolyBlend(void);\nvoid R_OnDisconnect(void);\nvoid R_StateDefault3D(void);\n\n// 2d rendering\nvoid R_FlushImageDraw(void);\nvoid R_EmptyImageQueue(void);\n\n// culling\nqbool R_CullBox(vec3_t mins, vec3_t maxs);\nqbool R_CullSphere(vec3_t centre, float radius);\n\n// vis\nextern refdef_t\tr_refdef;\nextern struct mleaf_s* r_viewleaf;\nextern struct mleaf_s* r_oldviewleaf;\nextern struct mleaf_s* r_viewleaf2;\nextern struct mleaf_s* r_oldviewleaf2;\t// 2 is for watervis hack\n\n// Using multiple renderers?\n#ifdef EZ_MULTIPLE_RENDERERS\n#undef EZ_MULTIPLE_RENDERERS\n#endif\n\n#if defined(RENDERER_OPTION_CLASSIC_OPENGL) && defined(RENDERER_OPTION_MODERN_OPENGL)\n#define EZ_MULTIPLE_RENDERERS\n\nextern cvar_t vid_renderer;\n#define R_UseImmediateOpenGL()    (vid_renderer.integer == 0)\n#define R_UseModernOpenGL()       (vid_renderer.integer == 1)\n#define R_UseVulkan()             (vid_renderer.integer == 2)\n\nvoid R_SelectRenderer(void);\n#endif\n\n#ifndef EZ_MULTIPLE_RENDERERS\n#define R_SelectRenderer()\n#if defined(RENDERER_OPTION_CLASSIC_OPENGL)\n#define R_UseImmediateOpenGL()    (1)\n#define R_UseModernOpenGL()       (0)\n#define R_UseVulkan()             (0)\n#elif defined(RENDERER_OPTION_MODERN_OPENGL)\n#define R_UseImmediateOpenGL()    (0)\n#define R_UseModernOpenGL()       (1)\n#define R_UseVulkan()             (0)\n#else\n#error No renderer options defined\n#endif\n#endif\n\n// Debug profile may or may not do anything, but if it does anything it's slower, so only enable in dev mode\n#define R_DebugProfileContext()  ((IsDeveloperMode() && COM_CheckParm(cmdline_param_client_video_r_debug)) || COM_CheckParm(cmdline_param_client_video_r_trace))\n#define R_CompressFullbrightTextures() (!R_UseImmediateOpenGL())\n#define R_LumaTexturesMustMatchDimensions() (!R_UseImmediateOpenGL())\n#define R_UseCubeMapForSkyBox() (!R_UseImmediateOpenGL() || GL_Supported(R_SUPPORT_SEAMLESS_CUBEMAPS))\n\n// bloom.c\nvoid R_InitBloomTextures(void);\nvoid R_BloomBlend(void);\n\n// r_draw.c (here for atlas)\n#define NUMCROSSHAIRS 6\n\n// r_main.c\ntypedef enum {\n\tr_shutdown_full = 0,       // shutting down program\n\tr_shutdown_restart = 1,    // restarting gfx system (window destroyed & recreated)\n\tr_shutdown_reload = 2      // soft restart (reload textures only)\n} r_shutdown_mode_t;\n\nvoid R_NewMapPrepare(qbool vid_restart);\nvoid R_Shutdown(r_shutdown_mode_t mode);\nvoid VID_GfxInfo_f(void);\nint VID_DisplayNumber(qbool fullscreen);\n\n// Shorthand\n#define ISUNDERWATER(contents) (contents == CONTENTS_WATER || contents == CONTENTS_SLIME || contents == CONTENTS_LAVA)\n//#define TruePointContents(p) CM_HullPointContents(&cl.worldmodel->hulls[0], 0, p)\n#define TruePointContents(p) CM_HullPointContents(&cl.clipmodels[1]->hulls[0], 0, p) // ?TONIK?\n\nextern int r_framecount;\n\n// palette\nvoid Check_Gamma(unsigned char *pal);\nvoid VID_SetPalette(unsigned char *palette);\nqbool R_OldGammaBehaviour(void);\n\nvoid R_Initialise(void);\nfloat R_WaterAlpha(void);\n\n// 3d rendering limits\n#define R_MINIMUM_NEARCLIP (2.0f)\n#define R_MAXIMUM_NEARCLIP (4.0f)\n#define R_MINIMUM_FARCLIP (4096.0f)\n#define R_MAXIMUM_FARCLIP (16384.0f)\nfloat R_FarPlaneZ(void);\nfloat R_NearPlaneZ(void);\n\n#endif // EZQUAKE_R_LOCAL_HEADER\n"
  },
  {
    "path": "src/r_main.c",
    "content": "\n#include \"quakedef.h\"\n#include \"common_draw.h\"\n#include \"r_local.h\"\n#include \"r_vao.h\"\n#include \"r_lightmaps.h\"\n#include \"gl_local.h\"\n#include \"glc_local.h\"\n#include \"glm_local.h\"\n#include \"r_texture.h\"\n#include \"r_renderer.h\"\n\nvoid GLM_Initialise(void);\nvoid GLC_Initialise(void);\n\nvoid CachePics_Shutdown(void);\nvoid R_LightmapShutdown(void);\nvoid R_Hud_Initialise(void);\nvoid R_BrushModelFreeMemory(void);\n\nrenderer_api_t renderer;\n\nvoid R_NewMapPrepare(qbool vid_restart)\n{\n\tif (cl.worldmodel) {\n\t\tR_BuildLightmaps();\n\t}\n\trenderer.PrepareModelRendering(vid_restart);\n}\n\nvoid VID_GfxInfo_f(void)\n{\n\trenderer.PrintGfxInfo();\n}\n\nvoid R_Shutdown(r_shutdown_mode_t mode)\n{\n\tif (renderer.Shutdown) {\n\t\trenderer.Shutdown(mode);\n\t}\n\n\tCachePics_Shutdown();\n\tR_LightmapShutdown();\n\n\t// Not texture related so leave alone (FIXME: move to renderer.Shutdown)\n\tif (mode != r_shutdown_reload) {\n\t\tR_BrushModelFreeMemory();\n\t\tif (renderer.DeleteVAOs) {\n\t\t\trenderer.DeleteVAOs();\n\t\t}\n\t\tif (buffers.Shutdown) {\n\t\t\tbuffers.Shutdown();\n\t\t}\n\t}\n\tR_DeleteTextures();\n\tR_TexturesInvalidateAllReferences();\n}\n\n#ifdef EZ_MULTIPLE_RENDERERS\nvoid R_SelectRenderer(void)\n{\n\tint i;\n\tconst int renderer_options[] = { \n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t\t0,\n#endif\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\t\t1,\n#endif\n#ifdef RENDERER_OPTION_VULKAN\n\t\t2\n#endif\n\t};\n\n\tfor (i = 0; i < sizeof(renderer_options) / sizeof(renderer_options[0]); ++i) {\n\t\tif (renderer_options[i] == vid_renderer.integer) {\n\t\t\t// Selected renderer is valid\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// Fall back to first on list\n\tCvar_LatchedSetValue(&vid_renderer, renderer_options[0]);\n\treturn;\n}\n#endif // EZ_MULTIPLE_RENDERERS\n\nvoid R_Initialise(void)\n{\n\tR_SelectRenderer();\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\tif (R_UseModernOpenGL()) {\n\t\tGLM_Initialise();\n\t}\n#endif\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_Initialise();\n\t}\n#endif\n#ifdef RENDERER_OPTION_VULKAN\n\tif (R_UseVulkan()) {\n\t\tVK_Initialise();\n\t\tVK_PopulateConfig();\n\t\tVK_InitialiseVAOHandling();\n\t\tVK_InitialiseBufferHandling(&buffers);\n\t\tVK_InitialiseState();\n\t}\n#endif\n\tR_Hud_Initialise();\n}\n\n// Called during disconnect\nvoid R_OnDisconnect(void)\n{\n\tR_ClearModelTextureData();\n}\n"
  },
  {
    "path": "src/r_matrix.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_local.h\"\n#include \"glc_matrix.h\"\n#include \"r_draw.h\"\n#include \"tr_types.h\"\n\nstatic float projectionMatrix[16];\nstatic float modelMatrix[16];\nstatic float identityMatrix[16] = {\n\t1, 0, 0, 0,\n\t0, 1, 0, 0,\n\t0, 0, 1, 0,\n\t0, 0, 0, 1\n};\n\nstatic void R_SetMatrix(float* target, const float* source)\n{\n\tmemcpy(target, source, sizeof(float) * 16);\n}\n\nvoid R_SetIdentityMatrix(float* matrix)\n{\n\tR_SetMatrix(matrix, identityMatrix);\n}\n\n// \nstatic const float* R_OrthoMatrix(float left, float right, float top, float bottom, float zNear, float zFar)\n{\n\tstatic float matrix[16];\n\n\tmemset(matrix, 0, sizeof(matrix));\n\tmatrix[0] = 2 / (right - left);\n\tmatrix[5] = 2 / (top - bottom);\n\tmatrix[10] = -2 / (zFar - zNear);\n\tmatrix[12] = -(right + left) / (right - left);\n\tmatrix[13] = -(top + bottom) / (top - bottom);\n\tmatrix[14] = -(zFar + zNear) / (zFar - zNear);\n\tmatrix[15] = 1;\n\n\treturn matrix;\n}\n\nfloat* R_ModelviewMatrix(void)\n{\n\treturn modelMatrix;\n}\n\nfloat* R_ProjectionMatrix(void)\n{\n\treturn projectionMatrix;\n}\n\nvoid R_OrthographicProjection(float left, float right, float top, float bottom, float zNear, float zFar)\n{\n\t// Deliberately inverting top & bottom here...\n\tR_SetMatrix(projectionMatrix, R_OrthoMatrix(left, right, bottom, top, zNear, zFar));\n\tR_Cache2DMatrix();\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_OrthographicProjection(left, right, top, bottom, zNear, zFar);\n\t}\n#endif\n}\n\nvoid R_RotateMatrix(float* matrix, float angle, float x, float y, float z)\n{\n\tvec3_t vec = { x, y, z };\n\tdouble s = sin(angle * M_PI / 180);\n\tdouble c = cos(angle * M_PI / 180);\n\tfloat rotation[16];\n\tfloat result[16];\n\n\tVectorNormalize(vec);\n\tx = vec[0];\n\ty = vec[1];\n\tz = vec[2];\n\n\t// Taken from https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glRotate.xml\n\trotation[0] = x * x * (1 - c) + c;\n\trotation[4] = x * y * (1 - c) - z * s;\n\trotation[8] = x * z * (1 - c) + y * s;\n\trotation[12] = 0;\n\trotation[1] = y * x * (1 - c) + z * s;\n\trotation[5] = y * y * (1 - c) + c;\n\trotation[9] = y * z * (1 - c) - x * s;\n\trotation[13] = 0;\n\trotation[2] = x * z * (1 - c) - y * s;\n\trotation[6] = y * z * (1 - c) + x * s;\n\trotation[10] = z * z * (1 - c) + c;\n\trotation[14] = 0;\n\trotation[3] = rotation[7] = rotation[11] = 0;\n\trotation[15] = 1;\n\n\tR_MultiplyMatrix(rotation, matrix, result);\n\tR_SetMatrix(matrix, result);\n}\n\nvoid R_RotateVector(vec3_t vector, float angle, float x, float y, float z)\n{\n\tvec3_t vec = { x, y, z };\n\tdouble s = sin(angle * M_PI / 180);\n\tdouble c = cos(angle * M_PI / 180);\n\tfloat rotation[16];\n\tfloat input[4] = { vector[0], vector[1], vector[2], 1 };\n\tfloat output[4];\n\n\tVectorNormalize(vec);\n\tx = vec[0];\n\ty = vec[1];\n\tz = vec[2];\n\n\t// Taken from https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glRotate.xml\n\trotation[0] = x * x * (1 - c) + c;\n\trotation[4] = x * y * (1 - c) - z * s;\n\trotation[8] = x * z * (1 - c) + y * s;\n\trotation[12] = 0;\n\trotation[1] = y * x * (1 - c) + z * s;\n\trotation[5] = y * y * (1 - c) + c;\n\trotation[9] = y * z * (1 - c) - x * s;\n\trotation[13] = 0;\n\trotation[2] = x * z * (1 - c) - y * s;\n\trotation[6] = y * z * (1 - c) + x * s;\n\trotation[10] = z * z * (1 - c) + c;\n\trotation[14] = 0;\n\trotation[3] = rotation[7] = rotation[11] = 0;\n\trotation[15] = 1;\n\n\tR_MultiplyVector(rotation, input, output);\n\tVectorCopy(output, vector);\n}\n\nvoid R_TransformMatrix(float* matrix, float x, float y, float z)\n{\n\tmatrix[12] += matrix[0] * x + matrix[4] * y + matrix[8] * z;\n\tmatrix[13] += matrix[1] * x + matrix[5] * y + matrix[9] * z;\n\tmatrix[14] += matrix[2] * x + matrix[6] * y + matrix[10] * z;\n\tmatrix[15] += matrix[3] * x + matrix[7] * y + matrix[11] * z;\n}\n\nvoid R_ScaleMatrix(float* matrix, float x_scale, float y_scale, float z_scale)\n{\n\tmatrix[0] *= x_scale;\n\tmatrix[1] *= x_scale;\n\tmatrix[2] *= x_scale;\n\tmatrix[3] *= x_scale;\n\tmatrix[4] *= y_scale;\n\tmatrix[5] *= y_scale;\n\tmatrix[6] *= y_scale;\n\tmatrix[7] *= y_scale;\n\tmatrix[8] *= z_scale;\n\tmatrix[9] *= z_scale;\n\tmatrix[10] *= z_scale;\n\tmatrix[11] *= z_scale;\n}\n\nvoid R_MultiplyMatrix(const float* lhs, const float* rhs, float* target)\n{\n\ttarget[0] = lhs[0] * rhs[0] + lhs[1] * rhs[4] + lhs[2] * rhs[8] + lhs[3] * rhs[12];\n\ttarget[1] = lhs[0] * rhs[1] + lhs[1] * rhs[5] + lhs[2] * rhs[9] + lhs[3] * rhs[13];\n\ttarget[2] = lhs[0] * rhs[2] + lhs[1] * rhs[6] + lhs[2] * rhs[10] + lhs[3] * rhs[14];\n\ttarget[3] = lhs[0] * rhs[3] + lhs[1] * rhs[7] + lhs[2] * rhs[11] + lhs[3] * rhs[15];\n\n\ttarget[4] = lhs[4] * rhs[0] + lhs[5] * rhs[4] + lhs[6] * rhs[8] + lhs[7] * rhs[12];\n\ttarget[5] = lhs[4] * rhs[1] + lhs[5] * rhs[5] + lhs[6] * rhs[9] + lhs[7] * rhs[13];\n\ttarget[6] = lhs[4] * rhs[2] + lhs[5] * rhs[6] + lhs[6] * rhs[10] + lhs[7] * rhs[14];\n\ttarget[7] = lhs[4] * rhs[3] + lhs[5] * rhs[7] + lhs[6] * rhs[11] + lhs[7] * rhs[15];\n\n\ttarget[8] = lhs[8] * rhs[0] + lhs[9] * rhs[4] + lhs[10] * rhs[8] + lhs[11] * rhs[12];\n\ttarget[9] = lhs[8] * rhs[1] + lhs[9] * rhs[5] + lhs[10] * rhs[9] + lhs[11] * rhs[13];\n\ttarget[10] = lhs[8] * rhs[2] + lhs[9] * rhs[6] + lhs[10] * rhs[10] + lhs[11] * rhs[14];\n\ttarget[11] = lhs[8] * rhs[3] + lhs[9] * rhs[7] + lhs[10] * rhs[11] + lhs[11] * rhs[15];\n\n\ttarget[12] = lhs[12] * rhs[0] + lhs[13] * rhs[4] + lhs[14] * rhs[8] + lhs[15] * rhs[12];\n\ttarget[13] = lhs[12] * rhs[1] + lhs[13] * rhs[5] + lhs[14] * rhs[9] + lhs[15] * rhs[13];\n\ttarget[14] = lhs[12] * rhs[2] + lhs[13] * rhs[6] + lhs[14] * rhs[10] + lhs[15] * rhs[14];\n\ttarget[15] = lhs[12] * rhs[3] + lhs[13] * rhs[7] + lhs[14] * rhs[11] + lhs[15] * rhs[15];\n}\n\nvoid R_MultiplyVector3f(const float* matrix, float x, float y, float z, float* result)\n{\n\tconst float vector[4] = { x, y, z, 1 };\n\n\tresult[0] = matrix[0] * vector[0] + matrix[4] * vector[1] + matrix[8] * vector[2] + matrix[12] * vector[3];\n\tresult[1] = matrix[1] * vector[0] + matrix[5] * vector[1] + matrix[9] * vector[2] + matrix[13] * vector[3];\n\tresult[2] = matrix[2] * vector[0] + matrix[6] * vector[1] + matrix[10] * vector[2] + matrix[14] * vector[3];\n\tresult[3] = matrix[3] * vector[0] + matrix[7] * vector[1] + matrix[11] * vector[2] + matrix[15] * vector[3];\n}\n\nvoid R_MultiplyVector(const float* matrix, const float* vector, float* result)\n{\n\tresult[0] = matrix[0] * vector[0] + matrix[4] * vector[1] + matrix[8] * vector[2] + matrix[12] * vector[3];\n\tresult[1] = matrix[1] * vector[0] + matrix[5] * vector[1] + matrix[9] * vector[2] + matrix[13] * vector[3];\n\tresult[2] = matrix[2] * vector[0] + matrix[6] * vector[1] + matrix[10] * vector[2] + matrix[14] * vector[3];\n\tresult[3] = matrix[3] * vector[0] + matrix[7] * vector[1] + matrix[11] * vector[2] + matrix[15] * vector[3];\n}\n\nvoid R_MultiplyVector3fv(const float* matrix, const vec3_t vector, float* result)\n{\n\tresult[0] = matrix[0] * vector[0] + matrix[4] * vector[1] + matrix[8] * vector[2] + matrix[12];\n\tresult[1] = matrix[1] * vector[0] + matrix[5] * vector[1] + matrix[9] * vector[2] + matrix[13];\n\tresult[2] = matrix[2] * vector[0] + matrix[6] * vector[1] + matrix[10] * vector[2] + matrix[14];\n\tresult[3] = matrix[3] * vector[0] + matrix[7] * vector[1] + matrix[11] * vector[2] + matrix[15];\n}\n\nvoid R_IdentityModelView(void)\n{\n\tR_SetIdentityMatrix(R_ModelviewMatrix());\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_IdentityModelview();\n\t}\n#endif\n}\n\nvoid R_GetModelviewMatrix(float* matrix)\n{\n\tmemcpy(matrix, modelMatrix, sizeof(modelMatrix));\n}\n\nvoid R_GetProjectionMatrix(float* matrix)\n{\n\tmemcpy(matrix, projectionMatrix, sizeof(projectionMatrix));\n}\n\nvoid R_RotateModelview(float angle, float x, float y, float z)\n{\n\tif (fmodf(angle, 360.0f) != 0) {\n\t\tR_RotateMatrix(modelMatrix, angle, x, y, z);\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t\tif (R_UseImmediateOpenGL()) {\n\t\t\tGLC_RotateModelview(angle, x, y, z);\n\t\t}\n#endif\n\t}\n}\n\nvoid R_TranslateModelview(float x, float y, float z)\n{\n\tif (x != 0 || y != 0 || z != 0) {\n\t\tR_TransformMatrix(modelMatrix, x, y, z);\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t\tif (R_UseImmediateOpenGL()) {\n\t\t\tGLC_TranslateModelview(x, y, z);\n\t\t}\n#endif\n\t}\n}\n\nvoid R_IdentityProjectionView(void)\n{\n\tR_SetIdentityMatrix(R_ProjectionMatrix());\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_IdentityProjectionView();\n\t}\n#endif\n}\n\nvoid R_PushModelviewMatrix(float* matrix)\n{\n\tmemcpy(matrix, modelMatrix, sizeof(modelMatrix));\n}\n\nvoid R_PushProjectionMatrix(float* matrix)\n{\n\tmemcpy(matrix, projectionMatrix, sizeof(projectionMatrix));\n}\n\nvoid R_PopModelviewMatrix(const float* matrix)\n{\n\tmemcpy(modelMatrix, matrix, sizeof(modelMatrix));\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_PopModelviewMatrix(matrix);\n\t}\n#endif\n}\n\nvoid R_PopProjectionMatrix(const float* matrix)\n{\n\tmemcpy(projectionMatrix, matrix, sizeof(projectionMatrix));\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_PopProjectionMatrix(matrix);\n\t}\n#endif\n}\n\nvoid R_ScaleModelview(float xScale, float yScale, float zScale)\n{\n\tR_ScaleMatrix(modelMatrix, xScale, yScale, zScale);\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_ScaleModelview(xScale, yScale, zScale);\n\t}\n#endif\n}\n\nvoid R_Frustum(double left, double right, double bottom, double top, double zNear, double zFar)\n{\n\tfloat perspective[16] = { 0 };\n\tfloat new_projection[16];\n\n\tperspective[0] = (2 * zNear) / (right - left);\n\tperspective[8] = (right + left) / (right - left);\n\tperspective[5] = (2 * zNear) / (top - bottom);\n\tperspective[9] = (top + bottom) / (top - bottom);\n\tperspective[10] = -(zFar + zNear) / (zFar - zNear);\n\tperspective[11] = -1;\n\tperspective[14] = -2 * (zFar * zNear) / (zFar - zNear);\n\n\tif (glConfig.reversed_depth) {\n\t\tperspective[10] = -zFar / (zNear - zFar) - 1;\n\t\tperspective[14] = -(zNear * zFar) / (zNear - zFar);\n\t}\n\n\tR_MultiplyMatrix(perspective, R_ProjectionMatrix(), new_projection);\n\tR_SetMatrix(R_ProjectionMatrix(), new_projection);\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_Frustum(left, right, bottom, top, zNear, zFar);\n\t}\n#endif\n}\n\nvoid GLM_MultiplyMatrixVector(float* matrix, vec3_t vector, float* result)\n{\n\tresult[0] = matrix[0] * vector[0] + matrix[4] * vector[1] + matrix[8] * vector[2] + matrix[12] * vector[3];\n\tresult[1] = matrix[1] * vector[0] + matrix[5] * vector[1] + matrix[9] * vector[2] + matrix[13] * vector[3];\n\tresult[2] = matrix[2] * vector[0] + matrix[6] * vector[1] + matrix[10] * vector[2] + matrix[14] * vector[3];\n\tresult[3] = matrix[3] * vector[0] + matrix[7] * vector[1] + matrix[11] * vector[2] + matrix[15] * vector[3];\n}\n\nvoid R_RotateForEntity(const struct entity_s *e)\n{\n\tR_TranslateModelview(e->origin[0], e->origin[1], e->origin[2]);\n\n\tR_RotateModelview(e->angles[1], 0, 0, 1);\n\tR_RotateModelview(-e->angles[0], 0, 1, 0);\n\tR_RotateModelview(e->angles[2], 1, 0, 0);\n}\n\nqbool R_Project3DCoordinates(float objx, float objy, float objz, float* winx, float* winy, float* winz)\n{\n\tfloat model[16], proj[16];\n\tfloat in[4] = { objx, objy, objz, 1.0f };\n\tfloat out[4];\n\tint view[4];\n\tint i;\n\n\tR_GetModelviewMatrix(model);\n\tR_GetProjectionMatrix(proj);\n\tR_GetViewport(view);\n\n\tfor (i = 0; i < 4; i++) {\n\t\tout[i] = in[0] * model[0 * 4 + i] + in[1] * model[1 * 4 + i] + in[2] * model[2 * 4 + i] + in[3] * model[3 * 4 + i];\n\t}\n\n\tfor (i = 0; i < 4; i++) {\n\t\tin[i] = out[0] * proj[0 * 4 + i] + out[1] * proj[1 * 4 + i] + out[2] * proj[2 * 4 + i] + out[3] * proj[3 * 4 + i];\n\t}\n\n\tif (!in[3]) {\n\t\treturn false;\n\t}\n\n\tVectorScale(in, 1 / in[3], in);\n\t*winx = view[0] + (1 + in[0]) * view[2] / 2;\n\t*winy = view[1] + (1 + in[1]) * view[3] / 2;\n\t*winz = (1 + in[2]) / 2;\n\n\treturn true;\n}\n"
  },
  {
    "path": "src/r_matrix.h",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_R_MATRIX_HEADER\n#define EZQUAKE_R_MATRIX_HEADER\n\nqbool R_Project3DCoordinates(float objx, float objy, float objz, float* winx, float* winy, float* winz);\n\nvoid R_GetModelviewMatrix(float* matrix);\nvoid R_GetProjectionMatrix(float* matrix);\nvoid R_GetViewport(int* view);\n\nvoid R_IdentityProjectionView(void);\nvoid R_IdentityModelView(void);\nvoid R_RotateModelview(float angle, float x, float y, float z);\nvoid R_ScaleModelview(float xScale, float yScale, float zScale);\nvoid R_TranslateModelview(float x, float y, float z);\nvoid R_Frustum(double left, double right, double bottom, double top, double zNear, double zFar);\nvoid R_OrthographicProjection(float left, float right, float top, float bottom, float zNear, float zFar);\n\nvoid R_PushModelviewMatrix(float* matrix);\nvoid R_PopModelviewMatrix(const float* matrix);\nvoid R_PushProjectionMatrix(float* matrix);\nvoid R_PopProjectionMatrix(const float* matrix);\n\nvoid R_ScaleMatrix(float* matrix, float x_scale, float y_scale, float z_scale);\nvoid R_TransformMatrix(float* matrix, float x, float y, float z);\nvoid R_RotateMatrix(float* matrix, float angle, float x, float y, float z);\nvoid R_RotateVector(vec3_t vector, float angle, float x, float y, float z);\n\nvoid R_SetIdentityMatrix(float* matrix);\nfloat* R_ModelviewMatrix(void);\nfloat* R_ProjectionMatrix(void);\n\nvoid R_MultiplyMatrix(const float* lhs, const float* rhs, float* target);\nvoid R_MultiplyVector(const float* matrix, const float* vector, float* result);\nvoid R_MultiplyVector3f(const float* matrix, float x, float y, float z, float* result);\nvoid R_MultiplyVector3fv(const float* matrix, const vec3_t vector, float* result);\n\nvoid R_RotateForEntity(const struct entity_s* e);\n\n#endif // EZQUAKE_R_MATRIX_HEADER\n"
  },
  {
    "path": "src/r_misc.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: r_part.c,v 1.19 2007-10-29 00:13:26 d3urk Exp $\n\n*/\n\n#include \"quakedef.h\"\n#include \"r_local.h\"\n"
  },
  {
    "path": "src/r_misc.cpp",
    "content": ""
  },
  {
    "path": "src/r_model.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: gl_model.c,v 1.41 2007-10-07 08:06:33 tonik Exp $\n*/\n// gl_model.c  -- model loading and caching\n\n// models are the only shared resource between a client and server running on the same machine.\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"rulesets.h\"\n#include \"wad.h\"\n#include \"crc.h\"\n#include \"fmod.h\"\n#include \"utils.h\"\n#include \"r_texture.h\"\n#include \"r_renderer.h\"\n\nmodel_t\t*loadmodel;\nchar\tloadname[32];\t// for hunk tags\n\nstatic mpic_t simpleitem_textures[MOD_NUMBER_HINTS][MAX_SIMPLE_TEXTURES];\n\nvoid Mod_LoadSpriteModel(model_t *mod, void *buffer);\nvoid Mod_LoadBrushModel(model_t *mod, void *buffer, int filesize);\nvoid Mod_LoadAliasModel(model_t *mod, void *buffer, int filesize, const char* loadname);\nmodel_t *Mod_LoadModel(model_t *mod, qbool crash);\nvoid Mod_AddModelFlags(model_t *mod);\n\nbyte\tmod_novis[MAX_MAP_LEAFS/8];\n\nmodel_t\tmod_known[MAX_MODELS];\nint\t\tmod_numknown;\n\nvoid Mod_Init(void)\n{\n\tmemset(mod_novis, 0xff, sizeof(mod_novis));\n}\n\n//Caches the data if needed\nvoid *Mod_Extradata(model_t *mod)\n{\n\tif (mod->cached_data) {\n\t\treturn mod->cached_data;\n\t}\n\n\tMod_LoadModel(mod, true);\n\n\tif (!mod->cached_data) {\n\t\tSys_Error(\"Mod_Extradata: caching failed\");\n\t}\n\treturn mod->cached_data;\n}\n\nmleaf_t *Mod_PointInLeaf(vec3_t p, model_t *model)\n{\n\tmnode_t *node;\n\tfloat d;\n\tmplane_t *plane;\n\n\tif (!model || !model->nodes) {\n\t\tSys_Error(\"Mod_PointInLeaf: bad model\");\n\t\treturn NULL;\n\t}\n\n\tnode = model->nodes;\n\twhile (1) {\n\t\tif (node->contents < 0) {\n\t\t\treturn (mleaf_t *)node;\n\t\t}\n\t\tplane = node->plane;\n\t\td = PlaneDiff(p, plane);\n\t\tnode = (d > 0) ? node->children[0] : node->children[1];\n\t}\n\n\treturn NULL;\t// never reached\n}\n\nbyte *Mod_DecompressVis(byte *in, model_t *model)\n{\n\tstatic byte\tdecompressed[MAX_MAP_LEAFS / 8];\n\tint c, row;\n\tbyte *out;\n\n\trow = (model->numleafs + 7) >> 3;\n\tout = decompressed;\n\n\tif (!in) {\t// no vis info, so make all visible\n\t\twhile (row) {\n\t\t\t*out++ = 0xff;\n\t\t\trow--;\n\t\t}\n\t\treturn decompressed;\n\t}\n\n\tdo {\n\t\tif (*in) {\n\t\t\t*out++ = *in++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tc = in[1];\n\t\tin += 2;\n\t\twhile (c) {\n\t\t\t*out++ = 0;\n\t\t\tc--;\n\t\t}\n\t} while (out - decompressed < row);\n\n\treturn decompressed;\n}\n\nbyte *Mod_LeafPVS(mleaf_t *leaf, model_t *model)\n{\n\tif (leaf == model->leafs) {\n\t\treturn mod_novis;\n\t}\n\treturn Mod_DecompressVis(leaf->compressed_vis, model);\n}\n\nvoid Mod_ClearAll(void)\n{\n\tint i;\n\tmodel_t\t*mod;\n\n\tfor (i = 0, mod = mod_known; i < mod_numknown; i++, mod++) {\n\t\tif (mod->type != mod_alias && mod->type != mod_alias3 && mod->type != mod_sprite) {\n\t\t\tmod->needload = true;\n\t\t}\n\t}\n}\n\nvoid Mod_FreeAllCachedData(void)\n{\n\tint i;\n\tmodel_t\t*mod;\n\n\tfor (i = 0, mod = mod_known; i < mod_numknown; i++, mod++) {\n\t\tQ_free(mod->cached_data);\n\t}\n}\n\nmodel_t *Mod_FindName(const char *name)\n{\n\tint i;\n\tmodel_t\t*mod;\n\n\tif (!name[0]) {\n\t\tSys_Error(\"Mod_ForName: NULL name\");\n\t}\n\n\t// search the currently loaded models\n\tfor (i = 0, mod = mod_known; i < mod_numknown; i++, mod++) {\n\t\tif (!strcmp(mod->name, name)) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (i == mod_numknown) {\n\t\tif (mod_numknown == MAX_MODELS) {\n\t\t\tSys_Error(\"mod_numknown == MAX_MOD_KNOWN\");\n\t\t}\n\t\tstrlcpy(mod->name, name, sizeof(mod->name));\n\t\tmod->needload = true;\n\t\tmod_numknown++;\n\t}\n\treturn mod;\n}\n\nvoid Mod_TouchModel(char *name)\n{\n\tMod_FindName(name);\n}\n\nvoid Mod_ReloadModels(qbool vid_restart)\n{\n\tint i;\n\n\tfor (i = 1; i < MAX_MODELS; ++i) {\n\t\tmodel_t* mod = cl.model_precache[i];\n\n\t\tif (mod && (mod == cl.worldmodel || !mod->isworldmodel)) {\n\t\t\tif (mod->type == mod_alias || mod->type == mod_alias3) {\n\t\t\t\tif (mod->vertsInVBO && !mod->temp_vbo_buffer) {\n\t\t\t\t\t// Invalidate cache so VBO buffer gets refilled\n\t\t\t\t\tQ_free(mod->cached_data);\n\t\t\t\t}\n\t\t\t}\n\t\t\tMod_LoadModel(mod, true);\n\t\t}\n\t}\n\n\tfor (i = 0; i < MAX_VWEP_MODELS; i++) {\n\t\tmodel_t* mod = cl.vw_model_precache[i];\n\n\t\tif (mod) {\n\t\t\tif (mod->type == mod_alias || mod->type == mod_alias3) {\n\t\t\t\tif (mod->vertsInVBO && !mod->temp_vbo_buffer) {\n\t\t\t\t\t// Invalidate cache so VBO buffer gets refilled\n\t\t\t\t\tQ_free(mod->cached_data);\n\t\t\t\t}\n\t\t\t}\n\t\t\tMod_LoadModel(mod, true);\n\t\t}\n\t}\n\n\tfor (i = 0; i < custom_model_count; i++) {\n\t\tmodel_t* mod = cl_custommodels[i];\n\n\t\tif (mod) {\n\t\t\tif (mod->type == mod_alias || mod->type == mod_alias3) {\n\t\t\t\tif (mod->vertsInVBO && !mod->temp_vbo_buffer) {\n\t\t\t\t\t// Invalidate cache so VBO buffer gets refilled\n\t\t\t\t\tQ_free(mod->cached_data);\n\t\t\t\t}\n\t\t\t}\n\t\t\tMod_LoadModel(mod, true);\n\t\t}\n\t}\n}\n\n//Loads a model into the cache\nmodel_t *Mod_LoadModel(model_t *mod, qbool crash)\n{\n\tunsigned *buf;\n\tint namelen;\n\tint filesize;\n\n\tif (!mod->needload) {\n\t\tif (mod->type == mod_alias || mod->type == mod_alias3 || mod->type == mod_sprite) {\n\t\t\tif (mod->cached_data) {\n\t\t\t\treturn mod;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\treturn mod; // not cached at all\n\t\t}\n\t}\n\n\tnamelen = strlen(mod->name);\n\tbuf = NULL;\n\tif (namelen >= 4 && (!strcmp(mod->name + namelen - 4, \".mdl\") ||\n\t\t(namelen >= 9 && mod->name[5] == 'b' && mod->name[6] == '_' && !strcmp(mod->name + namelen - 4, \".bsp\")))) {\n\t\tchar newname[MAX_QPATH];\n\t\tCOM_StripExtension(mod->name, newname, sizeof(newname));\n\t\tCOM_DefaultExtension(newname, \".md3\", sizeof(newname));\n\t\tbuf = (unsigned *)FS_LoadTempFile(newname, &filesize);\n\t}\n\n\t// load the file\n\tif (!buf) {\n\t\tbuf = (unsigned *)FS_LoadTempFile(mod->name, &filesize);\n\t}\n\tif (!buf) {\n\t\tif (crash) {\n\t\t\tHost_Error(\"Mod_LoadModel: %s not found\", mod->name);\n\t\t}\n\t\treturn NULL;\n\t}\n\n\t// allocate a new model\n\tCOM_FileBase(mod->name, loadname);\n\tloadmodel = mod;\n\tFMod_CheckModel(mod->name, buf, filesize);\n\n\t// call the apropriate loader\n\tmod->needload = false;\n\n\tswitch (LittleLong(*((unsigned *)buf))) {\n\tcase IDPOLYHEADER:\n\t\tMod_LoadAliasModel(mod, buf, filesize, loadname);\n\t\tbreak;\n\n\tcase MD3_IDENT:\n\t\tMod_LoadAlias3Model(mod, buf, filesize);\n\t\tbreak;\n\n\tcase IDSPRITEHEADER:\n\t\tMod_LoadSpriteModel(mod, buf);\n\t\tbreak;\n\n\tdefault:\n\t\tMod_LoadBrushModel(mod, buf, filesize);\n\t\tbreak;\n\t}\n\n\treturn mod;\n}\n\n//Loads in a model for the given name\nmodel_t *Mod_ForName(const char *name, qbool crash)\n{\n\tmodel_t\t*mod = Mod_FindName(name);\n\n\treturn Mod_LoadModel(mod, crash);\n}\n\nqbool Img_HasFullbrights(byte *pixels, int size)\n{\n\tint i;\n\n\tfor (i = 0; i < size; i++) {\n\t\tif (pixels[i] >= 224) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n// this is callback from VID after a vid_restart\nvoid Mod_ReloadModelsTextures(void)\n{\n\tint i, j;\n\tmodel_t *m;\n\ttexture_t *tx;\n\n\tif (cls.state != ca_active) {\n\t\treturn; // seems we are not loaded models yet, so no need to reload textures\n\t}\n\n\t// mark all textures as not loaded, for brush models only, this speed up loading.\n\t// seems textures shared between models, so we mark textures from ALL models than actually load textures, that why we use two for()\n\tfor (i = 1; i < MAX_MODELS; i++)\n\t{\n\t\tif (!cl.model_name[i][0]) {\n\t\t\tbreak;\n\t\t}\n\n\t\tm = cl.model_precache[i];\n\t\tif (m->type != mod_brush) {\n\t\t\tcontinue; // actual only for brush models\n\t\t}\n\n\t\tif (!m->textures) {\n\t\t\tcontinue; // hmm\n\t\t}\n\n\t\tfor (j = 0; j < m->numtextures; j++) {\n\t\t\ttx = m->textures[j];\n\t\t\tif (!tx) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttx->loaded = false; // so texture will be reloaded\n\t\t\tR_TextureReferenceInvalidate(tx->gl_texture_array);\n\t\t}\n\t}\n\n\t// now actually load textures for brush models\n\tfor (i = 1; i < MAX_MODELS; i++)\n\t{\n\t\tif (!cl.model_name[i][0]) {\n\t\t\tbreak;\n\t\t}\n\n\t\tm = cl.model_precache[i];\n\t\tif (m->type != mod_brush) {\n\t\t\tcontinue; // actual only for brush models\n\t\t}\n\n\t\tR_LoadBrushModelTextures(m);\n\t}\n}\n\nfloat RadiusFromBounds (vec3_t mins, vec3_t maxs)\n{\n\tint i;\n\tvec3_t corner;\n\n\tfor (i = 0; i < 3; i++)\n\t\tcorner[i] = fabs(mins[i]) > fabs(maxs[i]) ? fabs(mins[i]) : fabs(maxs[i]);\n\n\treturn VectorLength (corner);\n}\n\n//============================================================\n\n//=========================================================\n\n//VULT MODELS\n//This is incase we want to use a function other than Mod_LoadAliasModel\n//It was used in one of the older versions when it supported Q2 Models.\nvoid Mod_AddModelFlags(model_t *mod)\n{\n\tmod->renderfx = 0;\n\tmod->modhint = mod->modhint_trail = MOD_NORMAL;\n\tif (!strcmp(mod->name, \"progs/caltrop.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/biggren.mdl\") ||\n/*\t\t!strcmp(mod->name, \"progs/detpack.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/detpack2.mdl\") ||*/\n/*\t\t!strcmp(mod->name, \"progs/dgib.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/dgib2.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/dgib3.mdl\") ||*/\n\t\t!strcmp(mod->name, \"progs/flare.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/gren1.bsp\") ||\n\t\t//!strcmp(mod->name, \"progs/grenade.mdl\") ||\n\t\t//!strcmp(mod->name, \"progs/grenade2.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/grenade3.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/hook.mdl\") ||\n/*\t\t!strcmp(mod->name, \"progs/tesgib1.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tesgib2.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tesgib3.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tesgib4.mdl\") ||\n//\t\t!strcmp(mod->name, \"progs/turrgun.mdl\") || //turrets have dodgy origins, after all\n\t\t!strcmp(mod->name, \"progs/tgib1.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tgib2.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tgib3.mdl\") ||*/\n\t\t!strcmp(mod->name, \"progs/hgren2.mdl\")) {\n\t\tmod->modhint_trail = MOD_TF_TRAIL;\n\t}\n\n\t//modhints\n\tif (!strcmp(mod->name, \"progs/player.mdl\")) {\n\t\tmod->modhint = MOD_PLAYER;\n\t}\n\telse if (!strcmp(mod->name, \"progs/eyes.mdl\")) {\n\t\tmod->modhint = MOD_EYES;\n\t}\n\telse if (!strcmp(mod->name, \"fx_tele.mdl\")) {\n\t\t//Tei, special\n\t\tmod->modhint = MOD_TELEPORTDESTINATION;\n\t}\n\telse if (!strcmp(mod->name, \"progs/flame.mdl\")) {\n\t\tmod->modhint = MOD_FLAME;\n\t}\n\telse if (!strcmp(mod->name, \"progs/flame2.mdl\")) {\n\t\tmod->modhint = MOD_FLAME2;\n\t}\n\telse if (!strcmp(mod->name, \"progs/flame0.mdl\")) {\n\t\tmod->modhint = MOD_FLAME0;\n\t}\n\telse if (!strcmp(mod->name, \"progs/flame3.mdl\")) {\n\t\tmod->modhint = MOD_FLAME3;\n\t}\n\telse if (!strcmp(mod->name, \"progs/bolt.mdl\") || !strcmp(mod->name, \"progs/bolt2.mdl\") || !strcmp(mod->name, \"progs/bolt3.mdl\")) {\n\t\tmod->modhint = MOD_THUNDERBOLT;\n\t}\n\telse if (!strcmp(mod->name, \"progs/minimissile.mdl\")) {\n\t\tmod->modhint = MOD_CLUSTER;\n\t}\n\telse if (!strcmp(mod->name, \"progs/flag.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tf_flag.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/kkr.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/kkb\") ||\n\t\t!strcmp(mod->name, \"progs/w_g_key.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/w_s_key.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/b_g_key.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/b_s_key.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/stag.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/basrkey.bsp\") ||\n\t\t!strcmp(mod->name, \"progs/basbkey.bsp\") ||\n\t\t!strcmp(mod->name, \"progs/ff_flag.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/harbflag.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/princess.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tf_stan.mdl\")) {\n\t\tmod->modhint = MOD_FLAG;\n\t}\n\telse if (!strcmp(mod->name, \"progs/spike.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/s_spike.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/amf_spike.mdl\")) {\n\t\tmod->modhint = MOD_SPIKE;\n\t}\n\telse if (!strcmp(mod->name, \"progs/coil.mdl\") || !strcmp(mod->name, \"progs/tesla.mdl\")) {\n\t\tmod->modhint = MOD_TESLA;\n\t}\n\telse if (!strcmp(mod->name, \"progs/turrgun.mdl\")) {\n\t\tmod->modhint = MOD_SENTRYGUN;\n\t}\n\telse if (!strcmp(mod->name, \"progs/detpack.mdl\")) {\n\t\tmod->modhint = MOD_DETPACK;\n\t}\n\telse if (!strcmp(mod->name, \"progs/laser.mdl\")) {\n\t\tmod->modhint = MOD_LASER;\n\t}\n\telse if (!strcmp(mod->name, \"progs/demon.mdl\")/* || !strcmp(mod->name, \"progs/shambler.mdl\") */) {\n\t\tmod->modhint = MOD_DEMON;\n\t}\n\telse if (!strcmp(mod->name, \"progs/soldier.mdl\")) {\n\t\tmod->modhint = MOD_SOLDIER;\n\t}\n\telse if (!strcmp(mod->name, \"progs/enforcer.mdl\")) {\n\t\tmod->modhint = MOD_ENFORCER;\n\t}\n\telse if (!strcmp(mod->name, \"progs/ogre.mdl\")) {\n\t\tmod->modhint = MOD_OGRE;\n\t}\n\telse if (!strcmp(mod->name, \"progs/shambler.mdl\")) {\n\t\tmod->modhint = MOD_SHAMBLER;\n\t}\n\telse if (!strcmp(mod->name, \"progs/v_spike.mdl\")) {\n\t\tmod->modhint = MOD_VOORSPIKE;\n\t}\n\telse if (!strcmp(mod->name, \"progs/e_spike1.mdl\")) {\n\t\tmod->modhint = MOD_RAIL;\n\t}\n\telse if (!strcmp(mod->name, \"progs/e_spike2.mdl\")) {\n\t\tmod->modhint = MOD_RAIL2;\n\t}\n\telse if (!strcmp(mod->name, \"progs/lavaball.mdl\")) {\n\t\tmod->modhint = MOD_LAVABALL;\n\t}\n\telse if (!strcmp(mod->name, \"progs/dgib.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/dgib2.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/dgib3.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tesgib1.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tesgib2.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tesgib3.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tesgib4.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tgib1.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tgib2.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/tgib3.mdl\")) {\n\t\tmod->modhint = MOD_BUILDINGGIBS;\n\t}\n\telse if (!strcmp(mod->name, \"progs/backpack.mdl\")) {\n\t\tmod->modhint = MOD_BACKPACK;\n\t}\n\telse if (!strcmp(mod->name, \"progs/gib1.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/gib2.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/gib3.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/h_player.mdl\")) {\n\t\tmod->modhint = MOD_GIB;\n\t}\n\telse if (!strncasecmp(mod->name, \"progs/v_\", 8)) {\n\t\tqbool suppress_nolerp = false;\n\n\t\tmod->modhint = MOD_VMODEL;\n\n\t\t// the following models are excluded\n\t\tsuppress_nolerp |= !strcmp(cl_modelnames[mi_vaxe], mod->name);\n\t\tsuppress_nolerp |= !strcmp(cl_modelnames[mi_vbio], mod->name);\n\t\tsuppress_nolerp |= !strcmp(cl_modelnames[mi_vgrap], mod->name);\n\t\tsuppress_nolerp |= !strcmp(cl_modelnames[mi_vknife], mod->name);\n\t\tsuppress_nolerp |= !strcmp(cl_modelnames[mi_vknife2], mod->name);\n\t\tsuppress_nolerp |= !strcmp(cl_modelnames[mi_vmedi], mod->name);\n\t\tsuppress_nolerp |= !strcmp(cl_modelnames[mi_vspan], mod->name);\n\n\t\tmod->renderfx |= (suppress_nolerp ? 0 : RF_LIMITLERP);\n\t}\n\telse if (!strcmp(mod->name, \"progs/missile.mdl\")) {\n\t\tmod->modhint = MOD_ROCKET;\n\t}\n\telse if (!strcmp(mod->name, \"progs/grenade.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/flare.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/hgren2.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/biggren.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/grenade2.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/grenade3.mdl\") ||\n\t\t!strcmp(mod->name, \"progs/caltrop.mdl\")) {\n\t\tmod->modhint = MOD_GRENADE;\n\t}\n\telse if (!strcmp(mod->name, \"progs/g_rock2.mdl\")) {\n\t\tmod->modhint = MOD_ROCKETLAUNCHER;\n\t}\n\telse if (!strcmp(mod->name, \"progs/g_light.mdl\")) {\n\t\tmod->modhint = MOD_LIGHTNINGGUN;\n\t}\n\telse if (!strcmp(mod->name, \"progs/quaddama.mdl\")) {\n\t\tmod->modhint = MOD_QUAD;\n\t}\n\telse if (!strcmp(mod->name, \"progs/invulner.mdl\")) {\n\t\tmod->modhint = MOD_PENT;\n\t}\n\telse if (!strcmp(mod->name, \"progs/invisibl.mdl\")) {\n\t\tmod->modhint = MOD_RING;\n\t}\n\telse if (!strcmp(mod->name, \"maps/b_bh100.bsp\")) {\n\t\tmod->modhint = MOD_MEGAHEALTH;\n\t}\n\telse if (!strcmp(mod->name, \"progs/armor.mdl\")) {\n\t\tmod->modhint = MOD_ARMOR;\n\t}\n\telse if (!strcmp(mod->name, \"progs/suit.mdl\")) {\n\t\tmod->modhint = MOD_SUIT;\n\t}\n\telse if (!strcmp(mod->name, \"progs/g_rock.mdl\")) {\n\t\tmod->modhint = MOD_GRENADELAUNCHER;\n\t}\n\telse {\n\t\tmod->modhint = MOD_NORMAL;\n\t}\n}\n\ntexture_ref Mod_LoadSimpleTexture(model_t *mod, int skinnum)\n{\n\ttexture_ref tex = null_texture_reference;\n\tint texmode = 0;\n\tchar basename[64], identifier[64];\n\n\tif (!mod) {\n\t\treturn null_texture_reference;\n\t}\n\n\tif (RuleSets_DisallowExternalTexture(mod)) {\n\t\treturn null_texture_reference;\n\t}\n\n\t// well, it have nothing with luma, but quite same restrictions...\n\tif (RuleSets_DisallowSimpleTexture(mod)) {\n\t\treturn null_texture_reference;\n\t}\n\n\tCOM_StripExtension(COM_SkipPath(mod->name), basename, sizeof(basename));\n\n\ttexmode = TEX_MIPMAP | TEX_ALPHA | TEX_PREMUL_ALPHA;\n\tif (!gl_scaleModelSimpleTextures.integer) {\n\t\ttexmode |= TEX_NOSCALE;\n\t}\n\n\tsnprintf(identifier, sizeof(identifier), \"simple_%s_%d\", basename, skinnum);\n\n\tif (mod->type == mod_brush) {\n\t\ttex = R_LoadTextureImage(va(\"textures/bmodels/%s\", identifier), identifier, 0, 0, texmode);\n\t}\n\telse if (mod->type == mod_alias || mod->type == mod_alias3) {\n\t\t// hack for loading models saved as .bsp under /maps directory\n\t\tif (Utils_RegExpMatch(\"^(?i)maps\\\\/b_(.*)\\\\.bsp\", mod->name)) {\n\t\t\ttex = R_LoadTextureImage(va(\"textures/bmodels/%s\", identifier), identifier, 0, 0, texmode);\n\t\t}\n\t\telse {\n\t\t\ttex = R_LoadTextureImage(va(\"textures/models/%s\", identifier), identifier, 0, 0, texmode);\n\t\t}\n\t}\n\n\tif (!R_TextureReferenceIsValid(tex)) {\n\t\ttex = R_LoadTextureImage(va(\"textures/%s\", identifier), identifier, 0, 0, texmode);\n\t}\n\n\tif (developer.value > 1) {\n\t\tCom_Printf(\"Mod_LoadSimpleTexture: %s %s\\n\", identifier, R_TextureReferenceIsValid(tex) ? \"OK\" : \"FAIL\");\n\t}\n\n\tif (R_TextureReferenceIsValid(tex) && mod->modhint >= 0 && mod->modhint < MOD_NUMBER_HINTS && skinnum >= 0 && skinnum < MAX_SIMPLE_TEXTURES) {\n\t\tmpic_t* pic = &simpleitem_textures[mod->modhint][skinnum];\n\t\tpic->texnum = tex;\n\t\tpic->width = 16;\n\t\tpic->height = 16;\n\t\tpic->sl = pic->tl = 0;\n\t\tpic->sh = pic->th = 1;\n\n\t\tCachePics_MarkAtlasDirty();\n\t}\n\n\tif (R_TextureReferenceIsValid(tex)) {\n\t\trenderer.TextureWrapModeClamp(tex);\n\t}\n\treturn tex;\n}\n\nvoid Mod_ClearSimpleTextures(void)\n{\n\tmemset(simpleitem_textures, 0, sizeof(simpleitem_textures));\n}\n\nmpic_t* Mod_SimpleTextureForHint(int model_hint, int skinnum)\n{\n\tif (model_hint > 0 && model_hint < MOD_NUMBER_HINTS && skinnum >= 0 && skinnum < MAX_SIMPLE_TEXTURES) {\n\t\treturn &simpleitem_textures[model_hint][skinnum];\n\t}\n\n\treturn 0;\n}\n\nconst char* custom_model_names[] = {\n\t\"progs/s_explod.spr\",    // custom_model_explosion\n\t\"progs/bolt.mdl\",        // custom_model_bolt\n\t\"progs/bolt2.mdl\",       // custom_model_bolt2\n\t\"progs/bolt3.mdl\",       // custom_model_bolt3\n\t\"progs/beam.mdl\",        // custom_model_beam\n\t\"progs/flame0.mdl\"       // custom_model_flame0\n};\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(custom_model_names) / sizeof(custom_model_names[0]) == custom_model_count);\n#endif\n\nmodel_t* Mod_CustomModel(custom_model_id_t id, qbool crash)\n{\n\tif (id >= 0 && id < sizeof(custom_model_names) / sizeof(custom_model_names[0])) {\n\t\treturn (cl_custommodels[id] = Mod_ForName(custom_model_names[id], crash));\n\t}\n\treturn NULL;\n}\n\n// Used for lerping between frames\nint Mod_ExpectedNextFrame(model_t* mod, int framenum, int framecount)\n{\n\t// axe has 0, 1...4, 5...8\n\tif (!strcmp(mod->name, cl_modelnames[mi_weapon1]) && framenum == 4) {\n\t\treturn 0;\n\t}\n\n\t// assume it's increasing\n\tif (framenum < framecount - 1) {\n\t\treturn framenum + 1;\n\t}\n\n\t// check for continuous-fire weapons\n\tif (mod->modhint == MOD_VMODEL) {\n\t\tif (!strcmp(mod->name, cl_modelnames[mi_weapon4])) {\n\t\t\treturn 1;\n\t\t}\n\t\tif (!strcmp(mod->name, cl_modelnames[mi_weapon5])) {\n\t\t\treturn 1;\n\t\t}\n\t\tif (!strcmp(mod->name, cl_modelnames[mi_weapon8])) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\t// back to the start\n\treturn 0;\n}\n"
  },
  {
    "path": "src/r_netgraph.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// gl_ngraph.c\n\n#include \"quakedef.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n\n#define MAX_NET_GRAPHHEIGHT 256\n\ntexture_ref netgraphtexture;\t// netgraph texture\n\n// HUD -> hexum\n\n/*\n==============\nR_MQW_NetGraph\n==============\n*/\nvoid R_MQW_NetGraph(int outgoing_sequence, int incoming_sequence, int *packet_latency,\n                int lost, int minping, int avgping, int maxping, int devping,\n                int posx, int posy, int width, int height, int revx, int revy)\n{\n    int     a, x, i, y;\n    char st[80];\n    float alpha;\n\n    static hud_t *hud = NULL;\n    static cvar_t\n        *par_alpha, *par_inframes, *par_maxping, *par_dropheight;\n\n    if (hud == NULL)  // first time\n    {\n        hud = HUD_Find(\"netgraph\");\n        par_alpha      = HUD_FindVar(hud, \"alpha\");\n        par_inframes   = HUD_FindVar(hud, \"inframes\");\n        par_maxping    = HUD_FindVar(hud, \"scale\");\n        par_dropheight = HUD_FindVar(hud, \"lostscale\");\n    }\n\n    CL_CalcNet();\n\n    alpha = par_alpha->value;\n\n\tif (width < 0) {\n\t\twidth = NET_TIMINGS;\n\t}\n    width = min(width, 256);\n\tif (width < 16) {\n\t\treturn;\n\t}\n\n\tif (height < 0) {\n\t\theight = 32;\n\t}\n\tif (height > MAX_NET_GRAPHHEIGHT) {\n\t\theight = MAX_NET_GRAPHHEIGHT;\n\t}\n\tif (height < 1) {\n\t\treturn;\n\t}\n\n\tif (alpha < 0 || alpha > 1) {\n\t\talpha = 1;\n\t}\n\n    if (posx < 0  ||  posy < 0) {\n        int w, h;\n\n        w = width;\n        h = height;\n\t\tif (lost >= 0) {\n\t\t\th += 8;\n\t\t}\n\t\tif (!HUD_PrepareDraw(hud, w, h, &posx, &posy)) {\n\t\t\treturn;\n\t\t}\n    }\n\n    x = posx;\n    y = posy;\n\n    if (lost >= 0) {\n        if (avgping < 0) {\n            snprintf(st, sizeof (st),\" %3i%% packet loss\", lost);\n            st[(width-1)/8] = 0;\n            Draw_String(x+3, y, st);\n        }\n        else {\n            char buf[128];\n            if (lost > 99)\n                lost = 99;\n\n            strlcpy(st, \"\\x1D\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1E\\x1F\", sizeof (st));\n            //snprintf(st, sizeof (st), \"%3i%% packet loss, %3i ms ping\", lost, avgping);\n            snprintf (buf, sizeof (buf), \" %i \\xf %i%% \", avgping, lost);\n            strlcpy (st + strlen(st) - strlen(buf) - 3, buf, sizeof (st) - strlen (st) + strlen (buf) + 3);\n            Draw_String(x+4, y, st);\n        }\n\n        y += 8;\n    }\n\n    for (a=0; a < width; a++) {\n        int px, py1, py2;\n        unsigned char pColor[4];\n        int h, color;\n\n        i = (outgoing_sequence-a-1) & NET_TIMINGSMASK;\n        h = packet_latency[i];\n\n\t\tif (h == 10000) {\n\t\t\tcolor = 0x6f;   // yellow   [rate]\n\t\t}\n\t\telse if (h == 9999) {\n\t\t\tcolor = 0x4f;   // red      [lost]\n\t\t}\n\t\telse if (h == 9998) {\n\t\t\tcolor = 0xd0;   // blue     [delta]\n\t\t}\n\t\telse if (h == 10001) {\n\t\t\tcolor = 4;      // gray     [netlimit]\n\t\t}\n\t\telse {\n\t\t\tcolor = 0xfe;   // pink     [ms ping]\n\t\t}\n\n        if (h < 9000) {\n\t\t\tif (!par_inframes->value) {\n\t\t\t\th = h*height / par_maxping->value;\n\t\t\t}\n        }\n\t\telse {\n\t\t\th = min(height, height*par_dropheight->value);\n\t\t}\n        clamp(h, 0, height);\n\n\t\tmemcpy(pColor, (unsigned char *)&d_8to24table[(byte)color], 3);\n\t\tif (alpha < 1) {\n\t\t\tpColor[0] *= alpha;\n\t\t\tpColor[1] *= alpha;\n\t\t\tpColor[2] *= alpha;\n\t\t\tpColor[3] = (byte)(alpha * 255);\n\t\t}\n\t\telse {\n\t\t\tpColor[3] = 255;\n\t\t}\n\n        px = x + (revx ? a : width-a-1);\n        if (revy) {\n            py1 = y + 0;\n            py2 = y + h;\n        }\n        else {\n            py1 = y + height;\n            py2 = y + height-h;\n        }\n\n\t\tDraw_AlphaLineRGB(px, py1, px, py2, 1, RGBAVECT_TO_COLOR(pColor));\n    }\n}\n"
  },
  {
    "path": "src/r_palette.c",
    "content": "/*\nCopyright (C) 2002-2003 A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"r_local.h\"\n\nfloat vid_gamma = 1.0;\nbyte vid_gamma_table[256];\n\nunsigned short d_8to16table[256];\nunsigned d_8to24table[256];\nunsigned d_8to24table2[256];\n\n/************************************* VID GAMMA *************************************/\nvoid Check_Gamma(unsigned char *pal)\n{\n\tfloat inf;\n\tunsigned char palette[768];\n\tint i;\n\n\tif (R_OldGammaBehaviour() && vid_gamma != 1) {\n\t\tfor (i = 0; i < 256; i++) {\n\t\t\tinf = 255 * pow((i + 0.5) / 255.5, vid_gamma) + 0.5;\n\t\t\tif (inf > 255) {\n\t\t\t\tinf = 255;\n\t\t\t}\n\t\t\tvid_gamma_table[i] = inf;\n\t\t}\n\t}\n\telse {\n\t\tfor (i = 0; i < 256; i++) {\n\t\t\tvid_gamma_table[i] = i;\n\t\t}\n\t}\n\n\tfor (i = 0; i < 768; i++) {\n\t\tpalette[i] = vid_gamma_table[pal[i]];\n\t}\n\n\tmemcpy(pal, palette, sizeof(palette));\n}\n\n/************************************* HW GAMMA *************************************/\n\nvoid VID_SetPalette(unsigned char *palette)\n{\n\tint i;\n\tbyte *pal;\n\tunsigned r, g, b, v, *table;\n\n\t// 8 8 8 encoding\n\t// Macintosh has different byte order\n\tpal = palette;\n\ttable = d_8to24table;\n\tfor (i = 0; i < 256; i++) {\n\t\tr = pal[0];\n\t\tg = pal[1];\n\t\tb = pal[2];\n\t\tpal += 3;\n\t\tv = LittleLong((255 << 24) + (r << 0) + (g << 8) + (b << 16));\n\t\t*table++ = v;\n\t}\n\td_8to24table[255] = 0;\t\t// 255 is transparent\n\n\t// Tonik: create a brighter palette for bmodel textures\n\tpal = palette;\n\ttable = d_8to24table2;\n\tfor (i = 0; i < 256; i++) {\n\t\tr = pal[0] * (2.0 / 1.5); if (r > 255) r = 255;\n\t\tg = pal[1] * (2.0 / 1.5); if (g > 255) g = 255;\n\t\tb = pal[2] * (2.0 / 1.5); if (b > 255) b = 255;\n\t\tpal += 3;\n\t\t*table++ = LittleLong((255 << 24) + (r << 0) + (g << 8) + (b << 16));\n\t}\n\td_8to24table2[255] = 0;\t// 255 is transparent\n}\n\nqbool R_OldGammaBehaviour(void)\n{\n\treturn COM_CheckParm(cmdline_param_client_oldgammabehaviour) > 0;\n}\n"
  },
  {
    "path": "src/r_part.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: r_part.c,v 1.19 2007-10-29 00:13:26 d3urk Exp $\n\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"particles_classic.h\"\n#include \"r_sprite3d.h\"\n#include \"rulesets.h\"\n#include \"r_texture.h\"\n#include \"qmb_particles.h\"\n#include \"glm_particles.h\"\n#include \"r_state.h\"\n#include \"r_local.h\"\n#include \"r_renderer.h\"\n#include \"r_framestats.h\"\n\n#ifndef RENDERER_OPTION_MODERN_OPENGL\nint particletexture_array_index = 0;\nfloat particletexture_array_xpos_tr = 0.9999f;\nfloat particletexture_array_ypos_tr = 0.9999f;\nfloat particletexture_array_max_s = 1.0f;\nfloat particletexture_array_max_t = 1.0f;\n#else\nextern float particletexture_array_max_s;\nextern float particletexture_array_max_t;\n#endif\n\ntexture_ref particletexture;\nconst int default_size = 32;\n\nextern cvar_t gl_part_blood;\nextern cvar_t gl_part_gunshots;\nextern cvar_t gl_part_bloodtrails;\nextern cvar_t gl_part_trails;\nextern cvar_t gl_part_spikes;\nextern cvar_t gl_part_explosions;\nextern cvar_t gl_part_blobs;\nextern cvar_t gl_part_lavasplash;\nextern cvar_t gl_part_telesplash;\n\ncvar_t r_particles_count = { \"r_particles_count\", \"2048\" };\nstatic cvar_t r_drawparticles = { \"r_drawparticles\", \"1\" };\n\n// Which particles to draw this frame\ntypedef struct glm_particle_s {\n\tvec3_t      gl_org;\n\tfloat       gl_scale;\n\tbyte        gl_color[4];\n} glm_particle_t;\n\nqparticle_t particles[ABSOLUTE_MAX_PARTICLES];\nglm_particle_t glparticles[ABSOLUTE_MAX_PARTICLES];\nr_sprite3d_vert_t glvertices[ABSOLUTE_MAX_PARTICLES * 3];\n\nstatic int\tramp1[8] = { 0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61 };\nstatic int\tramp2[8] = { 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66 };\nstatic int\tramp3[8] = { 0x6d, 0x6b, 6, 5, 4, 3 };\n\nstatic int r_numparticles;\nstatic int r_numactiveparticles;\n\nfloat crand(void)\n{\n\treturn (rand() & 32767) * (2.0 / 32767) - 1;\n}\n\nbyte* Classic_CreateParticleTexture(int width, int height)\n{\n\tbyte* data = Q_malloc(sizeof(byte) * width * height * 4);\n\tint x, y;\n\tint size = min(width, height);\n\tfloat quarter = size / 4.0f - 0.5f;\n\textern cvar_t gl_squareparticles;\n\n\t// draw a circle in the top left corner or square, depends of cvar\n\tif (gl_squareparticles.integer) {\n\t\tfor (x = 0; x < size / 2; x++) {\n\t\t\tfor (y = 0; y < size / 2; y++) {\n\t\t\t\tdata[(x + y * width) * 4 + 0] = 255;\n\t\t\t\tdata[(x + y * width) * 4 + 1] = 255;\n\t\t\t\tdata[(x + y * width) * 4 + 2] = 255;\n\t\t\t\tdata[(x + y * width) * 4 + 3] = 255;\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\tfor (x = 0; x < size / 2; x++) {\n\t\t\tfor (y = 0; y < size / 2; y++) {\n\t\t\t\tfloat edge0 = size / 4.0f;\n\t\t\t\tfloat dist = sqrt((x - quarter) * (x - quarter) + (y - quarter) * (y - quarter));\n\n\t\t\t\tif (dist < edge0) {\n\t\t\t\t\tfloat edge1 = edge0 - size / 16;\n\n\t\t\t\t\tif (dist < edge1) {\n\t\t\t\t\t\tdata[(x + y * width) * 4 + 0] = 255;\n\t\t\t\t\t\tdata[(x + y * width) * 4 + 1] = 255;\n\t\t\t\t\t\tdata[(x + y * width) * 4 + 2] = 255;\n\t\t\t\t\t\tdata[(x + y * width) * 4 + 3] = 255;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\t// smoothstep\n\t\t\t\t\t\tfloat t = bound((dist - edge1) / (edge0 - edge1), 0, 1);\n\t\t\t\t\t\tfloat f = 1 - t * t * (3.0 - 2.0 * t);\n\n\t\t\t\t\t\tdata[(x + y * width) * 4 + 0] = 255 * f;\n\t\t\t\t\t\tdata[(x + y * width) * 4 + 1] = 255 * f;\n\t\t\t\t\t\tdata[(x + y * width) * 4 + 2] = 255 * f;\n\t\t\t\t\t\tdata[(x + y * width) * 4 + 3] = 255 * f;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// draw a square in top-right\n\tfor (x = width - 4; x < width; ++x) {\n\t\tfor (y = height - 4; y < height; ++y) {\n\t\t\tdata[(x + y * width) * 4 + 0] = 255;\n\t\t\tdata[(x + y * width) * 4 + 1] = 255;\n\t\t\tdata[(x + y * width) * 4 + 2] = 255;\n\t\t\tdata[(x + y * width) * 4 + 3] = 255;\n\t\t}\n\t}\n\n\treturn data;\n}\n\nvoid Classic_LoadParticleTexures(int width, int height)\n{\n\t// TEX_NOSCALE - so no affect from gl_picmip and gl_maxsize\n\tbyte* data = Classic_CreateParticleTexture(width, height);\n\tparticletexture = R_LoadTexture(\"particles:classic\", width, height, data, TEX_MIPMAP | TEX_ALPHA | TEX_NOSCALE, 4);\n\trenderer.TextureWrapModeClamp(particletexture);\n\tQ_free(data);\n\n\tif (!R_TextureReferenceIsValid(particletexture)) {\n\t\tSys_Error(\"Classic_LoadParticleTexures: can't load texture\");\n\t\treturn;\n\t}\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\tif (R_UseModernOpenGL()) {\n\t\tGLM_LoadParticleTextures();\n\t}\n#endif\n}\n\nvoid Classic_AllocParticles(void)\n{\n\tr_numparticles = bound(ABSOLUTE_MIN_PARTICLES, r_particles_count.integer, ABSOLUTE_MAX_PARTICLES);\n}\n\nvoid Classic_InitParticles(void)\n{\n\tif (!r_numparticles) {\n\t\tClassic_AllocParticles();\n\t}\n\telse {\n\t\tClassic_ClearParticles(); // also re-alloc particles\n\t}\n\n\tClassic_LoadParticleTexures(default_size, default_size);\n}\n\nvoid Classic_ClearParticles(void)\n{\n\tif (!r_numparticles) {\n\t\treturn;\n\t}\n\n\tClassic_AllocParticles();\t// and alloc again\n\n\tr_numactiveparticles = 0;\n}\n\n#ifndef CLIENTONLY\nvoid R_ReadPointFile_f(void)\n{\n\tvfsfile_t *v;\n\tchar line[1024];\n\tconst char *s;\n\tvec3_t org;\n\tint c;\n\tqparticle_t *p;\n\tchar name[MAX_OSPATH];\n\n\tif (!com_serveractive)\n\t\treturn;\n\n\tsnprintf(name, sizeof(name), \"maps/%s.pts\", host_mapname.string);\n\n\tif (!(v = FS_OpenVFS(name, \"rb\", FS_ANY))) {\n\t\tCom_Printf(\"couldn't open %s\\n\", name);\n\t\treturn;\n\t}\n\n\tCom_Printf(\"Reading %s...\\n\", name);\n\tc = 0;\n\twhile (1) {\n\t\tVFS_GETS(v, line, sizeof(line));\n\t\ts = COM_Parse(line);\n\t\torg[0] = atof(com_token);\n\n\t\ts = COM_Parse(s);\n\t\tif (!s)\n\t\t\tbreak;\n\t\torg[1] = atof(com_token);\n\n\t\ts = COM_Parse(s);\n\t\tif (!s)\n\t\t\tbreak;\n\t\torg[2] = atof(com_token);\n\t\tif (COM_Parse(s))\n\t\t\tbreak;\n\n\t\tc++;\n\t\tif (r_numactiveparticles >= r_numparticles) {\n\t\t\tCom_Printf(\"Not enough free particles\\n\");\n\t\t\tbreak;\n\t\t}\n\t\tp = &particles[r_numactiveparticles++];\n\n\t\tp->die = 99999;\n\t\tp->color = (-c) & 15;\n\t\tp->type = pt_static;\n\t\tVectorClear(p->vel);\n\t\tVectorCopy(org, p->org);\n\t}\n\n\tVFS_CLOSE(v);\n\tCom_Printf(\"%i points read\\n\", c);\n}\n#endif\n\nvoid Classic_ParticleExplosion(vec3_t org)\n{\n\tint\ti, j;\n\tqparticle_t\t*p;\n\n\tif (r_explosiontype.value != 9) {\n\t\tCL_ExplosionSprite(org);\n\t}\n\n\tif (r_explosiontype.value == 1) {\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < 1024; i++) {\n\t\tif (r_numactiveparticles >= r_numparticles) {\n\t\t\treturn;\n\t\t}\n\t\tp = &particles[r_numactiveparticles++];\n\n\t\tp->die = r_refdef2.time + 5;\n\t\tp->color = ramp1[0];\n\t\tp->ramp = rand() & 3;\n\t\tif (i & 1) {\n\t\t\tp->type = pt_explode;\n\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\tp->org[j] = org[j] + ((rand() % 32) - 16);\n\t\t\t\tp->vel[j] = (rand() % 512) - 256;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tp->type = pt_explode2;\n\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\tp->org[j] = org[j] + ((rand() % 32) - 16);\n\t\t\t\tp->vel[j] = (rand() % 512) - 256;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Classic_BlobExplosion(vec3_t org)\n{\n\tint i, j;\n\tqparticle_t *p;\n\n\tfor (i = 0; i < 1024; i++) {\n\t\tif (r_numactiveparticles >= r_numparticles) {\n\t\t\treturn;\n\t\t}\n\t\tp = &particles[r_numactiveparticles++];\n\n\t\tp->die = r_refdef2.time + 1 + (rand() & 8) * 0.05;\n\n\t\tif (i & 1) {\n\t\t\tp->type = pt_blob;\n\t\t\tp->color = 66 + rand() % 6;\n\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\tp->org[j] = org[j] + ((rand() % 32) - 16);\n\t\t\t\tp->vel[j] = (rand() % 512) - 256;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tp->type = pt_blob2;\n\t\t\tp->color = 150 + rand() % 6;\n\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\tp->org[j] = org[j] + ((rand() % 32) - 16);\n\t\t\t\tp->vel[j] = (rand() % 512) - 256;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Classic_RunParticleEffect(vec3_t org, vec3_t dir, int color, int count)\n{\n\tint i, j, scale;\n\tqparticle_t *p;\n\n\tscale = (count > 130) ? 3 : (count > 20) ? 2 : 1;\n\n\tif (color == 256)\t// gunshot magic\n\t\tcolor = 0;\n\n\tfor (i = 0; i < count; i++) {\n\t\tif (r_numactiveparticles >= r_numparticles) {\n\t\t\treturn;\n\t\t}\n\t\tp = &particles[r_numactiveparticles++];\n\n\t\tp->die = r_refdef2.time + 0.1 * (rand() % 5);\n\t\tp->color = (color & ~7) + (rand() & 7);\n\t\tp->type = pt_grav;\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tp->org[j] = org[j] + scale * ((rand() & 15) - 8);\n\t\t\tp->vel[j] = dir[j] * 15;\n\t\t}\n\t}\n}\n\nvoid Classic_LavaSplash(vec3_t org)\n{\n\tint i, j, k;\n\tqparticle_t *p;\n\tfloat vel;\n\tvec3_t dir;\n\n\tfor (i = -16; i < 16; i++) {\n\t\tfor (j = -16; j < 16; j++) {\n\t\t\tfor (k = 0; k < 1; k++) {\n\t\t\t\tif (r_numactiveparticles >= r_numparticles) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tp = &particles[r_numactiveparticles++];\n\n\t\t\t\tp->die = r_refdef2.time + 2 + (rand() & 31) * 0.02;\n\t\t\t\tp->color = 224 + (rand() & 7);\n\t\t\t\tp->type = pt_grav;\n\n\t\t\t\tdir[0] = j * 8 + (rand() & 7);\n\t\t\t\tdir[1] = i * 8 + (rand() & 7);\n\t\t\t\tdir[2] = 256;\n\n\t\t\t\tp->org[0] = org[0] + dir[0];\n\t\t\t\tp->org[1] = org[1] + dir[1];\n\t\t\t\tp->org[2] = org[2] + (rand() & 63);\n\n\t\t\t\tVectorNormalizeFast(dir);\n\t\t\t\tvel = 50 + (rand() & 63);\n\t\t\t\tVectorScale(dir, vel, p->vel);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Classic_TeleportSplash(vec3_t org)\n{\n\tint i, j, k;\n\tqparticle_t *p;\n\tfloat vel;\n\tvec3_t dir;\n\n\tfor (i = -16; i < 16; i += 4) {\n\t\tfor (j = -16; j < 16; j += 4) {\n\t\t\tfor (k = -24; k < 32; k += 4) {\n\t\t\t\tif (r_numactiveparticles >= r_numparticles) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tp = &particles[r_numactiveparticles++];\n\n\t\t\t\tp->die = r_refdef2.time + 0.2 + (rand() & 7) * 0.02;\n\t\t\t\tp->color = 7 + (rand() & 7);\n\t\t\t\tp->type = pt_grav;\n\n\t\t\t\tdir[0] = j * 8;\n\t\t\t\tdir[1] = i * 8;\n\t\t\t\tdir[2] = k * 8;\n\n\t\t\t\tp->org[0] = org[0] + i + (rand() & 3);\n\t\t\t\tp->org[1] = org[1] + j + (rand() & 3);\n\t\t\t\tp->org[2] = org[2] + k + (rand() & 3);\n\n\t\t\t\tVectorNormalizeFast(dir);\n\t\t\t\tvel = 50 + (rand() & 63);\n\t\t\t\tVectorScale(dir, vel, p->vel);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Classic_ParticleTrail(vec3_t start, vec3_t end, vec3_t* trail_stop, trail_type_t type)\n{\n\tvec3_t point, delta, dir;\n\tfloat len;\n\tint i, j, num_particles;\n\tqparticle_t *p;\n\tstatic int tracercount;\n\n\tVectorCopy(start, point);\n\tVectorSubtract(end, start, delta);\n\tif (!(len = VectorLength(delta))) {\n\t\tgoto done;\n\t}\n\tVectorScale(delta, 1 / len, dir);\t//unit vector in direction of trail\n\n\t// Convert from AMF/vult particle trail types\n\tif (type == AMF_ROCKET_TRAIL) {\n\t\ttype = ALT_ROCKET_TRAIL;\n\t}\n\telse if (type == BLEEDING_TRAIL || type == BLEEDING_TRAIL2) {\n\t\ttype = BLOOD_TRAIL;\n\t}\n\telse if (type == RAIL_TRAIL || type == RAIL_TRAIL2) {\n\t\ttype = RAIL_TRAIL;\n\t}\n\telse if (type == LAVA_TRAIL) {\n\t\ttype = ROCKET_TRAIL;\n\t}\n\n\tswitch (type) {\n\tcase ALT_ROCKET_TRAIL:\n\t\tlen /= 1.5; break;\n\tcase BLOOD_TRAIL:\n\t\tlen /= 6; break;\n\tdefault:\n\t\tlen /= 3; break;\n\t}\n\n\tif (!(num_particles = (int)len)) {\n\t\tgoto done;\n\t}\n\n\tVectorScale(delta, 1.0 / num_particles, delta);\n\n\tfor (i = 0; i < num_particles && r_numactiveparticles < r_numparticles; i++) {\n\t\tp = &particles[r_numactiveparticles++];\n\n\t\tVectorClear(p->vel);\n\t\tp->die = r_refdef2.time + 2;\n\n\t\tswitch (type) {\n\t\tcase GRENADE_TRAIL:\n\t\t\tp->ramp = (rand() & 3) + 2;\n\t\t\tp->color = ramp3[(int)p->ramp];\n\t\t\tp->type = pt_fire;\n\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\tp->org[j] = point[j] + ((rand() % 6) - 3);\n\t\t\tbreak;\n\t\tcase BLOOD_TRAIL:\n\t\t\tp->type = pt_slowgrav;\n\t\t\tp->color = 67 + (rand() & 3);\n\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\tp->org[j] = point[j] + ((rand() % 6) - 3);\n\t\t\tbreak;\n\t\tcase BIG_BLOOD_TRAIL:\n\t\t\tp->type = pt_slowgrav;\n\t\t\tp->color = 67 + (rand() & 3);\n\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\tp->org[j] = point[j] + ((rand() % 6) - 3);\n\t\t\tbreak;\n\t\tcase TRACER1_TRAIL:\n\t\tcase TRACER2_TRAIL:\n\t\t\tp->die = r_refdef2.time + 0.5;\n\t\t\tp->type = pt_static;\n\t\t\tif (type == TRACER1_TRAIL)\n\t\t\t\tp->color = 52 + ((tracercount & 4) << 1);\n\t\t\telse\n\t\t\t\tp->color = 230 + ((tracercount & 4) << 1);\n\n\t\t\ttracercount++;\n\n\t\t\tVectorCopy(point, p->org);\n\t\t\tif (tracercount & 1) {\n\t\t\t\tp->vel[0] = 90 * dir[1];\n\t\t\t\tp->vel[1] = 90 * -dir[0];\n\t\t\t}\n\t\t\telse {\n\t\t\t\tp->vel[0] = 90 * -dir[1];\n\t\t\t\tp->vel[1] = 90 * dir[0];\n\t\t\t}\n\t\t\tbreak;\n\t\tcase VOOR_TRAIL:\n\t\t\tp->color = 9 * 16 + 8 + (rand() & 3);\n\t\t\tp->type = pt_static;\n\t\t\tp->die = r_refdef2.time + 0.3;\n\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\tp->org[j] = point[j] + ((rand() & 15) - 8);\n\t\t\tbreak;\n\t\tcase ALT_ROCKET_TRAIL:\n\t\t\tp->ramp = (rand() & 3);\n\t\t\tp->color = ramp3[(int)p->ramp];\n\t\t\tp->type = pt_fire;\n\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\tp->org[j] = point[j] + ((rand() % 6) - 3);\n\t\t\tbreak;\n\t\tcase RAIL_TRAIL:\n\t\tcase ROCKET_TRAIL:\n\t\tdefault:\n\t\t\tp->ramp = (rand() & 3);\n\t\t\tp->color = ramp3[(int)p->ramp];\n\t\t\tp->type = pt_fire;\n\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\tp->org[j] = point[j] + ((rand() % 6) - 3);\n\t\t\tbreak;\n\t\t}\n\n\t\tVectorAdd(point, delta, point);\n\t}\ndone:\n\tif (trail_stop) {\n\t\tVectorCopy(point, *trail_stop);\n\t}\n}\n\n// deurk: ported from zquake, thx Tonik\nvoid Classic_ParticleRailTrail(vec3_t start, vec3_t end, int color)\n{\n\tvec3_t          move, vec, right, up, dir;\n\tfloat           len, dec, d, c, s;\n\tint             i, j;\n\tqparticle_t      *p;\n\n\tVectorCopy(start, move);\n\tVectorSubtract(end, start, vec);\n\tlen = VectorNormalize(vec);\n\n\tMakeNormalVectors(vec, right, up);\n\n\t// color spiral\n\tfor (i = 0; i < len; i++) {\n\t\tif (r_numactiveparticles >= r_numparticles) {\n\t\t\treturn;\n\t\t}\n\t\tp = &particles[r_numactiveparticles++];\n\n\t\tp->type = pt_rail;\n\n\t\tp->die = r_refdef2.time + 2;\n\n\t\td = i * 0.1;\n\t\tc = cos(d);\n\t\ts = sin(d);\n\n\t\tVectorScale(right, c, dir);\n\t\tVectorMA(dir, s, up, dir);\n\n\t\t//p->alpha = 1.0;\n\t\t//p->alphavel = -1.0 / (1+frand()*0.2);\n\t\tp->color = color + (rand() & 7);\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tp->org[j] = move[j] + dir[j] * 3;\n\t\t\tp->vel[j] = dir[j] * 2; //p->vel[j] = dir[j]*6;\n\t\t}\n\n\t\tVectorAdd(move, vec, move);\n\t}\n\n\tdec = 1.5;\n\tVectorScale(vec, dec, vec);\n\tVectorCopy(start, move);\n\n\t// white core\n\twhile (len > 0) {\n\t\tlen -= dec;\n\n\t\tif (r_numactiveparticles >= r_numparticles) {\n\t\t\treturn;\n\t\t}\n\t\tp = &particles[r_numactiveparticles++];\n\n\t\tp->type = pt_rail;\n\n\t\tp->die = r_refdef2.time + 2;\n\n\t\t//p->alpha = 1.0;\n\t\t//p->alphavel = -1.0 / (0.6+frand()*0.2);\n\t\tp->color = 0x0 + (rand() & 15);\n\n\t\tfor (j = 0; j < 3; j++) {\n\t\t\tp->org[j] = move[j] + crand() * 2;\n\t\t\tp->vel[j] = crand()*0.5; //p->vel[j] = crand()*3;\n\t\t}\n\n\t\tVectorAdd(move, vec, move);\n\t}\n}\n\nstatic int particles_to_draw = 0;\n\n// Particles are scaled according to distance from player's POV,\n//   so in multi-view we need to recalculate each time\nvoid Classic_ReScaleParticles(void)\n{\n\tfloat r_partscale = 0.004 * tan(r_refdef.fov_x * (M_PI / 180) * 0.5f);\n\tint i;\n\tvec3_t up, right, scaled_vpn;\n\tr_sprite3d_vert_t* vert;\n\tfloat dist_precalc = 0;\n\n\tif (particles_to_draw == 0) {\n\t\treturn;\n\t}\n\n\tvert = glvertices;\n\tVectorScale(vup, 1.5, up);\n\tVectorScale(vright, 1.5, right);\n\tVectorScale(vpn, r_partscale, scaled_vpn);\n\n\tdist_precalc = 1 - DotProduct(r_origin, scaled_vpn);\n\tfor (i = 0; i < particles_to_draw; ++i, vert += 3) {\n\t\tglm_particle_t* glpart = &glparticles[i];\n\t\tfloat scale = glpart->gl_scale = dist_precalc + DotProduct(glpart->gl_org, scaled_vpn);\n\n\t\tR_Sprite3DSetVert(vert, glpart->gl_org[0], glpart->gl_org[1], glpart->gl_org[2], 0, 0, vert->color, particletexture_array_index);\n\t\tR_Sprite3DSetVert(vert + 1, glpart->gl_org[0] + up[0] * scale, glpart->gl_org[1] + up[1] * scale, glpart->gl_org[2] + up[2] * scale, particletexture_array_max_s, 0, vert->color, particletexture_array_index);\n\t\tR_Sprite3DSetVert(vert + 2, glpart->gl_org[0] + right[0] * scale, glpart->gl_org[1] + right[1] * scale, glpart->gl_org[2] + right[2] * scale, 0, particletexture_array_max_t, vert->color, particletexture_array_index);\n\t}\n}\n\n// Prepares particles to be drawn this frame\nstatic void Classic_PrepareParticles(void)\n{\n\tqparticle_t* p;\n\tunsigned char *at;\n\tfloat theAlpha;\n\tfloat scale;\n\tvec3_t up, right;\n\tint i;\n\tfloat dist_precalc;\n\tvec3_t scaled_vpn;\n\n\tparticles_to_draw = 0;\n\tif (r_numactiveparticles == 0 || (!r_drawparticles.integer && !Rulesets_RestrictParticles())) {\n\t\treturn;\n\t}\n\n\tframeStats.particle_count = r_numactiveparticles;\n\n\tVectorScale(vup, 1.5, up);\n\tVectorScale(vright, 1.5, right);\n\tVectorScale(vpn, 0.004 * r_refdef2.distanceScale, scaled_vpn);\n\n\t// load texture if not done yet\n\tif (!R_TextureReferenceIsValid(particletexture)) {\n\t\tClassic_LoadParticleTexures(default_size, default_size);\n\t}\n\n\tp = particles;\n\tdist_precalc = 1 - DotProduct(r_origin, scaled_vpn);\n\tfor (i = 0; i < r_numactiveparticles; ++i, ++p) {\n\t\tif (p->die >= r_refdef2.time) {\n\t\t\tglm_particle_t* glpart;\n\t\t\tr_sprite3d_vert_t* vert;\n\n\t\t\t// hack a scale up to keep particles from disapearing\n\t\t\tscale = dist_precalc + DotProduct(p->org, scaled_vpn);\n\n\t\t\tglpart = &glparticles[particles_to_draw];\n\t\t\tvert = &glvertices[particles_to_draw * 3];\n\t\t\tat = (byte *)&d_8to24table[(int)p->color];\n\n\t\t\tif (p->type == pt_fire) {\n\t\t\t\ttheAlpha = (6 - p->ramp) / 6;\n\t\t\t\tglpart->gl_color[0] = at[0] * theAlpha;\n\t\t\t\tglpart->gl_color[1] = at[1] * theAlpha;\n\t\t\t\tglpart->gl_color[2] = at[2] * theAlpha;\n\t\t\t\tglpart->gl_color[3] = 255 * theAlpha;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tglpart->gl_color[0] = at[0];\n\t\t\t\tglpart->gl_color[1] = at[1];\n\t\t\t\tglpart->gl_color[2] = at[2];\n\t\t\t\tglpart->gl_color[3] = 255;\n\t\t\t}\n\n\t\t\tglpart->gl_scale = scale;\n\t\t\tVectorCopy(p->org, glpart->gl_org);\n\n\t\t\t*((unsigned int*)vert->color) = *(unsigned int*)glpart->gl_color;\n\t\t\tVectorSet(vert->tex, 0, 0, particletexture_array_index);\n\t\t\tVectorSet(vert->position, glpart->gl_org[0], glpart->gl_org[1], glpart->gl_org[2]);\n\t\t\t++vert;\n\n\t\t\t*((unsigned int*)vert->color) = *(unsigned int*)glpart->gl_color;\n\t\t\tVectorSet(vert->tex, particletexture_array_max_s, 0, particletexture_array_index);\n\t\t\tVectorSet(vert->position, glpart->gl_org[0] + up[0] * scale, glpart->gl_org[1] + up[1] * scale, glpart->gl_org[2] + up[2] * scale);\n\t\t\t++vert;\n\n\t\t\t*((unsigned int*)vert->color) = *(unsigned int*)glpart->gl_color;\n\t\t\tVectorSet(vert->tex, 0, particletexture_array_max_t, particletexture_array_index);\n\t\t\tVectorSet(vert->position, glpart->gl_org[0] + right[0] * scale, glpart->gl_org[1] + right[1] * scale, glpart->gl_org[2] + right[2] * scale);\n\t\t\t++vert;\n\n\t\t\t++particles_to_draw;\n\t\t}\n\t}\n}\n\nvoid R_InitParticles(void)\n{\n\tif (!host_initialized) {\n\t\tint i;\n\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_PARTICLES);\n\t\tCvar_Register(&r_particles_count);\n\t\tCvar_Register(&r_drawparticles);\n\t\tCvar_ResetCurrentGroup();\n\n\t\tif ((i = COM_CheckParm(cmdline_param_client_particlecount)) && i + 1 < COM_Argc()) {\n\t\t\tCvar_SetValue(&r_particles_count, Q_atoi(COM_Argv(i + 1)));\n\t\t}\n\t}\n\n\tClassic_InitParticles();\n\tQMB_InitParticles();\n}\n\nvoid R_ParticleFrame(void)\n{\n\tif (!r_worldentity.model || !cl.worldmodel) {\n\t\treturn;\n\t}\n\n\tClassic_PrepareParticles();\n\tQMB_CalculateParticles();\n}\n\nvoid R_ClearParticles(void)\n{\n\tClassic_ClearParticles();\n\tQMB_ClearParticles();\n}\n\nvoid R_DrawParticles(void)\n{\n\tif (!r_drawparticles.integer && !Rulesets_RestrictParticles()) {\n\t\treturn;\n\t}\n\n\tif (CL_MultiviewEnabled() && CL_MultiviewCurrentView() != 0) {\n\t\tClassic_ReScaleParticles();\n\t}\n\n\tif (particles_to_draw) {\n\t\trenderer.DrawClassicParticles(particles_to_draw);\n\t}\n\tQMB_DrawParticles();\n}\n\n#define RunParticleEffect(var, org, dir, color, count)\t\t\\\n\tif (qmb_initialized && gl_part_##var.value)\t\t\t\t\\\n\t\tQMB_RunParticleEffect(org, dir, color, count);\t\t\\\n\telse\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tClassic_RunParticleEffect(org, dir, color, count);\n\nvoid R_RunParticleEffect(vec3_t org, vec3_t dir, int color, int count)\n{\n\tif (color == 73 || color == 225) {\n\t\tRunParticleEffect(blood, org, dir, color, count);\n\t\treturn;\n\t}\n\n\tif (color == 256 /* gunshot magic */) {\n\t\tRunParticleEffect(gunshots, org, dir, color, count);\n\t\treturn;\n\t}\n\n\tswitch (count) {\n\tcase 10:\n\tcase 20:\n\tcase 30:\n\t\tRunParticleEffect(spikes, org, dir, color, count);\n\t\tbreak;\n\tcase 50: // LG Blood\n\t\tRunParticleEffect(blood, org, dir, color, count);\n\t\treturn;\n\tdefault:\n\t\tRunParticleEffect(gunshots, org, dir, color, count);\n\t}\n}\n\n// Used for temporary entities and untrackable effects\nvoid R_ParticleTrail(vec3_t start, vec3_t end, trail_type_t type)\n{\n\tswitch(type){\n\t\tcase BLOOD_TRAIL:\n\t\tcase BIG_BLOOD_TRAIL:\n\t\tif (qmb_initialized && gl_part_bloodtrails.integer) {\n\t\t\tQMB_ParticleTrail(start, end, type);\n\t\t}\n\t\telse {\n\t\t\tClassic_ParticleTrail(start, end, NULL, type);\n\t\t}\n\t\tbreak;\n\t\tdefault:\n\t\tif (qmb_initialized && gl_part_trails.integer) {\n\t\t\tQMB_ParticleTrail(start, end, type);\n\t\t}\n\t\telse {\n\t\t\tClassic_ParticleTrail(start, end, NULL, type);\n\t\t}\n\t\tbreak;\n\t}\n}\n\nvoid R_EntityParticleTrail(centity_t* cent, trail_type_t type)\n{\n\tswitch(type){\n\t\tcase BLOOD_TRAIL:\n\t\tcase BIG_BLOOD_TRAIL:\n\t\tif (qmb_initialized && gl_part_bloodtrails.integer) {\n\t\t\tQMB_EntityParticleTrail(cent, type);\n\t\t}\n\t\telse {\n\t\t\tClassic_ParticleTrail(cent->trails[0].stop, cent->lerp_origin, &cent->trails[0].stop, type);\n\t\t}\n\t\tbreak;\n\t\tdefault:\n\t\tif (qmb_initialized && gl_part_trails.integer) {\n\t\t\tQMB_EntityParticleTrail(cent, type);\n\t\t}\n\t\telse {\n\t\t\tClassic_ParticleTrail(cent->trails[0].stop, cent->lerp_origin, &cent->trails[0].stop, type);\n\t\t}\n\t\tbreak;\n\t}\n}\n\n#define ParticleFunction(var, name)\t\t\t\t\\\nvoid R_##name (vec3_t org) {\t\t\t\t\t\\\n\tif (qmb_initialized && gl_part_##var.value)\t\\\n\t\tQMB_##name(org);\t\t\t\t\t\t\\\n\telse\t\t\t\t\t\t\t\t\t\t\\\n\t\tClassic_##name(org);\t\t\t\t\t\\\n}\n\nParticleFunction(explosions, ParticleExplosion);\nParticleFunction(blobs, BlobExplosion);\nParticleFunction(lavasplash, LavaSplash);\nParticleFunction(telesplash, TeleportSplash);\n\n// Moves particles into new locations this frame\nstatic void Classic_MoveParticles(void)\n{\n\tqparticle_t *p;\n\tint i;\n\tfloat time2, time3, time1, dvel, frametime, grav;\n\n\tif (r_numactiveparticles == 0) {\n\t\treturn;\n\t}\n\n\tframetime = cls.frametime;\n\tif (ISPAUSED) {\n\t\tframetime = 0;\n\t}\n\ttime3 = frametime * 15;\n\ttime2 = frametime * 10; // 15;\n\ttime1 = frametime * 5;\n\tgrav = frametime * 800 * 0.05;\n\tdvel = 4 * frametime;\n\n\tp = particles;\n\tfor (i = 0; i < r_numactiveparticles; ++i, ++p) {\n\t\twhile (p->die < r_refdef2.time) {\n\t\t\tif (i >= r_numactiveparticles - 1) {\n\t\t\t\tgoto finished;\n\t\t\t}\n\n\t\t\t*p = particles[r_numactiveparticles - 1];\n\t\t\t--r_numactiveparticles;\n\t\t}\n\n\t\tVectorMA(p->org, frametime, p->vel, p->org);\n\n\t\tswitch (p->type) {\n\t\t\tcase pt_static:\n\t\t\t\tbreak;\n\t\t\tcase pt_fire:\n\t\t\t\tp->ramp += time1;\n\t\t\t\tif (p->ramp >= 6) {\n\t\t\t\t\tp->die = -1;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tp->color = ramp3[(int)p->ramp];\n\t\t\t\t}\n\t\t\t\tp->vel[2] += grav;\n\t\t\t\tbreak;\n\t\t\tcase pt_explode:\n\t\t\t\tp->ramp += time2;\n\t\t\t\tif (p->ramp >= 8) {\n\t\t\t\t\tp->die = -1;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tp->color = ramp1[(int)p->ramp];\n\t\t\t\t}\n\t\t\t\tVectorMA(p->vel, dvel, p->vel, p->vel);\n\t\t\t\tp->vel[2] -= grav * 30;\n\t\t\t\tbreak;\n\t\t\tcase pt_explode2:\n\t\t\t\tp->ramp += time3;\n\t\t\t\tif (p->ramp >= 8) {\n\t\t\t\t\tp->die = -1;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tp->color = ramp2[(int)p->ramp];\n\t\t\t\t}\n\t\t\t\tVectorMA(p->vel, -frametime, p->vel, p->vel);\n\t\t\t\tp->vel[2] -= grav * 30;\n\t\t\t\tbreak;\n\t\t\tcase pt_blob:\n\t\t\t\tVectorMA(p->vel, dvel, p->vel, p->vel);\n\t\t\t\tp->vel[2] -= grav;\n\t\t\t\tbreak;\n\t\t\tcase pt_blob2:\n\t\t\t\tVectorMA(p->vel, -dvel, p->vel, p->vel);\n\t\t\t\tp->vel[2] -= grav;\n\t\t\t\tbreak;\n\t\t\tcase pt_slowgrav:\n\t\t\tcase pt_grav:\n\t\t\t\tp->vel[2] -= grav;\n\t\t\t\tbreak;\n\t\t\tcase pt_rail:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\nfinished:\n\treturn;\n}\n\nvoid R_ParticleEndFrame(void)\n{\n\tClassic_MoveParticles();\n}\n"
  },
  {
    "path": "src/r_part_trails.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"vx_stuff.h\"\n#include \"qmb_particles.h\"\n\nstatic int fix_trail_num_for_grens(int trail_num)\n{\n\t//trails for rockets and grens are the same\n\t//only nums 1 and 2 must be swapped\n\tswitch (trail_num) {\n\t\tcase 1: trail_num = 2; break;\n\t\tcase 2: trail_num = 1; break;\n\t}\n\n\treturn trail_num;\n}\n\nstatic void R_MissileTrail(centity_t *cent, int trail_num)\n{\n\tif ((trail_num == 8 || trail_num == 10 || trail_num == 11) && !qmb_initialized) {\n\t\ttrail_num = 1;\n\t}\n\n\tif (trail_num == 0) {\n\t\tVectorCopy(cent->current.origin, cent->trails[0].stop);\n\t}\n\telse if (trail_num == 1) {\n\t\tR_EntityParticleTrail(cent, ROCKET_TRAIL);\n\t}\n\telse if (trail_num == 2) {\n\t\tR_EntityParticleTrail(cent, GRENADE_TRAIL);\n\t}\n\telse if (trail_num == 3) {\n\t\tR_EntityParticleTrail(cent, ALT_ROCKET_TRAIL);\n\t}\n\telse if (trail_num == 4) {\n\t\tR_EntityParticleTrail(cent, BLOOD_TRAIL);\n\t}\n\telse if (trail_num == 5) {\n\t\tR_EntityParticleTrail(cent, BIG_BLOOD_TRAIL);\n\t}\n\telse if (trail_num == 6) {\n\t\tR_EntityParticleTrail(cent, TRACER1_TRAIL);\n\t}\n\telse if (trail_num == 7) {\n\t\tR_EntityParticleTrail(cent, TRACER2_TRAIL);\n\t}\n\telse if (trail_num == 8) {\n\t\t// VULT PARTICLES\n\t\tbyte color[3];\n\t\tcolor[0] = 0; color[1] = 70; color[2] = 255;\n\t\tFireballTrail(cent, color, 2, 0.5);\n\t}\n\telse if (trail_num == 9) {\n\t\tR_EntityParticleTrail(cent, LAVA_TRAIL);\n\t}\n\telse if (trail_num == 10) {\n\t\t// VULT PARTICLES\n\t\tFuelRodGunTrail(cent);\n\t}\n\telse if (trail_num == 11) {\n\t\tbyte color[3];\n\t\tcolor[0] = 255; color[1] = 70; color[2] = 5;\n\t\tFireballTrailWave(cent, color, 2, 0.5, cent->current.angles);\n\t}\n\telse if (trail_num == 12) {\n\t\tR_EntityParticleTrail(cent, AMF_ROCKET_TRAIL);\n\t}\n\telse if (trail_num == 14) {\n\t\tR_EntityParticleTrail(cent, RAIL_TRAIL);\n\t}\n\telse {\n\t\tR_EntityParticleTrail(cent, GRENADE_TRAIL);\n\t}\n}\n\n// Moved out of CL_LinkPacketEntities() so NQD playback can use same code.\nvoid CL_AddParticleTrail(entity_t* ent, centity_t* cent, customlight_t* cst_lt, entity_state_t *state)\n{\n\textern cvar_t gl_no24bit;\n\tmodel_t* model = cl.model_precache[state->modelindex];\n\tfloat rocketlightsize = 0.0f;\n\n\tif (model->modhint == MOD_LAVABALL) {\n\t\tR_EntityParticleTrail(cent, LAVA_TRAIL);\n\t}\n\telse {\n\t\tif (model->flags & EF_ROCKET) {\n\t\t\tR_MissileTrail(cent, r_rockettrail.integer);\n\n\t\t\t// Add rocket lights\n\t\t\trocketlightsize = 200.0 * bound(0, r_rocketlight.value, 1);\n\t\t\tif (rocketlightsize >= 1) {\n\t\t\t\textern cvar_t gl_rl_globe;\n\t\t\t\tint bubble = gl_rl_globe.integer ? 2 : 1;\n\n\t\t\t\tif ((r_rockettrail.value < 8 || r_rockettrail.value == 12) && model->modhint != MOD_LAVABALL) {\n\t\t\t\t\tdlightColorEx(r_rocketlightcolor.value, r_rocketlightcolor.string, lt_rocket, false, cst_lt);\n\t\t\t\t\tCL_NewDlightEx(state->number, ent->origin, rocketlightsize, 0.1, cst_lt, bubble);\n\t\t\t\t\tif (!ISPAUSED && amf_coronas.integer) {\n\t\t\t\t\t\t//VULT CORONAS\n\t\t\t\t\t\tR_CoronasEntityNew(C_ROCKETLIGHT, cent);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (r_rockettrail.value == 9 || r_rockettrail.value == 11) {\n\t\t\t\t\tCL_NewDlight(state->number, ent->origin, rocketlightsize, 0.1, lt_default, bubble);\n\t\t\t\t}\n\t\t\t\telse if (r_rockettrail.value == 8) {\n\t\t\t\t\t// PLASMA ROCKETS\n\t\t\t\t\tCL_NewDlight(state->number, ent->origin, rocketlightsize, 0.1, lt_blue, bubble);\n\t\t\t\t}\n\t\t\t\telse if (r_rockettrail.value == 10) {\n\t\t\t\t\t// FUEL ROD GUN\n\t\t\t\t\tCL_NewDlight(state->number, ent->origin, rocketlightsize, 0.1, lt_green, bubble);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (model->flags & EF_GRENADE) {\n\t\t\tif (model->modhint == MOD_BUILDINGGIBS) {\n\t\t\t\tR_EntityParticleTrail(cent, GRENADE_TRAIL);\n\t\t\t}\n\t\t\telse if (r_grenadetrail.integer && model->modhint != MOD_RAIL) {\n\t\t\t\tR_MissileTrail(cent, fix_trail_num_for_grens(r_grenadetrail.integer));\n\t\t\t}\n\t\t\telse if (r_railtrail.integer && model->modhint == MOD_RAIL) {\n\t\t\t\tR_MissileTrail(cent, fix_trail_num_for_grens(r_railtrail.integer));\n\t\t\t}\n\t\t\telse {\n\t\t\t\tR_MissileTrail(cent, 0);\n\t\t\t}\n\t\t}\n\t\telse if (model->flags & EF_GIB) {\n\t\t\tif (amf_part_gibtrails.integer) {\n\t\t\t\tR_EntityParticleTrail(cent, BLEEDING_TRAIL);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tR_EntityParticleTrail(cent, BLOOD_TRAIL);\n\t\t\t}\n\t\t}\n\t\telse if (model->flags & EF_ZOMGIB) {\n\t\t\tif (model->modhint == MOD_RAIL2) {\n\t\t\t\tR_EntityParticleTrail(cent, RAIL_TRAIL2);\n\t\t\t}\n\t\t\telse if (amf_part_gibtrails.value) {\n\t\t\t\tR_EntityParticleTrail(cent, BLEEDING_TRAIL);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tR_EntityParticleTrail(cent, BIG_BLOOD_TRAIL);\n\t\t\t}\n\t\t}\n\t\telse if (model->flags & EF_TRACER) {\n\t\t\t// VULT TRACER GLOW\n\t\t\trocketlightsize = 35 * (1 + bound(0, r_rocketlight.value, 1));\n\t\t\tCL_NewDlight(state->number, ent->origin, rocketlightsize, 0.01, lt_green, true);\n\t\t\tif (!ISPAUSED && amf_coronas.integer) {\n\t\t\t\tR_CoronasEntityNew(C_WIZLIGHT, cent);\n\t\t\t}\n\n\t\t\tR_EntityParticleTrail(cent, TRACER1_TRAIL);\n\t\t}\n\t\telse if (model->flags & EF_TRACER2) {\n\t\t\t// VULT TRACER GLOW\n\t\t\trocketlightsize = 35 * (1 + bound(0, r_rocketlight.value, 1));\n\t\t\tCL_NewDlight(state->number, ent->origin, rocketlightsize, 0.01, lt_default, true);\n\t\t\tif (!ISPAUSED && amf_coronas.integer) {\n\t\t\t\tR_CoronasEntityNew(C_KNIGHTLIGHT, cent);\n\t\t\t}\n\n\t\t\tR_EntityParticleTrail(cent, TRACER2_TRAIL);\n\t\t}\n\t\telse if (model->flags & EF_TRACER3) {\n\t\t\t// VULT TRACER GLOW\n\t\t\trocketlightsize = 35 * (1 + bound(0, r_rocketlight.value, 1));\n\t\t\tCL_NewDlight(state->number, ent->origin, rocketlightsize, 0.01, lt_blue, true);\n\t\t\tif (!ISPAUSED && amf_coronas.integer) {\n\t\t\t\tR_CoronasEntityNew(C_VORELIGHT, cent);\n\t\t\t}\n\n\t\t\tR_EntityParticleTrail(cent, VOOR_TRAIL);\n\t\t}\n\t\telse if (model->modhint == MOD_SPIKE && amf_nailtrail.integer && !gl_no24bit.integer) {\n\t\t\t// VULT NAILTRAIL\n\t\t\tif (amf_nailtrail_plasma.integer) {\n\t\t\t\tbyte color[3];\n\t\t\t\tcolor[0] = 0; color[1] = 70; color[2] = 255;\n\t\t\t\tFireballTrail(cent, color, 0.6, 0.3);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tParticleNailTrail(cent, 2, 0.4f);\n\t\t\t}\n\t\t}\n\t\telse if (model->modhint_trail == MOD_TF_TRAIL && amf_extratrails.integer) {\n\t\t\t// VULT TRAILS\n\t\t\tParticleAlphaTrail(cent, 4, 0.4);\n\t\t}\n\t\telse if (model->modhint == MOD_LASER && amf_extratrails.integer) {\n\t\t\trocketlightsize = 35 * (1 + bound(0, r_rocketlight.value, 1));\n\t\t\tCL_NewDlight(state->number, ent->origin, rocketlightsize, 0.01, lt_default, true);\n\t\t\tif (!ISPAUSED && amf_coronas.integer) {\n\t\t\t\tR_CoronasEntityNew(C_KNIGHTLIGHT, cent);\n\t\t\t}\n\t\t\tif (cent->trails[1].lasttime + 0.01 < particle_time) {\n\t\t\t\tVX_LightningTrail(cent->trails[1].stop, ent->origin);\n\t\t\t\tVectorCopy(ent->origin, cent->trails[1].stop);\n\t\t\t\tcent->trails[1].lasttime = particle_time;\n\t\t\t}\n\t\t\tR_EntityParticleTrail(cent, TRACER2_TRAIL);\n\t\t}\n\t\telse if (model->modhint == MOD_DETPACK) {\n\t\t\t// VULT CORONAS\n\t\t\tif (qmb_initialized && amf_detpacklights.integer) {\n\t\t\t\tvec3_t liteorg, forward, right, up;\n\t\t\t\tVectorCopy(ent->origin, liteorg);\n\t\t\t\tAngleVectors(ent->angles, forward, right, up);\n\t\t\t\tVectorMA(liteorg, -15, up, liteorg);\n\t\t\t\tVectorMA(liteorg, -10, forward, liteorg);\n\n\t\t\t\t// MEAG: Fixme, was *old_origin, liteorg, cent->trail_origin - need to add check for detpack in ParticleAlphaTrail (?)\n\t\t\t\tParticleAlphaTrail(cent, 10, 0.8);\n\t\t\t\tif (!ISPAUSED && amf_coronas.integer) {\n\t\t\t\t\tif (ent->skinnum > 0) {\n\t\t\t\t\t\tR_CoronasNew(C_REDLIGHT, liteorg);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tR_CoronasNew(C_GREENLIGHT, liteorg);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// VULT BUILDING SPARKS\n\t\tif (!ISPAUSED && amf_buildingsparks.value && (model->modhint == MOD_BUILDINGGIBS && (rand() % 40 < 2))) {\n\t\t\tvec3_t liteorg, forward, right, up;\n\t\t\tbyte col[3] = { 60, 100, 240 };\n\t\t\tVectorCopy(ent->origin, liteorg);\n\t\t\tAngleVectors(ent->angles, forward, right, up);\n\t\t\tVectorMA(liteorg, rand() % 10 - 5, up, liteorg);\n\t\t\tVectorMA(liteorg, rand() % 10 - 5, right, liteorg);\n\t\t\tVectorMA(liteorg, rand() % 10 - 5, forward, liteorg);\n\t\t\tSparkGen(liteorg, col, (int)(6 * amf_buildingsparks.value), 100, 1);\n\t\t\tif (amf_coronas.integer) {\n\t\t\t\tR_CoronasNew(C_BLUESPARK, liteorg);\n\t\t\t}\n\t\t}\n\n\t\t// VULT TESLA CHARGE - Tesla or shambler in charging animation\n\t\tif (((model->modhint == MOD_TESLA && ent->frame >= 7 && ent->frame <= 12) ||\n\t\t\t(model->modhint == MOD_SHAMBLER && ent->frame >= 65 && ent->frame <= 68))\n\t\t\t&& !ISPAUSED && amf_cutf_tesla_effect.value) {\n\t\t\tvec3_t liteorg;\n\t\t\tVectorCopy(ent->origin, liteorg);\n\t\t\tliteorg[2] += 32;\n\t\t\tVX_TeslaCharge(liteorg);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/r_particles_qmb.c",
    "content": "/*\nCopyright (C) 2002-2003, Dr Labman, A. Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#include \"quakedef.h\"\n#include \"vx_stuff.h\"\n#include \"pmove.h\"\n#include \"glm_texture_arrays.h\"\n#include \"r_particles_qmb.h\"\n#include \"qmb_particles.h\"\n#include \"r_texture.h\"\n#include \"r_matrix.h\"\n#include \"r_state.h\"\n\n//VULT\nstatic float varray_vertex[16];\n\nint ParticleCount, ParticleCountHigh, CoronaCount, CoronaCountHigh;\n\n#define FLAME_FRAME_TOTAL      50  // how many sequences we have, per lifetime\n#define FLAME_GRAVITY          10  // how quickly it increases\n\ntypedef struct flame_s {\n\tvec3_t pos;\n\tunsigned char alpha;\n\tfloat size;\n} flame_t;\n\nstatic flame_t flame_frames[FLAME_FRAME_TOTAL][FLAME_FRAME_TOTAL];\n\nstatic void R_ParticleFlamePrecalculate(void)\n{\n\tint i, p;\n\n\tfor (i = 0; i < FLAME_FRAME_TOTAL; ++i) {\n\t\t// Create first frame\n\t\tfloat vel_x = lhrandom(-3, 3); // (rand() % 6) - 3;\n\t\tfloat vel_y = lhrandom(-3, 3); // (rand() % 6) - 3;\n\n\t\tfor (p = 0; p < FLAME_FRAME_TOTAL; ++p) {\n\t\t\tfloat t = (1.0f / FLAME_FRAME_TOTAL) * p;\n\t\t\tflame_t* f = &flame_frames[(p + i) % FLAME_FRAME_TOTAL][p];\n\n\t\t\tf->pos[0] = vel_x * t;\n\t\t\tf->pos[1] = vel_y * t;\n\t\t\tf->pos[2] = (1 + FLAME_GRAVITY * t * t) * 4;\n\t\t\tf->alpha = (unsigned char)((1 - (p * 1.0f / FLAME_FRAME_TOTAL)) * 200.0f);\n\t\t\tf->size = 7 - 3.5 * t * 0.8f;\n\t\t}\n\t}\n}\n\n//VULT PARTICLES\nvoid RainSplash(vec3_t org);\nvoid ParticleStats (int change);\nvoid VX_ParticleTrail (vec3_t start, vec3_t end, float size, float time, col_t color);\nstatic void R_PreCalcBeamVerts(vec3_t org1, vec3_t org2, vec3_t right1, vec3_t right2);\nstatic void R_CalcBeamVerts(float *vert, const vec3_t org1, const vec3_t org2, const vec3_t right1, const vec3_t right2, float width);\n\ntypedef struct part_blend_info_s {\n\tr_blendfunc_t func;\n\n\tfunc_color_transform_t color_transform;\n} part_blend_info_t;\n\nstatic void blend_premult_alpha(col_t input, col_t output)\n{\n\tfloat alpha = input[3] / 255.0f;\n\n\toutput[0] = input[0] * alpha;\n\toutput[1] = input[1] * alpha;\n\toutput[2] = input[2] * alpha;\n\toutput[3] = input[3];\n}\n\nstatic void blend_additive(col_t input, col_t output)\n{\n\tfloat alpha = input[3] / 255.0f;\n\n\toutput[0] = input[0] * alpha;\n\toutput[1] = input[1] * alpha;\n\toutput[2] = input[2] * alpha;\n\toutput[3] = 0;\n}\n\nstatic void blend_one_one(col_t input, col_t output)\n{\n\toutput[0] = input[0];\n\toutput[1] = input[1];\n\toutput[2] = input[2];\n\toutput[3] = 0;\n}\n\nstatic void blend_color_constant(col_t input, col_t output)\n{\n\toutput[0] = output[1] = output[2] = 0;\n\toutput[3] = input[3];\n}\n\nstatic part_blend_info_t blend_options[NUMBER_OF_BLEND_TYPES] = {\n\t// BLEND_GL_SRC_ALPHA_GL_ONE_MINUS_SRC_ALPHA\n\t{ r_blendfunc_premultiplied_alpha, blend_premult_alpha },\n\t// BLEND_GL_SRC_ALPHA_GL_ONE (additive)\n\t{ r_blendfunc_premultiplied_alpha, blend_additive },\n\t// BLEND_GL_ONE_GL_ONE, (meag: was blend_additive... telesplash only)\n\t{ r_blendfunc_premultiplied_alpha, blend_one_one },\n\t// BLEND_GL_ZERO_GL_ONE_MINUS_SRC_COLOR_CONSTANT\n\t{ r_blendfunc_premultiplied_alpha, blend_color_constant },\n\t// BLEND_GL_ZERO_GL_ONE_MINUS_SRC_COLOR, (this is now for varying color only, can't convert?)\n\t{ r_blendfunc_src_zero_dest_one_minus_src_color, blend_premult_alpha }\n};\n\n#define MAX_BEAM_TRAIL 10\n#define TEXTURE_DETAILS(x) (R_UseModernOpenGL() ? x->tex_array : x->texnum),(x->tex_index)\n\nstatic float sint[7] = {0.000000, 0.781832, 0.974928, 0.433884, -0.433884, -0.974928, -0.781832};\nstatic float cost[7] = {1.000000, 0.623490, -0.222521, -0.900969, -0.900969, -0.222521, 0.623490};\n\nstatic particle_t* particles;\nparticle_t* free_particles;\nparticle_texture_t particle_textures[num_particletextures];\nparticle_type_t particle_types[num_particletypes];\nint particle_type_index[num_particletypes];\n\nstatic int r_numparticles;\t\t\nfloat particle_time;\n\nqbool qmb_initialized = false;\n\nstatic cvar_t gl_clipparticles = {\"gl_clipparticles\", \"1\"};\nstatic cvar_t gl_part_cache = { \"gl_part_cache\", \"1\", CVAR_LATCH_GFX };\nstatic cvar_t gl_bounceparticles = {\"gl_bounceparticles\", \"1\"};\ncvar_t amf_part_fulldetail = { \"gl_particle_fulldetail\", \"0\", CVAR_LATCH_GFX };\n\nstatic int ParticleContents(particle_t* p, vec3_t movement)\n{\n\tif (gl_part_cache.integer) {\n\t\tfloat moved;\n\n\t\tVectorAdd(p->cached_movement, movement, p->cached_movement);\n\t\tmoved = p->cached_movement[0] * p->cached_movement[0] + p->cached_movement[1] * p->cached_movement[1] + p->cached_movement[2] * p->cached_movement[2];\n\n\t\tif (p->cached_distance <= moved) {\n\t\t\tp->cached_contents = CM_CachedHullPointContents(&cl.clipmodels[1]->hulls[0], 0, p->org, &p->cached_distance);\n\n\t\t\tVectorClear(p->cached_movement);\n\t\t\tp->cached_distance *= p->cached_distance;\n\t\t}\n\n\t\treturn p->cached_contents;\n\t}\n\telse {\n\t\treturn CM_HullPointContents(&cl.clipmodels[1]->hulls[0], 0, p->org);\n\t}\n}\n\nqbool TraceLineN (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal)\n{\n\ttrace_t trace = PM_TraceLine (start, end);\n\n\tVectorCopy (trace.endpos, impact);\n\n\tif (normal)\n\t\tVectorCopy (trace.plane.normal, normal);\n\n\tif (!trace.allsolid)\n\t\treturn false;\n\n\tif (trace.startsolid)\n\t\treturn false;\n\n\treturn true;\n}\n\n#define ADD_PARTICLE_TEXTURE(_ptex, _texnum, _texindex, _components, _s1, _t1, _s2, _t2)\t\\\ndo {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tparticle_textures[_ptex].texnum = _texnum;\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tparticle_textures[_ptex].components = _components;\t\t\t\t\t\t\t\t\t\t\\\n\tparticle_textures[_ptex].coords[_texindex][0] = (_s1 + 1) / 256.0;\t\t\t\t\t\t\\\n\tparticle_textures[_ptex].coords[_texindex][1] = (_t1 + 1) / 256.0;\t\t\t\t\t\t\\\n\tparticle_textures[_ptex].coords[_texindex][2] = (_s2 - 1) / 256.0;\t\t\t\t\t\t\\\n\tparticle_textures[_ptex].coords[_texindex][3] = (_t2 - 1) / 256.0;\t\t\t\t\t\t\\\n\tmemcpy(particle_textures[_ptex].originalCoords, particle_textures[_ptex].coords, sizeof(particle_textures[_ptex].originalCoords)); \\\n} while(0);\n\nstatic void QMB_AddParticleType(part_type_t id, part_draw_t drawtype, int blendtype, part_tex_t texture_id, float startalpha, float grav, float accel, part_move_t move, float custom, int verts_per_primitive, int* count, const char* name)\n{\n\tparticle_type_t* type = &particle_types[*count];\n\n\tif (blend_options[blendtype].func == r_blendfunc_src_zero_dest_one_minus_src_color) {\n\t\tif (R_TextureReferenceIsValid(particle_textures[texture_id].texnum)) {\n\t\t\ttype->state = r_state_particles_qmb_textured_blood;\n\t\t}\n\t\telse {\n\t\t\tSys_Error(\"No rendering state available for QMB particle type %s\", name);\n\t\t}\n\t}\n\telse if (blend_options[blendtype].func == r_blendfunc_premultiplied_alpha) {\n\t\tif (R_TextureReferenceIsValid(particle_textures[texture_id].texnum)) {\n\t\t\ttype->state = r_state_particles_qmb_textured;\n\t\t}\n\t\telse {\n\t\t\ttype->state = r_state_particles_qmb_untextured;\n\t\t}\n\t}\n\telse {\n\t\tSys_Error(\"No rendering state available for QMB particle type %s\", name);\n\t}\n\ttype->blendtype = blendtype;\n\ttype->id = id;\n\ttype->drawtype = drawtype;\n\ttype->texture = texture_id;\n\ttype->startalpha = startalpha;\n\ttype->grav = 9.8 * grav;\n\ttype->accel = accel;\n\ttype->move = move;\n\ttype->custom = custom;\n\ttype->verts_per_primitive = verts_per_primitive;\n\ttype->billboard_type = SPRITE3D_PARTICLES_NEW_p_spark + id;\n\tassert(type->billboard_type < SPRITE3D_PARTICLES_NEW_LIMIT);\n\tparticle_type_index[id] = *count;\n\t*count = *count + 1;\n}\n\nstatic int QMB_CompareParticleType(const void* lhs_, const void* rhs_)\n{\n\tconst particle_type_t* lhs = (const particle_type_t*)lhs_;\n\tconst particle_type_t* rhs = (const particle_type_t*)rhs_;\n\tint comparison;\n\n\tcomparison = lhs->blendtype - rhs->blendtype;\n\tcomparison = comparison ? comparison : lhs->texture - rhs->texture;\n\n\treturn comparison;\n}\n\nstatic void QMB_SortParticleTypes(void)\n{\n\tint i;\n\n\t// To reduce state changes: Sort by blend mode, then texture\n\tqsort(particle_types, num_particletypes, sizeof(particle_types[0]), QMB_CompareParticleType);\n\n\t// Fix up references\n\tfor (i = 0; i < num_particletypes; ++i) {\n\t\tparticle_type_index[particle_types[i].id] = i;\n\t}\n}\n\nvoid QMB_AllocParticles(void)\n{\n\textern cvar_t r_particles_count;\n\n\tr_numparticles = bound(ABSOLUTE_MIN_PARTICLES, r_particles_count.integer, ABSOLUTE_MAX_PARTICLES);\n\n\tif (particles || r_numparticles < 1) {\n\t\t// seems QMB_AllocParticles() called from wrong place\n\t\tSys_Error(\"QMB_AllocParticles: internal error\");\n\t}\n\n\t// can't alloc on Hunk, using native memory\n\tparticles = (particle_t *)Q_malloc(r_numparticles * sizeof(particle_t));\n}\n\nstatic void QMB_PreMultiplyAlpha(byte* data, int width, int height)\n{\n\tint x, y;\n\n\t// Adjust particle font as we simplified the blending rules...\n\tfor (x = 0; x < width; ++x) {\n\t\tfor (y = 0; y < height; ++y) {\n\t\t\tbyte* base = data + (x + y * width) * 4;\n\n\t\t\t// Pre-multiply alpha\n\t\t\tbase[0] = (byte)(((int)base[0] * (int)base[3]) / 255.0);\n\t\t\tbase[1] = (byte)(((int)base[1] * (int)base[3]) / 255.0);\n\t\t\tbase[2] = (byte)(((int)base[2] * (int)base[3]) / 255.0);\n\t\t}\n\t}\n}\n\nstatic void QMB_LoadTextureSubImage(part_tex_t tex, const char* id, const byte* pixels, byte* temp_buffer, int full_width, int full_height, int texIndex, int components, int sub_x, int sub_y, int sub_x2, int sub_y2)\n{\n\tconst int mode = TEX_ALPHA | TEX_COMPLAIN | TEX_NOSCALE | TEX_MIPMAP;\n\ttexture_ref tex_ref;\n\tint y;\n\tint width = sub_x2 - sub_x;\n\tint height = sub_y2 - sub_y;\n\n\twidth = (width * full_width) / 256;\n\theight = (height * full_height) / 256;\n\tsub_x = (sub_x * full_width) / 256;\n\tsub_y = (sub_y * full_height) / 256;\n\n\tfor (y = 0; y < height; ++y) {\n\t\tmemcpy(temp_buffer + y * width * 4, pixels + ((sub_y + y) * full_width + sub_x) * 4, width * 4);\n\t}\n\n\tQMB_PreMultiplyAlpha(temp_buffer, width, height);\n\n\ttex_ref = R_LoadTexture(id, width, height, temp_buffer, mode, 4);\n\n\tADD_PARTICLE_TEXTURE(tex, tex_ref, texIndex, components, 0, 0, 256, 256);\n}\n\nstatic texture_ref QMB_LoadTextureImage(const char* path)\n{\n\tconst int mode = TEX_ALPHA | TEX_COMPLAIN | TEX_NOSCALE | TEX_MIPMAP | TEX_PREMUL_ALPHA;\n\n\treturn R_LoadTextureImage(path, NULL, 0, 0, mode);\n}\n\nvoid QMB_InitParticles(void)\n{\n\tint\ti, count = 0;\n\ttexture_ref shockwave_texture, lightning_texture, spark_texture;\n\n\tCvar_Register(&amf_part_fulldetail);\n\tCvar_Register(&gl_part_cache);\n\tif (!host_initialized && COM_CheckParm(cmdline_param_client_detailtrails)) {\n\t\tCvar_LatchedSetValue(&amf_part_fulldetail, 1);\n\t}\n\n\tif (!qmb_initialized) {\n\t\tif (!host_initialized) {\n\t\t\tCvar_SetCurrentGroup(CVAR_GROUP_PARTICLES);\n\t\t\tCvar_Register(&gl_clipparticles);\n\t\t\tCvar_Register(&gl_bounceparticles);\n\t\t\tCvar_ResetCurrentGroup();\n\t\t}\n\n\t\tQ_free(particles); // yeah, shit happens, work around\n\t\tQMB_AllocParticles();\n\t}\n\n\tQMB_ClearParticles(); // re-alloc particles, and create linked list of next free particle\n\tqmb_initialized = false; // so QMB particle system will be turned off if we fail to load some texture\n\n\tADD_PARTICLE_TEXTURE(ptex_none, null_texture_reference, 0, 1, 0, 0, 0, 0);\n\n\t// Move particle fonts off atlas and into their own textures...\n\t{\n\t\tint real_width, real_height;\n\t\tbyte* original;\n\t\tbyte* temp_buffer;\n\n\t\toriginal = R_LoadImagePixels(\"textures/particles/particlefont\", 0, 0, TEX_ALPHA | TEX_COMPLAIN | TEX_NOSCALE | TEX_MIPMAP, &real_width, &real_height);\n\t\tif (!original) {\n\t\t\treturn;\n\t\t}\n\n\t\ttemp_buffer = Q_malloc(real_width * real_height * 4);\n\t\tQMB_LoadTextureSubImage(ptex_blood1, \"qmb:blood1\", original, temp_buffer, real_width, real_height, 0, 1, 0, 0, 64, 64);\n\t\tQMB_LoadTextureSubImage(ptex_blood2, \"qmb:blood2\", original, temp_buffer, real_width, real_height, 0, 1, 64, 0, 128, 64);\n\t\tQMB_LoadTextureSubImage(ptex_lava, \"qmb:lava\", original, temp_buffer, real_width, real_height, 0, 1, 128, 0, 192, 64);\n\t\tQMB_LoadTextureSubImage(ptex_blueflare, \"qmb:blueflare\", original, temp_buffer, real_width, real_height, 0, 1, 192, 0, 256, 64);\n\t\tQMB_LoadTextureSubImage(ptex_generic, \"qmb:generic\", original, temp_buffer, real_width, real_height, 0, 1, 0, 96, 96, 192);\n\t\tQMB_LoadTextureSubImage(ptex_smoke, \"qmb:smoke\", original, temp_buffer, real_width, real_height, 0, 1, 96, 96, 192, 192);\n\t\tQMB_LoadTextureSubImage(ptex_blood3, \"qmb:blood3\", original, temp_buffer, real_width, real_height, 0, 1, 192, 96, 256, 160);\n\t\tQMB_LoadTextureSubImage(ptex_bubble, \"qmb:bubble\", original, temp_buffer, real_width, real_height, 0, 1, 192, 160, 224, 192);\n\t\tfor (i = 0; i < 8; i++) {\n\t\t\tQMB_LoadTextureSubImage(ptex_dpsmoke, va(\"qmb:smoke%d\", i), original, temp_buffer, real_width, real_height, i, 8, i * 32, 64, (i + 1) * 32, 96);\n\t\t}\n\t\tQ_free(temp_buffer);\n\t\tQ_free(original);\n\t}\n\n\t//VULT PARTICLES\n\tshockwave_texture = QMB_LoadTextureImage(\"textures/shockwavetex\");\n\tif (!R_TextureReferenceIsValid(shockwave_texture)) {\n\t\treturn;\n\t}\n\tlightning_texture = QMB_LoadTextureImage(\"textures/zing1\");\n\tif (!R_TextureReferenceIsValid(lightning_texture)) {\n\t\treturn;\n\t}\n\tspark_texture = QMB_LoadTextureImage(\"textures/sparktex\");\n\tif (!R_TextureReferenceIsValid(spark_texture)) {\n\t\treturn;\n\t}\n\tADD_PARTICLE_TEXTURE(ptex_shockwave, shockwave_texture, 0, 1, 0, 0, 256, 256);\n\tADD_PARTICLE_TEXTURE(ptex_lightning, lightning_texture, 0, 1, 0, 0, 256, 256);\n\tADD_PARTICLE_TEXTURE(ptex_spark, spark_texture, 0, 1, 0, 0, 32, 32);\n\n\tQMB_AddParticleType(p_spark, pd_spark, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_none, 255, -32, 0, pm_bounce, 1.3, 9, &count, \"part:spark\");\n\tQMB_AddParticleType(p_sparkray, pd_sparkray, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_none, 255, -0, 0, pm_nophysics, 0, 9, &count, \"part:sparkray\");\n\tQMB_AddParticleType(p_gunblast, pd_spark, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_none, 255, -16, 0, pm_bounce, 1.3, 9, &count, \"part:gunblast\");\n\n\tQMB_AddParticleType(p_fire, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 204, 0, -2.95, pm_die, 0, 4, &count, \"part:fire\");\n\tQMB_AddParticleType(p_chunk, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 255, -16, 0, pm_bounce, 1.475, 4, &count, \"part:chunk\");\n\tQMB_AddParticleType(p_shockwave, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 255, 0, -4.85, pm_nophysics, 0, 4, &count, \"part:shockwave\");\n\tQMB_AddParticleType(p_inferno_flame, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 153, 0, 0, pm_static, 0, 4, &count, \"part:inferno_flame\");\n\tQMB_AddParticleType(p_inferno_trail, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 204, 0, 0, pm_die, 0, 4, &count, \"part:inferno_trail\");\n\tQMB_AddParticleType(p_trailpart, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 230, 0, 0, pm_static, 0, 4, &count, \"part:trailpart\");\n\tQMB_AddParticleType(p_smoke, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_smoke, 140, 3, 0, pm_normal, 0, 4, &count, \"part:smoke\");\n\tQMB_AddParticleType(p_dpfire, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_dpsmoke, 144, 0, 0, pm_die, 0, 4, &count, \"part:dpfire\");\n\tQMB_AddParticleType(p_dpsmoke, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_dpsmoke, 85, 3, 0, pm_die, 0, 4, &count, \"part:dpsmoke\");\n\n\tQMB_AddParticleType(p_teleflare, pd_billboard, BLEND_GL_ONE_GL_ONE, ptex_blueflare, 255, 0, 0, pm_die, 0, 4, &count, \"part:teleflare\");\n\tQMB_AddParticleType(p_blood1, pd_billboard, BLEND_GL_ZERO_GL_ONE_MINUS_SRC_COLOR, ptex_blood1, 255, -20, 0, pm_die, 0, 4, &count, \"part:blood1\");\n\tQMB_AddParticleType(p_blood2, pd_billboard_vel, BLEND_GL_ZERO_GL_ONE_MINUS_SRC_COLOR, ptex_blood2, 255, -25, 0, pm_die, 0.018, 4, &count, \"part:blood2\");\n\n\tQMB_AddParticleType(p_lavasplash, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE_MINUS_SRC_ALPHA, ptex_lava, 170, 0, 0, pm_nophysics, 0, 4, &count, \"part:lavasplash\");\n\tQMB_AddParticleType(p_blood3, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE_MINUS_SRC_ALPHA, ptex_blood3, 255, -20, 0, pm_normal, 0, 4, &count, \"part:blood3\");\n\tQMB_AddParticleType(p_bubble, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE_MINUS_SRC_ALPHA, ptex_bubble, 204, 8, 0, pm_float, 0, 4, &count, \"part:bubble\");\n\tQMB_AddParticleType(p_staticbubble, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE_MINUS_SRC_ALPHA, ptex_bubble, 204, 0, 0, pm_static, 0, 4, &count, \"part:staticbubble\");\n\n\t//VULT PARTICLES\n\tQMB_AddParticleType(p_rain, pd_hide, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_none, 100, 0, 0, pm_rain, 0, 4, &count, \"part:rain\");\n\tQMB_AddParticleType(p_alphatrail, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 100, 0, 0, pm_static, 0, 4, &count, \"part:alphatrail\");\n\tQMB_AddParticleType(p_railtrail, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 255, 0, 0, pm_die, 0, 4, &count, \"part:railtrail\");\n\n\tQMB_AddParticleType(p_vxblood, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE_MINUS_SRC_ALPHA, ptex_blood3, 255, -38, 0, pm_normal, 0, 4, &count, \"part:vxblood\"); //HyperNewbie - Blood does NOT glow like fairy light\n\tQMB_AddParticleType(p_streak, pd_hide, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_none, 255, -64, 0, pm_streak, 1.5, 4, &count, \"part:streak\"); //grav was -64\n\tQMB_AddParticleType(p_streakwave, pd_hide, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_none, 255, 0, 0, pm_streakwave, 0, 4, &count, \"part:streakwave\");\n\tQMB_AddParticleType(p_lavatrail, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 255, 3, 0, pm_normal, 0, 4, &count, \"part:lavatrail\");\n\tQMB_AddParticleType(p_vxsmoke, pd_billboard, BLEND_GL_ZERO_GL_ONE_MINUS_SRC_COLOR_CONSTANT, ptex_smoke, 140, 3, 0, pm_normal, 0, 4, &count, \"part:vxsmoke\");\n\tQMB_AddParticleType(p_vxsmoke_red, pd_billboard, BLEND_GL_ZERO_GL_ONE_MINUS_SRC_COLOR, ptex_smoke, 140, 3, 0, pm_normal, 0, 4, &count, \"part:vxsmoke_red\");\n\tQMB_AddParticleType(p_muzzleflash, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 128, 0, 0, pm_die, 0, 4, &count, \"part:muzzleflash\");\n\tQMB_AddParticleType(p_2dshockwave, pd_normal, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_shockwave, 255, 0, 0, pm_static, 0, 4, &count, \"part:2dshockwave\");\n\tQMB_AddParticleType(p_vxrocketsmoke, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 128, 0, 0, pm_normal, 0, 4, &count, \"part:vxrocketsmoke\");\n\tQMB_AddParticleType(p_flame, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 200, 10, 0, pm_die, 0, 4, &count, \"part:flame\");\n\tQMB_AddParticleType(p_trailbleed, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 200, 0, 0, pm_static, 0, 4, &count, \"part:flamebleed\");\n\tQMB_AddParticleType(p_bleedspike, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 200, 0, 0, pm_static, 0, 4, &count, \"part:bleedspike\");\n\tQMB_AddParticleType(p_lightningbeam, pd_beam, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_lightning, 128, 0, 0, pm_static, 0, 4, &count, \"part:lgbeam\");\n\tQMB_AddParticleType(p_bubble2, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE_MINUS_SRC_ALPHA, ptex_bubble, 204, 1, 0, pm_float, 0, 4, &count, \"part:bubble2\");\n\tQMB_AddParticleType(p_bloodcloud, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE_MINUS_SRC_ALPHA, ptex_generic, 255, -3, 0, pm_normal, 0, 4, &count, \"part:bloodcloud\");\n\tQMB_AddParticleType(p_chunkdir, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 255, -32, 0, pm_bounce, 1.475, 4, &count, \"part:chunkdir\");\n\tQMB_AddParticleType(p_smallspark, pd_beam, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_spark, 255, -64, 0, pm_bounce, 1.5, 4, &count, \"part:smallspark\"); //grav was -64\n\n\t//HyperNewbie particles\n\tQMB_AddParticleType(p_slimeglow, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_lava, 72, 0, 0, pm_nophysics, 0, 4, &count, \"part:slimeglow\"); //Glow\n\tQMB_AddParticleType(p_slimebubble, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE_MINUS_SRC_ALPHA, ptex_bubble, 204, 0, 0, pm_static, 0, 4, &count, \"part:slimebubble\");\n\tQMB_AddParticleType(p_blacklavasmoke, pd_billboard, BLEND_GL_ZERO_GL_ONE_MINUS_SRC_COLOR_CONSTANT, ptex_smoke, 140, 3, 0, pm_normal, 0, 4, &count, \"part:blacklavasmoke\");\n\n\t//Allow overkill trails\n\tif (amf_part_fulldetail.integer) {\n\t\tQMB_AddParticleType(p_streaktrail, pd_billboard, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 128, 0, 0, pm_static, 0, 4, &count, \"part:streaktrail\");\n\t}\n\telse {\n\t\tQMB_AddParticleType(p_streaktrail, pd_beam, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_none, 128, 0, 0, pm_die, 0, 4, &count, \"part:streaktrail\");\n\t}\n\n\t// meag: new, 'simple' trails following entities, updated as entity moves\n\tQMB_AddParticleType(p_nailtrail, pd_dynamictrail, BLEND_GL_SRC_ALPHA_GL_ONE_MINUS_SRC_ALPHA, ptex_none, R_SIMPLETRAIL_NEAR_ALPHA, 0, 0, pm_trail, 0, 8, &count, \"part:entitytrail\");\n\tQMB_AddParticleType(p_flametorch, pd_torch, BLEND_GL_SRC_ALPHA_GL_ONE, ptex_generic, 200, 10, 0, pm_static, 0, 4, &count, \"part:flame\");\n\n\tQMB_SortParticleTypes();\n\tR_ParticleFlamePrecalculate();\n\n\tqmb_initialized = true;\n}\n\nvoid QMB_ShutdownParticles(void)\n{\n\tQ_free(particles);\n}\n\nvoid QMB_ClearParticles (void)\n{\n\tint\ti;\n\n\t// FIXME: if r_numparticles hasn't changed, then no-need to reallocate again.\n\tQ_free(particles);\t\t// free\n\tQMB_AllocParticles();\t// and alloc again\n\n\tmemset(particles, 0, r_numparticles * sizeof(particle_t));\n\tfree_particles = &particles[0];\n\n\tfor (i = 0; i + 1 < r_numparticles; i++) {\n\t\tparticles[i].next = &particles[i + 1];\n\t}\n\tparticles[r_numparticles - 1].next = NULL;\n\n\tfor (i = 0; i < num_particletypes; i++) {\n\t\tparticle_types[i].start = NULL;\n\t}\n\n\t//VULT STATS\n\tParticleCount = 0;\n\tParticleCountHigh = 0;\n}\n\nstatic void QMB_BillboardAddVert(r_sprite3d_vert_t* vert, particle_type_t* type, float x, float y, float z, float s, float t, col_t color, int texture_index)\n{\n\tpart_blend_info_t* blend = &blend_options[type->blendtype];\n\tcol_t new_color;\n\n\tblend->color_transform(color, new_color);\n\n\tR_Sprite3DSetVert(vert, x, y, z, s, t, new_color, texture_index);\n}\n\n__inline static qbool CALCULATE_PARTICLE_BILLBOARD(particle_texture_t* ptex, particle_type_t* type, particle_t * p, vec3_t coord[4])\n{\n\tpart_blend_info_t* blend = &blend_options[type->blendtype];\n\tvec3_t verts[4];\n\tfloat scale = p->size;\n\tr_sprite3d_vert_t* vert;\n\tcol_t new_color;\n\n\tvert = R_Sprite3DAddEntry(type->billboard_type, 4);\n\tif (!vert) {\n\t\treturn false;\n\t}\n\n\tif (p->rotspeed) {\n\t\tmatrix3x3_t rotate_matrix;\n\t\tMatrix3x3_CreateRotate(rotate_matrix, DEG2RAD(p->rotangle), vpn);\n\n\t\tMatrix3x3_MultiplyByVector(verts[0], (const vec_t (*)[3]) rotate_matrix, coord[0]);\n\t\tMatrix3x3_MultiplyByVector(verts[1], (const vec_t (*)[3]) rotate_matrix, coord[1]);\n\t\t// do some fast math for verts[2] and verts[3].\n\t\tVectorNegate(verts[0], verts[2]);\n\t\tVectorNegate(verts[1], verts[3]);\n\n\t\tVectorMA(p->org, scale, verts[0], verts[0]);\n\t\tVectorMA(p->org, scale, verts[1], verts[1]);\n\t\tVectorMA(p->org, scale, verts[2], verts[2]);\n\t\tVectorMA(p->org, scale, verts[3], verts[3]);\n\t}\n\telse {\n\t\tVectorMA(p->org, scale, coord[0], verts[0]);\n\t\tVectorMA(p->org, scale, coord[1], verts[1]);\n\t\tVectorMA(p->org, scale, coord[2], verts[2]);\n\t\tVectorMA(p->org, scale, coord[3], verts[3]);\n\t}\n\n\t// Set color\n\tblend->color_transform(p->color, new_color);\n\n\tR_Sprite3DSetVert(vert++, verts[0][0], verts[0][1], verts[0][2], ptex->coords[p->texindex][0], ptex->coords[p->texindex][3], new_color, ptex->tex_index);\n\tR_Sprite3DSetVert(vert++, verts[3][0], verts[3][1], verts[3][2], ptex->coords[p->texindex][2], ptex->coords[p->texindex][3], new_color, ptex->tex_index);\n\tR_Sprite3DSetVert(vert++, verts[1][0], verts[1][1], verts[1][2], ptex->coords[p->texindex][0], ptex->coords[p->texindex][1], new_color, ptex->tex_index);\n\tR_Sprite3DSetVert(vert++, verts[2][0], verts[2][1], verts[2][2], ptex->coords[p->texindex][2], ptex->coords[p->texindex][1], new_color, ptex->tex_index);\n\n\treturn true;\n}\n\nstatic void QMB_FillParticleVertexBuffer(void)\n{\n\tvec3_t billboard[4], velcoord[4];\n\tparticle_type_t* pt;\n\tparticle_t* p;\n\tint i, j, k;\n\tint l;\n\n\tVectorAdd(vup, vright, billboard[2]);\n\tVectorSubtract(vright, vup, billboard[3]);\n\tVectorNegate(billboard[2], billboard[0]);\n\tVectorNegate(billboard[3], billboard[1]);\n\n\tfor (i = 0; i < num_particletypes; i++) {\n\t\tqbool first = true;\n\n\t\tpt = &particle_types[i];\n\n\t\tif (!pt->start) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (pt->drawtype == pd_hide) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t//VULT PARTICLES\n\t\tswitch (pt->drawtype) {\n\t\tcase pd_beam:\n\t\t\tfor (p = pt->start; p; p = p->next) {\n\t\t\t\tparticle_texture_t* ptex = &particle_textures[ptex_lightning];\n\t\t\t\tvec3_t right1, right2;\n\t\t\t\tfloat half_t = (ptex->coords[0][1] + ptex->coords[0][3]) * 0.5f;\n\t\t\t\tint trail_parts = min(amf_part_traildetail.integer, MAX_BEAM_TRAIL);\n\n\t\t\t\tif (i != particle_type_index[p_lightningbeam]) {\n\t\t\t\t\ttrail_parts = 1;\n\t\t\t\t}\n\n\t\t\t\tif (particle_time < p->start || particle_time >= p->die) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (first) {\n\t\t\t\t\tR_Sprite3DInitialiseBatch(pt->billboard_type, pt->state, TEXTURE_DETAILS(ptex), r_primitive_triangle_strip);\n\t\t\t\t\tfirst = false;\n\t\t\t\t}\n\n\t\t\t\tR_PreCalcBeamVerts(p->org, p->endorg, right1, right2);\n\t\t\t\tfor (l = trail_parts; l > 0; l--) {\n\t\t\t\t\tr_sprite3d_vert_t* vert = R_Sprite3DAddEntry(pt->billboard_type, 6);\n\t\t\t\t\tif (!vert) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tR_CalcBeamVerts(varray_vertex, p->org, p->endorg, right1, right2, p->size / (l * amf_part_trailwidth.value));\n\n\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, varray_vertex[4], varray_vertex[5], varray_vertex[6], ptex->coords[0][2], ptex->coords[0][3], p->color, ptex->tex_index);\n\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, varray_vertex[8], varray_vertex[9], varray_vertex[10], ptex->coords[0][0], ptex->coords[0][3], p->color, ptex->tex_index);\n\n\t\t\t\t\t// near center\n\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, (varray_vertex[0] + varray_vertex[4]) / 2, (varray_vertex[1] + varray_vertex[5]) / 2, (varray_vertex[2] + varray_vertex[6]) / 2, ptex->coords[0][2], half_t, p->color, ptex->tex_index);\n\t\t\t\t\t// far center\n\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, (varray_vertex[8] + varray_vertex[12]) / 2, (varray_vertex[9] + varray_vertex[13]) / 2, (varray_vertex[10] + varray_vertex[14]) / 2, ptex->coords[0][0], half_t, p->color, ptex->tex_index);\n\n\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, varray_vertex[0], varray_vertex[1], varray_vertex[2], ptex->coords[0][2], ptex->coords[0][1], p->color, ptex->tex_index);\n\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, varray_vertex[12], varray_vertex[13], varray_vertex[14], ptex->coords[0][0], ptex->coords[0][1], p->color, ptex->tex_index);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase pd_spark:\n\t\tcase pd_sparkray:\n\t\t\tfor (p = pt->start; p; p = p->next) {\n\t\t\t\tvec3_t neworg;\n\t\t\t\tfloat* point;\n\t\t\t\tbyte farColor[4];\n\t\t\t\tparticle_texture_t* ptex = &particle_textures[ptex_none];\n\t\t\t\tr_sprite3d_vert_t* vert;\n\n\t\t\t\tif (particle_time < p->start || particle_time >= p->die) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (first) {\n\t\t\t\t\tR_Sprite3DInitialiseBatch(pt->billboard_type, pt->state, TEXTURE_DETAILS(ptex), r_primitive_triangle_fan);\n\t\t\t\t\tfirst = false;\n\t\t\t\t}\n\n\t\t\t\tvert = R_Sprite3DAddEntry(pt->billboard_type, pt->verts_per_primitive);\n\t\t\t\tif (vert) {\n\t\t\t\t\tvec3_t v;\n\n\t\t\t\t\tif (!TraceLineN(p->endorg, p->org, neworg, NULL)) {\n\t\t\t\t\t\tVectorCopy(p->org, neworg);\n\t\t\t\t\t}\n\n\t\t\t\t\tpoint = (pt->drawtype == pd_spark ? p->org : p->endorg);\n\t\t\t\t\tfarColor[0] = p->color[0] >> 1;\n\t\t\t\t\tfarColor[1] = p->color[1] >> 1;\n\t\t\t\t\tfarColor[2] = p->color[2] >> 1;\n\t\t\t\t\tfarColor[3] = 0;\n\n\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, point[0], point[1], point[2], ptex->coords[0][0], ptex->coords[0][1], p->color, -1);\n\t\t\t\t\tif (pt->drawtype == pd_spark) {\n\t\t\t\t\t\tfor (j = 7; j >= 0; j--) {\n\t\t\t\t\t\t\tfor (k = 0; k < 3; k++) {\n\t\t\t\t\t\t\t\tv[k] = p->org[k] - p->vel[k] / 8 + vright[k] * cost[j % 7] * p->size + vup[k] * sint[j % 7] * p->size;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, v[0], v[1], v[2], ptex->coords[0][0], ptex->coords[0][1], farColor, -1);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tfor (j = 7; j >= 0; j--) {\n\t\t\t\t\t\t\tfor (k = 0; k < 3; k++) {\n\t\t\t\t\t\t\t\tv[k] = neworg[k] + vright[k] * cost[j % 7] * p->size + vup[k] * sint[j % 7] * p->size;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, v[0], v[1], v[2], ptex->coords[0][0], ptex->coords[0][1], farColor, -1);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase pd_billboard:\n\t\tcase pd_billboard_vel:\n\t\t\t{\n\t\t\t\tint drawncount = 0;\n\t\t\t\tparticle_texture_t* ptex = &particle_textures[pt->texture];\n\n\t\t\t\tfor (p = pt->start; p; p = p->next) {\n\t\t\t\t\tif (particle_time < p->start || particle_time >= p->die) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (first) {\n\t\t\t\t\t\tR_Sprite3DInitialiseBatch(pt->billboard_type, pt->state, TEXTURE_DETAILS(ptex), r_primitive_triangle_strip);\n\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (pt->drawtype == pd_billboard) {\n\t\t\t\t\t\tif (gl_clipparticles.integer) {\n\t\t\t\t\t\t\tif (drawncount >= 3 && VectorSupCompare(p->org, r_origin, 30)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdrawncount++;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tCALCULATE_PARTICLE_BILLBOARD(ptex, pt, p, billboard);\n\t\t\t\t\t}\n\t\t\t\t\telse if (pt->drawtype == pd_billboard_vel) {\n\t\t\t\t\t\tvec3_t up, right;\n\n\t\t\t\t\t\tVectorCopy(p->vel, up);\n\t\t\t\t\t\tCrossProduct(vpn, up, right);\n\t\t\t\t\t\tVectorNormalizeFast(right);\n\t\t\t\t\t\tVectorScale(up, pt->custom, up);\n\n\t\t\t\t\t\tVectorAdd(up, right, velcoord[2]);\n\t\t\t\t\t\tVectorSubtract(right, up, velcoord[3]);\n\t\t\t\t\t\tVectorNegate(velcoord[2], velcoord[0]);\n\t\t\t\t\t\tVectorNegate(velcoord[3], velcoord[1]);\n\n\t\t\t\t\t\tCALCULATE_PARTICLE_BILLBOARD(ptex, pt, p, velcoord);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\t\t//VULT PARTICLES - This produces the shockwave effect\n\t\t\t//Mind you it can be used for more than that... Decals come to mind first\n\t\tcase pd_normal:\n\t\t\t{\n\t\t\t\tfloat matrix[16];\n\t\t\t\tparticle_texture_t* ptex = &particle_textures[pt->texture];\n\n\t\t\t\tfor (p = pt->start; p; p = p->next) {\n\t\t\t\t\tfloat vector[4][4];\n\t\t\t\t\tr_sprite3d_vert_t* vert;\n\n\t\t\t\t\tif (particle_time < p->start || particle_time >= p->die) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (first) {\n\t\t\t\t\t\tR_Sprite3DInitialiseBatch(pt->billboard_type, pt->state, TEXTURE_DETAILS(ptex), r_primitive_triangle_strip);\n\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t}\n\n\t\t\t\t\tvert = R_Sprite3DAddEntry(pt->billboard_type, 4);\n\t\t\t\t\tif (vert) {\n\t\t\t\t\t\tR_SetIdentityMatrix(matrix);\n\t\t\t\t\t\tR_TransformMatrix(matrix, p->org[0], p->org[1], p->org[2]);\n\t\t\t\t\t\tR_ScaleMatrix(matrix, p->size, p->size, p->size);\n\t\t\t\t\t\tR_RotateMatrix(matrix, p->endorg[0], 0, 1, 0);\n\t\t\t\t\t\tR_RotateMatrix(matrix, p->endorg[1], 0, 0, 1);\n\t\t\t\t\t\tR_RotateMatrix(matrix, p->endorg[2], 1, 0, 0);\n\n\t\t\t\t\t\tR_MultiplyVector3f(matrix, -p->size, -p->size, 0, vector[0]);\n\t\t\t\t\t\tR_MultiplyVector3f(matrix, p->size, -p->size, 0, vector[1]);\n\t\t\t\t\t\tR_MultiplyVector3f(matrix, p->size, p->size, 0, vector[2]);\n\t\t\t\t\t\tR_MultiplyVector3f(matrix, -p->size, p->size, 0, vector[3]);\n\n\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, vector[0][0], vector[0][1], vector[0][2], ptex->coords[0][0], ptex->coords[0][1], p->color, ptex->tex_index);\n\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, vector[3][0], vector[3][1], vector[3][2], ptex->coords[0][0], ptex->coords[0][3], p->color, ptex->tex_index);\n\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, vector[1][0], vector[1][1], vector[1][2], ptex->coords[0][2], ptex->coords[0][1], p->color, ptex->tex_index);\n\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, vector[2][0], vector[2][1], vector[2][2], ptex->coords[0][2], ptex->coords[0][3], p->color, ptex->tex_index);\n\n\t\t\t\t\t\tif ((vert = R_Sprite3DAddEntry(pt->billboard_type, 4))) {\n\t\t\t\t\t\t\tR_RotateMatrix(matrix, 180, 1, 0, 0);\n\n\t\t\t\t\t\t\tR_MultiplyVector3f(matrix, -p->size, -p->size, 0, vector[0]);\n\t\t\t\t\t\t\tR_MultiplyVector3f(matrix, p->size, -p->size, 0, vector[1]);\n\t\t\t\t\t\t\tR_MultiplyVector3f(matrix, p->size, p->size, 0, vector[2]);\n\t\t\t\t\t\t\tR_MultiplyVector3f(matrix, -p->size, p->size, 0, vector[3]);\n\n\t\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, vector[0][0], vector[0][1], vector[0][2], ptex->coords[0][0], ptex->coords[0][1], p->color, ptex->tex_index);\n\t\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, vector[3][0], vector[3][1], vector[3][2], ptex->coords[0][0], ptex->coords[0][3], p->color, ptex->tex_index);\n\t\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, vector[1][0], vector[1][1], vector[1][2], ptex->coords[0][2], ptex->coords[0][1], p->color, ptex->tex_index);\n\t\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, vector[2][0], vector[2][1], vector[2][2], ptex->coords[0][2], ptex->coords[0][3], p->color, ptex->tex_index);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase pd_dynamictrail:\n\t\t\t{\n\t\t\t\tparticle_texture_t* ptex = &particle_textures[pt->texture];\n\n\t\t\t\tfor (p = pt->start; p; p = p->next) {\n\t\t\t\t\tr_sprite3d_vert_t* vert;\n\n\t\t\t\t\tif (particle_time < p->start || particle_time >= p->die) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (first) {\n\t\t\t\t\t\tR_Sprite3DInitialiseBatch(pt->billboard_type, pt->state, TEXTURE_DETAILS(ptex), r_primitive_triangle_strip);\n\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t}\n\n\t\t\t\t\tvert = R_Sprite3DAddEntry(pt->billboard_type, pt->verts_per_primitive);\n\t\t\t\t\tif (vert) {\n\t\t\t\t\t\tvec3_t points[6];\n\t\t\t\t\t\tcol_t near_color;\n\t\t\t\t\t\t\n\t\t\t\t\t\tVectorCopy(p->color, near_color);\n\t\t\t\t\t\tnear_color[3] = R_SIMPLETRAIL_NEAR_ALPHA;\n\n\t\t\t\t\t\tVectorMA(p->endorg, -1, vup, points[0]);\n\t\t\t\t\t\tVectorMA(points[0], -2, vright, points[2]);\n\t\t\t\t\t\tVectorMA(points[0], 2, vright, points[0]);\n\t\t\t\t\t\tVectorMA(p->endorg, 2, vup, points[1]);\n\n\t\t\t\t\t\tVectorMA(p->org, -0.5, vup, points[3]);\n\t\t\t\t\t\tVectorMA(points[0], -1, vright, points[5]);\n\t\t\t\t\t\tVectorMA(points[3], -1, vright, points[3]);\n\t\t\t\t\t\tVectorMA(p->org, 1, vup, points[4]);\n\n\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, points[0][0], points[0][1], points[0][2], 0, 0, near_color, -1);\n\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, points[3][0], points[3][1], points[3][2], 0, 0, p->color, -1);\n\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, points[1][0], points[1][1], points[1][2], 0, 0, near_color, -1);\n\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, points[4][0], points[4][1], points[4][2], 0, 0, p->color, -1);\n\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, points[2][0], points[2][1], points[2][2], 0, 0, near_color, -1);\n\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, points[5][0], points[5][1], points[5][2], 0, 0, p->color, -1);\n\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, points[0][0], points[0][1], points[0][2], 0, 0, near_color, -1);\n\t\t\t\t\t\tQMB_BillboardAddVert(vert++, pt, points[3][0], points[3][1], points[3][2], 0, 0, p->color, -1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase pd_torch:\n\t\t\t{\n\t\t\t\tparticle_texture_t* ptex = &particle_textures[pt->texture];\n\n\t\t\t\tfor (p = pt->start; p; p = p->next) {\n\t\t\t\t\tint frame = (int)(particle_time * 100) % FLAME_FRAME_TOTAL;\n\t\t\t\t\tint i;\n\n\t\t\t\t\tif (particle_time < p->start || particle_time >= p->die) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (first) {\n\t\t\t\t\t\tR_Sprite3DInitialiseBatch(pt->billboard_type, pt->state, TEXTURE_DETAILS(ptex), r_primitive_triangle_strip);\n\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t}\n\n\t\t\t\t\t// render multiple billboards\n\t\t\t\t\tfor (i = 0; i < FLAME_FRAME_TOTAL; ++i) {\n\t\t\t\t\t\tparticle_t part = *p;\n\t\t\t\t\t\tflame_t* flame = &flame_frames[frame][i];\n\n\t\t\t\t\t\tVectorAdd(part.org, flame->pos, part.org);\n\t\t\t\t\t\tif (amf_part_firecolor.string[0]) {\n\t\t\t\t\t\t\tVectorCopy(amf_part_firecolor.color, part.color);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpart.color[3] = flame->alpha;\n\t\t\t\t\t\tpart.size = flame->size;\n\n\t\t\t\t\t\tif (!CALCULATE_PARTICLE_BILLBOARD(ptex, pt, &part, billboard)) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tassert(!\"QMB_DrawParticles: unexpected drawtype\");\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nvoid QMB_ProcessParticle(particle_type_t* pt, particle_t* p)\n{\n\tfloat grav = movevars.gravity / 800.0;\n\tvec3_t oldorg, stop, normal, movement;\n\tint contents;\n\tfloat bounce;\n\tfloat lifetime;\n\n\tp->size += p->growth * cls.frametime;\n\n\tif (p->size <= 0) {\n\t\tp->die = 0;\n\t\treturn;\n\t}\n\n\t//VULT PARTICLE\n\tlifetime = ((p->die - particle_time) / (p->die - p->start));\n\tp->color[3] = p->initial_alpha * lifetime;\n\n\tif (p->color[3] <= 0) {\n\t\tp->die = 0;\n\t\treturn;\n\t}\n\n\tif (pt->move == pm_trail || pt->drawtype == pd_torch) {\n\t\textern cvar_t r_drawflame;\n\t\tqbool remove = false;\n\n\t\tremove |= (pt->drawtype == pd_torch && (!amf_part_fire.integer || !r_drawflame.integer));\n\t\tremove |= (p->entity_ref && cls.state != ca_active);\n\n\t\tif (remove) {\n\t\t\tp->entity_ref = 0;\n\t\t\tp->start = p->die = 0;\n\t\t\treturn;\n\t\t}\n\n\t\t// velocity isn't used, accel etc is irrelevant...\n\t\tif (p->entity_ref > 0) {\n\t\t\tcentity_t* cent = &cl_entities[p->entity_ref - 1];\n\n\t\t\tif (cent->trail_number == p->entity_trailnumber && cent->sequence == cl.validsequence) {\n\t\t\t\t// update based on entity\n\t\t\t\tfloat length;\n\t\t\t\tvec3_t diff;\n\n\t\t\t\tVectorCopy(cent->lerp_origin, p->endorg);\n\t\t\t\tVectorSubtract(p->org, p->endorg, diff);\n\t\t\t\tlength = VectorLength(diff);\n\t\t\t\tif (length > R_SIMPLETRAIL_MAXLENGTH) {\n\t\t\t\t\tVectorMA(p->endorg, R_SIMPLETRAIL_MAXLENGTH / length, diff, p->org);\n\t\t\t\t}\n\t\t\t\tp->die = particle_time + 0.2f;\n\t\t\t\tcent->trails[p->entity_trailindex].lasttime = particle_time;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// disconnect, let it die out\n\t\t\t\tp->entity_ref = p->entity_trailnumber = 0;\n\n\t\t\t\t// length should be reducing as it dies\n\t\t\t\t//p->size = p->;\n\t\t\t\t//VectorMA(p->endorg, p->size, diff, p->org);\n\t\t\t}\n\t\t}\n\t\telse if (p->entity_ref < 0) {\n\t\t\tentity_t* sent = &cl_static_entities[-p->entity_ref - 1];\n\n\t\t\tif (sent->visframe >= r_framecount - 1) {\n\t\t\t\tsent->particle_time = particle_time;\n\t\t\t\tp->start = particle_time;\n\t\t\t\tp->die = particle_time + 0.8f;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// kill it immediately\n\t\t\t\tp->start = p->die = 0;\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\tp->rotangle += p->rotspeed * cls.frametime;\n\tif (p->hit) {\n\t\treturn;\n\t}\n\n\t//VULT - switched these around so velocity is scaled before gravity is applied\n\tVectorScale(p->vel, 1 + pt->accel * cls.frametime, p->vel);\n\tp->vel[2] += pt->grav * grav * cls.frametime;\n\n\tswitch (pt->move) {\n\t\tcase pm_static:\n\t\t\tbreak;\n\t\tcase pm_normal:\n\t\t\tVectorCopy(p->org, oldorg);\n\t\t\tVectorScale(p->vel, cls.frametime, movement);\n\t\t\tVectorAdd(p->org, movement, p->org);\n\t\t\tif (ParticleContents(p, movement) == CONTENTS_SOLID) {\n\t\t\t\tp->hit = 1;\n\t\t\t\tVectorCopy(oldorg, p->org);\n\t\t\t\tVectorClear(p->vel);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase pm_float:\n\t\t\tVectorScale(p->vel, cls.frametime, movement);\n\t\t\tmovement[2] += p->size + 1;\n\t\t\tVectorAdd(p->org, movement, p->org);\n\t\t\tcontents = ParticleContents(p, movement);\n\t\t\tif (!ISUNDERWATER(contents)) {\n\t\t\t\tp->die = 0;\n\t\t\t}\n\t\t\tp->org[2] -= p->size + 1;\n\t\t\tbreak;\n\t\tcase pm_nophysics:\n\t\t\tVectorMA(p->org, cls.frametime, p->vel, p->org);\n\t\t\tbreak;\n\t\tcase pm_die:\n\t\t\tVectorScale(p->vel, cls.frametime, movement);\n\t\t\tVectorAdd(p->org, movement, p->org);\n\t\t\tif (CONTENTS_SOLID == ParticleContents(p, movement)) {\n\t\t\t\tp->die = 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase pm_bounce:\n\t\t\tif (!gl_bounceparticles.value || p->bounces) {\n\t\t\t\tif (pt->id == p_smallspark) {\n\t\t\t\t\tVectorCopy(p->org, p->endorg);\n\t\t\t\t}\n\n\t\t\t\tVectorScale(p->vel, cls.frametime, movement);\n\t\t\t\tVectorAdd(p->org, movement, p->org);\n\t\t\t\tif (CONTENTS_SOLID == ParticleContents(p, movement)) {\n\t\t\t\t\tp->die = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tVectorCopy(p->org, oldorg);\n\t\t\t\tif (pt->id == p_smallspark) {\n\t\t\t\t\tVectorCopy(oldorg, p->endorg);\n\t\t\t\t}\n\t\t\t\tVectorScale(p->vel, cls.frametime, movement);\n\t\t\t\tVectorAdd(p->org, movement, p->org);\n\t\t\t\tif (CONTENTS_SOLID == ParticleContents(p, movement)) {\n\t\t\t\t\tif (TraceLineN(oldorg, p->org, stop, normal)) {\n\t\t\t\t\t\tVectorCopy(stop, p->org);\n\t\t\t\t\t\tbounce = -pt->custom * DotProduct(p->vel, normal);\n\t\t\t\t\t\tVectorMA(p->vel, bounce, normal, p->vel);\n\t\t\t\t\t\tp->bounces++;\n\t\t\t\t\t\tif (pt->id == p_smallspark) {\n\t\t\t\t\t\t\tVectorCopy(stop, p->endorg);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t\t//VULT PARTICLES\n\t\tcase pm_rain:\n\t\t\tVectorCopy(p->org, oldorg);\n\t\t\tVectorScale(p->vel, cls.frametime, movement);\n\t\t\tVectorAdd(p->org, movement, p->org);\n\t\t\tcontents = ParticleContents(p, movement);\n\t\t\tif (ISUNDERWATER(contents) || contents == CONTENTS_SOLID) {\n\t\t\t\tif (!amf_weather_rain_fast.value || amf_weather_rain_fast.value == 2) {\n\t\t\t\t\tvec3_t rorg;\n\t\t\t\t\tVectorCopy(oldorg, rorg);\n\t\t\t\t\t//Find out where the rain should actually hit\n\t\t\t\t\t//This is a slow way of doing it, I'll fix it later maybe...\n\t\t\t\t\twhile (1) {\n\t\t\t\t\t\trorg[2] = rorg[2] - 0.5f;\n\t\t\t\t\t\tcontents = TruePointContents(rorg);\n\t\t\t\t\t\tif (contents == CONTENTS_WATER) {\n\t\t\t\t\t\t\tif (amf_weather_rain_fast.value == 2) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tRainSplash(rorg);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if (contents == CONTENTS_SOLID) {\n\t\t\t\t\t\t\tbyte col[3] = { 128,128,128 };\n\t\t\t\t\t\t\tSparkGen(rorg, col, 3, 50, 0.15);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tVectorCopy(rorg, p->org);\n\t\t\t\t\tVX_ParticleTrail(oldorg, p->org, p->size, 0.2, p->color);\n\t\t\t\t}\n\t\t\t\tp->die = 0;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tVX_ParticleTrail(oldorg, p->org, p->size, 0.2, p->color);\n\t\t\t}\n\t\t\tbreak;\n\t\t\t//VULT PARTICLES\n\t\tcase pm_streak:\n\t\t\tVectorCopy(p->org, oldorg);\n\t\t\tVectorScale(p->vel, cls.frametime, movement);\n\t\t\tVectorAdd(p->org, movement, p->org);\n\t\t\tif (CONTENTS_SOLID == ParticleContents(p, movement)) {\n\t\t\t\tif (TraceLineN(oldorg, p->org, stop, normal)) {\n\t\t\t\t\tVectorCopy(stop, p->org);\n\t\t\t\t\tbounce = -pt->custom * DotProduct(p->vel, normal);\n\t\t\t\t\tVectorMA(p->vel, bounce, normal, p->vel);\n\t\t\t\t}\n\t\t\t}\n\t\t\tVX_ParticleTrail(oldorg, p->org, p->size, 0.2, p->color);\n\t\t\tif (VectorLength(p->vel) == 0) {\n\t\t\t\tp->die = 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase pm_streakwave:\n\t\t\tVectorCopy(p->org, oldorg);\n\t\t\tVectorMA(p->org, cls.frametime, p->vel, p->org);\n\t\t\tVX_ParticleTrail(oldorg, p->org, p->size, 0.5, p->color);\n\t\t\tp->vel[0] = 19 * p->vel[0] / 20;\n\t\t\tp->vel[1] = 19 * p->vel[1] / 20;\n\t\t\tp->vel[2] = 19 * p->vel[2] / 20;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tassert(!\"QMB_UpdateParticles: unexpected pt->move\");\n\t\t\tbreak;\n\t}\n}\n\n// TODO: Split up\nstatic void QMB_UpdateParticles(void)\n{\n\tint i;\n\tparticle_type_t *pt;\n\tparticle_t *p;\n\tparticle_t **prev;\n\n\tif (!qmb_initialized) {\n\t\treturn;\n\t}\n\n\t//VULT PARTICLES\n\tWeatherEffect();\n\n\tfor (i = 0; i < num_particletypes; i++) {\n\t\tpt = &particle_types[i];\n\n#ifdef _WIN32\n\t\tif (pt && ((uintptr_t)pt->start == 1)) {\n\t\t\t/* hack! fixme!\n\t\t\t * for some reason in some occasions - MS VS 2005 compiler\n\t\t\t * this address doesn't point to 0 as other unitialized do, but to 0x00000001 */\n\t\t\tpt->start = NULL;\n\t\t\tCom_DPrintf(\"ERROR: particle_type[%i].start == 1\\n\", i);\n\t\t}\n#endif // _WIN32\n\n\t\tprev = &pt->start;\n\t\tfor (p = pt->start; p; ) {\n\t\t\tif (particle_time >= p->die) {\n\t\t\t\t*prev = p->next;\n\t\t\t\tp->next = free_particles;\n\t\t\t\tfree_particles = p;\n\t\t\t\tParticleStats(-1);\n\t\t\t\tp = *prev;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (particle_time >= p->start) {\n\t\t\t\t\tQMB_ProcessParticle(pt, p);\n\t\t\t\t}\n\n\t\t\t\tprev = &p->next;\n\t\t\t\tp = p->next;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid QMB_CalculateParticles(void)\n{\n\tif (!qmb_initialized) {\n\t\treturn;\n\t}\n\n\tparticle_time = r_refdef2.time;\n\n\tif (!ISPAUSED) {\n\t\tQMB_UpdateParticles();\n\t}\n}\nvoid QMB_DrawParticles(void)\n{\n\tif (!qmb_initialized) {\n\t\treturn;\n\t}\n\n\tQMB_FillParticleVertexBuffer();\n}\n\n//VULT STATS\nvoid ParticleStats (int change)\n{\n\tif (ParticleCount > ParticleCountHigh)\n\t\tParticleCountHigh = ParticleCount;\n\tParticleCount+=change;\n}\n\n//from darkplaces engine - finds which corner of a particle goes where, so I don't have to :D\nstatic void R_PreCalcBeamVerts(vec3_t org1, vec3_t org2, vec3_t right1, vec3_t right2)\n{\n\tvec3_t diff, normal;\n\n\tVectorSubtract(org2, org1, normal);\n\tVectorNormalize(normal);\n\n\t// calculate 'right' vector for start\n\tVectorSubtract(r_origin, org1, diff);\n\tVectorMA(diff, 32, vup, diff);\n\tVectorNormalize(diff);\n\tCrossProduct(normal, diff, right1);\n\n\t// calculate 'right' vector for end\n\tVectorSubtract(r_origin, org2, diff);\n\tVectorMA(diff, 32, vup, diff);\n\tVectorNormalize(diff);\n\tCrossProduct(normal, diff, right2);\n}\n\nstatic void R_CalcBeamVerts(float *vert, const vec3_t org1, const vec3_t org2, const vec3_t right1, const vec3_t right2, float width)\n{\n\tVectorMA(org1, +width, right1, &vert[0]);\n\tVectorMA(org1, -width, right1, &vert[4]);\n\tVectorMA(org2, -width, right2, &vert[8]);\n\tVectorMA(org2, +width, right2, &vert[12]);\n}\n\nint QMB_ParticleTextureCount(void)\n{\n\treturn num_particletextures;\n}\n\nvoid QMB_ImportTextureArrayReferences(texture_flag_t* texture_flags)\n{\n\tpart_tex_t tex;\n\tint i;\n\n\tfor (tex = 0; tex < num_particletextures; ++tex) {\n\t\tif (R_TextureReferenceIsValid(particle_textures[tex].texnum)) {\n\t\t\ttexture_array_ref_t* array_ref = &texture_flags[particle_textures[tex].texnum.index].array_ref[TEXTURETYPES_SPRITES];\n\n\t\t\tparticle_textures[tex].tex_array = array_ref->ref;\n\t\t\tparticle_textures[tex].tex_index = array_ref->index;\n\t\t\tfor (i = 0; i < particle_textures[tex].components; ++i) {\n\t\t\t\tparticle_textures[tex].coords[i][0] *= array_ref->scale_s;\n\t\t\t\tparticle_textures[tex].coords[i][2] *= array_ref->scale_s;\n\t\t\t\tparticle_textures[tex].coords[i][1] *= array_ref->scale_t;\n\t\t\t\tparticle_textures[tex].coords[i][3] *= array_ref->scale_t;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid QMB_FlagTexturesForArray(texture_flag_t* texture_flags)\n{\n\tpart_tex_t tex;\n\n\tfor (tex = 0; tex < num_particletextures; ++tex) {\n\t\tif (R_TextureReferenceIsValid(particle_textures[tex].texnum)) {\n\t\t\ttexture_flags[particle_textures[tex].texnum.index].flags |= (1 << TEXTURETYPES_SPRITES);\n\t\t\tmemcpy(particle_textures[tex].coords, particle_textures[tex].originalCoords, sizeof(particle_textures[tex].coords));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/r_particles_qmb.h",
    "content": "\n#ifndef EZQUAKE_GL_RPART_HEADER\n#define EZQUAKE_GL_RPART_HEADER\n\n#include \"r_sprite3d.h\"\n#include \"r_state.h\"\n\n#define ABSOLUTE_MIN_PARTICLES\t\t\t\t256\n#define ABSOLUTE_MAX_PARTICLES\t\t\t\t32768\n\n//Better rand nums\n#define lhrandom(MIN,MAX) ((rand() & 32767) * (((MAX)-(MIN)) * (1.0f / 32767.0f)) + (MIN))\n\ntypedef byte col_t[4];\n\ntypedef enum {\n\tp_spark, p_smoke, p_fire, p_bubble, p_lavasplash, p_gunblast, p_chunk, p_shockwave,\n\tp_inferno_flame, p_inferno_trail, p_sparkray, p_staticbubble, p_trailpart,\n\tp_dpsmoke, p_dpfire, p_teleflare, p_blood1, p_blood2, p_blood3,\n\t//VULT PARTICLES\n\tp_rain,\n\tp_alphatrail,\n\tp_railtrail,\n\tp_streak,\n\tp_streaktrail,\n\tp_streakwave,\n\tp_lightningbeam,\n\tp_vxblood,\n\tp_lavatrail,\n\tp_vxsmoke,\n\tp_vxsmoke_red,\n\tp_muzzleflash,\n\tp_inferno, //VULT - NOT TO BE CONFUSED WITH THE 0.36 FIREBALL\n\tp_2dshockwave,\n\tp_vxrocketsmoke,\n\tp_trailbleed,\n\tp_bleedspike,\n\tp_flame,\n\tp_bubble2,\n\tp_bloodcloud,\n\tp_chunkdir,\n\tp_smallspark,\n\t//[HyperNewbie] - particles!\n\tp_slimeglow, //slime glow\n\tp_slimebubble, //slime yellowish growing popping bubble\n\tp_blacklavasmoke,\n\tp_nailtrail,\n\tp_flametorch,\n\tnum_particletypes,\n} part_type_t;\n\ntypedef enum {\n\tpm_static, pm_normal, pm_bounce, pm_die, pm_nophysics, pm_float,\n\t//VULT PARTICLES\n\tpm_rain,\n\tpm_streak,\n\tpm_streakwave,\n\tpm_trail\n} part_move_t;\n\ntypedef enum {\n\tptex_none, ptex_smoke, ptex_bubble, ptex_generic, ptex_dpsmoke, ptex_lava,\n\tptex_blueflare, ptex_blood1, ptex_blood2, ptex_blood3,\n\t//VULT PARTICLES\n\tptex_shockwave,\n\tptex_lightning,\n\tptex_spark,\n\tnum_particletextures,\n} part_tex_t;\n\ntypedef enum {\n\tpd_spark, pd_sparkray, pd_billboard, pd_billboard_vel,\n\t//VULT PARTICLES\n\tpd_beam,\n\tpd_hide,\n\tpd_normal,\n\tpd_dynamictrail,\n\tpd_torch\n} part_draw_t;\n\ntypedef enum {\n\tBLEND_GL_SRC_ALPHA_GL_ONE_MINUS_SRC_ALPHA,\n\tBLEND_GL_SRC_ALPHA_GL_ONE,\n\tBLEND_GL_ONE_GL_ONE,\n\tBLEND_GL_ZERO_GL_ONE_MINUS_SRC_COLOR_CONSTANT,   // ONE_MINUS_SRC_COLOR but r=g=b, so reduce by constant\n\tBLEND_GL_ZERO_GL_ONE_MINUS_SRC_COLOR,            // ONE_MINUS_SRC_COLOR but different levels for each\n\n\tNUMBER_OF_BLEND_TYPES\n} part_blend_id;\n\ntypedef struct particle_s {\n\tstruct particle_s *next;\n\tvec3_t      org, endorg;\n\tcol_t       color;\n\tfloat       growth;\n\tvec3_t      vel;\n\tfloat       rotangle;\n\tfloat       rotspeed;\n\tfloat       size;\n\tfloat       start;\n\tfloat       die;\n\tbyte        hit;\n\tbyte        texindex;\n\tbyte        bounces;\n\tbyte        initial_alpha;\n\n\tint         cached_contents;\n\tvec3_t      cached_movement;\n\tfloat       cached_distance;\n\n\tint         entity_ref;\n\tint         entity_trailindex;\n\tint         entity_trailnumber;\n} particle_t;\n\ntypedef struct particle_type_s {\n\tparticle_t\t  *start;\n\tpart_type_t\t  id;\n\tpart_draw_t\t  drawtype;\n\tpart_blend_id blendtype;\n\tpart_tex_t\t  texture;\n\tfloat\t\t  startalpha;\n\tfloat\t\t  grav;\n\tfloat\t\t  accel;\n\tpart_move_t\t  move;\n\tfloat\t\t  custom;\n\n\tint           verts_per_primitive;\n\tsprite3d_batch_id billboard_type;\n\n\tint           particles;\n\tr_state_id    state;\n} particle_type_t;\n\n#define\tMAX_PTEX_COMPONENTS\t\t8\ntypedef struct particle_texture_s {\n\ttexture_ref texnum;\n\ttexture_ref tex_array;\n\tint         tex_index;\n\tint\t\t\tcomponents;\n\tfloat\t\tcoords[MAX_PTEX_COMPONENTS][4];\n\tfloat\t\toriginalCoords[MAX_PTEX_COMPONENTS][4];\n} particle_texture_t;\n\ntypedef void(*func_color_transform_t)(col_t input, col_t output);\nextern particle_type_t particle_types[num_particletypes];\nextern int particle_type_index[num_particletypes];\nextern particle_t* free_particles;\nextern particle_texture_t particle_textures[num_particletextures];\n\nextern cvar_t amf_part_fulldetail;\n\nvoid QMB_ProcessParticle(particle_type_t* pt, particle_t* p);\nqbool TraceLineN(vec3_t start, vec3_t end, vec3_t impact, vec3_t normal);\nextern particle_t* free_particles;\nvoid ParticleStats(int change);\nbyte *ColorForParticle(part_type_t type);\nvoid AddParticle(part_type_t type, vec3_t org, int count, float size, float time, col_t col, vec3_t dir);\n\n#define R_SIMPLETRAIL_MAXLENGTH    100\n#define R_SIMPLETRAIL_NEAR_ALPHA    75\n\n#endif // def(EZQUAKE_GL_RPART_HEADER)\n"
  },
  {
    "path": "src/r_particles_qmb_spawn.c",
    "content": "/*\nCopyright (C) 2002-2003, Dr Labman, A. Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"vx_stuff.h\"\n#include \"r_particles_qmb.h\"\n#include \"pmove.h\"\n#include \"utils.h\"\n#include \"qmb_particles.h\"\n#include \"r_brushmodel.h\" // R_PointIsUnderwater only\n\nstatic qbool QMB_InWaterEffect(vec3_t org, qbool isFire)\n{\n\textern cvar_t amf_underwater_fire;\n\textern cvar_t amf_underwater_effects;\n\n\tif ((isFire ? amf_underwater_fire.integer : amf_underwater_effects.integer)) {\n\t\treturn R_PointIsUnderwater(org);\n\t}\n\treturn false;\n}\n\nstatic vec3_t zerodir = { 22, 22, 22 };\n\nbyte *ColorForParticle(part_type_t type)\n{\n\tint lambda;\n\tstatic col_t color;\n\n\tswitch (type) {\n\t\tcase p_spark:\n\t\t\tcolor[0] = 224 + (rand() & 31); color[1] = 100 + (rand() & 31); color[2] = 0;\n\t\t\tbreak;\n\t\tcase p_smoke:\n\t\t\tcolor[0] = color[1] = color[2] = color[3] = 255;\n\t\t\tbreak;\n\t\tcase p_fire:\n\t\t\tcolor[0] = 255; color[1] = 142; color[2] = 62;\n\t\t\tbreak;\n\t\tcase p_slimebubble:\n\t\tcase p_bubble:\n\t\tcase p_staticbubble:\n\t\t\tcolor[0] = color[1] = color[2] = 192 + (rand() & 63);\n\t\t\tbreak;\n\t\tcase p_teleflare:\n\t\tcase p_slimeglow:\n\t\tcase p_lavasplash:\n\t\t\tcolor[0] = color[1] = color[2] = 128 + (rand() & 127);\n\t\t\tbreak;\n\t\tcase p_gunblast:\n\t\t\tcolor[0] = 224 + (rand() & 31); color[1] = 170 + (rand() & 31); color[2] = 0;\n\t\t\tbreak;\n\t\tcase p_chunk:\n\t\t\tcolor[0] = color[1] = color[2] = (32 + (rand() & 127));\n\t\t\tbreak;\n\t\tcase p_shockwave:\n\t\t\tcolor[0] = color[1] = color[2] = 64 + (rand() & 31);\n\t\t\tbreak;\n\t\tcase p_inferno_flame:\n\t\tcase p_inferno_trail:\n\t\t\tcolor[0] = 255; color[1] = 77; color[2] = 13;\n\t\t\tbreak;\n\t\tcase p_sparkray:\n\t\t\tcolor[0] = 255; color[1] = 102; color[2] = 25;\n\t\t\tbreak;\n\t\tcase p_dpsmoke:\n\t\t\tcolor[0] = color[1] = color[2] = 48 + (((rand() & 0xFF) * 48) >> 8);\n\t\t\tbreak;\n\t\tcase p_dpfire:\n\t\t\tlambda = rand() & 0xFF;\n\t\t\tcolor[0] = 160 + ((lambda * 48) >> 8); color[1] = 16 + ((lambda * 148) >> 8); color[2] = 16 + ((lambda * 16) >> 8);\n\t\t\tbreak;\n\t\tcase p_blood1:\n\t\tcase p_blood2:\n\t\t\tcolor[0] = color[1] = color[2] = 180 + (rand() & 63);\n\t\t\tbreak;\n\t\tcase p_blood3:\n\t\t\tcolor[0] = (100 + (rand() & 31)); color[1] = color[2] = 0;\n\t\t\tbreak;\n\t\tcase p_smallspark:\n\t\t\tcolor[0] = color[1] = color[2] = color[3] = 255;\n\t\t\tbreak;\n\t\tcase p_nailtrail:\n\t\t\tcolor[0] = color[1] = color[2] = color[3] = R_SIMPLETRAIL_NEAR_ALPHA;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t//assert(!\"ColorForParticle: unexpected type\"); -> hexum - FIXME not all types are handled, seems to work ok though\n\t\t\tbreak;\n\t}\n\treturn color;\n}\n\n__inline static void AddParticleEnt(part_type_t type, vec3_t org, int count, float size, float time, col_t col, vec3_t dir, int entity_id)\n{\n\tbyte *color;\n\tint i, j;\n\tfloat tempSize;\n\tparticle_t *p;\n\tparticle_type_t *pt;\n\n\tif (!qmb_initialized)\n\t\tSys_Error(\"QMB particle added without initialization\");\n\n\tif (size <= 0 || time <= 0) {\n\t\tCon_DPrintf(\"AddParticle() failed, type %d, size %f, time %f\\n\", type, size, time);\n\t\treturn;\n\t}\n\n\tif (type >= num_particletypes) {\n\t\tSys_Error(\"AddParticle: Invalid type (%d)\", type);\n\t}\n\n\tpt = &particle_types[particle_type_index[type]];\n\n\tfor (i = 0; i < count && free_particles; i++) {\n\t\tcolor = col ? col : ColorForParticle(type);\n\t\tINIT_NEW_PARTICLE(pt, p, color, size, time);\n\t\tp->entity_ref = entity_id;\n\t\tp->entity_trailnumber = 0;\n\n\t\tswitch (type) {\n\t\t\tcase p_spark:\n\t\t\t\tp->size = 1.175;\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\ttempSize = size * 2;\n\t\t\t\tp->vel[0] = (rand() % (int)tempSize) - ((int)tempSize / 2);\n\t\t\t\tp->vel[1] = (rand() % (int)tempSize) - ((int)tempSize / 2);\n\t\t\t\tp->vel[2] = (rand() % (int)tempSize) - ((int)tempSize / 3);\n\t\t\t\tbreak;\n\t\t\tcase p_smoke:\n\t\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\t\tp->org[j] = org[j] + ((rand() & 31) - 16) / 2.0;\n\t\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\t\tp->vel[j] = ((rand() % 10) - 5) / 20.0;\n\t\t\t\tbreak;\n\t\t\tcase p_fire:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\t\tp->vel[j] = ((rand() % 160) - 80) * (size / 25.0);\n\t\t\t\tbreak;\n\t\t\tcase p_bubble:\n\t\t\t\t//VULT PARTICLES\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tfor (j = 0; j < 2; j++)\n\t\t\t\t\tp->vel[j] = (rand() % 150) - 75;\n\t\t\t\tp->vel[2] = (rand() % 64) + 24;\n\t\t\t\tbreak;\n\t\t\tcase p_lavasplash:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorCopy(dir, p->vel);\n\t\t\t\tbreak;\n\t\t\tcase p_slimeglow:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorCopy(dir, p->vel);\n\t\t\t\tbreak;\n\t\t\tcase p_gunblast:\n\t\t\t\tp->size = 1;\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tp->vel[0] = (rand() & 127) - 64;\n\t\t\t\tp->vel[1] = (rand() & 127) - 64;\n\t\t\t\tp->vel[2] = (rand() & 127) - 10;\n\t\t\t\tbreak;\n\t\t\tcase p_chunk:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tp->vel[0] = (rand() % 40) - 20;\n\t\t\t\tp->vel[1] = (rand() % 40) - 20;\n\t\t\t\tp->vel[2] = (rand() % 40) - 5;\n\t\t\t\tbreak;\n\t\t\tcase p_shockwave:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorCopy(dir, p->vel);\n\t\t\t\tbreak;\n\t\t\tcase p_inferno_trail:\n\t\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\t\tp->org[j] = org[j] + (rand() & 15) - 8;\n\t\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\t\tp->vel[j] = (rand() & 3) - 2;\n\t\t\t\tp->growth = -1.5;\n\t\t\t\tbreak;\n\t\t\tcase p_inferno_flame:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorClear(p->vel);\n\t\t\t\tp->growth = -30;\n\t\t\t\tbreak;\n\t\t\tcase p_sparkray:\n\t\t\t\tVectorCopy(org, p->endorg);\n\t\t\t\tVectorCopy(dir, p->org);\n\t\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\t\tp->vel[j] = (rand() & 127) - 64;\n\t\t\t\tp->growth = -16;\n\t\t\t\tbreak;\n\t\t\tcase p_staticbubble:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorClear(p->vel);\n\t\t\t\tbreak;\n\t\t\tcase p_slimebubble:\n\t\t\t\t//HyperNewbie - Rising, popping bubbles\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorClear(p->vel);\n\t\t\t\tp->growth = 7.5;\n\t\t\t\tbreak;\n\t\t\tcase p_teleflare:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorCopy(dir, p->vel);\n\t\t\t\tp->growth = 1.75;\n\t\t\t\tbreak;\n\t\t\tcase p_blood1:\n\t\t\tcase p_blood2:\n\t\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\t\tp->org[j] = org[j] + (rand() & 15) - 8;\n\t\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\t\tp->vel[j] = (rand() & 63) - 32;\n\t\t\t\tbreak;\n\t\t\tcase p_blood3:\n\t\t\t\tp->size = size * (rand() % 20) / 5.0;\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\t\tp->vel[j] = (rand() % 40) - 20;\n\t\t\t\tbreak;\n\t\t\t\t//VULT PARTICLES\n\t\t\tcase p_rain:\n\t\t\t\tp->size = 1;\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tp->vel[0] = (rand() % 10 + 85)*size;\n\t\t\t\tp->vel[1] = (rand() % 10 + 85)*size;\n\t\t\t\tp->vel[2] = (rand() % -100 - 2000)*(size / 3);\n\t\t\t\tbreak;\n\t\t\t\t//VULT PARTICLES\n\t\t\tcase p_streaktrail:\n\t\t\tcase p_lightningbeam:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorCopy(dir, p->endorg);\n\t\t\t\tif (amf_part_fulldetail.integer && type == p_streaktrail)\n\t\t\t\t\tp->size = size * 3;\n\t\t\t\tVectorClear(p->vel);\n\t\t\t\tp->growth = -p->size / time;\n\t\t\t\tp->initial_alpha = color[3];\n\t\t\t\tbreak;\n\t\t\t\t//VULT PARTICLES\n\t\t\tcase p_streak:\n\t\t\tcase p_inferno:\n\t\t\tcase p_streakwave:\n\t\t\tcase p_smallspark:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorCopy(dir, p->vel);\n\t\t\t\tbreak;\n\t\t\t\t//VULT PARTICLES\n\t\t\tcase p_vxblood:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tp->vel[0] = p->vel[1] = 40;\n\t\t\t\tp->vel[2] = 17;\n\t\t\t\tbreak;\n\t\t\t\t//VULT PARTICLES\n\t\t\tcase p_vxsmoke:\n\t\t\tcase p_vxsmoke_red:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorCopy(dir, p->vel);\n\t\t\t\tp->growth = 4.5;\n\t\t\t\tp->rotspeed = (rand() & 63) + 96;\n\t\t\t\tbreak;\n\t\t\t\t//VULT PARTICLES\n\t\t\tcase p_muzzleflash:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorCopy(dir, p->vel);\n\t\t\t\tp->growth = -size / time;\n\t\t\t\tbreak;\n\t\t\t\t//VULT PARTICLES\n\t\t\tcase p_2dshockwave:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorCopy(dir, p->endorg);\n\t\t\t\tVectorClear(p->vel);\n\t\t\t\tp->size = 1;\n\t\t\t\tp->growth = size;\n\t\t\t\tbreak;\n\t\t\t\t//VULT PARTICLES\n\t\t\tcase p_flame:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tp->org[2] += 4;\n\t\t\t\tp->growth = -p->size / 2;\n\t\t\t\tVectorClear(p->vel);\n\t\t\t\tfor (j = 0; j < 2; j++)\n\t\t\t\t\tp->vel[j] = (rand() % 6) - 3;\n\t\t\t\tbreak;\n\t\t\t\t//VULT PARTICLES\n\t\t\tcase p_bloodcloud:\n\t\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\t\tp->org[j] = org[j] + (rand() & 30) - 15;\n\t\t\t\tVectorClear(p->vel);\n\t\t\t\tp->size = (rand() % (int)size) + 10;\n\t\t\t\tbreak;\n\t\t\t\t//HyperNewbie Particles\n\t\t\tcase p_blacklavasmoke:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorCopy(dir, p->vel);\n\t\t\t\tp->growth = 7.5;\n\t\t\t\tp->rotspeed = (rand() & 63) + 16;\n\t\t\t\tbreak;\n\t\t\tcase p_chunkdir:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorCopy(dir, p->vel);\n\t\t\t\tbreak;\n\t\t\tcase p_flametorch:\n\t\t\t\tVectorCopy(org, p->org);\n\t\t\t\tVectorCopy(dir, p->vel);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tassert(!\"AddParticle: unexpected type\");\n\t\t\t\tbreak;\n\t\t}\n\n\t\tQMB_ProcessParticle(pt, p);\n\t}\n}\n\nvoid AddParticle(part_type_t type, vec3_t org, int count, float size, float time, col_t col, vec3_t dir)\n{\n\tAddParticleEnt(type, org, count, size, time, col, dir, 0);\n}\n\n//VULT PARTICLES\nvoid DrawMuzzleflash(vec3_t start, vec3_t angle, vec3_t vel)\n{\n\tvec3_t dir, forward, up, right;\n\tcol_t color;\n\tint i;\n\tcolor[0] = 55;\n\tcolor[1] = 55;\n\tcolor[2] = 55;\n\tcolor[3] = 55;\n\tAngleVectors(angle, forward, right, up);\n\tVectorClear(dir);\n\tVectorMA(dir, 20, forward, dir);\n\tdir[2] += rand() & 5;\n\tAddParticle(p_vxsmoke, start, 1, 8, 1, color, dir);\n\n\tcolor[0] = 255;\n\tcolor[1] = 180;\n\tcolor[2] = 55;\n\tcolor[3] = 255;\n\tVectorCopy(start, dir);\n\tfor (i = 60; i > 0; i--) {\n\t\t//VULT - i here is basically the length of the gun flame\n\t\tVectorMA(dir, 0.33, forward, dir);\n\t\tAddParticle(p_muzzleflash, dir, 1, i / 3, 0.1, color, vel);\n\t}\n}\n\n//VULT PARTICLES\nvoid FuelRodExplosion(vec3_t org)\n{\n\tcol_t color;\n\tvec3_t dir, org2, angle;\n\tint a, i;\n\tfloat theta;\n\n\tcolor[0] = 75; color[1] = 255; color[2] = 75;\n\tcolor[3] = 255;\n\tAddParticle(p_fire, org, 10, 20, 0.5, color, zerodir);\n\tangle[2] = 0;\n\tif (amf_part_2dshockwaves.value) {\n\t\tAddParticle(p_2dshockwave, org, 1, 30, 0.5, NULL, vec3_origin);\n\t}\n\telse {\n\t\tfor (theta = 0; theta < 2 * M_PI; theta += 2 * M_PI / 90) {\n\t\t\tangle[0] = cos(theta) * 900;\n\t\t\tangle[1] = sin(theta) * 900;\n\t\t\tAddParticle(p_shockwave, org, 1, 15, 1, NULL, angle);\n\t\t}\n\t}\n\n\tfor (a = 0; a < 60 * amf_part_explosion.value; a++) {\n\t\tfor (i = 0; i < 3; i++) {\n\t\t\tdir[i] = (rand() % 1500) - 750;\n\t\t}\n\n\t\tif (amf_part_trailtype.value == 2) {\n\t\t\tAddParticle(p_smallspark, org, 1, 1, 0.5*amf_part_trailtime.value, color, dir);\n\t\t}\n\t\telse {\n\t\t\tAddParticle(p_streak, org, 1, 1, 0.5*amf_part_trailtime.value, color, dir);\n\t\t}\n\t}\n\n\tfor (i = 0; i < 5; i++) {\n\t\tVectorCopy(org, org2);\n\t\tfor (a = 0; a < 60; a = a + 10) {\n\t\t\torg2[0] = org2[0] + (rand() % 15) - 7;\n\t\t\torg2[1] = org2[1] + (rand() % 15) - 7;\n\t\t\torg2[2] = org2[2] + 10;\n\t\t\tAddParticle(p_fire, org2, 5, 10, (0.5 + a) / 60, color, zerodir);\n\t\t}\n\t}\n\tfor (i = 0; i < 15; i++) {\n\t\tVectorCopy(org, org2);\n\t\torg2[2] = org2[2] + 60;\n\t\torg2[0] = org2[0] + (rand() % 100) - 50;\n\t\torg2[1] = org2[1] + (rand() % 100) - 50;\n\t\tAddParticle(p_fire, org2, 5, 10, 1, color, zerodir);\n\t}\n\n\tfor (i = 0; i < 5; i++) {\n\t\tVectorCopy(org, org2);\n\t\torg2[2] = org2[2] + 70;\n\t\torg2[0] = org2[0] + (rand() % 70) - 35;\n\t\torg2[1] = org2[1] + (rand() % 70) - 35;\n\t\tAddParticle(p_fire, org2, 5, 10, 1.05, color, zerodir);\n\t}\n\tfor (i = 0; i < 5; i++) {\n\t\tVectorCopy(org, org2);\n\t\torg2[2] = org2[2] + 80;\n\t\torg2[0] = org2[0] + (rand() % 50) - 25;\n\t\torg2[1] = org2[1] + (rand() % 50) - 25;\n\t\tAddParticle(p_fire, org2, 5, 10, 1.1, color, zerodir);\n\t}\n\tfor (i = 0; i < 5; i++) {\n\t\tVectorCopy(org, org2);\n\t\torg2[2] = org2[2] + 90;\n\t\torg2[0] = org2[0] + (rand() % 25) - 12;\n\t\torg2[1] = org2[1] + (rand() % 25) - 12;\n\t\tAddParticle(p_fire, org2, 5, 10, 1.15, color, zerodir);\n\t}\n}\n\n//VULT PARTICLES\n//Not much, but anything is better than that alias torch\nvoid ParticleTorchFire(entity_t* ent)\n{\n\tcol_t color = { 255,100,25, 128 };\n\n\tif (ent->entity_id) {\n\t\tentity_t* sent = &cl_static_entities[ent->entity_id - 1];\n\t\tif (r_refdef2.time - sent->particle_time < MIN_ENTITY_PARTICLE_FRAMETIME) {\n\t\t\treturn;\n\t\t}\n\t\tsent->particle_time = r_refdef2.time;\n\n\t\tAddParticleEnt(p_flametorch, ent->origin, 1, 7, 0.8f, color, zerodir, -ent->entity_id);\n\t}\n\telse {\n\t\tif (QMB_InWaterEffect(ent->origin, true)) {\n\t\t\tAddParticle(p_bubble, ent->origin, 1, 2.8, 2.5, NULL, zerodir);\n\t\t}\n\t\telse {\n\t\t\tvec3_t end;\n\t\t\ttrace_t trace;\n\n\t\t\tVectorCopy(ent->origin, end);\n\t\t\tend[2] += 32;\n\t\t\ttrace = PM_TraceLine(ent->origin, end);\n\n\t\t\tAddParticle(p_flame, ent->origin, 1, 7, 0.8 * trace.fraction, amf_part_firecolor.string[0] ? StringToRGB(amf_part_firecolor.string) : color, zerodir);\n\t\t}\n\t}\n}\n\n// This is only used by lava surface effect now\nvoid ParticleFire(vec3_t org)\n{\n\tcol_t color = { 255,100,25, 128 };\n\n\tif (QMB_InWaterEffect(org, true)) {\n\t\tAddParticle(p_bubble, org, 1, 2.8, 2.5, NULL, zerodir);\n\t}\n\telse {\n\t\tvec3_t end;\n\t\ttrace_t trace;\n\n\t\tVectorCopy(org, end);\n\t\tend[2] += 32;\n\t\ttrace = PM_TraceLine(org, end);\n\n\t\tAddParticle(p_flame, org, 1, 7, 0.8 * trace.fraction, amf_part_firecolor.string[0] ? StringToRGB(amf_part_firecolor.string) : color, zerodir);\n\t}\n}\n\n//TEI PARTICLES\n//Idea: lavapool fire, Result: slighty lame\nvoid ParticleFirePool(vec3_t org)\n{\n\tcol_t color = { 255,100,25, 128 };\n\n\tAddParticle(p_flame, org, 1, lhrandom(1, 32), lhrandom(1, 1), color, zerodir);\n}\n\n//TEI PARTICLES\n//Idea: slimepool fire, Result: slighty lame\n//4 - fantastic\n//11 - fantastic\n//28 - blood pool ULTRA FANTASTIC (movies?)\nvoid ParticleSlimeHarcore(vec3_t org)\n{\n\tcol_t color = { 0,200,150, 240 };\n\tvec3_t dir = { 0,0,80 };\n\n\tAddParticle(p_lavasplash, org, 1, lhrandom(1, 32), lhrandom(1, 3), color, dir);//zerodir);\n\tAddParticle(p_staticbubble, org, 1, lhrandom(1, 128), lhrandom(1, 10), color, dir);//zerodir);\n}\n\nvoid ParticleSlime(vec3_t org)\n{\n\tcol_t color = { 0,200,150, 30 };\n\tvec3_t dir = { 0,0,80 };\n\n\tAddParticle(p_lavasplash, org, 1, lhrandom(1, 32), lhrandom(1, 3), color, dir);//zerodir);\n\tAddParticle(p_staticbubble, org, 1, lhrandom(1, 32), lhrandom(1, 10), color, dir);//zerodir);\n}\n\n//HyperNewbie Particles\nvoid ParticleSmallerFirePool(vec3_t org)\n{\n\tcol_t color = { 255,100,25, 78 };\n\n\tAddParticle(p_flame, org, 1, lhrandom(4, 7), 0.8, color, zerodir);\n}\n\nvoid ParticleSlimeBubbles(vec3_t org)\n{\n\tcol_t color = { 32,32,1, 250 };\n\tAddParticle(p_slimebubble, org, 1, lhrandom(4, 6), lhrandom(1, 5), color, zerodir);\n}\n\nvoid ParticleSlimeGlow(vec3_t org)\n{\n\tcol_t color = { 0,128,128, 200 };\n\tvec3_t dir = { 0,0,5 };\n\tAddParticle(p_slimeglow, org, 1, lhrandom(32, 64), lhrandom(7, 8), color, dir);\n}\n\nvoid ParticleLavaSmokePool(vec3_t org)\n{\n\tcol_t color = { 15,15,15,15 };\n\tvec3_t dir = { 0,0,lhrandom(0,1) };\n\n\tAddParticle(p_blacklavasmoke, org, 1, lhrandom(14, 23), lhrandom(1, 3), color, dir);\n}\n\n//TEI PARTICLES\n//Idea: bloodpool \nvoid ParticleBloodPool(vec3_t org)\n{\n\tcol_t color = { 30,100,150, 240 };\n\tvec3_t dir = { 0,0,80 };\n\n\tAddParticle(p_vxsmoke_red, org, 1, lhrandom(1, 11), lhrandom(1, 3), color, dir);\n}\n\n\n//VULT PARTICLES\n//This looks quite good with detailtrails on\nvoid VX_TeslaCharge(vec3_t org)\n{\n\tvec3_t pos, vec, dir;\n\tcol_t col = { 60,100,240, 128 };\n\tfloat time, len;\n\tint i;\n\n\tfor (i = 0; i < 5; i++) {\n\t\tVectorClear(vec);\n\t\tVectorClear(dir);\n\n\t\tVectorCopy(org, pos);\n\t\tpos[0] += (rand() % 200) - 100;\n\t\tpos[1] += (rand() % 200) - 100;\n\t\tpos[2] += (rand() % 200) - 100;\n\n\t\tVectorSubtract(pos, org, vec);\n\t\tlen = VectorLength(vec);\n\t\tVectorNormalize(vec);\n\t\tVectorMA(dir, -200, vec, dir);\n\t\ttime = len / 200; //\n\n\t\tAddParticle(p_streakwave, pos, 1, 3, time, col, dir);\n\t}\n}\n\n//VULT PARTICLES\n//Adds a beam type particle with the lightning texture\nvoid VX_LightningBeam(vec3_t start, vec3_t end)\n{\n\t//col_t color={120,140,255,255};\n\tAddParticle(p_lightningbeam, start, 1, 100, cls.frametime ? cls.frametime * 2 : 0.013, amf_lightning_color.color, end);\n}\n\n//VULT PARTICLES\n//Effect for when someone dies\nvoid VX_DeathEffect(vec3_t org)\n{\n\tint a, i;\n\tvec3_t dir;\n\tcol_t color;\n\tcolor[0] = 255;\n\tcolor[1] = 225;\n\tcolor[2] = 128;\n\tcolor[3] = 128;\n\n\tfor (a = 0; a < 60; a++) {\n\t\tfor (i = 0; i < 3; i++)\n\t\t\tdir[i] = (rand() % 700) - 300;\n\t\tAddParticle(p_streakwave, org, 1, 1, 0.5, color, dir);\n\t}\n}\n\n//VULT PARTICLES\n//Effect for when someone dies violently\nvoid VX_GibEffect(vec3_t org)\n{\n\tcol_t color;\n\tcolor[0] = 55;\n\tcolor[1] = 0;\n\tcolor[2] = 0;\n\tcolor[3] = 128;\n\n\tAddParticle(p_bloodcloud, org, 10, 30, 1, color, zerodir);\n}\n\n//VULT PARTICLES\n//Prototype detpack explosion, yeah, I know, it sucks. Thats why its never actually used\nvoid VX_DetpackExplosion(vec3_t org)\n{\n\tvec3_t angle, neworg;\n\tfloat theta;\n\tcol_t color = { 255,102,25,255 };\n\tint i, j;\n\n\tangle[2] = 0;\n\tfor (theta = 0; theta < 2 * M_PI; theta += 2 * M_PI / 90) {\n\t\tangle[0] = cos(theta) * 750;\n\t\tangle[1] = sin(theta) * 750;\n\t\tAddParticle(p_shockwave, org, 1, 20, 1, NULL, angle);\n\t\tangle[0] = cos(theta) * 200 + ((rand() % 100) - 50);\n\t\tangle[1] = sin(theta) * 200 + ((rand() % 100) - 50);\n\t\tangle[2] = rand() % 300;\n\t\tAddParticle(p_chunkdir, org, 1, 8, 1, color, angle);\n\t\tangle[2] = rand() % 100 - 50;\n\t}\n\tAddParticle(p_fire, org, 20, 80, 1, color, zerodir);\n\tfor (i = 0; i < 5; i++) {\n\t\tangle[2] = 0;\n\t\tfor (j = 0; j < 5; j++) {\n\t\t\tAngleVectors(angle, NULL, NULL, neworg);\n\t\t\tVectorMA(org, 180, neworg, neworg);\n\t\t\tAddParticle(p_sparkray, org, 1, 16 + (i & 3), 1, NULL, neworg);\n\t\t\tangle[2] += 360 / 5;\n\t\t}\n\t\tangle[0] += 180 / 5;\n\t}\n}\n\n\n//TEI PARTICLES\n//Teleport dimensional implosion\n//unfinished :(\n// testing cvs \nvoid VX_Implosion(vec3_t org)\n{\n\t//TODO\n}\n\n//VULT PARTICLES\n//This just adds a little lightning trail to the laser beams\nvoid VX_LightningTrail(vec3_t start, vec3_t end)\n{\n\tcol_t color = { 255,77,0,255 };\n\tAddParticle(p_lightningbeam, start, 1, 50, 0.75, color, end);\n}\n\nvoid QMB_ParticleExplosion(vec3_t org)\n{\n\tif (QMB_InWaterEffect(org, true)) {\n\t\tif (r_explosiontype.value != 9) {\n\t\t\tAddParticle(p_fire, org, 12, 14, 0.8, NULL, zerodir);\n\t\t}\n\t\tAddParticle(p_bubble, org, 6, 3.0, 2.5, NULL, zerodir);\n\t\tAddParticle(p_bubble, org, 4, 2.35, 2.5, NULL, zerodir);\n\t\tif (r_explosiontype.value != 1) {\n\t\t\tAddParticle(p_spark, org, 50, 100, 0.75, NULL, zerodir);\n\t\t\tAddParticle(p_spark, org, 25, 60, 0.75, NULL, zerodir);\n\t\t}\n\t}\n\telse {\n\t\tif (r_explosiontype.value != 9) {\n\t\t\tAddParticle(p_fire, org, 16, 18, 1, NULL, zerodir);\n\t\t}\n\t\tif (r_explosiontype.value != 1) {\n\t\t\tAddParticle(p_spark, org, 50, 250, 0.925f, NULL, zerodir);\n\t\t\tAddParticle(p_spark, org, 25, 150, 0.925f, NULL, zerodir);\n\t\t}\n\t}\n}\n\nvoid QMB_RunParticleEffect(vec3_t org, vec3_t dir, int col, int count)\n{\n\tcol_t color;\n\tvec3_t neworg;\n\tint i, blastcount, blastsize, chunkcount, particlecount, bloodcount, z;\n\tfloat scale;\n\tfloat blasttime;\n\textern cvar_t gl_part_gunshots;\n\n\tcount = max(1, count);\n\n\tif (col == 73) {\n\t\tbloodcount = Q_rint(count / 28.0);\n\t\tbloodcount = bound(2, bloodcount, 8);\n\t\tfor (i = 0; i < bloodcount; i++) {\n\t\t\tAddParticle(p_blood1, org, 1, 4.5, 2 + (rand() & 15) / 16.0, NULL, zerodir);\n\t\t\tAddParticle(p_blood2, org, 1, 4.5, 2 + (rand() & 15) / 16.0, NULL, zerodir);\n\t\t}\n\t\treturn;\n\t}\n\telse if (col == 225) {\n\t\tVectorClear(color);\n\t\tscale = (count > 130) ? 3 : (count > 20) ? 2 : 1;\n\t\tscale *= 0.758;\n\t\tfor (i = 0; i < (count >> 1); i++) {\n\t\t\tneworg[0] = org[0] + scale * ((rand() & 15) - 8);\n\t\t\tneworg[1] = org[1] + scale * ((rand() & 15) - 8);\n\t\t\tneworg[2] = org[2] + scale * ((rand() & 15) - 8);\n\t\t\tcolor[0] = 50 + (rand() & 63);\n\t\t\tAddParticle(p_blood3, org, 1, 1, 2.3, NULL, zerodir);\n\t\t}\n\t\treturn;\n\t}\n\telse if (col == 20 && count == 30) {\n\t\tcolor[0] = 51; color[2] = 51; color[1] = 255; color[3] = 255;\n\t\tAddParticle(p_chunk, org, 1, 1, 0.75, color, zerodir);\n\t\tAddParticle(p_spark, org, 12, 75, 0.4, color, zerodir);\n\t\treturn;\n\t}\n\telse if (col == 226 && count == 20) {\n\t\tcolor[0] = 230; color[1] = 204; color[2] = 26; color[3] = 255;\n\t\tAddParticle(p_chunk, org, 1, 1, 0.75, color, zerodir);\n\t\tAddParticle(p_spark, org, 12, 75, 0.4, color, zerodir);\n\t\treturn;\n\t}\n\n\tswitch (count) {\n\t\tcase 10:\n\t\t\tAddParticle(p_spark, org, 4, 70, 0.9, NULL, zerodir);\n\t\t\tbreak;\n\t\tcase 30:\n\t\t\tAddParticle(p_chunk, org, 10, 1, 4, NULL, zerodir);\n\t\t\tAddParticle(p_spark, org, 8, 105, 0.9, NULL, zerodir);\n\t\t\tbreak;\n\t\tcase 20:\n\t\t\tif (col != 256) {\n\t\t\t\tAddParticle(p_spark, org, 8, 85, 0.9, NULL, zerodir);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t/* fall through */\n\t\tdefault:\n\t\t\tif (count > 130) {\n\t\t\t\tscale = 2.274;\n\t\t\t\tblastcount = 50; blastsize = 50; blasttime = 0.4;\n\t\t\t\tchunkcount = 14;\n\t\t\t}\n\t\t\telse if (count > 20) {\n\t\t\t\tscale = 1.516;\n\t\t\t\tblastcount = 30; blastsize = 35; blasttime = 0.25;\n\t\t\t\tchunkcount = 7;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tscale = 0.758;\n\t\t\t\tblastcount = 15; blastsize = 5;\tblasttime = 0.15;\n\t\t\t\tchunkcount = 3;\n\t\t\t}\n\t\t\tparticlecount = max(1, count >> 1);\n\t\t\tAddParticle(p_gunblast, org, blastcount, blastsize, blasttime, NULL, zerodir);\n\t\t\tfor (i = 0; i < particlecount; i++) {\n\t\t\t\tneworg[0] = org[0] + scale * ((rand() & 15) - 8);\n\t\t\t\tneworg[1] = org[1] + scale * ((rand() & 15) - 8);\n\t\t\t\tneworg[2] = org[2] + scale * ((rand() & 15) - 8);\n\n\t\t\t\t// col == 256 means it gun shot.\n\t\t\t\t// So we always USE smoke when it NOT a gun shot, in case of gun shot we DON'T use smoke when gl_part_gunshots == 2\n\t\t\t\tif (col != 256 || gl_part_gunshots.integer != 2)\n\t\t\t\t\tAddParticle(p_smoke, neworg, 1, 4, 0.825f + ((rand() % 10) - 5) / 40.0, NULL, zerodir);\n\n\t\t\t\tz = particlecount / chunkcount;\n\t\t\t\tif (!z || (i % z == 0))\n\t\t\t\t\tAddParticle(p_chunk, neworg, 1, 0.75, 3.75, NULL, zerodir);\n\t\t\t}\n\t}\n}\n\nvoid QMB_BlobExplosion(vec3_t org)\n{\n\tfloat theta;\n\tcol_t color;\n\tvec3_t neworg, vel;\n\n\tcolor[0] = 60; color[1] = 100; color[2] = 240; color[3] = 255;\n\tAddParticle(p_spark, org, 44, 250, 1.15, color, zerodir);\n\n\tcolor[0] = 90; color[1] = 47; color[2] = 207; color[3] = 255;\n\tAddParticle(p_fire, org, 15, 30, 1.4, color, zerodir);\n\n\tvel[2] = 0;\n\t//VULT PARTICLES\n\tif (amf_part_shockwaves.integer) {\n\t\t//VULT Get 3 rings to fly out\n\t\tif (amf_part_2dshockwaves.value) {\n\t\t\tvec3_t shockdir;\n\t\t\tint i;\n\t\t\tcolor[0] = (60 + (rand() & 15)); color[1] = (65 + (rand() & 15)); color[2] = (200 + (rand() & 15));\n\t\t\tcolor[3] = 255;\n\t\t\tfor (i = 0; i<3; i++)\n\t\t\t\tshockdir[i] = rand() % 360;\n\t\t\tAddParticle(p_2dshockwave, org, 1, 30, 0.5, color, shockdir);\n\t\t\tfor (i = 0; i<3; i++)\n\t\t\t\tshockdir[i] = rand() % 360;\n\t\t\tAddParticle(p_2dshockwave, org, 1, 30, 0.5, color, shockdir);\n\t\t\tfor (i = 0; i<3; i++)\n\t\t\t\tshockdir[i] = rand() % 360;\n\t\t\tAddParticle(p_2dshockwave, org, 1, 30, 0.5, color, shockdir);\n\t\t}\n\t\telse {\n\t\t\tfor (theta = 0; theta < 2 * M_PI; theta += 2 * M_PI / 70) {\n\t\t\t\tcolor[0] = (60 + (rand() & 15)); color[1] = (65 + (rand() & 15)); color[2] = (200 + (rand() & 15));\n\t\t\t\tcolor[3] = 255;\n\n\t\t\t\tvel[0] = cos(theta) * 125;\n\t\t\t\tvel[1] = sin(theta) * 125;\n\t\t\t\tneworg[0] = org[0] + cos(theta) * 6;\n\t\t\t\tneworg[1] = org[1] + sin(theta) * 6;\n\t\t\t\tneworg[2] = org[2] + 0 - 10;\n\t\t\t\tAddParticle(p_shockwave, neworg, 1, 4, 0.8, color, vel);\n\t\t\t\tneworg[2] = org[2] + 0 + 10;\n\t\t\t\tAddParticle(p_shockwave, neworg, 1, 4, 0.8, color, vel);\n\n\n\t\t\t\tvel[0] *= 1.15;\n\t\t\t\tvel[1] *= 1.15;\n\t\t\t\tneworg[0] = org[0] + cos(theta) * 13;\n\t\t\t\tneworg[1] = org[1] + sin(theta) * 13;\n\t\t\t\tneworg[2] = org[2] + 0;\n\t\t\t\tAddParticle(p_shockwave, neworg, 1, 6, 1.0, color, vel);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid QMB_LavaSplash(vec3_t org)\n{\n\tint\ti, j;\n\tfloat vel;\n\tvec3_t dir, neworg;\n\n\tfor (i = -16; i < 16; i++) {\n\t\tfor (j = -16; j < 16; j++) {\n\t\t\tdir[0] = j * 8 + (rand() & 7);\n\t\t\tdir[1] = i * 8 + (rand() & 7);\n\t\t\tdir[2] = 256;\n\n\t\t\tneworg[0] = org[0] + dir[0];\n\t\t\tneworg[1] = org[1] + dir[1];\n\t\t\tneworg[2] = org[2] + (rand() & 63);\n\n\t\t\tVectorNormalizeFast(dir);\n\t\t\tvel = 50 + (rand() & 63);\n\t\t\tVectorScale(dir, vel, dir);\n\n\t\t\tAddParticle(p_lavasplash, neworg, 1, 4.5, 2.6 + (rand() & 31) * 0.02, NULL, dir);\n\t\t}\n\t}\n}\n\nvoid QMB_TeleportSplash(vec3_t org)\n{\n\tint i, j, k;\n\tvec3_t neworg, angle;\n\tcol_t color;\n\textern cvar_t gl_part_telesplash;\n\n\tfor (i = -12; i <= 12; i += 6) {\n\t\tfor (j = -12; j <= 12; j += 6) {\n\t\t\tfor (k = -24; k <= 32; k += 8) {\n\t\t\t\tneworg[0] = org[0] + i + (rand() & 3) - 1;\n\t\t\t\tneworg[1] = org[1] + j + (rand() & 3) - 1;\n\t\t\t\tneworg[2] = org[2] + k + (rand() & 3) - 1;\n\t\t\t\tangle[0] = (rand() & 15) - 7;\n\t\t\t\tangle[1] = (rand() & 15) - 7;\n\t\t\t\tangle[2] = (rand() % 160) - 80;\n\t\t\t\tAddParticle(p_teleflare, neworg, 1, 1.8, 0.30 + (rand() & 7) * 0.02, NULL, angle);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (gl_part_telesplash.integer != 2) {\n\t\tVectorSet(color, 140, 140, 255);\n\t\tcolor[3] = 255;\n\t\tVectorClear(angle);\n\t\tfor (i = 0; i < 5; i++) {\n\t\t\tangle[2] = 0;\n\t\t\tfor (j = 0; j < 5; j++) {\n\t\t\t\tAngleVectors(angle, NULL, NULL, neworg);\n\t\t\t\tVectorMA(org, 70, neworg, neworg);\n\t\t\t\tAddParticle(p_sparkray, org, 1, 6 + (i & 3), 5, color, neworg);\n\t\t\t\tangle[2] += 360 / 5;\n\t\t\t}\n\t\t\tangle[0] += 180 / 5;\n\t\t}\n\t}\n}\n\nvoid QMB_DetpackExplosion(vec3_t org)\n{\n\tint i, j;\n\tfloat theta;\n\tvec3_t neworg, angle = { 0, 0, 0 };\n\textern cvar_t gl_part_detpackexplosion_fire_color, gl_part_detpackexplosion_ray_color;\n\n\tbyte *f_color = (gl_part_detpackexplosion_fire_color.string[0] ? gl_part_detpackexplosion_fire_color.color : ColorForParticle(p_inferno_flame));\n\tbyte *r_color = (gl_part_detpackexplosion_ray_color.string[0] ? gl_part_detpackexplosion_ray_color.color : NULL);\n\n\tif (QMB_InWaterEffect(org, true)) {\n\t\tAddParticle(p_bubble, org, 8, 2.8, 2.5, NULL, zerodir);\n\t\tAddParticle(p_bubble, org, 6, 2.2, 2.5, NULL, zerodir);\n\t\tAddParticle(p_fire, org, 10, 25, 0.75, f_color, zerodir);\n\t}\n\telse {\n\t\tAddParticle(p_fire, org, 14, 33, 0.75, f_color, zerodir);\n\t}\n\n\tfor (i = 0; i < 5; i++) {\n\t\tangle[2] = 0;\n\t\tfor (j = 0; j < 5; j++) {\n\t\t\tAngleVectors(angle, NULL, NULL, neworg);\n\t\t\tVectorMA(org, 90, neworg, neworg);\n\t\t\tAddParticle(p_sparkray, org, 1, 9 + (i & 3), 0.75, r_color, neworg);\n\t\t\tangle[2] += 360 / 5;\n\t\t}\n\t\tangle[0] += 180 / 5;\n\t}\n\n\t//VULT PARTICLES\n\tif (amf_part_shockwaves.value) {\n\t\tif (amf_part_2dshockwaves.value) {\n\t\t\tAddParticle(p_2dshockwave, org, 1, 30, 0.5, NULL, vec3_origin);\n\t\t}\n\t\telse {\n\t\t\tangle[2] = 0;\n\t\t\tfor (theta = 0; theta < 2 * M_PI; theta += 2 * M_PI / 90) {\n\t\t\t\tangle[0] = cos(theta) * 350;\n\t\t\t\tangle[1] = sin(theta) * 350;\n\t\t\t\tAddParticle(p_shockwave, org, 1, 14, 0.75, NULL, angle);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid QMB_InfernoFlame(vec3_t org)\n{\n\tif (cls.frametime) {\n\t\tAddParticle(p_inferno_flame, org, 1, 30, 13.125 * cls.frametime, NULL, zerodir);\n\t\tAddParticle(p_inferno_trail, org, 2, 1.75, 45.0 * cls.frametime, NULL, zerodir);\n\t\tAddParticle(p_inferno_trail, org, 2, 1.0, 52.5 * cls.frametime, NULL, zerodir);\n\t}\n}\n\nvoid QMB_StaticBubble(entity_t *ent)\n{\n\tAddParticle(p_staticbubble, ent->origin, 1, ent->frame == 1 ? 1.85 : 2.9, ONE_FRAME_ONLY, NULL, zerodir);\n}\n\n//VULT PARTICLES\n//This WAS supposed to do more than just rain but I haven't really got around to\n//writing anything else for it.\nvoid ParticleFirePool(vec3_t);\nextern cvar_t tei_lavafire;\n\nvoid WeatherEffect(void)\n{\n\tvec3_t org, start, impact, normal;\n\tint i;\n\tcol_t colour = { 128, 128, 128, 75 };\n\n\tif (amf_weather_rain.integer) {\n\t\tfor (i = 0; i <= amf_weather_rain.integer; i++) {\n\t\t\tVectorCopy(r_refdef.vieworg, org);\n\t\t\torg[0] = org[0] + (rand() % 3000) - 1500;\n\t\t\torg[1] = org[1] + (rand() % 3000) - 1500;\n\t\t\tVectorCopy(org, start);\n\t\t\torg[2] = org[2] + 15000;\n\n\t\t\t//Trace a line straight up, get the impact location\n\n\t\t\t//trace up slowly until we are in sky\n\t\t\tTraceLineN(start, org, impact, normal);\n\n\t\t\t//fixme: see is surface above has SURF_DRAWSKY (we'll come back to that, when the important stuff is done fist, eh?)\n\t\t\t//if (TruePointContents (impact) == CONTENTS_SKY && trace) {\n\t\t\tif (Mod_PointInLeaf(impact, cl.worldmodel)->contents == CONTENTS_SKY) {\n\t\t\t\tVectorCopy(impact, org);\n\t\t\t\torg[2] = org[2] - 1;\n\t\t\t\tAddParticle(p_rain, org, 1, 1, 15, colour, zerodir);\n\t\t\t}\n\t\t}\n\t}\n\n\t//Tei, lavafire on 2 or superior \n\t// this can be more better for some users than \"eshaders\"\n\tif (tei_lavafire.integer > 2) {\n\t\tfor (i = 0; i < tei_lavafire.integer; i++) {\n\t\t\tVectorCopy(r_refdef.vieworg, org);\n\t\t\torg[0] = org[0] + (rand() % 3000) - 1500;\n\t\t\torg[1] = org[1] + (rand() % 3000) - 1500;\n\t\t\tVectorCopy(org, start);\n\t\t\torg[2] = org[2] - 15000;\n\n\t\t\tTraceLineN(start, org, impact, normal);\n\n\t\t\t//if (TruePointContents (impact) == CONTENTS_LAVA && trace) {\n\t\t\tif (Mod_PointInLeaf(impact, cl.worldmodel)->contents == CONTENTS_LAVA) {\n\t\t\t\tParticleFirePool(impact);\n\t\t\t}\n\t\t}\n\t}\n}\n\n//VULT PARTICLES\nvoid RainSplash(vec3_t org)\n{\n\tvec3_t pos;\n\tcol_t colour = { 255,255,255,255 };\n\n\tVectorCopy(org, pos);\n\tpos[2] = pos[2] + 1; //To get above the water on servers that don't use watervis\n\n\t\t\t\t\t\t //VULT - Sometimes the shockwave effect isn't noticable enough?\n\tAddParticle(p_2dshockwave, pos, 1, 5, 0.5, colour, vec3_origin);\n}\n\n//VULT PARTICLES\n//Cheap function which will generate some sparks for me to be called elsewhere\nvoid SparkGen(vec3_t org, byte col[3], float count, float size, float life)\n{\n\tcol_t color;\n\tvec3_t dir;\n\tint i, a;\n\n\tcolor[0] = col[0];\n\tcolor[1] = col[1];\n\tcolor[2] = col[2];\n\tcolor[3] = 255;\n\n\tif (amf_part_sparks.value) {\n\t\tfor (a = 0; a<count; a++) {\n\t\t\tfor (i = 0; i<3; i++)\n\t\t\t\tdir[i] = (rand() % (int)size * 2) - ((int)size * 2 / 3);\n\t\t\tif (amf_part_trailtype.value == 2)\n\t\t\t\tAddParticle(p_smallspark, org, 1, 1, life, color, dir);\n\t\t\telse\n\t\t\t\tAddParticle(p_streak, org, 1, 1, life, color, dir);\n\n\t\t}\n\t}\n\telse\n\t\tAddParticle(p_spark, org, count, size, life, color, zerodir);\n}\n\n//VULT PARTICLES\n//VULT - Does the deed when someone shoots a wall\nvoid VXGunshot(vec3_t org, float count)\n{\n\tcol_t color = { 255,80,10, 128 };\n\tvec3_t dir, neworg;\n\tint a, i;\n\n\tif (amf_part_gunshot_type.value == 2 || amf_part_gunshot_type.value == 3) {\n\t\tcolor[0] = 255;\n\t\tcolor[1] = 242;\n\t\tcolor[2] = 153;\n\t\tVectorClear(dir);\n\t\tfor (i = 0; i < 5; i++) {\n\t\t\tdir[2] = 0;\n\t\t\tfor (a = 0; a < 5; a++) {\n\t\t\t\tAngleVectors(dir, NULL, NULL, neworg);\n\t\t\t\tVectorMA(org, 40, neworg, neworg);\n\t\t\t\tAddParticle(p_sparkray, org, 1, 3, 1.5, color, neworg);\n\t\t\t\tdir[2] += 360 / 5;\n\t\t\t}\n\t\t\tdir[0] += 180 / 5;\n\t\t}\n\t\tif (amf_coronas.integer) {\n\t\t\tR_CoronasNew(C_WHITELIGHT, org);\n\t\t}\n\t\tif (amf_part_gunshot_type.value == 3) {\n\t\t\treturn;\n\t\t}\n\t}\n\telse if (amf_part_gunshot_type.value == 4) {\n\t\tcolor[0] = 255;\n\t\tcolor[1] = 80;\n\t\tcolor[2] = 10;\n\t\tcolor[3] = 128;\n\t\tif (amf_coronas.integer) {\n\t\t\tR_CoronasNew(C_GUNFLASH, org);\n\t\t}\n\t\tfor (a = 0; a < count*1.5; a++) {\n\t\t\tfor (i = 0; i < 3; i++) {\n\t\t\t\tdir[i] = (rand() % 700) - 300;\n\t\t\t}\n\t\t\tAddParticle(p_streakwave, org, 1, 1, 0.066*amf_part_trailtime.value, color, dir);\n\t\t}\n\t\treturn;\n\t}\n\telse if (amf_coronas.integer) {\n\t\tR_CoronasNew(C_GUNFLASH, org);\n\t}\n\n\tfor (a = 0; a < count; a++) {\n\t\tfor (i = 0; i < 3; ++i) {\n\t\t\tdir[i] = (rand() % 700) - 350;\n\t\t}\n\t\tif (amf_part_trailtype.value == 2) {\n\t\t\tAddParticle(p_smallspark, org, 1, 1, 1 * amf_part_trailtime.value, NULL, dir);\n\t\t}\n\t\telse {\n\t\t\tAddParticle(p_streak, org, 1, 1, 1 * amf_part_trailtime.value, color, dir);\n\t\t}\n\t}\n\n\tif (QMB_InWaterEffect(org, false)) {\n\t\tAddParticle(p_bubble, org, count / 5, 2.35, 2.5, NULL, zerodir);\n\t}\n}\n\n//VULT PARTICLES\nvoid VXNailhit(vec3_t org, float count)\n{\n\tcol_t color;\n\tvec3_t dir, neworg;\n\tint a, i;\n\tif (amf_nailtrail_plasma.integer) {\n\t\tcolor[0] = 10;\n\t\tcolor[1] = 80;\n\t\tcolor[2] = 255;\n\t\tcolor[3] = 128;\n\t\tAddParticle(p_fire, org, 15, 5, 0.5, color, zerodir);\n\t}\n\telse if (amf_part_spikes_type.integer == 2 || amf_part_spikes_type.integer == 3) {\n\t\tcolor[0] = 255;\n\t\tcolor[1] = 242;\n\t\tcolor[2] = 153;\n\t\tcolor[3] = 255;\n\t\tVectorClear(dir);\n\t\tfor (i = 0; i < 5; i++) {\n\t\t\tdir[2] = 0;\n\t\t\tfor (a = 0; a < 5; a++) {\n\t\t\t\tAngleVectors(dir, NULL, NULL, neworg);\n\t\t\t\tVectorMA(org, 40, neworg, neworg);\n\t\t\t\tAddParticle(p_sparkray, org, 1, 3, 1.5, color, neworg);\n\t\t\t\tdir[2] += 360 / 5;\n\t\t\t}\n\t\t\tdir[0] += 180 / 5;\n\t\t}\n\t\tif (amf_coronas.integer) {\n\t\t\tR_CoronasNew(C_WHITELIGHT, org);\n\t\t}\n\t\tif (amf_part_spikes_type.integer == 3) {\n\t\t\treturn;\n\t\t}\n\t}\n\telse if (amf_part_spikes_type.integer == 4) {\n\t\tcolor[0] = 255;\n\t\tcolor[1] = 80;\n\t\tcolor[2] = 10;\n\t\tcolor[3] = 128;\n\t\tif (amf_coronas.integer) {\n\t\t\tR_CoronasNew(C_GUNFLASH, org);\n\t\t}\n\t\tfor (a = 0; a<count*1.5; a++) {\n\t\t\tfor (i = 0; i < 3; i++) {\n\t\t\t\tdir[i] = (rand() % 700) - 300;\n\t\t\t}\n\t\t\tAddParticle(p_streakwave, org, 1, 1, 0.066*amf_part_trailtime.value, color, dir);\n\t\t}\n\t\treturn;\n\t}\n\telse {\n\t\tcolor[0] = 255;\n\t\tcolor[1] = 80;\n\t\tcolor[2] = 10;\n\t\tcolor[3] = 128;\n\t\tif (amf_coronas.integer) {\n\t\t\tR_CoronasNew(C_GUNFLASH, org);\n\t\t}\n\t}\n\n\tfor (a = 0; a < count; ++a) {\n\t\tfor (i = 0; i < 3; i++) {\n\t\t\tdir[i] = (rand() % 700) - 350;\n\t\t}\n\t\tif (amf_part_trailtype.value == 2) {\n\t\t\tif (amf_nailtrail_plasma.value) {\n\t\t\t\tAddParticle(p_smallspark, org, 1, 1, 1 * amf_part_trailtime.value, color, dir);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tAddParticle(p_smallspark, org, 1, 1, 1 * amf_part_trailtime.value, NULL, dir);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tAddParticle(p_streak, org, 1, 1, 1 * amf_part_trailtime.value, color, dir);\n\t\t}\n\t}\n\n\tif (QMB_InWaterEffect(org, false)) {\n\t\tAddParticle(p_bubble, org, count / 5, 2.35, 2.5, NULL, zerodir);\n\t}\n\n}\n\n//VULT PARTICLES\nvoid VXExplosion(vec3_t org)\n{\n\tcol_t color = { 255,100,25, 128 };\n\tvec3_t dir;\n\tint a, i;\n\tfloat theta;\n\tvec3_t angle;\n\n\tif (QMB_InWaterEffect(org, true)) {\n\t\tif (r_explosiontype.value != 9) {\n\t\t\tAddParticle(p_fire, org, 12, 14, 0.8, NULL, zerodir);\n\t\t}\n\t\tAddParticle(p_bubble, org, 12, 3.0, 2.5, NULL, zerodir);\n\t\tAddParticle(p_bubble, org, 8, 2.35, 2.5, NULL, zerodir);\n\t}\n\telse if (r_explosiontype.value != 9) {\n\t\tAddParticle(p_fire, org, 16, 18, 1, NULL, zerodir);\n\t}\n\n\tfor (a = 0; a < 120 * amf_part_explosion.value; a++) {\n\t\tfor (i = 0; i < 3; i++) {\n\t\t\tdir[i] = (rand() % 1500) - 750;\n\t\t}\n\t\tif (amf_part_trailtype.value == 2) {\n\t\t\tAddParticle(p_smallspark, org, 1, 1, 1 * amf_part_trailtime.value, NULL, dir);\n\t\t}\n\t\telse {\n\t\t\tAddParticle(p_streak, org, 1, 1, 1 * amf_part_trailtime.value, color, dir);\n\t\t}\n\t}\n\n\tif (amf_part_shockwaves.value) {\n\t\tif (amf_part_2dshockwaves.value) {\n\t\t\tAddParticle(p_2dshockwave, org, 1, 30, 0.5, NULL, vec3_origin);\n\t\t}\n\t\telse {\n\t\t\tangle[2] = 0;\n\t\t\tfor (theta = 0; theta < 2 * M_PI; theta += 2 * M_PI / 90) {\n\t\t\t\tangle[0] = cos(theta) * 500;\n\t\t\t\tangle[1] = sin(theta) * 500;\n\t\t\t\tAddParticle(p_shockwave, org, 1, 10, 0.5, NULL, angle);\n\t\t\t}\n\t\t}\n\t}\n}\n\n//VULT PARTICLES\nvoid VXBlobExplosion(vec3_t org)\n{\n\tfloat theta;\n\tcol_t color;\n\tvec3_t neworg, vel, dir;\n\tint a, i;\n\tcolor[0] = 60; color[1] = 100; color[2] = 240; color[3] = 128;\n\tfor (a = 0; a < 200 * amf_part_blobexplosion.value; a++) {\n\t\tfor (i = 0; i < 3; i++) {\n\t\t\tdir[i] = (rand() % 1500) - 750;\n\t\t}\n\t\tAddParticle(p_streakwave, org, 1, 1, 1 * amf_part_trailtime.value, color, dir);\n\t}\n\n\tcolor[0] = 90; color[1] = 47; color[2] = 207;\n\tAddParticle(p_fire, org, 15, 30, 1.4, color, zerodir);\n\n\tvel[2] = 0;\n\t//VULT PARTICLES\n\tif (amf_part_shockwaves.value) {\n\t\tif (amf_part_2dshockwaves.value) {\n\t\t\tvec3_t shockdir;\n\t\t\tint i;\n\t\t\tcolor[0] = (60 + (rand() & 15)); color[1] = (65 + (rand() & 15)); color[2] = (200 + (rand() & 15));\n\t\t\tfor (i = 0; i < 3; i++) {\n\t\t\t\tshockdir[i] = rand() % 360;\n\t\t\t}\n\t\t\tAddParticle(p_2dshockwave, org, 1, 30, 0.5, color, shockdir);\n\t\t\tfor (i = 0; i < 3; i++) {\n\t\t\t\tshockdir[i] = rand() % 360;\n\t\t\t}\n\t\t\tAddParticle(p_2dshockwave, org, 1, 30, 0.5, color, shockdir);\n\t\t\tfor (i = 0; i < 3; i++) {\n\t\t\t\tshockdir[i] = rand() % 360;\n\t\t\t}\n\t\t\tAddParticle(p_2dshockwave, org, 1, 30, 0.5, color, shockdir);\n\t\t}\n\t\telse {\n\t\t\tfor (theta = 0; theta < 2 * M_PI; theta += 2 * M_PI / 70) {\n\t\t\t\tcolor[0] = (60 + (rand() & 15)); color[1] = (65 + (rand() & 15)); color[2] = (200 + (rand() & 15));\n\n\t\t\t\tvel[0] = cos(theta) * 125;\n\t\t\t\tvel[1] = sin(theta) * 125;\n\t\t\t\tneworg[0] = org[0] + cos(theta) * 6;\n\t\t\t\tneworg[1] = org[1] + sin(theta) * 6;\n\t\t\t\tneworg[2] = org[2] + 0 - 10;\n\n\t\t\t\tAddParticle(p_shockwave, neworg, 1, 4, 0.8, color, vel);\n\t\t\t\tneworg[2] = org[2] + 0 + 10;\n\t\t\t\tAddParticle(p_shockwave, neworg, 1, 4, 0.8, color, vel);\n\n\t\t\t\tvel[0] *= 1.15;\n\t\t\t\tvel[1] *= 1.15;\n\t\t\t\tneworg[0] = org[0] + cos(theta) * 13;\n\t\t\t\tneworg[1] = org[1] + sin(theta) * 13;\n\t\t\t\tneworg[2] = org[2] + 0;\n\t\t\t\tAddParticle(p_shockwave, neworg, 1, 6, 1.0, color, vel);\n\t\t\t}\n\t\t}\n\t}\n}\n\n//VULT PARTICLES\nvoid VXTeleport(vec3_t org)\n{\n\tint i, j, a;\n\tvec3_t neworg, angle, dir;\n\tcol_t color;\n\n\tVectorSet(color, 140, 140, 255);\n\tVectorClear(angle);\n\tfor (i = 0; i < 5; i++) {\n\t\tangle[2] = 0;\n\t\tfor (j = 0; j < 5; j++) {\n\t\t\tAngleVectors(angle, NULL, NULL, neworg);\n\t\t\tVectorMA(org, 70, neworg, neworg);\n\t\t\tAddParticle(p_sparkray, org, 1, 6 + (i & 3), 5, color, neworg);\n\t\t\tangle[2] += 360 / 5;\n\t\t}\n\t\tangle[0] += 180 / 5;\n\t}\n\n\tcolor[0] = color[1] = color[2] = 255;\n\tcolor[3] = 128;\n\tfor (a = 0; a < 200 * amf_part_teleport.value; a++) {\n\t\tfor (i = 0; i < 3; i++)\n\t\t\tdir[i] = (rand() % 1500) - 750;\n\t\tAddParticle(p_streakwave, org, 1, 1, 1 * amf_part_trailtime.value, color, dir);\n\t}\n}\n\n//VULT - We need some kind of splatter effect, here it is\nvoid VXBlood(vec3_t org, float count)\n{\n\tcol_t color;\n\tint a, i;\n\tvec3_t start, end;\n\n\tif (amf_part_blood_type.integer) {\n\t\tVectorCopy(org, start);\n\n\t\tfor (i = 0; i < 15; i++) {\n\t\t\tVectorCopy(start, end);\n\n\t\t\tend[0] += rand() % 30 - 15;\n\t\t\tend[1] += rand() % 30 - 15;\n\t\t\tend[2] += rand() % 30 - 15;\n\n\t\t\tQMB_ParticleTrail(start, end, BLEEDING_TRAIL2);\n\t\t}\n\t}\n\n\t//blue\n\tif (amf_part_blood_color.value == 2) {\n\t\tcolor[0] = 55; color[1] = 102; color[2] = 255;\n\t}\n\t//green\n\telse if (amf_part_blood_color.value == 3) {\n\t\tcolor[0] = 80; color[1] = 255; color[2] = 80;\n\t}\n\t//red\n\telse {\n\t\tcolor[0] = 255; color[1] = 0; color[2] = 0;\n\t}\n\tcolor[3] = 255;\n\n\tfor (a = 0; a < count; a++) {\n\t\tAddParticle(p_vxblood, org, 1, 2.5, 2, color, 0);\n\t}\n}\n"
  },
  {
    "path": "src/r_particles_qmb_trails.c",
    "content": "/*\nCopyright (C) 2002-2003, Dr Labman, A. Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"vx_stuff.h\"\n#include \"r_particles_qmb.h\"\n#include \"pmove.h\"\n#include \"utils.h\"\n#include \"qmb_particles.h\"\n#include \"r_brushmodel.h\" // R_PointIsUnderwater only\n\n//#define qmb_end(cent) ((cent)->current.origin)\n#define qmb_end(cent) ((cent)->lerp_origin)\n\nextern cvar_t gl_part_tracer1_color, gl_part_tracer1_size, gl_part_tracer1_time;\nextern cvar_t gl_part_tracer2_color, gl_part_tracer2_size, gl_part_tracer2_time;\n\n__inline static void AddParticleTrailImpl(part_type_t type, vec3_t start, vec3_t end, float size, float time, col_t col, centity_t* cent, int trail_index)\n{\n\tbyte *color;\n\tint i, j, num_particles;\n\tfloat count = 0.0, theta = 0.0;\n\tvec3_t point, delta;\n\tparticle_t *p;\n\tparticle_type_t *pt;\n\t//VULT PARTICLES - for railtrail\n\tint loops = 0, entity_ref;\n\tvec3_t vf, vr, radial;\n\n\tif (!qmb_initialized) {\n\t\tSys_Error(\"QMB particle added without initialization\");\n\t}\n\n\tentity_ref = cent ? (cent - cl_entities) + 1 : 0;\n\tentity_ref = bound(0, entity_ref, CL_MAX_EDICTS);\n\n\tif (time == 0 || size == 0.0f) {\n\t\treturn;\n\t}\n\n\tif (type >= num_particletypes) {\n\t\tSys_Error(\"AddParticle: Invalid type (%d)\", type);\n\t}\n\n\tpt = &particle_types[particle_type_index[type]];\n\n\tVectorCopy(start, point);\n\tVectorSubtract(end, start, delta);\n\tif (pt->drawtype == pd_dynamictrail) {\n\t\tcount = 1;\n\t}\n\telse {\n\t\tfloat length = VectorLength(delta);\n\t\tif (length < 1) {\n\t\t\tgoto done;\n\t\t}\n\n\t\t// length > 140 was old limit in cl_ents.c\n\t\tif (length > 140 && entity_ref) {\n\t\t\tVectorCopy(end, point);\n\t\t\tgoto done;\n\t\t}\n\n\t\tswitch (type) {\n\t\t\tcase p_alphatrail:\n\t\t\tcase p_lavatrail:\n\t\t\tcase p_bleedspike:\n\t\t\tcase p_bubble:\n\t\t\t\tcount = length / 1.1;\n\t\t\t\tbreak;\n\t\t\tcase p_bubble2:\n\t\t\t\tcount = length / 5;\n\t\t\t\tbreak;\n\t\t\tcase p_trailpart:\n\t\t\t\tcount = length / 1.1;\n\t\t\t\tbreak;\n\t\t\tcase p_blood3:\n\t\t\t\tcount = length / 8;\n\t\t\t\tbreak;\n\t\t\tcase p_smoke:\n\t\t\tcase p_vxrocketsmoke:\n\t\t\t\tcount = length / 3.8;\n\t\t\t\tbreak;\n\t\t\tcase p_dpsmoke:\n\t\t\t\tcount = length / 2.5;\n\t\t\t\tbreak;\n\t\t\tcase p_dpfire:\n\t\t\t\tcount = length / 2.8;\n\t\t\t\tbreak;\n\t\t\t\t//VULT PARTICLES\n\t\t\tcase p_railtrail:\n\t\t\t\tcount = length * 1.6;\n\t\t\t\tif (!(loops = (int)length / 13.0)) {\n\t\t\t\t\tgoto done;\n\t\t\t\t}\n\t\t\t\tVectorScale(delta, 1 / length, vf);\n\t\t\t\tVectorVectors(vf, vr, vup);\n\t\t\t\tbreak;\n\t\t\tcase p_trailbleed:\n\t\t\t\tcount = length / 1.1;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tCom_DPrintf(\"AddParticleTrail: unexpected type %d\\n\", type);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!(num_particles = (int)count)) {\n\t\tgoto done;\n\t}\n\n\tif (!free_particles) {\n\t\t// we hit limit, don't try and draw from old position if this resolves\n\t\tVectorCopy(end, point);\n\t\tgoto done;\n\t}\n\n\tVectorScale(delta, 1.0 / num_particles, delta);\n\tfor (i = 0; i < num_particles && free_particles; i++) {\n\t\tcolor = col ? col : ColorForParticle(type);\n\t\tINIT_NEW_PARTICLE(pt, p, color, size, time);\n\t\tp->entity_ref = entity_ref;\n\t\tp->entity_trailindex = trail_index;\n\t\tif (entity_ref) {\n\t\t\tp->entity_trailnumber = cent->trail_number;\n\t\t}\n\n\t\tif (pt->drawtype == pd_dynamictrail) {\n\t\t\tVectorCopy(start, p->org);\n\t\t\tVectorCopy(end, p->endorg);\n\t\t}\n\t\telse {\n\t\t\tswitch (type) {\n\t\t\t\t//VULT PARTICLES\n\t\t\t\tcase p_trailpart:\n\t\t\t\tcase p_alphatrail:\n\t\t\t\t\tVectorCopy(point, p->org);\n\t\t\t\t\tVectorClear(p->vel);\n\t\t\t\t\tp->growth = -size / time;\n\t\t\t\t\tbreak;\n\t\t\t\tcase p_blood3:\n\t\t\t\t\tVectorCopy(point, p->org);\n\t\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\t\tp->org[j] += ((rand() & 15) - 8) / 8.0;\n\t\t\t\t\t}\n\t\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\t\tp->vel[j] = ((rand() & 15) - 8) / 2.0;\n\t\t\t\t\t}\n\t\t\t\t\tp->size = size * (rand() % 20) / 10.0;\n\t\t\t\t\tp->growth = 6;\n\t\t\t\t\tbreak;\n\t\t\t\tcase p_smoke:\n\t\t\t\t\tVectorCopy(point, p->org);\n\t\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\t\tp->org[j] += ((rand() & 7) - 4) / 8.0;\n\t\t\t\t\t}\n\t\t\t\t\tp->vel[0] = p->vel[1] = 0;\n\t\t\t\t\tp->vel[2] = rand() & 3;\n\t\t\t\t\tp->growth = 4.5;\n\t\t\t\t\tp->rotspeed = (rand() & 63) + 96;\n\t\t\t\t\tbreak;\n\t\t\t\tcase p_dpsmoke:\n\t\t\t\t\tVectorCopy(point, p->org);\n\t\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\t\tp->vel[j] = (rand() % 10) - 5;\n\t\t\t\t\t}\n\t\t\t\t\tp->growth = 3;\n\t\t\t\t\tp->rotspeed = (rand() & 63) + 96;\n\t\t\t\t\tbreak;\n\t\t\t\tcase p_dpfire:\n\t\t\t\t\tVectorCopy(point, p->org);\n\t\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\t\tp->vel[j] = (rand() % 40) - 20;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t\t//VULT PARTICLES\n\t\t\t\tcase p_railtrail:\n\t\t\t\t\ttheta += loops * 2 * M_PI / count;\n\t\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\t\tradial[j] = vr[j] * cos(theta) + vup[j] * sin(theta);\n\t\t\t\t\t}\n\t\t\t\t\tVectorMA(point, 2.6, radial, p->org);\n\t\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\t\tp->vel[j] = radial[j] * 5;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t\t//VULT PARTICLES\n\t\t\t\tcase p_bubble:\n\t\t\t\tcase p_bubble2:\n\t\t\t\t\tVectorCopy(point, p->org);\n\t\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\t\tp->vel[j] = (rand() % 10) - 5;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t\t//VULT PARTICLES\n\t\t\t\tcase p_lavatrail:\n\t\t\t\t\tVectorCopy(point, p->org);\n\t\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\t\tp->org[j] += ((rand() & 7) - 4);\n\t\t\t\t\t}\n\t\t\t\t\tp->vel[0] = p->vel[1] = 0;\n\t\t\t\t\tp->vel[2] = rand() & 3;\n\t\t\t\t\tbreak;\n\t\t\t\t\t//VULT PARTICLES\n\t\t\t\tcase p_vxrocketsmoke:\n\t\t\t\t\tVectorCopy(point, p->org);\n\t\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\t\tp->vel[j] = (rand() % 8) - 4;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t\t//VULT PARTICLES\n\t\t\t\tcase p_trailbleed:\n\t\t\t\t\tp->size = (rand() % (int)size);\n\t\t\t\t\tp->growth = -rand() % 5;\n\t\t\t\t\tVectorCopy(point, p->org);\n\t\t\t\t\tbreak;\n\t\t\t\t\t//VULT PARTICLES\n\t\t\t\tcase p_bleedspike:\n\t\t\t\t\tsize = 9 * size / 10;\n\t\t\t\t\tVectorCopy(point, p->org);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\t//assert(!\"AddParticleTrail: unexpected type\"); -> hexum - FIXME not all types are handled, seems to work ok though\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tVectorAdd(point, delta, point);\n\t}\ndone:\n\tif (cent) {\n\t\tVectorCopy(point, cent->trails[trail_index].stop);\n\t}\n}\n\n__inline static void AddEntityParticleTrail(part_type_t type, centity_t* cent, int trail_index, float size, float time, col_t color)\n{\n\tAddParticleTrailImpl(type, cent->trails[trail_index].stop, qmb_end(cent), size, time, color, cent, trail_index);\n}\n\n__inline static void AddParticleTrail(part_type_t type, vec3_t start, vec3_t stop, float size, float time, col_t color)\n{\n\tAddParticleTrailImpl(type, start, stop, size, time, color, NULL, 0);\n}\n\n//VULT PARTICLES\n//VULT - Draw a thin white trail that could be used to represent motion for just about anything\nvoid ParticleAlphaTrail(centity_t* cent, float size, float life)\n{\n\tif (amf_underwater_trails.integer && R_PointIsUnderwater(cent->trails[0].stop)) {\n\t\tAddEntityParticleTrail(amf_nailtrail_water.integer ? p_bubble2 : p_bubble, cent, 0, 1.5, 0.825, NULL);\n\t}\n\telse {\n\t\tcol_t color = { 10, 10, 10, 0 };\n\n\t\tAddEntityParticleTrail(p_alphatrail, cent, 0, size, life, color);\n\t}\n}\n\n//MEAG: Draw thin trail for nails etc\nvoid ParticleNailTrail(centity_t* client_ent, float size, float life)\n{\n\tif (amf_underwater_trails.integer && R_PointIsUnderwater(client_ent->trails[0].stop)) {\n\t\tAddEntityParticleTrail(amf_nailtrail_water.integer ? p_bubble2 : p_bubble, client_ent, 0, 1.5, 0.825, NULL);\n\t}\n\telse {\n\t\tif (particle_time - client_ent->trails[0].lasttime < MIN_ENTITY_PARTICLE_FRAMETIME) {\n\t\t\treturn;\n\t\t}\n\t\tclient_ent->trails[0].lasttime = particle_time;\n\t\tAddEntityParticleTrail(p_nailtrail, client_ent, 0, size, life, NULL);\n\t}\n}\n\n//VULT PARTICLES\nvoid FireballTrail(centity_t* cent, byte col[3], float size, float life)\n{\n\tcol_t color;\n\n\tcolor[0] = col[0];\n\tcolor[1] = col[1];\n\tcolor[2] = col[2];\n\tcolor[3] = 255;\n\n\t//head\n\tAddEntityParticleTrail(p_trailpart, cent, 0, size * 7, 0.15, color);\n\n\t//head-white part\n\tcolor[0] = 255; color[1] = 255; color[2] = 255;\n\tAddEntityParticleTrail(p_trailpart, cent, 1, size * 5, 0.15, color);\n\n\t//medium trail\n\tcolor[0] = col[0];\n\tcolor[1] = col[1];\n\tcolor[2] = col[2];\n\tAddEntityParticleTrail(p_trailpart, cent, 2, size * 3, life, color);\n}\n\n//VULT PARTICLES\nvoid FireballTrailWave(centity_t* cent, byte col[3], float size, float life, vec3_t angle)\n{\n\tint i, j;\n\tvec3_t dir, vec;\n\tcol_t color;\n\tcolor[0] = col[0];\n\tcolor[1] = col[1];\n\tcolor[2] = col[2];\n\tcolor[3] = 255;\n\n\tAngleVectors(angle, vec, NULL, NULL);\n\tvec[2] *= -1;\n\tFireballTrail(cent, col, size, life);\n\tif (cent->trails[2].lasttime == particle_time) {\n\t\tfor (i = 0; i < 3; i++) {\n\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\tdir[j] = vec[j] * -600 + ((rand() % 100) - 50);\n\t\t\t}\n\t\t\tAddParticle(p_streakwave, qmb_end(cent), 1, 1, 1, color, dir);\n\t\t}\n\t}\n}\n\n//VULT PARTICLES\nvoid FuelRodGunTrail(centity_t* cent)\n{\n\tcol_t color;\n\tint i, j;\n\tvec3_t dir, vec;\n\tcolor[3] = 255;\n\n\tcolor[0] = 0; color[1] = 255; color[2] = 0;\n\tAddEntityParticleTrail(p_trailpart, cent, 0, 15, 0.2, color);\n\tcolor[0] = 255; color[1] = 255; color[2] = 255;\n\tAddEntityParticleTrail(p_trailpart, cent, 1, 10, 0.2, color);\n\tcolor[0] = 0; color[1] = 128; color[2] = 0;\n\tAddEntityParticleTrail(p_trailpart, cent, 2, 10, 0.5, color);\n\tcolor[0] = 0; color[1] = 22; color[2] = 0;\n\tAddEntityParticleTrail(p_trailpart, cent, 3, 2, 3, color);\n\tif (cent->trails[3].lasttime == particle_time) {\n\t\tAngleVectors(cent->current.angles, vec, NULL, NULL);\n\t\tcolor[0] = 75; color[1] = 255; color[2] = 75;\n\t\tfor (i = 0; i < 3; i++) {\n\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\tdir[j] = vec[j] * -300 + ((rand() % 100) - 50);\n\t\t\t}\n\t\t\tdir[2] = dir[2] + 60;\n\t\t\tif (amf_part_trailtype.integer == 2) {\n\t\t\t\tAddParticle(p_smallspark, qmb_end(cent), 1, 1, 2, color, dir);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tAddParticle(p_streak, qmb_end(cent), 1, 1, 2, color, dir);\n\t\t\t}\n\t\t}\n\t}\n}\n\n//VULT PARTICLES\n//VULT - These trails were my initial motivation behind AMFQUAKE.\nvoid VX_ParticleTrail(vec3_t start, vec3_t end, float size, float time, col_t color)\n{\n\tvec3_t\t\tvec;\n\tfloat\t\tlen;\n\tstatic vec3_t zerodir = { 22, 22, 22 };\n\n\ttime *= amf_part_traillen.value;\n\tif (!time) {\n\t\treturn;\n\t}\n\n\tif (amf_part_fulldetail.integer) {\n\t\tVectorSubtract(end, start, vec);\n\t\tlen = VectorNormalize(vec);\n\n\t\twhile (len > 0) {\n\t\t\t//ADD PARTICLE HERE\n\t\t\tAddParticle(p_streaktrail, start, 1, size, time, color, zerodir);\n\t\t\tlen--;\n\t\t\tVectorAdd(start, vec, start);\n\t\t}\n\t}\n\tAddParticle(p_streaktrail, start, 1, size, time, color, end);\n}\n\nvoid QMB_ParticleTrail(vec3_t start, vec3_t end, trail_type_t type)\n{\n\tcol_t color;\n\n\tswitch (type) {\n\t\tcase GRENADE_TRAIL:\n\t\t\t//VULT PARTICLES\n\t\t\tif (amf_underwater_trails.integer && R_PointIsUnderwater(start)) {\n\t\t\t\tAddParticleTrail(p_bubble, start, end, 1.8, 0.825, NULL);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tAddParticleTrail(p_smoke, start, end, 1.45, 0.825, NULL);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase BLOOD_TRAIL:\n\t\tcase BIG_BLOOD_TRAIL:\n\t\t\tAddParticleTrail(p_blood3, start, end, type == BLOOD_TRAIL ? 1.35 : 2.4, 2, NULL);\n\t\t\tbreak;\n\t\tcase TRACER1_TRAIL:\n\t\t\tAddParticleTrail(p_trailpart, start, end, gl_part_tracer1_size.value, gl_part_tracer1_time.value, gl_part_tracer1_color.color);\n\t\t\tbreak;\n\t\tcase TRACER2_TRAIL:\n\t\t\tAddParticleTrail(p_trailpart, start, end, gl_part_tracer2_size.value, gl_part_tracer2_time.value, gl_part_tracer2_color.color);\n\t\t\tbreak;\n\t\tcase VOOR_TRAIL:\n\t\t\tcolor[0] = 77; color[1] = 0; color[2] = 255; color[3] = 255;\n\t\t\tAddParticleTrail(p_trailpart, start, end, 3.75, 0.5, color);\n\t\t\tbreak;\n\t\tcase ALT_ROCKET_TRAIL:\n\t\t\tif (amf_underwater_trails.integer && R_PointIsUnderwater(start)) {\n\t\t\t\tAddParticleTrail(p_bubble, start, end, 1.8, 0.825, NULL);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tAddParticleTrail(p_dpfire, start, end, 3, 0.26, NULL);\n\t\t\t\tAddParticleTrail(p_dpsmoke, start, end, 3, 0.825, NULL);\n\t\t\t}\n\t\t\tbreak;\n\t\t\t//VULT TRAILS\n\t\tcase RAIL_TRAIL:\n\t\tcase RAIL_TRAIL2:\n\t\t\tcolor[0] = 255; color[1] = 255; color[2] = 255; color[3] = 255;\n\t\t\t//VULT PARTICLES\n\t\t\tAddParticleTrail(p_alphatrail, start, end, 2, 0.525, color);\n\t\t\tif (type == RAIL_TRAIL2) {\n\t\t\t\tcolor[0] = 255;\n\t\t\t\tcolor[1] = 0;\n\t\t\t\tcolor[2] = 0;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcolor[0] = 0;\n\t\t\t\tcolor[1] = 0;\n\t\t\t\tcolor[2] = 255;\n\t\t\t}\n\t\t\tAddParticleTrail(p_railtrail, start, end, 1.3, 0.525, color);\n\t\t\tbreak;\n\t\t\t//VULT TRAILS\n\t\tcase LAVA_TRAIL:\n\t\t\tif (amf_underwater_trails.integer && R_PointIsUnderwater(start)) {\n\t\t\t\tAddParticleTrail(p_bubble, start, end, 1.8, 0.825, NULL);\n\t\t\t\tcolor[0] = 25;\n\t\t\t\tcolor[1] = 102;\n\t\t\t\tcolor[2] = 255;\n\t\t\t\tcolor[3] = 255;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcolor[0] = 255;\n\t\t\t\tcolor[1] = 102;\n\t\t\t\tcolor[2] = 25;\n\t\t\t\tcolor[3] = 255;\n\t\t\t}\n\t\t\tAddParticleTrail(p_lavatrail, start, end, 5, 1, color);\n\t\t\tbreak;\n\t\t\t//VULT TRAILS\n\t\tcase AMF_ROCKET_TRAIL:\n\t\t\tif (amf_underwater_trails.integer && R_PointIsUnderwater(start)) {\n\t\t\t\tAddParticleTrail(p_bubble, start, end, 1.8, 0.825, NULL);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcolor[0] = 128; color[1] = 128; color[2] = 128; color[3] = 255;\n\t\t\t\tAddParticleTrail(p_alphatrail, start, end, 2, 0.6, color);\n\t\t\t\tAddParticleTrail(p_vxrocketsmoke, start, end, 3, 0.5, color);\n\n\t\t\t\tcolor[0] = 128; color[1] = 56; color[2] = 9; color[3] = 255;\n\t\t\t\tAddParticleTrail(p_trailpart, start, end, 4, 0.2, color);\n\t\t\t}\n\t\t\tbreak;\n\t\t\t//VULT PARTICLES\n\t\tcase BLEEDING_TRAIL:\n\t\tcase BLEEDING_TRAIL2:\n\t\t\tif (amf_part_blood_color.value == 2) {\n\t\t\t\tcolor[0] = 55; color[1] = 102; color[2] = 255;\n\t\t\t}\n\t\t\t//green\n\t\t\telse if (amf_part_blood_color.value == 3) {\n\t\t\t\tcolor[0] = 80; color[1] = 255; color[2] = 80;\n\t\t\t}\n\t\t\t//red\n\t\t\telse {\n\t\t\t\tcolor[0] = 128; color[1] = 0; color[2] = 0;\n\t\t\t}\n\t\t\tcolor[3] = 255;\n\t\t\tAddParticleTrail(type == BLEEDING_TRAIL ? p_trailbleed : p_bleedspike, start, end, 4, type == BLEEDING_TRAIL ? 0.5 : 0.2, color);\n\t\t\tbreak;\n\t\tcase ROCKET_TRAIL:\n\t\tdefault:\n\t\t\tcolor[0] = 255; color[1] = 56; color[2] = 9; color[3] = 255;\n\t\t\tAddParticleTrail(p_trailpart, start, end, 6.2, 0.31, color);\n\t\t\tAddParticleTrail(p_smoke, start, end, 1.8, 0.825, NULL);\n\t\t\tbreak;\n\t}\n}\n\nvoid QMB_EntityParticleTrail(centity_t* cent, trail_type_t type)\n{\n\tcol_t color;\n\n\tswitch (type) {\n\t\tcase GRENADE_TRAIL:\n\t\t\t//VULT PARTICLES\n\t\t\tif (amf_underwater_trails.integer && R_PointIsUnderwater(cent->trails[0].stop)) {\n\t\t\t\tAddEntityParticleTrail(p_bubble, cent, 0, 1.8, 0.825, NULL);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tAddEntityParticleTrail(p_smoke, cent, 0, 1.45, 0.825, NULL);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase BLOOD_TRAIL:\n\t\tcase BIG_BLOOD_TRAIL:\n\t\t\tAddEntityParticleTrail(p_blood3, cent, 0, type == BLOOD_TRAIL ? 1.35 : 2.4, 2, NULL);\n\t\t\tbreak;\n\t\tcase TRACER1_TRAIL:\n\t\t\tAddEntityParticleTrail(p_trailpart, cent, 0, gl_part_tracer1_size.value, gl_part_tracer1_time.value, gl_part_tracer1_color.color);\n\t\t\tbreak;\n\t\tcase TRACER2_TRAIL:\n\t\t\tAddEntityParticleTrail(p_trailpart, cent, 0, gl_part_tracer2_size.value, gl_part_tracer2_time.value, gl_part_tracer2_color.color);\n\t\t\tbreak;\n\t\tcase VOOR_TRAIL:\n\t\t\tcolor[0] = 77; color[1] = 0; color[2] = 255; color[3] = 255;\n\t\t\tAddEntityParticleTrail(p_trailpart, cent, 0, 3.75, 0.5, color);\n\t\t\tbreak;\n\t\tcase ALT_ROCKET_TRAIL:\n\t\t\tif (amf_underwater_trails.integer && R_PointIsUnderwater(qmb_end(cent))) {\n\t\t\t\tAddEntityParticleTrail(p_bubble, cent, 0, 1.8, 0.825, NULL);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tAddEntityParticleTrail(p_dpfire, cent, 0, 3, 0.26, NULL);\n\t\t\t\tAddEntityParticleTrail(p_dpsmoke, cent, 1, 3, 0.825, NULL);\n\t\t\t}\n\t\t\tbreak;\n\t\t\t//VULT TRAILS\n\t\tcase RAIL_TRAIL:\n\t\tcase RAIL_TRAIL2:\n\t\t\tcolor[0] = 255; color[1] = 255; color[2] = 255; color[3] = 255;\n\t\t\t//VULT PARTICLES\n\t\t\tAddEntityParticleTrail(p_alphatrail, cent, 0, 2, 0.525, color);\n\t\t\tif (type == RAIL_TRAIL2) {\n\t\t\t\tcolor[0] = 255;\n\t\t\t\tcolor[1] = 0;\n\t\t\t\tcolor[2] = 0;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcolor[0] = 0;\n\t\t\t\tcolor[1] = 0;\n\t\t\t\tcolor[2] = 255;\n\t\t\t}\n\t\t\tAddEntityParticleTrail(p_railtrail, cent, 1, 1.3, 0.525, color);\n\t\t\tbreak;\n\t\t\t//VULT TRAILS\n\t\tcase LAVA_TRAIL:\n\t\t\tif (amf_underwater_trails.integer && R_PointIsUnderwater(qmb_end(cent))) {\n\t\t\t\tAddEntityParticleTrail(p_bubble, cent, 0, 1.8, 0.825, NULL);\n\t\t\t\tcolor[0] = 25;\n\t\t\t\tcolor[1] = 102;\n\t\t\t\tcolor[2] = 255;\n\t\t\t\tcolor[3] = 255;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcolor[0] = 255;\n\t\t\t\tcolor[1] = 102;\n\t\t\t\tcolor[2] = 25;\n\t\t\t\tcolor[3] = 255;\n\t\t\t}\n\t\t\tAddEntityParticleTrail(p_lavatrail, cent, 1, 5, 1, color);\n\t\t\tbreak;\n\t\t\t//VULT TRAILS\n\t\tcase AMF_ROCKET_TRAIL:\n\t\t\tif (amf_underwater_trails.integer && R_PointIsUnderwater(qmb_end(cent))) {\n\t\t\t\tAddEntityParticleTrail(p_bubble, cent, 0, 1.8, 0.825, NULL);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcolor[0] = 128; color[1] = 128; color[2] = 128; color[3] = 255;\n\t\t\t\tAddEntityParticleTrail(p_alphatrail, cent, 0, 2, 0.6, color);\n\t\t\t\tAddEntityParticleTrail(p_vxrocketsmoke, cent, 1, 3, 0.5, color);\n\n\t\t\t\tcolor[0] = 128; color[1] = 56; color[2] = 9; color[3] = 255;\n\t\t\t\tAddEntityParticleTrail(p_trailpart, cent, 2, 4, 0.2, color);\n\t\t\t}\n\t\t\tbreak;\n\t\t\t//VULT PARTICLES\n\t\tcase BLEEDING_TRAIL:\n\t\tcase BLEEDING_TRAIL2:\n\t\t\tif (amf_part_blood_color.value == 2) {\n\t\t\t\tcolor[0] = 55; color[1] = 102; color[2] = 255;\n\t\t\t}\n\t\t\t//green\n\t\t\telse if (amf_part_blood_color.value == 3) {\n\t\t\t\tcolor[0] = 80; color[1] = 255; color[2] = 80;\n\t\t\t}\n\t\t\t//red\n\t\t\telse {\n\t\t\t\tcolor[0] = 128; color[1] = 0; color[2] = 0;\n\t\t\t}\n\t\t\tcolor[3] = 255;\n\t\t\tAddEntityParticleTrail(type == BLEEDING_TRAIL ? p_trailbleed : p_bleedspike, cent, 0, 4, type == BLEEDING_TRAIL ? 0.5 : 0.2, color);\n\t\t\tbreak;\n\t\tcase ROCKET_TRAIL:\n\t\tdefault:\n\t\t\tcolor[0] = 255; color[1] = 56; color[2] = 9; color[3] = 255;\n\t\t\tAddEntityParticleTrail(p_trailpart, cent, 0, 6.2, 0.31, color);\n\t\t\tAddEntityParticleTrail(p_smoke, cent, 1, 1.8, 0.825, NULL);\n\t\t\tbreak;\n\t}\n}\n\n// deurk: QMB version of ported zquake rail trail\nvoid QMB_ParticleRailTrail(vec3_t start, vec3_t end, int color_num)\n{\n\tcol_t color;\n\n\tcolor[0] = 255; color[1] = 255; color[2] = 255; color[3] = 255;\n\tAddParticleTrail(p_alphatrail, start, end, 0.5, 0.5, color);\n\tswitch (color_num) {\n\t\tcase 180:\n\t\t\tcolor[0] = 91; color[1] = 74; color[2] = 56;\n\t\t\tbreak;\n\t\tcase 35:\n\t\t\tcolor[0] = 121; color[1] = 118; color[2] = 185;\n\t\t\tbreak;\n\t\tcase 224:\n\t\t\tcolor[0] = 206; color[1] = 0; color[2] = 0;\n\t\t\tbreak;\n\t\tcase 133:\n\t\t\tcolor[0] = 107; color[1] = 70; color[2] = 88;\n\t\t\tbreak;\n\t\tcase 192:\n\t\t\tcolor[0] = 255; color[1] = 242; color[2] = 32;\n\t\t\tbreak;\n\t\tcase 6:\n\t\t\tcolor[0] = 200; color[1] = 200; color[2] = 200;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tcolor[0] = 0; color[1] = 0; color[2] = 255;\n\t\t\tbreak;\n\t}\n\tAddParticleTrail(p_alphatrail, start, end, 2, 1, color);\n}\n"
  },
  {
    "path": "src/r_performance.c",
    "content": "\n/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// r_surf.c: surface-related refresh code\n\n#include \"quakedef.h\"\n#include \"r_framestats.h\"\n#include \"r_local.h\"\n#include \"r_renderer.h\"\n#include \"r_trace.h\"\n\nstatic cvar_t* framestats_shown;\n\nvoid R_PerformanceBeginFrame(void)\n{\n\tqbool frameStatsVisible;\n\n\tif (!framestats_shown) {\n\t\tframestats_shown = Cvar_Find(\"hud_framestats_show\");\n\t}\n\tframeStatsVisible = framestats_shown && framestats_shown->integer;\n\n\tif (r_speeds.integer) {\n\t\trenderer.EnsureFinished();\n\t}\n\n\tR_TraceResetRegion(true);\n\n\tmemcpy(&prevFrameStats, &frameStats, sizeof(prevFrameStats));\n\tmemset(&frameStats, 0, sizeof(frameStats));\n\tif (r_speeds.integer || frameStatsVisible || cls.timedemo) {\n\t\tframeStats.start_time = Sys_DoubleTime();\n\t\tR_TraceAPI(\"[timedemo] start-frame\");\n\t}\n\tframeStats.hotloop = true;\n}\n\nvoid R_PerformanceEndFrame(void)\n{\n\tqbool frameStatsVisible;\n\n\tif (!framestats_shown) {\n\t\tframestats_shown = Cvar_Find(\"hud_framestats_show\");\n\t}\n\tframeStatsVisible = framestats_shown && framestats_shown->integer;\n\tframeStats.hotloop = false;\n\n\tif (r_speeds.integer || frameStatsVisible || cls.timedemo) {\n\t\tframeStats.end_time = Sys_DoubleTime();\n\n\t\tif (cls.timedemo && cls.td_starttime) {\n\t\t\tint ms = (int)ceil((frameStats.end_time - frameStats.start_time) * 10000.0);\n\n\t\t\tR_TraceAPI(\"[timedemo] frametime: %dms\", ms);\n\t\t\tms = bound(0, ms, sizeof(cls.td_frametime_stats) / sizeof(cls.td_frametime_stats[0]) - 1);\n\n\t\t\tif (ms > cls.td_frametime_max) {\n\t\t\t\tcls.td_frametime_max = ms;\n\t\t\t\tcls.td_frametime_max_frame = cls.framecount - cls.td_startframe - 1;\n\t\t\t\tR_TraceAPI(\"[timedemo] worst-frametime-set\");\n\t\t\t}\n\n\t\t\t++cls.td_frametime_stats[ms];\n\t\t}\n\n\t\tif (r_speeds.integer) {\n\t\t\tdouble time = frameStats.end_time - frameStats.start_time;\n\n\t\t\tPrint_flags[Print_current] |= PR_TR_SKIP;\n\t\t\tif (cl.standby || com_serveractive) {\n\t\t\t\tCom_Printf(\"%5.2f ms %4i wpoly, %4i epoly, %4i texbinds\\n\", (time * 1000), frameStats.classic.polycount[polyTypeWorldModel], frameStats.classic.polycount[polyTypeAliasModel], frameStats.texture_binds);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCom_Printf(\"%5.2f ms %4i wpoly\\n\", (time * 1000), frameStats.classic.polycount[polyTypeWorldModel]);\n\t\t\t}\n\t\t}\n\t}\n}\n\n\n"
  },
  {
    "path": "src/r_performance.h",
    "content": "\n#ifndef EZQUAKE_R_PERFORMANCE_HEADER\n#define EZQUAKE_R_PERFORMANCE_HEADER\n\nvoid R_PerformanceBeginFrame(void);\nvoid R_PerformanceEndFrame(void);\n\nvoid GLC_TimeRefresh(void);\nvoid GLM_TimeRefresh(void);\n\n#endif // #ifndef EZQUAKE_R_PERFORMANCE_HEADER"
  },
  {
    "path": "src/r_program.h",
    "content": "\n#ifndef EZQUAKE_R_PROGRAM_HEADER\n#define EZQUAKE_R_PROGRAM_HEADER\n\ntypedef enum {\n\tr_program_none,\n\n\tr_program_aliasmodel,\n\tr_program_brushmodel,\n\tr_program_sprite3d,\n\tr_program_hud_images,\n\tr_program_hud_circles,\n\tr_program_post_process,\n\n\tr_program_post_process_glc,\n\tr_program_sky_glc,\n\tr_program_turb_glc,\n\tr_program_caustics_glc,\n\tr_program_aliasmodel_std_glc,\n\tr_program_aliasmodel_shell_glc,\n\tr_program_aliasmodel_shadow_glc,\n\tr_program_aliasmodel_outline_glc,\n\tr_program_world_drawflat_glc,\n\tr_program_world_textured_glc,\n\tr_program_world_secondpass_glc,\n\tr_program_sprites_glc,\n\tr_program_hud_images_glc,\n\n\tr_program_lightmap_compute,\n\n\tr_program_fx_world_geometry,\n\tr_program_brushmodel_alphatested,\n\tr_program_simple,\n\tr_program_simple3d,\n\n\tr_program_count\n} r_program_id;\n\ntypedef enum {\n\tr_program_uniform_aliasmodel_drawmode,\n\tr_program_uniform_brushmodel_outlines,\n\tr_program_uniform_brushmodel_sampler,\n\tr_program_uniform_sprite3d_alpha_test,\n\tr_program_uniform_hud_circle_matrix,\n\tr_program_uniform_hud_circle_color,\n\tr_program_uniform_post_process_glc_gamma,\n\tr_program_uniform_post_process_glc_base,\n\tr_program_uniform_post_process_glc_overlay,\n\tr_program_uniform_post_process_glc_v_blend,\n\tr_program_uniform_post_process_glc_contrast,\n\tr_program_uniform_post_process_glc_r_inv_width,\n\tr_program_uniform_post_process_glc_r_inv_height,\n\tr_program_uniform_sky_glc_cameraPosition,\n\tr_program_uniform_sky_glc_speedscale,\n\tr_program_uniform_sky_glc_speedscale2,\n\tr_program_uniform_sky_glc_skyTex,\n\tr_program_uniform_sky_glc_skyWind,\n\tr_program_uniform_sky_glc_skyDomeTex,\n\tr_program_uniform_sky_glc_skyDomeCloudTex,\n\tr_program_uniform_turb_glc_texSampler,\n\tr_program_uniform_turb_glc_time,\n\tr_program_uniform_caustics_glc_texSampler,\n\tr_program_uniform_caustics_glc_time,\n\tr_program_uniform_aliasmodel_std_glc_angleVector,\n\tr_program_uniform_aliasmodel_std_glc_shadelight,\n\tr_program_uniform_aliasmodel_std_glc_ambientlight,\n\tr_program_uniform_aliasmodel_std_glc_texSampler,\n\tr_program_uniform_aliasmodel_std_glc_fsTextureEnabled,\n\tr_program_uniform_aliasmodel_std_glc_fsMinLumaMix,\n\tr_program_uniform_aliasmodel_std_glc_time,\n\tr_program_uniform_aliasmodel_std_glc_fsCausticEffects,\n\tr_program_uniform_aliasmodel_std_glc_lerpFraction,\n\tr_program_uniform_aliasmodel_std_glc_causticsSampler,\n\tr_program_uniform_aliasmodel_shell_glc_fsBaseColor1,\n\tr_program_uniform_aliasmodel_shell_glc_fsBaseColor2,\n\tr_program_uniform_aliasmodel_shell_glc_texSampler,\n\tr_program_uniform_aliasmodel_shell_glc_lerpFraction,\n\tr_program_uniform_aliasmodel_shell_glc_scroll,\n\tr_program_uniform_aliasmodel_shadow_glc_lerpFraction,\n\tr_program_uniform_aliasmodel_shadow_glc_shadevector,\n\tr_program_uniform_aliasmodel_shadow_glc_lheight,\n\tr_program_uniform_world_drawflat_glc_wallcolor,\n\tr_program_uniform_world_drawflat_glc_floorcolor,\n\tr_program_uniform_world_drawflat_glc_skycolor,\n\tr_program_uniform_world_drawflat_glc_watercolor,\n\tr_program_uniform_world_drawflat_glc_slimecolor,\n\tr_program_uniform_world_drawflat_glc_lavacolor,\n\tr_program_uniform_world_drawflat_glc_telecolor,\n\tr_program_uniform_world_textured_glc_lightmapSampler,\n\tr_program_uniform_world_textured_glc_texSampler,\n\tr_program_uniform_world_textured_glc_lumaSampler,\n\tr_program_uniform_world_textured_glc_causticSampler,\n\tr_program_uniform_world_textured_glc_detailSampler,\n\tr_program_uniform_world_textured_glc_time,\n\tr_program_uniform_world_textured_glc_lumaScale,\n\tr_program_uniform_world_textured_glc_fbScale,\n\tr_program_uniform_world_textured_glc_r_floorcolor,\n\tr_program_uniform_world_textured_glc_r_wallcolor,\n\tr_program_uniform_sprites_glc_materialSampler,\n\tr_program_uniform_sprites_glc_alphaThreshold,\n\tr_program_uniform_hud_images_glc_primarySampler,\n\tr_program_uniform_hud_images_glc_secondarySampler,\n\tr_program_uniform_aliasmodel_outline_glc_lerpFraction,\n\tr_program_uniform_aliasmodel_outline_glc_outlineScale,\n\tr_program_uniform_brushmodel_alphatested_outlines,\n\tr_program_uniform_brushmodel_alphatested_sampler,\n\tr_program_uniform_turb_glc_alpha,\n\tr_program_uniform_turb_glc_color,\n\tr_program_uniform_simple_color,\n\tr_program_uniform_world_textures_glc_texture_multiplier,\n\tr_program_uniform_simple3d_color,\n\tr_program_uniform_lighting_firstLightmap,\n\tr_program_uniform_sky_glc_fog_skyFogMix,\n\tr_program_uniform_outline_color,\n\tr_program_uniform_outline_depth_threshold,\n\tr_program_uniform_outline_normal_threshold,\n\tr_program_uniform_outline_scale,\n\tr_program_uniform_aliasmodel_outline_color_model,\n\tr_program_uniform_aliasmodel_outline_color_team,\n\tr_program_uniform_aliasmodel_outline_color_enemy,\n\tr_program_uniform_aliasmodel_outline_use_player_color,\n\tr_program_uniform_aliasmodel_outline_scale,\n\t// Sampler uniforms for GL < 4.2 (no layout(binding=N))\n\tr_program_uniform_brushmodel_detailtex,\n\tr_program_uniform_brushmodel_causticstex,\n\tr_program_uniform_brushmodel_skytex,\n\tr_program_uniform_brushmodel_skydometex,\n\tr_program_uniform_brushmodel_skydomecloudtex,\n\tr_program_uniform_brushmodel_lightmaptex,\n\tr_program_uniform_brushmodel_materialtex,\n\tr_program_uniform_brushmodel_at_detailtex,\n\tr_program_uniform_brushmodel_at_causticstex,\n\tr_program_uniform_brushmodel_at_skytex,\n\tr_program_uniform_brushmodel_at_skydometex,\n\tr_program_uniform_brushmodel_at_skydomecloudtex,\n\tr_program_uniform_brushmodel_at_lightmaptex,\n\tr_program_uniform_brushmodel_at_materialtex,\n\tr_program_uniform_aliasmodel_causticstex,\n\tr_program_uniform_aliasmodel_samplers,\n\tr_program_uniform_sprites_materialtex,\n\tr_program_uniform_postprocess_base,\n\tr_program_uniform_postprocess_overlay,\n\tr_program_uniform_fxworldgeometry_normaltex,\n\tr_program_uniform_hudimage_tex,\n\tr_program_uniform_aliasmodel_instanceOffset,\n\tr_program_uniform_brushmodel_instanceOffset,\n\tr_program_uniform_brushmodel_alphatested_instanceOffset,\n\tr_program_uniform_count\n} r_program_uniform_id;\n\ntypedef enum {\n\tr_program_attribute_aliasmodel_std_glc_flags,\n\tr_program_attribute_aliasmodel_shell_glc_flags,\n\tr_program_attribute_aliasmodel_shadow_glc_flags,\n\tr_program_attribute_aliasmodel_outline_glc_flags,\n\tr_program_attribute_world_drawflat_style,\n\tr_program_attribute_world_textured_style,\n\tr_program_attribute_world_textured_detailCoord,\n\n\tr_program_attribute_count\n} r_program_attribute_id;\n\ntypedef enum {\n\tr_program_memory_barrier_image_access,\n\tr_program_memory_barrier_texture_access,\n\tr_program_memory_barrier_count\n} r_program_memory_barrier_id;\n\nvoid R_ProgramInitialiseState(void);\nint R_ProgramCustomOptions(r_program_id program_id);\nqbool R_ProgramReady(r_program_id program_id);\nvoid R_ProgramUse(r_program_id program_id);\nr_program_id R_ProgramInUse(void);\nr_program_id R_ProgramForAttribute(r_program_attribute_id attr_id);\nint R_ProgramCustomOptions(r_program_id program_id);\nvoid R_ProgramSetCustomOptions(r_program_id program_id, int options);\n\nvoid R_ProgramComputeDispatch(r_program_id program_id, unsigned int num_groups_x, unsigned int num_groups_y, unsigned int num_groups_z);\nvoid R_ProgramMemoryBarrier(r_program_id program_id);\nvoid R_ProgramComputeSetMemoryBarrierFlag(r_program_id program_id, r_program_memory_barrier_id barrier_id);\n\nvoid R_ProgramUniform1i(r_program_uniform_id uniform_id, int value);\nvoid R_ProgramUniform1iArrayBase(r_program_uniform_id uniform_id, int count, int base_value);\nvoid R_ProgramUniform1f(r_program_uniform_id uniform_id, float value);\nvoid R_ProgramUniform4fv(r_program_uniform_id uniform_id, const float* values);\nvoid R_ProgramUniform3f(r_program_uniform_id uniform_id, float x, float y, float z);\nvoid R_ProgramUniform3fv(r_program_uniform_id uniform_id, const float* values);\nvoid R_ProgramUniform2fv(r_program_uniform_id uniform_id, const float* values);\nvoid R_ProgramUniform3fNormalize(r_program_uniform_id uniform_id, const byte* values);\nvoid R_ProgramUniformMatrix4fv(r_program_uniform_id uniform_id, const float* values);\nint R_ProgramUniformGet1i(r_program_uniform_id uniform_id, int default_value);\n\nint R_ProgramAttributeLocation(r_program_attribute_id attr_id);\n\n// Check if a program needs to be recompiled\nqbool R_ProgramRecompileNeeded(r_program_id program_id, unsigned int options);\n\n// Get program handle (GLuint)\nunsigned int R_ProgramId(r_program_id program_id);\n\n// Compiles a simple program\nqbool R_ProgramCompile(r_program_id program_id);\n\n// Compiles a program with custom includes based on user's config\nqbool R_ProgramCompileWithInclude(r_program_id program_id, const char* included_definitions);\n\n// Asks all programs to compile themselves\nvoid R_ProgramCompileAll(void);\n\n// Switches between sub-programs (allows multiple copies of the same program with different flags)\nvoid R_ProgramSetSubProgram(r_program_id program_id, int sub_index);\n\n// Sets uniforms based on program flags (used for fog at the moment)\nvoid R_ProgramSetStandardUniforms(r_program_id program_id);\n\n#endif // EZQUAKE_R_PROGRAM_HEADER\n"
  },
  {
    "path": "src/r_refrag.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// gl_refrag.c\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_local.h\"\n\nmnode_t\t*r_pefragtopnode;\n\n//===========================================================================\n\n/*\n===============================================================================\n\t\t\t\t\tENTITY FRAGMENT FUNCTIONS\n===============================================================================\n*/\n\nefrag_t\t\t**lastlink;\nvec3_t\t\tr_emins, r_emaxs;\nentity_t\t*r_addent;\n\nvoid R_Init_EFrags (void)\n{\n\tcl.free_efrags = (efrag_t *) Hunk_AllocName (sizeof (efrag_t), \"efrag\");\n\tmemset (cl.free_efrags, 0, sizeof (efrag_t));\n}\n\nvoid R_Next_Free_EFrag (void)\n{\n\t// advance the linked list, growing it as needed\n\tif (cl.free_efrags->entnext == NULL)\n\t{\n\t\tcl.free_efrags->entnext = (efrag_t *) Hunk_AllocName (sizeof (efrag_t), \"efrag\");\n\t\tmemset (cl.free_efrags->entnext, 0, sizeof (efrag_t));\n\t}\n\tcl.free_efrags = cl.free_efrags->entnext;\n}\n\nvoid R_SplitEntityOnNode (mnode_t *node) {\n\tefrag_t *ef;\n\tmplane_t *splitplane;\n\tmleaf_t *leaf;\n\tint sides;\n\t\n\tif (node->contents == CONTENTS_SOLID)\n\t\treturn;\n\n\t// add an efrag if the node is a leaf\n\n\tif ( node->contents < 0) {\n\t\tif (!r_pefragtopnode)\n\t\t\tr_pefragtopnode = node;\n\n\t\tleaf = (mleaf_t *)node;\n\n\t\tR_Next_Free_EFrag ();\n\n\t\tef = cl.free_efrags;\n\t\tef->entity = r_addent;\n\n\t\t// add the entity link\t\n\t\t*lastlink = ef;\n\t\tlastlink = &ef->entnext;\n\t\tef->entnext = NULL;\n\n\t\t// set the leaf links\n\t\tef->leaf = leaf;\n\t\tef->leafnext = leaf->efrags;\n\t\tleaf->efrags = ef;\n\n\t\treturn;\n\t}\n\n\t// NODE_MIXED\n\n\tsplitplane = node->plane;\n\tsides = BOX_ON_PLANE_SIDE(r_emins, r_emaxs, splitplane);\n\t\n\tif (sides == 3) {\n\t\t// split on this plane\n\t\t// if this is the first splitter of this bmodel, remember it\n\t\tif (!r_pefragtopnode)\n\t\t\tr_pefragtopnode = node;\n\t}\n\n\t// recurse down the contacted sides\n\tif (sides & 1)\n\t\tR_SplitEntityOnNode (node->children[0]);\n\n\tif (sides & 2)\n\t\tR_SplitEntityOnNode (node->children[1]);\n}\n\nvoid R_AddEfrags (entity_t *ent) {\n\tmodel_t *entmodel;\n\tint i;\n\n\tif (!ent->model)\n\t\treturn;\n\n\tr_addent = ent;\n\n\tlastlink = &ent->efrag;\n\tr_pefragtopnode = NULL;\n\n\tentmodel = ent->model;\n\n\tfor (i = 0; i < 3; i++) {\n\t\tr_emins[i] = ent->origin[i] + entmodel->mins[i];\n\t\tr_emaxs[i] = ent->origin[i] + entmodel->maxs[i];\n\t}\n\n\tR_SplitEntityOnNode (cl.worldmodel->nodes);\n\n\tent->topnode = r_pefragtopnode;\n}\n\nvoid R_StoreEfrags(efrag_t **ppefrag)\n{\n\tentity_t *pent;\n\tmodel_t *model;\n\tefrag_t *pefrag;\n\textern cvar_t r_drawflame;\n\n\tfor (pefrag = *ppefrag; pefrag; pefrag = pefrag->leafnext) {\n\t\tpent = pefrag->entity;\n\t\tmodel = pent->model;\n\n\t\tif ((model->modhint == MOD_FLAME || model->modhint == MOD_FLAME2) && !r_drawflame.integer) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch (model->type) {\n\t\t\tcase mod_alias:\n\t\t\tcase mod_alias3:\n\t\t\tcase mod_brush:\n\t\t\tcase mod_sprite:\n\t\t\t\tif (pent->visframe != r_framecount) {\n\t\t\t\t\tCL_AddEntity(pent);\n\t\t\t\t\tpent->visframe = r_framecount;\t// mark that we've recorded this entity for this frame\n\t\t\t\t\tpent->oldframe = pent->frame;\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tSys_Error(\"R_StoreEfrags: Bad entity type %d\", model->type);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/r_renderer.h",
    "content": "\n#ifndef EZQUAKE_R_RENDERER_HEADER\n#define EZQUAKE_R_RENDERER_HEADER\n\n#include \"gl_model.h\"\n#include \"r_state.h\"\n#include \"r_vao.h\"\n#include \"gl_framebuffer.h\"\n\ntypedef enum {\n\tr_cubemap_right,\n\tr_cubemap_back,\n\tr_cubemap_left,\n\tr_cubemap_front,\n\tr_cubemap_up,\n\tr_cubemap_down,\n\n\tr_cubemap_direction_count\n} r_cubemap_direction_id;\n\n#ifdef RENDERER_METHOD\n#undef RENDERER_METHOD\n#endif\n\n#define RENDERER_METHOD(returntype, name, ...) returntype (*name)(__VA_ARGS__);\n\ntypedef struct renderer_api_s {\n#include \"r_renderer_structure.h\"\n\n\tqbool vaos_supported;\n} renderer_api_t;\n\nextern renderer_api_t renderer;\n\n#undef RENDERER_METHOD\n\n#endif // EZQUAKE_R_RENDERER_HEADER\n"
  },
  {
    "path": "src/r_renderer_structure.h",
    "content": "\n// Meta\nRENDERER_METHOD(void, Shutdown, r_shutdown_mode_t mode)\nRENDERER_METHOD(void, CvarForceRecompile, cvar_t* cvar)\nRENDERER_METHOD(void, PrintGfxInfo, void)\nRENDERER_METHOD(const char*, DescriptiveString, void)\n\n// Config/State\nRENDERER_METHOD(void, Viewport, int x, int y, int width, int height)\nRENDERER_METHOD(void, InvalidateViewport, void)\nRENDERER_METHOD(void, ApplyRenderingState, r_state_id state)\n\n// Called after map has been loaded\nRENDERER_METHOD(void, PrepareModelRendering, qbool vid_restart)\nRENDERER_METHOD(void, PrepareAliasModel, model_t* model, aliashdr_t* hdr)\n\n// Sky surfaces\nRENDERER_METHOD(void, DrawSky, void)\nRENDERER_METHOD(void, DrawWorld, void)\n\n// Entities\nRENDERER_METHOD(void, DrawAliasFrame, entity_t* ent, model_t* model, int pose1, int pose2, texture_ref texture, texture_ref fb_texture, qbool outline, int effects, int render_effects, float lerpfrac)\nRENDERER_METHOD(void, DrawAlias3Model, entity_t *ent, qbool outline, qbool additive_pass)\nRENDERER_METHOD(void, DrawAliasModelShadow, entity_t* ent)\nRENDERER_METHOD(void, DrawAliasModelPowerupShell, entity_t *ent)\nRENDERER_METHOD(void, DrawAlias3ModelPowerupShell, entity_t *ent)\nRENDERER_METHOD(void, DrawSpriteModel, entity_t *ent)\nRENDERER_METHOD(void, DrawSimpleItem, model_t* model, int skin, vec3_t origin, float scale, vec3_t up, vec3_t right)\n\n// Particles\nRENDERER_METHOD(void, DrawClassicParticles, int)\n\n// HUD\nRENDERER_METHOD(void, DrawImage, float x, float y, float width, float height, float tex_s, float tex_t, float tex_width, float tex_height, byte* color, int flags)\nRENDERER_METHOD(void, DrawRectangle, float x, float y, float width, float height, byte* color)\nRENDERER_METHOD(void, AdjustImages, int first, int last, float x_offset)\nRENDERER_METHOD(void, DrawDisc, void)\n\n// Lightmaps\nRENDERER_METHOD(void, UploadLightmap, int textureUnit, int lightmapnum)\nRENDERER_METHOD(void, LightmapFrameInit, void)\nRENDERER_METHOD(void, RenderDynamicLightmaps, msurface_t* surf, qbool world)\nRENDERER_METHOD(void, CreateLightmapTextures, void)\nRENDERER_METHOD(void, BuildLightmap, int lightmapnum)\nRENDERER_METHOD(void, InvalidateLightmapTextures, void)\nRENDERER_METHOD(void, LightmapShutdown, void)\n\n// Rendering loop\nRENDERER_METHOD(void, SetupGL, void)\nRENDERER_METHOD(void, ChainBrushModelSurfaces, model_t* model, entity_t* ent)\nRENDERER_METHOD(void, DrawBrushModel, entity_t* ent, qbool polygonOffset, qbool caustics)\nRENDERER_METHOD(int, BrushModelCopyVertToBuffer, model_t* mod, void* vbo_buffer_, int position, float* source, int lightmap, int material, float scaleS, float scaleT, msurface_t* surf, qbool has_fb_texture, qbool has_luma_texture)\nRENDERER_METHOD(void, ClearRenderingSurface, qbool clear_color)\nRENDERER_METHOD(void, DrawWaterSurfaces, void)\nRENDERER_METHOD(void, ScreenDrawStart, void)\nRENDERER_METHOD(void, EnsureFinished, void)\nRENDERER_METHOD(void, Begin2DRendering, void)\nRENDERER_METHOD(qbool, IsFramebufferEnabled3D, void)\n\n// Post-processing (scene)\nRENDERER_METHOD(void, RenderView, void)\nRENDERER_METHOD(void, PreRenderView, void)\n\n// Post-processing (screen)\nRENDERER_METHOD(void, PostProcessScreen, void)\nRENDERER_METHOD(void, BrightenScreen, void)\nRENDERER_METHOD(void, PolyBlend, float v_blend[4])\n\n// Performance\nRENDERER_METHOD(void, TimeRefresh, void)\n\n// Misc\nRENDERER_METHOD(void, Screenshot, byte* buffer, size_t size)\nRENDERER_METHOD(size_t, ScreenshotWidth, void)\nRENDERER_METHOD(size_t, ScreenshotHeight, void)\n\n// Textures\nRENDERER_METHOD(void, TextureInitialiseState, void)\nRENDERER_METHOD(void, TextureDelete, texture_ref texture)\nRENDERER_METHOD(void, TextureMipmapGenerate, texture_ref texture)\nRENDERER_METHOD(void, TextureWrapModeClamp, texture_ref tex)\nRENDERER_METHOD(void, TextureLabelSet, texture_ref texnum, const char* identifier)\nRENDERER_METHOD(qbool, TextureUnitBind, int unit, texture_ref texture)\nRENDERER_METHOD(qbool, TextureIsUnitBound, int unit, texture_ref texture)\nRENDERER_METHOD(void, TextureUnitMultiBind, int first_unit, int num_textures, texture_ref* textures)\nRENDERER_METHOD(void, TextureGet, texture_ref tex, int buffer_size, byte* buffer, int bpp)\nRENDERER_METHOD(void, TextureCompressionSet, qbool enabled)\nRENDERER_METHOD(void, TextureCreate2D, texture_ref* reference, int width, int height, const char* name, qbool is_lightmap)\nRENDERER_METHOD(void, TexturesCreate, r_texture_type_id type, int count, texture_ref* texture)\nRENDERER_METHOD(void, TextureReplaceSubImageRGBA, texture_ref ref, int offsetx, int offsety, int width, int height, byte* buffer)\nRENDERER_METHOD(void, TextureSetFiltering, texture_ref texture, texture_minification_id minification_filter, texture_magnification_id magnification_filter)\nRENDERER_METHOD(void, TextureSetAnisotropy, texture_ref texture, int anisotropy)\nRENDERER_METHOD(void, TextureLoadCubemapFace, texture_ref cubemap, r_cubemap_direction_id direction, const byte* data, int width, int height)\n\n// VAOs\nRENDERER_METHOD(void, DeleteVAOs, void)\nRENDERER_METHOD(void, GenVertexArray, r_vao_id vao, const char* name)\nRENDERER_METHOD(void, BindVertexArray, r_vao_id vao)\nRENDERER_METHOD(void, BindVertexArrayElementBuffer, r_vao_id vao, r_buffer_id ref)\nRENDERER_METHOD(qbool, VertexArrayCreated, r_vao_id vao)\n\n// Sprites\nRENDERER_METHOD(void, Prepare3DSprites, void)\nRENDERER_METHOD(void, Draw3DSprites, void)\nRENDERER_METHOD(void, Draw3DSpritesInline, void)   // FIXME get rid of this and all other inline rendering\n\n// Framebuffers\nRENDERER_METHOD(void, RenderFramebuffers, void)\nRENDERER_METHOD(qbool, FramebufferCreate, framebuffer_id id, int width, int height)\n\n// Programs\nRENDERER_METHOD(void, ProgramsInitialise, void)\nRENDERER_METHOD(void, ProgramsShutdown, qbool restarting)\n"
  },
  {
    "path": "src/r_rlight.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_sprite3d.h\"\n#include \"r_lightmaps.h\"\n#include \"r_state.h\"\n#include \"r_lighting.h\"\n\nint\tr_dlightframecount;\n\nvoid R_AnimateLight(void)\n{\n\tint i, j, l1, l2;\n\tfloat lerpfrac;\n\n\t// light animations : 'm' is normal light, 'a' is no light, 'z' is double bright\n\ti = (int) (r_refdef2.time * 10);\n\tlerpfrac = r_refdef2.time * 10 - i;\n\tfor (j = 0; j < MAX_LIGHTSTYLES; j++) {\n\t\tif (!cl_lightstyle[j].length) {\n\t\t\td_lightstylevalue[j] = 256;\n\t\t\tcontinue;\n\t\t}\n\n\t\tl1 = i % cl_lightstyle[j].length;\n\t\tl1 = (cl_lightstyle[j].map[l1] - 'a') * 22;\n\t\tl2 = (i + 1) % cl_lightstyle[j].length;\n\t\tl2 = (cl_lightstyle[j].map[l2] - 'a') * 22;\n\n\t\tif (l1 - l2 > 220 || l2 - l1 > 220) {\n\t\t\td_lightstylevalue[j] = l2;\n\t\t}\n\t\telse {\n\t\t\td_lightstylevalue[j] = l1 + (l2 - l1) * lerpfrac;\n\t\t}\n\t}\n}\n\n\nfloat bubble_sintable[17], bubble_costable[17];\n\nvoid R_InitBubble(void)\n{\n\tfloat a, *bub_sin, *bub_cos;\n\tint i;\n\n\tbub_sin = bubble_sintable;\n\tbub_cos = bubble_costable;\n\n\tfor (i = 16; i >= 0; i--) {\n\t\ta = i/16.0 * M_PI*2;\n\t\t*bub_sin++ = sin(a);\n\t\t*bub_cos++ = cos(a);\n\t}\n}\n\n//VULT LIGHTS\n/*float bubblecolor[NUM_DLIGHTTYPES][4] = {\n\t{ 0.2, 0.1, 0.05 },\t\t// dimlight or brightlight (lt_default)\n\t{ 0.2, 0.1, 0.05 },\t\t// muzzleflash\n\t{ 0.2, 0.1, 0.05 },\t\t// explosion\n\t{ 0.2, 0.1, 0.05 },\t\t// rocket\n\t{ 0.5, 0.05, 0.05 },\t// red\n\t{ 0.05, 0.05, 0.3 },\t// blue\n\t{ 0.5, 0.05, 0.4 },\t\t// red + blue\n\t{ 0.05, 0.45, 0.05 },\t// green\n\t{ 0.5, 0.5, 0.5},\t\t// white\n\t{ 0.5, 0.5, 0.5},\t\t// custom\n};*/\n//VULT LIGHTS - My lighting colours are different, I don't know why, but they've been this way since 0.31 or so\nfloat bubblecolor[NUM_DLIGHTTYPES][4] = {\n\t{ 0.4,  0.2,  0.1  },\t// dimlight or brightlight (lt_default)\n\t{   1,  0.4,  0.2  },\t// muzzleflash\n\t{ 0.8,  0.4,  0.2  },\t// explosion\n\t{ 0.2,  0.1,  0.05 },\t// rocket  fuh : lightbubble\n\t{ 0.5,  0.05, 0.05 },\t// red\n\t{ 0.05, 0.05, 0.3  },\t// blue\n\t{ 0.5,  0.05, 0.4  },\t// red + blue\n\t{ 0.05, 0.45, 0.05 },\t// green\n\t{ 0.5,  0.45, 0.05 },\t// red + green\n\t{ 0.05, 0.45, 0.4  },\t// blue + green\n\t{ 0.5,  0.5,  0.5  },\t// white\n\t{ 0.5,  0.5,  0.5  },\t// custom\n};\n\nstatic qbool first_dlight;\n\nvoid R_RenderDlight(dlight_t *light)\n{\n\t// muzzleflash keys are negative\n\tint abs_key = abs(light->key);\n\tqbool suppress_polyblend = light->bubble != 0 || (abs_key >= 0 && abs_key <= MAX_CLIENTS);\n\n\t// don't draw our own powerup glow and muzzleflashes\n\tif (abs_key == (cl.viewplayernum + 1)) {\n\t\treturn;\n\t}\n\n\t{\n\t\tint i, j;\n\t\tvec3_t v, v_right, v_up;\n\t\tfloat length;\n\t\tfloat rad = light->radius * 0.35;\n\t\tfloat *bub_sin, *bub_cos;\n\t\tbyte center_color[4] = { 255, 255, 255, 0 };\n\t\tbyte outer_color[4] = { 0, 0, 0, 0 };\n\t\tr_sprite3d_vert_t* vert;\n\n\t\tVectorSubtract(light->origin, r_origin, v);\n\t\tlength = VectorNormalize(v);\n\n\t\tif (light->type == lt_custom) {\n\t\t\tmemcpy(center_color, light->color, 3);\n\t\t}\n\t\telse {\n\t\t\t// FIXME: bubblecolor has 4 elements, but inline spec is only 3... ?\n\t\t\tcenter_color[0] = bubblecolor[light->type][0] * 255;\n\t\t\tcenter_color[1] = bubblecolor[light->type][1] * 255;\n\t\t\tcenter_color[2] = bubblecolor[light->type][2] * 255;\n\t\t}\n\n\t\tif (length < rad) {\n\t\t\t// view is inside the dlight\n\t\t\tV_AddLightBlend(center_color[0] / 255.0f, center_color[1] / 255.0f, center_color[2] / 255.0f, light->radius * 0.0015, suppress_polyblend);\n\t\t\treturn;\n\t\t}\n\n\t\tif (first_dlight) {\n\t\t\tR_Sprite3DInitialiseBatch(SPRITE3D_FLASHBLEND_LIGHTS, r_state_light_bubble, null_texture_reference, 0, r_primitive_triangle_fan);\n\n\t\t\tfirst_dlight = false;\n\t\t}\n\n\t\tvert = R_Sprite3DAddEntry(SPRITE3D_FLASHBLEND_LIGHTS, 18);\n\t\tif (!vert) {\n\t\t\treturn;\n\t\t}\n\n\t\tVectorVectors(v, v_right, v_up);\n\n\t\tif (length - rad > 8) {\n\t\t\tVectorScale(v, rad, v);\n\t\t}\n\t\telse {\n\t\t\t// make sure the light bubble will not be clipped by near z clip plane\n\t\t\tVectorScale(v, length - 8, v);\n\t\t}\n\n\t\tVectorSubtract(light->origin, v, v);\n\n\t\tif (light->type == lt_custom) {\n\t\t\tmemcpy(center_color, light->color, 3);\n\t\t}\n\t\telse {\n\t\t\t// FIXME: bubblecolor has 4 elements, but inline spec is only 3... ?\n\t\t\tcenter_color[0] = bubblecolor[light->type][0] * 255;\n\t\t\tcenter_color[1] = bubblecolor[light->type][1] * 255;\n\t\t\tcenter_color[2] = bubblecolor[light->type][2] * 255;\n\t\t}\n\n\t\tR_Sprite3DSetVert(vert++, v[0], v[1], v[2], 1, 1, center_color, -1);\n\n\t\tbub_sin = bubble_sintable;\n\t\tbub_cos = bubble_costable;\n\n\t\tfor (i = 16; i >= 0; i--) {\n\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\tv[j] = light->origin[j] + (v_right[j] * (*bub_cos) + +v_up[j] * (*bub_sin)) * rad;\n\t\t\t}\n\t\t\tbub_sin++;\n\t\t\tbub_cos++;\n\n\t\t\tR_Sprite3DSetVert(vert++, v[0], v[1], v[2], 1, 1, outer_color, -1);\n\t\t}\n\t}\n}\n\nvoid R_RenderDlights(void)\n{\n\tunsigned int i;\n\tunsigned int j;\n\tdlight_t *l;\n\n\tr_dlightframecount = r_framecount;\n\tfirst_dlight = true;\n\n\tfor (i = 0; i < MAX_DLIGHTS / 32; i++) {\n\t\tif (cl_dlight_active[i]) {\n\t\t\tfor (j = 0; j < 32; j++) {\n\t\t\t\tif ((cl_dlight_active[i] & (1 << j)) && i * 32 + j < MAX_DLIGHTS) {\n\t\t\t\t\tl = cl_dlights + i * 32 + j;\n\n\t\t\t\t\tif (l->bubble == 2) { // light from rl\n\t\t\t\t\t\tif (gl_rl_globe.integer & 1) {\n\t\t\t\t\t\t\tR_RenderDlight(l);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (gl_rl_globe.integer & 2) {\n\t\t\t\t\t\t\tR_MarkLights(l, 1 << (i * 32 + j), cl.worldmodel->nodes);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tR_MarkLights(l, 1 << (i * 32 + j), cl.worldmodel->nodes);\n\n\t\t\t\t\tif (gl_flashblend.integer && !(l->bubble && gl_flashblend.integer != 2)) {\n\t\t\t\t\t\tR_RenderDlight(l);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n=============================================================================\n\nDYNAMIC LIGHTS\n\n=============================================================================\n*/\n\n/*\n=============\nR_MarkLights\n=============\n*/\n//VULT: Lord Havoc light stuff\nvoid R_MarkLights(dlight_t *light, int bit, mnode_t *node)\n{\n\tfloat\t\tdist;\n\tmsurface_t\t*surf;\n\tint\t\t\ti;\n\n\tif (!R_SoftwareLighting()) {\n\t\treturn;\n\t}\n\nloc0:\n\tif (node->contents < 0) {\n\t\treturn;\n\t}\n\n\tdist = PlaneDiff(light->origin, node->plane);\n\tif (dist > light->radius) {\n\t\t// LordHavoc: .lit support begin (actually this is just a major lighting speedup, no relation to color :)\n\t\tnode = node->children[0];\n\t\tgoto loc0;\n\t\t// LordHavoc: .lit support end\n\t}\n\tif (dist < -light->radius) {\n\t\t// LordHavoc: .lit support begin (actually this is just a major lighting speedup, no relation to color :)\n\t\tnode = node->children[1];\n\t\tgoto loc0;\n\t\t// LordHavoc: .lit support end\n\t}\n\n\t// mark the polygons\n\tsurf = cl.worldmodel->surfaces + node->firstsurface;\n\tfor (i = 0; i < node->numsurfaces; i++, surf++) {\n\t\t// LordHavoc: .lit support begin (actually this is just a major lighting speedup, no relation to color :)\n\t\tif (surf->dlightframe != r_dlightframecount) {\n\t\t\t// not dynamic until now\n\t\t\tsurf->dlightbits = bit;\n\t\t\tsurf->dlightframe = r_dlightframecount;\n\t\t}\n\t\telse {\n\t\t\t// already dynamic\n\t\t\tsurf->dlightbits |= bit;\n\t\t}\n\t}\n\t// LordHavoc: .lit support begin (actually this is just a major lighting speedup, no relation to color :)\n\tif (node->children[0]->contents >= 0) {\n\t\tR_MarkLights(light, bit, node->children[0]); // LordHavoc: original code\n\t}\n\tif (node->children[1]->contents >= 0) {\n\t\tR_MarkLights(light, bit, node->children[1]); // LordHavoc: original code\n\t}\n\t// LordHavoc: .lit support end\n}\n\n/*\n=============\nR_PushDlights\n=============\n*/\nvoid R_PushDlights (void)\n{\n}\n\n\n/*\n=============================================================================\n\nLIGHT SAMPLING\n\n=============================================================================\n*/\n\nmplane_t\t\t*lightplane;\n\nstatic void R_LightFromSurface(msurface_t* surf, int ds, int dt, vec3_t color)\n{\n\tif (surf->samples) {\n\t\t// LordHavoc: enhanced to interpolate lighting\n\t\tint maps, line3, dsfrac = ds & 15, dtfrac = dt & 15, r00 = 0, g00 = 0, b00 = 0, r01 = 0, g01 = 0, b01 = 0, r10 = 0, g10 = 0, b10 = 0, r11 = 0, g11 = 0, b11 = 0;\n\t\tfloat scale;\n\t\tline3 = ((surf->extents[0] >> 4) + 1) * 3;\n\n\t\tif (cl.worldmodel->flags & MOD_HDRLIGHTING) {\n\t\t\tuint32_t *lightmap = (uint32_t*)surf->samples + (dt * (surf->extents[0]+1) + ds);\n\t\t\tline3 = (surf->extents[0]+1);\n\t\t\tfor (maps = 0;maps < MAXLIGHTMAPS && surf->styles[maps] != 255; maps++) {\n\t\t\t\tfloat e;\n\t\t\t\tscale = (1<<7) * (float) d_lightstylevalue[surf->styles[maps]] * 1.0f / 256.0f;\n\t\t\t\te = rgb9e5tab[lightmap[      0]>>27] * scale;r00 += ((lightmap[      0]>> 0)&0x1ff) * e;g00 += ((lightmap[      0]>> 9)&0x1ff) * e;b00 += ((lightmap[      0]>> 9)&0x1ff) * e;\n\t\t\t\te = rgb9e5tab[lightmap[      1]>>27] * scale;r01 += ((lightmap[      1]>> 0)&0x1ff) * e;g01 += ((lightmap[      1]>> 9)&0x1ff) * e;b01 += ((lightmap[      1]>> 9)&0x1ff) * e;\n\t\t\t\te = rgb9e5tab[lightmap[line3+0]>>27] * scale;r10 += ((lightmap[line3+0]>> 0)&0x1ff) * e;g10 += ((lightmap[line3+0]>> 9)&0x1ff) * e;b10 += ((lightmap[line3+0]>> 9)&0x1ff) * e;\n\t\t\t\te = rgb9e5tab[lightmap[line3+1]>>27] * scale;r11 += ((lightmap[line3+1]>> 0)&0x1ff) * e;g11 += ((lightmap[line3+1]>> 9)&0x1ff) * e;b11 += ((lightmap[line3+1]>> 9)&0x1ff) * e;\n\t\t\t\tlightmap += (surf->extents[0]+1) * (surf->extents[1]+1);\n\t\t\t}\n\t\t} else {\n\t\t\tbyte *lightmap = surf->samples + ((dt >> 4) * ((surf->extents[0] >> 4) + 1) + (ds >> 4)) * 3; // LordHavoc: *3 for color\n\t\t\tfor (maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255; maps++) {\n\t\t\t\tscale = (float)d_lightstylevalue[surf->styles[maps]] * 1.0 / 256.0;\n\t\t\t\tr00 += (float)lightmap[0] * scale; g00 += (float)lightmap[1] * scale; b00 += (float)lightmap[2] * scale;\n\t\t\t\tr01 += (float)lightmap[3] * scale; g01 += (float)lightmap[4] * scale; b01 += (float)lightmap[5] * scale;\n\t\t\t\tr10 += (float)lightmap[line3 + 0] * scale; g10 += (float)lightmap[line3 + 1] * scale; b10 += (float)lightmap[line3 + 2] * scale;\n\t\t\t\tr11 += (float)lightmap[line3 + 3] * scale; g11 += (float)lightmap[line3 + 4] * scale; b11 += (float)lightmap[line3 + 5] * scale;\n\t\t\t\tlightmap += ((surf->extents[0] >> 4) + 1) * ((surf->extents[1] >> 4) + 1) * 3; // LordHavoc: *3 for colored lighting\n\t\t\t}\n\t\t}\n\n\t\tcolor[0] += (float)((int)((((((((r11 - r10) * dsfrac) >> 4) + r10) - ((((r01 - r00) * dsfrac) >> 4) + r00)) * dtfrac) >> 4) + ((((r01 - r00) * dsfrac) >> 4) + r00)));\n\t\tcolor[1] += (float)((int)((((((((g11 - g10) * dsfrac) >> 4) + g10) - ((((g01 - g00) * dsfrac) >> 4) + g00)) * dtfrac) >> 4) + ((((g01 - g00) * dsfrac) >> 4) + g00)));\n\t\tcolor[2] += (float)((int)((((((((b11 - b10) * dsfrac) >> 4) + b10) - ((((b01 - b00) * dsfrac) >> 4) + b00)) * dtfrac) >> 4) + ((((b01 - b00) * dsfrac) >> 4) + b00)));\n\t}\n}\n\n// LordHavoc: .lit support begin\n// LordHavoc: original code replaced entirely\nstatic int RecursiveLightPoint(vec3_t color, mnode_t *node, const vec3_t start, const vec3_t end, vec3_t lightspot)\n{\n\tfloat\t\tfront, back, frac;\n\tvec3_t\t\tmid;\n\nloc0:\n\tif (node->contents < 0) {\n\t\t// didn't hit anything\n\t\treturn false;\n\t}\n\n\t// calculate mid point\n\tfront = PlaneDiff(start, node->plane);\n\tback = PlaneDiff(end, node->plane);\n\n\t// LordHavoc: optimized recursion\n\tif ((back < 0) == (front < 0)) {\n\t\tnode = node->children[front < 0];\n\t\tgoto loc0;\n\t}\n\n\tfrac = front / (front - back);\n\tmid[0] = start[0] + (end[0] - start[0])*frac;\n\tmid[1] = start[1] + (end[1] - start[1])*frac;\n\tmid[2] = start[2] + (end[2] - start[2])*frac;\n\n\t// go down front side\n\tif (RecursiveLightPoint(color, node->children[front < 0], start, mid, lightspot)) {\n\t\treturn true;\t// hit something\n\t}\n\telse {\n\t\tint i, ds, dt;\n\t\tmsurface_t *surf;\n\n\t\t// check for impact on this node\n\t\tVectorCopy(mid, lightspot);\n\t\tlightplane = node->plane;\n\n\t\tsurf = cl.worldmodel->surfaces + node->firstsurface;\n\t\tfor (i = 0; i < node->numsurfaces; i++, surf++) {\n\t\t\tif (surf->flags & SURF_DRAWTILED) {\n\t\t\t\tcontinue;\t// no lightmaps\n\t\t\t}\n\n\t\t\tds = (int)((float)DotProduct(mid, surf->lmvecs[0]) + surf->lmvecs[0][3]);\n\t\t\tdt = (int)((float)DotProduct(mid, surf->lmvecs[1]) + surf->lmvecs[1][3]);\n\t\t\tif (ds < surf->texturemins[0] || dt < surf->texturemins[1]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tds -= surf->texturemins[0];\n\t\t\tdt -= surf->texturemins[1];\n\n\t\t\tif (ds > surf->extents[0] || dt > surf->extents[1]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tR_LightFromSurface(surf, ds, dt, color);\n\t\t\treturn true; // success\n\t\t}\n\n\t\t// go down back side\n\t\treturn RecursiveLightPoint(color, node->children[front >= 0], mid, end, lightspot);\n\t}\n}\n\nvoid R_LightEntity(entity_t* ent)\n{\n\textern cvar_t r_shadows;\n\tvec3_t\t\tend;\n\tqbool\t\tfull_light = (R_FullBrightAllowed() || !cl.worldmodel->lightdata);\n\n\tif (full_light && !r_shadows.integer) {\n\t\tVectorSet(ent->lightcolor, 255, 255, 255);\n\t\tVectorCopy(ent->origin, ent->lightspot);\n\t\tent->ambientlight = ent->shadelight = 255;\n\t\treturn;\n\t}\n\n\tend[0] = ent->origin[0];\n\tend[1] = ent->origin[1];\n\tend[2] = ent->origin[2] - 2048;\n\n\tVectorClear(ent->lightcolor);\n\tRecursiveLightPoint(ent->lightcolor, cl.worldmodel->nodes, ent->origin, end, ent->lightspot);\n\tif (full_light) {\n\t\tVectorSet(ent->lightcolor, 255, 255, 255);\n\t\tent->ambientlight = ent->shadelight = 255;\n\t\treturn;\n\t}\n\n\tent->ambientlight = ent->shadelight = ((ent->lightcolor[0] + ent->lightcolor[1] + ent->lightcolor[2]) / 3.0f);\n}\n// LordHavoc: .lit support end\n"
  },
  {
    "path": "src/r_rmain.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"vx_stuff.h\"\n#include \"vx_vertexlights.h\"\n#include \"utils.h\"\n#include \"qsound.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"rulesets.h\"\n#include \"teamplay.h\"\n#include \"r_sprite3d.h\"\n#include \"r_performance.h\"\n#include \"r_chaticons.h\"\n#include \"r_matrix.h\"\n#include \"r_local.h\"\n#include \"r_state.h\"\n#include \"r_brushmodel_sky.h\"\n#include \"r_brushmodel.h\"\n#include \"r_lighting.h\"\n#include \"r_buffers.h\"\n#include \"r_draw.h\"\n#include \"r_aliasmodel.h\"\n#include \"r_lightmaps.h\"\n#include \"r_trace.h\"\n#include \"r_renderer.h\"\n\n#define R_FogAvailable() (!COM_CheckParm(cmdline_param_client_nomultitexturing))\n\nvoid GLM_ScreenDrawStart(void);\n\n// Move to API\nvoid GLM_SetupGL(void);\nvoid GLC_SetupGL(void);\nvoid GLM_PreRenderView(void);\nvoid GLC_PreRenderView(void);\nvoid GLM_DrawSpriteModel(entity_t *e);\nvoid GLC_DrawSpriteModel(entity_t *e);\nvoid GLM_PolyBlend(float v_blend[4]);\nvoid GLC_PolyBlend(float v_blend[4]);\nvoid GLM_InitialiseAliasModelBatches(void);\n\nvoid R_TimeRefresh_f(void);\nstatic void R_DrawEntities(void);\nvoid R_InitOtherTextures(void);\nvoid R_DrawViewModel(void);\n\nvoid OnChange_gl_clearColor(cvar_t *v, char *s, qbool *cancel);\nvoid SCR_OnChangeMVHudPos(cvar_t *var, char *newval, qbool *cancel);\nvoid OnChange_r_drawflat(cvar_t *v, char *skyname, qbool *cancel);\nvoid OnChange_r_skyname(cvar_t *v, char *s, qbool *cancel);\nvoid R_MarkLeaves(void);\nvoid R_InitBubble(void);\nvoid R_InitAliasModelCvars(void);\nvoid R_CreateWorldTextureChains(void);\nstatic void R_SetupGL(void);\n\nvoid GLM_RenderView(void);\n\nvoid SCR_SetupDamageIndicators(void);\n\nextern msurface_t *alphachain;\n\ntexture_t *r_notexture_mip = NULL;\nrefdef2_t r_refdef2;                          // screen size info\nrefdef_t  r_refdef;                           // screen size info\nentity_t  r_worldentity;\nmplane_t  frustum[4];\nmleaf_t   *r_viewleaf;\nmleaf_t   *r_oldviewleaf;\nmleaf_t   *r_viewleaf2;                       // for watervis hack\nmleaf_t   *r_oldviewleaf2;                    // for watervis hack\nvec3_t    vup, vpn, vright;                   // view origin\nvec3_t    vup_noroll, vright_noroll;          // view directions, with roll=0\nvec3_t    r_origin; // view origin\nfloat     clearColor[3] = {0, 0, 0};\nint       r_visframecount;                    // bumped when going to a new PVS\nint       r_framecount;                       // used for dlight push checking\nint       lightmode = 2;\nunsigned int d_lightstylevalue[256];             // 8.8 fraction of base light value\ntexture_ref underwatertexture, detailtexture, solidwhite_texture, solidblack_texture, transparent_texture;\n\nvoid OnSquareParticleChange(cvar_t *var, char *value, qbool *cancel)\n{\n\tCvar_SetIgnoreCallback(var, value);\n\n\tClassic_InitParticles();\n}\n\nstatic void OnDynamicLightingChange(cvar_t* var, char* value, qbool* cancel)\n{\n\tif (R_UseImmediateOpenGL() && atoi(value) > 1) {\n\t\tCon_Printf(\"Hardware lighting not supported when not using GLSL\\n\");\n\t\t*cancel = true;\n\t\treturn;\n\t}\n}\n\nstatic void OnRemoveCollinearVerticesChanged(cvar_t* var, char* value, qbool* cancel)\n{\n#if defined(__APPLE__) && defined(__aarch64__)\n\t// At least arm64 based MacBook laptops are known to require this workaround.\n\tCvar_SetIgnoreCallback(var, \"1\");\n\t*cancel = true;\n#endif\n}\n\ncvar_t cl_multiview                        = {\"cl_multiview\", \"0\" };\ncvar_t cl_mvdisplayhud                     = {\"cl_mvdisplayhud\", \"1\"};\ncvar_t cl_mvhudvertical                    = {\"cl_mvhudvertical\", \"0\"};\ncvar_t cl_mvhudflip                        = {\"cl_mvhudflip\", \"0\"};\ncvar_t cl_mvhudpos                         = {\"cl_mvhudpos\", \"bottom center\"};\ncvar_t cl_mvinset                          = {\"cl_mvinset\", \"0\"};\ncvar_t cl_mvinsetcrosshair                 = {\"cl_mvinsetcrosshair\", \"1\"};\ncvar_t cl_mvinsethud                       = {\"cl_mvinsethud\", \"1\"};\ncvar_t cl_mvinset_offset_x                 = {\"cl_mvinset_offset_x\", \"0\"};\ncvar_t cl_mvinset_offset_y                 = {\"cl_mvinset_offset_y\", \"0\"};\ncvar_t cl_mvinset_size_x                   = {\"cl_mvinset_size_x\", \"0.333\"};\ncvar_t cl_mvinset_size_y                   = {\"cl_mvinset_size_y\", \"0.333\"};\ncvar_t cl_mvinset_top                      = {\"cl_mvinset_top\", \"1\"};\ncvar_t cl_mvinset_right                    = {\"cl_mvinset_right\", \"1\"};\n\ncvar_t r_drawentities                      = {\"r_drawentities\", \"1\"};\ncvar_t r_drawworld                         = {\"r_drawworld\", \"1\"};\ncvar_t r_lerpframes                        = {\"r_lerpframes\", \"1\"};\ncvar_t r_drawflame                         = {\"r_drawflame\", \"1\"};\ncvar_t r_drawdisc                          = {\"r_drawdisc\", \"1\"};\ncvar_t r_speeds                            = {\"r_speeds\", \"0\"};\ncvar_t r_fullbright                        = {\"r_fullbright\", \"0\"};\ncvar_t r_shadows                           = {\"r_shadows\", \"0\"};\ncvar_t r_wateralpha                        = {\"gl_turbalpha\", \"1\"};\n#if defined(EZ_MULTIPLE_RENDERERS) || defined(RENDERER_OPTION_MODERN_OPENGL)\ncvar_t r_dynamic                           = {\"r_dynamic\", \"2\", 0, OnDynamicLightingChange };\n#else\ncvar_t r_dynamic                           = {\"r_dynamic\", \"1\", 0, OnDynamicLightingChange };\n#endif\ncvar_t r_novis                             = {\"r_novis\", \"0\"};\ncvar_t r_netgraph                          = {\"r_netgraph\", \"0\"};\ncvar_t r_netstats                          = {\"r_netstats\", \"0\"};\ncvar_t r_fastsky                           = {\"r_fastsky\", \"0\"};\ncvar_t r_fastturb                          = {\"r_fastturb\", \"0\"};\ncvar_t r_skycolor                          = {\"r_skycolor\", \"40 80 150\", CVAR_COLOR};\ncvar_t r_telecolor                         = {\"r_telecolor\", \"255 60 60\", CVAR_COLOR};\ncvar_t r_lavacolor                         = {\"r_lavacolor\", \"80 0 0\", CVAR_COLOR};\ncvar_t r_slimecolor                        = {\"r_slimecolor\", \"10 60 10\", CVAR_COLOR};\ncvar_t r_watercolor                        = {\"r_watercolor\", \"10 50 80\", CVAR_COLOR};\ncvar_t r_drawflat                          = {\"r_drawflat\", \"0\", 0, OnChange_r_drawflat};\ncvar_t r_drawflat_mode                     = {\"r_drawflat_mode\", \"0\", 0, OnChange_r_drawflat};\ncvar_t r_wallcolor                         = {\"r_wallcolor\", \"255 255 255\", CVAR_COLOR, OnChange_r_drawflat};\ncvar_t r_floorcolor                        = {\"r_floorcolor\", \"50 100 150\", CVAR_COLOR, OnChange_r_drawflat};\ncvar_t gl_textureless                      = {\"gl_textureless\", \"0\", 0, OnChange_r_drawflat}; //Qrack\ncvar_t r_farclip                           = {\"r_farclip\", \"8192\", CVAR_RULESET_MAX | CVAR_RULESET_MIN, NULL, 8192.0f, R_MAXIMUM_FARCLIP, R_MINIMUM_FARCLIP }; // previous default was 4096. 8192 helps some TF players in big maps\ncvar_t r_skyname                           = {\"r_skyname\", \"\", 0, OnChange_r_skyname};\ncvar_t r_skywind                           = {\"r_skywind\", \"1\"};\ncvar_t gl_detail                           = {\"gl_detail\",\"0\"};\ncvar_t gl_brush_polygonoffset              = {\"gl_brush_polygonoffset\", \"2.0\"}; // This is the one to adjust if you notice flicker on lift @ e1m1 for instance, for z-fighting\ncvar_t gl_brush_polygonoffset_factor       = {\"gl_brush_polygonoffset_factor\", \"0.05\"};\ncvar_t gl_caustics                         = {\"gl_caustics\", \"0\"}; // 1\ncvar_t gl_lumatextures                     = {\"gl_lumatextures\", \"1\"};\ncvar_t gl_subdivide_size                   = {\"gl_subdivide_size\", \"64\"};\ncvar_t gl_clear                            = {\"gl_clear\", \"0\"};\ncvar_t gl_clearColor                       = {\"gl_clearColor\", \"0 0 0\", CVAR_COLOR, OnChange_gl_clearColor};\ncvar_t gl_polyblend                        = {\"gl_polyblend\", \"1\"}; // 0\ncvar_t gl_flashblend                       = {\"gl_flashblend\", \"0\"};\ncvar_t gl_rl_globe                         = {\"gl_rl_globe\", \"0\"};\ncvar_t gl_playermip                        = {\"gl_playermip\", \"0\"};\ncvar_t gl_nocolors                         = {\"gl_nocolors\", \"0\"};\ncvar_t gl_finish                           = {\"gl_finish\", \"0\"};\ncvar_t gl_fb_bmodels                       = {\"gl_fb_bmodels\", \"1\"};\ncvar_t gl_fb_models                        = {\"gl_fb_models\", \"1\"};\ncvar_t gl_lightmode                        = {\"gl_lightmode\", \"1\"};\ncvar_t gl_loadlitfiles                     = {\"gl_loadlitfiles\", \"1\"};\ncvar_t gl_oldlitscaling                    = {\"gl_oldlitscaling\", \"0\"};\ncvar_t gl_colorlights                      = {\"gl_colorlights\", \"1\"};\ncvar_t gl_squareparticles                  = {\"gl_squareparticles\", \"0\", 0, OnSquareParticleChange};\ncvar_t gl_part_explosions                  = {\"gl_part_explosions\", \"0\"}; // 1\ncvar_t gl_part_bloodtrails                 = {\"gl_part_bloodtrails\", \"1\"}; // 1 was the default behaviour before this became a cvar\ncvar_t gl_part_trails                      = {\"gl_part_trails\", \"0\"}; // 1\ncvar_t gl_part_tracer1_color               = {\"gl_part_tracer1_color\", \"0 124 0\", CVAR_COLOR};\ncvar_t gl_part_tracer2_color               = {\"gl_part_tracer2_color\", \"255 77 0\", CVAR_COLOR};\ncvar_t gl_part_tracer1_size                = {\"gl_part_tracer1_size\", \"3.75\", CVAR_RULESET_MAX | CVAR_RULESET_MIN, NULL, 3.75f, 10.f, 0.f};\ncvar_t gl_part_tracer1_time                = {\"gl_part_tracer1_time\", \"0.5\", CVAR_RULESET_MAX | CVAR_RULESET_MIN, NULL, 0.5f, 3.f, 0.f};\ncvar_t gl_part_tracer2_size                = {\"gl_part_tracer2_size\", \"3.75\", CVAR_RULESET_MAX | CVAR_RULESET_MIN, NULL, 3.75f, 10.f, 0.f};\ncvar_t gl_part_tracer2_time                = {\"gl_part_tracer2_time\", \"0.5\", CVAR_RULESET_MAX | CVAR_RULESET_MIN, NULL, 0.5f, 3.f, 0.f};\ncvar_t gl_part_spikes                      = {\"gl_part_spikes\", \"0\"}; // 1\ncvar_t gl_part_gunshots                    = {\"gl_part_gunshots\", \"0\"}; // 1\ncvar_t gl_part_blood                       = {\"gl_part_blood\", \"0\"}; // 1\ncvar_t gl_part_telesplash                  = {\"gl_part_telesplash\", \"0\"}; // 1\ncvar_t gl_part_blobs                       = {\"gl_part_blobs\", \"0\"}; // 1\ncvar_t gl_part_lavasplash                  = {\"gl_part_lavasplash\", \"0\"}; // 1\ncvar_t gl_part_inferno                     = {\"gl_part_inferno\", \"0\"}; // 1\ncvar_t gl_part_bubble                      = {\"gl_part_bubble\", \"1\"}; // would prefer 0 but default was 1\ncvar_t gl_part_detpackexplosion_fire_color = {\"gl_part_detpackexplosion_fire_color\", \"\", CVAR_COLOR};\ncvar_t gl_part_detpackexplosion_ray_color  = {\"gl_part_detpackexplosion_ray_color\", \"\", CVAR_COLOR};\ncvar_t gl_powerupshells                    = {\"gl_powerupshells\", \"1\"};\n\n// 0: off, 1: on, 2: in water only\ncvar_t r_fx_fog                            = {\"r_fx_fog\", \"0\"};\n// 0: off, 1: dm & single-player, 2: single-player only\ncvar_t r_fx_fog_usemap                     = {\"r_fx_fog_usemap\", \"2\"};\ncvar_t r_fx_fog_model                      = {\"r_fx_fog_model\", \"2\"};\ncvar_t r_fx_fog_density                    = {\"r_fx_fog_density\", \"0.125\"};\ncvar_t r_fx_fog_start                      = {\"r_fx_fog_start\", \"50.0\"};\ncvar_t r_fx_fog_end                        = {\"r_fx_fog_end\", \"800.0\"};\ncvar_t r_fx_fog_color_air                  = {\"r_fx_fog_color_air\", \"153 128 103\", CVAR_COLOR};\ncvar_t r_fx_fog_color_water                = {\"r_fx_fog_color_water\", \"32 64 128\", CVAR_COLOR};\ncvar_t r_fx_fog_color_lava                 = {\"r_fx_fog_color_lava\", \"255 64 0\", CVAR_COLOR};\ncvar_t r_fx_fog_color_slime                = {\"r_fx_fog_color_slime\", \"128 255 0\", CVAR_COLOR};\ncvar_t r_fx_fog_sky                        = {\"r_fx_fog_sky\", \"0.3\"};\n\ncvar_t gl_simpleitems                      = {\"gl_simpleitems\", \"0\"};\ncvar_t gl_simpleitems_size                 = {\"gl_simpleitems_size\", \"16\"};\ncvar_t gl_simpleitems_orientation          = {\"gl_simpleitems_orientation\", \"2\"};\ncvar_t gl_modulate                         = {\"gl_modulate\", \"1\"};\n\ncvar_t gl_outline                          = {\"gl_outline\", \"0\"};\ncvar_t gl_outline_color_world              = {\"gl_outline_color_world\", \"0 0 0\"};\ncvar_t gl_outline_color_model              = {\"gl_outline_color_model\", \"0 0 0\"};\ncvar_t gl_outline_scale_world              = {\"gl_outline_scale_world\", \"1\"};\ncvar_t gl_outline_scale_model              = {\"gl_outline_scale_model\", \"1\"};\ncvar_t gl_outline_world_depth_threshold    = {\"gl_outline_world_depth_threshold\", \"4\"};\ncvar_t gl_outline_world_normal_threshold   = {\"gl_outline_world_normal_threshold\", \"0.997\"};\ncvar_t gl_outline_use_player_color         = {\"gl_outline_use_player_color\", \"0\"};\ncvar_t gl_spec_xray                        = {\"gl_spec_xray\", \"0\"};\ncvar_t gl_spec_xray_distance               = {\"gl_spec_xray_distance\", \"1500\"};\ncvar_t gl_outline_color_team               = {\"gl_outline_color_team\", \"\"};\ncvar_t gl_outline_color_enemy              = {\"gl_outline_color_enemy\", \"\"};\n\ncvar_t gl_smoothmodels                     = {\"gl_smoothmodels\", \"1\"};\n\ncvar_t gl_vbo_clientmemory                 = {\"gl_vbo_clientmemory\", \"0\", CVAR_LATCH_GFX };\n\ncvar_t r_remove_collinear_vertices         = {\"r_remove_collinear_vertices\", \"0\", 0, OnRemoveCollinearVerticesChanged};\n\n//Returns true if the box is completely outside the frustom\nqbool R_CullBox(vec3_t mins, vec3_t maxs)\n{\n\tint i;\n\n\tfor (i = 0; i < 4; i++) {\n\t\tif (BOX_ON_PLANE_SIDE(mins, maxs, &frustum[i]) == 2) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n//Returns true if the sphere is completely outside the frustum\nqbool R_CullSphere(vec3_t centre, float radius)\n{\n\tint i;\n\tmplane_t *p;\n\n\tfor (i = 0, p = frustum; i < 4; i++, p++) {\n\t\tif (PlaneDiff(centre, p) <= -radius) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nstatic int SignbitsForPlane(mplane_t *out)\n{\n\tint\tbits, j;\n\n\t// for fast box on planeside test\n\tbits = 0;\n\tfor (j = 0; j < 3; j++) {\n\t\tif (out->normal[j] < 0) {\n\t\t\tbits |= 1 << j;\n\t\t}\n\t}\n\treturn bits;\n}\n\nvoid R_SetFrustum(void)\n{\n\tint i;\n\n\t// rotate VPN right by FOV_X/2 degrees\n\tRotatePointAroundVector( frustum[0].normal, vup, vpn, -(90-r_refdef.fov_x / 2 ) );\n\t// rotate VPN left by FOV_X/2 degrees\n\tRotatePointAroundVector( frustum[1].normal, vup, vpn, 90-r_refdef.fov_x / 2 );\n\t// rotate VPN up by FOV_X/2 degrees\n\tRotatePointAroundVector( frustum[2].normal, vright, vpn, 90-r_refdef.fov_y / 2 );\n\t// rotate VPN down by FOV_X/2 degrees\n\tRotatePointAroundVector( frustum[3].normal, vright, vpn, -( 90 - r_refdef.fov_y / 2 ) );\n\n\tfor (i = 0; i < 4; i++) {\n\t\tfrustum[i].type = PLANE_ANYZ;\n\t\tfrustum[i].dist = DotProduct (r_origin, frustum[i].normal);\n\t\tfrustum[i].signbits = SignbitsForPlane (&frustum[i]);\n\t}\n}\n\nstatic void R_ConfigureFog(int contents)\n{\n\tint i;\n\tqbool use_map_fog;\n\n\t// enable it even if not showing this frame, to prevent potential program re-compiles when going in/out of water\n\tif (!R_FogAvailable()) {\n\t\tr_refdef2.fog_enabled = r_refdef2.fog_render = false;\n\t\treturn;\n\t}\n\n\tuse_map_fog = cl.map_fog_enabled && (r_fx_fog_usemap.integer == 1 || (r_fx_fog_usemap.integer == 2 && Ruleset_IsLocalSinglePlayerGame()));\n\tr_refdef2.fog_enabled = (use_map_fog || r_fx_fog.integer == 1 || r_fx_fog.integer == 2);\n\tr_refdef2.fog_render = (use_map_fog || r_fx_fog.integer == 1 || (r_fx_fog.integer == 2 && ISUNDERWATER(contents)));\n\tr_refdef2.fog_density = 0;\n\tr_refdef2.fog_calculation = fogcalc_none;\n\n\tif (r_refdef2.fog_render) {\n\t\tif (use_map_fog) {\n\t\t\tr_refdef2.fog_density = bound(0, cl.map_fog_density * cl.map_fog_density, 1);\n\t\t\tr_refdef2.fog_calculation = fogcalc_exp2;\n\t\t}\n\t\telse {\n\t\t\tr_refdef2.fog_density = bound(0, r_fx_fog_density.value / 64.0f, 1);\n\n\t\t\tif (r_fx_fog_model.integer == 0) {\n\t\t\t\tr_refdef2.fog_calculation = fogcalc_linear;\n\t\t\t}\n\t\t\telse if (r_fx_fog_model.integer == 1) {\n\t\t\t\tr_refdef2.fog_calculation = fogcalc_exp;\n\t\t\t}\n\t\t\telse if (r_fx_fog_model.integer == 2) {\n\t\t\t\tr_refdef2.fog_calculation = fogcalc_exp2;\n\t\t\t\tr_refdef2.fog_density = r_refdef2.fog_density * r_refdef2.fog_density;\n\t\t\t}\n\t\t}\n\n\t\tif (use_map_fog && cl.map_fog_sky >= 0) {\n\t\t\tr_refdef2.fog_sky = cl.map_fog_sky;\n\t\t}\n\t\telse {\n\t\t\tr_refdef2.fog_sky = r_refdef2.fog_density != 0 ? bound(0, r_fx_fog_sky.value, 1) : 0;\n\t\t}\n\t}\n\tr_refdef2.fog_linear_start = r_fx_fog_start.value;\n\tr_refdef2.fog_linear_end = r_fx_fog_end.value;\n\n\tif (contents == CONTENTS_LAVA) {\n\t\tVectorScale(r_fx_fog_color_lava.color, 1 / 255.0f, r_refdef2.fog_color);\n\t}\n\telse if (contents == CONTENTS_SLIME) {\n\t\tVectorScale(r_fx_fog_color_slime.color, 1 / 255.0f, r_refdef2.fog_color);\n\t}\n\telse if (contents == CONTENTS_WATER) {\n\t\tVectorScale(r_fx_fog_color_water.color, 1 / 255.0f, r_refdef2.fog_color);\n\t}\n\telse if (use_map_fog) {\n\t\tVectorCopy(cl.map_fog_color, r_refdef2.fog_color);\n\t}\n\telse {\n\t\tVectorScale(r_fx_fog_color_air.color, 1 / 255.0f, r_refdef2.fog_color);\n\t}\n\tfor (i = 0; i < 3; ++i) {\n\t\tr_refdef2.fog_skycolor[i] = r_refdef2.fog_color[i] * r_refdef2.fog_sky + r_skycolor.color[i] * (1.0f - r_refdef2.fog_sky) / 255.0f;\n\t}\n\tr_refdef2.fog_color[3] = r_refdef2.fog_skycolor[3] = 1.0f;\n}\n\nvoid R_SetupFrame(void)\n{\n\tvec3_t testorigin;\n\tmleaf_t\t*leaf;\n\n\tR_AnimateLight ();\n\n\tr_framecount++;\n\n\t// build the transformation matrix for the given view angles\n\tVectorCopy (r_refdef.vieworg, r_origin);\n\tAngleVectors (r_refdef.viewangles, vpn, vright, vup);\n\tif (r_refdef.viewangles[ROLL] != 0) {\n\t\tvec3_t noroll_angles = { r_refdef.viewangles[0], r_refdef.viewangles[1], 0 };\n\n\t\tAngleVectors(noroll_angles, NULL, vright_noroll, vup_noroll);\n\t}\n\telse {\n\t\tVectorCopy(vright, vright_noroll);\n\t\tVectorCopy(vup, vup_noroll);\n\t}\n\n\t// current viewleaf\n\tr_oldviewleaf = r_viewleaf;\n\tr_oldviewleaf2 = r_viewleaf2;\n\n\tr_viewleaf = Mod_PointInLeaf (r_origin, cl.worldmodel);\n\tr_viewleaf2 = NULL;\n\n\t// FIXME: might need to test falling out bottom of water as well?\n\n\t// check above and below so crossing solid water doesn't draw wrong\n\tif (r_viewleaf->contents <= CONTENTS_WATER && r_viewleaf->contents >= CONTENTS_LAVA) {\n\t\t// look up a bit\n\t\tVectorCopy (r_origin, testorigin);\n\t\ttestorigin[2] += 10;\n\t\tleaf = Mod_PointInLeaf (testorigin, cl.worldmodel);\n\t\tif (leaf->contents == CONTENTS_EMPTY) {\n\t\t\tr_viewleaf2 = leaf;\n\t\t}\n\t}\n\telse if (r_viewleaf->contents == CONTENTS_EMPTY) {\n\t\t// FIXME: If we test down and find CONTENTS_SOLID then we should reduce viewheight_test and try again?\n\n\t\t// look down a bit\n\t\tVectorCopy(r_origin, testorigin);\n\t\ttestorigin[2] -= r_refdef.viewheight_test;\n\t\tleaf = Mod_PointInLeaf(testorigin, cl.worldmodel);\n\t\tif (leaf->contents <= CONTENTS_WATER && leaf->contents >= CONTENTS_LAVA) {\n\t\t\tr_viewleaf2 = leaf;\n\t\t}\n\t}\n\n\tV_SetContentsColor(r_viewleaf->contents);\n\tR_ConfigureFog(r_viewleaf->contents);\n\tV_CalcBlend();\n\n\tR_LightmapFrameInit();\n}\n\nstatic void MYgluPerspective(double fovy, double aspect, double zNear, double zFar)\n{\n\tdouble xmin, xmax, ymin, ymax;\n\n\tymax = zNear * tan(fovy * M_PI / 360.0);\n\tymin = -ymax;\n\n\txmin = ymin * aspect;\n\txmax = ymax * aspect;\n\n\tif (cl_multiview.value == 2 && !cl_mvinset.value && cls.mvdplayback) {\n\t\tR_Frustum(xmin, xmax, ymin + (ymax - ymin)*0.25, ymax - (ymax - ymin)*0.25, zNear, zFar);\n\t}\n\telse if (CL_MultiviewActiveViews() == 3) {\n\t\tif (CL_MultiviewCurrentView() == 2) {\n\t\t\tR_Frustum(xmin, xmax, ymin + (ymax - ymin)*0.25, ymax - (ymax - ymin)*0.25, zNear, zFar);\n\t\t}\n\t\telse {\n\t\t\tR_Frustum(xmin, xmax, ymin, ymax, zNear, zFar);\n\t\t}\n\t}\n\telse {\n\t\tR_Frustum(xmin, xmax, ymin, ymax, zNear, zFar);\n\t}\n}\n\nvoid R_SetViewports(int glx, int x, int gly, int y2, int w, int h, float max)\n{\n\t//\n\t// Setup Multiview-viewports\n\t//\n\tif (max == 1) {\n\t\tR_Viewport(glx + x, gly + y2, w, h);\n\t\treturn;\n\t}\n\telse if (max == 2 && cl_mvinset.value) {\n\t\tif (CL_MultiviewCurrentView() == 2) {\n\t\t\tR_Viewport(glx + x, gly + y2, w, h);\n\t\t}\n\t\telse if (CL_MultiviewCurrentView() == 1) {\n\t\t\tint height = cl_sbar.integer ? h : glheight;\n\t\t\tint inset_left = glx + x + (cl_mvinset_right.integer ? glwidth - cl_mvinset_size_x.value * glwidth : 0) + cl_mvinset_offset_x.value;\n\t\t\tint inset_top = gly + y2 + (cl_mvinset_top.integer ? height - cl_mvinset_size_y.value * height : 0) - cl_mvinset_offset_y.value;\n\t\t\tint inset_width = w * cl_mvinset_size_x.value;\n\t\t\tint inset_height = h * cl_mvinset_size_y.value;\n\n\t\t\tCL_MultiviewInsetSetScreenCoordinates(inset_left, inset_top, inset_width, inset_height);\n\t\t\tR_Viewport(inset_left, inset_top, inset_width, inset_height);\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"ERROR!\\n\");\n\t\t}\n\t\treturn;\n\t}\n\telse if (max == 2 && !cl_mvinset.value) {\n\t\tif (CL_MultiviewCurrentView() == 2) {\n\t\t\tR_Viewport(0, h / 2, w, h / 2);\n\t\t}\n\t\telse if (CL_MultiviewCurrentView() == 1) {\n\t\t\tR_Viewport(0, 0, w, h / 2 - 1);\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"ERROR!\\n\");\n\t\t}\n\t\treturn;\n\t}\n\telse if (max == 3) {\n\t\tif (CL_MultiviewCurrentView() == 2) {\n\t\t\tR_Viewport(0, h / 2, w, h / 2);\n\t\t}\n\t\telse if (CL_MultiviewCurrentView() == 3) {\n\t\t\tR_Viewport(0, 0, w / 2, h / 2 - 1);\n\t\t}\n\t\telse {\n\t\t\tR_Viewport(w / 2, 0, w / 2, h / 2 - 1);\n\t\t}\n\t\treturn;\n\t}\n\telse {\n\t\tif (CL_MultiviewCurrentView() == 2) {\n\t\t\tR_Viewport(0, h / 2, w / 2, h / 2);\n\t\t}\n\t\telse if (CL_MultiviewCurrentView() == 3) {\n\t\t\tR_Viewport(w / 2, h / 2, w / 2, h / 2);\n\t\t}\n\t\telse if (CL_MultiviewCurrentView() == 4) {\n\t\t\tR_Viewport(0, 0, w / 2, h / 2 - 1);\n\t\t}\n\t\telse if (CL_MultiviewCurrentView() == 1) {\n\t\t\tR_Viewport(w / 2, 0, w / 2, h / 2 - 1);\n\t\t}\n\t}\n\n\treturn;\n}\n\nfloat R_FarPlaneZ(void)\n{\n\treturn bound(R_MINIMUM_FARCLIP, r_farclip.value, R_MAXIMUM_FARCLIP);\n}\n\nfloat R_NearPlaneZ(void)\n{\n\textern cvar_t r_nearclip;\n\n\treturn bound(R_MINIMUM_NEARCLIP, r_nearclip.value, R_MAXIMUM_NEARCLIP);\n}\n\nstatic void R_SetupViewport(void)\n{\n\tfloat screenaspect;\n\textern int glwidth, glheight;\n\tint x, x2, y2, y, w, h;\n\n\t// set up viewpoint\n\tx = r_refdef.vrect.x * glwidth / vid.width;\n\tx2 = (r_refdef.vrect.x + r_refdef.vrect.width) * glwidth / vid.width;\n\ty = (vid.height - r_refdef.vrect.y) * glheight / vid.height;\n\ty2 = (vid.height - (r_refdef.vrect.y + r_refdef.vrect.height)) * glheight / vid.height;\n\n\t// fudge around because of frac screen scale\n\tif (x > 0) {\n\t\tx--;\n\t}\n\tif (x2 < glwidth) {\n\t\tx2++;\n\t}\n\tif (y2 < 0) {\n\t\ty2--;\n\t}\n\tif (y < glheight) {\n\t\ty++;\n\t}\n\n\tw = x2 - x;\n\th = y - y2;\n\n\t// Multiview\n\tR_SetFullScreenViewport(glx + x, gly + y2, w, h);\n\tif (CL_MultiviewEnabled() && CL_MultiviewCurrentView() != 0) {\n\t\tR_SetViewports(glx, x, gly, y2, w, h, cl_multiview.value);\n\t}\n\tif (!CL_MultiviewEnabled()) {\n\t\tR_Viewport(glx + x, gly + y2, w, h);\n\t}\n\n\tscreenaspect = (float)r_refdef.vrect.width / r_refdef.vrect.height;\n\n\tR_IdentityProjectionView();\n\tMYgluPerspective(r_refdef.fov_y, screenaspect, R_NearPlaneZ(), R_FarPlaneZ());\n}\n\nstatic void R_SetupGL(void)\n{\n\tR_SetupViewport();\n\n\tR_StateDefault3D();\n\n\trenderer.SetupGL();\n}\n\nvoid R_Init(void)\n{\n\tR_SkyRegisterCvars();\n\tCmd_AddCommand(\"timerefresh\", R_TimeRefresh_f);\n#ifndef CLIENTONLY\n\tCmd_AddCommand(\"dev_pointfile\", R_ReadPointFile_f);\n#endif\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t{\n\t\tvoid GLC_BloomRegisterCvars(void);\n\n\t\tGLC_BloomRegisterCvars();\n\t}\n#endif\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_EYECANDY);\n\tCvar_Register(&r_drawentities);\n\tCvar_Register(&r_drawworld);\n\tCvar_Register(&r_lerpframes);\n\tCvar_Register(&r_drawflame);\n\tCvar_Register(&r_drawdisc);\n\tCvar_Register(&gl_detail);\n\tCvar_Register(&gl_powerupshells);\n\n\tCvar_Register(&gl_simpleitems);\n\tCvar_Register(&gl_simpleitems_size);\n\tCvar_Register(&gl_simpleitems_orientation);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_PARTICLES);\n\tCvar_Register(&gl_part_explosions);\n\tCvar_Register(&gl_part_bloodtrails);\n\tCvar_Register(&gl_part_trails);\n\tCvar_Register(&gl_part_tracer1_color);\n\tCvar_Register(&gl_part_tracer1_size);\n\tCvar_Register(&gl_part_tracer1_time);\n\tCvar_Register(&gl_part_tracer2_color);\n\tCvar_Register(&gl_part_tracer2_size);\n\tCvar_Register(&gl_part_tracer2_time);\n\tCvar_Register(&gl_part_spikes);\n\tCvar_Register(&gl_part_gunshots);\n\tCvar_Register(&gl_part_blood);\n\tCvar_Register(&gl_part_telesplash);\n\tCvar_Register(&gl_part_blobs);\n\tCvar_Register(&gl_part_lavasplash);\n\tCvar_Register(&gl_part_inferno);\n\tCvar_Register(&gl_part_bubble);\n\tCvar_Register(&gl_squareparticles);\n\n\tCvar_Register(&gl_part_detpackexplosion_fire_color);\n\tCvar_Register(&gl_part_detpackexplosion_ray_color);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_TURB);\n\tCvar_Register(&r_skyname);\n\tCvar_Register(&r_skywind);\n\tCvar_Register(&r_fastsky);\n\tCvar_Register(&r_skycolor);\n\tCvar_Register(&r_fastturb);\n\n\tCvar_Register(&r_telecolor);\n\tCvar_Register(&r_lavacolor);\n\tCvar_Register(&r_slimecolor);\n\tCvar_Register(&r_watercolor);\n\n\tCvar_Register(&r_novis);\n\tCvar_Register(&r_wateralpha);\n\tCvar_Register(&gl_caustics);\n\n\t// Fog cvars\n\tif (R_FogAvailable()) {\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_EYECANDY);\n\t\tCvar_Register(&r_fx_fog);\n\t\tCvar_Register(&r_fx_fog_model);\n\t\tCvar_Register(&r_fx_fog_usemap);\n\t\tCvar_Register(&r_fx_fog_density);\n\t\tCvar_Register(&r_fx_fog_start);\n\t\tCvar_Register(&r_fx_fog_end);\n\t\tCvar_Register(&r_fx_fog_sky);\n\t\tCvar_Register(&r_fx_fog_color_air);\n\t\tCvar_Register(&r_fx_fog_color_water);\n\t\tCvar_Register(&r_fx_fog_color_lava);\n\t\tCvar_Register(&r_fx_fog_color_slime);\n\t}\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_BLEND);\n\tCvar_Register(&gl_polyblend);\n\n\tR_InitAliasModelCvars();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_LIGHTING);\n\tCvar_Register(&r_dynamic);\n\tCvar_Register(&gl_fb_bmodels);\n\tCvar_Register(&gl_fb_models);\n\tCvar_Register(&gl_lightmode);\n\tCvar_Register(&gl_flashblend);\n\tCvar_Register(&gl_rl_globe);\n\tCvar_Register(&r_shadows);\n\tCvar_Register(&r_fullbright);\n\tCvar_Register(&gl_loadlitfiles);\n\tCvar_Register(&gl_oldlitscaling);\n\tCvar_Register(&gl_colorlights);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_TEXTURES);\n\tCvar_Register(&gl_playermip);\n\tCvar_Register(&gl_subdivide_size);\n\tCvar_Register(&gl_lumatextures);\n\tCvar_Register(&r_drawflat);\n\tCvar_Register(&r_drawflat_mode);\n\tCvar_Register(&r_wallcolor);\n\tCvar_Register(&r_floorcolor);\n\tCvar_Register(&gl_textureless); //Qrack\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_OPENGL);\n\tCvar_Register(&r_farclip);\n\tCvar_Register(&gl_clear);\n\tCvar_Register(&gl_clearColor);\n\tCvar_Register(&gl_brush_polygonoffset);\n\tCvar_Register(&gl_brush_polygonoffset_factor);\n\n\tCvar_Register(&gl_nocolors);\n\tCvar_Register(&gl_finish);\n\tCvar_Register(&gl_modulate);\n\n\tCvar_Register(&gl_outline);\n\tCvar_Register(&gl_outline_color_world);\n\tCvar_Register(&gl_outline_color_model);\n\t// Cvar_Register(&gl_outline_scale_world);\n\tCvar_Register(&gl_outline_scale_model);\n\tCvar_Register(&gl_outline_world_depth_threshold);\n\tCvar_Register(&gl_outline_world_normal_threshold);\n\tCvar_Register(&gl_outline_use_player_color);\n\tCvar_Register(&gl_spec_xray);\n\tCvar_Register(&gl_spec_xray_distance);\n\tCvar_Register(&gl_outline_color_team);\n\tCvar_Register(&gl_outline_color_enemy);\n\tCvar_Register(&gl_smoothmodels);\n\n\tCvar_Register(&gl_vbo_clientmemory);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\tif (IsDeveloperMode()) {\n\t\tCvar_Register(&r_speeds);\n\t}\n\tCvar_Register(&r_netgraph);\n\tCvar_Register(&r_netstats);\n\n\tCvar_Register(&cl_multiview);\n\tCvar_Register(&cl_mvdisplayhud);\n\tCvar_Register(&cl_mvhudvertical);\n\tCvar_Register(&cl_mvhudflip);\n\tCvar_Register(&cl_mvhudpos);\n\tcl_mvhudpos.OnChange = SCR_OnChangeMVHudPos;\n\tCvar_Register(&cl_mvinset);\n\tCvar_Register(&cl_mvinsetcrosshair);\n\tCvar_Register(&cl_mvinsethud);\n\tCvar_Register(&cl_mvinset_offset_x);\n\tCvar_Register(&cl_mvinset_offset_y);\n\tCvar_Register(&cl_mvinset_size_x);\n\tCvar_Register(&cl_mvinset_size_y);\n\tCvar_Register(&cl_mvinset_top);\n\tCvar_Register(&cl_mvinset_right);\n\n\tCvar_Register(&r_remove_collinear_vertices);\n\n\tCvar_ResetCurrentGroup();\n\n\tif (!hud_netgraph) {\n\t\thud_netgraph = HUD_Register(\n\t\t\t\"netgraph\", /*\"r_netgraph\"*/ NULL, \"Shows your network conditions in graph-form. With netgraph you can monitor your latency (ping), packet loss and network errors.\",\n\t\t\tHUD_PLUSMINUS | HUD_ON_SCORES, ca_onserver, 0, SCR_HUD_Netgraph,\n\t\t\t\"0\", \"top\", \"left\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\t\"swap_x\", \"0\",\n\t\t\t\"swap_y\", \"0\",\n\t\t\t\"inframes\", \"0\",\n\t\t\t\"scale\", \"256\",\n\t\t\t\"ploss\", \"1\",\n\t\t\t\"width\", \"256\",\n\t\t\t\"height\", \"32\",\n\t\t\t\"lostscale\", \"1\",\n\t\t\t\"alpha\", \"1\",\n\t\t\tNULL\n\t\t);\n\t}\n\n\tR_InitTextures();\t   // FIXME: not sure is this safe re-init\n\tR_InitBubble();\t       // safe re-init\n\tR_InitParticles();     // safe re-init imo\n\tR_InitChatIcons();     // safe re-init\n\n\t//VULT STUFF\n\tInitVXStuff(); // safe re-init imo\n\n\tInitTracker();\n\tR_InitOtherTextures(); // safe re-init\n\tR_InitBloomTextures();\n}\n\nvoid OnChange_gl_clearColor(cvar_t *v, char *s, qbool *cancel) {\n\tbyte *color;\n\tchar buf[MAX_COM_TOKEN];\n\n\tstrlcpy(buf,s,sizeof(buf));\n\tcolor = StringToRGB(buf);\n\n\tclearColor[0] = color[0] / 255.0;\n\tclearColor[1] = color[1] / 255.0;\n\tclearColor[2] = color[2] / 255.0;\n\n\tR_ClearColor(clearColor[0], clearColor[1], clearColor[2], 1.0);\n}\n\nstatic void R_Clear(void)\n{\n\tqbool clear_color = false;\n\n\t// This used to cause a bug with some graphics cards when\n\t// in multiview mode. It would clear all but the last\n\t// drawn views.\n\tclear_color |= (!cl_multiview.integer && (gl_clear.integer || (!vid_hwgamma_enabled && v_contrast.value > 1)));\n\n\t// If outside level or in wall, sky is usually visible\n\tclear_color |= (r_viewleaf->contents == CONTENTS_SOLID && (R_UseModernOpenGL() || r_fastsky.integer));\n\n\tif (gl_clear.integer) {\n\t\t//Tei custom clear color\n\t\tif (r_refdef2.fog_render) {\n\t\t\tR_ClearColor(r_refdef2.fog_color[0], r_refdef2.fog_color[1], r_refdef2.fog_color[2], 1.0);\n\t\t}\n\t\telse {\n\t\t\tR_ClearColor(clearColor[0], clearColor[1], clearColor[2], 1.0);\n\t\t}\n\t}\n\n\trenderer.ClearRenderingSurface(clear_color);\n}\n\nvoid R_PostProcessScene(void)\n{\n\tif (R_UseImmediateOpenGL()) {\n\t\tR_BloomBlend();\n\t}\n}\n\nstatic void R_Render3DEffects(void)\n{\n\t// Adds particles (all types)\n\tR_DrawParticles();\n\n\t// Adds chat icons over player's heads (afk etc)\n\tR_DrawChatIcons();\n\n\t// Run corona logic\n\tR_DrawCoronas();\n}\n\nstatic void R_Render3DHud(void)\n{\n\t// Draw the player's view model (gun)\n\tif (R_UseImmediateOpenGL()) {\n\t\tR_DrawViewModel();\n\t}\n\n\t// While still in 3D mode, calculate the location of labels to be printed in 2D\n\tSCR_SetupAutoID();\n\tSCR_SetupDamageIndicators();\n}\n\nvoid R_RenderView(void)\n{\n\tif (!r_worldentity.model || !cl.worldmodel) {\n\t\tSys_Error(\"R_RenderView: NULL worldmodel\");\n\t}\n\n\t// Wait for previous commands to 'complete'\n\tif (!r_speeds.integer && gl_finish.integer) {\n\t\trenderer.EnsureFinished();\n\t}\n\n\tR_SetFrustum();\n\tR_SetupGL();\n\tR_Clear();\n\tR_MarkLeaves();\t// done here so we know if we're in water\n\tR_CreateWorldTextureChains();\n\n\t// render normal view\n\tR_DrawWorld();\t\t// adds static entities to the list\n\n\tR_DrawEntities();\n\n\t// Adds 3d effects (particles, lights, chat icons etc)\n\tR_Render3DEffects();\n\n\t// Render billboards\n\trenderer.Draw3DSpritesInline();\n\n\t// Draw 3D hud elements\n\tR_Render3DHud();\n\n\trenderer.RenderView();\n}\n\nqbool R_PointIsUnderwater(vec3_t point)\n{\n\tint contents = TruePointContents(point);\n\n\treturn ISUNDERWATER(contents);\n}\n\nqbool R_CanDrawSimpleItem(entity_t* e)\n{\n\tint skin;\n\n\tif (!gl_simpleitems.integer || !e || !e->model) {\n\t\treturn false;\n\t}\n\n\tskin = e->skinnum >= 0 && e->skinnum < MAX_SIMPLE_TEXTURES ? e->skinnum : 0;\n\n\tif (R_UseModernOpenGL()) {\n\t\treturn R_TextureReferenceIsValid(e->model->simpletexture_array[skin]);\n\t}\n\telse {\n\t\treturn R_TextureReferenceIsValid(e->model->simpletexture[skin]);\n\t}\n}\n\nstatic qbool R_DrawTrySimpleItem(entity_t* ent)\n{\n\tint sprtype = gl_simpleitems_orientation.integer;\n\tfloat sprsize = bound(1, gl_simpleitems_size.value, 16), autorotate;\n\ttexture_ref simpletexture;\n\tvec3_t right, up, org, offset;\n\tint skin;\n\n\tif (!ent || !ent->model) {\n\t\treturn false;\n\t}\n\n\tskin = ent->skinnum >= 0 && ent->skinnum < MAX_SIMPLE_TEXTURES ? ent->skinnum : 0;\n\tif (R_UseModernOpenGL()) {\n\t\tsimpletexture = ent->model->simpletexture_array[skin];\n\t}\n\telse {\n\t\tsimpletexture = ent->model->simpletexture[skin];\n\t}\n\tif (!R_TextureReferenceIsValid(simpletexture)) {\n\t\treturn false;\n\t}\n\n\tautorotate = anglemod(100 * cl.time);\n\tif (sprtype == SPR_ORIENTED) {\n\t\t// bullet marks on walls\n\t\tvec3_t angles;\n\t\tangles[0] = angles[2] = 0;\n\t\tangles[1] = anglemod(autorotate);\n\t\tAngleVectors(angles, NULL, right, up);\n\t}\n\telse if (sprtype == SPR_FACING_UPRIGHT) {\n\t\tVectorSet(up, 0, 0, 1);\n\t\tright[0] = ent->origin[1] - r_origin[1];\n\t\tright[1] = -(ent->origin[0] - r_origin[0]);\n\t\tright[2] = 0;\n\t\tVectorNormalizeFast(right);\n\t}\n\telse if (sprtype == SPR_VP_PARALLEL_UPRIGHT) {\n\t\tVectorSet(up, 0, 0, 1);\n\t\tVectorCopy(vright, right);\n\t}\n\telse {\n\t\t// normal sprite\n\t\tVectorCopy(vup_noroll, up);\n\t\tVectorCopy(vright_noroll, right);\n\t}\n\n\tVectorCopy(ent->origin, org);\n\t// brush models require some additional centering\n\tif (ent->model->type == mod_brush) {\n\t\textern cvar_t cl_model_bobbing;\n\n\t\tVectorSubtract(ent->model->maxs, ent->model->mins, offset);\n\t\toffset[2] = 0;\n\t\tVectorMA(org, 0.5, offset, org);\n\n\t\tif (cl_model_bobbing.value) {\n\t\t\torg[2] += sin(autorotate / 90 * M_PI) * 5 + 5;\n\t\t}\n\t}\n\torg[2] += sprsize;\n\n\trenderer.DrawSimpleItem(ent->model, skin, org, sprsize, up, right);\n\treturn true;\n}\n\nstatic int R_DrawEntitiesSorter(const void* lhs_, const void* rhs_)\n{\n\tconst visentity_t* lhs = (const visentity_t*)lhs_;\n\tconst visentity_t* rhs = (const visentity_t*)rhs_;\n\n\tfloat alpha_lhs = lhs->type == mod_sprite ? 0.5 : lhs->ent.alpha == 0 ? 1 : lhs->ent.alpha;\n\tfloat alpha_rhs = rhs->type == mod_sprite ? 0.5 : rhs->ent.alpha == 0 ? 1 : rhs->ent.alpha;\n\n\t// Draw opaque entities first\n\tif (alpha_lhs == 1 && alpha_rhs != 1) {\n\t\treturn -1;\n\t}\n\telse if (alpha_lhs != 1 && alpha_rhs == 1) {\n\t\treturn 1;\n\t}\n\n\tif (alpha_lhs != 1) {\n\t\t// Furthest first\n\t\tif (lhs->distance > rhs->distance) {\n\t\t\treturn -1;\n\t\t}\n\t\telse if (lhs->distance < rhs->distance) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\t// order by brush/alias/etc for batching\n\tif (lhs->type != rhs->type) {\n\t\treturn lhs->type < rhs->type ? -1 : 1;\n\t}\n\n\t// Then by model\n\tif ((uintptr_t)lhs->ent.model < (uintptr_t)rhs->ent.model) {\n\t\treturn -1;\n\t}\n\tif ((uintptr_t)lhs->ent.model >(uintptr_t)rhs->ent.model) {\n\t\treturn 1;\n\t}\n\n\tif (alpha_lhs == 1) {\n\t\t// Closest first\n\t\tif (lhs->distance < rhs->distance) {\n\t\t\treturn -1;\n\t\t}\n\t\telse if (lhs->distance > rhs->distance) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\t// Then by position\n\tif ((uintptr_t)lhs < (uintptr_t)rhs) {\n\t\treturn -1;\n\t}\n\tif ((uintptr_t)lhs >(uintptr_t)rhs) {\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic void R_DrawEntitiesOnList(visentlist_t *vislist, visentlist_entrytype_t type)\n{\n\tint i;\n\n\tif (r_drawentities.integer && vislist->typecount[type] > 0) {\n\t\tfor (i = 0; i < vislist->count; i++) {\n\t\t\tvisentity_t* todraw = &vislist->list[i];\n\n\t\t\tif (!todraw->draw[type]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tswitch (todraw->type) {\n\t\t\t\tcase mod_brush:\n\t\t\t\t\tR_BrushModelDrawEntity(&todraw->ent);\n\t\t\t\t\tbreak;\n\t\t\t\tcase mod_sprite:\n\t\t\t\t\tif (todraw->ent.model->type == mod_sprite) {\n\t\t\t\t\t\trenderer.DrawSpriteModel(&todraw->ent);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tR_DrawTrySimpleItem(&todraw->ent);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase mod_alias:\n\t\t\t\t\tif (type != visent_additive) {\n\t\t\t\t\t\tif (type == visent_shells) {\n\t\t\t\t\t\t\trenderer.DrawAliasModelPowerupShell(&todraw->ent);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tR_DrawAliasModel(&todraw->ent, type == visent_outlines);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase mod_alias3:\n\t\t\t\t\tif (type == visent_shells) {\n\t\t\t\t\t\trenderer.DrawAlias3ModelPowerupShell(&todraw->ent);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\trenderer.DrawAlias3Model(&todraw->ent, type == visent_outlines, type == visent_additive);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase mod_unknown:\n\t\t\t\t\t// keeps compiler happy\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid R_PolyBlend(void)\n{\n\textern cvar_t gl_hwblend;\n\n\tif (vid_hwgamma_enabled && gl_hwblend.value && !cl.teamfortress) {\n\t\treturn;\n\t}\n\tif (!v_blend[3]) {\n\t\treturn;\n\t}\n\n\trenderer.PolyBlend(v_blend);\n}\n\nstatic void R_DrawEntities(void)\n{\n\tvisentlist_entrytype_t ent_type;\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\tif (R_UseModernOpenGL()) {\n\t\tGLM_InitialiseAliasModelBatches();\n\t}\n#endif\n\n\tif (!r_drawentities.integer) {\n\t\treturn;\n\t}\n\n\tR_TraceEnterNamedRegion(\"R_DrawEntities\");\n\n\tR_Sprite3DInitialiseBatch(SPRITE3D_ENTITIES, r_state_sprites_textured, null_texture_reference, 0, r_primitive_triangle_strip);\n\tqsort(cl_visents.list, cl_visents.count, sizeof(cl_visents.list[0]), R_DrawEntitiesSorter);\n\tfor (ent_type = 0; ent_type < visent_max; ++ent_type) {\n\t\tif (ent_type == visent_alpha) {\n\t\t\t// Transluscent before translucent entities, but after opaque ones.\n\t\t\trenderer.DrawWaterSurfaces();\n\t\t}\n\t\tR_DrawEntitiesOnList(&cl_visents, ent_type);\n\t}\n\tif (R_UseModernOpenGL() || R_UseVulkan()) {\n\t\tR_DrawViewModel();\n\t}\n\tR_TraceLeaveNamedRegion();\n}\n"
  },
  {
    "path": "src/r_rmisc.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: gl_rmisc.c,v 1.27 2007-09-17 19:37:55 qqshka Exp $\n*/\n// gl_rmisc.c\n\n#include \"quakedef.h\"\n#include \"vx_stuff.h\"\n#include \"vx_tracker.h\"\n#include \"gl_model.h\"\n#include \"rulesets.h\"\n#ifndef  __APPLE__\n#include \"tr_types.h\"\n#endif\n#include \"r_brushmodel_sky.h\"\n#include \"r_texture.h\"\n#include \"r_lightmaps.h\"\n#include \"r_local.h\"\n#include \"r_lighting.h\"\n#include \"r_performance.h\"\n#include \"r_aliasmodel.h\" // shelltexture only\n#include \"r_renderer.h\"\n\nstatic texture_ref R_GenerateShellTexture(void)\n{\n\tint x, y, d;\n\tbyte data[32][32][4];\n\n\tfor (y = 0;y < 32;y++)\n\t{\n\t\tfor (x = 0;x < 32;x++)\n\t\t{\n\t\t\td = (sin(x * M_PI / 8.0f) + cos(y * M_PI / 8.0f)) * 64 + 64;\n\t\t\tif (d < 0)\n\t\t\t\td = 0;\n\t\t\tif (d > 255)\n\t\t\t\td = 255;\n\t\t\tdata[y][x][0] = data[y][x][1] = data[y][x][2] = d;\n\t\t\tdata[y][x][3] = 255;\n\t\t}\n\t}\n\n\treturn R_LoadTexture(\"shelltexture\", 32, 32, &data[0][0][0], TEX_MIPMAP, 4);\n}\n\nvoid R_InitOtherTextures(void)\n{\n\tunsigned char solidwhitetexels[] = { 255, 255, 255, 255 };\n\tunsigned char solidblacktexels[] = { 0, 0, 0, 255 };\n\tunsigned char transparenttexels[] = { 0, 0, 0, 0 };\n\tint flags = TEX_MIPMAP | TEX_ALPHA;\n\textern cvar_t gl_caustics, gl_detail, gl_powerupshells;\n\n\tunderwatertexture = R_LoadTextureImage(\"textures/water_caustic\", NULL, 0, 0, flags | (gl_caustics.value ? TEX_COMPLAIN : 0));\n\tdetailtexture = R_LoadTextureImage(\"textures/detail\", NULL, 256, 256, flags | (gl_detail.value ? TEX_COMPLAIN : 0));\n\n\tshelltexture = R_LoadTextureImage(\"textures/shellmap\", NULL, 0, 0, flags | TEX_PREMUL_ALPHA | TEX_ZERO_ALPHA | (bound(0, gl_powerupshells.value, 1) ? TEX_COMPLAIN : 0));\n\tif (!R_TextureReferenceIsValid(shelltexture)) {\n\t\tshelltexture = R_GenerateShellTexture();\n\t}\n\n\tsolidwhite_texture = R_LoadTexture(\"billboard:solidwhite\", 1, 1, solidwhitetexels, TEX_ALPHA | TEX_NOSCALE, 4);\n\tsolidblack_texture = R_LoadTexture(\"billboard:solidblack\", 1, 1, solidblacktexels, TEX_ALPHA | TEX_NOSCALE, 4);\n\ttransparent_texture = R_LoadTexture(\"billboard:transparent\", 1, 1, transparenttexels, TEX_ALPHA | TEX_NOSCALE, 4);\n}\n\nvoid R_InitTextures(void)\n{\n\tint x, y, m;\n\tbyte *dest;\n\n\tif (r_notexture_mip) {\n\t\treturn; // FIXME: may be do not Hunk_AllocName but made other stuff ???\n\t}\n\n\t// create a simple checkerboard texture for the default\n\tr_notexture_mip = (texture_t *)Hunk_AllocName(sizeof(texture_t) + 16 * 16 + 8 * 8 + 4 * 4 + 2 * 2, \"notexture\");\n\n\tstrlcpy(r_notexture_mip->name, \"notexture\", sizeof(r_notexture_mip->name));\n\tr_notexture_mip->width = r_notexture_mip->height = 16;\n\tr_notexture_mip->offsets[0] = sizeof(texture_t);\n\tr_notexture_mip->offsets[1] = r_notexture_mip->offsets[0] + 16 * 16;\n\tr_notexture_mip->offsets[2] = r_notexture_mip->offsets[1] + 8 * 8;\n\tr_notexture_mip->offsets[3] = r_notexture_mip->offsets[2] + 4 * 4;\n\n\tfor (m = 0; m < 4; m++) {\n\t\tdest = (byte *)r_notexture_mip + r_notexture_mip->offsets[m];\n\t\tfor (y = 0; y < (16 >> m); y++) {\n\t\t\tfor (x = 0; x < (16 >> m); x++) {\n\t\t\t\tif ((y < (8 >> m)) ^ (x < (8 >> m)))\n\t\t\t\t\t*dest++ = 0;\n\t\t\t\telse\n\t\t\t\t\t*dest++ = 0x0e;\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Called after models have been downloaded but before they've been loaded into memory\nvoid R_NewMapPreLoad(void)\n{\n\tR_ClearSkyTextures();\n\n\t// Set this here so lightmaps & textures both use the same setting...\n\tlightmode = gl_lightmode.integer ? 2 : 0;\n}\n\nstatic qbool R_ParseWorldspawn(const char* entstring, worldspawn_info_t* worldspawn)\n{\n\tchar key[256];\n\tconst char* data;\n\n\tmemset(worldspawn, 0, sizeof(*worldspawn));\n\tworldspawn->wateralpha = -1;\n\tworldspawn->telealpha = -1;\n\tworldspawn->slimealpha = -1;\n\tworldspawn->lavaalpha = -1;\n\tworldspawn->fog_sky = -1;\n\n\t// parse the opening brace\n\tdata = COM_Parse(entstring);\n\tif (!data) {\n\t\treturn false;\n\t}\n\tif (com_token[0] != '{') {\n\t\tCon_Printf(\"R_ParseWorldSpawn: found %s when expecting {\", com_token);\n\t\treturn false;\n\t}\n\n\twhile (1) {\n\t\tdata = COM_Parse(data);\n\t\tif (!data) {\n\t\t\treturn false; // error\n\t\t}\n\t\tif (com_token[0] == '}') {\n\t\t\treturn true; // end of object: ignore other entities for now\n\t\t}\n\t\tif (com_token[0] == '_') {\n\t\t\tstrlcpy(key, com_token + 1, sizeof(key));\n\t\t}\n\t\telse {\n\t\t\tstrlcpy(key, com_token, sizeof(key));\n\t\t}\n\n\t\tdata = COM_Parse(data);\n\t\tif (!data) {\n\t\t\treturn false; // error\n\t\t}\n\n\t\tif (!strcmp(\"sky\", key)) {\n\t\t\tstrlcpy(worldspawn->skybox_name, com_token, sizeof(worldspawn->skybox_name));\n\t\t}\n\t\telse if (!strcmp(\"wateralpha\", key)) {\n\t\t\tworldspawn->wateralpha = atof(com_token);\n\t\t\tworldspawn->wateralpha = bound(0, worldspawn->wateralpha, 1);\n\t\t}\n\t\telse if (!strcmp(\"lavaalpha\", key)) {\n\t\t\tworldspawn->lavaalpha = atof(com_token);\n\t\t\tworldspawn->lavaalpha = bound(0, worldspawn->lavaalpha, 1);\n\t\t}\n\t\telse if (!strcmp(\"telealpha\", key)) {\n\t\t\tworldspawn->telealpha = atof(com_token);\n\t\t\tworldspawn->telealpha = bound(0, worldspawn->telealpha, 1);\n\t\t}\n\t\telse if (!strcmp(\"slimealpha\", key)) {\n\t\t\tworldspawn->slimealpha = atof(com_token);\n\t\t\tworldspawn->slimealpha = bound(0, worldspawn->slimealpha, 1);\n\t\t}\n\t\telse if (!strcmp(\"fog\", key)) {\n\t\t\t// <density> <r> <g> <b>\n\t\t\ttokenizecontext_t fog;\n\t\t\tchar temp[MAX_COM_TOKEN];\n\n\t\t\t// TokenizeString uses com_token unfortunately...\n\t\t\tstrlcpy(temp, com_token, sizeof(temp));\n\t\t\tCmd_TokenizeStringEx(&fog, temp);\n\t\t\tif (Cmd_ArgcEx(&fog) == 4) {\n\t\t\t\tworldspawn->fog_density = atof(Cmd_ArgvEx(&fog, 0));\n\t\t\t\tworldspawn->fog_color[0] = atof(Cmd_ArgvEx(&fog, 1));\n\t\t\t\tworldspawn->fog_color[1] = atof(Cmd_ArgvEx(&fog, 2));\n\t\t\t\tworldspawn->fog_color[2] = atof(Cmd_ArgvEx(&fog, 3));\n\n\t\t\t\tworldspawn->fog_density = bound(0, worldspawn->fog_density, 1);\n\t\t\t\tworldspawn->fog_color[0] = bound(0, worldspawn->fog_color[0], 1);\n\t\t\t\tworldspawn->fog_color[1] = bound(0, worldspawn->fog_color[1], 1);\n\t\t\t\tworldspawn->fog_color[2] = bound(0, worldspawn->fog_color[2], 1);\n\t\t\t}\n\t\t}\n\t\telse if (!strcmp(\"skyfog\", key) || !strcmp(\"r_skyfog\", key)) {\n\t\t\tworldspawn->fog_sky = atof(com_token);\n\t\t\tworldspawn->fog_sky = bound(0, worldspawn->fog_sky, 1);\n\t\t}\n\t}\n\n\treturn false;\n}\n\nvoid R_NewMap(qbool vid_restart)\n{\n\tworldspawn_info_t worldspawn;\n\tint\ti;\n\n\textern int R_SetSky(char *skyname);\n\textern void HUD_NewRadarMap(void); // hud_common.c\n\n\tif (R_ParseWorldspawn(CM_EntityString(), &worldspawn)) {\n\t\t// load skybox specified\n\t\tif (r_skyname.string[0] == 0 && worldspawn.skybox_name[0]) {\n\t\t\tR_SetSky(worldspawn.skybox_name);\n\t\t}\n\t\telse {\n\t\t\tR_SetSky(r_skyname.string);\n\t\t}\n\n\t\t// set fog controls, if specified\n\t\tif (worldspawn.fog_density > 0) {\n\t\t\tVectorCopy(worldspawn.fog_color, cl.map_fog_color);\n\t\t\tcl.map_fog_density = worldspawn.fog_density / 64.0f;\n\t\t\tcl.map_fog_enabled = true;\n\t\t}\n\n\t\tif (worldspawn.fog_sky >= 0) {\n\t\t\tcl.map_fog_sky = worldspawn.fog_sky;\n\t\t}\n\t}\n\telse {\n\t\tR_SetSky(r_skyname.string);\n\t}\n\n\tif (!vid_restart) {\n\t\tfor (i = 0; i < 256; i++) {\n\t\t\t// normal light value\n\t\t\td_lightstylevalue[i] = 264;\n\t\t}\n    \n\t\tmemset (&r_worldentity, 0, sizeof(r_worldentity));\n\t\tr_worldentity.model = cl.worldmodel;\n    \n\t\t// clear out efrags in case the level hasn't been reloaded\n\t\t// FIXME: is this one short?\n\t\tfor (i = 0; i < cl.worldmodel->numleafs; i++) {\n\t\t\tcl.worldmodel->leafs[i].efrags = NULL;\n\t\t}\n\n\t\tr_viewleaf = NULL;\n\t\tR_ClearParticles ();\n\t}\n\telse {\n\t\tMod_ReloadModelsTextures(); // reload textures for brush models\n#if defined(WITH_PNG)\n\t\tHUD_NewRadarMap();\t\t\t// Need to reload the radar picture.\n#endif\n\t}\n\n\tMod_ReloadModels(vid_restart);\n\tR_NewMapPrepare(vid_restart);\n\n\tif (!vid_restart) {\n\t\t// identify sky texture\n\t\tfor (i = 0; i < cl.worldmodel->numtextures; i++) {\n\t\t\tif (!cl.worldmodel->textures[i]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcl.worldmodel->textures[i]->texturechain = NULL;\n\t\t\tcl.worldmodel->textures[i]->texturechain_tail = &cl.worldmodel->textures[i]->texturechain;\n\t\t}\n\n\t\t//VULT CORONAS\n\t\tInitCoronas();\n\t\t//VULT NAMES\n\t\tVX_TrackerClear();\n\t}\n}\n\nvoid R_TimeRefresh_f(void)\n{\n\tif (cls.state != ca_active) {\n\t\treturn;\n\t}\n\n\tif (!Rulesets_AllowTimerefresh()) {\n\t\tCom_Printf(\"Timerefresh is disabled during match\\n\");\n\t\treturn;\n\t}\n\n\trenderer.TimeRefresh();\n}\n"
  },
  {
    "path": "src/r_shared.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n// r_shared.h: general refresh-related stuff shared between the refresh and the\n// driver\n\n// FIXME: clean up and move into d_iface.h\n\n#ifndef _R_SHARED_H_\n#define _R_SHARED_H_\n\n#include \"d_iface.h\"\n\n#define\tMAXVERTS\t16\t\t\t\t\t// max points in a surface polygon\n#define MAXWORKINGVERTS\t(MAXVERTS + 4)\t// max points in an intermediate\n\t\t\t\t\t\t\t\t\t\t// polygon (while processing)\n// !!! if this is changed, it must be changed in d_ifacea.h too !!!\n\n#define\tMAXHEIGHT\t1024\n#define\tMAXWIDTH\t1280\n\n#define INFINITE_DISTANCE\t0x10000\t\t// distance that's always guaranteed to\n\t\t\t\t\t\t\t\t\t\t//  be farther away than anything in the scene\n\n//===================================================================\n\nextern int\t\tcachewidth;\nextern pixel_t\t*cacheblock;\nextern int\t\tscreenwidth;\n\nextern\tfloat\tpixelAspect;\n\nextern int\t\tr_drawnpolycount;\n\nextern int\tsintable[MAXWIDTH + CYCLE];\nextern int\tintsintable[MAXWIDTH + CYCLE];\n\nextern\tvec3_t\tvup, base_vup;\nextern\tvec3_t\tvpn, base_vpn;\nextern\tvec3_t\tvright, base_vright;\n\n#define NUMSTACKEDGES\t\t2000\n#define\tMINEDGES\t\t\tNUMSTACKEDGES\n#define NUMSTACKSURFACES\t1000\n#define MINSURFACES\t\t\tNUMSTACKSURFACES\n#define\tMAXSPANS\t\t\t3000\n\n// !!! if this is changed, it must be changed in asm_draw.h too !!!\ntypedef struct espan_s {\n\tint\t\t\t\tu, v, count;\n\tstruct espan_s\t*pnext;\n} espan_t;\n\n// FIXME: compress, make a union if that will help\n// insubmodel is only 1, flags is fewer than 32, spanstate could be a byte\ntypedef struct surf_s {\n\tstruct surf_s\t*next;\t\t\t// active surface stack in r_edge.c\n\tstruct surf_s\t*prev;\t\t\t// used in r_edge.c for active surf stack\n\tstruct espan_s\t*spans;\t\t\t// pointer to linked list of spans to draw\n\tint\t\t\tkey;\t\t\t\t// sorting key (BSP order)\n\tint\t\t\tlast_u;\t\t\t\t// set during tracing\n\tint\t\t\tspanstate;\t\t\t// 0 = not in span\n\t\t\t\t\t\t\t\t\t// 1 = in span\n\t\t\t\t\t\t\t\t\t// -1 = in inverted span (end before start)\n\tint\t\t\tflags;\t\t\t\t// currentface flags\n\tvoid\t\t*data;\t\t\t\t// associated data like msurface_t\n\tentity_t\t*entity;\n\tfloat\t\tnearzi;\t\t\t\t// nearest 1/z on surface, for mipmapping\n\tqbool\tinsubmodel;\n\tfloat\t\td_ziorigin, d_zistepu, d_zistepv;\n\n\tint\t\t\tpad[2];\t\t\t\t// to 64 bytes\n} surf_t;\n\nextern\tsurf_t\t*surfaces, *surface_p, *surf_max;\n\n// surfaces are generated in back to front order by the bsp, so if a surf\n// pointer is greater than another one, it should be drawn in front\n// surfaces[1] is the background, and is used as the active surface stack.\n// surfaces[0] is a dummy, because index 0 is used to indicate no surface\n//  attached to an edge_t\n\n//===================================================================\n\nextern vec3_t\tsxformaxis[4];\t// s axis transformed into viewspace\nextern vec3_t\ttxformaxis[4];\t// t axis transformed into viewspac\n\nextern vec3_t\tmodelorg, base_modelorg;\n\nextern\tfloat\txcenter, ycenter;\nextern\tfloat\txscale, yscale;\nextern\tfloat\txscaleinv, yscaleinv;\nextern\tfloat\txscaleshrink, yscaleshrink;\n\nextern\tunsigned int d_lightstylevalue[256]; // 8.8 frac of base light value\n\nextern void TransformVector (vec3_t in, vec3_t out);\nextern void SetUpForLineScan(fixed8_t startvertu, fixed8_t startvertv,\n\tfixed8_t endvertu, fixed8_t endvertv);\n\nextern int\tr_skymade;\nextern void R_MakeSky (void);\n\nextern int\tubasestep, errorterm, erroradjustup, erroradjustdown;\n\n// flags in finalvert_t.flags\n#define ALIAS_LEFT_CLIP\t\t\t\t0x0001\n#define ALIAS_TOP_CLIP\t\t\t\t0x0002\n#define ALIAS_RIGHT_CLIP\t\t\t0x0004\n#define ALIAS_BOTTOM_CLIP\t\t\t0x0008\n#define ALIAS_Z_CLIP\t\t\t\t0x0010\n// !!! if this is changed, it must be changed in d_ifacea.h too !!!\n#define ALIAS_ONSEAM\t\t\t\t0x0020\t// also defined in modelgen.h;\n\t\t\t\t\t\t\t\t\t\t\t//  must be kept in sync\n#define ALIAS_XY_CLIP_MASK\t\t\t0x000F\n\n// !!! if this is changed, it must be changed in asm_draw.h too !!!\ntypedef struct edge_s {\n\tfixed16_t\t\tu;\n\tfixed16_t\t\tu_step;\n\tstruct edge_s\t*prev, *next;\n\tunsigned short\tsurfs[2];\n\tstruct edge_s\t*nextremove;\n\tfloat\t\t\tnearzi;\n\tmedge_t\t\t\t*owner;\n} edge_t;\n\n#endif\t// _R_SHARED_H_\n"
  },
  {
    "path": "src/r_sprite3d.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// 3D sprites\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_sprite3d.h\"\n#include \"r_sprite3d_internal.h\"\n#include \"r_state.h\"\n#include \"r_buffers.h\"\n#include \"tr_types.h\"\n#include \"r_renderer.h\"\n#include \"r_texture.h\"\n\nint indexes_start_quads, indexes_start_flashblend, indexes_start_sparks;\n\nstatic const char* batch_type_names[] = {\n\t\"ENTITIES\",\n\t\"PARTICLES_CLASSIC\",\n\t\"PARTICLES_NEW_p_spark\",\n\t\"PARTICLES_NEW_p_smoke\",\n\t\"PARTICLES_NEW_p_fire\",\n\t\"PARTICLES_NEW_p_bubble\",\n\t\"PARTICLES_NEW_p_lavasplash\",\n\t\"PARTICLES_NEW_p_gunblast\",\n\t\"PARTICLES_NEW_p_chunk\",\n\t\"PARTICLES_NEW_p_shockwave\",\n\t\"PARTICLES_NEW_p_inferno_flame\",\n\t\"PARTICLES_NEW_p_inferno_trail\",\n\t\"PARTICLES_NEW_p_sparkray\",\n\t\"PARTICLES_NEW_p_staticbubble\",\n\t\"PARTICLES_NEW_p_trailpart\",\n\t\"PARTICLES_NEW_p_dpsmoke\",\n\t\"PARTICLES_NEW_p_dpfire\",\n\t\"PARTICLES_NEW_p_teleflare\",\n\t\"PARTICLES_NEW_p_blood1\",\n\t\"PARTICLES_NEW_p_blood2\",\n\t\"PARTICLES_NEW_p_blood3\",\n\n\t//VULT PARTICLES\n\t\"PARTICLES_NEW_p_rain\",\n\t\"PARTICLES_NEW_p_alphatrail\",\n\t\"PARTICLES_NEW_p_railtrail\",\n\t\"PARTICLES_NEW_p_streak\",\n\t\"PARTICLES_NEW_p_streaktrail\",\n\t\"PARTICLES_NEW_p_streakwave\",\n\t\"PARTICLES_NEW_p_lightningbeam\",\n\t\"PARTICLES_NEW_p_vxblood\",\n\t\"PARTICLES_NEW_p_lavatrail\",\n\t\"PARTICLES_NEW_p_vxsmoke\",\n\t\"PARTICLES_NEW_p_vxsmoke_red\",\n\t\"PARTICLES_NEW_p_muzzleflash\",\n\t\"PARTICLES_NEW_p_inferno\",\n\t\"PARTICLES_NEW_p_2dshockwave\",\n\t\"PARTICLES_NEW_p_vxrocketsmoke\",\n\t\"PARTICLES_NEW_p_trailbleed\",\n\t\"PARTICLES_NEW_p_bleedspike\",\n\t\"PARTICLES_NEW_p_flame\",\n\t\"PARTICLES_NEW_p_bubble2\",\n\t\"PARTICLES_NEW_p_bloodcloud\",\n\t\"PARTICLES_NEW_p_chunkdir\",\n\t\"PARTICLES_NEW_p_smallspark\",\n\t\"PARTICLES_NEW_p_slimeglow\",\n\t\"PARTICLES_NEW_p_slimebubble\",\n\t\"PARTICLES_NEW_p_blacklavasmoke\",\n\t\"PARTICLES_NEW_p_entitytrail\",\n\t\"PARTICLES_NEW_p_flametorch\",\n\t\"FLASHBLEND_LIGHTS\",\n\t\"CORONATEX_STANDARD\",\n\t\"CORONATEX_GUNFLASH\",\n\t\"CORONATEX_EXPLOSIONFLASH1\",\n\t\"CORONATEX_EXPLOSIONFLASH2\",\n\t\"CORONATEX_EXPLOSIONFLASH3\",\n\t\"CORONATEX_EXPLOSIONFLASH4\",\n\t\"CORONATEX_EXPLOSIONFLASH5\",\n\t\"CORONATEX_EXPLOSIONFLASH6\",\n\t\"CORONATEX_EXPLOSIONFLASH7\",\n\t\"CHATICON_AFK_CHAT\",\n\t\"CHATICON_CHAT\",\n\t\"CHATICON_AFK\"\n};\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(batch_type_names) / sizeof(batch_type_names[0]) == MAX_SPRITE3D_BATCHES);\n#endif\n\nr_sprite3d_vert_t verts[MAX_VERTS_PER_SCENE];\ngl_sprite3d_batch_t batches[MAX_SPRITE3D_BATCHES];\nunsigned int batchMapping[MAX_SPRITE3D_BATCHES];\nunsigned int batchCount;\nunsigned int vertexCount;\nstatic unsigned int indexData[INDEXES_MAX_QUADS * 4 + INDEXES_MAX_SPARKS * 9 + INDEXES_MAX_FLASHBLEND * 18 + (INDEXES_MAX_QUADS + INDEXES_MAX_SPARKS + INDEXES_MAX_FLASHBLEND) * 3];\nstatic int sprites_in_batch[sprite_vertpool_count];\n\nsprite_vertpool_id batch_to_vertpool[] = {\n\tsprite_vertpool_game_entities, // SPRITE3D_ENTITIES,\n\tsprite_vertpool_simple_particles, // SPRITE3D_PARTICLES_CLASSIC,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_spark,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_smoke,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_fire,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_bubble,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_lavasplash,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_gunblast,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_chunk,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_shockwave,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_inferno_flame,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_inferno_trail,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_sparkray,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_staticbubble,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_trailpart,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_dpsmoke,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_dpfire,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_teleflare,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_blood1,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_blood2,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_blood3,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_rain,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_alphatrail,\n\tsprite_vertpool_game_annotations, // SPRITE3D_PARTICLES_NEW_p_railtrail,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_streak,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_streaktrail,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_streakwave,\n\tsprite_vertpool_game_annotations, // SPRITE3D_PARTICLES_NEW_p_lightningbeam,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_vxblood,\n\tsprite_vertpool_qmb_particles_environment, // SPRITE3D_PARTICLES_NEW_p_lavatrail,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_vxsmoke,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_vxsmoke_red,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_muzzleflash,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_inferno,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_2dshockwave,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_vxrocketsmoke,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_trailbleed,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_bleedspike,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_flame,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_bubble2,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_bloodcloud,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_chunkdir,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_smallspark,\n\tsprite_vertpool_qmb_particles_environment, // SPRITE3D_PARTICLES_NEW_p_slimeglow,\n\tsprite_vertpool_qmb_particles_environment, // SPRITE3D_PARTICLES_NEW_p_slimebubble,\n\tsprite_vertpool_qmb_particles_environment, // SPRITE3D_PARTICLES_NEW_p_blacklavasmoke,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_PARTICLES_NEW_p_entitytrail,\n\tsprite_vertpool_qmb_particles_environment, // SPRITE3D_PARTICLES_NEW_p_flametorch,\n\tsprite_vertpool_game_annotations, // SPRITE3D_FLASHBLEND_LIGHTS,  (put it here as it's important to gameplay)\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_CORONATEX_STANDARD,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_CORONATEX_GUNFLASH,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_CORONATEX_EXPLOSIONFLASH1,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_CORONATEX_EXPLOSIONFLASH2,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_CORONATEX_EXPLOSIONFLASH3,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_CORONATEX_EXPLOSIONFLASH4,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_CORONATEX_EXPLOSIONFLASH5,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_CORONATEX_EXPLOSIONFLASH6,\n\tsprite_vertpool_qmb_particles_normal, // SPRITE3D_CORONATEX_EXPLOSIONFLASH7,\n\tsprite_vertpool_game_annotations, // SPRITE3D_CHATICON_AFK_CHAT,\n\tsprite_vertpool_game_annotations, // SPRITE3D_CHATICON_CHAT,\n\tsprite_vertpool_game_annotations, // SPRITE3D_CHATICON_AFK\n};\n\n#ifdef C_ASSERT\nC_ASSERT(sizeof(batch_to_vertpool) / sizeof(batch_to_vertpool[0]) == MAX_SPRITE3D_BATCHES);\n#endif\n\nstatic gl_sprite3d_batch_t* BatchForType(sprite3d_batch_id type, qbool allocate)\n{\n\tunsigned int index = batchMapping[type];\n\n\tif (index == 0) {\n\t\tif (!allocate || batchCount >= sizeof(batches) / sizeof(batches[0])) {\n\t\t\treturn NULL;\n\t\t}\n\t\tbatchMapping[type] = index = ++batchCount;\n\t}\n\n\treturn &batches[index - 1];\n}\n\nvoid R_Sprite3DInitialiseBatch(sprite3d_batch_id type, r_state_id state, texture_ref texture, int index, r_primitive_id primitive_type)\n{\n\tgl_sprite3d_batch_t* batch = BatchForType(type, true);\n\n\tassert(state != r_state_null);\n\n\tbatch->rendering_state = state;\n\tbatch->texture = texture;\n\tbatch->count = 0;\n\tbatch->primitive_id = primitive_type;\n\tbatch->allSameNumber = true;\n\tbatch->texture_index = index;\n\tbatch->name = batch_type_names[type];\n\tbatch->id = type;\n}\n\nr_sprite3d_vert_t* R_Sprite3DAddEntrySpecific(sprite3d_batch_id type, int verts_required, texture_ref texture, int texture_index)\n{\n\tgl_sprite3d_batch_t* batch = BatchForType(type, false);\n\tint start = vertexCount;\n\tint sprite_count = sprites_in_batch[batch_to_vertpool[batch->id]];\n\n\tif (!batch || sprite_count >= MAX_3DSPRITES_PER_BATCH || (sprite_count + 1) * verts_required >= MAX_VERTS_PER_BATCH) {\n\t\treturn NULL;\n\t}\n\tif (start + verts_required >= MAX_VERTS_PER_SCENE) {\n\t\treturn NULL;\n\t}\n\tbatch->firstVertices[batch->count] = start;\n\tbatch->glFirstVertices[batch->count] = start + buffers.BufferOffset(r_buffer_sprite_vertex_data) / sizeof(verts[0]);\n\tbatch->numVertices[batch->count] = verts_required;\n\tbatch->textures[batch->count] = texture;\n\tbatch->textureIndexes[batch->count] = texture_index;\n\n\tif (batch->count) {\n\t\tbatch->allSameNumber &= verts_required == batch->numVertices[0];\n\t}\n\tsprites_in_batch[batch_to_vertpool[batch->id]] += 1;\n\t++batch->count;\n\tvertexCount += verts_required;\n\treturn &verts[start];\n}\n\nr_sprite3d_vert_t* R_Sprite3DAddEntryFixed(sprite3d_batch_id type, int verts_required)\n{\n\treturn R_Sprite3DAddEntrySpecific(type, verts_required, null_texture_reference, 0);\n}\n\nr_sprite3d_vert_t* R_Sprite3DAddEntry(sprite3d_batch_id type, int verts_required)\n{\n\treturn R_Sprite3DAddEntrySpecific(type, verts_required, null_texture_reference, 0);\n}\n\nvoid R_Sprite3DSetVert(r_sprite3d_vert_t* vert, float x, float y, float z, float s, float t, byte color[4], int texture_index)\n{\n\textern int particletexture_array_index;\n\textern float particletexture_array_xpos_tr;\n\textern float particletexture_array_ypos_tr;\n\n\tvert->color[0] = color[0];\n\tvert->color[1] = color[1];\n\tvert->color[2] = color[2];\n\tvert->color[3] = color[3];\n\tVectorSet(vert->position, x, y, z);\n\tif (texture_index < 0) {\n\t\tvert->tex[0] = particletexture_array_xpos_tr;\n\t\tvert->tex[1] = particletexture_array_ypos_tr;\n\t\tvert->tex[2] = particletexture_array_index;\n\t}\n\telse {\n\t\tvert->tex[0] = s;\n\t\tvert->tex[1] = t;\n\t\tvert->tex[2] = texture_index;\n\t}\n}\n\nvoid R_Sprite3DCreateVBO(void)\n{\n\tif (!R_BufferReferenceIsValid(r_buffer_sprite_vertex_data)) {\n\t\tbuffers.Create(r_buffer_sprite_vertex_data, buffertype_vertex, \"sprite3d-vbo\", sizeof(verts), verts, bufferusage_once_per_frame);\n\t}\n}\n\nvoid R_Sprite3DCreateIndexBuffer(void)\n{\n\tif (!R_BufferReferenceIsValid(r_buffer_sprite_index_data)) {\n\t\t// Meag: *3 is for case of primitive restart not being supported\n\t\tint i, j;\n\t\tint pos = 0;\n\t\tint vbo_pos;\n\n\t\tindexes_start_quads = pos;\n\t\tfor (i = 0, vbo_pos = 0; i < INDEXES_MAX_QUADS; ++i) {\n\t\t\tif (i) {\n\t\t\t\tif (GL_Supported(R_SUPPORT_PRIMITIVERESTART)) {\n\t\t\t\t\tindexData[pos++] = ~(unsigned int)0;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tindexData[pos++] = vbo_pos - 1;\n\t\t\t\t\tindexData[pos++] = vbo_pos;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (j = 0; j < 4; ++j) {\n\t\t\t\tindexData[pos++] = vbo_pos++;\n\t\t\t}\n\t\t}\n\n\t\t// These are currently only supported/used if primitive restart supported, as they're rendered as triangle fans\n\t\tindexes_start_flashblend = pos;\n\t\tfor (i = 0, vbo_pos = 0; i < INDEXES_MAX_FLASHBLEND; ++i) {\n\t\t\tif (i) {\n\t\t\t\tif (GL_Supported(R_SUPPORT_PRIMITIVERESTART)) {\n\t\t\t\t\tindexData[pos++] = ~(unsigned int)0;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tindexData[pos++] = vbo_pos - 1;\n\t\t\t\t\tindexData[pos++] = vbo_pos;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (j = 0; j < 18; ++j) {\n\t\t\t\tindexData[pos++] = vbo_pos++;\n\t\t\t}\n\t\t}\n\n\t\t// These are currently only supported/used if primitive restart supported, as they're rendered as triangle fans\n\t\tindexes_start_sparks = pos;\n\t\tfor (i = 0, vbo_pos = 0; i < INDEXES_MAX_SPARKS; ++i) {\n\t\t\tif (i) {\n\t\t\t\tif (GL_Supported(R_SUPPORT_PRIMITIVERESTART)) {\n\t\t\t\t\tindexData[pos++] = ~(unsigned int)0;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tindexData[pos++] = vbo_pos - 1;\n\t\t\t\t\tindexData[pos++] = vbo_pos - 1;\n\t\t\t\t\tindexData[pos++] = vbo_pos;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (j = 0; j < 9; ++j) {\n\t\t\t\tindexData[pos++] = vbo_pos++;\n\t\t\t}\n\t\t}\n\n\t\tbuffers.Create(r_buffer_sprite_index_data, buffertype_index, \"3dsprite-indexes\", sizeof(indexData), indexData, bufferusage_reuse_many_frames);\n\t}\n}\n\nvoid R_Sprite3DRender(r_sprite3d_vert_t* vert, vec3_t origin, vec3_t up, vec3_t right, float scale_up, float scale_down, float scale_left, float scale_right, float s, float t, int index)\n{\n\tstatic byte color_white[4] = { 255, 255, 255, 255 };\n\tvec3_t points[4];\n\n\n\tVectorMA(origin, scale_up, up, points[0]);\n\tVectorMA(points[0], scale_left, right, points[0]);\n\tVectorMA(origin, scale_down, up, points[1]);\n\tVectorMA(points[1], scale_left, right, points[1]);\n\tVectorMA(origin, scale_up, up, points[2]);\n\tVectorMA(points[2], scale_right, right, points[2]);\n\tVectorMA(origin, scale_down, up, points[3]);\n\tVectorMA(points[3], scale_right, right, points[3]);\n\n\tR_Sprite3DSetVert(vert++, points[0][0], points[0][1], points[0][2], 0, 0, color_white, index);\n\tR_Sprite3DSetVert(vert++, points[1][0], points[1][1], points[1][2], 0, t, color_white, index);\n\tR_Sprite3DSetVert(vert++, points[2][0], points[2][1], points[2][2], s, 0, color_white, index);\n\tR_Sprite3DSetVert(vert++, points[3][0], points[3][1], points[3][2], s, t, color_white, index);\n}\n\nvoid R_Sprite3DClearBatches(void)\n{\n\tbatchCount = vertexCount = 0;\n\tmemset(batchMapping, 0, sizeof(batchMapping));\n\tmemset(sprites_in_batch, 0, sizeof(sprites_in_batch));\n}\n"
  },
  {
    "path": "src/r_sprite3d.h",
    "content": "\n#ifndef EZQUAKE_R_SPRITE3D_HEADER\n#define EZQUAKE_R_SPRITE3D_HEADER\n\n#include \"r_state.h\"\n\n// Billboards\ntypedef enum {\n\tSPRITE3D_ENTITIES,\n\tSPRITE3D_PARTICLES_CLASSIC,\n\tSPRITE3D_PARTICLES_NEW_p_spark,\n\tSPRITE3D_PARTICLES_NEW_p_smoke,\n\tSPRITE3D_PARTICLES_NEW_p_fire,\n\tSPRITE3D_PARTICLES_NEW_p_bubble,\n\tSPRITE3D_PARTICLES_NEW_p_lavasplash,\n\tSPRITE3D_PARTICLES_NEW_p_gunblast,\n\tSPRITE3D_PARTICLES_NEW_p_chunk,\n\tSPRITE3D_PARTICLES_NEW_p_shockwave,\n\tSPRITE3D_PARTICLES_NEW_p_inferno_flame,\n\tSPRITE3D_PARTICLES_NEW_p_inferno_trail,\n\tSPRITE3D_PARTICLES_NEW_p_sparkray,\n\tSPRITE3D_PARTICLES_NEW_p_staticbubble,\n\tSPRITE3D_PARTICLES_NEW_p_trailpart,\n\tSPRITE3D_PARTICLES_NEW_p_dpsmoke,\n\tSPRITE3D_PARTICLES_NEW_p_dpfire,\n\tSPRITE3D_PARTICLES_NEW_p_teleflare,\n\tSPRITE3D_PARTICLES_NEW_p_blood1,\n\tSPRITE3D_PARTICLES_NEW_p_blood2,\n\tSPRITE3D_PARTICLES_NEW_p_blood3,\n\t//VULT PARTICLES\n\tSPRITE3D_PARTICLES_NEW_p_rain,\n\tSPRITE3D_PARTICLES_NEW_p_alphatrail,\n\tSPRITE3D_PARTICLES_NEW_p_railtrail,\n\tSPRITE3D_PARTICLES_NEW_p_streak,\n\tSPRITE3D_PARTICLES_NEW_p_streaktrail,\n\tSPRITE3D_PARTICLES_NEW_p_streakwave,\n\tSPRITE3D_PARTICLES_NEW_p_lightningbeam,\n\tSPRITE3D_PARTICLES_NEW_p_vxblood,\n\tSPRITE3D_PARTICLES_NEW_p_lavatrail,\n\tSPRITE3D_PARTICLES_NEW_p_vxsmoke,\n\tSPRITE3D_PARTICLES_NEW_p_vxsmoke_red,\n\tSPRITE3D_PARTICLES_NEW_p_muzzleflash,\n\tSPRITE3D_PARTICLES_NEW_p_inferno,\n\tSPRITE3D_PARTICLES_NEW_p_2dshockwave,\n\tSPRITE3D_PARTICLES_NEW_p_vxrocketsmoke,\n\tSPRITE3D_PARTICLES_NEW_p_trailbleed,\n\tSPRITE3D_PARTICLES_NEW_p_bleedspike,\n\tSPRITE3D_PARTICLES_NEW_p_flame,\n\tSPRITE3D_PARTICLES_NEW_p_bubble2,\n\tSPRITE3D_PARTICLES_NEW_p_bloodcloud,\n\tSPRITE3D_PARTICLES_NEW_p_chunkdir,\n\tSPRITE3D_PARTICLES_NEW_p_smallspark,\n\tSPRITE3D_PARTICLES_NEW_p_slimeglow,\n\tSPRITE3D_PARTICLES_NEW_p_slimebubble,\n\tSPRITE3D_PARTICLES_NEW_p_blacklavasmoke,\n\tSPRITE3D_PARTICLES_NEW_p_entitytrail,\n\tSPRITE3D_PARTICLES_NEW_p_flametorch,\n\tSPRITE3D_FLASHBLEND_LIGHTS,\n\tSPRITE3D_CORONATEX_STANDARD,\n\tSPRITE3D_CORONATEX_GUNFLASH,\n\tSPRITE3D_CORONATEX_EXPLOSIONFLASH1,\n\tSPRITE3D_CORONATEX_EXPLOSIONFLASH2,\n\tSPRITE3D_CORONATEX_EXPLOSIONFLASH3,\n\tSPRITE3D_CORONATEX_EXPLOSIONFLASH4,\n\tSPRITE3D_CORONATEX_EXPLOSIONFLASH5,\n\tSPRITE3D_CORONATEX_EXPLOSIONFLASH6,\n\tSPRITE3D_CORONATEX_EXPLOSIONFLASH7,\n\tSPRITE3D_CHATICON_AFK_CHAT,\n\tSPRITE3D_CHATICON_CHAT,\n\tSPRITE3D_CHATICON_AFK,\n\n\tMAX_SPRITE3D_BATCHES\n} sprite3d_batch_id;\n\n// Meag: use this as sanity check that any new particles have had batch created\n#define SPRITE3D_PARTICLES_NEW_LIMIT (SPRITE3D_FLASHBLEND_LIGHTS)\n\ntypedef struct r_sprite3d_vert_s {\n\t// 0->12\n\tfloat position[3];\n\t// 12->24\n\tfloat tex[3];\n\t// 24->28\n\tbyte color[4];\n\t// 28->32\n\tbyte padding[4];\n} r_sprite3d_vert_t;\n\ntypedef enum {\n\tr_primitive_triangle_strip,\n\tr_primitive_triangle_fan,\n\tr_primitive_triangles,\n\n\tr_primitive_count\n} r_primitive_id;\n\nvoid R_Sprite3DInitialiseBatch(sprite3d_batch_id type, r_state_id rendering_state, texture_ref texture, int index, r_primitive_id primitive_type);\nr_sprite3d_vert_t* R_Sprite3DAddEntry(sprite3d_batch_id type, int verts_required);\nr_sprite3d_vert_t* R_Sprite3DAddEntrySpecific(sprite3d_batch_id type, int verts_required, texture_ref texture, int index);\nvoid R_Sprite3DSetVert(r_sprite3d_vert_t* vert, float x, float y, float z, float s, float t, byte color[4], int texture_index);\nvoid R_Sprite3DRender(r_sprite3d_vert_t* vert, vec3_t origin, vec3_t up, vec3_t right, float scale_up, float scale_down, float scale_left, float scale_right, float s, float t, int index);\n\n// Internal for renderers\nvoid R_Sprite3DCreateIndexBuffer(void);\nvoid R_Sprite3DCreateVBO(void);\nvoid R_Sprite3DClearBatches(void);\n\n#endif // EZQUAKE_R_SPRITE3D_HEADER\n"
  },
  {
    "path": "src/r_sprite3d_internal.h",
    "content": "\n#ifndef EZQUAKE_GL_SPRITE3D_INTERNAL\n#define EZQUAKE_GL_SPRITE3D_INTERNAL\n\n// Internal only\n#define MAX_3DSPRITES_PER_BATCH    16384  // Batches limited to this so they can't break other functionality\n#define INDEXES_MAX_QUADS            512\n#define INDEXES_MAX_FLASHBLEND         8\n#define INDEXES_MAX_SPARKS            16\n\ntypedef struct gl_sprite3d_batch_s {\n\tr_state_id rendering_state;\n\tr_primitive_id primitive_id;\n\ttexture_ref texture;\n\tint texture_index;\n\tqbool allSameNumber;\n\n\tint firstVertices[MAX_3DSPRITES_PER_BATCH];\n\tint glFirstVertices[MAX_3DSPRITES_PER_BATCH];\n\tint numVertices[MAX_3DSPRITES_PER_BATCH];\n\ttexture_ref textures[MAX_3DSPRITES_PER_BATCH];\n\tint textureIndexes[MAX_3DSPRITES_PER_BATCH];\n\n\tconst char* name;\n\tsprite3d_batch_id id;\n\tunsigned int count;\n} gl_sprite3d_batch_t;\n\ntypedef enum {\n\tsprite_vertpool_game_entities,\n\tsprite_vertpool_game_annotations,\n\tsprite_vertpool_simple_particles,\n\tsprite_vertpool_qmb_particles_normal,\n\tsprite_vertpool_qmb_particles_environment,\n\n\tsprite_vertpool_count\n} sprite_vertpool_id;\n\n// FIXME: Bit ugly with these externs here\n#define MAX_VERTS_PER_BATCH (MAX_3DSPRITES_PER_BATCH * 4)\n#define MAX_VERTS_PER_SCENE (MAX_VERTS_PER_BATCH * sprite_vertpool_count)\n\nextern r_sprite3d_vert_t verts[MAX_VERTS_PER_SCENE];\nextern gl_sprite3d_batch_t batches[MAX_SPRITE3D_BATCHES];\n\nextern unsigned int batchCount;\nextern unsigned int vertexCount;\n#endif\n"
  },
  {
    "path": "src/r_sprites.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n// gl_sprites.c \n// taken from gl_model.c -- model loading and caching\n\n// models are the only shared resource between a client and server running on the same machine.\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_texture.h\"\n\nextern model_t* loadmodel;\nextern char     loadname[32];   // for hunk tags\n\n//=============================================================================\nstatic texture_ref Mod_LoadExternalSpriteSkin(char *identifier, int framenum)\n{\n\tchar loadpath[64];\n\tint texmode;\n\ttexture_ref texnum;\n\n\ttexmode = TEX_MIPMAP | TEX_ALPHA | TEX_PREMUL_ALPHA;\n\tif (!gl_scaleModelTextures.value && !loadmodel->isworldmodel) {\n\t\ttexmode |= TEX_NOSCALE;\n\t}\n\n\tsnprintf (loadpath, sizeof(loadpath), \"textures/sprites/%s\", identifier);\n\ttexnum = R_LoadTextureImage (loadpath, identifier, 0, 0, texmode);\n\n\tif (!R_TextureReferenceIsValid(texnum)) {\n\t\tsnprintf (loadpath, sizeof(loadpath), \"textures/%s\", identifier);\n\t\ttexnum = R_LoadTextureImage (loadpath, identifier, 0, 0, texmode);\n\t}\n\n\treturn texnum;\n}\n\nvoid *Mod_LoadSpriteFrame (void * pin, mspriteframe_t **ppframe, int framenum)\n{\n\tdspriteframe_t *pinframe;\n\tmspriteframe_t *pspriteframe;\n\tchar basename[64], identifier[64];\n\tint width, height, size, origin[2], texmode;\n\ttexture_ref texnum;\n\n\ttexmode = TEX_MIPMAP | TEX_ALPHA;\n\tif (!gl_scaleModelTextures.value && !loadmodel->isworldmodel) {\n\t\ttexmode |= TEX_NOSCALE;\n\t}\n\n\tCOM_StripExtension(COM_SkipPath(loadmodel->name), basename, sizeof(basename));\n\n\tpinframe = (dspriteframe_t *) pin;\n\n\twidth = LittleLong (pinframe->width);\n\theight = LittleLong (pinframe->height);\n\tsize = width * height;\n\n\tpspriteframe = (mspriteframe_t *) Hunk_AllocName (sizeof (mspriteframe_t),loadname);\n\n\tmemset (pspriteframe, 0, sizeof (mspriteframe_t));\n\n\t*ppframe = pspriteframe;\n\n\tpspriteframe->width = width;\n\tpspriteframe->height = height;\n\torigin[0] = LittleLong (pinframe->origin[0]);\n\torigin[1] = LittleLong (pinframe->origin[1]);\n\n\tpspriteframe->up = origin[1];\n\tpspriteframe->down = origin[1] - height;\n\tpspriteframe->left = origin[0];\n\tpspriteframe->right = width + origin[0];\n\n\tsnprintf (identifier, sizeof(identifier), \"sprites/%s_%i\", basename, framenum);\n\ttexnum = Mod_LoadExternalSpriteSkin(identifier, framenum);\n\tif (!R_TextureReferenceIsValid(texnum) && width > 0 && height > 0) {\n\t\ttexnum = R_LoadTexture(identifier, width, height, (byte*)(pinframe + 1), texmode, 1);\n\t}\n\n\tpspriteframe->gl_texturenum = texnum;\n\tpspriteframe->gl_scalingS = 1.0f;\n\tpspriteframe->gl_scalingT = 1.0f;\n\n\treturn (void *) ((byte *) pinframe + sizeof (dspriteframe_t) + size);\n}\n\nvoid *Mod_LoadSpriteGroup (void * pin, mspriteframe_t **ppframe, int framenum) {\n\tdspritegroup_t *pingroup;\n\tmspritegroup_t *pspritegroup;\n\tint i, numframes;\n\tdspriteinterval_t *pin_intervals;\n\tfloat *poutintervals;\n\tvoid *ptemp;\n\n\tpingroup = (dspritegroup_t *) pin;\n\n\tnumframes = LittleLong (pingroup->numframes);\n\n\tif (numframes < 1) {\n\t\tHost_Error(\"Mod_LoadSpriteGroup: numframes < 1\");\n\t}\n\n\tpspritegroup = (mspritegroup_t *) Hunk_AllocName (sizeof (mspritegroup_t) +\n\t\t(numframes - 1) * sizeof (pspritegroup->frames[0]), loadname);\n\n\tpspritegroup->numframes = numframes;\n\n\t*ppframe = (mspriteframe_t *) pspritegroup;\n\n\tpin_intervals = (dspriteinterval_t *) (pingroup + 1);\n\n\tpoutintervals = (float *) Hunk_AllocName (numframes * sizeof (float), loadname);\n\n\tpspritegroup->intervals = poutintervals;\n\n\tfor (i = 0; i < numframes; i++) {\n\t\t*poutintervals = LittleFloat (pin_intervals->interval);\n\t\tif (*poutintervals <= 0.0) {\n\t\t\tHost_Error(\"Mod_LoadSpriteGroup: interval <= 0\");\n\t\t}\n\n\t\tpoutintervals++;\n\t\tpin_intervals++;\n\t}\n\n\tptemp = (void *) pin_intervals;\n\tfor (i = 0; i < numframes; i++) {\n\t\tptemp = Mod_LoadSpriteFrame(ptemp, &pspritegroup->frames[i], framenum * 100 + i);\n\t}\n\n\treturn ptemp;\n}\n\nvoid Mod_LoadSpriteModel (model_t *mod, void *buffer)\n{\n\tint i, j, version, numframes, size, start, offset, sztmp;\n\tdsprite_t *pin;\n\tmsprite_t *psprite;\n\tdspriteframetype_t *pframetype;\n\n\tmsprite2_t *psprite2;\n\n\n\t// remember point\n\tstart = Hunk_LowMark ();\n\n\n\tpin = (dsprite_t *)buffer;\n\n\tversion = LittleLong (pin->version);\n\n\tif (version != SPRITE_VERSION) {\n\t\tHost_Error (\"Mod_LoadSpriteModel: %s has wrong version number (%i should be %i)\", mod->name, version, SPRITE_VERSION);\n\t\treturn;\n\t}\n\n\tnumframes = LittleLong (pin->numframes);\n\n\tsize = sizeof (msprite_t) +\t(numframes - 1) * sizeof (psprite->frames);\n\n\tpsprite = (msprite_t *) Hunk_AllocName (size, loadname);\n\n\tpsprite->type = LittleLong (pin->type);\n\tpsprite->maxwidth = LittleLong (pin->width);\n\tpsprite->maxheight = LittleLong (pin->height);\n\tpsprite->beamlength = LittleFloat (pin->beamlength);\n\tmod->synctype = LittleLong (pin->synctype);\n\tpsprite->numframes = numframes;\n\n\tmod->mins[0] = mod->mins[1] = -psprite->maxwidth/2;\n\tmod->maxs[0] = mod->maxs[1] = psprite->maxwidth/2;\n\tmod->mins[2] = -psprite->maxheight/2;\n\tmod->maxs[2] = psprite->maxheight/2;\n\n\t// load the frames\n\tif (numframes < 1 || numframes > MAX_SPRITE_FRAMES)\n\t\tHost_Error (\"Mod_LoadSpriteModel: Invalid # of frames: %d\\n\", numframes);\n\n\tmod->numframes = numframes;\n\n\tpframetype = (dspriteframetype_t *)(pin + 1);\n\n\tsize = sizeof(msprite2_t);\n\n\tfor (i = 0; i < numframes; i++) {\n\t\tspriteframetype_t\tframetype;\n\n\t\tframetype = LittleLong (pframetype->type);\n\t\tpsprite->frames[i].type = frametype;\n\n\t\tif (frametype == SPR_SINGLE) {\n\t\t\tpframetype = (dspriteframetype_t *)\tMod_LoadSpriteFrame (pframetype + 1, &psprite->frames[i].frameptr, i);\n\t\t\tsize += (int)sizeof(mspriteframe_t);\n\t\t} else {\n\t\t\tpframetype = (dspriteframetype_t *)\tMod_LoadSpriteGroup (pframetype + 1, &psprite->frames[i].frameptr, i);\n\t\t\tsize += (((mspritegroup_t*)(psprite->frames[i].frameptr))->numframes) * (int)sizeof(mspriteframe2_t);\n\t\t}\n\t}\n\n\tpsprite2 = Q_malloc(size); // !!!!!!! Q_free\n\n\tpsprite2->type\t\t= psprite->type;\n\tpsprite2->maxwidth\t= psprite->maxwidth;\n\tpsprite2->maxheight\t= psprite->maxheight;\n\tpsprite2->numframes\t= psprite->numframes;\n\n\tfor (i = 0, offset = sizeof(msprite2_t); i < numframes; i++) {\n\t\tpsprite2->frames[i].type   = psprite->frames[i].type;\n\t\tpsprite2->frames[i].offset = offset;\n\n\t\tif (psprite2->frames[i].type == SPR_SINGLE) {\n\t\t\tpsprite2->frames[i].numframes = 1; // always one\n\n\t\t\tsztmp = (int)sizeof(mspriteframe_t);\n\t\t\tif (offset + sztmp > size)\n\t\t\t\tSys_Error(\"Mod_LoadSpriteModel: internal error\");\n\n\t\t\tmemcpy((byte*)(psprite2) + offset, psprite->frames[i].frameptr, sztmp);\n\t\t\toffset += sztmp;\n\t\t}\n\t\telse {\n\t\t\tpsprite2->frames[i].numframes = ((mspritegroup_t*)(psprite->frames[i].frameptr))->numframes;\n\n\t\t\tfor (j = 0; j < psprite2->frames[i].numframes; j++) {\n\t\t\t\tmspriteframe2_t spr;\n\n\t\t\t\tsztmp = (int)sizeof(mspriteframe2_t);\n\t\t\t\tif (offset + sztmp > size)\n\t\t\t\t\tSys_Error(\"Mod_LoadSpriteModel: internal error\");\n\n\t\t\t\tmemset(&spr, 0, sizeof(spr));\n\t\t\t\tspr.frame    = *(((mspritegroup_t*)(psprite->frames[i].frameptr))->frames[j]); // copy whole struct\n\t\t\t\tspr.interval =   ((mspritegroup_t*)(psprite->frames[i].frameptr))->intervals[j];\n\t\t\t\tmemcpy((byte*)(psprite2) + offset, &spr, sztmp);\n\t\t\t\toffset += sztmp;\n\t\t\t}\n\t\t}\n\t}\n\n\tmod->type = mod_sprite;\n\n\t// move the complete, relocatable model to the cache\n\tmod->cached_data = Q_malloc(size);\n\tif (mod->cached_data) {\n\t\tmemcpy(mod->cached_data, psprite2, size);\n\t}\n\n\tQ_free(psprite2);\n\tHunk_FreeToLowMark(start);\n}\n\nmspriteframe_t *R_GetSpriteFrame(entity_t *e, msprite2_t *psprite)\n{\n\tmspriteframe_t  *pspriteframe;\n\tmspriteframe2_t *pspriteframe2;\n\tint i, numframes, frame, offset;\n\tfloat fullinterval, targettime, time;\n\n\tframe = e->frame;\n\n\tif (frame >= psprite->numframes || frame < 0) {\n\t\tCom_DPrintf (\"R_GetSpriteFrame: no such frame %d (model %s)\\n\", frame, e->model->name);\n\t\treturn NULL;\n\t}\n\n\toffset    = psprite->frames[frame].offset;\n\tnumframes = psprite->frames[frame].numframes;\n\n\tif (offset < (int)sizeof(msprite2_t) || numframes < 1) {\n\t\tCom_Printf (\"R_GetSpriteFrame: wrong sprite\\n\");\n\t\treturn NULL;\n\t}\n\n\tif (psprite->frames[frame].type == SPR_SINGLE) {\n\t\tpspriteframe  = (mspriteframe_t* )((byte*)psprite + offset);\n\t}\n\telse {\n\t\tpspriteframe2 = (mspriteframe2_t*)((byte*)psprite + offset);\n\n\t\tfullinterval = pspriteframe2[numframes-1].interval;\n\n\t\ttime = r_refdef2.time;\n\n\t\t// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values\n\t\t// are positive, so we don't have to worry about division by 0\n\t\ttargettime = time - ((int) (time / fullinterval)) * fullinterval;\n\n\t\tfor (i = 0; i < (numframes - 1); i++) {\n\t\t\tif (pspriteframe2[i].interval > targettime) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tpspriteframe = &pspriteframe2[i].frame;\n\t}\n\n\treturn pspriteframe;\n}\n"
  },
  {
    "path": "src/r_state.h",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_R_STATE_HEADER\n#define EZQUAKE_R_STATE_HEADER\n\n#include \"r_local.h\"\n#include \"r_vao.h\"\n\n#define MAX_GLC_TEXTURE_UNIT_STATES    4\n#define MAX_GLC_ATTRIBUTES            16\n\n// rendering state\ntypedef enum {\n\tr_depthfunc_less,\n\tr_depthfunc_equal,\n\tr_depthfunc_lessorequal,\n\tr_depthfunc_count\n} r_depthfunc_t;\n\ntypedef enum {\n\tr_cullface_front,\n\tr_cullface_back,\n\n\tr_cullface_count\n} r_cullface_t;\n\ntypedef enum {\n\tr_blendfunc_overwrite,\n\tr_blendfunc_additive_blending,\n\tr_blendfunc_premultiplied_alpha,\n\tr_blendfunc_src_dst_color_dest_zero,\n\tr_blendfunc_src_dst_color_dest_one,\n\tr_blendfunc_src_dst_color_dest_src_color,\n\tr_blendfunc_src_zero_dest_one_minus_src_color,\n\tr_blendfunc_src_zero_dest_src_color,\n\tr_blendfunc_src_one_dest_zero,\n\tr_blendfunc_src_zero_dest_one,\n\tr_blendfunc_src_one_dest_one_minus_src_color,\n\n\tr_blendfunc_count\n} r_blendfunc_t;\n\ntypedef enum {\n\tr_polygonmode_fill,\n\tr_polygonmode_line,\n\n\tr_polygonmode_count\n} r_polygonmode_t;\n\ntypedef enum {\n\tr_polygonoffset_disabled,\n\tr_polygonoffset_standard,\n\tr_polygonoffset_outlines,\n\n\tr_polygonoffset_count\n} r_polygonoffset_t;\n\ntypedef enum {\n\tr_alphatest_func_always,\n\tr_alphatest_func_greater,\n\n\tr_alphatest_func_count\n} r_alphatest_func_t;\n\ntypedef enum {\n\tr_texunit_mode_blend,\n\tr_texunit_mode_replace,\n\tr_texunit_mode_modulate,\n\tr_texunit_mode_decal,\n\tr_texunit_mode_add,\n\n\tr_texunit_mode_count\n} r_texunit_mode_t;\n\ntypedef enum {\n\tr_fogmode_disabled,\n\tr_fogmode_enabled,\n\n\tr_fogmode_count\n} r_fogmode_t;\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\ntypedef struct glc_vertex_array_element_s {\n\tqbool enabled;\n\n\tr_buffer_id buf;\n\tint size;\n\tunsigned int type;\n\tint stride;\n\tvoid* pointer_or_offset;\n} glc_vertex_array_element_t;\n\ntypedef struct glc_attribute_s {\n\tqbool enabled;\n\n\tint location;\n} glc_attribute_t;\n#endif\n\ntypedef struct rendering_state_s {\n\tstruct {\n\t\tr_depthfunc_t func;\n\t\tdouble nearRange;\n\t\tdouble farRange;\n\t\tqbool test_enabled;\n\t\tqbool mask_enabled;\n\t} depth;\n\n\t// FIXME: currentWidth & currentHeight should be initialised to dimensions of window\n\tint currentViewportX, currentViewportY;\n\tint currentViewportWidth, currentViewportHeight;\n\n\tint fullScreenViewportX, fullScreenViewportY;\n\tint fullScreenViewportWidth, fullScreenViewportHeight;\n\n\tqbool framebuffer_srgb;\n\n\tstruct {\n\t\tqbool smooth;\n\t\tfloat width;\n\t\tqbool flexible_width;\n\t} line;\n\n\tstruct {\n\t\tr_fogmode_t mode;\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t\tfloat color[4];\n\t\tfloat density;\n\t\tfogcalc_t calculation;\n\t\tfloat start;\n\t\tfloat end;\n#endif\n\t} fog;\n\n\tstruct {\n\t\tr_cullface_t mode;\n\t\tqbool enabled;\n\t} cullface;\n\n\tr_blendfunc_t blendFunc;\n\n\tstruct {\n\t\tr_polygonoffset_t option;\n\t\tfloat factor;\n\t\tfloat units;\n\t\tqbool fillEnabled;\n\t\tqbool lineEnabled;\n\t} polygonOffset;\n\n\tr_polygonmode_t polygonMode;\n\tfloat clearColor[4];\n\tfloat color[4];\n\tqbool blendingEnabled;\n\tqbool colorMask[4];\n\n\tr_vao_id vao_id;\n\n\t// GLC only...\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tstruct {\n\t\tqbool enabled;\n\t\tr_alphatest_func_t func;\n\t\tfloat value;\n\t} alphaTesting;\n\n\tstruct {\n\t\tqbool enabled;\n\t\tr_texunit_mode_t mode;\n\n\t\tglc_vertex_array_element_t va;\n\t} textureUnits[MAX_GLC_TEXTURE_UNIT_STATES];\n\n\tqbool colorValid;\n\n\tglc_vertex_array_element_t vertex_array;\n\tglc_vertex_array_element_t color_array;\n\tglc_vertex_array_element_t normal_array;\n#endif\n\n\t// always false if not classic\n\tqbool glc_vao_force_rebind;\n\n\t// misc (texture downloads, screenshots & atlas building)\n\tint pack_alignment;\n\n\t// meta\n\tqbool initialized;\n\tchar name[32];\n} rendering_state_t;\n\ntypedef enum {\n\tr_state_null,\n\n\tr_state_default_opengl,\n\n\tr_state_default_3d,\n\tr_state_sprites_textured,\n\n\tr_state_default_2d,\n\tr_state_brighten_screen,\n\tr_state_line,\n\tr_state_hud_images_glc,\n\tr_state_hud_images_glc_non_glsl,\n\tr_state_hud_images_alphatested_glc,\n\tr_state_hud_images_alphatested_glc_non_glsl,\n\tr_state_poly_blend,\n\tr_state_hud_images_glm,\n\tr_state_hud_polygons_glm,\n\n\tr_state_sky_fast,\n\tr_state_skydome_background_pass,\n\tr_state_skydome_cloud_pass,\n\tr_state_skydome_single_pass,\n\tr_state_skydome_single_pass_program,\n\tr_state_skydome_zbuffer_pass,\n\tr_state_skydome_zbuffer_pass_fogged,\n\tr_state_skybox,\n\n\tr_state_sky_fast_bmodel,\n\tr_state_skydome_single_pass_bmodel,\n\tr_state_skydome_cloud_pass_bmodel,\n\tr_state_skydome_background_pass_bmodel,\n\n\tr_state_world_texture_chain,\n\tr_state_world_texture_chain_fullbright,\n\tr_state_world_blend_lightmaps,\n\tr_state_world_caustics,\n\n\tr_state_world_fast_opaque_water,\n\tr_state_world_fast_translucent_water,\n\tr_state_world_opaque_water,\n\tr_state_world_translucent_water,\n\tr_state_world_alpha_surfaces,\n\tr_state_world_fullbrights,\n\tr_state_world_lumas,\n\tr_state_world_details,\n\tr_state_world_outline,\n\n\tr_state_world_singletexture_glc,\n\tr_state_world_material_lightmap,\n\tr_state_world_material_lightmap_luma,\n\tr_state_world_material_lightmap_fb,\n\tr_state_world_material_fb_lightmap,\n\tr_state_world_material_luma_lightmap,\n\n\tr_state_alpha_surfaces_offset_glm,\n\tr_state_opaque_surfaces_offset_glm,\n\tr_state_alpha_surfaces_glm,\n\tr_state_opaque_surfaces_glm,\n\n\tr_state_aliasmodel_caustics,\n\n\tr_state_aliasmodel_powerupshell,\n\tr_state_weaponmodel_powerupshell,\n\n\t// meag: no multitexture_additive: multitex currently only for .mdl files, additive only for .md3\n\tr_state_aliasmodel_notexture_opaque,\n\tr_state_aliasmodel_notexture_transparent,\n\tr_state_aliasmodel_notexture_additive,\n\tr_state_aliasmodel_singletexture_opaque,\n\tr_state_aliasmodel_singletexture_transparent,\n\tr_state_aliasmodel_singletexture_additive,\n\tr_state_aliasmodel_multitexture_opaque,\n\tr_state_aliasmodel_multitexture_transparent,\n\tr_state_aliasmodel_transparent_zpass,\n\tr_state_weaponmodel_singletexture_opaque,\n\tr_state_weaponmodel_singletexture_transparent,\n\tr_state_weaponmodel_singletexture_additive,\n\tr_state_weaponmodel_multitexture_opaque,\n\tr_state_weaponmodel_multitexture_transparent,\n\tr_state_weaponmodel_transparent_zpass,\n\n\tr_state_aliasmodel_shadows,\n\tr_state_aliasmodel_outline,\n\tr_state_aliasmodel_outline_spec,\n\tr_state_weaponmodel_outline,\n\n\tr_state_aliasmodel_opaque_batch,\n\tr_state_aliasmodel_translucent_batch,\n\tr_state_aliasmodel_translucent_batch_zpass,\n\n\tr_state_aliasmodel_additive_batch,\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tr_state_postprocess_bloom1,\n\tr_state_postprocess_bloom2,\n\tr_state_postprocess_bloom_darkenpass,\n\tr_state_postprocess_bloom_blurpass,\n\tr_state_postprocess_bloom_downsample,\n\tr_state_postprocess_bloom_downsample_blend,\n\tr_state_postprocess_bloom_restore,\n\tr_state_postprocess_bloom_draweffect,\n#endif\n\n\tr_state_light_bubble,\n\tr_state_chaticon,\n\tr_state_particles_classic,\n\tr_state_particles_qmb_textured_blood,\n\tr_state_particles_qmb_textured,\n\tr_state_particles_qmb_untextured,\n\tr_state_coronas,\n\n\tr_state_drawflat_with_lightmaps_glc,\n\tr_state_drawflat_without_lightmaps_glc,\n\tr_state_drawflat_without_lightmaps_unfogged_glc,\n\n\tr_state_fx_world_geometry,\n\n\tr_state_count\n} r_state_id;\n\nrendering_state_t* R_InitRenderingState(r_state_id id, qbool default_state, const char* name, r_vao_id vao);\nrendering_state_t* R_CopyRenderingState(r_state_id id, r_state_id original_id, const char* name);\nrendering_state_t* R_Init3DSpriteRenderingState(r_state_id id, const char* name);\nvoid R_ApplyRenderingState(r_state_id id);\nvoid R_BufferInvalidateBoundState(r_buffer_id ref);\n\nvoid R_CustomColor(float r, float g, float b, float a);\nvoid R_CustomColor4ubv(const byte* color);\nvoid R_CustomLineWidth(float width);\nvoid R_EnableScissorTest(int x, int y, int width, int height);\nvoid R_DisableScissorTest(void);\nvoid R_ClearColor(float r, float g, float b, float a);\nvoid R_CustomPolygonOffset(r_polygonoffset_t mode);\n\nvoid R_Hud_Initialise(void);\n\nvoid R_Viewport(int x, int y, int width, int height);\nvoid R_SetFullScreenViewport(int x, int y, int width, int height);\nvoid R_GetFullScreenViewport(int* viewport);\n\n#endif // EZQUAKE_R_STATE_HEADER\n"
  },
  {
    "path": "src/r_states.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n// gl_state_resets.c\n// moving state init/reset to here with intention of getting rid of all resets\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_state.h\"\n#include \"r_trace.h\"\n#include \"r_matrix.h\"\n#include \"glc_matrix.h\"\n#include \"glc_state.h\"\n#include \"tr_types.h\"\n\nstatic void R_InitialiseWorldStates(void)\n{\n\trendering_state_t* state;\n\n\tstate = R_InitRenderingState(r_state_world_texture_chain, true, \"worldTextureChainState\", vao_brushmodel);\n\tstate->fog.mode = r_fogmode_enabled;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_replace);\n\tR_GLC_TextureUnitSet(state, 1, true, r_texunit_mode_blend);\n\n\tstate = R_InitRenderingState(r_state_world_texture_chain_fullbright, true, \"worldTextureChainFullbrightState\", vao_brushmodel);\n\tstate->fog.mode = r_fogmode_enabled;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_replace);\n\n\tstate = R_InitRenderingState(r_state_world_blend_lightmaps, true, \"blendLightmapState\", vao_brushmodel_lightmap_pass);\n\tstate->depth.mask_enabled = false;\n\tstate->depth.func = r_depthfunc_equal;\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_src_zero_dest_one_minus_src_color;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_replace);\n\n\tstate = R_InitRenderingState(r_state_world_caustics, true, \"causticsState\", vao_brushmodel_simpletex);\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_decal);\n\tstate->blendFunc = r_blendfunc_src_dst_color_dest_src_color;\n\tstate->blendingEnabled = true;\n\n\tstate = R_InitRenderingState(r_state_aliasmodel_caustics, true, \"aliasModelCausticsState\", vao_aliasmodel);\n\tstate->blendFunc = r_blendfunc_src_dst_color_dest_src_color;\n\tstate->blendingEnabled = true;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_replace);\n\tR_GLC_TextureUnitSet(state, 1, true, r_texunit_mode_decal);\n\n\tstate = R_InitRenderingState(r_state_world_fast_opaque_water, true, \"fastWaterSurfacesState\", vao_brushmodel_simpletex);\n\tstate->depth.test_enabled = true;\n\tstate->fog.mode = r_fogmode_enabled;\n\n\tstate = R_CopyRenderingState(r_state_world_opaque_water, r_state_world_fast_opaque_water, \"waterSurfacesState\");\n\tstate->cullface.enabled = false;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_replace);\n\n\tstate = R_CopyRenderingState(r_state_world_translucent_water, r_state_world_fast_opaque_water, \"translucentWaterSurfacesState\");\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tstate->blendingEnabled = true;\n\tstate->depth.mask_enabled = false; // FIXME: water-alpha < 0.9 only?\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n\n\tstate = R_CopyRenderingState(r_state_world_fast_translucent_water, r_state_world_translucent_water, \"translucentFastWater\");\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tstate->blendingEnabled = true;\n\tR_GLC_TextureUnitSet(state, 0, false, r_texunit_mode_replace);\n\n\tstate = R_InitRenderingState(r_state_world_alpha_surfaces, true, \"alphaChainState\", vao_brushmodel);\n\tR_GLC_ConfigureAlphaTesting(state, true, r_alphatest_func_greater, 0.333f);\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_replace);\n\tif (glConfig.texture_units >= 2) {\n\t\tR_GLC_TextureUnitSet(state, 1, true, r_texunit_mode_blend); // modulate if !inv_lmaps\n\t}\n\n\tstate = R_InitRenderingState(r_state_world_fullbrights, true, \"fullbrightsState\", vao_brushmodel);\n\tstate->depth.mask_enabled = false;\n\tR_GLC_EnableAlphaTesting(state);\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_replace);\n\n\tstate = R_InitRenderingState(r_state_world_lumas, true, \"lumasState\", vao_brushmodel);\n\tstate->depth.mask_enabled = false;\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_additive_blending;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_replace);\n\n\tstate = R_InitRenderingState(r_state_world_details, true, \"detailPolyState\", vao_brushmodel_details);\n\tstate->fog.mode = r_fogmode_enabled;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_decal);\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_src_dst_color_dest_src_color;\n\n\tstate = R_InitRenderingState(r_state_world_outline, true, \"mapOutlineState\", vao_brushmodel);\n\tstate->polygonOffset.option = r_polygonoffset_outlines;\n\tstate->polygonMode = r_polygonmode_line;\n\tstate->depth.mask_enabled = false;\n\tstate->depth.test_enabled = false;\n\tstate->cullface.enabled = false;\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\n\tstate = R_InitRenderingState(r_state_alpha_surfaces_offset_glm, true, \"glmAlphaOffsetWorldState\", vao_brushmodel);\n\tstate->polygonOffset.option = r_polygonoffset_standard;\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\n\tstate = R_InitRenderingState(r_state_opaque_surfaces_offset_glm, true, \"glmOffsetWorldState\", vao_brushmodel);\n\tstate->polygonOffset.option = r_polygonoffset_standard;\n\n\tstate = R_InitRenderingState(r_state_alpha_surfaces_glm, true, \"glmAlphaWorldState\", vao_brushmodel);\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tstate->polygonOffset.option = r_polygonoffset_standard;\n\n\tR_InitRenderingState(r_state_opaque_surfaces_glm, true, \"glmWorldState\", vao_brushmodel);\n}\n\nstatic void R_Initialise2DStates(void)\n{\n\trendering_state_t* state;\n\tr_vao_id postprocess_vao = R_UseImmediateOpenGL() ? vao_none : vao_postprocess;\n\n\tstate = R_InitRenderingState(r_state_default_2d, true, \"default2DState\", postprocess_vao);\n\tstate->depth.test_enabled = false;\n\tstate->cullface.enabled = false;\n\n\tstate = R_InitRenderingState(r_state_brighten_screen, true, \"brightenScreenState\", postprocess_vao);\n\tstate->depth.test_enabled = false;\n\tstate->cullface.enabled = false;\n\tR_GLC_EnableAlphaTesting(state); // really?\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_src_dst_color_dest_one;\n\n\tstate = R_InitRenderingState(r_state_line, true, \"lineState\", vao_hud_lines);\n\tstate->depth.test_enabled = false;\n\tstate->cullface.enabled = false;\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tstate->line.flexible_width = true;\n\n\tif (R_UseImmediateOpenGL()) {\n\t\tstate->vao_id = vao_none;\n\t}\n\n\tstate = R_InitRenderingState(r_state_hud_images_glc, true, \"glcImageDrawState\", vao_hud_images);\n\tstate->depth.test_enabled = false;\n\tstate->cullface.enabled = false;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\n\tstate = R_CopyRenderingState(r_state_hud_images_alphatested_glc, r_state_hud_images_glc, \"glcAlphaTestedImageDrawState\");\n\tR_GLC_EnableAlphaTesting(state);\n\n\tstate = R_CopyRenderingState(r_state_hud_images_glc_non_glsl, r_state_hud_images_glc, \"glcImageDrawStateNonGLSL\");\n\tstate->vao_id = vao_hud_images_non_glsl;\n\n\tstate = R_CopyRenderingState(r_state_hud_images_alphatested_glc_non_glsl, r_state_hud_images_alphatested_glc, \"glcAlphaTestedImageDrawStateNonGLSL\");\n\tstate->vao_id = vao_hud_images_non_glsl;\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tstate = R_InitRenderingState(r_state_postprocess_bloom1, true, \"glcBloomState\", postprocess_vao);\n\tstate->depth.test_enabled = false;\n\tstate->cullface.enabled = false;\n\tR_GLC_EnableAlphaTesting(state);\n\tstate->blendingEnabled = false;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_replace);\n\n\tstate = R_CopyRenderingState(r_state_postprocess_bloom_darkenpass, r_state_postprocess_bloom1, \"glcBloomState-darken\");\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_src_dst_color_dest_zero;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n\n\tstate = R_CopyRenderingState(r_state_postprocess_bloom_blurpass, r_state_postprocess_bloom1, \"glcBloomState-blur\");\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_src_one_dest_one_minus_src_color;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n\n\tstate = R_CopyRenderingState(r_state_postprocess_bloom_downsample, r_state_postprocess_bloom1, \"glcBloomState-downsamp\");\n\n\tstate = R_CopyRenderingState(r_state_postprocess_bloom_downsample_blend, r_state_postprocess_bloom1, \"glcBloomState-downsamp-blend\");\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_additive_blending;\n\n\tstate = R_CopyRenderingState(r_state_postprocess_bloom_restore, r_state_postprocess_bloom1, \"glcBloomState-restore\");\n\n\tstate = R_CopyRenderingState(r_state_postprocess_bloom_draweffect, r_state_postprocess_bloom1, \"glcBloomState-restore\");\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_additive_blending;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n#endif\n\n\tstate = R_InitRenderingState(r_state_poly_blend, true, \"polyBlendState\", postprocess_vao);\n\tstate->depth.test_enabled = false;\n\tstate->cullface.enabled = false;\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\n\tstate = R_InitRenderingState(r_state_hud_images_glm, true, \"glmImageDrawState\", vao_hud_images);\n\tstate->depth.test_enabled = false;\n\tstate->cullface.enabled = false;\n\tR_GLC_DisableAlphaTesting(state);\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\n\tstate = R_CopyRenderingState(r_state_hud_polygons_glm, r_state_hud_images_glm, \"glmPolygonDrawState\");\n\tstate->vao_id = vao_hud_polygons;\n\n\tstate = R_InitRenderingState(r_state_fx_world_geometry, true, \"r_state_fx_world_geometry\", postprocess_vao);\n\tstate->depth.test_enabled = false;\n\tstate->depth.mask_enabled = false;\n\tstate->cullface.enabled = false;\n\tR_GLC_EnableAlphaTesting(state); // really?\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n}\n\nstatic void R_InitialiseSpriteStates(void)\n{\n\trendering_state_t* state;\n\n\t// Simple items\n\tstate = R_Init3DSpriteRenderingState(r_state_sprites_textured, \"sprite_entity_state\");\n\tR_GLC_ConfigureAlphaTesting(state, true, r_alphatest_func_greater, 0.333f);\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_replace);\n\n\t// Standard particles\n\tstate = R_Init3DSpriteRenderingState(r_state_particles_classic, \"particle_state\");\n\tstate->depth.mask_enabled = false;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n\n\t// QMB particles\n\tstate = R_Init3DSpriteRenderingState(r_state_particles_qmb_textured_blood, \"qmb-textured-blood\");\n\tstate->blendFunc = r_blendfunc_src_zero_dest_one_minus_src_color;\n\tstate->depth.mask_enabled = false;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n\n\tstate = R_Init3DSpriteRenderingState(r_state_particles_qmb_textured, \"qmb-textured\");\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tstate->depth.mask_enabled = false;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n\n\tstate = R_Init3DSpriteRenderingState(r_state_particles_qmb_untextured, \"qmb-untextured\");\n\tstate->depth.mask_enabled = false;\n\n\t// Flashblend bubbles\n\tstate = R_Init3DSpriteRenderingState(r_state_light_bubble, \"bubble-state\");\n\tstate->depth.test_enabled = true;\n\tstate->depth.mask_enabled = false;\n\n\t// Chaticons\n\tstate = R_Init3DSpriteRenderingState(r_state_chaticon, \"chaticon_state\");\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n\n\t// Coronas\n\tstate = R_Init3DSpriteRenderingState(r_state_coronas, \"coronaState\");\n\tstate->blendFunc = r_blendfunc_additive_blending;\n\tstate->depth.mask_enabled = false;\n\tstate->depth.test_enabled = false;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n}\n\nstatic void R_InitialiseEntityStates(void)\n{\n\trendering_state_t* state;\n\n\tstate = R_InitRenderingState(r_state_aliasmodel_powerupshell, true, \"powerupShellState\", vao_aliasmodel);\n\tstate->fog.mode = r_fogmode_enabled;\n\tstate->cullface.enabled = true;\n\tstate->cullface.mode = r_cullface_front;\n\tR_GLC_DisableAlphaTesting(state);\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_additive_blending;\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n\n\tstate = R_CopyRenderingState(r_state_aliasmodel_outline, r_state_aliasmodel_powerupshell, \"aliasmodel-outline\");\n\tstate->fog.mode = r_fogmode_enabled;\n\tstate->blendingEnabled = false;\n\tstate->cullface.mode = r_cullface_back;\n\n\tstate = R_CopyRenderingState(r_state_aliasmodel_outline_spec, r_state_aliasmodel_outline, \"aliasmodel-outline-spec\");\n\tstate->cullface.mode = r_cullface_front;\n\tstate->depth.test_enabled = false;\n\n\tstate = R_CopyRenderingState(r_state_weaponmodel_outline, r_state_aliasmodel_outline, \"weaponmodel-outline\");\n\tstate->depth.farRange = R_UseImmediateOpenGL() ? 0.3f : state->depth.farRange;\n\n\tstate = R_CopyRenderingState(r_state_weaponmodel_powerupshell, r_state_aliasmodel_powerupshell, \"weaponmodel-shell\");\n\tstate->depth.farRange = R_UseImmediateOpenGL() ? 0.3f : state->depth.farRange;\n\n\tstate = R_InitRenderingState(r_state_aliasmodel_notexture_opaque, true, \"opaqueAliasModelNoTexture\", vao_aliasmodel);\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tstate->blendingEnabled = false;\n\tR_GLC_DisableAlphaTesting(state);\n\tstate->polygonOffset.option = r_polygonoffset_disabled;\n\tstate->cullface.enabled = true;\n\tstate->cullface.mode = r_cullface_front;\n\tstate->polygonMode = r_polygonmode_fill;\n\tstate->line.smooth = false;\n\tstate->fog.mode = r_fogmode_enabled;\n\n\tstate = R_CopyRenderingState(r_state_aliasmodel_transparent_zpass, r_state_aliasmodel_notexture_opaque, \"aliasModelZPass\");\n\tstate->colorMask[0] = state->colorMask[1] = state->colorMask[2] = state->colorMask[3] = false;\n\n\tstate = R_CopyRenderingState(r_state_aliasmodel_singletexture_opaque, r_state_aliasmodel_notexture_opaque, \"opaqueAliasModelSingleTex\");\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n\n\tstate = R_CopyRenderingState(r_state_aliasmodel_multitexture_opaque, r_state_aliasmodel_notexture_opaque, \"opaqueAliasModelMultiTex\");\n\tR_GLC_TextureUnitSet(state, 0, true, r_texunit_mode_modulate);\n\tR_GLC_TextureUnitSet(state, 1, true, r_texunit_mode_decal);\n\n\t// transparent\n\tstate = R_CopyRenderingState(r_state_aliasmodel_notexture_transparent, r_state_aliasmodel_notexture_opaque, \"transparentAliasModelNoTex\");\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tstate = R_CopyRenderingState(r_state_aliasmodel_singletexture_transparent, r_state_aliasmodel_singletexture_opaque, \"transparentAliasModelSingleTex\");\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tstate = R_CopyRenderingState(r_state_aliasmodel_multitexture_transparent, r_state_aliasmodel_multitexture_opaque, \"transparentAliasModelMultiTex\");\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\n\t// additive\n\tstate = R_CopyRenderingState(r_state_aliasmodel_notexture_additive, r_state_aliasmodel_notexture_opaque, \"additiveAliasModelNoTex\");\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_additive_blending;\n\tstate = R_CopyRenderingState(r_state_aliasmodel_singletexture_additive, r_state_aliasmodel_singletexture_opaque, \"additiveAliasModelSingleTex\");\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_additive_blending;\n\n\tstate = R_CopyRenderingState(r_state_weaponmodel_singletexture_opaque, r_state_aliasmodel_singletexture_opaque, \"weaponModelSingleOpaque\");\n\tstate->depth.farRange = R_UseImmediateOpenGL() ? 0.3f : state->depth.farRange;\n\tstate = R_CopyRenderingState(r_state_weaponmodel_multitexture_opaque, r_state_weaponmodel_singletexture_opaque, \"weaponModelMultiOpaque\");\n\tstate->depth.farRange = R_UseImmediateOpenGL() ? 0.3f : state->depth.farRange;\n\n\t// transparent\n\tstate = R_CopyRenderingState(r_state_weaponmodel_singletexture_transparent, r_state_aliasmodel_singletexture_transparent, \"weaponModelSingleTransparent\");\n\tstate->depth.farRange = R_UseImmediateOpenGL() ? 0.3f : state->depth.farRange;\n\tstate = R_CopyRenderingState(r_state_weaponmodel_multitexture_transparent, r_state_weaponmodel_singletexture_transparent, \"weaponModelMultiTransparent\");\n\tstate->depth.farRange = R_UseImmediateOpenGL() ? 0.3f : state->depth.farRange;\n\tstate = R_CopyRenderingState(r_state_weaponmodel_transparent_zpass, r_state_weaponmodel_singletexture_transparent, \"weaponModelZPass\");\n\tR_GLC_TextureUnitSet(state, 0, false, r_texunit_mode_replace);\n\tstate->colorMask[0] = state->colorMask[1] = state->colorMask[2] = state->colorMask[3] = false;\n\n\t// additive\n\tstate = R_CopyRenderingState(r_state_weaponmodel_singletexture_additive, r_state_aliasmodel_singletexture_additive, \"weaponModelSingleAdditive\");\n\tstate->depth.farRange = R_UseImmediateOpenGL() ? 0.3f : state->depth.farRange;\n\n\tstate = R_InitRenderingState(r_state_aliasmodel_shadows, true, \"aliasModelShadowState\", vao_aliasmodel);\n\tstate->polygonOffset.option = r_polygonoffset_disabled;\n\tstate->cullface.enabled = true;\n\tstate->cullface.mode = r_cullface_front;\n\tstate->polygonMode = r_polygonmode_fill;\n\tstate->line.smooth = false;\n\tstate->fog.mode = r_fogmode_enabled;\n\tR_GLC_DisableAlphaTesting(state);\n\tstate->blendingEnabled = true;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tstate->color[0] = state->color[1] = state->color[2] = 0;\n\tstate->color[3] = 0.5f;\n\n\tstate = R_InitRenderingState(r_state_aliasmodel_opaque_batch, true, \"aliasModelBatchState\", vao_aliasmodel);\n\tstate->cullface.mode = r_cullface_front;\n\tstate->cullface.enabled = true;\n\tstate->depth.mask_enabled = true;\n\tstate->depth.test_enabled = true;\n\tstate->blendingEnabled = false;\n\n\tstate = R_CopyRenderingState(r_state_aliasmodel_translucent_batch, r_state_aliasmodel_opaque_batch, \"aliasModelTranslucentBatchState\");\n\tstate->depth.mask_enabled = false;\n\tstate->blendFunc = r_blendfunc_premultiplied_alpha;\n\tstate->blendingEnabled = true;\n\n\tstate = R_CopyRenderingState(r_state_aliasmodel_translucent_batch_zpass, r_state_aliasmodel_opaque_batch, \"aliasModelTranslucentBatchZPass\");\n\tstate->colorMask[0] = state->colorMask[1] = state->colorMask[2] = state->colorMask[3] = false;\n\n\tstate = R_CopyRenderingState(r_state_aliasmodel_additive_batch, r_state_aliasmodel_opaque_batch, \"aliasModelTranslucentBatchState\");\n\tstate->blendFunc = r_blendfunc_additive_blending;\n\tstate->blendingEnabled = true;\n}\n\nstatic void R_InitialiseBrushModelStates(void)\n{\n\trendering_state_t* current;\n\n\tcurrent = R_InitRenderingState(r_state_drawflat_without_lightmaps_glc, true, \"drawFlatNoLightmapState\", vao_brushmodel);\n\tcurrent->fog.mode = r_fogmode_enabled;\n\n\tcurrent = R_InitRenderingState(r_state_drawflat_without_lightmaps_unfogged_glc, true, \"drawFlatNoLightmapStateUnfogged\", vao_brushmodel);\n\tcurrent->fog.mode = r_fogmode_disabled;\n\n\tcurrent = R_InitRenderingState(r_state_drawflat_with_lightmaps_glc, true, \"drawFlatLightmapState\", vao_brushmodel_lightmap_pass);\n\tcurrent->fog.mode = r_fogmode_enabled;\n\tR_GLC_TextureUnitSet(current, 0, true, r_texunit_mode_blend);\n\n\t// Single-texture: all of these are the same so we don't need to bother about others\n\tcurrent = R_InitRenderingState(r_state_world_singletexture_glc, true, \"world:singletex\", vao_brushmodel);\n\tcurrent->fog.mode = r_fogmode_enabled;\n\tR_GLC_TextureUnitSet(current, 0, true, r_texunit_mode_replace);\n\n\t// material * lightmap\n\tcurrent = R_InitRenderingState(r_state_world_material_lightmap, true, \"r_state_world_material_lightmap\", vao_brushmodel_lm_unit1);\n\tcurrent->fog.mode = r_fogmode_enabled;\n\tR_GLC_TextureUnitSet(current, 0, true, r_texunit_mode_replace);\n\tR_GLC_TextureUnitSet(current, 1, glConfig.texture_units >= 2, r_texunit_mode_blend);\n\n\t// material * lightmap + luma\n\tcurrent = R_InitRenderingState(r_state_world_material_lightmap_luma, true, \"r_state_world_material_lightmap_luma\", vao_brushmodel_lm_unit1);\n\tcurrent->fog.mode = r_fogmode_enabled;\n\tR_GLC_TextureUnitSet(current, 0, true, r_texunit_mode_replace);\n\tR_GLC_TextureUnitSet(current, 1, glConfig.texture_units >= 2, r_texunit_mode_blend);\n\tR_GLC_TextureUnitSet(current, 2, glConfig.texture_units >= 3, r_texunit_mode_add);\n\n\t// r_state_world_material_lightmap_fb\n\tcurrent = R_CopyRenderingState(r_state_world_material_lightmap_fb, r_state_world_material_lightmap_luma, \"r_state_world_material_lightmap_fb\");\n\tR_GLC_TextureUnitSet(current, 2, glConfig.texture_units >= 3, r_texunit_mode_decal);\n\n\t// no fullbrights, 3 units: blend(material + luma, lightmap) \n\tcurrent = R_InitRenderingState(r_state_world_material_fb_lightmap, true, \"r_state_world_material_fb_lightmap\", vao_brushmodel);\n\tcurrent->fog.mode = r_fogmode_enabled;\n\tR_GLC_TextureUnitSet(current, 0, true, r_texunit_mode_replace);\n\tR_GLC_TextureUnitSet(current, 1, glConfig.texture_units >= 2, r_texunit_mode_add);\n\tR_GLC_TextureUnitSet(current, 2, glConfig.texture_units >= 3, r_texunit_mode_blend);\n\n\t// lumas enabled, 3 units\n\tcurrent = R_InitRenderingState(r_state_world_material_luma_lightmap, true, \"r_state_world_material_luma_lightmap\", vao_brushmodel);\n\tcurrent->fog.mode = r_fogmode_enabled;\n\tR_GLC_TextureUnitSet(current, 0, true, r_texunit_mode_replace);\n\tR_GLC_TextureUnitSet(current, 1, glConfig.texture_units >= 2, r_texunit_mode_add);\n\tR_GLC_TextureUnitSet(current, 2, glConfig.texture_units >= 3, r_texunit_mode_blend);\n}\n\nvoid R_InitialiseStates(void)\n{\n\tR_InitRenderingState(r_state_default_3d, true, \"default3DState\", vao_none);\n\n\tR_InitialiseSpriteStates();\n\tR_InitialiseBrushModelStates();\n\tR_Initialise2DStates();\n\tR_InitialiseEntityStates();\n\tR_InitialiseWorldStates();\n\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tGLC_InitialiseSkyStates();\n#endif\n}\n\nfloat R_WaterAlpha(void)\n{\n\textern cvar_t r_wateralpha;\n\n\treturn bound((1 - r_refdef2.max_watervis), r_wateralpha.value, 1);\n}\n\nvoid R_StateDefault3D(void)\n{\n\tR_TraceResetRegion(false);\n\n\tR_TraceEnterFunctionRegion;\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_PauseMatrixUpdate();\n\t}\n#endif\n\tR_IdentityModelView();\n\tR_RotateModelview(-90, 1, 0, 0);\t    // put Z going up\n\tR_RotateModelview(90, 0, 0, 1);\t    // put Z going up\n\tR_RotateModelview(-r_refdef.viewangles[2], 1, 0, 0);\n\tR_RotateModelview(-r_refdef.viewangles[0], 0, 1, 0);\n\tR_RotateModelview(-r_refdef.viewangles[1], 0, 0, 1);\n\tR_TranslateModelview(-r_refdef.vieworg[0], -r_refdef.vieworg[1], -r_refdef.vieworg[2]);\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\tGLC_ResumeMatrixUpdate();\n\t\tGLC_LoadModelviewMatrix();\n\t}\n#endif\n\tR_TraceLeaveFunctionRegion;\n}\n\nvoid R_StateBeginAlphaLineRGB(float thickness)\n{\n\tif (thickness > 0.0) {\n\t\tR_CustomLineWidth(thickness);\n\t}\n}\n\nvoid R_StateBeginDrawAliasModel(const entity_t* ent, aliashdr_t* paliashdr)\n{\n\textern cvar_t r_viewmodelsize;\n\n\tR_TraceEnterRegion(__func__, true);\n\n\tR_RotateForEntity(ent);\n\tif (ent->renderfx & RF_WEAPONMODEL) {\n\t\tR_ScaleModelview(0.5 + bound(0, r_viewmodelsize.value, 1) / 2, 1, 1);\n\t}\n\n\tR_TraceLeaveFunctionRegion;\n}\n"
  },
  {
    "path": "src/r_texture.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// \n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_local.h\"\n#include \"r_texture.h\"\n#include \"r_texture_internal.h\"\n#include \"r_lighting.h\"\n#include \"r_aliasmodel.h\"\n#include \"r_brushmodel_sky.h\"\n#include \"r_trace.h\"\n#include \"r_state.h\"\n#include \"gl_texture.h\"\n#include \"r_renderer.h\"\n#include \"image.h\"\n\n#ifdef WITH_RENDERING_TRACE\n#include \"utils.h\"\n#endif\n\nstatic void R_AllocateTextureNames(gltexture_t* glt);\n\ngltexture_t  gltextures[MAX_GLTEXTURES];\nstatic int   numgltextures = 1;\nstatic int   next_free_texture = 0;\nstatic texture_ref invalid_texture_reference = { 0 };\n\nstatic void R_ClearModelTextureReferences(model_t* mod, qbool all_textures)\n{\n\tint j;\n\n\tif (!mod) {\n\t\treturn;\n\t}\n\n\tmemset(mod->texture_arrays, 0, sizeof(mod->texture_arrays));\n\tmemset(mod->texture_arrays_scale_s, 0, sizeof(mod->texture_arrays_scale_s));\n\tmemset(mod->texture_arrays_scale_t, 0, sizeof(mod->texture_arrays_scale_t));\n\tmemset(mod->texture_array_first, 0, sizeof(mod->texture_array_first));\n\tmod->texture_array_count = 0;\n\n\tmemset(mod->simpletexture_scalingS, 0, sizeof(mod->simpletexture_scalingS));\n\tmemset(mod->simpletexture_scalingT, 0, sizeof(mod->simpletexture_scalingT));\n\tmemset(mod->simpletexture_indexes, 0, sizeof(mod->simpletexture_indexes));\n\tmemset(mod->simpletexture_array, 0, sizeof(mod->simpletexture_array));\n\n\tif (all_textures) {\n\t\tmemset(mod->simpletexture, 0, sizeof(mod->simpletexture));\n\t}\n\n\t// clear brush model data\n\tif (mod->type == mod_brush) {\n\t\tfor (j = 0; j < mod->numtextures; ++j) {\n\t\t\ttexture_t* tex = mod->textures[j];\n\t\t\tif (tex) {\n\t\t\t\tif (all_textures) {\n\t\t\t\t\tR_TextureReferenceInvalidate(tex->fb_texturenum);\n\t\t\t\t\tR_TextureReferenceInvalidate(tex->gl_texturenum);\n\t\t\t\t}\n\t\t\t\ttex->gl_texture_scaleS = tex->gl_texture_scaleT = 0;\n\t\t\t\tR_TextureReferenceInvalidate(tex->gl_texture_array);\n\t\t\t\ttex->gl_texture_index = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\t// clear sprite model data\n\tif (mod->type == mod_sprite) {\n\t\tmsprite2_t* psprite = (msprite2_t*)Mod_Extradata(mod);\n\t\tint j;\n\n\t\tfor (j = 0; j < psprite->numframes; ++j) {\n\t\t\tint offset = psprite->frames[j].offset;\n\t\t\tint numframes = psprite->frames[j].numframes;\n\t\t\tmspriteframe_t* frame;\n\n\t\t\tif (offset < (int)sizeof(msprite2_t) || numframes < 1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tframe = ((mspriteframe_t*)((byte*)psprite + offset));\n\n\t\t\tif (all_textures) {\n\t\t\t\tR_TextureReferenceInvalidate(frame->gl_texturenum);\n\t\t\t}\n\t\t\tframe->gl_scalingS = frame->gl_scalingT = 0;\n\t\t\tR_TextureReferenceInvalidate(frame->gl_arraynum);\n\t\t\tframe->gl_arrayindex = 0;\n\t\t}\n\t}\n\n\tif (mod->type == mod_alias && all_textures) {\n\t\taliashdr_t* paliashdr = (aliashdr_t *)Mod_Extradata(mod);\n\n\t\tmemset(paliashdr->gl_texturenum, 0, sizeof(paliashdr->gl_texturenum));\n\t\tmemset(paliashdr->glc_fb_texturenum, 0, sizeof(paliashdr->glc_fb_texturenum));\n\t}\n\telse if (mod->type == mod_alias3 && all_textures) {\n\t\tmd3model_t* md3Model = (md3model_t *)Mod_Extradata(mod);\n\t\tsurfinf_t* surfaceInfo = MD3_ExtraSurfaceInfoForModel(md3Model);\n\n\t\t// One day there will be more than one of these...\n\t\tR_TextureReferenceInvalidate(surfaceInfo->texnum);\n\t}\n}\n\nvoid R_TexturesInvalidateAllReferences(void)\n{\n\tint i;\n\n\tfor (i = 1; i < MAX_MODELS; ++i) {\n\t\tR_ClearModelTextureReferences(cl.model_precache[i], true);\n\t}\n\n\tfor (i = 0; i < MAX_VWEP_MODELS; i++) {\n\t\tR_ClearModelTextureReferences(cl.vw_model_precache[i], true);\n\t}\n}\n\nvoid R_ClearModelTextureData(void)\n{\n\tint i;\n\n\tfor (i = 0; i < MAX_MODELS; ++i) {\n\t\tR_ClearModelTextureReferences(cl.model_precache[i], false);\n\t}\n\n\tfor (i = 0; i < MAX_VWEP_MODELS; ++i) {\n\t\tR_ClearModelTextureReferences(cl.vw_model_precache[i], false);\n\t}\n}\n\nqbool R_TextureValid(texture_ref ref)\n{\n\treturn ref.index && ref.index < numgltextures && gltextures[ref.index].texnum;\n}\n\nqbool R_TexturesAreSameSize(texture_ref tex1, texture_ref tex2)\n{\n\tassert(tex1.index < sizeof(gltextures) / sizeof(gltextures[0]));\n\tassert(tex2.index < sizeof(gltextures) / sizeof(gltextures[0]));\n\n\treturn\n\t\t(gltextures[tex1.index].texture_width == gltextures[tex2.index].texture_width) &&\n\t\t(gltextures[tex1.index].texture_height == gltextures[tex2.index].texture_height);\n}\n\nint R_TextureWidth(texture_ref ref)\n{\n\tassert(ref.index && ref.index < numgltextures);\n\tif (ref.index >= numgltextures) {\n\t\treturn 0;\n\t}\n\n\treturn gltextures[ref.index].texture_width;\n}\n\nint R_TextureHeight(texture_ref ref)\n{\n\tassert(ref.index && ref.index < numgltextures);\n\tif (ref.index >= numgltextures) {\n\t\treturn 0;\n\t}\n\n\treturn gltextures[ref.index].texture_height;\n}\n\nint R_TextureDepth(texture_ref ref)\n{\n\tassert(ref.index && ref.index < numgltextures);\n\tassert(gltextures[ref.index].is_array);\n\tif (ref.index >= numgltextures || !gltextures[ref.index].is_array) {\n\t\treturn 0;\n\t}\n\n\treturn gltextures[ref.index].depth;\n}\n\nvoid R_TextureModeForEach(void(*func)(texture_ref tex, qbool mipmap, qbool viewmodel))\n{\n\tint i;\n\tgltexture_t* glt;\n\n\t// Make sure we set the proper texture filters for textures.\n\tfor (i = 1, glt = gltextures + 1; i < numgltextures; i++, glt++) {\n\t\tif (!R_TextureReferenceIsValid(glt->reference)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (glt->texmode & TEX_NO_TEXTUREMODE) {\n\t\t\tcontinue;\t// This texture must NOT be affected by texture mode changes,\n\t\t\t\t\t\t// for example charset which rather controlled by gl_smoothfont.\n\t\t}\n\n\t\tfunc(glt->reference, glt->texmode & TEX_MIPMAP, glt->texmode & TEX_VIEWMODEL);\n\t}\n}\n\nconst char* R_TextureIdentifier(texture_ref ref)\n{\n\tassert(ref.index < sizeof(gltextures) / sizeof(gltextures[0]));\n\n\tif (ref.index <= 0 || ref.index >= sizeof(gltextures) / sizeof(gltextures[0])) {\n\t\treturn \"null-texture\";\n\t}\n\n\treturn gltextures[ref.index].identifier[0] ? gltextures[ref.index].identifier : \"unnamed-texture\";\n}\n\nvoid R_TextureSetFlag(texture_ref ref, int mode)\n{\n\tif (ref.index <= 0 || ref.index >= sizeof(gltextures) / sizeof(gltextures[0]))\n\t\treturn;\n\n\tgltextures[ref.index].texmode = mode;\n}\n\nint R_TextureGetFlag(texture_ref ref)\n{\n\tif (ref.index <= 0 || ref.index >= sizeof(gltextures) / sizeof(gltextures[0]))\n\t\treturn 0;\n\n\treturn gltextures[ref.index].texmode;\n}\n\nvoid R_TextureSetDimensions(texture_ref ref, int width, int height)\n{\n\tassert(ref.index < sizeof(gltextures) / sizeof(gltextures[0]));\n\n\tgltextures[ref.index].texture_width = width;\n\tgltextures[ref.index].texture_height = height;\n}\n\nvoid R_Texture_Init(void)\n{\n\tint i;\n\n\t// Reset some global vars, probably we need here even more...\n\n\t// Reset textures array and linked globals\n\tfor (i = 0; i < numgltextures; i++) {\n\t\tif (gltextures[i].texnum) {\n\t\t\trenderer.TextureDelete(gltextures[i].reference);\n\t\t}\n\t\tQ_free(gltextures[i].pathname);\n\t}\n\n\tmemset(gltextures, 0, sizeof(gltextures));\n\n\tnumgltextures = 1;\n\tnext_free_texture = 0;\n\n\trenderer.TextureInitialiseState();\n\n\t// Powerup shells.\n\tR_TextureReferenceInvalidate(shelltexture); // Force reload.\n\n\t// Sky.\n\tmemset(skyboxtextures, 0, sizeof(skyboxtextures)); // Force reload.\n\n\tR_TextureRegisterCvars();\n}\n\nvoid R_DeleteTexture(texture_ref* texture)\n{\n\tif (!texture || !R_TextureReferenceIsValid(*texture)) {\n\t\treturn;\n\t}\n\n\t// Delete renderer's version\n\tif (renderer.TextureDelete) {\n\t\trenderer.TextureDelete(*texture);\n\t}\n\n\t// Free structure, updated linked list so we can re-use this slot\n\tQ_free(gltextures[texture->index].pathname);\n\tmemset(&gltextures[texture->index], 0, sizeof(gltextures[0]));\n\tgltextures[texture->index].next_free = next_free_texture;\n\tnext_free_texture = texture->index;\n\n\t// Invalidate the reference\n\ttexture->index = 0;\n}\n\nvoid R_DeleteCubeMap(texture_ref* texture)\n{\n\tR_DeleteTexture(texture);\n}\n\nvoid R_DeleteTextureArray(texture_ref* texture)\n{\n\tR_DeleteTexture(texture);\n}\n\nvoid R_GenerateMipmapsIfNeeded(texture_ref ref)\n{\n\tassert(ref.index && ref.index < numgltextures);\n\tif (!ref.index || ref.index >= numgltextures) {\n\t\treturn;\n\t}\n\n\tif (!gltextures[ref.index].mipmaps_generated) {\n\t\trenderer.TextureMipmapGenerate(ref);\n\t\tgltextures[ref.index].mipmaps_generated = true;\n\t}\n}\n\n// Called during vid_shutdown\nvoid R_DeleteTextures(void)\n{\n\textern void R_InvalidateLightmapTextures(void);\n\tint i;\n\n\tR_InvalidateLightmapTextures();\n\tSkin_InvalidateTextures();\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\t{\n\t\textern texture_ref particletexture_array;\n\t\tR_TextureReferenceInvalidate(particletexture_array);\n\t}\n#endif\n\n\tfor (i = 0; i < numgltextures; ++i) {\n\t\tR_DeleteTexture(&gltextures[i].reference);\n\t}\n\tMod_ClearSimpleTextures();\n\n\tmemset(gltextures, 0, sizeof(gltextures));\n\tnumgltextures = 0;\n\tnext_free_texture = 0;\n}\n\ntexture_ref R_CreateCubeMap(const char* identifier, int width, int height, int mode)\n{\n\tqbool new_texture;\n\tgltexture_t* slot = R_TextureAllocateSlot(texture_type_cubemap, identifier, width, height, 0, 4, mode | TEX_NOSCALE, 0, &new_texture);\n\n\tif (!slot) {\n\t\treturn invalid_texture_reference;\n\t}\n\n\tif (slot && !new_texture) {\n\t\treturn slot->reference;\n\t}\n\n\tR_TextureUtil_SetFiltering(slot->reference);\n\trenderer.TextureWrapModeClamp(slot->reference);\n\n\treturn slot->reference;\n}\n\ngltexture_t* R_TextureAllocateSlot(r_texture_type_id type, const char* identifier, int width, int height, int depth, int bpp, int mode, unsigned short crc, qbool* new_texture)\n{\n\tint gl_width, gl_height;\n\tgltexture_t* glt = NULL;\n\tqbool load_over_existing = false;\n\tint miplevels = 1;\n\n\t*new_texture = true;\n\n\tif (lightmode != 2) {\n\t\tmode &= ~TEX_BRIGHTEN;\n\t}\n\tif (bpp == 1 && (mode & TEX_FULLBRIGHT)) {\n\t\tmode |= TEX_ALPHA;\n\t}\n\n\tR_TextureUtil_ImageDimensionsToTexture(width, height, &gl_width, &gl_height, mode);\n\n\tif (mode & TEX_MIPMAP) {\n\t\tint temp_w = gl_width;\n\t\tint temp_h = gl_height;\n\n\t\twhile (temp_w > 1 || temp_h > 1) {\n\t\t\ttemp_w = max(1, temp_w / 2);\n\t\t\ttemp_h = max(1, temp_h / 2);\n\t\t\t++miplevels;\n\t\t}\n\t}\n\n\t// If we were given an identifier for the texture, search through\n\t// the list of loaded texture and see if we find a match, if so\n\t// return the texnum for the already loaded texture.\n\tif (identifier[0]) {\n\t\tint i;\n\t\tfor (i = 0, glt = gltextures; i < numgltextures; i++, glt++) {\n\t\t\tif (!strncmp(identifier, glt->identifier, sizeof(glt->identifier) - 1)) {\n\t\t\t\tqbool same_dimensions = (width == glt->image_width && height == glt->image_height && depth == glt->depth);\n\t\t\t\tqbool same_scaling = (gl_width == glt->texture_width && gl_height == glt->texture_height);\n\t\t\t\tqbool same_format = (glt->bpp == bpp && glt->type == type);\n\t\t\t\tqbool same_options = (mode & ~(TEX_COMPLAIN | TEX_NOSCALE)) == (glt->texmode & ~(TEX_COMPLAIN | TEX_NOSCALE));\n\n\t\t\t\t// Identifier matches, make sure everything else is the same\n\t\t\t\t// so that we can be really sure this is the correct texture.\n\t\t\t\tif (same_dimensions && same_scaling && same_format && same_options && crc == glt->crc) {\n\t\t\t\t\t*new_texture = false;\n\t\t\t\t\treturn glt;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Same identifier but different texture, so overwrite\n\t\t\t\t\t// the already loaded texture.\n\t\t\t\t\tload_over_existing = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// If the identifier was the same as another textures, we won't bother\n\t// with taking up a new texture slot, just load the new texture\n\t// over the old one.\n\tif (!load_over_existing) {\n\t\tglt = R_NextTextureSlot(type);\n\n\t\tstrlcpy(glt->identifier, identifier, sizeof(glt->identifier));\n\t\tR_AllocateTextureNames(glt);\n\t\trenderer.TextureLabelSet(glt->reference, glt->identifier);\n\t}\n\telse if (glt && !glt->texnum) {\n\t\tR_AllocateTextureNames(glt);\n\t\trenderer.TextureLabelSet(glt->reference, glt->identifier);\n\t}\n\telse if (glt && glt->storage_allocated) {\n\t\tif (gl_width != glt->texture_width || gl_height != glt->texture_height || depth != glt->depth || glt->bpp != bpp) {\n\t\t\ttexture_ref ref = glt->reference;\n\n\t\t\tR_DeleteTexture(&ref);\n\n\t\t\treturn R_TextureAllocateSlot(type, identifier, width, height, depth, bpp, mode, crc, new_texture);\n\t\t}\n\t}\n\n\tif (!glt) {\n\t\tSys_Error(\"R_LoadTexture: glt not initialized\\n\");\n\t}\n\n\tglt->image_width = width;\n\tglt->image_height = height;\n\tglt->depth = depth;\n\tglt->texmode = mode;\n\tglt->crc = crc;\n\tglt->bpp = bpp;\n\tglt->is_array = (depth > 0);\n\tglt->texture_width = gl_width;\n\tglt->texture_height = gl_height;\n\tglt->miplevels = miplevels;\n\tQ_free(glt->pathname);\n\tif (bpp == 4 && fs_netpath[0]) {\n\t\tglt->pathname = Q_strdup(fs_netpath);\n\t}\n\n\t// Allocate storage\n\tif (!glt->storage_allocated && !glt->is_array) {\n\t\tif (R_UseImmediateOpenGL() || R_UseModernOpenGL()) {\n\t\t\tGL_AllocateStorage(glt);\n\t\t}\n\t\telse if (R_UseVulkan()) {\n\t\t\t// VK_AllocateStorage(glt);\n\t\t}\n\t\tglt->storage_allocated = true;\n\t}\n\n\treturn glt;\n}\n\nvoid R_SetTextureArraySize(texture_ref tex, int width, int height, int depth, int bpp)\n{\n\tgltextures[tex.index].bpp = bpp;\n\tgltextures[tex.index].texture_width = width;\n\tgltextures[tex.index].texture_height = height;\n\tgltextures[tex.index].depth = depth;\n\tgltextures[tex.index].is_array = true;\n}\n\n// We could flag the textures as they're created and then move all 2d>3d to this module?\n// FIXME: Ensure not called in immediate-mode OpenGL\ntexture_ref R_CreateTextureArray(const char* identifier, int width, int height, int depth, int mode)\n{\n\tqbool new_texture = false;\n\tgltexture_t* slot;\n\ttexture_ref gl_texturenum;\n\n\tslot = R_TextureAllocateSlot(texture_type_2d_array, identifier, width, height, depth, 4, mode | TEX_NOSCALE, 0, &new_texture);\n\tif (!slot) {\n\t\treturn invalid_texture_reference;\n\t}\n\n\tif (slot && !new_texture) {\n\t\treturn slot->reference;\n\t}\n\n\tgl_texturenum = slot->reference;\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\tif (R_UseModernOpenGL()) {\n\t\ttexture_ref tex = slot->reference;\n\n\t\trenderer.TextureUnitBind(0, tex);\n\t\tif (GLM_TextureAllocateArrayStorage(slot)) {\n\t\t\tR_TextureUtil_SetFiltering(slot->reference);\n\t\t\treturn tex;\n\t\t}\n\n\t\tR_DeleteTexture(&tex);\n\t\treturn null_texture_reference;\n\t}\n#endif\n#ifdef RENDERER_OPTION_VULKAN\n\tif (R_UseVulkan()) {\n\n\t}\n#endif\n\n\treturn gl_texturenum;\n}\n\ngltexture_t* R_NextTextureSlot(r_texture_type_id type)\n{\n\tint slot;\n\tgltexture_t* glt;\n\n\t// Re-use a deleted slot if possible\n\tif (next_free_texture) {\n\t\tslot = next_free_texture;\n\t\tnext_free_texture = gltextures[slot].next_free;\n\t\tgltextures[slot].next_free = 0;\n\t}\n\telse if (numgltextures >= MAX_GLTEXTURES) {\n\t\tSys_Error(\"R_LoadTexture: numgltextures == MAX_GLTEXTURES\");\n\t\treturn NULL;\n\t}\n\telse {\n\t\tslot = numgltextures++;\n\t}\n\n\tglt = &gltextures[slot];\n\tglt->reference.index = slot;\n\tglt->type = type;\n\treturn glt;\n}\n\nstatic void R_AllocateTextureNames(gltexture_t* glt)\n{\n\tif (R_UseImmediateOpenGL()) {\n\t\tGL_AllocateTextureNames(glt);\n\t}\n\telse if (R_UseModernOpenGL()) {\n\t\tGL_AllocateTextureNames(glt);\n\t}\n\telse if (R_UseVulkan()) {\n\t\t// VK_AllocateTextureNames(...);\n\t}\n}\n\ngltexture_t* R_FindTexture(const char *identifier)\n{\n\tint i;\n\n\tfor (i = 0; i < numgltextures; i++) {\n\t\tif (!strcmp(identifier, gltextures[i].identifier)) {\n\t\t\treturn &gltextures[i];\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nvoid R_AllocateTextureReferences(r_texture_type_id type_id, int width, int height, int mode, int number, texture_ref* references)\n{\n\tint i;\n\n\tfor (i = 0; i < number; ++i) {\n\t\tqbool new_texture;\n\t\tgltexture_t* slot = R_TextureAllocateSlot(type_id, \"\", width, height, 0, 4, mode | TEX_NOSCALE, 0, &new_texture);\n\n\t\tif (slot) {\n\t\t\treferences[i] = slot->reference;\n\t\t}\n\t\telse {\n\t\t\treferences[i] = null_texture_reference;\n\t\t}\n\t}\n}\n\nr_texture_type_id R_TextureType(texture_ref ref)\n{\n\treturn gltextures[ref.index].type;\n}\n\nvoid R_TextureFindIdentifierByReference(unsigned int ref, char* label, int labelsize)\n{\n\tint i;\n\n\tfor (i = 0; i < numgltextures; ++i) {\n\t\tif (gltextures[i].texnum == ref) {\n\t\t\tstrlcpy(label, gltextures[i].identifier, labelsize);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tlabel[0] = '\\0';\n}\n\n// re-scale texture to match underlying (useful for fullbrights/lumas etc)\nvoid R_TextureRescaleOverlay(byte** overlay_pixels, int* overlay_width, int* overlay_height, int underlying_width, int underlying_height)\n{\n\tif (underlying_width != *overlay_width || underlying_height != *overlay_height) {\n\t\tbyte* new_pixels = Q_malloc(underlying_width * underlying_height * 4);\n\n\t\tImage_Resample(*overlay_pixels, *overlay_width, *overlay_height, new_pixels, underlying_width, underlying_height, 4, true);\n\n\t\tQ_free(*overlay_pixels);\n\t\t*overlay_pixels = new_pixels;\n\t\t*overlay_width = underlying_width;\n\t\t*overlay_height = underlying_height;\n\t}\n}\n\nint R_TextureCount(void)\n{\n\treturn numgltextures;\n}\n\n#ifdef WITH_RENDERING_TRACE\nvoid Dev_TextureList(void)\n{\n\tint i = 0;\n\n\tfor (i = 1; i < numgltextures; ++i) {\n\t\tif (Cmd_Argc() > 1 && !Utils_RegExpMatch(Cmd_Argv(1), gltextures[i].identifier)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (gltextures[i].texnum && gltextures[i].texture_width) {\n\t\t\tCon_Printf(\"%03d: %s, %d (%dx%d %d)\\n\", i, gltextures[i].identifier, gltextures[i].texture_width, gltextures[i].texture_height, gltextures[i].texmode);\n\t\t}\n\t}\n}\n#endif\n"
  },
  {
    "path": "src/r_texture.h",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_R_TEXTURE_H\n#define EZQUAKE_R_TEXTURE_H\n\n#define TEX_COMPLAIN        (1<<0)  // shout if texture missing while loading\n#define TEX_MIPMAP          (1<<1)  // use mipmaps generation while loading texture\n#define TEX_ALPHA           (1<<2)  // use transparency in texture\n// Note: if (TEX_LUMA & TEX_FULLBRIGHT) specified, colours close to black (determined by gl_luma_level) will be turned transparent\n#define TEX_LUMA            (1<<3)  // do not apply gamma adjustment to texture when loading\n#define TEX_FULLBRIGHT      (1<<4)  // make all non-fullbright colours transparent (8-bit only).\n#define TEX_NOSCALE         (1<<5)  // do no use gl_max_size or gl_picmap variables while loading texture\n#define TEX_BRIGHTEN        (1<<6)  // use brighter 8bit => 24bit conversion during load\n#define TEX_NOCOMPRESS      (1<<7)  // do not use texture compression extension\n#define TEX_NO_PCX          (1<<8)  // do not load pcx images\n#define TEX_NO_TEXTUREMODE  (1<<9)  // ignore gl_texturemode* changes for texture\n#define TEX_PREMUL_ALPHA    (1<<10) // pre-multiply alpha\n#define TEX_ZERO_ALPHA      (1<<11) // after pre-multiplying alpha, set alpha to 0 (additive blending)\n#define TEX_MERGED_LUMA     (1<<12) // alpha channel signifies luma %\n#define TEX_FLOAT           (1<<13) // floating point texture format\n#define TEX_VIEWMODEL       (1<<14) // use viewmodel texture mode, not normal\n\n#define MAX_GLTEXTURES 8192\t//dimman: old value 1024 isn't enough when using high framecount sprites (according to Spike)\n#define MAX_CHARSETS 256\n#define MAX_USER_CHARSETS 0xE0\n\n#define PRIVATE_USE_TRACKERIMAGES_CHARSET 0xE0\n// reservations:\n//   0xE000 - 0xE0FF = Tracker weapon images\n\ntypedef struct charset_s {\n\tmpic_t glyphs[256];\n\ttexture_ref master;\n\tfloat custom_scale_x;\n\tfloat custom_scale_y;\n} charset_t;\n\nextern charset_t char_textures[MAX_CHARSETS];\n\n/*\ntypedef struct texture_api_s {\n\ttexture_ref (*Load)(const char *identifier, int width, int height, byte *data, int mode, int bpp);\n\ttexture_ref (*LoadPic)(const char *name, mpic_t *pic, byte *data);\n\ttexture_ref (*LoadFromPixels)(byte *data, const char *identifier, int width, int height, int mode);\n\ttexture_ref (*LoadFromFile)(const char *filename, const char *identifier, int matchwidth, int matchheight, int mode);\n\ttexture_ref (*CreateArray)(const char* identifier, int width, int height, int* depth, int mode, int minimum_depth);\n\ttexture_ref (*CreateCubeMap)(const char* identifier, int width, int height, int mode);\n\tvoid (*DeleteArray)(texture_ref* texture);\n\tvoid (*DeleteCubeMap)(texture_ref* texture);\n\tvoid (*Delete)(texture_ref* texture);\n\tvoid (*DeleteAll)(void);\n\n\tqbool (*SameSize)(texture_ref tex1, texture_ref tex2);\n\n\tint (*Width)(texture_ref ref);\n\tint (*Height)(texture_ref ref);\n\tint (*Depth)(texture_ref ref);\n\tvoid (*GenerateMipmaps)(texture_ref ref);\n\n\tvoid (*InvalidateAll)(void);\n\tvoid (*CreateInternal)(texture_ref* reference, int width, int height, const char* name);\n\tvoid (*ReplaceSubImageRGBA)(texture_ref ref, int offsetx, int offsety, int width, int height, byte* buffer);\n\n\tvoid (*R_TextureWrapModeClamp)(texture_ref tex);\n\n#ifdef WITH_RENDERING_TRACE\n\tconst char* (*Identifier)(texture_ref ref);\n#endif\n\n} texture_api_t;\n\nextern texture_api_t textures;\n*/\n\nvoid R_Texture_Init(void);\n\nvoid R_TextureAnisotropyChanged(texture_ref tex, qbool mipmap, qbool viewmodel);\n\nmpic_t* R_LoadPicImage(const char *filename, char *id, int matchwidth, int matchheight, int mode);\nbyte* R_LoadImagePixels(const char *filename, int matchwidth, int matchheight, int mode, int *real_width, int *real_height);\nqbool R_LoadCharsetImage(char *filename, char *identifier, int flags, charset_t* pic);\nvoid R_ImagePreMultiplyAlpha(byte* image, int width, int height, qbool zero);\n\ntexture_ref R_LoadTexture(const char *identifier, int width, int height, byte *data, int mode, int bpp);\ntexture_ref R_LoadPicTexture(const char *name, mpic_t *pic, byte *data);\ntexture_ref R_LoadTexturePixels(byte *data, const char *identifier, int width, int height, int mode);\ntexture_ref R_LoadTextureImage(const char *filename, const char *identifier, int matchwidth, int matchheight, int mode);\ntexture_ref R_CreateTextureArray(const char* identifier, int width, int height, int depth, int mode);\ntexture_ref R_CreateCubeMap(const char* identifier, int width, int height, int mode);\nvoid R_DeleteTextureArray(texture_ref* texture);\nvoid R_DeleteCubeMap(texture_ref* texture);\nvoid R_DeleteTexture(texture_ref* texture);\nvoid R_DeleteTextures(void);\n\nqbool R_TexturesAreSameSize(texture_ref tex1, texture_ref tex2);\nqbool R_TextureValid(texture_ref ref);\nint R_TextureWidth(texture_ref ref);\nint R_TextureHeight(texture_ref ref);\nint R_TextureDepth(texture_ref ref);\nr_texture_type_id R_TextureType(texture_ref ref);\nvoid R_GenerateMipmapsIfNeeded(texture_ref ref);\n\nvoid R_TexturesInvalidateAllReferences(void);\n\nqbool R_ExternalTexturesEnabled(qbool worldmodel);\n\nvoid R_SetNonPowerOfTwoSupport(qbool supported);\nvoid R_TextureSizeRoundUp(int orig_width, int orig_height, int* width, int* height);\n\n#ifdef WITH_RENDERING_TRACE\nconst char* R_TextureIdentifier(texture_ref ref);\n#endif\n\nextern cvar_t gl_max_size, gl_scaleModelTextures, gl_scaleTurbTextures, gl_scaleAlphaTextures, gl_scaleModelSimpleTextures, gl_miptexLevel, gl_scaleskytextures;\nextern cvar_t gl_no24bit;\nextern texture_ref underwatertexture, detailtexture, solidwhite_texture, solidblack_texture, transparent_texture;\n\nvoid R_ClearModelTextureData(void);\n\nvoid R_AllocateTextureReferences(r_texture_type_id type_id, int width, int height, int mode, int number, texture_ref* references);\nvoid R_TextureRescaleOverlay(byte** overlay_pixels, int* overlay_width, int* overlay_height, int underlying_width, int underlying_height);\nint R_TextureCount(void);\nvoid R_TextureFindIdentifierByReference(unsigned int ref, char* label, int labelsize);\nint R_TextureGetFlag(texture_ref ref);\nvoid R_TextureSetFlag(texture_ref ref, int mode);\n\nvoid R_SetTextureArraySize(texture_ref tex, int width, int height, int depth, int bpp);\n\n#endif\t// EZQUAKE_R_TEXTURE_H\n"
  },
  {
    "path": "src/r_texture_cvars.c",
    "content": "/*\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: gl_texture.c,v 1.44 2007-10-05 19:06:24 johnnycz Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"r_local.h\"\n#include \"r_texture.h\"\n#include \"r_texture_internal.h\"\n#include \"tr_types.h\"\n#include \"gl_texture.h\"\n#include \"r_renderer.h\"\n\nstatic void OnChange_gl_max_size(cvar_t *var, char *string, qbool *cancel);\nstatic void OnChange_gl_texturemode(cvar_t *var, char *string, qbool *cancel);\nstatic void OnChange_gl_miptexLevel(cvar_t *var, char *string, qbool *cancel);\nstatic void OnChange_gl_anisotropy(cvar_t *var, char *string, qbool *cancel);\n\ncvar_t gl_lerpimages = { \"gl_lerpimages\", \"1\", CVAR_RELOAD_GFX };\nstatic cvar_t gl_externalTextures_world = { \"gl_externalTextures_world\", \"1\", CVAR_RELOAD_GFX };\nstatic cvar_t gl_externalTextures_bmodels = { \"gl_externalTextures_bmodels\", \"1\", CVAR_RELOAD_GFX };\ncvar_t gl_wicked_luma_level = { \"gl_luma_level\", \"1\", CVAR_RELOAD_GFX };\n\nstatic int anisotropy_tap = 1; //  1 - is off\n\ncvar_t gl_max_size = { \"gl_max_size\", \"2048\", CVAR_RELOAD_GFX, OnChange_gl_max_size };\ncvar_t gl_picmip = { \"gl_picmip\", \"0\", CVAR_RELOAD_GFX };\ncvar_t gl_miptexLevel = { \"gl_miptexLevel\", \"0\", CVAR_RELOAD_GFX, OnChange_gl_miptexLevel };\ncvar_t gl_texturemode = { \"gl_texturemode\", \"GL_LINEAR_MIPMAP_LINEAR\", 0, OnChange_gl_texturemode };\ncvar_t gl_texturemode_viewmodels = { \"gl_texturemode_viewmodels\", \"GL_LINEAR\", 0, OnChange_gl_texturemode };\ncvar_t gl_anisotropy = { \"gl_anisotropy\",\"16\", 0, OnChange_gl_anisotropy };\ncvar_t gl_scaleModelTextures = { \"gl_scaleModelTextures\", \"0\", CVAR_RELOAD_GFX };\ncvar_t gl_scaleModelSimpleTextures = { \"gl_scaleModelSimpleTextures\", \"0\", CVAR_RELOAD_GFX };\ncvar_t gl_scaleTurbTextures = { \"gl_scaleTurbTextures\", \"1\", CVAR_RELOAD_GFX };\ncvar_t gl_scaleAlphaTextures = { \"gl_scaleAlphaTextures\", \"0\", CVAR_RELOAD_GFX };\ncvar_t gl_scaleskytextures = { \"gl_scaleskytextures\", \"0\", CVAR_RELOAD_GFX };\ncvar_t gl_no24bit = { \"gl_no24bit\", \"0\", CVAR_RELOAD_GFX };\n\nstatic void OnChange_gl_max_size(cvar_t *var, char *string, qbool *cancel)\n{\n\tint i;\n\tfloat newvalue = Q_atof(string);\n\n\tif (newvalue > glConfig.gl_max_size_default) {\n\t\tCom_Printf(\"Your hardware doesn't support texture sizes bigger than %dx%d\\n\", glConfig.gl_max_size_default, glConfig.gl_max_size_default);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tQ_ROUND_POWER2(newvalue, i);\n\n\tif (i != newvalue) {\n\t\tCom_Printf(\"Valid values for %s are powers of 2 only\\n\", var->name);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n}\n\nvoid R_TextureAnisotropyChanged(texture_ref tex, qbool mipmap, qbool viewmodel)\n{\n\tif (mipmap || viewmodel) {\n\t\trenderer.TextureSetAnisotropy(tex, anisotropy_tap);\n\t}\n}\n\nstatic void OnChange_gl_anisotropy(cvar_t *var, char *string, qbool *cancel)\n{\n\tint newvalue = Q_atoi(string);\n\n\tanisotropy_tap = max(1, newvalue); // 0 is bad, 1 is off, 2 and higher are valid modes\n\n\tR_TextureModeForEach(R_TextureAnisotropyChanged);\n}\n\nstatic void OnChange_gl_miptexLevel(cvar_t *var, char *string, qbool *cancel)\n{\n\tfloat newval = Q_atof(string);\n\n\tif (newval != 0 && newval != 1 && newval != 2 && newval != 3) {\n\t\tCom_Printf(\"Valid values for %s are 0,1,2,3 only\\n\", var->name);\n\t\t*cancel = true;\n\t}\n}\n\ntypedef struct {\n\tchar *name;\n\ttexture_minification_id\tminimize;\n\ttexture_magnification_id maximize;\n} glmode_t;\n\nstatic glmode_t modes[] = {\n\t{ \"GL_NEAREST\", texture_minification_nearest, texture_magnification_nearest },\n\t{ \"GL_LINEAR\", texture_minification_linear, texture_magnification_linear },\n\t{ \"GL_NEAREST_MIPMAP_NEAREST\", texture_minification_nearest_mipmap_nearest, texture_magnification_nearest },\n\t{ \"GL_LINEAR_MIPMAP_NEAREST\", texture_minification_linear_mipmap_nearest, texture_magnification_linear },\n\t{ \"GL_NEAREST_MIPMAP_LINEAR\", texture_minification_nearest_mipmap_linear, texture_magnification_nearest },\n\t{ \"GL_LINEAR_MIPMAP_LINEAR\", texture_minification_linear_mipmap_linear, texture_magnification_linear }\n};\n\nstatic texture_minification_id gl_filter_min = texture_minification_linear_mipmap_nearest;\nstatic texture_magnification_id gl_filter_max = texture_magnification_linear;\nstatic texture_minification_id gl_filter_viewmodel_min = texture_minification_linear;\nstatic texture_magnification_id gl_filter_viewmodel_max = texture_magnification_linear;\nstatic const texture_minification_id gl_filter_min_2d = texture_minification_linear;\nstatic const texture_magnification_id gl_filter_max_2d = texture_magnification_linear;   // no longer controlled by cvar\n\nvoid R_TextureModeChanged(texture_ref tex, qbool mipmap, qbool viewmodel)\n{\n\tif (viewmodel) {\n\t\trenderer.TextureSetFiltering(tex, gl_filter_viewmodel_min, gl_filter_viewmodel_max);\n\t}\n\telse if (mipmap) {\n\t\trenderer.TextureSetFiltering(tex, gl_filter_min, gl_filter_max);\n\t}\n\telse {\n\t\trenderer.TextureSetFiltering(tex, gl_filter_min_2d, gl_filter_max_2d);\n\t}\n}\n\nstatic void OnChange_gl_texturemode(cvar_t *var, char *string, qbool *cancel)\n{\n\tint i;\n\n\tfor (i = 0; i < (sizeof(modes) / sizeof(glmode_t)); i++) {\n\t\tif (!strcasecmp(modes[i].name, string)) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (i == (sizeof(modes) / sizeof(glmode_t))) {\n\t\tCom_Printf(\"bad filter name: %s\\n\", string);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tif (var == &gl_texturemode) {\n\t\tgl_filter_min = modes[i].minimize;\n\t\tgl_filter_max = modes[i].maximize;\n\t}\n\telse if (var == &gl_texturemode_viewmodels) {\n\t\tgl_filter_viewmodel_min = modes[i].minimize;\n\t\tgl_filter_viewmodel_max = modes[i].maximize;\n\t}\n\telse {\n\t\tSys_Error(\"OnChange_gl_texturemode: unexpected cvar!\");\n\t\treturn;\n\t}\n\n\tR_TextureModeForEach(R_TextureModeChanged);\n}\n\nqbool R_ExternalTexturesEnabled(qbool worldmodel)\n{\n\treturn !gl_no24bit.integer && (worldmodel ? gl_externalTextures_world.integer : gl_externalTextures_bmodels.integer);\n}\n\nvoid R_TextureRegisterCvars(void)\n{\n\tint i;\n\tcvar_t* cv;\n\n\tif (!host_initialized) {\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_TEXTURES);\n\t\tCvar_Register(&gl_max_size);\n\t\tCvar_Register(&gl_scaleModelTextures);\n\t\tCvar_Register(&gl_scaleModelSimpleTextures);\n\t\tCvar_Register(&gl_scaleTurbTextures);\n\t\tCvar_Register(&gl_scaleskytextures);\n\t\tCvar_Register(&gl_scaleAlphaTextures);\n\t\tCvar_Register(&gl_miptexLevel);\n\t\tCvar_Register(&gl_picmip);\n\t\tCvar_Register(&gl_lerpimages);\n\t\tCvar_Register(&gl_texturemode);\n\t\tCvar_Register(&gl_texturemode_viewmodels);\n\t\tCvar_Register(&gl_anisotropy);\n\t\tCvar_Register(&gl_externalTextures_world);\n\t\tCvar_Register(&gl_externalTextures_bmodels);\n\n\t\tCvar_Register(&gl_no24bit);\n\t\tCvar_Register(&gl_wicked_luma_level);\n\t\tCvar_ResetCurrentGroup();\n\t}\n\n\t// This way user can specify gl_max_size in his cfg.\n\ti = (cv = Cvar_Find(gl_max_size.name)) ? cv->integer : 0;\n\tCvar_SetDefaultAndValue(&gl_max_size, glConfig.gl_max_size_default, i ? i : glConfig.gl_max_size_default);\n}\n"
  },
  {
    "path": "src/r_texture_internal.h",
    "content": "\n#ifndef EZQUAKE_R_TEXTURE_INTERNAL_HEADER\n#define EZQUAKE_R_TEXTURE_INTERNAL_HEADER\n\n#include \"r_texture.h\"\n\ntypedef struct gltexture_s {\n\t// Internal handle\n\tunsigned int texnum;\n\n\t// \n\tr_texture_type_id type;\n\tchar        identifier[MAX_QPATH];\n\tchar*       pathname;\n\tint         image_width, image_height;\n\tint         texture_width, texture_height;\n\tint         texmode;\n\tunsigned    crc;\n\tint         bpp;\n\tint         miplevels;\n\n\t// Arrays\n\tqbool       is_array;\n\tint         depth;\n\n\ttexture_ref reference;\n\tint         next_free;\n\n\tqbool       mipmaps_generated;\n\tqbool       storage_allocated;\n\n\ttexture_minification_id minification_filter;\n\ttexture_magnification_id magnification_filter;\n} gltexture_t;\n\nextern gltexture_t gltextures[MAX_GLTEXTURES];\n\nvoid R_TextureSetDimensions(texture_ref ref, int width, int height);\ngltexture_t* R_TextureAllocateSlot(r_texture_type_id type, const char* identifier, int width, int height, int depth, int bpp, int mode, unsigned short crc, qbool* new_texture);\n\nvoid R_TextureUtil_ScaleDimensions(int width, int height, int *scaled_width, int *scaled_height, int mode);\nvoid R_TextureUtil_Brighten32(byte *data, int size);\nvoid R_TextureUtil_ImageDimensionsToTexture(int imageWidth, int imageHeight, int* textureWidth, int* textureHeight, int mode);\n\nvoid R_TextureUtil_SetFiltering(texture_ref texture);\n\ngltexture_t* R_NextTextureSlot(r_texture_type_id target);\ngltexture_t* R_FindTexture(const char *identifier);\n\nvoid R_TextureRegisterCvars(void);\nvoid R_TextureModeChanged(texture_ref tex, qbool mipmap, qbool viewmodel);\nvoid R_TextureModeForEach(void(*func)(texture_ref tex, qbool mipmap, qbool viewmodel));\n\n#endif // EZQUAKE_R_TEXTURE_HEADER\n"
  },
  {
    "path": "src/r_texture_load.c",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#include \"quakedef.h\"\n#include \"r_texture_internal.h\"\n#include \"r_local.h\"\n#include \"image.h\"\n#include \"crc.h\"\n#include \"gl_texture.h\"\n#include \"r_trace.h\"\n\nstatic void R_LoadTextureData(gltexture_t* glt, int width, int height, byte *data, int mode, int bpp);\n\n// \n#define CHARSET_CHARS_PER_ROW\t16\n#define CHARSET_WIDTH\t\t\t1.0\n#define CHARSET_HEIGHT\t\t\t1.0\n#define CHARSET_CHAR_WIDTH\t\t(CHARSET_WIDTH / CHARSET_CHARS_PER_ROW)\n#define CHARSET_CHAR_HEIGHT\t\t(CHARSET_HEIGHT / CHARSET_CHARS_PER_ROW)\n\nstatic const texture_ref invalid_texture_reference = { 0 };\nstatic qbool r_texture_support_non_power_of_two;\n\n#define Block24BitTextures (COM_CheckParm(cmdline_param_client_no24bittextures) || gl_no24bit.integer)\n#define ForceTextureReload (COM_CheckParm(cmdline_param_client_forcetexturereload))\n\nstatic qbool CheckTextureLoaded(gltexture_t* current_texture)\n{\n\tif (!ForceTextureReload) {\n\t\tif (current_texture && current_texture->pathname && !strcmp(fs_netpath, current_texture->pathname)) {\n\t\t\tint textureWidth, textureHeight;\n\n\t\t\tR_TextureUtil_ImageDimensionsToTexture(current_texture->image_width, current_texture->image_height, &textureWidth, &textureHeight, current_texture->texmode);\n\n\t\t\tif (current_texture->texture_width == textureWidth && current_texture->texture_height == textureHeight) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\ntexture_ref R_LoadTextureImage(const char *filename, const char *identifier, int matchwidth, int matchheight, int mode)\n{\n\ttexture_ref reference;\n\tbyte *data;\n\tint image_width = -1, image_height = -1;\n\tgltexture_t *gltexture;\n\n\tR_TraceAPI(\"R_LoadTextureImage(filename=%s, identifier=%s, matchwidth=%d, matchheight=%d, mode=%d)\", filename, identifier, matchwidth, matchheight, mode);\n\tif (Block24BitTextures) {\n\t\treturn invalid_texture_reference;\n\t}\n\n\tif (!identifier) {\n\t\tidentifier = filename;\n\t}\n\n\tgltexture = R_FindTexture(identifier);\n\tif (CheckTextureLoaded(gltexture)) {\n\t\treturn gltexture->reference;\n\t}\n\tif (!(data = R_LoadImagePixels(filename, matchwidth, matchheight, mode, &image_width, &image_height))) {\n\t\tif (gltexture) {\n\t\t\treference = gltexture->reference;\n\t\t\tR_DeleteTexture(&reference);\n\t\t}\n\t\treturn invalid_texture_reference;\n\t}\n\telse {\n\t\treference = R_LoadTexturePixels(data, identifier, image_width, image_height, mode);\n\t\tQ_free(data);\t// Data was Q_malloc'ed by R_LoadImagePixels.\n\t}\n\n\treturn reference;\n}\n\nvoid R_ImagePreMultiplyAlpha(byte* image, int width, int height, qbool zero)\n{\n\t// Pre-multiply alpha component\n\tint pos;\n\n\tfor (pos = 0; pos < width * height * 4; pos += 4) {\n\t\tfloat alpha = image[pos + 3] / 255.0f;\n\n\t\timage[pos] *= alpha;\n\t\timage[pos + 1] *= alpha;\n\t\timage[pos + 2] *= alpha;\n\t\tif (zero) {\n\t\t\timage[pos + 3] = 0;\n\t\t}\n\t}\n}\n\nmpic_t* R_LoadPicImage(const char *filename, char *id, int matchwidth, int matchheight, int mode)\n{\n\tint width, height, i, real_width, real_height;\n\tchar identifier[MAX_QPATH] = \"pic:\";\n\tbyte *data, *src, *dest, *buf;\n\tstatic mpic_t pic;\n\n\t// this is 2D texture loading so it must not have MIP MAPS\n\tmode &= ~TEX_MIPMAP;\n\n\tR_TraceAPI(\"R_LoadPicImage(filename=%s, identifier=%s, matchwidth=%d, matchheight=%d, mode=%d)\", filename, id, matchwidth, matchheight, mode);\n\tif (Block24BitTextures) {\n\t\treturn NULL;\n\t}\n\n\t// Load the image data from file.\n\tif (!(data = R_LoadImagePixels(filename, matchwidth, matchheight, 0, &real_width, &real_height))) {\n\t\treturn NULL;\n\t}\n\n\tpic.width = real_width;\n\tpic.height = real_height;\n\n\t// Check if there's any actual alpha transparency in the image.\n\tif (mode & TEX_ALPHA) {\n\t\tmode &= ~TEX_ALPHA;\n\t\tfor (i = 0; i < real_width * real_height; i++) {\n\t\t\tif (((((unsigned *)data)[i] >> 24) & 0xFF) < 255) {\n\t\t\t\tmode |= TEX_ALPHA;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (mode & TEX_ALPHA) {\n\t\tR_ImagePreMultiplyAlpha(data, real_width, real_height, false);\n\t}\n\n\tR_TextureSizeRoundUp(pic.width, pic.height, &width, &height);\n\n\tstrlcpy(identifier + 4, id ? id : filename, sizeof(identifier) - 4);\n\n\t// Upload the texture to OpenGL.\n\tif (width == pic.width && height == pic.height) {\n\t\t// Size of the image is scaled by the power of 2 so we can just\n\t\t// load the texture as it is.\n\t\tpic.texnum = R_LoadTexture(identifier, pic.width, pic.height, data, mode, 4);\n\t\tpic.sl = 0;\n\t\tpic.sh = 1;\n\t\tpic.tl = 0;\n\t\tpic.th = 1;\n\t}\n\telse {\n\t\t// The size of the image data is not a power of 2, so we\n\t\t// need to load it into a bigger texture and then set the\n\t\t// texture coordinates so that only the relevant part of it is used.\n\n\t\tbuf = (byte *)Q_calloc(width * height, 4);\n\n\t\tsrc = data;\n\t\tdest = buf;\n\n\t\tfor (i = 0; i < pic.height; i++) {\n\t\t\tmemcpy(dest, src, pic.width * 4);\n\t\t\tsrc += pic.width * 4;\t// Next line in the source.\n\t\t\tdest += width * 4;\t\t// Next line in the target.\n\t\t}\n\n\t\tpic.texnum = R_LoadTexture(identifier, width, height, buf, mode, 4);\n\t\tpic.sl = 0;\n\t\tpic.sh = (float)pic.width / width;\n\t\tpic.tl = 0;\n\t\tpic.th = (float)pic.height / height;\n\t\tQ_free(buf);\n\t}\n\n\tQ_free(data);\t// Data was Q_malloc'ed by R_LoadImagePixels\n\treturn &pic;\n}\n\nqbool R_LoadCharsetImage(char *filename, char *identifier, int flags, charset_t* pic)\n{\n\tint i, j, image_size, real_width, real_height;\n\tbyte *data, *buf = NULL, *dest, *src;\n\ttexture_ref tex;\n\n\tR_TraceEnterRegion(va(\"R_LoadCharsetImage(filename=%s, identifier=%s, flags=%d)\", filename, identifier, flags), true);\n\tif (Block24BitTextures) {\n\t\tR_TraceAPI(\"24-bit textures blocked\");\n\t\tR_TraceLeaveRegion(true);\n\t\treturn false;\n\t}\n\n\tif (!(data = R_LoadImagePixels(filename, 0, 0, flags, &real_width, &real_height))) {\n\t\tR_TraceAPI(\"Failed to load image pixels\");\n\t\tR_TraceLeaveRegion(true);\n\t\treturn false;\n\t}\n\n\tR_ImagePreMultiplyAlpha(data, real_width, real_height, false);\n\n\tif (!identifier) {\n\t\tidentifier = filename;\n\t}\n\n\timage_size = real_width * real_height;\n\n\tbuf = dest = (byte *)Q_calloc(image_size * 4, 4);\n\tsrc = data;\n\tfor (j = 0; j < real_height; ++j) {\n\t\tfor (i = 0; i < real_width; ++i) {\n\t\t\tint x_offset = (i / (real_width >> 4)) * (real_width >> 4);\n\t\t\tint y_offset = (j / (real_height >> 4)) * (real_height >> 4);\n\n\t\t\tmemcpy(&buf[(i + x_offset + 2 * real_width * (j + y_offset)) * 4], &src[(i + real_width * j) * 4], 4);\n\t\t}\n\t}\n\n\ttex = R_LoadTexture(identifier, real_width * 2, real_height * 2, buf, flags, 4);\n\tQ_free(buf);\n\tif (R_TextureReferenceIsValid(tex)) {\n\t\tmemset(pic->glyphs, 0, sizeof(pic->glyphs));\n\t\tfor (i = 0; i < 255; ++i) {\n\t\t\tfloat char_height = 1.0f / (2 * CHARSET_CHARS_PER_ROW);\n\t\t\tfloat char_width = 1.0f / (2 * CHARSET_CHARS_PER_ROW);\n\t\t\tfloat frow = (i >> 4) * char_height * 2;\n\t\t\tfloat fcol = (i & 0x0F) * char_width * 2;\n\n\t\t\tpic->glyphs[i].texnum = tex;\n\t\t\tpic->glyphs[i].sl = fcol;\n\t\t\tpic->glyphs[i].sh = fcol + char_width;\n\t\t\tpic->glyphs[i].tl = frow;\n\t\t\tpic->glyphs[i].th = frow + char_height;\n\t\t\tpic->glyphs[i].width = real_width >> 4;\n\t\t\tpic->glyphs[i].height = real_height >> 4;\n\t\t}\n\n\t\tpic->master = tex;\n\t}\n\n\tpic->custom_scale_x = 1;\n\tpic->custom_scale_y = 1;\n\n\tQ_free(data);\t// data was Q_malloc'ed by R_LoadImagePixels\n\tR_TraceLeaveRegion(true);\n\treturn R_TextureReferenceIsValid(tex);\n}\n\ntypedef byte* (*ImageLoadFunction)(vfsfile_t *fin, const char *filename, int matchwidth, int matchheight, int *real_width, int *real_height);\ntypedef struct image_load_format_s {\n\tconst char* extension;\n\tImageLoadFunction function;\n\tint filter_mask;\n} image_load_format_t;\n\nbyte* R_LoadImagePixels(const char *filename, int matchwidth, int matchheight, int mode, int *real_width, int *real_height)\n{\n\tchar basename[MAX_QPATH], name[MAX_QPATH];\n\tbyte *c, *data = NULL;\n\tvfsfile_t *f;\n\n\tCOM_StripExtension(filename, basename, sizeof(basename));\n\tfor (c = (byte *)basename; *c; c++) {\n\t\tif (*c == '*') {\n\t\t\t*c = '#';\n\t\t}\n\t}\n\n\tsnprintf(name, sizeof(name), \"%s.link\", basename);\n\tif ((f = FS_OpenVFS(name, \"rb\", FS_ANY))) {\n\t\tchar link[128];\n\t\tint len;\n\t\tVFS_GETS(f, link, sizeof(link));\n\n\t\tlen = strlen(link);\n\n\t\t// Strip endline.\n\t\tif (link[len - 1] == '\\n') {\n\t\t\tlink[len - 1] = '\\0';\n\t\t\t--len;\n\t\t}\n\n\t\tif (link[len - 1] == '\\r') {\n\t\t\tlink[len - 1] = '\\0';\n\t\t\t--len;\n\t\t}\n\n\t\tsnprintf(name, sizeof(name), \"textures/%s\", link);\n\t\tif ((f = FS_OpenVFS(name, \"rb\", FS_ANY))) {\n\t\t\tif (!data && !strcasecmp(link + len - 3, \"tga\")) {\n\t\t\t\tdata = Image_LoadTGA(f, name, matchwidth, matchheight, real_width, real_height);\n\t\t\t}\n\n#ifdef WITH_PNG\n\t\t\tif (!data && !strcasecmp(link + len - 3, \"png\")) {\n\t\t\t\tdata = Image_LoadPNG(f, name, matchwidth, matchheight, real_width, real_height);\n\t\t\t}\n#endif // WITH_PNG\n\n#ifdef WITH_JPEG\n\t\t\tif (!data && !strcasecmp(link + len - 3, \"jpg\")) {\n\t\t\t\tdata = Image_LoadJPEG(f, name, matchwidth, matchheight, real_width, real_height);\n\t\t\t}\n#endif // WITH_JPEG\n\n\t\t\t// TEX_NO_PCX - preventing loading skins here\n\t\t\tif (!(mode & TEX_NO_PCX) && !data && !strcasecmp(link + len - 3, \"pcx\")) {\n\t\t\t\tdata = Image_LoadPCX_As32Bit(f, name, matchwidth, matchheight, real_width, real_height);\n\t\t\t}\n\n\t\t\tif (data)\n\t\t\t\treturn data;\n\t\t}\n\t}\n\n\timage_load_format_t formats[] = {\n\t\t{ \"tga\", Image_LoadTGA, 0 },\n#ifdef WITH_PNG\n\t\t{ \"png\", Image_LoadPNG, 0 },\n#endif\n#ifdef WITH_JPEG\n\t\t{ \"jpg\", Image_LoadJPEG, 0 },\n#endif\n\t\t{ \"pcx\", Image_LoadPCX_As32Bit, TEX_NO_PCX }\n\t};\n\tint i = 0;\n\n\timage_load_format_t* best = NULL;\n\tfor (i = 0; i < sizeof(formats) / sizeof(formats[0]); ++i) {\n\t\tvfsfile_t *file = NULL;\n\n\t\tif (mode & formats[i].filter_mask) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tsnprintf(name, sizeof(name), \"%s.%s\", basename, formats[i].extension);\n\t\tif ((file = FS_OpenVFS(name, \"rb\", FS_ANY))) {\n\t\t\tif (f == NULL || (f->copyprotected && !file->copyprotected)) {\n\t\t\t\tif (f) {\n\t\t\t\t\tVFS_CLOSE(f);\n\t\t\t\t}\n\t\t\t\tf = file;\n\t\t\t\tbest = &formats[i];\n\t\t\t}\n\t\t\telse {\n\t\t\t\tVFS_CLOSE(file);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (best && f) {\n\t\tsnprintf(name, sizeof(name), \"%s.%s\", basename, best->extension);\n\t\tif ((data = best->function(f, name, matchwidth, matchheight, real_width, real_height))) {\n\t\t\treturn data;\n\t\t}\n\t}\n\n\tif (mode & TEX_COMPLAIN) {\n\t\tif (!Block24BitTextures) {\n\t\t\tCom_Printf_State(PRINT_FAIL, \"Couldn't load %s image\\n\", COM_SkipPath(filename));\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\ntexture_ref R_LoadTexturePixels(byte *data, const char *identifier, int width, int height, int mode)\n{\n\tint i, j, image_size;\n\tqbool gamma;\n\textern byte vid_gamma_table[256];\n\textern float vid_gamma;\n\n\timage_size = width * height;\n\tgamma = (vid_gamma != 1);\n\n\tif (mode & TEX_LUMA) {\n\t\tgamma = false;\n\t}\n\telse if (mode & TEX_ALPHA) {\n\t\tmode &= ~TEX_ALPHA;\n\n\t\tfor (j = 0; j < image_size; j++) {\n\t\t\tif (((((unsigned *)data)[j] >> 24) & 0xFF) < 255) {\n\t\t\t\tmode |= TEX_ALPHA;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif ((mode & TEX_ALPHA) && (mode & TEX_PREMUL_ALPHA)) {\n\t\tR_ImagePreMultiplyAlpha(data, width, height, mode & TEX_ZERO_ALPHA);\n\t}\n\n\tif (R_OldGammaBehaviour() && gamma) {\n\t\tfor (i = 0; i < image_size; i++) {\n\t\t\tdata[4 * i] = vid_gamma_table[data[4 * i]];\n\t\t\tdata[4 * i + 1] = vid_gamma_table[data[4 * i + 1]];\n\t\t\tdata[4 * i + 2] = vid_gamma_table[data[4 * i + 2]];\n\t\t}\n\t}\n\n\treturn R_LoadTexture(identifier, width, height, data, mode, 4);\n}\n\ntexture_ref R_LoadPicTexture(const char *name, mpic_t *pic, byte *data)\n{\n\tint glwidth, glheight, i;\n\tchar fullname[MAX_QPATH] = \"pic:\";\n\tbyte *src, *dest, *buf;\n\n\tR_TextureSizeRoundUp(pic->width, pic->height, &glwidth, &glheight);\n\n\tstrlcpy(fullname + 4, name, sizeof(fullname) - 4);\n\tif (glwidth == pic->width && glheight == pic->height) {\n\t\tpic->texnum = R_LoadTexture(fullname, glwidth, glheight, data, TEX_ALPHA, 1);\n\t\tpic->sl = 0;\n\t\tpic->sh = 1;\n\t\tpic->tl = 0;\n\t\tpic->th = 1;\n\t}\n\telse {\n\t\tbuf = Q_calloc(glwidth * glheight, 1);\n\n\t\tsrc = data;\n\t\tdest = buf;\n\t\tfor (i = 0; i < pic->height; i++) {\n\t\t\tmemcpy(dest, src, pic->width);\n\t\t\tsrc += pic->width;\n\t\t\tdest += glwidth;\n\t\t}\n\t\tpic->texnum = R_LoadTexture(fullname, glwidth, glheight, buf, TEX_ALPHA, 1);\n\t\tpic->sl = 0;\n\t\tpic->sh = (float)pic->width / glwidth;\n\t\tpic->tl = 0;\n\t\tpic->th = (float)pic->height / glheight;\n\t\tQ_free(buf);\n\t}\n\n\treturn pic->texnum;\n}\n\ntexture_ref R_LoadTexture(const char *identifier, int width, int height, byte *data, int mode, int bpp)\n{\n\tunsigned short crc = identifier[0] && data ? CRC_Block(data, width * height * bpp) : 0;\n\tqbool new_texture = false;\n\tgltexture_t *glt = R_TextureAllocateSlot(texture_type_2d, identifier, width, height, 0, bpp, mode, crc, &new_texture);\n\n\tR_TraceEnterFunctionRegion;\n\tR_TraceAPI(\"R_LoadTexture(id=%s, width=%d, height=%d, mode=%d, bpp=%d)\", identifier, width, height, mode, bpp);\n\tif (glt && !new_texture) {\n\t\tR_TraceLeaveFunctionRegion;\n\t\treturn glt->reference;\n\t}\n\telse if (!glt) {\n\t\tR_TraceLeaveFunctionRegion;\n\t\treturn null_texture_reference;\n\t}\n\n\tif (data) {\n\t\tR_LoadTextureData(glt, width, height, data, mode, bpp);\n\t}\n\n\tR_TraceLeaveFunctionRegion;\n\treturn glt->reference;\n}\n\n//\n// Uploads a 32-bit texture to OpenGL. Makes sure it's the correct size and creates mipmaps if requested.\n//\nstatic void R_Upload32(gltexture_t* glt, unsigned *data, int width, int height, int mode)\n{\n\t// Tell OpenGL the texnum of the texture before uploading it.\n\textern cvar_t gl_lerpimages, gl_wicked_luma_level;\n\tint\ttempwidth, tempheight;\n\tbyte *newdata;\n\n\tR_TextureSizeRoundUp(width, height, &tempwidth, &tempheight);\n\n\tnewdata = Q_malloc(tempwidth * tempheight * 4);\n\n\t// Resample the image if it's not scaled to the power of 2,\n\t// we take care of this when drawing using the texture coordinates.\n\tif (width < tempwidth || height < tempheight) {\n\t\tImage_Resample(data, width, height, newdata, tempwidth, tempheight, 4, !!gl_lerpimages.value);\n\t\twidth = tempwidth;\n\t\theight = tempheight;\n\t}\n\telse {\n\t\t// Scale is a power of 2, just copy the data.\n\t\tmemcpy(newdata, data, width * height * 4);\n\t}\n\n\tif ((mode & TEX_FULLBRIGHT) && (mode & TEX_LUMA) && gl_wicked_luma_level.integer > 0) {\n\t\tint i, cnt = width * height * 4, level = gl_wicked_luma_level.integer;\n\n\t\tfor (i = 0; i < cnt; i += 4) {\n\t\t\tif (newdata[i] < level && newdata[i + 1] < level && newdata[i + 2] < level) {\n\t\t\t\t// make black pixels transparent, well not always black, depends of level...\n\t\t\t\tnewdata[i] = newdata[i + 1] = newdata[i + 2] = newdata[i + 3] = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Get the scaled dimension (scales according to gl_picmip and max allowed texture size).\n\tR_TextureUtil_ScaleDimensions(width, height, &tempwidth, &tempheight, mode);\n\n\t// If the image size is bigger than the max allowed size or \n\t// set picmip value we calculate it's next closest mip map.\n\twhile (width > tempwidth || height > tempheight) {\n\t\tImage_MipReduce(newdata, newdata, &width, &height, 4);\n\t}\n\n\tglt->texture_width = width;\n\tglt->texture_height = height;\n\n\tif (mode & TEX_MIPMAP) {\n\t\tint largest = max(width, height);\n\t\twhile (largest) {\n\t\t\tlargest /= 2;\n\t\t}\n\t}\n\n\tif (mode & TEX_BRIGHTEN) {\n\t\tR_TextureUtil_Brighten32(newdata, width * height * 4);\n\t}\n\n\tif (R_UseImmediateOpenGL() || R_UseModernOpenGL()) {\n\t\tGL_UploadTexture(glt->reference, glt->texmode, width, height, newdata);\n\t}\n\telse if (R_UseVulkan()) {\n\t\t//VK_UploadTexture(glt->reference, glt->texmode, width, height, newdata);\n\t}\n\n\tR_TextureUtil_SetFiltering(glt->reference);\n\n\tQ_free(newdata);\n}\n\nstatic void R_Upload8(gltexture_t* glt, byte *data, int width, int height, int mode)\n{\n\tstatic unsigned* trans;\n\tstatic int trans_size;\n\tint\ti, image_size, p;\n\tunsigned *table;\n\n\ttable = (mode & TEX_BRIGHTEN) ? d_8to24table2 : d_8to24table;\n\timage_size = width * height;\n\n\tif (image_size * 4 > trans_size) {\n\t\tunsigned* newmem = Q_realloc(trans, image_size * 4);\n\t\tif (newmem == NULL) {\n\t\t\tSys_Error(\"GL_Upload8: image too big (%s: %dx%d)\", glt->identifier[0] ? glt->identifier : \"?unknown?\", width, height);\n\t\t}\n\t\ttrans = newmem;\n\t\ttrans_size = image_size * 4;\n\t}\n\n\tif (mode & TEX_FULLBRIGHT) {\n\t\tqbool was_alpha = mode & TEX_ALPHA;\n\n\t\t// This is a fullbright mask, so make all non-fullbright colors transparent.\n\t\tmode |= TEX_ALPHA;\n\n\t\tfor (i = 0; i < image_size; i++) {\n\t\t\tp = data[i];\n\t\t\tif (p < 224 || (p == 255 && was_alpha)) {\n\t\t\t\ttrans[i] = 0; // Transparent.\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttrans[i] = (p == 255) ? LittleLong(0xff535b9f) : table[p]; // Fullbright. Disable transparancy on fullbright colors (255).\n\t\t\t}\n\t\t}\n\t}\n\telse if (mode & TEX_ALPHA) {\n\t\t// If there are no transparent pixels, make it a 3 component\n\t\t// texture even if it was specified as otherwise\n\t\tmode &= ~TEX_ALPHA;\n\n\t\tfor (i = 0; i < image_size; i++) {\n\t\t\tif ((p = data[i]) == 255) {\n\t\t\t\tmode |= TEX_ALPHA;\n\t\t\t}\n\t\t\ttrans[i] = table[p];\n\t\t}\n\t}\n\telse {\n\t\tif (image_size & 3) {\n\t\t\tSys_Error(\"GL_Upload8: image_size & 3\");\n\t\t}\n\n\t\t// Convert the 8-bit colors to 24-bit.\n\t\tfor (i = 0; i < image_size; i += 4) {\n\t\t\ttrans[i] = table[data[i]];\n\t\t\ttrans[i + 1] = table[data[i + 1]];\n\t\t\ttrans[i + 2] = table[data[i + 2]];\n\t\t\ttrans[i + 3] = table[data[i + 3]];\n\t\t}\n\t}\n\n\tR_Upload32(glt, trans, width, height, mode & ~TEX_BRIGHTEN);\n}\n\nstatic void R_LoadTextureData(gltexture_t* glt, int width, int height, byte *data, int mode, int bpp)\n{\n\t// Upload the texture to OpenGL based on the bytes per pixel.\n\tswitch (bpp) {\n\t\tcase 1:\n\t\t\tR_Upload8(glt, data, width, height, mode);\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\tR_Upload32(glt, (void *)data, width, height, mode);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tSys_Error(\"R_LoadTexture: unknown bpp\\n\");\n\t\t\tbreak;\n\t}\n}\n\nvoid R_TextureSizeRoundUp(int orig_width, int orig_height, int* width, int* height)\n{\n\tif (r_texture_support_non_power_of_two) {\n\t\t*width = orig_width;\n\t\t*height = orig_height;\n\t}\n\telse {\n\t\tQ_ROUND_POWER2(orig_width, (*width));\n\t\tQ_ROUND_POWER2(orig_height, (*height));\n\t}\n}\n\nvoid R_SetNonPowerOfTwoSupport(qbool supported)\n{\n\tr_texture_support_non_power_of_two = supported;\n}\n"
  },
  {
    "path": "src/r_texture_util.c",
    "content": "\n#include \"quakedef.h\"\n#include \"r_texture.h\"\n#include \"r_texture_internal.h\"\n#include \"tr_types.h\"\n\nvoid R_TextureUtil_ScaleDimensions(int width, int height, int *scaled_width, int *scaled_height, int mode)\n{\n\tint maxsize, picmip;\n\tqbool scale = (mode & TEX_MIPMAP) && !(mode & TEX_NOSCALE);\n\textern cvar_t gl_picmip;\n\n\tR_TextureSizeRoundUp(width, height, scaled_width, scaled_height);\n\n\tif (scale) {\n\t\tpicmip = (int)bound(0, gl_picmip.value, 16);\n\t\t*scaled_width >>= picmip;\n\t\t*scaled_height >>= picmip;\n\t}\n\n\tmaxsize = scale ? gl_max_size.value : glConfig.gl_max_size_default;\n\n\t*scaled_width = bound(1, *scaled_width, maxsize);\n\t*scaled_height = bound(1, *scaled_height, maxsize);\n}\n\nvoid R_TextureUtil_Brighten32(byte *data, int size)\n{\n\tbyte *p;\n\tint i;\n\n\tp = data;\n\tfor (i = 0; i < size / 4; i++) {\n\t\tp[0] = min(p[0] * 2.0 / 1.5, 255);\n\t\tp[1] = min(p[1] * 2.0 / 1.5, 255);\n\t\tp[2] = min(p[2] * 2.0 / 1.5, 255);\n\t\tp += 4;\n\t}\n}\n\nvoid R_TextureUtil_ImageDimensionsToTexture(int imageWidth_, int imageHeight_, int* textureWidth, int* textureHeight, int mode)\n{\n\tint imageWidth, imageHeight;\n\tint scaledWidth, scaledHeight;\n\n\tR_TextureSizeRoundUp(imageWidth_, imageHeight_, &imageWidth, &imageHeight);\n\tR_TextureUtil_ScaleDimensions(imageWidth, imageHeight, &scaledWidth, &scaledHeight, mode);\n\twhile (imageWidth > scaledWidth || imageHeight > scaledHeight) {\n\t\timageWidth = max(1, imageWidth / 2);\n\t\timageHeight = max(1, imageHeight / 2);\n\t}\n\t*textureWidth = imageWidth;\n\t*textureHeight = imageHeight;\n}\n\nvoid R_TextureUtil_SetFiltering(texture_ref texture)\n{\n\tint mode = gltextures[texture.index].texmode;\n\n\tR_TextureModeChanged(texture, mode & TEX_MIPMAP, mode & TEX_VIEWMODEL);\n\tR_TextureAnisotropyChanged(texture, mode & TEX_MIPMAP, mode & TEX_VIEWMODEL);\n}\n"
  },
  {
    "path": "src/r_trace.h",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_R_TRACE_HEADER\n#define EZQUAKE_R_TRACE_HEADER\n\n#ifdef WITH_RENDERING_TRACE\n#define R_TraceEnterFunctionRegion R_TraceEnterRegion(__FUNCTION__, true)\n#define R_TraceLeaveFunctionRegion R_TraceLeaveRegion(true)\n#define R_TraceEnterNamedRegion(x) R_TraceEnterRegion(x, false)\n#define R_TraceLeaveNamedRegion() R_TraceLeaveRegion(false)\nvoid R_TraceEnterRegion(const char* regionName, qbool trace_only);\nvoid R_TraceLeaveRegion(qbool trace_only);\nvoid R_TracePrintState(FILE* output, int indent);\nvoid R_TraceDebugState(void);\nvoid R_TraceResetRegion(qbool start);\nvoid R_TraceLogAPICallDirect(const char* message, ...);\n#define R_TraceLogAPICall(...) { R_TraceLogAPICallDirect(__VA_ARGS__); }\nvoid R_TraceAPI(const char* formatString, ...);\nqbool R_TraceLoggingEnabled(void);\nvoid R_TraceTextureLabelGet(unsigned int name, int bufSize, int* length, char* label);\n#else\n#define R_TraceEnterFunctionRegion\n#define R_TraceLeaveFunctionRegion\n#define R_TraceEnterNamedRegion(x)\n#define R_TraceLeaveNamedRegion()\n#define R_TraceEnterRegion(...)\n#define R_TraceLeaveRegion(...)\n#define R_TracePrintState(...)\n#define R_TraceDebugState()\n#define R_TraceResetRegion(x)\n#define R_TraceLogAPICallDirect(x, ...)\n#define R_TraceLogAPICall(...)\n#define R_TraceAPI(formatString, ...)\n#define R_TraceLoggingEnabled() (false)\n#define R_TraceTextureLabelSet(...)\n#define R_TraceTextureLabelGet(...)\n#endif\n\n#endif // EZQUAKE_R_TRACE_HEADER\n"
  },
  {
    "path": "src/r_vao.h",
    "content": "/*\nCopyright (C) 2018 ezQuake team.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_R_VAO_HEADER\n#define EZQUAKE_R_VAO_HEADER\n\n#include \"r_buffers.h\"\n\ntypedef enum {\n\tvao_none,\n\tvao_aliasmodel,\n\tvao_brushmodel,\n\tvao_3dsprites,\n\tvao_hud_circles,\n\tvao_hud_images,\n\tvao_hud_lines,\n\tvao_hud_polygons,\n\tvao_postprocess,\n\n\t// non-shader (classic OpenGL) only\n\tvao_aliasmodel_powerupshell,\n\tvao_brushmodel_details,\n\tvao_brushmodel_lightmap_pass,\n\tvao_brushmodel_lm_unit1,\n\tvao_brushmodel_simpletex,\n\tvao_hud_images_non_glsl,\n\n\tvao_count\n} r_vao_id;\n\nvoid R_GenVertexArray(r_vao_id vao);\nvoid R_BindVertexArray(r_vao_id vao);\nqbool R_VertexArrayCreated(r_vao_id vao);\nvoid R_InitialiseVAOState(void);\nqbool R_VAOBound(void);\nvoid R_BindVertexArrayElementBuffer(r_buffer_id ref);\n\ntypedef struct r_vaos_api_s {\n\tvoid(*Bind)(r_vao_id vao);\n} r_vaos_api_t;\n\n#endif // EZQUAKE_R_VAO_HEADER\n"
  },
  {
    "path": "src/render.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n// refresh.h -- public interface to refresh functions\n\n#define\tTOP_RANGE\t\t16\t\t\t// soldier uniform colors\n#define\tBOTTOM_RANGE\t96\n\n//=============================================================================\n\ntypedef struct efrag_s {\n\tstruct mleaf_s\t\t*leaf;\n\tstruct efrag_s\t\t*leafnext;\n\tstruct entity_s\t\t*entity;\n\tstruct efrag_s\t\t*entnext;\n} efrag_t;\n\n#define RF_WEAPONMODEL       1\n#define RF_NOSHADOW          2\n#define RF_LIMITLERP         4\n#define RF_PLAYERMODEL       8\n#define RF_NORMALENT        16\n#define RF_CAUSTICS         32\n#define RF_ALPHABLEND       64   // bit of a hack - always enable blending (used for 2nd pass when multi-texturing disabled)\n#define RF_ADDITIVEBLEND   128\n#define RF_ROCKETPACK      256\n#define RF_LGPACK          512\n#define RF_BEHINDWALL     1024\n#define RF_VWEPMODEL      2048\n#define RF_FORCECOLOURMOD 4096\n\n#define RF_BACKPACK_FLAGS (RF_ROCKETPACK | RF_LGPACK)\n\ntypedef struct custom_model_color_s {\n\tcvar_t color_cvar;\n\tcvar_t fullbright_cvar;\n\tcvar_t* amf_cvar;\n\tint model_hint;\n\tint renderfx;\n\tqbool disable_texturing;\n} custom_model_color_t;\n\ntypedef struct entity_s {\n\tvec3_t\t\t\t\t\torigin;\n\tvec3_t\t\t\t\t\tangles;\t\n\tstruct model_s\t\t\t*model;\t\t\t// NULL = no model\n\tbyte\t\t\t\t\t*colormap;\n\tint\t\t\t\t\t\tskinnum;\t\t// for Alias models\n\tstruct player_info_s\t*scoreboard;\t// identify player\n\tint\t\t\t\t\t\trenderfx;\t\t// RF_ bit mask\n\tint\t\t\t\t\t\teffects;\t\t// EF_ flags like EF_BLUE and etc, atm this used for powerup shells\n\n\tint\t\t\t\t\t\toldframe;\n\tint\t\t\t\t\t\tframe;\n\tfloat\t\t\t\t\tframelerp;\n\n\tstruct efrag_s\t\t\t*efrag;\t\t\t// linked list of efrags (FIXME)\n\tint\t\t\t\t\t\tvisframe;\t\t// last frame this entity was found in an active leaf. only used for static objects\n\n\t//int\t\t\t\t\t\tdlightframe;\t// dynamic lighting\n\t//int\t\t\t\t\t\tdlightbits;\n\n\t//VULT MOTION TRAILS\n\tfloat alpha;\n\n\t// FIXME: could turn these into a union\n\tstruct mnode_s\t\t\t*topnode;\t\t// for bmodels, first world node that splits bmodel, or NULL if not split\n\n\tqbool     full_light;\n\n\tfloat     r_modelcolor[3];\n\tfloat     r_modelalpha;\n\tfloat     shadelight;\n\tfloat     ambientlight;\n\tcustom_model_color_t* custom_model;\n\tvec3_t    lightcolor; // LordHavoc: used by model rendering\n\tvec3_t    lightspot;  // used for shadows\n\n\t// vertex lighting only\n\tint       bestlight;\n\tint       entity_id;\n\tint       corona_id;\n\tdouble    particle_time;\n\n\t// outlining\n\tfloat     outlineScale;\n} entity_t;\n\n// !!! if this is changed, it must be changed in asm_draw.h too !!!\ntypedef struct {\n\tvrect_t\t\tvrect;\t\t\t\t// subwindow in video for refresh\n\t\t\t\t\t\t\t\t\t// FIXME: not need vrect next field here?\n\tvrect_t\t\taliasvrect;\t\t\t// scaled Alias version\n\tint\t\t\tvrectright, vrectbottom;\t// right & bottom screen coords\n\tint\t\t\taliasvrectright, aliasvrectbottom;\t// scaled Alias versions\n\tfloat\t\tvrectrightedge;\t\t\t// rightmost right edge we care about,\n\t\t\t\t\t\t\t\t\t\t//  for use in edge list\n\tfloat\t\tfvrectx, fvrecty;\t\t// for floating-point compares\n\tfloat\t\tfvrectx_adj, fvrecty_adj; // left and top edges, for clamping\n\tint\t\t\tvrect_x_adj_shift20;\t// (vrect.x + 0.5 - epsilon) << 20\n\tint\t\t\tvrectright_adj_shift20;\t// (vrectright + 0.5 - epsilon) << 20\n\tfloat\t\tfvrectright_adj, fvrectbottom_adj;\n\t\t\t\t\t\t\t\t\t\t// right and bottom edges, for clamping\n\tfloat\t\tfvrectright;\t\t\t// rightmost edge, for Alias clamping\n\tfloat\t\tfvrectbottom;\t\t\t// bottommost edge, for Alias clamping\n\tfloat\t\thorizontalFieldOfView;\t// at Z = 1.0, this many X is visible \n\t\t\t\t\t\t\t\t\t\t// 2.0 = 90 degrees\n\tfloat\t\txOrigin;\t\t\t// should probably always be 0.5\n\tfloat\t\tyOrigin;\t\t\t// between be around 0.3 to 0.5\n\n\tvec3_t\t\tvieworg;\n\tvec3_t\t\tviewangles;\n\tint         viewheight_test;\n\n\tfloat\t\tfov_x, fov_y;\n\t\n\tint\t\t\tambientlight;\n} refdef_t;\n\ntypedef enum {\n\tfogcalc_none,\n\tfogcalc_linear,\n\tfogcalc_exp,\n\tfogcalc_exp2\n} fogcalc_t;\n\n// eye position, enitity list, etc - filled in before calling R_RenderView (TODO: port from ZQuake)\ntypedef struct {\n\tdouble\t\t\ttime;\n\tqbool\t\t\tallow_cheats;\n\tqbool\t\t\tallow_lumas;\n\tfloat\t\t\tmax_watervis;\t// how much water transparency we allow: 0..1 (reverse of alpha)\n\tfloat\t\t\tmax_fbskins;\t// max player skin brightness 0..1\n//\tint\t\t\t\tviewplayernum;  // don't draw own glow when gl_flashblend 1\n\n//\tlightstyle_t    *lightstyles;\n\tqbool           fog_enabled;    // fog is enabled (glsl programs etc should have fog enabled)\n\tqbool           fog_render;     // fog should be rendered\n\tfogcalc_t       fog_calculation;\n\tfloat           fog_linear_start;\n\tfloat           fog_linear_end;\n\tfloat           fog_density;    // 0+\n\tfloat           fog_color[4];\n\tfloat           fog_skycolor[4];// \n\tfloat           fog_sky;        // 0..1 - how much sky is affected by fog\n\n\tfloat           cos_time;\n\tfloat           sin_time;\n\n\tfloat           wateralpha;\n\tqbool           drawFlatFloors;\n\tqbool           drawFlatWalls;\n\tqbool           solidTexTurb;\n\tqbool           drawCaustics;\n\tqbool           drawWorldOutlines;\n\tfloat           distanceScale;\n\n\tvec3_t          outline_vpn;\n\tfloat           outlineBase;\n\n\tfloat           powerup_scroll_params[4];\n} refdef2_t;\n\n\n// refresh\n\nextern refdef_t\tr_refdef;\nextern refdef2_t r_refdef2;\nextern vec3_t r_origin, vpn, vright, vup;\nextern vec3_t vright_noroll, vup_noroll;\n\nextern struct texture_s\t*r_notexture_mip;\n\nextern entity_t r_worldentity;\n\nvoid R_Init(void);\nvoid R_InitTextures(void);\nvoid R_PostProcessScene(void);\nvoid R_RenderView(void);\t\t// must set r_refdef first\n\nvoid R_Init_EFrags (void);\nvoid R_AddEfrags(entity_t *ent);\nvoid R_NewMap(qbool vid_restart);\nvoid R_NewMapPreLoad(void);\n\nvoid R_PushDlights(void);\n"
  },
  {
    "path": "src/resource.h",
    "content": "//{{NO_DEPENDENCIES}}\n// Microsoft Visual C++ generated include file.\n// Used by ezQuake.rc\n//\n#define IDI_ICON1                       101\n\n// Next default values for new objects\n// \n#ifdef APSTUDIO_INVOKED\n#ifndef APSTUDIO_READONLY_SYMBOLS\n#define _APS_NEXT_RESOURCE_VALUE        102\n#define _APS_NEXT_COMMAND_VALUE         40001\n#define _APS_NEXT_CONTROL_VALUE         1001\n#define _APS_NEXT_SYMED_VALUE           101\n#endif\n#endif\n"
  },
  {
    "path": "src/rulesets.c",
    "content": "/*\nCopyright (C) 2001-2002 A Nourai\nCopyright (C) 2015 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"rulesets.h\"\n#include \"input.h\"\n\n/* FIXME: Figure out a nicer way to do all this */\n\nstatic void Rulesets_OnChange_ruleset (cvar_t *var, char *value, qbool *cancel);\n\ntypedef struct rulesetDef_s {\n\truleset_t ruleset;\n\tfloat maxfps;\n\tqbool restrictTriggers;\n\tqbool restrictPacket;\n\tqbool restrictParticles;\n\tqbool restrictPlay;\n\tqbool restrictLogging;\n\tqbool restrictRollAngle;\n\tqbool restrictIPC;\n\tqbool restrictExec;\n\tqbool restrictSetCalc;\n\tqbool restrictSetEval;\n\tqbool restrictSetEx;\n} rulesetDef_t;\n\nstatic rulesetDef_t rulesetDef = {\n\trs_default,    // ruleset\n\t72.0,          // maxfps\n\tfalse,         // restrict triggers\n\tfalse,         // restrict /packet command\n\tfalse,         // restrict particles\n\tfalse,         // restrict sound\n\tfalse,         // restrict logging\n\tfalse,         // restrict rollangle\n\tfalse,         // restrict IPC\n\tfalse,         // restrict /exec command\n\tfalse,         // restrict /set_calc command\n\tfalse,         // restrict /set_eval command\n\tfalse          // restrict /set_ex command\n};\n\ncvar_t ruleset = {\"ruleset\", \"default\", 0, Rulesets_OnChange_ruleset};\n\nqbool RuleSets_DisallowExternalTexture(struct model_s *mod)\n{\n\tswitch (mod->modhint) {\n\t\tcase MOD_EYES:\n\t\t\treturn true;\n\t\tcase MOD_BACKPACK:\n\t\t\treturn rulesetDef.ruleset == rs_smackdown || rulesetDef.ruleset == rs_qcon || rulesetDef.ruleset == rs_smackdrive;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nqbool RuleSets_DisallowSimpleTexture(model_t* mod)\n{\n\tswitch (mod->modhint) {\n\t\tcase MOD_EYES:\n\t\tcase MOD_PLAYER:\n\t\tcase MOD_SENTRYGUN: // tf\n\t\tcase MOD_DETPACK:   // tf\n\t\t\treturn true; // no replacement allowed\n\n\t\tcase MOD_BACKPACK:\n\t\t\t// Now allowed in Thunderdome...\n\t\t\treturn rulesetDef.ruleset == rs_smackdown || rulesetDef.ruleset == rs_qcon || rulesetDef.ruleset == rs_smackdrive;\n\n\t\tdefault:\n\t\t\treturn false; // replacement always allowed\n\t}\n}\n\n// for models (gl_outline 1 and 3)\nqbool RuleSets_DisallowModelOutline(struct model_s *mod)\n{\n\tif (mod == NULL) {\n\t\t// World model - only allow in default ruleset, cheats enabled\n\t\treturn !(r_refdef2.allow_cheats && rulesetDef.ruleset == rs_default);\n\t}\n\n\tswitch (mod->modhint) {\n\t\tcase MOD_EYES:\n\t\tcase MOD_THUNDERBOLT:\n\t\t\treturn true;\n\t\tcase MOD_BACKPACK:\n\t\t\treturn !cls.demoplayback && (rulesetDef.ruleset == rs_qcon || rulesetDef.ruleset == rs_smackdown || rulesetDef.ruleset == rs_smackdrive);\n\t\tdefault:\n\t\t\t// return to just rs_qcon once backface outlining tested\n//\t\t\treturn !cls.demoplayback && (rulesetDef.ruleset == rs_qcon || rulesetDef.ruleset == rs_smackdown || rulesetDef.ruleset == rs_smackdrive);\n\t\t\treturn !cls.demoplayback && (rulesetDef.ruleset == rs_qcon);\n\t}\n}\n\n// gl_outline_scale_model\n// 0-1 for smackdown and qcon, 0-5 for others\nfloat RuleSets_ModelOutlineScale(void) {\n\textern cvar_t gl_outline_scale_model;\n\tswitch(rulesetDef.ruleset) {\n\t\tcase rs_smackdown:\n\t\tcase rs_smackdrive:\n\t\tcase rs_qcon:\n\t\t\treturn bound(0.0f, gl_outline_scale_model.value, 1.0f);\n\t\tdefault:\n\t\t\treturn bound(0.0f, gl_outline_scale_model.value, 5.0f);\n\t}\n}\n\n// for edges (gl_outline 2 and 3)\nqbool RuleSets_AllowEdgeOutline(void)\n{\n\tswitch(rulesetDef.ruleset) {\n\t\tcase rs_qcon:\n\t\t\treturn false;\n\t\tdefault:\n\t\t\treturn true;\n\t}\n}\n\nqbool Rulesets_AllowTimerefresh(void)\n{\n\tswitch(rulesetDef.ruleset) {\n\t\tcase rs_smackdown:\n\t\tcase rs_smackdrive:\n\t\tcase rs_thunderdome:\n\t\tcase rs_qcon:\n\t\t\treturn (cl.standby || cl.spectator || cls.demoplayback);\n\t\tdefault:\n\t\t\treturn true;\n\t}\n}\n\nqbool Rulesets_AllowNoShadows(void)\n{\n\tswitch(rulesetDef.ruleset) {\n\t\tcase rs_mtfl:\n\t\tcase rs_smackdown:\n\t\tcase rs_smackdrive:\n\t\tcase rs_thunderdome:\n\t\tcase rs_qcon:\n\t\t\treturn false;\n\t\tdefault:\n\t\t\treturn true;\n\t}\n}\n\nqbool Rulesets_AllowAlternateModel (const char* modelName)\n{\n\tswitch(rulesetDef.ruleset) {\n\tcase rs_qcon:\n\t\tif (! strcmp (modelName, \"progs/player.mdl\"))\n\t\t\treturn false;\n\t\treturn true;\n\tdefault:\n\t\treturn true;\n\t}\n}\n\nfloat Rulesets_MaxFPS(void)\n{\n\treturn rulesetDef.maxfps;\n}\n\nqbool Rulesets_RestrictTriggers(void)\n{\n\treturn rulesetDef.restrictTriggers;\n}\n\nqbool Rulesets_RestrictPlay(const char* name)\n{\n\tif (!rulesetDef.restrictPlay) {\n\t\treturn false;\n\t}\n\n\tif (cls.state == ca_active && (cl.spectator || cls.demoplayback || cl.standby)) {\n\t\treturn false;\n\t}\n\n\tif (name == NULL || cbuf_current != &cbuf_svc) {\n\t\treturn true;\n\t}\n\n\tif (strstr(name, \"ktsound\")) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nqbool Rulesets_RestrictPacket(void)\n{\n\treturn cls.state == ca_active && !cl.spectator && !cls.demoplayback && !cl.standby && rulesetDef.restrictPacket;\n}\n\nqbool Rulesets_RestrictParticles(void)\n{\n\treturn !cl.spectator && !cls.demoplayback && !cl.standby && rulesetDef.restrictParticles && !r_refdef2.allow_cheats;\n}\n\nqbool Rulesets_RestrictIPC(void)\n{\n\treturn cls.state == ca_active && !cl.spectator && !cls.demoplayback && !cl.standby && rulesetDef.restrictIPC && cls.server_adr.type != NA_LOOPBACK;;\n}\n\nqbool Rulesets_RestrictExec(void)\n{\n\treturn cls.state == ca_active && !cl.spectator && !cls.demoplayback && !cl.standby && rulesetDef.restrictExec && cls.server_adr.type != NA_LOOPBACK;\n}\n\nqbool Rulesets_RestrictSetCalc(void)\n{\n\treturn cls.state == ca_active && !cl.spectator && !cls.demoplayback && !cl.standby && rulesetDef.restrictSetCalc;\n}\n\nqbool Rulesets_RestrictSetEval(void)\n{\n\treturn cls.state == ca_active && !cl.spectator && !cls.demoplayback && !cl.standby && rulesetDef.restrictSetEval;\n}\n\nqbool Rulesets_RestrictSetEx(void)\n{\n\treturn cls.state == ca_active && !cl.spectator && !cls.demoplayback && !cl.standby && rulesetDef.restrictSetEx;\n}\n\nqbool Rulesets_RestrictTCL(void)\n{\n\tswitch(rulesetDef.ruleset) {\n\t\tcase rs_smackdown:\n\t\tcase rs_smackdrive:\n\t\tcase rs_thunderdome:\n\t\tcase rs_qcon:\n\t\t\treturn true;\n\t\tcase rs_mtfl:\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nconst char *Rulesets_Ruleset(void)\n{\n\tswitch(rulesetDef.ruleset) {\n\t\tcase rs_mtfl:\n\t\t\treturn \"MTFL\";\n\t\tcase rs_smackdown:\n\t\t\treturn \"smackdown\";\n\t\tcase rs_thunderdome:\n\t\t\treturn \"thunderdome\";\n\t\tcase rs_qcon:\n\t\t\treturn \"qcon\";\n\t\tcase rs_smackdrive:\n\t\t\treturn \"smackdrive\";\n\t\tdefault:\n\t\t\treturn \"default\";\n\t}\n}\n\nstatic void Rulesets_Smackdown(qbool enable)\n{\n\textern cvar_t cl_independentPhysics, cl_c2spps;\n\textern cvar_t cl_hud;\n\textern cvar_t cl_rollalpha;\n\textern cvar_t r_shiftbeam;\n\textern cvar_t allow_scripts;\n\textern cvar_t cl_iDrive;\n\textern cvar_t scr_allowsnap;\n\tint i;\n\n\tlocked_cvar_t disabled_cvars[] = {\n\t\t{&allow_scripts, \"0\"},  // disable movement scripting\n\t\t{&cl_iDrive, \"0\"},      // disable strafing aid\n\t\t{&cl_hud, \"0\"},         // allows you place any text on the screen & filter incoming messages (hud strings)\n\t\t{&cl_rollalpha, \"20\"},  // allows you to not dodge while seeing enemies dodging\n\t\t{&r_shiftbeam, \"0\"},    // perphaps some people would think this allows you to aim better (maybe should be added for demo playback and spectating only)\n\t\t{&scr_allowsnap, \"1\"}\n\t};\n\n\tif (enable) {\n\t\tfor (i = 0; i < (sizeof(disabled_cvars) / sizeof(disabled_cvars[0])); i++) {\n\t\t\tCvar_RulesetSet(disabled_cvars[i].var, disabled_cvars[i].value, 2);\n\t\t\tCvar_Set(disabled_cvars[i].var, disabled_cvars[i].value);\n\t\t\tCvar_SetFlags(disabled_cvars[i].var, Cvar_GetFlags(disabled_cvars[i].var) | CVAR_ROM);\n\t\t}\n\n\t\tif (cl_independentPhysics.value) {\n\t\t\tCvar_Set(&cl_c2spps, \"0\"); // people were complaining that player move is jerky with this. however this has not much to do with independent physics, but people are too paranoid about it\n\t\t\tCvar_SetFlags(&cl_c2spps, Cvar_GetFlags(&cl_c2spps) | CVAR_ROM);\n\t\t}\n\n\t\trulesetDef.maxfps = 77;\n\t\trulesetDef.restrictTriggers = true;\n\t\trulesetDef.restrictPacket = true; // packet command could have been exploited for external timers\n\t\trulesetDef.restrictParticles = true;\n\t\trulesetDef.restrictLogging = true;\n\t\trulesetDef.restrictRollAngle = true;\n\t\trulesetDef.ruleset = rs_smackdown;\n\t\trulesetDef.restrictIPC = true;\n\t\trulesetDef.restrictExec = true;\n\t\trulesetDef.restrictSetCalc = true;\n\t\trulesetDef.restrictSetEval = true;\n\t\trulesetDef.restrictSetEx = true;\n\t\trulesetDef.restrictPlay = true;\n\t} else {\n\t\tfor (i = 0; i < (sizeof(disabled_cvars) / sizeof(disabled_cvars[0])); i++)\n\t\t\tCvar_SetFlags(disabled_cvars[i].var, Cvar_GetFlags(disabled_cvars[i].var) & ~CVAR_ROM);\n\n\t\tif (cl_independentPhysics.value)\n\t\t\tCvar_SetFlags(&cl_c2spps, Cvar_GetFlags(&cl_c2spps) & ~CVAR_ROM);\n\n\t\trulesetDef.maxfps = 72.0;\n\t\trulesetDef.restrictTriggers = false;\n\t\trulesetDef.restrictPacket = false;\n\t\trulesetDef.restrictParticles = false;\n\t\trulesetDef.restrictLogging = false;\n\t\trulesetDef.restrictRollAngle = false;\n\t\trulesetDef.ruleset = rs_default;\n\t\trulesetDef.restrictIPC = false;\n\t\trulesetDef.restrictExec = false;\n\t\trulesetDef.restrictSetCalc = false;\n\t\trulesetDef.restrictSetEval = false;\n\t\trulesetDef.restrictSetEx = false;\n\t\trulesetDef.restrictPlay = false;\n\t}\n}\n\nstatic void Rulesets_Qcon(qbool enable)\n{\n\textern cvar_t cl_independentPhysics, cl_c2spps;\n\textern cvar_t cl_hud;\n\textern cvar_t cl_rollalpha;\n\textern cvar_t r_shiftbeam;\n\textern cvar_t allow_scripts;\n\textern cvar_t cl_iDrive;\n\tint i;\n\n\tlocked_cvar_t disabled_cvars[] = {\n\t\t{&allow_scripts, \"0\"},  // disable movement scripting\n\t\t{&cl_iDrive, \"0\"},      // disable strafing aid\n\t\t{&cl_hud, \"0\"},         // allows you place any text on the screen & filter incoming messages (hud strings)\n\t\t{&cl_rollalpha, \"20\"},  // allows you to not dodge while seeing enemies dodging\n\t\t{&r_shiftbeam, \"0\"}     // perphaps some people would think this allows you to aim better (maybe should be added for demo playback and spectating only)\n\t};\n\n\tif (enable) {\n\t\tfor (i = 0; i < (sizeof(disabled_cvars) / sizeof(disabled_cvars[0])); i++) {\n\t\t\tCvar_RulesetSet(disabled_cvars[i].var, disabled_cvars[i].value, 2);\n\t\t\tCvar_Set(disabled_cvars[i].var, disabled_cvars[i].value);\n\t\t\tCvar_SetFlags(disabled_cvars[i].var, Cvar_GetFlags(disabled_cvars[i].var) | CVAR_ROM);\n\t\t}\n\n\t\tif (cl_independentPhysics.value) {\n\t\t\tCvar_Set(&cl_c2spps, \"0\"); // people were complaining that player move is jerky with this. however this has not much to do with independent physics, but people are too paranoid about it\n\t\t\tCvar_SetFlags(&cl_c2spps, Cvar_GetFlags(&cl_c2spps) | CVAR_ROM);\n\t\t}\n\n\t\trulesetDef.maxfps = 77;\n\t\trulesetDef.restrictTriggers = true;\n\t\trulesetDef.restrictPacket = true; // packet command could have been exploited for external timers\n\t\trulesetDef.restrictParticles = true;\n\t\trulesetDef.restrictPlay = true;\n\t\trulesetDef.restrictLogging = true;\n\t\trulesetDef.restrictRollAngle = true;\n\t\trulesetDef.ruleset = rs_qcon;\n\t\trulesetDef.restrictIPC = true;\n\t\trulesetDef.restrictExec = true;\n\t\trulesetDef.restrictSetCalc = true;\n\t\trulesetDef.restrictSetEval = true;\n\t\trulesetDef.restrictSetEx = true;\n\t} else {\n\t\tfor (i = 0; i < (sizeof(disabled_cvars) / sizeof(disabled_cvars[0])); i++)\n\t\t\tCvar_SetFlags(disabled_cvars[i].var, Cvar_GetFlags(disabled_cvars[i].var) & ~CVAR_ROM);\n\n\t\tif (cl_independentPhysics.value)\n\t\t\tCvar_SetFlags(&cl_c2spps, Cvar_GetFlags(&cl_c2spps) & ~CVAR_ROM);\n\n\t\trulesetDef.maxfps = 72.0;\n\t\trulesetDef.restrictTriggers = false;\n\t\trulesetDef.restrictPacket = false;\n\t\trulesetDef.restrictParticles = false;\n\t\trulesetDef.restrictPlay = false;\n\t\trulesetDef.restrictLogging = false;\n\t\trulesetDef.restrictRollAngle = false;\n\t\trulesetDef.ruleset = rs_default;\n\t\trulesetDef.restrictIPC = false;\n\t\trulesetDef.restrictExec = false;\n\t\trulesetDef.restrictSetCalc = false;\n\t\trulesetDef.restrictSetEval = false;\n\t\trulesetDef.restrictSetEx = false;\n\t}\n}\nstatic void Rulesets_Thunderdome(qbool enable)\n{\n\textern cvar_t cl_independentPhysics, cl_c2spps;\n\textern cvar_t cl_hud;\n\textern cvar_t r_shiftbeam;\n\textern cvar_t allow_scripts;\n\textern cvar_t cl_iDrive;\n\tint i;\n\n\tlocked_cvar_t disabled_cvars[] = {\n\t\t{&allow_scripts, \"0\"},  // disable movement scripting\n\t\t{&cl_iDrive, \"0\"},      // disable strafing aid\n\t\t{&cl_hud, \"0\"},         // allows you place any text on the screen & filter incoming messages (hud strings)\n\t\t{&r_shiftbeam, \"0\"}     // perphaps some people would think this allows you to aim better (maybe should be added for demo playback and spectating only)\n\t};\n\n\tif (enable) {\n\t\tfor (i = 0; i < (sizeof(disabled_cvars) / sizeof(disabled_cvars[0])); i++) {\n\t\t\tCvar_RulesetSet(disabled_cvars[i].var, disabled_cvars[i].value, 2);\n\t\t\tCvar_Set(disabled_cvars[i].var, disabled_cvars[i].value);\n\t\t\tCvar_SetFlags(disabled_cvars[i].var, Cvar_GetFlags(disabled_cvars[i].var) | CVAR_ROM);\n\t\t}\n\n\t\tif (cl_independentPhysics.value) {\n\t\t\tCvar_Set(&cl_c2spps, \"0\"); // people were complaining that player move is jerky with this. however this has not much to do with independent physics, but people are too paranoid about it\n\t\t\tCvar_SetFlags(&cl_c2spps, Cvar_GetFlags(&cl_c2spps) | CVAR_ROM);\n\t\t}\n\n\t\trulesetDef.maxfps = 77;\n\t\trulesetDef.restrictTriggers = true;\n\t\trulesetDef.restrictPacket = true; // packet command could have been exploited for external timers\n\t\trulesetDef.restrictParticles = false;\n\t\trulesetDef.restrictLogging = true;\n\t\trulesetDef.restrictRollAngle = true;\n\t\trulesetDef.ruleset = rs_thunderdome;\n\t\trulesetDef.restrictIPC = true;\n\t\trulesetDef.restrictExec = true;\n\t\trulesetDef.restrictSetCalc = true;\n\t\trulesetDef.restrictSetEval = true;\n\t\trulesetDef.restrictSetEx = true;\n\t\trulesetDef.restrictPlay = true;\n\t} else {\n\t\tfor (i = 0; i < (sizeof(disabled_cvars) / sizeof(disabled_cvars[0])); i++)\n\t\t\tCvar_SetFlags(disabled_cvars[i].var, Cvar_GetFlags(disabled_cvars[i].var) & ~CVAR_ROM);\n\n\t\tif (cl_independentPhysics.value)\n\t\t\tCvar_SetFlags(&cl_c2spps, Cvar_GetFlags(&cl_c2spps) & ~CVAR_ROM);\n\n\t\trulesetDef.maxfps = 72.0;\n\t\trulesetDef.restrictTriggers = false;\n\t\trulesetDef.restrictPacket = false;\n\t\trulesetDef.restrictParticles = false;\n\t\trulesetDef.restrictLogging = false;\n\t\trulesetDef.restrictRollAngle = false;\n\t\trulesetDef.ruleset = rs_default;\n\t\trulesetDef.restrictIPC = false;\n\t\trulesetDef.restrictExec = false;\n\t\trulesetDef.restrictSetCalc = false;\n\t\trulesetDef.restrictSetEval = false;\n\t\trulesetDef.restrictSetEx = false;\n\t\trulesetDef.restrictPlay = false;\n\t}\n}\nstatic void Rulesets_MTFL(qbool enable)\n{\n\t/* TODO:\n\t   f_flashout trigger\n\t   block all other ways to made textures flat(simple)\n\t   ?disable external textures for detpacks, grenades, sentry, disp, etc?\n\t */\n\textern cvar_t cl_c2spps, r_fullbrightSkins;\n\textern cvar_t amf_detpacklights;\n\textern cvar_t gl_picmip, gl_max_size, r_drawflat;\n\textern cvar_t vid_hwgammacontrol;\n\textern cvar_t gl_textureless;\n\n\tint i = 0;\n\n\tlocked_cvar_t disabled_cvars[] = {\n\t\t{&r_drawflat, \"0\"},\n\t\t{&amf_detpacklights, \"0\"},\n\t\t{&gl_textureless, \"0\"},\n\t\t{&r_fullbrightSkins, \"0\"},\n\t\t{&vid_hwgammacontrol, \"2\"},\n\t\t{&cl_c2spps, \"0\"},\n\t};\n\n\tlimited_cvar_max_t limited_max_cvars[] = {\n\t\t{&gl_picmip, \"3\"},\n\t};\n\n\tlimited_cvar_min_t limited_min_cvars[] = {\n\t\t{&gl_max_size, \"512\"},\n\t};\n\n\tif (enable) {\n\t\tfor (; i < (sizeof(disabled_cvars) / sizeof(disabled_cvars[0])); i++) {\n\t\t\tCvar_RulesetSet(disabled_cvars[i].var, disabled_cvars[i].value, 2);\n\t\t\tCvar_Set(disabled_cvars[i].var, disabled_cvars[i].value);\n\t\t\tCvar_SetFlags(disabled_cvars[i].var, Cvar_GetFlags(disabled_cvars[i].var) | CVAR_ROM);\n\t\t}\n\n\t\tfor (i = 0; i < (sizeof(limited_max_cvars) / sizeof(limited_max_cvars[0])); i++) {\n\t\t\tCvar_RulesetSet(limited_max_cvars[i].var, limited_max_cvars[i].maxrulesetvalue, 1);\n\t\t\tCvar_SetFlags(limited_max_cvars[i].var, Cvar_GetFlags(limited_max_cvars[i].var) | CVAR_RULESET_MAX);\n\t\t}\n\n\t\tfor (i = 0; i < (sizeof(limited_min_cvars) / sizeof(limited_min_cvars[0])); i++) {\n\t\t\tCvar_RulesetSet(limited_min_cvars[i].var, limited_min_cvars[i].minrulesetvalue, 0);\n\t\t\tCvar_SetFlags(limited_min_cvars[i].var, Cvar_GetFlags(limited_min_cvars[i].var) | CVAR_RULESET_MIN);\n\t\t}\n\n\t\trulesetDef.restrictRollAngle = false;\n\t\trulesetDef.ruleset = rs_mtfl;\n\t\tv_gamma.modified = true;\n\t} else {\n\t\tfor (i = 0; i < (sizeof(disabled_cvars) / sizeof(disabled_cvars[0])); i++)\n\t\t\tCvar_SetFlags(disabled_cvars[i].var, Cvar_GetFlags(disabled_cvars[i].var) & ~CVAR_ROM);\n\n\t\tfor (i = 0; i < (sizeof(limited_max_cvars) / sizeof(limited_max_cvars[0])); i++)\n\t\t\tCvar_SetFlags(limited_max_cvars[i].var, Cvar_GetFlags(limited_max_cvars[i].var) & ~CVAR_RULESET_MAX);\n\n\t\tfor (i = 0; i < (sizeof(limited_min_cvars) / sizeof(limited_min_cvars[0])); i++)\n\t\t\tCvar_SetFlags(limited_min_cvars[i].var, Cvar_GetFlags(limited_min_cvars[i].var) & ~CVAR_RULESET_MIN);\n\n\t\trulesetDef.ruleset = rs_default;\n\t\trulesetDef.restrictRollAngle = false;\n\t\tv_gamma.modified = true;\n\t}\n}\n\nstatic void Rulesets_Smackdrive(qbool enable)\n{\n\textern cvar_t cl_independentPhysics, cl_c2spps;\n\textern cvar_t cl_hud;\n\textern cvar_t cl_rollalpha;\n\textern cvar_t r_shiftbeam;\n\textern cvar_t allow_scripts;\n\textern cvar_t scr_allowsnap;\n\tint i;\n\n\tlocked_cvar_t disabled_cvars[] = {\n\t\t{&allow_scripts, \"0\"},  // disable movement scripting\n\t\t{&cl_hud, \"0\"},         // allows you place any text on the screen & filter incoming messages (hud strings)\n\t\t{&cl_rollalpha, \"20\"},  // allows you to not dodge while seeing enemies dodging\n\t\t{&r_shiftbeam, \"0\"},    // perphaps some people would think this allows you to aim better (maybe should be added for demo playback and spectating only)\n\t\t{&scr_allowsnap, \"1\"}\n\t};\n\n\tif (enable) {\n\t\tfor (i = 0; i < (sizeof(disabled_cvars) / sizeof(disabled_cvars[0])); i++) {\n\t\t\tCvar_RulesetSet(disabled_cvars[i].var, disabled_cvars[i].value, 2);\n\t\t\tCvar_Set(disabled_cvars[i].var, disabled_cvars[i].value);\n\t\t\tCvar_SetFlags(disabled_cvars[i].var, Cvar_GetFlags(disabled_cvars[i].var) | CVAR_ROM);\n\t\t}\n\n\t\tif (cl_independentPhysics.value) {\n\t\t\tCvar_Set(&cl_c2spps, \"0\"); // people were complaining that player move is jerky with this. however this has not much to do with independent physics, but people are too paranoid about it\n\t\t\tCvar_SetFlags(&cl_c2spps, Cvar_GetFlags(&cl_c2spps) | CVAR_ROM);\n\t\t}\n\n\t\trulesetDef.maxfps = 77;\n\t\trulesetDef.restrictTriggers = true;\n\t\trulesetDef.restrictPacket = true; // packet command could have been exploited for external timers\n\t\trulesetDef.restrictParticles = true;\n\t\trulesetDef.restrictLogging = true;\n\t\trulesetDef.restrictRollAngle = true;\n\t\trulesetDef.ruleset = rs_smackdrive;\n\t\trulesetDef.restrictIPC = true;\n\t\trulesetDef.restrictExec = true;\n\t\trulesetDef.restrictSetCalc = true;\n\t\trulesetDef.restrictSetEval = true;\n\t\trulesetDef.restrictSetEx = true;\n\t\trulesetDef.restrictPlay = true;\n\t} else {\n\t\tfor (i = 0; i < (sizeof(disabled_cvars) / sizeof(disabled_cvars[0])); i++)\n\t\t\tCvar_SetFlags(disabled_cvars[i].var, Cvar_GetFlags(disabled_cvars[i].var) & ~CVAR_ROM);\n\n\t\tif (cl_independentPhysics.value)\n\t\t\tCvar_SetFlags(&cl_c2spps, Cvar_GetFlags(&cl_c2spps) & ~CVAR_ROM);\n\n\t\trulesetDef.maxfps = 72.0;\n\t\trulesetDef.restrictTriggers = false;\n\t\trulesetDef.restrictPacket = false;\n\t\trulesetDef.restrictParticles = false;\n\t\trulesetDef.restrictLogging = false;\n\t\trulesetDef.restrictRollAngle = false;\n\t\trulesetDef.ruleset = rs_default;\n\t\trulesetDef.restrictIPC = false;\n\t\trulesetDef.restrictExec = false;\n\t\trulesetDef.restrictSetCalc = false;\n\t\trulesetDef.restrictSetEval = false;\n\t\trulesetDef.restrictSetEx = false;\n\t\trulesetDef.restrictPlay = false;\n\t}\n}\n\nstatic void Rulesets_Default(void)\n{\n\trulesetDef.ruleset = rs_default;\n}\n\nvoid Rulesets_Init(void)\n{\n\tint temp;\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_USERINFO);\n\tCvar_Register(&ruleset);\n\n\tif ((temp = COM_CheckParm(cmdline_param_client_ruleset)) && temp + 1 < COM_Argc()) {\n\t\tif (!strcasecmp(COM_Argv(temp + 1), \"smackdown\")) {\n\t\t\tCvar_Set(&ruleset, \"smackdown\");\n\t\t\treturn;\n\t\t} else if (!strcasecmp(COM_Argv(temp + 1), \"thunderdome\")) {\n\t\t\tCvar_Set(&ruleset, \"thunderdome\");\n\t\t\treturn;\n\t\t} else if (!strcasecmp(COM_Argv(temp + 1), \"mtfl\")) {\n\t\t\tCvar_Set(&ruleset, \"mtfl\");\n\t\t\treturn;\n\t\t} else if (strcasecmp(COM_Argv(temp + 1), \"qcon\")) {\n\t\t\tCvar_Set(&ruleset, \"qcon\");\n\t\t\treturn;\n\t\t} else if (strcasecmp(COM_Argv(temp + 1), \"default\")){\n\t\t\tCvar_Set(&ruleset, \"default\");\n\t\t\treturn;\n\t\t} else {\n\t\t\tRulesets_Default();\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nvoid Rulesets_OnChange_indphys(cvar_t *var, char *value, qbool *cancel)\n{\n\tif (cls.state != ca_disconnected) {\n\t\tCom_Printf(\"%s can be changed only when disconnected\\n\", var->name);\n\t\t*cancel = true;\n\t}\n\telse *cancel = false;\n}\n\nvoid Rulesets_OnChange_r_fullbrightSkins(cvar_t *var, char *value, qbool *cancel)\n{\n\tchar *fbs;\n\tqbool fbskins_policy = (cls.demoplayback || cl.spectator) ? 1 :\n\t\t*(fbs = Info_ValueForKey(cl.serverinfo, \"fbskins\")) ? bound(0, Q_atof(fbs), 1) :\n\t\tcl.teamfortress ? 0 : 1;\n\tfloat fbskins = bound(0.0, Q_atof (value), fbskins_policy);\n\n\tif (!cl.spectator && cls.state != ca_disconnected) {\n\t\tif (fbskins > 0.0) {\n\t\t\tCbuf_AddText(va(\"say all skins %d%% fullbright\\n\", (int) (fbskins * 100.0)));\n\t\t} else {\n\t\t\tCbuf_AddText(va(\"say not using fullbright skins\\n\"));\n\t\t}\n\t}\n}\n\nvoid Rulesets_OnChange_allow_scripts (cvar_t *var, char *value, qbool *cancel)\n{\n\tchar *p;\n\tqbool progress;\n\tint val;\n\n\tp = Info_ValueForKey(cl.serverinfo, \"status\");\n\tprogress = (strstr (p, \"left\")) ? true : false;\n\tval = Q_atoi(value);\n\n\tif (cls.state >= ca_connected && progress && !cl.spectator) {\n\t\tCom_Printf (\"%s changes are not allowed during the match.\\n\", var->name);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tIN_ClearProtectedKeys ();\n\n\tif (!cl.spectator && cls.state != ca_disconnected) {\n\t\tif (val < 1) {\n\t\t\tCbuf_AddText(\"say not using scripts\\n\");\n\t\t} else if (val < 2) {\n\t\t\tCbuf_AddText(\"say using simple scripts\\n\");\n\t\t} else {\n\t\t\tCbuf_AddText(\"say using advanced scripts\\n\");\n\t\t}\n\t}\n}\n\nvoid Rulesets_OnChange_cl_delay_packet(cvar_t *var, char *value, qbool *cancel)\n{\n\tint ival = Q_atoi(value);\n\n\tif (ival == var->integer) {\n\t\t// no change\n\t\treturn;\n\t}\n\n\tif (var == &cl_delay_packet && (ival < 0 || ival > CL_MAX_PACKET_DELAY * 2)) {\n\t\tCom_Printf(\"%s must be between 0 and %d\\n\", var->name, CL_MAX_PACKET_DELAY * 2);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tif (var == &cl_delay_packet_dev && (ival < 0 || ival > CL_MAX_PACKET_DELAY_DEVIATION)) {\n\t\tCom_Printf(\"%s must be between 0 and %d\\n\", var->name, CL_MAX_PACKET_DELAY_DEVIATION);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tif (var == &cl_delay_packet_target && (ival < 0 || ival > CL_MAX_PACKET_DELAY_TARGET)) {\n\t\tCom_Printf(\"%s must be between 0 and %d\\n\", var->name, CL_MAX_PACKET_DELAY_TARGET);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tif (cls.state == ca_active) {\n\t\tif ((cl.standby) || (cl.teamfortress)) {\n\t\t\tchar announce[128];\n\t\t\tint delay_target_ms = (var == &cl_delay_packet_target ? ival : cl_delay_packet_target.integer);\n\t\t\tint delay_deviation = (var == &cl_delay_packet_dev ? ival : cl_delay_packet_dev.integer);\n\t\t\tint delay_constant = (var == &cl_delay_packet ? ival : cl_delay_packet.integer);\n\n\t\t\tif (delay_target_ms) {\n\t\t\t\tsnprintf(announce, sizeof(announce), \"say delay packet: target ping %d ms (%dms dev)\\n\", delay_target_ms, delay_deviation);\n\t\t\t}\n\t\t\telse if (delay_constant) {\n\t\t\t\tsnprintf(announce, sizeof(announce), \"say delay packet: adding %d ms (%dms dev)\\n\", delay_constant, delay_deviation);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tsnprintf(announce, sizeof(announce), \"say delay packet: off\\n\");\n\t\t\t}\n\n\t\t\t// allow in standby or teamfortress. For teamfortress, more often than not\n\t\t\t// People 1on1 without \"match mode\" and they may want to sync pings.\n\t\t\tCbuf_AddText(announce);\n\t\t}\n\t\telse {\n\t\t\t// disallow during the match\n\t\t\tCom_Printf(\"%s changes are not allowed during the match\\n\", var->name);\n\t\t\t*cancel = true;\n\t\t}\n\t}\n\telse {\n\t\t// allow in not fully connected state\n\t}\n}\n\nvoid Rulesets_OnChange_cl_iDrive(cvar_t *var, char *value, qbool *cancel)\n{\n\tint ival = Q_atoi(value);\t// this is used in the code\n\tfloat fval = Q_atof(value); // this is used to check value validity\n\n\tif (ival == var->integer && fval == var->value) {\n\t\t// no change\n\t\treturn;\n\t}\n\n\tif (fval != 0 && fval != 1) {\n\t\tCom_Printf(\"Invalid value for %s, use 0 or 1.\\n\", var->name);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tif (cls.state == ca_active) {\n\t\tif (cl.standby) {\n\t\t\t// allow in standby\n\t\t\tCbuf_AddText(va(\"say side step aid (strafescript): %s\\n\", ival ? \"on\" : \"off\"));\n\t\t}\n\t\telse {\n\t\t\t// disallow during the match\n\t\t\tCom_Printf(\"%s changes are not allowed during the match\\n\", var->name);\n\t\t\t*cancel = true;\n\t\t}\n\t} else {\n\t\t// allow in not fully connected state\n\t}\n}\n\nvoid Rulesets_OnChange_cl_fakeshaft(cvar_t *var, char *value, qbool *cancel)\n{\n\tfloat fakeshaft = Q_atof(value);\n\n\n\tif (!cl.spectator && cls.state != ca_disconnected) {\n\t\tif (fakeshaft == 2)\n\t\t\tCbuf_AddText(\"say fakeshaft 2 (emulation of fakeshaft 0 for servers with antilag feature)\\n\");\n\t\telse if (fakeshaft > 0.999)\n\t\t\tCbuf_AddText(\"say fakeshaft on\\n\");\n\t\telse if (fakeshaft < 0.001)\n\t\t\tCbuf_AddText(\"say fakeshaft off\\n\");\n\t\telse\n\t\t\tCbuf_AddText(va(\"say fakeshaft %.1f%%\\n\", fakeshaft * 100.0));\n\t}\n}\n\nstatic void Rulesets_OnChange_ruleset(cvar_t *var, char *value, qbool *cancel)\n{\n\textern void Cmd_ReInitAllMacro(void);\n\n\tif (cls.state != ca_disconnected) {\n\t\tCom_Printf(\"%s can be changed only when disconnected\\n\", var->name);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tif (strncasecmp(value, \"smackdown\", sizeof(\"smackdown\")) &&\n\t\t\tstrncasecmp(value, \"thunderdome\", sizeof(\"thunderdome\")) &&\n\t\t\tstrncasecmp(value, \"mtfl\", sizeof(\"mtfl\")) &&\n\t\t\tstrncasecmp(value, \"qcon\", sizeof(\"qcon\")) &&\n\t\t\tstrncasecmp(value, \"smackdrive\", sizeof(\"smackdrive\")) &&\n\t\t\tstrncasecmp(value, \"default\", sizeof(\"default\"))) {\n\t\tCom_Printf_State(PRINT_INFO, \"Unknown ruleset \\\"%s\\\"\\n\", value);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\t// All checks passed  so we can remove old ruleset and set a new one\n\tswitch (rulesetDef.ruleset) {\n\t\tcase rs_mtfl:\n\t\t\tRulesets_MTFL(false);\n\t\t\tbreak;\n\t\tcase rs_smackdown:\n\t\t\tRulesets_Smackdown(false);\n\t\t\tbreak;\n\t\tcase rs_qcon:\n\t\t\tRulesets_Qcon(false);\n\t\t\tbreak;\n\t\tcase rs_thunderdome:\n\t\t\tRulesets_Thunderdome(false);\n\t\t\tbreak;\n\t\tcase rs_smackdrive:\n\t\t\tRulesets_Smackdrive(false);\n\t\tcase rs_default:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\t// we need to mark custom textures in the memory (like for backpack and eyes) to be reloaded again\n\tCache_Flush();\n\n\tif (!strncasecmp(value, \"smackdown\", sizeof(\"smackdown\"))) {\n\t\tRulesets_Smackdown(true);\n\t\tCom_Printf_State(PRINT_OK, \"Ruleset Smackdown initialized\\n\");\n\t} else if (!strncasecmp(value, \"mtfl\", sizeof(\"mtfl\"))) {\n\t\tRulesets_MTFL(true);\n\t\tCom_Printf_State(PRINT_OK, \"Ruleset MTFL initialized\\n\");\n\t} else if (!strncasecmp(value, \"thunderdome\", sizeof(\"thunderdome\"))) {\n\t\tRulesets_Thunderdome(true);\n\t\tCom_Printf_State(PRINT_OK, \"Ruleset Thunderdome initialized\\n\");\n\t} else if (!strncasecmp(value, \"qcon\", sizeof(\"qcon\"))) {\n\t\tRulesets_Qcon(true);\n\t\tCom_Printf_State(PRINT_OK, \"Ruleset Qcon initialized\\n\");\n\t} else if (!strncasecmp(value, \"smackdrive\", sizeof(\"smackdrive\"))) {\n\t\tRulesets_Smackdrive(true);\n\t\tCom_Printf_State(PRINT_OK, \"Ruleset Smackdrive initialized\\n\");\n\t} else if (!strncasecmp(value, \"default\", sizeof(\"default\"))) {\n\t\tRulesets_Default();\n\t\tCom_Printf_State(PRINT_OK, \"Ruleset default initialized\\n\");\n\t} else {\n\t\tSys_Error(\"OnChange_ruleset: WTF?\\n\");\n\t\t// this will never happen\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tCmd_ReInitAllMacro();\n\tIN_ClearProtectedKeys();\n\tInfo_SetValueForStarKey(cls.userinfo, \"*rsn\", value, MAX_INFO_STRING);\n}\n\nint Rulesets_MaxSequentialWaitCommands(void)\n{\n\tswitch (rulesetDef.ruleset) {\n\tcase rs_qcon:\n\t\treturn 10;\n\tdefault:\n\t\treturn 32768;\n\t}\n}\n\nqbool Ruleset_BlockHudPicChange(void)\n{\n\tswitch (rulesetDef.ruleset) {\n\tcase rs_qcon:\n\tcase rs_smackdown:\n\tcase rs_smackdrive:\n\tcase rs_thunderdome:\n\t\treturn cls.state != ca_disconnected && !(cl.standby || cl.spectator || cls.demoplayback);\n\tdefault:\n\t\treturn false;\n\t}\n}\n\nqbool Ruleset_AllowPolygonOffset(entity_t* ent)\n{\n\tswitch (rulesetDef.ruleset) {\n\tcase rs_qcon:\n\t\treturn false;\n\tcase rs_default:\n\t\treturn true;\n\tdefault:\n\t\treturn ent->model && ent->model->isworldmodel;\n\t}\n}\n\n// Not technically ruleset-based but limits functionaly for similar reasons...\nqbool Ruleset_IsLumaAllowed(struct model_s *mod)\n{\n\tswitch (mod->modhint)\n\t{\n\tcase MOD_EYES:\n\tcase MOD_BACKPACK:\n\tcase MOD_PLAYER:\n\n\tcase MOD_SENTRYGUN: // tf\n\tcase MOD_DETPACK:   // tf\n\n\t\treturn false; // no luma for such models\n\n\tdefault:\n\n\t\treturn true; // luma allowed\n\t}\n}\n\nqbool Rulesets_ToggleWhenFlashed(void)\n{\n\treturn rulesetDef.ruleset == rs_mtfl;\n}\n\nqbool Rulesets_FullbrightModel(struct model_s* model)\n{\n\textern cvar_t gl_fb_models;\n\tqbool protected_model = (model->modhint == MOD_EYES || model->modhint == MOD_BACKPACK) && rulesetDef.ruleset != rs_default;\n\tqbool fb_requested = gl_fb_models.integer == 1 && model->modhint != MOD_GIB && model->modhint != MOD_VMODEL && !Ruleset_IsLocalSinglePlayerGame();\n\n\treturn !protected_model && fb_requested;\n}\n\nconst char* Ruleset_BlockPlayerCountMacros(void)\n{\n\tif (rulesetDef.ruleset == rs_mtfl) {\n\t\treturn BANNED_BY_MTFL;\n\t}\n\treturn NULL;\n}\n\nqbool Ruleset_AllowPowerupShell(model_t* model)\n{\n\t// always allow powerupshells for specs or demos.\n\t// do not allow powerupshells for eyes in other cases\n\textern cvar_t gl_powerupshells;\n\n\treturn (bound(0, gl_powerupshells.value, 1) && ((cls.demoplayback || cl.spectator) || model->modhint != MOD_EYES));\n}\n\nqbool Ruleset_CanLogConsole(void)\n{\n\treturn cls.demoplayback || cls.state != ca_active || cl.standby || cl.countdown || !rulesetDef.restrictLogging;\n}\n\nqbool Ruleset_AllowNoHardwareGamma(void)\n{\n\treturn rulesetDef.ruleset != rs_mtfl;\n}\n\nfloat Ruleset_RollAngle(void)\n{\n\textern cvar_t cl_rollangle;\n\n\tif (cls.demoplayback || cl.spectator || !rulesetDef.restrictRollAngle) {\n\t\treturn fabs(cl_rollangle.value);\n\t}\n\n\treturn bound(0.0f, cl_rollangle.value, 5.0f);\n}\n\n#ifndef CLIENTONLY\nextern cvar_t     maxclients;\nqbool Ruleset_IsLocalSinglePlayerGame(void)\n{\n\treturn com_serveractive && cls.state == ca_active && !cl.deathmatch && maxclients.integer == 1;\n}\n#endif\n"
  },
  {
    "path": "src/rulesets.h",
    "content": "/*\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n\n#define BANNED_BY_MTFL \"banned by MTFL ruleset\"\n#define MTFL_FLASH_GAMMA (0.55)\n#define MTFL_FLASH_CONTRAST (1.0)\n\ntypedef struct locked_cvar_s {\n\tcvar_t *var;\n\tchar *value;\n} locked_cvar_t;\n\ntypedef struct limited_cvar_max_s {\n\tcvar_t *var;\n\tchar *maxrulesetvalue;\n} limited_cvar_max_t;\n\ntypedef struct limited_cvar_min_s {\n\tcvar_t *var;\n\tchar *minrulesetvalue;\n} limited_cvar_min_t;\n\ntypedef enum {\n\trs_default,\n\trs_smackdown,\n\trs_thunderdome,\n\trs_qcon,\n\trs_mtfl,\n\trs_smackdrive\n} ruleset_t;\n\nvoid  Rulesets_Init(void);\nconst char* Rulesets_Ruleset(void);\nqbool Rulesets_AllowTimerefresh(void);\nfloat Rulesets_MaxFPS(void);\nqbool Rulesets_RestrictTriggers(void);\nqbool Rulesets_RestrictPacket(void);\nqbool Rulesets_RestrictParticles(void);\nqbool Rulesets_RestrictExec(void);\nqbool Rulesets_RestrictIPC(void);\nqbool Rulesets_RestrictSetCalc(void);\nqbool Rulesets_RestrictSetEval(void);\nqbool Rulesets_RestrictSetEx(void);\nqbool Rulesets_AllowNoShadows(void);\nqbool Rulesets_RestrictTCL(void);\nqbool Rulesets_RestrictPlay(const char* name);\nint Rulesets_MaxSequentialWaitCommands(void);\nqbool Ruleset_BlockHudPicChange(void);\nqbool Ruleset_AllowPolygonOffset(entity_t* ent);\nqbool Rulesets_AllowAlternateModel(const char* modelName);\nqbool RuleSets_DisallowModelOutline(struct model_s *mod);\nfloat RuleSets_ModelOutlineScale(void);\nqbool RuleSets_AllowEdgeOutline(void);\nqbool RuleSets_DisallowExternalTexture(struct model_s *mod);\nqbool Ruleset_IsLumaAllowed(struct model_s *mod);\nqbool Ruleset_AllowPowerupShell(struct model_s* mod);\nqbool RuleSets_DisallowSimpleTexture(struct model_s *mod);\nqbool Ruleset_AllowNoHardwareGamma(void);\n\n// OnChange functions controling when a variable value changes\nvoid Rulesets_OnChange_indphys (cvar_t *var, char *value, qbool *cancel);\nvoid Rulesets_OnChange_r_fullbrightSkins (cvar_t *var, char *value, qbool *cancel);\nvoid Rulesets_OnChange_allow_scripts (cvar_t *var, char *value, qbool *cancel);\nvoid Rulesets_OnChange_cl_fakeshaft (cvar_t *var, char *value, qbool *cancel);\nvoid Rulesets_OnChange_cl_delay_packet(cvar_t *var, char *value, qbool *cancel);\nvoid Rulesets_OnChange_cl_iDrive(cvar_t *var, char *value, qbool *cancel);\n\nqbool Rulesets_ToggleWhenFlashed(void);\nqbool Rulesets_FullbrightModel(struct model_s* model);\nconst char* Ruleset_BlockPlayerCountMacros(void);\n\nqbool Ruleset_CanLogConsole(void);\nfloat Ruleset_RollAngle(void);\n\n#ifndef CLIENTONLY\nextern cvar_t     maxclients;\nqbool Ruleset_IsLocalSinglePlayerGame(void);\n#else\n#define Ruleset_IsLocalSinglePlayerGame() (0)\n#endif\n"
  },
  {
    "path": "src/sbar.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n \nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: sbar.c,v 1.42 2007-09-14 13:29:29 disconn3ct Exp $\n*/\n// sbar.c -- status bar code\n\n#include \"quakedef.h\"\n#include <jansson.h>\n#ifndef CLIENTONLY\n#include \"server.h\"\n#endif\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"vx_stuff.h\"\n#include \"gl_model.h\"\n#include \"r_texture.h\"\n#include \"teamplay.h\"\n#include \"utils.h\"\n#include \"sbar.h\"\n#include \"keys.h\"\n#include \"common_draw.h\"\n\n#include \"qsound.h\"\n\nint CL_LoginImageId(const char* name);\nstatic int JSON_readint(json_t* json);\nstatic const char* JSON_readstring(json_t* json);\nstatic mpic_t* CL_LoginFlag(int id);\nqbool CL_LoginImageLoad(const char* path);\nstatic void OnChange_scr_scoreboard_login_flagfile(cvar_t*, char*, qbool*);\n\ntypedef struct loginimage_s {\n\tchar name[16];\n\tmpic_t pic;\n} loginimage_t;\n\nstatic struct {\n\tloginimage_t* images;\n\tsize_t image_count;\n\tint bot_image_index;\n\tint max_width;\n\tint max_height;\n} login_image_data;\n\n#define FONT_WIDTH                 8 // Used for allocating space for scoreboard columns\n#define SHORT_SPECTATOR_NAME_LEN   5 // if it's not teamplay, there is only room for 4 characters here\n\nint sb_updates;\t\t// if >= vid.numpages, no update needed\n\n// --> mqwcl 0.96 oldhud customisation\n//cvar_t  sbar_teamfrags = {\"scr_sbar_teamfrags\", \"1\"};\n//cvar_t  sbar_fraglimit = {\"scr_sbar_fraglimit\", \"1\"};\n\ncvar_t  sbar_drawfaceicon   = {\"scr_sbar_drawfaceicon\",     \"1\"};\ncvar_t  sbar_drawammoicon   = {\"scr_sbar_drawammoicon\",     \"1\"};\ncvar_t  sbar_drawarmoricon  = {\"scr_sbar_drawarmoricon\",    \"1\"};\ncvar_t  sbar_drawguns       = {\"scr_sbar_drawguns\",         \"1\"};\ncvar_t  sbar_drawammocounts = {\"scr_sbar_drawammocounts\",   \"1\"};\ncvar_t  sbar_drawitems      = {\"scr_sbar_drawitems\",        \"1\"};\ncvar_t  sbar_drawsigils     = {\"scr_sbar_drawsigils\",       \"1\"};\ncvar_t  sbar_drawhealth     = {\"scr_sbar_drawhealth\",       \"1\"};\ncvar_t  sbar_drawarmor      = {\"scr_sbar_drawarmor\",        \"1\"};\ncvar_t  sbar_drawarmor666   = {\"scr_sbar_drawarmor666\",     \"1\"};\ncvar_t  sbar_drawammo       = {\"scr_sbar_drawammo\",         \"1\"};\ncvar_t  sbar_lowammo        = {\"scr_sbar_lowammo\",          \"5\"};\n\ncvar_t  hud_centerranking              = { \"scr_scoreboard_centered\",   \"1\" };\ncvar_t  hud_rankingpos_y               = { \"scr_scoreboard_posy\",       \"0\" };\ncvar_t  hud_rankingpos_x               = { \"scr_scoreboard_posx\",       \"0\" };\ncvar_t  hud_faderankings               = { \"scr_scoreboard_fadescreen\", \"0\" };\ncvar_t  scr_scoreboard_login_names     = { \"scr_scoreboard_login_names\", \"1\" };\ncvar_t  scr_scoreboard_login_indicator = { \"scr_scoreboard_login_indicator\", \"&cffc*&r\" };\ncvar_t  scr_scoreboard_login_color     = { \"scr_scoreboard_login_color\", \"255 255 192\" };\ncvar_t  scr_scoreboard_login_flagfile  = { \"scr_scoreboard_login_flagfile\", \"flags\", 0, OnChange_scr_scoreboard_login_flagfile };\n\n//cvar_t  hud_ranks_separate  = {\"scr_ranks_separate\",   \"1\"};\n// <-- mqwcl 0.96 oldhud customisation\n\nmpic_t\t\t*sb_nums[2][11];\nmpic_t\t\t*sb_colon, *sb_slash;\nmpic_t\t\t*sb_ibar;\nmpic_t\t\t*sb_sbar;\nmpic_t\t\t*sb_scorebar;\nmpic_t      sb_ib_ammo[4];\n\nmpic_t\t\t*sb_weapons[7][8];\t// 0 is active, 1 is owned, 2-5 are flashes\nmpic_t\t\t*sb_ammo[4];\nmpic_t\t\t*sb_sigil[4];\nmpic_t\t\t*sb_armor[3];\nmpic_t\t\t*sb_items[32];\n\nmpic_t\t*sb_faces[5][2];\t\t// 0 is dead, 1-4 are alive\n// 0 is static, 1 is temporary animation\nmpic_t\t*sb_face_invis;\nmpic_t\t*sb_face_quad;\nmpic_t\t*sb_face_invuln;\nmpic_t\t*sb_face_invis_invuln;\n\nqbool\tsb_showscores;\nqbool\tsb_showteamscores;\n\nint\t\t\tsb_lines;\t\t\t// scan lines to draw\n\nstatic int\tsbar_xofs;\n\ncvar_t\tscr_centerSbar                = {\"scr_centerSbar\",                \"1\"};\n\ncvar_t\tscr_compactHud                = {\"scr_compactHud\",                \"0\"};\ncvar_t\tscr_compactHudAlign           = {\"scr_compactHudAlign\",           \"0\"};\n\ncvar_t\tscr_drawHFrags                = {\"scr_drawHFrags\",                \"1\"};\ncvar_t\tscr_drawVFrags                = {\"scr_drawVFrags\",                \"1\"};\n\ncvar_t scr_scoreboard_afk             = {\"scr_scoreboard_afk\",            \"1\"};\ncvar_t scr_scoreboard_afk_style       = {\"scr_scoreboard_afk_style\",      \"1\"};\n\ncvar_t\tscr_scoreboard_teamsort       = {\"scr_scoreboard_teamsort\",       \"1\"};\ncvar_t\tscr_scoreboard_forcecolors    = {\"scr_scoreboard_forcecolors\",    \"1\"};\ncvar_t\tscr_scoreboard_showfrags      = {\"scr_scoreboard_showfrags\",      \"1\"};\ncvar_t\tscr_scoreboard_showflagstats  = {\"scr_scoreboard_showflagstats\",  \"0\"};\ncvar_t\tscr_scoreboard_drawtitle      = {\"scr_scoreboard_drawtitle\",      \"1\"};\ncvar_t\tscr_scoreboard_borderless     = {\"scr_scoreboard_borderless\",     \"1\"};\ncvar_t\tscr_scoreboard_spectator_name = {\"scr_scoreboard_spectator_name\", \"\\xF3\\xF0\\xE5\\xE3\\xF4\\xE1\\xF4\\xEF\\xF2\"}; // brown \"spectator\". old: &cF20s&cF50p&cF80e&c883c&cA85t&c668a&c55At&c33Bo&c22Dr\ncvar_t\tscr_scoreboard_fillalpha      = {\"scr_scoreboard_fillalpha\",      \"0.7\"};\ncvar_t\tscr_scoreboard_fillcolored    = {\"scr_scoreboard_fillcolored\",    \"2\"};\ncvar_t  scr_scoreboard_proportional   = {\"scr_scoreboard_proportional\",   \"0\"};\ncvar_t  scr_scoreboard_wipeout\t \t  = {\"scr_scoreboard_wipeout\",   \t  \"1\"};\ncvar_t\tscr_scoreboard_classic        = {\"scr_scoreboard_classic\", \"0\"};\ncvar_t\tscr_scoreboard_highlightself  = {\"scr_scoreboard_highlightself\", \"1\"};\ncvar_t\tscr_scoreboard_showclock      = {\"scr_scoreboard_showclock\", \"0\"};\ncvar_t\tscr_scoreboard_showmapname    = {\"scr_scoreboard_showmapname\", \"0\"};\n\nstatic void OnChange_scr_scoreboard_showqtvusers(cvar_t *old_value, char *new_value, qbool*);\ncvar_t scr_scoreboard_showqtvusers = {\"scr_scoreboard_showqtvusers\", \"1\", 0, OnChange_scr_scoreboard_showqtvusers};\ncvar_t scr_scoreboard_qtv_name = {\"scr_scoreboard_qtv_name\", \"\\xF1\\xF4\\xF6\"};\n\n// VFrags: only draw the frags for the first player when using mvinset\n#define MULTIVIEWTHISPOV() ((!cl_multiview.value) || (cl_mvinset.value && CL_MultiviewCurrentView() == 1))\n\nqbool Sbar_IsStandardBar(void)\n{\n\t// Old status bar is turned on, or the screen size is less than full width\n\treturn cl_sbar.value || scr_viewsize.value < 100;\n}\n\nstatic qbool Sbar_ShowScoreboardIndicator (void)\n{\n\tif (!cls.mvdplayback || !cl_multiview.value)\n\t\treturn true;\n\n\tif (cl_multiview.integer == 1 || (cl_multiview.integer == 2 && cl_mvinset.integer))\n\t\treturn true;\n\n\treturn false;\n}\n\n/********************************** CONTROL **********************************/\n\n//Tab key down\nvoid Sbar_ShowTeamScores (void) {\n\tif (sb_showteamscores)\n\t\treturn;\n\n\tsb_showteamscores = true;\n\tsb_updates = 0;\n}\n\n//Tab key up\nvoid Sbar_DontShowTeamScores (void) {\n\tsb_showteamscores = false;\n\tsb_updates = 0;\n}\n\n//Tab key down\nvoid Sbar_ShowScores (void) {\n\tif (sb_showscores)\n\t\treturn;\n\n\tsb_showscores = true;\n\tsb_updates = 0;\n}\n\n//Tab key up\nvoid Sbar_DontShowScores (void) {\n\tsb_showscores = false;\n\tsb_updates = 0;\n}\n\nvoid Sbar_Changed (void) {\n\tsb_updates = 0;\t// update next frame\n}\n\n/************************************ INIT ************************************/\n\nvoid Sbar_Init(void)\n{\n\tint i;\n\n\tfor (i = 0; i < 10; i++) {\n\t\tsb_nums[0][i] = Draw_CacheWadPic(va(\"num_%i\", i), WADPIC_NUM_0 + i);\n\t\tsb_nums[1][i] = Draw_CacheWadPic(va(\"anum_%i\", i), WADPIC_ANUM_0 + i);\n\t}\n\n\tsb_nums[0][10] = Draw_CacheWadPic(\"num_minus\", WADPIC_NUM_MINUS);\n\tsb_nums[1][10] = Draw_CacheWadPic(\"anum_minus\", WADPIC_ANUM_MINUS);\n\n\tsb_colon = Draw_CacheWadPic(\"num_colon\", WADPIC_NUM_COLON);\n\tsb_slash = Draw_CacheWadPic(\"num_slash\", WADPIC_NUM_SLASH);\n\n\tsb_weapons[0][0] = Draw_CacheWadPic(\"inv_shotgun\", WADPIC_INV_SHOTGUN);\n\tsb_weapons[0][1] = Draw_CacheWadPic(\"inv_sshotgun\", WADPIC_INV_SSHOTGUN);\n\tsb_weapons[0][2] = Draw_CacheWadPic(\"inv_nailgun\", WADPIC_INV_NAILGUN);\n\tsb_weapons[0][3] = Draw_CacheWadPic(\"inv_snailgun\", WADPIC_INV_SNAILGUN);\n\tsb_weapons[0][4] = Draw_CacheWadPic(\"inv_rlaunch\", WADPIC_INV_RLAUNCH);\n\tsb_weapons[0][5] = Draw_CacheWadPic(\"inv_srlaunch\", WADPIC_INV_SRLAUNCH);\n\tsb_weapons[0][6] = Draw_CacheWadPic(\"inv_lightng\", WADPIC_INV_LIGHTNG);\n\n\tsb_weapons[1][0] = Draw_CacheWadPic(\"inv2_shotgun\", WADPIC_INV2_SHOTGUN);\n\tsb_weapons[1][1] = Draw_CacheWadPic(\"inv2_sshotgun\", WADPIC_INV2_SSHOTGUN);\n\tsb_weapons[1][2] = Draw_CacheWadPic(\"inv2_nailgun\", WADPIC_INV2_NAILGUN);\n\tsb_weapons[1][3] = Draw_CacheWadPic(\"inv2_snailgun\", WADPIC_INV2_SNAILGUN);\n\tsb_weapons[1][4] = Draw_CacheWadPic(\"inv2_rlaunch\", WADPIC_INV2_RLAUNCH);\n\tsb_weapons[1][5] = Draw_CacheWadPic(\"inv2_srlaunch\", WADPIC_INV2_SRLAUNCH);\n\tsb_weapons[1][6] = Draw_CacheWadPic(\"inv2_lightng\", WADPIC_INV2_LIGHTNG);\n\n\tfor (i = 0; i < 5; i++) {\n\t\tsb_weapons[2 + i][0] = Draw_CacheWadPic(va(\"inva%i_shotgun\", i + 1), WADPIC_INVA1_SHOTGUN + i);\n\t\tsb_weapons[2 + i][1] = Draw_CacheWadPic(va(\"inva%i_sshotgun\", i + 1), WADPIC_INVA1_SSHOTGUN + i);\n\t\tsb_weapons[2 + i][2] = Draw_CacheWadPic(va(\"inva%i_nailgun\", i + 1), WADPIC_INVA1_NAILGUN + i);\n\t\tsb_weapons[2 + i][3] = Draw_CacheWadPic(va(\"inva%i_snailgun\", i + 1), WADPIC_INVA1_SNAILGUN + i);\n\t\tsb_weapons[2 + i][4] = Draw_CacheWadPic(va(\"inva%i_rlaunch\", i + 1), WADPIC_INVA1_RLAUNCH + i);\n\t\tsb_weapons[2 + i][5] = Draw_CacheWadPic(va(\"inva%i_srlaunch\", i + 1), WADPIC_INVA1_SRLAUNCH + i);\n\t\tsb_weapons[2 + i][6] = Draw_CacheWadPic(va(\"inva%i_lightng\", i + 1), WADPIC_INVA1_LIGHTNG + i);\n\t}\n\n\tsb_ammo[0] = Draw_CacheWadPic(\"sb_shells\", WADPIC_SB_SHELLS);\n\tsb_ammo[1] = Draw_CacheWadPic(\"sb_nails\", WADPIC_SB_NAILS);\n\tsb_ammo[2] = Draw_CacheWadPic(\"sb_rocket\", WADPIC_SB_ROCKET);\n\tsb_ammo[3] = Draw_CacheWadPic(\"sb_cells\", WADPIC_SB_CELLS);\n\n\tsb_armor[0] = Draw_CacheWadPic(\"sb_armor1\", WADPIC_SB_ARMOR1);\n\tsb_armor[1] = Draw_CacheWadPic(\"sb_armor2\", WADPIC_SB_ARMOR2);\n\tsb_armor[2] = Draw_CacheWadPic(\"sb_armor3\", WADPIC_SB_ARMOR3);\n\n\tsb_items[0] = Draw_CacheWadPic(\"sb_key1\", WADPIC_SB_KEY1);\n\tsb_items[1] = Draw_CacheWadPic(\"sb_key2\", WADPIC_SB_KEY2);\n\tsb_items[2] = Draw_CacheWadPic(\"sb_invis\", WADPIC_SB_INVIS);\n\tsb_items[3] = Draw_CacheWadPic(\"sb_invuln\", WADPIC_SB_INVULN);\n\tsb_items[4] = Draw_CacheWadPic(\"sb_suit\", WADPIC_SB_SUIT);\n\tsb_items[5] = Draw_CacheWadPic(\"sb_quad\", WADPIC_SB_QUAD);\n\n\tsb_sigil[0] = Draw_CacheWadPic(\"sb_sigil1\", WADPIC_SB_SIGIL1);\n\tsb_sigil[1] = Draw_CacheWadPic(\"sb_sigil2\", WADPIC_SB_SIGIL2);\n\tsb_sigil[2] = Draw_CacheWadPic(\"sb_sigil3\", WADPIC_SB_SIGIL3);\n\tsb_sigil[3] = Draw_CacheWadPic(\"sb_sigil4\", WADPIC_SB_SIGIL4);\n\n\tsb_faces[4][0] = Draw_CacheWadPic(\"face1\", WADPIC_SB_FACE1);\n\tsb_faces[4][1] = Draw_CacheWadPic(\"face_p1\", WADPIC_SB_FACE_P1);\n\tsb_faces[3][0] = Draw_CacheWadPic(\"face2\", WADPIC_SB_FACE2);\n\tsb_faces[3][1] = Draw_CacheWadPic(\"face_p2\", WADPIC_SB_FACE_P2);\n\tsb_faces[2][0] = Draw_CacheWadPic(\"face3\", WADPIC_SB_FACE3);\n\tsb_faces[2][1] = Draw_CacheWadPic(\"face_p3\", WADPIC_SB_FACE_P3);\n\tsb_faces[1][0] = Draw_CacheWadPic(\"face4\", WADPIC_SB_FACE4);\n\tsb_faces[1][1] = Draw_CacheWadPic(\"face_p4\", WADPIC_SB_FACE_P4);\n\tsb_faces[0][0] = Draw_CacheWadPic(\"face5\", WADPIC_SB_FACE5);\n\tsb_faces[0][1] = Draw_CacheWadPic(\"face_p5\", WADPIC_SB_FACE_P5);\n\n\tsb_face_invis = Draw_CacheWadPic(\"face_invis\", WADPIC_FACE_INVIS);\n\tsb_face_invuln = Draw_CacheWadPic(\"face_invul2\", WADPIC_FACE_INVUL2);\n\tsb_face_invis_invuln = Draw_CacheWadPic(\"face_inv2\", WADPIC_FACE_INV2);\n\tsb_face_quad = Draw_CacheWadPic(\"face_quad\", WADPIC_FACE_QUAD);\n\n\tsb_sbar = Draw_CacheWadPic(\"sbar\", WADPIC_SB_SBAR);\n\tsb_ibar = Draw_CacheWadPic(\"ibar\", WADPIC_SB_IBAR);\n\tsb_scorebar = Draw_CacheWadPic(\"scorebar\", WADPIC_SB_SCOREBAR);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SBAR);\n\tCvar_Register(&scr_centerSbar);\n\n\tCvar_Register(&scr_compactHud);\n\tCvar_Register(&scr_compactHudAlign);\n\n\t// --> mqwcl 0.96 oldhud customisation\n\t//Cvar_Register (&sbar_teamfrags);\n\t//Cvar_Register (&sbar_fraglimit);\n\tCvar_Register(&sbar_drawfaceicon);\n\tCvar_Register(&sbar_drawammoicon);\n\tCvar_Register(&sbar_drawarmoricon);\n\tCvar_Register(&sbar_drawguns);\n\tCvar_Register(&sbar_drawammocounts);\n\tCvar_Register(&sbar_drawitems);\n\tCvar_Register(&sbar_drawsigils);\n\tCvar_Register(&sbar_drawhealth);\n\tCvar_Register(&sbar_drawarmor);\n\tCvar_Register(&sbar_drawarmor666);\n\tCvar_Register(&sbar_drawammo);\n\tCvar_Register(&sbar_lowammo);\n\tCvar_Register(&hud_centerranking);\n\tCvar_Register(&hud_rankingpos_y);\n\tCvar_Register(&hud_rankingpos_x);\n\tCvar_Register(&hud_faderankings);\n\tCvar_Register(&scr_scoreboard_login_names);\n\tCvar_Register(&scr_scoreboard_login_indicator);\n\tCvar_Register(&scr_scoreboard_login_color);\n\tCvar_Register(&scr_scoreboard_login_flagfile);\n\t//Cvar_Register (&hud_ranks_separate);\n\t// <-- mqwcl 0.96 oldhud customisation\n\n\tCvar_Register(&scr_scoreboard_afk);\n\tCvar_Register(&scr_scoreboard_afk_style);\n\n\tCvar_Register(&scr_drawHFrags);\n\tCvar_Register(&scr_drawVFrags);\n\tCvar_Register(&scr_scoreboard_teamsort);\n\tCvar_Register(&scr_scoreboard_forcecolors);\n\tCvar_Register(&scr_scoreboard_showfrags);\n\tCvar_Register(&scr_scoreboard_showflagstats);\n\tCvar_Register(&scr_scoreboard_drawtitle);\n\tCvar_Register(&scr_scoreboard_borderless);\n\tCvar_Register(&scr_scoreboard_spectator_name);\n\tCvar_Register(&scr_scoreboard_fillalpha);\n\tCvar_Register(&scr_scoreboard_fillcolored);\n\tCvar_Register(&scr_scoreboard_proportional);\n\tCvar_Register(&scr_scoreboard_wipeout);\n\tCvar_Register(&scr_scoreboard_classic);\n\tCvar_Register(&scr_scoreboard_highlightself);\n\tCvar_Register(&scr_scoreboard_showclock);\n\tCvar_Register(&scr_scoreboard_showmapname);\n\tCvar_Register(&scr_scoreboard_showqtvusers);\n\tCvar_Register(&scr_scoreboard_qtv_name);\n\n\tCvar_ResetCurrentGroup();\n\n\tCmd_AddCommand(\"+showscores\", Sbar_ShowScores);\n\tCmd_AddCommand(\"-showscores\", Sbar_DontShowScores);\n\n\tCmd_AddCommand(\"+showteamscores\", Sbar_ShowTeamScores);\n\tCmd_AddCommand(\"-showteamscores\", Sbar_DontShowTeamScores);\n\n\tCL_LoginImageLoad(scr_scoreboard_login_flagfile.string);\n}\n\nvoid Request_Pings (void)\n{\n\tif (cls.state == ca_active && cls.realtime - cl.last_ping_request > 2)\n\t{\n\t\tcl.last_ping_request = cls.realtime;\n\t\tMSG_WriteByte (&cls.netchan.message, clc_stringcmd);\n\t\tSZ_Print (&cls.netchan.message, \"pings\");\n\t}\n}\n\n/****************************** SUPPORT ROUTINES ******************************/\n\n// drawing routines are relative to the status bar location\n\nvoid Sbar_DrawPic (int x, int y, mpic_t *pic)\n{\n\tDraw_Pic (x + sbar_xofs, y + (vid.height - SBAR_HEIGHT), pic);\n}\n\n//JACK: Draws a portion of the picture in the status bar.\nstatic void Sbar_DrawSubPic(int x, int y, mpic_t *pic, int srcx, int srcy, int width, int height)\n{\n\tDraw_SubPic (x, y + (vid.height - SBAR_HEIGHT), pic, srcx, srcy, width, height);\n}\n\nstatic void Sbar_DrawTransPic (int x, int y, mpic_t *pic)\n{\n\tDraw_TransPic (x + sbar_xofs, y + (vid.height - SBAR_HEIGHT), pic);\n}\n\n//Draws one solid graphics character\nstatic void Sbar_DrawCharacter (int x, int y, int num)\n{\n\tDraw_Character (x + 4 + sbar_xofs, y + vid.height - SBAR_HEIGHT, num);\n}\n\nvoid Sbar_DrawString(int x, int y, char *str)\n{\n\tDraw_String(x + sbar_xofs, y + vid.height - SBAR_HEIGHT, str);\n}\n\nstatic void Sbar_DrawAltString (int x, int y, char *str) {\n\tDraw_Alt_String(x + sbar_xofs, y + vid.height - SBAR_HEIGHT, str, 1, false);\n}\n\nstatic int Sbar_itoa (int num, char *buf) {\n\tchar *str;\n\tint pow10, dig;\n\n\tstr = buf;\n\n\tif (num < 0) {\n\t\t*str++ = '-';\n\t\tnum = -num;\n\t}\n\n\tfor (pow10 = 10 ; num >= pow10 ; pow10 *= 10)\n\t\t;\n\n\tdo {\n\t\tpow10 /= 10;\n\t\tdig = num / pow10;\n\t\t*str++ = '0' + dig;\n\t\tnum -= dig * pow10;\n\t} while (pow10 != 1);\n\n\t*str = 0;\n\n\treturn str - buf;\n}\n\nvoid Sbar_DrawNum (int x, int y, int num, int digits, int color) {\n\tchar str[12], *ptr;\n\tint l, frame;\n\n\tl = Sbar_itoa (num, str);\n\tptr = str;\n\tif (l > digits)\n\t\tptr += (l - digits);\n\tif (l < digits)\n\t\tx += (digits - l) * 24;\n\n\twhile (*ptr) {\n\t\tframe = (*ptr == '-') ? STAT_MINUS : *ptr -'0';\n\n\t\tSbar_DrawTransPic (x, y, sb_nums[color][frame]);\n\t\tx += 24;\n\t\tptr++;\n\t}\n}\n\n// this used to be static function\nint\tSbar_ColorForMap (int m) {\n\treturn m == 16 ? 0 : 16 * bound(0, m, 15) + 8;\n}\n\n// HUD -> hexum\nint Sbar_TopColor(player_info_t *player)\n{\n\treturn Sbar_ColorForMap(cl.teamfortress ? player->known_team_color : player->topcolor);\n}\n\nint Sbar_TopColorScoreboard(player_info_t* player)\n{\n\treturn Sbar_ColorForMap(cl.teamfortress ? player->known_team_color : (scr_scoreboard_forcecolors.integer ? player->topcolor : player->real_topcolor));\n}\n\nint Sbar_BottomColor(player_info_t *player)\n{\n\treturn Sbar_ColorForMap(cl.teamfortress ? player->known_team_color : player->bottomcolor);\n}\n\nint Sbar_BottomColorScoreboard(player_info_t* player)\n{\n\treturn Sbar_ColorForMap(cl.teamfortress ? player->known_team_color : (scr_scoreboard_forcecolors.integer ? player->bottomcolor : player->real_bottomcolor));\n}\n\n// ** HUD -> hexum\n\n/********************************* FRAG SORT *********************************/\n\nstatic int fragsort[MAX_CLIENTS];\nstatic int scoreboardlines;\n\n#define SCR_TEAM_T_MAXTEAMSIZE\t(16 + 1)\n\ntypedef struct {\n\tchar team[SCR_TEAM_T_MAXTEAMSIZE];\n\tint frags;\n\tint caps;\n\tint players;\n\tint plow, phigh, ptotal;\n\tint topcolor, bottomcolor;\n\tint known_team_number;\n\tqbool myteam;\n} team_t;\nstatic team_t teams[MAX_CLIENTS];\n\nstatic int teamsort[MAX_CLIENTS];\nstatic int scoreboardteams;\n\nstatic __inline int Sbar_PlayerNum(void) {\n\tint mynum = cl.playernum;\n\n\tif (cl.spectator) {\n\t\tmynum = Cam_TrackNum ();\n\n\t\tif (mynum < 0)\n\t\t\tmynum = cl.playernum;\n\t}\n\n\treturn mynum;\n}\n\n\nstatic __inline qbool Sbar_IsSpectator(int mynum) {\n\n\treturn (mynum == cl.playernum) ? cl.spectator : cl.players[mynum].spectator;\n}\n\nstatic qbool Sbar_SortFrags(qbool spec) {\n\tint i, j, k;\n\tstatic int lastframecount = 0;\n\tstatic qbool any_flags = false;\n\n\tif (!spec && lastframecount && lastframecount == cls.framecount) {\n\t\treturn any_flags;\n\t}\n\n\tany_flags = false;\n\tlastframecount = spec ? 0 : cls.framecount;\n\n\t// sort by frags\n\tscoreboardlines = 0;\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (cl.players[i].name[0]) {\n\t\t\tif (spec || !cl.players[i].spectator) {\n\t\t\t\tfragsort[scoreboardlines] = i;\n\t\t\t\tscoreboardlines++;\n\t\t\t\tif (cl.players[i].spectator) {\n\t\t\t\t\tcl.players[i].frags = -999;\n\t\t\t\t}\n\t\t\t}\n\t\t\tany_flags |= cl.players[i].loginname[0];\n\t\t}\n\t}\n\n\tfor (i = 0; i < scoreboardlines; i++) {\n\t\tfor (j = 0; j < scoreboardlines - 1 - i; j++) {\n\t\t\tif (cl.players[fragsort[j]].frags < cl.players[fragsort[j + 1]].frags) {\n\t\t\t\tk = fragsort[j];\n\t\t\t\tfragsort[j] = fragsort[j + 1];\n\t\t\t\tfragsort[j + 1] = k;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn any_flags;\n}\n\nstatic void Sbar_SortTeams (void) {\n\tint i, j, k, mynum, playertoteam[MAX_CLIENTS];\n\tplayer_info_t *s;\n\tchar t[SCR_TEAM_T_MAXTEAMSIZE];\n\tstatic int lastframecount = 0;\n\n\tif (!cl.teamplay)\n\t\treturn;\n\n\tif (lastframecount && lastframecount == cls.framecount)\n\t\treturn;\n\tlastframecount = cls.framecount;\n\n\tscoreboardteams = 0;\n\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t\tplayertoteam[i] = -1;\n\n\t// sort the teams\n\tmemset(teams, 0, sizeof(teams));\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t\tteams[i].plow = 999;\n\n\tmynum = Sbar_PlayerNum();\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tint flagstats[] = { 0, 0, 0 };\n\n\t\ts = &cl.players[i];\n\t\tif (!s->name[0] || s->spectator)\n\t\t\tcontinue;\n\n\t\t// find his team in the list\n\t\tstrlcpy (t, s->team, sizeof(t));\n\t\tif (!t[0])\n\t\t\tcontinue; // not on team\n\n\t\tStats_GetFlagStats(s - cl.players, flagstats);\n\n\t\tfor (j = 0; j < scoreboardteams; j++) {\n\t\t\tif (!strcmp(teams[j].team, t)) {\n\t\t\t\tplayertoteam[i] = j;\n\n\t\t\t\tif (cl.scoring_system == SCORING_SYSTEM_TEAMFRAGS) {\n\t\t\t\t\tif (teams[j].players == 0) {\n\t\t\t\t\t\tteams[j].frags = s->frags;\n\t\t\t\t\t\tteams[j].caps = flagstats[2];\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tteams[j].frags = max(s->frags, teams[j].frags);\n\t\t\t\t\t\tteams[j].caps = max(flagstats[2], teams[j].caps);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tteams[j].frags += s->frags;\n\t\t\t\t\tteams[j].caps += flagstats[2];\n\t\t\t\t}\n\t\t\t\tteams[j].players++;\n\t\t\t\tif (!cl.teamfortress && i == mynum) {\n\t\t\t\t\tteams[j].topcolor = scr_scoreboard_forcecolors.value ? s->topcolor : s->real_topcolor;\n\t\t\t\t\tteams[j].bottomcolor = scr_scoreboard_forcecolors.value ? s->bottomcolor : s->real_bottomcolor;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (j == scoreboardteams) { // must add him\n\t\t\tj = scoreboardteams++;\n\t\t\tplayertoteam[i] = j;\n\n\t\t\tteams[j].frags = s->frags;\n\t\t\tteams[j].caps = flagstats[2];\n\t\t\tteams[j].players = 1;\n\t\t\tteams[j].known_team_number = s->known_team_color;\n\t\t\tif (cl.teamfortress) {\n\t\t\t\tteams[j].topcolor = teams[j].bottomcolor = Utils_TF_TeamToColor(t);\n\t\t\t} else {\n\t\t\t\tteams[j].bottomcolor = scr_scoreboard_forcecolors.value ? s->bottomcolor : s->real_bottomcolor;\n\t\t\t\tteams[j].topcolor = scr_scoreboard_forcecolors.value ? s->topcolor : s->real_topcolor;\n\t\t\t}\n\t\t\tstrlcpy(teams[j].team, t, sizeof(teams[j].team));\n\n\t\t\tif (!Sbar_IsSpectator(mynum) && !strncmp(cl.players[mynum].team, t, sizeof(t) - 1))\n\t\t\t\tteams[j].myteam = true;\n\t\t\telse\n\t\t\t\tteams[j].myteam = false;\n\t\t}\n\t\tteams[j].plow = min(s->ping, teams[j].plow);\n\t\tteams[j].phigh = max(s->ping, teams[j].phigh);\n\t\tteams[j].ptotal += s->ping;\n\t}\n\n\t//prepare for sort\n\tfor (i = 0; i < scoreboardteams; i++)\n\t\tteamsort[i] = i;\n\n\t// good 'ol bubble sort\n\tfor (i = 0; i < scoreboardteams - 1; i++) {\n\t\tfor (j = i + 1; j < scoreboardteams; j++) {\n\t\t\tif (teams[teamsort[i]].frags < teams[teamsort[j]].frags) {\n\t\t\t\tk = teamsort[i];\n\t\t\t\tteamsort[i] = teamsort[j];\n\t\t\t\tteamsort[j] = k;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (cl.teamfortress && (cl.scoring_system == SCORING_SYSTEM_DEFAULT)) {\n\t\tfor (i = 0; i < scoreboardteams; i++) {\n\t\t\tfloat frags = (float) teams[i].frags / teams[i].players;\n\n\t\t\tif (frags != (int) frags)\n\t\t\t\treturn;\n\n\t\t\tfor (j = 0; j < MAX_CLIENTS; j++) {\n\t\t\t\ts = &cl.players[j];\n\t\t\t\tif (!s->name[0] || s->spectator || playertoteam[j] != i)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif (s->frags != frags)\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tfor (i = 0; i < scoreboardteams; i++) {\n\t\t\tteams[i].frags /= teams[i].players;\n\t\t}\n\t}\n}\n\n\n\nstatic int Sbar_SortTeamsAndFrags_Compare(int a, int b) {\n\tint team_comp, frag_comp;\n\tchar *team_one, *team_two;\n\tplayer_info_t *p1, *p2;\n\n\tp1 = &cl.players[a];\n\tp2 = &cl.players[b];\n\n\tif (p1->spectator)\n\t\treturn p2->spectator ? strcasecmp(p1->name, p2->name) : 1;\n\telse if (p2->spectator)\n\t\treturn -1;\n\n\tteam_one = cl.teamfortress ? Utils_TF_ColorToTeam(p1->real_bottomcolor) : p1->team;\n\tteam_two = cl.teamfortress ? Utils_TF_ColorToTeam(p2->real_bottomcolor) : p2->team;\n\n\tif ((team_comp = strcmp(team_one, team_two))) {\n\t\tint i;\n\t\tteam_t *t1 = NULL, *t2 = NULL;\n\n\t\tfor (i = 0; i < scoreboardteams; i++) {\n\t\t\tif (!strcmp(team_one, teams[i].team)) {\n\t\t\t\tt1 = &teams[i];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tfor (i = 0; i < scoreboardteams; i++) {\n\t\t\tif (!strcmp(team_two, teams[i].team)) {\n\t\t\t\tt2 = &teams[i];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn (t1 && t2 && t1->frags != t2->frags) ? (t2->frags - t1->frags) : team_comp;\n\t} else {\n\t\treturn (frag_comp = p2->frags - p1->frags) ? frag_comp : strcasecmp(p1->name, p2->name);\n\t}\n}\n\nstatic qbool Sbar_SortTeamsAndFrags(qbool specs) {\n\tint i, j, k;\n\tqbool real_teamplay;\n\tqbool any_flags = false;\n\n\treal_teamplay = cl.teamplay && (TP_CountPlayers() > 2);\n\n\tif (!real_teamplay || !scr_scoreboard_teamsort.value) {\n\t\treturn Sbar_SortFrags(specs);\n\t}\n\n\tscoreboardlines = 0;\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (cl.players[i].name[0]) {\n\t\t\tif (specs || !cl.players[i].spectator) {\n\t\t\t\tfragsort[scoreboardlines++] = i;\n\t\t\t\tif (cl.players[i].spectator) {\n\t\t\t\t\tcl.players[i].frags = -999;\n\t\t\t\t}\n\t\t\t}\n\t\t\tany_flags |= cl.players[i].loginname[0];\n\t\t}\n\t}\n\n\tSbar_SortTeams();\n\n\tfor (i = 0; i < scoreboardlines; i++) {\n\t\tfor (j = 0; j < scoreboardlines - 1 - i ; j++) {\n\t\t\tif (Sbar_SortTeamsAndFrags_Compare(fragsort[j], fragsort[j + 1]) > 0) {\n\t\t\t\tk = fragsort[j];\n\t\t\t\tfragsort[j] = fragsort[j + 1];\n\t\t\t\tfragsort[j + 1] = k;\n\t\t\t}\n\t\t}\n\t}\n\treturn any_flags;\n}\n\n\n\n/********************************* INVENTORY *********************************/\nstatic void Sbar_DrawInventory(void)\n{\n\tint i, flashon;\n\tchar num[6];\n\tfloat time;\n\tqbool headsup, hudswap;\n\n\theadsup = !Sbar_IsStandardBar();\n\thudswap = cl_hudswap.integer;\n\n\tif (!headsup) {\n\t\tDraw_TileClear (sbar_xofs, vid.height - sb_lines, sbar_xofs + 320, 24);\n\t\tSbar_DrawPic (0, -24, sb_ibar);\n\t}\n\n\t// weapons\n\tif (sbar_drawguns.integer) { // kazik\n\t\tfor (i = 0; i < 7; i++) {\n\t\t\tif (cl.stats[STAT_ITEMS] & (IT_SHOTGUN << i) ) {\n\t\t\t\ttime = cl.item_gettime[i];\n\t\t\t\tflashon = (int)((cl.time - time) * 10);\n\t\t\t\tif (flashon < 0) {\n\t\t\t\t\tflashon = 0;\n\t\t\t\t}\n\t\t\t\tif (flashon >= 10) {\n\t\t\t\t\tflashon = (cl.stats[STAT_ACTIVEWEAPON] == (IT_SHOTGUN << i)) ? 1 : 0;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tflashon = (flashon % 5) + 2;\n\t\t\t\t}\n\n\t\t\t\tif (headsup) {\n\t\t\t\t\tif (i || vid.height > 200)\n\t\t\t\t\t\tSbar_DrawSubPic (hudswap ? 0 : vid.width - 24,-68 - (7 - i) * 16 , sb_weapons[flashon][i], 0, 0, 24, 16);\n\n\t\t\t\t} else {\n\t\t\t\t\tSbar_DrawPic (i * 24, -16, sb_weapons[flashon][i]);\n\t\t\t\t}\n\n\t\t\t\tif (flashon > 1)\n\t\t\t\t\tsb_updates = 0;\t\t// force update to remove flash\n\t\t\t}\n\t\t}\n\t}\n\n\t// ammo counts\n\tif (sbar_drawammocounts.integer) { // kazik\n\t\tfor (i = 0; i < 4; i++) {\n\t\t\tsnprintf (num, sizeof(num), \"%3i\", cl.stats[STAT_SHELLS + i]);\n\t\t\tif (headsup) {\n\t\t\t\tSbar_DrawSubPic(hudswap ? 0 : vid.width - 42, -24 - (4 - i) * 11, sb_ibar, 3 + (i * 48), 0, 42, 11);\n\t\t\t\tif (num[0] != ' ')\n\t\t\t\t\tDraw_Character (hudswap ? 7: vid.width - 35, vid.height - SBAR_HEIGHT - 24 - (4 - i) * 11, 18 + num[0] - '0');\n\t\t\t\tif (num[1] != ' ')\n\t\t\t\t\tDraw_Character (hudswap ? 15: vid.width - 27, vid.height - SBAR_HEIGHT - 24 - (4 - i) * 11, 18 + num[1] - '0');\n\t\t\t\tif (num[2] != ' ')\n\t\t\t\t\tDraw_Character (hudswap ? 23: vid.width - 19, vid.height - SBAR_HEIGHT - 24 - (4 - i) * 11, 18 + num[2] - '0');\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (num[0] != ' ')\n\t\t\t\t\tSbar_DrawCharacter ((6 * i + 1) * 8 - 2, -24, 18 + num[0] - '0');\n\t\t\t\tif (num[1] != ' ')\n\t\t\t\t\tSbar_DrawCharacter ((6 * i + 2) * 8 - 2, -24, 18 + num[1] - '0');\n\t\t\t\tif (num[2] != ' ')\n\t\t\t\t\tSbar_DrawCharacter ((6 * i + 3) * 8 - 2, -24, 18 + num[2] - '0');\n\t\t\t}\n\t\t}\n\t}\n\n\t// items\n\tflashon = 0;\n\tif (sbar_drawitems.integer) { // kazik\n\t\tfor (i = 0; i < 6; i++) {\n\t\t\tif (cl.stats[STAT_ITEMS] & (1 << (17 + i))) {\n\t\t\t\ttime = cl.item_gettime[17 + i];\n\t\t\t\tif (time && time > cl.time - 2 && flashon) {\n\t\t\t\t\t// flash frame\n\t\t\t\t\tsb_updates = 0;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tSbar_DrawPic(192 + i * 16, -16, sb_items[i]);\n\t\t\t\t}\n\n\t\t\t\tif (time && time > cl.time - 2) {\n\t\t\t\t\tsb_updates = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// sigils\n\tif (sbar_drawsigils.integer) { // kazik\n\t\tfor (i = 0; i < 4; i++) {\n\t\t\tif (cl.stats[STAT_ITEMS] & (1 << (28 + i)))\t{\n\t\t\t\ttime = cl.item_gettime[28 + i];\n\t\t\t\tif (time && time > cl.time - 2 && flashon) {\n\t\t\t\t\t// flash frame\n\t\t\t\t\tsb_updates = 0;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tSbar_DrawPic(320 - 32 + i * 8, -16, sb_sigil[i]);\n\t\t\t\t}\n\n\t\t\t\tif (time && time > cl.time - 2) {\n\t\t\t\t\tsb_updates = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n/************************************ HUD ************************************/\n\nstatic void Sbar_DrawFrags_DrawCellPlayer(int x, int y, player_info_t* player, int brackets)\n{\n\tchar num[4];\n\n\t// draw background\n\tDraw_Fill (sbar_xofs + x * 8 + 10, y, 28, 4, Sbar_TopColorScoreboard(player));\n\tDraw_Fill (sbar_xofs + x * 8 + 10, y + 4, 28, 3, Sbar_BottomColorScoreboard(player));\n\n\t// draw number\n\tsnprintf (num, sizeof(num), \"%3i\", bound(-99, player->frags, 999));\n\n\tSbar_DrawCharacter ((x + 1) * 8 , -24, num[0]);\n\tSbar_DrawCharacter ((x + 2) * 8 , -24, num[1]);\n\tSbar_DrawCharacter ((x + 3) * 8 , -24, num[2]);\n\n\tif (brackets) {\n\t\tSbar_DrawCharacter (x * 8 + 2, -24, 16);\n\t\tSbar_DrawCharacter ((x + 4) * 8 - 4, -24, 17);\n\t}\n}\n\nstatic void Sbar_DrawFrags_DrawTeamCell(int x, int y, team_t* team)\n{\n\tchar num[4];\n\tint frags = team->frags;\n\tplayer_info_t p = { 0 };\n\tp.topcolor = team->topcolor;\n\tp.bottomcolor = team->bottomcolor;\n\tp.known_team_color = team->known_team_number;\n\n\t// draw background\n\tDraw_Fill(sbar_xofs + x * 8 + 10, y, 28, 4, Sbar_TopColorScoreboard(&p));\n\tDraw_Fill(sbar_xofs + x * 8 + 10, y + 4, 28, 3, Sbar_BottomColorScoreboard(&p));\n\n\t// draw number\n\tsnprintf(num, sizeof(num), \"%3i\", bound(-99, frags, 999));\n\n\tSbar_DrawCharacter((x + 1) * 8, -24, num[0]);\n\tSbar_DrawCharacter((x + 2) * 8, -24, num[1]);\n\tSbar_DrawCharacter((x + 3) * 8, -24, num[2]);\n\n\tif (team->myteam) {\n\t\tSbar_DrawCharacter(x * 8 + 2, -24, 16);\n\t\tSbar_DrawCharacter((x + 4) * 8 - 4, -24, 17);\n\t}\n}\n\nstatic void Sbar_DrawFrags (void) {\n\tint i, k, l, x, y, mynum, myteam = -1;\n\tplayer_info_t *s;\n\tteam_t *tm;\n\tqbool drawn_self = false, drawn_self_team = false;\n\n\tif (!scr_drawHFrags.value)\n\t\treturn;\n\n\tx = 23;\n\ty = vid.height - SBAR_HEIGHT - 23;\n\n\tmynum = Sbar_PlayerNum();\n\n\tif (!cl.teamplay || scr_drawHFrags.value == 1) {\n\t\tSbar_SortFrags (false);\n\t\tl = min(scoreboardlines, 4);\n\n\t\tfor (i = 0; i < l; i++) {\n\t\t\tif (i + 1 == l && !drawn_self && !Sbar_IsSpectator(mynum)) {\n\t\t\t\tk = mynum;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tk = fragsort[i];\n\t\t\t}\n\n\t\t\ts = &cl.players[k];\n\n\t\t\tif (!s->name[0] || s->spectator) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tSbar_DrawFrags_DrawCellPlayer(x, y, s, k == mynum);\n\n\t\t\tdrawn_self |= (k == mynum);\n\n\t\t\tx += 4;\n\t\t}\n\t}\n\telse {\n\t\tSbar_SortTeams();\n\n\t\tfor (i = 0; i < scoreboardteams; i++) {\n\t\t\tif (teams[i].myteam) {\n\t\t\t\tmyteam = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tl = min(scoreboardteams, 4);\n\t\tfor (i = 0; i < l; i++) {\n\t\t\tif (myteam != -1 && i + 1 == l && !drawn_self_team)\n\t\t\t\tk = myteam;\n\t\t\telse\n\t\t\t\tk = teamsort[i];\n\n\t\t\ttm = &teams[k];\n\t\t\tSbar_DrawFrags_DrawTeamCell(x, y, tm);\n\n\t\t\tif (k == myteam)\n\t\t\t\tdrawn_self_team = true;\n\n\t\t\tx += 4;\n\t\t}\n\n\t\tif (scr_drawHFrags.value != 3 && l <= 2 && !Sbar_IsSpectator(mynum)) {\n\n\t\t\tx += 4 * (3 - l);\n\n\t\t\ts = &cl.players[mynum];\n\n\t\t\tSbar_DrawFrags_DrawCellPlayer(x, y, s, 1);\n\t\t}\n\t}\n}\n\nstatic void Sbar_DrawFace (void) {\n\tint f, anim;\n\n\tif ( (cl.stats[STAT_ITEMS] & (IT_INVISIBILITY | IT_INVULNERABILITY) )\n\t\t\t== (IT_INVISIBILITY | IT_INVULNERABILITY) )\t{\n\t\tSbar_DrawPic (112, 0, sb_face_invis_invuln);\n\t\treturn;\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_QUAD) {\n\t\tSbar_DrawPic (112, 0, sb_face_quad );\n\t\treturn;\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) {\n\t\tSbar_DrawPic (112, 0, sb_face_invis );\n\t\treturn;\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) {\n\t\tSbar_DrawPic (112, 0, sb_face_invuln);\n\t\treturn;\n\t}\n\n\tf = cl.stats[STAT_HEALTH] / 20;\n\tf = bound (0, f, 4);\n\n\tif (cl.time <= cl.faceanimtime)\t{\n\t\tanim = 1;\n\t\tsb_updates = 0;\t\t// make sure the anim gets drawn over\n\t} else {\n\t\tanim = 0;\n\t}\n\tSbar_DrawPic (112, 0, sb_faces[f][anim]);\n}\n\nstatic void Sbar_DrawNormal (void)\n{\n\tif (Sbar_IsStandardBar())\n\t\tSbar_DrawPic (0, 0, sb_sbar);\n\n\t// armor\n\tif ((cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) && sbar_drawarmor666.value)\t{\n\t\tif (sbar_drawarmor.value)\n\t\t\tSbar_DrawNum (24, 0, 666, 3, 1);\n\t\tif (sbar_drawarmoricon.value)\n\t\t\tSbar_DrawPic (0, 0, draw_disc);\n\t} else {\n\t\tif (sbar_drawarmor.value)\n\t\t\tSbar_DrawNum (24, 0, cl.stats[STAT_ARMOR], 3, cl.stats[STAT_ARMOR] <= 25);\n\t\tif (sbar_drawarmoricon.value)\n\t\t{\n\t\t\tif (cl.stats[STAT_ITEMS] & IT_ARMOR3)\n\t\t\t\tSbar_DrawPic (0, 0, sb_armor[2]);\n\t\t\telse if (cl.stats[STAT_ITEMS] & IT_ARMOR2)\n\t\t\t\tSbar_DrawPic (0, 0, sb_armor[1]);\n\t\t\telse if (cl.stats[STAT_ITEMS] & IT_ARMOR1)\n\t\t\t\tSbar_DrawPic (0, 0, sb_armor[0]);\n\t\t}\n\n\t\tif (amf_stat_loss.value)\n\t\t\tDraw_AMFStatLoss (STAT_ARMOR, NULL);\n\t}\n\n\t// face\n\tif (sbar_drawfaceicon.value)\n\t\tSbar_DrawFace ();\n\n\t// health\n\tif (sbar_drawhealth.value)\n\t{\n\t\tSbar_DrawNum (136, 0, cl.stats[STAT_HEALTH], 3, cl.stats[STAT_HEALTH] <= 25);\n\t\tif (amf_stat_loss.value)\n\t\t\tDraw_AMFStatLoss (STAT_HEALTH, NULL);\n\t}\n\n\t// ammo icon\n\tif (sbar_drawammoicon.value)    // kazik\n\t{\n\t\tif (cl.stats[STAT_ITEMS] & IT_SHELLS)\n\t\t\tSbar_DrawPic (224, 0, sb_ammo[0]);\n\t\telse if (cl.stats[STAT_ITEMS] & IT_NAILS)\n\t\t\tSbar_DrawPic (224, 0, sb_ammo[1]);\n\t\telse if (cl.stats[STAT_ITEMS] & IT_ROCKETS)\n\t\t\tSbar_DrawPic (224, 0, sb_ammo[2]);\n\t\telse if (cl.stats[STAT_ITEMS] & IT_CELLS)\n\t\t\tSbar_DrawPic (224, 0, sb_ammo[3]);\n\t}\n\tif (sbar_drawammo.value)\n\t\tSbar_DrawNum (248, 0, cl.stats[STAT_AMMO], 3, cl.stats[STAT_AMMO] <= 10);\n}\n\nstatic void Sbar_DrawCompact_WithIcons(void) {\n\tint i, align, old_sbar_xofs;\n\tchar str[4];\n\n\tif (Sbar_IsStandardBar())\n\t\tSbar_DrawPic (0, 0, sb_sbar);\n\n\told_sbar_xofs = sbar_xofs;\n\tsbar_xofs = scr_centerSbar.value ? (vid.width - 158) >> 1: 0;\n\n\tif ((cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) && sbar_drawarmor666.value)\n\t\tSbar_DrawNum (2, 0, 666, 3, 1);\n\telse\n\t\tSbar_DrawNum (2, 0, cl.stats[STAT_ARMOR], 3, cl.stats[STAT_ARMOR] <= 25);\n\n\tif (cl.stats[STAT_ITEMS] & IT_ARMOR3)\n\t\tSbar_DrawPic (-24, 0, sb_armor[2]);\n\telse if (cl.stats[STAT_ITEMS] & IT_ARMOR2)\n\t\tSbar_DrawPic (-24, 0, sb_armor[1]);\n\telse if (cl.stats[STAT_ITEMS] & IT_ARMOR1)\n\t\tSbar_DrawPic (-24, 0, sb_armor[0]);\n\n\tSbar_DrawNum (86, 0, cl.stats[STAT_HEALTH], 3, cl.stats[STAT_HEALTH] <= 25);\n\n\talign = scr_compactHudAlign.value ? 1 : 0;\n\tfor (i = 0; i < 4; i++) {\n\t\tsnprintf(str, sizeof(str), \"%d\", cl.stats[STAT_SHELLS + i]);\n\t\tif (cl.stats[STAT_SHELLS + i] <= sbar_lowammo.integer)\n\t\t\tSbar_DrawAltString(align * 8 * (3 - strlen(str)) + 24 + 32 * i, -16, str);\n\t\telse\n\t\t\tSbar_DrawString(align * 8 * (3 - strlen(str)) + 24 + 32 * i, -16, str);\n\t}\n\tfor (i = 0; i < 7; i++) {\n\t\tif (cl.stats[STAT_ITEMS] & (IT_SHOTGUN << i) ) {\n\t\t\tif (cl.stats[STAT_ACTIVEWEAPON] == (IT_SHOTGUN << i))\n\t\t\t\tSbar_DrawPic (align * 8 + 18 + 16 * i, -32, sb_weapons[1][i]);\n\t\t\telse\n\t\t\t\tSbar_DrawPic (align * 8 + 18 + 16 * i, -32, sb_weapons[0][i]);\n\t\t}\n\t}\n\tsbar_xofs = old_sbar_xofs;\n}\n\nstatic void Sbar_DrawCompact(void) {\n\tint i, align, old_sbar_xofs;\n\tstatic char *weapons[7] = {\"sg\", \"bs\", \"ng\", \"sn\", \"gl\", \"rl\", \"lg\"};\n\tchar str[4];\n\n\tif (Sbar_IsStandardBar())\n\t\tSbar_DrawPic (0, 0, sb_sbar);\n\n\told_sbar_xofs = sbar_xofs;\n\tsbar_xofs = scr_centerSbar.value ? (vid.width - 306) >> 1: 0;\n\n\tif ((cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) && sbar_drawarmor666.value)\n\t\tSbar_DrawNum (2, 0, 666, 3, 1);\n\telse\n\t\tSbar_DrawNum (2, 0, cl.stats[STAT_ARMOR], 3, cl.stats[STAT_ARMOR] <= 25);\n\n\tSbar_DrawNum (86, 0, cl.stats[STAT_HEALTH], 3, cl.stats[STAT_HEALTH] <= 25);\n\n\talign = scr_compactHudAlign.value ? 1 : 0;\n\tfor (i = 0; i < 4; i++) {\n\t\tsnprintf(str, sizeof(str), \"%d\", cl.stats[STAT_SHELLS + i]);\n\t\tif (cl.stats[STAT_SHELLS + i] <= sbar_lowammo.integer)\n\t\t\tSbar_DrawAltString(align * 8 * (3 - strlen(str)) + 174 + 32 * i, 3, str);\n\t\telse\n\t\t\tSbar_DrawString(align * 8 * (3 - strlen(str)) + 174 + 32 * i, 3, str);\n\t}\n\tfor (i = 0; i < 7; i++) {\n\t\tif (cl.stats[STAT_ITEMS] & (IT_SHOTGUN << i)) {\n\t\t\tif (cl.stats[STAT_ACTIVEWEAPON] == (IT_SHOTGUN << i))\n\t\t\t\tSbar_DrawAltString(166 + 5 * 4 * i, 14, weapons[i]);\n\t\t\telse\n\t\t\t\tSbar_DrawString(166 + 5 * 4 * i, 14, weapons[i]);\n\t\t}\n\t}\n\tsbar_xofs = old_sbar_xofs;\n}\n\nstatic void Sbar_DrawCompact_TF(void) {\n\tint i, align, old_sbar_xofs;\n\tchar str[4];\n\n\tif (Sbar_IsStandardBar())\n\t\tSbar_DrawPic (0, 0, sb_sbar);\n\n\told_sbar_xofs = sbar_xofs;\n\tsbar_xofs = scr_centerSbar.value ? (vid.width - 222) >> 1: 0;\n\n\talign = scr_compactHudAlign.value ? 1 : 0;\n\tif ((cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) && sbar_drawarmor666.value)\n\t\tSbar_DrawNum (2, 0, 666, 3, 1);\n\telse\n\t\tSbar_DrawNum (2, 0, cl.stats[STAT_ARMOR], 3, cl.stats[STAT_ARMOR] <= 25);\n\tSbar_DrawNum (86, 0, cl.stats[STAT_HEALTH], 3, cl.stats[STAT_HEALTH] <= 25);\n\tfor (i = 0; i < 4; i++) {\n\t\tsnprintf(str, sizeof(str), \"%d\", cl.stats[STAT_SHELLS + i]);\n\t\tif (cl.stats[STAT_SHELLS + i] <= sbar_lowammo.integer)\n\t\t\tSbar_DrawAltString(align * 8 * (3 - strlen(str)) + 166 + 32 * (i % 2), i >= 2 ? 14 : 3, str);\n\t\telse\n\t\t\tSbar_DrawString(align * 8 * (3 - strlen(str)) + 166 + 32 * (i % 2), i >= 2 ? 14 : 3, str);\n\t}\n\tsbar_xofs = old_sbar_xofs;\n}\n\nstatic void Sbar_DrawCompact_Bare (void) {\n\tint old_sbar_xofs;\n\n\tif (Sbar_IsStandardBar())\n\t\tSbar_DrawPic (0, 0, sb_sbar);\n\n\told_sbar_xofs = sbar_xofs;\n\tsbar_xofs = scr_centerSbar.value ? (vid.width - 158) >> 1: 0;\n\n\tif ((cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) && sbar_drawarmor666.value)\n\t\tSbar_DrawNum (2, 0, 666, 3, 1);\n\telse\n\t\tSbar_DrawNum (2, 0, cl.stats[STAT_ARMOR], 3, cl.stats[STAT_ARMOR] <= 25);\n\tSbar_DrawNum (86, 0, cl.stats[STAT_HEALTH], 3, cl.stats[STAT_HEALTH] <= 25);\n\tsbar_xofs = old_sbar_xofs;\n}\n\n/******************************** SCOREBOARD ********************************/\n\n/*\n   ===============\n   Sbar_SoloScoreboard -- johnfitz -- new layout\n   ===============\n   */\nvoid Sbar_SoloScoreboard (void)\n{\n\tchar\tstr[256];\n\tint\tlen;\n\tint\tclock_y;\n\tint\tmapname_y;\n\n\tif (cl.gametype == GAME_COOP)\n\t{\n\t\tsprintf (str,\"Kills: %i/%i\", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]);\n\t\tSbar_DrawString (8, 12, str);\n\n\t\tsprintf (str,\"Secrets: %i/%i\", cl.stats[STAT_SECRETS], cl.stats[STAT_TOTALSECRETS]);\n\t\tSbar_DrawString (312 - strlen(str)*8, 12, str);\n#ifndef CLIENTONLY\n\t\tsprintf (str,\"skill %i\", (int)(skill.value + 0.5));\n\t\tSbar_DrawString (160 - strlen(str)*4, 12, str);\n#endif\n\t\tstrlcpy(str, cl.levelname, sizeof(str));\n\t\tstrlcat(str, \" (\", sizeof(str));\n\t\tstrlcat(str, host_mapname.string, sizeof(str));\n\t\tstrlcat(str, \")\", sizeof(str));\n\t\tlen = strlen (str);\n\t\tSbar_DrawString (160 - len*4, 4, str);\n\t}\n\telse\n\t{\n\t\tif ((scr_scoreboard_showclock.integer && !scr_scoreboard_showmapname.integer))\n\t\t{\n\t\t\tclock_y = -10;\n\t\t}\n\t\telse if (!scr_scoreboard_showclock.integer && scr_scoreboard_showmapname.integer)\n\t\t{\n\t\t\tmapname_y = -10;\n\t\t}\n\t\telse if (scr_scoreboard_showclock.integer && scr_scoreboard_showmapname.integer == 3)\n\t\t{\n\t\t\tclock_y = 2;\n\t\t\tmapname_y = -10;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tclock_y = -10;\n\t\t\tmapname_y = 2;\n\t\t}\n\n\t\tif (scr_scoreboard_showclock.integer)\n\t\t{\n\t\t\tsnprintf(str, sizeof(str), \"%s\", SCR_GetTimeString(TIMETYPE_CLOCK, \"%H:%M:%S\"));\n\t\t\tSbar_DrawString(160 - strlen(str) * 4, clock_y, str);\n\t\t}\n\n\t\tif (scr_scoreboard_showmapname.integer)\n\t\t{\n\t\t\tswitch (scr_scoreboard_showmapname.integer)\n\t\t\t{\n\t\t\t\tcase 2:\n\t\t\t\t\tsnprintf(str, sizeof(str), \"%s\", cl.levelname);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tsnprintf(str, sizeof(str), \"%s\", host_mapname.string);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tsnprintf(str, sizeof(str), \"%s (%s)\", cl.levelname, host_mapname.string);\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tSbar_DrawString (160 - strlen(str) * 4, mapname_y, str);\n\t\t}\n\t}\n}\n\n#define SCOREBOARD_LASTROW\t\t(vid.height - 34)\n\n#define\tRANK_WIDTH_BASICSTATS\t(11 * 8)\n#define\tRANK_WIDTH_TEAMSTATS\t(4 * 8)\n#define\tRANK_WIDTH_TCHSTATS\t\t(5 * 8)\n#define\tRANK_WIDTH_CAPSTATS\t\t(5 * 8)\n\n#define RANK_WIDTH_DM\t\t\t\t(-8 + 168 + (MAX_SCOREBOARDNAME * 8))\n#define RANK_WIDTH_TEAM\t\t\t\t(-8 + 208 + (MAX_SCOREBOARDNAME * 8))\n\n#define SCOREBOARD_ALPHA\t\t\t(0.5 * bound(0, scr_scoreboard_fillalpha.value, 1))\n#define SCOREBOARD_HEADINGALPHA\t\t(bound(0, scr_scoreboard_fillalpha.value, 1))\n\nstatic qbool Sbar_ShowTeamKills(void)\n{\n\tif (cl.teamfortress) {\n\t\treturn ((cl.teamplay & 21) != 21); // 21 = (1)Teamplay On + (4)Team-members take No damage from direct fire + (16)Team-members take No damage from area-affect weaponry, so its like teamplay is off??\n\t}\n\telse {\n\t\t// in teamplay 3 and 4 it's not possible to make teamkills\n\t\treturn !(cl.teamplay == 3 || cl.teamplay == 4);\n\t}\n}\n\nextern qtvuser_t *qtvuserlist;\n\nstatic void Sbar_DeathmatchOverlay(int start)\n{\n\tint stats_team, stats_touches, stats_caps, playerstats[7];\n\tint scoreboardsize, colors_thickness, statswidth, stats_xoffset = 0;\n\tint i, k, x, y, xofs, total, p, skip = 10, fragsint;\n\tint rank_width, leftover, startx, tempx, mynum;\n\tchar num[12];\n\tchar myminutes[11];\n\tchar fragsstr[10];\n\tplayer_info_t *s;\n\tti_player_t *ti_cl;\n\tmpic_t *pic;\n\tfloat scale = 1.0f;\n\tfloat alpha = 1.0f;\n\tfloat ca_alpha;\t// alpha value for scoreboard elements during clan arena / wipeout\n\tqbool proportional = scr_scoreboard_proportional.integer;\n\tqbool any_flags = false;\n\textern ti_player_t ti_clients[MAX_CLIENTS];\n\tint is_classic = scr_scoreboard_classic.integer;\n\tqbool is_classic_spec;\n\tqtvuser_t *qu;\n\n\tif (!start && hud_faderankings.value) {\n\t\tDraw_FadeScreen(hud_faderankings.value);\n\t}\n\n#ifndef CLIENTONLY\n\t// FIXME\n\t// check number of connections instead?\n\tif (cl.gametype == GAME_COOP && com_serveractive && !coop.value) {\n\t\treturn;\n\t}\n#endif\n\n\tif (cls.state == ca_active && !cls.demoplayback) {\n\t\t// request new ping times every two seconds\n\t\tif (cls.realtime - cl.last_ping_request >= 2) {\n\t\t\tcl.last_ping_request = cls.realtime;\n\t\t\tMSG_WriteByte(&cls.netchan.message, clc_stringcmd);\n\t\t\tSZ_Print(&cls.netchan.message, \"pings\");\n\t\t}\n\t}\n\n\tscr_copyeverything = 1;\n\tscr_fullupdate = 0;\n\n\tmynum = Sbar_PlayerNum();\n\n\trank_width = (cl.teamplay ? RANK_WIDTH_TEAM : RANK_WIDTH_DM);\n\n\tstatswidth = 0;\n\tstats_team = stats_touches = stats_caps = 0;\n\tif (scr_scoreboard_showfrags.value && Stats_IsActive()) {\n\t\tif (rank_width + statswidth + RANK_WIDTH_BASICSTATS < vid.width - 16) {\n\t\t\tstatswidth += RANK_WIDTH_BASICSTATS;\n\t\t\tif (cl.teamplay) {\n\t\t\t\tif (rank_width + statswidth + RANK_WIDTH_TEAMSTATS < vid.width - 16) {\n\t\t\t\t\tif (Sbar_ShowTeamKills()) {\n\t\t\t\t\t\tstatswidth += RANK_WIDTH_TEAMSTATS;\n\t\t\t\t\t\tstats_team++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif ((cl.teamfortress || scr_scoreboard_showflagstats.value) && Stats_IsFlagsParsed()) {\n\t\t\t\t\tif (rank_width + statswidth + RANK_WIDTH_TCHSTATS < vid.width - 16) {\n\t\t\t\t\t\tstatswidth += RANK_WIDTH_TCHSTATS;\n\t\t\t\t\t\tstats_touches++;\n\t\t\t\t\t}\n\t\t\t\t\tif (rank_width + statswidth + RANK_WIDTH_CAPSTATS < vid.width - 16) {\n\t\t\t\t\t\tstatswidth += RANK_WIDTH_CAPSTATS;\n\t\t\t\t\t\tstats_caps++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\trank_width += statswidth;\n\n\tleftover = rank_width;\n\trank_width = bound(0, rank_width, vid.width - 16);\n\tleftover = max(0, leftover - rank_width);\n\tif (hud_centerranking.value) {\n\t\txofs = ((vid.width - rank_width) >> 1) + hud_rankingpos_x.value;\n\t}\n\telse {\n\t\txofs = hud_rankingpos_x.value;\n\t}\n\n\tif (start) {\n\t\ty = start;\n\t}\n\telse {\n\t\ty = hud_rankingpos_y.value;\n\t\tif (y < 0 || y > vid.height / 2) {\n\t\t\ty = 0;\n\t\t}\n\t}\n\n\tif (!start) {\n\t\tif (scr_scoreboard_drawtitle.value) {\n\t\t\tpic = Draw_CachePic(CACHEPIC_RANKING);\n\t\t\tDraw_Pic(xofs + (rank_width - pic->width) / 2, y, pic);\n\t\t\tstart = 36 + hud_rankingpos_y.value;\n\t\t}\n\t\telse {\n\t\t\tstart = 12 + hud_rankingpos_y.value;\n\t\t}\n\t}\n\n\tfor (i = 0; i < scoreboardlines && y <= SCOREBOARD_LASTROW; i++) {\n\t\tk = fragsort[i];\n\t\ts = &cl.players[k];\n\n\t\tif (!s->name[0]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tany_flags |= (s->loginname[0] && scr_scoreboard_login_indicator.string[0]);\n\t}\n\n\ty = start;\n\n\tif (!scr_scoreboard_borderless.value) {\n\t\tDraw_Fill(xofs - 1, y - 9, rank_width + 2, 1, 0);\t\t\t\t\t\t//Border - Top\n\t}\n\n\tif (!is_classic)\n\t\tDraw_AlphaFill(xofs, y - 8, rank_width, 9, 1, SCOREBOARD_HEADINGALPHA);\t//Draw heading row\n\n\tx = xofs + 1;\n\t// teamplay: \" ping pl  fps frags team name\" | \" ping pl time frags team name\"\n\t// non-tp  : \" ping pl  fps frags name\" | \" ping pl time frags name\"\n\n\tx += FONT_WIDTH;\n\tDraw_SStringAligned(x, y - 8, \"ping\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 4);\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y, is_classic == 2 ? \"----\" : \"\\x1d\\x1e\\x1e\\x1f\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 4);\n\tx += 5 * FONT_WIDTH;\n\tDraw_SStringAligned(x, y - 8, \"pl\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 2);\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y, is_classic == 2 ? \"--\" : \"\\x1d\\x1f\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 2);\n\tx += 3 * FONT_WIDTH;\n\tDraw_SStringAligned(x, y - 8, \"time\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 4);\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y, is_classic == 2 ? \"----\" : \"\\x1d\\x1e\\x1e\\x1f\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 4);\n\tx += 5 * FONT_WIDTH;\n\tDraw_SStringAligned(x, y - 8, \"frags\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 5);\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y, is_classic == 2 ? \"-----\" : \"\\x1d\\x1e\\x1e\\x1e\\x1f\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 5);\n\tx += 6 * FONT_WIDTH;\n\tif (cl.teamplay) {\n\t\tDraw_SStringAligned(x, y - 8, \"team\", scale, alpha, proportional, text_align_center, x + FONT_WIDTH * 4);\n\t\tif (is_classic)\n\t\t\tDraw_SStringAligned(x, y, is_classic == 2 ? \"----\" : \"\\x1d\\x1e\\x1e\\x1f\", scale, alpha, proportional, text_align_center, x + FONT_WIDTH * 4);\n\t\tx += 5 * FONT_WIDTH;\n\t}\n\n\tif (any_flags) {\n\t\tx += FONT_WIDTH;\n\t}\n\n\tDraw_SStringAligned(x, y - 8, \"name\", scale, alpha, proportional, text_align_left, x + FONT_WIDTH * 15);\n\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y, is_classic == 2 ? \"---------------\" : \"\\x1d\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1f\", scale, alpha, proportional, text_align_left, x + FONT_WIDTH * 15);\n\tx += (any_flags ? 15 : 16) * FONT_WIDTH;\n\tif (!is_classic && statswidth) {\n\t\tstats_xoffset = x;\n\n\t\tDraw_SStringAligned(x, y - 8, \"kills\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 5);\n\t\tx += FONT_WIDTH * 6;\n\t\tif (stats_team) {\n\t\t\tDraw_SStringAligned(x, y - 8, \"tks\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 3);\n\t\t\tx += FONT_WIDTH * 4;\n\t\t}\n\t\tDraw_SStringAligned(x, y - 8, \"dths\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 4);\n\t\tx += FONT_WIDTH * 5;\n\n\t\tif (stats_touches) {\n\t\t\tDraw_SStringAligned(x, y - 8, \"tchs\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 4);\n\t\t\tx += FONT_WIDTH * 5;\n\t\t}\n\n\t\tif (stats_caps) {\n\t\t\tDraw_SStringAligned(x, y - 8, \"caps\", scale, alpha, proportional, text_align_right, x + FONT_WIDTH * 4);\n\t\t\tx += FONT_WIDTH * 5;\n\t\t}\n\t}\n\n\tx = xofs + 1;\n\n\tif (!is_classic)\n\t\tDraw_Fill(xofs, y + 1, rank_width, 1, 0);\t//Border - Top (under header)\n\n\t// If scr_scoreboard_classic is true, we've added an extra line for the\n\t// separator, which must be accounted for.\n\ty += is_classic ? 10 : 2;\t\t\t\t//dont go over the black border, move the rest down\n\tif (!scr_scoreboard_borderless.value) {\n\t\tDraw_Fill(xofs - 1, y - 10, 1, 10, 0);\t\t\t\t\t\t//Border - Left\n\t\tDraw_Fill(xofs - 1 + rank_width + 1, y - 10, 1, 10, 0);\t//Border - Right\n\t}\n\tstartx = x = xofs + 8;\n\n\ttempx = xofs + rank_width - MAX_SCOREBOARDNAME * 8 - (48 + statswidth) + leftover;\n\n\tSbar_SortTeamsAndFrags(true);\t// scores\n\n\tfor (i = scoreboardsize = 0; i < scoreboardlines; i++) {\n\t\tk = fragsort[i];\n\t\ts = &cl.players[k];\n\n\t\tif (s->name[0]) {\n\t\t\tscoreboardsize++;\n\t\t}\n\t}\n\n\tif (y + (scoreboardsize - 1) * skip > SCOREBOARD_LASTROW) {\n\t\tcolors_thickness = 3;\n\t\tskip = 8;\n\t}\n\telse {\n\t\tcolors_thickness = 4;\n\t}\n\n\tfor (i = 0; i < scoreboardlines && y <= SCOREBOARD_LASTROW; i++) {\n\t\tcolor_t background;\n\t\tfloat bk_alpha;\n\t\tbyte c;\n\t\tclrinfo_t color;\n\n\t\tk = fragsort[i];\n\t\ts = &cl.players[k];\n\t\tis_classic_spec = is_classic && s->spectator;\n\t\tti_cl = &ti_clients[k];\n\t\tca_alpha = (check_ktx_ca_wo() && scr_scoreboard_wipeout.value && ti_cl->isdead) ? 0.25f : 1.0f; // fade dead players in CA/wipeout\n\n\t\tif (!s->name[0]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t//render the main background transparencies behind players row\n\t\tif (is_classic) {\n\t\t\tbk_alpha = 0;\n\t\t}\n\t\telse if (k == mynum && scr_scoreboard_highlightself.value) {\n\t\t\tbk_alpha = 1.7 * SCOREBOARD_ALPHA;\n\t\t\tbk_alpha = min(alpha, 0.75);\n\t\t}\n\t\telse {\n\t\t\tbk_alpha = SCOREBOARD_ALPHA;\n\t\t}\n\n\t\tif (!cl.teamplay || s->spectator || !scr_scoreboard_fillcolored.value ||\n\t\t\t(scr_scoreboard_fillcolored.value == 2 && !scr_scoreboard_teamsort.value)) {\n\t\t\tc = 2;\n\t\t}\n\t\telse {\n\t\t\tc = Sbar_BottomColorScoreboard(s);\n\t\t}\n\n\t\tif (S_Voip_Speaking(k)) {\n\t\t\tbackground = RGBA_TO_COLOR(0, 255, 0, (byte)(bk_alpha * 255));\n\t\t}\n\t\telse {\n\t\t\tbackground = RGBA_TO_COLOR(host_basepal[c * 3], host_basepal[c * 3 + 1], host_basepal[c * 3 + 2], (byte)(bk_alpha * 255));\n\t\t}\n\n\t\tDraw_AlphaFillRGB(xofs, y, rank_width, skip, background);\n\n\t\tif (!scr_scoreboard_borderless.value) {\n\t\t\tDraw_Fill(xofs - 1, y, 1, skip, 0);\t\t\t\t\t//Border - Left\n\t\t\tDraw_Fill(xofs - 1 + rank_width + 1, y, 1, skip, 0);\t//Border - Right\n\t\t}\n\n\t\t// draw ping\n\t\tp = s->ping;\n\t\tif (p < 0 || p > 999) {\n\t\t\tp = 999;\n\t\t}\n\t\tsnprintf(num, sizeof(num), \"%i\", p);\n\t\tcolor.c = is_classic\n\t\t\t? RGBA_TO_COLOR(0xFF, 0xFF, 0xFF, (byte)(ca_alpha * 255))\n\t\t\t: RGBA_TO_COLOR(0xAA, 0xAA, 0xDD, (byte)(ca_alpha * 255));\n\t\tcolor.i = 0;\n\t\tDraw_SColoredStringAligned(x, y, num, &color, 1, scale, alpha * ca_alpha, proportional, text_align_right, x + FONT_WIDTH * 4);\n\t\tx += 4 * FONT_WIDTH; // move it forward, ready to print next column\n\n\t\t// draw pl\n\t\tp = s->pl;\n\t\tsnprintf(num, sizeof(num), \"%i\", p);\n\n\t\tif (is_classic)\n\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xFF, 0xFF, (byte)(ca_alpha * 255));\n\t\telse {\n\t\t\tif (p == 0) {\n\t\t\t\t// 0 - white\n\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xFF, 0xFF, (byte)(ca_alpha * 255));\n\t\t\t}\n\t\t\telse if (p < 3) {\n\t\t\t\t// 1-2 - yellow\n\t\t\t\tcolor.c = RGBA_TO_COLOR(0xCC, 0xDD, 0xDD, (byte)(ca_alpha * 255));\n\t\t\t}\n\t\t\telse if (p < 6) {\n\t\t\t\t// 3-5 orange\n\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0x55, 0x00, (byte)(ca_alpha * 255));\n\t\t\t}\n\t\t\telse {\t// 6+ - red\n\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0x00, 0x00, (byte)(ca_alpha * 255));\n\t\t\t}\n\t\t}\n\n\t\tif (!is_classic_spec)\n\t\t\tDraw_SColoredStringAligned(x, y, num, &color, 1, scale, alpha * ca_alpha, proportional, text_align_right, x + 3 * FONT_WIDTH);\n\n\t\tx += 4 * FONT_WIDTH;\n\n\t\t// draw time\n\t\ttotal = (cl.intermission ? cl.completed_time : cls.demoplayback ? cls.demotime : cls.realtime) - s->entertime;\n\t\ttotal = (int)total / 60;\n\t\ttotal = bound(0, total, 999); // limit to 3 symbols int\n\n\t\tcolor.c = RGBA_TO_COLOR(255, 255, 255, 255);\n\t\tmyminutes[0] = '\\0';\n\n\t\t// overwrite time column with spawn times in KTX wipeout\n\t\tif (check_ktx_wo() && scr_scoreboard_wipeout.value && (ti_cl->isdead == 1) && (ti_cl->timetospawn > 0) && (ti_cl->timetospawn < 999)){\n\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xAA, 0x00, 255);\n\t\t\tsnprintf(myminutes, sizeof(myminutes), \"%d\", ti_cl->timetospawn);\n\n\t\t\tif (!is_classic_spec)\n\t\t\t\tDraw_SColoredStringAligned(x, y, myminutes, &color, 1, scale * 0.85, alpha, proportional, text_align_right, x + 4 * FONT_WIDTH);\n\t\t}\n\n\t\telse if (!check_ktx_wo() || !scr_scoreboard_wipeout.value)\n\t\t{\n\t\t\tsnprintf(myminutes, sizeof(myminutes), \"%i\", total);\n\n\t\t\tif (scr_scoreboard_afk.integer && (s->chatflag & CIF_AFK)) {\n\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0x11, 0x11, 0xFF);\n\t\t\t\tif (scr_scoreboard_afk_style.integer == 1) {\n\t\t\t\t\tsnprintf(myminutes, sizeof(myminutes), \"afk\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!is_classic_spec)\n\t\t\t\tDraw_SColoredStringAligned(x, y, myminutes, &color, 1, scale, alpha, proportional, text_align_right, x + 4 * FONT_WIDTH);\n\t\t}\n\n\t\tx += 5 * FONT_WIDTH;\n\n\t\t// draw spectator\n\t\tif (s->spectator) {\n\t\t\tif (cl.teamplay) {\n\t\t\t\t// use columns frags and team\n\t\t\t\tDraw_SStringAligned(is_classic_spec ? x - (8 * FONT_WIDTH) : x, y, is_classic_spec ? \"(spectator)\" : scr_scoreboard_spectator_name.string, scale, alpha, proportional, text_align_left, x + 10 * FONT_WIDTH);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// use only frags column\n\t\t\t\tDraw_SStringAligned(is_classic_spec ? x - (8 * FONT_WIDTH): x, y, is_classic_spec ? \"(spectator)\" : scr_scoreboard_spectator_name.string, scale, alpha, proportional, text_align_left, x + SHORT_SPECTATOR_NAME_LEN * FONT_WIDTH);\n\t\t\t}\n\n\t\t\tx += (cl.teamplay ? 11 : 6) * FONT_WIDTH; // move across to print the name\n\n\t\t\tif (s->loginname[0] && scr_scoreboard_login_indicator.string[0]) {\n\t\t\t\tmpic_t* flag = CL_LoginFlag(s->loginflag_id);\n\t\t\t\tif (s->loginflag[0] && flag) {\n\t\t\t\t\tDraw_FitPicAlphaCenter(x - FONT_WIDTH * 0.75, y, FONT_WIDTH * 1.6, FONT_WIDTH, flag, 1.0f);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tDraw_SStringAligned(x - FONT_WIDTH * 0.75, y, scr_scoreboard_login_indicator.string, scale, alpha, proportional, text_align_center, x + FONT_WIDTH * 1.6);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (any_flags) {\n\t\t\t\tx += FONT_WIDTH;\n\t\t\t}\n\t\t\tif (s->loginname[0] && scr_scoreboard_login_names.integer) {\n\t\t\t\tif (scr_scoreboard_login_color.string[0]) {\n\t\t\t\t\tcolor.c = RGBAVECT_TO_COLOR(scr_scoreboard_login_color.color);\n\t\t\t\t\tDraw_SColoredStringAligned(x, y, s->loginname, &color, 1, scale, alpha, proportional, text_align_left, x + FONT_WIDTH * 15);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tDraw_SStringAligned(x, y, s->loginname, scale, alpha, proportional, text_align_left, x + FONT_WIDTH * 15);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SStringAligned(x, y, s->name, scale, ca_alpha, proportional, text_align_left, x + FONT_WIDTH * 15);\n\t\t\t}\n\n\t\t\ty += skip;\n\t\t\tx = startx;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// print the shirt/pants colour bars\n\t\t// use Draw_AlphaFill with additional alpha arg for wipeout deaths\n\t\tDraw_AlphaFill(cl.teamplay ? tempx - 40 : tempx, y + 4 - colors_thickness, 40, colors_thickness, Sbar_TopColorScoreboard(s), ca_alpha);\n\t\tDraw_AlphaFill(cl.teamplay ? tempx - 40 : tempx, y + 4, 40, 4, Sbar_BottomColorScoreboard(s), ca_alpha);\n\n\t\t// frags\n\t\tif (Sbar_ShowScoreboardIndicator() && k == mynum) {\n\t\t\tDraw_Character(x, y, 16);\n\t\t}\n\t\tfragsint = bound(-999, s->frags, 9999); // limit to 4 symbols int\n\t\tsnprintf(fragsstr, sizeof(fragsstr), \"%i\", fragsint);\n\t\t\n\t\tif (check_ktx_ca_wo() && scr_scoreboard_wipeout.value && ti_cl->isdead)\n\t\t{\n\t\t\tcolor.c = RGBA_TO_COLOR(85, 85, 85, 255 * ca_alpha);\t// change team/name to gray transparent text if dead in ca/wipeout\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcolor.c = RGBA_TO_COLOR(255, 255, 255, 255 * ca_alpha);\n\t\t}\n\t\t\n\t\tDraw_SColoredStringAligned(x, y, fragsstr, &color, 1, scale, alpha * ca_alpha, proportional, text_align_right, x + 4 * FONT_WIDTH);\n\t\tx += 4 * FONT_WIDTH;\n\t\tif (Sbar_ShowScoreboardIndicator() && k == mynum) {\n\t\t\tDraw_Character(x, y, 17);\n\t\t}\n\t\tx += 2 * FONT_WIDTH;\n\n\t\t// team\n\t\tif (cl.teamplay) {\n\t\t\tDraw_SColoredStringAligned(x, y, s->team, &color, 1, scale, alpha * ca_alpha, proportional, text_align_center, x + 4 * FONT_WIDTH);\n\t\t\tx += 5 * FONT_WIDTH;\n\t\t}\n\n\t\tif (s->loginname[0] && scr_scoreboard_login_indicator.string[0]) {\n\t\t\tmpic_t* flag = CL_LoginFlag(s->loginflag_id);\n\t\t\tif (s->loginflag[0] && flag) {\n\t\t\t\tDraw_FitPicAlphaCenter(x - FONT_WIDTH * 0.75, y, FONT_WIDTH * 1.6, FONT_WIDTH, flag, 1.0f * ca_alpha);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SStringAligned(x - FONT_WIDTH * 0.75, y, scr_scoreboard_login_indicator.string, scale, alpha * ca_alpha, proportional, text_align_center, x + FONT_WIDTH * 1.6);\n\t\t\t}\n\t\t}\n\t\tif (any_flags) {\n\t\t\tx += FONT_WIDTH;\n\t\t}\n\t\tif (s->loginname[0] && scr_scoreboard_login_names.integer) {\n\t\t\tif (scr_scoreboard_login_color.string[0]) {\n\t\t\t\tcolor.c = RGBAVECT_TO_COLOR(scr_scoreboard_login_color.color);\n\t\t\t\tDraw_SColoredStringAligned(x, y, s->loginname, &color, 1, scale, alpha * ca_alpha, proportional, text_align_left, x + FONT_WIDTH * 15);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_SStringAligned(x, y, s->loginname, scale, alpha * ca_alpha, proportional, text_align_left, x + FONT_WIDTH * 15);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tDraw_SColoredStringAligned(x, y, s->name, &color, 1, scale, alpha * ca_alpha, proportional, text_align_left, x + FONT_WIDTH * 15);\n\t\t}\n\n\t\tif (statswidth) {\n\t\t\tx = stats_xoffset;\n\t\t\tStats_GetBasicStats(s - cl.players, playerstats);\n\t\t\tif (stats_touches || stats_caps) {\n\t\t\t\tStats_GetFlagStats(s - cl.players, playerstats + 4);\n\t\t\t}\n\n\t\t\t// kills\n\t\t\tif (check_ktx_wo() && scr_scoreboard_wipeout.value)\n\t\t\t{\n\t\t\t\tsnprintf(num, sizeof(num), \"%d\", ti_cl->round_kills);\n\t\t\t\tcolor.c = (ti_cl->round_kills == 0 ? RGBA_TO_COLOR(255, 255, 255, 255) : RGBA_TO_COLOR(0, 187, 68, 255));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tsnprintf(num, sizeof(num), \"%5i\", playerstats[0]);\n\t\t\t\tcolor.c = (playerstats[0] == 0 ? RGBA_TO_COLOR(255, 255, 255, 255) : RGBA_TO_COLOR(0, 187, 68, 255));\n\t\t\t}\n\t\t\tDraw_SColoredStringAligned(x, y, num, &color, 1, scale, alpha, proportional, text_align_right, x + 5 * FONT_WIDTH);\n\t\t\tx += 6 * FONT_WIDTH;\n\n\t\t\t// teamkills\n\t\t\tif (stats_team) {\n\t\t\t\tsnprintf(num, sizeof(num), \"%3i\", playerstats[2]);\n\t\t\t\tcolor.c = (playerstats[2] == 0 ? RGBA_TO_COLOR(255, 255, 255, 255) : RGBA_TO_COLOR(255, 255, 0, 255));\n\t\t\t\tDraw_SColoredStringAligned(x, y, num, &color, 1, scale, alpha, proportional, text_align_right, x + 3 * FONT_WIDTH);\n\t\t\t\tx += 4 * FONT_WIDTH;\n\t\t\t}\n\n\t\t\t// deaths\n\t\t\tif (check_ktx_wo() && scr_scoreboard_wipeout.value)\n\t\t\t{\n\t\t\t\tsnprintf(num, sizeof(num), \"%d\", ti_cl->round_deaths);\n\t\t\t\tcolor.c = (ti_cl->round_deaths == 0 ? RGBA_TO_COLOR(255, 255, 255, 255) : RGBA_TO_COLOR(255, 0, 0, 255));\n\t\t\t}\n\t\t\telse{\n\t\t\t\tsnprintf(num, sizeof(num), \"%4i\", playerstats[1]);\n\t\t\t\tcolor.c = (playerstats[1] == 0 ? RGBA_TO_COLOR(255, 255, 255, 255) : RGBA_TO_COLOR(255, 0, 0, 255));\n\t\t\t}\n\t\t\tDraw_SColoredStringAligned(x, y, num, &color, 1, scale, alpha, proportional, text_align_right, x + 4 * FONT_WIDTH);\n\t\t\tx += 5 * FONT_WIDTH;\n\n\t\t\tif (stats_touches) {\n\t\t\t\t// flag touches\n\t\t\t\tif (playerstats[4] < 1) {\n\t\t\t\t\t// 0 flag touches white\n\t\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xFF, 0xFF, 0xFF);\n\t\t\t\t}\n\t\t\t\telse if (playerstats[4] < 2) {\n\t\t\t\t\t// 1 flag touches yellow\n\t\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xFF, 0x00, 0xFF);\n\t\t\t\t}\n\t\t\t\telse if (playerstats[4] < 5) {\n\t\t\t\t\t// 2-4 flag touches orange\n\t\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0x55, 0x00, 0xFF);\n\t\t\t\t}\n\t\t\t\telse if (playerstats[4] < 10) {\n\t\t\t\t\t// 5-9 flag touches pink\n\t\t\t\t\tcolor.c = RGBA_TO_COLOR(0xBB, 0x33, 0xBB, 0xFF);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// >9 flag touches green\n\t\t\t\t\tcolor.c = RGBA_TO_COLOR(0x00, 0xFF, 0x00, 0xFF);\n\t\t\t\t}\n\t\t\t\tsnprintf(num, sizeof(num), \"%4i\", playerstats[4]);\n\t\t\t\tDraw_SColoredStringAligned(x, y, num, &color, 1, scale, alpha, proportional, text_align_right, x + 4 * FONT_WIDTH);\n\t\t\t\tx += 5 * FONT_WIDTH;\n\t\t\t}\n\n\t\t\tif (stats_caps) // flag captures\n\t\t\t{\n\t\t\t\tif (playerstats[6] < 1) {\n\t\t\t\t\t// 0 caps white\n\t\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xFF, 0xFF, 0xFF);\n\t\t\t\t}\n\t\t\t\telse if (playerstats[6] < 2) {\n\t\t\t\t\t// 1 cap yellow\n\t\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xFF, 0x00, 0xFF);\n\t\t\t\t}\n\t\t\t\telse if (playerstats[6] < 5) {\n\t\t\t\t\t// 2-4 caps orange\n\t\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0x55, 0x00, 0xFF);\n\t\t\t\t}\n\t\t\t\telse if (playerstats[6] < 10) {\n\t\t\t\t\t// 5-9 caps pink\n\t\t\t\t\tcolor.c = RGBA_TO_COLOR(0xBB, 0x33, 0xBB, 0xFF);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// >9 caps green\n\t\t\t\t\tcolor.c = RGBA_TO_COLOR(0x00, 0xFF, 0x00, 0xFF);\n\t\t\t\t}\n\t\t\t\tsnprintf(num, sizeof(num), \"%4i\", playerstats[6]);\n\t\t\t\tDraw_SColoredStringAligned(x, y, num, &color, 1, scale, alpha, proportional, text_align_right, x + 4 * FONT_WIDTH);\n\t\t\t\tx += 5 * FONT_WIDTH;\n\t\t\t}\n\t\t}\n\n\t\ty += skip;\n\t\tx = startx;\n\t}\n\n\tif (!scr_scoreboard_showqtvusers.value) {\n\t\tgoto finalize;\n\t}\n\n\tfor (qu = qtvuserlist; qu && y <= SCOREBOARD_LASTROW; qu = qu->next) {\n\t\tcolor_t background;\n\t\tfloat bk_alpha;\n\t\tbyte c = 2;\n\t\tclrinfo_t color;\n\t\tca_alpha = 1.0f;\n\n\t\tif (!qu->name[0]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (is_classic) {\n\t\t\tbk_alpha = 0;\n\t\t} else if (k == mynum && scr_scoreboard_highlightself.value) {\n\t\t\tbk_alpha = 1.7 * SCOREBOARD_ALPHA;\n\t\t\tbk_alpha = min(alpha, 0.75);\n\t\t} else {\n\t\t\tbk_alpha = SCOREBOARD_ALPHA;\n\t\t}\n\n\t\tbackground = RGBA_TO_COLOR(host_basepal[c * 3], host_basepal[c * 3 + 1], host_basepal[c * 3 + 2], (byte)(bk_alpha * 255));\n\t\tDraw_AlphaFillRGB(xofs, y, rank_width, skip, background);\n\n\t\tif (!scr_scoreboard_borderless.value) {\n\t\t\tDraw_Fill(xofs - 1, y, 1, skip, 0);\n\t\t\tDraw_Fill(xofs - 1 + rank_width + 1, y, 1, skip, 0);\n\t\t}\n\n\t\tsnprintf(num, sizeof(num), \"%i\", 0);\n\n\t\tcolor.c = is_classic\n\t\t\t? RGBA_TO_COLOR(0xFF, 0xFF, 0xFF, (byte)(ca_alpha * 255))\n\t\t\t: RGBA_TO_COLOR(0xAA, 0xAA, 0xDD, (byte)(ca_alpha * 255));\n\t\tcolor.i = 0;\n\t\tDraw_SColoredStringAligned(x, y, num, &color, 1, scale, alpha * ca_alpha, proportional, text_align_right, x + FONT_WIDTH * 4);\n\t\tx += 4 * FONT_WIDTH;\n\n\t\tif (is_classic) {\n\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xFF, 0xFF, (byte)(ca_alpha * 255));\n\t\t} else {\n\t\t\tif (p == 0) {\n\t\t\t\t// 0 - white\n\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0xFF, 0xFF, (byte)(ca_alpha * 255));\n\t\t\t} else if (p < 3) {\n\t\t\t\t// 1-2 - yellow\n\t\t\t\tcolor.c = RGBA_TO_COLOR(0xCC, 0xDD, 0xDD, (byte)(ca_alpha * 255));\n\t\t\t} else if (p < 6) {\n\t\t\t\t// 3-5 orange\n\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0x55, 0x00, (byte)(ca_alpha * 255));\n\t\t\t} else {\n\t\t\t\t// 6+ - red\n\t\t\t\tcolor.c = RGBA_TO_COLOR(0xFF, 0x00, 0x00, (byte)(ca_alpha * 255));\n\t\t\t}\n\t\t}\n\n\t\tif (!is_classic) {\n\t\t\tDraw_SColoredStringAligned(x, y, num, &color, 1, scale, alpha * ca_alpha, proportional, text_align_right, x + 3 * FONT_WIDTH);\n\t\t}\n\n\t\tx += 4 * FONT_WIDTH;\n\n\t\tcolor.c = RGBA_TO_COLOR(255, 255, 255, 255);\n\n\t\tif (!is_classic) {\n\t\t\tsnprintf(myminutes, sizeof(myminutes), \"%i\", 0);\n\t\t\tDraw_SColoredStringAligned(x, y, myminutes, &color, 1, scale, alpha, proportional, text_align_right, x + 4 * FONT_WIDTH);\n\t\t}\n\n\t\tx += 5 * FONT_WIDTH;\n\n\t\tif (cl.teamplay) {\n\t\t\tDraw_SStringAligned(is_classic ? x - (8 * FONT_WIDTH) : x, y, is_classic ? \"(qtv)\" : scr_scoreboard_qtv_name.string, scale, alpha, proportional, text_align_left, x + 10 * FONT_WIDTH);\n\t\t} else {\n\t\t\tDraw_SStringAligned(is_classic ? x - (8 * FONT_WIDTH): x, y, is_classic ? \"(qtv)\" : scr_scoreboard_qtv_name.string, scale, alpha, proportional, text_align_left, x + SHORT_SPECTATOR_NAME_LEN * FONT_WIDTH);\n\t\t}\n\n\t\tx += (cl.teamplay ? 11 : 6) * FONT_WIDTH;\n\t\tDraw_SStringAligned(x, y, qu->name, scale, ca_alpha, proportional, text_align_left, x + FONT_WIDTH * 15);\n\t\ty += skip;\n\t\tx = startx;\n\t}\n\nfinalize:\n\tif (!scr_scoreboard_borderless.value) {\n\t\tDraw_Fill(xofs - 1, y - 1, rank_width + 2, 1, 0); //Border - Bottom\n\t}\n}\n\nstatic void Sbar_TeamOverlay(void)\n{\n\tint i, k, x, y, xofs, plow, phigh, pavg, rank_width, skip = 10;\n\tchar num[12], team[5];\n\tteam_t *tm;\n\tmpic_t *pic;\n\tqbool proportional = scr_scoreboard_proportional.integer;\n\tfloat lhs;\n\tint is_classic = scr_scoreboard_classic.integer;\n\n\tif (key_dest == key_console && !SCR_TakingAutoScreenshot())\n\t\treturn;\n\n#ifndef CLIENTONLY\n\t// FIXME\n\t// check number of connections instead?\n\tif (cl.gametype == GAME_COOP && com_serveractive && !coop.value)\n\t\treturn;\n#endif\n\n\tif (!cl.teamplay) {\n\t\tSbar_DeathmatchOverlay(0);\n\t\treturn;\n\t}\n\n\tif (hud_faderankings.value)\n\t\tDraw_FadeScreen(hud_faderankings.value);\n\n\tscr_copyeverything = 1;\n\tscr_fullupdate = 0;\n\n\trank_width = cl.teamplay ? RANK_WIDTH_TEAM : RANK_WIDTH_DM;\n\trank_width = bound(0, rank_width, vid.width - 16);\n\n\ty = hud_rankingpos_y.value;\n\tif (y < 0 || y > vid.height / 2) {\n\t\ty = 0;\n\t}\n\n\tif (hud_centerranking.value) {\n\t\txofs = ((vid.width - rank_width) >> 1) + hud_rankingpos_x.value;\n\t}\n\telse {\n\t\txofs = hud_rankingpos_x.value;\n\t}\n\n\tif (scr_scoreboard_drawtitle.value) {\n\t\tpic = Draw_CachePic(CACHEPIC_RANKING);\n\t\tDraw_Pic(xofs + (rank_width - pic->width) / 2, y, pic);\n\t\ty = 26 + hud_rankingpos_y.value;\n\t}\n\telse {\n\t\ty = 2 + hud_rankingpos_y.value;\n\t}\n\n\tif (!scr_scoreboard_borderless.value) {\n\t\tDraw_Fill(xofs - 1, y + 1, rank_width + 2, 1, 0);\t\t\t\t\t\t\t//Border - Top\n\t}\n\n\tif (!is_classic) {\n\t\tDraw_AlphaFill(xofs, y + 2, rank_width, skip, 1, SCOREBOARD_HEADINGALPHA); //draw heading row\n\t\tDraw_Fill(xofs, y + 11, rank_width, 1, 0); //Border - Top (under header)\n\t}\n\ty += 2;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t//dont go over the black border, move the rest down\n\tif (!scr_scoreboard_borderless.value) {\n\t\tDraw_Fill(xofs - 1, y, 1, skip, 0);\t\t\t\t\t\t\t\t\t\t//Border - Left\n\t\tDraw_Fill(xofs - 1 + rank_width + 1, y, 1, skip, 0);\t\t\t\t\t\t//Border - Right\n\t}\n\n\tx = xofs + rank_width * 0.1 + 8;\n\n\tlhs = x;\n\n\tDraw_SStringAligned(x, y, \"low\", 1, 1, proportional, text_align_right, x + 3 * FONT_WIDTH);\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y + 8, is_classic == 2 ? \"---\" : \"\\x1d\\x1e\\x1e\", 1, 1, proportional, text_align_right, x + 3 * FONT_WIDTH);\n\tx += 3 * FONT_WIDTH;\n\tDraw_SStringAligned(x, y, \"/\", 1, 1, proportional, text_align_right, x + FONT_WIDTH);\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y + 8, is_classic == 2 ? \"-\" : \"\\x1e\", 1, 1, proportional, text_align_right, x + FONT_WIDTH);\n\tx += FONT_WIDTH;\n\tDraw_SStringAligned(x, y, \"avg\", 1, 1, proportional, text_align_right, x + FONT_WIDTH * 3);\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y + 8, is_classic == 2 ? \"---\" : \"\\x1e\\x1e\\x1e\", 1, 1, proportional, text_align_right, x + FONT_WIDTH * 3);\n\tx += 3 * FONT_WIDTH;\n\tDraw_SStringAligned(x, y, \"/\", 1, 1, proportional, text_align_right, x + FONT_WIDTH);\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y + 8, is_classic == 2 ? \"-\" : \"\\x1e\", 1, 1, proportional, text_align_right, x + FONT_WIDTH);\n\tx += FONT_WIDTH;\n\tDraw_SStringAligned(x, y, \"high\", 1, 1, proportional, text_align_right, x + FONT_WIDTH * 4);\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y + 8, is_classic == 2 ? \"----\" : \"\\x1e\\x1e\\x1e\\x1f\", 1, 1, proportional, text_align_right, x + FONT_WIDTH * 4);\n\tx += 4 * FONT_WIDTH;\n\tx += FONT_WIDTH;\n\tDraw_SStringAligned(x, y, \"team\", 1, 1, proportional, text_align_center, x + FONT_WIDTH * 4);\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y + 8, is_classic == 2 ? \"----\" : \"\\x1d\\x1e\\x1e\\x1f\", 1, 1, proportional, text_align_center, x + FONT_WIDTH * 4);\n\tx += 4 * FONT_WIDTH;\n\tx += FONT_WIDTH;\n\tDraw_SStringAligned(x, y, (cl.scoring_system == SCORING_SYSTEM_TEAMFRAGS ? \"score\" : \"total\"), 1, 1, proportional, text_align_right, x + FONT_WIDTH * 5);\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y + 8, is_classic == 2 ? \"-----\" : \"\\x1d\\x1e\\x1e\\x1e\\x1f\", 1, 1, proportional, text_align_right, x + FONT_WIDTH * 5);\n\tx += 5 * FONT_WIDTH;\n\tx += FONT_WIDTH;\n\tif ((cl.teamfortress || scr_scoreboard_showflagstats.value) && Stats_IsFlagsParsed() && !is_classic) {\n\t\tDraw_SStringAligned(x, y, \"caps\", 1, 1, proportional, text_align_center, x + FONT_WIDTH * 4);\n\t\tx += 4 * FONT_WIDTH;\n\t\tx += FONT_WIDTH;\n\t}\n\n\tDraw_SStringAligned(x, y, \"players\", 1, 1, proportional, text_align_left, x + FONT_WIDTH * 7);\n\tif (is_classic)\n\t\tDraw_SStringAligned(x, y + 8, is_classic == 2 ? \"-------\" : \"\\x1d\\x1e\\x1e\\x1e\\x1e\\x1e\\x1f\", 1, 1, proportional, text_align_left, x + FONT_WIDTH * 7);\n\tx = lhs;\n\n\t// If scr_scoreboard_classic is true, we've added an extra line for the\n\t// separator, which must be accounted for.\n\ty += is_classic ? 18 : 10;\n\n\tSbar_SortTeams();\t\t// sort the teams\n\n\tfor (i = 0; i < scoreboardteams && y <= SCOREBOARD_LASTROW; i++) {\n\t\tk = teamsort[i];\n\t\ttm = teams + k;\n\t\tx = lhs;\n\n\t\tif (!is_classic)\n\t\t\tDraw_AlphaFill(xofs, y, rank_width, skip, 2, SCOREBOARD_ALPHA);\n\n\t\tif (!scr_scoreboard_borderless.value) {\n\t\t\tDraw_Fill(xofs - 1, y, 1, skip, 0);\t\t\t\t\t\t//Border - Left\n\t\t\tDraw_Fill(xofs - 1 + rank_width + 1, y, 1, skip, 0);\t\t//Border - Right\n\t\t}\n\n\t\t// draw pings\n\t\tplow = tm->plow;\n\t\tif (plow < 0 || plow > 999) {\n\t\t\tplow = 999;\n\t\t}\n\n\t\tphigh = tm->phigh;\n\t\tif (phigh < 0 || phigh > 999) {\n\t\t\tphigh = 999;\n\t\t}\n\n\t\tpavg = tm->players ? tm->ptotal / tm->players : 999;\n\t\tif (pavg < 0 || pavg > 999) {\n\t\t\tpavg = 999;\n\t\t}\n\n\t\tsnprintf(num, sizeof(num), \"%3i\", plow);\n\t\tDraw_SStringAligned(x, y, num, 1, 1, proportional, text_align_right, x + 3 * FONT_WIDTH);\n\t\tx += 3 * FONT_WIDTH;\n\t\tDraw_SStringAligned(x, y, \"/\", 1, 1, proportional, text_align_right, x + FONT_WIDTH);\n\t\tx += FONT_WIDTH;\n\t\tsnprintf(num, sizeof(num), \"%3i\", pavg);\n\t\tDraw_SStringAligned(x, y, num, 1, 1, proportional, text_align_right, x + 3 * FONT_WIDTH);\n\t\tx += 3 * FONT_WIDTH;\n\t\tDraw_SStringAligned(x, y, \"/\", 1, 1, proportional, text_align_right, x + FONT_WIDTH);\n\t\tx += FONT_WIDTH;\n\t\tsnprintf(num, sizeof(num), \"%3i\", phigh);\n\t\tDraw_SStringAligned(x, y, num, 1, 1, proportional, text_align_right, x + 3 * FONT_WIDTH);\n\t\tx += 3 * FONT_WIDTH;\n\t\tx += FONT_WIDTH;\n\n\t\t// draw team\n\t\tif (Sbar_ShowScoreboardIndicator() && tm->myteam) {\n\t\t\tDraw_SStringAligned(x, y, \"\\020\", 1, 1, proportional, text_align_right, x + FONT_WIDTH);\n\t\t}\n\t\tx += FONT_WIDTH;\n\t\tstrlcpy(team, tm->team, sizeof(team));\n\t\tDraw_SStringAligned(x, y, team, 1, 1, proportional, text_align_center, x + 4 * FONT_WIDTH);\n\t\tx += 4 * FONT_WIDTH;\n\t\tif (Sbar_ShowScoreboardIndicator() && tm->myteam) {\n\t\t\tDraw_SStringAligned(x, y, \"\\021\", 1, 1, proportional, text_align_left, x + FONT_WIDTH);\n\t\t}\n\t\tx += FONT_WIDTH;\n\n\t\t// draw total\n\t\tsnprintf(num, sizeof(num), \"%5i\", tm->frags);\n\t\tDraw_SStringAligned(x, y, num, 1, 1, proportional, text_align_right, x + 5 * FONT_WIDTH);\n\t\tx += 5 * FONT_WIDTH;\n\t\tx += FONT_WIDTH;\n\n\t\tif ((cl.teamfortress || scr_scoreboard_showflagstats.value) && Stats_IsFlagsParsed()) {\n\t\t\tsnprintf(num, sizeof(num), \"%4i\", tm->caps);\n\t\t\tDraw_SStringAligned(x, y, num, 1, 1, proportional, text_align_right, x + 4 * FONT_WIDTH);\n\t\t\tx += 4 * FONT_WIDTH;\n\t\t\tx += FONT_WIDTH;\n\t\t}\n\n\t\t// draw players\n\t\tsnprintf(num, sizeof(num), \"%7i\", tm->players);\n\t\tDraw_SStringAligned(x, y, num, 1, 1, proportional, text_align_left, x + 7 * FONT_WIDTH);\n\n\t\ty += skip;\n\t}\n\n\tif (!scr_scoreboard_borderless.value) {\n\t\tDraw_Fill(xofs - 1, y - 1, rank_width + 2, 1, 0);              //Border - Bottom\n\t}\n\n\ty += 14;\n\tSbar_DeathmatchOverlay(y);\n}\n\n\nstatic void Sbar_MiniDeathmatchOverlay (void) {\n\tint i, k, x, y, mynum, numlines;\n\tchar num[4 + 1], name[16 + 1], team[4 + 1];\n\tplayer_info_t *s;\n\tteam_t *tm;\n\n\tif (!scr_drawVFrags.value)\n\t\treturn;\n\n\tif (!sb_lines)\n\t\treturn; // not enuff room\n\n\tscr_copyeverything = 1;\n\tscr_fullupdate = 0;\n\n\tmynum = Sbar_PlayerNum();\n\n\tx = 324;\n\n\n\tif (vid.width < 640 && cl.teamplay && scr_drawVFrags.value == 2)\n\t\tgoto drawteams;\n\n\t// scores\n\tSbar_SortFrags (false);\n\n\tif (!scoreboardlines)\n\t\treturn; // no one there?\n\n\t// draw the text\n\ty = vid.height - sb_lines - 1;\n\tnumlines = sb_lines / 8;\n\tif (numlines < 3)\n\t\treturn; // not enough room\n\n\t// find us\n\tfor (i = 0; i < scoreboardlines; i++)\n\t\tif (fragsort[i] == mynum)\n\t\t\tbreak;\n\n\tif (i == scoreboardlines)\t// we're not there, we are probably a spectator, just display top\n\t\ti = 0;\n\telse\t\t\t\t\t\t// figure out start\n\t\ti = i - numlines / 2;\n\n\ti = bound(0, i, scoreboardlines - numlines);\n\n\tfor ( ; i < scoreboardlines && y < vid.height - 8 + 1; i++) {\n\t\tk = fragsort[i];\n\t\ts = &cl.players[k];\n\n\t\tif (!s->name[0]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tDraw_Fill(x, y + 1, 40, 3, Sbar_TopColorScoreboard(s));\n\t\tDraw_Fill(x, y + 4, 40, 4, Sbar_BottomColorScoreboard(s));\n\n\t\t// draw number\n\t\tsnprintf (num, sizeof(num), \"%3i\", bound(-99, s->frags, 999));\n\n\t\tDraw_Character (x + 8 , y, num[0]);\n\t\tDraw_Character (x + 16, y, num[1]);\n\t\tDraw_Character (x + 24, y, num[2]);\n\n\t\tif (Sbar_ShowScoreboardIndicator()) {\n\t\t\tif (k == mynum) {\n\t\t\t\tDraw_Character (x, y, 16);\n\t\t\t\tDraw_Character (x + 32, y, 17);\n\t\t\t}\n\t\t}\n\n\t\t// team\n\t\tif (cl.teamplay) {\n\t\t\tstrlcpy (team, s->team, sizeof(team));\n\t\t\tDraw_String (x + 48, y, team);\n\t\t}\n\n\t\t// draw name\n\t\tstrlcpy (name, s->name, sizeof(name));\n\t\tif (cl.teamplay) {\n\t\t\tDraw_String(x + 48 + 40, y, name);\n\t\t}\n\t\telse {\n\t\t\tDraw_String(x + 48, y, name);\n\t\t}\n\n\t\ty += 8;\n\t}\n\n\t// draw teams if room\n\tif (vid.width < 640 || !cl.teamplay) {\n\t\treturn;\n\t}\n\n\tSbar_SortTeams();\n\tif (!scoreboardteams) {\n\t\treturn;\n\t}\n\n\t// draw seperator\n\tx += 208;\n\tfor (y = vid.height - sb_lines; y < vid.height - 6; y += 2) {\n\t\tDraw_Character(x, y, 14);\n\t}\n\n\tx += 16;\n\ndrawteams:\n\tSbar_SortTeams();\n\n\ty = vid.height - sb_lines;\n\tfor (i = 0; i < scoreboardteams && y < vid.height - 8 + 1; i++) {\n\t\tk = teamsort[i];\n\t\ttm = teams + k;\n\n\t\t//draw name\n\t\tstrlcpy(team, tm->team, sizeof(team));\n\t\tDraw_String(x, y, team);\n\n\t\t// draw total\n\t\tDraw_Fill(x + 40, y + 1, 48, 3, Sbar_ColorForMap(tm->topcolor));\n\t\tDraw_Fill(x + 40, y + 4, 48, 4, Sbar_ColorForMap(tm->bottomcolor));\n\t\tsnprintf(num, sizeof(num), \"%4i\", bound(-999, tm->frags, 9999));\n\t\tDraw_Character(x + 40 + 8, y, num[0]);\n\t\tDraw_Character(x + 40 + 16, y, num[1]);\n\t\tDraw_Character(x + 40 + 24, y, num[2]);\n\t\tDraw_Character(x + 40 + 32, y, num[3]);\n\n\t\tif (tm->myteam) {\n\t\t\tDraw_Character(x + 40, y, 16);\n\t\t\tDraw_Character(x + 40 + 40, y, 17);\n\t\t}\n\n\t\ty += 8;\n\t}\n}\n\nstatic void Sbar_IntermissionNumber (int x, int y, int num, int digits, int color, int rightadjustify) {\n\tchar str[12], *ptr;\n\tint l, frame;\n\n\tl = Sbar_itoa (num, str);\n\tptr = str;\n\tif (l > digits)\n\t\tptr += (l - digits);\n\tif (rightadjustify && l < digits)\n\t\tx += (digits - l) * 24;\n\n\twhile (*ptr) {\n\t\tframe = (*ptr == '-') ? STAT_MINUS : *ptr -'0';\n\t\tDraw_TransPic (x, y, sb_nums[color][frame]);\n\t\tx += 24;\n\t\tptr++;\n\t}\n}\n\nvoid Sbar_IntermissionOverlay (void) {\n\tmpic_t *pic;\n\tfloat time;\n\tint\tdig, num, xofs;\n\n\tscr_copyeverything = 1;\n\tscr_fullupdate = 0;\n\n\tif (cl.gametype == GAME_DEATHMATCH)\t{\n\t\tif (cl.teamplay && !sb_showscores)\n\t\t\tSbar_TeamOverlay ();\n\t\telse\n\t\t\tSbar_DeathmatchOverlay (0);\n\t\treturn;\n\t}\n\n\txofs = (vid.width - 320 ) >> 1;\n\n\tpic = Draw_CachePic(CACHEPIC_COMPLETE);\n\tDraw_Pic (xofs + 64, 24, pic);\n\n\tpic = Draw_CachePic(CACHEPIC_INTER);\n\tDraw_TransPic (xofs, 56, pic);\n\n\tif (cl.servertime_works)\n\t\ttime = cl.solo_completed_time;\t// we know the exact time\n\telse\n\t\ttime = cl.completed_time - cl.players[cl.playernum].entertime;\t// use an estimate\n\n\t//time\n\tdig = time / 60;\n\tdig = max(dig, 0);\n\tSbar_IntermissionNumber (xofs + 152, 64, dig, 3, 0, true);\n\tnum = time - dig * 60;\n\tDraw_TransPic (xofs + 224, 64, sb_colon);\n\tDraw_TransPic (xofs + 248, 64, sb_nums[0][num / 10]);\n\tDraw_TransPic (xofs + 272, 64, sb_nums[0][num % 10]);\n\n\t//secrets\n\tSbar_IntermissionNumber (xofs + 152, 104, cl.stats[STAT_SECRETS], 3, 0, true);\n\tDraw_TransPic (xofs + 224, 104, sb_slash);\n\tSbar_IntermissionNumber (xofs + 248, 104, cl.stats[STAT_TOTALSECRETS], 3, 0, false);\n\n\t//monsters\n\tSbar_IntermissionNumber (xofs + 152, 144, cl.stats[STAT_MONSTERS], 3, 0, true);\n\tDraw_TransPic (xofs + 224, 144, sb_slash);\n\tSbar_IntermissionNumber (xofs + 248, 144, cl.stats[STAT_TOTALMONSTERS], 3, 0, false);\n}\n\nvoid Sbar_FinaleOverlay (void) {\n\tmpic_t *pic;\n\n\tscr_copyeverything = 1;\n\n\tpic = Draw_CachePic (CACHEPIC_FINALE);\n\tDraw_TransPic ( (vid.width-pic->width)/2, 16, pic);\n}\n\n/********************************* INTERFACE *********************************/\nvoid Sbar_Draw(void) {\n\tqbool headsup;\n\textern cvar_t scr_newHud;\n\n\theadsup = !Sbar_IsStandardBar();\n\tif (sb_updates >= vid.numpages && !headsup)\n\t{\n\t\t// Always show who we're tracking\n\t\tSbar_DrawTrackingString();\n\n\t\treturn;\n\t}\n\n\tscr_copyeverything = 1;\n\n\tsb_updates++;\n\n\tif (scr_centerSbar.value)\n\t\tsbar_xofs = (vid.width - 320) >> 1;\n\telse\n\t\tsbar_xofs = 0;\n\n\t// Top line. Do not show with +showscores\n\tif (sb_lines > 24 && scr_newHud.value != 1 && !sb_showscores && !sb_showteamscores) \n\t{ \n\t\tif (!cl.spectator || cl.autocam == CAM_TRACK)\n\t\t\tSbar_DrawInventory();\n\n\t\tif (cl.gametype == GAME_DEATHMATCH && (!headsup || vid.width < 512 || (vid.width >= 512 && scr_centerSbar.value )))\n\t\t\tSbar_DrawFrags();\n\t}\n\n\t// main area\n\tif (sb_lines > 0 && scr_newHud.value != 1) {  // HUD -> hexum\n\t\tif (cl.spectator) {\n\t\t\tif (cl.autocam != CAM_TRACK) {\n\t\t\t\tSbar_DrawSpectatorMessage();\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (sb_showscores || sb_showteamscores || cl.stats[STAT_HEALTH] <= 0)\n\t\t\t\t\tSbar_SoloScoreboard ();\n\n\t\t\t\telse if (scr_compactHud.value == 4)\n\t\t\t\t\tSbar_DrawCompact_WithIcons();\n\t\t\t\telse if (scr_compactHud.value == 3)\n\t\t\t\t\tSbar_DrawCompact_Bare();\n\t\t\t\telse if (scr_compactHud.value == 2)\n\t\t\t\t\tSbar_DrawCompact_TF();\n\t\t\t\telse if (scr_compactHud.value == 1)\n\t\t\t\t\tSbar_DrawCompact();\n\t\t\t\telse\n\t\t\t\t\tSbar_DrawNormal();\n\n\t\t\t\tSbar_DrawTrackingString();\n\t\t\t}\n\t\t} else if (sb_showscores || sb_showteamscores || cl.stats[STAT_HEALTH] <= 0) {\n\t\t\tSbar_SoloScoreboard();\n\t\t} else if (scr_compactHud.value == 4) {\n\t\t\tSbar_DrawCompact_WithIcons();\n\t\t} else if (scr_compactHud.value == 3) {\n\t\t\tSbar_DrawCompact_Bare();\n\t\t} else if (scr_compactHud.value == 2) {\n\t\t\tSbar_DrawCompact_TF();\n\t\t} else if (scr_compactHud.value == 1) {\n\t\t\tSbar_DrawCompact();\n\t\t} else {\n\t\t\tSbar_DrawNormal();\n\t\t}\n\t}\n\tif (sb_lines > 0 && scr_newHud.value == 1 && cl.deathmatch == 0\n\t\t\t&& (sb_showscores || sb_showteamscores || cl.stats[STAT_HEALTH] <= 0)) {\n\t\tSbar_SoloScoreboard();\n\t}\n\n\t//VULT STAT LOSS\n\tif (amf_stat_loss.value && cl.stats[STAT_HEALTH] <= 0)\n\t{\n\t\tAmf_Reset_DamageStats();\n\t}\n\n\t// main screen deathmatch rankings\n\t// if we're dead show team scores in team games\n\tif (cl.stats[STAT_HEALTH] <= 0 && !cl.spectator) {\n\t\tif (cl.teamplay && !sb_showscores)\n\t\t\tSbar_TeamOverlay();\n\t\telse\n\t\t\tSbar_DeathmatchOverlay (0);\n\t} else if (sb_showscores) {\n\t\tSbar_DeathmatchOverlay (0);\n\t} else if (sb_showteamscores) {\n\t\tSbar_TeamOverlay();\n\t}\n\n\tif (sb_showscores || sb_showteamscores || cl.stats[STAT_HEALTH] <= 0)\n\t\tsb_updates = 0;\n\n\tif (scr_newHud.value == 1) // HUD -> hexum\n\t\treturn;\n\n\t// clear unused areas in GL\n\tif (vid.width > 320 && !headsup) {\n\t\tif (scr_centerSbar.value)\t// left\n\t\t\tDraw_TileClear (0, vid.height - sb_lines, sbar_xofs, sb_lines);\n\t\tDraw_TileClear (320 + sbar_xofs, vid.height - sb_lines, vid.width - (320 + sbar_xofs), sb_lines);\t// right\n\t}\n\tif (!headsup && cl.spectator && cl.autocam != CAM_TRACK && sb_lines > SBAR_HEIGHT)\n\t\tDraw_TileClear (sbar_xofs, vid.height - sb_lines, 320, sb_lines - SBAR_HEIGHT);\n\n\tif (vid.width >= 512 && sb_lines > 0 \n\t\t\t&& cl.gametype == GAME_DEATHMATCH && !scr_centerSbar.value \n\t\t\t&& MULTIVIEWTHISPOV())\n\t{\n\t\tSbar_MiniDeathmatchOverlay ();\n\t}\n}\n\nint CL_LoginImageId(const char* name)\n{\n\tint index = -1;\n\tint i;\n\n\tif (name[0]) {\n\t\tfor (i = 0; i < login_image_data.image_count; ++i) {\n\t\t\tif (!strcasecmp(name, login_image_data.images[i].name)) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t}\n\treturn index;\n}\n\nint CL_LoginImageBot(void)\n{\n\treturn login_image_data.bot_image_index;\n}\n\nqbool CL_LoginImageLoad(const char* path)\n{\n\tjson_error_t error;\n\tchar truepath[MAX_OSPATH];\n\tjson_t* json;\n\tloginimage_t* new_login_images;\n\tsize_t new_login_image_count = 0;\n\tint new_login_bot_image = -1;\n\tint i, tex_width, tex_height, max_width = 0, max_height = 0;\n\tjson_t* val;\n\n\tif (!path[0]) {\n\t\tQ_free(login_image_data.images);\n\t\tlogin_image_data.image_count = 0;\n\t\tlogin_image_data.bot_image_index = -1;\n\t\treturn true;\n\t}\n\n\tstrlcpy(truepath, \"textures/scoreboard/\", sizeof(truepath));\n\tstrlcat(truepath, path, sizeof(truepath));\n\tCOM_ForceExtensionEx(truepath, \".json\", sizeof(truepath));\n\t{\n\t\tint json_len;\n\t\tchar* json_bytes = (char*)FS_LoadHeapFile(truepath, &json_len);\n\t\tif (!json_bytes) {\n\t\t\tCon_Printf(\"Unable to load %s\\n\", truepath);\n\t\t\treturn false;\n\t\t}\n\n\t\tjson = json_loadb(json_bytes, json_len, 0, &error);\n\t\tQ_free(json_bytes);\n\t}\n\tif (!json || !json_is_array(json)) {\n\t\tCon_Printf(\"Invalid json file %s\\n\", truepath);\n\t\tif (json) {\n\t\t\tjson_decref(json);\n\t\t}\n\t\treturn false;\n\t}\n\n\tnew_login_image_count = json_array_size(json);\n\tnew_login_images = Q_malloc(sizeof(new_login_images[0]) * new_login_image_count);\n\tjson_array_foreach(json, i, val) {\n\t\tconst char* code = JSON_readstring(json_object_get(val, \"code\"));\n\t\tconst char* path = JSON_readstring(json_object_get(val, \"file\"));\n\t\tint x = JSON_readint(json_object_get(val, \"x\"));\n\t\tint y = JSON_readint(json_object_get(val, \"y\"));\n\t\tint width = JSON_readint(json_object_get(val, \"width\"));\n\t\tint height = JSON_readint(json_object_get(val, \"height\"));\n\t\ttexture_ref texture;\n\n\t\tstrlcpy(truepath, \"textures/scoreboard/\", sizeof(truepath));\n\t\tstrlcat(truepath, path, sizeof(truepath));\n\t\tCOM_StripExtension(truepath, truepath, sizeof(truepath));\n\n\t\tif (code == NULL || !code[0]) {\n\t\t\tQ_free(new_login_images);\n\t\t\tCon_Printf(\"Failed to load screenshot flags: json error on element#%d\\n\", i);\n\t\t\treturn false;\n\t\t}\n\n\t\ttexture = R_LoadTextureImage(truepath, truepath, 0, 0, TEX_ALPHA | TEX_PREMUL_ALPHA | TEX_NOSCALE);\n\t\tif (!R_TextureReferenceIsValid(texture)) {\n\t\t\tCon_Printf(\"Unable to load %s\\n\", truepath);\n\t\t\treturn false;\n\t\t}\n\t\ttex_width = R_TextureWidth(texture);\n\t\ttex_height = R_TextureHeight(texture);\n\n\t\tx = max(x, 0);\n\t\ty = max(y, 0);\n\t\twidth = (width < 0 ? tex_width : width);\n\t\theight = (height < 0 ? tex_height : height);\n\n\t\twidth = min(width, tex_width - x);\n\t\theight = min(height, tex_height - y);\n\n\t\tstrlcpy(new_login_images[i].name, code, sizeof(new_login_images[i].name));\n\t\tnew_login_images[i].pic.width = width;\n\t\tnew_login_images[i].pic.height = height;\n\t\tnew_login_images[i].pic.texnum = texture;\n\t\tnew_login_images[i].pic.sl = (1.0f * x) / tex_width;\n\t\tnew_login_images[i].pic.tl = (1.0f * y) / tex_height;\n\t\tnew_login_images[i].pic.sh = (1.0f * width) / tex_width;\n\t\tnew_login_images[i].pic.th = (1.0f * height) / tex_height;\n\n\t\tmax_width = max(width, max_width);\n\t\tmax_height = max(height, max_height);\n\t}\n\n\tlogin_image_data.images = new_login_images;\n\tlogin_image_data.image_count = new_login_image_count;\n\tlogin_image_data.bot_image_index = new_login_bot_image;\n\tlogin_image_data.max_width = max_width;\n\tlogin_image_data.max_width = max_height;\n\n\tfor (i = 0; i < sizeof(cl.players) / sizeof(cl.players[0]); ++i) {\n\t\tcl.players[i].loginflag_id = CL_LoginImageId(cl.players[i].loginflag);\n\t}\n\n\tjson_decref(json);\n\treturn true;\n}\n\nstatic void OnChange_scr_scoreboard_login_flagfile(cvar_t* cv, char* newvalue, qbool* cancel)\n{\n\t*cancel = CL_LoginImageLoad(newvalue);\n}\n\nstatic void OnChange_scr_scoreboard_showqtvusers(cvar_t *old_value, char *new_value, qbool *cancel)\n{\n\tqbool is_set = (new_value && *new_value == '1');\n\tInfo_SetValueForKey (cls.userinfo, \"qul\", is_set ? \"1\" : \"0\", MAX_INFO_STRING);\n\n\tif (cls.state >= ca_connected)\n\t{\n\t\tMSG_WriteByte(&cls.netchan.message, clc_stringcmd);\n\t\tSZ_Print(&cls.netchan.message, va(\"setinfo \\\"qul\\\" \\\"%s\\\"\", is_set ? \"1\" : \"0\"));\n\t}\n\n\tif (!is_set)\n\t{\n\t\tQTV_FreeUserList();\n\t}\n}\n\nstatic mpic_t* CL_LoginFlag(int id)\n{\n\tif (id < 0 || id >= login_image_data.image_count) {\n\t\treturn NULL;\n\t}\n\treturn &login_image_data.images[id].pic;\n}\n\nstatic int JSON_readint(json_t* json)\n{\n\tif (json_is_integer(json)) {\n\t\treturn (int)json_integer_value(json);\n\t}\n\treturn -1;\n}\n\nstatic const char* JSON_readstring(json_t* json)\n{\n\tif (json_is_string(json)) {\n\t\treturn json_string_value(json);\n\t}\n\treturn \"\";\n}\n"
  },
  {
    "path": "src/sbar.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n// the status bar is only redrawn if something has changed, but if anything\n// does, the entire thing will be redrawn for the next vid.numpages frames.\n\n#ifndef EZQUAKE_SBAR_HEADER\n#define EZQUAKE_SBAR_HEADER\n\n#define\tSBAR_HEIGHT\t\t24\n\nextern\tint\t\t\tsb_lines;\t\t\t// scan lines to draw\n\nvoid Sbar_Init (void);\n\nvoid Sbar_Changed (void);\n// call whenever any of the client stats represented on the sbar changes\n\nvoid Sbar_Draw (void);\n// called every frame by screen\n\nvoid Sbar_IntermissionOverlay (void);\n// called each frame after the level has been completed\n\nvoid Sbar_FinaleOverlay (void);\n\nint Sbar_TopColor(player_info_t *player);\nint Sbar_BottomColor(player_info_t *player);\n\nvoid Sbar_DrawNum (int x, int y, int num, int digits, int color);\n\n// covert quake pallete color number to rgb color\nint Sbar_ColorForMap (int m);\n\n#define STAT_MINUS\t\t10\t// num frame for '-' stats digit\n\nvoid Sbar_DrawPic(int x, int y, mpic_t *pic);\nvoid Sbar_DrawString(int x, int y, char *str);\nqbool Sbar_IsStandardBar(void);\n\nvoid Sbar_DrawTrackingString(void);\nvoid Sbar_DrawSpectatorMessage(void);\n\n#endif // #ifdef EZQUAKE_SBAR_HEADER\n"
  },
  {
    "path": "src/screen.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n/**\n  $Id: screen.h,v 1.10 2007-09-26 21:51:33 tonik Exp $\n  \n  Common declarations for modules associated with drawing on screen\n  cl_screen.c, sbar.c\n*/\n\n#ifndef EZQUAKE_SCREEN_HEADER\n#define EZQUAKE_SCREEN_HEADER\n\n#define\t\tSCR_NEED_CONSOLE_BACKGROUND\t\t(cls.state < ca_active && !cl.intermission)\n\nvoid SCR_Init (void);\n\n#define UPDATESCREEN_MULTIVIEW   1\n#define UPDATESCREEN_POSTPROCESS 2\n#define UPDATESCREEN_3D_ONLY     4\n#define UPDATESCREEN_2D_ONLY     8\n\nqbool SCR_UpdateScreen (void);\nqbool SCR_UpdateScreenPrePlayerView (void);\nvoid SCR_UpdateScreenPlayerView(int flags);\nvoid SCR_UpdateScreenHudOnly(void);\nvoid SCR_UpdateScreenPostPlayerView (void);\n\nvoid SCR_UpdateWholeScreen (void);\nvoid SCR_AutoScreenshot(char *matchname);\nqbool SCR_TakingAutoScreenshot(void);\n\nvoid SCR_CenterPrint(const char *str);\nvoid SCR_CenterPrint_Clear(void);\nvoid SCR_CenterPrint_Init(void);\nvoid SCR_CenterString_Draw(void);\n\nextern\tfloat\t\tscr_con_current;\nextern\tfloat\t\tscr_conlines;\t\t// lines of console to display\n\nextern\tint\t\t\tscr_fullupdate;\t// set to 0 to force full redraw\nextern\tint\t\t\tsb_lines;\t// sbar.c !!\n\nextern\tint\t\t\tclearnotify;\t// set to 0 whenever notify text is drawn\nextern\tqbool\t\tscr_disabled_for_loading;\n\nextern\tcvar_t\t\tscr_viewsize;\n\n// only the refresh window will be updated unless these variables are flagged \nextern\tint\t\t\tscr_copytop;\nextern\tint\t\t\tscr_copyeverything;\n\nextern\tqbool\t\tscr_skipupdate;\nextern\tqbool\t\tblock_drawing;\n\n// QW262 HUD\ntypedef char* (*Hud_Func)(void);\n\n#define\t\tHUD_CVAR\t\t1\n#define\t\tHUD_FUNC\t\t2\n#define\t\tHUD_STRING\t\t4\n#define\t\tHUD_BLINK_F\t\t8\n#define\t\tHUD_BLINK_B\t\t16\n#define\t\tHUD_IMAGE\t\t32\n#define\t\tHUD_ENABLED\t\t512\n\ntypedef struct hud_element_s {\n\tstruct hud_element_s*\tnext;\n\tchar\t\t\t\t\t*name;\n\tunsigned\t\t\t\tflags;\n\tsigned char\t\t\t\tcoords[4]; // pos_type, x, y, bg\n\tunsigned\t\t\t\twidth;\n\tfloat\t\t\t\t\tblink;\n\tvoid*\t\t\t\t\tcontents;\n\t//int\t\t\t\t\tcharset;\n\tfloat\t\t\t\t\talpha;\n\tchar\t\t\t\t\t*f_hover, *f_button;\n\tunsigned\t\t\t\tscr_width, scr_height;\n} hud_element_t;\n\nvoid Hud_262Init (void);\nvoid Hud_262LoadOnFirstStart(void);\nvoid Hud_262CatchStringsOnLoad(char *line);\n\nqbool Hud_ElementExists(const char* name);\n\n// Flash & Conc for TF\nextern qbool\tconcussioned;\nextern qbool flashed;\n\n#define\tELEMENT_X_COORD(var)\t((var##_x.value < 0) ? vid.width - strlen(str) * 8 + 8 * var##_x.value: 8 * var##_x.value)\n#define\tELEMENT_Y_COORD(var)\t((var##_y.value < 0) ? vid.height - sb_lines + 8 * var##_y.value : 8 * var##_y.value)\n\nvoid SCR_OnChangeMVHudPos(cvar_t *var, char *newval, qbool *cancel);\nvoid SCR_TileClear(void);\nvoid SCR_RestoreAutoID(void);\nvoid SCR_SaveAutoID(void);\nvoid SCR_SetupAutoID(void);\nvoid SCR_RegisterAutoIDCvars(void);\nvoid SCR_DrawAutoID(void);\nvoid SCR_DrawAntilagIndicators(void);\nvoid SShot_RegisterCvars(void);\nqbool SCR_TakingAutoScreenshot(void);\nvoid SCR_CheckAutoScreenshot(void);\n\n// the current position of the mouse pointer\nextern double cursor_x, cursor_y;\n\n// kazik, HUD, incremented every screen update, never reset\nextern  int         host_screenupdatecount;\n\n// scr_teaminfo\n\n#define TEAMINFO_SHOWSELF() ((scr_teaminfo.integer == 1) && (scr_teaminfo_show_self.integer >= 1 && cls.mvdplayback))\n#define TEAMINFO_NICKLEN 5\n#define FONTWIDTH (8)\n\n/*do not show player if no info about him during this time, affect us if we lagging too*/\n#define TI_TIMEOUT (5)\n\ntypedef struct ti_player_s {\n\n\tint \t\tclient;\n\n\tvec3_t\t\torg;\n\tint\t\t\titems;\n\tint\t\t\thealth;\n\tint\t\t\tarmor;\n\tint\t\t\tshells;\n\tint\t\t\tnails;\n\tint\t\t\trockets;\n\tint \t\tcells;\n\tint\t\t\tcamode;\n\tint\t\t\tisdead;\n\tint\t\t\ttimetospawn;\n\tint\t\t\tround_kills;\n\tint\t\t\tround_deaths;\n\tchar\t\tnick[TEAMINFO_NICKLEN]; // yeah, nick is nick, must be short\n\tdouble\t\ttime; // when we recive last update about this player, so we can guess disconnects and etc\n\n} ti_player_t;\n\nchar *SCR_GetWeaponShortNameByFlag (int flag);\n\n//Must be called whenever vid changes\nvoid SCR_CalcRefdef(void);\nvoid SCR_DrawMultiviewIndividualElements(void);\n\n// end - scr_teaminfo\n\n#endif // EZQUAKE_SCREEN_HEADER\n"
  },
  {
    "path": "src/server.h",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n// server.h\n#ifndef __SERVER_H__\n#define __SERVER_H__\n\n#include \"progs.h\"\n#ifdef USE_PR2\n#include \"vm.h\"\n#include \"pr2.h\"\n#include \"g_public.h\"\n#endif\n#ifndef SERVERONLY\n#include \"qtv.h\"\n#endif\n\n#define CHAT_ICON_EXPERIMENTAL 1\n\n#define\tMAX_MASTERS 8 // max recipients for heartbeat packets\n\n#define\tMAX_SIGNON_BUFFERS 16\n\n// sv_specprint stuff\n#define SPECPRINT_CENTERPRINT\t0x1\n#define SPECPRINT_SPRINT\t0x2\n#define SPECPRINT_STUFFCMD\t0x4\n\ntypedef enum {\n\tss_dead,\t// no map loaded\n\tss_loading,\t// spawning level edicts\n\tss_active\t// actively running\n} server_state_t;\n// some qc commands are only valid before the server has finished\n// initializing (precache commands, static sounds / objects, etc)\n\ntypedef struct packet_s\n{\n\tdouble\t\ttime;\n\tsizebuf_t\tmsg;\n\tbyte\t\tbuf[MSG_BUF_SIZE]; // ?MAX_MSGLEN?\n\tstruct packet_s *next;\n} packet_t;\n\n#define MAX_DELAYED_PACKETS 1024 // maxclients 32 * 77fps * max minping 0.3 = 739.2\n#define MAP_NAME_LEN 64\ntypedef struct\n{\n\tserver_state_t\tstate;\t\t\t\t// precache commands are only valid during load\n\n\tdouble\t\ttime;\n\tdouble\t\told_time;\t\t\t// bumped by SV_Physics\n\tdouble      old_bot_time;       // bumped by SV_RunBots\n\n\tdouble\t\tphysicstime;\t\t// last time physics was run\n\n\tint\t\t\tlastcheck;\t\t\t// used by PF_checkclient\n\tdouble\t\tlastchecktime;\t\t\t// for monster ai\n\n\tqbool\t\tpaused;\t\t\t\t// are we paused?\n\tdouble\t\tpausedsince;\t\t// Sys_DoubleTime() when pause started\n\n\tqbool\t\tloadgame;\t\t\t// handle connections specially\n\n\t//check player/eyes models for hacks\n\tunsigned\tmodel_player_checksum;\n\tunsigned\tmodel_newplayer_checksum;\n\tunsigned\teyes_player_checksum;\n\n\tchar\t\tmapname[MAP_NAME_LEN];\t\t// map name\n\tchar\t\tmodelname[MAX_QPATH];\t\t// maps/<name>.bsp, for model_precache[0]\n\tunsigned\tmap_checksum;\n\tunsigned\tmap_checksum2;\n\tcmodel_t\t*worldmodel;\n\tchar\t\t*model_precache[MAX_MODELS];\t// NULL terminated\n\tchar\t\t*vw_model_name[MAX_VWEP_MODELS];\t// NULL terminated\n\tchar\t\t*sound_precache[MAX_SOUNDS];\t// NULL terminated\n\tchar\t\t*lightstyles[MAX_LIGHTSTYLES];\n\tcmodel_t\t*models[MAX_MODELS];\n\n\tint         num_edicts;         // increases towards MAX_EDICTS\n\tint         num_baseline_edicts;// number of entities that have baselines\n\n\tedict_t\t\tedicts[MAX_EDICTS];\n\tentvars_t\t*game_edicts;\t\t// can NOT be array indexed, because entvars_t is variable sized\n\n\tint         max_edicts;         // might not MAX_EDICTS if mod allocates memory\n\n\tbyte\t\t*pvs, *phs;\t\t\t// fully expanded and decompressed\n\n\t// added to every client's unreliable buffer each frame, then cleared\n\tsizebuf_t\tdatagram;\n\tbyte\t\tdatagram_buf[MAX_DATAGRAM];\n\n\t// added to every client's reliable buffer each frame, then cleared\n\tsizebuf_t\treliable_datagram;\n\tbyte\t\treliable_datagram_buf[MAX_MSGLEN];\n\n\t// the multicast buffer is used to send a message to a set of clients\n\tsizebuf_t\tmulticast;\n\tbyte\t\tmulticast_buf[MAX_MSGLEN];\n\n\t// the signon buffer will be sent to each client as they connect\n\t// includes the entity baselines, the static entities, etc\n\t// large levels will have >MAX_DATAGRAM sized signons, so \n\t// multiple signon messages are kept\n\tsizebuf_t      signon;\n\tunsigned int   num_signon_buffers;\n\tint            signon_buffer_size[MAX_SIGNON_BUFFERS];\n\tbyte           signon_buffers[MAX_SIGNON_BUFFERS][MAX_DATAGRAM];\n\n\tqbool\t\t   mvdrecording;\n\n\tentity_state_t static_entities[MAX_STATIC_ENTITIES];\n\tint            static_entity_count;\n} server_t;\n\n#define\tNUM_SPAWN_PARMS 16\n\n// { sv_antilag related\ntypedef struct\n{\n\tqbool present;\n\tvec3_t laggedpos;\n} laggedentinfo_t;\n// }\n\ntypedef enum\n{\n\tcs_free,\t\t// can be reused for a new connection\n\tcs_zombie,\t\t// client has been disconnected, but don't reuse\n\t\t\t\t// connection for a couple seconds\n\tcs_preconnected,\t// has been assigned, but login/realip not settled yet\n\tcs_connected,\t\t// has been assigned to a client_t, but not in game yet\n\tcs_spawned\t\t// client is fully in game\n} sv_client_state_t;\t\t// FIXME\n\ntypedef struct\n{\n\t// received from client\n\n\t// reply\n\tdouble\t\t\tsenttime;\n\tfloat\t\t\tping_time;\n\n// { sv_antilag\n\tdouble\t\t\t\tsv_time;\n// }\n\n\tpacket_entities_t\tentities;\n} client_frame_t;\n\ntypedef struct\n{\n\tdouble\t\t\tlocaltime;\n\tvec3_t\t\t\torigin;\n} antilag_position_t;\n\n#define MAX_ANTILAG_POSITIONS      128\n#define MAX_BACK_BUFFERS           128\n#define MAX_STUFFTEXT              256\n#define\tCLIENT_LOGIN_LEN            16\n#define\tCLIENT_NAME_LEN             32\n#define LOGIN_CHALLENGE_LENGTH     128\n#define LOGIN_FLAG_LENGTH            8\n#define LOGIN_MIN_RETRY_TIME         5    // 1 login attempt per x seconds\n\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n#define MAX_WEAPONSWITCH_OPTIONS    10\n#endif\n\ntypedef struct client_s\n{\n\tsv_client_state_t\tstate;\n\n\tint\t\t\t\textensions;\t\t\t// what ZQuake extensions the client supports\n\tint\t\t\t\tspectator;\t\t\t// non-interactive\n\tint\t\t\t\tvip;\n\n\tqbool\t\t\tsendinfo;\t\t\t// at end of frame, send info to all\n\t\t\t\t\t\t\t// this prevents malicious multiple broadcasts\n\tfloat\t\t\tlastnametime;\t\t\t// time of last name change\n\tint\t\t\t\tlastnamecount;\t\t\t// time of last name change\n\tunsigned\t\tchecksum;\t\t\t// checksum for calcs\n\tqbool\t\t\tdrop;\t\t\t\t// lose this guy next opportunity\n\tint\t\t\t\tlossage;\t\t\t// loss percentage\n\n\tint\t\t\t\tuserid;\t\t\t\t// identifying number\n\tctxinfo_t\t\t_userinfo_ctx_;\t\t\t// infostring\n\tctxinfo_t\t\t_userinfoshort_ctx_;\t// infostring\n\n\tantilag_position_t\tantilag_positions[MAX_ANTILAG_POSITIONS];\n\tint\t\t\t\tantilag_position_next;\n\n\tusercmd_t\t\tlastcmd;\t\t\t// for filling in big drops and partial predictions\n\tdouble\t\t\tlocaltime;\t\t\t// of last message\n\tqbool\t\t\tjump_held;\n\n\tfloat\t\t\tmaxspeed;\t\t\t// localized maxspeed\n\tfloat\t\t\tentgravity;\t\t\t// localized ent gravity\n\n\tedict_t\t\t\t*edict;\t\t\t\t// EDICT_NUM(clientnum+1)\n#ifdef USE_PR2\n\tint\t\t\t\tisBot;\n\tusercmd_t\t\tbotcmd;\t\t\t\t// bot movment\n#endif\n\tchar\t\t\tname[CLIENT_NAME_LEN];\t\t// for printing to other people\n\n\tchar\t\t\tteam[CLIENT_NAME_LEN];\n\t// extracted from userinfo\n\tint\t\t\t\tmessagelevel;\t\t\t// for filtering printed messages\n\n\t// the datagram is written to after every frame, but only cleared\n\t// when it is sent out to the client.  overflow is tolerated.\n\tsizebuf_t\t\tdatagram;\n\tbyte\t\t\tdatagram_buf[MAX_DATAGRAM];\n\n\t// back buffers for client reliable data\n\tsizebuf_t\t\tbackbuf;\n\tint\t\t\t\tnum_backbuf;\n\tint\t\t\t\tbackbuf_size[MAX_BACK_BUFFERS];\n\tbyte\t\t\tbackbuf_data[MAX_BACK_BUFFERS][MAX_MSGLEN];\n\n\tchar\t\t\tstufftext_buf[MAX_STUFFTEXT];\n\n\t// Use SV_ClientConnectedTime & SV_ClientGameTime instead\n\tdouble          connection_started_realtime; // or time of disconnect for zombies\n\tdouble          connection_started_curtime;  // like connection_started but curtime (not affected by pause)\n\tqbool           send_message;                // set on frames a datagram arrived on\n\n// { sv_antilag related\n\tlaggedentinfo_t\tlaggedents[MAX_CLIENTS];\n\tunsigned int\tlaggedents_count;\n\tfloat\t\t\tlaggedents_frac;\n\tfloat           laggedents_time;\n// }\n\n\t// spawn parms are carried from level to level\n\tfloat\t\t\tspawn_parms[NUM_SPAWN_PARMS];\n\n\t// client known data for deltas\n\tint\t\t\t\told_frags;\n\n\tint\t\t\t\tstats[MAX_CL_STATS];\n\n\tdouble\t\t\tlastservertimeupdate;\t\t// last realtime we sent STAT_TIME to the client\n\n\tclient_frame_t\tframes[UPDATE_BACKUP];\t\t// updates can be deltad from here\n\n\tvfsfile_t\t\t*download;\t\t\t// file being downloaded\n\tint             dupe;               // duplicate packets requested\n#ifdef PROTOCOL_VERSION_FTE\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tint\t\t\t\tdownload_chunks_perframe;\n#endif\n#endif\n\tint\t\t\t\tdownloadsize;\t\t\t// total bytes\n\tint\t\t\t\tdownloadcount;\t\t\t// bytes sent\n\n\t// demo download list for internal cmd dl function\n\t// Added by VVD {\n\tint\t\t\t\tdemonum[MAX_ARGS];\n\tqbool\t\t\tdemolist;\n\t// } Added by VVD\n\n\tint             spec_track;             // entnum of player tracking\n\n\tdouble          whensaid[10];           // JACK: For floodprots\n\tint             whensaidhead;           // Head value for floodprots\n\tdouble          lockedtill;\n\n\tFILE\t\t\t*upload;\n\tchar\t\t\tuploadfn[MAX_QPATH];\n\tnetadr_t\t\tsnap_from;\n\tqbool\t\t\tremote_snap;\n\n\tchar\t\t\tlogin[CLIENT_LOGIN_LEN];\n\tchar            login_alias[CLIENT_NAME_LEN];\n\tchar            login_flag[LOGIN_FLAG_LENGTH];\n\tchar            login_confirmation[LOGIN_CHALLENGE_LENGTH];\n\tchar            login_challenge[LOGIN_CHALLENGE_LENGTH];\n\tint\t\t\t\tlogged;\n\tqbool           logged_in_via_web;\n\tdouble          login_request_time;\n\n\tint\t\t\t\tspawncount;\t\t\t// for tracking map changes during downloading\n\n//bliP: additional ->\n\tint\t\t\t\tfile_percent;\n\tqbool\t\t\tspecial;\n\tint\t\t\t\tlogincount;\n\tfloat\t\t\tlasttoptime;\t\t\t// time of last topcolor change\n\tint\t\t\t\tlasttopcount;\t\t\t// count of last topcolor change\n\tint\t\t\t\tspec_print;\n\tdouble\t\t\tcuff_time;\n//bliP: 24/9 anti speed ->\n\tint\t\t\t\tmsecs;\n\tdouble\t\t\tlast_check;\n//<-\n//<-\n\tfloat\t\t\tlastuserinfotime;\t\t// time of last userinfo change\n\tint\t\t\t\tlastuserinfocount;\t\t// count of last userinfo change\n\n#ifdef PROTOCOL_VERSION_FTE\n\tunsigned int\tfteprotocolextensions;\n#endif // PROTOCOL_VERSION_FTE\n\n#ifdef PROTOCOL_VERSION_FTE2\n\tunsigned int\tfteprotocolextensions2;\n#endif // PROTOCOL_VERSION_FTE2\n\n#ifdef PROTOCOL_VERSION_MVD1\n\tunsigned int    mvdprotocolextensions1;\n#endif\n\n#ifdef FTE_PEXT2_VOICECHAT\n\tunsigned int voice_read;\t/*place in ring*/\n\tunsigned char voice_mute[MAX_CLIENTS/8];\n\tqbool voice_active;\n\tenum\n\t{\n\t\t/*note - when recording an mvd, only 'all' will be received by non-spectating viewers. all other chat will only be heard when spectating the receiver(or sender) of said chat*/\n\n\t\t/*should we add one to respond to the last speaker? or should that be an automagic +voip_reply instead?*/\n\t\tVT_TEAM,\n\t\tVT_ALL,\n\t\tVT_NONMUTED,\t/*cheap, but allows custom private channels with no external pesters*/\n\t\tVT_PLAYERSLOT0\n\t\t/*player0+...*/\n\t} voice_target;\n#endif\n\n\t//===== NETWORK ============\n\tqbool           process_pext;             // true if we wait for reply from client on \"cmd pext\" command.\n\tint             chokecount;\n\tint             delta_sequence;           // -1 = no compression\n\tnetchan_t       netchan;\n\tnetadr_t        realip;                   // client's ip, not latest proxy's\n\tint             realip_num;               // random value\n\tint             realip_count;\n\tint             rip_vip;\n\tdouble          delay;\n\tdouble          disable_updates_stop;     // Vladis\n\tqbool           maxping_met;              // set if user meets maxping requirements\n\tpacket_t        *packets, *last_packet;\n\n#ifdef MVD_PEXT1_HIGHLAGTELEPORT\n\t// lagged-teleport extension\n\tqbool           lastteleport_teleport;    // true if teleport, otherwise it was spawn\n\tint             lastteleport_outgoingseq; // outgoing sequence# when the player teleported\n\tint             lastteleport_incomingseq; // incoming sequence# when the player teleported\n\tfloat           lastteleport_teleportyaw; // new yaw angle, post-teleport\n#endif\n\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\t// server-side weapons extension\n\tint             weaponswitch_sequence_set; // need to remember what packet current choices were sent in for forgetorder\n\tqbool           weaponswitch_pending;\n\tqbool           weaponswitch_hide;         // automatically flick back when not firing\n\tqbool           weaponswitch_hide_on_death;// switch back to 2 1 when dying\n\tqbool           weaponswitch_wasfiring;    // fire pressed on previous frame (will only hide if so)\n\n\tqbool           weaponswitch_enabled;      // allow user to disable while connected\n\tint             weaponswitch_mode;         // user preference\n\tqbool           weaponswitch_forgetorder;  // if set, decide best weapon immediately and don't rank on fire\n\tbyte            weaponswitch_priority[MAX_WEAPONSWITCH_OPTIONS];\n#endif\n\n\tqbool           mvd_write_usercmds;\n} client_t;\n\n// a client can leave the server in one of four ways:\n// dropping properly by quiting or disconnecting\n// timing out if no valid messages are received for timeout.value seconds\n// getting kicked off by the server operator\n// a program error, like an overflowed reliable buffer\n\ntypedef struct\n{\n\tint\t\t\t\tparsecount;\n\n\tvec3_t\t\t\torigin;\n\tvec3_t\t\t\tangles;\n\tint\t\t\t\tweaponframe;\n\tint\t\t\t\tframe;\n\tint\t\t\t\tskinnum;\n\tint\t\t\t\tmodel;\n\tint\t\t\t\teffects;\n\tint\t\t\t\tflags;\n\n\tqbool\t\t\tfixangle;\n\n\tfloat\t\t\tsec;\n\n} demo_client_t;\n\ntypedef struct\n{\n\tdemo_client_t\tclients[MAX_CLIENTS];\n\tdouble\t\t\ttime;\n\tqbool           paused;\n\tbyte            pause_duration;\n\n// { reset each time frame wroten with SV_MVDWritePackets()\n\tsizebuf_t\t\t_buf_;\n\t// !!! OUCH OUCH OUCH, 64 frames, so it about 2mb !!!\n\t// here data with mvd headers, so it up to 4 mvd msg with maximum size, however basically here alot of small msgs,\n\t// so this size pathetic\n\tbyte\t\t\t_buf__data[(MAX_MVD_SIZE + 10) * 4];\n\n\tint\t\t\t\tlastto;\n\tint\t\t\t\tlasttype;\n\tint\t\t\t\tlastsize;\n\tint\t\t\t\tlastsize_offset; // this is tricky\n// }\n\n} demo_frame_t;\n\n//qtv proxies are meant to send a small header now, bit like http\n//this header gives supported version numbers and stuff\ntypedef struct mvdpendingdest_s\n{\n\tqbool error; //disables writers, quit ASAP.\n\tint socket;\n\n\tchar inbuffer[2048];\n\tchar outbuffer[2048];\n\n\tchar challenge[64];\n\tqbool hasauthed;\n\n\tint insize;\n\tint outsize;\n\n\tqbool\t\t\tmust_be_qizmo_tcp_connect; // HACK, this stream should not be allowed but just checked ONLY AND ONLY for qizmo tcp connection\n\n\tdouble\t\t\tio_time; // when last IO occur on socket, so we can timeout this dest\n\tnetadr_t\t\tna;\n\n\tstruct mvdpendingdest_s *nextdest;\n} mvdpendingdest_t;\n\ntypedef enum {DEST_NONE, DEST_FILE, DEST_BUFFEREDFILE, DEST_STREAM} desttype_t;\n\n#define MAX_PROXY_INBUFFER\t\t4096 /* qqshka: too small??? */\n\ntypedef struct mvddest_s\n{\n\tqbool error; //disables writers, quit ASAP.\n\n\tdesttype_t desttype;\n\n\tint socket;\n\tFILE *file;\n\n\tchar name[MAX_QPATH];\n\tchar path[MAX_QPATH];\n\n\tchar *cache;\n\tint cacheused;\n\tint maxcachesize;\n\n\tunsigned int totalsize;\n\n// { used by QTV\n\tdouble\t\t\tio_time; // when last IO occur on socket, so we can timeout this dest\n\tint\t\t\t\tid; // dest id, used by QTV only\n\tnetadr_t\t\tna;\n\n\tchar\t\t\tinbuffer[MAX_PROXY_INBUFFER];\n\tint\t\t\t\tinbuffersize;\n\n\tchar\t\t\tqtvname[64];\n\n\tqtvuser_t\t\t*qtvuserlist;\n\n\tchar            qtvaddress[128];\n\tint             qtvstreamid;\n// }\n\n\tstruct mvddest_s *nextdest;\n} mvddest_t;\n\ntypedef struct\n{\n\tsizebuf_t\t\tdatagram;\n\tbyte\t\t\tdatagram_data[MAX_MVD_SIZE]; // data without mvd header\n\n\tdouble          time;             // sv.time\n\tdouble          curtime;          // curtime\n\tdouble          pingtime;         // compare to curtime\n\n\t// Something like time of last mvd message, so we can guess delta milliseconds for next message.\n\t// you better not relay on this variable...\n\tdouble\t\t\tprevtime;\n\n\tclient_t\t\trecorder;\n\n\tqbool\t\t\tfixangle[MAX_CLIENTS];\n\n\tint\t\t\t\tstats[MAX_CLIENTS][MAX_CL_STATS];\n\n\tint\t\t\t\tparsecount;  // current frame, to which we add demo data\n\tint\t\t\t\tlastwritten; // lastwriten frame\n\n\tdemo_frame_t\tframes[UPDATE_BACKUP]; // here we store all previous frames\n\tdemo_client_t\tclients[MAX_CLIENTS]; // we store here what we wrote last time so we can delta\n\n\t// =====================================\n\tchar\t\t\tmem_set_point; // fields below, like ->dest and ->pendingdest must not be memset to 0\n\t// =====================================\n\n\tstruct mvddest_s\t\t*dest;\n\tstruct mvdpendingdest_s *pendingdest;\n\n\t// last recorded demo's names for command \"cmd dl . ..\" (maximum 15 dots)\n\tchar\t\t\t*lastdemosname[16];\n\tint\t\t\t\tlastdemospos;\n} demo_t;\n\n//=============================================================================\n\n\n#define\tSTATFRAMES\t100\ntypedef struct\n{\n\tdouble\t\t\tactive;\n\tdouble\t\t\tidle;\n\tdouble\t\t\tdemo;\n\tint\t\t\t\tcount;\n\tint\t\t\t\tpackets;\n\n\tdouble\t\t\tlatched_active;\n\tdouble\t\t\tlatched_idle;\n\tdouble\t\t\tlatched_demo;\n\tint\t\t\t\tlatched_packets;\n} svstats_t;\n\n// MAX_CHALLENGES is made large to prevent a denial\n// of service attack that could cycle all of them\n// out before legitimate users connected\n#define\tMAX_CHALLENGES\t1024\n\ntypedef struct\n{\n\tnetadr_t\t\tadr;\n\tint\t\t\t\tchallenge;\n\tint\t\t\t\ttime;\n} challenge_t;\n\n// TCPCONNECT -->\ntypedef struct svtcpstream_s\n{\n\tint                     socketnum;                         // socket\n\tqbool                   waitingforprotocolconfirmation;    // wait for \"qizmo\\n\", first 6 bytes before confirming that is tcpconnection\n\tint                     inlen;                             // how much bytes we have in inbuffer\n\tchar                    inbuffer[1500];                    // recv buffer\n\tint                     outlen;                            // how much bytes we have in outbuffer\n\tchar                    outbuffer[1500 * 5];               // send buffer\n\tqbool                   drop;                              // do we need drop that connection ASAP\n\tfloat                   timeouttime;                       // I/O timeout\n\tnetadr_t                remoteaddr;                        // peer remoter addr\n\tstruct svtcpstream_s    *next;                             // next tcpconnection in list\n} svtcpstream_t;\n// <-- TCPCONNECT\n\ntypedef struct\n{\n\tint\t\t\t\tspawncount;\t\t// number of servers spawned since start,\n\t\t\t\t\t\t\t\t\t// used to check late spawns\n\tint\t\t\t\tlastuserid;\t\t// userid of last spawned client\n\n\tsocket_t\t\tsocketip;\t\t// main server UDP socket.\n\n// TCPCONNECT -->\n\tint\t\t\t\tsockettcp;\t\t// server TCP socket, used for QTV/TCPCONNECT.\n\tsvtcpstream_t *\ttcpstreams;\n// <-- TCPCONNECT\n\n\tclient_t        clients[MAX_CLIENTS];\n\tint             serverflags;    // episode completion information\n\n\tdouble          last_heartbeat;\n\tint             heartbeat_sequence;\n\tsvstats_t       stats;\n\n\tchar            info[MAX_SERVERINFO_STRING];\n\n#ifdef PROTOCOL_VERSION_FTE\n\tunsigned int fteprotocolextensions;\n#endif // PROTOCOL_VERSION_FTE\n\n#ifdef PROTOCOL_VERSION_FTE2\n\tunsigned int fteprotocolextensions2;\n#endif // PROTOCOL_VERSION_FTE2\n\n#ifdef PROTOCOL_VERSION_MVD1\n\tunsigned int mvdprotocolextension1;\n#endif\n\n\t// log messages are used so that fraglog processes can get stats\n\tint\t\t\t\tlogsequence;\t\t// the message currently being filled\n\tdouble\t\t\tlogtime;\t\t// time of last swap\n\tsizebuf_t\t\tlog[2];\n\tbyte\t\t\tlog_buf[2][MAX_DATAGRAM];\n\n\tchallenge_t\t\tchallenges[MAX_CHALLENGES];\t// to prevent invalid IPs from connecting\n\n\tpacket_t\t\t*free_packets;\n} server_static_t;\n\n//=============================================================================\n\n// edict->movetype values\n#define\tMOVETYPE_NONE\t\t\t0\t\t// never moves\n#define\tMOVETYPE_ANGLENOCLIP\t\t1\n#define\tMOVETYPE_ANGLECLIP\t\t2\n#define\tMOVETYPE_WALK\t\t\t3\t\t// gravity\n#define\tMOVETYPE_STEP\t\t\t4\t\t// gravity, special edge handling\n#define\tMOVETYPE_FLY\t\t\t5\n#define\tMOVETYPE_TOSS\t\t\t6\t\t// gravity\n#define\tMOVETYPE_PUSH\t\t\t7\t\t// no clip to world, push and crush\n#define\tMOVETYPE_NOCLIP\t\t\t8\n#define\tMOVETYPE_FLYMISSILE\t\t9\t\t// extra size to monsters\n#define\tMOVETYPE_BOUNCE\t\t\t10\n#define\tMOVETYPE_LOCK\t\t\t15\t\t// server controls view angles\n\n// edict->solid values\n#define\tSOLID_NOT\t\t\t0\t\t// no interaction with other objects\n#define\tSOLID_TRIGGER\t\t\t1\t\t// touch on edge, but not blocking\n#define\tSOLID_BBOX\t\t\t2\t\t// touch on edge, block\n#define\tSOLID_SLIDEBOX\t\t\t3\t\t// touch on edge, but not an onground\n#define\tSOLID_BSP\t\t\t4\t\t// bsp clip, touch on edge, block\n\n// edict->deadflag values\n#define\tDAMAGE_NO\t\t\t0\n#define\tDAMAGE_YES\t\t\t1\n#define\tDAMAGE_AIM\t\t\t2\n\n// edict->flags\n#define\tFL_FLY\t\t\t\t1\n#define\tFL_SWIM\t\t\t\t2\n#define\tFL_GLIMPSE\t\t\t4\n#define\tFL_CLIENT\t\t\t8\n#define\tFL_INWATER\t\t\t16\n#define\tFL_MONSTER\t\t\t32\n#define\tFL_GODMODE\t\t\t64\n#define\tFL_NOTARGET\t\t\t128\n#define\tFL_ITEM\t\t\t\t256\n#define\tFL_ONGROUND\t\t\t512\n#define\tFL_PARTIALGROUND\t\t1024\t// not all corners are valid\n#define\tFL_WATERJUMP\t\t\t2048\t// player jumping out of water\n\n// { sv_antilag\n#define FL_LAGGEDMOVE\t\t\t(1<<16)\n// }\n\n#define\tSPAWNFLAG_NOT_EASY\t\t256\n#define\tSPAWNFLAG_NOT_MEDIUM\t\t512\n#define\tSPAWNFLAG_NOT_HARD\t\t1024\n#define\tSPAWNFLAG_NOT_DEATHMATCH\t2048\n\n#define\tMULTICAST_ALL\t\t\t0\n#define\tMULTICAST_PHS\t\t\t1\n#define\tMULTICAST_PVS\t\t\t2\n\n#define\tMULTICAST_ALL_R\t\t\t3\n#define\tMULTICAST_PHS_R\t\t\t4\n#define\tMULTICAST_PVS_R\t\t\t5\n\n#define MULTICAST_KTX1_EXT      6  // Only send to those using ktx1 protocol extension (todo)\n#define MULTICAST_MVD_HIDDEN    7  // Insert into MVD stream only, as dem_multiple(0)\n\n#define MAX_LOCALINFOS\t\t\t10000\n// maps in localinfo {\n#define LOCALINFO_MAPS_LIST_START\t1000\n#define LOCALINFO_MAPS_LIST_END\t\t4999\n// }\n\n#define MAX_REDIRECTMESSAGES\t128\n#define OUTPUTBUF_SIZE\t\t\t8000\n\n// { server flags\n\n// force player enter server as spectator if all players's slots are busy and\n// if there are empty slots for spectators and sv_forcespec_onfull == 2\n#define SVF_SPEC_ONFULL\t\t\t(1<<0)\n// do not join server as spectator if server full and sv_forcespec_onfull == 1\n#define SVF_NO_SPEC_ONFULL\t\t(1<<1)\n\n// } server flags\n\n//============================================================================\n\nextern\tcvar_t\tsv_paused; // 1 - normal, 2 - auto (single player), 3 - both\nextern\tcvar_t\tsv_maxspeed;\nextern\tcvar_t\tsv_mintic, sv_maxtic, sv_maxfps;\nextern\tcvar_t\tsv_antilag, sv_antilag_no_pred, sv_antilag_projectiles;\n\nextern\tint current_skill;\n\nextern\tcvar_t\tspawn;\nextern\tcvar_t\tteamplay;\nextern\tcvar_t\tserverdemo;\nextern\tcvar_t\tdeathmatch;\nextern\tcvar_t\tfraglimit;\nextern\tcvar_t\ttimelimit;\nextern\tcvar_t\tskill;\nextern\tcvar_t\tcoop;\nextern\tcvar_t\tmaxclients;\n\nextern\tcvar_t\tsv_specprint;\t//bliP: spectator print\n\nextern\tserver_static_t\tsvs;\t// persistant server info\nextern\tserver_t\tsv;\t// local server\nextern\tdemo_t\t\tdemo;\t// server demo struct\n\nextern\tclient_t\t*sv_client;\nextern\tedict_t\t\t*sv_player;\n\n#define\tMODEL_NAME_LEN\t6\nextern\tchar\t\tlocalmodels[MAX_MODELS][MODEL_NAME_LEN]; // inline model names for precache\n//extern\tchar\t\tlocalinfo[MAX_LOCALINFO_STRING+1];\nextern  ctxinfo_t _localinfo_;\n\nextern\tqbool\t\tsv_error;\n\nextern char\t\t\tmaster_rcon_password[128];\n\n//===========================================================\n\n//\n// sv_main.c\n//\n\nextern\tdouble\trealtime; // not bounded in any way, changed at start of every frame, never reset\n\ntypedef enum {\n\tipft_ban,\n\tipft_safe\n} ipfiltertype_t;\n\ntypedef struct\n{\n\tunsigned\tmask;\n\tunsigned\tcompare;\n\tint\t\t\tlevel;\n\tdouble\t\ttime; // for ban expiration\n\tipfiltertype_t type;\n} ipfilter_t;\n\n//bliP: penalty filters ->\ntypedef enum {\n\tft_mute,\n\tft_cuff\n} filtertype_t;\n\ntypedef struct\n{\n\tbyte\t\tip[4];\n\tdouble\t\ttime;\n\tfiltertype_t\ttype;\n} penfilter_t;\n//<-\n\nvoid SV_Frame (double time);\nvoid SV_FinalMessage (const char *message);\nvoid SV_DropClient (client_t *drop);\n\nint SV_CalcPing (client_t *cl);\nvoid SV_FullClientUpdate (client_t *client, sizebuf_t *buf);\nvoid SV_FullClientUpdateToClient (client_t *client, client_t *cl);\n\nqbool SV_CheckBottom (edict_t *ent);\nqbool SV_movestep (edict_t *ent, vec3_t move, qbool relink);\n\nqbool SV_CloseEnough (edict_t *ent, edict_t *goal, float dist);\nqbool SV_StepDirection (edict_t *ent, float yaw, float dist);\nvoid SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist);\n\nvoid SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg);\n\nvoid SV_MoveToGoal (void);\n\nvoid SV_InitOperatorCommands (void);\n\nvoid SV_SendServerinfo (client_t *client);\nvoid SV_ExtractFromUserinfo (client_t *cl, qbool namechanged);\nint SV_BoundRate (qbool dl, int rate);\n\ntypedef struct\n{\n\tint sec;\n\tint min;\n\tint hour;\n\tint day;\n\tint mon;\n\tint year;\n\tchar str[128];\n} date_t;\n\nvoid SV_TimeOfDay(date_t *date, char *timeformat);\n\n\n//bliP: init ->\nvoid SV_ListFiles_f (void);\nvoid SV_RemoveFile_f (void);\nvoid SV_RemoveDirectory_f (void);\n\n#define\tMAX_PENFILTERS 512\nvoid SV_RemoveIPFilter (int i);\n//static qbool SV_IPCompare (byte *a, byte *b);\n//static void SV_IPCopy (byte *dest, byte *src);\nvoid SV_SavePenaltyFilter (client_t *cl, filtertype_t type, double pentime);\ndouble SV_RestorePenaltyFilter (client_t *cl, filtertype_t type);\n\nqbool SV_FilterPacket (void);\nvoid SV_SendBan (void);\nqbool GameStarted(void);\n//<-\nvoid SV_Script_f (void);\nint SV_GenerateUserID (void);\n\n//\n// sv_init.c\n//\nint SV_ModelIndex (char *name);\nvoid SV_FlushSignon (void);\nvoid SV_SpawnServer (char *server, qbool devmap, char* entityfile, qbool loading_savegame);\n\n\n//\n// sv_phys.c\n//\nvoid SV_ProgStartFrame (qbool isBotFrame);\nvoid SV_Physics (void);\nvoid SV_CheckVelocity (edict_t *ent);\nvoid SV_AddGravity (edict_t *ent, float scale);\nqbool SV_RunThink (edict_t *ent);\nvoid SV_Physics_Toss (edict_t *ent);\nvoid SV_RunNewmis (void);\nvoid SV_RunNQNewmis (void);\nvoid SV_Impact (edict_t *e1, edict_t *e2);\nvoid SV_SetMoveVars(void);\n#ifdef USE_PR2\nvoid SV_RunBots(void);\n#endif\n\n//\n// sv_send.c\n//\ntypedef enum {RD_NONE, RD_CLIENT, RD_PACKET, RD_MOD} redirect_t;\nvoid SV_BeginRedirect (redirect_t rd);\nvoid SV_EndRedirect (void);\nqbool SV_AddToRedirect(char *msg);\n\nvoid SV_Multicast(vec3_t origin, int to);\nvoid SV_MulticastEx(vec3_t origin, int to, const char *cl_reliable_key);\nvoid SV_StartParticle(vec3_t org, vec3_t dir, int color, int count, int replacement_te, int replacement_count);\nvoid SV_StartSound(edict_t *entity, int channel, char *sample, int volume, float attenuation);\nvoid SV_ClientPrintf(client_t *cl, int level, char *fmt, ...);\nvoid SV_ClientPrintf2(client_t *cl, int level, char *fmt, ...);\nvoid SV_BroadcastPrintf(int level, char *fmt, ...);\n#define BPRINT_IGNOREINDEMO  (1<<0) // broad cast print will be not put in demo\n#define BPRINT_IGNORECLIENTS (1<<1) // broad cast print will not be seen by clients, but may be seen in demo\n#define BPRINT_QTVONLY       (1<<2) // if broad cast print goes to demo, then it will be only qtv sream, but not file\n#define BPRINT_IGNORECONSOLE (1<<3) // broad cast print will not be put in server console\nvoid SV_BroadcastPrintfEx (int level, int flags, char *fmt, ...);\nvoid SV_BroadcastCommand (char *fmt, ...);\nvoid SV_SendClientMessages (void);\nvoid SV_SendDemoMessage(void);\nvoid SV_SendMessagesToAll (void);\nvoid SV_FindModelNumbers (void);\n\n\n//\n// sv_user.c\n//\nvoid SV_ExecuteClientMessage (client_t *cl);\nvoid SV_UserInit (void);\nvoid SV_TogglePause (const char *msg, int bit);\nvoid ProcessUserInfoChange (client_t* sv_client, const char* key, const char* old_value);\nvoid SV_RotateCmd(client_t* cl, usercmd_t* cmd);\n\n#ifdef FTE_PEXT2_VOICECHAT\nvoid SV_VoiceInitClient(client_t *client);\nvoid SV_VoiceSendPacket(client_t *client, sizebuf_t *buf);\n#endif\n\n//\n// sv_ccmds.c\n//\nvoid SV_Status_f (void);\nvoid SV_ServerinfoChanged (char *key, char *string);\nvoid SV_SendServerInfoChange (char *key, char *value);\nvoid SV_KickClient(client_t* client, const char* reason);\n\n//\n// sv_ents.c\n//\nvoid SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg, qbool recorder);\nvoid SV_SetVisibleEntitiesForBot (client_t* client);\n\n//\n// sv_nchan.c\n//\nvoid ClientReliableCheckBlock(client_t *cl, int maxsize);\nvoid ClientReliable_FinishWrite(client_t *cl);\nvoid ClientReliableWrite_Begin(client_t *cl, int c, int maxsize);\nvoid ClientReliableWrite_Angle(client_t *cl, float f);\nvoid ClientReliableWrite_Angle16(client_t *cl, float f);\nvoid ClientReliableWrite_Byte(client_t *cl, int c);\nvoid ClientReliableWrite_Char(client_t *cl, int c);\nvoid ClientReliableWrite_Float(client_t *cl, float f);\nvoid ClientReliableWrite_Coord(client_t *cl, float f);\nvoid ClientReliableWrite_Long(client_t *cl, int c);\nvoid ClientReliableWrite_Short(client_t *cl, int c);\nvoid ClientReliableWrite_String(client_t *cl, char *s);\nvoid ClientReliableWrite_SZ(client_t *cl, void *data, int len);\nvoid SV_ClearReliable (client_t *cl); // clear cl->netchan.message and backbuf\n\n//\n// sv_demo.c\n//\n\nvoid MVD_MSG_WriteChar   (const int c);\nvoid MVD_MSG_WriteByte   (const int c);\nvoid MVD_MSG_WriteShort  (const int c);\nvoid MVD_MSG_WriteLong   (const int c);\nvoid MVD_MSG_WriteFloat  (const float f);\nvoid MVD_MSG_WriteString (const char *s);\nvoid MVD_MSG_WriteCoord  (const float f);\nvoid MVD_MSG_WriteAngle  (const float f);\nvoid MVD_SZ_Write        (const void *data, int length);\n\nqbool MVDWrite_Begin(byte type, int to, int size);\nqbool MVDWrite_HiddenBlockBegin(int length);\nqbool MVDWrite_HiddenBlock(const void* data, int length);\n\nvoid SV_MVD_Record_f (void);\nvoid SV_MVDEasyRecord_f (void);\n\nvoid SV_MVDStop (int reason, qbool mvdonly);\nvoid SV_MVDStop_f (void);\nqbool SV_MVDWritePackets (int num);\nvoid SV_MVD_SendInitialGamestate(mvddest_t *dest);\nqbool SV_MVD_Record (mvddest_t *dest, qbool mapchange);\n\nmvddest_t\t*DestByName (char *name);\nvoid\t\tDestClose (mvddest_t *d, qbool destroyfiles);\n\nint DemoWriteDest (void *data, int len, mvddest_t *d);\n\nextern demo_t\tdemo; // server demo struct\n\nextern cvar_t\tsv_demoUseCache;\nextern cvar_t\tsv_demoCacheSize;\nextern cvar_t\tsv_demoMaxDirSize;\nextern cvar_t\tsv_demoClearOld;\nextern cvar_t\tsv_demoDir;\nextern cvar_t\tsv_demoDirAlt;\nextern cvar_t\tsv_demofps;\nextern cvar_t\tsv_demoPings;\nextern cvar_t\tsv_demoMaxSize;\nextern cvar_t\tsv_demoExtraNames;\n\nextern cvar_t\tsv_demoPrefix;\nextern cvar_t\tsv_demoSuffix;\nextern cvar_t\tsv_demotxt;\nextern cvar_t\tsv_onrecordfinish;\n\nextern cvar_t\tsv_ondemoremove;\nextern cvar_t\tsv_demoRegexp;\n\nextern cvar_t\tsv_silentrecord;\n\nvoid SV_MVDInit (void);\nchar *SV_MVDNum(int num);\n\n//\n// sv_demo_misc.c\n//\n\nchar\t*SV_PrintTeams (void);\nvoid\tRun_sv_demotxt_and_sv_onrecordfinish (const char *dest_name, const char *dest_path, qbool destroyfiles);\nqbool\tSV_DirSizeCheck (void);\nchar\t*SV_CleanName (unsigned char *name);\nint     Dem_CountPlayers (void);\nchar\t*Dem_Team (int num);\nchar\t*Dem_PlayerName (int num);\nchar\t*Dem_PlayerNameTeam (char *t);\nint\t\tDem_CountTeamPlayers (char *t);\nchar\t*quote (char *str);\nvoid\tCleanName_Init (void);\nvoid\tSV_LastScores_f (void);\nvoid\tSV_LastStats_f (void);\nvoid\tSV_DemoList_f (void);\nvoid\tSV_DemoListRegex_f (void);\nvoid\tSV_MVDRemove_f (void);\nvoid\tSV_MVDRemoveNum_f (void);\nvoid\tSV_MVDInfoAdd_f (void);\nvoid\tSV_MVDInfoRemove_f (void);\nvoid\tSV_MVDInfo_f (void);\nvoid\tSV_LastScores_f (void);\nchar*   SV_MVDName2Txt (const char *name);\nvoid SV_MVDEmbedInfo_f(void);\n\n//\n// sv_demo_qtv.c\n//\n\nextern cvar_t\tqtv_streamtimeout;\n\n\nvoid SV_MVDStream_Poll(void);\nvoid SV_MVDCloseStreams(void);\nvoid SV_QTV_Init(void);\n\nvoid DemoWriteQTV (sizebuf_t *msg);\nvoid QTVsv_FreeUserList(mvddest_t *d);\nvoid QTV_Streams_List (void);\nvoid QTV_Streams_UserList (void);\nconst char* SV_MVDDemoName(void);\n\n//\n// sv_login.c\n//\n\nvoid SV_LoadAccounts(void);\nvoid SV_CreateAccount_f(void);\nvoid SV_RemoveAccount_f(void);\nvoid SV_ListAccount_f (void);\nvoid Login_Init (void);\nqbool SV_Login(client_t *cl);\nvoid SV_Logout(client_t *cl);\nvoid SV_ParseWebLogin(client_t* cl);\nvoid SV_ParseLogin(client_t *cl);\nvoid SV_LoginCheckTimeOut(client_t *cl);\nvoid SV_LoginWebCheck(client_t* cl);\nvoid SV_LoginWebFailed(client_t* cl);\nqbool SV_LoginRequired(client_t* cl);\nqbool SV_LoginBlockJoinRequest(client_t* cl);\n\n// sv_master.c\nvoid SV_SetMaster_f (void);\nvoid SV_Heartbeat_f (void);\nvoid Master_Shutdown (void);\nvoid Master_Heartbeat (void);\n\n// sv_save.c \nvoid SV_SaveGame_f (void); \nvoid SV_LoadGame_f (void); \n\n//\nvoid SV_WriteDelta(client_t* client, entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qbool force);\nqbool SV_SkipCommsBotMessage(client_t* client);\n\n// \n#ifdef SERVERONLY\n#include \"central.h\"\n#else\nextern qbool server_cfg_done;\n#endif\n\n// These functions tell us how much time has passed since the client connected\n// Sometimes this should be affected by pause (scoreboards) and sometimes not (spam, networking)\n// GameTime() stops while game is paused, Connected() continues as normal\n// Both return 0 if client hasn't connected yet\ndouble SV_ClientConnectedTime(client_t* client);    // real-world time passed\ndouble SV_ClientGameTime(client_t* client);         // affected by pause\nvoid SV_SetClientConnectionTime(client_t* client);\n\n#ifdef SERVERONLY\n// mvdsv not changed over to enums yet, which was more about documentation\n#define SV_CommandLineEnableCheats() (COM_CheckParm(\"-cheats\"))\n#define SV_CommandLineEnableLocalCommand() (COM_CheckParm(\"-enablelocalcommand\"))\n#define SV_CommandLineDemoCacheArgument() (COM_CheckParm(\"-democache\"))\n#define SV_CommandLineProgTypeArgument() (COM_CheckParm(\"-progtype\"))\n#define SV_CommandLineUseMinimumMemory() (COM_CheckParm(\"-minmemory\"))\n#define SV_CommandLineHeapSizeMemoryKB() (COM_CheckParm(\"-heapsize\"))\n#define SV_CommandLineHeapSizeMemoryMB() (COM_CheckParm(\"-mem\"))\n#else\n#define SV_CommandLineEnableCheats() (COM_CheckParm(cmdline_param_server_enablecheats))\n#define SV_CommandLineEnableLocalCommand() (COM_CheckParm(cmdline_param_server_enablelocalcommand))\n#define SV_CommandLineDemoCacheArgument() (COM_CheckParm(cmdline_param_server_democache_kb))\n#define SV_CommandLineProgTypeArgument() (COM_CheckParm(cmdline_param_server_progtype))\n#define SV_CommandLineUseMinimumMemory() (COM_CheckParm(cmdline_param_host_memory_minimum))\n#define SV_CommandLineHeapSizeMemoryKB() (COM_CheckParm(cmdline_param_host_memory_kb))\n#define SV_CommandLineHeapSizeMemoryMB() (COM_CheckParm(cmdline_param_host_memory_mb))\n#endif\n\n#endif /* !__SERVER_H__ */\n"
  },
  {
    "path": "src/settings.h",
    "content": "/*\nCopyright (C) 2011 johnnycz\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n// used by settings_page and menu_options modules\n\n//\n// Macros\n// Use following macros to create array of 'setting' variables\n// see settings_page.h for further info\n//\n\n#ifndef __SETTINGS_H__\n#define __SETTINGS_H__\n\n#include \"Ctrl.h\"\n\n// start advanced settings section\n#define ADDSET_ADVANCED_SECTION() { stt_advmark, NULL, false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL }\n\n// starts basic settings setion (terminates advanced settings section) (started by default)\n#define ADDSET_BASIC_SECTION() { stt_basemark, NULL, false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL }\n\n// separator: decorating purpose\n#define ADDSET_SEPARATOR(label) { stt_separator, label, false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL }\n\n// blank: decorating purpose\n#define ADDSET_BLANK() { stt_blank, NULL, false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL }\n\n// action: user can hit enter to execute function assigned to this setting\n#define ADDSET_ACTION(label,fnc,desc) { stt_action, label, false, NULL, 0, 0, 0, NULL, NULL, fnc, NULL, NULL, desc }\n\n// number: will show a slider allowing you to choose setting from range min..max \n#define ADDSET_NUMBER(label, var, min, max, step) { stt_num, label, false, &var, min, max, step, NULL, NULL, NULL, NULL }\n\n// intnumber: same like number, but we change a \"int\", not a \"cvar_t\"\n#define ADDSET_INTNUMBER(label, var, min, max, step) { stt_intnum, label, false, (cvar_t *) &var, min, max, step, NULL, NULL, NULL, NULL }\n\n// enum: custom name for each custom value .. works like \"named\", but you specify the range of values too\n#define ADDSET_ENUM(label, var, strs) { stt_enum, label, false, &var, 0, (sizeof(strs)/sizeof(char*))/2-1, 1, NULL, NULL, NULL, strs }\n\n// bool: will display on/off option\n#define ADDSET_BOOL(label, var) { stt_bool, label, false, &var, 0, 0, 0, NULL, NULL, NULL, NULL }\n\n// latebool: use for cvars that don't exist on compile time - be carefull when using this, may lead to crashes!\n#define ADDSET_BOOLLATE(label, var) { stt_bool, label, false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, #var }\n\n// string: will show an edit box allowing you to edit teh value\n#define ADDSET_STRING(label, var) { stt_string, label, false, &var, 0, 0, 0, NULL, NULL, NULL, NULL }\n\n// named: give it array of strings, will assign values 0, 1, ... to the variable\n#define ADDSET_NAMED(label, var, strs) { stt_named, label, false, &var, 0, sizeof(strs)/sizeof(char*)-1, 1, NULL, NULL, NULL, strs }\n\n// color\n#define ADDSET_COLOR(label, var) { stt_playercolor, label, false, &var, -1, 16, 1, NULL, NULL, NULL, NULL }\n\n// key bind\n#define ADDSET_BIND(label, cmd) { stt_bind, label, false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, cmd }\n\n// custom: completely customizable setting, define your own reading and writing function\n// see below for function types\n#define ADDSET_CUSTOM(label, readfnc, togglefnc, desc) { stt_custom, label, false, NULL, 0, 0, 0, readfnc, togglefnc, NULL, NULL, NULL, desc }\n\n// skin setting\n#define ADDSET_SKIN(label, var) { stt_skin, label, false, &var, 0, 0, 0, NULL, NULL, NULL, NULL, NULL }\n\n\n//\n// function types:\n//\n\ntypedef const char* (*enum_readfnc) (void);\ntypedef void (*enum_togglefnc) (qbool);\ntypedef void (*action_fnc) (void);\n\n\n//\n// internal structures\n//\n\ntypedef enum  {\n\tstt_separator,             // decorating purpose only, needs only type+label then\n\tstt_num,                   // integer or float variable, needs cvar, min, max and step are required\n\tstt_intnum,                // integer non-quake-variable\n\tstt_bool,                  // simple boolean setting, needs cvar\n\tstt_custom,                // fully customizable setting, needs readfnc and togglefnc\n\tstt_named,                 // named integer 0..max, max is number of elements in array of strings assigned to readfnc\n\tstt_enum,                  // named enum, pairs of \"name\", \"value\"\n\tstt_action,                // function is assigned to this, pointer must be stored in togglefnc\n\tstt_string,                // string - fully editable by the user, needs only cvar\n\tstt_playercolor,           // named enum 0..16\n\tstt_skin,                  // player skin\n\tstt_bind,                  // keybinding\n\tstt_advmark,               // denotes advanced settings area\n\tstt_basemark,              // denotes basic settings area\n\tstt_blank\n} setting_type;\n\ntypedef struct {\n\tsetting_type type;         // see above; always required\n\tconst char* label;         // to be displayed on screen; always required\n\tqbool advanced;            // is this settings advanced?\n\tcvar_t* cvar;              // assigned variable; required for num, bool, named\n\tfloat min;                 // min value; required for num, named\n\tfloat max;                 // max value; required for num, named\n\tfloat step;                // change step; required for num, named\n\tenum_readfnc readfnc;      // reading function pointer; required for enum\n\tenum_togglefnc togglefnc;  // toggle function pointer; required for enum\n\taction_fnc actionfnc;      // action function pointer; required for stt_action\n\tconst char** named_ints;   // array of strings; required for sett_named and stt_enum\n\tconst char* varname;       // name of a non-static cvar_t, also used for command name for bind\n\tconst char* description;   // manual-like description\n\tint top;                   // distance of the setting from the top of the settings page\n} setting;\n\ntypedef struct {\n\tsetting* settings;         // array of settings\n\tint count;                 // amount of elements in set_tab\n\tint marked;                // currently selected element in settings\n\tint viewpoint;\t\t   // where rendering start (internal)\n\tPScrollBar  scrollbar;     // scrollbar gui element\n\tenum {\n\t\tSPM_NORMAL,\n\t\tSPM_BINDING,\n\t\tSPM_VIEWHELP,\n\t\tSPM_CHOOSESKIN\n\t} mode;\n\tint width;                 // last drawn width\n\tint height;                // last drawn height\n\tqbool mini;                // minimalistic version (doesn't display help and has infinite scrolling)\n} settings_page;\n\nchar* SettingColorName(int color);\n\n#endif // __SETTINGS_H__\n"
  },
  {
    "path": "src/settings_page.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/**\n\n\tSettings page module\n\n\tmade by johnnycz, Jan 2007\n\tlast edit:\n\t\t$Id: settings_page.c,v 1.46 2007-09-30 22:59:23 disconn3ct Exp $\n\n*/\n\n#include \"quakedef.h\"\n#include \"settings.h\"\n#include \"Ctrl.h\"\n#include \"Ctrl_EditBox.h\"\n#include \"EX_FileList.h\"\n#include \"help.h\"\n#include \"crc.h\"\n#include \"sbar.h\"\n#include \"menu.h\"\n#include \"keys.h\"\n\n\nCEditBox editbox;\n\nextern cvar_t menu_advanced;\n\nfilelist_t skins_filelist;\n\n#define LETW 8\n#define LINEHEIGHT 8\n#define PADDING 1\n#define EDITBOXWIDTH 16\n#define EDITBOXMAXLENGTH 64\n#define HELPLINES 5\n#define SKINPREVIEWHEIGHT 120\n\n#define ENUM_NAME(setting_pointer,position) (setting_pointer->named_ints[position*2])\n#define ENUM_VALUE(setting_pointer,position) (setting_pointer->named_ints[position*2+1])\n#define ENUM_ITEM_NOT_FOUND\t-1\n\nstatic float SliderPos(float min, float max, float val) { return (val-min)/(max-min); }\n\nstatic const char* colors[17] = { \"White\", \"Brown\", \"Lavender\", \"Khaki\", \"Red\", \"Light Brown\", \"Peach\", \"Light Peach\", \"Purple\", \"Dark Purple\", \"Tan\", \"Green\", \"Yellow\", \"Blue\", \"Orange\", \"Bright Red\", \"Black\"};\n#define COLORNAME(x) colors[bound(0, ((int) x), sizeof(colors) / sizeof(char*) - 1)]\n\nchar* SettingColorName(int color)\n{\n\tstatic char tempBuffer[64];\n\n\tif (color < 0 || color > sizeof(colors) / sizeof(colors[0]))\n\t\tHost_Error(\"SettingColorName(color %d) out of range.\\n\", color);\n\n\tstrlcpy(tempBuffer, colors[color], sizeof(tempBuffer));\n\n\treturn tempBuffer;\n}\n\nfloat VARFVAL(const cvar_t *v)\n{\n\tif ((v->flags & CVAR_LATCH) && v->latchedString)\n\t\treturn atof(v->latchedString);\n\telse return v->value;\n}\n\nchar* VARSVAL(const cvar_t *v)\n{\n\tif (v->flags & CVAR_LATCH && v->latchedString)\n\t\treturn v->latchedString;\n\telse return v->string;\n}\n\nstatic int STHeight(setting* s) {\n\tif (s->advanced && !menu_advanced.value)\n\t{\n\t\treturn 0;\n\t}\n\tswitch (s->type) {\n\t\tcase stt_separator: return LINEHEIGHT*3;\n\t\tcase stt_advmark: case stt_basemark: return 0;\n\t\tdefault: return LINEHEIGHT+PADDING;\n\t}\n}\n\nstatic int Setting_PrintLabel(int x, int y, int w, const char *l, qbool active)\n{\n\tint startpos = x + w/2 - min(strlen(l), w/2)*LETW;\n\tUI_Print(startpos, y, l, (int) active);\n\tx = w/2 + x;\n\t// if (active) UI_DrawCharacter(x, y, FLASHINGARROW());\n\treturn x + LETW*2;\n}\n\n// tells what is the horizontal position of the left corner of the slider\nstatic int Slider_Startpos(int w)\n{\n\treturn w/2 + LETW*2;\n}\n\nstatic void Setting_DrawIntNum(int x, int y, int w, setting* setting, qbool active)\n{\n\tchar buf[16];\n\tx = Setting_PrintLabel(x,y,w, setting->label, active);\n\tx = UI_DrawSlider (x, y, SliderPos(setting->min, setting->max, *((int *) setting->cvar)));\n\tsnprintf(buf, sizeof(buf), \"%3d\", *((int *) setting->cvar));\n\n\tUI_Print(x + LETW, y, buf, active);\n}\n\nstatic void Setting_DrawNum(int x, int y, int w, setting* setting, qbool active)\n{\n\tchar buf[16];\n\tx = Setting_PrintLabel(x,y,w, setting->label, active);\n\tx = UI_DrawSlider (x, y, SliderPos(setting->min, setting->max, VARFVAL(setting->cvar)));\n\tif (setting->step > 0.99)\n\t\tsnprintf(buf, sizeof(buf), \"%3d\", (int) VARFVAL(setting->cvar));\n\telse if (((setting->step)*10) < 1)\n\t\tsnprintf(buf, sizeof(buf), \"%3.2f\", VARFVAL(setting->cvar));\n\telse\n\t\tsnprintf(buf, sizeof(buf), \"%3.1f\", VARFVAL(setting->cvar));\n\n\tUI_Print(x + LETW, y, buf, active);\n}\n\nstatic void Setting_DrawBool(int x, int y, int w, setting* setting, qbool active)\n{\n\tx = Setting_PrintLabel(x,y,w, setting->label, active);\n\tUI_Print(x, y, VARFVAL(setting->cvar) ? \"on\" : \"off\", active);\n}\n\nstatic void Setting_DrawBoolAdv(int x, int y, int w, setting* setting, qbool active)\n{\n\tconst char* val;\n\tx = Setting_PrintLabel(x,y,w, setting->label, active);\n\tval = setting->readfnc ? setting->readfnc() : \"off\";\n\tUI_Print(x, y, val, active);\n}\n\nstatic void Setting_DrawSeparator(int x, int y, int w, setting* set)\n{\n\tchar buf[32];\n\tsnprintf(buf, sizeof(buf), \"\\x1d %s \\x1f\", set->label);\n\tUI_Print_Center(x, y+LINEHEIGHT+LINEHEIGHT/2, w, buf, true);\n}\n\nstatic void Setting_DrawAction(int x, int y, int w, setting* set, qbool active)\n{\n\t// this will make it centered\n\tUI_Print_Center(x, y, w, set->label, active);\n\n\t// this will make it aligned to the left side from the center\n\t// Setting_PrintLabel(x, y, w, set->label, active);\n}\n\nstatic void Setting_DrawNamed(int x, int y, int w, setting* set, qbool active)\n{\n\tx = Setting_PrintLabel(x, y, w, set->label, active);\n\tUI_Print(x, y, set->named_ints[(int) bound(set->min, VARFVAL(set->cvar), set->max)], active);\n}\n\nstatic int Enum_Find_ValueCode(setting* set)\n{\n\tint i;\n\n\tfor (i = 0; i <= set->max; i++)\n\t{\n\t\tif (!strcasecmp(VARSVAL(set->cvar), ENUM_VALUE(set, i)))\n\t\t\treturn i;\n\t}\n\treturn ENUM_ITEM_NOT_FOUND;\n}\n\nstatic void Setting_DrawEnum(int x, int y, int w, setting* set, qbool active)\n{\n\tint i;\n\tx = Setting_PrintLabel(x, y, w, set->label, active);\n\ti = Enum_Find_ValueCode(set);\n\tif (i == ENUM_ITEM_NOT_FOUND) {\n\t\tUI_Print(x, y, \"custom\", active);\n\t} else {\n\t\tUI_Print(x, y, ENUM_NAME(set, i), active);\n\t}\n}\n\nstatic void Setting_DrawString(int x, int y, int w, setting* setting, qbool active)\n{\n\tint x0 = x;\n\tx = Setting_PrintLabel(x,y,w, setting->label, active);\n\tif (active) {\n\t\teditbox.width = (w - x + x0) / 8;\n\t\tCEditBox_Draw(&editbox, x, y, true);\n\t} else {\n\t\tUI_Print(x, y, VARSVAL(setting->cvar), false);\n\t}\n}\n\nstatic void Setting_DrawColor(int x, int y, int w, setting* set, qbool active)\n{\n\tx = Setting_PrintLabel(x, y, w, set->label, active);\n\tif (VARFVAL(set->cvar) >= 0) {\n\t\tDraw_Fill(x, y, LETW*3, LINEHEIGHT, Sbar_ColorForMap(VARFVAL(set->cvar)));\n\t\tUI_Print(x + 4*LETW, y, va(\"%i (%s)\", (int) VARFVAL(set->cvar), COLORNAME(VARFVAL(set->cvar))), active);\n\t} else\n\t\tUI_Print(x, y, \"off\", active);\n}\n\nstatic void Setting_DrawSkin(int x, int y, int w, setting* set, qbool active)\n{\n\tx = Setting_PrintLabel(x, y, w, set->label, active);\n\tUI_Print(x, y, set->cvar->string, active);\n}\n\nstatic void Setting_DrawBind(int x, int y, int w, setting* set, qbool active, qbool bindmode)\n{\n\tint keys[2];\n\tchar *name;\n\tchar c[2];\n\n\tc[0] = FLASHINGARROW();\n\tc[1] = 0;\n\n\tx = Setting_PrintLabel(x, y, w, set->label, active);\n\n\tif (bindmode && active) {\n\t\tUI_Print(x, y, c, active);\n\t}\n\n\t//x += LETW*2;\n\n\tM_FindKeysForCommand (set->varname, keys);\n\n\tif (keys[0] == -1) {\n\t\tUI_Print (x, y, \"???\", active);\n\t} else {\n\t\tname = Key_KeynumToString (keys[0]);\n\t\tUI_Print (x, y, name, active);\n\t\tx += strlen(name)*8;\n\t\tif (keys[1] != -1) {\n\t\t\tUI_Print (x + 8, y, \"or\", active);\n\t\t\tUI_Print (x + 4*8, y, Key_KeynumToString (keys[1]), active);\n\t\t}\n\t}\n\n}\n\nstatic void Setting_IncreaseEnum(setting* set, int step)\n{\n\tint i;\n\tif (set->type != stt_enum) return;\n\ti = Enum_Find_ValueCode(set) + step;\n\tif (i > set->max) i = 0;\n\tif (i < 0) i = set->max;\n\t// Cvar_Set doesn't take const char*\n\tCvar_Set(set->cvar, va(\"%s\", ENUM_VALUE(set, i)));\n}\n\nstatic void Setting_Increase(setting* set) {\n\tfloat newval;\n\n\tswitch (set->type) {\n\t\tcase stt_bool: Cvar_Set (set->cvar, VARFVAL(set->cvar) ? \"0\" : \"1\"); break;\n\t\tcase stt_custom: if (set->togglefnc) set->togglefnc(false); break;\n\t\tcase stt_num:\n\t\tcase stt_named:\n\t\tcase stt_playercolor:\n\t\t\t\t\t newval = VARFVAL(set->cvar) + set->step;\n\t\t\t\t\t if (set->max >= newval)\n\t\t\t\t\t\t Cvar_SetValue(set->cvar, newval);\n\t\t\t\t\t else if (set->type == stt_named || set->type == stt_playercolor)\n\t\t\t\t\t\t Cvar_SetValue(set->cvar, set->min);\n\t\t\t\t\t break;\n\t\tcase stt_action: if (set->actionfnc) set->actionfnc(); break;\n\t\tcase stt_enum: Setting_IncreaseEnum(set, set->step); break;\n\t\tcase stt_intnum:\n\t\t\t       *((int *)set->cvar) += set->step;\n\t\t\t       if (*((int *) set->cvar) > set->max) {\n\t\t\t\t       *((int *)set->cvar) = set->max;\n\t\t\t       }\n\t\t\t       break;\n\n\t\t\t       // unhandled\n\t\tcase stt_separator:\n\t\tcase stt_string:\n\t\tcase stt_bind:\n\t\tcase stt_skin:\n\t\tcase stt_advmark:\n\t\tcase stt_basemark:\n\t\tcase stt_blank:\n\t\t\t       break;\n\t}\n}\n\nstatic void Setting_Decrease(setting* set) {\n\tfloat newval;\n\n\tswitch (set->type) {\n\t\tcase stt_bool: Cvar_Set (set->cvar, VARFVAL(set->cvar) ? \"0\" : \"1\"); break;\n\t\tcase stt_custom: if (set->togglefnc) set->togglefnc(true); break;\n\t\tcase stt_num:\n\t\tcase stt_named:\n\t\tcase stt_playercolor:\n\t\t\t\t\t newval = VARFVAL(set->cvar) - set->step;\n\t\t\t\t\t if (set->min <= newval)\n\t\t\t\t\t\t Cvar_SetValue(set->cvar, newval);\n\t\t\t\t\t else if (set->type == stt_named || set->type == stt_playercolor)\n\t\t\t\t\t\t Cvar_SetValue(set->cvar, set->max);\n\t\t\t\t\t break;\n\n\t\tcase stt_enum: Setting_IncreaseEnum(set, -set->step); break;\n\t\tcase stt_intnum:\n\t\t\t       *((int *)set->cvar) -= set->step;\n\t\t\t       if (*((int *) set->cvar) < set->min) {\n\t\t\t\t       *((int *)set->cvar) = set->min;\n\t\t\t       }\n\t\t\t       break;\n\n\t\t\t       //unhandled\n\t\tcase stt_separator:\n\t\tcase stt_action:\n\t\tcase stt_string:\n\t\tcase stt_bind:\n\t\tcase stt_skin:\n\t\tcase stt_advmark:\n\t\tcase stt_basemark:\n\t\tcase stt_blank:\n\t\t\t       break;\n\t}\n}\n\nstatic void Setting_Reset(setting* set)\n{\n\tswitch (set->type) {\n\t\tcase stt_num:\n\t\tcase stt_string:\n\t\tcase stt_named:\n\t\tcase stt_bool:\n\t\t\tCvar_ResetVar(set->cvar);\n\t\t\tbreak;\n\t\t\t// unhandled\n\t\tcase stt_bind:\n\t\tcase stt_separator:\n\t\tcase stt_custom:\n\t\tcase stt_enum:\n\t\tcase stt_action:\n\t\tcase stt_playercolor:\n\t\tcase stt_skin:\n\t\tcase stt_advmark:\n\t\tcase stt_basemark:\n\t\tcase stt_intnum:\n\t\tcase stt_blank:\n\t\t\tbreak;\n\t}\n}\n\nstatic void Setting_BindKey(setting* set, int key)\n{\n\tKey_SetBinding(key, set->varname);\n}\n\nstatic void M_UnbindCommand (const char *command) {\n\tint j, l;\n\tchar *b;\n\n\tl = strlen(command) + 1;\n\n\tfor (j = 0; j < (sizeof(keybindings) / sizeof(*keybindings)); j++) {\n\t\tb = keybindings[j];\n\t\tif (!b)\n\t\t\tcontinue;\n\t\tif (!strncmp (b, command, l) )\n\t\t\tKey_Unbind (j);\n\t}\n}\n\nstatic void Setting_UnbindKey(setting* set)\n{\n\tM_UnbindCommand(set->varname);\n}\n\nstatic int Settings_PageHeight(const settings_page *page)\n{\n\treturn page->settings[page->count - 1].top + STHeight(page->settings + page->count - 1);\n}\n\n// will find the lowest number of the setting on 'page' that can be used as the lowest viewpoint\n// and will ensure that whole bottom of the page is still visible\nstatic int Settings_LowestViewpoint(const settings_page *page)\n{\n\tint bottom = Settings_PageHeight(page);\n\n\t// represents the 'top' number we are looking for\n\tint best_top = bottom - page->height;\n\n\t// presume the lowest viewpoint is the last entry\n\tint lwp = page->count - 1;\n\n\t// this is when the page fits whole on the screen\n\tif (bottom < page->height) return 0;\n\n\tif (!page->count) return 0;\n\n\t// it can only get better from now\n\twhile (lwp && page->settings[lwp].top >= best_top) lwp--;\n\n\treturn lwp + 1;\n}\n\n// adjusts current viewed area of the settings page\nstatic void CheckViewpoint(settings_page *tab)\n{\n\tint lwp = Settings_LowestViewpoint(tab);\n\n\ttab->viewpoint = bound(0, tab->viewpoint, lwp);\n\n\tif (tab->marked == 1 && tab->settings[0].type == stt_separator) tab->viewpoint = 0;\n\n\tif (tab->viewpoint > tab->marked) {\n\t\t// marked entry is above us\n\t\ttab->viewpoint = tab->marked;\n\t} else while(STHeight(tab->settings + tab->marked) + tab->settings[tab->marked].top > tab->settings[tab->viewpoint].top + tab->height) {\n\t\t// marked entry is below\n\t\ttab->viewpoint++;\n\t}\n}\n\nstatic void CheckCursor(settings_page *tab, qbool up)\n{\t// this makes sure that cursor doesn't point at some meta-entry, section heading or hidden setting\n\tsetting *s;\n\twhile (tab->marked < 0) {\n\t\tif (tab->mini) {\n\t\t\ttab->marked = tab->count - 1;\n\t\t}\n\t\telse {\n\t\t\ttab->marked = 0;\n\t\t}\n\t\tup = false;\n\t}\n\tif (tab->marked >= tab->count) {\n\t\tif (tab->mini) {\n\t\t\ttab->marked = 1;\n\t\t}\n\t\telse {\n\t\t\ttab->marked = tab->count - 1;\n\t\t}\n\t\tup = true;\n\t}\n\ts = tab->settings + tab->marked;\n\tif (s->type == stt_separator || (s->advanced && !menu_advanced.value) || s->type == stt_advmark || s->type == stt_basemark || s->type == stt_blank) {\n\t\ttab->marked += up ? -1 : +1;\n\t\tCheckCursor(tab, up);\n\t}\n}\n\nstatic void StringEntryLeave(setting* set) {\n\tCvar_Set(set->cvar, editbox.text);\n}\n\nstatic void StringEntryEnter(setting* set) {\n\tCEditBox_Init(&editbox, EDITBOXWIDTH, EDITBOXMAXLENGTH);\n\tstrlcpy(editbox.text, set->cvar->string, EDITBOXMAXLENGTH);\n}\n\nstatic void EditBoxCheck(settings_page* tab, int oldm, int newm)\n{\n\tif (tab->settings[oldm].type == stt_string && oldm != newm)\n\t\tStringEntryLeave(tab->settings + oldm);\n\tif (tab->settings[newm].type == stt_string && oldm != newm)\n\t\tStringEntryEnter(tab->settings + newm);\n}\n\nstatic void RecalcPositions(settings_page* page)\n{\t// especially when \"show advanced options\" is being changed\n\t// we need to recalculate where each option is\n\tint i;\n\tint curtop = 0;\n\tsetting *s;\n\n\tfor (i = 0; i < page->count; i++)\n\t{\n\t\ts = page->settings + i;\n\t\ts->top = curtop;\n\n\t\tcurtop += STHeight(s);\n\t}\n}\n\nstatic int Setting_DrawHelpBox(int x, int y, int w, int h, settings_page* page, qbool full)\n{\n\tsetting* s;\n\tchar buf[2048];\n\tconst char *helptext = \"\";\n\tint maxh;\n\tconst char *cp;\n\n\ts = page->settings + page->marked;\n\n\tswitch (s->type) {\n\t\tcase stt_bool:\n\t\tcase stt_named:\n\t\tcase stt_num:\n\t\tcase stt_string:\n\t\tcase stt_playercolor:\n\t\tcase stt_enum:\n\t\t\tbuf[0] = 0;\n\t\t\thelptext = \"Further info not available...\";\n\t\t\tHelp_VarDescription (s->cvar->name, buf, sizeof(buf));\n\t\t\thelptext = buf;\n\t\t\tbreak;\n\n\t\tcase stt_bind:\n\t\t\tif (page->mode == SPM_BINDING)\n\t\t\t\thelptext = \"Press the key you want to assiciate with given action\";\n\t\t\telse\n\t\t\t\thelptext = \"Press Enter to change the associated key; Press Del to remove the binding\";\n\t\t\tbreak;\n\n\t\tcase stt_skin:\n\t\t\thelptext = \"Press [Enter] to choose a skin image for this type of player\";\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tif (s->description)\n\t\t\t\thelptext = s->description;\n\t\t\tbreak;\n\t}\n\n\tif (full) {\n\t\t// add some lines for wrapped words, just a rough approximation here\n\t\tmaxh = (int) ((((double) strlen(helptext) / (w / LETW - 2)) + 2) * 1.33) * LETW;\n\t\tcp = helptext;\n\t\twhile (*cp) { if (*cp++ == '\\n') maxh += LETW; } // add new line for each newline\n\t\tmaxh = max((HELPLINES + 2) * LETW, maxh);\n\t\tif (maxh < h) {\n\t\t\ty += h - maxh;\n\t\t\th = maxh;\n\t\t}\n\t}\n\n\tUI_DrawBox(x, y, w, h);\n\n\tif (!UI_PrintTextBlock(x + LETW, y + LETW, w - LETW*2, h - LETW*2, helptext, false) && !full)\n\t\tUI_Print(x + LETW, y + h - LETW, \"Press [F1] to read more...\", true);\n\n\treturn h;\n}\n\n// will draw the skin preview in given window\nstatic void Setting_DrawSkinPreview(int x, int y, int w, int h, char *skinfile)\n{\n\tstatic mpic_t *curpic = NULL;\n\tstatic char lastpicname[MAX_PATH] = \"\";\n\n\tif (!skinfile) return;\n\n\t// this means the length of \"qw/\"\n#define QWDIRLEN 3\n\n\tUI_DrawBox(x, y, w, h);\n\tx += LETTERWIDTH;\n\ty += LETTERHEIGHT;\n\tw -= LETTERWIDTH;\n\th -= LETTERHEIGHT;\n\n\tif (strcmp(lastpicname, skinfile))\n\t{\n\t\tchar *c;\n\t\tchar buf[MAX_PATH];\n\n\t\t// get the \"qw/skins/freddy\" part from the full path\n\t\tif (strlen(skinfile) <= strlen(com_basedir) + QWDIRLEN)\n\t\t{\n\t\t\treturn;\n\t\t}\n\t\tc = skinfile + strlen(com_basedir) + QWDIRLEN + 1;\n\t\tCOM_StripExtension(c, buf, sizeof(buf));\n\n\t\tcurpic = Draw_CachePicSafe(buf, false, true);\n\t\tstrlcpy(lastpicname, skinfile, sizeof(lastpicname));\n\t}\n\n\tif (curpic)\n\t{\n\t\tDraw_FitPic(x, y, w, h, curpic);\n\t}\n#undef QWDIRLEN\n}\n\nstatic int FindSetting_AtPos(const settings_page *page, int top)\n{\n\tint i, r = 0;\n\tfor (i = 0; i < page->count; i++)\n\t{\n\t\tif (page->settings[i].top < top)\n\t\t\tr = i;\n\t}\n\treturn r;\n}\n\n// will choose correct value for the selected setting according to\n// where the user has clicked on the slidebar\nstatic void Setting_Slider_Click(const settings_page *page, const mouse_state_t *ms)\n{\n\tdouble p, vmin, vmax, vnew, vsteps;\n\tsetting* s = page->settings + page->marked;\n\n\tp = (ms->x - Slider_Startpos(page->width)) / UI_SliderWidth();\n\tp = bound(0, p, 1);\n\n\tif (s->type != stt_num && s->type != stt_intnum) return;\n\n\tvmin = s->min;\n\tvmax = s->max;\n\tvnew = vmin + (vmax-vmin) * p;\n\tfor (vsteps = vmin; vsteps < vmax && vsteps < vnew; vsteps += s->step) ; // empty body\n\n\tif (s->type == stt_intnum) {\n\t\t*((int *) s->cvar) = vsteps;\n\t}\n\telse {\n\t\tCvar_SetValue(s->cvar, vsteps);\n\t}\n}\n\nqbool Settings_Key(settings_page* tab, int key, wchar unichar)\n{\n\tqbool up = false;\n\tsetting_type type;\n\tint oldm = tab->marked;\n\tchar *skinpath;\n\tqbool skip_check_viewpoint = false;\n\n\ttype = tab->settings[tab->marked].type;\n\n\tif (tab->mode == SPM_BINDING) {\n\t\tif (key != K_ESCAPE)\n\t\t\tSetting_BindKey(tab->settings + tab->marked, key);\n\n\t\ttab->mode = SPM_NORMAL;\n\t\treturn true;\n\t}\n\n\tif (tab->mode == SPM_CHOOSESKIN) {\n\t\tif (key == K_ENTER || key == K_MOUSE1) {\n\t\t\tchar buf[MAX_PATH];\n\t\t\tskinpath = FL_GetCurrentPath(&skins_filelist);\n\t\t\tif (skinpath) {\n\t\t\t\tCOM_StripExtension(COM_SkipPath(skinpath), buf, sizeof(buf));\n\t\t\t\tif (strcmp(buf, \".\"))\n\t\t\t\t\tCvar_Set(tab->settings[tab->marked].cvar, buf);\n\t\t\t}\n\t\t\ttab->mode = SPM_NORMAL;\n\t\t\treturn true;\n\t\t}\n\n\t\tif (key == K_ESCAPE || key == K_MOUSE2) {\n\t\t\ttab->mode = SPM_NORMAL;\n\t\t\treturn true;\n\t\t}\n\n\t\treturn FL_Key(&skins_filelist, key);\n\t}\n\n\tif (tab->mode == SPM_VIEWHELP) {\n\t\ttab->mode = SPM_NORMAL;\n\t\treturn true;\n\t}\n\n\tswitch (key) {\n\t\tcase K_DOWNARROW:   tab->marked++; break;\n\t\tcase K_UPARROW:     tab->marked--; up = true; break;\n\t\tcase K_MWHEELDOWN:\n\t\t\t\t    if (tab->viewpoint < Settings_LowestViewpoint(tab))\n\t\t\t\t\t    tab->viewpoint++;\n\n\t\t\t\t    skip_check_viewpoint = true;\n\t\t\t\t    break;\n\n\t\tcase K_MWHEELUP:\n\t\t\t\t    if (tab->viewpoint > 0)\n\t\t\t\t\t    tab->viewpoint--;\n\t\t\t\t    skip_check_viewpoint = true;\n\t\t\t\t    break;\n\n\t\tcase K_PGDN: tab->marked += 5; break;\n\t\tcase K_PGUP: tab->marked -= 5; up = true; break;\n\t\tcase K_END: tab->marked = tab->count - 1; up = true; break;\n\t\tcase K_HOME: tab->marked = 0; break;\n\n\t\tcase K_RIGHTARROW:\n\t\t\tswitch (type) {\n\t\t\t\tcase stt_string:\n\t\t\t\t\tCEditBox_Key(&editbox, key, unichar);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tSetting_Increase(tab->settings + tab->marked);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase K_LEFTARROW:\n\t\t\tswitch (type) {\n\t\t\t\tcase stt_string:\n\t\t\t\t\tCEditBox_Key(&editbox, key, unichar);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tSetting_Decrease(tab->settings + tab->marked);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase K_ENTER:\n\t\tcase K_MOUSE1:\n\t\tcase '=':\n\t\tcase KP_PLUS:\n\t\t\t     switch (type) {\n\t\t\t\t     case stt_string: StringEntryLeave(tab->settings + tab->marked); break;\n\t\t\t\t     case stt_bind: tab->mode = SPM_BINDING; break;\n\t\t\t\t     case stt_skin: tab->mode = SPM_CHOOSESKIN; break;\n\t\t\t\t     default: Setting_Increase(tab->settings + tab->marked); break;\n\t\t\t     }\n\t\t\t     return true;\n\n\t\tcase K_BACKSPACE: case '-': case KP_MINUS:\n\t\t\t     switch (type) {\n\t\t\t\t     case stt_action: return false;\n\t\t\t\t     case stt_string: CEditBox_Key(&editbox, key, unichar); return true;\n\t\t\t\t     default: Setting_Decrease(tab->settings + tab->marked);\treturn true;\n\t\t\t     }\n\n\t\tcase K_DEL:\n\t\t\t     switch (type) {\n\t\t\t\t     case stt_string: CEditBox_Key(&editbox, key, unichar); return true;\n\t\t\t\t     case stt_bind: Setting_UnbindKey(tab->settings + tab->marked); return true;\n\t\t\t\t     default: Setting_Reset(tab->settings + tab->marked); return true;\n\t\t\t     }\n\n\t\tcase K_F1:\n\t\tcase K_INS:\n\t\t\t     switch (tab->mode) {\n\t\t\t\t     case SPM_NORMAL: tab->mode = SPM_VIEWHELP; return true;\n\t\t\t\t     case SPM_VIEWHELP: tab->mode = SPM_NORMAL; return true;\n\t\t\t\t\t\t\t// unhandled\n\t\t\t\t     case SPM_BINDING:\n\t\t\t\t     case SPM_CHOOSESKIN:\n\t\t\t\t\t\t\tbreak;\n\t\t\t     }\n\t\t\t     break;\n\n\t\tcase K_MOUSE2:\n\t\t\t     if (tab->mode == SPM_VIEWHELP) {\n\t\t\t\t     tab->mode = SPM_NORMAL; return true;\n\t\t\t     } else return false;\n\n\t\tcase K_ESCAPE:\n\t\t\t     if (tab->mode == SPM_VIEWHELP) {\n\t\t\t\t     tab->mode = SPM_NORMAL;\n\t\t\t\t     return true;\n\t\t\t     } else return false;\n\n\t\tdefault:\n\t\t\t     switch (type) {\n\t\t\t\t     case stt_string:\n\t\t\t\t\t     if (key != K_TAB && key != K_ESCAPE && key != K_LEFTARROW && key != K_RIGHTARROW) {\n\t\t\t\t\t\t     CEditBox_Key(&editbox, key, unichar);\n\t\t\t\t\t\t     return true;\n\t\t\t\t\t     }\n\t\t\t\t\t     return false;\n\n\t\t\t\t     default: return false;\n\t\t\t     }\n\t}\n\n\tCheckCursor(tab, up);\n\n\tif (!skip_check_viewpoint)\n\t\tCheckViewpoint(tab);\n\n\tEditBoxCheck(tab, oldm, tab->marked);\n\treturn true;\n}\n\nstatic void Setting_Click(settings_page* page, const mouse_state_t *ms)\n{\n\t// don't accept clicks on the label\n\tif (ms->x < page->width/2 && (page->settings + page->marked)->type != stt_action) return;\n\n\tif (page->settings[page->marked].type == stt_num) {\n\t\tSetting_Slider_Click(page, ms);\n\t} else Settings_Key(page, K_MOUSE1, 0);\n}\n\nstatic void Settings_AdjustScrollBar(settings_page *page)\n{\n\tdouble lwp = Settings_LowestViewpoint(page);\n\tdouble percentage = lwp ? (double) page->viewpoint / lwp : 0;\n\n\t// do not adjust the scrollbar if we are scrolling, it would look weird\n\tif (page->scrollbar->mouselocked) return;\n\n\tpage->scrollbar->curpos = bound(0, percentage, 1);\n}\n\nvoid Settings_Draw(int x, int y, int w, int h, settings_page* tab)\n{\n\tint i;\n\tint ch;\n\t//int nexttop;\n\tint hbh = 0;\t// help box height\n\tsetting *set;\n\tqbool active;\n\tstatic qbool prev_adv_state = false;\n\n\tif (!tab->count) return;\n\n\tif (prev_adv_state != (qbool) menu_advanced.value) {\n\t\t// someone toggled menu_advanced setting right in the currently viewed menu!\n\t\tRecalcPositions(tab);\n\t\tprev_adv_state = (qbool) menu_advanced.value;\n\t}\n\n\t//nexttop = tab->settings[0].top;\n\n\tif (tab->mode == SPM_CHOOSESKIN)\n\t{\n\t\tFL_Draw(&skins_filelist, x, y, w, h - SKINPREVIEWHEIGHT);\n\t\tSetting_DrawSkinPreview(x, y + h - SKINPREVIEWHEIGHT, w, SKINPREVIEWHEIGHT, FL_GetCurrentPath(&skins_filelist));\n\t\treturn;\n\t}\n\n\tw -= tab->scrollbar->width;\n\n\tif (!tab->mini) {\n\t\tif (tab->mode != SPM_VIEWHELP) {\n\t\t\thbh = HELPLINES * LINEHEIGHT;\n\t\t\th -= Setting_DrawHelpBox(x, y + h - hbh, w, hbh, tab, false);\n\t\t} else {\n\t\t\thbh = h - LINEHEIGHT * 4;\n\t\t\th -= Setting_DrawHelpBox(x, y + h - hbh, w, hbh, tab, true);\n\t\t}\n\t}\n\n\ttab->width = w; tab->height = h;\n\n\tSettings_AdjustScrollBar(tab);\n\tif (tab->height < Settings_PageHeight(tab))\n\t\tScrollBar_Draw(tab->scrollbar, x + w, y, h);\n\n\tfor (i = tab->viewpoint; i < tab->count && tab->settings[i].top + STHeight(tab->settings + i) <= h + tab->settings[tab->viewpoint].top; i++)\n\t{\n\t\tactive = i == tab->marked;\n\t\tset = tab->settings + i;\n\t\tch = STHeight(tab->settings + i);\n\t\tif ((set->advanced && !menu_advanced.value) || set->type == stt_advmark || set->type == stt_basemark) continue;\n\n\t\tif (active && set->type != stt_separator) {\n\t\t\tUI_DrawGrayBox(x, y, w, ch);\n\t\t}\n\t\tswitch (set->type) {\n\t\t\tcase stt_bool: if (set->cvar) Setting_DrawBool(x, y, w, set, active); break;\n\t\t\tcase stt_custom: Setting_DrawBoolAdv(x, y, w, set, active); break;\n\t\t\tcase stt_num: Setting_DrawNum(x, y, w, set, active); break;\n\t\t\tcase stt_intnum: Setting_DrawIntNum(x, y, w, set, active); break;\n\t\t\tcase stt_separator: Setting_DrawSeparator(x, y, w, set); break;\n\t\t\tcase stt_action: Setting_DrawAction(x, y, w, set, active); break;\n\t\t\tcase stt_named: Setting_DrawNamed(x, y, w, set, active); break;\n\t\t\tcase stt_enum: Setting_DrawEnum(x, y, w, set, active); break;\n\t\t\tcase stt_string: Setting_DrawString(x, y, w, set, active); break;\n\t\t\tcase stt_playercolor: Setting_DrawColor(x, y, w, set, active); break;\n\t\t\tcase stt_skin: Setting_DrawSkin(x, y, w, set, active); break;\n\t\t\tcase stt_bind: Setting_DrawBind(x, y, w, set, active, tab->mode == SPM_BINDING); break;\n\t\t\t\t       // unhandled\n\t\t\tcase stt_advmark:\n\t\t\tcase stt_basemark:\n\t\t\tcase stt_blank:\n\t\t\t\t       break;\n\t\t}\n\t\ty += ch;\n\t\t//if (i < tab->count)\n\t\t//\tnexttop = tab->settings[i+1].top;\n\t}\n}\n\nvoid Settings_OnShow(settings_page *page)\n{\n\tRecalcPositions(page);\n\n\tCheckCursor(page, false);\n\n\tif (page->settings[page->marked].type == stt_string)\n\t\tStringEntryEnter(page->settings + page->marked);\n}\n\nqbool Settings_Mouse_Event(settings_page *page, const mouse_state_t *ms)\n{\n\tint nmark;\n\tint omark = page->marked;\n\n\t// scrollbar associated with this page handles the event\n\t// page has to be in normal mode and user is already scrolling or\n\t// just started scrolling\n\tif (page->mode == SPM_NORMAL && (page->scrollbar->mouselocked ||\n\t\t\t\t(ms->x > (page->width - page->scrollbar->width))))\n\t{\n\t\tif (ScrollBar_MouseEvent(page->scrollbar, ms))\n\t\t{\n\t\t\tpage->viewpoint = Settings_LowestViewpoint(page) * page->scrollbar->curpos;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tif (page->mode == SPM_NORMAL && ms->button_up == 1) {\n\t\tSetting_Click(page, ms);\n\t\treturn true;\n\t}\n\n\tswitch (page->mode) {\n\t\tcase SPM_BINDING:\n\t\t\tif (ms->button_down) return true;\n\t\t\tif (ms->button_up) {\n\t\t\t\tSetting_BindKey(page->settings + page->marked, K_MOUSE1 + ms->button_up - 1);\n\t\t\t\tpage->mode = SPM_NORMAL;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase SPM_CHOOSESKIN:\n\t\t\tif (FL_Mouse_Event(&skins_filelist, ms)) return true;\n\t\t\telse if (ms->button_up == 1) Settings_Key(page, K_MOUSE1, 0);\n\t\t\treturn true;\n\t\t\tbreak;\n\n\t\tcase SPM_NORMAL:\n\t\t\tnmark = FindSetting_AtPos(page, page->settings[page->viewpoint].top + ms->y);\n\t\t\tnmark = bound(0, nmark, page->count - 1);\n\t\t\tif ((page->settings[nmark].type == stt_num || page->settings[nmark].type == stt_intnum)\n\t\t\t\t\t&& ms->buttons[1] == true)\n\t\t\t{\n\t\t\t\tSetting_Slider_Click(page, ms);\n\t\t\t}\n\t\t\telse if (page->settings[nmark].type != stt_separator)\n\t\t\t{\n\t\t\t\tpage->marked = nmark;\n\t\t\t\tCheckCursor(page, ms->x < ms->x_old);\n\t\t\t\tEditBoxCheck(page, omark, nmark);\n\t\t\t}\n\t\t\treturn true;\n\n\t\tcase SPM_VIEWHELP:\n\t\t\treturn false;\n\t\t\tbreak;\n\t}\n\treturn false;\n}\n\nvoid Settings_Init(settings_page *page, setting *arr, int size, const char* name)\n{\n\tint i;\n\tqbool onlyseparators = true;\n\tqbool advancedmode = false;\n\n\tpage->count = size;\n\tpage->marked = 0;\n\tpage->settings = arr;\n\tpage->viewpoint = 0;\n\tpage->mode = SPM_NORMAL;\n\tpage->scrollbar = ScrollBar_Create(NULL, name);\n\tpage->mini = false;\n\n\tfor (i = 0; i < size; i++) {\n\t\tif (arr[i].type == stt_advmark) advancedmode = true;\n\t\tif (arr[i].type == stt_basemark) advancedmode = false;\n\t\tarr[i].advanced = advancedmode;\n\n\t\tif (onlyseparators && arr[i].type != stt_separator) {\n\t\t\tonlyseparators = false;\n\t\t\tpage->marked = i;\n\t\t}\n\t\tif (arr[i].varname && !arr[i].cvar && arr[i].type == stt_bool) {\n\t\t\tarr[i].cvar = Cvar_Find(arr[i].varname);\n\t\t\tarr[i].varname = NULL;\n\t\t\tif (!arr[i].cvar)\n\t\t\t\tCbuf_AddText(va(\"Warning: variable %s not found\\n\", arr[i].varname));\n\t\t}\n\t}\n\n\tRecalcPositions(page);\n\n\tif (onlyseparators) {\n\t\tCbuf_AddText(\"Warning (Settings_Init): menu contained only separators\\n\");\n\t\tpage->count = 0;\n\t}\n}\n\nvoid Settings_Shutdown(settings_page *page)\n{\n\tQ_free(page->scrollbar);\n}\n\nvoid Settings_MainInit(void)\n{\n\tFL_Init(&skins_filelist, \"./qw/skins\");\n\n\tFL_SetDirUpOption(&skins_filelist, false);\n\tFL_SetDirsOption(&skins_filelist, false);\n\tFL_AddFileType(&skins_filelist, 0, \".pcx\");\n\tFL_AddFileType(&skins_filelist, 1, \".png\");\n\tFL_AddFileType(&skins_filelist, 2, \".jpg\");\n\tFL_AddFileType(&skins_filelist, 3, \".tga\");\n}\n\nvoid Settings_MainShutdown(void)\n{\n\tFL_Shutdown(&skins_filelist);\n}\n"
  },
  {
    "path": "src/settings_page.h",
    "content": "/*\nCopyright (C) 2011 johnnycz\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/** Guide to create settings page:\n\t1) make new variables:\n\t\ta) 'settings_page' variable\n\t\tb) array of 'setting' elements, use macros from settings.h to do this\n\t2) in some Init function call Settings_Page_Init(a, b); - a and b are variables from steps a and b\n\t3) place calls to _Draw and _Key functions to appropriate places\n */\n\n#ifndef __SETTINGS_PAGE_H__\n#define __SETTINGS_PAGE_H__\n\n#include \"keys.h\"\n\n#define Settings_Page_Init(set_page, settings) Settings_Init(&set_page, settings, (int)(sizeof(settings) / sizeof(setting)), #set_page)\n#define Settings_Page_SetMinit(set_page) { set_page.mini = true; }\n\n// initializes the page structure\nvoid Settings_Init(settings_page *page, setting *settings, int size, const char* name);\n\nvoid Settings_Shutdown(settings_page *page);\n\n// initilalize settings pages structures\nvoid Settings_MainInit(void);\nvoid Settings_MainShutdown(void);\n\n// draw request handler\nvoid Settings_Draw(int x, int y, int w, int h, settings_page* page);\n\n// optonal call at the moment of first display\nvoid Settings_OnShow(settings_page *tab);\n\n// key press handler\nqbool Settings_Key(settings_page* page, int key, wchar unichar);\n\n// mouse move handler\nqbool Settings_Mouse_Event(settings_page * page, const mouse_state_t *ms);\n\n#endif // __SETTINGS_PAGE_H__\n"
  },
  {
    "path": "src/sha1.c",
    "content": "/*\nSHA-1 in C\nBy Steve Reid <steve@edmweb.com>\n100% Public Domain\n\nTest Vectors (from FIPS PUB 180-1)\n\"abc\"\n  A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D\n\"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq\"\n  84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1\nA million repetitions of \"a\"\n  34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F\n\n\t$Id: sha1.c,v 1.11 2007-08-08 00:53:29 disconn3ct Exp $\n*/\n\n/* #define SHA1HANDSOFF * Copies data before messing with it. */\n\n#include <stdio.h>\n#include <string.h>\n#include \"sha1.h\"\n#include \"common.h\"\n\n/* Hash a single 512-bit block. This is the core of the algorithm. */\n\nvoid SHA1Transform(unsigned int state[5], unsigned char buffer[64])\n{\n    unsigned int a, b, c, d, e;\n    typedef union {\n        unsigned char c[64];\n        unsigned int l[16];\n    } CHAR64LONG16;\n    CHAR64LONG16* block;\n#ifdef SHA1HANDSOFF\n    static unsigned char workspace[64];\n    block = (CHAR64LONG16*)workspace;\n    memcpy(block, buffer, 64);\n#else\n    block = (CHAR64LONG16*)buffer;\n#endif\n    /* Copy context->state[] to working vars */\n    a = state[0];\n    b = state[1];\n    c = state[2];\n    d = state[3];\n    e = state[4];\n    /* 4 rounds of 20 operations each. Loop unrolled. */\n    R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);\n    R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);\n    R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);\n    R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);\n    R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);\n    R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);\n    R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);\n    R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);\n    R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);\n    R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);\n    R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);\n    R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);\n    R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);\n    R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);\n    R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);\n    R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);\n    R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);\n    R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);\n    R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);\n    R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);\n    /* Add the working vars back into context.state[] */\n    state[0] += a;\n    state[1] += b;\n    state[2] += c;\n    state[3] += d;\n    state[4] += e;\n    /* Wipe variables */\n    a = b = c = d = e = 0;\n}\n\n\n/* SHA1Init - Initialize new context */\n\nvoid SHA1Init(SHA1_CTX* context)\n{\n    /* SHA1 initialization constants */\n    context->state[0] = 0x67452301;\n    context->state[1] = 0xEFCDAB89;\n    context->state[2] = 0x98BADCFE;\n    context->state[3] = 0x10325476;\n    context->state[4] = 0xC3D2E1F0;\n    context->count[0] = context->count[1] = 0;\n}\n\n\n/* Run your data through this. */\n\nvoid SHA1Update(SHA1_CTX* context, unsigned char* data, size_t len)\n{\n\tvolatile unsigned int i, j;\n\n    j = (context->count[0] >> 3) & 63;\n\tif ((context->count[0] += len << 3) < (len << 3)) {\n\t\tcontext->count[1]++;\n\t}\n    context->count[1] += (len >> 29);\n    if ((j + len) > 63) {\n        memcpy(&context->buffer[j], data, (i = 64-j));\n        SHA1Transform(context->state, context->buffer);\n        for ( ; i + 63 < len; i += 64) {\n            SHA1Transform(context->state, &data[i]);\n        }\n        j = 0;\n    }\n\telse {\n\t\ti = 0;\n\t}\n    memcpy(&context->buffer[j], &data[i], len - i);\n}\n\n\n/* Add padding and return the message digest. */\n\nvoid SHA1Final(unsigned char digest[DIGEST_SIZE], SHA1_CTX* context)\n{\nunsigned int i, j;\nunsigned char finalcount[8];\n\n    for (i = 0; i < 8; i++) {\n        finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]\n         >> ((3-(i & 3)) * 8) ) & 255);  /* Endian independent */\n    }\n    SHA1Update(context, (unsigned char *)\"\\200\", 1);\n    while ((context->count[0] & 504) != 448) {\n        SHA1Update(context, (unsigned char *)\"\\0\", 1);\n    }\n    SHA1Update(context, finalcount, 8);  /* Should cause a SHA1Transform() */\n    for (i = 0; i < DIGEST_SIZE; i++) {\n        digest[i] = (unsigned char)\n         ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);\n    }\n    /* Wipe variables */\n    i = j = 0;\n    memset(context->buffer, 0, 64);\n    memset(context->state, 0, 20);\n    memset(context->count, 0, 8);\n    memset(&finalcount, 0, 8);\n#ifdef SHA1HANDSOFF  /* make SHA1Transform overwrite it's own static vars */\n    SHA1Transform(context->state, context->buffer);\n#endif\n}\n\n//VVD: SHA1 crypt\nchar *bin2hex(unsigned char *d)\n{\n\tstatic char\tret[DIGEST_SIZE * 2 + 1];\n\tint\t\ti;\n\tfor (i = 0; i < DIGEST_SIZE * 2; i += 2, d++)\n\t\tsnprintf(ret + i, DIGEST_SIZE * 2 + 1 - i, \"%02X\", *d);\n\treturn ret;\n}\n\nchar *SHA1(char *string)\n{\n\tSHA1_CTX\tcontext;\n\tunsigned char\tdigest[DIGEST_SIZE];\n\tSHA1Init(&context);\n\tSHA1Update(&context, (unsigned char*) string, strlen(string));\n\tSHA1Final(digest, &context);\n\treturn bin2hex(digest);\n}\n\nSHA1_CTX\tcontext;\nvoid SHA1_Init(void)\n{\n\tSHA1Init(&context);\n}\nvoid SHA1_Update(unsigned char *string)\n{\n\tSHA1Update(&context, string, strlen((char *)string));\n}\nchar *SHA1_Final(void)\n{\n\tunsigned char\tdigest[DIGEST_SIZE];\n\tSHA1Final(digest, &context);\n\treturn bin2hex(digest);\n}\n"
  },
  {
    "path": "src/sha1.h",
    "content": "/*\nSHA-1 in C\nBy Steve Reid <steve@edmweb.com>\n100% Public Domain\n\nTest Vectors (from FIPS PUB 180-1)\n\"abc\"\n  A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D\n\"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq\"\n  84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1\nA million repetitions of \"a\"\n  34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F\n\n\t$Id: sha1.h,v 1.9 2007-08-08 00:53:29 disconn3ct Exp $\n*/\n\n#define SHA1HANDSOFF /* Copies data before messing with it. */\n#ifndef _SHA1\n#define _SHA1\ntypedef struct {\n    unsigned int state[5];\n    size_t count[2];\n    unsigned char buffer[64];\n} SHA1_CTX;\n\n#define DIGEST_SIZE 20\nvoid SHA1Transform(unsigned int state[5], unsigned char buffer[64]);\nvoid SHA1Init(SHA1_CTX* context);\nvoid SHA1Update(SHA1_CTX* context, unsigned char* data, size_t len);\nvoid SHA1Final(unsigned char digest[DIGEST_SIZE], SHA1_CTX* context);\n\n#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))\n\n/* blk0() and blk() perform the initial expand. */\n/* I got the idea of expanding during the round function from SSLeay */\n/*\n#ifdef __BIG_ENDIAN\n#define blk0(i) block->l[i]\n#else\n#define blk0(i) (block->l[i] =\t(rol(block->l[i], 24) & 0xFF00FF00) | \\\n\t\t\t\t\t\t\t    (rol(block->l[i],  8) & 0x00FF00FF))\n#endif\n*/\n\n#define blk0(i) (block->l[i] = BigLong(block->l[i]))\n\n#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \\\n    ^block->l[(i+2)&15]^block->l[i&15],1))\n\n/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */\n#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);\n#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);\n#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);\n#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);\n#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);\n\n//VVD: SHA1 crypt\nchar *SHA1(char *string);\nvoid SHA1_Init(void);\nvoid SHA1_Update(unsigned char* data);\nchar *SHA1_Final(void);\nchar *bin2hex(unsigned char *d);\n#endif //_SHA1\n"
  },
  {
    "path": "src/sha3.c",
    "content": "/* -------------------------------------------------------------------------\n * Works when compiled for either 32-bit or 64-bit targets, optimized for \n * 64 bit.\n *\n * Canonical implementation of Init/Update/Finalize for SHA-3 byte input. \n *\n * SHA3-256, SHA3-384, SHA-512 are implemented. SHA-224 can easily be added.\n *\n * Based on code from http://keccak.noekeon.org/ .\n *\n * I place the code that I wrote into public domain, free to use. \n *\n * I would appreciate if you give credits to this work if you used it to \n * write or test * your code.\n *\n * Aug 2015. Andrey Jivsov. crypto@brainhub.org\n * ---------------------------------------------------------------------- */\n\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n\n#include \"sha3.h\"\n\n#define SHA3_ASSERT( x )\n#define SHA3_TRACE( format, ...)\n#define SHA3_TRACE_BUF(format, buf, l)\n\n/* \n * This flag is used to configure \"pure\" Keccak, as opposed to NIST SHA3.\n */\n#define SHA3_USE_KECCAK_FLAG 0x80000000\n#define SHA3_CW(x) ((x) & (~SHA3_USE_KECCAK_FLAG))\n\n\n#if defined(_MSC_VER)\n#define SHA3_CONST(x) x\n#else\n#define SHA3_CONST(x) x##L\n#endif\n\n#ifndef SHA3_ROTL64\n#define SHA3_ROTL64(x, y) \\\n\t(((x) << (y)) | ((x) >> ((sizeof(uint64_t)*8) - (y))))\n#endif\n\nstatic const uint64_t keccakf_rndc[24] = {\n    SHA3_CONST(0x0000000000000001UL), SHA3_CONST(0x0000000000008082UL),\n    SHA3_CONST(0x800000000000808aUL), SHA3_CONST(0x8000000080008000UL),\n    SHA3_CONST(0x000000000000808bUL), SHA3_CONST(0x0000000080000001UL),\n    SHA3_CONST(0x8000000080008081UL), SHA3_CONST(0x8000000000008009UL),\n    SHA3_CONST(0x000000000000008aUL), SHA3_CONST(0x0000000000000088UL),\n    SHA3_CONST(0x0000000080008009UL), SHA3_CONST(0x000000008000000aUL),\n    SHA3_CONST(0x000000008000808bUL), SHA3_CONST(0x800000000000008bUL),\n    SHA3_CONST(0x8000000000008089UL), SHA3_CONST(0x8000000000008003UL),\n    SHA3_CONST(0x8000000000008002UL), SHA3_CONST(0x8000000000000080UL),\n    SHA3_CONST(0x000000000000800aUL), SHA3_CONST(0x800000008000000aUL),\n    SHA3_CONST(0x8000000080008081UL), SHA3_CONST(0x8000000000008080UL),\n    SHA3_CONST(0x0000000080000001UL), SHA3_CONST(0x8000000080008008UL)\n};\n\nstatic const unsigned keccakf_rotc[24] = {\n    1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62,\n    18, 39, 61, 20, 44\n};\n\nstatic const unsigned keccakf_piln[24] = {\n    10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20,\n    14, 22, 9, 6, 1\n};\n\n/* generally called after SHA3_KECCAK_SPONGE_WORDS-ctx->capacityWords words \n * are XORed into the state s \n */\nstatic void\nkeccakf(uint64_t s[25])\n{\n    int i, j, round;\n    uint64_t t, bc[5];\n#define KECCAK_ROUNDS 24\n\n    for(round = 0; round < KECCAK_ROUNDS; round++) {\n\n        /* Theta */\n        for(i = 0; i < 5; i++)\n            bc[i] = s[i] ^ s[i + 5] ^ s[i + 10] ^ s[i + 15] ^ s[i + 20];\n\n        for(i = 0; i < 5; i++) {\n            t = bc[(i + 4) % 5] ^ SHA3_ROTL64(bc[(i + 1) % 5], 1);\n            for(j = 0; j < 25; j += 5)\n                s[j + i] ^= t;\n        }\n\n        /* Rho Pi */\n        t = s[1];\n        for(i = 0; i < 24; i++) {\n            j = keccakf_piln[i];\n            bc[0] = s[j];\n            s[j] = SHA3_ROTL64(t, keccakf_rotc[i]);\n            t = bc[0];\n        }\n\n        /* Chi */\n        for(j = 0; j < 25; j += 5) {\n            for(i = 0; i < 5; i++)\n                bc[i] = s[j + i];\n            for(i = 0; i < 5; i++)\n                s[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5];\n        }\n\n        /* Iota */\n        s[0] ^= keccakf_rndc[round];\n    }\n}\n\n/* *************************** Public Inteface ************************ */\n\n/* For Init or Reset call these: */\nsha3_return_t\nsha3_Init(void *priv, unsigned bitSize) {\n    sha3_context *ctx = (sha3_context *) priv;\n    if( bitSize != 256 && bitSize != 384 && bitSize != 512 )\n        return SHA3_RETURN_BAD_PARAMS;\n    memset(ctx, 0, sizeof(*ctx));\n    ctx->capacityWords = 2 * bitSize / (8 * sizeof(uint64_t));\n    return SHA3_RETURN_OK;\n}\n\nvoid\nsha3_Init256(void *priv)\n{\n    sha3_Init(priv, 256);\n}\n\nvoid\nsha3_Init384(void *priv)\n{\n    sha3_Init(priv, 384);\n}\n\nvoid\nsha3_Init512(void *priv)\n{\n    sha3_Init(priv, 512);\n}\n\nenum SHA3_FLAGS\nsha3_SetFlags(void *priv, enum SHA3_FLAGS flags)\n{\n    sha3_context *ctx = (sha3_context *) priv;\n    flags &= SHA3_FLAGS_KECCAK;\n    ctx->capacityWords |= (flags == SHA3_FLAGS_KECCAK ? SHA3_USE_KECCAK_FLAG : 0);\n    return flags;\n}\n\n\nvoid\nsha3_Update(void *priv, void const *bufIn, size_t len)\n{\n    sha3_context *ctx = (sha3_context *) priv;\n\n    /* 0...7 -- how much is needed to have a word */\n    unsigned old_tail = (8 - ctx->byteIndex) & 7;\n\n    size_t words;\n    unsigned tail;\n    size_t i;\n\n    const uint8_t *buf = bufIn;\n\n    SHA3_TRACE_BUF(\"called to update with:\", buf, len);\n\n    SHA3_ASSERT(ctx->byteIndex < 8);\n    SHA3_ASSERT(ctx->wordIndex < sizeof(ctx->u.s) / sizeof(ctx->u.s[0]));\n\n    if(len < old_tail) {        /* have no complete word or haven't started \n                                 * the word yet */\n        SHA3_TRACE(\"because %d<%d, store it and return\", (unsigned)len,\n                (unsigned)old_tail);\n        /* endian-independent code follows: */\n        while (len--)\n            ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8);\n        SHA3_ASSERT(ctx->byteIndex < 8);\n        return;\n    }\n\n    if(old_tail) {              /* will have one word to process */\n        SHA3_TRACE(\"completing one word with %d bytes\", (unsigned)old_tail);\n        /* endian-independent code follows: */\n        len -= old_tail;\n        while (old_tail--)\n            ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8);\n\n        /* now ready to add saved to the sponge */\n        ctx->u.s[ctx->wordIndex] ^= ctx->saved;\n        SHA3_ASSERT(ctx->byteIndex == 8);\n        ctx->byteIndex = 0;\n        ctx->saved = 0;\n        if(++ctx->wordIndex ==\n                (SHA3_KECCAK_SPONGE_WORDS - SHA3_CW(ctx->capacityWords))) {\n            keccakf(ctx->u.s);\n            ctx->wordIndex = 0;\n        }\n    }\n\n    /* now work in full words directly from input */\n\n    SHA3_ASSERT(ctx->byteIndex == 0);\n\n    words = len / sizeof(uint64_t);\n    tail = len - words * sizeof(uint64_t);\n\n    SHA3_TRACE(\"have %d full words to process\", (unsigned)words);\n\n    for(i = 0; i < words; i++, buf += sizeof(uint64_t)) {\n        const uint64_t t = (uint64_t) (buf[0]) |\n                ((uint64_t) (buf[1]) << 8 * 1) |\n                ((uint64_t) (buf[2]) << 8 * 2) |\n                ((uint64_t) (buf[3]) << 8 * 3) |\n                ((uint64_t) (buf[4]) << 8 * 4) |\n                ((uint64_t) (buf[5]) << 8 * 5) |\n                ((uint64_t) (buf[6]) << 8 * 6) |\n                ((uint64_t) (buf[7]) << 8 * 7);\n#if defined(__x86_64__ ) || defined(__i386__)\n        SHA3_ASSERT(memcmp(&t, buf, 8) == 0);\n#endif\n        ctx->u.s[ctx->wordIndex] ^= t;\n        if(++ctx->wordIndex ==\n                (SHA3_KECCAK_SPONGE_WORDS - SHA3_CW(ctx->capacityWords))) {\n            keccakf(ctx->u.s);\n            ctx->wordIndex = 0;\n        }\n    }\n\n    SHA3_TRACE(\"have %d bytes left to process, save them\", (unsigned)tail);\n\n    /* finally, save the partial word */\n    SHA3_ASSERT(ctx->byteIndex == 0 && tail < 8);\n    while (tail--) {\n        SHA3_TRACE(\"Store byte %02x '%c'\", *buf, *buf);\n        ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8);\n    }\n    SHA3_ASSERT(ctx->byteIndex < 8);\n    SHA3_TRACE(\"Have saved=0x%016\" PRIx64 \" at the end\", ctx->saved);\n}\n\n/* This is simply the 'update' with the padding block.\n * The padding block is 0x01 || 0x00* || 0x80. First 0x01 and last 0x80 \n * bytes are always present, but they can be the same byte.\n */\nvoid const *\nsha3_Finalize(void *priv)\n{\n    sha3_context *ctx = (sha3_context *) priv;\n\n    SHA3_TRACE(\"called with %d bytes in the buffer\", ctx->byteIndex);\n\n    /* Append 2-bit suffix 01, per SHA-3 spec. Instead of 1 for padding we\n     * use 1<<2 below. The 0x02 below corresponds to the suffix 01.\n     * Overall, we feed 0, then 1, and finally 1 to start padding. Without\n     * M || 01, we would simply use 1 to start padding. */\n\n    uint64_t t;\n\n    if( ctx->capacityWords & SHA3_USE_KECCAK_FLAG ) {\n        /* Keccak version */\n        t = (uint64_t)(((uint64_t) 1) << (ctx->byteIndex * 8));\n    }\n    else {\n        /* SHA3 version */\n        t = (uint64_t)(((uint64_t)(0x02 | (1 << 2))) << ((ctx->byteIndex) * 8));\n    }\n\n    ctx->u.s[ctx->wordIndex] ^= ctx->saved ^ t;\n\n    ctx->u.s[SHA3_KECCAK_SPONGE_WORDS - SHA3_CW(ctx->capacityWords) - 1] ^=\n            SHA3_CONST(0x8000000000000000UL);\n    keccakf(ctx->u.s);\n\n    /* Return first bytes of the ctx->s. This conversion is not needed for\n     * little-endian platforms e.g. wrap with #if !defined(__BYTE_ORDER__)\n     * || !defined(__ORDER_LITTLE_ENDIAN__) || __BYTE_ORDER__!=__ORDER_LITTLE_ENDIAN__ \n     *    ... the conversion below ...\n     * #endif */\n    {\n        unsigned i;\n        for(i = 0; i < SHA3_KECCAK_SPONGE_WORDS; i++) {\n            const unsigned t1 = (uint32_t) ctx->u.s[i];\n            const unsigned t2 = (uint32_t) ((ctx->u.s[i] >> 16) >> 16);\n            ctx->u.sb[i * 8 + 0] = (uint8_t) (t1);\n            ctx->u.sb[i * 8 + 1] = (uint8_t) (t1 >> 8);\n            ctx->u.sb[i * 8 + 2] = (uint8_t) (t1 >> 16);\n            ctx->u.sb[i * 8 + 3] = (uint8_t) (t1 >> 24);\n            ctx->u.sb[i * 8 + 4] = (uint8_t) (t2);\n            ctx->u.sb[i * 8 + 5] = (uint8_t) (t2 >> 8);\n            ctx->u.sb[i * 8 + 6] = (uint8_t) (t2 >> 16);\n            ctx->u.sb[i * 8 + 7] = (uint8_t) (t2 >> 24);\n        }\n    }\n\n    SHA3_TRACE_BUF(\"Hash: (first 32 bytes)\", ctx->u.sb, 256 / 8);\n\n    return (ctx->u.sb);\n}\n\nsha3_return_t sha3_HashBuffer( unsigned bitSize, enum SHA3_FLAGS flags, const void *in, unsigned inBytes, void *out, unsigned outBytes ) {\n    sha3_return_t err;\n    sha3_context c;\n\n    err = sha3_Init(&c, bitSize);\n    if( err != SHA3_RETURN_OK )\n        return err;\n    if( sha3_SetFlags(&c, flags) != flags ) {\n        return SHA3_RETURN_BAD_PARAMS;\n    }\n    sha3_Update(&c, in, inBytes);\n    const void *h = sha3_Finalize(&c);\n\n    if(outBytes > bitSize/8)\n        outBytes = bitSize/8;\n    memcpy(out, h, outBytes);\n    return SHA3_RETURN_OK;\n}\n\n//=========================================================================\n\nstatic void byte_to_hex(uint8_t b, char s[3])\n{\n    unsigned i = 1;\n    s[0] = s[1] = '0';\n    s[2] = '\\0';\n    while(b) {\n        unsigned t = b & 0x0f;\n        if( t < 10 ) {\n            s[i] = '0' + t;\n        } else {\n            s[i] = 'a' + t - 10;\n        }\n        i--;\n        b >>= 4;\n    }\n}\n\nvoid sha3_512_ByteToHex(char out[SHA3_512_DIGEST_HEX_STR_SIZE], const uint8_t in[SHA3_512_DIGEST_SIZE])\n{\n    int i;\n    for (i = 0; i < SHA3_512_DIGEST_SIZE; i++) {\n        byte_to_hex(in[i], out + i * 2);\n    }\n}\n"
  },
  {
    "path": "src/sha3.h",
    "content": "#ifndef SHA3_H\n#define SHA3_H\n\n#include <stdint.h>\n\n/* -------------------------------------------------------------------------\n * Works when compiled for either 32-bit or 64-bit targets, optimized for \n * 64 bit.\n *\n * Canonical implementation of Init/Update/Finalize for SHA-3 byte input. \n *\n * SHA3-256, SHA3-384, SHA-512 are implemented. SHA-224 can easily be added.\n *\n * Based on code from http://keccak.noekeon.org/ .\n *\n * I place the code that I wrote into public domain, free to use. \n *\n * I would appreciate if you give credits to this work if you used it to \n * write or test * your code.\n *\n * Aug 2015. Andrey Jivsov. crypto@brainhub.org\n * ---------------------------------------------------------------------- */\n\n/* 'Words' here refers to uint64_t */\n#define SHA3_KECCAK_SPONGE_WORDS \\\n\t(((1600)/8/*bits to byte*/)/sizeof(uint64_t))\ntypedef struct sha3_context_ {\n    uint64_t saved;             /* the portion of the input message that we\n                                 * didn't consume yet */\n    union {                     /* Keccak's state */\n        uint64_t s[SHA3_KECCAK_SPONGE_WORDS];\n        uint8_t sb[SHA3_KECCAK_SPONGE_WORDS * 8];\n    } u;\n    unsigned byteIndex;         /* 0..7--the next byte after the set one\n                                 * (starts from 0; 0--none are buffered) */\n    unsigned wordIndex;         /* 0..24--the next word to integrate input\n                                 * (starts from 0) */\n    unsigned capacityWords;     /* the double size of the hash output in\n                                 * words (e.g. 16 for Keccak 512) */\n} sha3_context;\n\nenum SHA3_FLAGS {\n    SHA3_FLAGS_NONE=0,\n    SHA3_FLAGS_KECCAK=1\n};\n\nenum SHA3_RETURN {\n    SHA3_RETURN_OK=0,\n    SHA3_RETURN_BAD_PARAMS=1\n};\ntypedef enum SHA3_RETURN sha3_return_t;\n\n/* For Init or Reset call these: */\nsha3_return_t sha3_Init(void *priv, unsigned bitSize);\n\nvoid sha3_Init256(void *priv);\nvoid sha3_Init384(void *priv);\nvoid sha3_Init512(void *priv);\n\nenum SHA3_FLAGS sha3_SetFlags(void *priv, enum SHA3_FLAGS);\n\nvoid sha3_Update(void *priv, void const *bufIn, size_t len);\n\nvoid const *sha3_Finalize(void *priv);\n\n/* Single-call hashing */\nsha3_return_t sha3_HashBuffer( \n    unsigned bitSize,   /* 256, 384, 512 */\n    enum SHA3_FLAGS flags, /* SHA3_FLAGS_NONE or SHA3_FLAGS_KECCAK */\n    const void *in, unsigned inBytes, \n    void *out, unsigned outBytes );     /* up to bitSize/8; truncation OK */\n\n//=========================================================================\n\n#define SHA3_512_DIGEST_SIZE (512 / 8)\n#define SHA3_512_DIGEST_HEX_STR_SIZE (SHA3_512_DIGEST_SIZE * 2 + 1)\n\nvoid sha3_512_ByteToHex(char out[SHA3_512_DIGEST_HEX_STR_SIZE], const uint8_t in[SHA3_512_DIGEST_SIZE]);\n\n#endif\n"
  },
  {
    "path": "src/skin.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: skin.c,v 1.24 2007-10-04 15:52:04 dkure Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"image.h\"\n#include \"qtv.h\"\n#include \"utils.h\"\n#include \"tr_types.h\"\n#include \"r_texture.h\"\n#include \"rulesets.h\"\n\nextern cvar_t gl_playermip;\nextern cvar_t gl_nocolors;\n\ntypedef struct player_skin_s {\n\ttexture_ref base;       // The standard skin.\n\ttexture_ref fb;         // Fullbright - for PCX textures only?\n\ttexture_ref dead;       // Skin to use for corpses (if null, use base instead)\n\n\tqbool owned[3];\n} player_skin_t;\n\nstatic player_skin_t playerskins[MAX_CLIENTS];\nstatic qbool skins_need_preache = true;\n\nstatic byte* Skin_Cache(skin_t *skin, qbool no_baseskin);\nstatic void Skin_Blend(byte* original, skin_t* skin, int skin_number);\n\nvoid OnChangeSkinForcing(cvar_t *var, char *string, qbool *cancel);\n\ncvar_t\tnoskins = { \"noskins\", \"0\", CVAR_RELOAD_GFX };\n\ncvar_t  enemyforceskins     = {\"enemyforceskins\", \"0\", 0, OnChangeSkinForcing};\ncvar_t  teamforceskins      = {\"teamforceskins\", \"0\", 0, OnChangeSkinForcing};\n\nstatic cvar_t  baseskin = { \"baseskin\", \"base\", CVAR_RELOAD_GFX };\nstatic cvar_t  cl_name_as_skin     = {\"cl_name_as_skin\", \"0\", 0, OnChangeSkinForcing};\ncvar_t  r_enemyskincolor    = {\"r_enemyskincolor\", \"\", CVAR_COLOR, OnChangeSkinForcing};\ncvar_t  r_teamskincolor     = {\"r_teamskincolor\",  \"\", CVAR_COLOR, OnChangeSkinForcing};\nstatic cvar_t  r_skincolormode     = {\"r_skincolormode\",  \"0\", 0, OnChangeSkinForcing};\nstatic cvar_t  r_skincolormodedead = {\"r_skincolormodedead\", \"-1\", 0, OnChangeSkinForcing};\ncvar_t  r_fullbrightSkins = { \"r_fullbrightSkins\", \"1\", 0, Rulesets_OnChange_r_fullbrightSkins };\n\nchar\tallskins[MAX_OSPATH];\n\n#define\tMAX_CACHED_SKINS\t128\nstatic skin_t\tskins[MAX_CACHED_SKINS];\n\nstatic int numskins;\n\n// return type of skin forcing if it's allowed for the player in the POV\nstatic int Skin_ForcingType(const char *team)\n{\n\tif (teamforceskins.integer && TP_ThisPOV_IsHisTeam(team)) {\n\t\treturn teamforceskins.integer;\t// allow for teammates\n\t}\n\n\tif (enemyforceskins.integer && !TP_ThisPOV_IsHisTeam(team)) {\n\t\tif (cls.demoplayback || cl.spectator) { // allow for demos\n\t\t\treturn enemyforceskins.integer;\n\t\t}\n\t\telse {  // for gameplay respect the FPD\n\t\t\tif (!(cl.fpd & FPD_NO_FORCE_SKIN) && !(cl.fpd & FPD_NO_FORCE_COLOR)) {\n\t\t\t\treturn enemyforceskins.integer;\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\n\t// this was always only for demos\n\tif (cl_name_as_skin.integer && (cls.demoplayback || cl.spectator)) {\n\t\treturn cl_name_as_skin.integer;\n\t}\n\n\treturn 0;\n}\n\n// get player skin as player name or player id\nstatic char* Skin_AsNameOrId(player_info_t *sc)\n{\n\tstatic char name[MAX_OSPATH];\n\tint pn;\n\tchar* mask;\n\n\tswitch (Skin_ForcingType(sc->team)) {\n\tcase 1: // get skin as player name\n\t\tUtil_ToValidFileName(sc->name, name, sizeof(name));\n\t\tQ_strlwr(name);\n\t\treturn name;\n\t\tbreak;\n\n\tcase 2: // get skin as id\n\t\tsnprintf(name, sizeof(name), \"%d\", sc->userid);\n\t\treturn name;\n\t\tbreak;\n\n\tcase 3: // get player's number (first teammate gets 1, second 2, ...)\n\t\tpn = TP_PlayersNumber(sc->userid, sc->team);\n\t\tif (pn) {\n\t\t\tmask = TP_ThisPOV_IsHisTeam(sc->team) ? \"t%d\" : \"e%d\";\n\t\t\tsnprintf(name, sizeof(name), mask, pn);\n\t\t\treturn name;\n\t\t}\n\t\tbreak;\n\t}\n\n\treturn NULL;\n}\n\nstatic char* Skin_FindName(player_info_t* sc, qbool* is_teammate)\n{\n\tstatic char name[MAX_OSPATH];\n\n\tif (is_teammate) {\n\t\t*is_teammate = false;\n\t}\n\tif (allskins[0]) {\n\t\tstrlcpy(name, allskins, sizeof(name));\n\t}\n\telse {\n\t\tchar *s = Skin_AsNameOrId(sc);\n\n\t\tif (!s || !s[0]) {\n\t\t\ts = Info_ValueForKey(sc->userinfo, \"skin\");\n\t\t}\n\n\t\tif (s && s[0]) {\n\t\t\tstrlcpy(name, s, sizeof(name));\n\t\t}\n\t\telse {\n\t\t\tstrlcpy(name, baseskin.string, sizeof(name));\n\t\t}\n\t}\n\n\tskinforcing_team = TP_SkinForcingTeam();\n\n\tif (!cl.teamfortress && !(cl.fpd & FPD_NO_FORCE_SKIN)) {\n\t\tchar *skinname = NULL;\n\t\tplayer_state_t *state;\n\t\tqbool teammate = (cl.teamplay && !strcmp(sc->team, skinforcing_team));\n\t\tif (is_teammate) {\n\t\t\t*is_teammate = teammate;\n\t\t}\n\n\t\tif (!cl.validsequence) {\n\t\t\tgoto nopowerups;\n\t\t}\n\n\t\tstate = cl.frames[cl.parsecount & UPDATE_MASK].playerstate + (sc - cl.players);\n\n\t\tif (state->messagenum != cl.parsecount) {\n\t\t\tgoto nopowerups;\n\t\t}\n\n\t\tif ((state->effects & (EF_BLUE | EF_RED)) == (EF_BLUE | EF_RED)) {\n\t\t\tskinname = teammate ? cl_teambothskin.string : cl_enemybothskin.string;\n\t\t}\n\t\telse if (state->effects & EF_BLUE) {\n\t\t\tskinname = teammate ? cl_teamquadskin.string : cl_enemyquadskin.string;\n\t\t}\n\t\telse if (state->effects & EF_RED) {\n\t\t\tskinname = teammate ? cl_teampentskin.string : cl_enemypentskin.string;\n\t\t}\n\n\tnopowerups:\n\t\tif (!skinname || !skinname[0]) {\n\t\t\tskinname = teammate ? cl_teamskin.string : cl_enemyskin.string;\n\t\t}\n\t\tif (skinname[0]) {\n\t\t\tstrlcpy(name, skinname, sizeof(name));\n\t\t}\n\t}\n\n\tif (strstr(name, \"..\") || *name == '.') {\n\t\tstrlcpy(name, baseskin.string, sizeof(name));\n\t}\n\n\treturn name;\n}\n\n//Determines the best skin for the given scoreboard slot, and sets scoreboard->skin\nstatic void Skin_Find_Ex(player_info_t *sc, char *skin_name)\n{\n\tskin_t *skin;\n\tint i;\n\tchar name[MAX_OSPATH];\n\n\tif (!skin_name || !skin_name[0]) {\n\t\tskin_name = baseskin.string;\n\n\t\tif (!skin_name[0]) {\n\t\t\tskin_name = \"base\";\n\t\t}\n\t}\n\n\tstrlcpy(name, skin_name, sizeof(name));\n\tCOM_StripExtension(name, name, sizeof(name));\n\n\tfor (i = 0; i < numskins; i++) {\n\t\tif (!strcmp(name, skins[i].name)) {\n\t\t\tif (sc) {\n\t\t\t\tsc->skin = &skins[i];\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (numskins == MAX_CACHED_SKINS) {\n\t\t// ran out of spots, so flush everything\n\t\tCom_Printf(\"MAX_CACHED_SKINS reached, flushing skins\\n\");\n\t\tSkin_Skins_f(); // this must set numskins to 0\n\t}\n\n\tskin = &skins[numskins];\n\tif (sc) {\n\t\tsc->skin = skin;\n\t}\n\tnumskins++;\n\n\tmemset(skin, 0, sizeof(*skin));\n\tstrlcpy(skin->name, name, sizeof(skin->name));\n}\n\nstatic void Skin_Find(player_info_t *sc)\n{\n\tSkin_Find_Ex(sc, Skin_FindName(sc, NULL));\n}\n\nstatic byte* Skin_PixelsLoad(char *name, int *max_w, int *max_h, int *bpp, int *real_width, int *real_height)\n{\n\tbyte *pic;\n\n\t*max_w = *max_h = *bpp = 0;\n\n\t// PCX skins loads different, so using TEX_NO_PCX\n\tif (gl_no24bit.integer == 0 && (pic = R_LoadImagePixels(name, 0, 0, TEX_NO_PCX, real_width, real_height))) {\n\t\t// No limit in gl.\n\t\t*max_w = *real_width;\n\t\t*max_h = *real_height;\n\t\t*bpp = 4; // 32 bit.\n\n\t\treturn pic;\n\t}\n\n\tif ((pic = Image_LoadPCX(NULL, name, 0, 0, real_width, real_height))) {\n\t\t// PCX is limited.\n\t\t*max_w = 320;\n\t\t*max_h = 200;\n\t\t*bpp = 1; // 8 bit\n\n\t\treturn pic;\n\t}\n\n\treturn NULL;\n}\n\n// \"HACK\"\n// Called before rendering scene, chance to load textures again if skin\n//   rules have changed\nvoid Skins_PreCache(void)\n{\n\tint i;\n\tbyte *tex;\n\n\tif (!skins_need_preache) {\n\t\t// no need, we have all skins we need\n\t\treturn;\n\t}\n\n\tskins_need_preache = false;\n\n\t// this must register skins with such names in skins[] array\n\tSkin_Find_Ex(NULL, cl_teamskin.string);\n\tSkin_Find_Ex(NULL, cl_enemyskin.string);\n\tSkin_Find_Ex(NULL, cl_teamquadskin.string);\n\tSkin_Find_Ex(NULL, cl_enemyquadskin.string);\n\tSkin_Find_Ex(NULL, cl_teampentskin.string);\n\tSkin_Find_Ex(NULL, cl_enemypentskin.string);\n\tSkin_Find_Ex(NULL, cl_teambothskin.string);\n\tSkin_Find_Ex(NULL, cl_enemybothskin.string);\n\tSkin_Find_Ex(NULL, baseskin.string);\n\tSkin_Find_Ex(NULL, \"base\");\n\n\t// now load all 24 bit skins in skins[] array\n\tfor (i = 0; i < numskins; i++) {\n\t\ttex = Skin_Cache(&skins[i], false); // this precache skin file in mem\n\n\t\tif (!tex) {\n\t\t\tcontinue; // nothing more we can do\n\t\t}\n\n\t\tif (skins[i].bpp != 4) {\n\t\t\t// we interested in 24 bit skins only\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (R_TextureReferenceIsValid(skins[i].texnum[skin_base])) {\n\t\t\t// seems skin alredy loaded, at least we have some texture\n\t\t\tcontinue;\n\t\t}\n\n\t\tSkin_Blend(tex, &skins[i], i);\n\n\t\tCom_DPrintf(\"skin precache: %s, texnum %d\\n\", skins[i].name, skins[i].texnum[skin_base]);\n\t}\n}\n\n// Returns a pointer to the skin bitmap, or NULL to use the default\nstatic byte* Skin_Cache(skin_t *skin, qbool no_baseskin)\n{\n\tint y, max_w, max_h, bpp, real_width = -1, real_height = -1;\n\tbyte *pic = NULL, *out, *pix;\n\tchar name[MAX_OSPATH];\n\n\t// JACK: So NOSKINS > 1 will show skins, but not download new ones.\n\tif (noskins.integer == 1) {\n\t\treturn NULL;\n\t}\n\n\tif (skin->failedload) {\n\t\treturn NULL;\n\t}\n\n\tif (skin->cached_data) {\n\t\treturn skin->cached_data;\n\t}\n\n\t// not cached, load from HDD\n\tsnprintf(name, sizeof(name), \"skins/%s.pcx\", skin->name);\n\tif (!(pic = Skin_PixelsLoad(name, &max_w, &max_h, &bpp, &real_width, &real_height)) || real_width > max_w || real_height > max_h) {\n\t\tQ_free(pic);\n\n\t\tif (no_baseskin) {\n\t\t\tskin->warned = true;\n\t\t\treturn NULL; // well, we not set skin->failedload = true, that how I need it here\n\t\t}\n\t\telse if (!skin->warned && strcmp(skin->name, \"base\")) {\n\t\t\tCom_Printf(\"&cf22Couldn't load skin:&r %s\\n\", name);\n\t\t}\n\n\t\tskin->warned = true;\n\t}\n\n\tif (!pic) {\n\t\t// Attempt load at least default/base.\n\t\tsnprintf(name, sizeof(name), \"skins/%s.pcx\", baseskin.string);\n\n\t\tif (!(pic = Skin_PixelsLoad(name, &max_w, &max_h, &bpp, &real_width, &real_height)) || real_width > max_w || real_height > max_h) {\n\t\t\tQ_free(pic);\n\t\t\tskin->failedload = true;\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tif (!(out = pix = skin->cached_data = (byte *)Q_malloc_named(max_w * max_h * bpp, skin->name))) {\n\t\tSys_Error(\"Skin_Cache: couldn't allocate\");\n\t}\n\n\tmemset(out, 0, max_w * max_h * bpp);\n\tfor (y = 0; y < real_height; y++, pix += (max_w * bpp)) {\n\t\tmemcpy(pix, pic + y * real_width * bpp, real_width * bpp);\n\t}\n\n\tQ_free(pic);\n\tskin->bpp = bpp;\n\tskin->width = real_width;\n\tskin->height = real_height;\n\n\t// Comments about not requiring cache for 24-bit skins removed...\n\t//   now each player gets their own copy\n\tskin->failedload = false;\n\n\treturn out;\n}\n\nvoid Skin_NextDownload(void)\n{\n\tplayer_info_t *sc;\n\tint i;\n\n\tif (cls.downloadnumber == 0)\n\t\tif (!com_serveractive || developer.value)\n\t\t\tCom_DPrintf(\"Checking skins...\\n\");\n\n\tcls.downloadtype = dl_skin;\n\n\tfor (; cls.downloadnumber >= 0 && cls.downloadnumber < MAX_CLIENTS; cls.downloadnumber++) {\n\t\tsc = &cl.players[cls.downloadnumber];\n\t\tif (!sc->name[0]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tSkin_Find(sc);\n\n\t\tif (noskins.integer) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (Skin_Cache(sc->skin, true)) {\n\t\t\tcontinue; // we have it in cache, that mean we somehow able load this skin\n\t\t}\n\n\t\tif (!CL_CheckOrDownloadFile(va(\"skins/%s.pcx\", sc->skin->name))) {\n\t\t\treturn;   // started a download\n\t\t}\n\t}\n\n\tcls.downloadtype = dl_none;\n\n\t// now load them in for real\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tsc = &cl.players[i];\n\t\tif (!sc->name[0]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!sc->skin) {\n\t\t\tSkin_Find(sc);\n\t\t}\n\n\t\tSkin_Cache(sc->skin, false);\n\t\tsc->skin = NULL; // this way triggered skin loading, as i understand in R_TranslatePlayerSkin()\n\t}\n\n\tif (cls.state == ca_onserver /* && cbuf_current != &cbuf_main */) {\t//only download when connecting\n\t\tMSG_WriteByte(&cls.netchan.message, clc_stringcmd);\n\t\tMSG_WriteString(&cls.netchan.message, va(\"begin %i\", cl.servercount));\n\t}\n}\n\nvoid Skin_Clear(qbool download)\n{\n\tint i;\n\n\tfor (i = 0; i < numskins; i++) {\n\t\tQ_free(skins[i].cached_data);\n\t}\n\tnumskins = 0;\n\n\tskins_need_preache = true; // we need precache it ASAP\n\n\tif (download) {\n\t\tcls.downloadnumber = 0;\n\t\tcls.downloadtype = dl_skin;\n\t\tSkin_NextDownload();\n\n\t\tif (cls.mvdplayback == QTV_PLAYBACK && cbuf_current != &cbuf_main) {\n\t\t\tcls.qtv_donotbuffer = false;\n\t\t}\n\t}\n}\n\n//Refind all skins, downloading if needed.\nvoid Skin_Skins_f(void)\n{\n\tSkin_Clear(true);\n}\n\n//Sets all skins to one specific one\nvoid Skin_AllSkins_f(void)\n{\n\tif (Cmd_Argc() == 1) {\n\t\tCom_Printf(\"allskins set to \\\"%s\\\"\\n\", allskins);\n\t\treturn;\n\t}\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"Usage: %s [skin]\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\tstrlcpy(allskins, Cmd_Argv(1), sizeof(allskins));\n\tSkin_Skins_f();\n}\n\n//Just show skins which ezquake assign to each player, that depends on alot of variables and different conditions,\n//so may be useful for checking settings\nvoid Skin_ShowSkins_f(void)\n{\n\tint i, count, maxlen;\n\n\tmaxlen = sizeof(\"name\") - 1;\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (cl.players[i].name[0] && !cl.players[i].spectator) {\n\t\t\t// get len of longest name, but no more than 17\n\t\t\tmaxlen = bound(maxlen, strlen(cl.players[i].name), 17);\n\t\t}\n\t}\n\n\tfor (i = count = 0; i < MAX_CLIENTS; i++) {\n\t\tif (cl.players[i].name[0] && !cl.players[i].spectator) {\n\t\t\tif (!count) {\n\t\t\t\tCom_Printf(\"\\x02%-*.*s %s\\n\", maxlen, maxlen, \"name\", \"skin\");\n\t\t\t}\n\n\t\t\tCom_Printf(\"%-*.*s %s\\n\", maxlen, maxlen, cl.players[i].name, Skin_FindName(&cl.players[i], NULL));\n\n\t\t\tcount++;\n\t\t}\n\t}\n}\n\nstatic texture_ref Skin_ApplyRGBColor(byte* original, int width, int height, byte* specific, byte* color, int mode, const char* texture_name)\n{\n\tint x, y;\n\tfloat fColor[3];\n\n\tif (color) {\n\t\tVectorScale(color, 1 / 255.0f, fColor);\n\t}\n\telse {\n\t\tVectorSet(fColor, 1, 1, 1);\n\t}\n\tmemcpy(specific, original, width * height * 4);\n\tfor (x = 0; x < width; ++x) {\n\t\tfor (y = 0; y < height; ++y) {\n\t\t\tbyte* src = &original[(x + y * width) * 4];\n\t\t\tbyte* dst = &specific[(x + y * width) * 4];\n\n\t\t\tswitch (mode) {\n\t\t\t\tcase 0:\n\t\t\t\t\t// Solid colour\n\t\t\t\t\tif (color) {\n\t\t\t\t\t\tVectorCopy(color, dst);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 1: // GL_REPLACE, no action\n\t\t\t\tcase 3: // GL_DECAL... should be affected by alpha but it's RGB, so it's the same (?)\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2: // GL_BLEND\n\t\t\t\t\tdst[0] = fColor[0] * (255 - src[0]);\n\t\t\t\t\tdst[1] = fColor[1] * (255 - src[1]);\n\t\t\t\t\tdst[2] = fColor[2] * (255 - src[2]);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 4: // GL_ADD\n\t\t\t\t\tif (color) {\n\t\t\t\t\t\tdst[0] = min(255, src[0] + color[0]);\n\t\t\t\t\t\tdst[1] = min(255, src[1] + color[1]);\n\t\t\t\t\t\tdst[2] = min(255, src[2] + color[2]);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault: // GL_MODULATE\n\t\t\t\t\tdst[0] = fColor[0] * src[0];\n\t\t\t\t\tdst[1] = fColor[1] * src[1];\n\t\t\t\t\tdst[2] = fColor[2] * src[2];\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn R_LoadTexture(texture_name, width, height, specific, TEX_MIPMAP | TEX_NOSCALE, 4);\n}\n\nstatic void Skin_Blend(byte* original, skin_t* skin, int skin_number)\n{\n\tconst char* types[skin_textures] = { \"$skin-base\", \"$skin-base-tm\", \"$skin-fb\", \"$skin-dead\", \"$skin-dead-tm\" };\n\tqbool teammates[skin_textures] = { false, true, false, false, true };\n\tint i;\n\tbyte* specific;\n\tchar texture_name[128];\n\tqbool block_adjustments = (cl.teamfortress || (cl.fpd & (FPD_NO_FORCE_SKIN | FPD_NO_FORCE_COLOR)));\n\n\t// FIXME: Delete old, don't leave lying around.  Watch out for solidtexture references\n\tmemset(skin->texnum, 0, sizeof(skin->texnum));\n\n\t// If config says no color adjustments, load as normal skin\n\tblock_adjustments |= (!r_teamskincolor.string[0] && !r_enemyskincolor.string[0]) || (r_skincolormodedead.integer <= 0 && r_skincolormode.integer == 0);\n\tif (block_adjustments) {\n\t\tsnprintf(texture_name, sizeof(texture_name), \"%s-%02d\", types[skin_base], skin_number);\n\t\tskin->texnum[skin_base] = R_LoadTexture(texture_name, skin->width, skin->height, original, TEX_MIPMAP | TEX_NOSCALE, 4);\n\t\treturn;\n\t}\n\n\tspecific = Q_malloc(skin->width * skin->height * 4);\n\tfor (i = 0; i < skin_textures; ++i) {\n\t\tcvar_t* color = teammates[i] ? &r_teamskincolor : &r_enemyskincolor;\n\t\tint mode = (i == skin_dead || i == skin_dead_teammate) && r_skincolormodedead.integer >= 0 ? r_skincolormodedead.integer : r_skincolormode.integer;\n\n\t\tsnprintf(texture_name, sizeof(texture_name), \"%s-%02d\", types[i], skin_number);\n\t\tif (!color->string[0]) {\n\t\t\t// If no color adjustment required then we only need one version\n\t\t\tif (i == skin_base) {\n\t\t\t\t// Load normal texture\n\t\t\t\tskin->texnum[skin_base] = R_LoadTexture(texture_name, skin->width, skin->height, original, TEX_MIPMAP | TEX_NOSCALE, 4);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Modify the original as per mapping to OpenGL functions\n\t\tskin->texnum[i] = Skin_ApplyRGBColor(original, skin->width, skin->height, specific, color->color, mode, texture_name);\n\t}\n\tQ_free(specific);\n}\n\nstatic void Skin_RemoveSkinsForPlayer(int playernum)\n{\n\tif (playerskins[playernum].owned[0]) {\n\t\tR_DeleteTexture(&playerskins[playernum].base);\n\t}\n\tif (playerskins[playernum].owned[1]) {\n\t\tR_DeleteTexture(&playerskins[playernum].fb);\n\t}\n\tif (playerskins[playernum].owned[2]) {\n\t\tR_DeleteTexture(&playerskins[playernum].dead);\n\t}\n\tR_TextureReferenceInvalidate(playerskins[playernum].base);\n\tR_TextureReferenceInvalidate(playerskins[playernum].fb);\n\tR_TextureReferenceInvalidate(playerskins[playernum].dead);\n\tmemset(playerskins[playernum].owned, 0, sizeof(playerskins[playernum].owned));\n}\n\nstatic void R_BlendPlayerSkin(skin_t* skin, qbool teammate, int playernum, byte* original, int width, int height, qbool fullbright)\n{\n\tcvar_t* color = teammate ? &r_teamskincolor : &r_enemyskincolor;\n\tbyte* specific = Q_malloc(width * height * 4);\n\ttexture_ref blended;\n\tchar texture_name[128];\n\n\tsnprintf(texture_name, sizeof(texture_name), \"$player-skin-%d\", playernum);\n\tblended = Skin_ApplyRGBColor(original, width, height, specific, color->string[0] ? color->color : NULL, r_skincolormode.integer, texture_name);\n\tif (fullbright) {\n\t\tplayerskins[playernum].fb = blended;\n\t\tplayerskins[playernum].owned[1] = true;\n\t}\n\telse {\n\t\tplayerskins[playernum].base = blended;\n\t\tplayerskins[playernum].owned[0] = true;\n\n\t\tif (r_skincolormodedead.integer >= 0 && r_skincolormodedead.integer != r_skincolormode.integer) {\n\t\t\tstrlcat(texture_name, \"-dead\", sizeof(texture_name));\n\t\t\tplayerskins[playernum].dead = Skin_ApplyRGBColor(original, width, height, specific, color->color, r_skincolormodedead.integer, texture_name);\n\t\t\tplayerskins[playernum].owned[2] = true;\n\t\t}\n\t}\n\tQ_free(specific);\n}\n\n//Translates a skin texture by the per-player color lookup\nvoid R_TranslatePlayerSkin(int playernum)\n{\n\tbyte translate[256], *inrow, *original;\n\tchar s[512];\n\tint\ttop, bottom, i, j, scaled_width, scaled_height, inwidth, inheight, tinwidth, tinheight;\n\tunsigned translate32[256], *out, frac, fracstep;\n\tunsigned pixels[512 * 256];\n\textern byte player_8bit_texels[256 * 256];\n\textern cvar_t gl_scaleModelTextures;\n\tplayer_info_t *player;\n\tqbool teammate = false;\n\n\tplayer = &cl.players[playernum];\n\tif (!player->name[0]) {\n\t\treturn;\n\t}\n\n\tstrlcpy(s, Skin_FindName(player, &teammate), sizeof(s));\n\tCOM_StripExtension(s, s, sizeof(s));\n\n\tif (player->skin && strcasecmp(s, player->skin->name)) {\n\t\tplayer->skin = NULL;\n\t}\n\n\tif (player->_topcolor == player->topcolor && player->_bottomcolor == player->bottomcolor && player->skin && player->teammate == player->_teammate) {\n\t\treturn;\n\t}\n\n\tplayer->_topcolor = player->topcolor;\n\tplayer->_bottomcolor = player->bottomcolor;\n\tplayer->_teammate = player->teammate;\n\n\tif (!player->skin) {\n\t\tSkin_Find(player);\n\t}\n\n\tSkin_RemoveSkinsForPlayer(playernum);\n\n\tif (R_TextureReferenceIsValid(player->skin->texnum[skin_base]) && player->skin->bpp == 4 && !(r_enemyskincolor.string[0] || r_teamskincolor.string[0])) {\n\t\t// do not even bother call Skin_Cache(), we have texture num already\n\t\tif (teammate && R_TextureReferenceIsValid(player->skin->texnum[skin_base_teammate])) {\n\t\t\tplayerskins[playernum].base = player->skin->texnum[skin_base_teammate];\n\t\t}\n\t\telse {\n\t\t\tplayerskins[playernum].base = player->skin->texnum[skin_base];\n\t\t}\n\n\t\tif (teammate && R_TextureReferenceIsValid(player->skin->texnum[skin_dead_teammate])) {\n\t\t\tplayerskins[playernum].dead = player->skin->texnum[skin_dead_teammate];\n\t\t}\n\t\telse {\n\t\t\tplayerskins[playernum].dead = player->skin->texnum[skin_dead];\n\t\t}\n\t\tif (!R_TextureReferenceIsValid(playerskins[playernum].dead)) {\n\t\t\tplayerskins[playernum].dead = playerskins[playernum].base;\n\t\t}\n\t\treturn;\n\t}\n\n\tif ((original = Skin_Cache(player->skin, false)) != NULL) {\n\t\tswitch (player->skin->bpp) {\n\t\tcase 4:\n\t\t\t// 32 bit skin\n\t\t\tR_BlendPlayerSkin(player->skin, teammate, playernum, original, player->skin->width, player->skin->height, false);\n\t\t\treturn;\n\n\t\tcase 1:\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tSys_Error(\"R_TranslatePlayerSkin: wrong bpp %d\", player->skin->bpp);\n\t\t}\n\n\t\t//skin data width\n\t\tinwidth = 320;\n\t\tinheight = 200;\n\t}\n\telse {\n\t\toriginal = player_8bit_texels;\n\t\tinwidth = 296;\n\t\tinheight = 194;\n\t}\n\n\t// locate the original skin pixels\n\t// real model width\n\ttinwidth = 296;\n\ttinheight = 194;\n\n\ttop = bound(0, player->topcolor, 13) * 16;\n\tbottom = bound(0, player->bottomcolor, 13) * 16;\n\n\tfor (i = 0; i < 256; i++) {\n\t\ttranslate[i] = i;\n\t}\n\n\tfor (i = 0; i < 16; i++) {\n\t\t// the artists made some backwards ranges.  sigh.\n\t\ttranslate[TOP_RANGE + i] = (top < 128) ? (top + i) : (top + 15 - i);\n\t\ttranslate[BOTTOM_RANGE + i] = (bottom < 128) ? (bottom + i) : (bottom + 15 - i);\n\t}\n\n\tscaled_width = gl_scaleModelTextures.value ? min(gl_max_size.value, tinwidth) : min(glConfig.gl_max_size_default, tinwidth);\n\tscaled_height = gl_scaleModelTextures.value ? min(gl_max_size.value, tinheight) : min(glConfig.gl_max_size_default, tinheight);\n\n\t// allow users to crunch sizes down even more if they want\n\tscaled_width >>= (int)gl_playermip.value;\n\tscaled_height >>= (int)gl_playermip.value;\n\tif (scaled_width < 1) {\n\t\tscaled_width = 1;\n\t}\n\tif (scaled_height < 1) {\n\t\tscaled_height = 1;\n\t}\n\n\tfor (i = 0; i < 256; i++) {\n\t\ttranslate32[i] = d_8to24table[translate[i]];\n\t}\n\n\tout = pixels;\n\tmemset(pixels, 0, sizeof(pixels));\n\tfracstep = tinwidth * 0x10000 / scaled_width;\n\tfor (i = 0; i < scaled_height; i++, out += scaled_width) {\n\t\tinrow = original + inwidth*(i * tinheight / scaled_height);\n\t\tfrac = fracstep >> 1;\n\t\tfor (j = 0; j < scaled_width; j += 4) {\n\t\t\tout[j] = translate32[inrow[frac >> 16]];\n\t\t\tfrac += fracstep;\n\t\t\tout[j + 1] = translate32[inrow[frac >> 16]];\n\t\t\tfrac += fracstep;\n\t\t\tout[j + 2] = translate32[inrow[frac >> 16]];\n\t\t\tfrac += fracstep;\n\t\t\tout[j + 3] = translate32[inrow[frac >> 16]];\n\t\t\tfrac += fracstep;\n\t\t}\n\t}\n\n\tR_BlendPlayerSkin(player->skin, teammate, playernum, (byte*)pixels, scaled_width, scaled_height, false);\n\n\t// TODO: Dead skins\n\n\tif (Img_HasFullbrights((byte *)original, inwidth * inheight)) {\n\t\tout = pixels;\n\t\tmemset(pixels, 0, sizeof(pixels));\n\t\tfracstep = tinwidth * 0x10000 / scaled_width;\n\n\t\t// make all non-fullbright colors transparent\n\t\tfor (i = 0; i < scaled_height; i++, out += scaled_width) {\n\t\t\tinrow = original + inwidth * (i * tinheight / scaled_height);\n\t\t\tfrac = fracstep >> 1;\n\t\t\tfor (j = 0; j < scaled_width; j += 4) {\n\t\t\t\tif (inrow[frac >> 16] < 224) {\n\t\t\t\t\tout[j] = translate32[inrow[frac >> 16]] & LittleLong(0x00FFFFFF); // transparent\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tout[j] = translate32[inrow[frac >> 16]]; // fullbright\n\t\t\t\t}\n\t\t\t\tfrac += fracstep;\n\t\t\t\tif (inrow[frac >> 16] < 224) {\n\t\t\t\t\tout[j + 1] = translate32[inrow[frac >> 16]] & LittleLong(0x00FFFFFF);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tout[j + 1] = translate32[inrow[frac >> 16]];\n\t\t\t\t}\n\t\t\t\tfrac += fracstep;\n\t\t\t\tif (inrow[frac >> 16] < 224) {\n\t\t\t\t\tout[j + 2] = translate32[inrow[frac >> 16]] & LittleLong(0x00FFFFFF);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tout[j + 2] = translate32[inrow[frac >> 16]];\n\t\t\t\t}\n\t\t\t\tfrac += fracstep;\n\t\t\t\tif (inrow[frac >> 16] < 224) {\n\t\t\t\t\tout[j + 3] = translate32[inrow[frac >> 16]] & LittleLong(0x00FFFFFF);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tout[j + 3] = translate32[inrow[frac >> 16]];\n\t\t\t\t}\n\t\t\t\tfrac += fracstep;\n\t\t\t}\n\t\t}\n\n\t\tR_BlendPlayerSkin(player->skin, teammate, playernum, (byte*)pixels, scaled_width, scaled_height, true);\n\t}\n}\n\nvoid R_SetSkinForPlayerEntity(entity_t* ent, texture_ref* texture, texture_ref* fb_texture, byte** color32bit)\n{\n\tqbool is_player_model = (ent->model->modhint == MOD_PLAYER || ent->renderfx & RF_PLAYERMODEL);\n\tint playernum = ent->scoreboard - cl.players;\n\n\tif (!gl_nocolors.integer) {\n\t\t// we can't dynamically colormap textures, so they are cached separately for the players.  Heads are just uncolored.\n\t\tif (!ent->scoreboard->skin) {\n\t\t\tCL_NewTranslation(playernum);\n\t\t}\n\n\t\t*fb_texture = playerskins[playernum].fb;\n\t\tif (ISDEAD(ent->frame) && r_skincolormodedead.integer != -1 && r_skincolormodedead.integer != r_skincolormode.integer) {\n\t\t\tif (r_skincolormodedead.integer && R_TextureReferenceIsValid(playerskins[playernum].dead)) {\n\t\t\t\t*texture = playerskins[playernum].dead;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t*texture = playerskins[playernum].base;\n\t\t}\n\n\t\tif (is_player_model && R_TextureReferenceEqual(*texture, solidwhite_texture)) {\n\t\t\tqbool custom_skins_blocked = cl.teamfortress || (cl.fpd & (FPD_NO_FORCE_SKIN | FPD_NO_FORCE_COLOR));\n\t\t\tif (!custom_skins_blocked) {\n\t\t\t\tcvar_t* cv = &r_enemyskincolor;\n\t\t\t\tif (cl.teamplay && !strcmp(cl.players[playernum].team, TP_SkinForcingTeam())) {\n\t\t\t\t\tcv = &r_teamskincolor;\n\t\t\t\t}\n\t\t\t\tif (cv->string[0]) {\n\t\t\t\t\t*color32bit = cv->color;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Skin_InvalidateTextures(void)\n{\n\tmemset(playerskins, 0, sizeof(playerskins));\n}\n\nvoid Skin_RegisterCvars(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SKIN);\n\tCvar_Register(&noskins);\n\tCvar_Register(&baseskin);\n\tCvar_Register(&cl_name_as_skin);\n\tCvar_Register(&enemyforceskins);\n\tCvar_Register(&teamforceskins);\n\tCvar_Register(&r_fullbrightSkins);\n\tCvar_Register(&r_enemyskincolor);\n\tCvar_Register(&r_teamskincolor);\n\tCvar_Register(&r_skincolormode);\n\tCvar_Register(&r_skincolormodedead);\n}\n\n// Called when connected to server and skin-forcing rules have changed\nvoid Skin_Refresh(void)\n{\n\tint oldskins = noskins.integer;\n\tint oldflags = noskins.flags;\n\n\tnoskins.flags &= ~(CVAR_RELOAD_GFX);\n\tCvar_SetValue(&noskins, 2);\n\n\tcon_suppress = true;\n\tSkin_Skins_f();\n\tcon_suppress = false;\n\n\tCvar_SetValue(&noskins, oldskins);\n\tnoskins.flags = oldflags;\n}\n\nvoid Skin_UserInfoChange(int slot, player_info_t* player, const char* key)\n{\n\tqbool update_skin;\n\tint mynum;\n\n\tif (!cl.spectator || (mynum = Cam_TrackNum()) == -1) {\n\t\tmynum = cl.playernum;\n\t}\n\n\tupdate_skin = !key || (!player->spectator && (!strcmp(key, \"skin\") || !strcmp(key, \"topcolor\") ||\n\t\t!strcmp(key, \"bottomcolor\") || !strcmp(key, \"team\") ||\n\t\t(!strcmp(key, \"name\") && cl_name_as_skin.value)));\n\n\tif (slot == mynum && TP_NeedRefreshSkins() && strcmp(player->team, player->_team)) {\n\t\tTP_RefreshSkins();\n\t}\n\telse if (update_skin) {\n\t\tTP_RefreshSkin(slot);\n\t}\n}\n\nvoid OnChangeSkinForcing(cvar_t *var, char *string, qbool *cancel)\n{\n\textern cvar_t cl_name_as_skin, enemyforceskins;\n\n\tif (cl.teamfortress || (cl.fpd & FPD_NO_FORCE_SKIN)) {\n\t\treturn;\n\t}\n\n\tif (var == &cl_name_as_skin && (!cls.demoplayback && !cl.spectator)) {\n\t\treturn; // allow in demos or for specs\n\t}\n\n\tif (var == &enemyforceskins && (!cl.spectator && cls.state != ca_disconnected)) {\n\t\tif (!cl.standby && !cl.countdown) {\n\t\t\tCon_Printf(\"%s cannot be changed during match\\n\", var->name);\n\t\t\treturn;\n\t\t}\n\n\t\tif (Q_atoi(string)) {\n\t\t\tCbuf_AddText(\"say Individual enemy skins: enabled\\n\");\n\t\t}\n\t\telse if (strcmp(var->string, string)) {\n\t\t\tCbuf_AddText(\"say Individual enemy skins: disabled\\n\");\n\t\t}\n\t}\n\n\tif (cls.state == ca_active) {\n\t\tCvar_Set(var, string);\n\n\t\tSkin_Refresh();\n\t\t*cancel = true;\n\t\treturn;\n\t}\n}\n"
  },
  {
    "path": "src/snd_main.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: snd_dma.c,v 1.48 2007-09-26 21:51:34 tonik Exp $\n*/\n// snd_dma.c -- main control for any streaming sound output device\n\n#include <SDL.h>\n#include \"quakedef.h\"\n#include \"qsound.h\"\n#include \"utils.h\"\n#include \"rulesets.h\"\n#include \"fmod.h\"\n#define SELF_SOUND_ENTITY 0xFFEFFFFF // [EZH] Fan told me 0xFFEFFFFF is damn cool value for it :P\n#define PLAY_SOUND_ENTITY 0xFFEFFFFE // /play or /playvol command, take distance & direction into account\n\n#include \"movie.h\" // /demo_capture\n\nextern qbool ActiveApp, Minimized;\nextern cvar_t sys_inactivesound;\n\nstatic void OnChange_s_khz (cvar_t *var, char *string, qbool *cancel);\nstatic void OnChange_s_desiredsamples (cvar_t *var, char *string, qbool *cancel);\nstatic void S_Play_f (void);\nstatic void S_MuteSound_f (void);\nstatic void S_SoundList_f (void);\nstatic void S_Update_ (void);\nstatic void S_StopSoundScript_f(void);\nstatic void S_StopAllSounds_f (void);\nstatic void S_Register_LatchCvars(void);\nvoid S_Voip_RegisterCvars (void);\n\n// =======================================================================\n// Internal sound data & structures\n// =======================================================================\n\nchannel_t\tchannels[MAX_CHANNELS] = { {0} };\nunsigned int\ttotal_channels;\n\nqbool\t\tsnd_initialized = false;\nqbool\t\tsnd_started = false;\n\nint\t\tsoundtime;\n\nstatic vec3_t\tlistener_origin;\nstatic vec3_t\tlistener_forward;\nstatic vec3_t\tlistener_right;\nstatic vec3_t\tlistener_up;\n#define sound_nominal_clip_dist 1000.0\n\n// during registration it is possible to have more sounds\n// than could actually be referenced during gameplay,\n// because we don't want to free anything until we are\n// sure we won't need it.\n\n#define MAX_SFX (MAX_SOUNDS*2) // 256 * 2 = 512\n\nstatic sfx_t\t*known_sfx;\nstatic int\tnum_sfx;\n\nstatic qbool\tsound_spatialized = false;\n\nstatic sfx_t\t*ambient_sfx[NUM_AMBIENTS] = {0};\n\n// ====================================================================\n// User-setable variables\n// ====================================================================\n\n\ncvar_t bgmvolume = {\"bgmvolume\", \"1\"}; // CD music volume\ncvar_t s_volume = {\"volume\", \"0.7\"};\ncvar_t s_raw_volume = {\"s_raw_volume\", \"1\"};\ncvar_t s_nosound = {\"s_nosound\", \"0\"};\ncvar_t s_precache = {\"s_precache\", \"1\"};\ncvar_t s_loadas8bit = {\"s_loadas8bit\", \"0\"};\ncvar_t s_ambientlevel = {\"s_ambientlevel\", \"0.3\"};\ncvar_t s_ambientfade = {\"s_ambientfade\", \"100\"};\ncvar_t s_show = {\"s_show\", \"0\"};\ncvar_t s_swapstereo = {\"s_swapstereo\", \"0\"};\ncvar_t s_linearresample = {\"s_linearresample\", \"0\", CVAR_LATCH_SOUND };\ncvar_t s_linearresample_stream = {\"s_linearresample_stream\", \"0\"};\ncvar_t s_khz = {\"s_khz\", \"11\", CVAR_NONE, OnChange_s_khz}; // If > 11, default sounds are noticeably different.\ncvar_t s_desiredsamples = {\"s_desiredsamples\", \"0\", CVAR_AUTO, OnChange_s_desiredsamples };\ncvar_t s_audiodevice = {\"s_audiodevice\", \"0\", CVAR_LATCH_SOUND };\ncvar_t s_silent_racing = { \"s_silent_racing\", \"0\" };\n\nSDL_mutex *smutex;\nsoundhw_t *shw;\nstatic SDL_AudioDeviceID audiodevid;\n\nstatic void S_ListDrivers(void)\n{\n\tint i = 0, numdrivers;\n\n\tnumdrivers = SDL_GetNumAudioDrivers();\n\n\tCom_Printf(\"Audio driver support compiled into SDL:\\n\");\n\tfor (; i < numdrivers; i++) {\n\t\tCom_Printf(\"%s\\n\", SDL_GetAudioDriver(i));\n\t}\n}\n\nstatic void S_ListAudioDevicesInternal(qbool capture)\n{\n\tint i = 0, numdevices;\n\n\tnumdevices = SDL_GetNumAudioDevices(capture); /* arg is iscapture */\n\tfor (; i < numdevices; i++) {\n\t\tCom_Printf(\" %2d  %s\\n\", i+1, SDL_GetAudioDeviceName(i, 0));\n\t}\n}\n\nstatic void S_ListAudioDevices(void)\n{\n\tCom_Printf(\"Playback devices:\\n\");\n\tCom_Printf(\" id  device name\\n-------------------------\\n\");\n\tCom_Printf(\"  0  system default\\n\");\n\tS_ListAudioDevicesInternal (false);\n\n\tCom_Printf(\"\\nInput devices:\\n\");\n\tCom_Printf(\" id  device name\\n-------------------------\\n\");\n\tCom_Printf(\"  0  system default\\n\");\n\tS_ListAudioDevicesInternal (true);\n}\n\nstatic void S_LockMixer(void)\n{\n\tSDL_LockMutex(smutex);\n}\n\nstatic void S_UnlockMixer(void)\n{\n\tSDL_UnlockMutex(smutex);\n}\n\nstatic void S_SoundInfo_f (void)\n{\n\tif (!shw) {\n\t\tCom_Printf (\"sound system not started\\n\");\n\t\treturn;\n\t}\n\tCom_Printf(\"%5d speakers\\n\", shw->numchannels);\n\tCom_Printf(\"%5d samples\\n\", shw->samples);\n\tCom_Printf(\"%5d samplepos\\n\", shw->samplepos);\n\tCom_Printf(\"%5d samplebits\\n\", shw->samplebits);\n\tCom_Printf(\"%5d kHz\\n\", shw->khz);\n\tCom_Printf(\"%5u total_channels\\n\", total_channels);\n}\n\nstatic void S_SDL_callback(void *userdata, Uint8 *stream, int len)\n{\n\t// Mixer is run in main thread when capturing, play silence instead\n\tif (Movie_IsCapturing()) {\n\t\tSDL_memset(stream, 0, len);\n\t\treturn;\n\t}\n\n\tS_LockMixer();\n\tshw->buffer = stream;\n\tshw->samples = len / shw->numchannels;\n\tS_Update_();\n\tshw->snd_sent += len;\n\tS_UnlockMixer();\n\n\t// Implicit Minimized in first case\n\tif ((sys_inactivesound.integer == 0 && !ActiveApp) || (sys_inactivesound.integer == 2 && Minimized) || cls.demoseeking) {\n\t\tSDL_memset(stream, 0, len);\n\t}\n}\n\nstatic void S_SDL_Shutdown(void)\n{\n\tCon_Printf(\"Shutting down SDL audio.\\n\");\n\n\tSDL_CloseAudioDevice(audiodevid);\n\taudiodevid = 0;\n\n\tif (SDL_WasInit(SDL_INIT_AUDIO) != 0)\n\t\tSDL_QuitSubSystem(SDL_INIT_AUDIO);\n\n\tif (smutex) {\n\t\tSDL_DestroyMutex(smutex);\n\t\tsmutex = NULL;\n\t}\n\n\tif (shw != NULL) {\n\t\tQ_free(shw);\n\t\tshw = NULL;\n\t}\n}\n\nstatic qbool S_SDL_Init(void)\n{\n\tSDL_AudioSpec desired, obtained;\n\tsoundhw_t *shw_tmp = NULL;\n\tint ret = 0;\n\tconst char *requested_device = NULL;\n\n\tif (SDL_WasInit(SDL_INIT_AUDIO) == 0)\n\t\tret = SDL_InitSubSystem(SDL_INIT_AUDIO);\n\n\tif (ret == -1) {\n\t\tCon_Printf(\"Couldn't initialize SDL audio: %s\\n\", SDL_GetError());\n\t\treturn false;\n\t}\n\n\tif (!smutex) {\n\t\tsmutex = SDL_CreateMutex();\n\t}\n\n\tmemset(&desired, 0, sizeof(desired));\n\tswitch (s_khz.integer) {\n\t\tcase 48:\n\t\t\tdesired.freq = 48000;\n\t\t\tdesired.samples = 512;\n\t\t\tbreak;\n\t\tcase 44:\n\t\t\tdesired.freq = 44100;\n\t\t\tdesired.samples = 512;\n\t\t\tbreak;\n\t\tcase 22:\n\t\t\tdesired.freq = 22050;\n\t\t\tdesired.samples = 256;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tdesired.freq = 11025;\n\t\t\tdesired.samples = 128;\n\t\t\tbreak;\n\t}\n\n\tdesired.format = AUDIO_S16LSB;\n\tdesired.channels = 2;\n\tif (s_desiredsamples.integer) {\n\t\tint desired_samples = 1;\n\n\t\t// make sure it's a power of 2\n\t\twhile (desired_samples < s_desiredsamples.integer)\n\t\t\tdesired_samples <<= 1;\n\n\t\tdesired.samples = desired_samples;\n\t}\n\tdesired.callback = S_SDL_callback;\n\n\t/* Make audiodevice list start from index 1 so that 0 can be system default */\n\tif (s_audiodevice.integer > 0) {\n\t\trequested_device = SDL_GetAudioDeviceName(s_audiodevice.integer - 1, 0);\n\t}\n\n\tif ((audiodevid = SDL_OpenAudioDevice(requested_device, 0, &desired, &obtained, 0)) <= 0) {\n\t\tCom_Printf(\"sound: couldn't open SDL audio: %s\\n\", SDL_GetError());\n\t\tif (requested_device != NULL) {\n\t\t\tCom_Printf(\"sound: retrying with default audio device\\n\");\n\t\t\tif ((audiodevid = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0)) <= 0) {\n\t\t\t\tCom_Printf(\"sound: failure again, aborting...\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tCvar_LatchedSet(&s_audiodevice, \"0\");\n\t\t}\n\t\treturn false;\n\t}\n\n\tif (obtained.format != AUDIO_S16LSB) {\n\t\tCom_Printf(\"SDL audio format %d unsupported.\\n\", obtained.format);\n\t\tgoto fail;\n\t}\n\n\tif (obtained.channels != 1 && obtained.channels != 2) {\n\t\tCom_Printf(\"SDL audio channels %d unsupported.\\n\", obtained.channels);\n\t\tgoto fail;\n\t}\n\n\tshw_tmp = Q_calloc(1, sizeof(*shw));\n\tif (!shw_tmp) {\n\t\tCom_Printf(\"Failed to alloc memory for sound structure\\n\");\n\t\tgoto fail;\n\t}\n\n\tshw_tmp->khz = obtained.freq;\n\tshw_tmp->numchannels = obtained.channels;\n\tshw_tmp->samplebits = obtained.format & 0xFF;\n\tshw_tmp->samples = 65536;\n\n\tCvar_AutoSetInt(&s_desiredsamples, obtained.samples);\n\n\tshw = shw_tmp;\n\n\tCom_Printf(\"Using SDL audio driver: %s @ %d Hz\\n\", SDL_GetCurrentAudioDriver(), obtained.freq);\n\n\tSDL_PauseAudioDevice(audiodevid, 0);\n\n\treturn true;\n\nfail:\n\tS_SDL_Shutdown();\n\treturn false;\n}\n\nstatic void S_FModCheckExtraSounds(void)\n{\n\tchar *soundlist[] = {\n\t\t\"sound/misc/menu1.wav\",\n\t\t\"sound/misc/menu2.wav\",\n\t\t\"sound/misc/menu3.wav\",\n\t\t\"sound/misc/basekey.wav\",\n\t\t\"sound/misc/talk.wav\",\n\t\t\"sound/doors/runeuse.wav\",\n\t};\n\tbyte *data;\n\tint filesize;\n\tint i;\n\n\tfor (i = 0; i < (sizeof(soundlist)/sizeof(*soundlist)); i++) {\n\t\tif (!(data = FS_LoadTempFile(soundlist[i], &filesize))) {\n\t\t\tCom_Printf(\"Couldn't load file, ignoring FMod check for '%s'\\n\", soundlist[i]);\n\t\t\tcontinue;\n\t\t}\n\n\t\tFMod_CheckModel(soundlist[i], data, filesize);\n\t}\n}\n\nstatic qbool S_Startup (void)\n{\n\tif (!snd_initialized)\n\t\treturn false;\n\n\tS_Register_LatchCvars();\n\n\tif (known_sfx == NULL) {\n\t\tknown_sfx = Q_malloc(MAX_SFX * sizeof(sfx_t));\n\t}\n\tnum_sfx = 0;\n\n\tif (!S_SDL_Init()) {\n\t\tCom_Printf (\"S_Startup: S_Init failed.\\n\");\n\t\tsnd_started = false;\n\t\tsound_spatialized = false;\n\t\treturn false;\n\t}\n\n\tsnd_started = true;\n\n\tambient_sfx[AMBIENT_WATER] = S_PrecacheSound(\"ambience/water1.wav\");\n\tambient_sfx[AMBIENT_SKY] = S_PrecacheSound(\"ambience/wind2.wav\");\n\n\t/* Make sure all extra sounds are processed through FMod checks */\n\tS_FModCheckExtraSounds();\n\n\tS_StopAllSounds();\n\n\treturn true;\n}\n\nvoid S_Shutdown (void)\n{\n\tif (!shw)\n\t\treturn;\n\n\t/* FIXME: this one free's sfx->buf's in channels array, is that correct ?? */\n\tS_StopAllSounds();\n#ifdef FTE_PEXT2_VOICECHAT\n\tS_Capture_Shutdown();\n#endif\n\tS_SDL_Shutdown();\n\n\tif (known_sfx != NULL) {\n\t\tint i;\n\t\tfor (i = 0; i < num_sfx; i++) {\n\t\t\tif (known_sfx[i].buf != NULL) {\n\t\t\t\tQ_free(known_sfx[i].buf);\n\t\t\t}\n\t\t}\n\t}\n\tQ_free(known_sfx);\n\tnum_sfx = 0;\n\n\tsnd_started = false;\n\tsound_spatialized = false;\n}\n\nstatic void S_Restart_f (void)\n{\n\tint i;\n\n\tCom_DPrintf(\"Restarting sound system....\\n\");\n\tS_Shutdown();\n\tS_Startup();\n\n\tCL_InitTEnts();\n\tfor (i=1; i < MAX_SOUNDS; i++) {\n\n\t\tif (!cl.sound_name[i][0])\n\t\t\tbreak;\n\t\tcl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i]);\n\t}\n\n}\n\nstatic void OnChange_s_khz (cvar_t *var, char *string, qbool *cancel) {\n\tif (shw && shw->khz / 1000 != Q_atoi(string)) {\n\t\tCbuf_AddText(\"s_restart\\n\");\n\t}\n}\n\nstatic void OnChange_s_desiredsamples (cvar_t *var, char *string, qbool *cancel) {\n\tif (atoi (string) != var->integer) {\n\t\tCbuf_AddText(\"s_restart\\n\");\n\t}\n}\n\nstatic void S_Register_RegularCvarsAndCommands(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SOUND);\n\tCvar_Register(&bgmvolume);\n\tCvar_Register(&s_volume);\n\tCvar_Register(&s_raw_volume);\n\tCvar_Register(&s_nosound);\n\tCvar_Register(&s_precache);\n\tCvar_Register(&s_loadas8bit);\n\tCvar_Register(&s_khz);\n\tCvar_Register(&s_ambientlevel);\n\tCvar_Register(&s_ambientfade);\n\tCvar_Register(&s_show);\n\tCvar_Register(&s_swapstereo);\n\tCvar_Register(&s_linearresample_stream);\n\tCvar_Register(&s_desiredsamples);\n\tCvar_Register(&s_silent_racing);\n\n\tCvar_ResetCurrentGroup();\n\n\t// compatibility with old configs\n\tCmd_AddLegacyCommand(\"snd_restart\", \"s_restart\"); // and snd_restart a legacy command\n\n\tCmd_AddCommand(\"s_restart\", S_Restart_f); // dimman: made s_restart the actual command\n\tCmd_AddCommand(\"mutesound\", S_MuteSound_f);\n\tCmd_AddCommand(\"play\", S_Play_f);\n\tCmd_AddCommand(\"playvol\", S_Play_f);\n\tCmd_AddCommand(\"stopsound\", S_StopAllSounds_f);\n\tCmd_AddCommand(\"stopsound_script\", S_StopSoundScript_f);\n\tCmd_AddCommand(\"soundlist\", S_SoundList_f);\n\tCmd_AddCommand(\"soundinfo\", S_SoundInfo_f);\n\tCmd_AddCommand(\"s_listdrivers\", S_ListDrivers);\n\n\t/* Naming it like this to be seen together with s_audiodevice cvar */\n\tCmd_AddCommand(\"s_audiodevicelist\", S_ListAudioDevices);\n\n#ifdef FTE_PEXT2_VOICECHAT\n\tS_Voip_RegisterCvars ();\n#endif\n}\n\nstatic void S_Register_LatchCvars(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SOUND);\n\n\tCvar_Register(&s_linearresample);\n\tCvar_Register(&s_audiodevice);\n\n\tCvar_ResetCurrentGroup();\n}\n\nvoid S_Init (void)\n{\n\tCom_DPrintf(\"\\n[sound] Initialization\\n\");\n\tif (snd_initialized) { //whoops\n\t\tCom_Printf_State (PRINT_INFO, \"[sound] Sound is already initialized!\\n\");\n\t\treturn;\n\t}\n\tif (COM_CheckParm(cmdline_param_client_nosound)) {\n\t\tCmd_AddLegacyCommand (\"play\", \"\"); // just suppress warnings\n\t\treturn;\n\t}\t\n\tif (host_memsize < 0x800000) {\n\t\tCvar_Set (&s_loadas8bit, \"1\");\n\t\tCom_Printf (\"[sound] Not enough memory. Loading all sounds as 8bit\\n\");\n\t}\n\n\tS_Register_RegularCvarsAndCommands();\n\tS_Register_LatchCvars();\n\tSND_InitScaletable ();\n\n\tknown_sfx = Q_malloc(MAX_SFX * sizeof(sfx_t));\n\tnum_sfx = 0;\n\n\tsnd_initialized = true;\n\n\tS_Startup();\n}\n\n// =======================================================================\n// Load a sound\n// =======================================================================\n\nstatic sfx_t *S_FindName (char *name)\n{\n\tint i;\n\tsfx_t *sfx;\n\n\tif (!snd_initialized || !snd_started)\n\t\treturn NULL;\n\n\tif (!name)\n\t\tSys_Error (\"S_FindName: NULL\");\n\n\tif (strlen(name) >= MAX_QPATH) {\n\t\tCom_Printf (\"S_FindName: sound name too long (%s)\\n\", name);\n\t\treturn NULL;\n\t}\n\n\t// see if already loaded\n\tfor (i = 0; i < num_sfx; i++) {\n\t\tif (!strcmp(known_sfx[i].name, name))\n\t\t\treturn &known_sfx[i];\n\t}\n\n\tif (num_sfx == MAX_SFX)\n\t\tSys_Error (\"S_FindName: out of sfx_t\");\n\n\tsfx = &known_sfx[i];\n\tstrlcpy (sfx->name, name, sizeof (sfx->name));\n\n\tnum_sfx++;\n\n\treturn sfx;\n}\n\nsfx_t *S_PrecacheSound (char *name)\n{\n\tsfx_t *sfx;\n\tif (!snd_initialized || !snd_started || s_nosound.value)\n\t\treturn NULL;\n\n\tif (name == NULL || name[0] == 0)\n\t\treturn NULL;\n\n\tsfx = S_FindName (name);\n\tif (sfx == NULL)\n\t\treturn NULL;\n\n\t// cache it in\n\tif (s_precache.value)\n\t\tS_LoadSound (sfx);\n\n\treturn sfx;\n}\n\n//=============================================================================\n\n// picks a channel based on priorities, empty slots, number of channels\nstatic channel_t *SND_PickChannel (int entnum, int entchannel)\n{\n\tint ch_idx, first_to_die, life_left;\n\n\t// Check for replacement sound, or find the best one to replace\n\tfirst_to_die = -1;\n\tlife_left = 0x7fffffff;\n\tfor (ch_idx = NUM_AMBIENTS; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS; ch_idx++) {\n\t\tif (entchannel != 0\t\t// channel 0 never overrides\n\t\t        && channels[ch_idx].entnum == entnum\n\t\t        && (channels[ch_idx].entchannel == entchannel || entchannel == -1) ) {\n\t\t\t// always override sound from same entity\n\t\t\tfirst_to_die = ch_idx;\n\t\t\tbreak;\n\t\t}\n\n\t\t// don't let monster sounds override player sounds\n\t\tif (channels[ch_idx].entnum == cl.playernum+1 && entnum != cl.playernum+1 && channels[ch_idx].sfx)\n\t\t\tcontinue;\n\n\t\tif (channels[ch_idx].end - shw->paintedtime < life_left) {\n\t\t\tlife_left = channels[ch_idx].end - shw->paintedtime;\n\t\t\tfirst_to_die = ch_idx;\n\t\t}\n\t}\n\n\tif (first_to_die == -1)\n\t\treturn NULL;\n\n\tif (channels[first_to_die].sfx)\n\t\tchannels[first_to_die].sfx = NULL;\n\n\treturn &channels[first_to_die];\n}\n\n// spatializes a channel\nstatic void SND_Spatialize (channel_t *ch)\n{\n\tvec_t dot, dist, lscale, rscale, scale;\n\tvec3_t source_vec;\n\n\t// anything coming from the view entity will always be full volume\n\tif ((ch->entnum == cl.playernum + 1) || (ch->entnum == SELF_SOUND_ENTITY)) {\n\t\tch->leftvol = ch->master_vol;\n\t\tch->rightvol = ch->master_vol;\n\t\treturn;\n\t}\n\n\t// calculate stereo seperation and distance attenuation\n\tVectorSubtract(ch->origin, listener_origin, source_vec);\n\n\tdist = VectorNormalize(source_vec) * ch->dist_mult;\n\tdot = DotProduct(listener_right, source_vec);\n\n\tif (shw->numchannels == 1) {\n\t\trscale = 1.0;\n\t\tlscale = 1.0;\n\t} else {\n\t\trscale = 1.0 + dot;\n\t\tlscale = 1.0 - dot;\n\t}\n\n\t// add in distance effect\n\tscale = (1.0 - dist) * rscale;\n\tch->rightvol = (int) (ch->master_vol * scale);\n\tif (ch->rightvol < 0)\n\t\tch->rightvol = 0;\n\n\tscale = (1.0 - dist) * lscale;\n\tch->leftvol = (int) (ch->master_vol * scale);\n\tif (ch->leftvol < 0)\n\t\tch->leftvol = 0;\n}\n\n// =======================================================================\n// Start a sound effect\n// =======================================================================\n\nvoid S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation)\n{\n\tchannel_t *target_chan, *check;\n\tsfxcache_t *sc;\n\tint ch_idx, skip;\n\n\tif (!shw || !sfx || s_nosound.value)\n\t\treturn;\n\n\tS_LockMixer();\n\n\t// pick a channel to play on\n\ttarget_chan = SND_PickChannel(entnum, entchannel);\n\tif (!target_chan) {\n\t\tS_UnlockMixer();\n\t\treturn;\n\t}\n\n\t// spatialize\n\tmemset (target_chan, 0, sizeof(*target_chan));\n\tVectorCopy(origin, target_chan->origin);\n\ttarget_chan->dist_mult = attenuation / sound_nominal_clip_dist;\n\ttarget_chan->master_vol = (int) (fvol * 255);\n\ttarget_chan->entnum = entnum;\n\ttarget_chan->entchannel = entchannel;\n\tSND_Spatialize(target_chan);\n\n\tif (!target_chan->leftvol && !target_chan->rightvol) {\n\t\tS_UnlockMixer();\n\t\treturn; // not audible at all\n\t}\n\n\t// new channel\n\tsc = S_LoadSound (sfx);\n\tif (!sc) {\n\t\ttarget_chan->sfx = NULL;\n\t\tS_UnlockMixer();\n\t\treturn; // couldn't load the sound's data\n\t}\n\n\ttarget_chan->sfx = sfx;\n\ttarget_chan->pos = 0.0;\n\ttarget_chan->end = shw->paintedtime + (int) sc->total_length;\n\n\t// if an identical sound has also been started this frame, offset the pos\n\t// a bit to keep it from just making the first one louder\n\tcheck = &channels[NUM_AMBIENTS];\n\tfor (ch_idx=NUM_AMBIENTS; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS; ch_idx++, check++) {\n\t\tif (check == target_chan)\n\t\t\tcontinue;\n\t\tif (check->sfx == sfx && !check->pos) {\n\t\t\tskip = rand () % (int)(0.1 * shw->khz);\n\t\t\tif (skip >= target_chan->end)\n\t\t\t\tskip = target_chan->end - 1;\n\t\t\ttarget_chan->pos += skip;\n\t\t\ttarget_chan->end -= skip;\n\t\t\tbreak;\n\t\t}\n\t}\n\tS_UnlockMixer();\n}\n\nvoid S_StopSound (int entnum, int entchannel)\n{\n\tunsigned int i;\n\n\tS_LockMixer();\n\n\tfor (i = 0; i < MAX_DYNAMIC_CHANNELS; i++) {\n\t\tif (channels[i].entnum == entnum && channels[i].entchannel == entchannel) {\n\t\t\tchannels[i].end = 0;\n\t\t\tchannels[i].sfx = NULL;\n\t\t\tS_UnlockMixer();\n\t\t\treturn;\n\t\t}\n\t}\n\tS_UnlockMixer();\n}\n\nstatic void S_StopSoundScript_f(void) {\n\tS_StopSound(SELF_SOUND_ENTITY, 0);\n}\n\nvoid S_StopAllSounds(void)\n{\n\tif (!shw)\n\t\treturn;\n\n\ttotal_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; // no statics\n\n\tS_LockMixer();\n\n\tmemset(channels, 0, MAX_CHANNELS * sizeof(channel_t));\n\n\tshw->numwraps = shw->oldsamplepos = shw->paintedtime = shw->samplepos = shw->snd_sent = 0;\n\n\tS_UnlockMixer();\n}\n\nstatic void S_StopAllSounds_f(void)\n{\n\tS_StopAllSounds();\n}\n\nvoid S_StaticSound (sfx_t *sfx, vec3_t origin, float vol, float attenuation)\n{\n\tchannel_t *ss;\n\tsfxcache_t *sc;\n\n\tif (!shw || !sfx || s_nosound.value)\n\t\treturn;\n\n\tS_LockMixer();\n\n\tif (total_channels == MAX_CHANNELS) {\n\t\tCom_Printf (\"total_channels == MAX_CHANNELS\\n\");\n\t\tS_UnlockMixer();\n\t\treturn;\n\t}\n\n\tss = &channels[total_channels];\n\ttotal_channels++;\n\n\tsc = S_LoadSound (sfx);\n\tif (!sc) {\n\t\tS_UnlockMixer();\n\t\treturn;\n\t}\n\n\tif (sc->loopstart == -1) {\n\t\tCom_Printf (\"Sound %s not looped\\n\", sfx->name);\n\t\tS_UnlockMixer();\n\t\treturn;\n\t}\n\n\tss->sfx = sfx;\n\tVectorCopy (origin, ss->origin);\n\tss->master_vol = (int) vol;\n\tss->dist_mult = (attenuation/64) / sound_nominal_clip_dist;\n\tss->end = shw->paintedtime + (int) sc->total_length;\n\n\tSND_Spatialize (ss);\n\n\tS_UnlockMixer();\n}\n\n//=============================================================================\n\nstatic void S_UpdateAmbientSounds (void)\n{\n\tstatic double last_adjusted = 0;\n\tstruct cleaf_s *leaf;\n\tint vol;\n\tint ambient_channel;\n\tchannel_t *chan;\n\tdouble frametime = (last_adjusted ? cls.realtime - last_adjusted : cls.frametime);\n\tint adjustment = Q_rint (frametime * s_ambientfade.value);\n\n\tif (cls.state != ca_active) {\n\t\tlast_adjusted = 0;\n\t\treturn;\n\t}\n\n\tleaf = CM_PointInLeaf (listener_origin);\n\tif (!CM_Leafnum(leaf) || !s_ambientlevel.value) {\n\t\tfor (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++)\n\t\t\tchannels[ambient_channel].sfx = NULL;\n\t\tlast_adjusted = cls.realtime;\n\t\treturn;\n\t}\n\n\tif (!adjustment) {\n\t\treturn;\n\t}\n\n\tlast_adjusted = cls.realtime;\n\n\tfor (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++) {\n\t\tchan = &channels[ambient_channel];\n\t\tchan->sfx = ambient_sfx[ambient_channel];\n\n\t\tvol = (int) (s_ambientlevel.value * CM_LeafAmbientLevel(leaf, ambient_channel));\n\t\tif (vol < 8)\n\t\t\tvol = 0;\n\n\t\t// don't adjust volume too fast\n\t\tif (chan->master_vol < vol) {\n\t\t\tchan->master_vol += adjustment;\n\t\t\tif (chan->master_vol > vol)\n\t\t\t\tchan->master_vol = vol;\n\t\t} else if (chan->master_vol > vol) {\n\t\t\tchan->master_vol -= adjustment;\n\t\t\tif (chan->master_vol < vol)\n\t\t\t\tchan->master_vol = vol;\n\t\t}\n\n\t\tchan->leftvol = chan->rightvol = chan->master_vol;\n\t}\n}\n\n//Called once each time through the main loop\nvoid S_Update (vec3_t origin, vec3_t forward, vec3_t right, vec3_t up)\n{\n\tunsigned int i, j, total;\n\tstatic unsigned int printed_total = 0;\n\tchannel_t *ch, *combine;\n\n\tif (!snd_initialized || !snd_started || !shw)\n\t\treturn;\n\n\tS_LockMixer();\n\n\tVectorCopy(origin, listener_origin);\n\tVectorCopy(forward, listener_forward);\n\tVectorCopy(right, listener_right);\n\tVectorCopy(up, listener_up);\n\n\t// update general area ambient sound sources\n\tS_UpdateAmbientSounds ();\n\n\tcombine = NULL;\n\n\t// update spatialization for static and dynamic sounds\n\tch = channels + NUM_AMBIENTS;\n\tfor (i = NUM_AMBIENTS; i < total_channels; i++, ch++) {\n\t\tif (!ch->sfx)\n\t\t\tcontinue;\n\t\tSND_Spatialize(ch); // respatialize channel\n\t\tif (!ch->leftvol && !ch->rightvol)\n\t\t\tcontinue;\n\n\t\t// try to combine static sounds with a previous channel of the same\n\t\t// sound effect so we don't mix five torches every frame\n\n\t\tif (i >= MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS) {\n\t\t\t// see if it can just use the last one\n\t\t\tif (combine && combine->sfx == ch->sfx) {\n\t\t\t\tcombine->leftvol += ch->leftvol;\n\t\t\t\tcombine->rightvol += ch->rightvol;\n\t\t\t\tch->leftvol = ch->rightvol = 0;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// search for one\n\t\t\tcombine = channels+MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS;\n\t\t\tfor (j = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; j < i; j++, combine++)\n\t\t\t\tif (combine->sfx == ch->sfx)\n\t\t\t\t\tbreak;\n\n\t\t\tif (j == total_channels) {\n\t\t\t\tcombine = NULL;\n\t\t\t} else {\n\t\t\t\tif (combine != ch) {\n\t\t\t\t\tcombine->leftvol += ch->leftvol;\n\t\t\t\t\tcombine->rightvol += ch->rightvol;\n\t\t\t\t\tch->leftvol = ch->rightvol = 0;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\n\tsound_spatialized = true;\n\n\t// debugging output\n\tif (s_show.value) {\n\t\ttotal = 0;\n\t\tch = channels;\n\n\t\tfor (i = 0; i < total_channels; i++, ch++)\n\t\t\tif (ch->sfx && (ch->leftvol || ch->rightvol)) {\n\t\t\t\tif ((cl.standby || cls.demoplayback) && s_show.value == 2)\n\t\t\t\t\tCom_Printf (\"%3i %3i %s\\n\", ch->leftvol, ch->rightvol, ch->sfx->name); // s_show 2\n\t\t\t\ttotal++;\n\t\t\t}\n\n\t\tPrint_flags[Print_current] |= PR_TR_SKIP;\n\t\t\n\t\tif (total != printed_total) { // This if statement is needed so we don't get spammed by the message\n\t\t\tCom_Printf (\"%i sound(s) playing\\n\", total); // s_show 1\n\t\t\tprinted_total = total;\n\t\t}\n\t}\n\n\tif (Movie_IsCapturing()) {\n\t\tMovie_MixFrameSound(S_Update_);\n\t}\n\n\tS_UnlockMixer();\n}\n\nstatic void GetSoundtime(void)\n{\n\tshw->samplepos = shw->snd_sent/2;\n\n\tif (shw->samplepos < shw->oldsamplepos) {\n\t\tshw->numwraps++; // buffer wrapped\n\n\t\tif (shw->paintedtime > 0x40000000) {\n\t\t\t// time to chop things off to avoid 32 bit limits\n\t\t\tshw->numwraps = 0;\n\t\t\tshw->paintedtime = shw->samples / shw->numchannels;\n\t\t\t//S_StopAllSounds (true);\n\t\t}\n\t}\n\n\tshw->oldsamplepos = shw->samplepos;\n\n\tsoundtime = shw->numwraps * (shw->samples / shw->numchannels) + shw->samplepos / shw->numchannels;\n}\n\nstatic void S_Update_(void)\n{\n\tunsigned int endtime;\n\tint samps;\n\n\tif (!shw) {\n\t\treturn;\n\t}\n\n\t// Updates soundtime\n\tGetSoundtime();\n\n\tendtime = soundtime + shw->samples / shw->numchannels;\n\tsoundtime = shw->paintedtime;\n\tsamps = shw->samples / shw->numchannels;\n\n\tif (endtime - soundtime > samps) {\n\t\tendtime = soundtime + samps;\n\t}\n\n\tS_PaintChannels(endtime);\n}\n\n/*\n===============================================================================\nconsole functions\n===============================================================================\n*/\n\nstatic void S_Play_f (void)\n{\n\tchar name[256];\n\tsfx_t *sfx;\n\tint i;\n\tqbool playvol = (strcmp(Cmd_Argv(0), \"playvol\") == 0);\n\n\tif (!snd_initialized || !snd_started || s_nosound.value)\n\t\treturn;\n\n\tfor (i = 1; i < Cmd_Argc(); ++i) {\n\t\tfloat vol = 1.0f;                 // Set by playvol command\n\t\tfloat attenuation = 0.0f;         // full volume regardless of distance\n\t\tvec3_t sound_origin;\n\t\tint entity = SELF_SOUND_ENTITY;   // ezhfan: pnum+1 changed to SELF_SOUND to make sound not to disappear\n\n\t\tstrlcpy (name, Cmd_Argv(i), sizeof(name));\n\t\tif (Rulesets_RestrictPlay(name)) {\n\t\t\tCom_Printf(\"The use of play is not allowed during matches\\n\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tVectorCopy(listener_origin, sound_origin);\n\t\tCOM_DefaultExtension (name, \".wav\", sizeof(name));\n\t\tsfx = S_PrecacheSound(name);\n\t\tif (playvol)\n\t\t\tvol = Q_atof(Cmd_Argv(++i));\n\n\t\t// Allow sounds to be created elsewhere on the map, to hear what spawns sound like\n\t\tif (i < Cmd_Argc () - 4 && strcmp (Cmd_Argv (i + 1), \"@\") == 0) {\n\t\t\tsound_origin[0] = Q_atof (Cmd_Argv (i + 2));\n\t\t\tsound_origin[1] = Q_atof (Cmd_Argv (i + 3));\n\t\t\tsound_origin[2] = Q_atof (Cmd_Argv (i + 4));\n\t\t\ti += 4;\n\t\t\tentity = PLAY_SOUND_ENTITY;\n\t\t\tattenuation = 1.0f;                 // distance should matter\n\t\t}\n\t\tS_StartSound(entity, 0, sfx, sound_origin, vol, attenuation);\n\t}\n}\n\nstatic void S_MuteSound_f(void)\n{\n\tstatic float old_volume;\n\tstatic int is_muted;\n\n\tif (is_muted == 0) {\n\t\told_volume = s_volume.value;\n\t\tCvar_SetValue(&s_volume, 0);\n\t\tCom_Printf(\"%s\\n\", CharsToBrownStatic(\"Mute\"));\n\t\tis_muted = 1;\n\t}\n\telse if (is_muted == 1) {\n\t\tCvar_SetValue(&s_volume, old_volume);\n\t\tCom_Printf(\"%s %.2f\\n\", CharsToBrownStatic(\"Volume is now\"), s_volume.value);\n\t\tis_muted = 0;\n\t}\n}\n\nstatic void S_SoundList_f (void)\n{\n\tint i, total = 0;\n\tunsigned int size;\n\tsfx_t *sfx;\n\tsfxcache_t *sc;\n\n\tS_LockMixer();\n\n\tfor (sfx = known_sfx, i = 0; i < num_sfx; i++, sfx++) {\n\t\tsc = (sfxcache_t *) sfx->buf;\n\t\tif (!sc)\n\t\t\tcontinue;\n\t\tsize = sc->total_length * sc->format.width * (sc->format.channels);\n\t\ttotal += size;\n\t\tif (sc->loopstart >= 0)\n\t\t\tCom_Printf (\"L\");\n\t\telse\n\t\t\tCom_Printf (\" \");\n\t\tCom_Printf (\"(%2db) %6i : %s\\n\",sc->format.width*8,  size, sfx->name);\n\t}\n\tCom_Printf (\"Total resident: %i\\n\", total);\n\n\tS_UnlockMixer();\n}\n\nvoid S_LocalSound (char *sound)\n{\n\tsfx_t *sfx;\n\n\tif (!snd_initialized || !snd_started || s_nosound.value)\n\t\treturn;\n\n\tif (!(sfx = S_PrecacheSound (sound))) {\n\t\tCom_Printf (\"S_LocalSound: can't cache %s\\n\", sound);\n\t\treturn;\n\t}\n\n\tS_StartSound (cl.playernum+1, -1, sfx, vec3_origin, 1, 0);\n}\n\nvoid S_LocalSoundWithVol(char *sound, float volume)\n{\n\tsfx_t *sfx;\n\n\tclamp(volume, 0, 1.0);\n\n\tif (!snd_initialized || !snd_started || s_nosound.value)\n\t\treturn;\n\n\tif (!(sfx = S_PrecacheSound (sound))) {\n\t\tCom_Printf (\"S_LocalSound: can't cache %s\\n\", sound);\n\t\treturn;\n\t}\n\n\tS_StartSound (cl.playernum+1, -1, sfx, vec3_origin, volume, 1);\n}\n\n\n//===================================================================\n// Streaming audio.\n// This is useful when there is one source, and the sound is to be played with no attenuation.\n//===================================================================\n\n#define MAX_RAW_CACHE (1024 * 32) // have no idea which size it actually should be.\n\ntypedef struct\n{\n\tqbool\t\t\tinuse;\n\tint\t\t\t\tid;\n\tsfx_t\t\t\tsfx;\n} streaming_t;\n\nstatic void S_RawClearStream(streaming_t *s);\n\n#define MAX_RAW_SOURCES (MAX_CLIENTS+1)\n\nstreaming_t s_streamers[MAX_RAW_SOURCES] = {{0}};\n\nvoid S_RawClear(void)\n{\n\tint i;\n\tstreaming_t *s;\n\n\tfor (s = s_streamers, i = 0; i < MAX_RAW_SOURCES; i++, s++) {\n\t\tS_RawClearStream(s);\n\t}\n\n\tmemset(s_streamers, 0, sizeof(s_streamers));\n}\n\n// Stop playing particular stream and make it free.\nstatic void S_RawClearStream(streaming_t *s)\n{\n\tint i;\n\tsfxcache_t * currentcache;\n\n\tif (!s)\n\t\treturn;\n\n\t// get current cache if any.\n\tcurrentcache = s->sfx.buf;\n\tif (currentcache) {\n\t\tcurrentcache->loopstart = -1;\t//stop mixing it\n\t}\n\n\t// remove link on sfx from the channels array.\n\tfor (i = 0; i < total_channels; i++)\n\t{\n\t\tif (channels[i].sfx == &s->sfx)\n\t\t{\n\t\t\tchannels[i].sfx = NULL;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// free cache.\n\t//if (s->sfx.buf)\n\t//\tCache_Free(&s->sfx.cache);\n\n\t// clear whole struct.\n\tmemset(s, 0, sizeof(*s));\n}\n\n// Searching for free slot or re-use previous one with the same sourceid.\nstatic streaming_t * S_RawGetFreeStream(int sourceid)\n{\n\tint i;\n\tstreaming_t *s, *free = NULL;\n\n\tfor (s = s_streamers, i = 0; i < MAX_RAW_SOURCES; i++, s++)\n\t{\n\t\tif (!s->inuse)\n\t\t{\n\t\t\tif (!free)\n\t\t\t{\n\t\t\t\tfree = s;\t// found free stream slot.\n\t\t\t}\n\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (s->id == sourceid)\n\t\t{\n\t\t\treturn s; // re-using slot.\n\t\t}\n\t}\n\n\treturn free;\n}\n\n// Streaming audio.\n// This is useful when there is one source, and the sound is to be played with no attenuation.\nvoid S_RawAudio(int sourceid, byte *data, unsigned int speed, unsigned int samples, unsigned int channelsnum, unsigned int width)\n{\n\tunsigned int\ti;\n\tint\t\t\t\tnewsize;\n\tint\t\t\t\tprepadl;\n\tint\t\t\t\tspare;\n\tint\t\t\t\toutsamples;\n\tint\t\t\t\traw_flags;\n\tfloat\t\t\traw_volume;\n\tdouble\t\t\tspeedfactor;\n\tsfxcache_t *\tcurrentcache;\n\tstreaming_t *\ts;\n\n\tS_LockMixer();\n\n\traw_flags = (sourceid >= 0 && sourceid <= RAW_SOURCE_QIZMO_VOICE) ? CHANNEL_FLAG_VOICE : 0;\n\traw_volume = (raw_flags & CHANNEL_FLAG_VOICE) ? 1 : s_raw_volume.value;\n\n\t// search for free slot or re-use previous one with the same sourceid.\n\ts = S_RawGetFreeStream(sourceid);\n\n\tif (!s) {\n\t\tCom_DPrintf(\"No free audio streams or stream not found\\n\");\n\t\tS_UnlockMixer();\n\t\treturn;\n\t}\n\n\t// empty data mean someone tell us to shut up particular slot.\n\tif (!data) {\n\t\tS_RawClearStream(s);\n\t\tS_UnlockMixer();\n\t\treturn;\n\t}\n\n\t// attempting to add new stream\n\tif (!s->inuse) {\n\t\tsfxcache_t* newcache;\n\n\t\t// clear whole struct.\n\t\tmemset(s, 0, sizeof(*s));\n\t\t// allocate cache.\n\t\tnewsize = MAX_RAW_CACHE; //sizeof(sfxcache_t)\n\n\t\tif (newsize < sizeof (sfxcache_t)) {\n\t\t\tS_UnlockMixer ();\n\t\t\tSys_Error (\"MAX_RAW_CACHE too small %d\", newsize);\n\t\t}\n\n\t\tnewcache = Q_malloc(newsize);\n\t\tif (!newcache) {\n\t\t\tCom_DPrintf(\"Cache_Alloc failed\\n\");\n\t\t\tS_UnlockMixer();\n\t\t\treturn;\n\t\t}\n\n\t\ts->inuse = true;\n\t\ts->id = sourceid;\n\t\t//\t\tstrcpy(s->sfx.name, \"\"); // FIXME: probably we should put some specific tag name here?\n\t\ts->sfx.buf = newcache;\n\t\tnewcache->format.speed = shw->khz;\n\t\tnewcache->format.channels = channelsnum;\n\t\tnewcache->format.width = width;\n\t\tnewcache->loopstart = -1;\n\t\tnewcache->total_length = 0;\n\t\tnewcache = NULL;\n\t}\n\n\t// get current cache if any.\n\tcurrentcache = (sfxcache_t *)s->sfx.buf;\n\tif (!currentcache) {\n\t\tCom_DPrintf(\"Cache_Check failed\\n\");\n\t\tS_RawClearStream(s);\n\t\tS_UnlockMixer();\n\t\treturn;\n\t}\n\n\tif (   currentcache->format.speed != shw->khz\n\t\t|| currentcache->format.channels != channelsnum\n\t\t|| currentcache->format.width != width)\n\t{\n\t\tcurrentcache->format.speed = shw->khz;\n\t\tcurrentcache->format.channels = channelsnum;\n\t\tcurrentcache->format.width = width;\n\t\t//\t\tnewcache->loopstart = -1;\n\t\tcurrentcache->total_length = 0;\n\t\t//\t\tCom_Printf(\"Restarting raw stream\\n\");\n\t}\n\n\tspeedfactor\t= (double)speed/shw->khz;\n\toutsamples = samples/speedfactor;\n\n\tprepadl = 0x7fffffff;\n\t// FIXME: qqshka: I have no idea that spike is doing here, really. WTF is prepadl??? PITCHSHIFT ???\n\t// spike: make sure that we have a prepad.\n\tfor (i = 0; i < total_channels; i++)\n\t{\n\t\tif (channels[i].sfx == &s->sfx)\n\t\t{\n\t\t\tif (prepadl > (channels[i].pos/*>>PITCHSHIFT*/))\n\t\t\t\tprepadl = (channels[i].pos/*>>PITCHSHIFT*/);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (prepadl == 0x7fffffff)\n\t{\n\t\tif (s_show.integer)\n\t\t\tCom_Printf(\"Wasn't playing\\n\");\n\t\tprepadl = 0;\n\t\tspare = 0;\n\t\tif (spare > shw->khz)\n\t\t{\n\t\t\tCom_DPrintf(\"Sacrificed raw sound stream\\n\");\n\t\t\tspare = 0;\t//too far out. sacrifice it all\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (prepadl < 0)\n\t\t\tprepadl = 0;\n\t\tspare = currentcache->total_length - prepadl;\n\t\tif (spare < 0)\t//remaining samples since last time\n\t\t\tspare = 0;\n\n\t\tif (spare > shw->khz * 2) { // more than 2 seconds of sound\n\t\t\tCom_DPrintf(\"Sacrificed raw sound stream\\n\");\n\t\t\tspare = 0;\t//too far out. sacrifice it all\n\t\t}\n\t}\n\n\tnewsize = sizeof(sfxcache_t) + (spare + outsamples) * currentcache->format.channels * currentcache->format.width;\n\tif (newsize >= MAX_RAW_CACHE)\n\t{\n\t\tCom_DPrintf(\"Cache buffer overflowed\\n\");\n\t\tS_RawClearStream(s);\n\t\tS_UnlockMixer();\n\t\treturn;\n\t}\n\t// move along spare/remaning samples in the begging of the buffer.\n\tmemmove(currentcache->data, \n\t\tcurrentcache->data + prepadl * currentcache->format.channels * currentcache->format.width,\n\t\tspare * currentcache->format.channels * currentcache->format.width);\n\n\tcurrentcache->total_length = spare + outsamples;\n\n\t// resample.\n\t{\n\t\textern cvar_t s_linearresample_stream;\n\t\tshort *outpos = (short *)(currentcache->data + spare * currentcache->format.channels * currentcache->format.width);\n\t\tSND_ResampleStream(data,\n\t\t\tspeed,\n\t\t\twidth,\n\t\t\tchannelsnum,\n\t\t\tsamples,\n\t\t\toutpos,\n\t\t\tshw->khz,\n\t\t\tcurrentcache->format.width,\n\t\t\tcurrentcache->format.channels,\n\t\t\ts_linearresample_stream.integer\n\t\t);\n\t}\n\n\tcurrentcache->loopstart = -1;//currentcache->total_length;\n\tfor (i = 0; i < total_channels; i++)\n\t{\n\t\tif (channels[i].sfx == &s->sfx)\n\t\t{\n#if 0\n\t\t\t// FIXME: qqshka: hrm, should it be just like this all the time??? I think it should.\n\t\t\tchannels[i].pos = 0;\n\t\t\tchannels[i].end = paintedtime + currentcache->total_length;\n#else\n\t\t\tchannels[i].pos -= prepadl; // * channels[i].rate;\n\t\t\tchannels[i].end += outsamples;\n\t\t\tchannels[i].master_vol = (int) (raw_volume * 255); // this should changed volume on alredy playing sound.\n\t\t\tchannels[i].flags = raw_flags;\n\n\t\t\tif (channels[i].end < shw->paintedtime)\n\t\t\t{\n\t\t\t\tchannels[i].pos = 0;\n\t\t\t\tchannels[i].end = shw->paintedtime + currentcache->total_length;\n\t\t\t}\n#endif\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t//this one wasn't playing, lets start it then.\n\tif (i == total_channels) {\n\t\tS_StartSound(SELF_SOUND_ENTITY, 0, &s->sfx, r_origin, raw_volume, 0);\n\t\tfor (i = 0; i < total_channels; i++) {\n\t\t\tif (channels[i].sfx == &s->sfx) {\n\t\t\t\tchannels[i].flags = raw_flags;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tS_UnlockMixer();\n}\n"
  },
  {
    "path": "src/snd_mem.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: snd_mem.c,v 1.16 2007-10-25 14:54:30 dkure Exp $\n*/\n// snd_mem.c -- sound caching\n\n#include \"quakedef.h\"\n#include \"fmod.h\"\n#include \"qsound.h\"\n#ifndef OLD_WAV_LOADING\n#include \"sndfile.h\"\n#endif\n\n#define LINEARUPSCALE(in, inrate, insamps, out, outrate, outlshift, outrshift) \\\n\t{ \\\n\t\tscale = inrate / (double)outrate; \\\n\t\tinfrac = floor(scale * 65536); \\\n\t\toutsamps = insamps / scale; \\\n\t\tinaccum = 0; \\\n\t\toutnlsamps = floor(1.0 / scale); \\\n\t\toutsamps -= outnlsamps; \\\n\t\\\n\t\twhile (outsamps) \\\n\t\t{ \\\n\t\t\t*out = ((0xFFFF - inaccum)*in[0] + inaccum*in[1]) >> (16 - outlshift + outrshift); \\\n\t\t\tinaccum += infrac; \\\n\t\t\tin += (inaccum >> 16); \\\n\t\t\tinaccum &= 0xFFFF; \\\n\t\t\tout++; \\\n\t\t\toutsamps--; \\\n\t\t} \\\n\t\twhile (outnlsamps) \\\n\t\t{ \\\n\t\t\t*out = (*in >> outrshift) << outlshift; \\\n\t\t\tout++; \\\n\t\t\toutnlsamps--; \\\n\t\t} \\\n\t}\n\n#define LINEARUPSCALESTEREO(in, inrate, insamps, out, outrate, outlshift, outrshift) \\\n\t{ \\\n\t\tscale = inrate / (double)outrate; \\\n\t\tinfrac = floor(scale * 65536); \\\n\t\toutsamps = insamps / scale; \\\n\t\tinaccum = 0; \\\n\t\toutnlsamps = floor(1.0 / scale); \\\n\t\toutsamps -= outnlsamps; \\\n\t\\\n\t\twhile (outsamps) \\\n\t\t{ \\\n\t\t\tout[0] = ((0xFFFF - inaccum)*in[0] + inaccum*in[2]) >> (16 - outlshift + outrshift); \\\n\t\t\tout[1] = ((0xFFFF - inaccum)*in[1] + inaccum*in[3]) >> (16 - outlshift + outrshift); \\\n\t\t\tinaccum += infrac; \\\n\t\t\tin += (inaccum >> 16) * 2; \\\n\t\t\tinaccum &= 0xFFFF; \\\n\t\t\tout += 2; \\\n\t\t\toutsamps--; \\\n\t\t} \\\n\t\twhile (outnlsamps) \\\n\t\t{ \\\n\t\t\tout[0] = (in[0] >> outrshift) << outlshift; \\\n\t\t\tout[1] = (in[1] >> outrshift) << outlshift; \\\n\t\t\tout += 2; \\\n\t\t\toutnlsamps--; \\\n\t\t} \\\n\t}\n\n#define LINEARUPSCALESTEREOTOMONO(in, inrate, insamps, out, outrate, outlshift, outrshift) \\\n\t{ \\\n\t\tscale = inrate / (double)outrate; \\\n\t\tinfrac = floor(scale * 65536); \\\n\t\toutsamps = insamps / scale; \\\n\t\tinaccum = 0; \\\n\t\toutnlsamps = floor(1.0 / scale); \\\n\t\toutsamps -= outnlsamps; \\\n\t\\\n\t\twhile (outsamps) \\\n\t\t{ \\\n\t\t\t*out = ((((0xFFFF - inaccum)*in[0] + inaccum*in[2]) >> (16 - outlshift + outrshift)) + \\\n\t\t\t\t(((0xFFFF - inaccum)*in[1] + inaccum*in[3]) >> (16 - outlshift + outrshift))) >> 1; \\\n\t\t\tinaccum += infrac; \\\n\t\t\tin += (inaccum >> 16) * 2; \\\n\t\t\tinaccum &= 0xFFFF; \\\n\t\t\tout++; \\\n\t\t\toutsamps--; \\\n\t\t} \\\n\t\twhile (outnlsamps) \\\n\t\t{ \\\n\t\t\tout[0] = (((in[0] >> outrshift) << outlshift) + ((in[1] >> outrshift) << outlshift)) >> 1; \\\n\t\t\tout++; \\\n\t\t\toutnlsamps--; \\\n\t\t} \\\n\t}\n\n#define LINEARDOWNSCALE(in, inrate, insamps, out, outrate, outlshift, outrshift) \\\n\t{ \\\n\t\tscale = outrate / (double)inrate; \\\n\t\tinfrac = floor(scale * 65536); \\\n\t\tinaccum = 0; \\\n\t\tinsamps--; \\\n\t\toutsampleft = 0; \\\n\t\\\n\t\twhile (insamps) \\\n\t\t{ \\\n\t\t\tinaccum += infrac; \\\n\t\t\tif (inaccum >> 16) \\\n\t\t\t{ \\\n\t\t\t\tinaccum &= 0xFFFF; \\\n\t\t\t\toutsampleft += (infrac - inaccum) * (*in); \\\n\t\t\t\t*out = outsampleft >> (16 - outlshift + outrshift); \\\n\t\t\t\tout++; \\\n\t\t\t\toutsampleft = inaccum * (*in); \\\n\t\t\t} \\\n\t\t\telse \\\n\t\t\t\toutsampleft += infrac * (*in); \\\n\t\t\tin++; \\\n\t\t\tinsamps--; \\\n\t\t} \\\n\t\toutsampleft += (0xFFFF - inaccum) * (*in);\\\n\t\t*out = outsampleft >> (16 - outlshift + outrshift); \\\n\t}\n\n#define LINEARDOWNSCALESTEREO(in, inrate, insamps, out, outrate, outlshift, outrshift) \\\n\t{ \\\n\t\tscale = outrate / (double)inrate; \\\n\t\tinfrac = floor(scale * 65536); \\\n\t\tinaccum = 0; \\\n\t\tinsamps--; \\\n\t\toutsampleft = 0; \\\n\t\toutsampright = 0; \\\n\t\\\n\t\twhile (insamps) \\\n\t\t{ \\\n\t\t\tinaccum += infrac; \\\n\t\t\tif (inaccum >> 16) \\\n\t\t\t{ \\\n\t\t\t\tinaccum &= 0xFFFF; \\\n\t\t\t\toutsampleft += (infrac - inaccum) * in[0]; \\\n\t\t\t\toutsampright += (infrac - inaccum) * in[1]; \\\n\t\t\t\tout[0] = outsampleft >> (16 - outlshift + outrshift); \\\n\t\t\t\tout[1] = outsampright >> (16 - outlshift + outrshift); \\\n\t\t\t\tout += 2; \\\n\t\t\t\toutsampleft = inaccum * in[0]; \\\n\t\t\t\toutsampright = inaccum * in[1]; \\\n\t\t\t} \\\n\t\t\telse \\\n\t\t\t{ \\\n\t\t\t\toutsampleft += infrac * in[0]; \\\n\t\t\t\toutsampright += infrac * in[1]; \\\n\t\t\t} \\\n\t\t\tin += 2; \\\n\t\t\tinsamps--; \\\n\t\t} \\\n\t\toutsampleft += (0xFFFF - inaccum) * in[0];\\\n\t\toutsampright += (0xFFFF - inaccum) * in[1];\\\n\t\tout[0] = outsampleft >> (16 - outlshift + outrshift); \\\n\t\tout[1] = outsampright >> (16 - outlshift + outrshift); \\\n\t}\n\n#define LINEARDOWNSCALESTEREOTOMONO(in, inrate, insamps, out, outrate, outlshift, outrshift) \\\n\t{ \\\n\t\tscale = outrate / (double)inrate; \\\n\t\tinfrac = floor(scale * 65536); \\\n\t\tinaccum = 0; \\\n\t\tinsamps--; \\\n\t\toutsampleft = 0; \\\n\t\\\n\t\twhile (insamps) \\\n\t\t{ \\\n\t\t\tinaccum += infrac; \\\n\t\t\tif (inaccum >> 16) \\\n\t\t\t{ \\\n\t\t\t\tinaccum &= 0xFFFF; \\\n\t\t\t\toutsampleft += (infrac - inaccum) * ((in[0] + in[1]) >> 1); \\\n\t\t\t\t*out = outsampleft >> (16 - outlshift + outrshift); \\\n\t\t\t\tout++; \\\n\t\t\t\toutsampleft = inaccum * ((in[0] + in[1]) >> 1); \\\n\t\t\t} \\\n\t\t\telse \\\n\t\t\t\toutsampleft += infrac * ((in[0] + in[1]) >> 1); \\\n\t\t\tin += 2; \\\n\t\t\tinsamps--; \\\n\t\t} \\\n\t\toutsampleft += (0xFFFF - inaccum) * ((in[0] + in[1]) >> 1);\\\n\t\t*out = outsampleft >> (16 - outlshift + outrshift); \\\n\t}\n\n#define STANDARDRESCALE(in, inrate, insamps, out, outrate, outlshift, outrshift) \\\n\t{ \\\n\t\tscale = inrate / (double)outrate; \\\n\t\tinfrac = floor(scale * 65536); \\\n\t\toutsamps = insamps / scale; \\\n\t\tinaccum = 0; \\\n\t\\\n\t\twhile (outsamps) \\\n\t\t{ \\\n\t\t\t*out = (*in >> outrshift) << outlshift; \\\n\t\t\tinaccum += infrac; \\\n\t\t\tin += (inaccum >> 16); \\\n\t\t\tinaccum &= 0xFFFF; \\\n\t\t\tout++; \\\n\t\t\toutsamps--; \\\n\t\t} \\\n\t}\n\n#define STANDARDRESCALESTEREO(in, inrate, insamps, out, outrate, outlshift, outrshift) \\\n\t{ \\\n\t\tscale = inrate / (double)outrate; \\\n\t\tinfrac = floor(scale * 65536); \\\n\t\toutsamps = insamps / scale; \\\n\t\tinaccum = 0; \\\n\t\\\n\t\twhile (outsamps) \\\n\t\t{ \\\n\t\t\tout[0] = (in[0] >> outrshift) << outlshift; \\\n\t\t\tout[1] = (in[1] >> outrshift) << outlshift; \\\n\t\t\tinaccum += infrac; \\\n\t\t\tin += (inaccum >> 16) * 2; \\\n\t\t\tinaccum &= 0xFFFF; \\\n\t\t\tout += 2; \\\n\t\t\toutsamps--; \\\n\t\t} \\\n\t}\n\n#define STANDARDRESCALESTEREOTOMONO(in, inrate, insamps, out, outrate, outlshift, outrshift) \\\n\t{ \\\n\t\tscale = inrate / (double)outrate; \\\n\t\tinfrac = floor(scale * 65536); \\\n\t\toutsamps = insamps / scale; \\\n\t\tinaccum = 0; \\\n\t\\\n\t\twhile (outsamps) \\\n\t\t{ \\\n\t\t\tout[0] = (((in[0] >> outrshift) << outlshift) + ((in[1] >> outrshift) << outlshift)) >> 1; \\\n\t\t\tinaccum += infrac; \\\n\t\t\tin += (inaccum >> 16) * 2; \\\n\t\t\tinaccum &= 0xFFFF; \\\n\t\t\tout++; \\\n\t\t\toutsamps--; \\\n\t\t} \\\n\t}\n\n#define QUICKCONVERT(in, insamps, out, outlshift, outrshift) \\\n\t{ \\\n\t\twhile (insamps) \\\n\t\t{ \\\n\t\t\t*out = (*in >> outrshift) << outlshift; \\\n\t\t\tout++; \\\n\t\t\tin++; \\\n\t\t\tinsamps--; \\\n\t\t} \\\n\t}\n\n#define QUICKCONVERTSTEREOTOMONO(in, insamps, out, outlshift, outrshift) \\\n\t{ \\\n\t\twhile (insamps) \\\n\t\t{ \\\n\t\t\t*out = (((in[0] >> outrshift) << outlshift) + ((in[1] >> outrshift) << outlshift)) >> 1; \\\n\t\t\tout++; \\\n\t\t\tin += 2; \\\n\t\t\tinsamps--; \\\n\t\t} \\\n\t}\n\n// SND_ResampleStream: takes a sound stream and converts with given parameters. Limited to\n// 8-16-bit signed conversions and mono-to-mono/stereo-to-stereo conversions.\n// Not an in-place algorithm.\nvoid SND_ResampleStream (void *in, int inrate, int inwidth, int inchannels, int insamps, void *out, int outrate, int outwidth, int outchannels, int resampstyle)\n{\n\tdouble scale;\n\tsigned char *in8 = (signed char *)in;\n\tshort *in16 = (short *)in;\n\tsigned char *out8 = (signed char *)out;\n\tshort *out16 = (short *)out;\n\tint outsamps, outnlsamps, outsampleft, outsampright;\n\tint infrac, inaccum;\n\n\tif (insamps <= 0)\n\t\treturn;\n\n\tif (inchannels == outchannels && inwidth == outwidth && inrate == outrate)\n\t{\n\t\tmemcpy(out, in, inwidth*insamps*inchannels);\n\t\treturn;\n\t}\n\n\tif (inchannels == 1 && outchannels == 1)\n\t{\n\t\tif (inwidth == 1)\n\t\t{\n\t\t\tif (outwidth == 1)\n\t\t\t{\n\t\t\t\tif (inrate < outrate) // upsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle)\n\t\t\t\t\t\tLINEARUPSCALE(in8, inrate, insamps, out8, outrate, 0, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALE(in8, inrate, insamps, out8, outrate, 0, 0)\n\t\t\t\t}\n\t\t\t\telse // downsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle > 1)\n\t\t\t\t\t\tLINEARDOWNSCALE(in8, inrate, insamps, out8, outrate, 0, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALE(in8, inrate, insamps, out8, outrate, 0, 0)\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (inrate == outrate) // quick convert\n\t\t\t\t\tQUICKCONVERT(in8, insamps, out16, 8, 0)\n\t\t\t\telse if (inrate < outrate) // upsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle)\n\t\t\t\t\t\tLINEARUPSCALE(in8, inrate, insamps, out16, outrate, 8, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALE(in8, inrate, insamps, out16, outrate, 8, 0)\n\t\t\t\t}\n\t\t\t\telse // downsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle > 1)\n\t\t\t\t\t\tLINEARDOWNSCALE(in8, inrate, insamps, out16, outrate, 8, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALE(in8, inrate, insamps, out16, outrate, 8, 0)\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\telse // 16-bit\n\t\t{\n\t\t\tif (outwidth == 2)\n\t\t\t{\n\t\t\t\tif (inrate < outrate) // upsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle)\n\t\t\t\t\t\tLINEARUPSCALE(in16, inrate, insamps, out16, outrate, 0, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALE(in16, inrate, insamps, out16, outrate, 0, 0)\n\t\t\t\t}\n\t\t\t\telse // downsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle > 1)\n\t\t\t\t\t\tLINEARDOWNSCALE(in16, inrate, insamps, out16, outrate, 0, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALE(in16, inrate, insamps, out16, outrate, 0, 0)\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (inrate == outrate) // quick convert\n\t\t\t\t\tQUICKCONVERT(in16, insamps, out8, 0, 8)\n\t\t\t\telse if (inrate < outrate) // upsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle)\n\t\t\t\t\t\tLINEARUPSCALE(in16, inrate, insamps, out8, outrate, 0, 8)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALE(in16, inrate, insamps, out8, outrate, 0, 8)\n\t\t\t\t}\n\t\t\t\telse // downsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle > 1)\n\t\t\t\t\t\tLINEARDOWNSCALE(in16, inrate, insamps, out8, outrate, 0, 8)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALE(in16, inrate, insamps, out8, outrate, 0, 8)\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\telse if (outchannels == 2 && inchannels == 2)\n\t{\n\t\tif (inwidth == 1)\n\t\t{\n\t\t\tif (outwidth == 1)\n\t\t\t{\n\t\t\t\tif (inrate < outrate) // upsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle)\n\t\t\t\t\t\tLINEARUPSCALESTEREO(in8, inrate, insamps, out8, outrate, 0, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALESTEREO(in8, inrate, insamps, out8, outrate, 0, 0)\n\t\t\t\t}\n\t\t\t\telse // downsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle > 1)\n\t\t\t\t\t\tLINEARDOWNSCALESTEREO(in8, inrate, insamps, out8, outrate, 0, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALESTEREO(in8, inrate, insamps, out8, outrate, 0, 0)\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (inrate == outrate) // quick convert\n\t\t\t\t{\n\t\t\t\t\tinsamps *= 2;\n\t\t\t\t\tQUICKCONVERT(in8, insamps, out16, 8, 0)\n\t\t\t\t}\n\t\t\t\telse if (inrate < outrate) // upsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle)\n\t\t\t\t\t\tLINEARUPSCALESTEREO(in8, inrate, insamps, out16, outrate, 8, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALESTEREO(in8, inrate, insamps, out16, outrate, 8, 0)\n\t\t\t\t}\n\t\t\t\telse // downsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle > 1)\n\t\t\t\t\t\tLINEARDOWNSCALESTEREO(in8, inrate, insamps, out16, outrate, 8, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALESTEREO(in8, inrate, insamps, out16, outrate, 8, 0)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse // 16-bit\n\t\t{\n\t\t\tif (outwidth == 2)\n\t\t\t{\n\t\t\t\tif (inrate < outrate) // upsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle)\n\t\t\t\t\t\tLINEARUPSCALESTEREO(in16, inrate, insamps, out16, outrate, 0, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALESTEREO(in16, inrate, insamps, out16, outrate, 0, 0)\n\t\t\t\t}\n\t\t\t\telse // downsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle > 1)\n\t\t\t\t\t\tLINEARDOWNSCALESTEREO(in16, inrate, insamps, out16, outrate, 0, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALESTEREO(in16, inrate, insamps, out16, outrate, 0, 0)\n\t\t\t\t}\n\t\t\t}\n\t\t\telse \n\t\t\t{\n\t\t\t\tif (inrate == outrate) // quick convert\n\t\t\t\t{\n\t\t\t\t\tinsamps *= 2;\n\t\t\t\t\tQUICKCONVERT(in16, insamps, out8, 0, 8)\n\t\t\t\t}\n\t\t\t\telse if (inrate < outrate) // upsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle)\n\t\t\t\t\t\tLINEARUPSCALESTEREO(in16, inrate, insamps, out8, outrate, 0, 8)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALESTEREO(in16, inrate, insamps, out8, outrate, 0, 8)\n\t\t\t\t}\n\t\t\t\telse // downsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle > 1)\n\t\t\t\t\t\tLINEARDOWNSCALESTEREO(in16, inrate, insamps, out8, outrate, 0, 8)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALESTEREO(in16, inrate, insamps, out8, outrate, 0, 8)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n#if 0\n\telse if (outchannels == 1 && inchannels == 2)\n\t{\n\t\tif (inwidth == 1)\n\t\t{\n\t\t\tif (outwidth == 1)\n\t\t\t{\n\t\t\t\tif (inrate < outrate) // upsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle)\n\t\t\t\t\t\tLINEARUPSCALESTEREOTOMONO(in8, inrate, insamps, out8, outrate, 0, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALESTEREOTOMONO(in8, inrate, insamps, out8, outrate, 0, 0)\n\t\t\t\t}\n\t\t\t\telse // downsample\n\t\t\t\t\tSTANDARDRESCALESTEREOTOMONO(in8, inrate, insamps, out8, outrate, 0, 0)\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (inrate == outrate) // quick convert\n\t\t\t\t\tQUICKCONVERTSTEREOTOMONO(in8, insamps, out16, 8, 0)\n\t\t\t\telse if (inrate < outrate) // upsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle)\n\t\t\t\t\t\tLINEARUPSCALESTEREOTOMONO(in8, inrate, insamps, out16, outrate, 8, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALESTEREOTOMONO(in8, inrate, insamps, out16, outrate, 8, 0)\n\t\t\t\t}\n\t\t\t\telse // downsample\n\t\t\t\t\tSTANDARDRESCALESTEREOTOMONO(in8, inrate, insamps, out16, outrate, 8, 0)\n\t\t\t}\n\t\t}\n\t\telse // 16-bit\n\t\t{\n\t\t\tif (outwidth == 2)\n\t\t\t{\n\t\t\t\tif (inrate < outrate) // upsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle)\n\t\t\t\t\t\tLINEARUPSCALESTEREOTOMONO(in16, inrate, insamps, out16, outrate, 0, 0)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALESTEREOTOMONO(in16, inrate, insamps, out16, outrate, 0, 0)\n\t\t\t\t}\n\t\t\t\telse // downsample\n\t\t\t\t\tSTANDARDRESCALESTEREOTOMONO(in16, inrate, insamps, out16, outrate, 0, 0)\n\t\t\t}\n\t\t\telse \n\t\t\t{\n\t\t\t\tif (inrate == outrate) // quick convert\n\t\t\t\t\tQUICKCONVERTSTEREOTOMONO(in16, insamps, out8, 0, 8)\n\t\t\t\telse if (inrate < outrate) // upsample\n\t\t\t\t{\n\t\t\t\t\tif (resampstyle)\n\t\t\t\t\t\tLINEARUPSCALESTEREOTOMONO(in16, inrate, insamps, out8, outrate, 0, 8)\n\t\t\t\t\telse\n\t\t\t\t\t\tSTANDARDRESCALESTEREOTOMONO(in16, inrate, insamps, out8, outrate, 0, 8)\n\t\t\t\t}\n\t\t\t\telse // downsample\n\t\t\t\t\tSTANDARDRESCALESTEREOTOMONO(in16, inrate, insamps, out8, outrate, 0, 8)\n\t\t\t}\n\t\t}\n\t}\n#endif\n}\n\n/*\n================\nResampleSfx\n================\n*/\nstatic void ResampleSfx (sfx_t *sfx, int inrate, int inchannels, int inwidth, int insamps, int inloopstart, byte *data)\n{\n\textern cvar_t s_linearresample;\n\tdouble scale;\n\tsfxcache_t\t*sc;\n\tint len;\n\tint outsamps;\n\tint outwidth;\n\tint outchannels = 1; // inchannels;\n\n\tscale = shw->khz / (double)inrate;\n\toutsamps = insamps * scale;\n\tif (s_loadas8bit.integer < 0)\n\t\toutwidth = 2;\n\telse if (s_loadas8bit.integer)\n\t\toutwidth = 1;\n\telse\n\t\toutwidth = inwidth;\n\tlen = outsamps * outwidth * outchannels;\n\n\tsc = sfx->buf = Q_malloc(len + sizeof(sfxcache_t));\n\tif (!sc)\n\t{\n\t\treturn;\n\t}\n\n\tsc->format.channels = outchannels;\n\tsc->format.width = outwidth;\n\tsc->format.speed = shw->khz;\n\tsc->total_length = outsamps;\n\tif (inloopstart == -1)\n\t\tsc->loopstart = inloopstart;\n\telse\n\t\tsc->loopstart = inloopstart * scale;\n\n\tSND_ResampleStream (data, \n\t\tinrate, \n\t\tinwidth, \n\t\tinchannels, \n\t\tinsamps, \n\t\tsc->data, \n\t\tsc->format.speed, \n\t\tsc->format.width, \n\t\tsc->format.channels, \n\t\ts_linearresample.integer);\n}\n\n#ifndef OLD_WAV_LOADING\n\n/*\n===============================================================================\nWAV loading\n===============================================================================\n*/\n\ntypedef struct sfviodata_s {\n\tchar *path;\n\tint filesize;\n\tint position;\n\tunsigned char *data;\n} sfviodata_t;\n\nsf_count_t SFVIO_GetFilelen(void *user_data)\n{\n\tsfviodata_t *sfviodata;\n\n\tsfviodata = user_data;\n\treturn sfviodata->filesize;\n}\n\nsf_count_t SFVIO_Seek(sf_count_t offset, int whence, void *user_data)\n{\n\tsfviodata_t *sfviodata;\n\n\tsfviodata = user_data;\n\tif (whence == SEEK_CUR)\n\t{\n\t\tsfviodata->position += offset;\n\t}\n\telse if (whence == SEEK_SET)\n\t{\n\t\tsfviodata->position = offset;\n\t}\n\telse if (whence == SEEK_END)\n\t{\n\t\tsfviodata->position = sfviodata->filesize + offset;\n\t}\n\tif (sfviodata->position > sfviodata->filesize)\n\t{\n\t\tsfviodata->position = sfviodata->filesize;\n\t}\n\telse if (sfviodata->position < 0)\n\t{\n\t\tsfviodata->position = 0;\n\t}\n\treturn sfviodata->position;\n}\n\nsf_count_t SFVIO_Read(void *ptr, sf_count_t count, void *user_data)\n{\n\tint i = 0;\n\tsfviodata_t *sfviodata;\n\n\tsfviodata = user_data;\n\tfor (i = 0; i < count; ++i)\n\t{\n\t\tif (sfviodata->position == sfviodata->filesize)\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t\t((char *)ptr)[i] = sfviodata->data[sfviodata->position];\n\t\tsfviodata->position++;\n\t}\n\treturn count;\n}\n\nsf_count_t SFVIO_Tell(void *user_data)\n{\n\tsfviodata_t *sfviodata;\n\n\tsfviodata = user_data;\n\treturn sfviodata->position;\n}\n\nstatic qbool S_ParseCueMark(const byte* chunk, int len, int cue_point_id, int* sample_length)\n{\n\tint pos = 0;\n\tunsigned int size = 1;\n\n\t*sample_length = 0;\n\twhile ((pos < (len - 8)) && (size != 0)) {\n\t\tsize = BuffLittleLong(chunk + pos + 4);\n\n\t\t// Looking for ltxt chunk with purpose \"mark\"\n\t\tif (size >= 20 && !strncmp(chunk + pos, \"ltxt\", 4) && !strncmp(chunk + pos + 16, \"mark\", 4)) {\n\t\t\t// Might be for a different cue point\n\t\t\tif (cue_point_id == BuffLittleLong(chunk + pos + 8)) {\n\t\t\t\t*sample_length = BuffLittleLong(chunk + pos + 12);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tpos += size;\n\t}\n\n\treturn false;\n}\n\nstatic qbool S_FindCuePointSampleLength(SNDFILE* sndfile, int cue_point_id, int* sample_length)\n{\n\tSF_CHUNK_INFO chunk_info;\n\tchunk_info.datalen = 0;\n\tstrlcpy(chunk_info.id, \"LIST\", sizeof(chunk_info.id));\n\tchunk_info.id_size = 4;\n\tSF_CHUNK_ITERATOR* iterator = sf_get_chunk_iterator(sndfile, &chunk_info);\n\tbyte chunk_data[1024];\n\n\t*sample_length = 0;\n\n\twhile (iterator != NULL) {\n\t\tif (sf_get_chunk_size(iterator, &chunk_info) != SF_ERR_NO_ERROR) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (chunk_info.datalen >= 24 && chunk_info.datalen <= sizeof(chunk_data)) {\n\t\t\tchunk_info.data = chunk_data;\n\n\t\t\tif (sf_get_chunk_data(iterator, &chunk_info) != SF_ERR_NO_ERROR) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\telse if (chunk_data[0] == 'a' && chunk_data[1] == 'd' && chunk_data[2] == 't' && chunk_data[3] == 'l') {\n\t\t\t\tif (S_ParseCueMark(chunk_data + 4, chunk_info.datalen - 4, cue_point_id, sample_length)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\titerator = sf_next_chunk_iterator(iterator);\n\t}\n\n\treturn false;\n}\n\nsfxcache_t *S_LoadSound (sfx_t *s)\n{\n\tchar namebuffer[256];\n\tunsigned char *data;\n\tint filesize;\n\tSF_VIRTUAL_IO sfvio;\n\tSF_INFO sfinfo;\n\tsfviodata_t sfviodata;\n\tshort *buf; \n\tuint32_t cue_count;\n\tint loopstart;\n\tSF_CUES sfcues;\n\tSNDFILE *sndfile;\n\n\t// see if allocated\n\tif (s->buf)\n\t\treturn s->buf;\n\n\t// load it in\n\tsnprintf(namebuffer, sizeof(namebuffer), \"sound/%s\", s->name);\n\n\tif (!(data = FS_LoadTempFile(namebuffer, &filesize))) {\n\t\tCom_Printf (\"Couldn't load %s\\n\", namebuffer);\n\t\treturn NULL;\n\t}\n\n\tFMod_CheckModel(namebuffer, data, filesize);\n\n\tsfvio.get_filelen = SFVIO_GetFilelen;\n\tsfvio.seek = SFVIO_Seek;\n\tsfvio.read = SFVIO_Read;\n\tsfvio.tell = SFVIO_Tell;\n\n\tsfinfo.format = 0;\n\n\tsfviodata.path = namebuffer;\n\tsfviodata.position = 0;\n\tsfviodata.data = data;\n\tsfviodata.filesize = filesize;\n\n\tsndfile = sf_open_virtual(&sfvio, SFM_READ, &sfinfo, &sfviodata);\n\n\tbuf = (short *)Q_malloc(sfinfo.frames * sfinfo.channels * sizeof(short));\n\tsf_readf_short(sndfile, buf, sfinfo.frames);\n\tcue_count = 0;\n\tsf_command(sndfile, SFC_GET_CUE_COUNT, &cue_count, sizeof(cue_count));\n\tloopstart = -1;\n\n\tif (cue_count != 0) {\n\t\tint loop_sample_count;\n\n\t\tsf_command(sndfile, SFC_GET_CUE, &sfcues, sizeof(sfcues)) ;\n\n\t\tif (S_FindCuePointSampleLength(sndfile, sfcues.cue_points[0].position, &loop_sample_count)) {\n\t\t\tloopstart = sfcues.cue_points[0].sample_offset;\n\t\t\tif (loopstart + loop_sample_count > sfinfo.frames) {\n\t\t\t\tSys_Error(\"Sound %s has a bad loop length\", s->name);\n\t\t\t}\n\t\t\tsfinfo.frames = loopstart + loop_sample_count;\n\t\t}\n\t}\n\n\tsf_close(sndfile);\n\n\tif (sfinfo.channels < 1 || sfinfo.channels > 2) {\n\t\tCom_Printf(\"%s has an unsupported number of channels (%i)\\n\", s->name, sfinfo.channels);\n\t\tQ_free(buf);\n\t\treturn NULL;\n\t}\n\n\tResampleSfx (s, sfinfo.samplerate, sfinfo.channels, sizeof(short), sfinfo.frames, loopstart, (byte *)buf);\n\tQ_free(buf);\n\n\treturn s->buf;\n}\n\n#else\n\n/*\n===============================================================================\nOld WAV loading\n===============================================================================\n*/\n\nstatic unsigned char *data_p;\nstatic unsigned char *iff_end;\nstatic unsigned char *last_chunk;\nstatic unsigned char *iff_data;\nstatic int iff_chunk_len;\n\nstatic short GetLittleShort(void)\n{\n\tshort val;\n\n\tval = BuffLittleShort (data_p);\n\tdata_p += 2;\n\n\treturn val;\n}\n\nstatic int GetLittleLong(void)\n{\n\tint val = 0;\n\n\tval = BuffLittleLong (data_p);\n\tdata_p += 4;\n\n\treturn val;\n}\n\nstatic void FindNextChunk(char *name)\n{\n\twhile (1) {\n\t\tdata_p=last_chunk;\n\n\t\tif (data_p >= iff_end) { // didn't find the chunk\n\t\t\tdata_p = NULL;\n\t\t\treturn;\n\t\t}\n\n\t\tdata_p += 4;\n\t\tiff_chunk_len = GetLittleLong();\n\t\tif (iff_chunk_len < 0) {\n\t\t\tdata_p = NULL;\n\t\t\treturn;\n\t\t}\n\n\t\tdata_p -= 8;\n\t\tlast_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 );\n\t\tif (!strncmp((const char *)data_p, name, 4))\n\t\t\treturn;\n\t}\n}\n\nstatic void FindChunk(char *name)\n{\n\tlast_chunk = iff_data;\n\tFindNextChunk (name);\n}\n\nstatic wavinfo_t GetWavinfo (char *name, unsigned char *wav, int wavlength)\n{\n\tint samples, format, i;\n\twavinfo_t info;\n\n\tmemset (&info, 0, sizeof(info));\n\n\tif (!wav)\n\t\treturn info;\n\n\tiff_data = wav;\n\tiff_end = wav + wavlength;\n\n\t// find \"RIFF\" chunk\n\tFindChunk(\"RIFF\");\n\tif (!(data_p && !strncmp((const char *)(data_p+8), \"WAVE\", 4))) {\n\t\tCom_Printf (\"Missing RIFF/WAVE chunks\\n\");\n\t\treturn info;\n\t}\n\n\t// get \"fmt \" chunk\n\tiff_data = data_p + 12;\n\tFindChunk(\"fmt \");\n\tif (!data_p) {\n\t\tCom_Printf (\"Missing fmt chunk\\n\");\n\t\treturn info;\n\t}\n\n\tdata_p += 8;\n\tformat = GetLittleShort();\n\tif (format != 1) {\n\t\tCom_Printf (\"Microsoft PCM format only\\n\");\n\t\treturn info;\n\t}\n\n\tinfo.channels = GetLittleShort();\n\tinfo.rate = GetLittleLong();\n\tdata_p += 4 + 2;\n\tinfo.width = GetLittleShort() / 8;\n\n\t// get cue chunk\n\tFindChunk(\"cue \");\n\tif (data_p) {\n\t\tdata_p += 32;\n\t\tinfo.loopstart = GetLittleLong();\n\n\t\t// if the next chunk is a LIST chunk, look for a cue length marker\n\t\tFindNextChunk (\"LIST\");\n\t\tif (data_p) {\n\t\t\t// this is not a proper parse, but it works with cooledit...\n\t\t\tif (!strncmp ((const char *)(data_p + 28), \"mark\", 4)) {\n\t\t\t\tdata_p += 24;\n\t\t\t\ti = GetLittleLong (); // samples in loop\n\t\t\t\tinfo.samples = info.loopstart + i;\n\t\t\t}\n\t\t}\n\t} else\n\t\tinfo.loopstart = -1;\n\n\t// find data chunk\n\tFindChunk(\"data\");\n\tif (!data_p) {\n\t\tCom_Printf (\"Missing data chunk\\n\");\n\t\treturn info;\n\t}\n\n\tdata_p += 4;\n\tsamples = GetLittleLong () / info.width / info.channels;\n\n\tif (info.samples) {\n\t\tif (samples < info.samples)\n\t\t\tSys_Error (\"Sound %s has a bad loop length\", name);\n\t} else\n\t\tinfo.samples = samples;\n\n\tinfo.dataofs = data_p - wav;\n\n\treturn info;\n}\n\n\n//=============================================================================\n\nstatic void COM_SwapLittleShortBlock (short *s, int size)\n{\n//FIXME: qqshka: I have no idea that we have to do for PDP endian.\n\n#if defined __BIG_ENDIAN__\n//\tif (!bigendian)\n//\t\treturn;\n\n\tif (size <= 0)\n\t\treturn;\n\n\twhile (size)\n\t{\n\t\t*s = ShortSwap(*s);\n\t\ts++;\n\t\tsize--;\n\t}\n#endif\n}\n\nstatic void COM_CharBias (signed char *c, int size)\n{\n\tif (size <= 0)\n\t\treturn;\n\n\twhile (size)\n\t{\n\t\t*c = (*(unsigned char *)c) - 128;\n\t\tc++;\n\t\tsize--;\n\t}\n}\n\nsfxcache_t *S_LoadSound (sfx_t *s)\n{\n\tchar namebuffer[256];\n\tunsigned char *data;\n\twavinfo_t info;\n\tint filesize;\n\n\t// see if allocated\n\tif (s->buf)\n\t\treturn (sfxcache_t*)s->buf;\n\n\t// load it in\n\tsnprintf(namebuffer, sizeof(namebuffer), \"sound/%s\", s->name);\n\n\tif (!(data = FS_LoadTempFile(namebuffer, &filesize))) {\n\t\tCom_Printf (\"Couldn't load %s\\n\", namebuffer);\n\t\treturn NULL;\n\t}\n\n\tFMod_CheckModel(namebuffer, data, filesize);\n\n\tinfo = GetWavinfo (s->name, data, filesize);\n\n\t// Stereo sounds are allowed (intended for music)\n\tif (info.channels < 1 || info.channels > 2) {\n\t\tCom_Printf(\"%s has an unsupported number of channels (%i)\\n\",s->name, info.channels);\n\t\treturn NULL;\n\t}\n\n\tif (info.dataofs + info.samples * info.channels > filesize) {\n\t\tCom_Printf(\"%s is corrupt/truncated, delete and re-download\\n\", s->name);\n\t\treturn NULL;\n\t}\n\n\tif (info.width == 1)\n\t\tCOM_CharBias((signed char*)data + info.dataofs, info.samples * info.channels);\n\telse if (info.width == 2)\n\t\tCOM_SwapLittleShortBlock((short *)(data + info.dataofs), info.samples * info.channels);\n\n\tResampleSfx (s, info.rate, info.channels, info.width, info.samples, info.loopstart, data + info.dataofs);\n\n\treturn s->buf;\n}\n\nint SND_Rate(int rate)\n{\n\tswitch (rate)\n\t{\n\t\tcase 48:\n\t\t\treturn 48000;\n\t\tcase 44:\n\t\t\treturn 44100;\n\t\tcase 32:\n\t\t\treturn 32000;\n\t\tcase 24:\n\t\t\treturn 24000;\n\t\tcase 22:\n\t\t\treturn 22050;\n\t\tcase 16:\n\t\t\treturn 16000;\n\t\tcase 12:\n\t\t\treturn 12000;\n\t\tcase 8:\n\t\t\treturn 8000;\n\t\tdefault:\n\t\t\treturn 11025;\n\t}\n}\n\n#endif\n"
  },
  {
    "path": "src/snd_mix.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: snd_mix.c,v 1.15 2007-03-11 06:01:42 disconn3ct Exp $\n\n*/\n// snd_mix.c -- portable code to mix sounds for snd_dma.c\n\n#include \"quakedef.h\"\n#include \"qsound.h\"\n#include \"movie.h\" // /demo_capture\n\n\n#define PAINTBUFFER_SIZE 512\ntypedef struct portable_samplepair_s {\n\tint left;\n\tint right;\n} portable_samplepair_t;\nportable_samplepair_t paintbuffer[PAINTBUFFER_SIZE];\nstatic portable_samplepair_t voice_paintbuffer[PAINTBUFFER_SIZE];\nstatic portable_samplepair_t *painttarget = paintbuffer;\nint snd_scaletable[32][256];\nint snd_vol, *snd_p;\nstatic int *voice_snd_p;\n\nstatic int snd_linear_count;\nstatic short *snd_out;\n\nstatic void Snd_WriteLinearBlastStereo16 (int* input_buffer, int* voice_input_buffer, short* output_buffer, int snd_vol, int voice_vol)\n{\n\tint val, i;\n\n\tfor (i = 0; i < snd_linear_count; i += 2) {\n\t\tval = ((input_buffer[i] * snd_vol) + (voice_input_buffer[i] * voice_vol)) >> 8;\n\t\toutput_buffer[i] = bound (-32768, val, 32767);\n\t\tval = ((input_buffer[i + 1] * snd_vol) + (voice_input_buffer[i + 1] * voice_vol)) >> 8;\n\t\toutput_buffer[i+1] = bound (-32768, val, 32767);\n\t}\n}\n\nstatic void Snd_WriteLinearBlastStereo16_SwapStereo (int* input_buffer, int* voice_input_buffer, short* output_buffer, int snd_vol, int voice_vol)\n{\n\tint val, i;\n\n\tfor (i = 0; i < snd_linear_count; i +=2 ) {\n\t\tval = ((input_buffer[i + 1] * snd_vol) + (voice_input_buffer[i + 1] * voice_vol)) >> 8;\n\t\toutput_buffer[i] = bound (-32768, val, 32767);\n\t\tval = ((input_buffer[i] * snd_vol) + (voice_input_buffer[i] * voice_vol)) >> 8;\n\t\toutput_buffer[i+1] = bound (-32768, val, 32767);\n\t}\n}\n\nstatic void S_TransferStereo16 (int endtime)\n{\n\tint lpaintedtime, lpos, clientVolume;\n\tint voiceVolume;\n\tDWORD *pbuf;\n\n\tclientVolume = snd_vol = (s_volume.value * S_VoipVoiceTransmitVolume()) * 256;\n\tvoiceVolume = max(0, s_raw_volume.value * 256);\n\n\tsnd_p = (int *) paintbuffer;\n\tvoice_snd_p = (int *) voice_paintbuffer;\n\tlpaintedtime = shw->paintedtime;\n\n\tpbuf = (DWORD *)shw->buffer;\n\twhile (lpaintedtime < endtime) {\n\n\t\t// handle recirculating buffer issues\n\t\tlpos = lpaintedtime % ((shw->samples>>1));\n\t\tsnd_out = (short *) pbuf + (lpos << 1);\n\n\t\tsnd_linear_count = (shw->samples>>1) - lpos;\n\t\tif (lpaintedtime + snd_linear_count > endtime)\n\t\t\tsnd_linear_count = endtime - lpaintedtime;\n\n\t\tsnd_linear_count <<= 1;\n\n\t\t// write a linear blast of samples\n\t\tif (s_swapstereo.value)\n\t\t\tSnd_WriteLinearBlastStereo16_SwapStereo (snd_p, voice_snd_p, snd_out, clientVolume, voiceVolume);\n\t\telse\n\t\t\tSnd_WriteLinearBlastStereo16 (snd_p, voice_snd_p, snd_out, clientVolume, voiceVolume);\n\n\t\tif (Movie_IsCapturing()) {\n\t\t\tMovie_TransferSound (snd_out, snd_linear_count);\n\t\t}\n\n\t\tsnd_p += snd_linear_count;\n\t\tvoice_snd_p += snd_linear_count;\n\t\tlpaintedtime += (snd_linear_count>>1);\n\t}\n}\n\nstatic void S_TransferPaintBuffer(int endtime)\n{\n\tDWORD *pbuf;\n\tint *p;\n\tint out_idx;\n\tint out_mask;\n\tint count;\n\tint step;\n\tint val;\n\tint snd_vol;\n\tint voice_vol;\n\tint *voice_p;\n\n\tif (shw->samplebits == 16 && shw->numchannels == 2) {\n\t\tS_TransferStereo16(endtime);\n\t\treturn;\n\t}\n\n\tp = (int *) paintbuffer;\n\tvoice_p = (int *) voice_paintbuffer;\n\tcount = (endtime - shw->paintedtime) * shw->numchannels;\n\tout_mask = shw->samples - 1;\n\tout_idx = shw->paintedtime * shw->numchannels & out_mask;\n\tstep = 3 - shw->numchannels;\n\tsnd_vol = (s_volume.value * S_VoipVoiceTransmitVolume()) * 256;\n\tvoice_vol = max(0, s_raw_volume.value * 256);\n\n\tpbuf = (DWORD *)shw->buffer;\n\n\tif (shw->samplebits == 16) {\n\t\tshort *out = (short *) pbuf;\n\t\twhile (count--) {\n\t\t\tval = ((*p * snd_vol) + (*voice_p * voice_vol)) >> 8;\n\t\t\tp+= step;\n\t\t\tvoice_p += step;\n\t\t\tif (val > 0x7fff)\n\t\t\t\tval = 0x7fff;\n\t\t\telse if (val < (int)0x8000)\n\t\t\t\tval = (int)0x8000;\n\t\t\tout[out_idx] = val;\n\t\t\tout_idx = (out_idx + 1) & out_mask;\n\t\t}\n\t} else if (shw->samplebits == 8) {\n\t\tunsigned char *out = (unsigned char *) pbuf;\n\t\twhile (count--) {\n\t\t\tval = ((*p * snd_vol) + (*voice_p * voice_vol)) >> 8;\n\t\t\tp+= step;\n\t\t\tvoice_p += step;\n\t\t\tif (val > 0x7fff)\n\t\t\t\tval = 0x7fff;\n\t\t\telse if (val < (int)0x8000)\n\t\t\t\tval = (int)0x8000;\n\t\t\tout[out_idx] = (val>>8) + 128;\n\t\t\tout_idx = (out_idx + 1) & out_mask;\n\t\t}\n\t}\n}\n\n\n/*\n===============================================================================\nCHANNEL MIXING\n===============================================================================\n*/\n\nstatic void SND_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int count)\n{\n\tint data, i;\n\tint *lscale, *rscale;\n\tunsigned char *sfx;\n\n\tif (ch->leftvol > 255)\n\t\tch->leftvol = 255;\n\tif (ch->rightvol > 255)\n\t\tch->rightvol = 255;\n\n\tlscale = snd_scaletable[ch->leftvol >> 3];\n\trscale = snd_scaletable[ch->rightvol >> 3];\n\tsfx = (unsigned char *) ((signed char *)sc->data + ch->pos);\n\n\tfor (i = 0; i < count ;i++) {\n\t\tdata = sfx[i];\n\t\tpainttarget[i].left += lscale[data];\n\t\tpainttarget[i].right += rscale[data];\n\t}\n\n\tch->pos += count;\n}\n\nstatic void SND_PaintChannelFrom16 (channel_t *ch, sfxcache_t *sc, int count)\n{\n\tint data, left, right, leftvol, rightvol, i;\n\tsigned short *sfx;\n\n\tleftvol = ch->leftvol;\n\trightvol = ch->rightvol;\n\tsfx = (signed short *)sc->data + ch->pos;\n\n\tfor (i = 0; i < count ;i++) {\n\t\tdata = sfx[i];\n\t\tleft = (data * leftvol) >> 8;\n\t\tright = (data * rightvol) >> 8;\n\t\tpainttarget[i].left += left;\n\t\tpainttarget[i].right += right;\n\t}\n\n\tch->pos += count;\n}\n\nvoid SND_InitScaletable (void)\n{\n\tint i, j;\n\n\tfor (i = 0 ; i < 32; i++)\n\t\tfor (j = 0; j < 256; j++)\n\t\t\tsnd_scaletable[i][j] = ((j < 128) ? j : j - 0xff) * i * 8;\n}\n\nvoid S_PaintChannels(int endtime)\n{\n\tint ltime, count, end;\n\tunsigned int i;\n\tsfxcache_t *sc;\n\tchannel_t *ch;\n\textern cvar_t s_silent_racing;\n\n\twhile (shw->paintedtime < endtime) {\n\t\t// if paintbuffer is smaller than DMA buffer\n\t\tend = endtime;\n\t\tif (endtime - shw->paintedtime > PAINTBUFFER_SIZE)\n\t\t\tend = shw->paintedtime + PAINTBUFFER_SIZE;\n\n\t\t// clear the paint buffer\n\t\tmemset (paintbuffer, 0, (end - shw->paintedtime) * sizeof(portable_samplepair_t));\n\t\tmemset (voice_paintbuffer, 0, (end - shw->paintedtime) * sizeof(portable_samplepair_t));\n\n\t\t// paint in the channels.\n\t\tch = channels;\n\t\tfor (i = 0; i < total_channels; i++, ch++) {\n\t\t\tif (!ch->sfx)\n\t\t\t\tcontinue;\n\t\t\tif (!ch->leftvol && !ch->rightvol)\n\t\t\t\tcontinue;\n\t\t\tif (cl.racing && s_silent_racing.integer && ch->entnum > 0 && ch->entnum <= MAX_CLIENTS) {\n\t\t\t\tif (cl.spectator) {\n\t\t\t\t\tif (Cam_TrackNum() < 0) {\n\t\t\t\t\t\t// silence all racers\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\telse if (ch->entnum - 1 != Cam_TrackNum()) {\n\t\t\t\t\t\t// not the tracked player\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (ch->entnum - 1 != cl.playernum) {\n\t\t\t\t\t// a different player\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tsc = S_LoadSound (ch->sfx);\n\t\t\tif (!sc)\n\t\t\t\tcontinue;\n\n\t\t\tpainttarget = (ch->flags & CHANNEL_FLAG_VOICE) ? voice_paintbuffer : paintbuffer;\n\t\t\tltime = shw->paintedtime;\n\n\t\t\twhile (ltime < end) { // paint up to end\n\t\t\t\tcount = (ch->end < end) ? (ch->end - ltime) : (end - ltime);\n\n\t\t\t\tif (count > 0) {\n\t\t\t\t\tif (sc->format.width == 1)\n\t\t\t\t\t\tSND_PaintChannelFrom8(ch, sc, count);\n\t\t\t\t\telse\n\t\t\t\t\t\tSND_PaintChannelFrom16(ch, sc, count);\n\n\t\t\t\t\tltime += count;\n\t\t\t\t}\n\n\t\t\t\t// if at end of loop, restart\n\t\t\t\tif (ltime >= ch->end) {\n\t\t\t\t\tif (sc->loopstart >= 0) {\n\t\t\t\t\t\tch->pos = bound(0, sc->loopstart, (int) sc->total_length - 1);\n\t\t\t\t\t\tch->end = ltime + (int) sc->total_length - ch->pos;\n\t\t\t\t\t} else { // channel just stopped\n\t\t\t\t\t\tch->sfx = NULL;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tpainttarget = paintbuffer;\n\n\t\t// transfer out according to DMA format\n\t\tS_TransferPaintBuffer(end);\n\t\tshw->paintedtime = end;\n\t}\n}\n"
  },
  {
    "path": "src/snd_qizmo.c",
    "content": "/*\n * Copyright (C) 2026 Oscar Linderholm <osm@recv.se>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n */\n#include \"quakedef.h\"\n#include \"qsound.h\"\n#include \"sndfile.h\"\n\n#define QIZMO_VOICE_GSM_BYTES\t33\n#define QIZMO_VOICE_RATE\t8000\n#define QIZMO_VOICE_PCM_SAMPLES\t160\n\n// Keep a bounded GSM segment so libsndfile decodes with prior GSM state. Each\n// frame is 20 ms, at the cap, playback continues from a fresh segment.\n#define QIZMO_VOICE_MAX_FRAMES\t1024\n\n// libsndfile decodes from file-like objects, so expose the accumulated GSM\n// bytes as a read-only in-memory file.\ntypedef struct qizmo_voice_sfvio_s {\n\tconst byte *data;\n\tsf_count_t size;\n\tsf_count_t position;\n} qizmo_voice_sfvio_t;\n\nstatic sf_count_t S_QizmoVoice_GetFileLen(void *user_data)\n{\n\tqizmo_voice_sfvio_t *vio = user_data;\n\treturn vio->size;\n}\n\nstatic sf_count_t S_QizmoVoice_Seek(sf_count_t offset, int whence, void *user_data)\n{\n\tqizmo_voice_sfvio_t *vio = user_data;\n\n\tif (whence == SEEK_CUR) {\n\t\tvio->position += offset;\n\t}\n\telse if (whence == SEEK_SET) {\n\t\tvio->position = offset;\n\t}\n\telse if (whence == SEEK_END) {\n\t\tvio->position = vio->size + offset;\n\t}\n\n\tif (vio->position < 0) {\n\t\tvio->position = 0;\n\t}\n\telse if (vio->position > vio->size) {\n\t\tvio->position = vio->size;\n\t}\n\n\treturn vio->position;\n}\n\nstatic sf_count_t S_QizmoVoice_Read(void *ptr, sf_count_t count, void *user_data)\n{\n\tqizmo_voice_sfvio_t *vio = user_data;\n\tsf_count_t available = vio->size - vio->position;\n\n\tif (count > available) {\n\t\tcount = available;\n\t}\n\n\tmemcpy(ptr, vio->data + vio->position, count);\n\tvio->position += count;\n\treturn count;\n}\n\nstatic sf_count_t S_QizmoVoice_Tell(void *user_data)\n{\n\tqizmo_voice_sfvio_t *vio = user_data;\n\treturn vio->position;\n}\n\n// Decode the segment from the beginning so the GSM decoder sees the prior\n// frame history it needs. The caller submits only the newest decoded frame.\nstatic sf_count_t S_QizmoVoice_DecodeSegment(const byte *gsm_segment,\n\tint segment_frames, short *decoded_pcm)\n{\n\tstatic SF_VIRTUAL_IO sfvio = {\n\t\tS_QizmoVoice_GetFileLen,\n\t\tS_QizmoVoice_Seek,\n\t\tS_QizmoVoice_Read,\n\t\tNULL,\n\t\tS_QizmoVoice_Tell\n\t};\n\tSF_INFO sfinfo;\n\tqizmo_voice_sfvio_t sfviodata;\n\tSNDFILE *sndfile;\n\tsf_count_t decoded_samples;\n\n\tmemset(&sfinfo, 0, sizeof(sfinfo));\n\tsfinfo.samplerate = QIZMO_VOICE_RATE;\n\tsfinfo.channels = 1;\n\tsfinfo.format = SF_FORMAT_RAW | SF_FORMAT_GSM610;\n\n\tsfviodata.data = gsm_segment;\n\tsfviodata.size = segment_frames * QIZMO_VOICE_GSM_BYTES;\n\tsfviodata.position = 0;\n\n\tsndfile = sf_open_virtual(&sfvio, SFM_READ, &sfinfo, &sfviodata);\n\tif (!sndfile) {\n\t\tCom_DPrintf(\"Qizmo voice: could not open GSM frame: %s\\n\", sf_strerror(NULL));\n\t\treturn 0;\n\t}\n\n\tdecoded_samples = sf_readf_short(sndfile, decoded_pcm,\n\t\tsegment_frames * QIZMO_VOICE_PCM_SAMPLES);\n\tsf_close(sndfile);\n\treturn decoded_samples;\n}\n\nvoid S_QizmoVoice_PlayFrame(int sequence, int voice_id, const byte *frame_data, int bytes)\n{\n\tstatic byte gsm_segment[QIZMO_VOICE_MAX_FRAMES * QIZMO_VOICE_GSM_BYTES];\n\tstatic short decoded_pcm[QIZMO_VOICE_MAX_FRAMES * QIZMO_VOICE_PCM_SAMPLES];\n\tstatic int last_sequence;\n\tstatic int last_voice_id;\n\tstatic int segment_frames;\n\tstatic qbool have_sequence;\n\tsf_count_t decoded_samples;\n\tshort *latest_pcm_frame;\n\tbyte *gsm_frame;\n\n\tif (!snd_initialized || !snd_started) {\n\t\treturn;\n\t}\n\n\tif (bytes != QIZMO_VOICE_GSM_BYTES) {\n\t\tCom_DPrintf(\"Qizmo voice: unexpected GSM frame size %d\\n\", bytes);\n\t\treturn;\n\t}\n\n\t// The Qizmo sequence groups packets into one voice transmission. A new\n\t// voice id, sequence, gap, or the bounded history limit starts a fresh GSM\n\t// decoder segment.\n\tif (sequence == 0 || !have_sequence ||\n\t\tlast_voice_id != voice_id ||\n\t\t((last_sequence + 1) & 0x3ff) != sequence ||\n\t\tsegment_frames >= QIZMO_VOICE_MAX_FRAMES) {\n\t\tsegment_frames = 0;\n\t}\n\n\tgsm_frame = gsm_segment + segment_frames * QIZMO_VOICE_GSM_BYTES;\n\tmemcpy(gsm_frame, frame_data, QIZMO_VOICE_GSM_BYTES);\n\n\t// Qizmo stores GSM frames with a different high nibble than standard raw\n\t// GSM 06.10. Preserve the low bits and normalize the magic nibble.\n\tgsm_frame[0] = (gsm_frame[0] & 0x0f) | 0xd0;\n\n\tsegment_frames++;\n\tlast_sequence = sequence;\n\tlast_voice_id = voice_id;\n\thave_sequence = true;\n\n\tdecoded_samples = S_QizmoVoice_DecodeSegment(gsm_segment, segment_frames, decoded_pcm);\n\tif (decoded_samples < QIZMO_VOICE_PCM_SAMPLES) {\n\t\tCom_DPrintf(\"Qizmo voice: decoded only %d PCM samples\\n\", (int)decoded_samples);\n\t\treturn;\n\t}\n\n\tlatest_pcm_frame = decoded_pcm + decoded_samples - QIZMO_VOICE_PCM_SAMPLES;\n\n\tS_RawAudio(RAW_SOURCE_QIZMO_VOICE,\n\t\t(byte *)latest_pcm_frame,\n\t\tQIZMO_VOICE_RATE,\n\t\tQIZMO_VOICE_PCM_SAMPLES,\n\t\t1,\n\t\tsizeof(short));\n}\n"
  },
  {
    "path": "src/snd_voip.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// snd_voip.c -- voice over ip (mm3)\n// Original code taken from ezquake 2.2, which used speex\n// Now using SDL audio capture (requires SDL 2.0.5 or above)\n\n#include <SDL.h>\n#include \"quakedef.h\"\n#include \"qsound.h\"\n\n#ifdef FTE_PEXT2_VOICECHAT\n\n#include <speex/speex.h>\n#include <speex/speex_preprocess.h>\n\n// FTEQW type and naming compatibility\n// it's not really necessary, simple find & replace would do the job too\n#ifdef _MSC_VER\n#define VARGS __cdecl\n#endif\n#ifndef VARGS\n#define VARGS\n#endif\n\nstatic void S_Voip_Play_Callback (cvar_t *var, char *string, qbool *cancel);\nvoid CL_SendClientCommand (qbool reliable, char *format, ...);\n\nstatic cvar_t s_inputdevice = { \"s_inputdevice\", \"0\" };                                // SDL device to use as microphone\nstatic cvar_t cl_voip_send = { \"cl_voip_send\", \"0\" };                                  // Sends voice-over-ip data to the server whenever it is set.\nstatic cvar_t cl_voip_vad_threshhold = { \"cl_voip_vad_threshhold\", \"15\" };             // This is the threshhold for voice-activation-detection when sending voip data.\nstatic cvar_t cl_voip_vad_delay = { \"cl_voip_vad_delay\", \"0.3\" };                      // Keeps sending voice data for this many seconds after voice activation would normally stop.\nstatic cvar_t cl_voip_capturingvol = { \"cl_voip_capturingvol\", \"0.5\" };                // Multiplier applied while capturing, to avoid your audio from being heard by others.\nstatic cvar_t cl_voip_play = { \"cl_voip_play\", \"1\", CVAR_NONE, S_Voip_Play_Callback }; // Enables voip playback from other clients connected to server.\nstatic cvar_t cl_voip_micamp = { \"cl_voip_micamp\", \"2\" };                              // Amplifies your microphone when using voip.\nstatic cvar_t cl_voip_demorecord = { \"cl_voip_demorecord\", \"1\" };                      // Record VOIP in demo.\nstatic cvar_t cl_voip_showmeter = { \"cl_voip_showmeter\", \"1\" };                        // Show speech volume above the hud.  0 = hide, 1=when transmitting, 2=even when voice-activation not triggered\nstatic cvar_t cl_voip_showmeter_x = { \"cl_voip_showmeter_x\", \"0\" };                    // horizontal coordinate\nstatic cvar_t cl_voip_showmeter_y = { \"cl_voip_showmeter_y\", \"0\" };                    // vertical coordinate\n\nstatic float voicevolumemod = 1; // voice volume modifier.\n\nfloat S_VoipVoiceTransmitVolume(void)\n{\n\treturn voicevolumemod;\n}\n\nstatic struct\n{\n\tqbool inited;\n\tqbool loaded;\n\n\tSpeexBits encbits;\n\tvoid *encoder;\n\tSpeexPreprocessState *preproc;\n\tunsigned int framesize;\n\tunsigned int samplerate;\n\n\tSpeexBits decbits[MAX_CLIENTS];\n\tvoid *decoder[MAX_CLIENTS];\n\tunsigned char decseq[MAX_CLIENTS];\t/*sender's sequence, to detect+cover minor packetloss*/\n\tunsigned char decgen[MAX_CLIENTS];\t/*last generation. if it changes, we flush speex to reset packet loss*/\n\tfloat decamp[MAX_CLIENTS];\t/*amplify them by this*/\n\tfloat lastspoke[MAX_CLIENTS];\t/*time when they're no longer considered talking. if future, they're talking*/\n\n\tunsigned char capturebuf[32768]; /*pending data*/\n\tunsigned int capturepos;/*amount of pending data*/\n\tunsigned int encsequence;/*the outgoing sequence count*/\n\tunsigned int generation;/*incremented whenever capture is restarted*/\n\tqbool wantsend;\t/*set if we're capturing data to send*/\n\tfloat voiplevel;\t/*your own voice level*/\n\tunsigned int dumps;\t/*trigger a new generation thing after a bit*/\n\tunsigned int keeps;\t/*for vad_delay*/\n\n\tvoid *driverctx;\t/*capture driver context*/\n} s_speex;\n\nstatic qbool S_Speex_Init (void)\n{\n\tint i;\n\tconst SpeexMode *mode;\n\tif (s_speex.inited)\n\t\treturn s_speex.loaded;\n\ts_speex.inited = true;\n\n\tmode = speex_lib_get_mode (SPEEX_MODEID_NB);\n\tspeex_bits_init (&s_speex.encbits);\n\tspeex_bits_reset (&s_speex.encbits);\n\n\ts_speex.encoder = speex_encoder_init (mode);\n\n\tspeex_encoder_ctl (s_speex.encoder, SPEEX_GET_FRAME_SIZE, &s_speex.framesize);\n\tspeex_encoder_ctl (s_speex.encoder, SPEEX_GET_SAMPLING_RATE, &s_speex.samplerate);\n\ts_speex.samplerate = 11025;\n\tspeex_encoder_ctl (s_speex.encoder, SPEEX_SET_SAMPLING_RATE, &s_speex.samplerate);\n\n\ts_speex.preproc = speex_preprocess_state_init (s_speex.framesize, s_speex.samplerate);\n\n\ti = 1;\n\tspeex_preprocess_ctl (s_speex.preproc, SPEEX_PREPROCESS_SET_DENOISE, &i);\n\n\ti = 1;\n\tspeex_preprocess_ctl (s_speex.preproc, SPEEX_PREPROCESS_SET_AGC, &i);\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tspeex_bits_init (&s_speex.decbits[i]);\n\t\tspeex_bits_reset (&s_speex.decbits[i]);\n\t\ts_speex.decoder[i] = speex_decoder_init (mode);\n\t\ts_speex.decamp[i] = 1;\n\t}\n\ts_speex.loaded = true;\n\treturn s_speex.loaded;\n}\n\nstatic int S_CaptureDriverInit (int sampleRate)\n{\n\tSDL_AudioDeviceID inputdevid = 0;\n\tSDL_AudioSpec desired, obtained;\n\tint ret = 0;\n\tconst char *requested_device = NULL;\n\n\tif (SDL_WasInit (SDL_INIT_AUDIO) == 0)\n\t\tret = SDL_InitSubSystem (SDL_INIT_AUDIO);\n\n\tif (ret == -1) {\n\t\tCon_Printf (\"Couldn't initialize SDL audio: %s\\n\", SDL_GetError ());\n\t\treturn false;\n\t}\n\n\tmemset (&desired, 0, sizeof (desired));\n\tdesired.freq = sampleRate;\n\tdesired.samples = 64;\n\tdesired.format = AUDIO_S16LSB;\n\tdesired.channels = 1;\n\n\t/* Make audiodevice list start from index 1 so that 0 can be system default */\n\tif (s_inputdevice.integer > 0) {\n\t\trequested_device = SDL_GetAudioDeviceName (s_inputdevice.integer - 1, 0);\n\t}\n\n\tif ((inputdevid = SDL_OpenAudioDevice (requested_device, 1, &desired, &obtained, 0)) <= 0) {\n\t\tCom_Printf (\"sound: couldn't open SDL audio: %s\\n\", SDL_GetError ());\n\t\tif (requested_device != NULL) {\n\t\t\tCom_Printf (\"sound: retrying with default audio device\\n\");\n\t\t\tif ((inputdevid = SDL_OpenAudioDevice (NULL, 1, &desired, &obtained, 0)) <= 0) {\n\t\t\t\tCom_Printf (\"sound: failure again, aborting...\\n\");\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tCvar_LatchedSet (&s_inputdevice, \"0\");\n\t\t}\n\t}\n\n\tif (obtained.format != AUDIO_S16LSB) {\n\t\tCom_Printf (\"SDL audio format %d unsupported.\\n\", obtained.format);\n\t\tSDL_CloseAudioDevice (inputdevid);\n\t\tinputdevid = 0;\n\t\treturn 0;\n\t}\n\n\tif (obtained.channels != 1 && obtained.channels != 2) {\n\t\tCom_Printf (\"SDL audio channels %d unsupported.\\n\", obtained.channels);\n\t\tSDL_CloseAudioDevice (inputdevid);\n\t\tinputdevid = 0;\n\t\treturn 0;\n\t}\n\n\tCom_Printf (\"Using SDL audio capture driver: %s @ %d Hz (samplerate %d)\\n\", SDL_GetCurrentAudioDriver (), obtained.freq, obtained.samples);\n\tSDL_PauseAudioDevice (inputdevid, 0);\n\n\treturn inputdevid;\n}\n\nstatic void S_CaptureDriverStart (void *ctx)\n{\n\tSDL_AudioDeviceID inputdevid = (SDL_AudioDeviceID)ctx;\n\n\tSDL_PauseAudioDevice (inputdevid, 0);\n}\n\nstatic void S_CaptureDriverStop (void *ctx)\n{\n\tSDL_AudioDeviceID inputdevid = (SDL_AudioDeviceID)ctx;\n\n\tSDL_PauseAudioDevice (inputdevid, 1);\n}\n\nstatic void S_CaptureDriverShutdown (void *ctx)\n{\n\tSDL_AudioDeviceID inputdevid = (SDL_AudioDeviceID)ctx;\n\n\tif (inputdevid) {\n\t\tSDL_CloseAudioDevice (inputdevid);\n\t}\n}\n\nstatic unsigned int S_CaptureDriverUpdate (void* driverContext, unsigned char* buffer, int minBytes, int maxBytes)\n{\n\tSDL_AudioDeviceID inputdevid = (SDL_AudioDeviceID)driverContext;\n\tunsigned int available = SDL_GetQueuedAudioSize (inputdevid);\n\n\tif (available > minBytes) {\n\t\treturn SDL_DequeueAudio (inputdevid, buffer, maxBytes);\n\t}\n\n\treturn 0;\n}\n\ntypedef struct voip_data_s {\n\tunsigned int sender;\n\tunsigned char gen;\n\tunsigned char seq;\n\tint bytes;\n\tunsigned char data[1024];\n} voip_data_t;\n\n// Called when data is received from server\n// TODO: store the voip data and then process in the main S_Update() call\nvoid S_Voip_Parse (void)\n{\n\tunsigned int sender;\n\tint bytes;\n\tunsigned char data[1024], *start;\n\tshort decodebuf[1024];\n\tunsigned int decodesamps, len, newseq, drops;\n\tunsigned char seq, gen;\n\tfloat amp = 1;\n\tunsigned int i;\n\n\tsender = MSG_ReadByte ();\n\tgen = MSG_ReadByte ();\n\tseq = MSG_ReadByte ();\n\tbytes = MSG_ReadShort ();\n\n\tif (bytes > sizeof (data) || !cl_voip_play.integer || !S_Speex_Init () || (gen & 0xf0)) {\n\t\tCom_DPrintf (\"skip data: %d\\n\", bytes);\n\t\tMSG_ReadSkip (bytes);\n\t\treturn;\n\t}\n\tMSG_ReadData (data, bytes);\n\n\tsender &= MAX_CLIENTS - 1;\n\n\tamp = s_speex.decamp[sender];\n\n\tdecodesamps = 0;\n\tnewseq = 0;\n\tdrops = 0;\n\tstart = data;\n\n\ts_speex.lastspoke[sender] = cls.realtime + 0.5;\n\tif (s_speex.decgen[sender] != gen) {\n\t\tspeex_bits_reset (&s_speex.decbits[sender]);\n\t\ts_speex.decgen[sender] = gen;\n\t\ts_speex.decseq[sender] = seq;\n\t}\n\n\twhile (bytes > 0) {\n\t\tif (decodesamps + s_speex.framesize > sizeof (decodebuf) / sizeof (decodebuf[0])) {\n\t\t\tS_RawAudio (sender, (byte*)decodebuf, s_speex.samplerate, decodesamps, 1, 2);\n\t\t\tdecodesamps = 0;\n\t\t}\n\n\t\tif (s_speex.decseq[sender] != seq) {\n\t\t\tspeex_decode_int (s_speex.decoder[sender], NULL, decodebuf + decodesamps);\n\t\t\ts_speex.decseq[sender]++;\n\t\t\tdrops++;\n\t\t}\n\t\telse {\n\t\t\tbytes--;\n\t\t\tlen = *start++;\n\t\t\tspeex_bits_read_from (&s_speex.decbits[sender], (char *)start, len);\n\t\t\tbytes -= len;\n\t\t\tstart += len;\n\t\t\tspeex_decode_int (s_speex.decoder[sender], &s_speex.decbits[sender], decodebuf + decodesamps);\n\t\t\tnewseq++;\n\t\t}\n\t\tif (amp != 1) {\n\t\t\tfor (i = decodesamps; i < decodesamps + s_speex.framesize; i++)\n\t\t\t\tdecodebuf[i] *= amp;\n\t\t}\n\t\tdecodesamps += s_speex.framesize;\n\t}\n\ts_speex.decseq[sender] += newseq;\n\n\tif (drops)\n\t\tCon_DPrintf (\"%i dropped audio frames\\n\", drops);\n\n\tif (decodesamps > 0) {\n\t\tS_RawAudio (sender, (byte*)decodebuf, s_speex.samplerate, decodesamps, 1, 2);\n\t}\n}\n\n// Called just prior to sending command to server\nvoid S_Voip_Transmit (unsigned char clc, sizebuf_t *buf)\n{\n\tunsigned char outbuf[1024];\n\tunsigned int outpos;//in bytes\n\tunsigned int encpos;//in bytes\n\tshort *start;\n\tunsigned char initseq;//in frames\n\tunsigned int i;\n\tunsigned int samps;\n\tfloat level, f;\n\tfloat micamp = cl_voip_micamp.value;\n\tqbool voipsendenable = (cl_voip_play.integer && (cls.fteprotocolextensions2 & FTE_PEXT2_VOICECHAT));\n\n\tif (!voipsendenable) {\n\t\tS_Capture_Shutdown();\n\t\treturn;\n\t}\n\n\tvoipsendenable = cl_voip_send.integer > 0;\n\n\tif (!s_speex.driverctx) {\n\t\ts_speex.voiplevel = -1;\n\t\t/*only init the first time capturing is requested*/\n\t\tif (!voipsendenable)\n\t\t\treturn;\n\n\t\t/*see if we can init speex...*/\n\t\tif (!S_Speex_Init ())\n\t\t\treturn;\n\n\t\ts_speex.driverctx = (void*)S_CaptureDriverInit (s_speex.samplerate);\n\t}\n\n\t/*couldn't init a driver?*/\n\tif (!s_speex.driverctx) {\n\t\treturn;\n\t}\n\n\tif (!voipsendenable && s_speex.wantsend) {\n\t\ts_speex.wantsend = false;\n\t\ts_speex.capturepos += S_CaptureDriverUpdate (s_speex.driverctx, (unsigned char*)s_speex.capturebuf + s_speex.capturepos, 1, sizeof (s_speex.capturebuf) - s_speex.capturepos);\n\t\tS_CaptureDriverStop (s_speex.driverctx);\n\t\t/*note: we still grab audio to flush everything that was captured while it was active*/\n\t}\n\telse if (voipsendenable && !s_speex.wantsend) {\n\t\ts_speex.wantsend = true;\n\t\tif (!s_speex.capturepos) {\t/*if we were actually still sending, it was probably only off for a single frame, in which case don't reset it*/\n\t\t\ts_speex.dumps = 0;\n\t\t\ts_speex.generation++;\n\t\t\ts_speex.encsequence = 0;\n\t\t\tspeex_bits_reset (&s_speex.encbits);\n\t\t}\n\t\telse {\n\t\t\ts_speex.capturepos += S_CaptureDriverUpdate (s_speex.driverctx, (unsigned char*)s_speex.capturebuf + s_speex.capturepos, 1, sizeof (s_speex.capturebuf) - s_speex.capturepos);\n\t\t}\n\t\tS_CaptureDriverStart (s_speex.driverctx);\n\n\t\tvoicevolumemod = cl_voip_capturingvol.value;\n\t}\n\n\ts_speex.capturepos += S_CaptureDriverUpdate (s_speex.driverctx, (unsigned char*)s_speex.capturebuf + s_speex.capturepos, s_speex.framesize * 2, sizeof (s_speex.capturebuf) - s_speex.capturepos);\n\tif (!s_speex.wantsend && s_speex.capturepos < s_speex.framesize * 2) {\n\t\ts_speex.voiplevel = -1;\n\t\ts_speex.capturepos = 0;\n\t\tvoicevolumemod = 1;\n\t\treturn;\n\t}\n\n\tinitseq = s_speex.encsequence;\n\tlevel = 0;\n\tsamps = 0;\n\tfor (encpos = 0, outpos = 0; s_speex.capturepos - encpos >= s_speex.framesize * 2 && sizeof (outbuf) - outpos > 64; s_speex.encsequence++) {\n\t\tstart = (short*)(s_speex.capturebuf + encpos);\n\n\t\tspeex_preprocess_run (s_speex.preproc, start);\n\n\t\tfor (i = 0; i < s_speex.framesize; i++) {\n\t\t\tf = start[i] * micamp;\n\t\t\tstart[i] = f;\n\t\t\tf = (float)abs(start[i]);\n\t\t\tlevel += f*f;\n\t\t}\n\t\tsamps += s_speex.framesize;\n\n\t\tspeex_bits_reset (&s_speex.encbits);\n\t\tspeex_encode_int (s_speex.encoder, start, &s_speex.encbits);\n\t\toutbuf[outpos] = speex_bits_write (&s_speex.encbits, (char *)outbuf + outpos + 1, sizeof (outbuf) - (outpos + 1));\n\t\toutpos += 1 + outbuf[outpos];\n\t\tencpos += s_speex.framesize * 2;\n\t}\n\n\tif (samps) {\n\t\tfloat nl = (3000 * level) / (32767.0f * 32767 * samps);\n\n\t\ts_speex.voiplevel = (s_speex.voiplevel * 7 + nl) / 8;\n\t\tif (s_speex.voiplevel < cl_voip_vad_threshhold.integer && !(cl_voip_send.integer & 2)) {\n\t\t\t/*try and dump it, it was too quiet, and they're not pressing +voip*/\n\t\t\tif (s_speex.keeps > samps) {\n\t\t\t\t/*but not instantly*/\n\t\t\t\ts_speex.keeps -= samps;\n\t\t\t}\n\t\t\telse {\n\t\t\t\toutpos = 0;\n\t\t\t\ts_speex.dumps += samps;\n\t\t\t\ts_speex.keeps = 0;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\ts_speex.keeps = s_speex.samplerate * cl_voip_vad_delay.value;\n\t\t}\n\n\t\tif (outpos) {\n\t\t\tif (s_speex.dumps > s_speex.samplerate / 4) {\n\t\t\t\ts_speex.generation++;\n\t\t\t}\n\t\t\ts_speex.dumps = 0;\n\t\t}\n\t}\n\n\tif (outpos && buf->maxsize - buf->cursize >= outpos + 4) {\n\t\tMSG_WriteByte (buf, clc);\n\t\tMSG_WriteByte (buf, (s_speex.generation & 0x0f)); /*gonna leave that nibble clear here... in this version, the client will ignore packets with those bits set. can use them for codec or something*/\n\t\tMSG_WriteByte (buf, initseq);\n\t\tMSG_WriteShort (buf, outpos);\n\t\tSZ_Write (buf, outbuf, outpos);\n\t}\n\n\t/*remove sent data*/\n\tmemmove (s_speex.capturebuf, s_speex.capturebuf + encpos, s_speex.capturepos - encpos);\n\ts_speex.capturepos -= encpos;\n}\n\n// Called when VOIP playback is toggled - pass stop/start to server\nstatic void S_Voip_Play_Callback (cvar_t *var, char *string, qbool *cancel)\n{\n\tif (cls.fteprotocolextensions2 & FTE_PEXT2_VOICECHAT) {\n\t\tif (atoi (string))\n\t\t\tCL_SendClientCommand (true, \"unmuteall\");\n\t\telse\n\t\t\tCL_SendClientCommand (true, \"muteall\");\n\t}\n}\n\n// Add to the ignore list\nvoid S_Voip_Ignore (unsigned int slot, qbool ignore)\n{\n\tif (cls.fteprotocolextensions2 & FTE_PEXT2_VOICECHAT) {\n\t\tCL_SendClientCommand (true, \"vignore %i %i\", slot, ignore);\n\t}\n}\n\n// +voip\nstatic void S_Voip_Enable_f (void)\n{\n\tCvar_SetValue (&cl_voip_send, cl_voip_send.integer | 2);\n}\n\n// -voip\nstatic void S_Voip_Disable_f (void)\n{\n\tCvar_SetValue (&cl_voip_send, cl_voip_send.integer & ~2);\n}\n\n// \nvoid S_Voip_RegisterCvars (void)\n{\n\tCvar_SetCurrentGroup (CVAR_GROUP_SOUND);\n\tCvar_Register (&cl_voip_send);\n\tCvar_Register (&cl_voip_vad_threshhold);\n\tCvar_Register (&cl_voip_vad_delay);\n\tCvar_Register (&cl_voip_capturingvol);\n\tCvar_Register (&cl_voip_play);\n\tCvar_Register (&cl_voip_micamp);\n\tCvar_Register (&cl_voip_demorecord);\n\tCvar_Register (&cl_voip_showmeter);\n\tCvar_Register (&cl_voip_showmeter_x);\n\tCvar_Register (&cl_voip_showmeter_y);\n\n\tCmd_AddCommand (\"+voip\", S_Voip_Enable_f);\n\tCmd_AddCommand (\"-voip\", S_Voip_Disable_f);\n}\n\n// Called after new serverdata received\nvoid S_Voip_MapChange (void)\n{\n\tCvar_ForceCallback (&cl_voip_play);\n}\n\nint S_Voip_Loudness(void)\n{\n\tqbool ignorevad = cl_voip_showmeter.integer == 2;\n\n\tif (s_speex.voiplevel > 100)\n\t\treturn 100;\n\tif (!s_speex.driverctx || (!ignorevad && s_speex.dumps))\n\t\treturn -1;\n\treturn s_speex.voiplevel;\n}\n\nqbool S_Voip_ShowMeter (int* x, int* y)\n{\n\t*x = cl_voip_showmeter_x.integer + 10;\n\t*y = cl_voip_showmeter_y.integer + vid.height - 8 - 10;\n\n\treturn cl_voip_showmeter.integer;\n}\n\nqbool S_Voip_Speaking(unsigned int plno)\n{\n\tif (!snd_initialized) {\n\t\treturn false;\n\t}\n\n\tif (plno == cl.playernum) {\n\t\treturn s_speex.voiplevel >= cl_voip_vad_threshhold.integer || (cl_voip_send.integer & 2);\n\t}\n\n\treturn plno < MAX_CLIENTS && s_speex.lastspoke[plno] > cls.realtime;\n}\n\nvoid S_Capture_Shutdown(void)\n{\n\tS_CaptureDriverShutdown (s_speex.driverctx);\n\tmemset(&s_speex, 0, sizeof(s_speex));\n}\n\n#endif\n"
  },
  {
    "path": "src/spritegn.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n//\n// spritegn.h: header file for sprite generation program\n//\n\n// **********************************************************\n// * This file must be identical in the spritegen directory *\n// * and in the Quake directory, because it's used to       *\n// * pass data from one to the other via .spr files.        *\n// **********************************************************\n\n//-------------------------------------------------------\n// This program generates .spr sprite package files.\n// The format of the files is as follows:\n//\n// dsprite_t file header structure\n// <repeat dsprite_t.numframes times>\n//   <if spritegroup, repeat dspritegroup_t.numframes times>\n//     dspriteframe_t frame header structure\n//     sprite bitmap\n//   <else (single sprite frame)>\n//     dspriteframe_t frame header structure\n//     sprite bitmap\n// <endrepeat>\n//-------------------------------------------------------\n\n#ifdef INCLUDELIBS\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <math.h>\n#include <string.h>\n\n#include \"cmdlib.h\"\n#include \"scriplib.h\"\n#include \"dictlib.h\"\n#include \"trilib.h\"\n#include \"lbmlib.h\"\n#include \"mathlib.h\"\n\n#endif\n\n#define SPRITE_VERSION\t\t1\n\n// must match definition in modelgen.h\n#ifndef SYNCTYPE_T\n#define SYNCTYPE_T\ntypedef enum {ST_SYNC=0, ST_RAND } synctype_t;\n#endif\n\n// TODO: shorten these?\ntypedef struct {\n\tint\t\t\tident;\n\tint\t\t\tversion;\n\tint\t\t\ttype;\n\tfloat\t\tboundingradius;\n\tint\t\t\twidth;\n\tint\t\t\theight;\n\tint\t\t\tnumframes;\n\tfloat\t\tbeamlength;\n\tsynctype_t\tsynctype;\n} dsprite_t;\n\n#define SPR_VP_PARALLEL_UPRIGHT\t\t0\n#define SPR_FACING_UPRIGHT\t\t\t1\n#define SPR_VP_PARALLEL\t\t\t\t2\n#define SPR_ORIENTED\t\t\t\t3\n#define SPR_VP_PARALLEL_ORIENTED\t4\n\ntypedef struct {\n\tint\t\t\torigin[2];\n\tint\t\t\twidth;\n\tint\t\t\theight;\n} dspriteframe_t;\n\ntypedef struct {\n\tint\t\t\tnumframes;\n} dspritegroup_t;\n\ntypedef struct {\n\tfloat\tinterval;\n} dspriteinterval_t;\n\ntypedef enum { SPR_SINGLE=0, SPR_GROUP } spriteframetype_t;\n\ntypedef struct {\n\tspriteframetype_t\ttype;\n} dspriteframetype_t;\n\n#define IDSPRITEHEADER\t(('P'<<24)+('S'<<16)+('D'<<8)+'I')\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// little-endian \"IDSP\"\n\n"
  },
  {
    "path": "src/stats_grid.c",
    "content": "/*\nCopyright (C) 2011 Cokeman\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"stats_grid.h\"\n#include \"sbar.h\"\n#include \"hud.h\"\n#include \"utils.h\"\n#include \"fonts.h\"\n\nstatic hud_t* teamholdinfo;\nstatic hud_t* teamholdbar;\n\nstats_weight_grid_t *stats_grid = NULL;\nstats_entities_t *stats_important_ents = NULL;\n\nvoid StatsGrid_Remove(stats_weight_grid_t **grid)\n{\n\tint row;\n\n\t// Nothing to remove.\n\tif((*grid) == NULL)\n\t{\n\t\treturn;\n\t}\n\n\t// Free all the rows.\n\tif((*grid)->cells != NULL)\n\t{\n\t\tfor(row = 0; row < (*grid)->row_count; row++)\n\t\t{\n\t\t\t// Make sure it hasn't already been freed.\n\t\t\tif((*grid)->cells[row] != NULL)\n\t\t\t{\n\t\t\t\tQ_free((*grid)->cells[row]);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Free all columns.\n\tQ_free((*grid)->cells);\n\n\t// Free the grid itself.\n\tQ_free((*grid));\n}\n\n\nvoid StatsGrid_Init(stats_weight_grid_t **grid,\n\t\t\t\t\t\t float falloff_interval,\n\t\t\t\t\t\t float falloff_value,\n\t\t\t\t\t\t int cell_length,\n\t\t\t\t\t\t float grid_width,\n\t\t\t\t\t\t float grid_height,\n\t\t\t\t\t\t float hold_threshold)\n{\n\tint row;\n\tint col;\n\n\t// Allocate the grid.\n\t(*grid) = (stats_weight_grid_t *)Q_malloc(sizeof(stats_weight_grid_t));\n\tmemset((*grid), 0, sizeof(stats_weight_grid_t));\n\n\tif((*grid) == NULL)\n\t{\n\t\t// Failure.\n\t\treturn;\n\t}\n\n\t// Get the row and col count.\n\t(*grid)->row_count = Q_rint(grid_height / cell_length);\n\t(*grid)->col_count = Q_rint(grid_width / cell_length);\n\n\t// Allocate the rows.\n\t(*grid)->cells = (stats_cell_t **)Q_calloc((*grid)->row_count, sizeof(stats_cell_t *));\n\n\t// If we failed allocating the rows, cleanup and return.\n\tif((*grid)->cells == NULL)\n\t{\n\t\tQ_free(*grid);\n\t\treturn;\n\t}\n\n\t// Allocate the columns for each row.\n\tfor(row = 0; row < (*grid)->row_count; row++)\n\t{\n\t\t// Allocate memory for the current rows columns.\n\t\t(*grid)->cells[row] = (stats_cell_t *)Q_calloc((*grid)->col_count, sizeof(stats_cell_t));\n\n\t\t// If something went wrong cleanup and return.\n\t\tif((*grid)->cells[row] == NULL)\n\t\t{\n\t\t\t// Make sure we're not trying to free more than we allocated.\n\t\t\t(*grid)->row_count = row;\n\n\t\t\t// Remove the grid.\n\t\t\tStatsGrid_Remove(grid);\n\n\t\t\t// Failure.\n\t\t\treturn;\n\t\t}\n\n\t\t// Set initial values for all the cells.\n\t\tfor(col = 0; col < (*grid)->col_count; col++)\n\t\t{\n\t\t\t(*grid)->cells[row][col].teams[STATS_TEAM1].weight = 0.0;\n\t\t\t(*grid)->cells[row][col].teams[STATS_TEAM1].change_time = 0.0;\n\t\t\t(*grid)->cells[row][col].teams[STATS_TEAM1].death_weight = 0.0;\n\n\t\t\t(*grid)->cells[row][col].teams[STATS_TEAM2].weight = 0.0;\n\t\t\t(*grid)->cells[row][col].teams[STATS_TEAM2].change_time = 0.0;\n\t\t\t(*grid)->cells[row][col].teams[STATS_TEAM2].death_weight = 0.0;\n\n\t\t\t// Save the quake coordinates of the cells upper left corner.\n\t\t\t(*grid)->cells[row][col].tl_x = cl.worldmodel->mins[0] + (col * cell_length);\n\t\t\t(*grid)->cells[row][col].tl_y = cl.worldmodel->mins[1] + (row * cell_length);\n\t\t}\n\t}\n\n\t// If everything went well set the rest of the stuff.\n\t(*grid)->falloff_interval\t= falloff_interval;\n\t(*grid)->falloff_value\t\t= falloff_value;\n\t(*grid)->cell_length\t\t= cell_length;\n\t(*grid)->width\t\t\t\t= grid_width;\n\t(*grid)->height\t\t\t\t= grid_height;\n\n\t// We will wait with setting these until the match has started.\n\n\t(*grid)->teams[STATS_TEAM1].color\t\t\t= 0;\n\t(*grid)->teams[STATS_TEAM2].color\t\t\t= 0;\n\t(*grid)->teams[STATS_TEAM1].hold_count\t\t= 0;\n\t(*grid)->teams[STATS_TEAM2].hold_count\t\t= 0;\n\t(*grid)->teams[STATS_TEAM1].color\t\t\t= 0;\n\t(*grid)->teams[STATS_TEAM2].color\t\t\t= 0;\n\t(*grid)->hold_threshold\t\t\t\t\t\t= hold_threshold;\n}\n\n#define STATS_MAX_IMPORTANT_ENTS\t16\n\nvoid StatsGrid_ResetHoldItemsOrder(void)\n{\n\tint i;\n\n\tif (stats_important_ents == NULL)\n\t{\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < stats_important_ents->count; i++)\n\t{\n\t\tstats_important_ents->list[i].order = STATS_MAX_IMPORTANT_ENTS;\n\t}\n}\n\nvoid StatsGrid_SetHoldItemOrder(const char *item_name, int order)\n{\n\tint i;\n\tint len = strlen(item_name);\n\n\tif (stats_important_ents == NULL)\n\t{\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < stats_important_ents->count; i++)\n\t{\n\t\tif (!strncasecmp(stats_important_ents->list[i].name, item_name, len))\n\t\t{\n\t\t\tstats_important_ents->list[i].order = order;\n\t\t}\n\t}\n}\n\nvoid StatsGrid_SetHoldItemName(char *dst_name, const char *src_name, int count)\n{\n\t// If there are more than one of this item already then name it \"ITEM#\"\n\t// RL, RL2, RL3 and so on.\n\tif(count > 1)\n\t{\n\t\tstrcpy(dst_name, va(\"%s%d\", src_name, count));\n\t}\n\telse\n\t{\n\t\tstrcpy(dst_name, src_name);\n\t}\n}\n\nvoid StatsGrid_InitHoldItems(void)\n{\n\tint i;\n\tint ents_count = 0;\n\n\t// This is used to keep count of how many of the different\n\t// types of items that exist on the map, so that they\n\t// can be named \"RL\" \"RL2\" and so on.\n\tint pent_count\t= 0;\n\tint quad_count\t= 0;\n\tint ring_count\t= 0;\n\tint suit_count\t= 0;\n\tint rl_count\t= 0;\n\tint gl_count\t= 0;\n\tint lg_count\t= 0;\n\tint sng_count\t= 0;\n\tint mega_count\t= 0;\n\tint ra_count\t= 0;\n\tint ya_count\t= 0;\n\tint ga_count\t= 0;\n\n\t// Buffer.\n\tstats_entity_t temp_ents[STATS_MAX_IMPORTANT_ENTS];\n\n\t// Entities (weapons and such). cl_main.c\n\textern visentlist_t cl_visents;\n\n\t// Don't create the list before we have any entities to work with.\n\tif(cl_visents.count <= 0)\n\t{\n\t\treturn;\n\t}\n\n\tstats_important_ents = (stats_entities_t *)Q_malloc(sizeof(stats_entities_t));\n\n\t// Something bad happened.\n\tif(stats_important_ents == NULL)\n\t{\n\t\treturn;\n\t}\n\n\t// Go through the entities and check for the important ones\n\t// and save their name and location.\n\tfor (i = 0; i < cl_visents.count && (ents_count < STATS_MAX_IMPORTANT_ENTS); i++)\n\t{\n\t\tif(cl_visents.list[i].ent.model->modhint == MOD_PENT)\n\t\t{\n\t\t\tStatsGrid_SetHoldItemName(temp_ents[ents_count].name, \"PENT\", ++pent_count);\n\t\t}\n\t\telse if(cl_visents.list[i].ent.model->modhint == MOD_QUAD)\n\t\t{\n\t\t\tStatsGrid_SetHoldItemName(temp_ents[ents_count].name, \"QUAD\", ++quad_count);\n\t\t}\n\t\telse if(cl_visents.list[i].ent.model->modhint == MOD_RING)\n\t\t{\n\t\t\tStatsGrid_SetHoldItemName(temp_ents[ents_count].name, \"RING\", ++ring_count);\n\t\t}\n\t\telse if(cl_visents.list[i].ent.model->modhint == MOD_SUIT)\n\t\t{\n\t\t\tStatsGrid_SetHoldItemName(temp_ents[ents_count].name, \"SUIT\", ++suit_count);\n\t\t}\n\t\telse if(cl_visents.list[i].ent.model->modhint == MOD_ROCKETLAUNCHER)\n\t\t{\n\t\t\tStatsGrid_SetHoldItemName(temp_ents[ents_count].name, \"RL\", ++rl_count);\n\t\t}\n\t\telse if(cl_visents.list[i].ent.model->modhint == MOD_LIGHTNINGGUN)\n\t\t{\n\t\t\tStatsGrid_SetHoldItemName(temp_ents[ents_count].name, \"LG\", ++lg_count);\n\t\t}\n\t\telse if(cl_visents.list[i].ent.model->modhint == MOD_GRENADELAUNCHER)\n\t\t{\n\t\t\tStatsGrid_SetHoldItemName(temp_ents[ents_count].name, \"GL\", ++gl_count);\n\t\t}\n\t\telse if(!strcmp(cl_visents.list[i].ent.model->name, \"progs/g_nail2.mdl\"))\n\t\t{\n\t\t\tStatsGrid_SetHoldItemName(temp_ents[ents_count].name, \"SNG\", ++sng_count);\n\t\t}\n\t\telse if (cl_visents.list[i].ent.model->modhint == MOD_MEGAHEALTH)\n\t\t{\n\t\t\t// Megahealth.\n\t\t\tStatsGrid_SetHoldItemName(temp_ents[ents_count].name, \"MH\", ++mega_count);\n\t\t}\n\t\telse if (cl_visents.list[i].ent.model->modhint == MOD_ARMOR)\n\t\t{\n\t\t\tif(cl_visents.list[i].ent.skinnum == 0)\n\t\t\t{\n\t\t\t\tStatsGrid_SetHoldItemName(temp_ents[ents_count].name, \"GA\", ++ga_count);\n\t\t\t}\n\t\t\telse if(cl_visents.list[i].ent.skinnum == 1)\n\t\t\t{\n\t\t\t\tStatsGrid_SetHoldItemName(temp_ents[ents_count].name, \"YA\", ++ya_count);\n\t\t\t}\n\t\t\telse if(cl_visents.list[i].ent.skinnum == 2)\n\t\t\t{\n\t\t\t\tStatsGrid_SetHoldItemName(temp_ents[ents_count].name, \"RA\", ++ra_count);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// The entity wasn't one we wanted.\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Copy the position of the entity into the buffer.\n\t\tVectorCopy(cl_visents.list[i].ent.origin, temp_ents[ents_count].origin);\n\n\t\t// Reset the team values.\n\t\ttemp_ents[ents_count].teams_hold_count[STATS_TEAM1] = 0;\n\t\ttemp_ents[ents_count].teams_hold_count[STATS_TEAM2] = 0;\n\n\t\tents_count++;\n\t}\n\n\t// Set the count of found entities and allocate memory for the\n\t// final list of items.\n\tstats_important_ents->count = ents_count;\n\tstats_important_ents->list = Q_calloc(ents_count, sizeof(stats_entity_t));\n\n\t// Something bad happened, cleanup.\n\tif(stats_important_ents->list == NULL)\n\t{\n\t\tQ_free(stats_important_ents);\n\t\treturn;\n\t}\n\n\t// Copy the entities from the buffer to the final list.\n\tfor(i = 0; i < ents_count; i++)\n\t{\n\t\tmemcpy(&stats_important_ents->list[i], &temp_ents[i], sizeof(stats_entity_t));\n\t}\n\n\t// Set the radius around the items that decides if it's being\n\t// held by a team or not.\n\tstats_important_ents->hold_radius = 264.0;\n\n\t// Get the entity with the longest name to use for padding\n\t// (so we don't have to calculate this more than once).\n\tstats_important_ents->longest_name = 0;\n\tfor(i = 0; i < stats_important_ents->count; i++)\n\t{\n\t\tint current = strlen(stats_important_ents->list[i].name);\n\t\tstats_important_ents->longest_name = max(current, stats_important_ents->longest_name);\n\t}\n}\n\nvoid StatsGrid_InitTeamNames(stats_weight_grid_t *grid)\n{\n\tint i;\n\n\t// Go through the rest of the players until another team is found, that must be team2.\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\t// Skip spectators.\n\t\tif(!cl.players[i].name[0] || cl.players[i].spectator)\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Get the first player's team and set that as team1.\n\t\tif(!grid->teams[STATS_TEAM1].name[0])\n\t\t{\n\t\t\tstrlcpy(grid->teams[STATS_TEAM1].name, cl.players[i].team, MAX_INFO_STRING);\n\t\t\tgrid->teams[STATS_TEAM1].color = Sbar_BottomColor(&cl.players[i]);\n\t\t}\n\n\t\t// If this players team isn't the same as the first players team\n\t\t// set this players team as team 2.\n\t\tif(strcmp(grid->teams[STATS_TEAM1].name, cl.players[i].team))\n\t\t{\n\t\t\tstrlcpy(grid->teams[STATS_TEAM2].name, cl.players[i].team, MAX_INFO_STRING);\n\t\t\tgrid->teams[STATS_TEAM2].color = Sbar_BottomColor(&cl.players[i]);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nvoid StatsGrid_ValidateTeamColors(void)\n{\n\t// This is needed to handle when a user switches the player being tracked\n\t// and has \"teamcolor\" and \"enemycolor\" set. When you switch to a player\n\t// that has another team than the one you tracked at match start, the team\n\t// colors saved on the stats grid will not match the team color of the actual\n\t// players on the level.\n\n\tint player_color;\n\tplayer_info_t *player_info;\n\tint tracked = Cam_TrackNum();\n\n\tif (tracked < 0)\n\t{\n\t\treturn;\n\t}\n\n\t// Get the tracked player.\n\tplayer_info = &cl.players[tracked];\n\n\t// Get the team color of the tracked player.\n\tplayer_color = Sbar_BottomColor(player_info);\n\n\tif (!stats_important_ents || !stats_grid)\n\t{\n\t\treturn;\n\t}\n\n\t// If the player being tracked is a member of team 1 for instance but has a\n\t// team color that doesn't match the one saved for team 1 in the\n\t// stats grid, then swap the team colors in the stats grid.\n\tif (!strncmp(stats_grid->teams[STATS_TEAM1].name, player_info->team, sizeof(stats_grid->teams[STATS_TEAM1].name))\n\t\t&& stats_grid->teams[STATS_TEAM1].color != player_color)\n\t{\n\t\tint old_team1_color = stats_grid->teams[STATS_TEAM1].color;\n\t\tstats_grid->teams[STATS_TEAM1].color = stats_important_ents->teams[STATS_TEAM1].color = player_color;\n\t\tstats_grid->teams[STATS_TEAM2].color = stats_important_ents->teams[STATS_TEAM2].color = old_team1_color;\n\t}\n\telse if (!strncmp(stats_grid->teams[STATS_TEAM2].name, player_info->team, sizeof(stats_grid->teams[STATS_TEAM2].name))\n\t\t&& stats_grid->teams[STATS_TEAM2].color != player_color)\n\t{\n\t\tint old_team2_color = stats_grid->teams[STATS_TEAM2].color;\n\t\tstats_grid->teams[STATS_TEAM2].color = stats_important_ents->teams[STATS_TEAM2].color = player_color;\n\t\tstats_grid->teams[STATS_TEAM1].color = stats_important_ents->teams[STATS_TEAM1].color = old_team2_color;\n\t}\n}\n\nvoid StatsGrid_Change(stats_weight_grid_t *grid,\n\t\t\t\t\t\t\tfloat falloff_interval,\n\t\t\t\t\t\t\tfloat falloff_value,\n\t\t\t\t\t\t\tint grid_width,\n\t\t\t\t\t\t\tint grid_height)\n{\n\tint row;\n\tint col;\n\tint cell_length;\n\n\t// Don't do something stupid.\n\tif(grid == NULL)\n\t{\n\t\treturn;\n\t}\n\n\t// Calculate the new cell length.\n\tcell_length = grid_height / grid->row_count;\n\n\tfor(row = 0; row < grid->row_count; row++)\n\t{\n\t\t// Reset the location of the cells.\n\t\tfor(col = 0; col < grid->col_count; col++)\n\t\t{\n\t\t\tgrid->cells[row][col].tl_x = col * cell_length;\n\t\t\tgrid->cells[row][col].tl_y = row * cell_length;\n\t\t}\n\t}\n\n\tgrid->falloff_interval\t= falloff_interval;\n\tgrid->falloff_value\t\t= falloff_value;\n}\n\nvoid StatsGrid_DecreaseWeight(cell_weight_t *weight, stats_weight_grid_t *grid)\n{\n\t// Decrease the weight of the specified cell.\n\tif(weight->weight > 0 && (cls.demotime - weight->change_time >= grid->falloff_interval))\n\t{\n\t\tweight->change_time = cls.demotime;\n\t\tweight->weight -= grid->falloff_value;\n\t}\n}\n\nvoid StatsGrid_ResetHoldItems(void)\n{\n\t// Nothing to reset.\n\tif(stats_important_ents == NULL)\n\t{\n\t\treturn;\n\t}\n\n\t// Free any entities in the list.\n\tif(stats_important_ents->list != NULL)\n\t{\n\t\tQ_free(stats_important_ents->list);\n\t}\n\n\t// Reset.\n\tQ_free(stats_important_ents);\n}\n\nvoid StatsGrid_ResetHoldItemCounts(void)\n{\n\tint i = 0;\n\n\tif(stats_important_ents == NULL)\n\t{\n\t\treturn;\n\t}\n\n\tfor(i = 0; i < stats_important_ents->count; i++)\n\t{\n\t\tstats_important_ents->list[i].teams_hold_count[STATS_TEAM1] = 0;\n\t\tstats_important_ents->list[i].teams_hold_count[STATS_TEAM2] = 0;\n\t}\n}\n\nstatic int StatsGrid_CompareHoldItems(const void *it1, const void *it2)\n{\n\tconst stats_entity_t *ent1 = (stats_entity_t *)it1;\n\tconst stats_entity_t *ent2 = (stats_entity_t *)it2;\n\n\treturn ent1->order - ent2->order;\n}\n\nvoid StatsGrid_SortHoldItems(void)\n{\n\tif (stats_important_ents == NULL)\n\t{\n\t\treturn;\n\t}\n\n\tqsort(stats_important_ents->list, stats_important_ents->count, sizeof(stats_entity_t), StatsGrid_CompareHoldItems);\n}\n\nvoid StatsGrid_CalculateHoldItem(stats_weight_grid_t *grid, int row, int col, float hold_threshold, int team_id)\n{\n\tint i = 0;\n\n\tif(stats_important_ents == NULL || stats_important_ents->list == NULL)\n\t{\n\t\treturn;\n\t}\n\n\tfor(i = 0; i < stats_important_ents->count; i++)\n\t{\n\t\t// Check if the cell is within the \"hold radius\" for this item\n\t\t// if it is, increase the hold count for this team for this item.\n\t\t// The team with the most \"owned\" cells within this radius around\n\t\t// the item is considered to hold it.\n\t\tif(fabs(grid->cells[row][col].tl_x - stats_important_ents->list[i].origin[0]) <= stats_important_ents->hold_radius\n\t\t\t&& fabs(grid->cells[row][col].tl_y - stats_important_ents->list[i].origin[1]) <= stats_important_ents->hold_radius)\n\t\t{\n\t\t\tstats_important_ents->list[i].teams_hold_count[team_id]++;\n\n\t\t\t// If this is the first time, set the name of the team in the entity struct.\n\t\t\tif(!stats_important_ents->teams[team_id].name[0])\n\t\t\t{\n\t\t\t\tstrlcpy(stats_important_ents->teams[team_id].name, grid->teams[team_id].name, MAX_INFO_STRING);\n\t\t\t\tstats_important_ents->teams[team_id].color = grid->teams[team_id].color;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid StatsGrid_DecreaseTeamWeights(stats_weight_grid_t *grid, int row, int col, float hold_threshold)\n{\n\t// Don't try to do something stupid.\n\tif(grid == NULL || row < 0 || col < 0)\n\t{\n\t\treturn;\n\t}\n\n\t// No point in doing anything if the weights are all zero.\n\tif(grid->cells[row][col].teams[STATS_TEAM1].weight + grid->cells[row][col].teams[STATS_TEAM2].weight <= 0)\n\t{\n\t\treturn;\n\t}\n\n\t// Increase the amount of cells that is being \"hold\" by the teams.\n\tif(grid->cells[row][col].teams[STATS_TEAM1].weight > grid->cells[row][col].teams[STATS_TEAM2].weight)\n\t{\n\t\tif(grid->cells[row][col].teams[STATS_TEAM1].weight > hold_threshold)\n\t\t{\n\t\t\tgrid->teams[STATS_TEAM1].hold_count++;\n\t\t\tStatsGrid_CalculateHoldItem(grid, row, col, hold_threshold, STATS_TEAM1);\n\t\t}\n\t}\n\telse\n\t{\n\t\tif(grid->cells[row][col].teams[STATS_TEAM2].weight > hold_threshold)\n\t\t{\n\t\t\tgrid->teams[STATS_TEAM2].hold_count++;\n\t\t\tStatsGrid_CalculateHoldItem(grid, row, col, hold_threshold, STATS_TEAM2);\n\t\t}\n\t}\n\n\t// Decrease the weights for both teams.\n\tStatsGrid_DecreaseWeight(&grid->cells[row][col].teams[STATS_TEAM1], grid);\n\tStatsGrid_DecreaseWeight(&grid->cells[row][col].teams[STATS_TEAM2], grid);\n}\n\nvoid StatsGrid_SetWeightForPlayer(stats_weight_grid_t *grid,\n\t\t\t\t\t\t\t\t  player_state_t *player_state,\n\t\t\t\t\t\t\t\t  player_info_t *player_info)\n{\n\tfloat weight = 0.0;\n\tint row, col;\n\tint team_id = 0;\n\n\tint row_top;\n\tint row_bottom;\n\tint col_left;\n\tint col_right;\n\n\t// FIXME : Bad hack!\n\t// HACK: This is so that I can keep death status of the players\n\t// and since all I have to differentiate them is the userid,\n\t// I have to have enough room to fit the players. Most of this\n\t// space is unused. Too lazy to do something more fancy atm :S\n\t#define DEAD_SIZE 1024\n\tstatic qbool isdead[DEAD_SIZE];\n\n\t// Don't calculate any weights before a match has started.\n\t// Don't allow setting the weight at the exact time the countdown\n\t// or standby ends (cl.gametime == 0), that will result in a weight\n\t// being set in the position that the player was the milisecond before\n\t// he spawned, so wait one second before starting to set any weights.\n\tif(cl.countdown\t\t\t\t|| cl.standby\t\t\t|| cl.gametime <= 1\n\t\t|| grid == NULL\t\t\t|| grid->cells == NULL\n\t\t|| grid->col_count <= 0 || grid->row_count <= 0\n\t\t|| player_info == NULL\t|| player_state == NULL )\n\t{\n\t\treturn;\n\t}\n\n\t// Set the team names.\n\tif(!grid->teams[STATS_TEAM1].name[0] || !grid->teams[STATS_TEAM2].name[0])\n\t{\n\t\tStatsGrid_InitTeamNames(grid);\n\t}\n\n\t// TODO: Make this properly (this is just a quick hack atm). Use autotrack values?\n\tif(player_info->stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER)\n\t{\n\t\tweight += 0.5;\n\t}\n\telse if(player_info->stats[STAT_ITEMS] & IT_LIGHTNING)\n\t{\n\t\tweight += 0.4;\n\t}\n\telse if(player_info->stats[STAT_ITEMS] & IT_GRENADE_LAUNCHER)\n\t{\n\t\tweight += 0.3;\n\t}\n\telse if(player_info->stats[STAT_ITEMS] & IT_SUPER_NAILGUN)\n\t{\n\t\tweight += 0.2;\n\t}\n\telse if(player_info->stats[STAT_ITEMS] & IT_SUPER_SHOTGUN)\n\t{\n\t\tweight += 0.2;\n\t}\n\telse if(player_info->stats[STAT_ITEMS] & IT_NAILGUN)\n\t{\n\t\tweight += 0.05;\n\t}\n\n\tweight += player_info->stats[STAT_HEALTH] / 1000.0;\n\n\tif(player_info->stats[STAT_ITEMS] & IT_ARMOR3)\n\t{\n\t\tweight += (player_info->stats[STAT_ARMOR] * (200.0/500.0)) / 1000.0;\n\t}\n\telse if(player_info->stats[STAT_ITEMS] & IT_ARMOR2)\n\t{\n\t\tweight += (player_info->stats[STAT_ARMOR] * (150.0/500.0)) / 1000.0;\n\t}\n\telse if(player_info->stats[STAT_ITEMS] & IT_ARMOR1)\n\t{\n\t\tweight += (player_info->stats[STAT_ARMOR] * (100.0/500.0)) / 1000.0;\n\t}\n\n\t//\n\t// Get the grid cell that the player is located in based on it's quake coordinates.\n\t//\n\trow = fabs(cl.worldmodel->mins[1] - player_state->origin[1]) / grid->cell_length;\n\tcol = fabs(cl.worldmodel->mins[0] - player_state->origin[0]) / grid->cell_length;\n\n\t// Make sure we're not out of bounds.\n\trow = min(row, grid->row_count - 1);\n\tcol = min(col, grid->col_count - 1);\n\n\t//\n\t// Get the neighbours\n\t//\n\t{\n\t\t// The row above (make sure it's >= 0).\n\t\trow_top = row - 1;\n\t\trow_top = max(0, row_top);\n\n\t\t// Row below.\n\t\trow_bottom = row + 1;\n\t\trow_bottom = min(grid->row_count - 1, row_bottom);\n\n\t\t// Column to the left.\n\t\tcol_left = col - 1;\n\t\tcol_left = max(0, col_left);\n\n\t\t// Column to the right.\n\t\tcol_right = col + 1;\n\t\tcol_right = min(grid->col_count - 1, col_right);\n\t}\n\n\t// Get the team.\n\tteam_id = !strcmp(player_info->team, grid->teams[STATS_TEAM1].name) ? STATS_TEAM1 : STATS_TEAM2;\n\n\t// Raise the death weight for this cell if the player is dead.\n\tif((player_info->stats[STAT_HEALTH] > 0) && (player_info->userid < DEAD_SIZE) && isdead[player_info->userid])\n\t{\n\t\tisdead[player_info->userid] = false;\n\t}\n\n\tif(player_info->stats[STAT_HEALTH] <= 0 && (player_info->userid < DEAD_SIZE) && !isdead[player_info->userid])\n\t{\n\t\t#define DEATH_WEIGHT\t\t0.2\n\t\t#define DEATH_WEIGHT_CLOSE\tDEATH_WEIGHT * 0.8\n\t\t#define DEATH_WEIGHT_FAR\tDEATH_WEIGHT * 0.5\n\n\t\tisdead[player_info->userid] = true;\n\t\tgrid->cells[row][col].teams[team_id].death_weight += DEATH_WEIGHT;\n\n\t\t// Top.\n\t\tgrid->cells[row_top][col].teams[team_id].death_weight += DEATH_WEIGHT_CLOSE;\n\n\t\t// Top Right.\n\t\tgrid->cells[row_top][col_right].teams[team_id].death_weight = DEATH_WEIGHT_FAR;\n\n\t\t// Right.\n\t\tgrid->cells[row][col_right].teams[team_id].death_weight = DEATH_WEIGHT_CLOSE;\n\n\t\t// Bottom Right.\n\t\tgrid->cells[row_bottom][col_right].teams[team_id].death_weight = DEATH_WEIGHT_FAR;\n\n\t\t// Bottom.\n\t\tgrid->cells[row_bottom][col].teams[team_id].death_weight = DEATH_WEIGHT_CLOSE;\n\n\t\t// Bottom Left.\n\t\tgrid->cells[row_bottom][col_left].teams[team_id].death_weight = DEATH_WEIGHT_FAR;\n\n\t\t// Left.\n\t\tgrid->cells[row][col_left].teams[team_id].death_weight = DEATH_WEIGHT_CLOSE;\n\n\t\t// Top Left.\n\t\tgrid->cells[row_top][col_left].teams[team_id].death_weight = DEATH_WEIGHT_FAR;\n\t}\n\n\t// Only change the weight if the new weight is greater than the current one.\n\tif(weight >= grid->cells[row][col].teams[team_id].weight)\n\t{\n\t\tfloat neighbour_weight_close;\n\t\tfloat neighbour_weight_far;\n\n\t\t// Fade the weights for the neighbours. The edge corners\n\t\t// are at 50% opacity, and the ones at top/bottom, left/right at 80%.\n\t\tneighbour_weight_close\t= weight * 0.8;\n\t\tneighbour_weight_far\t= weight * 0.5;\n\n\t\t// Set the team weight for the current cell + surrounding cells.\n\t\t{\n\t\t\t// The current cell.\n\t\t\tgrid->cells[row][col].teams[team_id].weight = weight;\n\t\t\tgrid->cells[row][col].teams[team_id].change_time = cls.demotime;\n\n\t\t\t// Top.\n\t\t\tgrid->cells[row_top][col].teams[team_id].weight = max(grid->cells[row_top][col].teams[team_id].weight, neighbour_weight_close);\n\t\t\tgrid->cells[row_top][col].teams[team_id].change_time = cls.demotime;\n\n\t\t\t// Top right.\n\t\t\tgrid->cells[row_top][col_right].teams[team_id].weight = max(grid->cells[row_top][col_right].teams[team_id].weight, neighbour_weight_far);\n\t\t\tgrid->cells[row_top][col_right].teams[team_id].change_time = cls.demotime;\n\n\t\t\t// Right.\n\t\t\tgrid->cells[row][col_right].teams[team_id].weight = max(grid->cells[row][col_right].teams[team_id].weight, neighbour_weight_close);\n\t\t\tgrid->cells[row][col_right].teams[team_id].change_time = cls.demotime;\n\n\t\t\t// Bottom right.\n\t\t\tgrid->cells[row_bottom][col_right].teams[team_id].weight = max(grid->cells[row_bottom][col_right].teams[team_id].weight, neighbour_weight_far);\n\t\t\tgrid->cells[row_bottom][col_right].teams[team_id].change_time = cls.demotime;\n\n\t\t\t// Bottom.\n\t\t\tgrid->cells[row_bottom][col].teams[team_id].weight = max(grid->cells[row_bottom][col].teams[team_id].weight, neighbour_weight_close);\n\t\t\tgrid->cells[row_bottom][col].teams[team_id].change_time = cls.demotime;\n\n\t\t\t// Bottom left.\n\t\t\tgrid->cells[row_bottom][col_left].teams[team_id].weight = max(grid->cells[row_bottom][col_left].teams[team_id].weight, neighbour_weight_far);\n\t\t\tgrid->cells[row_bottom][col_left].teams[team_id].change_time = cls.demotime;\n\n\t\t\t// Left.\n\t\t\tgrid->cells[row][col_left].teams[team_id].weight = max(grid->cells[row][col_left].teams[team_id].weight, neighbour_weight_close);\n\t\t\tgrid->cells[row][col_left].teams[team_id].change_time = cls.demotime;\n\n\t\t\t// Top left.\n\t\t\tgrid->cells[row_top][col_left].teams[team_id].weight = max(grid->cells[row_top][col_left].teams[team_id].weight, neighbour_weight_far);\n\t\t\tgrid->cells[row_top][col_left].teams[team_id].change_time = cls.demotime;\n\t\t}\n\t}\n}\n\nvoid StatsGrid_Gather(void)\n{\n\tint i;\n\tint row, col;\n\tstatic int lastframecount = -1;\n\tplayer_state_t *state;\n\tplayer_info_t *info;\n\n\t// Initiate the grid if it hasn't already been initiated.\n\t// The grid is reset on all level changes.\n\tif (stats_grid == NULL) {\n\t\tif (!cl.worldmodel) {\n\t\t\treturn;\n\t\t}\n\n\t\t// TODO: Create cvars that let us set these values.\n\t\tStatsGrid_Init(&stats_grid, // The grid to initiate.\n\t\t\t5.0,\t\t\t\t\t// The time in miliseconds between fall offs.\n\t\t\t0.2,\t\t\t\t\t// At each fall off period, how much should be substracted from the weight?\n\t\t\t50,\t\t\t\t\t\t// The length in quad coordinates of a cells side length.\n\t\t\tfabs(cl.worldmodel->maxs[0] - cl.worldmodel->mins[0]), // Width of the map in quake coordinates.\n\t\t\tfabs(cl.worldmodel->maxs[1] - cl.worldmodel->mins[1]), // Height (if we're talking 2D where Y = height).\n\t\t\t0.0);\t\t\t\t\t// The threshold before a team is considered to \"hold\" a cell.\n\n\t\t// We failed initializing the grid, so don't do anything.\n\t\tif(stats_grid == NULL) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// If it's the first time for this level initiate.\n\tif (stats_important_ents == NULL) {\n\t\tStatsGrid_InitHoldItems();\n\t}\n\n\tif (teamholdinfo == NULL || teamholdbar == NULL) {\n\t\treturn;\n\t}\n\tif (teamholdinfo->show->value == 0 && teamholdbar->show->value == 0) {\n\t\treturn;\n\t}\n\n\t// Only gather once per frame.\n\tif (cls.framecount == lastframecount) {\n\t\treturn;\n\t}\n\tlastframecount = cls.framecount;\n\n\tif (!cl.oldparsecount || !cl.parsecount || cls.state < ca_active) {\n\t\treturn;\n\t}\n\n\t// Make sure the team colors are correct.\n\tStatsGrid_ValidateTeamColors();\n\n\t// Get player state so we can know where he is (or on rare occassions, she).\n\tstate = cl.frames[cl.oldparsecount & UPDATE_MASK].playerstate;\n\n\t// Get the info for the player.\n\tinfo = cl.players;\n\n\tfor (i = 0; i < MAX_CLIENTS; i++, info++, state++) {\n\t\t// Skip spectators.\n\t\tif(!cl.players[i].name[0] || cl.players[i].spectator) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Calculate the weight for this player and set it at the\n\t\t// cell the player is located on in the grid.\n\t\tStatsGrid_SetWeightForPlayer(stats_grid, state, info);\n\t}\n\n\t// Reset the amount of cells that each team \"holds\" since\n\t// they will be recalculated when the weights are decreased.\n\tstats_grid->teams[STATS_TEAM1].hold_count = 0;\n\tstats_grid->teams[STATS_TEAM2].hold_count = 0;\n\tStatsGrid_ResetHoldItemCounts();\n\n\t// Go through all the cells and decrease their weights.\n\tfor(row = 0; row < stats_grid->row_count; row++) {\n\t\tfor(col = 0; col < stats_grid->col_count; col++) {\n\t\t\tStatsGrid_DecreaseTeamWeights(stats_grid, row, col, stats_grid->hold_threshold);\n\t\t}\n\t}\n}\n\n\n\n\n// HUD rendering\n\n#define HUD_TEAMHOLDINFO_STYLE_TEAM_NAMES\t\t0\n#define HUD_TEAMHOLDINFO_STYLE_PERCENT_BARS\t\t1\n#define HUD_TEAMHOLDINFO_STYLE_PERCENT_BARS2\t2\n\n// Team hold filters.\nstatic qbool teamhold_show_pent = false;\nstatic qbool teamhold_show_quad = false;\nstatic qbool teamhold_show_ring = false;\nstatic qbool teamhold_show_suit = false;\nstatic qbool teamhold_show_rl = false;\nstatic qbool teamhold_show_lg = false;\nstatic qbool teamhold_show_gl = false;\nstatic qbool teamhold_show_sng = false;\nstatic qbool teamhold_show_mh = false;\nstatic qbool teamhold_show_ra = false;\nstatic qbool teamhold_show_ya = false;\nstatic qbool teamhold_show_ga = false;\n\nvoid TeamHold_DrawBars(int x, int y, int width, int height,\n\t\t\t\t\t   float team1_percent, float team2_percent,\n\t\t\t\t\t   int team1_color, int team2_color,\n\t\t\t\t\t   float opacity)\n{\n\tint team1_width = 0;\n\tint team2_width = 0;\n\tint bar_height = 0;\n\n\tbar_height = Q_rint(height / 2.0);\n\tteam1_width = (int)(width * team1_percent);\n\tteam2_width = (int)(width * team2_percent);\n\n\tclamp(team1_width, 0, width);\n\tclamp(team2_width, 0, width);\n\n\tDraw_AlphaFill(x, y, team1_width, bar_height, team1_color, opacity);\n\n\ty += bar_height;\n\n\tDraw_AlphaFill(x, y, team2_width, bar_height, team2_color, opacity);\n}\n\nvoid TeamHold_DrawPercentageBar(\n\tint x, int y, int width, int height,\n\tfloat team1_percent, float team2_percent,\n\tint team1_color, int team2_color,\n\tint show_text, int vertical,\n\tint vertical_text, float opacity, float scale,\n\tqbool proportional\n)\n{\n\tint _x, _y;\n\tint _width, _height;\n\n\tif (vertical) {\n\t\t//\n\t\t// Draw vertical.\n\t\t//\n\n\t\t// Team 1.\n\t\t_x = x;\n\t\t_y = y;\n\t\t_width = max(0, width);\n\t\t_height = Q_rint(height * team1_percent);\n\t\t_height = max(0, height);\n\n\t\tDraw_AlphaFill(_x, _y, _width, _height, team1_color, opacity);\n\n\t\t// Team 2.\n\t\t_x = x;\n\t\t_y = Q_rint(y + (height * team1_percent));\n\t\t_width = max(0, width);\n\t\t_height = Q_rint(height * team2_percent);\n\t\t_height = max(0, _height);\n\n\t\tDraw_AlphaFill(_x, _y, _width, _height, team2_color, opacity);\n\n\t\t// Show the percentages in numbers also.\n\t\tif (show_text) {\n\t\t\t// TODO: Move this to a separate function (since it's prett much copy and paste for both teams).\n\t\t\t// Team 1.\n\t\t\tif (team1_percent > 0.05) {\n\t\t\t\tif (vertical_text) {\n\t\t\t\t\tint percent = 0;\n\t\t\t\t\tint percent10 = 0;\n\t\t\t\t\tint percent100 = 0;\n\n\t\t\t\t\t_x = x + (width / 2) - FontFixedWidth(1, scale / 2, true, proportional);\n\t\t\t\t\t_y = Q_rint(y + (height * team1_percent) / 2 - 8 * 1.5 * scale);\n\n\t\t\t\t\tpercent = Q_rint(100 * team1_percent);\n\n\t\t\t\t\tif ((percent100 = percent / 100)) {\n\t\t\t\t\t\tDraw_SString(_x, _y, va(\"%d\", percent100), scale, proportional);\n\t\t\t\t\t\t_y += 8 * scale;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ((percent10 = percent / 10)) {\n\t\t\t\t\t\tDraw_SString(_x, _y, va(\"%d\", percent10), scale, proportional);\n\t\t\t\t\t\t_y += 8 * scale;\n\t\t\t\t\t}\n\n\t\t\t\t\tDraw_SString(_x, _y, va(\"%d\", percent % 10), scale, proportional);\n\t\t\t\t\t_y += 8 * scale;\n\n\t\t\t\t\tDraw_SString(_x, _y, \"%\", scale, proportional);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t_x = x + (width / 2) - FontFixedWidth(1, 1.5 * scale, 1, proportional);\n\t\t\t\t\t_y = Q_rint(y + (height * team1_percent) / 2 - 8 * scale * 0.5);\n\t\t\t\t\tDraw_SString(_x, _y, va(\"%2.0f%%\", 100 * team1_percent), scale, false);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Team 2.\n\t\t\tif (team2_percent > 0.05) {\n\t\t\t\tif (vertical_text) {\n\t\t\t\t\tint percent = 0;\n\t\t\t\t\tint percent10 = 0;\n\t\t\t\t\tint percent100 = 0;\n\n\t\t\t\t\t_x = x + (width / 2) - FontFixedWidth(1, scale / 2, true, proportional);\n\t\t\t\t\t_y = Q_rint(y + (height * team1_percent) + (height * team2_percent) / 2 - 12);\n\n\t\t\t\t\tpercent = Q_rint(100 * team2_percent);\n\n\t\t\t\t\tif ((percent100 = percent / 100)) {\n\t\t\t\t\t\tDraw_SString(_x, _y, va(\"%d\", percent100), scale, proportional);\n\t\t\t\t\t\t_y += 8;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ((percent10 = percent / 10)) {\n\t\t\t\t\t\tDraw_SString(_x, _y, va(\"%d\", percent10), scale, proportional);\n\t\t\t\t\t\t_y += 8;\n\t\t\t\t\t}\n\n\t\t\t\t\tDraw_SString(_x, _y, va(\"%d\", percent % 10), scale, proportional);\n\t\t\t\t\t_y += 8;\n\n\t\t\t\t\tDraw_SString(_x, _y, \"%\", scale, proportional);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t_x = x + (width / 2) - FontFixedWidth(1, scale * 1.5, true, proportional);\n\t\t\t\t\t_y = Q_rint(y + (height * team1_percent) + (height * team2_percent) / 2 - 4);\n\t\t\t\t\tDraw_SString(_x, _y, va(\"%2.0f%%\", 100 * team2_percent), scale, proportional);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\t//\n\t\t// Draw horizontal.\n\t\t//\n\n\t\t// Team 1.\n\t\t_x = x;\n\t\t_y = y;\n\t\t_width = Q_rint(width * team1_percent);\n\t\t_width = max(0, _width);\n\t\t_height = max(0, height);\n\n\t\tDraw_AlphaFill(_x, _y, _width, _height, team1_color, opacity);\n\n\t\t// Team 2.\n\t\t_x = Q_rint(x + (width * team1_percent));\n\t\t_y = y;\n\t\t_width = Q_rint(width * team2_percent);\n\t\t_width = max(0, _width);\n\t\t_height = max(0, height);\n\n\t\tDraw_AlphaFill(_x, _y, _width, _height, team2_color, opacity);\n\n\t\t// Show the percentages in numbers also.\n\t\tif (show_text) {\n\t\t\t// Team 1.\n\t\t\tif (team1_percent > 0.05) {\n\t\t\t\t_x = Q_rint(x + (width * team1_percent) / 2 - FontFixedWidth(1, scale, true, proportional));\n\t\t\t\t_y = y + (height / 2) - 4 * scale;\n\t\t\t\tDraw_SString(_x, _y, va(\"%2.0f%%\", 100 * team1_percent), scale, proportional);\n\t\t\t}\n\n\t\t\t// Team 2.\n\t\t\tif (team2_percent > 0.05) {\n\t\t\t\t_x = Q_rint(x + (width * team1_percent) + (width * team2_percent) / 2 - FontFixedWidth(1, scale, true, proportional));\n\t\t\t\t_y = y + (height / 2) - 4 * scale;\n\t\t\t\tDraw_SString(_x, _y, va(\"%2.0f%%\", 100 * team2_percent), scale, proportional);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void TeamHold_OnChangeItemFilterInfo(cvar_t *var, char *s, qbool *cancel)\n{\n\tchar *start = s;\n\tchar *end = start;\n\tint order = 0;\n\n\t// Parse the item filter.\n\tteamhold_show_rl = Utils_RegExpMatch(\"RL\", s);\n\tteamhold_show_quad = Utils_RegExpMatch(\"QUAD\", s);\n\tteamhold_show_ring = Utils_RegExpMatch(\"RING\", s);\n\tteamhold_show_pent = Utils_RegExpMatch(\"PENT\", s);\n\tteamhold_show_suit = Utils_RegExpMatch(\"SUIT\", s);\n\tteamhold_show_lg = Utils_RegExpMatch(\"LG\", s);\n\tteamhold_show_gl = Utils_RegExpMatch(\"GL\", s);\n\tteamhold_show_sng = Utils_RegExpMatch(\"SNG\", s);\n\tteamhold_show_mh = Utils_RegExpMatch(\"MH\", s);\n\tteamhold_show_ra = Utils_RegExpMatch(\"RA\", s);\n\tteamhold_show_ya = Utils_RegExpMatch(\"YA\", s);\n\tteamhold_show_ga = Utils_RegExpMatch(\"GA\", s);\n\n\t// Reset the ordering of the items.\n\tStatsGrid_ResetHoldItemsOrder();\n\n\t// Trim spaces from the start of the word.\n\twhile (*start && *start == ' ') {\n\t\tstart++;\n\t}\n\n\tend = start;\n\n\t// Go through the string word for word and set a\n\t// rising order for each hold item based on their\n\t// order in the string.\n\twhile (*end) {\n\t\tif (*end != ' ') {\n\t\t\t// Not at the end of the word yet.\n\t\t\tend++;\n\t\t\tcontinue;\n\t\t}\n\t\telse {\n\t\t\t// We've found a word end.\n\t\t\tchar temp[256];\n\n\t\t\t// Try matching the current word with a hold item\n\t\t\t// and set it's ordering according to it's placement\n\t\t\t// in the string.\n\t\t\tstrlcpy(temp, start, min(end - start, sizeof(temp)));\n\t\t\tStatsGrid_SetHoldItemOrder(temp, order);\n\t\t\torder++;\n\n\t\t\t// Get rid of any additional spaces.\n\t\t\twhile (*end && *end == ' ') {\n\t\t\t\tend++;\n\t\t\t}\n\n\t\t\t// Start trying to find a new word.\n\t\t\tstart = end;\n\t\t}\n\t}\n\n\t// Order the hold items.\n\tStatsGrid_SortHoldItems();\n}\n\nvoid SCR_HUD_DrawTeamHoldInfo(hud_t *hud)\n{\n\tint i;\n\tint x, y;\n\tint width, height;\n\n\tstatic cvar_t\n\t\t*hud_teamholdinfo_style = NULL,\n\t\t*hud_teamholdinfo_opacity,\n\t\t*hud_teamholdinfo_width,\n\t\t*hud_teamholdinfo_height,\n\t\t*hud_teamholdinfo_onlytp,\n\t\t*hud_teamholdinfo_itemfilter,\n\t\t*hud_teamholdinfo_scale,\n\t\t*hud_teamholdinfo_proportional;\n\n\tif (hud_teamholdinfo_style == NULL)    // first time\n\t{\n\t\tchar val[256];\n\n\t\thud_teamholdinfo_style = HUD_FindVar(hud, \"style\");\n\t\thud_teamholdinfo_opacity = HUD_FindVar(hud, \"opacity\");\n\t\thud_teamholdinfo_width = HUD_FindVar(hud, \"width\");\n\t\thud_teamholdinfo_height = HUD_FindVar(hud, \"height\");\n\t\thud_teamholdinfo_onlytp = HUD_FindVar(hud, \"onlytp\");\n\t\thud_teamholdinfo_itemfilter = HUD_FindVar(hud, \"itemfilter\");\n\t\thud_teamholdinfo_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_teamholdinfo_proportional = HUD_FindVar(hud, \"proportional\");\n\n\t\t// Unecessary to parse the item filter string on each frame.\n\t\thud_teamholdinfo_itemfilter->OnChange = TeamHold_OnChangeItemFilterInfo;\n\n\t\t// Parse the item filter the first time (trigger the OnChange function above).\n\t\tstrlcpy(val, hud_teamholdinfo_itemfilter->string, sizeof(val));\n\t\tCvar_Set(hud_teamholdinfo_itemfilter, val);\n\t}\n\n\t// Get the height based on how many items we have, or what the user has set it to.\n\theight = max(0, hud_teamholdinfo_height->value);\n\twidth = max(0, hud_teamholdinfo_width->value);\n\n\t// Don't show when not in teamplay/demoplayback.\n\tif (!HUD_ShowInDemoplayback(hud_teamholdinfo_onlytp->value)) {\n\t\tHUD_PrepareDraw(hud, width, height, &x, &y);\n\t\treturn;\n\t}\n\n\t// We don't have anything to show.\n\tif (stats_important_ents == NULL || stats_grid == NULL) {\n\t\tHUD_PrepareDraw(hud, width, height, &x, &y);\n\t\treturn;\n\t}\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\tint _y = 0;\n\n\t\t_y = y;\n\n\t\t// Go through all the items and print the stats for them.\n\t\tfor (i = 0; i < stats_important_ents->count; i++) {\n\t\t\tfloat team1_percent;\n\t\t\tfloat team2_percent;\n\t\t\tint team1_hold_count = 0;\n\t\t\tint team2_hold_count = 0;\n\t\t\tint names_width = 0;\n\n\t\t\t// Don't draw outside the specified height.\n\t\t\tif ((_y - y) + 8 * hud_teamholdinfo_scale->value > height) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// If the item isn't of the specified type, then skip it.\n\t\t\tif (!((teamhold_show_rl && !strncmp(stats_important_ents->list[i].name, \"RL\", 2))\n\t\t\t\t|| (teamhold_show_quad && !strncmp(stats_important_ents->list[i].name, \"QUAD\", 4))\n\t\t\t\t|| (teamhold_show_ring && !strncmp(stats_important_ents->list[i].name, \"RING\", 4))\n\t\t\t\t|| (teamhold_show_pent && !strncmp(stats_important_ents->list[i].name, \"PENT\", 4))\n\t\t\t\t|| (teamhold_show_suit && !strncmp(stats_important_ents->list[i].name, \"SUIT\", 4))\n\t\t\t\t|| (teamhold_show_lg && !strncmp(stats_important_ents->list[i].name, \"LG\", 2))\n\t\t\t\t|| (teamhold_show_gl && !strncmp(stats_important_ents->list[i].name, \"GL\", 2))\n\t\t\t\t|| (teamhold_show_sng && !strncmp(stats_important_ents->list[i].name, \"SNG\", 3))\n\t\t\t\t|| (teamhold_show_mh && !strncmp(stats_important_ents->list[i].name, \"MH\", 2))\n\t\t\t\t|| (teamhold_show_ra && !strncmp(stats_important_ents->list[i].name, \"RA\", 2))\n\t\t\t\t|| (teamhold_show_ya && !strncmp(stats_important_ents->list[i].name, \"YA\", 2))\n\t\t\t\t|| (teamhold_show_ga && !strncmp(stats_important_ents->list[i].name, \"GA\", 2))\n\t\t\t\t)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Calculate the width of the longest item name so we can use it for padding.\n\t\t\tnames_width = FontFixedWidth(stats_important_ents->longest_name + 1, hud_teamholdinfo_scale->value, false, hud_teamholdinfo_proportional->integer);\n\n\t\t\t// Calculate the percentages of this item that the two teams holds.\n\t\t\tteam1_hold_count = stats_important_ents->list[i].teams_hold_count[STATS_TEAM1];\n\t\t\tteam2_hold_count = stats_important_ents->list[i].teams_hold_count[STATS_TEAM2];\n\n\t\t\tteam1_percent = ((float)team1_hold_count) / (team1_hold_count + team2_hold_count);\n\t\t\tteam2_percent = ((float)team2_hold_count) / (team1_hold_count + team2_hold_count);\n\n\t\t\tteam1_percent = fabs(max(0, team1_percent));\n\t\t\tteam2_percent = fabs(max(0, team2_percent));\n\n\t\t\t// Write the name of the item.\n\t\t\tDraw_SColoredStringBasic(x, _y, va(\"&cff0%s:\", stats_important_ents->list[i].name), 0, hud_teamholdinfo_scale->value, hud_teamholdinfo_proportional->integer);\n\n\t\t\tif (hud_teamholdinfo_style->value == HUD_TEAMHOLDINFO_STYLE_TEAM_NAMES) {\n\t\t\t\t//\n\t\t\t\t// Prints the team name that holds the item.\n\t\t\t\t//\n\t\t\t\tif (team1_percent > team2_percent) {\n\t\t\t\t\tDraw_SColoredStringBasic(x + names_width, _y, stats_important_ents->teams[STATS_TEAM1].name, 0, hud_teamholdinfo_scale->value, hud_teamholdinfo_proportional->integer);\n\t\t\t\t}\n\t\t\t\telse if (team1_percent < team2_percent) {\n\t\t\t\t\tDraw_SColoredStringBasic(x + names_width, _y, stats_important_ents->teams[STATS_TEAM2].name, 0, hud_teamholdinfo_scale->value, hud_teamholdinfo_proportional->integer);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (hud_teamholdinfo_style->value == HUD_TEAMHOLDINFO_STYLE_PERCENT_BARS) {\n\t\t\t\t//\n\t\t\t\t// Show a percentage bar for the item.\n\t\t\t\t//\n\t\t\t\tTeamHold_DrawPercentageBar(\n\t\t\t\t\tx + names_width, _y,\n\t\t\t\t\tQ_rint(hud_teamholdinfo_width->value - names_width), 8,\n\t\t\t\t\tteam1_percent, team2_percent,\n\t\t\t\t\tstats_important_ents->teams[STATS_TEAM1].color,\n\t\t\t\t\tstats_important_ents->teams[STATS_TEAM2].color,\n\t\t\t\t\t0, // Don't show percentage values, get's too cluttered.\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\thud_teamholdinfo_opacity->value,\n\t\t\t\t\thud_teamholdinfo_scale->value,\n\t\t\t\t\thud_teamholdinfo_proportional->integer\n\t\t\t\t);\n\t\t\t}\n\t\t\telse if (hud_teamholdinfo_style->value == HUD_TEAMHOLDINFO_STYLE_PERCENT_BARS2) {\n\t\t\t\tTeamHold_DrawBars(x + names_width, _y,\n\t\t\t\t\t\t\t\t  Q_rint(hud_teamholdinfo_width->value - names_width), 8,\n\t\t\t\t\t\t\t\t  team1_percent, team2_percent,\n\t\t\t\t\t\t\t\t  stats_important_ents->teams[STATS_TEAM1].color,\n\t\t\t\t\t\t\t\t  stats_important_ents->teams[STATS_TEAM2].color,\n\t\t\t\t\t\t\t\t  hud_teamholdinfo_opacity->value);\n\t\t\t}\n\n\t\t\t// Next line.\n\t\t\t_y += 8 * hud_teamholdinfo_scale->value;\n\t\t}\n\t}\n}\n\nvoid SCR_HUD_DrawTeamHoldBar(hud_t *hud)\n{\n\tint x, y;\n\tint height = 8;\n\tint width = 0;\n\tfloat team1_percent = 0;\n\tfloat team2_percent = 0;\n\n\tstatic cvar_t\n\t\t*hud_teamholdbar_style = NULL,\n\t\t*hud_teamholdbar_opacity,\n\t\t*hud_teamholdbar_width,\n\t\t*hud_teamholdbar_height,\n\t\t*hud_teamholdbar_vertical,\n\t\t*hud_teamholdbar_show_text,\n\t\t*hud_teamholdbar_onlytp,\n\t\t*hud_teamholdbar_vertical_text,\n\t\t*hud_teamholdbar_scale,\n\t\t*hud_teamholdbar_proportional;\n\n\tif (hud_teamholdbar_style == NULL)    // first time\n\t{\n\t\thud_teamholdbar_style = HUD_FindVar(hud, \"style\");\n\t\thud_teamholdbar_opacity = HUD_FindVar(hud, \"opacity\");\n\t\thud_teamholdbar_width = HUD_FindVar(hud, \"width\");\n\t\thud_teamholdbar_height = HUD_FindVar(hud, \"height\");\n\t\thud_teamholdbar_vertical = HUD_FindVar(hud, \"vertical\");\n\t\thud_teamholdbar_show_text = HUD_FindVar(hud, \"show_text\");\n\t\thud_teamholdbar_onlytp = HUD_FindVar(hud, \"onlytp\");\n\t\thud_teamholdbar_vertical_text = HUD_FindVar(hud, \"vertical_text\");\n\t\thud_teamholdbar_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_teamholdbar_proportional = HUD_FindVar(hud, \"proportional\");\n\t}\n\n\theight = max(1, hud_teamholdbar_height->value);\n\twidth = max(0, hud_teamholdbar_width->value);\n\n\t// Don't show when not in teamplay/demoplayback.\n\tif (!HUD_ShowInDemoplayback(hud_teamholdbar_onlytp->value)) {\n\t\tHUD_PrepareDraw(hud, width, height, &x, &y);\n\t\treturn;\n\t}\n\n\tif (HUD_PrepareDraw(hud, width, height, &x, &y)) {\n\t\t// We need something to work with.\n\t\tif (stats_grid != NULL) {\n\t\t\t// Check if we have any hold values to calculate from.\n\t\t\tif (stats_grid->teams[STATS_TEAM1].hold_count + stats_grid->teams[STATS_TEAM2].hold_count > 0) {\n\t\t\t\t// Calculate the percentage for the two teams for the \"team strength bar\".\n\t\t\t\tteam1_percent = ((float)stats_grid->teams[STATS_TEAM1].hold_count) / (stats_grid->teams[STATS_TEAM1].hold_count + stats_grid->teams[STATS_TEAM2].hold_count);\n\t\t\t\tteam2_percent = ((float)stats_grid->teams[STATS_TEAM2].hold_count) / (stats_grid->teams[STATS_TEAM1].hold_count + stats_grid->teams[STATS_TEAM2].hold_count);\n\n\t\t\t\tteam1_percent = fabs(max(0, team1_percent));\n\t\t\t\tteam2_percent = fabs(max(0, team2_percent));\n\t\t\t}\n\t\t\telse {\n\t\t\t\tDraw_AlphaFill(x, y, hud_teamholdbar_width->value, height, 0, hud_teamholdbar_opacity->value*0.5);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Draw the percentage bar.\n\t\t\tTeamHold_DrawPercentageBar(\n\t\t\t\tx, y, width, height,\n\t\t\t\tteam1_percent, team2_percent,\n\t\t\t\tstats_grid->teams[STATS_TEAM1].color,\n\t\t\t\tstats_grid->teams[STATS_TEAM2].color,\n\t\t\t\thud_teamholdbar_show_text->value,\n\t\t\t\thud_teamholdbar_vertical->value,\n\t\t\t\thud_teamholdbar_vertical_text->value,\n\t\t\t\thud_teamholdbar_opacity->value,\n\t\t\t\thud_teamholdbar_scale->value,\n\t\t\t\thud_teamholdbar_proportional->integer\n\t\t\t);\n\t\t}\n\t\telse {\n\t\t\t// If there's no stats grid available we don't know what to show, so just show a black frame.\n\t\t\tDraw_AlphaFill(x, y, hud_teamholdbar_width->value, height, 0, hud_teamholdbar_opacity->value * 0.5);\n\t\t}\n\t}\n}\n\nvoid TeamHold_HudInit(void)\n{\n\tteamholdinfo = HUD_Register(\n\t\t\"teamholdinfo\", NULL, \"Shows which important items in the level that are being held by the teams.\",\n\t\tHUD_PLUSMINUS, ca_active, 0, SCR_HUD_DrawTeamHoldInfo,\n\t\t\"0\", \"top\", \"left\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"opacity\", \"0.8\",\n\t\t\"width\", \"200\",\n\t\t\"height\", \"8\",\n\t\t\"onlytp\", \"0\",\n\t\t\"style\", \"1\",\n\t\t\"itemfilter\", \"quad ra ya ga mega pent rl quad\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n\n\tteamholdbar = HUD_Register(\n\t\t\"teamholdbar\", NULL, \"Shows how much of the level (in percent) that is currently being held by either team.\",\n\t\tHUD_PLUSMINUS, ca_active, 0, SCR_HUD_DrawTeamHoldBar,\n\t\t\"0\", \"top\", \"left\", \"bottom\", \"0\", \"0\", \"0\", \"0 0 0\", NULL,\n\t\t\"opacity\", \"0.8\",\n\t\t\"width\", \"200\",\n\t\t\"height\", \"8\",\n\t\t\"vertical\", \"0\",\n\t\t\"vertical_text\", \"0\",\n\t\t\"show_text\", \"1\",\n\t\t\"onlytp\", \"0\",\n\t\t\"scale\", \"1\",\n\t\t\"proportional\", \"0\",\n\t\tNULL\n\t);\n}\n"
  },
  {
    "path": "src/stats_grid.h",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"common.h\"\n#include \"q_shared.h\"\n\n#ifndef __STATS_GRID__H__\n#define __STATS_GRID__H__\n\n#define TEAM_COUNT\t\t2\n#define STATS_TEAM1\t\t0\n#define STATS_TEAM2\t\t1\n\ntypedef struct cell_weight_s\n{\n\tfloat\t\t\tweight;\t\t\t\t\t// Weight of the box. Between 0.0 and 1.0\n\tfloat\t\t\tchange_time;\t\t\t// The last time the weight was changed. (Used for fading weight).\n\tfloat\t\t\tdeath_weight;\t\t\t// The amount of deaths by this team in this cell (Never fades).\n} cell_weight_t;\n\ntypedef struct stats_cell_s\n{\n\tcell_weight_t\tteams[TEAM_COUNT];\t\t// The team weights for this cell.\n\tfloat\t\t\ttl_x;\t\t\t\t\t// Top left x position of the cell.\n\tfloat\t\t\ttl_y;\t\t\t\t\t// Top left y position of the cell.\n} stats_cell_t;\n\ntypedef struct stats_team_s\n{\n\tchar\tname[MAX_INFO_STRING];\t\t\t// Team name.\n\tint\t\tcolor;\t\t\t\t\t\t\t// Team color.\n\tint\t\thold_count;\t\t\t\t\t\t// The amount of visited cells that this team \"holds\".\n} stats_team_t;\n\ntypedef struct stats_weight_grid_s\n{\n\tstats_cell_t\t**cells;\t\t\t\t// The cells.\n\tfloat\t\t\tfalloff_interval;\t\t// The duration since the last weight change\n\t\t\t\t\t\t\t\t\t\t\t// to decrease the weight of the cells in the grid.\n\tfloat\t\t\tfalloff_value;\t\t\t// The amount the cell weight should decrease by\n\t\t\t\t\t\t\t\t\t\t\t// at each falloff interval.\n\tint\t\t\t\tcell_length;\t\t\t// Cell side length.\n\tint\t\t\t\trow_count;\t\t\t\t// Row count.\n\tint\t\t\t\tcol_count;\t\t\t\t// Column count.\n\tint\t\t\t\twidth;\t\t\t\t\t// The width of the grid.\n\tint\t\t\t\theight;\t\t\t\t\t// The height of the grid.\n\tstats_team_t\tteams[TEAM_COUNT];\t\t// The teams (No more than 2). \n\tfloat\t\t\thold_threshold;\t\t\t// The threshold for the weight that is required before\n\t\t\t\t\t\t\t\t\t\t\t// a cell is considered being held by a team. (0.0 is default).\n} stats_weight_grid_t;\n\ntypedef struct stats_entity_s\n{\n\tchar\t\t\tname[MAX_INFO_STRING];\t// The name of the entity (RA, RL, YA, QUAD).\n\tvec3_t\t\t\torigin;\t\t\t\t\t// The entitys origin.\n\tint\t\t\t\tteams_hold_count[TEAM_COUNT];\n\tint\t\t\t\torder;\n} stats_entity_t;\n\ntypedef struct\n{\n\tstats_entity_t\t*list;\n\tint\t\t\t\tcount;\n\tfloat\t\t\thold_radius;\n\tint\t\t\t\tlongest_name;\n\tstats_team_t\tteams[TEAM_COUNT];\n} stats_entities_t;\n\nvoid StatsGrid_Remove(stats_weight_grid_t **grid);\nvoid StatsGrid_Init(stats_weight_grid_t **grid, \n\t\t\t\t\t\t float falloff_interval, \n\t\t\t\t\t\t float falloff_value,\n\t\t\t\t\t\t int cell_length,\n\t\t\t\t\t\t float grid_width, \n\t\t\t\t\t\t float grid_height,\n\t\t\t\t\t\t float hold_threshold);\nvoid StatsGrid_InitTeamNames(stats_weight_grid_t *grid);\nvoid StatsGrid_Change(stats_weight_grid_t *grid,\n\t\t\t\t\t\t\tfloat falloff_interval,\n\t\t\t\t\t\t\tfloat falloff_value,\n\t\t\t\t\t\t\tint grid_width,\n\t\t\t\t\t\t\tint grid_height);\nvoid StatsGrid_DecreaseWeight(cell_weight_t *weight, stats_weight_grid_t *grid);\nvoid StatsGrid_Gather(void);\nvoid StatsGrid_ResetHoldItems(void);\nvoid StatsGrid_SortHoldItems(void);\nvoid StatsGrid_SetHoldItemOrder(const char *item_name, int order);\nvoid StatsGrid_ResetHoldItemsOrder(void);\n\nextern stats_weight_grid_t\t*stats_grid;\t\t\t// The weight grid for all the statistics.\nextern stats_entities_t\t\t*stats_important_ents;\t// A list of \"important\" entities on the map, and counts on what team holds it.\n\n#endif\n"
  },
  {
    "path": "src/sv_ccmds.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n \nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n \nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n \nSee the GNU General Public License for more details.\n \nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n \n\t\n*/\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\ncvar_t\tsv_cheats = {\"sv_cheats\", \"0\"};\nqbool\tsv_allow_cheats = false;\n\nint fp_messages=4, fp_persecond=4, fp_secondsdead=10;\nchar fp_msg[255] = { 0 };\nextern\tcvar_t\t\tsv_logdir; //bliP: 24/7 logdir\nextern\tredirect_t\tsv_redirected;\n\nvoid SV_Localinfo_Set (const char *name, const char *value);\n\n/*\n===============================================================================\n\nOPERATOR CONSOLE ONLY COMMANDS\n\nThese commands can only be entered from stdin or by a remote operator datagram\n===============================================================================\n*/\n\n/*\n==================\nSV_Quit\n==================\n*/\nvoid SV_Quit (qbool restart)\n{\n\tSV_Shutdown (\"Server shutdown.\\n\");\n#ifdef SERVERONLY\n\tSys_Quit (restart);\n#else\n\tHost_Quit(); // will also call SV_Shutdown(), but it is not an issue.\n#endif\n}\n\n/*\n==================\nSV_Quit_f\n==================\n*/\nvoid SV_Quit_f (void)\n{\n\tSV_Quit(false);\n}\n\n/*\n==================\nSV_Restart_f\n==================\n*/\nvoid SV_Restart_f (void)\n{\n\tSV_Quit(true);\n}\n\n/*\n============\nSV_Logfile\n============\n*/\nvoid SV_Logfile (int sv_log, qbool newlog)\n{\n\tint\t\tsv_port = NET_UDPSVPort();\n\tchar\tname[MAX_OSPATH];\n\tint\t\ti;\n\n\t// newlog - stands for: we want open new log file\n\n\tif (logs[sv_log].sv_logfile)\n\t{\n\t\t// turn off logging\n\n\t\tfclose (logs[sv_log].sv_logfile);\n\t\tlogs[sv_log].sv_logfile = NULL;\n\n\t\t// in case of NON \"newlog\" we do some additional work and exit function\n\t\tif (!newlog)\n\t\t{\n\t\t\tCon_Printf (\"%s\", logs[sv_log].message_off);\n\t\t\tlogs[sv_log].log_level = 0;\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// always use new log file for frag log\n\tif (sv_log == FRAG_LOG || sv_log == MOD_FRAG_LOG)\n\t\tnewlog = true;\n\n\tfor (i = 0; i < 1000; i++)\n\t{\n\t\tsnprintf (name, sizeof(name), \"%s/%s%d_%04d.log\", sv_logdir.string, logs[sv_log].file_name, sv_port, i);\n\n\t\tif (!COM_FileExists(name))\n\t\t\tbreak; // file doesn't exist\n\t}\n\n\tif (!newlog) //use last log if possible\n\t\tsnprintf (name, sizeof(name), \"%s/%s%d_%04d.log\",  sv_logdir.string, logs[sv_log].file_name, sv_port, (int)max(0, i - 1));\n\n\tCon_Printf (\"Logging %s to %s\\n\", logs[sv_log].message_on, name);\n\n\tif (!(logs[sv_log].sv_logfile = fopen (name, \"a\")))\n\t{\n\t\tCon_Printf (\"Failed.\\n\");\n\t\tlogs[sv_log].sv_logfile = NULL;\n\t\treturn;\n\t}\n\n\tswitch (sv_log)\n\t{\n\tcase TELNET_LOG:\n\t\tlogs[TELNET_LOG].log_level = Cvar_Value(\"telnet_log_level\");\n\t\tbreak;\n\tcase CONSOLE_LOG:\n\t\tlogs[CONSOLE_LOG].log_level = Cvar_Value(\"qconsole_log_say\");\n\t\tbreak;\n\tdefault:\n\t\tlogs[sv_log].log_level = 1;\n\t}\n}\n\n/*\n============\nSV_Logfile_f\n============\n*/\nvoid SV_Logfile_f (void)\n{\n\tSV_Logfile(CONSOLE_LOG, false);\n}\n\n/*\n============\nSV_ErrorLogfile_f\n============\n*/\nvoid SV_ErrorLogfile_f (void)\n{\n\tSV_Logfile(ERROR_LOG, false);\n}\n\n/*\n============\nSV_RconLogfile_f\n============\n*/\nvoid SV_RconLogfile_f (void)\n{\n\tSV_Logfile(RCON_LOG, false);\n}\n\n/*\n============\nSV_RconLogfile_f\n============\n*/\nvoid SV_TelnetLogfile_f (void)\n{\n\tSV_Logfile(TELNET_LOG, false);\n}\n\n/*\n============\nSV_FragLogfile_f\n============\n*/\nvoid SV_FragLogfile_f (void)\n{\n\tSV_Logfile(FRAG_LOG, false);\n}\n\n//bliP: player log\n/*\n============\nSV_PlayerLogfile_f\n============\n*/\nvoid SV_PlayerLogfile_f (void)\n{\n\tSV_Logfile(PLAYER_LOG, false);\n}\n//<-\n\n/*\n============\nSV_ModFragLogfile_f\n============\n*/\nvoid SV_ModFragLogfile_f (void)\n{\n\tSV_Logfile(MOD_FRAG_LOG, false);\n}\n\nlog_t\tlogs[MAX_LOG] =\n    {\n        {NULL, \"logfile\",        \"qconsole_\", \"File logging off.\\n\",          \"console\",  SV_Logfile_f,        0},\n        {NULL, \"logerrors\",      \"qerror_\",   \"Error logging off.\\n\",         \"errors\",   SV_ErrorLogfile_f,   0},\n        {NULL, \"logrcon\",        \"rcon_\",     \"Rcon logging off.\\n\",          \"rcon\",     SV_RconLogfile_f,    0},\n        {NULL, \"logtelnet\",      \"qtelnet_\",  \"Telnet logging off.\\n\",        \"telnet\",   SV_TelnetLogfile_f,  0},\n        {NULL, \"fraglogfile\",    \"frag_\",     \"Frag file logging off.\\n\",     \"frags\",    SV_FragLogfile_f,    0},\n        {NULL, \"logplayers\",     \"player_\",   \"Player logging off.\\n\",        \"players\",  SV_PlayerLogfile_f,  0},//bliP: player logging\n        {NULL, \"modfraglogfile\", \"modfrag_\",  \"Mod frag file logging off.\\n\", \"modfrags\", SV_ModFragLogfile_f, 0}\n    };\n\n/*\n==================\nSV_SetPlayer\n \nSets sv_client and sv_player to the player with idnum Cmd_Argv(1)\n==================\n*/\nqbool SV_SetPlayer (void)\n{\n\tclient_t\t*cl;\n\tint\t\t\ti;\n\tint\t\t\tidnum;\n\n\tidnum = Q_atoi(Cmd_Argv(1));\n\n\t// HACK: for cheat commands which comes from client rather than from server console\n\tif (sv_client && sv_redirected == RD_CLIENT)\n\t{\n\t\tidnum = sv_client->userid;\n\t}\n\n\tfor (i=0,cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)\n\t{\n\t\tif (!cl->state)\n\t\t\tcontinue;\n\t\tif (cl->userid == idnum)\n\t\t{\n\t\t\tsv_client = cl;\n\t\t\tsv_player = sv_client->edict;\n\t\t\treturn true;\n\t\t}\n\t}\n\tCon_Printf (\"Userid %i is not on the server\\n\", idnum);\n\treturn false;\n}\n\n\n/*\n==================\nSV_God_f\n \nSets client to godmode\n==================\n*/\nvoid SV_God_f (void)\n{\n\tif (!sv_allow_cheats)\n\t{\n\t\tCon_Printf (\"Cheats are not allowed on this server\\n\");\n\t\treturn;\n\t}\n\n\tif (!SV_SetPlayer ())\n\t\treturn;\n\n\tsv_player->v->flags = (int)sv_player->v->flags ^ FL_GODMODE;\n\tif (!((int)sv_player->v->flags & FL_GODMODE) )\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"godmode OFF\\n\");\n\telse\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"godmode ON\\n\");\n}\n\n\nvoid SV_Noclip_f (void)\n{\n\tif (!sv_allow_cheats)\n\t{\n\t\tCon_Printf (\"Cheats are not allowed on this server\\n\");\n\t\treturn;\n\t}\n\n\tif (!SV_SetPlayer ())\n\t\treturn;\n\n\tif (sv_player->v->movetype != MOVETYPE_NOCLIP)\n\t{\n\t\tsv_player->v->movetype = MOVETYPE_NOCLIP;\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"noclip ON\\n\");\n\t}\n\telse\n\t{\n\t\tsv_player->v->movetype = MOVETYPE_WALK;\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"noclip OFF\\n\");\n\t}\n}\n\n\n/*\n==================\nSV_Give_f\n==================\n*/\nvoid SV_Give_f (void)\n{\n\tchar\t*t;\n\tint\t\tv, cnt;\n\n\tif (!sv_allow_cheats)\n\t{\n\t\tCon_Printf (\"Cheats are not allowed on this server\\n\");\n\t\treturn;\n\t}\n\n\tif (!SV_SetPlayer ())\n\t\treturn;\n\n\t// HACK: for cheat commands which comes from client rather than from server console\n\tcnt = (sv_redirected == RD_CLIENT ? 1 : 2);\n\n\tt = Cmd_Argv(cnt++);\n\tv = Q_atoi (Cmd_Argv(cnt++));\n\n\tswitch (t[0])\n\t{\n\tcase '2':\n\tcase '3':\n\tcase '4':\n\tcase '5':\n\tcase '6':\n\tcase '7':\n\tcase '8':\n\tcase '9':\n\t\tsv_player->v->items = (int)sv_player->v->items | IT_SHOTGUN<< (t[0] - '2');\n\t\tbreak;\n\n\tcase 's':\n\t\tsv_player->v->ammo_shells = v;\n\t\tbreak;\n\tcase 'n':\n\t\tsv_player->v->ammo_nails = v;\n\t\tbreak;\n\tcase 'r':\n\t\tsv_player->v->ammo_rockets = v;\n\t\tbreak;\n\tcase 'h':\n\t\tsv_player->v->health = v;\n\t\tbreak;\n\tcase 'c':\n\t\tsv_player->v->ammo_cells = v;\n\t\tbreak;\n\t}\n}\n\nvoid SV_Fly_f (void)\n{\n\tif (!sv_allow_cheats)\n\t{\n\t\tCon_Printf (\"Cheats are not allowed on this server\\n\");\n\t\treturn;\n\t}\n\n\tif (!SV_SetPlayer ())\n\t\treturn;\n\n\tif (sv_player->v->solid != SOLID_SLIDEBOX)\n\t\treturn;\t\t// dead don't fly\n\n\tif (sv_player->v->movetype != MOVETYPE_FLY)\n\t{\n\t\tsv_player->v->movetype = MOVETYPE_FLY;\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"flymode ON\\n\");\n\t}\n\telse\n\t{\n\t\tsv_player->v->movetype = MOVETYPE_WALK;\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"flymode OFF\\n\");\n\t}\n}\n\n/*\n======================\nSV_Map_f\n\nhandle a \nmap <mapname>\ncommand from the console or progs.\n======================\n*/\nvoid SV_Map (qbool now)\n{\n\tstatic char     level[MAX_QPATH];\n\tstatic char     expanded[MAX_QPATH];\n\tstatic char     entityfile[MAX_QPATH];\n\tstatic qbool    changed = false;\n\n\tchar\t*s;\n\n\t// if now, change it\n\tif (now)\n\t{\n\t\tif (!changed)\n\t\t\treturn;\n\n\t\tchanged = false;\n\n\t\tif (!FS_FLocateFile(expanded, FSLFRT_IFFOUND, NULL))\n\t\t{\n\t\t\tSys_Printf (\"Can't find %s\\n\", expanded);\n\t\t\treturn;\n\t\t}\n\n\t\tif (sv.mvdrecording)\n\t\t\tSV_MVDStop_f();\n\n#ifndef SERVERONLY\n\t\tCL_BeginLocalConnection ();\n#endif\n\n\t\t// -> scream\n\t\tif ((int)frag_log_type.value)\n\t\t{\n\t\t\t//bliP: date check ->\n\t\t\tdate_t date;\n\t\t\tSV_TimeOfDay(&date, \"%a %b %d, %H:%M:%S %Y\");\n\t\t\ts = va(\"\\\\newmap\\\\%s\\\\\\\\\\\\\\\\%d-%d-%d %d:%d:%d\\\\\\n\",\n\t\t\t       level,\n\t\t\t       date.year,\n\t\t\t       date.mon+1,\n\t\t\t       //bliP: check me - date.mon or date.mon+1? existing code was date.mon+1\n\t\t\t       date.day,\n\t\t\t       date.hour,\n\t\t\t       date.min,\n\t\t\t       date.sec);\n\t\t\t//<-\n\t\t\tif (logs[FRAG_LOG].sv_logfile)\n\t\t\t\tSZ_Print (&svs.log[svs.logsequence&1], s);\n\t\t\tSV_Write_Log(FRAG_LOG, 0, s);\n\t\t}\n\t\t// <-\n\n\t\tSV_SpawnServer (level, !strcasecmp(Cmd_Argv(0), \"devmap\"), entityfile, false);\n\n#ifdef SERVERONLY\n\t\tSV_BroadcastCommand (\"changing\\n\"\n\t\t                     \"reconnect\\n\");\n\t\tSV_SendMessagesToAll ();\n#else\n\t\tSV_BroadcastCommand (\"reconnect\\n\");\n#endif\n\n\t\treturn;\n\t}\n\n\t// get the map name, but don't change now, could be executed from progs.dat\n\n\tif (Cmd_Argc() < 2 || Cmd_Argc() > 3)\n\t{\n\t\tCon_Printf (\"map <levelname> [<entityfile>] : continue game on a new level\\n\");\n\t\treturn;\n\t}\n\n\tstrlcpy (level, Cmd_Argv(1), MAX_QPATH);\n\n\tmemset(entityfile, 0, sizeof(entityfile));\n\tif (Cmd_Argc() >= 3)\n\t\tstrlcpy (entityfile, Cmd_Argv(2), MAX_QPATH);\n\n\t// check to make sure the level exists\n\tsnprintf (expanded, MAX_QPATH, \"maps/%s.bsp\", level);\n\n\t// Flush FS cache on each map change.\n\tFS_FlushFSHash();\n\n\tif (!FS_FLocateFile(expanded, FSLFRT_IFFOUND, NULL))\n\t{\n\t\tCon_Printf (\"Can't find %s\\n\", expanded);\n\t\treturn;\n\t}\n\n\tchanged = true;\n#ifndef SERVERONLY\n\tif (!com_serveractive) {\n\t\tHost_EndGame();\n\t}\n\tcom_serveractive = true;\n#endif\n}\n\nvoid SV_Map_f (void)\n{\n\tSV_Map(false);\n}\n\n/*==================\nSV_ReplaceChar\nReplace char in string\n==================*/\nvoid SV_ReplaceChar(char *s, char from, char to)\n{\n\tif (s)\n\t\tfor ( ;*s ; ++s)\n\t\t\tif (*s == from)\n\t\t\t\t*s = to;\n}\n//bliP: ls, rm, rmdir, chmod ->\n/*==================\nSV_ListFiles_f\nLists files\n==================*/\nvoid SV_ListFiles_f (void)\n{\n\tdir_t\tdir;\n\tfile_t\t*list;\n\tchar\t*key;\n\tchar\t*dirname;\n\tint\ti;\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tCon_Printf (\"ls <directory> <match>\\n\");\n\t\treturn;\n\t}\n\n\tdirname = Cmd_Argv(1);\n\tSV_ReplaceChar(dirname, '\\\\', '/');\n\n\t// Double-check then move to FS_UnsafeFilename() ?\n\tif (\t!strncmp(dirname, \"../\", 3) || strstr(dirname, \"/../\") || *dirname == '/'\n\t        ||\t( (i = strlen(dirname)) < 3 ? 0 : !strncmp(dirname + i - 3, \"/..\", 4) )\n\t        ||\t!strncmp(dirname, \"..\", 3)\n#ifdef _WIN32\n\t        ||\t( dirname[1] == ':' && ((*dirname >= 'a' && *dirname <= 'z') ||\n\t                                   (*dirname >= 'A' && *dirname <= 'Z'))\n\t           )\n#endif //_WIN32\n\t   )\n\t{\n\t\tCon_Printf(\"Unable to list %s\\n\", dirname);\n\t\treturn;\n\t}\n\n\tCon_Printf(\"Content of %s/*.*\\n\", dirname);\n\tdir = Sys_listdir(va(\"%s\", dirname), \".*\", SORT_BY_NAME);\n\tlist = dir.files;\n\tif (!list->name[0])\n\t{\n\t\tCon_Printf(\"No files\\n\");\n\t\treturn;\n\t}\n\n\tkey = (Cmd_Argc() == 3) ? Cmd_Argv(2) : (char *) \"\";\n\n\t//directories...\n\tfor (; list->name[0]; list++)\n\t{\n\t\tif (!strstr(list->name, key) || !list->isdir)\n\t\t\tcontinue;\n\t\tCon_Printf(\"- %s\\n\", list->name);\n\t}\n\n\tlist = dir.files;\n\n\t//files...\n\tfor (; list->name[0]; list++)\n\t{\n\t\tif (!strstr(list->name, key) || list->isdir)\n\t\t\tcontinue;\n\t\tif ((int)list->size / 1024 > 0)\n\t\t\tCon_Printf(\"%s %.0fKB (%.2fMB)\\n\", list->name,\n\t\t\t           (float)list->size / 1024, (float)list->size / 1024 / 1024);\n\t\telse\n\t\t\tCon_Printf(\"%s %dB\\n\", list->name, list->size);\n\t}\n\tCon_Printf(\"Total: %d files, %.0fKB (%.2fMB)\\n\", dir.numfiles,\n\t           (float)dir.size / 1024, (float)dir.size / 1024 / 1024);\n}\n\n/*==================\nSV_RemoveDirectory_f\nRemoves an empty directory\n==================*/\nvoid SV_RemoveDirectory_f (void)\n{\n\tchar\t*dirname;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCon_Printf(\"rmdir <directory>\\n\");\n\t\treturn;\n\t}\n\n\tdirname = Cmd_Argv(1);\n\tSV_ReplaceChar(dirname, '\\\\', '/');\n\n\tif (\t!strncmp(dirname, \"../\", 3) || strstr(dirname, \"/../\") || *dirname == '/'\n#ifdef _WIN32\n\t        ||\t( dirname[1] == ':' && ((*dirname >= 'a' && *dirname <= 'z') ||\n\t                                   (*dirname >= 'A' && *dirname <= 'Z'))\n\t           )\n#endif //_WIN32\n\t   )\n\t{\n\t\tCon_Printf(\"Unable to remove\\n\");\n\t\treturn;\n\t}\n\n\tif (!Sys_rmdir(dirname))\n\t\tCon_Printf(\"Directory %s successfully removed\\n\", dirname);\n\telse\n\t\tCon_Printf(\"Unable to remove directory %s\\n\", dirname);\n}\n\n/*==================\nSV_RemoveFile_f\nRemove a file\n==================*/\nvoid SV_RemoveFile_f (void)\n{\n\tchar *dirname;\n\tchar *filename;\n\tint i;\n\n\tif (Cmd_Argc() < 3)\n\t{\n\t\tCon_Printf(\"rm <directory> {<filename> | *<token> | *} - removes a file | with token | all\\n\");\n\t\treturn;\n\t}\n\n\tdirname = Cmd_Argv(1);\n\tfilename = Cmd_Argv(2);\n\tSV_ReplaceChar(dirname, '\\\\', '/');\n\tSV_ReplaceChar(filename, '\\\\', '/');\n\n\tif (\t!strncmp(dirname, \"../\", 3) || strstr(dirname, \"/../\")\n\t        ||\t*dirname == '/'             || strchr(filename, '/')\n\t        ||\t( (i = strlen(filename)) < 3 ? 0 : !strncmp(filename + i - 3, \"/..\", 4) )\n#ifdef _WIN32\n\t        ||\t( dirname[1] == ':' && ((*dirname >= 'a' && *dirname <= 'z') ||\n\t                                   (*dirname >= 'A' && *dirname <= 'Z'))\n\t           )\n#endif //_WIN32\n\t   )\n\t{\n\t\tCon_Printf(\"Unable to remove\\n\");\n\t\treturn;\n\t}\n\n\tif (*filename == '*') //token, many files\n\t{\n\t\tdir_t dir;\n\t\tfile_t *list;\n\n\t\t// remove all files with specified token\n\t\tfilename++;\n\n\t\tdir = Sys_listdir(va(\"%s\", dirname), \".*\", SORT_BY_NAME);\n\t\tlist = dir.files;\n\t\tfor (i = 0; list->name[0]; list++)\n\t\t{\n\t\t\tif (!list->isdir && strstr(list->name, filename))\n\t\t\t{\n\t\t\t\tif (!Sys_remove(va(\"%s/%s\", dirname, list->name)))\n\t\t\t\t{\n\t\t\t\t\tCon_Printf(\"Removing %s...\\n\", list->name);\n\t\t\t\t\ti++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (i)\n\t\t\tCon_Printf(\"%d files removed\\n\", i);\n\t\telse\n\t\t\tCon_Printf(\"No matching found\\n\");\n\t}\n\telse // 1 file\n\t{\n\t\tif (!Sys_remove(va(\"%s/%s\", dirname, filename)))\n\t\t\tCon_Printf(\"File %s successfully removed\\n\", filename);\n\t\telse\n\t\t\tCon_Printf(\"Unable to remove file %s\\n\", filename);\n\t}\n\n\t// force cache rebuild.\n\tFS_FlushFSHash();\n}\n\n/*==================\nSV_ChmodFile_f\nChmod a script\n==================*/\n#ifndef _WIN32\nvoid SV_ChmodFile_f (void)\n{\n\tchar\t*_mode, *filename;\n\tunsigned int\tmode, m;\n\n\tif (Cmd_Argc() != 3)\n\t{\n\t\tCon_Printf(\"chmod <mode> <file>\\n\");\n\t\treturn;\n\t}\n\n\t_mode = Cmd_Argv(1);\n\tfilename = Cmd_Argv(2);\n\n\tif (!strncmp(filename, \"../\",  3) || strstr(filename, \"/../\") ||\n\t        *filename == '/'              || strlen(_mode) != 3 ||\n\t        ( (m = strlen(filename)) < 3 ? 0 : !strncmp(filename + m - 3, \"/..\", 4) ))\n\t{\n\t\tCon_Printf(\"Unable to chmod\\n\");\n\t\treturn;\n\t}\n\tfor (mode = 0; *_mode; _mode++)\n\t{\n\t\tm = *_mode - '0';\n\t\tif (m > 7)\n\t\t{\n\t\t\tCon_Printf(\"Unable to chmod\\n\");\n\t\t\treturn;\n\t\t}\n\t\tmode = (mode << 3) + m;\n\t}\n\n\tif (chmod(filename, mode))\n\t\tCon_Printf(\"Unable to chmod %s\\n\", filename);\n\telse\n\t\tCon_Printf(\"Chmod %s successful\\n\", filename);\n}\n#endif //_WIN32\n\n/*==================\nSV_LocalCommand_f\nExecute system command\n==================*/\n//bliP: REMOVE ME REMOVE ME REMOVE ME REMOVE ME REMOVE ME ->\nvoid SV_LocalCommand_f (void)\n{\n\tint i, c;\n\tchar str[1024], *temp_file = \"__output_temp_file__\";\n\n\tif ((c = Cmd_Argc()) < 2)\n\t{\n\t\tCon_Printf(\"localcommand [command]\\n\");\n\t\treturn;\n\t}\n\n\tstr[0] = 0;\n\tfor (i = 1; i < c; i++)\n\t{\n\t\tstrlcat (str, Cmd_Argv(i), sizeof(str));\n\t\tstrlcat (str, \" \", sizeof(str));\n\t}\n\tstrlcat (str, va(\"> %s 2>&1\\n\", temp_file), sizeof(str));\n\n\tif (system(str) == -1)\n\t\tCon_Printf(\"command failed\\n\");\n\telse\n\t{\n\t\tchar\tbuf[512];\n\t\tFILE\t*f;\n\t\tif ((f = fopen(temp_file, \"rt\")) == NULL)\n\t\t\tCon_Printf(\"(empty)\\n\");\n\t\telse\n\t\t{\n\t\t\twhile (!feof(f))\n\t\t\t{\n\t\t\t\tbuf[fread (buf, 1, sizeof(buf) - 1, f)] = 0;\n\t\t\t\tCon_Printf(\"%s\", buf);\n\t\t\t}\n\t\t\tfclose(f);\n\t\t\tif (Sys_remove(temp_file))\n\t\t\t\tCon_Printf(\"Unable to remove file %s\\n\", temp_file);\n\t\t}\n\t}\n\n}\n//REMOVE ME REMOVE ME REMOVE ME REMOVE ME REMOVE ME\n\n/*\n==================\nSV_Kick_f\n \nKick a user off of the server\n==================\n*/\nvoid SV_Kick_f (void)\n{\n\tint\t\t\ti, j;\n\tclient_t\t*cl;\n\tint\t\t\tuid;\n\tint\t\t\tc;\n\tchar\t\treason[80] = \"\";\n\n\tc = Cmd_Argc ();\n\tif (c < 2)\n\t{\n#ifndef SERVERONLY\n\t\t// some mods use a \"kick\" alias for their own needs, sigh\n\t\tif (CL_ClientState() && Cmd_FindAlias(\"kick\"))\n\t\t{\n\t\t\tCmd_ExecuteString (Cmd_AliasString(\"kick\"));\n\t\t\treturn;\n\t\t}\n#endif\n\t\tCon_Printf (\"kick <userid> [reason]\\n\");\n\t\treturn;\n\t}\n\n\tuid = Q_atoi(Cmd_Argv(1));\n\n\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t{\n\t\tif (!cl->state)\n\t\t\tcontinue;\n\t\tif (cl->userid == uid)\n\t\t{\n\t\t\tif (c > 2)\n\t\t\t{\n\t\t\t\tstrlcpy (reason, \" (\", sizeof(reason));\n\t\t\t\tfor (j=2 ; j<c; j++)\n\t\t\t\t{\n\t\t\t\t\tstrlcat (reason, Cmd_Argv(j), sizeof(reason)-4);\n\t\t\t\t\tif (j < c-1)\n\t\t\t\t\t\tstrlcat (reason, \" \", sizeof(reason)-4);\n\t\t\t\t}\n\t\t\t\tif (strlen(reason) < 3)\n\t\t\t\t\treason[0] = '\\0';\n\t\t\t\telse\n\t\t\t\t\tstrlcat (reason, \")\", sizeof(reason));\n\t\t\t}\n\n\t\t\tSV_KickClient(cl, reason);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tCon_Printf (\"Couldn't find user number %i\\n\", uid);\n}\n\nvoid SV_KickClient(client_t* cl, const char* reason)\n{\n\tsv_client_state_t saved_state = cl->state;\n\tcl->state = cs_free; // HACK: don't broadcast to this client\n\tSV_BroadcastPrintf(PRINT_HIGH, \"%s was kicked%s\\n\", cl->name, reason);\n\tcl->state = (sv_client_state_t)saved_state;\n\tSV_ClientPrintf(cl, PRINT_HIGH, \"You were kicked from the game%s\\n\", reason);\n\tSV_LogPlayer(cl, va(\"kick%s\\n\", reason), 1); //bliP: logging\n\tSV_DropClient(cl);\n}\n\n//bliP: mute, cuff ->\nint SV_MatchUser (char *s)\n{\n\tint         i;\n\tclient_t   *cl;\n\n\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t{\n\t\tif (!cl->state)\n\t\t\tcontinue;\n\t\tif (!strcmp (cl->name, s))\n\t\t\treturn cl->userid;\n\t}\n\ti = Q_atoi(s);\n\treturn i;\n}\n\n/*\n================\nSV_Cuff_f\n================\n*/\n#define MAXPENALTY 1200.0\nvoid SV_Cuff_f (void)\n{\n\tint         c, i, uid;\n\tdouble      mins = 0.5;\n\tqbool    done = false;\n\tqbool    print = true;\n\tclient_t    *cl;\n\tchar        reason[80];\n\tchar        text[100];\n\n\tif ((c = Cmd_Argc()) < 2)\n\t{\n\t\tCon_Printf (\"usage: cuff <userid/name> <minutes> [reason]\\n(default = 0.5, 0 = cancel cuff).\\n\");\n\t\treturn;\n\t}\n\n\tuid = SV_MatchUser(Cmd_Argv(1));\n\tif (!uid)\n\t{\n\t\tCon_Printf (\"Couldn't find user %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tif (c >= 3)\n\t{\n\t\tmins = Q_atof(Cmd_Argv(2));\n\t\tif (mins < 0.0 || mins > MAXPENALTY)\n\t\t{\n\t\t\tmins = MAXPENALTY;\n\t\t}\n\t}\n\n\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t{\n\t\tif (!cl->state)\n\t\t\tcontinue;\n\t\tif (cl->userid == uid)\n\t\t{\n\t\t\tcl->cuff_time = curtime + (mins * 60.0);\n\t\t\tdone = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (done)\n\t{\n\t\treason[0] = 0;\n\t\tif (c > 2)\n\t\t{\n\t\t\tfor (i = 3; i < c; i++)\n\t\t\t{\n\t\t\t\tstrlcat (reason, Cmd_Argv(i), sizeof(reason) - 1 - strlen(reason));\n\t\t\t\tif (i < c - 1)\n\t\t\t\t\tstrlcat (reason, \" \", sizeof(reason) - strlen(reason));\n\t\t\t}\n\t\t}\n\n\t\tif (mins)\n\t\t{\n\t\t\tSV_BroadcastPrintf (PRINT_CHAT, \"%s cuffed for %.1f minutes%s%s\\n\", cl->name, mins, reason[0] ? \": \" : \"\", reason[0] ? reason : \"\");\n\n\t\t\tsnprintf(text, sizeof(text), \"You are cuffed for %.1f minutes%s%s\\n\", mins, reason[0] ? \"\\n\\n\" : \"\", reason[0] ? reason : \"\");\n\t\t\tClientReliableWrite_Begin(cl,svc_centerprint, 2+strlen(text));\n\t\t\tClientReliableWrite_String (cl, text);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (print)\n\t\t\t{\n\t\t\t\tSV_BroadcastPrintf (PRINT_CHAT, \"%s un-cuffed.\\n\", cl->name);\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tCon_Printf (\"Couldn't find user %s\\n\", Cmd_Argv(1));\n\t}\n}\n\n/*\n================\nSV_Mute_f\n================\n*/\nvoid SV_Mute_f (void)\n{\n\tint         c, i, uid;\n\tdouble      mins = 0.5;\n\tqbool    done = false;\n\tqbool    print = true;\n\tclient_t    *cl;\n\tchar        reason[1024];\n\tchar        text[1024];\n\tchar        *ptr;\n\n\tif ((c = Cmd_Argc()) < 2)\n\t{\n\t\tCon_Printf (\"usage: mute <userid/name> <minutes> [reason]\\n(default = 0.5, 0 = cancel mute).\\n\");\n\t\treturn;\n\t}\n\n\tuid = SV_MatchUser(Cmd_Argv(1));\n\tif (!uid)\n\t{\n\t\tCon_Printf (\"Couldn't find user %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tif (c >= 3)\n\t{\n\t\tptr = Cmd_Argv(2);\n\t\tif (*ptr == '*')\n\t\t{\n\t\t\tptr++;\n\t\t\tprint = false;\n\t\t}\n\t\tmins = Q_atof(ptr);\n\t\tif (mins < 0.0 || mins > MAXPENALTY)\n\t\t\tmins = MAXPENALTY;\n\t}\n\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t{\n\t\tif (!cl->state)\n\t\t\tcontinue;\n\t\tif (cl->userid == uid)\n\t\t{\n\t\t\tcl->lockedtill = curtime + (mins * 60.0);\n\t\t\tdone = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (done)\n\t{\n\t\treason[0] = 0;\n\t\tif (c > 2)\n\t\t{\n\t\t\tfor (i = 3; i < c; i++)\n\t\t\t{\n\t\t\t\tstrlcat (reason, Cmd_Argv(i), sizeof(reason) - 1 - strlen(reason));\n\t\t\t\tif (i < c - 1)\n\t\t\t\t\tstrlcat (reason, \" \", sizeof(reason) - strlen(reason));\n\t\t\t}\n\t\t}\n\n\t\tif (mins)\n\t\t{\n\t\t\tif (print)\n\t\t\t\tSV_BroadcastPrintf (PRINT_CHAT, \"%s muted for %.1f minutes%s%s\\n\", cl->name, mins, reason[0] ? \": \" : \"\", reason[0] ? reason : \"\");\n\t\t\tsnprintf(text, sizeof(text), \"You are muted for %.1f minutes%s%s\\n\", mins, reason[0] ? \"\\n\\n\" : \"\", reason[0] ? reason : \"\");\n\t\t\tClientReliableWrite_Begin(cl, svc_centerprint, 2+strlen(text));\n\t\t\tClientReliableWrite_String (cl, text);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (print)\n\t\t\t{\n\t\t\t\tSV_BroadcastPrintf (PRINT_CHAT, \"%s un-muted.\\n\", cl->name);\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tCon_Printf (\"Couldn't find user %s\\n\", Cmd_Argv(1));\n\t}\n}\n\nvoid SV_RemovePenalty_f (void)\n{\n\tint     i;\n\tint     num;\n\textern int numpenfilters;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCon_Printf (\"penaltyremove [num]\\n\");\n\t\treturn;\n\t}\n\n\tnum = Q_atoi(Cmd_Argv(1));\n\n\tfor (i = 0; i < numpenfilters; i++)\n\t{\n\t\tif (i == num)\n\t\t{\n\t\t\tSV_RemoveIPFilter (i);\n\t\t\tCon_Printf (\"Removed.\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\tCon_Printf (\"Didn't find penalty filter %i.\\n\", num);\n}\n\nvoid SV_ListPenalty_f (void)\n{\n\tclient_t *cl;\n\tint     i;\n\tchar\t\ts[8];\n\textern int numpenfilters;\n\textern penfilter_t penfilters[MAX_PENFILTERS];\n\n\tCon_Printf (\"Active Penalty List:\\n\");\n\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t{\n\t\tif (!cl->state)\n\t\t\tcontinue;\n\n\t\tif (cl->lockedtill >= curtime)\n\t\t{\n\t\t\tCon_Printf (\"%i %s mute (remaining: %d)\\n\", cl->userid, cl->name, (cl->lockedtill) ? (int)(cl->lockedtill - curtime) : 0);\n\t\t}\n\t\tif (cl->cuff_time >= curtime)\n\t\t{\n\t\t\tCon_Printf (\"%i %s cuff (remaining: %d)\\n\", cl->userid, cl->name, (cl->cuff_time) ? (int)(cl->cuff_time - curtime) : 0);\n\t\t}\n\t}\n\n\tCon_Printf (\"Saved Penalty List:\\n\");\n\tfor (i = 0; i < numpenfilters; i++)\n\t{\n\t\tswitch (penfilters[i].type)\n\t\t{\n\t\tcase ft_mute: strlcpy(s, \"Mute\", sizeof(s)); break;\n\t\tcase ft_cuff: strlcpy(s, \"Cuff\", sizeof(s)); break;\n\t\tdefault: strlcpy(s, \"Unknown\", sizeof(s)); break;\n\t\t}\n\t\tCon_Printf (\"%i: %s for %i.%i.%i.%i (remaining: %d)\\n\", i, s,\n\t\t            penfilters[i].ip[0],\n\t\t            penfilters[i].ip[1],\n\t\t            penfilters[i].ip[2],\n\t\t            penfilters[i].ip[3],\n\t\t            (penfilters[i].time) ? (int)(penfilters[i].time - realtime) : 0);\n\t}\n}\n//<-\n\n/*\n================\nSV_Resolve\n \nresolve IP via DNS lookup\n================\n*/\nchar *SV_Resolve(char *addr)\n{\n#if defined (__linux__) || defined (_WIN32)\n\tunsigned long ip;\n#else\n\tin_addr_t ip;\n#endif\n\n\tstruct hostent *hp;\n\n\tip = inet_addr(addr);\n\tif ((hp = gethostbyaddr((const char *)&ip, sizeof(ip), AF_INET)) != NULL)\n\t\taddr = hp->h_name;\n\treturn addr;\n}\n\n/*\n==================\nSV_Nslookup_f\n==================\n*/\nvoid SV_Nslookup_f (void)\n{\n\tchar\t\t*ip, *name;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCon_Printf (\"Usage: nslookup <IP address>\\n\");\n\t\treturn;\n\t}\n\n\tip = Cmd_Argv(1);\n\tname = SV_Resolve(ip);\n\tif (ip != name)\n\t\tCon_Printf (\"Name:    %s\\nAddress:  %s\\n\", name, ip);\n\telse\n\t\tCon_Printf (\"Couldn't resolve %s\\n\", ip);\n\n}\n\n/*\n================\nSV_Status_f\n================\n*/\nextern cvar_t sv_use_dns;\nvoid SV_Status_f (void)\n{\n\tint i;\n\tclient_t *cl;\n\tfloat cpu, avg, pak, demo1 = 0.0;\n\tchar *s;\n\n\tcpu = (svs.stats.latched_active + svs.stats.latched_idle);\n\n\tif (cpu)\n\t{\n\t\tdemo1 = 100.0 * svs.stats.latched_demo  / cpu;\n\t\tcpu  = 100.0 * svs.stats.latched_active / cpu;\n\t}\n\n\tavg = 1000 * svs.stats.latched_active  / STATFRAMES;\n\tpak = (float)svs.stats.latched_packets / STATFRAMES;\n\n\tCon_Printf (\"net address                 : %s\\n\"\n\t\t\t\t\"cpu utilization (overall)   : %3i%%\\n\"\n\t\t\t\t\"cpu utilization (recording) : %3i%%\\n\"\n\t\t\t\t\"avg response time           : %i ms\\n\"\n\t\t\t\t\"packets/frame               : %5.2f (%d)\\n\",\n\t\t\t\tNET_AdrToString (net_local_sv_ipadr),\n\t\t\t\t(int)cpu,\n\t\t\t\t(int)demo1,\n\t\t\t\t(int)avg,\n\t\t\t\tpak, num_prstr);\n\n\tswitch (sv_redirected)\n\t{\n\t\tcase RD_NONE:\n\t\t\tCon_Printf (\"name             ping frags   id   address                real ip\\n\"\n\t\t\t\t\t\t\"---------------- ---- ----- ------ ---------------------- ---------------\\n\");\n\t\t\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t\t\t{\n\t\t\t\tif (!cl->state)\n\t\t\t\t\tcontinue;\n\t\t\t\ts = NET_BaseAdrToString(cl->netchan.remote_address);\n\t\t\t\tCon_Printf (\"%-16s %4i %5i %6i %-22s \", cl->name, (int)SV_CalcPing(cl),\n\t\t\t\t\t\t(int)cl->edict->v->frags, cl->userid, (int)sv_use_dns.value ? SV_Resolve(s) : s);\n\t\t\t\tif (cl->realip.ip[0])\n\t\t\t\t\tCon_Printf (\"%-15s\", NET_BaseAdrToString (cl->realip));\n\t\t\t\tCon_Printf (cl->spectator ? (char *) \"(s)\" : (char *) \"\");\n\n\t\t\t\tswitch (cl->state)\n\t\t\t\t{\n\t\t\t\t\tcase cs_connected:\n\t\t\t\t\tcase cs_preconnected:\n\t\t\t\t\t\tCon_Printf (\" CONNECTING\\n\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tcase cs_zombie:\n\t\t\t\t\t\tCon_Printf (\" ZOMBIE\\n\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tCon_Printf (\"\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t//case RD_MOD:\n\t\t//case RD_CLIENT:\n\t\t//case RD_PACKET:\n\t\tdefault:\n\t\t\t// most remote clients are 40 columns\n\t\t\t//           01234567890123456789012345678901234567890123456789\n\t\t\tCon_Printf (\"name               ping frags   id\\n\");\n\t\t\tCon_Printf (\"  address\\n\");\n\t\t\tCon_Printf (\"  real ip\\n\");\n\t\t\tCon_Printf (\"------------------ ---- ----- ------\\n\");\n\t\t\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t\t\t{\n\t\t\t\tif (!cl->state)\n\t\t\t\t\tcontinue;\n\n\t\t\t\ts = NET_BaseAdrToString(cl->netchan.remote_address);\n\t\t\t\tCon_Printf (\"%-18s %4i %5i %6s %s\\n%-36s\\n\", cl->name, (int)SV_CalcPing(cl),\n\t\t\t\t\t\t\t(int)cl->edict->v->frags, Q_yelltext((unsigned char*)va(\"%d\", cl->userid)),\n\t\t\t\t\t\t\tcl->spectator ? \" (s)\" : \"\", (int)sv_use_dns.value ? SV_Resolve(s) : s);\n\n\t\t\t\tif (cl->realip.ip[0])\n\t\t\t\t\tCon_Printf (\"%-36s\\n\", NET_BaseAdrToString (cl->realip));\n\n\t\t\t\tswitch (cl->state)\n\t\t\t\t{\n\t\t\t\t\tcase cs_connected:\n\t\t\t\t\tcase cs_preconnected:\n\t\t\t\t\t\tCon_Printf (\"CONNECTING\\n\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tcase cs_zombie:\n\t\t\t\t\t\tCon_Printf (\"ZOMBIE\\n\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tdefault:;\n\t\t\t\t}\n\t\t\t}\n\t} // switch\n\tCon_Printf (\"\\n\");\n}\n\n/*\n==================\nSV_Check_maps_f\n==================\n*/\nvoid SV_Check_maps_f(void)\n{\n\tdir_t d;\n\tfile_t *list;\n\tint i, j, maps_id1;\n\n\td = Sys_listdir(\"id1/maps\", \".bsp$\", SORT_BY_NAME);\n\tlist = d.files;\n\tfor (i = LOCALINFO_MAPS_LIST_START; list->name[0] && i <= LOCALINFO_MAPS_LIST_END; list++, i++)\n\t{\n\t\tlist->name[strlen(list->name) - 4] = 0;\n\t\tif (!list->name[0])\n\t\t\tcontinue;\n\n\t\tSV_Localinfo_Set(va(\"%d\", i), list->name);\n\t}\n\tmaps_id1 = i - 1;\n\n\td = Sys_listdir(\"qw/maps\", \".bsp$\", SORT_BY_NAME);\n\tlist = d.files;\n\tfor (; list->name[0] && i <= LOCALINFO_MAPS_LIST_END; list++, i++)\n\t{\n\t\tlist->name[strlen(list->name) - 4] = 0;\n\t\tif (!list->name[0])\n\t\t\tcontinue;\n\n\t\tfor (j = LOCALINFO_MAPS_LIST_START; j <= maps_id1; j++)\n\t\t\tif (!strncmp(Info_Get(&_localinfo_, va(\"%d\", j)), list->name, MAX_KEY_STRING))\n\t\t\t\tbreak;\n\t\tif (j <= maps_id1)\n\t\t\tcontinue;\n\n\t\tSV_Localinfo_Set(va(\"%d\", i), list->name);\n\t}\n\n\tfor (; i <= LOCALINFO_MAPS_LIST_END; i++)\n\t{\n\t\tSV_Localinfo_Set(va(\"%d\", i), \"\");\n\t}\n}\n\n/*\n==================\nSV_ConSay_f\n==================\n*/\nvoid SV_ConSay_f(void)\n{\n\tclient_t *client;\n\tint\t\tj;\n\tchar\t*p;\n\tchar\ttext[1024] = \"console: \";\n\n\tif (Cmd_Argc () < 2)\n\t\treturn;\n\n\tp = Cmd_Args();\n\n\tif (*p == '\"')\n\t{\n\t\tp++;\n\t\tp[strlen(p)-1] = 0;\n\t}\n\n\tstrlcat(text,    p, sizeof(text));\n\tstrlcat(text, \"\\n\", sizeof(text));\n\n\tfor (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)\n\t{\n\t\tif (client->state != cs_spawned)\n\t\t\tcontinue;\n\t\tSV_ClientPrintf2(client, PRINT_CHAT, \"%s\", text);\n\t}\n\n\tif (sv.mvdrecording)\n\t{\n\t\tif (MVDWrite_Begin (dem_all, 0, strlen(text)+3))\n\t\t{\n\t\t\tMVD_MSG_WriteByte (svc_print);\n\t\t\tMVD_MSG_WriteByte (PRINT_CHAT);\n\t\t\tMVD_MSG_WriteString (text);\n\t\t}\n\t}\n\n\tSys_Printf(\"%s\", text);\n\tSV_Write_Log(CONSOLE_LOG, 1, text);\n}\n\nvoid SV_SendServerInfoChange(char *key, char *value)\n{\n\tif (!sv.state)\n\t\treturn;\n\n\tMSG_WriteByte (&sv.reliable_datagram, svc_serverinfo);\n\tMSG_WriteString (&sv.reliable_datagram, key);\n\tMSG_WriteString (&sv.reliable_datagram, value);\n}\n\n//Cvar system calls this when a CVAR_SERVERINFO cvar changes\nvoid SV_ServerinfoChanged (char *key, char *string)\n{\n\tstring = Cvar_ServerInfoValue(key, string);\n\n\tif (strcmp(string, Info_ValueForKey (svs.info, key))) {\n\t\tInfo_SetValueForKey (svs.info, key, string, MAX_SERVERINFO_STRING);\n\t\tSV_SendServerInfoChange (key, string);\n\t}\n}\n\n/*\n===========\nSV_Serverinfo_f\n\nExamine or change the serverinfo string\n===========\n*/\nvoid SV_Serverinfo_f (void)\n{\n\tcvar_t\t*var;\n\tchar *s;\n\tchar\t*key, *value;\n\n\tif (Cmd_Argc() == 1)\n\t{\n\t\tCon_Printf (\"Server info settings:\\n\");\n\t\tInfo_Print (svs.info);\n\t\tCon_Printf (\"[%d/%d]\\n\", strlen(svs.info), MAX_SERVERINFO_STRING);\n\t\treturn;\n\t}\n\n\t//bliP: sane serverinfo usage (mercury) ->\n\tif (Cmd_Argc() == 2)\n\t{\n\t\ts = Info_ValueForKey(svs.info, Cmd_Argv(1));\n\t\tif (*s)\n\t\t\tCon_Printf (\"Serverinfo %s: \\\"%s\\\"\\n\", Cmd_Argv(1), s);\n\t\telse\n\t\t\tCon_Printf (\"No such key %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\t//<-\n\n\tif (Cmd_Argc() != 3)\n\t{\n\t\tCon_Printf (\"usage: serverinfo [ <key> [ <value> ] ]\\n\");\n\t\treturn;\n\t}\n\n\tkey = Cmd_Argv(1);\n\tvalue = Cmd_Argv(2);\n\n\tif (key[0] == '*')\n\t{\n\t\tCon_Printf (\"Star variables cannot be changed.\\n\");\n\t\treturn;\n\t}\n\n\t// force serverinfo \"0\" vars to be \"\"\n\tif (!strcmp(value, \"0\"))\n\t\tvalue = \"\";\n\n\t// if the key is also a serverinfo cvar, change it too\n\tvar = Cvar_Find(key);\n\tif (var && (var->flags & CVAR_SERVERINFO))\n\t{\n\t\tCvar_Set (var, value); // this call SV_ServerinfoChanged() as well.\n\t}\n\telse\n\t{\n\t\tSV_ServerinfoChanged(key, value);\n\t}\n}\n\nvoid SV_Localinfo_Set (const char *name, const char *value)\n{\n\tchar *old_value;\n\n\tif (!name || !*name)\n\t\treturn;\n\n\tif (!value)\n\t\tvalue = \"\";\n\n\told_value = Info_Get(&_localinfo_, name); // remember old value.\n\tInfo_Set (&_localinfo_, name, value); // set new value.\n\n\tif (mod_localinfoChanged)\n\t{\n\t\tpr_global_struct->time = sv.time;\n\t\tpr_global_struct->self = 0;\n\t\tPR_SetTmpString(&G_INT(OFS_PARM0), name);\n\t\tPR_SetTmpString(&G_INT(OFS_PARM1), old_value);\n\t\tPR_SetTmpString(&G_INT(OFS_PARM2), Info_Get(&_localinfo_, name));\n\t\tPR_ExecuteProgram (mod_localinfoChanged);\n\t}\n}\n\n/*\n===========\nSV_Localinfo_f\n \n  Examine or change the localinfo string\n===========\n*/\nvoid SV_Localinfo_f (void)\n{\n\tif (Cmd_Argc() == 1)\n\t{\n\t\tchar info[MAX_LOCALINFO_STRING];\n\n\t\tCon_Printf (\"Local info settings:\\n\");\n\t\tInfo_ReverseConvert(&_localinfo_, info, sizeof(info));\n\t\tInfo_Print (info);\n\t\tCon_Printf (\"[%d/%d]\\n\", strlen(info), sizeof(info));\n\t\treturn;\n\t}\n\n\t//bliP: sane localinfo usage (mercury) ->\n\tif (Cmd_Argc() == 2)\n\t{\n\t\tchar *s = Info_Get(&_localinfo_, Cmd_Argv(1));\n\n\t\tif (*s)\n\t\t\tCon_Printf (\"Localinfo %s: \\\"%s\\\"\\n\", Cmd_Argv(1), s);\n\t\telse\n\t\t\tCon_Printf (\"No such key %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\t//<-\n\n\tif (Cmd_Argc() != 3)\n\t{\n\t\tCon_Printf (\"usage: localinfo [ <key> <value> ]\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argv(1)[0] == '*')\n\t{\n\t\tCon_Printf (\"Star variables cannot be changed.\\n\");\n\t\treturn;\n\t}\n\n\tSV_Localinfo_Set(Cmd_Argv(1), Cmd_Argv(2));\n}\n\n\n/*\n===========\nSV_User_f\n \nExamine a users info strings\n===========\n*/\nvoid SV_User_f (void)\n{\n\tchar info[MAX_EXT_INFO_STRING];\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCon_Printf (\"Usage: user <userid>\\n\");\n\t\treturn;\n\t}\n\n\tif (!SV_SetPlayer ())\n\t\treturn;\n\n\tInfo_ReverseConvert(&sv_client->_userinfo_ctx_, info, sizeof(info));\n\tInfo_Print(info);\n\tCon_DPrintf (\"[%d/%d]\\n\", strlen(info), sizeof(info));\n}\n\n/*\n================\nSV_Gamedir\n \nSets the fake *gamedir to a different directory.\n================\n*/\nvoid SV_Gamedir (void)\n{\n\tchar\t\t\t*dir;\n\n\tif (Cmd_Argc() == 1)\n\t{\n\t\tCon_Printf (\"Current *gamedir: %s\\n\", Info_ValueForKey (svs.info, \"*gamedir\"));\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCon_Printf (\"Usage: sv_gamedir <newgamedir>\\n\");\n\t\treturn;\n\t}\n\n\tdir = Cmd_Argv(1);\n\n\tif (strstr(dir, \"..\") || strchr(dir, '/')\n\t        || strchr(dir, '\\\\') || strchr(dir, ':') )\n\t{\n\t\tCon_Printf (\"*Gamedir should be a single filename, not a path\\n\");\n\t\treturn;\n\t}\n\n\tInfo_SetValueForStarKey (svs.info, \"*gamedir\", dir, MAX_SERVERINFO_STRING);\n}\n\n/*\n================\nSV_Floodport_f\n \nSets the gamedir and path to a different directory.\n================\n*/\n\nvoid SV_Floodprot_f (void)\n{\n\tint arg1, arg2, arg3;\n\n\tif (Cmd_Argc() == 1)\n\t{\n\t\tif (fp_messages)\n\t\t{\n\t\t\tCon_Printf (\"Current floodprot settings: \\nAfter %d msgs per %d seconds, silence for %d seconds\\n\",\n\t\t\t            fp_messages, fp_persecond, fp_secondsdead);\n\t\t\treturn;\n\t\t}\n\t\telse\n\t\t\tCon_Printf (\"No floodprots enabled.\\n\");\n\t}\n\n\tif (Cmd_Argc() != 4)\n\t{\n\t\tCon_Printf (\"Usage: floodprot <# of messages> <per # of seconds> <seconds to silence>\\n\");\n\t\tCon_Printf (\"Use floodprotmsg to set a custom message to say to the flooder.\\n\");\n\t\treturn;\n\t}\n\n\targ1 = Q_atoi(Cmd_Argv(1));\n\targ2 = Q_atoi(Cmd_Argv(2));\n\targ3 = Q_atoi(Cmd_Argv(3));\n\n\tif (arg1<=0 || arg2 <= 0 || arg3<=0)\n\t{\n\t\tCon_Printf (\"All values must be positive numbers\\n\");\n\t\treturn;\n\t}\n\n\tif (arg1 > 10)\n\t{\n\t\tCon_Printf (\"Can only track up to 10 messages.\\n\");\n\t\treturn;\n\t}\n\n\tfp_messages\t= arg1;\n\tfp_persecond = arg2;\n\tfp_secondsdead = arg3;\n}\n\nvoid SV_Floodprotmsg_f (void)\n{\n\tif (Cmd_Argc() == 1)\n\t{\n\t\tCon_Printf(\"Current msg: %s\\n\", fp_msg);\n\t\treturn;\n\t}\n\telse if (Cmd_Argc() != 2)\n\t{\n\t\tCon_Printf(\"Usage: floodprotmsg \\\"<message>\\\"\\n\");\n\t\treturn;\n\t}\n\tsnprintf(fp_msg, sizeof(fp_msg), \"%s\", Cmd_Argv(1));\n}\n\n/*\n================\nSV_Gamedir_f\n \nSets the gamedir and path to a different directory.\n================\n*/\nvoid SV_Gamedir_f (void)\n{\n\tchar\t\t\t*dir;\n\n\tif (Cmd_Argc() == 1)\n\t{\n\t\tCon_Printf (\"Current gamedir: %s\\n\", fs_gamedir);\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCon_Printf (\"Usage: gamedir <newdir>\\n\");\n\t\treturn;\n\t}\n\n\tdir = Cmd_Argv(1);\n\n\tif (strstr(dir, \"..\") || strchr(dir, '/') || strchr(dir, '\\\\') || strchr(dir, ':') )\n\t{\n\t\tCon_Printf (\"Gamedir should be a single filename, not a path\\n\");\n\t\treturn;\n\t}\n\n#ifndef SERVERONLY\n\tif (CL_ClientState())\n\t{\n\t\tCon_Printf (\"you must disconnect before changing gamedir\\n\");\n\t\treturn;\n\t}\n#endif\n\n\tFS_SetGamedir (dir, false);\n\tInfo_SetValueForStarKey (svs.info, \"*gamedir\", dir, MAX_SERVERINFO_STRING);\n}\n\n/*\n================\nSV_Snap\n================\n*/\nvoid SV_Snap (int uid)\n{\n\tclient_t *cl;\n\tchar pcxname[80];\n\tchar checkname[MAX_OSPATH];\n\tint i;\n\tFILE *f;\n\n\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t{\n\t\tif (cl->state < cs_preconnected)\n\t\t\tcontinue;\n\t\tif (cl->userid == uid)\n\t\t\tbreak;\n\t}\n\tif (i >= MAX_CLIENTS)\n\t{\n\t\tCon_Printf (\"userid not found\\n\");\n\t\treturn;\n\t}\n\n\tFS_CreatePath (va (\"%s/snap/\", fs_gamedir));\n\tsnprintf (pcxname, sizeof (pcxname), \"%d-00.pcx\", uid);\n\n\tfor (i=0 ; i<=99 ; i++)\n\t{\n\t\tpcxname[strlen(pcxname) - 6] = i/10 + '0';\n\t\tpcxname[strlen(pcxname) - 5] = i%10 + '0';\n\t\tsnprintf (checkname, MAX_OSPATH, \"%s/snap/%s\", fs_gamedir, pcxname);\n\t\tf = fopen (checkname, \"rb\");\n\t\tif (!f)\n\t\t\tbreak; // file doesn't exist\n\t\tfclose (f);\n\t}\n\tif (i==100)\n\t{\n\t\tCon_Printf (\"Snap: Couldn't create a file, clean some out.\\n\");\n\t\treturn;\n\t}\n\tstrlcpy(cl->uploadfn, checkname, MAX_QPATH);\n\n\tmemcpy(&cl->snap_from, &net_from, sizeof(net_from));\n\tif (sv_redirected != RD_NONE)\n\t\tcl->remote_snap = true;\n\telse\n\t\tcl->remote_snap = false;\n\n\tClientReliableWrite_Begin (cl, svc_stufftext, 24);\n\tClientReliableWrite_String (cl, \"cmd snap\\n\");\n\tCon_Printf (\"Requesting snap from user %d...\\n\", uid);\n}\n\n/*\n================\nSV_Snap_f\n================\n*/\nvoid SV_Snap_f (void)\n{\n\tint\t\t\tuid;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCon_Printf (\"Usage:  snap <userid>\\n\");\n\t\treturn;\n\t}\n\n\tuid = Q_atoi(Cmd_Argv(1));\n\n\tSV_Snap(uid);\n}\n\n/*\n================\nSV_Snap\n================\n*/\nvoid SV_SnapAll_f (void)\n{\n\tclient_t *cl;\n\tint\t\t\ti;\n\n\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t{\n\t\tif (cl->state < cs_preconnected || cl->spectator)\n\t\t\tcontinue;\n\t\tSV_Snap(cl->userid);\n\t}\n}\n\n// QW262 -->\n/*\n================\nSV_MasterPassword\n================\n*/\nvoid SV_MasterPassword_f (void)\n{\n#ifdef SERVERONLY\n\tif (!host_everything_loaded)\n#else\n\tif (!server_cfg_done)\n#endif\n\t\tstrlcpy(master_rcon_password, Cmd_Argv(1), sizeof(master_rcon_password));\n\telse\n\t\tCon_DPrintf(\"master_rcon_password can be set only in server.cfg\\n\");\n}\n// <-- QW262\n\n/*\n==================\nSV_InitOperatorCommands\n==================\n*/\nvoid SV_InitOperatorCommands (void)\n{\n\tint i;\n\n\tCvar_Register (&sv_cheats);\n\n\tif (SV_CommandLineEnableCheats())\n\t{\n\t\tsv_allow_cheats = true;\n\t\tCvar_SetValue (&sv_cheats, 1);\n\t\tInfo_SetValueForStarKey (svs.info, \"*cheats\", \"ON\", MAX_SERVERINFO_STRING);\n\t}\n\n\tfor (i = MIN_LOG; i < MAX_LOG; ++i)\n\t\tCmd_AddCommand (logs[i].command, logs[i].function);\n\n#ifdef SERVERONLY\n\tCmd_AddCommand (\"nslookup\", SV_Nslookup_f);\n#endif\n\tCmd_AddCommand (\"check_maps\", SV_Check_maps_f);\n\tCmd_AddCommand (\"snap\", SV_Snap_f);\n\tCmd_AddCommand (\"snapall\", SV_SnapAll_f);\n\tCmd_AddCommand (\"kick\", SV_Kick_f);\n\n\t// Add sv_status as client allows 'status' alias to over-ride (ezQuake #532)\n\tCmd_AddCommand (\"status\", SV_Status_f);\n\tCmd_AddCommand (\"sv_status\", SV_Status_f);\n\n#ifdef SERVERONLY\n\t//bliP: init ->\n\tCmd_AddCommand (\"rmdir\", SV_RemoveDirectory_f);\n\tCmd_AddCommand (\"rm\", SV_RemoveFile_f);\n\tCmd_AddCommand (\"ls\", SV_ListFiles_f);\n#endif\n\n\tCmd_AddCommand (\"mute\", SV_Mute_f);\n\tCmd_AddCommand (\"cuff\", SV_Cuff_f);\n\n\tCmd_AddCommand (\"penaltylist\", SV_ListPenalty_f);\n\tCmd_AddCommand (\"penaltyremove\", SV_RemovePenalty_f);\n\n#ifdef SERVERONLY\n#ifndef _WIN32\n\tCmd_AddCommand (\"chmod\", SV_ChmodFile_f);\n#endif //_WIN32\n\t//<-\n\tif (SV_CommandLineEnableLocalCommand())\n\t\tCmd_AddCommand (\"localcommand\", SV_LocalCommand_f);\n#endif\n\n\tCmd_AddCommand (\"map\", SV_Map_f);\n#ifdef SERVERONLY\n\tCmd_AddCommand (\"devmap\", SV_Map_f);\n#else\n\tif (IsDeveloperMode()) {\n\t\tCmd_AddCommand(\"devmap\", SV_Map_f);\n\t}\n#endif\n\tCmd_AddCommand (\"setmaster\", SV_SetMaster_f);\n\n\tCmd_AddCommand (\"heartbeat\", SV_Heartbeat_f);\n\tCmd_AddCommand (\"save\", SV_SaveGame_f); \n\tCmd_AddCommand (\"load\", SV_LoadGame_f); \n\n#ifdef SERVERONLY\n\tCmd_AddCommand (\"say\", SV_ConSay_f);\n\tCmd_AddCommand (\"quit\", SV_Quit_f);\n\tCmd_AddCommand (\"restart\", SV_Restart_f);\n#endif\n\n#ifdef SERVERONLY\n\tCmd_AddCommand (\"god\", SV_God_f);\n\tCmd_AddCommand (\"give\", SV_Give_f);\n\tCmd_AddCommand (\"noclip\", SV_Noclip_f);\n#endif\n\n\tCmd_AddCommand (\"localinfo\", SV_Localinfo_f);\n\n#ifdef SERVERONLY\n\tCmd_AddCommand (\"serverinfo\", SV_Serverinfo_f);\n\tCmd_AddCommand (\"user\", SV_User_f); // FIXME: probably should be done like CL_Serverinfo_f().\n#endif\n\n\tCmd_AddCommand (\"gamedir\", SV_Gamedir_f);\n\tCmd_AddCommand (\"sv_gamedir\", SV_Gamedir);\n\n// I wonder why it registered in host.c in ezquake...\n#ifdef SERVERONLY\n\tCmd_AddCommand (\"floodprot\", SV_Floodprot_f);\n\tCmd_AddCommand (\"floodprotmsg\", SV_Floodprotmsg_f);\n#endif\n\n\tCmd_AddCommand (\"master_rcon_password\", SV_MasterPassword_f);\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_demo.c",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    \n*/\n\n// sv_demo.c - mvd demo related code\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\n// minimal cache which can be used for demos, must be few times greater than DEMO_FLUSH_CACHE_IF_LESS_THAN_THIS\n#define DEMO_CACHE_MIN_SIZE 0x1000000\n\n// flush demo cache if we have less than this free bytes\n#define DEMO_FLUSH_CACHE_IF_LESS_THAN_THIS\t65536\n\n\nstatic void sv_demoDir_OnChange(cvar_t *cvar, char *value, qbool *cancel);\n\ncvar_t  sv_demoUseCache     = {\"sv_demoUseCache\",   \"0\"};\ncvar_t  sv_demoCacheSize    = {\"sv_demoCacheSize\",  \"0\", CVAR_ROM};\ncvar_t  sv_demoMaxDirSize   = {\"sv_demoMaxDirSize\", \"102400\"};\ncvar_t  sv_demoClearOld     = {\"sv_demoClearOld\",   \"0\"};\ncvar_t  sv_demoDir          = {\"sv_demoDir\",        \"demos\", 0, sv_demoDir_OnChange};\ncvar_t  sv_demoDirAlt       = {\"sv_demoDirAlt\",     \"\", 0, sv_demoDir_OnChange };\ncvar_t  sv_demofps          = {\"sv_demofps\",        \"30\"};\ncvar_t  sv_demoIdlefps      = {\"sv_demoIdlefps\",    \"10\"};\ncvar_t  sv_demoPings        = {\"sv_demopings\",      \"3\"};\ncvar_t  sv_demoMaxSize      = {\"sv_demoMaxSize\",    \"20480\"};\ncvar_t  sv_demoExtraNames   = {\"sv_demoExtraNames\", \"0\"};\n\ncvar_t\tsv_demoPrefix\t\t= {\"sv_demoPrefix\",\t\t\"\"};\ncvar_t\tsv_demoSuffix\t\t= {\"sv_demoSuffix\",\t\t\"\"};\ncvar_t\tsv_demotxt\t\t\t= {\"sv_demotxt\",\t\t\"1\"};\ncvar_t\tsv_onrecordfinish\t= {\"sv_onRecordFinish\", \"\"};\n\ncvar_t\tsv_ondemoremove\t\t= {\"sv_onDemoRemove\",\t\"\"};\ncvar_t\tsv_demoRegexp\t\t= {\"sv_demoRegexp\",\t\t\"\\\\.mvd(\\\\.(gz|bz2|rar|zip))?$\"};\n\ncvar_t\tsv_silentrecord\t\t= {\"sv_silentrecord\",   \"0\"};\n\ncvar_t\textralogname\t\t= {\"extralogname\",\t\t\"unset\"}; // no sv_ prefix? WTF!\n\nmvddest_t\t\t\t*singledest;\n\n// only one .. is allowed (security)\nstatic void sv_demoDir_OnChange(cvar_t *cvar, char *value, qbool *cancel)\n{\n\tif (cvar == &sv_demoDir && !value[0]) {\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tif (value[0] == '.' && value[1] == '.') {\n\t\tvalue += 2;\n\t}\n\n\tif (strstr(value, \"/..\")) {\n\t\t*cancel = true;\n\t\treturn;\n\t}\n}\n\n// { MVD writing functions, just wrappers\n\nvoid MVD_MSG_WriteChar (const int c)\n{\n\tMSG_WriteChar (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, c);\n}\n\nvoid MVD_MSG_WriteByte (const int c)\n{\n\tMSG_WriteByte (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, c);\n}\n\nvoid MVD_MSG_WriteShort (const int c)\n{\n\tMSG_WriteShort (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, c);\n}\n\nvoid MVD_MSG_WriteLong (const int c)\n{\n\tMSG_WriteLong (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, c);\n}\n\nvoid MVD_MSG_WriteFloat (const float f)\n{\n\tMSG_WriteFloat (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, f);\n}\n\nvoid MVD_MSG_WriteString (const char *s)\n{\n\tMSG_WriteString (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, s);\n}\n\nvoid MVD_MSG_WriteCoord (const float f)\n{\n\tMSG_WriteCoord (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, f);\n}\n\nvoid MVD_MSG_WriteAngle (const float f)\n{\n\tMSG_WriteAngle (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, f);\n}\n\nvoid MVD_SZ_Write (const void *data, int length)\n{\n\tSZ_Write (&demo.frames[demo.parsecount&UPDATE_MASK]._buf_, data, length);\n}\n\n// }\n\nmvddest_t *DestByName (char *name)\n{\n\tmvddest_t *d;\n\n\tfor (d = demo.dest; d; d = d->nextdest)\n\t\tif (!strncmp(name, d->name, sizeof(d->name)-1))\n\t\t\treturn d;\n\n\treturn NULL;\n}\n\nvoid DestClose (mvddest_t *d, qbool destroyfiles)\n{\n\tchar path[MAX_OSPATH];\n\n\tif (d->cache)\n\t\tQ_free(d->cache);\n\tif (d->file)\n\t\tfclose(d->file);\n\tif (d->socket)\n\t\tclosesocket(d->socket);\n\tif (d->qtvuserlist)\n\t\tQTVsv_FreeUserList(d);\n\n\tif (destroyfiles)\n\t{\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, d->path, d->name);\n\t\tSys_remove(path);\n\t\tstrlcpy(path + strlen(path) - 3, \"txt\", MAX_OSPATH - strlen(path) + 3);\n\t\tSys_remove(path);\n\n\t\t// force cache rebuild.\n\t\tFS_FlushFSHash();\n\t}\n\n\tQ_free(d);\n}\n\n//\n// complete - just force flush for cached dests (dest->desttype == DEST_BUFFEREDFILE)\n//\nvoid DestFlush (qbool complete)\n{\n\tint len;\n\tmvddest_t *d, *t;\n\n\tif (!demo.dest)\n\t\treturn;\n\n\twhile (demo.dest->error)\n\t{\n\t\td = demo.dest;\n\t\tdemo.dest = d->nextdest;\n\n\t\tDestClose(d, false);\n\n\t\tif (!demo.dest)\n\t\t{\n\t\t\tSV_MVDStop(3, false);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tfor (d = demo.dest; d; d = d->nextdest)\n\t{\n\t\tswitch(d->desttype)\n\t\t{\n\t\tcase DEST_FILE:\n\t\t\tfflush (d->file);\n\t\t\tbreak;\n\n\t\tcase DEST_BUFFEREDFILE:\n\t\t\tif (d->cacheused + DEMO_FLUSH_CACHE_IF_LESS_THAN_THIS > d->maxcachesize || complete)\n\t\t\t{\n\t\t\t\tlen = (int)fwrite(d->cache, 1, d->cacheused, d->file);\n\t\t\t\tif (len != d->cacheused)\n\t\t\t\t{\n\t\t\t\t\tSys_Printf(\"DestFlush: fwrite() error\\n\");\n\t\t\t\t\td->error = true;\n\t\t\t\t}\n\t\t\t\tfflush(d->file);\n\n\t\t\t\td->cacheused = 0;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase DEST_STREAM:\n\t\t\tif (d->io_time + qtv_streamtimeout.value <= Sys_DoubleTime())\n\t\t\t{\n\t\t\t\t// problem what send() have internal buffer, so send() success some time even peer side does't read,\n\t\t\t\t// this may take some time before internal buffer overflow and timeout trigger, depends of buffer size.\n\t\t\t\tSys_Printf(\"DestFlush: stream timeout\\n\");\n\t\t\t\td->error = true;\n\t\t\t}\n\n\t\t\tif (d->cacheused && !d->error)\n\t\t\t{\n\t\t\t\tlen = send(d->socket, d->cache, d->cacheused, 0);\n\n\t\t\t\tif (len == 0) //client died\n\t\t\t\t{\n//\t\t\t\t\td->error = true;\n\t\t\t\t\t// man says: The calls return the number of characters sent, or -1 if an error occurred.   \n\t\t\t\t\t// so 0 is legal or what?\n\t\t\t\t}\n\t\t\t\telse if (len > 0) //we put some data through\n\t\t\t\t{ //move up the buffer\n\t\t\t\t\td->cacheused -= len;\n\t\t\t\t\tmemmove(d->cache, d->cache+len, d->cacheused);\n\n\t\t\t\t\td->io_time = Sys_DoubleTime(); // update IO activity\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{ //error of some kind. would block or something\n\t\t\t\t\tif (qerrno != EWOULDBLOCK && qerrno != EAGAIN)\n\t\t\t\t\t{\n\t\t\t\t\t\tSys_Printf(\"DestFlush: error on stream\\n\");\n\t\t\t\t\t\td->error = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase DEST_NONE:\n\t\tdefault:\n\t\t\tSys_Error(\"DestFlush: encountered bad dest.\");\n\t\t}\n\n\t\tif (d->desttype != DEST_STREAM) // no max size for stream\n\t\t{\n\t\t\tif ((unsigned int)sv_demoMaxSize.value && d->totalsize > ((unsigned int)sv_demoMaxSize.value * 1024))\n\t\t\t{\n\t\t\t\tSys_Printf(\"DestFlush: sv_demoMaxSize = %db trigger for dest\\n\", (int)sv_demoMaxSize.value);\n\t\t\t\td->error = true;\n\t\t\t}\n\t\t}\n\n\t\twhile (d->nextdest && d->nextdest->error)\n\t\t{\n\t\t\tt = d->nextdest;\n\t\t\td->nextdest = t->nextdest;\n\n\t\t\tDestClose(t, false);\n\t\t}\n\t}\n}\n\n// if param \"mvdonly\" == true then close only demos, not QTV's steams\nstatic int DestCloseAllFlush (qbool destroyfiles, qbool mvdonly)\n{\n\tint numclosed = 0;\n\tmvddest_t *d, **prev, *next;\n\n\tDestFlush(true); //make sure it's all written.\n\n\tprev = &demo.dest;\n\td = demo.dest;\n\twhile (d)\n\t{\n\t\tnext = d->nextdest;\n\n\t\tif (!mvdonly || d->desttype != DEST_STREAM)\n\t\t{\n\t\t\tdesttype_t dt = d->desttype;\n\t\t\tchar dest_name[sizeof(d->name)];\n\t\t\tchar dest_path[sizeof(d->path)];\n\n\t\t\tstrlcpy(dest_name, d->name, sizeof(dest_name));\n\t\t\tstrlcpy(dest_path, d->path, sizeof(dest_path));\n\n\t\t\t*prev = d->nextdest;\n\t\t\tDestClose(d, destroyfiles); // NOTE: this free dest struck, so we can't use 'd' below\n\t\t\tnumclosed++;\n\n\t\t\tif (dt != DEST_STREAM && dest_name[0]) // ignore stream or empty file name\n\t\t\t\tRun_sv_demotxt_and_sv_onrecordfinish (dest_name, dest_path, destroyfiles);\n\t\t}\n\t\telse\n\t\t\tprev = &d->nextdest;\n\n\t\td = next;\n\t}\n\n\treturn numclosed;\n}\n\nint DemoWriteDest (void *data, int len, mvddest_t *d)\n{\n\tint ret;\n\n\tif (d->error)\n\t\treturn 0;\n\n\td->totalsize += len;\n\n\tswitch(d->desttype)\n\t{\n\t\tcase DEST_FILE:\n\t\t\tret = (int)fwrite(data, 1, len, d->file);\n\t\t\tif (ret != len)\n\t\t\t{\n\t\t\t\tSys_Printf(\"DemoWriteDest: fwrite() error\\n\");\n\t\t\t\td->error = true;\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tbreak;\n\t\tcase DEST_BUFFEREDFILE:\t//these write to a cache, which is flushed later\n\t\tcase DEST_STREAM:\n\t\t\tif (d->cacheused + len > d->maxcachesize)\n\t\t\t{\n\t\t\t\tSys_Printf(\"DemoWriteDest: cache overflow %d > %d\\n\", d->cacheused + len, d->maxcachesize);\n\t\t\t\td->error = true;\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tmemcpy(d->cache + d->cacheused, data, len);\n\t\t\td->cacheused += len;\n\n\t\t\tbreak;\n\t\tcase DEST_NONE:\n\t\tdefault:\n\t\t\tSys_Error(\"DemoWriteDest: encountered bad dest.\");\n\t}\n\treturn len;\n}\n\nstatic void DemoWrite (void *data, int len) //broadcast to all proxies/mvds\n{\n\tmvddest_t *d;\n\tfor (d = demo.dest; d; d = d->nextdest)\n\t{\n\t\tif (singledest && singledest != d)\n\t\t\tcontinue;\n\n\t\tDemoWriteDest(data, len, d);\n\t}\n}\n\n/*\n====================\nMVDWrite_Begin\n====================\n*/\n\nstatic qbool MVDWrite_BeginEx (byte type, int to, int size)\n{\n\tqbool new_mvd_msg;\n\n\tif (!sv.mvdrecording)\n\t\treturn false;\n\n\t// we alredy overflowed, sorry but it no go\n\tif (demo.frames[demo.parsecount&UPDATE_MASK]._buf_.overflowed)\n\t\treturn false; // ERROR\n\n\tif (size > MAX_MVD_SIZE)\n\t{\n\t\tSys_Printf(\"MVDWrite_Begin: bad demo message size: %d > MAX_MVD_SIZE\\n\", size);\n\t\treturn false; // ERROR\n\t}\n\n\t// check we have proper demo message type\n\tswitch (type)\n\t{\n\tcase dem_all: case dem_multiple: case dem_single: case dem_stats:\n\t\tbreak;\n\tdefault:\n\t\tSys_Printf(\"MVDWrite_Begin: bad demo message type: %d\\n\", type);\n\t\treturn false; // ERROR\n\t}\n\n\tnew_mvd_msg =    demo.frames[demo.parsecount&UPDATE_MASK].lasttype != type\n\t\t\t\t  || demo.frames[demo.parsecount&UPDATE_MASK].lastto   != to\n\t\t\t\t  || demo.frames[demo.parsecount&UPDATE_MASK].lastsize + size > MAX_MVD_SIZE;\n\n\tif (new_mvd_msg)\n\t{\n\t\t// we need add mvd header for next message since \"type\" or \"to\" differ from previous message,\n\t\t// or it first message in this frame.\n\n\t\tMVD_MSG_WriteByte(0); // 0 milliseconds\n\n\t\tdemo.frames[demo.parsecount&UPDATE_MASK].lasttype = type;\n\t\tdemo.frames[demo.parsecount&UPDATE_MASK].lastto   = to;\n\t\tdemo.frames[demo.parsecount&UPDATE_MASK].lastsize = size; // set initial size\n\n\t\tswitch (type)\n\t\t{\n\t\tcase dem_all:\n\t\t\tMVD_MSG_WriteByte(dem_all);\n\t\t\tbreak;\n\t\tcase dem_multiple:\n\t\t\tMVD_MSG_WriteByte(dem_multiple);\n\t\t\tMVD_MSG_WriteLong(to);\n\t\t\tbreak;\n\t\tcase dem_single:\n\t\tcase dem_stats:\n\t\t\tMVD_MSG_WriteByte(type | (to << 3)); // msg \"type\" and \"to\" incapsulated in one byte\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn false; // ERROR: wrong type\n\t\t}\n\n\t\tMVD_MSG_WriteLong(size); // msg size\n\n\t\tif (demo.frames[demo.parsecount&UPDATE_MASK]._buf_.overflowed)\n\t\t\treturn false; // ERROR: overflow\n\n\t\t// THIS IS TRICKY, SORRY.\n\t\t// remember size offset, so latter we can access it\n\t\tdemo.frames[demo.parsecount&UPDATE_MASK].lastsize_offset = demo.frames[demo.parsecount&UPDATE_MASK]._buf_.cursize - 4;\n\t}\n\telse\n\t{\n\t\t// we must alredy have mvd header here of previous message, so just change size in header\n\t\tbyte *buf;\n\t\tint lastsize;\n\n\t\tdemo.frames[demo.parsecount&UPDATE_MASK].lastsize += size;\n\n\t\t// THIS IS TRICKY, SORRY.\n\t\t// and here we use size offset\n\t\tlastsize = demo.frames[demo.parsecount&UPDATE_MASK].lastsize;\n\n\t\tbuf = demo.frames[demo.parsecount&UPDATE_MASK]._buf_.data + demo.frames[demo.parsecount&UPDATE_MASK].lastsize_offset;\n\n\t\tbuf[0] = lastsize&0xff;\n\t\tbuf[1] = (lastsize>> 8)&0xff;\n\t\tbuf[2] = (lastsize>>16)&0xff;\n\t\tbuf[3] = (lastsize>>24)&0xff;\n\t}\n\n\treturn (sv.mvdrecording ? true : false);\n}\n\nqbool MVDWrite_Begin (byte type, int to, int size)\n{\n\tif (!sv.mvdrecording)\n\t\treturn false;\n\n\tif (!MVDWrite_BeginEx(type, to, size))\n\t{\n\t\tCon_DPrintf(\"MVDWrite_Begin: error\\n\");\n\n\t\tif (sv.mvdrecording)\n\t\t\tSV_MVDStop(4, false); // stop all mvd recording\n\n\t\treturn false;\n\t}\n\n\treturn (sv.mvdrecording ? true : false);\n}\n\nqbool MVDWrite_HiddenBlockBegin(int length)\n{\n\treturn MVDWrite_Begin(dem_multiple, 0, length);\n}\n\nqbool MVDWrite_HiddenBlock(const void* data, int length)\n{\n\tif (MVDWrite_HiddenBlockBegin(length)) {\n\t\tMVD_SZ_Write(data, length);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/*\n====================\nMVD_FrameDeltaTime\n\nGet frame time mark (delta between two demo frames).\n\nAlso advance demo.prevtime.\n\n====================\n*/\nstatic byte MVD_FrameDeltaTime (double time1)\n{\n\tint\tmsec;\n\n\tif (!sv.mvdrecording)\n\t\treturn 0;\n\n\tmsec = (time1 - demo.prevtime) * 1000;\n\tdemo.prevtime += 0.001 * msec;\n\n\tif (msec > 255)\n\t\tmsec = 255;\n\tif (msec < 2)\n\t\tmsec = 0; // uh, why 0 but not 2? \n\n\treturn (byte)msec;\n}\n\n/*\n====================\nMVD_WriteMessage\n====================\n*/\nstatic qbool MVD_WriteMessage (sizebuf_t *msg, byte msec)\n{\n\tint\t\tlen;\n\tbyte\tc;\n\n\tif (!sv.mvdrecording)\n\t\treturn false;\n\n\tif (msg && msg->overflowed)\n\t\treturn false; // ERROR\n\n\tc = msec;\n\tDemoWrite(&c, sizeof(c));\n\n\tc = dem_all;\n\tDemoWrite (&c, sizeof(c));\n\n\tif (msg && msg->cursize)\n\t{\n\t\tlen = LittleLong (msg->cursize);\n\t\tDemoWrite (&len, 4);\n\t\tDemoWrite (msg->data, msg->cursize);\n\t}\n\telse\n\t{\n\t\tlen = LittleLong (0);\n\t\tDemoWrite (&len, 4);\n\t}\n\n\treturn (sv.mvdrecording ? true : false);\n}\n\nstatic void SV_MVDWritePausedTimeToStreams(demo_frame_t* frame)\n{\n\t// When writing out a paused frame, send a packet to let QTV keep delay in sync\n\tif (frame->paused) {\n\t\tmvddest_t* d;\n\t\tsizebuf_t msg;\n\t\tbyte msg_buffer[128];\n\t\tbyte duration = frame->pause_duration;\n\n\t\tSZ_Init(&msg, msg_buffer, sizeof(msg_buffer));\n\t\tMSG_WriteByte(&msg, 0);                                           //     0: duration == 0, for demos\n\t\tMSG_WriteByte(&msg, dem_multiple);                                //     1: target of the packet\n\t\tMSG_WriteLong(&msg, 0);                                           //  2- 5: 0 ... demo_multiple(0) => hidden packet\n\t\tMSG_WriteLong(&msg, LittleLong(sizeof(short) + sizeof(byte)));    //  6-10: length = <short> + <byte>\n\t\tMSG_WriteShort(&msg, LittleShort(mvdhidden_paused_duration));     // 11-12: tell QTV how much time has really passed\n\t\tMSG_WriteByte(&msg, duration);                                    //    13: true ms value, as demo packets will have 0\n\n\t\tfor (d = demo.dest; d; d = d->nextdest) {\n\t\t\tif (d->desttype == DEST_STREAM) {\n\t\t\t\tDemoWriteDest(msg.data, msg.cursize, d);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n====================\nSV_MVDWritePackets\n\nInterpolates to get exact players position for current frame\nand writes packets to the disk/memory\n====================\n*/\nstatic qbool SV_MVDWritePacketsEx (int num)\n{\n\tdemo_frame_t\t*frame, *nextframe;\n\tdemo_client_t\t*cl, *nextcl = NULL, *last_cl;\n\tint\t\t\t\ti, j, flags;\n\tqbool\t\t\tvalid;\n\tdouble\t\t\ttime1, playertime, nexttime;\n\tvec3_t\t\t\torigin, angles;\n\tsizebuf_t\t\tmsg;\n\tbyte\t\t\tmsg_buf[MAX_MVD_SIZE]; // data without mvd header\n\tbyte\t\t\tmsec;\n\n\tif (!sv.mvdrecording)\n\t\treturn false;\n\n\t// allow overflow, but cancel demo recording in case of overflow\n\tSZ_InitEx(&msg, msg_buf, sizeof(msg_buf), true);\n\n\tif (num > demo.parsecount - demo.lastwritten + 1)\n\t\tnum = demo.parsecount - demo.lastwritten + 1;\n\n\t// 'num' frames to write\n\tfor ( ; num; num--, demo.lastwritten++)\n\t{\n\t\tSZ_Clear(&msg);\n\n\t\tframe = &demo.frames[demo.lastwritten&UPDATE_MASK];\n\t\ttime1 = frame->time;\n\t\tnextframe = frame;\n\n\t\tSV_MVDWritePausedTimeToStreams(frame);\n\n\t\t// find two frames\n\t\t// one before the exact time (time - msec) and one after,\n\t\t// then we can interpolte exact position for current frame\n\t\tfor (i = 0, cl = frame->clients, last_cl = demo.clients; i < MAX_CLIENTS; i++, cl++, last_cl++)\n\t\t{\n\t\t\tif (cl->parsecount != demo.lastwritten)\n\t\t\t\tcontinue; // not valid\n\n\t\t\tif (!cl->parsecount && svs.clients[i].state != cs_spawned)\n\t\t\t\tcontinue; // not valid, occur on first frame\n\n\t\t\tnexttime = playertime = time1 - cl->sec;\n\n\t\t\tvalid = false;\n\n\t\t\tfor (j = demo.lastwritten+1; nexttime < time1 && j < demo.parsecount; j++)\n\t\t\t{\n\t\t\t\tnextframe = &demo.frames[j&UPDATE_MASK];\n\t\t\t\tnextcl = &nextframe->clients[i];\n\n\t\t\t\tif (nextcl->parsecount != j)\n\t\t\t\t\tbreak; // disconnected?\n\t\t\t\tif (nextcl->fixangle)\n\t\t\t\t\tbreak; // respawned, or walked into teleport, do not interpolate!\n\t\t\t\tif (!(nextcl->flags & DF_DEAD) && (cl->flags & DF_DEAD))\n\t\t\t\t\tbreak; // respawned, do not interpolate\n\n\t\t\t\tnexttime = nextframe->time - nextcl->sec;\n\n\t\t\t\tif (nexttime >= time1)\n\t\t\t\t{\n\t\t\t\t\t// good, found what we were looking for\n\t\t\t\t\tvalid = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (valid)\n\t\t\t{\n\t\t\t\tfloat f = 0;\n\t\t\t\tfloat z = nexttime - playertime;\n\n\t\t\t\tif ( z )\n\t\t\t\t\tf = (time1 - nexttime) / z;\n\n\t\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\t{\n\t\t\t\t\tangles[j] = AdjustAngle(cl->angles[j], nextcl->angles[j], 1.0 + f);\n\t\t\t\t\torigin[j] = nextcl->origin[j] + f * (nextcl->origin[j] - cl->origin[j]);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorCopy(cl->origin, origin);\n\t\t\t\tVectorCopy(cl->angles, angles);\n\t\t\t}\n\n\t\t\t// now write it to buf\n\t\t\tflags = cl->flags;\n\n\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\tif (origin[j] != last_cl->origin[j])\n\t\t\t\t\tflags |= DF_ORIGIN << j;\n\n\t\t\tfor (j = 0; j < 3; j++)\n\t\t\t\tif (angles[j] != last_cl->angles[j])\n\t\t\t\t\tflags |= DF_ANGLES << j;\n\n\t\t\tif (cl->model != last_cl->model)\n\t\t\t\tflags |= DF_MODEL;\n\t\t\tif (cl->effects != last_cl->effects)\n\t\t\t\tflags |= DF_EFFECTS;\n\t\t\tif (cl->skinnum != last_cl->skinnum)\n\t\t\t\tflags |= DF_SKINNUM;\n\t\t\tif (cl->weaponframe != last_cl->weaponframe)\n\t\t\t\tflags |= DF_WEAPONFRAME;\n\n\t\t\tMSG_WriteByte (&msg, svc_playerinfo);\n\t\t\tMSG_WriteByte (&msg, i);\n\t\t\tMSG_WriteShort (&msg, flags);\n\n\t\t\tMSG_WriteByte (&msg, cl->frame);\n\n\t\t\tfor (j = 0 ; j < 3 ; j++)\n\t\t\t\tif (flags & (DF_ORIGIN << j))\n\t\t\t\t\tMSG_WriteCoord (&msg, origin[j]);\n\n\t\t\tfor (j = 0 ; j < 3 ; j++)\n\t\t\t\tif (flags & (DF_ANGLES << j))\n\t\t\t\t\tMSG_WriteAngle16 (&msg, angles[j]);\n\n\t\t\tif (flags & DF_MODEL)\n\t\t\t\tMSG_WriteByte (&msg, cl->model);\n\n\t\t\tif (flags & DF_SKINNUM)\n\t\t\t\tMSG_WriteByte (&msg, cl->skinnum);\n\n\t\t\tif (flags & DF_EFFECTS)\n\t\t\t\tMSG_WriteByte (&msg, cl->effects);\n\n\t\t\tif (flags & DF_WEAPONFRAME)\n\t\t\t\tMSG_WriteByte (&msg, cl->weaponframe);\n\n\t\t\tcl->flags = flags;\n\n\t\t\t// save in last_cl what we wrote to msg so later we can delta from it\n\t\t\t*last_cl = *cl; // struct copy\n\t\t}\n\n\t\t// get frame time mark (delta between two frames)\n\t\tmsec = MVD_FrameDeltaTime(time1);\n\n\t\tif (demo.frames[demo.lastwritten&UPDATE_MASK]._buf_.overflowed)\n\t\t{\n\t\t\tCon_DPrintf(\"SV_MVDWritePackets: error: in _buf_.overflowed\\n\");\n\t\t\treturn false; // ERROR\n\t\t}\n\n\t\t// write cumulative data from different sources\n\t\tif (demo.frames[demo.lastwritten&UPDATE_MASK]._buf_.cursize)\n\t\t{\n\t\t\t// well, first byte must be milliseconds, set it then\n\t\t\tdemo.frames[demo.lastwritten&UPDATE_MASK]._buf_.data[0] = msec;\n\t\t\tDemoWrite(demo.frames[demo.lastwritten&UPDATE_MASK]._buf_.data, demo.frames[demo.lastwritten&UPDATE_MASK]._buf_.cursize);\n\n\t\t\tmsec = 0; // That matter, we wrote time mark, so next data(if any) in this frame follow with zero milliseconds offset, since it same frame.\n\t\t\t\t\t  // NOTE: demo.frames[demo.lastwritten&UPDATE_MASK]._buf_.cursize possible to be zero, in this case we wrote msec below...\n\t\t}\n\n\t\t// Write data about players, if we have data.\n\t\t// also if we does't have data and did't wrote msec above, we wrote it here, even packet will be empty, this will break fuh,\n\t\t// but I think I correct, also it doubtfull what we did't wrote msec above.\n\t\tif (msg.cursize || msec)\n\t\t{\n\t\t\tif (!MVD_WriteMessage(&msg, msec))\n\t\t\t{\n\t\t\t\tCon_DPrintf(\"SV_MVDWritePackets: error: in msg\\n\");\n\t\t\t\treturn false; // ERROR\n\t\t\t}\n\t\t}\n\n\t\t// { reset frame for future usage\n\t\tSZ_Clear(&demo.frames[demo.lastwritten&UPDATE_MASK]._buf_);\n\n\t\tdemo.frames[demo.lastwritten&UPDATE_MASK].lastto = 0;\n\t\tdemo.frames[demo.lastwritten&UPDATE_MASK].lasttype = 0;\n\t\tdemo.frames[demo.lastwritten&UPDATE_MASK].lastsize = 0;\n\t\tdemo.frames[demo.lastwritten&UPDATE_MASK].lastsize_offset = 0;\n\t\t// }\n\n\t\tif (!sv.mvdrecording)\n\t\t{\n\t\t\tCon_DPrintf(\"SV_MVDWritePackets: error: in sv.mvdrecording\\n\");\n\t\t\treturn false; // ERROR\n\t\t}\n\t}\n\n\tif (!sv.mvdrecording)\n\t\treturn false; // ERROR\n\n\tif (demo.lastwritten > demo.parsecount)\n\t\tdemo.lastwritten = demo.parsecount;\n\n\treturn true;\n}\n\nqbool SV_MVDWritePackets (int num)\n{\n\tif (!sv.mvdrecording)\n\t\treturn false;\n\n\tif (!SV_MVDWritePacketsEx(num))\n\t{\n\t\tCon_DPrintf(\"SV_MVDWritePackets: error\\n\");\n\n\t\tif (sv.mvdrecording)\n\t\t\tSV_MVDStop(4, false); // stop all mvd recording\n\n\t\treturn false;\n\t}\n\n\treturn (sv.mvdrecording ? true : false);\n}\n\n/*\n====================\nSV_InitRecord\n====================\n*/\nstatic mvddest_t *SV_InitRecordFile (char *name)\n{\n\tchar *s;\n\tmvddest_t *dst;\n\tFILE *file;\n\n\tchar path[MAX_OSPATH];\n\n\tCon_DPrintf(\"SV_InitRecordFile: Demo name: \\\"%s\\\"\\n\", name);\n\tfile = fopen (name, \"wb\");\n\tif (!file)\n\t{\n\t\tCon_Printf (\"ERROR: couldn't open \\\"%s\\\"\\n\", name);\n\t\treturn NULL;\n\t}\n\n\tdst = (mvddest_t*) Q_malloc (sizeof(mvddest_t));\n\n\tif (!(int)sv_demoUseCache.value)\n\t{\n\t\tdst->desttype = DEST_FILE;\n\t\tdst->file = file;\n\t\tdst->maxcachesize = 0;\n\t}\n\telse\n\t{\n\t\tdst->desttype = DEST_BUFFEREDFILE;\n\t\tdst->file = file;\n\t\tdst->maxcachesize = 1024 * (int) sv_demoCacheSize.value;\n\t\tdst->cache = (char *) Q_malloc (dst->maxcachesize);\n\t}\n\n\ts = name + strlen(name);\n\twhile (*s != '/') s--;\n\tstrlcpy(dst->name, s+1, sizeof(dst->name));\n\tstrlcpy(dst->path, sv_demoDir.string, sizeof(dst->path));\n\n\tif ( !sv_silentrecord.value )\n\t\tSV_BroadcastPrintf (PRINT_CHAT, \"Server starts recording (%s):\\n%s\\n\",\n\t\t                    (dst->desttype == DEST_BUFFEREDFILE) ? \"memory\" : \"disk\", s+1);\n\tCvar_SetROM(&serverdemo, dst->name);\n\n\tstrlcpy(path, name, MAX_OSPATH);\n\tstrlcpy(path + strlen(path) - 3, \"txt\", MAX_OSPATH - strlen(path) + 3);\n\n\tif ((int)sv_demotxt.value)\n\t{\n\t\tFILE *f;\n\t\tchar *text;\n\n\t\tif (sv_demotxt.value == 2)\n\t\t{\n\t\t\tif ((f = fopen (path, \"a+t\")))\n\t\t\t\tfclose(f); // at least made empty file\n\t\t}\n\t\telse if ((f = fopen (path, \"w+t\")))\n\t\t{\n\t\t\ttext = SV_PrintTeams();\n\t\t\tfwrite(text, strlen(text), 1, f);\n\t\t\tfflush(f);\n\t\t\tfclose(f);\n\t\t}\n\t}\n\telse\n\t\tSys_remove(path);\n\n\t// force cache rebuild.\n\tFS_FlushFSHash();\n\n\treturn dst;\n}\n\nstatic void SV_AddLastDemo(void)\n{\n\tchar *name = NULL;\n\tmvddest_t *d;\n\n\tfor (d = demo.dest; d; d = d->nextdest)\n\t{\n\t\tif (d->desttype != DEST_STREAM && d->name[0])\n\t\t{\n\t\t\tname = d->name;\n\t\t\tbreak; // we found file dest with non empty name, use it as last demo name\n\t\t}\n\t}\n\n\tif (name && name[0])\n\t{\n\t\tsize_t name_len = strlen(name) + 1;\n\n\t\tdemo.lastdemospos = (demo.lastdemospos + 1) & 0xF;\n\t\tQ_free(demo.lastdemosname[demo.lastdemospos]);\n\t\tdemo.lastdemosname[demo.lastdemospos] = (char *) Q_malloc(name_len);\n\t\tstrlcpy(demo.lastdemosname[demo.lastdemospos], name, name_len);\n\t\tCon_DPrintf(\"SV_MVDStop: Demo name for 'cmd dl .': \\\"%s\\\"\\n\", demo.lastdemosname[demo.lastdemospos]);\n\t}\n}\n\n/*\n====================\nSV_MVDStop\n\nstop recording a demo\n====================\n*/\nvoid SV_MVDStop (int reason, qbool mvdonly)\n{\n\tstatic qbool instop = false;\n\n\tint numclosed;\n\n\tif (instop)\n\t{\n\t\tCon_Printf(\"SV_MVDStop: recursion\\n\");\n\t\treturn;\n\t}\n\n\tif (!sv.mvdrecording)\n\t{\n\t\tCon_Printf (\"Not recording a demo.\\n\");\n\t\treturn;\n\t}\n\n\tinstop = true; // SET TO TRUE, DON'T FORGET SET TO FALSE ON RETURN\n\n\tif (reason == 2 || reason == 3 || reason == 4)\n\t{\n\t\tif (reason == 4)\n\t\t\tmvdonly = false; // if serious error, then close all dests including qtv streams\n\n\t\t// stop and remove\n\t\tDestCloseAllFlush(true, mvdonly);\n\n\t\tif (!demo.dest)\n\t\t\tsv.mvdrecording = false;\n\n\t\tif (reason == 4)\n\t\t\tSV_BroadcastPrintf (PRINT_CHAT, \"Error in MVD/QTV recording, recording stopped\\n\");\n\t\telse if (reason == 3)\n\t\t\tSV_BroadcastPrintf (PRINT_CHAT, \"QTV disconnected\\n\");\n\t\telse\n\t\t{\n\t\t\tif ( !sv_silentrecord.value )\n\t\t\t\tSV_BroadcastPrintf (PRINT_CHAT, \"Server recording canceled, demo removed\\n\");\n\t\t}\n\n\t\tCvar_SetROM(&serverdemo, \"\");\n\n\t\tinstop = false; // SET TO FALSE\n\n\t\treturn;\n\t}\n\t\n\t// write a disconnect message to the demo file\n\t// FIXME: qqshka: add clearup to be sure message will fit!!!\n\n\tif (MVDWrite_Begin(dem_all, 0, 2+strlen(\"EndOfDemo\")))\n\t{\n\t\tMVD_MSG_WriteByte (svc_disconnect);\n\t\tMVD_MSG_WriteString (\"EndOfDemo\");\n\t}\n\n\t// finish up\n\tSV_MVDWritePackets(demo.parsecount - demo.lastwritten + 1);\n\n\t// last recorded demo's names for command \"cmd dl . ..\" (maximum 15 dots)\n\tSV_AddLastDemo();\n\n\tnumclosed = DestCloseAllFlush(false, mvdonly);\n\n\tif (!demo.dest)\n\t\tsv.mvdrecording = false;\n\n\tif (numclosed)\n\t{\n\t\tif (!reason)\n\t\t{\n\t\t\tif ( !sv_silentrecord.value )\n\t\t\t\tSV_BroadcastPrintf (PRINT_CHAT, \"Server recording completed\\n\");\n\t\t}\n\t\telse\n\t\t\tSV_BroadcastPrintf (PRINT_CHAT, \"Server recording stopped\\nMax demo size exceeded\\n\");\n\t}\n\n\tCvar_SetROM(&serverdemo, \"\");\n\n\tinstop = false; // SET TO FALSE\n}\n\n/*\n====================\nSV_MVDStop_f\n====================\n*/\nvoid SV_MVDStop_f (void)\n{\n\tSV_MVDStop(0, true);\n}\n\n/*\n====================\nSV_MVD_Cancel_f\n\nStops recording, and removes the demo\n====================\n*/\nvoid SV_MVD_Cancel_f (void)\n{\n\tSV_MVDStop(2, true);\n}\n\n/*\n====================\nSV_WriteRecordMVDMessage\n====================\n*/\nstatic void SV_WriteRecordMVDMessage (sizebuf_t *msg)\n{\n\tint len;\n\tbyte c;\n\n\tif (!sv.mvdrecording)\n\t\treturn;\n\n\tif (!msg->cursize)\n\t\treturn;\n\n\tc = 0;\n\tDemoWrite (&c, sizeof(c));\n\n\tc = dem_all;\n\tDemoWrite (&c, sizeof(c));\n\n\tlen = LittleLong (msg->cursize);\n\tDemoWrite (&len, 4);\n\n\tDemoWrite (msg->data, msg->cursize);\n}\n\n/*\n====================\nSV_WriteRecordMVDStatsMessage\n====================\n*/\nstatic void SV_WriteRecordMVDStatsMessage (sizebuf_t *msg, int client)\n{\n\tint len;\n\tbyte c;\n\n\tif (!sv.mvdrecording)\n\t\treturn;\n\n\tif (!msg->cursize)\n\t\treturn;\n\n\tif (client < 0 || client >= MAX_CLIENTS)\n\t\treturn;\n\n\tc = 0;\n\tDemoWrite (&c, sizeof(c));\n\n\tc = dem_stats | (client << 3) ; // msg \"type\" and \"to\" incapsulated in one byte\n\tDemoWrite (&c, sizeof(c));\n\n\tlen = LittleLong (msg->cursize);\n\tDemoWrite (&len, 4);\n\n\tDemoWrite (msg->data, msg->cursize);\n}\n\n\n/*\nstatic void SV_WriteSetMVDMessage (void)\n{\n\tint len;\n\tbyte c;\n\n\tif (!sv.mvdrecording)\n\t\treturn;\n\n\tc = 0;\n\tDemoWrite (&c, sizeof(c));\n\n\tc = dem_set;\n\tDemoWrite (&c, sizeof(c));\n\n\n\tlen = LittleLong(0);\n\tDemoWrite (&len, 4);\n\tlen = LittleLong(0);\n\tDemoWrite (&len, 4);\n}\n*/\n\n// mvd/qtv related stuff\n// Well, here is a chance what player connect after demo recording started,\n// so demo.info[edictnum - 1].model == player_model so SV_MVDWritePackets() will not wrote player model index,\n// so client during playback this demo will got invisible model, because model index will be 0.\n// Fixing that.\n// Btw, struct demo contain different client specific structs, may be they need clearing too, not sure.\nvoid MVD_PlayerReset(int player)\n{\n\tif (player < 0 || player >= MAX_CLIENTS) { // protect from lamers\n\t\tCon_Printf(\"MVD_PlayerReset: wrong player num %d\\n\", player);\n\t\treturn;\n\t}\n\n\tmemset(&(demo.clients[player]), 0, sizeof(demo.clients[0]));\n}\n\nqbool SV_MVD_Record (mvddest_t *dest, qbool mapchange)\n{\n\tint i;\n\n\tif (mapchange)\n\t{\n\t\tif (dest) // during mapchange dest must be NULL\n\t\t\treturn false;\n\t}\n\telse\n\t{\n\t\tif (!dest) // in non mapchange dest must be not NULL\n\t\t\treturn false;\n\t}\n\n\t// If we initialize MVD recording, then reset some data structs\n\tif (!sv.mvdrecording)\n\t{\n\t\t// this is either mapchange and we have QTV connected\n\t\t// or we just use /record or whatever command first time and here no recording yet\n\n    \t// and here we memset() not whole demo_t struct, but part,\n    \t// so demo.dest and demo.pendingdest is not overwriten\n\t\tmemset(&demo, 0, (int)((uintptr_t)&(((demo_t *)0)->mem_set_point)));\n\n\t\tfor (i = 0; i < UPDATE_BACKUP; i++)\n\t\t{\n\t\t\t// set up buffer for record in each frame\n\t\t\tSZ_InitEx(&demo.frames[i]._buf_, demo.frames[i]._buf__data, sizeof(demo.frames[0]._buf__data), true);\n\t\t}\n\n\t\t// set up buffer for non releable data\n\t\tSZ_InitEx(&demo.datagram, demo.datagram_data, sizeof(demo.datagram_data), true);\n\t}\n\n\tif (mapchange)\n\t{\n\t\t//\n\t\t// map change, sent initial stats to all dests\n\t\t//\n\t\tSV_MVD_SendInitialGamestate(NULL);\n\t}\n\telse\n\t{\n\t\t//\n\t\t// seems we initializing new dest, sent initial stats only to this dest\n\t\t//\n\t\tdest->nextdest = demo.dest;\n\t\tdemo.dest = dest;\n\n\t\tSV_MVD_SendInitialGamestate(dest);\n\t}\n\n\t// done\n\treturn true;\n}\n\nvoid SV_MVD_SendInitialGamestate(mvddest_t* dest)\n{\n\tsizebuf_t\tbuf;\n\tunsigned char buf_data[MAX_MSGLEN];\n\tunsigned int n;\n\tchar* s, info[MAX_EXT_INFO_STRING];\n\n\tclient_t* player;\n\tedict_t* ent;\n\tchar* gamedir;\n\tint i;\n\n\tif (!demo.dest)\n\t\treturn;\n\n\tsv.mvdrecording = true; // NOTE:  afaik set to false on map change, so restore it here\n\tdemo.pingtime = demo.time = sv.time;\n\tsingledest = dest;\n\n\t/*-------------------------------------------------*/\n\n\t// serverdata\n\t// send the info about the new client to all connected clients\n\tSZ_Init(&buf, buf_data, sizeof(buf_data));\n\n\t// send the serverdata\n\n\tgamedir = Info_ValueForKey(svs.info, \"*gamedir\");\n\tif (!gamedir[0])\n\t\tgamedir = \"qw\";\n\n\tMSG_WriteByte(&buf, svc_serverdata);\n\n#ifdef FTE_PEXT_256PACKETENTITIES\n\tdemo.recorder.fteprotocolextensions |= FTE_PEXT_256PACKETENTITIES;\n#endif\n#ifdef FTE_PEXT_MODELDBL\n\tdemo.recorder.fteprotocolextensions |= FTE_PEXT_MODELDBL;\n#endif\n#ifdef FTE_PEXT_ENTITYDBL\n\tdemo.recorder.fteprotocolextensions |= FTE_PEXT_ENTITYDBL;\n#endif\n#ifdef FTE_PEXT_ENTITYDBL2\n\tdemo.recorder.fteprotocolextensions |= FTE_PEXT_ENTITYDBL2;\n#endif\n#ifdef FTE_PEXT_SPAWNSTATIC2\n\tdemo.recorder.fteprotocolextensions |= FTE_PEXT_SPAWNSTATIC2;\n#endif\n#ifdef FTE_PEXT_TRANS\n\tdemo.recorder.fteprotocolextensions |= FTE_PEXT_TRANS;\n#endif\n#ifdef FTE_PEXT_COLOURMOD\n\tdemo.recorder.fteprotocolextensions |= FTE_PEXT_COLOURMOD;\n#endif\n#ifdef FTE_PEXT2_VOICECHAT\n\tdemo.recorder.fteprotocolextensions2 |= FTE_PEXT2_VOICECHAT;\n#endif\n\n#ifdef FTE_PEXT_FLOATCOORDS\n\t//fix up extensions to match sv_bigcoords correctly. sorry for old clients not working.\n\tif (msg_coordsize == 4)\n\t\tdemo.recorder.fteprotocolextensions |= FTE_PEXT_FLOATCOORDS;\n\telse\n\t\tdemo.recorder.fteprotocolextensions &= ~FTE_PEXT_FLOATCOORDS;\n#endif\n\n#ifdef PROTOCOL_VERSION_FTE\n\tif (demo.recorder.fteprotocolextensions)\n\t{\n\t\tMSG_WriteLong(&buf, PROTOCOL_VERSION_FTE);\n\t\tMSG_WriteLong(&buf, demo.recorder.fteprotocolextensions);\n\t}\n#endif\n\n#ifdef PROTOCOL_VERSION_FTE2\n\tif (demo.recorder.fteprotocolextensions2)\n\t{\n\t\tMSG_WriteLong(&buf, PROTOCOL_VERSION_FTE2);\n\t\tMSG_WriteLong(&buf, demo.recorder.fteprotocolextensions2);\n\t}\n#endif\n\n#ifdef PROTOCOL_VERSION_MVD1\n\tdemo.recorder.mvdprotocolextensions1 |= MVD_PEXT1_HIDDEN_MESSAGES;\n\tif (demo.recorder.mvdprotocolextensions1)\n\t{\n\t\tMSG_WriteLong(&buf, PROTOCOL_VERSION_MVD1);\n\t\tMSG_WriteLong(&buf, demo.recorder.mvdprotocolextensions1);\n\t}\n#endif\n\n\tMSG_WriteLong (&buf, PROTOCOL_VERSION);\n\tMSG_WriteLong (&buf, svs.spawncount);\n\tMSG_WriteString (&buf, gamedir);\n\n\n\tMSG_WriteFloat (&buf, sv.time);\n\n\t// send full levelname\n\tMSG_WriteString (&buf, PR_GetEntityString(sv.edicts->v->message));\n\n\t// send the movevars\n\tMSG_WriteFloat(&buf, movevars.gravity);\n\tMSG_WriteFloat(&buf, movevars.stopspeed);\n\tMSG_WriteFloat(&buf, movevars.maxspeed);\n\tMSG_WriteFloat(&buf, movevars.spectatormaxspeed);\n\tMSG_WriteFloat(&buf, movevars.accelerate);\n\tMSG_WriteFloat(&buf, movevars.airaccelerate);\n\tMSG_WriteFloat(&buf, movevars.wateraccelerate);\n\tMSG_WriteFloat(&buf, movevars.friction);\n\tMSG_WriteFloat(&buf, movevars.waterfriction);\n\tMSG_WriteFloat(&buf, movevars.entgravity);\n\n\t// send music\n\tMSG_WriteByte (&buf, svc_cdtrack);\n\tMSG_WriteByte (&buf, 0); // none in demos\n\n\t// send server info string\n\tMSG_WriteByte (&buf, svc_stufftext);\n\tMSG_WriteString (&buf, va(\"fullserverinfo \\\"%s\\\"\\n\", svs.info) );\n\n\t// flush packet\n\tSV_WriteRecordMVDMessage (&buf);\n\tSZ_Clear (&buf);\n\n\t// soundlist\n\tMSG_WriteByte (&buf, svc_soundlist);\n\tMSG_WriteByte (&buf, 0);\n\n\tn = 0;\n\ts = sv.sound_precache[n+1];\n\twhile (s)\n\t{\n\t\tMSG_WriteString (&buf, s);\n\t\tif (buf.cursize > MAX_MSGLEN/2)\n\t\t{\n\t\t\tMSG_WriteByte (&buf, 0);\n\t\t\tMSG_WriteByte (&buf, n);\n\t\t\tSV_WriteRecordMVDMessage (&buf);\n\t\t\tSZ_Clear (&buf);\n\t\t\tMSG_WriteByte (&buf, svc_soundlist);\n\t\t\tMSG_WriteByte (&buf, n + 1);\n\t\t}\n\t\tn++;\n\t\ts = sv.sound_precache[n+1];\n\t}\n\n\tif (buf.cursize)\n\t{\n\t\tMSG_WriteByte (&buf, 0);\n\t\tMSG_WriteByte (&buf, 0);\n\t\tSV_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t// modellist\n\tMSG_WriteByte (&buf, svc_modellist);\n\tMSG_WriteByte (&buf, 0);\n\n\tn = 0;\n\ts = sv.model_precache[n+1];\n\twhile (s)\n\t{\n\t\tMSG_WriteString (&buf, s);\n\t\tif (buf.cursize > MAX_MSGLEN/2 && n & 0xff)\n\t\t{\n\t\t\t// partial flush as long as not at a zero boundary\n\t\t\tMSG_WriteByte (&buf, 0);\n\t\t\tMSG_WriteByte (&buf, n);\n\t\t\tSV_WriteRecordMVDMessage (&buf);\n\t\t\tSZ_Clear (&buf);\n\t\t\tif (n + 1 > 0xff)\n\t\t\t{\n\t\t\t\tMSG_WriteByte (&buf, svc_fte_modellistshort);\n\t\t\t\tMSG_WriteShort (&buf, n + 1);\n\t\t\t} else\n\t\t\t{\n\t\t\t\tMSG_WriteByte (&buf, svc_modellist);\n\t\t\t\tMSG_WriteByte (&buf, n + 1);\n\t\t\t}\n\t\t}\n\t\tn++;\n\t\ts = sv.model_precache[n+1];\n\t}\n\n\tif (buf.cursize)\n\t{\n\t\tMSG_WriteByte (&buf, 0);\n\t\tMSG_WriteByte (&buf, 0);\n\t\tSV_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t// static entities\n\t{\n\t\tint i, j;\n\t\tentity_state_t from = { 0 };\n\n\t\tfor (i = 0; i < sv.static_entity_count; ++i) {\n\t\t\tentity_state_t* s = &sv.static_entities[i];\n\n\t\t\tif (buf.cursize >= MAX_MSGLEN/2) {\n\t\t\t\tSV_WriteRecordMVDMessage (&buf);\n\t\t\t\tSZ_Clear (&buf);\n\t\t\t}\n\n\t\t\tif (demo.recorder.fteprotocolextensions & FTE_PEXT_SPAWNSTATIC2) {\n\t\t\t\tMSG_WriteByte(&buf, svc_fte_spawnstatic2);\n\t\t\t\tSV_WriteDelta(&demo.recorder, &from, s, &buf, true);\n\t\t\t}\n\t\t\telse if (s->modelindex < 256) {\n\t\t\t\tMSG_WriteByte(&buf, svc_spawnstatic);\n\t\t\t\tMSG_WriteByte(&buf, s->modelindex);\n\t\t\t\tMSG_WriteByte(&buf, s->frame);\n\t\t\t\tMSG_WriteByte(&buf, s->colormap);\n\t\t\t\tMSG_WriteByte(&buf, s->skinnum);\n\t\t\t\tfor (j = 0; j < 3; ++j) {\n\t\t\t\t\tMSG_WriteCoord(&buf, s->origin[j]);\n\t\t\t\t\tMSG_WriteAngle(&buf, s->angles[j]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// entity baselines\n\t{\n\t\tstatic entity_state_t empty_baseline = { 0 };\n\t\tint i, j;\n\n\t\tfor (i = 0; i < sv.num_baseline_edicts; ++i) {\n\t\t\tedict_t* svent = EDICT_NUM(i);\n\t\t\tentity_state_t* s = &svent->e.baseline;\n\n\t\t\tif (buf.cursize >= MAX_MSGLEN/2) {\n\t\t\t\tSV_WriteRecordMVDMessage (&buf);\n\t\t\t\tSZ_Clear (&buf);\n\t\t\t}\n\n\t\t\tif (!s->number || !s->modelindex || !memcmp(s, &empty_baseline, sizeof(empty_baseline))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (demo.recorder.fteprotocolextensions & FTE_PEXT_SPAWNSTATIC2) {\n\t\t\t\tMSG_WriteByte(&buf, svc_fte_spawnbaseline2);\n\t\t\t\tSV_WriteDelta(&demo.recorder, &empty_baseline, s, &buf, true);\n\t\t\t}\n\t\t\telse if (s->modelindex < 256) {\n\t\t\t\tMSG_WriteByte(&buf, svc_spawnbaseline);\n\t\t\t\tMSG_WriteShort(&buf, i);\n\t\t\t\tMSG_WriteByte(&buf, s->modelindex);\n\t\t\t\tMSG_WriteByte(&buf, s->frame);\n\t\t\t\tMSG_WriteByte(&buf, s->colormap);\n\t\t\t\tMSG_WriteByte(&buf, s->skinnum);\n\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\tMSG_WriteCoord(&buf, s->origin[j]);\n\t\t\t\t\tMSG_WriteAngle(&buf, s->angles[j]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// prespawn\n\tfor (n = 0; n < sv.num_signon_buffers; n++)\n\t{\n\t\tif (buf.cursize+sv.signon_buffer_size[n] > MAX_MSGLEN/2)\n\t\t{\n\t\t\tSV_WriteRecordMVDMessage (&buf);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t\tSZ_Write (&buf,\n\t\t          sv.signon_buffers[n],\n\t\t          sv.signon_buffer_size[n]);\n\t}\n\n\tif (buf.cursize > MAX_MSGLEN/2)\n\t{\n\t\tSV_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\tMSG_WriteByte (&buf, svc_stufftext);\n\tMSG_WriteString (&buf, va(\"cmd spawn %i 0\\n\",svs.spawncount) );\n\n\tif (buf.cursize)\n\t{\n\t\tSV_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t// send current status of all other players\n\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tplayer = svs.clients + i;\n\n\t\t// there spectators NOT ignored, since this info required, at least userinfo\n\n\t\tMSG_WriteByte (&buf, svc_updatefrags);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteShort (&buf, player->old_frags);\n\n\t\tMSG_WriteByte (&buf, svc_updateping);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteShort (&buf, SV_CalcPing(player));\n\n\t\tMSG_WriteByte (&buf, svc_updatepl);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteByte (&buf, player->lossage);\n\n\t\tMSG_WriteByte (&buf, svc_updateentertime);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteFloat (&buf, SV_ClientGameTime(player));\n\n\t\tInfo_ReverseConvert(&player->_userinfoshort_ctx_, info, sizeof(info));\n\t\tInfo_RemovePrefixedKeys (info, '_');\t// server passwords, etc\n\n\t\tMSG_WriteByte (&buf, svc_updateuserinfo);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteLong (&buf, player->userid);\n\t\tMSG_WriteString (&buf, info);\n\n\t\tif (buf.cursize > MAX_MSGLEN/2)\n\t\t{\n\t\t\tSV_WriteRecordMVDMessage (&buf);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\tif (buf.cursize)\n\t{\n\t\tSV_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t// this set proper model origin and angles etc for players\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tvec3_t origin, angles;\n\t\tint j, flags;\n\n\t\tplayer = svs.clients + i;\n\t\tent = player->edict;\n\n\t\tif (player->state != cs_spawned)\n\t\t\tcontinue;\n\n\t\tif (player->spectator)\n\t\t\tcontinue; // ignore specs\n\n\t\tflags =   (DF_ORIGIN << 0) | (DF_ORIGIN << 1) | (DF_ORIGIN << 2)\n\t\t\t\t| (DF_ANGLES << 0) | (DF_ANGLES << 1) | (DF_ANGLES << 2)\n\t\t\t\t| DF_EFFECTS | DF_SKINNUM \n\t\t\t\t| (ent->v->health <= 0 ? DF_DEAD : 0)\n\t\t\t\t| (ent->v->mins[2] != -24 ? DF_GIB : 0)\n\t\t\t\t| DF_WEAPONFRAME | DF_MODEL;\n\n\t\tVectorCopy(ent->v->origin, origin);\n\t\tVectorCopy(ent->v->angles, angles);\n\t\tangles[0] *= -3;\n#ifdef USE_PR2\n\t\tif( player->isBot )\n\t\t\tVectorCopy(ent->v->v_angle, angles);\n#endif\n\t\tangles[2] = 0; // no roll angle\n\n\t\tif (ent->v->health <= 0)\n\t\t{\t// don't show the corpse looking around...\n\t\t\tangles[0] = 0;\n\t\t\tangles[1] = ent->v->angles[1];\n\t\t\tangles[2] = 0;\n\t\t}\n\n\t\tMSG_WriteByte (&buf, svc_playerinfo);\n\t\tMSG_WriteByte (&buf, i);\n\t\tMSG_WriteShort (&buf, flags);\n\n\t\tMSG_WriteByte (&buf, ent->v->frame);\n\n\t\tfor (j = 0 ; j < 3 ; j++)\n\t\t\tif (flags & (DF_ORIGIN << j))\n\t\t\t\tMSG_WriteCoord (&buf, origin[j]);\n\n\t\tfor (j = 0 ; j < 3 ; j++)\n\t\t\tif (flags & (DF_ANGLES << j))\n\t\t\t\tMSG_WriteAngle16 (&buf, angles[j]);\n\n\t\tif (flags & DF_MODEL)\n\t\t\tMSG_WriteByte (&buf, ent->v->modelindex);\n\n\t\tif (flags & DF_SKINNUM)\n\t\t\tMSG_WriteByte (&buf, ent->v->skin);\n\n\t\tif (flags & DF_EFFECTS)\n\t\t\tMSG_WriteByte (&buf, ent->v->effects);\n\n\t\tif (flags & DF_WEAPONFRAME)\n\t\t\tMSG_WriteByte (&buf, ent->v->weaponframe);\n\n\t\tif (buf.cursize > MAX_MSGLEN/2)\n\t\t{\n\t\t\tSV_WriteRecordMVDMessage (&buf);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\t// we really need clear buffer before sending stats\n\tif (buf.cursize)\n\t{\n\t\tSV_WriteRecordMVDMessage (&buf);\n\t\tSZ_Clear (&buf);\n\t}\n\n\t// send stats\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tint\t\tstats[MAX_CL_STATS];\n\t\tint\t\tj;\n\n\t\tplayer = svs.clients + i;\n\t\tent = player->edict;\n\n\t\tif (player->state != cs_spawned)\n\t\t\tcontinue;\n\n\t\tif (player->spectator)\n\t\t\tcontinue;\n\n\t\tmemset(stats, 0, sizeof(stats));\n\n\t\tstats[STAT_HEALTH]       = ent->v->health;\n\t\tstats[STAT_WEAPON]       = SV_ModelIndex(PR_GetEntityString(ent->v->weaponmodel));\n\t\tstats[STAT_AMMO]         = ent->v->currentammo;\n\t\tstats[STAT_ARMOR]        = ent->v->armorvalue;\n\t\tstats[STAT_SHELLS]       = ent->v->ammo_shells;\n\t\tstats[STAT_NAILS]        = ent->v->ammo_nails;\n\t\tstats[STAT_ROCKETS]      = ent->v->ammo_rockets;\n\t\tstats[STAT_CELLS]        = ent->v->ammo_cells;\n\t\tstats[STAT_ACTIVEWEAPON] = ent->v->weapon;\n\n\t\tif (ent->v->health > 0) // viewheight for PF_DEAD & PF_GIB is hardwired\n\t\t\tstats[STAT_VIEWHEIGHT] = ent->v->view_ofs[2];\n\n\t\t// stuff the sigil bits into the high bits of items for sbar\n\t\tstats[STAT_ITEMS] = (int) ent->v->items | ((int) PR_GLOBAL(serverflags) << 28);\n\n\t\tfor (j = 0; j < MAX_CL_STATS; j++)\n\t\t{\n\t\t\tif (stats[j] >= 0 && stats[j] <= 255)\n\t\t\t{\n\t\t\t\tMSG_WriteByte(&buf, svc_updatestat);\n\t\t\t\tMSG_WriteByte(&buf, j);\n\t\t\t\tMSG_WriteByte(&buf, stats[j]);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMSG_WriteByte(&buf, svc_updatestatlong);\n\t\t\t\tMSG_WriteByte(&buf, j);\n\t\t\t\tMSG_WriteLong(&buf, stats[j]);\n\t\t\t}\n\t\t}\n\n\t\tif (buf.cursize)\n\t\t{\n\t\t\tSV_WriteRecordMVDStatsMessage(&buf, i);\n\t\t\tSZ_Clear (&buf);\n\t\t}\n\t}\n\n\t// above stats writing must clear buffer\n\tif (buf.cursize)\n\t{\n\t\tSys_Error(\"SV_MVD_SendInitialGamestate: buf.cursize %d\", buf.cursize);\n\t}\n\n\t// send all current light styles\n\tfor (i=0 ; i<MAX_LIGHTSTYLES ; i++)\n\t{\n\t\tMSG_WriteByte (&buf, svc_lightstyle);\n\t\tMSG_WriteByte (&buf, (char)i);\n\t\tMSG_WriteString (&buf, sv.lightstyles[i]);\n\t}\n\n\t// get the client to check and download skins\n\t// when that is completed, a begin command will be issued\n\tMSG_WriteByte (&buf, svc_stufftext);\n\tMSG_WriteString (&buf, \"skins\\n\");\n\n\tSV_WriteRecordMVDMessage (&buf);\n\n//\tSV_WriteSetMVDMessage();\n\n\tsingledest = NULL;\n}\n\n/*\n====================\nSV_MVD_Record_f\n\nrecord <demoname>\n====================\n*/\nvoid SV_MVD_Record_f (void)\n{\n\tint c;\n\tchar name[MAX_OSPATH+MAX_DEMO_NAME];\n\tchar newname[MAX_DEMO_NAME];\n\n\tc = Cmd_Argc();\n\tif (c != 2)\n\t{\n\t\tCon_Printf (\"record <demoname>\\n\");\n\t\treturn;\n\t}\n\n\tif (sv.state != ss_active)\n\t{\n\t\tCon_Printf (\"Not active yet.\\n\");\n\t\treturn;\n\t}\n\n\t// clear old demos\n\tif (!SV_DirSizeCheck())\n\t\treturn;\n\n\tstrlcpy(newname, va(\"%s%s%s\", sv_demoPrefix.string, SV_CleanName((unsigned char*)Cmd_Argv(1)),\n\t\t\t\t\t\tsv_demoSuffix.string), sizeof(newname) - 4);\n\n\tSys_mkdir(va(\"%s/%s\", fs_gamedir, sv_demoDir.string));\n\n\tsnprintf (name, sizeof(name), \"%s/%s/%s\", fs_gamedir, sv_demoDir.string, newname);\n\n\tif ((c = strlen(name)) > 3)\n\t\tif (strcmp(name + c - 4, \".mvd\"))\n\t\t\tstrlcat(name, \".mvd\", sizeof(name));\n\n\t//\n\t// open the demo file and start recording\n\t//\n\tSV_MVD_Record (SV_InitRecordFile(name), false);\n}\n\n/*\n====================\nSV_MVDEasyRecord_f\n\neasyrecord [demoname]\n====================\n*/\n\nvoid SV_MVDEasyRecord_f (void)\n{\n\tint\t\tc;\n\tchar\tname[MAX_DEMO_NAME];\n\tchar\tname2[MAX_OSPATH*7]; // scream\n\tchar\tname4[MAX_OSPATH*7]; // scream\n\n\tint\t\ti;\n\tdir_t\tdir;\n\tchar\t*name3;\n\n\tc = Cmd_Argc();\n\tif (c > 2)\n\t{\n\t\tCon_Printf (\"easyrecord [demoname]\\n\");\n\t\treturn;\n\t}\n\n\tif (!SV_DirSizeCheck()) // clear old demos\n\t\treturn;\n\n\tif (c == 2)\n\t\tstrlcpy (name, Cmd_Argv(1), sizeof(name));\n\telse\n\t{\n\t\ti = Dem_CountPlayers();\n\t\tif ((int)teamplay.value >= 1 && i > 2)\n\t\t{\n\t\t\t// Teamplay\n\t\t\tsnprintf (name, sizeof(name), \"%don%d_\", Dem_CountTeamPlayers(Dem_Team(1)), Dem_CountTeamPlayers(Dem_Team(2)));\n\t\t\tif ((int)sv_demoExtraNames.value > 0)\n\t\t\t{\n\t\t\t\tstrlcat (name, va(\"[%s]_%s_vs_[%s]_%s_%s\",\n\t\t\t\t                  Dem_Team(1), Dem_PlayerNameTeam(Dem_Team(1)),\n\t\t\t\t                  Dem_Team(2), Dem_PlayerNameTeam(Dem_Team(2)),\n\t\t\t\t                  sv.mapname), sizeof(name));\n\t\t\t}\n\t\t\telse\n\t\t\t\tstrlcat (name, va(\"%s_vs_%s_%s\", Dem_Team(1), Dem_Team(2), sv.mapname), sizeof(name));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (i == 2)\n\t\t\t{\n\t\t\t\t// Duel\n\t\t\t\tsnprintf (name, sizeof(name), \"duel_%s_vs_%s_%s\",\n\t\t\t\t          Dem_PlayerName(1),\n\t\t\t\t          Dem_PlayerName(2),\n\t\t\t\t          sv.mapname);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// FFA\n\t\t\t\tsnprintf (name, sizeof(name), \"ffa_%s(%d)\", sv.mapname, i);\n\t\t\t}\n\t\t}\n\t}\n\n\t// <-\n\n\t// Make sure the filename doesn't contain illegal characters\n\tstrlcpy(name, va(\"%s%s%s\", sv_demoPrefix.string,\n\t\t\t SV_CleanName((unsigned char*)name), sv_demoSuffix.string), MAX_DEMO_NAME);\n\tstrlcpy(name2, name, sizeof(name2));\n\tSys_mkdir(va(\"%s/%s\", fs_gamedir, sv_demoDir.string));\n\n\t// FIXME: very SLOW\n\tif (!(name3 = quote(name2)))\n\t\treturn;\n\tdir = Sys_listdir(va(\"%s/%s\", fs_gamedir, sv_demoDir.string),\n\t\t\t\t\t  va(\"^%s%s\", name3, sv_demoRegexp.string), SORT_NO);\n\tQ_free(name3);\n\tfor (i = 1; dir.numfiles; )\n\t{\n\t\tsnprintf(name2, sizeof(name2), \"%s_%02i\", name, i++);\n\t\tif (!(name3 = quote(name2)))\n\t\t\treturn;\n\t\tdir = Sys_listdir(va(\"%s/%s\", fs_gamedir, sv_demoDir.string),\n\t\t\t\t\t\t  va(\"^%s%s\", name3, sv_demoRegexp.string), SORT_NO);\n\t\tQ_free(name3);\n\t}\n\n\tstrlcpy(name4, name2, sizeof(name4));\n\tsnprintf(name2, sizeof(name2), \"%s\", va(\"%s/%s/%s.mvd\", fs_gamedir, sv_demoDir.string, name2));\n\tsnprintf(name4, sizeof(name4), \"%s\", va(\"%s/%s.xml\", sv_demoDir.string, name4));\n\tCvar_Set(&extralogname, name4);\n\n\tSV_MVD_Record (SV_InitRecordFile(name2), false);\n}\n\n//============================================================\n\nstatic void MVD_Init (void)\n{\n\tint p, size = DEMO_CACHE_MIN_SIZE;\n\n\tmemset(&demo, 0, sizeof(demo)); // clear whole demo struct at least once\n\t\n\tCvar_Register (&sv_demofps);\n\tCvar_Register (&sv_demoIdlefps);\n\tCvar_Register (&sv_demoPings);\n\tCvar_Register (&sv_demoUseCache);\n\tCvar_Register (&sv_demoCacheSize);\n\tCvar_Register (&sv_demoMaxSize);\n\tCvar_Register (&sv_demoMaxDirSize);\n\tCvar_Register (&sv_demoClearOld); //bliP: 24/9 clear old demos\n\tCvar_Register (&sv_demoDir);\n\tCvar_Register (&sv_demoDirAlt);\n\tCvar_Register (&sv_demoPrefix);\n\tCvar_Register (&sv_demoSuffix);\n\tCvar_Register (&sv_onrecordfinish);\n\tCvar_Register (&sv_ondemoremove);\n\tCvar_Register (&sv_demotxt);\n\tCvar_Register (&sv_demoExtraNames);\n\tCvar_Register (&sv_demoRegexp);\n\tCvar_Register (&sv_silentrecord);\n\n\tCvar_Register (&extralogname);\n\n\tp = SV_CommandLineDemoCacheArgument();\n\tif (p)\n\t{\n\t\tif (p < COM_Argc()-1)\n\t\t\tsize = Q_atoi (COM_Argv(p+1)) * 1024;\n\t\telse\n\t\t\tSys_Error (\"MVD_Init: you must specify a size in KB after -democache\");\n\t}\n\n\tif (size < DEMO_CACHE_MIN_SIZE)\n\t{\n\t\tCon_Printf(\"Minimum memory size for demo cache is %dk\\n\", DEMO_CACHE_MIN_SIZE / 1024);\n\t\tsize = DEMO_CACHE_MIN_SIZE;\n\t}\n\n\tCvar_SetROM(&sv_demoCacheSize, va(\"%d\", size/1024));\n\tCleanName_Init();\n}\n\nvoid SV_UserCmdTrace_f(void)\n{\n\tconst char* user = Cmd_Argv(1);\n\tconst char* option_ = Cmd_Argv(2);\n\tqbool option = false;\n\tint uid, i;\n\n\tif (Cmd_Argc() != 3) {\n\t\tCon_Printf(\"Usage: %s userid (on | off)\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tif (!strcmp(option_, \"on\")) {\n\t\toption = true;\n\t}\n\telse if (strcmp(option_, \"off\")) {\n\t\tCon_Printf(\"Usage: %s userid (on | off)\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tuid = atoi(user);\n\tif (!uid) {\n\t\tCon_Printf(\"Usage: %s userid (on | off)\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!svs.clients[i].state) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (svs.clients[i].userid == uid) {\n\t\t\tsvs.clients[i].mvd_write_usercmds = option;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tCon_Printf(\"Couldn't find userid %d\\n\", uid);\n\treturn;\n}\n\nvoid SV_MVDInit(void)\n{\n\tMVD_Init();\n\n#ifdef SERVERONLY\n\t// name clashes with client.\n\t// would be nice to prefix it with sv_demo*,\n\t// but mods use it like that already, so we keep it for backward compatibility.\n\tCmd_AddCommand (\"record\",\t\t\tSV_MVD_Record_f);\n\tCmd_AddCommand (\"easyrecord\",\t\tSV_MVDEasyRecord_f);\n\tCmd_AddCommand (\"stop\",\t\t\t\tSV_MVDStop_f);\n#endif\n\t// that how thouse commands should be called.\n\tCmd_AddCommand (\"sv_demorecord\",\tSV_MVD_Record_f);\n\tCmd_AddCommand (\"sv_demoeasyrecord\",SV_MVDEasyRecord_f);\n\tCmd_AddCommand (\"sv_demostop\",\t\tSV_MVDStop_f);\n\n\t// that one does not clashes with client, but keep name for backward compatibility.\n\tCmd_AddCommand (\"cancel\",\t\t\tSV_MVD_Cancel_f);\n\t// that how thouse commands should be called.\n\tCmd_AddCommand (\"sv_democancel\",\tSV_MVD_Cancel_f);\n\t// this ones prefixed OK.\n\tCmd_AddCommand (\"sv_lastscores\",\tSV_LastScores_f);\n\tCmd_AddCommand (\"sv_demolist\",\t\tSV_DemoList_f);\n\tCmd_AddCommand (\"sv_demolistr\",\t\tSV_DemoListRegex_f);\n\tCmd_AddCommand (\"sv_demolistregex\",\tSV_DemoListRegex_f);\n\tCmd_AddCommand (\"sv_demoremove\",\tSV_MVDRemove_f);\n\tCmd_AddCommand (\"sv_demonumremove\",\tSV_MVDRemoveNum_f);\n\tCmd_AddCommand (\"sv_demoinfoadd\",\tSV_MVDInfoAdd_f);\n\tCmd_AddCommand (\"sv_demoinforemove\",SV_MVDInfoRemove_f);\n\tCmd_AddCommand (\"sv_demoinfo\",\t\tSV_MVDInfo_f);\n\tCmd_AddCommand (\"sv_demoembedinfo\", SV_MVDEmbedInfo_f);\n\t// not prefixed.\n#ifdef SERVERONLY\n\tCmd_AddCommand (\"script\",\t\t\tSV_Script_f);\n#endif\n\n\tCmd_AddCommand (\"sv_usercmdtrace\",  SV_UserCmdTrace_f);\n\n\tSV_QTV_Init();\n}\n\nconst char* SV_MVDDemoName(void)\n{\n\tmvddest_t* d;\n\n\tfor (d = demo.dest; d; d = d->nextdest) {\n\t\tif (d->desttype == DEST_STREAM) {\n\t\t\tcontinue; // streams are not saved on to HDD, so ignore it...\n\t\t}\n\t\tif (d->name[0]) {\n\t\t\treturn d->name;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_demo_misc.c",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    \n*/\n\n// sv_demo_misc.c - misc demo related stuff, helpers\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n#ifndef SERVERONLY\n#include \"pcre2.h\"\n#endif\n\n#define MAX_DEMOINFO_SIZE (1024 * 200)\nstatic char chartbl[256];\n\n/*\n====================\nCleanName_Init\n\nsets chararcter table for quake text->filename translation\n====================\n*/\nvoid CleanName_Init (void)\n{\n\tint i;\n\n\tfor (i = 0; i < 256; i++)\n\t\tchartbl[i] = (((i&127) < 'a' || (i&127) > 'z') && ((i&127) < '0' || (i&127) > '9')) ? '_' : (i&127);\n\n\t// special cases\n\n\t// numbers\n\tfor (i = 18; i < 29; i++)\n\t\tchartbl[i] = chartbl[i + 128] = i + 30;\n\n\t// allow lowercase only\n\tfor (i = 'A'; i <= 'Z'; i++)\n\t\tchartbl[i] = chartbl[i+128] = i + 'a' - 'A';\n\n\t// brackets\n\tchartbl[29] = chartbl[29+128] = chartbl[128] = '(';\n\tchartbl[31] = chartbl[31+128] = chartbl[130] = ')';\n\tchartbl[16] = chartbl[16 + 128]= '[';\n\tchartbl[17] = chartbl[17 + 128] = ']';\n\n\t// dot\n\tchartbl[5] = chartbl[14] = chartbl[15] = chartbl[28] = chartbl[46] = '.';\n\tchartbl[5 + 128] = chartbl[14 + 128] = chartbl[15 + 128] = chartbl[28 + 128] = chartbl[46 + 128] = '.';\n\n\t// !\n\tchartbl[33] = chartbl[33 + 128] = '!';\n\n\t// #\n\tchartbl[35] = chartbl[35 + 128] = '#';\n\n\t// %\n\tchartbl[37] = chartbl[37 + 128] = '%';\n\n\t// &\n\tchartbl[38] = chartbl[38 + 128] = '&';\n\n\t// '\n\tchartbl[39] = chartbl[39 + 128] = '\\'';\n\n\t// (\n\tchartbl[40] = chartbl[40 + 128] = '(';\n\n\t// )\n\tchartbl[41] = chartbl[41 + 128] = ')';\n\n\t// +\n\tchartbl[43] = chartbl[43 + 128] = '+';\n\n\t// -\n\tchartbl[45] = chartbl[45 + 128] = '-';\n\n\t// @\n\tchartbl[64] = chartbl[64 + 128] = '@';\n\n\t// ^\n\t//\tchartbl[94] = chartbl[94 + 128] = '^';\n\n\n\tchartbl[91] = chartbl[91 + 128] = '[';\n\tchartbl[93] = chartbl[93 + 128] = ']';\n\n\tchartbl[16] = chartbl[16 + 128] = '[';\n\tchartbl[17] = chartbl[17 + 128] = ']';\n\n\tchartbl[123] = chartbl[123 + 128] = '{';\n\tchartbl[125] = chartbl[125 + 128] = '}';\n}\n\n/*\n====================\nSV_CleanName\n\nCleans the demo name, removes restricted chars, makes name lowercase\n====================\n*/\nchar *SV_CleanName (unsigned char *name)\n{\n\tstatic char text[1024];\n\tchar *out = text;\n\n\tif (!name || !*name)\n\t{\n\t\t*out = '\\0';\n\t\treturn text;\n\t}\n\n\t*out = chartbl[*name++];\n\n\twhile (*name && ((out - text) < (int) sizeof(text)))\n\t\tif (*out == '_' && chartbl[*name] == '_')\n\t\t\tname++;\n\t\telse *++out = chartbl[*name++];\n\n\t*++out = 0;\n\treturn text;\n}\n\n/*\n====================\nSV_DirSizeCheck\n\nDeletes sv_demoClearOld files from demo dir if out of space\n====================\n*/\nqbool SV_DirSizeCheck (void)\n{\n\tdir_t\tdir;\n\tfile_t\t*list;\n\tint\tn;\n\n\tif ((int)sv_demoMaxDirSize.value)\n\t{\n\t\tdir = Sys_listdir(va(\"%s/%s\", fs_gamedir, sv_demoDir.string), \".*\", SORT_NO/*BY_DATE*/);\n\t\tif ((float)dir.size > sv_demoMaxDirSize.value * 1024)\n\t\t{\n\t\t\tif ((int)sv_demoClearOld.value <= 0)\n\t\t\t{\n\t\t\t\tCon_Printf(\"Insufficient directory space, increase sv_demoMaxDirSize\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tlist = dir.files;\n\t\t\tn = (int) sv_demoClearOld.value;\n\t\t\tCon_Printf(\"Clearing %d old demos\\n\", n);\n\t\t\t// HACK!!! HACK!!! HACK!!!\n\t\t\tif ((int)sv_demotxt.value) // if our server record demos and txts, then to remove\n\t\t\t\tn <<= 1;  // 50 demos, we have to remove 50 demos and 50 txts = 50*2 = 100 files\n\n\t\t\tqsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_date);\n\t\t\tfor (; list->name[0] && n > 0; list++)\n\t\t\t{\n\t\t\t\tif (list->isdir)\n\t\t\t\t\tcontinue;\n\t\t\t\tSys_remove(va(\"%s/%s/%s\", fs_gamedir, sv_demoDir.string, list->name));\n\t\t\t\t//Con_Printf(\"Remove %d - %s/%s/%s\\n\", n, fs_gamedir, sv_demoDir.string, list->name);\n\t\t\t\tn--;\n\t\t\t}\n\n\t\t\t// force cache rebuild.\n\t\t\tFS_FlushFSHash();\n\t\t}\n\t}\n\treturn true;\n}\n\nvoid Run_sv_demotxt_and_sv_onrecordfinish (const char *dest_name, const char *dest_path, qbool destroyfiles)\n{\n\tchar path[MAX_OSPATH];\n\n\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, dest_path, dest_name);\n\tstrlcpy(path + strlen(path) - 3, \"txt\", MAX_OSPATH - strlen(path) + 3);\n\n\tif ((int)sv_demotxt.value && !destroyfiles) // dont keep txt's for deleted demos\n\t{\n\t\tFILE *f;\n\t\tchar *text;\n\n\t\tif (sv_demotxt.value == 2)\n\t\t{\n\t\t\tif ((f = fopen (path, \"a+t\")))\n\t\t\t\tfclose(f); // at least made empty file, but do not owerwite\n\t\t}\n\t\telse if ((f = fopen (path, \"w+t\")))\n\t\t{\n\t\t\ttext = SV_PrintTeams();\n\t\t\tfwrite(text, strlen(text), 1, f);\n\t\t\tfflush(f);\n\t\t\tfclose(f);\n\t\t}\n\t}\n\n\tif (sv_onrecordfinish.string[0] && !destroyfiles) // dont gzip deleted demos\n\t{\n\t\textern redirect_t sv_redirected;\n\t\tredirect_t old = sv_redirected;\n\t\tchar *p;\n\t\n\t\tif ((p = strchr(sv_onrecordfinish.string, ' ')) != NULL)\n\t\t\t*p = 0; // strip parameters\n\t\n\t\tstrlcpy(path, dest_name, sizeof(path));\n#ifdef SERVERONLY\n\t\tCOM_StripExtension(path);\n#else\n\t\tCOM_StripExtension(path, path, sizeof(path));\n#endif\n\n\t\tsv_redirected = RD_NONE; // onrecord script is called always from the console\n\t\tCmd_TokenizeString(va(\"script %s \\\"%s\\\" \\\"%s\\\" %s\", sv_onrecordfinish.string, dest_path, path, p != NULL ? p+1 : \"\"));\n\n\t\tif (p)\n\t\t\t*p = ' '; // restore params\n\n\t\tSV_Script_f();\n\t\n\t\tsv_redirected = old;\n\t}\n\n\t// force cache rebuild.\n\tFS_FlushFSHash();\n}\n\nchar *SV_PrintTeams (void)\n{\n\tchar\t\t\t*teams[MAX_CLIENTS];\n\tint\t\t\t\ti, j, numcl = 0, numt = 0, scores;\n\tclient_t\t\t*clients[MAX_CLIENTS];\n\tchar\t\t\tbuf[2048];\n\tstatic char\t\tlastscores[2048];\n\textern cvar_t\tteamplay;\n\tdate_t\t\t\tdate;\n\tSV_TimeOfDay(&date, \"%a %b %d, %H:%M:%S %Y\");\n\n\t// count teams and players\n\tfor (i=0; i < MAX_CLIENTS; i++)\n\t{\n\t\tif (svs.clients[i].state != cs_spawned)\n\t\t\tcontinue;\n\t\tif (svs.clients[i].spectator)\n\t\t\tcontinue;\n\n\t\tclients[numcl++] = &svs.clients[i];\n\t\tfor (j = 0; j < numt; j++)\n\t\t\tif (!strcmp(svs.clients[i].team, teams[j]))\n\t\t\t\tbreak;\n\t\tif (j != numt)\n\t\t\tcontinue;\n\n\t\tteams[numt++] = svs.clients[i].team;\n\t}\n\n\t// create output\n\tlastscores[0] = 0;\n\tsnprintf(buf, sizeof(buf),\n\t\t\"date %s\\nmap %s\\nteamplay %d\\ndeathmatch %d\\ntimelimit %d\\n\",\n\t\tdate.str, sv.mapname, (int)teamplay.value, (int)deathmatch.value,\n\t\t(int)timelimit.value);\n\tif (numcl == 2) // duel\n\t{\n\t\tsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),\n\t\t\t\"player1: %s (%i)\\nplayer2: %s (%i)\\n\",\n\t\t\tclients[0]->name, clients[0]->old_frags,\n\t\t\tclients[1]->name, clients[1]->old_frags);\n\t\tsnprintf(lastscores, sizeof(lastscores), \"duel: %s vs %s @ %s - %i:%i\\n\",\n\t\t\tclients[0]->name, clients[1]->name, sv.mapname,\n\t\t\tclients[0]->old_frags, clients[1]->old_frags);\n\t}\n\telse if (!(int)teamplay.value) // ffa\n\t{\n\t\tsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), \"players:\\n\");\n\t\tsnprintf(lastscores, sizeof(lastscores), \"ffa:\");\n\t\tfor (i = 0; i < numcl; i++)\n\t\t{\n\t\t\tsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),\n\t\t\t\t\"  %s (%i)\\n\", clients[i]->name, clients[i]->old_frags);\n\t\t\tsnprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores),\n\t\t\t\t\"  %s(%i)\", clients[i]->name, clients[i]->old_frags);\n\t\t}\n\t\tsnprintf(lastscores + strlen(lastscores),\n\t\t\tsizeof(lastscores) - strlen(lastscores), \" @ %s\\n\", sv.mapname);\n\t}\n\telse\n\t{ // teamplay\n\t\tsnprintf(lastscores, sizeof(lastscores), \"tp:\");\n\t\tfor (j = 0; j < numt; j++)\n\t\t{\n\t\t\tsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),\n\t\t\t\t\"team[%i] %s:\\n\", j, teams[j]);\n\t\t\tsnprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores),\n\t\t\t\t\"%s[\", teams[j]);\n\t\t\tscores = 0;\n\t\t\tfor (i = 0; i < numcl; i++)\n\t\t\t\tif (!strcmp(clients[i]->team, teams[j]))\n\t\t\t\t{\n\t\t\t\t\tsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),\n\t\t\t\t\t\t\"  %s (%i)\\n\", clients[i]->name, clients[i]->old_frags);\n\t\t\t\t\tsnprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores),\n\t\t\t\t\t\t\" %s(%i) \", clients[i]->name, clients[i]->old_frags);\n\t\t\t\t\tscores += clients[i]->old_frags;\n\t\t\t\t}\n\t\t\tsnprintf(lastscores + strlen(lastscores), sizeof(lastscores) - strlen(lastscores),\n\t\t\t\t\"](%i)  \", scores);\n\n\t\t}\n\t\tsnprintf(lastscores + strlen(lastscores),\n\t\t\tsizeof(lastscores) - strlen(lastscores), \"@ %s\\n\", sv.mapname);\n\t}\n\n\tQ_normalizetext(buf);\n\tQ_normalizetext(lastscores);\n\tstrlcat(lastscores, buf, sizeof(lastscores));\n\treturn lastscores;\n}\n\n\nvoid SV_DemoList (qbool use_regex)\n{\n\tmvddest_t *d;\n\tdir_t\tdir;\n\tfile_t\t*list;\n\tfloat\tfree_space;\n\tint\t\ti, j, n;\n\tint\t\tfiles[MAX_DIRFILES + 1];\n\n\tPCRE2_SIZE\terror_offset;\n\tpcre2_code\t*preg;\n\tpcre2_match_data *match_data = NULL;\n\tint error;\n\n\tmemset(files, 0, sizeof(files));\n\n\tCon_Printf(\"Listing content of %s/%s/%s\\n\", fs_gamedir, sv_demoDir.string, sv_demoRegexp.string);\n\tdir = Sys_listdir(va(\"%s/%s\", fs_gamedir, sv_demoDir.string), sv_demoRegexp.string, SORT_BY_DATE);\n\tlist = dir.files;\n\tif (!list->name[0])\n\t{\n\t\tCon_Printf(\"no demos\\n\");\n\t}\n\n\tfor (i = 1, n = 0; list->name[0]; list++, i++)\n\t{\n\t\tfor (j = 1; j < Cmd_Argc(); j++)\n\t\t{\n\t\t\tif (use_regex)\n\t\t\t{\n\t\t\t\tif (!(preg = pcre2_compile((PCRE2_SPTR)Q_normalizetext(Cmd_Argv(j)), PCRE2_ZERO_TERMINATED, PCRE2_CASELESS, &error, &error_offset, NULL)))\n\t\t\t\t{\n\t\t\t\t\tPCRE2_UCHAR error_str[256];\n\t\t\t\t\tpcre2_get_error_message(error, error_str, sizeof(error_str));\n\t\t\t\t\tCon_Printf(\"SV_DemoList: pcre2_compile(%s) error: %s at offset %d\\n\",\n\t\t\t\t\t           Cmd_Argv(j), error_str, error_offset);\n\t\t\t\t\tpcre2_code_free(preg);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tmatch_data = pcre2_match_data_create_from_pattern(preg, NULL);\n\t\t\t\terror = pcre2_match(preg, (PCRE2_SPTR)list->name, strlen(list->name), 0, 0, match_data, NULL);\n\t\t\t\tpcre2_match_data_free(match_data);\n\t\t\t\tpcre2_code_free(preg);\n\t\t\t\tif (error < 0)\n\t\t\t\t{\n\t\t\t\t\tif (error != PCRE2_ERROR_NOMATCH) {\n\t\t\t\t\t\tCon_Printf(\"SV_DemoList: pcre2_match(%s, %s) error code: %d\\n\",\n\t\t\t\t\t\t\tCmd_Argv(j), list->name, error);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (strstr(list->name, Cmd_Argv(j)) == NULL)\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (Cmd_Argc() == j)\n\t\t{\n\t\t\tfiles[n++] = i;\n\t\t}\n\t}\n\n\tlist = dir.files;\n\tfor (j = (GameStarted() && n > 100) ? n - 100 : 0; files[j]; j++)\n\t{\n\t\ti = files[j];\n\n\t\tif ((d = DestByName(list[i - 1].name)))\n\t\t\tCon_Printf(\"*%4d: %s (%dk)\\n\", i, list[i - 1].name, d->totalsize / 1024);\n\t\telse\n\t\t\tCon_Printf(\"%4d: %s (%dk)\\n\", i, list[i - 1].name, list[i - 1].size / 1024);\n\t}\n\n\tfor (d = demo.dest; d; d = d->nextdest)\n\t{\n\t\tif (d->desttype == DEST_STREAM)\n\t\t\tcontinue; // streams are not saved on to HDD, so inogre it...\n\t\tdir.size += d->totalsize;\n\t}\n\n\tCon_Printf(\"\\ndirectory size: %.1fMB\\n\", (float)dir.size / (1024 * 1024));\n\tif ((int)sv_demoMaxDirSize.value)\n\t{\n\t\tfree_space = (sv_demoMaxDirSize.value * 1024 - dir.size) / (1024 * 1024);\n\t\tif (free_space < 0)\n\t\t\tfree_space = 0;\n\t\tCon_Printf(\"space available: %.1fMB\\n\", free_space);\n\t}\n}\n\nvoid SV_DemoList_f (void)\n{\n\tSV_DemoList (false);\n}\n\nvoid SV_DemoListRegex_f (void)\n{\n\tSV_DemoList (true);\n}\n\nchar *SV_MVDNum (int num)\n{\n\tfile_t\t*list;\n\tdir_t\tdir;\n\n\tif (!num)\n\t\treturn NULL;\n\n\t// last recorded demo's names for command \"cmd dl . ..\" (maximum 15 dots)\n\tif (num & 0xFF000000)\n\t{\n\t\tchar *name = demo.lastdemosname[(demo.lastdemospos - (num >> 24) + 1) & 0xF];\n\t\tchar *name2;\n\t\tint c;\n\n\t\tif (!(name2 = quote(name)))\n\t\t\treturn NULL;\n\t\tif ((c = strlen(name2)) > 5)\n\t\t\tname2[c - 5] = '\\0'; // crop quoted extension '\\.mvd'\n\n\t\tdir = Sys_listdir(va(\"%s/%s\", fs_gamedir, sv_demoDir.string),\n\t\t\t\t\t\t  va(\"^%s%s\", name2, sv_demoRegexp.string), SORT_NO);\n\t\tlist = dir.files;\n\t\tif (dir.numfiles > 1)\n\t\t{\n\t\t\tCon_Printf(\"SV_MVDNum: where are %d demos with name: %s%s\\n\",\n\t\t\t\t\t\tdir.numfiles, name2, sv_demoRegexp.string);\n\t\t}\n\t\tif (!dir.numfiles)\n\t\t{\n\t\t\tCon_Printf(\"SV_MVDNum: where are no demos with name: %s%s\\n\",\n\t\t\t\t\t\tname2, sv_demoRegexp.string);\n\t\t\treturn NULL;\n\t\t}\n\t\tQ_free(name2);\n\t\t//Con_Printf(\"%s\", dir.files[0].name);\n\t\treturn dir.files[0].name;\n\t}\n\n\tdir = Sys_listdir(va(\"%s/%s\", fs_gamedir, sv_demoDir.string), sv_demoRegexp.string, SORT_BY_DATE);\n\tlist = dir.files;\n\n\tif (num & 0x00800000)\n\t{\n\t\tnum |= 0xFF000000;\n\t\tnum += dir.numfiles;\n\t}\n\telse\n\t{\n\t\t--num;\n\t}\n\n\tif (num > dir.numfiles)\n\t\treturn NULL;\n\n\twhile (list->name[0] && num)\n\t{\n\t\tlist++;\n\t\tnum--;\n\t}\n\n\treturn list->name[0] ? list->name : NULL;\n}\n\n#define OVECCOUNT 3\nchar *SV_MVDName2Txt (const char *name)\n{\n\tchar\ts[MAX_OSPATH];\n\tint\t\tlen;\n\n\tPCRE2_SIZE\terror_offset;\n\tpcre2_code\t*preg;\n\tint error;\n\tpcre2_match_data *match_data = NULL;\n\n\tif (!name)\n\t\treturn NULL;\n\n\tif (!*name)\n\t\treturn NULL;\n\n\tstrlcpy(s, name, MAX_OSPATH);\n\tlen = strlen(s);\n\n\tif (!(preg = pcre2_compile((PCRE2_SPTR)sv_demoRegexp.string, PCRE2_ZERO_TERMINATED, PCRE2_CASELESS, &error, &error_offset, NULL)))\n\t{\n\t\tPCRE2_UCHAR error_str[256];\n\t\tpcre2_get_error_message(error, error_str, sizeof(error_str));\n\t\tCon_Printf(\"SV_MVDName2Txt: pcre2_compile(%s) error: %s at offset %d\\n\",\n\t\t\t\t\tsv_demoRegexp.string, error_str, error_offset);\n\t\tpcre2_code_free(preg);\n\t\treturn NULL;\n\t}\n\tmatch_data = pcre2_match_data_create_from_pattern(preg, NULL);\n\terror = pcre2_match(preg, (PCRE2_SPTR)s, len, 0, 0, match_data, NULL);\n\n\tif (error < 0)\n\t{\n\t\tpcre2_match_data_free(match_data);\n\t\tpcre2_code_free(preg);\n\n\t\tswitch (error)\n\t\t{\n\t\tcase PCRE2_ERROR_NOMATCH:\n\t\t\treturn NULL;\n\t\tdefault:\n\t\t\tCon_Printf(\"SV_MVDName2Txt: pcre2_match(%s, %s) error code: %d\\n\",\n\t\t\t\t\t\tsv_demoRegexp.string, s, error);\n\t\t\treturn NULL;\n\t\t}\n\t}\n\telse\n\t{\n\t\tPCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);\n\n\t\tif (ovector[0] + 5 > MAX_OSPATH)\n\t\t\tlen = MAX_OSPATH - 5;\n\t\telse\n\t\t\tlen = ovector[0];\n\t}\n\ts[len++] = '.';\n\ts[len++] = 't';\n\ts[len++] = 'x';\n\ts[len++] = 't';\n\ts[len]   = '\\0';\n\n\tpcre2_match_data_free(match_data);\n\tpcre2_code_free(preg);\n\n\t//Con_Printf(\"%d) %s, %s\\n\", r, name, s);\n\treturn va(\"%s\", s);\n}\n\nstatic char *SV_MVDTxTNum (int num)\n{\n\treturn SV_MVDName2Txt (SV_MVDNum(num));\n}\n\n\nvoid SV_MVDRemove_f (void)\n{\n\tchar name[MAX_DEMO_NAME], *ptr;\n\tchar path[MAX_OSPATH];\n\tint i;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCon_Printf(\"rmdemo <demoname> - removes the demo\\nrmdemo *<token>   - removes demo with <token> in the name\\nrmdemo *          - removes all demos\\n\");\n\t\treturn;\n\t}\n\n\tptr = Cmd_Argv(1);\n\tif (*ptr == '*')\n\t{\n\t\tdir_t dir;\n\t\tfile_t *list;\n\n\t\t// remove all demos with specified token\n\t\tptr++;\n\n\t\tdir = Sys_listdir(va(\"%s/%s\", fs_gamedir, sv_demoDir.string), sv_demoRegexp.string, SORT_BY_DATE);\n\t\tlist = dir.files;\n\t\tfor (i = 0;list->name[0]; list++)\n\t\t{\n\t\t\tif (strstr(list->name, ptr))\n\t\t\t{\n\t\t\t\tif (sv.mvdrecording && DestByName(list->name)/*!strcmp(list->name, demo.name)*/)\n\t\t\t\t\tSV_MVDStop_f(); // FIXME: probably we must stop not all demos, but only partial dest\n\n\t\t\t\t// stop recording first;\n\t\t\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, sv_demoDir.string, list->name);\n\t\t\t\tif (!Sys_remove(path))\n\t\t\t\t{\n\t\t\t\t\tCon_Printf(\"removing %s...\\n\", list->name);\n\t\t\t\t\ti++;\n\t\t\t\t}\n\n\t\t\t\tSys_remove(SV_MVDName2Txt(path));\n\t\t\t}\n\t\t}\n\n\t\tif (i)\n\t\t{\n\t\t\tCon_Printf(\"%d demos removed\\n\", i);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf(\"no match found\\n\");\n\t\t}\n\n\t\t// force cache rebuild.\n\t\tFS_FlushFSHash();\n\n\t\treturn;\n\t}\n\n\tstrlcpy(name, Cmd_Argv(1), MAX_DEMO_NAME);\n\tCOM_DefaultExtension(name, \".mvd\", sizeof(name));\n\n\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, sv_demoDir.string, name);\n\n\tif (sv.mvdrecording && DestByName(name) /*!strcmp(name, demo.name)*/)\n\t\tSV_MVDStop_f(); // FIXME: probably we must stop not all demos, but only partial dest\n\n\tif (!Sys_remove(path))\n\t{\n\t\tCon_Printf(\"demo %s successfully removed\\n\", name);\n\n\t\tif (*sv_ondemoremove.string)\n\t\t{\n\t\t\textern redirect_t sv_redirected;\n\t\t\tredirect_t old = sv_redirected;\n\n\t\t\tsv_redirected = RD_NONE; // this script is called always from the console\n\t\t\tCmd_TokenizeString(va(\"script %s \\\"%s\\\" \\\"%s\\\"\", sv_ondemoremove.string, sv_demoDir.string, name));\n\t\t\tSV_Script_f();\n\n\t\t\tsv_redirected = old;\n\t\t}\n\t}\n\telse\n\t\tCon_Printf(\"unable to remove demo %s\\n\", name);\n\n\tSys_remove(SV_MVDName2Txt(path));\n\n\t// force cache rebuild.\n\tFS_FlushFSHash();\n}\n\nvoid SV_MVDRemoveNum_f (void)\n{\n\tint\t\tnum;\n\tchar\t*val, *name;\n\tchar path[MAX_OSPATH];\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCon_Printf(\"rmdemonum <#>\\n\");\n\t\treturn;\n\t}\n\n\tval = Cmd_Argv(1);\n\tif ((num = Q_atoi(val)) == 0 && val[0] != '0')\n\t{\n\t\tCon_Printf(\"rmdemonum <#>\\n\");\n\t\treturn;\n\t}\n\n\tname = SV_MVDNum(num);\n\n\tif (name != NULL)\n\t{\n\t\tif (sv.mvdrecording && DestByName(name)/*!strcmp(name, demo.name)*/)\n\t\t\tSV_MVDStop_f(); // FIXME: probably we must stop not all demos, but only partial dest\n\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, sv_demoDir.string, name);\n\t\tif (!Sys_remove(path))\n\t\t{\n\t\t\tCon_Printf(\"demo %s successfully removed\\n\", name);\n\t\t\tif (*sv_ondemoremove.string)\n\t\t\t{\n\t\t\t\textern redirect_t sv_redirected;\n\t\t\t\tredirect_t old = sv_redirected;\n\n\t\t\t\tsv_redirected = RD_NONE; // this script is called always from the console\n\t\t\t\tCmd_TokenizeString(va(\"script %s \\\"%s\\\" \\\"%s\\\"\", sv_ondemoremove.string, sv_demoDir.string, name));\n\t\t\t\tSV_Script_f();\n\n\t\t\t\tsv_redirected = old;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\tCon_Printf(\"unable to remove demo %s\\n\", name);\n\n\t\tSys_remove(SV_MVDName2Txt(path));\n\n\t\t// force cache rebuild.\n\t\tFS_FlushFSHash();\n\t}\n\telse\n\t\tCon_Printf(\"invalid demo num\\n\");\n}\n\nvoid SV_MVDInfoAdd_f (void)\n{\n\tchar *name, *args, path[MAX_OSPATH];\n\tFILE *f;\n\n\tif (Cmd_Argc() < 3)\n\t{\n\t\tCon_Printf(\"usage:demoInfoAdd <demonum> <info string>\\n<demonum> = * for currently recorded demo\\n\");\n\t\treturn;\n\t}\n\n\tif (!strcmp(Cmd_Argv(1), \"*\") || !strcmp(Cmd_Argv(1), \"**\")) {\n\t\tconst char* demoname = SV_MVDDemoName();\n\n\t\tif (!sv.mvdrecording || !demoname) {\n\t\t\tCon_Printf(\"Not recording demo!\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(demoname));\n\t}\n\telse {\n\t\tname = SV_MVDTxTNum(Q_atoi(Cmd_Argv(1)));\n\n\t\tif (!name) {\n\t\t\tCon_Printf(\"invalid demo num\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, sv_demoDir.string, name);\n\t}\n\n\tif ((f = fopen(path, !strcmp(Cmd_Argv(1), \"**\") ? \"a+b\" : \"a+t\")) == NULL)\n\t{\n\t\tCon_Printf(\"failed to open the file\\n\");\n\t\treturn;\n\t}\n\n\tif (!strcmp(Cmd_Argv(1), \"**\"))\n\t{\n\t\t// put content of one file to another\n\t\tFILE *src;\n\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s\", fs_gamedir, Cmd_Argv(2));\n\n\t\tif ((src = fopen(path, \"rb\")) == NULL) // open src\n\t\t{\n\t\t\tCon_Printf(\"failed to open input file\\n\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\tbyte buf[1024 * 200] = { 0 }; // 200 kb\n\t\t\tsize_t sz = fread((void*)buf, 1, sizeof(buf), src); // read from src\n\n\t\t\tif (sz <= 0) {\n\t\t\t\tCon_Printf(\"failed to read or empty input file\\n\");\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// write to f\n\t\t\t\tif (sz != fwrite((void*)buf, 1, sz, f)) {\n\t\t\t\t\tCon_Printf(\"failed write to file\\n\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfclose(src); // close src\n\t\t}\n\t}\n\telse\n\t{\n\t\t// skip demonum\n\t\targs = Cmd_Args();\n\n\t\twhile (*args > 32) args++;\n\t\twhile (*args && *args <= 32) args++;\n\n\t\tfwrite(args, strlen(args), 1, f);\n\t\tfwrite(\"\\n\", 1, 1, f);\n\t}\n\n\tfflush(f);\n\tfclose(f);\n\n\t// force cache rebuild.\n\tFS_FlushFSHash();\n}\n\n// Put content of one file into .mvd\nvoid SV_MVDEmbedInfo_f(void)\n{\n\t// put content of one file to another\n\tFILE* src;\n\tbyte* buf;\n\tsize_t sz;\n\tchar name[MAX_OSPATH];\n\tchar path[MAX_OSPATH];\n\n\tif (Cmd_Argc() == 1) {\n\t\tCon_Printf(\"Embeds contents of a file into mvd/qtv stream\\n\");\n\t\tCon_Printf(\"Usage: %s <filename>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\t// No config files, no sub-directories\n\tstrlcpy(name, Cmd_Argv(1), sizeof(name));\n\tsnprintf(path, MAX_OSPATH, \"%s/%s\", fs_gamedir, Cmd_Argv(1));\n\tif (FS_UnsafeFilename(path) || !strcasecmp(COM_FileExtension(name), \"cfg\")) {\n\t\tCon_Printf(\"Unsafe filename detected - cancelled\\n\");\n\t\treturn;\n\t}\n\n\tif ((src = fopen(path, \"rb\")) == NULL) {\n\t\tCon_Printf(\"failed to open input file\\n\");\n\t\treturn;\n\t}\n\n\tbuf = Q_malloc(MAX_DEMOINFO_SIZE);\n\tsz = fread((void*)buf, 1, MAX_DEMOINFO_SIZE, src);\n\tfclose(src);\n\tif (sz <= 0) {\n\t\tCon_Printf(\"failed to read or empty input file\\n\");\n\t\tQ_free(buf);\n\t\treturn;\n\t}\n\n\tCon_Printf(\"Embedding (%s):\\n\", path);\n\n\t// embed in .mvd/qtv (sanity check limits)\n\tif (sz < 2 * 1024 * 1024) {\n\t\tmvdhidden_block_header_t header;\n\t\tbyte* data = buf;\n\t\tshort block_number = 1;\n\n\t\twhile (sz > 0) {\n\t\t\tint prefix_length = sizeof_mvdhidden_block_header_t_range0 + sizeof(block_number);\n\t\t\tint length = (int)min(sz + prefix_length, MAX_MVD_SIZE);\n\n\t\t\tif (MVDWrite_HiddenBlockBegin(length)) {\n\t\t\t\tlength -= prefix_length;\n\n\t\t\t\tsz -= length;\n\t\t\t\tif (sz == 0) {\n\t\t\t\t\tblock_number = 0;\n\t\t\t\t}\n\n\t\t\t\theader.length = LittleLong(length + sizeof(short));\n\t\t\t\theader.type_id = LittleShort(mvdhidden_demoinfo);\n\t\t\t\tMVD_SZ_Write(&header.length, sizeof(header.length));\n\t\t\t\tMVD_SZ_Write(&header.type_id, sizeof(header.type_id));\n\t\t\t\tMVD_SZ_Write(&block_number, sizeof(block_number));\n\t\t\t\tMVD_SZ_Write(data, length);\n\n\t\t\t\tdata += length;\n\t\t\t\t++block_number;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCon_Printf(\"failed write to mvd/qtv\\n\");\n\t\t\t\tQ_free(buf);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tQ_free(buf);\n}\n\nvoid SV_MVDInfoRemove_f (void)\n{\n\tchar *name, path[MAX_OSPATH];\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tCon_Printf(\"usage:demoInfoRemove <demonum>\\n<demonum> = * for currently recorded demo\\n\");\n\t\treturn;\n\t}\n\n\tif (!strcmp(Cmd_Argv(1), \"*\"))\n\t{\n\t\tif (!sv.mvdrecording || !demo.dest)\n\t\t{\n\t\t\tCon_Printf(\"Not recording demo!\\n\");\n\t\t\treturn;\n\t\t}\n\n//\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, demo.path, SV_MVDName2Txt(demo.name));\n// FIXME: dunno is this right, just using first dest, also may be we must use demo.dest->path instead of sv_demoDir\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(demo.dest->name));\n\t}\n\telse\n\t{\n\t\tname = SV_MVDTxTNum(Q_atoi(Cmd_Argv(1)));\n\n\t\tif (!name)\n\t\t{\n\t\t\tCon_Printf(\"invalid demo num\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, sv_demoDir.string, name);\n\t}\n\n\tif (Sys_remove(path))\n\t\tCon_Printf(\"failed to remove the file %s\\n\", path);\n\telse\n\t\tCon_Printf(\"file %s removed\\n\", path);\n\n\t// force cache rebuild.\n\tFS_FlushFSHash();\n}\n\nvoid SV_MVDInfo_f (void)\n{\n\tunsigned char buf[512];\n\tFILE *f = NULL;\n\tchar *name, path[MAX_OSPATH];\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tCon_Printf(\"usage: demoinfo <demonum>\\n<demonum> = * for currently recorded demo\\n\");\n\t\treturn;\n\t}\n\n\tif (!strcmp(Cmd_Argv(1), \"*\"))\n\t{\n\t\tif (!sv.mvdrecording || !demo.dest)\n\t\t{\n\t\t\tCon_Printf(\"Not recording demo!\\n\");\n\t\t\treturn;\n\t\t}\n\n//\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, demo.path, SV_MVDName2Txt(demo.name));\n// FIXME: dunno is this right, just using first dest, also may be we must use demo.dest->path instead of sv_demoDir\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, sv_demoDir.string, SV_MVDName2Txt(demo.dest->name));\n\t}\n\telse\n\t{\n\t\tname = SV_MVDTxTNum(Q_atoi(Cmd_Argv(1)));\n\n\t\tif (!name)\n\t\t{\n\t\t\tCon_Printf(\"invalid demo num\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, sv_demoDir.string, name);\n\t}\n\n\tif ((f = fopen(path, \"rt\")) == NULL)\n\t{\n\t\tCon_Printf(\"(empty)\\n\");\n\t\treturn;\n\t}\n\n\twhile (!feof(f))\n\t{\n\t\tbuf[fread (buf, 1, sizeof(buf) - 1, f)] = 0;\n\t\tCon_Printf(\"%s\", Q_yelltext(buf));\n\t}\n\n\tfclose(f);\n}\n\n#define MAXDEMOS\t\t\t10\n#define MAXDEMOS_RD_PACKET\t100\nvoid SV_LastScores_f (void)\n{\n\tint\t\tdemos = MAXDEMOS, i;\n\tchar\tbuf[512];\n\tFILE\t*f = NULL;\n\tchar\tpath[MAX_OSPATH];\n\tdir_t\tdir;\n\textern redirect_t sv_redirected;\n\n\tif (Cmd_Argc() > 2)\n\t{\n\t\tCon_Printf(\"usage: lastscores [<numlastdemos>]\\n<numlastdemos> = '0' for all demos\\n<numlastdemos> = '' for last %i demos\\n\", MAXDEMOS);\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() == 2)\n\t\tif ((demos = Q_atoi(Cmd_Argv(1))) <= 0)\n\t\t\tdemos = MAXDEMOS;\n\n\tdir = Sys_listdir(va(\"%s/%s\", fs_gamedir, sv_demoDir.string),\n\t                  sv_demoRegexp.string, SORT_BY_DATE);\n\tif (!dir.numfiles)\n\t{\n\t\tCon_Printf(\"No demos.\\n\");\n\t\treturn;\n\t}\n\n\tif (demos > dir.numfiles)\n\t\tdemos = dir.numfiles;\n\n\tif (demos > MAXDEMOS && GameStarted())\n\t\tCon_Printf(\"<numlastdemos> was decreased to %i: match is in progress.\\n\",\n\t\t\t\t\tdemos = MAXDEMOS);\n\n\tif (demos > MAXDEMOS_RD_PACKET && sv_redirected == RD_PACKET)\n\t\tCon_Printf(\"<numlastdemos> was decreased to %i: command from connectionless packet.\\n\",\n\t\t\t\t\tdemos = MAXDEMOS_RD_PACKET);\n\n\tCon_Printf(\"List of %d last demos:\\n\", demos);\n\n\tfor (i = dir.numfiles - demos; i < dir.numfiles; )\n\t{\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, sv_demoDir.string,\n\t\t\t\t\tSV_MVDName2Txt(dir.files[i].name));\n\n\t\tCon_Printf(\"%i. \", ++i);\n\t\tif ((f = fopen(path, \"rt\")) == NULL)\n\t\t\tCon_Printf(\"(empty)\\n\");\n\t\telse\n\t\t{\n\t\t\tif (!feof(f))\n\t\t\t{\n\t\t\t\tchar *nl;\n\n\t\t\t\tbuf[fread (buf, 1, sizeof(buf) - 1, f)] = 0;\n\t\t\t\tif ((nl = strchr(buf, '\\n')))\n\t\t\t\t\tnl[0] = 0;\n\t\t\t\tCon_Printf(\"%s\\n\", Q_yelltext((unsigned char*)buf));\n\t\t\t}\n\t\t\telse\n\t\t\t\tCon_Printf(\"(empty)\\n\");\n\t\t\tfclose(f);\n\t\t}\n\t}\n}\n\n#define STATS_LIMIT_DEFAULT 10\n#define STATS_LIMIT_MAX     50\nvoid SV_LastStats_f (void)\n{\n\tint limit = STATS_LIMIT_DEFAULT;\n\tint i;\n\tchar buf[512];\n\tFILE *f = NULL;\n\tchar path[MAX_OSPATH];\n\tdir_t dir;\n\textern redirect_t sv_redirected;\n\n\tif (sv_redirected != RD_PACKET)\n\t{\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() > 2)\n\t{\n\t\tCon_Printf(\"usage: laststats [<limit>]\\n<limit> = '0' for last %i stats\\n<limit> = 'n' for last n stats (max %i)\\n<limit> = '' (empty) for last %i stats\\n\", STATS_LIMIT_MAX, STATS_LIMIT_MAX, STATS_LIMIT_DEFAULT);\n\t\treturn;\n\t}\n\telse if (Cmd_Argc() == 2)\n\t{\n\t\tlimit = Q_atoi(Cmd_Argv(1));\n\n\t\tif (limit <= 0 || limit > STATS_LIMIT_MAX)\n\t\t{\n\t\t\tlimit = STATS_LIMIT_MAX;\n\t\t}\n\t}\n\n\tdir = Sys_listdir(va(\"%s/%s\", fs_gamedir, sv_demoDir.string), sv_demoRegexp.string,\n\t\t\tSORT_BY_DATE);\n\n\tif (!dir.numfiles)\n\t{\n\t\tCon_Printf(\"laststats 0:\\n\");\n\t\tCon_Printf(\"[]\\n\");\n\t\treturn;\n\t}\n\n\tif (limit > dir.numfiles)\n\t{\n\t\tlimit = dir.numfiles;\n\t}\n\n\tCon_Printf(\"laststats %i\\n\", limit);\n\tCon_Printf(\"[\\n\");\n\n\tfor (i = dir.numfiles - limit; i < dir.numfiles; i++)\n\t{\n\t\tsnprintf(path, MAX_OSPATH, \"%s/%s/%s\", fs_gamedir, sv_demoDir.string,\n\t\t\t\t\tSV_MVDName2Txt(dir.files[i].name));\n\n\t\tif ((f = fopen(path, \"rt\")) != NULL)\n\t\t{\n\t\t\tif (FS_FileLength(f) == 0) {\n\t\t\t    continue;\n\t\t\t}\n\n\t\t\tif (i != dir.numfiles - limit)\n\t\t\t{\n\t\t\t\tCon_Printf(\",\\n\");\n\t\t\t}\n\n\t\t\twhile (fread(buf, 1, sizeof(buf)-1, f))\n\t\t\t{\n\t\t\t\tCon_Printf(\"%s\", (unsigned char*)buf);\n\t\t\t\tmemset(buf, 0, sizeof(buf));\n\t\t\t}\n\t\t\tfclose(f);\n\t\t}\n\t}\n\n\tCon_Printf(\"\\n]\\n\");\n}\n\n// easyrecord helpers\n\nint Dem_CountPlayers (void)\n{\n\tint\ti, count;\n\n\tcount = 0;\n\tfor (i = 0; i < MAX_CLIENTS ; i++)\n\t{\n\t\tif (svs.clients[i].name[0] && !svs.clients[i].spectator)\n\t\t\tcount++;\n\t}\n\n\treturn count;\n}\n\nchar *Dem_Team (int num)\n{\n\tint i;\n\tstatic char *lastteam[2];\n\tqbool first = true;\n\tclient_t *client;\n\tstatic int index1 = 0;\n\n\tindex1 = 1 - index1;\n\n\tfor (i = 0, client = svs.clients; num && i < MAX_CLIENTS; i++, client++)\n\t{\n\t\tif (!client->name[0] || client->spectator)\n\t\t\tcontinue;\n\n\t\tif (first || strcmp(lastteam[index1], client->team))\n\t\t{\n\t\t\tfirst = false;\n\t\t\tnum--;\n\t\t\tlastteam[index1] = client->team;\n\t\t}\n\t}\n\n\tif (num)\n\t\treturn \"\";\n\n\treturn lastteam[index1];\n}\n\nchar *Dem_PlayerName (int num)\n{\n\tint i;\n\tclient_t *client;\n\n\tfor (i = 0, client = svs.clients; i < MAX_CLIENTS; i++, client++)\n\t{\n\t\tif (!client->name[0] || client->spectator)\n\t\t\tcontinue;\n\n\t\tif (!--num)\n\t\t\treturn client->name;\n\t}\n\n\treturn \"\";\n}\n\nchar *Dem_PlayerNameTeam (char *t)\n{\n\tint\ti;\n\tclient_t *client;\n\tstatic char\tn[1024];\n\tint\tsep;\n\n\tn[0] = 0;\n\n\tsep = 0;\n\n\tfor (i = 0, client = svs.clients; i < MAX_CLIENTS; i++, client++)\n\t{\n\t\tif (!client->name[0] || client->spectator)\n\t\t\tcontinue;\n\n\t\tif (strcmp(t, client->team)==0)\n\t\t{\n\t\t\tif (sep >= 1)\n\t\t\t\tstrlcat (n, \"_\", sizeof(n));\n\t\t\t//\t\t\t\tsnprintf (n, sizeof(n), \"%s_\", n);\n\t\t\tstrlcat (n, client->name, sizeof(n));\n\t\t\t//\t\t\tsnprintf (n, sizeof(n),\"%s%s\", n, client->name);\n\t\t\tsep++;\n\t\t}\n\t}\n\n\treturn n;\n}\n\nint Dem_CountTeamPlayers (char *t)\n{\n\tint\ti, count;\n\n\tcount = 0;\n\tfor (i = 0; i < MAX_CLIENTS ; i++)\n\t{\n\t\tif (svs.clients[i].name[0] && !svs.clients[i].spectator)\n\t\t\tif (strcmp(&svs.clients[i].team[0], t)==0)\n\t\t\t\tcount++;\n\t}\n\n\treturn count;\n}\n\nchar *quote (char *str)\n{\n\tchar *out, *s;\n\tif (!str)\n\t\treturn NULL;\n\tif (!*str)\n\t\treturn NULL;\n\n\ts = out = (char *) Q_malloc (strlen(str) * 2 + 1);\n\twhile (*str)\n\t{\n\t\tif (!isdigit(*str) && !isalpha(*str))\n\t\t\t*s++ = '\\\\';\n\t\t*s++ = *str++;\n\t}\n\t*s = '\\0';\n\treturn out;\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_demo_qtv.c",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    \n*/\n\n//\tsv_demo_qtv.c - misc QTV's code\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\nstatic cvar_t qtv_streamport     = {\"qtv_streamport\",      \"0\"};\nstatic cvar_t qtv_maxstreams     = {\"qtv_maxstreams\",      \"1\"};\nstatic cvar_t qtv_password       = {\"qtv_password\",         \"\"};\nstatic cvar_t qtv_pendingtimeout = {\"qtv_pendingtimeout\",  \"5\"}; // 5  seconds must be enough\nstatic cvar_t qtv_sayenabled     = {\"qtv_sayenabled\",      \"0\"}; // allow mod to override GameStarted() logic\ncvar_t qtv_streamtimeout         = {\"qtv_streamtimeout\",  \"45\"}; // 45 seconds\n\nstatic unsigned short int\tlistenport\t\t= 0;\nstatic double\t\t\t\twarned_time\t\t= 0;\n\nstatic mvddest_t *SV_InitStream (int socket1, netadr_t na, char *userinfo)\n{\n\tstatic int lastdest = 0;\n\tint count;\n\tmvddest_t *dst;\n\tchar name[sizeof(dst->qtvname)];\n\tchar address[sizeof(dst->qtvaddress)];\n\tint streamid = 0;\n\n\t// extract name\n\tstrlcpy(name, Info_ValueForKey(userinfo, \"name\"), sizeof(name));\n\tstrlcpy(address, Info_ValueForKey(userinfo, \"address\"), sizeof(address));\n\tstreamid = atoi(Info_ValueForKey(userinfo, \"streamid\"));\n\n\tcount = 0;\n\tfor (dst = demo.dest; dst; dst = dst->nextdest)\n\t{\n\t\tif (dst->desttype == DEST_STREAM)\n\t\t{\n\t\t\tif (name[0] && !strcasecmp(name, dst->qtvname))\n\t\t\t\treturn NULL; // duplicate name, well empty names may still duplicates...\n\n\t\t\tcount++;\n\t\t}\n\t}\n\n\tif ((int)qtv_maxstreams.value > 0 && count >= (int)qtv_maxstreams.value)\n\t\treturn NULL; //sorry\n\n\tdst = (mvddest_t *) Q_malloc (sizeof(mvddest_t));\n\n\tdst->desttype = DEST_STREAM;\n\tdst->socket = socket1;\n\tdst->maxcachesize = 65536;\t//is this too small?\n\tdst->cache = (char *) Q_malloc(dst->maxcachesize);\n\tdst->io_time = Sys_DoubleTime();\n\tdst->id = ++lastdest;\n\tdst->na = na;\n\n\tstrlcpy(dst->qtvname, name, sizeof(dst->qtvname));\n\tstrlcpy(dst->qtvaddress, address, sizeof(dst->qtvaddress));\n\tdst->qtvstreamid = streamid;\n\n\tif (dst->qtvname[0])\n\t\tCon_Printf (\"Connected to QTV(%s)\\n\", dst->qtvname);\n\telse\n\t\tCon_Printf (\"Connected to QTV\\n\");\n\n\treturn dst;\n}\n\nstatic void SV_MVD_InitPendingStream (int socket1, netadr_t na, qbool must_be_qizmo_tcp_connect)\n{\n\tmvdpendingdest_t *dst;\n\tunsigned int i;\n\tdst = (mvdpendingdest_t*) Q_malloc(sizeof(mvdpendingdest_t));\n\tdst->socket = socket1;\n\tdst->io_time = Sys_DoubleTime();\n\tdst->na = na;\n\tdst->must_be_qizmo_tcp_connect = must_be_qizmo_tcp_connect;\n\n\tstrlcpy(dst->challenge, NET_AdrToString(dst->na), sizeof(dst->challenge));\n\tfor (i = strlen(dst->challenge); i < sizeof(dst->challenge)-1; i++)\n\t\tdst->challenge[i] = rand()%(127-33) + 33;\t//generate a random challenge\n\n\tdst->nextdest = demo.pendingdest;\n\tdemo.pendingdest = dst;\n}\n\nvoid SV_MVDCloseStreams(void)\n{\n\tmvddest_t *d;\n\tmvdpendingdest_t *p;\n\n\tfor (d = demo.dest; d; d = d->nextdest)\n\t\tif (!d->error && d->desttype == DEST_STREAM)\n\t\t\td->error = true; // mark demo stream dest to close later\n\n\tfor (p = demo.pendingdest; p; p = p->nextdest)\n\t\tif (!p->error)\n\t\t\tp->error = true; // mark pending dest to close later\n}\n\nstatic void SV_CheckQTVPort(void)\n{\n\tqbool changed;\n\t// so user can't specify something stupid\n\tunsigned short int streamport = bound(0, (unsigned short int)qtv_streamport.value, 65534);\n\n\t// if we have non zero stream port, but fail to open listen socket, repeat open listen socket after some time\n\tchanged = ( streamport != listenport // port changed.\n\t    || (streamport  && NET_GetSocket(NS_SERVER, true) == INVALID_SOCKET && warned_time + 10 < Sys_DoubleTime()) // stream port non zero but socket still not open, lets open socket then.\n\t    || (!streamport && NET_GetSocket(NS_SERVER, true) != INVALID_SOCKET) // stream port is zero but socket still open, lets close socket then.\n\t);\n\n\t// port not changed\n\tif (!changed)\n\t\treturn;\n\n\tSV_MVDCloseStreams(); // also close ative connects if any, this will help actually close listen socket, so later we can bind to port again\n\n\twarned_time = Sys_DoubleTime(); // so we repeat warning time to time\n\n\t// port was changed, lets remember\n\tlistenport = streamport;\n\n\t// open/close/reopen TCP port.\n\tNET_InitServer_TCP(listenport);\n}\n\nvoid SV_MVDStream_Poll (void)\n{\n\tint client;\n\tqbool must_be_qizmo_tcp_connect = false;\n\tnetadr_t na;\n\tstruct sockaddr_storage addr;\n\tsocklen_t addrlen;\n\tint count;\n\tmvddest_t *dest;\n\tunsigned long _true = true;\n\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\tSV_CheckQTVPort(); // open/close/switch qtv port\n\n\tif (NET_GetSocket(NS_SERVER, true) == INVALID_SOCKET) // we can't accept connection from QTV\n\t{\n\t\tSV_MVDCloseStreams(); // also close ative connects if any, this will help actually close listen socket, so later we can bind to port again\n\t\treturn;\n\t}\n\n\taddrlen = sizeof(addr);\n\tclient = accept (NET_GetSocket(NS_SERVER, true), (struct sockaddr *)&addr, &addrlen);\n\n\tif (client == INVALID_SOCKET)\n\t\treturn;\n\n\tif (ioctlsocket (client, FIONBIO, &_true) == SOCKET_ERROR) {\n\t\tCon_Printf (\"SV_MVDStream_Poll: ioctl FIONBIO: (%i): %s\\n\", qerrno, strerror (qerrno));\n\t\tclosesocket(client);\n\t\treturn;\n\t}\n\n\tif (!TCP_Set_KEEPALIVE(client))\n\t{\n\t\tCon_Printf (\"SV_MVDStream_Poll: TCP_Set_KEEPALIVE: failed\\n\");\n\t\tclosesocket(client);\n\t\treturn;\n\t}\n\n\tif ((int)qtv_maxstreams.value > 0)\n\t{\n\t\tcount = 0;\n\t\tfor (dest = demo.dest; dest; dest = dest->nextdest)\n\t\t{\n\t\t\tif (dest->desttype == DEST_STREAM)\n\t\t\t{\n\t\t\t\tcount++;\n\t\t\t}\n\t\t}\n\n\t\tif (count >= (int)qtv_maxstreams.value)\n\t\t{\n\t\t\t// we use + 3 so there qizmo tcp connection have chance...\n\t\t\tif (count >= (int)qtv_maxstreams.value + 3)\n\t\t\t{\n\t\t\t\t//sorry, there really way too much connections\n\t\t\t\tchar *goawaymessage = \"QTVSV 1\\nERROR: This server enforces a limit on the number of proxies connected at any one time. Please try again later\\n\\n\";\n\n\t\t\t\tsend(client, goawaymessage, strlen(goawaymessage), 0);\n\t\t\t\tclosesocket(client);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// ok, give qizmo tcp connect a chance, but only and only for tcp connect\n\t\t\t\tmust_be_qizmo_tcp_connect = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tSockadrToNetadr(&addr, &na);\n\tCon_Printf(\"MVD streaming client connected from %s\\n\", NET_AdrToString(na));\n\n\tSV_MVD_InitPendingStream(client, na, must_be_qizmo_tcp_connect);\n}\n\nvoid SV_MVD_RunPendingConnections (void)\n{\n\tunsigned short ushort_result;\n\tchar *e;\n\tint len;\n\tmvdpendingdest_t *p;\n\tmvdpendingdest_t *np;\n\tchar userinfo[1024] = {0};\n\n\tif (!demo.pendingdest)\n\t\treturn;\n\n\tfor (p = demo.pendingdest; p; p = p->nextdest)\n\t\tif (p->io_time + qtv_pendingtimeout.value <= Sys_DoubleTime())\n\t\t{\n\t\t\tCon_Printf(\"Pending dest timeout\\n\");\n\t\t\tp->error = true;\n\t\t}\n\n\twhile (demo.pendingdest && demo.pendingdest->error)\n\t{\n\t\tnp = demo.pendingdest->nextdest;\n\n\t\tif (demo.pendingdest->socket != -1)\n\t\t\tclosesocket(demo.pendingdest->socket);\n\t\tQ_free(demo.pendingdest);\n\t\tdemo.pendingdest = np;\n\t}\n\n\tfor (p = demo.pendingdest; p && p->nextdest; p = p->nextdest)\n\t{\n\t\tif (p->nextdest->error)\n\t\t{\n\t\t\tnp = p->nextdest->nextdest;\n\t\t\tif (p->nextdest->socket != -1)\n\t\t\t\tclosesocket(p->nextdest->socket);\n\t\t\tQ_free(p->nextdest);\n\t\t\tp->nextdest = np;\n\t\t}\n\t}\n\n\tfor (p = demo.pendingdest; p; p = p->nextdest)\n\t{\n\t\tif (p->outsize && !p->error)\n\t\t{\n\t\t\tlen = send(p->socket, p->outbuffer, p->outsize, 0);\n\n\t\t\tif (len == 0) //client died\n\t\t\t{\n//\t\t\t\tp->error = true;\n\t\t\t\t// man says: The calls return the number of characters sent, or -1 if an error occurred.   \n\t\t\t\t// so 0 is legal or what?\n\t\t\t}\n\t\t\telse if (len > 0)\t//we put some data through\n\t\t\t{\n\t\t\t\tp->io_time = Sys_DoubleTime(); // update IO activity\n\n\t\t\t\t//move up the buffer\n\t\t\t\tp->outsize -= len;\n\t\t\t\tmemmove(p->outbuffer, p->outbuffer+len, p->outsize );\n\n\t\t\t}\n\t\t\telse\n\t\t\t{ //error of some kind. would block or something\n\t\t\t\tif (qerrno != EWOULDBLOCK && qerrno != EAGAIN)\n\t\t\t\t\tp->error = true;\n\t\t\t}\n\t\t}\n\n\t\tif (!p->error)\n\t\t{\n\t\t\tlen = recv(p->socket, p->inbuffer + p->insize, sizeof(p->inbuffer) - p->insize - 1, 0);\n\n\t\t\tif (len > 0)\n\t\t\t{ //fixme: cope with extra \\rs\n\t\t\t\tchar *end;\n\n\t\t\t\tp->io_time = Sys_DoubleTime(); // update IO activity\n\n\t\t\t\tp->insize += len;\n\t\t\t\tp->inbuffer[p->insize] = 0;\n\n// TCPCONNECT -->\n// kinda hack to allow both qtv and qizmo tcp connection work on the same server port\n\n\t\t\t\t// check for qizmo tcp connection\n\t\t\t\tif (p->insize >=6)\n\t\t\t\t{\n\t\t\t\t\tif (strncmp(p->inbuffer, \"qizmo\\n\", 6))\n\t\t\t\t\t{\n\t\t\t\t\t\t// no. seems it like QTV client... but if we expect qizmo so we better drop it ASAP\n\t\t\t\t\t\tif (p->must_be_qizmo_tcp_connect)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tp->error = true;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// new qizmo tcpconnection\n\n\t\t\t\t\t\textern svtcpstream_t *sv_tcp_connection_new(int sock, netadr_t from, char *buf, int buf_len, qbool link);\n\t\t\t\t\t\textern int sv_tcp_connection_count(void);\n\n\t\t\t\t\t\tsvtcpstream_t *st = sv_tcp_connection_new(p->socket, p->na, p->inbuffer, p->insize, true);\n\t\t\t\t\t\tint _true = true;\n\n\t\t\t\t\t\t// set some timeout\n\t\t\t\t\t\tst->timeouttime = Sys_DoubleTime() + 10;\n\t\t\t\t\t\t// send protocol confirmation\n\t\t\t\t\t\tif (send(st->socketnum, \"qizmo\\n\", 6, 0) != 6)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tst->drop = true; // failed miserable to send some chunk of data\t\t\t\t\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (sv_tcp_connection_count() >= MAX_CLIENTS)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tst->drop = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (setsockopt(st->socketnum, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)) == -1)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tCon_DPrintf (\"SV_MVD_RunPendingConnections: setsockopt: (%i): %s\\n\", qerrno, strerror(qerrno));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tp->error = true;\n\t\t\t\t\t\tp->socket = -1;\t//so it's not cleared wrongly.\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (p->must_be_qizmo_tcp_connect)\n\t\t\t\t\tcontinue; // HACK, this stream should not be allowed but just checked ONLY AND ONLY for qizmo tcp connection\n// <--TCPCONNECT\n\n\t\t\t\tfor (end = p->inbuffer; ; end++)\n\t\t\t\t{\n\t\t\t\t\tif (*end == '\\0')\n\t\t\t\t\t{\n\t\t\t\t\t\tend = NULL;\n\t\t\t\t\t\tbreak;\t//not enough data\n\t\t\t\t\t}\n\n\t\t\t\t\tif (end[0] == '\\n')\n\t\t\t\t\t{\n\t\t\t\t\t\tif (end[1] == '\\n')\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tend[1] = '\\0';\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (end)\n\t\t\t\t{ //we found the end of the header\n\t\t\t\t\tchar *start, *lineend;\n\t\t\t\t\tint versiontouse = 0;\n\t\t\t\t\tint raw = 0;\n\t\t\t\t\tchar password[256] = \"\";\n\t\t\t\t\ttypedef enum {\n\t\t\t\t\t\tQTVAM_NONE,\n\t\t\t\t\t\tQTVAM_PLAIN,\n\t\t\t\t\t\tQTVAM_CCITT,\n\t\t\t\t\t\tQTVAM_MD4,\n\t\t\t\t\t\tQTVAM_SHA3_512,\n\t\t\t\t\t} authmethod_t;\n\t\t\t\t\tauthmethod_t authmethod = QTVAM_NONE;\n\t\t\t\t\tstart = p->inbuffer;\n\n\t\t\t\t\tlineend = strchr(start, '\\n');\n\t\t\t\t\tif (!lineend)\n\t\t\t\t\t{\n//\t\t\t\t\t\tchar *e;\n//\t\t\t\t\t\te = \"This is a QTV server.\";\n//\t\t\t\t\t\tsend(p->socket, e, strlen(e), 0);\n\n\t\t\t\t\t\tp->error = true;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\t*lineend = '\\0';\n\t\t\t\t\tCOM_ParseToken(start, NULL);\n\t\t\t\t\tstart = lineend+1;\n\t\t\t\t\tif (strcmp(com_token, \"QTV\"))\n\t\t\t\t\t{ //it's an error if it's not qtv.\n\t\t\t\t\t\tp->error = true;\n\t\t\t\t\t\tlineend = strchr(start, '\\n');\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tfor(;;)\n\t\t\t\t\t{\n\t\t\t\t\t\tlineend = strchr(start, '\\n');\n\t\t\t\t\t\tif (!lineend)\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t*lineend = '\\0';\n\t\t\t\t\t\tstart = COM_ParseToken(start, NULL);\n\t\t\t\t\t\tif (*start == ':')\n\t\t\t\t\t\t{\n//VERSION: a list of the different qtv protocols supported. Multiple versions can be specified. The first is assumed to be the prefered version.\n//RAW: if non-zero, send only a raw mvd with no additional markup anywhere (for telnet use). Doesn't work with challenge-based auth, so will only be accepted when proxy passwords are not required.\n//AUTH: specifies an auth method, the exact specs varies based on the method\n//\t\tPLAIN: the password is sent as a PASSWORD line\n//\t\tMD4: the server responds with an \"AUTH: MD4\\n\" line as well as a \"CHALLENGE: somerandomchallengestring\\n\" line, the client sends a new 'initial' request with CHALLENGE: MD4\\nRESPONSE: hexbasedmd4checksumhere\\n\"\n//\t\tCCITT: same as md4, but using the CRC stuff common to all quake engines.\n//\t\tif the supported/allowed auth methods don't match, the connection is silently dropped.\n//SOURCE: which stream to play from, DEFAULT is special. Without qualifiers, it's assumed to be a tcp address.\n//COMPRESSION: Suggests a compression method (multiple are allowed). You'll get a COMPRESSION response, and compression will begin with the binary data.\n\n\t\t\t\t\t\t\tstart = start+1;\n\t\t\t\t\t\t\tCon_Printf(\"qtv, got (%s) (%s)\\n\", com_token, start);\n\t\t\t\t\t\t\tif (!strcmp(com_token, \"VERSION\"))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstart = COM_ParseToken(start, NULL);\n\t\t\t\t\t\t\t\tif (atoi(com_token) == 1)\n\t\t\t\t\t\t\t\t\tversiontouse = 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if (!strcmp(com_token, \"RAW\"))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstart = COM_ParseToken(start, NULL);\n\t\t\t\t\t\t\t\traw = atoi(com_token);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if (!strcmp(com_token, \"PASSWORD\"))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstart = COM_ParseToken(start, NULL);\n\t\t\t\t\t\t\t\tstrlcpy(password, com_token, sizeof(password));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if (!strcmp(com_token, \"AUTH\"))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tauthmethod_t thisauth;\n\t\t\t\t\t\t\t\tstart = COM_ParseToken(start, NULL);\n\t\t\t\t\t\t\t\tif (!strcmp(com_token, \"NONE\"))\n\t\t\t\t\t\t\t\t\tthisauth = QTVAM_PLAIN;\n\t\t\t\t\t\t\t\telse if (!strcmp(com_token, \"PLAIN\"))\n\t\t\t\t\t\t\t\t\tthisauth = QTVAM_PLAIN;\n\t\t\t\t\t\t\t\telse if (!strcmp(com_token, \"CCITT\"))\n\t\t\t\t\t\t\t\t\tthisauth = QTVAM_CCITT;\n\t\t\t\t\t\t\t\telse if (!strcmp(com_token, \"MD4\"))\n\t\t\t\t\t\t\t\t\tthisauth = QTVAM_MD4;\n\t\t\t\t\t\t\t\telse if (!strcmp(com_token, \"SHA3_512\"))\n\t\t\t\t\t\t\t\t\tthisauth = QTVAM_SHA3_512;\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tthisauth = QTVAM_NONE;\n\t\t\t\t\t\t\t\t\tCon_DPrintf(\"qtv: received unrecognised auth method (%s)\\n\", com_token);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (authmethod < thisauth)\n\t\t\t\t\t\t\t\t\tauthmethod = thisauth;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if (!strcmp(com_token, \"SOURCE\"))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t//servers don't support source, and ignore it.\n\t\t\t\t\t\t\t\t//source is only useful for qtv proxy servers.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if (!strcmp(com_token, \"COMPRESSION\"))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t//compression not supported yet\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if (!strcmp(com_token, \"USERINFO\"))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstart = COM_ParseToken(start, NULL);\n\t\t\t\t\t\t\t\tstrlcpy(userinfo, com_token, sizeof(userinfo));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t//not recognised.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstart = lineend+1;\n\t\t\t\t\t}\n\n\t\t\t\t\tlen = (end - p->inbuffer)+2;\n\t\t\t\t\tp->insize -= len;\n\t\t\t\t\tmemmove(p->inbuffer, p->inbuffer + len, p->insize);\n\t\t\t\t\tp->inbuffer[p->insize] = 0;\n\n\t\t\t\t\te = NULL;\n\t\t\t\t\tif (p->hasauthed)\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\telse if (!*qtv_password.string)\n\t\t\t\t\t\tp->hasauthed = true; //no password, no need to auth.\n\t\t\t\t\telse if (*password)\n\t\t\t\t\t{\n\t\t\t\t\t\tswitch (authmethod)\n\t\t\t\t\t\t{\n\t\t\t\t\t\tcase QTVAM_NONE:\n\t\t\t\t\t\t\te = (\"QTVSV 1\\n\"\n\t\t\t\t\t\t\t\t \"PERROR: You need to provide a common auth method.\\n\\n\");\n\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\tcase QTVAM_PLAIN:\n\t\t\t\t\t\t\tp->hasauthed = !strcmp(qtv_password.string, password);\n\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\tcase QTVAM_CCITT:\n\t\t\t\t\t\t\tCRC_Init(&ushort_result);\n\t\t\t\t\t\t\tCRC_AddBlock(&ushort_result, (byte *) p->challenge, strlen(p->challenge));\n\t\t\t\t\t\t\tCRC_AddBlock(&ushort_result, (byte *) qtv_password.string, strlen(qtv_password.string));\n\t\t\t\t\t\t\tp->hasauthed = (ushort_result == Q_atoi(password));\n\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\tcase QTVAM_MD4:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tchar hash[512];\n\t\t\t\t\t\t\t\tint md4sum[4];\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tsnprintf (hash, sizeof(hash), \"%s%s\", p->challenge, qtv_password.string);\n\t\t\t\t\t\t\t\tCom_BlockFullChecksum (hash, strlen(hash), (unsigned char*)md4sum);\n\t\t\t\t\t\t\t\tsnprintf (hash, sizeof(hash), \"%X%X%X%X\", md4sum[0], md4sum[1], md4sum[2], md4sum[3]);\n\t\t\t\t\t\t\t\tp->hasauthed = !strcmp(password, hash);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\tcase QTVAM_SHA3_512:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsha3_context c;\n\t\t\t\t\t\t\t\tconst uint8_t *byte_hash;\n\t\t\t\t\t\t\t\tchar hash[SHA3_512_DIGEST_HEX_STR_SIZE] = {0};\n\n\t\t\t\t\t\t\t\tsha3_Init512(&c);\n\t\t\t\t\t\t\t\tsha3_Update(&c, p->challenge, strlen(p->challenge));\n\t\t\t\t\t\t\t\tsha3_Update(&c, qtv_password.string, strlen(qtv_password.string));\n\t\t\t\t\t\t\t\tbyte_hash = sha3_Finalize(&c);\n\t\t\t\t\t\t\t\tsha3_512_ByteToHex(hash, byte_hash);\n\t\t\t\t\t\t\t\tp->hasauthed = !strcmp(password, hash);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\te = (\"QTVSV 1\\n\"\n\t\t\t\t\t\t\t\t \"PERROR: FTEQWSV bug detected.\\n\\n\");\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!p->hasauthed && !e)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (raw)\n\t\t\t\t\t\t\t\te = \"\";\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\te = (\"QTVSV 1\\n\"\n\t\t\t\t\t\t\t\t\t \"PERROR: Bad password.\\n\\n\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t//no password, and not automagically authed\n\t\t\t\t\t\tswitch (authmethod)\n\t\t\t\t\t\t{\n\t\t\t\t\t\tcase QTVAM_NONE:\n\t\t\t\t\t\t\tif (raw)\n\t\t\t\t\t\t\t\te = \"\";\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\te = (\"QTVSV 1\\n\"\n\t\t\t\t\t\t\t\t\t \"PERROR: You need to provide a common auth method.\\n\\n\");\n\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\tcase QTVAM_PLAIN:\n\t\t\t\t\t\t\tp->hasauthed = !strcmp(qtv_password.string, password);\n\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\tcase QTVAM_CCITT:\n\t\t\t\t\t\t\te =\t(\"QTVSV 1\\n\"\n\t\t\t\t\t\t\t\t\"AUTH: CCITT\\n\"\n\t\t\t\t\t\t\t\t\"CHALLENGE: \");\n\n\t\t\t\t\t\t\tsend(p->socket, e, strlen(e), 0);\n\t\t\t\t\t\t\tsend(p->socket, p->challenge, strlen(p->challenge), 0);\n\t\t\t\t\t\t\te = \"\\n\\n\";\n\t\t\t\t\t\t\tsend(p->socket, e, strlen(e), 0);\n\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\tcase QTVAM_MD4:\n\t\t\t\t\t\t\te = (\"QTVSV 1\\n\"\n\t\t\t\t\t\t\t\t\"AUTH: MD4\\n\"\n\t\t\t\t\t\t\t\t\"CHALLENGE: \");\n\n\t\t\t\t\t\t\tsend(p->socket, e, strlen(e), 0);\n\t\t\t\t\t\t\tsend(p->socket, p->challenge, strlen(p->challenge), 0);\n\t\t\t\t\t\t\te = \"\\n\\n\";\n\t\t\t\t\t\t\tsend(p->socket, e, strlen(e), 0);\n\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\tcase QTVAM_SHA3_512:\n\t\t\t\t\t\t\te = (\"QTVSV 1\\n\"\n\t\t\t\t\t\t\t\t\"AUTH: SHA3_512\\n\"\n\t\t\t\t\t\t\t\t\"CHALLENGE: \");\n\n\t\t\t\t\t\t\tsend(p->socket, e, strlen(e), 0);\n\t\t\t\t\t\t\tsend(p->socket, p->challenge, strlen(p->challenge), 0);\n\t\t\t\t\t\t\te = \"\\n\\n\";\n\t\t\t\t\t\t\tsend(p->socket, e, strlen(e), 0);\n\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\te = (\"QTVSV 1\\n\"\n\t\t\t\t\t\t\t\t \"PERROR: FTEQWSV bug detected.\\n\\n\");\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (e)\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\telse if (!versiontouse)\n\t\t\t\t\t{\n\t\t\t\t\t\te = (\"QTVSV 1\\n\"\n\t\t\t\t\t\t\t \"PERROR: Incompatible version (valid version is v1)\\n\\n\");\n\t\t\t\t\t}\n\t\t\t\t\telse if (raw)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (p->hasauthed == false)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\te =\t\"\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmvddest_t *tmpdest;\n\n\t\t\t\t\t\t\tif ((tmpdest = SV_InitStream(p->socket, p->na, userinfo)))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif (!SV_MVD_Record(tmpdest, false))\n\t\t\t\t\t\t\t\t\tDestClose(tmpdest, false); // can't start record for some reason, close dest then\n\n\t\t\t\t\t\t\t\tp->socket = -1;\t//so it's not cleared wrongly.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// RAW mode, can't sent error, right?\n//\t\t\t\t\t\t\t\te = (\"QTVSV 1\\n\"\n//\t\t\t\t\t\t\t\t\t\"ERROR: Can't init stream, probably server reach a limit on the number of proxies connected at any one time.\\n\\n\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tp->error = true;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tif (p->hasauthed == true)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmvddest_t *tmpdest;\n\n\t\t\t\t\t\t\tif ((tmpdest = SV_InitStream(p->socket, p->na, userinfo)))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\te = (\"QTVSV 1\\n\"\n\t\t\t\t\t\t\t\t \t\"BEGIN\\n\\n\");\n\t\t\t\t\t\t\t\tsend(p->socket, e, strlen(e), 0);\n\t\t\t\t\t\t\t\te = NULL;\n\n\t\t\t\t\t\t\t\tif (!SV_MVD_Record(tmpdest, false))\n\t\t\t\t\t\t\t\t\tDestClose(tmpdest, false); // can't start record for some reason, close dest then\n\n\t\t\t\t\t\t\t\tp->socket = -1;\t//so it's not cleared wrongly.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\te = (\"QTVSV 1\\n\"\n\t\t\t\t\t\t\t\t\t\"ERROR: Can't init stream, probably server reach a limit on the number of proxies connected at any one time.\\n\\n\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\te = (\"QTVSV 1\\n\"\n\t\t\t\t\t\t\t\t\"PERROR: You need to provide a password.\\n\\n\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tp->error = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (e)\n\t\t\t\t\t{\n\t\t\t\t\t\tsend(p->socket, e, strlen(e), 0);\n\t\t\t\t\t\tp->error = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (len == 0)\n\t\t\t\tp->error = true;\n\t\t\telse\n\t\t\t{\t//error of some kind. would block or something\n\t\t\t\tint err;\n\t\t\t\terr = qerrno;\n\t\t\t\tif (err != EWOULDBLOCK && err != EAGAIN)\n\t\t\t\t\tp->error = true;\n\t\t\t}\n\t\t}\n\t}\n}\n\n//============================================================\n//\n// QTV user input\n//\n//============================================================\n\n// { qtv commands\n\n// say [say_game] text\n// say_team [say_game] text\n// say_game text\n\nvoid QTVcmd_Say_f(mvddest_t *d)\n{\n\tqbool gameStarted;\n\tclient_t *client;\n\tint\t\tj;\n\tchar\t*p;\n\tchar\ttext[1024], text2[1024], *cmd;\n\n\tif (Cmd_Argc () < 2)\n\t\treturn;\n\n\tif (qtv_sayenabled.value || !strcasecmp(Info_ValueForKey(svs.info, \"status\"), \"Countdown\"))\n\t\tgameStarted\t= false; // if status is \"Countdown\" then game is not started yet\n\telse\n\t\tgameStarted = GameStarted();\n\n\tp = Cmd_Args();\n\n\tif (*p == '\"' && (j = strlen(p)) > 2)\n\t{\n\t\tp[j-1] = 0;\n\t\tp++;\n\t}\n\n\tcmd = Cmd_Argv(0);\n\n\t// strip leading say_game but not in case of \"cmd say_game say_game\"\n\tif (strcmp(cmd, \"say_game\") && !strncasecmp(p, \"say_game \", sizeof(\"say_game \") - 1))\n\t{\n\t\tp += sizeof(\"say_game \") - 1;\n\t}\n\n\tif (!strcmp(cmd, \"say_game\"))\n\t\tcmd = \"say\"; // this makes qtv_%s_game looks right\n\n\tif (!strcmp(cmd, \"say_team\"))\n\t\tgameStarted = true; // send to specs only\n\n\tif (gameStarted)\n\t\tcmd = \"say_team\"; // we can accept only this command, since we will send to specs only\n\n\t// for clients and demo\n\tsnprintf(text, sizeof(text), \"#0:qtv_%s_game:#%d:%s: %s\\n\", cmd, d->id, d->qtvname, p);\n\t// for server console and logs\n\tsnprintf(text2, sizeof(text2), \"qtv: #0:qtv_%s_game:#%d:%s: %s\\n\", cmd, d->id, d->qtvname, p);\n\n\tfor (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)\n\t{\n\t\tif (client->state != cs_spawned)\n\t\t\tcontinue;\n\n\t\tif (gameStarted && !client->spectator)\n\t\t\tcontinue; // game started, don't send QTV chat to players, specs still get QTV chat\n\n\t\tSV_ClientPrintf2(client, PRINT_CHAT, \"%s\", text);\n\t}\n\n\tif (sv.mvdrecording) {\n\t\tsizebuf_t\t\tmsg;\n\t\tbyte\t\t\tmsg_buf[1024];\n\n\t\tSZ_InitEx(&msg, msg_buf, sizeof(msg_buf), true);\n\t\tMSG_WriteByte (&msg, svc_print);\n\t\tMSG_WriteByte (&msg, PRINT_CHAT);\n\t\tMSG_WriteString (&msg, text);\n\n\t\tDemoWriteQTV(&msg);\n\t}\n\n\tSys_Printf(\"%s\", text2);\n\tSV_Write_Log(CONSOLE_LOG, 1, text2);\n}\n\n// }\n\n// {\n\nstatic qtvuser_t *QTVsv_UserById(mvddest_t *d, int id)\n{\n\tqtvuser_t *current;\n\n\tfor (current = d->qtvuserlist; current; current = current->next)\n\t\tif (current->id == id)\n\t\t\treturn current;\n\n\treturn NULL;\n}\n\nstatic void QTVsv_SetUser(qtvuser_t *to, qtvuser_t *from)\n{\n\t*to = *from;\n}\n\nstatic int QTVsv_UsersCount(mvddest_t *d)\n{\n\tqtvuser_t *current;\n\tint c = 0;\n\n\tfor (current = d->qtvuserlist; current; current = current->next)\n\t\tc++;\n\n\treturn c;\n}\n\n// allocate data and set fields, perform linkage to qtvuserlist\n// Well, instead of QTVsv_NewUser(int id, char *name, ...) I pass params with single qtvuser_t *user struct, well its OK for current struct.\nstatic qtvuser_t *QTVsv_NewUser(mvddest_t *d, qtvuser_t *user)\n{\n\t// check, may be user alredy exist, so reuse it\n\tqtvuser_t *newuser = QTVsv_UserById(d, user->id);\n\n\tif (!newuser)\n\t{\n\t\t// user does't exist, alloc data\n\t\tif (QTVsv_UsersCount(d) > 2048)\n\t\t{\n\t\t\tCon_Printf(\"QTVsv_NewUser: too much users, haxors? Dropping dest\\n\");\n\t\t\td->error = true; // drop dest later\n\t\t\treturn NULL;\n\t\t}\n\n\t\tnewuser = Q_malloc(sizeof(*newuser)); // alloc\n\n\t\tQTVsv_SetUser(newuser, user);\n\n\t\t// perform linkage\n\t\tnewuser->next = d->qtvuserlist;\n\t\td->qtvuserlist = newuser;\n\t}\n\telse\n\t{\n\t\t// we do not need linkage, just save current\n\t\tqtvuser_t *oldnext = newuser->next; // we need save this before assign all fields\n\n\t\tQTVsv_SetUser(newuser, user);\n\n\t\tnewuser->next = oldnext;\n\t}\n\n\treturn newuser;\n}\n\n// free data, perform unlink if requested\nstatic void QTVsv_FreeUser(mvddest_t *d, qtvuser_t *user, qbool unlink)\n{\n\tif (!user)\n\t\treturn;\n\n\tif (unlink)\n\t{\n\t\tqtvuser_t *next, *prev, *current;\n\n\t\tprev = NULL;\n\t\tcurrent = d->qtvuserlist;\n\n\t\tfor ( ; current; )\n\t\t{\n\t\t\tnext = current->next;\n\n\t\t\tif (user == current)\n\t\t\t{\n\t\t\t\tif (prev)\n\t\t\t\t\tprev->next = next;\n\t\t\t\telse\n\t\t\t\t\td->qtvuserlist = next;\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tprev = current;\n\t\t\tcurrent = next;\n\t\t}\n\t}\n\n\tQ_free(user);\n}\n\n// free whole qtvuserlist\nvoid QTVsv_FreeUserList(mvddest_t *d)\n{\n\tqtvuser_t *next, *current;\n\n\tcurrent = d->qtvuserlist;\n\n\tfor ( ; current; current = next)\n\t{\n\t\tnext = current->next;\n\t\tQTVsv_FreeUser(d, current, false);\n\t}\n\n\td->qtvuserlist = NULL;\n}\n\n#define QTV_EVENT_PREFIX \"QTV: \"\n\n// user join qtv\nvoid QTVsv_JoinEvent(mvddest_t *d, qtvuser_t *user)\n{\n\t// make it optional message\n//\tif (!qtv_event_join.string[0])\n//\t\treturn;\n\n\t// do not show \"user joined\" at moment of connection to QTV, it mostly QTV just spammed userlist to us.\n//\tif (cls.state <= ca_demostart)\n//\t\treturn;\n\n\tif (QTVsv_UserById(d, user->id))\n\t{\n\t\t// we alredy have this user, do not double trigger\n\t\treturn;\n\t}\n\n\tCon_Printf(\"%s%s: %s%s\\n\", QTV_EVENT_PREFIX, d->qtvname, user->name, /*qtv_event_join.string*/ \" join\");\n}\n\n// user leaved/left qtv\nvoid QTVsv_LeaveEvent(mvddest_t *d, qtvuser_t *user)\n{\n\tqtvuser_t *olduser;\n\n\t// make it optional message\n//\tif (!qtv_event_leave.string[0])\n//\t\treturn;\n\n\tif (!(olduser = QTVsv_UserById(d, user->id)))\n\t{\n\t\t// we do not have this user\n\t\treturn;\n\t}\n\n\tCon_Printf(\"%s%s: %s%s\\n\", QTV_EVENT_PREFIX, d->qtvname, olduser->name, /*qtv_event_leave.string*/ \" left\");\n}\n\n// user changed name on qtv\nvoid QTVsv_ChangeEvent(mvddest_t *d, qtvuser_t *user)\n{\n\tqtvuser_t *olduser;\n\n\t// well, too spammy, make it as option\n//\tif (!qtv_event_changename.string[0])\n//\t\treturn;\n\n\tif (!(olduser = QTVsv_UserById(d, user->id)))\n\t{\n\t\t// we do not have this user yet\n\t\tCon_DPrintf(\"qtv: change event without olduser\\n\");\n\t\treturn;\n\t}\n\n\tCon_Printf(\"%s%s: %s%s%s\\n\", QTV_EVENT_PREFIX, d->qtvname, olduser->name, /*qtv_event_changename.string*/ \" changed name to \", user->name);\n}\n\nstatic void QTVcmd_QtvUserList_f(mvddest_t *d)\n{\n\tqtvuser_t\t\ttmpuser;\n\tqtvuserlist_t\taction;\n\tint\t\t\t\tcnt = 1;\n\t\n\tmemset(&tmpuser, 0, sizeof(tmpuser));\n\n\t// action id [\\\"name\\\"]\n\n//\tCmd_TokenizeString( s );\n\n\taction \t\t= atoi( Cmd_Argv( cnt++ ) );\n\ttmpuser.id\t= atoi( Cmd_Argv( cnt++ ) );\n\tstrlcpy(tmpuser.name, Cmd_Argv( cnt++ ), sizeof(tmpuser.name)); // name is optional in some cases\n\n\tswitch ( action )\n\t{\n\t\tcase QUL_ADD:\n\t\t\tQTVsv_JoinEvent(d, &tmpuser);\n\t\t\tQTVsv_NewUser(d, &tmpuser);\n\n\t\tbreak;\n\t\t\n\t\tcase QUL_CHANGE:\n\t\t\tQTVsv_ChangeEvent(d, &tmpuser);\n\t\t\tQTVsv_NewUser(d, &tmpuser);\n\n\t\tbreak;\n\n\t\tcase QUL_DEL:\n\t\t\tQTVsv_LeaveEvent(d, &tmpuser);\n\t\t\tQTVsv_FreeUser(d, QTVsv_UserById(d, tmpuser.id), true);\n\n\t\tbreak;\n\n\t\tdefault:\n\t\t\tCon_Printf(\"Parse_QtvUserList: unknown action %d\\n\", action);\n\n\t\treturn;\n\t}\n}\n\n// this issued remotely by user\nvoid Cmd_Qtvusers_f (void)\n{\n\tmvddest_t *d;\n\tqbool found = false;\n\n\tfor (d = demo.dest; d; d = d->nextdest)\n\t{\n\t\tqtvuser_t *current;\n\t\tint c;\n\n\t\tif (d->desttype != DEST_STREAM)\n\t\t\tcontinue;\n\n\t\tfound = true;\n\n\t\tc = 0;\n\t\tCon_Printf (\"Proxy name: %s, id: %d\\n\", d->qtvname, d->id);\n\t\tCon_Printf (\"userid name\\n\");\n\t\tCon_Printf (\"------ ----\\n\");\n\n\t\tfor (current = d->qtvuserlist; current; current = current->next)\n\t\t{\n\t\t\tCon_Printf (\"%6i %s\\n\", current->id, current->name);\n\t\t\tc++;\n\t\t}\n\n\t\tCon_Printf (\"%i total users\\n\", c);\n\t}\n\n\tif (!found)\n\t{\n\t\tCon_Printf (\"no QTVs connected\\n\");\n\t\treturn;\n\t}\n}\n\n\n// }\n\nchar QTV_cmd[MAX_PROXY_INBUFFER]; // global so it does't allocated on stack, this save some CPU I think\n\ntypedef struct\n{\n\tchar\t*name;\n\tvoid\t(*func) (mvddest_t *d);\n}\nqtv_ucmd_t;\n\nstatic qtv_ucmd_t ucmds[] =\n{\n\t{\"say\", \t\t\tQTVcmd_Say_f},\n\t{\"say_team\",\t\tQTVcmd_Say_f},\n\t{\"say_game\",\t\tQTVcmd_Say_f},\n\n\t{\"qul\",\t\t\t\tQTVcmd_QtvUserList_f},\n\n\t{NULL, NULL}\n};\n\n// { qtv utils\n\ntypedef struct {\n\tunsigned int readpos;\n\tunsigned int cursize;\n\tunsigned int maxsize;\n\tchar *data;\n\tunsigned int startpos;\n//\tqbool overflowed;\n//\tqbool allowoverflow;\n} netmsg_t;\n\nstatic void InitNetMsg(netmsg_t *b, char *buffer, int bufferlength)\n{\n\tmemset(b, 0, sizeof(netmsg_t));\n\n\tb->data    = buffer;\n\tb->maxsize = bufferlength;\n}\n\n//probably not the place for these any more..\nstatic unsigned char ReadByte(netmsg_t *b)\n{\n\tif (b->readpos >= b->cursize)\n\t{\n\t\tb->readpos = b->cursize+1;\n\t\treturn 0;\n\t}\n\treturn b->data[b->readpos++];\n}\n\nstatic unsigned short ReadShort(netmsg_t *b)\n{\n\tint b1, b2;\n\tb1 = ReadByte(b);\n\tb2 = ReadByte(b);\n\n\treturn b1 | (b2<<8);\n}\n\nstatic void ReadString(netmsg_t *b, char *string, int maxlen)\n{\n\tmaxlen--;\t//for null terminator\n\twhile(maxlen)\n\t{\n\t\t*string = ReadByte(b);\n\t\tif (!*string)\n\t\t\treturn;\n\t\tstring++;\n\t\tmaxlen--;\n\t}\n\t*string++ = '\\0';\t//add the null\n}\n\n// }\n\nvoid QTV_ExecuteCmd(mvddest_t *d, char *cmd)\n{\n    char *arg0;\n    qbool found = false;\n\tqtv_ucmd_t *u;\n\n//\tSys_Printf(\"qtv cmd: %s\\n\", cmd);\n\n\tCmd_TokenizeString (cmd);\n\n\targ0 = Cmd_Argv(0);\n\n//\tSys_RedirectStart(???);\n\n\tfor (u = ucmds; u->name; u++)\n\t{\n\t\tif (!strcmp(arg0, u->name))\n\t\t{\n\t\t\tif (u->func)\n\t\t\t\tu->func(d);\n\t\t\tfound = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!found)\n\t{\n\t\tif (developer.value)\n\t\t\tSys_Printf(\"Bad QTV command: %s\\n\", arg0);\n\t}\n\n//\tSys_RedirectStop();\n}\n\nvoid QTV_ReadInput( mvddest_t *d )\n{\n\tint len, parse_end, clc;\n\tnetmsg_t buf;\n\n\tif (d->error)\n\t\treturn;\n\n\tlen = sizeof(d->inbuffer) - d->inbuffersize - 1; // -1 since it null terminated\n\n\tif (len)\n\t{\n\t\tlen = recv(d->socket, d->inbuffer + d->inbuffersize, len, 0);\n\n\t\tif (len == 0)\n\t\t{\n\t\t\tSys_Printf(\"QTV_ReadInput: read error from QTV client, dropping\\n\");\n\t\t\td->error = true;\n\t\t\treturn;\n\t\t}\n\t\telse if (len < 0)\n\t\t{\n\t\t\tlen = 0;\n\t\t}\n\n\t\td->inbuffersize += len;\n\t\td->inbuffer[d->inbuffersize] = 0; // null terminated\n\t}\n\n\tif (d->inbuffersize < 2)\n\t\treturn; // we need at least size\n\n\tInitNetMsg(&buf, d->inbuffer, d->inbuffersize);\n\tbuf.cursize\t= d->inbuffersize; // we laredy have some data in buffer\n\n\tparse_end\t= 0;\n\n\twhile(buf.readpos < buf.cursize)\n\t{\n//\t\tSys_Printf(\"%d %d\\n\", buf.readpos, buf.cursize);\n\n\t\tif (buf.readpos > buf.cursize)\n\t\t{\n\t\t\td->error = true;\n\t\t\tSys_Printf(\"QTV_ReadInput: Read past end of parse buffer\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tbuf.startpos = buf.readpos;\n\n\t\tif (buf.cursize - buf.startpos < 2)\n\t\t\tbreak; // we need at least size\n\n\t\tlen = ReadShort(&buf);\n\n\t\tif (len > (int)sizeof(d->inbuffer) - 1 || len < 3)\n\t\t{\n\t\t\td->error = true;\n\t\t\tSys_Printf(\"QTV_ReadInput: can't handle such long/short message: %i\\n\", len);\n\t\t\treturn;\n\t\t}\n\n\t\tif (len > buf.cursize - buf.startpos)\n\t\t\tbreak; // not enough data yet\n\n\t\tparse_end = buf.startpos + len; // so later we know which part of buffer we alredy served\n\n\t\tswitch (clc = ReadByte(&buf))\n\t\t{\n\t\t#define qtv_clc_stringcmd    1\n\n\t\tcase qtv_clc_stringcmd:\n\t\t\tQTV_cmd[0] = 0;\n\t\t\tReadString(&buf, QTV_cmd, sizeof(QTV_cmd));\n\t\t\tQTV_ExecuteCmd(d, QTV_cmd);\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\td->error = true;\n\t\t\tSys_Printf(\"QTV_ReadInput: can't handle clc %i\\n\", clc);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (parse_end)\n\t{\n\t\td->inbuffersize -= parse_end;\n\t\tmemmove(d->inbuffer, d->inbuffer + parse_end, d->inbuffersize);\n\t}\n}\n\nvoid QTV_ReadDests( void )\n{\n\tmvddest_t *d;\n\n\tfor (d = demo.dest; d; d = d->nextdest)\n\t{\n\t\tif (d->desttype != DEST_STREAM)\n\t\t\tcontinue;\n\n\t\tif (d->error)\n\t\t\tcontinue;\n\n\t\tQTV_ReadInput(d);\n\t}\n}\n\n//============================================================ \n\n\n\n\n/*\nvoid DemoWriteQTVTimePad (int msecs)\t//broadcast to all proxies\n{\n\tmvddest_t *d;\n\tunsigned char buffer[6];\n\twhile (msecs > 0)\n\t{\n\t\t//duration\n\t\tif (msecs > 255)\n\t\t\tbuffer[0] = 255;\n\t\telse\n\t\t\tbuffer[0] = msecs;\n\t\tmsecs -= buffer[0];\n\t\t//message type\n\t\tbuffer[1] = dem_read;\n\t\t//length\n\t\tbuffer[2] = 0;\n\t\tbuffer[3] = 0;\n\t\tbuffer[4] = 0;\n\t\tbuffer[5] = 0;\n\n\t\tfor (d = demo.dest; d; d = d->nextdest)\n\t\t{\n\t\t\tif (d->desttype == DEST_STREAM)\n\t\t\t{\n\t\t\t\tDemoWriteDest(buffer, sizeof(buffer), d);\n\t\t\t}\n\t\t}\n\t}\n}\n*/\n\n//broadcast to all proxies\nvoid DemoWriteQTV (sizebuf_t *msg)\n{\n\tmvddest_t\t\t*d;\n\tsizebuf_t\t\tmvdheader;\n\tbyte\t\t\tmvdheader_buf[6];\n\n\tSZ_InitEx(&mvdheader, mvdheader_buf, sizeof(mvdheader_buf), false);\n\n\t//duration\n\tMSG_WriteByte (&mvdheader, 0);\n\t//message type\n\tMSG_WriteByte (&mvdheader, dem_all);\n\t//length\n\tMSG_WriteLong (&mvdheader, msg->cursize);\n\n\tfor (d = demo.dest; d; d = d->nextdest)\n\t{\n\t\tif (d->desttype == DEST_STREAM)\n\t\t{\n\t\t\tDemoWriteDest(mvdheader.data, mvdheader.cursize, d);\n\t\t\tDemoWriteDest(msg->data, msg->cursize, d);\n\t\t}\n\t}\n}\n\nvoid Qtv_List_f(void)\n{\n\tmvddest_t *d;\n\tint cnt;\n\n\tfor (cnt = 0, d = demo.dest; d; d = d->nextdest)\n\t{\n\t\tif (d->desttype != DEST_STREAM)\n\t\t\tcontinue; // not qtv\n\n\t\tif (!cnt) // print banner\n\t\t\tCon_Printf (\"QTV list:\\n\"\n\t\t\t\t\t\t\"%4.4s %s\\n\", \"#Id\", \"Addr\");\n\n\t\tcnt++;\n\n\t\tCon_Printf (\"%4d %s\\n\", d->id, NET_AdrToString(d->na));\n\t}\n\n\tif (!cnt)\n\t\tCon_Printf (\"QTV list: empty\\n\");\n}\n\n// Very similar to Qtv_list_f, but for disconnected clients.\nvoid QTV_Streams_List (void)\n{\n\tmvddest_t *dst;\n\tfor (dst = demo.dest; dst; dst = dst->nextdest) {\n\t\tif (dst->desttype == DEST_STREAM) {\n\t\t\tint qtv_users = QTVsv_UsersCount (dst);\n\n\t\t\tif (dst->qtvaddress[0])\n\t\t\t\tCon_Printf (\"qtv %d \\\"%s\\\" \\\"%d@%s\\\" %d\\n\", dst->id, dst->qtvname, dst->qtvstreamid, dst->qtvaddress, qtv_users);\n\t\t\telse\n\t\t\t\tCon_Printf (\"qtv %d \\\"%s\\\" \\\"\\\" %d\\n\", dst->id, dst->qtvname, qtv_users);\n\t\t}\n\t}\n}\n\n// Expose user list to disconnected clients.\nvoid QTV_Streams_UserList (void)\n{\n\tmvddest_t *dst;\n\tfor (dst = demo.dest; dst; dst = dst->nextdest) {\n\t\tif (dst->desttype == DEST_STREAM) {\n\t\t\tqtvuser_t *current;\n\n\t\t\tCon_Printf (\"qtvusers %d\", dst->id);\n\t\t\tfor (current = dst->qtvuserlist; current; current = current->next)\n\t\t\t\tCon_Printf (\" \\\"%s\\\"\", current->name);\n\t\t\tCon_Printf (\"\\n\");\n\t\t}\n\t}\n}\n\nvoid Qtv_Close_f(void)\n{\n\tmvddest_t *d;\n\tint id, cnt;\n\tqbool all;\n\n\tif (Cmd_Argc() < 2 || !*Cmd_Argv(1))  // not less than one param, first param non empty\n\t{\n\t\tCon_Printf (\"Usage: %s <#id | all>\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tfor (d = demo.dest; d; d = d->nextdest)\n\t\tif (d->desttype == DEST_STREAM)\n\t\t\tbreak; // at least one qtv present\n\n\tif (!d) {\n\t\tCon_Printf (\"QTV list alredy empty\\n\");\n\t\treturn;\n\t}\n\n\tid  = atoi(Cmd_Argv(1));\n\tall = !strcasecmp(Cmd_Argv(1), \"all\");\n\tcnt = 0;\n\n\tfor (d = demo.dest; d; d = d->nextdest)\n\t{\n\t\tif (d->desttype != DEST_STREAM)\n\t\t\tcontinue; // not qtv\n\n\t\tif (all || d->id == id) {\n\t\t\tCon_Printf (\"QTV id:%d aka %s will be dropped asap\\n\", d->id, NET_AdrToString(d->na));\n\t\t\td->error = true;\n\t\t\tcnt++;\n\t\t}\n\t}\n\n\tif (!cnt)\n\t\tCon_Printf (\"QTV id:%d not found\\n\", id);\n}\n\nvoid Qtv_Status_f(void)\n{\n\tint cnt;\n\tmvddest_t *d;\n\tmvdpendingdest_t *p;\n\n\tCon_Printf (\"QTV status\\n\");\n\tCon_Printf (\"Listen socket  : %s\\n\", NET_GetSocket(NS_SERVER, true) == INVALID_SOCKET ? \"invalid\" : \"listen\");\n\tCon_Printf (\"Port           : %d\\n\", listenport);\n\n\tfor (cnt = 0, d = demo.dest; d; d = d->nextdest)\n\t\tif (d->desttype == DEST_STREAM)\n\t\t\tcnt++;\n\n\tCon_Printf (\"Streams        : %d\\n\", cnt);\n\n\tfor (cnt = 0, p = demo.pendingdest; p; p = p->nextdest)\n\t\tcnt++;\n\n\tCon_Printf (\"Pending streams: %d\\n\", cnt);\n}\n\n//====================================\n\nvoid SV_QTV_Init(void)\n{\n\tCvar_Register (&qtv_streamport);\n\tCvar_Register (&qtv_maxstreams);\n\tCvar_Register (&qtv_password);\n\tCvar_Register (&qtv_pendingtimeout);\n\tCvar_Register (&qtv_streamtimeout);\n\tCvar_Register (&qtv_sayenabled);\n\n\tCmd_AddCommand (\"qtv_list\", Qtv_List_f);\n\tCmd_AddCommand (\"qtv_close\", Qtv_Close_f);\n\tCmd_AddCommand (\"qtv_status\", Qtv_Status_f);\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_ents.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\n\n//=============================================================================\n\n// because there can be a lot of nails, there is a special\n// network protocol for them\n#define MAX_NAILS 32\nstatic edict_t *nails[MAX_NAILS];\nstatic int numnails;\nstatic int nailcount = 0;\n\nextern\tint sv_nailmodel, sv_supernailmodel, sv_playermodel;\n\ncvar_t\tsv_nailhack\t= {\"sv_nailhack\", \"1\"};\n\n// Maximum packet we will send - currently 256 if extension supported\n#define MAX_PACKETENTITIES_POSSIBLE 256\n\nstatic qbool SV_AddNailUpdate (edict_t *ent)\n{\n\tif ((int)sv_nailhack.value)\n\t\treturn false;\n\n\tif (ent->v->modelindex != sv_nailmodel && ent->v->modelindex != sv_supernailmodel)\n\t\treturn false;\n\n\tif (msg_coordsize != 2)\n\t\treturn false; // Do not allow nailhack in case of sv_bigcoords.\n\n\tif (numnails == MAX_NAILS)\n\t\treturn true;\n\n\tnails[numnails] = ent;\n\tnumnails++;\n\treturn true;\n}\n\nstatic void SV_EmitNailUpdate (sizebuf_t *msg, qbool recorder)\n{\n\tint x, y, z, p, yaw, n, i;\n\tbyte bits[6]; // [48 bits] xyzpy 12 12 12 4 8\n\tedict_t *ent;\n\n\n\tif (!numnails)\n\t\treturn;\n\n\tif (recorder)\n\t\tMSG_WriteByte (msg, svc_nails2);\n\telse\n\t\tMSG_WriteByte (msg, svc_nails);\n\n\tMSG_WriteByte (msg, numnails);\n\n\tfor (n=0 ; n<numnails ; n++)\n\t{\n\t\tent = nails[n];\n\t\tif (recorder)\n\t\t{\n\t\t\tif (!ent->v->colormap)\n\t\t\t{\n\t\t\t\tif (!((++nailcount)&255)) nailcount++;\n\t\t\t\tent->v->colormap = nailcount&255;\n\t\t\t}\n\n\t\t\tMSG_WriteByte (msg, (byte)ent->v->colormap);\n\t\t}\n\n\t\tx = ((int)(ent->v->origin[0] + 4096 + 1) >> 1) & 4095;\n\t\ty = ((int)(ent->v->origin[1] + 4096 + 1) >> 1) & 4095;\n\t\tz = ((int)(ent->v->origin[2] + 4096 + 1) >> 1) & 4095;\n\t\tp = Q_rint(ent->v->angles[0]*(16.0/360.0)) & 15;\n\t\tyaw = Q_rint(ent->v->angles[1]*(256.0/360.0)) & 255;\n\n\t\tbits[0] = x;\n\t\tbits[1] = (x>>8) | (y<<4);\n\t\tbits[2] = (y>>4);\n\t\tbits[3] = z;\n\t\tbits[4] = (z>>8) | (p<<4);\n\t\tbits[5] = yaw;\n\n\t\tfor (i=0 ; i<6 ; i++)\n\t\t\tMSG_WriteByte (msg, bits[i]);\n\t}\n}\n\n//=============================================================================\n\n\n/*\n==================\nSV_WriteDelta\n\nWrites part of a packetentities message.\nCan delta from either a baseline or a previous packet_entity\n==================\n*/\nvoid SV_WriteDelta(client_t* client, entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qbool force)\n{\n\tint bits, i;\n#ifdef PROTOCOL_VERSION_FTE\n\tint evenmorebits = 0;\n\tunsigned int required_extensions = 0;\n\tunsigned int fte_extensions = client->fteprotocolextensions;\n#endif\n\n\t// send an update\n\tbits = 0;\n\n\tfor (i = 0; i < 3; i++)\n\t\tif (to->origin[i] != from->origin[i])\n\t\t\tbits |= U_ORIGIN1 << i;\n\n\tif (to->angles[0] != from->angles[0])\n\t\tbits |= U_ANGLE1;\n\n\tif (to->angles[1] != from->angles[1])\n\t\tbits |= U_ANGLE2;\n\n\tif (to->angles[2] != from->angles[2])\n\t\tbits |= U_ANGLE3;\n\n\tif (to->colormap != from->colormap)\n\t\tbits |= U_COLORMAP;\n\n\tif (to->skinnum != from->skinnum)\n\t\tbits |= U_SKIN;\n\n\tif (to->frame != from->frame)\n\t\tbits |= U_FRAME;\n\n\tif (to->effects != from->effects)\n\t\tbits |= U_EFFECTS;\n\n\tif (to->modelindex != from->modelindex) {\n\t\tbits |= U_MODEL;\n#ifdef FTE_PEXT_ENTITYDBL\n\t\tif (to->modelindex > 255) {\n\t\t\tif (to->modelindex >= 512) {\n\t\t\t\tbits &= ~U_MODEL;\n\t\t\t}\n\t\t\tevenmorebits |= U_FTE_MODELDBL;\n\t\t\trequired_extensions |= FTE_PEXT_MODELDBL;\n\t\t}\n#endif\n\t}\n\n\tif (bits & U_CHECKMOREBITS)\n\t\tbits |= U_MOREBITS;\n\n\tif (to->flags & U_SOLID)\n\t\tbits |= U_SOLID;\n\n\t// Taken from FTE\n\tif (msg->cursize + 40 > msg->maxsize && !MSG_HasOverflowHandler(msg))\n\t{\n\t\t// not enough space in the buffer, don't send the entity this frame. (not sending means nothing changes, and it takes no bytes!!)\n\t\tint oldnum = to->number;\n\t\t*to = *from;\n\t\tif (oldnum && !from->number) {\n\t\t\tto->number = oldnum;\n\t\t}\n\t\treturn;\n\t}\n\n\t//\n\t// write the message\n\t//\n\tif (!to->number)\n\t\tSV_Error(\"Unset entity number\");\n\n#ifdef PROTOCOL_VERSION_FTE\n\tif (to->number >= 512)\n\t{\n\t\tif (to->number >= 1024)\n\t\t{\n\t\t\tif (to->number >= 1024 + 512) {\n\t\t\t\tevenmorebits |= U_FTE_ENTITYDBL;\n\t\t\t\trequired_extensions |= FTE_PEXT_ENTITYDBL;\n\t\t\t}\n\n\t\t\tevenmorebits |= U_FTE_ENTITYDBL2;\n\t\t\trequired_extensions |= FTE_PEXT_ENTITYDBL2;\n\t\t\tif (to->number >= 2048)\n\t\t\t\tSV_Error (\"Entity number >= 2048\");\n\t\t}\n\t\telse {\n\t\t\tevenmorebits |= U_FTE_ENTITYDBL;\n\t\t\trequired_extensions |= FTE_PEXT_ENTITYDBL;\n\t\t}\n\t}\n\n#ifdef U_FTE_TRANS\n\tif (to->trans != from->trans && (fte_extensions & FTE_PEXT_TRANS)) {\n\t\tevenmorebits |= U_FTE_TRANS;\n\t\trequired_extensions |= FTE_PEXT_TRANS;\n\t}\n#endif\n\n#ifdef U_FTE_COLOURMOD\n\tif ((to->colourmod[0] != from->colourmod[0] ||\n\t     to->colourmod[1] != from->colourmod[1] ||\n\t     to->colourmod[2] != from->colourmod[2]) && (fte_extensions & FTE_PEXT_COLOURMOD)) {\n\t\tevenmorebits |= U_FTE_COLOURMOD;\n\t\trequired_extensions |= FTE_PEXT_COLOURMOD;\n\t}\n#endif\n\n\tif (evenmorebits&0xff00)\n\t\tevenmorebits |= U_FTE_YETMORE;\n\tif (evenmorebits&0x00ff)\n\t\tbits |= U_FTE_EVENMORE;\n\tif (bits & 511)\n\t\tbits |= U_MOREBITS;\n#endif\n\n\tif (to->number >= sv.max_edicts) {\n\t\t/*SV_Error*/\n\t\tCon_Printf(\"Entity number >= MAX_EDICTS (%d), set to MAX_EDICTS - 1\\n\", sv.max_edicts);\n\t\tto->number = sv.max_edicts - 1;\n\t}\n\n#ifdef PROTOCOL_VERSION_FTE\n\tif (evenmorebits && (fte_extensions & required_extensions) != required_extensions) {\n\t\treturn;\n\t}\n#endif\n\tif (!bits && !force) {\n\t\treturn;\t\t// nothing to send!\n\t}\n\ti = (to->number & U_CHECKMOREBITS) | (bits&~U_CHECKMOREBITS);\n\tif (i & U_REMOVE)\n\t\tSys_Error(\"U_REMOVE\");\n\tMSG_WriteShort(msg, i);\n\n\tif (bits & U_MOREBITS)\n\t\tMSG_WriteByte(msg, bits & 255);\n#ifdef PROTOCOL_VERSION_FTE\n\tif (bits & U_FTE_EVENMORE)\n\t\tMSG_WriteByte (msg, evenmorebits&255);\n\tif (evenmorebits & U_FTE_YETMORE)\n\t\tMSG_WriteByte (msg, (evenmorebits>>8)&255);\n#endif\n\n\tif (bits & U_MODEL)\n\t\tMSG_WriteByte(msg, to->modelindex & 255);\n\telse if (evenmorebits & U_FTE_MODELDBL)\n\t\tMSG_WriteShort(msg, to->modelindex);\n\tif (bits & U_FRAME)\n\t\tMSG_WriteByte(msg, to->frame);\n\tif (bits & U_COLORMAP)\n\t\tMSG_WriteByte(msg, to->colormap);\n\tif (bits & U_SKIN)\n\t\tMSG_WriteByte(msg, to->skinnum);\n\tif (bits & U_EFFECTS)\n\t\tMSG_WriteByte(msg, to->effects);\n\tif (bits & U_ORIGIN1) {\n\t\tif (client->mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) {\n\t\t\tMSG_WriteLongCoord(msg, to->origin[0]);\n\t\t}\n\t\telse {\n\t\t\tMSG_WriteCoord(msg, to->origin[0]);\n\t\t}\n\t}\n\tif (bits & U_ANGLE1) {\n\t\tMSG_WriteAngle(msg, to->angles[0]);\n\t}\n\tif (bits & U_ORIGIN2) {\n\t\tif (client->mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) {\n\t\t\tMSG_WriteLongCoord(msg, to->origin[1]);\n\t\t}\n\t\telse {\n\t\t\tMSG_WriteCoord(msg, to->origin[1]);\n\t\t}\n\t}\n\tif (bits & U_ANGLE2) {\n\t\tMSG_WriteAngle(msg, to->angles[1]);\n\t}\n\tif (bits & U_ORIGIN3) {\n\t\tif (client->mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) {\n\t\t\tMSG_WriteLongCoord(msg, to->origin[2]);\n\t\t}\n\t\telse {\n\t\t\tMSG_WriteCoord(msg, to->origin[2]);\n\t\t}\n\t}\n\tif (bits & U_ANGLE3) {\n\t\tMSG_WriteAngle(msg, to->angles[2]);\n\t}\n\n#ifdef U_FTE_TRANS\n\tif (evenmorebits & U_FTE_TRANS)\n\t\tMSG_WriteByte (msg, to->trans);\n#endif\n\n#ifdef U_FTE_COLOURMOD\n\tif (evenmorebits & U_FTE_COLOURMOD)\n\t{\n\t\tMSG_WriteByte (msg, to->colourmod[0]);\n\t\tMSG_WriteByte (msg, to->colourmod[1]);\n\t\tMSG_WriteByte (msg, to->colourmod[2]);\n\t}\n#endif\n}\n\n/*\n=============\nSV_EmitPacketEntities\n\nWrites a delta update of a packet_entities_t to the message.\n\n=============\n*/\nstatic void SV_EmitPacketEntities (client_t *client, packet_entities_t *to, sizebuf_t *msg)\n{\n\tint oldindex, newindex, oldnum, newnum, oldmax;\n\tclient_frame_t\t*fromframe;\n\tpacket_entities_t *from1;\n\tedict_t\t*ent;\n\n\t// this is the frame that we are going to delta update from\n\tif (client->delta_sequence != -1)\n\t{\n\t\tfromframe = &client->frames[client->delta_sequence & UPDATE_MASK];\n\t\tfrom1 = &fromframe->entities;\n\t\toldmax = from1->num_entities;\n\n\t\tMSG_WriteByte (msg, svc_deltapacketentities);\n\t\tMSG_WriteByte (msg, client->delta_sequence);\n\t}\n\telse\n\t{\n\t\toldmax = 0;\t// no delta update\n\t\tfrom1 = NULL;\n\n\t\tMSG_WriteByte (msg, svc_packetentities);\n\t}\n\n\tnewindex = 0;\n\toldindex = 0;\n\t//Con_Printf (\"---%i to %i ----\\n\", client->delta_sequence & UPDATE_MASK\n\t//\t\t\t, client->netchan.outgoing_sequence & UPDATE_MASK);\n\twhile (newindex < to->num_entities || oldindex < oldmax)\n\t{\n\t\tnewnum = newindex >= to->num_entities ? 9999 : to->entities[newindex].number;\n\t\toldnum = oldindex >= oldmax ? 9999 : from1->entities[oldindex].number;\n\n\t\tif (newnum == oldnum)\n\t\t{\t// delta update from old position\n\t\t\t//Con_Printf (\"delta %i\\n\", newnum);\n\t\t\tSV_WriteDelta (client, &from1->entities[oldindex], &to->entities[newindex], msg, false);\n\t\t\toldindex++;\n\t\t\tnewindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (newnum < oldnum)\n\t\t{\t// this is a new entity, send it from the baseline\n\t\t\tif (newnum == 9999)\n\t\t\t{\n\t\t\t\tSys_Printf(\"LOL, %d, %d, %d, %d %d %d\\n\", // nice message\n\t\t\t\t           newnum, oldnum, to->num_entities, oldmax,\n\t\t\t\t           client->netchan.incoming_sequence & UPDATE_MASK,\n\t\t\t\t           client->delta_sequence & UPDATE_MASK);\n\t\t\t\tif (client->edict == NULL)\n\t\t\t\t\tSys_Printf(\"demo\\n\");\n\t\t\t}\n\t\t\tent = EDICT_NUM(newnum);\n\t\t\t//Con_Printf (\"baseline %i\\n\", newnum);\n\t\t\tSV_WriteDelta (client, &ent->e.baseline, &to->entities[newindex], msg, true);\n\t\t\tnewindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (newnum > oldnum)\n\t\t{\n\t\t\t// the old entity isn't present in the new message\n\t\t\tif (oldnum >= 512) {\n\t\t\t\t//yup, this is expensive.\n\t\t\t\tMSG_WriteShort (msg, oldnum | U_REMOVE | U_MOREBITS);\n\t\t\t\tMSG_WriteByte (msg, U_FTE_EVENMORE);\n\t\t\t\tif (oldnum >= 1024) {\n\t\t\t\t\tif (oldnum >= 1024 + 512) {\n\t\t\t\t\t\tMSG_WriteByte(msg, U_FTE_ENTITYDBL | U_FTE_ENTITYDBL2);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tMSG_WriteByte(msg, U_FTE_ENTITYDBL2);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tMSG_WriteByte(msg, U_FTE_ENTITYDBL);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tMSG_WriteShort(msg, oldnum | U_REMOVE);\n\t\t\t}\n\n\t\t\toldindex++;\n\t\t\tcontinue;\n\t\t}\n\t}\n\n\tMSG_WriteShort (msg, 0);\t// end of packetentities\n}\n\nstatic int TranslateEffects (edict_t *ent)\n{\n\tint fx = (int)ent->v->effects;\n\tif (pr_nqprogs)\n\t\tfx &= ~EF_MUZZLEFLASH;\n\tif (pr_nqprogs && (fx & EF_DIMLIGHT)) {\n\t\tif ((int)ent->v->items & IT_QUAD)\n\t\t\tfx |= EF_BLUE;\n\t\tif ((int)ent->v->items & IT_INVULNERABILITY)\n\t\t\tfx |= EF_RED;\n\t}\n\treturn fx;\n}\n\n/*\n=============\nSV_MVD_WritePlayersToClient\n=============\n*/\nstatic void SV_MVD_WritePlayersToClient ( void )\n{\n\tint j;\n\tdemo_frame_t *demo_frame;\n\tdemo_client_t *dcl;\n\tclient_t *cl;\n\tedict_t *ent;\n\n\tif ( !sv.mvdrecording )\n\t\treturn;\n\n\tdemo_frame = &demo.frames[demo.parsecount&UPDATE_MASK];\n\n\tfor (j = 0, cl = svs.clients, dcl = demo_frame->clients; j < MAX_CLIENTS; j++, cl++, dcl++)\n\t{\n\t\tif ( cl->state != cs_spawned || cl->spectator )\n\t\t\tcontinue;\n\n\t\tent = cl->edict;\n\n\t\tdcl->parsecount = demo.parsecount;\n\n\t\tVectorCopy(ent->v->origin, dcl->origin);\n\t\tVectorCopy(ent->v->angles, dcl->angles);\n\t\tdcl->angles[0] *= -3;\n#ifdef USE_PR2\n\t\tif( cl->isBot )\n\t\t\tVectorCopy(ent->v->v_angle, dcl->angles);\n#endif\n\t\tdcl->angles[2] = 0; // no roll angle\n\n\t\tif (ent->v->health <= 0)\n\t\t{\t// don't show the corpse looking around...\n\t\t\tdcl->angles[0] = 0;\n\t\t\tdcl->angles[1] = ent->v->angles[1];\n\t\t\tdcl->angles[2] = 0;\n\t\t}\n\n\t\tdcl->weaponframe = ent->v->weaponframe;\n\t\tdcl->frame       = ent->v->frame;\n\t\tdcl->skinnum     = ent->v->skin;\n\t\tdcl->model       = ent->v->modelindex;\n\t\tdcl->effects     = TranslateEffects(ent);\n\t\tdcl->flags       = 0;\n\n\t\tdcl->fixangle    = demo.fixangle[j];\n\t\tdemo.fixangle[j] = 0;\n\n\t\tdcl->sec         = sv.time - cl->localtime;\n\n\t\tif (ent->v->health <= 0)\n\t\t\tdcl->flags |= DF_DEAD;\n\t\tif (ent->v->mins[2] != -24)\n\t\t\tdcl->flags |= DF_GIB;\n\n\t\tcontinue;\n\t}\n}\n\n/*\n=============\nSV_PlayerVisibleToClient\n=============\n*/\nqbool SV_PlayerVisibleToClient (client_t* client, int j, byte* pvs, edict_t* self_ent, edict_t* ent)\n{\n\tclient_t* cl = &svs.clients[j];\n\n\t// ZOID visibility tracking\n\tif (ent != self_ent && !(client->spec_track && client->spec_track - 1 == j))\n\t{\n\t\tint i;\n\n\t\tif (cl->spectator)\n\t\t\treturn false;\n\n\t\tif (pvs && ent->e.num_leafs >= 0) {\n\t\t\t// ignore if not touching a PV leaf\n\t\t\tfor (i = 0; i < ent->e.num_leafs; i++) {\n\t\t\t\tif (pvs[ent->e.leafnums[i] >> 3] & (1 << (ent->e.leafnums[i] & 7))) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (i == ent->e.num_leafs) {\n\t\t\t\treturn false; // not visible\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n=============\nSV_WritePlayersToClient\n=============\n*/\n\nint SV_PMTypeForClient (client_t *cl);\nstatic void SV_WritePlayersToClient (client_t *client, client_frame_t *frame, byte *pvs, qbool disable_updates, sizebuf_t *msg)\n{\n\tint msec, pflags, pm_type = 0, pm_code = 0, i, j;\n\tusercmd_t cmd;\n\tint hideent = 0;\n\tint trackent = 0;\n\tqbool hide_players = fofs_hide_players && ((eval_t *)((byte *)(client->edict)->v + fofs_hide_players))->_int;\n\n\tif (fofs_hideentity)\n\t\thideent = ((eval_t *)((byte *)(client->edict)->v + fofs_hideentity))->_int / pr_edict_size;\n\n\tif (fofs_trackent)\n\t{\n\t\ttrackent = ((eval_t *)((byte *)(client->edict)->v + fofs_trackent))->_int;\n\t\tif (trackent < 1 || trackent > MAX_CLIENTS || svs.clients[trackent - 1].state != cs_spawned)\n\t\t\ttrackent = 0;\n\t}\n\n\tframe->sv_time = sv.time;\n\n\tfor (j = 0; j < MAX_CLIENTS; j++)\n\t{\n\t\tclient_t*   cl = &svs.clients[j];\n\t\tedict_t*    ent = NULL;\n\t\tedict_t*    self_ent = NULL;\n\t\tedict_t*    track_ent = NULL;\n\n\t\tif (fofs_visibility) {\n\t\t\t// Presume not visible\n\t\t\t((eval_t *)((byte *)(cl->edict)->v + fofs_visibility))->_int &= ~(1 << (client - svs.clients));\n\t\t}\n\n\t\tif (cl->state != cs_spawned)\n\t\t\tcontinue;\n\n\t\tif (client != cl && hide_players)\n\t\t\tcontinue;\n\n\t\tif (trackent && cl == client)\n\t\t{\n\t\t\tcl = &svs.clients[trackent - 1]; // fakenicking.\n\n\t\t\ttrack_ent = svs.clients[trackent - 1].edict;\n\n\t\t\tself_ent = track_ent;\n\t\t\tent = track_ent;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tself_ent = client->edict;\n\t\t\tent = cl->edict;\n\t\t}\n\n\t\t// set up edicts.\n\t\tif (!SV_PlayerVisibleToClient (client, j, pvs, self_ent, ent))\n\t\t\tcontinue;\n\n\t\tif (fofs_visibility) {\n\t\t\t// Update flags so mods can tell what was visible\n\t\t\t((eval_t *)((byte *)(ent)->v + fofs_visibility))->_int |= (1 << (client - svs.clients));\n\t\t}\n\n\t\tif (j == hideent - 1)\n\t\t\tcontinue;\n\n\t\tif (j == trackent - 1)\n\t\t\tcontinue;\n\n\t\tif (disable_updates && ent != self_ent)\n\t\t{ // Vladis\n\t\t\tcontinue;\n\t\t}\n\n\t\t//====================================================\n\t\t// OK, seems we must send info about this player below\n\n\t\tpflags = PF_MSEC | PF_COMMAND;\n\n\t\tif (ent->v->modelindex != sv_playermodel)\n\t\t\tpflags |= PF_MODEL;\n\t\tfor (i=0 ; i<3 ; i++)\n\t\t\tif (ent->v->velocity[i])\n\t\t\t\tpflags |= PF_VELOCITY1<<i;\n\t\tif (ent->v->effects)\n\t\t\tpflags |= PF_EFFECTS;\n\t\tif (ent->v->skin || ent->v->modelindex >= 256)\n\t\t\tpflags |= PF_SKINNUM;\n\t\tif (ent->v->health <= 0)\n\t\t\tpflags |= PF_DEAD;\n\t\tif (ent->v->mins[2] != -24)\n\t\t\tpflags |= PF_GIB;\n\n\t\tif (cl->spectator)\n\t\t{\t// only sent origin and velocity to spectators\n\t\t\tpflags &= PF_VELOCITY1 | PF_VELOCITY2 | PF_VELOCITY3;\n\t\t}\n\t\telse if (ent == self_ent)\n\t\t{\t// don't send a lot of data on personal entity\n\t\t\tpflags &= ~(PF_MSEC|PF_COMMAND);\n\t\t\tif (ent->v->weaponframe)\n\t\t\t\tpflags |= PF_WEAPONFRAME;\n\t\t}\n\n#ifdef FTE_PEXT_TRANS\n\t\tif (client->fteprotocolextensions & FTE_PEXT_TRANS && ent->xv.alpha > 0.0f && ent->xv.alpha < 1.0f)\n\t\t{\n\t\t\tpflags |= PF_TRANS_Z;\n\t\t}\n#endif\n\n\t\t// Z_EXT_PM_TYPE protocol extension\n\t\t// encode pm_type and jump_held into pm_code\n\t\tpm_type = track_ent ? PM_LOCK : SV_PMTypeForClient (cl);\n\t\tswitch (pm_type)\n\t\t{\n\t\t\tcase PM_DEAD:\n\t\t\t\tpm_code = PMC_NORMAL; // plus PF_DEAD\n\t\t\t\tbreak;\n\t\t\tcase PM_NORMAL:\n\t\t\t\tpm_code = PMC_NORMAL;\n\t\t\t\tif (cl->jump_held)\n\t\t\t\t\tpm_code = PMC_NORMAL_JUMP_HELD;\n\t\t\t\tbreak;\n\t\t\tcase PM_OLD_SPECTATOR:\n\t\t\t\tpm_code = PMC_OLD_SPECTATOR;\n\t\t\t\tbreak;\n\t\t\tcase PM_SPECTATOR:\n\t\t\t\tpm_code = PMC_SPECTATOR;\n\t\t\t\tbreak;\n\t\t\tcase PM_FLY:\n\t\t\t\tpm_code = PMC_FLY;\n\t\t\t\tbreak;\n\t\t\tcase PM_NONE:\n\t\t\t\tpm_code = PMC_NONE;\n\t\t\t\tbreak;\n\t\t\tcase PM_LOCK:\n\t\t\t\tpm_code = PMC_LOCK;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tassert (false);\n\t\t}\n\n\t\tpflags |= pm_code << PF_PMC_SHIFT;\n\n\t\t// Z_EXT_PF_ONGROUND protocol extension\n\t\tif ((int)ent->v->flags & FL_ONGROUND)\n\t\t\tpflags |= PF_ONGROUND;\n\n\t\t// Z_EXT_PF_SOLID protocol extension\n\t\tif (ent->v->solid == SOLID_BBOX || ent->v->solid == SOLID_SLIDEBOX)\n\t\t\tpflags |= PF_SOLID;\n\n\t\tif (pm_type == PM_LOCK && ent == self_ent)\n\t\t\tpflags |= PF_COMMAND;\t// send forced view angles\n\n\t\tif (client->spec_track && client->spec_track - 1 == j && ent->v->weaponframe)\n\t\t\tpflags |= PF_WEAPONFRAME;\n\n\t\tMSG_WriteByte (msg, svc_playerinfo);\n\t\tMSG_WriteByte (msg, j);\n\n#if defined(FTE_PEXT_TRANS)\n\t\tif (client->fteprotocolextensions & FTE_PEXT_TRANS)\n\t\t{\n\t\t\tif (pflags & 0xff0000)\n\t\t\t{\n\t\t\t\tpflags |= PF_EXTRA_PFS;\n\t\t\t}\n\t\t\tMSG_WriteShort (msg, pflags & 0xffff);\n\t\t\tif (pflags & PF_EXTRA_PFS)\n\t\t\t{\n\t\t\t\tMSG_WriteByte(msg, (pflags & 0xff0000) >> 16);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Without PEXT_TRANS there's no PF_EXTRA_PFS, move\n\t\t\t// PF_ONGROUND and PF_SOLID to their expected offsets.\n\t\t\tMSG_WriteShort (msg, pflags & 0x3fff | (pflags & 0xc00000) >> 8);\n\t\t}\n#else\n\t\tMSG_WriteShort (msg, pflags);\n#endif\n\n\t\tif (client->mvdprotocolextensions1 & MVD_PEXT1_FLOATCOORDS) {\n\t\t\tMSG_WriteLongCoord(msg, ent->v->origin[0]);\n\t\t\tMSG_WriteLongCoord(msg, ent->v->origin[1]);\n\t\t\tMSG_WriteLongCoord(msg, ent->v->origin[2]);\n\t\t}\n\t\telse {\n\t\t\tMSG_WriteCoord(msg, ent->v->origin[0]);\n\t\t\tMSG_WriteCoord(msg, ent->v->origin[1]);\n\t\t\tMSG_WriteCoord(msg, ent->v->origin[2]);\n\t\t}\n\n\t\tMSG_WriteByte (msg, ent->v->frame);\n\n\t\tif (pflags & PF_MSEC)\n\t\t{\n\t\t\tmsec = 1000*(sv.time - cl->localtime);\n\t\t\tif (msec > 255)\n\t\t\t\tmsec = 255;\n\t\t\tMSG_WriteByte (msg, msec);\n\t\t}\n\n\t\tif (pflags & PF_COMMAND)\n\t\t{\n\t\t\tcmd = cl->lastcmd;\n\n\t\t\tif (ent->v->health <= 0)\n\t\t\t{\t// don't show the corpse looking around...\n\t\t\t\tcmd.angles[0] = 0;\n\t\t\t\tcmd.angles[1] = ent->v->angles[1];\n\t\t\t\tcmd.angles[0] = 0;\n\t\t\t}\n\n\t\t\tcmd.buttons = 0;\t// never send buttons\n\t\t\tcmd.impulse = 0;\t// never send impulses\n\n\t\t\tif (ent == self_ent)\n\t\t\t{\n\t\t\t\t// this is PM_LOCK, we only want to send view angles\n\t\t\t\tVectorCopy(ent->v->v_angle, cmd.angles);\n\t\t\t\tcmd.forwardmove = 0;\n\t\t\t\tcmd.sidemove = 0;\n\t\t\t\tcmd.upmove = 0;\n\t\t\t}\n\n\t\t\tif ((client->extensions & Z_EXT_VWEP) && sv.vw_model_name[0] && fofs_vw_index) {\n\t\t\t\tcmd.impulse = EdictFieldFloat (ent, fofs_vw_index);\n\t\t\t}\n\n\t\t\tMSG_WriteDeltaUsercmd (msg, &nullcmd, &cmd, 0);\n\t\t}\n\n\t\tfor (i=0 ; i<3 ; i++)\n\t\t\tif (pflags & (PF_VELOCITY1<<i) )\n\t\t\t\tMSG_WriteShort (msg, ent->v->velocity[i]);\n\n\t\tif (pflags & PF_MODEL)\n\t\t\tMSG_WriteByte (msg, ent->v->modelindex);\n\n\t\tif (pflags & PF_SKINNUM)\n\t\t\tMSG_WriteByte (msg, (int)ent->v->skin | (((pflags & PF_MODEL)&&(ent->v->modelindex >= 256))<<7));\n\n\t\tif (pflags & PF_EFFECTS)\n\t\t\tMSG_WriteByte (msg, TranslateEffects(ent));\n\n\t\tif (pflags & PF_WEAPONFRAME)\n\t\t\tMSG_WriteByte (msg, ent->v->weaponframe);\n\n#ifdef FTE_PEXT_TRANS\n\t\tif (pflags & PF_TRANS_Z)\n\t\t{\n\t\t\tMSG_WriteByte (msg, bound(1, (byte)(ent->xv.alpha * 254.0f), 254));\n\t\t}\n#endif\n\t}\n}\n\n/*\n=============\nSV_EntityVisibleToClient\n=============\n*/\nqbool SV_EntityVisibleToClient (client_t* client, int e, byte* pvs)\n{\n\tedict_t* ent = EDICT_NUM (e);\n\n\tif (pr_nqprogs)\n\t{\n\t\t// don't send the player's model to himself\n\t\tif (e < MAX_CLIENTS + 1 && svs.clients[e-1].state != cs_free)\n\t\t\treturn false;\n\t}\n\n\t// ignore ents without visible models\n\tif (!ent->v->modelindex || !*PR_GetEntityString(ent->v->model))\n\t\treturn false;\n\n\tif ( pvs && ent->e.num_leafs >= 0 )\n\t{\n\t\tint i;\n\n\t\t// ignore if not touching a PV leaf\n\t\tfor (i=0 ; i < ent->e.num_leafs ; i++)\n\t\t\tif (pvs[ent->e.leafnums[i] >> 3] & (1 << (ent->e.leafnums[i]&7) ))\n\t\t\t\tbreak;\n\n\t\tif (i == ent->e.num_leafs)\n\t\t\treturn false;\t\t// not visible\n\t}\n\n\treturn true;\n}\n\n/*\n=============\nSV_WriteEntitiesToClient\n\nEncodes the current state of the world as\na svc_packetentities messages and possibly\na svc_nails message and\nsvc_playerinfo messages\n=============\n*/\n\nvoid SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg, qbool recorder)\n{\n\tqbool disable_updates; // disables sending entities to the client\n\tint e, i, max_packet_entities;\n\tpacket_entities_t *pack;\n\tclient_frame_t *frame;\n\tentity_state_t *state;\n\tedict_t *ent;\n\tbyte *pvs;\n\tint hideent;\n\tunsigned int client_flag = (1 << (client - svs.clients));\n\tedict_t\t*clent = client->edict;\n\n\tfloat distances[MAX_PACKETENTITIES_POSSIBLE] = { 0 };\n\tfloat distance;\n\tint position;\n\tvec3_t org;\n\n\tif ( recorder )\n\t{\n\t\tif ( !sv.mvdrecording )\n\t\t\treturn;\n\t}\n\n\t// this is the frame we are creating\n\tframe = &client->frames[client->netchan.incoming_sequence & UPDATE_MASK];\n\n\t// find the client's PVS\n\tif ( recorder )\n\t{// demo\n\t\thideent = 0;\n\t\tpvs = NULL; // ignore PVS for demos\n\t\tmax_packet_entities = MAX_MVD_PACKET_ENTITIES;\n\t\tdisable_updates = false; // updates always allowed in demos\n\t}\n\telse\n\t{// normal client\n\t\tvec3_t org;\n\t\tint trackent = 0;\n\n\t\tif (fofs_hideentity)\n\t\t\thideent = ((eval_t *)((byte *)(client->edict)->v + fofs_hideentity))->_int / pr_edict_size;\n\t\telse\n\t\t\thideent = 0;\n\n\t\tif (fofs_trackent)\n\t\t{\n\t\t\ttrackent = ((eval_t *)((byte *)(client->edict)->v + fofs_trackent))->_int;\n\t\t\tif (trackent < 1 || trackent > MAX_CLIENTS || svs.clients[trackent - 1].state != cs_spawned)\n\t\t\t\ttrackent = 0;\n\t\t}\n\n\t\t// we should use org of tracked player in case or trackent.\n\t\tif (trackent)\n\t\t{\n\t\t\tVectorAdd (svs.clients[trackent - 1].edict->v->origin, svs.clients[trackent - 1].edict->v->view_ofs, org);\t\t\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorAdd (client->edict->v->origin, client->edict->v->view_ofs, org);\n\t\t}\n\n\t\tpvs = CM_FatPVS (org); // search some PVS\n\t\tmax_packet_entities = (client->fteprotocolextensions & FTE_PEXT_256PACKETENTITIES) ? MAX_PEXT256_PACKET_ENTITIES : MAX_PACKET_ENTITIES;\n\n\t\tif (client->disable_updates_stop > realtime)\n\t\t{\n\t\t\t#define ISUNDERWATER(x) ((x) == CONTENTS_WATER || (x) == CONTENTS_SLIME || (x) == CONTENTS_LAVA)\n\n\t\t\t// server flash should not work underwater\n\t\t\tint content = CM_HullPointContents(&sv.worldmodel->hulls[0], 0, client->edict->v->origin);\n\t\t\tdisable_updates = !ISUNDERWATER(content);\n\n\t\t\t#undef ISUNDERWATER\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdisable_updates = false;\n\t\t}\n\t}\n\n\t// send over the players in the PVS\n\tif ( recorder )\n\t\tSV_MVD_WritePlayersToClient (); // nice, no params at all!\n\telse\n\t\tSV_WritePlayersToClient (client, frame, pvs, disable_updates, msg);\n\n\t// put other visible entities into either a packet_entities or a nails message\n\tpack = &frame->entities;\n\tpack->num_entities = 0;\n\n\tnumnails = 0;\n\n\tif (!disable_updates)\n\t{// Vladis, server flash\n\n\t\t// QW protocol can only handle 512 entities. Any entity with number >= 512 will be invisible\n\t\t// from ZQuake unless using protocol extensions.\n\t\t// max_edicts = min(sv.num_edicts, MAX_EDICTS);\n\n\t\tfor (e = pr_nqprogs ? 1 : MAX_CLIENTS + 1, ent = EDICT_NUM(e); e < sv.num_edicts; e++, ent = NEXT_EDICT(ent))\n\t\t{\n\t\t\tif (!SV_EntityVisibleToClient(client, e, pvs)) {\n\t\t\t\tif (fofs_visibility) {\n\t\t\t\t\t((eval_t *)((byte *)(ent)->v + fofs_visibility))->_int &= ~client_flag;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (fofs_visibility) {\n\t\t\t\t// Don't include other filters in logic for setting this field\n\t\t\t\t((eval_t *)((byte *)(ent)->v + fofs_visibility))->_int |= (1 << (client - svs.clients));\n\t\t\t}\n\n\t\t\tif (e == hideent) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (SV_AddNailUpdate (ent))\n\t\t\t\tcontinue; // added to the special update list\n\n\t\t\tif (clent) {\n\t\t\t\tVectorAdd(ent->v->absmin, ent->v->absmax, org);\n\t\t\t\tVectorMA(clent->v->origin, -0.5, org, org);\n\t\t\t\tdistance = DotProduct(org, org);\t//Length\n\n\t\t\t\t// add to the packetentities\n\t\t\t\tif (pack->num_entities == max_packet_entities) {\n\t\t\t\t\t// replace the furthest entity\n\t\t\t\t\tfloat furthestdist = -1;\n\t\t\t\t\tint best = -1;\n\t\t\t\t\tfor (i = 0; i < max_packet_entities; i++) {\n\t\t\t\t\t\tif (furthestdist < distances[i]) {\n\t\t\t\t\t\t\tfurthestdist = distances[i];\n\t\t\t\t\t\t\tbest = i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (furthestdist <= distance || best == -1) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// shuffle other entities down, add to end\n\t\t\t\t\tif (best < pack->num_entities - 1) {\n\t\t\t\t\t\tmemmove(&pack->entities[best], &pack->entities[best + 1], sizeof(pack->entities[0]) * (pack->num_entities - 1 - best));\n\t\t\t\t\t\tmemmove(&distances[best], &distances[best + 1], sizeof(distances[0]) * (pack->num_entities - 1 - best));\n\t\t\t\t\t}\n\t\t\t\t\tposition = pack->num_entities - 1;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tposition = pack->num_entities++;\n\t\t\t\t}\n\n\t\t\t\tdistances[position] = distance;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (pack->num_entities == max_packet_entities) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tposition = pack->num_entities++;\n\t\t\t}\n\n\t\t\tstate = &pack->entities[position];\n\t\t\tmemset(state, 0, sizeof(*state));\n\n\t\t\tstate->number = e;\n\t\t\tstate->flags = 0;\n\t\t\tVectorCopy (ent->v->origin, state->origin);\n\t\t\tVectorCopy (ent->v->angles, state->angles);\n\t\t\tstate->modelindex = ent->v->modelindex;\n\t\t\tstate->frame = ent->v->frame;\n\t\t\tstate->colormap = ent->v->colormap;\n\t\t\tstate->skinnum = ent->v->skin;\n\t\t\tstate->effects = TranslateEffects(ent);\n#ifdef FTE_PEXT_TRANS\n\t\t\tstate->trans = ent->xv.alpha >= 1.0f ? 0 : bound(0, (byte)(ent->xv.alpha * 254.0f), 254);\n#endif\n#ifdef FTE_PEXT_COLOURMOD\n\t\t\tif (ent->xv.colourmod[0] != 1.0f && ent->xv.colourmod[1] != 1.0f && ent->xv.colourmod[2] != 1.0f)\n\t\t\t{\n\t\t\t\tstate->colourmod[0] = bound(0, ent->xv.colourmod[0] * (256.0f / 8.0f), 255);\n\t\t\t\tstate->colourmod[1] = bound(0, ent->xv.colourmod[1] * (256.0f / 8.0f), 255);\n\t\t\t\tstate->colourmod[2] = bound(0, ent->xv.colourmod[2] * (256.0f / 8.0f), 255);\n\t\t\t}\n#endif\n\t\t}\n\t} // server flash\n\n\t// encode the packet entities as a delta from the\n\t// last packetentities acknowledged by the client\n\n\tSV_EmitPacketEntities (client, pack, msg);\n\n\t// now add the specialized nail update\n\tSV_EmitNailUpdate (msg, recorder);\n\n\t// Translate NQ progs' EF_MUZZLEFLASH to svc_muzzleflash\n\tif (pr_nqprogs)\n\t{\n\t\tfor (e=1, ent=EDICT_NUM(e) ; e < sv.num_edicts ; e++, ent = NEXT_EDICT(ent))\n\t\t{\n\t\t\t// ignore ents without visible models\n\t\t\tif (!ent->v->modelindex || !*PR_GetEntityString(ent->v->model))\n\t\t\t\tcontinue;\n\n\t\t\t// ignore if not touching a PV leaf (meag: this does nothing... complete or remove?)\n\t\t\tif (pvs && ent->e.num_leafs >= 0) {\n\t\t\t\tfor (i = 0; i < ent->e.num_leafs; i++)\n\t\t\t\t\tif (pvs[ent->e.leafnums[i] >> 3] & (1 << (ent->e.leafnums[i] & 7)))\n\t\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif ((int)ent->v->effects & EF_MUZZLEFLASH) {\n\t\t\t\tent->v->effects = (int)ent->v->effects & ~EF_MUZZLEFLASH;\n\t\t\t\tMSG_WriteByte (msg, svc_muzzleflash);\n\t\t\t\tMSG_WriteShort (msg, e);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n============\nSV_SetVisibleEntitiesForBot\n============\n*/\nvoid SV_SetVisibleEntitiesForBot (client_t* client)\n{\n\tint j = 0;\n\tint e = 0;\n\tunsigned int client_flag = 1 << (client - svs.clients);\n\tvec3_t org;\n\tbyte* pvs = NULL;\n\n\tif (!fofs_visibility)\n\t\treturn;\n\n\tVectorAdd (client->edict->v->origin, client->edict->v->view_ofs, org);\n\tpvs = CM_FatPVS (org); // search some PVS\n\n\t// players first\n\tfor (j = 0; j < MAX_CLIENTS; j++)\n\t{\n\t\tif (SV_PlayerVisibleToClient(client, j, pvs, client->edict, svs.clients[j].edict)) {\n\t\t\t((eval_t *)((byte *)(svs.clients[j].edict)->v + fofs_visibility))->_int |= client_flag;\n\t\t}\n\t\telse {\n\t\t\t((eval_t *)((byte *)(svs.clients[j].edict)->v + fofs_visibility))->_int &= ~client_flag;\n\t\t}\n\t}\n\n\t// Other entities\n\tfor (e = pr_nqprogs ? 1 : MAX_CLIENTS + 1; e < sv.num_edicts; e++)\n\t{\n\t\tedict_t* ent = EDICT_NUM (e);\n\n\t\tif (SV_EntityVisibleToClient(client, e, pvs)) {\n\t\t\t((eval_t *)((byte *)(ent)->v + fofs_visibility))->_int |= client_flag;\n\t\t}\n\t\telse {\n\t\t\t((eval_t *)((byte *)(ent)->v + fofs_visibility))->_int &= ~client_flag;\n\t\t}\n\t}\n}\n\nqbool SV_SkipCommsBotMessage(client_t* client)\n{\n\textern cvar_t sv_serveme_fix;\n\n\treturn sv_serveme_fix.value && client->spectator && !strcmp(client->name, \"[ServeMe]\");\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_init.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\n#ifndef SERVERONLY\nvoid CL_ClearState(void);\nvoid CL_ClearQueuedPackets(void);\n#endif\n\nserver_static_t\tsvs;\t\t\t\t// persistent server info\nserver_t\t\tsv;\t\t\t\t\t// local server\ndemo_t\t\t\tdemo;\t\t\t\t// server demo struct\n\nchar\tlocalmodels[MAX_MODELS][MODEL_NAME_LEN];\t// inline model names for precache\n\n//char localinfo[MAX_LOCALINFO_STRING+1]; // local game info\nctxinfo_t _localinfo_;\n\nint fofs_items2;\nint fofs_maxspeed, fofs_gravity;\nint fofs_movement;\nint fofs_vw_index;\nint fofs_hideentity;\nint fofs_trackent;\nint fofs_visibility;\nint fofs_hide_players;\nint fofs_teleported;\n\n/*\n================\nSV_ModelIndex\n\n================\n*/\nint SV_ModelIndex (char *name)\n{\n\tint i;\n\n\tif (!name || !name[0])\n\t\treturn 0;\n\n\tfor (i=0 ; i<MAX_MODELS && sv.model_precache[i] ; i++)\n\t\tif (!strcmp(sv.model_precache[i], name))\n\t\t\treturn i;\n\tif (i==MAX_MODELS || !sv.model_precache[i])\n\t\tSV_Error (\"SV_ModelIndex: model %s not precached\", name);\n\treturn i;\n}\n\n/*\n================\nSV_FlushSignon\n\nMoves to the next signon buffer if needed\n================\n*/\nvoid SV_FlushSignon (void)\n{\n\tif (sv.signon.cursize < sv.signon.maxsize - 512)\n\t\treturn;\n\n\tif (sv.num_signon_buffers == MAX_SIGNON_BUFFERS-1)\n\t\tSV_Error (\"sv.num_signon_buffers == MAX_SIGNON_BUFFERS-1\");\n\n\tsv.signon_buffer_size[sv.num_signon_buffers-1] = sv.signon.cursize;\n\tsv.signon.data = sv.signon_buffers[sv.num_signon_buffers];\n\tsv.num_signon_buffers++;\n\tsv.signon.cursize = 0;\n}\n\n/*\n================\nSV_CreateBaseline\n\nEntity baselines are used to compress the update messages\nto the clients -- only the fields that differ from the\nbaseline will be transmitted\n================\n*/\nstatic void SV_CreateBaseline (void)\n{\n\tedict_t  *svent;\n\tint      entnum;\n\n\tfor (entnum = 0; entnum < sv.num_edicts ; entnum++)\n\t{\n\t\tsvent = EDICT_NUM(entnum);\n\t\tif (svent->e.free)\n\t\t\tcontinue;\n\t\t// create baselines for all player slots,\n\t\t// and any other edict that has a visible model\n\t\tif (entnum > MAX_CLIENTS && !svent->v->modelindex)\n\t\t\tcontinue;\n\n\t\t//\n\t\t// create entity baseline\n\t\t//\n\t\tsvent->e.baseline.number = entnum;\n\t\tVectorCopy (svent->v->origin, svent->e.baseline.origin);\n\t\tVectorCopy (svent->v->angles, svent->e.baseline.angles);\n\t\tsvent->e.baseline.frame = svent->v->frame;\n\t\tsvent->e.baseline.skinnum = svent->v->skin;\n\t\tif (entnum > 0 && entnum <= MAX_CLIENTS)\n\t\t{\n\t\t\tsvent->e.baseline.colormap = entnum;\n\t\t\tsvent->e.baseline.modelindex = SV_ModelIndex(\"progs/player.mdl\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsvent->e.baseline.colormap = 0;\n\t\t\tsvent->e.baseline.modelindex = svent->v->modelindex;\n\t\t}\n\n#ifdef FTE_PEXT_TRANS\n\t\tsvent->e.baseline.trans = svent->xv.alpha >= 1.0f ? 0 : bound(0, (byte)(svent->xv.alpha * 254.0), 254);\n#endif\n#ifdef FTE_PEXT_COLOURMOD\n\t\tsvent->e.baseline.colourmod[0] = bound(0, svent->xv.colourmod[0] * (256.0f / 8.0f), 255);\n\t\tsvent->e.baseline.colourmod[1] = bound(0, svent->xv.colourmod[1] * (256.0f / 8.0f), 255);\n\t\tsvent->e.baseline.colourmod[2] = bound(0, svent->xv.colourmod[2] * (256.0f / 8.0f), 255);\n#endif\n\n\t}\n\tsv.num_baseline_edicts = sv.num_edicts;\n}\n\n/*\n================\nSV_SaveSpawnparms\n\nGrabs the current state of the progs serverinfo flags \nand each client for saving across the\ntransition to another level\n================\n*/\nstatic void SV_SaveSpawnparms (void)\n{\n\tint i, j;\n\n\tif (!sv.state)\n\t\treturn;\t\t// no progs loaded yet\n\n\t// serverflags is the only game related thing maintained\n\tsvs.serverflags = PR_GLOBAL(serverflags);\n\n\tfor (i=0, sv_client = svs.clients ; i<MAX_CLIENTS ; i++, sv_client++)\n\t{\n\t\tif (sv_client->state != cs_spawned)\n\t\t\tcontinue;\n\n\t\t// needs to reconnect\n\t\tsv_client->state = cs_connected;\n\n\t\t// call the progs to get default spawn parms for the new client\n\t\tpr_global_struct->self = EDICT_TO_PROG(sv_client->edict);\n\t\tPR_GameSetChangeParms();\n\t\tfor (j=0 ; j<NUM_SPAWN_PARMS ; j++)\n\t\t\tsv_client->spawn_parms[j] = (&PR_GLOBAL(parm1))[j];\n\t}\n}\n\nstatic unsigned SV_CheckModel(char *mdl)\n{\n\tunsigned char *buf;\n\tunsigned short crc;\n\tint filesize;\n\tint mark;\n\n\tmark = Hunk_LowMark ();\n\tbuf = (byte *) FS_LoadHunkFile (mdl, &filesize);\n\tif (!buf)\n\t{\n\t\tif (!strcmp (mdl, \"progs/player.mdl\"))\n\t\t\treturn 33168;\n\t\telse if (!strcmp (mdl, \"progs/newplayer.mdl\"))\n\t\t\treturn 62211;\n\t\telse if (!strcmp (mdl, \"progs/eyes.mdl\"))\n\t\t\treturn 6967;\n\t\telse\n\t\t\tSV_Error (\"SV_CheckModel: could not load %s\\n\", mdl);\n\t}\n\n\tcrc = CRC_Block (buf, filesize);\n\tHunk_FreeToLowMark (mark);\n\n\treturn crc;\n}\n\n/*\n================\nSV_SpawnServer\n\nChange the server to a new map, taking all connected\nclients along with it.\n\nThis is called from the SV_Map_f() function, and when loading .sav files\n================\n*/\nvoid SV_SpawnServer(char *mapname, qbool devmap, char* entityfile, qbool loading_savegame)\n{\n\textern func_t ED_FindFunctionOffset (char *name);\n\n\tedict_t *ent;\n\tint i;\n\tint skill_level = current_skill;\n\n\textern cvar_t sv_loadentfiles, sv_loadentfiles_dir;\n\tchar *entitystring;\n\tchar oldmap[MAP_NAME_LEN];\n\textern qbool\tsv_allow_cheats;\n\textern cvar_t\tsv_cheats, sv_paused, sv_bigcoords;\n\n\tsv_error = false;\n\n\t// store old map name\n\tsnprintf (oldmap, MAP_NAME_LEN, \"%s\", sv.mapname);\n\n\tCon_DPrintf (\"SpawnServer: %s\\n\",mapname);\n\n#ifndef SERVERONLY\n\t// As client+server we do it here.\n\t// As serveronly we do it in NET_Init().\n\tNET_InitServer();\n#endif\n\n\tSV_SaveSpawnparms ();\n\tSV_LoadAccounts();\n\n#ifdef USE_PR2\n\t// remove bot clients\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tif( sv_vm && svs.clients[i].isBot )\n\t\t{\n\t\t\tsvs.clients[i].old_frags = 0;\n\t\t\tsvs.clients[i].edict->v->frags = 0.0;\n\t\t\tsvs.clients[i].name[0] = 0;\n\t\t\tsvs.clients[i].state = cs_free;\n\t\t\tInfo_RemoveAll(&svs.clients[i]._userinfo_ctx_);\n\t\t\tInfo_RemoveAll(&svs.clients[i]._userinfoshort_ctx_);\n\t\t\tSV_FullClientUpdate(&svs.clients[i], &sv.reliable_datagram);\n\t\t\tsvs.clients[i].isBot = 0;\n\t\t}\n\t}\n#endif\n\n\t// Shutdown game.\n\tPR_GameShutDown();\n\tPR_UnLoadProgs();\n\n\tsvs.spawncount++; // any partially connected client will be restarted\n\n#ifndef SERVERONLY\n\tcom_serveractive = false;\n#endif\n\tsv.state = ss_dead;\n\tsv.paused = false;\n\tCvar_SetROM(&sv_paused, \"0\");\n\n\tHost_ClearMemory();\n\n#ifndef SERVERONLY\n\tif (!oldmap[0]) {\n\t\tCbuf_InsertTextEx(&cbuf_server, \"exec server.cfg\\n\");\n\t\tCbuf_ExecuteEx(&cbuf_server);\n\t}\n#endif\n\n\tif (loading_savegame) {\n\t\tCvar_SetValue(&skill, skill_level);\n\t\tCvar_SetValue(&deathmatch, 0);\n\t\tCvar_SetValue(&coop, 0);\n\t\tCvar_SetValue(&teamplay, 0);\n\t\tCvar_SetValue(&maxclients, 1);\n\t\tCvar_Set(&sv_progsname, \"spprogs\"); // force progsname\n#ifdef USE_PR2\n\t\tCvar_SetValue(&sv_progtype, 0); // force .dat\n#endif\n\t}\n\n#ifdef FTE_PEXT_FLOATCOORDS\n\tif (sv_bigcoords.value)\n\t{\n\t\tmsg_coordsize = 4;\n\t\tmsg_anglesize = 2;\n\t}\n\telse\n\t{\n\t\tmsg_coordsize = 2;\n\t\tmsg_anglesize = 1;\n\t}\n#endif\n\n\tif ((int)coop.value)\n\t\tCvar_Set (&deathmatch, \"0\");\n\tcurrent_skill = (int) (skill.value + 0.5);\n\tif (current_skill < 0)\n\t\tcurrent_skill = 0;\n\tCvar_Set (&skill, va(\"%d\", current_skill));\n\tif (current_skill > 3)\n\t\tcurrent_skill = 3;\n\n\tif ((sv_cheats.value || devmap) && !sv_allow_cheats) {\n\t\tsv_allow_cheats = true;\n\t\tInfo_SetValueForStarKey (svs.info, \"*cheats\", \"ON\", MAX_SERVERINFO_STRING);\n\t}\n\telse if ((!sv_cheats.value && !devmap) && sv_allow_cheats) {\n\t\tsv_allow_cheats = false;\n\t\tInfo_SetValueForStarKey (svs.info, \"*cheats\", \"\", MAX_SERVERINFO_STRING);\n\t}\n\n\n\t// wipe the entire per-level structure\n\t// NOTE: this also set sv.mvdrecording to false, so calling SV_MVD_Record() at end of function\n\tmemset (&sv, 0, sizeof(sv));\n\tsv.max_edicts = MAX_EDICTS_SAFE;\n\n\tsv.datagram.maxsize = sizeof(sv.datagram_buf);\n\tsv.datagram.data = sv.datagram_buf;\n\tsv.datagram.allowoverflow = true;\n\n\tsv.reliable_datagram.maxsize = sizeof(sv.reliable_datagram_buf);\n\tsv.reliable_datagram.data = sv.reliable_datagram_buf;\n\n\tsv.multicast.maxsize = sizeof(sv.multicast_buf);\n\tsv.multicast.data = sv.multicast_buf;\n\n\tsv.signon.maxsize = sizeof(sv.signon_buffers[0]);\n\tsv.signon.data = sv.signon_buffers[0];\n\tsv.num_signon_buffers = 1;\n\n\tsv.time = 1.0;\n\n\t// load progs to get entity field count\n\t// which determines how big each edict is\n\t// and allocate edicts\n\tPR_LoadProgs ();\n#ifdef WITH_NQPROGS\n\tPR_InitPatchTables();\n#endif\n\tPR_InitProg();\n\n\tfor (i = 0; i < sv.max_edicts; i++)\n\t{\n\t\tsv.edicts[i].v = (entvars_t *)((byte *)sv.game_edicts + i * pr_edict_size);\n\t\tsv.edicts[i].e.entnum = i;\n\t\tsv.edicts[i].e.area.ed = &sv.edicts[i]; // yeah, pretty funny, but this help to find which edict_t own this area (link_t)\n\t\tPR_ClearEdict(&sv.edicts[i]);\n\t}\n\n\tfofs_items2 = ED_FindFieldOffset (\"items2\"); // ZQ_ITEMS2 extension\n\tfofs_maxspeed = ED_FindFieldOffset (\"maxspeed\");\n\tfofs_gravity = ED_FindFieldOffset (\"gravity\");\n\tfofs_movement = ED_FindFieldOffset (\"movement\");\n\tfofs_vw_index = ED_FindFieldOffset (\"vw_index\");\n\tfofs_hideentity = ED_FindFieldOffset (\"hideentity\");\n\tfofs_trackent = ED_FindFieldOffset (\"trackent\");\n\tfofs_visibility = ED_FindFieldOffset (\"visclients\");\n\tfofs_hide_players = ED_FindFieldOffset (\"hideplayers\");\n\tfofs_teleported = ED_FindFieldOffset (\"teleported\");\n\n#ifdef MVD_PEXT1_HIGHLAGTELEPORT\n\tif (fofs_teleported) {\n\t\tsvs.mvdprotocolextension1 |= MVD_PEXT1_HIGHLAGTELEPORT;\n\t}\n\telse {\n\t\tsvs.mvdprotocolextension1 &= ~MVD_PEXT1_HIGHLAGTELEPORT;\n\t}\n#endif\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\t{\n\t\textern cvar_t sv_pext_mvdsv_serversideweapon;\n\n\t\t// Cheap 'ktx' detection\n\t\tif (sv_pext_mvdsv_serversideweapon.value && strstr(Cvar_String(\"qwm_name\"), \"KTX\")) {\n\t\t\tsvs.mvdprotocolextension1 |= MVD_PEXT1_SERVERSIDEWEAPON;\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON2\n\t\t\tsvs.mvdprotocolextension1 |= MVD_PEXT1_SERVERSIDEWEAPON2;\n#endif\n\t\t}\n\t\telse {\n\t\t\tsvs.mvdprotocolextension1 &= ~MVD_PEXT1_SERVERSIDEWEAPON;\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON2\n\t\t\tsvs.mvdprotocolextension1 &= ~MVD_PEXT1_SERVERSIDEWEAPON2;\n#endif\n\t\t}\n\n\t}\n#endif\n#ifdef MVD_PEXT1_DEBUG_ANTILAG\n\t{\n\t\textern cvar_t sv_debug_antilag;\n\n\t\tif (sv_debug_antilag.value) {\n\t\t\tsvs.mvdprotocolextension1 |= MVD_PEXT1_DEBUG_ANTILAG;\n\t\t}\n\t\telse {\n\t\t\tsvs.mvdprotocolextension1 &= ~MVD_PEXT1_DEBUG_ANTILAG;\n\t\t}\n\t}\n#endif\n#ifdef MVD_PEXT1_DEBUG_WEAPON\n\t{\n\t\textern cvar_t sv_debug_weapons;\n\n\t\tif (sv_debug_weapons.value) {\n\t\t\tsvs.mvdprotocolextension1 |= MVD_PEXT1_DEBUG_WEAPON;\n\t\t}\n\t\telse {\n\t\t\tsvs.mvdprotocolextension1 &= ~MVD_PEXT1_DEBUG_WEAPON;\n\t\t}\n\t}\n#endif\n\n\t// find optional QC-exported functions.\n\t// we have it here, so we set it to NULL in case of PR2 progs.\n\tmod_SpectatorConnect = ED_FindFunctionOffset (\"SpectatorConnect\");\n\tmod_SpectatorThink = ED_FindFunctionOffset (\"SpectatorThink\");\n\tmod_SpectatorDisconnect = ED_FindFunctionOffset (\"SpectatorDisconnect\");\n\tmod_ChatMessage = ED_FindFunctionOffset (\"ChatMessage\");\n\tmod_UserInfo_Changed = ED_FindFunctionOffset (\"UserInfo_Changed\");\n\tmod_ConsoleCmd = ED_FindFunctionOffset (\"ConsoleCmd\");\n\tmod_UserCmd = ED_FindFunctionOffset (\"UserCmd\");\n\tmod_localinfoChanged = ED_FindFunctionOffset (\"localinfoChanged\");\n\tGE_ClientCommand = ED_FindFunctionOffset (\"GE_ClientCommand\");\n\tGE_PausedTic = ED_FindFunctionOffset (\"GE_PausedTic\");\n\tGE_ShouldPause = ED_FindFunctionOffset (\"GE_ShouldPause\");\n\n\t// leave slots at start for clients only\n\tsv.num_edicts = MAX_CLIENTS+1;\n\tfor (i=0 ; i<MAX_CLIENTS ; i++)\n\t{\n\t\tent = EDICT_NUM(i+1);\n\t\t// restore client name.\n\t\tPR_SetEntityString(ent, ent->v->netname, svs.clients[i].name);\n\t\t// reserve edict.\n\t\tsvs.clients[i].edict = ent;\n\t\t//ZOID - make sure we update frags right\n\t\tsvs.clients[i].old_frags = 0;\n\t}\n\n\t// fill sv.mapname and sv.modelname with new map name\n\tstrlcpy (sv.mapname, mapname, sizeof(sv.mapname));\n\tsnprintf (sv.modelname, sizeof(sv.modelname), \"maps/%s.bsp\", sv.mapname);\n#ifndef SERVERONLY\n\t// set cvar\n\tCvar_ForceSet (&host_mapname, mapname);\n#endif\n\n\tif (!(sv.worldmodel = CM_LoadMap (sv.modelname, false, &sv.map_checksum, &sv.map_checksum2))) // true if bad map\n\t{\n\t\tCon_Printf (\"Cant load map %s, falling back to %s\\n\", mapname, oldmap);\n\n\t\t// fill mapname, sv.mapname and sv.modelname with old map name\n\t\tstrlcpy (sv.mapname, oldmap, sizeof(sv.mapname)); \n\t\tsnprintf (sv.modelname, sizeof(sv.modelname), \"maps/%s.bsp\", sv.mapname);\n\t\tmapname = oldmap;\n\n\t\t// and re-load old map\n\t\tsv.worldmodel = CM_LoadMap (sv.modelname, false, &sv.map_checksum, &sv.map_checksum2);\n\n\t\t// this should never happen\n\t\tif (!sv.worldmodel)\n\t\t\tSV_Error (\"CM_LoadMap: bad map\");\n\t}\n\n\t{\n\t\textern cvar_t sv_extlimits, sv_bspversion;\n\n\t\tif (sv_extlimits.value == 0 || (sv_extlimits.value == 2 && sv_bspversion.value < 2)) {\n\t\t\tsv.max_edicts = min(sv.max_edicts, MAX_EDICTS_SAFE);\n\t\t}\n\t}\n\n\tsv.map_checksum2 = Com_TranslateMapChecksum (sv.mapname, sv.map_checksum2);\n\tsv.static_entity_count = 0;\n\n\tSV_ClearWorld (); // clear physics interaction links\n\n#ifdef USE_PR2\n\tif ( sv_vm )\n\t{\n\t\tsv.sound_precache[0] = \"\";\n\t\tsv.model_precache[0] = \"\";\n\t}\n\telse\n#endif\n\t{\n\t\tsv.sound_precache[0] = pr_strings;\n\t\tsv.model_precache[0] = pr_strings;\n\t}\n\tsv.model_precache[1] = sv.modelname;\n\tsv.models[1] = sv.worldmodel;\n\tfor (i = 1; i < CM_NumInlineModels(); i++)\n\t{\n\t\tsv.model_precache[1+i] = localmodels[i];\n\t\tsv.models[i+1] =  CM_InlineModel (localmodels[i]);\n\t}\n\n\t//check player/eyes models for hacks\n\tsv.model_player_checksum = SV_CheckModel(\"progs/player.mdl\");\n\tsv.model_newplayer_checksum = SV_CheckModel(\"progs/newplayer.mdl\");\n\tsv.eyes_player_checksum = SV_CheckModel(\"progs/eyes.mdl\");\n\n\t//\n\t// spawn the rest of the entities on the map\n\t//\n\n\t// precache and static commands can be issued during\n\t// map initialization\n\tsv.state = ss_loading;\n#ifndef SERVERONLY\n\tcom_serveractive = true;\n#endif\n\n\tent = EDICT_NUM(0);\n\tent->e.free = false;\n\tPR_SetEntityString(ent, ent->v->model, sv.modelname);\n\tent->v->modelindex = 1;\t\t// world model\n\tent->v->solid = SOLID_BSP;\n\tent->v->movetype = MOVETYPE_PUSH;\n\n\t// information about the server\n\tPR_SetEntityString(ent, ent->v->netname, VersionStringFull());\n\tPR_SetEntityString(ent, ent->v->targetname, SERVER_NAME);\n\tent->v->impulse = VERSION_NUM;\n\tent->v->items = pr_numbuiltins - 1;\n\n\tPR_SetGlobalString(PR_GLOBAL(mapname), sv.mapname);\n\t// serverflags are for cross level information (sigils)\n\tPR_GLOBAL(serverflags) = svs.serverflags;\n\tif (pr_nqprogs)\n\t{\n\t\tpr_globals[35] = deathmatch.value;\n\t\tpr_globals[36] = coop.value;\n\t\tpr_globals[37] = teamplay.value;\n\t\tNQP_Reset ();\n\t}\n\n\tif (pr_nqprogs)\n\t{\n\t\t// register the cvars that NetQuake provides for mod use\n\t\tconst char **var, *nqcvars[] = {\"gamecfg\", \"scratch1\", \"scratch2\", \"scratch3\", \"scratch4\",\n\t\t\t\"saved1\", \"saved2\", \"saved3\", \"saved4\", \"savedgamecfg\", \"temp1\", NULL};\n\t\tfor (var = nqcvars; *var; var++)\n\t\t\tCvar_Create((char *)/*stupid const warning*/ *var, \"0\", 0);\n\t}\n\n\t// run the frame start qc function to let progs check cvars\n\tif (!pr_nqprogs)\n\t\tSV_ProgStartFrame (false);\n\n\t// ********* External Entity support (.ent file(s) in gamedir/maps) pinched from ZQuake *********\n\t// load and spawn all other entities\n\tentitystring = NULL;\n\tif ((int)sv_loadentfiles.value)\n\t{\n\t\tchar ent_path[1024] = {0};\n\n\t\tif (!entityfile || !entityfile[0])\n\t\t\tentityfile = sv.mapname;\n\n\t\t// first try maps/sv_loadentfiles_dir/\n\t\tif (sv_loadentfiles_dir.string[0])\n\t\t{\n\t\t\tsnprintf(ent_path, sizeof(ent_path), \"maps/%s/%s.ent\", sv_loadentfiles_dir.string, entityfile);\n\t\t\tentitystring = (char *) FS_LoadHunkFile(ent_path, NULL);\n\t\t}\n\n\t\t// try maps/ if not loaded yet.\n\t\tif (!entitystring)\n\t\t{\n\t\t\tsnprintf(ent_path, sizeof(ent_path), \"maps/%s.ent\", entityfile);\n\t\t\tentitystring = (char *) FS_LoadHunkFile(ent_path, NULL);\n\t\t}\n\n\t\tif (entitystring) {\n\t\t\tCon_DPrintf (\"Using entfile %s\\n\", ent_path);\n\t\t}\n\t}\n\n\tif (!entitystring) {\n\t\tentitystring = CM_EntityString();\n\t}\n\n\tPR_LoadEnts(entitystring);\n\t// ********* End of External Entity support code *********\n\n\t// look up some model indexes for specialized message compression\n\tSV_FindModelNumbers ();\n\n\t// all spawning is completed, any further precache statements\n\t// or prog writes to the signon message are errors\n\tsv.state = ss_active;\n\n\t// run two frames to allow everything to settle\n\tSV_Physics ();\n\tsv.time += 0.1;\n\tSV_Physics ();\n\tsv.time += 0.1;\n\tsv.old_time = sv.time;\n\n\t// save movement vars\n\tSV_SetMoveVars();\n\n\t// create a baseline for more efficient communications\n\tSV_CreateBaseline ();\n\tsv.signon_buffer_size[sv.num_signon_buffers-1] = sv.signon.cursize;\n\n\tInfo_SetValueForKey (svs.info, \"map\", sv.mapname, MAX_SERVERINFO_STRING);\n\n\t// calltimeofday.\n\t{\n\t\textern void PF_calltimeofday (void);\n\t\tpr_global_struct->time = sv.time;\n\t\tpr_global_struct->self = 0;\n\n\t\tPF_calltimeofday();\n\t}\n\n\tCon_DPrintf (\"Server spawned.\\n\");\n\n\t// we change map - clear whole demo struct and sent initial state to all dest if any (for QTV only I thought)\n\tSV_MVD_Record(NULL, true);\n\n#ifndef SERVERONLY\n\tCL_ClearState();\n\tCL_ClearQueuedPackets();\n#endif\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_log.h",
    "content": "/*\nCopyright (C) 2004 VVD (vvd0@sorceforge.net).\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifndef __LOG_H__\n#define __LOG_H__\n\nenum {\tMIN_LOG = 0, CONSOLE_LOG = 0, ERROR_LOG,  RCON_LOG,\n\t\tTELNET_LOG,  FRAG_LOG,        PLAYER_LOG, MOD_FRAG_LOG, MAX_LOG};\n\ntypedef struct log_s {\n\tFILE\t\t*sv_logfile;\n\tchar\t\t*command;\n\tchar\t\t*file_name;\n\tchar\t\t*message_off;\n\tchar\t\t*message_on;\n\txcommand_t\tfunction;\n\tint\t\t\tlog_level;\n} log_t;\n\nextern\tlog_t\tlogs[MAX_LOG];\nextern\tcvar_t\tfrag_log_type;\nextern\tcvar_t\ttelnet_log_level;\n\n//bliP: logging\nvoid\tSV_Logfile (int sv_log, qbool newlog);\nvoid\tSV_LogPlayer(client_t *cl, char *msg, int level);\n//<-\nvoid\tSV_Write_Log(int sv_log, int level, char *msg);\n\n#endif /* !__LOG_H__ */\n"
  },
  {
    "path": "src/sv_login.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\n*/\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\n#ifdef WEBSITE_LOGIN_SUPPORT\n#undef WEBSITE_LOGIN_SUPPORT\n#endif\n#if defined(SERVERONLY) && defined(WWW_INTEGRATION)\n#define WEBSITE_LOGIN_SUPPORT\n#include \"central.h\"\n#endif\n\n#define MAX_ACCOUNTS 1000\n#define MAX_FAILURES 10\n#define MAX_LOGINNAME (DIGEST_SIZE * 2 + 1)\n#define ACC_FILE \"accounts\"\n#define ACC_DIR \"users\"\n\ncvar_t sv_login = { \"sv_login\", \"0\" };\t// if enabled, login required\n#ifdef WEBSITE_LOGIN_SUPPORT\ncvar_t sv_login_web = { \"sv_login_web\", \"1\" }; // 0=local files, 1=auth via website (bans can be in local files), 2=mandatory auth (must have account in local files)\n#define LoginModeFileBased() ((int)sv_login_web.value == 0)\n#define LoginModeOptionalWeb() ((int)sv_login_web.value == 1)\n#define LoginModeMandatoryWeb() ((int)sv_login_web.value == 2)\n#define LoginMustHaveLocalAccount() (LoginModeMandatoryWeb() || LoginModeFileBased())\n#define WebLoginsEnabled() (!LoginModeFileBased())\n#else\n#define LoginModeFileBased() (1)\n#define LoginModeOptionalWeb() (0)\n#define LoginModeMandatoryWeb() (0)\n#define LoginMustHaveLocalAccount() (1)\n#define WebLoginsEnabled() (0)\n#endif\n\nextern cvar_t sv_hashpasswords;\nstatic void SV_SuccessfulLogin(client_t* cl);\nstatic void SV_BlockedLogin(client_t* cl);\nstatic void SV_ForceClientName(client_t* cl, const char* forced_name);\n\ntypedef enum { a_free, a_ok, a_blocked } acc_state_t;\ntypedef enum { use_log, use_ip } quse_t;\n\ntypedef struct\n{\n\tchar        login[MAX_LOGINNAME];\n\tchar        pass[MAX_LOGINNAME];\n\tint         failures;\n\tint         inuse;\n\tipfilter_t  address;\n\tacc_state_t state;\n\tquse_t      use;\n} account_t;\n\nstatic account_t accounts[MAX_ACCOUNTS];\nstatic int       num_accounts = 0;\n\nstatic qbool validAcc(char* acc)\n{\n\tchar* s = acc;\n\n\tfor (; *acc; acc++)\n\t{\n\t\tif (*acc < 'a' || *acc > 'z')\n\t\t\tif (*acc < 'A' || *acc > 'Z')\n\t\t\t\tif (*acc < '0' || *acc > '9')\n\t\t\t\t\tif (*acc != '.' && *acc != '_')\n\t\t\t\t\t\treturn false;\n\t}\n\n\treturn acc - s <= MAX_LOGINNAME && acc - s >= 3;\n}\n\n/*\n=================\nWriteAccounts\n\nWrites account list to disk\n=================\n*/\n\nstatic void WriteAccounts(void)\n{\n\tint c;\n\tFILE* f;\n\taccount_t* acc;\n\n\t//Sys_mkdir(ACC_DIR);\n\tif ((f = fopen(va(\"%s/\" ACC_FILE, fs_gamedir), \"wt\")) == NULL)\n\t{\n\t\tCon_Printf(\"Warning: couldn't open for writing \" ACC_FILE \"\\n\");\n\t\treturn;\n\t}\n\n\tfor (acc = accounts, c = 0; c < num_accounts; acc++)\n\t{\n\t\tif (acc->state == a_free)\n\t\t\tcontinue;\n\n\t\tif (acc->use == use_log)\n\t\t\tfprintf(f, \"%s %s %d %d\\n\", acc->login, acc->pass, acc->state, acc->failures);\n\t\telse\n\t\t\tfprintf(f, \"%s %s %d\\n\", acc->login, acc->pass, acc->state);\n\n\t\tc++;\n\t}\n\n\tfclose(f);\n\n\t// force cache rebuild.\n\tFS_FlushFSHash();\n}\n\n/*\n=================\nSV_LoadAccounts\n\nloads account list from disk\n=================\n*/\nqbool StringToFilter(char* s, ipfilter_t* f);\nvoid SV_LoadAccounts(void)\n{\n\tint i;\n\tFILE* f;\n\taccount_t* acc = accounts;\n\tclient_t* cl;\n\n\tif ((f = fopen(va(\"%s/\" ACC_FILE, fs_gamedir), \"rt\")) == NULL)\n\t{\n\t\tCon_DPrintf(\"couldn't open \" ACC_FILE \"\\n\");\n\t\t// logout\n\t\tnum_accounts = 0;\n\t\tfor (cl = svs.clients; cl - svs.clients < MAX_CLIENTS; cl++)\n\t\t{\n\t\t\tif (cl->logged > 0) {\n\t\t\t\tcl->logged = 0;\n\t\t\t}\n\t\t\tif (!cl->logged_in_via_web) {\n\t\t\t\tcl->login[0] = 0;\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\twhile (!feof(f))\n\t{\n\t\tmemset(acc, 0, sizeof(account_t));\n\t\t// Is realy safe to use 'fscanf(f, \"%s\", s)'? FIXME!\n\t\tif (fscanf(f, \"%s\", acc->login) != 1) {\n\t\t\tCon_Printf(\"Error reading account data\\n\");\n\t\t\tbreak;\n\t\t}\n\t\tif (StringToFilter(acc->login, &acc->address))\n\t\t{\n\t\t\tstrlcpy(acc->pass, acc->login, MAX_LOGINNAME);\n\t\t\tacc->use = use_ip;\n\t\t\tif (fscanf(f, \"%s %d\\n\", acc->pass, (int*)&acc->state) != 2) {\n\t\t\t\tCon_Printf(\"Error reading account data\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (fscanf(f, \"%s %d %d\\n\", acc->pass, (int*)&acc->state, &acc->failures) != 3) {\n\t\t\t\tCon_Printf(\"Error reading account data\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (acc->state != a_free) // lol?\n\t\t\tacc++;\n\t}\n\n\tnum_accounts = acc - accounts;\n\n\tfclose(f);\n\n\t// for every connected client check if their login is still valid\n\tfor (cl = svs.clients; cl - svs.clients < MAX_CLIENTS; cl++)\n\t{\n\t\tif (cl->state < cs_connected)\n\t\t\tcontinue;\n\n\t\tif (cl->logged <= 0)\n\t\t\tcontinue;\n\n\t\tif (cl->logged_in_via_web)\n\t\t\tcontinue;\n\n\t\tfor (i = 0, acc = accounts; i < num_accounts; i++, acc++)\n\t\t\tif ((acc->use == use_log && !strncmp(acc->login, cl->login, CLIENT_LOGIN_LEN))\n\t\t\t\t|| (acc->use == use_ip && !strcmp(acc->login, va(\"%d.%d.%d.%d\", cl->realip.ip[0], cl->realip.ip[1], cl->realip.ip[2], cl->realip.ip[3]))))\n\t\t\t\tbreak;\n\n\t\tif (i < num_accounts && acc->state == a_ok)\n\t\t{\n\t\t\t// login again if possible\n\t\t\tif (!acc->inuse || acc->use == use_ip)\n\t\t\t{\n\t\t\t\tcl->logged = i + 1;\n\t\t\t\tif (acc->use == use_ip)\n\t\t\t\t\tstrlcpy(cl->login, acc->pass, CLIENT_LOGIN_LEN);\n\n\t\t\t\tacc->inuse++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\t// login is not valid anymore, logout\n\t\tcl->logged = 0;\n\t\tcl->login[0] = 0;\n\t}\n}\n\n/*\n=================\nSV_CreateAccount_f\n\nacc_create <login> [<password>]\nif password is not given, login will be used for password\nlogin/pass has to be max 16 chars and at least 3, only regular chars are acceptable\n=================\n*/\nvoid SV_CreateAccount_f(void)\n{\n\tint i, spot, c;\n\tipfilter_t adr;\n\tquse_t use;\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tCon_Printf(\"usage: acc_create <login> [<password>]\\n       acc_create <address> <username>\\nmaximum %d characters for login/pass\\n\", MAX_LOGINNAME - 1); //bliP: address typo\n\t\treturn;\n\t}\n\n\tif (num_accounts == MAX_ACCOUNTS)\n\t{\n\t\tCon_Printf(\"MAX_ACCOUNTS reached\\n\");\n\t\treturn;\n\t}\n\n\tif (StringToFilter(Cmd_Argv(1), &adr))\n\t{\n\t\tuse = use_ip;\n\t\tif (Cmd_Argc() < 3)\n\t\t{\n\t\t\tCon_Printf(\"usage: acc_create <address> <username>\\nmaximum %d characters for username\\n\", MAX_LOGINNAME - 1); //bliP; address typo\n\t\t\treturn;\n\t\t}\n\t}\n\telse\n\t{\n\t\tuse = use_log;\n\n\t\t// validate user login/pass\n\t\tif (!validAcc(Cmd_Argv(1)))\n\t\t{\n\t\t\tCon_Printf(\"Invalid login!\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (Cmd_Argc() == 4 && !validAcc(Cmd_Argv(2)))\n\t\t{\n\t\t\tCon_Printf(\"Invalid pass!\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// find free spot, check if login exist;\n\tfor (i = 0, c = 0, spot = -1; c < num_accounts; i++)\n\t{\n\t\tif (accounts[i].state == a_free)\n\t\t{\n\t\t\tif (spot == -1)\tspot = i;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!strcasecmp(accounts[i].login, Cmd_Argv(1)) ||\n\t\t\t(use == use_ip && !strcasecmp(accounts[i].login, Cmd_Argv(2))))\n\t\t\tbreak;\n\n\t\tc++;\n\t}\n\n\tif (c < num_accounts)\n\t{\n\t\tCon_Printf(\"Login already in use\\n\");\n\t\treturn;\n\t}\n\n\tif (spot == -1)\n\t\tspot = i;\n\n\t// create an account\n\tnum_accounts++;\n\tstrlcpy(accounts[spot].login, Cmd_Argv(1), MAX_LOGINNAME);\n\tif (Cmd_Argc() == 3)\n\t\ti = 2;\n\telse\n\t\ti = 1;\n\tstrlcpy(accounts[spot].pass, (int)sv_hashpasswords.value && use == use_log ?\n\t\tSHA1(Cmd_Argv(i)) : Cmd_Argv(i), MAX_LOGINNAME);\n\n\taccounts[spot].state = a_ok;\n\taccounts[spot].use = use;\n\n\tCon_Printf(\"login %s created\\n\", Cmd_Argv(1));\n\tWriteAccounts();\n}\n\n/*\n=================\nSV_RemoveAccount_f\n\nacc_remove <login>\nremoves the login\n=================\n*/\nvoid SV_RemoveAccount_f(void)\n{\n\tint i, c, j;\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tCon_Printf(\"usage: acc_remove <login>\\n\");\n\t\treturn;\n\t}\n\n\tfor (i = 0, c = 0; c < num_accounts; i++)\n\t{\n\t\tif (accounts[i].state == a_free)\n\t\t\tcontinue;\n\n\t\tif (!strcasecmp(accounts[i].login, Cmd_Argv(1))) {\n\t\t\t// Logout anyone using this login\n\t\t\tif ((int)sv_login.value == 1) {\n\t\t\t\t// Mandatory web logins, or using local files\n\t\t\t\tif (LoginMustHaveLocalAccount()) {\n\t\t\t\t\tfor (j = 0; j < MAX_CLIENTS; ++j) {\n\t\t\t\t\t\tclient_t* cl = &svs.clients[j];\n\n\t\t\t\t\t\tif (!strcasecmp(cl->login, Cmd_Argv(1))) {\n\t\t\t\t\t\t\tSV_Logout(cl);\n\t\t\t\t\t\t\tSV_DropClient(cl);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update 'logged' pointers back to accounts list\n\t\t\tif (i != num_accounts - 1) {\n\t\t\t\tmemcpy(&accounts[i], &accounts[num_accounts - 1], sizeof(accounts[i]));\n\t\t\t\tmemset(&accounts[num_accounts - 1], 0, sizeof(accounts[num_accounts - 1]));\n\n\t\t\t\t// Update references from the last account which we just moved\n\t\t\t\tfor (j = 0; j < MAX_CLIENTS; ++j) {\n\t\t\t\t\tclient_t* cl = &svs.clients[j];\n\t\t\t\t\tif (cl->logged == num_accounts) {\n\t\t\t\t\t\tcl->logged = i + 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tnum_accounts--;\n\t\t\tCon_Printf(\"login %s removed\\n\", Cmd_Argv(1));\n\t\t\tWriteAccounts();\n\t\t\treturn;\n\t\t}\n\n\t\tc++;\n\t}\n\n\tCon_Printf(\"account for %s not found\\n\", Cmd_Argv(1));\n}\n\n/*\n=================\nSV_ListAccount_f\n\nshows the list of accounts\n=================\n*/\nvoid SV_ListAccount_f(void)\n{\n\tint i, c;\n\n\tif (!num_accounts)\n\t{\n\t\tCon_Printf(\"account list is empty\\n\");\n\t\treturn;\n\t}\n\n\tCon_Printf(\"account list:\\n\");\n\n\tfor (i = 0, c = 0; c < num_accounts; i++)\n\t{\n\t\tif (accounts[i].state != a_free)\n\t\t{\n\t\t\tCon_Printf(\"%.16s %s\\n\", accounts[i].login, accounts[i].state == a_ok ? \"\" : \"blocked\");\n\t\t\tc++;\n\t\t}\n\t}\n\n\tCon_Printf(\"%d login(s) found\\n\", num_accounts);\n}\n\n/*\n=================\nSV_blockAccount\n\nblocks/unblocks an account\n=================\n*/\nvoid SV_blockAccount(qbool block)\n{\n\tint i, j;\n\n\tfor (i = 0; i < num_accounts; i++)\n\t{\n\t\tif (accounts[i].state == a_free)\n\t\t\tcontinue;\n\n\t\tif (!strcasecmp(accounts[i].login, Cmd_Argv(1)))\n\t\t{\n\t\t\tif (block) {\n\t\t\t\taccounts[i].state = a_blocked;\n\t\t\t\tCon_Printf(\"account %s blocked\\n\", Cmd_Argv(1));\n\n\t\t\t\tfor (j = 0; j < MAX_CLIENTS; ++j) {\n\t\t\t\t\tif (!strcasecmp(svs.clients[j].login, accounts[i].login)) {\n\t\t\t\t\t\tSV_DropClient(&svs.clients[j]);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (accounts[i].state != a_blocked) {\n\t\t\t\tCon_Printf(\"account %s not blocked\\n\", Cmd_Argv(1));\n\t\t\t}\n\t\t\telse {\n\t\t\t\taccounts[i].state = a_ok;\n\t\t\t\taccounts[i].failures = 0;\n\t\t\t\tCon_Printf(\"account %s unblocked\\n\", Cmd_Argv(1));\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\t}\n\n\tCon_Printf(\"account %s not found\\n\", Cmd_Argv(1));\n}\n\nvoid SV_UnblockAccount_f(void)\n{\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tCon_Printf(\"usage: acc_unblock <login>\\n\");\n\t\treturn;\n\t}\n\n\tSV_blockAccount(false);\n\tWriteAccounts();\n}\n\nvoid SV_BlockAccount_f(void)\n{\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tCon_Printf(\"usage: acc_block <login>\\n\");\n\t\treturn;\n\t}\n\n\tSV_blockAccount(true);\n\tWriteAccounts();\n}\n\n\n/*\n=================\nchecklogin\n\nreturns positive value if login/pass are valid\nvalues <= 0 indicates a failure\n=================\n*/\nstatic int checklogin(char* log1, char* pass, quse_t use)\n{\n\tint i, c;\n\n\tfor (i = 0, c = 0; c < num_accounts; i++)\n\t{\n\t\tif (accounts[i].state == a_free)\n\t\t\tcontinue;\n\n\t\tif (use == accounts[i].use &&\n\t\t\t/*use == use_log && accounts[i].use == use_log && */\n\t\t\t!strcasecmp(log1, accounts[i].login))\n\t\t{\n\t\t\tif (accounts[i].state == a_blocked)\n\t\t\t\treturn -2;\n\n\t\t\t// Only do logins/failures if using file-based login list\n\t\t\tif (LoginMustHaveLocalAccount()) {\n\t\t\t\tif (accounts[i].inuse && accounts[i].use == use_log) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\n\t\t\t\tif (use == use_ip ||\n\t\t\t\t\t(!(int)sv_hashpasswords.value && !strcasecmp(pass,       accounts[i].pass)) ||\n\t\t\t\t\t( (int)sv_hashpasswords.value && !strcasecmp(SHA1(pass), accounts[i].pass)))\n\t\t\t\t{\n\t\t\t\t\taccounts[i].failures = 0;\n\t\t\t\t\taccounts[i].inuse++;\n\t\t\t\t\treturn i + 1;\n\t\t\t\t}\n\n\t\t\t\tif (++accounts[i].failures >= MAX_FAILURES) {\n\t\t\t\t\tSys_Printf(\"account %s blocked after %d failed login attempts\\n\", accounts[i].login, accounts[i].failures);\n\t\t\t\t\taccounts[i].state = a_blocked;\n\t\t\t\t}\n\t\t\t\tWriteAccounts();\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn i + 1;\n\t\t\t}\n\n\t\t\treturn 0;\n\t\t}\n\n\t\tc++;\n\t}\n\n\treturn 0;\n}\n\nvoid Login_Init(void)\n{\n\tCvar_Register(&sv_login);\n#ifdef WEBSITE_LOGIN_SUPPORT\n\tCvar_Register(&sv_login_web);\n#endif\n\n\tCmd_AddCommand(\"acc_create\", SV_CreateAccount_f);\n\tCmd_AddCommand(\"acc_remove\", SV_RemoveAccount_f);\n\tCmd_AddCommand(\"acc_list\", SV_ListAccount_f);\n\tCmd_AddCommand(\"acc_unblock\", SV_UnblockAccount_f);\n\tCmd_AddCommand(\"acc_block\", SV_BlockAccount_f);\n\n\t// load account list\n\t//SV_LoadAccounts();\n}\n\n/*\n===============\nSV_Login\n\ncalled on connect after cmd new is issued\n===============\n*/\nqbool SV_Login(client_t* cl)\n{\n\textern cvar_t sv_registrationinfo;\n\tchar* ip;\n\n\t// is sv_login is disabled, login is not necessery\n\tif (!(int)sv_login.value) {\n\t\t// If using local files then logout\n\t\tif (!cl->logged_in_via_web) {\n\t\t\tSV_Logout(cl);\n\t\t\tcl->logged = -1;\n\t\t}\n\t\treturn true;\n\t}\n\n\t// if we're already logged return (probably map change)\n\tif (cl->logged > 0 || cl->logged_in_via_web) {\n\t\treturn true;\n\t}\n\n\t// sv_login == 1 -> spectators don't login\n\tif ((int)sv_login.value == 1 && cl->spectator)\n\t{\n\t\tSV_Logout(cl);\n\t\tcl->logged = -1;\n\t\treturn true;\n\t}\n\n\t// check for account for ip\n\tip = va(\"%d.%d.%d.%d\", cl->realip.ip[0], cl->realip.ip[1], cl->realip.ip[2], cl->realip.ip[3]);\n\tif ((cl->logged = checklogin(ip, ip, use_ip)) > 0)\n\t{\n\t\tstrlcpy(cl->login, accounts[cl->logged - 1].pass, CLIENT_LOGIN_LEN);\n\t\treturn true;\n\t}\n\n\t// need to login before connecting\n\tcl->logged = 0;\n\tcl->login[0] = 0;\n\n\tif (sv_registrationinfo.string[0])\n\t\tSV_ClientPrintf2(cl, PRINT_HIGH, \"%s\\n\", sv_registrationinfo.string);\n\n\tif (WebLoginsEnabled()) {\n\t\tchar buffer[128];\n\t\tstrlcpy(buffer, \"//authprompt\\n\", sizeof(buffer));\n\n\t\tClientReliableWrite_Begin(cl, svc_stufftext, 2 + strlen(buffer));\n\t\tClientReliableWrite_String(cl, buffer);\n\n\t\tSV_ClientPrintf2(cl, PRINT_HIGH, \"Enter username:\\n\");\n\t}\n\telse {\n\t\tSV_ClientPrintf2(cl, PRINT_HIGH, \"Enter login & password:\\n\");\n\t}\n\n\treturn false;\n}\n\nvoid SV_Logout(client_t* cl)\n{\n\tif (cl->logged > 0 && cl->logged <= sizeof(accounts) / sizeof(accounts[0])) {\n\t\taccounts[cl->logged - 1].inuse--;\n\t}\n\n\tInfo_SetStar(&cl->_userinfo_ctx_, \"*auth\", \"\");\n\tInfo_SetStar(&cl->_userinfo_ctx_, \"*flag\", \"\");\n\tProcessUserInfoChange(cl, \"*auth\", cl->login);\n\tProcessUserInfoChange(cl, \"*flag\", cl->login_flag);\n\n\tmemset(cl->login, 0, sizeof(cl->login));\n\tmemset(cl->login_alias, 0, sizeof(cl->login_alias));\n\tmemset(cl->login_flag, 0, sizeof(cl->login_flag));\n\tmemset(cl->login_challenge, 0, sizeof(cl->login_challenge));\n\tmemset(cl->login_confirmation, 0, sizeof(cl->login_confirmation));\n\tcl->logged = 0;\n\tcl->logged_in_via_web = false;\n}\n\n#ifdef WEBSITE_LOGIN_SUPPORT\nvoid SV_ParseWebLogin(client_t* cl)\n{\n\tchar parameter[128] = { 0 };\n\tchar* p;\n\n\tstrlcpy(parameter, Cmd_Argv(1), sizeof(parameter));\n\tfor (p = parameter; *p > 32; ++p) {\n\t}\n\t*p = '\\0';\n\n\tif (!parameter[0]) {\n\t\treturn;\n\t}\n\n\tif (cl->login_challenge[0]) {\n\t\t// This is response to challenge, treat as password\n\t\tCentral_VerifyChallengeResponse(cl, cl->login_challenge, parameter);\n\n\t\tSV_ClientPrintf2(cl, PRINT_HIGH, \"Challenge received, please wait...\\n\");\n\t}\n\telse if (curtime - cl->login_request_time < LOGIN_MIN_RETRY_TIME) {\n\t\tSV_ClientPrintf2(cl, PRINT_HIGH, \"Please wait and try again\\n\");\n\t}\n\telse {\n\t\t// Treat as username\n\t\tCentral_GenerateChallenge(cl, parameter, true);\n\n\t\tSV_ClientPrintf2(cl, PRINT_HIGH, \"Generating challenge, please wait...\\n\");\n\t}\n}\n#else\nvoid SV_ParseWebLogin(client_t* cl)\n{\n}\n#endif\n\nvoid SV_ParseLogin(client_t* cl)\n{\n\tchar *log1, *pass;\n\n\tif (WebLoginsEnabled()) {\n\t\tSV_ParseWebLogin(cl);\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() > 2)\n\t{\n\t\tlog1 = Cmd_Argv(1);\n\t\tpass = Cmd_Argv(2);\n\t}\n\telse\n\t{\n\t\t// bah usually whole text in 'say' is put into \"\"\n\t\tlog1 = pass = Cmd_Argv(1);\n\t\twhile (*pass && *pass != ' ')\n\t\t\tpass++;\n\n\t\tif (*pass)\n\t\t\t*pass++ = 0;\n\n\t\twhile (*pass == ' ')\n\t\t\tpass++;\n\t}\n\n\t// if login is parsed, we read just a password\n\tif (cl->login[0])\n\t{\n\t\tpass = log1;\n\t\tlog1 = cl->login;\n\t}\n\telse\n\t{\n\t\tstrlcpy(cl->login, log1, CLIENT_LOGIN_LEN);\n\t}\n\n\tif (!*pass)\n\t{\n\t\tstrlcpy(cl->login, log1, CLIENT_LOGIN_LEN);\n\t\tSV_ClientPrintf2(cl, PRINT_HIGH, \"Enter password for %s:\\n\", cl->login);\n\n\t\treturn;\n\t}\n\n\tcl->logged = checklogin(log1, pass, use_log);\n\n\tswitch (cl->logged)\n\t{\n\tcase -2:\n\t\tSV_BlockedLogin(cl);\n\t\tbreak;\n\tcase -1:\n\t\tSV_ClientPrintf2(cl, PRINT_HIGH, \"Login in use!\\ntry again:\\n\");\n\t\tcl->logged = 0;\n\t\tcl->login[0] = 0;\n\t\tbreak;\n\tcase 0:\n\t\tSV_ClientPrintf2(cl, PRINT_HIGH, \"Access denied\\nPassword for %s:\\n\", cl->login);\n\t\tbreak;\n\tdefault:\n\t\tstrlcpy(cl->login_alias, cl->login, sizeof(cl->login_alias));\n\t\tSV_SuccessfulLogin(cl);\n\t\tbreak;\n\t}\n}\n\nstatic void SV_BlockedLogin(client_t* cl)\n{\n\tSV_ClientPrintf2(cl, PRINT_HIGH, \"Login blocked\\n\");\n\tSV_DropClient(cl);\n}\n\nstatic void SV_SuccessfulLogin(client_t* cl)\n{\n\textern cvar_t sv_forcenick;\n\n\tif (!cl->spectator || !GameStarted()) {\n\t\tSV_BroadcastPrintf(PRINT_HIGH, \"%s logged in as %s\\n\", cl->name, cl->login);\n\t}\n\tif (cl->state < cs_spawned) {\n\t\tSV_ClientPrintf2(cl, PRINT_HIGH, \"Welcome %s\\n\", cl->login);\n\t}\n\n\t//VVD: forcenick ->\n\tif ((int)sv_forcenick.value)\n\t{\n\t\tconst char* forced_name = cl->login_alias[0] ? cl->login_alias : cl->login;\n\n\t\tif (forced_name[0]) {\n\t\t\tSV_ForceClientName(cl, forced_name);\n\t\t}\n\t}\n\t//<-\n\n\tif (cl->state < cs_spawned) {\n\t\tMSG_WriteByte(&cl->netchan.message, svc_stufftext);\n\t\tMSG_WriteString(&cl->netchan.message, \"cmd new\\n\");\n\t}\n}\n\nstatic void SV_ForceClientName(client_t* cl, const char* forced_name)\n{\n\tchar oldval[MAX_EXT_INFO_STRING];\n\tint i;\n\n\t// If any other clients are using this name, kick them\n\tfor (i = 0; i < MAX_CLIENTS; ++i) {\n\t\tclient_t* other = &svs.clients[i];\n\n\t\tif (!other->state)\n\t\t\tcontinue;\n\t\tif (other == cl) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!Q_namecmp(other->name, forced_name)) {\n\t\t\tSV_KickClient(other, \" (using authenticated user's name)\");\n\t\t}\n\t}\n\n\t// Set server-side name: allow colors/case changes\n\tif (!Q_namecmp(cl->name, forced_name)) {\n\t\treturn;\n\t}\n\tstrlcpy(oldval, cl->name, MAX_EXT_INFO_STRING);\n\tInfo_Set(&cl->_userinfo_ctx_, \"name\", forced_name);\n\tProcessUserInfoChange(cl, \"name\", oldval);\n\n\t// Change name cvar in client\n\tMSG_WriteByte(&cl->netchan.message, svc_stufftext);\n\tMSG_WriteString(&cl->netchan.message, va(\"name %s\\n\", forced_name));\n}\n\nvoid SV_LoginCheckTimeOut(client_t* cl)\n{\n\tdouble connected = SV_ClientConnectedTime(cl);\n\n\tif (connected && connected > 60)\n\t{\n\t\tSys_Printf(\"Login time out for %s\\n\", cl->name);\n\n\t\tSV_ClientPrintf2(cl, PRINT_HIGH, \"Login timeout expired\\n\");\n\t\tSV_DropClient(cl);\n\t}\n}\n\nvoid SV_LoginWebCheck(client_t* cl)\n{\n\tint status = checklogin(cl->login, cl->login, use_log);\n\n\tif (status < 0) {\n\t\t// Server admin explicitly blocked this account\n\t\tSV_BlockedLogin(cl);\n\t}\n\telse if (status == 0 && LoginMustHaveLocalAccount()) {\n\t\t// Server admin needs to create accounts for people to use\n\t\tSV_BlockedLogin(cl);\n\t}\n\telse {\n\t\t// Continue logging in\n\t\tSV_SuccessfulLogin(cl);\n\t}\n}\n\nvoid SV_LoginWebFailed(client_t* cl)\n{\n\tmemset(cl->login_challenge, 0, sizeof(cl->login_challenge));\n\tcl->login_request_time = 0;\n\n\tSV_ClientPrintf2(cl, PRINT_HIGH, \"Challenge response failed.\\n\");\n\tif (cl->state < cs_spawned) {\n\t\tSV_BlockedLogin(cl);\n\t}\n}\n\nqbool SV_LoginRequired(client_t* cl)\n{\n\tint login = (int)sv_login.value;\n\n\tif (login == 2 || (login == 1 && !cl->spectator)) {\n\t\tif (WebLoginsEnabled()) {\n\t\t\treturn !cl->logged_in_via_web;\n\t\t}\n\t\telse {\n\t\t\treturn !cl->logged;\n\t\t}\n\t}\n\treturn false;\n}\n\nqbool SV_LoginBlockJoinRequest(client_t* cl)\n{\n\tif (WebLoginsEnabled()) {\n\t\tif (!cl->logged_in_via_web && (int)sv_login.value) {\n\t\t\tSV_ClientPrintf(cl, PRINT_HIGH, \"This server requires users to login.  Please authenticate first (/cmd login <username>).\\n\");\n\t\t\treturn true;\n\t\t}\n\t}\n\telse if (cl->logged <= 0 && (int)sv_login.value) {\n\t\tSV_ClientPrintf(cl, PRINT_HIGH, \"This server requires users to login.  Please disconnect and reconnect as a player.\\n\");\n\t\treturn true;\n\t}\n\n\t// Allow\n\treturn false;\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_main.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\n#ifdef SERVERONLY\n\nqbool       host_initialized;\nqbool       host_everything_loaded;\t// true if OnChange() applied to every var, end of Host_Init()\n\ndouble      curtime;\t\t\t// not bounded or scaled, shared by local client and server.\ndouble      realtime;\t\t\t// affected by pause, you should not use it unless it something like physics and such.\n\nstatic int  host_hunklevel;\n\n#else\n\nqbool       server_cfg_done = false;\ndouble      realtime;\t\t\t// affected by pause, you should not use it unless it something like physics and such.\n\n#endif\n\nint\t\tcurrent_skill;\t\t\t// for entity spawnflags checking\n\nclient_t\t*sv_client;\t\t\t// current client\n\nchar\t\tmaster_rcon_password[128] = \"\";\t//bliP: password for remote server commands\n\ncvar_t\tsv_mintic = {\"sv_mintic\",\"0.013\"};\t// bound the size of the\ncvar_t\tsv_maxtic = {\"sv_maxtic\",\"0.1\"};\t// physics time tic\ncvar_t\tsv_maxfps = {\"maxfps\", \"77\", CVAR_SERVERINFO};  // It actually should be called maxpps (max packets per second).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// It was serverinfo variable for quite long time, lets legolize it as cvar.\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Sad part is what we can't call it like sv_maxfps since clients relay on its name 'maxfps' already.\n\nvoid OnChange_sysselecttimeout_var (cvar_t *var, char *value, qbool *cancel);\ncvar_t\tsys_select_timeout = {\"sys_select_timeout\", \"10000\", 0, OnChange_sysselecttimeout_var}; // microseconds.\n\ncvar_t\tsys_restart_on_error = {\"sys_restart_on_error\", \"0\"};\ncvar_t  sv_mod_extensions = { \"sv_mod_extensions\", \"2\", CVAR_ROM };\n\n#ifdef SERVERONLY\ncvar_t  sys_simulation = { \"sys_simulation\", \"0\" };\ncvar_t\tdeveloper = {\"developer\", \"0\"};\t\t// show extra messages\ncvar_t\tversion = {\"version\", \"\", CVAR_ROM};\n#endif\n\ncvar_t\ttimeout = {\"timeout\", \"65\"};\t\t// seconds without any message\ncvar_t\tzombietime = {\"zombietime\", \"2\"};\t// seconds to sink messages\n// after disconnect\n\n#ifdef SERVERONLY\ncvar_t\trcon_password = {\"rcon_password\", \"\"};\t// password for remote server commands\ncvar_t\tpassword = {\"password\", \"\"};\t// password for entering the game\n#else\n// client already have such variables.\nextern cvar_t rcon_password;\nextern cvar_t password;\n#endif\n\ncvar_t\tsv_hashpasswords = {\"sv_hashpasswords\", \"1\"}; // 0 - plain passwords; 1 - hashed passwords\ncvar_t\tsv_crypt_rcon = {\"sv_crypt_rcon\", \"1\"}; // use SHA1 for encryption of rcon_password and using timestamps\n// Time in seconds during which in rcon command this encryption is valid (change only with master_rcon_password).\ncvar_t\tsv_timestamplen = {\"sv_timestamplen\", \"60\"};\ncvar_t\tsv_rconlim = {\"sv_rconlim\", \"10\"};\t// rcon bandwith limit: requests per second\n\n//bliP: telnet log level\nvoid OnChange_telnetloglevel_var (cvar_t *var, char *string, qbool *cancel);\ncvar_t  telnet_log_level = {\"telnet_log_level\", \"0\", 0, OnChange_telnetloglevel_var};\n//<-\n\ncvar_t\tfrag_log_type = {\"frag_log_type\", \"0\"};\n//\tfrag log type:\n//\t\t0 - old style (  qwsv - v0.165)\n//\t\t1 - new style (v0.168 - v0.172)\n\nvoid OnChange_qconsolelogsay_var (cvar_t *var, char *string, qbool *cancel);\ncvar_t\tqconsole_log_say = {\"qconsole_log_say\", \"0\", 0, OnChange_qconsolelogsay_var};\n// logging \"say\" and \"say_team\" messages to the qconsole_PORT.log file\n\ncvar_t\tsys_command_line = {\"sys_command_line\", \"\", CVAR_ROM};\n\ncvar_t\tsv_use_dns = {\"sv_use_dns\", \"0\"}; // 1 - use DNS lookup in status command, 0 - don't use\ncvar_t\tspectator_password = {\"spectator_password\", \"\"};\t// password for entering as a sepctator\ncvar_t\tvip_password = {\"vip_password\", \"\"};\t// password for entering as a VIP sepctator\ncvar_t\tvip_values = {\"vip_values\", \"\"};\n\ncvar_t\tallow_download = {\"allow_download\", \"1\"};\ncvar_t\tallow_download_skins = {\"allow_download_skins\", \"1\"};\ncvar_t\tallow_download_models = {\"allow_download_models\", \"1\"};\ncvar_t\tallow_download_sounds = {\"allow_download_sounds\", \"1\"};\ncvar_t\tallow_download_maps\t= {\"allow_download_maps\", \"1\"};\ncvar_t\tallow_download_pakmaps = {\"allow_download_pakmaps\", \"1\"};\ncvar_t\tallow_download_demos = {\"allow_download_demos\", \"1\"};\ncvar_t\tallow_download_other = {\"allow_download_other\", \"0\"};\n//bliP: init ->\ncvar_t\tdownload_map_url = {\"download_map_url\", \"\"};\n\ncvar_t\tsv_specprint = {\"sv_specprint\", \"0\"};\ncvar_t\tsv_reconnectlimit = {\"sv_reconnectlimit\", \"0\"};\n\nvoid OnChange_admininfo_var (cvar_t *var, char *string, qbool *cancel);\ncvar_t  sv_admininfo = {\"sv_admininfo\", \"\", 0, OnChange_admininfo_var};\n\ncvar_t\tsv_unfake = {\"sv_unfake\", \"1\"}; //bliP: 24/9 kickfake to unfake\ncvar_t\tsv_kicktop = {\"sv_kicktop\", \"1\"};\n\ncvar_t\tsv_allowlastscores = {\"sv_allowlastscores\", \"1\"};\n\ncvar_t\tsv_maxlogsize = {\"sv_maxlogsize\", \"0\"};\n//bliP: 24/9 ->\nvoid OnChange_logdir_var (cvar_t *var, char *string, qbool *cancel);\ncvar_t  sv_logdir = {\"sv_logdir\", \".\", 0, OnChange_logdir_var};\n\ncvar_t  sv_speedcheck = {\"sv_speedcheck\", \"1\"};\n//<-\n//<-\n//cvar_t\tsv_highchars = {\"sv_highchars\", \"1\"};\ncvar_t\tsv_phs = {\"sv_phs\", \"1\"};\ncvar_t\tpausable = {\"pausable\", \"0\"};\ncvar_t\tsv_maxrate = {\"sv_maxrate\", \"0\"};\ncvar_t\tsv_getrealip = {\"sv_getrealip\", \"1\"};\ncvar_t\tsv_serverip = {\"sv_serverip\", \"\"};\ncvar_t\tsv_forcespec_onfull = {\"sv_forcespec_onfull\", \"2\"};\ncvar_t\tsv_maxdownloadrate = {\"sv_maxdownloadrate\", \"0\"};\n\ncvar_t  sv_loadentfiles = {\"sv_loadentfiles\", \"1\"}; //loads .ent files by default if there\ncvar_t  sv_loadentfiles_dir = {\"sv_loadentfiles_dir\", \"\"}; // check for .ent file in maps/sv_loadentfiles_dir first then just maps/\ncvar_t\tsv_default_name = {\"sv_default_name\", \"unnamed\"};\n\nvoid sv_mod_msg_file_OnChange(cvar_t *cvar, char *value, qbool *cancel);\ncvar_t\tsv_mod_msg_file = {\"sv_mod_msg_file\", \"\", CVAR_NONE, sv_mod_msg_file_OnChange};\n\ncvar_t  sv_reliable_sound = {\"sv_reliable_sound\", \"0\"};\n\n//\n// game rules mirrored in svs.info\n//\ncvar_t\tfraglimit = {\"fraglimit\",\"0\",CVAR_SERVERINFO};\ncvar_t\ttimelimit = {\"timelimit\",\"0\",CVAR_SERVERINFO};\ncvar_t\tteamplay = {\"teamplay\",\"0\",CVAR_SERVERINFO};\ncvar_t\tmaxclients = {\"maxclients\",\"24\",CVAR_SERVERINFO};\ncvar_t\tmaxspectators = {\"maxspectators\",\"8\",CVAR_SERVERINFO};\ncvar_t\tmaxvip_spectators = {\"maxvip_spectators\",\"0\"/*,CVAR_SERVERINFO*/};\ncvar_t\tdeathmatch = {\"deathmatch\",\"3\",CVAR_SERVERINFO};\ncvar_t\twatervis = {\"watervis\",\"0\",CVAR_SERVERINFO};\ncvar_t\tserverdemo = {\"serverdemo\",\"\",CVAR_SERVERINFO | CVAR_ROM};\n\ncvar_t\tsamelevel = {\"samelevel\",\"1\"}; // dont delete this variable - it used by mods\ncvar_t\tskill = {\"skill\", \"1\"}; // dont delete this variable - it used by mods\ncvar_t\tcoop = {\"coop\", \"0\"}; // dont delete this variable - it used by mods\n\ncvar_t\tsv_paused = {\"sv_paused\", \"0\", CVAR_ROM};\n\ncvar_t\thostname = {\"hostname\", \"unnamed\", CVAR_SERVERINFO};\n\ncvar_t sv_forcenick = {\"sv_forcenick\", \"0\"}; //0 - don't force; 1 - as login;\ncvar_t sv_registrationinfo = {\"sv_registrationinfo\", \"\"}; // text shown before \"enter login\"\n\n// We need this cvar, because some mods didn't allow us to go at some placeses of, for example, start map.\ncvar_t registered = {\"registered\", \"1\", CVAR_ROM};\n\ncvar_t\tsv_halflifebsp = {\"halflifebsp\", \"0\", CVAR_ROM};\ncvar_t  sv_bspversion = {\"sv_bspversion\", \"1\", CVAR_ROM};\n\n// If set, don't send broadcast messages, entities or player info to ServeMe bot\ncvar_t sv_serveme_fix = { \"sv_serveme_fix\", \"1\", CVAR_ROM };\n\n#ifdef FTE_PEXT_FLOATCOORDS\ncvar_t sv_bigcoords = {\"sv_bigcoords\", \"\", CVAR_SERVERINFO};\n#endif\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n// Only enabled on KTX mod (see sv_init)\ncvar_t sv_pext_mvdsv_serversideweapon = { \"sv_pext_mvdsv_serversideweapon\", \"1\" };\n#endif\n\ncvar_t sv_extlimits = { \"sv_extlimits\", \"2\" };\n\n#if defined(FTE_PEXT_TRANS)\ncvar_t sv_pext_ezquake_verfortrans = {\"pext_ezquake_verfortrans\", \"7814\", CVAR_NONE};\n#endif\n\nqbool sv_error = false;\n\nclient_t *WatcherId = NULL; // QW262\n\n//============================================================================\n\nqbool GameStarted(void)\n{\n\tmvddest_t *d;\n\n\tfor (d = demo.dest; d; d = d->nextdest)\n\t\tif (d->desttype != DEST_STREAM) // oh, its not stream, treat as \"game is started\"\n\t\t\tbreak;\n\n\treturn (d || strncasecmp(Info_ValueForKey(svs.info, \"status\"), \"Standby\", 8));\n}\n/*\n================\nSV_Shutdown\n\nQuake calls this before calling Sys_Quit or Sys_Error\n================\n*/\nvoid SV_Shutdown (char *finalmsg)\n{\n\tint i;\n\n\tif (!sv.state)\n\t\treturn; // already shutdown. FIXME: what about error during SV_SpawnServer() ?\n\n\tSV_FinalMessage(finalmsg);\n\n\tMaster_Shutdown ();\n\n\tfor (i = MIN_LOG; i < MAX_LOG; ++i)\n\t{\n\t\tif (logs[i].sv_logfile)\n\t\t{\n\t\t\tfclose (logs[i].sv_logfile);\n\t\t\tlogs[i].sv_logfile = NULL;\n\t\t}\n\t}\n\tif (sv.mvdrecording)\n\t\tSV_MVDStop_f();\n\n#ifndef SERVER_ONLY\n\tNET_CloseServer ();\n#endif\n\n#if defined(SERVERONLY) && defined(WWW_INTEGRATION)\n\tCentral_Shutdown();\n#endif\n\n\t// Shutdown game.\n\tPR_GameShutDown();\n\tPR_UnLoadProgs();\n\n\tmemset (&sv, 0, sizeof(sv));\n\tsv.state = ss_dead;\n#ifndef SERVERONLY\n\tcom_serveractive = false;\n\t{\n\t\textern  ctxinfo_t _localinfo_;\n\n\t\tInfo_RemoveAll(&_localinfo_);\n\t\tfor (i = 0; i < MAX_CLIENTS; ++i) {\n\t\t\tInfo_RemoveAll(&svs.clients[i]._userinfo_ctx_);\n\t\t\tInfo_RemoveAll(&svs.clients[i]._userinfoshort_ctx_);\n\t\t}\n\t}\n#endif\n\n\tmemset (svs.clients, 0, sizeof(svs.clients));\n\tsvs.lastuserid = 0;\n\tsvs.serverflags = 0;\n}\n\n/*\n================\nSV_Error\n\nSends a datagram to all the clients informing them of the server crash,\nthen stops the server\n================\n*/\nvoid SV_Error (char *error, ...)\n{\n\tstatic char string[1024];\n\tva_list argptr;\n\n\tsv_error = true;\n\n\tva_start (argptr, error);\n\tvsnprintf (string, sizeof (string), error, argptr);\n\tva_end (argptr);\n\n\tSV_Shutdown (va (\"SV_Error: %s\\n\", string));\n\n\tHost_EndGame();\n\tHost_Error(\"SV_Error: %s\", string);\n}\n\nstatic void SV_FreeHeadDelayedPacket(client_t *cl) {\n\tif (cl->packets) {\n\t\tpacket_t *next = cl->packets->next;\n\t\tcl->packets->next = svs.free_packets;\n\t\tsvs.free_packets = cl->packets;\n\t\tcl->packets = next;\n\t}\n}\n\n\nvoid SV_FreeDelayedPackets (client_t *cl) {\n\twhile (cl->packets)\n\t\tSV_FreeHeadDelayedPacket(cl);\n}\n\n/*\n==================\nSV_FinalMessage\n\nUsed by SV_Error and SV_Quit_f to send a final message to all connected\nclients before the server goes down.  The messages are sent immediately,\nnot just stuck on the outgoing message list, because the server is going\nto totally exit after returning from this function.\n==================\n*/\nvoid SV_FinalMessage (const char *message)\n{\n\tclient_t *cl;\n\tint i;\n\n\tSZ_Clear (&net_message);\n\tMSG_WriteByte (&net_message, svc_print);\n\tMSG_WriteByte (&net_message, PRINT_HIGH);\n\tMSG_WriteString (&net_message, message);\n\tMSG_WriteByte (&net_message, svc_disconnect);\n\n\tfor (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)\n\t\tif (cl->state >= cs_spawned\n#ifdef USE_PR2\n\t\t\t&& !cl->isBot\n#endif\n\t\t) {\n\t\t\tNetchan_Transmit(&cl->netchan, net_message.cursize\n\t\t\t\t, net_message.data);\n\t\t}\n}\n\n\n\n/*\n=====================\nSV_DropClient\n\nCalled when the player is totally leaving the server, either willingly\nor unwillingly.  This is NOT called if the entire server is quiting\nor crashing.\n=====================\n*/\nvoid SV_DropClient(client_t* drop)\n{\n\t//bliP: cuff, mute ->\n\tSV_SavePenaltyFilter (drop, ft_mute, drop->lockedtill);\n\tSV_SavePenaltyFilter (drop, ft_cuff, drop->cuff_time);\n\t//<-\n\n\t//bliP: player logging\n\tif (drop->name[0])\n\t\tSV_LogPlayer(drop, \"disconnect\", 1);\n\t//<-\n\n\t// add the disconnect\n#ifdef USE_PR2\n\tif( drop->isBot )\n\t{\n\t\textern void RemoveBot(client_t *cl);\n\t\tRemoveBot(drop);\n\t\treturn;\n\t}\n#endif\n\tMSG_WriteByte (&drop->netchan.message, svc_disconnect);\n\n\tif (drop->state == cs_spawned)\n\t{\n\t\t// call the prog function for removing a client\n\t\t// this will set the body to a dead frame, among other things\n\t\tpr_global_struct->self = EDICT_TO_PROG(drop->edict);\n\t\tPR_GameClientDisconnect(drop->spectator);\n\t}\n\n\tif (drop->spectator)\n\t\tCon_Printf (\"Spectator %s removed\\n\",drop->name);\n\telse\n\t\tCon_Printf (\"Client %s removed\\n\",drop->name);\n\n\tif (drop->download)\n\t{\n\t\tVFS_CLOSE(drop->download);\n\t\tdrop->download = NULL;\n\t}\n\tif (drop->upload)\n\t{\n\t\tfclose (drop->upload);\n\t\tdrop->upload = NULL;\n\t}\n\t*drop->uploadfn = 0;\n\n\tSV_Logout(drop);\n\n\tdrop->state = cs_zombie;\t\t    // become free in a few seconds\n\tSV_SetClientConnectionTime(drop);   // for zombie timeout\n\n// MD -->\n\tif (drop == WatcherId)\n\t\tWatcherId = NULL;\n// <-- MD\n\n\tdrop->old_frags = 0;\n\tdrop->edict->v->frags = 0.0;\n\tdrop->name[0] = 0;\n\n\tInfo_RemoveAll(&drop->_userinfo_ctx_);\n\tInfo_RemoveAll(&drop->_userinfoshort_ctx_);\n\n\t// send notification to all remaining clients\n\tSV_FullClientUpdate(drop, &sv.reliable_datagram);\n}\n\n\n//====================================================================\n\n/*\n===================\nSV_CalcPing\n\n===================\n*/\nint SV_CalcPing (client_t *cl)\n{\n\tregister client_frame_t *frame;\n\tint count, i;\n\tfloat ping;\n\n\n\t//bliP: 999 ping for connecting players\n\tif (cl->state != cs_spawned)\n\t\treturn 999;\n\t//<-\n\n\tping = 0;\n\tcount = 0;\n#ifdef USE_PR2\n\tif (cl->isBot) {\n\t\treturn 10;\n\t}\n#endif\n\tfor (frame = cl->frames, i=0 ; i<UPDATE_BACKUP ; i++, frame++)\n\t{\n\t\tif (frame->ping_time > 0)\n\t\t{\n\t\t\tping += frame->ping_time;\n\t\t\tcount++;\n\t\t}\n\t}\n\tif (!count)\n\t\treturn 9999;\n\tping /= count;\n\n\treturn ping*1000;\n}\n\n/*\n===================\nSV_FullClientUpdate\n\nWrites all update values to a sizebuf\n===================\n*/\nvoid SV_FullClientUpdate (client_t *client, sizebuf_t *buf)\n{\n\tchar info[MAX_EXT_INFO_STRING];\n\tint i;\n\n\ti = client - svs.clients;\n\n\t//Sys_Printf(\"SV_FullClientUpdate:  Updated frags for client %d\\n\", i);\n\n\tMSG_WriteByte (buf, svc_updatefrags);\n\tMSG_WriteByte (buf, i);\n\tMSG_WriteShort (buf, client->old_frags);\n\n\tMSG_WriteByte (buf, svc_updateping);\n\tMSG_WriteByte (buf, i);\n\tMSG_WriteShort (buf, SV_CalcPing (client));\n\n\tMSG_WriteByte (buf, svc_updatepl);\n\tMSG_WriteByte (buf, i);\n\tMSG_WriteByte (buf, client->lossage);\n\n\tMSG_WriteByte (buf, svc_updateentertime);\n\tMSG_WriteByte (buf, i);\n\tMSG_WriteFloat (buf, SV_ClientGameTime(client));\n\n\tInfo_ReverseConvert(&client->_userinfoshort_ctx_, info, sizeof(info));\n\tInfo_RemovePrefixedKeys (info, '_');\t// server passwords, etc\n\n\tMSG_WriteByte (buf, svc_updateuserinfo);\n\tMSG_WriteByte (buf, i);\n\tMSG_WriteLong (buf, client->userid);\n\tMSG_WriteString (buf, info);\n}\n\n/*\n===================\nSV_FullClientUpdateToClient\n\nWrites all update values to a client's reliable stream\n===================\n*/\nvoid SV_FullClientUpdateToClient (client_t *client, client_t *cl)\n{\n\tchar info[MAX_EXT_INFO_STRING];\n\n\tInfo_ReverseConvert(&client->_userinfoshort_ctx_, info, sizeof(info));\n\n\tClientReliableCheckBlock(cl, 24 + strlen(info));\n\tif (cl->num_backbuf)\n\t{\n\t\tSV_FullClientUpdate (client, &cl->backbuf);\n\t\tClientReliable_FinishWrite(cl);\n\t}\n\telse\n\t\tSV_FullClientUpdate (client, &cl->netchan.message);\n}\n\n//Returns a unique userid in [1..MAXUSERID] range\n#define MAXUSERID 99\nint SV_GenerateUserID (void)\n{\n\tclient_t *cl;\n\tint i;\n\n\n\tdo {\n\t\tsvs.lastuserid++;\n\t\tif (svs.lastuserid == 1 + MAXUSERID)\n\t\t\tsvs.lastuserid = 1;\n\t\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t\t\tif (cl->state != cs_free && cl->userid == svs.lastuserid)\n\t\t\t\tbreak;\n\t} while (i != MAX_CLIENTS);\n\n\treturn svs.lastuserid;\n}\n\n/*\n==============================================================================\n\nCONNECTIONLESS COMMANDS\n\n==============================================================================\n*/\n\n/*\nSVC_QTVStreams\n\nResponds with info on connected QTV users\n*/\nstatic void SVC_QTVUsers (void)\n{\n\tSV_BeginRedirect (RD_PACKET);\n\tQTV_Streams_UserList ();\n\tSV_EndRedirect ();\n}\n\n/*\n================\nSVC_Status\n\nResponds with all the info that qplug or qspy can see\nThis message can be up to around 5k with worst case string lengths.\n================\n*/\n#define STATUS_OLDSTYLE                 0\n#define STATUS_SERVERINFO               1\n#define STATUS_PLAYERS                  2\n#define STATUS_SPECTATORS               4\n#define STATUS_SPECTATORS_AS_PLAYERS    8 //for ASE - change only frags: show as \"S\"\n#define STATUS_SHOWTEAMS                16\n#define STATUS_SHOWQTV                  32\n#define STATUS_SHOWFLAGS                64\n\nstatic void SVC_Status (void)\n{\n\tint top, bottom, ping, i, opt = 0;\n\tchar *name, *frags;\n\tclient_t *cl;\n\n\n\tif (Cmd_Argc() > 1)\n\t\topt = Q_atoi(Cmd_Argv(1));\n\n\tSV_BeginRedirect (RD_PACKET);\n\tif (opt == STATUS_OLDSTYLE || (opt & STATUS_SERVERINFO))\n\t\tCon_Printf (\"%s\\n\", svs.info);\n\tif (opt == STATUS_OLDSTYLE || (opt & (STATUS_PLAYERS | STATUS_SPECTATORS)))\n\t\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t\t{\n\t\t\tcl = &svs.clients[i];\n\t\t\tif ( (cl->state >= cs_preconnected/* || cl->state == cs_spawned */) &&\n\t\t\t        ( (!cl->spectator && ((opt & STATUS_PLAYERS) || opt == STATUS_OLDSTYLE)) ||\n\t\t\t          ( cl->spectator && ( opt & STATUS_SPECTATORS)) ) )\n\t\t\t{\n\t\t\t\ttop    = Q_atoi(Info_Get (&cl->_userinfo_ctx_, \"topcolor\"));\n\t\t\t\tbottom = Q_atoi(Info_Get (&cl->_userinfo_ctx_, \"bottomcolor\"));\n\t\t\t\ttop    = (top    < 0) ? 0 : ((top    > 13) ? 13 : top);\n\t\t\t\tbottom = (bottom < 0) ? 0 : ((bottom > 13) ? 13 : bottom);\n\t\t\t\tping   = SV_CalcPing (cl);\n\t\t\t\tname   = cl->name;\n\t\t\t\tif (cl->spectator)\n\t\t\t\t{\n\t\t\t\t\tif (opt & STATUS_SPECTATORS_AS_PLAYERS)\n\t\t\t\t\t\tfrags = \"S\";\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tping  = -ping;\n\t\t\t\t\t\tfrags = \"-9999\";\n\t\t\t\t\t\tname  = va(\"\\\\s\\\\%s\", name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tfrags = va(\"%i\", cl->old_frags);\n\n\t\t\t\tCon_Printf (\"%i %s %i %i \\\"%s\\\" \\\"%s\\\" %i %i\", cl->userid, frags,\n\t\t\t\t            (int)(SV_ClientConnectedTime(cl) / 60.0f), ping, name,\n\t\t\t\t            Info_Get (&cl->_userinfo_ctx_, \"skin\"), top, bottom);\n\n\t\t\t\tif (opt & STATUS_SHOWTEAMS) {\n\t\t\t\t\tCon_Printf(\" \\\"%s\\\"\", cl->team);\n\t\t\t\t}\n\n\t\t\t\tif (opt & STATUS_SHOWFLAGS) {\n\t\t\t\t\tif (cl->login_flag[0]) {\n\t\t\t\t\t\tCon_Printf(\" \\\"%s\\\"\", cl->login_flag);\n\t\t\t\t\t}\n\t\t\t\t\telse if (cl->logged_in_via_web || cl->logged > 0) {\n\t\t\t\t\t\tCon_Printf(\" \\\"none\\\"\");\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tCon_Printf(\" \\\"\\\"\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tCon_Printf(\"\\n\");\n\t\t\t}\n\t\t}\n\n\tif (opt & STATUS_SHOWQTV)\n\t\tQTV_Streams_List ();\n\tSV_EndRedirect ();\n}\n\n/*\n===================\nSVC_LastScores\n\n===================\n*/\nvoid SV_LastScores_f (void);\nstatic void SVC_LastScores (void)\n{\n\tif(!(int)sv_allowlastscores.value)\n\t\treturn;\n\n\tSV_BeginRedirect (RD_PACKET);\n\tSV_LastScores_f ();\n\tSV_EndRedirect ();\n}\n\n/*\n===================\nSVC_LastStats\n===================\n*/\nvoid SV_LastStats_f (void);\nstatic void SVC_LastStats (void)\n{\n\tif(!(int)sv_allowlastscores.value)\n\t\treturn;\n\n\tSV_BeginRedirect (RD_PACKET);\n\tSV_LastStats_f ();\n\tSV_EndRedirect ();\n}\n\n/*\n===================\nSVC_DemoList\nSVC_DemoListRegex\n===================\n*/\nvoid SV_DemoList_f (void);\nstatic void SVC_DemoList (void)\n{\n\tSV_BeginRedirect (RD_PACKET);\n\tSV_DemoList_f ();\n\tSV_EndRedirect ();\n}\nvoid SV_DemoListRegex_f (void);\nstatic void SVC_DemoListRegex (void)\n{\n\tSV_BeginRedirect (RD_PACKET);\n\tSV_DemoListRegex_f ();\n\tSV_EndRedirect ();\n}\n\n/*\n===================\nSV_CheckLog\n\n===================\n*/\n#define\tLOG_HIGHWATER\t(MAX_DATAGRAM - 128)\n#define\tLOG_FLUSH\t\t10*60\nstatic void SV_CheckLog (void)\n{\n\tsizebuf_t *sz;\n\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\tsz = &svs.log[svs.logsequence&1];\n\n\t// bump sequence if allmost full, or ten minutes have passed and\n\t// there is something still sitting there\n\tif (sz->cursize > LOG_HIGHWATER\n\t        || (realtime - svs.logtime > LOG_FLUSH && sz->cursize) )\n\t{\n\t\t// swap buffers and bump sequence\n\t\tsvs.logtime = realtime;\n\t\tsvs.logsequence++;\n\t\tsz = &svs.log[svs.logsequence&1];\n\t\tsz->cursize = 0;\n\t\tCon_DPrintf (\"beginning fraglog sequence %i\\n\", svs.logsequence);\n\t}\n\n}\n\n/*\n================\nSVC_Log\n\nResponds with all the logged frags for ranking programs.\nIf a sequence number is passed as a parameter and it is\nthe same as the current sequence, an A2A_NACK will be returned\ninstead of the data.\n================\n*/\nstatic void SVC_Log (void)\n{\n\tchar data[MAX_DATAGRAM+64];\n\tint seq;\n\n\n\tif (Cmd_Argc() == 2)\n\t\tseq = Q_atoi(Cmd_Argv(1));\n\telse\n\t\tseq = -1;\n\n\tif (seq == svs.logsequence-1 || !logs[FRAG_LOG].sv_logfile)\n\t{\t// they already have this data, or we aren't logging frags\n\t\tdata[0] = A2A_NACK;\n\t\tNET_SendPacket (NS_SERVER, 1, data, net_from);\n\t\treturn;\n\t}\n\n\tCon_DPrintf (\"sending log %i to %s\\n\", svs.logsequence-1, NET_AdrToString(net_from));\n\n\tsnprintf (data, MAX_DATAGRAM + 64, \"stdlog %i\\n\", svs.logsequence-1);\n\tstrlcat (data, (char *)svs.log_buf[((svs.logsequence-1)&1)], MAX_DATAGRAM + 64);\n\n\tNET_SendPacket (NS_SERVER, strlen(data)+1, data, net_from);\n}\n\n/*\n================\nSVC_Ping\n\nJust responds with an acknowledgement\n================\n*/\nstatic void SVC_Ping (void)\n{\n\tchar data = A2A_ACK;\n\n\tNET_SendPacket (NS_SERVER, 1, &data, net_from);\n}\n\n/*\n=================\nSVC_GetChallenge\n\nReturns a challenge number that can be used\nin a subsequent client_connect command.\nWe do this to prevent denial of service attacks that\nflood the server with invalid connection IPs.  With a\nchallenge, they must give a valid IP address.\n=================\n*/\nstatic void SVC_GetChallenge (void)\n{\n\tint oldestTime, oldest, i;\n\tchar buf[256], *over;\n\n\n\toldest = 0;\n\toldestTime = 0x7fffffff;\n\n\t// see if we already have a challenge for this ip\n\tfor (i = 0 ; i < MAX_CHALLENGES ; i++)\n\t{\n\t\tif (NET_CompareBaseAdr (net_from, svs.challenges[i].adr))\n\t\t\tbreak;\n\t\tif (svs.challenges[i].time < oldestTime)\n\t\t{\n\t\t\toldestTime = svs.challenges[i].time;\n\t\t\toldest = i;\n\t\t}\n\t}\n\n\tif (i == MAX_CHALLENGES)\n\t{\n\t\t// overwrite the oldest\n\t\tsvs.challenges[oldest].challenge = (rand() << 16) ^ rand();\n\t\tsvs.challenges[oldest].adr = net_from;\n\t\tsvs.challenges[oldest].time = realtime;\n\t\ti = oldest;\n\t}\n\n\t// send it back\n\tsnprintf(buf, sizeof(buf), \"%c%i\", S2C_CHALLENGE, svs.challenges[i].challenge);\n\tover = buf + strlen(buf) + 1;\n\n#ifdef PROTOCOL_VERSION_FTE\n\t//tell the client what fte extensions we support\n\tif (svs.fteprotocolextensions)\n\t{\n\t\tint lng;\n\n\t\tlng = LittleLong(PROTOCOL_VERSION_FTE);\n\t\tmemcpy(over, &lng, sizeof(int));\n\t\tover += 4;\n\n\t\tlng = LittleLong(svs.fteprotocolextensions);\n\t\tmemcpy(over, &lng, sizeof(int));\n\t\tover += 4;\n\t}\n#endif // PROTOCOL_VERSION_FTE\n\n#ifdef PROTOCOL_VERSION_FTE2\n\t//tell the client what fte extensions2 we support\n\tif (svs.fteprotocolextensions2)\n\t{\n\t\tint lng;\n\n\t\tlng = LittleLong(PROTOCOL_VERSION_FTE2);\n\t\tmemcpy(over, &lng, sizeof(int));\n\t\tover += 4;\n\n\t\tlng = LittleLong(svs.fteprotocolextensions2);\n\t\tmemcpy(over, &lng, sizeof(int));\n\t\tover += 4;\n\t}\n#endif // PROTOCOL_VERSION_FTE2\n\n#ifdef PROTOCOL_VERSION_MVD1\n\t// tell the client what mvdsv extensions we support\n\tif (svs.mvdprotocolextension1) {\n\t\tint lng;\n\n\t\tlng = LittleLong(PROTOCOL_VERSION_MVD1);\n\t\tmemcpy(over, &lng, sizeof(int));\n\t\tover += 4;\n\n\t\tlng = LittleLong(svs.mvdprotocolextension1);\n\t\tmemcpy(over, &lng, sizeof(int));\n\t\tover += 4;\n\t}\n#endif\n\n\tNetchan_OutOfBand(NS_SERVER, net_from, over-buf, (byte*) buf);\n}\n\nstatic qbool ValidateUserInfo (char *userinfo)\n{\n\tif (strstr(userinfo, \"&c\") || strstr(userinfo, \"&r\"))\n\t\treturn false;\n\n\twhile (*userinfo)\n\t{\n\t\tif (*userinfo == '\\\\')\n\t\t\tuserinfo++;\n\n\t\tif (*userinfo++ == '\\\\')\n\t\t\treturn false;\n\t\twhile (*userinfo && *userinfo != '\\\\')\n\t\t\tuserinfo++;\n\t}\n\treturn true;\n}\n\n//==============================================\n\nvoid FixMaxClientsCvars(void)\n{\n\tif ((int)maxclients.value > MAX_CLIENTS)\n\t\tCvar_SetValue (&maxclients, MAX_CLIENTS);\n\n\tif ((int)maxspectators.value > MAX_CLIENTS)\n\t\tCvar_SetValue (&maxspectators, MAX_CLIENTS);\n\n\tif ((int)maxvip_spectators.value > MAX_CLIENTS)\n\t\tCvar_SetValue (&maxvip_spectators, MAX_CLIENTS);\n\n\tif ((int)maxspectators.value + maxclients.value > MAX_CLIENTS)\n\t\tCvar_SetValue (&maxspectators, MAX_CLIENTS - (int)maxclients.value);\n\n\tif ((int)maxspectators.value + maxclients.value + maxvip_spectators.value > MAX_CLIENTS)\n\t\tCvar_SetValue (&maxvip_spectators, MAX_CLIENTS - (int)maxclients.value - (int)maxspectators.value);\n\n}\n\n//==============================================\n\n// see if the challenge is valid\nqbool CheckChallange( int challenge )\n{\n\tint i;\n\n\tif (net_from.type == NA_LOOPBACK)\n\t\treturn true; // local client do not need challenge\n\n\tfor (i = 0; i < MAX_CHALLENGES; i++)\n\t{\n\t\tif (NET_CompareBaseAdr (net_from, svs.challenges[i].adr))\n\t\t{\n\t\t\tif (challenge == svs.challenges[i].challenge)\n\t\t\t\tbreak;\t\t// good\n\n\t\t\tNetchan_OutOfBandPrint (NS_SERVER, net_from, \"%c\\nBad challenge.\\n\", A2C_PRINT);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (i == MAX_CHALLENGES)\n\t{\n\t\tNetchan_OutOfBandPrint (NS_SERVER, net_from, \"%c\\nNo challenge for address.\\n\", A2C_PRINT);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n//==============================================\n\nqbool CheckProtocol( int ver )\n{\n\tif (ver != PROTOCOL_VERSION)\n\t{\n\t\tNetchan_OutOfBandPrint (NS_SERVER, net_from, \"%c\\nServer is version \" QW_VERSION \".\\n\", A2C_PRINT);\n\t\tCon_Printf (\"* rejected connect from version %i\\n\", ver);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n//==============================================\n\nqbool CheckUserinfo( char *userinfobuf, unsigned int bufsize, char *userinfo )\n{\n\tstrlcpy (userinfobuf, userinfo, bufsize);\n\n\t// and now validate userinfo\n\tif ( !ValidateUserInfo( userinfobuf ) )\n\t{\n\t\tNetchan_OutOfBandPrint (NS_SERVER, net_from, \"%c\\nInvalid userinfo, perhaps &c sequences. Restart your qwcl\\n\", A2C_PRINT);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n//==============================================\n\nint SV_VIPbyIP(netadr_t adr);\nint SV_VIPbyPass (char *pass);\n\nqbool CheckPasswords( char *userinfo, int userinfo_size, qbool *spass_ptr, qbool *vip_ptr, int *spectator_ptr )\n{\n\tint spectator;\n\tqbool spass, vip;\n\n\tchar *s = Info_ValueForKey (userinfo, \"spectator\");\n\tchar *pwd;\n\n\tspass = vip = spectator = false;\n\n\tif (s[0] && strcmp(s, \"0\"))\n\t{\n\t\tspass = true;\n\n\t\t// first the pass, then ip\n\t\tif ( !( vip = SV_VIPbyPass( s ) ) )\n\t\t{\n\t\t\tif ( !( vip = SV_VIPbyPass( Info_ValueForKey( userinfo, \"password\") ) ) )\n\t\t\t{\n\t\t\t\tvip = SV_VIPbyIP( net_from );\n\t\t\t}\n\t\t}\n\n\t\tpwd = spectator_password.string;\n\n\t\tif (pwd[0] && strcasecmp(pwd, \"none\") && strcmp(pwd, s))\n\t\t{\n\t\t\tspass = false; // failed\n\t\t}\n\n\t\tif (!vip && !spass)\n\t\t{\n\t\t\tCon_Printf (\"%s:spectator password failed\\n\", NET_AdrToString (net_from));\n\t\t\tNetchan_OutOfBandPrint (NS_SERVER, net_from, \"%c\\nrequires a spectator password\\n\\n\", A2C_PRINT);\n\n\t\t\treturn false;\n\t\t}\n\n\t\tInfo_RemoveKey (userinfo, \"spectator\"); // remove passwd\n\t\tInfo_SetValueForStarKey (userinfo, \"*spectator\", \"1\", userinfo_size);\n\n\t\tspectator = Q_atoi(s);\n\n\t\tif (!spectator)\n\t\t\tspectator = true;\n\t}\n\telse\n\t{\n\t\ts = Info_ValueForKey (userinfo, \"password\");\n\n\t\t// first the pass, then ip\n\t\tif (!(vip = SV_VIPbyPass(s)))\n\t\t{\n\t\t\tvip = SV_VIPbyIP(net_from);\n\t\t}\n\n\t\tpwd = password.string;\n\n\t\tif (!vip && pwd[0] && strcasecmp(pwd, \"none\") && strcmp(pwd, s))\n\t\t{\n\t\t\tCon_Printf (\"%s:password failed\\n\", NET_AdrToString (net_from));\n\t\t\tNetchan_OutOfBandPrint (NS_SERVER, net_from, \"%c\\nserver requires a password\\n\\n\", A2C_PRINT);\n\n\t\t\treturn false;\n\t\t}\n\n\t\tInfo_RemoveKey (userinfo, \"spectator\"); // remove \"spectator 0\" for example\n\n\t\tspectator = false;\n\t}\n\n\tInfo_RemoveKey (userinfo, \"password\"); // remove passwd\n\n\t// copy \n\t*spass_ptr     = spass;\n\t*vip_ptr       = vip;\n\t*spectator_ptr = spectator;\n\n\treturn true;\n}\n\n//==============================================\n\nqbool CheckReConnect( netadr_t adr, int qport )\n{\n\tint i;\n\tclient_t *cl;\n\n\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t{\n\t\tif (cl->state == cs_free)\n\t\t\tcontinue;\n\n\t\tif (NET_CompareBaseAdr (adr, cl->netchan.remote_address) &&\n\t\t\t(cl->netchan.qport == qport || adr.port == cl->netchan.remote_address.port))\n\t\t{\n\t\t\tif (SV_ClientConnectedTime(cl) < sv_reconnectlimit.value)\n\t\t\t{\n\t\t\t\tCon_Printf (\"%s:reconnect rejected: too soon\\n\", NET_AdrToString (adr));\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tswitch ( cl->state )\n\t\t\t{\n\t\t\t\tcase cs_zombie: // zombie already dropped.\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase cs_preconnected:\n\t\t\t\tcase cs_connected:\n\t\t\t\tcase cs_spawned:\n\n\t\t\t\t\tSV_DropClient (cl);\n\t\t\t\t\tSV_ClearReliable (cl);\t// don't send the disconnect\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\treturn false; // unknown state, should not be the case.\n\t\t\t}\n\n\t\t\tcl->state = cs_free;\n\t\t\tCon_Printf (\"%s:reconnect\\n\", NET_AdrToString (adr));\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n//==============================================\n\nvoid CountPlayersSpecsVips(int *clients_ptr, int *spectators_ptr, int *vips_ptr, client_t **newcl_ptr)\n{\n\tclient_t *cl = NULL, *newcl = NULL;\n\tint clients = 0, spectators = 0, vips = 0;\n\tint i;\n\n\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t{\n\t\tif (cl->state == cs_free)\n\t\t{\n\t\t\tif (!newcl)\n\t\t\t\tnewcl = cl; // grab first available slot\n\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (cl->spectator)\n\t\t{\n\t\t\tif (cl->vip)\n\t\t\t\tvips++;\n\t\t\telse\n\t\t\t\tspectators++;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tclients++;\n\t\t}\n\t}\n\n\tif (clients_ptr)\n\t\t*clients_ptr = clients;\n\tif (spectators_ptr)\n\t\t*spectators_ptr = spectators;\n\tif (vips_ptr)\n\t\t*vips_ptr = vips;\n\tif (newcl_ptr)\n\t\t*newcl_ptr = newcl;\n}\n\n//==============================================\n\nqbool SpectatorCanConnect(int vip, int spass, int spectators, int vips)\n{\n\tFixMaxClientsCvars(); // not a bad idea\n\n\tif (vip)\n\t{\n\t\tif (spass && (spectators < (int)maxspectators.value || vips < (int)maxvip_spectators.value))\n\t\t\treturn true;\n\t}\n\telse\n\t{\n\t\tif (spass && spectators < (int)maxspectators.value)\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nqbool PlayerCanConnect(int clients)\n{\n\tFixMaxClientsCvars(); // not a bad idea\n\n\tif (clients < (int)maxclients.value)\n\t\treturn true;\n\n\treturn false;\n}\n\n/*\n==================\nSVC_DirectConnect\n\nA connection request that did not come from the master\n==================\n*/\nextern void MVD_PlayerReset(int player);\n\nextern char *shortinfotbl[];\n\nstatic void SVC_DirectConnect (void)\n{\n\tint spectator;\n\tqbool spass, vip, rip_vip;\n\n\tint clients, spectators, vips;\n\tint qport, i, edictnum;\n\n\tclient_t *newcl;\n\n\tchar userinfo[1024];\n\tchar *s;\n\tnetadr_t adr;\n\tedict_t *ent;\n\n#ifdef PROTOCOL_VERSION_FTE\n\tunsigned int protextsupported = 0;\n#endif // PROTOCOL_VERSION_FTE\n\n#ifdef PROTOCOL_VERSION_FTE2\n\tunsigned int protextsupported2 = 0;\n#endif // PROTOCOL_VERSION_FTE2\n\n#ifdef PROTOCOL_VERSION_MVD1\n\tunsigned int mvdext_supported1 = 0;\n#endif\n\n\t// check version/protocol\n\tif ( !CheckProtocol( Q_atoi( Cmd_Argv( 1 ) ) ) )\n\t\treturn; // wrong protocol number\n\n\t// get qport\n\tqport = Q_atoi( Cmd_Argv( 2 ) );\n\n\t// see if the challenge is valid\n\tif ( !CheckChallange( Q_atoi( Cmd_Argv( 3 ) ) ) )\n\t\treturn; // wrong challange\n\n\t// and now validate userinfo\n\tif ( !CheckUserinfo( userinfo, sizeof( userinfo ), Cmd_Argv( 4 ) ) )\n\t\treturn; // wrong userinfo\n\n//\n// WARNING: WARNING: WARNING: using Cmd_TokenizeString() so do all Cmd_Argv() above.\n//\n\n\twhile( !msg_badread )\n\t{\n\t\tCmd_TokenizeString( MSG_ReadStringLine() );\n\n\t\tswitch( Q_atoi( Cmd_Argv( 0 ) ) )\n\t\t{\n#ifdef PROTOCOL_VERSION_FTE\n\t\tcase PROTOCOL_VERSION_FTE:\n\t\t\tprotextsupported = Q_atoi( Cmd_Argv( 1 ) );\n\t\t\tCon_DPrintf(\"Client supports 0x%x fte extensions\\n\", protextsupported);\n\t\t\tbreak;\n#endif // PROTOCOL_VERSION_FTE\n\n#ifdef PROTOCOL_VERSION_FTE2\n\t\tcase PROTOCOL_VERSION_FTE2:\n\t\t\tprotextsupported2 = Q_atoi( Cmd_Argv( 1 ) );\n\t\t\tCon_DPrintf(\"Client supports 0x%x fte extensions2\\n\", protextsupported2);\n\t\t\tbreak;\n#endif // PROTOCOL_VERSION_FTE2\n\n#ifdef PROTOCOL_VERSION_MVD1\n\t\tcase PROTOCOL_VERSION_MVD1:\n\t\t\tmvdext_supported1 = Q_atoi( Cmd_Argv( 1 ) );\n\t\t\tCon_DPrintf(\"Client supports 0x%x mvdsv extensions\\n\", mvdext_supported1);\n\t\t\tbreak;\n#endif\n\t\t}\n\t}\n\n\tmsg_badread = false;\n\n\tspass = vip = rip_vip = spectator = false;\n\n\t// check for password or spectator_password\n\tif ( !CheckPasswords( userinfo, sizeof(userinfo), &spass, &vip, &spectator) )\n\t\treturn; // pass was wrong\n\n\tadr = net_from;\n\n\t// if there is already a slot for this ip, reuse (changed from drop) it\n\tif ( !CheckReConnect( adr, qport ) )\n\t\treturn; // can't do that for some reason\n\n\t// count up the clients and spectators\n\tCountPlayersSpecsVips(&clients, &spectators, &vips, &newcl);\n\n\tFixMaxClientsCvars();\n\n\t// if at server limits, refuse connection\n\n\tif ((spectator && !SpectatorCanConnect(vip, spass, spectators, vips)) || \n\t    (!spectator && !PlayerCanConnect(clients)) || \n\t    !newcl)\n\t{\n\t\tSys_Printf (\"%s:full connect\\n\", NET_AdrToString (adr));\n\n\t\t// no way to connect does't matter VIP or whatever, just no free slots\n\t\tif (!newcl)\n\t\t{\n\t\t\tNetchan_OutOfBandPrint (NS_SERVER, adr, \"%c\\nserver is full\\n\\n\", A2C_PRINT);\n\t\t\treturn;\n\t\t}\n\n\t\t// !!! SPECTATOR 2 FEATURE !!!\n\t\tif (spectator == 2 && !vip &&  vips < (int)maxvip_spectators.value)\n\t\t{\n\t\t\tvip = rip_vip = 1; // yet can be connected if realip is on vip list\n\t\t}\n\t\telse if (    !spectator && spectators < (int)maxspectators.value\n\t\t\t\t  && (\n\t\t\t\t  \t      ( (int)sv_forcespec_onfull.value == 2\n\t\t\t\t\t\t\t&&   (Q_atoi(Info_ValueForKey(userinfo, \"svf\")) & SVF_SPEC_ONFULL)\n\t\t\t\t  \t      ) \n\t\t\t\t   \t   \t\t||\n\t\t\t\t\t\t  ( (int)sv_forcespec_onfull.value == 1\n\t\t\t\t\t\t\t&&   !(Q_atoi(Info_ValueForKey(userinfo, \"svf\")) & SVF_NO_SPEC_ONFULL)\n\t\t\t\t\t\t  )\n\t\t\t\t   \t )\n\t\t\t\t)\n\t\t{\n\t\t\tNetchan_OutOfBandPrint (NS_SERVER, adr, \"%c\\nserver is full: connecting as spectator\\n\", A2C_PRINT);\n\t\t\tInfo_SetValueForStarKey (userinfo, \"*spectator\", \"1\", sizeof(userinfo));\n\t\t\tspectator = true;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tNetchan_OutOfBandPrint (NS_SERVER, adr, \"%c\\nserver is full\\n\\n\", A2C_PRINT);\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// build a new connection\n\t// accept the new client\n\t// this is the only place a client_t is ever initialized\n\tmemset (newcl, 0, sizeof(*newcl));\n\n\tnewcl->userid = SV_GenerateUserID();\n\n#ifdef PROTOCOL_VERSION_FTE\n\tnewcl->fteprotocolextensions = protextsupported;\n#endif // PROTOCOL_VERSION_FTE\n\n#ifdef PROTOCOL_VERSION_FTE2\n\tnewcl->fteprotocolextensions2 = protextsupported2;\n#endif // PROTOCOL_VERSION_FTE2\n\n#ifdef PROTOCOL_VERSION_MVD1\n\tnewcl->mvdprotocolextensions1 = mvdext_supported1;\n#endif\n\n\tnewcl->_userinfo_ctx_.max      = MAX_CLIENT_INFOS;\n\tnewcl->_userinfoshort_ctx_.max = MAX_CLIENT_INFOS;\n\tInfo_Convert(&newcl->_userinfo_ctx_, userinfo);\n\n\t// request protocol extensions.\n\tif (*Info_Get(&newcl->_userinfo_ctx_, \"Qizmo\")\n\t\t|| *Info_Get(&newcl->_userinfo_ctx_, \"*qtv\")\n\t)\n\t{\n\t\tnewcl->process_pext = false; // this whould not work over such proxies.\n\t}\n\telse\n\t{\n\t\tnewcl->process_pext = true;\n\t}\n\n\tNetchan_OutOfBandPrint (NS_SERVER, adr, \"%c\", S2C_CONNECTION);\n\n\tNetchan_Setup (NS_SERVER, &newcl->netchan, adr, qport, Q_atoi(Info_Get(&newcl->_userinfo_ctx_, \"mtu\")));\n\n\tnewcl->state = cs_preconnected;\n\n\tnewcl->datagram.allowoverflow = true;\n\tnewcl->datagram.data = newcl->datagram_buf;\n\tnewcl->datagram.maxsize = sizeof(newcl->datagram_buf);\n\n\t// spectator mode can ONLY be set at join time\n\tnewcl->spectator = spectator;\n\tnewcl->vip = vip;\n\tnewcl->rip_vip = rip_vip;\n\n\t// extract extensions mask\n\tnewcl->extensions = Q_atoi(Info_Get(&newcl->_userinfo_ctx_, \"*z_ext\"));\n\tInfo_Remove (&newcl->_userinfo_ctx_, \"*z_ext\");\n\n\tedictnum = (newcl-svs.clients)+1;\n\tent = EDICT_NUM(edictnum);\n\tent->e.free = false;\n\tnewcl->edict = ent;\n\t// restore client name.\n\tPR_SetEntityString(ent, ent->v->netname, newcl->name);\n\n\ts = ( vip ? va(\"%d\", vip) : \"\" );\n\n\tInfo_SetStar (&newcl->_userinfo_ctx_, \"*VIP\", s);\n\n\t// copy the most important userinfo into userinfoshort\n\t// {\n\n\t// parse some info from the info strings\n\tSV_ExtractFromUserinfo (newcl, true);\n\n\tfor (i = 0; shortinfotbl[i] != NULL; i++)\n\t{\n\t\ts = Info_Get(&newcl->_userinfo_ctx_, shortinfotbl[i]);\n\t\tInfo_SetStar (&newcl->_userinfoshort_ctx_, shortinfotbl[i], s);\n\t}\n\n\t// move star keys to infoshort\n\tInfo_CopyStar( &newcl->_userinfo_ctx_, &newcl->_userinfoshort_ctx_ );\n\n\t// }\n\n\t// JACK: Init the floodprot stuff.\n\tmemset(newcl->whensaid, 0, sizeof(newcl->whensaid));\n\tnewcl->whensaidhead = 0;\n\tnewcl->lockedtill = 0;\n\tnewcl->disable_updates_stop = -1.0; // Vladis\n\n\tnewcl->realip_num = rand();\n\n\t//bliP: init\n\tnewcl->spec_print = (int)sv_specprint.value;\n\tnewcl->logincount = 0;\n\t//<-\n\n#ifdef FTE_PEXT2_VOICECHAT\n\tSV_VoiceInitClient(newcl);\n#endif\n\n\t// call the progs to get default spawn parms for the new client\n\tPR_GameSetNewParms();\n\n\tfor (i=0 ; i<NUM_SPAWN_PARMS ; i++)\n\t\tnewcl->spawn_parms[i] = (&PR_GLOBAL(parm1))[i];\n\n\t// mvd/qtv related stuff\n\t// Well, here is a chance what player connect after demo recording started,\n\t// so demo.info[edictnum - 1].model == player_model so SV_MVDWritePackets() will not wrote player model index,\n\t// so client during playback this demo will got invisible model, because model index will be 0.\n\t// Fixing that.\n\t// Btw, struct demo contain different client specific structs, may be they need clearing too, not sure.\n\t// Also, we have Cmd_Join_f()/Cmd_Observe_f() which have close behaviour to SVC_DirectConnect(),\n\t// so I put same demo fix in mentioned functions too.\n\tMVD_PlayerReset(NUM_FOR_EDICT(newcl->edict) - 1);\n\n\tnewcl->sendinfo = true;\n}\n\nstatic int char2int (int c)\n{\n\tif (c <= '9' && c >= '0')\n\t\treturn c - '0';\n\telse if (c <= 'f' && c >= 'a')\n\t\treturn c - 'a' + 10;\n\telse if (c <= 'F' && c >= 'A')\n\t\treturn c - 'A' + 10;\n\treturn 0;\n}\n/*\n * rcon_bandlim() - check for rcon requests bandwidth limit\n *\n *      From kernel of the FreeBSD 4.10 release:\n *      sys/netinet/ip_icmp.c(846): int badport_bandlim(int which);\n *\n *\tReturn false if it is ok to check rcon_password, true if we have\n *\thit our bandwidth limit and it is not ok.\n *\n *\tIf sv_rconlim.value is <= 0, the feature is disabled and false is returned.\n *\n *\tNote that the printing of the error message is delayed so we can\n *\tproperly print the rcon limit error rate that the system was trying to do\n *\t(i.e. 22000/100 rcon pps, etc...).  This can cause long delays in printing\n *\tthe 'final' error, but it doesn't make sense to solve the printing\n *\tdelay with more complex code.\n */\nstatic qbool rcon_bandlim (void)\n{\n\tstatic double lticks = 0;\n\tstatic int lpackets = 0;\n\n\t/*\n\t * Return ok status if feature disabled or argument out of\n\t * ranage.\n\t */\n\n\tif ((int)sv_rconlim.value <= 0)\n\t\treturn false;\n\n\t/*\n\t * reset stats when cumulative dt exceeds one second.\n\t */\n\n\tif (realtime - lticks > 1.0)\n\t{\n\t\tif (lpackets > (int)sv_rconlim.value)\n\t\t\tSys_Printf(\"WARNING: Limiting rcon response from %d to %d rcon pequests per second from %s\\n\",\n\t\t\t           lpackets, (int)sv_rconlim.value, NET_AdrToString(net_from));\n\t\tlticks = realtime;\n\t\tlpackets = 0;\n\t}\n\n\t/*\n\t * bump packet count\n\t */\n\n\tif (++lpackets > (int)sv_rconlim.value)\n\t\treturn true;\n\n\treturn false;\n}\n\n//bliP: master rcon/logging ->\nint Rcon_Validate (char *client_string, char *password1)\n{\n\tunsigned int i;\n\n\tif (rcon_bandlim()) {\n\t\treturn 0;\n\t}\n\n\tif (!strlen(password1)) {\n\t\treturn 0;\n\t}\n\n\tif ((int)sv_crypt_rcon.value) {\n\t\tconst char* digest = Cmd_Argv(1);\n\t\tconst char* time_start = Cmd_Argv(1) + DIGEST_SIZE * 2;\n\n\t\tif (strlen(digest) < DIGEST_SIZE * 2 + sizeof(time_t) * 2) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tif ((int)sv_timestamplen.value) {\n\t\t\ttime_t server_time, client_time = 0;\n\t\t\tdouble difftime_server_client;\n\n\t\t\ttime(&server_time);\n\t\t\tfor (i = 0; i < sizeof(client_time) * 2; i += 2) {\n\t\t\t\tclient_time += (char2int((unsigned char)time_start[i]) << (4 + i * 4)) +\n\t\t\t\t               (char2int((unsigned char)time_start[i + 1]) << (i * 4));\n\t\t\t}\n\t\t\tdifftime_server_client = difftime(server_time, client_time);\n\n\t\t\tif (difftime_server_client > (double)sv_timestamplen.value || difftime_server_client < -(double)sv_timestamplen.value) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t\tSHA1_Init();\n\t\tSHA1_Update((unsigned char*)Cmd_Argv(0));\n\t\tSHA1_Update((unsigned char*)\" \");\n\t\tSHA1_Update((unsigned char*)password1);\n\t\tSHA1_Update((unsigned char*)time_start);\n\t\tSHA1_Update((unsigned char*)\" \");\n\t\tfor (i = 2; (int) i < Cmd_Argc(); i++) {\n\t\t\tSHA1_Update((unsigned char*)Cmd_Argv(i));\n\t\t\tSHA1_Update((unsigned char*)\" \");\n\t\t}\n\t\tif (strncmp(digest, SHA1_Final(), DIGEST_SIZE * 2)) {\n\t\t\treturn 0;\n\t\t}\n\t}\n\telse if (strcmp(Cmd_Argv(1), password1)) {\n\t\treturn 0;\n\t}\n\treturn 1;\n}\n\nint Master_Rcon_Validate (void)\n{\n\tint i, client_string_len = Cmd_Argc() + 1;\n\tchar *client_string;\n\n\tfor (i = 0; i < Cmd_Argc(); ++i) {\n\t\tclient_string_len += strlen(Cmd_Argv(i));\n\t}\n\tclient_string = (char *) Q_malloc (client_string_len);\n\n\t*client_string = 0;\n\tfor (i = 0; i < Cmd_Argc(); ++i) {\n\t\tstrlcat(client_string, Cmd_Argv(i), client_string_len);\n\t\tstrlcat(client_string, \" \", client_string_len);\n\t}\n\ti = Rcon_Validate (client_string, master_rcon_password);\n\tQ_free(client_string);\n\treturn i;\n}\n\n// QW262 -->\nvoid SV_Admin_f (void)\n{\n\tclient_t *cl;\n\tint i = 0;\n\n\tif (Cmd_Argc () == 2 && !strcmp (Cmd_Argv (1), \"off\") && WatcherId &&\n\t\t\tNET_CompareAdr (WatcherId->netchan.remote_address, net_from))\n\t{\n\t\tCon_Printf (\"Rcon Watch stopped\\n\");\n\t\tWatcherId = NULL;\n\t\treturn;\n\t}\n\n\tif (WatcherId)\n\t\tCon_Printf (\"Rcon Watch is already being made by %s\\n\", WatcherId->name);\n\telse\n\t{\n\t\tfor (cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t\t{\n\t\t\tif (cl->state != cs_spawned)\n\t\t\t\tcontinue;\n\n\t\t\tif (NET_CompareAdr (cl->netchan.remote_address, net_from))\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (i == MAX_CLIENTS)\n\t\t{\n\t\t\tCon_Printf (\"You are not connected to server!\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tWatcherId = cl;\n\t\tCon_Printf (\"Rcon Watch started for %s\\n\", cl->name);\n\t}\n}\n// <-- QW262\n\n/*\n===============\nSVC_RemoteCommand\n\nA client issued an rcon command.\nShift down the remaining args\nRedirect all printfs\n===============\n*/\nstatic void SVC_RemoteCommand (char *remote_command)\n{\n\tint\t\t\ti;\n\tchar\t\tstr[1024];\n\tchar\t\tplain[32];\n\tchar\t\t*p;\n\tunsigned char *hide;\n\tclient_t\t*cl;\n\tqbool\t\tadmin_cmd = false;\n\tqbool\t\tdo_cmd = false;\n\tqbool\t\tbad_cmd = false;\n\tqbool\t\tbanned = false;\n\n\n\tif (Rcon_Validate (remote_command, master_rcon_password))\n\t{\n\t\tif (SV_FilterPacket()) //banned players can't use rcon, but we log it\n\t\t\tbanned = true;\n\t\telse\n\t\t\tdo_cmd = true;\n\t}\n\telse if (Rcon_Validate (remote_command, rcon_password.string))\n\t{\n\t\tadmin_cmd = true;\n\t\tif (SV_FilterPacket()) //banned players can't use rcon, but we log it\n\t\t{\n\t\t\tbad_cmd = true;\n\t\t\tbanned = true;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t//\n\t\t\t// the following line prevents exploits like:\n\t\t\t//   coop rm\n\t\t\t//   $coop . *\n\t\t\t// which expands to:\n\t\t\t//   rm . *\n\n\t\t\tCmd_ExpandString (remote_command, str); // check *expanded* command\n\n\t\t\t//\n\t\t\t// since the execution parser is not case sensitive, we\n\t\t\t// must check not only for chmod, but also CHMOD, ChmoD, etc.\n\t\t\t// so we lowercase the whole temporary line before checking\n\n\t\t\t// VVD: strcmp => strcasecmp and we don't need to do this (yes?)\n\t\t\t//for(i = 0; str[i]; i++)\n\t\t\t//\tstr[i] = (char)tolower(str[i]);\n\n\t\t\tCmd_TokenizeString (str);\t\t// must check *all* tokens, because\n\t\t\t\t\t\t\t\t\t\t\t// a command/var may not be the first\n\t\t\t\t\t\t\t\t\t\t\t// token -- example: \"\" ls .\n\n\t\t\t//\n\t\t\t// normal rcon can't use these commands\n\t\t\t//\n\t\t\t// NOTE: this would still be vulnerable to semicolons if\n\t\t\t// they were still allowed, so keep that in mind before\n\t\t\t// re-enabling them\n\n\t\t\tfor (i = 2; i < Cmd_Argc(); i++)\n\t\t\t{\n\t\t\t\tconst char *tstr = Cmd_Argv(i);\n\n\t\t\t\tif(!tstr[0]) // skip leading empty tokens\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif (!strcasecmp(tstr, \"rm\") ||\n\t\t\t\t\t!strcasecmp(tstr, \"rmdir\") ||\n\t\t\t\t\t!strcasecmp(tstr, \"ls\") ||\n\t\t\t\t\t!strcasecmp(tstr, \"chmod\") ||\n\t\t\t\t\t!strcasecmp(tstr, \"sv_admininfo\") ||\n\t\t\t\t\t!strcasecmp(tstr, \"if\") ||\n\t\t\t\t\t!strcasecmp(tstr, \"localcommand\") ||\n\t\t\t\t\t!strcasecmp(tstr, \"sv_crypt_rcon\") ||\n\t\t\t\t\t!strcasecmp(tstr, \"sv_timestamplen\") ||\n\t\t\t\t\t!strncasecmp(tstr, \"log\", 3) ||\n\t\t\t\t\t!strcasecmp(tstr, \"sys_command_line\")\n\t\t\t\t\t)\n\t\t\t\t{\n\t\t\t\t\tbad_cmd = true;\n\t\t\t\t}\n\t\t\t\tbreak; // stop after first non-empty token\n\t\t\t}\n\n\t\t\tCmd_TokenizeString (remote_command); // restore original tokens\n\t\t}\n\t\tdo_cmd = !bad_cmd;\n\t}\n\n\t//find player name if rcon came from someone on server\n\tplain[0] = '\\0';\n\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t{\n\t\tif (cl->state == cs_free)\n\t\t\tcontinue;\n#ifdef USE_PR2\n\t\tif (cl->isBot)\n\t\t\tcontinue;\n#endif\n\t\tif (!NET_CompareBaseAdr(net_from, cl->netchan.remote_address))\n\t\t\tcontinue;\n\t\tif (cl->netchan.remote_address.port != net_from.port)\n\t\t\tcontinue;\n\n\t\tstrlcpy(plain, cl->name, sizeof(plain));\n\t\tQ_normalizetext(plain);\n\n\t\t// we found what we need\n\t\tbreak;\n\t}\n\n\tif (do_cmd)\n\t{\n\t\tif (!(int)sv_crypt_rcon.value)\n\t\t{\n\t\t\thide = net_message.data + 9;\n\t\t\tp = admin_cmd ? rcon_password.string : master_rcon_password;\n\t\t\twhile (*p)\n\t\t\t{\n\t\t\t\tp++;\n\t\t\t\t*hide++ = '*';\n\t\t\t}\n\t\t}\n\n\t\tif (plain[0])\n\t\t\tSV_Write_Log(RCON_LOG, 1, va(\"Rcon from %s (%s): %s\\n\", NET_AdrToString(net_from), plain, net_message.data + 4));\n\t\telse\n\t\t\tSV_Write_Log(RCON_LOG, 1, va(\"Rcon from %s: %s\\n\", NET_AdrToString(net_from), net_message.data + 4));\n\n\t\tCon_Printf(\"Rcon from %s:\\n%s\\n\", NET_AdrToString(net_from), net_message.data + 4);\n\n\t\tSV_BeginRedirect(RD_PACKET);\n\n\t\tstr[0] = '\\0';\n\t\tfor (i = 2; i < Cmd_Argc(); i++)\n\t\t{\n\t\t\tstrlcat(str, Cmd_Argv(i), sizeof(str));\n\t\t\tstrlcat(str, \" \", sizeof(str));\n\t\t}\n\n\t\tCmd_ExecuteString(str);\n\t}\n\telse\n\t{\n\t\tif (admin_cmd && !(int)sv_crypt_rcon.value)\n\t\t{\n\t\t\thide = net_message.data + 9;\n\t\t\tp = admin_cmd ? rcon_password.string : master_rcon_password;\n\t\t\twhile (*p)\n\t\t\t{\n\t\t\t\tp++;\n\t\t\t\t*hide++ = '*';\n\t\t\t}\n\t\t}\n\n\t\tCon_Printf (\"Bad rcon from %s: %s\\n\", NET_AdrToString(net_from), net_message.data + 4);\n\n\t\tif (!banned)\n\t\t{\n\t\t\tif (plain[0])\n\t\t\t\tSV_Write_Log(RCON_LOG, 1, va(\"Bad rcon from %s (%s):\\n%s\\n\", NET_AdrToString(net_from), plain, net_message.data + 4));\n\t\t\telse\n\t\t\t\tSV_Write_Log(RCON_LOG, 1, va(\"Bad rcon from %s:\\n%s\\n\",\tNET_AdrToString (net_from), net_message.data + 4));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tSV_Write_Log(RCON_LOG, 1, va(\"Rcon from banned IP: %s: %s\\n\", NET_AdrToString(net_from), net_message.data + 4));\n\t\t\tSV_SendBan();\n\t\t\treturn;\n\t\t}\n\n\t\tSV_BeginRedirect (RD_PACKET);\n\t\tif (admin_cmd)\n\t\t\tCon_Printf (\"Command not valid.\\n\");\n\t\telse\n\t\t\tCon_Printf (\"Bad rcon_password.\\n\");\n\t}\n\tSV_EndRedirect ();\n}\n//<-\n\nstatic void SVC_IP(void)\n{\n\tint num;\n\tclient_t *client;\n\n\tif (Cmd_Argc() < 3)\n\t\treturn;\n\n\tnum = Q_atoi(Cmd_Argv(1));\n\n\tif (num < 0 || num >= MAX_CLIENTS)\n\t\treturn;\n\n\tclient = &svs.clients[num];\n\tif (client->state != cs_preconnected)\n\t\treturn;\n\n\t// prevent cheating\n\tif (client->realip_num != Q_atoi(Cmd_Argv(2)))\n\t\treturn;\n\n\t// don't override previously set ip\n\tif (client->realip.ip[0])\n\t\treturn;\n\n\tclient->realip = net_from;\n\n\t// if banned drop\n\tif (SV_FilterPacket()/* && !client->vip*/)\n\t\tSV_DropClient(client);\n\n}\n\n\n/*\n=================\nSV_ConnectionlessPacket\n\nA connectionless packet has four leading 0xff\ncharacters to distinguish it from a game channel.\nClients that are in the game can still send\nconnectionless packets.\n=================\n*/\n\nstatic void SV_ConnectionlessPacket (void)\n{\n\tchar\t*s;\n\tchar\t*c;\n\n\tMSG_BeginReading ();\n\tMSG_ReadLong ();\t\t// skip the -1 marker\n\n\ts = MSG_ReadStringLine ();\n\n\tCmd_TokenizeString (s);\n\n\tc = Cmd_Argv(0);\n\n\tif (!strcmp(c, \"ping\") || ( c[0] == A2A_PING && (c[1] == 0 || c[1] == '\\n')) )\n\t\tSVC_Ping ();\n\telse if (c[0] == A2A_ACK && (c[1] == 0 || c[1] == '\\n') )\n\t\tCon_Printf (\"A2A_ACK from %s\\n\", NET_AdrToString (net_from));\n\telse if (!strcmp(c,\"status\"))\n\t\tSVC_Status ();\n\telse if (!strcmp(c,\"log\"))\n\t\tSVC_Log ();\n\telse if (!strcmp(c, \"rcon\"))\n\t\tSVC_RemoteCommand (s);\n\telse if (!strcmp(c, \"ip\"))\n\t\tSVC_IP();\n\telse if (!strcmp(c,\"connect\"))\n\t\tSVC_DirectConnect ();\n\telse if (!strcmp(c,\"getchallenge\"))\n\t\tSVC_GetChallenge ();\n\telse if (!strcmp(c,\"lastscores\"))\n\t\tSVC_LastScores ();\n\telse if (!strcmp(c,\"laststats\"))\n\t\tSVC_LastStats ();\n\telse if (!strcmp(c,\"dlist\"))\n\t\tSVC_DemoList ();\n\telse if (!strcmp(c,\"dlistr\"))\n\t\tSVC_DemoListRegex ();\n\telse if (!strcmp(c,\"dlistregex\"))\n\t\tSVC_DemoListRegex ();\n\telse if (!strcmp(c,\"demolist\"))\n\t\tSVC_DemoList ();\n\telse if (!strcmp(c,\"demolistr\"))\n\t\tSVC_DemoListRegex ();\n\telse if (!strcmp(c,\"demolistregex\"))\n\t\tSVC_DemoListRegex ();\n\telse if (!strcmp(c,\"qtvusers\"))\n\t\tSVC_QTVUsers ();\n\telse\n\t\tCon_Printf (\"bad connectionless packet from %s:\\n%s\\n\"\n\t\t            , NET_AdrToString (net_from), s);\n}\n\n/*\n==============================================================================\n\nPACKET FILTERING\n\n\nYou can add or remove addresses from the filter list with:\n\naddip <ip>\nremoveip <ip>\n\nThe ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire class C network with \"addip 192.246.40\".\n\nRemoveip will only remove an address specified exactly the same way.  You cannot addip a subnet, then removeip a single host.\n\nlistip\nPrints the current list of filters.\n\nwriteip\nDumps \"addip <ip>\" commands to listip.cfg so it can be execed at a later date.  The filter lists are not saved and restored by default, because I beleive it would cause too much confusion.\n\nfilterban <0 or 1>\n\nIf 1 (the default), then ip addresses matching the current list will be prohibited from entering the game.  This is the default setting.\n\nIf 0, then only addresses matching the list will be allowed.  This lets you easily set up a private game, or a game that only allows players from your local network.\n\n\n==============================================================================\n*/\n\n\n/*typedef struct\n{\n\tunsigned\tmask;\n\tunsigned\tcompare;\n\tint\t\t\tlevel;\n} ipfilter_t;\n*/\n\n#define\tMAX_IPFILTERS\t1024\n\nipfilter_t\tipfilters[MAX_IPFILTERS];\nint\t\tnumipfilters;\n\nipfilter_t\tipvip[MAX_IPFILTERS];\nint\t\tnumipvips;\n\n//bliP: cuff, mute ->\npenfilter_t\tpenfilters[MAX_PENFILTERS];\nint\t\tnumpenfilters;\n//<-\n\ncvar_t\tfilterban = {\"filterban\", \"1\"};\n\n/*\n=================\nStringToFilter\n=================\n*/\nqbool StringToFilter (char *s, ipfilter_t *f)\n{\n\tchar\tnum[128];\n\tint\t\ti, j;\n\tbyte\tb[4];\n\tbyte\tm[4];\n\n\tfor (i=0 ; i<4 ; i++)\n\t{\n\t\tb[i] = 0;\n\t\tm[i] = 0;\n\t}\n\n\tfor (i=0 ; i<4 ; i++)\n\t{\n\t\tif (*s < '0' || *s > '9')\n\t\t{\n\t\t\t//Con_Printf (\"Bad filter address: %s\\n\", s);\n\t\t\treturn false;\n\t\t}\n\n\t\tj = 0;\n\t\twhile (*s >= '0' && *s <= '9')\n\t\t{\n\t\t\tnum[j++] = *s++;\n\t\t}\n\t\tnum[j] = 0;\n\t\tb[i] = Q_atoi(num);\n\t\tif (b[i] != 0)\n\t\t\tm[i] = 255;\n\n\t\tif (!*s)\n\t\t\tbreak;\n\t\ts++;\n\t}\n\n\tf->mask = *(unsigned *)m;\n\tf->compare = *(unsigned *)b;\n\n\treturn true;\n}\n\n/*\n=================\nSV_AddIPVIP_f\n=================\n*/\nstatic void SV_AddIPVIP_f (void)\n{\n\tint\t\ti, l;\n\tipfilter_t f;\n\n\tif (!StringToFilter (Cmd_Argv(1), &f))\n\t{\n\t\tCon_Printf (\"Bad filter address: %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tl = Q_atoi(Cmd_Argv(2));\n\n\tif (l < 1) l = 1;\n\n\tfor (i=0 ; i<numipvips ; i++)\n\t\tif (ipvip[i].compare == 0xffffffff || (ipvip[i].mask == f.mask\n\t\t                                       && ipvip[i].compare == f.compare))\n\t\t\tbreak;\t\t// free spot\n\tif (i == numipvips)\n\t{\n\t\tif (numipvips == MAX_IPFILTERS)\n\t\t{\n\t\t\tCon_Printf (\"VIP spectator IP list is full\\n\");\n\t\t\treturn;\n\t\t}\n\t\tnumipvips++;\n\t}\n\n\tipvip[i] = f;\n\tipvip[i].level = l;\n}\n\n/*\n=================\nSV_RemoveIPVIP_f\n=================\n*/\nstatic void SV_RemoveIPVIP_f (void)\n{\n\tipfilter_t\tf;\n\tint\t\ti, j;\n\n\tif (!StringToFilter (Cmd_Argv(1), &f))\n\t{\n\t\tCon_Printf (\"Bad filter address: %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\tfor (i=0 ; i<numipvips ; i++)\n\t\tif (ipvip[i].mask == f.mask\n\t\t        && ipvip[i].compare == f.compare)\n\t\t{\n\t\t\tfor (j=i+1 ; j<numipvips ; j++)\n\t\t\t\tipvip[j-1] = ipvip[j];\n\t\t\tnumipvips--;\n\t\t\tCon_Printf (\"Removed.\\n\");\n\t\t\treturn;\n\t\t}\n\tCon_Printf (\"Didn't find %s.\\n\", Cmd_Argv(1));\n}\n\n/*\n=================\nSV_ListIP_f\n=================\n*/\nstatic void SV_ListIPVIP_f (void)\n{\n\tint\t\ti;\n\tbyte\tb[4];\n\n\tCon_Printf (\"VIP list:\\n\");\n\tfor (i=0 ; i<numipvips ; i++)\n\t{\n\t\t*(unsigned *)b = ipvip[i].compare;\n\t\tCon_Printf (\"%3i.%3i.%3i.%3i   level %d\\n\", b[0], b[1], b[2], b[3], ipvip[i].level);\n\t}\n}\n\n/*\n=================\nSV_WriteIPVIP_f\n=================\n*/\nstatic void SV_WriteIPVIP_f (void)\n{\n\tFILE\t*f;\n\tchar\tname[MAX_OSPATH];\n\tbyte\tb[4];\n\tint\t\ti;\n\n\tsnprintf (name, MAX_OSPATH, \"%s/vip_ip.cfg\", fs_gamedir);\n\n\tCon_Printf (\"Writing %s.\\n\", name);\n\n\tf = fopen (name, \"wb\");\n\tif (!f)\n\t{\n\t\tCon_Printf (\"Couldn't open %s\\n\", name);\n\t\treturn;\n\t}\n\n\tfor (i=0 ; i<numipvips ; i++)\n\t{\n\t\t*(unsigned *)b = ipvip[i].compare;\n\t\tfprintf (f, \"vip_addip %i.%i.%i.%i %d\\n\", b[0], b[1], b[2], b[3], ipvip[i].level);\n\t}\n\n\tfclose (f);\n\n\t// force cache rebuild.\n\tFS_FlushFSHash();\n}\n\n\n/*\n=================\nSV_AddIP_f\n=================\n*/\nstatic void SV_AddIP_f (void)\n{\n\tint\t\ti;\n\tdouble\tt = 0;\n\tchar\t*s;\n\ttime_t\tlong_time = time(NULL);\n\tipfilter_t f;\n\tipfiltertype_t ipft = ipft_ban; // default is ban\n\n\tif (!StringToFilter (Cmd_Argv(1), &f) || f.compare == 0)\n\t{\n\t\tCon_Printf (\"Bad filter address: %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\ts = Cmd_Argv(2);\n\tif ( !s[0] || !strcmp(s, \"ban\"))\n\t\tipft = ipft_ban;\n\telse if (!strcmp(s, \"safe\"))\n\t\tipft = ipft_safe;\n\telse {\n\t\tCon_Printf (\"Wrong filter type %s, use ban or safe\\n\", Cmd_Argv(2));\n\t\treturn;\n\t}\n\n\ts = Cmd_Argv(3);\n\tif (long_time > 0) {\n\t\tif (*s == '+')     // \"addip 127.0.0.1 ban +10\" will ban for 10 seconds from current time\n\t\t\ts++;\n\t\telse\n\t\t\tlong_time = 0; // \"addip 127.0.0.1 ban 1234567\" will ban for some seconds since 00:00:00 GMT, January 1, 1970\n\n\t\tt = (sscanf(s, \"%lf\", &t) == 1) ? t + long_time : 0;\n\t}\n\n\tf.time = t;\n\tf.type = ipft;\n\n\tfor (i=0 ; i<numipfilters ; i++)\n\t\tif (ipfilters[i].compare == 0xffffffff || (ipfilters[i].mask == f.mask\n\t\t        && ipfilters[i].compare == f.compare))\n\t\t\tbreak;\t\t// free spot\n\tif (i == numipfilters)\n\t{\n\t\tif (numipfilters == MAX_IPFILTERS)\n\t\t{\n\t\t\tCon_Printf (\"IP filter list is full\\n\");\n\t\t\treturn;\n\t\t}\n\t\tnumipfilters++;\n\t}\n\n\tipfilters[i] = f;\n}\n\n/*\n=================\nSV_RemoveIP_f\n=================\n*/\nstatic void SV_RemoveIP_f (void)\n{\n\tipfilter_t\tf;\n\tint\t\t\ti, j;\n\n\tif (!StringToFilter (Cmd_Argv(1), &f))\n\t{\n\t\tCon_Printf (\"Bad filter address: %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tfor (i=0 ; i<numipfilters ; i++)\n\t\tif (ipfilters[i].mask == f.mask\n\t\t        && ipfilters[i].compare == f.compare)\n\t\t{\n\t\t\tfor (j=i+1 ; j<numipfilters ; j++)\n\t\t\t\tipfilters[j-1] = ipfilters[j];\n\t\t\tnumipfilters--;\n\t\t\tCon_Printf (\"Removed.\\n\");\n\t\t\treturn;\n\t\t}\n\tCon_Printf (\"Didn't find %s.\\n\", Cmd_Argv(1));\n}\n\n/*\n=================\nSV_ListIP_f\n=================\n*/\nstatic void SV_ListIP_f (void)\n{\n\ttime_t\tlong_time = time(NULL);\n\tint\t\ti;\n\tbyte\tb[4];\n\n\tCon_Printf (\"Filter list:\\n\");\n\tfor (i=0 ; i<numipfilters ; i++)\n\t{\n\t\t*(unsigned *)b = ipfilters[i].compare;\n\t\tCon_Printf (\"%3i.%3i.%3i.%3i | \", b[0], b[1], b[2], b[3]);\n\t\tswitch((int)ipfilters[i].type){\n\t\t\tcase ipft_ban:  Con_Printf (\" ban\"); break;\n\t\t\tcase ipft_safe: Con_Printf (\"safe\"); break;\n\t\t\tdefault: Con_Printf (\"unkn\"); break;\n\t\t}\n\t\tif (ipfilters[i].time)\n\t\t\tCon_Printf (\" | %i s\", (int)(ipfilters[i].time-long_time));\n\t\tCon_Printf (\"\\n\");\n\t}\n}\n\n/*\n=================\nSV_WriteIP_f\n=================\n*/\nstatic void SV_WriteIP_f (void)\n{\n\tFILE\t*f;\n\tchar\tname[MAX_OSPATH], *s;\n\tbyte\tb[4];\n\tint\t\ti;\n\n\tsnprintf (name, MAX_OSPATH, \"%s/listip.cfg\", fs_gamedir);\n\n\tCon_Printf (\"Writing %s.\\n\", name);\n\n\tf = fopen (name, \"wb\");\n\tif (!f)\n\t{\n\t\tCon_Printf (\"Couldn't open %s\\n\", name);\n\t\treturn;\n\t}\n\n\t// write safe filters first\n\tfor (i=0 ; i<numipfilters ; i++)\n\t{\n\t\tif(ipfilters[i].type != ipft_safe)\n\t\t\tcontinue;\n\n\t\t*(unsigned *)b = ipfilters[i].compare;\n\t\tfprintf (f, \"addip %i.%i.%i.%i safe %.0f\\n\", b[0], b[1], b[2], b[3], ipfilters[i].time);\n\t}\n\n\tfor (i=0 ; i<numipfilters ; i++)\n\t{\n\t\tif(ipfilters[i].type == ipft_safe)\n\t\t\tcontinue; // ignore safe, we already save it\n\n\t\tswitch((int)ipfilters[i].type){\n\t\t\tcase ipft_ban:  s = \" ban\"; break;\n\t\t\tcase ipft_safe: s = \"safe\"; break;\n\t\t\tdefault: s = \"unkn\"; break;\n\t\t}\n\t\t*(unsigned *)b = ipfilters[i].compare;\n\t\tfprintf (f, \"addip %i.%i.%i.%i %s %.0f\\n\", b[0], b[1], b[2], b[3], s, ipfilters[i].time);\n\t}\n\n\tfclose (f);\n\n\t// force cache rebuild.\n\tFS_FlushFSHash();\n}\n\n/*\n=================\nSV_SendBan\n=================\n*/\nvoid SV_SendBan (void)\n{\n\tchar\t\tdata[128];\n\n\tdata[0] = data[1] = data[2] = data[3] = 0xff;\n\tdata[4] = A2C_PRINT;\n\tdata[5] = 0;\n\tstrlcat (data, \"\\nbanned.\\n\", sizeof(data));\n\n\tNET_SendPacket (NS_SERVER, strlen(data), data, net_from);\n}\n\n/*\n=================\nSV_FilterPacket\n=================\n*/\nqbool SV_FilterPacket (void)\n{\n\tint\t\ti;\n\tunsigned\tin;\n\n\tin = *(unsigned *)net_from.ip;\n\n\tfor (i=0 ; i<numipfilters ; i++)\n\t\tif ( ipfilters[i].type == ipft_ban && (in & ipfilters[i].mask) == ipfilters[i].compare )\n\t\t\treturn (int)filterban.value;\n\n\treturn !(int)filterban.value;\n}\n\n// { server internal BAN support\n\n#define AF_REAL_ADMIN    (1<<1)    // pass/vip granted admin.\n\nvoid Do_BanList(ipfiltertype_t ipft)\n{\n\ttime_t\tlong_time = time(NULL);\n\tint\t\ti;\n\tbyte\tb[4];\n\n\tfor (i=0 ; i<numipfilters ; i++)\n\t{\n\t\tif (ipfilters[i].type != ipft)\n\t\t\tcontinue;\n\n\t\t*(unsigned *)b = ipfilters[i].compare;\n\t\tCon_Printf (\"%3i|%3i.%3i.%3i.%3i\", i, b[0], b[1], b[2], b[3]);\n\t\tswitch((int)ipfilters[i].type){\n\t\t\tcase ipft_ban:  Con_Printf (\"| ban\"); break;\n\t\t\tcase ipft_safe: Con_Printf (\"|safe\"); break;\n\t\t\tdefault: Con_Printf (\"|unkn\"); break;\n\t\t}\n\n\t\tif (ipfilters[i].time) {\n\t\t\tlong df = ipfilters[i].time-long_time;\n\t\t\tlong d, h, m, s;\n\t\t\td = df / (60*60*24);\n\t\t\tdf -= d * 60*60*24;\n\t\t\th = df / (60*60);\n\t\t\tdf -= h * 60*60;\n\t\t\tm = df /  60;\n\t\t\tdf -= m * 60;\n\t\t\ts = df;\n\n\t\t\tif (d)\n\t\t\t\tCon_Printf (\"|%4ldd:%2ldh\", d, h);\n\t\t\telse if (h)\n\t\t\t\tCon_Printf (\"|%4ldh:%2ldm\", h, m);\n\t\t\telse\n\t\t\t\tCon_Printf (\"|%4ldm:%2lds\", m, s);\n\t\t}\n\t\telse\n\t\t\tCon_Printf (\"|permanent\");\n\t\tCon_Printf (\"\\n\");\n\t}\n}\n\nvoid SV_BanList (void)\n{\n\tunsigned char blist[64] = \"Ban list:\", id[64] = \"id\", ipmask[64] = \"ip mask\", type[64] = \"type\", expire[64] = \"expire\";\n\n\tif (numipfilters < 1) {\n\t\tCon_Printf (\"Ban list: empty\\n\");\n\t\treturn;\n\t}\n\n\tCon_Printf (\"%s\\n\"\n\t\t\t\t\"\\235\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\"\n\t\t\t\t\"\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\236\\237\\n\"\n\t\t\t\t\"%3.3s|%15.15s|%4.4s|%9.9s\\n\",\n\t\t\t\tQ_redtext(blist), Q_redtext(id), Q_redtext(ipmask), Q_redtext(type), Q_redtext(expire));\n\n\tDo_BanList(ipft_safe);\n\tDo_BanList(ipft_ban);\n}\n\nqbool SV_CanAddBan (ipfilter_t *f)\n{\n\tint i;\n\n\tif (f->compare == 0)\n\t\treturn false;\n\n\tfor (i=0 ; i<numipfilters ; i++)\n\t\tif (ipfilters[i].mask == f->mask && ipfilters[i].compare == f->compare && ipfilters[i].type == ipft_safe)\n\t\t\treturn false; // can't add filter f because present \"safe\" filter\n\n\treturn true;\n}\n\nvoid SV_RemoveBansIPFilter (int i)\n{\n\tfor (; i + 1 < numipfilters; i++)\n\t\tipfilters[i] = ipfilters[i + 1];\n\n\tnumipfilters--;\n}\n\nvoid SV_CleanBansIPList (void)\n{\n\ttime_t\tlong_time = time(NULL);\n\tint     i;\n\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\tfor (i = 0; i < numipfilters;)\n\t{\n\t\tif (ipfilters[i].time && ipfilters[i].time <= long_time)\n\t\t{\n\t\t\tSV_RemoveBansIPFilter (i);\n\t\t}\n\t\telse\n\t\t\ti++;\n\t}\n}\n\nvoid SV_Cmd_Ban_f(void)\n{\n\tedict_t\t*ent;\n\teval_t *val;\n\tdouble\t\td;\n\tint\t\t\ti, j, t;\n\tclient_t\t*cl;\n\tipfilter_t  f;\n\tint\t\t\tuid;\n\tint\t\t\tc;\n\tchar\t\treason[80] = \"\", arg2[32], arg2c[sizeof(arg2)], *s;\n\n\t// set up the edict\n\tent = sv_client->edict;\n\n// ============\n// get ADMIN rights from MOD via \"mod_admin\" field, mod MUST export such field if wanna server ban support\n// ============\n\n\tval = PR_GetEdictFieldValue(ent, \"mod_admin\");\n\tif (!val || !(val->_int & AF_REAL_ADMIN) ) {\n\t\tCon_Printf(\"You are not an admin\\n\");\n\t\treturn;\n\t}\n\n\tc = Cmd_Argc ();\n\tif (c < 3)\n\t{\n\t\tCon_Printf(\"usage: cmd ban <id/nick> <time<s m h d>> [reason]\\n\");\n\t\treturn;\n\t}\n\n\tuid = Q_atoi(Cmd_Argv(1));\n\n\tstrlcpy(arg2, Cmd_Argv(2), sizeof(arg2));\n\n\t// sscanf safe here since sizeof(arg2) == sizeof(arg2c), right?\n\tif (sscanf(arg2, \"%d%s\", &t, arg2c) != 2 || strlen(arg2c) != 1) {\n\t\tCon_Printf(\"ban: wrong time arg\\n\");\n\t\treturn;\n\t}\n\n\td = t = bound(0, t, 999);\n\tswitch(arg2c[0]) {\n\t\tcase 's': break; // seconds is seconds\n\t\tcase 'm': d *= 60; break; // 60 seconds per minute\n\t\tcase 'h': d *= 60*60; break; // 3600 seconds per hour\n\t\tcase 'd': d *= 60*60*24; break; // 86400 seconds per day\n\t\tdefault:\n\t\tCon_Printf(\"ban: wrong time arg\\n\");\n\t\treturn;\n\t}\n\n\tfor (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t{\n\t\tif (!cl->state)\n\t\t\tcontinue;\n\t\tif (cl->userid == uid || !strcmp(Cmd_Argv(1), cl->name))\n\t\t{\n\t\t\tif (c > 3) // serve reason arguments\n\t\t\t{\n\t\t\t\tstrlcpy (reason, \" (\", sizeof(reason));\n\t\t\t\tfor (j=3 ; j<c; j++)\n\t\t\t\t{\n\t\t\t\t\tstrlcat (reason, Cmd_Argv(j), sizeof(reason)-4);\n\t\t\t\t\tif (j < c-1)\n\t\t\t\t\t\tstrlcat (reason, \" \", sizeof(reason)-4);\n\t\t\t\t}\n\t\t\t\tif (strlen(reason) < 3)\n\t\t\t\t\treason[0] = '\\0';\n\t\t\t\telse\n\t\t\t\t\tstrlcat (reason, \")\", sizeof(reason));\n\t\t\t}\n\n\t\t\ts = NET_BaseAdrToString(cl->netchan.remote_address);\n\t\t\tif (!StringToFilter (s, &f))\n\t\t\t{\n\t\t\t\tCon_Printf (\"ban: bad ip address: %s\\n\", s);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!SV_CanAddBan(&f))\n\t\t\t{\n\t\t\t\tCon_Printf (\"ban: can't ban such ip: %s\\n\", s);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tSV_BroadcastPrintf (PRINT_HIGH, \"%s was banned for %d%s%s\\n\", cl->name, t, arg2c, reason);\n\n\t\t\tCbuf_AddText(va(\"addip %s ban %s%.0lf\\n\", s, d ? \"+\" : \"\", d));\n\t\t\tCbuf_AddText(\"writeip\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\tCon_Printf (\"Couldn't find user %s\\n\", Cmd_Argv(1));\n}\n\nvoid SV_Cmd_Banip_f(void)\n{\n\tedict_t\t*ent;\n\teval_t *val;\n\tbyte\tb[4];\n\tdouble\t\td;\n\tint\t\t\tc, t;\n\tipfilter_t  f;\n\tchar\t\targ2[32], arg2c[sizeof(arg2)];\n\n\t// set up the edict\n\tent = sv_client->edict;\n\n// ============\n// get ADMIN rights from MOD via \"mod_admin\" field, mod MUST export such field if wanna server ban support\n// ============\n\n\tval = PR_GetEdictFieldValue(ent, \"mod_admin\");\n\tif (!val || !(val->_int & AF_REAL_ADMIN) ) {\n\t\tCon_Printf(\"You are not an admin\\n\");\n\t\treturn;\n\t}\n\n\tc = Cmd_Argc ();\n\tif (c < 3)\n\t{\n\t\tCon_Printf(\"usage: cmd banip <ip> <time<s m h d>>\\n\");\n\t\treturn;\n\t}\n\n\tif (!StringToFilter (Cmd_Argv(1), &f))\n\t{\n\t\tCon_Printf (\"ban: bad ip address: %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tif (!SV_CanAddBan(&f))\n\t{\n\t\tCon_Printf (\"ban: can't ban such ip: %s\\n\", Cmd_Argv(1));\n\t\treturn;\n\t}\n\n\tstrlcpy(arg2, Cmd_Argv(2), sizeof(arg2));\n\n\t// sscanf safe here since sizeof(arg2) == sizeof(arg2c), right?\n\tif (sscanf(arg2, \"%d%s\", &t, arg2c) != 2 || strlen(arg2c) != 1) {\n\t\tCon_Printf(\"ban: wrong time arg\\n\");\n\t\treturn;\n\t}\n\n\td = t = bound(0, t, 999);\n\tswitch(arg2c[0]) {\n\t\tcase 's': break; // seconds is seconds\n\t\tcase 'm': d *= 60; break; // 60 seconds per minute\n\t\tcase 'h': d *= 60*60; break; // 3600 seconds per hour\n\t\tcase 'd': d *= 60*60*24; break; // 86400 seconds per day\n\t\tdefault:\n\t\tCon_Printf(\"ban: wrong time arg\\n\");\n\t\treturn;\n\t}\n\n\t*(unsigned *)b = f.compare;\n\tSV_BroadcastPrintf (PRINT_HIGH, \"%3i.%3i.%3i.%3i was banned for %d%s\\n\", b[0], b[1], b[2], b[3], t, arg2c);\n\n\tCbuf_AddText(va(\"addip %i.%i.%i.%i ban %s%.0lf\\n\", b[0], b[1], b[2], b[3], d ? \"+\" : \"\", d));\n\tCbuf_AddText(\"writeip\\n\");\n}\n\nvoid SV_Cmd_Banremove_f(void)\n{\n\tedict_t\t*ent;\n\teval_t *val;\n\tbyte\tb[4];\n\tint\t\tid;\n\n\t// set up the edict\n\tent = sv_client->edict;\n\n// ============\n// get ADMIN rights from MOD via \"mod_admin\" field, mod MUST export such field if wanna server ban support\n// ============\n\n\tval = PR_GetEdictFieldValue(ent, \"mod_admin\");\n\tif (!val || !(val->_int & AF_REAL_ADMIN) ) {\n\t\tCon_Printf(\"You are not an admin\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc () < 2)\n\t{\n\t\tCon_Printf(\"usage: cmd banrem [banid]\\n\");\n\t\tSV_BanList();\n\t\treturn;\n\t}\n\n\tid = Q_atoi(Cmd_Argv(1));\n\n\tif (id < 0 || id >= numipfilters) {\n\t\tCon_Printf (\"Wrong ban id: %d\\n\", id);\n\t\treturn;\n\t}\n\n\tif (ipfilters[id].type == ipft_safe) {\n\t\tCon_Printf (\"Can't remove such ban with id: %d\\n\", id);\n\t\treturn;\n\t}\n\n\t*(unsigned *)b = ipfilters[id].compare;\n\tSV_BroadcastPrintf (PRINT_HIGH, \"%3i.%3i.%3i.%3i was unbanned\\n\", b[0], b[1], b[2], b[3]);\n\n\tSV_RemoveBansIPFilter (id);\n\tCbuf_AddText(\"writeip\\n\");\n}\n\n// } server internal BAN support\n\n/*\n=================\nSV_VIPbyIP\n=================\n*/\nint SV_VIPbyIP (netadr_t adr)\n{\n\tint\t\ti;\n\tunsigned\tin;\n\n\tin = *(unsigned *)adr.ip;\n\n\tfor (i=0 ; i<numipvips ; i++)\n\t\tif ( (in & ipvip[i].mask) == ipvip[i].compare)\n\t\t\treturn ipvip[i].level;\n\n\treturn 0;\n}\n\n/*\n=================\nSV_VIPbyPass\n=================\n*/\nint SV_VIPbyPass (char *pass)\n{\n\tqbool use_value = false;\n\tint vip_value[MAX_ARGS];\n\tint i;\n\n\tif (!vip_password.string[0] || !strcasecmp(vip_password.string, \"none\"))\n\t\treturn 0;\n\n\tif (vip_values.string[0]) {\n\t\tuse_value = true;\n\t\t// 2VVD: vip_password count may be not equal vip_values count, what we must do in this case?\n\t\tmemset((void*)vip_value, 0, sizeof(vip_value));\n\t\tCmd_TokenizeString(vip_values.string);\n\t\tfor (i = 0; i < Cmd_Argc(); i++)\n\t\t\tvip_value[i] = atoi(Cmd_Argv(i));\n\t}\n\n\tCmd_TokenizeString(vip_password.string);\n\n\tfor (i = 0; i < Cmd_Argc(); i++)\n\t\tif (!strcmp(Cmd_Argv(i), pass) && strcasecmp(Cmd_Argv(i), \"none\"))\n\t\t\treturn (use_value ? vip_value[i] : i+1);\n\n\treturn 0;\n}\n\nstatic char *DecodeArgs(char *args)\n{\n\tstatic char string[1024];\n\tchar *p, key[32], *s, *value, ch, tmp_value[512];\n\n\tstring[0] = 0;\n\tp = string;\n\n\twhile (*args)\n\t{\n\t\t// skip whitespaces\n\t\twhile (*args && *args <= 32)\n\t\t\t*p++ = *args++;\n\n\t\tif (*args == '\\\"')\n\t\t{\n\t\t\tdo *p++ = *args++; while (*args && *args != '\\\"');\n\t\t\t*p++ = '\\\"';\n\t\t\tif (*args)\n\t\t\t\targs++;\n\t\t}\n\t\telse if (*args == '@' || *args == '$')\n\t\t{\n\t\t\t// get the key and read value from localinfo\n\t\t\tch = *args;\n\t\t\ts = key;\n\t\t\targs++;\n\t\t\twhile (*args > 32)\n\t\t\t\t*s++ = *args++;\n\t\t\t*s = 0;\n\n\t\t\tif ((value = Info_ValueForKey (svs.info, key)) == NULL || !*value)\n\t\t\t\tvalue = Info_Get(&_localinfo_, key);\n\n\t\t\tif (ch == '$' && value)\n\t\t\t{\n\t\t\t\tstrlcpy(tmp_value, value, sizeof(tmp_value));\n\t\t\t\tQ_normalizetext(tmp_value);\n\t\t\t\tvalue = tmp_value;\n\t\t\t}\n\n\t\t\t*p++ = '\\\"';\n\t\t\tif (value)\n\t\t\t{\n\t\t\t\twhile (*value)\n\t\t\t\t\t*p++ = *value++;\n\t\t\t}\n\t\t\t*p++ = '\\\"';\n\t\t}\n\t\telse {\n\t\t\twhile (*args > 32) {\n\t\t\t\t*p++ = *args++;\n\t\t\t}\n\t\t}\n\t}\n\n\t*p = 0;\n\n\treturn string;\n}\n\nvoid SV_Script_f (void)\n{\n\tchar *path, *p;\n\textern redirect_t sv_redirected;\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tCon_Printf(\"usage: script <path> [<args>]\\n\");\n\t\treturn;\n\t}\n\n\tpath = Cmd_Argv(1);\n\n\t//bliP: 24/9 need subdirs here ->\n\tif (!strncmp(path, \"../\", 3) || !strncmp(path, \"..\\\\\", 3))\n\t\tpath += 3;\n\n\tif (strstr(path, \"..\"))\n\t{\n\t\tCon_Printf(\"Invalid path.\\n\");\n\t\treturn;\n\t}\n\t//<-\n\n\tpath = Cmd_Argv(1);\n\n\tp = Cmd_Args();\n\twhile (*p > 32)\n\t\tp++;\n\twhile (*p && *p <= 32)\n\t\tp++;\n\n\tp = DecodeArgs(p);\n\n\tif (sv_redirected != RD_MOD)\n\t\tSys_Printf(\"Running %s.qws\\n\", path);\n\n\tSys_Script(path, va(\"%d %s\", sv_redirected, p));\n\n}\n\n//============================================================================\n\n//bliP: cuff, mute ->\nvoid SV_RemoveIPFilter (int i)\n{\n\tfor (; i + 1 < numpenfilters; i++)\n\t\tpenfilters[i] = penfilters[i + 1];\n\n\tnumpenfilters--;\n}\n\nstatic void SV_CleanIPList (void)\n{\n\tint     i;\n\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\tfor (i = 0; i < numpenfilters;)\n\t{\n\t\tif (penfilters[i].time && (penfilters[i].time <= realtime))\n\t\t{\n\t\t\tSV_RemoveIPFilter (i);\n\t\t}\n\t\telse\n\t\t\ti++;\n\t}\n}\n\nstatic qbool SV_IPCompare (byte *a, byte *b)\n{\n\tint i;\n\n\tfor (i = 0; i < 1; i++)\n\t\tif (((unsigned int *)a)[i] != ((unsigned int *)b)[i])\n\t\t\treturn false;\n\n\treturn true;\n}\n\nstatic void SV_IPCopy (byte *dest, byte *src)\n{\n\tint i;\n\n\tfor (i = 0; i < 1; i++)\n\t\t((unsigned int *)dest)[i] = ((unsigned int *)src)[i];\n}\n\nvoid SV_SavePenaltyFilter (client_t *cl, filtertype_t type, double pentime)\n{\n\tint i;\n\n\tif (pentime < curtime)   // no point\n\t\treturn;\n\n\tfor (i = 0; i < numpenfilters; i++)\n\t\tif (SV_IPCompare (penfilters[i].ip, cl->realip.ip)\t&& penfilters[i].type == type)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\tif (numpenfilters == MAX_IPFILTERS)\n\t{\n\t\treturn;\n\t}\n\n\tSV_IPCopy (penfilters[numpenfilters].ip, cl->realip.ip);\n\tpenfilters[numpenfilters].time = pentime;\n\tpenfilters[numpenfilters].type = type;\n\tnumpenfilters++;\n}\n\ndouble SV_RestorePenaltyFilter (client_t *cl, filtertype_t type)\n{\n\tint i;\n\tdouble time1 = 0.0;\n\n\t// search for existing penalty filter of same type\n\tfor (i = 0; i < numpenfilters; i++)\n\t{\n\t\tif (type == penfilters[i].type && SV_IPCompare (cl->realip.ip, penfilters[i].ip))\n\t\t{\n\t\t\ttime1 = penfilters[i].time;\n\t\t\tSV_RemoveIPFilter (i);\n\t\t\treturn time1;\n\t\t}\n\t}\n\treturn time1;\n}\n//<-\n\n//============================================================================\n\n/*\n=================\nSV_ReadPackets\n=================\n*/\nstatic void SV_ReadPackets (void)\n{\n\tclient_t *cl;\n\tint qport;\n\tint i;\n\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\t// first deal with delayed packets from connected clients\n\tfor (i = 0, cl=svs.clients; i < MAX_CLIENTS; i++, cl++)\n\t{\n\t\tif (cl->state == cs_free)\n\t\t\tcontinue;\n\n\t\tnet_from = cl->netchan.remote_address;\n\n\t\twhile (cl->packets && (realtime - cl->packets->time >= cl->delay || sv.paused))\n\t\t{\n\t\t\tSZ_Clear(&net_message);\n\t\t\tSZ_Write(&net_message, cl->packets->msg.data, cl->packets->msg.cursize);\n\t\t\tSV_ExecuteClientMessage(cl);\n\t\t\tSV_FreeHeadDelayedPacket(cl);\n\t\t}\n\t}\n\n\t// now deal with new packets\n\twhile (NET_GetPacket(NS_SERVER))\n\t{\n\t\tif (SV_FilterPacket ())\n\t\t{\n\t\t\tSV_SendBan ();\t// tell them we aren't listening...\n\t\t\tcontinue;\n\t\t}\n\n\t\t// check for connectionless packet (0xffffffff) first\n\t\tif (*(int *)net_message.data == -1)\n\t\t{\n\t\t\tSV_ConnectionlessPacket ();\n\t\t\tcontinue;\n\t\t}\n\n\t\t// read the qport out of the message so we can fix up\n\t\t// stupid address translating routers\n\t\tMSG_BeginReading ();\n\t\tMSG_ReadLong (); // sequence number\n\t\tMSG_ReadLong (); // sequence number\n\t\tqport = MSG_ReadShort () & 0xffff;\n\n\t\t// check which client sent this packet\n\t\tfor (i=0, cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)\n\t\t{\n\t\t\tif (cl->state == cs_free)\n\t\t\t\tcontinue;\n\t\t\tif (!NET_CompareBaseAdr (net_from, cl->netchan.remote_address))\n\t\t\t\tcontinue;\n\t\t\tif (cl->netchan.qport != qport)\n\t\t\t\tcontinue;\n\t\t\tif (cl->netchan.remote_address.port != net_from.port)\n\t\t\t{\n\t\t\t\tCon_DPrintf (\"SV_ReadPackets: fixing up a translated port\\n\");\n\t\t\t\tcl->netchan.remote_address.port = net_from.port;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\n\t\tif (i == MAX_CLIENTS)\n\t\t\tcontinue;\n\n\t\t// ok, we know who sent this packet, but do we need to delay executing it?\n\t\tif (cl->delay > 0)\n\t\t{\n\t\t\tif (!svs.free_packets) // packet has to be dropped..\n\t\t\t\tbreak;\n\n\t\t\t// insert at end of list\n\t\t\tif (!cl->packets) {\n\t\t\t\tcl->last_packet = cl->packets = svs.free_packets;\n\t\t\t} else {\n\t\t\t\t// this works because '=' associates from right to left\n\t\t\t\tcl->last_packet = cl->last_packet->next = svs.free_packets;\n\t\t\t}\n\n\t\t\tsvs.free_packets = svs.free_packets->next;\n\t\t\tcl->last_packet->next = NULL;\n\n\t\t\tcl->last_packet->time = realtime;\n\t\t\tSZ_Clear(&cl->last_packet->msg);\n\t\t\tSZ_Write(&cl->last_packet->msg, net_message.data, net_message.cursize);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tSV_ExecuteClientMessage (cl);\n\t\t}\n\t}\n}\n\n\n/*\n==================\nSV_CheckTimeouts\n\nIf a packet has not been received from a client in timeout.value\nseconds, drop the conneciton.\n\nWhen a client is normally dropped, the client_t goes into a zombie state\nfor a few seconds to make sure any final reliable message gets resent\nif necessary\n==================\n*/\nstatic void SV_CheckTimeouts (void)\n{\n\tint i, nclients;\n\tfloat droptime;\n\tclient_t *cl;\n\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\tdroptime = curtime - timeout.value;\n\tnclients = 0;\n\n\tfor (i=0,cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)\n\t{\n#ifdef USE_PR2\n\t\tif( cl->isBot )\n\t\t\tcontinue;\n#endif\n\t\tif (cl->state >= cs_preconnected /*|| cl->state == cs_spawned*/)\n\t\t{\n\t\t\tif (!cl->spectator)\n\t\t\t\tnclients++;\n\t\t\tif (cl->netchan.last_received < droptime)\n\t\t\t{\n\t\t\t\tSV_BroadcastPrintf (PRINT_HIGH, \"%s timed out\\n\", cl->name);\n\t\t\t\tSV_DropClient (cl);\n\t\t\t\tcl->state = cs_free;\t// don't bother with zombie state\n\t\t\t}\n\t\t\tif (!cl->logged && !cl->logged_in_via_web) {\n\t\t\t\tSV_LoginCheckTimeOut(cl);\n\t\t\t}\n\t\t}\n\t\tif (cl->state == cs_zombie && SV_ClientConnectedTime(cl) > zombietime.value)\n\t\t{\n\t\t\tcl->state = cs_free;\t// can now be reused\n\t\t}\n\t}\n\tif ((sv.paused & 1) && !nclients)\n\t{\n\t\t// nobody left, unpause the server\n\t\tif (GE_ShouldPause) {\n\t\t\tpr_global_struct->time = sv.time;\n\t\t\tpr_global_struct->self = EDICT_TO_PROG(sv.edicts);\n\t\t\tG_FLOAT(OFS_PARM0) = 0 /* newstate = false */;\n\t\t\tPR_ExecuteProgram (GE_ShouldPause);\n\t\t\tif (!G_FLOAT(OFS_RETURN))\n\t\t\t\treturn;\t\t// progs said don't unpause\n\t\t}\n\t\tSV_TogglePause(\"Pause released since no players are left.\\n\", 1);\n\t}\n}\n\n#ifdef SERVERONLY\n/*\n===================\nSV_GetConsoleCommands\n\nAdd them exactly as if they had been typed at the console\n===================\n*/\nstatic void SV_GetConsoleCommands (void)\n{\n\tchar\t*cmd;\n\n\twhile (1)\n\t{\n\t\tcmd = Sys_ConsoleInput ();\n\t\tif (!cmd)\n\t\t\tbreak;\n\t\tCbuf_AddText (cmd);\n\t\tCbuf_AddText (\"\\n\");\n\t}\n}\n#endif\n\n/*\n===================\nSV_BoundRate\n===================\n*/\nint SV_BoundRate (qbool dl, int rate)\n{\n\tif (!rate)\n\t\trate = 2500;\n\tif (dl)\n\t{\n\t\tif (!(int)sv_maxdownloadrate.value && (int)sv_maxrate.value && rate > (int)sv_maxrate.value)\n\t\t\trate = (int)sv_maxrate.value;\n\n\t\tif (sv_maxdownloadrate.value && rate > sv_maxdownloadrate.value)\n\t\t\trate = (int)sv_maxdownloadrate.value;\n\t}\n\telse\n\t\tif ((int)sv_maxrate.value && rate > (int) sv_maxrate.value)\n\t\t\trate = (int)sv_maxrate.value;\n\n\tif (rate < 500)\n\t\trate = 500;\n\n\tif (rate > 100000 * MAX_DUPLICATE_PACKETS)\n\t\trate = 100000 * MAX_DUPLICATE_PACKETS;\n\n\treturn rate;\n}\n\n\n/*\n===================\nSV_CheckVars\n\n===================\n*/\n\nstatic void SV_CheckVars (void)\n{\n\tstatic char pw[MAX_KEY_STRING] = {0}, spw[MAX_KEY_STRING] = {0}, vspw[MAX_KEY_STRING]= {0};\n\tstatic float old_maxrate = 0, old_maxdlrate = 0;\n\tint v;\n\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\t// check password and spectator_password\n\tif (strcmp(password.string, pw) ||\n\t\tstrcmp(spectator_password.string, spw) || strcmp(vip_password.string, vspw))\n\t{\n\t\tstrlcpy (pw, password.string, sizeof(pw));\n\t\tstrlcpy (spw, spectator_password.string, sizeof(spw));\n\t\tstrlcpy (vspw, vip_password.string, sizeof(vspw));\n\t\tCvar_Set (&password, pw);\n\t\tCvar_Set (&spectator_password, spw);\n\t\tCvar_Set (&vip_password, vspw);\n\n\t\tv = 0;\n\t\tif (pw[0] && strcmp(pw, \"none\"))\n\t\t\tv |= 1;\n\t\tif (spw[0] && strcmp(spw, \"none\"))\n\t\t\tv |= 2;\n\t\tif (vspw[0] && strcmp(vspw, \"none\"))\n\t\t\tv |= 4;\n\n\t\tCon_DPrintf (\"Updated needpass.\\n\");\n\t\tif (!v)\n\t\t\tInfo_SetValueForKey (svs.info, \"needpass\", \"\", MAX_SERVERINFO_STRING);\n\t\telse\n\t\t\tInfo_SetValueForKey (svs.info, \"needpass\", va(\"%i\",v), MAX_SERVERINFO_STRING);\n\t}\n\n\t// check sv_maxrate\n\tif ((int)sv_maxrate.value != old_maxrate || (int)sv_maxdownloadrate.value != old_maxdlrate )\n\t{\n\t\tclient_t\t*cl;\n\t\tint\t\t\ti;\n\t\tchar\t\t*val;\n\n\t\told_maxrate = (int)sv_maxrate.value;\n\t\told_maxdlrate = (int)sv_maxdownloadrate.value;\n\n\t\tfor (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)\n\t\t{\n\t\t\tif (cl->state < cs_preconnected)\n\t\t\t\tcontinue;\n\n\t\t\tval = Info_Get (&cl->_userinfo_ctx_, cl->download ? \"drate\" : \"rate\");\n\t\t\tcl->netchan.rate = 1.0 / SV_BoundRate (cl->download != NULL, Q_atoi(*val ? val : \"99999\"));\n\t\t}\n\t}\n}\n\nstatic void PausedTic (void)\n{\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\tPR_PausedTic(Sys_DoubleTime() - sv.pausedsince);\n}\n\n/*\n==================\nSV_Frame\n\n==================\n*/\nvoid SV_Map (qbool now);\nvoid SV_Frame (double time1)\n{\n\tstatic double start, end;\n\tdouble demo_start, demo_end;\n\n\tstart = Sys_DoubleTime ();\n\tsvs.stats.idle += start - end;\n\n\t// keep the random time dependent\n\trand ();\n\n\t// decide the simulation time\n\tif (!sv.paused)\n\t{\n\t\trealtime += time1;\n\t\tsv.time += time1;\n\t}\n\n\t// check timeouts\n\tSV_CheckTimeouts ();\n\n\t//bliP: cuff, mute ->\n\t// clean out expired cuffs/mutes\n\tSV_CleanIPList ();\n\t//<-\n\n\t// clean out bans\n\tSV_CleanBansIPList ();\n\n\t// toggle the log buffer if full\n\tSV_CheckLog ();\n\n\tSV_MVDStream_Poll();\n\n#ifdef SERVERONLY\n\t// check for commands typed to the host\n\tSV_GetConsoleCommands ();\n\n\t// process console commands\n\tCbuf_Execute ();\n#endif\n\n\t// check for map change;\n\tSV_Map(true);\n\n\tSV_CheckVars ();\n\n\t// get packets\n\tSV_ReadPackets ();\n\n\t// move autonomous things around if enough time has passed\n\tif (!sv.paused) {\n\t\tSV_Physics();\n#ifdef USE_PR2\n\t\tSV_RunBots();\n#endif\n\t}\n\telse\n\t\tPausedTic ();\n\n\t// send messages back to the clients that had packets read this frame\n\tSV_SendClientMessages ();\n\n#if defined(SERVERONLY) && defined(WWW_INTEGRATION)\n\tCentral_ProcessResponses();\n#endif\n\n\tdemo_start = Sys_DoubleTime ();\n\tSV_SendDemoMessage();\n\tdemo_end = Sys_DoubleTime ();\n\tsvs.stats.demo += demo_end - demo_start;\n\n\t// send a heartbeat to the master if needed\n\tMaster_Heartbeat ();\n\n\t// collect timing statistics\n\tend = Sys_DoubleTime ();\n\tsvs.stats.active += end-start;\n\tif (++svs.stats.count == STATFRAMES)\n\t{\n\t\tsvs.stats.latched_active = svs.stats.active;\n\t\tsvs.stats.latched_idle = svs.stats.idle;\n\t\tsvs.stats.latched_packets = svs.stats.packets;\n\t\tsvs.stats.latched_demo = svs.stats.demo;\n\t\tsvs.stats.active = 0;\n\t\tsvs.stats.idle = 0;\n\t\tsvs.stats.packets = 0;\n\t\tsvs.stats.count = 0;\n\t\tsvs.stats.demo = 0;\n\t}\n}\n\n/*\n===============\nSV_InitLocal\n===============\n*/\nvoid SV_InitLocal (void)\n{\n\tint\t\ti;\n\tchar\tcmd_line[1024] = {0};\n\n\textern\tcvar_t\tsv_maxvelocity;\n\textern\tcvar_t\tsv_gravity;\n\textern\tcvar_t\tsv_stopspeed;\n\textern\tcvar_t\tsv_spectatormaxspeed;\n\textern\tcvar_t\tsv_accelerate;\n\textern\tcvar_t\tsv_airaccelerate;\n\textern\tcvar_t\tsv_wateraccelerate;\n\textern\tcvar_t\tsv_friction;\n\textern\tcvar_t\tsv_waterfriction;\n\textern\tcvar_t\tsv_nailhack;\n\n\textern cvar_t\tsv_maxpitch;\n\textern cvar_t\tsv_minpitch;\n\n\textern\tcvar_t\tpm_airstep;\n\textern\tcvar_t\tpm_pground;\n\textern  cvar_t  pm_rampjump;\n\textern\tcvar_t\tpm_slidefix;\n\textern\tcvar_t\tpm_ktjump;\n\textern\tcvar_t\tpm_bunnyspeedcap;\n\n\t// qws = QuakeWorld Server information\n\tstatic cvar_t qws_name = { \"qws_name\", SERVER_NAME, CVAR_ROM };\n\tstatic cvar_t qws_fullname = { \"qws_fullname\", SERVER_FULLNAME, CVAR_ROM };\n\tstatic cvar_t qws_version = { \"qws_version\", SERVER_VERSION, CVAR_ROM };\n\tstatic cvar_t qws_buildnum = { \"qws_buildnum\", \"unknown\", CVAR_ROM };\n\tstatic cvar_t qws_platform = { \"qws_platform\", QW_PLATFORM_SHORT, CVAR_ROM };\n\tstatic cvar_t qws_builddate = { \"qws_builddate\", BUILD_DATE, CVAR_ROM };\n\tstatic cvar_t qws_homepage = { \"qws_homepage\", SERVER_HOME_URL, CVAR_ROM };\n\t// qwm = QuakeWorld Mod information placeholders\n\tstatic cvar_t qwm_name = { \"qwm_name\", \"\" };\n\tstatic cvar_t qwm_fullname = { \"qwm_fullname\", \"\" };\n\tstatic cvar_t qwm_version = { \"qwm_version\", \"\" };\n\tstatic cvar_t qwm_buildnum = { \"qwm_buildnum\", \"\" };\n\tstatic cvar_t qwm_platform = { \"qwm_platform\", \"\" };\n\tstatic cvar_t qwm_builddate = { \"qwm_builddate\", \"\" };\n\tstatic cvar_t qwm_homepage = { \"qwm_homepage\", \"\" };\n\n\tpacket_t *packet_freeblock; // initialise delayed packet free block\n\n\tSV_InitOperatorCommands\t();\n\tSV_UserInit ();\n\n\tCvar_Register (&sv_getrealip);\n\tCvar_Register (&sv_maxdownloadrate);\n\tCvar_Register (&sv_serverip);\n\tCvar_Register (&sv_forcespec_onfull);\n\n#ifdef SERVERONLY\n\tCvar_Register (&rcon_password);\n\tCvar_Register (&password);\n#endif\n\n\tCvar_Register (&sv_hashpasswords);\n\t//Added by VVD {\n\tCvar_Register (&sv_crypt_rcon);\n\tCvar_Register (&sv_timestamplen);\n\tCvar_Register (&sv_rconlim);\n\n\tCvar_Register (&telnet_log_level);\n\n\tCvar_Register (&frag_log_type);\n\tCvar_Register (&qconsole_log_say);\n\tCvar_Register (&sv_use_dns);\n\n\tfor (i = 0; i < COM_Argc(); i++)\n\t{\n\t\tif (i)\n\t\t\tstrlcat(cmd_line, \" \", sizeof(cmd_line));\n\t\tstrlcat(cmd_line, COM_Argv(i), sizeof(cmd_line));\n\t}\n\tCvar_Register (&sys_command_line);\n\tCvar_SetROM(&sys_command_line, cmd_line);\n\n\t//Added by VVD }\n\tCvar_Register (&spectator_password);\n\tCvar_Register (&vip_password);\n\tCvar_Register (&vip_values);\n\n\tCvar_Register (&sv_nailhack);\n\n\tCvar_Register (&sv_mintic);\n\tCvar_Register (&sv_maxtic);\n\tCvar_Register (&sv_maxfps);\n\tCvar_Register (&sys_select_timeout);\n\tCvar_Register (&sys_restart_on_error);\n\n\tCvar_Register (&sv_maxpitch);\n\tCvar_Register (&sv_minpitch);\n\n\tCvar_Register (&skill);\n\tCvar_Register (&coop);\n\n\tCvar_Register (&fraglimit);\n\tCvar_Register (&timelimit);\n\tCvar_Register (&teamplay);\n\tCvar_Register (&samelevel);\n\tCvar_Register (&maxclients);\n\tCvar_Register (&maxspectators);\n\tCvar_Register (&maxvip_spectators);\n\tCvar_Register (&hostname);\n\tCvar_Register (&deathmatch);\n\tCvar_Register (&watervis);\n\tCvar_Register (&serverdemo);\n\tCvar_Register (&sv_paused);\n\n\tCvar_Register (&timeout);\n\tCvar_Register (&zombietime);\n\n\tCvar_Register (&sv_maxvelocity);\n\tCvar_Register (&sv_gravity);\n\tCvar_Register (&sv_stopspeed);\n\tCvar_Register (&sv_maxspeed);\n\tCvar_Register (&sv_spectatormaxspeed);\n\tCvar_Register (&sv_accelerate);\n\tCvar_Register (&sv_airaccelerate);\n\tCvar_Register (&sv_wateraccelerate);\n\tCvar_Register (&sv_friction);\n\tCvar_Register (&sv_waterfriction);\n\n\tCvar_Register (&sv_antilag);\n\tCvar_Register (&sv_antilag_no_pred);\n\tCvar_Register (&sv_antilag_projectiles);\n\n\tCvar_Register (&pm_bunnyspeedcap);\n\tCvar_Register (&pm_ktjump);\n\tCvar_Register (&pm_slidefix);\n\tCvar_Register (&pm_pground);\n\tCvar_Register (&pm_airstep);\n\tCvar_Register (&pm_rampjump);\n\n\tCvar_Register (&filterban);\n\n\tCvar_Register (&allow_download);\n\tCvar_Register (&allow_download_skins);\n\tCvar_Register (&allow_download_models);\n\tCvar_Register (&allow_download_sounds);\n\tCvar_Register (&allow_download_maps);\n\tCvar_Register (&allow_download_pakmaps);\n\tCvar_Register (&allow_download_demos);\n\tCvar_Register (&allow_download_other);\n\t//bliP: init ->\n\tCvar_Register (&download_map_url);\n\n\tCvar_Register (&sv_specprint);\n\tCvar_Register (&sv_admininfo);\n\tCvar_Register (&sv_reconnectlimit);\n\tCvar_Register (&sv_maxlogsize);\n\t//bliP: 24/9 ->\n\tCvar_Register (&sv_logdir);\n\tCvar_Register (&sv_speedcheck);\n\tCvar_Register (&sv_unfake); // kickfake to unfake\n\t//<-\n\tCvar_Register (&sv_kicktop);\n\t//<-\n\tCvar_Register (&sv_allowlastscores);\n//\tCvar_Register (&sv_highchars);\n\tCvar_Register (&sv_phs);\n\tCvar_Register (&pausable);\n\tCvar_Register (&sv_maxrate);\n\tCvar_Register (&sv_loadentfiles);\n\tCvar_Register (&sv_loadentfiles_dir);\n\tCvar_Register (&sv_default_name);\n\tCvar_Register (&sv_mod_msg_file);\n\tCvar_Register (&sv_forcenick);\n\tCvar_Register (&sv_registrationinfo);\n\tCvar_Register (&registered);\n\n\tCvar_Register (&sv_halflifebsp);\n\tCvar_Register (&sv_bspversion);\n\tCvar_Register (&sv_serveme_fix);\n\n#ifdef FTE_PEXT_FLOATCOORDS\n\tCvar_Register (&sv_bigcoords);\n#endif\n\n\tCvar_Register (&sv_extlimits);\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\tCvar_Register (&sv_pext_mvdsv_serversideweapon);\n#endif\n\n#ifdef FTE_PEXT_TRANS\n\tCvar_Register(&sv_pext_ezquake_verfortrans);\n#endif\n\n\tCvar_Register (&sv_reliable_sound);\n\n\tCvar_Register(&qws_name);\n\tCvar_Register(&qws_fullname);\n\tCvar_Register(&qws_version);\n\tif (GIT_COMMIT[0]) {\n\t\tqws_buildnum.string = GIT_COMMIT;\n\t}\n\tCvar_Register(&qws_buildnum);\n\tCvar_Register(&qws_platform);\n\tCvar_Register(&qws_builddate);\n\tCvar_Register(&qws_homepage);\n\tCvar_Register(&qwm_name);\n\tCvar_Register(&qwm_fullname);\n\tCvar_Register(&qwm_version);\n\tCvar_Register(&qwm_buildnum);\n\tCvar_Register(&qwm_platform);\n\tCvar_Register(&qwm_builddate);\n\tCvar_Register(&qwm_homepage);\n\n\tCvar_Register(&sv_mod_extensions);\n\n// QW262 -->\n\tCmd_AddCommand (\"svadmin\", SV_Admin_f);\n// <-- QW262\n\n\tCmd_AddCommand (\"addip\", SV_AddIP_f);\n\tCmd_AddCommand (\"removeip\", SV_RemoveIP_f);\n\tCmd_AddCommand (\"listip\", SV_ListIP_f);\n\tCmd_AddCommand (\"writeip\", SV_WriteIP_f);\n\tCmd_AddCommand (\"vip_addip\", SV_AddIPVIP_f);\n\tCmd_AddCommand (\"vip_removeip\", SV_RemoveIPVIP_f);\n\tCmd_AddCommand (\"vip_listip\", SV_ListIPVIP_f);\n\tCmd_AddCommand (\"vip_writeip\", SV_WriteIPVIP_f);\n\n\n\tfor (i=0 ; i<MAX_MODELS ; i++)\n\t\tsnprintf (localmodels[i], MODEL_NAME_LEN, \"*%i\", i);\n\n#ifdef FTE_PEXT_ACCURATETIMINGS\n\tsvs.fteprotocolextensions |= FTE_PEXT_ACCURATETIMINGS;\n#endif\n#ifdef FTE_PEXT_256PACKETENTITIES\n\tsvs.fteprotocolextensions |= FTE_PEXT_256PACKETENTITIES;\n#endif\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tsvs.fteprotocolextensions |= FTE_PEXT_CHUNKEDDOWNLOADS;\n#endif\n#ifdef FTE_PEXT_FLOATCOORDS\n\tsvs.fteprotocolextensions |= FTE_PEXT_FLOATCOORDS;\n#endif\n#ifdef FTE_PEXT_MODELDBL\n\tsvs.fteprotocolextensions |= FTE_PEXT_MODELDBL;\n#endif\n#ifdef FTE_PEXT_ENTITYDBL\n\tsvs.fteprotocolextensions |= FTE_PEXT_ENTITYDBL;\n#endif\n#ifdef FTE_PEXT_ENTITYDBL2\n\tsvs.fteprotocolextensions |= FTE_PEXT_ENTITYDBL2;\n#endif\n#ifdef FTE_PEXT_SPAWNSTATIC2\n\tsvs.fteprotocolextensions |= FTE_PEXT_SPAWNSTATIC2;\n#endif\n#ifdef FTE_PEXT_TRANS\n    svs.fteprotocolextensions |= FTE_PEXT_TRANS;\n#endif\n#ifdef FTE_PEXT_COLOURMOD\n\tsvs.fteprotocolextensions |= FTE_PEXT_COLOURMOD;\n#endif\n#ifdef FTE_PEXT2_VOICECHAT\n\tsvs.fteprotocolextensions2 |= FTE_PEXT2_VOICECHAT;\n#endif\n\n#ifdef MVD_PEXT1_FLOATCOORDS\n\tsvs.mvdprotocolextension1 |= MVD_PEXT1_FLOATCOORDS;\n#endif\n#ifdef MVD_PEXT1_HIGHLAGTELEPORT\n\tsvs.mvdprotocolextension1 |= MVD_PEXT1_HIGHLAGTELEPORT;\n#endif\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\tsvs.mvdprotocolextension1 |= MVD_PEXT1_SERVERSIDEWEAPON;\n#endif\n#ifdef MVD_PEXT1_HIDDEN_MESSAGES\n\tsvs.mvdprotocolextension1 |= MVD_PEXT1_HIDDEN_MESSAGES;\n#endif\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON2\n\tsvs.mvdprotocolextension1 |= MVD_PEXT1_SERVERSIDEWEAPON2;\n#endif\n\n\tInfo_SetValueForStarKey (svs.info, \"*version\", SERVER_NAME \" \" SERVER_VERSION, MAX_SERVERINFO_STRING);\n\tInfo_SetValueForStarKey (svs.info, \"*z_ext\", va(\"%i\", SERVER_EXTENSIONS), MAX_SERVERINFO_STRING);\n\n\t// init fraglog stuff\n\tsvs.logsequence = 1;\n\tsvs.logtime = realtime;\n\tsvs.log[0].data = svs.log_buf[0];\n\tsvs.log[0].maxsize = sizeof(svs.log_buf[0]);\n\tsvs.log[0].cursize = 0;\n\tsvs.log[0].allowoverflow = true;\n\tsvs.log[1].data = svs.log_buf[1];\n\tsvs.log[1].maxsize = sizeof(svs.log_buf[1]);\n\tsvs.log[1].cursize = 0;\n\tsvs.log[1].allowoverflow = true;\n\n\tpacket_freeblock = (packet_t *) Hunk_AllocName (MAX_DELAYED_PACKETS * sizeof(packet_t), \"delayed_packets\");\n\n\tfor (i = 0; i < MAX_DELAYED_PACKETS; i++) {\n\t\tSZ_Init (&packet_freeblock[i].msg, packet_freeblock[i].buf, sizeof(packet_freeblock[i].buf));\n\t\tpacket_freeblock[i].next = &packet_freeblock[i + 1];\n\t}\n\tpacket_freeblock[MAX_DELAYED_PACKETS - 1].next = NULL;\n\tsvs.free_packets = &packet_freeblock[0];\n}\n\n\n//============================================================================\n\n/*\n=================\nSV_ExtractFromUserinfo\n\nPull specific info from a newly changed userinfo string\ninto a more C freindly form.\n=================\n*/\nvoid SV_ExtractFromUserinfo (client_t *cl, qbool namechanged)\n{\n\tchar\t*val, *p;\n\tint\t\ti;\n\tclient_t\t*client;\n\tint\t\tdupc = 1;\n\tchar\tnewname[CLIENT_NAME_LEN];\n\n\tif (namechanged)\n\t{\n\t\t// name for C code\n\t\tval = Info_Get (&cl->_userinfo_ctx_, \"name\");\n\n\t\t// trim user name\n\t\tstrlcpy (newname, val, sizeof(newname));\n\n\t\tfor (p = val; *p; p++)\n\t\t{\n\t\t\tif ((*p & 127) == '\\\\' || *p == '\\r' || *p == '\\n' || *p == '$' || *p == '#' || *p == '\"' || *p == ';')\n\t\t\t{ // illegal characters in name, set some default\n\t\t\t\tstrlcpy(newname, sv_default_name.string, sizeof(newname));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tfor (p = newname; *p && (*p & 127) == ' '; p++)\n\t\t\t; // empty operator\n\n\t\tif (p != newname) // skip prefixed spaces, if any, even whole string of spaces\n\t\t\tstrlcpy(newname, p, sizeof(newname));\n\n\t\tfor (p = newname + strlen(newname) - 1; p >= newname; p--)\n\t\t{\n\t\t\tif (*p && (*p & 127) != ' ') // skip spaces in suffix, if any\n\t\t\t{\n\t\t\t\tp[1] = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (strcmp(val, newname))\n\t\t{\n\t\t\tInfo_Set (&cl->_userinfo_ctx_, \"name\", newname);\n\t\t\tval = Info_Get (&cl->_userinfo_ctx_, \"name\");\n\t\t}\n\n\t\tif (!val[0] || !Q_namecmp(val, \"console\") || strstr(val, \"&c\") || strstr(val, \"&r\"))\n\t\t{\n\t\t\tInfo_Set (&cl->_userinfo_ctx_, \"name\", sv_default_name.string);\n\t\t\tval = Info_Get (&cl->_userinfo_ctx_, \"name\");\n\t\t}\n\n\t\t// check to see if another user by the same name exists\n\t\twhile ( 1 )\n\t\t{\n\t\t\tfor (i = 0, client = svs.clients ; i<MAX_CLIENTS ; i++, client++)\n\t\t\t{\n\t\t\t\tif (client->state != cs_spawned || client == cl)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif (!Q_namecmp(client->name, val))\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (i != MAX_CLIENTS)\n\t\t\t{ // dup name\n\t\t\t\tif (strlen(val) > CLIENT_NAME_LEN - 1)\n\t\t\t\t\tval[CLIENT_NAME_LEN - 4] = 0;\n\t\t\t\tp = val;\n\n\t\t\t\tif (val[0] == '(')\n\t\t\t\t{\n\t\t\t\t\tif (val[2] == ')')\n\t\t\t\t\t\tp = val + 3;\n\t\t\t\t\telse if (val[3] == ')')\n\t\t\t\t\t\tp = val + 4;\n\t\t\t\t}\n\n\t\t\t\tsnprintf(newname, sizeof(newname), \"(%d)%-.10s\", dupc++, p);\n\t\t\t\tInfo_Set (&cl->_userinfo_ctx_, \"name\", newname);\n\t\t\t\tval = Info_Get (&cl->_userinfo_ctx_, \"name\");\n\t\t\t}\n\t\t\telse\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (strncmp(val, cl->name, strlen(cl->name) + 1))\n\t\t{\n\t\t\tif (!cl->lastnametime || curtime - cl->lastnametime > 5) {\n\t\t\t\tcl->lastnamecount = 0;\n\t\t\t\tcl->lastnametime = curtime;\n\t\t\t}\n\t\t\telse if (cl->lastnamecount++ > 4) {\n\t\t\t\tSV_BroadcastPrintf(PRINT_HIGH, \"%s was kicked for name spamming\\n\", cl->name);\n\t\t\t\tSV_ClientPrintf(cl, PRINT_HIGH, \"You were kicked from the game for name spamming\\n\");\n\t\t\t\tSV_LogPlayer(cl, \"name spam\", 1); //bliP: player logging\n\t\t\t\tSV_DropClient(cl);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (cl->state >= cs_spawned && !cl->spectator) {\n\t\t\t\tSV_BroadcastPrintf(PRINT_HIGH, \"%s changed name to %s\\n\", cl->name, val);\n\t\t\t}\n\t\t}\n\n\t\tstrlcpy(cl->name, val, CLIENT_NAME_LEN);\n\n\t\tif (cl->state >= cs_spawned) //bliP: player logging\n\t\t\tSV_LogPlayer(cl, \"name change\", 1);\n\t}\n\n\t// team\n\tval = Info_Get (&cl->_userinfo_ctx_, \"team\");\n\tif (strstr(val, \"&c\") || strstr(val, \"&r\"))\n\t\tInfo_Set (&cl->_userinfo_ctx_, \"team\", \"none\");\n\tstrlcpy (cl->team, Info_Get (&cl->_userinfo_ctx_, \"team\"), sizeof(cl->team));\n\n\t// rate\n\tval = Info_Get (&cl->_userinfo_ctx_, cl->download ? \"drate\" : \"rate\");\n\tcl->netchan.rate = 1.0 / SV_BoundRate (cl->download != NULL, Q_atoi(*val ? val : \"99999\"));\n\n\t// s2c packet dupes\n\tval = Info_Get(&cl->_userinfo_ctx_, \"dupe\");\n\tcl->dupe = atoi(val);\n\tcl->dupe = (int)bound(0, cl->dupe, MAX_DUPLICATE_PACKETS); // 0=1 packet (aka: no dupes)\n\tcl->netchan.dupe = (cl->download ? 0 : cl->dupe);\n\n\t// message level\n\tval = Info_Get (&cl->_userinfo_ctx_, \"msg\");\n\tif (val[0])\n\t\tcl->messagelevel = Q_atoi(val);\n\n\t//spectator print\n\tval = Info_Get(&cl->_userinfo_ctx_, \"sp\");\n\tif (val[0])\n\t\tcl->spec_print = Q_atoi(val);\n}\n\n\n//============================================================================\n\nvoid OnChange_sysselecttimeout_var (cvar_t *var, char *value, qbool *cancel)\n{\n\tint t = Q_atoi (value);\n\n\tif (t < 1000 || t > 1000000)\n\t{\n\t\tCon_Printf(\"WARNING: sys_select_timeout can't be less then 1000 (1 millisecond) and more then 1 000 000 (1 second).\\n\");\n\t\t*cancel = true;\n\t\treturn;\n\t}\n}\n\n//bliP: 24/9 logdir ->\nvoid OnChange_logdir_var (cvar_t *var, char *value, qbool *cancel)\n{\n\tif (strstr(value, \"..\"))\n\t{\n\t\t*cancel = true;\n\t\treturn;\n\t}\t\n\n\tif (value[0])\n\t\tSys_mkdir (value);\n}\n//<-\n\n//bliP: admininfo ->\nvoid OnChange_admininfo_var (cvar_t *var, char *value, qbool *cancel)\n{\n\tif (value[0])\n\t\tInfo_SetValueForStarKey (svs.info, \"*admin\", value, MAX_SERVERINFO_STRING);\n\telse\n\t\tInfo_RemoveKey (svs.info, \"*admin\");\n}\n//<-\n\n//bliP: telnet log level ->\nvoid OnChange_telnetloglevel_var (cvar_t *var, char *value, qbool *cancel)\n{\n\tlogs[TELNET_LOG].log_level = Q_atoi(value);\n}\n//<-\nvoid OnChange_qconsolelogsay_var (cvar_t *var, char *value, qbool *cancel)\n{\n\tlogs[CONSOLE_LOG].log_level = Q_atoi(value);\n}\n\n#ifdef SERVERONLY\n\nvoid COM_Init (void)\n{\n\tCvar_Register (&developer);\n\tCvar_Register (&version);\n\tCvar_Register (&sys_simulation);\n\n\tCvar_SetROM(&version, SERVER_NAME \" \" SERVER_VERSION);\n}\n\n//Free hunk memory up to host_hunklevel\n//Can only be called when changing levels!\nvoid Host_ClearMemory (void)\n{\n\tif (!host_initialized)\n\t\tSys_Error (\"Host_ClearMemory before host initialized\");\n\n\tCM_InvalidateMap ();\n\n\t// any data previously allocated on hunk is no longer valid\n\tHunk_FreeToLowMark (host_hunklevel);\n}\n\n//memsize is the recommended amount of memory to use for hunk\nvoid Host_InitMemory (int memsize)\n{\n\tint t;\n\n\tif (SV_CommandLineUseMinimumMemory())\n\t\tmemsize = MINIMUM_MEMORY;\n\n\tif ((t = SV_CommandLineHeapSizeMemoryKB()) != 0 && t + 1 < COM_Argc())\n\t\tmemsize = Q_atoi (COM_Argv(t + 1)) * 1024;\n\n\tif ((t = SV_CommandLineHeapSizeMemoryMB()) != 0 && t + 1 < COM_Argc())\n\t\tmemsize = Q_atoi (COM_Argv(t + 1)) * 1024 * 1024;\n\n\tif (memsize < MINIMUM_MEMORY)\n\t\tSys_Error (\"Only %4.1f megs of memory reported, can't execute game\", memsize / (float)0x100000);\n\n\tMemory_Init (Q_malloc(memsize), memsize);\n}\n\nvoid Host_Init (int argc, char **argv, int default_memsize)\n{\n\textern int\t\thunk_size;\n\tcvar_t\t\t\t*v;\n\n\tsrand((unsigned)time(NULL));\n\n\tCOM_InitArgv (argc, argv);\n\n\tHost_InitMemory (default_memsize);\n\n\tCon_Printf (\"============= Starting %s =============\\n\", VersionStringFull());\n\n\tCbuf_Init ();\n\tCmd_Init ();\n\tCvar_Init ();\n\tCOM_Init ();\n\n\tFS_Init ();\n\tNET_Init ();\n\n\tNetchan_Init ();\n\n\tSys_Init ();\n\tCM_Init ();\n\n\tSV_Init ();\n\n\tHunk_AllocName (0, \"-HOST_HUNKLEVEL-\");\n\thost_hunklevel = Hunk_LowMark ();\n\n\thost_initialized = true;\n\n\t// walk through all vars and forse OnChange event if cvar was modified,\n\t// also apply that to variables which mirrored in userinfo because of cl_parsefunchars was't applyed as this moment,\n\t// same for serverinfo and may be this fix something also.\n\tfor ( v = NULL; (v = Cvar_Next ( v )); )\n\t{\n\t\tif ( Cvar_GetFlags( v ) & (CVAR_ROM) )\n\t\t\tcontinue;\n\n\t\tCvar_Set(v, v->string);\n\t}\n\n\tCon_Printf (\"%4.1f megabyte heap\\n\", (float)hunk_size / (1024 * 1024));\n\tCon_Printf (\"QuakeWorld Initialized\\n\");\n#ifndef\tWWW_INTEGRATION\n\tCon_Printf (\"www authentication disabled (no curl support)\\n\");\n#endif\n\n\tCbuf_InsertText (\"exec server.cfg\\n\");\n\n\t// process command line arguments\n\tCmd_StuffCmds_f ();\n\tCbuf_Execute ();\n\n\thost_everything_loaded = true;\n\n\tSV_Map(true);\n\n\t// if a map wasn't specified on the command line, spawn mvdsv-kg map\n\tif (sv.state == ss_dead)\n\t{\n\t\tCmd_ExecuteString (\"map mvdsv-kg\");\n\t\tSV_Map(true);\n\t}\n\n\t// last resort - start map\n\tif (sv.state == ss_dead)\n\t{\n\t\tCmd_ExecuteString (\"map start\");\n\t\tSV_Map(true);\n\t}\n\n\tif (sv.state == ss_dead)\n\t\tSV_Error (\"Couldn't spawn a server\");\n\n#if defined (_WIN32) && !defined(_CONSOLE)\n\t{\n\t\tvoid SetWindowText_(char*);\n\t\tSetWindowText_(va(SERVER_NAME \":%d - QuakeWorld server\", NET_UDPSVPort()));\n\t}\n#endif\n}\n\n#endif // SERVERONLY\n\n/*\n====================\nSV_Init\n====================\n*/\n\nvoid SV_Init (void)\n{\n\tmemset(&_localinfo_, 0, sizeof(_localinfo_));\n\t_localinfo_.max = MAX_LOCALINFOS;\n\n\tPR_Init ();\n\n\t// send immediately\n\tsvs.last_heartbeat = -99999;\n\n\tSV_InitLocal ();\n\n\tSV_MVDInit ();\n\tLogin_Init ();\n#ifndef SERVERONLY\n\tserver_cfg_done = true;\n#endif\n\n#if defined(SERVERONLY) && defined(WWW_INTEGRATION)\n\tCentral_Init ();\n#endif\n}\n\n/*\n============\nSV_TimeOfDay\n============\n*/\nvoid SV_TimeOfDay(date_t *date, char *timeformat)\n{\n\tstruct tm *newtime;\n\ttime_t long_time;\n\n\ttime(&long_time);\n\tnewtime = localtime(&long_time);\n\n\t//bliP: date check ->\n\tif (!newtime)\n\t{\n\t\tdate->day = 0;\n\t\tdate->mon = 0;\n\t\tdate->year = 0;\n\t\tdate->hour = 0;\n\t\tdate->min = 0;\n\t\tdate->sec = 0;\n\t\tstrlcpy(date->str, \"#bad date#\", sizeof(date->str));\n\t\treturn;\n\t}\n\t//<-\n\n\tdate->day = newtime->tm_mday;\n\tdate->mon = newtime->tm_mon;\n\tdate->year = newtime->tm_year + 1900;\n\tdate->hour = newtime->tm_hour;\n\tdate->min = newtime->tm_min;\n\tdate->sec = newtime->tm_sec;\n\tstrftime(date->str, sizeof(date->str)-1, timeformat, newtime);\n}\n\n//bliP: player logging ->\n/*\n============\nSV_LogPlayer\n============\n*/\nvoid SV_LogPlayer(client_t *cl, char *msg, int level)\n{\n\tchar info[MAX_EXT_INFO_STRING];\n\tchar name[CLIENT_NAME_LEN];\n\n\tInfo_ReverseConvert(&cl->_userinfo_ctx_, info, sizeof(info));\n\tQ_normalizetext(info);\n\tstrlcpy(name, cl->name, sizeof(name));\n\tQ_normalizetext(name);\n\n\tSV_Write_Log(PLAYER_LOG, level,\n\t             va(\"%s\\\\%s\\\\%i\\\\%s\\\\%s\\\\%i%s\\n\",\n\t                msg,\n\t                name,\n\t                cl->userid,\n\t                NET_BaseAdrToString(cl->netchan.remote_address),\n\t                NET_BaseAdrToString(cl->realip),\n\t                cl->netchan.remote_address.port,\n\t                info\n\t               )\n\t            );\n}\n\n/*\n============\nSV_Write_Log\n============\n*/\nvoid SV_Write_Log(int sv_log, int level, char *msg)\n{\n\tstatic date_t date;\n\tchar *log_msg, *error_msg;\n\n\tif (!(logs[sv_log].sv_logfile && *msg))\n\t\treturn;\n\n\tif (logs[sv_log].log_level < level)\n\t\treturn;\n\n\tSV_TimeOfDay(&date, \"%a %b %d, %H:%M:%S %Y\");\n\n\tswitch (sv_log)\n\t{\n\tcase FRAG_LOG:\n\tcase MOD_FRAG_LOG:\n\t\tlog_msg = msg; // these logs aren't in fs_gamedir\n\t\terror_msg = va(\"Can't write in %s log file: \"/*%s/ */\"%sN.log.\\n\",\n\t\t               /*fs_gamedir,*/ logs[sv_log].message_on,\n\t\t               logs[sv_log].file_name);\n\t\tbreak;\n\tdefault:\n\t\tlog_msg = va(\"[%s].[%d] %s\", date.str, level, msg);\n\t\terror_msg = va(\"Can't write in %s log file: \"/*%s/ */\"%s%i.log.\\n\",\n\t\t               /*fs_gamedir,*/ logs[sv_log].message_on,\n\t\t               logs[sv_log].file_name, NET_UDPSVPort());\n\t}\n\n\tif (fprintf(logs[sv_log].sv_logfile, \"%s\", log_msg) < 0)\n\t{\n\t\t//bliP: Sys_Error to Con_DPrintf ->\n\t\t//VVD: Con_DPrintf to Sys_Printf ->\n\t\tSys_Printf(\"%s\", error_msg);\n\t\t//<-\n\t\tSV_Logfile(sv_log, false);\n\t}\n\telse\n\t{\n\t\tfflush(logs[sv_log].sv_logfile);\n\t\tif ((int)sv_maxlogsize.value &&\n\t\t        (FS_FileLength(logs[sv_log].sv_logfile) > (int)sv_maxlogsize.value))\n\t\t{\n\t\t\tSV_Logfile(sv_log, true);\n\t\t}\n\t}\n}\n\n/*\n============\nSys_compare_by functions for sort files in list\n============\n*/\nint Sys_compare_by_date (const void *a, const void *b)\n{\n\treturn (int)(((file_t *)a)->time - ((file_t *)b)->time);\n}\n\nint Sys_compare_by_name (const void *a, const void *b)\n{\n\treturn strncmp(((file_t *)a)->name, ((file_t *)b)->name, MAX_DEMO_NAME);\n}\n\n// real-world time passed\ndouble SV_ClientConnectedTime(client_t* client)\n{\n\tif (!client->connection_started_curtime) {\n\t\treturn 0;\n\t}\n\treturn curtime - client->connection_started_curtime;\n}\n\n// affected by pause\ndouble SV_ClientGameTime(client_t* client)\n{\n\tif (!client->connection_started_realtime) {\n\t\treturn 0;\n\t}\n\n\treturn realtime - client->connection_started_realtime;\n}\n\nvoid SV_SetClientConnectionTime(client_t* client)\n{\n\tclient->connection_started_realtime = realtime;\n\tclient->connection_started_curtime = curtime;\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_master.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    \n*/\n// sv_master.c - send heartbeats to master server\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\n#define HEARTBEAT_SECONDS 300\n\nstatic netadr_t master_adr[MAX_MASTERS]; // address of group servers\n\n/*\n====================\nSV_SetMaster_f\n\nMake a master server current\n====================\n*/\nvoid SV_SetMaster_f (void)\n{\n\tchar data[2];\n\tint i;\n\n\tmemset (&master_adr, 0, sizeof(master_adr));\n\n\tfor (i=1 ; i<Cmd_Argc() ; i++)\n\t{\n\t\tif (!strcmp(Cmd_Argv(i), \"none\") || !NET_StringToAdr (Cmd_Argv(i), &master_adr[i-1]))\n\t\t{\n\t\t\tCon_Printf (\"Setting nomaster mode.\\n\");\n\t\t\treturn;\n\t\t}\n\t\tif (master_adr[i-1].port == 0)\n\t\t\tmaster_adr[i-1].port = BigShort (27000);\n\n\t\tCon_Printf (\"Master server at %s\\n\", NET_AdrToString (master_adr[i-1]));\n\n\t\tCon_Printf (\"Sending a ping.\\n\");\n\n\t\tdata[0] = A2A_PING;\n\t\tdata[1] = 0;\n\t\tNET_SendPacket (NS_SERVER, 2, data, master_adr[i-1]);\n\t}\n\n\tsvs.last_heartbeat = -99999;\n}\n\n/*\n==================\nSV_Heartbeat_f\n==================\n*/\nvoid SV_Heartbeat_f (void)\n{\n\tsvs.last_heartbeat = -99999;\n}\n\n/*\n================\nMaster_Heartbeat\n\nSend a message to the master every few minutes to\nlet it know we are alive, and log information\n================\n*/\nvoid Master_Heartbeat (void)\n{\n\tchar string[2048];\n\tint active;\n\tint i;\n\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\tif (realtime - svs.last_heartbeat < HEARTBEAT_SECONDS)\n\t\treturn; // not time to send yet\n\n\tsvs.last_heartbeat = realtime;\n\n\t//\n\t// count active users\n\t//\n\tactive = 0;\n\tfor (i=0 ; i<MAX_CLIENTS ; i++)\n\t\tif (svs.clients[i].state == cs_connected ||\n\t\t\t\tsvs.clients[i].state == cs_spawned )\n\t\t\tactive++;\n\n\tsvs.heartbeat_sequence++;\n\tsnprintf (string, sizeof(string), \"%c\\n%i\\n%i\\n\", S2M_HEARTBEAT,\n\t\t  svs.heartbeat_sequence, active);\n\n\n\t// send to group master\n\tfor (i=0 ; i<MAX_MASTERS ; i++)\n\t\tif (master_adr[i].port)\n\t\t{\n\t\t\tCon_Printf (\"Sending heartbeat to %s\\n\", NET_AdrToString (master_adr[i]));\n\t\t\tNET_SendPacket (NS_SERVER, strlen(string), string, master_adr[i]);\n\t\t}\n}\n\n/*\n=================\nMaster_Shutdown\n\nInforms all masters that this server is going down\n=================\n*/\nvoid Master_Shutdown (void)\n{\n\tchar string[2048];\n\tint i;\n\n\tsnprintf (string, sizeof(string), \"%c\\n\", S2M_SHUTDOWN);\n\n\t// send to group master\n\tfor (i=0 ; i<MAX_MASTERS ; i++)\n\t\tif (master_adr[i].port)\n\t\t{\n\t\t\tCon_Printf (\"Sending heartbeat to %s\\n\", NET_AdrToString (master_adr[i]));\n\t\t\tNET_SendPacket (NS_SERVER, strlen(string), string, master_adr[i]);\n\t\t}\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_mod_frags.c",
    "content": "/*\n * mvdsv_mod_frags.c\n * main mod_frags file\n * cases all functions in \"stdout\" form\n * to use it at console\n * and main function with example of using\n * (c) kreon 2005\n * Idea by gLAd\n *\n */\n/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n \nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n \nSee the GNU General Public License for more details.\n \nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n \n\t\n*/\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n#ifndef SERVERONLY\n#include \"pcre2.h\"\n#endif\n#include \"sv_mod_frags.h\"\n\nqwmsg_t *qwmsg[MOD_MSG_MAX + 1];\nstatic qbool qwm_static = true;\n\nvoid free_qwmsg_t(qwmsg_t **qwmsg1)\n{\n\tint i;\n\n\tif (!qwm_static) {\n\t\tfor (i = 0; qwmsg1[i]; i++) {\n\t\t\tQ_free(qwmsg1[i]->str);\n\t\t\tQ_free(qwmsg1[i]);\n\t\t}\n\t}\n}\n\nvoid sv_mod_msg_file_OnChange(cvar_t *cvar, char *value, qbool *cancel)\n{\n\tFILE *fp = NULL;\n\tchar *str_tok, buf[128];\n\tsize_t len;\n\tint i;\n\n\tfree_qwmsg_t(qwmsg);\n\n\tif (value[0])\n\t\tfp = fopen(value, \"r\");\n\n\tif (fp == NULL)\n\t{\n\t\tif (value[0])\n\t\t\tCon_Printf(\"WARNING: sv_mod_msg_file_OnChange: can't open file %s.\\n\", value);\n\n\t\tfor (i = 0; i < MOD_MSG_MAX && qwmsg_def[i].str; i++) {\n\t\t\tqwmsg[i] = &qwmsg_def[i];\n\t\t}\n\t\tqwm_static = true;\n\t\tCon_DPrintf(\"Initialized default mod messages.\\nTotal: %d messages.\\n\", i);\n\t}\n\telse\n\t{\n\t\tfor (i = 0; i < MOD_MSG_MAX && !feof(fp); i++)\n\t\t{\n\t\t\tif (fgets(buf, sizeof(buf), fp))\n\t\t\t{\n\t\t\t\tqwmsg[i] = (qwmsg_t *) Q_malloc (sizeof(qwmsg_t));\n\t\t\t\t// fill system_id\n\t\t\t\tstr_tok = (char *)strtok(buf, \"#\");\n\t\t\t\tqwmsg[i]->msg_type = Q_atoi(str_tok);\n\t\t\t\t// fill weapon_id\n\t\t\t\tstr_tok = (char *)strtok(NULL, \"#\");\n\t\t\t\tqwmsg[i]->id = Q_atoi(str_tok);\n\t\t\t\t// fill pl_count\n\t\t\t\tstr_tok = (char *)strtok(NULL, \"#\");\n\t\t\t\tqwmsg[i]->pl_count = Q_atoi(str_tok) == 1 ? 1 : 2;\n\t\t\t\t// fill reverse\n\t\t\t\tstr_tok = (char *)strtok(NULL, \"#\");\n\t\t\t\tqwmsg[i]->reverse = Q_atoi(str_tok) ? true : false;\n\t\t\t\t// fill str\n\t\t\t\tstr_tok = (char *)strtok(NULL, \"#\");\n\n\t\t\t\tlen = strlen (str_tok) + 1;\n\t\t\t\tqwmsg[i]->str =  (char *) Q_malloc (len);\n\t\t\t\tstrlcpy(qwmsg[i]->str, str_tok, len);\n\t\t\t}\n\t\t\telse\n\t\t\t\tbreak;\n\t\t\t//            Sys_Printf(\"msg_type = %d, id = %d, pl_count = %d, str = %s, reverse = %d\\n\",\n\t\t\t//\tqwmsg[i]->msg_type, qwmsg[i]->id, qwmsg[i]->pl_count, qwmsg[i]->str, qwmsg[i]->reverse);\n\t\t}\n\t\tqwm_static = false;\n\t\tCon_DPrintf(\"Initialized mod messages from file %s.\\nTotal: %d messages.\\n\", value, i);\n\t\tfclose(fp);\n\t}\n\tqwmsg[i] = NULL;\n\t*cancel = false;\n}\n\nconst char **qwmsg_pcre_check(const char *str, const char *qwm_str, int str_len)\n{\n\tpcre2_code *reg;\n\tint error;\n\tPCRE2_SIZE error_offset = 0;\n\tconst char **buf = NULL;\n\tint stringcount;\n\tpcre2_match_data *match_data = NULL;\n\n\tif (!(reg = pcre2_compile((PCRE2_SPTR)qwm_str, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL)))\n\t{\n\t\tPCRE2_UCHAR error_str[256];\n\t\tpcre2_get_error_message(error, error_str, sizeof(error_str));\n\t\tSys_Printf(\"WARNING: qwmsg_pcre_check: pcre2_compile(%s) error %s\\n\", qwm_str, error_str);\n\t\treturn NULL;\n\t}\n\n\tmatch_data = pcre2_match_data_create_from_pattern(reg, NULL);\n\tstringcount = pcre2_match(reg, (PCRE2_SPTR)str, str_len, 0, 0, match_data, NULL);\n\n\tif (stringcount < 0) {\n\t\tpcre2_match_data_free (match_data);\n\t\tpcre2_code_free(reg);\n\t\treturn NULL;\n\t}\n\n\tpcre2_substring_list_get(match_data, (PCRE2_UCHAR8***)&buf, NULL);\n\tpcre2_match_data_free (match_data);\n\tpcre2_code_free(reg);\n\treturn buf;\n}\n\n// main function\nchar *parse_mod_string(char *str)\n{\n\tconst char **buf;\n\tint i, str_len = strlen(str);\n\tchar *ret = NULL;\n\tfor (i = 0; qwmsg[i]; i++)\n\t{\n\t\tif ((buf = qwmsg_pcre_check(str, qwmsg[i]->str, str_len)))\n\t\t{\n\t\t\tint pl1, pl2;\n\t\t\tswitch (qwmsg[i]->msg_type)\n\t\t\t{\n\t\t\tcase WEAPON:\n\t\t\t\tpl1 = pl2 = 1;\n\t\t\t\tswitch (qwmsg[i]->pl_count)\n\t\t\t\t{\n\t\t\t\tcase 2:\n\t\t\t\t\tpl2 += qwmsg[i]->reverse;\n\t\t\t\t\tpl1 = 3 - pl2;\n\t\t\t\tcase 1:\n\t\t\t\t\tstr_len = strlen(buf[pl1]) + strlen(buf[pl2]) + strlen(qw_weapon[qwmsg[i]->id]) + 5 + 10;\n\t\t\t\t\tret = (char *) Q_malloc (str_len);\n\t\t\t\t\tsnprintf(ret, str_len, \"%s\\\\%s\\\\%s\\\\%d\\n\", buf[pl1], buf[pl2], qw_weapon[qwmsg[i]->id], (int)time(NULL));\n\t\t\t\t\tbreak;\n\t\t\t\tdefault: ret = NULL;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase SYSTEM:\n\t\t\t\tstr_len = strlen(buf[1]) * 2 + strlen(qw_system[qwmsg[i]->id]) + 4 + 10;\n\t\t\t\tret = (char *) Q_malloc (str_len);\n\t\t\t\tsnprintf(ret, str_len, \"%s\\\\%s\\\\%d\\n\", buf[1], qw_system[qwmsg[i]->id], (int)time(NULL));\n\t\t\t\tbreak;\n\t\t\tdefault: ret = NULL;\n\t\t\t}\n\t\t\tpcre2_substring_list_free((const PCRE2_UCHAR8**)buf);\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn ret;\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_mod_frags.h",
    "content": "/*\n * sv_mod_frags.h\n * QuakeWorld message definitions\n * For glad & vvd\n * (C) kreon 2005\n * Messages from fuhquake's fragfile.dat\n */\n/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifndef __SV_MOD_FRAGS__\n#define __SV_MOD_FRAGS__\n\nextern cvar_t\tsv_mod_msg_file;\n#define MOD_MSG_MAX\t512\n// weapons definitions\n\nchar *qw_weapon[] = {\n    \"die\", \n    \"axe\",\n    \"sg\",\n    \"ssg\",\n    \"ng\",\n    \"sng\",\n    \"gl\",\n    \"rl\",\n    \"lg\",\n    \"rail\",\n    \"drown\",\n    \"trap\",\n    \"tele\",\n    \"dschrg\",\n    \"squish\",\n    \"fall\",\n    \"team\",\n    \"stomps\"\n};\n// system messages (not released yet) definitions\nchar *qw_system[] = {\n    \"start\",\n    \"end\",\n    \"connect\",\n    \"disconnect\",\n    \"timeout\"\n};\n// main struct for mod's messages searching\ntypedef struct qw_message\n{\n    int msg_type; // type of message \n    int id; // id of weapon id for WEAPON\n    int pl_count; // count of players in each message\n    char *str; // pointer to string\n    qbool reverse; // reversing message? (a->b or b->a)\n} qwmsg_t;\n// messages types\nenum {\tMIN_TYPE = 0, WEAPON = 0, SYSTEM, MAX_TYPE};\n\n// DEFAULT mod messages\n// From fuhquake's fragfile.dat\n\nqwmsg_t qwmsg_def[] =\n{\n    {WEAPON, 10, 1, \"(.*) sleeps with the fishes\", false},\n    {WEAPON, 10, 1, \"(.*) sucks it down\", false},\n    {WEAPON, 10, 1, \"(.*) gulped a load of slime\", false},\n    {WEAPON, 10, 1, \"(.*) can't exist on slime alone\", false},\n    {WEAPON, 10, 1, \"(.*) burst into flames\", false},\n    {WEAPON, 10, 1, \"(.*) turned into hot slag\", false},\n    {WEAPON, 10, 1, \"(.*) visits the Volcano God\", false},\n    {WEAPON, 15, 1, \"(.*) cratered\", false},\n    {WEAPON, 15, 1, \"(.*) fell to his death\", false},\n    {WEAPON, 15, 1, \"(.*) fell to her death\", false},\n    {WEAPON, 11, 1, \"(.*) blew up\", false},\n    {WEAPON, 11, 1, \"(.*) was spiked\", false},\n    {WEAPON, 11, 1, \"(.*) was zapped\", false},\n    {WEAPON, 11, 1, \"(.*) ate a lavaball\", false},\n    {WEAPON, 12, 1, \"(.*) was telefragged by his teammate\", false},\n    {WEAPON, 12, 1, \"(.*) was telefragged by her teammate\", false},\n    {WEAPON,  0, 1, \"(.*) died\", false},\n    {WEAPON,  0, 1, \"(.*) tried to leave\", false},\n    {WEAPON, 14, 1, \"(.*) was squished\", false},\n    {WEAPON,  0, 1, \"(.*) suicides\", false},\n    {WEAPON,  6, 1, \"(.*) tries to put the pin back in\", false},\n    {WEAPON,  7, 1, \"(.*) becomes bored with life\", false},\n    {WEAPON,  7, 1, \"(.*) discovers blast radius\", false},\n    {WEAPON, 13, 1, \"(.*) electrocutes himself.\", false},\n    {WEAPON, 13, 1, \"(.*) electrocutes herself.\", false},\n    {WEAPON, 13, 1, \"(.*) discharges into the slime\", false},\n    {WEAPON, 13, 1, \"(.*) discharges into the lava\", false},\n    {WEAPON, 13, 1, \"(.*) discharges into the water\", false},\n    {WEAPON, 13, 1, \"(.*) heats up the water\", false},\n    {WEAPON, 16, 1, \"(.*) squished a teammate\", false},\n    {WEAPON, 16, 1, \"(.*) mows down a teammate\", false},\n    {WEAPON, 16, 1, \"(.*) checks his glasses\", false},\n    {WEAPON, 16, 1, \"(.*) checks her glasses\", false},\n    {WEAPON, 16, 1, \"(.*) gets a frag for the other team\", false},\n    {WEAPON, 16, 1, \"(.*) loses another friend\", false},\n    {WEAPON,  1, 2, \"(.*) was ax-murdered by (.*)\", false},\n    {WEAPON,  2, 2, \"(.*) was lead poisoned by (.*)\", false},\n    {WEAPON,  2, 2, \"(.*) chewed on (.*)'s boomstick\", false},\n    {WEAPON,  3, 2, \"(.*) ate 8 loads of (.*)'s buckshot\", false},\n    {WEAPON,  3, 2, \"(.*) ate 2 loads of (.*)'s buckshot\", false},\n    {WEAPON,  4, 2, \"(.*) was body pierced by (.*)\", false},\n    {WEAPON,  4, 2, \"(.*) was nailed by (.*)\", false},\n    {WEAPON,  5, 2, \"(.*) was perforated by (.*)\", false},\n    {WEAPON,  5, 2, \"(.*) was punctured by (.*)\", false},\n    {WEAPON,  5, 2, \"(.*) was ventilated by (.*)\", false},\n    {WEAPON,  5, 2, \"(.*) was straw-cuttered by (.*)\", false},\n    {WEAPON,  6, 2, \"(.*) eats (.*)'s pineapple\", false},\n    {WEAPON,  6, 2, \"(.*) was gibbed by (.*)'s grenade\", false},\n    {WEAPON,  7, 2, \"(.*) was smeared by (.*)'s quad rocket\", false},\n    {WEAPON,  7, 2, \"(.*) was brutalized by (.*)'s quad rocket\", false},\n    {WEAPON,  7, 2, \"(.*) rips (.*) a new one\", true},\n    {WEAPON,  7, 2, \"(.*) was gibbed by (.*)'s rocket\", false},\n    {WEAPON,  7, 2, \"(.*) rides (.*)'s rocket\", false},\n    {WEAPON,  8, 2, \"(.*) accepts (.*)'s shaft\", false},\n    {WEAPON,  9, 2, \"(.*) was railed by (.*)\", false},\n    {WEAPON, 12, 2, \"(.*) was telefragged by (.*)\", false},\n    {WEAPON, 14, 2, \"(.*) squishes (.*)\", true},\n    {WEAPON, 13, 2, \"(.*) accepts (.*)'s discharge\", false},\n    {WEAPON, 13, 2, \"(.*) drains (.*)'s batteries\", false},\n    {WEAPON,  8, 2, \"(.*) gets a natural disaster from (.*)\", false},\n    {     0,  0, 0, NULL, 0}\n};\n// end\n\n#endif /* !__SV_MOD_FRAGS__ */\n"
  },
  {
    "path": "src/sv_move.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n \nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n \nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n \nSee the GNU General Public License for more details.\n \nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n \n\t\n*/\n// sv_move.c -- monster movement\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\n#define\tSTEPSIZE\t18\n\n/*\n=============\nSV_CheckBottom\n \nReturns false if any part of the bottom of the entity is off an edge that\nis not a staircase.\n \n=============\n*/\nqbool SV_CheckBottom (edict_t *ent)\n{\n\tvec3_t\tmins, maxs, start, stop;\n\ttrace_t\ttrace;\n\tint\t\tx, y;\n\tfloat\tmid, bottom;\n\n\tVectorAdd (ent->v->origin, ent->v->mins, mins);\n\tVectorAdd (ent->v->origin, ent->v->maxs, maxs);\n\n\t// if all of the points under the corners are solid world, don't bother\n\t// with the tougher checks\n\t// the corners must be within 16 of the midpoint\n\tstart[2] = mins[2] - 1;\n\tfor\t(x=0 ; x<=1 ; x++)\n\t\tfor\t(y=0 ; y<=1 ; y++)\n\t\t{\n\t\t\tstart[0] = x ? maxs[0] : mins[0];\n\t\t\tstart[1] = y ? maxs[1] : mins[1];\n\t\t\tif (SV_PointContents (start) != CONTENTS_SOLID)\n\t\t\t\tgoto realcheck;\n\t\t}\n\treturn true;\t\t// we got out easy\n\nrealcheck:\n\t//\n\t// check it for real...\n\t//\n\tstart[2] = mins[2];\n\n\t// the midpoint must be within 16 of the bottom\n\tstart[0] = stop[0] = (mins[0] + maxs[0])*0.5;\n\tstart[1] = stop[1] = (mins[1] + maxs[1])*0.5;\n\tstop[2] = start[2] - 2*STEPSIZE;\n\ttrace = SV_Trace (start, vec3_origin, vec3_origin, stop, true, ent);\n\n\tif (trace.fraction == 1.0)\n\t\treturn false;\n\tmid = bottom = trace.endpos[2];\n\n\t// the corners must be within 16 of the midpoint\n\tfor\t(x=0 ; x<=1 ; x++)\n\t\tfor\t(y=0 ; y<=1 ; y++)\n\t\t{\n\t\t\tstart[0] = stop[0] = x ? maxs[0] : mins[0];\n\t\t\tstart[1] = stop[1] = y ? maxs[1] : mins[1];\n\n\t\t\ttrace = SV_Trace (start, vec3_origin, vec3_origin, stop, true, ent);\n\n\t\t\tif (trace.fraction != 1.0 && trace.endpos[2] > bottom)\n\t\t\t\tbottom = trace.endpos[2];\n\t\t\tif (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE)\n\t\t\t\treturn false;\n\t\t}\n\treturn true;\n}\n\n\n/*\n=============\nSV_movestep\n \nCalled by monster program code.\nThe move will be adjusted for slopes and stairs, but if the move isn't\npossible, no move is done, false is returned, and\npr_global_struct->trace_normal is set to the normal of the blocking wall\n=============\n*/\nqbool SV_movestep (edict_t *ent, vec3_t move, qbool relink)\n{\n\tfloat\t\tdz;\n\tvec3_t\t\toldorg, neworg, end;\n\ttrace_t\t\ttrace;\n\tint\t\t\ti;\n\tedict_t\t\t*enemy;\n\n\t// try the move\n\tVectorCopy (ent->v->origin, oldorg);\n\tVectorAdd (ent->v->origin, move, neworg);\n\n\t// flying monsters don't step up\n\tif ( (int)ent->v->flags & (FL_SWIM | FL_FLY) )\n\t{\n\t\t// try one move with vertical motion, then one without\n\t\tfor (i=0 ; i<2 ; i++)\n\t\t{\n\t\t\tVectorAdd (ent->v->origin, move, neworg);\n\t\t\tenemy = PROG_TO_EDICT(ent->v->enemy);\n\t\t\tif (i == 0 && enemy != sv.edicts)\n\t\t\t{\n\t\t\t\tdz = ent->v->origin[2] - PROG_TO_EDICT(ent->v->enemy)->v->origin[2];\n\t\t\t\tif (dz > 40)\n\t\t\t\t\tneworg[2] -= 8;\n\t\t\t\tif (dz < 30)\n\t\t\t\t\tneworg[2] += 8;\n\t\t\t}\n\t\t\ttrace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, neworg, false, ent);\n\n\t\t\tif (trace.fraction == 1)\n\t\t\t{\n\t\t\t\tif ( ((int)ent->v->flags & FL_SWIM) && SV_PointContents(trace.endpos) == CONTENTS_EMPTY )\n\t\t\t\t\treturn false;\t// swim monster left water\n\n\t\t\t\tVectorCopy (trace.endpos, ent->v->origin);\n\t\t\t\tif (relink)\n\t\t\t\t\tSV_LinkEdict (ent, true);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (enemy == sv.edicts)\n\t\t\t\tbreak;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t// push down from a step height above the wished position\n\tneworg[2] += STEPSIZE;\n\tVectorCopy (neworg, end);\n\tend[2] -= STEPSIZE*2;\n\n\ttrace = SV_Trace (neworg, ent->v->mins, ent->v->maxs, end, false, ent);\n\n\tif (trace.allsolid)\n\t\treturn false;\n\n\tif (trace.startsolid)\n\t{\n\t\tneworg[2] -= STEPSIZE;\n\t\ttrace = SV_Trace (neworg, ent->v->mins, ent->v->maxs, end, false, ent);\n\t\tif (trace.allsolid || trace.startsolid)\n\t\t\treturn false;\n\t}\n\tif (trace.fraction == 1)\n\t{\n\t\t// if monster had the ground pulled out, go ahead and fall\n\t\tif ( (int)ent->v->flags & FL_PARTIALGROUND )\n\t\t{\n\t\t\tVectorAdd (ent->v->origin, move, ent->v->origin);\n\t\t\tif (relink)\n\t\t\t\tSV_LinkEdict (ent, true);\n\t\t\tent->v->flags = (int)ent->v->flags & ~FL_ONGROUND;\n\t\t\t//\tCon_Printf (\"fall down\\n\");\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\t\t// walked off an edge\n\t}\n\n\t// check point traces down for dangling corners\n\tVectorCopy (trace.endpos, ent->v->origin);\n\n\tif (!SV_CheckBottom (ent))\n\t{\n\t\tif ( (int)ent->v->flags & FL_PARTIALGROUND )\n\t\t{\t// entity had floor mostly pulled out from underneath it\n\t\t\t// and is trying to correct\n\t\t\tif (relink)\n\t\t\t\tSV_LinkEdict (ent, true);\n\t\t\treturn true;\n\t\t}\n\t\tVectorCopy (oldorg, ent->v->origin);\n\t\treturn false;\n\t}\n\n\tif ( (int)ent->v->flags & FL_PARTIALGROUND )\n\t{\n\t\t//\t\tCon_Printf (\"back on ground\\n\");\n\t\tent->v->flags = (int)ent->v->flags & ~FL_PARTIALGROUND;\n\t}\n\tent->v->groundentity = EDICT_TO_PROG(trace.e.ent);\n\n\t// the move is ok\n\tif (relink)\n\t\tSV_LinkEdict (ent, true);\n\treturn true;\n}\n\n\n//============================================================================\n\n/*\n======================\nSV_StepDirection\n \nTurns to the movement direction, and walks the current distance if\nfacing it.\n \n======================\n*/\nvoid PF_changeyaw (void);\nqbool SV_StepDirection (edict_t *ent, float yaw, float dist)\n{\n\tvec3_t\t\tmove, oldorigin;\n\tfloat\t\tdelta;\n\n\tent->v->ideal_yaw = yaw;\n\n\tPF_changeyaw(); // OUCH OUCH: its relay on what ent == self ? I'm not even mention about PR2...\n\n\tyaw = yaw*M_PI*2 / 360;\n\tmove[0] = cos(yaw)*dist;\n\tmove[1] = sin(yaw)*dist;\n\tmove[2] = 0;\n\n\tVectorCopy (ent->v->origin, oldorigin);\n\tif (SV_movestep (ent, move, false))\n\t{\n\t\tdelta = ent->v->angles[YAW] - ent->v->ideal_yaw;\n\t\tif (delta > 45 && delta < 315)\n\t\t{\t\t// not turned far enough, so don't take the step\n\t\t\tVectorCopy (oldorigin, ent->v->origin);\n\t\t}\n\t\tSV_LinkEdict (ent, true);\n\t\treturn true;\n\t}\n\tSV_LinkEdict (ent, true);\n\n\treturn false;\n}\n\n/*\n======================\nSV_FixCheckBottom\n \n======================\n*/\nvoid SV_FixCheckBottom (edict_t *ent)\n{\n\t//\tCon_Printf (\"SV_FixCheckBottom\\n\");\n\n\tent->v->flags = (int)ent->v->flags | FL_PARTIALGROUND;\n}\n\n\n\n/*\n================\nSV_NewChaseDir\n \n================\n*/\n#define\tDI_NODIR\t-1\nvoid SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist)\n{\n\tfloat\t\tdeltax,deltay;\n\tfloat\t\t\td[3];\n\tfloat\t\ttdir, olddir, turnaround;\n\n\tolddir = anglemod( (int)(actor->v->ideal_yaw/45)*45 );\n\tturnaround = anglemod(olddir - 180);\n\n\tdeltax = enemy->v->origin[0] - actor->v->origin[0];\n\tdeltay = enemy->v->origin[1] - actor->v->origin[1];\n\tif (deltax>10)\n\t\td[1]= 0;\n\telse if (deltax<-10)\n\t\td[1]= 180;\n\telse\n\t\td[1]= DI_NODIR;\n\tif (deltay<-10)\n\t\td[2]= 270;\n\telse if (deltay>10)\n\t\td[2]= 90;\n\telse\n\t\td[2]= DI_NODIR;\n\n\t// try direct route\n\tif (d[1] != DI_NODIR && d[2] != DI_NODIR)\n\t{\n\t\tif (d[1] == 0)\n\t\t\ttdir = d[2] == 90 ? 45 : 315;\n\t\telse\n\t\t\ttdir = d[2] == 90 ? 135 : 215;\n\n\t\tif (tdir != turnaround && SV_StepDirection(actor, tdir, dist))\n\t\t\treturn;\n\t}\n\n\t// try other directions\n\tif ( ((rand()&3) & 1) || fabs(deltay) > fabs(deltax))\n\t{\n\t\ttdir=d[1];\n\t\td[1]=d[2];\n\t\td[2]=tdir;\n\t}\n\n\tif (d[1] != DI_NODIR && d[1] != turnaround && SV_StepDirection(actor, d[1], dist))\n\t\treturn;\n\n\tif (d[2] != DI_NODIR && d[2] != turnaround && SV_StepDirection(actor, d[2], dist))\n\t\treturn;\n\n\t/* there is no direct path to the player, so pick another direction */\n\n\tif (olddir != DI_NODIR && SV_StepDirection(actor, olddir, dist))\n\t\treturn;\n\n\tif (rand()&1) \t/*randomly determine direction of search*/\n\t{\n\t\tfor (tdir=0 ; tdir<=315 ; tdir += 45)\n\t\t\tif (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )\n\t\t\t\treturn;\n\t}\n\telse\n\t{\n\t\tfor (tdir=315 ; tdir >=0 ; tdir -= 45)\n\t\t\tif (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )\n\t\t\t\treturn;\n\t}\n\n\tif (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) )\n\t\treturn;\n\n\tactor->v->ideal_yaw = olddir;\t\t// can't move\n\n\t// if a bridge was pulled out from underneath a monster, it may not have\n\t// a valid standing position at all\n\n\tif (!SV_CheckBottom (actor))\n\t\tSV_FixCheckBottom (actor);\n\n}\n\n/*\n======================\nSV_CloseEnough\n \n======================\n*/\nqbool SV_CloseEnough (edict_t *ent, edict_t *goal, float dist)\n{\n\tint\t\ti;\n\n\tfor (i=0 ; i<3 ; i++)\n\t{\n\t\tif (goal->v->absmin[i] > ent->v->absmax[i] + dist)\n\t\t\treturn false;\n\t\tif (goal->v->absmax[i] < ent->v->absmin[i] - dist)\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\n/*\n======================\nSV_MoveToGoal\n \n======================\n*/\n\n// NOTE: If you change this, then change PF2_MoveToGoal too!!!\n\nvoid SV_MoveToGoal (void)\n{\n\tedict_t\t\t*ent, *goal;\n\tfloat\t\tdist;\n\n\tent = PROG_TO_EDICT(pr_global_struct->self);\n\tgoal = PROG_TO_EDICT(ent->v->goalentity);\n\tdist = G_FLOAT(OFS_PARM0);\n\n\tif ( !( (int)ent->v->flags & (FL_ONGROUND|FL_FLY|FL_SWIM) ) )\n\t{\n\t\tG_FLOAT(OFS_RETURN) = 0;\n\t\treturn;\n\t}\n\n\t// if the next step hits the enemy, return immediately\n\tif ( PROG_TO_EDICT(ent->v->enemy) != sv.edicts && SV_CloseEnough (ent, goal, dist) )\n\t\treturn;\n\n\t// bump around...\n\tif ( (rand()&3)==1 || !SV_StepDirection (ent, ent->v->ideal_yaw, dist))\n\t{\n\t\tSV_NewChaseDir (ent, goal, dist);\n\t}\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_nchan.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n \nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n \nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n \nSee the GNU General Public License for more details.\n \nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n \n\t\n*/\n// sv_nchan.c, user reliable data stream writes\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\n// check to see if client block will fit, if not, rotate buffers\nvoid ClientReliableCheckBlock(client_t *cl, int maxsize)\n{\n\tif (cl->num_backbuf\n\t\t|| cl->netchan.message.cursize > cl->netchan.message.maxsize - maxsize - 1)\n\t{\n\t\t// we would probably overflow the buffer, save it for next\n\t\tif (!cl->num_backbuf || cl->backbuf.cursize > cl->backbuf.maxsize - maxsize - 1)\n\t\t{\n\t\t\tif (cl->num_backbuf == MAX_BACK_BUFFERS)\n\t\t\t{\n\t\t\t\tSys_Printf (\"WARNING: MAX_BACK_BUFFERS for %s\\n\", cl->name);\n\t\t\t\tcl->backbuf.cursize = 0; // don't overflow without allowoverflow set\n\t\t\t\tcl->netchan.message.overflowed = true; // this will drop the client\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tmemset(&cl->backbuf, 0, sizeof(cl->backbuf));\n\t\t\tcl->backbuf.allowoverflow = true;\n\t\t\tcl->backbuf.data = cl->backbuf_data[cl->num_backbuf];\n\t\t\tcl->backbuf.maxsize = cl->netchan.message.maxsize;\n\t\t\tcl->backbuf_size[cl->num_backbuf] = 0;\n\t\t\tcl->num_backbuf++;\n\t\t}\n\t}\n}\n\n// begin a client block, estimated maximum size\nvoid ClientReliableWrite_Begin(client_t *cl, int c, int maxsize)\n{\n\tClientReliableCheckBlock(cl, maxsize);\n\tClientReliableWrite_Byte(cl, c);\n}\n\nvoid ClientReliable_FinishWrite(client_t *cl)\n{\n\tif (cl->num_backbuf)\n\t{\n\t\tcl->backbuf_size[cl->num_backbuf - 1] = cl->backbuf.cursize;\n\n\t\tif (cl->backbuf.overflowed)\n\t\t{\n\t\t\tSys_Printf (\"WARNING: backbuf [%d] reliable overflow for %s\\n\",cl->num_backbuf,cl->name);\n\t\t\tcl->netchan.message.overflowed = true; // this will drop the client\n\t\t}\n\t}\n}\n\nvoid ClientReliableWrite_Angle(client_t *cl, float f)\n{\n\tif (cl->num_backbuf)\n\t{\n\t\tMSG_WriteAngle(&cl->backbuf, f);\n\t\tClientReliable_FinishWrite(cl);\n\t}\n\telse\n\t\tMSG_WriteAngle(&cl->netchan.message, f);\n}\n\nvoid ClientReliableWrite_Angle16(client_t *cl, float f)\n{\n\tif (cl->num_backbuf)\n\t{\n\t\tMSG_WriteAngle16(&cl->backbuf, f);\n\t\tClientReliable_FinishWrite(cl);\n\t}\n\telse\n\t\tMSG_WriteAngle16(&cl->netchan.message, f);\n}\n\nvoid ClientReliableWrite_Byte(client_t *cl, int c)\n{\n\tif (cl->num_backbuf)\n\t{\n\t\tMSG_WriteByte(&cl->backbuf, c);\n\t\tClientReliable_FinishWrite(cl);\n\t}\n\telse\n\t\tMSG_WriteByte(&cl->netchan.message, c);\n}\n\nvoid ClientReliableWrite_Char(client_t *cl, int c)\n{\n\tif (cl->num_backbuf)\n\t{\n\t\tMSG_WriteChar(&cl->backbuf, c);\n\t\tClientReliable_FinishWrite(cl);\n\t}\n\telse\n\t\tMSG_WriteChar(&cl->netchan.message, c);\n}\n\nvoid ClientReliableWrite_Float(client_t *cl, float f)\n{\n\tif (cl->num_backbuf)\n\t{\n\t\tMSG_WriteFloat(&cl->backbuf, f);\n\t\tClientReliable_FinishWrite(cl);\n\t}\n\telse\n\t\tMSG_WriteFloat(&cl->netchan.message, f);\n}\n\nvoid ClientReliableWrite_Coord(client_t *cl, float f)\n{\n\tif (cl->num_backbuf)\n\t{\n\t\tMSG_WriteCoord(&cl->backbuf, f);\n\t\tClientReliable_FinishWrite(cl);\n\t}\n\telse\n\t\tMSG_WriteCoord(&cl->netchan.message, f);\n}\n\nvoid ClientReliableWrite_Long(client_t *cl, int c)\n{\n\tif (cl->num_backbuf)\n\t{\n\t\tMSG_WriteLong(&cl->backbuf, c);\n\t\tClientReliable_FinishWrite(cl);\n\t}\n\telse\n\t\tMSG_WriteLong(&cl->netchan.message, c);\n}\n\nvoid ClientReliableWrite_Short(client_t *cl, int c)\n{\n\tif (cl->num_backbuf)\n\t{\n\t\tMSG_WriteShort(&cl->backbuf, c);\n\t\tClientReliable_FinishWrite(cl);\n\t}\n\telse\n\t\tMSG_WriteShort(&cl->netchan.message, c);\n}\n\nvoid ClientReliableWrite_String(client_t *cl, char *s)\n{\n\tif (cl->num_backbuf)\n\t{\n\t\tMSG_WriteString(&cl->backbuf, s);\n\t\tClientReliable_FinishWrite(cl);\n\t}\n\telse\n\t\tMSG_WriteString(&cl->netchan.message, s);\n}\n\nvoid ClientReliableWrite_SZ(client_t *cl, void *data, int len)\n{\n\tif (cl->num_backbuf)\n\t{\n\t\tSZ_Write(&cl->backbuf, data, len);\n\t\tClientReliable_FinishWrite(cl);\n\t}\n\telse\n\t\tSZ_Write(&cl->netchan.message, data, len);\n}\n\nvoid SV_ClearBackbuf (client_t *cl)\n{\n\tcl->num_backbuf = 0;\n}\n\n// clears both cl->netchan.message and backbuf\nvoid SV_ClearReliable (client_t *cl)\n{\n\tSZ_Clear (&cl->netchan.message);\n\tSV_ClearBackbuf (cl);\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_null.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// sv_null.c -- this file can stub out the entire server system\n// for pure net-only clients\n\nvoid SV_Init (void)\n{\n}\nvoid SV_Shutdown (char *finalmsg)\n{\n}\nvoid SV_Frame (double time)\n{\n}\n"
  },
  {
    "path": "src/sv_phys.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n// sv_phys.c\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\n/*\n\n\npushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move.\n\nonground is set for toss objects when they come to a complete rest.  it is set for steping or walking objects \n\ndoors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH\nbonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS\ncorpses are SOLID_NOT and MOVETYPE_TOSS\ncrates are SOLID_BBOX and MOVETYPE_TOSS\nwalking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP\nflying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY\n\nsolid_edge items only clip against bsp models.\n\n*/\n\ncvar_t\tsv_maxvelocity\t\t= { \"sv_maxvelocity\",\"2000\"};\n\ncvar_t\tsv_gravity\t\t= { \"sv_gravity\", \"800\"};\ncvar_t\tsv_stopspeed\t\t= { \"sv_stopspeed\", \"100\"};\ncvar_t\tsv_maxspeed\t\t= { \"sv_maxspeed\", \"320\"};\ncvar_t\tsv_spectatormaxspeed \t= { \"sv_spectatormaxspeed\", \"500\"};\ncvar_t\tsv_accelerate\t\t= { \"sv_accelerate\", \"10\"};\ncvar_t\tsv_airaccelerate\t= { \"sv_airaccelerate\", \"10\"};\n\ncvar_t\tsv_antilag\t\t= { \"sv_antilag\", \"\", CVAR_SERVERINFO};\ncvar_t\tsv_antilag_no_pred\t= { \"sv_antilag_no_pred\", \"\", CVAR_SERVERINFO}; // \"negative\" cvar so it doesn't show on serverinfo for no reason\ncvar_t\tsv_antilag_projectiles\t= { \"sv_antilag_projectiles\", \"\", CVAR_SERVERINFO};\n\ncvar_t\tsv_wateraccelerate\t= { \"sv_wateraccelerate\", \"10\"};\ncvar_t\tsv_friction\t\t= { \"sv_friction\", \"4\"};\ncvar_t\tsv_waterfriction\t= { \"sv_waterfriction\", \"4\"};\ncvar_t\tpm_ktjump\t\t= { \"pm_ktjump\", \"1\", CVAR_SERVERINFO};\ncvar_t\tpm_bunnyspeedcap\t= { \"pm_bunnyspeedcap\", \"\", CVAR_SERVERINFO};\ncvar_t\tpm_slidefix\t\t= { \"pm_slidefix\", \"\", CVAR_SERVERINFO};\nvoid OnChange_pm_airstep (cvar_t *var, char *value, qbool *cancel);\ncvar_t\tpm_airstep\t\t= { \"pm_airstep\", \"\", CVAR_SERVERINFO, OnChange_pm_airstep};\ncvar_t\tpm_pground\t\t= { \"pm_pground\", \"\", CVAR_SERVERINFO|CVAR_ROM};\ncvar_t  pm_rampjump     = { \"pm_rampjump\", \"\", CVAR_SERVERINFO };\n\ndouble\tsv_frametime;\n\n\n// when pm_airstep is 1, set pm_pground to 1, and vice versa\n// airstep works best with pground on\nvoid OnChange_pm_airstep (cvar_t *var, char *value, qbool *cancel)\n{\n\tfloat val = Q_atoi(value);\n\tCvar_SetROM (&pm_pground, val ? \"1\" : \"\");\n}\n\n\nvoid SV_Physics_Toss (edict_t *ent);\n\n\n/*\n================\nSV_CheckVelocity\n================\n*/\nvoid SV_CheckVelocity (edict_t *ent)\n{\n\tint i;\n\tfloat wishspeed;\n\n\t//\n\t// bound velocity\n\t//\n\tfor (i=0 ; i<3 ; i++)\n\t{\n\t\tif (IS_NAN(ent->v->velocity[i]))\n\t\t{\n\t\t\tCon_DPrintf (\"Got a NaN velocity on %s\\n\", PR_GetEntityString(ent->v->classname));\n\t\t\tent->v->velocity[i] = 0;\n\t\t}\n\t\tif (IS_NAN(ent->v->origin[i]))\n\t\t{\n\t\t\tCon_DPrintf (\"Got a NaN origin on %s\\n\", PR_GetEntityString(ent->v->classname));\n\t\t\tent->v->origin[i] = 0;\n\t\t}\n/*\t\tif (ent->v->velocity[i] > sv_maxvelocity.value)\n\t\t\tent->v->velocity[i] = sv_maxvelocity.value;\n\t\telse if (ent->v->velocity[i] < -sv_maxvelocity.value)\n\t\t\tent->v->velocity[i] = -sv_maxvelocity.value;\n*/\n\t}\n\n\t// SV_MAXVELOCITY fix by Maddes\n\twishspeed = VectorLength(ent->v->velocity);\n\tif (wishspeed > sv_maxvelocity.value)\n\t{\n\t\tVectorScale (ent->v->velocity, sv_maxvelocity.value/wishspeed, ent->v->velocity);\n\t\twishspeed = sv_maxvelocity.value;\n\t}\n}\n\n/*\n=============\nSV_RunThink\n\nRuns thinking code if time.  There is some play in the exact time the think\nfunction will be called, because it is called before any movement is done\nin a frame.  Not used for pushmove objects, because they must be exact.\nReturns false if the entity removed itself.\n=============\n*/\nqbool SV_RunThink (edict_t *ent)\n{\n\tfloat thinktime;\n\n\tdo\n\t{\n\t\tthinktime = ent->v->nextthink;\n\t\tif (thinktime <= 0)\n\t\t\treturn true;\n\t\tif (thinktime > sv.time + sv_frametime)\n\t\t\treturn true;\n\n\t\tif (thinktime < sv.time)\n\t\t\tthinktime = sv.time; // don't let things stay in the past.\n\t\t// it is possible to start that way\n\t\t// by a trigger with a local time.\n\t\tent->v->nextthink = 0;\n\t\tpr_global_struct->time = thinktime;\n\t\tpr_global_struct->self = EDICT_TO_PROG(ent);\n\t\tpr_global_struct->other = EDICT_TO_PROG(sv.edicts);\n\t\tPR_EdictThink(ent->v->think);\n\n\t\tif (ent->e.free)\n\t\t\treturn false;\n\t} while (1);\n\n\treturn true;\n}\n\n/*\n==================\nSV_Impact\n\nTwo entities have touched, so run their touch functions\n==================\n*/\nvoid SV_Impact (edict_t *e1, edict_t *e2)\n{\n\tint old_self, old_other;\n\n\told_self = pr_global_struct->self;\n\told_other = pr_global_struct->other;\n\n\tpr_global_struct->time = sv.time;\n\tif (e1->v->touch && e1->v->solid != SOLID_NOT)\n\t{\n\t\tpr_global_struct->self = EDICT_TO_PROG(e1);\n\t\tpr_global_struct->other = EDICT_TO_PROG(e2);\n\t\tPR_EdictTouch(e1->v->touch);\n\t}\n\n\tif (e2->v->touch && e2->v->solid != SOLID_NOT)\n\t{\n\t\tpr_global_struct->self = EDICT_TO_PROG(e2);\n\t\tpr_global_struct->other = EDICT_TO_PROG(e1);\n\t\tPR_EdictTouch(e2->v->touch);\n\t}\n\n\tpr_global_struct->self = old_self;\n\tpr_global_struct->other = old_other;\n}\n\n\n/*\n==================\nClipVelocity\n\nSlide off of the impacting object\n==================\n*/\n#define\tSTOP_EPSILON\t0.1\n\nvoid ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce)\n{\n\tfloat backoff;\n\tfloat change;\n\tint i;\n\n\n\tbackoff = DotProduct (in, normal) * overbounce;\n\n\tfor (i=0 ; i<3 ; i++)\n\t{\n\t\tchange = normal[i]*backoff;\n\t\tout[i] = in[i] - change;\n\t\tif (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)\n\t\t\tout[i] = 0;\n\t}\n}\n\n\n/*\n============\nSV_FlyMove\n\nThe basic solid body movement clip that slides along multiple planes\nReturns the clipflags if the velocity was modified (hit something solid)\n1 = floor\n2 = wall / step\n4 = dead stop\nIf steptrace is not NULL, the trace of any vertical wall hit will be stored\n============\n*/\n#define MAX_CLIP_PLANES\t5\nint SV_FlyMove (edict_t *ent, float time1, trace_t *steptrace, int type)\n{\n\tint\t\tbumpcount, numbumps;\n\tvec3_t\tdir;\n\tfloat\td;\n\tint\t\tnumplanes;\n\tvec3_t\tplanes[MAX_CLIP_PLANES];\n\tvec3_t\tprimal_velocity, original_velocity, new_velocity;\n\tint\t\ti, j;\n\ttrace_t\ttrace;\n\tvec3_t\tend;\n\tfloat\ttime_left;\n\tint\t\tblocked;\n\n\tnumbumps = 4;\n\n\tblocked = 0;\n\tVectorCopy (ent->v->velocity, original_velocity);\n\tVectorCopy (ent->v->velocity, primal_velocity);\n\tnumplanes = 0;\n\n\ttime_left = time1;\n\n\tfor (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)\n\t{\n\t\tfor (i=0 ; i<3 ; i++)\n\t\t\tend[i] = ent->v->origin[i] + time_left * ent->v->velocity[i];\n\n\t\ttrace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, end, type, ent);\n\n\t\tif (trace.allsolid)\n\t\t{\t// entity is trapped in another solid\n\t\t\tVectorClear (ent->v->velocity);\n\t\t\treturn 3;\n\t\t}\n\n\t\tif (trace.fraction > 0)\n\t\t{\t// actually covered some distance\n\t\t\tVectorCopy (trace.endpos, ent->v->origin);\n\t\t\tVectorCopy (ent->v->velocity, original_velocity);\n\t\t\tnumplanes = 0;\n\t\t}\n\n\t\tif (trace.fraction == 1)\n\t\t\tbreak; // moved the entire distance\n\n\t\tif (!trace.e.ent)\n\t\t\tSV_Error (\"SV_FlyMove: !trace.e.ent\");\n\n\t\tif (trace.plane.normal[2] > 0.7)\n\t\t{\n\t\t\tblocked |= 1; // floor\n\t\t\tif (trace.e.ent->v->solid == SOLID_BSP)\n\t\t\t{\n\t\t\t\tent->v->flags = (int)ent->v->flags | FL_ONGROUND;\n\t\t\t\tent->v->groundentity = EDICT_TO_PROG(trace.e.ent);\n\t\t\t}\n\t\t}\n\t\tif (!trace.plane.normal[2])\n\t\t{\n\t\t\tblocked |= 2; // step\n\t\t\tif (steptrace)\n\t\t\t\t*steptrace = trace; // save for player extrafriction\n\t\t}\n\n\t\t//\n\t\t// run the impact function\n\t\t//\n\t\tSV_Impact (ent, trace.e.ent);\n\t\tif (ent->e.free)\n\t\t\tbreak;\t // removed by the impact function\n\n\n\t\ttime_left -= time_left * trace.fraction;\n\n\t\t// cliped to another plane\n\t\tif (numplanes >= MAX_CLIP_PLANES)\n\t\t{\t// this shouldn't really happen\n\t\t\tVectorClear (ent->v->velocity);\n\t\t\treturn 3;\n\t\t}\n\n\t\tVectorCopy (trace.plane.normal, planes[numplanes]);\n\t\tnumplanes++;\n\n\t\t//\n\t\t// modify original_velocity so it parallels all of the clip planes\n\t\t//\n\t\tfor (i=0 ; i<numplanes ; i++)\n\t\t{\n\t\t\tClipVelocity (original_velocity, planes[i], new_velocity, 1);\n\t\t\tfor (j=0 ; j<numplanes ; j++)\n\t\t\t\tif (j != i)\n\t\t\t\t{\n\t\t\t\t\tif (DotProduct (new_velocity, planes[j]) < 0)\n\t\t\t\t\t\tbreak; // not ok\n\t\t\t\t}\n\t\t\tif (j == numplanes)\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (i != numplanes)\n\t\t{\t// go along this plane\n\t\t\tVectorCopy (new_velocity, ent->v->velocity);\n\t\t}\n\t\telse\n\t\t{\t// go along the crease\n\t\t\tif (numplanes != 2)\n\t\t\t{\n\t\t\t\t// Con_Printf (\"clip velocity, numplanes == %i\\n\",numplanes);\n\t\t\t\tVectorClear (ent->v->velocity);\n\t\t\t\treturn 7;\n\t\t\t}\n\t\t\tCrossProduct (planes[0], planes[1], dir);\n\t\t\td = DotProduct (dir, ent->v->velocity);\n\t\t\tVectorScale (dir, d, ent->v->velocity);\n\t\t}\n\n\t\t//\n\t\t// if original velocity is against the original velocity, stop dead\n\t\t// to avoid tiny occilations in sloping corners\n\t\t//\n\t\tif (DotProduct (ent->v->velocity, primal_velocity) <= 0)\n\t\t{\n\t\t\tVectorClear (ent->v->velocity);\n\t\t\treturn blocked;\n\t\t}\n\t}\n\n\treturn blocked;\n}\n\n\n/*\n============\nSV_AddGravity\n============\n*/\nvoid SV_AddGravity (edict_t *ent, float scale)\n{\n\tent->v->velocity[2] -= scale * movevars.gravity * sv_frametime;\n}\n\n/*\n===============================================================================\n\nPUSHMOVE\n\n===============================================================================\n*/\n\n/*\n============\nSV_PushEntity\n\nDoes not change the entities velocity at all\n============\n*/\ntrace_t SV_PushEntity (edict_t *ent, vec3_t push, unsigned int traceflags)\n{\n\ttrace_t\ttrace;\n\tvec3_t\tend;\n\n\tVectorAdd (ent->v->origin, push, end);\n\n\tif ((int)ent->v->flags&FL_LAGGEDMOVE)\n\t\ttraceflags |= MOVE_LAGGED;\n\n\tif (ent->v->movetype == MOVETYPE_FLYMISSILE)\n\t\ttrace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, end, MOVE_MISSILE|traceflags, ent);\n\telse if (ent->v->solid == SOLID_TRIGGER || ent->v->solid == SOLID_NOT)\n\t\t// only clip against bmodels\n\t\ttrace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, end, MOVE_NOMONSTERS|traceflags, ent);\n\telse\n\t\ttrace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, end, MOVE_NORMAL|traceflags, ent);\n\n\tVectorCopy (trace.endpos, ent->v->origin);\n\tSV_LinkEdict (ent, true);\n\n\tif (trace.e.ent)\n\t\tSV_Impact (ent, trace.e.ent);\n\n\treturn trace;\n}\n\n\n/*\n============\nSV_Push\n\n============\n*/\nqbool SV_Push (edict_t *pusher, vec3_t move)\n{\n\tint\t\t\ti, e;\n\tedict_t\t\t*check, *block;\n\tvec3_t\t\tmins, maxs;\n\tvec3_t\t\tpushorig;\n\tint\t\t\tnum_moved;\n\tedict_t\t\t*moved_edict[MAX_EDICTS];\n\tvec3_t\t\tmoved_from[MAX_EDICTS];\n\tfloat\t\tsolid_save;\n\n\tfor (i=0 ; i<3 ; i++)\n\t{\n\t\tmins[i] = pusher->v->absmin[i] + move[i];\n\t\tmaxs[i] = pusher->v->absmax[i] + move[i];\n\t}\n\n\tVectorCopy (pusher->v->origin, pushorig);\n\n\t// move the pusher to its final position\n\n\tVectorAdd (pusher->v->origin, move, pusher->v->origin);\n\tSV_LinkEdict (pusher, false);\n\n\t// see if any solid entities are inside the final position\n\tnum_moved = 0;\n\tcheck = NEXT_EDICT(sv.edicts);\n\tfor (e=1 ; e<sv.num_edicts ; e++, check = NEXT_EDICT(check))\n\t{\n\t\tif (check->e.free)\n\t\t\tcontinue;\n\t\tif (check->v->movetype == MOVETYPE_PUSH\n\t\t|| check->v->movetype == MOVETYPE_NONE\n\t\t|| check->v->movetype == MOVETYPE_NOCLIP)\n\t\t\tcontinue;\n\n\t\tsolid_save = pusher->v->solid;\n\t\tpusher->v->solid = SOLID_NOT;\n\t\tblock = SV_TestEntityPosition (check);\n\t\tpusher->v->solid = solid_save;\n\t\tif (block)\n\t\t\tcontinue;\n\n\t\t// if the entity is standing on the pusher, it will definately be moved\n\t\tif ( ! ( ((int)check->v->flags & FL_ONGROUND)\n\t\t&& PROG_TO_EDICT(check->v->groundentity) == pusher) )\n\t\t{\n\t\t\tif ( check->v->absmin[0] >= maxs[0]\n\t\t\t|| check->v->absmin[1] >= maxs[1]\n\t\t\t|| check->v->absmin[2] >= maxs[2]\n\t\t\t|| check->v->absmax[0] <= mins[0]\n\t\t\t|| check->v->absmax[1] <= mins[1]\n\t\t\t|| check->v->absmax[2] <= mins[2] )\n\t\t\t\tcontinue;\n\n\t\t\t// see if the ent's bbox is inside the pusher's final position\n\t\t\tif (!SV_TestEntityPosition (check))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\t// remove the onground flag for non-players\n\t\tif (check->v->movetype != MOVETYPE_WALK)\n\t\t\tcheck->v->flags = (int)check->v->flags & ~FL_ONGROUND;\n\n\t\tVectorCopy (check->v->origin, moved_from[num_moved]);\n\t\tmoved_edict[num_moved] = check;\n\t\tnum_moved++;\n\n\t\t// try moving the contacted entity\n\t\tVectorAdd (check->v->origin, move, check->v->origin);\n\t\tblock = SV_TestEntityPosition (check);\n\t\tif (!block)\n\t\t{\t// pushed ok\n\t\t\tSV_LinkEdict (check, false);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// if it is ok to leave in the old position, do it\n\t\tVectorSubtract (check->v->origin, move, check->v->origin);\n\t\tblock = SV_TestEntityPosition (check);\n\t\tif (!block)\n\t\t{\n\t\t\t//if leaving it where it was, allow it to drop to the floor again (useful for plats that move downward)\n\t\t\t//check->v->flags = (int)check->v->flags & ~FL_ONGROUND; // disconnect: is it needed?\n\n\t\t\tnum_moved--;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// if it is still inside the pusher, block\n\t\tif (check->v->mins[0] == check->v->maxs[0])\n\t\t{\n\t\t\tSV_LinkEdict (check, false);\n\t\t\tcontinue;\n\t\t}\n\t\tif (check->v->solid == SOLID_NOT || check->v->solid == SOLID_TRIGGER)\n\t\t{\t// corpse\n\t\t\tcheck->v->mins[0] = check->v->mins[1] = 0;\n\t\t\tVectorCopy (check->v->mins, check->v->maxs);\n\t\t\tSV_LinkEdict (check, false);\n\t\t\tcontinue;\n\t\t}\n\n\t\tVectorCopy (pushorig, pusher->v->origin);\n\t\tSV_LinkEdict (pusher, false);\n\n\t\t// if the pusher has a \"blocked\" function, call it\n\t\t// otherwise, just stay in place until the obstacle is gone\n\t\tif (pusher->v->blocked)\n\t\t{\n\t\t\tpr_global_struct->self = EDICT_TO_PROG(pusher);\n\t\t\tpr_global_struct->other = EDICT_TO_PROG(check);\n\t\t\tPR_EdictBlocked (pusher->v->blocked);\n\t\t}\n\n\t\t// move back any entities we already moved\n\t\tfor (i=0 ; i<num_moved ; i++)\n\t\t{\n\t\t\tVectorCopy (moved_from[i], moved_edict[i]->v->origin);\n\t\t\tSV_LinkEdict (moved_edict[i], false);\n\t\t}\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n============\nSV_PushMove\n\n============\n*/\nvoid SV_PushMove (edict_t *pusher, float movetime)\n{\n\tint i;\n\tvec3_t move;\n\n\tif (!pusher->v->velocity[0] && !pusher->v->velocity[1] && !pusher->v->velocity[2])\n\t{\n\t\tpusher->v->ltime += movetime;\n\t\treturn;\n\t}\n\n\tfor (i=0 ; i<3 ; i++)\n\t\tmove[i] = pusher->v->velocity[i] * movetime;\n\n\tif (SV_Push (pusher, move))\n\t\tpusher->v->ltime += movetime;\n}\n\n\n/*\n================\nSV_Physics_Pusher\n\n================\n*/\nvoid SV_Physics_Pusher (edict_t *ent)\n{\n\tfloat thinktime;\n\tfloat oldltime;\n\tfloat movetime;\n\tfloat l;\n\tvec3_t oldorg, move;\n\n\toldltime = ent->v->ltime;\n\n\tthinktime = ent->v->nextthink;\n\tif (thinktime < ent->v->ltime + sv_frametime)\n\t{\n\t\tmovetime = thinktime - ent->v->ltime;\n\t\tif (movetime < 0)\n\t\t\tmovetime = 0;\n\t}\n\telse\n\t\tmovetime = sv_frametime;\n\n\tif (movetime)\n\t{\n\t\tSV_PushMove (ent, movetime);\t// advances ent->v->ltime if not blocked\n\t}\n\n\tif (thinktime > oldltime && thinktime <= ent->v->ltime)\n\t{\n\t\tVectorCopy (ent->v->origin, oldorg);\n\t\tent->v->nextthink = 0;\n\t\tpr_global_struct->time = sv.time;\n\t\tpr_global_struct->self = EDICT_TO_PROG(ent);\n\t\tpr_global_struct->other = EDICT_TO_PROG(sv.edicts);\n\t\tPR_EdictThink(ent->v->think);\n\n\t\tif (ent->e.free)\n\t\t\treturn;\n\t\tVectorSubtract (ent->v->origin, oldorg, move);\n\n\t\tl = VectorLength(move);\n\t\tif (l > 1.0/64)\n\t\t{\n\t\t\t// Con_Printf (\"**** snap: %f\\n\", VectorLength (l));\n\t\t\tVectorCopy (oldorg, ent->v->origin);\n\t\t\tSV_Push (ent, move);\n\t\t}\n\t}\n}\n\n/*\n=============\nSV_Physics_None\n\nNon moving objects can only think\n=============\n*/\nvoid SV_Physics_None (edict_t *ent)\n{\n\t// regular thinking\n\tSV_RunThink (ent);\n}\n\n/*\n=============\nSV_Physics_Noclip\n\nA moving object that doesn't obey physics\n=============\n*/\nvoid SV_Physics_Noclip (edict_t *ent)\n{\n\t// regular thinking\n\tif (!SV_RunThink (ent))\n\t\treturn;\n\n\tVectorMA (ent->v->angles, sv_frametime, ent->v->avelocity, ent->v->angles);\n\tVectorMA (ent->v->origin, sv_frametime, ent->v->velocity, ent->v->origin);\n\n\tSV_LinkEdict (ent, false);\n}\n\n/*\n==============================================================================\n\nTOSS / BOUNCE\n\n==============================================================================\n*/\n\n/*\n=============\nSV_CheckWaterTransition\n\n=============\n*/\nvoid SV_CheckWaterTransition (edict_t *ent)\n{\n\tint cont;\n\n\tcont = SV_PointContents (ent->v->origin);\n\tif (!ent->v->watertype)\n\t{\t// just spawned here\n\t\tent->v->watertype = cont;\n\t\tent->v->waterlevel = 1;\n\t\treturn;\n\t}\n\n\tif (cont <= CONTENTS_WATER)\n\t{\n\t\tif (ent->v->watertype == CONTENTS_EMPTY)\n\t\t{\t// just crossed into water\n\t\t\tSV_StartSound (ent, 0, \"misc/h2ohit1.wav\", 255, 1);\n\t\t}\n\t\tent->v->watertype = cont;\n\t\tent->v->waterlevel = 1;\n\t}\n\telse\n\t{\n\t\tif (ent->v->watertype != CONTENTS_EMPTY)\n\t\t{\t// just crossed into water\n\t\t\tSV_StartSound (ent, 0, \"misc/h2ohit1.wav\", 255, 1);\n\t\t}\n\t\tent->v->watertype = CONTENTS_EMPTY;\n\t\tent->v->waterlevel = cont;\n\t}\n}\n\n/*\n=============\nSV_Physics_Toss\n\nToss, bounce, and fly movement.  When onground, do nothing.\n=============\n*/\nvoid SV_Physics_Toss (edict_t *ent)\n{\n\ttrace_t\ttrace;\n\tvec3_t\tmove;\n\tfloat\tbackoff;\n\n\t// regular thinking\n\tif (!SV_RunThink (ent))\n\t\treturn;\n\n\tif (ent->v->velocity[2] > 0)\n\t\tent->v->flags = (int)ent->v->flags & ~FL_ONGROUND;\n\n\t// if onground, return without moving\n\tif ( ((int)ent->v->flags & FL_ONGROUND) )\n\t\treturn;\n\n\tSV_CheckVelocity (ent);\n\n\t// add gravity\n\tif (ent->v->movetype != MOVETYPE_FLY\n\t&& ent->v->movetype != MOVETYPE_FLYMISSILE)\n\t\tSV_AddGravity (ent, 1.0);\n\n\t// move angles\n\tVectorMA (ent->v->angles, sv_frametime, ent->v->avelocity, ent->v->angles);\n\n\t// move origin\n\tVectorScale (ent->v->velocity, sv_frametime, move);\n\ttrace = SV_PushEntity (ent, move, (sv_antilag.value == 2 && sv_antilag_projectiles.value) ? MOVE_LAGGED:0);\n\tif (trace.fraction == 1)\n\t\treturn;\n\tif (ent->e.free)\n\t\treturn;\n\n\tif (ent->v->movetype == MOVETYPE_BOUNCE)\n\t\tbackoff = 1.5;\n\telse\n\t\tbackoff = 1;\n\n\tClipVelocity (ent->v->velocity, trace.plane.normal, ent->v->velocity, backoff);\n\n\t// stop if on ground\n\tif (trace.plane.normal[2] > 0.7)\n\t{\n\t\tif (ent->v->velocity[2] < 60 || ent->v->movetype != MOVETYPE_BOUNCE )\n\t\t{\n\t\t\tent->v->flags = (int)ent->v->flags | FL_ONGROUND;\n\t\t\tent->v->groundentity = EDICT_TO_PROG(trace.e.ent);\n\t\t\tVectorClear (ent->v->velocity);\n\t\t\tVectorClear (ent->v->avelocity);\n\t\t}\n\t}\n\n\t// check for in water\n\tSV_CheckWaterTransition (ent);\n}\n\n/*\n===============================================================================\n\nSTEPPING MOVEMENT\n\n===============================================================================\n*/\n\n/*\n=============\nSV_Physics_Step\n\nMonsters freefall when they don't have a ground entity, otherwise\nall movement is done with discrete steps.\n\nThis is also used for objects that have become still on the ground, but\nwill fall if the floor is pulled out from under them.\nFIXME: is this true?\n=============\n*/\nvoid SV_Physics_Step (edict_t *ent)\n{\n\tqbool hitsound;\n\n\t// frefall if not onground\n\tif ( ! ((int)ent->v->flags & (FL_ONGROUND | FL_FLY | FL_SWIM) ) )\n\t{\n\t\tif (ent->v->velocity[2] < movevars.gravity*-0.1)\n\t\t\thitsound = true;\n\t\telse\n\t\t\thitsound = false;\n\n\t\tSV_AddGravity (ent, 1.0);\n\t\tSV_CheckVelocity (ent);\n\t\t// Tonik: the check for SOLID_NOT is to fix the way dead bodies and\n\t\t// gibs behave (should not be blocked by players & monsters);\n\t\t// The SOLID_TRIGGER check is disabled lest we break frikbots\n\t\tif (ent->v->solid == SOLID_NOT /* || ent->v->solid == SOLID_TRIGGER*/)\n\t\t\tSV_FlyMove (ent, sv_frametime, NULL, MOVE_NOMONSTERS);\n\t\telse\n\t\t\tSV_FlyMove (ent, sv_frametime, NULL, MOVE_NORMAL);\n\t\tSV_LinkEdict (ent, true);\n\n\t\tif ( (int)ent->v->flags & FL_ONGROUND ) // just hit ground\n\t\t{\n\t\t\tif (hitsound)\n\t\t\t\tSV_StartSound (ent, 0, \"demon/dland2.wav\", 255, 1);\n\t\t}\n\t}\n\n\t// regular thinking\n\tSV_RunThink (ent);\n\n\tSV_CheckWaterTransition (ent);\n}\n\n//============================================================================\n\nvoid SV_ProgStartFrame (qbool isBotFrame)\n{\n\t// let the progs know that a new frame has started\n\tpr_global_struct->self = EDICT_TO_PROG(sv.edicts);\n\tpr_global_struct->other = EDICT_TO_PROG(sv.edicts);\n\tpr_global_struct->time = sv.time;\n\tPR_GameStartFrame(isBotFrame);\n}\n\n/*\n================\nSV_RunEntity\n================\n*/\nvoid SV_RunEntity (edict_t *ent)\n{\n\tif (ent->e.lastruntime == sv.time)\n\t\treturn;\n\tent->e.lastruntime = sv.time;\n\n\tswitch ((int)ent->v->movetype)\n\t{\n\tcase MOVETYPE_PUSH:\n\t\tSV_Physics_Pusher (ent);\n\t\tbreak;\n\tcase MOVETYPE_NONE:\n\tcase MOVETYPE_LOCK:\n\t\tSV_Physics_None (ent);\n\t\tbreak;\n\tcase MOVETYPE_NOCLIP:\n\t\tSV_Physics_Noclip (ent);\n\t\tbreak;\n\tcase MOVETYPE_STEP:\n\t\tSV_Physics_Step (ent);\n\t\tbreak;\n\tcase MOVETYPE_TOSS:\n\tcase MOVETYPE_BOUNCE:\n\tcase MOVETYPE_FLY:\n\tcase MOVETYPE_FLYMISSILE:\n\t\tSV_Physics_Toss (ent);\n\t\tbreak;\n\tdefault:\n\t\tSV_Error (\"SV_Physics: bad movetype %i\", (int)ent->v->movetype);\n\t}\n}\n\n/*\n** SV_RunNQNewmis\n** \n** sv_player will be valid\n*/\nvoid SV_RunNQNewmis (void)\n{\n\tedict_t\t*ent;\n\tdouble save_frametime;\n\tint i, pl;\n\n\tpl = EDICT_TO_PROG(sv_player);\n\tent = NEXT_EDICT(sv.edicts);\n\tfor (i=1 ; i<sv.num_edicts ; i++, ent = NEXT_EDICT(ent))\n\t{\n\t\tif (ent->e.free)\n\t\t\tcontinue;\n\t\tif (ent->e.lastruntime || ent->v->owner != pl)\n\t\t\tcontinue;\n\t\tif (ent->v->movetype != MOVETYPE_FLY &&\n\t\t\tent->v->movetype != MOVETYPE_FLYMISSILE && \n\t\t\tent->v->movetype != MOVETYPE_BOUNCE) \n\t\t\tcontinue;\n\t\tif (ent->v->solid != SOLID_BBOX && ent->v->solid != SOLID_TRIGGER)\n\t\t\tcontinue;\n\n\t\tsave_frametime = sv_frametime;\n\t\tsv_frametime = 0.05;\n\t\tSV_RunEntity (ent);\n\t\tsv_frametime = save_frametime;\n\t\treturn;\n\t}\n}\n\n/*\n================\nSV_RunNewmis\n================\n*/\nvoid SV_RunNewmis (void)\n{\n\tedict_t\t*ent;\n\tdouble save_frametime;\n\n\tif (pr_nqprogs)\n\t\treturn;\n\n\tif (!pr_global_struct->newmis)\n\t\treturn;\n\n\tent = PROG_TO_EDICT(pr_global_struct->newmis);\n\tpr_global_struct->newmis = 0;\n\n\tsave_frametime = sv_frametime;\n\tsv_frametime = 0.05;\n\n\tSV_RunEntity (ent);\n\n\tsv_frametime = save_frametime;\n}\n\n/*\n================\nSV_Physics\n================\n*/\nvoid SV_Physics (void)\n{\n\tint i;\n\tclient_t *cl,*savehc;\n\tedict_t *savesvpl;\n\tedict_t *ent;\n\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\tif (sv.old_time)\n\t{\n\t\t// don't bother running a frame if sv_mintic seconds haven't passed\n\t\tsv_frametime = sv.time - sv.old_time;\n\t\tif (sv_frametime < (double) sv_mintic.value)\n\t\t\treturn;\n\t\tif (sv_frametime > (double) sv_maxtic.value)\n\t\t\tsv_frametime = (double) sv_maxtic.value;\n\t\tsv.old_time = sv.time;\n\t}\n\telse\n\t\tsv_frametime = 0.1; // initialization frame\n\n\tsv.physicstime = sv.time;\n\n\tif (pr_nqprogs)\n\t\tNQP_Reset ();\n\n\tPR_GLOBAL(frametime) = sv_frametime;\n\n\tSV_ProgStartFrame(false);\n\n\t//\n\t// treat each object in turn\n\t// even the world gets a chance to think\n\t//\n\tent = sv.edicts;\n\tfor (i=0 ; i<sv.num_edicts ; i++, ent = NEXT_EDICT(ent))\n\t{\n\t\tif (ent->e.free)\n\t\t\tcontinue;\n\n\t\tif (PR_GLOBAL(force_retouch))\n\t\t\tSV_LinkEdict (ent, true);\t// force retouch even for stationary\n\n\t\tif (i > 0 && i <= MAX_CLIENTS)\n\t\t\tcontinue;\t\t// clients are run directly from packets\n\n\t\tSV_RunEntity (ent);\n\t\tSV_RunNewmis ();\n\t}\n\n\tif (PR_GLOBAL(force_retouch))\n\t\tPR_GLOBAL(force_retouch)--;\n\n\tsavesvpl = sv_player;\n\tsavehc = sv_client;\n\n\t// so spec will have right goalentity - if speccing someone\n\tfor ( i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++ )\n\t{\n\t\tif ( cl->state == cs_free )\n\t\t\tcontinue;\n\n\t\tsv_client = cl;\n\t\tsv_player = cl->edict;\n\n\t\tif (sv_client->spectator && sv_client->spec_track > 0)\n\t\t\tsv_player->v->goalentity = EDICT_TO_PROG(svs.clients[sv_client->spec_track-1].edict);\n\t}\n\n\tsv_player = savesvpl;\n\tsv_client = savehc;\n}\n\n#ifdef USE_PR2\nvoid SV_RunBots(void)\n{\n\tint i;\n\tclient_t *cl,*savehc;\n\tedict_t *savesvpl;\n\tdouble max_physfps = sv_maxfps.value;\n#ifdef SERVERONLY\n\tstatic double extramsec = 0;\n#endif\n\n\tif (max_physfps < 20 || max_physfps > 1000) {\n\t\tmax_physfps = 77.0;\n\t}\n\n\tif (sv.state != ss_active || !sv.physicstime)\n\t\treturn;\n\n#ifdef SERVERONLY\n\tif (sv.old_bot_time) {\n\t\t// don't bother running a frame if 1/fps seconds haven't passed\n\t\tdouble required = (double) 1.0f / max_physfps;\n\n\t\textramsec += (sv.time - sv.old_bot_time);\n\t\tsv.old_bot_time = sv.time;\n\t\tif (extramsec < required) {\n\t\t\treturn;\n\t\t}\n\t\tsv_frametime = required;\n\t\textramsec -= required;\n\t}\n\telse {\n\t\tsv_frametime = 1.0f / max_physfps; // initialization frame\n\t\textramsec = 0;\n\t\tsv.old_bot_time = sv.time;\n\t}\n#else\n\t// On internal server, try and match the user's framerate\n\t// ... don't run if no time passed, that is a user packet only\n\tif (sv.old_bot_time && sv.old_bot_time == sv.time) {\n\t\treturn;\n\t}\n\tsv.old_bot_time = sv.time;\n#endif\n\n\tsavesvpl = sv_player;\n\tsavehc = sv_client;\n\n\tPR_GLOBAL(frametime) = sv_frametime;\n\tSV_ProgStartFrame (true);\n\n\t//\n\t// Run bots physics.\n\t//\n\tfor ( i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++ )\n\t{\n\t\textern void SV_PreRunCmd(void);\n\t\textern void SV_RunCmd (usercmd_t *ucmd, qbool inside, qbool simulate);\n\t\textern void SV_PostRunCmd(void);\n\n\t\tif ( cl->state == cs_free )\n\t\t\tcontinue;\n\t\tif ( !cl->isBot )\n\t\t\tcontinue;\n\n\t\tsv_client = cl;\n\t\tsv_player = cl->edict;\n\n\t\tSV_PreRunCmd();\n\t\tSV_RunCmd (&cl->botcmd, false, false);\n\t\tSV_PostRunCmd();\n\n\t\tcl->lastcmd = cl->botcmd;\n\t\tcl->lastcmd.buttons = 0;\n\n\t\tmemset(&cl->botcmd,0,sizeof(cl->botcmd));\n\n\t\tcl->localtime = sv.time;\n\t\tcl->delta_sequence = -1;\t// no delta unless requested\n\n\t\tif (sv_antilag.value) {\n\t\t\tif (cl->antilag_position_next == 0 || cl->antilag_positions[(cl->antilag_position_next - 1) % MAX_ANTILAG_POSITIONS].localtime < cl->localtime) {\n\t\t\t\tcl->antilag_positions[cl->antilag_position_next % MAX_ANTILAG_POSITIONS].localtime = cl->localtime;\n\t\t\t\tVectorCopy(cl->edict->v->origin, cl->antilag_positions[cl->antilag_position_next % MAX_ANTILAG_POSITIONS].origin);\n\t\t\t\tcl->antilag_position_next++;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tcl->antilag_position_next = 0;\n\t\t}\n\t}\n\n\tsv_player = savesvpl;\n\tsv_client = savehc;\n}\n#endif\n\nvoid SV_SetMoveVars(void)\n{\n\tmovevars.gravity            = sv_gravity.value;\n\tmovevars.stopspeed          = sv_stopspeed.value;\n\tmovevars.maxspeed           = sv_maxspeed.value;\n\tmovevars.spectatormaxspeed  = sv_spectatormaxspeed.value;\n\tmovevars.accelerate         = sv_accelerate.value;\n\tmovevars.airaccelerate      = sv_airaccelerate.value;\n\tmovevars.wateraccelerate    = sv_wateraccelerate.value;\n\tmovevars.friction           = sv_friction.value;\n\tmovevars.waterfriction      = sv_waterfriction.value;\n\tmovevars.entgravity         = 1.0;\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_save.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n\n// sv_save.c\n\n#ifndef CLIENTONLY\n#ifdef SERVERONLY\n#include \"qwsvdef.h\"\n#else\n#include \"quakedef.h\"\n#include \"vfs.h\"\n#include \"server.h\"\n#include \"sv_world.h\"\n#endif\n\nextern cvar_t maxclients;\n\n#define\tSAVEGAME_COMMENT_LENGTH\t39\n#define\tSAVEGAME_VERSION\t6\n\n#ifdef SERVERONLY\nstatic void SV_SaveGameFileName(char* buffer, int buffer_size, char* name)\n{\n\tsnprintf(buffer, buffer_size, \"%s/save/%s\", fs_gamedir, name);\n}\n#else\nstatic void SV_SaveGameFileName(char* buffer, int buffer_size, char* name)\n{\n\tFS_SaveGameDirectory(buffer, buffer_size);\n\tstrlcat(buffer, name, buffer_size);\n}\n#endif\n\n//Writes a SAVEGAME_COMMENT_LENGTH character comment\nvoid SV_SavegameComment (char *buffer) {\n\tint i;\n\tchar kills[20];\n#ifdef SERVERONLY\n\tchar *mapname = sv.mapname;\n\tint killed_monsters = (int)PR_GLOBAL(killed_monsters);\n\tint total_monsters = (int)PR_GLOBAL(total_monsters);\n#else\n\tchar *mapname = cl.levelname;\n\tint killed_monsters = cl.stats[STAT_MONSTERS];\n\tint total_monsters = cl.stats[STAT_TOTALMONSTERS];\n#endif\n\tif (!mapname || !*mapname)\n\t\tmapname = \"Unnamed_Level\";\n\n\tfor (i = 0; i < SAVEGAME_COMMENT_LENGTH; i++)\n\t\tbuffer[i] = ' ';\n\tmemcpy (buffer, mapname, min(strlen(mapname), 21));\n\tsnprintf (kills, sizeof (kills), \"kills:%3i/%-3i\", killed_monsters, total_monsters);\n\tmemcpy (buffer + 22, kills, strlen(kills));\n\n\t// convert space to _ to make stdio happy\n\tfor (i = 0; i < SAVEGAME_COMMENT_LENGTH; i++)\n\t\tif (buffer[i] == ' ')\n\t\t\tbuffer[i] = '_';\n\n\tbuffer[SAVEGAME_COMMENT_LENGTH] = 0;\n}\n\nvoid SV_SaveGame_f(void)\n{\n\tchar fname[MAX_OSPATH], comment[SAVEGAME_COMMENT_LENGTH+1];\n\tFILE *f;\n\tint i;\n\n\tif (Cmd_Argc() != 2) {\n\t\tCon_Printf (\"Usage: %s <savefname> : save a game\\n\", Cmd_Argv(0));\n\t\treturn;\n\t} else if (strstr(Cmd_Argv(1), \"..\")) {\n\t\tCon_Printf (\"Relative pathfnames are not allowed.\\n\");\n\t\treturn;\n\t} else if (sv.state != ss_active) {\n\t\tCon_Printf (\"Not playing a local game.\\n\");\n\t\treturn;\n#ifndef SERVERONLY\n\t} else if (cl.intermission) {\n\t\tCon_Printf (\"Can't save in intermission.\\n\");\n\t\treturn;\n#endif\n\t} else if (deathmatch.value != 0 || coop.value != 0 || maxclients.value != 1) {\n\t\tCon_Printf (\"Can't save multiplayer games.\\n\");\n\t\treturn;\n\t}\n\n\tfor (i = 1; i < MAX_CLIENTS; i++) {\n\t\tif (svs.clients[i].state == cs_spawned) {\n\t\t\tCon_Printf (\"Can't save multiplayer games.\\n\");\n\t\t\treturn;\n\t\t}\n\t}\t\n\n\tif (svs.clients[0].state != cs_spawned) {\n\t\tCon_Printf (\"Can't save, client #0 not spawned.\\n\");\n\t\treturn;\n\t} else if (svs.clients[0].edict->v->health <= 0) {\n\t\tCon_Printf (\"Can't save game with a dead player\\n\");\n\t\t// in fact, we can, but does it make sense?\n\t\treturn;\n\t}\n\n\tSV_SaveGameFileName (fname, sizeof(fname), Cmd_Argv(1));\n\tCOM_DefaultExtension (fname, \".sav\", sizeof(fname));\n\t\n\tCon_Printf (\"Saving game to %s...\\n\", fname);\n\tif (!(f = fopen (fname, \"w\"))) {\t\t\n\t\tFS_CreatePath (fname);\n\t\tif (!(f = fopen (fname, \"w\"))) {\n\t\t\tCon_Printf (\"ERROR: couldn't open.\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\tfprintf (f, \"%i\\n\", SAVEGAME_VERSION);\n\tSV_SavegameComment (comment);\n\tfprintf (f, \"%s\\n\", comment);\n\tfor (i = 0 ; i < NUM_SPAWN_PARMS; i++)\n\t\tfprintf (f, \"%f\\n\", svs.clients->spawn_parms[i]);\n\tfprintf (f, \"%d\\n\", current_skill);\n\tfprintf (f, \"%s\\n\", sv.mapname);\n\tfprintf (f, \"%f\\n\", sv.time);\n\n\t// write the light styles\n\tfor (i = 0; i < MAX_LIGHTSTYLES; i++) {\n\t\tif (sv.lightstyles[i])\n\t\t\tfprintf (f, \"%s\\n\", sv.lightstyles[i]);\n\t\telse\n\t\t\tfprintf (f,\"m\\n\");\n\t}\n\n\tED_WriteGlobals (f);\n\tfor (i = 0; i < sv.num_edicts; i++) {\n\t\tED_Write (f, EDICT_NUM(i));\n\t\tfflush (f);\n\t}\n\tfclose (f);\n\tCon_Printf (\"done.\\n\");\n\n\t// force cache rebuild.\n\tFS_FlushFSHash();\n}\n\nvoid SV_LoadGame_f(void)\n{\n\tchar name[MAX_OSPATH], mapname[MAX_QPATH], str[32 * 1024];\n\tconst char* start;\n\tFILE *f;\n\tfloat time, tfloat, spawn_parms[NUM_SPAWN_PARMS];\n\tedict_t *ent;\n\tint entnum, version, r;\n\tunsigned int i;\n\n\tif (Cmd_Argc() != 2) {\n\t\tCon_Printf (\"Usage: %s <savename> : load a game\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tSV_SaveGameFileName (name, sizeof(name), Cmd_Argv(1));\n\tCOM_DefaultExtension (name, \".sav\", sizeof(name));\n\n\tCon_Printf (\"Loading game from %s...\\n\", name);\n\tif (!(f = fopen (name, \"rb\"))) {\n\t\tCon_Printf (\"ERROR: couldn't open.\\n\");\n\t\treturn;\n\t}\n\n\tif (fscanf (f, \"%i\\n\", &version) != 1) {\n\t\tfclose (f);\n\t\tCon_Printf (\"Error reading savegame data\\n\");\n\t\treturn;\n\t}\n\n\tif (version != SAVEGAME_VERSION) {\n\t\tfclose (f);\n\t\tCon_Printf (\"Savegame is version %i, not %i\\n\", version, SAVEGAME_VERSION);\n\t\treturn;\n\t}\n\n\tif (fscanf (f, \"%s\\n\", str) != 1) {\n\t\tfclose (f);\n\t\tCon_Printf (\"Error reading savegame data\\n\");\n\t\treturn;\n\t}\n\tfor (i = 0; i < NUM_SPAWN_PARMS; i++) {\n\t\tif (fscanf (f, \"%f\\n\", &spawn_parms[i]) != 1) {\n\t\t\tfclose (f);\n\t\t\tCon_Printf (\"Error reading savegame data\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// this silliness is so we can load 1.06 save files, which have float skill values\n\tif (fscanf (f, \"%f\\n\", &tfloat) != 1) {\n\t\tfclose (f);\n\t\tCon_Printf (\"Error reading savegame data\\n\");\n\t\treturn;\n\t}\n\tcurrent_skill = (int)(tfloat + 0.1);\n\n\tif (fscanf (f, \"%s\\n\", mapname) != 1) {\n\t\tfclose (f);\n\t\tCon_Printf (\"Error reading savegame data\\n\");\n\t\treturn;\n\t}\n\tif (fscanf (f, \"%f\\n\", &time) != 1) {\n\t\tfclose (f);\n\t\tCon_Printf (\"Error reading savegame data\\n\");\n\t\treturn;\n\t}\n\n#ifndef SERVERONLY\n\tHost_EndGame();\n\tCL_BeginLocalConnection ();\n#endif\n\n\tSV_SpawnServer(mapname, false, NULL, true);\n\n\tif (sv.state != ss_active) {\n\t\tCon_Printf (\"Couldn't load map\\n\");\n\t\tfclose (f);\n\t\treturn;\n\t}\n\n\t// load the light styles\n\tfor (i = 0; i < MAX_LIGHTSTYLES; i++) {\n\t\tint length;\n\t\tif (fscanf (f, \"%s\\n\", str) != 1) {\n\t\t\tCon_Printf(\"Couldn't read lightstyles\\n\");\n\t\t\tfclose (f);\n\t\t\treturn;\n\t\t}\n\t\tstr[sizeof(str) - 1] = '\\0';\n\t\tlength = strlen(str) + 1;\n\t\tsv.lightstyles[i] = (char *) Hunk_AllocName (length, \"lightstyle\");\n\t\tstrlcpy (sv.lightstyles[i], str, length);\n\t}\n\n\t// pause until all clients connect\n\tif (!(sv.paused & 1))\n\t\tSV_TogglePause (NULL, 1);\n\n\tsv.loadgame = true;\n\n\t// load the edicts out of the savegame file\n\tentnum = -1;\t\t// -1 is the globals\n\twhile (!feof(f)) {\n\t\tfor (i = 0; i < sizeof(str) - 1; i++) {\n\t\t\tr = fgetc (f);\n\t\t\tif (r == EOF || !r)\n\t\t\t\tbreak;\n\t\t\tstr[i] = r;\n\t\t\tif (r == '}') {\n\t\t\t\ti++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (i == sizeof(str)-1)\n\t\t\tHost_Error (\"Loadgame buffer overflow\");\n\t\tstr[i] = 0;\n\t\tstart = str;\n\t\tstart = COM_Parse(str);\n\t\tif (!com_token[0])\n\t\t\tbreak;\t\t// end of file\n\n\t\tif (strcmp(com_token,\"{\"))\n\t\t\tHost_Error (\"First token isn't a brace\");\n\n\t\tif (entnum == -1) {\t\n\t\t\t// parse the global vars\n\t\t\tED_ParseGlobals (start);\n\t\t}\n\t\telse {\t\n\t\t\t// parse an edict\n\t\t\tent = EDICT_NUM(entnum);\n\t\t\tED_ClearEdict (ent); // FIXME: we also clear world edict here, is it OK?\n\t\t\tED_ParseEdict (start, ent);\n\n\t\t\t// link it into the bsp tree\n\t\t\tif (!ent->e.free)\n\t\t\t\tSV_LinkEdict (ent, false);\n\t\t}\n\t\tentnum++;\n\t}\n\n\tsv.num_edicts = entnum;\n\tsv.time = time;\n\n\tfclose (f);\n\n\tfor (i = 0; i < NUM_SPAWN_PARMS; i++)\n\t\tsvs.clients->spawn_parms[i] = spawn_parms[i];\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_send.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n \nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n \nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n \nSee the GNU General Public License for more details.\n \nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n \n\t\n*/\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\nstatic void SV_BotWriteDamage(client_t* c, int i);\n\n#define CHAN_AUTO   0\n#define CHAN_WEAPON 1\n#define CHAN_VOICE  2\n#define CHAN_ITEM   3\n#define CHAN_BODY   4\n\n/*\n=============================================================================\n \nCon_Printf redirection\n \n=============================================================================\n*/\n\nchar\toutputbuf[OUTPUTBUF_SIZE];\n\nredirect_t\tsv_redirected;\nstatic int\tsv_redirectbufcount;\n\nqbool SV_SkipCommsBotMessage(client_t* client);\nextern cvar_t sv_phs, sv_reliable_sound;\n\n/*\n==================\nSV_FlushRedirect\n==================\n*/\nvoid SV_FlushRedirect (void)\n{\n\tchar send1[OUTPUTBUF_SIZE + 6];\n\n\tif (sv_redirected == RD_PACKET)\n\t{\n\t\tsend1[0] = 0xff;\n\t\tsend1[1] = 0xff;\n\t\tsend1[2] = 0xff;\n\t\tsend1[3] = 0xff;\n\t\tsend1[4] = A2C_PRINT;\n\t\tmemcpy (send1 + 5, outputbuf, strlen(outputbuf) + 1);\n\n\t\tNET_SendPacket (NS_SERVER, strlen(send1) + 1, send1, net_from);\n\t}\n\telse if (sv_redirected == RD_CLIENT && sv_redirectbufcount < MAX_REDIRECTMESSAGES)\n\t{\n\t\tClientReliableWrite_Begin (sv_client, svc_print, strlen(outputbuf)+3);\n\t\tClientReliableWrite_Byte (sv_client, PRINT_HIGH);\n\t\tClientReliableWrite_String (sv_client, outputbuf);\n\t\tsv_redirectbufcount++;\n\t}\n\telse if (sv_redirected == RD_MOD)\n\t{\n\t\t//return;\n\t}\n\telse if (sv_redirected > RD_MOD && sv_redirectbufcount < MAX_REDIRECTMESSAGES)\n\t{\n\t\tclient_t *cl;\n\n\t\tcl = svs.clients + sv_redirected - RD_MOD - 1;\n\n\t\tif (cl->state == cs_spawned)\n\t\t{\n\t\t\tClientReliableWrite_Begin (cl, svc_print, strlen(outputbuf)+3);\n\t\t\tClientReliableWrite_Byte (cl, PRINT_HIGH);\n\t\t\tClientReliableWrite_String (cl, outputbuf);\n\t\t\tsv_redirectbufcount++;\n\t\t}\n\t}\n\n\t// clear it\n\toutputbuf[0] = 0;\n}\n\n\n/*\n==================\nSV_BeginRedirect\n \n  Send Con_Printf data to the remote client\n  instead of the console\n==================\n*/\nvoid SV_BeginRedirect (redirect_t rd)\n{\n\tsv_redirected = rd;\n\toutputbuf[0] = 0;\n\tsv_redirectbufcount = 0;\n\n}\n\nvoid SV_EndRedirect (void)\n{\n\tSV_FlushRedirect ();\n\tsv_redirected = RD_NONE;\n}\n\nqbool SV_AddToRedirect(char *msg)\n{\n\tif (!sv_redirected)\n\t\treturn false;\n\n\t// FIXME: probably we should check client's MTU instead of fixed MIN_MTU.\n\tif (strlen(msg) + strlen(outputbuf) > /* MAX_MSGLEN */ MIN_MTU - 10)\n\t\tSV_FlushRedirect ();\n\n\tstrlcat(outputbuf, msg, sizeof(outputbuf));\n\n\treturn true;\n}\n\n#ifdef SERVERONLY\n\n/*\n================\nCon_Printf\n\nHandles cursor positioning, line wrapping, etc\n================\n*/\n#define\tMAXPRINTMSG\t4096\nvoid Con_Printf (char *fmt, ...)\n{\n\tva_list argptr;\n\tchar msg[MAXPRINTMSG];\n\n\tva_start (argptr,fmt);\n\tvsnprintf (msg, MAXPRINTMSG, fmt, argptr);\n\tva_end (argptr);\n\n\t// add to redirected message\n\tif (SV_AddToRedirect(msg))\n\t\treturn; // added.\n\n\tSys_Printf (\"%s\", msg);\t// also echo to debugging console\n\tSV_Write_Log(CONSOLE_LOG, 0, msg);\n\n\t// dumb error message to log file if\n\tif (sv_error)\n\t\tSV_Write_Log(ERROR_LOG, 1, msg);\n}\n\n/*\n================\nCon_DPrintf\n\nA Con_Printf that only shows up if the \"developer\" cvar is set\n================\n*/\nvoid Con_DPrintf (char *fmt, ...)\n{\n\tva_list\t\targptr;\n\tchar\t\tmsg[MAXPRINTMSG];\n\n\tif (!(int)developer.value)\n\t\treturn;\n\n\tva_start (argptr,fmt);\n\tvsnprintf (msg, MAXPRINTMSG, fmt, argptr);\n\tva_end (argptr);\n\n\tCon_Printf (\"%s\", msg);\n}\n\n#endif // SERVERONLY\n\n/*\n=============================================================================\n \nEVENT MESSAGES\n \n=============================================================================\n*/\n\nstatic void SV_PrintToClient(client_t *cl, int level, char *string)\n{\n\tif (cl->state < cs_preconnected)\n\t{\n\t\tSys_Printf(\"SV_PrintToClient: client not ready.\");\n\t\treturn;\n\t}\n\n\tClientReliableWrite_Begin (cl, svc_print, strlen(string)+3);\n\tClientReliableWrite_Byte (cl, level);\n\tClientReliableWrite_String (cl, string);\n}\n\n\n/*\n=================\nSV_ClientPrintf\n \nSends text across to be displayed if the level passes\n=================\n*/\nvoid SV_ClientPrintf (client_t *cl, int level, char *fmt, ...)\n{\n\tva_list\t\targptr;\n\tchar\t\tstring[1024];\n\n\tif (level < cl->messagelevel)\n\t\treturn;\n\n\tva_start (argptr,fmt);\n\tvsnprintf (string, sizeof(string), fmt, argptr);\n\tva_end (argptr);\n\n\tif (sv.mvdrecording)\n\t{\n\t\tif (MVDWrite_Begin (dem_single, cl - svs.clients, strlen(string)+3))\n\t\t{\n\t\t\tMVD_MSG_WriteByte (svc_print);\n\t\t\tMVD_MSG_WriteByte (level);\n\t\t\tMVD_MSG_WriteString (string);\n\t\t}\n\t}\n\n\tSV_PrintToClient(cl, level, string);\n}\n\nvoid SV_ClientPrintf2 (client_t *cl, int level, char *fmt, ...)\n{\n\tva_list\t\targptr;\n\tchar\t\tstring[1024];\n\n\tif (level < cl->messagelevel)\n\t\treturn;\n\n\tva_start (argptr,fmt);\n\tvsnprintf (string, sizeof(string), fmt, argptr);\n\tva_end (argptr);\n\n\tSV_PrintToClient(cl, level, string);\n}\n\n\n/*\n=================\nSV_BroadcastPrintf\n\nSends text to all active clients\n=================\n*/\nchar *parse_mod_string(char *str);\nvoid SV_DoBroadcastPrintf (int level, int flags, char *string)\n{\n\tchar\t\t*fraglog;\n\tstatic char\tstring2[1024] = {0};\n\tclient_t\t*cl;\n\tint\t\t\ti;\n\n\tif (!(flags & BPRINT_IGNORECONSOLE))\n\t\tSys_Printf (\"%s\", string);\t// print to the console\n\n\tif (!(flags & BPRINT_IGNORECLIENTS))\n\t{\n\t\tfor (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)\n\t\t{\n\t\t\tif (level < cl->messagelevel)\n\t\t\t\tcontinue;\n\t\t\tif (cl->state < cs_connected)\n\t\t\t\tcontinue;\n\n\t\t\tSV_PrintToClient(cl, level, string); // this does't go to mvd demo\n\t\t}\n\t}\n\n\tif (!(flags & BPRINT_IGNOREINDEMO))\n\t{\n\t\tif (flags & BPRINT_QTVONLY)\n\t\t{\n\t\t\tsizebuf_t\t\tmsg;\n\t\t\tbyte\t\t\tmsg_buf[1024];\n\n\t\t\tSZ_InitEx(&msg, msg_buf, sizeof(msg_buf), true);\n\t\t\tMSG_WriteByte (&msg, svc_print);\n\t\t\tMSG_WriteByte (&msg, level);\n\t\t\tMSG_WriteString (&msg, string);\n\n\t\t\tDemoWriteQTV(&msg);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (sv.mvdrecording)\n\t\t\t{\n\t\t\t\tif (MVDWrite_Begin (dem_all, 0, strlen(string)+3))\n\t\t\t\t{\n\t\t\t\t\tMVD_MSG_WriteByte (svc_print);\n\t\t\t\t\tMVD_MSG_WriteByte (level);\n\t\t\t\t\tMVD_MSG_WriteString (string);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t//\tSV_Write_Log(MOD_FRAG_LOG, 1, \"=== SV_BroadcastPrintf ===\\n\");\n\t//\tSV_Write_Log(MOD_FRAG_LOG, 1, va(\"%d\\n===>\", time(NULL)));\n\t//\tSV_Write_Log(MOD_FRAG_LOG, 1, string);\n\t//\tSV_Write_Log(MOD_FRAG_LOG, 1, \"<===\\n\");\n\tif (string[0] && logs[MOD_FRAG_LOG].sv_logfile)\n\t{\n\t\tif (string[strlen(string) - 1] == '\\n')\n\t\t{\n\t\t\tstrlcat(string2, string, sizeof(string2));\n\t\t\t//\t\t\tSV_Write_Log(MOD_FRAG_LOG, 1, \"=== SV_BroadcastPrintf ==={\\n\");\n\t\t\t//\t\t\tSV_Write_Log(MOD_FRAG_LOG, 1, string2);\n\t\t\t//\t\t\tSV_Write_Log(MOD_FRAG_LOG, 1, \"}==========================\\n\");\n\t\t\tif ((fraglog = parse_mod_string(string2)))\n\t\t\t{\n\t\t\t\tSV_Write_Log(MOD_FRAG_LOG, 1, fraglog);\n\t\t\t\tQ_free(fraglog);\n\t\t\t}\n\t\t\tstring2[0] = 0;\n\t\t}\n\t\telse\n\t\t\tstrlcat(string2, string, sizeof(string2));\n\t}\n\t//\tSV_Write_Log(MOD_FRAG_LOG, 1, \"==========================\\n\\n\");\n}\n\nvoid SV_BroadcastPrintf (int level, char *fmt, ...)\n{\n\tva_list\t\targptr;\n\tchar\t\tstring[1024];\n\n\tva_start (argptr,fmt);\n\tvsnprintf (string, sizeof(string), fmt, argptr);\n\tva_end (argptr);\n\n\tSV_DoBroadcastPrintf (level, 0, string);\n}\n\nvoid SV_BroadcastPrintfEx (int level, int flags, char *fmt, ...)\n{\n\tva_list\t\targptr;\n\tchar\t\tstring[1024];\n\n\tva_start (argptr,fmt);\n\tvsnprintf (string, sizeof(string), fmt, argptr);\n\tva_end (argptr);\n\n\tSV_DoBroadcastPrintf (level, flags, string);\n}\n\n/*\n=================\nSV_BroadcastCommand\n \nSends text to all active clients\n=================\n*/\nvoid SV_BroadcastCommand (char *fmt, ...)\n{\n\tva_list\t\targptr;\n\tchar\t\tstring[1024];\n\n\tif (!sv.state)\n\t\treturn;\n\tva_start (argptr,fmt);\n\tvsnprintf (string, sizeof(string), fmt, argptr);\n\tva_end (argptr);\n\n\tMSG_WriteByte (&sv.reliable_datagram, svc_stufftext);\n\tMSG_WriteString (&sv.reliable_datagram, string);\n}\n\n\n/*\n=================\nSV_Multicast\n\nSends the contents of sv.multicast to a subset of the clients,\nthen clears sv.multicast.\n\nMULTICAST_ALL\tsame as broadcast\nMULTICAST_PVS\tsend to clients potentially visible from org\nMULTICAST_PHS\tsend to clients potentially hearable from org\n=================\n*/\nvoid SV_MulticastEx (vec3_t origin, int to, const char *cl_reliable_key)\n{\n\tclient_t*   client;\n\tbyte*       mask;\n\tint         leafnum;\n\tint         j;\n\tqbool       reliable;\n\tvec3_t      vieworg;\n\tqbool       mvd_only = false;\n\n\treliable = false;\n\n\tswitch (to)\n\t{\n\tcase MULTICAST_ALL_R:\n\t\treliable = true;\t// intentional fallthrough\n\tcase MULTICAST_ALL:\n\t\tmask = NULL;\t\t// everything\n\t\tbreak;\n\n\tcase MULTICAST_PHS_R:\n\t\treliable = true;\t// intentional fallthrough\n\tcase MULTICAST_PHS:\n\t\tmask = CM_LeafPHS (CM_PointInLeaf(origin));\n\t\tbreak;\n\n\tcase MULTICAST_PVS_R:\n\t\treliable = true;\t// intentional fallthrough\n\tcase MULTICAST_PVS:\n\t\tmask = CM_LeafPVS (CM_PointInLeaf (origin));\n\t\tbreak;\n\tcase MULTICAST_MVD_HIDDEN:\n\t\tmask = NULL;\n\t\tmvd_only = true;\n\t\tbreak;\n\n\tdefault:\n\t\tmask = NULL;\n\t\tSV_Error (\"SV_Multicast: bad to:%i\", to);\n\t}\n\n\t// send the data to all relevent clients\n\tfor (j = 0, client = svs.clients; j < MAX_CLIENTS && !mvd_only; j++, client++)\n\t{\n\t\tint trackent = 0;\n\n\t\tif (client->state != cs_spawned)\n\t\t\tcontinue;\n\t\tif (SV_SkipCommsBotMessage(client))\n\t\t\tcontinue;\n\n\t\tif (!mask)\n\t\t\tgoto inrange; // multicast to all\n\n\t\t// in case of trackent we have to reflect his origin so PHS work right.\n\t\tif (fofs_trackent)\n\t\t{\n\t\t\ttrackent = ((eval_t *)((byte *)(client->edict)->v + fofs_trackent))->_int;\n\t\t\tif (trackent < 1 || trackent > MAX_CLIENTS || svs.clients[trackent - 1].state != cs_spawned)\n\t\t\t\ttrackent = 0;\n\t\t}\n\n\t\tif (trackent)\n\t\t{\n\t\t\tVectorAdd (svs.clients[trackent - 1].edict->v->origin, svs.clients[trackent - 1].edict->v->view_ofs, vieworg);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorAdd (client->edict->v->origin, client->edict->v->view_ofs, vieworg);\n\t\t}\n\n\t\tif (to == MULTICAST_PHS_R || to == MULTICAST_PHS)\n\t\t{\n\t\t\tvec3_t delta;\n\t\t\tVectorSubtract(origin, vieworg, delta);\n\t\t\tif (VectorLength(delta) <= 1024)\n\t\t\t\tgoto inrange;\n\t\t}\n\n\t\tleafnum = CM_Leafnum(CM_PointInLeaf(vieworg));\n\t\tif (leafnum)\n\t\t{\n\t\t\t// -1 is because pvs rows are 1 based, not 0 based like leafs\n\t\t\tleafnum = leafnum - 1;\n\t\t\tif ( !(mask[leafnum>>3] & (1<<(leafnum&7)) ) )\n\t\t\t{\n\t\t\t\t// Con_Printf (\"supressed multicast\\n\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\ninrange:\n\t\tif (reliable || (cl_reliable_key && *cl_reliable_key && strcmp(\"0\", Info_Get(&client->_userinfo_ctx_, cl_reliable_key))))\n\t\t{\n\t\t\tClientReliableCheckBlock(client, sv.multicast.cursize);\n\t\t\tClientReliableWrite_SZ(client, sv.multicast.data, sv.multicast.cursize);\n\t\t}\n\t\telse\n\t\t\tSZ_Write (&client->datagram, sv.multicast.data, sv.multicast.cursize);\n\t}\n\n\tif (sv.mvdrecording) {\n\t\tif (mvd_only) {\n\t\t\tmvdhidden_block_header_t header;\n\t\t\theader.length = sv.multicast.cursize - 2;\n\t\t\t// header.type_id = ...; < up to the mod to fill this part in\n\n\t\t\t// write to dem_multiple(0), which will be skipped by all major clients (ezQuake, FTE, fod)\n\t\t\tif (MVDWrite_HiddenBlockBegin(sv.multicast.cursize + sizeof(header.length))) {\n\t\t\t\tMVD_SZ_Write(&header.length, sizeof(header.length));\n\t\t\t\tMVD_SZ_Write(sv.multicast.data, sv.multicast.cursize);\n\t\t\t}\n\t\t}\n\t\telse if (reliable) {\n\t\t\tif (MVDWrite_Begin(dem_all, 0, sv.multicast.cursize)) {\n\t\t\t\tMVD_SZ_Write(sv.multicast.data, sv.multicast.cursize);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tSZ_Write(&demo.datagram, sv.multicast.data, sv.multicast.cursize);\n\t\t}\n\t}\n\n\tSZ_Clear (&sv.multicast);\n}\n\nvoid SV_Multicast (vec3_t origin, int to)\n{\n\tSV_MulticastEx(origin, to, NULL);\n}\n\nvoid SV_StartParticle (vec3_t org, vec3_t dir, int color, int count,\n\t\t\t\t\t   int replacement_te, int replacement_count)\n{\n\tint\t\ti, v;\n\tqbool\tsend_count;\n\n\t//if (AllClientsWantSVCParticle())\n\tif (0)\n\t{\n\t\tMSG_WriteByte (&sv.multicast, nq_svc_particle);\n\t\tMSG_WriteCoord (&sv.multicast, org[0]);\n\t\tMSG_WriteCoord (&sv.multicast, org[1]);\n\t\tMSG_WriteCoord (&sv.multicast, org[2]);\n\t\tfor (i=0 ; i<3 ; i++)\n\t\t{\n\t\t\tv = dir[i]*16;\n\t\t\tif (v > 127)\n\t\t\t\tv = 127;\n\t\t\telse if (v < -128)\n\t\t\t\tv = -128;\n\t\t\tMSG_WriteChar (&sv.multicast, v);\n\t\t}\n\t\tMSG_WriteByte (&sv.multicast, count);\n\t\tMSG_WriteByte (&sv.multicast, color);\n\t}\n\telse\n\t{\n\t\tif (replacement_te == TE_EXPLOSION || replacement_te == TE_LIGHTNINGBLOOD)\n\t\t\tsend_count = false;\n\t\telse if (replacement_te == TE_BLOOD || replacement_te == TE_GUNSHOT)\n\t\t\tsend_count = true;\n\t\telse\n\t\t\treturn;\t\t// don't send anything\n\n\t\tMSG_WriteByte (&sv.multicast, svc_temp_entity);\n\t\tMSG_WriteByte (&sv.multicast, replacement_te);\n\t\tif (send_count)\n\t\t\tMSG_WriteByte (&sv.multicast, replacement_count);\n\t\tMSG_WriteCoord (&sv.multicast, org[0]);\n\t\tMSG_WriteCoord (&sv.multicast, org[1]);\n\t\tMSG_WriteCoord (&sv.multicast, org[2]);\n\t}\n\n\tSV_Multicast (org, MULTICAST_PVS);\n}\n\n\n/*\n==================\nSV_StartSound\n \nEach entity can have eight independant sound sources, like voice,\nweapon, feet, etc.\n \nChannel 0 is an auto-allocate channel, the others override anything\nalready running on that entity/channel pair.\n \nAn attenuation of 0 will play full volume everywhere in the level.\nLarger attenuations will drop off.  (max 4 attenuation)\n \n==================\n*/\nvoid SV_StartSound (edict_t *entity, int channel, char *sample, int volume, float attenuation)\n{\n\tint     sound_num;\n\tint     i;\n\tint     ent;\n\tvec3_t  origin;\n\tqbool   use_phs;\n\tqbool   reliable = false;\n\n\tif (volume < 0 || volume > 255)\n\t\tSV_Error (\"SV_StartSound: volume = %i\", volume);\n\n\tif (attenuation < 0 || attenuation > 4)\n\t\tSV_Error (\"SV_StartSound: attenuation = %f\", attenuation);\n\n\tif (channel < 0 || channel > 15)\n\t\tSV_Error (\"SV_StartSound: channel = %i\", channel);\n\n\t// find precache number for sound\n\tfor (sound_num=1 ; sound_num<MAX_SOUNDS\n\t        && sv.sound_precache[sound_num] ; sound_num++)\n\t\tif (!strcmp(sample, sv.sound_precache[sound_num]))\n\t\t\tbreak;\n\n\tif ( sound_num == MAX_SOUNDS || !sv.sound_precache[sound_num] )\n\t{\n\t\tCon_Printf (\"SV_StartSound: %s not precached\\n\", sample);\n\t\treturn;\n\t}\n\n\tent = NUM_FOR_EDICT(entity);\n\n\tif ((channel & 8) || !(int)sv_phs.value)\t// no PHS flag\n\t{\n\t\tif (channel & 8)\n\t\t\treliable = true; // sounds that break the phs are reliable\n\t\tuse_phs = false;\n\t\tchannel &= 7;\n\t}\n\telse\n\t\tuse_phs = true;\n\n\t//\tif (channel == CHAN_BODY || channel == CHAN_VOICE)\n\t//\t\treliable = true;\n\n\tchannel = (ent<<3) | channel;\n\n\tif (volume != DEFAULT_SOUND_PACKET_VOLUME)\n\t\tchannel |= SND_VOLUME;\n\tif (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION)\n\t\tchannel |= SND_ATTENUATION;\n\n\t// use the entity origin unless it is a bmodel or a trigger\n\tif (entity->v->solid == SOLID_BSP || (entity->v->solid == SOLID_TRIGGER && entity->v->modelindex == 0))\n\t{\n\t\tfor (i=0 ; i<3 ; i++)\n\t\t\torigin[i] = entity->v->origin[i]+0.5*(entity->v->mins[i]+entity->v->maxs[i]);\n\t}\n\telse\n\t{\n\t\tVectorCopy (entity->v->origin, origin);\n\t}\n\n\tMSG_WriteByte (&sv.multicast, svc_sound);\n\tMSG_WriteShort (&sv.multicast, channel);\n\tif (channel & SND_VOLUME)\n\t\tMSG_WriteByte (&sv.multicast, volume);\n\tif (channel & SND_ATTENUATION)\n\t\tMSG_WriteByte (&sv.multicast, attenuation*64);\n\tMSG_WriteByte (&sv.multicast, sound_num);\n\tfor (i=0 ; i<3 ; i++)\n\t\tMSG_WriteCoord (&sv.multicast, origin[i]);\n\n\tif (use_phs)\n\t\tSV_MulticastEx (origin, reliable ? MULTICAST_PHS_R : MULTICAST_PHS, sv_reliable_sound.value ? \"rsnd\" : NULL);\n\telse\n\t\tSV_MulticastEx (origin, reliable ? MULTICAST_ALL_R : MULTICAST_ALL, sv_reliable_sound.value ? \"rsnd\" : NULL);\n}\n\n\n/*\n===============================================================================\n \nFRAME UPDATES\n \n===============================================================================\n*/\n\nint\t\tsv_nailmodel, sv_supernailmodel, sv_playermodel;\n\nvoid SV_FindModelNumbers (void)\n{\n\tint\t\ti;\n\n\tsv_nailmodel = -1;\n\tsv_supernailmodel = -1;\n\tsv_playermodel = -1;\n\n\tfor (i=0 ; i<MAX_MODELS ; i++)\n\t{\n\t\tif (!sv.model_precache[i])\n\t\t\tbreak;\n\t\tif (!strcmp(sv.model_precache[i],\"progs/spike.mdl\"))\n\t\t\tsv_nailmodel = i;\n\t\tif (!strcmp(sv.model_precache[i],\"progs/s_spike.mdl\"))\n\t\t\tsv_supernailmodel = i;\n\t\tif (!strcmp(sv.model_precache[i],\"progs/player.mdl\"))\n\t\t\tsv_playermodel = i;\n\t}\n}\n\n\n/*\n==================\nSV_WriteClientdataToMessage\n \n==================\n*/\nvoid SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg)\n{\n\tint\t\ti, clnum;\n\tedict_t\t*other;\n\tedict_t\t*ent;\n\n\tent = client->edict;\n\n\tclnum = NUM_FOR_EDICT(ent) - 1;\n\n\t// send the chokecount for r_netgraph\n\tif (client->chokecount)\n\t{\n\t\tMSG_WriteByte (msg, svc_chokecount);\n\t\tMSG_WriteByte (msg, client->chokecount);\n\t\tclient->chokecount = 0;\n\t}\n\n\t// send a damage message if the player got hit this frame\n\tif (ent->v->dmg_take || ent->v->dmg_save)\n\t{\n\t\tother = PROG_TO_EDICT(ent->v->dmg_inflictor);\n\t\tMSG_WriteByte (msg, svc_damage);\n\t\tMSG_WriteByte (msg, ent->v->dmg_save);\n\t\tMSG_WriteByte (msg, ent->v->dmg_take);\n\t\tfor (i=0 ; i<3 ; i++)\n\t\t\tMSG_WriteCoord (msg, other->v->origin[i] + 0.5*(other->v->mins[i] + other->v->maxs[i]));\n\n\t\tent->v->dmg_take = 0;\n\t\tent->v->dmg_save = 0;\n\t}\n\n\t// add this to server demo\n\tif (sv.mvdrecording && msg->cursize)\n\t{\n\t\tif (MVDWrite_Begin(dem_single, clnum, msg->cursize))\n\t\t{\n\t\t\tMVD_SZ_Write(msg->data, msg->cursize);\n\t\t}\n\t}\n\n\t// a fixangle might get lost in a dropped packet.  Oh well.\n\tif (ent->v->fixangle)\n\t{\n\t\tent->v->fixangle = 0;\n\t\tdemo.fixangle[clnum] = true;\n\n\t\tMSG_WriteByte(msg, svc_setangle);\n\n#ifdef MVD_PEXT1_HIGHLAGTELEPORT\n\t\tif (client->mvdprotocolextensions1 & MVD_PEXT1_HIGHLAGTELEPORT) {\n\t\t\tif (fofs_teleported) {\n\t\t\t\tclient->lastteleport_teleport = ((eval_t *)((byte *)(client->edict)->v + fofs_teleported))->_int;\n\t\t\t\tif (client->lastteleport_teleport) {\n\t\t\t\t\tMSG_WriteByte(msg, 1); // signal a teleport\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tMSG_WriteByte(msg, 2); // respawn\n\t\t\t\t}\n\t\t\t\tclient->lastteleport_outgoingseq = client->netchan.outgoing_sequence;\n\t\t\t\tclient->lastteleport_incomingseq = client->netchan.incoming_sequence;\n\t\t\t\tclient->lastteleport_teleportyaw = (client->edict)->v->angles[YAW] - client->lastcmd.angles[YAW];\n\n\t\t\t\t((eval_t *)((byte *)(client->edict)->v + fofs_teleported))->_int = 0;\n\t\t\t\tSV_RotateCmd(client, &client->lastcmd);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tMSG_WriteByte(msg, 0); // we don't know, so no changes made...\n\t\t\t}\n\t\t}\n#endif\n\n\t\tfor (i=0 ; i < 3 ; i++)\n\t\t\tMSG_WriteAngle (msg, ent->v->angles[i] );\n\n\t\tif (sv.mvdrecording)\n\t\t{\n\t\t\tMSG_WriteByte (&demo.datagram, svc_setangle);\n\t\t\tMSG_WriteByte (&demo.datagram, clnum);\n\t\t\tfor (i=0 ; i < 3 ; i++)\n\t\t\t\tMSG_WriteAngle (&demo.datagram, ent->v->angles[i] );\n\t\t}\n\t}\n\n\t// Z_EXT_TIME protocol extension\n\t// every now and then, send an update so that extrapolation\n\t// on client side doesn't stray too far off\n#ifdef FTE_PEXT_ACCURATETIMINGS\n\tif (client->fteprotocolextensions & FTE_PEXT_ACCURATETIMINGS)\n\t{\t//the fte pext causes the server to send out accurate timings, allowing for perfect interpolation.\n\t\tif (sv.physicstime - client->lastservertimeupdate > 0)\n\t\t{\n\t\t\tMSG_WriteByte(msg, svc_updatestatlong);\n\t\t\tMSG_WriteByte(msg, STAT_TIME);\n\t\t\tMSG_WriteLong(msg, (int)(sv.physicstime * 1000));\n\n\t\t\tclient->lastservertimeupdate = sv.physicstime;\n\t\t}\n\t}\n\telse\n#endif\n\tif ((SERVER_EXTENSIONS & Z_EXT_SERVERTIME) && (client->extensions & Z_EXT_SERVERTIME))\n\t{\t//the zquake ext causes the server to send out peridoic timings, allowing for moderatly accurate game time.\n\t\tif (realtime - client->lastservertimeupdate > 5)\n\t\t{\n\t\t\tMSG_WriteByte(msg, svc_updatestatlong);\n\t\t\tMSG_WriteByte(msg, STAT_TIME);\n\t\t\tMSG_WriteLong(msg, (int) (sv.time * 1000));\n\n\t\t\tclient->lastservertimeupdate = realtime;\n\t\t}\n\t}\n}\n\n/*\n=======================\nSV_UpdateClientStats\n \nPerforms a delta update of the stats array.  This should only be performed\nwhen a reliable message can be delivered this frame.\n=======================\n*/\nvoid SV_UpdateClientStats (client_t *client)\n{\n\tedict_t *ent;\n\tint stats[MAX_CL_STATS], i;\n\n\tmemset (stats, 0, sizeof(stats));\n\n\tent = client->edict;\n\n\t// if we are a spectator and we are tracking a player, we get his stats\n\t// so our status bar reflects his\n\tif (client->spectator && client->spec_track > 0)\n\t\tent = svs.clients[client->spec_track - 1].edict;\n\n\t// in case of trackent we have to reflect his stats like for spectator.\n\tif (fofs_trackent)\n\t{\n\t\tint trackent = ((eval_t *)((byte *)(client->edict)->v + fofs_trackent))->_int;\n\t\tif (trackent < 1 || trackent > MAX_CLIENTS || svs.clients[trackent - 1].state != cs_spawned)\n\t\t\ttrackent = 0;\n\n\t\tif (trackent)\n\t\t\tent = svs.clients[trackent - 1].edict;\n\t}\n\n\tstats[STAT_HEALTH] = ent->v->health;\n\tstats[STAT_WEAPON] = SV_ModelIndex(PR_GetEntityString(ent->v->weaponmodel));\n\tstats[STAT_AMMO] = ent->v->currentammo;\n\tstats[STAT_ARMOR] = ent->v->armorvalue;\n\tstats[STAT_SHELLS] = ent->v->ammo_shells;\n\tstats[STAT_NAILS] = ent->v->ammo_nails;\n\tstats[STAT_ROCKETS] = ent->v->ammo_rockets;\n\tstats[STAT_CELLS] = ent->v->ammo_cells;\n\tif (!client->spectator || client->spec_track > 0)\n\t\tstats[STAT_ACTIVEWEAPON] = ent->v->weapon;\n\t// stuff the sigil bits into the high bits of items for sbar\n\tstats[STAT_ITEMS] = (int) ent->v->items | ((int) PR_GLOBAL(serverflags) << 28);\n\tif (fofs_items2)\t// ZQ_ITEMS2 extension\n\t\tstats[STAT_ITEMS] |= (int)EdictFieldFloat(ent, fofs_items2) << 23;\n\n\tif (ent->v->health > 0 || client->spectator) // viewheight for PF_DEAD & PF_GIB is hardwired\n\t\tstats[STAT_VIEWHEIGHT] = ent->v->view_ofs[2];\n\n\tfor (i=0 ; i<MAX_CL_STATS ; i++)\n\t\tif (stats[i] != client->stats[i])\n\t\t{\n\t\t\tclient->stats[i] = stats[i];\n\t\t\tif (stats[i] >=0 && stats[i] <= 255)\n\t\t\t{\n\t\t\t\tClientReliableWrite_Begin(client, svc_updatestat, 3);\n\t\t\t\tClientReliableWrite_Byte(client, i);\n\t\t\t\tClientReliableWrite_Byte(client, stats[i]);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tClientReliableWrite_Begin(client, svc_updatestatlong, 6);\n\t\t\t\tClientReliableWrite_Byte(client, i);\n\t\t\t\tClientReliableWrite_Long(client, stats[i]);\n\t\t\t}\n\t\t}\n}\n\n/*\n=======================\nSV_SendClientDatagram\n=======================\n*/\nvoid SV_SendClientDatagram (client_t *client, int client_num)\n{\n\tbyte\t\tbuf[MAX_DATAGRAM];\n\tsizebuf_t\tmsg;\n\t//\tpacket_t\t*pack;\n\n\tSZ_InitEx(&msg, buf, sizeof(buf), true);\n\n\t// for faster downloading skip half the frames\n\t/*if (client->download && client->netchan.outgoing_sequence & 1)\n\t{\n\t\t// we're sending fake invalid delta update, so that client won't update screen\n\t\tMSG_WriteByte (&msg, svc_deltapacketentities);\n\t\tMSG_WriteByte (&msg, 0);\n\t\tMSG_WriteShort (&msg, 0);\n\n\t\tNetchan_Transmit (&client->netchan, msg.cursize, buf);\n\t}\n\t*/\n\n\tif (!SV_SkipCommsBotMessage(client)) {\n\t\t// add the client specific data to the datagram\n\t\tSV_WriteClientdataToMessage(client, &msg);\n\n\t\t// send over all the objects that are in the PVS\n\t\t// this will include clients, a packetentities, and\n\t\t// possibly a nails update\n\t\tSV_WriteEntitiesToClient(client, &msg, false);\n\n#ifdef FTE_PEXT2_VOICECHAT\n\t\tSV_VoiceSendPacket(client, &msg);\n#endif\n\t}\n\n\t// copy the accumulated multicast datagram\n\t// for this client out to the message\n\tif (client->datagram.overflowed)\n\t\tCon_Printf (\"WARNING: datagram overflowed for %s\\n\", client->name);\n\telse\n\t\tSZ_Write (&msg, client->datagram.data, client->datagram.cursize);\n\tSZ_Clear (&client->datagram);\n\n\t// send deltas over reliable stream\n\tif (Netchan_CanReliable (&client->netchan))\n\t\tSV_UpdateClientStats (client);\n\n\tif (msg.overflowed)\n\t{\n\t\tCon_Printf (\"WARNING: msg overflowed for %s\\n\", client->name);\n\t\tSZ_Clear (&msg);\n\t}\n\n\t// send the datagram\n\tNetchan_Transmit (&client->netchan, msg.cursize, buf);\n}\n\n/*\n=======================\nSV_UpdateToReliableMessages\n=======================\n*/\nstatic void SV_UpdateToReliableMessages (void)\n{\n\tint i, j;\n\tclient_t *client;\n\tedict_t *ent;\n\n\t// check for changes to be sent over the reliable streams to all clients\n\tfor (i=0, sv_client = svs.clients ; i<MAX_CLIENTS ; i++, sv_client++)\n\t{\n\t\tif (sv_client->state != cs_spawned)\n\t\t\tcontinue;\n\n\t\tif (sv_client->sendinfo)\n\t\t{\n\t\t\tsv_client->sendinfo = false;\n\t\t\tSV_FullClientUpdate (sv_client, &sv.reliable_datagram);\n\t\t}\n\n\t\tent = sv_client->edict;\n\n\t\tif (sv_client->old_frags != (int)ent->v->frags)\n\t\t{\n\t\t\tfor (j=0, client = svs.clients ; j<MAX_CLIENTS ; j++, client++)\n\t\t\t{\n\t\t\t\tif (client->state < cs_preconnected)\n\t\t\t\t\tcontinue;\n\t\t\t\tClientReliableWrite_Begin(client, svc_updatefrags, 4);\n\t\t\t\tClientReliableWrite_Byte(client, i);\n\t\t\t\tClientReliableWrite_Short(client, (int) ent->v->frags);\n\t\t\t}\n\n\t\t\tif (sv.mvdrecording)\n\t\t\t{\n\t\t\t\tif (MVDWrite_Begin(dem_all, 0, 4))\n\t\t\t\t{\n\t\t\t\t\tMVD_MSG_WriteByte(svc_updatefrags);\n\t\t\t\t\tMVD_MSG_WriteByte(i);\n\t\t\t\t\tMVD_MSG_WriteShort((int)ent->v->frags);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsv_client->old_frags = (int) ent->v->frags;\n\t\t}\n\n\t\t// maxspeed/entgravity changes\n\t\tif (fofs_gravity && sv_client->entgravity != EdictFieldFloat(ent, fofs_gravity))\n\t\t{\n\t\t\tsv_client->entgravity = EdictFieldFloat(ent, fofs_gravity);\n\t\t\tClientReliableWrite_Begin(sv_client, svc_entgravity, 5);\n\t\t\tClientReliableWrite_Float(sv_client, sv_client->entgravity);\n\t\t\tif (sv.mvdrecording)\n\t\t\t{\n\t\t\t\tif (MVDWrite_Begin(dem_single, i, 5))\n\t\t\t\t{\n\t\t\t\t\tMVD_MSG_WriteByte(svc_entgravity);\n\t\t\t\t\tMVD_MSG_WriteFloat(sv_client->entgravity);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (fofs_maxspeed && sv_client->maxspeed != EdictFieldFloat(ent, fofs_maxspeed))\n\t\t{\n\t\t\tsv_client->maxspeed = EdictFieldFloat(ent, fofs_maxspeed);\n\t\t\tClientReliableWrite_Begin(sv_client, svc_maxspeed, 5);\n\t\t\tClientReliableWrite_Float(sv_client, sv_client->maxspeed);\n\t\t\tif (sv.mvdrecording)\n\t\t\t{\n\t\t\t\tif (MVDWrite_Begin(dem_single, i, 5))\n\t\t\t\t{\n\t\t\t\t\tMVD_MSG_WriteByte(svc_maxspeed);\n\t\t\t\t\tMVD_MSG_WriteFloat(sv_client->maxspeed);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\tif (sv.datagram.overflowed)\n\t\tSZ_Clear (&sv.datagram);\n\n\t// append the broadcast messages to each client messages\n\tfor (j=0, client = svs.clients ; j<MAX_CLIENTS ; j++, client++)\n\t{\n\t\tif (client->state < cs_preconnected)\n\t\t\tcontinue; // reliables go to all connected or spawned\n\n\t\tClientReliableCheckBlock(client, sv.reliable_datagram.cursize);\n\t\tClientReliableWrite_SZ(client, sv.reliable_datagram.data, sv.reliable_datagram.cursize);\n\n\t\tif (client->state != cs_spawned)\n\t\t\tcontinue; // datagrams only go to spawned\n\n\t\tSZ_Write (&client->datagram, sv.datagram.data, sv.datagram.cursize);\n\t}\n\n\tif (sv.mvdrecording && sv.reliable_datagram.cursize)\n\t{\n\t\tif (MVDWrite_Begin(dem_all, 0, sv.reliable_datagram.cursize))\n\t\t{\n\t\t\tMVD_SZ_Write(sv.reliable_datagram.data, sv.reliable_datagram.cursize);\n\t\t}\n\t}\n\n\tif (sv.mvdrecording)\n\t\tSZ_Write(&demo.datagram, sv.datagram.data, sv.datagram.cursize); // FIXME: ???\n\n\tSZ_Clear (&sv.reliable_datagram);\n\tSZ_Clear (&sv.datagram);\n}\n\n//#ifdef _WIN32\n//#pragma optimize( \"\", off )\n//#endif\n\n\n\n/*\n=======================\nSV_SendClientMessages\n=======================\n*/\nvoid SV_SendClientMessages (void)\n{\n\tint\t\t\ti, j;\n\tclient_t\t*c;\n\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\t// update frags, names, etc\n\tSV_UpdateToReliableMessages ();\n\n\tif (fofs_visibility) {\n\t\tfor (i = 0; i < MAX_CLIENTS; ++i) {\n\t\t\t((eval_t *)((byte *)(svs.clients[i].edict)->v + fofs_visibility))->_int = 0;\n\t\t}\n\t}\n\n\t// build individual updates\n\tfor (i=0, c = svs.clients ; i<MAX_CLIENTS ; i++, c++)\n\t{\n\t\tif (!c->state)\n\t\t\tcontinue;\n\n\t\tif (c->drop)\n\t\t{\n\t\t\tSV_DropClient(c);\n\t\t\tc->drop = false;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// check to see if we have a backbuf to stick in the reliable\n\t\tif (c->num_backbuf)\n\t\t{\n\t\t\t// will it fit?\n\t\t\tif (c->netchan.message.cursize + c->backbuf_size[0] <\n\t\t\t        c->netchan.message.maxsize)\n\t\t\t{\n\n\t\t\t\tCon_DPrintf(\"%s: backbuf %d bytes\\n\",\n\t\t\t\t            c->name, c->backbuf_size[0]);\n\n\t\t\t\t// it'll fit\n\t\t\t\tSZ_Write(&c->netchan.message, c->backbuf_data[0],\n\t\t\t\t         c->backbuf_size[0]);\n\n\t\t\t\t//move along, move along\n\t\t\t\tfor (j = 1; j < c->num_backbuf; j++)\n\t\t\t\t{\n\t\t\t\t\tmemcpy(c->backbuf_data[j - 1], c->backbuf_data[j],\n\t\t\t\t\t       c->backbuf_size[j]);\n\t\t\t\t\tc->backbuf_size[j - 1] = c->backbuf_size[j];\n\t\t\t\t}\n\n\t\t\t\tc->num_backbuf--;\n\t\t\t\tif (c->num_backbuf)\n\t\t\t\t{\n\t\t\t\t\tmemset(&c->backbuf, 0, sizeof(c->backbuf));\n\t\t\t\t\tc->backbuf.data = c->backbuf_data[c->num_backbuf - 1];\n\t\t\t\t\tc->backbuf.cursize = c->backbuf_size[c->num_backbuf - 1];\n\t\t\t\t\tc->backbuf.maxsize = c->netchan.message.maxsize;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n#ifdef USE_PR2\n\t\tif (c->isBot) {\n\t\t\t// Write damage to bot clients too (for mvd playback)\n\t\t\tSV_BotWriteDamage(c, i);\n\n\t\t\tSZ_Clear(&c->netchan.message);\n\t\t\tSZ_Clear(&c->datagram);\n\t\t\tc->num_backbuf = 0;\n\n\t\t\t// Need to tell mod what the bot would have seen\n\t\t\tSV_SetVisibleEntitiesForBot(c);\n\t\t\tcontinue;\n\t\t}\n#endif\n\t\t// if the reliable message overflowed,\n\t\t// drop the client\n\t\tif (c->netchan.message.overflowed)\n\t\t{\n\t\t\tSZ_Clear (&c->netchan.message);\n\t\t\tSZ_Clear (&c->datagram);\n\t\t\tSV_BroadcastPrintf (PRINT_HIGH, \"%s overflowed\\n\", c->name);\n\t\t\tCon_Printf (\"WARNING: reliable overflow for %s\\n\",c->name);\n\t\t\tSV_DropClient (c);\n\t\t\tc->send_message = true;\n\t\t\tc->netchan.cleartime = 0;\t// don't choke this message\n\t\t}\n\n\t\t// only send messages if the client has sent one\n\t\t// and the bandwidth is not choked\n\t\tif (!c->send_message)\n\t\t\tcontinue;\n\t\tc->send_message = false;\t// try putting this after choke?\n\t\tif (!sv.paused && !Netchan_CanPacket (&c->netchan))\n\t\t{\n\t\t\tc->chokecount++;\n\t\t\tcontinue;\t\t// bandwidth choke\n\t\t}\n\n\t\tif (c->state == cs_spawned)\n\t\t\tSV_SendClientDatagram (c, i);\n\t\telse {\n\t\t\tNetchan_Transmit (&c->netchan, c->datagram.cursize, c->datagram.data);\t// just update reliable\n\t\t\tc->datagram.cursize = 0;\n\t\t}\n\t}\n}\n\nstatic void SV_BotWriteDamage(client_t* c, int i)\n{\n\tedict_t* ent = c->edict;\n\n\tif (c->edict->v->dmg_take || c->edict->v->dmg_save) {\n\t\tif (ent->v->dmg_take || ent->v->dmg_save) {\n\t\t\tint length = 3 + 3 * msg_coordsize;\n\n\t\t\tif (MVDWrite_Begin(dem_single, i, length)) {\n\t\t\t\tedict_t* other = PROG_TO_EDICT(ent->v->dmg_inflictor);\n\n\t\t\t\tMVD_MSG_WriteByte(svc_damage);\n\t\t\t\tMVD_MSG_WriteByte(ent->v->dmg_save);\n\t\t\t\tMVD_MSG_WriteByte(ent->v->dmg_take);\n\t\t\t\tfor (i = 0; i < 3; i++)\n\t\t\t\t\tMVD_MSG_WriteCoord(other->v->origin[i] + 0.5 * (other->v->mins[i] + other->v->maxs[i]));\n\t\t\t}\n\n\t\t\tent->v->dmg_take = 0;\n\t\t\tent->v->dmg_save = 0;\n\t\t}\n\t}\n}\n\nvoid SV_MVDPings (void)\n{\n\tclient_t *client;\n\tint j;\n\n\tfor (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)\n\t{\n\t\tif (client->state != cs_spawned)\n\t\t\tcontinue;\n\n\t\tif (MVDWrite_Begin (dem_all, 0, 7))\n\t\t{\n\t\t\tMVD_MSG_WriteByte (svc_updateping);\n\t\t\tMVD_MSG_WriteByte (j);\n\t\t\tMVD_MSG_WriteShort(SV_CalcPing(client));\n\t\t\tMVD_MSG_WriteByte (svc_updatepl);\n\t\t\tMVD_MSG_WriteByte (j);\n\t\t\tMVD_MSG_WriteByte (client->lossage);\n\t\t}\n\t}\n}\n\nvoid MVD_WriteStats(void)\n{\n\tclient_t\t*c;\n\tint i, j;\n\tedict_t\t\t*ent;\n\tint\t\t\tstats[MAX_CL_STATS];\n\n\tfor (i = 0, c = svs.clients ; i < MAX_CLIENTS ; i++, c++)\n\t{\n\t\tif (c->state != cs_spawned)\n\t\t\tcontinue;\t// datagrams only go to spawned\n\n\t\tif (c->spectator)\n\t\t\tcontinue;\n\n\t\tent = c->edict;\n\t\tmemset (stats, 0, sizeof(stats));\n\n\t\tstats[STAT_HEALTH] = ent->v->health;\n\t\tstats[STAT_WEAPON] = SV_ModelIndex(PR_GetEntityString(ent->v->weaponmodel));\n\t\tstats[STAT_AMMO] = ent->v->currentammo;\n\t\tstats[STAT_ARMOR] = ent->v->armorvalue;\n\t\tstats[STAT_SHELLS] = ent->v->ammo_shells;\n\t\tstats[STAT_NAILS] = ent->v->ammo_nails;\n\t\tstats[STAT_ROCKETS] = ent->v->ammo_rockets;\n\t\tstats[STAT_CELLS] = ent->v->ammo_cells;\n\t\tstats[STAT_ACTIVEWEAPON] = ent->v->weapon;\n\n\t\tif (ent->v->health > 0) // viewheight for PF_DEAD & PF_GIB is hardwired\n\t\t\tstats[STAT_VIEWHEIGHT] = ent->v->view_ofs[2];\n\n\t\t// stuff the sigil bits into the high bits of items for sbar\n\t\tstats[STAT_ITEMS] = (int) ent->v->items | ((int) PR_GLOBAL(serverflags) << 28);\n\n\t\tfor (j = 0 ; j < MAX_CL_STATS; j++)\n\t\t{\n\t\t\tif (stats[j] != demo.stats[i][j])\n\t\t\t{\n\t\t\t\tdemo.stats[i][j] = stats[j];\n\t\t\t\tif (stats[j] >= 0 && stats[j] <= 255)\n\t\t\t\t{\n\t\t\t\t\tif (MVDWrite_Begin(dem_stats, i, 3))\n\t\t\t\t\t{\n\t\t\t\t\t\tMVD_MSG_WriteByte(svc_updatestat);\n\t\t\t\t\t\tMVD_MSG_WriteByte(j);\n\t\t\t\t\t\tMVD_MSG_WriteByte(stats[j]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif (MVDWrite_Begin(dem_stats, i, 6))\n\t\t\t\t\t{\n\t\t\t\t\t\tMVD_MSG_WriteByte(svc_updatestatlong);\n\t\t\t\t\t\tMVD_MSG_WriteByte(j);\n\t\t\t\t\t\tMVD_MSG_WriteLong(stats[j]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid DestFlush(qbool compleate);\nvoid SV_MVD_RunPendingConnections(void);\nvoid QTV_ReadDests( void );\n\nvoid SV_SendDemoMessage(void)\n{\n\tint\t\t\ti, cls = 0;\n\tclient_t\t*c;\n\tsizebuf_t\tmsg;\n\tbyte\t\tmsg_buf[MAX_MVD_SIZE]; // data without mvd header\n\n\tfloat\t\tmin_fps;\n\textern\t\tcvar_t sv_demofps, sv_demoIdlefps;\n\textern\t\tcvar_t sv_demoPings;\n\n\tif (sv.state != ss_active)\n\t\treturn;\n\n\tSV_MVD_RunPendingConnections();\n\n\tif (!sv.mvdrecording)\n\t{\n\t\tDestFlush(false); // well, this may help close some fucked up dests\n\t\treturn;\n\t}\n\n\tfor (i = 0, c = svs.clients; i < MAX_CLIENTS; i++, c++)\n\t{\n\t\tif (c->state != cs_spawned)\n\t\t\tcontinue;\t// datagrams only go to spawned\n\n\t\tcls |= 1 << i;\n\t}\n\n\t// if no players or paused, use idle fps\n\tif (cls && !sv.paused)\n\t\tmin_fps = max(4.0, (int)sv_demofps.value ? (int)sv_demofps.value : 20.0);\n\telse\n\t\tmin_fps = bound(4.0, (int)sv_demoIdlefps.value, 30);\n\n\tif (curtime - demo.curtime < 1.0 / min_fps) {\n\t\treturn;\n\t}\n\n\tSZ_InitEx(&msg, msg_buf, sizeof(msg_buf), true);\n\n\tif ((int)sv_demoPings.value)\n\t{\n\t\tif (curtime - demo.pingtime > sv_demoPings.value)\n\t\t{\n\t\t\tSV_MVDPings();\n\t\t\tdemo.pingtime = curtime;\n\t\t}\n\t}\n\n\tMVD_WriteStats();\n\n\t// send over all the objects that are in the PVS\n\t// this will include clients, a packetentities, and\n\t// possibly a nails update\n\n\tif (!demo.recorder.delta_sequence)\n\t\tdemo.recorder.delta_sequence = -1;\n\n\tSV_WriteEntitiesToClient (&demo.recorder, &msg, true);\n\n\tif (msg.overflowed)\n\t{\n\t\tSys_Printf(\"WARNING: msg overflowed in SV_SendDemoMessage\\n\");\n\t}\n\telse\n\t{\n\t\tif (msg.cursize)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_all, 0, msg.cursize))\n\t\t\t{\n\t\t\t\tMVD_SZ_Write(msg.data, msg.cursize);\n\t\t\t}\n\t\t}\n\t}\n\n\tSZ_Clear(&msg);\n\n\t// copy the accumulated multicast datagram\n\t// for this client out to the message\n\tif (demo.datagram.overflowed)\n\t{\n\t\tSys_Printf(\"WARNING: demo.datagram overflowed in SV_SendDemoMessage\\n\");\n\t}\n\telse\n\t{\n\t\tif (demo.datagram.cursize)\n\t\t{\n\t\t\tif (MVDWrite_Begin(dem_all, 0, demo.datagram.cursize))\n\t\t\t{\n\t\t\t\tMVD_SZ_Write (demo.datagram.data, demo.datagram.cursize);\n\t\t\t}\n\t\t}\n\t}\n\n\tSZ_Clear (&demo.datagram);\n\n\tdemo.recorder.delta_sequence = demo.recorder.netchan.incoming_sequence&255;\n\tdemo.recorder.netchan.incoming_sequence++;\n\tdemo.frames[demo.parsecount & UPDATE_MASK].time = sv.time;\n\tdemo.frames[demo.parsecount & UPDATE_MASK].paused = sv.paused;\n\tdemo.frames[demo.parsecount & UPDATE_MASK].pause_duration = (int)bound(0, (curtime - demo.curtime) * 1000.0f, 255);\n\tdemo.curtime = curtime;\n\tdemo.time = sv.time;\n\n\t// let's not wait so much time (was 60)\n\tif (demo.parsecount - demo.lastwritten > 5) {\n\t\tSV_MVDWritePackets(1);\n\t}\n\n\t// flush once per demo frame\n\tDestFlush(false);\n\n\t// read QTV input once per demo frame\n\tQTV_ReadDests();\n\n\tif (!sv.mvdrecording)\n\t\treturn;\n\n\tdemo.parsecount++;\n}\n\n\n//#ifdef _WIN32\n//#pragma optimize( \"\", on )\n//#endif\n\n\n\n/*\n=======================\nSV_SendMessagesToAll\n \nFIXME: does this sequence right?\n=======================\n*/\nvoid SV_SendMessagesToAll (void)\n{\n\tint\t\t\ti;\n\tclient_t\t*c;\n\n\tfor (i=0, c = svs.clients ; i<MAX_CLIENTS ; i++, c++)\n\t\tif (c->state >= cs_connected)\t\t// FIXME: should this only send to active?\n\t\t\tc->send_message = true;\n\n\tSV_SendClientMessages ();\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_sys_unix.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: sv_sys_unix.c,v 1.11 2006-12-30 11:24:54 disconn3ct Exp $\n\n*/\n#include <sys/types.h>\n#include \"qwsvdef.h\"\n\n#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)\n#include <sys/stat.h>\n#include <unistd.h>\n#include <sys/time.h>\n#include <errno.h>\n#else\n#include <sys/dir.h>\n#endif\n\ncvar_t\tsys_nostdout = {\"sys_nostdout\",\"0\"};\ncvar_t\tsys_extrasleep = {\"sys_extrasleep\",\"0\"};\n\nqbool stdin_ready;\nint do_stdin = 1;\n\n/*\n===============================================================================\n\n\t\t\t\tREQUIRED SYS FUNCTIONS\n\n===============================================================================\n*/\n\n\n/*\n============\nSys_mkdir\n\n============\n*/\nvoid Sys_mkdir (const char *path)\n{\n\tif (mkdir (path, 0777) != -1)\n\t\treturn;\n\tif (errno != EEXIST)\n\t\tSys_Error (\"mkdir %s: %s\",path, strerror(errno)); \n}\n\n\n/*\n================\nSys_DoubleTime\n================\n*/\ndouble Sys_DoubleTime (void)\n{\n\tstruct timeval tp;\n\tstruct timezone tzp;\n\tstatic int\t\tsecbase;\n\n\tgettimeofday(&tp, &tzp);\n\t\n\tif (!secbase)\n\t{\n\t\tsecbase = tp.tv_sec;\n\t\treturn tp.tv_usec/1000000.0;\n\t}\n\t\n\treturn (tp.tv_sec - secbase) + tp.tv_usec/1000000.0;\n}\n\n\n/*\n================\nSys_Error\n================\n*/\nvoid Sys_Error (char *error, ...)\n{\n\tva_list argptr;\n\tchar string[1024];\n\t\n\tva_start (argptr ,error);\n\tvsnprintf (string, sizeof(string), error, argptr);\n\tva_end (argptr);\n\tprintf (\"Fatal error: %s\\n\",string);\n\t\n\texit (1);\n}\n\n\n/*\n================\nSys_Printf\n================\n*/\nvoid Sys_Printf (char *fmt, ...)\n{\n\tva_list argptr;\n\tstatic char text[2048];\n\tunsigned char *p;\n\n\tva_start (argptr, fmt);\n\tvsnprintf (text, sizeof(text), fmt, argptr);\n\tva_end (argptr);\n\n\tif (sys_nostdout.value)\n\t\treturn;\n\n\tfor (p = (unsigned char *)text; *p; p++) {\n\t\t*p &= 0x7f;\n\t\tif ((*p > 128 || *p < 32) && *p != 10 && *p != 13 && *p != 9)\n\t\t\tprintf(\"[%02x]\", *p);\n\t\telse\n\t\t\tputc(*p, stdout);\n\t}\n\tfflush(stdout);\n}\n\n\n/*\n================\nSys_Quit\n================\n*/\nvoid Sys_Quit (void)\n{\n\texit (0);\t\t// appkit isn't running\n}\n\n\n/*\n=============\nSys_Init\n\nQuake calls this so the system can register variables before host_hunklevel\nis marked\n=============\n*/\nvoid Sys_Init (void)\n{\n\tCvar_Register (&sys_nostdout);\n\tCvar_Register (&sys_extrasleep);\n}\n\n/*\n=============\nmain\n=============\n*/\nint main (int argc, char *argv[])\n{\n\tdouble\ttime, oldtime, newtime;\n\n\tHost_Init (argc, argv, 16*1024*1024);\n\n//\n// main loop\n//\n\toldtime = Sys_DoubleTime () - 0.1;\n\twhile (1)\n\t{\n\t\t// select on the net socket and stdin\n\t\tNET_Sleep (10);\n\n\t\t// find time passed since last cycle\n\t\tnewtime = Sys_DoubleTime ();\n\t\ttime = newtime - oldtime;\n\t\toldtime = newtime;\n\t\t\n\t\tHost_Frame (time);\t\t\n\t\t\n\t\t// extrasleep is just a way to generate a fucked up connection on purpose\n\t\tif (sys_extrasleep.value)\n\t\t\tusleep (sys_extrasleep.value);\n\t}\t\n}\n\n"
  },
  {
    "path": "src/sv_sys_win.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n \nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n \nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n \nSee the GNU General Public License for more details.\n \nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n \n*/\n\n#include \"qwsvdef.h\"\n#include <mmsystem.h>\n#include <io.h>\n\nextern cvar_t sys_restart_on_error;\nextern cvar_t sys_select_timeout, sys_simulation;\n\ncvar_t\tsys_nostdout\t= {\"sys_nostdout\", \"0\"};\ncvar_t\tsys_sleep\t\t= {\"sys_sleep\", \"8\"};\n\nstatic char title[16];\n\nstatic qbool\tisdaemon = false;\n\n//==============================================================================\n// WINDOWS CMD LINE CRAP\n//==============================================================================\n\nstatic int\t\t\targc;\nstatic char\t\t\t*argv[MAX_NUM_ARGVS];\n\nchar *Sys_GetModuleName(void)\n{\n\tstatic char\texename[1024] = {0}; // static\n\tint i;\n\n\tif (exename[0])\n\t\treturn exename;\n\n\tif(!(i = GetModuleFileName(NULL, exename, sizeof(exename)-1))) // here we get loong string, with full path\n\t{\n\t\texename[0] = 0; // oh, something bad\n\t}\n\telse \n\t{\n\t\texename[i] = 0; // ensure null terminator\n\t}\n\n\treturn exename;\n}\n\n#ifdef _CONSOLE\nvoid ParseCommandLine (int ac, char **av)\n{\n\targc = 1;\n\targv[0] = Sys_GetModuleName();\n\n\tfor( ; argc < MAX_NUM_ARGVS && ac > 0; argc++, ac--)\n\t{\n\t\targv[argc] = av[argc-1];\n\t}\n}\n#else\nvoid ParseCommandLine (char *lpCmdLine)\n{\n\targc = 1;\n\targv[0] = Sys_GetModuleName();\n\n\twhile (*lpCmdLine && (argc < MAX_NUM_ARGVS))\n\t{\n\t\twhile (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126)))\n\t\t\tlpCmdLine++;\n\n\t\tif (*lpCmdLine)\n\t\t{\n\t\t\tif (*lpCmdLine == '\\\"')\n\t\t\t{\n\t\t\t\tlpCmdLine++;\n\n\t\t\t\targv[argc] = lpCmdLine;\n\t\t\t\targc++;\n\n\t\t\t\twhile (*lpCmdLine && *lpCmdLine != '\\\"') // this include chars less that 32 and greate than 126... is that evil?\n\t\t\t\t\tlpCmdLine++;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\targv[argc] = lpCmdLine;\n\t\t\t\targc++;\n\n\t\t\t\twhile (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126)))\n\t\t\t\t\tlpCmdLine++;\n\t\t\t}\n\n\t\t\tif (*lpCmdLine)\n\t\t\t{\n\t\t\t\t*lpCmdLine = 0;\n\t\t\t\tlpCmdLine++;\n\t\t\t}\n\t\t}\n\t}\n}\n#endif\n\n/*\n================\nSys_FileTime\n================\n*/\nint\tSys_FileTime (const char *path)\n{\n\tstruct _stat buf;\n\treturn _stat (path, &buf) == -1 ? -1 : buf.st_mtime;\n}\n\n/*\n================\nSys_mkdir\n================\n*/\nvoid Sys_mkdir (const char *path)\n{\n\t_mkdir(path);\n}\n\n/*\n================\nSys_remove\n================\n*/\nint Sys_remove (const char *path)\n{\n\treturn remove(path);\n}\n\n//bliP: rmdir ->\n/*\n================\nSys_rmdir\n================\n*/\nint Sys_rmdir (const char *path)\n{\n\treturn _rmdir(path);\n}\n//<-\n\n/*\n================\nSys_listdir\n================\n*/\n\ndir_t Sys_listdir (const char *path, const char *ext, int sort_type)\n{\n\tstatic file_t\tlist[MAX_DIRFILES];\n\tdir_t\tdir;\n\tHANDLE\th;\n\tWIN32_FIND_DATA fd;\n\tchar\tpathname[MAX_DEMO_NAME];\n\tqbool all;\n\n\tPCRE2_SIZE\terror_offset;\n\tpcre2_code\t*preg;\n\tpcre2_match_data *match_data = NULL;\n\tint error;\n\n\tmemset(list, 0, sizeof(list));\n\tmemset(&dir, 0, sizeof(dir));\n\n\tdir.files = list;\n\tall = !strncmp(ext, \".*\", 3);\n\tif (!all)\n\t\tif (!(preg = pcre2_compile((PCRE2_SPTR)ext, PCRE2_ZERO_TERMINATED, PCRE2_CASELESS, &error, &error_offset, NULL)))\n\t\t{\n\t\t\tPCRE2_UCHAR error_str[256];\n\t\t\tpcre2_get_error_message(error, error_str, sizeof(error_str));\n\t\t\tCon_Printf(\"Sys_listdir: pcre2_compile(%s) error: %s at offset %d\\n\",\n\t\t\t           ext, error_str, error_offset);\n\t\t\tpcre2_code_free(preg);\n\t\t\treturn dir;\n\t\t}\n\n\tsnprintf(pathname, sizeof(pathname), \"%s/*.*\", path);\n\tif ((h = FindFirstFile (pathname , &fd)) == INVALID_HANDLE_VALUE)\n\t{\n\t\tif (!all)\n\t\t\tpcre2_code_free(preg);\n\t\treturn dir;\n\t}\n\n\tdo\n\t{\n\t\tif (!strncmp(fd.cFileName, \".\", 2) || !strncmp(fd.cFileName, \"..\", 3))\n\t\t\tcontinue;\n\t\tif (!all)\n\t\t{\n\t\t\tmatch_data = pcre2_match_data_create_from_pattern(preg, NULL);\n\t\t\terror = pcre2_match(preg, (PCRE2_SPTR)fd.cFileName,\n\t\t\t\tstrlen(fd.cFileName), 0, 0, match_data, NULL);\n\t\t\tif (error < 0) {\n\t\t\t\tif (error != PCRE2_ERROR_NOMATCH) {\n\t\t\t\t\tCon_Printf(\"Sys_listdir: pcre2_match(%s, %s) error code: %d\\n\",\n\t\t\t\t\t\text, fd.cFileName, error);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //bliP: list dir\n\t\t{\n\t\t\tdir.numdirs++;\n\t\t\tlist[dir.numfiles].isdir = true;\n\t\t\tlist[dir.numfiles].size = list[dir.numfiles].time = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlist[dir.numfiles].isdir = false;\n\t\t\tsnprintf(pathname, sizeof(pathname), \"%s/%s\", path, fd.cFileName);\n\t\t\tlist[dir.numfiles].time = Sys_FileTime(pathname);\n\t\t\tdir.size += (list[dir.numfiles].size = fd.nFileSizeLow);\n\t\t}\n\t\tstrlcpy (list[dir.numfiles].name, fd.cFileName, sizeof(list[0].name));\n\n\t\tif (++dir.numfiles == MAX_DIRFILES - 1)\n\t\t\tbreak;\n\n\t}\n\twhile (FindNextFile(h, &fd));\n\n\tFindClose (h);\n\tif (!all)\n\t\tpcre2_code_free(preg);\n\n\tswitch (sort_type)\n\t{\n\tcase SORT_NO: break;\n\tcase SORT_BY_DATE:\n\t\tqsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_date);\n\t\tbreak;\n\tcase SORT_BY_NAME:\n\t\tqsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_name);\n\t\tbreak;\n\t}\n\treturn dir;\n}\n\nint Sys_EnumerateFiles (char *gpath, char *match, int (*func)(char *, int, void *), void *parm)\n{\n\tHANDLE r;\n\tWIN32_FIND_DATA fd; \n\tchar apath[MAX_OSPATH];\n\tchar apath2[MAX_OSPATH];\n\tchar file[MAX_OSPATH];\n\tchar *s;\n\tint go;\n\tif (!gpath)\n\t\treturn 0;\n\n\tsnprintf(apath, sizeof(apath), \"%s/%s\", gpath, match);\n\tfor (s = apath+strlen(apath)-1; s> apath; s--)\n\t{\n\t\tif (*s == '/') \n\t\t\tbreak;\n\t}\n\t*s = '\\0';\n\n\t// This is what we ask windows for.\n\tsnprintf(file, sizeof(file), \"%s/*.*\", apath);\n\n\t// We need to make apath contain the path in match but not gpath\n\tstrlcpy(apath2, match, sizeof(apath));\n\tmatch = s+1;\n\tfor (s = apath2+strlen(apath2)-1; s> apath2; s--)\n\t{\n\t\tif (*s == '/')\n\t\t\tbreak;\n\t}\n\t*s = '\\0';\n\tif (s != apath2)\n\t\tstrlcat (apath2, \"/\", sizeof (apath2));\n\n\tr = FindFirstFile(file, &fd);\n\tif (r==(HANDLE)-1)\n\t\treturn 1;\n\tgo = true;\n\tdo\n\t{\n\t\tif (*fd.cFileName == '.');  // Don't ever find files with a name starting with '.'\n\t\telse if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)    //is a directory\n\t\t{\n\t\t\tif (wildcmp(match, fd.cFileName))\n\t\t\t{\n\t\t\t\tsnprintf(file, sizeof(file), \"%s%s/\", apath2, fd.cFileName);\n\t\t\t\tgo = func(file, fd.nFileSizeLow, parm);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (wildcmp(match, fd.cFileName))\n\t\t\t{\n\t\t\t\tsnprintf(file, sizeof(file), \"%s%s\", apath2, fd.cFileName);\n\t\t\t\tgo = func(file, fd.nFileSizeLow, parm);\n\t\t\t}\n\t\t}\n\t}\n\twhile(FindNextFile(r, &fd) && go);\n\tFindClose(r);\n\n\treturn go;\n}\n\n/*\n================\nSys_Exit\n================\n*/\nvoid Sys_Exit(int code)\n{\n#ifndef _CONSOLE\n\tRemoveNotifyIcon();\n#endif\n\texit(code);\n}\n\n/*\n================\nSys_Quit\n================\n*/\n\nvoid myInvalidParameterHandler(const wchar_t* expression,\n   const wchar_t* function, \n   const wchar_t* file, \n   unsigned int line, \n   uintptr_t pReserved)\n{\n\t// nothing\n}\n\nvoid Sys_Quit (qbool restart)\n{\n\tif (restart)\n\t{\n#ifndef __MINGW32__\n\t\tint maxfd = 131072; // well, should be enough for everyone...\n\n\t\t_set_invalid_parameter_handler(myInvalidParameterHandler); // so close() does not crash our program on invalid handle...\n\n\t\t// close all file descriptors even stdin stdout and stderr, seems that not hurt...\n\t\tfor (; maxfd > -1; maxfd--)\n\t\t{\n\t\t\tclose(maxfd);\n\t\t\tclosesocket(maxfd); // yeah, windows separate sockets and files, so you can't close socket with close() like on *nix.\n\t\t}\n\n\t\tif (execv(argv[0], com_argv) == -1)\n#endif\n\t\t{\n#ifdef _CONSOLE\n\t\t\tif (!((int)sys_nostdout.value || isdaemon))\n\t\t\t\tprintf(\"Restart failed: (%i): %s\\n\", qerrno, strerror(qerrno));\n#else\n\t\t\tif (!(COM_CheckParm(\"-noerrormsgbox\") || isdaemon))\n\t\t\t\tMessageBox(NULL, strerror(qerrno), \"Restart failed\", 0 /* MB_OK */ );\n#endif\n\t\t\tSys_Exit(1);\n\t\t}\n\t}\n\tSys_Exit(0);\n}\n\n/*\n================\nSys_Error\n================\n*/\nvoid Sys_Error (const char *error, ...)\n{\n\tstatic qbool inerror = false;\n\tva_list argptr;\n\tchar text[1024];\n\n\tsv_error = true;\n\n\tif (inerror)\n\t\tSys_Exit (1);\n\n\tinerror = true;\n\n\tva_start (argptr, error);\n\tvsnprintf (text, sizeof (text), error, argptr);\n\tva_end (argptr);\n\n#ifdef _CONSOLE\n\tif (!((int)sys_nostdout.value || isdaemon))\n\t\tprintf (\"ERROR: %s\\n\", text);\n#else\n\tif (!(COM_CheckParm (\"-noerrormsgbox\") || isdaemon))\n\t\tMessageBox (NULL, text, \"Error\", 0 /* MB_OK */ );\n\telse\n\t\tSys_Printf (\"ERROR: %s\\n\", text);\n\n#endif\n\n\tif (logs[ERROR_LOG].sv_logfile)\n\t{\n\t\tSV_Write_Log (ERROR_LOG, 1, va (\"ERROR: %s\\n\", text));\n//\t\tfclose (logs[ERROR_LOG].sv_logfile);\n\t}\n\n// FIXME: hack - checking SV_Shutdown with svs.socketip set in -1 NET_Shutdown\n\tif (svs.socketip != -1)\n\t\tSV_Shutdown (va(\"ERROR: %s\\n\", text));\n\n\tif ((int)sys_restart_on_error.value)\n\t\tSys_Quit (true);\n\n\tSys_Exit (1);\n}\n\nstatic double pfreq;\nstatic qbool hwtimer = false;\nstatic __int64 startcount;\nvoid Sys_InitDoubleTime (void)\n{\n\t__int64 freq;\n\n\tif (!COM_CheckParm(\"-nohwtimer\") &&\n\t\tQueryPerformanceFrequency ((LARGE_INTEGER *)&freq) && freq > 0)\n\t{\n\t\t// hardware timer available\n\t\tpfreq = (double)freq;\n\t\thwtimer = true;\n\t\tQueryPerformanceCounter ((LARGE_INTEGER *)&startcount);\n\t}\n\telse\n\t{\n\t\t// make sure the timer is high precision, otherwise\n\t\t// NT gets 18ms resolution\n\t\ttimeBeginPeriod (1);\n\t}\n}\n\ndouble Sys_DoubleTime (void)\n{\n\t__int64 pcount;\n\n\tstatic DWORD starttime;\n\tstatic qbool first = true;\n\tDWORD now;\n\n\tif (hwtimer)\n\t{\n\t\tQueryPerformanceCounter ((LARGE_INTEGER *)&pcount);\n\t\tif (first) {\n\t\t\tfirst = false;\n\t\t\tstartcount = pcount;\n\t\t\treturn 0.0;\n\t\t}\n\t\t// TODO: check for wrapping; is it necessary?\n\t\treturn (pcount - startcount) / pfreq;\n\t}\n\n\tnow = timeGetTime();\n\n\tif (first)\n\t{\n\t\tfirst = false;\n\t\tstarttime = now;\n\t\treturn 0.0;\n\t}\n\n\tif (now < starttime) // wrapped?\n\t\treturn (now / 1000.0) + (LONG_MAX - starttime / 1000.0);\n\n\tif (now - starttime == 0)\n\t\treturn 0.0;\n\n\treturn (now - starttime) / 1000.0;\n}\n\n/*\n================\nSys_ConsoleInput\n================\n*/\nchar *Sys_ConsoleInput (void)\n{\n#ifdef _CONSOLE\n\n\tstatic char\ttext[256], *t;\n\tstatic int\tlen = 0;\n\n\tint\t\tc;\n\n\t// read a line out\n\tif (!isdaemon)\n\t\twhile (_kbhit())\n\t\t{\n\t\t\tc = _getch();\n\n\t\t\tif (c == 224)\n\t\t\t{\n\t\t\t\tif (_kbhit())\n\t\t\t\t{\n\t\t\t\t\t// assume escape sequence (arrows etc), skip\n\t\t\t\t\t_getch();\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// assume character\n\t\t\t}\n\n\t\t\tif (c < 32 && c != '\\r' && c != 8)\n\t\t\t\tcontinue;\n\n\t\t\tputch (c);\n\t\t\tif (c == '\\r')\n\t\t\t{\n\t\t\t\ttext[len] = 0;\n\t\t\t\tputch ('\\n');\n\t\t\t\tlen = 0;\n\t\t\t\treturn text;\n\t\t\t}\n\t\t\tif (c == 8)\n\t\t\t{\n\t\t\t\tif (len)\n\t\t\t\t{\n\t\t\t\t\tputch (' ');\n\t\t\t\t\tputch (c);\n\t\t\t\t\tlen--;\n\t\t\t\t\ttext[len] = 0;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttext[len] = c;\n\t\t\tlen++;\n\t\t\ttext[len] = 0;\n\t\t\tif (len == sizeof(text))\n\t\t\t\tlen = 0;\n\t\t}\n#endif\n\n\t// If you searching where input added under non console application, then you should check DialogFunc WM_COMMAND.\n\n\treturn NULL;\n}\n\n/*\n================\nSys_Printf\n================\n*/\nvoid Sys_Printf(char* fmt, ...)\n{\n\tva_list argptr;\n\tchar text[MAXCMDBUF];\n\tchar* startpos;\n\tchar* endpos;\n\tdate_t      date;\n\n#ifdef _CONSOLE\n\tif ((int)sys_nostdout.value) {\n\t\treturn;\n\t}\n#endif\n\n\tif (isdaemon) {\n\t\treturn;\n\t}\n\n\tva_start(argptr, fmt);\n\tvsnprintf(text, MAXCMDBUF, fmt, argptr);\n\tva_end(argptr);\n\n\t// normalize text before add to console.\n\tQ_normalizetext(text);\n\n#ifndef _CONSOLE\n\tConsoleAddText(text);\n#else\n\tSV_TimeOfDay(&date, \"%Y-%m-%d %H:%M:%S\");\n\tstartpos = text;\n\twhile (startpos && startpos[0]) {\n\t\tendpos = strchr(startpos, '\\n');\n\t\tif (endpos) {\n\t\t\t*endpos = '\\0';\n\t\t}\n\t\tfprintf(stdout, \"[%s] %s\\n\", date.str, startpos);\n\t\tfflush(stdout);\n\t\tif (endpos) {\n\t\t\tstartpos = endpos + 1;\n\t\t\tif (startpos[0] == (char)10) {\n\t\t\t\t++startpos;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tbreak;\n\t\t}\n\t}\n#endif //_CONSOLE\n}\n\n/*\n=============\nSys_Init\n \nQuake calls this so the system can register variables before host_hunklevel\nis marked\n=============\n*/\nvoid Sys_Init (void)\n{\n\tqbool\tWinNT;\n\tOSVERSIONINFO\tvinfo;\n\n\t// make sure the timer is high precision, otherwise\n\t// NT gets 18ms resolution\n\ttimeBeginPeriod( 1 );\n\n\tvinfo.dwOSVersionInfoSize = sizeof(vinfo);\n\n\tif (!GetVersionEx (&vinfo))\n\t\tSys_Error (\"Couldn't get OS info\");\n\n\tif (vinfo.dwMajorVersion < 4 || vinfo.dwPlatformId == VER_PLATFORM_WIN32s)\n\t\tSys_Error (SERVER_NAME \" requires at least Win95 or NT 4.0\");\n\n\tWinNT = (vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT ? true : false);\n\n\tCvar_Register (&sys_nostdout);\n\tCvar_Register (&sys_sleep);\n\n\tif (COM_CheckParm (\"-nopriority\"))\n\t{\n\t\tCvar_Set (&sys_sleep, \"0\");\n\t}\n\telse\n\t{\n\t\tif ( ! SetPriorityClass (GetCurrentProcess(), HIGH_PRIORITY_CLASS))\n\t\t\tCon_Printf (\"SetPriorityClass() failed\\n\");\n\t\telse\n\t\t\tCon_Printf (\"Process priority class set to HIGH\\n\");\n\n\t\t// sys_sleep > 0 seems to cause packet loss on WinNT (why?)\n\t\tif (WinNT)\n\t\t\tCvar_Set (&sys_sleep, \"0\");\n\t}\n\n\tSys_InitDoubleTime ();\n}\n\nvoid Sys_Sleep (unsigned long ms)\n{\n\tSleep (ms);\n}\n\nint Sys_Script (const char *path, const char *args)\n{\n\tSTARTUPINFO\t\t\tsi;\n\tPROCESS_INFORMATION\tpi;\n\tchar cmdline[1024], curdir[MAX_OSPATH];\n\n\tmemset (&si, 0, sizeof(si));\n\tsi.cb = sizeof(si);\n\tsi.dwFlags = STARTF_USESHOWWINDOW;\n\tsi.wShowWindow = SW_SHOWMINNOACTIVE;\n\n\tGetCurrentDirectory(sizeof(curdir), curdir);\n\n\n\tsnprintf(cmdline, sizeof(cmdline), \"%s\\\\sh.exe %s.qws %s\", curdir, path, args);\n\tstrlcat(curdir, va(\"\\\\%s\", fs_gamedir+2), MAX_OSPATH);\n\n\treturn CreateProcess (NULL, cmdline, NULL, NULL,\n\t                      FALSE, 0/*DETACHED_PROCESS /*CREATE_NEW_CONSOLE*/ , NULL, curdir, &si, &pi);\n}\n\nDL_t Sys_DLOpen (const char *path)\n{\n\treturn LoadLibrary (path);\n}\n\nqbool Sys_DLClose (DL_t dl)\n{\n\treturn FreeLibrary (dl);\n}\n\nvoid *Sys_DLProc (DL_t dl, const char *name)\n{\n\treturn (void *) GetProcAddress (dl, name);\n}\n\nint Sys_CreateThread(DWORD (WINAPI *func)(void *), void *param)\n{\n    DWORD threadid;\n    HANDLE thread;\n\n    thread = CreateThread (\n        NULL,               // pointer to security attributes\n        0,                  // initial thread stack size\n        func,               // pointer to thread function\n        param,              // argument for new thread\n        CREATE_SUSPENDED,   // creation flags\n        &threadid);         // pointer to receive thread ID\n\n    ResumeThread(thread);\n\n    return 1;\n}\n\n#ifdef _CONSOLE\n/*\n==================\nmain\n \n==================\n*/\n\nint main(int ac, char *av[])\n{\n\tdouble\t\t\tnewtime, time, oldtime;\n\tint\t\t\t\tsleep_msec;\n\n\tParseCommandLine (ac, av);\n\n\tCOM_InitArgv (argc, argv);\n\n\tGetConsoleTitle(title, sizeof(title));\n\n\tHost_Init(argc, argv, DEFAULT_MEM_SIZE);\n\n\t// run one frame immediately for first heartbeat\n\tSV_Frame (0.1);\n\n\t//\n\t// main loop\n\t//\n\toldtime = Sys_DoubleTime () - 0.1;\n\n\twhile (1)\n\t{\n\t\tsleep_msec = (int)sys_sleep.value;\n\t\tif (sleep_msec > 0)\n\t\t{\n\t\t\tif (sleep_msec > 13)\n\t\t\t\tsleep_msec = 13;\n\t\t\tSleep (sleep_msec);\n\t\t}\n\n\t\t// select on the net socket and stdin\n\t\t// the only reason we have a timeout at all is so that if the last\n\t\t// connected client times out, the message would not otherwise\n\t\t// be printed until the next event.\n\t\tif (!sys_simulation.value) {\n\t\t\tNET_Sleep((int)sys_select_timeout.value / 1000, false);\n\t\t}\n\n\t\t// find time passed since last cycle\n\t\tnewtime = Sys_DoubleTime ();\n\t\ttime = newtime - oldtime;\n\t\toldtime = newtime;\n\n\t\tcurtime = newtime;\n\t\tSV_Frame (time);\n\t}\n\n\treturn true;\n}\n\n#else  // _CONSOLE\n\nint APIENTRY WinMain(   HINSTANCE   hInstance,\n\t\t\t\t\t\tHINSTANCE   hPrevInstance,\n\t\t\t\t\t\tLPSTR       lpCmdLine,\n\t\t\t\t\t\tint         nCmdShow)\n{\n\tstatic MSG\t\t\tmsg;\n\tstatic double\t\tnewtime, time, oldtime;\n\tregister int\t\tsleep_msec;\n\n\tstatic qbool\t\tdisable_gpf = false;\n\n\tParseCommandLine(lpCmdLine);\n\n\tCOM_InitArgv (argc, argv);\n\n\t// create main window\n\tif (!CreateMainWindow(hInstance, nCmdShow))\n\t\treturn 1;\n\n\tif (COM_CheckParm(\"-noerrormsgbox\"))\n\t\tdisable_gpf = true;\n\n\tif (COM_CheckParm (\"-d\"))\n\t{\n\t\tisdaemon = disable_gpf = true;\n\t\t//close(0); close(1); close(2);\n\t}\n\n\tif (disable_gpf)\n\t{\n\t\tDWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX);\n\t\tSetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX);\n\t}\n\t\n\tHost_Init(argc, argv, DEFAULT_MEM_SIZE);\n\n\t// if stared miminize update notify icon message (with correct port)\n\tif (minimized)\n\t\tUpdateNotifyIconMessage(va(SERVER_NAME \":%d\", NET_UDPSVPort()));\n\n\t// run one frame immediately for first heartbeat\n\tSV_Frame (0.1);\n\n\t//\n\t// main loop\n\t//\n\toldtime = Sys_DoubleTime () - 0.1;\n\n\twhile(1)\n\t{\n\t\t// get messeges sent to windows\n\t\tif( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )\n\t\t{\n\t\t\tif( !GetMessage( &msg, NULL, 0, 0 ) )\n\t\t\t\tbreak;\n\t\t\tif(!IsDialogMessage(DlgHwnd, &msg))\n\t\t\t{\n\t\t\t\tTranslateMessage(&msg);\n\t\t\t\tDispatchMessage(&msg);\n\t\t\t}\n\t\t}\n\n\t\tCheckIdle();\n\n\t\t// server frame\n\n\t\tsleep_msec = (int)sys_sleep.value;\n\t\tif (sleep_msec > 0)\n\t\t{\n\t\t\tif (sleep_msec > 13)\n\t\t\t\tsleep_msec = 13;\n\t\t\tSleep (sleep_msec);\n\t\t}\n\n\t\t// select on the net socket and stdin\n\t\t// the only reason we have a timeout at all is so that if the last\n\t\t// connected client times out, the message would not otherwise\n\t\t// be printed until the next event.\n\t\tif (!sys_simulation.value) {\n\t\t\tNET_Sleep((int)sys_select_timeout.value / 1000, false);\n\t\t}\n\n\t\t// find time passed since last cycle\n\t\tnewtime = Sys_DoubleTime ();\n\t\ttime = newtime - oldtime;\n\t\toldtime = newtime;\n\n\t\tcurtime = newtime;\n\t\tSV_Frame (time);\n\t}\n\n\n\tSys_Exit(msg.wParam);\n\n\treturn msg.wParam;\n}\n\n#endif // _CONSOLE\n"
  },
  {
    "path": "src/sv_user.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t\n*/\n// sv_user.c -- server code for moving users\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\nstatic void SV_ClientDownloadComplete(client_t* cl);\n\nedict_t\t*sv_player;\n\nusercmd_t\tcmd;\n\ncvar_t\tsv_spectalk = {\"sv_spectalk\", \"1\"};\ncvar_t\tsv_sayteam_to_spec = {\"sv_sayteam_to_spec\", \"1\"};\ncvar_t\tsv_mapcheck = {\"sv_mapcheck\", \"1\"};\ncvar_t\tsv_minping = {\"sv_minping\", \"0\"};\ncvar_t\tsv_maxping = {\"sv_maxping\", \"0\"};\ncvar_t\tsv_enable_cmd_minping = {\"sv_enable_cmd_minping\", \"1\"};\n\ncvar_t\tsv_kickuserinfospamtime = {\"sv_kickuserinfospamtime\", \"3\"};\ncvar_t\tsv_kickuserinfospamcount = {\"sv_kickuserinfospamcount\", \"300\"};\n\ncvar_t\tsv_maxuploadsize = {\"sv_maxuploadsize\", \"1048576\"};\n\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\ncvar_t  sv_downloadchunksperframe = {\"sv_downloadchunksperframe\", \"15\"};\n#endif\n\n#ifdef FTE_PEXT2_VOICECHAT\n// Enable reception of voice packets.\ncvar_t sv_voip = {\"sv_voip\", \"1\"};\n// Record voicechat into mvds. Requires player support. 0=noone, 1=everyone, 2=spectators only.\ncvar_t sv_voip_record = {\"sv_voip_record\", \"0\"};\n// Echo voice packets back to their sender, a debug/test setting.\ncvar_t sv_voip_echo = {\"sv_voip_echo\", \"0\"};\n#endif\n\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\nstatic qbool SV_ClientExtensionWeaponSwitch(client_t* cl);\n#endif\n\n#ifdef MVD_PEXT1_DEBUG\ntypedef struct {\n\tint msec;\n\tbyte model;\n\tvec3_t pos;\n\tqbool present;\n} antilag_client_info_t;\n\nstatic struct {\n\tantilag_client_info_t antilag_clients[MAX_CLIENTS];\n} debug_info;\n\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\nstatic void SV_DebugServerSideWeaponScript(client_t* cl, int best_impulse);\nstatic void SV_DebugServerSideWeaponInstruction(client_t* cl);\n#endif\ncvar_t sv_debug_weapons = { \"sv_debug_weapons\", \"0\" };\n#endif\n\n// These don't need any protocol extensions\ncvar_t sv_debug_usercmd = { \"sv_debug_usercmd\", \"0\" };\ncvar_t sv_debug_antilag = { \"sv_debug_antilag\", \"0\" };\n\n\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\nstatic void SV_UserSetWeaponRank(client_t* cl, const char* new_wrank);\n#endif\nstatic void SV_DebugClientCommand(byte playernum, const usercmd_t* cmd, int dropnum_);\n\nextern\tvec3_t\tplayer_mins;\n\nextern int\tfp_messages, fp_persecond, fp_secondsdead;\nextern char\tfp_msg[];\nextern cvar_t   pausable;\nextern cvar_t   pm_bunnyspeedcap;\nextern cvar_t   pm_ktjump;\nextern cvar_t   pm_slidefix;\nextern cvar_t   pm_airstep;\nextern cvar_t   pm_pground;\nextern cvar_t   pm_rampjump;\nextern double\tsv_frametime;\n\n//bliP: init ->\nextern cvar_t\tsv_unfake; //bliP: 24/9 kickfake to unfake\nextern cvar_t\tsv_kicktop;\nextern cvar_t\tsv_speedcheck; //bliP: 24/9\n//<-\n\nstatic void OnChange_sv_maxpitch (cvar_t *var, char *str, qbool *cancel);\nstatic void OnChange_sv_minpitch (cvar_t *var, char *str, qbool *cancel);\ncvar_t\tsv_maxpitch = {\"sv_maxpitch\", \"80\", 0, OnChange_sv_maxpitch};\ncvar_t\tsv_minpitch = {\"sv_minpitch\", \"-70\", 0, OnChange_sv_minpitch};\n\nstatic void SetUpClientEdict (client_t *cl, edict_t *ent);\n\nstatic qbool IsLocalIP(netadr_t a)\n{\n\treturn a.ip[0] == 10 || (a.ip[0] == 172 && (a.ip[1] & 0xF0) == 16)\n\t       || (a.ip[0] == 192 && a.ip[1] == 168) || a.ip[0] >= 224;\n}\nstatic qbool IsInetIP(netadr_t a)\n{\n\treturn a.ip[0] != 127 && !IsLocalIP(a);\n}\n\n//\n// pitch clamping\n//\n// All this OnChange code is because we want the cvar names to have sv_ prefixes,\n// but don't want them in serverinfo (save a couple of bytes of space)\n// Value sanity checks are also done here\n//\nstatic void OnChange_sv_maxpitch (cvar_t *var, char *str, qbool *cancel)\n{\n\tfloat\tnewval;\n\tchar\t*newstr;\n\n\t*cancel = true;\n\n\tnewval = bound (0, Q_atof(str), 89.9f);\n\tif (newval == var->value)\n\t\treturn;\n\n\tCvar_SetValue (var, newval);\n\tnewstr = (newval == 80.0f) ? (char *)\"\" : var->string;\t// don't show default values in serverinfo\n\tSV_ServerinfoChanged(\"maxpitch\", newstr);\n}\n\nstatic void OnChange_sv_minpitch (cvar_t *var, char *str, qbool *cancel)\n{\n\tfloat\tnewval;\n\tchar\t*newstr;\n\n\t*cancel = true;\n\n\tnewval = bound (-89.9f, Q_atof(str), 0.0f);\n\tif (newval == var->value)\n\t\treturn;\n\n\tCvar_SetValue (var, newval);\n\tnewstr = (newval == -70.0f) ? (char *)\"\" : var->string;\t// don't show default values in serverinfo\n\tSV_ServerinfoChanged(\"minpitch\", newstr);\n}\n\n/*\n============================================================\n\nUSER STRINGCMD EXECUTION\n\nsv_client and sv_player will be valid.\n============================================================\n*/\n\n/*\n==================\nPlayerCheckPing\n\nCheck that player's ping falls below sv_maxping value\n==================\n*/\nqbool PlayerCheckPing(void)\n{\n\n\tif (sv_client->maxping_met) return true;\n\n\tint maxping = Q_atof(sv_maxping.string);\n\tint playerping = sv_client->frames[sv_client->netchan.incoming_acknowledged & UPDATE_MASK].ping_time * 1000;\n\n\tif (maxping && playerping > maxping)\n\t{\n\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"\\nYour ping is too high for this server!  Maximum ping is set to %i, your ping is %i.\\nForcing spectator.\\n\\n\", maxping, playerping);\n\t\treturn false;\n\t}\n\tsv_client->maxping_met = true;\n\treturn true;\n}\n\n/*\n================\nCmd_New_f\n\nSends the first message from the server to a connected client.\nThis will be sent on the initial connection and upon each server load.\n================\n*/\nint SV_VIPbyIP (netadr_t adr);\nstatic void Cmd_New_f (void)\n{\n\tchar\t\t*gamedir;\n\tint\t\t\tplayernum;\n\textern cvar_t sv_serverip;\n\textern cvar_t sv_getrealip;\n\n\tif (sv_client->state == cs_spawned)\n\t\treturn;\n\n\tif (!SV_ClientConnectedTime(sv_client) || sv_client->state == cs_connected) {\n\t\tSV_SetClientConnectionTime(sv_client);\n\t}\n\n\tsv_client->spawncount = svs.spawncount;\n\n\t// request protocol extensions.\n\tif (sv_client->process_pext)\n\t{\n\t\tMSG_WriteByte (&sv_client->netchan.message, svc_stufftext);\n\t\tMSG_WriteString (&sv_client->netchan.message, \"cmd pext\\n\");\n\t\treturn;\n\t}\n\n\t// do not proceed if realip is unknown\n    if (sv_client->state == cs_preconnected && !sv_client->realip.ip[0] && (int)sv_getrealip.value)\n\t{\n\t\tchar *server_ip = sv_serverip.string[0] ? sv_serverip.string : NET_AdrToString(net_local_sv_ipadr);\n\n\t\tif (!((IsLocalIP(net_local_sv_ipadr) && IsLocalIP(sv_client->netchan.remote_address))  ||\n\t\t        (IsInetIP (net_local_sv_ipadr) && IsInetIP (sv_client->netchan.remote_address))) &&\n\t\t        sv_client->netchan.remote_address.ip[0] != 127 && !sv_serverip.string[0])\n\t\t{\n\t\t\tSys_Printf (\"WARNING: Incorrect server ip address: %s\\n\"\n\t\t\t            \"Set hostname in your operation system or set correctly sv_serverip cvar.\\n\",\n\t\t\t            server_ip);\n\t\t\t*(int *)&sv_client->realip = *(int *)&sv_client->netchan.remote_address;\n\t\t\tsv_client->state = cs_connected;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (sv_client->realip_count++ < 10)\n\t\t\t{\n\t\t\t\tsv_client->state = cs_preconnected;\n\t\t\t\tMSG_WriteByte (&sv_client->netchan.message, svc_stufftext);\n\t\t\t\tMSG_WriteString (&sv_client->netchan.message,\n\t\t\t\t\t\t\t\t va(\"packet %s \\\"ip %d %d\\\"\\ncmd new\\n\", server_ip,\n\t\t\t\t\t\t\t\t\tsv_client - svs.clients, sv_client->realip_num));\n\t\t\t}\n\t\t\tif (SV_ClientConnectedTime(sv_client) > 3 || sv_client->realip_count > 10) {\n\t\t\t\tif ((int)sv_getrealip.value == 2) {\n\t\t\t\t\tNetchan_OutOfBandPrint (NS_SERVER, net_from, \"%c\\nFailed to validate client's IP.\\n\\n\", A2C_PRINT);\n\t\t\t\t\tsv_client->rip_vip = 2;\n\t\t\t\t}\n\t\t\t\tsv_client->state = cs_connected;\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t// rip_vip means that client can be connected if he has VIP for he's real ip\n\t// drop him if he hasn't\n\tif (sv_client->rip_vip == 1)\n\t{\n\t\tif ((sv_client->vip = SV_VIPbyIP(sv_client->realip)) == 0)\n\t\t{\n\t\t\tSys_Printf (\"%s:full connect\\n\", NET_AdrToString (net_from));\n\t\t\tNetchan_OutOfBandPrint (NS_SERVER, net_from,\n\t\t\t                        \"%c\\nserver is full\\n\\n\", A2C_PRINT);\n\t\t}\n\t\telse\n\t\t\tsv_client->rip_vip = 0;\n\t}\n\n\t// we can be connected now, announce it, and possibly login\n\tif (!sv_client->rip_vip)\n\t{\n\t\tif (sv_client->state == cs_preconnected)\n\t\t{\n\t\t\t// get highest VIP level\n\t\t\tif (sv_client->vip < SV_VIPbyIP(sv_client->realip))\n\t\t\t\tsv_client->vip = SV_VIPbyIP(sv_client->realip);\n\n\t\t\tif (sv_client->vip && sv_client->spectator)\n\t\t\t\tSys_Printf (\"VIP spectator %s connected\\n\", sv_client->name);\n\t\t\telse if (sv_client->spectator)\n\t\t\t\tSys_Printf (\"Spectator %s connected\\n\", sv_client->name);\n\t\t\telse\n\t\t\t\tSys_Printf (\"Client %s connected\\n\", sv_client->name);\n\n\t\t\tInfo_SetStar (&sv_client->_userinfo_ctx_, \"*VIP\", sv_client->vip ? va(\"%d\", sv_client->vip) : \"\");\n\n\t\t\t// now we are connected\n\t\t\tsv_client->state = cs_connected;\n\t\t}\n\n\t\tif (!SV_Login(sv_client))\n\t\t\treturn;\n\n\t\t// If logins are mandatory, check\n\t\tif (SV_LoginRequired(sv_client)) {\n\t\t\treturn;\n\t\t}\n\n\t\t//bliP: cuff, mute ->\n\t\tsv_client->lockedtill = SV_RestorePenaltyFilter(sv_client, ft_mute);\n\t\tsv_client->cuff_time = SV_RestorePenaltyFilter(sv_client, ft_cuff);\n\t\t//<-\n\t}\n\t// send the info about the new client to all connected clients\n\t//\tSV_FullClientUpdate (sv_client, &sv.reliable_datagram);\n\t//\tsv_client->sendinfo = true;\n\n\tgamedir = Info_ValueForKey (svs.info, \"*gamedir\");\n\tif (!gamedir[0])\n\t\tgamedir = \"qw\";\n\n#ifdef FTE_PEXT_FLOATCOORDS\n\tif (msg_coordsize > 2 && !(sv_client->fteprotocolextensions & FTE_PEXT_FLOATCOORDS))\n\t{\n\t\tSV_ClientPrintf(sv_client, 2, \"\\n\\n\\n\\n\"\n\t\t\t\"Your client lacks the necessary extensions\\n\"\n\t\t\t\"  to connect to this server.\\n\"\n\t\t\t\"Set /cl_pext_floatcoords 1, or upgrade.\\n\"\n\t\t\t\"Please upgrade to one of the following:\\n\"\n\t\t\t\"> ezQuake 2.2 (https://ezquake.github.io)\\n\"\n\t\t\t\"> fodquake 0.4 (http://fodquake.net)\\n\"\n\t\t\t\"> FTEQW (http://fte.triptohell.info/)\\n\");\n\t\tif (!sv_client->spectator) {\n\t\t\tSV_DropClient (sv_client);\n\t\t\treturn;\n\t\t}\n\t\tif (!SV_SkipCommsBotMessage(sv_client)) {\n\t\t\treturn;\n\t\t}\n\t}\n#endif\n\n#ifdef FTE_PEXT_ENTITYDBL\n\tif (sv.max_edicts > 512 && !(sv_client->fteprotocolextensions & FTE_PEXT_ENTITYDBL)) {\n\t\tSV_ClientPrintf(sv_client, 2, \"\\n\\nWARNING:\\n\"\n\t\t\t\"Your client lacks support for extended\\n\"\n\t\t\t\"  entity limits, some enemies/projectiles\\n\"\n\t\t\t\"  may be invisible to you.\\n\"\n\t\t\t\"Please upgrade to one of the following:\\n\"\n\t\t\t\"> ezQuake 2.2 (https://ezquake.github.io)\\n\"\n\t\t\t\"> fodquake 0.4 (http://fodquake.net)\\n\"\n\t\t\t\"> FTEQW (http://fte.triptohell.info/)\\n\");\n\t}\n#endif\n\n#if defined(FTE_PEXT_TRANS)\n\tif (sv_client->fteprotocolextensions & FTE_PEXT_TRANS)\n\t{\n\t\tconst char *client_string = Info_Get(&sv_client->_userinfo_ctx_, \"*client\");\n\t\tchar *ptr = strchr(client_string, ' ');\n\t\tif (ptr != NULL) {\n\t\t\tptr++;\n\t\t\tif (strncmp(client_string, \"ezQuake\", 7) == 0 && *ptr != '\\0')\n\t\t\t{\n\t\t\t\textern cvar_t sv_pext_ezquake_verfortrans;\n\t\t\t\tchar *endptr;\n\t\t\t\tlong revision = strtol(ptr, &endptr, 10);\n\t\t\t\tif (*endptr != '\\0' || (revision > 0 && revision < sv_pext_ezquake_verfortrans.value))\n\t\t\t\t{\n\t\t\t\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"\\n\\nWARNING:\\n\"\n\t\t\t\t\t\t\"Alpha support disabled due to buggy client, \"\n\t\t\t\t\t\t\"if the map contains transparency you may be at a disadvantage.\\n\"\n\t\t\t\t\t\t\"Please upgrade to one of the following:\\n\"\n\t\t\t\t\t\t\"> ezQuake (https://www.ezquake.com)\\n\"\n\t\t\t\t\t\t\"> FTEQW (http://fte.triptohell.info/)\\n\");\n\t\t\t\t\tsv_client->fteprotocolextensions &= ~FTE_PEXT_TRANS;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n#endif\n\n\t//NOTE:  This doesn't go through ClientReliableWrite since it's before the user\n\t//spawns.  These functions are written to not overflow\n\tif (sv_client->num_backbuf)\n\t{\n\t\tCon_Printf(\"WARNING %s: [SV_New] Back buffered (%d0), clearing\\n\",\n\t\t           sv_client->name, sv_client->netchan.message.cursize);\n\t\tsv_client->num_backbuf = 0;\n\t\tSZ_Clear(&sv_client->netchan.message);\n\t}\n\n\t// send the serverdata\n\tMSG_WriteByte  (&sv_client->netchan.message, svc_serverdata);\n#ifdef PROTOCOL_VERSION_FTE\n\tif (sv_client->fteprotocolextensions) // let the client know\n\t{\n\t\tunsigned int ext = sv_client->fteprotocolextensions;\n\n#ifdef FTE_PEXT_FLOATCOORDS\n\t\tif (msg_coordsize == 2) //we're not using float orgs on this level.\n\t\t\text &= ~FTE_PEXT_FLOATCOORDS;\n#endif\n\n\t\tMSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION_FTE);\n\t\tMSG_WriteLong (&sv_client->netchan.message, ext);\n\t}\n#endif\n#ifdef PROTOCOL_VERSION_FTE2\n\tif (sv_client->fteprotocolextensions2) // let the client know\n\t{\n\t\tMSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION_FTE2);\n\t\tMSG_WriteLong (&sv_client->netchan.message, sv_client->fteprotocolextensions2);\n\t}\n#endif // PROTOCOL_VERSION_FTE2\n#ifdef PROTOCOL_VERSION_MVD1\n\tif (sv_client->mvdprotocolextensions1) {\n\t\tMSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION_MVD1);\n\t\tMSG_WriteLong (&sv_client->netchan.message, sv_client->mvdprotocolextensions1);\n\t}\n#endif\n\tMSG_WriteLong  (&sv_client->netchan.message, PROTOCOL_VERSION);\n\tMSG_WriteLong  (&sv_client->netchan.message, svs.spawncount);\n\tMSG_WriteString(&sv_client->netchan.message, gamedir);\n\n\tplayernum = NUM_FOR_EDICT(sv_client->edict)-1;\n\tif (sv_client->spectator)\n\t\tplayernum |= 128;\n\tMSG_WriteByte (&sv_client->netchan.message, playernum);\n\n\t// send full levelname\n\tif (sv_client->rip_vip)\n\t\tMSG_WriteString (&sv_client->netchan.message, \"\");\n\telse\n\t\tMSG_WriteString (&sv_client->netchan.message, PR_GetEntityString(sv.edicts->v->message));\n\n\t// send the movevars\n\tMSG_WriteFloat(&sv_client->netchan.message, movevars.gravity);\n\tMSG_WriteFloat(&sv_client->netchan.message, movevars.stopspeed);\n\tMSG_WriteFloat(&sv_client->netchan.message, /* sv_client->maxspeed */ movevars.maxspeed); // FIXME: this does't work, Tonik?\n\tMSG_WriteFloat(&sv_client->netchan.message, movevars.spectatormaxspeed);\n\tMSG_WriteFloat(&sv_client->netchan.message, movevars.accelerate);\n\tMSG_WriteFloat(&sv_client->netchan.message, movevars.airaccelerate);\n\tMSG_WriteFloat(&sv_client->netchan.message, movevars.wateraccelerate);\n\tMSG_WriteFloat(&sv_client->netchan.message, movevars.friction);\n\tMSG_WriteFloat(&sv_client->netchan.message, movevars.waterfriction);\n\tMSG_WriteFloat(&sv_client->netchan.message, /* sv_client->entgravity */ movevars.entgravity); // FIXME: this does't work, Tonik?\n\n\tif (!sv_client->spectator) {\n\t\tif (!PlayerCheckPing()) {\n\t\t\tsv_client->old_frags = 0;\n\t\t\tSV_SetClientConnectionTime(sv_client);\n\t\t\tsv_client->spectator = true;\n\t\t\tsv_client->spec_track = 0;\n\t\t\tInfo_SetStar(&sv_client->_userinfo_ctx_, \"*spectator\", \"1\");\n\t\t\tInfo_SetStar(&sv_client->_userinfoshort_ctx_, \"*spectator\", \"1\");\n\t\t}\n\t}\n\n\tif (sv_client->rip_vip)\n\t{\n\t\tSV_LogPlayer(sv_client, va(\"dropped %d\", sv_client->rip_vip), 1);\n\t\tSV_DropClient (sv_client);\n\t\treturn;\n\t}\n\n\t// send music\n\tMSG_WriteByte (&sv_client->netchan.message, svc_cdtrack);\n\tMSG_WriteByte (&sv_client->netchan.message, sv.edicts->v->sounds);\n\n\t// send server info string\n\tMSG_WriteByte (&sv_client->netchan.message, svc_stufftext);\n\tMSG_WriteString (&sv_client->netchan.message, va(\"fullserverinfo \\\"%s\\\"\\n\", svs.info) );\n\n\t//bliP: player logging\n\tSV_LogPlayer(sv_client, \"connect\", 1);\n}\n\n/*\n==================\nCmd_Soundlist_f\n==================\n*/\nstatic void Cmd_Soundlist_f (void)\n{\n\tchar\t\t**s;\n\tunsigned\tn;\n\n\tif (sv_client->state != cs_connected)\n\t{\n\t\tCon_Printf (\"soundlist not valid -- already spawned\\n\");\n\t\treturn;\n\t}\n\n\t// handle the case of a level changing while a client was connecting\n\tif (Q_atoi(Cmd_Argv(1)) != svs.spawncount)\n\t{\n\t\tSV_ClearReliable (sv_client);\n\t\tCon_Printf (\"SV_Soundlist_f from different level\\n\");\n\t\tCmd_New_f ();\n\t\treturn;\n\t}\n\n\tn = Q_atoi(Cmd_Argv(2));\n\tif (n >= MAX_SOUNDS)\n\t{\n\t\tSV_ClearReliable (sv_client);\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH,\n\t\t                 \"SV_Soundlist_f: Invalid soundlist index\\n\");\n\t\tSV_DropClient (sv_client);\n\t\treturn;\n\t}\n\n\t//NOTE:  This doesn't go through ClientReliableWrite since it's before the user\n\t//spawns.  These functions are written to not overflow\n\tif (sv_client->num_backbuf)\n\t{\n\t\tCon_Printf(\"WARNING %s: [SV_Soundlist] Back buffered (%d0), clearing\\n\", sv_client->name, sv_client->netchan.message.cursize);\n\t\tsv_client->num_backbuf = 0;\n\t\tSZ_Clear(&sv_client->netchan.message);\n\t}\n\n\tMSG_WriteByte (&sv_client->netchan.message, svc_soundlist);\n\tMSG_WriteByte (&sv_client->netchan.message, n);\n\tfor (s = sv.sound_precache+1 + n ;\n\t        *s && sv_client->netchan.message.cursize < (MAX_MSGLEN/2);\n\t        s++, n++)\n\t\tMSG_WriteString (&sv_client->netchan.message, *s);\n\n\tMSG_WriteByte (&sv_client->netchan.message, 0);\n\n\t// next msg\n\tif (*s)\n\t\tMSG_WriteByte (&sv_client->netchan.message, n);\n\telse\n\t\tMSG_WriteByte (&sv_client->netchan.message, 0);\n}\n\nstatic char *TrimModelName (const char *full)\n{\n\tstatic char shortn[MAX_QPATH];\n\tint len;\n\n\tif (!strncmp(full, \"progs/\", 6) && !strchr(full + 6, '/'))\n\t\tstrlcpy (shortn, full + 6, sizeof(shortn));\t\t// strip progs/\n\telse\n\t\tstrlcpy (shortn, full, sizeof(shortn));\n\n\tlen = strlen(shortn);\n\tif (len > 4 && !strcmp(shortn + len - 4, \".mdl\")\n\t\t&& strchr(shortn, '.') == shortn + len - 4)\n\t{\t// strip .mdl\n\t\tshortn[len - 4] = '\\0';\n\t}\n\n\treturn shortn;\n}\n\n/*\n==================\nCmd_Modellist_f\n==================\n*/\nstatic void Cmd_Modellist_f (void)\n{\n\tchar\t\t**s;\n\tunsigned\tn;\n\tint         i;\n\tunsigned    maxclientsupportedmodels;\n\n\tif (sv_client->state != cs_connected) {\n\t\tCon_Printf (\"modellist not valid -- already spawned\\n\");\n\t\treturn;\n\t}\n\n\t// handle the case of a level changing while a client was connecting\n\tif (Q_atoi(Cmd_Argv(1)) != svs.spawncount) {\n\t\tSV_ClearReliable (sv_client);\n\t\tCon_Printf (\"SV_Modellist_f from different level\\n\");\n\t\tCmd_New_f ();\n\t\treturn;\n\t}\n\n\tn = Q_atoi(Cmd_Argv(2));\n\tif (n >= MAX_MODELS) {\n\t\tSV_ClearReliable (sv_client);\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"SV_Modellist_f: Invalid modellist index\\n\");\n\t\tSV_DropClient (sv_client);\n\t\treturn;\n\t}\n\n\tif (n == 0 && (sv_client->extensions & Z_EXT_VWEP) && sv.vw_model_name[0]) {\n\t\tint i;\n\t\tchar ss[1024] = \"//vwep \";\n\t\t// send VWep precaches\n\t\tfor (i = 0, s = sv.vw_model_name; i < MAX_VWEP_MODELS; s++, i++) {\n\t\t\tif (!*s || !**s) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (i > 0) {\n\t\t\t\tstrlcat(ss, \" \", sizeof(ss));\n\t\t\t}\n\t\t\tstrlcat(ss, TrimModelName(*s), sizeof(ss));\n\t\t}\n\t\tstrlcat(ss, \"\\n\", sizeof(ss));\n\t\tif (ss[strlen(ss) - 1] == '\\n') {       // didn't overflow?\n\t\t\tClientReliableWrite_Begin (sv_client, svc_stufftext, 2 + strlen(ss));\n\t\t\tClientReliableWrite_String (sv_client, ss);\n\t\t}\n\t}\n\n\t//NOTE:  This doesn't go through ClientReliableWrite since it's before the user\n\t//spawns.  These functions are written to not overflow\n\tif (sv_client->num_backbuf) {\n\t\tCon_Printf(\"WARNING %s: [SV_Modellist] Back buffered (%d0), clearing\\n\", sv_client->name, sv_client->netchan.message.cursize);\n\t\tsv_client->num_backbuf = 1;\n\n\t\tSZ_Clear(&sv_client->netchan.message);\n\t}\n\n#ifdef FTE_PEXT_MODELDBL\n\tif (n > 255) {\n\t\tMSG_WriteByte(&sv_client->netchan.message, svc_fte_modellistshort);\n\t\tMSG_WriteShort(&sv_client->netchan.message, n);\n\t}\n\telse\n#endif\n\t{\n\t\tMSG_WriteByte(&sv_client->netchan.message, svc_modellist);\n\t\tMSG_WriteByte(&sv_client->netchan.message, n);\n\t}\n\n\tmaxclientsupportedmodels = 256;\n#ifdef FTE_PEXT_MODELDBL\n\tif (sv_client->fteprotocolextensions & FTE_PEXT_MODELDBL) {\n\t\tmaxclientsupportedmodels = MAX_MODELS;\n\t}\n#endif\n\n\ts = sv.model_precache + 1 + n;\n\tfor (i = 1 + n; i < maxclientsupportedmodels && *s && (((i-1)&255) == 0 || sv_client->netchan.message.cursize < (MAX_MSGLEN/2)); i++, s++) {\n\t\tMSG_WriteString (&sv_client->netchan.message, *s);\n\t}\n\tn = i - 1;\n\n\tif (!s[0] || n == maxclientsupportedmodels - 1) {\n\t\tn = 0;\n\t}\n\n\t// next msg (nul terminator then next request indicator)\n\tMSG_WriteByte (&sv_client->netchan.message, 0);\n\tMSG_WriteByte (&sv_client->netchan.message, n);\n}\n\n/*\n==================\nCmd_PreSpawn_f\n==================\n*/\nstatic void Cmd_PreSpawn_f (void)\n{\n\tunsigned int buf;\n\tunsigned int check;\n\tint i, j;\n\n\tif (sv_client->state != cs_connected)\n\t{\n\t\tCon_Printf (\"prespawn not valid -- already spawned\\n\");\n\t\treturn;\n\t}\n\n\t// handle the case of a level changing while a client was connecting\n\tif (Q_atoi(Cmd_Argv(1)) != svs.spawncount)\n\t{\n\t\tSV_ClearReliable (sv_client);\n\t\tCon_Printf (\"SV_PreSpawn_f from different level\\n\");\n\t\tCmd_New_f ();\n\t\treturn;\n\t}\n\n\tbuf = Q_atoi(Cmd_Argv(2));\n\tif (buf >= sv.num_signon_buffers + sv.static_entity_count + sv.num_baseline_edicts)\n\t\tbuf = 0;\n\n\tif (!buf)\n\t{\n\t\t// should be three numbers following containing checksums\n\t\tcheck = Q_atoi(Cmd_Argv(3));\n\n\t\t//\t\tCon_DPrintf(\"Client check = %d\\n\", check);\n\n\t\tif ((int)sv_mapcheck.value && check != sv.map_checksum &&\n\t\t\tcheck != sv.map_checksum2)\n\t\t{\n\t\t\tSV_ClientPrintf (sv_client, PRINT_HIGH,\n\t\t\t                 \"Map model file does not match (%s), %i != %i/%i.\\n\"\n\t\t\t                 \"You may need a new version of the map, or the proper install files.\\n\",\n\t\t\t\t\t sv.modelname, check, sv.map_checksum, sv.map_checksum2);\n\t\t\tSV_DropClient (sv_client);\n\t\t\treturn;\n\t\t}\n\t\tsv_client->checksum = check;\n\t}\n\n\tif (SV_SkipCommsBotMessage(sv_client)) {\n\t\t// skip pre-spawning\n\t\tMSG_WriteByte (&sv_client->netchan.message, svc_stufftext);\n\t\tMSG_WriteString (&sv_client->netchan.message, va(\"cmd spawn %i 0\\n\", svs.spawncount) );\n\t\treturn;\n\t}\n\n\t//NOTE:  This doesn't go through ClientReliableWrite since it's before the user\n\t//spawns.  These functions are written to not overflow\n\tif (sv_client->num_backbuf)\n\t{\n\t\tCon_Printf(\"WARNING %s: [SV_PreSpawn] Back buffered (%d0), clearing\\n\", sv_client->name, sv_client->netchan.message.cursize);\n\t\tsv_client->num_backbuf = 0;\n\t\tSZ_Clear(&sv_client->netchan.message);\n\t}\n\n\tif (buf < sv.static_entity_count) {\n\t\tentity_state_t from = { 0 };\n\n\t\twhile (buf < sv.static_entity_count) {\n\t\t\tentity_state_t* s = &sv.static_entities[buf];\n\n\t\t\tif (sv_client->netchan.message.cursize >= (sv_client->netchan.message.maxsize / 2)) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (sv_client->fteprotocolextensions & FTE_PEXT_SPAWNSTATIC2) {\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, svc_fte_spawnstatic2);\n\t\t\t\tSV_WriteDelta(sv_client, &from, s, &sv_client->netchan.message, true);\n\t\t\t}\n\t\t\telse if (s->modelindex < 256) {\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, svc_spawnstatic);\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, s->modelindex);\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, s->frame);\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, s->colormap);\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, s->skinnum);\n\t\t\t\tfor (i = 0; i < 3; ++i) {\n\t\t\t\t\tMSG_WriteCoord(&sv_client->netchan.message, s->origin[i]);\n\t\t\t\t\tMSG_WriteAngle(&sv_client->netchan.message, s->angles[i]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t++buf;\n\t\t}\n\t}\n\telse if (buf < sv.static_entity_count + sv.num_baseline_edicts) {\n\t\tstatic entity_state_t empty_baseline = { 0 };\n\n\t\tfor (i = buf - sv.static_entity_count; i < sv.num_baseline_edicts; ++i) {\n\t\t\tedict_t* svent = EDICT_NUM(i);\n\t\t\tentity_state_t* s = &svent->e.baseline;\n\n\t\t\tif (sv_client->netchan.message.cursize >= (sv_client->netchan.message.maxsize / 2)) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (!s->number || !s->modelindex || !memcmp(s, &empty_baseline, sizeof(empty_baseline))) {\n\t\t\t\t++buf;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (sv_client->fteprotocolextensions & FTE_PEXT_SPAWNSTATIC2) {\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, svc_fte_spawnbaseline2);\n\t\t\t\tSV_WriteDelta(sv_client, &empty_baseline, s, &sv_client->netchan.message, true);\n\t\t\t}\n\t\t\telse if (s->modelindex < 256) {\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, svc_spawnbaseline);\n\t\t\t\tMSG_WriteShort(&sv_client->netchan.message, i);\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, svent->e.baseline.modelindex);\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, svent->e.baseline.frame);\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, svent->e.baseline.colormap);\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, svent->e.baseline.skinnum);\n\t\t\t\tfor (j = 0; j < 3; j++) {\n\t\t\t\t\tMSG_WriteCoord(&sv_client->netchan.message, svent->e.baseline.origin[j]);\n\t\t\t\t\tMSG_WriteAngle(&sv_client->netchan.message, svent->e.baseline.angles[j]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t++buf;\n\t\t}\n\t}\n\telse {\n\t\tSZ_Write(\n\t\t\t&sv_client->netchan.message,\n\t\t\tsv.signon_buffers[buf - sv.static_entity_count - sv.num_baseline_edicts],\n\t\t\tsv.signon_buffer_size[buf - sv.static_entity_count - sv.num_baseline_edicts]\n\t\t);\n\t\t++buf;\n\t}\n\n\tif (buf == sv.num_signon_buffers + sv.static_entity_count + sv.num_baseline_edicts) {\n\t\t// all done prespawning\n\t\tMSG_WriteByte (&sv_client->netchan.message, svc_stufftext);\n\t\tMSG_WriteString (&sv_client->netchan.message, va(\"cmd spawn %i 0\\n\", svs.spawncount) );\n\t}\n\telse {\n\t\t// need to prespawn more\n\t\tMSG_WriteByte (&sv_client->netchan.message, svc_stufftext);\n\t\tMSG_WriteString (&sv_client->netchan.message,\n\t\t                 va(\"cmd prespawn %i %i\\n\", svs.spawncount, buf) );\n\t}\n}\n\n/*\n==================\nCmd_Spawn_f\n==================\n*/\nstatic void Cmd_Spawn_f (void)\n{\n\tint         i;\n\tclient_t    *client;\n\tunsigned    n;\n\n\tif (sv_client->state != cs_connected)\n\t{\n\t\tCon_Printf (\"Spawn not valid -- already spawned\\n\");\n\t\treturn;\n\t}\n\n\t// handle the case of a level changing while a client was connecting\n\tif (Q_atoi(Cmd_Argv(1)) != svs.spawncount)\n\t{\n\t\tSV_ClearReliable (sv_client);\n\t\tCon_Printf (\"SV_Spawn_f from different level\\n\");\n\t\tCmd_New_f ();\n\t\treturn;\n\t}\n\n\tn = Q_atoi(Cmd_Argv(2));\n\tif (n >= MAX_CLIENTS)\n\t{\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH,\n\t\t                 \"SV_Spawn_f: Invalid client start\\n\");\n\t\tSV_DropClient (sv_client);\n\t\treturn;\n\t}\n\n\t// send all current names, colors, and frag counts\n\t// FIXME: is this a good thing?\n\tSZ_Clear (&sv_client->netchan.message);\n\n\t// send current status of all other players\n\n\t// normally this could overflow, but no need to check due to backbuf\n\tfor (i=n, client = svs.clients + n ; i<MAX_CLIENTS && sv_client->netchan.message.cursize < (MAX_MSGLEN/2); i++, client++)\n\t\tSV_FullClientUpdateToClient (client, sv_client);\n\n\tif (i < MAX_CLIENTS)\n\t{\n\t\tMSG_WriteByte (&sv_client->netchan.message, svc_stufftext);\n\t\tMSG_WriteString (&sv_client->netchan.message,\n\t\t                 va(\"cmd spawn %i %d\\n\", svs.spawncount, i) );\n\t\treturn;\n\t}\n\n\t// send all current light styles\n\tfor (i=0 ; i<MAX_LIGHTSTYLES ; i++)\n\t{\n\t\tClientReliableWrite_Begin (sv_client, svc_lightstyle,\n\t\t                           3 + (sv.lightstyles[i] ? strlen(sv.lightstyles[i]) : 1));\n\t\tClientReliableWrite_Byte (sv_client, (char)i);\n\t\tClientReliableWrite_String (sv_client, sv.lightstyles[i]);\n\t}\n\n\tif (sv.loadgame)\n\t{\n\t\t// loaded games are already fully initialized \n\t\t// if this is the last client to be connected, unpause\n\n\t\t// gravity and maxspeed are optional, set it if save file omited it.\n\t\tedict_t\t*ent = sv_client->edict;\n\t\tsv_client->entgravity = fofs_gravity ? EdictFieldFloat(ent, fofs_gravity) : 1.0;\n\t\tsv_client->maxspeed = fofs_maxspeed? EdictFieldFloat(ent, fofs_maxspeed) : (int)sv_maxspeed.value;\n\n\t\tif (sv.paused & 1)\n\t\t\tSV_TogglePause (NULL, 1);\n\t}\n\telse\n\t{\n\t\tSetUpClientEdict(sv_client, sv_client->edict);\n\t}\n\n\t//\n\t// force stats to be updated\n\t//\n\tmemset (sv_client->stats, 0, sizeof(sv_client->stats));\n\n\tClientReliableWrite_Begin (sv_client, svc_updatestatlong, 6);\n\tClientReliableWrite_Byte (sv_client, STAT_TOTALSECRETS);\n\tClientReliableWrite_Long (sv_client, PR_GLOBAL(total_secrets));\n\n\tClientReliableWrite_Begin (sv_client, svc_updatestatlong, 6);\n\tClientReliableWrite_Byte (sv_client, STAT_TOTALMONSTERS);\n\tClientReliableWrite_Long (sv_client, PR_GLOBAL(total_monsters));\n\n\tClientReliableWrite_Begin (sv_client, svc_updatestatlong, 6);\n\tClientReliableWrite_Byte (sv_client, STAT_SECRETS);\n\tClientReliableWrite_Long (sv_client, PR_GLOBAL(found_secrets));\n\n\tClientReliableWrite_Begin (sv_client, svc_updatestatlong, 6);\n\tClientReliableWrite_Byte (sv_client, STAT_MONSTERS);\n\tClientReliableWrite_Long (sv_client, PR_GLOBAL(killed_monsters));\n\n\t// get the client to check and download skins\n\t// when that is completed, a begin command will be issued\n\tClientReliableWrite_Begin (sv_client, svc_stufftext, 8);\n\tClientReliableWrite_String (sv_client, \"skins\\n\" );\n}\n\n/*\n==================\nSV_SpawnSpectator\n==================\n*/\nstatic void SV_SpawnSpectator (void)\n{\n\tint i;\n\tedict_t *e;\n\n\tVectorClear (sv_player->v->origin);\n\tVectorClear (sv_player->v->view_ofs);\n\tsv_player->v->view_ofs[2] = 22;\n\tsv_player->v->fixangle = true;\n\tsv_player->v->movetype = MOVETYPE_NOCLIP; // progs can change this to MOVETYPE_FLY, for example\n\n\t// search for an info_playerstart to spawn the spectator at\n\tfor (i=MAX_CLIENTS-1 ; i<sv.num_edicts ; i++)\n\t{\n\t\te = EDICT_NUM(i);\n\t\tif (!strcmp(PR_GetEntityString(e->v->classname), \"info_player_start\"))\n\t\t{\n\t\t\tVectorCopy (e->v->origin, sv_player->v->origin);\n\t\t\tVectorCopy (e->v->angles, sv_player->v->angles);\n\t\t\treturn;\n\t\t}\n\t}\n}\n\n/*\n==================\nCmd_Begin_f\n==================\n*/\nstatic void Cmd_Begin_f (void)\n{\n\tunsigned pmodel = 0, emodel = 0;\n\tint i;\n\n\tif (sv_client->state == cs_spawned)\n\t\treturn; // don't begin again\n\n\t// handle the case of a level changing while a client was connecting\n\tif (Q_atoi(Cmd_Argv(1)) != svs.spawncount)\n\t{\n\t\tSV_ClearReliable (sv_client);\n\t\tCon_Printf (\"SV_Begin_f from different level\\n\");\n\t\tCmd_New_f ();\n\t\treturn;\n\t}\n\n\tsv_client->state = cs_spawned;\n\n\tif (!sv.loadgame)\n\t{\n\t\tif (sv_client->spectator)\n\t\t\tSV_SpawnSpectator ();\n\n\t\t// copy spawn parms out of the client_t\n\t\tfor (i=0 ; i< NUM_SPAWN_PARMS ; i++)\n\t\t\t(&PR_GLOBAL(parm1))[i] = sv_client->spawn_parms[i];\n\n\t\t// call the spawn function\n\t\tpr_global_struct->time = sv.time;\n\t\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\t\tG_FLOAT(OFS_PARM0) = (float) sv_client->vip;\n\t\tPR_GameClientConnect(sv_client->spectator);\n\n\t\t// actually spawn the player\n\t\tpr_global_struct->time = sv.time;\n\t\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\t\tPR_GamePutClientInServer(sv_client->spectator);\n\t}\n\n\t// clear the net statistics, because connecting gives a bogus picture\n\tsv_client->netchan.frame_latency = 0;\n\tsv_client->netchan.frame_rate = 0;\n\tsv_client->netchan.drop_count = 0;\n\tsv_client->netchan.good_count = 0;\n\n\t//check he's not cheating\n\tif (!sv_client->spectator)\n\t{\n\t\tif (!*Info_Get (&sv_client->_userinfo_ctx_, \"pmodel\") ||\n\t\t        !*Info_Get (&sv_client->_userinfo_ctx_, \"emodel\")) //bliP: typo? 2nd pmodel to emodel\n\t\t\tSV_BroadcastPrintf (PRINT_HIGH, \"%s WARNING: missing player/eyes model checksum\\n\", sv_client->name);\n\t\telse\n\t\t{\n\t\t\tpmodel = Q_atoi(Info_Get (&sv_client->_userinfo_ctx_, \"pmodel\"));\n\t\t\temodel = Q_atoi(Info_Get (&sv_client->_userinfo_ctx_, \"emodel\"));\n\n\t\t\tif (!(pmodel == sv.model_newplayer_checksum || pmodel == sv.model_player_checksum) || emodel != sv.eyes_player_checksum)\n\t\t\t\tSV_BroadcastPrintf (PRINT_HIGH, \"%s WARNING: non standard player/eyes model detected\\n\", sv_client->name);\n\t\t}\n\t}\n\n\t// if we are paused, tell the client\n\tif (sv.paused)\n\t{\n\t\tClientReliableWrite_Begin (sv_client, svc_setpause, 2);\n\t\tClientReliableWrite_Byte (sv_client, sv.paused);\n\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"Server is paused.\\n\");\n\t}\n\n\tif (sv.loadgame)\n\t{\n\t\t// send a fixangle over the reliable channel to make sure it gets there\n\t\t// Never send a roll angle, because savegames can catch the server\n\t\t// in a state where it is expecting the client to correct the angle\n\t\t// and it won't happen if the game was just loaded, so you wind up\n\t\t// with a permanent head tilt\n\t\tedict_t *ent;\n\n\t\tent = EDICT_NUM( 1 + (sv_client - svs.clients) );\n\t\tMSG_WriteByte (&sv_client->netchan.message, svc_setangle);\n\t\tfor (i = 0; i < 2; i++)\n\t\t\tMSG_WriteAngle (&sv_client->netchan.message, ent->v->v_angle[i]);\n\t\tMSG_WriteAngle (&sv_client->netchan.message, 0);\n\t}\n\n\tsv_client->lastservertimeupdate = -99; // update immediately\n}\n\n//=============================================================================\n\n/*\n==================\nSV_DownloadNextFile\n==================\n*/\nstatic qbool SV_DownloadNextFile (void)\n{\n\tint\t\tnum;\n\tchar\t\t*name, n[MAX_OSPATH];\n\tunsigned char\tall_demos_downloaded[]\t= \"All demos downloaded.\\n\";\n\tunsigned char\tincorrect_demo_number[]\t= \"Incorrect demo number.\\n\";\n\n\tswitch (sv_client->demonum[0])\n\t{\n\tcase 1:\n\t\tif (sv_client->demolist)\n\t\t{\n\t\t\tCon_Printf((char *)Q_redtext(all_demos_downloaded));\n\t\t\tsv_client->demolist = false;\n\t\t}\n\t\tsv_client->demonum[0] = 0;\n\tcase 0:\n\t\treturn false;\n\tdefault:;\n\t}\n\n\tnum = sv_client->demonum[--(sv_client->demonum[0])];\n\tif (num == 0)\n\t{\n\t\tCon_Printf((char *)Q_redtext(incorrect_demo_number));\n\t\treturn SV_DownloadNextFile();\n\t}\n\tif (!(name = SV_MVDNum(num)))\n\t{\n\t\tCon_Printf((char *)Q_yelltext((unsigned char*)va(\"Demo number %d not found.\\n\",\n\t\t\t(num & 0xFF000000) ? -(num >> 24) : \n\t\t\t\t((num & 0x00800000) ? (num | 0xFF000000) : num) )));\n\t\treturn SV_DownloadNextFile();\n\t}\n\t//Con_Printf(\"downloading demos/%s\\n\",name);\n\tsnprintf(n, sizeof(n), \"download demos/%s\\n\", name);\n\n\tClientReliableWrite_Begin (sv_client, svc_stufftext, strlen(n) + 2);\n\tClientReliableWrite_String (sv_client, n);\n\n\treturn true;\n}\n\n/*\n==================\nSV_CompleteDownoload\n==================\n\nThis is a sub routine for  SV_NextDownload(), called when download complete, we set up some fields for sv_client.\n\n*/\n\nvoid SV_CompleteDownoload(void)\n{\n\tunsigned char download_completed[] = \"Download completed.\\n\";\n\n\tif (!sv_client->download)\n\t\treturn;\n\n\tSV_ClientDownloadComplete(sv_client);\n\n\tCon_Printf((char *)Q_redtext(download_completed));\n\n\tif (SV_DownloadNextFile())\n\t\treturn;\n\n\t// if map changed tell the client to reconnect\n\tif (sv_client->spawncount != svs.spawncount)\n\t{\n\t\tchar *str = \"changing\\n\"\n\t\t            \"reconnect\\n\";\n\n\t\tClientReliableWrite_Begin (sv_client, svc_stufftext, strlen(str)+2);\n\t\tClientReliableWrite_String (sv_client, str);\n\t}\n}\n\n/*\n==================\nCmd_NextDownload_f\n==================\n*/\n\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\n// qqshka: percent is optional, u can't relay on it\n\nvoid SV_NextChunkedDownload(int chunknum, int percent, int chunked_download_number)\n{\n#define CHUNKSIZE 1024\n\tchar buffer[CHUNKSIZE];\n\tint i;\n\tint maxchunks = bound(1, (int)sv_downloadchunksperframe.value, 30);\n\n\tsv_client->file_percent = bound(0, percent, 100); //bliP: file percent\n\n\tif (chunknum < 0)\n\t{  // qqshka: FTE's chunked download does't have any way of signaling what client complete dl-ing, so doing it this way.\n\t\tSV_CompleteDownoload();\n\t\treturn;\n\t}\n\n\t// Check if too much requests or client sent something wrong\n\tif (sv_client->download_chunks_perframe >= maxchunks || chunked_download_number < 1)\n\t\treturn;\n\n\tif (!sv_client->download_chunks_perframe) // ignore \"rate\" if not first packet per frame\n\t\tif (sv_client->datagram.cursize + CHUNKSIZE+5+50 > sv_client->datagram.maxsize)\n\t\t\treturn;\t//choked!\n\n\tif (VFS_SEEK(sv_client->download, chunknum*CHUNKSIZE, SEEK_SET))\n\t\treturn; // FIXME: ERROR of some kind\n\n\ti = VFS_READ(sv_client->download, buffer, CHUNKSIZE, NULL);\n\n\tif (i > 0)\n\t{\n\t\tbyte data[1+ (sizeof(\"\\\\chunk\")-1) + 4 + 1 + 4 + CHUNKSIZE]; // byte + (sizeof(\"\\\\chunk\")-1) + long + byte + long + CHUNKSIZE\n\t\tsizebuf_t *msg, msg_oob;\n\n\t\tif (sv_client->download_chunks_perframe)\n\t\t{\n\t\t\tmsg = &msg_oob;\n\n\t\t\tSZ_Init (&msg_oob, data, sizeof(data));\n\n\t\t\tMSG_WriteByte(msg, A2C_PRINT);\n\t\t\tSZ_Write(msg, \"\\\\chunk\", sizeof(\"\\\\chunk\")-1);\n\t\t\tMSG_WriteLong(msg, chunked_download_number); // return back, so they sure what it proper chunk\n\t\t}\n\t\telse\n\t\t\tmsg = &sv_client->datagram;\n\n\t\tif (i != CHUNKSIZE)\n\t\t\tmemset(buffer+i, 0, CHUNKSIZE-i);\n\n\t\tMSG_WriteByte(msg, svc_download);\n\t\tMSG_WriteLong(msg, chunknum);\n\t\tSZ_Write(msg, buffer, CHUNKSIZE);\n\n\t\tif (sv_client->download_chunks_perframe)\n\t\t\tNetchan_OutOfBand (NS_SERVER, sv_client->netchan.remote_address, msg->cursize, msg->data);\n\t}\n\telse {\n\t\t; // FIXME: EOF/READ ERROR\n\t}\n\n\tsv_client->download_chunks_perframe++;\n}\n\n#endif\n\nstatic void Cmd_NextDownload_f (void)\n{\n\tbyte    buffer[FILE_TRANSFER_BUF_SIZE];\n\tint     r, tmp;\n\tint     percent;\n\tint     size;\n\tdouble  frametime;\n\n\tif (!sv_client->download)\n\t\treturn;\n\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tif (sv_client->fteprotocolextensions & FTE_PEXT_CHUNKEDDOWNLOADS)\n\t{\n\t\tSV_NextChunkedDownload(atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2)), atoi(Cmd_Argv(3)));\n\t\treturn;\n\t}\n#endif\n\n\ttmp = sv_client->downloadsize - sv_client->downloadcount;\n\n\tframetime = max(0.05, min(0, sv_client->netchan.frame_rate));\n\t//Sys_Printf(\"rate:%f\\n\", sv_client->netchan.frame_rate);\n\n\tr = (int)((curtime + frametime - sv_client->netchan.cleartime)/sv_client->netchan.rate);\n\tif (r <= 10)\n\t\tr = 10;\n\tif (r > FILE_TRANSFER_BUF_SIZE)\n\t\tr = FILE_TRANSFER_BUF_SIZE;\n\n\t// don't send too much if already buffering\n\tif (sv_client->num_backbuf)\n\t\tr = 10;\n\n\tif (r > tmp)\n\t\tr = tmp;\n\n\tCon_DPrintf(\"Downloading: %d\", r);\n\tr = VFS_READ(sv_client->download, buffer, r, NULL);\n\tCon_DPrintf(\" => %d, total: %d => %d\", r, sv_client->downloadsize, sv_client->downloadcount);\n\tClientReliableWrite_Begin (sv_client, svc_download, 6 + r);\n\tClientReliableWrite_Short (sv_client, r);\n\tsv_client->downloadcount += r;\n\tif (!(size = sv_client->downloadsize))\n\t\tsize = 1;\n\tpercent = (sv_client->downloadcount * (double)100.) / size;\n\tif (percent == 100 && sv_client->downloadcount != sv_client->downloadsize)\n\t\tpercent = 99;\n\telse if (percent != 100 && sv_client->downloadcount == sv_client->downloadsize)\n\t\tpercent = 100;\n\tCon_DPrintf(\"; %d\\n\", percent);\n\tClientReliableWrite_Byte (sv_client, percent);\n\tClientReliableWrite_SZ (sv_client, buffer, r);\n\tsv_client->file_percent = percent; //bliP: file percent\n\n\tif (sv_client->downloadcount == sv_client->downloadsize)\n\t\tSV_CompleteDownoload();\n}\n\nstatic void OutofBandPrintf(netadr_t where, char *fmt, ...)\n{\n\tva_list\t argptr;\n\tchar send1[1024];\n\n\tsend1[0] = 0xff;\n\tsend1[1] = 0xff;\n\tsend1[2] = 0xff;\n\tsend1[3] = 0xff;\n\tsend1[4] = A2C_PRINT;\n\tva_start (argptr, fmt);\n\tvsnprintf (send1 + 5, sizeof(send1) - 5, fmt, argptr);\n\tva_end (argptr);\n\n\tNET_SendPacket (NS_SERVER, strlen(send1) + 1, send1, where);\n}\n\n/*\n==================\nSV_NextUpload\n==================\n*/\nvoid SV_ReplaceChar(char *s, char from, char to);\nvoid SV_CancelUpload(void)\n{\n\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"Upload denied\\n\");\n\tClientReliableWrite_Begin (sv_client, svc_stufftext, 8);\n\tClientReliableWrite_String (sv_client, \"stopul\");\n\tif (sv_client->upload)\n\t{\n\t\tfclose (sv_client->upload);\n\t\tsv_client->upload = NULL;\n\t\tsv_client->file_percent = 0; //bliP: file percent\n\t}\n}\nstatic void SV_NextUpload (void)\n{\n\tint\tpercent;\n\tint\tsize;\n\tchar\t*name = sv_client->uploadfn;\n\t//\tSys_Printf(\"-- %s\\n\", name);\n\n\tSV_ReplaceChar(name, '\\\\', '/');\n\tif (!*name || !strncmp(name, \"../\", 3) || strstr(name, \"/../\") || *name == '/'\n#ifdef _WIN32\n\t        || (isalpha(*name) && name[1] == ':')\n#endif //_WIN32\n\t   )\n\t{ //bliP: can't upload back a directory\n\t\tSV_CancelUpload();\n\t\t// suck out rest of packet\n\t\tsize = MSG_ReadShort ();\n\t\tMSG_ReadByte ();\n\t\tif (size > 0)\n\t\t\tmsg_readcount += size;\n\t\treturn;\n\t}\n\n\tsize = MSG_ReadShort ();\n\tsv_client->file_percent = percent = MSG_ReadByte ();\n\n\tif (size <= 0 || size >= MAX_DATAGRAM || percent < 0 || percent > 100)\n\t{\n\t\tSV_CancelUpload();\n\t\treturn;\n\t}\n\n\tif (sv_client->upload)\n\t{\n\t\tint pos = ftell(sv_client->upload);\n\t\tif (pos == -1 || (sv_client->remote_snap && (pos + size) > (int)sv_maxuploadsize.value))\n\t\t{\n\t\t\tmsg_readcount += size;\n\t\t\tSV_CancelUpload();\n\t\t\treturn;\n\t\t}\n\t}\n\telse\n\t{\n\t\tsv_client->upload = fopen(name, \"wb\");\n\t\tif (!sv_client->upload)\n\t\t{\n\t\t\tSys_Printf(\"Can't create %s\\n\", name);\n\t\t\tClientReliableWrite_Begin (sv_client, svc_stufftext, 8);\n\t\t\tClientReliableWrite_String (sv_client, \"stopul\");\n\t\t\t*name = 0;\n\t\t\treturn;\n\t\t}\n\t\tSys_Printf(\"Receiving %s from %d...\\n\", name, sv_client->userid);\n\t\tif (sv_client->remote_snap)\n\t\t\tOutofBandPrintf(sv_client->snap_from, \"Server receiving %s from %d...\\n\",\n\t\t\t                name, sv_client->userid);\n\n\t\t// force cache rebuild.\n\t\tFS_FlushFSHash();\n\t}\n\n\tSys_Printf(\"-\");\n\tfwrite (net_message.data + msg_readcount, 1, size, sv_client->upload);\n\tmsg_readcount += size;\n\n\tCon_DPrintf (\"UPLOAD: %d received\\n\", size);\n\n\tif (percent != 100)\n\t{\n\t\tClientReliableWrite_Begin (sv_client, svc_stufftext, 8);\n\t\tClientReliableWrite_String (sv_client, \"nextul\\n\");\n\t}\n\telse\n\t{\n\t\tfclose (sv_client->upload);\n\t\tsv_client->upload = NULL;\n\t\tsv_client->file_percent = 0; //bliP: file percent\n\n\t\tSys_Printf(\"\\n%s upload completed.\\n\", name);\n\n\t\tif (sv_client->remote_snap)\n\t\t{\n\t\t\tchar *p;\n\n\t\t\tif ((p = strchr(name, '/')) != NULL)\n\t\t\t\tp++;\n\t\t\telse\n\t\t\t\tp = name;\n\t\t\tOutofBandPrintf(sv_client->snap_from,\n\t\t\t                \"%s upload completed.\\nTo download, enter:\\ndownload %s\\n\",\n\t\t\t                name, p);\n\t\t}\n\t}\n}\n\nstatic void SV_UserCleanFilename(char* name)\n{\n\tchar* p;\n\n\t// lowercase name (needed for casesen file systems)\n\tfor (p = name; *p; p++) {\n\t\t*p = (char)tolower(*p);\n\t}\n}\n\n/*\n==================\nCmd_Download_f\n==================\n*/\n//void SV_ReplaceChar(char *s, char from, char to);\nstatic void Cmd_Download_f(void)\n{\n\tchar\t*name, n[MAX_OSPATH], *val;\n\tchar alternative_path[MAX_OSPATH];\n\textern\tcvar_t\tallow_download;\n\textern\tcvar_t\tallow_download_skins;\n\textern\tcvar_t\tallow_download_models;\n\textern\tcvar_t\tallow_download_sounds;\n\textern\tcvar_t\tallow_download_maps;\n\textern  cvar_t\tallow_download_pakmaps;\n\textern\tcvar_t\tallow_download_demos;\n\textern\tcvar_t\tallow_download_other;\n\textern  cvar_t  download_map_url; //bliP: download url\n\textern\tcvar_t\tsv_demoDir;\n\tint i;\n\tqbool allow_dl = false;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tCon_Printf(\"download [filename]\\n\");\n\t\treturn;\n\t}\n\n\tname = Cmd_Argv(1);\n\n\tSV_ReplaceChar(name, '\\\\', '/');\n\n\t// couple of checks to not allow dl-ing anything except in quake dir\n\tif (\n\t\t//TODO: split name to pathname and filename and check for 'bad symbols' only in pathname\n\t\t*name == '/' // no absolute\n\t\t|| !strncmp(name, \"../\", 3) // no leading ../\n\t\t|| strstr(name, \"/../\") // no /../\n\t\t|| ((i = strlen(name)) < 3 ? 0 : !strncmp(name + i - 3, \"/..\", 4)) // no /.. at end\n\t\t|| *name == '.' //relative is pointless\n\t\t|| ((i = strlen(name)) < 4 ? 0 : !strncasecmp(name + i - 4, \".log\", 5)) // no logs\n#ifdef _WIN32\n\t\t// no leading X:\n\t   \t|| ( name[0] && name[1] == ':' && ((*name >= 'a' && *name <= 'z') || (*name >= 'A' && *name <= 'Z')))\n#endif //_WIN32\n\t   )\n\t\tgoto deny_download;\n\n\tif (sv_client->special)\n\t\tallow_dl = true; // NOTE: user used techlogin, allow dl anything in quake dir in such case!\n\telse if (!strstr(name, \"/\"))\n\t\tallow_dl = false; // should be in subdir\n\telse if (!(int)allow_download.value)\n\t\tallow_dl = false; // global allow check\n\telse if (!strncmp(name, \"skins/\", 6))\n\t\tallow_dl = allow_download_skins.value; // skins\n\telse if (!strncmp(name, \"progs/\", 6))\n\t\tallow_dl = allow_download_models.value; // models\n\telse if (!strncmp(name, \"sound/\", 6))\n\t\tallow_dl = allow_download_sounds.value; // sounds\n\telse if (!strncmp(name, \"maps/\", 5)) // maps, note usage of allow_download_pakmaps a bit below\n\t\tallow_dl = allow_download_maps.value; // maps\n\telse if (!strncmp(name, \"demos/\", 6) || !strncmp(name, \"demonum/\", 8))\n\t\tallow_dl = allow_download_demos.value; // demos\n\telse\n\t\tallow_dl = allow_download_other.value; // all other stuff\n\n\tif (!allow_dl)\n\t\tgoto deny_download;\n\n\tSV_ClientDownloadComplete(sv_client);\n\n\tmemset(alternative_path, 0, sizeof(alternative_path));\n\tif ( !strncmp(name, \"demos/\", 6) && sv_demoDir.string[0])\n\t{\n\t\tsnprintf(n,sizeof(n), \"%s/%s\", sv_demoDir.string, name + 6);\n\t\tname = n;\n\n\t\tif (sv_demoDirAlt.string[0]) {\n\t\t\tstrlcpy(alternative_path, sv_demoDirAlt.string, sizeof(alternative_path));\n\t\t\tstrlcat(alternative_path, \"/\", sizeof(alternative_path));\n\t\t\tstrlcat(alternative_path, name + 6, sizeof(alternative_path));\n\t\t}\n\t}\n\telse if (!strncmp(name, \"demonum/\", 8))\n\t{\n\t\tint num = Q_atoi(name + 8);\n\t\tif (num == 0 && name[8] != '0')\n\t\t{\n\t\t\tchar *num_s = name + 8;\n\t\t\tint num_s_len = strlen(num_s);\n\t\t\tfor (num = 0; num < num_s_len; num++)\n\t\t\t\tif (num_s[num] != '.')\n\t\t\t\t{\n\t\t\t\t\tCon_Printf(\"usage: download demonum/num\\n\"\n\t\t\t\t\t           \"if num is negative then download the Nth to last recorded demo, \"\n\t\t\t\t\t           \"also can type any quantity of dots and \"\n\t\t\t\t\t           \"where N dots is the Nth to last recorded demo\\n\");\n\n\t\t\t\t\tgoto deny_download;\n\t\t\t\t}\n\t\t\tnum &= 0xF;\n\t\t\tnum <<= 24;\n\t\t}\n\t\telse\n\t\t\tnum &= 0x00FFFFFF;\n\t\tname = SV_MVDNum(num);\n\t\tif (!name)\n\t\t{\n\t\t\tCon_Printf((char *)Q_yelltext((unsigned char*)va(\"Demo number %d not found.\\n\",\n\t\t\t\t(num & 0xFF000000) ? -(num >> 24) : ((num & 0x00800000) ? (num | 0xFF000000) : num) )));\n\t\t\tgoto deny_download;\n\t\t}\n\t\t//Con_Printf(\"downloading demos/%s\\n\",name);\n\t\tsnprintf(n, sizeof(n), \"download demos/%s\\n\", name);\n\n\t\tClientReliableWrite_Begin (sv_client, svc_stufftext,strlen(n) + 2);\n\t\tClientReliableWrite_String (sv_client, n);\n\t\treturn;\n\t}\n\n\tSV_UserCleanFilename(name);\n\tSV_UserCleanFilename(alternative_path);\n\n\tsv_client->downloadcount = 0;\n\n#ifdef SERVERONLY\n#define CLIENT_DOWNLOAD_RELATIVE_BASE FS_GAME // FIXME: Should we use FS_BASE ???\n#else\n#define CLIENT_DOWNLOAD_RELATIVE_BASE FS_BASE\n#endif\n\n\tsv_client->download = FS_OpenVFS(name, \"rb\", CLIENT_DOWNLOAD_RELATIVE_BASE);\n\tif (!sv_client->download && alternative_path[0]) {\n\t\tsv_client->download = FS_OpenVFS(alternative_path, \"rb\", CLIENT_DOWNLOAD_RELATIVE_BASE);\n\t}\n\tif (sv_client->download) {\n\t\tsv_client->downloadsize = VFS_GETLEN(sv_client->download);\n\t}\n\n\t// if not techlogin, perform extra check to block .pak maps\n\tif (!sv_client->special) {\n\t\t// special check for maps that came from a pak file\n\t\tif (sv_client->download && !strncmp(name, \"maps/\", 5) && VFS_COPYPROTECTED(sv_client->download) && !(int)allow_download_pakmaps.value) {\n\t\t\tSV_ClientDownloadComplete(sv_client);\n\t\t\tgoto deny_download;\n\t\t}\n\t}\n\n\tif (!sv_client->download)\n\t{\n\t\tSys_Printf (\"Couldn't download %s to %s\\n\", name, sv_client->name);\n\t\tgoto deny_download;\n\t}\n\n\t// set donwload rate\n\tval = Info_Get (&sv_client->_userinfo_ctx_, \"drate\");\n\tsv_client->netchan.rate = 1. / SV_BoundRate(true, Q_atoi(*val ? val : \"99999\"));\n\t// disable duplicate packet setting while downloading\n\tsv_client->netchan.dupe = 0;\n\n\t// all checks passed, start downloading\n\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tif (sv_client->fteprotocolextensions & FTE_PEXT_CHUNKEDDOWNLOADS)\n\t{\n\t\tClientReliableWrite_Begin (sv_client, svc_download, 10+strlen(name));\n\t\tClientReliableWrite_Long (sv_client, -1);\n\t\tClientReliableWrite_Long (sv_client, sv_client->downloadsize);\n\t\tClientReliableWrite_String (sv_client, name);\n\t}\n#endif\n\n\tCmd_NextDownload_f ();\n\tSys_Printf (\"Downloading %s to %s\\n\", name, sv_client->name);\n\n\t//bliP: download info/download url ->\n\tif (!strncmp(name, \"maps/\", 5))\n\t{\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Map %s is %.0fKB (%.2fMB)\\n\",\n\t\t                 name, (float)sv_client->downloadsize / 1024,\n\t\t                 (float)sv_client->downloadsize / 1024 / 1024);\n\t\tif (download_map_url.string[0])\n\t\t{\n\t\t\tname += 5;\n\t\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Download this map faster:\\n\");\n\t\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"%s%s\\n\\n\",\n\t\t\t                 download_map_url.string, name);\n\t\t}\n\t}\n\telse\n\t{\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"File %s is %.0fKB (%.2fMB)\\n\",\n\t\t                 name, (float)sv_client->downloadsize / 1024,\n\t\t                 (float)sv_client->downloadsize / 1024 / 1024);\n\t}\n\t//<-\n\n\treturn;\n\ndeny_download:\n\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tif (sv_client->fteprotocolextensions & FTE_PEXT_CHUNKEDDOWNLOADS)\n\t{\n\t\tClientReliableWrite_Begin (sv_client, svc_download, 10+strlen(name));\n\t\tClientReliableWrite_Long (sv_client, -1);\n\t\tClientReliableWrite_Long (sv_client, -1); // FIXME: -1 = Couldn't download, -2 = deny, we always use -1 atm\n\t\tClientReliableWrite_String (sv_client, name);\n\t}\n\telse\n#endif\n\t{\n\t\tClientReliableWrite_Begin (sv_client, svc_download, 4);\n\t\tClientReliableWrite_Short (sv_client, -1);\n\t\tClientReliableWrite_Byte (sv_client, 0);\n\t}\n\n\tSV_DownloadNextFile();\n\n\treturn;\n}\n\n/*\n==================\nCmd_DemoDownload_f\n==================\n*/\nstatic void Cmd_StopDownload_f(void);\n\nstatic void Cmd_DemoDownload_f(void)\n{\n\tint\t\ti, num, cmd_argv_i_len;\n\tchar\t\t*cmd_argv_i;\n\tunsigned char\tdownload_queue_cleared[] = \"Download queue cleared.\\n\";\n\tunsigned char\tdownload_queue_empty[] = \"Download queue empty.\\n\";\n\tunsigned char\tdownload_queue_already_exists[]\t= \"Download queue already exists.\\n\";\n\tunsigned char\tcmdhelp_dldesc[] = \"Download a demo from the server your are connected to\";\n\tunsigned char\tcmdhelp_dl[] = \"cmd dl\";\n\tunsigned char\tcmdhelp_pound[] = \"#\";\n\tunsigned char\tcmdhelp_dot[] = \".\";\n\tunsigned char\tcmdhelp_bs[] = \"\\\\\";\n\tunsigned char\tcmdhelp_stop[] = \"stop\";\n\tunsigned char\tcmdhelp_cancel[] = \"cancel\";\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tCon_Printf(\"\\n%s\\n\"\n\t\t           \"Usage:\\n\"\n\t\t           \"  %s %s [%s [%s]]\\n\"\n\t\t           \"    \\\"#\\\" is one or several numbers from the demo list\\n\"\n\t\t           \"  %s %s [%s%s [%s%s%s]]\\n\"\n\t\t           \"    Each number of dots represents the Nth last recorded demo\\n\"\n\t\t           \"    (Note that you can mix numbers and groups of dots)\\n\"\n\t\t           \"  %s [%s|%s|%s]\\n\"\n\t\t           \"     \\\"\\\\\\\", \\\"stop\\\" or \\\"cancel\\\" clear the download queue\\n\\n\",\n\t\t           Q_redtext(cmdhelp_dldesc),\n\t\t           Q_redtext(cmdhelp_dl), Q_redtext(cmdhelp_pound), Q_redtext(cmdhelp_pound), Q_redtext(cmdhelp_pound),\n\t\t           Q_redtext(cmdhelp_dl), Q_redtext(cmdhelp_dot), Q_redtext(cmdhelp_dot), Q_redtext(cmdhelp_dot),\n\t\t           Q_redtext(cmdhelp_dot), Q_redtext(cmdhelp_dot), Q_redtext(cmdhelp_dot),\n\t\t           Q_redtext(cmdhelp_dl), Q_redtext(cmdhelp_bs), Q_redtext(cmdhelp_stop), Q_redtext(cmdhelp_cancel)\n\t\t);\n\t\treturn;\n\t}\n\n\tif (!strcmp(Cmd_Argv(1), \"cancel\") || !strcmp(Cmd_Argv(1), \"stop\"))\n\t{\n\t\tCmd_StopDownload_f(); // should not have any arguments, so it's OK\n\t\treturn;\n\t}\n\n\tif (!strcmp(Cmd_Argv(1), \"\\\\\"))\n\t{\n\t\tif (sv_client->demonum[0])\n\t\t{\n\t\t\tCon_Printf((char *)Q_redtext(download_queue_cleared));\n\t\t\tsv_client->demonum[0] = 0;\n\t\t}\n\t\telse\n\t\t\tCon_Printf((char *)Q_redtext(download_queue_empty));\n\t\treturn;\n\t}\n\n\tif (sv_client->demonum[0])\n\t{\n\t\tCon_Printf((char *)Q_redtext(download_queue_already_exists));\n\t\treturn;\n\t}\n\n\tsv_client->demolist = ((sv_client->demonum[0] = Cmd_Argc()) > 2);\n\tfor (i = 1; i < sv_client->demonum[0]; i++)\n\t{\n\t\tcmd_argv_i = Cmd_Argv(i);\n\t\tcmd_argv_i_len = strlen(cmd_argv_i);\n\t\tnum = Q_atoi(cmd_argv_i);\n\t\tif (num == 0 && cmd_argv_i[0] != '0')\n\t\t{\n\t\t\tfor (num = 0; num < cmd_argv_i_len; num++)\n\t\t\t\tif (cmd_argv_i[num] != '.')\n\t\t\t\t{\n\t\t\t\t\tnum = 0;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tif (num)\n\t\t\t{\n\t\t\t\tnum &= 0xF;\n\t\t\t\tnum <<= 24;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\tnum &= 0x00FFFFFF;\n\t\tsv_client->demonum[sv_client->demonum[0] - i] = num;\n\t}\n\tSV_DownloadNextFile();\n}\n\n/*\n==================\nCmd_StopDownload_f\n==================\n*/\nstatic void Cmd_StopDownload_f(void)\n{\n\tunsigned char\tdownload_stopped[] = \"Download stopped and download queue cleared.\\n\";\n\n\tif (!sv_client->download)\n\t\treturn;\n\n\tsv_client->downloadcount = sv_client->downloadsize;\n\tSV_ClientDownloadComplete(sv_client);\n\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tif (sv_client->fteprotocolextensions & FTE_PEXT_CHUNKEDDOWNLOADS)\n\t{\n\t\tchar *name = \"\"; // FIXME: FTE's chunked dl does't support \"cmd stopdl\", work around\n\n\t\tClientReliableWrite_Begin (sv_client, svc_download, 10+strlen(name));\n\t\tClientReliableWrite_Long (sv_client, -1);\n\t\tClientReliableWrite_Long (sv_client, -3); // -3 = dl was stopped\n\t\tClientReliableWrite_String (sv_client, name);\n\t}\n\telse\n#endif\n\t{\n\t\tClientReliableWrite_Begin (sv_client, svc_download, 6);\n\t\tClientReliableWrite_Short (sv_client, 0);\n\t\tClientReliableWrite_Byte (sv_client, 100);\n\t}\n\n\tsv_client->demonum[0] = 0;\n\tsv_client->demolist = false;\n\n\tCon_Printf ((char *)Q_redtext(download_stopped));\n}\n//=============================================================================\n\n/*\n==================\nSV_Say\n==================\n*/\n\nstatic void SV_Say (qbool team)\n{\n\tqbool    fake = false;\n\tclient_t *client;\n\tint      j, tmp, cls = 0;\n\tchar     *p;                  // used basically for QC based mods.\n\tchar     text[2048] = {0};    // used if mod does not have own support for say/say_team.\n\tqbool    write_begin;\n\n\tif (Cmd_Argc () < 2)\n\t\treturn;\n\n\tp = Cmd_Args();\n\n\t// unfake if requested.\n\tif (!team && sv_unfake.value)\n\t{\n\t\tchar *ch;\n\n\t\tfor (ch = p; *ch; ch++)\n\t\t\tif (*ch == 13)\n\t\t\t\t*ch = '#';\n\t}\n\n\t// remove surrounding \" if any.\n\tif (p[0] == '\"' && (j = (int)strlen(p)) > 2 && p[j-1] == '\"')\n\t{\n\t\t// form text[].\n\t\tsnprintf(text, sizeof(text), \"%s\", p + 1); // skip opening \" and copy rest text including closing \".\n\t\ttext[max(0,(int)strlen(text)-1)] = '\\n';   // replace closing \" with new line.\n\t}\n\telse\n\t{\n\t\t// form text[].\n\t\tsnprintf(text, sizeof(text), \"%s\\n\", p);\n\t}\n\n\tif (!sv_client->logged && !sv_client->logged_in_via_web)\n\t{\n\t\tSV_ParseLogin(sv_client);\n\t\treturn;\n\t}\n\n\t// try handle say in the mod.\n\tSV_EndRedirect ();\n\n\tpr_global_struct->time = sv.time;\n\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\n\tj = PR_ClientSay(team, p);\n\n\tSV_BeginRedirect (RD_CLIENT);\n\n\tif (j)\n\t\treturn; // say was handled by mod.\n\n\tif (sv_client->spectator && (!(int)sv_spectalk.value || team))\n\t\tstrlcpy(text, va(\"[SPEC] %s: %s\", sv_client->name, text), sizeof(text));\n\telse if (team)\n\t\tstrlcpy(text, va(\"(%s): %s\", sv_client->name, text), sizeof(text));\n\telse\n\t{\n\t\tstrlcpy(text, va(\"%s: %s\", sv_client->name, text), sizeof(text));\n\t}\n\n\tif (fp_messages) {\n\t\tif (curtime < sv_client->lockedtill) {\n\t\t\tSV_ClientPrintf(sv_client, PRINT_CHAT, \"You can't talk for %d more seconds\\n\", (int) (sv_client->lockedtill - curtime));\n\t\t\treturn;\n\t\t}\n\t\ttmp = sv_client->whensaidhead - fp_messages + 1;\n\t\tif (tmp < 0)\n\t\t\ttmp = 10+tmp;\n\t\tif (sv_client->whensaid[tmp] && (curtime - sv_client->whensaid[tmp] < fp_persecond)) {\n\t\t\tsv_client->lockedtill = curtime + fp_secondsdead;\n\t\t\tif (fp_msg[0]) {\n\t\t\t\tSV_ClientPrintf(sv_client, PRINT_CHAT, \"FloodProt: %s\\n\", fp_msg);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tSV_ClientPrintf(sv_client, PRINT_CHAT, \"FloodProt: You can't talk for %d seconds.\\n\", fp_secondsdead);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tsv_client->whensaidhead++;\n\t\tif (sv_client->whensaidhead > 9)\n\t\t\tsv_client->whensaidhead = 0;\n\t\tsv_client->whensaid[sv_client->whensaidhead] = curtime;\n\t}\n\n\tSys_Printf (\"%s\", text);\n\tSV_Write_Log(CONSOLE_LOG, 1, text);\n\n\tfake = ( strchr(text, 13) ? true : false ); // check if string contain \"$\\\"\n\n\tfor (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)\n\t{\n\t\tif (client->state < cs_preconnected)\n\t\t\tcontinue;\n\n\t\tif (team)\n\t\t{\n\t\t\t// the spectator team\n\t\t\tif (sv_client->spectator)\n\t\t\t{\n\t\t\t\tif (!client->spectator)\n\t\t\t\t\tcontinue;\t// on different teams\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (sv_client == client)\n\t\t\t\t\t; // send msg to self anyway\n\t\t\t\telse if (client->spectator)\n\t\t\t\t{\n\t\t\t\t\tif( !sv_sayteam_to_spec.value // player can't say_team to spec in this case\n\t\t\t\t\t    || !fake // self say_team does't contain $\\ so this is treat as private message\n\t\t\t\t\t    || (client->spec_track <= 0 && strcmp(sv_client->team, client->team)) // spec do not track player and on different team\n\t\t\t\t\t    || (client->spec_track > 0 && strcmp(sv_client->team, svs.clients[client->spec_track - 1].team)) // spec track player on different team\n\t\t\t\t\t)\n\t\t\t\t\t\tcontinue;\t// on different teams\n\t\t\t\t}\n\t\t\t\telse if (coop.value)\n\t\t\t\t\t; // allow team messages to everyone in coop from players.\n\t\t\t\telse if (!teamplay.value)\n\t\t\t\t\tcontinue; // non team game\n\t\t\t\telse if (strcmp(sv_client->team, client->team))\n\t\t\t\t\tcontinue; // on different teams\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (sv_client->spectator)\n\t\t\t{\n\t\t\t\t// check for spectalk off.\n\t\t\t\tif (!client->spectator && !(int)sv_spectalk.value)\n\t\t\t\t\tcontinue; // off - specs can't talk to players.\n\t\t\t}\n\t\t}\n\n\t\tcls |= 1 << j;\n\t\tSV_ClientPrintf2(client, PRINT_CHAT, \"%s\", text);\n\t}\n\n\tif (!sv.mvdrecording || !cls)\n\t\treturn;\n\n\t// non-team messages should be seen always, even if not tracking any player\n\tif (!team && ((sv_client->spectator && (int)sv_spectalk.value) || !sv_client->spectator))\n\t{\n\t\twrite_begin = MVDWrite_Begin (dem_all, 0, strlen(text)+3);\n\t}\n\telse\n\t{\n\t\twrite_begin = MVDWrite_Begin (dem_multiple, cls, strlen(text)+3);\n\t}\n\n\tif (write_begin)\n\t{\n\t\tMVD_MSG_WriteByte (svc_print);\n\t\tMVD_MSG_WriteByte (PRINT_CHAT);\n\t\tMVD_MSG_WriteString (text);\n\t}\n}\n\n/*\n==================\nCmd_Say_f\n==================\n*/\nstatic void Cmd_Say_f(void)\n{\n\tSV_Say (false);\n}\n/*\n==================\nCmd_Say_Team_f\n==================\n*/\nstatic void Cmd_Say_Team_f(void)\n{\n\tSV_Say (true);\n}\n\n\n\n//============================================================================\n\n/*\n=================\nCmd_Pings_f\n\nThe client is showing the scoreboard, so send new ping times for all\nclients\n=================\n*/\nstatic void Cmd_Pings_f (void)\n{\n\tclient_t *client;\n\tint j;\n\n\tfor (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)\n\t{\n\t\tif (!(client->state == cs_spawned || (client->state == cs_connected/* && client->spawncount != svs.spawncount*/)) )\n\t\t\tcontinue;\n\n\t\tClientReliableWrite_Begin (sv_client, svc_updateping, 4);\n\t\tClientReliableWrite_Byte (sv_client, j);\n\t\tClientReliableWrite_Short (sv_client, SV_CalcPing(client));\n\t\tClientReliableWrite_Begin (sv_client, svc_updatepl, 4);\n\t\tClientReliableWrite_Byte (sv_client, j);\n\t\tClientReliableWrite_Byte (sv_client, client->lossage);\n\t}\n}\n\n\n/*\n==================\nCmd_Kill_f\n==================\n*/\nstatic void Cmd_Kill_f (void)\n{\n\tif (sv_player->v->health <= 0)\n\t{\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Can't suicide -- already dead!\\n\");\n\t\treturn;\n\t}\n\n\tpr_global_struct->time = sv.time;\n\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\tPR_ClientKill();\n}\n\nstatic void SV_NotifyStreamsOfPause(void)\n{\n\tif (sv.mvdrecording) {\n\t\tsizebuf_t\t\tmsg;\n\t\tbyte\t\t\tmsg_buf[20];\n\n\t\tSZ_InitEx(&msg, msg_buf, sizeof(msg_buf), true);\n\t\tMSG_WriteByte(&msg, svc_setpause);\n\t\tMSG_WriteByte(&msg, sv.paused ? 1 : 0);\n\n\t\tDemoWriteQTV(&msg);\n\t}\n}\n\n/*\n==================\nSV_TogglePause\n==================\n*/\nvoid SV_TogglePause (const char *msg, int bit)\n{\n\tint i;\n\tclient_t *cl;\n\textern cvar_t sv_paused;\n\n\tsv.paused ^= bit;\n\t\n\tCvar_SetROM (&sv_paused, va(\"%i\", sv.paused));\n\n\tif (sv.paused)\n\t\tsv.pausedsince = Sys_DoubleTime();\n\n\tif (msg)\n\t\tSV_BroadcastPrintf (PRINT_HIGH, \"%s\", msg);\n\n\t// send notification to all clients\n\tfor (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)\n\t{\n\t\tif (!cl->state)\n\t\t\tcontinue;\n\t\tClientReliableWrite_Begin (cl, svc_setpause, 2);\n\t\tClientReliableWrite_Byte (cl, sv.paused ? 1 : 0);\n\n\t\tcl->lastservertimeupdate = -99; // force an update to be sent\n\t}\n\n\t// send notification to all streams\n\tSV_NotifyStreamsOfPause();\n}\n\n\n/*\n==================\nCmd_Pause_f\n==================\n*/\nstatic void Cmd_Pause_f (void)\n{\n\tchar st[CLIENT_NAME_LEN + 32];\n\tqbool newstate;\n\n\tnewstate = sv.paused ^ 1;\n\n\tif (!(int)pausable.value)\n\t{\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Pause not allowed.\\n\");\n\t\treturn;\n\t}\n\n\tif (sv_client->spectator)\n\t{\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Spectators can not pause.\\n\");\n\t\treturn;\n\t}\n\n\tif (GE_ShouldPause) {\n\t\tpr_global_struct->time = sv.time;\n\t\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\t\tG_FLOAT(OFS_PARM0) = newstate;\n\t\tPR_ExecuteProgram (GE_ShouldPause);\n\t\tif (!G_FLOAT(OFS_RETURN))\n\t\t\treturn;\t\t// progs said ignore the request\n\t}\n\n\tif (newstate & 1)\n\t\tsnprintf (st, sizeof(st), \"%s paused the game\\n\", sv_client->name);\n\telse\n\t\tsnprintf (st, sizeof(st), \"%s unpaused the game\\n\", sv_client->name);\n\n\tSV_TogglePause(st, 1);\n}\n\n\n/*\n=================\nCmd_Drop_f\n\nThe client is going to disconnect, so remove the connection immediately\n=================\n*/\nstatic void Cmd_Drop_f (void)\n{\n\tSV_EndRedirect ();\n\tif (sv_client->state == cs_zombie) // FIXME\n\t\treturn; // FIXME\n\tif (!sv_client->spectator)\n\t\tSV_BroadcastPrintf (PRINT_HIGH, \"%s dropped\\n\", sv_client->name);\n\tSV_DropClient (sv_client);\n}\n\n/*\n=================\nCmd_PTrack_f\n\nChange the bandwidth estimate for a client\n=================\n*/\nstatic void Cmd_PTrack_f (void)\n{\n\tint\t\ti;\n\tedict_t *ent, *tent;\n\n\tif (!sv_client->spectator)\n\t\treturn;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\t// turn off tracking\n\t\tsv_client->spec_track = 0;\n\t\tent = EDICT_NUM(sv_client - svs.clients + 1);\n\t\ttent = EDICT_NUM(0);\n\t\tent->v->goalentity = EDICT_TO_PROG(tent);\n\t\treturn;\n\t}\n\n\ti = Q_atoi(Cmd_Argv(1));\n\tif (i < 0 || i >= MAX_CLIENTS || svs.clients[i].state != cs_spawned || svs.clients[i].spectator)\n\t{\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Invalid client to track\\n\");\n\t\tsv_client->spec_track = 0;\n\t\tent = EDICT_NUM(sv_client - svs.clients + 1);\n\t\ttent = EDICT_NUM(0);\n\t\tent->v->goalentity = EDICT_TO_PROG(tent);\n\t\treturn;\n\t}\n\tsv_client->spec_track = i + 1; // now tracking\n\n\tent = EDICT_NUM(sv_client - svs.clients + 1);\n\ttent = EDICT_NUM(i + 1);\n\tent->v->goalentity = EDICT_TO_PROG(tent);\n}\n\n/*\n=================\nCmd_Rate_f\n\nChange the bandwidth estimate for a client\n=================\n*/\nstatic void Cmd_Rate_f (void)\n{\n\tint\t\trate;\n\n\tif (Cmd_Argc() != 2)\n\t{\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Current rate is %i\\n\",\n\t\t                 (int)(1.0/sv_client->netchan.rate + 0.5));\n\t\treturn;\n\t}\n\n\trate = SV_BoundRate (sv_client->download != NULL, Q_atoi(Cmd_Argv(1)));\n\n\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Net rate set to %i\\n\", rate);\n\tsv_client->netchan.rate = 1.0/rate;\n}\n\n//bliP: upload files ->\n/*\n=================\nCmd_TechLogin_f\nLogin to upload\n=================\n*/\nint Master_Rcon_Validate (void);\nstatic void Cmd_TechLogin_f (void)\n{\n\tif (sv_client->logincount > 4) //denied\n\t\treturn;\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\tif (sv_client->special)\n\t\t{\n\t\t\tsv_client->special = false;\n\t\t\tsv_client->logincount = 0;\n\t\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Logged out.\\n\");\n\t\t}\n\t\treturn;\n\t}\n\n\tif (!Master_Rcon_Validate()) //don't even let them know they're wrong\n\t{\n\t\tsv_client->logincount++;\n\t\treturn;\n\t}\n\n\tsv_client->special = true;\n\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Logged in.\\n\");\n}\n\n/*\n================\nCmd_Upload_f\n================\n*/\nstatic void Cmd_Upload_f (void)\n{\n\tFILE *f;\n\tchar str[MAX_OSPATH];\n\n\tif (sv_client->state != cs_spawned)\n\t\treturn;\n\n\tif (!sv_client->special)\n\t{\n\t\tCon_Printf (\"Client not tagged to upload.\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() != 3)\n\t{\n\t\tCon_Printf (\"upload [local filename] [remote filename]\\n\");\n\t\treturn;\n\t}\n\n\tsnprintf(sv_client->uploadfn, sizeof(sv_client->uploadfn), \"%s\", Cmd_Argv(2));\n\n\tif (!sv_client->uploadfn[0])\n\t{ //just in case..\n\t\tCon_Printf (\"Bad file name.\\n\");\n\t\treturn;\n\t}\n\n\tif ((f = fopen(sv_client->uploadfn, \"rb\")))\n\t{\n\t\tCon_Printf (\"File already exists.\\n\");\n\t\tfclose(f);\n\t\treturn;\n\t}\n\n\tsv_client->remote_snap = false;\n\tFS_CreatePath (sv_client->uploadfn); //fixed, need to create path\n\tsnprintf (str, sizeof (str), \"cmd fileul \\\"%s\\\"\\n\", Cmd_Argv(1));\n\tClientReliableWrite_Begin (sv_client, svc_stufftext, strlen(str) + 2);\n\tClientReliableWrite_String (sv_client, str);\n}\n//<-\n\n/*\n==================\nCmd_SetInfo_f\n\nAllow clients to change userinfo\n==================\n*/\n\nchar *shortinfotbl[] =\n{\n\t\"name\",\n\t\"team\",\n\t\"skin\",\n\t\"topcolor\",\n\t\"bottomcolor\",\n#ifdef CHAT_ICON_EXPERIMENTAL\n\t\"chat\",\n#endif\n\t\"gender\",\n\t\"*auth\",\n\t\"*flag\",\n\t//\"*client\",\n\t//\"*spectator\",\n\t//\"*VIP\",\n\tNULL\n};\n\nstatic void Cmd_SetInfo_f (void)\n{\n\textern cvar_t sv_forcenick;\n\tsv_client_state_t saved_state;\n\tchar oldval[MAX_EXT_INFO_STRING];\n\tchar info[MAX_EXT_INFO_STRING];\n\n\tif (sv_kickuserinfospamtime.value > 0 && (int)sv_kickuserinfospamcount.value > 0)\n\t{\n\t\tif (!sv_client->lastuserinfotime ||\n\t\t\tcurtime - sv_client->lastuserinfotime > sv_kickuserinfospamtime.value)\n\t\t{\n\t\t\tsv_client->lastuserinfocount = 0;\n\t\t\tsv_client->lastuserinfotime = curtime;\n\t\t}\n\t\telse if (++(sv_client->lastuserinfocount) > (int)sv_kickuserinfospamcount.value)\n\t\t{\n\t\t\tif (!sv_client->drop)\n\t\t\t{\n\t\t\t\tsaved_state = sv_client->state;\n\t\t\t\tsv_client->state = cs_free;\n\t\t\t\tSV_BroadcastPrintf (PRINT_HIGH,\n\t\t\t\t                    \"%s was kicked for userinfo spam\\n\", sv_client->name);\n\t\t\t\tsv_client->state = saved_state;\n\t\t\t\tSV_ClientPrintf (sv_client, PRINT_HIGH,\n\t\t\t    \t             \"You were kicked from the game for userinfo spamming\\n\");\n\t\t\t\tSV_LogPlayer (sv_client, \"userinfo spam\", 1);\n\t\t\t\tsv_client->drop = true;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n\n\tswitch (Cmd_Argc())\n\t{\n\t\tcase 1:\n\t\t\tCon_Printf (\"User info settings:\\n\");\n\n\t\t\tInfo_ReverseConvert(&sv_client->_userinfo_ctx_, info, sizeof(info));\n\t\t\tInfo_Print(info);\n\t\t\tCon_DPrintf (\"[%d/%d]\\n\", strlen(info), sizeof(info));\n\n\t\t\tif (developer.value)\n\t\t\t{\n\t\t\t\tCon_Printf (\"User info settings short:\\n\");\n\t\t\t\tInfo_ReverseConvert(&sv_client->_userinfoshort_ctx_, info, sizeof(info));\n\t\t\t\tInfo_Print(info);\n\t\t\t\tCon_DPrintf (\"[%d/%d]\\n\", strlen(info), sizeof(info));\n\t\t\t}\n\n\t\t\treturn;\n\t\tcase 3:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tCon_Printf (\"usage: setinfo [ <key> <value> ]\\n\");\n\t\t\treturn;\n\t}\n\n\tif (Cmd_Argv(1)[0] == '*')\n\t\treturn;\t\t// don't set privileged values\n\n\tif (strchr(Cmd_Argv(1), '\\\\') || strchr(Cmd_Argv(2), '\\\\'))\n\t\treturn;\t\t// illegal char\n\n\tif (strstr(Cmd_Argv(1), \"&c\") || strstr(Cmd_Argv(1), \"&r\") || strstr(Cmd_Argv(2), \"&c\") || strstr(Cmd_Argv(2), \"&r\"))\n\t\treturn;\n\n\tstrlcpy(oldval, Info_Get(&sv_client->_userinfo_ctx_, Cmd_Argv(1)), sizeof(oldval));\n\n\tpr_global_struct->time = sv.time;\n\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\tif (PR_UserInfoChanged(0))\n\t\treturn; // does not allowed to be changed by mod.\n\n\tInfo_Set (&sv_client->_userinfo_ctx_, Cmd_Argv(1), Cmd_Argv(2));\n\t// name is extracted below in ExtractFromUserInfo\n\t//\tstrlcpy (sv_client->name, Info_ValueForKey (sv_client->userinfo, \"name\")\n\t//\t\t, CLIENT_NAME_LEN);\n\t//\tSV_FullClientUpdate (sv_client, &sv.reliable_datagram);\n\t//\tsv_client->sendinfo = true;\n\n\t//Info_ValueForKey(sv_client->userinfo, Cmd_Argv(1));\n\tif (!strcmp(Info_Get(&sv_client->_userinfo_ctx_, Cmd_Argv(1)), oldval))\n\t\treturn; // key hasn't changed\n\n\tif (!strcmp(Cmd_Argv(1), \"name\"))\n\t{\n\t\t//bliP: mute ->\n\t\tif (curtime < sv_client->lockedtill)\n\t\t{\n\t\t\tSV_ClientPrintf(sv_client, PRINT_CHAT, \"You can't change your name while you're muted\\n\");\n\t\t\treturn;\n\t\t}\n\t\t//<-\n\t\t//VVD: forcenick ->\n\t\t//meag: removed sv_login check to allow optional logins... sv_forcenick should still take effect\n\t\tif ((int)sv_forcenick.value && /*(int)sv_login.value &&*/ sv_client->login[0])\n\t\t{\n\t\t\t// allow differences in case, redtext\n\t\t\tif (Q_namecmp(sv_client->login, Cmd_Argv(2))) {\n\t\t\t\tSV_ClientPrintf(sv_client, PRINT_CHAT, \"You can't change your name while logged in on this server.\\n\");\n\t\t\t\tInfo_Set(&sv_client->_userinfo_ctx_, \"name\", sv_client->login);\n\t\t\t\tstrlcpy(sv_client->name, sv_client->login, CLIENT_NAME_LEN);\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, svc_stufftext);\n\t\t\t\tMSG_WriteString(&sv_client->netchan.message, va(\"name %s\\n\", sv_client->login));\n\t\t\t\tMSG_WriteByte(&sv_client->netchan.message, svc_stufftext);\n\t\t\t\tMSG_WriteString(&sv_client->netchan.message, va(\"setinfo name %s\\n\", sv_client->login));\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\t//<-\n\t}\n\n\t//bliP: kick top ->\n\tif ((int)sv_kicktop.value && !strcmp(Cmd_Argv(1), \"topcolor\"))\n\t{\n\t\tif (!sv_client->lasttoptime || curtime - sv_client->lasttoptime > 8)\n\t\t{\n\t\t\tsv_client->lasttopcount = 0;\n\t\t\tsv_client->lasttoptime = curtime;\n\t\t}\n\t\telse if (sv_client->lasttopcount++ > 5)\n\t\t{\n\t\t\tif (!sv_client->drop)\n\t\t\t{\n\t\t\t\tsaved_state = sv_client->state;\n\t\t\t\tsv_client->state = cs_free;\n\t\t\t\tSV_BroadcastPrintf (PRINT_HIGH,\n\t\t\t\t                    \"%s was kicked for topcolor spam\\n\", sv_client->name);\n\t\t\t\tsv_client->state = saved_state;\n\t\t\t\tSV_ClientPrintf (sv_client, PRINT_HIGH,\n\t\t\t\t                 \"You were kicked from the game for topcolor spamming\\n\");\n\t\t\t\tSV_LogPlayer (sv_client, \"topcolor spam\", 1);\n\t\t\t\tsv_client->drop = true;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n\t//<-\n\n\tProcessUserInfoChange (sv_client, Cmd_Argv (1), oldval);\n\tPR_UserInfoChanged(1);\n}\n\nvoid ProcessUserInfoChange (client_t* sv_client, const char* key, const char* old_value)\n{\n\tint i;\n\n\t// process any changed values\n\tSV_ExtractFromUserinfo (sv_client, !strcmp(key, \"name\"));\n\n\tif (mod_UserInfo_Changed)\n\t{\n\t\tpr_global_struct->time = sv.time;\n\t\tpr_global_struct->self = EDICT_TO_PROG(sv_client->edict);\n\t\tPR_SetTmpString(&G_INT(OFS_PARM0), key);\n\t\tPR_SetTmpString(&G_INT(OFS_PARM1), old_value);\n\t\tPR_SetTmpString(&G_INT(OFS_PARM2), Info_Get(&sv_client->_userinfo_ctx_, key));\n\t\tPR_ExecuteProgram (mod_UserInfo_Changed);\n\t}\n\n\tfor (i = 0; shortinfotbl[i] != NULL; i++)\n\t{\n\t\tif (!strcmp(key, shortinfotbl[i]))\n\t\t{\n\t\t\tchar *nuw = Info_Get(&sv_client->_userinfo_ctx_, key);\n\n\t\t\tInfo_SetStar (&sv_client->_userinfoshort_ctx_, key, nuw);\n\n\t\t\ti = sv_client - svs.clients;\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, svc_setinfo);\n\t\t\tMSG_WriteByte (&sv.reliable_datagram, i);\n\t\t\tMSG_WriteString (&sv.reliable_datagram, key);\n\t\t\tMSG_WriteString (&sv.reliable_datagram, nuw);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n==================\nSV_ShowServerinfo_f\n\nDumps the serverinfo info string\n==================\n*/\nstatic void Cmd_ShowServerinfo_f (void)\n{\n\tInfo_Print (svs.info);\n}\n\nstatic void Cmd_NoSnap_f(void)\n{\n\tif (*sv_client->uploadfn)\n\t{\n\t\t*sv_client->uploadfn = 0;\n\t\tSV_BroadcastPrintf (PRINT_HIGH, \"%s refused remote screenshot\\n\", sv_client->name);\n\t}\n}\n\n/*\n==============\nCmd_MinPing_f\n==============\n*/\nstatic void Cmd_MinPing_f (void)\n{\n\tfloat minping;\n\tswitch (Cmd_Argc())\n\t{\n\tcase 2:\n\t\tif (GameStarted())\n\t\t\tCon_Printf(\"Can't change sv_minping: demo recording or match in progress.\\n\");\n\t\telse if (!(int)sv_enable_cmd_minping.value)\n\t\t\tCon_Printf(\"Can't change sv_minping: sv_enable_cmd_minping == 0.\\n\");\n\t\telse\n\t\t{\n\t\t\tminping = Q_atof(Cmd_Argv(1));\n\t\t\tif (minping < 0 || minping > 300)\n\t\t\t\tCon_Printf(\"Value must be >= 0 and <= 300.\\n\");\n\t\t\telse\n\t\t\t\tCvar_SetValue (&sv_minping, (int)minping);\n\t\t}\n\tcase 1:\n\t\tCon_Printf(\"sv_minping = %s\\n\", sv_minping.string);\n\t\tbreak;\n\tdefault:\n\t\tCon_Printf(\"usage: minping [<value>]\\n<value> = '' show current sv_minping value\\n\");\n\t}\n}\n\n/*\n==============\nCmd_AirStep_f\n==============\n*/\nstatic void Cmd_AirStep_f (void)\n{\n\tint val;\n\tunsigned char red_airstep[64] = \"pm_airstep\";\n\n\tif (sv_client->spectator) {\n\t\tCon_Printf(\"Spectators can't change pm_airstep\\n\");\n\t\treturn;\n\t}\n\n\tswitch (Cmd_Argc())\n\t{\n\tcase 2:\n\t\tif (GameStarted())\n\t\t\tCon_Printf(\"Can't change pm_airstep: demo recording in progress or serverinfo key status is not 'Standby'.\\n\");\n\t\telse\n\t\t{\n\t\t\tval = Q_atoi(Cmd_Argv(1));\n\t\t\tif (val != 0 && val != 1)\n\t\t\t\tCon_Printf(\"Value must be 0 or 1.\\n\");\n\t\t\telse {\n\t\t\t\tfloat old = pm_airstep.value; // remember\n\t\t\t\tCvar_Set (&pm_airstep, val ? \"1\" : \"\"); // set new value\n\n\t\t\t\tif (pm_airstep.value != old) { // seems value was changed\n\t\t\t\t\tSV_BroadcastPrintf (2, \"%s turns %s %s\\n\", \n\t\t\t\t\t\t\t\tsv_client->name, Q_redtext(red_airstep), pm_airstep.value ? \"on\" : \"off\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tcase 1:\n\t\tCon_Printf(\"pm_airstep = %s\\n\", pm_airstep.string);\n\t\tbreak;\n\tdefault:\n\t\tCon_Printf(\"usage: airstep [0 | 1]\\n\");\n\t}\n}\n\n/*\n==============\nCmd_ShowMapsList_f\n==============\n*/\nstatic void Cmd_ShowMapsList_f(void)\n{\n\tchar\t*value, *key;\n\tint\ti, j, len, i_mod_2 = 1;\n\tunsigned char\tztndm3[] = \"ztndm3\";\n\tunsigned char\tlist_of_custom_maps[] = \"list of custom maps\";\n\tunsigned char\tend_of_list[] = \"end of list\";\n\n\tCon_Printf(\"Vote for maps by typing the mapname, for example \\\"%s\\\"\\n\\n---%s\\n\",\n\t           Q_redtext(ztndm3), Q_redtext(list_of_custom_maps));\n\n\tfor (i = LOCALINFO_MAPS_LIST_START; i <= LOCALINFO_MAPS_LIST_END; i++)\n\t{\n\t\tkey = va(\"%d\", i);\n\t\tvalue = Info_Get(&_localinfo_, key);\n\t\tif (*value)\n\t\t{\n\n\t\t\tif (!(i_mod_2 = i % 2))\n\t\t\t{\n\t\t\t\tif ((len = 19 - strlen(value)) < 1)\n\t\t\t\t\tlen = 1;\n\t\t\t\tfor (j = 0; j < len; j++)\n\t\t\t\t\tstrlcat(value, \" \", MAX_KEY_STRING);\n\t\t\t}\n\t\t\tCon_Printf(\"%s%s\", value, i_mod_2 ? \"\\n\" : \"\");\n\t\t}\n\t\telse\n\t\t\tbreak;\n\t}\n\tCon_Printf(\"%s---%s\\n\", i_mod_2 ? \"\" : \"\\n\", Q_redtext(end_of_list));\n}\n\nstatic void SetUpClientEdict (client_t *cl, edict_t *ent)\n{\n\tED_ClearEdict(ent);\n\t// restore client name.\n\tPR_SetEntityString(ent, ent->v->netname, cl->name);\n\t// so spec will have right goalentity - if speccing someone\n\tif(cl->spectator && cl->spec_track > 0)\n\t\tent->v->goalentity = EDICT_TO_PROG(svs.clients[cl->spec_track-1].edict);\n\n\tent->v->colormap = NUM_FOR_EDICT(ent);\n\n\tent->v->team = 0; // FIXME\n\n\tcl->entgravity = 1.0;\n\tif (fofs_gravity)\n\t\tEdictFieldFloat(ent, fofs_gravity) = 1.0;\n\n\tcl->maxspeed = sv_maxspeed.value;\n\tif (fofs_maxspeed)\n\t\tEdictFieldFloat(ent, fofs_maxspeed) = (int)sv_maxspeed.value;\n}\n\nextern cvar_t spectator_password, password;\nextern void MVD_PlayerReset(int player);\nextern void CountPlayersSpecsVips(int *clients_ptr, int *spectators_ptr, int *vips_ptr, client_t **newcl_ptr);\nextern qbool SpectatorCanConnect(int vip, int spass, int spectators, int vips);\nextern qbool PlayerCanConnect(int clients);\n\n/*\n==================\nCmd_Join_f\n\nSet client to player mode without reconnecting\n==================\n*/\nstatic void Cmd_Join_f (void)\n{\n\tint i;\n\tint clients;\n\n\tif (sv_client->state != cs_spawned)\n\t\treturn;\n\n\tif (!sv_client->spectator)\n\t\treturn; // already a player\n\n\tif (!(sv_client->extensions & Z_EXT_JOIN_OBSERVE)) {\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Your QW client doesn't support this command\\n\");\n\t\treturn;\n\t}\n\n\tif (password.string[0] && strcmp (password.string, \"none\")) {\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"This server requires a %s password. Please disconnect, set the password and reconnect as %s.\\n\", \"player\", \"player\");\n\t\treturn;\n\t}\n\n\t// Might have been 'not necessary' for spectator but needed for player\n\tif (SV_LoginBlockJoinRequest(sv_client)) {\n\t\treturn;\n\t}\n\n\tif (SV_ClientConnectedTime(sv_client) < 5) {\n\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"Wait %d seconds\\n\", 5 - (int)SV_ClientConnectedTime(sv_client));\n\t\treturn;\n\t}\n\n\t// count players already on server\n\tCountPlayersSpecsVips(&clients, NULL, NULL, NULL);\n\tif (!PlayerCanConnect(clients)) {\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Can't join, all player slots full\\n\");\n\t\treturn;\n\t}\n\n\tif (!PlayerCheckPing()) {\n\t\treturn;\n\t}\n\n\t// call the prog function for removing a client\n\t// this will set the body to a dead frame, among other things\n\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\tPR_GameClientDisconnect(1);\n\n\t// this is like SVC_DirectConnect.\n\t// turn the spectator into a player\n\tsv_client->old_frags = 0;\n\tSV_SetClientConnectionTime(sv_client);\n\tsv_client->spectator = false;\n\tsv_client->spec_track = 0;\n\tInfo_Remove(&sv_client->_userinfo_ctx_, \"*spectator\");\n\tInfo_Remove(&sv_client->_userinfoshort_ctx_, \"*spectator\");\n\n\t// like Cmd_Spawn_f()\n\tSetUpClientEdict (sv_client, sv_client->edict);\n\n\t// call the progs to get default spawn parms for the new client\n\tPR_GameSetNewParms();\n\n\t// copy spawn parms out of the client_t\n\tfor (i=0 ; i<NUM_SPAWN_PARMS ; i++)\n\t\tsv_client->spawn_parms[i] = (&PR_GLOBAL(parm1))[i];\n\n\t// call the spawn function\n\tpr_global_struct->time = sv.time;\n\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\tG_FLOAT(OFS_PARM0) = (float) sv_client->vip;\n\tPR_GameClientConnect(0);\n\n\t// actually spawn the player\n\tpr_global_struct->time = sv.time;\n\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\tG_FLOAT(OFS_PARM0) = (float) sv_client->vip;\n\tPR_GamePutClientInServer(0);\n\n\t// look in SVC_DirectConnect() for for extended comment whats this for\n\tMVD_PlayerReset(NUM_FOR_EDICT(sv_player) - 1);\n\n\t// send notification to all clients\n\tsv_client->sendinfo = true;\n}\n\n/*\n==================\nCmd_Observe_f\n\nSet client to spectator mode without reconnecting\n==================\n*/\nstatic void Cmd_Observe_f (void)\n{\n\tint i;\n\tint spectators, vips;\n\n\tif (sv_client->state != cs_spawned)\n\t\treturn;\n\tif (sv_client->spectator)\n\t\treturn; // already a spectator\n\n\tif (!(sv_client->extensions & Z_EXT_JOIN_OBSERVE)) {\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Your QW client doesn't support this command\\n\");\n\t\treturn;\n\t}\n\n\tif (spectator_password.string[0] && strcmp (spectator_password.string, \"none\")) {\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"This server requires a %s password. Please disconnect, set the password and reconnect as %s.\\n\", \"spectator\", \"spectator\");\n\t\treturn;\n\t}\n\n\tif (SV_ClientConnectedTime(sv_client) < 5)\n\t{\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Wait %d seconds\\n\", 5 - (int)SV_ClientConnectedTime(sv_client));\n\t\treturn;\n\t}\n\n\t// count spectators already on server\n\tCountPlayersSpecsVips(NULL, &spectators, &vips, NULL);\n\n\tif (!SpectatorCanConnect(sv_client->vip, true/*kinda HACK*/, spectators, vips))\n\t{\n\t\tSV_ClientPrintf (sv_client, PRINT_HIGH, \"Can't join, all spectator/vip slots full\\n\");\n\t\treturn;\n\t}\n\n\t// call the prog function for removing a client\n\t// this will set the body to a dead frame, among other things\n\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\tPR_GameClientDisconnect(0);\n\n\t// this is like SVC_DirectConnect.\n\t// turn the player into a spectator\n\tsv_client->old_frags = 0;\n\tSV_SetClientConnectionTime(sv_client);\n\tsv_client->spectator = true;\n\tsv_client->spec_track = 0;\n\tInfo_SetStar (&sv_client->_userinfo_ctx_, \"*spectator\", \"1\");\n\tInfo_SetStar (&sv_client->_userinfoshort_ctx_, \"*spectator\", \"1\");\n\n\t// like Cmd_Spawn_f()\n\tSetUpClientEdict (sv_client, sv_client->edict);\n\n\t// call the progs to get default spawn parms for the new client\n\tPR_GameSetNewParms();\n\n\tSV_SpawnSpectator ();\n\n\t// copy spawn parms out of the client_t\n\tfor (i=0 ; i<NUM_SPAWN_PARMS ; i++)\n\t\tsv_client->spawn_parms[i] = (&PR_GLOBAL(parm1))[i];\n\n\t// call the spawn function\n\tpr_global_struct->time = sv.time;\n\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\tG_FLOAT(OFS_PARM0) = (float) sv_client->vip;\n\tPR_GameClientConnect(1);\n\n\tpr_global_struct->time = sv.time;\n\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\tPR_GamePutClientInServer(1); // let mod know we put spec not player\n\n\t// look in SVC_DirectConnect() for for extended comment whats this for\n\tMVD_PlayerReset(NUM_FOR_EDICT(sv_player) - 1);\n\n\t// send notification to all clients\n\tsv_client->sendinfo = true;\n}\n\n#ifdef FTE_PEXT2_VOICECHAT\n/*\nPrivacy issues:\nBy sending voice chat to a server, you are unsure who might be listening.\nVoice can be recorded to an mvd, potentially including voice.\nSpectators tracking you are able to hear team chat of your team.\nYou're never quite sure if anyone might join the server and your team before you finish saying a sentance.\nYou run the risk of sounds around you being recorded by quake, including but not limited to: TV channels, loved ones, phones, YouTube videos featuring certain moans.\nDefault on non-team games is to broadcast.\n*/\n\n// FTEQW type and naming compatibility\n// it's not really necessary, simple find & replace would do the job too\n\n#define qboolean qbool\n#define host_client sv_client\n#define ival value // for cvars compatibility\n\n#define VOICE_RING_SIZE 512 /*POT*/\nstruct\n{\n\tstruct voice_ring_s\n\t{\n\t\tunsigned int sender;\n\t\tunsigned char receiver[MAX_CLIENTS/8];\n\t\tunsigned char gen;\n\t\tunsigned char seq;\n\t\tunsigned int datalen;\n\t\tunsigned char data[1024];\n\t} ring[VOICE_RING_SIZE];\n\tunsigned int write;\n} voice;\n\nvoid SV_VoiceReadPacket(void)\n{\n\tunsigned int vt = host_client->voice_target;\n\tunsigned int j;\n\tstruct voice_ring_s *ring;\n\tunsigned short bytes;\n\tclient_t *cl;\n\tunsigned char gen = MSG_ReadByte();\n\tunsigned char seq = MSG_ReadByte();\n\t/*read the data from the client*/\n\tbytes = MSG_ReadShort();\n\tring = &voice.ring[voice.write & (VOICE_RING_SIZE-1)];\n\tif (bytes > sizeof(ring->data) || curtime < host_client->lockedtill || !sv_voip.ival) {\n\t\tMSG_ReadSkip(bytes);\n\t\treturn;\n\t}\n\telse {\n\t\tvoice.write++;\n\t\tMSG_ReadData(ring->data, bytes);\n\t}\n\n\tring->datalen = bytes;\n\tring->sender = host_client - svs.clients;\n\tring->gen = gen;\n\tring->seq = seq;\n\n\t/*broadcast it its to their team, and its not teamplay*/\n\tif (vt == VT_TEAM && !teamplay.ival)\n\t\tvt = VT_ALL;\n\n\t/*figure out which team members are meant to receive it*/\n\tfor (j = 0; j < MAX_CLIENTS/8; j++)\n\t\tring->receiver[j] = 0;\n\tfor (j = 0, cl = svs.clients; j < MAX_CLIENTS; j++, cl++)\n\t{\n\t\tif (cl->state != cs_spawned && cl->state != cs_connected)\n\t\t\tcontinue;\n\t\t/*spectators may only talk to spectators*/\n\t\tif (host_client->spectator && !sv_spectalk.ival)\n\t\t\tif (!cl->spectator)\n\t\t\t\tcontinue;\n\n\t\tif (vt == VT_TEAM)\n\t\t{\n\t\t\t// the spectator team\n\t\t\tif (host_client->spectator)\n\t\t\t{\n\t\t\t\tif (!cl->spectator)\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (strcmp(cl->team, host_client->team) || cl->spectator)\n\t\t\t\t\tcontinue;\t// on different teams\n\t\t\t}\n\t\t}\n\t\telse if (vt == VT_NONMUTED)\n\t\t{\n\t\t\tif (host_client->voice_mute[j>>3] & (1<<(j&3)))\n\t\t\t\tcontinue;\n\t\t}\n\t\telse if (vt >= VT_PLAYERSLOT0)\n\t\t{\n\t\t\tif (j != vt - VT_PLAYERSLOT0)\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tring->receiver[j>>3] |= 1<<(j&3);\n\t}\n\n\tif (sv.mvdrecording && sv_voip_record.ival && !(sv_voip_record.ival == 2 && !host_client->spectator))\n\t{\n\t\t// non-team messages should be seen always, even if not tracking any player\n\t\tif (vt == VT_ALL && (!host_client->spectator || sv_spectalk.ival))\n\t\t{\n\t\t\tMVDWrite_Begin (dem_all, 0, ring->datalen+6);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tunsigned int cls;\n\t\t\tcls = ring->receiver[0] |\n\t\t\t\t(ring->receiver[1]<<8) |\n\t\t\t\t(ring->receiver[2]<<16) |\n\t\t\t\t(ring->receiver[3]<<24);\n\n\t\t\tif (!cls) {\n\t\t\t\t// prevent dem_multiple(0) being sent\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMVDWrite_Begin(dem_multiple, cls, ring->datalen + 6);\n\t\t}\n\n\t\tMVD_MSG_WriteByte( svc_fte_voicechat);\n\t\tMVD_MSG_WriteByte( ring->sender);\n\t\tMVD_MSG_WriteByte( ring->gen);\n\t\tMVD_MSG_WriteByte( ring->seq);\n\t\tMVD_MSG_WriteShort( ring->datalen);\n\t\tMVD_SZ_Write(       ring->data, ring->datalen);\n\t}\n}\n\nvoid SV_VoiceInitClient(client_t *client)\n{\n\tclient->voice_target = VT_TEAM;\n\tclient->voice_active = false;\n\tclient->voice_read = voice.write;\n\tmemset(client->voice_mute, 0, sizeof(client->voice_mute));\n}\n\nvoid SV_VoiceSendPacket(client_t *client, sizebuf_t *buf)\n{\n\tunsigned int clno;\n\tqboolean send;\n\tstruct voice_ring_s *ring;\n//\tclient_t *split;\n\n//\tif (client->controller)\n//\t\tclient = client->controller;\n\tclno = client - svs.clients;\n\n\tif (!(client->fteprotocolextensions2 & FTE_PEXT2_VOICECHAT))\n\t\treturn;\n\tif (!client->voice_active || client->num_backbuf)\n\t{\n\t\tclient->voice_read = voice.write;\n\t\treturn;\n\t}\n\n\twhile(client->voice_read < voice.write)\n\t{\n\t\t/*they might be too far behind*/\n\t\tif (client->voice_read+VOICE_RING_SIZE < voice.write)\n\t\t\tclient->voice_read = voice.write - VOICE_RING_SIZE;\n\n\t\tring = &voice.ring[(client->voice_read) & (VOICE_RING_SIZE-1)];\n\n\t\t/*figure out if it was for us*/\n\t\tsend = false;\n\t\tif (ring->receiver[clno>>3] & (1<<(clno&3)))\n\t\t\tsend = true;\n\n\t\t// FIXME: qqshka: well, is it RIGHTWAY at all???\n#if 0  // qqshka: I am turned it off.\n\t\t/*if you're spectating, you can hear whatever your tracked player can hear*/\n\t\tif (client->spectator && client->spec_track)\n\t\t\tif (ring->receiver[(client->spec_track-1)>>3] & (1<<((client->spec_track-1)&3)))\n\t\t\t\tsend = true;\n#endif\n\n\t\tif (client->voice_mute[ring->sender>>3] & (1<<(ring->sender&3)))\n\t\t\tsend = false;\n\n\t\tif (ring->sender == clno && !sv_voip_echo.ival)\n\t\t\tsend = false;\n\n\t\t/*additional ways to block voice*/\n\t\tif (client->download)\n\t\t\tsend = false;\n\n\t\tif (send)\n\t\t{\n\t\t\tif (buf->maxsize - buf->cursize < ring->datalen+5)\n\t\t\t\tbreak;\n\t\t\tMSG_WriteByte(buf, svc_fte_voicechat);\n\t\t\tMSG_WriteByte(buf, ring->sender);\n\t\t\tMSG_WriteByte(buf, ring->gen);\n\t\t\tMSG_WriteByte(buf, ring->seq);\n\t\t\tMSG_WriteShort(buf, ring->datalen);\n\t\t\tSZ_Write(buf, ring->data, ring->datalen);\n\t\t}\n\t\tclient->voice_read++;\n\t}\n}\n\nvoid SV_Voice_Ignore_f(void)\n{\n\tunsigned int other;\n\tint type = 0;\n\n\tif (Cmd_Argc() < 2)\n\t{\n\t\t/*only a name = toggle*/\n\t\ttype = 0;\n\t}\n\telse\n\t{\n\t\t/*mute if 1, unmute if 0*/\n\t\tif (atoi(Cmd_Argv(2)))\n\t\t\ttype = 1;\n\t\telse\n\t\t\ttype = -1;\n\t}\n\tother = atoi(Cmd_Argv(1));\n\tif (other >= MAX_CLIENTS)\n\t\treturn;\n\n\tswitch(type)\n\t{\n\tcase -1:\n\t\thost_client->voice_mute[other>>3] &= ~(1<<(other&3));\n\t\tbreak;\n\tcase 0:\t\n\t\thost_client->voice_mute[other>>3] ^= (1<<(other&3));\n\t\tbreak;\n\tcase 1:\n\t\thost_client->voice_mute[other>>3] |= (1<<(other&3));\n\t}\n}\n\nvoid SV_Voice_Target_f(void)\n{\n\tunsigned int other;\n\tchar *t = Cmd_Argv(1);\n\tif (!strcmp(t, \"team\"))\n\t\thost_client->voice_target = VT_TEAM;\n\telse if (!strcmp(t, \"all\"))\n\t\thost_client->voice_target = VT_ALL;\n\telse if (!strcmp(t, \"nonmuted\"))\n\t\thost_client->voice_target = VT_NONMUTED;\n\telse if (*t >= '0' && *t <= '9')\n\t{\n\t\tother = atoi(t);\n\t\tif (other >= MAX_CLIENTS)\n\t\t\treturn;\n\t\thost_client->voice_target = VT_PLAYERSLOT0 + other;\n\t}\n\telse\n\t{\n\t\t/*don't know who you mean, futureproofing*/\n\t\thost_client->voice_target = VT_TEAM;\n\t}\n}\n\nvoid SV_Voice_MuteAll_f(void)\n{\n\thost_client->voice_active = false;\n}\n\nvoid SV_Voice_UnmuteAll_f(void)\n{\n\thost_client->voice_active = true;\n}\n\n#endif // FTE_PEXT2_VOICECHAT\n\n/*\n * Parse protocol extensions which supported by client.\n * This is workaround for the proxy case, like: qwfwd. We can't use it in case of qizmo thought.\n */\nvoid Cmd_PEXT_f(void)\n{\n\tint idx;\n\tint proto_ver, proto_value;\n\n\tif (!sv_client->process_pext)\n\t\treturn; // sorry, we do not expect it right now.\n\n\tsv_client->process_pext = false;\n\n\tfor ( idx = 1; idx < Cmd_Argc(); )\n\t{\n\t\tproto_ver   = Q_atoi(Cmd_Argv(idx++));\n\t\tproto_value = Q_atoi(Cmd_Argv(idx++));\n\n\t\tswitch( proto_ver )\n\t\t{\n#ifdef PROTOCOL_VERSION_FTE\n\t\tcase PROTOCOL_VERSION_FTE:\n\t\t\t// do not reset it.\n\t\t\tif (!sv_client->fteprotocolextensions)\n\t\t\t{\n\t\t\t\tsv_client->fteprotocolextensions = proto_value & svs.fteprotocolextensions;\n\t\t\t\tif (sv_client->fteprotocolextensions)\n\t\t\t\t\tCon_DPrintf(\"PEXT: Client supports 0x%x fte extensions\\n\", sv_client->fteprotocolextensions);\n\t\t\t}\n\t\t\tbreak;\n#endif // PROTOCOL_VERSION_FTE\n\n#ifdef PROTOCOL_VERSION_FTE2\n\t\tcase PROTOCOL_VERSION_FTE2:\n\t\t\t// do not reset it.\n\t\t\tif (!sv_client->fteprotocolextensions2)\n\t\t\t{\n\t\t\t\tsv_client->fteprotocolextensions2 = proto_value & svs.fteprotocolextensions2;\n\t\t\t\tif (sv_client->fteprotocolextensions2)\n\t\t\t\t\tCon_DPrintf(\"PEXT: Client supports 0x%x fte extensions2\\n\", sv_client->fteprotocolextensions2);\n\t\t\t}\n\t\t\tbreak;\n#endif // PROTOCOL_VERSION_FTE2\n\n#ifdef PROTOCOL_VERSION_MVD1\n\t\tcase PROTOCOL_VERSION_MVD1:\n\t\t\tif (!sv_client->mvdprotocolextensions1)\n\t\t\t{\n\t\t\t\tsv_client->mvdprotocolextensions1 = proto_value & svs.mvdprotocolextension1;\n\t\t\t\tif (sv_client->mvdprotocolextensions1)\n\t\t\t\t\tCon_DPrintf(\"PEXT: Client supports 0x%x mvdsv extensions\\n\", sv_client->mvdprotocolextensions1);\n\t\t\t}\n\t\t\tbreak;\n#endif\n\t\t}\n\t}\n\n\t// we are ready for new command now.\n\tMSG_WriteByte (&sv_client->netchan.message, svc_stufftext);\n\tMSG_WriteString (&sv_client->netchan.message, \"cmd new\\n\");\n}\n\n#if defined(SERVERONLY) && defined(WWW_INTEGRATION)\n// { Central login\nvoid Cmd_Login_f(void)\n{\n\textern cvar_t sv_login;\n\n\tif (sv_client->state != cs_spawned && !(int)sv_login.value) {\n\t\tSV_ClientPrintf2(sv_client, PRINT_HIGH, \"Cannot login during connection\\n\");\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() != 2) {\n\t\tSV_ClientPrintf2(sv_client, PRINT_HIGH, \"Usage: login <username>\\n\");\n\t\treturn;\n\t}\n\n\tif (curtime - sv_client->login_request_time < LOGIN_MIN_RETRY_TIME) {\n\t\tSV_ClientPrintf2(sv_client, PRINT_HIGH, \"Please wait and try again\\n\");\n\t\treturn;\n\t}\n\n\tif (sv_client->logged_in_via_web) {\n\t\tSV_ClientPrintf2(sv_client, PRINT_HIGH, \"You are already logged in as '%s'\\n\", sv_client->login);\n\t\treturn;\n\t}\n\n\tif (sv_client->state != cs_spawned) {\n\t\tSV_ParseWebLogin(sv_client);\n\t}\n\telse {\n\t\tCentral_GenerateChallenge(sv_client, Cmd_Argv(1), false);\n\t}\n}\n\nvoid Cmd_ChallengeResponse_f(void)\n{\n\tif (Cmd_Argc() != 2) {\n\t\tMSG_WriteByte(&sv_client->netchan.message, svc_print);\n\t\tMSG_WriteByte(&sv_client->netchan.message, PRINT_HIGH);\n\t\tMSG_WriteString(&sv_client->netchan.message, \"Usage: challenge-response <response>\\n\");\n\t\treturn;\n\t}\n\n\tif (!sv_client->login_challenge[0]) {\n\t\tMSG_WriteByte(&sv_client->netchan.message, svc_print);\n\t\tMSG_WriteByte(&sv_client->netchan.message, PRINT_HIGH);\n\t\tMSG_WriteString(&sv_client->netchan.message, \"Please wait and try again\\n\");\n\t\treturn;\n\t}\n\n\tCentral_VerifyChallengeResponse(sv_client, sv_client->login_challenge, Cmd_Argv(1));\n}\n\nvoid Cmd_Logout_f(void)\n{\n\textern cvar_t sv_login;\n\n\tif (sv_client->logged_in_via_web) {\n\t\tif (sv_client->login[0]) {\n\t\t\tSV_BroadcastPrintf(PRINT_HIGH, \"%s logged out\\n\", sv_client->name);\n\t\t}\n\n\t\tSV_Logout(sv_client);\n\n\t\t// \n\t\tif (!(int)sv_login.value || ((int)sv_login.value == 1 && sv_client->spectator)) {\n\t\t\tsv_client->logged = -1;\n\t\t}\n\t}\n\n\t// If logins are mandatory then treat as disconnect\n\tif ((int)sv_login.value > 1 || ((int)sv_login.value == 1 && !sv_client->spectator)) {\n\t\tSV_DropClient(sv_client);\n\t}\n}\n// } Central login\n#endif\n\nvoid SV_DemoList_f(void);\nvoid SV_DemoListRegex_f(void);\nvoid SV_MVDInfo_f(void);\nvoid SV_LastScores_f(void);\n\n// { bans\nvoid SV_Cmd_Ban_f(void);\nvoid SV_Cmd_Banip_f(void);\nvoid SV_Cmd_Banremove_f(void);\n// } bans\n\n// { qtv\nvoid Cmd_Qtvusers_f (void);\n// }\n\n// { cheats\nvoid SV_God_f (void);\nvoid SV_Give_f (void);\nvoid SV_Noclip_f (void);\nvoid SV_Fly_f (void);\n// }\n\n#if defined(SERVERONLY) && defined(WWW_INTEGRATION)\n// { central login\nvoid Cmd_Login_f(void);\nvoid Cmd_Logout_f(void);\nvoid Cmd_ChallengeResponse_f(void);\n// }\n#endif\n\ntypedef struct\n{\n\tchar\t*name;\n\tvoid\t(*func) (void);\n\tqbool\toverrideable;\n}\nucmd_t;\n\n\nstatic ucmd_t ucmds[] =\n{\n\t{\"new\", Cmd_New_f, false},\n\t{\"modellist\", Cmd_Modellist_f, false},\n\t{\"soundlist\", Cmd_Soundlist_f, false},\n\t{\"prespawn\", Cmd_PreSpawn_f, false},\n\t{\"spawn\", Cmd_Spawn_f, false},\n\t{\"begin\", Cmd_Begin_f, false},\n\n\t{\"drop\", Cmd_Drop_f, false},\n\t{\"pings\", Cmd_Pings_f, false},\n\n\t// issued by hand at client consoles\n\t{\"rate\", Cmd_Rate_f, true},\n\t{\"kill\", Cmd_Kill_f, true},\n\t{\"pause\", Cmd_Pause_f, true},\n\n\t{\"say\", Cmd_Say_f, true},\n\t{\"say_team\", Cmd_Say_Team_f, true},\n\n\t{\"setinfo\", Cmd_SetInfo_f, false},\n\n\t{\"serverinfo\", Cmd_ShowServerinfo_f, false},\n\n\t{\"download\", Cmd_Download_f, false},\n\t{\"nextdl\", Cmd_NextDownload_f, false},\n\t{\"dl\", Cmd_DemoDownload_f, false},\n\n\t{\"ptrack\", Cmd_PTrack_f, false}, //ZOID - used with autocam\n\n\t//bliP: file upload ->\n\t{\"techlogin\", Cmd_TechLogin_f, false},\n\t{\"upload\", Cmd_Upload_f, false},\n\t//<-\n\n\t{\"snap\", Cmd_NoSnap_f, false},\n\t{\"stopdownload\", Cmd_StopDownload_f, false},\n\t{\"stopdl\", Cmd_StopDownload_f, false},\n\t{\"dlist\", SV_DemoList_f, false},\n\t{\"dlistr\", SV_DemoListRegex_f, false},\n\t{\"dlistregex\", SV_DemoListRegex_f, false},\n\t{\"demolist\", SV_DemoList_f, false},\n\t{\"demolistr\", SV_DemoListRegex_f, false},\n\t{\"demolistregex\", SV_DemoListRegex_f, false},\n\t{\"demoinfo\", SV_MVDInfo_f, false},\n\t{\"lastscores\", SV_LastScores_f, false},\n\t{\"minping\", Cmd_MinPing_f, true},\n\t{\"airstep\", Cmd_AirStep_f, true},\n\t{\"maps\", Cmd_ShowMapsList_f, true},\n\t{\"ban\", SV_Cmd_Ban_f, true}, // internal server ban support\n\t{\"banip\", SV_Cmd_Banip_f, true}, // internal server ban support\n\t{\"banrem\", SV_Cmd_Banremove_f, true}, // internal server ban support\n\n\t{\"join\", Cmd_Join_f, true},\n\t{\"observe\", Cmd_Observe_f, true},\n\n\t{\"qtvusers\", Cmd_Qtvusers_f, true},\n\n\t// cheat commands\n\t{\"god\", SV_God_f, true},\n\t{\"give\", SV_Give_f, true},\n\t{\"noclip\", SV_Noclip_f, true},\n\t{\"fly\", SV_Fly_f, true},\n\n#ifdef FTE_PEXT2_VOICECHAT\n\t{\"voicetarg\", SV_Voice_Target_f, false},\n\t{\"vignore\", SV_Voice_Ignore_f, false},\t/*ignore/mute specific player*/\n\t{\"muteall\", SV_Voice_MuteAll_f, false},\t/*disables*/\n\t{\"unmuteall\", SV_Voice_UnmuteAll_f, false}, /*reenables*/\n#endif\n\n\t{\"pext\", Cmd_PEXT_f, false}, // user reply with supported protocol extensions.\n\n#if defined(SERVERONLY) && defined(WWW_INTEGRATION)\n\t{\"login\", Cmd_Login_f, false},\n\t{\"login-response\", Cmd_ChallengeResponse_f, false},\n\t{\"logout\", Cmd_Logout_f, false},\n#endif\n\n\t{NULL, NULL}\n};\n\nstatic qbool SV_ExecutePRCommand (void)\n{\n\tpr_global_struct->time = sv.time;\n\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\treturn PR_ClientCmd();\n}\n\n/*\n==================\nSV_ExecuteUserCommand\n==================\n*/\nstatic void SV_ExecuteUserCommand (char *s)\n{\n\tucmd_t *u;\n\n\tCmd_TokenizeString (s);\n\tsv_player = sv_client->edict;\n\n\tSV_BeginRedirect (RD_CLIENT);\n\n\tfor (u=ucmds ; u->name ; u++) {\n\t\tif (!strcmp (Cmd_Argv(0), u->name) ) {\n\t\t\tif (!u->overrideable) {\n\t\t\t\tu->func();\n\t\t\t\tgoto out;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (SV_ExecutePRCommand())\n\t\tgoto out;\n\n\tif (u->name)\n\t\tu->func();\t\n\telse\n\t\tCon_Printf(\"Bad user command: %s\\n\", Cmd_Argv(0));\n\nout:\n\tSV_EndRedirect ();\n}\n\n/*\n===========================================================================\n\nUSER CMD EXECUTION\n\n===========================================================================\n*/\n\n/*\n====================\nAddLinksToPmove\n\n====================\n*/\nstatic void AddLinksToPmove ( areanode_t *node )\n{\n\tlink_t\t\t*l, *next;\n\tedict_t\t\t*check;\n\tint \t\tpl;\n\tint \t\ti;\n\tphysent_t\t*pe;\n\tvec3_t\t\tpmove_mins, pmove_maxs;\n\n\tfor (i=0 ; i<3 ; i++)\n\t{\n\t\tpmove_mins[i] = pmove.origin[i] - 256;\n\t\tpmove_maxs[i] = pmove.origin[i] + 256;\n\t}\n\n\tpl = EDICT_TO_PROG(sv_player);\n\n\t// touch linked edicts\n\tfor (l = node->solid_edicts.next ; l != &node->solid_edicts ; l = next)\n\t{\n\t\tnext = l->next;\n\t\tcheck = EDICT_FROM_AREA(l);\n\n\t\tif (check->v->owner == pl)\n\t\t\tcontinue;\t\t// player's own missile\n\t\tif (check->v->solid == SOLID_BSP\n\t\t\t\t|| check->v->solid == SOLID_BBOX\n\t\t\t\t|| check->v->solid == SOLID_SLIDEBOX)\n\t\t{\n\t\t\tif (check == sv_player)\n\t\t\t\tcontinue;\n\n\t\t\tfor (i=0 ; i<3 ; i++)\n\t\t\t\tif (check->v->absmin[i] > pmove_maxs[i]\n\t\t\t\t|| check->v->absmax[i] < pmove_mins[i])\n\t\t\t\t\tbreak;\n\t\t\tif (i != 3)\n\t\t\t\tcontinue;\n\t\t\tif (pmove.numphysent == MAX_PHYSENTS)\n\t\t\t\treturn;\n\t\t\tpe = &pmove.physents[pmove.numphysent];\n\t\t\tpmove.numphysent++;\n\n\t\t\tVectorCopy (check->v->origin, pe->origin);\n\t\t\tpe->info = NUM_FOR_EDICT(check);\n\t\t\tif (check->v->solid == SOLID_BSP) {\n\t\t\t\tif ((unsigned)check->v->modelindex >= MAX_MODELS)\n\t\t\t\t\tSV_Error (\"AddLinksToPmove: check->v->modelindex >= MAX_MODELS\");\n\t\t\t\tpe->model = sv.models[(int)(check->v->modelindex)];\n\t\t\t\tif (!pe->model)\n\t\t\t\t\tSV_Error (\"SOLID_BSP with a non-bsp model\");\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpe->model = NULL;\n\t\t\t\tVectorCopy (check->v->mins, pe->mins);\n\t\t\t\tVectorCopy (check->v->maxs, pe->maxs);\n\t\t\t}\n\t\t}\n\t}\n\n\t// recurse down both sides\n\tif (node->axis == -1)\n\t\treturn;\n\n\tif ( pmove_maxs[node->axis] > node->dist )\n\t\tAddLinksToPmove ( node->children[0] );\n\tif ( pmove_mins[node->axis] < node->dist )\n\t\tAddLinksToPmove ( node->children[1] );\n}\n\nint SV_PMTypeForClient (client_t *cl)\n{\n\tif (cl->edict->v->movetype == MOVETYPE_NOCLIP) {\n\t\tif (cl->extensions & Z_EXT_PM_TYPE_NEW)\n\t\t\treturn PM_SPECTATOR;\n\t\treturn PM_OLD_SPECTATOR;\n\t}\n\n\tif (cl->edict->v->movetype == MOVETYPE_FLY)\n\t\treturn PM_FLY;\n\n\tif (cl->edict->v->movetype == MOVETYPE_NONE)\n\t\treturn PM_NONE;\n\n\tif (cl->edict->v->movetype == MOVETYPE_LOCK)\n\t\treturn PM_LOCK;\n\n\tif (cl->edict->v->health <= 0)\n\t\treturn PM_DEAD;\n\n\treturn PM_NORMAL;\n}\n\n/*\n===========\nSV_PreRunCmd\n===========\nDone before running a player command.  Clears the touch array\n*/\nstatic byte playertouch[(MAX_EDICTS+7)/8];\n\nvoid SV_PreRunCmd(void)\n{\n\tmemset(playertouch, 0, sizeof(playertouch));\n}\n\n/*\n===========\nSV_RunCmd\n===========\n*/\nvoid SV_RunCmd (usercmd_t *ucmd, qbool inside, qbool second_attempt) //bliP: 24/9\n{\n\tint i, n;\n\tvec3_t originalvel, offset;\n\tqbool onground;\n\t//bliP: 24/9 anti speed ->\n\tint tmp_time;\n\tint blocked;\n\n\tif (!inside && (int)sv_speedcheck.value\n#ifdef USE_PR2\n\t\t&& !sv_client->isBot\n#endif\n\t)\n\t{\n\t\t/* AM101 method */\n\t\ttmp_time = Q_rint((realtime - sv_client->last_check) * 1000); // ie. Old 'timepassed'\n\t\tif (tmp_time)\n\t\t{\n\t\t\tif (ucmd->msec > tmp_time)\n\t\t\t{\n\t\t\t\ttmp_time += sv_client->msecs; // use accumulated msecs\n\t\t\t\tif (ucmd->msec > tmp_time)\n\t\t\t\t{ // If still over...\n\t\t\t\t\tucmd->msec = tmp_time;\n\t\t\t\t\tsv_client->msecs = 0;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tsv_client->msecs = tmp_time - ucmd->msec; // readjust to leftovers\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Add up extra msecs\n\t\t\t\tsv_client->msecs += (tmp_time - ucmd->msec);\n\t\t\t}\n\t\t}\n\n\t\tsv_client->last_check = realtime;\n\n\t\t/* Cap it */\n\t\tif (sv_client->msecs > 500)\n\t\t\tsv_client->msecs = 500;\n\t\telse if (sv_client->msecs < 0)\n\t\t\tsv_client->msecs = 0;\n\t}\n\t//<-\n\tcmd = *ucmd;\n\n\t// chop up very long command\n\tif (cmd.msec > 50)\n\t{\n\t\tint oldmsec;\n\t\toldmsec = ucmd->msec;\n\t\tcmd.msec = oldmsec/2;\n\t\tSV_RunCmd (&cmd, true, second_attempt);\n\t\tcmd.msec = oldmsec/2;\n\t\tcmd.impulse = 0;\n\t\tSV_RunCmd (&cmd, true, second_attempt);\n\t\treturn;\n\t}\n\n\t// copy humans' intentions to progs\n\tsv_player->v->button0 = ucmd->buttons & 1;\n\tsv_player->v->button2 = (ucmd->buttons & 2) >> 1;\n\tsv_player->v->button1 = (ucmd->buttons & 4) >> 2;\n\tif (ucmd->impulse)\n\t\tsv_player->v->impulse = ucmd->impulse;\n\tif (fofs_movement) {\n\t\tEdictFieldVector(sv_player, fofs_movement)[0] = ucmd->forwardmove;\n\t\tEdictFieldVector(sv_player, fofs_movement)[1] = ucmd->sidemove;\n\t\tEdictFieldVector(sv_player, fofs_movement)[2] = ucmd->upmove;\n\t}\n\t// bliP: cuff\n\tif (sv_client->cuff_time > curtime)\n\t\tsv_player->v->button0 = sv_player->v->impulse = 0;\n\t//<-\n\n\t// clamp view angles\n\tucmd->angles[PITCH] = bound(sv_minpitch.value, ucmd->angles[PITCH], sv_maxpitch.value);\n\tif (!sv_player->v->fixangle && ! second_attempt)\n\t\tVectorCopy (ucmd->angles, sv_player->v->v_angle);\n\n\t// model angles\n\t// show 1/3 the pitch angle and all the roll angle\n\tif (sv_player->v->health > 0)\n\t{\n\t\tif (!sv_player->v->fixangle)\n\t\t{\n\t\t\tsv_player->v->angles[PITCH] = -sv_player->v->v_angle[PITCH]/3;\n\t\t\tsv_player->v->angles[YAW] = sv_player->v->v_angle[YAW];\n\t\t}\n\t\tsv_player->v->angles[ROLL] = 0;\n\t}\n\n\tsv_frametime = ucmd->msec * 0.001;\n\tif (sv_frametime > 0.1)\n\t\tsv_frametime = 0.1;\n\n\t// Don't run think function twice...\n\tif (!sv_client->spectator && !second_attempt)\n\t{\n\t\tvec3_t\toldvelocity;\n\t\tfloat\told_teleport_time;\n\n\t\tVectorCopy (sv_player->v->velocity, originalvel);\n\t\tonground = (int) sv_player->v->flags & FL_ONGROUND;\n\n\t\tVectorCopy (sv_player->v->velocity, oldvelocity);\n\t\told_teleport_time = sv_player->v->teleport_time;\n\n\t\tPR_GLOBAL(frametime) = sv_frametime;\n\t\tpr_global_struct->time = sv.time;\n\t\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\t\tPR_GameClientPreThink(0);\n\n\t\tif (pr_nqprogs)\n\t\t{\n\t\t\tsv_player->v->teleport_time = old_teleport_time;\n\t\t\tVectorCopy (oldvelocity, sv_player->v->velocity);\n\t\t}\n\n\t\tif ( onground && originalvel[2] < 0 && sv_player->v->velocity[2] == 0 &&\n\t\t     originalvel[0] == sv_player->v->velocity[0] &&\n\t\t     originalvel[1] == sv_player->v->velocity[1] )\n\t\t{\n\t\t\t// don't let KTeams mess with physics\n\t\t\tsv_player->v->velocity[2] = originalvel[2];\n\t\t}\n\n\t\tSV_RunThink (sv_player);\n\t}\n\n\t// copy player state to pmove\n\tVectorSubtract (sv_player->v->mins, player_mins, offset);\n\tVectorAdd (sv_player->v->origin, offset, pmove.origin);\n\tVectorCopy (sv_player->v->velocity, pmove.velocity);\n\tVectorCopy (sv_player->v->v_angle, pmove.angles);\n\tpmove.waterjumptime = sv_player->v->teleport_time;\n\tpmove.cmd = *ucmd;\n\tpmove.pm_type = SV_PMTypeForClient (sv_client);\n\tpmove.onground = ((int)sv_player->v->flags & FL_ONGROUND) != 0;\n\tpmove.jump_held = sv_client->jump_held;\n\tpmove.jump_msec = 0;\n\t\n\t// let KTeams \"broken ankle\" code work\n\tif (\n#if 0\nFIXME\n\tPR_GetEdictFieldValue(sv_player, \"brokenankle\")\n\t&&\n#endif\n\t(pmove.velocity[2] == -270) && (pmove.cmd.buttons & BUTTON_JUMP))\n\t\tpmove.jump_held = true;\n\n\t// build physent list\n\tpmove.numphysent = 1;\n\tpmove.physents[0].model = sv.worldmodel;\n\tAddLinksToPmove ( sv_areanodes );\n\n\t// fill in movevars\n\tmovevars.entgravity = sv_client->entgravity;\n\tmovevars.maxspeed = sv_client->maxspeed;\n\tmovevars.bunnyspeedcap = pm_bunnyspeedcap.value;\n\tmovevars.ktjump = pm_ktjump.value;\n\tmovevars.slidefix = ((int)pm_slidefix.value != 0);\n\tmovevars.airstep = ((int)pm_airstep.value != 0);\n\tmovevars.pground = ((int)pm_pground.value != 0);\n\tmovevars.rampjump = ((int)pm_rampjump.value != 0);\n\n\t// do the move\n\tblocked = PM_PlayerMove ();\n\n#ifdef USE_PR2\n\t// This is a temporary hack for Frogbots, who adjust after bumping into things\n\t// Better would be to provide a way to simulate a move command, but at least this doesn't require API change\n\tif (blocked && !second_attempt && sv_client->isBot && sv_player->v->blocked)\n\t{\n\t\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\n\t\t// Don't store in the bot's entity as we will run this again\n\t\tVectorSubtract (pmove.origin, offset, pr_global_struct->trace_endpos);\n\t\tVectorCopy (pmove.velocity, pr_global_struct->trace_plane_normal);\n\t\tif (pmove.onground)\n\t\t{\n\t\t\tpr_global_struct->trace_allsolid = (int) sv_player->v->flags | FL_ONGROUND;\n\t\t\tpr_global_struct->trace_ent = EDICT_TO_PROG(EDICT_NUM(pmove.physents[pmove.groundent].info));\n\t\t} else {\n\t\t\tpr_global_struct->trace_allsolid = (int) sv_player->v->flags & ~FL_ONGROUND;\n\t\t}\n\n\t\t// Give the mod a chance to replace the command\n\t\tPR_EdictBlocked (sv_player->v->blocked);\n\n\t\t// Run the command again\n\t\tSV_RunCmd (ucmd, false, true);\n\t\treturn;\n\t}\n#endif\n\n\t// get player state back out of pmove\n\tsv_client->jump_held = pmove.jump_held;\n\tsv_player->v->teleport_time = pmove.waterjumptime;\n\tif (pr_nqprogs)\n\t\tsv_player->v->flags = ((int)sv_player->v->flags & ~FL_WATERJUMP) | (pmove.waterjumptime ? FL_WATERJUMP : 0);\n\tsv_player->v->waterlevel = pmove.waterlevel;\n\tsv_player->v->watertype = pmove.watertype;\n\tif (pmove.onground)\n\t{\n\t\tsv_player->v->flags = (int) sv_player->v->flags | FL_ONGROUND;\n\t\tsv_player->v->groundentity = EDICT_TO_PROG(EDICT_NUM(pmove.physents[pmove.groundent].info));\n\t} else {\n\t\tsv_player->v->flags = (int) sv_player->v->flags & ~FL_ONGROUND;\n\t}\n\n\tVectorSubtract (pmove.origin, offset, sv_player->v->origin);\n\tVectorCopy (pmove.velocity, sv_player->v->velocity);\n\n\tVectorCopy (pmove.angles, sv_player->v->v_angle);\n\n\tif (sv_player->v->solid != SOLID_NOT)\n\t{\n\t\t// link into place and touch triggers\n\t\tSV_LinkEdict (sv_player, true);\n\n\t\t// touch other objects\n\t\tfor (i=0 ; i<pmove.numtouch ; i++)\n\t\t{\n\t\t\tedict_t *ent;\n\t\t\tn = pmove.physents[pmove.touchindex[i]].info;\n\t\t\tent = EDICT_NUM(n);\n\t\t\tif (!ent->v->touch || (playertouch[n/8]&(1<<(n%8))))\n\t\t\t\tcontinue;\n\t\t\tpr_global_struct->self = EDICT_TO_PROG(ent);\n\t\t\tpr_global_struct->other = EDICT_TO_PROG(sv_player);\n\t\t\tPR_EdictTouch (ent->v->touch);\n\t\t\tplayertouch[n/8] |= 1 << (n%8);\n\t\t}\n\t}\n}\n\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\ntypedef struct ssw_info_s {\n\tint impulse_set;\n\tint hide_weapon;\n\tint best_weapon;\n\tqbool hiding;\n\tqbool firing;\n} ssw_info_t;\n\n// Ranks best weapon for player\nvoid SV_ServerSideWeaponRank(client_t* client, int* best_weapon, int* best_impulse, int* hide_weapon, int* hide_impulse)\n{\n\tentvars_t* ent = client->edict->v;\n\tint i;\n\tint items = (int)ent->items;\n\tint weapon = (int)ent->weapon;\n\tint shells = (int)ent->ammo_shells;\n\tint nails = (int)ent->ammo_nails;\n\tint rockets = (int)ent->ammo_rockets;\n\tint cells = (int)ent->ammo_cells;\n\n\tif (client->weaponswitch_hide == 2 && shells > 0) {\n\t\t*hide_weapon = IT_SHOTGUN;\n\t\t*hide_impulse = 2;\n\t}\n\telse if (client->weaponswitch_hide == 1) {\n\t\t*hide_weapon = IT_AXE;\n\t\t*hide_impulse = 1;\n\t}\n\telse {\n\t\t*hide_weapon = 0;\n\t\t*hide_impulse = 0;\n\t}\n\n\t// Default to staying on the current weapon, regardless of ammo\n\t*best_weapon = weapon;\n\t*best_impulse = 0;\n\n\tfor (i = 0; i < sizeof(client->weaponswitch_priority) / sizeof(client->weaponswitch_priority[0]); ++i) {\n\t\tswitch (client->weaponswitch_priority[i]) {\n\t\t\tcase 0:\n\t\t\t\t// end of list\n\t\t\t\treturn;\n\t\t\tcase 1:\n\t\t\t\tif (items & IT_AXE) {\n\t\t\t\t\t*best_weapon = IT_AXE;\n\t\t\t\t\t*best_impulse = 1;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tif ((items & IT_SHOTGUN) && shells > 0) {\n\t\t\t\t\t*best_weapon = IT_SHOTGUN;\n\t\t\t\t\t*best_impulse = 2;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\tif ((items & IT_SUPER_SHOTGUN) && shells > 1) {\n\t\t\t\t\t*best_weapon = IT_SUPER_SHOTGUN;\n\t\t\t\t\t*best_impulse = 3;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 4:\n\t\t\t\tif ((items & IT_NAILGUN) && nails > 0) {\n\t\t\t\t\t*best_weapon = IT_NAILGUN;\n\t\t\t\t\t*best_impulse = 4;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 5:\n\t\t\t\tif ((items & IT_SUPER_NAILGUN) && nails > 1) {\n\t\t\t\t\t*best_weapon = IT_SUPER_NAILGUN;\n\t\t\t\t\t*best_impulse = 5;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 6:\n\t\t\t\tif ((items & IT_GRENADE_LAUNCHER) && rockets > 0) {\n\t\t\t\t\t*best_weapon = IT_GRENADE_LAUNCHER;\n\t\t\t\t\t*best_impulse = 6;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 7:\n\t\t\t\tif ((items & IT_ROCKET_LAUNCHER) && rockets > 0) {\n\t\t\t\t\t*best_weapon = IT_ROCKET_LAUNCHER;\n\t\t\t\t\t*best_impulse = 7;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 8:\n\t\t\t\tif ((items & IT_LIGHTNING) && cells > 0) {\n\t\t\t\t\t*best_weapon = IT_LIGHTNING;\n\t\t\t\t\t*best_impulse = 8;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn;\n}\n\nstatic void SV_NotifyUserOfBestWeapon(client_t* sv_client, int new_impulse)\n{\n\tchar stuffcmd_buffer[64];\n\n\tstrlcpy(stuffcmd_buffer, va(\"//mvdsv_ssw %d %d\\n\", sv_client->weaponswitch_sequence_set, new_impulse), sizeof(stuffcmd_buffer));\n\n\tClientReliableWrite_Begin(sv_client, svc_stufftext, 2 + strlen(stuffcmd_buffer));\n\tClientReliableWrite_String(sv_client, stuffcmd_buffer);\n}\n\nstatic void SV_ExecuteServerSideWeaponForgetOrder(client_t* sv_client, int best_impulse, int hide_impulse)\n{\n\tchar new_wrank[16] = { 0 };\n\n\tSV_DebugServerSideWeaponScript(sv_client, best_impulse);\n\tSV_NotifyUserOfBestWeapon(sv_client, best_impulse);\n\n\t// Over-write the list sent with the result\n\t{\n\t\tif (Info_Get(&sv_client->_userinfo_ctx_, \"dev\")[0] == '1') {\n\t\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"Best: %d, forgetorder enabled\\n\", best_impulse);\n\t\t}\n\t}\n\tsv_client->weaponswitch_priority[0] = best_impulse;\n\tsv_client->weaponswitch_priority[1] = (hide_impulse == 1 || best_impulse == 2 ? 1 : 2);\n\tsv_client->weaponswitch_priority[2] = (sv_client->weaponswitch_priority[1] != 1 ? 1 : 0);\n\tsv_client->weaponswitch_priority[3] = 0;\n\n\tnew_wrank[0] = '0' + best_impulse;\n\tif (hide_impulse) {\n\t\tnew_wrank[1] = '0' + hide_impulse;\n\t\tif (hide_impulse == 2) {\n\t\t\tnew_wrank[2] = '1';\n\t\t}\n\t}\n\telse {\n\t\tnew_wrank[1] = '2';\n\t\tnew_wrank[2] = '1';\n\t}\n\n\tSV_UserSetWeaponRank(sv_client, new_wrank);\n}\n\nstatic void SV_ExecuteServerSideWeaponHideOnDeath(client_t* sv_client, int hide_impulse, int hide_weapon)\n{\n\tchar new_wrank[16] = { 0 };\n\n\tif (sv_client->edict->v->health > 0.0f || !sv_client->weaponswitch_hide_on_death) {\n\t\treturn;\n\t}\n\n\t// might not have general hiding enabled...\n\thide_impulse = (hide_impulse == 1 ? 1 : 2);\n\thide_weapon = (hide_impulse == 1 ? IT_AXE : IT_SHOTGUN);\n\tnew_wrank[0] = (hide_impulse == 1 ? '1' : '2');\n\tnew_wrank[1] = (hide_impulse == 1 ? '0' : '1');\n\tsv_client->weaponswitch_priority[0] = (hide_impulse == 1 ? 1 : 2);\n\tsv_client->weaponswitch_priority[1] = (hide_impulse == 1 ? 1 : 2);\n\tsv_client->weaponswitch_priority[2] = 0;\n\n\tSV_DebugServerSideWeaponScript(sv_client, hide_impulse);\n\tSV_NotifyUserOfBestWeapon(sv_client, hide_impulse);\n\tSV_UserSetWeaponRank(sv_client, new_wrank);\n\n\tif (Info_Get(&sv_client->_userinfo_ctx_, \"dev\")[0] == '1' && sv_client->edict->v->weapon != hide_weapon) {\n\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"Hiding on death: %d\\n\", hide_impulse);\n\t}\n}\n\nstatic void SV_ServerSideWeaponLogic_PrePostThink(client_t* sv_client, ssw_info_t* ssw)\n{\n\tentvars_t* ent = sv_client->edict->v;\n\tqbool dev_trace = (Info_Get(&sv_client->_userinfo_ctx_, \"dev\")[0] == '1');\n\n\tssw->firing = (ent->button0 != 0);\n\n\tif ((sv_client->mvdprotocolextensions1 & MVD_PEXT1_SERVERSIDEWEAPON) && sv_client->weaponswitch_enabled) {\n\t\tint best_impulse, hide_impulse;\n\t\tqbool switch_to_best_weapon = false;\n\t\tint mode = sv_client->weaponswitch_mode;\n\n\t\t// modes: 0 immediately choose best, 1 preselect (wait until fire), 2 immediate if firing\n\t\t// mode 2 = \"preselect(1) when not holding +attack, else immediate(0)\"\n\t\tif (mode == 2) {\n\t\t\tmode = (ssw->firing ? 0 : 1);\n\t\t}\n\t\tswitch_to_best_weapon = sv_client->weaponswitch_pending && (mode == 0 || ssw->firing) && (ent->health >= 1.0f);\n\n\t\tSV_ServerSideWeaponRank(sv_client, &ssw->best_weapon, &best_impulse, &ssw->hide_weapon, &hide_impulse);\n\n\t\tssw->hiding = (sv_client->weaponswitch_wasfiring && !ssw->firing && hide_impulse);\n\t\tsv_client->weaponswitch_wasfiring |= ssw->firing;\n\n\t\tif (switch_to_best_weapon && sv_client->weaponswitch_forgetorder) {\n\t\t\tSV_ExecuteServerSideWeaponForgetOrder(sv_client, best_impulse, hide_impulse);\n\t\t}\n\t\tSV_ExecuteServerSideWeaponHideOnDeath(sv_client, hide_impulse, ssw->hide_weapon);\n\n\t\tif (!ent->impulse) {\n\t\t\tif (switch_to_best_weapon) {\n\t\t\t\tif (best_impulse && ent->weapon != ssw->best_weapon) {\n\t\t\t\t\tSV_DebugServerSideWeaponScript(sv_client, best_impulse);\n\n\t\t\t\t\tif (dev_trace) {\n\t\t\t\t\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"Switching to best weapon: %d\\n\", best_impulse);\n\t\t\t\t\t}\n\n\t\t\t\t\tent->impulse = best_impulse;\n\t\t\t\t\tssw->impulse_set = 2;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tsv_client->weaponswitch_pending = false;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (ssw->hiding) {\n\t\t\t\tif (ent->weapon != ssw->hide_weapon) {\n\t\t\t\t\tif (dev_trace) {\n\t\t\t\t\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"Hiding: %d\\n\", hide_impulse);\n\t\t\t\t\t}\n\t\t\t\t\tent->impulse = hide_impulse;\n\t\t\t\t\tssw->impulse_set = 1;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tsv_client->weaponswitch_pending = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (dev_trace) {\n\t\t\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"Non-wp impulse: %f\\n\", ent->impulse);\n\t\t\t}\n\t\t}\n\t\tsv_client->weaponswitch_pending &= (ent->health >= 1.0f);\n\t}\n}\n\nstatic void SV_ServerSideWeaponLogic_PostPostThink(client_t* sv_client, ssw_info_t* ssw)\n{\n\tentvars_t* ent = sv_client->edict->v;\n\tqbool dev_trace = (Info_Get(&sv_client->_userinfo_ctx_, \"dev\")[0] == '1');\n\n\tif (ssw->impulse_set) {\n\t\tqbool hide_failed = (ssw->impulse_set == 1 && ent->weapon != ssw->hide_weapon);\n\t\tqbool pickbest_failed = (ssw->impulse_set == 2 && ent->weapon != ssw->best_weapon);\n\t\tqbool failure = (hide_failed || pickbest_failed);\n\n\t\tent->impulse = 0;\n\n\t\tif (dev_trace) {\n\t\t\tif (failure) {\n\t\t\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"... %s failed, will try again\\n\", ssw->impulse_set == 1 ? \"hide\" : \"pickbest\");\n\t\t\t}\n\t\t\telse {\n\t\t\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"... %s successful, stopping\\n\", ssw->impulse_set == 1 ? \"hide\" : \"pickbest\");\n\t\t\t}\n\t\t}\n\n\t\tsv_client->weaponswitch_pending &= failure;\n\t}\n\tif (ssw->hiding && ent->weapon == ssw->hide_weapon) {\n\t\tif (dev_trace) {\n\t\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"Hide successful\\n\");\n\t\t}\n\t\tsv_client->weaponswitch_wasfiring = false;\n\t}\n\telse if (!(ssw->hiding || ssw->firing)) {\n\t\tif (sv_client->weaponswitch_wasfiring && dev_trace) {\n\t\t\tSV_ClientPrintf(sv_client, PRINT_HIGH, \"No longer firing...\\n\");\n\t\t}\n\t\tsv_client->weaponswitch_wasfiring = false;\n\t}\n}\n#endif\n\n/*\n===========\nSV_PostRunCmd\n===========\nDone after running a player command.\n*/\nvoid SV_PostRunCmd(void)\n{\n\tvec3_t originalvel;\n\tqbool onground;\n\t// run post-think\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\tssw_info_t ssw = { 0 };\n#endif\n\n\tif (!sv_client->spectator)\n\t{\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\t\tSV_ServerSideWeaponLogic_PrePostThink(sv_client, &ssw);\n#endif\n\n\t\tonground = (int) sv_player->v->flags & FL_ONGROUND;\n\t\tpr_global_struct->time = sv.time;\n\t\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\t\tVectorCopy (sv_player->v->velocity, originalvel);\n\t\tPR_GameClientPostThink(0);\n\n\t\tif ( onground && originalvel[2] < 0 && sv_player->v->velocity[2] == 0\n\t\t&& originalvel[0] == sv_player->v->velocity[0]\n\t\t&& originalvel[1] == sv_player->v->velocity[1] ) {\n\t\t\t// don't let KTeams mess with physics\n\t\t\tsv_player->v->velocity[2] = originalvel[2];\n\t\t}\n\n\t\tif (pr_nqprogs)\n\t\t\tVectorCopy (originalvel, sv_player->v->velocity);\n\n\t\tif (pr_nqprogs)\n\t\t\tSV_RunNQNewmis ();\n\t\telse\n\t\t\tSV_RunNewmis ();\n\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\t\tSV_ServerSideWeaponLogic_PostPostThink(sv_client, &ssw);\n#endif\n\t}\n\telse\n\t{\n\t\tpr_global_struct->time = sv.time;\n\t\tpr_global_struct->self = EDICT_TO_PROG(sv_player);\n\t\tPR_GameClientPostThink(1);\n\t}\n}\n\n/*\nSV_UserSetWeaponRank\nSets wrank userinfo for mods to pick best weapon based on user's preferences\n*/\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\nstatic void SV_UserSetWeaponRank(client_t* cl, const char* new_wrank)\n{\n\tchar old_wrank[128] = { 0 };\n\tstrlcpy(old_wrank, Info_Get(&cl->_userinfo_ctx_, \"w_rank\"), sizeof(old_wrank));\n\tif (strcmp(old_wrank, new_wrank)) {\n\t\tInfo_Set(&cl->_userinfo_ctx_, \"w_rank\", new_wrank);\n\t\tProcessUserInfoChange(cl, \"w_rank\", old_wrank);\n\n\t\tif (Info_Get(&cl->_userinfo_ctx_, \"dev\")[0] == '1') {\n\t\t\tSV_ClientPrintf(cl, PRINT_HIGH, \"Setting new w_rank: %s\\n\", new_wrank);\n\t\t}\n\t}\n}\n#endif\n\n// SV_RotateCmd\n// Rotates client command so a high-ping player can better control direction as they exit teleporters on high-ping\nvoid SV_RotateCmd(client_t* cl, usercmd_t* cmd_)\n{\n\tif (cl->lastteleport_teleport) {\n\t\tstatic vec3_t up = { 0, 0, 1 };\n\t\tvec3_t direction = { cmd_->sidemove, cmd_->forwardmove, 0 };\n\t\tvec3_t result;\n\n\t\tRotatePointAroundVector(result, up, direction, cl->lastteleport_teleportyaw);\n\n\t\tcmd_->sidemove = result[0];\n\t\tcmd_->forwardmove = result[1];\n\t}\n\telse {\n\t\tcmd_->angles[YAW] = (cl->edict)->v->angles[YAW];\n\t}\n}\n\n/*\n===================\nSV_ExecuteClientMove\n\nRun one or more client move commands (more than one if some\npackets were dropped)\n===================\n*/\nstatic void SV_ExecuteClientMove(client_t* cl, usercmd_t oldest, usercmd_t oldcmd, usercmd_t newcmd)\n{\n\tint net_drop;\n\tint playernum = cl - svs.clients;\n\n\tif (sv.paused) {\n\t\treturn;\n\t}\n\n\tSV_PreRunCmd();\n\n\tnet_drop = cl->netchan.dropped;\n\tif (net_drop < 20) {\n\t\twhile (net_drop > 2) {\n\t\t\tSV_DebugClientCommand(playernum, &cl->lastcmd, net_drop);\n\t\t\tSV_RunCmd(&cl->lastcmd, false, false);\n\t\t\tnet_drop--;\n\t\t}\n\t}\n\tif (net_drop > 1) {\n\t\tSV_DebugClientCommand(playernum, &oldest, 2);\n\t\tSV_RunCmd(&oldest, false, false);\n\t}\n\tif (net_drop > 0) {\n\t\tSV_DebugClientCommand(playernum, &oldcmd, 1);\n\t\tSV_RunCmd(&oldcmd, false, false);\n\t}\n\tSV_DebugClientCommand(playernum, &newcmd, 0);\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\t{\n\t\t// This is necessary to interrupt LG/SNG where the firing takes place inside animation frames\n\t\tif (sv_client->weaponswitch_enabled && sv_client->weaponswitch_pending && !sv_client->edict->v->impulse) {\n\t\t\tsv_client->edict->v->impulse = 255;\n\t\t\tSV_RunCmd(&newcmd, false, false);\n\t\t\tsv_client->edict->v->impulse = 0;\n\t\t}\n\t\telse {\n\t\t\tSV_RunCmd(&newcmd, false, false);\n\t\t}\n\t}\n#else\n\tSV_RunCmd(&newcmd, false, false);\n#endif\n\n\tSV_PostRunCmd();\n}\n\n#ifdef MVD_PEXT1_DEBUG_ANTILAG\n/*\nSV_DebugWriteServerAntilagPositions\n\nWrites the position of clients, as rewound by antilag\n*/\nstatic void SV_DebugWriteServerAntilagPositions(client_t* cl, int present)\n{\n\tmvdhidden_block_header_t header;\n\tmvdhidden_antilag_position_header_t antilag_header;\n\tint i;\n\tfloat target_time = cl->laggedents_time;\n\n\theader.type_id = mvdhidden_antilag_position;\n\theader.length = sizeof_mvdhidden_antilag_position_header_t + present * sizeof_mvdhidden_antilag_position_t;\n\n\tantilag_header.incoming_seq = LittleLong(cl->netchan.incoming_sequence);\n\tantilag_header.playernum = cl - svs.clients;\n\tantilag_header.players = present;\n\tantilag_header.server_time = LittleFloat(sv.time);\n\tantilag_header.target_time = LittleFloat(target_time);\n\n\tif (MVDWrite_HiddenBlockBegin(sizeof_mvdhidden_block_header_t_range0 + header.length)) {\n\t\theader.length = LittleLong(header.length);\n\t\tMVD_SZ_Write(&header.length, sizeof(header.length));\n\t\tMVD_SZ_Write(&header.type_id, sizeof(header.type_id));\n\t\tMVD_SZ_Write(&antilag_header.playernum, sizeof(antilag_header.playernum));\n\t\tMVD_SZ_Write(&antilag_header.players, sizeof(antilag_header.players));\n\t\tMVD_SZ_Write(&antilag_header.incoming_seq, sizeof(antilag_header.incoming_seq));\n\t\tMVD_SZ_Write(&antilag_header.server_time, sizeof(antilag_header.server_time));\n\t\tMVD_SZ_Write(&antilag_header.target_time, sizeof(antilag_header.target_time));\n\t\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\t\tif (cl->laggedents[i].present) {\n\t\t\t\tmvdhidden_antilag_position_t pos = { 0 };\n\t\t\t\tint j;\n\n\t\t\t\tpos.playernum = i;\n#ifdef MVD_PEXT1_DEBUG\n\t\t\t\tif (debug_info.antilag_clients[i].present) {\n\t\t\t\t\tpos.playernum |= MVD_PEXT1_ANTILAG_CLIENTPOS;\n\t\t\t\t\tpos.msec = debug_info.antilag_clients[i].msec;\n\t\t\t\t\tpos.predmodel = debug_info.antilag_clients[i].model;\n\t\t\t\t\tVectorCopy(debug_info.antilag_clients[i].pos, pos.clientpos);\n\t\t\t\t}\n#endif\n\t\t\t\tMVD_SZ_Write(&pos.playernum, sizeof(pos.playernum));\n\t\t\t\tfor (j = 0; j < 3; ++j) {\n\t\t\t\t\tpos.pos[j] = LittleFloat(cl->laggedents[i].laggedpos[j]);\n\t\t\t\t\tMVD_SZ_Write(&pos.pos[j], sizeof(pos.pos[j]));\n\t\t\t\t}\n\t\t\t\tMVD_SZ_Write(&pos.msec, sizeof(pos.msec));\n\t\t\t\tMVD_SZ_Write(&pos.predmodel, sizeof(pos.predmodel));\n\t\t\t\tfor (j = 0; j < 3; ++j) {\n\t\t\t\t\tpos.clientpos[j] = LittleFloat(pos.clientpos[j]);\n\t\t\t\t\tMVD_SZ_Write(&pos.clientpos[j], sizeof(pos.clientpos[j]));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n#endif // MVD_PEXT1_DEBUG_ANTILAG\n\n#ifdef MVD_PEXT1_DEBUG_WEAPON\nstatic void SV_DebugWriteWeaponScript(byte playernum, qbool server_side, int items, byte shells, byte nails, byte rockets, byte cells, byte choice, const char* weaponlist)\n{\n\tif (sv_debug_weapons.value >= 1) {\n\t\tmvdhidden_block_header_t header;\n\n\t\t// Write out immediately\n\t\theader.type_id = (server_side ? mvdhidden_usercmd_weapons_ss : mvdhidden_usercmd_weapons);\n\t\theader.length = LittleLong(10 + strlen(weaponlist) + 1);\n\n\t\tif (MVDWrite_HiddenBlockBegin(sizeof_mvdhidden_block_header_t_range0 + header.length)) {\n\t\t\tMVD_SZ_Write(&header.length, sizeof(header.length));\n\t\t\tMVD_SZ_Write(&header.type_id, sizeof(header.type_id));\n\t\t\tMVD_SZ_Write(&playernum, sizeof(playernum));\n\t\t\tMVD_SZ_Write(&items, sizeof(items));\n\t\t\tMVD_SZ_Write(&shells, sizeof(shells));\n\t\t\tMVD_SZ_Write(&nails, sizeof(nails));\n\t\t\tMVD_SZ_Write(&rockets, sizeof(rockets));\n\t\t\tMVD_SZ_Write(&cells, sizeof(cells));\n\t\t\tMVD_SZ_Write(&choice, sizeof(choice));\n\t\t\tMVD_SZ_Write(weaponlist, strlen(weaponlist) + 1);\n\t\t}\n\t}\n}\n\nstatic void SV_DebugClientSideWeaponScript(client_t* cl)\n{\n\tbyte playernum = cl - svs.clients;\n\tint items = LittleLong(MSG_ReadLong());\n\tbyte shells = MSG_ReadByte();\n\tbyte nails = MSG_ReadByte();\n\tbyte rockets = MSG_ReadByte();\n\tbyte cells = MSG_ReadByte();\n\tbyte choice = MSG_ReadByte();\n\tconst char* weaponlist = MSG_ReadString();\n\n\tSV_DebugWriteWeaponScript(playernum, false, items, shells, nails, rockets, cells, choice, weaponlist);\n}\n\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\nstatic void SV_DebugServerSideWeaponInstruction(client_t* cl)\n{\n\tif (sv_debug_weapons.value >= 1) {\n\t\tmvdhidden_block_header_t header;\n\n\t\t// Write out immediately\n\t\theader.type_id = mvdhidden_usercmd_weapon_instruction;\n\t\theader.length = sizeof_mvdhidden_usercmd_weapon_instruction;\n\n\t\tif (MVDWrite_HiddenBlockBegin(sizeof_mvdhidden_block_header_t_range0 + header.length)) {\n\t\t\tbyte playernum = cl - svs.clients;\n\t\t\tbyte flags = 0;\n\t\t\tint sequence_set = cl->weaponswitch_sequence_set;\n\t\t\tint mode = cl->weaponswitch_mode;\n\t\t\tbyte weaponlist[10] = { 0 };\n\n\t\t\tflags |= (cl->weaponswitch_pending ? MVDHIDDEN_SSWEAPON_PENDING : 0);\n\t\t\tflags |= (cl->weaponswitch_hide == 1 ? MVDHIDDEN_SSWEAPON_HIDE_AXE : (cl->weaponswitch_hide == 2 ? MVDHIDDEN_SSWEAPON_HIDE_SG : 0));\n\t\t\tflags |= (cl->weaponswitch_hide_on_death ? MVDHIDDEN_SSWEAPON_HIDEONDEATH : 0);\n\t\t\tflags |= (cl->weaponswitch_wasfiring ? MVDHIDDEN_SSWEAPON_WASFIRING : 0);\n\t\t\tflags |= (cl->weaponswitch_enabled ? MVDHIDDEN_SSWEAPON_ENABLED : 0);\n\t\t\tflags |= (cl->weaponswitch_forgetorder ? MVDHIDDEN_SSWEAPON_FORGETORDER : 0);\n\n\t\t\tmemcpy(weaponlist, cl->weaponswitch_priority, min(10, sizeof(cl->weaponswitch_priority)));\n\n\t\t\tMVD_SZ_Write(&header.length, sizeof(header.length));\n\t\t\tMVD_SZ_Write(&header.type_id, sizeof(header.type_id));\n\t\t\tMVD_SZ_Write(&playernum, sizeof(playernum));\n\t\t\tMVD_SZ_Write(&flags, sizeof(flags));\n\t\t\tMVD_SZ_Write(&sequence_set, sizeof(sequence_set));\n\t\t\tMVD_SZ_Write(&mode, sizeof(mode));\n\t\t\tMVD_SZ_Write(weaponlist, sizeof(weaponlist));\n\t\t}\n\t}\n}\n\nstatic void SV_DebugServerSideWeaponScript(client_t* cl, int best_impulse)\n{\n\tif (sv_debug_weapons.value >= 1) {\n\t\tchar old_wrank[128] = { 0 };\n\t\tchar encoded[128] = { 0 };\n\t\tchar* w;\n\t\tchar* o;\n\t\tentvars_t* ent = cl->edict->v;\n\n\t\tstrlcpy(old_wrank, Info_Get(&cl->_userinfo_ctx_, \"w_rank\"), sizeof(old_wrank));\n\n\t\tw = old_wrank;\n\t\to = encoded;\n\t\twhile (*w) {\n\t\t\t*o = (*w - '0');\n\t\t\t++w;\n\t\t\t++o;\n\t\t}\n\n\t\tSV_DebugWriteWeaponScript(cl - svs.clients, true, ent->items, ent->ammo_shells, ent->ammo_nails, ent->ammo_rockets, ent->ammo_cells, best_impulse, encoded);\n\t}\n}\n#endif\n\n#endif\n\n/*\n===================\nSV_ExecuteClientMessage\n \nThe current net_message is parsed for the given client\n===================\n*/\nvoid SV_ExecuteClientMessage (client_t *cl)\n{\n\tint             c, i;\n\tchar            *s;\n\tusercmd_t       oldest, oldcmd, newcmd;\n\tclient_frame_t  *frame;\n\tvec3_t          o;\n\tqbool           move_issued = false; //only allow one move command\n\tint             checksumIndex;\n\tbyte            checksum, calculatedChecksum;\n\tint             seq_hash;\n\n#ifdef MVD_PEXT1_DEBUG\n\tint             antilag_players_present = 0;\n#endif\n\n\tif (!Netchan_Process(&cl->netchan))\n\t\treturn;\n//\tif (cl->state == cs_zombie) // FIXME\n//\t\treturn; // FIXME\n\n\t// this is a valid, sequenced packet, so process it\n\tsvs.stats.packets++;\n\tcl->send_message = true; // reply at end of frame\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tcl->download_chunks_perframe = 0;\n#endif\n\n\t// calc ping time\n\tframe = &cl->frames[cl->netchan.incoming_acknowledged & UPDATE_MASK];\n\tframe->ping_time = curtime - frame->senttime;\n\n\t// update delay based on ping and sv_minping\n\tif (!cl->spectator && !sv.paused)\n\t{\n\t\tif (frame->ping_time * 1000 > sv_minping.value + 1)\n\t\t\tcl->delay -= 0.001;\n\t\telse if (frame->ping_time * 1000 < sv_minping.value)\n\t\t\tcl->delay += 0.001;\n\n\t\tcl->delay = bound(0, cl->delay, 1);\n\t}\n\n\tcl->laggedents_count = 0; // init at least this\n\tcl->laggedents_frac = 1; // sv_antilag_frac.value;\n\n\tif (sv_antilag.value)\n\t{\n//#pragma msg(\"FIXME: make antilag optionally support non-player ents too\")\n\n#define MAX_PREDICTION 0.02\n#define MAX_EXTRAPOLATE 0.02\n\n\t\tdouble target_time, max_physfps = sv_maxfps.value;\n\n\t\tif (max_physfps < 20 || max_physfps > 1000)\n\t\t\tmax_physfps = 77.0;\n\n\t\tif (sv_antilag_no_pred.value) {\n\t\t\ttarget_time = frame->sv_time;\n\t\t} else {\n\t\t\t// try to figure out what time client is currently predicting, basically this is just 6.5ms with 13ms ping and 13.5ms with higher\n\t\t\t// might be off with different max_physfps values\n\t\t\ttarget_time = min(frame->sv_time + (frame->ping_time < MAX_PREDICTION ? 1/max_physfps : MAX_PREDICTION), sv.time);\n\t\t}\n\n\t\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t\t{\n\t\t\tclient_t *target_cl = &svs.clients[i];\n\t\t\tantilag_position_t *base, *interpolate = NULL;\n\t\t\tdouble factor;\n\t\t\tint j;\n\n\t\t\t// don't hit dead players\n\t\t\tif (target_cl->state != cs_spawned || target_cl->antilag_position_next == 0 || (target_cl->spectator == 0 && target_cl->edict->v->health <= 0)) {\n\t\t\t\tcl->laggedents[i].present = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcl->laggedents[i].present = true;\n\t\t\t++antilag_players_present;\n\n\t\t\t// target player's movement commands are late, extrapolate his position based on velocity\n\t\t\tif (target_time > target_cl->localtime) {\n\t\t\t\tVectorMA(target_cl->edict->v->origin, min(target_time - target_cl->localtime, MAX_EXTRAPOLATE), target_cl->edict->v->velocity, cl->laggedents[i].laggedpos);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// we have only one antilagged position, use that\n\t\t\tif (target_cl->antilag_position_next == 1) {\n\t\t\t\tVectorCopy(target_cl->antilag_positions[0].origin, cl->laggedents[i].laggedpos);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// find the position before target time (base) and the one after that (interpolate)\n\t\t\tfor (j = target_cl->antilag_position_next - 2; j > target_cl->antilag_position_next - 1 - MAX_ANTILAG_POSITIONS && j >= 0; j--) {\n\t\t\t\tif (target_cl->antilag_positions[j % MAX_ANTILAG_POSITIONS].localtime < target_time)\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tbase = &target_cl->antilag_positions[j % MAX_ANTILAG_POSITIONS];\n\t\t\tinterpolate = &target_cl->antilag_positions[(j + 1) % MAX_ANTILAG_POSITIONS];\n\n\t\t\t// we have two positions, just interpolate between them\n\t\t\tfactor = (target_time - base->localtime) / (interpolate->localtime - base->localtime);\n\t\t\tVectorInterpolate(base->origin, factor, interpolate->origin, cl->laggedents[i].laggedpos);\n\t\t}\n\n\t\tcl->laggedents_count = MAX_CLIENTS; // FIXME: well, FTE do it a bit different way...\n\t\tcl->laggedents_time = target_time;\n\t}\n\n\t// make sure the reply sequence number matches the incoming\n\t// sequence number\n\tif (cl->netchan.incoming_sequence >= cl->netchan.outgoing_sequence)\n\t\tcl->netchan.outgoing_sequence = cl->netchan.incoming_sequence;\n\telse\n\t\tcl->send_message = false;\t// don't reply, sequences have slipped\n\n\t// save time for ping calculations\n\tcl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].senttime = curtime;\n\tcl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].ping_time = -1;\n\n\tsv_client = cl;\n\tsv_player = sv_client->edict;\n\n\tseq_hash = cl->netchan.incoming_sequence;\n\n\t// mark time so clients will know how much to predict\n\t// other players\n\tcl->localtime = sv.time;\n\tcl->delta_sequence = -1;\t// no delta unless requested\n#ifdef MVD_PEXT1_DEBUG\n\tmemset(&debug_info, 0, sizeof(debug_info));\n#endif\n\twhile (1)\n\t{\n\t\tif (msg_badread)\n\t\t{\n\t\t\tCon_Printf (\"SV_ReadClientMessage: badread\\n\");\n\t\t\tSV_DropClient (cl);\n\t\t\treturn;\n\t\t}\n\n\t\tc = MSG_ReadByte ();\n\t\tif (c == -1)\n\t\t\tbreak;\n\n\t\tswitch (c)\n\t\t{\n\t\tdefault:\n\t\t\tCon_Printf (\"SV_ReadClientMessage: unknown command char\\n\");\n\t\t\tSV_DropClient (cl);\n\t\t\treturn;\n\n\t\tcase clc_nop:\n\t\t\tbreak;\n\n\t\tcase clc_delta:\n\t\t\tcl->delta_sequence = MSG_ReadByte ();\n\t\t\tbreak;\n\n#ifdef MVD_PEXT1_DEBUG\n\t\tcase clc_mvd_debug:\n\t\t{\n\t\t\tbyte type = MSG_ReadByte();\n\t\t\tif (type == clc_mvd_debug_type_antilag) {\n\t\t\t\tint players = MSG_ReadByte();\n\n\t\t\t\tfor (i = 0; i < players; ++i) {\n\t\t\t\t\tint num = MSG_ReadByte();\n\t\t\t\t\tint msec = MSG_ReadByte();\n\t\t\t\t\tint model = MSG_ReadByte();\n\t\t\t\t\tfloat x = LittleFloat(MSG_ReadFloat());\n\t\t\t\t\tfloat y = LittleFloat(MSG_ReadFloat());\n\t\t\t\t\tfloat z = LittleFloat(MSG_ReadFloat());\n\n\t\t\t\t\tif (num >= 0 && num < MAX_CLIENTS) {\n\t\t\t\t\t\tdebug_info.antilag_clients[num].present = true;\n\t\t\t\t\t\tdebug_info.antilag_clients[num].model = model;\n\t\t\t\t\t\tdebug_info.antilag_clients[num].msec = msec;\n\t\t\t\t\t\tVectorSet(debug_info.antilag_clients[num].pos, x, y, z);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (type == clc_mvd_debug_type_weapon) {\n\t\t\t\tSV_DebugClientSideWeaponScript(cl);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCon_Printf(\"SV_ReadClientMessage: unknown debug message type %d\\n\", type);\n\t\t\t\tSV_DropClient(cl);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n#endif\n\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\n\t\tcase clc_mvd_weapon:\n\t\t{\n\t\t\tif (!SV_ClientExtensionWeaponSwitch(cl)) {\n\t\t\t\tCon_Printf(\"SV_ClientExtensionWeaponSwitch: corrupt data string\\n\");\n\t\t\t\tSV_DropClient(cl);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n#endif\n\n\t\tcase clc_move:\n\t\t\tif (move_issued)\n\t\t\t\treturn;\t\t// someone is trying to cheat...\n\n\t\t\tmove_issued = true;\n\n\t\t\tchecksumIndex = MSG_GetReadCount();\n\t\t\tchecksum = (byte)MSG_ReadByte ();\n\n\t\t\t// read loss percentage\n\t\t\t//bliP: file percent ->\n\t\t\tcl->lossage = MSG_ReadByte();\n\t\t\tif (cl->file_percent)\n\t\t\t\tcl->lossage = cl->file_percent;\n\n\t\t\t/*if (cl->state < cs_spawned && cl->download != NULL) {\n\t\t\t\tif (cl->downloadsize)\n\t\t\t\t\tcl->lossage = cl->downloadcount*100/cl->downloadsize;\n\t\t\t\telse\n\t\t\t\t\tcl->lossage = 100;\n\t\t\t}*/\n\t\t\t//<-\n\n#ifndef SERVERONLY\n\t\t\tMSG_ReadDeltaUsercmd (&nullcmd, &oldest, PROTOCOL_VERSION);\n\t\t\tMSG_ReadDeltaUsercmd (&oldest, &oldcmd, PROTOCOL_VERSION);\n\t\t\tMSG_ReadDeltaUsercmd (&oldcmd, &newcmd, PROTOCOL_VERSION);\n#else\n\t\t\tMSG_ReadDeltaUsercmd (&nullcmd, &oldest);\n\t\t\tMSG_ReadDeltaUsercmd (&oldest, &oldcmd);\n\t\t\tMSG_ReadDeltaUsercmd (&oldcmd, &newcmd);\n#endif\n\n\t\t\tif ( cl->state != cs_spawned )\n\t\t\t\tbreak;\n\n#ifdef CHAT_ICON_EXPERIMENTAL\n\t\t\ts = Info_Get(&cl->_userinfoshort_ctx_, \"chat\");\n\t\t\tif ( s[0] ) {\n// allow movement while in console\n//\t\t\t\tnewcmd.forwardmove = newcmd.sidemove = newcmd.upmove = 0;\n\t\t\t\tnewcmd.buttons &= BUTTON_JUMP; // only jump button allowed while in console\n\n// somemods uses impulses for commands, so let them use\n//\t\t\t\tnewcmd.impulse = 0;\n\t\t\t}\n#endif\n\n\t\t\t// if the checksum fails, ignore the rest of the packet\n\t\t\tcalculatedChecksum = COM_BlockSequenceCRCByte(\n\t\t\t\tnet_message.data + checksumIndex + 1,\n\t\t\t\tMSG_GetReadCount() - checksumIndex - 1,\n\t\t\t\tseq_hash\n\t\t\t);\n\n\t\t\tif (calculatedChecksum != checksum)\n\t\t\t{\n\t\t\t\tCon_DPrintf (\"Failed command checksum for %s(%d) (%d != %d)\\n\", cl->name, cl->netchan.incoming_sequence, checksum, calculatedChecksum);\n\t\t\t\treturn;\n\t\t\t}\n\n#ifdef MVD_PEXT1_HIGHLAGTELEPORT\n\t\t\tif (cl->mvdprotocolextensions1 & MVD_PEXT1_HIGHLAGTELEPORT) {\n\t\t\t\tif (cl->lastteleport_outgoingseq && cl->netchan.incoming_acknowledged < cl->lastteleport_outgoingseq) {\n\t\t\t\t\tif (cl->netchan.incoming_sequence - 2 > cl->lastteleport_incomingseq) {\n\t\t\t\t\t\tSV_RotateCmd(cl, &oldest);\n\t\t\t\t\t}\n\t\t\t\t\tif (cl->netchan.incoming_sequence - 1 > cl->lastteleport_incomingseq) {\n\t\t\t\t\t\tSV_RotateCmd(cl, &oldcmd);\n\t\t\t\t\t}\n\t\t\t\t\tSV_RotateCmd(cl, &newcmd);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcl->lastteleport_outgoingseq = 0;\n\t\t\t\t}\n\t\t\t}\n#endif\n\n\t\t\tSV_ExecuteClientMove(cl, oldest, oldcmd, newcmd);\n\n\t\t\tcl->lastcmd = newcmd;\n\t\t\tcl->lastcmd.buttons = 0; // avoid multiple fires on lag\n\n\t\t\tif (sv_antilag.value) {\n\t\t\t\tif (cl->antilag_position_next == 0 || cl->antilag_positions[(cl->antilag_position_next - 1) % MAX_ANTILAG_POSITIONS].localtime < cl->localtime) {\n\t\t\t\t\tcl->antilag_positions[cl->antilag_position_next % MAX_ANTILAG_POSITIONS].localtime = cl->localtime;\n\t\t\t\t\tVectorCopy(cl->edict->v->origin, cl->antilag_positions[cl->antilag_position_next % MAX_ANTILAG_POSITIONS].origin);\n\t\t\t\t\tcl->antilag_position_next++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcl->antilag_position_next = 0;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase clc_stringcmd:\n\t\t\ts = MSG_ReadString ();\n\t\t\ts[1023] = 0;\n\t\t\tSV_ExecuteUserCommand (s);\n\t\t\tbreak;\n\n\t\tcase clc_tmove:\n\t\t\to[0] = MSG_ReadCoord();\n\t\t\to[1] = MSG_ReadCoord();\n\t\t\to[2] = MSG_ReadCoord();\n\t\t\t// only allowed by spectators\n\t\t\tif (sv_client->spectator)\n\t\t\t{\n\t\t\t\tVectorCopy(o, sv_player->v->origin);\n\t\t\t\tSV_LinkEdict(sv_player, false);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase clc_upload:\n\t\t\tSV_NextUpload();\n\t\t\tbreak;\n\n#ifdef FTE_PEXT2_VOICECHAT\n\t\tcase clc_voicechat:\n\t\t\tSV_VoiceReadPacket();\n\t\t\tbreak;\n#endif\n\t\t}\n\t}\n\n#ifdef MVD_PEXT1_DEBUG_ANTILAG\n\tif (antilag_players_present && sv_debug_antilag.value) {\n\t\tSV_DebugWriteServerAntilagPositions(cl, antilag_players_present);\n\t}\n#endif\n}\n\n#ifdef MVD_PEXT1_SERVERSIDEWEAPON\nstatic qbool SV_ClientExtensionWeaponSwitch(client_t* cl)\n{\n\tint flags = MSG_ReadByte();\n\tint weap = 0, w = 0;\n\tqbool write = true;\n\tint sequence_set = cl->netchan.incoming_sequence;\n\tint weapon_hide_selection = 0;\n\tbyte new_selections[MAX_WEAPONSWITCH_OPTIONS] = { 0 };\n\tchar new_wrank[MAX_WEAPONSWITCH_OPTIONS + 1] = { 0 };\n\tint wrank_index = 0;\n\n\tif (flags == -1) {\n\t\treturn false;\n\t}\n\n\t// This might be a duplicate that should be ignored\n\tif (flags & clc_mvd_weapon_forget_ranking) {\n\t\tint age = MSG_ReadByte();\n\n\t\tif (age < 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\tsequence_set = cl->netchan.incoming_sequence - age;\n\n\t\twrite = (sequence_set > cl->weaponswitch_sequence_set);\n\t}\n\n\twhile ((weap = MSG_ReadByte()) > 0) {\n\t\tif (flags & clc_mvd_weapon_full_impulse) {\n\t\t\tif (weap && write && w < MAX_WEAPONSWITCH_OPTIONS) {\n\t\t\t\t// only add 1-8 to the wrank weapon preference string...\n\t\t\t\tif (weap >= 1 && weap <= 8 && wrank_index < MAX_WEAPONSWITCH_OPTIONS) {\n\t\t\t\t\tnew_wrank[wrank_index++] = '0' + weap;\n\t\t\t\t}\n\t\t\t\tnew_selections[w++] = weap;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tint weap1 = (weap >> 4) & 15;\n\t\t\tint weap2 = (weap & 15);\n\n\t\t\tweap1 = bound(0, weap1, 9);\n\t\t\tweap2 = bound(0, weap2, 9);\n\n\t\t\tif (weap1 && write && w < MAX_WEAPONSWITCH_OPTIONS) {\n\t\t\t\tnew_wrank[w] = '0' + weap1;\n\t\t\t\tnew_selections[w++] = weap1;\n\t\t\t}\n\t\t\tif (weap1 && weap2 && write && w < MAX_WEAPONSWITCH_OPTIONS) {\n\t\t\t\tnew_wrank[w] = '0' + weap2;\n\t\t\t\tnew_selections[w++] = weap2;\n\t\t\t}\n\n\t\t\tif (!weap1 || !weap2) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tif (flags & clc_mvd_weapon_hide_axe) {\n\t\tweapon_hide_selection = 1;\n\t}\n\telse if (flags & clc_mvd_weapon_hide_sg) {\n\t\tweapon_hide_selection = 2;\n\t}\n\n\tif (weap < 0) {\n\t\treturn false;\n\t}\n\n\tcl->weaponswitch_enabled = (flags & clc_mvd_weapon_switching);\n\tif (write) {\n\t\tcl->weaponswitch_sequence_set = sequence_set;\n\t\tcl->weaponswitch_forgetorder = (flags & clc_mvd_weapon_forget_ranking);\n\t\tcl->weaponswitch_mode = (flags & (clc_mvd_weapon_mode_presel | clc_mvd_weapon_mode_iffiring));\n\t\tcl->weaponswitch_hide = weapon_hide_selection;\n\t\tcl->weaponswitch_hide_on_death = (flags & clc_mvd_weapon_reset_on_death);\n\t\tmemcpy(cl->weaponswitch_priority, new_selections, sizeof(new_selections));\n\t\tSV_DebugServerSideWeaponInstruction(cl);\n\n\t\tif (!cl->weaponswitch_forgetorder) {\n\t\t\tSV_UserSetWeaponRank(cl, new_wrank);\n\t\t}\n\t}\n\tcl->weaponswitch_pending |= write;\n\tcl->weaponswitch_sequence_set = sequence_set;\n\treturn true;\n}\n#endif // MVD_PEXT1_SERVERSIDEWEAPON\n\n/*\n==============\nSV_UserInit\n==============\n*/\nvoid SV_UserInit (void)\n{\n\tCvar_Register (&sv_spectalk);\n\tCvar_Register (&sv_sayteam_to_spec);\n\tCvar_Register (&sv_mapcheck);\n\tCvar_Register (&sv_minping);\n\tCvar_Register (&sv_maxping);\n\tCvar_Register (&sv_enable_cmd_minping);\n\tCvar_Register (&sv_kickuserinfospamtime);\n\tCvar_Register (&sv_kickuserinfospamcount);\n\tCvar_Register (&sv_maxuploadsize);\n#ifdef FTE_PEXT_CHUNKEDDOWNLOADS\n\tCvar_Register (&sv_downloadchunksperframe);\n#endif\n\n#ifdef FTE_PEXT2_VOICECHAT\n\tCvar_Register (&sv_voip);\n\tCvar_Register (&sv_voip_echo);\n\tCvar_Register (&sv_voip_record);\n#endif\n\n#ifdef MVD_PEXT1_DEBUG\n\tCvar_Register(&sv_debug_weapons);\n#endif\n\tCvar_Register(&sv_debug_antilag);\n\tCvar_Register(&sv_debug_usercmd);\n}\n\nstatic void SV_DebugClientCommand(byte playernum, const usercmd_t* usercmd, int dropnum_)\n{\n\tif (playernum >= MAX_CLIENTS) {\n\t\treturn;\n\t}\n\n\tif (sv_debug_usercmd.value >= 1 || svs.clients[playernum].mvd_write_usercmds) {\n\t\tmvdhidden_block_header_t header;\n\t\tbyte dropnum = min(dropnum_, 255);\n\n\t\theader.type_id = mvdhidden_usercmd;\n\t\theader.length = sizeof_mvdhidden_block_header_t_usercmd;\n\n\t\tif (MVDWrite_HiddenBlockBegin(sizeof_mvdhidden_block_header_t_range0 + header.length)) {\n\t\t\tMVD_SZ_Write(&header.length, sizeof(header.length));\n\t\t\tMVD_SZ_Write(&header.type_id, sizeof(header.type_id));\n\n\t\t\tMVD_SZ_Write(&playernum, sizeof(playernum));\n\t\t\tMVD_SZ_Write(&dropnum, sizeof(dropnum));\n\t\t\tMVD_SZ_Write(&usercmd->msec, sizeof(usercmd->msec));\n\t\t\tMVD_SZ_Write(&usercmd->angles[0], sizeof(usercmd->angles[0]));\n\t\t\tMVD_SZ_Write(&usercmd->angles[1], sizeof(usercmd->angles[1]));\n\t\t\tMVD_SZ_Write(&usercmd->angles[2], sizeof(usercmd->angles[2]));\n\t\t\tMVD_SZ_Write(&usercmd->forwardmove, sizeof(usercmd->forwardmove));\n\t\t\tMVD_SZ_Write(&usercmd->sidemove, sizeof(usercmd->sidemove));\n\t\t\tMVD_SZ_Write(&usercmd->upmove, sizeof(usercmd->upmove));\n\t\t\tMVD_SZ_Write(&usercmd->buttons, sizeof(usercmd->buttons));\n\t\t\tMVD_SZ_Write(&usercmd->impulse, sizeof(usercmd->impulse));\n\t\t}\n\t}\n}\n\nstatic void SV_ClientDownloadComplete(client_t* cl)\n{\n\tif (cl->download) {\n\t\tconst char* val;\n\n\t\tVFS_CLOSE(cl->download);\n\t\tcl->download = NULL;\n\t\tcl->file_percent = 0; //bliP: file percent\n\t\t// set normal rate\n\t\tval = Info_Get(&cl->_userinfo_ctx_, \"rate\");\n\t\tcl->netchan.rate = 1.0 / SV_BoundRate(false, Q_atoi(*val ? val : \"99999\"));\n\t\t// set normal duplicate packets\n\t\tcl->netchan.dupe = cl->dupe;\n\t}\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_world.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// world.c -- world query functions\n\n#ifndef CLIENTONLY\n#include \"qwsvdef.h\"\n\n/*\n\nentities never clip against themselves, or their owner\n\nline of sight checks trace->crosscontent, but bullets don't\n\n*/\n\n\ntypedef struct\n{\n\tvec3_t\t\tboxmins, boxmaxs;// enclose the test object along entire move\n\tfloat\t\t*mins, *maxs;\t// size of the moving object\n\tvec3_t\t\tmins2, maxs2;\t// size when clipping against mosnters\n\tfloat\t\t*start, *end;\n\ttrace_t\t\ttrace;\n\tint\t\t\ttype;\n\tedict_t\t\t*passedict;\n} moveclip_t;\n\n\n/*\n================\nSV_HullForEntity\n\nReturns a hull that can be used for testing or clipping an object of mins/maxs\nsize.\nOffset is filled in to contain the adjustment that must be added to the\ntesting object's origin to get a point to use with the returned hull.\n================\n*/\nhull_t *SV_HullForEntity (edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset)\n{\n\tvec3_t size, hullmins, hullmaxs;\n\tcmodel_t *model;\n\thull_t *hull;\n\n\n\t// decide which clipping hull to use, based on the size\n\tif (ent->v->solid == SOLID_BSP)\n\t{\t// explicit hulls in the BSP model\n\t\tif (ent->v->movetype != MOVETYPE_PUSH)\n\t\t\tSV_Error (\"SOLID_BSP without MOVETYPE_PUSH\");\n\n\t\tif ((unsigned)ent->v->modelindex >= MAX_MODELS)\n\t\t\tSV_Error (\"SV_HullForEntity: ent.modelindex >= MAX_MODELS\");\n\n\t\tmodel = sv.models[(int)ent->v->modelindex];\n\t\tif (!model)\n\t\t\tSV_Error (\"SOLID_BSP with a non-bsp model\");\n\n\t\tVectorSubtract (maxs, mins, size);\n\t\tif (size[0] < 3)\n\t\t\thull = &model->hulls[0];\n\t\telse if (size[0] <= 32)\n\t\t\thull = &model->hulls[1];\n\t\telse\n\t\t\thull = &model->hulls[2];\n\n\t\t// calculate an offset value to center the origin\n\t\tVectorSubtract (hull->clip_mins, mins, offset);\n\t\tVectorAdd (offset, ent->v->origin, offset);\n\t}\n\telse\n\t{\t// create a temp hull from bounding box sizes\n\n\t\tVectorSubtract (ent->v->mins, maxs, hullmins);\n\t\tVectorSubtract (ent->v->maxs, mins, hullmaxs);\n\t\thull = CM_HullForBox (hullmins, hullmaxs);\n\t\t\n\t\tVectorCopy (ent->v->origin, offset);\n\t}\n\n\n\treturn hull;\n}\n\n/*\n===============================================================================\n\nENTITY AREA CHECKING\n\n===============================================================================\n*/\n\n// ClearLink is used for new headnodes\nvoid ClearLink (link_t *l)\n{\n\tl->prev = l->next = l;\n}\n\nvoid RemoveLink (link_t *l)\n{\n\tl->next->prev = l->prev;\n\tl->prev->next = l->next;\n}\n\nvoid InsertLinkBefore (link_t *l, link_t *before)\n{\n\tl->next = before;\n\tl->prev = before->prev;\n\tl->prev->next = l;\n\tl->next->prev = l;\n}\nvoid InsertLinkAfter (link_t *l, link_t *after)\n{\n\tl->next = after->next;\n\tl->prev = after;\n\tl->prev->next = l;\n\tl->next->prev = l;\n}\n\n//============================================================================\n\n// well, here should be all things related to world but atm it antilag only\ntypedef struct world_s\n{\n// { sv_antilag related\n\tfloat lagentsfrac;\n\tlaggedentinfo_t *lagents;\n\tunsigned int maxlagents;\n// }\n} world_t;\n\nstatic world_t w;\n\nareanode_t sv_areanodes[AREA_NODES];\nint sv_numareanodes;\n\n/*\n===============\nSV_CreateAreaNode\n===============\n*/\nareanode_t *SV_CreateAreaNode (int depth, vec3_t mins, vec3_t maxs)\n{\n\tareanode_t\t*anode;\n\tvec3_t\t\tsize;\n\tvec3_t\t\tmins1, maxs1, mins2, maxs2;\n\n\tanode = &sv_areanodes[sv_numareanodes];\n\tsv_numareanodes++;\n\n\tClearLink (&anode->trigger_edicts);\n\tClearLink (&anode->solid_edicts);\n\n\tif (depth == AREA_DEPTH)\n\t{\n\t\tanode->axis = -1;\n\t\tanode->children[0] = anode->children[1] = NULL;\n\t\treturn anode;\n\t}\n\n\tVectorSubtract (maxs, mins, size);\n\tif (size[0] > size[1])\n\t\tanode->axis = 0;\n\telse\n\t\tanode->axis = 1;\n\n\tanode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]);\n\tVectorCopy (mins, mins1);\n\tVectorCopy (mins, mins2);\n\tVectorCopy (maxs, maxs1);\n\tVectorCopy (maxs, maxs2);\n\n\tmaxs1[anode->axis] = mins2[anode->axis] = anode->dist;\n\n\tanode->children[0] = SV_CreateAreaNode (depth+1, mins2, maxs2);\n\tanode->children[1] = SV_CreateAreaNode (depth+1, mins1, maxs1);\n\n\treturn anode;\n}\n\n/*\n===============\nSV_ClearWorld\n===============\n*/\nvoid SV_ClearWorld (void)\n{\n\tmemset (sv_areanodes, 0, sizeof(sv_areanodes));\n\tsv_numareanodes = 0;\n\tSV_CreateAreaNode (0, sv.worldmodel->mins, sv.worldmodel->maxs);\n}\n\n\n/*\n===============\nSV_UnlinkEdict\n===============\n*/\nvoid SV_UnlinkEdict (edict_t *ent)\n{\n\tif (!ent->e.area.prev)\n\t\treturn;\t\t// not linked in anywhere\n\tRemoveLink (&ent->e.area);\n\tent->e.area.prev = ent->e.area.next = NULL;\n}\n\n/*\n====================\nSV_AreaEdicts\n====================\n*/\nint SV_AreaEdicts (vec3_t mins, vec3_t maxs, edict_t **edicts, int max_edicts, int area)\n{\n\tlink_t\t\t*l, *start;\n\tedict_t\t\t*touch;\n\tint\t\t\tstackdepth = 0, count = 0;\n\tareanode_t\t*localstack[AREA_NODES], *node = sv_areanodes;\n\n// touch linked edicts\n\twhile (1)\n\t{\n\t\tif (area == AREA_SOLID)\n\t\t\tstart = &node->solid_edicts;\n\t\telse\n\t\t\tstart = &node->trigger_edicts;\n\n\t\tfor (l = start->next ; l != start ; l = l->next)\n\t\t{\n\t\t\ttouch = EDICT_FROM_AREA(l);\n\t\t\tif (touch->v->solid == SOLID_NOT)\n\t\t\t\tcontinue;\n\n\t\t\tif (mins[0] > touch->v->absmax[0]\n\t\t\t\t\t\t || mins[1] > touch->v->absmax[1]\n\t\t\t\t\t\t || mins[2] > touch->v->absmax[2]\n\t\t\t\t\t\t || maxs[0] < touch->v->absmin[0]\n\t\t\t\t\t\t || maxs[1] < touch->v->absmin[1]\n\t\t\t\t\t\t || maxs[2] < touch->v->absmin[2])\n\t\t\t\tcontinue;\n\n\t\t\tif (count == max_edicts)\n\t\t\t\treturn count;\n\t\t\tedicts[count++] = touch;\n\t\t}\n\n\t\tif (node->axis == -1)\n\t\t\tgoto checkstack;\t\t// terminal node\n\n\t\t// recurse down both sides\n\t\tif (maxs[node->axis] > node->dist)\n\t\t{\n\t\t\tif (mins[node->axis] < node->dist)\n\t\t\t{\n\t\t\t\tlocalstack[stackdepth++] = node->children[0];\n\t\t\t\tnode = node->children[1];\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tnode = node->children[0];\n\t\t\tcontinue;\n\t\t}\n\t\tif (mins[node->axis] < node->dist)\n\t\t{\n\t\t\tnode = node->children[1];\n\t\t\tcontinue;\n\t\t}\n\ncheckstack:\n\t\tif (!stackdepth)\n\t\treturn count;\n\t\tnode = localstack[--stackdepth];\n\t}\n\n\treturn count;\n}\n\n/*\n====================\nSV_TouchLinks\n====================\n*/\nstatic void SV_TouchLinks ( edict_t *ent, areanode_t *node )\n{\n\tint\t\t\ti, numtouch;\n\tedict_t\t\t*touchlist[MAX_EDICTS], *touch;\n\tint\t\t\told_self, old_other;\n\n\tnumtouch = SV_AreaEdicts(ent->v->absmin, ent->v->absmax, touchlist, sv.max_edicts, AREA_TRIGGERS);\n\n// touch linked edicts\n\tfor (i = 0; i < numtouch; i++)\n\t{\n\t\ttouch = touchlist[i];\n\t\tif (touch == ent)\n\t\t\tcontinue;\n\t\tif (!touch->v->touch || touch->v->solid != SOLID_TRIGGER)\n\t\t\tcontinue;\n\n\t\told_self = pr_global_struct->self;\n\t\told_other = pr_global_struct->other;\n\n\t\tpr_global_struct->self = EDICT_TO_PROG(touch);\n\t\tpr_global_struct->other = EDICT_TO_PROG(ent);\n\t\tpr_global_struct->time = sv.time;\n\t\tPR_EdictTouch (touch->v->touch);\n\n\t\tpr_global_struct->self = old_self;\n\t\tpr_global_struct->other = old_other;\n\t}\n}\n\n/*\n====================\nSV_LinkToLeafs\n====================\n*/\nvoid SV_LinkToLeafs (edict_t *ent)\n{\n\tint\ti, leafnums[MAX_ENT_LEAFS];\n\n\tent->e.num_leafs = CM_FindTouchedLeafs (ent->v->absmin, ent->v->absmax, leafnums,\n\t\t\t\t\t      MAX_ENT_LEAFS, 0, NULL);\n\tfor (i = 0; i < ent->e.num_leafs; i++) {\n\t\t// ent->e.leafnums are real leafnum minus one (for pvs checks)\n\t\tent->e.leafnums[i] = leafnums[i] - 1;\n\t}\n}\n\n\n/*\n===============\nSV_LinkEdict\n\n===============\n*/\nvoid SV_LinkEdict (edict_t *ent, qbool touch_triggers)\n{\n\tareanode_t\t*node;\n\t\n\tif (ent->e.area.prev)\n\t\tSV_UnlinkEdict (ent);\t// unlink from old position\n\n\tif (ent == sv.edicts)\n\t\treturn;\t\t// don't add the world\n\n\tif (ent->e.free)\n\t\treturn;\n\n// set the abs box\n\tVectorAdd (ent->v->origin, ent->v->mins, ent->v->absmin);\n\tVectorAdd (ent->v->origin, ent->v->maxs, ent->v->absmax);\n\n\t//\n// to make items easier to pick up and allow them to be grabbed off\n// of shelves, the abs sizes are expanded\n\t//\n\tif ((int)ent->v->flags & FL_ITEM)\n\t{\n\t\tent->v->absmin[0] -= 15;\n\t\tent->v->absmin[1] -= 15;\n\t\tent->v->absmax[0] += 15;\n\t\tent->v->absmax[1] += 15;\n\t}\n\telse\n\t{\t// because movement is clipped an epsilon away from an actual edge,\n\t\t// we must fully check even when bounding boxes don't quite touch\n\t\tent->v->absmin[0] -= 1;\n\t\tent->v->absmin[1] -= 1;\n\t\tent->v->absmin[2] -= 1;\n\t\tent->v->absmax[0] += 1;\n\t\tent->v->absmax[1] += 1;\n\t\tent->v->absmax[2] += 1;\n\t}\n\n// link to PVS leafs\n\tif (ent->v->modelindex)\n\t\tSV_LinkToLeafs (ent);\n\telse\n\t\tent->e.num_leafs = 0;\n\n\tif (ent->v->solid == SOLID_NOT)\n\t\treturn;\n\n// find the first node that the ent's box crosses\n\tnode = sv_areanodes;\n\twhile (1)\n\t{\n\t\tif (node->axis == -1)\n\t\t\tbreak;\n\t\tif (ent->v->absmin[node->axis] > node->dist)\n\t\t\tnode = node->children[0];\n\t\telse if (ent->v->absmax[node->axis] < node->dist)\n\t\t\tnode = node->children[1];\n\t\telse\n\t\t\tbreak; // crosses the node\n\t}\n\t\n// link it in\t\n\n\tif (ent->v->solid == SOLID_TRIGGER)\n\t\tInsertLinkBefore (&ent->e.area, &node->trigger_edicts);\n\telse\n\t\tInsertLinkBefore (&ent->e.area, &node->solid_edicts);\n\t\n// if touch_triggers, touch all entities at this node and decend for more\n\tif (touch_triggers)\n\t\tSV_TouchLinks ( ent, sv_areanodes );\n}\n\n\n\n/*\n===============================================================================\n \nPOINT TESTING IN HULLS\n\n===============================================================================\n*/\n\n/*\n==================\nSV_PointContents\n \n==================\n*/\nint SV_PointContents (vec3_t p)\n{\n\treturn CM_HullPointContents (&sv.worldmodel->hulls[0], sv.worldmodel->hulls[0].firstclipnode, p);\n}\n\n//===========================================================================\n\n/*\n============\nSV_TestEntityPosition\n\nA small wrapper around SV_BoxInSolidEntity that never clips against the\nsupplied entity.\n============\n*/\nedict_t\t*SV_TestEntityPosition (edict_t *ent)\n{\n\ttrace_t\ttrace;\n\n\tif (ent->v->solid == SOLID_TRIGGER || ent->v->solid == SOLID_NOT)\n\t\t// only clip against bmodels\n\t\ttrace = SV_Trace (ent->v->origin, ent->v->mins, ent->v->maxs, ent->v->origin, MOVE_NOMONSTERS, ent);\n\telse\n\t\ttrace = SV_Trace(ent->v->origin, ent->v->mins, ent->v->maxs, ent->v->origin, MOVE_NORMAL, ent);\n\t\n\tif (trace.startsolid)\n\t\treturn sv.edicts;\n\t\t\n\treturn NULL;\n}\n\n/*\n==================\nSV_ClipMoveToEntity\n\nHandles selection or creation of a clipping hull, and offseting (and\neventually rotation) of the end points\n==================\n*/\ntrace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t *eorg, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)\n{\n\ttrace_t\t\ttrace;\n\tvec3_t\t\toffset;\n\tvec3_t\t\tstart_l, end_l;\n\thull_t\t\t*hull;\n\n\t// get the clipping hull\n\thull = SV_HullForEntity (ent, mins, maxs, offset);\n\n\t// { well, its hack for sv_antilag\n\tif (eorg)\n\t\tVectorCopy((*eorg), offset);\n\t// }\n\n\tVectorSubtract (start, offset, start_l);\n\tVectorSubtract (end, offset, end_l);\n\n\t// trace a line through the apropriate clipping hull\n\ttrace = CM_HullTrace (hull, start_l, end_l);\n\n\t// fix trace up by the offset\n\tVectorAdd (trace.endpos, offset, trace.endpos);\n\n\t// did we clip the move?\n\tif (trace.fraction < 1 || trace.startsolid)\n\t\ttrace.e.ent = ent;\n\n\treturn trace;\n}\n\n//===========================================================================\n\n/*\n====================\nSV_ClipToLinks\n\nMins and maxs enclose the entire area swept by the move\n====================\n*/\nvoid SV_ClipToLinks ( areanode_t *node, moveclip_t *clip )\n{\n\tint\t\t\ti, numtouch;\n\tedict_t\t\t*touchlist[MAX_EDICTS], *touch;\n\ttrace_t\t\ttrace;\n\n\tnumtouch = SV_AreaEdicts (clip->boxmins, clip->boxmaxs, touchlist, sv.max_edicts, AREA_SOLID);\n\n\t// touch linked edicts\n\tfor (i = 0; i < numtouch; i++)\n\t{\n\t\t// might intersect, so do an exact clip\n\t\tif (clip->trace.allsolid)\n\t\t\treturn; // return!!!\n\n\t\ttouch = touchlist[i];\n\t\tif (touch == clip->passedict)\n\t\t\tcontinue;\n\t\tif (touch->v->solid == SOLID_TRIGGER)\n\t\t\tSV_Error (\"Trigger in clipping list\");\n\n\t\tif ((clip->type & MOVE_NOMONSTERS) && touch->v->solid != SOLID_BSP)\n\t\t\tcontinue;\n\n\t\tif (clip->passedict && clip->passedict->v->size[0] && !touch->v->size[0])\n\t\t\tcontinue;\t// points never interact\n\n\t\tif (clip->type & MOVE_LAGGED)\n\t\t{\n\t\t\t//can't touch lagged ents - we do an explicit test for them later in SV_AntilagClipCheck.\n\t\t\tif (touch->e.entnum - 1 < w.maxlagents)\n\t\t\t\tif (w.lagents[touch->e.entnum - 1].present)\n\t\t\t\t\tcontinue;\n\t\t}\n\n\t\tif (clip->passedict)\n\t\t{\n\t\t\tif (PROG_TO_EDICT(touch->v->owner) == clip->passedict)\n\t\t\t\tcontinue;\t// don't clip against own missiles\n\t\t\tif (PROG_TO_EDICT(clip->passedict->v->owner) == touch)\n\t\t\t\tcontinue;\t// don't clip against owner\n\t\t}\n\n\t\tif ((int)touch->v->flags & FL_MONSTER)\n\t\t\ttrace = SV_ClipMoveToEntity (touch, NULL, clip->start, clip->mins2, clip->maxs2, clip->end);\n\t\telse\n\t\t\ttrace = SV_ClipMoveToEntity (touch, NULL, clip->start, clip->mins, clip->maxs, clip->end);\n\n\t\t// qqshka: I have NO idea why we keep startsolid but let do it.\n\n\t\t// make sure we keep a startsolid from a previous trace\n\t\tclip->trace.startsolid |= trace.startsolid;\n\n\t\tif ( trace.allsolid || trace.fraction < clip->trace.fraction )\n\t\t{\n\t\t\t// set edict\n\t\t\ttrace.e.ent = touch;\n\t\t\t// make sure we keep a startsolid from a previous trace\n\t\t\ttrace.startsolid |= clip->trace.startsolid;\n\t\t\t// bit by bit copy trace struct\n\t\t\tclip->trace = trace;\n\t\t}\n\t}\n}\n\n\n/*\n==================\nSV_MoveBounds\n==================\n*/\nvoid SV_MoveBounds (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, vec3_t boxmins, vec3_t boxmaxs)\n{\n#if 0\n\t// debug to test against everything\n\tboxmins[0] = boxmins[1] = boxmins[2] = -9999;\n\tboxmaxs[0] = boxmaxs[1] = boxmaxs[2] = 9999;\n#else\n\tint\t\ti;\n\n\tfor (i=0 ; i<3 ; i++)\n\t{\n\t\tif (end[i] > start[i])\n\t\t{\n\t\t\tboxmins[i] = start[i] + mins[i] - 1;\n\t\t\tboxmaxs[i] = end[i] + maxs[i] + 1;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tboxmins[i] = end[i] + mins[i] - 1;\n\t\t\tboxmaxs[i] = start[i] + maxs[i] + 1;\n\t\t}\n\t}\n#endif\n}\n\n//=============================================\n\nvoid SV_AntilagReset (edict_t *ent)\n{\n\tif (ent->e.entnum == 0 || ent->e.entnum > MAX_CLIENTS)\n\t\treturn;\n\n\tsvs.clients[ent->e.entnum - 1].antilag_position_next = 0;\n}\n\nvoid SV_AntilagClipSetUp ( areanode_t *node, moveclip_t *clip )\n{\n\tedict_t *passedict = clip->passedict;\n\tint entnum = passedict->e.entnum;\n\n\tclip->type &= ~MOVE_LAGGED;\n\n\tif (entnum && entnum <= MAX_CLIENTS && !svs.clients[entnum - 1].isBot)\n\t{\n\t\tclip->type |= MOVE_LAGGED;\n\t\tw.lagents = svs.clients[entnum - 1].laggedents;\n\t\tw.maxlagents = svs.clients[entnum - 1].laggedents_count;\n\t\tw.lagentsfrac = svs.clients[entnum - 1].laggedents_frac;\n\t}\n\telse if (passedict->v->owner)\n\t{\n\t\tint owner = PROG_TO_EDICT(passedict->v->owner)->e.entnum;\n\n\t\tif (owner && owner <= MAX_CLIENTS && !svs.clients[owner - 1].isBot)\n\t\t{\n\t\t\tclip->type |= MOVE_LAGGED;\n\t\t\tw.lagents = svs.clients[owner - 1].laggedents;\n\t\t\tw.maxlagents = svs.clients[owner - 1].laggedents_count;\n\t\t\tw.lagentsfrac = svs.clients[owner - 1].laggedents_frac;\n\t\t}\n\t}\n}\n\nvoid SV_AntilagClipCheck ( areanode_t *node, moveclip_t *clip )\n{\n\ttrace_t trace;\n\tedict_t *touch;\n\tvec3_t lp;\n\tint i;\n\n\tfor (i = 0; i < w.maxlagents; i++)\n\t{\n\t\tif (clip->trace.allsolid)\n\t\t\treturn; // return!!!\n\n\t\tif (!w.lagents[i].present)\n\t\t\tcontinue;\n\n\t\ttouch = EDICT_NUM(i + 1);\n\t\tif (touch->v->solid == SOLID_NOT)\n\t\t\tcontinue;\n\t\tif (touch == clip->passedict)\n\t\t\tcontinue;\n\t\tif (touch->v->solid == SOLID_TRIGGER)\n\t\t\tSV_Error (\"Trigger (%s) in clipping list\", PR_GetEntityString(touch->v->classname));\n\n\t\tif ((clip->type & MOVE_NOMONSTERS) && touch->v->solid != SOLID_BSP)\n\t\t\tcontinue;\n\n\t\tVectorInterpolate(touch->v->origin, w.lagentsfrac, w.lagents[i].laggedpos, lp);\n\n\t\tif (   clip->boxmins[0] > lp[0]+touch->v->maxs[0]\n\t\t\t|| clip->boxmins[1] > lp[1]+touch->v->maxs[1]\n\t\t\t|| clip->boxmins[2] > lp[2]+touch->v->maxs[2]\n\t\t\t|| clip->boxmaxs[0] < lp[0]+touch->v->mins[0]\n\t\t\t|| clip->boxmaxs[1] < lp[1]+touch->v->mins[1]\n\t\t\t|| clip->boxmaxs[2] < lp[2]+touch->v->mins[2] )\n\t\t\tcontinue;\n\n\t\tif (clip->passedict && clip->passedict->v->size[0] && !touch->v->size[0])\n\t\t\tcontinue;\t// points never interact\n\n\t\tif (clip->passedict)\n\t\t{\n\t\t\tif (PROG_TO_EDICT(touch->v->owner) == clip->passedict)\n\t\t\t\tcontinue;\t// don't clip against own missiles\n\t\t\tif (PROG_TO_EDICT(clip->passedict->v->owner) == touch)\n\t\t\t\tcontinue;\t// don't clip against owner\n\t\t}\n\n\t\tif ((int)touch->v->flags & FL_MONSTER)\n\t\t\ttrace = SV_ClipMoveToEntity (touch, &lp, clip->start, clip->mins2, clip->maxs2, clip->end);\n\t\telse\n\t\t\ttrace = SV_ClipMoveToEntity (touch, &lp, clip->start, clip->mins, clip->maxs, clip->end);\n\n\t\t// qqshka: I have NO idea why we keep startsolid but let do it.\n\n\t\t// make sure we keep a startsolid from a previous trace\n\t\tclip->trace.startsolid |= trace.startsolid;\n\n\t\tif ( trace.allsolid || trace.fraction < clip->trace.fraction )\n\t\t{\n\t\t\t// set edict\n\t\t\ttrace.e.ent = touch;\n\t\t\t// make sure we keep a startsolid from a previous trace\n\t\t\ttrace.startsolid |= clip->trace.startsolid;\n\t\t\t// bit by bit copy trace struct\n\t\t\tclip->trace = trace;\n\t\t}\n\t}\n}\n\n/*\n==================\nSV_Trace\n==================\n*/\ntrace_t SV_Trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int type, edict_t *passedict)\n{\n\tmoveclip_t\tclip;\n\tint\t\t\ti;\n\n\tmemset ( &clip, 0, sizeof ( moveclip_t ) );\n\n\t// clip to world\n\tclip.trace = SV_ClipMoveToEntity ( sv.edicts, NULL, start, mins, maxs, end );\n\n\tclip.start = start;\n\tclip.end = end;\n\tclip.mins = mins;\n\tclip.maxs = maxs;\n\tclip.type = type;\n\tclip.passedict = passedict;\n\n\tif (type & MOVE_MISSILE)\n\t{\n\t\tfor (i=0 ; i<3 ; i++)\n\t\t{\n\t\t\tclip.mins2[i] = -15;\n\t\t\tclip.maxs2[i] = 15;\n\t\t}\n\t}\n\telse\n\t{\n\t\tVectorCopy (mins, clip.mins2);\n\t\tVectorCopy (maxs, clip.maxs2);\n\t}\n\n\t// create the bounding box of the entire move\n\tSV_MoveBounds ( start, clip.mins2, clip.maxs2, end, clip.boxmins, clip.boxmaxs );\n\n\t// set up antilag\n\tif (clip.type & MOVE_LAGGED)\n\t\tSV_AntilagClipSetUp ( sv_areanodes, &clip );\n\n\t// clip to entities\n\tSV_ClipToLinks ( sv_areanodes, &clip );\n\n\t// additional antilag clip check\n\tif (clip.type & MOVE_LAGGED)\n\t\tSV_AntilagClipCheck ( sv_areanodes, &clip );\n\n\treturn clip.trace;\n}\n\n#endif // !CLIENTONLY\n"
  },
  {
    "path": "src/sv_world.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// world.h\n#ifndef __WORLD_H__\n#define __WORLD_H__\n\n#define\tMOVE_NORMAL\t0\n#define\tMOVE_NOMONSTERS\t1\n#define\tMOVE_MISSILE\t2\n// { sv_antilag related\n#define MOVE_LAGGED\t\t64\t//trace touches current last-known-state, instead of actual ents (just affects players for now)\n// }\n\ntypedef struct areanode_s\n{\n\tint\t\taxis;\t\t// -1 = leaf node\n\tfloat\tdist;\n\tstruct areanode_s\t*children[2];\n\tlink_t\ttrigger_edicts;\n\tlink_t\tsolid_edicts;\n} areanode_t;\n\n#define AREA_SOLID\t0\n#define AREA_TRIGGERS\t1\n\n#define\tAREA_DEPTH\t4\n#define\tAREA_NODES\t32\n\nextern\tareanode_t\tsv_areanodes[AREA_NODES];\n\nvoid SV_ClearWorld (void);\n// called after the world model has been loaded, before linking any entities\n\nvoid SV_UnlinkEdict (edict_t *ent);\n// call before removing an entity, and before trying to move one,\n// so it doesn't clip against itself\n// flags ent->v->modified\n\nvoid SV_LinkEdict (edict_t *ent, qbool touch_triggers);\n// Needs to be called any time an entity changes origin, mins, maxs, or solid\n// flags ent->v->modified\n// sets ent->v->absmin and ent->v->absmax\n// if touchtriggers, calls prog functions for the intersected triggers\n\nint SV_PointContents (vec3_t p);\n// returns the CONTENTS_* value from the world at the given point.\n// does not check any entities at all\n\nedict_t\t*SV_TestEntityPosition (edict_t *ent);\n\ntrace_t SV_Trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int type, edict_t *passedict);\n// mins and maxs are relative\n\n// if the entire move stays in a solid volume, trace.allsolid will be set\n\n// if the starting point is in a solid, it will be allowed to move out\n// to an open area\n\n// nomonsters is used for line of sight or edge testing, where mosnters\n// shouldn't be considered solid objects\n\n// passedict is explicitly excluded from clipping checks (normally NULL)\n\nint SV_AreaEdicts (vec3_t mins, vec3_t maxs, edict_t **edicts, int max_edicts, int area);\n\nvoid SV_AntilagReset (edict_t *ent);\n\n#endif /* !__WORLD_H__ */\n"
  },
  {
    "path": "src/sys.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// sys.h -- non-portable functions\n\n#include \"q_platform.h\"\n\n#ifdef _WIN32\n#define Sys_MSleep(x) Sleep(x)\n#else\n#define Sys_MSleep(x) usleep((x) * 1000)\n#endif\n\n#ifndef _WIN32\n#define DWORD unsigned int\n#define WINAPI\n#ifndef MAX_PATH\n\t#define MAX_PATH (1024)\n#endif\n#define _MAX_FNAME 1024\n#define _MAX_EXT 64\n#define _MAX_DIR 1024\n\ntypedef void *DL_t;\n#else\ntypedef HMODULE DL_t;\n#endif\n\nDL_t Sys_DLOpen(const char *path);\nqbool Sys_DLClose(DL_t dl);\nvoid *Sys_DLProc(DL_t dl, const char *name);\n\n#include \"localtime.h\"\n\n#include <SDL.h>\n\n// create detached thread\nint  Sys_CreateDetachedThread(int (*func)(void *), void *data);\n\n// create thread\nSDL_Thread *Sys_CreateThread(int (*func)(void *), void *data);\n\n#define MAX_PATH_LENGTH 1024\n\ntypedef struct sys_dirent_s\n{\n    int directory;\n    int hidden;\n    char fname[MAX_PATH_LENGTH];\n    unsigned int size;\n    SYSTEMTIME time;\n} sys_dirent; \n\n#ifdef _WIN32\ntypedef HANDLE SysDirEnumHandle;\n#else\ntypedef unsigned long SysDirEnumHandle;\n#endif\n\nchar *Sys_fullpath(char *absPath, const char *relPath, int maxLength);\nSysDirEnumHandle  Sys_ReadDirFirst(sys_dirent *);               // 0 if failed\nint            Sys_ReadDirNext(SysDirEnumHandle, sys_dirent *); // 0 if failed (EOF)\nvoid           Sys_ReadDirClose(SysDirEnumHandle);\nint    Sys_chdir (const char *path);\nchar * Sys_getcwd (char *buf, int bufsize);\n\n\n// file IO\nvoid Sys_mkdir (const char *path);\nint Sys_remove (char *path);\nint Sys_EnumerateFiles (char *gpath, char *match, int (*func)(char *, int, void *), void *parm);\n\n// an error will cause the entire program to exit\nvoid Sys_Error (char *error, ...);\n\n// send text to the console\nvoid Sys_Printf (char *fmt, ...);\n\nvoid Sys_Quit (void);\n\ndouble Sys_DoubleTime (void);\n\n\n// Perform Key_Event() callbacks until the input que is empty\nvoid Sys_SendKeyEvents (void);\n// Some events (mouse wheel in particular) we won't get a secondary 'up/stop' event, so\n//   we flag & execute them here, after any commands have been sent to the server\nvoid Sys_SendDeferredKeyEvents(void);\n\nvoid Sys_Init (void);\n\nwchar *Sys_GetClipboardTextW(void);\nvoid Sys_CopyToClipboard(const char *);\n\nvoid Sys_GetFullExePath(char *path, unsigned int path_length, int long_name);\n\n// Inter Process Call functions.\nvoid Sys_InitIPC(void);\nvoid Sys_ReadIPC(void);\nvoid Sys_CloseIPC(void);\nunsigned int Sys_SendIPC(const char *buf);\nvoid Sys_RegisterQWURLProtocol_f(void);\n\n// Semaphore functions\n#ifdef _WIN32\ntypedef HANDLE sem_t;\n#else\n#include <semaphore.h>\n#endif\nint Sys_SemInit(sem_t *sem, int value, int max_value);\nint Sys_SemWait(sem_t *sem);\nint Sys_SemPost(sem_t *sem);\nint Sys_SemDestroy(sem_t *sem);\n\n// Timer Resolution\n// On windows to Sleep(1) really take only 1 ms it is necessary to explicitly request\n// such a high precision, otherwise the thread would sleep for much higher time (materials mention 18 ms or 50 ms)\n// This interface allows to request a temporary increased resolution of the timer device\ntypedef struct timerresolution_session_s {\n\tint set;\n\tunsigned int interval;\n} timerresolution_session_t;\n\n#ifdef _WIN32\n\n// for initialization of the session structure\nvoid Sys_TimerResolution_InitSession(timerresolution_session_t * s);\n\n// request minimum timer resolution for given session\n// call this before a block in which you are using Sleep() with low argument value\nvoid Sys_TimerResolution_RequestMinimum(timerresolution_session_t * s);\n\n// release the requested resolution for this session\n// lets timer device use lower resolution\n// always call this after you don't need precise Sleep anymore!\nvoid Sys_TimerResolution_Clear(timerresolution_session_t * s);\n\n// Cancel deadkey combination for keyboards where console toggle is also deadkey\nvoid Sys_CancelDeadKey (void);\n\nvoid Sys_CheckQWProtocolHandler(void);\n\n#else // NOT _WIN32 below\n\n// not implemented on other platforms\n#define Sys_CheckQWProtocolHandler(x)\n#define Sys_TimerResolution_InitSession(x)\n#define Sys_TimerResolution_RequestMinimum(x)\n#define Sys_TimerResolution_Clear(x)\n\n#endif\n\n// MVDSV compatibility\n\n#define MAX_DIRFILES 4096\n#define MAX_DEMO_NAME 196\n\ntypedef struct\n{\n\tchar\tname[MAX_DEMO_NAME];\n\tint\tsize;\n\tint\ttime;\n\tqbool\tisdir; //bliP: list dir\n} file_t;\n\ntypedef struct\n{\n\tfile_t *files;\n\tint\tsize;\n\tint\tnumfiles;\n\tint\tnumdirs;\n} dir_t;\n\nint\t\tSys_Script (const char *path, const char *args);\n\nint\t\tSys_rmdir (const char *path);\ndir_t\tSys_listdir (const char *path, const char *ext, int sort_type);\n\nint\t\tSys_compare_by_date (const void *a, const void *b);\nint\t\tSys_compare_by_name (const void *a, const void *b);\n#define SORT_NO\t\t\t0\n#define SORT_BY_DATE\t1\n#define SORT_BY_NAME\t2\n\n#define ARRAY_LEN(x)\t\t(sizeof(x) / sizeof(*(x)))\n\n// library loading.\n\ntypedef struct\n{\n\tvoid **funcptr;\n\tchar *name;\n} dllfunction_t;\n\ntypedef void *dllhandle_t;\n\ndllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs);\nvoid Sys_CloseLibrary(dllhandle_t *lib);\nvoid *Sys_GetAddressForName(dllhandle_t *module, const char *exportname);\n\nvoid Sys_CvarInit(void);\n\nconst char* Sys_FontsDirectory(void);\nconst char* Sys_HomeDirectory(void);\n"
  },
  {
    "path": "src/sys_osx.m",
    "content": "#import <AppKit/Appkit.h>\n#import <Foundation/Foundation.h>\n#import <SDL.h>\n#import \"common.h\"\n\n@interface URL : NSObject\n- (void)getURL:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)reply;\n@end\n\n@implementation URL\n- (void)getURL:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)reply\n{\n\tCbuf_AddText(va(\"qwurl \\\"%s\\\"\\n\", [[[event paramDescriptorForKeyword:keyDirectObject] stringValue] cStringUsingEncoding:NSASCIIStringEncoding]));\n}\n@end\n\nstatic URL *url = NULL;\n\nstatic void EzClearBookmarkedDirectory(void);\n\nvoid init_macos_extras(void)\n{\n\tif (url == NULL) {\n\t\turl = [[URL alloc] init];\n\t}\n\n\t[[NSAppleEventManager sharedAppleEventManager] setEventHandler:url andSelector:@selector(getURL:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];\n\n\tCmd_AddCommand(\"sys_forget_sandbox\", EzClearBookmarkedDirectory);\n}\n\nstatic void EzSimpleAlert(NSString *title, NSString *message)\n{\n\tNSAlert *alert = [[NSAlert alloc] init];\n\t[alert setMessageText:title];\n\t[alert setInformativeText:message];\n\t[alert addButtonWithTitle:@\"Ok\"];\n\t[alert runModal];\n}\n\nstatic BOOL EzIsValidEzQuakeDirectory(NSString *directoryPath)\n{\n\tNSString *id1Path = [directoryPath stringByAppendingPathComponent:@\"id1\"];\n\tNSString *pakFilePath = [id1Path stringByAppendingPathComponent:@\"pak0.pak\"];\n\n\tBOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:pakFilePath];\n\tif (!exists) {\n\t\tEzSimpleAlert(@\"Game directory not found\", @\"Select a directory containing 'id1/pak0.pak'.\");\n\t}\n\n\treturn exists;\n}\n\nstatic BOOL EzCheckExistingSettings(char *basedir, int length)\n{\n\tNSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];\n\tNSData *bookmarkData = [defaults objectForKey:@\"basedir\"];\n\tNSString *version = [defaults stringForKey:@\"version\"];\n\tBOOL bookmarkDataIsStale = NO;\n\tNSError *error = nil;\n\tNSURL *resolvedURL;\n\n\tif (!(bookmarkData && version)) {\n\t\treturn NO;\n\t}\n\n\tresolvedURL = [NSURL URLByResolvingBookmarkData:bookmarkData\n\t                                        options:NSURLBookmarkResolutionWithSecurityScope\n\t                                  relativeToURL:nil\n\t                            bookmarkDataIsStale:&bookmarkDataIsStale\n\t                                          error:&error];\n\tif (bookmarkDataIsStale || error) {\n\t\tNSString *msg = error.localizedDescription ? error.localizedDescription : @\"Bookmark data is stale\";\n\t\tEzSimpleAlert(@\"Error resolving bookmark\", msg);\n\t\treturn NO;\n\t}\n\n\tif (![resolvedURL startAccessingSecurityScopedResource]) {\n\t\tEzSimpleAlert(@\"Failed to start accessing security-scoped resource\", @\"\");\n\t\treturn NO;\n\t}\n\n\tNSString *resolvedPath = [resolvedURL path];\n\tif (EzIsValidEzQuakeDirectory(resolvedPath)) {\n\t\tstrlcpy(basedir, [resolvedPath UTF8String], length);\n\t\treturn YES;\n\t}\n\n\treturn NO;\n}\n\nvoid SysLibrarySupportDir(char *basedir, int length)\n{\n\t@autoreleasepool {\n\t\t// Need to temporarily start SDL here to not break initialization of application which\n\t\t// prevents both the dummy menu, and more importantly mouseMovedHandler from working.\n\t\tSDL_Window *window = SDL_CreateWindow(\n\t\t\t\t\"ezQuake\", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, SDL_WINDOW_HIDDEN\n\t\t);\n\n\t\tif (EzCheckExistingSettings(basedir, length)) {\n\t\t\tSDL_DestroyWindow(window);\n\t\t\treturn;\n\t\t}\n\n\t\tNSOpenPanel *openPanel = [NSOpenPanel openPanel];\n\t\t[openPanel setCanChooseFiles:NO];\n\t\t[openPanel setCanChooseDirectories:YES];\n\t\t[openPanel setAllowsMultipleSelection:NO];\n\t\t[openPanel setMessage:@\"Select game directory containing id1/pak0.pak:\"];\n\n\t\tBOOL validDirectorySelected = NO;\n\t\twhile (!validDirectorySelected) {\n\t\t\tif ([openPanel runModal] != NSModalResponseOK) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tNSURL *directoryURL = [[openPanel URLs] objectAtIndex:0];\n\t\t\tif (!EzIsValidEzQuakeDirectory([directoryURL path])) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tNSError *error = nil;\n\t\t\tNSData *bookmarkData = [directoryURL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope\n\t\t\t                              includingResourceValuesForKeys:nil\n\t\t\t                                               relativeToURL:nil\n\t\t\t                                                       error:&error];\n\t\t\tif (error) {\n\t\t\t\tEzSimpleAlert(@\"Error creating bookmark\", error.localizedDescription);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t[[NSUserDefaults standardUserDefaults] setObject:bookmarkData forKey:@\"basedir\"];\n\t\t\t[[NSUserDefaults standardUserDefaults] setObject:@VERSION forKey:@\"version\"];\n\n\t\t\tvalidDirectorySelected = YES;\n\n\t\t\tNSString *resolvedPath = [directoryURL path];\n\t\t\tstrlcpy(basedir, [resolvedPath UTF8String], length);\n\t\t}\n\n\t\tSDL_DestroyWindow(window);\n\t}\n}\n\nstatic void EzClearBookmarkedDirectory(void) {\n\tNSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];\n\n\tNSData *bookmarkData = [defaults objectForKey:@\"basedir\"];\n\tif (bookmarkData) {\n\t\tNSError *error = nil;\n\t\tBOOL bookmarkDataIsStale = NO;\n\n\t\tNSURL *resolvedURL = [NSURL URLByResolvingBookmarkData:bookmarkData\n\t\t                                               options:NSURLBookmarkResolutionWithSecurityScope\n\t\t                                         relativeToURL:nil\n\t\t                                   bookmarkDataIsStale:&bookmarkDataIsStale\n\t\t                                                 error:&error];\n\t\tif (!error && resolvedURL) {\n\t\t\t[resolvedURL stopAccessingSecurityScopedResource];\n\t\t}\n\n\t\t[defaults removeObjectForKey:@\"basedir\"];\n\t\t[defaults removeObjectForKey:@\"version\"];\n\t\t[defaults synchronize];\n\n\t\tCon_Printf(\"Sandbox directory cleared.\\n\");\n\t} else {\n\t\tCon_Printf(\"No sandbox directory selected.\\n\");\n\t}\n}\n"
  },
  {
    "path": "src/sys_posix.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include <unistd.h>\n#include <signal.h>\n#include <stdlib.h>\n#include <limits.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <sys/ipc.h>\n#include <sys/shm.h>\n#include <sys/stat.h>\n#include <string.h>\n#include <ctype.h>\n#include <sys/wait.h>\n#include <sys/mman.h>\n#include <semaphore.h>\n#include <sys/ioctl.h>\n#include <sys/poll.h>\n#include <sched.h>\n#include <errno.h>\n#include <dirent.h>\n#include <sys/resource.h>\n\n#include <SDL.h>\n#include <dlfcn.h>\n\n#include \"quakedef.h\"\n#include \"server.h\"\n#include <pcre2.h>\n\n\n// BSD only defines FNDELAY:\n#ifndef O_NDELAY\n#  define O_NDELAY\tFNDELAY\n#endif\n\n\nint noconinput = 0;\n\nqbool stdin_ready;\nint do_stdin = 1;\n\nvoid OnChange_sys_highpriority (cvar_t *, char *, qbool *);\n\ncvar_t sys_highpriority = {\"sys_highpriority\", \"\", 0, OnChange_sys_highpriority};\ncvar_t sys_nostdout     = {\"sys_nostdout\", \"0\" };\ncvar_t sys_fontsdir     = {\"sys_fontsdir\", \"/usr/local/share/fonts/\"};\n\nvoid Sys_Printf (char *fmt, ...)\n{\n#ifdef DEBUG\n\tva_list argptr;\n\tchar text[2048];\n\tunsigned char *p;\n\n\treturn;\n\n\tva_start (argptr,fmt);\n\tvsnprintf (text, sizeof(text), fmt, argptr);\n\tva_end (argptr);\n\n\tif (sys_nostdout.value)\n\t\treturn;\n\n\tfor (p = (unsigned char *) text; *p; p++)\n\t\tif ((*p > 128 || *p < 32) && *p != 10 && *p != 13 && *p != 9)\n\t\t\tprintf(\"[%02x]\", *p);\n\t\telse\n\t\t\tputc(*p, stdout);\n#else\n\treturn;\n#endif\n}\n\nvoid Sys_Quit(void)\n{\n\tfcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~O_NDELAY);\n\texit(0);\n}\n\nvoid Sys_Init(void)\n{\n#ifdef __APPLE__\n\textern void init_macos_extras(void);\n\tinit_macos_extras();\n#endif\n\tCvar_Register(&sys_highpriority);\n\tCvar_Register(&sys_fontsdir);\n}\n\nvoid Sys_Error(char *error, ...)\n{\n\textern FILE *qconsole_log;\n\tva_list argptr;\n\tchar string[1024];\n\n\tfcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~O_NDELAY);\t//change stdin to non blocking\n\n\tva_start (argptr, error);\n\tvsnprintf (string, sizeof(string), error, argptr);\n\tva_end (argptr);\n\tfprintf(stderr, \"Error: %s\\n\", string);\n\tif (qconsole_log)\n\t\tfprintf(qconsole_log, \"Error: %s\\n\", string);\n\n\tHost_Shutdown ();\n\tSDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, \"Error\", string, NULL);\n\texit(1);\n}\n\nvoid Sys_mkdir (const char *path)\n{\n\tmkdir (path, 0777);\n}\n\n/*\n================\nSys_remove\n================\n*/\nint Sys_remove(char *path)\n{\n\treturn unlink(path);\n}\n\nint Sys_rmdir(const char *path)\n{\n\treturn rmdir(path);\n}\n\nint Sys_FileSizeTime(char *path, int *time1)\n{\n\tstruct stat buf;\n\tif (stat(path, &buf) == -1)\n\t{\n\t\t*time1 = -1;\n\t\treturn 0;\n\t}\n\telse\n\t{\n\t\t*time1 = buf.st_mtime;\n\t\treturn buf.st_size;\n\t}\n}\n\n/*\n================\nSys_listdir\n================\n*/\n\ndir_t Sys_listdir (const char *path, const char *ext, int sort_type)\n{\n\tstatic file_t list[MAX_DIRFILES]; // FIXME: thread un-safe.\n\tdir_t dir;\n\tchar pathname[MAX_OSPATH];\n\tDIR *d;\n\tDIR *testdir; //bliP: list dir\n\tstruct dirent *oneentry;\n\tqbool all;\n\n\tPCRE2_SIZE error_offset;\n\tpcre2_code *preg = NULL;\n\tpcre2_match_data *match_data = NULL;\n\tint error;\n\n\tmemset(list, 0, sizeof(list));\n\tmemset(&dir, 0, sizeof(dir));\n\n\tdir.files = list;\n\tall = !strncmp(ext, \".*\", 3);\n\tif (!all)\n\t\tif (!(preg = pcre2_compile((PCRE2_SPTR)ext, PCRE2_ZERO_TERMINATED, PCRE2_CASELESS, &error, &error_offset, NULL)))\n\t\t{\n\t\t\tPCRE2_UCHAR error_str[256];\n\t\t\tpcre2_get_error_message(error, error_str, sizeof(error_str));\n\t\t\tCon_Printf(\"Sys_listdir: pcre2_compile(%s) error: %s at offset %d\\n\",\n\t\t\t           ext, error_str, error_offset);\n\t\t\tpcre2_code_free(preg);\n\t\t\treturn dir;\n\t\t}\n\n\tif (!(d = opendir(path)))\n\t{\n\t\tif (!all) {\n\t\t\tpcre2_code_free(preg);\n\t\t}\n\t\treturn dir;\n\t}\n\twhile ((oneentry = readdir(d)))\n\t{\n\t\tif (!strncmp(oneentry->d_name, \".\", 2) || !strncmp(oneentry->d_name, \"..\", 3))\n\t\t\tcontinue;\n\t\tif (!all)\n\t\t{\n\t\t\tmatch_data = pcre2_match_data_create_from_pattern(preg, NULL);\n\t\t\terror = pcre2_match(preg, (PCRE2_SPTR)oneentry->d_name,\n\t\t\t\tstrlen(oneentry->d_name), 0, 0, match_data, NULL);\n\t\t\tpcre2_match_data_free(match_data);\n\t\t\tif (error < 0) {\n\t\t\t\tif (error != PCRE2_ERROR_NOMATCH) {\n\t\t\t\t\tCon_Printf(\"Sys_listdir: pcre2_match(%s, %s) error code: %d\\n\",\n\t\t\t\t\t\text, oneentry->d_name, error);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tsnprintf(pathname, sizeof(pathname), \"%s/%s\", path, oneentry->d_name);\n\t\tif ((testdir = opendir(pathname)))\n\t\t{\n\t\t\tdir.numdirs++;\n\t\t\tlist[dir.numfiles].isdir = true;\n\t\t\tlist[dir.numfiles].size = list[dir.numfiles].time = 0;\n\t\t\tclosedir(testdir);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlist[dir.numfiles].isdir = false;\n\t\t\t//list[dir.numfiles].time = Sys_FileTime(pathname);\n\t\t\tdir.size +=\n\t\t\t\t(list[dir.numfiles].size = Sys_FileSizeTime(pathname, &list[dir.numfiles].time));\n\t\t}\n\t\tstrlcpy (list[dir.numfiles].name, oneentry->d_name, MAX_DEMO_NAME);\n\n\t\tif (++dir.numfiles == MAX_DIRFILES - 1)\n\t\t\tbreak;\n\t}\n\tclosedir(d);\n\tif (!all) {\n\t\tpcre2_code_free(preg);\n\t}\n\n\tswitch (sort_type)\n\t{\n\tcase SORT_NO: break;\n#ifndef CLIENTONLY\n\tcase SORT_BY_DATE:\n\t\tqsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_date);\n\t\tbreak;\n\tcase SORT_BY_NAME:\n\t\tqsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_name);\n\t\tbreak;\n#endif\n\t}\n\n\treturn dir;\n}\n\nint Sys_chdir(const char *path)\n{\n\treturn (chdir(path) == 0);\n}\n\nchar *Sys_getcwd(char *buf, int bufsize)\n{\n\treturn getcwd(buf, bufsize);\n}\n\n#if (_POSIX_TIMERS > 0) && defined(_POSIX_MONOTONIC_CLOCK)\n#include <time.h>\ndouble Sys_DoubleTime(void)\n{\n\tstatic unsigned int secbase;\n\tstruct timespec ts;\n\n#ifdef __linux__\n\tclock_gettime(CLOCK_MONOTONIC_RAW, &ts);\n#else\n\tclock_gettime(CLOCK_MONOTONIC, &ts);\n#endif\n\n\tif (!secbase) {\n\t\tsecbase = ts.tv_sec;\n\t\treturn ts.tv_nsec / 1000000000.0;\n\t}\n\n\treturn (ts.tv_sec - secbase) + ts.tv_nsec / 1000000000.0;\n}\n#else\ndouble Sys_DoubleTime(void)\n{\n\tstruct timeval tp;\n\tstruct timezone tzp;\n\tstatic int secbase;\n\n\tgettimeofday(&tp, &tzp);\n\n\tif (!secbase) {\n\t    secbase = tp.tv_sec;\n\t    return tp.tv_usec/1000000.0;\n\t}\n\n\treturn (tp.tv_sec - secbase) + tp.tv_usec / 1000000.0;\n}\n#endif\n\nint main(int argc, char **argv)\n{\n\tdouble time, oldtime, newtime;\n\tint i;\n\n#ifdef __linux__\n\textern void InitSig(void);\n\tInitSig();\n#endif\n\n\tCOM_InitArgv (argc, argv);\n\n\t// let me use -condebug C:\\condebug.log before Quake FS init, so I get ALL messages before quake fully init\n\tif ((i = COM_CheckParm(cmdline_param_console_debug)) && i < COM_Argc() - 1) {\n\t\textern FILE *qconsole_log;\n\t\tchar *s = COM_Argv(i + 1);\n\t\tif (*s != '-' && *s != '+')\n\t\t\tqconsole_log = fopen(s, \"a\");\n\t}\n\n\tsignal(SIGFPE, SIG_IGN);\n\n\t// we need to check for -noconinput and -nostdout before Host_Init is called\n\tif (!(noconinput = COM_CheckParm(cmdline_param_client_nostdinput)))\n\t\tfcntl(0, F_SETFL, fcntl (0, F_GETFL, 0) | FNDELAY);\n\n\tif (COM_CheckParm(cmdline_param_client_nostdoutput))\n\t\tsys_nostdout.value = 1;\n\n\tHost_Init (argc, argv, 256 * 1024 * 1024);\n\n\toldtime = Sys_DoubleTime ();\n\twhile (1) {\n\t\t// find time spent rendering last frame\n\t\tnewtime = Sys_DoubleTime();\n\t\ttime = newtime - oldtime;\n\t\toldtime = newtime;\n\n\t\tHost_Frame(time);\n\t}\n}\n\nvoid Sys_MakeCodeWriteable (unsigned long startaddr, unsigned long length) {\n\tint r;\n\tunsigned long addr;\n\tint psize = getpagesize();\n\n\taddr = (startaddr & ~(psize - 1)) - psize;\n\tr = mprotect((char*) addr, length + startaddr - addr + psize, 7);\n\tif (r < 0)\n    \t\tSys_Error(\"Protection change failed\");\n}\n\n// kazik -->\n// directory listing functions\nint CopyDirent(sys_dirent *ent, struct dirent *tmpent)\n{\n    struct stat statbuf;\n    struct tm * lintime;\n    if (stat(tmpent->d_name, &statbuf) != 0)\n        return 0;\n\n    strncpy(ent->fname, tmpent->d_name, MAX_PATH_LENGTH);\n    ent->fname[MAX_PATH_LENGTH-1] = 0;\n\n    lintime = localtime(&statbuf.st_mtime);\n    UnixtimeToWintime(&ent->time, lintime);\n\n    ent->size = statbuf.st_size;\n\n    ent->directory = (statbuf.st_mode & S_IFDIR);\n\n    return 1;\n}\n\nSysDirEnumHandle Sys_ReadDirFirst(sys_dirent *ent)\n{\n    struct dirent *tmpent;\n    DIR *dir = opendir(\".\");\n    if (dir == NULL)\n        return 0;\n\n    tmpent = readdir(dir);\n    if (tmpent == NULL)\n    {\n        closedir(dir);\n        return 0;\n    }\n\n    if (!CopyDirent(ent, tmpent))\n    {\n        closedir(dir);\n        return 0;\n    }\n\n    return (SysDirEnumHandle)dir;\n}\n\nint Sys_ReadDirNext(SysDirEnumHandle search, sys_dirent *ent)\n{\n    struct dirent *tmpent;\n\n    tmpent = readdir((DIR*)search);\n\n    if (tmpent == NULL)\n        return 0;\n\n    if (!CopyDirent(ent, tmpent))\n        return 0;\n\n    return 1;\n}\n\nvoid Sys_ReadDirClose(SysDirEnumHandle search)\n{\n    closedir((DIR *)search);\n}\n\nvoid _splitpath(const char *path, char *drive, char *dir, char *file, char *ext)\n{\n    const char *f, *e;\n\n    if (drive)\n    drive[0] = 0;\n\n    f = path;\n    while (strchr(f, '/'))\n    f = strchr(f, '/') + 1;\n\n    if (dir)\n    {\n    strncpy(dir, path, min(f-path, _MAX_DIR));\n        dir[_MAX_DIR-1] = 0;\n    }\n\n    e = f;\n    while (*e == '.')   // skip dots at beginning\n    e++;\n    if (strchr(e, '.'))\n    {\n    while (strchr(e, '.'))\n        e = strchr(e, '.')+1;\n    e--;\n    }\n    else\n    e += strlen(e);\n\n    if (file)\n    {\n        strncpy(file, f, min(e-f, _MAX_FNAME));\n    file[min(e-f, _MAX_FNAME-1)] = 0;\n    }\n\n    if (ext)\n    {\n    strncpy(ext, e, _MAX_EXT);\n    ext[_MAX_EXT-1] = 0;\n    }\n}\n\n// full path\nchar *Sys_fullpath(char *absPath, const char *relPath, int maxLength)\n{\n    // too small buffer, copy in tmp[] and then look is enough space in output buffer aka absPath\n    if (maxLength-1 < PATH_MAX)\t{\n\t\tchar tmp[PATH_MAX+1];\n\t\tif (realpath(relPath, tmp) && absPath && strlen(tmp) < maxLength) {\n\t\t\tstrlcpy(absPath, tmp, maxLength);\n\t\t\treturn absPath;\n\t\t}\n\n\t\treturn NULL;\n\t}\n\n    return realpath(relPath, absPath);\n}\n// kazik <--\n\nint Sys_EnumerateFiles (char *gpath, char *match, int (*func)(char *, int, void *), void *parm)\n{\n\tDIR *dir, *dir2;\n\tchar apath[MAX_OSPATH];\n\tchar file[MAX_OSPATH];\n\tchar truepath[MAX_OSPATH];\n\tchar *s;\n\tstruct dirent *ent;\n\n\t//printf(\"path = %s\\n\", gpath);\n\t//printf(\"match = %s\\n\", match);\n\n\tif (!gpath)\n\t\tgpath = \"\";\n\t*apath = '\\0';\n\n\tstrlcpy(apath, match, sizeof(apath));\n\tfor (s = apath+strlen(apath)-1; s >= apath; s--)\n\t{\n\t\tif (*s == '/')\n\t\t{\n\t\t\ts[1] = '\\0';\n\t\t\tmatch += s - apath+1;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (s < apath)  //didn't find a '/'\n\t\t*apath = '\\0';\n\n\tsnprintf(truepath, sizeof(truepath), \"%s/%s\", gpath, apath);\n\n\n\t//printf(\"truepath = %s\\n\", truepath);\n\t//printf(\"gamepath = %s\\n\", gpath);\n\t//printf(\"apppath = %s\\n\", apath);\n\t//printf(\"match = %s\\n\", match);\n\tdir = opendir(truepath);\n\tif (!dir)\n\t{\n\t\tCom_DPrintf(\"Failed to open dir %s\\n\", truepath);\n\t\treturn true;\n\t}\n\tdo\n\t{\n\t\tent = readdir(dir);\n\t\tif (!ent)\n\t\t\tbreak;\n\t\tif (*ent->d_name != '.')\n\t\t\tif (wildcmp(match, ent->d_name))\n\t\t\t{\n\t\t\t\tsnprintf(file, sizeof(file), \"%s/%s\", truepath, ent->d_name);\n\t\t\t\t//would use stat, but it breaks on fat32.\n\n\t\t\t\tif ((dir2 = opendir(file)))\n\t\t\t\t{\n\t\t\t\t\tclosedir(dir2);\n\t\t\t\t\tsnprintf(file, sizeof(file), \"%s%s/\", apath, ent->d_name);\n\t\t\t\t\t//printf(\"is directory = %s\\n\", file);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tsnprintf(file, sizeof(file), \"%s%s\", apath, ent->d_name);\n\t\t\t\t\t//printf(\"file = %s\\n\", file);\n\t\t\t\t}\n\n\t\t\t\tif (!func(file, -2, parm))\n\t\t\t\t{\n\t\t\t\t\tclosedir(dir);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t} while(1);\n\tclosedir(dir);\n\n\treturn true;\n}\n\n/*************************** INTER PROCESS CALLS *****************************/\n#define PIPE_BUFFERSIZE\t\t1024\n\nstatic int fifo_pipe = -1;\n\nstatic char *Sys_PipeFile(void) {\n\tstatic char pipe[MAX_PATH] = {0};\n\n\tif (*pipe)\n\t\treturn pipe;\n\n\tsnprintf(pipe, sizeof(pipe), \"/tmp/ezquake_fifo_%s\", getlogin());\n\treturn pipe;\n}\n\nvoid Sys_InitIPC(void)\n{\n\tmode_t old;\n\n\t/* Don't use the user's umask, make sure we set the proper access */\n\told = umask(0);\n\t// This could legitimately fail (pipe exists), but it should be fine.\n\tmkfifo(Sys_PipeFile(), 0600);\n\tumask(old); // Reset old mask\n\n\t/* Open in non blocking mode */\n\tif ((fifo_pipe = open(Sys_PipeFile(), O_RDONLY | O_NONBLOCK)) == -1) {\n\t\t// We failed ...\n\t\treturn;\n\t}\n}\n\nvoid Sys_CloseIPC(void)\n{\n\tif (fifo_pipe != -1) {\n\t\tclose(fifo_pipe);\n\t\tfifo_pipe = -1;\n\t\tunlink(Sys_PipeFile());\n\t}\n}\n\nvoid Sys_ReadIPC(void)\n{\n\tchar buf[PIPE_BUFFERSIZE] = {0};\n\tint num_bytes_read = 0;\n\n\tif (fifo_pipe == -1)\n\t\treturn;\n\n\tnum_bytes_read = read(fifo_pipe, buf, sizeof(buf) - 1); // nul terminated.\n\tif (num_bytes_read <= 0) {\n\t\treturn;\n\t}\n\tCOM_ParseIPCData(buf, num_bytes_read);\n}\n\nunsigned int Sys_SendIPC(const char *buf)\n{\n\tFILE *fifo_out;\n\tint nelms_written;\n\t\n\tif (!(fifo_out = fopen(Sys_PipeFile(), \"w\")))\n\t\treturn true;\n\n\tnelms_written = fwrite(buf, strlen(buf) + 1, 1, fifo_out);\n\n\tfclose(fifo_out);\n\n\tif (nelms_written != 1)\n\t\treturn false;\n\t\n\treturn true;\n}\n\n\n/********************************** SEMAPHORES *******************************/\n/* Sys_Sem*() returns 0 on success; on error, -1 is returned */\nint Sys_SemInit(sem_t *sem, int value, int max_value) \n{\n\treturn sem_init(sem, 0, value); // Don't share between processes\n}\n\nint Sys_SemWait(sem_t *sem) \n{\n\treturn sem_wait(sem);\n}\n\nint Sys_SemPost(sem_t *sem)\n{\n\treturn sem_post(sem);\n}\n\nint Sys_SemDestroy(sem_t *sem)\n{\n\treturn sem_destroy(sem);\n}\n\n/*********************************************************************************/\nint Sys_Script (const char *path, const char *args)\n{\n\tchar str[1024];\n\n\tsnprintf(str, sizeof(str), \"cd %s\\n./%s.qws %s &\\ncd ..\", fs_gamedir, path, args);\n\n\tif (system(str) == -1)\n\t\treturn 0;\n\n\treturn 1;\n}\n\nDL_t Sys_DLOpen(const char *path)\n{\n\tDL_t ret = dlopen(path,\n#ifdef __OpenBSD__\n\t              DL_LAZY\n#else\n\t              RTLD_NOW\n#endif\n\t             );\n\tif (!ret)\n\t\tCon_DPrintf(\"Sys_DLOpen: %s\\n\", dlerror());\n\treturn ret;\n}\n\nqbool Sys_DLClose(DL_t dl)\n{\n\treturn !dlclose(dl);\n}\n\nvoid *Sys_DLProc(DL_t dl, const char *name)\n{\n\treturn dlsym(dl, name);\n}\n\n/*********************************************************************************/\n\nvoid Sys_CloseLibrary(dllhandle_t *lib)\n{\n\tdlclose((void*)lib);\n}\n\ndllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs)\n{\n\tint i;\n\tdllhandle_t lib;\n\n\tlib = dlopen (name, RTLD_LAZY);\n\tif (!lib)\n\t\treturn NULL;\n\n\tfor (i = 0; funcs[i].name; i++)\n\t{\n\t\t*funcs[i].funcptr = dlsym(lib, funcs[i].name);\n\t\tif (!*funcs[i].funcptr)\n\t\t\tbreak;\n\t}\n\tif (funcs[i].name)\n\t{\n\t\tSys_CloseLibrary((dllhandle_t*)lib);\n\t\tlib = NULL;\n\t}\n\n\treturn (dllhandle_t*)lib;\n}\n\nvoid *Sys_GetAddressForName(dllhandle_t *module, const char *exportname)\n{\n\tif (!module)\n\t\treturn NULL;\n\treturn dlsym(module, exportname);\n}\n\nconst char* Sys_FontsDirectory(void)\n{\n\treturn sys_fontsdir.string;\n}\n\nconst char* Sys_HomeDirectory(void)\n{\n\tchar *ev = getenv(\"HOME\");\n\tif (ev) {\n\t\treturn ev;\n\t}\n\treturn \"\";\n}\n\n#ifdef __MACOSX__\n\nvoid Sys_RegisterQWURLProtocol_f(void)\n{\n\tCon_Printf(\"Not yet implemented for this platform.\");\n\treturn;\n}\n\n#else\n\nvoid Sys_RegisterQWURLProtocol_f(void)\n{\n    char open_cmd[MAX_PATH*2+1024] = { 0 };\n    char exe_path[MAX_PATH] = { 0 };\n    char buf[MAX_PATH] = { 0 };\n    const char *homedir=Sys_HomeDirectory();\n    int nchar = -1;\n    FILE *fptr;\n\n    qbool quiet = Cmd_Argc() == 2 && !strcmp(Cmd_Argv(1), \"quiet\");\n\n    nchar = readlink(\"/proc/self/exe\", exe_path, sizeof(exe_path)-1);\n    if (nchar < 0 || nchar >= sizeof exe_path) {\n        Con_Printf(\"Failed to get executable path.\");\n        return;\n    }\n\n    snprintf(open_cmd, sizeof(open_cmd), \"[Desktop Entry]\\n\"\n            \"Type=Application\\n\"\n            \"Name=ezQuake\\n\"\n            \"GenericName=Handles qw:// protocol links from browsers\\n\"\n            \"Comment=Browser uses ezQuake to open qw:// urls.\\n\"\n            \"Keywords=qtv,qw://;\\n\"\n            \"Categories=quakeworld;qtv;Game;\\n\"\n            \"Exec=%s +qwurl %s\\n\"\n            \"Path=%s\\n\"\n            \"MimeType=x-scheme-handler/qw;\\n\"\n            \"Terminal=false\\n\"\n            \"StartupNotify=false\\n\"\n            , exe_path, \"%u\", com_basedir);\n    snprintf(buf, sizeof(buf), \"%s/.local/share/applications/qw-url-handler.desktop\", homedir);\n\n    fptr = fopen(buf, \"wb+\");\n    if(!fptr){\n        //create directory path to url handler file\n        snprintf(buf, sizeof(buf), \"%s/.local\", homedir);\n        Sys_mkdir(buf);\n        snprintf(buf, sizeof(buf), \"%s/.local/share\", homedir);\n        Sys_mkdir(buf);\n        snprintf(buf, sizeof(buf), \"%s/.local/share/applications\", homedir);\n        Sys_mkdir(buf);\n        //attempt opening the file again\n        snprintf(buf, sizeof(buf), \"%s/.local/share/applications/qw-url-handler.desktop\", homedir);\n        fptr = fopen(buf, \"wb+\");\n    }\n    if (fptr == NULL) {\n        Con_Printf(\"Failed to open qw-url-handler file.\");\n        return;\n    }\n    fprintf(fptr, \"%s\", open_cmd);\n    fclose(fptr);\n\n\tif(system(\"xdg-mime default qw-url-handler.desktop x-scheme-handler/qw\") != 0){\n        Con_Printf(\"Failed to register qw-url-handler file with xdg-mime, please make sure this program is installed.\");\n    }\n\n    if (!quiet) {\n        Com_Printf_State(PRINT_WARNING, \"qw:// protocol registered\\n\");\n    }\n\n    return;\n}\n\n#endif //platforms other than osx\n\nint Sys_SetPriority(int priority)\n{\n\tint ret, i=0;\n\n\tswitch (priority) {\n\t\tcase 1:\n\t\t\tfor (i=-19;i<0;i++) {\n\t\t\t\tret = setpriority(PRIO_PGRP, 0, i);\n\t\t\t\tif (ret == 0) break;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase -1:\n\t\t\tfor (i=19;i>0;i--) {\n\t\t\t\tret = setpriority(PRIO_PGRP, 0, i);\n\t\t\t\tif (ret == 0) break;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tret = setpriority(PRIO_PGRP, 0, 0);\n\t}\n\n\tif (ret == 0) return i;\n\treturn 255;\n}\n\nvoid OnChange_sys_highpriority (cvar_t *var, char *s, qbool *cancel)\n{\n\t//do not attempt to change priority if cvar is unset\n\tif (s[0] == '\\0') return;\n\n\tint priority, ret;\n\tchar *desc;\n\n\tpriority = Q_atoi(s);\n\tif (priority > 0){\n\t\tdesc = \"high\";\n\t} else if (priority < 0) {\n\t\tdesc = \"low\";\n\t} else {\n\t\tdesc = \"normal\";\n\t}\n\n\tret = Sys_SetPriority(priority);\n\tif (ret != 255) {\n\t\tCom_Printf(\"Process priority set to %s (nice level %d)\\n\", desc, ret);\n\t} else {\n\t\tCom_Printf(\"Failed to set process priority set to %s\\n\", desc);\n\t}\n}\n"
  },
  {
    "path": "src/sys_sdl2.c",
    "content": "/*\nCopyright (C) 2014-2016 ezQuake team \n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#include \"quakedef.h\"\n#include <SDL.h>\n\ncvar_t sys_yieldcpu = {\"sys_yieldcpu\", \"0\"};\ncvar_t sys_inactivesound = {\"sys_inactivesound\", \"0\", CVAR_ARCHIVE};\ncvar_t sys_inactivesleep = {\"sys_inactivesleep\", \"1\"};\ncvar_t sys_disable_alt_enter = {\"sys_disable_alt_enter\", \"0\"};\n\nstatic void Sys_BatteryInfo_f(void)\n{\n\tSDL_PowerState res;\n\tint secs, percent;\n\n\tif ((res = SDL_GetPowerInfo(&secs, &percent)) == SDL_POWERSTATE_UNKNOWN) {\n\t\tCom_Printf(\"Failed to retrieve power state info\\n\");\n\t\treturn;\n\t}\n\n\tswitch (res) {\n\t\tcase SDL_POWERSTATE_ON_BATTERY:\n\t\t\tCom_Printf(\"%d%% left (%d:%02dh)\\n\", percent, secs/3600, (secs%3600)/60);\n\t\t\tbreak;\n\t\tcase SDL_POWERSTATE_NO_BATTERY:\n\t\t\tCom_Printf(\"No battery available\\n\");\n\t\t\tbreak;\n\t\tcase SDL_POWERSTATE_CHARGING:\n\t\t\tCom_Printf(\"Plugged in, charging battery (%d%%)\\n\", percent);\n\t\t\tbreak;\n\t\tcase SDL_POWERSTATE_CHARGED:\n\t\t\tCom_Printf(\"Plugged in, battery is fully charged\\n\");\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nvoid Sys_CvarInit(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SYSTEM_SETTINGS);\n\tCvar_Register(&sys_yieldcpu);\n\tCvar_Register(&sys_inactivesound);\n\tCvar_Register(&sys_inactivesleep);\n\tCvar_Register(&sys_disable_alt_enter);\n\tCvar_ResetCurrentGroup();\n\n\tCmd_AddCommand(\"batteryinfo\", Sys_BatteryInfo_f);\n}\n\nwchar *Sys_GetClipboardTextW(void)\n{\n\tchar *tmp;\n\twchar *wtmp = NULL;\n\n\tif (SDL_HasClipboardText()) {\n\t\ttmp = SDL_GetClipboardText();\n\t\twtmp = str2wcs(tmp);\n\t\tSDL_free(tmp);\n\t}\n\n\treturn wtmp;\n}\n\nvoid Sys_CopyToClipboard(const char *text)\n{\n\tSDL_SetClipboardText(text);\n}\n\nint Sys_CreateDetachedThread(int (*func)(void *), void *data)\n{\n\tSDL_Thread *thread;\n\t\n\tthread = SDL_CreateThread((SDL_ThreadFunction)func, NULL, data);\n\tif (!thread) {\n\t\treturn -1;\n\t}\n\n\tSDL_DetachThread(thread);\n\n\treturn 0;\n}\n\nSDL_Thread *Sys_CreateThread(int (*func)(void *), void *data)\n{\n\treturn SDL_CreateThread((SDL_ThreadFunction)func, NULL, data);\n}\n"
  },
  {
    "path": "src/sys_win.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n// sys_win.c\n\n#include \"quakedef.h\"\n#include <windows.h>\n#include <commctrl.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <limits.h>\n#include <io.h>\t\t\t// _open, etc\n#include <direct.h>\t\t// _mkdir\n#include <conio.h>\t\t// _putch\n#include <tchar.h>\n#include \"keys.h\"\n#include \"server.h\"\n#include \"pcre2.h\"\n#include <shlobj.h>\n\n// \"Starting with the Release 302 drivers, application developers can direct the\n// Optimus driver at runtime to use the High Performance Graphics to render any\n// application -- even those applications for which there is no existing application\n// profile. They can do this by exporting a global variable named NvOptimusEnablement.\n// The Optimus driver looks for the existence and value of the export. Only the LSB of\n// the DWORD matters at this time. A value of 0x00000001 indicates that rendering should\n// be performed using High Performance Graphics. A value of 0x00000000 indicates that\n// this method should be ignored.\n// https://docs.nvidia.com/gameworks/content/technologies/desktop/optimus.htm\n#ifdef _MSC_VER\n_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;\n#else\n__attribute__((dllexport)) DWORD NvOptimusEnablement = 0x00000001;\n#endif\n\n// Many Gaming and workstation laptops are available with both(1) integrated power saving and\n// (2) discrete high performance graphics devices.Unfortunately, 3D intensive application performance\n// may suffer greatly if the best graphics device is not selected.For example, a game may run at 30\n// Frames Per Second(FPS) on the integrated GPU rather than the 60 FPS the discrete GPU would enable.\n// As a developer you can easily fix this problem by adding only one line to your executable's source code :\n// https://gpuopen.com/amdpowerxpressrequesthighperformance/\n#ifdef _MSC_VER\n_declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;\n#else\n__attribute__((dllexport)) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;\n#endif\n\n#define MINIMUM_WIN_MEMORY\t0x0c00000\n#define MAXIMUM_WIN_MEMORY\t0xfffffff\n\n#define PAUSE_SLEEP\t\t50\t\t\t\t// sleep time on pause or minimization\n#define NOT_FOCUS_SLEEP\t20\t\t\t\t// sleep time when not focus\n\nvoid OnChange_sys_highpriority (cvar_t *, char *, qbool *);\ncvar_t\tsys_highpriority = {\"sys_highpriority\", \"0\", 0, OnChange_sys_highpriority};\n\nstatic HANDLE\tqwclsemaphore;\nstatic HANDLE\ttevent;\nHANDLE\thinput, houtput;\nHINSTANCE\tglobal_hInstance;\n\nvoid MaskExceptions (void);\nvoid Sys_PopFPCW (void);\nvoid Sys_PushFPCW_SetHigh (void);\n\ntypedef HRESULT (WINAPI *SetProcessDpiAwarenessFunc)(_In_ DWORD value);\n\n#ifndef WITHOUT_WINKEYHOOK\n\n#ifndef WH_KEYBOARD_LL\n#define WH_KEYBOARD_LL 13\n#endif\n\nunsigned int windows_keys_down, windows_keys_up;\n\nstatic HHOOK WinKeyHook;\nstatic qbool WinKeyHook_isActive;\nstatic qbool ScreenSaver_isDisabled = false;\nstatic qbool PowerOff_isDisabled = false;\n\nLRESULT CALLBACK LLWinKeyHook(int Code, WPARAM wParam, LPARAM lParam);\nvoid OnChange_sys_disableWinKeys(cvar_t *var, char *string, qbool *cancel);\ncvar_t\tsys_disableWinKeys = {\"sys_disableWinKeys\", \"0\", 0, OnChange_sys_disableWinKeys};\n\nextern qbool ActiveApp, Minimized;\n\nstatic void ReleaseKeyHook (void)\n{\n\tif (WinKeyHook_isActive) {\n\t\tUnhookWindowsHookEx (WinKeyHook);\n\t\tWinKeyHook_isActive = false;\n\t}\n}\n\nstatic qbool RegisterKeyHook (void)\n{\n\tif (!WinKeyHook_isActive) {\n\t\tWinKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL, LLWinKeyHook, global_hInstance, 0);\n\t\tWinKeyHook_isActive = (WinKeyHook != NULL);\n\t}\n\n\treturn WinKeyHook_isActive;\n}\n\nvoid OnChange_sys_disableWinKeys(cvar_t *var, char *string, qbool *cancel) \n{\n\textern cvar_t r_fullscreen;\n\n\tif (Q_atof(string) == 1 || (Q_atof(string) == 2 && r_fullscreen.value))\n\t{\n\t\tif (!WinKeyHook_isActive) \n\t\t{\n\t\t\tif (! RegisterKeyHook())\n\t\t\t{\n\t\t\t\tCom_Printf(\"Failed to install winkey hook.\\n\");\n\t\t\t\tCom_Printf(\"Microsoft Windows NT 4.0, 2000 or XP is required.\\n\");\n\t\t\t\t*cancel = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\telse \n\t{\n\t\tReleaseKeyHook ();\n\t}\n}\n\nLRESULT CALLBACK LLWinKeyHook(int Code, WPARAM wParam, LPARAM lParam) \n{\n\tPKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam;\n\tunsigned int* flags = (p->flags & LLKHF_UP) ? &windows_keys_up : &windows_keys_down;\n\n\tswitch (p->vkCode)\n\t{\n\t\tcase VK_LWIN:\n\t\t\t*flags |= WINDOWS_LWINDOWSKEY;\n\t\t\treturn 1;\n\t\tcase VK_RWIN:\n\t\t\t*flags |= WINDOWS_RWINDOWSKEY;\n\t\t\treturn 1;\n\t\tcase VK_APPS:\n\t\t\t*flags |= WINDOWS_MENU;\n\t\t\treturn 1;\n\t\tcase VK_SNAPSHOT:\n\t\t\t*flags |= WINDOWS_PRINTSCREEN;\n\t\t\treturn 1;\n\t\tcase VK_CAPITAL:\n\t\t\tif (key_dest != key_console && key_dest != key_message) {\n\t\t\t\t// Don't toggle capslock when in game\n\t\t\t\t*flags |= WINDOWS_CAPSLOCK;\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tbreak;\n\t}\n\n\treturn CallNextHookEx(NULL, Code, wParam, lParam);\n}\n\nvoid Sys_ActiveAppChanged(void)\n{\n\tstatic qbool appWasActive = true;\n\tstatic qbool hookWasActive = false;\n\n\tif (appWasActive == ActiveApp)\n\t\treturn;\n\n\tappWasActive = ActiveApp;\n\tif (ActiveApp && hookWasActive) {\n\t\tRegisterKeyHook();\n\t}\n\telse if (!ActiveApp) {\n\t\thookWasActive = WinKeyHook_isActive;\n\n\t\tReleaseKeyHook();\n\t}\n}\n\n#endif\n\n\nint Sys_SetPriority(int priority) \n{\n    DWORD p;\n\n\tswitch (priority) \n\t{\n\t\tcase 0:\tp = IDLE_PRIORITY_CLASS; break;\n\t\tcase 1:\tp = NORMAL_PRIORITY_CLASS; break;\n\t\tcase 2:\tp = HIGH_PRIORITY_CLASS; break;\n\t\tcase 3:\tp = REALTIME_PRIORITY_CLASS; break;\n\t\tdefault: return 0;\n\t}\n\n\treturn SetPriorityClass(GetCurrentProcess(), p);\n}\n\nvoid OnChange_sys_highpriority (cvar_t *var, char *s, qbool *cancel) \n{\n\tint ok, q_priority;\n\tchar *desc;\n\tfloat priority;\n\n\tpriority = Q_atof(s);\n\tif (priority == 1) \n\t{\n\t\tq_priority = 2;\n\t\tdesc = \"high\";\n\t} \n\telse if (priority == -1) \n\t{\n\t\tq_priority = 0;\n\t\tdesc = \"low\";\n\t} \n\telse \n\t{\n\t\tq_priority = 1;\n\t\tdesc = \"normal\";\n\t}\n\n\tif (!(ok = Sys_SetPriority(q_priority))) \n\t{\n\t\tCom_Printf(\"Changing process priority failed\\n\");\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tCom_Printf(\"Process priority set to %s\\n\", desc);\n}\n\n//===============================================================================\n// FILE IO\n//===============================================================================\n\nvoid Sys_mkdir (const char *path) \n{\n\t_mkdir (path);\n}\n\nint Sys_remove (char *path)\n{\n\treturn remove(path);\n}\n\nint Sys_rmdir (const char *path)\n{\n\treturn _rmdir(path);\n}\n\n// D-Kure: This is added for FTE vfs\nint Sys_EnumerateFiles (char *gpath, char *match, int (*func)(char *, int, void *), void *parm)\n{\n\tHANDLE r;\n\tWIN32_FIND_DATA fd; \n\tchar apath[MAX_OSPATH];\n\tchar apath2[MAX_OSPATH];\n\tchar file[MAX_OSPATH];\n\tchar *s;\n\tint go;\n\tif (!gpath)\n\t\treturn 0;\n\n\tsnprintf(apath, sizeof(apath), \"%s/%s\", gpath, match);\n\tfor (s = apath+strlen(apath)-1; s> apath; s--)\n\t{\n\t\tif (*s == '/') \n\t\t\tbreak;\n\t}\n\t*s = '\\0';\n\n\t// This is what we ask windows for.\n\tsnprintf(file, sizeof(file), \"%s/*.*\", apath);\n\n\t// We need to make apath contain the path in match but not gpath\n\tstrlcpy(apath2, match, sizeof(apath));\n\tmatch = s+1;\n\tfor (s = apath2+strlen(apath2)-1; s> apath2; s--)\n\t{\n\t\tif (*s == '/')\n\t\t\tbreak;\n\t}\n\t*s = '\\0';\n\tif (s != apath2)\n\t\tstrlcat (apath2, \"/\", sizeof (apath2));\n\n\tr = FindFirstFile(file, &fd);\n\tif (r==(HANDLE)-1)\n\t\treturn 1;\n\tgo = true;\n\tdo\n\t{\n\t\tif (*fd.cFileName == '.');  // Don't ever find files with a name starting with '.'\n\t\telse if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)    //is a directory\n\t\t{\n\t\t\tif (wildcmp(match, fd.cFileName))\n\t\t\t{\n\t\t\t\tsnprintf(file, sizeof(file), \"%s%s/\", apath2, fd.cFileName);\n\t\t\t\tgo = func(file, fd.nFileSizeLow, parm);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (wildcmp(match, fd.cFileName))\n\t\t\t{\n\t\t\t\tsnprintf(file, sizeof(file), \"%s%s\", apath2, fd.cFileName);\n\t\t\t\tgo = func(file, fd.nFileSizeLow, parm);\n\t\t\t}\n\t\t}\n\t}\n\twhile(FindNextFile(r, &fd) && go);\n\tFindClose(r);\n\n\treturn go;\n}\n\n#ifndef CLIENTONLY\n/*\n================\nSys_listdir\n================\n*/\ndir_t Sys_listdir (const char *path, const char *ext, int sort_type)\n{\n\tstatic file_t\tlist[MAX_DIRFILES];\n\tdir_t\tdir;\n\tHANDLE\th;\n\tWIN32_FIND_DATA fd;\n\tchar\tpathname[MAX_DEMO_NAME];\n\tqbool all;\n\n\tPCRE2_SIZE\terror_offset;\n\tpcre2_code\t*preg = NULL;\n\tpcre2_match_data *match_data = NULL;\n\tint error;\n\n\tmemset(list, 0, sizeof(list));\n\tmemset(&dir, 0, sizeof(dir));\n\n\tdir.files = list;\n\tall = !strncmp(ext, \".*\", 3);\n\tif (!all) {\n\t\tif (!(preg = pcre2_compile((PCRE2_SPTR)ext, PCRE2_ZERO_TERMINATED, PCRE2_CASELESS, &error, &error_offset, NULL))) {\n\t\t\tCon_Printf(\"Sys_listdir: pcre2_compile(%s) error: %d at offset %d\\n\",\n\t\t\t\text, error, error_offset);\n\t\t\treturn dir;\n\t\t}\n\t}\n\n\tsnprintf(pathname, sizeof(pathname), \"%s/*.*\", path);\n\tif ((h = FindFirstFile (pathname , &fd)) == INVALID_HANDLE_VALUE)\n\t{\n\t\tif (!all) {\n\t\t\tpcre2_code_free(preg);\n\t\t}\n\t\treturn dir;\n\t}\n\n\tdo\n\t{\n\t\tif (!strncmp(fd.cFileName, \".\", 2) || !strncmp(fd.cFileName, \"..\", 3))\n\t\t\tcontinue;\n\t\tif (!all)\n\t\t{\n\t\t\tmatch_data = pcre2_match_data_create_from_pattern(preg, NULL);\n\t\t\terror = pcre2_match(preg, (PCRE2_SPTR)fd.cFileName,\n\t\t\t\tstrlen(fd.cFileName), 0, 0, match_data, NULL);\n\t\t\tpcre2_match_data_free(match_data);\n\t\t\tif (error < 0) {\n\t\t\t\tif (error != PCRE2_ERROR_NOMATCH) {\n\t\t\t\t\tCon_Printf(\"Sys_listdir: pcre2_match(%s, %s) error code: %d\\n\",\n\t\t\t\t\t\text, fd.cFileName, error);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\n\t\t\t//bliP: list dir\n\t\t\tdir.numdirs++;\n\t\t\tlist[dir.numfiles].isdir = true;\n\t\t\tlist[dir.numfiles].size = list[dir.numfiles].time = 0;\n\t\t}\n\t\telse {\n\t\t\tlist[dir.numfiles].isdir = false;\n\t\t\tsnprintf(pathname, sizeof(pathname), \"%s/%s\", path, fd.cFileName);\n\t\t\tlist[dir.numfiles].time = 0; //Sys_FileTime(pathname);\n\t\t\tdir.size += (list[dir.numfiles].size = fd.nFileSizeLow);\n\t\t}\n\t\tstrlcpy (list[dir.numfiles].name, fd.cFileName, sizeof(list[0].name));\n\n\t\tif (++dir.numfiles == MAX_DIRFILES - 1) {\n\t\t\tbreak;\n\t\t}\n\t} while (FindNextFile(h, &fd));\n\n\tFindClose (h);\n\tif (!all) {\n\t\tpcre2_code_free(preg);\n\t}\n\n\tswitch (sort_type)\n\t{\n\tcase SORT_NO: break;\n\tcase SORT_BY_DATE:\n\t\tqsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_date);\n\t\tbreak;\n\tcase SORT_BY_NAME:\n\t\tqsort((void *)list, dir.numfiles, sizeof(file_t), Sys_compare_by_name);\n\t\tbreak;\n\t}\n\treturn dir;\n}\n#endif\n\n// ===============================================================================\n// SYSTEM IO\n// ===============================================================================\n\n/// turn back on screen saver and monitor power off\nstatic void Sys_RestoreScreenSaving(void)\n{\n    if (ScreenSaver_isDisabled)\n        SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, TRUE, 0, SPIF_SENDWININICHANGE);\n\n\tif (PowerOff_isDisabled)\n\t\tSystemParametersInfo(SPI_SETPOWEROFFACTIVE, TRUE, 0, SPIF_SENDWININICHANGE);\n}\n\n/// disable screen saver and monitor power off\nstatic void Sys_DisableScreenSaving(void)\n{\n\tint bIsEnabled = 0;\n\n\t// disables screen saver\n\tif ( SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, (PVOID)(&bIsEnabled), 0) && bIsEnabled ) \n\t{\n\t\tif ( SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, 0, SPIF_SENDWININICHANGE) ) \n\t\t{\n\t\t\tScreenSaver_isDisabled = true;\n\t\t}\n\t}\n\n\t// disables screen power off\n\tif ( SystemParametersInfo(SPI_GETPOWEROFFACTIVE, 0, (PVOID)(&bIsEnabled), 0) && bIsEnabled ) \n\t{\n\t\tif ( SystemParametersInfo(SPI_SETPOWEROFFACTIVE, FALSE, 0, SPIF_SENDWININICHANGE) ) \n\t\t{\n\t\t\tPowerOff_isDisabled = true;\n\t\t}\n\t}\n}\n\nvoid Sys_Error (char *error, ...) \n{\n\tva_list argptr;\n\tchar text[1024];\n\n\tva_start(argptr, error);\n\tvsnprintf(text, sizeof(text), error, argptr);\n\tva_end(argptr);\n\n\tHost_Shutdown ();\n\n\tMessageBox(NULL, text, \"Error\", 0);\n\n\tif (qwclsemaphore)\n\t\tCloseHandle (qwclsemaphore);\n\n\tSys_RestoreScreenSaving();\n \n\texit (1);\n}\n\nvoid Sys_Printf_Direct(const char* text)\n{\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tif (houtput == NULL) {\n\t\thoutput = CreateFile(\"SysPrintf.log\", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);\n\t}\n\n\tif (houtput != NULL) {\n\t\tDWORD dummy;\n\n\t\tWriteFile(houtput, text, strlen(text), &dummy, NULL);\n\t}\n#endif\n}\n\nvoid Sys_Printf (char *fmt, ...) \n{\n#ifndef _DEBUG\n\treturn;\n#endif\n\n#ifdef DEBUG_MEMORY_ALLOCATIONS\n\tif (houtput == NULL) {\n\t\thoutput = CreateFile(\"SysPrintf.log\", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);\n\t}\n\n\tif (houtput != NULL) {\n\t\tva_list argptr;\n\t\tchar text[1024];\n\t\tDWORD dummy;\n\t\tint n;\n\n\t\tva_start(argptr, fmt);\n\t\tn = vsnprintf(text, sizeof(text), fmt, argptr);\n\t\tva_end(argptr);\n\n\t\tif (n >= sizeof(text)) {\n\t\t\tchar* buffer = (char*)Q_malloc(n + 1);\n\t\t\tva_start(argptr, fmt);\n\t\t\tvsnprintf(buffer, n, fmt, argptr);\n\t\t\tva_end(argptr);\n\n\t\t\tWriteFile(houtput, (byte*)buffer, strlen(buffer), &dummy, NULL);\n\t\t\tQ_free(buffer);\n\t\t}\n\t\telse {\n\t\t\tWriteFile(houtput, text, strlen(text), &dummy, NULL);\n\t\t}\n\t}\n#endif\n}\n\nvoid Sys_Quit (void) \n{\n\tif (tevent)\n\t\tCloseHandle (tevent);\n\n\tif (qwclsemaphore)\n\t\tCloseHandle (qwclsemaphore);\n\n#ifndef WITHOUT_WINKEYHOOK\n\tif (WinKeyHook_isActive)\n\t\tUnhookWindowsHookEx(WinKeyHook);\n#endif\n\n\tif (houtput) {\n\t\tif (houtput != GetStdHandle(STD_OUTPUT_HANDLE)) {\n\t\t\tCloseHandle(houtput);\n\t\t\thoutput = NULL;\n\t\t}\n\t}\n\n\tSys_RestoreScreenSaving();\n \n\texit (0);\n}\n\nstatic double pfreq;\nstatic qbool hwtimer = false;\n\nvoid Sys_InitDoubleTime (void) \n{\n\t__int64 freq;\n\n\tif (!COM_CheckParm(cmdline_param_client_nohardwaretimers) && QueryPerformanceFrequency((LARGE_INTEGER *)&freq) && freq > 0)\n\t{\n\t\t// Hardware timer available\n\t\tpfreq = (double)freq;\n\t\thwtimer = true;\n\t} \n\telse \n\t{\n\t\t// Make sure the timer is high precision, otherwise NT gets 18ms resolution\n\t\ttimeBeginPeriod (1);\n\t}\n}\n\ndouble Sys_DoubleTime (void) \n{\n\t__int64 pcount;\n\tstatic __int64 startcount;\n\tstatic DWORD starttime;\n\tstatic qbool first = true;\n\tDWORD now;\n\n\tif (hwtimer) \n\t{\n\t\tQueryPerformanceCounter ((LARGE_INTEGER *)&pcount);\n\t\tif (first) \n\t\t{\n\t\t\tfirst = false;\n\t\t\tstartcount = pcount;\n\t\t\treturn 0.0;\n\t\t}\n\t\t// TODO: check for wrapping\n\t\treturn (pcount - startcount) / pfreq;\n\t}\n\n\tnow = timeGetTime();\n\n\tif (first) {\n\t\tfirst = false;\n\t\tstarttime = now;\n\t\treturn 0.0;\n\t}\n\n\tif (now < starttime) // Wrapped?\n\t\treturn (now / 1000.0) + (LONG_MAX - starttime / 1000.0);\n\n\tif (now - starttime == 0)\n\t\treturn 0.0;\n\n\treturn (now - starttime) / 1000.0;\n}\n\nBOOL WINAPI HandlerRoutine (DWORD dwCtrlType) \n{\n\tswitch (dwCtrlType) {\n\t\tcase CTRL_C_EVENT:\t\t\n\t\tcase CTRL_BREAK_EVENT:\n\t\tcase CTRL_CLOSE_EVENT:\n\t\tcase CTRL_LOGOFF_EVENT:\n\t\tcase CTRL_SHUTDOWN_EVENT:\n\t\t\tCbuf_AddText (\"quit\\n\");\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\n// Quake calls this so the system can register variables before host_hunklevel is marked\nvoid Sys_Init (void) \n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SYSTEM_SETTINGS);\n\tCvar_Register(&sys_highpriority);\n#ifndef WITHOUT_WINKEYHOOK\n\tCvar_Register(&sys_disableWinKeys);\t\n#endif\n\tCvar_ResetCurrentGroup();\n\n}\n\nvoid WinCheckOSInfo(void)\n{\n\tOSVERSIONINFO vinfo;\n\n\tvinfo.dwOSVersionInfoSize = sizeof(vinfo);\n\n\tif (!GetVersionEx(&vinfo)) {\n\t\tSys_Error(\"Couldn't get OS info\");\n\t\treturn;\n\t}\n\n\tif (vinfo.dwPlatformId != VER_PLATFORM_WIN32_NT || vinfo.dwMajorVersion < 5 || (vinfo.dwMajorVersion == 5 && vinfo.dwMinorVersion < 1)) {\n\t\tSys_Error(\"ezQuake requires at least Windows XP.\");\n\t\treturn;\n\t}\n\n\t// Use raw resolutions, not scaled\n\t{\n\t\tHMODULE lib = LoadLibrary(\"Shcore.dll\");\n\t\tif (lib != NULL) {\n\t\t\tSetProcessDpiAwarenessFunc SetProcessDpiAwareness;\n\n\t\t\tSetProcessDpiAwareness = (SetProcessDpiAwarenessFunc) GetProcAddress(lib, \"SetProcessDpiAwareness\");\n\t\t\tif (SetProcessDpiAwareness != NULL) {\n\t\t\t\tSetProcessDpiAwareness(2);\n\t\t\t}\n\t\t\tFreeLibrary(lib);\n\t\t}\n\t}\n}\n\nvoid Sys_Init_ (void) \n{\n\tif (!COM_CheckParm(cmdline_param_client_allowmultipleclients))\n\t{\n\t\t// Mutex will fail if semaphore already exists.\n\t\tqwclsemaphore = CreateMutex(\n\t\t\tNULL,\t\t// Security attributes\n\t\t\t0,\t\t\t// Owner\n\t\t\t\"qwcl\");\t// Semaphore name\n\n\t\tif (!qwclsemaphore)\n\t\t{\n\t\t\tint qwurl_parm = COM_FindParm(\"+qwurl\");\n\t\t\tchar cmd[1024] = { 0 };\n\n\t\t\tif (COM_CheckArgsForPlayableFiles(cmd, sizeof(cmd)))\n\t\t\t{\n\t\t\t\t// Play a demo/.qtv file if it was specified as the first argument.\n\t\t\t\tSys_SendIPC(cmd);\n\t\t\t\tSys_Quit();\n\t\t\t}\n\t\t\telse if (qwurl_parm)\n\t\t\t{\n\t\t\t\t// If the user specified a QW URL on the commandline\n\t\t\t\t// we forward it to the already running client.\n\t\t\t\tSys_SendIPC(COM_Argv(qwurl_parm + 1));\n\t\t\t\tSys_Quit();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tSys_Error (\"QWCL is already running on this system\");\n\t\t\t}\n\t\t}\n\t\t\n\t\tCloseHandle (qwclsemaphore);\n\n\t\tqwclsemaphore = CreateSemaphore(\n\t\t\tNULL,\t\t// Security attributes\n\t\t\t0,\t\t\t// Initial count\n\t\t\t1,\t\t\t// Maximum count\n\t\t\t\"qwcl\");\t// Semaphore name\n\t}\n\n\tSys_InitDoubleTime ();\n}\n\n\n//==============================================================================\n// WINDOWS CRAP\n//==============================================================================\n\n#define MAX_NUM_ARGVS\t50\n\nint\t\targc;\nchar\t*argv[MAX_NUM_ARGVS];\nstatic char exename[1024] = {0};\n\nvoid ParseCommandLine (char *lpCmdLine) \n{\n    int i;\n\targc = 1;\n\targv[0] = exename;\n\n\tif(!(i = GetModuleFileName(NULL, exename, sizeof(exename)-1))) // here we get loong string, with full path\n\t\texename[0] = 0; // oh, something bad\n\telse \n\t{\n\t\texename[i] = 0; // ensure null terminator\n\t\tstrlcpy(exename, COM_SkipPath(exename), sizeof(exename));\n\t}\n\n\twhile (*lpCmdLine && (argc < MAX_NUM_ARGVS))\n\t{\n\t\twhile (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126)))\n\t\t\tlpCmdLine++;\n\n\t\tif (*lpCmdLine)\n\t\t{\n\t\t\tif (*lpCmdLine == '\\\"')\n\t\t\t{\n\t\t\t\tlpCmdLine++;\n\n\t\t\t\targv[argc] = lpCmdLine;\n\t\t\t\targc++;\n\n\t\t\t\twhile (*lpCmdLine && *lpCmdLine != '\\\"') // this include chars less that 32 and greate than 126... is that evil?\n\t\t\t\t\tlpCmdLine++;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\targv[argc] = lpCmdLine;\n\t\t\t\targc++;\n\n\t\t\t\twhile (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126)))\n\t\t\t\t\tlpCmdLine++;\n\t\t\t}\n\n\t\t\tif (*lpCmdLine)\n\t\t\t{\n\t\t\t\t*lpCmdLine = 0;\n\t\t\t\tlpCmdLine++;\n\t\t\t}\n\t\t}\n\t}\n}\n\n#define QW_URL_ROOT_REGKEY         \"Software\\\\Classes\\\\qw\"\n#define QW_URL_OPEN_CMD_REGKEY     QW_URL_ROOT_REGKEY\"\\\\shell\\\\Open\\\\Command\"\n#define QW_URL_DEFAULTICON_REGKEY  QW_URL_ROOT_REGKEY\"\\\\DefaultIcon\"\n\n//\n// Checks if this client is the default qw protocol handler.\n//\nstatic qbool Sys_CheckIfQWProtocolHandler(void)\n{\n\tDWORD type;\n\tchar reg_path[1024];\n\tDWORD len = (DWORD) sizeof(reg_path);\n\tHKEY hk;\n\n\tif (RegOpenKey(HKEY_CURRENT_USER, QW_URL_OPEN_CMD_REGKEY, &hk) != 0) {\n\t\treturn false;\n\t}\n\n\t// Get the size we need to read.\n\tif (RegQueryValueEx(hk, NULL, 0, &type, (BYTE *) reg_path, &len) == ERROR_SUCCESS) {\n\t\tchar expanded_reg_path[MAX_PATH];\n\n\t\t// Expand any environment variables in the reg value so that we get a real path to compare with.\n\t\tExpandEnvironmentStrings(reg_path, expanded_reg_path, sizeof(expanded_reg_path));\n\n\t\t#if 0\n\t\t// This checks if the current exe is associated with, otherwise it will prompt the user\n\t\t// a bit more \"in the face\" if the user has more than one ezquake version.\n\t\t{\n\t\t\tchar exe_path[MAX_PATH];\n\t\t\n\t\t\t// Get the long path of the current process.\n\t\t\t// C:\\Program Files\\Quake\\ezquake-gl.exe\n\t\t\tSys_GetFullExePath(exe_path, sizeof(exe_path), true);\n\n\t\t\tif (strstri(reg_path, exe_path) || strstri(expanded_reg_path, exe_path))\n\t\t\t{\n\t\t\t\t// This exe is associated with the qw:// protocol, return true.\n\t\t\t\tCloseHandle(hk);\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Get the short path and check if that matches instead.\n\t\t\t// C:\\Program~1\\Quake\\ezquake-gl.exe\n\t\t\tSys_GetFullExePath(exe_path, sizeof(exe_path), false);\n\n\t\t\tif (strstri(reg_path, exe_path) || strstri(expanded_reg_path, exe_path))\n\t\t\t{\n\t\t\t\tCloseHandle(hk);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\t#else\n\t\t// Only check if ezquake is in the string that associates with the qw:// protocol\n\t\t// so if you have several ezquake exes it won't bug you if you just switch between those\n\t\t// (Only one will be registered as the protocol handler though ofcourse).\n\n\t\tif (strstri(reg_path, \"ezquake\"))\n\t\t{\n\t\t\tCloseHandle(hk);\n\t\t\treturn true;\n\t\t}\n\n\t\t#endif\n\t}\n\n\tCloseHandle(hk);\n\treturn false;\n}\n\nvoid Sys_CheckQWProtocolHandler(void)\n{\n\t// Verify that ezQuake is associated with the QW:// protocl handler.\n\t//\n\t#define INITIAL_CON_WIDTH 35\n\textern cvar_t cl_verify_qwprotocol;\n\n\tif (cl_verify_qwprotocol.integer >= 2) {\n\t\t// Always register the qw:// protocol.\n\t\tCbuf_AddText(\"register_qwurl_protocol quiet\\n\");\n\t} else if (cl_verify_qwprotocol.integer == 1 && !Sys_CheckIfQWProtocolHandler()) {\n\t\t// Check if the running exe is the one associated with the qw:// protocol.\n\t\tCom_PrintVerticalBar(INITIAL_CON_WIDTH);\n\t\tCom_Printf(\"\\n\");\n\t\tCom_Printf(\"ezQuake is not associated with the \");\n\t\tCom_Printf(\"\\x02QW:// protocol. \");\n\t\tCom_Printf(\"Register it using\"); \n\t\tCom_Printf(\"\\x02/register_qwurl_protocol\\n\");\n\t\tCom_Printf(\"(set\");\n\t\tCom_Printf(\"\\x02 cl_verify_qwprotocol 0 \");\n\t\tCom_Printf(\"to hide this warning)\\n\");\n\t\tCom_PrintVerticalBar(INITIAL_CON_WIDTH);\n\t\tCom_Printf(\"\\n\\n\");\n\t}\n}\n\nvoid Sys_RegisterQWURLProtocol_f(void)\n{\n\t//\n\t// Note!\n\t// HKEY_CLASSES_ROOT is a \"merged view\" of both: HKEY_LOCAL_MACHINE\\Software\\Classes \n\t// and HKEY_CURRENT_USER\\Software\\Classes. \n\t// User specific settings has priority over machine settings.\n\t//\n\t// If you try to write to HKEY_CLASSES_ROOT directly, it will default to\n\t// trying to write to the machine specific settings. If the user isn't\n\t// admin this will fail. On Vista this requires UAC usage.\n\t// Because of this, we always write specifically to \"HKEY_CURRENT_USER\\Software\\Classes\"\n\t//\n\tqbool quiet = Cmd_Argc() == 2 && !strcmp(Cmd_Argv(1), \"quiet\");\n\n\tHKEY keyhandle;\n\tchar exe_path[MAX_PATH];\n\n\tSys_GetFullExePath(exe_path, sizeof(exe_path), true);\n\n\t//\n\t// HKCU\\qw\\shell\\Open\\Command\n\t//\n\t{\n\t\tchar open_cmd[1024];\n\t\tsnprintf(open_cmd, sizeof(open_cmd), \"\\\"%s\\\" +qwurl %%1\", exe_path);\n\n\t\t// Open / Create the key.\n\t\tif (RegCreateKeyEx(HKEY_CURRENT_USER,\t\t// A handle to an open subkey.\n\t\t\t\t\t\tQW_URL_OPEN_CMD_REGKEY,\t\t// Subkey.\n\t\t\t\t\t\t0,\t\t\t\t\t\t\t// Reserved, must be 0.\n\t\t\t\t\t\tNULL,\t\t\t\t\t\t// Class, ignored.\n\t\t\t\t\t\tREG_OPTION_NON_VOLATILE,\t// Save the change to disk.\n\t\t\t\t\t\tKEY_WRITE,\t\t\t\t\t// Access rights.\n\t\t\t\t\t\tNULL,\t\t\t\t\t\t// Security attributes (NULL means default, inherited from direct parent).\n\t\t\t\t\t\t&keyhandle,\t\t\t\t\t// Handle to the created key.\n\t\t\t\t\t\tNULL))\t\t\t\t\t\t// Don't care if the key existed or not.\n\t\t{\n\t\t\tCom_Printf_State(PRINT_WARNING, \"Could not create HKCU\\\\\"QW_URL_OPEN_CMD_REGKEY\"\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Set the key value.\n\t\tif (RegSetValueEx(keyhandle, NULL, 0, REG_SZ, (BYTE *) open_cmd,  strlen(open_cmd) * sizeof(char)))\n\t\t{\n\t\t\tCom_Printf_State(PRINT_WARNING, \"Could not set HKCU\\\\\"QW_URL_OPEN_CMD_REGKEY\"\\\\@\\n\");\n\t\t\tRegCloseKey(keyhandle);\n\t\t\treturn;\n\t\t}\n\n\t\tRegCloseKey(keyhandle);\n\t}\n\n\t//\n\t// HKCU\\qw\\DefaultIcon\n\t//\n\t{\n\t\tchar default_icon[1024];\n\t\tsnprintf(default_icon, sizeof(default_icon), \"\\\"%s\\\",1\", exe_path);\n\n\t\t// Open / Create the key.\n\t\tif (RegCreateKeyEx(HKEY_CURRENT_USER, QW_URL_DEFAULTICON_REGKEY, \n\t\t\t0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &keyhandle, NULL))\n\t\t{\n\t\t\tCom_Printf_State(PRINT_WARNING, \"Could not create HKCU\\\\\"QW_URL_OPEN_CMD_REGKEY\"\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Set the key value.\n\t\tif (RegSetValueEx(keyhandle, NULL, 0, REG_SZ, (BYTE *) default_icon, strlen(default_icon) * sizeof(char)))\n\t\t{\n\t\t\tCom_Printf_State(PRINT_WARNING, \"Could not set HKCU\\\\\"QW_URL_OPEN_CMD_REGKEY\"\\\\@\\n\");\n\t\t\tRegCloseKey(keyhandle);\n\t\t\treturn;\n\t\t}\n\n\t\tRegCloseKey(keyhandle);\n\t}\n\n\t//\n\t// HKCU\\qw\n\t//\n\t{\n\t\tchar protocol_name[] = \"URL:QW Protocol\";\n\n\t\t// Open / Create the key.\n\t\tif (RegCreateKeyEx(HKEY_CURRENT_USER, QW_URL_ROOT_REGKEY, \n\t\t\t0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &keyhandle, NULL))\n\t\t{\n\t\t\tCom_Printf_State(PRINT_WARNING, \"Could not create HKCU\\\\qw\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Set the protocol name.\n\t\tif (RegSetValueEx(keyhandle, NULL, 0, REG_SZ, (BYTE *) protocol_name, strlen(protocol_name) * sizeof(char)))\n\t\t{\n\t\t\tCom_Printf_State(PRINT_WARNING, \"Could not set HKCU\\\\qw\\\\@\\n\");\n\t\t\tRegCloseKey(keyhandle);\n\t\t\treturn;\n\t\t}\n\n\t\tif (RegSetValueEx(keyhandle, \"URL Protocol\", 0, REG_SZ, (BYTE *) \"\", sizeof(char)))\n\t\t{\n\t\t\tCom_Printf_State(PRINT_WARNING, \"Could not set HKCU\\\\qw\\\\URL Protocol\\n\");\n\t\t\tRegCloseKey(keyhandle);\n\t\t\treturn;\n\t\t}\n\n\t\tRegCloseKey(keyhandle);\n\n\t\tif (!quiet) {\n\t\t\tCom_Printf_State(PRINT_WARNING, \"qw:// protocol registered\\n\");\n\t\t}\n\t}\n}\n\n//============================================================================\nHHOOK hMsgBoxHook;\n\nLRESULT CALLBACK QWURLProtocolButtonsHookProc(int nCode, WPARAM wParam, LPARAM lParam)\n{\n\tHWND hwnd;\n\tHWND hwndYESButton;\n\tHWND hwndNOButton;\n\tHWND hwndCANCELButton;\n\n\tif(nCode < 0)\n\t\treturn CallNextHookEx(hMsgBoxHook, nCode, wParam, lParam);\n\n\tswitch(nCode)\n\t{\n\t\tcase HCBT_ACTIVATE:\n\t\t{\n\t\t\t#define BUTTON_HEIGHT\t\t23\n\t\t\t#define YES_BUTTON_WIDTH\t110\n\t\t\t#define NO_BUTTON_WIDTH\t\t110\n\t\t\t#define CANCEL_BUTTON_WIDTH\t140\n\t\t\t#define BUTTON_GAP\t\t\t5\n\t\t\t#define BOTTOM_OFFSET\t\t10\n\t\t\t#define ALL_BUTTON_WIDTH\t(YES_BUTTON_WIDTH + BUTTON_GAP + NO_BUTTON_WIDTH + BUTTON_GAP + CANCEL_BUTTON_WIDTH)\n\n\t\t\tRECT rectWindow;\n\t\t\tRECT rectClient;\n\t\t\tint y_pos = 0;\n\t\t\tint x_pos = 0;\n\n\t\t\t// Get handle to the message box.\n\t\t\thwnd = (HWND)wParam;\n\t\t\tGetClientRect(hwnd, &rectClient);\n\t\t\tGetWindowRect(hwnd, &rectWindow);\n\t\t\t\n\t\t\t// Place the buttons at the bottom of the window.\n\t\t\ty_pos = rectClient.bottom - BOTTOM_OFFSET - BUTTON_HEIGHT;\n\n\t\t\t// Resize the messagebox to at least fit the buttons.\n\t\t\tif (rectClient.right < ALL_BUTTON_WIDTH)\n\t\t\t{\n\t\t\t\t// TODO: Hmm does this work properly? Got some weird behaviour where the control wouldn't draw if the cy argument wasn't a constant.\n\t\t\t\tSetWindowPos(hwnd, HWND_TOP, 20, 20, \n\t\t\t\t\t(ALL_BUTTON_WIDTH + (BUTTON_GAP * 2)), \n\t\t\t\t\t(int)(rectWindow.bottom - rectWindow.top), \n\t\t\t\t\t0);\n\t\t\t}\n\n\t\t\t// Center the buttons.\n\t\t\tx_pos = Q_rint((rectClient.right - ALL_BUTTON_WIDTH) / 2.0);\n\t\t\t\n\t\t\t// Modify the Yes button.\n\t\t\thwndYESButton = GetDlgItem(hwnd, IDYES);\n\t\t\tSetWindowText(hwndYESButton, _T(\"Set as default\"));\n\t\t\tSetWindowPos(hwndYESButton, HWND_TOP, x_pos, y_pos, YES_BUTTON_WIDTH, BUTTON_HEIGHT, 0);\n\t\t\tShowWindow(hwndYESButton, SW_SHOWNORMAL);\n\t\t\tx_pos += YES_BUTTON_WIDTH + BUTTON_GAP;\n\n\t\t\t// No button.\n\t\t\thwndNOButton = GetDlgItem(hwnd, IDNO);\n\t\t\tSetWindowText(hwndNOButton, _T(\"Ask me later\"));\n\t\t\tSetWindowPos(hwndNOButton, HWND_TOP, x_pos, y_pos, NO_BUTTON_WIDTH, BUTTON_HEIGHT, 0);\n\t\t\tShowWindow(hwndNOButton, SW_SHOWNORMAL);\n\t\t\tx_pos += NO_BUTTON_WIDTH + BUTTON_GAP;\n\n\t\t\t// Cancel button.\n\t\t\thwndCANCELButton = GetDlgItem(hwnd, IDCANCEL);\n\t\t\tSetWindowText(hwndCANCELButton, _T(\"Don't show me this again\"));\n\t\t\tSetWindowPos(hwndCANCELButton, HWND_TOP, x_pos, y_pos, CANCEL_BUTTON_WIDTH, BUTTON_HEIGHT, 0);\n\t\t\tShowWindow(hwndCANCELButton, SW_SHOWNORMAL);\n\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\treturn CallNextHookEx(hMsgBoxHook, nCode, wParam, lParam);\n}\n\nint MsgBoxEx(HWND hwnd, TCHAR *szText, TCHAR *szCaption, HOOKPROC hookproc, UINT uType)\n{\n\tint retval;\n\n\t// Install a window hook, so we can intercept the message-box\n\t// creation, and customize it\n\tif (hookproc != NULL)\n\t{\n\t\thMsgBoxHook = SetWindowsHookEx(\n\t\t\tWH_CBT, \n\t\t\thookproc, \n\t\t\tNULL, \n\t\t\tGetCurrentThreadId()\t\t// Only install for THIS thread!!!\n\t\t\t);\n\t}\n\n\t// Display a standard message box.\n\tretval = MessageBox(hwnd, szText, szCaption, uType);\n\n\t// Remove the window hook\n\tif (hookproc != NULL)\n\t{\n\t\tUnhookWindowsHookEx(hMsgBoxHook);\n\t}\n\n\treturn retval;\n}\n\ntypedef enum qwurl_regkey_e\n{\n\tQWURL_DONT_ASK = 0,\n\tQWURL_ASK = 1,\n\tQWURL_ASK_IF_OTHER = 2\n} qwurl_regkey_t;\n\n//\n// Sets the registry that decides if the QW URL dialog should be shown at startup or not.\n//\nvoid WinSetCheckQWURLRegKey(qwurl_regkey_t val)\n{\n\t#define EZQUAKE_REG_SUBKEY\t\t\t\"Software\\\\ezQuake\"\n\t#define EZQUAKE_REG_QWPROTOCOLKEY\t\"AskForQWProtocol\"\n\n\tHKEY keyhandle;\n\n\t//\n\t// HKCU\\Software\\ezQuake\n\t//\n\t{\n\t\tDWORD dval = (DWORD)val;\n\n\t\t// Open / Create the key.\n\t\tif (RegCreateKeyEx(HKEY_CURRENT_USER, EZQUAKE_REG_SUBKEY, \n\t\t\t0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &keyhandle, NULL))\n\t\t{\n\t\t\tCom_Printf_State(PRINT_WARNING, \"Could not create HKCU\\\\\"EZQUAKE_REG_SUBKEY\"\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Set the key value.\n\t\tif (RegSetValueEx(keyhandle, EZQUAKE_REG_QWPROTOCOLKEY, 0, REG_DWORD, (BYTE *)&dval, sizeof(DWORD)))\n\t\t{\n\t\t\tCom_Printf_State(PRINT_WARNING, \"Could not set HKCU\\\\\"EZQUAKE_REG_SUBKEY\"\\\\\"EZQUAKE_REG_QWPROTOCOLKEY\"\\n\");\n\t\t\tRegCloseKey(keyhandle);\n\t\t\treturn;\n\t\t}\n\n\t\tRegCloseKey(keyhandle);\n\t}\n}\n\n//\n// Gets the registry value for the \"HKCU\\Software\\ezQuake\\AskForQWProtocol key\"\n//\nqwurl_regkey_t WinGetCheckQWURLRegKey(void)\n{\n\tHKEY keyhandle = NULL;\n\tDWORD returnval = QWURL_ASK;\n\n\t//\n\t// HKCU\\Software\\ezQuake\n\t//\n\tdo\n\t{\t\t\n\t\tDWORD val;\n\t\tDWORD type = REG_DWORD;\n\t\tDWORD size = sizeof(DWORD);\n\t\tLONG returnstatus;\n\n\t\t// Open the key.\n\t\tif ((returnstatus = RegOpenKeyEx(HKEY_CURRENT_USER, EZQUAKE_REG_SUBKEY, 0, KEY_ALL_ACCESS, &keyhandle)) != ERROR_SUCCESS)\n\t\t{\n\t\t\tCom_Printf_State(PRINT_WARNING, \"Could not open HKCU\\\\\"EZQUAKE_REG_SUBKEY\", %l\\n\", returnstatus);\n\t\t\tbreak;\n\t\t}\n\n\n\t\t// Set the key value.\n\t\tif (RegQueryValueEx(keyhandle, EZQUAKE_REG_QWPROTOCOLKEY, 0, &type, (BYTE *)&val, &size))\n\t\t{\n\t\t\tCom_Printf_State(PRINT_WARNING, \"Could not set HKCU\\\\\"EZQUAKE_REG_SUBKEY\"\\\\\"EZQUAKE_REG_QWPROTOCOLKEY\"\\n\");\n\t\t\tbreak;\n\t\t}\n\n\t\treturnval = (qwurl_regkey_t)val;\n\t\tbreak;\t\n\t}\n\twhile (0);\n\n\tif (keyhandle)\n\t{\n\t\tRegCloseKey(keyhandle);\n\t}\n\n\treturn returnval;\n}\n\n//\n// Check if we're the registered QW:// protocol handler, if not show a messagebox\n// asking the user what to do. Returns false if the user wants to turn this check off.\n//\nqbool WinCheckQWURL(void)\n{\n\tint retval;\n\n\t// Check the registry if we should ask at all. By relying on this\n\t// instead of the cfg, the user doesn't have to do a cfg_save after answering\n\t// \"Don't ask me this again\" to keep from getting bugged :D\n\tqwurl_regkey_t regstatus = WinGetCheckQWURLRegKey();\n\n\tswitch (regstatus)\n\t{\n\t\tcase QWURL_ASK:\n\t\t\tbreak;\n\n\t\tcase QWURL_ASK_IF_OTHER:\n\t\t\tbreak;\n\n\t\tcase QWURL_DONT_ASK:\n\t\t\t// Get out of here!!!\n\t\t\treturn true;\n\t}\n\n\tif (Sys_CheckIfQWProtocolHandler()) {\n\t\treturn true;\n\t}\n\t\n\t// Instead of creating a completly custom messagebox (which is a major pain)\n\t// just show a normal one, but replace the text on the buttons using event hooking.\n\tretval = MsgBoxEx(NULL, \n\t\t\t\t\t\"The current ezQuake client is not associated with the qw:// protocol,\\n\"\n\t\t\t\t\t\"which lets you launch ezQuake by opening qw:// URLs (.qtv files).\\n\\n\"\n\t\t\t\t\t\"Do you want to associate ezQuake with the qw:// protocol?\",\n\t\t\t\t\t\"QW URL Protocol\", QWURLProtocolButtonsHookProc, MB_YESNOCANCEL | MB_ICONWARNING);\n\n\tswitch (retval)\n\t{\n\t\tcase IDYES :\n\t\t\tSys_RegisterQWURLProtocol_f();\n\t\t\tWinSetCheckQWURLRegKey(QWURL_ASK);\n\t\t\treturn true;\n\n\t\tcase IDNO :\n\t\t\t// Do nothing!\n\t\t\treturn true;\n\n\t\tcase IDCANCEL :\n\t\t\t// User doesn't want to be bugged anymore.\n\t\t\tWinSetCheckQWURLRegKey(QWURL_DONT_ASK);\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n//\n// Application entry point.\n//\nint WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) \n{\n\tint memsize, i;\n\tdouble time, oldtime, newtime;\n\tMEMORYSTATUS lpBuffer;\n\n\tglobal_hInstance = hInstance;\n\n\tWinCheckOSInfo();\n\n\tParseCommandLine(lpCmdLine);\n\n\t// Check if we're the registered QW url protocol handler.\n\tif (!WinCheckQWURL() && ((argc + 3) < MAX_NUM_ARGVS))\n\t{\n\t\t// User doesn't want to be bothered again.\n\t\targv[argc++] = \"+cl_verify_qwprotocol\";\n\t\targv[argc++] = \"0\";\n\t}\n\n\t// We need to check some parms before Host_Init is called\n\tCOM_InitArgv (argc, argv);\n\n\t// Let me use -condebug C:\\condebug.log before Quake FS init, so I get ALL messages before quake fully init\n\tif ((i = COM_CheckParm(cmdline_param_console_debug)) && i < COM_Argc() - 1)\n\t{\n\t\textern FILE *qconsole_log;\n\t\tchar *s = COM_Argv(i + 1);\n\t\tif (*s != '-' && *s != '+')\n\t\t\tqconsole_log = fopen(s, \"a\");\n\t}\n\n\tSys_DisableScreenSaving();\n\n\t// Take the greater of all the available memory or half the total memory,\n\t// but at least 8 Mb and no more than 32 Mb, unless they explicitly request otherwise\n\tlpBuffer.dwLength = sizeof(MEMORYSTATUS);\n\tGlobalMemoryStatus (&lpBuffer);\n\n\t// Maximum of 2GiB to work around signed int.\n\tif(lpBuffer.dwAvailPhys > 0x7FFFFFFF)\n\t\tlpBuffer.dwAvailPhys = 0x7FFFFFFF;\n\n\tif(lpBuffer.dwTotalPhys > 0x7FFFFFFF)\n\t\tlpBuffer.dwTotalPhys = 0x7FFFFFFF;\n\n\tmemsize = lpBuffer.dwAvailPhys;\n\n\tif (memsize < MINIMUM_WIN_MEMORY)\n\t\tmemsize = MINIMUM_WIN_MEMORY;\n\n\tif (memsize < (lpBuffer.dwTotalPhys >> 1))\n\t\tmemsize = lpBuffer.dwTotalPhys >> 1;\n\n\tif (memsize > MAXIMUM_WIN_MEMORY)\n\t\tmemsize = MAXIMUM_WIN_MEMORY;\n\n\ttevent = CreateEvent (NULL, FALSE, FALSE, NULL);\n\tif (!tevent)\n\t\tSys_Error (\"Couldn't create event\");\n\n\tSys_Init_();\n\n\tSys_Printf (\"Host_Init\\n\");\n\tHost_Init (argc, argv, memsize);\n\n\toldtime = Sys_DoubleTime ();\n\n    // Main window message loop.\n\twhile (1) \n\t{\n\t\tnewtime = Sys_DoubleTime ();\n\t\ttime = newtime - oldtime;\n\t\tHost_Frame (time);\n\t\toldtime = newtime;\n\n\t}\n    return TRUE;\t/* return success of application */\n}\n\nvoid MakeDirent(sys_dirent *ent, WIN32_FIND_DATA *data)\n{\n    FILETIME ft1;\n\n    strlcpy (ent->fname, data->cFileName, min(strlen(data->cFileName)+1, MAX_PATH_LENGTH));\n\n\tent->size = (data->nFileSizeHigh > 0) ? 0xffffffff : data->nFileSizeLow;\n\n    FileTimeToLocalFileTime(&data->ftLastWriteTime, &ft1);\n    FileTimeToSystemTime(&ft1, &ent->time);\n\n\tent->directory = (data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;\n\tent->hidden = (data->dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) ? 1 : 0;\n}\n\nSysDirEnumHandle Sys_ReadDirFirst(sys_dirent *ent)\n{\n    HANDLE search;\n    WIN32_FIND_DATA data;\n\n    search = FindFirstFile(\"*\", &data);\n\n    if (search == (HANDLE)ERROR_INVALID_HANDLE)\n        return 0;\n\n    MakeDirent(ent, &data);\n\n    return (SysDirEnumHandle) search;\n}\n\nint Sys_ReadDirNext(SysDirEnumHandle search, sys_dirent *ent)\n{\n    WIN32_FIND_DATA data;\n    if (!FindNextFile((HANDLE)search, &data))\n        return 0;\n\n    MakeDirent(ent, &data);\n    return 1;\n}\n\nvoid Sys_ReadDirClose(SysDirEnumHandle search)\n{\n    FindClose((HANDLE)search);\n}\n\nint Sys_chdir (const char *path)\n{\n    if (SetCurrentDirectory(path))\n        return 1;\n    else\n        return 0;\n}\n\nchar * Sys_getcwd (char *buf, int bufsize)\n{\n    if (GetCurrentDirectory(bufsize, buf))\n        return buf;\n    else\n        return NULL;\n}\n\nchar *Sys_fullpath(char *absPath, const char *relPath, int maxLength)\n{\n    return _fullpath(absPath, relPath, maxLength);\n} \n\nvoid Sys_GetFullExePath(char *path, unsigned int path_length, int long_name)\n{\n\tGetModuleFileName(GetModuleHandle(NULL), path, path_length);\n\n\tif (!long_name)\n\t{\n\t\tGetShortPathName(path, path, path_length);\n\t}\n}\n\n#define EZQUAKE_MAILSLOT\t\"\\\\\\\\.\\\\mailslot\\\\ezquake\"\n#define MAILSLOT_BUFFERSIZE 1024\n\nHANDLE ezquake_server_mailslot;\n\nvoid Sys_InitIPC(void)\n{\t\n\tezquake_server_mailslot = CreateMailslot( \n\t\t\t\t\t\t\t  EZQUAKE_MAILSLOT,\t\t\t\t\t// Mailslot name\n\t\t\t\t\t\t\t  MAILSLOT_BUFFERSIZE,              // Input buffer size \n\t\t\t\t\t\t\t  0,\t\t\t\t\t\t\t\t// Timeout\n\t\t\t\t\t\t\t  NULL);\t\t\t\t\t\t\t// Default security attribute \n}\n\nvoid Sys_CloseIPC(void)\n{\n\tCloseHandle(ezquake_server_mailslot);\n}\n\nvoid Sys_ReadIPC(void)\n{\n\tchar buf[MAILSLOT_BUFFERSIZE] = {0};\n\tDWORD num_bytes_read = 0;\n\n\tif (INVALID_HANDLE_VALUE == ezquake_server_mailslot)\n\t{\n\t\treturn;\n\t}\n\n\t// Read client message\n\tReadFile( ezquake_server_mailslot,\t// Handle to mailslot \n\t\t\t\tbuf,\t\t\t\t\t\t// Buffer to receive data \n\t\t\t\tsizeof(buf),\t\t\t\t// Size of buffer \n\t\t\t\t&num_bytes_read,\t\t\t// Number of bytes read \n\t\t\t\tNULL);\t\t\t\t\t\t// Not overlapped I/O \n\tif (num_bytes_read <= 0)\n\t{\n\t\treturn;\n\t}\n\n\tCOM_ParseIPCData(buf, num_bytes_read);\n}\n\nunsigned int Sys_SendIPC(const char *buf)\n{\n\tHANDLE hMailslot;\n\tDWORD num_bytes_written;\n\tqbool result = false;\n\n\t// Connect to the server mailslot using CreateFile()\n\thMailslot = CreateFile( EZQUAKE_MAILSLOT,\t\t// Mailslot name \n\t\t\t\t\t\t\tGENERIC_WRITE,\t\t\t// Mailslot write only \n\t\t\t\t\t\t\tFILE_SHARE_READ,\t\t// Required for mailslots\n\t\t\t\t\t\t\tNULL,\t\t\t\t\t// Default security attributes\n\t\t\t\t\t\t\tOPEN_EXISTING,\t\t\t// Opens existing mailslot \n\t\t\t\t\t\t\tFILE_ATTRIBUTE_NORMAL,\t// Normal attributes \n\t\t\t\t\t\t\tNULL);\t\t\t\t\t// No template file \n\n\t// Send the message to server.\n\tresult = WriteFile( hMailslot,\t\t\t// Handle to mailslot \n\t\t\t\t\t\tbuf,\t\t\t\t// Buffer to write from \n\t\t\t\t\t\tstrlen(buf) + 1,\t// Number of bytes to write, include the NULL\n\t\t\t\t\t\t&num_bytes_written,\t// Number of bytes written \n\t\t\t\t\t\tNULL);\t\t\t\t// Not overlapped I/O\n\n\t CloseHandle(hMailslot);\n\t return result;\n}\n\n/********************************** SEMAPHORES *******************************/\n/* Sys_Sem*() returns 0 on success; on error, -1 is returned */\nint Sys_SemInit(sem_t *sem, int value, int max_value) \n{\n\t*sem = CreateSemaphore(NULL, value, max_value, NULL);// None named Semaphore\n\tif (*sem == NULL)\n\t\treturn -1;\n\treturn 0;\n}\n\nint Sys_SemWait(sem_t *sem) \n{\n\tif (WaitForSingleObject(*sem, INFINITE) == WAIT_FAILED)\n\t\treturn -1;\n\treturn 0;\n}\n\nint Sys_SemPost(sem_t *sem)\n{\n\tif (ReleaseSemaphore(*sem, 1, NULL))\n\t\treturn 0;\n\treturn -1;\n}\n\nint Sys_SemDestroy(sem_t *sem)\n{\n\tif (CloseHandle(*sem))\n\t\treturn 0;\n\treturn -1;\n}\n\n// Timer Resolution Requesting\nvoid Sys_TimerResolution_InitSession(timerresolution_session_t * s)\n{\n\ts->set = false;\n\ts->interval = 0;\n}\n\nvoid Sys_TimerResolution_RequestMinimum(timerresolution_session_t * s)\n{\n\tTIMECAPS t;\n\tif (timeGetDevCaps(&t, sizeof(TIMECAPS)) == TIMERR_NOERROR) {\n\t\tif (timeBeginPeriod(t.wPeriodMin) == TIMERR_NOERROR) {\n\t\t\ts->set = true;\n\t\t\ts->interval = t.wPeriodMin;\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"Error: Requesting minimum timer resolution failed\\n\");\n\t\t}\n\t}\n\telse {\n\t\tCom_Printf(\"Error: Failed querying timer device\\n\");\n\t}\n}\n\nvoid Sys_TimerResolution_Clear(timerresolution_session_t * s)\n{\n\tif (s->set) {\n\t\tif (timeEndPeriod(s->interval) == TIMERR_NOERROR) {\n\t\t\ts->set = false;\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"Error: Failed to clear timer resolution\\n\");\n\t\t}\n\t}\n}\n\n//========================================================================\n\nint Sys_Script (const char *path, const char *args)\n{\n\tSTARTUPINFO\t\t\tsi;\n\tPROCESS_INFORMATION\tpi;\n\tchar cmdline[1024], curdir[MAX_OSPATH];\n\n\tmemset (&si, 0, sizeof(si));\n\tsi.cb = sizeof(si);\n\tsi.dwFlags = STARTF_USESHOWWINDOW;\n\tsi.wShowWindow = SW_SHOWMINNOACTIVE;\n\n\tGetCurrentDirectory(sizeof(curdir), curdir);\n\n\n\tsnprintf(cmdline, sizeof(cmdline), \"%s\\\\sh.exe %s.qws %s\", curdir, path, args);\n\tstrlcat(curdir, va(\"\\\\%s\", fs_gamedir+2), MAX_OSPATH);\n\n\treturn CreateProcess (NULL, cmdline, NULL, NULL,\n\t                      FALSE, 0/*DETACHED_PROCESS CREATE_NEW_CONSOLE*/ , NULL, curdir, &si, &pi);\n}\n\n//=========================================================================\n\nDL_t Sys_DLOpen (const char *path)\n{\n\treturn LoadLibrary (path);\n}\n\nqbool Sys_DLClose (DL_t dl)\n{\n\treturn FreeLibrary (dl);\n}\n\nvoid *Sys_DLProc (DL_t dl, const char *name)\n{\n\treturn (void *) GetProcAddress (dl, name);\n}\n\n//===========================================================================\n\nvoid Sys_CloseLibrary(dllhandle_t *lib)\n{\n\tFreeLibrary((HMODULE)lib);\n}\n\ndllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs)\n{\n\tint i;\n\tHMODULE lib;\n\n\tlib = LoadLibrary(name);\n\tif (!lib)\n\t\treturn NULL;\n\n\tfor (i = 0; funcs[i].name; i++)\n\t{\n\t\t*funcs[i].funcptr = GetProcAddress(lib, funcs[i].name);\n\t\tif (!*funcs[i].funcptr)\n\t\t\tbreak;\n\t}\n\tif (funcs[i].name)\n\t{\n\t\tSys_CloseLibrary((dllhandle_t*)lib);\n\t\tlib = NULL;\n\t}\n\n\treturn (dllhandle_t*)lib;\n}\n\nvoid *Sys_GetAddressForName(dllhandle_t *module, const char *exportname)\n{\n\tif (!module)\n\t\treturn NULL;\n\treturn GetProcAddress((HINSTANCE)module, exportname);\n}\n\n// ===========================================================================\n\n// Fakes a backspace character to take Windows out of deadkey mode\n// See: https://github.com/ezQuake/ezquake-source/issues/101\n// Bug is due to SDL not handling deadkeys correctly - this can probably\n//     be removed in future, once library updated\n// See: https://github.com/flibitijibibo/FNA-MGHistory/issues/277\nvoid Sys_CancelDeadKey (void)\n{\n\tINPUT input[2] = { { 0 } };\n\tinput[0].type = input[1].type = INPUT_KEYBOARD;\n\tinput[0].ki.wVk = input[1].ki.wVk = VK_BACK;\n\tinput[1].ki.dwFlags = KEYEVENTF_KEYUP;\n\tSendInput (2, input, sizeof (INPUT));\n}\n\nconst char* Sys_FontsDirectory(void)\n{\n\tstatic char path[MAX_OSPATH];\n\n\tif (!SHGetSpecialFolderPath(NULL, path, CSIDL_FONTS, 0)) {\n\t\tpath[0] = 0;\n\t}\n\n\treturn path;\n}\n\nconst char* Sys_HomeDirectory(void)\n{\n\tstatic char path[MAX_OSPATH];\n\n\t// gets \"C:\\documents and settings\\johnny\\my documents\" path\n\tif (!SHGetSpecialFolderPath(0, path, CSIDL_PERSONAL, 0)) {\n\t\tpath[0] = 0;\n\t}\n\n\t// <Cokeman> yea, but it shouldn't be in My Documents\n\t// <Cokeman> it should be in the application data dir\n\t// c:\\documents and settings\\<user>\\application data\n\t//if (!SHGetSpecialFolderPath(0, path, CSIDL_APPDATA, 0)) {\n\t//\tpath[0] = 0;\n\t//}\n\n\treturn path;\n}\n"
  },
  {
    "path": "src/teamplay.c",
    "content": "/*\nCopyright (C) 2000-2003       Anton Gavrilov, A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n    $Id: teamplay.c,v 1.96 2007-10-23 08:51:02 himan Exp $\n*/\n\n#include <time.h>\n#include <string.h>\n#include <limits.h>\n#include \"quakedef.h\"\n#include \"ignore.h\"\n#include \"gl_model.h\"\n#include \"teamplay.h\"\n#include \"rulesets.h\"\n#include \"pmove.h\"\n#include \"stats_grid.h\"\n#include \"utils.h\"\n#include \"qsound.h\"\n#include \"tp_msgs.h\"\n\n#define CASE_PLAYER_COUNT_ALLOWED(c, fn) \\\n\tcase c: \\\n\t\tif ((cl.fpd & FPD_ENABLE_PLAYER_COUNT)) { \\\n\t\t\tmacro_string = fn(); \\\n\t\t\tbreak; \\\n\t\t} else { \\\n\t\t\tgoto do_default; \\\n\t\t}\n\nvoid OnChangeSkinForcing(cvar_t *var, char *string, qbool *cancel);\nvoid OnChangeColorForcing(cvar_t *var, char *string, qbool *cancel);\nvoid OnChangeSkinAndColorForcing(cvar_t *var, char *string, qbool *cancel);\n\ncvar_t cl_parseSay = {\"cl_parseSay\", \"1\"};\ncvar_t cl_parseFunChars = {\"cl_parseFunChars\", \"1\"};\ncvar_t cl_nofake = {\"cl_nofake\", \"2\"};\ncvar_t tp_loadlocs = {\"tp_loadlocs\", \"1\"};\ncvar_t tp_pointpriorities = {\"tp_pointpriorities\", \"0\"}; // FIXME: buggy\ncvar_t tp_tookpriorities = {\"tp_tookpriorities\", \"0\"};\ncvar_t tp_tooktimeout = {\"tp_tooktimeout\", \"15\"};\ncvar_t tp_pointtimeout = {\"tp_pointtimeout\", \"15\"};\nstatic cvar_t tp_poweruptextstyle = { \"tp_poweruptextstyle\", \"0\" };\n\ncvar_t  cl_teamtopcolor = {\"teamtopcolor\", \"-1\", 0, OnChangeColorForcing};\ncvar_t  cl_teambottomcolor = {\"teambottomcolor\", \"-1\", 0, OnChangeColorForcing};\ncvar_t  cl_enemytopcolor = {\"enemytopcolor\", \"-1\", 0, OnChangeColorForcing};\ncvar_t  cl_enemybottomcolor = {\"enemybottomcolor\", \"-1\", 0, OnChangeColorForcing};\ncvar_t  cl_teamskin = {\"teamskin\", \"\", 0, OnChangeSkinForcing};\ncvar_t  cl_enemyskin = {\"enemyskin\", \"\", 0, OnChangeSkinForcing};\ncvar_t  cl_teamquadskin = {\"teamquadskin\", \"\", 0, OnChangeSkinForcing};\ncvar_t  cl_enemyquadskin = {\"enemyquadskin\", \"\", 0, OnChangeSkinForcing};\ncvar_t  cl_teampentskin = {\"teampentskin\", \"\", 0, OnChangeSkinForcing};\ncvar_t  cl_enemypentskin = {\"enemypentskin\", \"\", 0, OnChangeSkinForcing};\ncvar_t  cl_teambothskin = {\"teambothskin\", \"\", 0, OnChangeSkinForcing};\ncvar_t  cl_enemybothskin = {\"enemybothskin\", \"\", 0, OnChangeSkinForcing};\ncvar_t  cl_teamlock = {\"teamlock\", \"0\", 0, OnChangeSkinAndColorForcing};\n\ncvar_t\ttp_name_axe = {\"tp_name_axe\", \"axe\"};\ncvar_t\ttp_name_sg = {\"tp_name_sg\", \"sg\"};\ncvar_t\ttp_name_ssg = {\"tp_name_ssg\", \"ssg\"};\ncvar_t\ttp_name_ng = {\"tp_name_ng\", \"ng\"};\ncvar_t\ttp_name_sng = {\"tp_name_sng\", \"sng\"};\ncvar_t\ttp_name_gl = {\"tp_name_gl\", \"gl\"};\ncvar_t\ttp_name_rl = {\"tp_name_rl\", \"{&cf13rl&cfff}\"};\ncvar_t\ttp_name_lg = {\"tp_name_lg\", \"{&c2aalg&cfff}\"};\ncvar_t\ttp_name_rlg = {\"tp_name_rlg\", \"{&cf13rl&cfff}{&c2aag&cfff}\"};\ncvar_t\ttp_name_armortype_ra = {\"tp_name_armortype_ra\", \"{&cf00r&cfff}\"};\ncvar_t\ttp_name_armortype_ya = {\"tp_name_armortype_ya\", \"{&cff0y&cfff}\"};\ncvar_t\ttp_name_armortype_ga = {\"tp_name_armortype_ga\", \"{&c0b0g&cfff}\"};\ncvar_t\ttp_name_ra = {\"tp_name_ra\", \"{&cf00ra&cfff}\"};\ncvar_t\ttp_name_ya = {\"tp_name_ya\", \"{&cff0ya&cfff}\"};\ncvar_t\ttp_name_ga = {\"tp_name_ga\", \"{&c0b0ga&cfff}\"};\ncvar_t\ttp_name_quad = {\"tp_name_quad\", \"{&c05fquad&cfff}\"};\ncvar_t\ttp_name_pent = {\"tp_name_pent\", \"{&cf00pent&cfff}\"};\ncvar_t\ttp_name_ring = {\"tp_name_ring\", \"{&cff0ring&cfff}\"};\ncvar_t\ttp_name_suit = {\"tp_name_suit\", \"suit\"};\ncvar_t\ttp_name_shells = {\"tp_name_shells\", \"shells\"};\ncvar_t\ttp_name_nails = {\"tp_name_nails\", \"nails\"};\ncvar_t\ttp_name_rockets = {\"tp_name_rockets\", \"rox\"};\ncvar_t\ttp_name_cells = {\"tp_name_cells\", \"cells\"};\ncvar_t\ttp_name_mh = {\"tp_name_mh\", \"{&c0a0mega&cfff}\"};\ncvar_t\ttp_name_health = {\"tp_name_health\", \"health\"};\ncvar_t\ttp_name_armor = {\"tp_name_armor\", \"armor\"};\ncvar_t\ttp_name_weapon = {\"tp_name_weapon\", \"weapon\"};\ncvar_t\ttp_name_backpack = {\"tp_name_backpack\", \"{&cf2apack&cfff}\"};\ncvar_t\ttp_name_flag = {\"tp_name_flag\", \"flag\"};\ncvar_t\ttp_name_sentry = {\"tp_name_sentry\", \"sentry gun\"};\ncvar_t\ttp_name_disp = {\"tp_name_disp\", \"dispenser\"};\ncvar_t\ttp_name_filter = {\"tp_name_filter\", \"\"};\ncvar_t\ttp_name_rune1 = {\"tp_name_rune1\", \"{&c0f0resistance&cfff}\"};\ncvar_t\ttp_name_rune2 = {\"tp_name_rune2\", \"{&cf00strength&cfff}\"};\ncvar_t\ttp_name_rune3 = {\"tp_name_rune3\", \"{&cff0haste&cfff}\"};\ncvar_t\ttp_name_rune4 = {\"tp_name_rune4\", \"{&c0ffregeneration&cfff}\"};\ncvar_t\ttp_name_teammate = {\"tp_name_teammate\", \"\"};\ncvar_t\ttp_name_enemy = {\"tp_name_enemy\", \"{&cf00enemy&cfff}\"};\ncvar_t\ttp_name_eyes = {\"tp_name_eyes\", \"{&cff0eyes&cfff}\"};\ncvar_t\ttp_name_quaded = {\"tp_name_quaded\", \"{&c05fquaded&cfff}\"};\ncvar_t\ttp_name_pented = {\"tp_name_pented\", \"{&cf00pented&cfff}\"};\ncvar_t\ttp_name_nothing = {\"tp_name_nothing\", \"nothing\"};\ncvar_t\ttp_name_someplace = {\"tp_name_someplace\", \"someplace\"};\ncvar_t\ttp_name_at = {\"tp_name_at\", \"at\"};\ncvar_t\ttp_name_none = {\"tp_name_none\", \"\"};\ncvar_t\ttp_name_separator = {\"tp_name_separator\", \"/\"};\ncvar_t\ttp_weapon_order = {\"tp_weapon_order\", \"78654321\"};\n\ncvar_t\ttp_name_status_green = {\"tp_name_status_green\", \"$G\"};\ncvar_t\ttp_name_status_yellow = {\"tp_name_status_yellow\", \"$Y\"};\ncvar_t\ttp_name_status_red = {\"tp_name_status_red\", \"$R\"};\ncvar_t\ttp_name_status_blue = {\"tp_name_status_blue\", \"$B\"};\ncvar_t\ttp_name_status_white = {\"tp_name_status_white\", \"$W\"};\n\ncvar_t\ttp_need_ra = {\"tp_need_ra\", \"120\"};\ncvar_t\ttp_need_ya = {\"tp_need_ya\", \"80\"};\ncvar_t\ttp_need_ga = {\"tp_need_ga\", \"60\"};\ncvar_t\ttp_need_health = {\"tp_need_health\", \"50\"};\ncvar_t\ttp_need_weapon = {\"tp_need_weapon\", \"87\"};\ncvar_t\ttp_need_rl = {\"tp_need_rl\", \"1\"};\ncvar_t\ttp_need_rockets = {\"tp_need_rockets\", \"5\"};\ncvar_t\ttp_need_cells = {\"tp_need_cells\", \"13\"};\ncvar_t\ttp_need_nails = {\"tp_need_nails\", \"0\"}; // not so important, so let's not have it spam msg need\ncvar_t\ttp_need_shells = {\"tp_need_shells\", \"0\"}; // not so important, so let's not have it spam msg need\n\nstatic qbool suppress;\n\nchar *skinforcing_team = \"\";\n\nvoid TP_FindModelNumbers (void);\nvoid TP_FindPoint (void);\n\nstatic void CountNearbyPlayers(qbool dead);\nchar *Macro_LastTookOrPointed (void);\nchar *Macro_LastTookOrPointed2 (void);\nvoid R_TranslatePlayerSkin (int playernum);\n\n#define\tPOINT_TYPE_ITEM\t\t\t1\n#define POINT_TYPE_POWERUP\t\t2\n#define POINT_TYPE_TEAMMATE\t\t3\n#define\tPOINT_TYPE_ENEMY\t\t4\n\n#define TP_MACRO_ALIGNMENT_LEFT     0\n#define TP_MACRO_ALIGNMENT_RIGHT    1\n#define TP_MACRO_ALIGNMENT_CENTERED 2\n\ntvars_t vars;\n\nchar lastip[64]; // FIXME: remove it\n\n/*********************************** MACROS ***********************************/\n\n#define MAX_MACRO_VALUE\t256\nstatic char\tmacro_buf[MAX_MACRO_VALUE] = \"\";\n\nchar *Macro_Lastip_f (void)\n{\n\tsnprintf (macro_buf, sizeof(macro_buf), \"%s\", lastip);\n\treturn macro_buf;\n}\n\nchar *Macro_Quote_f (void)\n{\n\treturn \"\\\"\";\n}\n\nchar *Macro_Latency (void)\n{\n\tsnprintf(macro_buf, sizeof(macro_buf), \"%i\", Q_rint(cls.latency * 1000));\n\treturn macro_buf;\n}\n\nchar *Macro_Health (void)\n{\n\tsnprintf(macro_buf, sizeof(macro_buf), \"%i\", cl.stats[STAT_HEALTH]);\n\treturn macro_buf;\n}\n\nchar *Macro_Armor (void)\n{\n\tsnprintf(macro_buf, sizeof(macro_buf), \"%i\", cl.stats[STAT_ARMOR]);\n\treturn macro_buf;\n}\n\nchar *Macro_Shells (void)\n{\n\tsnprintf(macro_buf, sizeof(macro_buf), \"%i\", cl.stats[STAT_SHELLS]);\n\treturn macro_buf;\n}\n\nchar *Macro_Nails (void)\n{\n\tsnprintf(macro_buf, sizeof(macro_buf), \"%i\", cl.stats[STAT_NAILS]);\n\treturn macro_buf;\n}\n\nchar *Macro_Rockets (void)\n{\n\tsnprintf(macro_buf, sizeof(macro_buf), \"%i\", cl.stats[STAT_ROCKETS]);\n\treturn macro_buf;\n}\n\nchar *Macro_Cells (void)\n{\n\tsnprintf(macro_buf, sizeof(macro_buf), \"%i\", cl.stats[STAT_CELLS]);\n\treturn macro_buf;\n}\n\nchar *Macro_Ammo (void)\n{\n\tsnprintf(macro_buf, sizeof(macro_buf), \"%i\", cl.stats[STAT_AMMO]);\n\treturn macro_buf;\n}\n\nchar *Macro_Weapon (void)\n{\n\treturn TP_ItemName(cl.stats[STAT_ACTIVEWEAPON]);\n}\n\nchar *Macro_WeaponAndAmmo (void)\n{\n\tchar buf[MAX_MACRO_VALUE];\n\tsnprintf (buf, sizeof(buf), \"%s:%s\", Macro_Weapon(), Macro_Ammo());\n\tstrlcpy (macro_buf, buf, sizeof(macro_buf));\n\treturn macro_buf;\n}\n\nchar *Macro_WeaponNum (void)\n{\n\textern cvar_t cl_weaponpreselect;\n\tint best;\n\n\tif (cl_weaponpreselect.integer && (best = IN_BestWeapon(true))) {\n\t\tchar buf[4];\n\t\tsnprintf(buf, sizeof(buf), \"%d\", best);\n\t\tstrlcpy(macro_buf, buf, sizeof(macro_buf));\n\t\treturn macro_buf;\n\t}\n\telse {\n\t\tswitch (cl.stats[STAT_ACTIVEWEAPON]) {\n\t\t\tcase IT_AXE: return \"1\";\n\t\t\tcase IT_SHOTGUN: return \"2\";\n\t\t\tcase IT_SUPER_SHOTGUN: return \"3\";\n\t\t\tcase IT_NAILGUN: return \"4\";\n\t\t\tcase IT_SUPER_NAILGUN: return \"5\";\n\t\t\tcase IT_GRENADE_LAUNCHER: return \"6\";\n\t\t\tcase IT_ROCKET_LAUNCHER: return \"7\";\n\t\t\tcase IT_LIGHTNING: return \"8\";\n\t\t\tdefault:\n\t\t\treturn \"0\";\n\t\t}\n\t}\n}\n\nint BestWeapon (void)\n{\n\treturn BestWeaponFromStatItems (cl.stats[STAT_ITEMS]);\n}\n\nint BestWeaponFromStatItems (int stat)\n{\n\tint i;\n\tchar *t[] = {tp_weapon_order.string, \"78654321\", NULL}, **s;\n\n\tfor (s = t; *s; s++) {\n\t\tfor (i = 0 ; i < strlen(*s) ; i++) {\n\t\t\tswitch ((*s)[i]) {\n\t\t\t\t\tcase '1': if (stat & IT_AXE) return IT_AXE; break;\n\t\t\t\t\tcase '2': if (stat & IT_SHOTGUN) return IT_SHOTGUN; break;\n\t\t\t\t\tcase '3': if (stat & IT_SUPER_SHOTGUN) return IT_SUPER_SHOTGUN; break;\n\t\t\t\t\tcase '4': if (stat & IT_NAILGUN) return IT_NAILGUN; break;\n\t\t\t\t\tcase '5': if (stat & IT_SUPER_NAILGUN) return IT_SUPER_NAILGUN; break;\n\t\t\t\t\tcase '6': if (stat & IT_GRENADE_LAUNCHER) return IT_GRENADE_LAUNCHER; break;\n\t\t\t\t\tcase '7': if (stat & IT_ROCKET_LAUNCHER) return IT_ROCKET_LAUNCHER; break;\n\t\t\t\t\tcase '8': if (stat & IT_LIGHTNING) return IT_LIGHTNING; break;\n\t\t\t}\n\t\t}\n\t}\n\treturn 0;\n}\n\nchar *Macro_BestWeapon (void)\n{\n\treturn TP_ItemName(BestWeapon());\n}\n\nchar *TP_ItemName(int item_flag)\n{\n\tswitch (item_flag) {\n\t\tcase IT_SHOTGUN: return tp_name_sg.string;\n\t\tcase IT_SUPER_SHOTGUN: return tp_name_ssg.string;\n\t\tcase IT_NAILGUN: return tp_name_ng.string;\n\t\tcase IT_SUPER_NAILGUN: return tp_name_sng.string;\n\t\tcase IT_GRENADE_LAUNCHER: return tp_name_gl.string;\n\t\tcase IT_ROCKET_LAUNCHER: return tp_name_rl.string;\n\t\tcase IT_LIGHTNING: return tp_name_lg.string;\n\t\tcase IT_SUPER_LIGHTNING: return \"slg\";\n\t\tcase IT_SHELLS: return tp_name_shells.string;\n\t\tcase IT_NAILS: return tp_name_nails.string;\n\t\tcase IT_ROCKETS: return tp_name_rockets.string;\n\t\tcase IT_CELLS: return tp_name_cells.string;\n\t\tcase IT_AXE: return tp_name_axe.string;\n\t\tcase IT_ARMOR1: return tp_name_ga.string;\n\t\tcase IT_ARMOR2: return tp_name_ya.string;\n\t\tcase IT_ARMOR3: return tp_name_ra.string;\n\t\tcase IT_SUPERHEALTH: return tp_name_mh.string;\n\t\tcase IT_KEY1: return \"key1\";\n\t\tcase IT_KEY2: return \"key2\";\n\t\tcase IT_INVISIBILITY: return tp_name_ring.string;\n\t\tcase IT_INVULNERABILITY: return tp_name_pent.string;\n\t\tcase IT_SUIT: return tp_name_suit.string;\n\t\tcase IT_QUAD: return tp_name_quad.string;\n\t\tcase IT_SIGIL1: return tp_name_rune1.string;\n\t\tcase IT_SIGIL2: return tp_name_rune2.string;\n\t\tcase IT_SIGIL3: return tp_name_rune3.string;\n\t\tcase IT_SIGIL4: return tp_name_rune4.string;\n\t\tdefault: return tp_name_none.string;\n\t}\n}\n\nchar *Macro_BestAmmo (void)\n{\n\tswitch (BestWeapon()) {\n\t\t\tcase IT_SHOTGUN: case IT_SUPER_SHOTGUN:\n\t\t\tsnprintf(macro_buf, sizeof(macro_buf), \"%i\", cl.stats[STAT_SHELLS]);\n\t\t\treturn macro_buf;\n\n\t\t\tcase IT_NAILGUN: case IT_SUPER_NAILGUN:\n\t\t\tsnprintf(macro_buf, sizeof(macro_buf), \"%i\", cl.stats[STAT_NAILS]);\n\t\t\treturn macro_buf;\n\n\t\t\tcase IT_GRENADE_LAUNCHER: case IT_ROCKET_LAUNCHER:\n\t\t\tsnprintf(macro_buf, sizeof(macro_buf), \"%i\", cl.stats[STAT_ROCKETS]);\n\t\t\treturn macro_buf;\n\n\t\t\tcase IT_LIGHTNING:\n\t\t\tsnprintf(macro_buf, sizeof(macro_buf), \"%i\", cl.stats[STAT_CELLS]);\n\t\t\treturn macro_buf;\n\n\t\t\tdefault: return \"0\";\n\t}\n}\n\n// needed for %b parsing\nchar *Macro_BestWeaponAndAmmo (void)\n{\n\tchar buf[MAX_MACRO_VALUE];\n\n\tsnprintf (buf, sizeof(buf), \"%s:%s\", Macro_BestWeapon(), Macro_BestAmmo());\n\n\tstrlcpy (macro_buf, buf, sizeof(macro_buf));\n\treturn macro_buf;\n}\n\nchar *Macro_Colored_Armor_f (void)\n{\n    snprintf(macro_buf, sizeof(macro_buf), \"%s\", TP_MSG_Colored_Armor());\n    return macro_buf;\n}\n\nchar *Macro_Colored_Powerups_f (void)\n{\n\tsnprintf (macro_buf, sizeof(macro_buf), \"%s\", TP_MSG_Colored_Powerup());\n\treturn macro_buf;\n}\n\nchar *Macro_Colored_Short_Powerups_f (void) // same as above, but displays \"qrp\" instead of \"quad ring pent\"\n{\n\tsnprintf (macro_buf, sizeof(macro_buf), \"%s\", TP_MSG_Colored_Short_Powerups());\n\treturn macro_buf;\n}\n\nchar* Macro_Teamplay_Powerups_f(void)\n{\n\tif (tp_poweruptextstyle.integer) {\n\t\treturn Macro_Colored_Short_Powerups_f();\n\t}\n\treturn Macro_Colored_Powerups_f();\n}\n\nchar *Macro_ArmorType (void)\n{\n\tif (cl.stats[STAT_ITEMS] & IT_ARMOR1)\n\t\treturn tp_name_armortype_ga.string;\n\telse if (cl.stats[STAT_ITEMS] & IT_ARMOR2)\n\t\treturn tp_name_armortype_ya.string;\n\telse if (cl.stats[STAT_ITEMS] & IT_ARMOR3)\n\t\treturn tp_name_armortype_ra.string;\n\telse\n\t\treturn tp_name_none.string;\n}\n\nchar *Macro_Powerups (void)\n{\n\tint effects;\n\n\tmacro_buf[0] = 0;\n\n\tif (cl.stats[STAT_ITEMS] & IT_QUAD)\n\t\tstrlcpy(macro_buf, tp_name_quad.string, sizeof(macro_buf));\n\n\tif (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) {\n\t\tif (macro_buf[0])\n\t\t\tstrlcat(macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\tstrlcat(macro_buf, tp_name_pent.string, sizeof(macro_buf));\n\t}\n\n\tif (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) {\n\t\tif (macro_buf[0])\n\t\t\tstrlcat(macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\tstrlcat(macro_buf, tp_name_ring.string, sizeof(macro_buf));\n\t}\n\n\teffects = cl.frames[cl.parsecount & UPDATE_MASK].playerstate[cl.playernum].effects;\n\tif ( (effects & (EF_FLAG1|EF_FLAG2)) /* CTF */ ||\n\t        (cl.teamfortress && cl.stats[STAT_ITEMS] & (IT_KEY1|IT_KEY2)) /* TF */ ) {\n\t\tif (macro_buf[0])\n\t\t\tstrlcat(macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\tstrlcat(macro_buf, tp_name_flag.string, sizeof(macro_buf));\n\t}\n\n\tif (!macro_buf[0])\n\t\tstrlcpy(macro_buf, tp_name_none.string, sizeof(macro_buf));\n\n\treturn macro_buf;\n}\n\nchar *Macro_Location (void)\n{\n\tstrlcpy(vars.lastreportedloc, TP_LocationName(cl.simorg), sizeof(vars.lastreportedloc));\n\n\treturn vars.lastreportedloc;\n}\n\nchar *Macro_LastDeath (void)\n{\n\treturn vars.deathtrigger_time ? vars.lastdeathloc : tp_name_someplace.string;\n}\n\nchar *Macro_Last_Location (void)\n{\n\tif (vars.deathtrigger_time && cls.realtime - vars.deathtrigger_time <= 5)\n\t\tstrlcpy(vars.lastreportedloc, vars.lastdeathloc, sizeof(vars.lastreportedloc));\n\telse\n\t\tstrlcpy(vars.lastreportedloc, TP_LocationName(cl.simorg), sizeof(vars.lastreportedloc));\n\treturn vars.lastreportedloc;\n}\n\nchar *Macro_LastReportedLoc(void)\n{\n\tif (!vars.lastreportedloc[0])\n\t\treturn tp_name_someplace.string;\n\treturn vars.lastreportedloc;\n}\n\nchar *Macro_Rune (void)\n{\n\tif (cl.stats[STAT_ITEMS] & IT_SIGIL1)\n\t\treturn tp_name_rune1.string;\n\telse if (cl.stats[STAT_ITEMS] & IT_SIGIL2)\n\t\treturn tp_name_rune2.string;\n\telse if (cl.stats[STAT_ITEMS] & IT_SIGIL3)\n\t\treturn tp_name_rune3.string;\n\telse if (cl.stats[STAT_ITEMS] & IT_SIGIL4)\n\t\treturn tp_name_rune4.string;\n\telse\n\t\treturn \"\";\n}\n\nchar *Macro_Time (void)\n{\n\ttime_t t;\n\tstruct tm *ptm;\n\n\ttime (&t);\n\tif (!(ptm = localtime (&t)))\n\t\treturn \"#bad date#\";\n\tstrftime (macro_buf, sizeof(macro_buf) - 1, \"%H:%M\", ptm);\n\treturn macro_buf;\n}\n\nchar *Macro_Date (void)\n{\n\ttime_t t;\n\tstruct tm *ptm;\n\n\ttime (&t);\n\tif (!(ptm = localtime (&t)))\n\t\treturn \"#bad date#\";\n\tstrftime (macro_buf, sizeof(macro_buf) - 1, \"%d.%m.%y\", ptm);\n\treturn macro_buf;\n}\n\nchar *Macro_DateIso(void) {\n\ttime_t t;\n\tstruct tm *ptm;\n\n\ttime (&t);\n\tif (!(ptm = localtime (&t)))\n\t\treturn \"#bad date#\";\n\tstrftime (macro_buf, sizeof(macro_buf) - 1, \"%Y-%m-%d_%H-%M\", ptm);\n\treturn macro_buf;\n}\n\nchar* Macro_TimeStamp(void)\n{\n\ttime_t t;\n\tstruct tm* ptm;\n\n\ttime(&t);\n\tif (!(ptm = localtime(&t)))\n\t\treturn \"_baddate_\";\n\n\tstrftime(macro_buf, sizeof(macro_buf) - 1, \"%Y%m%d-%H%M\", ptm);\n\treturn macro_buf;\n}\n\n// returns the last item picked up\nchar *Macro_Took (void)\n{\n\tif (TOOK_EMPTY())\n\t\tstrlcpy (macro_buf, tp_name_nothing.string, sizeof(macro_buf));\n\telse\n\t\tstrlcpy (macro_buf, vars.tookname, sizeof(macro_buf));\n\treturn macro_buf;\n}\n\n// returns location of the last item picked up\nchar *Macro_TookLoc (void)\n{\n\tif (TOOK_EMPTY())\n\t\tstrlcpy (macro_buf, tp_name_someplace.string, sizeof(macro_buf));\n\telse\n\t\tstrlcpy (macro_buf, vars.tookloc, sizeof(macro_buf));\n\treturn macro_buf;\n}\n\n// %i macro - last item picked up in \"name at location\" style\nchar *Macro_TookAtLoc (void)\n{\n\tif (TOOK_EMPTY())\n\t\tstrlcpy (macro_buf, tp_name_nothing.string, sizeof(macro_buf));\n\telse {\n\t\tstrlcpy (macro_buf, va(\"%s %s %s\", vars.tookname, tp_name_at.string, vars.tookloc), sizeof(macro_buf));\n\t}\n\treturn macro_buf;\n}\n\n// pointing calculations are CPU expensive, so the results are cached\n// in vars.pointname & vars.pointloc\nchar *Macro_PointName (void)\n{\n\tif (flashed) // there should be more smart way to do it\n\t\treturn tp_name_nothing.string;\n\n\tTP_FindPoint ();\n\treturn vars.pointname;\n}\n\nchar *Macro_PointLocation (void)\n{\n\tif (flashed) // there should be more smart way to do it\n\t\treturn tp_name_nothing.string;\n\n\tTP_FindPoint ();\n\treturn vars.pointloc[0] ? vars.pointloc : Macro_Location();\n}\n\nchar *Macro_LastPointAtLoc (void)\n{\n\tif (!vars.pointtime || cls.realtime - vars.pointtime > tp_pointtimeout.value)\n\t\tstrlcpy (macro_buf, tp_name_nothing.string, sizeof(macro_buf));\n\telse\n\t\tsnprintf (macro_buf, sizeof(macro_buf), \"%s %s %s\", vars.pointname, tp_name_at.string, vars.pointloc[0] ? vars.pointloc : Macro_Location());\n\treturn macro_buf;\n}\n\nchar *Macro_PointNameAtLocation (void)\n{\n\tif (flashed) // there should be more smart way to do it\n\t\treturn tp_name_nothing.string;\n\n\tTP_FindPoint();\n\treturn Macro_LastPointAtLoc();\n}\n\nchar *Macro_Weapons (void)\n{\n\tmacro_buf[0] = 0;\n\n\tif (cl.stats[STAT_ITEMS] & IT_LIGHTNING)\n\t\tstrlcpy(macro_buf, tp_name_lg.string, sizeof(macro_buf));\n\n\tif (cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER) {\n\t\tif (macro_buf[0])\n\t\t\tstrlcat(macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\tstrlcat(macro_buf, tp_name_rl.string, sizeof(macro_buf));\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_GRENADE_LAUNCHER) {\n\t\tif (macro_buf[0])\n\t\t\tstrlcat(macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\tstrlcat(macro_buf, tp_name_gl.string, sizeof(macro_buf));\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_SUPER_NAILGUN) {\n\t\tif (macro_buf[0])\n\t\t\tstrlcat(macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\tstrlcat(macro_buf, tp_name_sng.string, sizeof(macro_buf));\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_NAILGUN) {\n\t\tif (macro_buf[0])\n\t\t\tstrlcat(macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\tstrlcat(macro_buf, tp_name_ng.string, sizeof(macro_buf));\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_SUPER_SHOTGUN) {\n\t\tif (macro_buf[0])\n\t\t\tstrlcat(macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\tstrlcat(macro_buf, tp_name_ssg.string, sizeof(macro_buf));\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_SHOTGUN) {\n\t\tif (macro_buf[0])\n\t\t\tstrlcat(macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\tstrlcat(macro_buf, tp_name_sg.string, sizeof(macro_buf));\n\t}\n\tif (cl.stats[STAT_ITEMS] & IT_AXE) {\n\t\tif (macro_buf[0])\n\t\t\tstrlcat(macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\tstrlcat(macro_buf, tp_name_axe.string, sizeof(macro_buf));\n\t}\n\n\tif (!macro_buf[0])\n\t\tstrlcpy(macro_buf, tp_name_none.string, sizeof(macro_buf));\n\n\treturn macro_buf;\n}\n\nstatic char *Skin_To_TFSkin (char *myskin) // These four TF classes don't have their full names as the skin (i.e. they have tf_snipe instead of tf_sniper)\n{\n\tif (!cl.teamfortress || cl.spectator || strncasecmp(myskin, \"tf_\", 3)) {\n\t\tstrlcpy(macro_buf, myskin, sizeof(macro_buf));\n\t} else {\n\t\tif (!strcasecmp(myskin, \"tf_demo\"))\n\t\t\tstrlcpy(macro_buf, \"demoman\", sizeof(macro_buf));\n\t\telse if (!strcasecmp(myskin, \"tf_eng\"))\n\t\t\tstrlcpy(macro_buf, \"engineer\", sizeof(macro_buf));\n\t\telse if (!strcasecmp(myskin, \"tf_snipe\"))\n\t\t\tstrlcpy(macro_buf, \"sniper\", sizeof(macro_buf));\n\t\telse if (!strcasecmp(myskin, \"tf_sold\"))\n\t\t\tstrlcpy(macro_buf, \"soldier\", sizeof(macro_buf));\n\t\telse\n\t\t\tstrlcpy(macro_buf, myskin + 3, sizeof(macro_buf));\n\t}\n\treturn macro_buf;\n}\n\nchar *Macro_TF_Skin (void)\n{\n\treturn Skin_To_TFSkin(Info_ValueForKey(cl.players[cl.playernum].userinfo, \"skin\"));\n}\n\nchar *Macro_LastDrop (void)\n{\n\tif (vars.lastdrop_time)\n\t\treturn vars.lastdroploc;\n\telse\n\t\treturn tp_name_someplace.string;\n}\n\nchar *Macro_GameDir(void)\n{\n\tsnprintf(macro_buf, sizeof (macro_buf), \"%s\", cls.gamedirfile);\n\treturn macro_buf;\n}\n\nchar *Macro_LastTrigger_Match(void)\n{\n\treturn vars.lasttrigger_match;\n}\n\nchar *Macro_LastDropTime (void)\n{\n\tif (vars.lastdrop_time)\n\t\tsnprintf (macro_buf, sizeof (macro_buf), \"%d\", (int) (cls.realtime - vars.lastdrop_time));\n\telse\n\t\tsnprintf (macro_buf, sizeof (macro_buf), \"%d\", -1);\n\treturn macro_buf;\n}\n\n// fixme: rewrite this function into two separate functions\n// first will just set vars.needflags value (put into TP_GetNeed function below)\n// second will read it's value and produce appropriate $need macro string\nchar *Macro_Need (void)\n{\n\tint i, weapon;\n\tchar *needammo = NULL;\n\n\tmacro_buf[0] = 0;\n\tvars.needflags = 0;\n\n\t// check armor\n\tif (((cl.stats[STAT_ITEMS] & IT_ARMOR1) && cl.stats[STAT_ARMOR] < tp_need_ga.value)\n\t\t|| ((cl.stats[STAT_ITEMS] & IT_ARMOR2) && cl.stats[STAT_ARMOR] < tp_need_ya.value)\n\t\t|| ((cl.stats[STAT_ITEMS] & IT_ARMOR3) && cl.stats[STAT_ARMOR] < tp_need_ra.value)\n\t\t|| (!(cl.stats[STAT_ITEMS] & (IT_ARMOR1|IT_ARMOR2|IT_ARMOR3))\n\t\t&& (tp_need_ga.value || tp_need_ya.value || tp_need_ra.value))\n\t   )\n\t{\n\t\tstrlcpy (macro_buf, tp_name_armor.string, sizeof(macro_buf));\n\t\tvars.needflags |= it_armor;\n\t}\n\n\t// check health\n\tif (tp_need_health.value && cl.stats[STAT_HEALTH] < tp_need_health.value) {\n\t\tif (macro_buf[0])\n\t\t\tstrlcat (macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\tstrlcat (macro_buf, tp_name_health.string, sizeof(macro_buf));\n\t\tvars.needflags |= it_health;\n\t}\n\n\tif (cl.teamfortress) {\n\t\tif (cl.stats[STAT_ROCKETS] < tp_need_rockets.value)\t{\n\t\t\tif (macro_buf[0])\n\t\t\t\tstrlcat (macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\t\tstrlcat (macro_buf, tp_name_rockets.string, sizeof(macro_buf));\n\t\t\tvars.needflags |= it_rockets;\n\t\t}\n\t\tif (cl.stats[STAT_SHELLS] < tp_need_shells.value) {\n\t\t\tif (macro_buf[0])\n\t\t\t\tstrlcat (macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\t\tstrlcat (macro_buf, tp_name_shells.string, sizeof(macro_buf));\n\t\t\tvars.needflags |= it_shells;\n\t\t}\n\t\tif (cl.stats[STAT_NAILS] < tp_need_nails.value)\t{\n\t\t\tif (macro_buf[0])\n\t\t\t\tstrlcat (macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\t\tstrlcat (macro_buf, tp_name_nails.string, sizeof(macro_buf));\n\t\t\tvars.needflags |= it_nails;\n\t\t}\n\t\tif (cl.stats[STAT_CELLS] < tp_need_cells.value)\t{\n\t\t\tif (macro_buf[0])\n\t\t\t\tstrlcat (macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\t\tstrlcat (macro_buf, tp_name_cells.string, sizeof(macro_buf));\n\t\t\tvars.needflags |= it_cells;\n\t\t}\n\t\tgoto done;\n\t}\n\n\t// check weapon\n\tweapon = 0;\n\tfor (i = strlen(tp_need_weapon.string) - 1 ; i >= 0 ; i--) {\n\t\tswitch (tp_need_weapon.string[i]) {\n\t\t\t\tcase '2': if (cl.stats[STAT_ITEMS] & IT_SHOTGUN) weapon = 2; break;\n\t\t\t\tcase '3': if (cl.stats[STAT_ITEMS] & IT_SUPER_SHOTGUN) weapon = 3; break;\n\t\t\t\tcase '4': if (cl.stats[STAT_ITEMS] & IT_NAILGUN) weapon = 4; break;\n\t\t\t\tcase '5': if (cl.stats[STAT_ITEMS] & IT_SUPER_NAILGUN) weapon = 5; break;\n\t\t\t\tcase '6': if (cl.stats[STAT_ITEMS] & IT_GRENADE_LAUNCHER) weapon = 6; break;\n\t\t\t\tcase '7': if (cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER) weapon = 7; break;\n\t\t\t\tcase '8': if (cl.stats[STAT_ITEMS] & IT_LIGHTNING) weapon = 8; break;\n\t\t}\n\t\tif (weapon)\n\t\t\tbreak;\n\t}\n\n\tif (!weapon) {\n\t\tif (macro_buf[0])\n\t\t\tstrlcat (macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\tstrlcat (macro_buf, tp_name_weapon.string, sizeof(macro_buf));\n\t\tvars.needflags |= it_weapons;\n\t} else {\n\t\tif (tp_need_rl.value && !(cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER)) {\n\t\t\tif (macro_buf[0])\n\t\t\t\tstrlcat (macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\t\tstrlcat (macro_buf, tp_name_rl.string, sizeof(macro_buf));\n\t\t\tvars.needflags |= it_rl;\n\t\t}\n\n\t\tswitch (weapon) {\n\t\t\t\tcase 2: case 3: if (cl.stats[STAT_SHELLS] < tp_need_shells.value) {\n\t\t\t\t\tneedammo = tp_name_shells.string;\n\t\t\t\t\tvars.needflags |= it_shells;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t\tcase 4: case 5: if (cl.stats[STAT_NAILS] < tp_need_nails.value) {\n\t\t\t\t\tneedammo = tp_name_nails.string;\n\t\t\t\t\tvars.needflags |= it_nails;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t\tcase 6: case 7: if (cl.stats[STAT_ROCKETS] < tp_need_rockets.value) {\n\t\t\t\t\tneedammo = tp_name_rockets .string;\n\t\t\t\t\tvars.needflags |= it_rockets;\n\t\t\t\t} break;\n\t\t\t\tcase 8: if (cl.stats[STAT_CELLS] < tp_need_cells.value) {\n\t\t\t\t\tneedammo = tp_name_cells.string;\n\t\t\t\t\tvars.needflags |= it_cells;\n\t\t\t\t} break;\n\t\t}\n\n\t\tif (needammo) {\n\t\t\tif (macro_buf[0])\n\t\t\t\tstrlcat (macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\t\tstrlcat (macro_buf, needammo, sizeof(macro_buf));\n\t\t\tvars.needflags |= it_ammo;\n\t\t}\n\t}\n\ndone:\n\tif (!macro_buf[0])\n\t\tstrlcpy (macro_buf, tp_name_nothing.string, sizeof(macro_buf));\n\n\treturn macro_buf;\n}\n\nvoid TP_GetNeed(void)\n{\n\tMacro_Need();\n}\n\nchar *Macro_Point_LED(void)\n{\n\tTP_FindPoint();\n\n\tif (vars.pointtype == POINT_TYPE_ENEMY)\n\t\treturn tp_name_status_red.string;\n\telse if (vars.pointtype == POINT_TYPE_TEAMMATE)\n\t\treturn tp_name_status_green.string;\n\telse if (vars.pointtype == POINT_TYPE_POWERUP)\n\t\treturn tp_name_status_yellow.string;\n\telse // POINT_TYPE_ITEM\n\t\treturn tp_name_status_blue.string;\n}\n\nstatic int Macro_TeamSort(const void* lhs_, const void* rhs_)\n{\n\tconst char* lhs = *(const char**)lhs_;\n\tconst char* rhs = *(const char**)rhs_;\n\n\treturn Q_strcmp2(lhs, rhs);\n}\n\nstatic const char* Macro_TeamPick(int team_number, const char* default_teamname)\n{\n\tint i, j;\n\tint team_count = 0;\n\tconst char* teamnames[MAX_CLIENTS];\n\n\n\tfor (i = 0; i < sizeof(cl.players) / sizeof(cl.players[0]); ++i) {\n\t\tif (cl.players[i].name[0] && !cl.players[i].spectator) {\n\t\t\tconst char* team = (cl.teamplay ? cl.players[i].team : cl.players[i].name);\n\n\t\t\tfor (j = 0; j < team_count; ++j) {\n\t\t\t\tif (!strcmp(team, teamnames[j])) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (j >= team_count && team_count < sizeof(teamnames) / sizeof(teamnames[0])) {\n\t\t\t\tteamnames[team_count++] = team;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (team_count > team_number) {\n\t\tqsort((void*)teamnames, team_count, sizeof(teamnames[0]), Macro_TeamSort);\n\n\t\treturn teamnames[team_number];\n\t}\n\telse {\n\t\treturn default_teamname;\n\t}\n}\n\nchar *Macro_Team1(void)\n{\n\tstatic char buffer[MAX_MACRO_VALUE];\n\n\tstrlcpy(buffer, Macro_TeamPick(0, \"team1\"), sizeof(buffer));\n\n\treturn buffer;\n}\n\nchar* Macro_Team2(void)\n{\n\tstatic char buffer[MAX_MACRO_VALUE];\n\n\tstrlcpy(buffer, Macro_TeamPick(1, \"team2\"), sizeof(buffer));\n\n\treturn buffer;\n}\n\nchar *Macro_MyStatus_LED(void)\n{\n\tint count;\n\tfloat save_need_rl;\n\tchar *s, *save_separator;\n\tstatic char separator[] = {'/', '\\0'};\n\n\tsave_need_rl = tp_need_rl.value;\n\tsave_separator = tp_name_separator.string;\n\ttp_need_rl.value = 0;\n\ttp_name_separator.string = separator;\n\ts = Macro_Need();\n\ttp_need_rl.value = save_need_rl;\n\ttp_name_separator.string = save_separator;\n\n\tif (!strcmp(s, tp_name_nothing.string)) {\n\t\tcount = 0;\n\t} else  {\n\t\tfor (count = 1; *s; s++)\n\t\t\tif (*s == separator[0])\n\t\t\t\tcount++;\n\t}\n\n\tif (count == 0)\n\t\tsnprintf(macro_buf, sizeof(macro_buf), \"%s\", tp_name_status_green.string);\n\telse if (count <= 1)\n\t\tsnprintf(macro_buf, sizeof(macro_buf), \"%s\", tp_name_status_yellow.string);\n\telse\n\t\tsnprintf(macro_buf, sizeof(macro_buf), \"%s\", tp_name_status_red.string);\n\n\treturn macro_buf;\n}\n\nchar *Macro_EnemyStatus_LED(void)\n{\n\tCountNearbyPlayers(false);\n\tif (vars.numenemies == 0)\n\t\tsnprintf(macro_buf, sizeof(macro_buf), \"\\xffl%s\\xff\", tp_name_status_green.string);\n\telse if (vars.numenemies <= vars.numfriendlies)\n\t\tsnprintf(macro_buf, sizeof(macro_buf), \"\\xffl%s\\xff\", tp_name_status_yellow.string);\n\telse\n\t\tsnprintf(macro_buf, sizeof(macro_buf), \"\\xffl%s\\xff\", tp_name_status_red.string);\n\n\tsuppress = true;\n\treturn macro_buf;\n}\n\n\n#define TP_PENT 1\n#define TP_QUAD 2\n#define TP_RING 4\n\nchar *Macro_LastSeenPowerup(void)\n{\n\tif (!vars.enemy_powerups_time || cls.realtime - vars.enemy_powerups_time > 5) {\n\t\tstrlcpy(macro_buf, tp_name_quad.string, sizeof(macro_buf));\n\t} else {\n\t\tmacro_buf[0] = 0;\n\t\tif (vars.enemy_powerups & TP_QUAD)\n\t\t\tstrlcat(macro_buf, tp_name_quad.string, sizeof(macro_buf));\n\t\tif (vars.enemy_powerups & TP_PENT) {\n\t\t\tif (macro_buf[0])\n\t\t\t\tstrlcat(macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\t\tstrlcat(macro_buf, tp_name_pent.string, sizeof(macro_buf));\n\t\t}\n\t\tif (vars.enemy_powerups & TP_RING) {\n\t\t\tif (macro_buf[0])\n\t\t\t\tstrlcat(macro_buf, tp_name_separator.string, sizeof(macro_buf));\n\t\t\tstrlcat(macro_buf, tp_name_ring.string, sizeof(macro_buf));\n\t\t}\n\t}\n\treturn macro_buf;\n}\n\nqbool TP_SuppressMessage (wchar *buf)\n{\n\tsize_t len;\n\twchar *s;\n\n\tif ((len = qwcslen (buf)) < 4)\n\t\treturn false;\n\n\ts = buf + len - 4;\n\n\tif (s[0] == 0x7F && s[1] == '!' && s[3] == '\\n') {\n\t\t*s++ = '\\n';\n\t\t*s++ = 0;\n\t\treturn (!cls.demoplayback && !cl.spectator && *s - 'A' == cl.playernum);\n\t}\n\n\treturn false;\n}\n\n// things like content '%e' macro get hidden in here causing you yourself cannot see\n// how many enemies are around you, the number get replaced with a 'x' char\n// and then printed on screen as a message\nvoid TP_PrintHiddenMessage(char *buf, int nodisplay)\n{\n\tqbool team, hide = false;\n\tchar dest[4096], msg[4096], *s, *d, c, *name;\n\tint length, offset, flags;\n\textern cvar_t con_sound_mm2_file, con_sound_mm2_volume, cl_fakename, cl_fakename_suffix;\n\n\tif (!buf || !(length = strlen(buf)))\n\t\treturn;\n\n\tteam = !strcasecmp(\"say_team\", Cmd_Argv(0));\n\n\tif (length >= 2 && buf[0] == '\\\"' && buf[length - 1] == '\\\"') {\n\t\tmemmove(buf, buf + 1, length - 2);\n\t\tbuf[length - 2] = 0;\n\t}\n\n\ts = buf;\n\td = dest;\n\n\twhile ((c = *s++) && (c != '\\x7f')) {\n\t\tif (c == '\\xff') {\n\t\t\tif ((hide = !hide)) {\n\t\t\t\t*d++ = (*s == 'z') ? 'x' : (char)139;\n\t\t\t\ts++;\n\t\t\t\tmemmove(s - 2, s, strlen(s) + 1);\n\t\t\t\ts -= 2;\n\t\t\t} else {\n\t\t\t\tmemmove(s - 1, s, strlen(s) + 1);\n\t\t\t\ts -= 1;\n\t\t\t}\n\t\t} else if (!hide) {\n\t\t\t*d++ = c;\n\t\t}\n\t}\n\t*d = 0;\n\n\tif (cls.demoplayback)\n\t\treturn;\n\n\t// Player is ignoring themselves\n\tif (cl.players[cl.playernum].ignored) {\n\t\treturn;\n\t}\n\n\tname = Info_ValueForKey (cl.players[cl.playernum].userinfo, \"name\");\n\tif (strlen(name) >= 32)\n\t\tname[31] = 0;\n\n\tif (team)\n    {\n        if (cl_fakename.string[0])\n        {\n        \tchar c_fn[1024], c_fna[1024];\n        \tstrlcpy (c_fn, cl_fakename.string, sizeof(c_fn));\n        \tstrlcpy (c_fna, cl_fakename_suffix.string, sizeof(c_fna));\n\n\t\t\tsnprintf(msg, sizeof(msg), \"%s\\n\", TP_ParseFunChars(strcat(strcat(c_fn, c_fna), dest) , true));\n        }\n        else\n        {\n            snprintf(msg, sizeof(msg), \"(%s): %s\\n\", name, TP_ParseFunChars(dest, true));\n        }\n    }\n\telse\n\t{\n\t\tsnprintf(msg, sizeof(msg), \"%s: %s\\n\", name, TP_ParseFunChars(dest, true));\n\t}\n\n\tflags = TP_CategorizeMessage (msg, &offset);\n\n\tif (flags == msgtype_team && !TP_FilterMessage(str2wcs(msg) + offset))\n\t\treturn;\n\n\tif (con_sound_mm2_volume.value > 0 && nodisplay == 0) {\n\t\tS_LocalSoundWithVol(con_sound_mm2_file.string, con_sound_mm2_volume.value);\n\t}\n\n\tif (cl_nofake.value == 1 || (cl_nofake.value == 2 && flags != msgtype_team)) {\n\t\tfor (s = msg; *s; s++)\n\t\t\tif (*s == 0x0D || (*s == 0x0A && s[1]))\n\t\t\t\t*s = ' ';\n\t}\n\n\tif (nodisplay == 0) {\n\t\tCom_Printf(wcs2str(TP_ParseWhiteText (str2wcs(msg), team, offset)));\n\t}\n\n}\n\nstatic void CountNearbyPlayers(qbool dead)\n{\n\tint i;\n\tplayer_state_t *state;\n\tplayer_info_t *info;\n\tstatic int lastframecount = -1;\n\n\tif (cls.framecount == lastframecount)\n\t\treturn;\n\tlastframecount = cls.framecount;\n\n\tvars.numenemies = vars.numfriendlies = 0;\n\n\tif (!cl.spectator && !dead)\n\t\tvars.numfriendlies++;\n\n\tif (!cl.oldparsecount || !cl.parsecount || cls.state < ca_active)\n\t\treturn;\n\n\tstate = cl.frames[cl.oldparsecount & UPDATE_MASK].playerstate;\n\tinfo = cl.players;\n\tfor (i = 0; i < MAX_CLIENTS; i++, info++, state++) {\n\t\tif (i != cl.playernum && state->messagenum == cl.oldparsecount && !info->spectator && !ISDEAD(state->frame)) {\n\t\t\tif (cl.teamplay && !strcmp(info->team, TP_PlayerTeam()))\n\t\t\t\tvars.numfriendlies++;\n\t\t\telse\n\t\t\t\tvars.numenemies++;\n\t\t}\n\t}\n}\n\nchar *Macro_CountNearbyEnemyPlayers (void)\n{\n\tconst char* override_text = Ruleset_BlockPlayerCountMacros();\n\n\tif (override_text) {\n\t\tstrlcpy(macro_buf, \"\\xff\", sizeof(macro_buf));\n\t\tstrlcat(macro_buf, override_text, sizeof(macro_buf));\n\t\tstrlcat(macro_buf, \"\\xff\", sizeof(macro_buf));\n\t}\n\telse {\n\t\tCountNearbyPlayers(false);\n\t\tsnprintf(macro_buf, sizeof(macro_buf), \"\\xffz%d\\xff\", vars.numenemies);\n\t\tsuppress = true;\n\t}\n\treturn macro_buf;\n}\n\nchar *Macro_Count_Last_NearbyEnemyPlayers (void)\n{\n\tconst char* override_text = Ruleset_BlockPlayerCountMacros();\n\n\tif (override_text) {\n\t\tstrlcpy(macro_buf, override_text, sizeof(macro_buf));\n\t}\n\telse {\n\t\tif (vars.deathtrigger_time && cls.realtime - vars.deathtrigger_time <= 5) {\n\t\t\tsnprintf(macro_buf, sizeof(macro_buf), \"\\xffz%d\\xff\", vars.last_numenemies);\n\t\t}\n\t\telse {\n\t\t\tCountNearbyPlayers(false);\n\t\t\tsnprintf(macro_buf, sizeof(macro_buf), \"\\xffz%d\\xff\", vars.numenemies);\n\t\t}\n\t\tsuppress = true;\n\t}\n\treturn macro_buf;\n}\n\nchar *Macro_CountNearbyFriendlyPlayers (void)\n{\n\tconst char* override_text = Ruleset_BlockPlayerCountMacros();\n\n\tif (override_text) {\n\t\tstrlcpy(macro_buf, override_text, sizeof(macro_buf));\n\t}\n\telse {\n\t\tCountNearbyPlayers(false);\n\t\tsnprintf(macro_buf, sizeof(macro_buf), \"\\xffz%d\\xff\", vars.numfriendlies);\n\t\tsuppress = true;\n\t}\n\treturn macro_buf;\n}\n\nchar* Macro_Count_Last_NearbyFriendlyPlayers (void)\n{\n\tconst char* override_text = Ruleset_BlockPlayerCountMacros();\n\n\tif (override_text) {\n\t\tstrlcpy(macro_buf, override_text, sizeof(macro_buf));\n\t}\n\telse {\n\t\tif (vars.deathtrigger_time && cls.realtime - vars.deathtrigger_time <= 5) {\n\t\t\tsnprintf(macro_buf, sizeof(macro_buf), \"\\xffz%d\\xff\", vars.last_numfriendlies);\n\t\t}\n\t\telse {\n\t\t\tCountNearbyPlayers(false);\n\t\t\tsnprintf(macro_buf, sizeof(macro_buf), \"\\xffz%d\\xff\", vars.numfriendlies);\n\t\t}\n\t\tsuppress = true;\n\t}\n\treturn macro_buf;\n}\n\n// Note: longer macro names like \"armortype\" must be defined\n// _before_ the shorter ones like \"armor\" to be parsed properly\nvoid TP_AddMacros(void)\n{\n\tint teamplay = (int)Rulesets_RestrictTriggers();\n\n\tCmd_AddMacro(macro_lastip, Macro_Lastip_f);\n\tCmd_AddMacro(macro_qt, Macro_Quote_f);\n\tCmd_AddMacro(macro_latency, Macro_Latency);\n\tCmd_AddMacro(macro_ping, Macro_Latency);\n\tCmd_AddMacro(macro_timestamp, Macro_TimeStamp);\n\tCmd_AddMacro(macro_time, Macro_Time);\n\tCmd_AddMacro(macro_date, Macro_Date);\n\tCmd_AddMacro(macro_dateiso, Macro_DateIso);\n\n\tCmd_AddMacroEx(macro_health, Macro_Health, teamplay);\n\tCmd_AddMacroEx(macro_armortype, Macro_ArmorType, teamplay);\n\tCmd_AddMacroEx(macro_armor, Macro_Armor, teamplay);\n\tCmd_AddMacroEx(macro_colored_armor, Macro_Colored_Armor_f, teamplay);\n\tCmd_AddMacroEx(macro_colored_powerups, Macro_Colored_Powerups_f, teamplay);\n\tCmd_AddMacroEx(macro_colored_short_powerups, Macro_Colored_Short_Powerups_f, teamplay);\n\tCmd_AddMacroEx(macro_tp_powerups, Macro_Teamplay_Powerups_f, teamplay);\n\n\tCmd_AddMacroEx(macro_shells, Macro_Shells, teamplay);\n\tCmd_AddMacroEx(macro_nails, Macro_Nails, teamplay);\n\tCmd_AddMacroEx(macro_rockets, Macro_Rockets, teamplay);\n\tCmd_AddMacroEx(macro_cells, Macro_Cells, teamplay);\n\n\tCmd_AddMacro(macro_weaponnum, Macro_WeaponNum);\n\tCmd_AddMacroEx(macro_weapons, Macro_Weapons, teamplay);\n\tCmd_AddMacro(macro_weapon, Macro_Weapon);\n\n\tCmd_AddMacroEx(macro_ammo, Macro_Ammo, teamplay);\n\n\tCmd_AddMacroEx(macro_bestweapon, Macro_BestWeapon, teamplay);\n\tCmd_AddMacroEx(macro_bestammo, Macro_BestAmmo, teamplay);\n\n\tCmd_AddMacroEx(macro_powerups, Macro_Powerups, teamplay);\n\n\tCmd_AddMacroEx(macro_location, Macro_Location, teamplay);\n\tCmd_AddMacroEx(macro_deathloc, Macro_LastDeath, teamplay);\n\n\tCmd_AddMacroEx(macro_tookatloc, Macro_TookAtLoc, teamplay);\n\tCmd_AddMacroEx(macro_tookloc, Macro_TookLoc, teamplay);\n\tCmd_AddMacroEx(macro_took, Macro_Took, teamplay);\n\n\tCmd_AddMacroEx(macro_pointatloc, Macro_PointNameAtLocation, teamplay);\n\tCmd_AddMacroEx(macro_pointloc, Macro_PointLocation, teamplay);\n\tCmd_AddMacroEx(macro_point, Macro_PointName, teamplay);\n\n\tCmd_AddMacroEx(macro_need, Macro_Need, teamplay);\n\n\tCmd_AddMacroEx(macro_droploc, Macro_LastDrop, teamplay);\n\tCmd_AddMacroEx(macro_droptime, Macro_LastDropTime, teamplay);\n\n\tCmd_AddMacro(macro_tf_skin, Macro_TF_Skin);\n\tCmd_AddMacro(macro_gamedir, Macro_GameDir);\n\n\tCmd_AddMacro(macro_triggermatch, Macro_LastTrigger_Match);\n\tCmd_AddMacroEx(macro_ledpoint, Macro_Point_LED, teamplay);\n\tCmd_AddMacroEx(macro_ledstatus, Macro_MyStatus_LED, teamplay);\n\n\tCmd_AddMacroEx(macro_lastloc, Macro_Last_Location, teamplay);\n\tCmd_AddMacroEx(macro_lastpowerup, Macro_LastSeenPowerup, teamplay);\n\n\tCmd_AddMacro(macro_team1, Macro_Team1);\n\tCmd_AddMacro(macro_team2, Macro_Team2);\n}\n\n/********************** MACRO/FUNCHAR/WHITE TEXT PARSING **********************/\n\nwchar *TP_ParseWhiteText (const wchar *s, qbool team, int offset)\n{\n\tstatic wchar\tbuf[4096];\n\twchar *out, *p1;\n\tconst wchar* p;\n\textern cvar_t\tcl_parseWhiteText;\n\tqbool\tparsewhite;\n\n\tparsewhite = cl_parseWhiteText.value == 1 || (cl_parseWhiteText.value == 2 && team);\n\n\tbuf[0] = 0;\n\tout = buf;\n\n\tfor (p = s; *p; p++) {\n\t\tif  (parsewhite && *p == '{' && p-s >= offset) {\n\t\t\tif ((p1 = qwcschr (p + 1, '}'))) {\n\t\t\t\tmemcpy (out, p + 1, (p1 - p - 1)*sizeof(wchar));\n\t\t\t\tout += p1 - p - 1;\n\t\t\t\tp = p1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tif (*p != 10 && *p != 13 && !(p==s && (*p==1 || *p==2)) && *p <= 0x7F)\n\t\t\t*out++ = *p | 128;\t// convert to red\n\t\telse\n\t\t\t*out++ = *p;\n\t}\n\t*out = 0;\n\treturn buf;\n}\n\nstatic int TP_MacroStringLength(char* output, const char* text, int buffer_length, int printable_length)\n{\n\textern cvar_t cl_parseWhiteText;\n\tqbool in_colour = false;\n\tqbool in_braces = false;\n\tint printed_length = 0;\n\tint buffered_length = 0;\n\tconst char* s = 0;\n\n\tmemset(output, 0, buffer_length);\n\tbuffer_length -= 1;\n\tfor (s = text; *s && printed_length < printable_length && buffered_length < buffer_length - (in_braces ? 1 : 0) - (in_colour ? 2 : 0); ++s) {\n\t\tif (*s == '&') {\n\t\t\tif (s[1] == 'c' && s[2] && s[3] && s[4]) {\n\t\t\t\tif (HexToInt(s[2]) >= 0 && HexToInt(s[3]) >= 0 && HexToInt(s[4]) >= 0) {\n\t\t\t\t\tif (buffer_length - buffered_length <= 5)\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tmemcpy(output + buffered_length, s, 5);\n\t\t\t\t\tbuffered_length += 5;\n\t\t\t\t\tin_colour = HexToInt(s[2]) != 15 || HexToInt(s[3]) != 15 || HexToInt(s[4]) != 15;\t// &cFFF used instead of &r...\n\t\t\t\t\ts += 4;\n\t\t\t\t\tcontinue; \n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (s[1] == 'r') {\n\t\t\t\tif (buffer_length - buffered_length <= 2)\n\t\t\t\t\tbreak;\n\t\t\t\toutput[buffered_length++] = '&';\n\t\t\t\toutput[buffered_length++] = 'r';\n\t\t\t\t++s;\n\t\t\t\tin_colour = false;\n\t\t\t\tcontinue; \n\t\t\t}\n\t\t}\n\t\telse if (cl_parseWhiteText.value) {\n\t\t\t// We don't really know if the other clients have this set, presume same settings on all machines\n\t\t\tif ((s[0] == '{' && s[1] != '{') || (s[0] == '}' && s[1] != '}')) {\n\t\t\t\toutput[buffered_length++] = s[0];\n\t\t\t\tin_braces = (s[0] == '{');\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\toutput[buffered_length++] = s[0];\n\t\t++printed_length;\n\t}\n\n\tif (in_colour && buffered_length < buffer_length - 2) {\n\t\toutput[buffered_length++] = '&';\n\t\toutput[buffered_length++] = 'r';\n\t}\n\tif (in_braces && buffered_length < buffer_length - 1) {\n\t\toutput[buffered_length++] = '}';\n\t}\n\toutput[buffer_length - 1] = 0;\n\n\treturn printed_length;\n}\n\nchar* TP_AlignMacroText(char* text, int fixed_width, int alignment)\n{\n\tstatic char output[MAX_MACRO_STRING];\n\tstatic char content[MAX_MACRO_STRING];\n\tint string_length = 0;\n\tint spaces = 0;\n\n\tif (fixed_width == 0)\n\t\treturn text;\n\n\tstring_length = TP_MacroStringLength(content, text, MAX_MACRO_STRING, fixed_width);\n\tspaces = max(fixed_width - string_length, 0);\n\n\tmemset(output, 0, sizeof(output));\n\tswitch (alignment)\n\t{\n\tcase TP_MACRO_ALIGNMENT_RIGHT:\n\t\tsnprintf(output, MAX_MACRO_STRING - 1, \"%*s%s\", spaces, \"\", content);\n\t\tbreak;\n\tcase TP_MACRO_ALIGNMENT_CENTERED:\n\t\tsnprintf(output, MAX_MACRO_STRING - 1, \"%*s%s%*s\", spaces / 2, \"\", content, (spaces + 1) / 2, \"\");\n\t\tbreak;\n\tcase TP_MACRO_ALIGNMENT_LEFT:\n\tdefault:\n\t\tsnprintf(output, MAX_MACRO_STRING - 1, \"%s%*s\", content, spaces, \"\");\n\t\tbreak;\n\t}\n\n\treturn output;\n}\n\nvoid TP_SetDefaultMacroFormat(char* cvar_ext, int* fixed_width, int* alignment)\n{\n\tchar cvar_name[128] = { 0 };\n\tcvar_t* width_cvar; \n\tcvar_t* alignment_cvar;\n\n\tsnprintf(cvar_name, sizeof(cvar_name) - 1, \"tp_length_%s\", cvar_ext);\n\twidth_cvar = Cvar_Find(cvar_name);\n\n\t*fixed_width = 0;\n\t*alignment = TP_MACRO_ALIGNMENT_LEFT;\n\n\tif (width_cvar) {\n\t\t*fixed_width = max(0, min(width_cvar->integer, 40));\n\n\t\tsnprintf(cvar_name, sizeof(cvar_name) - 1, \"tp_align_%s\", cvar_ext);\n\t\talignment_cvar = Cvar_Find(cvar_name);\n\t\tif (alignment_cvar && tolower(alignment_cvar->string[0]) == 'r')\n\t\t\t*alignment = TP_MACRO_ALIGNMENT_RIGHT;\n\t\telse if (alignment_cvar && tolower(alignment_cvar->string[0]) == 'c')\n\t\t\t*alignment = TP_MACRO_ALIGNMENT_CENTERED;\n\t}\n}\n\nstatic void TP_SetDefaultMacroCharFormat(qbool extended, char character, int* fixed_width, int* alignment)\n{\n\tchar cvar_ext[128] = { 0 };\n\n\tsnprintf(cvar_ext, sizeof(cvar_ext) - 1, \"%s%s%c\", extended ? \"ext_\" : \"\", isupper(character) ? \"caps_\" : \"\", character);\n\n\tTP_SetDefaultMacroFormat(cvar_ext, fixed_width, alignment);\n}\n\nqbool TP_ReadMacroFormat(char* s, int* fixed_width, int* alignment, char** new_s)\n{\n\t*new_s = s;\n\t*fixed_width = 0;\n\t*alignment = TP_MACRO_ALIGNMENT_LEFT;\n\n\tif (s[0] && s[1] == '<') {\n\t\ts += 2;\n\n\t\twhile (s[0] && s[0] != '>') {\n\t\t\tif (s[0] >= '0' && s[0] <= '9') {\n\t\t\t\t*fixed_width = (*fixed_width) * 10 + (s[0] - '0');\n\t\t\t}\n\t\t\telse if ((s[0] == 'l' || s[0] == 'L') && s[1] == '>') {\n\t\t\t\t*alignment = TP_MACRO_ALIGNMENT_LEFT;\n\t\t\t}\n\t\t\telse if ((s[0] == 'r' || s[0] == 'R') && s[1] == '>') {\n\t\t\t\t*alignment = TP_MACRO_ALIGNMENT_RIGHT;\n\t\t\t}\n\t\t\telse if ((s[0] == 'c' || s[0] == 'C') && s[1] == '>') {\n\t\t\t\t*alignment = TP_MACRO_ALIGNMENT_CENTERED;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t++s;\n\t\t}\n\n\t\t// not valid string\n\t\tif (s[0] != '>') {\n\t\t\t*fixed_width = *alignment = 0;\n\t\t\treturn false;\n\t\t}\n\n\t\t*new_s = s;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n//Parses %a-like expressions\nchar *TP_ParseMacroString (char *s)\n{\n\tstatic char\tbuf[MAX_MACRO_STRING];\n\tint i = 0;\n\tint pN, pn;\n\tchar *macro_string;\n\n\tint r = 0;\n\n\tplayer_state_t *state;\n\tplayer_info_t *info;\n\tstatic int lastframecount = -1;\n\n\tif (!cl_parseSay.value)\n\t\treturn s;\n\n\tsuppress = false;\n\tpn = pN = 0;\n\n\twhile (*s && i < MAX_MACRO_STRING - 1) {\n\t\tqbool is_macro_indicator = (*s == '%');\n\t\tint fixed_width = 0;\n\t\tint alignment = 0;\n\t\tqbool explicit_format = false;\n\n\t\t// check for %<size[alignment]>\n\t\texplicit_format = is_macro_indicator && TP_ReadMacroFormat(s, &fixed_width, &alignment, &s);\n\n\t\t// check %[P], etc\n\t\tif (is_macro_indicator && s[1]=='[' && s[2] && s[3]==']') {\n\t\t\tstatic char mbuf[MAX_MACRO_VALUE];\n\t\t\tchar cvar_lookup_char = s[2];\n\n\t\t\tswitch (s[2]) {\n\t\t\t\tcase 'a':\n\t\t\t\t\tmacro_string = Macro_ArmorType();\n\t\t\t\t\tif (!strcmp(macro_string, tp_name_none.string))\n\t\t\t\t\t\tmacro_string = \"a\";\n\t\t\t\t\tif (cl.stats[STAT_ARMOR] < 30)\n\t\t\t\t\t\tsnprintf (mbuf, sizeof(mbuf), \"\\x10%s:%i\\x11\", macro_string, cl.stats[STAT_ARMOR]);\n\t\t\t\t\telse\n\t\t\t\t\t\tsnprintf (mbuf, sizeof(mbuf), \"%s:%i\", macro_string, cl.stats[STAT_ARMOR]);\n\t\t\t\t\tmacro_string = mbuf;\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'h':\n\t\t\t\t\tif (cl.stats[STAT_HEALTH] >= 50)\n\t\t\t\t\t\tsnprintf (macro_buf, sizeof(macro_buf), \"%i\", cl.stats[STAT_HEALTH]);\n\t\t\t\t\telse\n\t\t\t\t\t\tsnprintf (macro_buf, sizeof(macro_buf), \"\\x10%i\\x11\", cl.stats[STAT_HEALTH]);\n\t\t\t\t\tmacro_string = macro_buf;\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'p':\n\t\t\t\tcase 'P':\n\t\t\t\t\tmacro_string = Macro_Powerups();\n\t\t\t\t\tif (strcmp(macro_string, tp_name_none.string))\n\t\t\t\t\t\tsnprintf (mbuf, sizeof(mbuf), \"\\x10%s\\x11\", macro_string);\n\t\t\t\t\telse\n\t\t\t\t\t\tmbuf[0] = 0;\n\t\t\t\t\tmacro_string = mbuf;\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tbuf[i++] = *s++;\n\t\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!explicit_format)\n\t\t\t\tTP_SetDefaultMacroCharFormat(true, cvar_lookup_char, &fixed_width, &alignment);\n\n\t\t\tif (fixed_width)\n\t\t\t\tmacro_string = TP_AlignMacroText(macro_string, fixed_width, alignment);\n\n\t\t\tif (i + strlen(macro_string) >= MAX_MACRO_STRING - 1)\n\t\t\t\tSys_Error(\"TP_ParseMacroString: macro string length > MAX_MACRO_STRING)\");\n\t\t\tstrlcpy (&buf[i], macro_string, MAX_MACRO_STRING - i);\n\t\t\ti += strlen(macro_string);\n\t\t\ts += 4;\t// skip %[<char>]\n\t\t\tcontinue;\n\t\t}\n\n\t\t// check %a, etc\n\t\tif (is_macro_indicator) {\n\t\t\tchar cvar_lookup_char = s[1];\n\n\t\t\tswitch (s[1]) {\n\t\t\t\t//case '\\x7f': macro_string = \"\"; break;// skip cause we use this to hide mesgs\n\t\t\t\t//case '\\xff': macro_string = \"\"; break;\n\t\t\t\tcase 'n':   pn = 1; macro_string = \"\"; break;\n\t\t\t\tcase 'N':   pN = 1; macro_string = \"\"; break;\n\t\t\t\tcase 'a':\tmacro_string = Macro_Armor(); break;\n\t\t\t\tcase 'A':\tmacro_string = Macro_ArmorType(); break;\n\t\t\t\tcase 'b':\tmacro_string = Macro_BestWeaponAndAmmo(); break;\n\t\t\t\tcase 'c':\tmacro_string = Macro_Cells(); break;\n\t\t\t\tcase 'd':\tmacro_string = Macro_LastDeath(); cvar_lookup_char = 'l'; break;\n\t\t\t\tcase 'h':\tmacro_string = Macro_Health(); break;\n\t\t\t\tcase 'i':\tmacro_string = Macro_TookAtLoc(); cvar_lookup_char = 'l'; break;\n\t\t\t\tcase 'j':\tmacro_string = Macro_LastPointAtLoc(); cvar_lookup_char = 'l'; break;\n\t\t\t\tcase 'k':\tmacro_string = Macro_LastTookOrPointed(); break;\n\t\t\t\tcase 'l':\tmacro_string = Macro_Location(); cvar_lookup_char = 'l'; break;\n\t\t\t\tcase 'L':\tmacro_string = Macro_Last_Location(); cvar_lookup_char = 'l'; break;\n\t\t\t\tcase 'm':\tmacro_string = Macro_LastTookOrPointed(); break;\n\t\t\t\tcase 'P':\n\t\t\t\tcase 'p':\tmacro_string = Macro_Powerups(); cvar_lookup_char = 'p'; break;\n\t\t\t\tcase 'q':\tmacro_string = Macro_LastSeenPowerup(); break;\n\t\t\t\tcase 'r':\tmacro_string = Macro_LastReportedLoc(); cvar_lookup_char = 'l'; break;\n\t\t\t\tcase 'R':\tmacro_string = Macro_Rune(); break;\n\t\t\t\tcase 'S':\tmacro_string = Macro_TF_Skin(); break;\n\t\t\t\tcase 't':\tmacro_string = Macro_PointNameAtLocation(); break;\n\t\t\t\tcase 'u':\tmacro_string = Macro_Need(); break;\n\t\t\t\tcase 'w':\tmacro_string = Macro_WeaponAndAmmo(); break;\n\t\t\t\tcase 'x':\tmacro_string = Macro_PointName(); break;\n\t\t\t\tcase 'X':\tmacro_string = Macro_Took(); cvar_lookup_char = 'x'; break;\n\t\t\t\tcase 'y':\tmacro_string = Macro_PointLocation(); cvar_lookup_char = 'l'; break;\n\t\t\t\tcase 'Y':\tmacro_string = Macro_TookLoc(); cvar_lookup_char = 'l'; break;\n\t\t\t\tCASE_PLAYER_COUNT_ALLOWED('E', Macro_Count_Last_NearbyEnemyPlayers)\n\t\t\t\tCASE_PLAYER_COUNT_ALLOWED('e', Macro_CountNearbyEnemyPlayers)\n\t\t\t\tCASE_PLAYER_COUNT_ALLOWED('O', Macro_Count_Last_NearbyFriendlyPlayers)\n\t\t\t\tCASE_PLAYER_COUNT_ALLOWED('o', Macro_CountNearbyFriendlyPlayers)\n\t\t\t\tCASE_PLAYER_COUNT_ALLOWED('s', Macro_EnemyStatus_LED)\n\t\t\t\tcase '%':\n\t\t\t\t\t++s;\t// deliberate fall-through, skip this % and print the next\n\t\t\t\tdefault:\n\t\t\t\tdo_default:\n\t\t\t\t\tbuf[i++] = '%';\n\t\t\t\t\t++s;\n\t\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!explicit_format)\n\t\t\t\tTP_SetDefaultMacroCharFormat(false, cvar_lookup_char, &fixed_width, &alignment);\n\n\t\t\tif (fixed_width)\n\t\t\t\tmacro_string = TP_AlignMacroText(macro_string, fixed_width, alignment);\n\n\t\t\tif (i + strlen(macro_string) >= MAX_MACRO_STRING - 1)\n\t\t\t\tSys_Error(\"TP_ParseMacroString: macro string length > MAX_MACRO_STRING)\");\n\t\t\tstrlcpy (&buf[i], macro_string, MAX_MACRO_STRING - i);\n\t\t\ti += strlen(macro_string);\n\t\t\ts += 2;\t// skip % and letter\n\t\t\tcontinue;\n\t\t}\n\t\tbuf[i++] = *s++;\n\t}\n\tbuf[i] = 0;\n\n\ti = strlen(buf);\n\n\tif (pN) {\n\t\tbuf[i++] = 0x7f;\n\t\tbuf[i++] = '!';\n\t\tbuf[i++] = 'A' + cl.playernum;\n\t}\n\tif (pn) {\n\n\t\tif (!pN)\n\t\t\tbuf[i++] = 0x7f;\n\n\t\tif (cls.framecount != lastframecount) {\n\n\t\t\tlastframecount = cls.framecount;\n\n\t\t\tif (!(!cl.oldparsecount || !cl.parsecount || cls.state < ca_active)) {\n\n\t\t\t\tstate = cl.frames[cl.oldparsecount & UPDATE_MASK].playerstate;\n\t\t\t\tinfo = cl.players;\n\n\t\t\t\tfor (r = 0; r < MAX_CLIENTS; r++, info++, state++) {\n\t\t\t\t\tif (r != cl.playernum && state->messagenum == cl.oldparsecount && !info->spectator && !ISDEAD(state->frame)) {\n\t\t\t\t\t\tif (cl.teamplay && !strcmp(info->team, TP_PlayerTeam()))\n\t\t\t\t\t\t\tbuf[i++] = 'A' + r;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\t}\n\n\tif (suppress) {\n\t\tqbool quotes = false;\n\n\t\tTP_PrintHiddenMessage(buf,pN);\n\n\t\ti = strlen(buf);\n\n\t\tif (i > 0 && buf[i - 1] == '\\\"') {\n\t\t\tbuf[i - 1] = 0;\n\t\t\tquotes = true;\n\t\t\ti--;\n\t\t}\n\n\t\tif (!pN) {\n\t\t\tif (!pn) {\n\t\t\t\tbuf[i++] = 0x7f;\n\t\t\t}\n\t\t\tbuf[i++] = '!';\n\t\t\tbuf[i++] = 'A' + cl.playernum;\n\t\t}\n\n\t\tif (quotes)\n\t\t\tbuf[i++] = '\\\"';\n\t\tbuf[i] = 0;\n\t}\n\n\n\treturn buf;\n}\n\n//Doesn't check for overflows, so strlen(s) should be < MAX_MACRO_STRING\nchar *TP_ParseFunChars (const char *s, qbool chat)\n{\n\tstatic char\t buf[MAX_MACRO_STRING];\n\tchar\t\t*out = buf;\n\tint\t\t\t c;\n\n\tif (!cl_parseFunChars.value) {\n\t\tstrlcpy(buf, s, sizeof(buf));\n\t\treturn buf;\n\t}\n\n\twhile (*s) {\n\t\tif (*s == '$' && s[1] == 'x') {\n\t\t\tint i;\n\t\t\t// check for $x10, $x8a, etc\n\t\t\tc = tolower((int)(unsigned char)s[2]);\n\t\t\tif ( isdigit(c) )\n\t\t\t\ti = (c - (int)'0') << 4;\n\t\t\telse if ( isxdigit(c) )\n\t\t\t\ti = (c - (int)'a' + 10) << 4;\n\t\t\telse goto skip;\n\t\t\tc = tolower((int)(unsigned char)s[3]);\n\t\t\tif ( isdigit(c) )\n\t\t\t\ti += (c - (int)'0');\n\t\t\telse if ( isxdigit(c) )\n\t\t\t\ti += (c - (int)'a' + 10);\n\t\t\telse goto skip;\n\t\t\tif (!i)\n\t\t\t\ti = (int)' ';\n\t\t\t*out++ = (char)i;\n\t\t\ts += 4;\n\t\t\tcontinue;\n\t\t}\n\t\tif (*s == '$' && s[1]) {\n\t\t\tc = 0;\n\t\t\tswitch (s[1]) {\n\t\t\t\t\tcase '\\\\': c = 0x0D; break;\n\t\t\t\t\tcase ':': c = 0x0A; break;\n\t\t\t\t\tcase '[': c = 0x10; break;\n\t\t\t\t\tcase ']': c = 0x11; break;\n\t\t\t\t\tcase 'G': c = 0x86; break; // green led\n\t\t\t\t\tcase 'R': c = 0x87; break; // red led\n\t\t\t\t\tcase 'Y': c = 0x88; break; // yellow led\n\t\t\t\t\tcase 'B': c = 0x89; break; // blue led\n\t\t\t\t\tcase 'W': c = 0x84; break; // white led\n\t\t\t\t\tcase '(': c = 0x80; break;\n\t\t\t\t\tcase '=': c = 0x81; break;\n\t\t\t\t\tcase ')': c = 0x82; break;\n\t\t\t\t\tcase 'a': c = 0x83; break;\n\t\t\t\t\tcase '<': c = 0x1d; break;\n\t\t\t\t\tcase '-': c = 0x1e; break;\n\t\t\t\t\tcase '>': c = 0x1f; break;\n\t\t\t\t\tcase ',': c = 0x1c; break;\n\t\t\t\t\tcase '.': c = 0x9c; break;\n\t\t\t\t\tcase 'b': c = 0x8b; break;\n\t\t\t\t\tcase 'c':\n\t\t\t\t\tcase 'd': c = 0x8d; break;\n\t\t\t\t\tcase '$': c = '$'; break;\n\t\t\t\t\tcase '^': c = '^'; break;\n\t\t\t}\n\t\t\tif ( isdigit((int)(unsigned char)s[1]) )\n\t\t\t\tc = s[1] - (int)'0' + 0x12;\n\t\t\tif (c) {\n\t\t\t\t*out++ = (char)c;\n\t\t\t\ts += 2;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tif (!chat && *s == '^' && s[1] && s[1] != ' ') {\n\t\t\t*out++ = s[1] | 128;\n\t\t\ts += 2;\n\t\t\tcontinue;\n\t\t}\n\tskip:\n\t\t*out++ = *s++;\n\t}\n\t*out = 0;\n\n\treturn buf;\n}\n\n/************************* SKIN FORCING & REFRESHING *************************/\nqbool TP_TeamLockSpecified(void)\n{\n\t// 1 => return 1st team (doesn't matter which, just locks colours)\n\t// non-blank string => use that teamname as the client's team\n\treturn cl.spectator && (cl_teamlock.integer != 0 || (cl_teamlock.string[0] && strcmp(cl_teamlock.string, \"0\")));\n}\n\nchar *TP_SkinForcingTeam(void)\n{\n\tint tracknum;\n\n\t// FIXME: teams with names 0 & 1 will clash with this - teamlock & the team name should be diff cvars\n\tif (!cl.spectator)\n\t{\n\t\t// Normal player.\n\t\treturn cl.players[cl.playernum].team;\n\t}\n\telse if (cl_teamlock.string[0] == '1' && cl_teamlock.string[1] == '\\0') {\n\t\textern const char* HUD_FirstTeam(void);\n\t\tint i;\n\n\t\tif (cls.mvdplayback && HUD_FirstTeam()[0]) {\n\t\t\treturn (char*)HUD_FirstTeam();\n\t\t}\n\n\t\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\t\tif (cl.players[i].name[0] && !cl.players[i].spectator && cl.players[i].team[0]) {\n\t\t\t\treturn cl.players[i].team;\n\t\t\t}\n\t\t}\n\t}\n\telse if (!(cl_teamlock.string[0] == '0' && cl_teamlock.string[1] == '\\0')) {\n\t\t// anything that isn't \"0\" to disable\n\t\treturn cl_teamlock.string;\n\t}\n\telse if ((tracknum = Cam_TrackNum()) != -1)\n\t{\n\t\t// Spectating and tracking someone (not free flying).\n\t\treturn cl.players[tracknum].team;\n\t}\n\n\treturn \"\";\n}\n\nstatic qbool need_skin_refresh;\nvoid TP_UpdateSkins(void)\n{\n\tint slot;\n\n\tif (!need_skin_refresh)\n\t{\n\t\treturn;\n\t}\n\n\tfor (slot = 0; slot < MAX_CLIENTS; slot++)\n\t{\n\t\tif (cl.players[slot].skin_refresh)\n\t\t{\n\t\t\tCL_NewTranslation(slot);\n\t\t\tcl.players[slot].skin_refresh = false;\n\t\t}\n\t}\n\n\tneed_skin_refresh = false;\n}\n\n// Returns true if a change in player/team needs skins to be reloaded\nqbool TP_NeedRefreshSkins(void)\n{\n\textern cvar_t r_enemyskincolor, r_teamskincolor;\n\n\tif (cl.teamfortress) {\n\t\treturn false;\n\t}\n\n\tif ((cl_enemyskin.string[0] || cl_teamskin.string[0] || cl_enemypentskin.string[0] || cl_teampentskin.string[0] ||\n\t     cl_enemyquadskin.string[0] || cl_teamquadskin.string[0] || cl_enemybothskin.string[0] || cl_teambothskin.string[0])\n\t     && !(cl.fpd & FPD_NO_FORCE_SKIN))\n\t\treturn true;\n\n\tif ((cl_teamtopcolor.value >= 0 || cl_enemytopcolor.value >= 0) && !(cl.fpd & FPD_NO_FORCE_COLOR))\n\t\treturn true;\n\n\tif ((r_enemyskincolor.string[0] || r_teamskincolor.string[0])) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid TP_RefreshSkin(int slot)\n{\n\tif (cls.state < ca_connected || slot < 0 || slot >= MAX_CLIENTS || !cl.players[slot].name[0] || cl.players[slot].spectator)\n\t\treturn;\n\n\t// Multiview\n\t// Never allow a skin refresh in multiview, since it\n\t// results in players getting the wrong color when\n\t// force colors is used (Team/enemycolor).\n\t// TODO: Any better solution for this?\n\t/*if(cls.mvdplayback && cl_multiview.value)\n\t{\n\t\treturn;\n\t}*/\n\n\tcl.players[slot].skin_refresh = true;\n\tneed_skin_refresh = true;\n}\n\nvoid TP_RefreshSkins(void)\n{\n\tint i;\n\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t\tTP_RefreshSkin(i);\n}\n\nvoid OnChangeSkinAndColorForcing(cvar_t *var, char *string, qbool *cancel)\n{\n\tOnChangeColorForcing(var, string, cancel);\n\tOnChangeSkinForcing(var, string, cancel);\n\treturn;\n}\n\nvoid OnChangeColorForcing(cvar_t *var, char *string, qbool *cancel)\n{\n\tTP_RefreshSkins();\n\treturn;\n}\n\nvoid TP_ColorForcing (cvar_t *topcolor, cvar_t *bottomcolor)\n{\n\tint\ttop, bottom;\n\n\tif (Cmd_Argc() == 1) {\n\t\tif (topcolor->integer == -1 && bottomcolor->integer == -1)\n\t\t\tCom_Printf (\"\\\"%s\\\" is \\\"off\\\"\\n\", Cmd_Argv(0));\n\t\telse\n\t\t\tCom_Printf (\"\\\"%s\\\" is \\\"%i %i\\\"\\n\", Cmd_Argv(0), topcolor->integer, bottomcolor->integer);\n\t\treturn;\n\t}\n\n\tif (!strcasecmp(Cmd_Argv(1), \"off\") || !strcasecmp(Cmd_Argv(1), \"\")) {\n\t\tCvar_SetValue(topcolor, -1);\n\t\tCvar_SetValue(bottomcolor, -1);\n\t\tTP_RefreshSkins();\n\t\treturn;\n\t}\n\n\tif (Cmd_Argc() == 2) {\n\t\ttop = bottom = atoi(Cmd_Argv(1));\n\t} else {\n\t\ttop = atoi(Cmd_Argv(1));\n\t\tbottom = atoi(Cmd_Argv(2));\n\t}\n\n\tCvar_SetValue(topcolor, bound(0, top, 16));\n\tCvar_SetValue(bottomcolor, bound(0, bottom, 16));\n\n\tTP_RefreshSkins();\n}\n\nvoid TP_TeamColor_f(void)\n{\n\tTP_ColorForcing(&cl_teamtopcolor, &cl_teambottomcolor);\n}\n\nvoid TP_EnemyColor_f(void)\n{\n\tTP_ColorForcing(&cl_enemytopcolor, &cl_enemybottomcolor);\n}\n\n/************************* BASIC MATCH INFO FUNCTIONS *************************/\n\nchar *TP_PlayerName (void)\n{\n\tstatic char myname[MAX_INFO_STRING];\n\n\tstrlcpy (myname, Info_ValueForKey(cl.players[cl.playernum].userinfo, \"name\"), MAX_INFO_STRING);\n\treturn myname;\n}\n\nchar *TP_PlayerTeam (void)\n{\n\tstatic char myteam[MAX_INFO_STRING];\n\n\tstrlcpy (myteam, cl.players[cl.playernum].team, MAX_INFO_STRING);\n\treturn myteam;\n}\n\nint\tTP_CountPlayers (void)\n{\n\tint\ti, count = 0;\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (cl.players[i].name[0] && !cl.players[i].spectator)\n\t\t\tcount++;\n\t}\n\treturn count;\n}\n\n// tells the playernum of the player in the current point of view\nint TP_CurrentTrackNum(void)\n{\n\tif (!cl.spectator) return cl.playernum;\n\telse return Cam_TrackNum();\n}\n\n// returns true if the player in the current POV is from given team\nqbool TP_ThisPOV_IsHisTeam(const char* team)\n{\n\tint pn = TP_CurrentTrackNum();\n\t\n\tif (pn < 0 || pn >= MAX_CLIENTS)\n\t\treturn false;\n\telse\n\t\treturn !strcmp(team, cl.players[pn].team);\n}\n\n// returns the team of the player in the current POV\n// if no player tracked in the POV, returns NULL pointer\nstatic char* TP_ThisPOV_Team(void) {\n\tint n = TP_CurrentTrackNum();\n\tif (n >= 0 && n < MAX_CLIENTS)\n\t\treturn cl.players[n].team;\n\telse\n\t\treturn NULL;\n}\n\nint TP_PlayersNumber(int userid, const char* team)\n{\n\tqbool t1 = TP_ThisPOV_IsHisTeam(team); // is the looked up one our teammate?\n\tqbool t2;\n\tchar *pt = TP_ThisPOV_Team();\n\tint pc = 0, i;\n\tplayer_info_t* cp;\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tcp = &cl.players[i];\n\t\tif (!cp->name[0] || cp->spectator) continue;\n\t\tif (pt)\n\t\t\tt2 = !strcmp(cp->team, pt);\t// is the current one our teammate?\n\t\telse\n\t\t\tt2 = false;\n\n\t\tif ((t1 && t2) || (!t1 && !t2)) {\n\t\t\tpc++;\n\t\t}\n\t\tif (cp->userid == userid)\n\t\t\treturn pc;\n\t}\n\n\treturn 0;\n}\n\nchar *TP_MapName(void)\n{\n\treturn host_mapname.string;\n}\n\nchar *MT_GetSkyGroupName(char *mapname, qbool *system);\n\nchar *TP_GetSkyGroupName(char *mapname, qbool *system)\n{\n\treturn MT_GetSkyGroupName(mapname, system);\n}\n\nchar *MT_GetMapGroupName(char *mapname, qbool *system);\n\nchar *TP_GetMapGroupName(char *mapname, qbool *system)\n{\n\treturn MT_GetMapGroupName(mapname, system);\n}\n\n/****************************** PUBLIC FUNCTIONS ******************************/\n\nvoid TP_NewMap (void)\n{\n\tmemset (&vars, 0, sizeof(vars));\n\tTP_FindModelNumbers ();\n\n\tTP_LocFiles_NewMap();\n\tTP_ExecTrigger (\"f_newmap\");\n\tif (cl.teamfortress) {\n\t\tV_TF_ClearGrenadeEffects();\n\t}\n\tIgnore_ResetFloodList();\n}\n\nint TP_CategorizeMessage (const char *s, int *offset)\n{\n\tint i, msglen, len, flags, tracknum;\n\tplayer_info_t\t*player;\n\tchar *name, *team=NULL;\n\n\ttracknum = -1;\n\tif (cl.spectator && (tracknum = Cam_TrackNum()) != -1)\n\t\tteam = cl.players[tracknum].team;\n\telse if (!cl.spectator)\n\t\tteam = cl.players[cl.playernum].team;\n\n\tflags = msgtype_unknown;\n\t*offset = 0;\n\tif (!(msglen = strlen(s)))\n\t\treturn msgtype_unknown;\n\n\tfor (i = 0, player = cl.players; i < MAX_CLIENTS; i++, player++)\t{\n\t\tif (!player->name[0])\n\t\t\tcontinue;\n\t\tname = Info_ValueForKey (player->userinfo, \"name\");\n\t\tlen = strlen(name);\n\t\tlen = min (len, 31);\n\t\t// check messagemode1\n\t\tif (len + 2 <= msglen && s[len] == ':' && s[len + 1] == ' ' && !strncmp(name, s, len))\t{\n\t\t\tif (player->spectator)\n\t\t\t\tflags |= msgtype_spec;\n\t\t\telse\n\t\t\t\tflags |= msgtype_normal;\n\t\t\t*offset = len + 2;\n\t\t}\n\t\t// check messagemode2\n\t\telse if (s[0] == '(' && len + 4 <= msglen && !strncmp(s + len + 1, \"): \", 3) && !strncmp(name, s + 1, len)\n\n\t\t         && (!cl.spectator || tracknum != -1)\n\t\t        ) {\n\t\t\t// no team messages in teamplay 0, except for our own\n\t\t\tif (i == cl.playernum || ( cl.teamplay && !strcmp(team, player->team)) )\n\t\t\t\tflags |= msgtype_team;\n\t\t\t*offset = len + 4;\n\t\t}\n\t\t//check spec mm2\n\t\telse if (cl.spectator && !strncmp(s, \"[SPEC] \", 7) && player->spectator &&\n\t\t         len + 9 <= msglen && s[len + 7] == ':' && s[len + 8] == ' ' && !strncmp(name, s + 7, len)) {\n\t\t\tflags |= msgtype_specteam;\n\t\t\t*offset = len + 9;\n\t\t}\n\t}\n\treturn flags;\n}\n\n/****************************** POINTING & TOOK ******************************/\n\n// symbolic names used in tp_took, tp_pickup, tp_point commands\nchar *pknames[] = {\"quad\", \"pent\", \"ring\", \"suit\", \"ra\", \"ya\",\t\"ga\",\n                   \"mh\", \"health\", \"lg\", \"rl\", \"gl\", \"sng\", \"ng\", \"ssg\", \"pack\",\n                   \"cells\", \"rockets\", \"nails\", \"shells\", \"flag\",\n                   \"teammate\", \"enemy\", \"eyes\", \"sentry\", \"disp\", \"quaded\", \"pented\", \\\n\t\t\t\t   \"rune1\", \"rune2\", \"rune3\", \"rune4\", \"resistance\", \"strength\", \"haste\", \"regeneration\"};\n\n#define default_pkflags ((unsigned int) (it_powerups|it_armor|it_weapons|it_mh|it_pack| \\\n\t\t\t\tit_rockets|it_cells|it_pack|it_flag|it_runes))\n\n // tp_took\n#define default_tookflags ((unsigned int) (it_powerups|it_armor|it_weapons|it_pack| \\\n\t\t\t\tit_rockets|it_cells|it_mh|it_flag|it_runes))\n\n/*\npowerups flag runes players suit armor sentry  mh disp rl lg pack gl sng rockets cells nails\nNotice this list takes into account ctf/tf as well. Dm players don't worry about ctf/tf items.\n\n below are defaults for tp_point (what comes up in point. also see tp_pointpriorities to prioritize this list) First items have highest priority (powerups in this case)\n*/\n// tp_point\n#define default_pointflags ((unsigned int) (it_powerups|it_players|it_armor|it_weapons|it_mh|it_pack| \\\n\t\t\t\tit_rockets|it_cells|it_sentry|it_disp|it_flag|it_runes))\n\nunsigned int pkflags = default_pkflags;\nunsigned int tookflags = default_tookflags;\nunsigned int pointflags = default_pointflags;\nbyte tookpriorities[NUM_ITEMFLAGS];\nbyte pointpriorities[NUM_ITEMFLAGS];\n\nstatic void DumpFlagCommand(FILE *f, char *name, unsigned int flags, unsigned int default_flags)\n{\n\tint i;\n\tunsigned int all_flags = UINT_MAX;\n\n\tfprintf(f, \"%s \", name);\n\n\tif (flags == 0) {\n\t\tfprintf(f, \"none\\n\");\n\t\treturn;\n\t}\n\tif (flags == all_flags) {\n\t\tfprintf(f, \"all\\n\");\n\t\treturn;\n\t} else if (flags == default_flags) {\n\t\tfprintf(f, \"default\\n\");\n\t\treturn;\n\t}\n\n\tif ((flags & it_powerups) == it_powerups) {\n\t\tfprintf(f, \"powerups \");\n\t\tflags &= ~it_powerups;\n\t}\n\tif ((flags & it_weapons) == it_weapons) {\n\t\tfprintf(f, \"weapons \");\n\t\tflags &= ~it_weapons;\n\t}\n\tif ((flags & it_armor) == it_armor) {\n\t\tfprintf(f, \"armor \");\n\t\tflags &= ~it_armor;\n\t}\n\tif ((flags & it_ammo) == it_ammo) {\n\t\tfprintf(f, \"ammo \");\n\t\tflags &= ~it_ammo;\n\t}\n\tif ((flags & it_players) == it_players) {\n\t\tfprintf(f, \"players \");\n\t\tflags &= ~it_players;\n\t}\n\tif ((flags & it_runes) == it_runes) {\n\t\tfprintf(f, \"runes \");\n\t\tflags &= ~it_runes;\n\t}\n\tfor (i = 0; i < NUM_ITEMFLAGS; i++) {\n\t\tif (flags & (1 << i))\n\t\t\tfprintf (f, \"%s \", pknames[i]);\n\t}\n\tfprintf(f, \"\\n\");\n}\n\nvoid DumpFlagCommands(FILE *f)\n{\n\tDumpFlagCommand(f, \"tp_pickup   \", pkflags, default_pkflags);\n\tDumpFlagCommand(f, \"tp_took     \", tookflags, default_tookflags);\n\tDumpFlagCommand(f, \"tp_point    \", pointflags, default_pointflags);\n}\n\nstatic void FlagCommand(unsigned int* flags, unsigned int defaultflags)\n{\n\tint i, j, c, offset = 0;\n\tunsigned int flag;\n\tchar* p, str[255] = { 0 };\n\tqbool removeflag = false;\n\tbyte *priorities = (flags == &tookflags) ? tookpriorities : pointpriorities;\n\tqbool use_priorities = (flags == &tookflags) ? tp_tookpriorities.integer : tp_pointpriorities.integer;\n\n\tc = Cmd_Argc();\n\tif (c == 1) {\n\t\tqbool notfirst = false;\n\t\tif (!*flags)\n\t\t\tstrlcpy(str, \"none\", sizeof(str));\n\n\t\tif (use_priorities) {\n\t\t\tint p;\n\t\t\tfor (p = 0; p < NUM_ITEMFLAGS; ++p) {\n\t\t\t\tfor (i = 0; i < NUM_ITEMFLAGS; i++) {\n\t\t\t\t\tif (priorities[i] == p && (*flags & (1 << i))) {\n\t\t\t\t\t\tif (notfirst) {\n\t\t\t\t\t\t\tCom_Printf(\" \");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tnotfirst = true;\n\t\t\t\t\t\tCom_Printf(\"%s\", pknames[i]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor (i = 0; i < NUM_ITEMFLAGS; i++) {\n\t\t\t\tif (*flags & (1 << i)) {\n\t\t\t\t\tif (notfirst) {\n\t\t\t\t\t\tCom_Printf(\" \");\n\t\t\t\t\t}\n\n\t\t\t\t\tnotfirst = true;\n\t\t\t\t\tCom_Printf(\"%s\", pknames[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tCom_Printf(\"\\n\");\n\t\treturn;\n\t}\n\n\tif (c == 2 && !strcasecmp(Cmd_Argv(1), \"none\")) {\n\t\t*flags = 0;\n\t\tmemset(priorities, 0, NUM_ITEMFLAGS);\n\t\treturn;\n\t}\n\n\tif (*Cmd_Argv(1) != '+' && *Cmd_Argv(1) != '-') {\n\t\t*flags = 0;\n\t\tmemset(priorities, 0, NUM_ITEMFLAGS);\n\t}\n\telse if (*Cmd_Argv(1) == '+') {\n\t\tfor (i = 0; i < NUM_ITEMFLAGS; ++i) {\n\t\t\tif (*flags & (1 << i)) {\n\t\t\t\t++offset;\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (i = 1; i < c; i++) {\n\t\tp = Cmd_Argv(i);\n\t\tif (*p == '+') {\n\t\t\tremoveflag = false;\n\t\t\tp++;\n\t\t}\n\t\telse if (*p == '-') {\n\t\t\tremoveflag = true;\n\t\t\tp++;\n\t\t}\n\n\t\tflag = 0;\n\t\tfor (j = 0; j < NUM_ITEMFLAGS; j++) {\n\t\t\tif (!strcasecmp(p, pknames[j])) {\n\t\t\t\tflag = 1 << j;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!flag) {\n\t\t\tif (!strcasecmp(p, \"armor\"))\n\t\t\t\tflag = it_armor;\n\t\t\telse if (!strcasecmp(p, \"weapons\"))\n\t\t\t\tflag = it_weapons;\n\t\t\telse if (!strcasecmp(p, \"powerups\"))\n\t\t\t\tflag = it_powerups;\n\t\t\telse if (!strcasecmp(p, \"ammo\"))\n\t\t\t\tflag = it_ammo;\n\t\t\telse if (!strcasecmp(p, \"players\"))\n\t\t\t\tflag = it_players;\n\t\t\telse if (!strcasecmp(p, \"default\"))\n\t\t\t\tflag = defaultflags;\n\t\t\telse if (!strcasecmp(p, \"runes\"))\n\t\t\t\tflag = it_runes;\n\t\t\telse if (!strcasecmp(p, \"all\"))\n\t\t\t\tflag = UINT_MAX; //(1 << NUM_ITEMFLAGS); //-1;\n\t\t}\n\n\t\tif (flags != &pointflags) {\n\t\t\tflag &= ~(it_sentry | it_disp | it_players);\n\t\t}\n\n\t\tif (removeflag) {\n\t\t\t*flags &= ~flag;\n\n\t\t\tfor (j = 1; j <= NUM_ITEMFLAGS; j++) {\n\t\t\t\tif (flag & (1 << (j - 1))) {\n\t\t\t\t\tpriorities[j - 1] = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (flag) {\n\t\t\t*flags |= flag;\n\n\t\t\tfor (j = 1; j <= NUM_ITEMFLAGS; j++) {\n\t\t\t\tif (flag & (1 << (j - 1))) {\n\t\t\t\t\tpriorities[j - 1] = i + offset;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid TP_Took_f (void)\n{\n\tFlagCommand (&tookflags, default_tookflags);\n}\n\nvoid TP_Pickup_f (void)\n{\n\tFlagCommand (&pkflags, default_pkflags);\n}\n\nvoid TP_Point_f (void)\n{\n\tFlagCommand (&pointflags, default_pointflags);\n}\n\ntypedef struct\n{\n\tunsigned int\t\titemflag;\n\tcvar_t\t*cvar;\n\tchar\t*modelname;\n\tvec3_t\toffset;\t\t// offset of model graphics center\n\tfloat\tradius;\t\t// model graphics radius\n\tunsigned int\t\tflags;\t\t// TODO: \"NOPICKUP\" (disp), \"TEAMENEMY\" (flag, disp)\n} item_t;\n\nitem_t\ttp_items[] = {\n                        {\tit_quad,\t&tp_name_quad,\t\"progs/quaddama.mdl\",\n                          {0, 0, 24},\t25,\n                        },\n                        {\tit_pent,\t&tp_name_pent,\t\"progs/invulner.mdl\",\n                          {0, 0, 22},\t25,\n                        },\n                        {\tit_ring,\t&tp_name_ring,\t\"progs/invisibl.mdl\",\n                          {0, 0, 16},\t12,\n                        },\n                        {\tit_suit,\t&tp_name_suit,\t\"progs/suit.mdl\",\n                          {0, 0, 24}, 20,\n                        },\n                        {\tit_lg,\t\t&tp_name_lg,\t\"progs/g_light.mdl\",\n                          {0, 0, 30},\t20,\n                        },\n                        {\tit_rl,\t\t&tp_name_rl,\t\"progs/g_rock2.mdl\",\n                          {0, 0, 30},\t20,\n                        },\n                        {\tit_gl,\t\t&tp_name_gl,\t\"progs/g_rock.mdl\",\n                          {0, 0, 30},\t20,\n                        },\n                        {\tit_sng,\t\t&tp_name_sng,\t\"progs/g_nail2.mdl\",\n                          {0, 0, 30},\t20,\n                        },\n                        {\tit_ng,\t\t&tp_name_ng,\t\"progs/g_nail.mdl\",\n                          {0, 0, 30},\t20,\n                        },\n                        {\tit_ssg,\t\t&tp_name_ssg,\t\"progs/g_shot.mdl\",\n                          {0, 0, 30},\t20,\n                        },\n                        {\tit_cells,\t&tp_name_cells,\t\"maps/b_batt0.bsp\",\n                          {16, 16, 24},\t18,\n                        },\n                        {\tit_cells,\t&tp_name_cells,\t\"maps/b_batt1.bsp\",\n                          {16, 16, 24},\t18,\n                        },\n                        {\tit_rockets,\t&tp_name_rockets,\"maps/b_rock0.bsp\",\n                          {8, 8, 20},\t18,\n                        },\n                        {\tit_rockets,\t&tp_name_rockets,\"maps/b_rock1.bsp\",\n                          {16, 8, 20},\t18,\n                        },\n                        {\tit_nails,\t&tp_name_nails,\t\"maps/b_nail0.bsp\",\n                          {16, 16, 10},\t18,\n                        },\n                        {\tit_nails,\t&tp_name_nails,\t\"maps/b_nail1.bsp\",\n                          {16, 16, 10},\t18,\n                        },\n                        {\tit_shells,\t&tp_name_shells,\"maps/b_shell0.bsp\",\n                          {16, 16, 10},\t18,\n                        },\n                        {\tit_shells,\t&tp_name_shells,\"maps/b_shell1.bsp\",\n                          {16, 16, 10},\t18,\n                        },\n                        {\tit_health,\t&tp_name_health,\"maps/b_bh10.bsp\",\n                          {16, 16, 8},\t18,\n                        },\n                        {\tit_health,\t&tp_name_health,\"maps/b_bh25.bsp\",\n                          {16, 16, 8},\t18,\n                        },\n                        {\tit_mh,\t\t&tp_name_mh,\t\"maps/b_bh100.bsp\",\n                          {16, 16, 14},\t20,\n                        },\n                        {\tit_pack,\t&tp_name_backpack, \"progs/backpack.mdl\",\n                          {0, 0, 18},\t18,\n                        },\n                        {\tit_flag,\t&tp_name_flag,\t\"progs/tf_flag.mdl\",\n                          {0, 0, 14},\t25,\n                        },\n                        {\tit_flag,\t&tp_name_flag,\t\"progs/tf_stan.mdl\",\n                          {0, 0, 45},\t40,\n                        },\n                        {\tit_flag,\t&tp_name_flag,\t\"progs/w_g_key.mdl\",\n                          {0, 0, 20},\t18,\n                        },\n                        {\tit_flag,\t&tp_name_flag,\t\"progs/w_s_key.mdl\",\n                          {0, 0, 20},\t18,\n                        },\n                        {\tit_flag,\t&tp_name_flag,\t\"progs/m_g_key.mdl\",\n                          {0, 0, 20},\t18,\n                        },\n                        {\tit_flag,\t&tp_name_flag,\t\"progs/m_s_key.mdl\",\n                          {0, 0, 20},\t18,\n                        },\n                        {\tit_flag,\t&tp_name_flag,\t\"progs/b_s_key.mdl\",\n                          {0, 0, 20},\t18,\n                        },\n                        {\tit_flag,\t&tp_name_flag,\t\"progs/b_g_key.mdl\",\n                          {0, 0, 20},\t18,\n                        },\n\t\t\t\t\t\t{\tit_flag,\t&tp_name_flag,\t\"progs/stag.mdl\",\n\t\t\t\t\t\t  {0, 0, 20},\t18,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\tit_flag,\t&tp_name_flag,\t\"progs/basrkey.bsp\",\n\t\t\t\t\t\t  {0, 0, 20},\t18,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\tit_flag,\t&tp_name_flag,\t\"progs/basbkey.bsp\",\n\t\t\t\t\t\t  {0, 0, 20},\t18,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\tit_flag,\t&tp_name_flag,\t\"progs/ff_flag.mdl\",\n\t\t\t\t\t\t  {0, 0, 20},\t18,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\tit_flag,\t&tp_name_flag,\t\"progs/harbflag.mdl\",\n\t\t\t\t\t\t  {0, 0, 20},\t18,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\tit_flag,\t&tp_name_flag,\t\"progs/princess.mdl\",\n\t\t\t\t\t\t  {0, 0, 20},\t18,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\tit_flag,\t&tp_name_flag,\t\"progs/flag.mdl\",\n                          {0, 0, 14},\t25,\n                        },\n                        {\tit_rune1,\t&tp_name_rune1,\t\"progs/end1.mdl\",\n                          {0, 0, 20},\t18,\n                        },\n                        {\tit_rune2,\t&tp_name_rune2,\t\"progs/end2.mdl\",\n                          {0, 0, 20},\t18,\n                        },\n                        {\tit_rune3,\t&tp_name_rune3,\t\"progs/end3.mdl\",\n                          {0, 0, 20},\t18,\n                        },\n                        {\t(unsigned int) it_rune4,\t&tp_name_rune4,\t\"progs/end4.mdl\",\n                          {0, 0, 20},\t18,\n                        },\n                        {\tit_ra|it_ya|it_ga, NULL,\t\"progs/armor.mdl\",\n                          {0, 0, 24},\t22,\n                        },\n                        {\tit_sentry, &tp_name_sentry, \"progs/turrgun.mdl\",\n                          {0, 0, 23},\t25,\n                        },\n                        {\tit_disp, &tp_name_disp,\t\"progs/disp.mdl\",\n                          {0, 0, 24},\t25,\n                        }\n                    };\n\n#define NUMITEMS (sizeof(tp_items) / sizeof(tp_items[0]))\n\nitem_t *model2item[MAX_MODELS];\n\nvoid TP_FindModelNumbers (void)\n{\n\tint i, j;\n\tchar *s;\n\titem_t *item;\n\n\tfor (i = 0 ; i < MAX_MODELS ; i++) {\n\t\tmodel2item[i] = NULL;\n\t\ts = cl.model_name[i];\n\t\tif (!s)\n\t\t\tcontinue;\n\t\tfor (j = 0, item = tp_items ; j < NUMITEMS ; j++, item++)\n\t\t\tif (!strcmp(s, item->modelname))\n\t\t\t\tmodel2item[i] = item;\n\t}\n}\n\n// on success, result is non-zero\n// on failure, result is zero\n// for armors, returns skinnum+1 on success\nstatic int FindNearestItem (int flags, item_t **pitem)\n{\n\tframe_t *frame;\n\tpacket_entities_t *pak;\n\tentity_state_t *ent, *bestent = NULL;\n\tint\ti;\n\tfloat bestdist, dist;\n\tvec3_t org, v;\n\titem_t *item;\n\n\tVectorCopy (cl.frames[cl.validsequence & UPDATE_MASK].playerstate[cl.playernum].origin, org);\n\n\t// look in previous frame\n\tframe = &cl.frames[cl.oldvalidsequence&UPDATE_MASK];\n\tpak = &frame->packet_entities;\n\tbestdist = 100 * 100;\n\t*pitem = NULL;\n\tfor (i = 0, ent = pak->entities; i < pak->num_entities; i++, ent++) {\n\t\titem = model2item[ent->modelindex];\n\t\tif (!item || !(item->itemflag & flags))\n\t\t\tcontinue;\n\n\t\tVectorSubtract (ent->origin, org, v);\n\t\tVectorAdd (v, item->offset, v);\n\t\tdist = DotProduct (v, v);\n\n\t\tif (dist <= bestdist) {\n\t\t\tbestdist = dist;\n\t\t\tbestent = ent;\n\t\t\t*pitem = item;\n\t\t}\n\t}\n\n\tif (bestent)\n\t\tstrlcpy(vars.nearestitemloc, TP_LocationName(bestent->origin), sizeof(vars.nearestitemloc));\n\telse\n\t\tvars.nearestitemloc[0] = 0;\n\n\tif (bestent && (*pitem)->itemflag == it_armor)\n\t\treturn bestent->skinnum + 1;\t// 1 = green, 2 = yellow, 3 = red\n\n\treturn bestent ? bestent->modelindex : 0;\n}\n\nchar *Macro_LastTookOrPointed (void)\n{\n\tif (vars.tooktime && vars.tooktime > vars.pointtime && cls.realtime - vars.tooktime < 5)\n\t\treturn Macro_TookAtLoc();\n\telse if (vars.pointtime && vars.tooktime <= vars.pointtime && cls.realtime - vars.pointtime < 5)\n\t\treturn Macro_LastPointAtLoc();\n\n\tsnprintf(macro_buf, sizeof(macro_buf), \"%s %s %s\", tp_name_nothing.string, tp_name_at.string, tp_name_someplace.string);\n\treturn macro_buf;\n}\n\nstatic qbool CheckTrigger (void)\n{\n\tint\ti, count;\n\tplayer_info_t *player;\n\tchar *myteam;\n\textern cvar_t tp_forceTriggers;\n\n\tif (cl.spectator || Rulesets_RestrictTriggers())\n\t\treturn false;\n\n\tif (tp_forceTriggers.value)\n\t\treturn true;\n\n\tif (!cl.teamplay)\n\t\treturn false;\n\n\tcount = 0;\n\tmyteam = cl.players[cl.playernum].team;\n\tfor (i = 0, player= cl.players; i < MAX_CLIENTS; i++, player++) {\n\t\tif (player->name[0] && !player->spectator && i != cl.playernum && !strcmp(player->team, myteam))\n\t\t\tcount++;\n\t}\n\n\treturn count;\n}\n\nstatic int TP_TookPriority(unsigned int flag)\n{\n\tint i, priority = 0;\n\tunsigned int item_flag;\n\tint item_priority;\n\n\tif (!tp_tookpriorities.integer)\n\t\treturn 0;\n\n\tfor (i = 0; i < NUM_ITEMFLAGS; i++) {\n\t\titem_flag = (unsigned int)1 << i;\n\t\titem_priority = tookpriorities[i];\n\n\t\tif (!(flag & item_flag) || !item_priority)\n\t\t\tcontinue;\n\n\t\tif (!priority || item_priority < priority)\n\t\t\tpriority = item_priority;\n\t}\n\n\treturn priority;\n}\n\nstatic qbool TP_TookPriorityCanReplace(unsigned int flag)\n{\n\tint priority, old_priority;\n\n\tpriority = TP_TookPriority(flag);\n\told_priority = TP_TookPriority((unsigned int)vars.tookflag);\n\n\treturn !old_priority || (priority && priority <= old_priority);\n}\n\nstatic qbool TP_ShouldStoreTook(unsigned int flag)\n{\n\tif (TOOK_EMPTY())\n\t\treturn true;\n\n\tif (!tp_tookpriorities.integer)\n\t\treturn true;\n\n\treturn TP_TookPriorityCanReplace(flag);\n}\n\nstatic void ExecTookTrigger (char *s, unsigned int flag, vec3_t org)\n{\n\tint pkflags_dmm, tookflags_dmm;\n\n\tpkflags_dmm = pkflags;\n\ttookflags_dmm = tookflags;\n\n\tif (!cl.teamfortress && cl.deathmatch >= 1 && cl.deathmatch <= 4) {\n\t\tif (cl.deathmatch == 4) {\n\t\t\tpkflags_dmm &= ~(it_ammo|it_weapons);\n\t\t\ttookflags_dmm &= ~(it_ammo|it_weapons);\n\t\t}\n\t}\n\tif (!((pkflags_dmm|tookflags_dmm) & flag))\n\t\treturn;\n\n\tif (TP_ShouldStoreTook(flag)) {\n\t\tvars.tooktime = cls.realtime;\n\t\tvars.tookflag = flag;\n\t\tstrlcpy(vars.tookname, s, sizeof(vars.tookname));\n\t\tstrlcpy(vars.tookloc, TP_LocationName(org), sizeof(vars.tookloc));\n\t}\n\n\tif ((tookflags_dmm & flag) && CheckTrigger())\n\t\tTP_ExecTrigger (\"f_took\");\n}\n\nvoid TP_ParsePlayerInfo(player_state_t *oldstate, player_state_t *state, player_info_t *info)\n{\n\tif (TP_NeedRefreshSkins()) {\n\t\tif ((state->effects & (EF_BLUE|EF_RED) ) != (oldstate->effects & (EF_BLUE|EF_RED)))\n\t\t\tTP_RefreshSkin(info - cl.players);\n\t}\n\n\tif (!cl.spectator && cl.teamplay && strcmp(info->team, TP_PlayerTeam())) {\n\t\tqbool eyes;\n\n\t\teyes = state->modelindex && cl.model_precache[state->modelindex] && cl.model_precache[state->modelindex]->modhint == MOD_EYES;\n\n\t\tif (state->effects & (EF_BLUE | EF_RED) || eyes) {\n\t\t\tvars.enemy_powerups = 0;\n\t\t\tvars.enemy_powerups_time = cls.realtime;\n\n\t\t\tif (state->effects & EF_BLUE)\n\t\t\t\tvars.enemy_powerups |= TP_QUAD;\n\t\t\tif (state->effects & EF_RED)\n\t\t\t\tvars.enemy_powerups |= TP_PENT;\n\t\t\tif (eyes)\n\t\t\t\tvars.enemy_powerups |= TP_RING;\n\t\t}\n\t}\n\tif (!cl.spectator && !cl.teamfortress && info - cl.players == cl.playernum) {\n\t\tif ((state->effects & (EF_FLAG1|EF_FLAG2)) && !(oldstate->effects & (EF_FLAG1|EF_FLAG2))) {\n\t\t\tExecTookTrigger (tp_name_flag.string, it_flag, cl.frames[cl.validsequence & UPDATE_MASK].playerstate[cl.playernum].origin);\n\t\t} else if (!(state->effects & (EF_FLAG1|EF_FLAG2)) && (oldstate->effects & (EF_FLAG1|EF_FLAG2))) {\n\t\t\tvars.lastdrop_time = cls.realtime;\n\t\t\tstrlcpy (vars.lastdroploc, Macro_Location(), sizeof (vars.lastdroploc));\n\t\t}\n\t}\n}\n\nstatic qbool TP_DetectWeaponPickup(void)\n{\n\tif (vars.items & ~vars.olditems & IT_LIGHTNING)\n\t\tExecTookTrigger(tp_name_lg.string, it_lg, cl.simorg);\n\telse if (vars.items & ~vars.olditems & IT_ROCKET_LAUNCHER)\n\t\tExecTookTrigger(tp_name_rl.string, it_rl, cl.simorg);\n\telse if (vars.items & ~vars.olditems & IT_GRENADE_LAUNCHER)\n\t\tExecTookTrigger(tp_name_gl.string, it_gl, cl.simorg);\n\telse if (vars.items & ~vars.olditems & IT_SUPER_NAILGUN)\n\t\tExecTookTrigger(tp_name_sng.string, it_sng, cl.simorg);\n\telse if (vars.items & ~vars.olditems & IT_NAILGUN)\n\t\tExecTookTrigger(tp_name_ng.string, it_ng, cl.simorg);\n\telse if (vars.items & ~vars.olditems & IT_SUPER_SHOTGUN)\n\t\tExecTookTrigger(tp_name_ssg.string, it_ssg, cl.simorg);\n\telse\n\t\treturn false;\n\treturn true;\n}\n\nvoid TP_CheckPickupSound (char *s, vec3_t org)\n{\n\titem_t *item;\n\n\tif (cl.spectator)\n\t\treturn;\n\n\tif (!strcmp(s, \"items/damage.wav\"))\n\t\tExecTookTrigger (tp_name_quad.string, it_quad, org);\n\telse if (!strcmp(s, \"items/protect.wav\"))\n\t\tExecTookTrigger (tp_name_pent.string, it_pent, org);\n\telse if (!strcmp(s, \"items/inv1.wav\"))\n\t\tExecTookTrigger (tp_name_ring.string, it_ring, org);\n\telse if (!strcmp(s, \"items/suit.wav\"))\n\t\tExecTookTrigger (tp_name_suit.string, it_suit, org);\n\telse if (!strcmp(s, \"items/health1.wav\") || !strcmp(s, \"items/r_item1.wav\"))\n\t\tExecTookTrigger (tp_name_health.string, it_health, org);\n\telse if (!strcmp(s, \"items/r_item2.wav\"))\n\t\tExecTookTrigger (tp_name_mh.string, it_mh, org);\n\telse\n\t\tgoto more;\n\treturn;\n\nmore:\n\tif (!cl.validsequence || !cl.oldvalidsequence)\n\t\treturn;\n\n\t// weapons\n\tif (!strcmp(s, \"weapons/pkup.wav\"))\t{\n\t\tif (FindNearestItem (it_weapons, &item)) {\n\t\t\tExecTookTrigger (item->cvar->string, item->itemflag, org);\n\t\t} else if (vars.stat_framecounts[STAT_ITEMS] == cls.framecount) {\n\t\t\tif (! TP_DetectWeaponPickup())\n\t\t\t\tcl.last_weapon_pickup = cls.framecount;\n\t\t} else {\n\t\t\tcl.last_weapon_pickup = cls.framecount;\n\t\t}\n\t\treturn;\n\t}\n\n\t// armor\n\tif (!strcmp(s, \"items/armor1.wav\"))\t{\n\t\tqbool armor_updated;\n\t\tint armortype;\n\n\t\tarmor_updated = (vars.stat_framecounts[STAT_ARMOR] == cls.framecount);\n\t\tarmortype = FindNearestItem (it_armor, &item);\n\t\tif (armortype == 1 || (!armortype && armor_updated && cl.stats[STAT_ARMOR] == 100))\n\t\t\tExecTookTrigger (tp_name_ga.string, it_ga, org);\n\t\telse if (armortype == 2 || (!armortype && armor_updated && cl.stats[STAT_ARMOR] == 150))\n\t\t\tExecTookTrigger (tp_name_ya.string, it_ya, org);\n\t\telse if (armortype == 3 || (!armortype && armor_updated && cl.stats[STAT_ARMOR] == 200))\n\t\t\tExecTookTrigger (tp_name_ra.string, it_ra, org);\n\t\telse \n\t\t\tcl.last_armor_pickup = cls.framecount;\n\t\treturn;\n\t}\n\n\t// backpack, ammo or runes\n\tif (!strcmp (s, \"weapons/lock4.wav\")) {\n\t\tif (FindNearestItem(it_ammo | it_pack | it_runes, &item))\n\t\t\tExecTookTrigger(item->cvar->string, item->itemflag, org);\n\t\telse\n\t\t\tcl.last_ammo_pickup = cls.framecount;\n\t\treturn;\n\t}\n}\n\nqbool TP_IsItemVisible(item_vis_t *visitem)\n{\n\tvec3_t end, v;\n\ttrace_t trace;\n\n\tif (visitem->dist <= visitem->radius)\n\t\treturn true;\n\n\tVectorNegate (visitem->dir, v);\n\tVectorNormalizeFast (v);\n\tVectorMA (visitem->entorg, visitem->radius, v, end);\n\ttrace = PM_TraceLine (visitem->vieworg, end);\n\tif ((int)trace.fraction == 1)\n\t\treturn true;\n\n\tVectorMA (visitem->entorg, visitem->radius, visitem->right, end);\n\tVectorSubtract (visitem->vieworg, end, v);\n\tVectorNormalizeFast (v);\n\tVectorMA (end, visitem->radius, v, end);\n\ttrace = PM_TraceLine (visitem->vieworg, end);\n\tif ((int)trace.fraction == 1)\n\t\treturn true;\n\n\tVectorMA(visitem->entorg, -visitem->radius, visitem->right, end);\n\tVectorSubtract(visitem->vieworg, end, v);\n\tVectorNormalizeFast(v);\n\tVectorMA(end, visitem->radius, v, end);\n\ttrace = PM_TraceLine(visitem->vieworg, end);\n\tif ((int)trace.fraction == 1)\n\t\treturn true;\n\n\tVectorMA(visitem->entorg, visitem->radius, visitem->up, end);\n\tVectorSubtract(visitem->vieworg, end, v);\n\tVectorNormalizeFast(v);\n\tVectorMA (end, visitem->radius, v, end);\n\ttrace = PM_TraceLine(visitem->vieworg, end);\n\tif ((int)trace.fraction == 1)\n\t\treturn true;\n\n\t// use half the radius, otherwise it's possible to see through floor in some places\n\tVectorMA(visitem->entorg, -visitem->radius / 2, visitem->up, end);\n\tVectorSubtract(visitem->vieworg, end, v);\n\tVectorNormalizeFast(v);\n\tVectorMA(end, visitem->radius, v, end);\n\ttrace = PM_TraceLine(visitem->vieworg, end);\n\tif ((int)trace.fraction == 1)\n\t\treturn true;\n\n\treturn false;\n}\n\nstatic float TP_RankPoint(item_vis_t *visitem)\n{\n\tvec3_t v2, v3;\n\tfloat miss;\n\n\tif (visitem->dist < 10)\n\t\treturn -1;\n\n\tVectorScale (visitem->forward, visitem->dist, v2);\n\tVectorSubtract (v2, visitem->dir, v3);\n\tmiss = VectorLength (v3);\n\tif (miss > 300)\n\t\treturn -1;\n\tif (miss > visitem->dist * (tp_pointpriorities.value ? 0.55 : 1.7)) // for prioritized point\n\t\treturn -1;\t\t// over 60 degrees off\n\n\tif (tp_pointpriorities.value)\n\t\treturn 1;\n\tif (visitem->dist < 3000.0 / 8.0)\n\t\treturn miss * (visitem->dist * 8.0 * 0.0002f + 0.3f);\n\telse return miss;\n}\n\nvoid TP_FindPoint (void)\n{\n\tpacket_entities_t *pak;\n\tentity_state_t *ent;\n\tint\ti, j, tempflags;\n\tunsigned int pointflags_dmm;\n\tfloat best = -1, rank;\n\tentity_state_t *bestent = NULL;\n\tvec3_t ang;\n\titem_t *item, *bestitem = NULL;\n\tplayer_state_t *state, *beststate = NULL;\n\tplayer_info_t *info, *bestinfo = NULL;\n\titem_vis_t visitem;\n\textern cvar_t v_viewheight;\n\n\tif (vars.pointtime == cls.realtime)\n\t\treturn;\n\n\tif (!cl.validsequence)\n\t\tgoto nothing;\n\n\tang[0] = cl.viewangles[0]; ang[1] = cl.viewangles[1]; ang[2] = 0;\n\tAngleVectors (ang, visitem.forward, visitem.right, visitem.up);\n\tVectorCopy (cl.simorg, visitem.vieworg);\n\tvisitem.vieworg[2] += 22 + (v_viewheight.value ? bound (-7, v_viewheight.value, 4) : 0);\n\n\tpointflags_dmm = pointflags;\n\tif (!cl.teamfortress && cl.deathmatch >= 1 && cl.deathmatch <= 4) {\n\t\tif (cl.deathmatch == 4)\n\t\t\tpointflags_dmm &= ~it_ammo;\n\t\tif (cl.deathmatch != 1)\n\t\t\tpointflags_dmm &= ~it_weapons;\n\t}\n\n\tpak = &cl.frames[cl.validsequence & UPDATE_MASK].packet_entities;\n\tfor (i = 0,ent = pak->entities; i < pak->num_entities; i++, ent++) {\n\t\titem = model2item[ent->modelindex];\n\t\tif (!item || !(item->itemflag & pointflags_dmm))\n\t\t\tcontinue;\n\t\t// special check for armors\n\t\tif (item->itemflag == (it_ra|it_ya|it_ga)) {\n\t\t\tswitch (ent->skinnum) {\n\t\t\t\t\tcase 0: if (!(pointflags_dmm & it_ga)) continue; break;\n\t\t\t\t\tcase 1: if (!(pointflags_dmm & it_ya)) continue; break;\n\t\t\t\t\tcase 2: if (!(pointflags_dmm & it_ra)) continue; break;\n\t\t\t}\n\t\t}\n\n\t\tVectorAdd (ent->origin, item->offset, visitem.entorg);\n\t\tVectorSubtract (visitem.entorg, visitem.vieworg, visitem.dir);\n\t\tvisitem.dist = DotProduct (visitem.dir, visitem.forward);\n\t\tvisitem.radius = ent->effects & (EF_BLUE|EF_RED|EF_DIMLIGHT|EF_BRIGHTLIGHT) ? 200 : item->radius;\n\n\t\tif ((rank = TP_RankPoint(&visitem)) < 0)\n\t\t\tcontinue;\n\n\t\tif (tp_pointpriorities.value && rank != -1) {\n\t\t\ttempflags = item->itemflag;\n\t\t\tfor (j = 1; j < NUM_ITEMFLAGS; j++)\n\t\t\t\tif (!(tempflags & 1))\n\t\t\t\t\ttempflags >>= 1;\n\t\t\t\telse\n\t\t\t\t\tbreak;\n\n\t\t\t/* FIXME: Added to prevent potential array out of bounds.\n\t\t\t * Look into if its practically possible ... Anyway it wont\n\t\t\t * crash now in case the loop above runs out\n\t\t\t */\n\t\t\tif (j == NUM_ITEMFLAGS) {\n\t\t\t\tj--;\n\t\t\t}\n\t\t\trank = pointpriorities[j];\n\t\t}\n\n\t\t// check if we can actually see the object\n\t\tif ((rank < best || best < 0) && TP_IsItemVisible(&visitem)) {\n\t\t\tbest = rank;\n\t\t\tbestent = ent;\n\t\t\tbestitem = item;\n\t\t}\n\t}\n\n\tstate = cl.frames[cl.parsecount & UPDATE_MASK].playerstate;\n\tinfo = cl.players;\n\tfor (j = 0; j < MAX_CLIENTS; j++, info++, state++) {\n\t\tif (state->messagenum != cl.parsecount || j == cl.playernum || info->spectator)\n\t\t\tcontinue;\n\n\t\tif (\n\t\t    ( state->modelindex == cl_modelindices[mi_player] && ISDEAD(state->frame) ) ||\n\t\t    ( state->modelindex == cl_modelindices[mi_h_player] )\n\t\t)\n\t\t\tcontinue;\n\n\t\tVectorCopy (state->origin, visitem.entorg);\n\t\tvisitem.entorg[2] += 30;\n\t\tVectorSubtract (visitem.entorg, visitem.vieworg, visitem.dir);\n\t\tvisitem.dist = DotProduct (visitem.dir, visitem.forward);\n\t\tvisitem.radius = (state->effects & (EF_BLUE|EF_RED|EF_DIMLIGHT|EF_BRIGHTLIGHT) ) ? 200 : 27;\n\n\t\tif ((rank = TP_RankPoint(&visitem)) < 0)\n\t\t\tcontinue;\n\n\t\t// check if we can actually see the object\n\t\tif ((rank < best || best < 0) && TP_IsItemVisible(&visitem)) {\n\t\t\tqbool teammate, eyes = false;\n\n\t\t\teyes = state->modelindex && cl.model_precache[state->modelindex] && cl.model_precache[state->modelindex]->modhint == MOD_EYES;\n\t\t\tteammate = !!(cl.teamplay && !strcmp(info->team, TP_PlayerTeam()));\n\n\t\t\tif (eyes && !(pointflags_dmm & it_eyes))\n\t\t\t\tcontinue;\n\t\t\telse if (teammate && !(pointflags_dmm & it_teammate))\n\t\t\t\tcontinue;\n\t\t\telse if (!(pointflags_dmm & it_enemy))\n\t\t\t\tcontinue;\n\n\t\t\tbest = rank;\n\t\t\tbestinfo = info;\n\t\t\tbeststate = state;\n\t\t}\n\t}\n\n\tif (best >= 0 && bestinfo) {\n\t\tqbool teammate, eyes;\n\t\tchar *name, buf[256] = {0};\n        int flag = 0;\n\n\t\teyes = beststate->modelindex && cl.model_precache[beststate->modelindex] &&\n\t\t       cl.model_precache[beststate->modelindex]->modhint == MOD_EYES;\n\t\tif (cl.teamfortress) {\n\t\t\tteammate = !strcmp(Utils_TF_ColorToTeam(bestinfo->real_bottomcolor), TP_PlayerTeam());\n\n\t\t\tif (eyes)\n            {\n\t\t\t\tname = tp_name_eyes.string;\t\t//duck on 2night2.bsp (TF map)\n                flag = it_eyes;\n            }\n\t\t\telse if (cl.spectator)\n            {\n\t\t\t\tname = bestinfo->name;\n                flag = it_players;\n            }\n\t\t\telse if (teammate)\n            {\n\t\t\t\tname = tp_name_teammate.string[0] ? tp_name_teammate.string : \"teammate\";\n                flag = it_teammate;\n            }\n\t\t\telse\n            {\n\t\t\t\tname = tp_name_enemy.string;\n                flag = it_enemy;\n            }\n\n\t\t\tif (!eyes)\n\t\t\t\tname = va(\"%s%s%s\", name, name[0] ? \" \" : \"\", Skin_To_TFSkin(Info_ValueForKey(bestinfo->userinfo, \"skin\")));\n\t\t} else {\n\t\t\tteammate = (cl.teamplay && !strcmp(bestinfo->team, TP_PlayerTeam()));\n\n\t\t\tif (eyes)\n            {\n\t\t\t\tname = tp_name_eyes.string;\n                flag = it_eyes;\n            }\n\t\t\telse if (cl.spectator || (teammate && !tp_name_teammate.string[0]))\n            {\n\t\t\t\tname = bestinfo->name;\n                flag = it_teammate;\n            }\n\t\t\telse\n            {\n\t\t\t\tname = teammate ? tp_name_teammate.string : tp_name_enemy.string;\n                flag = teammate ? it_teammate : it_enemy;\n            }\n\t\t}\n\t\tif (beststate->effects & EF_BLUE)\n        {\n\t\t\tstrlcat (buf, tp_name_quaded.string, sizeof (buf) - strlen (buf));\n            flag |= it_quaded;\n        }\n\t\tif (beststate->effects & EF_RED)\n        {\n\t\t\tstrlcat (buf, va(\"%s%s\", buf[0] ? \" \" : \"\", tp_name_pented.string), sizeof (buf) - strlen (buf));\n            flag |= it_pented;\n        }\n\t\tstrlcat (buf, va(\"%s%s\", buf[0] ? \" \" : \"\", name), sizeof (buf) - strlen (buf));\n\t\tstrlcpy (vars.pointname, buf, sizeof (vars.pointname));\n        vars.pointflag = flag;\n\t\tstrlcpy (vars.pointloc, TP_LocationName(beststate->origin), sizeof(vars.pointloc));\n\t\t\n\t\tvars.pointtype = (teammate && !eyes) ? POINT_TYPE_TEAMMATE : POINT_TYPE_ENEMY;\n\t} else if (best >= 0) {\n\t\tchar *p;\n\n        vars.pointflag = bestitem->itemflag;\n\n        if (!bestitem->cvar) {\n\t\t\t// armors are special\n\t\t\tswitch (bestent->skinnum) {\n\t\t\t\t\tcase 0: p = tp_name_ga.string; vars.pointflag = it_ga; break;\n                    case 1: p = tp_name_ya.string; vars.pointflag = it_ya; break;\n\t\t\t\t\tdefault: p = tp_name_ra.string; vars.pointflag = it_ra;\n\t\t\t}\n\t\t} else {\n\t\t\tp = bestitem->cvar->string;\n\t\t}\n\n\t\tvars.pointtype = (bestitem->itemflag & (it_powerups|it_flag)) ? POINT_TYPE_POWERUP : POINT_TYPE_ITEM;\n\t\tstrlcpy (vars.pointname, p, sizeof(vars.pointname));\n\t\tstrlcpy (vars.pointloc, TP_LocationName(bestent->origin), sizeof(vars.pointloc));\n\t} else {\n\tnothing:\n\t\tstrlcpy (vars.pointname, tp_name_nothing.string, sizeof(vars.pointname));\n\t\tvars.pointloc[0] = 0;\n\t\tvars.pointtype = POINT_TYPE_ITEM;\n        vars.pointflag = 0;\n\t}\n\tvars.pointtime = cls.realtime;\n}\n\nvoid TP_ParseWeaponModel(model_t *model)\n{\n\tstatic model_t *last_model = NULL;\n\n\tif (cl.teamfortress && (!cl.spectator || Cam_TrackNum() != -1)) {\n\t\tif (model && !last_model)\n\t\t\tTP_ExecTrigger (\"f_reloadend\");\n\t\telse if (!model && last_model)\n\t\t\tTP_ExecTrigger (\"f_reloadstart\");\n\t}\n\tlast_model = model;\n}\n\nvoid TP_StatChanged (int stat, int value)\n{\n\tint effects;\n\n\teffects = cl.frames[cl.parsecount & UPDATE_MASK].playerstate[cl.playernum].effects;\n\n\tswitch (stat) {\n\t\tcase STAT_HEALTH:\n\t\t\tif (value > 0) {\n\t\t\t\tif (vars.health <= 0) {\n\t\t\t\t\textern cshift_t\tcshift_empty;\n\t\t\t\t\tif (cl.teamfortress)\n\t\t\t\t\t\tmemset (&cshift_empty, 0, sizeof(cshift_empty));\n\t\t\t\t\tif (CheckTrigger())\n\t\t\t\t\t\tTP_ExecTrigger (\"f_respawn\");\n\t\t\t\t}\n\t\t\t\tvars.health = value;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (vars.health > 0) {\t\t// We have just died\n\t\t\t\tvars.deathtrigger_time = cls.realtime;\n\t\t\t\tstrlcpy (vars.lastdeathloc, Macro_Location(), sizeof (vars.lastdeathloc));\n\n\t\t\t\tCountNearbyPlayers(true);\n\t\t\t\tvars.last_numenemies = vars.numenemies;\n\t\t\t\tvars.last_numfriendlies = vars.numfriendlies;\n\n\t\t\t\tif (CheckTrigger()) {\n\t\t\t\t\tif (cl.teamfortress && (cl.stats[STAT_ITEMS] & (IT_KEY1|IT_KEY2)))\n\t\t\t\t\t\tTP_ExecTrigger (\"f_flagdeath\");\n\t\t\t\t\telse if (effects & (EF_FLAG1|EF_FLAG2))\n\t\t\t\t\t\tTP_ExecTrigger (\"f_flagdeath\");\n\t\t\t\t\telse\n\t\t\t\t\t\tTP_ExecTrigger (\"f_death\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tvars.health = value;\n\t\t\tbreak;\n\t\tcase STAT_ITEMS:\n\t\t\tif (value & ~vars.items & (IT_KEY1|IT_KEY2)) {\n\t\t\t\tif (cl.teamfortress && !cl.spectator)\n\t\t\t\t\tExecTookTrigger (tp_name_flag.string, it_flag, cl.frames[cl.validsequence & UPDATE_MASK].playerstate[cl.playernum].origin);\n\t\t\t}\n\t\t\tif (!cl.spectator && cl.teamfortress && ~value & vars.items & (IT_KEY1|IT_KEY2)) {\n\t\t\t\tvars.lastdrop_time = cls.realtime;\n\t\t\t\tstrlcpy (vars.lastdroploc, Macro_Location(), sizeof (vars.lastdroploc));\n\t\t\t}\n\n\t\t\tvars.olditems = vars.items;\n\t\t\tvars.items = value;\n\n\t\t\t// If we have received a sound previously, update \n\t\t\tif (cl.last_weapon_pickup == cls.framecount)\n\t\t\t\tTP_DetectWeaponPickup();\n\t\t\telse if (cl.last_ammo_pickup == cls.framecount)\n\t\t\t\tExecTookTrigger(tp_name_backpack.string, it_pack, cl.simorg);\n\t\t\tcl.last_weapon_pickup = 0;\n\n\t\t\tbreak;\n\t\tcase STAT_ARMOR:\n\t\t\tif (cl.last_armor_pickup == cls.framecount)\n\t\t\t{\n\t\t\t\tif (value == 100)\n\t\t\t\t\tExecTookTrigger(tp_name_ga.string, it_ga, cl.simorg);\n\t\t\t\telse if (value == 150)\n\t\t\t\t\tExecTookTrigger(tp_name_ya.string, it_ya, cl.simorg);\n\t\t\t\telse if (value == 200)\n\t\t\t\t\tExecTookTrigger(tp_name_ra.string, it_ra, cl.simorg);\n\n\t\t\t\tcl.last_armor_pickup = 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase STAT_ACTIVEWEAPON:\n\t\t\tif (cl.stats[STAT_ACTIVEWEAPON] != vars.activeweapon) {\n\t\t\t\tTP_ExecTrigger (\"f_weaponchange\");\n\t\t\t\tvars.activeweapon = cl.stats[STAT_ACTIVEWEAPON];\n\t\t\t}\n\t\t\tbreak;\n\t}\n\tvars.stat_framecounts[stat] = cls.framecount;\n}\n\n/****************************** MESSAGE FILTERS ******************************/\n\n#ifdef _WIN32\n#define wcscasecmp(s1, s2)\t_wcsicmp  ((s1),   (s2))\n#endif\n\n#define MAX_FILTER_LENGTH 4\nchar filter_strings[8][MAX_FILTER_LENGTH + 1];\nint\tnum_filters = 0;\n\n//returns false if the message shouldn't be printed.\n//Matching filters are stripped from the message\nqbool TP_FilterMessage (wchar *source)\n{\n\tsize_t i, j, maxlen, len;\n\n\tif (!num_filters)\n\t\treturn true;\n\n\tlen = qwcslen (source);\n\tif (len < 2 || source[len - 1] != '\\n' || source[len - 2] == '#')\n\t\treturn true;\n\n\tmaxlen = MAX_FILTER_LENGTH + 1;\n\tfor (i = len - 2 ; i != 0 && maxlen != 0 ; i--, maxlen--) {\n\t\tif (source[i] == ' ')\n\t\t\treturn true;\n\t\tif (source[i] == '#')\n\t\t\tbreak;\n\t}\n\tif (!i || !maxlen)\n\t\treturn true; // no filter at all\n\n\tsource[len - 1] = 0; // so that strcmp works properly\n\n\tfor (j = 0; j < num_filters; j++)\n#ifdef _WIN32\n\t\tif (!wcscasecmp(source + i + 1, str2wcs(filter_strings[j]))) {\n#else\n\t\tif (!strcasecmp(wcs2str(source + i + 1), filter_strings[j])) {\n#endif\n\t\t\t// strip the filter from message\n\t\t\tif (i && source[i - 1] == ' ')\t{\n\t\t\t\t// there's a space just before the filter, remove it\n\t\t\t\t// so that soundtriggers like ^blah #att work\n\t\t\t\tsource[i - 1] = '\\n';\n\t\t\t\tsource[i] = 0;\n\t\t\t} else {\n\t\t\t\tsource[i] = '\\n';\n\t\t\t\tsource[i + 1] = 0;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\tsource[len - 1] = '\\n';\n\treturn false; // this message is not for us, don't print it\n}\n\nvoid TP_MsgFilter_f (void)\n{\n\tint c, i;\n\tchar *s;\n\n\tif ((c = Cmd_Argc()) == 1) {\n\t\tif (!num_filters) {\n\t\t\tCom_Printf (\"No filters defined\\n\");\n\t\t\treturn;\n\t\t}\n\t\tfor (i = 0; i < num_filters; i++)\n\t\t\tCom_Printf (\"%s#%s\", i ? \" \" : \"\", filter_strings[i]);\n\t\tCom_Printf (\"\\n\");\n\t\treturn;\n\t}\n\n\tif (c == 2 && (Cmd_Argv(1)[0] == 0 || !strcasecmp(Cmd_Argv(1), \"clear\"))) {\n\t\tnum_filters = 0;\n\t\treturn;\n\t}\n\n\tnum_filters = 0;\n\tfor (i = 1; i < c; i++) {\n\t\ts = Cmd_Argv(i);\n\t\tif (*s != '#') {\n\t\t\tCom_Printf (\"A filter must start with \\\"#\\\"\\n\");\n\t\t\treturn;\n\t\t}\n\t\tif (strchr(s + 1, ' ')) {\n\t\t\tCom_Printf (\"A filter may not contain spaces\\n\");\n\t\t\treturn;\n\t\t}\n\t\tstrlcpy (filter_strings[num_filters], s + 1, sizeof(filter_strings[0]));\n\t\tnum_filters++;\n\t\tif (num_filters >= 8)\n\t\t\tbreak;\n\t}\n}\n\nvoid TP_DumpMsgFilters(FILE *f)\n{\n\tint i;\n\n\tfprintf(f, \"filter       \");\n\tif (!num_filters)\n\t\tfprintf(f, \"clear\");\n\n\tfor (i = 0; i < num_filters; i++)\n\t\tfprintf (f, \"#%s \", filter_strings[i]);\n\n\tfprintf(f, \"\\n\");\n}\n\n/************************************ INIT ************************************/\nextern void TP_InitTriggers (void);\nvoid TP_Init (void)\n{\n\tTP_InitTriggers();\n\tTP_AddMacros();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_CHAT);\n\tCvar_Register (&cl_parseFunChars);\n\tCvar_Register (&cl_parseSay);\n\tCvar_Register (&cl_nofake);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SKIN);\n\tCvar_Register (&cl_teamtopcolor);\n\tCvar_Register (&cl_teambottomcolor);\n\tCvar_Register (&cl_enemytopcolor);\n\tCvar_Register (&cl_enemybottomcolor);\n\tCvar_Register (&cl_enemybothskin);\n\tCvar_Register (&cl_teambothskin);\n\tCvar_Register (&cl_enemypentskin);\n\tCvar_Register (&cl_teampentskin);\n\tCvar_Register (&cl_enemyquadskin);\n\tCvar_Register (&cl_teamquadskin);\n\tCvar_Register (&cl_enemyskin);\n\tCvar_Register (&cl_teamskin);\n\tCvar_Register (&cl_teamlock);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_COMMUNICATION);\n\tCvar_Register(&tp_loadlocs);\n\tCvar_Register(&tp_pointpriorities);\n\tCvar_Register(&tp_tookpriorities);\n\tCvar_Register(&tp_weapon_order);\n\tCvar_Register(&tp_tooktimeout);\n\tCvar_Register(&tp_pointtimeout);\n\tCvar_Register(&tp_poweruptextstyle);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_ITEM_NAMES);\n\tCvar_Register (&tp_name_separator);\n\tCvar_Register (&tp_name_none);\n\tCvar_Register (&tp_name_nothing);\n\tCvar_Register (&tp_name_at);\n\tCvar_Register (&tp_name_someplace);\n\n\tCvar_Register (&tp_name_rune1);\n\tCvar_Register (&tp_name_rune2);\n\tCvar_Register (&tp_name_rune3);\n\tCvar_Register (&tp_name_rune4);\n\n\tCvar_Register (&tp_name_status_blue);\n\tCvar_Register (&tp_name_status_red);\n\tCvar_Register (&tp_name_status_yellow);\n\tCvar_Register (&tp_name_status_green);\n\tCvar_Register (&tp_name_status_white);\n\n\tCvar_Register (&tp_name_pented);\n\tCvar_Register (&tp_name_quaded);\n\tCvar_Register (&tp_name_eyes);\n\tCvar_Register (&tp_name_enemy);\n\tCvar_Register (&tp_name_teammate);\n\tCvar_Register (&tp_name_disp);\n\tCvar_Register (&tp_name_sentry);\n\tCvar_Register (&tp_name_filter);\n\tCvar_Register (&tp_name_flag);\n\tCvar_Register (&tp_name_backpack);\n\tCvar_Register (&tp_name_weapon);\n\tCvar_Register (&tp_name_armor);\n\tCvar_Register (&tp_name_health);\n\tCvar_Register (&tp_name_mh);\n\tCvar_Register (&tp_name_suit);\n\tCvar_Register (&tp_name_ring);\n\tCvar_Register (&tp_name_pent);\n\tCvar_Register (&tp_name_quad);\n\tCvar_Register (&tp_name_cells);\n\tCvar_Register (&tp_name_rockets);\n\tCvar_Register (&tp_name_nails);\n\tCvar_Register (&tp_name_shells);\n\tCvar_Register (&tp_name_armortype_ra);\n\tCvar_Register (&tp_name_armortype_ya);\n\tCvar_Register (&tp_name_armortype_ga);\n\tCvar_Register (&tp_name_ra);\n\tCvar_Register (&tp_name_ya);\n\tCvar_Register (&tp_name_ga);\n\tCvar_Register (&tp_name_lg);\n\tCvar_Register (&tp_name_rl);\n\tCvar_Register (&tp_name_rlg);\n\tCvar_Register (&tp_name_gl);\n\tCvar_Register (&tp_name_sng);\n\tCvar_Register (&tp_name_ng);\n\tCvar_Register (&tp_name_ssg);\n\tCvar_Register (&tp_name_sg);\n\tCvar_Register (&tp_name_axe);\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_ITEM_NEED);\n\tCvar_Register (&tp_need_health);\n\tCvar_Register (&tp_need_cells);\n\tCvar_Register (&tp_need_rockets);\n\tCvar_Register (&tp_need_nails);\n\tCvar_Register (&tp_need_shells);\n\tCvar_Register (&tp_need_ra);\n\tCvar_Register (&tp_need_ya);\n\tCvar_Register (&tp_need_ga);\n\tCvar_Register (&tp_need_weapon);\n\tCvar_Register (&tp_need_rl);\n\n\tTP_LocFiles_Init();\n\n\tCvar_ResetCurrentGroup();\n\tCmd_AddCommand (\"teamcolor\", TP_TeamColor_f);\n\tCmd_AddCommand (\"enemycolor\", TP_EnemyColor_f);\n\n\tCmd_AddCommand (\"tp_msgreport\", TP_Msg_Report_f);\n\tCmd_AddCommand (\"tp_msgcoming\", TP_Msg_Coming_f);\n    Cmd_AddCommand (\"tp_msglost\", TP_Msg_Lost_f);\n    Cmd_AddCommand (\"tp_msgenemypwr\", TP_Msg_EnemyPowerup_f);\n\tCmd_AddCommand (\"tp_msgquaddead\", TP_Msg_QuadDead_f);\n    Cmd_AddCommand (\"tp_msgsafe\", TP_Msg_Safe_f);\n\tCmd_AddCommand (\"tp_msgkillme\", TP_Msg_KillMe_f);\n    Cmd_AddCommand (\"tp_msghelp\", TP_Msg_Help_f);\n\tCmd_AddCommand (\"tp_msggetquad\", TP_Msg_GetQuad_f);\n\tCmd_AddCommand (\"tp_msggetpent\", TP_Msg_GetPent_f);\n\tCmd_AddCommand (\"tp_msgpoint\", TP_Msg_Point_f);\n\tCmd_AddCommand (\"tp_msgtook\", TP_Msg_Took_f);\n\tCmd_AddCommand (\"tp_msgtrick\", TP_Msg_Trick_f);\n\tCmd_AddCommand (\"tp_msgreplace\", TP_Msg_Replace_f);\n\tCmd_AddCommand (\"tp_msgneed\", TP_Msg_Need_f);\n\tCmd_AddCommand (\"tp_msgyesok\", TP_Msg_YesOk_f);\n\tCmd_AddCommand (\"tp_msgnocancel\", TP_Msg_NoCancel_f);\n\tCmd_AddCommand (\"tp_msgutake\", TP_Msg_YouTake_f);\n\tCmd_AddCommand (\"tp_msgitemsoon\", TP_Msg_ItemSoon_f);\n\tCmd_AddCommand (\"tp_msgwaiting\", TP_Msg_Waiting_f);\n\tCmd_AddCommand (\"tp_msgslipped\", TP_Msg_Slipped_f);\n\t//TF messages\n\tCmd_AddCommand (\"tp_msgtfconced\", TP_Msg_TFConced_f);\n}\n\nextern void TP_ShutdownTriggers(void);\n\nvoid TP_Shutdown(void)\n{\n\tTP_ShutdownTriggers();\n\n\tTP_LocFiles_Shutdown();\n}\n"
  },
  {
    "path": "src/teamplay.h",
    "content": "/*\n\tteamplay.c\n\n\tTeamplay enhancements (\"proxy features\")\n\n\tCopyright (C) 2000-2001       Anton Gavrilov\n\n\tThis program is free software; you can redistribute it and/or\n\tmodify it under the terms of the GNU General Public License\n\tas published by the Free Software Foundation; either version 2\n\tof the License, or (at your option) any later version.\n\n\tThis program is distributed in the hope that it will be useful,\n\tbut WITHOUT ANY WARRANTY; without even the implied warranty of\n\tMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n\tSee the GNU General Public License for more details.\n\n\tYou should have received a copy of the GNU General Public License\n\talong with this program; if not, write to:\n\n\t\tFree Software Foundation, Inc.\n\t\t59 Temple Place - Suite 330\n\t\tBoston, MA  02111-1307, USA\n*/\n\n#include <pcre2.h>\n\nextern cvar_t cl_parsesay;\nextern cvar_t cl_nofake;\nextern cvar_t cl_teamskin, cl_enemyskin, cl_teamquadskin, cl_enemyquadskin;\nextern cvar_t cl_teampentskin, cl_enemypentskin, cl_teambothskin, cl_enemybothskin;\n\ntypedef struct item_vis_s {\n\tvec3_t\tvieworg;\n\tvec3_t\tforward;\n\tvec3_t\tright;\n\tvec3_t\tup;\n\tvec3_t\tentorg;\n\tfloat\tradius;\n\tvec3_t\tdir;\t\t\n\tfloat\tdist;\t\t\n} item_vis_t;\n\nqbool TP_IsItemVisible(item_vis_t *visitem);\n\n\n// triggers\nvoid TP_ExecTrigger (const char *s);\nvoid TP_StatChanged (int stat, int value);\nvoid TP_CheckPickupSound (char *s, vec3_t org);\nqbool TP_CheckSoundTrigger (wchar *str);\n\nchar *TP_PlayerName(void);\nchar *TP_PlayerTeam(void);\nchar *TP_MapName(void);\nint\tTP_CountPlayers(void);\n\n// returns true if the player in the current POV is from given team\nqbool TP_ThisPOV_IsHisTeam(const char* team);\n\n// assigns numbers 1..n to teammates and enemies, returns given players number\nint TP_PlayersNumber(int userid, const char* team);\n\n// teamcolor & enemycolor\nextern cvar_t cl_teamtopcolor, cl_teambottomcolor, cl_enemytopcolor, cl_enemybottomcolor;\n\nchar *TP_GetSkyGroupName(char *mapname, qbool *system);\nchar *TP_GetMapGroupName(char *mapname, qbool *system);\nchar *TP_ParseMacroString (char *s);\nchar *TP_ParseFunChars (const char *s, qbool chat);\nvoid TP_NewMap (void);\n\n\n#define msgtype_unknown   0\n#define msgtype_normal    1\n#define msgtype_team      2\n#define msgtype_spec      4\n#define msgtype_specteam  8\n#define msgtype_qtv       16\n/*\nreturns a logical combination of msgtype_* values\nNote that sometimes we can't be sure who really sent the message,  e.g. when there's a\nplayer \"unnamed\" in your team and \"(unnamed)\" in the enemy team. The result will be 3 (1+2)\n*/\nint TP_CategorizeMessage (const char *s, int *offset);\nqbool TP_FilterMessage (wchar *s);\n\n\nwchar *TP_ParseWhiteText (const wchar *s, qbool team, int offset);\n\n\n\nvoid TP_UpdateSkins(void);\nvoid TP_RefreshSkin(int);\nvoid TP_RefreshSkins(void);\nqbool TP_NeedRefreshSkins(void);\n\nextern char *skinforcing_team;\nchar *TP_SkinForcingTeam(void);\nqbool TP_TeamLockSpecified(void);\n\nvoid TP_Init(void);\nvoid TP_Shutdown(void);\n\n//#define FPD_NO_TEAM_MACROS\t1\n#define FPD_NO_TIMERS\t\t2\n#define FPD_NO_SOUNDTRIGGERS\t4 // disables triggers\n#define FPD_NO_FORCE_SKIN\t256\n#define FPD_NO_FORCE_COLOR\t512\n#define FPD_LIMIT_PITCH\t\t(1 << 14)\n#define FPD_LIMIT_YAW\t\t(1 << 15)\n#define FPD_ENABLE_PLAYER_COUNT\t(1 << 16)\n\n/*fpd values from qizmo.txt\n  1 = Disable %-reporting\n  2 = Disable use of powerup timer (obsolete in v2.55)\n  4 = Disable use of soundtrigger\n  8 = Disable use of lag features\n 16 = Make Qizmo report any changes in lag settins\n 32 = Silent %e enemy vicinity reporting (reporter doesn't see the message)\n      (always on in v2.55)\n 64 = Spectators can't talk to players and vice versa (voice)\n128 = Silent %x and %y (reporter doesn't see the message) (always on in v2.8)\n256 = Disable skin forcing\n512 = Disable color forcing\n*/\n\n#define MAX_LOC_NAME\t\t64\n#define MAX_MACRO_STRING \t2048\n\ntypedef struct locdata_s {\n\tvec3_t coord;\n\tchar *name;\n\tstruct locdata_s *next;\n} locdata_t;\n\nint BestWeaponFromStatItems (int stat);\n\n#define it_quad\t\t(1 << 0)\n#define it_pent\t\t(1 << 1)\n#define it_ring\t\t(1 << 2)\n#define it_suit\t\t(1 << 3)\n#define it_ra\t\t(1 << 4)\n#define it_ya\t\t(1 << 5)\n#define it_ga\t\t(1 << 6)\n#define it_mh\t\t(1 << 7)\n#define it_health\t(1 << 8)\n#define it_lg\t\t(1 << 9)\n#define it_rl\t\t(1 << 10)\n#define it_gl\t\t(1 << 11)\n#define it_sng\t\t(1 << 12)\n#define it_ng\t\t(1 << 13)\n#define it_ssg\t\t(1 << 14)\n#define it_pack\t\t(1 << 15)\n#define it_cells\t(1 << 16)\n#define it_rockets\t(1 << 17)\n#define it_nails\t(1 << 18)\n#define it_shells\t(1 << 19)\n#define it_flag\t\t(1 << 20)\n#define it_teammate\t(1 << 21)\n#define it_enemy\t(1 << 22)\n#define it_eyes\t\t(1 << 23)\n#define it_sentry   (1 << 24)\n#define it_disp\t\t(1 << 25)\n#define it_quaded   (1 << 26)\n#define it_pented   (1 << 27)\n#define it_rune1\t(1 << 28)\n#define it_rune2\t(1 << 29)\n#define it_rune3\t(1 << 30)\n#define it_rune4\t((unsigned int) (1 << 31))\n#define NUM_ITEMFLAGS 32\n \n#define it_runes\t(it_rune1|it_rune2|it_rune3|it_rune4)\n#define it_powerups\t(it_quad|it_pent|it_ring|it_suit)\n#define it_weapons\t(it_lg|it_rl|it_gl|it_sng|it_ssg)\n#define it_armor\t(it_ra|it_ya|it_ga)\n#define it_ammo\t\t(it_cells|it_rockets|it_nails|it_shells)\n#define it_players\t(it_teammate|it_enemy|it_eyes)\n\n// this structure is cleared after entering a new map\ntypedef struct tvars_s\n{\n\tint\t\thealth;\n\tint\t\titems;\n\tint\t\tolditems;\n\tint\t\tactiveweapon;\n\tint\t\tstat_framecounts[MAX_CL_STATS];\n\tdouble\tdeathtrigger_time;\n\tfloat\tf_skins_reply_time;\n\tfloat\tf_version_reply_time;\n\tchar\tlastdeathloc[MAX_LOC_NAME];\n\tchar\ttookname[32];\n    int     tookflag;\n    char\ttookloc[MAX_LOC_NAME];\n\tdouble\ttooktime;\n\tdouble\tpointtime; // cls.realtime for which pointitem & pointloc are valid\n\tchar\tpointname[64];\n    int     pointflag;\n\tchar\tpointloc[MAX_LOC_NAME];\n\tint\t\tpointtype;\n\tchar\tnearestitemloc[MAX_LOC_NAME];\n\tchar\tlastreportedloc[MAX_LOC_NAME];\n\tdouble\tlastdrop_time;\n\tchar\tlastdroploc[MAX_LOC_NAME];\n\tchar\tlasttrigger_match[256];\n\tint\t\tneedflags;\t// sum of items player needs, updated on TP_GetNeed()\n \n\tint\tnumenemies;\n\tint\tnumfriendlies;\n\tint\tlast_numenemies;\n\tint\tlast_numfriendlies;\n \n\tint enemy_powerups;\n\tdouble enemy_powerups_time;\n} tvars_t;\n \nextern tvars_t vars;\n\nextern char *TP_PlayerName (void);\n\nextern cvar_t tp_tooktimeout;\n\nextern void TP_FindPoint (void);\n#define TOOK_EMPTY() (!vars.tooktime || cls.realtime > vars.tooktime + tp_tooktimeout.value)\n\n// updates the state of vars.needflags\nvoid TP_GetNeed(void);\n\nchar *Macro_PointName (void);\n\n// Locations\nchar *TP_LocationName (vec3_t location);\nvoid TP_LocFiles_Init(void);\nvoid TP_LocFiles_NewMap(void);\nvoid TP_LocFiles_Shutdown(void);\n\nchar *TP_ItemName(int item_flag);\n"
  },
  {
    "path": "src/teamplay_locfiles.c",
    "content": "/*\nCopyright (C) 2000-2003       Anton Gavrilov, A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n$Id: teamplay.c,v 1.96 2007-10-23 08:51:02 himan Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"teamplay.h\"\n\n/********************************* .LOC FILES *********************************/\n\nstatic locdata_t *locdata = NULL;\nstatic int loc_count = 0;\n\nstatic void TP_ClearLocs(void)\n{\n\tlocdata_t *node, *temp;\n\n\tfor (node = locdata; node; node = temp) {\n\t\tQ_free(node->name);\n\t\ttemp = node->next;\n\t\tQ_free(node);\n\t}\n\n\tlocdata = NULL;\n\tloc_count = 0;\n}\n\nstatic void TP_ClearLocs_f(void)\n{\n\tint num_locs = 0;\n\n\tif (Cmd_Argc() > 1) {\n\t\tCom_Printf(\"clearlocs : Clears all locs in memory.\\n\");\n\t\treturn;\n\t}\n\n\tnum_locs = loc_count;\n\tTP_ClearLocs();\n\n\tCom_Printf(\"Cleared %d locs.\\n\", num_locs);\n}\n\nstatic void TP_AddLocNode(vec3_t coord, char *name)\n{\n\tlocdata_t *newnode, *node;\n\n\tnewnode = (locdata_t *)Q_malloc(sizeof(locdata_t));\n\tnewnode->name = Q_strdup(name);\n\tnewnode->next = NULL;\n\tmemcpy(newnode->coord, coord, sizeof(vec3_t));\n\n\tif (!locdata) {\n\t\tlocdata = newnode;\n\t\tloc_count++;\n\t\treturn;\n\t}\n\n\tfor (node = locdata; node->next; node = node->next)\n\t\t;\n\n\tnode->next = newnode;\n\tloc_count++;\n}\n\n#define SKIPBLANKS(ptr) while (*ptr == ' ' || *ptr == '\\t' || *ptr == '\\r') ptr++\n#define SKIPTOEOL(ptr) {while (*ptr != '\\n' && *ptr != 0) ptr++; if (*ptr == '\\n') ptr++;}\n\nqbool TP_LoadLocFile(char *path, qbool quiet)\n{\n\tchar *buf, *p, locname[MAX_OSPATH] = { 0 }, location[MAX_LOC_NAME];\n\tint i, n, sign, line, nameindex, overflow;\n\tvec3_t coord;\n\n\tif (!*path) {\n\t\treturn false;\n\t}\n\n\tstrlcpy(locname, \"locs/\", sizeof(locname));\n\tif (strlen(path) + strlen(locname) + 2 + 4 > MAX_OSPATH) {\n\t\tCom_Printf(\"TP_LoadLocFile: path name > MAX_OSPATH\\n\");\n\t\treturn false;\n\t}\n\n\tstrlcat(locname, path, sizeof(locname) - strlen(locname));\n\tCOM_DefaultExtension(locname, \".loc\", sizeof(locname));\n\n\tif (!(buf = (char *)FS_LoadHeapFile(locname, NULL))) {\n\t\tif (!quiet) {\n\t\t\tCom_Printf(\"Could not load %s\\n\", locname);\n\t\t}\n\t\treturn false;\n\t}\n\n\tTP_ClearLocs();\n\n\t// Parse the whole file now\n\tp = buf;\n\tline = 1;\n\n\twhile (1) {\n\t\tSKIPBLANKS(p);\n\n\t\tif (!*p) {\n\t\t\tgoto _endoffile;\n\t\t}\n\t\telse if (*p == '\\n') {\n\t\t\tp++;\n\t\t\tgoto _endofline;\n\t\t}\n\t\telse if (*p == '/' && p[1] == '/') {\n\t\t\tSKIPTOEOL(p);\n\t\t\tgoto _endofline;\n\t\t}\n\n\t\t// parse three ints\n\t\tfor (i = 0; i < 3; i++) {\n\t\t\tn = 0;\n\t\t\tsign = 1;\n\t\t\twhile (1) {\n\t\t\t\tswitch (*p++) {\n\t\t\t\t\tcase ' ':\n\t\t\t\t\tcase '\\t':\n\t\t\t\t\t\tgoto _next;\n\t\t\t\t\tcase '-':\n\t\t\t\t\t\tif (n) {\n\t\t\t\t\t\t\tCom_Printf(\"Locfile error (line %d): unexpected '-'\\n\", line);\n\t\t\t\t\t\t\tSKIPTOEOL(p);\n\t\t\t\t\t\t\tgoto _endofline;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsign = -1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase '0': case '1': case '2': case '3': case '4':\n\t\t\t\t\tcase '5': case '6': case '7': case '8': case '9':\n\t\t\t\t\t\tn = n * 10 + (p[-1] - '0');\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\t// including eol or eof\n\t\t\t\t\t\tCom_Printf(\"Locfile error (line %d): couldn't parse coords\\n\", line);\n\t\t\t\t\t\tSKIPTOEOL(p);\n\t\t\t\t\t\tgoto _endofline;\n\t\t\t\t}\n\t\t\t}\n\t\t_next:\n\t\t\tn *= sign;\n\t\t\tcoord[i] = n / 8.0;\n\n\t\t\tSKIPBLANKS(p);\n\t\t}\n\n\t\t// parse location name\n\t\toverflow = nameindex = 0;\n\t\twhile (1) {\n\t\t\tswitch (*p) {\n\t\t\t\tcase '\\r':\n\t\t\t\t\tp++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase '\\n':\n\t\t\t\tcase '\\0':\n\t\t\t\t\tlocation[nameindex] = 0;\n\t\t\t\t\tTP_AddLocNode(coord, location);\n\t\t\t\t\tif (*p == '\\n')\n\t\t\t\t\t\tp++;\n\t\t\t\t\tgoto _endofline;\n\t\t\t\tdefault:\n\t\t\t\t\tif (nameindex < MAX_LOC_NAME - 1) {\n\t\t\t\t\t\tlocation[nameindex++] = *p;\n\t\t\t\t\t}\n\t\t\t\t\telse if (!overflow) {\n\t\t\t\t\t\toverflow = 1;\n\t\t\t\t\t\tCom_Printf(\"Locfile warning (line %d): truncating loc name to %d chars\\n\", line, MAX_LOC_NAME - 1);\n\t\t\t\t\t}\n\t\t\t\t\tp++;\n\t\t\t}\n\t\t}\n\t_endofline:\n\t\tline++;\n\t}\n_endoffile:\n\n\tQ_free(buf);\n\n\tif (loc_count) {\n\t\tif (!quiet) {\n\t\t\tCom_Printf(\"Loaded locfile \\\"%s\\\" (%i loc points)\\n\", COM_SkipPath(locname), loc_count); // loc_numentries);\n\t\t}\n\t}\n\telse {\n\t\tTP_ClearLocs();\n\t\tif (!quiet) {\n\t\t\tCom_Printf(\"Locfile \\\"%s\\\" was empty\\n\", COM_SkipPath(locname));\n\t\t}\n\t}\n\n\treturn true;\n}\n\nvoid TP_LoadLocFile_f(void)\n{\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"loadloc <filename> : load a loc file\\n\");\n\t\treturn;\n\t}\n\tTP_LoadLocFile(Cmd_Argv(1), false);\n}\n\nqbool TP_SaveLocFile(char *path, qbool quiet)\n{\n\tlocdata_t\t*node;\n\tchar\t\t*buf;\n\tchar\t\tlocname[MAX_OSPATH];\n\n\t// Prevents saving of junk data\n\tif (!loc_count) {\n\t\tCom_Printf(\"TP_SaveLocFile: There is no locations to save.\\n\");\n\t\treturn false;\n\t}\n\n\t// Make sure we have a path to work with.\n\tif (!*path) {\n\t\treturn false;\n\t}\n\n\t// Check if the filename is too long.\n\tif (strlen(path) > MAX_LOC_NAME) {\n\t\tCom_Printf(\"TP_SaveLocFile: Filename too long. Max allowed is %d characters\\n\", MAX_LOC_NAME);\n\t\treturn false;\n\t}\n\n\t// Get the default path for loc-files and make sure the path\n\t// won't be too long.\n\tstrlcpy(locname, \"locs/\", sizeof(locname));\n\tif (strlen(path) + strlen(locname) + 2 + 4 > MAX_OSPATH) {\n\t\tCom_Printf(\"TP_SaveLocFile: path name > MAX_OSPATH\\n\");\n\t\treturn false;\n\t}\n\n\t// Add an extension if it doesn't exist already.\n\tstrlcat(locname, path, sizeof(locname) - strlen(locname));\n\tCOM_DefaultExtension(locname, \".loc\", sizeof(locname));\n\n\t// Allocate a buffer to hold the file contents.\n\tbuf = (char *)Q_calloc(loc_count * (MAX_LOC_NAME + 24), sizeof(char));\n\n\tif (!buf) {\n\t\tCom_Printf(\"TP_SaveLocFile: Could not initialize buffer.\\n\");\n\t\treturn false;\n\t}\n\n\t// Write all the nodes to the buffer.\n\t//for (node = locdata; node; node = node->next) {\n\tnode = locdata;\n\twhile (node) {\n\t\tchar row[2 * MAX_LOC_NAME];\n\n\t\tsnprintf(row, sizeof(row), \"%4d %4d %4d %s\\n\", Q_rint(8 * node->coord[0]), Q_rint(8 * node->coord[1]), Q_rint(8 * node->coord[2]), node->name);\n\t\tstrlcat(buf, row, (loc_count * (MAX_LOC_NAME + 24)));\n\t\tnode = node->next;\n\t}\n\n\t// Try writing the buffer containing the locs to file.\n\tif (!FS_WriteFile(locname, buf, strlen(buf))) {\n\t\tif (!quiet) {\n\t\t\tCom_Printf(\"TP_SaveLocFile: Could not open %s for writing\\n\", locname);\n\t\t}\n\n\t\t// Make sure we free our buffer.\n\t\tQ_free(buf);\n\n\t\treturn false;\n\t}\n\n\t// Make sure we free our buffer.\n\tQ_free(buf);\n\n\treturn true;\n}\n\nvoid TP_SaveLocFile_f(void)\n{\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"saveloc <filename> : save a loc file\\n\");\n\t\treturn;\n\t}\n\tTP_SaveLocFile(Cmd_Argv(1), false);\n}\n\nvoid TP_AddLoc(char *locname)\n{\n\tvec3_t location;\n\n\t// We need to be up and running.\n\tif (cls.state != ca_active) {\n\t\tCom_Printf(\"Need to be active to add a location.\\n\");\n\t\treturn;\n\t}\n\n\tif (cl.players[cl.playernum].spectator && Cam_TrackNum() >= 0) {\n\t\tVectorCopy(cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK].playerstate[Cam_TrackNum()].origin, location);\n\t}\n\telse {\n\t\tVectorCopy(cl.simorg, location);\n\t}\n\n\tTP_AddLocNode(location, locname);\n\n\tCom_Printf(\"Added location \\\"%s\\\" at (%4.0f, %4.0f, %4.0f)\\n\", locname, location[0], location[1], location[2]);\n}\n\nstatic void TP_AddLoc_f(void)\n{\n\tif (Cmd_Argc() != 2) {\n\t\tCom_Printf(\"addloc <name of location> : add a location\\n\");\n\t\treturn;\n\t}\n\tTP_AddLoc(Cmd_Argv(1));\n}\n\nstatic void TP_RemoveClosestLoc(vec3_t location)\n{\n\tfloat dist, mindist;\n\tvec3_t vec;\n\tlocdata_t *node, *best, *previous, *best_previous;\n\n\tbest_previous = previous = best = NULL;\n\tmindist = 0;\n\n\t// Find the closest loc.\n\tnode = locdata;\n\twhile (node) {\n\t\t// Get the distance to the loc.\n\t\tVectorSubtract(location, node->coord, vec);\n\t\tdist = vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2];\n\n\t\t// Check if it's closer than the previously best.\n\t\tif (!best || dist < mindist) {\n\t\t\tbest_previous = previous;\n\t\t\tbest = node;\n\t\t\tmindist = dist;\n\t\t}\n\n\t\t// Advance and save the previous node.\n\t\tprevious = node;\n\t\tnode = node->next;\n\t}\n\n\tif (!best) {\n\t\tCom_Printf(\"There is no locations left for deletion!\\n\");\n\t\treturn;\n\t}\n\n\n\tCom_Printf(\"Removed location \\\"%s\\\" at (%4.0f, %4.0f, %4.0f)\\n\", best->name, best->coord[0], best->coord[1], best->coord[2]);\n\n\t// If the node we're trying to delete has a\n\t// next node attached to it, copy the data from\n\t// that node into the node we're deleting, and then\n\t// delete the next node instead.\n\tif (best->next) {\n\t\tlocdata_t *temp;\n\n\t\t// Copy the data from the next node into the one we're deleting.\n\t\tVectorCopy(best->next->coord, best->coord);\n\n\t\tQ_free(best->name);\n\t\tbest->name = (char *)Q_calloc(strlen(best->next->name) + 1, sizeof(char));\n\t\tstrcpy(best->name, best->next->name);\n\n\t\t// Save the pointer to the next node.\n\t\ttemp = best->next->next;\n\n\t\t// Free the current next node.\n\t\tQ_free(best->next->name);\n\t\tQ_free(best->next);\n\n\t\t// Set the pointer to the next node.\n\t\tbest->next = temp;\n\t}\n\telse {\n\t\t// Free the current node.\n\t\tQ_free(best->name);\n\t\tQ_free(best);\n\t\tbest = NULL;\n\n\t\t// Make sure the previous node doesn't point to garbage.\n\t\tif (best_previous != NULL) {\n\t\t\tbest_previous->next = NULL;\n\t\t}\n\t}\n\n\t// Decrease the loc count.\n\tloc_count--;\n\n\t// If this was the last loc, remove the entire node list.\n\tif (loc_count <= 0) {\n\t\tlocdata = NULL;\n\t}\n}\n\nstatic void TP_RemoveLoc_f(void)\n{\n\tif (Cmd_Argc() == 1) {\n\t\tif (cl.players[cl.playernum].spectator && Cam_TrackNum() >= 0) {\n\t\t\tTP_RemoveClosestLoc(cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK].playerstate[Cam_TrackNum()].origin);\n\t\t}\n\t\telse {\n\t\t\tTP_RemoveClosestLoc(cl.simorg);\n\t\t}\n\t}\n\telse {\n\t\tCom_Printf(\"removeloc : remove the closest location\\n\");\n\t\treturn;\n\t}\n}\n\ntypedef struct locmacro_s {\n\tchar *macro;\n\tchar *val;\n} locmacro_t;\n\nstatic locmacro_t locmacros[] = {\n\t{ \"ssg\", \"ssg\" },\n\t{ \"ng\", \"ng\" },\n\t{ \"sng\", \"sng\" },\n\t{ \"gl\", \"gl\" },\n\t{ \"rl\", \"rl\" },\n\t{ \"lg\", \"lg\" },\n\t{ \"separator\", \"-\" },\n\t{ \"ga\", \"ga\" },\n\t{ \"ya\", \"ya\" },\n\t{ \"ra\", \"ra\" },\n\t{ \"quad\", \"quad\" },\n\t{ \"pent\", \"pent\" },\n\t{ \"ring\", \"ring\" },\n\t{ \"suit\", \"suit\" },\n\t{ \"mh\", \"mega\" },\n};\n\n#define NUM_LOCMACROS\t(sizeof(locmacros) / sizeof(locmacros[0]))\n\nchar *TP_LocationName(vec3_t location)\n{\n\tchar *in, *out, *value;\n\tint i;\n\tfloat dist, mindist;\n\tvec3_t vec;\n\tstatic locdata_t *node, *best;\n\tcvar_t *cvar;\n\textern cvar_t tp_name_someplace;\n\tstatic qbool recursive;\n\tstatic char\tbuf[1024], newbuf[MAX_LOC_NAME];\n\n\tif (!locdata || cls.state != ca_active) {\n\t\treturn tp_name_someplace.string;\n\t}\n\n\tif (recursive) {\n\t\treturn \"\";\n\t}\n\n\tbest = NULL;\n\tmindist = 0;\n\n\tfor (node = locdata; node; node = node->next) {\n\t\tVectorSubtract(location, node->coord, vec);\n\t\tdist = vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2];\n\t\tif (!best || dist < mindist) {\n\t\t\tbest = node;\n\t\t\tmindist = dist;\n\t\t}\n\t}\n\n\tnewbuf[0] = 0;\n\tout = newbuf;\n\tin = best->name;\n\twhile (*in && out - newbuf < sizeof(newbuf) - 1) {\n\t\tif (!strncasecmp(in, \"$loc_name_\", 10)) {\n\t\t\tin += 10;\n\t\t\tfor (i = 0; i < NUM_LOCMACROS; i++) {\n\t\t\t\tif (!strncasecmp(in, locmacros[i].macro, strlen(locmacros[i].macro))) {\n\t\t\t\t\tif ((cvar = Cvar_Find(va(\"loc_name_%s\", locmacros[i].macro))))\n\t\t\t\t\t\tvalue = cvar->string;\n\t\t\t\t\telse\n\t\t\t\t\t\tvalue = locmacros[i].val;\n\t\t\t\t\tif (out - newbuf >= sizeof(newbuf) - strlen(value) - 1)\n\t\t\t\t\t\tgoto done_locmacros;\n\t\t\t\t\tstrcpy(out, value);\n\t\t\t\t\tout += strlen(value);\n\t\t\t\t\tin += strlen(locmacros[i].macro);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (i == NUM_LOCMACROS) {\n\t\t\t\tif (out - newbuf >= sizeof(newbuf) - 10 - 1)\n\t\t\t\t\tgoto done_locmacros;\n\t\t\t\tstrcpy(out, \"$loc_name_\");\n\t\t\t\tout += 10;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t*out++ = *in++;\n\t\t}\n\t}\ndone_locmacros:\n\t*out = 0;\n\n\tbuf[0] = 0;\n\trecursive = true;\n\tCmd_ExpandString(newbuf, buf);\n\trecursive = false;\n\n\treturn buf;\n}\n\nvoid TP_LocFiles_Init(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_COMMUNICATION);\n\tCmd_AddCommand(\"locations_loadfile\", TP_LoadLocFile_f);\n\tCmd_AddLegacyCommand(\"loadloc\", \"locations_loadfile\");\n\tCmd_AddCommand(\"locations_savefile\", TP_SaveLocFile_f);\n\tCmd_AddLegacyCommand(\"saveloc\", \"locations_savefile\");\n\tCmd_AddCommand(\"locations_add\", TP_AddLoc_f);\n\tCmd_AddLegacyCommand(\"addloc\", \"locations_add\");\n\tCmd_AddCommand(\"locations_remove\", TP_RemoveLoc_f);\n\tCmd_AddLegacyCommand(\"removeloc\", \"locations_remove\");\n\tCmd_AddCommand(\"locations_clearall\", TP_ClearLocs_f);\n\tCmd_AddLegacyCommand(\"clearlocs\", \"locations_clearall\");\n\tCvar_ResetCurrentGroup();\n}\n\nvoid TP_LocFiles_NewMap(void)\n{\n\tstatic char last_map[MAX_QPATH] = { 0 };\n\tchar *groupname;\n\tqbool system;\n\tchar* mapname = TP_MapName();\n\textern cvar_t tp_loadlocs;\n\n\tif (strcmp(mapname, last_map)) {\t// map name has changed\n\t\tTP_ClearLocs();\t\t\t\t\t// clear loc file\n\n\t\t// always load when playing back demos (teaminfo might be enabled etc)\n\t\tif ((tp_loadlocs.integer && cl.deathmatch) || cls.demoplayback) {\n\t\t\tif (!TP_LoadLocFile(va(\"%s.loc\", mapname), true)) {\n\t\t\t\tif ((groupname = TP_GetMapGroupName(mapname, &system)) && !system) {\n\t\t\t\t\tTP_LoadLocFile(va(\"%s.loc\", groupname), true);\n\t\t\t\t}\n\t\t\t}\n\t\t\tstrlcpy(last_map, mapname, sizeof(last_map));\n\t\t}\n\t\telse {\n\t\t\tlast_map[0] = 0;\n\t\t}\n\t}\n}\n\nvoid TP_LocFiles_Shutdown(void)\n{\n\tTP_ClearLocs();\n}\n"
  },
  {
    "path": "src/textencoding.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n//#include \"q_shared.h\"\n#include \"common.h\"\t\t// Com_DPrintf\n#include \"textencoding.h\"\n#include \"utils.h\"\n\n#define ENCODED_BUFFER_SIZE 1024\n\n// Each encoding function should return the number of bytes written to the output string\n//   maxCharacters gives the characters before end of string, if function can't generate in given space, output ? instead\ntypedef int(*EncodingFunction)(char* output, wchar input, int maxCharacters);\n\nstatic int koiEncoder(char* output, wchar input, int maxCharacters);\nstatic int utf8Encoder(char* output, wchar input, int maxCharacters);\nstatic int fteEncoder(char* output, wchar input, int maxCharacters);\n\nstatic const char* encodingStrings[] = { \"=`k8:\", \"=`utf8:\", \"\" };\nstatic const char* encodingSuffixes[] = { \"`=\", \"`=\", \"\" };\nstatic const EncodingFunction encodingFunctions[] = { koiEncoder, utf8Encoder, fteEncoder };\n\nextern cvar_t cl_textEncoding;\nextern qbool R_CharAvailable (wchar c);\n\n/* KOI8-R encodes Russian capital hard sign as 0xFF, but we can't use it\nbecause it breaks older clients (qwcl).  We use 0xaf ('/'+ 0x80) instead. */\nstatic char *wc2koi_table =\n\t\"?3??4?67??\" \"??\" \"??\" \">?\"\n\t\"abwgdevzijklmnop\"\n\t\"rstufhc~{}/yx|`q\"\n\t\"ABWGDEVZIJKLMNOP\"\n\t\"RSTUFHC^[]_YX\\\\@Q\"\n\t\"?#??$?&'??\" \"??\" \"??.?\";\n\nstatic int koiEncoder(char* out, wchar in, int maxCharacters) {\n\tif (in <= 255)\n\t\tout[0] = in;\n\telse if (in >= 0x400 && in <= 0x45f)\n\t\tout[0] = wc2koi_table[in - 0x400] + 128;\n\telse\n\t\tout[0] = '?';\n\treturn 1;\n}\n\nstatic int utf8Encoder(char* out, wchar in, int maxCharacters) {\n\tif (in <= 0x7F) {\n\t\t// <= 127 is encoded as single byte, no translation\n\t\tout[0] = in;\n\t\treturn 1;\n\t}\n\telse if (in <= 0x7FF && maxCharacters >= 2) {\n\t\t// Two byte characters... 5 bits then 6\n\t\tout[0] = 0xC0 | ((in >> 6) & 0x1F);\n\t\tout[1] = 0x80 | (in & 0x3F);\n\t\treturn 2;\n\t}\n\telse if (in <= 0xFFFF && maxCharacters >= 3) {\n\t\t// Three byte characters... 4 bits then 6 then 6\n\t\tout[0] = 0xE0 | ((in >> 12) & 0x0F);\n\t\tout[1] = 0x80 | ((in >> 6) & 0x3F);\n\t\tout[2] = 0x80 | (in & 0x3F);\n\t\treturn 3;\n\t}\n\telse {\n\t\t// Can't support four characters at the moment - values would be higher than wchar's two bytes\n\t\t// If we're here for a character we could support, we don't have room for it...\n\t\tout[0] = '?';\n\t\treturn 1;\n\t}\n}\n\n// FIXME: Should we encode ^ as ^^?  FTE interprets ^^ as ^, but doesn't encode that way.\nstatic int fteEncoder(char* out, wchar in, int maxCharacters) {\n\tif (in <= 0x7F) {\n\t\t// <= 127 is encoded as single byte, no translation\n\t\tout[0] = in;\n\t\treturn 1;\n\t}\n\n\t// Otherwise it is ^UXXXX where XXXX is the codepage in hex - so need an extra 5 characters\n\tif (maxCharacters < sizeof(wchar) * 2 + 1)\n\t{\n\t\tout[0] = '?';\n\t\treturn 1;\n\t}\n\n\tsnprintf(out, maxCharacters + 1, \"^U%04x\", in);\n\treturn 6;\n}\n\nstatic int TextEncodingMethod(void)\n{\n\treturn bound(0, cl_textEncoding.value, sizeof(encodingFunctions) / sizeof(encodingFunctions[0]) - 1);\n}\n\nint TextEncodingEncode(char* out, wchar input, int maxBytes)\n{\n\treturn (*encodingFunctions[TextEncodingMethod()])(out, input, maxBytes);\n}\n\nconst char* TextEncodingPrefix(void)\n{\n\treturn encodingStrings[TextEncodingMethod()];\n}\n\nconst char* TextEncodingSuffix(void)\n{\n\treturn encodingSuffixes[TextEncodingMethod()];\n}\n\nchar *encode_say (wchar *in)\n{\n\tstatic char buf[ENCODED_BUFFER_SIZE];\n\twchar *p;\n\tchar *out;\n\n\tmemset(buf, 0, sizeof(buf));\n\tfor (p = in; *p; p++)\n\t\tif (*p > 255)\n\t\t\tgoto encode;\n\tstrlcpy (buf, wcs2str(in), sizeof(buf));\n\treturn buf;\nencode:\n\tstrlcpy (buf, wcs2str(in), min(p - in + 1, sizeof(buf)));\n\tin = p;\n\tstrlcat (buf, TextEncodingPrefix(), sizeof(buf));\n\tout = buf + strlen(buf);\n\t\n\tchar* lastValidCharacter = &buf[ENCODED_BUFFER_SIZE - strlen(TextEncodingSuffix()) - 1];\t// Leave space for terminator\n\twhile (*in && out <= lastValidCharacter)\n\t{\n\t\tout += TextEncodingEncode(out, *in, lastValidCharacter - out + 1);\n\t\t++in;\n\t}\n\tstrlcat(buf, TextEncodingSuffix(), sizeof(buf));\n\treturn buf;\n}\n\n//\n// Decoding functions\n//\n\nstatic wchar koi2wc (char c)\n{\n\tstatic char koi2wc_table[64] = {\n\t\t0x4e,0x30,0x31,0x46,0x34,0x35,0x44,0x33,0x45,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,\n\t\t0x3f,0x4f,0x40,0x41,0x42,0x43,0x36,0x32,0x4c,0x4b,0x37,0x48,0x4d,0x49,0x47,0x4a,\n\t\t0x2e,0x10,0x11,0x26,0x14,0x15,0x24,0x13,0x25,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,\n\t\t0x1f,0x2f,0x20,0x21,0x22,0x23,0x16,0x12,0x2c,0x2b,0x17,0x28,0x2d,0x29,0x27,0x2a\n\t};\n\n\tunsigned char uc = c;\n\n\tif (uc >= 192 /* && (unsigned char)c <= 255 */)\n\t\treturn koi2wc_table[(unsigned char)c - 192] + 0x400;\n\telse if (uc == '#' + 128)\n\t\treturn 0x0451;\t// russian small yo\n\telse if (uc == '3' + 128)\n\t\treturn 0x0401;\t// russian capital yo\n\telse if (uc == '4' + 128)\n\t\treturn 0x0404;\t// ukrainian capital round E\n\telse if (uc == '$' + 128)\n\t\treturn 0x0454;\t// ukrainian small round E\n\telse if (uc == '6' + 128)\n\t\treturn 0x0406;\t// ukrainian capital I\n\telse if (uc == '&' + 128)\n\t\treturn 0x0456;\t// ukrainian small i\n\telse if (uc == '7' + 128)\n\t\treturn 0x0407;\t// ukrainian capital I with two dots\n\telse if (uc == '\\'' + 128)\n\t\treturn 0x0457;\t// ukrainian small i with two dots\n\telse if (uc == '>' + 128)\n\t\treturn 0x040e;\t// belarusian Y\n\telse if (uc == '.' + 128)\n\t\treturn 0x045e;\t// belarusian y\n\telse if (uc == '/' + 128)\n\t\treturn 0x042a;\t// russian capital hard sign\n\telse\n\t\treturn (wchar)(unsigned char)c;\n}\n\nstatic wchar cp1251towc (char c)\n{\n\tunsigned char uc = c;\n\tif (uc >= 192)\n\t\treturn 0x0410 + (uc - 192);\n\telse if (uc == 168)\n\t\treturn 0x0401;\t// russian capital yo\n\telse if (uc == 184)\n\t\treturn 0x0451;\t// russian small yo\n\telse if (uc == 170)\n\t\treturn 0x0404;\t// ukrainian capital round E\n\telse if (uc == 186)\n\t\treturn 0x0454;\t// ukrainian small round E\n\telse if (uc == 178)\n\t\treturn 0x0406;\t// ukrainian capital I\n\telse if (uc == 179)\n\t\treturn 0x0456;\t// ukrainian small i\n\telse if (uc == 175)\n\t\treturn 0x0407;\t// ukrainian capital I with two dots\n\telse if (uc == 191)\n\t\treturn 0x0457;\t// ukrainian small i with two dots\n\telse if (uc == 161)\n\t\treturn 0x040e;\t// belarusian Y\n\telse if (uc == 162)\n\t\treturn 0x045e;\t// belarusian y\n\treturn (wchar)uc;\n}\n\n// returns Q_malloc'ed data\nwchar *decode_koi8q (char *str) {\n\twchar *buf, *out;\n\tbuf = out = Q_malloc ((strlen(str) + 1)*sizeof(wchar));\n\twhile (*str)\n\t\t*out++ = koi2wc(*str++);\n\t*out = 0;\n\treturn buf;\n};\n\n// returns Q_malloc'ed data\nwchar *decode_cp1251 (char *str) {\n\twchar *buf, *out;\n\tbuf = out = Q_malloc ((strlen(str) + 1)*sizeof(wchar));\n\twhile (*str)\n\t\t*out++ = cp1251towc(*str++);\n\t*out = 0;\n\treturn buf;\n};\n\nwchar TextEncodingDecodeUTF8(char* str, int* index)\n{\n\tint stringLength = strlen(str);\n\tint extraBytes = 0;\n\twchar result = 0;\n\n\tif (!(str[*index] & 0x80))\n\t{\n\t\t// Standard ASCII\n\t\tresult = str[*index];\n\t}\n\telse\n\t{\n\t\tchar valueMask = 0x7F;\n\t\tchar lengthBits = (str[*index] << 1);\n\n\t\t// First character is 1<length in set bits><value>\n\t\twhile (lengthBits & 0x80)\n\t\t{\n\t\t\t++extraBytes;\n\t\t\tlengthBits = (lengthBits << 1);\n\n\t\t\tvalueMask >>= 1;\n\t\t}\n\n\t\t// Extract value from first character\n\t\tresult = str[*index] & valueMask;\n\n\t\twhile (extraBytes--)\n\t\t{\n\t\t\t*index = *index + 1;\n\n\t\t\t// Extra characters must be 10xx xxxx\n\t\t\tif (*index >= stringLength || (str[*index] & 0xC0) != 0x80)\n\t\t\t\treturn 0;\n\n\t\t\tresult <<= 6;\n\t\t\tresult += str[*index] & 0x3F;\n\t\t}\n\t}\n\n\treturn result;\n}\n\nwchar* decode_utf8(char* str) {\n\twchar *buf, *out;\n\tint length = strlen(str);\n\tint i = 0;\n\n\tbuf = out = Q_malloc((length + 1)*sizeof(wchar));\t// This size would be worst case scenario\n\tfor (i = 0; i < length; ++i)\n\t{\n\t\t*out = TextEncodingDecodeUTF8(str, &i);\n\t\tout++;\n\t}\n\t*out = 0;\n\treturn buf;\n};\n\ntypedef wchar *(*decodeFUNC) (char *);\n\nstatic struct {\n\tchar *name;\n\tdecodeFUNC func;\n} decode_table[] = {\n\t{\"koi8q\", decode_koi8q},\n\t{\"koi8r\", decode_koi8q},\n\t{\"k8\", decode_koi8q},\n\t{\"cp1251\", decode_cp1251},\n\t{\"wr\", decode_cp1251},\t// wc = Windows Cyrillic wr = Windows Russian\n\t{\"utf8\", decode_utf8},\n\t{NULL, NULL}\n};\n\nqbool ishex(char ch)\n{\n\treturn (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f');\n}\n\nqbool wc_ishex(wchar ch)\n{\n\treturn ch < 127 && ishex(ch);\n}\n\nint wc_hextodec(wchar ch)\n{\n\treturn ch < 127 ? HexToInt(ch) : 0;\n}\n\nvoid decode_fte_string(wchar* s)\n{\n\twhile (s[0] && s[1])\n\t{\n\t\tif (s[0] == '^' && s[1] == '^')\n\t\t{\n\t\t\t// ^^ => ^\n\t\t\tqwcscpy(s + 1, s + 2);\n\t\t}\n\t\telse if (s[0] == '^' && s[1] == 'U' && s[2] && s[3] && s[4] && s[5] && wc_ishex(s[2]) && wc_ishex(s[3]) && wc_ishex(s[4]) && wc_ishex(s[5]))\n\t\t{\n\t\t\t// ^UXXXX, XXXX is hex for wchar\n\t\t\ts[0] = (wc_hextodec(s[2]) << 12) + (wc_hextodec(s[3]) << 8) + (wc_hextodec(s[4]) << 4) + wc_hextodec(s[5]);\n\n\t\t\tqwcscpy(s + 1, s + 6);\n\t\t}\n\t\telse if (s[0] == '^' && s[1] == '{')\n\t\t{\n\t\t\t// ^U{XXX...}, XXX.. is hex for wchar\n\t\t\twchar* num = s + 2;\n\t\t\twchar result = 0;\n\n\t\t\twhile (*num && wc_ishex(*num))\n\t\t\t{\n\t\t\t\tresult = result * 16 + wc_hextodec(*num);\n\n\t\t\t\t++num;\n\t\t\t}\n\n\t\t\tif (*num == '}')\n\t\t\t{\n\t\t\t\tif (result)\n\t\t\t\t\ts[0] = result;\n\t\t\t\telse\n\t\t\t\t\ts[0] = '?';\n\n\t\t\t\tqwcscpy(s + 1, num + 1);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\ts[0] = '?';\n\t\t\t}\n\t\t}\n\t\t++s;\n\t}\n}\n\nwchar *decode_string (const char *s)\n{\n\tstatic wchar buf[2048];\t// should be enough for everyone!!!\n\tchar encoding[13];\n\tchar enc_str[1024];\n\tconst char *p, *q, *r;\n\twchar *decoded;\n\tint i;\n\n\tbuf[0] = 0;\n\tp = s;\n\n\twhile (1)\n\t{\n\t\tp = strstr(p, \"=`\");\n\t\tif (!p)\n\t\t\tbreak;\n\n\t\t// copy source string up to p as is\n\t\tqwcslcat (buf, str2wcs(s), min(p-s+qwcslen(buf)+1, sizeof(buf)/sizeof(buf[0])));\n\t\ts = p;\n\n\t\tp += 2;\t\t// skip the =`\n\t\tfor (q = p; isalnum(*q) && q - p < 12; q++)\t{\n\t\t\t;\n\t\t}\n\t\tif (!*q)\n\t\t\tbreak;\n\t\tif (*q != ':') {\n\t\t\tp += 2;\n\t\t\tcontinue;\n\t\t}\n\n\t\tq++;\t// skip the :\n\t\tassert (q - p <= sizeof(encoding));\n\t\tstrlcpy (encoding, p, q - p);\n\t\t\n\t\tr = strstr(q, \"`=\");\n\t\tif (!r) {\n\t\t\tp = q;\n\t\t\tcontinue;\n\t\t}\n\n\t\tstrlcpy (enc_str, q, min(r - q + 1, sizeof(enc_str)));\n\n\t\tfor (i = 0; decode_table[i].name; i++) {\n\t\t\tif (!strcasecmp(encoding, decode_table[i].name))\n\t\t\t\tbreak;\t// found it\n\t\t}\n\n\t\tif (!decode_table[i].name) {\n\t\t\t// unknown encoding\n\t\t\tCon_DPrintf(\"Unknown encoding %s\\n\", encoding);\n\t\t\tp = r + 2;\n\t\t\tcontinue;\t\n\t\t}\n\n\t\tdecoded = decode_table[i].func(enc_str);\n\t\tqwcslcat (buf, decoded, sizeof(buf)/sizeof(buf[0]));\n\t\tQ_free (decoded);\n\t\ts = p = r + 2;\n\t}\n\n\t// copy remainder as is\n\tqwcslcat (buf, str2wcs(s), sizeof(buf)/sizeof(buf[0]));\n\n\t// FTE encodes strings differently. Decoded is always smaller, can execute in-place\n\tdecode_fte_string(buf);\n\n\treturn maybe_transliterate(buf);\n}\n\n\nchar cyr_trans_table[] = {\"ABVGDEZZIJKLMNOPRSTUFHCCSS'Y'EYY\"};\nwchar *transliterate_char (wchar c)\n{\n\tstatic wchar s[2] = {0};\n\n\tif (c == 0x401)\n\t\treturn str2wcs(\"YO\");\n\tif (c == 0x404)\n\t\treturn str2wcs(\"E\");\n\tif (c == 0x406)\n\t\treturn str2wcs(\"I\");\n\tif (c == 0x407)\n\t\treturn str2wcs(\"J\");\n\tif (c == 0x40e)\n\t\treturn str2wcs(\"U\");\n\tif (c == 0x416)\n\t\treturn str2wcs(\"ZH\");\n\tif (c == 0x427)\n\t\treturn str2wcs(\"CH\");\n\tif (c == 0x428)\n\t\treturn str2wcs(\"SH\");\n\tif (c == 0x429)\n\t\treturn str2wcs(\"SH\");\n\tif (c == 0x42e)\n\t\treturn str2wcs(\"YU\");\n\tif (c == 0x42f)\n\t\treturn str2wcs(\"YA\");\n\tif (c == 0x451)\n\t\treturn str2wcs(\"yo\");\n\tif (c == 0x454)\n\t\treturn str2wcs(\"e\");\n\tif (c == 0x456)\n\t\treturn str2wcs(\"i\");\n\tif (c == 0x457)\n\t\treturn str2wcs(\"j\");\n\tif (c == 0x45e)\n\t\treturn str2wcs(\"u\");\n\tif (c == 0x436)\n\t\treturn str2wcs(\"zh\");\n\tif (c == 0x447)\n\t\treturn str2wcs(\"ch\");\n\tif (c == 0x448)\n\t\treturn str2wcs(\"sh\");\n\tif (c == 0x449)\n\t\treturn str2wcs(\"sh\");\n\tif (c == 0x44e)\n\t\treturn str2wcs(\"yu\");\n\tif (c == 0x44f)\n\t\treturn str2wcs(\"ya\");\n\tif (c >= 0x0410 && c < 0x0430)\n\t\ts[0] = cyr_trans_table[c - 0x410];\n\telse if (c >= 0x0430 && c < 0x0450)\n\t\ts[0] = tolower(cyr_trans_table[c - 0x430]);\n\telse\n\t\ts[0] = '?';\n\treturn s;\n}\n\n// Make sure the renderer can display all Unicode chars in the string,\n// otherwise try to replace them with Latin equivalents\nwchar *maybe_transliterate (wchar *src)\n{\n\twchar *dst, *trans;\n\tstatic wchar buf[2048];\n#define buflen (sizeof(buf)/sizeof(buf[0]))\n\tint len;\n\n\tdst = buf;\n\twhile (*src && dst < buf+buflen-1) {\n\t\tif (R_CharAvailable(*src)) {\n\t\t\t*dst++ = *src;\n\t\t}\n\t\telse {\n\t\t\ttrans = transliterate_char (*src);\n\t\t\tlen = min(qwcslen(trans), buf+buflen-1 - dst);\n\t\t\tmemcpy (dst, trans, len*sizeof(wchar));\n\t\t\tdst += len;\n\t\t}\n\t\tsrc++;\n\t}\n\t*dst = 0;\n\n\treturn buf;\n}\n"
  },
  {
    "path": "src/textencoding.h",
    "content": "/*\nCopyright (C) 2011-2015 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\nwchar *decode_string (const char *s);\nchar *encode_say (wchar *in);\n\nwchar TextEncodingDecodeUTF8(char* str, int* index);\n\n// Make sure the renderer can display all Unicode chars in the string,\n// otherwise try to replace them with Latin equivalents\nwchar *maybe_transliterate (wchar *src);\n"
  },
  {
    "path": "src/tp_msgs.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/**\n\n  Inbuilt teamplay messages module\n\n  made by johnnycz, Up2nOgoOd[ROCK]\n  last edit:\n  $Id: tp_msgs.c,v 1.6 2007-10-23 08:51:02 himan Exp $\n\n*/\n\n#include \"quakedef.h\"\n#include \"teamplay.h\"\n\n#define GLOBAL /* */\n#define LOCAL static\n\n#define NEED_WEAPON() (NEED(rl) || NEED(lg))\n#define HAVE_FLAG() (cl.stats[STAT_ITEMS] & IT_FLAG)\n#define HAVE_RL() (cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER)\n#define HAVE_LG() (cl.stats[STAT_ITEMS] & IT_LIGHTNING)\n#define HAVE_GL() (cl.stats[STAT_ITEMS] & IT_GRENADE_LAUNCHER)\n#define HAVE_SNG() (cl.stats[STAT_ITEMS] & IT_SUPER_NAILGUN)\n#define HAVE_SSG() (cl.stats[STAT_ITEMS] & IT_SUPER_SHOTGUN)\n#define HOLD_GL() (cl.stats[STAT_ACTIVEWEAPON] == IT_GRENADE_LAUNCHER)\n#define HOLD_RL() (cl.stats[STAT_ACTIVEWEAPON] == IT_ROCKET_LAUNCHER)\n#define HOLD_LG() (cl.stats[STAT_ACTIVEWEAPON] == IT_LIGHTNING)\n \n#define HAVE_POWERUP() (HAVE_QUAD() || HAVE_PENT() || HAVE_RING())\n#define HAVE_QUAD() (cl.stats[STAT_ITEMS] & IT_QUAD) //quad\n#define HAVE_PENT() (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) //pent\n#define HAVE_RING() (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) //ring\n#define HAVE_GA() (cl.stats[STAT_ITEMS] & IT_ARMOR1) //ga\n#define HAVE_YA() (cl.stats[STAT_ITEMS] & IT_ARMOR2) //ya\n#define HAVE_RA() (cl.stats[STAT_ITEMS] & IT_ARMOR3) //ra\n\n#define HAVE_CELLS() (cl.stats[STAT_CELLS] > 0)\n#define HAVE_ROCKETS() (cl.stats[STAT_ROCKETS] > 0)\n\n// just some shortcuts:\n#define INPOINTARMOR() (INPOINT(ra) || INPOINT(ya) || INPOINT(ga))\n#define INPOINTWEAPON() (INPOINT(rl) || INPOINT(lg) || INPOINT(gl) || INPOINT(sng))\n#define INPOINTPOWERUP() (INPOINT(quad) || INPOINT(pent) || INPOINT(ring))\n#define INPOINTAMMO() (INPOINT(rockets) || INPOINT(cells) || INPOINT(nails))\n \n#define TOOK(x) (!TOOK_EMPTY() && vars.tookflag == it_##x)\n#define COLORED(c,str) \"{&c\" #c #str \"&cfff}\"\n\n// it's important to call TP_FindPoint() before using these\n// don't call TP_FindPoint if (flashed) is true\n#define POINT_EMPTY() (!vars.pointflag)\n#define INPOINT(thing) (!flashed && (vars.pointflag & it_##thing))\n\n#define DEAD() (cl.stats[STAT_HEALTH] < 1)\n\n// call TP_GetNeed() before using this!\n#define NEED(x) (vars.needflags & it_##x)\n\ntypedef const char * MSGPART;\n \nextern cvar_t cl_fakename;\n// will return short version of player's nick for teamplay messages\nLOCAL char *TP_ShortNick(void)\n{\n\tstatic char buf[64];\n \n\tif (*(cl_fakename.string)) return \"\";\n\telse {\n\t\tif (*(Cvar_String(\"nick\"))) { // isn't cl_fakename and name enough?\n\t\t\tsnprintf(buf, sizeof(buf), \"$\\\\%s%s\", Cvar_String(\"nick\"), Cvar_String(\"cl_fakename_suffix\"));\n\t\t} else {\n\t\t\tsnprintf(buf, sizeof(buf), \"$\\\\%.3s%s\", TP_PlayerName(), Cvar_String(\"cl_fakename_suffix\"));\n\t\t}\n\t\treturn buf;\n\t}\n}      \n \n// wrapper for snprintf & Cbuf_AddText that will add say_team nick part\nLOCAL void TP_Send_TeamSay(char *format, ...)\n{\n    char tp_msg_head[256], tp_msg_body[256], tp_msg[512];\n    va_list argptr;\n \n    snprintf(tp_msg_head, sizeof(tp_msg_head), \"say_team %s\", TP_ShortNick());\n \n\tva_start (argptr, format);\n    vsnprintf(tp_msg_body, sizeof(tp_msg_body), format, argptr);\n\tva_end (argptr);\n \n    snprintf(tp_msg, sizeof(tp_msg), \"%s%s\\n\", tp_msg_head, tp_msg_body);\n \n    Cbuf_AddText(tp_msg);\n}\n \n \n \n///////////////////////////////////\n////// Start teamplay scripts /////\n///////////////////////////////////\n\nGLOBAL void TP_Msg_Lost_f (void)\n{\n    MSGPART quad = \"\";\n\tMSGPART over = \"\";\n\tMSGPART dropped_or_lost = \"\";\n\tMSGPART location_enemy = \" {&cf00[&cfff}{%d}{&cf00]&cfff} {%E}\";\n\textern cvar_t tp_name_quad;\n\n\tif (DEAD()) {\n\t\tif (HAVE_QUAD()) {\n\t\t\tquad = tp_name_quad.string;\t\t\t\n\t\t\tover = \" over \";\n\t\t\tlocation_enemy = \"{&cf00[&cfff}{%d}{&cf00]&cfff} {%E}\";\n\t\t}\n\t\telse\n\t\t\tdropped_or_lost = \"{&cf00lost&cfff}\";\n\n\t\tif (HOLD_RL() || HOLD_LG()) {\n\t\t\tdropped_or_lost = \"{&cf00DROPPED} $weapon\";\n\t\t\tlocation_enemy = \" {&cf00[&cfff}{%d}{&cf00]&cfff} {%E}\";\n\t\t}\n\t}\n\telse\n\t\tdropped_or_lost = \"{&cf00lost&cfff}\";\n\n\tTP_Send_TeamSay(\"%s%s%s%s\", quad, over, dropped_or_lost, location_enemy);\n}\n\nGLOBAL void TP_Msg_Report_f (void)\n{\n\textern cvar_t tp_name_lg, tp_name_rl, tp_name_gl, tp_name_sng, tp_name_ssg;\n\tMSGPART powerup = \"\";\n\tMSGPART armor_health = \"$colored_armor/%h\";\n\tMSGPART location = \"$[{%l}$]\";\n\tMSGPART weapon = \"\";\n\tMSGPART rl = \"\"; // note we need by \"rl\" \"lg\" and \"weapon\" for the case that player has both\n\tMSGPART lg = \"\";\n\tMSGPART cells = \"\";\n\tMSGPART extra_cells = \"\"; //\"extra\" MSGPART needed to we can put these after %l\n\tMSGPART rockets = \"\";\n\tMSGPART extra_rockets = \"\";\n\tMSGPART ammo = \"\";\n \n\tif (DEAD()) {\n\t\tTP_Msg_Lost_f();\n\t\treturn;\n\t}\n\t\n\tif (HAVE_POWERUP())\n\t\tpowerup = \"$tp_powerups \";\n \n\tif\t(HAVE_RL() && HAVE_LG()) {\n\t\trl = tp_name_rl.string;\n\t\trockets = \":$rockets \";\n\t\tlg = tp_name_lg.string;\n\t\tcells = \":$cells \";\n\t}\n\telse if (HAVE_RL()) {\n\t\trl = tp_name_rl.string;\n\t\trockets = \":$rockets \";\n\t}\n\telse if (HAVE_LG()) {\n\t\tlg = tp_name_lg.string;\n\t\tcells = \":$cells \";\n\t}\n\telse if (HAVE_GL()) {\n\t\tweapon = tp_name_gl.string;\n\t\tammo = \":$rockets \";\n\t}\n\telse if (HAVE_SNG()) {\n\t\tweapon = tp_name_sng.string;\n\t\tammo = \":$nails \";\n\t}\n\telse if (HAVE_SSG()) {\n\t\tweapon = tp_name_ssg.string;\n\t\tammo = \":$shells \";\n\t}\n\t\n\t// extra rockets and cells\n\tif (!HAVE_RL() && HAVE_ROCKETS())\n\t\textra_rockets = \" {&cf13r&cfff}:$rockets\"; // see below comment\n\tif (!HAVE_LG() && HAVE_CELLS())\n\t\textra_cells = \" {&c2aac&cfff}:$cells\"; //the \"r\" and \"c\" are hard-coded to have the same colors as tp_name_rl and tp_name_lg. Not sure if this is a good idea\n\t\t\t\t\t\t\t\t\t\t\t  //since the user can change those colors and then it won't match up\n \t \n\tTP_Send_TeamSay(\"%s%s %s%s%s%s%s%s%s%s%s\", powerup, armor_health, weapon, ammo, rl, rockets, lg, cells, location, extra_rockets, extra_cells);\n}\n\nvoid TP_Msg_Need_f (void);\nGLOBAL void TP_Msg_EnemyPowerup_f (void) // might as well add flag to this monster. need $point and $took to see red/blue flag!\n{\n\t/*\tThis is the \"go-to\" function!\". It contains all possible scenarios for any player, teammate or enemy, with any combination of powerup.\n\t!!! Please note this function is grouped into four parts: player with eyes (assumed enemy), enemy with powerup, you with powerup, teammate with powerup. !!! */\n\textern cvar_t tp_name_quad, tp_name_pent, tp_name_quaded, tp_name_pented, tp_name_eyes, tp_name_enemy;\n\tMSGPART quad = \"\";\n\tMSGPART quaded = \"\";\n\tMSGPART pent = \"\";\n\tMSGPART pented = \"\";\n\tMSGPART eyes = \"\";\n\tMSGPART team = \"\";\n\tMSGPART enemy = \"\";\n\tMSGPART location = \"at $[{%y}$]\";\n\tMSGPART space = \"\";\n\t\n\tif (flashed) return;\n    TP_FindPoint();\n\t\n\t/* Note we don't have && INPOINT(enemy) in the below if.\n\tThis is because $point DOES NOT TELL YOU TEAMMATE/ENEMY if they have ring (because there is no way to know).\n\tTherefore, we don't assume enemy when we see eyes anymore because this confuses people into thinking it's ALWAYS enemy, so we just say \"eyes at location\" */\t\t\n\tif (INPOINT(eyes)) {\n\t\teyes = tp_name_eyes.string;\n\n\t\tif (INPOINT(quaded) && INPOINT(pented))\t{ // Since we can't say \"enemy (or) team eyes\", instead we say \"quaded pented eyes\"\n\t\t\tquaded = tp_name_quaded.string;\n\t\t\tpented = tp_name_pented.string;\n\t\t}\n\t\telse if (INPOINT(quaded))\n\t\t\tquad = tp_name_quad.string;\n\t\telse if (INPOINT(pented))\n\t\t\tpent = tp_name_pent.string;\n\t}\n\telse if (INPOINT(enemy)) {\n\t\tenemy = tp_name_enemy.string; // %q is last seen powerup of enemy. defaults to quad, which is nice (but it won't be colored)\n\t\tspace = \" \";\n\n\t\tif (INPOINT(quaded) && INPOINT(pented))\t{\n\t\t\tquad = tp_name_quad.string;\n\t\t\tpent = tp_name_pent.string;\n\t\t}\n\t\telse if (INPOINT(quaded))\n\t\t\tquad = tp_name_quad.string;\n\t\telse if (INPOINT(pented))\n\t\t\tpent = tp_name_pent.string;\n\t\telse {// Default to \"enemy powerup\", because that's the purpose of this function\n\t\t\tquad = \"%q\"; //we're hi-jacking \"quad\" here\n\t\t\tlocation = \"\"; //Report only \"enemy quad\", not \"enemy quad %l\" which is confusing\n\t\t}\n\t}\n\telse if (HAVE_POWERUP()) {\n\t\tif (DEAD()) { // if you are dead with powerup, then you dont technically have it.\n\t\t\tTP_Msg_Lost_f(); // this function will take care of it\n\t\t\treturn;\n\t\t}\n\t\t\n\t\tTP_GetNeed();\t\n\t\tif (NEED(health) || NEED(armor) || NEED_WEAPON() || NEED(rockets) || NEED(cells)) { // Note this doesn't include shells/nails. Not as broad as tp_msgneed.\n\t\t\tTP_Msg_Need_f();\n\t\t\treturn;\n\t\t}\n\t\telse\n\t\t\tteam = \"{&c0b0team&cfff} $tp_powerups\";\n\t}\n\telse if (INPOINT(teammate)) {\n\t\tteam = \"{&c0b0team&cfff} \";\n\n\t\tif (INPOINT(quaded) && INPOINT(pented))\t{\n\t\t\tquad = tp_name_quad.string;\n\t\t\tpent = tp_name_pent.string;\n\t\t}\n\t\telse if (INPOINT(quaded))\n\t\t\tquad = tp_name_quad.string;\n\t\telse if (INPOINT(pented))\n\t\t\tpent = tp_name_pent.string;\n\t\telse { // Default to \"enemy powerup\", because that's the purpose of this function\n\t\t\tteam = tp_name_enemy.string; // We're hi-jacking \"team\" to mean enemy here. %q is last seen powerup of enemy. defaults to quad, which is nice (but it won't be colored)\n\t\t\tquad = \"%q\"; // we're hi-jacking \"quad\" here\n\t\t\tlocation = \"\"; //Report only \"enemy quad\", not \"enemy quad %l\" which is confusing\n\t\t}\n\t}\n\telse { // Default to \"enemy powerup\", because that's the purpose of this function\n\t\tenemy = tp_name_enemy.string; // %q is last seen powerup of enemy. defaults to quad, which is nice (but it won't be colored)\n\t\tlocation = \"%q\"; //we're hi-jacking \"location\" here\n\t}\n\n\tTP_Send_TeamSay(\"%s%s%s%s%s%s%s%s %s\", team, enemy, space, quad, pent, quaded, pented, eyes, location);\n}\n\n\nLOCAL void TP_Msg_GetPentQuad(qbool quad)\n{\n\textern cvar_t tp_name_quad, tp_name_pent;\n\tMSGPART get = \"get\";\n\tMSGPART powerup = \"\";\n\n\tTP_FindPoint();\n \n\tif (quad)\n\t{\n\t\tif (INPOINT(eyes) && INPOINT(quaded))\n\t\t\treturn; // Don't know for sure if it's enemy or not, and can't assume like we do in tp_enemypwr because this isn't tp_ENEMYpwr\n\t\telse if (HAVE_QUAD() || INPOINT(quaded)) {\n\t\t\tTP_Msg_EnemyPowerup_f(); // let tp_msgenemypwr handle it. Also works for the case that we have quad and are dead\n\t\t\treturn;\n\t\t}\n\t\telse\n\t\t\tpowerup = tp_name_quad.string;\n\t}\n\telse // get pent\n\t{\n\t\tif (INPOINT(eyes) && INPOINT(pented))\n\t\t\treturn; // Don't know for sure if it's enemy or not, and can't assume like we do in tp_enemypwr because this isn't tp_ENEMYpwr\n\t\telse if (HAVE_PENT() || INPOINT(pented)) { // if anyone has pent, as long as they dont have ring\n\t\t\tTP_Msg_EnemyPowerup_f(); // send to tp_enemypwr\n\t\t\treturn;\n\t\t}\t\t\t\n\t\telse\n\t\t\tpowerup = tp_name_pent.string;\n\t}\n \n\tTP_Send_TeamSay(\"%s %s\", get, powerup);\n}\nGLOBAL void TP_Msg_GetQuad_f (void) { TP_Msg_GetPentQuad(true); }\nGLOBAL void TP_Msg_GetPent_f (void) { TP_Msg_GetPentQuad(false); }\n\nGLOBAL void TP_Msg_QuadDead_f (void)\n{\n\textern cvar_t tp_name_quad;\n    MSGPART quad = tp_name_quad.string;\n\tMSGPART dead = \"dead/over\";\n\n\tTP_FindPoint();\n\tif (HAVE_QUAD() && DEAD()) {\n\t\tTP_Msg_Lost_f(); // we use this function because it checks for dropped RL's, etc\n\t\treturn;\n\t}\n\telse if (INPOINT(quaded)) { // This check is to make sure the button is not pressed accidentally.\n\t\tTP_Msg_EnemyPowerup_f(); // tp_enemypwr can handle this & all cases regarding players/powerups\n\t\treturn;\n\t}\n\n\tTP_Send_TeamSay(\"%s %s\", quad, dead);\n}\n\n\nGLOBAL void TP_Msg_Took_f (void)\n{\n\textern cvar_t tp_name_lg, tp_name_rl, tp_name_gl, tp_name_sng, tp_name_backpack, tp_name_cells, tp_name_rockets, tp_name_mh,\n\t\ttp_name_ra, tp_name_ya, tp_name_ga, tp_name_flag, tp_name_rune1, tp_name_rune2, tp_name_rune3, tp_name_rune4;\n\tMSGPART took = \"\";\n\tMSGPART at_location = \" $[{%Y}$]\"; // %Y is took location, remembers for 15 secs\n\tMSGPART powerup = \"\";\n\tMSGPART took_msg = \"\";\n \n\tif (TOOK_EMPTY())\n\t\treturn;\n\t\n\tif (TOOK(quad) || TOOK(pent) || TOOK(ring)) {\n\t\tTP_GetNeed();\n\t\t\n\t\tif (DEAD())\t{\n\t\t\tTP_Msg_QuadDead_f();\n\t\t\treturn;\n\t\t}\n\t\t\n\t\t// Note that we check if you are holding powerup. This is because TOOK remembers for 15 seconds.\n\t\t// So a case could arise where you took quad then died less than 15 seconds later, and you'd be reporting \"team need %u\" (because $tp_powerups would be empty)\n\t\telse if ((NEED(health) || NEED(armor) || NEED_WEAPON() || NEED(rockets) || NEED(cells)) && HAVE_POWERUP())\n\t\t\ttook = \"{&c0b0team&cfff} $tp_powerups need %u\";\n\t\telse if (HAVE_QUAD() || HAVE_RING()) // notice we can't send this check to tp_msgenemypwr, because if enemy with powerup is in your view, tp_enemypwr reports enemypwr first, but in this function you want to report TEAM powerup.\n\t\t\ttook = \"{&c0b0team&cfff} $tp_powerups\";\n\t\telse { // In this case, you took quad or ring and died before 15 secs later. So just report what you need, nothing about powerups.\n\t\t\ttook = \"need %u\"; //notice we don't say quad over, because it could be that you held ring. No way to distinguish\n\t\t}\n\t}\n\telse {\n\t\tif\t\t(TOOK(rl))\t\t\ttook = tp_name_rl.string;\n\t\telse if (TOOK(lg))\t\t\ttook = tp_name_lg.string;\n\t\telse if (TOOK(gl))\t\t\ttook = tp_name_gl.string;\n\t\telse if (TOOK(sng))\t\t\ttook = tp_name_sng.string;\n\t\telse if (TOOK(pack))    \ttook = tp_name_backpack.string;\n\t\telse if (TOOK(cells))\t\ttook = tp_name_cells.string;\n\t\telse if (TOOK(rockets))\t\ttook = tp_name_rockets.string;\n\t\telse if (TOOK(mh))\t\t\ttook = tp_name_mh.string;\n\t\telse if (TOOK(ra))\t\t\ttook = tp_name_ra.string;\n\t\telse if (TOOK(ya))\t\t\ttook = tp_name_ya.string;\n\t\telse if (TOOK(ga))\t\t\ttook = tp_name_ga.string;\n\t\telse if (TOOK(flag))\t\ttook = tp_name_flag.string;\n\t\telse if (TOOK(rune1))\t\ttook = tp_name_rune1.string;\n\t\telse if (TOOK(rune2))\t\ttook = tp_name_rune2.string;\n\t\telse if (TOOK(rune3))\t\ttook = tp_name_rune3.string;\n\t\telse if (TOOK(rune4))\t\ttook = tp_name_rune4.string;\n\t\telse \t\t\t\t\t\ttook = \"{$took}\"; // This should never happen\n\t\t\n\t\ttook_msg = \"took \";\n\t\t\n\t\tif (HAVE_POWERUP())\n\t\t\tpowerup = \"$tp_powerups \";\n\t\telse\n\t\t\tpowerup = \"\";\t\n\t}\n\tTP_Send_TeamSay(\"%s%s%s%s\", powerup, took_msg, took, at_location);\n}\n\n\nGLOBAL void TP_Msg_Point_f (void)\n{\n\textern cvar_t tp_name_quad, tp_name_pent, tp_name_ring, tp_name_lg, tp_name_rl, tp_name_gl, tp_name_sng, tp_name_backpack,\n\t\ttp_name_ra, tp_name_ya, tp_name_ga, tp_name_cells, tp_name_rockets, tp_name_nails, tp_name_shells, tp_name_mh,\n\t\ttp_name_flag, tp_name_disp,\ttp_name_sentry, tp_name_rune1, tp_name_rune2, tp_name_rune3, tp_name_rune4, tp_name_teammate, tp_name_enemy;\n\tMSGPART point = \"\";\t\n\tMSGPART at_location = \"at $[{%y}$] {%E}\";\n\tMSGPART enemy = \"\";\n\tMSGPART powerup = \"\";\n \n    if (flashed) // for teamfortres.\n\t\t\treturn;\n\n    TP_FindPoint();\n\n\tif (POINT_EMPTY())\n\t\treturn;\n\telse if (INPOINT(eyes) || INPOINT(quaded) || INPOINT (pented)) {\n\t\tTP_Msg_EnemyPowerup_f(); // we use tp_enemypwr because it checks for all cases of player + powerup =)\n\t\treturn;\n\t}\n\n\t// if you see something other than a player with powerup\n\tif (INPOINT(enemy))\t{\n\t\tpoint = \"{%E}\";\n\t\tenemy = tp_name_enemy.string;\n\t\tat_location = \" at $[{%y}$]\"; // here we change at_location (remove {%E} at the end) because our point says how many enemies we have.\n\t}\n\telse if (INPOINT(teammate))\t{\n\t\tif (tp_name_teammate.string[0]) // This will print the nick of your teammate if tp_name_teammate \"\" (not set). Else, it will print the value for tp_name_teammate\n\t\t\tpoint = tp_name_teammate.string;\n\t\telse\n\t\t\tpoint = va (\"{&c0b0%s&cfff}\", Macro_PointName());\n\t}\n\telse if (INPOINTPOWERUP() || INPOINTWEAPON() || INPOINTARMOR() || INPOINTAMMO() || INPOINT(pack) || INPOINT(mh) || INPOINT(flag) || INPOINT (disp) || INPOINT(sentry) || INPOINT(runes)) {\n\t\tif\t\t(INPOINT(quad))\t\tpoint = tp_name_quad.string;\n\t\telse if (INPOINT(pent))\t\tpoint = tp_name_pent.string;\n\t\telse if (INPOINT(ring))\t\tpoint = tp_name_ring.string;\n\t\t\n\t\telse if (INPOINT(rl))\t\tpoint = tp_name_rl.string;\n\t\telse if (INPOINT(lg))\t\tpoint = tp_name_lg.string;\n\t\telse if (INPOINT(gl))\t\tpoint = tp_name_gl.string;\n\t\telse if (INPOINT(sng))\t\tpoint = tp_name_sng.string;\n\t\telse if (INPOINT(pack))\t\tpoint = tp_name_backpack.string;\n\t\t\n\t\telse if (INPOINT(ra))\t\tpoint = tp_name_ra.string;\n\t\telse if (INPOINT(ya))\t\tpoint = tp_name_ya.string;\n\t\telse if (INPOINT(ga))\t\tpoint = tp_name_ga.string;\n\t\t\n\t\telse if (INPOINT(rockets)) \tpoint = tp_name_rockets.string;\n\t\telse if (INPOINT(cells)) \tpoint = tp_name_cells.string;\n\t\telse if (INPOINT(nails)) \tpoint = tp_name_nails.string;\n\t\telse if (INPOINT(shells)) \tpoint = tp_name_shells.string;\n\t\t\n\t\telse if (INPOINT(mh))\t\tpoint = tp_name_mh.string;\n\t\t\n\t\t//TF. Note we can't tell whether they are enemy or team flag/disp/sent\n\t\telse if (INPOINT(flag))\t\tpoint = tp_name_flag.string;\n\t\telse if (INPOINT(disp))\t\tpoint = tp_name_disp.string;\n\t\telse if (INPOINT(sentry))\tpoint = tp_name_sentry.string;\n\t\t\n\t\t//ctf, other\n\t\telse if (INPOINT(rune1))\tpoint = tp_name_rune1.string;\n\t\telse if (INPOINT(rune2))\tpoint = tp_name_rune2.string;\n\t\telse if (INPOINT(rune3))\tpoint = tp_name_rune3.string;\n\t\telse if (INPOINT(rune4))\tpoint = tp_name_rune4.string;\n\t}\n\telse point = \"{$point}\"; // this should never happen\n\n\tTP_Send_TeamSay(\"%s%s %s%s\", powerup, point, enemy, at_location);\n}\n\n\nGLOBAL void TP_Msg_Need_f (void)\n{\n\tMSGPART team_powerup = \"\";\n    MSGPART need = \"\";\n \n\tif (DEAD())\n\t\treturn;\n\n\tTP_GetNeed();\n\n\tif (NEED(health) || NEED(armor) || NEED_WEAPON() || NEED(rockets) || NEED(cells) || NEED(shells) || NEED(nails)) {\n\t\tneed = \"need %u $[{%l}$]\";\n\t\t\n\t\tif (HAVE_POWERUP())\n\t\t\tteam_powerup = \"{&c0b0team&cfff} $tp_powerups \";\n\t\t\n\t\tif (need[0] == 0 && team_powerup[0] == 0)\n\t\t\treturn;\n\n\t\tTP_Send_TeamSay(\"%s%s\", team_powerup, need);\n\t}\n}\n\nGLOBAL void TP_Msg_Safe_f (void)\n{\n\textern cvar_t tp_name_rlg, tp_name_separator;\n\tMSGPART armor = \"\";\n\tMSGPART separator = \"\";\n\tMSGPART weapon = \"\";\n\n\tif (DEAD())\n\t\treturn;\n\n\tTP_FindPoint(); // needed to make sure the area is, in fact, safe\n\tif (INPOINT(enemy) && !(INPOINT(quaded) || INPOINT(pented))) {\n\t\treturn; //if you see an enemy without powerup, the place is still not 100% safe. But maybe you can handle him, so don't report anything yet.\n\t}\n\tif (INPOINT(enemy) && (INPOINT(quaded) || INPOINT(pented))) {\n\t\tTP_Msg_EnemyPowerup_f(); // if you see an enemy with a powerup, place is definitely not secure. report enemy powerup.\n\t\treturn;\n\t}\n\t\n\tif ((HAVE_RA() || HAVE_YA() || HAVE_GA()) && (HAVE_RL() || HAVE_LG()))\n\t\tseparator = tp_name_separator.string;\n\tif (HAVE_RA() || HAVE_YA() || HAVE_GA())\n\t\tarmor = \"$colored_armor\";\n\tif (HAVE_RL() && HAVE_LG())\n\t\tweapon = tp_name_rlg.string;\n\telse if (HAVE_RL() || HAVE_LG())\n\t\tweapon = \"$bestweapon\";\n\tTP_Send_TeamSay(\"%s %s%s%s\", \"{&c0b0safe&cfff} {&c0b0[&cfff}{%l}{&c0b0]&cfff}\", armor, separator, weapon);\n}\n\nGLOBAL void TP_Msg_KillMe_f (void)\n{\n\textern cvar_t tp_name_rl, tp_name_lg;\n\tMSGPART point = \"\";\n\tMSGPART kill_me = \"{&cb1akill me [&cfff}{%l}{&cf2a]&cfff}\";\n\tMSGPART weapon = \"\";\n\tMSGPART weapon_ammo = \"\";\n\tMSGPART extra_rockets = \"\";\n\tMSGPART extra_cells = \"\";\n\n\tif (DEAD())\n\t\treturn;\n\n\tTP_FindPoint();\n\tif (INPOINT(teammate))\n\t\tpoint = va (\"{&c0b0%s&cfff} \", Macro_PointName()); // Saying teammate kill me isn't much help. Only report if you can say e.g. Up2 Kill Me!\n\n\tif (HOLD_RL()) {\n\t\tweapon = tp_name_rl.string;\n\t\tweapon_ammo = \":$rockets \";\n\t}\n\telse if (HOLD_LG()) {\n\t\tweapon = tp_name_lg.string;\n\t\tweapon_ammo = \":$cells \";\n\t}\n\n\tif (!HOLD_RL() && HAVE_ROCKETS())\n\t\textra_rockets = \"{&cf13r&cfff}:$rockets \"; // see below comment\n\tif (!HOLD_LG() && HAVE_CELLS())\n\t\textra_cells = \"{&c2aac&cfff}:$cells\"; //the \"r\" and \"c\" are hard-coded to have the same colors as tp_name_rl and tp_name_lg. Not sure if this is a good idea\n\t\t\t\t\t\t\t\t\t\t\t  //since the user can change those colors and then it won't match up\n\n\tTP_Send_TeamSay(\"%s%s %s%s%s%s\", point, kill_me, weapon, weapon_ammo, extra_rockets, extra_cells);\n}\n\nGLOBAL void TP_Msg_YouTake_f (void)\n{\n\tMSGPART point = \"\";\n\tMSGPART take = \"you take $[{%l}$]\";\n\n\tTP_FindPoint();\n\tif (INPOINT(teammate)) {\n\t\t\tpoint = va (\"{&c0b0%s&cfff} \", Macro_PointName()); // Saying teammate take isn't much help. Only report if you can say e.g. Up2 take!\n\t\t\ttake = \"take $[{%l}$]\";\n\t}\n\n\tTP_Send_TeamSay(\"%s%s\", point, take);\n}\n\nGLOBAL void TP_Msg_Help_f (void)\n{\n\tTP_Send_TeamSay(\"%s%s\", (HAVE_POWERUP() ? \"$tp_powerups \" : \"\"), \"{&cff0help&cfff} {&cff0[&cfff}{%l}{&cff0]&cfff} {%e}\"); // yellow help\n}\n\n// The following define allows us to make as many functions as we want and get the message \"powerup message location\"\n#define TP_MSG_GENERIC(type) TP_Send_TeamSay(\"%s\" type \" $[{%%l}$]\", (HAVE_POWERUP() ? \"$tp_powerups \" : \"\"))\n\nGLOBAL void TP_Msg_YesOk_f (void)\t\t{ TP_MSG_GENERIC(\"{yes/ok}\"); }\nGLOBAL void TP_Msg_NoCancel_f (void)\t{ TP_MSG_GENERIC(\"{&cf00no/cancel&cfff}\"); } //red no/cancel\nGLOBAL void TP_Msg_ItemSoon_f (void)\t{ TP_MSG_GENERIC(\"item soon\"); }\nGLOBAL void TP_Msg_Waiting_f (void)\t\t{ TP_MSG_GENERIC(\"waiting\"); }\nGLOBAL void TP_Msg_Slipped_f (void)\t\t{ TP_MSG_GENERIC(\"enemy slipped\"); }\nGLOBAL void TP_Msg_Replace_f (void)\t\t{ TP_MSG_GENERIC(\"replace\"); }\nGLOBAL void TP_Msg_Trick_f (void)\t\t{ TP_MSG_GENERIC(\"trick\"); }\nGLOBAL void TP_Msg_Coming_f (void)\t\t{ TP_MSG_GENERIC(\"coming\"); }\n\n//TF binds\nGLOBAL void TP_Msg_TFConced_f (void)\n{\n\textern cvar_t tp_name_filter;\n\tMSGPART conced = \"\";\n\tMSGPART filter = tp_name_filter.string;\n\n\tTP_FindPoint();\n\tif (INPOINT(enemy))\n\t\tconced = \"$point conced at $[{%y}$]\";\n\telse\n\t\tconced = \"{Enemy conced}\";\n\n\tTP_Send_TeamSay(\"%s %s\", conced, filter);\n}\n\n///////////////////////////////////\n////// End teamplay scripts ///////\n///////////////////////////////////\n\n\nGLOBAL const char *TP_MSG_Colored_Armor(void) // $colored_armor\n{\n\tMSGPART armor = \"\";\n \n\tif (HAVE_GA())\n\t\tarmor = COLORED(0b0,%a);\n\telse if (HAVE_YA())\n\t\tarmor = COLORED(ff0,%a);\n\telse if (HAVE_RA())\n\t\tarmor = COLORED(e00,%a);\n\telse\n\t\tarmor = \"0\";\n \n    return armor;\n}\n\nGLOBAL const char * TP_MSG_Colored_Powerup(void)\n{\n\textern cvar_t tp_name_pent, tp_name_quad, tp_name_ring, tp_name_separator;\n    MSGPART pent = \"\";\n\tMSGPART quad = \"\";\n\tMSGPART ring = \"\";\n\n\tif (HAVE_QUAD() && HAVE_PENT() && HAVE_RING()) {\n\t\tpent = tp_name_pent.string;\n\t\tquad = tp_name_quad.string;\n\t\tring = tp_name_ring.string;\n\t}\n\telse if (HAVE_QUAD() && HAVE_PENT()) {\n\t\tpent = tp_name_pent.string;\n\t\tquad = tp_name_quad.string;\n\t}\n\telse if (HAVE_QUAD() && HAVE_RING()) {\n\t\tquad = tp_name_quad.string;\n\t\tring = tp_name_ring.string;\n\t}\n\telse if (HAVE_PENT() && HAVE_RING()) {\n\t\tpent = tp_name_pent.string;\n\t\tring = tp_name_ring.string;\n\t}\n\telse if (HAVE_QUAD())\n\t\tquad = tp_name_quad.string;\n\telse if (HAVE_PENT())\n\t\tpent = tp_name_pent.string;\n\telse if (HAVE_RING())\n\t\tring = tp_name_ring.string;\n\n    return va(\"%s%s%s\", quad, pent, ring);\n}\n\n#define tp_ib_name_q\t    COLORED(03F,q)\t\t// blue q for quad\n#define tp_ib_name_p\t    COLORED(e00,p)\t\t// red p for pent\n#define tp_ib_name_r\t    COLORED(ff0,r)\t\t// yellow r for ring\n\nGLOBAL const char * TP_MSG_Colored_Short_Powerups(void) // this displays \"qrp\" instead of \"quad ring pent\", some people prefer this!\n{\n    MSGPART msg = \"\";\n\n    if (HAVE_QUAD() && HAVE_RING() && HAVE_PENT())\n\t\tmsg = tp_ib_name_q tp_ib_name_r tp_ib_name_p;\n\telse if (HAVE_QUAD() && HAVE_PENT())\n\t\tmsg = tp_ib_name_q tp_ib_name_p;\n\telse if (HAVE_QUAD() && HAVE_RING())\n\t\tmsg = tp_ib_name_q tp_ib_name_r;\n\telse if (HAVE_PENT() && HAVE_RING())\n\t\tmsg = tp_ib_name_p tp_ib_name_r;\n\telse if (HAVE_QUAD())\n\t\tmsg = tp_ib_name_q;\n\telse if (HAVE_PENT())\n\t\tmsg = tp_ib_name_p;\n\telse if (HAVE_RING())\n\t\tmsg = tp_ib_name_r;\n\telse\n\t\tmsg = \"\";\n\n    return msg;\n}\n"
  },
  {
    "path": "src/tp_msgs.h",
    "content": "/*\nCopyright (C) 2011 johnnycz\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/**\n  \\file\n\n  \\brief\n  Inbuilt teamplay messages module\n\n  \\author johnnycz\n  \\author Up2nOgOoD[ROCK]\n**/\n\n#ifndef __TP_MSGS_H__\n#define __TP_MSGS_H__\n\nextern void TP_Msg_Lost_f (void);\nextern void TP_Msg_Report_f (void);\nextern void TP_Msg_Coming_f (void);\nextern void TP_Msg_EnemyPowerup_f (void);\nextern void TP_Msg_Safe_f (void);\nextern void TP_Msg_KillMe_f (void);\nextern void TP_Msg_Help_f (void);\nextern void TP_Msg_GetQuad_f (void);\nextern void TP_Msg_GetPent_f (void);\nextern void TP_Msg_QuadDead_f (void);\nextern void TP_Msg_Took_f (void);\nextern void TP_Msg_Point_f (void);\nextern void TP_Msg_Need_f (void);\nextern void TP_Msg_YesOk_f (void);\nextern void TP_Msg_NoCancel_f (void);\nextern void TP_Msg_YouTake_f (void);\nextern void TP_Msg_ItemSoon_f (void);\nextern void TP_Msg_Waiting_f (void);\nextern void TP_Msg_Slipped_f (void);\nextern void TP_Msg_Replace_f (void);\nextern void TP_Msg_Trick_f (void);\n//TF messages\nextern void TP_Msg_TFConced_f (void);\n\nextern const char* TP_MSG_Colored_Armor(void);\nextern const char * TP_MSG_Colored_Powerup(void);\nextern const char * TP_MSG_Colored_Short_Powerups(void);\n\n#endif // __TP_MSGS_H__\n"
  },
  {
    "path": "src/tp_triggers.c",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n \nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n \nSee the GNU General Public License for more details.\n \nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: tp_triggers.c,v 1.9 2007-10-01 18:31:06 disconn3ct Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"qsound.h\"\n#include \"teamplay.h\"\n#include \"rulesets.h\"\n#include \"tp_triggers.h\"\n#include \"utils.h\"\n\ncvar_t tp_msgtriggers = {\"tp_msgtriggers\", \"1\"};\ncvar_t tp_soundtrigger = {\"tp_soundtrigger\", \"~\"};\ncvar_t tp_triggers = {\"tp_triggers\", \"1\"};\ncvar_t tp_forceTriggers = {\"tp_forceTriggers\", \"0\"};\n// re-triggers stuff\ncvar_t re_sub[10] = {{\"re_trigger_match_0\", \"\", CVAR_ROM},\n                    {\"re_trigger_match_1\", \"\", CVAR_ROM},\n                    {\"re_trigger_match_2\", \"\", CVAR_ROM},\n                    {\"re_trigger_match_3\", \"\", CVAR_ROM},\n                    {\"re_trigger_match_4\", \"\", CVAR_ROM},\n                    {\"re_trigger_match_5\", \"\", CVAR_ROM},\n                    {\"re_trigger_match_6\", \"\", CVAR_ROM},\n                    {\"re_trigger_match_7\", \"\", CVAR_ROM},\n                    {\"re_trigger_match_8\", \"\", CVAR_ROM},\n                    {\"re_trigger_match_9\", \"\", CVAR_ROM}};\n \ncvar_t re_subi[10] = {{\"internal0\"},\n                     {\"internal1\"},\n                     {\"internal2\"},\n                     {\"internal3\"},\n                     {\"internal4\"},\n                     {\"internal5\"},\n                     {\"internal6\"},\n                     {\"internal7\"},\n                     {\"internal8\"},\n                     {\"internal9\"}};\n \nstatic pcre_trigger_t *re_triggers;\nstatic pcre_internal_trigger_t *internal_triggers;\n\nextern char lastip[64];\n\n/********************************** TRIGGERS **********************************/\n \ntypedef struct f_trigger_s {\n\tchar *name;\n\tqbool restricted;\n\tqbool teamplay;\n} f_trigger_t;\n \nf_trigger_t f_triggers[] = {\n\t{\"f_newmap\", false, false},\n\t{\"f_spawn\", false, false},\n\t{\"f_mapend\", false, false},\n\t{\"f_reloadstart\", false, false},\n\t{\"f_reloadend\", false, false},\n\t{\"f_cfgload\", false, false},\n\t{\"f_exit\", false, false},\n\t{\"f_demostart\", false, false},\n\t{\"f_demoend\", false, false},\n\t{\"f_captureframe\", false, false},\n\t{\"f_sbrefreshdone\", false, false},\n\t{\"f_sbupdatesourcesdone\", false, false},\n\t{\"f_focusgained\", false, false},\n\n\t{\"f_freeflyspectate\", false, false},\n\t{\"f_trackspectate\", false, false},\n\n\t{\"f_weaponchange\", false, false},\n\n\t{\"f_took\", true, true},\n\t{\"f_respawn\", true, true},\n\t{\"f_death\", true, true},\n\t{\"f_flagdeath\", true, true},\n\n\t{\"f_conc\", true, true},\n\t{\"f_flash\", true, true},\n\t{\"f_bonusflash\", true, true},\n\n\t{\"f_demomatchstart\", false, false},\n\t{\"f_countdownstart\", false, false},\n\t{\"f_countdownbreak\", false, false}\n};\n \n#define num_f_triggers\t(sizeof(f_triggers) / sizeof(f_triggers[0]))\n \nvoid TP_ExecTrigger (const char *trigger)\n{\n\tint i, j, numteammates = 0;\n\tcmd_alias_t *alias;\n \n\tif (!tp_triggers.value || ((cls.demoplayback || cl.spectator) && cl_restrictions.value))\n\t\treturn;\n \n\tfor (i = 0; i < num_f_triggers; i++) {\n\t\tif (!strcmp (f_triggers[i].name, trigger))\n\t\t\tbreak;\n\t}\n\n\tif (i == num_f_triggers) {\n\t\tCom_Printf (\"Unknown f_trigger \\\"%s\\\"\", trigger);\n\t\treturn;\n\t}\n \n\tif (f_triggers[i].teamplay && !tp_forceTriggers.value) {\n\t\tif (!cl.teamplay)\n\t\t\treturn;\n \n\t\tfor (j = 0; j < MAX_CLIENTS; j++)\n\t\t\tif (cl.players[j].name[0] && !cl.players[j].spectator && j != cl.playernum)\n\t\t\t\tif (!strcmp(cl.players[j].team, cl.players[cl.playernum].team))\n\t\t\t\t\tnumteammates++;\n \n\t\tif (!numteammates)\n\t\t\treturn;\n\t}\n \n\tif ((alias = Cmd_FindAlias (trigger))) {\n\t\tif (!(f_triggers[i].restricted && Rulesets_RestrictTriggers ())) {\n\t\t\tCbuf_AddTextEx(alias->flags & ALIAS_SERVER ? &cbuf_svc : &cbuf_main,\n\t\t\t\tva(\"%s\\n\", alias->value));\n\t\t}\n\t}\n}\n \n/******************************* SOUND TRIGGERS *******************************/\n \n//Find and execute sound triggers. A sound trigger must be terminated by either a CR or LF.\n//Returns true if a sound was found and played\nqbool TP_CheckSoundTrigger (wchar *wstr)\n{\n\tchar *str;\n\tint i, j, start, length;\n\tchar soundname[MAX_OSPATH];\n\tvfsfile_t *v;\n\n\tstr = wcs2str (wstr);\n \n\tif (!*str) {\n\t\treturn false;\n\t}\n \n\tif (!tp_soundtrigger.string[0]) {\n\t\treturn false;\n\t}\n\n\tif (Rulesets_RestrictTriggers()) {\n\t\treturn false;\n\t}\n \n\tfor (i = strlen (str) - 1; i; i--)\t{\n\t\tif (str[i] != 0x0A && str[i] != 0x0D)\n\t\t\tcontinue;\n \n\t\tfor (j = i - 1; j >= 0; j--) {\n\t\t\t// quick check for chars that cannot be used\n\t\t\t// as sound triggers but might be part of a file name\n\t\t\tif (isalnum((unsigned char)str[j]))\n\t\t\t\tcontinue;\t// file name or chat\n \n\t\t\tif (strchr(tp_soundtrigger.string, str[j]))\t{\n\t\t\t\t// this might be a sound trigger\n \n\t\t\t\tstart = j + 1;\n\t\t\t\tlength = i - start;\n \n\t\t\t\tif (!length)\n\t\t\t\t\tbreak;\n\t\t\t\tif (length >= MAX_QPATH)\n\t\t\t\t\tbreak;\n \n\t\t\t\tstrlcpy (soundname, str + start, length + 1);\n\t\t\t\tif (strstr(soundname, \"..\"))\n\t\t\t\t\tbreak;\t// no thank you\n \n\t\t\t\t// clean up the message\n\t\t\t\tQ_strcpy (str + j, str + i);\n\t\t\t\tqwcscpy (wstr + j, wstr + i);\n \n\t\t\t\tif (!snd_initialized || !snd_started)\n\t\t\t\t\treturn false;\n \n\t\t\t\tCOM_DefaultExtension (soundname, \".wav\", sizeof(soundname));\n \n\t\t\t\t// make sure we have it on disk (FIXME)\n\t\t\t\tif (!(v = FS_OpenVFS(va(\"sound/%s\", soundname), \"rb\", FS_ANY))) \n\t\t\t\t\treturn false;\n\t\t\t\tVFS_CLOSE(v);\n \n\t\t\t\t// now play the sound\n\t\t\t\tS_LocalSound (soundname);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (str[j] == '\\\\')\n\t\t\t\tstr[j] = '/';\n\t\t\tif (str[j] <= ' ' || strchr(\"\\\"&'*,:;<>?\\\\|\\x7f\", str[j]))\n\t\t\t\tbreak;\t// we don't allow these in a file name\n\t\t}\n\t}\n \n\treturn false;\n}\n\n/****************************** MESSAGE TRIGGERS ******************************/\n \ntypedef struct msg_trigger_s\n{\n\tchar\tname[32];\n\tchar\tstring[64];\n\tint\t\tlevel;\n\tstruct msg_trigger_s *next;\n} msg_trigger_t;\n \nstatic msg_trigger_t *msg_triggers;\n \nvoid TP_ResetAllTriggers (void)\n{\n\tmsg_trigger_t *temp;\n \n\twhile (msg_triggers) {\n\t\ttemp = msg_triggers->next;\n\t\tQ_free(msg_triggers);\n\t\tmsg_triggers = temp;\n\t}\n}\n \nvoid TP_DumpTriggers (FILE *f)\n{\n\tmsg_trigger_t *t;\n \n\tfor (t = msg_triggers; t; t = t->next) {\n\t\tif (t->level == PRINT_HIGH)\n\t\t\tfprintf(f, \"msg_trigger  %s \\\"%s\\\"\\n\", t->name, t->string);\n\t\telse\n\t\t\tfprintf(f, \"msg_trigger  %s \\\"%s\\\" -l %c\\n\", t->name, t->string, t->level == 4 ? 't' : '0' + t->level);\n\t}\n}\n \nmsg_trigger_t *TP_FindTrigger (char *name)\n{\n\tmsg_trigger_t *t;\n \n\tfor (t = msg_triggers; t; t = t->next)\n\t\tif (!strcmp(t->name, name))\n\t\t\treturn t;\n \n\treturn NULL;\n}\n \nvoid TP_MsgTrigger_f (void)\n{\n\tint c;\n\tchar *name;\n\tmsg_trigger_t *trig;\n \n\tc = Cmd_Argc();\n \n\tif (c > 5) {\n\t\tCom_Printf (\"msg_trigger <trigger name> \\\"string\\\" [-l <level>]\\n\");\n\t\treturn;\n\t}\n \n\tif (c == 1) {\n\t\tif (!msg_triggers)\n\t\t\tCom_Printf (\"no triggers defined\\n\");\n\t\telse\n\t\t\tfor (trig=msg_triggers; trig; trig=trig->next)\n\t\t\t\tCom_Printf (\"%s : \\\"%s\\\"\\n\", trig->name, trig->string);\n\t\treturn;\n\t}\n \n\tname = Cmd_Argv(1);\n\tif (strlen(name) > 31) {\n\t\tCom_Printf (\"trigger name too long\\n\");\n\t\treturn;\n\t}\n \n\tif (c == 2) {\n\t\ttrig = TP_FindTrigger (name);\n\t\tif (trig)\n\t\t\tCom_Printf (\"%s: \\\"%s\\\"\\n\", trig->name, trig->string);\n\t\telse\n\t\t\tCom_Printf (\"trigger \\\"%s\\\" not found\\n\", name);\n\t\treturn;\n\t}\n \n\tif (c >= 3) {\n\t\tif (strlen(Cmd_Argv(2)) > 63) {\n\t\t\tCom_Printf (\"trigger string too long\\n\");\n\t\t\treturn;\n\t\t}\n \n\t\tif (!(trig = TP_FindTrigger (name))) {\n\t\t\t// allocate new trigger\n\t\t\ttrig = (msg_trigger_t *) Q_malloc(sizeof(msg_trigger_t));\n\t\t\ttrig->next = msg_triggers;\n\t\t\tmsg_triggers = trig;\n\t\t\tstrlcpy (trig->name, name, sizeof (trig->name));\n\t\t\ttrig->level = PRINT_HIGH;\n\t\t}\n \n\t\tstrlcpy (trig->string, Cmd_Argv(2), sizeof(trig->string));\n\t\tif (c == 5 && !strcasecmp (Cmd_Argv(3), \"-l\")) {\n\t\t\tif (!strcmp(Cmd_Argv(4), \"t\")) {\n\t\t\t\ttrig->level = 4;\n\t\t\t} else {\n\t\t\t\ttrig->level = Q_atoi (Cmd_Argv(4));\n\t\t\t\tif ((unsigned) trig->level > PRINT_CHAT)\n\t\t\t\t\ttrig->level = PRINT_HIGH;\n\t\t\t}\n\t\t}\n\t}\n}\n \nstatic qbool TP_IsFlagMessage (const char *message)\n{\n\tif (strstr(message, \" has your key!\") ||\n\t        strstr(message, \" has taken your Key\") ||\n\t        strstr(message, \" has your flag\") ||\n\t        strstr(message, \" took your flag!\") ||\n\t        strstr(message, \" &#65533;&#65533; &#65533;&#65533; flag!\") ||\n\t        strstr(message, \" &#65533;&#65533;&#65533;&#65533; &#65533;&#65533;\") || strstr(message, \" &#65533;&#65533;&#65533;&#65533;&#65533;&#65533;&#65533;\") ||\n\t        strstr(message, \" took the blue flag\") || strstr(message, \" took the red flag\") ||\n\t        strstr(message, \" Has the Red Flag\") || strstr(message, \" Has the Blue Flag\")\n\t   )\n\t\treturn true;\n \n\treturn false;\n}\n \nvoid TP_SearchForMsgTriggers (const char *s, int level)\n{\n\tmsg_trigger_t\t*t;\n\tchar *string;\n \n\t// message triggers disabled\n\tif (!tp_msgtriggers.value)\n\t\treturn;\n \n\t// triggers banned by ruleset\n\tif (Rulesets_RestrictTriggers () && !cls.demoplayback && !cl.spectator)\n\t\treturn;\n \n\t// we are in spec/demo mode, so play triggers if user want it\n\tif ((cls.demoplayback || cl.spectator) && cl_restrictions.value)\n\t\treturn;\n \n\tfor (t = msg_triggers; t; t = t->next) {\n\t\tif ((t->level == level || (t->level == 3 && level == 4)) && t->string[0] && strstr(s, t->string)) {\n\t\t\tif (level == PRINT_CHAT && (\n\t\t\t            strstr (s, \"f_version\") || strstr (s, \"f_skins\") || strstr(s, \"f_fakeshaft\") ||\n\t\t\t            strstr (s, \"f_server\") || strstr (s, \"f_scripts\") || strstr (s, \"f_cmdline\") ||\n\t\t\t            strstr (s, \"f_system\") || strstr (s, \"f_speed\") || strstr (s, \"f_modified\"))\n\t\t\t   )\n\t\t\t\tcontinue; // don't let llamas fake proxy replies\n \n\t\t\tif (cl.teamfortress && level == PRINT_HIGH && TP_IsFlagMessage (s))\n\t\t\t\tcontinue;\n \n\t\t\tif ((string = Cmd_AliasString (t->name))) {\n\t\t\t\tstrlcpy(vars.lasttrigger_match, s, sizeof (vars.lasttrigger_match));\n\t\t\t\tCbuf_AddTextEx (&cbuf_safe, va(\"%s\\n\", string));\n\t\t\t} else {\n\t\t\t\tCom_Printf (\"trigger \\\"%s\\\" has no matching alias\\n\", t->name);\n\t\t\t}\n\t\t}\n\t}\n}\n \n/**************************** REGEXP TRIGGERS *********************************/\n \ntypedef void ReTrigger_func (pcre_trigger_t *);\n \nstatic void Trig_ReSearch_do (ReTrigger_func f)\n{\n\tpcre_trigger_t *trig;\n \n\tfor( trig = re_triggers; trig; trig = trig->next) {\n\t\tif (ReSearchMatch (trig->name))\n\t\t\tf (trig);\n\t}\n}\n \nstatic pcre_trigger_t *prev;\npcre_trigger_t *CL_FindReTrigger (char *name)\n{\n\tpcre_trigger_t *t;\n \n\tprev=NULL;\n\tfor (t=re_triggers; t; t=t->next) {\n\t\tif (!strcmp(t->name, name))\n\t\t\treturn t;\n \n\t\tprev = t;\n\t}\n\treturn NULL;\n}\n \nstatic void DeleteReTrigger (pcre_trigger_t *t)\n{\n\tif (t->regexp)\n\t\t(pcre2_code_free)(t->regexp);\n\n\tif (t->regexpstr)\n\t\tQ_free(t->regexpstr);\n\n\tQ_free(t->name);\n\tQ_free(t);\n}\n \nstatic void RemoveReTrigger (pcre_trigger_t *t)\n{\n\t// remove from list\n\tif (prev)\n\t\tprev->next = t->next;\n\telse\n\t\tre_triggers = t->next;\n\t// free memory\n\tDeleteReTrigger(t);\n}\n \nstatic void CL_RE_Trigger_f (void)\n{\n\tint c,i,m;\n\tchar *name;\n\tchar *regexpstr;\n\tpcre_trigger_t *trig;\n\tpcre2_code *re;\n\tint error;\n\tPCRE2_SIZE error_offset;\n\tqbool newtrigger=false;\n\tqbool re_search = false;\n \n\tc = Cmd_Argc();\n\tif (c > 3) {\n\t\tCom_Printf (\"re_trigger <trigger name> <regexp>\\n\");\n\t\treturn;\n\t}\n \n\tif (c == 2 && IsRegexp(Cmd_Argv(1))) {\n\t\tre_search = true;\n\t}\n \n\tif (c == 1 || re_search) {\n\t\tif (!re_triggers) {\n\t\t\tCom_Printf (\"no regexp_triggers defined\\n\");\n\t\t} else {\n\t\t\tif (re_search && !ReSearchInit(Cmd_Argv(1)))\n\t\t\t\treturn;\n \n\t\t\tCom_Printf (\"List of re_triggers:\\n\");\n \n\t\t\tfor (trig=re_triggers, i=m=0; trig; trig=trig->next, i++) {\n\t\t\t\tif (!re_search || ReSearchMatch(trig->name)) {\n\t\t\t\t\tCom_Printf (\"%s : \\\"%s\\\" : %d\\n\", trig->name, trig->regexpstr, trig->counter);\n\t\t\t\t\tm++;\n\t\t\t\t}\n\t\t\t}\n \n\t\t\tCom_Printf (\"------------\\n%i/%i re_triggers\\n\", m, i);\n\t\t\tif (re_search)\n\t\t\t\tReSearchDone();\n\t\t}\n\t\treturn;\n\t}\n \n\tname = Cmd_Argv(1);\n\ttrig = CL_FindReTrigger (name);\n \n\tif (c == 2) {\n\t\tif (trig) {\n\t\t\tCom_Printf (\"%s: \\\"%s\\\"\\n\", trig->name, trig->regexpstr);\n\t\t\tCom_Printf (\"  options: mask=%d interval=%g%s%s%s%s%s\\n\", trig->flags & 0xFF,\n\t\t\t            trig->min_interval,\n\t\t\t            trig->flags & RE_FINAL ? \" final\" : \"\",\n\t\t\t            trig->flags & RE_REMOVESTR ? \" remove\" : \"\",\n\t\t\t            trig->flags & RE_NOLOG ? \" nolog\" : \"\",\n\t\t\t            trig->flags & RE_ENABLED ? \"\" : \" disabled\",\n\t\t\t            trig->flags & RE_NOACTION ? \" noaction\" : \"\"\n\t\t\t           );\n\t\t\tCom_Printf (\"  matched %d times\\n\", trig->counter);\n\t\t} else {\n\t\t\tCom_Printf (\"re_trigger \\\"%s\\\" not found\\n\", name);\n\t\t}\n\t\treturn;\n\t}\n \n\tif (c == 3) {\n\t\tregexpstr = Cmd_Argv(2);\n\t\tif (!trig) {\n\t\t\t// allocate new trigger\n\t\t\tnewtrigger = true;\n\t\t\ttrig = (pcre_trigger_t *) Q_malloc(sizeof(pcre_trigger_t));\n\t\t\ttrig->next = re_triggers;\n\t\t\tre_triggers = trig;\n\t\t\ttrig->name = Q_strdup(name);\n\t\t\ttrig->flags = RE_PRINT_ALL | RE_ENABLED; // catch all printed messages by default\n\t\t}\n \n\t\terror = 0;\n\t\tif ((re = pcre2_compile((PCRE2_SPTR)regexpstr, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL))) {\n\t\t\terror = 0;\n\t\t\tif (!newtrigger) {\n\t\t\t\t(pcre2_code_free)(trig->regexp);\n\t\t\t\tQ_free(trig->regexpstr);\n\t\t\t}\n\t\t\ttrig->regexpstr = Q_strdup(regexpstr);\n\t\t\ttrig->regexp = re;\n\t\t\treturn;\n\t\t} else {\n\t\t\tCom_Printf (\"Invalid regexp: %s\\n\", error);\n\t\t}\n\t\tprev = NULL;\n\t\tRemoveReTrigger(trig);\n\t}\n}\n \nstatic void CL_RE_Trigger_Options_f (void)\n{\n\tint c,i;\n\tchar* name;\n\tpcre_trigger_t *trig;\n \n\tc = Cmd_Argc ();\n\tif (c < 3) {\n\t\tCom_Printf (\"re_trigger_options <trigger name> <option1> <option2>\\n\");\n\t\treturn;\n\t}\n \n\tname = Cmd_Argv (1);\n\ttrig = CL_FindReTrigger (name);\n \n\tif (!trig) {\n\t\tCom_Printf (\"re_trigger \\\"%s\\\" not found\\n\", name);\n\t\treturn;\n\t}\n \n\tfor(i=2; i<c; i++) {\n\t\tif (!strcmp(Cmd_Argv(i), \"final\")) {\n\t\t\ttrig->flags |= RE_FINAL;\n\t\t} else if (!strcmp(Cmd_Argv(i), \"remove\")) {\n\t\t\ttrig->flags |= RE_REMOVESTR;\n\t\t} else if (!strcmp(Cmd_Argv(i), \"notfinal\")) {\n\t\t\ttrig->flags &= ~RE_FINAL;\n\t\t} else if (!strcmp(Cmd_Argv(i), \"noremove\")) {\n\t\t\ttrig->flags &= ~RE_REMOVESTR;\n\t\t} else if (!strcmp(Cmd_Argv(i), \"mask\")) {\n\t\t\ttrig->flags &= ~0xFF;\n\t\t\ttrig->flags |= 0xFF & atoi(Cmd_Argv(i+1));\n\t\t\ti++;\n\t\t} else if (!strcmp(Cmd_Argv(i), \"interval\") ) {\n\t\t\ttrig->min_interval = atof(Cmd_Argv(i+1));\n\t\t\ti++;\n\t\t} else if (!strcmp(Cmd_Argv(i), \"enable\")) {\n\t\t\ttrig->flags |= RE_ENABLED;\n\t\t} else if (!strcmp(Cmd_Argv(i), \"disable\")) {\n\t\t\ttrig->flags &= ~RE_ENABLED;\n\t\t} else if (!strcmp(Cmd_Argv(i), \"noaction\")) {\n\t\t\ttrig->flags |= RE_NOACTION;\n\t\t} else if (!strcmp(Cmd_Argv(i), \"action\")) {\n\t\t\ttrig->flags &= ~RE_NOACTION;\n\t\t} else if (!strcmp(Cmd_Argv(i), \"nolog\")) {\n\t\t\ttrig->flags |= RE_NOLOG;\n\t\t} else if (!strcmp(Cmd_Argv(i), \"log\")) {\n\t\t\ttrig->flags &= ~RE_NOLOG;\n\t\t} else {\n\t\t\tCom_Printf(\"re_trigger_options: invalid option.\\n\"\n\t\t\t           \"valid options:\\n  final\\n  notfinal\\n  remove\\n\"\n\t\t\t           \"  noremove\\n  mask <trigger_mask>\\n  interval <min_interval>)\\n\"\n\t\t\t           \"  enable\\n  disable\\n  noaction\\n  action\\n  nolog\\n  log\\n\");\n\t\t}\n\t}\n}\n \nstatic void CL_RE_Trigger_Delete_f (void)\n{\n\tpcre_trigger_t *trig, *next_trig;\n\tchar *name;\n\tint i;\n \n\tfor (i = 1; i < Cmd_Argc(); i++) {\n\t\tname = Cmd_Argv(i);\n\t\tif (IsRegexp(name)) {\n\t\t\tif(!ReSearchInit(name))\n\t\t\t\treturn;\n\t\t\tprev = NULL;\n\t\t\tfor (trig = re_triggers; trig; ) {\n\t\t\t\tif (ReSearchMatch (trig->name)) {\n\t\t\t\t\tnext_trig = trig->next;\n\t\t\t\t\tRemoveReTrigger(trig);\n\t\t\t\t\ttrig = next_trig;\n\t\t\t\t} else {\n\t\t\t\t\tprev = trig;\n\t\t\t\t\ttrig = trig->next;\n\t\t\t\t}\n\t\t\t}\n\t\t\tReSearchDone();\n\t\t} else {\n\t\t\tif ((trig = CL_FindReTrigger(name)))\n\t\t\t\tRemoveReTrigger(trig);\n\t\t}\n\t}\n}\n \nstatic void Trig_Enable(pcre_trigger_t *trig)\n{\n\ttrig->flags |= RE_ENABLED;\n}\n \nstatic void CL_RE_Trigger_Enable_f (void)\n{\n\tpcre_trigger_t *trig;\n\tchar *name;\n\tint i;\n \n\tfor (i = 1; i < Cmd_Argc(); i++) {\n\t\tname = Cmd_Argv (i);\n\t\tif (IsRegexp (name)) {\n\t\t\tif(!ReSearchInit (name))\n\t\t\t\treturn;\n\t\t\tTrig_ReSearch_do (Trig_Enable);\n\t\t\tReSearchDone ();\n\t\t} else {\n\t\t\tif ((trig = CL_FindReTrigger (name)))\n\t\t\t\tTrig_Enable (trig);\n\t\t}\n\t}\n}\n \nstatic void Trig_Disable (pcre_trigger_t *trig)\n{\n\ttrig->flags &= ~RE_ENABLED;\n}\n \nstatic void CL_RE_Trigger_Disable_f (void)\n{\n\tpcre_trigger_t *trig;\n\tchar *name;\n\tint i;\n \n\tfor (i = 1; i < Cmd_Argc (); i++) {\n\t\tname = Cmd_Argv (i);\n\t\tif (IsRegexp (name)) {\n\t\t\tif(!ReSearchInit (name))\n\t\t\t\treturn;\n\t\t\tTrig_ReSearch_do (Trig_Disable);\n\t\t\tReSearchDone ();\n\t\t} else {\n\t\t\tif ((trig = CL_FindReTrigger (name)))\n\t\t\t\tTrig_Disable (trig);\n\t\t}\n\t}\n}\n \nvoid CL_RE_Trigger_ResetLasttime (void)\n{\n\tpcre_trigger_t *trig;\n \n\tfor (trig=re_triggers; trig; trig=trig->next)\n\t\ttrig->lasttime = 0.0;\n}\n\nvoid Re_Trigger_Copy_Subpatterns (const char *s, size_t* offsets, int num, cvar_t *re_sub)\n{\n\tint i;\n\tchar *tmp;\n\tsize_t len;\n\n\tfor (i = 0; i < 2 * num; i += 2) {\n\t\tlen = offsets[i + 1] - offsets[i] + 1;\n\t\ttmp = (char *) Q_malloc(len);\n\t\tsnprintf (tmp, len, \"%s\", s + offsets[i]);\n\t\tCvar_ForceSet(&re_sub[i / 2], tmp);\n\t\tQ_free(tmp);\n\t}\n}\n \nstatic void CL_RE_Trigger_Match_f (void)\n{\n\tint c;\n\tchar *tr_name;\n\tchar *s;\n\tpcre_trigger_t *rt;\n\tchar *string;\n\tint result;\n\n\tc = Cmd_Argc();\n \n\tif (c != 3) {\n\t\tCom_Printf (\"re_trigger_match <trigger name> <string>\\n\");\n\t\treturn;\n\t}\n \n\ttr_name = Cmd_Argv(1);\n\ts = Cmd_Argv(2);\n \n\tfor (rt = re_triggers; rt; rt = rt->next)\n\t\tif (!strcmp(rt->name, tr_name)) {\n\t\t\tpcre2_match_data *match_data = pcre2_match_data_create_from_pattern(rt->regexp, NULL);\n\t\t\tresult = pcre2_match (rt->regexp, (PCRE2_SPTR)s, strlen(s), 0, 0, match_data, NULL);\n \n\t\t\tif (result >= 0) {\n\t\t\t\trt->lasttime = cls.realtime;\n\t\t\t\trt->counter++;\n\n\t\t\t\tPCRE2_SIZE *offsets = pcre2_get_ovector_pointer(match_data);\n\t\t\t\tRe_Trigger_Copy_Subpatterns (s, offsets, min (result,10), re_sub);\n\n\t\t\t\tif (!(rt->flags & RE_NOACTION)) {\n\t\t\t\t\tstring = Cmd_AliasString (rt->name);\n\t\t\t\t\tif (string) {\n\t\t\t\t\t\tCbuf_InsertTextEx (&cbuf_safe, \"\\nwait\\n\");\n\t\t\t\t\t\tCbuf_InsertTextEx (&cbuf_safe, string);\n\t\t\t\t\t\tCbuf_ExecuteEx (&cbuf_safe);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tCom_Printf (\"re_trigger \\\"%s\\\" has no matching alias\\n\", rt->name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tpcre2_match_data_free (match_data);\n\t\t\treturn;\n\t\t}\n\tCom_Printf (\"re_trigger \\\"%s\\\" not found\\n\", tr_name);\n}\n \nqbool allow_re_triggers;\nqbool CL_SearchForReTriggers (const char *s, unsigned trigger_type)\n{\n\tpcre_trigger_t *rt;\n\tpcre_internal_trigger_t *irt;\n\tcmd_alias_t *trig_alias;\n\tqbool removestr = false;\n\tint result;\n\tint len = strlen(s);\n\tpcre2_match_data *match_data;\n\tPCRE2_SIZE *offsets;\n \n\t// internal triggers - always enabled\n\tif (trigger_type < RE_PRINT_ECHO) {\n\t\tallow_re_triggers = true;\n\t\tfor (irt = internal_triggers; irt; irt = irt->next) {\n\t\t\tif (irt->flags & trigger_type) {\n\t\t\t\tmatch_data = pcre2_match_data_create_from_pattern(irt->regexp, NULL);\n\t\t\t\tresult = pcre2_match (irt->regexp, (PCRE2_SPTR)s, len, 0, 0, match_data, NULL);\n\t\t\t\tif (result >= 0) {\n\t\t\t\t\toffsets = pcre2_get_ovector_pointer(match_data);\n\t\t\t\t\tRe_Trigger_Copy_Subpatterns (s, offsets, min(result,10), re_subi);\n\t\t\t\t\tirt->func (s);\n\t\t\t\t}\n\t\t\t\tpcre2_match_data_free (match_data);\n\t\t\t}\n\t\t}\n\t\tif (!allow_re_triggers)\n\t\t\treturn false;\n\t}\n \n\t// message triggers disabled\n\tif (!tp_msgtriggers.value)\n\t\treturn false;\n \n\t// triggers banned by ruleset or FPD and we are a player\n\tif (((cl.fpd & FPD_NO_SOUNDTRIGGERS) || (cl.fpd & FPD_NO_TIMERS) ||\n\t        Rulesets_RestrictTriggers ()) && !cls.demoplayback && !cl.spectator)\n\t\treturn false;\n \n\t// we are in spec/demo mode, so play triggers if user want it\n\tif ((cls.demoplayback || cl.spectator) && cl_restrictions.value)\n\t\treturn false;\n \n\t// regexp triggers\n\tfor (rt = re_triggers; rt; rt = rt->next)\n\t\tif ( (rt->flags & RE_ENABLED) &&\t// enabled\n\t\t        (rt->flags & trigger_type) &&\t// mask fits\n\t\t        rt->regexp &&\t\t\t\t\t// regexp not empty\n\t\t        (rt->min_interval == 0.0 ||\n\t\t         cls.realtime >= rt->min_interval + rt->lasttime)) // not too fast.\n\t\t\t// TODO: disable it ^^^ for FPD_NO_TIMERS case.\n\t\t\t// probably it dont solve re_trigger timers problem\n\t\t\t// you always trigger on statusbar(TF) or wp_stats (KTPro/KTX) messages and get 0.5~1.5 accuracy for your timer\n\t\t{\n\t\t\tpcre2_match_data *match_data = pcre2_match_data_create_from_pattern(rt->regexp, NULL);\n\t\t\tresult = pcre2_match (rt->regexp, (PCRE2_SPTR)s, len, 0, 0, match_data, NULL);\n\t\t\tif (result >= 0) {\n\t\t\t\trt->lasttime = cls.realtime;\n\t\t\t\trt->counter++;\n\t\t\t\toffsets = pcre2_get_ovector_pointer(match_data);\n\t\t\t\tRe_Trigger_Copy_Subpatterns (s, offsets, min(result,10), re_sub);\n \n\t\t\t\tif (!(rt->flags & RE_NOACTION)) {\n\t\t\t\t\ttrig_alias = Cmd_FindAlias (rt->name);\n\t\t\t\t\tPrint_current++;\n\t\t\t\t\tif (trig_alias) {\n\t\t\t\t\t\tCbuf_InsertTextEx (&cbuf_safe, \"\\nwait\\n\");\n\t\t\t\t\t\tCbuf_InsertTextEx (&cbuf_safe, rt->name);\n\t\t\t\t\t\tCbuf_ExecuteEx (&cbuf_safe);\n\t\t\t\t\t} else\n\t\t\t\t\t\tCom_Printf (\"re_trigger \\\"%s\\\" has no matching alias\\n\", rt->name);\n\t\t\t\t\tPrint_current--;\n\t\t\t\t}\n \n\t\t\t\tif (rt->flags & RE_REMOVESTR)\n\t\t\t\t\tremovestr = true;\n\t\t\t\tif (rt->flags & RE_NOLOG)\n\t\t\t\t\tPrint_flags[Print_current] |= PR_LOG_SKIP;\n\t\t\t\tif (rt->flags & RE_FINAL)\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tpcre2_match_data_free (match_data);\n\t\t}\n \n\tif (removestr)\n\t\tPrint_flags[Print_current] |= PR_SKIP;\n \n\treturn removestr;\n}\n \n// Internal triggers\nstatic void AddInternalTrigger (char* regexpstr, unsigned mask, internal_trigger_func func)\n{\n\tpcre_internal_trigger_t *trig;\n\tint error;\n\tPCRE2_SIZE error_offset;\n \n\ttrig = (pcre_internal_trigger_t *) Q_malloc(sizeof(pcre_internal_trigger_t));\n\ttrig->next = internal_triggers;\n\tinternal_triggers = trig;\n \n\ttrig->regexp = pcre2_compile ((PCRE2_SPTR)regexpstr, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL);\n\ttrig->func = func;\n\ttrig->flags = mask;\n}\n \nstatic void INTRIG_Disable (const char *s)\n{\n\tallow_re_triggers = false;\n\tPrint_flags[Print_current] |= PR_LOG_SKIP;\n}\n \nstatic void INTRIG_Lastip_port (const char *s)\n{\n\t/* subpatterns of this regexp is maximum 21 chars */\n\t/* strlen (<3>.<3>.<3>.<3>:< 5 >) = 21 */\n\t/* or if it's matched as a string it can be up to 63 characters */\n \n\t// reset current lastip value\n\tmemset (lastip, 0, sizeof (lastip));\n \n\tif ( strlen(re_subi[1].string) <= 3 ) {\n\t\tsnprintf (lastip, sizeof (lastip), \"%d.%d.%d.%d:%d\",\n\t\t\t\t\t\tre_subi[1].integer,\n\t\t\t\t\t\tre_subi[2].integer,\n\t\t\t\t\t\tre_subi[3].integer,\n\t\t\t\t\t\tre_subi[4].integer,\n\t\t\t\t\t\tre_subi[5].integer);\n\t} else {\n\t\tsnprintf (lastip, sizeof (lastip), \"%s\", re_subi[1].string);\n\t}\n}\n \nstatic void InitInternalTriggers(void)\n{\n\t// dont allow cheating by triggering showloc command\n\tAddInternalTrigger(\"^(Location :|Angles   :)\", 4, INTRIG_Disable); // showloc command\n\t// dont allow cheating by triggering dispenser warning\n\tAddInternalTrigger(\"^Enemies are using your dispenser!$\", 16, INTRIG_Disable);\n\t// lastip\n\tAddInternalTrigger(\"([0-9]|[01]?\\\\d\\\\d|2[0-4]\\\\d|25[0-5])\\\\.([0-9]|[01]?\\\\d\\\\d|2[0-4]\\\\d|25[0-5])\\\\.([0-9]|[01]?\\\\d\\\\d|2[0-4]\\\\d|25[0-5])\\\\.([0-9]|[01]?\\\\d\\\\d|2[0-4]\\\\d|25[0-5])\\\\:(\\\\d{4,5})\", 8, INTRIG_Lastip_port);\n\t// lastip address, restricted to 64 bytes\n\tAddInternalTrigger(\"\\\\b([A-Za-z0-9-.]{1,53}?\\\\.[A-Za-z]{2,5}\\\\:\\\\d{4,5})\", 8, INTRIG_Lastip_port);\n}\n \nvoid TP_InitTriggers (void)\n{\n\tunsigned i;\n \n\tfor(i=0;i<10;i++)\n\t\tCvar_Register (re_sub+i);\n \n\tCmd_AddCommand (\"re_trigger\", CL_RE_Trigger_f);\n\tCmd_AddCommand (\"re_trigger_options\", CL_RE_Trigger_Options_f);\n\tCmd_AddCommand (\"re_trigger_delete\", CL_RE_Trigger_Delete_f);\n\tCmd_AddCommand (\"re_trigger_enable\", CL_RE_Trigger_Enable_f);\n\tCmd_AddCommand (\"re_trigger_disable\", CL_RE_Trigger_Disable_f);\n\tCmd_AddCommand (\"re_trigger_match\", CL_RE_Trigger_Match_f);\n\tInitInternalTriggers();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_COMMUNICATION);\n\tCvar_Register (&tp_msgtriggers);\n\tCvar_Register (&tp_soundtrigger);\n\tCvar_Register (&tp_triggers);\n\tCvar_Register (&tp_forceTriggers);\n\tCvar_ResetCurrentGroup();\n}\n\nvoid TP_ShutdownTriggers(void)\n{\n\tpcre_internal_trigger_t* trigger;\n\tpcre_internal_trigger_t* next_trigger;\n\n\tfor (trigger = internal_triggers; trigger; trigger = next_trigger) {\n\t\tnext_trigger = trigger->next;\n\t\tif (trigger->regexp) {\n\t\t\t(pcre2_code_free)(trigger->regexp);\n\t\t}\n\n\t\tQ_free(trigger);\n\t}\n}\n"
  },
  {
    "path": "src/tp_triggers.h",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n \nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n \nSee the GNU General Public License for more details.\n \nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: tp_triggers.h,v 1.2 2007-05-13 13:41:44 johnnycz Exp $\n*/\n\n#define\t\tRE_PRINT_LOW\t\t1\n#define\t\tRE_PRINT_NEDIUM\t\t2\n#define\t\tRE_PRINT_HIGH\t\t4\n#define\t\tRE_PRINT_CHAT\t\t8\n#define\t\tRE_PRINT_CENTER\t\t16\n#define\t\tRE_PRINT_ECHO\t\t32\n#define\t\tRE_PRINT_INTERNAL\t64\n#define\t\tRE_PRINT_ALL\t\t31 // all of the above except internal\n#define\t\tRE_FINAL\t\t\t256 // do not look for other triggers if matching is succesful\n#define\t\tRE_REMOVESTR\t\t512 // do not display string if matching is uccesful\n#define\t\tRE_NOLOG\t\t\t1024 // do not log string if matching is succesful\n#define\t\tRE_ENABLED\t\t\t2048 // trigger is enabled\n#define\t\tRE_NOACTION\t\t\t4096 // do not call alias\n\ntypedef struct pcre_trigger_s {\n\tchar\t\t\t\t\t*name;\n\tchar\t\t\t\t\t*regexpstr;\n\tstruct pcre_trigger_s*\tnext;\n\tpcre2_code*\t\t\t\tregexp;\n\tunsigned\t\t\t\tflags;\n\tfloat\t\t\t\t\tmin_interval;\n\tdouble\t\t\t\t\tlasttime;\n\tint\t\t\t\t\t\tcounter;\n} pcre_trigger_t;\n\ntypedef void internal_trigger_func (const char *s);\n\ntypedef struct pcre_internal_trigger_s {\n\tstruct pcre_internal_trigger_s\t*next;\n\tpcre2_code\t\t\t\t\t\t*regexp;\n\tinternal_trigger_func\t\t\t*func;\n\tunsigned\t\t\t\t\t\tflags;\n} pcre_internal_trigger_t;\n\n// re-triggers\nqbool CL_SearchForReTriggers (const char *s, unsigned trigger_type);\n// if true, string should not be displayed\npcre_trigger_t *CL_FindReTrigger (char *name);\nvoid CL_RE_Trigger_ResetLasttime (void);\n\n// message triggers\nvoid TP_SearchForMsgTriggers (const char *s, int level);\n"
  },
  {
    "path": "src/tr_types.h",
    "content": "/*\n===========================================================================\nCopyright (C) 1999-2005 Id Software, Inc.\n\nThis file is part of Quake III Arena source code.\n\nQuake III Arena source code is free software; you can redistribute it\nand/or modify it under the terms of the GNU General Public License as\npublished by the Free Software Foundation; either version 2 of the License,\nor (at your option) any later version.\n\nQuake III Arena source code is distributed in the hope that it will be\nuseful, but WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Foobar; if not, write to the Free Software\nFoundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n===========================================================================\n*/\n//\n#ifndef __TR_TYPES_H\n#define __TR_TYPES_H\n\n#ifdef X11_GAMMA_WORKAROUND\n#include <SDL.h>\n#include <SDL_syswm.h>\n#include <X11/extensions/xf86vmode.h>\n#endif\n/*\n** glconfig_t\n**\n** Contains variables specific to the OpenGL configuration\n** being run right now.  These are constant once the OpenGL\n** subsystem is initialized.\n*/\n\n// FIXME: Is this still relevant?\ntypedef enum {\n\tGLHW_GENERIC, // where everthing works the way it should\n\tGLHW_INTEL    // Causes flickering console if you write directly to the front \n\t              // buffer and then flip the back buffer for instance when drawing\n\t              // the I/O icon or doing timerefresh.\n\t              // http://www.intel.com/cd/ids/developer/asmo-na/eng/168252.htm?page=7\n} glHardwareType_t;\n\n#define R_SUPPORT_FRAMEBUFFERS        (1 << 0)      // rendering to framebuffers\n#define R_SUPPORT_RENDERING_SHADERS   (1 << 1)      // rendering using shaders\n#define R_SUPPORT_COMPUTE_SHADERS     (1 << 2)      // non-rendering shaders\n#define R_SUPPORT_PRIMITIVERESTART    (1 << 3)      // primitive restart indexes\n#define R_SUPPORT_MULTITEXTURING      (1 << 4)      // multi-texturing (some people still disable this...)\n#define R_SUPPORT_IMAGE_PROCESSING    (1 << 5)      // reading/writing to images\n#define R_SUPPORT_TEXTURE_SAMPLERS    (1 << 6)      // samplers\n#define R_SUPPORT_TEXTURE_ARRAYS      (1 << 7)      // 3D images (texture arrays)\n#define R_SUPPORT_INDIRECT_RENDERING  (1 << 8)      // indirect rendering (api parameters stored in buffer)\n#define R_SUPPORT_INSTANCED_RENDERING (1 << 9)      // instanced rendering\n#define R_SUPPORT_FRAMEBUFFERS_BLIT   (1 << 10)     // blit from one framebuffer to another\n#define R_SUPPORT_BGRA_LIGHTMAPS      (1 << 11)     // BGRA lightmaps (if optimal format)\n#define R_SUPPORT_INT8888R_LIGHTMAPS  (1 << 12)     // Lightmaps uploaded as UINT8888R rather than UNSIGNED_BYTE\n#define R_SUPPORT_SEAMLESS_CUBEMAPS   (1 << 13)     // filtering works across faces of the cubemap\n#define R_SUPPORT_DEPTH32F            (1 << 14)     // floating point 32-bit depth buffers\n#define R_SUPPORT_FRAMEBUFFERS_SRGB   (1 << 15)     // framebuffers support sRGB\n#define R_SUPPORT_IMMEDIATEMODE       (1 << 16)     // immediate-mode rendering (doesn't require programs)\n#define R_SUPPORT_FOG                 (1 << 17)     // fog (OpenGL 1.4+, not currently working)\n#define R_SUPPORT_CUBE_MAPS           (1 << 18)     // cube maps (OpenGL 1.3+)\n#define R_SUPPORT_FRAMEBUFFER_MS      (1 << 19)     // multi-sampled framebuffers\n\n#define R_SUPPORT_FEATURE_HW_LIGHTING (R_SUPPORT_TEXTURE_ARRAYS | R_SUPPORT_COMPUTE_SHADERS | R_SUPPORT_IMAGE_PROCESSING)\n\n#define R_SUPPORT_MODERN_OPENGL_REQUIREMENTS ( \\\n\tR_SUPPORT_FRAMEBUFFERS | R_SUPPORT_RENDERING_SHADERS | R_SUPPORT_PRIMITIVERESTART | \\\n\tR_SUPPORT_MULTITEXTURING | R_SUPPORT_TEXTURE_SAMPLERS | R_SUPPORT_TEXTURE_ARRAYS \\\n)\n\n#define R_BROKEN_GLBINDTEXTURES       (1 << 0)\n#define R_BROKEN_PREFERMULTIDRAW      (1 << 1)\n\ntypedef struct {\n\tconst unsigned char                     *renderer_string;\n\tconst unsigned char                     *vendor_string;\n\tconst unsigned char                     *version_string;\n\tconst unsigned char                     *glsl_version;\n\n\tint\t\tcolorBits, depthBits, stencilBits;\n\tint\t\tvidWidth, vidHeight;\n\tint\t\tdisplayFrequency;\n\tint     majorVersion;\n\tint     minorVersion;\n\tqbool   coreProfile;\n\tqbool   amd_issues;                    // github bug #416: avoid certain paths to workaround\n\n\tglHardwareType_t\t\t\thardwareType;\n\n\tqbool\t\t\t\t\tinitialized;\n#ifdef X11_GAMMA_WORKAROUND\n\tstruct {\n\t\tSDL_SysWMinfo info;\n\t\tDisplay *display;\n\t\tint screen;\n\t\tint size;\n\t} gammacrap;\n#endif\n\n\tint gl_max_size_default;\n\tint max_3d_texture_size;\n\tint max_texture_depth;\n\tint texture_units;\n\tint max_multisampling_level;\n\n\tint tripleBufferIndex;\n\tint uniformBufferOffsetAlignment;\n\tint shaderStorageBufferOffsetAlignment;\n\n\tunsigned int supported_features;\n\tunsigned int broken_features;\n\tunsigned int preferred_format;\n\tunsigned int preferred_type;\n\n\tqbool reversed_depth;\n} glconfig_t;\n\n#define GL_Supported(x) ((glConfig.supported_features & (x)) == (x))\n#define GL_WorkaroundNeeded(x) ((glConfig.broken_features & (x)) == (x))\n\nextern glconfig_t\tglConfig;\n\n//\n// latched variables that can only change over a restart\n//\n\nextern cvar_t\tr_colorbits;\nextern cvar_t\tr_24bit_depth;\nextern cvar_t\tr_fullscreen;\nextern cvar_t\tvid_width;\nextern cvar_t\tvid_height;\nextern cvar_t\tvid_win_width;\nextern cvar_t\tvid_win_height;\nextern cvar_t\tr_win_save_pos;\nextern cvar_t\tr_win_save_size;\nextern cvar_t\tr_displayRefresh;\nextern cvar_t\tvid_borderless;\nextern cvar_t   vid_usedesktopres;\n\n//\n// archived variables that can change at any time\n//\nextern cvar_t\tr_swapInterval;\nextern cvar_t\tvid_xpos;\nextern cvar_t\tvid_ypos;\nextern cvar_t\tvid_minpos;\nextern cvar_t\tr_conwidth;\nextern cvar_t\tr_conheight;\nextern cvar_t\tvid_ref;\nextern cvar_t\tvid_flashonactivity;\nextern cvar_t\tr_verbose;\nextern cvar_t r_showextensions;\n\n\n#endif\t// __TR_TYPES_H\n"
  },
  {
    "path": "src/utils.c",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: utils.c,v 1.47 2007-09-30 22:59:24 disconn3ct Exp $\n*/\n\n#include \"quakedef.h\"\n#include <pcre2.h>\n#include \"hud.h\"\n#include \"utils.h\"\n#include \"qtv.h\"\n#include \"teamplay.h\"\n#include \"q_shared.h\"\n\nint TP_CategorizeMessage (const char *s, int *offset);\n\n/************************************** General Utils **************************************/\n\nvoid str_align_right (char *target, size_t size, const char *source, size_t length)\n{\n\tif (length > size - 1)\n\t\tlength = size - 1;\n\n\tif (strlen(source) >= length) {\n\t\tstrlcpy(target, source, size);\n\t\ttarget[length] = 0;\n\t} else {\n\t\tint i;\n\n\t\tfor (i = 0; i < length - strlen(source); i++) {\n\t\t\ttarget[i] = ' ';\n\t\t}\n\n\t\tstrlcpy(target + i, source, size - i);\n\t}\n}\n\nchar *str_repeat (char *str, int amount)\n{\n\tchar *ret = NULL;\n\tint i = 0;\n\n\tif (str == NULL)\n\t\treturn NULL;\n\n\tif (amount <= 0)\n\t\tamount = 0;\n\n\tret = (char *) Q_calloc(strlen(str) * amount + 1, sizeof(char)); \n\n\tfor (i = 0; i < amount; i++)\n\t\tstrcat(ret, str);\n\n\treturn ret;\n}\n\nchar *CreateSpaces(int amount) {\n\tstatic char spaces[1024];\n\tint size;\n\n\tsize = bound(1, amount, sizeof(spaces) - 1);\n\tmemset(spaces, ' ', size);\n\tspaces[size] = 0;\n\n\treturn spaces;\n}\n\nchar *SecondsToMinutesString(int print_time) {\n\tstatic char time[128];\n\tint tens_minutes, minutes, tens_seconds, seconds;\n\n\ttens_minutes = fmod (print_time / 600, 6);\n\tminutes = fmod (print_time / 60, 10);\n\ttens_seconds = fmod (print_time / 10, 6);\n\tseconds = fmod (print_time, 10);\n\tsnprintf (time, sizeof(time), \"%i%i:%i%i\", tens_minutes, minutes, tens_seconds, seconds);\n\treturn time;\n}\n\nchar *SecondsToHourString(int print_time) {\n\tstatic char time[128];\n\tint tens_hours, hours,tens_minutes, minutes, tens_seconds, seconds;\n\n\ttens_hours = fmod (print_time / 36000, 10);\n\thours = fmod (print_time / 3600, 10);\n\ttens_minutes = fmod (print_time / 600, 6);\n\tminutes = fmod (print_time / 60, 10);\n\ttens_seconds = fmod (print_time / 10, 6);\n\tseconds = fmod (print_time, 10);\n\tsnprintf (time, sizeof(time), \"%i%i:%i%i:%i%i\", tens_hours, hours, tens_minutes, minutes, tens_seconds, seconds);\n\treturn time;\n}\n\n#define RGB_COLOR_RED\t\t\t\t\t\"255 0 0\"\n#define RGB_COLOR_GREEN\t\t\t\t\t\"0 255 0\"\n#define RGB_COLOR_BLUE\t\t\t\t\t\"0 0 255\"\n#define RGB_COLOR_BLACK\t\t\t\t\t\"0 0 0\"\n#define RGB_COLOR_WHITE\t\t\t\t\t\"255 255 255\"\n#define RGB_COLOR_YELLOW\t\t\t\t\"255 255 0\"\n#define RGB_COLOR_PINK\t\t\t\t\t\"255 0 255\"\n\n#define COLOR_CHECK(_colorname, _colorstring, _rgbstring) \\\n\tif(!strncmp(_colorstring, _colorname, sizeof(_colorstring))) return _rgbstring\n\nchar *ColorNameToRGBString(char *color_name)\n{\n\tCOLOR_CHECK(color_name, \"red\",\t\tRGB_COLOR_RED);\n\tCOLOR_CHECK(color_name, \"green\",\tRGB_COLOR_GREEN);\n\tCOLOR_CHECK(color_name, \"blue\",\t\tRGB_COLOR_BLUE);\n\tCOLOR_CHECK(color_name, \"black\",\tRGB_COLOR_BLACK);\n\tCOLOR_CHECK(color_name, \"yellow\",\tRGB_COLOR_YELLOW);\n\tCOLOR_CHECK(color_name, \"pink\",\t\tRGB_COLOR_PINK);\n\tCOLOR_CHECK(color_name, \"white\",\tRGB_COLOR_WHITE);\n\n\treturn color_name;\n}\n\n/// \\brief converts \"255 255 0 128\" to (255, 255, 0, 128) array and saves it to rgb output argument\n/// \\param[in] s string in \"R G B A\" format\n/// \\param[out] rgb array of 4 bytes\nint StringToRGB_W(char *s, byte *rgb)\n{\n\tint i;\n\tchar buf[20]; // \"255 255 255 255\" - the longest possible string\n\tchar *result;\n\n\trgb[0] = rgb[1] = rgb[2] = rgb[3] = 255;\n\n\tstrlcpy(buf, s, sizeof(buf));\n\tresult = strtok(buf, \" \");\n\n\tfor (i = 0; i < 4 && result; i++, result = strtok(NULL, \" \")) {\n\t\trgb[i] = (byte) Q_atoi(result);\n\t}\n\n\t// TODO: Ok to do this in software also?\n\t// Use normal quake pallete if not all arguments where given.\n\tif (i < 3)\n\t{\n\t\tbyte *col = (byte *) &d_8to24table[rgb[0]];\n\t\trgb[0] = col[0];\n\t\trgb[1] = col[1];\n\t\trgb[2] = col[2];\n\t}\n\n\treturn i;\n}\n\nvoid TrackerStringToRGB_W(const char *s, byte *rgb)\n{\n\trgb[0] = rgb[1] = rgb[2] = rgb[3] = 255;\n\n\tif (s[0] >= '0' && s[0] <= '9' && s[1] >= '0' && s[1] <= '9' && s[2] >= '0' && s[2] <= '9') {\n\t\trgb[0] = ((s[0] - '0') / 9.0f) * 255;\n\t\trgb[1] = ((s[1] - '0') / 9.0f) * 255;\n\t\trgb[2] = ((s[2] - '0') / 9.0f) * 255;\n\t}\n}\n\nbyte* StringToRGB(char *s)\n{\n\tstatic byte rgb[4];\n\tStringToRGB_W(s, rgb);\n\treturn rgb;\n}\n\nvoid RGBToString(const byte *rgb, char *s) {\n\t// Scale down each value from 0x0 to 0xff to the range of 0x0 to 0xf.\n\tbyte r = rgb[0] * 0xf / 0xff;\n\tbyte g = rgb[1] * 0xf / 0xff;\n\tbyte b = rgb[2] * 0xf / 0xff;\n\n\t// Convert the value to its hex representation. The characters will be\n\t// used in a \"&cRGB\" string.\n\ts[0] = (r < 10) ? ('0' + r) : ('a' + r - 0xa);\n\ts[1] = (g < 10) ? ('0' + g) : ('a' + g - 0xa);\n\ts[2] = (b < 10) ? ('0' + b) : ('a' + b - 0xa);\n}\n\n/*\n   float f[10];\n   int size = sizeof(f)/sizeof(f[0]);\n\n// this will fill \"f\" with succesfully parsed floats from first string parammeter\n// \"size\" will contain count of parsed floats\nParseFloats(\"1.0 2.0 999 5\", f, &size);\n*/\nint ParseFloats(char *s, float *f, int *f_size) {\n\tint i, argc;\n\ttokenizecontext_t ctx;\n\n\tif (!s || !f || !f_size) {\n\t\tSys_Error(\"ParseFloats() wrong params\");\n\t\treturn 0;\n\t}\n\n\tif (f_size[0] <= 0)\n\t\treturn (f_size[0] = 0); // array have no size, unusual but no crime\n\n\tCmd_TokenizeStringEx(&ctx, s);\n\n\targc = min(Cmd_ArgcEx(&ctx), f_size[0]);\n\n\tfor(i = 0; i < argc; i++)\n\t\tf[i] = Q_atof(Cmd_ArgvEx(&ctx, i));\n\n\tfor( ; i < f_size[0]; i++)\n\t\tf[i] = 0; // zeroing unused elements\n\n\treturn (f_size[0] = argc);\n}\n\nint strlen_color_by_terminator(const char *str, char terminator)\n{\n\tint len = 0;\n\n\tif ( !str )\n\t\treturn 0;\n\n\twhile ( str[0] && str[0] != terminator )\n\t{\n\t\tif (str[0] == '&')\n\t\t{\n\t\t\tif (str[1] == 'c' && HexToInt(str[2]) >= 0 && HexToInt(str[3]) >= 0 && HexToInt(str[4]) >= 0)\n\t\t\t{\n\t\t\t\tstr += 5; // skip \"&cRGB\"\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse if (str[1] == 'r')\n\t\t\t{\n\t\t\t\tstr += 2; // skip \"&r\"\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tlen++;\n\t\tstr++;\n\t}\n\n\treturn len;\n}\n\n// don't count ezquake color sequence\nint strlen_color(const char *str)\n{\n\treturn strlen_color_by_terminator(str, 0);\n}\n\n// skip ezquake color sequence\nvoid Util_SkipEZColors(char *dst, const char *src, size_t size)\n{\n\tif (!dst || !src) {\n\t\tSys_Error(\"Util_SkipColors: invalid input params\");\n\t\treturn;\n\t}\n\n\tif ( !size )\n\t\treturn; // no space\n\n\twhile ( src[0] && size )\n\t{\n\t\tif ( src[0] == '&' )\n\t\t{\n\t\t\tif ( src[1] == 'c' && HexToInt(src[2]) >= 0 && HexToInt(src[3]) >= 0 && HexToInt(src[4]) >= 0 )\n\t\t\t{\n\t\t\t\tsrc += 5; // skip \"&cRGB\"\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse if ( src[1] == 'r' )\n\t\t\t{\n\t\t\t\tsrc += 2; // skip \"&r\"\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t*dst++ = *src++;\n\t\tsize--;\n\t}\n\n\tif ( !size )\n\t\tdst--; // seems we truncate string, step back for null terminator\n\n\tdst[0] = 0;\n}\n\n// strips all @chars chars from the @src string\nvoid Util_SkipChars(const char *src, const char *chars, char *dst, size_t dstsize) {\n\tif (!dst || !src || !dstsize) {\n\t\tCom_Printf(\"Util_SkipChatWhite: illegal argument\\n\");\n\t\treturn;\n\t}\n\telse {\n\t\tchar *dstlast = dst + dstsize - 1;\n\t\tsize_t chars_len = strlen(chars);\n\t\tint i;\n\n\t\twhile (*src && dst < dstlast) {\n\t\t\tchar c = *src++;\n\t\t\tqbool skipped = false;\n\t\t\tfor (i = 0; i < chars_len; i++) {\n\t\t\t\tif (c == chars[i]) {\n\t\t\t\t\tskipped = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!skipped) {\n\t\t\t\t*dst++ = c;\n\t\t\t}\n\t\t}\n\t\t*dst = '\\0';\n\t}\n}\n\nchar *str_trim(char *str)\n{\n\tchar *start; // points at first non-whitespace\n\tchar *end; // points at last non-whitespace\n\tchar *pos = str, *orig_str = str;\n\n\twhile (*pos && isspace(*pos)) {\n\t\tpos++;\n\t}\n\n\tif (*pos == '\\0') {\n\t\t*str = '\\0';\n\t\treturn orig_str;\n\t}\n\n\tstart = pos;\n\tend = pos++;\n\n\twhile (*pos) {\n\t\tif (!isspace(*pos)) {\n\t\t\tend = pos;\n\t\t}\n\t\tpos++;\n\t}\n\n\tif (start != orig_str || (end + 1) != pos) {\n\t\tfor (pos = start; pos <= end; pos++) {\n\t\t\t*str++ = *pos;\n\t\t}\n\n\t\t*str = '\\0';\n\t}\n\n\treturn orig_str;\n}\n\nint HexToInt(char c)\n{\n\tif (isdigit(c))\n\t\treturn c - '0';\n\telse if (c >= 'a' && c <= 'f')\n\t\treturn 10 + c - 'a';\n\telse if (c >= 'A' && c <= 'F')\n\t\treturn 10 + c - 'A';\n\telse\n\t\treturn -1;\n}\n\n/************************************** Game Mode Utils *********************************/\n\nchar *get_ktx_mode (void)\n{\n\treturn Info_ValueForKey(cl.serverinfo, \"mode\");\n}\n\nqbool check_ktx_ca (void)\t// playing clan arena\n{\n\tchar *ktxmode = get_ktx_mode();\n\n\treturn (strstr(ktxmode, \"-ca\") != NULL);\n}\n\nqbool check_ktx_wo (void)\t// playing wipeout\n{\n\tchar *ktxmode = get_ktx_mode();\n\n\treturn (strstr(ktxmode, \"-wo\") != NULL);\n}\n\nqbool check_ktx_ca_wo (void) // playing clan arena or wipeout\n{\n\treturn (check_ktx_ca() || check_ktx_wo());\n}\n\n/************************************** File Utils **************************************/\n\nint Util_Extend_Filename(char *filename, char **ext) {\n\tchar extendedname[1024], **s;\n\tint i, offset;\n\tFILE *f;\n\n\tstrlcpy(extendedname, filename, sizeof(extendedname));\n\toffset = strlen(extendedname);\n\n\ti = -1;\n\twhile(1) {\n\t\tif (++i == 1000)\n\t\t\tbreak;\n\t\tfor (s = ext; *s; s++) { \n\t\t\tsnprintf (extendedname + offset, sizeof(extendedname) - offset, \"_%03i.%s\", i, *s);\n\t\t\tif ((f = fopen(extendedname, \"rb\"))) {\n\t\t\t\tfclose(f);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!*s)\n\t\t\treturn i;\n\t}\n\treturn -1;\n}\n\nvoid Util_Process_FilenameEx(char *string, qbool allow_root) {\n\tint i;\n\n\tif (!string)\n\t\treturn;\n\n\tfor (i = 0; i < strlen(string); i++)\n\t\tif (string[i] == '\\\\')\n\t\t\tstring[i] = '/';\n\n\tif (!allow_root && string[0] == '/')\n\t\tfor (i = 1; i <= strlen(string); i++)\n\t\t\tstring[i - 1] = string[i];\n}\n\nvoid Util_ToValidFileName(const char* i, char* o, size_t buffersize)\n{\n\tint k;\n\tunsigned char c;\n\tbuffersize--;\t// keep space for terminating zero\n\n\tfor (k = 0; *i && k < buffersize; i++, k++) {\n\t\tc = (*i) & 127;\n\t\tif (c < 32 || c == '?' || c == '*' || c == ':' || c == '<' || c == '>' || c == '\"')\n\t\t\to[k] = '_'; // u can't use skin with such chars, so replace with some safe char\n\t\telse\n\t\t\to[k] = c;\n\t}\n\to[k] = '\\0';\n}\n\nvoid Util_Process_Filename(char *string) {\n\tUtil_Process_FilenameEx(string, false);\n}\n\nqbool Util_Is_Valid_FilenameEx(char *s, qbool drive_prefix_valid) {\n\tstatic char badchars[] = {'?', '*', ':', '<', '>', '\"', '\\0'};\n\n\tif (!s || !*s)\n\t\treturn false;\n\n\t// this will allow things like this: c:/quake/<demo_dir>\n\t// probably windows specific only\n\tif ( drive_prefix_valid && (isupper(s[0]) || islower(s[0])) && s[1] == ':' )\n\t\ts += 2;\n\n\tif (strstr(s, \"../\") || strstr(s, \"..\\\\\") )\n\t\treturn false;\n\n\twhile (*s) {\n\t\tif (*s < 32 || *s >= 127 || strchr(badchars, *s))\n\t\t\treturn false;\n\t\ts++;\n\t}\n\treturn true;\n}\n\nqbool Util_Is_Valid_Filename(char *s)\n{\n\treturn  Util_Is_Valid_FilenameEx(s, false);\n}\n\nchar *Util_Invalid_Filename_Msg(char *s) {\n\tstatic char err[192];\n\n\tif (!s)\n\t\treturn NULL;\n\n\tsnprintf(err, sizeof(err), \"%s is not a valid filename (?*:<>\\\" are illegal characters)\\n\", s);\n\treturn err;\n}\n\n/************************************* Player Utils *************************************/\n\nstatic int Player_Compare (const void *p1, const void *p2) {\n\tplayer_info_t *player1, *player2;\n\tint team_comp;\n\n\tplayer1 = *((player_info_t **) p1);\n\tplayer2 = *((player_info_t **) p2);\n\n\tif (player1->spectator)\n\t\treturn player2->spectator ? (player1 - player2) : 1;\n\tif (player2->spectator)\n\t\treturn -1;\n\n\tif (cl.teamplay && (team_comp = strcmp(player1->team, player2->team)))\n\t\treturn team_comp;\n\n\treturn (player1 - player2);\n}\n\nint Player_NumtoSlot (int num) \n{\n\tint count, i;\n\tplayer_info_t *players[MAX_CLIENTS];\n\n\t// Get all the joined players (including spectators).\n\tfor (count = i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tif (cl.players[i].name[0])\n\t\t{\n\t\t\tplayers[count++] = &cl.players[i];\n\t\t}\n\t}\n\n\t// Sort them according to team and if they're a spectator.\n\tqsort(players, count, sizeof(player_info_t *), Player_Compare);\n\n\tif (num < 1 || num > count)\n\t{\n\t\treturn PLAYER_NUM_NOMATCH;\n\t}\n\n\t// Find the ith player.\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tif (&cl.players[i] == players[num-1])\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\treturn PLAYER_NUM_NOMATCH;\n}\n\nint Player_IdtoSlot (int id) {\n\tint j;\n\n\tfor (j = 0; j < MAX_CLIENTS; j++) {\n\t\tif (cl.players[j].name[0] && cl.players[j].userid == id)\n\t\t\treturn j;\n\t}\n\treturn -1;\n}\n\nchar *Player_StripNameColor(const char *name)\n{\t\n\textern char readableChars[]; // console.c\n\tchar *stripped = NULL;\n\tint i, name_length;\n\tname_length = strlen(name);\n\n\tstripped = (char *)Q_calloc(name_length + 1, sizeof(char));\n\n\t// Strip the color by setting bit 7 = 0 on all letters.\n\tfor (i = 0; i < name_length; i++)\n\t{\n\t\tstripped[i] = readableChars[(unsigned char)name[i]] & 127;\n\t}\n\n\tstripped[i] = '\\0';\n\n\treturn stripped;\n}\n\nint Player_IdStringToSlot(const char* arg)\n{\n\tint i;\n\n\t// Check if the argument is a user id instead\n\t// Make sure all chars in the given arg are digits in that case.\n\tfor (i = 0; arg[i]; i++)\n\t{\n\t\tif (!isdigit((byte)arg[i])) {\n\t\t\treturn PLAYER_NAME_NOMATCH;\n\t\t}\n\t}\n\n\t// Get player ID.\n\treturn Player_IdtoSlot(Q_atoi(arg));\n}\n\nint Player_StringtoSlot(char *arg, qbool use_regular_expression, qbool prioritise_user_id)\n{\n\tint i, slot, arg_length;\n\n\tif (!arg[0]) {\n\t\treturn PLAYER_NAME_NOMATCH;\n\t}\n\n\tif (prioritise_user_id) {\n\t\tslot = Player_IdStringToSlot(arg);\n\n\t\tif (slot >= 0) {\n\t\t\treturn slot;\n\t\t}\n\t}\n\n\t// Match on partial names by only comparing the\n\t// same amount of chars as in the given argument\n\t// with all the player names. The first match will be picked.\n\targ_length = strlen(arg) + 1;\n\n\t// Try finding the player by comparing the argument to all the player names (CASE SENSITIVE)\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tif (cl.players[i].name[0] && !strncmp(arg, cl.players[i].name, arg_length))\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\t// Maybe the user didn't use the correct case, so try without CASE SENSITIVITY.\n\t// (We loop once more so that if the correct case is given, that match will get precedence).\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tif (cl.players[i].name[0] && !strncasecmp(arg, cl.players[i].name, arg_length))\n\t\t{\n\t\t\treturn i;\n\t\t}\n\t}\n\n\t// If the players name is color coded it's hard to type. \n\t// So strip the color codes and see if it's a match.\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tchar *stripped = Player_StripNameColor(cl.players[i].name);\n\n\t\tif (cl.players[i].name[0] && !strncasecmp(arg, stripped, arg_length))\n\t\t{\n\t\t\tQ_free(stripped);\n\t\t\treturn i;\n\t\t}\n\n\t\tQ_free(stripped);\n\t}\n\n\tif (use_regular_expression) {\n\t\t// Regexp match against stripped player name if previous attempts have failed\n\t\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\t\tchar *stripped = Player_StripNameColor(cl.players[i].name);\n\n\t\t\tif (cl.players[i].name[0] && Utils_RegExpMatch(arg, stripped)) {\n\t\t\t\tQ_free(stripped);\n\t\t\t\treturn i;\n\t\t\t}\n\n\t\t\tQ_free(stripped);\n\t\t}\n\t}\n\n\t// Regexp match against stripped player name if previous attempts have failed\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tchar *stripped = Player_StripNameColor(cl.players[i].name);\n\n\t\tif (cl.players[i].name[0] && Utils_RegExpMatch(arg, stripped))\n\t\t{\n\t\t\tQ_free(stripped);\n\t\t\treturn i;\n\t\t}\n\n\t\tQ_free(stripped);\n\t}\n\n\tslot = Player_IdStringToSlot(arg);\n\treturn (slot >= 0) ? slot : PLAYER_ID_NOMATCH;\n}\n\nint Player_NametoSlot(char *name) {\n\tint i;\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (cl.players[i].name[0] && !strncmp(Info_ValueForKey(cl.players[i].userinfo, \"name\"), name, 31))\n\t\t\treturn i;\n\t}\n\treturn PLAYER_NAME_NOMATCH;\n}\n\nint Player_SlottoId (int slot) {\t\n\treturn (slot >= 0 && slot < MAX_CLIENTS && cl.players[slot].name[0]) ? cl.players[slot].userid : -1;\n}\n\nchar *Player_MyName (void) {\n\treturn Info_ValueForKey(cls.demoplayback ? cls.userinfo : cl.players[cl.playernum].userinfo, \"name\");\n}\n\nint Player_GetTrackId(int player_id)\n{\n\tint i, count;\n\tplayer_info_t *players[MAX_CLIENTS];\n\n\t// Get all the joined players (including spectators).\n\tfor (count = i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tif (cl.players[i].name[0])\n\t\t{\n\t\t\tplayers[count++] = &cl.players[i];\n\t\t}\n\t}\n\n\t// Sort them according to team and if they're a spectator.\n\tqsort(players, count, sizeof(player_info_t *), Player_Compare);\n\n\t// Find track_id of player.\n\tfor (i = 0; i < count; i++)\n\t{\n\t\tif (player_id == players[i]->userid)\n\t\t{\n\t\t\treturn i+1;\n\t\t}\n\t}\n\treturn -1;\n}\n\n\nint Player_GetSlot(char *arg, qbool prioritise_user_id)\n{\n\tint response;\n\n\t// Try getting the slot by name or id.\n\tif ((response = Player_StringtoSlot(arg, false, prioritise_user_id)) >= 0 )\n\t\t//|| response == PLAYER_ID_NOMATCH)\n\t{\n\t\treturn response;\n\t}\n\n\t// We didn't find any player or ID that matched\n\t// so we'll try treating it as the players\n\t// sorted position.\n\tif (arg[0] != '#')\n\t{\n\t\treturn response;\n\t}\n\n\tif ((response = Player_NumtoSlot(Q_atoi(arg + 1))) >= 0)\n\t{\n\t\treturn response;\n\t}\n\n\treturn PLAYER_NUM_NOMATCH;\n}\n\n/********************************** Nick completion related ****************************************/\n\nconst char disallowed_in_nick[] = {'\\n', '\\f', '\\\\', '/', '\\\"', ' ' , ';', '\\0'};\n\n// yet another utility, there also exist at least one similar function Player_StripNameColor(), but not the same\nvoid RemoveColors (char *name, size_t len)\n{\n\textern char readableChars[];\n\tchar *s = name;\n\n\tif (!s || !*s)\n\t\treturn;\n\n\twhile (*s)\n\t{\n\t\t*s = readableChars[(unsigned char)*s] & 127;\n\n\t\tif (strchr (disallowed_in_nick, *s))\n\t\t\t*s = '_';\n\n\t\ts++;\n\t}\n\n\t// get rid of whitespace\n\ts = name;\n\tfor (s = name; *s == '_'; s++) ;\n\tmemmove (name, s, strlen(s) + 1);\n\n\tfor (s = name + strlen(name); s > name  &&  (*(s - 1) == '_'); s--)\n\t\t; // empty\n\n\t*s = 0;\n\n\tif (!name[0])\n\t\tstrlcpy (name, \"_\", len);\n}\n\nqbool FindBestNick (const char *nick, int flags, char *result, size_t result_len)\n{\n\tint i, bestplayer = -1, best = 999999;\n\tchar name[MAX_SCOREBOARDNAME], *match;\n\n\tresult[0] = 0;\n\n\tfor (i = 0; i < MAX_CLIENTS; i++)\n\t{\n\t\tif (flags & FBN_IGNORE_SPECS)\n\t\t\tif (cl.players[i].spectator)\n\t\t\t\tcontinue;\n\t\tif (flags & FBN_IGNORE_PLAYERS)\n\t\t\tif (!cl.players[i].spectator)\n\t\t\t\tcontinue;\n\n\t\tif (!cl.players[i].name[0])\n\t\t\tcontinue;\n\n\t\tstrlcpy(name, cl.players[i].name, sizeof(name));\n\t\tRemoveColors (name, sizeof (name));\n\t\tfor (match = name; match[0]; match++)\n\t\t\tmatch[0] = tolower(match[0]);\n\n\t\tif (!name[0])\n\t\t\tcontinue;\n\n\t\tif ((match = strstr(name, nick))  && match - name < best)\n\t\t{\n\t\t\tbest = match - name;\n\t\t\tbestplayer = i;\n\t\t}\n\t}\n\n\tif (bestplayer != -1)\n\t{\n\t\tstrlcpy(result, cl.players[bestplayer].name, result_len);\n\t\treturn true;\n\t}\n\n\tif (flags & FBN_IGNORE_QTVSPECS)\n\t\treturn false;\n\n\treturn QTV_FindBestNick (nick, result, result_len);\n}\n\n\n/********************************** Clipboard ****************************************/\n\n#ifndef _WIN32\n#define CLIPBOARDSIZE 1024\nstatic char clipboard[CLIPBOARDSIZE] = \"\\0\";    // for clipboard implementation\n#endif\n// copies given text to clipboard\nvoid CopyToClipboard(const char *text)\n{\n#ifdef _WIN32\n\tif (OpenClipboard(NULL))\n\t{\n\t\tLPTSTR  lptstrCopy;\n\t\tHGLOBAL hglbCopy;\n\n\t\tEmptyClipboard();\n\t\thglbCopy = GlobalAlloc(GMEM_DDESHARE, strlen(text)+1);\n\t\tlptstrCopy = GlobalLock(hglbCopy);\n\t\tstrcpy((char *)lptstrCopy, text);\n\t\tGlobalUnlock(hglbCopy);\n\t\tSetClipboardData(CF_TEXT, hglbCopy);\n\n\t\tCloseClipboard();\n\t}\n#else\n\tstrlcpy (clipboard, text, CLIPBOARDSIZE);\n\tSys_CopyToClipboard(text);\n#endif\n}\n\n// reads from clipboard\nchar *ReadFromClipboard(void)\n{\n#ifdef _WIN32\n\tstatic char clipbuf[1024];\n\tint     i;\n\tHANDLE  th;\n\tchar    *clipText;\n\n\tclipbuf[0] = 0;\n\n\tif (OpenClipboard(NULL))\n\t{\n\t\tth = GetClipboardData(CF_TEXT);\n\t\tif (th)\n\t\t{\n\t\t\tclipText = GlobalLock(th);\n\t\t\tif (clipText)\n\t\t\t{\n\t\t\t\tstrlcpy(clipbuf, clipText, sizeof (clipbuf));\n\t\t\t\tfor (i=0; i < strlen(clipbuf); i++)\n\t\t\t\t\tif (clipbuf[i]=='\\n' || clipbuf[i]=='\\t' || clipbuf[i]=='\\b')\n\t\t\t\t\t\tclipbuf[i] = ' ';\n\t\t\t}\n\t\t\tGlobalUnlock(th);\n\t\t}\n\t\tCloseClipboard();\n\t}\n\treturn clipbuf;\n#else\n\treturn clipboard;\n#endif\n}\n\n\n/********************************** String Utils ****************************************/\n\nqbool Util_F_Match (const char *_msg, char *f_request) {\n\tint offset, i, status, flags;\n\tchar *s, *msg;\n\n\tmsg = Q_strdup(_msg);\n\tflags = TP_CategorizeMessage(msg, &offset);\n\n\tif (flags != msgtype_normal && flags != msgtype_spec) {\n\t\tQ_free(msg);\n\t\treturn false;\n\t}\n\n\tfor (i = 0, s = msg + offset; i < strlen(s); i++)\n\t\ts[i] = s[i] & ~128;\t\t\n\n\tif (strstr(s, f_request) != s) {\n\t\tQ_free(msg);\n\t\treturn false;\n\t}\n\tstatus = 0;\n\tfor (s += strlen(f_request); *s; s++) {\n\t\tif (isdigit(*s) && status <= 1) {\n\t\t\tstatus = 1;\n\t\t} else if (isspace(*s)) {\n\t\t\tstatus = (status == 1) ? 2 : status;\n\t\t} else {\n\t\t\tQ_free(msg);\t\t\t\n\t\t\treturn false;\n\t\t}\n\t}\n\tQ_free(msg);\n\treturn true;\n}\n\n\nvoid Replace_In_String (char *src, int n, char delim, int num_args, ...)\n{\n\tva_list ap;\n\tchar msg[1024];\n\tchar buf[256];\n\tchar *msgp;\n\tint i, pad;\n\tqbool right;\n\tchar *arg1, *arg2;\n\n\t// we will write the result back to src in code that follows\n\tstrlcpy(msg,src,sizeof(msg));\n\tmsgp = msg;\n\t*src = '\\0';\n\n\twhile (*msgp)\n\t{\n\t\tif(*msgp != delim)\n\t\t{\n\t\t\tbuf[0] = *msgp++;\n\t\t\tbuf[1] = '\\0';\n\t\t\tstrlcat(src, buf, n);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// process the delimiter\n\t\t\tmsgp++;\n\t\t\tif (!*msgp) break;\n\n\t\t\t// process the initial minus\n\t\t\tright = *msgp == '-';\n\t\t\tif (right) msgp++;\n\t\t\tif (!*msgp) break;\n\n\t\t\t// process the number\n\t\t\tpad = atoi(msgp);\n\t\t\twhile (isdigit(*msgp)) msgp++;\n\t\t\tif (!*msgp) break;\n\n\t\t\t// go through all available patterns\n\t\t\tva_start(ap, num_args);\n\t\t\tfor (i=0; i < num_args; i++)\n\t\t\t{\n\t\t\t\targ1 = va_arg(ap,char *);   // the pattern\n\t\t\t\tif (!arg1) break;\n\n\t\t\t\targ2 = va_arg(ap,char *);   // the string to replace it with\n\t\t\t\tif (!arg2) break;\n\n\t\t\t\t// the pattern matched\n\t\t\t\tif (*msgp == *arg1)\n\t\t\t\t{\n\t\t\t\t\tif (pad)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (right)  snprintf(buf, sizeof(buf)-1, \"%-*s\", pad, arg2);\n\t\t\t\t\t\telse        snprintf(buf, sizeof(buf)-1, \"%*s\", pad, arg2);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tstrlcpy(buf, arg2, sizeof(buf));\n\t\t\t\t\t}\n\n\t\t\t\t\tstrlcat(src, buf, n);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tva_end(ap);\n\n\t\t\tmsgp++;\n\t\t}\n\t}\t\n}\n\n// compares two fun strings\nint funcmp(const char *s1, const char *s2)\n{\n\tchar *t1, *t2;\n\tint ret;\n\n\tif (s1 == NULL  &&  s2 == NULL)\n\t\treturn 0;\n\n\tif (s1 == NULL)\n\t\treturn -1;\n\n\tif (s2 == NULL)\n\t\treturn 1;\n\n\tt1 = Q_strdup(s1);\n\tt2 = Q_strdup(s2);\n\n\tFunToSort(t1);\n\tFunToSort(t2);\n\n\tret = strcmp(t1, t2);\n\n\tQ_free(t1);\n\tQ_free(t2);\n\n\treturn ret;\n}\n\nvoid FunToSort(char *text)\n{\n\tchar *tmp;\n\tchar *s, *d;\n\tunsigned char c;\n\ttmp = (char *)Q_malloc(strlen(text) + 1);\n\n\ts = text;\n\td = tmp;\n\n\twhile ((c = (unsigned char)(*s++)) != 0) {\n\t\tif (c >= 18  &&  c <= 27)\n\t\t\tc += 30;\n\t\telse if (c >= 146  &&  c <= 155)\n\t\t\tc -= 98; // Yellow numbers.\n\t\telse if (c >= 32  &&  c <= 126)\n\t\t\tc = tolower(c);\n\t\telse if (c >= 160  &&  c <= 254)\n\t\t\tc  = tolower(c-128);\n\t\telse {\n\t\t\tswitch (c) {\n\t\t\t\tcase 1:\n\t\t\t\tcase 2:\n\t\t\t\tcase 3:\n\t\t\t\tcase 4:\n\t\t\t\tcase 6:\n\t\t\t\tcase 7:\n\t\t\t\tcase 8:\n\t\t\t\tcase 9:\n\t\t\t\tcase 132:   // kwadrat\n\t\t\t\t\tc = 210; break;\n\t\t\t\tcase 5:\n\t\t\t\tcase 14:\n\t\t\t\tcase 15:\n\t\t\t\tcase 28:\n\t\t\t\tcase 133:\n\t\t\t\tcase 142:\n\t\t\t\tcase 143:\n\t\t\t\tcase 156:   // dot\n\t\t\t\t\tc = 201; break;\n\t\t\t\tcase 29:\n\t\t\t\tcase 157:   // <\n\t\t\t\t\tc = 202; break;\n\t\t\t\tcase 30:\n\t\t\t\tcase 158:   // -\n\t\t\t\t\tc = 203; break;\n\t\t\t\tcase 31:\n\t\t\t\tcase 159:   // >\n\t\t\t\t\tc = 204; break;\n\t\t\t\tcase 128:   // '('\n\t\t\t\t\tc = 205; break;\n\t\t\t\tcase 129:   // '='\n\t\t\t\t\tc = 206; break;\n\t\t\t\tcase 130:   // ')'\n\t\t\t\t\tc = 207; break;\n\t\t\t\tcase 131:   // '+'\n\t\t\t\t\tc = 208; break;\n\t\t\t\tcase 127:\n\t\t\t\tcase 255:   // <-\n\t\t\t\t\tc = 209; break;\n\t\t\t\tcase 134:   // d1\n\t\t\t\t\tc = 211; break;\n\t\t\t\tcase 135:   // d2\n\t\t\t\t\tc = 212; break;\n\t\t\t\tcase 136:   // d3\n\t\t\t\t\tc = 213; break;\n\t\t\t\tcase 137:   // d4\n\t\t\t\t\tc = 214; break;\n\t\t\t\tcase 16:\n\t\t\t\tcase 144:   // '['\n\t\t\t\t\tc = '['; break;\n\t\t\t\tcase 17:\n\t\t\t\tcase 145:   // ']'\n\t\t\t\t\tc = ']'; break;\n\t\t\t\tcase 141:   // '>'\n\t\t\t\t\tc = 200; break;\n\t\t\t\tcase 10:\n\t\t\t\tcase 11:\n\t\t\t\tcase 12:\n\t\t\t\tcase 13:\n\t\t\t\tcase 138:\n\t\t\t\tcase 139:\n\t\t\t\tcase 140:   // ' '\n\t\t\t\t\tc = ' '; break;\n\t\t\t}\n\t\t}\n\n\t\t*d++ = c;\n\t}\n\t*d = 0;\n\n\tstrcpy(text, tmp);\n\tQ_free(tmp);\n}\n\n#ifdef UNUSED_CODE\n// todo: these functions are unused and there might already exist\n// equivalent functions in our project; in such case remove them\nunsigned char CharToBrown(unsigned char ch)\n{\n\tif ( ch > 32 && ch <= 127 )\n\t\treturn ch + 128;\n\telse\n\t\treturn ch;\n}\n\nunsigned char CharToWhite(unsigned char ch)\n{\n\tif ( ch > 160 )\n\t\treturn ch - 128;\n\telse\n\t\treturn ch;\n}\n#endif\n\nvoid CharsToBrown(char* start, char* end)\n{\n\tchar *p = start;\n\n\twhile (p < end) {\n\t\tif ( *p > 32 && *p <= 127 )\n\t\t\t*p += 128;\n\t\tp++;\n\t}\n}\n\nchar *CharsToBrownStatic(char *in) // Print brown characters to console\n{\n\tstatic char string[8][1024];\n\tstatic int idx = 0;\n\tint write_pos = 0;\n\n\tidx++;\n\tif (idx == 8)\n\t\tidx = 0;\n\n\tfor (; *in && write_pos < 1023; in++) {\n\t\tstring[idx][write_pos++] = 128 + *in;\n\t}\n\tstring[idx][write_pos] = '\\0';\n\n\treturn string[idx];\n}\n\nvoid CharsToWhite(char* start, char* end)\n{\n\tunsigned char *p = (unsigned char *)start;\n\n\twhile (p < (unsigned char*)end) {\n\t\tif ( *p > 160 )\n\t\t\t*p -= 128;\n\t\tp++;\n\t}\n}\n\n/********************************** TF Utils ****************************************/\n\nstatic char *Utils_TF_ColorToTeam_Failsafe(int color) {\n\tint i, j, teamcounts[8], numteamsseen = 0, best = -1;\n\tchar *teams[MAX_CLIENTS];\n\n\tmemset(teams, 0, sizeof(teams));\n\tmemset(teamcounts, 0, sizeof(teamcounts));\n\n\tfor (i = 0; i < MAX_CLIENTS; i++) {\n\t\tif (!cl.players[i].name[0] || cl.players[i].spectator)\n\t\t\tcontinue;\n\t\tif (cl.players[i].real_bottomcolor != color)\n\t\t\tcontinue;\n\t\tfor (j = 0; j < numteamsseen; j++) {\n\t\t\tif (!strcmp(cl.players[i].team, teams[j]))\n\t\t\t\tbreak;\n\t\t}\n\t\tif (j == numteamsseen) {\n\t\t\tteams[numteamsseen] = cl.players[i].team;\n\t\t\tteamcounts[numteamsseen] = 1;\n\t\t\tnumteamsseen++;\n\t\t} else {\n\t\t\tteamcounts[j]++;\n\t\t}\n\t}\n\tfor (i = 0; i < numteamsseen; i++) {\n\t\tif (best == -1 || teamcounts[i] > teamcounts[best])\n\t\t\tbest = i;\n\t}\n\treturn (best == -1) ? \"\" : teams[best];\n}\n\nchar *Utils_TF_ColorToTeam(int color) {\n\tswitch (color) {\n\t\tcase 13:\n\t\t\treturn cl.fixed_team_names[0];\n\t\tcase 4:\n\t\t\treturn cl.fixed_team_names[1];\n\t\tcase 12:\n\t\t\treturn cl.fixed_team_names[2];\n\t\tcase 11:\n\t\t\treturn cl.fixed_team_names[3];\n\t\tdefault:\n\t\t\treturn \"\";\n\t}\n\treturn Utils_TF_ColorToTeam_Failsafe(color);\n}\n\nint Utils_TF_TeamToColor(char *team) {\n\tif (!strcasecmp(team, Utils_TF_ColorToTeam(13)))\n\t\treturn 13;\n\tif (!strcasecmp(team, Utils_TF_ColorToTeam(4)))\n\t\treturn 4;\n\tif (!strcasecmp(team, Utils_TF_ColorToTeam(12)))\n\t\treturn 12;\n\tif (!strcasecmp(team, Utils_TF_ColorToTeam(11)))\n\t\treturn 11;\n\treturn 0;\n}\n\n/********************************** REGEXP ****************************************/\n\nqbool Utils_RegExpMatch(char *regexp, char *matchstring)\n{\n\tpcre2_code *re = NULL;\n\tint error;\n\tPCRE2_SIZE error_offset = 0;\n\tint match = 0;\n\tpcre2_match_data *match_data = NULL;\n\n\tre = pcre2_compile(\n\t\t\t(PCRE2_SPTR)regexp,\t// The pattern.\n\t\t\tPCRE2_ZERO_TERMINATED,\n\t\t\tPCRE2_CASELESS,\t\t// Case insensitive.\n\t\t\t&error,\t\t\t\t// Error message.\n\t\t\t&error_offset,\t\t// Error offset.\n\t\t\tNULL);\t\t\t\t// use default character tables.\n\n\t// Check for an error compiling the regexp.\n\tif (!re)\n\t\treturn false;\n\n\t// Check if we have a match.\n\tmatch_data = pcre2_match_data_create_from_pattern(re, NULL);\n\tif (re && (match = pcre2_match(re, (PCRE2_SPTR)matchstring, strlen(matchstring), 0, 0, match_data, NULL)) >= 0) {\n\t\tpcre2_match_data_free(match_data);\n\t\tpcre2_code_free(re);\n\n\t\treturn true;\n\t}\n\n\t// Make sure we clean up.\n\tif (re) {\n\t\tpcre2_match_data_free(match_data);\n\t\tpcre2_code_free(re);\n\t}\n\treturn false;\n}\n\nqbool Utils_RegExpGetGroup(char *regexp, char *matchstring, const char **resultstring, int *resultlength, int group)\n{\n\tpcre2_code *re = NULL;\n\tint error;\n\tPCRE2_SIZE error_offset = 0;\n\tint match = 0;\n\tpcre2_match_data *match_data;\n\n\tre = pcre2_compile(\n\t\t\t(PCRE2_SPTR)regexp,\t// The pattern.\n\t\t\tPCRE2_ZERO_TERMINATED,\n\t\t\tPCRE2_CASELESS,\t\t// Case insensitive.\n\t\t\t&error,\t\t\t\t// Error message.\n\t\t\t&error_offset,\t\t// Error offset.\n\t\t\tNULL);\t\t\t\t// use default character tables.\n\n\tif (!re)\n\t\treturn false;\n\n\tmatch_data = pcre2_match_data_create_from_pattern(re, NULL);\n\tif (re && (match = pcre2_match(re, (PCRE2_SPTR)matchstring, strlen(matchstring), 0, 0, match_data, NULL)) >= 0) {\n\t\tint substring_length = 0;\n\t\terror = pcre2_substring_get_bynumber (match_data, group, (PCRE2_UCHAR8**)resultstring, (size_t*)&substring_length);\n\n\t\tif (resultlength != NULL) {\n\t\t\t(*resultlength) = substring_length;\n\t\t}\n\n\t\tpcre2_match_data_free (match_data);\n\t\tif (re) {\n\t\t\tpcre2_code_free(re);\n\t\t}\n\n\t\treturn (error != PCRE2_ERROR_NOSUBSTRING && error != PCRE2_ERROR_NOMEMORY);\n\t}\n\n\tpcre2_match_data_free (match_data);\n\tif (re) {\n\t\tpcre2_code_free(re);\n\t}\n\n\treturn false;\n}\n\n\n// QW262 -->\n// regexp match support for group operations in scripts\nint\t\t\twildcard_level = 0;\npcre2_code\t*wildcard_re[4];\n\nqbool IsRegexp(const char *str)\n{\n\tif (*str == '+' || *str == '-') {\n\t\t// +/- aliases; valid regexp can not start with +/-\n\t\treturn false;\n\t}\n\n\treturn (strcspn(str, \"\\\\\\\"()[]{}.*+?^$|\")) != strlen(str) ? true : false;\n}\n\nqbool ReSearchInit(const char* wildcard)\n{\n\treturn ReSearchInitEx(wildcard, true);\n}\n\nqbool ReSearchInitEx(const char *wildcard, qbool case_sensitive)\n{\n\tint error;\n\tPCRE2_SIZE error_offset;\n\n\tif (wildcard_level == 4) {\n\t\tCom_Printf(\"Error: Regexp commands nested too deep\\n\");\n\t\treturn false;\n\t}\n\twildcard_re[wildcard_level] = pcre2_compile((PCRE2_SPTR)wildcard, PCRE2_ZERO_TERMINATED, (case_sensitive ? 0 : PCRE2_CASELESS), &error, &error_offset, NULL);\n\tif (!wildcard_re[wildcard_level]) {\n\t\tPCRE2_UCHAR error_str[256];\n\t\tpcre2_get_error_message(error, error_str, sizeof(error_str));\n\t\tCom_Printf (\"Invalid regexp: %s %d\\n\", error_str, error_offset);\n\t\treturn false;\n\t}\n\n\n\twildcard_level++;\n\treturn true;\n}\n\nqbool ReSearchMatch (const char *str)\n{\n\tint result;\n\tpcre2_match_data *match_data = NULL;\n\tmatch_data = pcre2_match_data_create_from_pattern(wildcard_re[wildcard_level-1], NULL);\n\tresult = pcre2_match(wildcard_re[wildcard_level-1],\n\t\t\t(PCRE2_SPTR)str, strlen(str), 0, 0, match_data, NULL);\n\tpcre2_match_data_free(match_data);\n\treturn (result>0) ? true : false;\n}\n\nvoid ReSearchDone (void)\n{\n\twildcard_level--;\n\tif (wildcard_re[wildcard_level]) {\n\t\t(pcre2_code_free)(wildcard_re[wildcard_level]);\n\t}\n}\n// <-- QW262\n\n\n// ***************** VC issues **********************************\n\n#if (_MSC_VER && (_MSC_VER < 1400))\nextern _ftol2(double f);\nlong _ftol2_sse(double f)\n{\n\treturn _ftol2(f);\n}\n#endif\n\n// ***************** random *************************************\n\nfloat f_rnd( float from, float to )\n{\n\tfloat r;\n\n\tif ( from >= to )\n\t\treturn from;\n\n\tr = from + (to - from) * ((float)rand() / RAND_MAX);\n\n\treturn bound(from, r, to);\n}\n\nint i_rnd( int from, int to )\n{\n\tfloat r;\n\n\tif ( from >= to )\n\t\treturn from;\n\n\tr = (int)(from + (1.0 + to - from) * ((float)rand() / RAND_MAX));\n\n\treturn bound(from, r, to);\n}\n\n//\n// Gets the next part of a string that fits within a specified width.\n//\n//\t\tinput\t\t\t= The string we want to wordwrap.\n//\t\ttarget\t\t\t= The string that we should put the next line in.\n//\t\tstart_index\t\t= The index in the input string where we start wordwrapping.\n//\t\tend_index\t\t= The returned end index of the word wrapped string.\n//\t\ttarget_max_size = The length of the target string buffer.\n//\t\tmax_pixel_width\t= The pixel width that the string should be wordwrapped within.\n//\t\tchar_size\t\t= The size of the characters in the string.\n//\nqbool Util_GetNextWordwrapString(const char *input, char *target, int start_index, int *end_index_ret, int target_max_size, int max_pixel_width, int char_size)\n{\n\tqbool wrap_found\t= false;\n\tint retval\t\t\t= true;\n\n\tint max_char_width\t= Q_rint((float)max_pixel_width / char_size - 1);\t// The max number of characters allowed (excluding white spaces at the end).\n\tint end_index\t\t= start_index;\t\t\t\t\t\t\t\t\t\t// The index of the last character to include in this line.\n\tint input_len\t\t= strlen(input + start_index) + 1;\t\t\t\t\t// Length of the input including \\0.\n\tint max_width\t\t= min(max_char_width, input_len);\t\t\t\t\t// The max width allowed for the line.\n\n\t// New lines take precendence as a wrap.\n\tfor (end_index = start_index; end_index < (start_index + max_width); end_index++)\n\t{\n\t\tif (input[end_index] == '\\n')\n\t\t{\n\t\t\twrap_found = true;\n\t\t\tbreak;\n\t\t}\n\n\t\t// Have we reached the end of the string?\n\t\tif (input[end_index] == '\\0')\n\t\t{\n\t\t\tretval = false;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!wrap_found)\n\t{\n\t\tif (input[end_index] != ' ')\n\t\t{\n\t\t\t// We're in the middle of a word, try to find a word boundary instead.\n\t\t\tint i;\n\n\t\t\tfor (i = end_index; i > start_index; i--)\n\t\t\t{\n\t\t\t\tif (input[i] == ' ')\n\t\t\t\t{\n\t\t\t\t\tend_index = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Eat up all white spaces at the end of the line.\n\t\t\twhile (input[end_index + 1] == ' ')\n\t\t\t{\n\t\t\t\tend_index++;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the end index.\n\tif (end_index_ret)\n\t{\n\t\t(*end_index_ret) = end_index;\n\t}\n\n\t// Copy the wrap string to the target buffer.\n\tif (target)\n\t{\n\t\tsnprintf(target, min(target_max_size, max_width), \"%s\", input + start_index);\n\t}\n\n\treturn retval;\n}\n\nvoid Utils_RegExpFreeSubstring(char* substring)\n{\n\tif (substring) {\n\t\tpcre2_substring_free((PCRE2_UCHAR8*)substring);\n\t}\n}\n\n"
  },
  {
    "path": "src/utils.h",
    "content": "/*\n\nCopyright (C) 2001-2002       A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the included (GNU.txt) GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: utils.h,v 1.21 2007-09-15 15:26:17 cokeman1982 Exp $\n\n*/\n\n#ifndef __UTILS_H__\n#define __UTILS_H__\n\n#define\tPLAYER_ID_NOMATCH\t\t-1\n#define\tPLAYER_NAME_NOMATCH\t\t-2\n#define\tPLAYER_NUM_NOMATCH\t\t-3\n\n/// \n/// General Utils\n///\n\nchar *CreateSpaces(int amount);\nchar *SecondsToMinutesString(int print_time);\nchar *SecondsToHourString(int time);\nchar *ColorNameToRGBString(char *color_name);\nbyte *StringToRGB(char *s);\nint StringToRGB_W(char *s, byte *rgb);\nvoid RGBToString(const byte *rgb, char *s);\nvoid TrackerStringToRGB_W(const char *s, byte *rgb);\nint ParseFloats(char *s, float *f, int *f_size);\n\n// don't count ezquake color sequence\nint strlen_color(const char *str);\nint strlen_color_by_terminator(const char *str, char terminator);\n// skip ezquake color sequence\nvoid Util_SkipEZColors(char *dst, const char *src, size_t size);\nvoid Util_SkipChars(const char *src, const char *chars, char *dst, size_t dstsize);\nvoid str_align_right (char *target, size_t size, const char *source, size_t length);\n\n// skip whitespace from both beginning and end\nchar *str_trim(char *str);\n\nint HexToInt(char c);\n\n///\n/// Game Mode utils\n///\n\nchar *get_ktx_mode(void);\nqbool check_ktx_ca(void);\nqbool check_ktx_wo(void);\nqbool check_ktx_ca_wo(void);\n\n///\n/// Filename utils\n///\n\nint Util_Extend_Filename(char *filename, char **ext);\nqbool Util_Is_Valid_Filename(char *s);\nqbool Util_Is_Valid_FilenameEx(char *s, qbool drive_prefix_valid);\nchar *Util_Invalid_Filename_Msg(char *s);\nvoid Util_Process_Filename(char *string);\nvoid Util_Process_FilenameEx(char *string, qbool allow_root);\nvoid Util_ToValidFileName(const char* input, char* output, size_t buffersize);\n\n///\n/// Player utils\n///\n\nint Player_IdtoSlot (int id);\nint Player_SlottoId (int slot);\nint Player_NametoSlot(char *name);\nint Player_StringtoSlot(char *arg, qbool use_regular_expressions, qbool prioritise_user_id);\nint Player_NumtoSlot (int num);\nint Player_GetSlot(char *arg, qbool prioritise_user_id);\nint Player_GetTrackId(int player_id);\nchar *Player_MyName(void);\n\n///\n/// Nick completion related\n///\n\n\n// yet another utility, there also exist at least one similar function Player_StripNameColor(), but not the same\nvoid RemoveColors (char *name, size_t len);\n\n#define FBN_IGNORE_SPECS\t\t(1<<0)\n#define FBN_IGNORE_PLAYERS\t\t(1<<1)\n#define FBN_IGNORE_QTVSPECS\t\t(1<<2)\n\nqbool FindBestNick (const char *nick, int flags, char *result, size_t result_len);\n\n///\n/// Clipboard\n///\nvoid CopyToClipboard(const char *text);\nchar *ReadFromClipboard(void);\n\n///\n/// TF Team-Color Utils\n///\n\nchar *Utils_TF_ColorToTeam(int);\nint Utils_TF_TeamToColor(char *);\n\n///\n/// String Utils \n///\n\nqbool Util_F_Match (const char *msg, char *f_req);\nvoid Replace_In_String (char *src,int n, char delim, int arg, ...);\n/// converts fun text to string prepared to sort\nvoid FunToSort(char *text);\n/// compares two fun strings\nint funcmp(const char *s1, const char *s2);\n#ifdef UNUSED_CODE\nunsigned char CharToBrown(unsigned char ch);\nunsigned char CharToWhite(unsigned char ch);\n#endif\nvoid CharsToBrown(char* start, char* end);\nchar *CharsToBrownStatic(char *in);\nvoid CharsToWhite(char* start, char* end);\n\n///\n/// REGEXP\n///\n\nqbool Utils_RegExpMatch(char *regexp, char *matchstring);\nqbool Utils_RegExpGetGroup(char *regexp, char *matchstring, const char **resultstring, int *resultlength, int group);\nvoid Utils_RegExpFreeSubstring(char* substring);\n\n// regexp match support for group operations in scripts\nqbool IsRegexp(const char *str);\nqbool ReSearchInit(const char *wildcard);\nqbool ReSearchInitEx(const char* wildcard, qbool case_sensitive);\nqbool ReSearchMatch(const char *str);\nvoid ReSearchDone(void);\n\n///\n/// RANDOM GENERATORS\n///\n\nfloat f_rnd( float from, float to );\nint i_rnd( int from, int to );\n\n//\n// Gets the next part of a string that fits within a specified width.\n//\n//\t\tinput\t\t\t= The string we want to wordwrap.\n//\t\ttarget\t\t\t= The string that we should put the next line in.\n//\t\tstart_index\t\t= The index in the input string where we start wordwrapping.\n//\t\tend_index\t\t= The returned end index of the word wrapped string.\n//\t\ttarget_max_size = The length of the target string buffer.\n//\t\tmax_pixel_width\t= The pixel width that the string should be wordwrapped within.\n//\t\tchar_size\t\t= The size of the characters in the string.\n//\nqbool Util_GetNextWordwrapString(const char *input, char *target, int start_index, int *end_index, int target_max_size, int max_pixel_width, int char_size);\n\n#endif /* !__UTILS_H__ */\n"
  },
  {
    "path": "src/version.c",
    "content": "/*\n\tversion.c\n\n\tBuild number and version strings\n\n\tCopyright (C) 1996-1997 Id Software, Inc.\n\n\tThis program is free software; you can redistribute it and/or\n\tmodify it under the terms of the GNU General Public License\n\tas published by the Free Software Foundation; either version 2\n\tof the License, or (at your option) any later version.\n\n\tThis program is distributed in the hope that it will be useful,\n\tbut WITHOUT ANY WARRANTY; without even the implied warranty of\n\tMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n\tSee the GNU General Public License for more details.\n\n\tYou should have received a copy of the GNU General Public License\n\talong with this program; if not, write to:\n\n\t\tFree Software Foundation, Inc.\n\t\t59 Temple Place - Suite 330\n\t\tBoston, MA  02111-1307, USA\n\n\t$Id: version.c,v 1.14 2007-05-28 10:47:36 johnnycz Exp $\n*/\n\n#include \"common.h\"\n#include \"version.h\"\n\n#include <jansson.h>\n#include <SDL_mutex.h>\n#include <SDL_thread.h>\n#include <curl/curl.h>\n\n\n#define VERSION_UNKNOWN \"Unknown\"\n#define VERSION_GITHUB_MAXLEN 32768 // ~2k @ ~2023\n#define VERSION_GITHUB_URL \"https://api.github.com/repos/qw-group/ezquake-source/releases/latest\"\n\nstatic char version_latest[VERSION_MAX_LEN] = VERSION_UNKNOWN;\nstatic qbool version_refreshing = false;\nstatic SDL_mutex *version_mutex = NULL;\n\nstatic void VersionCheck_OnConfigChange(cvar_t *var, char *string, qbool *cancel);\nstatic cvar_t allow_update_check  = {\"sys_update_check\",  \"1\", CVAR_NONE, VersionCheck_OnConfigChange};\n\n/*\n=======================\nCL_Version_f\n======================\n*/\nvoid CL_Version_f (void)\n{\n\tCom_Printf (\"ezQuake %s\\n\", VersionString());\n\tCom_Printf (\"Exe: \"__DATE__\" \"__TIME__\"\\n\");\n\n#ifdef _DEBUG\n\tCon_Printf(\"debug build\\n\");\n#endif\n\n#ifdef __MINGW32__\n\tCon_Printf(\"Compiled with MinGW version: %i.%i\\n\",__MINGW32_MAJOR_VERSION, __MINGW32_MINOR_VERSION);\n#endif\n\n#ifdef __CYGWIN__\n\tCon_Printf(\"Compiled with Cygwin\\n\");\n#endif\n\n#ifdef __GNUC__\n\tCon_Printf(\"Compiled with GCC version: %i.%i.%i (%i)\\n\",__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__, __VERSION__);\n\n\t#ifdef __OPTIMIZE__\n\t\t#ifdef __OPTIMIZE_SIZE__\n\t\t\tCon_Printf(\"GCC Optimization: Optimized for size\\n\");\n\t\t#else\n\t\t\tCon_Printf(\"GCC Optimization: Optimized for speed\\n\");\n\t\t#endif\n\t#endif\n\n\t#ifdef __NO_INLINE__\n\t\tCon_Printf(\"GCC Optimization: Functions currently not inlined into their callers\\n\");\n\t#else\n\t\tCon_Printf(\"GCC Optimization: Functions currently inlined into their callers\\n\");\n\t#endif\n#endif\n\n#ifdef _M_IX86\n\tCon_Printf(\"x86 code optimized for: \");\n\n\tif (_M_IX86 == 600) { Con_Printf(\"Pentium Pro, Pentium II and Pentium III\"); }\n\telse if (_M_IX86 == 500) { Con_Printf(\"Pentium\"); }\n\telse if (_M_IX86 == 400) { Con_Printf(\"486\"); }\n\telse if (_M_IX86 == 300) { Con_Printf(\"386\"); }\n\telse\n\t{\n\t\tCon_Printf(\"Unknown (%i)\\n\",_M_IX86);\n\t}\n\n\tCon_Printf(\"\\n\");\n#endif\n\n#ifdef _M_IX86_FP\n\tif (_M_IX86_FP == 0) { Con_Printf(\"SSE & SSE2 instructions disabled\\n\"); }\n\telse if (_M_IX86_FP == 1) { Con_Printf(\"SSE instructions enabled\\n\"); }\n\telse if (_M_IX86_FP == 2) { Con_Printf(\"SSE2 instructions enabled\\n\"); }\n\telse\n\t{\n\t\tCon_Printf(\"Unknown Arch specified: %i\\n\",_M_IX86_FP);\n\t}\n#endif\n\n#ifdef EZ_FREETYPE_SUPPORT\n\tCon_Printf(\"Portions of this software are copyright (c) 2018 The FreeType Project(www.freetype.org). All rights reserved.\\n\");\n#endif\n}\n\n/*\n=======================\nVersionString\n======================\n*/\nchar *VersionString (void)\n{\n\tstatic char str[64];\n\n\tsnprintf (str, sizeof(str), \"%s %s\", VERSION_NUMBER, VERSION);\n\n\treturn str;\n}\n\nchar *VersionStringColour(void)\n{\n\tstatic char str[64];\n\n\tsnprintf (str, sizeof(str), \"&c1e1%s&r %s\", VERSION_NUMBER, VERSION);\n\n\treturn str;\n}\n\nchar *VersionStringFull (void)\n{\n\tstatic char str[256];\n\n\tsnprintf (str, sizeof(str), SERVER_NAME \" %s \" \"(\" QW_PLATFORM \")\" \"\\n\", VersionString());\n\n\treturn str;\n}\n\ntypedef struct version_context_St\n{\n\tchar *buffer;\n\tchar *ptr;\n} version_context_t;\n\nstatic size_t VersionReadGitHubResponse(void* chunk, size_t size, size_t nmemb, void* user_data)\n{\n\tversion_context_t* ctx = (version_context_t *) user_data;\n\n\tsize_t chunk_len = size * nmemb;\n\tsize_t remaining = VERSION_GITHUB_MAXLEN - (ctx->ptr - ctx->buffer) - 1;\n\n\tif (chunk_len > remaining)\n\t{\n\t\treturn 0;\n\t}\n\n\tmemcpy(ctx->ptr, chunk, chunk_len);\n\tctx->ptr += chunk_len;\n\t*ctx->ptr = '\\0';\n\n\treturn chunk_len;\n}\n\nstatic int VersionCheck_Thread(void *args)\n{\n\tchar buffer[VERSION_GITHUB_MAXLEN] = { 0 };\n\tjson_error_t error;\n\tjson_t *root, *tag_name;\n\tstruct curl_slist *headers = NULL;\n\tversion_context_t ctx = {\n\t\t.buffer = buffer,\n\t\t.ptr = buffer,\n\t};\n\n\troot = tag_name = NULL;\n\n\tCURL* curl = curl_easy_init();\n\tif (!curl)\n\t{\n\t\tCom_DPrintf(\"Unable to create cURL client\\n\");\n\t\tgoto cleanup;\n\t}\n\n\tcurl_easy_setopt(curl, CURLOPT_URL, VERSION_GITHUB_URL);\n\tcurl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, VersionReadGitHubResponse);\n\tcurl_easy_setopt(curl, CURLOPT_WRITEDATA, &ctx);\n\n\theaders = curl_slist_append(headers, \"User-Agent: Shub-Niggurath Inc\");\n\tcurl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);\n\t\n\tCURLcode res = curl_easy_perform(curl);\n\tif (res != CURLE_OK)\n\t{\n\t\tCom_DPrintf(\"Could not fetch latest version: %s\", curl_easy_strerror(res));\n\t\tgoto cleanup;\n\t}\n\n\troot = json_loads(buffer, 0, &error);\n\tif (!root)\n\t{\n\t\tCom_DPrintf(\"JSON decode failed: %s\", error.text);\n\t\tgoto cleanup;\n\t}\n\t\n\ttag_name = json_object_get(root, \"tag_name\");\n\tif (!json_is_string(tag_name))\n\t{\n\t\tCom_DPrintf(\"Version not found in GitHub response\");\n\t\tgoto cleanup;\n\t}\n\n\tconst char* version_str = json_string_value(tag_name);\n\n\tSDL_LockMutex(version_mutex);\n\tstrlcpy(version_latest, version_str, VERSION_MAX_LEN);\n\tSDL_UnlockMutex(version_mutex);\n\ncleanup:\n\tif (root != NULL)\n\t{\n\t\tjson_decref(root);\n\t}\n\tif (curl != NULL)\n\t{\n\t\tcurl_slist_free_all(headers);\n\t\tcurl_easy_cleanup(curl);\n\t}\n\n\tSDL_LockMutex(version_mutex);\n\tversion_refreshing = false;\n\tSDL_UnlockMutex(version_mutex);\n\n\treturn 0;\n}\n\nstatic void VersionCheck_Refresh(void)\n{\n\tSDL_LockMutex(version_mutex);\n\n\tif (!version_refreshing)\n\t{\n\t\tSDL_Thread *thread = SDL_CreateThread(VersionCheck_Thread, \"Version fetcher\", NULL);\n\t\tif (!thread)\n\t\t{\n\t\t\tCom_DPrintf(\"Could not start version fetcher: %s\\n\", SDL_GetError());\n\t\t\tversion_refreshing = false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tSDL_DetachThread(thread);\n\t\t\tversion_refreshing = true;\n\t\t}\n\t}\n\n\tSDL_UnlockMutex(version_mutex);\n}\n\nstatic void VersionCheck_OnConfigChange(cvar_t *cfg, char *string, qbool *cancel)\n{\n\tif (strcmp(string, \"0\") == 0)\n\t{\n\t\treturn;\n\t}\n\n\tVersionCheck_Refresh();\n}\n\nvoid VersionCheck_Init(void)\n{\n\tCvar_Register(&allow_update_check);\n\n\tversion_mutex = SDL_CreateMutex();\n}\n\nvoid VersionCheck_Shutdown(void)\n{\n\tif (version_mutex != NULL)\n\t{\n\t\tSDL_DestroyMutex(version_mutex);\n\t\tversion_mutex = NULL;\n\t}\n}\n\ntypedef struct semantic_version_St\n{\n\tint major;\n\tint minor;\n\tint patch;\n} semantic_version_t;\n\nstatic qbool SemanticVersionFromString(const char* value, semantic_version_t* version)\n{\n\tif (!value)\n\t\treturn false;\n\tif (sscanf(value, \"%d.%d.%d\", &version->major, &version->minor, &version->patch) != 3)\n\t\treturn false;\n\treturn true;\n}\n\nstatic qbool VersionCheck_CompareVersions(const semantic_version_t *this_version, const semantic_version_t *latest_version)\n{\n\tif (this_version->major < latest_version->major)\n\t\treturn true;\n\tif (this_version->major > latest_version->major)\n\t\treturn false;\n\tif (this_version->minor < latest_version->minor)\n\t\treturn true;\n\tif (this_version->minor > latest_version->minor)\n\t\treturn false;\n\treturn this_version->patch < latest_version->patch;\n}\n\n// Assumes any failures = already on latest version to avoid erroneous showing of update message\nstatic qbool VersionCheck_IsOutdated(const char recent_version[VERSION_MAX_LEN])\n{\n\tsemantic_version_t v1, v2;\n\n\tif (strcmp(recent_version, VERSION_UNKNOWN) == 0)\n\t\treturn false;\n\t\n\tif (!SemanticVersionFromString(VERSION_NUMBER, &v1))\n\t{\n\t\tCom_DPrintf(\"Could not parse own version: %s\", VERSION_NUMBER);\n\t\treturn false;\n\t}\n\n\tif (!SemanticVersionFromString(recent_version, &v2))\n\t{\n\t\tCom_DPrintf(\"Could not parse recent version: %s\", recent_version);\n\t\treturn false;\n\t}\n\n\treturn VersionCheck_CompareVersions(&v1, &v2);\n}\n\nqbool VersionCheck_GetLatest(char dest[VERSION_MAX_LEN])\n{\n\tSDL_LockMutex(version_mutex);\n\tstrlcpy(dest, version_latest, VERSION_MAX_LEN);\n\tSDL_UnlockMutex(version_mutex);\n\n\treturn VersionCheck_IsOutdated(dest);\n}"
  },
  {
    "path": "src/version.h",
    "content": "/*\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: version.h,v 1.7 2007-10-04 15:18:20 johnnycz Exp $\n*/\n// version.h\n\n#ifndef __VERSION_H__\n#define __VERSION_H__\n\n#if defined(_WIN32)\n// Warning: this is different from mvdsv (no 64-bit check)\n#if defined(_WIN64)\n\t#define QW_PLATFORM\t\t\t\"Win64\"\n#else\n\t#define QW_PLATFORM\t\t\t\"Win32\"\n#endif\n#define QW_PLATFORM_SHORT\t\"w\"\n\n#elif defined(__FreeBSD__)\n#define QW_PLATFORM\t\t\t\"FreeBSD\"\n#define QW_PLATFORM_SHORT\t\"f\"\n\n#elif defined(__OpenBSD__)\n#define QW_PLATFORM\t\t\t\"OpenBSD\"\n#define QW_PLATFORM_SHORT\t\"o\"\n\n#elif defined(__NetBSD__)\n#define QW_PLATFORM\t\t\t\"NetBSD\"\n#define QW_PLATFORM_SHORT\t\"n\"\n\n#elif defined(__DragonFly__)\n#define QW_PLATFORM\t\t\t\"DragonFly\"\n#define QW_PLATFORM_SHORT\t\"d\"\n\n#elif defined(__linux__)\n// Warning: this is different from mvdsv (no 64-bit check)\n\t#ifdef __x86_64__\n\t\t#define QW_PLATFORM\t\"Linux64\"\n\t#else\n\t\t#define QW_PLATFORM\t\"Linux32\"\n\t#endif\n#define QW_PLATFORM_SHORT\t\"l\"\n\n#elif defined(__sun__)\n#define QW_PLATFORM\t\t\t\"SunOS\"\n#define QW_PLATFORM_SHORT\t\"s\"\n\n#elif defined(__APPLE__)\n#define QW_PLATFORM\t\t\t\"MacOSX\"\n#define QW_PLATFORM_SHORT\t\"m\"\n\n#else\n#define QW_PLATFORM\t\t\t\"Unknown\"\n#define QW_PLATFORM_SHORT\t\"u\"\n#endif\n\n#define QW_RENDERER\t\t\t\"GL\"\n\n#ifdef _DEBUG\n#define QW_CONFIGURATION\t\"Debug\"\n#else\n#define QW_CONFIGURATION\t\"Release\"\n#endif\n\n// Note: for server mods to detect version, change VERSION_NUM below\n#define VERSION_NUMBER \"3.7.0-dev\"\n#define VERSION_MAX_LEN 32\n\nvoid CL_Version_f(void);\nchar *VersionString(void);\nchar *VersionStringColour(void);\nchar *VersionStringFull(void);\n\nvoid VersionCheck_Init(void);\nvoid VersionCheck_Shutdown(void);\nqbool VersionCheck_GetLatest(char dest[VERSION_MAX_LEN]);\n\n#ifndef SERVERONLY\n#define SERVER_NAME         \"EZQUAKE\"\n#else\n#define SERVER_NAME         \"MVDSV\"\n#endif\n\n// MVDSV compatibility\n#define\tQW_VERSION\t\t\t\"2.40\"\n#define SERVER_VERSION      \"0.34-beta\"\n#define SERVER_VERSION_NUM  0.33\n#define SERVER_FULLNAME     \"MVDSV: MultiView Demo SerVer\"\n#define SERVER_HOME_URL     \"https://mvdsv.deurk.net\"\n#define VERSION_NUM         3.6\n#define BUILD_DATE          __DATE__ \", \" __TIME__\n#define GIT_COMMIT          \"\"\n\n// ezQuake URLs etc\n#define EZ_VERSION_WEBSITE \"http://www.ezquake.com/\"\n#define EZ_MVD_SIGNOFF \\\n\t\"\\x1d\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1f\\n\" \\\n\t\"Recorded by ezQuake (http://www.ezquake.com/)\\n\" \\\n\t\"Discord: http://discord.quake.world/\\n\" \\\n\t\"Forums: http://quakeworld.nu/\\n\" \\\n\t\"\\x1d\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1f\\n\"\n#define EZ_QWD_SIGNOFF EZ_MVD_SIGNOFF\n\t// \"\\x1d\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e Recorded by ezQuake \\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1e\\x1f\"\n\n#endif /* !__VERSION_H__ */\n"
  },
  {
    "path": "src/vfs.h",
    "content": "/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n *     \n * $Id: vfs.h,v 1.13 2007-10-13 16:24:50 dkure Exp $\n *             \n */\n\n#ifndef __VFS_H__\n#define __VFS_H__\n\n#include \"hash.h\"\n\n//=================================\n// Quake filesystem\n//=================================\nextern hashtable_t *filesystemhash;\nextern int fs_hash_dups;\t\t\nextern int fs_hash_files;\t\t\n\ntypedef struct {\n\tstruct searchpath_s *search;\n\tint             index;\n\tchar            rawname[MAX_OSPATH];\n\tint             offset;\n\tint             len;\n} flocation_t;\n\ntypedef struct {\n\tvoid\t(*PrintPath)(void *handle);\n\tvoid\t(*ClosePath)(void *handle);\n\tvoid\t(*BuildHash)(void *handle);\n\tqbool   (*FindFile)(void *handle, flocation_t *loc, const char *name, void *hashedresult);\t\n\t\t// true if found (hashedresult can be NULL)\n\t\t// note that if rawfile and offset are set, many Com_FileOpens will \n\t\t// read the raw file otherwise ReadFile will be called instead.\n\tvoid\t(*ReadFile)(void *handle, flocation_t *loc, char *buffer);\t\n\t\t// reads the entire file\n\tint\t\t(*EnumerateFiles)(void *handle, char *match, int (*func)(char *, int, void *), void *parm);\n\n\tvoid\t*(*OpenNew)(vfsfile_t *file, const char *desc);\t\n\t\t// returns a handle to a new pak/path\n\n\tint\t\t(*GeneratePureCRC) (void *handle, int seed, int usepure);\n\n\tvfsfile_t *(*OpenVFS)(void *handle, flocation_t *loc, char *mode);\n} searchpathfuncs_t;\n\ntypedef struct searchpath_s\n{\n\tsearchpathfuncs_t *funcs;\n\tqbool copyprotected;\t// don't allow downloads from here.\n\tqbool istemporary;\n\tvoid *handle;\n\tint crc_check;\t// client sorts packs according to this checksum\n\tint crc_reply;\t// client sends a different crc back to the server, \n\t                // for the paks it's actually loaded.\n\tstruct searchpath_s *nextpure;\n\tstruct searchpath_s *next;\n} searchpath_t;\n\n\n//=================================\n// STDIO Files (OS)\n//=================================\ntypedef struct {\n\tvfsfile_t funcs; // <= must be at top/begining of struct\n\n\tFILE *handle;\n\n} vfsosfile_t;\n\nvfsfile_t *FS_OpenTemp(void);\nvfsfile_t *VFSOS_Open(char *osname, char *mode);\n\nextern searchpathfuncs_t osfilefuncs;\n\n//====================\n// PACK (*pak) Support\n//====================\n\n// in memory\n// VFS-FIXME: This is a bad name, it really is just a filename + offset....\ntypedef struct\n{\n\tchar\tname[MAX_QPATH];\n\tint\t\tfilepos, filelen;\n} packfile_t;\n\nextern searchpathfuncs_t packfilefuncs;\n\n//===========================\n// ZIP (*.zip, *.pk3) Support\n//===========================\n#ifdef WITH_ZIP\nextern searchpathfuncs_t zipfilefuncs;\n#endif // WITH_ZIP\n\n//=============================\n// TCP Support - VFS Functions\n//=============================\nint VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread, vfserrno_t *err);\nint VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestowrite);\nint VFSTCP_Seek (struct vfsfile_s *file, unsigned long pos, int whence);\nunsigned long VFSTCP_Tell (struct vfsfile_s *file);\nunsigned long VFSTCP_GetLen (struct vfsfile_s *file);\nvoid VFSTCP_Close (struct vfsfile_s *file);\nvoid VFSTCP_Tick(void);\nvfsfile_t *FS_OpenTCP(char *name);\n\n//=====================\n// GZIP (*.gz) Support\n//=====================\n#ifdef WITH_ZLIB\nextern searchpathfuncs_t gzipfilefuncs;\n#endif // WITH_ZLIB\n\n//=====================\n// TAR (*.tar) Support\n//=====================\nextern searchpathfuncs_t tarfilefuncs;\n\n//=====================\n// Memory Mapped files\n//=====================\nvfsfile_t *FSMMAP_OpenVFS(void *buf, size_t buf_len);\nqbool FSMMAP_IsMemoryMapped(vfsfile_t* file);\n\n//=====================\n// Doomwad Support\n//=====================\n#ifdef DOOMWADS\nextern searchpathfuncs_t doomwadfilefuncs;\n#endif //DOOMWADS\n\n// FS_FLocateFile return type.\ntypedef enum\n{\n\tFSLFRT_IFFOUND,\t\t\t// return true if file found, false if not found.\n\tFSLFRT_LENGTH,\t\t\t// return file length if found, -1 if not found.\n\tFSLFRT_DEPTH_OSONLY,\t// return depth (no paks), 0x7fffffff if not found.\n\tFSLFRT_DEPTH_ANYPATH\t// return depth, 0x7fffffff if not found.\n} FSLF_ReturnType_e;\n\nvoid FS_FlushFSHash(void);\nint FS_FLocateFile(const char *filename, FSLF_ReturnType_e returntype, flocation_t *loc);\nvoid FS_Shutdown(void);\n\n#endif /* __VFS_H__ */\n"
  },
  {
    "path": "src/vfs_doomwad.c",
    "content": "/*\n Copyright (C) 1996-1997 Id Software, Inc.\n \n This program is free software; you can redistribute it and/or\n modify it under the terms of the GNU General Public License\n as published by the Free Software Foundation; either version 2\n of the License, or (at your option) any later version.\n \n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n \n See the GNU General Public License for more details.\n \n You should have received a copy of the GNU General Public License\n along with this program; if not, write to the Free Software\n Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n \n $Id: vfs_doomwad.c,v 1.3 2007-09-30 22:59:24 disconn3ct Exp $\n*/\n\n\n#include \"quakedef.h\"\n#include \"hash.h\"\n#include \"common.h\"\n#include \"fs.h\"\n#include \"vfs.h\"\n\n#ifdef DOOMWADS\ntypedef struct\n{\n\tint\t\tfilepos, filelen;\n\tchar\tname[8];\n} dwadfile_t;\n\ntypedef struct\n{\n\tchar\tid[4];\n\tint\t\tdirlen;\n\tint\t\tdirofs;\n} dwadheader_t;\n\nvoid *FSPAK_LoadDoomWadFile (vfsfile_t *packhandle, char *desc)\n{\n\tdwadheader_t\theader;\n\tint\t\t\t\ti;\n\tpackfile_t\t\t*newfiles;\n\tint\t\t\t\tnumpackfiles;\n\tpack_t\t\t\t*pack;\n\tdwadfile_t\t\tinfo;\n\n\tint section=0;\n\tchar sectionname[MAX_QPATH];\n\tchar filename[52];\n\tchar neatwadname[52];\n\n\tif (packhandle == NULL)\n\t\treturn NULL;\n\n\tVFS_READ(packhandle, &header, sizeof(header), NULL);\n\tif (header.id[1] != 'W'\t|| header.id[2] != 'A' || header.id[3] != 'D')\n\t\treturn NULL;\t//not a doom wad\n\n\t//doom wads come in two sorts. iwads and pwads.\n\t//iwads are the master wads, pwads are meant to replace parts of the master wad.\n\t//this is awkward, of course.\n\t//we ignore the i/p bit for the most part, but with maps, pwads are given a prefixed name.\n\tif (header.id[0] == 'I')\n\t\t*neatwadname = '\\0';\n\telse if (header.id[0] == 'P')\n\t{\n\t\tCOM_FileBase(desc, neatwadname);\n\t\tstrlcat (neatwadname, \"#\", sizeof (neatwadname));\n\t}\n\telse\n\t\treturn NULL;\n\n\theader.dirofs = LittleLong (header.dirofs);\n\theader.dirlen = LittleLong (header.dirlen);\n\n\tnumpackfiles = header.dirlen;\n\tnewfiles = (packfile_t*)Q_malloc (numpackfiles * sizeof(packfile_t));\n\tVFS_SEEK(packhandle, header.dirofs, SEEK_SET);\n\n\t//doom wads are awkward.\n\t//they have no directory structure, except for start/end 'files'.\n\t//they follow along the lines of lumps after the parent name.\n\t//a map is the name of that map, and then a squence of the lumps that form that map (found by next-with-that-name).\n\t//this is a problem for a real virtual filesystem, so we add a hack to recognise special names and expand them specially.\n\tfor (i=0 ; i<numpackfiles ; i++)\n\t{\n\t\tVFS_READ (packhandle, &info, sizeof(info), NULL);\n\n\t\tstrlcpy (filename, info.name, sizeof (filename));\n\t\tfilename[8] = '\\0';\n\t\tQ_strlwr(filename);\n\n\t\tnewfiles[i].filepos = LittleLong(info.filepos);\n\t\tnewfiles[i].filelen = LittleLong(info.filelen);\n\n\t\tswitch(section)\t//be prepared to remap filenames.\n\t\t{\nnewsection:\n\t\tcase 0:\n\t\t\tif (info.filelen == 0)\n\t\t\t{\t//marker for something...\n\n\t\t\t\tif (!strcmp(filename, \"s_start\"))\n\t\t\t\t{\n\t\t\t\t\tsection = 2;\n\t\t\t\t\tsnprintf (newfiles[i].name, sizeof (newfiles[i].name), \"sprites/%s\", filename);\t//the model loader has a hack to recognise .dsp\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (!strcmp(filename, \"p_start\"))\n\t\t\t\t{\n\t\t\t\t\tsection = 3;\n\t\t\t\t\tsnprintf (newfiles[i].name, sizeof (newfiles[i].name), \"patches/%s\", filename); //the map loader will find these.\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (!strcmp(filename, \"f_start\"))\n\t\t\t\t{\n\t\t\t\t\tsection = 4;\n\t\t\t\t\tsnprintf (newfiles[i].name, sizeof (newfiles[i].name), \"flats/%s\", filename);\t//the map loader will find these\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif ((filename[0] == 'e' && filename[2] == 'm') || !strncmp(filename, \"map\", 3))\n\t\t\t\t{\t//this is the start of a beutiful new map\n\t\t\t\t\tsection = 1;\n\t\t\t\t\tstrlcpy (sectionname, filename, sizeof (sectionname));\n\t\t\t\t\tsnprintf (newfiles[i].name, sizeof (newfiles[i].name), \"maps/%s%s.bsp\", neatwadname, filename);\t//generate fake bsps to allow the server to find them\n\t\t\t\t\tnewfiles[i].filepos = 0;\n\t\t\t\t\tnewfiles[i].filelen = 4;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (!strncmp(filename, \"gl_\", 3) && ((filename[4] == 'e' && filename[5] == 'm') || !strncmp(filename+3, \"map\", 3)))\n\t\t\t\t{\t//this is the start of a beutiful new map\n\t\t\t\t\tsection = 5;\n\t\t\t\t\tstrlcpy (sectionname, filename+3, sizeof (sectionname));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsnprintf (newfiles[i].name, sizeof (newfiles[i].name), \"wad/%s\", filename);\t//but there are many files that we don't recognise/know about. archive them off to keep the vfs moderatly clean.\n\t\t\tbreak;\n\t\tcase 1:\t//map section\n\t\t\tif (strcmp(filename, \"things\") &&\n\t\t\t\tstrcmp(filename, \"linedefs\") &&\n\t\t\t\tstrcmp(filename, \"sidedefs\") &&\n\t\t\t\tstrcmp(filename, \"vertexes\") &&\n\t\t\t\tstrcmp(filename, \"segs\") &&\n\t\t\t\tstrcmp(filename, \"ssectors\") &&\n\t\t\t\tstrcmp(filename, \"nodes\") &&\n\t\t\t\tstrcmp(filename, \"sectors\") &&\n\t\t\t\tstrcmp(filename, \"reject\") &&\n\t\t\t\tstrcmp(filename, \"blockmap\"))\n\t\t\t{\n\t\t\t\tsection = 0;\n\t\t\t\tgoto newsection;\n\t\t\t}\n\t\t\tsnprintf (newfiles[i].name, sizeof (newfiles[i].name), \"maps/%s%s.%s\", neatwadname, sectionname, filename);\n\t\t\tbreak;\n\t\tcase 5:\t//glbsp output section\n\t\t\tif (strcmp(filename, \"gl_vert\") &&\n\t\t\t\tstrcmp(filename, \"gl_segs\") &&\n\t\t\t\tstrcmp(filename, \"gl_ssect\") &&\n\t\t\t\tstrcmp(filename, \"gl_pvs\") &&\n\t\t\t\tstrcmp(filename, \"gl_nodes\"))\n\t\t\t{\n\t\t\t\tsection = 0;\n\t\t\t\tgoto newsection;\n\t\t\t}\n\t\t\tsnprintf (newfiles[i].name, sizeof (newfiles[i].name), \"maps/%s%s.%s\", neatwadname, sectionname, filename);\n\t\t\tbreak;\n\t\tcase 2:\t//sprite section\n\t\t\tif (!strcmp(filename, \"s_end\"))\n\t\t\t{\n\t\t\t\tsection = 0;\n\t\t\t\tgoto newsection;\n\t\t\t}\n\t\t\tsnprintf (newfiles[i].name, sizeof (newfiles[i].name), \"sprites/%s\", filename);\n\t\t\tbreak;\n\t\tcase 3:\t//patches section\n\t\t\tif (!strcmp(filename, \"p_end\"))\n\t\t\t{\n\t\t\t\tsection = 0;\n\t\t\t\tgoto newsection;\n\t\t\t}\n\t\t\tsnprintf (newfiles[i].name, sizeof (newfiles[i].name), \"patches/%s\", filename);\n\t\t\tbreak;\n\t\tcase 4:\t//flats section\n\t\t\tif (!strcmp(filename, \"f_end\"))\n\t\t\t{\n\t\t\t\tsection = 0;\n\t\t\t\tgoto newsection;\n\t\t\t}\n\t\t\tsnprintf (newfiles[i].name, sizeof (newfiles[i].name), \"flats/%s\", filename);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tpack = (pack_t*)Q_malloc (sizeof (pack_t));\n\tstrlcpy (pack->filename, desc, sizeof (pack->filename));\n\tpack->handle = packhandle;\n\tpack->numfiles = numpackfiles;\n\tpack->files = newfiles;\n\tpack->filepos = 0;\n\tVFS_SEEK(packhandle, pack->filepos, SEEK_SET);\n\n\tpack->references++;\n\n\tCom_Printf(\"Added packfile %s (%i files)\\n\", desc, numpackfiles);\n\treturn pack;\n}\n\nsearchpathfuncs_t doomwadfilefuncs = {\n\tFSPAK_PrintPath,\n\tFSPAK_ClosePath,\n\tFSPAK_BuildHash,\n\tFSPAK_FLocate,\n\tFSOS_ReadFile,\n\tFSPAK_EnumerateFiles,\n\tFSPAK_LoadDoomWadFile,\n\tNULL,\n\tFSPAK_OpenVFS\n};\n\n\n#endif // DOOMWADS\n\n"
  },
  {
    "path": "src/vfs_gzip.c",
    "content": "/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n *     \n * $Id: vfs_gzip.c,v 1.12 2007-10-13 17:30:09 borisu Exp $\n *             \n */\n\n#ifdef WITH_ZLIB\n\n#include <zlib.h>\n#ifdef _WIN32\n#include <io.h>\n#endif // _WIN32\n#include \"quakedef.h\"\n#include \"hash.h\"\n#include \"common.h\"\n#include \"hash.h\"\n#include \"fs.h\"\n#include \"vfs.h\"\n\n//=============================================================================\n//                       G Z I P   V F S\n//=============================================================================\ntypedef struct gzipfile_s\n{\n\tchar filename[MAX_QPATH];\n\tvfsfile_t *handle;\n\n\tpackfile_t file; // Only one file can be stored in a gzip file\n\n\tunsigned long filepos;\n\tvfsfile_t *raw;\n\n\tint references;\n} gzipfile_t;\n\n\ntypedef struct \n{\n\tvfsfile_t funcs; // <= must be at top/begining of struct\n\n\tgzipfile_t *parent;\n\n\tunsigned long startpos;\n\tunsigned long length;\n\tunsigned long currentpos;\n} vfsgzipfile_t;\n\n// FIXME:\n// Everything below assumes that the input file was an OS file\n// This may not be the case if we are opening a gz file in a gz file...\n// The assumption is due to the minizip library not having hooks for\n// f* operations (fread, fwrite, fseek etc)\n//\n// This assumption may be possible to over come by using streams instead\n//\nstatic int VFSGZIP_ReadBytes(vfsfile_t *file, void *buffer, int bytestoread, vfserrno_t *err) \n{\n\tint r;\n\tvfsgzipfile_t *vfsgz = (vfsgzipfile_t *)file;\n\n\tif (bytestoread < 0)\n\t\t        Sys_Error(\"VFSGZIP_ReadBytes: bytestoread < 0\");\n\t\n\tr = gzread((gzFile)vfsgz->parent->handle, buffer, bytestoread);\n\t// r == -1 on error\n\n\tif (err) // if bytestoread <= 0 it will be treated as non error even we read zero bytes\n\t\t*err = ((r || bytestoread <= 0) ? VFSERR_NONE : VFSERR_EOF);\n\n\n\treturn r;\n}\n\nstatic int VFSGZIP_WriteBytes(vfsfile_t *file, const void *buffer, int bytestowrite) \n{\n\tint r;\n\tvfsgzipfile_t *vfsgz = (vfsgzipfile_t *)file;\n\n\tr = gzwrite((gzFile)vfsgz->parent->handle, buffer, bytestowrite);\n\n\t// r == 0 on error\n\t\n\treturn r;\n}\n\nstatic int VFSGZIP_Seek(vfsfile_t *file, unsigned long offset, int whence) \n{\n\tint r;\n\tvfsgzipfile_t *vfsgz = (vfsgzipfile_t *)file;\n\n\tr = gzseek((gzFile)vfsgz->parent->handle, offset, whence);\n\n\tif (r == -1)\n\t\treturn -1;\n\telse\n\t\treturn 0;\n}\n\nstatic unsigned long VFSGZIP_Tell(vfsfile_t *file) \n{\n\tint r;\n\tvfsgzipfile_t *vfsgz = (vfsgzipfile_t *)file;\n\n\tr = gztell((gzFile)vfsgz->parent->handle);\n\treturn r;\n}\n\nstatic unsigned long VFSGZIP_GetLen(vfsfile_t *file) \n{\n\tint r, currentpos;\n\tvfsgzipfile_t *vfsgz = (vfsgzipfile_t *)file;\n\t\n\t// VFS-FIXME: Error handling\n\tcurrentpos = gztell((gzFile)vfsgz->parent->handle);\n\tr = gzseek((gzFile)vfsgz->parent->handle, 0, SEEK_END);\n\tgzseek((gzFile)vfsgz->parent->handle, currentpos, SEEK_SET);\n\n\treturn r;\n}\n\nstatic void FSGZIP_ClosePath(void *handle);\nstatic void VFSGZIP_Close(vfsfile_t *file) \n{\n\tvfsgzipfile_t *vfsgz = (vfsgzipfile_t *)file;\n\n\tFSGZIP_ClosePath(vfsgz->parent);\n}\n\nstatic void VFSGZIP_Flush(vfsfile_t *file) \n{\n\tvfsgzipfile_t *vfsgz = (vfsgzipfile_t *)file;\n\n\t// gzflush((gzFile)vfsgz->parent->handle, Z_NO_FLUSH); \t// <-- Allows better compression\n\t(void) gzflush((gzFile)vfsgz->parent->handle, Z_SYNC_FLUSH); \t// <-- All pending output is flushed\n}\n\nstatic vfsfile_t *FSGZIP_OpenVFS(void *handle, flocation_t *loc, char *mode) \n{\n\tgzipfile_t *gzip = (gzipfile_t *)handle;\n\tvfsgzipfile_t *vfsgz;\n\n\tif (strcmp(mode, \"rb\"))\n\t\treturn NULL;\n\n\tvfsgz = Q_calloc(1, sizeof(*vfsgz));\n\tvfsgz->parent = gzip;\n\tgzip->references++;\n\n\tvfsgz->startpos   = loc->offset;\n\tvfsgz->length     = loc->len;\n\tvfsgz->currentpos = vfsgz->startpos;\n\n\tvfsgz->funcs.ReadBytes  = strcmp(mode, \"rb\") ? NULL : VFSGZIP_ReadBytes;\n\tvfsgz->funcs.WriteBytes = strcmp(mode, \"wb\") ? NULL : VFSGZIP_WriteBytes;\n\tvfsgz->funcs.Seek       = VFSGZIP_Seek;\n\tvfsgz->funcs.Tell       = VFSGZIP_Tell;\n\tvfsgz->funcs.GetLen     = VFSGZIP_GetLen;\n\tvfsgz->funcs.Close      = VFSGZIP_Close;\n\tvfsgz->funcs.Flush      = VFSGZIP_Flush;\n\tif (loc->search)\n\t\tvfsgz->funcs.copyprotected = loc->search->copyprotected;\n\n\treturn (vfsfile_t *)vfsgz;\n}\n\n\n//=============================================\n// GZIP file  (*.gz) - Search Functions\n//=============================================\nstatic void FSGZIP_PrintPath(void *handle)\n{\n\tgzipfile_t *gzip = (gzipfile_t *)handle;\n\n\tif (gzip->references != 1)\n\t\tCom_Printf(\"%s (%i)\\n\", gzip->filename, gzip->references-1);\n\telse\n\t\tCom_Printf(\"%s\\n\", gzip->filename);\n}\n\nstatic void FSGZIP_ClosePath(void *handle)\n{\n\tgzipfile_t *gzip = (gzipfile_t *)handle;\n\n\tif (--gzip->references > 0)\n\t\treturn; //not yet time\n\n\n\tgzclose((gzFile)gzip->handle);\n\tVFS_CLOSE(gzip->raw);\n\tQ_free(gzip);\n}\n\nstatic void FSGZIP_BuildHash(void *handle)\n{\n\tgzipfile_t *gzip = (gzipfile_t *)handle;\n\n\tif (!Hash_GetInsensitive(filesystemhash, gzip->file.name))\n\t{\n\t\tHash_AddInsensitive(filesystemhash, gzip->file.name, &gzip->file);\n\t\tfs_hash_files++;\n\t}\n\telse {\n\t\tfs_hash_dups++;\n\t}\n}\n\nstatic qbool FSGZIP_FLocate(void *handle, flocation_t *loc, const char *filename, void *hashedresult)\n{\n\tpackfile_t *pf = hashedresult;\n\tgzipfile_t *gzip = (gzipfile_t *)handle;\n\n\t// look through all the pak file elements\n\n\t//is this a pointer to a file in this pak?\n\tif (pf && pf != &(gzip->file)) {\n\t\treturn false;   //was found in a different path\n\t} else if (!strcmp (gzip->file.name, filename)) {\n\t\tpf = &gzip->file;\n\t}\n\n\tif (pf)\n\t{\n\t\tif (loc)\n\t\t{\n\t\t\tloc->index = 0;\n\t\t\tsnprintf(loc->rawname, sizeof(loc->rawname), \"%s/%s\", gzip->filename, filename);\n\t\t\tloc->offset = pf->filepos;\n\t\t\tloc->len = pf->filelen;\n\t\t}\n\t\treturn true;\n\t}\n\treturn false;\n\n}\n\nstatic void FSGZIP_ReadFile(void *handle, flocation_t *loc, char *buffer)\n{\n\tgzipfile_t *gzip = handle;\n\tint err;\n\n\tVFS_SEEK(gzip->handle, gzip->file.filepos, SEEK_SET);\n\n\terr = VFS_READ(gzip->handle, buffer, gzip->file.filelen, NULL);\n\n\tif (err!=gzip->file.filelen)\n\t{\n\t\tCom_Printf (\"Can't extract file \\\"%s:%s\\\" (corrupt)\\n\", gzip->filename, gzip->file.name);\n\t\treturn;\n\t}\n\treturn;\n}\n\nstatic int FSGZIP_EnumerateFiles (void *handle, char *match, int (*func)(char *, int, void *), void *parm)\n{\n\tgzipfile_t *gzip = handle;\n\n\tif (wildcmp(match, gzip->file.name))\n\t{\n\t\tif (!func(gzip->file.name, gzip->file.filelen, parm))\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n// =================\n// FSTAR_LoadGZipFile\n// =================\n// Takes an explicit (not game tree related) path to a gzip file.\n//\n// Loads the header and directory, adding the files at the beginning\n// of the list so they override previous pack files.\n\nstatic void *FSGZIP_LoadGZipFile(vfsfile_t *gziphandle, const char *desc)\n{\n\tgzipfile_t *gzip;\n\tconst char *base;\n\tchar *ext;\n\tint fd;\n\n\tgzip = Q_calloc(1, sizeof(*gzip));\n\tstrlcpy(gzip->filename, desc, sizeof(gzip->filename));\n\tif (gziphandle == NULL) goto fail;\n\tgzip->raw = gziphandle;\n\n\tfd = fileno(((vfsosfile_t *)gziphandle)->handle); // <-- ASSUMPTION! that file is OS\n\tgzip->handle = (vfsfile_t *)gzdopen(dup(fd), \"r\");\n\tgzip->references = 1;\n\n\t/* Remove the .gz from the file.name */\n\tbase = COM_SkipPath(desc);\n\text = COM_FileExtension(desc);\n\tif (strcmp(ext, \"gz\") == 0) {\n\t\tCOM_StripExtension(base, gzip->file.name, sizeof(gzip->file.name));\n\t} else {\n\t\tstrlcpy(gzip->file.name, base, sizeof(gzip->file.name));\n\t}\n\n\treturn gzip;\n\nfail:\n\t// Q_free is safe to call on NULL pointers\n\tQ_free(gzip);\n\treturn NULL;\n}\n\nsearchpathfuncs_t gzipfilefuncs = {\n\tFSGZIP_PrintPath,\n\tFSGZIP_ClosePath,\n\tFSGZIP_BuildHash,\n\tFSGZIP_FLocate,\n\tFSGZIP_ReadFile,\n\tFSGZIP_EnumerateFiles,\n\tFSGZIP_LoadGZipFile,\n\tNULL, // VFS-FIXME: Might be used for f_modification\n\tFSGZIP_OpenVFS \n};\n\n#endif // WITH_ZLIB\n"
  },
  {
    "path": "src/vfs_mmap.c",
    "content": "/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n *     \n * $Id: vfs_mmap.c,v 1.2 2007-10-10 17:30:43 dkure Exp $\n *             \n */\n\n//#ifdef WITH_VFS_MMAP\n\n#include \"quakedef.h\"\n#include \"hash.h\"\n#include \"common.h\"\n#include \"hash.h\"\n#include \"fs.h\"\n#include \"vfs.h\"\n\n//=============================================================================\n//                       M M A P    V F S\n//=============================================================================\n// These functions give a VFS layer to operations for a file in \n// mapped in memory (aka mmap)\n\ntypedef struct {\n\tvfsfile_t funcs; // <= must be at top/begining of struct\n\n\tbyte *handle;\n\tsize_t position;\n\tsize_t len;\n} vfsmmapfile_t;\n\nstatic int VFSMMAP_ReadBytes(vfsfile_t *file, void *buffer, int bytestoread_, vfserrno_t *err) \n{\n\tvfsmmapfile_t *intfile = (vfsmmapfile_t *)file;\n\tsize_t bytestoread = bytestoread_;\n\n\tif (bytestoread < 0) {\n\t\tSys_Error(\"VFSMMAP_ReadBytes: bytestoread < 0\");\n\t}\n\n\t/* Make sure we don't read past the valid memory */\n\tif ((bytestoread + intfile->position) > intfile->len) {\n\t\tbytestoread = max(0, intfile->len - intfile->position);\n\t}\n\n\t// Read.\n\tmemcpy(buffer, intfile->handle + intfile->position, bytestoread);\n\n\t// Advance in the file.\n\tintfile->position += bytestoread;\n\n\tif (err) {\n\t\t*err = (bytestoread == 0) ? VFSERR_EOF : VFSERR_NONE;\n\t}\n\n\t// safe cast: will be <= bytestoread_\n\treturn (int)bytestoread;\n}\n\nstatic int VFSMMAP_WriteBytes(vfsfile_t *file, const void *buffer, int bytestowrite) \n{\n\tvfsmmapfile_t *intfile = (vfsmmapfile_t *)file;\n\n\t/* Allocate more memory if we would overflow */\n\tif (bytestowrite + intfile->position > intfile->len) {\n\t\tsize_t newlen  = bytestowrite + intfile->position;\n\t\tvoid *p = Q_realloc(intfile->handle, newlen);\n\t\tif (!p) {\n\t\t\tCom_Printf(\"VFSMMAP_WriteBytes: Unable to write to file, memory full\\n\");\n\t\t\treturn 0;\n\t\t} \n\t\tintfile->handle = p;\n\t}\n\n\tmemcpy(intfile->handle + intfile->position, buffer, bytestowrite);\n\tintfile->position += bytestowrite;\n\n\treturn bytestowrite;\n}\n\nstatic int VFSMMAP_Seek(vfsfile_t *file, unsigned long offset, int whence) \n{\n\tvfsmmapfile_t *intfile = (vfsmmapfile_t *)file;\n\n\tswitch(whence) \n\t{\n\t\tcase SEEK_SET: \n\t\t\tintfile->position = offset;\n\t\t\tbreak;\n\t\tcase SEEK_CUR: \n\t\t\tintfile->position += offset;\n\t\t\tbreak;\n\t\tcase SEEK_END: \n\t\t\tintfile->position = intfile->len + offset; \n\t\t\tbreak;\n\t\tdefault:\n\t\t\tSys_Error(\"VFSMMAP_Seek: Unknown whence value(%d)\\n\", whence);\n\t\t\treturn -1;\n\t}\n\n\t// We seeked outside the bounds.\n\tif (intfile->position > intfile->len)\n\t{\n\t\t// Be sure we don't have an invalid file position. (Should we do this?)\n\t\tclamp(intfile->position, 0, intfile->len);\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic unsigned long VFSMMAP_Tell(vfsfile_t *file) \n{\n\tvfsmmapfile_t *intfile = (vfsmmapfile_t *)file;\n\n\treturn (unsigned long)intfile->position;\n}\n\nstatic unsigned long VFSMMAP_GetLen(vfsfile_t *file) \n{\n\tvfsmmapfile_t *intfile = (vfsmmapfile_t *)file;\n\t\n\treturn (unsigned long)intfile->len;\n}\n\nstatic void VFSMMAP_Close(vfsfile_t *file) \n{\n\tvfsmmapfile_t *intfile = (vfsmmapfile_t *)file;\n\n\tQ_free(intfile->handle);\n\tQ_free(intfile);\n}\n\nstatic void VFSMMAP_Flush(vfsfile_t *file) \n{\n\tvfsmmapfile_t *intfile = (vfsmmapfile_t *)file;\n\n\t(void)intfile;\n\tSys_Error(\"VFSMMAP_Flush: Invalid operation\\n\");\n}\n\nvfsfile_t *FSMMAP_OpenVFS(void *buf, size_t buf_len) \n{\n\tvfsmmapfile_t *mmapfile = Q_calloc(1, sizeof(*mmapfile));\n\n\tmmapfile->handle           = buf;\n\tmmapfile->len              = buf_len;\n\tmmapfile->funcs.ReadBytes  = VFSMMAP_ReadBytes;\n\tmmapfile->funcs.WriteBytes = VFSMMAP_WriteBytes;\n\tmmapfile->funcs.Seek       = VFSMMAP_Seek;\n\tmmapfile->funcs.Tell       = VFSMMAP_Tell;\n\tmmapfile->funcs.GetLen     = VFSMMAP_GetLen;\n\tmmapfile->funcs.Close      = VFSMMAP_Close;\n\tmmapfile->funcs.Flush      = VFSMMAP_Flush;\n\n\treturn (vfsfile_t *)mmapfile;\n}\n\nqbool FSMMAP_IsMemoryMapped(vfsfile_t* file)\n{\n\treturn file && file->ReadBytes == VFSMMAP_ReadBytes;\n}\n\n//#endif // WITH_VFS_MMAP\n"
  },
  {
    "path": "src/vfs_os.c",
    "content": "/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n *     \n * $Id: vfs_os.c,v 1.11 2007/10/10 17:30:43 dkure Exp $\n *             \n */\n\n#include \"quakedef.h\"\n#include \"hash.h\"\n#include \"common.h\"\n#include \"fs.h\"\n#include \"vfs.h\"\n\n//==================================\n// STDIO files (OS) - VFS Functions\n//==================================\nstatic int VFSOS_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread, vfserrno_t *err)\n{\n\tsize_t r;\n\tvfsosfile_t *intfile = (vfsosfile_t*)file;\n\n\tif (bytestoread < 0)\n\t\tSys_Error(\"VFSOS_ReadBytes: bytestoread < 0\"); // ffs\n\n\tr = fread(buffer, 1, bytestoread, intfile->handle);\n\n\tif (err) // if bytestoread <= 0 it will be treated as non error even we read zero bytes\n\t\t*err = ((r || bytestoread <= 0) ? VFSERR_NONE : VFSERR_EOF);\n\n\treturn (int)r;\n}\n\nstatic int VFSOS_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestowrite)\n{\n\tvfsosfile_t *intfile = (vfsosfile_t*)file;\n\treturn (int)fwrite(buffer, 1, bytestowrite, intfile->handle);\n}\n\nstatic int VFSOS_Seek (struct vfsfile_s *file, unsigned long pos, int whence)\n{\n\tvfsosfile_t *intfile = (vfsosfile_t*)file;\n\treturn (int)fseek(intfile->handle, pos, whence);\n}\n\nstatic unsigned long VFSOS_Tell (struct vfsfile_s *file)\n{\n\tvfsosfile_t *intfile = (vfsosfile_t*)file;\n\treturn ftell(intfile->handle);\n}\n\nstatic unsigned long VFSOS_GetSize (struct vfsfile_s *file)\n{\n\tvfsosfile_t *intfile = (vfsosfile_t*)file;\n\n\tunsigned long curpos, maxlen;\n\n\t// FIXME: add error checks here?\n\n\tcurpos = ftell(intfile->handle);\n\tfseek(intfile->handle, 0, SEEK_END);\n\tmaxlen = ftell(intfile->handle);\n\tfseek(intfile->handle, curpos, SEEK_SET);\n\n\treturn maxlen;\n}\n\nstatic void VFSOS_Close(vfsfile_t *file)\n{\n\tvfsosfile_t *intfile = (vfsosfile_t*)file;\n\tfclose(intfile->handle);\n\tQ_free(file);\n}\n\nvfsfile_t *FS_OpenTemp(void)\n{\n\tFILE *f;\n\tvfsosfile_t *file;\n\n\tf = tmpfile();\n\tif (!f)\n\t\treturn NULL;\n\n\tfile = Q_calloc(1, sizeof(vfsosfile_t));\n\n\tfile->funcs.ReadBytes\t= VFSOS_ReadBytes;\n\tfile->funcs.WriteBytes\t= VFSOS_WriteBytes;\n\tfile->funcs.Seek\t\t= VFSOS_Seek;\n\tfile->funcs.Tell\t\t= VFSOS_Tell;\n\tfile->funcs.GetLen\t\t= VFSOS_GetSize;\n\tfile->funcs.Close\t\t= VFSOS_Close;\n\n\tfile->handle = f;\n\n\treturn (vfsfile_t*)file;\n}\n\n// VFS-XXX: This is slightly different to fs.c version in that we don't take in a FILE *f\nvfsfile_t *VFSOS_Open(char *osname, char *mode)\n{\n\tFILE *f;\n\tvfsosfile_t *file;\n\tqbool read   = !!strchr(mode, 'r');\n\tqbool write  = !!strchr(mode, 'w');\n\tqbool append = !!strchr(mode, 'a');\n\tqbool text   = !!strchr(mode, 't');\n\tchar newmode[10];\n\tint modec = 0;\n\n\tif (read)\n\t\tnewmode[modec++] = 'r';\n\tif (write)\n\t\tnewmode[modec++] = 'w';\n\tif (append)\n\t\tnewmode[modec++] = 'a';\n\tif (text)\n\t\tnewmode[modec++] = 't';\n\telse\n\t\tnewmode[modec++] = 'b';\n\tnewmode[modec++] = '\\0';\n\n\tf = fopen(osname, newmode);\n\tif (!f)\n\t\treturn NULL;\n\n\tfile = Q_calloc_named(1, sizeof(vfsosfile_t), osname);\n\n\tfile->funcs.ReadBytes  = ( strchr(mode, 'r')                      ? VFSOS_ReadBytes  : NULL);\n\tfile->funcs.WriteBytes = ((strchr(mode, 'w') || strchr(mode, 'a'))? VFSOS_WriteBytes : NULL);\n\tfile->funcs.Seek       = VFSOS_Seek;\n\tfile->funcs.Tell       = VFSOS_Tell;\n\tfile->funcs.GetLen     = VFSOS_GetSize;\n\tfile->funcs.Close      = VFSOS_Close;\n\n\tfile->handle = f;\n\n\treturn (vfsfile_t*)file;\n}\n\n//==================================\n// STDIO files (OS) - Search functions\n//==================================\nvfsfile_t *FSOS_OpenVFS(void *handle, flocation_t *loc, char *mode)\n{\n\tchar diskname[MAX_OSPATH];\n\n\tsnprintf(diskname, sizeof(diskname), \"%s/%s\", (char*)handle, loc->rawname);\n\n\treturn VFSOS_Open(diskname, mode);\n}\n\nstatic void FSOS_PrintPath(void *handle)\n{\n\tCom_Printf(\"%s\\n\", handle);\n}\n\nstatic void FSOS_ClosePath(void *handle)\n{\n\tQ_free(handle);\n}\n\nstatic int FSOS_RebuildFSHash(char *filename, int filesize, void *data)\n{\n\tif (filename[strlen(filename)-1] == '/')\n\t{\t//this is actually a directory\n\n\t\tchar childpath[256];\n\t\tsnprintf(childpath, sizeof (childpath), \"%s*\", filename);\n\t\tSys_EnumerateFiles((char*)data, childpath, FSOS_RebuildFSHash, data);\n\t\treturn true;\n\t}\n\tif (!Hash_GetInsensitive(filesystemhash, filename))\n\t{\n\t\tHash_AddInsensitive(filesystemhash, filename, data);\n\t\tfs_hash_files++;\n\t}\n\telse\n\t\tfs_hash_dups++;\n\treturn true;\n}\n\nstatic void FSOS_BuildHash(void *handle)\n{\n\tSys_EnumerateFiles(handle, \"*\", FSOS_RebuildFSHash, handle);\n}\n\nstatic qbool FSOS_FLocate(void *handle, flocation_t *loc, const char *filename, void *hashedresult)\n{\n\tFILE *f;\n\tint len;\n\tchar netpath[MAX_OSPATH];\n\n\n\tif (hashedresult && (void *)hashedresult != handle)\n\t\treturn false;\n\n/*\n\tif (!static_registered)\n\t{\t// if not a registered version, don't ever go beyond base\n\t\tif ( strchr (filename, '/') || strchr (filename,'\\\\'))\n\t\t\tcontinue;\n\t}\n*/\n\n// check a file in the directory tree\n\tsnprintf (netpath, sizeof(netpath)-1, \"%s/%s\",(char*)handle, filename);\n\n\t// VFS-FIXME: This could be optimised to only do this once and save the result!\n\tf = fopen(netpath, \"rb\");\n\tif (!f)\n\t\treturn false;\n\n\tfseek(f, 0, SEEK_END);\n\tlen = ftell(f);\n\tfclose(f);\n\tif (loc)\n\t{\n\t\tloc->len = len;\n\t\tloc->offset = 0;\n\t\tloc->index = 0;\n\t\tstrlcpy (loc->rawname, filename, sizeof (loc->rawname));\n\t}\n\n\treturn true;\n}\n\nvoid FSOS_ReadFile(void *handle, flocation_t *loc, char *buffer)\n{\n\tFILE *f;\n\tint read;\n\n\tf = fopen(loc->rawname, \"rb\");\n\tif (!f)\t//err...\n\t\treturn;\n\tfseek(f, loc->offset, SEEK_SET);\n\tread = (int)fread(buffer, 1, loc->len, f);\n\tif (read != loc->len) {\n\t\tCom_Printf (\"Can't read file \\\"%s\\\" (%d bytes, expected %d)\\n\", loc->rawname, read, loc->len);\n\t}\n\tfclose(f);\n}\n\nstatic int FSOS_EnumerateFiles (void *handle, char *match, int (*func)(char *, int, void *), void *parm)\n{\n\treturn Sys_EnumerateFiles(handle, match, func, parm);\n}\n\nsearchpathfuncs_t osfilefuncs = {\n\tFSOS_PrintPath,\n\tFSOS_ClosePath,\n\tFSOS_BuildHash,\n\tFSOS_FLocate,\n\tFSOS_ReadFile,\n\tFSOS_EnumerateFiles,\n\tNULL,\n\tNULL,\n\tFSOS_OpenVFS\n};\n"
  },
  {
    "path": "src/vfs_pak.c",
    "content": "/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n *     \n * $Id: vfs_pak.c,v 1.17 2007-10-13 16:02:51 dkure Exp $\n *             \n */\n\n#include \"quakedef.h\"\n#include \"hash.h\"\n#include \"common.h\"\n#include \"fs.h\"\n#include \"vfs.h\"\n\n// on disk\ntypedef struct pack_s\n{\n\tchar    filename[MAX_OSPATH];\n\tvfsfile_t    *handle;\n\tunsigned int filepos;   // the pos the subfiles left it at \n\t\t\t\t\t\t\t// (to optimize calls to vfs_seek)\n\tint references;         // seeing as all vfiles from a pak file use the \n\t\t\t\t\t\t\t// parent's vfsfile, we need to keep the parent \n\t\t\t\t\t\t\t// open until all subfiles are closed.\n\n\tint     numfiles;\n\tpackfile_t  *files;\n} pack_t;\n\ntypedef struct\n{\n\tchar    name[56];\n\tint     filepos, filelen;\n} dpackfile_t;\n\ntypedef struct\n{\n\tchar    id[4];\n\tint     dirofs;\n\tint     dirlen;\n} dpackheader_t;\n\ntypedef struct {\n    vfsfile_t funcs; // <= must be at top/begining of struct\n\tpack_t *parentpak;\n    unsigned long startpos;\n    unsigned long length;\n    unsigned long currentpos;\n} vfspack_t;\n\n#define\tMAX_FILES_IN_PACK\t2048\n\n//=====================================\n//PACK files (*.pak) - VFS functions\n//=====================================\nstatic int VFSPAK_ReadBytes (struct vfsfile_s *vfs, void *buffer, int bytestoread, vfserrno_t *err)\n{\n\tvfspack_t *vfsp = (vfspack_t*)vfs;\n\tint read;\n\n\tif (vfsp->currentpos - vfsp->startpos + bytestoread > vfsp->length)\n\t\tbytestoread = vfsp->length - (vfsp->currentpos - vfsp->startpos);\n\tif (bytestoread <= 0)\n\t\treturn -1;\n\n\tif (vfsp->parentpak->filepos != vfsp->currentpos) {\n\t\tVFS_SEEK(vfsp->parentpak->handle, vfsp->currentpos, SEEK_SET);\n\t}\n\n\tread = VFS_READ(vfsp->parentpak->handle, buffer, bytestoread, err);\n\tvfsp->currentpos += read;\n\tvfsp->parentpak->filepos = vfsp->currentpos;\n\n\t// VFS-FIXME: Need to handle errors\n\n\treturn read;\n}\nstatic int VFSPAK_WriteBytes (struct vfsfile_s *vfs, const void *buffer, int bytestoread)\n{\t//not supported.\n\tSys_Error(\"VFSPAK_WriteBytes: Cannot write to pak files\");\n\treturn 0;\n}\n\nstatic int VFSPAK_Seek (struct vfsfile_s *vfs, unsigned long offset, int whence)\n{\n\tvfspack_t *vfsp = (vfspack_t*)vfs;\n\tint rel_offset;\n\n\t// VFS-FIXME Support other whence types\n\tswitch(whence) {\n\tcase SEEK_SET: \n\t\tvfsp->currentpos = vfsp->startpos + offset; \n\t\tbreak;\n\tcase SEEK_CUR: \n\t\tvfsp->currentpos += offset; \n\t\tbreak;\n\tcase SEEK_END: \n\t\tvfsp->currentpos = vfsp->startpos + vfsp->length + offset;\n\t\tbreak;\n\tdefault:\n\t\tSys_Error(\"VFSTAR_Seek: Unknown whence value(%d)\\n\", whence);\n\t\treturn -1;\n\t}\n\n\trel_offset = vfsp->currentpos - vfsp->startpos;\n\tif (rel_offset < 0 || rel_offset >= vfsp->length) {\n\t\tCom_Printf(\"VFSPAK_Seek: Warning seeking past the file's size\\n\");\n\t}\n\n\treturn 0;\n}\n\nstatic unsigned long VFSPAK_Tell (struct vfsfile_s *vfs)\n{\n\tvfspack_t *vfsp = (vfspack_t*)vfs;\n\treturn vfsp->currentpos - vfsp->startpos;\n}\n\nstatic unsigned long VFSPAK_GetLen (struct vfsfile_s *vfs)\n{\n\tvfspack_t *vfsp = (vfspack_t*)vfs;\n\treturn vfsp->length;\n}\n\nstatic void FSPAK_ClosePath(void *handle);\nstatic void VFSPAK_Close(vfsfile_t *vfs)\n{\n\tvfspack_t *vfsp = (vfspack_t*)vfs;\n\tFSPAK_ClosePath(vfsp->parentpak);\t// tell the parent that we don't need it open any \n\t\t\t\t\t\t\t\t\t\t// more (reference counts)\n\tQ_free(vfsp);\n}\n\nstatic vfsfile_t *FSPAK_OpenVFS(void *handle, flocation_t *loc, char *mode)\n{\n\tpack_t *pack = (pack_t*)handle;\n\tvfspack_t *vfsp;\n\n\tif (strcmp(mode, \"rb\"))\n\t\treturn NULL; //urm, unable to write/append\n\n\tvfsp = Q_calloc(1, sizeof(*vfsp));\n\n\tvfsp->parentpak = pack;\n\tvfsp->parentpak->references++;\n\n\tvfsp->startpos   = loc->offset;\n\tvfsp->length     = loc->len;\n\tvfsp->currentpos = vfsp->startpos;\n\n\tvfsp->funcs.ReadBytes     = strcmp(mode, \"rb\") ? NULL : VFSPAK_ReadBytes;\n\tvfsp->funcs.WriteBytes    = strcmp(mode, \"wb\") ? NULL : VFSPAK_WriteBytes;\n\tvfsp->funcs.Seek\t\t  = VFSPAK_Seek;\n\tvfsp->funcs.Tell\t\t  = VFSPAK_Tell;\n\tvfsp->funcs.GetLen\t      = VFSPAK_GetLen;\n\tvfsp->funcs.Close\t      = VFSPAK_Close;\n\tvfsp->funcs.Flush         = NULL;\n\tif (loc->search)\n\t\tvfsp->funcs.copyprotected = loc->search->copyprotected;\n\n\treturn (vfsfile_t *)vfsp;\n}\n\n//======================================\n// PACK files (*.pak) - Search functions\n//======================================\nstatic void FSPAK_PrintPath(void *handle)\n{\n\tpack_t *pak = handle;\n\n\tif (pak->references != 1)\n\t\tCom_Printf(\"%s (%i)\\n\", pak->filename, pak->references-1);\n\telse\n\t\tCom_Printf(\"%s\\n\", pak->filename);\n}\n\nstatic void FSPAK_ClosePath(void *handle)\n{\n\tpack_t *pak = handle;\n\n\tpak->references--;\n\tif (pak->references > 0)\n\t\treturn;\t//not free yet\n\n\tVFS_CLOSE (pak->handle);\n\tif (pak->files)\n\t\tQ_free(pak->files);\n\tQ_free(pak);\n}\n\nstatic void FSPAK_BuildHash(void *handle)\n{\n\tpack_t *pak = handle;\n\tint i;\n\n\tfor (i = 0; i < pak->numfiles; i++)\n\t{\n\t\tif (!Hash_GetInsensitive(filesystemhash, pak->files[i].name))\n\t\t{\n\t\t\tHash_AddInsensitive(filesystemhash, pak->files[i].name, &pak->files[i]);\n\t\t\tfs_hash_files++;\n\t\t}\n\t\telse\n\t\t\tfs_hash_dups++;\n\t}\n}\n\nstatic qbool FSPAK_FLocate(void *handle, flocation_t *loc, const char *filename, void *hashedresult)\n{\n\tpackfile_t *pf = hashedresult;\n\tint i;\n\tpack_t\t\t*pak = handle;\n\n// look through all the pak file elements\n\n\tif (pf)\n\t{\t//is this a pointer to a file in this pak?\n\t\tif (pf < pak->files || pf > pak->files + pak->numfiles)\n\t\t\treturn false;\t//was found in a different path\n\t}\n\telse\n\t{\n\t\tfor (i=0 ; i<pak->numfiles ; i++)\t//look for the file\n\t\t{\n\t\t\tif (!strcmp (pak->files[i].name, filename))\n\t\t\t{\n\t\t\t\tpf = &pak->files[i];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (pf)\n\t{\n\t\tif (loc)\n\t\t{\n\t\t\tloc->index = pf - pak->files;\n\t\t\tsnprintf(loc->rawname, sizeof(loc->rawname), \"%s/%s\", pak->filename, filename);\n\t\t\tloc->offset = pf->filepos;\n\t\t\tloc->len = pf->filelen;\n\t\t}\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic int FSPAK_EnumerateFiles (void *handle, char *match, int (*func)(char *, int, void *), void *parm)\n{\n\tpack_t\t*pak = handle;\n\tint\t\tnum;\n\n\tfor (num = 0; num<(int)pak->numfiles; num++)\n\t{\n\t\tif (wildcmp(match, pak->files[num].name))\n\t\t{\n\t\t\tif (!func(pak->files[num].name, pak->files[num].filelen, parm))\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n=================\nFSPAK_LoadPackFile\n\nTakes an explicit (not game tree related) path to a pak file.\n\nLoads the header and directory, adding the files at the beginning\nof the list so they override previous pack files.\n=================\n*/\nstatic void *FSPAK_LoadPackFile (vfsfile_t *file, const char *desc)\n{\n\tdpackheader_t\theader;\n\tint\t\t\t\ti;\n\tpackfile_t\t\t*newfiles;\n\tint\t\t\t\tnumpackfiles;\n\tpack_t\t\t\t*pack;\n\tvfsfile_t\t\t*packhandle;\n\tdpackfile_t\t\tinfo;\n\tvfserrno_t err;\n\n\tpackhandle = file;\n\tif (packhandle == NULL)\n\t\treturn NULL;\n\n\tVFS_READ(packhandle, &header, sizeof(header), &err);\n\tif (header.id[0] != 'P' || header.id[1] != 'A'\n\t|| header.id[2] != 'C' || header.id[3] != 'K')\n\t{\n\t\treturn NULL;\n\t}\n\theader.dirofs = LittleLong (header.dirofs);\n\theader.dirlen = LittleLong (header.dirlen);\n\n\tnumpackfiles = header.dirlen / sizeof(dpackfile_t);\n\n//\tif (numpackfiles != PAK0_COUNT)\n//\t\tcom_modified = true;\t// not the original file\n\n\tnewfiles = (packfile_t*)Q_malloc (numpackfiles * sizeof(packfile_t));\n\n\tVFS_SEEK(packhandle, header.dirofs, SEEK_SET);\n//\tfread (&info, 1, header.dirlen, packhandle);\n\n// crc the directory to check for modifications\n//\tcrc = QCRC_Block((qbyte *)info, header.dirlen);\n\n\n//\tQCRC_Init (&crc);\n\n\tpack = (pack_t *)Q_calloc(1, sizeof (pack_t));\n\n// parse the directory\n\tfor (i=0 ; i<numpackfiles ; i++)\n\t{\n\t\t*info.name = '\\0';\n\t\tVFS_READ(packhandle, &info, sizeof(info), &err);\n/*\n\t\tfor (j=0 ; j<sizeof(info) ; j++)\n\t\t\tCRC_ProcessByte(&crc, ((qbyte *)&info)[j]);\n*/\n\t\tstrlcpy (newfiles[i].name, info.name, MAX_QPATH);\n\t\tnewfiles[i].filepos = LittleLong(info.filepos);\n\t\tnewfiles[i].filelen = LittleLong(info.filelen);\n\t}\n/*\n\tif (crc != PAK0_CRC)\n\t\tcom_modified = true;\n*/\n\tstrlcpy (pack->filename, desc, sizeof (pack->filename));\n\tpack->handle = packhandle;\n\tpack->numfiles = numpackfiles;\n\tpack->files = newfiles;\n\tpack->filepos = 0;\n\tVFS_SEEK(packhandle, pack->filepos, SEEK_SET);\n\n\tpack->references++;\n\n\treturn pack;\n}\n\nextern void FSOS_ReadFile(void *handle, flocation_t *loc, char *buffer);\n\nsearchpathfuncs_t packfilefuncs = {\n\tFSPAK_PrintPath,\n\tFSPAK_ClosePath,\n\tFSPAK_BuildHash,\n\tFSPAK_FLocate,\n\tFSOS_ReadFile,\n\tFSPAK_EnumerateFiles,\n\tFSPAK_LoadPackFile,\n\tNULL,\n\tFSPAK_OpenVFS\n};\n"
  },
  {
    "path": "src/vfs_tar.c",
    "content": "/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n *     \n * $Id: vfs_tar.c,v 1.8 2007-10-13 16:02:51 dkure Exp $\n *             \n */\n\n#include \"quakedef.h\"\n#include \"hash.h\"\n#include \"common.h\"\n#include \"hash.h\"\n#include \"fs.h\"\n#include \"vfs.h\"\n#include \"vfs_tar.h\"\n\n//=============================================================================\n//                       T A R    V F S\n//=============================================================================\n// These functions give a VFS layer to operations for a file tar file \n\ntypedef struct tarfile_s\n{\n\tchar filename[MAX_QPATH];\n\thashtable_t hash;\n\tvfsfile_t *raw;\n\n\tint numfiles;\n\tpackfile_t  *files;\n\n\tunsigned long filepos;\n\n\tint references;\n} tarfile_t;\n\ntypedef struct \n{\n\tvfsfile_t funcs; // <= must be at top/begining of struct\n\n\ttarfile_t *parent;\n\n\tunsigned long startpos;\n\tunsigned long length;\n\tunsigned long currentpos;\n} vfstarfile_t;\n\n// convert octal digits to int\n// on error return -1\nstatic int getoct (char *p,int width)\n{\n\tint result = 0;\n\tchar c;\n\n\twhile (width--)\n\t{\n\t\tc = *p++;\n\t\tif (c == 0)\n\t\t\tbreak;\n\t\tif (c == ' ')\n\t\t\tcontinue;\n\t\tif (c < '0' || c > '7')\n\t\t\treturn -1;\n\t\tresult = result * 8 + (c - '0');\n\t}\n\treturn result;\n}\n\n// =====================\n// tarOperationGetFiles\n// =====================\n// Returns the number of files found\n// Placing the names in files if a valid pointer\n//\n// -1 is returned on error\n\nstatic int tarOperationIndexFiles(vfsfile_t *in, packfile_t *files) {\n\t// Tar vars\n\tunion  tar_buffer buffer;\n\tint    len;\n\tint    getheader = HEADER_SHORTNAME;\n\tint    remaining = 0;\n\tchar   fname[BLOCKSIZE];\n\t// Quake vars\n\tint numfiles = 0;\n\n\tVFS_SEEK(in, 0, SEEK_SET); /* Read the file from the begining */\n\n\twhile (1)\n\t{\n\t\tlen = VFS_READ(in, &buffer, BLOCKSIZE, NULL);\n\t\tif (len < 0 || len != BLOCKSIZE) {\n\t\t\tCom_Printf(\"Invalid tar file\\n\");\n\t\t\treturn -1;\n\t\t}\n\n\t\t// We have reached the end of the tar \n\t\tif (len == 0 || buffer.header.name[0] == 0)\n\t\t\tbreak;\n\n\t\tif (getheader == HEADER_SHORTNAME)\n\t\t{\n\t\t\tstrlcpy(fname,buffer.header.name,sizeof(fname));\n\t\t\tif (fname[SHORTNAMESIZE-1] != 0)\n\t\t\t\tfname[SHORTNAMESIZE] = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t/* The file name is longer than SHORTNAMESIZE */\n\t\t\tif (strncmp(fname,buffer.header.name,SHORTNAMESIZE-1) != 0) {\n\t\t\t\tCom_Printf(\"bad long name\\n\");\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tgetheader = HEADER_SHORTNAME;\n\t\t}\n\n\t\t/* Act according to the type flag */\n\t\tswitch (buffer.header.typeflag)\n\t\t{\n\t\t\tcase DIRTYPE:\n\t\t\t\tbreak;\n\t\t\tcase REGTYPE:\n\t\t\tcase AREGTYPE:\n\t\t\t\tremaining = getoct(buffer.header.size,12);\n\t\t\t\tif (remaining == -1)\n\t\t\t\t{\n\t\t\t\t\tCom_Printf(\"Invalid tar file\\n\");\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\n\t\t\t\t//Com_Printf(\" %9d %s\\n\",remaining,fname);\n\n\t\t\t\tif (files) {\n\t\t\t\t\tstrlcpy(files[numfiles].name, fname, sizeof(files[numfiles].name));\n\t\t\t\t\tfiles[numfiles].filelen = remaining;\n\t\t\t\t\tfiles[numfiles].filepos = VFS_TELL(in);\n\t\t\t\t}\n\t\t\t\tnumfiles++;\n\n\t\t\t\tgetheader = HEADER_NONE;\n\t\t\t\tbreak;\n\t\t\tcase GNUTYPE_LONGLINK:\n\t\t\tcase GNUTYPE_LONGNAME:\n\t\t\t\t/* FIXME: Handle long names */\n\t\t\t\tremaining = getoct(buffer.header.size,12);\n\t\t\t\tif (remaining < 0 || remaining >= BLOCKSIZE)\n\t\t\t\t{\n\t\t\t\t\tprintf(\"Invalid tar file\\n\");\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\tlen = VFS_READ(in, fname, BLOCKSIZE, NULL);\n\t\t\t\tif (len < 0) {\n\t\t\t\t\tprintf(\"Problem reading blocksize\");\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\tif (fname[BLOCKSIZE-1] != 0 || (int)strlen(fname) > remaining)\n\t\t\t\t{\n\t\t\t\t\tprintf(\"Invalid tar file\\n\");\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\tgetheader = HEADER_LONGNAME;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tprintf(\"      <---> %s\\n\",fname);\n\t\t\t\tnumfiles++;\n\t\t\t\tbreak;\n\t\t}\n\n\t\t/* Seek the next valid header which contains file names */\n\t\tif (getheader == HEADER_NONE) {\n\t\t\tunsigned int offset; \n\n\t\t\t/* Round up to the nearest BLOCKSIZE */\n\t\t\tif (remaining % BLOCKSIZE) {\n\t\t\t\toffset = remaining + (BLOCKSIZE - (remaining % BLOCKSIZE));\n\t\t\t} else {\n\t\t\t\toffset = remaining;\n\t\t\t}\n\n\t\t\tVFS_SEEK(in, offset, SEEK_CUR);\n\t\t\tgetheader = HEADER_SHORTNAME;\n\t\t}\n\t}\n\n\n\treturn numfiles;\n}\n\n//=============================================================================\n\nstatic int VFSTAR_ReadBytes(vfsfile_t *file, void *buffer, int bytestoread, vfserrno_t *err) \n{\n\tvfstarfile_t *vfst = (vfstarfile_t *)file;\n\tint read;\n\t//unsigned long base, bytes; <-- For reading in multiples of BLOCKSIZE\n\n\t// VFS-FIXME: Need to read in chunks of BLOCKSIZE\n\tif (vfst->currentpos - vfst->startpos + bytestoread > vfst->length)\n\t\tbytestoread = vfst->length - (vfst->currentpos - vfst->startpos);\n\n\tif (bytestoread < 0)\n\t\tSys_Error(\"VFSTAR_ReadBytes: bytestoread < 0\");\n\n\tif (vfst->parent->filepos != vfst->currentpos) {\n\t\tVFS_SEEK(vfst->parent->raw, vfst->currentpos, SEEK_SET);\n\t}\n\n\tread = VFS_READ(vfst->parent->raw, buffer, bytestoread, NULL);\n\tvfst->currentpos += read;\n\tvfst->parent->filepos = vfst->currentpos;\n\n\t// VFS-FIXME: Need to figure out errors\n\tif (err) \n\t\t*err = bytestoread == 0 ? VFSERR_EOF : VFSERR_NONE;\n\n\treturn read;\n}\n\nstatic int VFSTAR_WriteBytes(vfsfile_t *file, const void *buffer, int bytestowrite) \n{\n\tSys_Error(\"VFSTAR_WriteBytes: Invalid operatioin\\n\");\n\treturn 0;\n}\n\nstatic int VFSTAR_Seek(vfsfile_t *file, unsigned long offset, int whence) \n{\n\tvfstarfile_t *vfst = (vfstarfile_t *)file;\n\n\tswitch(whence) {\n\tcase SEEK_SET: \n\t\tvfst->currentpos = vfst->startpos + offset; \n\t\tbreak;\n\tcase SEEK_CUR: \n\t\tvfst->currentpos += offset; \n\t\tbreak;\n\tcase SEEK_END: \n\t\tvfst->currentpos = vfst->startpos + vfst->length + offset;\n\t\tbreak;\n\tdefault:\n\t\tSys_Error(\"VFSTAR_Seek: Unknown whence value(%d)\\n\", whence);\n\t\treturn -1;\n\t}\n\n\tif (vfst->currentpos > vfst->length) {\n\t\tCom_Printf(\"VFSTAR_Seek: Warning seeking past the file's size\\n\");\n\t}\n\n\treturn 0;\n}\n\nstatic unsigned long VFSTAR_Tell(vfsfile_t *file) \n{\n\tvfstarfile_t *vfst = (vfstarfile_t *)file;\n\n\treturn vfst->currentpos - vfst->startpos;;\n}\n\nstatic unsigned long VFSTAR_GetLen(vfsfile_t *file) \n{\n\tvfstarfile_t *vfst = (vfstarfile_t *)file;\n\t\n\treturn vfst->length;\n}\n\nstatic void FSTAR_ClosePath(void *handle);\nstatic void VFSTAR_Close(vfsfile_t *file) \n{\n\tvfstarfile_t *vfst = (vfstarfile_t *)file;\n\n\tFSTAR_ClosePath(vfst->parent);\n\tQ_free(vfst);\n}\n\nstatic void VFSTAR_Flush(vfsfile_t *file) \n{\n\tSys_Error(\"VFSTAR_Flush: Invalid operation\\n\");\n}\n\nstatic vfsfile_t *FSTAR_OpenVFS(void *handle, flocation_t *loc, char *mode) \n{\n\ttarfile_t *tar = (tarfile_t *) handle;\n\tvfstarfile_t *vfst;\n\t\n\tif (strcmp(mode, \"rb\"))\n\t\treturn NULL;\n\n\tvfst = Q_calloc(1, sizeof(*vfst));\n\tvfst->parent = tar;\n\tvfst->parent->references++;\n\n\tvfst->startpos   = loc->offset;\n\tvfst->length     = loc->len;\n\tvfst->currentpos = vfst->startpos;\n\n\tvfst->funcs.ReadBytes  = VFSTAR_ReadBytes;\n\tvfst->funcs.WriteBytes = VFSTAR_WriteBytes;\n\tvfst->funcs.Seek       = VFSTAR_Seek;\n\tvfst->funcs.Tell       = VFSTAR_Tell;\n\tvfst->funcs.GetLen     = VFSTAR_GetLen;\n\tvfst->funcs.Close      = VFSTAR_Close;\n\tvfst->funcs.Flush      = VFSTAR_Flush;\n\tif (loc->search)\n\t\tvfst->funcs.copyprotected = loc->search->copyprotected;\n\n\treturn (vfsfile_t *)vfst;\n}\n\n//=============================================\n// TAR file  (*.tar) - Search Functions\n//=============================================\nstatic void FSTAR_PrintPath(void *handle)\n{\n\ttarfile_t *tar = (tarfile_t *)handle;\n\n\tif (tar->references != 1)\n\t\tCom_Printf(\"%s (%i)\\n\", tar->filename, tar->references-1);\n\telse\n\t\tCom_Printf(\"%s\\n\", tar->filename);\n}\n\nstatic void FSTAR_ClosePath(void *handle)\n{\n\ttarfile_t *tar = (tarfile_t *)handle;\n\n\tif (--tar->references > 0)\n\t\treturn; //not yet time\n\n\tVFS_CLOSE(tar->raw);\n\tif (tar->files)\n\t\tQ_free(tar->files);\n\tQ_free(tar);\n}\n\nstatic void FSTAR_BuildHash(void *handle)\n{\n\ttarfile_t *tar = (tarfile_t *)handle;\n\tint i;\n\n\tfor (i = 0; i < tar->numfiles; i++)\n\t{\n\t\tif (!Hash_GetInsensitive(filesystemhash, tar->files[i].name))\n\t\t{\n\t\t\tHash_AddInsensitive(filesystemhash, tar->files[i].name, &tar->files[i]);\n\t\t\tfs_hash_files++;\n\t\t}\n\t\telse\n\t\t\tfs_hash_dups++;\n\t}\n}\n\nstatic qbool FSTAR_FLocate(void *handle, flocation_t *loc, const char *filename, void *hashedresult)\n{\n\tpackfile_t *pf = (packfile_t *) hashedresult;\n\ttarfile_t *tar = (tarfile_t  *) handle;\n\tint i;\n\n\t// look through all the pak file elements\n\n\tif (pf)\n\t{   //is this a pointer to a file in this pak?\n\t\tif (pf < tar->files || pf > tar->files + tar->numfiles)\n\t\t\treturn false;   //was found in a different path\n\t}\n\telse\n\t{\n\t\tfor (i=0 ; i<tar->numfiles ; i++)   //look for the file\n\t\t{\n\t\t\tif (!strcmp (tar->files[i].name, filename))\n\t\t\t{\n\t\t\t\tpf = &tar->files[i];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (pf)\n\t{\n\t\tif (loc)\n\t\t{\n\t\t\tloc->index = pf - tar->files;\n\t\t\tsnprintf(loc->rawname, sizeof(loc->rawname), \"%s/%s\", tar->filename, filename);\n\t\t\tloc->offset = pf->filepos;\n\t\t\tloc->len = pf->filelen;\n\t\t}\n\t\treturn true;\n\t}\n\treturn false;\n\n}\n\nstatic void FSTAR_ReadFile(void *handle, flocation_t *loc, char *buffer)\n{\n\ttarfile_t *tar = handle;\n\tint err;\n\n\tVFS_SEEK(tar->raw, tar->files[loc->index].filepos, SEEK_SET);\n\n\terr = VFS_READ(tar->raw, buffer, tar->files[loc->index].filelen, NULL);\n\n\tif (err!=tar->files[loc->index].filelen)\n\t{\n\t\tCom_Printf (\"Can't extract file \\\"%s:%s\\\" (corrupt)\\n\", tar->filename, tar->files[loc->index].name);\n\t\treturn;\n\t}\n\treturn;\n}\n\nstatic int FSTAR_EnumerateFiles (void *handle, char *match, int (*func)(char *, int, void *), void *parm)\n{\n\ttarfile_t *tar = handle;\n\tint     num;\n\n\tfor (num = 0; num < tar->numfiles; num++)\n\t{\n\t\tif (wildcmp(match, tar->files[num].name))\n\t\t{\n\t\t\tif (!func(tar->files[num].name, tar->files[num].filelen, parm))\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n// =================\n// FSTAR_LoadTarFile\n// =================\n// Takes an explicit (not game tree related) path to a tar file.\n//\n// Loads the header and directory, adding the files at the beginning\n// of the list so they override previous pack files.\n\nstatic void *FSTAR_LoadTarFile(vfsfile_t *tarhandle, const char *desc)\n{\n\ttarfile_t *tar;\n\n\ttar = Q_calloc(1, sizeof(*tar));\n\tstrlcpy (tar->filename, desc, sizeof (tar->filename));\n\ttar->raw = tarhandle;\n\tif (!tar->raw) goto fail;\n\n\t// Get the number of files inside the tar\n\ttar->numfiles = tarOperationIndexFiles(tar->raw, NULL);\n\tif (tar->numfiles < 0) goto fail;\n\n\t// Create a list of the number of files\n\ttar->files = (packfile_t *)Q_malloc(tar->numfiles * sizeof(packfile_t));\n\ttarOperationIndexFiles(tar->raw, tar->files);\n\n\ttar->references = 1;\n\n\treturn tar;\n\nfail:\n\t// Q_free is safe to call on NULL pointers\n\tQ_free(tar->files);\n\tQ_free(tar);\n\treturn NULL;\n}\n\nsearchpathfuncs_t tarfilefuncs = {\n\tFSTAR_PrintPath,\n\tFSTAR_ClosePath,\n\tFSTAR_BuildHash,\n\tFSTAR_FLocate,\n\tFSTAR_ReadFile,\n\tFSTAR_EnumerateFiles,\n\tFSTAR_LoadTarFile,\n\tNULL, // VFS-FIXME: Might be used for f_modification\n\tFSTAR_OpenVFS \n};\n"
  },
  {
    "path": "src/vfs_tar.h",
    "content": "/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n *     \n * $Id: vfs_tar.h,v 1.1 2007-10-08 14:50:53 dkure Exp $\n *             \n */\n\n#ifndef __VFS_TAR_H__\n#define __VFS_TAR_H__\n\n/* values used in typeflag field */\n\n#define REGTYPE  '0'            /* regular file */\n#define AREGTYPE '\\0'           /* regular file */\n#define LNKTYPE  '1'            /* link */\n#define SYMTYPE  '2'            /* reserved */\n#define CHRTYPE  '3'            /* character special */\n#define BLKTYPE  '4'            /* block special */\n#define DIRTYPE  '5'            /* directory */\n#define FIFOTYPE '6'            /* FIFO special */\n#define CONTTYPE '7'            /* reserved */\n\n/* GNU tar extensions */\n\n#define GNUTYPE_DUMPDIR  'D'    /* file names from dumped directory */\n#define GNUTYPE_LONGLINK 'K'    /* long link name */\n#define GNUTYPE_LONGNAME 'L'    /* long file name */\n#define GNUTYPE_MULTIVOL 'M'    /* continuation of file from another volume */\n#define GNUTYPE_NAMES    'N'    /* file name that does not fit into main hdr */\n#define GNUTYPE_SPARSE   'S'    /* sparse file */\n#define GNUTYPE_VOLHDR   'V'    /* tape/volume header */\n\n\n/* tar header */\n\n#define BLOCKSIZE     512\n#define SHORTNAMESIZE 100\n\nstruct tar_header\n{                               /* byte offset */\n  char name[100];               /*   0 */\n  char mode[8];                 /* 100 */\n  char uid[8];                  /* 108 */\n  char gid[8];                  /* 116 */\n  char size[12];                /* 124 */\n  char mtime[12];               /* 136 */\n  char chksum[8];               /* 148 */\n  char typeflag;                /* 156 */\n  char linkname[100];           /* 157 */\n  char magic[6];                /* 257 */\n  char version[2];              /* 263 */\n  char uname[32];               /* 265 */\n  char gname[32];               /* 297 */\n  char devmajor[8];             /* 329 */\n  char devminor[8];             /* 337 */\n  char prefix[155];             /* 345 */\n                                /* 500 */\n};\n\nunion tar_buffer\n{\n  char               buffer[BLOCKSIZE];\n  struct tar_header  header;\n};\n\nstruct attr_item\n{\n  struct attr_item  *next;\n  char              *fname;\n  int                mode;\n  time_t             time;\n};\n\nenum { \n\tTGZ_EXTRACT,\n\tTGZ_LIST,\n\tTGZ_INVALID \n};\n\ntypedef enum { \n\tHEADER_NONE,\n\tHEADER_SHORTNAME,\n\tHEADER_LONGNAME,\n\tGET_HEADER = HEADER_SHORTNAME | HEADER_LONGNAME\n} vfsTarHeaderType_t;\n\n#endif /* __VFS_TAR_H__ */\n\n"
  },
  {
    "path": "src/vfs_tcp.c",
    "content": "/*\n Copyright (C) 1996-1997 Id Software, Inc.\n \n This program is free software; you can redistribute it and/or\n modify it under the terms of the GNU General Public License\n as published by the Free Software Foundation; either version 2\n of the License, or (at your option) any later version.\n \n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n \n See the GNU General Public License for more details.\n \n You should have received a copy of the GNU General Public License\n along with this program; if not, write to the Free Software\n Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n \n $Id: vfs_tcp.c,v 1.2 2007-09-28 05:21:45 dkure Exp $\n*/\n\n#include \"quakedef.h\"\n#include \"hash.h\"\n#include \"common.h\"\n#include \"fs.h\"\n#include \"vfs.h\"\n\n//==========\n// TCP file\n//==========\n\ntypedef struct tcpfile_s {\n\tvfsfile_t funcs; // <= must be at top/begining of struct\n\n\tint\t\tsock;\n\n\tchar\treadbuffer [5 * 65536];\n\tint\t\treadbuffered;\n\n\tchar\twritebuffer[5 * 65536];\n\tint\t\twritebuffered;\n\n\tstruct tcpfile_s *next;\n\n} tcpfile_t;\n\n\ntcpfile_t *vfs_tcp_list = NULL;\n\n\nvoid VFSTCP_Error(tcpfile_t *f)\n{\n\tif (f->sock != INVALID_SOCKET)\n\t{\n\t\tclosesocket(f->sock);\n\t\tf->sock = INVALID_SOCKET;\n\t}\n}\n\nint VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread, vfserrno_t *err)\n{\n\ttcpfile_t *tf = (tcpfile_t*)file;\n\n\tif (bytestoread < 0)\n\t\tSys_Error(\"VFSTCP_ReadBytes: bytestoread < 0\"); // ffs\n\n\tif (tf->sock != INVALID_SOCKET)\n\t{\n\t\tint len;\n\n\t\tif (sizeof(tf->readbuffer) == tf->readbuffered)\n\t\t\tlen = -2; // full buffer\n\t\telse\n\t\t\tlen = recv(tf->sock, tf->readbuffer + tf->readbuffered, sizeof(tf->readbuffer) - tf->readbuffered, 0);\n\n\t\tif (len == -2) {\n\t\t\t; // full buffer\n\t\t}\n\t\telse if (len == -1)\n\t\t{\n\t\t\tint e = qerrno;\n\t\n\t\t\tif (e == EWOULDBLOCK) {\n\t\t\t\t; // no data yet, nothing to do\n\t\t\t} else {\n\n\t\t\t\tif (e == ECONNABORTED || e == ECONNRESET)\n\t\t\t\t\tCom_Printf (\"VFSTCP: Connection lost or aborted\\n\"); //server died/connection lost.\n\t\t\t\telse\n\t\t\t\t\tCom_Printf (\"VFSTCP: Error (%i): %s\\n\", e, strerror(e));\n\n\t\t\t\tVFSTCP_Error(tf);\n\t\t\t}\n\t\t}\n\t\telse if (len == 0) {\n\t\t\tCom_Printf (\"VFSTCP: EOF on socket\\n\");\n\t\t\tVFSTCP_Error(tf);\n\t\t}\n\t\telse\n\t\t\ttf->readbuffered += len;\n\t}\n\n\tif (bytestoread <= tf->readbuffered)\n\t{ // we have all required bytes\n\t\tif (bytestoread > 0) {\n\t\t\tmemcpy(buffer, tf->readbuffer, bytestoread);\n\t\t\ttf->readbuffered -= bytestoread;\n\t\t\tmemmove(tf->readbuffer, tf->readbuffer+bytestoread, tf->readbuffered);\n\t\t}\n\n\t\tif (err)\n\t\t\t*err = VFSERR_NONE; // we got required bytes somehow, so no error\n\n\t\treturn bytestoread;\n\t}\n\telse\n\t{ // lack of data, but probably have at least something in buffer\n\t\tif (tf->readbuffered > 0 && buffer != NULL) {\n\t\t\tbytestoread = tf->readbuffered;\n\t\t\tmemcpy(buffer, tf->readbuffer, tf->readbuffered);\n\t\t\ttf->readbuffered = 0;\n\t\t}\n\t\telse\n\t\t\tbytestoread = 0; // this help guess EOF\n\n\t\tif (err) // if socket is not closed we still have chance to get data, so no error\n\t\t\t*err = ((bytestoread || tf->sock != INVALID_SOCKET) ? VFSERR_NONE : VFSERR_EOF);\n\n\t\treturn bytestoread;\n\t}\n}\n\nint VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestowrite)\n{\n\ttcpfile_t *tf = (tcpfile_t*)file;\n\tint len;\n\n\tif (bytestowrite < 0)\n\t\tSys_Error(\"VFSTCP_WriteBytes: bytestowrite < 0\"); // ffs\n\n\tif (tf->sock == INVALID_SOCKET)\n\t\treturn 0;\n\n\tif (bytestowrite > (int)sizeof(tf->writebuffer) - tf->writebuffered)\n\t{\n\t\tCom_Printf(\"VFSTCP: failed to write %d bytes, buffer overflow, closing link\\n\", bytestowrite);\n\t\tVFSTCP_Error(tf);\n\t\treturn 0;\n\t}\n\n\t// append data\n\tmemmove(tf->writebuffer + tf->writebuffered, buffer, bytestowrite);\n\ttf->writebuffered += bytestowrite;\n\n\tlen = send(tf->sock, tf->writebuffer, tf->writebuffered, 0);\n\n\tif (len < 0)\n\t{\n\t\tint err = qerrno;\n\n\t\tif (err != EWOULDBLOCK/* FIXME: this is require winsock2.h on windows && err != EAGAIN && err != ENOTCONN */)\n\t\t{\n\t\t\tCom_Printf(\"VFSTCP: failed to write %d bytes, closing link, socket error %d\\n\", bytestowrite, err);\n\t\t\tVFSTCP_Error(tf);\n\t\t\treturn 0;\n\t\t}\n\t}\n\telse if (len > 0)\n\t{\n\t\ttf->writebuffered -= len;\n\t\tmemmove(tf->writebuffer, tf->writebuffer + len, tf->writebuffered);\n\t}\n\telse\n\t{\n\t\t// hm, zero bytes was sent\n\t}\n\n\treturn bytestowrite; // well at least we put something in buffer, sure if bytestowrite not zero\n}\n\nint VFSTCP_Seek (struct vfsfile_s *file, unsigned long pos, int whence)\n{\n\tCom_Printf(\"VFSTCP: seek is illegal, closing link\\n\");\n\tVFSTCP_Error((tcpfile_t*)file);\n\treturn -1;\n}\n\nunsigned long VFSTCP_Tell (struct vfsfile_s *file)\n{\n\tCom_Printf(\"VFSTCP: tell is illegal, closing link\\n\");\n\tVFSTCP_Error((tcpfile_t*)file);\n\treturn 0;\n}\n\nunsigned long VFSTCP_GetLen (struct vfsfile_s *file)\n{\n\tCom_Printf(\"VFSTCP: GetLen non functional\\n\");\n\treturn 0;\n}\n\nvoid VFSTCP_Close (struct vfsfile_s *file)\n{\n\ttcpfile_t *tmp, *prev;\n\n\tVFSTCP_Error((tcpfile_t*)file); // close socket\n\n\t// link out\n\tfor (tmp = prev = vfs_tcp_list; tmp; tmp = tmp->next)\n\t{\n\t\tif (tmp == (tcpfile_t*)file)\n\t\t{\n\t\t\tif (tmp == vfs_tcp_list)\n\t\t\t\tvfs_tcp_list = tmp->next; // we remove from head a bit different\n\t\t\telse\n\t\t\t\tprev->next   = tmp->next; // removing not from head\n\n\t\t\tbreak;\n\t\t}\n\n\t\tprev = tmp;\n\t}\n\n\tif (!tmp)\n\t\tCom_Printf(\"VFSTCP: Close: not found in list\\n\");\n\n\tQ_free(file);\n}\n\nvoid VFSTCP_Tick(void)\n{\n\ttcpfile_t *tmp;\n\tvfserrno_t err;\n\n\tfor (tmp = vfs_tcp_list; tmp; tmp = tmp->next)\n\t{\n\t\tVFSTCP_ReadBytes  ((vfsfile_t*)tmp, NULL, 0, &err); // perform read in our internal buffer\n\t\tVFSTCP_WriteBytes ((vfsfile_t*)tmp, NULL, 0);\t\t// perform write from our internall buffer\n\n//\t\tCom_Printf(\"r %d, w %d\\n\", tmp->readbuffered, tmp->writebuffered);\n\t}\n}\n\nvfsfile_t *FS_OpenTCP(char *name)\n{\n\ttcpfile_t *newf;\n\tint sock;\n//\tint _true = true;\n\tnetadr_t adr = {0};\n\n\tif (NET_StringToAdr(name, &adr))\n\t{\n\t\tsock = TCP_OpenStream(adr);\n\t\tif (sock == INVALID_SOCKET)\n\t\t\treturn NULL;\n\n//\t\tif (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&_true, sizeof(_true)) == -1)\n//\t\t\tCom_Printf (\"FS_OpenTCP: setsockopt: (%i): %s\\n\", qerrno, strerror(qerrno));\n\n\t\tnewf = Q_calloc(1, sizeof(tcpfile_t));\n\t\tmemset(newf, 0, sizeof(tcpfile_t));\n\t\tnewf->sock\t\t\t\t= sock;\n\t\tnewf->funcs.Close\t\t= VFSTCP_Close;\n\t\tnewf->funcs.Flush\t\t= NULL;\n\t\tnewf->funcs.GetLen\t\t= VFSTCP_GetLen;\n\t\tnewf->funcs.ReadBytes\t= VFSTCP_ReadBytes;\n\t\tnewf->funcs.Seek\t\t= VFSTCP_Seek;\n\t\tnewf->funcs.Tell\t\t= VFSTCP_Tell;\n\t\tnewf->funcs.WriteBytes\t= VFSTCP_WriteBytes;\n\t\tnewf->funcs.seekingisabadplan = true;\n\n\t\t// link in\n\t\tnewf->next   = vfs_tcp_list;\n\t\tvfs_tcp_list = newf;\n\n\t\treturn &newf->funcs;\n\t}\n\telse\n\t\treturn NULL;\n}\n\nvoid VFS_TICK(void)\n{\n\tVFSTCP_Tick(); // fill in/out our internall buffers (do read/write on socket)\n}\n"
  },
  {
    "path": "src/vfs_zip.c",
    "content": "/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; either version 2\n * of the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n * See the GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n *     \n * $Id: vfs_zip.c,v 1.12 2007-10-13 16:02:51 dkure Exp $\n *             \n */\n\n#ifdef WITH_ZIP\n\n#include \"quakedef.h\"\n#include \"hash.h\"\n#include \"common.h\"\n#include \"fs.h\"\n#include \"vfs.h\"\n\n//===========================\n// Unzip library interfacing\n//===========================\n// These functions provide IO support for the unzip library using our VFS layer\n\nstatic void *FSZIP_ZOpenFile(void *fin, const char *filename, int mode) {\n\tvfsfile_t *vfs_fin = (vfsfile_t *)fin;\n\n\t// VFS-TODO Maybe need to increase reference count\n\tif ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) {\n\t} else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) {\n\t\tSys_Error(\"FSZIP_ZOpenFile: Unsupported file mode: MODE_EXISTING\");\n\t\treturn NULL;\n\t} else if (mode & ZLIB_FILEFUNC_MODE_CREATE) {\n\t\tSys_Error(\"FSZIP_ZOpenFile: Unsupported file mode: write\");\n\t\treturn NULL;\n\t}\n\n\treturn vfs_fin;\n}\n\nstatic unsigned long FSZIP_ZReadFile(void *fin, void *stream, void *buf, unsigned long size) {\n\tvfsfile_t *vfs_fin = (vfsfile_t *)stream;\n\treturn VFS_READ(vfs_fin, buf, size, NULL);\n}\n\nstatic unsigned long FSZIP_ZWriteFile(void *fin, void *stream, const void *buf, unsigned long size) {\n\tvfsfile_t *vfs_fin = (vfsfile_t *)stream;\n\treturn VFS_WRITE(vfs_fin, buf, size);\n}\n\nstatic long FSZIP_ZTellFile(void *fin, void *stream) {\n\tvfsfile_t *vfs_fin = (vfsfile_t *)stream;\n\treturn VFS_TELL(vfs_fin);\n}\n\nstatic long FSZIP_ZSeekFile(void *fin, void *stream, unsigned long offset, int origin) {\n\tvfsfile_t *vfs_fin = (vfsfile_t *)stream;\n\tint fseek_origin;\n\tint r;\n\n\tswitch (origin)\n\t{\n\tcase ZLIB_FILEFUNC_SEEK_CUR: fseek_origin = SEEK_CUR; break;\n\tcase ZLIB_FILEFUNC_SEEK_END: fseek_origin = SEEK_END; break;\n\tcase ZLIB_FILEFUNC_SEEK_SET: fseek_origin = SEEK_SET; break;\n\tdefault: \n\t\tSys_Error(\"FSZIP_ZSeekFile: Unsupported seek origin: Unknown\");\n\t\treturn -1;\n\t}\n\n\tr = VFS_SEEK(vfs_fin, offset, fseek_origin);\n\n\treturn r;\n}\n\nstatic int FSZIP_ZCloseFile(void *fin, void *stream) {\n\t// vfsfile_t *vfs_fin = (vfsfile_t *)stream;\n\t// VFS-TODO: Need to decrease references \n\t// VFS_CLOSE(vfs_fin);\n\treturn 0; \n}\n\nstatic int FSZIP_ZErrorFileFile(void *fin, void *stream) {\n\t// vfsfile_t *vfs_fin = (vfsfile_t *)stream;\n\t// VFS-TODO: Need to get the error code somehow\n\treturn 0;\n}\n\nstatic void FSZIP_CreteFileFuncs(zlib_filefunc_def *funcs) {\n\tif (funcs == NULL)\n\t\treturn;\n\n\tfuncs->zopen_file  = FSZIP_ZOpenFile;\n\tfuncs->zread_file  = FSZIP_ZReadFile;\n\tfuncs->zwrite_file = FSZIP_ZWriteFile;\n\tfuncs->ztell_file  = FSZIP_ZTellFile;\n\tfuncs->zseek_file  = FSZIP_ZSeekFile;\n\tfuncs->zclose_file = FSZIP_ZCloseFile;\n\tfuncs->zerror_file = FSZIP_ZErrorFileFile;\n}\n\n//==========================================\n// ZIP file  (*.zip, *.pk3) - VFS Functions\n//==========================================\ntypedef struct zipfile_s\n{\n\tchar filename[MAX_QPATH];\n\tunzFile handle;\n\tint\t\tnumfiles;\n\tpackfile_t\t*files;\n\n#ifdef HASH_FILESYSTEM\n\thashtable_t hash;\n#endif\n\n\tzlib_filefunc_def zlib_funcs;\n\n\tvfsfile_t *raw;\n\tvfsfile_t *currentfile;\t//our unzip.c can only handle one active file at any one time\n\t\t\t\t\t\t\t//so we have to keep closing and switching.\n\t\t\t\t\t\t\t//slow, but it works. most of the time we'll only have a single file open anyway.\n\tint references;\t//and a reference count\n} zipfile_t;\n\ntypedef struct {\n\tvfsfile_t funcs;\n\n\tvfsfile_t *defer;\n\n\t//in case we're forced away.\n\tzipfile_t *parent;\n\tint pos;\n\tint length;\t//try and optimise some things\n\tint index;\n\tint startpos;\n} vfszip_t;\n\n// VFS-FIXME Need to figure what this function is trying to do\nstatic void VFSZIP_MakeActive(vfszip_t *vfsz)\n{\n\tint i;\n\tchar buffer[8192];\t//must be power of two\n\n\tif ((vfszip_t*)vfsz->parent->currentfile == vfsz)\n\t\treturn;\t//already us\n\tif (vfsz->parent->currentfile)\n\t\tunzCloseCurrentFile(vfsz->parent->handle);\n\n\t//unzLocateFileMy(vfsz->parent->handle, vfsz->index, vfsz->startpos);\n\tunzSetOffset(vfsz->parent->handle, vfsz->startpos);\n\tunzOpenCurrentFile(vfsz->parent->handle);\n\n\tif (vfsz->pos > 0)\n\t{\n\t\tCom_DPrintf(\"VFSZIP_MakeActive: Shockingly inefficient\\n\");\n\n\t\t//now we need to seek up to where we had previously gotten to.\n\t\tfor (i = 0; i < vfsz->pos-sizeof(buffer); i++)\n\t\t\tunzReadCurrentFile(vfsz->parent->handle, buffer, sizeof(buffer));\n\t\tunzReadCurrentFile(vfsz->parent->handle, buffer, vfsz->pos - i);\n\t}\n\n\tvfsz->parent->currentfile = (vfsfile_t*)vfsz;\n}\n\nstatic int VFSZIP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestoread, vfserrno_t *err)\n{\n\tint read;\n\tvfszip_t *vfsz = (vfszip_t*)file;\n\n\tif (vfsz->defer)\n\t\treturn VFS_READ(vfsz->defer, buffer, bytestoread, err);\n\n\tVFSZIP_MakeActive(vfsz);\n\tread = unzReadCurrentFile(vfsz->parent->handle, buffer, bytestoread);\n\n\tif (err)\n\t\t*err = ((read || bytestoread <= 0) ? VFSERR_NONE : VFSERR_EOF);\n\n\tvfsz->pos += read;\n\treturn read;\n}\n\nstatic int VFSZIP_WriteBytes (struct vfsfile_s *file, const void *buffer, int bytestoread)\n{\n\tSys_Error(\"VFSZIP_WriteBytes: Not supported\\n\");\n\treturn 0;\n}\n\n// VFS-FIXME: What is going on here... why not just unzSetOffset\nstatic int VFSZIP_Seek (struct vfsfile_s *file, unsigned long pos, int whence)\n{\n\tvfszip_t *vfsz = (vfszip_t*)file;\n\n\tif (vfsz->defer)\n\t\treturn VFS_SEEK(vfsz->defer, pos, whence);\n\n\t//This is *really* inefficient\n\tif (vfsz->parent->currentfile == file)\n\t{\n\t\tchar buffer[8192];\n\t\tunsigned int chunk;\n\t\tunsigned int i;\n\t\tunsigned int length;\n\n\t\tvfsz->defer = FS_OpenTemp();\n\t\tif (vfsz->defer)\n\t\t{\n\t\t\tunzCloseCurrentFile(vfsz->parent->handle);\n\t\t\tvfsz->parent->currentfile = NULL;\t//make it not us\n\n\t\t\tlength = vfsz->length;\n\t\t\ti = 0;\n\t\t\tvfsz->pos = 0;\n\t\t\tVFSZIP_MakeActive(vfsz);\n\t\t\twhile (1)\n\t\t\t{\n\t\t\t\tchunk = length - i;\n\t\t\t\tif (chunk > sizeof(buffer))\n\t\t\t\t\tchunk = sizeof(buffer);\n\t\t\t\tif (chunk == 0)\n\t\t\t\t\tbreak;\n\t\t\t\tunzReadCurrentFile(vfsz->parent->handle, buffer, chunk);\n\t\t\t\tVFS_WRITE(vfsz->defer, buffer, chunk);\n\n\t\t\t\ti += chunk;\n\t\t\t}\n\t\t}\n\n\t\tunzCloseCurrentFile(vfsz->parent->handle);\n\t\tvfsz->parent->currentfile = NULL;\t//make it not us\n\n\t\tif (vfsz->defer)\n\t\t\treturn VFS_SEEK(vfsz->defer, pos, SEEK_SET);\n\t}\n\n\n\n\tif (pos > vfsz->length)\n\t\treturn -1;\n\tvfsz->pos = pos;\n\n\treturn 0;\n}\n\nstatic unsigned long VFSZIP_Tell (struct vfsfile_s *file)\n{\n\tvfszip_t *vfsz = (vfszip_t*)file;\n\n\tif (vfsz->defer)\n\t\treturn VFS_TELL(vfsz->defer);\n\n\treturn vfsz->pos;\n}\n\nstatic unsigned long VFSZIP_GetLen (struct vfsfile_s *file)\n{\n\tvfszip_t *vfsz = (vfszip_t*)file;\n\treturn vfsz->length;\n}\n\nstatic void FSZIP_ClosePath(void *handle);\nstatic void VFSZIP_Close (struct vfsfile_s *file)\n{\n\tvfszip_t *vfsz = (vfszip_t*)file;\n\n\tif (vfsz->parent->currentfile == file)\n\t\tvfsz->parent->currentfile = NULL;\t//make it not us\n\n\tif (vfsz->defer)\n\t\tVFS_CLOSE(vfsz->defer);\n\n\tFSZIP_ClosePath(vfsz->parent);\n\tQ_free(vfsz);\n}\n\nstatic vfsfile_t *FSZIP_OpenVFS(void *handle, flocation_t *loc, char *mode)\n{\n\t//int rawofs;\n\tzipfile_t *zip = handle;\n\tvfszip_t *vfsz;\n\n\tif (strcmp(mode, \"rb\"))\n\t\treturn NULL; //urm, unable to write/append\n\n\tvfsz = Q_calloc(1, sizeof(vfszip_t));\n\n\tvfsz->parent = zip;\n\tvfsz->index = loc->index;\n\tvfsz->startpos = zip->files[loc->index].filepos;\n\tvfsz->length = loc->len;\n\n\tvfsz->funcs.ReadBytes  = strcmp(mode, \"rb\") ? NULL : VFSZIP_ReadBytes;\n\tvfsz->funcs.WriteBytes = strcmp(mode, \"wb\") ? NULL : VFSZIP_WriteBytes;\n\tvfsz->funcs.Seek       = VFSZIP_Seek;\n\tvfsz->funcs.seekingisabadplan = true;\n\tvfsz->funcs.Tell       = VFSZIP_Tell;\n\tvfsz->funcs.GetLen     = VFSZIP_GetLen;\n\tvfsz->funcs.Close      = VFSZIP_Close;\n\tif (loc->search)\n\t\tvfsz->funcs.copyprotected = loc->search->copyprotected;\n\n\tzip->references++;\n\n\treturn (vfsfile_t*)vfsz;\n}\n\n//=============================================\n// ZIP file  (*.zip, *.pk3) - Search Functions\n//=============================================\nstatic void FSZIP_PrintPath(void *handle)\n{\n\tzipfile_t *zip = handle;\n\n\tif (zip->references != 1)\n\t\tCom_Printf(\"%s (%i)\\n\", zip->filename, zip->references-1);\n\telse\n\t\tCom_Printf(\"%s\\n\", zip->filename);\n}\n\nstatic void FSZIP_ClosePath(void *handle)\n{\n\tzipfile_t *zip = handle;\n\n\tif (--zip->references > 0)\n\t\treturn;\t//not yet time\n\n\tunzClose(zip->handle);\n\tVFS_CLOSE(zip->raw);\n\tif (zip->files)\n\t\tQ_free(zip->files);\n\tQ_free(zip);\n}\nstatic void FSZIP_BuildHash(void *handle)\n{\n\tzipfile_t *zip = handle;\n\tint i;\n\n\tfor (i = 0; i < zip->numfiles; i++)\n\t{\n\t\tif (!Hash_GetInsensitive(filesystemhash, zip->files[i].name))\n\t\t{\n\t\t\tHash_AddInsensitive(filesystemhash, zip->files[i].name, &zip->files[i]);\n\t\t\tfs_hash_files++;\n\t\t}\n\t\telse\n\t\t\tfs_hash_dups++;\n\t}\n}\n\nstatic qbool FSZIP_FLocate(void *handle, flocation_t *loc, const char *filename, void *hashedresult)\n{\n\tpackfile_t *pf  = (packfile_t *) hashedresult;\n\tzipfile_t  *zip = (zipfile_t  *) handle;\n\tint i;\n\n// look through all the pak file elements\n\n\tif (pf)\n\t{\t//is this a pointer to a file in this pak?\n\t\tif (pf < zip->files || pf >= zip->files + zip->numfiles)\n\t\t\treturn false;\t//was found in a different path\n\t}\n\telse\n\t{\n\t\tfor (i=0 ; i<zip->numfiles ; i++)\t//look for the file\n\t\t{\n\t\t\tif (!strcasecmp (zip->files[i].name, filename))\n\t\t\t{\n\t\t\t\tpf = &zip->files[i];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (pf)\n\t{\n\t\tif (loc)\n\t\t{\n\t\t\tloc->index = pf - zip->files;\n\t\t\tstrlcpy (loc->rawname, zip->filename, sizeof (loc->rawname));\n\t\t\tloc->offset = pf->filepos;\n\t\t\tloc->len = pf->filelen;\n\t\t\tloc->search = NULL;\n\t\n\t\t\t// VFS-FIXME: What is the purpose of the stuff below....\n//\t\t\tunzLocateFileMy (zip->handle, loc->index, zip->files[loc->index].filepos);\n//\t\t\tloc->offset = unzGetCurrentFileUncompressedPos(zip->handle);\n//\t\t\tif (loc->offset<0)\n//\t\t\t{\t//file not found, or is compressed.\n//\t\t\t\t*loc->rawname = '\\0';\n//\t\t\t\tloc->offset=0;\n//\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n\nstatic void FSZIP_ReadFile(void *handle, flocation_t *loc, char *buffer)\n{\n\tzipfile_t *zip = handle;\n\tint err;\n\n\tunzSetOffset(zip->handle, zip->files[loc->index].filepos);\n\n\tunzOpenCurrentFile (zip->handle);\n\terr = unzReadCurrentFile (zip->handle, buffer, zip->files[loc->index].filelen);\n\tunzCloseCurrentFile (zip->handle);\n\n\tif (err!=zip->files[loc->index].filelen)\n\t{\n\t\tCom_Printf (\"Can't extract file \\\"%s:%s\\\" (corrupt)\\n\", zip->filename, zip->files[loc->index].name);\n\t\treturn;\n\t}\n\treturn;\n}\n\n\nstatic int FSZIP_EnumerateFiles (void *handle, char *match, int (*func)(char *, int, void *), void *parm)\n{\n\tzipfile_t *zip = handle;\n\tint\t\tnum;\n\n\tfor (num = 0; num < zip->numfiles; num++)\n\t{\n\t\tif (wildcmp(match, zip->files[num].name))\n\t\t{\n\t\t\tif (!func(zip->files[num].name, zip->files[num].filelen, parm))\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n=================\nCOM_LoadZipFile\n\nTakes an explicit (not game tree related) path to a pak file.\n\nLoads the header and directory, adding the files at the beginning\nof the list so they override previous pack files.\n=================\n*/\nstatic void *FSZIP_LoadZipFile(vfsfile_t *packhandle, const char *desc)\n{\n\tint i, r;\n\n\tzipfile_t *zip;\n\tpackfile_t\t\t*newfiles;\n\tzlib_filefunc_def *funcs = NULL;\n\tunz_global_info info;\n\t\n\tzip   = (zipfile_t *) Q_calloc(1, sizeof(*zip));\n\tstrlcpy (zip->filename, desc, sizeof (zip->filename));\n\tFSZIP_CreteFileFuncs(&(zip->zlib_funcs));\n\tzip->raw = packhandle;\n\tzip->handle = unzOpen2(desc, funcs);\n\tif (!zip->handle) goto fail;\n\n\tif (unzGetGlobalInfo(zip->handle, &info) != UNZ_OK) goto fail;\n\n\t// Get the number of zip files\n\tzip->numfiles = info.number_entry;\n\n\t// Create a list of the number of files\n\tzip->files = newfiles = Q_malloc (zip->numfiles * sizeof(packfile_t));\n\tif (unzGoToFirstFile(zip->handle) != UNZ_OK) goto fail;\n\tfor (i = 0; i < zip->numfiles; i++) {\n\t\tunz_file_info file_info;\n\t\tif (unzGetCurrentFileInfo(zip->handle, &file_info, newfiles[i].name, sizeof(newfiles[i].name), NULL, 0, NULL, 0) != UNZ_OK) goto fail;\n\n\t\tQ_strlwr(newfiles[i].name);\n\t\tnewfiles[i].filelen = file_info.uncompressed_size;\n\t\tnewfiles[i].filepos = unzGetOffset(zip->handle); // VFS-FIXME: Need to verify this\n\t\tr = unzGoToNextFile (zip->handle);\n\t\tif (r == UNZ_END_OF_LIST_OF_FILE) {\n\t\t\tbreak;\n\t\t} else if (r != UNZ_OK) {\n\t\t\tgoto fail;\n\t\t}\n\n\t}\n\t\n\tzip->references = 1;\n\tzip->currentfile = NULL;\n\n\treturn zip;\n\nfail:\n\t// Q_free is safe to call on NULL pointers\n\tQ_free(funcs);\n\tQ_free(zip->handle);\n\tQ_free(zip->files);\n\tQ_free(zip);\n\treturn NULL;\n}\n\n// VFS-FIXME: Don't really seem to know what this does\nstatic int FSZIP_GeneratePureCRC(void *handle, int seed, int crctype)\n{\n\tzipfile_t *zip = handle;\n\tunz_file_info   file_info;\n\n\tint *filecrcs;\n\tint numcrcs=0;\n\tint i;\n\n\tfilecrcs = Q_malloc((zip->numfiles+1)*sizeof(int));\n\tfilecrcs[numcrcs++] = seed;\n\n\tunzGoToFirstFile(zip->handle);\n\tfor (i = 0; i < zip->numfiles; i++)\n\t{\n\t\tif (zip->files[i].filelen>0)\n\t\t{\n\t\t\tunzGetCurrentFileInfo (zip->handle, &file_info, NULL, 0, NULL, 0, NULL, 0);\n\t\t\tfilecrcs[numcrcs++] = file_info.crc;\n\t\t}\n\t\tunzGoToNextFile (zip->handle);\n\t}\n\n\tif (crctype)\n\t\treturn Com_BlockChecksum(filecrcs, numcrcs*sizeof(int));\n\telse\n\t\treturn Com_BlockChecksum(filecrcs+1, (numcrcs-1)*sizeof(int));\n}\n\nsearchpathfuncs_t zipfilefuncs = {\n\tFSZIP_PrintPath,\n\tFSZIP_ClosePath,\n\tFSZIP_BuildHash,\n\tFSZIP_FLocate,\n\tFSZIP_ReadFile,\n\tFSZIP_EnumerateFiles,\n\tFSZIP_LoadZipFile,\n\tFSZIP_GeneratePureCRC,\n\tFSZIP_OpenVFS\n};\n\n#endif // WITH_ZIP\n"
  },
  {
    "path": "src/vid.h",
    "content": "/*\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: vid.h,v 1.7 2007-01-07 21:48:47 disconn3ct Exp $\n*/\n// vid.h -- video driver defs\n\n#ifndef EZQUAKE_VID_HEADER\n#define EZQUAKE_VID_HEADER\n\n#include <SDL.h>\n\n#define VID_CBITS 6\n#define VID_GRADES (1 << VID_CBITS)\n\n// a pixel can be one, two, or four bytes\ntypedef byte pixel_t;\n\ntypedef struct vrect_s {\n\tint x,y,width,height;\n\tstruct vrect_s *pnext;\n} vrect_t;\n\ntypedef struct {\n\tpixel_t*        colormap;       // 256 * VID_GRADES size\n\tint             width;\n\tint             height;\n\tfloat           aspect;         // width / height -- < 0 is taller than wide\n\tint             numpages;\n\tint             recalc_refdef;  // if true, recalc vid-based stuff\n\tint             conwidth;\n\tint             conheight;\n} viddef_t;\n\nextern viddef_t vid; // global video state\nextern unsigned short d_8to16table[256];\nextern unsigned\td_8to24table[256];\nextern unsigned d_8to24table2[256];\nextern qbool vid_windowedmouse;\nextern void (*vid_menudrawfn)(void);\nextern void (*vid_menukeyfn)(int key);\n\nvoid VID_SetPalette (unsigned char *palette);\n// called at startup and after any gamma correction\n\nvoid VID_Init (unsigned char *palette);\n// Called at startup to set up translation tables, takes 256 8 bit RGB values\n// the palette data will go away after the call, so it must be copied off if\n// the video driver will need it again\n\nint VID_ScaledWidth3D(void);\nint VID_ScaledHeight3D(void);\n\nint VID_RenderWidth2D(void);\nint VID_RenderHeight2D(void);\n\nvoid VID_Shutdown(qbool restart);\nvoid VID_SoftRestart(void);\n// Called at shutdown\n\n// int VID_SetMode (int modenum, unsigned char *palette);\n// sets the mode; only used by the Quake engine for resetting to mode 0 (the\n// base mode) on memory allocation failures\n\nqbool IN_QuakeMouseCursorRequired (void);\nqbool IN_MouseTrackingRequired (void);\n\nvoid VID_NotifyActivity(void);\n\nvoid VID_SetCaption (char *text);\n\nint VID_SetDeviceGammaRamp (unsigned short *ramps);\nextern qbool vid_hwgamma_enabled;\n\nextern int glx, gly, glwidth, glheight;\n\nvoid VID_Minimize(void);\nvoid VID_Restore(void);\n\nqbool VID_VSyncIsOn(void);\nqbool VID_VSyncLagFix(void);\nqbool VID_VsyncLagEnabled(void);\nvoid VID_RenderFrameEnd(void);\n\nconst SDL_DisplayMode *VID_GetDisplayMode(int index);\nint VID_GetCurrentModeIndex(void);\nint VID_GetModeIndexCount(void);\n\nvoid VID_ReloadCheck(void);\n\n#endif // EZQUAKE_VID_HEADER\n"
  },
  {
    "path": "src/vid_common_gl.c",
    "content": "/*\nCopyright (C) 2002-2003 A Nourai\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n// vid_common_gl.c -- Common code for vid_wgl.c and vid_glx.c\n\n#include <SDL.h>\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"gl_local.h\"\n#include \"tr_types.h\"\n#include \"image.h\"\n#include \"r_texture.h\"\n#include \"r_renderer.h\"\n\nint anisotropy_ext = 0;\n\nqbool gl_mtexable = false;\nint gl_textureunits = 1;\n\nbyte color_white[4] = {255, 255, 255, 255};\nbyte color_black[4] = {0, 0, 0, 255};\n\nvoid GL_CheckMultiTextureExtensions(void);\nstatic void OnChange_gl_ext_texture_compression(cvar_t *, char *, qbool *);\n\nstatic cvar_t gl_ext_texture_compression = {\"gl_ext_texture_compression\", \"0\", CVAR_SILENT, OnChange_gl_ext_texture_compression};\n\n/************************************* EXTENSIONS *************************************/\n\nstatic qbool GL_InitialiseRenderer(void)\n{\n\tqbool shaders_supported = false;\n\n\tglConfig.supported_features = 0;\n\tglConfig.broken_features = 0;\n\n\tGL_InitialiseFramebufferHandling();\n\tGL_LoadProgramFunctions();\n\tGL_LoadStateFunctions();\n\tGL_LoadTextureManagementFunctions();\n\tGL_LoadDrawFunctions();\n\tGL_InitialiseDebugging();\n\n\tshaders_supported = GL_Supported(R_SUPPORT_MODERN_OPENGL_REQUIREMENTS);\n\tif ((glConfig.preferred_format == 0 && GL_VersionAtLeast(1, 2)) || glConfig.preferred_format == GL_BGRA) {\n\t\tglConfig.supported_features |= R_SUPPORT_BGRA_LIGHTMAPS;\n\t}\n\tif ((glConfig.preferred_type == 0 && GL_VersionAtLeast(1, 2)) || glConfig.preferred_type == GL_UNSIGNED_INT_8_8_8_8_REV) {\n\t\tglConfig.supported_features |= R_SUPPORT_INT8888R_LIGHTMAPS;\n\t}\n\n\tR_TraceAPI(\"Supported features:\");\n\tif (glConfig.supported_features & R_SUPPORT_FRAMEBUFFERS) {\n\t\tR_TraceAPI(\"... rendering to framebuffers\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_RENDERING_SHADERS) {\n\t\tR_TraceAPI(\"... rendering using shaders\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_COMPUTE_SHADERS) {\n\t\tR_TraceAPI(\"... non-rendering shaders\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_PRIMITIVERESTART) {\n\t\tR_TraceAPI(\"... primitive restart indexes\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_MULTITEXTURING) {\n\t\tR_TraceAPI(\"... multi-texturing (some people still disable this...)\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_IMAGE_PROCESSING) {\n\t\tR_TraceAPI(\"... reading/writing to images\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_TEXTURE_SAMPLERS) {\n\t\tR_TraceAPI(\"... texture samplers\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_TEXTURE_ARRAYS) {\n\t\tR_TraceAPI(\"... 3D images (texture arrays)\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_INDIRECT_RENDERING) {\n\t\tR_TraceAPI(\"... indirect rendering (api parameters stored in buffer)\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_INSTANCED_RENDERING) {\n\t\tR_TraceAPI(\"... instanced rendering\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_FRAMEBUFFERS_BLIT) {\n\t\tR_TraceAPI(\"... blit from one framebuffer to another\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_BGRA_LIGHTMAPS) {\n\t\tR_TraceAPI(\"... BGRA lightmaps (if optimal format)\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_INT8888R_LIGHTMAPS) {\n\t\tR_TraceAPI(\"... Lightmaps uploaded as UINT8888R rather than UNSIGNED_BYTE\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_CUBE_MAPS) {\n\t\tR_TraceAPI(\"... cube maps\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_SEAMLESS_CUBEMAPS) {\n\t\tR_TraceAPI(\"... filtering works across faces of the cubemap\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_DEPTH32F) {\n\t\tR_TraceAPI(\"... floating point 32-bit depth buffers\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_FRAMEBUFFERS_SRGB) {\n\t\tR_TraceAPI(\"... framebuffers support sRGB\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_IMMEDIATEMODE) {\n\t\tR_TraceAPI(\"... immediate-mode rendering (doesn't require programs)\");\n\t}\n\tif (glConfig.supported_features & R_SUPPORT_FOG) {\n\t\tR_TraceAPI(\"... fog\");\n\t}\n\n\tif (glConfig.broken_features) {\n\t\tR_TraceAPI(\"Broken features:\");\n\t\tif (GL_WorkaroundNeeded(R_BROKEN_GLBINDTEXTURES)) {\n\t\t\tR_TraceAPI(\"... glBindTextures() - not using\");\n\t\t}\n\t\tif (GL_WorkaroundNeeded(R_BROKEN_PREFERMULTIDRAW)) {\n\t\t\tR_TraceAPI(\"... glDrawArray() - using glMultiDrawArrays()\");\n\t\t}\n\t}\n\n\tif (R_UseModernOpenGL() && shaders_supported) {\n\t\tCon_Printf(\"&c0f0Renderer&r: OpenGL (GLSL)\\n\");\n\t\treturn true;\n\t}\n\telse if (R_UseImmediateOpenGL()) {\n\t\tCon_Printf(\"&c0f0Renderer&r: OpenGL (classic)\\n\");\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic void OnChange_gl_ext_texture_compression(cvar_t *var, char *string, qbool *cancel)\n{\n\tif (renderer.TextureCompressionSet) {\n\t\trenderer.TextureCompressionSet(Q_atof(string));\n\t}\n}\n\n/************************************** GL INIT **************************************/\n\nstatic qbool GL_BrokenAmdVersion(void)\n{\n\tif (COM_CheckParm(cmdline_param_client_no_amd_fix)) {\n\t\t// user has asked for driver detection to be turned off\n\t\treturn false;\n\t}\n\n\tif (!strstr((const char*)glConfig.vendor_string, \"ATI\")) {\n\t\treturn false;\n\t}\n\n\t// <anything>.13399 <anything>, might get different version number depending on what we asked for previously\n\treturn strstr((const char*)glConfig.version_string, \".13399 \") != NULL;\n}\n\nstatic void GL_PopulateConfig(void)\n{\n\tint r, g, b, a;\n\n\tSDL_GL_GetAttribute(SDL_GL_RED_SIZE, &r);\n\tSDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &g);\n\tSDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &b);\n\tSDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &a);\n\n\tglConfig.colorBits = r + g + b + a;\n\tSDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &glConfig.depthBits);\n\tSDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &glConfig.stencilBits);\n\n\tglConfig.vendor_string = glGetString(GL_VENDOR);\n\tglConfig.renderer_string = glGetString(GL_RENDERER);\n\tglConfig.version_string = glGetString(GL_VERSION);\n\n\tif (glConfig.version_string) {\n\t\tconst char* dot = strchr((const char*)glConfig.version_string, '.');\n\t\tglConfig.majorVersion = atoi((const char*)glConfig.version_string);\n\t\tglConfig.minorVersion = (dot ? atoi(dot + 1) : 0);\n\t}\n\telse {\n\t\tglConfig.majorVersion = 2;\n\t\tglConfig.minorVersion = 1;\n\t}\n\n\tif (GL_VersionAtLeast(3, 2)) {\n\t\tGLint profile = 0;\n\t\tglGetIntegerv(GL_CONTEXT_PROFILE_MASK, &profile);\n\t\tglConfig.coreProfile = (profile & GL_CONTEXT_CORE_PROFILE_BIT) != 0;\n\t}\n\telse {\n\t\tglConfig.coreProfile = false;\n\t}\n\n\tglGetIntegerv(GL_MAX_TEXTURE_SIZE, &glConfig.gl_max_size_default);\n\tif (R_UseImmediateOpenGL()) {\n\t\tif (GL_VersionAtLeast(2, 1)) {\n\t\t\tglGetIntegerv(GL_MAX_TEXTURE_UNITS, &glConfig.texture_units);\n\t\t}\n\t\telse if (SDL_GL_ExtensionSupported(\"GL_ARB_multitexture\")) {\n\t\t\tglGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &glConfig.texture_units);\n\t\t}\n\t\telse {\n\t\t\tglConfig.texture_units = 1;\n\t\t}\n\t}\n\telse {\n\t\tglGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &glConfig.texture_units);\n\t}\n\tglConfig.texture_units = max(glConfig.texture_units, 1);\n\n\tglConfig.max_3d_texture_size = 0;\n\tglConfig.glsl_version = (unsigned char*)\"0\";\n\tglConfig.max_texture_depth = 0;\n\tglConfig.uniformBufferOffsetAlignment = 0;\n\tglConfig.shaderStorageBufferOffsetAlignment = 0;\n\tif (GL_VersionAtLeast(2, 0)) {\n\t\tglGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &glConfig.max_3d_texture_size);\n\t\tglConfig.glsl_version = glGetString(GL_SHADING_LANGUAGE_VERSION);\n\n\t\tif (GL_VersionAtLeast(3, 0)) {\n\t\t\tglGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &glConfig.max_texture_depth);\n\n\t\t\tif (GL_VersionAtLeast(3, 1)) {\n\t\t\t\tglGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &glConfig.uniformBufferOffsetAlignment);\n\n\t\t\t\tif (GL_VersionAtLeast(4, 3)) {\n\t\t\t\t\tglGetIntegerv(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, &glConfig.shaderStorageBufferOffsetAlignment);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tR_TraceAPI(\"---Config---\");\n\tif (glConfig.renderer_string) {\n\t\tR_TraceAPI(\"Renderer: %s\", glConfig.renderer_string);\n\t}\n\tif (glConfig.vendor_string) {\n\t\tR_TraceAPI(\"Vendor: %s\", glConfig.vendor_string);\n\t}\n\tif (glConfig.version_string) {\n\t\tR_TraceAPI(\"Version: %s\", glConfig.version_string);\n\t}\n\tif (glConfig.glsl_version) {\n\t\tR_TraceAPI(\"GLSL: %s\", glConfig.glsl_version);\n\t}\n\n\tR_TraceAPI(\"OpenGL version: %d.%d\", glConfig.majorVersion, glConfig.minorVersion);\n\tR_TraceAPI(\"Color/depth/stencil: %d/%d/%d\", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits);\n\tR_TraceAPI(\"Hardware type: %d\", glConfig.hardwareType);\n\tR_TraceAPI(\"Max sizes: %d %d %d\", glConfig.gl_max_size_default, glConfig.max_3d_texture_size, glConfig.max_texture_depth);\n\tR_TraceAPI(\"Texture units: %d\", glConfig.texture_units);\n\tR_TraceAPI(\"Alignments: ubo(%d) ssb(%d)\", glConfig.uniformBufferOffsetAlignment, glConfig.shaderStorageBufferOffsetAlignment);\n\n\tglConfig.amd_issues = GL_BrokenAmdVersion();\n}\n\n// meag: EXT => ARB didn't change value of constants, so still using _EXT versions\nstatic void GL_CheckAnisotropyExtensions(void)\n{\n\tanisotropy_ext = 0;\n\tR_TraceAPI(\"Checking for anisotropic filtering...\");\n\tif (GL_VersionAtLeast(4, 6) || SDL_GL_ExtensionSupported(\"GL_ARB_texture_filter_anisotropic\") || SDL_GL_ExtensionSupported(\"GL_EXT_texture_filter_anisotropic\")) {\n\t\tint gl_anisotropy_factor_max;\n\n\t\tanisotropy_ext = 1;\n\n\t\tglGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gl_anisotropy_factor_max);\n\n\t\tCom_Printf_State(PRINT_OK, \"Anisotropic Filtering Extension Found (%d max)\\n\", gl_anisotropy_factor_max);\n\t\tR_TraceAPI(\"... filtering extension found (max level %d)\", gl_anisotropy_factor_max);\n\t}\n}\n\nstatic void GL_CheckTextureCompressionExtensions(void)\n{\n\tR_TraceAPI(\"Checking for texture compression...\");\n\tif (GL_VersionAtLeast(1, 3) || SDL_GL_ExtensionSupported(\"GL_ARB_texture_compression\")) {\n\t\tCom_Printf_State(PRINT_OK, \"Texture compression extensions found\\n\");\n\t\tR_TraceAPI(\"... texture compression extensions found\");\n\t\tCvar_SetCurrentGroup(CVAR_GROUP_TEXTURES);\n\t\tCvar_Register(&gl_ext_texture_compression);\n\t\tCvar_ResetCurrentGroup();\n\t}\n}\n\nstatic void GL_CheckNPOTTextureExtensions(void)\n{\n\tqbool supported = false;\n\n\tif (!COM_CheckParm(cmdline_param_client_no_npot_textures)) {\n\t\tsupported = (GL_VersionAtLeast(2, 0) || SDL_GL_ExtensionSupported(\"GL_ARB_texture_non_power_of_two\"));\n\t}\n\n\tR_SetNonPowerOfTwoSupport(supported);\n\tCom_Printf_State(PRINT_OK, \"GL_ARB_texture_non_power_of_two extension %s\\n\", supported ? \"found\" : \"not found\");\n\tR_TraceAPI(\"Non-power-of-two textures: %s\", supported ? \"supported\" : \"not supported (!)\");\n}\n\nstatic void GL_CheckExtensions(void)\n{\n\tGL_CheckMultiTextureExtensions();\n\tGL_CheckAnisotropyExtensions();\n\tGL_CheckTextureCompressionExtensions();\n\tGL_CheckNPOTTextureExtensions();\n}\n\nvoid GL_Init(void)\n{\n\tGL_PopulateConfig();\n\n\tif (!GL_InitialiseRenderer()) {\n\t\tSys_Error(\"Failed to initialise graphics renderer\");\n\t}\n\n\tGL_CheckExtensions();\n\n#if !defined( _WIN32 ) && !defined( __linux__ ) /* we print this in different place on WIN and Linux */\n\t{\n\t\t/* FIXME/TODO: FreeBSD too? */\n\t\tCom_Printf_State(PRINT_INFO, \"GL_VENDOR: %s\\n\", (const char*)glGetString(GL_VENDOR));\n\t\tCom_Printf_State(PRINT_INFO, \"GL_RENDERER: %s\\n\", (const char*)glGetString(GL_RENDERER));\n\t\tCom_Printf_State(PRINT_INFO, \"GL_VERSION: %s\\n\", (const char*)glGetString(GL_VERSION));\n\t\tif (COM_CheckParm(cmdline_param_client_printopenglextensions)) {\n\t\t\tconst char* gl_extensions = \"\";\n\t\t\tif (R_UseModernOpenGL()) {\n\t\t\t\tgl_extensions = \"(using modern OpenGL)\\n\";\n\t\t\t}\n\t\t\telse {\n\t\t\t\tgl_extensions = (const char*)glGetString(GL_EXTENSIONS);\n\t\t\t}\n\t\t\tCom_Printf_State(PRINT_INFO, \"GL_EXTENSIONS: %s\\n\", gl_extensions);\n\t\t}\n\t}\n#endif\n}\n"
  },
  {
    "path": "src/vid_sdl2.c",
    "content": "/*\n   ===========================================================================\n   Copyright (C) 1999-2005 Id Software, Inc.\n\n   This file is part of Quake III Arena source code.\n\n   Quake III Arena source code is free software; you can redistribute it\n   and/or modify it under the terms of the GNU General Public License as\n   published by the Free Software Foundation; either version 2 of the License,\n   or (at your option) any later version.\n\n   Quake III Arena source code is distributed in the hope that it will be\n   useful, but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n   GNU General Public License for more details.\n\n   You should have received a copy of the GNU General Public License\n   along with Foobar; if not, write to the Free Software\n   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n   ===========================================================================\n\n */\n\n#include \"quakedef.h\"\n\n#include <SDL.h>\n#include <SDL_syswm.h>\n\n#ifdef X11_GAMMA_WORKAROUND\n#include <X11/extensions/xf86vmode.h>\n#endif\n\n#ifdef _WIN32\n#include <windows.h>\n\nvoid Sys_ActiveAppChanged (void);\n#endif\n\n#ifdef __APPLE__\n#include <OpenGL/gl.h>\n#include <OpenGL/OpenGL.h>\n#include \"in_osx.h\"\n#else\n#include <GL/gl.h>\n#endif\n\n#include \"ezquake-icon.c\"\n#include \"keys.h\"\n#include \"tr_types.h\"\n#include \"input.h\"\n#include \"rulesets.h\"\n#include \"utils.h\"\n#include \"textencoding.h\"\n\n#include \"r_local.h\"\n#include \"gl_framebuffer.h\"\n#include \"r_texture.h\"\n#include \"qmb_particles.h\"\n#include \"r_state.h\"\n#include \"r_buffers.h\"\n#include \"r_renderer.h\"\n#include \"r_program.h\"\n\nSDL_GLContext GLM_SDL_CreateContext(SDL_Window* window);\nSDL_GLContext GLC_SDL_CreateContext(SDL_Window* window);\n\n#ifdef __linux__\n// This is hack to ignore keyboard events we receive between FOCUS_GAINED & TAKE_FOCUS\n// Without it the keys you press to switch back to ezQuake will fire, which is probably not desired\n// Affects X11 only - might also be needed on FreeBSD/OSX?\nstatic qbool block_keyboard_input = false;\n#endif\n#ifdef __APPLE__\nstatic int deadkey_modifiers_held_down = 0;\nstatic cvar_t in_ignore_deadkeys = { \"in_ignore_deadkeys\", \"1\", CVAR_SILENT };\n\n#define APPLE_RALT_HELD_DOWN 1\n#define APPLE_LALT_HELD_DOWN 2\n#endif\n\n#define\tWINDOW_CLASS_NAME\t\"ezQuake\"\n\n#define VID_RENDERER_MIN 0\n#define VID_RENDERER_MAX 1\n\n#define VID_MULTISAMPLED   1\n#define VID_ACCELERATED    2\n#define VID_DEPTHBUFFER24  4\n#define VID_GAMMACORRECTED 8\n\n/* FIXME: This should be in a header file and it probably shouldn't be called TP_\n *        since there are a lot of triggers that has nothing to do with teamplay.\n *        Should probably split them\n */\nextern void TP_ExecTrigger(const char *);\n\nstatic void in_raw_callback(cvar_t *var, char *value, qbool *cancel);\nstatic void in_grab_windowed_mouse_callback(cvar_t *var, char *value, qbool *cancel);\nstatic void conres_changed_callback (cvar_t *var, char *string, qbool *cancel);\nstatic void framebuffer_smooth_changed_callback(cvar_t* var, char* string, qbool* cancel);\nstatic void vid_reload_callback(cvar_t* var, char* string, qbool* cancel);\nstatic void GrabMouse(qbool grab, qbool raw);\nstatic void HandleEvents(void);\nstatic void VID_UpdateConRes(void);\nvoid IN_Restart_f(void);\n\nstatic SDL_Window       *sdl_window;\nstatic SDL_GLContext    sdl_context;\n\nglconfig_t glConfig;\nqbool vid_hwgamma_enabled = false;\nstatic qbool mouse_active = false;\nqbool mouseinitialized = false; // unfortunately non static, lame...\nint mx, my;\nstatic int old_x = 0, old_y = 0;\nextern double cursor_x, cursor_y;\n\nqbool ActiveApp = true;\nqbool Minimized = false;\n\ndouble vid_vsync_lag;\ndouble vid_last_swap_time;\n\nstatic SDL_DisplayMode *modelist;\nstatic int modelist_count;\n\n#ifdef X11_GAMMA_WORKAROUND\nstatic unsigned short sysramps[3*4096];\n#endif\n\nqbool vid_initialized = false;\n\nstatic int last_working_width;\nstatic int last_working_height;\nstatic int last_working_hz;\nstatic int last_working_display;\nstatic qbool last_working_values = false;\n\n// deferred events (Sys_SendDeferredKeyEvents)\nstatic qbool wheelup_deferred = false;\nstatic qbool wheeldown_deferred = false;\n\n// vid_reload\n#define CVAR_RELOAD_GFX_COMMAND \"vid_reload\"\nstatic qbool vid_reload_pending = false;\nstatic cvar_t vid_reload_auto = { \"vid_reload_auto\", \"1\", 0, vid_reload_callback };\n\nstatic void vid_reload_callback(cvar_t* var, char* string, qbool* cancel)\n{\n\tvid_reload_pending = false;\n\n\tif (atoi(string) != 0) {\n\t\tvid_reload_pending = Cvar_AnyModified(CVAR_RELOAD_GFX);\n\t}\n}\n\n//\n// OS dependent cvar defaults\n//\n#ifdef __APPLE__\n\t#define CVAR_DEF1 \"0\"\n#else\n\t#define CVAR_DEF1 \"1\"\n#endif\n\n#if defined(__linux__) || defined(__FreeBSD__)\n\t#define CVAR_DEF2 \"0\"\n#else\n\t#define CVAR_DEF2 \"1\"\n#endif\n\n//\n// cvars\n//\nextern cvar_t sys_inactivesleep;\n\n// latched variables that can only change over a restart\ncvar_t r_colorbits                = {\"vid_colorbits\",              \"0\",       CVAR_LATCH_GFX };\ncvar_t r_24bit_depth              = {\"vid_24bit_depth\",            \"1\",       CVAR_LATCH_GFX };\ncvar_t r_fullscreen               = {\"vid_fullscreen\",             \"1\",       CVAR_LATCH_GFX };\ncvar_t r_displayRefresh           = {\"vid_displayfrequency\",       \"0\",       CVAR_LATCH_GFX | CVAR_AUTO };\ncvar_t vid_displayNumber          = {\"vid_displaynumber\",          \"0\",       CVAR_LATCH_GFX | CVAR_AUTO };\ncvar_t vid_usedesktopres          = {\"vid_usedesktopres\",          \"1\",       CVAR_LATCH_GFX | CVAR_AUTO };\ncvar_t vid_win_borderless         = {\"vid_win_borderless\",         \"0\",       CVAR_LATCH_GFX };\ncvar_t vid_width                  = {\"vid_width\",                  \"0\",       CVAR_LATCH_GFX | CVAR_AUTO };\ncvar_t vid_height                 = {\"vid_height\",                 \"0\",       CVAR_LATCH_GFX | CVAR_AUTO };\ncvar_t vid_win_width              = {\"vid_win_width\",              \"640\",     CVAR_LATCH_GFX };\ncvar_t vid_win_height             = {\"vid_win_height\",             \"480\",     CVAR_LATCH_GFX };\n#ifdef __APPLE__\ncvar_t vid_hwgammacontrol         = {\"vid_hwgammacontrol\",         \"2\",       CVAR_LATCH_GFX };\n#else\ncvar_t vid_hwgammacontrol         = {\"vid_hwgammacontrol\",         \"0\",       CVAR_LATCH_GFX };\n#endif\ncvar_t vid_minimize_on_focus_loss = {\"vid_minimize_on_focus_loss\", CVAR_DEF1, CVAR_LATCH_GFX };\n// TODO: Move the in_* cvars\ncvar_t in_raw                     = {\"in_raw\",                     \"1\",       CVAR_ARCHIVE | CVAR_SILENT, in_raw_callback};\ncvar_t in_grab_windowed_mouse     = {\"in_grab_windowed_mouse\",     \"1\",       CVAR_ARCHIVE | CVAR_SILENT, in_grab_windowed_mouse_callback};\ncvar_t vid_grab_keyboard          = {\"vid_grab_keyboard\",          \"0\",       CVAR_LATCH_GFX }; /* Needs vid_restart thus vid_.... */\n#ifdef EZ_MULTIPLE_RENDERERS\ncvar_t vid_renderer               = {\"vid_renderer\",               \"1\",       CVAR_LATCH_GFX };\n#endif\ncvar_t vid_gl_core_profile        = {\"vid_gl_core_profile\",        \"0\",       CVAR_LATCH_GFX };\n\n#ifdef X11_GAMMA_WORKAROUND\ncvar_t vid_gamma_workaround       = {\"vid_gamma_workaround\",       \"1\",       CVAR_LATCH_GFX };\n#endif\n\ncvar_t in_release_mouse_modes     = {\"in_release_mouse_modes\",     \"2\",       CVAR_SILENT };\ncvar_t in_ignore_touch_events     = {\"in_ignore_touch_events\",     \"1\",       CVAR_SILENT };\n#ifdef __linux__\ncvar_t in_ignore_unfocused_keyb   = {\"in_ignore_unfocused_keyb\",   \"0\",       CVAR_SILENT };\n#endif\ncvar_t vid_vsync_lag_fix          = {\"vid_vsync_lag_fix\",          \"0\"                    };\ncvar_t vid_vsync_lag_tweak        = {\"vid_vsync_lag_tweak\",        \"1.0\"                  };\ncvar_t r_swapInterval             = {\"vid_vsync\",                  \"0\",       CVAR_SILENT };\ncvar_t r_win_save_pos             = {\"vid_win_save_pos\",           \"1\",       CVAR_SILENT };\ncvar_t r_win_save_size            = {\"vid_win_save_size\",          \"1\",       CVAR_SILENT };\ncvar_t vid_xpos                   = {\"vid_xpos\",                   \"3\",       CVAR_SILENT };\ncvar_t vid_ypos                   = {\"vid_ypos\",                   \"39\",      CVAR_SILENT };\ncvar_t vid_win_displayNumber      = {\"vid_win_displaynumber\",      \"0\",       CVAR_SILENT };\ncvar_t r_conwidth                 = {\"vid_conwidth\",               \"0\",       CVAR_NO_RESET | CVAR_SILENT | CVAR_AUTO, conres_changed_callback };\ncvar_t r_conheight                = {\"vid_conheight\",              \"0\",       CVAR_NO_RESET | CVAR_SILENT | CVAR_AUTO, conres_changed_callback };\ncvar_t r_conscale                 = {\"vid_conscale\",               \"2.0\",     CVAR_NO_RESET | CVAR_SILENT, conres_changed_callback };\ncvar_t vid_flashonactivity        = {\"vid_flashonactivity\",        \"1\",       CVAR_SILENT };\ncvar_t r_verbose                  = {\"vid_verbose\",                \"0\",       CVAR_SILENT };\ncvar_t r_showextensions           = {\"vid_showextensions\",         \"0\",       CVAR_SILENT };\ncvar_t gl_multisamples            = {\"gl_multisamples\",            \"0\",       CVAR_LATCH_GFX | CVAR_AUTO }; // It's here because it needs to be registered before window creation\ncvar_t vid_gammacorrection        = {\"vid_gammacorrection\",        \"0\",       CVAR_LATCH_GFX };\n#ifdef __APPLE__\ncvar_t vid_software_palette       = {\"vid_software_palette\",       \"0\",       CVAR_NO_RESET | CVAR_LATCH_GFX };\n#else\ncvar_t vid_software_palette       = {\"vid_software_palette\",       \"1\",       CVAR_NO_RESET | CVAR_LATCH_GFX };\n#endif\n\ncvar_t vid_framebuffer             = {\"vid_framebuffer\",               \"0\",       CVAR_NO_RESET | CVAR_LATCH_GFX, conres_changed_callback };\ncvar_t vid_framebuffer_blit        = {\"vid_framebuffer_blit\",          \"0\",       CVAR_NO_RESET };\ncvar_t vid_framebuffer_width       = {\"vid_framebuffer_width\",         \"0\",       CVAR_NO_RESET | CVAR_AUTO, conres_changed_callback };\ncvar_t vid_framebuffer_height      = {\"vid_framebuffer_height\",        \"0\",       CVAR_NO_RESET | CVAR_AUTO, conres_changed_callback };\ncvar_t vid_framebuffer_scale       = {\"vid_framebuffer_scale\",         \"1\",       CVAR_NO_RESET, conres_changed_callback };\ncvar_t vid_framebuffer_depthformat = {\"vid_framebuffer_depthformat\",   \"0\",       CVAR_NO_RESET | CVAR_LATCH_GFX };\ncvar_t vid_framebuffer_hdr         = {\"vid_framebuffer_hdr\",           \"0\",       CVAR_NO_RESET | CVAR_LATCH_GFX };\ncvar_t vid_framebuffer_hdr_tonemap = {\"vid_framebuffer_hdr_tonemap\",   \"0\" };\ncvar_t vid_framebuffer_smooth      = {\"vid_framebuffer_smooth\",        \"1\",       CVAR_NO_RESET, framebuffer_smooth_changed_callback };\ncvar_t vid_framebuffer_sshotmode   = {\"vid_framebuffer_sshotmode\",     \"0\" };\ncvar_t vid_framebuffer_multisample = {\"vid_framebuffer_multisample\",   \"0\" };\ncvar_t vid_framebuffer_fxaa        = {\"vid_framebuffer_fxaa\",          \"0\" };\n\n//\n// function declaration\n//\n\n// True if we need to release the mouse and let the OS show cursor again\nstatic qbool IN_OSMouseCursorRequired(void)\n{\n\t// Explicit check here for key_game... really setting all modes is equivalent to \"in_grab_windowed_mouse 0\"\n\tqbool in_os_cursor_mode = (key_dest != key_game || cls.demoplayback) && (in_release_mouse_modes.integer & (1 << key_dest));\n\n\t// Windowed & (not-grabbing mouse | in OS cursor mode)\n\treturn (!r_fullscreen.value && (!in_grab_windowed_mouse.value || in_os_cursor_mode));\n}\n\n// True if we're in a mode where we need to keep track of mouse movement\nqbool IN_MouseTrackingRequired(void)\n{\n\treturn (key_dest == key_menu || key_dest == key_hudeditor || key_dest == key_demo_controls);\n}\n\n// True if we need to display the internal Quake cursor to track the mouse\nqbool IN_QuakeMouseCursorRequired(void)\n{\n\treturn mouse_active && IN_MouseTrackingRequired() && !IN_OSMouseCursorRequired();\n}\n\nstatic void IN_SnapMouseBackToCentre(void)\n{\n\tSDL_WarpMouseInWindow(sdl_window, glConfig.vidWidth / 2, glConfig.vidHeight / 2);\n\told_x = glConfig.vidWidth / 2;\n\told_y = glConfig.vidHeight / 2;\n}\n\nstatic void in_raw_callback(cvar_t *var, char *value, qbool *cancel)\n{\n\tif ((var == &in_raw) && (atoi(value) != in_raw.value)) {\n\t\tCvar_SetValue(&in_raw, atoi(value));\n\t\tIN_Restart_f();\n\t}\n}\n\nstatic void in_grab_windowed_mouse_callback(cvar_t *val, char *value, qbool *cancel)\n{\n\tGrabMouse((atoi(value) > 0 ? true : false), in_raw.integer);\n}\n\nstatic void GrabMouse(qbool grab, qbool raw)\n{\n\tif ((grab && mouse_active && raw == in_raw.integer) || (!grab && !mouse_active) || !mouseinitialized || !sdl_window) {\n\t\treturn;\n\t}\n\n\tif (!r_fullscreen.integer && in_grab_windowed_mouse.integer == 0) {\n\t\tif (!mouse_active) {\n\t\t\treturn;\n\t\t}\n\t\tgrab = 0;\n\t}\n\n\t// set initial position\n\tif (!raw && grab) {\n\t\t// the first getState() will still return the old values so snapping back doesn't work...\n\t\t// ... open problem, people will get a jump if releasing mouse when re-grabbing with in_raw 0\n\t\tIN_SnapMouseBackToCentre();\n\t}\n\n\tSDL_SetWindowGrab(sdl_window, grab ? SDL_TRUE : SDL_FALSE);\n\tSDL_SetRelativeMouseMode((raw && grab) ? SDL_TRUE : SDL_FALSE);\n\tSDL_GetRelativeMouseState(NULL, NULL);\n\n\t// never show real cursor in fullscreen\n\tif (r_fullscreen.integer) {\n\t\tSDL_ShowCursor(SDL_DISABLE);\n\t} else {\n\t\tSDL_ShowCursor(grab ? SDL_DISABLE : SDL_ENABLE);\n\t}\n\n\t// Force rewrite of it\n\tSDL_SetCursor(NULL);\n\n\tmouse_active = grab;\n}\n\nvoid IN_StartupMouse(void)\n{\n\tCvar_Register(&in_raw);\n\tCvar_Register(&in_grab_windowed_mouse);\n\tCvar_Register(&in_release_mouse_modes);\n\tCvar_Register(&in_ignore_touch_events);\n#ifdef __APPLE__\n\tCvar_Register(&in_ignore_deadkeys);\n\n\tif (in_raw.integer > 0) {\n\t\tif (OSX_Mouse_Init() != 0) {\n\t\t\tCom_Printf(\"warning: failed to initialize raw input mouse thread...\\n\");\n\t\t\tCvar_SetValue(&in_raw, 0);\n\t\t}\n\t}\n#endif\n\n\tmouseinitialized = true;\n\n\tCom_Printf(\"%s mouse input initialized\\n\", in_raw.integer > 0 ? \"RAW\" : \"SDL\");\n\tif (in_raw.integer == 0) {\n\t\tIN_SnapMouseBackToCentre();\n\t}\n}\n\nvoid IN_ActivateMouse(void)\n{\n\tGrabMouse(true, in_raw.integer);\n}\n\nvoid IN_DeactivateMouse(void)\n{\n\tGrabMouse(false, in_raw.integer);\n}\n\nstatic void IN_Frame(void)\n{\n\tif (!sdl_window) {\n\t\treturn;\n\t}\n\n\tHandleEvents();\n\n\tif (!ActiveApp || Minimized || IN_OSMouseCursorRequired()) {\n\t\tIN_DeactivateMouse();\n\t\treturn;\n\t}\n\telse {\n\t\tIN_ActivateMouse();\n\t}\n\n\tif (mouse_active && SDL_GetRelativeMouseMode()) {\n#ifdef __APPLE__\n\t\tOSX_Mouse_GetMouseMovement(&mx, &my);\n#else\n\t\tSDL_GetRelativeMouseState(&mx, &my);\n#endif\n\t}\n\t\n}\n\nvoid Sys_SendDeferredKeyEvents(void)\n{\n\tif (wheelup_deferred) {\n\t\tKey_Event(K_MWHEELUP, false);\n\t\twheelup_deferred = false;\n\t}\n\tif (wheeldown_deferred) {\n\t\tKey_Event(K_MWHEELDOWN, false);\n\t\twheeldown_deferred = false;\n\t}\n}\n\nvoid Sys_SendKeyEvents(void)\n{\n\tIN_Frame();\n\n\tif (sys_inactivesleep.integer > 0) {\n\t\t// Yield the CPU a little\n\t\tif ((ISPAUSED && (!ActiveApp)) || Minimized || block_drawing) {\n\t\t\tif (!cls.download) {\n\t\t\t\tSDL_Delay(50);\n\t\t\t}\n\t\t\tscr_skipupdate = 1; // no point to draw anything\n\t\t} else if (!ActiveApp) { // Delay a bit less if just not active window\n\t\t\tif (!cls.download) {\n\t\t\t\tSDL_Delay(20);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid IN_Restart_f(void)\n{\n\tqbool old_mouse_active = mouse_active;\n\n\tIN_Shutdown();\n\tIN_Init();\n\n\t// if mouse was active before restart, try to re-activate it\n\tif (old_mouse_active) {\n\t\tIN_ActivateMouse();\n\t}\n}\n\n// Converts co-ordinates for the whole desktop to co-ordinates for a specific display\nstatic void VID_RelativePositionFromAbsolute(int* x, int* y, int* display)\n{\n\tint displays = SDL_GetNumVideoDisplays();\n\tint i = 0;\n\n\tfor (i = 0; i < displays; ++i)\n\t{\n\t\tSDL_Rect bounds;\n\n\t\tif (SDL_GetDisplayBounds(i, &bounds) == 0)\n\t\t{\n\t\t\tif (*x >= bounds.x && *x < bounds.x + bounds.w && *y >= bounds.y && *y < bounds.y + bounds.h)\n\t\t\t{\n\t\t\t\t*x = *x - bounds.x;\n\t\t\t\t*y = *y - bounds.y;\n\t\t\t\t*display = i;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t*display = 0;\n}\n\n// Converts co-ordinates for a specific display to those for whole desktop\nstatic void VID_AbsolutePositionFromRelative(int* x, int* y, int* display)\n{\n\tSDL_Rect bounds;\n\t\n\t// Try and get bounds for the specified display - default back to main display if there's an issue\n\tif (SDL_GetDisplayBounds(*display, &bounds))\n\t{\n\t\t*display = 0;\n\t\tif (SDL_GetDisplayBounds(*display, &bounds))\n\t\t{\n\t\t\t// Still an issue - reset back to top-left of screen\n\t\t\tCom_Printf(\"Error detecting resolution...\\n\");\n\t\t\t*x = *y = 0;\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// Adjust co-ordinates, making sure some of the window will always be visible\n\t*x = bounds.x + min(*x, bounds.w - 30);\n\t*y = bounds.y + min(*y, bounds.h - 30);\n}\n\nstatic int VID_SetDeviceGammaRampReal(unsigned short *ramps)\n{\n#ifdef X11_GAMMA_WORKAROUND\n\tstatic short once = 1;\n\tstatic short gamma_works = 0;\n\n\tif (!vid_gamma_workaround.integer) {\n\t\tSDL_SetWindowGammaRamp(sdl_window, ramps, ramps + 4096, ramps + (2 * 4096));\n\t\tvid_hwgamma_enabled = true;\n\t\treturn 0;\n\t}\n\n\tif (once) {\n\t\tif (glConfig.gammacrap.size < 0 || glConfig.gammacrap.size > 4096) {\n\t\t\tCom_Printf(\"error: gamma size is broken, gamma won't work (reported size %d)\\n\", glConfig.gammacrap.size);\n\t\t\tonce = 0;\n\t\t\treturn 0;\n\t\t}\n\t\tif (!XF86VidModeGetGammaRamp(glConfig.gammacrap.display, glConfig.gammacrap.screen, glConfig.gammacrap.size, sysramps, sysramps + 4096, sysramps + (2 * 4096))) {\n\t\t\tCom_Printf(\"error: cannot get system gamma ramps, gamma won't work\\n\");\n\t\t\tonce = 0;\n\t\t\treturn 0;\n\t\t}\n\t\tonce = 0;\n\t\tgamma_works = 1;\n\t}\n\n\tif (gamma_works) {\n\t\t/* Just double check the gamma size... */\n\t\tif (glConfig.gammacrap.size < 0 || glConfig.gammacrap.size > 4096) {\n\t\t\tCom_Printf(\"error: gamma size broken but worked initially, wtf?! gamma won't work\\n\");\n\t\t\tgamma_works = 0;\n\t\t\tvid_hwgamma_enabled = false;\n\t\t}\n\t\t/* It returns true unconditionally ... */\n\t\tXF86VidModeSetGammaRamp(glConfig.gammacrap.display, glConfig.gammacrap.screen, glConfig.gammacrap.size, ramps, ramps + 4096, ramps + (2 * 4096));\n\t\tvid_hwgamma_enabled = true;\n\t}\n\treturn 0;\n#else\n\tif (SDL_SetWindowGammaRamp(sdl_window, ramps, ramps + 256, ramps + 512) == 0) {\n\t\tvid_hwgamma_enabled = true;\n\t\treturn 0;\n\t}\n\treturn -1;\n#endif\n}\n\n#ifdef X11_GAMMA_WORKAROUND\nstatic void VID_RestoreSystemGamma(void)\n{\n\tif (!sdl_window || COM_CheckParm(cmdline_param_client_nohardwaregamma)) {\n\t\treturn;\n\t}\n\tVID_SetDeviceGammaRampReal(sysramps);\n}\n#endif\n\nstatic void window_event(SDL_WindowEvent *event)\n{\n\textern qbool scr_skipupdate;\n\tint flags = SDL_GetWindowFlags(sdl_window);\n\n\tswitch (event->event) {\n\t\tcase SDL_WINDOWEVENT_MINIMIZED:\n\t\t\tMinimized = true;\n\n\t\tcase SDL_WINDOWEVENT_FOCUS_LOST:\n\t\t\tActiveApp = false;\n#ifdef __linux__\n\t\t\tblock_keyboard_input = in_ignore_unfocused_keyb.integer;\n#endif\n#ifdef X11_GAMMA_WORKAROUND\n\t\t\tif (vid_gamma_workaround.integer) {\n\t\t\t\tif (Minimized || vid_hwgammacontrol.integer != 3) {\n\t\t\t\t\tVID_RestoreSystemGamma();\n\t\t\t\t}\n\t\t\t}\n#endif\n#ifdef _WIN32\n\t\t\tSys_ActiveAppChanged ();\n#endif\n\t\t\tbreak;\n\n\t\tcase SDL_WINDOWEVENT_FOCUS_GAINED:\n\t\t\tTP_ExecTrigger(\"f_focusgained\");\n\t\t\t/* Fall through */\n\t\tcase SDL_WINDOWEVENT_RESTORED:\n\t\t\tMinimized = false;\n\t\t\tActiveApp = true;\n\t\t\tscr_skipupdate = 0;\n#ifdef X11_GAMMA_WORKAROUND\n\t\t\tif (vid_gamma_workaround.integer) {\n\t\t\t\tv_gamma.modified = true;\n\t\t\t}\n#endif\n#ifdef _WIN32\n\t\t\tSys_ActiveAppChanged ();\n#endif\n\t\t\tbreak;\n\n\t\tcase SDL_WINDOWEVENT_MOVED:\n\t\t\tif (!(flags & SDL_WINDOW_FULLSCREEN) && r_win_save_pos.integer) {\n\t\t\t\tint displayNumber = 0;\n\t\t\t\tint x = event->data1;\n\t\t\t\tint y = event->data2;\n\n\t\t\t\tVID_RelativePositionFromAbsolute(&x, &y, &displayNumber);\n\n\t\t\t\tCvar_SetValue(&vid_win_displayNumber, displayNumber);\n\t\t\t\tCvar_SetValue(&vid_xpos, x);\n\t\t\t\tCvar_SetValue(&vid_ypos, y);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase SDL_WINDOWEVENT_RESIZED:\n\t\t\tif (!(flags & SDL_WINDOW_FULLSCREEN)) {\n\t\t\t\tglConfig.vidWidth = event->data1;\n\t\t\t\tglConfig.vidHeight = event->data2;\n\t\t\t\tif (r_win_save_size.integer) {\n\t\t\t\t\tCvar_LatchedSetValue(&vid_win_width, event->data1);\n\t\t\t\t\tCvar_LatchedSetValue(&vid_win_height, event->data2);\n\t\t\t\t}\n\t\t\t\tif (!r_conwidth.integer || !r_conheight.integer)\n\t\t\t\t\tVID_UpdateConRes();\n\t\t\t}\n\t\t\tif (renderer.InvalidateViewport)\n\t\t\t\trenderer.InvalidateViewport();\n\t\t\tbreak;\n\n#ifdef __linux__\n\t\tcase SDL_WINDOWEVENT_TAKE_FOCUS:\n\t\t\t// On X, sequence is FOCUS_GAINED, [Keyboard 'down' events], TAKE_FOCUS\n\t\t\t// On Windows, it's just FOCUS_GAINED then TAKE_FOCUS, so nothing to block really\n\t\t\tblock_keyboard_input = false;\n\t\t\tbreak;\n#endif\n\t}\n}\n\n// FIXME: APPLE K_F13-15 etc...\n\nstatic const byte scantokey[128] = {\n//  0               1               2               3               4               5               6                   7\n//  8               9               A               B               C               D               E                   F\n    0,              0,              0,              0,              'a',            'b',            'c',                'd',            // 0\n    'e',            'f',            'g',            'h',            'i',            'j',            'k',                'l',\n    'm',            'n',            'o',            'p',            'q',            'r',            's',                't',            // 1\n    'u',            'v',            'w',            'x',            'y',            'z',            '1',                '2',\n    '3',            '4',            '5',            '6',            '7',            '8',            '9',                '0',            // 2\n    K_ENTER,        K_ESCAPE,       K_BACKSPACE,    K_TAB,          K_SPACE,        '-',            '=',                '[',\n    ']',            '\\\\',           0,              ';',            '\\'',           '`',            ',',                '.',            // 3\n    '/' ,           K_CAPSLOCK,     K_F1,           K_F2,           K_F3,           K_F4,           K_F5,               K_F6,\n    K_F7,           K_F8,           K_F9,           K_F10,          K_F11,          K_F12,          K_PRINTSCR,         K_SCRLCK,    // 4\n    K_PAUSE,        K_INS,          K_HOME,         K_PGUP,         K_DEL,          K_END,          K_PGDN,             K_RIGHTARROW,\n    K_LEFTARROW,    K_DOWNARROW,    K_UPARROW,      KP_NUMLOCK,     KP_SLASH,       KP_STAR,        KP_MINUS,           KP_PLUS,        // 5\n    KP_ENTER,       KP_END,         KP_DOWNARROW,   KP_PGDN,        KP_LEFTARROW,   KP_5,           KP_RIGHTARROW,      KP_HOME,\n    KP_UPARROW,     KP_PGUP,        KP_INS,         KP_DEL,         K_ISO,          K_MENU,         0,                  0,              // 6\n    0,              0,              0,              0,              0,              0,              0,                  0,\n    0,              0,              0,              0,              0,              0,              K_MENU,             0,              // 7\n#ifdef __APPLE__\n    K_LCTRL,        K_LSHIFT,       K_LALT,         K_CMD,          K_RCTRL,        K_RSHIFT,       K_RALT,             K_CMD,         // E\n#else\n    K_LCTRL,        K_LSHIFT,       K_LALT,         K_LWIN,         K_RCTRL,        K_RSHIFT,       K_RALT,             K_RWIN,         // E\n#endif\n};\n\nbyte Key_ScancodeToQuakeCode(int scancode)\n{\n\tbyte quakeCode = 0;\n\tif (scancode < 120)\n\t\tquakeCode = scantokey[scancode];\n\telse if (scancode >= 224 && scancode < 224 + 8)\n\t\tquakeCode = scantokey[scancode - 104];\n\n\tif (cl_keypad.integer == 0 || key_dest == key_menu || (cl_keypad.integer == 2 && !(key_dest == key_console || key_dest == key_message))) {\n\t\t// compatibility mode without knowledge about keypad-keys:\n\t\tswitch (quakeCode)\n\t\t{\n\t\tcase KP_NUMLOCK:     quakeCode = K_PAUSE;          break;\n\t\tcase KP_SLASH:       quakeCode = '/';              break;\n\t\tcase KP_STAR:        quakeCode = '*';              break;\n\t\tcase KP_MINUS:       quakeCode = '-';              break;\n\t\tcase KP_HOME:        quakeCode = K_HOME;           break;\n\t\tcase KP_UPARROW:     quakeCode = K_UPARROW;        break;\n\t\tcase KP_PGUP:        quakeCode = K_PGUP;           break;\n\t\tcase KP_LEFTARROW:   quakeCode = K_LEFTARROW;      break;\n\t\tcase KP_5:           quakeCode = '5';              break;\n\t\tcase KP_RIGHTARROW:  quakeCode = K_RIGHTARROW;     break;\n\t\tcase KP_PLUS:        quakeCode = '+';              break;\n\t\tcase KP_END:         quakeCode = K_END;            break;\n\t\tcase KP_DOWNARROW:   quakeCode = K_DOWNARROW;      break;\n\t\tcase KP_PGDN:        quakeCode = K_PGDN;           break;\n\t\tcase KP_INS:         quakeCode = K_INS;            break;\n\t\tcase KP_DEL:         quakeCode = K_DEL;            break;\n\t\tcase KP_ENTER:       quakeCode = K_ENTER;          break;\n\t\tdefault:                                           break;\n\t\t}\n\t}\n\telse if (cl_keypad.integer == 1 && key_dest != key_game) {\n\t\t// Treat as normal return key\n\t\tif (quakeCode == KP_ENTER) {\n\t\t\tquakeCode = K_ENTER;\n\t\t}\n\t}\n\n\treturn quakeCode;\n}\n\nbyte Key_CharacterToQuakeCode(char ch)\n{\n\t// Uses fact that SDLK_a == 'a'... is this okay?\n\t\n\t// Convert from key-code to scan-code to see what physical button they pressed\n\tint scancode = SDL_GetScancodeFromKey(ch);\n\n\treturn Key_ScancodeToQuakeCode(scancode);\n}\n\nwchar Key_Event_TextInput(wchar unichar);\n\nstatic void keyb_textinputevent(char* text)\n{\n\tint i = 0;\n\tint len = 0;\n\twchar unichar = 0;\n\n\t// Only process text input messages here\n\tif (key_dest != key_console && key_dest != key_message)\n\t\treturn;\n\n\tif (!*text)\n\t\treturn;\n\n#ifdef __APPLE__\n\t// operating system is sending deadkey-modified input... ignore\n\tif (deadkey_modifiers_held_down) {\n\t\treturn;\n\t}\n#endif\n\n#ifdef __linux__\n\tif (block_keyboard_input) {\n\t\treturn;\n\t}\n#endif\n\n\tlen = strlen(text);\n\tfor (i = 0; i < len; ++i)\n\t{\n\t\tunichar = TextEncodingDecodeUTF8(text, &i);\n\n\t\tif (unichar)\n\t\t\tKey_Event_TextInput(unichar);\n\t}\n}\n\nstatic void keyb_event(SDL_KeyboardEvent *event)\n{\n\tbyte result = Key_ScancodeToQuakeCode(event->keysym.scancode);\n\n#ifdef __APPLE__\n\tif (in_ignore_deadkeys.integer) {\n\t\t// Apologies for the guesswork, no Apple keyboard...\n\t\tint left_alt = (in_ignore_deadkeys.integer == 2 ? SDLK_LALT : SDLK_LGUI);\n\t\tint right_alt = (in_ignore_deadkeys.integer == 2 ? SDLK_RALT : SDLK_RGUI);\n\n\t\tif (event->keysym.sym == left_alt) {\n\t\t\tdeadkey_modifiers_held_down ^= APPLE_LALT_HELD_DOWN;\n\t\t\tdeadkey_modifiers_held_down |= (event->state ? APPLE_LALT_HELD_DOWN : 0);\n\t\t}\n\t\telse if (event->keysym.sym == right_alt) {\n\t\t\tdeadkey_modifiers_held_down ^= APPLE_RALT_HELD_DOWN;\n\t\t\tdeadkey_modifiers_held_down |= (event->state ? APPLE_RALT_HELD_DOWN : 0);\n\t\t}\n\t}\n#endif\n\n\tif (result == 0) {\n\t\tCom_DPrintf(\"%s: unknown scancode %d\\n\", __func__, event->keysym.scancode);\n\t\treturn;\n\t}\n\n#ifdef __linux__\n\tif (block_keyboard_input) {\n\t\tCom_DPrintf(\"%s: scan-code %d, qchar %d: suppressed\\n\", __func__, event->keysym.scancode, result);\n\t\treturn;\n\t}\n#endif\n\tKey_Event(result, event->state);\n}\n\nstatic void mouse_button_event(SDL_MouseButtonEvent *event)\n{\n\tunsigned key;\n\n\tswitch (event->button) {\n\tcase SDL_BUTTON_LEFT:\n\t\tkey = K_MOUSE1;\n\t\tbreak;\n\tcase SDL_BUTTON_RIGHT:\n\t\tkey = K_MOUSE2;\n\t\tbreak;\n\tcase SDL_BUTTON_MIDDLE:\n\t\tkey = K_MOUSE3;\n\t\tbreak;\n\tcase 8:\n\tcase SDL_BUTTON_X1:\n\t\tkey = K_MOUSE4;\n\t\tbreak;\n\tcase 9:\n\tcase SDL_BUTTON_X2:\n\t\tkey = K_MOUSE5;\n\t\tbreak;\n\tdefault:\n\t\tCom_DPrintf(\"%s: unknown button %d\\n\", __func__, event->button);\n\t\treturn;\n\t}\n\n\tKey_Event(key, event->state);\n}\n\nstatic void mouse_wheel_event(SDL_MouseWheelEvent *event)\n{\n\tif (event->y > 0) {\n\t\tif (wheelup_deferred) {\n\t\t\tKey_Event(K_MWHEELUP, false);\n\t\t}\n\t\tKey_Event(K_MWHEELUP, true);\n\t\twheelup_deferred = true;\n\t}\n\telse if (event->y < 0) {\n\t\tif (wheeldown_deferred) {\n\t\t\tKey_Event(K_MWHEELDOWN, false);\n\t\t}\n\t\tKey_Event(K_MWHEELDOWN, true);\n\t\twheeldown_deferred = true;\n\t}\n}\n\n#if defined(_WIN32) && !defined(WITHOUT_WINKEYHOOK)\nstatic void HandleWindowsKeyboardEvents(unsigned int flags, qbool down)\n{\n\tif (flags & WINDOWS_LWINDOWSKEY) {\n\t\tKey_Event(K_LWIN, down);\n\t}\n\tif (flags & WINDOWS_RWINDOWSKEY) {\n\t\tKey_Event(K_RWIN, down);\n\t}\n\tif (flags & WINDOWS_MENU) {\n\t\tKey_Event(K_MENU, down);\n\t}\n\tif (flags & WINDOWS_PRINTSCREEN) {\n\t\tKey_Event(K_PRINTSCR, down);\n\t}\n\tif (flags & WINDOWS_CAPSLOCK) {\n\t\tKey_Event(K_CAPSLOCK, down);\n\t}\n}\n#endif\n\nstatic void HandleEvents(void)\n{\n\tSDL_Event event;\n\tqbool track_movement_through_state = (mouse_active && !SDL_GetRelativeMouseMode());\n\n#if defined(_WIN32) && !defined(WITHOUT_WINKEYHOOK)\n\tHandleWindowsKeyboardEvents(windows_keys_down, true);\n\tHandleWindowsKeyboardEvents(windows_keys_up, false);\n\n\twindows_keys_down = windows_keys_up = 0;\n#endif\n\n\twhile (SDL_PollEvent(&event)) {\n\t\tswitch (event.type) {\n\t\tcase SDL_QUIT:\n\t\t\tSys_Quit();\n\t\t\tbreak;\n\t\tcase SDL_WINDOWEVENT:\n\t\t\twindow_event(&event.window);\n\t\t\tbreak;\n\t\tcase SDL_KEYDOWN:\n\t\tcase SDL_KEYUP:\n#ifdef __APPLE__\n\t\t\tif (developer.integer == 2) {\n\t\t\t\tCon_Printf(\"key%s event, scan=%d, sym=%d, mod=%d\\n\", event.type == SDL_KEYDOWN ? \"down\" : \"up\", event.key.keysym.scancode, event.key.keysym.sym, event.key.keysym.mod);\n\t\t\t}\n#endif\n\t\t\tkeyb_event(&event.key);\n\t\t\tbreak;\n\t\tcase SDL_TEXTINPUT:\n\t\t\tkeyb_textinputevent(event.text.text);\n\t\t\tbreak;\n\t\tcase SDL_MOUSEMOTION:\n\t\t\tif (event.motion.which != SDL_TOUCH_MOUSEID || !in_ignore_touch_events.integer) {\n#ifdef __APPLE__\n\t\t\t\tif (developer.integer == 2) {\n\t\t\t\t\tCon_Printf(\"motion event, which=%d\\n\", event.motion.which);\n\t\t\t\t}\n#endif\n\t\t\t\tif (!track_movement_through_state) {\n\t\t\t\t\tfloat factor = (IN_MouseTrackingRequired() ? cursor_sensitivity.value : 1);\n\n\t\t\t\t\tcursor_x += event.motion.xrel * factor;\n\t\t\t\t\tcursor_y += event.motion.yrel * factor;\n\n\t\t\t\t\tcursor_x = bound(0, cursor_x, VID_RenderWidth2D());\n\t\t\t\t\tcursor_y = bound(0, cursor_y, VID_RenderHeight2D());\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase SDL_MOUSEBUTTONDOWN:\n\t\tcase SDL_MOUSEBUTTONUP:\n#ifdef __APPLE__\n\t\t\tif (developer.integer == 2) {\n\t\t\t\tCon_Printf(\"mouse%s event, which=%d, button=%d\\n\", event.type == SDL_MOUSEBUTTONDOWN ? \"down\" : \"up\", event.button.which, event.button.button);\n\t\t\t}\n#endif\n\t\t\tif (event.button.which != SDL_TOUCH_MOUSEID || !in_ignore_touch_events.integer) {\n\t\t\t\tmouse_button_event(&event.button);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase SDL_MOUSEWHEEL:\n\t\t\tif (event.wheel.which != SDL_TOUCH_MOUSEID || !in_ignore_touch_events.integer) {\n\t\t\t\tmouse_wheel_event(&event.wheel);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase SDL_DROPFILE:\n\t\t\t/* TODO: Add handling for different file types */\n\t\t\tif (strncmp(event.drop.file, \"qw://\", 5) == 0) {\n\t\t\t\tCbuf_AddText(\"qwurl \");\n\t\t\t} else {\n\t\t\t\tCbuf_AddText(\"playdemo \");\n\t\t\t}\n\t\t\tCbuf_AddText(event.drop.file);\n\t\t\tCbuf_AddText(\"\\n\");\n\t\t\tSDL_free(event.drop.file);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (track_movement_through_state) {\n\t\tfloat factor = (IN_MouseTrackingRequired() ? cursor_sensitivity.value : 1);\n\t\tint pos_x, pos_y;\n\n\t\tSDL_GetMouseState(&pos_x, &pos_y);\n\n\t\tmx = pos_x - old_x;\n\t\tmy = pos_y - old_y;\n\n\t\tcursor_x = min(max(0, cursor_x + (pos_x - glConfig.vidWidth / 2) * factor), VID_RenderWidth2D());\n\t\tcursor_y = min(max(0, cursor_y + (pos_y - glConfig.vidHeight / 2) * factor), VID_RenderHeight2D());\n\n\t\tIN_SnapMouseBackToCentre();\n\t}\n}\n\n/*****************************************************************************/\n\nvoid VID_SoftRestart(void)\n{\n\tR_Shutdown(r_shutdown_reload);\n\tQMB_ShutdownParticles();\n}\n\nvoid VID_Shutdown(qbool restart)\n{\n\tIN_DeactivateMouse();\n\n\tSDL_StopTextInput();\n\n#ifdef X11_GAMMA_WORKAROUND\n\tif (vid_gamma_workaround.integer) {\n\t\tVID_RestoreSystemGamma();\n\t}\n#endif\n\n\tR_Shutdown(restart ? r_shutdown_restart : r_shutdown_full);\n\n\tif (sdl_context) {\n\t\tSDL_GL_DeleteContext(sdl_context);\n\t\tsdl_context = NULL;\n\t}\n\n\tif (sdl_window) {\n\t\tSDL_DestroyWindow(sdl_window);\n\t\tsdl_window = NULL;\n\t}\n\n\tSDL_GL_ResetAttributes();\n\n\tif (SDL_WasInit(SDL_INIT_VIDEO) != 0) {\n\t\tSDL_QuitSubSystem(SDL_INIT_VIDEO);\n\t}\n\n\tmemset(&glConfig, 0, sizeof(glConfig));\n\n\tQ_free(modelist);\n\tmodelist_count = 0;\n\tvid_hwgamma_enabled = false;\n\tvid_initialized = false;\n\n\tif (!restart) {\n\t\tQMB_ShutdownParticles();\n\t}\n}\n\nstatic int VID_SDL_InitSubSystem(void)\n{\n\tif (SDL_WasInit(SDL_INIT_VIDEO) == 0) {\n\t\tif (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) {\n\t\t\tSys_Error(\"Couldn't initialize SDL video: %s\\n\", SDL_GetError());\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tSDL_StartTextInput();\n\n\treturn 0;\n}\n\n// This is called during video initialisation & vid_restart, but not vid_reload\n// Do not include any cvars here that should take effect without full restart\nstatic void VID_RegisterLatchCvars(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_VIDEO);\n\n\tCvar_Register(&vid_width);\n\tCvar_Register(&vid_height);\n\tCvar_Register(&vid_win_width);\n\tCvar_Register(&vid_win_height);\n\tCvar_Register(&vid_hwgammacontrol);\n\tCvar_Register(&r_colorbits);\n\tCvar_Register(&r_24bit_depth);\n\tCvar_Register(&r_fullscreen);\n\tCvar_Register(&r_displayRefresh);\n\tCvar_Register(&vid_usedesktopres);\n\tCvar_Register(&vid_win_borderless);\n\tCvar_Register(&gl_multisamples);\n\tCvar_Register(&vid_displayNumber);\n\tCvar_Register(&vid_minimize_on_focus_loss);\n\tCvar_Register(&vid_grab_keyboard);\n#ifdef EZ_MULTIPLE_RENDERERS\n\tCvar_Register(&vid_renderer);\n#endif\n\tCvar_Register(&vid_gl_core_profile);\n\tCvar_Register(&vid_framebuffer);\n\tCvar_Register(&vid_software_palette);\n\tCvar_Register(&vid_framebuffer_depthformat);\n\tCvar_Register(&vid_framebuffer_hdr);\n\n#ifdef X11_GAMMA_WORKAROUND\n\tCvar_Register(&vid_gamma_workaround);\n#endif\n\tCvar_Register(&vid_gammacorrection);\n\n\tCvar_ResetCurrentGroup();\n}\n\nvoid VID_RegisterCvars(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_VIDEO);\n\n\tCvar_Register(&vid_vsync_lag_fix);\n\tCvar_Register(&vid_vsync_lag_tweak);\n\tCvar_Register(&r_win_save_pos);\n\tCvar_Register(&r_win_save_size);\n\tCvar_Register(&r_swapInterval);\n\tCvar_Register(&r_verbose);\n\tCvar_Register(&vid_xpos);\n\tCvar_Register(&vid_ypos);\n\tCvar_Register(&r_conwidth);\n\tCvar_Register(&r_conheight);\n\tCvar_Register(&r_conscale);\n\tCvar_Register(&vid_flashonactivity);\n\tCvar_Register(&r_showextensions);\n\tCvar_Register(&vid_win_displayNumber);\n#ifdef __linux__\n\tCvar_Register(&in_ignore_unfocused_keyb);\n#endif\n\n\tCvar_Register(&vid_framebuffer_blit);\n\tCvar_Register(&vid_framebuffer_width);\n\tCvar_Register(&vid_framebuffer_height);\n\tCvar_Register(&vid_framebuffer_scale);\n\tCvar_Register(&vid_framebuffer_hdr_tonemap);\n\tCvar_Register(&vid_framebuffer_smooth);\n\tCvar_Register(&vid_framebuffer_sshotmode);\n\tCvar_Register(&vid_framebuffer_multisample);\n\tCvar_Register(&vid_framebuffer_fxaa);\n\n\tCvar_Register(&vid_reload_auto);\n\n\tCvar_ResetCurrentGroup();\n}\n\n// Returns valid display number\nint VID_DisplayNumber(qbool fullscreen)\n{\n\tint displayNumber = (fullscreen ? vid_displayNumber.value : vid_win_displayNumber.value);\n\tint displays = SDL_GetNumVideoDisplays();\n\n\treturn max(0, min(displays - 1, displayNumber));\n}\n\nstatic void VID_SetupModeList(void)\n{\n\tint i;\n\n\tmodelist_count = SDL_GetNumDisplayModes(VID_DisplayNumber(r_fullscreen.integer == 1));\n\n\tif (modelist_count <= 0) {\n\t\tCom_Printf(\"error getting display modes: %s\\n\", SDL_GetError());\n\t\tmodelist_count = 0;\n\t}\n\n\tmodelist = Q_calloc(modelist_count, sizeof(*modelist));\n\n\tfor (i = 0; i < modelist_count; i++) {\n\t\tSDL_GetDisplayMode(0, i, &modelist[i]);\n\t}\n}\n\nstatic void VID_SetupResolution(void)\n{\n\tSDL_DisplayMode display_mode;\n\tint display_nbr;\n\n\tif (r_fullscreen.integer) {\n\t\tdisplay_nbr = VID_DisplayNumber(true);\n\t\tif (vid_usedesktopres.integer == 1) {\n\t\t\tif (SDL_GetDesktopDisplayMode(display_nbr, &display_mode) == 0) {\n\t\t\t\tglConfig.vidWidth = last_working_width = display_mode.w;\n\t\t\t\tglConfig.vidHeight = last_working_height = display_mode.h;\n\t\t\t\tglConfig.displayFrequency = last_working_hz = display_mode.refresh_rate;\n\t\t\t\tlast_working_display = display_nbr;\n\t\t\t\tlast_working_values = true;\n\t\t\t\tCvar_AutoSetInt(&vid_width, display_mode.w);\n\t\t\t\tCvar_AutoSetInt(&vid_height, display_mode.h);\n\t\t\t\tCvar_AutoSetInt(&r_displayRefresh, display_mode.refresh_rate);\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\tCom_Printf(\"warning: failed to get desktop resolution\\n\");\n\t\t\t}\n\t\t}\n\t\t/* Note the fall through in case the above fails */\n\n\t\tif (vid_width.integer == 0 || vid_height.integer == 0) {\n\t\t\t/* Try some default if nothing is set and we failed to get desktop res */\n\t\t\tglConfig.vidWidth = 1024;\n\t\t\tglConfig.vidHeight = 768;\n\t\t\tglConfig.displayFrequency = 0;\n\n\t\t\tCvar_LatchedSetValue(&vid_width, 1024);\n\t\t\tCvar_LatchedSetValue(&vid_height, 768);\n\t\t\tCvar_LatchedSetValue(&r_displayRefresh, 0);\n\n\t\t\treturn;\n\t\t}\n\n\t\t/* USER specified resolution */\n\t\tglConfig.vidWidth = bound(320, vid_width.integer, vid_width.integer);\n\t\tglConfig.vidHeight = bound(200, vid_height.integer, vid_height.integer);\n\t\tglConfig.displayFrequency = r_displayRefresh.integer;\n\n\t} else { /* Windowed mode */\n\t\tif (vid_win_width.integer == 0 || vid_win_height.integer == 0) {\n\t\t\tCvar_LatchedSetValue(&vid_win_width, 640);\n\t\t\tCvar_LatchedSetValue(&vid_win_height, 480);\n\t\t\tCvar_LatchedSetValue(&r_displayRefresh, 0);\n\t\t}\n\n\t\tglConfig.vidWidth = bound(320, vid_win_width.integer, vid_win_width.integer);\n\t\tglConfig.vidHeight = bound(240, vid_win_height.integer, vid_win_height.integer);\n\t\tglConfig.displayFrequency = 0;\n\t}\n}\n\nint VID_GetCurrentModeIndex(void)\n{\n\tint i;\n\n\tint best_freq = 0;\n\tint best_idx = -1;\n\n\tfor (i = 0; i < modelist_count; i++) {\n\t\tif (modelist[i].w == vid_width.integer && modelist[i].h == vid_height.integer) {\n\t\t\tif (modelist[i].refresh_rate == r_displayRefresh.integer) {\n\t\t\t\tCom_DPrintf(\"MATCHED: %dx%d hz:%d\\n\", modelist[i].w, modelist[i].h, modelist[i].refresh_rate);\n\t\t\t\treturn i;\n\t\t\t}\n\n\t\t\tif (modelist[i].refresh_rate > best_freq) {\n\t\t\t\tbest_freq = modelist[i].refresh_rate;\n\t\t\t\tbest_idx = i;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* width/height matched but not hz, using the best available */\n\tif (best_idx >= 0) {\n\t\tCvar_AutoSetInt(&r_displayRefresh, modelist[best_idx].refresh_rate);\n\t}\n\n\treturn best_idx;\n}\n\nint VID_GetModeIndexCount(void) {\n\treturn modelist_count;\n}\n\nconst SDL_DisplayMode *VID_GetDisplayMode(int index)\n{\n\tif (index < 0 || index >= modelist_count) {\n\t\treturn NULL;\n\t}\n\n\treturn &modelist[index];\n}\n\nstatic void VID_SDL_GL_SetupWindowAttributes(int options)\n{\n\tSDL_GL_ResetAttributes();\n\tSDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, options & VID_MULTISAMPLED ? 1 : 0);\n\tSDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, options & VID_MULTISAMPLED ? bound(2, gl_multisamples.integer, 16) : 0);\n\tSDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, options & VID_ACCELERATED ? 1 : 0);\n\tif (vid_framebuffer.integer) {\n\t\tSDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);\n\t}\n\telse {\n\t\tSDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, options & VID_DEPTHBUFFER24 ? 24 : 16);\n\t}\n\tSDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, options & VID_GAMMACORRECTED ? 1 : 0);\n}\n\nstatic SDL_GLContext VID_SDL_GL_SetupContextAttributes(void)\n{\n#ifdef EZ_MULTIPLE_RENDERERS\n\tif (vid_renderer.integer < VID_RENDERER_MIN || vid_renderer.integer > VID_RENDERER_MAX) {\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\t\tCon_Printf(\"Invalid vid_renderer value detected, falling back to default.\\n\");\n\t\tCvar_LatchedSetValue(&vid_renderer, VID_RENDERER_MIN);\n#else\n\t\tSys_Error(\"Invalid vid_renderer value detected\");\n#endif\n\t}\n#endif\n\n#ifdef RENDERER_OPTION_MODERN_OPENGL\n\tif (R_UseModernOpenGL()) {\n\t\treturn GLM_SDL_CreateContext(sdl_window);\n\t}\n#endif\n#ifdef RENDERER_OPTION_CLASSIC_OPENGL\n\tif (R_UseImmediateOpenGL()) {\n\t\treturn GLC_SDL_CreateContext(sdl_window);\n\t}\n#endif\n#ifdef RENDERER_OPTION_VULKAN\n\tif (R_UseVulkan()) {\n\t\t//return VK_SDL_CreateContext(sdl_window);\n\t}\n#endif\n\n\treturn NULL;\n}\n\nstatic int VID_SetWindowIcon(SDL_Window *sdl_window)\n{\n#ifdef __APPLE__\n\t// on OS X the icon is handled by the app bundle\n\t// it actually is higher resolution than the one here.\n\treturn 0;\n#else\n\tSDL_Surface *icon_surface;\n        icon_surface = SDL_CreateRGBSurfaceFrom((void *)ezquake_icon.pixel_data, ezquake_icon.width, ezquake_icon.height, ezquake_icon.bytes_per_pixel * 8,\n                ezquake_icon.width * ezquake_icon.bytes_per_pixel,\n                0x000000FF,0x0000FF00,0x00FF0000,0xFF000000);\n\n\tif (icon_surface) {\n\t\tSDL_SetWindowIcon(sdl_window, icon_surface);\n\t\tSDL_FreeSurface(icon_surface);\n\t\treturn 0;\n\t}\n\n\treturn -1;\n#endif\n}\n\nstatic SDL_Window *VID_SDL_CreateWindow(int flags)\n{\n\tif (r_fullscreen.integer == 0) {\n\t\tint displayNumber = VID_DisplayNumber(false);\n\t\tint xpos = vid_xpos.integer;\n\t\tint ypos = vid_ypos.integer;\n\n\t\tVID_AbsolutePositionFromRelative(&xpos, &ypos, &displayNumber);\n\n\t\treturn SDL_CreateWindow(WINDOW_CLASS_NAME, xpos, ypos, glConfig.vidWidth, glConfig.vidHeight, flags);\n\t}\n\telse {\n\t\tint windowWidth = glConfig.vidWidth;\n\t\tint windowHeight = glConfig.vidHeight;\n\t\tint windowX = SDL_WINDOWPOS_CENTERED;\n\t\tint windowY = SDL_WINDOWPOS_CENTERED;\n\t\tint displayNumber = VID_DisplayNumber(true);\n\t\tSDL_Rect bounds;\n\n\t\tif (SDL_GetDisplayBounds(displayNumber, &bounds) == 0) {\n\t\t\twindowX = bounds.x;\n\t\t\twindowY = bounds.y;\n\t\t\twindowWidth = bounds.w;\n\t\t\twindowHeight = bounds.h;\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"Couldn't determine bounds of display #%d, defaulting to main display\\n\", displayNumber);\n\t\t}\n\n\t\treturn SDL_CreateWindow(WINDOW_CLASS_NAME, windowX, windowY, windowWidth, windowHeight, flags);\n\t}\n}\n\n#ifdef X11_GAMMA_WORKAROUND\nstatic void VID_X11_GetGammaRampSize(void)\n{\n\tglConfig.gammacrap.size = -1;\n\n\tSDL_VERSION(&glConfig.gammacrap.info.version);\n\tglConfig.gammacrap.screen = SDL_GetWindowDisplayIndex(sdl_window);\n\n\tif (glConfig.gammacrap.screen < 0) {\n\t\tCom_Printf(\"error: couldn't get screen number to set gamma\\n\");\n\t\treturn;\n\t}\n\n\tif (SDL_GetWindowWMInfo(sdl_window, &glConfig.gammacrap.info) != SDL_TRUE) {\n\t\tCom_Printf(\"error: can not get display pointer, gamma won't work: %s\\n\", SDL_GetError());\n\t\treturn;\n\t}\n\n\tif (glConfig.gammacrap.info.subsystem != SDL_SYSWM_X11) {\n\t\tCom_Printf(\"error: not x11, gamma won't work\\n\");\n\t\treturn;\n\t}\n\n\tglConfig.gammacrap.display = glConfig.gammacrap.info.info.x11.display;\n\tXF86VidModeGetGammaRampSize(glConfig.gammacrap.display, glConfig.gammacrap.screen, &glConfig.gammacrap.size);\n\n\tif (glConfig.gammacrap.size <= 0 || glConfig.gammacrap.size > 4096) {\n\t\tCom_Printf(\"error: gamma size '%d' seems weird, refusing to use it\\n\", glConfig.gammacrap.size);\n\t\tglConfig.gammacrap.size = -1;\n\t\treturn;\n\t}\n}\n#endif\n\nstatic void VID_SetWindowResolution(void)\n{\n\tif (r_fullscreen.integer > 0 && vid_usedesktopres.integer != 1) {\n\t\tint index = VID_GetCurrentModeIndex();\n\n\t\tif (index < 0) {\n\t\t\tCom_Printf(\"Couldn't find a matching video mode for the selected values, check video settings!\\n\");\n\t\t\tif (last_working_values == true) {\n\t\t\t\tCom_Printf(\"Using last known working settings: %dx%d@%dHz\\n\", last_working_width, last_working_height, last_working_hz);\n\t\t\t\tCvar_LatchedSetValue(&vid_width, (float)last_working_width);\n\t\t\t\tCvar_LatchedSetValue(&vid_height, (float)last_working_height);\n\t\t\t\tCvar_LatchedSetValue(&r_displayRefresh, (float)last_working_hz);\n\t\t\t\tCvar_LatchedSetValue(&vid_displayNumber, (float)last_working_display);\n\t\t\t\tCvar_AutoSetInt(&vid_width, last_working_width);\n\t\t\t\tCvar_AutoSetInt(&vid_height, last_working_height);\n\t\t\t\tCvar_AutoSetInt(&r_displayRefresh, last_working_hz);\n\t\t\t\tCvar_AutoSetInt(&vid_displayNumber, last_working_display);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCom_Printf(\"Using desktop resolution as fallback\\n\");\n\t\t\t\tCvar_LatchedSet(&vid_usedesktopres, \"1\");\n\t\t\t\tCvar_AutoSet(&vid_usedesktopres, \"1\");\n\t\t\t}\n\t\t\tVID_SetupResolution();\n\t\t}\n\t\telse {\n\t\t\tif (SDL_SetWindowDisplayMode(sdl_window, &modelist[index]) != 0) {\n\t\t\t\tCom_Printf(\"sdl error: %s\\n\", SDL_GetError());\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlast_working_width = (&modelist[index])->w;\n\t\t\t\tlast_working_height = (&modelist[index])->h;\n\t\t\t\tlast_working_hz = (&modelist[index])->refresh_rate;\n\t\t\t\tlast_working_display = vid_displayNumber.integer;\n\t\t\t\tlast_working_values = true;\n\t\t\t}\n\t\t}\n\n\t\tif (SDL_SetWindowFullscreen(sdl_window, SDL_WINDOW_FULLSCREEN) < 0) {\n\t\t\tCom_Printf(\"Failed to change to fullscreen mode\\n\");\n\t\t}\n\t}\n\n\tSDL_SetWindowMinimumSize(sdl_window, 320, 240);\n}\n\nstatic void VID_SDL_Init(void)\n{\n\tint flags;\n\t\n\tif (glConfig.initialized == true) {\n\t\treturn;\n\t}\n\n\tVID_SDL_InitSubSystem();\n\n\tflags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_SHOWN;\n\t// MEAG: deliberately not specifying SDL_WINDOW_ALLOW_HIGHDPI as in our current workflow, it\n\t//          breaks retina devices (we ask for display resolution and get told lower value)\n\t//       Understand this is meant to be helped by NSHighResolutionCapable in Info.plist, but\n\t//          BLooD_DoG tried that and it didn't help.  No OSX device to test on, so removed\n\t//          for the moment.\n\t//       Flag has no effect on Windows (see SetProcessDpiAwarenessFunc in sys_win.c)\n\tif (r_fullscreen.integer > 0) {\n\t\tflags |= (vid_usedesktopres.integer == 1 ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);\n\t}\n\telse {\n\t\tflags |= (vid_win_borderless.integer > 0 ? SDL_WINDOW_BORDERLESS : 0);\n\t}\n\n\tSDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, vid_minimize_on_focus_loss.integer == 0 ? \"0\" : \"1\");\n#ifdef __APPLE__\n\tSDL_SetHint(SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES, \"0\");\n#endif\n\tSDL_SetHint(SDL_HINT_GRAB_KEYBOARD, vid_grab_keyboard.integer == 0 ? \"0\" : \"1\");\n\tSDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, \"0\", SDL_HINT_OVERRIDE);\n#ifdef __APPLE__\n#ifdef SDL_HINT_TOUCH_MOUSE_EVENTS\n\tSDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, \"0\");\n#endif\n#endif\n\n\t{\n\t\tint i;\n\t\tint vid_options[] = {\n\t\t\t// Try to get everything they ask for...\n\t\t\tVID_MULTISAMPLED | VID_ACCELERATED | VID_DEPTHBUFFER24 | VID_GAMMACORRECTED,\n\t\t\t// ... multisampled off\n\t\t\tVID_ACCELERATED | VID_DEPTHBUFFER24 | VID_GAMMACORRECTED,\n\t\t\t// ... give up on gamma corrected\n\t\t\tVID_MULTISAMPLED | VID_ACCELERATED | VID_DEPTHBUFFER24,\n\t\t\tVID_ACCELERATED | VID_DEPTHBUFFER24,\n\t\t\t// \n\t\t\tVID_ACCELERATED | VID_DEPTHBUFFER24 | VID_GAMMACORRECTED,\n\t\t\tVID_ACCELERATED | VID_GAMMACORRECTED,\n\t\t\tVID_ACCELERATED,\n\t\t\t// Un-accelerated and at this point we're desperate\n\t\t\tVID_DEPTHBUFFER24,\n\t\t\tVID_GAMMACORRECTED,\n\t\t\t0\n\t\t};\n\n\t\twhile (true) {\n\t\t\tfor (i = 0, sdl_window = NULL; sdl_window == NULL && i < sizeof(vid_options) / sizeof(vid_options[0]); ++i) {\n\t\t\t\tif ((vid_options[i] & VID_MULTISAMPLED) && gl_multisamples.integer <= 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif ((vid_options[i] & VID_DEPTHBUFFER24) && !r_24bit_depth.integer) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif ((vid_options[i] & VID_ACCELERATED) && COM_CheckParm(cmdline_param_client_unaccelerated_visuals)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif ((vid_options[i] & VID_GAMMACORRECTED) && vid_gammacorrection.integer == 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (!(vid_options[i] & VID_GAMMACORRECTED) && vid_gammacorrection.integer == 2) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tsdl_window = NULL;\n\t\t\t\tVID_SDL_GL_SetupWindowAttributes(vid_options[i]);\n\t\t\t\tVID_SetupModeList();\n\t\t\t\tVID_SetupResolution();\n\t\t\t\tsdl_window = VID_SDL_CreateWindow(flags);\n\n\t\t\t\tif (sdl_window) {\n\t\t\t\t\tVID_SetWindowResolution();\n\n\t\t\t\t\t// Try to create context and see what we get\n\t\t\t\t\tsdl_context = VID_SDL_GL_SetupContextAttributes();\n\n\t\t\t\t\tif (!sdl_context) {\n\t\t\t\t\t\tSDL_DestroyWindow(sdl_window);\n\t\t\t\t\t\tsdl_window = NULL;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n#if defined(RENDERER_OPTION_CLASSIC_OPENGL) && defined(EZ_MULTIPLE_RENDERERS)\n\t\t\t// FIXME: Implement falling back from Vulkan too\n\t\t\tif (!sdl_window && !R_UseImmediateOpenGL()) {\n\t\t\t\tCon_Printf(\"&cf00Error&r: failed to create rendering context, trying classic OpenGL...\\n\");\n\n\t\t\t\tCvar_LatchedSetValue(&vid_renderer, 0);\n\t\t\t\tcontinue;\n\t\t\t}\n#endif\n\n\t\t\tbreak;\n\t\t}\n\n\t\tif (!sdl_window) {\n\t\t\tSys_Error(\"Failed to create SDL window/context: %s\\n\", SDL_GetError());\n\t\t}\n\n\t\t// Alert user if our mode doesn't match what they requested\n\t\tif (!(vid_options[i] & VID_MULTISAMPLED) && gl_multisamples.integer > 0) {\n\t\t\tCvar_AutoSetInt(&gl_multisamples, 0);\n\t\t\tCom_Printf(\"WARNING: MSAA request failed - disabled\\n\");\n\t\t}\n\n\t\tif (!(vid_options[i] & VID_DEPTHBUFFER24) && r_24bit_depth.integer) {\n\t\t\tCvar_AutoSetInt(&r_24bit_depth, 0);\n\t\t\tCom_Printf(\"WARNING: 24-bit depth buffer request failed\\n\");\n\t\t}\n\n\t\tif (!(vid_options[i] & VID_ACCELERATED) && !COM_CheckParm(cmdline_param_client_unaccelerated_visuals)) {\n\t\t\tCom_Printf(\"WARNING: Using unaccelerated graphics\\n\");\n\t\t}\n\n\t\tif (vid_gammacorrection.integer && !(vid_options[i] & VID_GAMMACORRECTED)) {\n\t\t\tCom_Printf(\"WARNING: Not able to apply gamma-correction\\n\");\n\t\t\tCvar_LatchedSetValue(&vid_gammacorrection, 0);\n\t\t}\n\t}\n\n\tif (VID_SetWindowIcon(sdl_window) < 0) {\n\t\tCom_Printf(\"Failed to set window icon\");\n\t}\n\n\tv_gamma.modified = true;\n\tr_swapInterval.modified = true;\n\n#ifdef X11_GAMMA_WORKAROUND\n\t/* PLEASE REMOVE ME AS SOON AS SDL2 AND XORG ARE TALKING NICELY TO EACHOTHER AGAIN IN TERMS OF GAMMA */\n\tif (vid_gamma_workaround.integer != 0) {\n\t\tVID_X11_GetGammaRampSize();\n\t} else {\n\t\tglConfig.gammacrap.size = 256;\n\t}\n#endif\n\n\tR_Initialise();\n\n\t//always get/set refresh rate\n\tSDL_DisplayMode display_mode;\n\tint display_nbr;\n\n\tdisplay_nbr = VID_DisplayNumber(true);\n\tif (SDL_GetDesktopDisplayMode(display_nbr, &display_mode) == 0) {\n\t\tCvar_AutoSetInt(&r_displayRefresh, display_mode.refresh_rate);\n\t}\n\n\tglConfig.initialized = true;\n}\n\nstatic void VID_SwapBuffers (void)\n{\n\tSDL_GL_SwapWindow(sdl_window);\n}\n\nstatic void VID_SwapBuffersWithVsyncFix(void)\n{\n\tdouble time_before_swap;\n\n\ttime_before_swap = Sys_DoubleTime();\n\n\tSDL_GL_SwapWindow(sdl_window);\n\n\tvid_last_swap_time = Sys_DoubleTime();\n\tvid_vsync_lag = vid_last_swap_time - time_before_swap;\n}\n\nvoid R_BeginRendering(int *x, int *y, int *width, int *height)\n{\n\n\t*x = *y = 0;\n\t*width = glConfig.vidWidth;\n\t*height = glConfig.vidHeight;\n\n\tif (renderer.IsFramebufferEnabled3D()) {\n\t\tint scaled_width = VID_ScaledWidth3D();\n\t\tint scaled_height = VID_ScaledHeight3D();\n\n\t\tif (scaled_width && scaled_height) {\n\t\t\t*width = scaled_width;\n\t\t\t*height = scaled_height;\n\t\t}\n\t}\n\n\tif (cls.state != ca_active) {\n\t\trenderer.ClearRenderingSurface(true);\n\t}\n}\n\nvoid R_EndRendering(void)\n{\n\tif (r_swapInterval.modified) {\n\t\tif (r_swapInterval.integer == 0) {\n\t\t\tif (SDL_GL_SetSwapInterval(0)) {\n\t\t\t\tCon_Printf(\"vsync: Failed to disable vsync...\\n\");\n\t\t\t}\n            // MacOS vsync fix\n            #ifdef __APPLE__\n            GLint                       sync = 0;\n            CGLContextObj               ctx = CGLGetCurrentContext();\n            CGLSetParameter(ctx, kCGLCPSwapInterval, &sync);\n            #endif\n\t\t} else if (r_swapInterval.integer == -1) {\n\t\t\tif (SDL_GL_SetSwapInterval(-1)) {\n\t\t\t\tCon_Printf(\"vsync: Failed to enable late swap tearing (vid_vsync -1), setting vid_vsync 1 instead...\\n\");\n\t\t\t\tCvar_SetValueByName(\"vid_vsync\", 1);\n\t\t\t}\n\t\t}\n\n\t\tif (r_swapInterval.integer == 1) {\n\t\t\tif (SDL_GL_SetSwapInterval(1)) {\n\t\t\t\tCon_Printf(\"vsync: Failed to enable vsync...\\n\");\n\t\t\t}\n\t\t}\n\n\t\tr_swapInterval.modified = false;\n    }\n\n\tif (!scr_skipupdate || block_drawing) {\n\t\tif (vid_vsync_lag_fix.integer > 0) {\n\t\t\tVID_SwapBuffersWithVsyncFix();\n\t\t}\n\t\telse {\n\t\t\tVID_SwapBuffers(); \n\t\t}\n\t}\n\n\tbuffers.EndFrame();\n}\n\nvoid VID_SetCaption (char *text)\n{\n\tif (!sdl_window) {\n\t\treturn;\n\t}\n\n\tSDL_SetWindowTitle(sdl_window, text);\n}\n\nvoid VID_NotifyActivity(void)\n{\n#ifdef _WIN32\n\tSDL_SysWMinfo info;\n\tSDL_VERSION(&info.version);\n\n\tif (ActiveApp || !vid_flashonactivity.value) {\n\t\treturn;\n\t}\n\n\tif (SDL_GetWindowWMInfo(sdl_window, &info) == SDL_TRUE) {\n\t\tif (info.subsystem == SDL_SYSWM_WINDOWS) {\n\t\t\tFlashWindow(info.info.win.window, TRUE);\n\t\t}\n\t}\n\telse {\n\t\tCom_DPrintf(\"Sys_NotifyActivity: SDL_GetWindowWMInfo failed: %s\\n\", SDL_GetError());\n\t}\n#endif\n}\n\nint VID_SetDeviceGammaRamp(unsigned short *ramps)\n{\n\tif (!sdl_window || (COM_CheckParm(cmdline_param_client_nohardwaregamma) && Ruleset_AllowNoHardwareGamma())) {\n\t\treturn 0;\n\t}\n\n\tif (r_fullscreen.integer > 0) {\n\t\tif (vid_hwgammacontrol.integer > 0) {\n\t\t\treturn VID_SetDeviceGammaRampReal(ramps);\n\t\t}\n\t}\n\telse {\n\t\tif (vid_hwgammacontrol.integer >= 2) {\n\t\t\treturn VID_SetDeviceGammaRampReal(ramps);\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nvoid VID_Minimize (void) \n{\n\tif (!sdl_window) {\n\t\treturn;\n\t}\n\n\tSDL_MinimizeWindow(sdl_window);\n}\n\nvoid VID_Restore (void)\n{\n\tif (!sdl_window || (SDL_GetWindowFlags(sdl_window) & SDL_WINDOW_INPUT_FOCUS)) {\n\t\treturn;\n\t}\n\n\tSDL_RestoreWindow(sdl_window);\n\tSDL_RaiseWindow(sdl_window);\n}\n\nstatic void VID_ParseCmdLine(void)\n{\n\tint i, w = 0, h = 0, display = 0;\n\n\tif (COM_CheckParm(cmdline_param_client_windowedmode) || COM_CheckParm(cmdline_param_client_startwindowed)) {\n\t\tCvar_LatchedSetValue(&r_fullscreen, 0);\n\t}\n\n\tif ((i = COM_CheckParm(cmdline_param_client_video_frequency)) && i + 1 < COM_Argc()) {\n\t\tCvar_LatchedSetValue(&r_displayRefresh, Q_atoi(COM_Argv(i + 1)));\n\t}\n\n\tif ((i = COM_CheckParm(cmdline_param_client_video_bpp)) && i + 1 < COM_Argc()) {\n\t\tCvar_LatchedSetValue(&r_colorbits, Q_atoi(COM_Argv(i + 1)));\n\t}\n\n\tif (COM_CheckParm(cmdline_param_client_video_nodesktopres)) {\n\t\tCvar_LatchedSetValue(&vid_usedesktopres, 0);\n\t}\n\n\tw = ((i = COM_CheckParm(cmdline_param_client_video_width))  && i + 1 < COM_Argc()) ? Q_atoi(COM_Argv(i + 1)) : 0;\n\th = ((i = COM_CheckParm(cmdline_param_client_video_height)) && i + 1 < COM_Argc()) ? Q_atoi(COM_Argv(i + 1)) : 0;\n\n\tdisplay = ((i = COM_CheckParm(cmdline_param_client_video_displaynumber)) && i + 1 < COM_Argc()) ? Q_atoi(COM_Argv(i + 1)) : 0;\n\tif (i) {\n\t\tif (COM_CheckParm(cmdline_param_client_windowedmode)) {\n\t\t\tCvar_LatchedSetValue(&vid_win_displayNumber, display);\n\t\t} else {\n\t\t\tCvar_LatchedSetValue(&vid_displayNumber, display);\n\t\t}\n\t}\n\n\tif (w && h) {\n\t\tif (COM_CheckParm(cmdline_param_client_windowedmode)) {\n\t\t\tCvar_LatchedSetValue(&vid_win_width,  w);\n\t\t\tCvar_LatchedSetValue(&vid_win_height, h);\n\t\t}\n\t\telse {\n\t\t\tCvar_LatchedSetValue(&vid_width, w);\n\t\t\tCvar_LatchedSetValue(&vid_height, h);\n\t\t}\n\t} // else if (w || h) { Sys_Error(\"Must specify both -width and -height\\n\"); }\n\n\tif ((i = COM_CheckParm(cmdline_param_client_video_conwidth)) && i + 1 < COM_Argc()) {\n\t\tCvar_SetIgnoreCallback(&r_conwidth, COM_Argv(i + 1));\n\t}\n\n\tif ((i = COM_CheckParm(cmdline_param_client_video_conheight)) && i + 1 < COM_Argc()) {\n\t\tCvar_SetIgnoreCallback(&r_conheight, COM_Argv(i + 1));\n\t}\n\n#if defined(EZ_MULTIPLE_RENDERERS) && defined(RENDERER_OPTION_MODERN_OPENGL)\n\tif (COM_CheckParm(cmdline_param_client_video_glsl_renderer)) {\n\t\tCvar_LatchedSetValue(&vid_renderer, 1);\n\t}\n#endif\n}\n\nvoid GFX_Init(void);\nvoid ReloadPaletteAndColormap(void);\n\nstatic void VID_Startup(void)\n{\n\tqbool old_con_suppress = con_suppress;\n\n\t// force models to reload (just flush, no actual loading code here)\n\tCache_Flush();\n\n\t// shut up warnings during GFX_Init();\n\tcon_suppress = !developer.integer;\n\t// reload 2D textures, particles textures, some other textures and gfx.wad\n\tGFX_Init();\n\n\t// reload skins\n\tSkin_Skins_f();\n\n\tcon_suppress = old_con_suppress;\n\n\t// we need done something like for map reloading, for example reload textures for brush models\n\tR_NewMap(true);\n\n\t// window may be re-created, so caption need to be forced to update\n\tCL_UpdateCaption(true);\n\n\t// last chance\n\tCachePics_AtlasFrame();\n\t// compile all programs\n\tR_ProgramCompileAll();\n\n\tCvar_ClearAllModifiedFlags(CVAR_RELOAD_GFX);\n}\n\nvoid VID_ReloadCvarChanged(cvar_t* var)\n{\n\tif (!vid_reload_auto.integer) {\n\t\tCon_Printf(\"%s needs %s to immediately take effect.\\n\", var->name, CVAR_RELOAD_GFX_COMMAND);\n\t}\n\telse {\n\t\tvid_reload_pending = true;\n\t}\n}\n\nstatic void VID_Reload_f(void)\n{\n\tif (!host_initialized) { // sanity\n\t\tCom_Printf(\"Can't do %s yet\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tVID_SoftRestart();\n\tReloadPaletteAndColormap();\n\tVID_Startup();\n\tvid_reload_pending = false;\n}\n\nvoid VID_ReloadCheck(void)\n{\n\tif (vid_reload_pending && host_initialized) {\n\t\tVID_Reload_f();\n\t}\n}\n\nstatic void VID_Restart_f(void)\n{\n\tif (!host_initialized) { // sanity\n\t\tCom_Printf(\"Can't do %s yet\\n\", Cmd_Argv(0));\n\t\treturn;\n\t}\n\n\tVID_Shutdown(true);\n\n\tReloadPaletteAndColormap();\n\n\t// keys can get stuck because SDL2 doesn't send keyup event when the video system is down\n\tKey_ClearStates();\n\n\tVID_Init(host_basepal);\n\n\tVID_Startup();\n}\n\nstatic void VID_DisplayList_f(void)\n{\n\tint displays = SDL_GetNumVideoDisplays();\n\tint i;\n\n\tfor (i = 0; i < displays; i++) {\n\t\tconst char *displayname = SDL_GetDisplayName(i);\n\t\tif (displayname == NULL) {\n\t\t\tdisplayname = \"Unknown\";\n\t\t}\n\t\tCom_Printf(\"%d: %s\\n\", i, displayname);\n\t}\n}\n\nstatic void VID_ModeList_f(void)\n{\n\tint i = 0;\n\n\tif (modelist == NULL || modelist_count <= 0) {\n\t\tCom_Printf(\"error: no modes available\\n\");\n\t\treturn;\n\t}\n\t\n\tfor (; i < modelist_count; i++) {\n\t\tCom_Printf(\"%dx%d@%dHz\\n\", (&modelist[i])->w, (&modelist[i])->h, (&modelist[i])->refresh_rate);\n\t}\n}\n\nvoid VID_RegisterCommands(void) \n{\n\tif (!host_initialized) {\n\t\tCmd_AddCommand(\"vid_gfxinfo\", VID_GfxInfo_f);\n\t\tCmd_AddCommand(\"vid_restart\", VID_Restart_f);\n\t\tCmd_AddCommand(CVAR_RELOAD_GFX_COMMAND, VID_Reload_f);\n\t\tCmd_AddCommand(\"vid_displaylist\", VID_DisplayList_f);\n\t\tCmd_AddCommand(\"vid_modelist\", VID_ModeList_f);\n\t\tCmd_AddLegacyCommand(\"vid_framebuffer_palette\", vid_software_palette.name);\n\t}\n}\n\nstatic void VID_UpdateConRes(void)\n{\n\tqbool set_width = false;\n\tqbool set_height = false;\n\n\tint vidWidth = glConfig.vidWidth;\n\tint vidHeight = glConfig.vidHeight;\n\n\tint effective_width = vid_framebuffer.integer == 1 ? VID_ScaledWidth3D() : 0;\n\tint effective_height = vid_framebuffer.integer == 1 ? VID_ScaledHeight3D() : 0;\n\n\tif (effective_width && effective_height) {\n\t\tvidWidth = effective_width;\n\t\tvidHeight = effective_height;\n\n\t\tCvar_AutoSetInt(&vid_framebuffer_width, effective_width);\n\t\tCvar_AutoSetInt(&vid_framebuffer_height, effective_height);\n\t}\n\telse {\n\t\tCvar_AutoReset(&vid_framebuffer_width);\n\t\tCvar_AutoReset(&vid_framebuffer_height);\n\t}\n\n\t// Default\n\tif (r_conwidth.integer == 0 && r_conheight.integer == 0) {\n\t\tvid.width = vid.conwidth = bound(320, (int)(vidWidth  / r_conscale.value), vidWidth);\n\t\tvid.height = vid.conheight = bound(200, (int)(vidHeight / r_conscale.value), vidHeight);\n\n\t\tset_width = set_height = true;\n\t}\n\telse if (r_conwidth.integer == 0) {\n\t\tdouble ar_w = (double)vidWidth/(double)vidHeight;\n\n\t\tvid.height = vid.conheight = bound(200, r_conheight.integer, vidHeight);\n\t\tvid.width = vid.conwidth = bound(320, (int)(r_conheight.integer*ar_w + 0.5), vidWidth);\n\n\t\tset_width = true;\n\t}\n\telse if (r_conheight.integer == 0) {\n\t\tdouble ar_h = (double)vidHeight/(double)vidWidth;\n\n\t\tvid.height = vid.conheight = bound(200, (int)(r_conwidth.integer*ar_h + 0.5), vidHeight);\n\t\tvid.width = vid.conwidth = bound(320, r_conwidth.integer, vidWidth);\n\n\t\tset_height = true;\n\t}\n\telse {\n\t\t// User specified, use that but check boundaries\n\t\tvid.width = vid.conwidth = bound(320, r_conwidth.integer,  vidWidth);\n\t\tvid.height = vid.conheight = bound(200, r_conheight.integer, vidHeight);\n\n\t\tCvar_SetValue(&r_conwidth, vid.conwidth);\n\t\tCvar_SetValue(&r_conheight, vid.conheight);\n\t}\n\n\tif (set_width) {\n\t\tCvar_AutoSetInt(&r_conwidth, vid.conwidth);\n\t}\n\tif (set_height) {\n\t\tCvar_AutoSetInt(&r_conheight, vid.conheight);\n\t}\n\n\tvid.aspect = (double) vidWidth / (double) vidHeight;\n\n\tvid.numpages = 2; // ??\n\tDraw_AdjustConback();\n\tvid.recalc_refdef = 1;\n\tCon_CheckResize();\n}\n\nvoid GL_FramebufferSetFiltering(qbool linear);\n\nstatic void framebuffer_smooth_changed_callback(cvar_t* var, char* string, qbool* cancel)\n{\n\tif (string[0] == '\\0' || string[1] != '\\0' || !(string[0] == '0' || string[0] == '1')) {\n\t\tCom_Printf(\"Value of %s must be 0 or 1\\n\", var->name);\n\t\t*cancel = true;\n\t\treturn;\n\t}\n\n\tGL_FramebufferSetFiltering(string[0] == '1');\n}\n\nstatic void conres_changed_callback(cvar_t *var, char *string, qbool *cancel)\n{\n\t/* Cvar_SetValue won't trigger a recursive callback since we're in the callback,\n\t * but it's required to set the values here first to make them apply, and then cancel\n\t * set to true will force the caller to return immediatly when this callback returns...\n\t */\n\tif (var == &r_conwidth) {\n\t\tCvar_SetValue(&r_conwidth, Q_atoi(string));\n\t}\n\telse if (var == &r_conheight) {\n\t\tCvar_SetValue(&r_conheight, Q_atoi(string));\n\t}\n\telse if (var == &r_conscale) {\n\t\tCvar_SetValue(&r_conscale, Q_atof(string));\n\t}\n\telse if (var == &vid_framebuffer) {\n\t\tCvar_SetValue(&vid_framebuffer, Q_atof(string));\n\t}\n\telse if (var == &vid_framebuffer_width) {\n\t\tCvar_SetValue(&vid_framebuffer_width, Q_atof(string));\n\t}\n\telse if (var == &vid_framebuffer_height) {\n\t\tCvar_SetValue(&vid_framebuffer_height, Q_atof(string));\n\t}\n\telse if (var == &vid_framebuffer_scale) {\n\t\tCvar_SetValue(&vid_framebuffer_scale, Q_atof(string));\n\t}\n\telse {\n\t\tCom_Printf(\"Called with unknown variable: %s\\n\", var->name ? var->name : \"unknown\");\n\t}\n\n\tVID_UpdateConRes();\n\t*cancel = true;\n}\n\nvoid VID_Init(unsigned char *palette)\n{\n\tvid.colormap = host_colormap;\n\n\tCheck_Gamma(palette);\n\tVID_SetPalette(palette);\n\n\tVID_RegisterLatchCvars();\n\n\tif (!host_initialized) {\n\t\tVID_RegisterCvars();\n\t\tVID_RegisterCommands();\n\t\tVID_ParseCmdLine();\n\t}\n\n\tVID_SDL_Init();\n\n\t// print info\n\tif (!host_initialized || r_verbose.integer) {\n\t\tVID_GfxInfo_f();\n\t}\n\n\tVID_UpdateConRes();\n\n\tvid_initialized = true;\n}\n\nint VID_ScaledWidth3D(void)\n{\n\tif (vid_framebuffer_scale.value > 0) {\n\t\treturn glConfig.vidWidth / max(vid_framebuffer_scale.value, 0.25);\n\t}\n\telse if (vid_framebuffer_width.integer > 0) {\n\t\treturn max(vid_framebuffer_width.integer, 320);\n\t}\n\treturn glConfig.vidWidth;\n}\n\nint VID_ScaledHeight3D(void)\n{\n\tif (vid_framebuffer_scale.value > 0) {\n\t\treturn glConfig.vidHeight / max(vid_framebuffer_scale.value, 0.25);\n\t}\n\telse if (vid_framebuffer_height.integer > 0) {\n\t\treturn max(vid_framebuffer_height.integer, 200);\n\t}\n\treturn glConfig.vidHeight;\n}\n\nint VID_RenderWidth2D(void)\n{\n\tif (vid_framebuffer.integer == USE_FRAMEBUFFER_3DONLY) {\n\t\treturn glConfig.vidWidth;\n\t}\n\treturn glwidth;\n}\n\nint VID_RenderHeight2D(void)\n{\n\tif (vid_framebuffer.integer == USE_FRAMEBUFFER_3DONLY) {\n\t\treturn glConfig.vidHeight;\n\t}\n\treturn glheight;\n}\n"
  },
  {
    "path": "src/vid_vsync.c",
    "content": "/*\n===========================================================================\nCopyright (C) 1999-2005 Id Software, Inc.\n\nThis file is part of Quake III Arena source code.\n\nQuake III Arena source code is free software; you can redistribute it\nand/or modify it under the terms of the GNU General Public License as\npublished by the Free Software Foundation; either version 2 of the License,\nor (at your option) any later version.\n\nQuake III Arena source code is distributed in the hope that it will be\nuseful, but WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Foobar; if not, write to the Free Software\nFoundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n===========================================================================\n*/\n\n#include \"quakedef.h\"\n#include \"tr_types.h\"\n\nextern cvar_t r_swapInterval;\nextern cvar_t vid_vsync_lag_fix;\nextern cvar_t vid_vsync_lag_tweak;\n\n#define NUMTIMINGS 5\nstatic double timings[NUMTIMINGS];\nstatic double render_frame_start, render_frame_end;\n\nqbool VID_VSyncIsOn(void)\n{\n\treturn (r_swapInterval.integer != 0);\n}\n\nqbool VID_VsyncLagEnabled(void)\n{\n\treturn VID_VSyncIsOn() && vid_vsync_lag_fix.integer;\n}\n\nvoid VID_RenderFrameEnd(void)\n{\n\textern double render_frame_end;\n\n\tif (VID_VsyncLagEnabled()) {\n\t\trender_frame_end = Sys_DoubleTime();\n\t}\n}\n\n// Returns true if it's not time yet to run a frame\nqbool VID_VSyncLagFix(void)\n{\n\textern double vid_last_swap_time;\n\tdouble avg_rendertime, tmin, tmax;\n\n\tstatic int timings_idx;\n\tint i;\n\n\tif (!VID_VSyncIsOn() || !vid_vsync_lag_fix.integer) {\n\t\treturn false;\n\t}\n\n\tif (!glConfig.displayFrequency) {\n\t\tCom_Printf(\"VID_VSyncLagFix: displayFrequency isn't set, can't enable vsync lag fix\\n\");\n\t\treturn false;\n\t}\n\n\t// collect statistics so that\n\ttimings[timings_idx] = render_frame_end - render_frame_start;\n\ttimings_idx = (timings_idx + 1) % NUMTIMINGS;\n\tavg_rendertime = tmin = tmax = 0;\n\tfor (i = 0; i < NUMTIMINGS; i++) {\n\t\tif (timings[i] == 0) {\n\t\t\treturn false;   // not enough statistics yet\n\t\t}\n\n\t\tavg_rendertime += timings[i];\n\n\t\tif (timings[i] < tmin || !tmin) {\n\t\t\ttmax = timings[i];\n\t\t}\n\n\t\tif (timings[i] > tmax) {\n\t\t\ttmax = timings[i];\n\t\t}\n\t}\n\tavg_rendertime /= NUMTIMINGS;\n\t// if (tmax and tmin differ too much) do_something(); ?\n\tavg_rendertime = tmax;  // better be on the safe side\n\n\tdouble time_left = vid_last_swap_time + 1.0 / glConfig.displayFrequency - Sys_DoubleTime();\n\ttime_left -= avg_rendertime;\n\ttime_left -= vid_vsync_lag_tweak.value * 0.001;\n\tif (time_left > 0) {\n\t\textern cvar_t sys_yieldcpu;\n\n\t\tif (time_left > 0.001 && sys_yieldcpu.integer) {\n\t\t\tSys_MSleep(min(time_left * 1000, 500));\n\t\t}\n\n\t\treturn true;    // don't run a frame yet\n\t}\n\n\trender_frame_start = Sys_DoubleTime();\n\treturn false;\n}\n"
  },
  {
    "path": "src/vk_blending.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// vk_blending.c\n// - Converts internal v_blendfunc_t => Vulkan structures\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include <vulkan/vulkan.h>\n#include \"quakedef.h\"\n#include \"r_state.h\"\n\n#include \"vk_local.h\"\n\nvoid VK_BlendingConfigure(VkPipelineColorBlendStateCreateInfo* info, VkPipelineColorBlendAttachmentState* blending, r_blendfunc_t func)\n{\n\tmemset(info, 0, sizeof(*info));\n\n\tinfo->sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;\n\tinfo->logicOpEnable = VK_FALSE;\n\tinfo->logicOp = VK_LOGIC_OP_COPY;\n\tinfo->attachmentCount = 1;\n\tinfo->pAttachments = blending;\n\tinfo->blendConstants[0] = info->blendConstants[1] = info->blendConstants[2] = info->blendConstants[3] = 0.0f;\n\n\tmemset(blending, 0, sizeof(*blending));\n\n\tblending->colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;\n\tblending->colorBlendOp = blending->alphaBlendOp = VK_BLEND_OP_ADD;\n\n\tswitch (func) {\n\t\tcase r_blendfunc_overwrite:\n\t\t\tblending->srcColorBlendFactor = blending->srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;\n\t\t\tblending->dstColorBlendFactor = blending->dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;\n\t\t\tblending->blendEnable = false;\n\t\t\tbreak;\n\t\tcase r_blendfunc_additive_blending:\n\t\t\tblending->srcColorBlendFactor = blending->srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;\n\t\t\tblending->dstColorBlendFactor = blending->dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;\n\t\t\tblending->blendEnable = true;\n\t\t\tbreak;\n\t\tcase r_blendfunc_premultiplied_alpha:\n\t\t\tblending->srcColorBlendFactor = blending->srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;\n\t\t\tblending->dstColorBlendFactor = blending->dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;\n\t\t\tblending->blendEnable = true;\n\t\t\tbreak;\n\t\tcase r_blendfunc_src_dst_color_dest_zero:\n\t\t\tblending->srcColorBlendFactor = VK_BLEND_FACTOR_DST_COLOR;\n\t\t\tblending->srcAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;\n\t\t\tblending->dstColorBlendFactor = blending->dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;\n\t\t\tblending->blendEnable = true;\n\t\t\tbreak;\n\t\tcase r_blendfunc_src_dst_color_dest_one:\n\t\t\tblending->srcColorBlendFactor = VK_BLEND_FACTOR_DST_COLOR;\n\t\t\tblending->srcAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;\n\t\t\tblending->dstColorBlendFactor = blending->dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;\n\t\t\tblending->blendEnable = true;\n\t\t\tbreak;\n\t\tcase r_blendfunc_src_dst_color_dest_src_color:\n\t\t\tblending->srcColorBlendFactor = VK_BLEND_FACTOR_DST_COLOR;\n\t\t\tblending->srcAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;\n\t\t\tblending->dstColorBlendFactor = VK_BLEND_FACTOR_SRC_COLOR;\n\t\t\tblending->dstAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;\n\t\t\tblending->blendEnable = true;\n\t\t\tbreak;\n\t\tcase r_blendfunc_src_zero_dest_one_minus_src_color:\n\t\t\tblending->srcColorBlendFactor = blending->srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;\n\t\t\tblending->dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;\n\t\t\tblending->dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;\n\t\t\tblending->blendEnable = true;\n\t\t\tbreak;\n\t\tcase r_blendfunc_src_zero_dest_src_color:\n\t\t\tblending->srcColorBlendFactor = blending->srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;\n\t\t\tblending->dstColorBlendFactor = VK_BLEND_FACTOR_SRC_COLOR;\n\t\t\tblending->dstAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;\n\t\t\tblending->blendEnable = true;\n\t\t\tbreak;\n\t\tcase r_blendfunc_src_one_dest_zero:\n\t\t\tblending->srcColorBlendFactor = blending->srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;\n\t\t\tblending->dstColorBlendFactor = blending->dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;\n\t\t\tblending->blendEnable = true;\n\t\t\tbreak;\n\t\tcase r_blendfunc_src_zero_dest_one:\n\t\t\tblending->srcColorBlendFactor = blending->srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;\n\t\t\tblending->dstColorBlendFactor = blending->dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;\n\t\t\tblending->blendEnable = true;\n\t\t\tbreak;\n\t}\n}\n\n#endif // RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_buffers.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// vk_buffers.c\n// - Vulkan buffer handling\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include <vulkan/vulkan.h>\n#include \"quakedef.h\"\n\n#include \"vk_local.h\"\n#include \"r_local.h\"\n#include \"r_buffers.h\"\n\ntypedef struct vk_buffer_s {\n\tVkBuffer handle;\n} vk_buffer_t;\n\nstatic vk_buffer_t bufferData[64];\n\nstatic VkBufferUsageFlags VK_BufferMemoryStyle(bufferusage_t usage)\n{\n\tswitch (usage) {\n\t\tcase bufferusage_once_per_frame:\n\t\t\t// filled & used once per frame\n\t\t\tbreak;\n\t\tcase bufferusage_reuse_per_frame:\n\t\t\t// filled & used many times per frame\n\t\t\tbreak;\n\t\tcase bufferusage_reuse_many_frames:\n\t\t\t// filled once, expect to use many times over subsequent frames\n\t\t\tbreak;\n\t\tcase bufferusage_constant_data:\n\t\t\t// filled once, never updated again\n\t\t\tbreak;\n\t}\n}\n\nstatic VkBufferUsageFlags VK_BufferUsageForType(buffertype_t type)\n{\n\tswitch (type) {\n\t\tcase buffertype_vertex:\n\t\t\treturn VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;\n\t\tcase buffertype_index:\n\t\t\treturn VK_BUFFER_USAGE_INDEX_BUFFER_BIT;\n\t\tcase buffertype_indirect:\n\t\t\treturn VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;\n\t\tcase buffertype_storage:\n\t\t\treturn VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;\n\t\tcase buffertype_uniform:\n\t\t\treturn VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;\n\t}\n\n\tassert(false);\n\tSys_Error(\"Invalid buffertype passed to VK_BufferUsage(%d)\", type);\n\treturn 0;\n}\n\nstatic void VK_BufferStartFrame(void)\n{\n}\n\nstatic void VK_BufferEndFrame(void)\n{\n}\n\nstatic qbool VK_BufferReady(void)\n{\n\treturn true;\n}\n\nstatic buffer_ref VK_BufferCreate(buffertype_t type, const char* name, int size, void* data, bufferusage_t usage)\n{\n\tVkBuffer buffer = { 0 };\n\tVkBufferCreateInfo bufferInfo = { 0 };\n\tVkMemoryRequirements memRequirements = { 0 };\n\n\tbufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;\n\tbufferInfo.size = size;\n\tbufferInfo.usage = VK_BufferUsageForType(type);\n\tbufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; // FIXME: compute queue?\n\tif (vkCreateBuffer(vk_options.logicalDevice, &bufferInfo, NULL, &buffer) != VK_SUCCESS) {\n\t\treturn null_buffer_reference;\n\t}\n\n\tvkGetBufferMemoryRequirements(vk_options.logicalDevice, buffer, &memRequirements);\n\n\t// TODO\n\t//memRequirements.\n\treturn null_buffer_reference;\n}\n\nstatic void VK_BufferEnsureSize(buffer_ref buffer, int size)\n{\n}\n\nstatic void VK_BufferInitialiseState(void)\n{\n}\n\nstatic size_t VK_BufferSize(buffer_ref vbo)\n{\n\treturn 0;\n}\n\nstatic uintptr_t VK_BufferOffset(buffer_ref ref)\n{\n\treturn 0;\n}\n\nstatic void VK_BufferBind(buffer_ref ref)\n{\n}\n\nstatic void VK_BufferBindBase(buffer_ref ref, unsigned int index)\n{\n}\n\nstatic void VK_BufferBindRange(buffer_ref ref, unsigned int index, ptrdiff_t offset, int size)\n{\n}\n\nstatic void VK_BufferUnBind(buffertype_t type)\n{\n}\n\nstatic void VK_BufferUpdate(buffer_ref vbo, int size, void* data)\n{\n}\n\nstatic void VK_BufferUpdateSection(buffer_ref vbo, ptrdiff_t offset, int size, const void* data)\n{\n}\n\nstatic buffer_ref VK_BufferResize(buffer_ref vbo, int size, void* data)\n{\n\treturn null_buffer_reference;\n}\n\nstatic qbool VK_BufferIsValid(buffer_ref ref)\n{\n\treturn false;\n}\n\nstatic void VK_BufferSetElementArray(buffer_ref ref)\n{\n\treturn;\n}\n\nstatic void VK_BufferShutdown(void)\n{\n\treturn;\n}\n\n#ifdef WITH_RENDERING_TRACE\nstatic void VK_PrintBufferState(FILE* output, int depth)\n{\n}\n#endif\n\nvoid VK_InitialiseBufferHandling(api_buffers_t* api)\n{\n\tmemset(api, 0, sizeof(*api));\n\n\tapi->InitialiseState = VK_BufferInitialiseState;\n\n\tapi->StartFrame = VK_BufferStartFrame;\n\tapi->EndFrame = VK_BufferEndFrame;\n\tapi->FrameReady = VK_BufferReady;\n\n\tapi->Size = VK_BufferSize;\n\tapi->Create = VK_BufferCreate;\n\tapi->BufferOffset = VK_BufferOffset;\n\n\tapi->Bind = VK_BufferBind;\n\tapi->BindBase = VK_BufferBindBase;\n\tapi->BindRange = VK_BufferBindRange;\n\tapi->UnBind = VK_BufferUnBind;\n\n\tapi->Update = VK_BufferUpdate;\n\tapi->UpdateSection = VK_BufferUpdateSection;\n\tapi->Resize = VK_BufferResize;\n\tapi->EnsureSize = VK_BufferEnsureSize;\n\n\tapi->IsValid = VK_BufferIsValid;\n\tapi->SetElementArray = VK_BufferSetElementArray;\n\tapi->Shutdown = VK_BufferShutdown;\n\n#ifdef WITH_RENDERING_TRACE\n\tapi->PrintState = VK_PrintBufferState;\n#endif\n}\n\n#endif // #ifdef RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_debug.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// vk_debug.c\n// - Vulkan debug callback\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include <vulkan/vulkan.h>\n#include \"quakedef.h\"\n\n#include <SDL.h>\n#include <SDL_vulkan.h>\n\n#include \"vk_local.h\"\n\nstatic qbool callbackRegistered = false;\nstatic VkDebugReportCallbackEXT callbackHandle;\n\nstatic VKAPI_ATTR VkBool32 VKAPI_CALL VK_DebugCallback\n(\n\tVkDebugReportFlagsEXT flags,\n\tVkDebugReportObjectTypeEXT objType,\n\tuint64_t obj,\n\tsize_t location,\n\tint32_t code,\n\tconst char* layerPrefix,\n\tconst char* msg,\n\tvoid* userData\n)\n{\n\tCon_Printf(\"vulkan: %s\\n\", msg);\n\n\treturn VK_FALSE;\n}\n\nvoid VK_InitialiseDebugCallback(VkInstance instance)\n{\n\tEZ_VKFUNC_DECL_LOAD(instance, vkCreateDebugReportCallbackEXT);\n\n\tif (qvkCreateDebugReportCallbackEXT) {\n\t\tVkDebugReportCallbackCreateInfoEXT createInfo = { 0 };\n\t\tcreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;\n\t\tcreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;\n\t\tcreateInfo.pfnCallback = VK_DebugCallback;\n\n\t\tif (qvkCreateDebugReportCallbackEXT(instance, &createInfo, NULL, &callbackHandle) == VK_SUCCESS) {\n\t\t\tcallbackRegistered = true;\n\t\t\tCon_Printf(\"Callback registered\\n\");\n\t\t}\n\t\telse {\n\t\t\tCon_Printf(\"Callback registration failed\\n\");\n\t\t}\n\t}\n\telse {\n\t\tCon_Printf(\"Callback not supported\\n\");\n\t}\n}\n\nvoid VK_ShutdownDebugCallback(VkInstance instance)\n{\n\tif (callbackRegistered) {\n\t\tEZ_VKFUNC_DECL_LOAD(instance, vkDestroyDebugReportCallbackEXT);\n\n\t\tif (qvkDestroyDebugReportCallbackEXT) {\n\t\t\tqvkDestroyDebugReportCallbackEXT(instance, callbackHandle, NULL);\n\t\t}\n\t\tcallbackRegistered = false;\n\t}\n}\n\n#endif // RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_draw.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// vk_draw.c\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include \"quakedef.h\"\n\nvoid VK_HudDrawComplete(void)\n{\n\n}\n\nvoid VK_HudPrepareCircles(void)\n{\n}\n\nvoid VK_HudDrawCircles(texture_ref tex, int start, int end)\n{\n}\n\nvoid VK_HudPrepareImages(void)\n{\n}\n\nvoid VK_HudDrawImages(texture_ref tex, int start, int end)\n{\n}\n\nvoid VK_HudPreparePolygons(void)\n{\n}\n\nvoid VK_HudDrawPolygons(texture_ref tex, int start, int end)\n{\n}\n\nvoid VK_HudPrepareLines(void)\n{\n}\n\nvoid VK_HudDrawLines(texture_ref ref, int start, int end)\n{\n}\n\nvoid VK_DrawImage(float x, float y, float width, float height, float tex_s, float tex_t, float tex_width, float tex_height, byte* color, int flags)\n{\n}\n\nvoid VK_DrawRectangle(float x, float y, float width, float height, byte* color)\n{\n}\n\nvoid VK_AdjustImages(int first, int last, float x_offset)\n{\n}\n\n#endif // #ifdef RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_graphics_pipeline.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// vk_vao.c\n// - Vulkan graphics pipeline functions\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include <vulkan/vulkan.h>\n#include \"quakedef.h\"\n\nvoid VK_CreateGraphicsPipelines(void)\n{\n\tVkPipelineVertexInputStateCreateInfo vertexInputInfo = {};\n\tvertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;\n\tvertexInputInfo.vertexBindingDescriptionCount = 0;\n\tvertexInputInfo.pVertexBindingDescriptions = nullptr; // Optional\n\tvertexInputInfo.vertexAttributeDescriptionCount = 0;\n\tvertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optional\n\n\tVkPipelineInputAssemblyStateCreateInfo inputAssembly = {};\n\tinputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;\n\tinputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;\n\tinputAssembly.primitiveRestartEnable = VK_FALSE;\n}\n\n#endif // RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_instance.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// vk_instance.c\n// - Code to create a vulkan instance\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include <vulkan/vulkan.h>\n#include \"quakedef.h\"\n\n#include <SDL.h>\n#include <SDL_vulkan.h>\n\n#include \"vk_local.h\"\n\n// VK_EXT_debug_report extension to get debug messages \n#define EZ_MAX_ADDITIONAL_EXTENSIONS 1\n\nstatic const char* validationLayers[] = { \"VK_LAYER_LUNARG_standard_validation\" };\n\nstatic qbool VK_AddValidationLayers(VkInstanceCreateInfo* createInfo)\n{\n\tVkLayerProperties* availableLayers = NULL;\n\n\tcreateInfo->enabledLayerCount = 0;\n\tif (COM_CheckParm(cmdline_param_developer_mode)) {\n\t\tunsigned int layerCount, j;\n\t\tint i;\n\t\tqbool available = true;\n\t\tVkResult result;\n\n\t\tresult = vkEnumerateInstanceLayerProperties(&layerCount, NULL);\n\t\tif (result != VK_SUCCESS) {\n\t\t\treturn false;\n\t\t}\n\n\t\tavailableLayers = Q_malloc(sizeof(availableLayers[0]) * layerCount);\n\t\tresult = vkEnumerateInstanceLayerProperties(&layerCount, availableLayers);\n\t\tif (result != VK_SUCCESS) {\n\t\t\tQ_free(availableLayers);\n\t\t\treturn false;\n\t\t}\n\t\tfor (i = 0; i < sizeof(validationLayers) / sizeof(validationLayers[0]); ++i) {\n\t\t\tqbool found = false;\n\t\t\tfor (j = 0; j < layerCount && !found; ++j) {\n\t\t\t\tfound |= !strcmp(availableLayers[j].layerName, validationLayers[i]);\n\t\t\t}\n\t\t\tavailable &= found;\n\t\t}\n\t\tQ_free(availableLayers);\n\n\t\tif (available) {\n\t\t\tcreateInfo->ppEnabledLayerNames = validationLayers;\n\t\t\tcreateInfo->enabledLayerCount = sizeof(validationLayers) / sizeof(validationLayers[0]);\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nqbool VK_CreateInstance(SDL_Window* window, VkInstance* instance)\n{\n\tqbool debugCallback = false;\n\tVkInstanceCreateInfo createInfo = { 0 };\n\tVkApplicationInfo appInfo = { 0 };\n\tVkResult result;\n\tuint32_t extensionCount = 0;\n\tconst char** extensionStrings;\n\n\tappInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;\n\tappInfo.pApplicationName = \"ezQuake\";\n\tappInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);\n\tappInfo.pEngineName = \"?\";\n\tappInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);\n\tappInfo.apiVersion = VK_API_VERSION_1_0;\n\n\tcreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;\n\tcreateInfo.pApplicationInfo = &appInfo;\n\n\tif (!SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, NULL)) {\n\t\tCon_Printf(\"vulkan:GetInstanceExtensions() failed\\n\");\n\t\treturn false;\n\t}\n\n\textensionStrings = Q_malloc(sizeof(extensionStrings[0]) * (extensionCount + EZ_MAX_ADDITIONAL_EXTENSIONS));\n\tif (!SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, extensionStrings)) {\n\t\tCon_Printf(\"vulkan:GetInstanceExtensions() failed\\n\");\n\t\treturn false;\n\t}\n\n\tif (VK_AddValidationLayers(&createInfo)) {\n\t\t// Add VK_EXT_DEBUG_REPORT_EXTENSION_NAME\n\t\textensionStrings[extensionCount++] = VK_EXT_DEBUG_REPORT_EXTENSION_NAME;\n\t\tCon_Printf(\"vulkan: validation layers available\\n\");\n\t\tdebugCallback = true;\n\t}\n\n\tcreateInfo.enabledExtensionCount = extensionCount;\n\tcreateInfo.ppEnabledExtensionNames = extensionStrings;\n\n\tresult = vkCreateInstance(&createInfo, NULL, instance);\n\tQ_free((void*)extensionStrings);\n\tif (result != VK_SUCCESS) {\n\t\t*instance = NULL;\n\t\tCon_Printf(\"vulkan:GetInstanceExtensions() failed\\n\");\n\t\treturn false;\n\t}\n\n\tCon_Printf(\"Vulkan initialised successfully!\");\n\n\tif (debugCallback) {\n\t\tVK_InitialiseDebugCallback(*instance);\n\t}\n\n\treturn true;\n}\n\n#endif // RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_lightmaps.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifdef RENDERER_OPTION_VULKAN\n\nvoid VK_CreateLightmapTextures(void)\n{\n}\n\nvoid VK_UploadLightmap(int textureUnit, int lightmapnum)\n{\n\n}\n\nvoid VK_BuildLightmap(int lightmapnum)\n{\n}\n\n#endif // #ifdef RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_local.h",
    "content": "/*\nCopyright (C) 2018 ezQuake team.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_VK_LOCAL_HEADER\n#define EZQUAKE_VK_LOCAL_HEADER\n\n#define EZ_VKFUNC_DECL_LOAD(instance, func) PFN_##func q##func = (PFN_##func)vkGetInstanceProcAddr(instance, #func)\n#define EZ_VKFUNC_LOAD(instance, func) q##func = (PFN_##func)vkGetInstanceProcAddr(instance, #func)\n\n// vk_main.c\nqbool VK_Initialise(SDL_Window* window);\nvoid VK_Shutdown(void);\n\n// vk_instance.c\nqbool VK_CreateInstance(SDL_Window* window, VkInstance* instance);\n\n// vk_debug.c\nvoid VK_ShutdownDebugCallback(VkInstance instance);\nvoid VK_InitialiseDebugCallback(VkInstance instance);\n\n// vk_physical_devices.c\nqbool VK_SelectPhysicalDevice(VkInstance instance, VkSurfaceKHR surface);\nuint32_t VK_PhysicalDeviceGraphicsQueueFamilyIndex(void);\nuint32_t VK_PhysicalDeviceComputeQueueFamilyIndex(void);\nuint32_t VK_PhysicalDevicePresentQueueFamilyIndex(void);\nqbool VK_CreateLogicalDevice(VkInstance instance);\n\n// vk_window_surface.c\nqbool VK_CreateWindowSurface(SDL_Window* window, VkInstance instance, VkSurfaceKHR* surface);\nvoid VK_DestroyWindowSurface(VkInstance instance, VkSurfaceKHR surface);\n\n// vk_swapchain.c\nqbool VK_CreateSwapChain(SDL_Window* window, VkInstance instance, VkSurfaceKHR surface);\nvoid VK_DestroySwapChain(void);\n\n// vk_blending.c\nvoid VK_BlendingConfigure(VkPipelineColorBlendStateCreateInfo* info, VkPipelineColorBlendAttachmentState* blending, r_blendfunc_t func);\n\n// (common)\ntypedef struct vk_options_s {\n\tVkInstance instance;\n\tVkSurfaceKHR surface;\n\n\tVkPhysicalDevice physicalDevice;\n\tVkPhysicalDeviceFeatures physicalDeviceFeatures;\n\tVkPhysicalDeviceProperties physicalDeviceProperties;\n\tuint32_t physicalDeviceGraphicsQueueFamilyIndex;\n\tuint32_t physicalDeviceComputeQueueFamilyIndex;\n\tuint32_t physicalDevicePresentQueueFamilyIndex;\n\tVkPresentModeKHR physicalDevicePresentationMode;\n\tVkSurfaceFormatKHR physicalDeviceSurfaceFormat;\n\tVkSurfaceCapabilitiesKHR physicalDeviceSurfaceCapabilities;\n\tVkDevice logicalDevice;\n\tVkQueue graphicsQueue;\n\tVkQueue presentQueue;\n\tstruct {\n\t\tVkSwapchainKHR handle;\n\t\tVkImage* images;\n\t\tVkImageView* imageViews;\n\t\tVkExtent2D imageSize;\n\t\tint imageCount;\n\t} swapChain;\n} vk_options_t;\n\nextern vk_options_t vk_options;\n\nvoid VK_PrintGfxInfo(void);\n\n#define VK_Initialise(x) { memset(&x, 0, sizeof(x)); }\n\n#endif\n"
  },
  {
    "path": "src/vk_main.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// vk_main.c\n// - Main entry point for Vulkan\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include <vulkan/vulkan.h>\n#include \"quakedef.h\"\n\n#include <SDL.h>\n#include <SDL_vulkan.h>\n\n#include \"vk_local.h\"\n\nvk_options_t vk_options;\n\nqbool VK_Initialise(SDL_Window* window)\n{\n\tmemset(&vk_options, 0, sizeof(vk_options));\n\n\tif (!VK_CreateInstance(window, &vk_options.instance)) {\n\t\treturn false;\n\t}\n\n\tif (!VK_CreateWindowSurface(window, vk_options.instance, &vk_options.surface)) {\n\t\tVK_Shutdown();\n\t\treturn false;\n\t}\n\n\tif (!VK_SelectPhysicalDevice(vk_options.instance, vk_options.surface)) {\n\t\tVK_Shutdown();\n\t\treturn false;\n\t}\n\n\tif (!VK_CreateLogicalDevice(vk_options.instance)) {\n\t\tVK_Shutdown();\n\t\treturn false;\n\t}\n\n\tif (!VK_CreateSwapChain(window, vk_options.instance, vk_options.surface)) {\n\t\tVK_Shutdown();\n\t\treturn false;\n\t}\n\n\tCon_Printf(\"Vulkan initialised successfully\\n\");\n\treturn true;\n}\n\nvoid VK_Shutdown(r_shutdown_mode_t mode)\n{\n\tif (mode != r_shutdown_reload) {\n\t\tVK_DestroySwapChain();\n\n\t\tVK_RenderPassDelete();\n\n\t\tif (vk_options.instance) {\n\t\t\tVK_DestroyWindowSurface(vk_options.instance, vk_options.surface);\n\t\t\tVK_ShutdownDebugCallback(vk_options.instance);\n\t\t\tvkDestroyInstance(vk_options.instance, NULL);\n\t\t}\n\n\t\tmemset(&vk_options, 0, sizeof(vk_options));\n\t}\n\n\t// FIXME\n}\n\nvoid VK_PopulateConfig(void)\n{\n}\n\n#endif\n"
  },
  {
    "path": "src/vk_md3.c",
    "content": "\n/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include \"quakedef.h\"\n\nvoid VK_DrawAlias3Model(entity_t* ent, qbool outline, qbool additive_pass)\n{\n}\n\n#endif // #ifdef RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_misc.c",
    "content": "\n/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include \"quakedef.h\"\n\nvoid VK_PrintGfxInfo(void)\n{\n}\n\n#endif // #ifdef RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_physical_devices.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// vk_physical_devices.c\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include <vulkan/vulkan.h>\n#include \"quakedef.h\"\n\n#include <SDL.h>\n#include <SDL_vulkan.h>\n\n#include \"vk_local.h\"\n\nstatic const char* validationLayers[] = { \"VK_LAYER_LUNARG_standard_validation\" };\nstatic const char* requiredDeviceExtensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };\n\nstatic void VK_PhysicalDeviceQueryQueueFamilies(VkPhysicalDevice device, VkSurfaceKHR surface, int* graphics_queue_index, int* compute_queue_index, int* present_queue_index)\n{\n\tuint32_t queue_families_count;\n\tVkQueueFamilyProperties* queue_family_properties;\n\tuint32_t j;\n\n\tvkGetPhysicalDeviceQueueFamilyProperties(device, &queue_families_count, NULL);\n\tqueue_family_properties = Q_malloc(sizeof(VkQueueFamilyProperties) * queue_families_count);\n\tvkGetPhysicalDeviceQueueFamilyProperties(device, &queue_families_count, queue_family_properties);\n\n\t*graphics_queue_index = *compute_queue_index = *present_queue_index = -1;\n\tfor (j = 0; j < queue_families_count; ++j) {\n\t\tif (queue_family_properties[j].queueCount > 0) {\n\t\t\tif (queue_family_properties[j].queueFlags & VK_QUEUE_GRAPHICS_BIT) {\n\t\t\t\t*graphics_queue_index = j;\n\n\t\t\t\tif (surface != VK_NULL_HANDLE) {\n\t\t\t\t\tVkBool32 present_supported;\n\n\t\t\t\t\tif (vkGetPhysicalDeviceSurfaceSupportKHR(device, j, surface, &present_supported) == VK_SUCCESS && present_supported) {\n\t\t\t\t\t\t*present_queue_index = j;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (queue_family_properties[j].queueFlags & VK_QUEUE_COMPUTE_BIT) {\n\t\t\t\t*compute_queue_index = j;\n\t\t\t}\n\t\t}\n\t}\n\n\tQ_free(queue_family_properties);\n}\n\nstatic qbool VK_PhysicalDeviceSupportsRequiredExtensions(VkPhysicalDevice device)\n{\n\t// \n\tuint32_t count;\n\tVkExtensionProperties* properties;\n\tuint32_t foundCount = 0;\n\tuint32_t i, j;\n\n\tvkEnumerateDeviceExtensionProperties(device, NULL, &count, NULL);\n\tproperties = Q_malloc(count * sizeof(VkExtensionProperties));\n\tvkEnumerateDeviceExtensionProperties(device, NULL, &count, properties);\n\n\tfor (i = 0; i < count; ++i) {\n\t\tfor (j = 0; j < sizeof(requiredDeviceExtensions) / sizeof(requiredDeviceExtensions[0]); ++j) {\n\t\t\tif (!strcmp(properties[i].extensionName, requiredDeviceExtensions[j])) {\n\t\t\t\t++foundCount;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tQ_free(properties);\n\n\treturn (foundCount == sizeof(requiredDeviceExtensions) / sizeof(requiredDeviceExtensions[0]));\n}\n\nstatic qbool VK_PhysicalDeviceBestPresentationMode(VkPhysicalDevice device, VkSurfaceKHR surface, VkPresentModeKHR* best)\n{\n\textern cvar_t r_swapInterval;\n\tVkPresentModeKHR preferredModes[] = {\n\t\tVK_PRESENT_MODE_MAILBOX_KHR,       // triple buffered\n\t\tVK_PRESENT_MODE_IMMEDIATE_KHR,     // tearing\n\t\tVK_PRESENT_MODE_FIFO_RELAXED_KHR,\n\t\tVK_PRESENT_MODE_FIFO_KHR\n\t};\n\tVkPresentModeKHR* presentationModes;\n\tuint32_t count;\n\tuint32_t i, j;\n\n\t// This is guaranteed to be supported\n\tif (r_swapInterval.integer) {\n\t\t*best = VK_PRESENT_MODE_FIFO_KHR;\n\t\treturn true;\n\t}\n\n\tif (vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &count, NULL) != VK_SUCCESS) {\n\t\treturn false;\n\t}\n\n\tpresentationModes = Q_malloc(count * sizeof(presentationModes[0]));\n\tif (vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &count, presentationModes) != VK_SUCCESS) {\n\t\tQ_free(presentationModes);\n\t\treturn false;\n\t}\n\n\tfor (i = 0; i < sizeof(preferredModes) / sizeof(preferredModes[0]); ++i) {\n\t\tfor (j = 0; j < count; ++j) {\n\t\t\tif (preferredModes[i] == presentationModes[j]) {\n\t\t\t\tQ_free(presentationModes);\n\t\t\t\t*best = preferredModes[i];\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\tQ_free(presentationModes);\n\t*best = VK_PRESENT_MODE_FIFO_KHR;\n\treturn true;\n}\n\nstatic qbool VK_PhysicalDeviceSwapChainCompatible(VkPhysicalDevice device, VkSurfaceKHR surface, VkSurfaceFormatKHR* preferred_format, VkSurfaceCapabilitiesKHR* capabilities)\n{\n\textern cvar_t vid_gammacorrection;\n\tuint32_t num_formats;\n\tVkSurfaceFormatKHR* formats;\n\tVkColorSpaceKHR req_color_space = (vid_gammacorrection.integer ? VK_COLOR_SPACE_SRGB_NONLINEAR_KHR : VK_COLOR_SPACE_PASS_THROUGH_EXT);\n\n\tif (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, capabilities) != VK_SUCCESS) {\n\t\treturn false;\n\t}\n\n\tif (vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &num_formats, NULL) != VK_SUCCESS || num_formats == 0) {\n\t\treturn false;\n\t}\n\tformats = Q_malloc(num_formats * sizeof(formats[0]));\n\tif (vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &num_formats, formats) != VK_SUCCESS) {\n\t\tQ_free(formats);\n\t\treturn false;\n\t}\n\n\tif (num_formats == 1 && formats[0].format == VK_FORMAT_UNDEFINED) {\n\t\t// Special case: no preferred format\n\t\tpreferred_format->colorSpace = req_color_space;\n\t\tpreferred_format->format = VK_FORMAT_B8G8R8A8_UNORM;\n\t}\n\telse {\n\t\t// Find optimal format.... FIXME (what are we really doing here?)\n\t\tuint32_t i;\n\n\t\tfor (i = 0; i < num_formats; ++i) {\n\t\t\tif (formats[i].format == VK_FORMAT_B8G8R8A8_UNORM && formats[i].colorSpace == req_color_space) {\n\t\t\t\tpreferred_format->colorSpace = req_color_space;\n\t\t\t\tpreferred_format->format = VK_FORMAT_B8G8R8A8_UNORM;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (i >= num_formats) {\n\t\t\tpreferred_format->colorSpace = formats[0].colorSpace;\n\t\t\tpreferred_format->format = formats[0].format;\n\t\t}\n\t}\n\tQ_free(formats);\n\treturn true;\n}\n\nqbool VK_SelectPhysicalDevice(VkInstance instance, VkSurfaceKHR surface)\n{\n\tuint32_t deviceCount = 0;\n\tVkResult result;\n\tVkPhysicalDevice* physicalDevices;\n\tuint32_t i;\n\n\tresult = vkEnumeratePhysicalDevices(instance, &deviceCount, NULL);\n\tif (result != VK_SUCCESS) {\n\t\tCon_Printf(\"vulkan: enumerating physical devices failed\\n\");\n\t\treturn false;\n\t}\n\tphysicalDevices = Q_malloc(deviceCount * sizeof(VkPhysicalDevice));\n\tresult = vkEnumeratePhysicalDevices(instance, &deviceCount, physicalDevices);\n\tif (result != VK_SUCCESS) {\n\t\tQ_free(physicalDevices);\n\t\treturn false;\n\t}\n\n\tvk_options.physicalDevice = VK_NULL_HANDLE;\n\tfor (i = 0; i < deviceCount; ++i) {\n\t\tVkPhysicalDeviceFeatures features;\n\t\tVkPhysicalDeviceProperties properties;\n\t\tVkPresentModeKHR best_presentation_mode;\n\t\tqbool new_best = true;\n\t\tint graphics_queue_index = -1;\n\t\tint compute_queue_index = -1;\n\t\tint present_queue_index = -1;\n\t\tVkSurfaceFormatKHR preferred_format;\n\t\tVkSurfaceCapabilitiesKHR capabilities;\n\n\t\tvkGetPhysicalDeviceProperties(physicalDevices[i], &properties);\n\t\tCon_Printf(\"Device %d: %s\\n\", i, properties.deviceName);\n\n\t\tif (!VK_PhysicalDeviceSupportsRequiredExtensions(physicalDevices[i])) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Must support graphics queues\n\t\tVK_PhysicalDeviceQueryQueueFamilies(physicalDevices[i], surface, &graphics_queue_index, &compute_queue_index, &present_queue_index);\n\t\tif (graphics_queue_index < 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!VK_PhysicalDeviceSwapChainCompatible(physicalDevices[i], surface, &preferred_format, &capabilities)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!VK_PhysicalDeviceBestPresentationMode(physicalDevices[i], surface, &best_presentation_mode)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (vk_options.physicalDevice != VK_NULL_HANDLE) {\n\t\t\t// Score?  Or cvar...\n\t\t\t/*\n\t\t\tvkGetPhysicalDeviceFeatures(physicalDevices[i], &features);\n\n\t\t\tif (properties.deviceType == )\n\t\t\tphysicalDevices[i]*/\n\t\t\tnew_best = false;\n\t\t}\n\n\t\tif (new_best) {\n\t\t\tvk_options.physicalDevice = physicalDevices[i];\n\t\t\tmemcpy(&vk_options.physicalDeviceFeatures, &features, sizeof(vk_options.physicalDeviceFeatures));\n\t\t\tmemcpy(&vk_options.physicalDeviceProperties, &properties, sizeof(vk_options.physicalDeviceProperties));\n\t\t\tvk_options.physicalDeviceGraphicsQueueFamilyIndex = graphics_queue_index;\n\t\t\tvk_options.physicalDeviceComputeQueueFamilyIndex = compute_queue_index;\n\t\t\tvk_options.physicalDevicePresentQueueFamilyIndex = present_queue_index;\n\t\t\tvk_options.physicalDevicePresentationMode = best_presentation_mode;\n\t\t\tvk_options.physicalDeviceSurfaceFormat = preferred_format;\n\t\t\tvk_options.physicalDeviceSurfaceCapabilities = capabilities;\n\t\t}\n\t}\n\n\tQ_free(physicalDevices);\n\tif (vk_options.physicalDevice == VK_NULL_HANDLE) {\n\t\tCom_Printf(\"No appropriate device found :(\\n\");\n\t\treturn false;\n\t}\n\n\tCom_Printf(\"Selected device: %s\\n\", vk_options.physicalDeviceProperties.deviceName);\n\n\treturn true;\n}\n\nuint32_t VK_PhysicalDeviceGraphicsQueueFamilyIndex(void)\n{\n\tassert(vk_options.physicalDevice != VK_NULL_HANDLE);\n\n\treturn vk_options.physicalDeviceGraphicsQueueFamilyIndex;\n}\n\nuint32_t VK_PhysicalDeviceComputeQueueFamilyIndex(void)\n{\n\tassert(vk_options.physicalDevice != VK_NULL_HANDLE);\n\n\treturn vk_options.physicalDeviceComputeQueueFamilyIndex;\n}\n\nuint32_t VK_PhysicalDevicePresentQueueFamilyIndex(void)\n{\n\tassert(vk_options.physicalDevice != VK_NULL_HANDLE);\n\n\treturn vk_options.physicalDevicePresentQueueFamilyIndex;\n}\n\nstatic qbool VK_AddDeviceValidationLayers(VkDeviceCreateInfo* createInfo)\n{\n\tVkLayerProperties* availableLayers = NULL;\n\n\tcreateInfo->enabledLayerCount = 0;\n\tif (COM_CheckParm(cmdline_param_developer_mode)) {\n\t\tunsigned int layerCount, j;\n\t\tint i;\n\t\tqbool available = true;\n\t\tVkResult result;\n\n\t\tresult = vkEnumerateInstanceLayerProperties(&layerCount, NULL);\n\t\tif (result != VK_SUCCESS) {\n\t\t\treturn false;\n\t\t}\n\n\t\tavailableLayers = Q_malloc(sizeof(availableLayers[0]) * layerCount);\n\t\tresult = vkEnumerateInstanceLayerProperties(&layerCount, availableLayers);\n\t\tif (result != VK_SUCCESS) {\n\t\t\tQ_free(availableLayers);\n\t\t\treturn false;\n\t\t}\n\t\tfor (i = 0; i < sizeof(validationLayers) / sizeof(validationLayers[0]); ++i) {\n\t\t\tqbool found = false;\n\t\t\tfor (j = 0; j < layerCount && !found; ++j) {\n\t\t\t\tfound |= !strcmp(availableLayers[j].layerName, validationLayers[i]);\n\t\t\t}\n\t\t\tavailable &= found;\n\t\t}\n\t\tQ_free(availableLayers);\n\n\t\tif (available) {\n\t\t\tcreateInfo->ppEnabledLayerNames = validationLayers;\n\t\t\tcreateInfo->enabledLayerCount = sizeof(validationLayers) / sizeof(validationLayers[0]);\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nqbool VK_CreateLogicalDevice(VkInstance instance)\n{\n\tVkDeviceQueueCreateInfo queueInfos[2] = { { 0 } };\n\tVkDeviceCreateInfo deviceInfo = { 0 };\n\tVkPhysicalDeviceFeatures deviceFeatures = { 0 };\n\tfloat priorities[] = { 1.0f };\n\tuint32_t queueCount = 0;\n\n\tqueueInfos[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;\n\tqueueInfos[0].queueFamilyIndex = VK_PhysicalDeviceGraphicsQueueFamilyIndex();\n\tqueueInfos[0].queueCount = sizeof(priorities) / sizeof(priorities[0]);\n\tqueueInfos[0].pQueuePriorities = priorities;\n\t++queueCount;\n\n\tif (VK_PhysicalDeviceGraphicsQueueFamilyIndex() != VK_PhysicalDevicePresentQueueFamilyIndex()) {\n\t\tqueueInfos[queueCount].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;\n\t\tqueueInfos[queueCount].queueFamilyIndex = VK_PhysicalDevicePresentQueueFamilyIndex();\n\t\tqueueInfos[queueCount].queueCount = sizeof(priorities) / sizeof(priorities[0]);\n\t\tqueueInfos[queueCount].pQueuePriorities = priorities;\n\t\t++queueCount;\n\t}\n\n\tdeviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;\n\tdeviceInfo.pQueueCreateInfos = queueInfos;\n\tdeviceInfo.queueCreateInfoCount = sizeof(queueInfos) / sizeof(queueInfos[0]);\n\tdeviceInfo.pEnabledFeatures = &deviceFeatures;\n\n\tdeviceInfo.enabledExtensionCount = 0;\n\tVK_AddDeviceValidationLayers(&deviceInfo);\n\n\tvk_options.logicalDevice = VK_NULL_HANDLE;\n\tif (vkCreateDevice(vk_options.physicalDevice, &deviceInfo, NULL, &vk_options.logicalDevice) != VK_SUCCESS) {\n\t\tCon_Printf(\"vkCreateDevice() failed\\n\");\n\t\treturn false;\n\t}\n\n\tvkGetDeviceQueue(vk_options.logicalDevice, VK_PhysicalDeviceGraphicsQueueFamilyIndex(), 0, &vk_options.graphicsQueue);\n\tif (VK_PhysicalDeviceGraphicsQueueFamilyIndex() != VK_PhysicalDevicePresentQueueFamilyIndex()) {\n\t\tvkGetDeviceQueue(vk_options.logicalDevice, VK_PhysicalDevicePresentQueueFamilyIndex(), 0, &vk_options.presentQueue);\n\t}\n\telse {\n\t\tvk_options.presentQueue = vk_options.graphicsQueue;\n\t}\n\n\treturn true;\n}\n\n#endif // RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_renderpass.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// vk_vao.c\n// - Vulkan VAO-equivalent functions\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include <vulkan/vulkan.h>\n#include \"quakedef.h\"\n#include \"r_state.h\"\n\n#include \"vk_local.h\"\n\ntypedef enum {\n\tvk_renderpass_none,\n\n\tvk_renderpass_count\n} vk_renderpass_id;\n\nstatic VkRenderPass renderPasses[vk_renderpass_count];\n\nqbool VK_RenderPassCreate(vk_renderpass_id id)\n{\n\tVkAttachmentDescription colorAttachment;\n\tVkAttachmentReference colorAttachmentRef;\n\tVkSubpassDescription subpass;\n\tVkRenderPassCreateInfo renderPassInfo;\n\n\t// single color buffer attachment\n\tVK_InitialiseStructure(colorAttachment);\n\tcolorAttachment.format = vk_options.physicalDeviceSurfaceFormat.format;\n\tcolorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;\n\tcolorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;\n\tcolorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;\n\tcolorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;\n\tcolorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;\n\tcolorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;\n\tcolorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;\n\n\t// attachment reference\n\tVK_InitialiseStructure(colorAttachmentRef);\n\tcolorAttachmentRef.attachment = 0;\n\tcolorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;\n\n\t// Sub-passes\n\tVK_InitialiseStructure(subpass);\n\tsubpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;\n\tsubpass.colorAttachmentCount = 1;\n\tsubpass.pColorAttachments = &colorAttachmentRef;\n\n\t// Render pass\n\tVK_InitialiseStructure(renderPassInfo);\n\trenderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;\n\trenderPassInfo.attachmentCount = 1;\n\trenderPassInfo.pAttachments = &colorAttachment;\n\trenderPassInfo.subpassCount = 1;\n\trenderPassInfo.pSubpasses = &subpass;\n\n\tif (vkCreateRenderPass(vk_options.logicalDevice, &renderPassInfo, NULL, &renderPasses[id]) != VK_SUCCESS) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid VK_RenderPassDelete(vk_renderpass_id id)\n{\n\tint i;\n\n\tfor (i = 0; i < vk_renderpass_count; ++i) {\n\t\tif (renderPasses[i]) {\n\t\t\tvkDestroyRenderPass(vk_options.logicalDevice, renderPasses[i], NULL);\n\t\t}\n\t}\n}\n\n#endif // RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_sdl.c",
    "content": "/*\n===========================================================================\nCopyright (C) 1999-2005 Id Software, Inc.\n\nThis file is part of Quake III Arena source code.\n\nQuake III Arena source code is free software; you can redistribute it\nand/or modify it under the terms of the GNU General Public License as\npublished by the Free Software Foundation; either version 2 of the License,\nor (at your option) any later version.\n\nQuake III Arena source code is distributed in the hope that it will be\nuseful, but WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Foobar; if not, write to the Free Software\nFoundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n===========================================================================\n\n*/\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include <SDL.h>\n#include \"quakedef.h\"\n\nvoid VK_SDL_SetupAttributes(void)\n{\n\t// Do nothing at the moment...\n}\n\n#endif // #ifdef RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_swapchain.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// vk_main.c\n// - Main entry point for Vulkan\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include <vulkan/vulkan.h>\n#include \"quakedef.h\"\n\n#include <SDL.h>\n#include <SDL_vulkan.h>\n\n#include \"vk_local.h\"\n\nqbool VK_CreateSwapChain(SDL_Window* window, VkInstance instance, VkSurfaceKHR surface)\n{\n\tuint32_t requestedImageCount;\n\tuint32_t queueFamilyIndices[2];\n\tuint32_t swapChainImageCount;\n\tuint32_t i;\n\tVkSwapchainCreateInfoKHR createInfo = { 0 };\n\n\trequestedImageCount = vk_options.physicalDeviceSurfaceCapabilities.minImageCount;\n\tif (vk_options.physicalDevicePresentationMode == VK_PRESENT_MODE_MAILBOX_KHR) {\n\t\trequestedImageCount += 1;\n\t}\n\tif (vk_options.physicalDeviceSurfaceCapabilities.maxImageCount > 0) {\n\t\trequestedImageCount = min(requestedImageCount, vk_options.physicalDeviceSurfaceCapabilities.maxImageCount);\n\t}\n\n\tcreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;\n\tcreateInfo.minImageCount = requestedImageCount;\n\tcreateInfo.surface = surface;\n\tcreateInfo.imageArrayLayers = 1;\n\tcreateInfo.imageColorSpace = vk_options.physicalDeviceSurfaceFormat.colorSpace;\n\tcreateInfo.imageFormat = vk_options.physicalDeviceSurfaceFormat.format;\n\tif (vk_options.physicalDeviceSurfaceCapabilities.currentExtent.width == ~(uint32_t)0) {\n\t\tcreateInfo.imageExtent = vk_options.physicalDeviceSurfaceCapabilities.currentExtent;\n\t}\n\telse {\n\t\tint width, height;\n\n\t\tSDL_GL_GetDrawableSize(window, &width, &height);\n\n\t\twidth = bound(vk_options.physicalDeviceSurfaceCapabilities.minImageExtent.width, width, vk_options.physicalDeviceSurfaceCapabilities.maxImageExtent.width);\n\t\theight = bound(vk_options.physicalDeviceSurfaceCapabilities.minImageExtent.height, height, vk_options.physicalDeviceSurfaceCapabilities.maxImageExtent.height);\n\n\t\tcreateInfo.imageExtent.width = width;\n\t\tcreateInfo.imageExtent.height = height;\n\t}\n\tcreateInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; // VK_IMAGE_USAGE_TRANSFER_DST_BIT if pre-processing enabled\n\tif (VK_PhysicalDeviceGraphicsQueueFamilyIndex() != VK_PhysicalDevicePresentQueueFamilyIndex()) {\n\t\tqueueFamilyIndices[0] = VK_PhysicalDeviceGraphicsQueueFamilyIndex();\n\t\tqueueFamilyIndices[1] = VK_PhysicalDevicePresentQueueFamilyIndex();\n\n\t\tcreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;\n\t\tcreateInfo.queueFamilyIndexCount = 2;\n\t\tcreateInfo.pQueueFamilyIndices = queueFamilyIndices;\n\t}\n\telse {\n\t\tcreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;\n\t\tcreateInfo.queueFamilyIndexCount = 0;\n\t\tcreateInfo.pQueueFamilyIndices = NULL;\n\t}\n\tcreateInfo.preTransform = vk_options.physicalDeviceSurfaceCapabilities.currentTransform;\n\tcreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;\n\tcreateInfo.presentMode = vk_options.physicalDevicePresentationMode;\n\tcreateInfo.clipped = VK_FALSE; // meag: setting this to false so we can read-back for screenshots\n\tcreateInfo.oldSwapchain = vk_options.swapChain.handle;\n\n\tif (vkCreateSwapchainKHR(vk_options.logicalDevice, &createInfo, NULL, &vk_options.swapChain.handle) != VK_SUCCESS) {\n\t\treturn false;\n\t}\n\n\t// Create images\n\tQ_free(vk_options.swapChain.images);\n\tif (vkGetSwapchainImagesKHR(vk_options.logicalDevice, vk_options.swapChain.handle, &swapChainImageCount, NULL) != VK_SUCCESS) {\n\t\treturn false;\n\t}\n\tvk_options.swapChain.images = Q_malloc(swapChainImageCount * sizeof(vk_options.swapChain.images[0]));\n\tif (vkGetSwapchainImagesKHR(vk_options.logicalDevice, vk_options.swapChain.handle, &swapChainImageCount, vk_options.swapChain.images) != VK_SUCCESS) {\n\t\tQ_free(vk_options.swapChain.images);\n\t\treturn false;\n\t}\n\tvk_options.swapChain.imageCount = swapChainImageCount;\n\tvk_options.swapChain.imageSize = createInfo.imageExtent;\n\n\t// Create image views\n\tvk_options.swapChain.imageViews = Q_malloc(swapChainImageCount * sizeof(vk_options.swapChain.imageViews[0]));\n\tfor (i = 0; i < vk_options.swapChain.imageCount; ++i) {\n\t\tVkImageViewCreateInfo createImageViewInfo = { 0 };\n\t\tcreateImageViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;\n\t\tcreateImageViewInfo.image = vk_options.swapChain.images[i];\n\t\tcreateImageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;\n\t\tcreateImageViewInfo.format = createInfo.imageFormat;\n\t\tcreateImageViewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;\n\t\tcreateImageViewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;\n\t\tcreateImageViewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;\n\t\tcreateImageViewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;\n\t\tcreateImageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n\t\tcreateImageViewInfo.subresourceRange.baseMipLevel = 0;\n\t\tcreateImageViewInfo.subresourceRange.levelCount = 1;\n\t\tcreateImageViewInfo.subresourceRange.baseArrayLayer = 0;\n\t\tcreateImageViewInfo.subresourceRange.layerCount = 1;\n\t\tif (vkCreateImageView(vk_options.logicalDevice, &createImageViewInfo, NULL, &vk_options.swapChain.imageViews[i]) != VK_SUCCESS) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nvoid VK_DestroySwapChain(void)\n{\n\tif (vk_options.swapChain.imageViews) {\n\t\tuint32_t i;\n\n\t\tfor (i = 0; i < vk_options.swapChain.imageCount; ++i) {\n\t\t\tvkDestroyImageView(vk_options.logicalDevice, vk_options.swapChain.imageViews[i], NULL);\n\t\t}\n\n\t\tQ_free(vk_options.swapChain.imageViews);\n\t}\n\n\tif (vk_options.swapChain.handle != VK_NULL_HANDLE) {\n\t\tvkDestroySwapchainKHR(vk_options.logicalDevice, vk_options.swapChain.handle, NULL);\n\t\tvk_options.swapChain.handle = VK_NULL_HANDLE;\n\t}\n}\n\n#endif // RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_vao.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// vk_vao.c\n// - Vulkan VAO-equivalent functions\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include <vulkan/vulkan.h>\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"r_local.h\"\n#include \"r_vao.h\"\n#include \"r_state.h\"\n\n#include \"vk_local.h\"\n\n#define MAX_VAO_BINDINGS 16\n\ntypedef struct {\n\tVkVertexInputBindingDescription bindings[MAX_VAO_BINDINGS];\n\tVkVertexInputAttributeDescription attributes[MAX_VAO_BINDINGS];\n\tVkPipelineVertexInputStateCreateInfo vertexInfo;\n} vk_vao_t;\n\nstatic vk_vao_t vaos[vao_count];\n\nvoid VK_ConfigureVertexAttribPointer(r_vao_id vao, buffer_ref vbo, uint32_t index, VkFormat format, int stride, uint32_t offset, qbool instanced)\n{\n\tVkVertexInputBindingDescription* binding = vaos[vao].bindings + index;\n\tVkVertexInputAttributeDescription* attribute = vaos[vao].attributes + index;\n\n\tassert(index < MAX_VAO_BINDINGS);\n\n\tmemset(attribute, 0, sizeof(*attribute));\n\tmemset(binding, 0, sizeof(*binding));\n\n\tbinding->binding = index;\n\tbinding->stride = stride;\n\tbinding->inputRate = (instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX);\n\n\tattribute->binding = index;\n\tattribute->location = index;\n\tattribute->format = format;\n\tattribute->offset = offset;\n}\n\nqbool VK_CreateAliasModelPipeline(buffer_ref aliasModelVBO, buffer_ref instanceVBO)\n{\n\tVkPipelineInputAssemblyStateCreateInfo inputAssembly;\n\tVkViewport viewport = { 0 };\n\tVkRect2D scissor = { 0 };\n\tVkPipelineViewportStateCreateInfo viewportState;\n\tVkPipelineRasterizationStateCreateInfo rasterizer;\n\tVkPipelineDepthStencilStateCreateInfo depthStencil;\n\tVkPipelineMultisampleStateCreateInfo multisampling;\n\tVkPipelineColorBlendAttachmentState blending;\n\tVkPipelineColorBlendStateCreateInfo colorBlending;\n\tVkPipeline pipelineLayout;\n\tVkPipelineLayoutCreateInfo pipelineLayoutInfo;\n\n\tVK_InitialiseStructure(inputAssembly);\n\tVK_InitialiseStructure(viewport);\n\tVK_InitialiseStructure(scissor);\n\tVK_InitialiseStructure(viewportState);\n\tVK_InitialiseStructure(rasterizer);\n\tVK_InitialiseStructure(depthStencil);\n\tVK_InitialiseStructure(multisampling);\n\tVK_InitialiseStructure(blending);\n\tVK_InitialiseStructure(pipelineLayoutInfo);\n\n\tVK_InitialiseStructure(vaos[vao_aliasmodel].vertexInfo);\n\tvaos[vao_aliasmodel].vertexInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;\n\tvaos[vao_aliasmodel].vertexInfo.pVertexBindingDescriptions = vaos[vao_aliasmodel].bindings;\n\tvaos[vao_aliasmodel].vertexInfo.pVertexAttributeDescriptions = vaos[vao_aliasmodel].attributes;\n\n\tVK_ConfigureVertexAttribPointer(vao_aliasmodel, aliasModelVBO, 0, VK_FORMAT_R32G32B32_SFLOAT, sizeof(vbo_model_vert_t), VK_VBO_FIELDOFFSET(vbo_model_vert_t, position), false);\n\tVK_ConfigureVertexAttribPointer(vao_aliasmodel, aliasModelVBO, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(vbo_model_vert_t), VK_VBO_FIELDOFFSET(vbo_model_vert_t, texture_coords), false);\n\tVK_ConfigureVertexAttribPointer(vao_aliasmodel, aliasModelVBO, 2, VK_FORMAT_R32G32B32_SFLOAT, sizeof(vbo_model_vert_t), VK_VBO_FIELDOFFSET(vbo_model_vert_t, normal), false);\n\tVK_ConfigureVertexAttribPointer(vao_aliasmodel, instanceVBO, 3, VK_FORMAT_R32_UINT, sizeof(uint32_t), 0, true);\n\t//VK_ConfigureVertexAttribPointer(vao_aliasmodel, aliasModelVBO, 4, VK_FORMAT_R32_SINT, sizeof(vbo_model_vert_t), VK_VBO_FIELDOFFSET(vbo_model_vert_t, flags), false);\n\t\n\tinputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;\n\tinputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;\n\tinputAssembly.primitiveRestartEnable = VK_FALSE;\n\n\tviewport.x = 0.0f;\n\tviewport.y = 0.0f;\n\tviewport.width = (float)vk_options.swapChain.imageSize.width;\n\tviewport.height = (float)vk_options.swapChain.imageSize.height;\n\tviewport.minDepth = 0.0f;\n\tviewport.maxDepth = 1.0f;\n\n\tscissor.offset.x = scissor.offset.y = 0;\n\tscissor.extent = vk_options.swapChain.imageSize;\n\n\tviewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;\n\tviewportState.viewportCount = 1;\n\tviewportState.pViewports = &viewport;\n\tviewportState.scissorCount = 1;\n\tviewportState.pScissors = &scissor;\n\n\trasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;\n\trasterizer.depthClampEnable = VK_FALSE;\n\trasterizer.rasterizerDiscardEnable = VK_FALSE;\n\trasterizer.polygonMode = VK_POLYGON_MODE_FILL;\n\trasterizer.lineWidth = 1.0f;\n\trasterizer.cullMode = VK_CULL_MODE_BACK_BIT;\n\trasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;\n\trasterizer.depthBiasEnable = VK_FALSE;\n\trasterizer.depthBiasConstantFactor = 0.0f; // Optional\n\trasterizer.depthBiasClamp = 0.0f; // Optional\n\trasterizer.depthBiasSlopeFactor = 0.0f; // Optional\n\n\tmultisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;\n\tmultisampling.sampleShadingEnable = VK_FALSE;\n\tmultisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;\n\tmultisampling.minSampleShading = 1.0f; // Optional\n\tmultisampling.pSampleMask = NULL; // Optional\n\tmultisampling.alphaToCoverageEnable = VK_FALSE; // Optional\n\tmultisampling.alphaToOneEnable = VK_FALSE; // Optional\n\n\t// depth-buffer: todo\n\n\t// blending modes\n\tVK_BlendingConfigure(&colorBlending, &blending, r_blendfunc_premultiplied_alpha);\n\n\tpipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;\n\tpipelineLayoutInfo.setLayoutCount = 0; // Optional\n\tpipelineLayoutInfo.pSetLayouts = NULL; // Optional\n\tpipelineLayoutInfo.pushConstantRangeCount = 0; // Optional\n\tpipelineLayoutInfo.pPushConstantRanges = NULL; // Optional\n\n\tif (vkCreatePipelineLayout(vk_options.logicalDevice, &pipelineLayoutInfo, NULL, &pipelineLayout) != VK_SUCCESS) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid VK_DeleteVAOs(void)\n{\n}\n\nvoid VK_GenVertexArray(r_vao_id vao)\n{\n}\n\nqbool VK_VertexArrayCreated(r_vao_id vao)\n{\n\treturn false;\n}\n\nvoid VK_BindVertexArray(r_vao_id vao)\n{\n}\n\nqbool VK_InitialiseVAOHandling(void)\n{\n\treturn true;\n}\n\n#endif // RENDERER_OPTION_VULKAN\n"
  },
  {
    "path": "src/vk_vao.h",
    "content": "/*\nCopyright (C) 2018 ezQuake team.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#ifndef EZQUAKE_VK_VAO_HEADER\n#define EZQUAKE_VK_VAO_HEADER\n\n#include \"r_vao.h\"\n\nvoid VK_DeleteVAOs(void);\nvoid VK_GenVertexArray(r_vao_id vao, const char* name);\nqbool VK_VertexArrayCreated(r_vao_id vao);\nvoid VK_BindVertexArray(r_vao_id vao);\nqbool VK_InitialiseVAOHandling(void);\n\n#endif // EZQUAKE_VK_VAO_HEADER\n\n"
  },
  {
    "path": "src/vk_window_surface.c",
    "content": "/*\nCopyright (C) 2018 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n// vk_window_surface.c\n// - Window surfaces\n\n#ifdef RENDERER_OPTION_VULKAN\n\n#include <vulkan/vulkan.h>\n#include \"quakedef.h\"\n\n#include <SDL.h>\n#include <SDL_vulkan.h>\n\n#include \"vk_local.h\"\n\nqbool VK_CreateWindowSurface(SDL_Window* window, VkInstance instance, VkSurfaceKHR* surface)\n{\n\tsurface = VK_NULL_HANDLE;\n\n\tif (SDL_Vulkan_CreateSurface(window, instance, surface) != SDL_TRUE) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid VK_DestroyWindowSurface(VkInstance instance, VkSurfaceKHR surface)\n{\n\tif (surface != VK_NULL_HANDLE) {\n\t\tvkDestroySurfaceKHR(instance, surface, NULL);\n\t\tsurface = VK_NULL_HANDLE;\n\t}\n}\n\n#endif\n"
  },
  {
    "path": "src/vm.c",
    "content": "/*\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by\n *  the Free Software Foundation; either version 2 of the License, or\n *  (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n *\n *\n */\n// vm.c -- virtual machine\n/*\n\n\nintermix code and data\nsymbol table\n\na dll has one imported function: VM_SystemCall\nand one exported function: Perform\n\n\n*/\n#ifdef USE_PR2\n#include \"qwsvdef.h\"\n#include \"vm_local.h\"\n\nopcode_info_t ops[ OP_MAX ] =\n{\n\t{ 0, 0, 0, 0 }, // undef\n\t{ 0, 0, 0, 0 }, // ignore\n\t{ 0, 0, 0, 0 }, // break\n\n\t{ 4, 0, 0, 0 }, // enter\n\t{ 4,-4, 0, 0 }, // leave\n\t{ 0, 0, 1, 0 }, // call\n\t{ 0, 4, 0, 0 }, // push\n\t{ 0,-4, 1, 0 }, // pop\n\n\t{ 4, 4, 0, 0 }, // const\n\t{ 4, 4, 0, 0 }, // local\n\t{ 0,-4, 1, 0 }, // jump\n\n\t{ 4,-8, 2, JUMP }, // eq\n\t{ 4,-8, 2, JUMP }, // ne\n\n\t{ 4,-8, 2, JUMP }, // lti\n\t{ 4,-8, 2, JUMP }, // lei\n\t{ 4,-8, 2, JUMP }, // gti\n\t{ 4,-8, 2, JUMP }, // gei\n\n\t{ 4,-8, 2, JUMP }, // ltu\n\t{ 4,-8, 2, JUMP }, // leu\n\t{ 4,-8, 2, JUMP }, // gtu\n\t{ 4,-8, 2, JUMP }, // geu\n\n\t{ 4,-8, 2, JUMP }, // eqf\n\t{ 4,-8, 2, JUMP }, // nef\n\n\t{ 4,-8, 2, JUMP }, // ltf\n\t{ 4,-8, 2, JUMP }, // lef\n\t{ 4,-8, 2, JUMP }, // gtf\n\t{ 4,-8, 2, JUMP }, // gef\n\n\t{ 0, 0, 1, 0 }, // load1\n\t{ 0, 0, 1, 0 }, // load2\n\t{ 0, 0, 1, 0 }, // load4\n\t{ 0,-8, 2, 0 }, // store1\n\t{ 0,-8, 2, 0 }, // store2\n\t{ 0,-8, 2, 0 }, // store4\n\t{ 1,-4, 1, 0 }, // arg\n\t{ 4,-8, 2, 0 }, // bcopy\n\n\t{ 0, 0, 1, 0 }, // sex8\n\t{ 0, 0, 1, 0 }, // sex16\n\n\t{ 0, 0, 1, 0 }, // negi\n\t{ 0,-4, 3, 0 }, // add\n\t{ 0,-4, 3, 0 }, // sub\n\t{ 0,-4, 3, 0 }, // divi\n\t{ 0,-4, 3, 0 }, // divu\n\t{ 0,-4, 3, 0 }, // modi\n\t{ 0,-4, 3, 0 }, // modu\n\t{ 0,-4, 3, 0 }, // muli\n\t{ 0,-4, 3, 0 }, // mulu\n\n\t{ 0,-4, 3, 0 }, // band\n\t{ 0,-4, 3, 0 }, // bor\n\t{ 0,-4, 3, 0 }, // bxor\n\t{ 0, 0, 1, 0 }, // bcom\n\n\t{ 0,-4, 3, 0 }, // lsh\n\t{ 0,-4, 3, 0 }, // rshi\n\t{ 0,-4, 3, 0 }, // rshu\n\n\t{ 0, 0, 1, 0 }, // negf\n\t{ 0,-4, 3, 0 }, // addf\n\t{ 0,-4, 3, 0 }, // subf\n\t{ 0,-4, 3, 0 }, // divf\n\t{ 0,-4, 3, 0 }, // mulf\n\n\t{ 0, 0, 1, 0 }, // cvif\n\t{ 0, 0, 1, 0 } // cvfi\n};\n\nconst char *opname[ 256 ] = {\n\t\"OP_UNDEF\", \n\n\t\"OP_IGNORE\", \n\n\t\"OP_BREAK\",\n\n\t\"OP_ENTER\",\n\t\"OP_LEAVE\",\n\t\"OP_CALL\",\n\t\"OP_PUSH\",\n\t\"OP_POP\",\n\n\t\"OP_CONST\",\n\n\t\"OP_LOCAL\",\n\n\t\"OP_JUMP\",\n\n\t//-------------------\n\n\t\"OP_EQ\",\n\t\"OP_NE\",\n\n\t\"OP_LTI\",\n\t\"OP_LEI\",\n\t\"OP_GTI\",\n\t\"OP_GEI\",\n\n\t\"OP_LTU\",\n\t\"OP_LEU\",\n\t\"OP_GTU\",\n\t\"OP_GEU\",\n\n\t\"OP_EQF\",\n\t\"OP_NEF\",\n\n\t\"OP_LTF\",\n\t\"OP_LEF\",\n\t\"OP_GTF\",\n\t\"OP_GEF\",\n\n\t//-------------------\n\n\t\"OP_LOAD1\",\n\t\"OP_LOAD2\",\n\t\"OP_LOAD4\",\n\t\"OP_STORE1\",\n\t\"OP_STORE2\",\n\t\"OP_STORE4\",\n\t\"OP_ARG\",\n\n\t\"OP_BLOCK_COPY\",\n\n\t//-------------------\n\n\t\"OP_SEX8\",\n\t\"OP_SEX16\",\n\n\t\"OP_NEGI\",\n\t\"OP_ADD\",\n\t\"OP_SUB\",\n\t\"OP_DIVI\",\n\t\"OP_DIVU\",\n\t\"OP_MODI\",\n\t\"OP_MODU\",\n\t\"OP_MULI\",\n\t\"OP_MULU\",\n\n\t\"OP_BAND\",\n\t\"OP_BOR\",\n\t\"OP_BXOR\",\n\t\"OP_BCOM\",\n\n\t\"OP_LSH\",\n\t\"OP_RSHI\",\n\t\"OP_RSHU\",\n\n\t\"OP_NEGF\",\n\t\"OP_ADDF\",\n\t\"OP_SUBF\",\n\t\"OP_DIVF\",\n\t\"OP_MULF\",\n\n\t\"OP_CVIF\",\n\t\"OP_CVFI\"\n};\n\ncvar_t\tvm_rtChecks\t\t= { \"vm_rtChecks\", \"1\"};\n\nint\t\tvm_debugLevel;\n\n// used by SV_Error to get rid of running vm's before longjmp\n// static int forced_unload;\n\nstruct vm_s\tvmTable[ VM_COUNT ];\nvoid VM_VmInfo_f( void );\nvoid VM_VmProfile_f( void );\n\n\nvoid VM_Debug( int level ) {\n\tvm_debugLevel = level;\n}\n\n\n/*\n==============\nVM_CheckBounds\n==============\n*/\nvoid VM_CheckBounds( const vm_t *vm, unsigned int address, unsigned int length )\n{\n\t//if ( !vm->entryPoint )\n\t{\n\t\tif ( (address | length) > vm->dataMask || (address + length) > vm->dataMask )\n\t\t{\n\t\t\tSV_Error( \"program tried to bypass data segment bounds\" );\n\t\t}\n\t}\n}\n\n/*\n==============\nVM_CheckBounds2\n==============\n*/\nvoid VM_CheckBounds2( const vm_t *vm, unsigned int addr1, unsigned int addr2, unsigned int length )\n{\n\t//if ( !vm->entryPoint )\n\t{\n\t\tif ( (addr1 | addr2 | length) > vm->dataMask || (addr1 + length) > vm->dataMask || (addr2+length) > vm->dataMask )\n\t\t{\n\t\t\tSV_Error( \"program tried to bypass data segment bounds\" );\n\t\t}\n\t}\n}\n\n/*\n==============\nVM_Init\n==============\n*/\n\nvoid ED2_PrintEdicts (void);\nvoid PR2_Profile_f (void);\nvoid ED2_PrintEdict_f (void);\nvoid ED_Count (void);\nvoid PR_CleanLogText_Init(void); \nvm_t\t*currentVM = NULL; // bk001212\nvm_t\t*lastVM    = NULL; // bk001212\nint     vm_debugLevel;\n\n\nvoid *VM_ArgPtr( intptr_t intValue ) {\n\tif ( !intValue ) {\n\t\treturn NULL;\n\t}\n\t// bk001220 - currentVM is missing on reconnect\n\tif ( currentVM==NULL )\n\t  return NULL;\n\n\tif ( currentVM->entryPoint ) {\n\t\treturn (void *)(currentVM->dataBase + intValue);\n\t}\n\telse {\n\t\treturn (void *)(currentVM->dataBase + (intValue & currentVM->dataMask));\n\t}\n}\n\nvoid *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue ) {\n\tif ( !intValue ) {\n\t\treturn NULL;\n\t}\n\n\t// bk010124 - currentVM is missing on reconnect here as well?\n\tif ( vm==NULL )\n\t  return NULL;\n\n\t//\n\tif ( vm->entryPoint ) {\n\t\treturn (void *)(vm->dataBase + intValue);\n\t}\n\telse {\n\t\treturn (void *)(vm->dataBase + (intValue & vm->dataMask));\n\t}\n}\n\nintptr_t VM_Ptr2VM( void* ptr ) {\n\tif ( !ptr ) {\n\t\treturn 0;\n\t}\n\t// bk001220 - currentVM is missing on reconnect\n\tif ( currentVM==NULL )\n\t  return 0;\n\n\tif ( currentVM->entryPoint ) {\n\t\treturn (intptr_t)ptr;\n\t} else {\n\t\treturn (((byte*)ptr - currentVM->dataBase )) & currentVM->dataMask;\n\t}\n}\n\n\nintptr_t VM_ExplicitPtr2VM( vm_t *vm, void* ptr ) {\n\tif ( !ptr ) {\n\t\treturn 0;\n\t}\n\t// bk001220 - currentVM is missing on reconnect\n\tif ( vm==NULL )\n\t  return 0;\n\n\tif ( vm->entryPoint ) {\n\t\treturn (intptr_t)ptr;\n\t} else {\n\t\treturn (((byte*)ptr - vm->dataBase )) & vm->dataMask;\n\t}\n}\n\n/*\n===============\nVM_ValueToSymbol\n\nAssumes a program counter value\n===============\n*/\nconst char *VM_ValueToSymbol( vm_t *vm, int value ) {\n\tvmSymbol_t\t*sym;\n\tstatic char\t\ttext[MAX_COM_TOKEN];\n\n\tsym = vm->symbols;\n\tif ( !sym ) {\n\t\treturn \"NO SYMBOLS\";\n\t}\n\n\t// find the symbol\n\twhile ( sym->next && sym->next->symValue <= value ) {\n\t\tsym = sym->next;\n\t}\n\n\tif ( value == sym->symValue ) {\n\t\treturn sym->symName;\n\t}\n\n\tsnprintf( text, sizeof( text ), \"%s+%i\", sym->symName, value - sym->symValue );\n\n\treturn text;\n}\n\n/*\n===============\nVM_ValueToFunctionSymbol\n\nFor profiling, find the symbol behind this value\n===============\n*/\nvmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) {\n\tvmSymbol_t\t*sym;\n\tstatic vmSymbol_t\tnullSym;\n\n\tsym = vm->symbols;\n\tif ( !sym ) {\n\t\treturn &nullSym;\n\t}\n\n\twhile ( sym->next && sym->next->symValue <= value ) {\n\t\tsym = sym->next;\n\t}\n\n\treturn sym;\n}\n\n/*\n===============\nVM_SymbolToValue\n===============\n*/\nint VM_SymbolToValue( vm_t *vm, const char *symbol ) {\n\tvmSymbol_t\t*sym;\n\n\tfor ( sym = vm->symbols ; sym ; sym = sym->next ) {\n\t\tif ( !strcmp( symbol, sym->symName ) ) {\n\t\t\treturn sym->symValue;\n\t\t}\n\t}\n\treturn 0;\n}\n\n/*\n===============\nParseHex\n===============\n*/\nint\tParseHex( const char *text ) {\n\tint\t\tvalue;\n\tint\t\tc;\n\n\tvalue = 0;\n\twhile ( ( c = *text++ ) != 0 ) {\n\t\tif ( c >= '0' && c <= '9' ) {\n\t\t\tvalue = value * 16 + c - '0';\n\t\t\tcontinue;\n\t\t}\n\t\tif ( c >= 'a' && c <= 'f' ) {\n\t\t\tvalue = value * 16 + 10 + c - 'a';\n\t\t\tcontinue;\n\t\t}\n\t\tif ( c >= 'A' && c <= 'F' ) {\n\t\t\tvalue = value * 16 + 10 + c - 'A';\n\t\t\tcontinue;\n\t\t}\n\t}\n\n\treturn value;\n}\n\n/*\n===============\nVM_LoadSymbols\n===============\n*/\nvoid VM_LoadSymbols( vm_t *vm ) {\n\tunion {\n\t\tchar\t*c;\n\t\tvoid\t*v;\n\t} mapfile;\n\tconst char *text_p;\n\t//char\tname[MAX_QPATH];\n\tchar\tsymbols[MAX_QPATH];\n\tvmSymbol_t\t**prev, *sym;\n\tint\t\tcount;\n\tint\t\tvalue;\n\tsize_t\tchars;\n\tint\t\tsegment;\n\tint\t\tnumInstructions;\n\n\t// don't load symbols if not developer\n\t//if ( !com_developer->integer ) { return; }\n\n\t//COM_StripExtension((char*)vm->name, name);\n    snprintf( symbols, sizeof( symbols ), \"%s.map\", vm->name );\n\tmapfile.v = FS_LoadTempFile( symbols, NULL );\n\tif ( !mapfile.c ) {\n\t\tCon_Printf( \"Couldn't load symbol file: %s\\n\", symbols );\n\t\treturn;\n\t}\n\n\tnumInstructions = vm->instructionCount;\n\n\t// parse the symbols\n\ttext_p = mapfile.c;\n\tprev = &vm->symbols;\n\tcount = 0;\n\n\twhile ( 1 ) {\n\t\ttext_p = COM_Parse( text_p );\n\t\tif ( !text_p ) {\n\t\t\tbreak;\n\t\t}\n\t\tsegment = ParseHex( com_token );\n\t\tif ( segment ) {\n\t\t\tCOM_Parse( text_p );\n\t\t\tCOM_Parse( text_p );\n\t\t\tcontinue;\t\t// only load code segment values\n\t\t}\n\n\t\ttext_p = COM_Parse( text_p );\n\t\tif ( !text_p ) {\n\t\t\tCon_Printf( \"WARNING: incomplete line at end of file\\n\" );\n\t\t\tbreak;\n\t\t}\n\t\tvalue = ParseHex( com_token );\n\n\t\ttext_p = COM_Parse( text_p );\n\t\tif ( !text_p ) {\n\t\t\tCon_Printf( \"WARNING: incomplete line at end of file\\n\" );\n\t\t\tbreak;\n\t\t}\n\t\tchars = strlen( com_token );\n\t\tsym = Hunk_AllocName( sizeof( *sym ) + chars, \"qvm-symbols\");\n\t\t*prev = sym;\n\t\tprev = &sym->next;\n\t\tsym->next = NULL;\n\n\t\t// convert value from an instruction number to a code offset\n\t\tif ( vm->instructionPointers && value >= 0 && value < numInstructions ) {\n\t\t\tvalue = vm->instructionPointers[value];\n\t\t}\n\n\t\tsym->symValue = value;\n\t\tstrlcpy( sym->symName, com_token, chars + 1 );\n\n\t\tcount++;\n\t}\n\n\tvm->numSymbols = count;\n\tCon_Printf( \"%i symbols parsed from %s\\n\", count, symbols );\n    \n}\n\nstatic void VM_SwapLongs( void *data, int length )\n{\n\tint i, *ptr;\n\tptr = (int *) data;\n\tlength /= sizeof( int );\n\tfor ( i = 0; i < length; i++ ) {\n\t\tptr[ i ] = LittleLong( ptr[ i ] );\n\t}\n}\n\n/*\n============\nVM_DllSyscall\n\nDlls will call this directly\n\n rcg010206 The horror; the horror.\n\n  The syscall mechanism relies on stack manipulation to get its args.\n   This is likely due to C's inability to pass \"...\" parameters to\n   a function in one clean chunk. On PowerPC Linux, these parameters\n   are not necessarily passed on the stack, so while (&arg[0] == arg)\n   is true, (&arg[1] == 2nd function parameter) is not necessarily\n   accurate, as arg's value might have been stored to the stack or\n   other piece of scratch memory to give it a valid address, but the\n   next parameter might still be sitting in a register.\n\n  Quake's syscall system also assumes that the stack grows downward,\n   and that any needed types can be squeezed, safely, into a signed int.\n\n  This hack below copies all needed values for an argument to a\n   array in memory, so that Quake can get the correct values. This can\n   also be used on systems where the stack grows upwards, as the\n   presumably standard and safe stdargs.h macros are used.\n\n  As for having enough space in a signed int for your datatypes, well,\n   it might be better to wait for DOOM 3 before you start porting.  :)\n\n  The original code, while probably still inherently dangerous, seems\n   to work well enough for the platforms it already works on. Rather\n   than add the performance hit for those platforms, the original code\n   is still in use there.\n\n  For speed, we just grab 15 arguments, and don't worry about exactly\n   how many the syscall actually needs; the extra is thrown away.\n \n============\n*/\n#if 1 // - disabled because now is different for each module\nintptr_t QDECL VM_DllSyscall( intptr_t arg, ... ) {\n#if !idx386 || defined __clang__\n  // rcg010206 - see commentary above\n  intptr_t\targs[16];\n  va_list\tap;\n  int i;\n  \n  args[0] = arg;\n  \n  va_start( ap, arg );\n  for (i = 1; i < ARRAY_LEN( args ); i++ )\n    args[ i ] = va_arg( ap, intptr_t );\n  va_end( ap );\n  \n  return currentVM->systemCall( args );\n#else // original id code\n\treturn currentVM->systemCall( &arg );\n#endif\n}\n#endif\n\n/*\n=================\nVM_ValidateHeader\n=================\n*/\nstatic char *VM_ValidateHeader( vmHeader_t *header, int fileSize ) \n{\n\tstatic char errMsg[128];\n\tint n;\n\n\t// truncated\n\tif ( fileSize < ( sizeof( vmHeader_t ) - sizeof( int ) ) ) {\n\t\tsprintf( errMsg, \"truncated image header (%i bytes long)\", fileSize );\n\t\treturn errMsg;\n\t}\n\n\t// bad magic\n\tif ( LittleLong( header->vmMagic ) != VM_MAGIC && LittleLong( header->vmMagic ) != VM_MAGIC_VER2 ) {\n\t\tsprintf( errMsg, \"bad file magic %08x\", LittleLong( header->vmMagic ) );\n\t\treturn errMsg;\n\t}\n\t\n\t// truncated\n\tif ( fileSize < sizeof( vmHeader_t ) && LittleLong( header->vmMagic ) != VM_MAGIC_VER2 ) {\n\t\tsprintf( errMsg, \"truncated image header (%i bytes long)\", fileSize );\n\t\treturn errMsg;\n\t}\n\n\tif ( LittleLong( header->vmMagic ) == VM_MAGIC_VER2 )\n\t\tn = sizeof( vmHeader_t );\n\telse\n\t\tn = ( sizeof( vmHeader_t ) - sizeof( int ) );\n\n\t// byte swap the header\n\tVM_SwapLongs( header, n );\n\n\t// bad code offset\n\tif ( header->codeOffset >= fileSize ) {\n\t\tsprintf( errMsg, \"bad code segment offset %i\", header->codeOffset );\n\t\treturn errMsg;\n\t}\n\n\t// bad code length\n\tif ( header->codeLength <= 0 || header->codeOffset + header->codeLength > fileSize ) {\n\t\tsprintf( errMsg, \"bad code segment length %i\", header->codeLength );\n\t\treturn errMsg;\n\t}\n\n\t// bad data offset\n\tif ( header->dataOffset >= fileSize || header->dataOffset != header->codeOffset + header->codeLength ) {\n\t\tsprintf( errMsg, \"bad data segment offset %i\", header->dataOffset );\n\t\treturn errMsg;\n\t}\n\n\t// bad data length\n\tif ( header->dataOffset + header->dataLength > fileSize )  {\n\t\tsprintf( errMsg, \"bad data segment length %i\", header->dataLength );\n\t\treturn errMsg;\n\t}\n\n\tif ( header->vmMagic == VM_MAGIC_VER2 ) \n\t{\n\t\t// bad lit/jtrg length\n\t\tif ( header->dataOffset + header->dataLength + header->litLength + header->jtrgLength != fileSize ) {\n\t\t\tsprintf( errMsg, \"bad lit/jtrg segment length\" );\n\t\t\treturn errMsg;\n\t\t}\n\t} \n\t// bad lit length\n\telse if ( header->dataOffset + header->dataLength + header->litLength != fileSize ) \n\t{\n\t\tsprintf( errMsg, \"bad lit segment length %i\", header->litLength );\n\t\treturn errMsg;\n\t}\n\n\treturn NULL;\t\n}\n\n/*\n=================\nVM_LoadQVM\n\nLoad a .qvm file\n\nif ( alloc )\n - Validate header, swap data\n - Alloc memory for data/instructions\n - Alloc memory for instructionPointers - NOT NEEDED\n - Load instructions\n - Clear/load data\nelse\n - Check for header changes\n - Clear/load data\n\n=================\n*/\nstatic vmHeader_t *VM_LoadQVM( vm_t *vm, qbool alloc ) {\n\tint\t\t\t\t\tlength;\n\tunsigned int\t\tdataLength;\n\tunsigned int\t\tdataAlloc;\n\tint\t\t\t\t\ti;\n\tchar\t\t\t\tfilename[MAX_QPATH], *errorMsg;\n\tunsigned int\t\tcrc32sum;\n\t//qbool\t\t\ttryjts;\n\tvmHeader_t\t\t\t*header;\n\tchar    num[32];\n\n\t// load the image\n\tsnprintf( filename, sizeof( filename ), \"%s.qvm\", vm->name );\n\tCon_Printf( \"Loading vm file %s...\\n\", filename );\n\theader = ( vmHeader_t*)FS_LoadTempFile( filename, &length );\n\tif ( !header ) {\n\t\tCon_Printf( \"Failed.\\n\" );\n\t\tVM_Free( vm );\n\t\treturn NULL;\n\t}\n\n\tcrc32sum = CRC_Block( ( byte * ) header, length );\n\tsprintf( num, \"%i\",  crc32sum );\n\tInfo_SetValueForStarKey( svs.info, \"*progs\", num, MAX_SERVERINFO_STRING );\n\n\t// will also swap header\n\terrorMsg = VM_ValidateHeader( header, length );\n\tif ( errorMsg ) {\n\t\tVM_Free( vm );\n\t\tCon_Printf( \"%s\\n\", errorMsg );\n\t\treturn NULL;\n\t}\n\n\tvm->crc32sum = crc32sum;\n\t//tryjts = false;\n\n\tif( header->vmMagic == VM_MAGIC_VER2 ) {\n\t\tCon_Printf( \"...which has vmMagic VM_MAGIC_VER2\\n\" );\n\t} else {\n\t//\ttryjts = true;\n\t}\n\n\tvm->exactDataLength = header->dataLength + header->litLength + header->bssLength;\n\tdataLength = vm->exactDataLength + PROGRAM_STACK_EXTRA;\n\tvm->dataLength = dataLength;\n\n\t// round up to next power of 2 so all data operations can\n\t// be mask protected\n\tfor ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) \n\t\t;\n\tdataLength = 1 << i;\n\n\t// reserve some space for effective LOCAL+LOAD* checks\n\tdataAlloc = dataLength + 1024;\n\n\tif ( dataLength >= (1U<<31) || dataAlloc >= (1U<<31) ) {\n\t\tVM_Free( vm );\n\t\tCon_Printf( \"%s: data segment is too large\\n\", __func__ );\n\t\treturn NULL;\n\t}\n\n\tif ( alloc ) {\n\t\t// allocate zero filled space for initialized and uninitialized data\n\t\tvm->dataBase = Hunk_AllocName( dataAlloc, \"qvm\");\n\t\tvm->dataMask = dataLength - 1;\n\t\tvm->dataAlloc = dataAlloc;\n\t} else {\n\t\t// clear the data, but make sure we're not clearing more than allocated\n\t\tif ( vm->dataAlloc != dataAlloc ) {\n\t\t\tVM_Free( vm );\n\t\t\tCon_Printf( \"Warning: Data region size of %s not matching after\"\n\t\t\t\t\t\"VM_Restart()\\n\", filename );\n\t\t\treturn NULL;\n\t\t}\n\t\tmemset( vm->dataBase, 0, vm->dataAlloc );\n\t}\n\n\t// copy the intialized data\n\tmemcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength );\n\n\t// byte swap the longs\n\tVM_SwapLongs( vm->dataBase, header->dataLength );\n\n\tif( header->vmMagic == VM_MAGIC_VER2 ) {\n\t\tint previousNumJumpTableTargets = vm->numJumpTableTargets;\n\n\t\theader->jtrgLength &= ~0x03;\n\n\t\tvm->numJumpTableTargets = header->jtrgLength >> 2;\n\t\tCon_Printf( \"Loading %d jump table targets\\n\", vm->numJumpTableTargets );\n\n\t\tif ( alloc ) {\n\t\t\tvm->jumpTableTargets = Hunk_AllocName( header->jtrgLength, \"qvm-jtt\");\n\t\t} else {\n\t\t\tif ( vm->numJumpTableTargets != previousNumJumpTableTargets ) {\n\t\t\t\tVM_Free( vm );\n\n\t\t\t\tCon_Printf( \"Warning: Jump table size of %s not matching after \"\n\t\t\t\t\t\t\"VM_Restart()\\n\", filename );\n\t\t\t\treturn NULL;\n\t\t\t}\n\n\t\t\tmemset( vm->jumpTableTargets, 0, header->jtrgLength );\n\t\t}\n\n\t\tmemcpy( vm->jumpTableTargets, (byte *)header + header->dataOffset +\n\t\t\t\theader->dataLength + header->litLength, header->jtrgLength );\n\n\t\t// byte swap the longs\n\t\tVM_SwapLongs( vm->jumpTableTargets, header->jtrgLength );\n\t}\n\n\t/*if ( tryjts == true && (length = Load_JTS( vm, crc32sum, NULL, vmPakIndex )) >= 0 ) {\n\t\t// we are trying to load newer file?\n\t\tif ( vm->jumpTableTargets && vm->numJumpTableTargets != length >> 2 ) {\n\t\t\tCon_Printf( \"Reload jts file\\n\" );\n\t\t\tvm->jumpTableTargets = NULL;\n\t\t\talloc = true;\n\t\t}\n\t\tvm->numJumpTableTargets = length >> 2;\n\t\tCon_Printf( \"Loading %d external jump table targets\\n\", vm->numJumpTableTargets );\n\t\tif ( alloc == true ) {\n\t\t\tvm->jumpTableTargets = Hunk_Alloc( length);\n\t\t} else {\n\t\t\tmemset( vm->jumpTableTargets, 0, length );\n\t\t}\n\t\tLoad_JTS( vm, crc32sum, vm->jumpTableTargets, vmPakIndex );\n\t}*/\n\n\treturn header;\n}\n\n/*\n=================\nVM_LoadInstructions\n\nloads instructions in structured format\n=================\n*/\nconst char *VM_LoadInstructions( const byte *code_pos, int codeLength, int instructionCount, instruction_t *buf )\n{\n\tstatic char errBuf[ 128 ];\n\tconst byte *code_start, *code_end;\n\tint i, n, op0, op1, opStack;\n\tinstruction_t *ci;\n\t\n\tcode_start = code_pos; // for printing\n\tcode_end = code_pos + codeLength;\n\n\tci = buf;\n\topStack = 0;\n\top1 = OP_UNDEF;\n\n\t// load instructions and perform some initial calculations/checks\n\tfor ( i = 0; i < instructionCount; i++, ci++, op1 = op0 ) {\n\t\top0 = *code_pos;\n\t\tif ( op0 < 0 || op0 >= OP_MAX ) {\n\t\t\tsprintf( errBuf, \"bad opcode %02X at offset %d\", op0, (int)(code_pos - code_start) );\n\t\t\treturn errBuf;\n\t\t}\n\t\tn = ops[ op0 ].size;\n\t\tif ( code_pos + 1 + n  > code_end ) {\n\t\t\tsprintf( errBuf, \"code_pos > code_end\" );\n\t\t\treturn errBuf;\n\t\t}\n\t\tcode_pos++;\n\t\tci->op = op0;\n\t\tif ( n == 4 ) {\n\t\t\tci->value = LittleLong( *((int*)code_pos) );\n\t\t\tcode_pos += 4;\n\t\t} else if ( n == 1 ) { \n\t\t\tci->value = *((unsigned char*)code_pos);\n\t\t\tcode_pos += 1;\n\t\t} else {\n\t\t\tci->value = 0;\n\t\t}\n\n\t\t// setup jump value from previous const\n\t\tif ( op0 == OP_JUMP && op1 == OP_CONST ) {\n\t\t\tci->value = (ci-1)->value;\n\t\t}\n\n\t\tci->opStack = opStack;\n\t\topStack += ops[ op0 ].stack;\n\t}\n\n\treturn NULL;\n}\n\n/*\n===============================\nVM_CheckInstructions\n\nperforms additional consistency and security checks\n===============================\n*/\nconst char *VM_CheckInstructions( instruction_t *buf,\n\t\t\t\t\t\t\t\tint instructionCount,\n\t\t\t\t\t\t\t\tconst byte *jumpTableTargets,\n\t\t\t\t\t\t\t\tint numJumpTableTargets,\n\t\t\t\t\t\t\t\tint dataLength )\n{\n\tstatic char errBuf[ 128 ];\n\tint i, n, v, op0, op1, opStack, pstack;\n\tinstruction_t *ci, *proc;\n\tint startp, endp;\n\n\tci = buf;\n\topStack = 0;\n\n\t// opstack checks\n\tfor ( i = 0; i < instructionCount; i++, ci++ ) {\n\t\topStack += ops[ ci->op ].stack;\n\t\tif ( opStack < 0 ) {\n\t\t\tsprintf( errBuf, \"opStack underflow at %i\", i ); \n\t\t\treturn errBuf;\n\t\t}\n\t\tif ( opStack >= PROC_OPSTACK_SIZE * 4 ) {\n\t\t\tsprintf( errBuf, \"opStack overflow at %i\", i ); \n\t\t\treturn errBuf;\n\t\t}\n\t}\n\n\tci = buf;\n\tpstack = 0;\n\top1 = OP_UNDEF;\n\tproc = NULL;\n\n\tstartp = 0;\n\tendp = instructionCount - 1;\n\n\t// Additional security checks\n\n\tfor ( i = 0; i < instructionCount; i++, ci++, op1 = op0 ) {\n\t\top0 = ci->op;\n\n\t\t// function entry\n\t\tif ( op0 == OP_ENTER ) {\n\t\t\t// missing block end \n\t\t\tif ( proc || ( pstack && op1 != OP_LEAVE ) ) {\n\t\t\t\tsprintf( errBuf, \"missing proc end before %i\", i ); \n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\tif ( ci->opStack != 0 ) {\n\t\t\t\tv = ci->opStack;\n\t\t\t\tsprintf( errBuf, \"bad entry opstack %i at %i\", v, i ); \n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\tv = ci->value;\n\t\t\tif ( v < 0 || v >= PROGRAM_STACK_SIZE || (v & 3) ) {\n\t\t\t\tsprintf( errBuf, \"bad entry programStack %i at %i\", v, i ); \n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\t\n\t\t\tpstack = ci->value;\n\t\t\t\n\t\t\t// mark jump target\n\t\t\tci->jused = 1;\n\t\t\tproc = ci;\n\t\t\tstartp = i + 1;\n\n\t\t\t// locate endproc\n\t\t\tfor ( endp = 0, n = i+1 ; n < instructionCount; n++ ) {\n\t\t\t\tif ( buf[n].op == OP_PUSH && buf[n+1].op == OP_LEAVE ) {\n\t\t\t\t\tendp = n;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( endp == 0 ) {\n\t\t\t\tsprintf( errBuf, \"missing end proc for %i\", i ); \n\t\t\t\treturn errBuf;\n\t\t\t}\n\n\t\t\tcontinue;\n\t\t}\n\n\t\t// proc opstack will carry max.possible opstack value\n\t\tif ( proc && ci->opStack > proc->opStack ) \n\t\t\tproc->opStack = ci->opStack;\n\n\t\t// function return\n\t\tif ( op0 == OP_LEAVE ) {\n\t\t\t// bad return programStack\n\t\t\tif ( pstack != ci->value ) {\n\t\t\t\tv = ci->value;\n\t\t\t\tsprintf( errBuf, \"bad programStack %i at %i\", v, i ); \n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\t// bad opStack before return\n\t\t\tif ( ci->opStack != 4 ) {\n\t\t\t\tv = ci->opStack;\n\t\t\t\tsprintf( errBuf, \"bad opStack %i at %i\", v, i );\n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\tv = ci->value;\n\t\t\tif ( v < 0 || v >= PROGRAM_STACK_SIZE || (v & 3) ) {\n\t\t\t\tsprintf( errBuf, \"bad return programStack %i at %i\", v, i ); \n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\tif ( op1 == OP_PUSH ) {\n\t\t\t\tif ( proc == NULL ) {\n\t\t\t\t\tsprintf( errBuf, \"unexpected proc end at %i\", i ); \n\t\t\t\t\treturn errBuf;\n\t\t\t\t}\n\t\t\t\tproc = NULL;\n\t\t\t\tstartp = i + 1; // next instruction\n\t\t\t\tendp = instructionCount - 1; // end of the image\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// conditional jumps\n\t\tif ( ops[ ci->op ].flags & JUMP ) {\n\t\t\tv = ci->value;\n\t\t\t// conditional jumps should have opStack == 8\n\t\t\tif ( ci->opStack != 8 ) {\n\t\t\t\tsprintf( errBuf, \"bad jump opStack %i at %i\", ci->opStack, i ); \n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\t//if ( v >= header->instructionCount ) {\n\t\t\t// allow only local proc jumps\n\t\t\tif ( v < startp || v > endp ) {\n\t\t\t\tsprintf( errBuf, \"jump target %i at %i is out of range (%i,%i)\", v, i-1, startp, endp );\n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\tif ( buf[v].opStack != 0 ) {\n\t\t\t\tn = buf[v].opStack;\n\t\t\t\tsprintf( errBuf, \"jump target %i has bad opStack %i\", v, n ); \n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\t// mark jump target\n\t\t\tbuf[v].jused = 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// unconditional jumps\n\t\tif ( op0 == OP_JUMP ) {\n\t\t\t// jumps should have opStack == 4\n\t\t\tif ( ci->opStack != 4 ) {\n\t\t\t\tsprintf( errBuf, \"bad jump opStack %i at %i\", ci->opStack, i ); \n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\tif ( op1 == OP_CONST ) {\n\t\t\t\tv = buf[i-1].value;\n\t\t\t\t// allow only local jumps\n\t\t\t\tif ( v < startp || v > endp ) {\n\t\t\t\t\tsprintf( errBuf, \"jump target %i at %i is out of range (%i,%i)\", v, i-1, startp, endp );\n\t\t\t\t\treturn errBuf;\n\t\t\t\t}\n\t\t\t\tif ( buf[v].opStack != 0 ) {\n\t\t\t\t\tn = buf[v].opStack;\n\t\t\t\t\tsprintf( errBuf, \"jump target %i has bad opStack %i\", v, n ); \n\t\t\t\t\treturn errBuf;\n\t\t\t\t}\n\t\t\t\tif ( buf[v].op == OP_ENTER ) {\n\t\t\t\t\tn = buf[v].op;\n\t\t\t\t\tsprintf( errBuf, \"jump target %i has bad opcode %i\", v, n ); \n\t\t\t\t\treturn errBuf;\n\t\t\t\t}\n\t\t\t\tif ( v == (i-1) ) {\n\t\t\t\t\tsprintf( errBuf, \"self loop at %i\", v ); \n\t\t\t\t\treturn errBuf;\n\t\t\t\t}\n\t\t\t\t// mark jump target\n\t\t\t\tbuf[v].jused = 1;\n\t\t\t} else {\n\t\t\t\tif ( proc )\n\t\t\t\t\tproc->swtch = 1;\n\t\t\t\telse\n\t\t\t\t\tci->swtch = 1;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ( op0 == OP_CALL ) {\n\t\t\tif ( ci->opStack < 4 ) {\n\t\t\t\tsprintf( errBuf, \"bad call opStack at %i\", i ); \n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\tif ( op1 == OP_CONST ) {\n\t\t\t\tv = buf[i-1].value;\n\t\t\t\t// analyse only local function calls\n\t\t\t\tif ( v >= 0 ) {\n\t\t\t\t\tif ( v >= instructionCount ) {\n\t\t\t\t\t\tsprintf( errBuf, \"call target %i is out of range\", v ); \n\t\t\t\t\t\treturn errBuf;\n\t\t\t\t\t}\n\t\t\t\t\tif ( buf[v].op != OP_ENTER ) {\n\t\t\t\t\t\tn = buf[v].op;\n\t\t\t\t\t\tsprintf( errBuf, \"call target %i has bad opcode %i\", v, n );\n\t\t\t\t\t\treturn errBuf;\n\t\t\t\t\t}\n\t\t\t\t\tif ( v == 0 ) {\n\t\t\t\t\t\tsprintf( errBuf, \"explicit vmMain call inside VM\" );\n\t\t\t\t\t\treturn errBuf;\n\t\t\t\t\t}\n\t\t\t\t\t// mark jump target\n\t\t\t\t\tbuf[v].jused = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ( ci->op == OP_ARG ) {\n\t\t\tv = ci->value & 255;\n\t\t\t// argument can't exceed programStack frame\n\t\t\tif ( v < 8 || v > pstack - 4 || (v & 3) ) {\n\t\t\t\tsprintf( errBuf, \"bad argument address %i at %i\", v, i );\n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ( ci->op == OP_LOCAL ) {\n\t\t\tv = ci->value;\n\t\t\tif ( proc == NULL ) {\n\t\t\t\tsprintf( errBuf, \"missing proc frame for local %i at %i\", v, i );\n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t\tif ( (ci+1)->op == OP_LOAD1 || (ci+1)->op == OP_LOAD2 || (ci+1)->op == OP_LOAD4 || (ci+1)->op == OP_ARG ) {\n\t\t\t\t// FIXME: alloc 256 bytes of programStack in VM_CallCompiled()?\n\t\t\t\tif ( v < 8 || v >= proc->value + 256 ) {\n\t\t\t\t\tsprintf( errBuf, \"bad local address %i at %i\", v, i );\n\t\t\t\t\treturn errBuf;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( ci->op == OP_LOAD4 && op1 == OP_CONST ) {\n\t\t\tv = (ci-1)->value;\n\t\t\tif ( v < 0 || v > dataLength - 4 ) {\n\t\t\t\tsprintf( errBuf, \"bad load4 address %i at %i\", v, i - 1 );\n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t}\n\n\t\tif ( ci->op == OP_LOAD2 && op1 == OP_CONST ) {\n\t\t\tv = (ci-1)->value;\n\t\t\tif ( v < 0 || v > dataLength - 2 ) {\n\t\t\t\tsprintf( errBuf, \"bad load2 address %i at %i\", v, i - 1 );\n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t}\n\n\t\tif ( ci->op == OP_LOAD1 && op1 == OP_CONST ) {\n\t\t\tv =  (ci-1)->value;\n\t\t\tif ( v < 0 || v > dataLength - 1 ) {\n\t\t\t\tsprintf( errBuf, \"bad load1 address %i at %i\", v, i - 1 );\n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t}\n\n\t\tif ( ci->op == OP_BLOCK_COPY ) {\n\t\t\tv = ci->value;\n\t\t\tif ( v >= dataLength ) {\n\t\t\t\tsprintf( errBuf, \"bad count %i for block copy at %i\", v, i - 1 );\n\t\t\t\treturn errBuf;\n\t\t\t}\n\t\t}\n\n//\t\top1 = op0;\n//\t\tci++;\n\t}\n\n\tif ( op1 != OP_UNDEF && op1 != OP_LEAVE ) {\n\t\tsprintf( errBuf, \"missing return instruction at the end of the image\" );\n\t\treturn errBuf;\n\t}\n\n\t// ensure that the optimization pass knows about all the jump table targets\n\tif ( jumpTableTargets ) {\n\t\t// first pass - validate\n\t\tfor( i = 0; i < numJumpTableTargets; i++ ) {\n\t\t\tn = *(int *)(jumpTableTargets + ( i * sizeof( int ) ) );\n\t\t\tif ( n < 0 || n >= instructionCount ) {\n\t\t\t\tCon_Printf( \"jump target %i set on instruction %i that is out of range [0..%i]\",\n\t\t\t\t\ti, n, instructionCount - 1 ); \n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( buf[n].opStack != 0 ) {\n\t\t\t\tCon_Printf( \"jump target %i set on instruction %i (%s) with bad opStack %i\\n\",\n\t\t\t\t\ti, n, opname[ buf[n].op ], buf[n].opStack ); \n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif ( i != numJumpTableTargets ) {\n\t\t\t// we may trap this on buggy VM_MAGIC_VER2 images\n\t\t\t// but we can safely optimize code even without JTRGSEG\n\t\t\t// so just switch to VM_MAGIC path here\n\t\t\tgoto __noJTS;\n\t\t}\n\t\t// second pass - apply\n\t\tfor( i = 0; i < numJumpTableTargets; i++ ) {\n\t\t\tn = *(int *)(jumpTableTargets + ( i * sizeof( int ) ) );\n\t\t\tbuf[n].jused = 1;\n\t\t}\n\t} else {\n__noJTS:\n\t\tv = 0;\n\t\t// instructions with opStack > 0 can't be jump labels so its safe to optimize/merge\n\t\tfor ( i = 0, ci = buf; i < instructionCount; i++, ci++ ) {\n\t\t\tif ( ci->op == OP_ENTER ) {\n\t\t\t\tv = ci->swtch;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// if there is a switch statement in function -\n\t\t\t// mark all potential jump labels\n\t\t\tif ( ci->swtch )\n\t\t\t\tv = ci->swtch;\n\t\t\tif ( ci->opStack > 0 )\n\t\t\t\tci->jused = 0;\n\t\t\telse if ( v )\n\t\t\t\tci->jused = 1;\n\t\t}\n\t}\n\n\treturn NULL;\n}\nqbool VM_LoadNative( vm_t * vm)\n{\n\tchar    name[MAX_OSPATH];\n\tchar   *gpath = NULL;\n\tvoid    ( *dllEntry ) ( void * );\n\n\twhile ( ( gpath = FS_NextPath( gpath ) ) )\n\t{\n\t\tsnprintf( name, sizeof( name ), \"%s/%s.\" DLEXT, gpath, vm->name );\n\t\tvm->dllHandle = Sys_DLOpen( name );\n\t\tif ( vm->dllHandle )\n\t\t{\n\t\t\tCon_Printf( \"LoadLibrary (%s)\\n\", name );\n\t\t\tbreak;\n\t\t}\n\t}\n\tif ( !vm->dllHandle )\n\t\treturn false;\n\n\tdllEntry = Sys_DLProc( vm->dllHandle, \"dllEntry\" );\n\tvm->entryPoint = Sys_DLProc( vm->dllHandle, \"vmMain\" );\n\tif ( !dllEntry || !vm->entryPoint )\n\t{\n        Sys_DLClose( vm->dllHandle );\n\t\tSV_Error( \"VM_LoadNative: couldn't initialize module %s\", name );\n\t}\n\tdllEntry( vm->dllSyscall );\n\n\tInfo_SetValueForStarKey( svs.info, \"*qvm\", DLEXT, MAX_SERVERINFO_STRING );\n\tInfo_SetValueForStarKey( svs.info, \"*progs\", DLEXT, MAX_SERVERINFO_STRING );\n\tvm->type = VMI_NATIVE;\n\treturn true;\n}\n\n/*\n================\nVM_Create\n\nIf image ends in .qvm it will be interpreted, otherwise\nit will attempt to load as a system dll\n================\n*/\nvm_t *VM_Create( vmIndex_t index, const char\t*name, syscall_t systemCalls, /*dllSyscall_t dllSyscalls,*/ vmInterpret_t interpret ) {\n\t//int\t\t\tremaining;\n\tvmHeader_t\t*header;\n\tvm_t\t\t*vm;\n\n\tif ( !systemCalls ) {\n\t\tSV_Error( \"VM_Create: bad parms\" );\n\t}\n\n\tif ( (unsigned)index >= VM_COUNT ) {\n\t\tSV_Error( \"VM_Create: bad vm index %i\", index );\t\n\t}\n\n\t//remaining = Hunk_MemoryRemaining();\n\n\tvm = &vmTable[ index ];\n\n\t// see if we already have the VM\n\tif ( vm->name ) {\n\t\tif ( vm->index != index ) {\n\t\t\tSV_Error( \"VM_Create: bad allocated vm index %i\", vm->index );\n\t\t\treturn NULL;\n\t\t}\n\t\treturn vm;\n\t}\n\n\tvm->name = name;\n\tvm->index = index;\n\tvm->systemCall = systemCalls;\n\tvm->dllSyscall = VM_DllSyscall;//dllSyscalls;\n\t//vm->privateFlag = CVAR_PRIVATE;\n\n\t// never allow dll loading with a demo\n\t/*if ( interpret == VMI_NATIVE ) {\n\t\tif ( Cvar_VariableIntegerValue( \"fs_restrict\" ) ) {\n\t\t\tinterpret = VMI_COMPILED;\n\t\t}\n\t}*/\n\n\tif ( interpret == VMI_NATIVE ) {\n\t\t// try to load as a system dll\n\t\t//Con_Printf( \"Loading dll file %s.\\n\", name );\n\t\tif ( VM_LoadNative( vm ) ) {\n\t\t\t//vm->privateFlag = 0; // allow reading private cvars\n\t\t\tvm->dataAlloc = ~0U;\n\t\t\tvm->dataMask = ~0U;\n\t\t\tvm->dataBase = 0;\n\t\t\treturn vm;\n\t\t}\n\n\t\tCon_Printf( \"Failed to load dll, looking for qvm.\\n\" );\n\t\tinterpret = VMI_COMPILED;\n\t}\n\n\t// load the image\n\tif( ( header = VM_LoadQVM( vm, true ) ) == NULL ) {\n\t\treturn NULL;\n\t}\n\n\t// allocate space for the jump targets, which will be filled in by the compile/prep functions\n\tvm->instructionCount = header->instructionCount;\n\t//vm->instructionPointers = Hunk_Alloc(vm->instructionCount * sizeof(*vm->instructionPointers), h_high);\n\tvm->instructionPointers = NULL;\n\n\t// copy or compile the instructions\n\tvm->codeLength = header->codeLength;\n\n\t// the stack is implicitly at the end of the image\n\tvm->programStack = vm->dataMask + 1;\n\tvm->stackBottom = vm->programStack - PROGRAM_STACK_SIZE - PROGRAM_STACK_EXTRA;\n\n\tvm->compiled = false;\n\n\n#ifdef NO_VM_COMPILED\n\tif(interpret >= VMI_COMPILED) {\n\t\tCon_Printf(\"Architecture doesn't have a bytecode compiler, using interpreter\\n\");\n\t\tinterpret = VMI_BYTECODE;\n\t}\n#else\n\tif ( interpret >= VMI_COMPILED ) {\n\t\tif ( VM_Compile( vm, header ) ) {\n\t\t\tvm->compiled = true;\n\t\t}\n\t}\n#endif\n\t// VM_Compile may have reset vm->compiled if compilation failed\n\tif ( !vm->compiled ) {\n\t\tif ( !VM_PrepareInterpreter2( vm, header ) ) {\n\t\t\t//FS_FreeFile( header );\t// free the original file\n\t\t\tVM_Free( vm );\n\t\t\treturn NULL;\n\t\t}\n\t}\n\tvm->type = interpret;\n\n\t// free the original file\n\t//FS_FreeFile( header );\n\n\t// load the map file\n\tVM_LoadSymbols( vm );\n\n\t//Con_Printf( \"%s loaded in %d bytes on the hunk\\n\", vm->name, remaining - Hunk_MemoryRemaining() );\n\n\treturn vm;\n}\n\n/*\n==============\nVM_Free\n==============\n*/\nvoid VM_Free( vm_t *vm ) {\n\n\tif( !vm ) {\n\t\treturn;\n\t}\n\n/*\tif ( vm->callLevel ) {\n\t\tif ( !forced_unload ) {\n\t\t\tSV_Error( ERR_FATAL, \"VM_Free(%s) on running vm\", vm->name );\n\t\t\treturn;\n\t\t} else {\n\t\t\tCon_Printf( \"forcefully unloading %s vm\\n\", vm->name );\n\t\t}\n\t}*/\n\n\tif ( vm->destroy )\n\t\tvm->destroy( vm );\n\n\tif ( vm->dllHandle )\n\t\t\tSys_DLClose( vm->dllHandle );\n\n#if 0\t// now automatically freed by hunk\n\tif ( vm->codeBase.ptr ) {\n\t\tZ_Free( vm->codeBase.ptr );\n\t}\n\tif ( vm->dataBase ) {\n\t\tZ_Free( vm->dataBase );\n\t}\n\tif ( vm->instructionPointers ) {\n\t\tZ_Free( vm->instructionPointers );\n\t}\n#endif\n\tcurrentVM = NULL;\n\tlastVM = NULL;\n\tmemset( vm, 0, sizeof( *vm ) );\n}\n\n\nvoid VM_Clear( void ) {\n\tint i;\n\tfor ( i = 0; i < VM_COUNT; i++ ) {\n\t\tVM_Free( &vmTable[ i ] );\n\t}\n\tcurrentVM = NULL;\n\tlastVM = NULL;\n}\n\n/*\n==============\nVM_Call\n\n\nUpon a system call, the stack will look like:\n\nsp+32\tparm1\nsp+28\tparm0\nsp+24\treturn value\nsp+20\treturn address\nsp+16\tlocal1\nsp+14\tlocal0\nsp+12\targ1\nsp+8\targ0\nsp+4\treturn stack\nsp\t\treturn address\n\nAn interpreted function will immediately execute\nan OP_ENTER instruction, which will subtract space for\nlocals from sp\n==============\n*/\n\nintptr_t QDECL VM_Call( vm_t *vm, int nargs, int callnum, ... )\n{\n\tvm_t\t*oldVM;\n\tintptr_t r;\n\tint i;\n\n\tif ( !vm ) {\n\t\tSV_Error( \"VM_Call with NULL vm\" );\n\t}\n\n\n\toldVM = currentVM;\n\tcurrentVM = vm;\n\tlastVM = vm;\n\n\tif ( vm_debugLevel ) {\n\t  Con_Printf( \"VM_Call( %d )\\n\", callnum );\n\t}\n\n#ifdef DEBUG\n\tif ( nargs >= MAX_VMMAIN_CALL_ARGS ) {\n\t\tSV_Error( \"VM_Call: nargs >= MAX_VMMAIN_CALL_ARGS\" );\n\t}\n#endif\n\n\t++vm->callLevel;\n\t// if we have a dll loaded, call it directly\n\tif ( vm->entryPoint ) \n\t{\n\t\t//rcg010207 -  see dissertation at top of VM_DllSyscall() in this file.\n\t\tint args[MAX_VMMAIN_CALL_ARGS-1] = {0};\n\t\tva_list ap;\n\t\tva_start( ap, callnum );\n\t\tfor ( i = 0; i < nargs; i++ ) {\n\t\t\targs[i] = va_arg( ap, int );\n\t\t}\n\t\tva_end(ap);\n\n\t\t// add more agruments if you're changed MAX_VMMAIN_CALL_ARGS:\n\t\tr = vm->entryPoint( callnum, args[0], args[1], args[2] );\n\t} else {\n#if idx386 && !defined __clang__ // calling convention doesn't need conversion in some cases\n#ifndef NO_VM_COMPILED\n\t\tif ( vm->compiled )\n\t\t\tr = VM_CallCompiled( vm, nargs+1, (int*)&callnum );\n\t\telse\n#endif\n\t\t\tr = VM_CallInterpreted2( vm, nargs+1, (int*)&callnum );\n#else\n\t\tint args[MAX_VMMAIN_CALL_ARGS] = {0};\n\t\tva_list ap;\n\n\t\targs[0] = callnum;\n\t\tva_start( ap, callnum );\n\t\tfor ( i = 0; i < nargs; i++ ) {\n\t\t\targs[i+1] = va_arg( ap, int );\n\t\t}\n\t\tva_end(ap);\n#ifndef NO_VM_COMPILED\n\t\tif ( vm->compiled )\n\t\t\tr = VM_CallCompiled( vm, nargs+1, &args[0] );\n\t\telse\n#endif\n\t\t\tr = VM_CallInterpreted2( vm, nargs+1, &args[0] );\n#endif\n\t}\n\t--vm->callLevel;\n\tif ( oldVM != NULL ) // bk001220 - assert(currentVM!=NULL) for oldVM==NULL\n\t  currentVM = oldVM;\n\n\treturn r;\n}\n\n\n//=================================================================\n\nstatic int QDECL VM_ProfileSort( const void *a, const void *b ) {\n\tvmSymbol_t\t*sa, *sb;\n\n\tsa = *(vmSymbol_t **)a;\n\tsb = *(vmSymbol_t **)b;\n\n\tif ( sa->profileCount < sb->profileCount ) {\n\t\treturn -1;\n\t}\n\tif ( sa->profileCount > sb->profileCount ) {\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\n/*\n==============\nVM_VmProfile_f\n\n==============\n*/\nvoid VM_VmProfile_f( void ) {\n\tvm_t\t\t*vm;\n\tvmSymbol_t\t**sorted, *sym;\n\tint\t\t\ti;\n\tdouble\t\ttotal;\n\n    vm = &vmTable[VM_GAME];\n\n\tif ( !vm->name ) {\n\t\tCon_Printf( \" VM is not running.\\n\" );\n\t\treturn;\n\t}\n\tif ( vm == NULL ) {\n\t\treturn;\n\t}\n\n\tif ( !vm->numSymbols ) {\n\t\treturn;\n\t}\n\n\tsorted = Q_malloc( vm->numSymbols * sizeof( *sorted ) );\n\tsorted[0] = vm->symbols;\n\ttotal = sorted[0]->profileCount;\n\tfor ( i = 1 ; i < vm->numSymbols ; i++ ) {\n\t\tsorted[i] = sorted[i-1]->next;\n\t\ttotal += sorted[i]->profileCount;\n\t}\n\n\tqsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort );\n\n\tfor ( i = 0 ; i < vm->numSymbols ; i++ ) {\n\t\tint\t\tperc;\n\n\t\tsym = sorted[i];\n\n\t\tperc = 100 * (float) sym->profileCount / total;\n\t\tCon_Printf( \"%2i%% %9i %s\\n\", perc, sym->profileCount, sym->symName );\n\t\tsym->profileCount = 0;\n\t}\n\n\tCon_Printf(\"    %9.0f total\\n\", total );\n\n\tQ_free( sorted );\n}\n\n/*\n==============\nVM_VmInfo_f\n==============\n*/\nvoid VM_VmInfo_f( void ) {\n\tvm_t\t*vm;\n\tint\t\ti;\n\n\tCon_Printf( \"Registered virtual machines:\\n\" );\n\tfor ( i = 0 ; i < VM_COUNT ; i++ ) {\n\t\tvm = &vmTable[i];\n\t\tif ( !vm->name ) {\n\t\t\tcontinue;\n\t\t}\n\t\tCon_Printf( \"%s : \", vm->name );\n\t\tif ( vm->dllHandle ) {\n\t\t\tCon_Printf( \"native\\n\" );\n\t\t\tcontinue;\n\t\t}\n\t\tif ( vm->compiled ) {\n\t\t\tCon_Printf( \"compiled on load\\n\" );\n\t\t} else {\n\t\t\tCon_Printf( \"interpreted\\n\" );\n\t\t}\n\t\tCon_Printf( \"    code length : %7i\\n\", vm->codeLength );\n\t\tCon_Printf( \"    table length: %7i\\n\", vm->instructionCount*4 );\n\t\tCon_Printf( \"    data length : %7i\\n\", vm->dataMask + 1 );\n\t}\n}\n\n/*\n===============\nVM_LogSyscalls\n\nInsert calls to this while debugging the vm compiler\n===============\n*/\nvoid VM_LogSyscalls(int *args) {\n#if 1\n\tstatic\tint\t\tcallnum;\n\tstatic\tFILE\t*f;\n\n\tif (!f) {\n\t\tf = fopen(\"syscalls.log\", \"w\");\n\t\tif (!f) {\n\t\t\treturn;\n\t\t}\n\t}\n\tcallnum++;\n\tfprintf(f, \"%i: %p (%i) = %i %i %i %i\\n\", callnum, (void*)(args - (int *)currentVM->dataBase),\n\t\targs[0], args[1], args[2], args[3], args[4]);\n#endif\n}\n#endif\t\t\t\t/* USE_PR2 */\n"
  },
  {
    "path": "src/vm.h",
    "content": "#ifndef VM_H\n#define VM_H\n\n/*\n==============================================================\n\nVIRTUAL MACHINE\n\n==============================================================\n*/\n\n#ifdef _WIN32\n#define QDECL __cdecl\n#else\n#define QDECL\n#endif\n\ntypedef struct vm_s vm_t;\n\ntypedef enum {\n\tVMI_NONE,\n\tVMI_NATIVE,\n\tVMI_BYTECODE,\n\tVMI_COMPILED\n} vmInterpret_t;\n\n\ntypedef enum {\n\tVM_BAD = -1,\n\tVM_GAME = 0,\n\tVM_COUNT\n} vmIndex_t;\n\nextern  vm_t    *currentVM;\ntypedef intptr_t (*syscall_t)( intptr_t *parms );\ntypedef intptr_t (QDECL *dllSyscall_t)( intptr_t callNum, ... );\ntypedef void (QDECL *dllEntry_t)( dllSyscall_t syscallptr );\n\n\nvoid\tVM_Init( void );\nvm_t\t*VM_Create( vmIndex_t index, const char* name, syscall_t systemCalls, /*dllSyscall_t dllSyscalls,*/ vmInterpret_t interpret );\n\nextern  vm_t    *currentVM;\n\n// module should be bare: \"cgame\", not \"cgame.dll\" or \"vm/cgame.qvm\"\n\nvoid\tVM_Free( vm_t *vm );\nvoid\tVM_Clear(void);\nvoid\tVM_Forced_Unload_Start(void);\nvoid\tVM_Forced_Unload_Done(void);\nvm_t\t*VM_Restart( vm_t *vm );\n\nintptr_t\tQDECL VM_Call( vm_t *vm, int nargs, int callNum, ... );\n\nvoid\tVM_Debug( int level );\nvoid\tVM_CheckBounds( const vm_t *vm, unsigned int address, unsigned int length );\nvoid\tVM_CheckBounds2( const vm_t *vm, unsigned int addr1, unsigned int addr2, unsigned int length );\n\n#if 1\n#define VM_CHECKBOUNDS VM_CheckBounds\n#define VM_CHECKBOUNDS2 VM_CheckBounds2\n#else // for performance evaluation purposes\n#define VM_CHECKBOUNDS(vm,a,b)\n#define VM_CHECKBOUNDS2(vm,a,b,c)\n#endif\n\nvoid\t    *VM_ArgPtr( intptr_t intValue );\nvoid *VM_ExplicitArgPtr( vm_t *vm, intptr_t intValue );\nintptr_t     VM_Ptr2VM( void* ptr ) ;\nintptr_t VM_ExplicitPtr2VM( vm_t *vm, void* ptr );\n\ntypedef union floatint_u\n{\n\tint i;\n\tunsigned int u;\n\tfloat f;\n\tbyte b[4];\n}\nfloatint_t;\n\n#define\tVMA(x) VM_ArgPtr(args[x])\nstatic inline float _vmf(intptr_t x)\n{\n\tfloatint_t v;\n\tv.i = (int)x;\n\treturn v.f;\n}\n#define\tVMF(x)\t_vmf(args[x])\n\n#endif\n"
  },
  {
    "path": "src/vm_interpreted.c",
    "content": "/*\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by\n *  the Free Software Foundation; either version 2 of the License, or\n *  (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n*/\n#ifdef USE_PR2\n#ifdef SERVERONLY\n#include \"qwsvdef.h\"\n#else\n#include \"quakedef.h\"\n#endif\n#include \"vm_local.h\"\n\n\nchar *VM_Indent( vm_t *vm ) {\n\tstatic char\t*string = \"                                        \";\n\tif ( vm->callLevel > 20 ) {\n\t\treturn string;\n\t}\n\treturn string + 2 * ( 20 - vm->callLevel );\n}\n\n\nvoid VM_StackTrace( vm_t *vm, int programCounter, int programStack ) {\n\tint\t\tcount;\n\n\tcount = 0;\n\tdo {\n\t\tCon_Printf( \"%s\\n\", VM_ValueToSymbol( vm, programCounter ) );\n\t\tprogramStack =  *(int *)&vm->dataBase[programStack+4];\n\t\tprogramCounter = *(int *)&vm->dataBase[programStack];\n\t} while ( programCounter != -1 && ++count < 32 );\n\n}\n\n// macro opcode sequences\ntypedef enum {\n\tMOP_LOCAL_LOAD4 = OP_MAX,\n\tMOP_LOCAL_LOAD4_CONST,\n\tMOP_LOCAL_LOCAL,\n\tMOP_LOCAL_LOCAL_LOAD4,\n} macro_op_t;\n\n\n/*\n=================\nVM_FindMOps\n\nSearch for known macro-op sequences\n=================\n*/\nstatic void VM_FindMOps( instruction_t *buf, int instructionCount )\n{\n\tint i, op0;\n\tinstruction_t *ci;\n\t\n\tci = buf;\n\ti = 0;\n\n\twhile ( i < instructionCount )\n\t{\n\t\top0 = ci->op;\n\n\t\tif ( op0 == OP_LOCAL && (ci+1)->op == OP_LOAD4 && (ci+2)->op == OP_CONST ) {\n\t\t\tci->op = MOP_LOCAL_LOAD4_CONST;\n\t\t\tci += 3; i += 3;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ( op0 == OP_LOCAL && (ci+1)->op == OP_LOAD4 ) {\n\t\t\tci->op = MOP_LOCAL_LOAD4;\n\t\t\tci += 2; i += 2;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ( op0 == OP_LOCAL && (ci+1)->op == OP_LOCAL && (ci+2)->op == OP_LOAD4 ) {\n\t\t\tci->op = MOP_LOCAL_LOCAL_LOAD4;\n\t\t\tci += 3; i += 3;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ( op0 == OP_LOCAL && (ci+1)->op == OP_LOCAL ) {\n\t\t\tci->op = MOP_LOCAL_LOCAL;\n\t\t\tci += 2; i += 2;\n\t\t\tcontinue;\n\t\t}\n\n\t\tci++;\n\t\ti++;\n\t}\n}\n\n\n/*\n====================\nVM_PrepareInterpreter2\n====================\n*/\nqbool VM_PrepareInterpreter2( vm_t *vm, vmHeader_t *header ) \n{\n\tconst char *errMsg;\n\tinstruction_t *buf;\n\tbuf = ( instruction_t *) Hunk_AllocName( (vm->instructionCount + 8) * sizeof( instruction_t ), \"vm\");\n\n\terrMsg = VM_LoadInstructions( (byte *) header + header->codeOffset, header->codeLength, header->instructionCount, buf );\n\tif ( !errMsg ) {\n\t\terrMsg = VM_CheckInstructions( buf, vm->instructionCount, vm->jumpTableTargets, vm->numJumpTableTargets, vm->exactDataLength );\n\t}\n\tif ( errMsg ) {\n\t\tCon_Printf( \"VM_PrepareInterpreter2 error: %s\\n\", errMsg );\n\t\treturn false;\n\t}\n\n\t//VM_ReplaceInstructions( vm, buf );\n\n\tVM_FindMOps( buf, vm->instructionCount );\n\n\tvm->codeBase.ptr = (void*)buf;\n\treturn true;\n}\n\n\n/*\n==============\nVM_CallInterpreted2\n\n\nUpon a system call, the stack will look like:\n\nsp+32\tparm1\nsp+28\tparm0\nsp+24\treturn stack\nsp+20\treturn address\nsp+16\tlocal1\nsp+14\tlocal0\nsp+12\targ1\nsp+8\targ0\nsp+4\treturn stack\nsp\t\treturn address\n\nAn interpreted function will immediately execute\nan OP_ENTER instruction, which will subtract space for\nlocals from sp\n==============\n*/\nint\tVM_CallInterpreted2( vm_t *vm, int nargs, int *args ) {\n\tint\t\tstack[MAX_OPSTACK_SIZE];\n\tint\t\t*opStack, *opStackTop;\n\tunsigned int programStack;\n\tunsigned int stackOnEntry;\n\tbyte\t*image;\n\tint\t\tv1, v0;\n\tint\t\tdataMask;\n\tinstruction_t *inst, *ci;\n\tfloatint_t\tr0, r1;\n\tint\t\topcode;\n\tint\t\t*img;\n\tint\t\ti;\n\n\t// interpret the code\n\t//vm->currentlyInterpreting = true;\n\n\t// we might be called recursively, so this might not be the very top\n\tprogramStack = stackOnEntry = vm->programStack;\n\n\t// set up the stack frame \n\timage = vm->dataBase;\n\tinst = (instruction_t *)vm->codeBase.ptr;\n\tdataMask = vm->dataMask;\n\t\n\t// leave a free spot at start of stack so\n\t// that as long as opStack is valid, opStack-1 will\n\t// not corrupt anything\n\topStack = &stack[1];\n\topStackTop = stack + ARRAY_LEN( stack ) - 1;\n\n\tprogramStack -= (MAX_VMMAIN_CALL_ARGS+2)*4;\n\timg = (int*)&image[ programStack ];\n\tfor ( i = 0; i < nargs; i++ ) {\n\t\timg[ i + 2 ] = args[ i ];\n\t}\n\timg[ 1 ] = 0; \t// return stack\n\timg[ 0 ] = -1;\t// will terminate the loop on return\n\n\tci = inst;\n\n\t// main interpreter loop, will exit when a LEAVE instruction\n\t// grabs the -1 program counter\n\n\twhile ( 1 ) {\n\n\t\tr0.i = opStack[0];\n\t\tr1.i = opStack[-1];\n\nnextInstruction2:\n\n\t\tv0 = ci->value;\n\t\topcode = ci->op; \n\t\tci++;\n\n\t\tswitch ( opcode ) {\n\n\t\tcase OP_BREAK:\n\t\t\tvm->breakCount++;\n\t\t\tgoto nextInstruction2;\n\n\t\tcase OP_ENTER:\n\t\t\t// get size of stack frame\n\t\t\tprogramStack -= v0;\n\t\t\tif ( programStack <= vm->stackBottom ) {\n                VM_StackTrace(vm, ci - (instruction_t *)vm->codeBase.ptr, programStack);\n\t\t\t\tSV_Error( \"VM programStack overflow\" );\n\t\t\t}\n\t\t\tif ( opStack + ((ci-1)->opStack/4) >= opStackTop ) {\n                VM_StackTrace(vm, ci - (instruction_t *)vm->codeBase.ptr, programStack);\n\t\t\t\tSV_Error( \"VM opStack overflow\" );\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OP_LEAVE:\n\t\t\t// remove our stack frame\n\t\t\tprogramStack += v0;\n\n\t\t\t// grab the saved program counter\n\t\t\tv1 = *(int *)&image[ programStack ];\n\t\t\t// check for leaving the VM\n\t\t\tif ( v1 == -1 ) {\n\t\t\t\tgoto done;\n\t\t\t} else if ( (unsigned)v1 >= vm->instructionCount ) {\n                VM_StackTrace(vm, ci - (instruction_t *)vm->codeBase.ptr, programStack);\n\t\t\t\tSV_Error( \"VM program counter out of range in OP_LEAVE\" );\n\t\t\t}\n\t\t\tci = inst + v1;\n\t\t\tbreak;\n\n\t\tcase OP_CALL:\n\t\t\t// save current program counter\n\t\t\t*(int *)&image[ programStack ] = ci - inst;\n\t\t\t\n\t\t\t// jump to the location on the stack\n\t\t\tif ( r0.i < 0 ) {\n\t\t\t\t// system call\n\t\t\t\t// save the stack to allow recursive VM entry\n\t\t\t\t//vm->programStack = programStack - 4;\n\t\t\t\tvm->programStack = programStack - 8;\n\t\t\t\t*(int *)&image[ programStack + 4 ] = ~r0.i;\n\t\t\t\tif (sizeof(int) != sizeof(intptr_t)) {\n\t\t\t\t\t// the vm has ints on the stack, we expect\n\t\t\t\t\t// longs so we have to convert it\n\t\t\t\t\tintptr_t argarr[16];\n\t\t\t\t\tint argn;\n\t\t\t\t\tfor ( argn = 0; argn < ARRAY_LEN( argarr ); ++argn ) {\n\t\t\t\t\t\targarr[ argn ] = *(int*)&image[ programStack + 4 + 4*argn ];\n\t\t\t\t\t}\n\t\t\t\t\tv0 = vm->systemCall( &argarr[0] );\n\t\t\t\t} else {\n                    //VM_LogSyscalls( (int *)&image[ programStack + 4 ] );\n\t\t\t\t\tv0 = vm->systemCall( (intptr_t *)&image[ programStack + 4 ] );\n\t\t\t\t}\n\n\t\t\t\t// save return value\n\t\t\t\t//opStack++;\n\t\t\t\tci = inst + *(int *)&image[ programStack ];\n\t\t\t\t*opStack = v0;\n\t\t\t} else if ( r0.u < vm->instructionCount ) {\n\t\t\t\t// vm call\n\t\t\t\tci = inst + r0.i;\n\t\t\t\topStack--;\n\t\t\t} else {\n                VM_StackTrace(vm, ci - (instruction_t *)vm->codeBase.ptr, programStack);\n\t\t\t\tSV_Error( \"VM program counter out of range in OP_CALL\" );\n\t\t\t}\n\t\t\tbreak;\n\n\t\t// push and pop are only needed for discarded or bad function return values\n\t\tcase OP_PUSH:\n\t\t\topStack++;\n\t\t\tbreak;\n\n\t\tcase OP_POP:\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_CONST:\n\t\t\topStack++;\n\t\t\tr1.i = r0.i;\n\t\t\tr0.i = *opStack = v0;\n\t\t\tgoto nextInstruction2;\n\n\t\tcase OP_LOCAL:\n\t\t\topStack++;\n\t\t\tr1.i = r0.i;\n\t\t\tr0.i = *opStack = v0 + programStack;\n\t\t\tgoto nextInstruction2;\n\n\t\tcase OP_JUMP:\n\t\t\tif ( r0.u >= vm->instructionCount ) {\n                VM_StackTrace(vm, ci - (instruction_t *)vm->codeBase.ptr, programStack);\n\t\t\t\tSV_Error( \"VM program counter out of range in OP_JUMP\" );\n\t\t\t}\n\t\t\tci = inst + r0.i;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\t/*\n\t\t===================================================================\n\t\tBRANCHES\n\t\t===================================================================\n\t\t*/\n\n\t\tcase OP_EQ:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.i == r0.i )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_NE:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.i != r0.i )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_LTI:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.i < r0.i )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_LEI:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.i <= r0.i )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_GTI:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.i > r0.i )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_GEI:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.i >= r0.i )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_LTU:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.u < r0.u )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_LEU:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.u <= r0.u )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_GTU:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.u > r0.u )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_GEU:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.u >= r0.u )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_EQF:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.f == r0.f )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_NEF:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.f != r0.f )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_LTF:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.f < r0.f )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_LEF:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.f <= r0.f )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_GTF:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.f > r0.f )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\tcase OP_GEF:\n\t\t\topStack -= 2;\n\t\t\tif ( r1.f >= r0.f )\n\t\t\t\tci = inst + v0;\n\t\t\tbreak;\n\n\t\t//===================================================================\n\n\t\tcase OP_LOAD1:\n\t\t\tr0.i = *opStack = image[ r0.i & dataMask ];\n\t\t\tgoto nextInstruction2;\n\n\t\tcase OP_LOAD2:\n\t\t\tr0.i = *opStack = *(unsigned short *)&image[ r0.i & dataMask ];\n\t\t\tgoto nextInstruction2;\n\n\t\tcase OP_LOAD4:\n\t\t\tr0.i = *opStack = *(int *)&image[ r0.i & dataMask ];\n\t\t\tgoto nextInstruction2;\n\n\t\tcase OP_STORE1:\n\t\t\timage[ r1.i & dataMask ] = r0.i;\n\t\t\topStack -= 2;\n\t\t\tbreak;\n\n\t\tcase OP_STORE2:\n\t\t\t*(short *)&image[ r1.i & dataMask ] = r0.i;\n\t\t\topStack -= 2;\n\t\t\tbreak;\n\n\t\tcase OP_STORE4:\n\t\t\t*(int *)&image[ r1.i & dataMask ] = r0.i;\n\t\t\topStack -= 2;\n\t\t\tbreak;\n\n\t\tcase OP_ARG:\n\t\t\t// single byte offset from programStack\n\t\t\t*(int *)&image[ ( v0 + programStack ) /*& ( dataMask & ~3 ) */ ] = r0.i;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_BLOCK_COPY:\n\t\t\t{\n\t\t\t\tint\t\t*src, *dest;\n\t\t\t\tint\t\tcount, srci, desti;\n\n\t\t\t\tcount = v0;\n\t\t\t\t// MrE: copy range check\n\t\t\t\tsrci = r0.i & dataMask;\n\t\t\t\tdesti = r1.i & dataMask;\n\t\t\t\tcount = ((srci + count) & dataMask) - srci;\n\t\t\t\tcount = ((desti + count) & dataMask) - desti;\n\n\t\t\t\tsrc = (int *)&image[ srci ];\n\t\t\t\tdest = (int *)&image[ desti ];\n\t\t\t\t\n\t\t\t\tmemcpy( dest, src, count );\n\t\t\t\topStack -= 2;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OP_SEX8:\n\t\t\t*opStack = (signed char)*opStack;\n\t\t\tbreak;\n\n\t\tcase OP_SEX16:\n\t\t\t*opStack = (short)*opStack;\n\t\t\tbreak;\n\n\t\tcase OP_NEGI:\n\t\t\t*opStack = -r0.i;\n\t\t\tbreak;\n\n\t\tcase OP_ADD:\n\t\t\topStack[-1] = r1.i + r0.i;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_SUB:\n\t\t\topStack[-1] = r1.i - r0.i;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_DIVI:\n\t\t\topStack[-1] = r1.i / r0.i;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_DIVU:\n\t\t\topStack[-1] = r1.u / r0.u;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_MODI:\n\t\t\topStack[-1] = r1.i % r0.i;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_MODU:\n\t\t\topStack[-1] = r1.u % r0.u;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_MULI:\n\t\t\topStack[-1] = r1.i * r0.i;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_MULU:\n\t\t\topStack[-1] = r1.u * r0.u;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_BAND:\n\t\t\topStack[-1] = r1.u & r0.u;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_BOR:\n\t\t\topStack[-1] = r1.u | r0.u;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_BXOR:\n\t\t\topStack[-1] = r1.u ^ r0.u;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_BCOM:\n\t\t\t*opStack = ~ r0.u;\n\t\t\tbreak;\n\n\t\tcase OP_LSH:\n\t\t\topStack[-1] = r1.i << r0.i;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_RSHI:\n\t\t\topStack[-1] = r1.i >> r0.i;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_RSHU:\n\t\t\topStack[-1] = r1.u >> r0.i;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_NEGF:\n\t\t\t*(float *)opStack =  - r0.f;\n\t\t\tbreak;\n\n\t\tcase OP_ADDF:\n\t\t\t*(float *)(opStack-1) = r1.f + r0.f;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_SUBF:\n\t\t\t*(float *)(opStack-1) = r1.f - r0.f;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_DIVF:\n\t\t\t*(float *)(opStack-1) = r1.f / r0.f;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_MULF:\n\t\t\t*(float *)(opStack-1) = r1.f * r0.f;\n\t\t\topStack--;\n\t\t\tbreak;\n\n\t\tcase OP_CVIF:\n\t\t\t*(float *)opStack = (float) r0.i;\n\t\t\tbreak;\n\n\t\tcase OP_CVFI:\n\t\t\t*opStack = (int) r0.f;\n\t\t\tbreak;\n\n\t\tcase MOP_LOCAL_LOAD4:\n\t\t\tci++;\n\t\t\topStack++;\n\t\t\tr1.i = r0.i;\n\t\t\tr0.i = *opStack = *(int *)&image[ v0 + programStack ];\n\t\t\tgoto nextInstruction2;\n\n\t\tcase MOP_LOCAL_LOAD4_CONST:\n\t\t\tr1.i = opStack[1] = *(int *)&image[ v0 + programStack ];\n\t\t\tr0.i = opStack[2] = (ci+1)->value;\n\t\t\topStack += 2;\n\t\t\tci += 2;\n\t\t\tgoto nextInstruction2;\n\n\t\tcase MOP_LOCAL_LOCAL:\n\t\t\tr1.i = opStack[1] = v0 + programStack;\n\t\t\tr0.i = opStack[2] = ci->value + programStack;\n\t\t\topStack += 2;\n\t\t\tci++;\n\t\t\tgoto nextInstruction2;\n\n\t\tcase MOP_LOCAL_LOCAL_LOAD4:\n\t\t\tr1.i = opStack[1] = v0 + programStack;\n\t\t\tr0.i /*= opStack[2]*/ = ci->value + programStack;\n\t\t\tr0.i = opStack[2] = *(int *)&image[ r0.i /*& dataMask*/ ];\n\t\t\topStack += 2;\n\t\t\tci += 2;\n\t\t\tgoto nextInstruction2;\n\t\t}\n\t}\n\ndone:\n\t//vm->currentlyInterpreting = false;\n\n\tif ( opStack != &stack[2] ) {\n        VM_StackTrace(vm, ci - (instruction_t *)vm->codeBase.ptr, programStack);\n\t\tSV_Error( \"Interpreter error: opStack = %ld\", (long int) (opStack - stack) );\n\t}\n\n\tvm->programStack = stackOnEntry;\n\n\t// return the result\n\treturn *opStack;\n}\n#endif\t\t\t\t/* USE_PR2 */\n"
  },
  {
    "path": "src/vm_local.h",
    "content": "/*\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by\n *  the Free Software Foundation; either version 2 of the License, or\n *  (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n *\n *\n */\n\n#ifndef VM_LOCAL_H\n#define VM_LOCAL_H\n\n#include \"vm.h\"\n#define\tMAX_OPSTACK_SIZE\t512\n#define\tPROC_OPSTACK_SIZE\t30\n#define\tSTACK_MASK\t(MAX_OPSTACK_SIZE-1)\n#define\tDEBUGSTR va(\"%s%i\", VM_Indent(vm), opStack-stack )\n\n// we don't need more than 4 arguments (counting callnum) for vmMain, at least in Quake3\n#define MAX_VMMAIN_CALL_ARGS 4\n\n// don't change\n// Hardcoded in q3asm an reserved at end of bss\n#define\tPROGRAM_STACK_SIZE\t0x10000\n\n// for some buggy mods\n#define\tPROGRAM_STACK_EXTRA\t(32*1024)\n\n#define PAD(base, alignment)\t(((base)+(alignment)-1) & ~((alignment)-1))\n#define PADLEN(base, alignment)\t(PAD((base), (alignment)) - (base))\n\ntypedef enum {\n\tOP_UNDEF,\n\n\tOP_IGNORE,\n\n\tOP_BREAK,\n\n\tOP_ENTER,\n\tOP_LEAVE,\n\tOP_CALL,\n\tOP_PUSH,\n\tOP_POP,\n\n\tOP_CONST,\n\tOP_LOCAL,\n\n\tOP_JUMP,\n\n\t//-------------------\n\n\tOP_EQ,\n\tOP_NE,\n\n\tOP_LTI,\n\tOP_LEI,\n\tOP_GTI,\n\tOP_GEI,\n\n\tOP_LTU,\n\tOP_LEU,\n\tOP_GTU,\n\tOP_GEU,\n\n\tOP_EQF,\n\tOP_NEF,\n\n\tOP_LTF,\n\tOP_LEF,\n\tOP_GTF,\n\tOP_GEF,\n\n\t//-------------------\n\n\tOP_LOAD1,\n\tOP_LOAD2,\n\tOP_LOAD4,\n\tOP_STORE1,\n\tOP_STORE2,\n\tOP_STORE4,\t\t\t\t// *(stack[top-1]) = stack[top]\n\tOP_ARG,\n\n\tOP_BLOCK_COPY,\n\n\t//-------------------\n\n\tOP_SEX8,\n\tOP_SEX16,\n\n\tOP_NEGI,\n\tOP_ADD,\n\tOP_SUB,\n\tOP_DIVI,\n\tOP_DIVU,\n\tOP_MODI,\n\tOP_MODU,\n\tOP_MULI,\n\tOP_MULU,\n\n\tOP_BAND,\n\tOP_BOR,\n\tOP_BXOR,\n\tOP_BCOM,\n\n\tOP_LSH,\n\tOP_RSHI,\n\tOP_RSHU,\n\n\tOP_NEGF,\n\tOP_ADDF,\n\tOP_SUBF,\n\tOP_DIVF,\n\tOP_MULF,\n\n\tOP_CVIF,\n\tOP_CVFI,\n\n\tOP_MAX\n} opcode_t;\n\ntypedef struct {\n\tint\t\tvalue;\t// 32\n\tbyte\top;\t\t// 8\n\tbyte\topStack;\t// 8\n\tunsigned jused:1;\n\tunsigned swtch:1;\n} instruction_t;\n\ntypedef struct vmSymbol_s {\n\tstruct vmSymbol_s\t*next;\n\tint\t\tsymValue;\n\tint\t\tprofileCount;\n\tchar\tsymName[1];\t\t// variable sized\n} vmSymbol_t;\n\n\n//typedef void(*vmfunc_t)(void);\n\ntypedef union vmFunc_u {\n\tbyte\t\t*ptr;\n\tvoid (*func)(void);\n} vmFunc_t;\n\n#define\tVM_MAGIC\t0x12721444\n#define\tVM_MAGIC_VER2\t0x12721445\ntypedef struct\n{\n\tint\t\tvmMagic;\n\n\tint\t\tinstructionCount;\n\n\tint\t\tcodeOffset;\n\tint\t\tcodeLength;\n\n\tint\t\tdataOffset;\n\tint\t\tdataLength;\n\tint\t\tlitLength;\t\t\t// ( dataLength - litLength ) should be byteswapped on load\n\tint\t\tbssLength;\t\t\t// zero filled memory appended to datalength\n    \t//!!! below here is VM_MAGIC_VER2 !!!\n\tint\t\tjtrgLength;\t\t\t// number of jump table targets\n} vmHeader_t;\n\n\ntypedef struct vm_s vm_t;\n\nstruct vm_s {\n\n\tunsigned int programStack;\t\t// the vm may be recursively entered\n\tsyscall_t\tsystemCall;\n\tbyte\t\t*dataBase;\n\tint\t\t\t*opStack;\t\t\t// pointer to local function stack\n\n\tint\t\t\tinstructionCount;\n\tintptr_t\t*instructionPointers;\n\n\t//------------------------------------\n   \n\tconst char\t*name;\n\tvmIndex_t\tindex;\n\n\t// for dynamic linked modules\n\tvoid\t\t*dllHandle;\n\tdllSyscall_t entryPoint;\n\tdllSyscall_t dllSyscall;\n\tvoid (*destroy)(vm_t* self);\n\n\t// for interpreted modules\n\t//qbool\tcurrentlyInterpreting;\n\n\tqbool\tcompiled;\n\n\tvmFunc_t\tcodeBase;\n\tunsigned int codeSize;\t\t\t// code + jump targets, needed for proper munmap()\n\tunsigned int codeLength;\t\t// just for information\n\n\tunsigned int dataMask;\n\tunsigned int dataLength;\t\t\t// data segment length\n\tunsigned int exactDataLength;\t// from qvm header\n\tunsigned int dataAlloc;\t\t\t// actually allocated\n\n\tunsigned int stackBottom;\t\t// if programStack < stackBottom, error\n\tint\t\t\t*opStackTop;\n\n\tint\t\t\tnumSymbols;\n\tvmSymbol_t\t*symbols;\n\n\tint\t\t\tcallLevel;\t\t\t// counts recursive VM_Call\n\tint\t\t\tbreakFunction;\t\t// increment breakCount on function entry to this\n\tint\t\t\tbreakCount;\n\n\tbyte\t\t*jumpTableTargets;\n\tint\t\t\tnumJumpTableTargets;\n\n\tuint32_t\tcrc32sum;\n\n\tqbool\tforceDataMask;\n    vmInterpret_t type;\n\tqbool pr2_references;\n\t//int\t\t\tprivateFlag;\n};\n\nextern\tint\t\tvm_debugLevel;\n\nextern cvar_t\tvm_rtChecks;\nqbool VM_Compile( vm_t *vm, vmHeader_t *header );\nint\tVM_CallCompiled( vm_t *vm, int nargs, int *args );\n\nqbool VM_PrepareInterpreter2( vm_t *vm, vmHeader_t *header );\nint\tVM_CallInterpreted2( vm_t *vm, int nargs, int *args );\n\nvmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value );\nint VM_SymbolToValue( vm_t *vm, const char *symbol );\nconst char *VM_ValueToSymbol( vm_t *vm, int value );\nvoid VM_LogSyscalls( int *args );\n\nconst char *VM_LoadInstructions( const byte *code_pos, int codeLength, int instructionCount, instruction_t *buf );\nconst char *VM_CheckInstructions( instruction_t *buf, int instructionCount, \n\t\t\t\t\t\t\t\t const byte *jumpTableTargets, \n\t\t\t\t\t\t\t\t int numJumpTableTargets, \n\t\t\t\t\t\t\t\t int dataLength );\n\n//void VM_ReplaceInstructions( vm_t *vm, instruction_t *buf );\n\n#define JUMP\t(1<<0)\n\ntypedef struct opcode_info_s \n{\n\tint   size; \n\tint\t  stack;\n\tint   nargs;\n\tint   flags;\n} opcode_info_t ;\n\nextern opcode_info_t ops[ OP_MAX ];\n\nvoid\tVM_Init( void );\nvm_t\t*VM_Create( vmIndex_t index, const char* name, syscall_t systemCalls, /*dllSyscall_t dllSyscalls,*/ vmInterpret_t interpret );\n\n// module should be bare: \"cgame\", not \"cgame.dll\" or \"vm/cgame.qvm\"\n\nvoid\tVM_Free( vm_t *vm );\nvoid\tVM_Clear(void);\nvoid\tVM_Forced_Unload_Start(void);\nvoid\tVM_Forced_Unload_Done(void);\nvm_t\t*VM_Restart( vm_t *vm );\n\nintptr_t\tQDECL VM_Call( vm_t *vm, int nargs, int callNum, ... );\n\nvoid\tVM_Debug( int level );\nvoid\tVM_CheckBounds( const vm_t *vm, unsigned int address, unsigned int length );\nvoid\tVM_CheckBounds2( const vm_t *vm, unsigned int addr1, unsigned int addr2, unsigned int length );\n\n#if 1\n#define VM_CHECKBOUNDS VM_CheckBounds\n#define VM_CHECKBOUNDS2 VM_CheckBounds2\n#else // for performance evaluation purposes\n#define VM_CHECKBOUNDS(vm,a,b)\n#define VM_CHECKBOUNDS2(vm,a,b,c)\n#endif\n\n\n\n#endif // VM_LOCAL_H\n\n"
  },
  {
    "path": "src/vm_x86.c",
    "content": "/*\n *  This program is free software; you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by\n *  the Free Software Foundation; either version 2 of the License, or\n *  (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n *\n *\n */\n\n#ifdef USE_PR2\n#ifdef SERVERONLY\n#include \"qwsvdef.h\"\n#else\n#include \"quakedef.h\"\n#include \"pr_comp.h\"\n#include \"g_public.h\"\n#endif\n#include \"vm_local.h\"\n\n#if ( idx386) || (idx64)\n#ifdef _WIN32\n#include <windows.h>\n#endif\n\n#ifdef __FreeBSD__\n#include <sys/types.h>\n#endif\n\n#ifndef _WIN32\n#include <sys/mman.h> // for PROT_ stuff\n#endif\n\n/* need this on NX enabled systems (i386 with PAE kernel or\n * noexec32=on x86_64) */\n#if defined(__linux__) || defined(__FreeBSD__)\n#define VM_X86_MMAP\n#endif\n\n//#define VM_LOG_SYSCALLS\n#define JUMP_OPTIMIZE 0\n\n#if JUMP_OPTIMIZE\n#define NUM_PASSES 7\n#else\n#define NUM_PASSES 3\n#endif\n\n/*\n** --------------------------------------------------------------------------------\n**\n** PROCESSOR STUFF\n**\n** --------------------------------------------------------------------------------\n*/\n\n#define CPU_FCOM   0x01\n#define CPU_MMX    0x02\n#define CPU_SSE    0x04\n#define CPU_SSE2   0x08\n#define CPU_SSE3   0x10\n\nint\t\tCPU_Flags = 0;\n#if defined _MSC_VER\n\nstatic void CPUID( int func, unsigned int *regs )\n{\n#if _MSC_VER >= 1400\n\t__cpuid( regs, func );\n#else\n\t__asm {\n\t\tmov edi,regs\n\t\tmov eax,[edi]\n\t\tcpuid\n\t\tmov [edi], eax\n\t\tmov [edi+4], ebx\n\t\tmov [edi+8], ecx\n\t\tmov [edi+12], edx\n\t}\n#endif\n}\n\n#else\n\nstatic void CPUID( int func, unsigned int *regs )\n{\n\t__asm__ __volatile__( \"cpuid\" :\n\t\t\"=a\"(regs[0]),\n\t\t\"=b\"(regs[1]),\n\t\t\"=c\"(regs[2]),\n\t\t\"=d\"(regs[3]) :\n\t\t\"a\"(func) );\n}\n#endif\n\nint Sys_GetProcessorId( char *vendor )\n{\n\tunsigned int regs[4];\n\n\t// setup initial features\n#if idx64\n\tCPU_Flags |= CPU_SSE | CPU_SSE2 | CPU_FCOM;\n#else\n\tCPU_Flags = 0;\n#endif\n\n\t// get CPU feature bits\n\tCPUID( 1, regs );\n\n\t// bit 15 of EDX denotes CMOV/FCMOV/FCOMI existence\n\tif ( regs[3] & ( 1 << 15 ) )\n\t\tCPU_Flags |= CPU_FCOM;\n\n\t// bit 23 of EDX denotes MMX existence\n\tif ( regs[3] & ( 1 << 23 ) )\n\t\tCPU_Flags |= CPU_MMX;\n\n\t// bit 25 of EDX denotes SSE existence\n\tif ( regs[3] & ( 1 << 25 ) )\n\t\tCPU_Flags |= CPU_SSE;\n\n\t// bit 26 of EDX denotes SSE2 existence\n\tif ( regs[3] & ( 1 << 26 ) )\n\t\tCPU_Flags |= CPU_SSE2;\n\n\t// bit 0 of ECX denotes SSE3 existence\n\tif ( regs[2] & ( 1 << 0 ) )\n\t\tCPU_Flags |= CPU_SSE3;\n\n\tif ( vendor ) {\n#if idx64\n\t\tstrcpy( vendor, \"64-bit \" );\n\t\tvendor += strlen( vendor );\n#else\n\t\tvendor[0] = '\\0';\n#endif\n\t\t// get CPU vendor string\n\t\tCPUID( 0, regs );\n\t\tmemcpy( vendor+0, (char*) &regs[1], 4 );\n\t\tmemcpy( vendor+4, (char*) &regs[3], 4 );\n\t\tmemcpy( vendor+8, (char*) &regs[2], 4 );\n\t\tvendor[12] = '\\0'; vendor += 12;\n\t\tif ( CPU_Flags ) {\n\t\t\t// print features\n#if !idx64\t// do not print default 64-bit features in 32-bit mode\n\t\t\tstrcat( vendor, \" w/\" );\n\t\t\tif ( CPU_Flags & CPU_FCOM )\n\t\t\t\tstrcat( vendor, \" CMOV\" );\n\t\t\tif ( CPU_Flags & CPU_MMX )\n\t\t\t\tstrcat( vendor, \" MMX\" );\n\t\t\tif ( CPU_Flags & CPU_SSE )\n\t\t\t\tstrcat( vendor, \" SSE\" );\n\t\t\tif ( CPU_Flags & CPU_SSE2 )\n\t\t\t\tstrcat( vendor, \" SSE2\" );\n#endif\n\t\t\t//if ( CPU_Flags & CPU_SSE3 )\n\t\t\t//\tstrcat( vendor, \" SSE3\" );\n\t\t}\n\t}\n\treturn 1;\n}\n\nstatic void *VM_Alloc_Compiled( vm_t *vm, int codeLength, int tableLength );\nstatic void VM_Destroy_Compiled( vm_t *vm );\n\n/*\n  -------------\n  eax\tscratch\n  ebx*\tdataBase\n  ecx\tscratch (required for shifts)\n  edx\tscratch (required for divisions)\n  esi*\tprogram stack\n  edi*\topstack\n  ebp*  current proc stack ( dataBase + program stack )\n  -------------\n  rax\tscratch\n  rbx*\tdataBase\n  rcx\tscratch (required for shifts)\n  rdx\tscratch (required for divisions)\n  rsi*\tprogramStack\n  rdi*\topstack\n  rbp*  current proc stack ( dataBase + program stack )\n  r8\tinstructionPointers\n  r9    dataMask\n  r12*  systemCall\n  r13*  stackBottom\n  r14*  opStackTop\n  xmm0  scratch\n  xmm1  scratch\n  xmm2  scratch\n  xmm3  scratch\n  xmm4  scratch\n  xmm5  scratch\n\n  Windows  ABI: you are required to preserve the XMM6-XMM15 registers\n  System V ABI: you don't have to preserve any of the XMM registers\n\n  Example how data segment will look like during vmMain execution:\n  | .... |\n  |------| vm->programStack -=36 (8+12+16) // set by vmMain\n  | ???? | +0 - unused, reserved for interpreter\n  | ???? | +4 - unused, reserved for interpreter\n  |-------\n  | arg0 | +8  \\\n  | arg4 | +12  | - passed arguments, accessible from subroutines\n  | arg8 | +16 /\n  |------|\n  | loc0 | +20 \\\n  | loc4 | +24  \\ - locals, accessible only from local scope\n  | loc8 | +28  /\n  | lc12 | +32 /\n  |------| vm->programStack -= 24 ( 8 + MAX_VMMAIN_CALL_ARGS*4 ) // set by VM_CallCompiled()\n  | ???? | +0 - unused, reserved for interpreter\n  | ???? | +4 - unused, reserved for interpreter\n  | arg0 | +8  \\\n  | arg1 | +12  \\ - passed arguments, accessible from vmMain\n  | arg2 | +16  / \n  | arg3 | +20 /\n  |------| vm->programStack = vm->dataMask + 1 // set by VM_Create()\n\n  jump/call opStack rules:\n\n  1) opStack must be 8 before conditional jump\n  2) opStack must be 4 before unconditional jump\n  3) opStack must be >=4 before OP_CALL\n  4) opStack must remain the same after OP_CALL\n  5) you may not jump in/call locations with opStack != 0\n\n*/\n\n#define ISS8(V) ( (V) >= -128 && (V) <= 127 )\n#define ISU8(V) ( (V) >= 0 && (V) <= 127 )\n\n#define REWIND(N) { compiledOfs -= (N); instructionOffsets[ ip-1 ] = compiledOfs; };\n\ntypedef enum \n{\n\tREG_EAX = 0,\n\tREG_ECX\n} reg_t;\n\n\ntypedef enum \n{\n\tLAST_COMMAND_NONE = 0,\n\tLAST_COMMAND_MOV_EDI_EAX,\n\tLAST_COMMAND_MOV_EDI_CONST,\n\tLAST_COMMAND_MOV_EAX_EDI,\n\tLAST_COMMAND_MOV_EAX_EDI_CALL,\n\tLAST_COMMAND_SUB_DI_4,\n\tLAST_COMMAND_SUB_DI_8,\n\tLAST_COMMAND_SUB_DI_12,\n\tLAST_COMMAND_STORE_FLOAT_EDI,\n\tLAST_COMMAND_STORE_FLOAT_EDI_X87,\n\tLAST_COMMAND_STORE_FLOAT_EDI_SSE\n} ELastCommand;\n\ntypedef enum \n{\n\tFUNC_ENTR = 0,\n\tFUNC_CALL,\n\tFUNC_SYSC,\n\tFUNC_FTOL,\n\tFUNC_BCPY,\n\tFUNC_NCPY,\n\tFUNC_PSOF,\n\tFUNC_OSOF,\n\tFUNC_BADJ,\n\tFUNC_ERRJ,\n\tFUNC_DATA,\n\tFUNC_LAST\n} funcx86_t;\n\n// macro opcode sequences\ntypedef enum {\n\tMOP_UNDEF = OP_MAX,\n\tMOP_IGNORE4,\n\tMOP_ADD4,\n\tMOP_SUB4,\n\tMOP_BAND4,\n\tMOP_BOR4,\n\tMOP_NCPY,\n} macro_op_t;\n\nstatic\tbyte     *code;\nstatic\tint      compiledOfs;\nstatic\tint      *instructionOffsets;\nstatic\tintptr_t *instructionPointers;\n\nstatic  instruction_t *inst = NULL;\nstatic  instruction_t *ci;\nstatic  instruction_t *ni;\n\nstatic int fp_cw[2] = { 0x0000, 0x0F7F }; // [0] - current value, [1] - round towards zero\n\nstatic\tint\tip, pass;\nstatic\tint\tlastConst;\nstatic\topcode_t\tpop1;\n\nstatic\tELastCommand\tLastCommand;\n\nint\t\tfuncOffset[FUNC_LAST];\n\n#ifdef DEBUG_VM\nstatic int\terrParam = 0;\n#endif\n\nstatic void ErrJump( void )\n{\n\tSV_Error( \"program tried to execute code outside VM\" ); \n}\n\n\nstatic void BadJump( void )\n{\n\tSV_Error( \"program tried to execute code at bad location inside VM\" ); \n}\n\n\nstatic void BadStack( void )\n{\n\tSV_Error( \"program tried to overflow program stack\" ); \n}\n\n\nstatic void BadOpStack( void )\n{\n\tSV_Error( \"program tried to overflow opcode stack\" ); \n}\n\n\nstatic void BadData( void )\n{\n#ifdef DEBUG_VM\t\n\tSV_Error( \"program tried to read/write out of data segment at %i\", errParam ); \n#else\n\tSV_Error( \"program tried to read/write out of data segment\" ); \n#endif\n}\n\n\nstatic void (*const errJumpPtr)(void) = ErrJump;\nstatic void (*const badJumpPtr)(void) = BadJump;\nstatic void (*const badStackPtr)(void) = BadStack;\nstatic void (*const badOpStackPtr)(void) = BadOpStack;\nstatic void (*const badDataPtr)(void) = BadData;\n\nstatic void VM_FreeBuffers( void )\n{\n\t// should be freed in reversed allocation order\n\tQ_free( instructionOffsets );\n\tQ_free( inst ); \n}\n\nstatic const inline qbool HasFCOM( void )\n{\n#if idx386\n\treturn ( CPU_Flags & CPU_FCOM );\n#else\n\treturn true; // assume idx64\n#endif\n}\n\n\nstatic const inline qbool HasSSEFP( void )\n{\n#if idx386\n\treturn ( CPU_Flags & CPU_SSE ) && ( CPU_Flags & CPU_SSE2 );\n#else\n\treturn true; // assume idx64\n#endif\n}\n\nstatic void Emit1( int v )\n{\n\tif ( code ) \n\t{\n\t\tcode[ compiledOfs ] = v;\n\t}\n\tcompiledOfs++;\n\n\tLastCommand = LAST_COMMAND_NONE;\n}\n\nstatic void Emit4( int v )\n{\n\tEmit1( v & 255 );\n\tEmit1( ( v >> 8 ) & 255 );\n\tEmit1( ( v >> 16 ) & 255 );\n\tEmit1( ( v >> 24 ) & 255 );\n}\n\n\n#if idx64\nstatic void Emit8( int64_t v )\n{\n\tEmit1( ( v >> 0 ) & 255 );\n\tEmit1( ( v >> 8 ) & 255 );\n\tEmit1( ( v >> 16 ) & 255 );\n\tEmit1( ( v >> 24 ) & 255 );\n\tEmit1( ( v >> 32 ) & 255 );\n\tEmit1( ( v >> 40 ) & 255 );\n\tEmit1( ( v >> 48 ) & 255 );\n\tEmit1( ( v >> 56 ) & 255 );\n}\n#endif\n\n\nstatic void EmitPtr( const void *ptr )\n{\n#if idx64\n\tEmit8( (intptr_t)ptr );\n#else\n\tEmit4( (intptr_t)ptr );\n#endif\n}\n\n\nstatic int Hex( int c ) \n{\n\tif ( c >= '0' && c <= '9' ) {\n\t\treturn c - '0';\n\t}\n\tif ( c >= 'A' && c <= 'F' ) {\n\t\treturn 10 + c - 'A';\n\t}\n\tif ( c >= 'a' && c <= 'f' ) {\n\t\treturn 10 + c - 'a';\n\t}\n\n\tVM_FreeBuffers();\n\tSV_Error( \"Hex: bad char '%c'\", c );\n\n\treturn 0;\n}\n\n\nstatic void EmitString( const char *string )\n{\n\tint\t\tc1, c2;\n\tint\t\tv;\n\n\twhile ( 1 ) {\n\t\tc1 = string[0];\n\t\tc2 = string[1];\n\n\t\tv = ( Hex( c1 ) << 4 ) | Hex( c2 );\n\t\tEmit1( v );\n\n\t\tif ( !string[2] ) {\n\t\t\tbreak;\n\t\t}\n\t\tstring += 3;\n\t}\n}\n\n\nstatic void EmitRexString( const char *string )\n{\n#if idx64\n\tEmit1( 0x48 );\n#endif\n\tEmitString( string );\n}\n\n\nstatic void EmitAlign( int align )\n{\n\tint i, n;\n\n\tn = compiledOfs & ( align - 1 );\n\n\tfor ( i = 0; i < n ; i++ )\n\t\tEmitString( \"90\" );\t// nop\n}\n\n\nstatic void EmitCommand( ELastCommand command )\n{\n\tswitch( command )\n\t{\n\t\tcase LAST_COMMAND_MOV_EDI_EAX:\n\t\t\tEmitString( \"89 07\" );\t\t// mov dword ptr [edi], eax\n\t\t\tbreak;\n\n\t\tcase LAST_COMMAND_MOV_EAX_EDI:\n\t\t\tEmitString( \"8B 07\" );\t\t// mov eax, dword ptr [edi]\n\t\t\tbreak;\n\n\t\tcase LAST_COMMAND_SUB_DI_4:\n\t\t\tEmitRexString( \"83 EF 04\" );\t// sub edi, 4\n\t\t\tbreak;\n\n\t\tcase LAST_COMMAND_SUB_DI_8:\n\t\t\tEmitRexString( \"83 EF 08\" );\t// sub edi, 8\n\t\t\tbreak;\n\n\t\tcase LAST_COMMAND_SUB_DI_12:\n\t\t\tEmitRexString( \"83 EF 0C\" );\t// sub edi, 12\n\t\t\tbreak;\n\n\t\tcase LAST_COMMAND_STORE_FLOAT_EDI_SSE:\n\t\t\tEmitString( \"F3 0F 11 07\" );\t// movss dword ptr [edi], xmm0\n\t\t\tbreak;\n\n\t\tcase LAST_COMMAND_STORE_FLOAT_EDI_X87:\n\t\t\tEmitString( \"D9 1F\" );\t\t\t// fstp dword ptr [edi]\n\t\t\tbreak;\n\n\t\tcase LAST_COMMAND_STORE_FLOAT_EDI:\t// meta command\n\t\t\tif ( HasSSEFP() ) {\n\t\t\t\tEmitString( \"F3 0F 11 07\" );// movss dword ptr [edi], xmm0\n\t\t\t\tcommand = LAST_COMMAND_STORE_FLOAT_EDI_SSE;\n\t\t\t} else {\n\t\t\t\tEmitString( \"D9 1F\" );\t\t// fstp dword ptr [edi]\n\t\t\t\tcommand = LAST_COMMAND_STORE_FLOAT_EDI_X87;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\tLastCommand = command;\n}\n\nstatic void EmitAddEDI4( vm_t *vm )\n{\n\tif ( LastCommand == LAST_COMMAND_NONE ) \n\t{\n\t\tEmitRexString( \"83 C7 04\" );\t\t\t// add edi,4\n\t\treturn;\n\t}\n\n\tif ( LastCommand == LAST_COMMAND_SUB_DI_4 ) // sub edi, 4\n\t{\n#if idx64\n\t\tREWIND( 4 );\n#else\n\t\tREWIND( 3 );\n#endif\n\t\tLastCommand = LAST_COMMAND_NONE;\n\t\treturn;\n\t}\n\n\tif ( LastCommand == LAST_COMMAND_SUB_DI_8 ) // sub edi, 8\n\t{\t\n#if idx64\n\t\tREWIND( 4 );\n#else\n\t\tREWIND( 3 );\n#endif\n\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\n\t\treturn;\n\t}\n\n\tif ( LastCommand == LAST_COMMAND_SUB_DI_12 ) // sub edi, 12\n\t{\t\n#if idx64\n\t\tREWIND( 4 );\n#else\n\t\tREWIND( 3 );\n#endif\n\t\tEmitCommand( LAST_COMMAND_SUB_DI_8 );\n\t\treturn;\n\t}\n\n\tEmitRexString( \"83 C7 04\" );\t\t\t\t// add edi,4\n}\n\n\nstatic void EmitMovEAXEDI( vm_t *vm )\n{\n\topcode_t pop = pop1;\n\tpop1 = OP_UNDEF;\n\n\tif ( LastCommand == LAST_COMMAND_NONE ) \n\t{\n\t\tEmitString( \"8B 07\" );\t\t// mov eax, dword ptr [edi]\n\t\treturn;\n\t}\n\n\tif ( LastCommand == LAST_COMMAND_MOV_EAX_EDI )\n\t\treturn;\n\n\tif ( LastCommand == LAST_COMMAND_MOV_EAX_EDI_CALL )\n\t\treturn;\n\n\tif ( LastCommand == LAST_COMMAND_MOV_EDI_EAX ) // mov dword ptr [edi], eax\n\t{\t\n\t\tREWIND( 2 );\n\t\tLastCommand = LAST_COMMAND_NONE; \n\t\treturn;\n\t}\n\n\tif ( pop == OP_DIVI || pop == OP_DIVU || pop == OP_MULI || pop == OP_MULU ||\n\t\tpop == OP_STORE4 || pop == OP_STORE2 || pop == OP_STORE1 ) \n\t{\t\n\t\treturn;\n\t}\n\n\tif ( LastCommand == LAST_COMMAND_MOV_EDI_CONST ) // mov dword ptr [edi], 0x12345678\n\t{\t\n\t\tREWIND( 6 );\n\t\tif ( lastConst == 0 ) {\n\t\t\tEmitString( \"31 C0\" );\t\t// xor eax, eax\n\t\t} else {\n\t\t\tEmitString( \"B8\" );\t\t\t// mov\teax, 0x12345678\n\t\t\tEmit4( lastConst );\n\t\t}\n\t\treturn;\n\t}\n\n\tEmitString( \"8B 07\" );\t\t    // mov eax, dword ptr [edi]\n}\n\n\nvoid EmitMovECXEDI( vm_t *vm ) \n{\n\topcode_t pop = pop1;\n\tpop1 = OP_UNDEF;\n\n\tif ( LastCommand == LAST_COMMAND_NONE ) \n\t{\n\t\tEmitString( \"8B 0F\" );\t\t// mov ecx, dword ptr [edi]\n\t\treturn;\n\t}\n\n\tif ( LastCommand == LAST_COMMAND_MOV_EAX_EDI_CALL ) \n\t{\n\t\tEmitString( \"89 C1\" );\t\t// mov ecx, eax\n\t\treturn;\n\t}\n\n\tif ( LastCommand == LAST_COMMAND_MOV_EAX_EDI ) // mov eax, dword ptr [edi] \n\t{\n\t\tREWIND( 2 );\n\t\tEmitString( \"8B 0F\" );\t\t// mov ecx, dword ptr [edi]\n\t\treturn;\n\t}\n\n\tif ( LastCommand == LAST_COMMAND_MOV_EDI_EAX ) // mov dword ptr [edi], eax\n\t{\n\t\tREWIND( 2 );\n\t\tEmitString( \"89 C1\" );\t\t// mov ecx, eax\n\t\treturn;\n\t}\n\n\tif (pop == OP_DIVI || pop == OP_DIVU || pop == OP_MULI || pop == OP_MULU ||\n\t\tpop == OP_STORE4 || pop == OP_STORE2 || pop == OP_STORE1 ) \n\t{\t\n\t\tEmitString( \"89 C1\" );\t\t// mov ecx, eax\n\t\treturn;\n\t}\n\n\tif ( LastCommand == LAST_COMMAND_MOV_EDI_CONST ) // mov dword ptr [edi], 0x12345678\n\t{\t\n\t\tREWIND( 6 );\n\t\tEmitString( \"B9\" );\t\t\t// mov ecx, 0x12345678\n\t\tEmit4( lastConst );\n\t\treturn;\n\t}\n\n\tEmitString( \"8B 0F\" );\t\t    // mov ecx, dword ptr [edi]\n}\n\n\nstatic void EmitCheckReg( vm_t *vm, int reg, int size )\n{\n\tint n;\n\n\tif ( !( (int)vm_rtChecks.value & 8 ) || vm->forceDataMask ) {\n\t\tif ( vm->forceDataMask ) {\n\t\t\tif ( reg == REG_EAX )\n\t\t\t\tEmitString( \"25\" ); \t// and eax, 0x12345678\n\t\t\telse\n\t\t\t\tEmitString( \"81 E1\" );  // and ecx, 0x12345678\n\t\t\tEmit4( vm->dataMask );\n\t\t}\n\t\treturn;\n\t}\n\n#ifdef DEBUG_VM\n\tEmitString( \"50\" );\t\t\t// push eax\n\tEmitRexString( \"B8\" );\t\t// mov eax, &errParam\n\tEmitPtr( &errParam );\n\tEmitString( \"C7 00\" );\t\t// mov [rax], ip-1\n\tEmit4( ip-1 );\n\tEmitString( \"58\" );\t\t\t// pop eax\n#endif\n\n#if idx64\n\tif ( reg == REG_EAX )\n\t\tEmitString( \"44 39 C8\" );// cmp eax, r9d // vm->dataMask\n\telse\n\t\tEmitString( \"44 39 C9\" );// cmp ecx, r9d // vm->dataMask\n#else\n\tif ( reg == REG_EAX )\n\t\tEmitString( \"3D\" );\t\t// cmp eax, 0x12345678\n\telse\n\t\tEmitString( \"81 F9\" );\t// cmp ecx, 0x12345678\n\n\tEmit4( vm->dataMask - (size - 1) );\n#endif\n\n\t// error reporting\n\tEmitString( \"0F 87\" );\t\t// ja +errorFunction\n\tn = funcOffset[FUNC_DATA] - compiledOfs;\n\tEmit4( n - 6 );\n}\n\n\nstatic int EmitLoadFloatEDI_SSE( vm_t *vm )\n{\n\t// movss dword ptr [edi], xmm0\n\tif ( LastCommand == LAST_COMMAND_STORE_FLOAT_EDI_SSE )\n\t{\n\t\tif ( !vm )\n\t\t\treturn 1;\n\t\tREWIND( 4 );\n\t\tLastCommand = LAST_COMMAND_NONE;\n\t\treturn 1;\n\t}\n\tEmitString( \"F3 0F 10 07\" ); // movss xmm0, dword ptr [edi]\n\treturn 0;\n}\n\n\nstatic int EmitLoadFloatEDI_X87( vm_t *vm )\n{\n\t// fstp dword ptr [edi]\n\tif ( LastCommand == LAST_COMMAND_STORE_FLOAT_EDI_X87 )\n\t{ \t\n\t\tif ( !vm )\n\t\t\treturn 1;\n\t\tREWIND( 2 );\n\t\tLastCommand = LAST_COMMAND_NONE;\n\t\treturn 1;\n\t}\n\n\tEmitString( \"D9 07\" );\t\t// fld dword ptr [edi]\n\treturn 0;\n}\n\n\nstatic int EmitLoadFloatEDI( vm_t *vm )\n{\n\tif ( HasSSEFP() )\n\t\treturn EmitLoadFloatEDI_SSE( vm );\n\telse\n\t\treturn EmitLoadFloatEDI_X87( vm );\n}\n\n#if JUMP_OPTIMIZE\nconst char *NearJumpStr( int op ) \n{\n\tswitch ( op )\n\t{\n\t\tcase OP_EQF:\n\t\tcase OP_EQ:  return \"74\"; // je\n\t\t\n\t\tcase OP_NEF:\n\t\tcase OP_NE:  return \"75\"; // jne\n\t\t\n\t\tcase OP_LTI: return \"7C\"; // jl\n\t\tcase OP_LEI: return \"7E\"; // jle\n\t\tcase OP_GTI: return \"7F\"; // jg\n\t\tcase OP_GEI: return \"7D\"; // jge\n\n\t\tcase OP_LTF:\n\t\tcase OP_LTU: return \"72\"; // jb\n\t\t\n\t\tcase OP_LEF:\n\t\tcase OP_LEU: return \"76\"; // jbe\n\t\t\n\t\tcase OP_GTF:\n\t\tcase OP_GTU: return \"77\"; // ja \n\t\t\n\t\tcase OP_GEF:\n\t\tcase OP_GEU: return \"73\"; // jae\n\n\t\tcase OP_JUMP: return \"EB\";   // jmp\n\n\t\t//default:\n\t\t//\tSV_Error( \"Bad opcode %i\", op );\n\t};\n\treturn NULL;\n}\n#endif\n\n\nconst char *FarJumpStr( int op, int *n ) \n{\n\tswitch ( op )\n\t{\n\t\tcase OP_EQF:\n\t\tcase OP_EQ:  *n = 2; return \"0F 84\"; // je\n\t\t\n\t\tcase OP_NEF:\n\t\tcase OP_NE:  *n = 2; return \"0F 85\"; // jne\n\t\t\n\t\tcase OP_LTI: *n = 2; return \"0F 8C\"; // jl\n\t\tcase OP_LEI: *n = 2; return \"0F 8E\"; // jle\n\t\tcase OP_GTI: *n = 2; return \"0F 8F\"; // jg\n\t\tcase OP_GEI: *n = 2; return \"0F 8D\"; // jge\n\n\t\tcase OP_LTF:\n\t\tcase OP_LTU: *n = 2; return \"0F 82\"; // jb\n\n\t\tcase OP_LEF:\n\t\tcase OP_LEU: *n = 2; return \"0F 86\"; // jbe\n\t\t\n\t\tcase OP_GTF:\n\t\tcase OP_GTU: *n = 2; return \"0F 87\"; // ja \n\t\t\n\t\tcase OP_GEF:\n\t\tcase OP_GEU: *n = 2; return \"0F 83\"; // jae\n\n\t\tcase OP_JUMP: *n = 1; return \"E9\";   // jmp\n\t};\n\treturn NULL;\n}\n\n\nvoid EmitJump( vm_t *vm, instruction_t *i, int op, int addr ) \n{\n\tconst char *str;\n\tint v, jump_size = 0;\n\n\tv = instructionOffsets[ addr ] - compiledOfs;\n\n#if JUMP_OPTIMIZE\n\tif ( i->njump ) {\n\t\t// can happen\n\t\tif ( v < -126 || v > 129 ) {\n\t\t\tstr = FarJumpStr( op, &jump_size );\t\n\t\t\tEmitString( str );\n\t\t\tEmit4( v - 4 - jump_size ); \n\t\t\ti->njump = 0;\n\t\t\treturn;\n\t\t}\n\t\tEmitString( NearJumpStr( op ) );\n\t\tEmit1( v - 2 );\n\t\treturn;\n\t}\n\n\tif ( pass >= 2 && pass < NUM_PASSES-2 ) {\n\t\tif ( v >= -126 && v <= 129 ) {\n\t\t\tEmitString( NearJumpStr( op ) );\n\t\t\tEmit1( v - 2 ); \n\t\t\ti->njump = 1;\n\t\t\treturn;\n\t\t}\n\t}\n#endif\n\n\tstr = FarJumpStr( op, &jump_size );\t\n\tif ( jump_size == 0 ) {\n\t\tSV_Error( \"VM_CompileX86 error: %s\\n\", \"bad jump size\" );\n\t} else {\n\t\tEmitString( str );\n\t\tEmit4( v - 4 - jump_size );\n\t}\n}\n\n\nvoid EmitFloatJump( vm_t *vm, instruction_t *i, int op, int addr ) \n{\n\tswitch ( op ) {\n\t\tcase OP_EQF:\n\t\t\tEmitString( \"80 E4 40\" );\t// and ah,0x40\n\t\t\tEmitJump( vm, i, OP_NE, addr );\n\t\t\tbreak;\t\t\t\n\n\t\tcase OP_NEF:\n\t\t\tEmitString( \"80 E4 40\" );\t// and ah,0x40\n\t\t\tEmitJump( vm, i, OP_EQ, addr );\n\t\t\tbreak;\t\t\t\n\n\t\tcase OP_LTF:\n\t\t\tEmitString( \"80 E4 01\" );\t// and ah,0x01\n\t\t\tEmitJump( vm, i, OP_NE, addr );\n\t\t\tbreak;\t\t\t\n\n\t\tcase OP_LEF:\n\t\t\tEmitString( \"80 E4 41\" );\t// and ah,0x41\n\t\t\tEmitJump( vm, i, OP_NE, addr );\n\t\t\tbreak;\t\t\t\n\n\t\tcase OP_GTF:\n\t\t\tEmitString( \"80 E4 41\" );\t// and ah,0x41\n\t\t\tEmitJump( vm, i, OP_EQ, addr );\n\t\t\tbreak;\t\t\t\n\n\t\tcase OP_GEF:\n\t\t\tEmitString( \"80 E4 01\" );\t// and ah,0x01\n\t\t\tEmitJump( vm, i, OP_EQ, addr );\n\t\t\tbreak;\t\t\t\n\t};\n\n}\n\n\nstatic void EmitCallAddr( vm_t *vm, int addr )\n{\n\tint v;\n\tv = instructionOffsets[ addr ] - compiledOfs;\n\tEmitString( \"E8\" );\n\tEmit4( v - 5 ); \n}\n\n\nstatic void EmitCallOffset( funcx86_t Func )\n{\n\tint  v;\n\tv = funcOffset[ Func ] - compiledOfs;\n\tEmitString( \"E8\" );\t\t// call +funcOffset[ Func ]\n\tEmit4( v - 5 );\n}\n\n\n#ifdef _WIN32\n#define SHADOW_BASE 40\n#else // linux/*BSD ABI\n#define SHADOW_BASE 8\n#endif\n\n#define PUSH_STACK  32\n#define PARAM_STACK 128\n\nstatic void EmitCallFunc( vm_t *vm )\n{\n\tstatic int sysCallOffset = 0;\n\tint n;\n\n\tEmitString( \"85 C0\" );\t\t\t\t// test eax, eax\n\tEmitString( \"7C\" );\t\t\t\t\t// jl +offset (SystemCall) \n\tEmit1( sysCallOffset );\t\t\t\t// will be valid after first pass\nsysCallOffset = compiledOfs;\t\t\t\t\n\n\t// jump target range check\n\tif ( (int)vm_rtChecks.value & 4 ) {\n\t\tEmitString( \"3D\" );\t\t\t\t\t// cmp eax, vm->instructionCount\n\t\tEmit4( vm->instructionCount );\n\t\tEmitString( \"0F 83\" );\t\t\t\t// jae +funcOffset[FUNC_ERRJ]\n\t\tn = funcOffset[FUNC_ERRJ] - compiledOfs;\n\t\tEmit4( n - 6 );\n\t}\n\n\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\n\t// save proc base and programStack\n\tEmitString( \"55\" );\t\t\t\t// push ebp\n\tEmitString( \"56\" );\t\t\t\t// push esi\n\n\t// calling another vm function\n#if idx64\n\tEmitString( \"41 FF 14 C0\" );\t\t// call dword ptr [r8+rax*8]\n#else\n\tEmitString( \"8D 0C 85\" );\t\t\t// lea ecx, [vm->instructionPointers+eax*4]\n\tEmitPtr( instructionPointers );\n\tEmitString( \"FF 11\" );\t\t\t\t// call dword ptr [ecx]\n#endif\n\n\t// restore proc base and programStack so there is \n\t// no need to validate programStack anymore\n\tEmitString( \"5E\" );\t\t\t\t// pop esi\n\tEmitString( \"5D\" );\t\t\t\t// pop ebp\n\n\tEmitString( \"C3\" );\t\t\t\t// ret\n\nsysCallOffset = compiledOfs - sysCallOffset;\n\n\t// systemCall:\n\t// convert negative num to system call number\n\t// and store right before the first arg\n\tEmitString( \"F7 D0\" );          // not eax\n\n\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\n\t// we may jump here from ConstOptimize() also\nfuncOffset[FUNC_SYSC] = compiledOfs;\n\n#if idx64\n\t// allocate stack for shadow(win32)+parameters\n\tEmitString( \"48 81 EC\" );\t\t\t\t// sub rsp, 200\n\tEmit4( SHADOW_BASE + PUSH_STACK + PARAM_STACK );\n\n\t// save scratch registers\n\tEmitString( \"48 8D 54 24\" );\t\t\t// lea rdx, [rsp+SHADOW_BASE]\n\tEmit1( SHADOW_BASE );\n\tEmitString( \"48 89 32\" );\t\t\t\t// mov [rdx+00], rsi\n\tEmitString( \"48 89 7A 08\" );\t\t\t// mov [rdx+08], rdi\n\tEmitString( \"4C 89 42 10\" );\t\t\t// mov [rdx+16], r8\n\tEmitString( \"4C 89 4A 18\" );\t\t\t// mov [rdx+24], r9\n\n\t// ecx = &int64_params[0]\n\tEmitString( \"48 8D 4C 24\" );\t\t\t// lea rcx, [rsp+SHADOW_BASE+PUSH_STACK]\n\tEmit1( SHADOW_BASE + PUSH_STACK );\n\n\t// save syscallNum\n\tEmitString( \"48 89 01\" );\t\t\t\t// mov [rcx], rax\n\n\t// vm->programStack = programStack - 4;\n\tEmitString( \"48 BA\" );\t\t\t\t\t// mov rdx, &vm->programStack\n\tEmitPtr( &vm->programStack );\n\t//EmitString( \"8D 46 FC\" );\t\t\t\t// lea eax, [esi-4]\n\tEmitString( \"8D 46 F8\" );\t\t\t\t// lea eax, [esi-8]\n\tEmitString( \"89 02\" );\t\t\t\t\t// mov [rdx], eax\n\t//EmitString( \"89 32\" );\t\t\t\t// mov dword ptr [rdx], esi\n\n\t// params = (vm->dataBase + programStack + 8);\n\tEmitString( \"48 8D 74 33 08\" );\t\t\t// lea rsi, [rbx+rsi+8]\n\n\t// rcx = &int64_params[1]\n\tEmitString( \"48 83 C1 08\" );\t\t\t// add rcx, 8\n\n\t// dest_params[1-15] = params[1-15];\n\tEmitString( \"31 D2\" );\t\t\t\t\t// xor edx, edx\n\t// loop\n\tEmitString( \"48 63 04 96\" );\t\t\t// movsxd rax, dword [rsi+rdx*4]\n\tEmitString( \"48 89 04 D1\" );\t\t\t// mov qword ptr[rcx+rdx*8], rax\n\tEmitString( \"48 83 C2 01\" );\t\t\t// add rdx, 1\n\tEmitString( \"48 83 FA\" );\t\t\t\t// cmp rdx, 15\n\tEmit1( (PARAM_STACK/8) - 1 );\n\tEmitString( \"7C EE\" );\t\t\t\t\t// jl -18\n\n#ifdef _WIN32\n\t// rcx = &int64_params[0]\n\tEmitString( \"48 83 E9 08\" );\t\t\t// sub rcx, 8\n#else // linux/*BSD ABI\n\t// rdi = &int64_params[0]\n\tEmitString( \"48 8D 79 F8\" );\t\t\t// lea rdi, [rcx-8]\n#endif\n\t\n\t// currentVm->systemCall( param );\n\tEmitString( \"41 FF 14 24\" );\t\t\t// call qword [r12]\n\n\t// restore registers\n\tEmitString( \"48 8D 54 24\" );\t\t\t// lea rdx, [rsp+SHADOW_BASE]\n\tEmit1( SHADOW_BASE );\n\tEmitString( \"48 8B 32\" );\t\t\t\t// mov rsi, [rdx+00]\n\tEmitString( \"48 8B 7A 08\" );\t\t\t// mov rdi, [rdx+08]\n\tEmitString( \"4C 8B 42 10\" );\t\t\t// mov r8,  [rdx+16]\n\tEmitString( \"4C 8B 4A 18\" );\t\t\t// mov r9,  [rdx+24]\n\n\t// we added the return value: *(opstack+1) = eax\n\tEmitAddEDI4( vm );\t\t\t\t\t\t// add edi, 4\n\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\n\t// return stack\n\tEmitString( \"48 81 C4\" );\t\t\t\t// add rsp, 200\n\tEmit4( SHADOW_BASE + PUSH_STACK + PARAM_STACK );\n\n\tEmitRexString( \"8D 2C 33\" );\t\t\t// lea rbp, [rbx+rsi]\n\n\tEmitString( \"C3\" );\t\t\t\t\t\t// ret\n\n#else // i386\n\n\t// params = (int *)((byte *)currentVM->dataBase + programStack + 4);\n\tEmitString( \"8D 4D 04\" );\t\t\t\t// lea ecx, [ebp+4]\n\n\t// function prologue\n\tEmitString( \"55\" );\t\t\t\t\t\t// push ebp\n\tEmitRexString( \"89 E5\" );\t\t\t\t// mov ebp, esp\n\tEmitRexString( \"83 EC 04\" );\t\t\t// sub esp, 4\n\t// align stack before call\n\tEmitRexString( \"83 E4 F0\" );\t\t\t// and esp, -16\n\n\t// ABI note: esi/edi must not change during call!\n\n\t// currentVM->programStack = programStack - 4;\n\tEmitString( \"8D 56 FC\" );\t\t\t\t// lea edx, [esi-4]\n\tEmitString( \"89 15\" );\t\t\t\t\t// mov [&vm->programStack], edx \n\tEmitPtr( &vm->programStack );\n\n\t// params[0] = syscallNum\n\tEmitString( \"89 01\" );\t\t\t\t\t// mov [ecx], eax\n\n\t// cdecl - set params\n\tEmitString( \"89 0C 24\" );\t\t\t\t// mov [esp], ecx\n\t\n\t// currentVm->systemCall( param );\n\tEmitString( \"FF 15\" );\t\t\t\t\t// call dword ptr [&currentVM->systemCall]\n\tEmitPtr( &vm->systemCall );\n\t\n\t// we added the return value: *(opstack+1) = eax\n#if 0\n\tEmitAddEDI4( vm );\t\t\t\t\t\t// add edi, 4\n\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov [edi], eax\n#else // break dependency from edi value?\n\tEmitString( \"89 47 04\" );\t\t\t\t// mov [edi+4], eax\n\tEmitAddEDI4( vm );\t\t\t\t\t\t// add edi, 4\n#endif\n\n\t// function epilogue\n\tEmitRexString( \"89 EC\" );\t\t\t\t// mov esp, ebp\n\tEmitString( \"5D\" );\t\t\t\t\t\t// pop ebp\n\tEmitString( \"C3\" );\t\t\t\t\t\t// ret\n#endif\n}\n\n\nstatic void EmitFTOLFunc( vm_t *vm )\n{\n\tEmitRexString( \"B8\" );\t\t// mov eax, &fp_cw[0]\n\tEmitPtr( &fp_cw[0] );\t\t\n\tEmitString( \"9B D9 38\" );\t// fnstcw word ptr [eax]\n\tEmitString( \"D9 68 04\" );\t// fldcw word ptr [eax+4]\n\tEmitString( \"DB 1F\" );\t\t// fistp dword ptr [edi]\n\tEmitString( \"D9 28\" );\t\t// fldcw word ptr [eax]\n\tEmitString( \"C3\" );\t\t\t// ret\n}\n\n\nstatic void EmitBCPYFunc( vm_t *vm )\n{\n\t// FIXME: range check\n\tEmitString( \"56\" );\t\t\t\t\t\t// push esi\n\tEmitString( \"57\" );\t\t\t\t\t\t// push edi\n\tEmitString( \"8B 37\" );\t\t\t\t\t// mov esi,[edi] \n\tEmitString( \"8B 7F FC\" );\t\t\t\t// mov edi,[edi-4] \n\tEmitString( \"B8\" );\t\t\t\t\t\t// mov eax, datamask\n\tEmit4( vm->dataMask );\n\tEmitString( \"21 C6\" );\t\t\t\t\t// and esi, eax\n\tEmitString( \"21 C7\" );\t\t\t\t\t// and edi, eax\n#if idx64\n\tEmitString( \"48 01 DE\" );\t\t\t\t// add rsi, rbx\n\tEmitString( \"48 01 DF\" );\t\t\t\t// add rdi, rbx\n#else\n\tEmitString( \"03 F3\" );\t\t\t\t\t// add esi, ebx\n\tEmitString( \"03 FB\" );\t\t\t\t\t// add edi, ebx\n#endif\n\tEmitString( \"F3 A5\" );\t\t\t\t\t// rep movsd\n\tEmitString( \"5F\" );\t\t\t\t\t\t// pop edi\n\tEmitString( \"5E\" );\t\t\t\t\t\t// pop esi\n\tEmitCommand( LAST_COMMAND_SUB_DI_8 );\t// sub edi, 8\n\tEmitString( \"C3\" );\t\t\t\t\t\t// ret\n}\n\n\nstatic void EmitPSOFFunc( vm_t *vm )\n{\n\tEmitRexString( \"B8\" );\t\t\t// mov eax, badStackPtr\n\tEmitPtr( &badStackPtr );\n\tEmitString( \"FF 10\" );\t\t\t// call [eax]\n\tEmitString( \"C3\" );\t\t\t\t// ret\n}\n\n\nstatic void EmitOSOFFunc( vm_t *vm )\n{\n\tEmitRexString( \"B8\" );\t\t\t// mov eax, badOptackPtr\n\tEmitPtr( &badOpStackPtr );\n\tEmitString( \"FF 10\" );\t\t\t// call [eax]\n\tEmitString( \"C3\" );\t\t\t\t// ret\n}\n\n\nstatic void EmitBADJFunc( vm_t *vm )\n{\n\tEmitRexString( \"B8\" );\t\t\t// mov eax, badJumpPtr\n\tEmitPtr( &badJumpPtr );\n\tEmitString( \"FF 10\" );\t\t\t// call [eax]\n\tEmitString( \"C3\" );\t\t\t\t// ret\n}\n\n\nstatic void EmitERRJFunc( vm_t *vm )\n{\n\tEmitRexString( \"B8\" );\t\t\t// mov eax, errJumpPtr\n\tEmitPtr( &errJumpPtr );\n\tEmitString( \"FF 10\" );\t\t\t// call [eax]\n\tEmitString( \"C3\" );\t\t\t\t// ret\n}\n\n\nstatic void EmitDATAFunc( vm_t *vm )\n{\n\tEmitRexString( \"B8\" );\t\t\t// mov eax, badDataPtr\n\tEmitPtr( &badDataPtr );\n\tEmitString( \"FF 10\" );\t\t\t// call [eax]\n\tEmitString( \"C3\" );\t\t\t\t// ret\n}\n\n\nstatic void EmitNCPYFunc( vm_t *vm )\n{\n\tstatic int Lend, Lcopy, Lpadz, Lpop0, Lpop1; // jump labels\n\tint n;\n\n\t//EmitString( \"8B 4D 10\" );\t// mov ecx, dword ptr [ebp+16] // counter\n\tEmitString( \"89 C1\" );\t\t// mov ecx, eax // get cached value from previous OP_ARG instruction\n\tEmitString( \"85 C9\" );\t\t// test ecx, ecx\n\tEmitString( \"74\" );\t\t\t// je +Lend\n\tEmit1( Lend );\tLend = compiledOfs;\n\tEmitString( \"57\" );\t\t\t// push edi\n\tEmitString( \"8B 55 0C\" );\t// mov edx, dword ptr [ebp+12] // source\n\tEmitString( \"8B 7D 08\" );\t// mov edi, dword ptr [ebp+08] // destination\n\tEmitRexString( \"01 DA\" );\t// add edx, ebx // + vm->dataBase\n\n#if 0\n\tif ( vm->forceDataMask )\n\t{\n#ifdef idx64\n\t\tEmitString( \"44 21 CF\" );\t// and edi, r9d\n#else\n\t\tEmitString( \"81 E7\" );\t\t// and edi, vm->dataMask\n\t\tEmit4( vm->dataMask );\n#endif\n\t\tEmitRexString( \"01 DF\" );\t// add edi, ebx // + vm->dataBase\n\t}\n\telse\n#endif\n\tif ( (int)vm_rtChecks.value & 8 ) // security checks\n\t{\n\t\tEmitString( \"89 F8\" );\t\t// mov eax, edi\n\t\tEmitString( \"09 C8\" );\t\t// or eax, ecx\n\t\tEmitString( \"3D\" );\t\t\t// cmp eax, vm->dataMask\n\t\tEmit4( vm->dataMask );\n\t\tEmitString( \"0F 87\" );\t\t// ja +errorFunction\n\t\tn = funcOffset[FUNC_DATA] - compiledOfs;\n\t\tEmit4( n - 6 );\n\t\tEmitString( \"8D 04 0F\" );\t// lea eax, dword ptr [edi + ecx]\n\t\tEmitRexString( \"01 DF\" );\t// add edi, ebx // + vm->dataBase\n\t\tEmitString( \"3D\" );\t\t\t// cmp eax, vm->dataMask\n\t\tEmit4( vm->dataMask );\n\t\tEmitString( \"0F 87\" );\t\t// ja +errorFunction\n\t\tn = funcOffset[FUNC_DATA] - compiledOfs;\n\t\tEmit4( n - 6 );\n\t}\n\telse\n\t{\n\t\tEmitRexString( \"01 DF\" );\t// add edi, ebx // + vm->dataBase\n\t}\n\nLcopy = compiledOfs - Lcopy;\n\tEmitString( \"8A 02\" );\t\t// mov al, dword ptr [edx]\n\tEmitString( \"88 07\" );\t\t// mov dword ptr [edi], al\n\tEmitRexString( \"83 C2 01\" );// add edx, 1\n\tEmitRexString( \"83 C7 01\" );// add edi, 1\n\tEmitRexString( \"84 C0\" );\t// test al, al\n\tEmitString( \"74\" );\t\t\t// je +Lpadz\n\tEmit1( Lpadz );\tLpadz = compiledOfs;\n\tEmitString( \"83 E9 01\" );\t// sub ecx, 1\n\tEmitString( \"75\" );\t\t\t// jne +Lcopy\n\tEmit1( Lcopy );\tLcopy = compiledOfs;\n\tEmitString( \"5F\" );\t\t\t// pop edi\n\tEmitString( \"C3\" );\t\t\t// ret\nLpadz = compiledOfs - Lpadz;\n\tEmitString( \"85 C9\" );\t\t// test ecx, ecx\n\tEmitString( \"74\" );\t\t\t// je +Lpop0\n\tEmit1( Lpop0 );\tLpop0 = compiledOfs;\n#if 0\n\t// zero only one char\n\tEmitString( \"31 C0\" );\t\t// xor eax, eax\n\tEmitString( \"88 07\" );\t\t// mov dword ptr [edi], al\n#else\n\t// zero all remaining chars\n\tEmitString( \"83 E9 01\" );\t// sub ecx, 1\n\tEmitString( \"74\" );\t\t\t// je +Lpop1\n\tEmit1( Lpop1 );\tLpop1 = compiledOfs;\n\tEmitString( \"89 CA\" );\t\t// mov edx, ecx\n\tEmitString( \"C1 E9 02\" );\t// shr ecx, 2\n\tEmitString( \"31 C0\" );\t\t// xor eax, eax\n\tEmitString( \"83 E2 03\" );\t// and edx, 3\n\tEmitString( \"F3 AB\" );\t\t// rep stosd\n\tEmitString( \"89 D1\" );\t\t// mov ecx, edx\n\t//EmitString( \"83 E1 03\" );\t// and ecx, 3\n\tEmitString( \"F3 AA\" );\t\t// rep stosb\nLpop1 = compiledOfs - Lpop1;\n#endif\nLpop0 = compiledOfs - Lpop0;\n\tEmitString( \"5F\" );\t\t\t// pop edi\nLend = compiledOfs - Lend;\n\tEmitString( \"C3\" );\t\t\t// ret\n}\n\n\n/*\n=================\nEmitFCalcEDI\n=================\n*/\nstatic void EmitFCalcEDI( int op )\n{\n\tswitch ( op )\n\t{\n\t\tcase OP_ADDF: EmitString( \"D8 07\" ); break; // fadd dword ptr [edi]\n\t\tcase OP_SUBF: EmitString( \"D8 27\" ); break;\t// fsub dword ptr [edi]\n\t\tcase OP_MULF: EmitString( \"D8 0F\" ); break; // fmul dword ptr [edi]\n\t\tcase OP_DIVF: EmitString( \"D8 37\" ); break; // fdiv dword ptr [edi]\n\t\tdefault: SV_Error( \"bad float op\" ); break;\n\t};\n}\n\n\n/*\n=================\nEmitFCalcPop\n=================\n*/\nstatic void EmitFCalcPop( int op )\n{\n\tswitch ( op )\n\t{\n\t\tcase OP_ADDF: EmitString( \"DE C1\" ); break; // faddp\n\t\tcase OP_SUBF: EmitString( \"DE E9\" ); break; // fsubp\n\t\tcase OP_MULF: EmitString( \"DE C9\" ); break; // fmulp\n\t\tcase OP_DIVF: EmitString( \"DE F9\" ); break; // fdivp\n\t\tdefault: SV_Error( \"bad opcode %02x\", op ); break;\n\t};\n}\n\n\n/*\n=================\nCommuteFloatOp\n=================\n*/\nstatic int CommuteFloatOp( int op ) \n{\n\tswitch ( op ) {\n\t\tcase OP_LEF: return OP_GEF;\n\t\tcase OP_LTF: return OP_GTF;\n\t\tcase OP_GEF: return OP_LEF;\n\t\tcase OP_GTF: return OP_LTF;\n\t\tdefault: return op;\n\t}\n}\n\n\n/*\n=================\nConstOptimize\n=================\n*/\nstatic qbool ConstOptimize( vm_t *vm ) \n{\n\tint v;\n\tint op1;\n\tqbool sign_extend;\n\n\top1 = ni->op;\n\n\tswitch ( op1 ) {\n\n\tcase OP_LOAD4:\n\t\tEmitAddEDI4( vm );\n\t\tif ( ISS8( ci->value ) ) {\n\t\t\tEmitString( \"8B 43\" );\t\t// mov eax, dword ptr [ebx+0x7F]\n\t\t\tEmit1( ci->value );\n\t\t} else {\n\t\t\tEmitString( \"8B 83\" );\t\t// mov eax, dword ptr [ebx+0x12345678]\n\t\t\tEmit4( ci->value );\n\t\t}\n\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax\n\t\tip += 1;\n\t\treturn true;\n\n\tcase OP_LOAD2:\n\t\tEmitAddEDI4( vm );\n\t\tsign_extend = ( (ci+2)->op == OP_SEX16 );\n\t\tif ( ISS8( ci->value ) ) {\n\t\t\tif ( sign_extend ) {\n\t\t\t\tEmitString( \"0F BF 43\" );\t// movsx eax, word ptr [ebx+0x7F]\n\t\t\t\tip += 1;\n\t\t\t} else {\n\t\t\t\tEmitString( \"0F B7 43\" );\t// movzx eax, word ptr [ebx+0x7F]\n\t\t\t}\n\t\t\tEmit1( ci->value );\n\t\t} else {\n\t\t\tif ( sign_extend ) {\n\t\t\t\tEmitString( \"0F BF 83\" );\t// movsx eax, word ptr [ebx+0x12345678]\n\t\t\t\tip += 1;\n\t\t\t} else {\n\t\t\t\tEmitString( \"0F B7 83\" );\t// movzx eax, word ptr [ebx+0x12345678]\n\t\t\t}\n\t\t\tEmit4( ci->value );\n\t\t}\n\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax\n\t\tip += 1;\n\t\treturn true;\n\n\tcase OP_LOAD1:\n\t\tEmitAddEDI4( vm );\n\t\tsign_extend = ( (ci+2)->op == OP_SEX8 );\n\t\tif ( ISS8( ci->value ) ) {\n\t\t\tif ( sign_extend ) {\n\t\t\t\tEmitString( \"0F BE 43\" );\t// movsx eax, byte ptr [ebx+0x7F]\n\t\t\t\tip += 1;\n\t\t\t} else {\n\t\t\t\tEmitString( \"0F B6 43\" );\t// movzx eax, byte ptr [ebx+0x7F]\n\t\t\t}\n\t\t\tEmit1( ci->value );\n\t\t} else {\n\t\t\tif ( sign_extend ) {\n\t\t\t\tEmitString( \"0F BE 83\" );\t// movsx eax, word ptr [ebx+0x12345678]\n\t\t\t\tip += 1;\n\t\t\t} else {\n\t\t\t\tEmitString( \"0F B6 83\" );\t// movzx eax, word ptr [ebx+0x12345678]\n\t\t\t}\n\t\t\tEmit4( ci->value );\n\t\t}\n\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax\n\t\tip += 1;\n\t\treturn true;\n\n\tcase OP_STORE4:\n\t\tEmitMovEAXEDI( vm );\n\t\tif ( !ci->value ) {\n\t\t\tEmitString( \"31 C9\" );\t\t// xor ecx, ecx\n\t\t} else {\n\t\t\tEmitString( \"B9\" );\t\t\t// mov\tecx, 0x12345678\n\t\t\tEmit4( ci->value );\n\t\t}\n\t\tEmitCheckReg( vm, REG_EAX, 4 );\n\t\tEmitString( \"89 0C 03\" );\t\t// mov dword ptr [ebx + eax], ecx\n\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t\t// sub edi, 4\n\t\tip += 1;\n\t\treturn true;\n\n\tcase OP_STORE2:\n\t\tEmitMovEAXEDI( vm );\n\t\tif ( !ci->value ) {\n\t\t\tEmitString( \"31 C9\" );\t\t// xor ecx, ecx\n\t\t} else {\n\t\t\tEmitString( \"B9\" );\t\t\t// mov\tecx, 0x12345678\n\t\t\tEmit4( ci->value );\n\t\t}\n\t\tEmitCheckReg( vm, REG_EAX, 2 );\n\t\tEmitString( \"66 89 0C 03\" );\t// mov word ptr [ebx + eax], cx\n\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 ); // sub edi, 4\n\t\tip += 1;\n\t\treturn true;\n\n\tcase OP_STORE1:\n\t\tEmitMovEAXEDI( vm );\n\t\tif ( !ci->value ) {\n\t\t\tEmitString( \"31 C9\" );\t\t// xor ecx, ecx\n\t\t} else {\n\t\t\tEmitString( \"B9\" );\t\t\t// mov\tecx, 0x12345678\n\t\t\tEmit4( ci->value );\n\t\t}\n\t\tEmitCheckReg( vm, REG_EAX, 1 );\n\t\tEmitString( \"88 0C 03\" );\t\t// mov byte ptr [ebx + eax], cl\n\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\tip += 1;\n\t\treturn true;\n\t\n\tcase OP_ADD:\n\t\tv = ci->value;\n\t\tEmitMovEAXEDI( vm ); \n\t\tif ( ISS8( v ) ) {\n\t\t\tEmitString( \"83 C0\" );\t// add eax, 0x7F\n\t\t\tEmit1( v );\n\t\t} else {\n\t\t\tEmitString( \"05\" );\t\t// add eax, 0x12345678\n\t\t\tEmit4( v );\n\t\t}\n\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );\n\t\tip += 1; // OP_ADD\n\t\treturn true;\n\n\tcase OP_SUB:\n\t\tv = ci->value;\n\t\tEmitMovEAXEDI( vm );\n\t\tif ( ISS8( v ) ) {\n\t\t\tEmitString( \"83 E8\" );\t// sub eax, 0x7F\n\t\t\tEmit1( v );\n\t\t} else {\n\t\t\tEmitString( \"2D\" );\t\t// sub eax, 0x12345678\n\t\t\tEmit4( v );\n\t\t}\n\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );\n\t\tip += 1;\n\t\treturn true;\n\n\tcase OP_MULI:\n\t\tv = ci->value;\n\t\tEmitMovEAXEDI( vm );\n\t\tif ( ISS8( v ) ) {\n\t\t\tEmitString( \"6B C0\" );\t// imul eax, 0x7F\n\t\t\tEmit1( v );\n\t\t} else {\n\t\t\tEmitString( \"69 C0\" );\t// imul eax, 0x12345678\n\t\t\tEmit4( v );\n\t\t}\n\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );\n\t\tip += 1;\n\t\treturn true;\n\n\tcase OP_MULF:\n\tcase OP_DIVF:\n\tcase OP_ADDF:\n\tcase OP_SUBF:\n\t\tv = ci->value;\n\t\tEmitLoadFloatEDI( vm );\n\t\tif ( HasSSEFP() ) {\n\t\t\tEmitString( \"C7 45 00\" );\t\t\t// mov dword ptr [ebp], v\n\t\t\tEmit4( v );\n\t\t\tEmitString( \"F3 0F 10 4D 00\" );\t\t// movss xmm1, dword ptr [ebp]\n\t\t\tswitch( op1 ) {\n\t\t\t\tcase OP_ADDF: EmitString( \"0F 58 C1\" ); break;\t// addps xmm0, xmm1\n\t\t\t\tcase OP_SUBF: EmitString( \"0F 5C C1\" ); break;\t// subps xmm0, xmm1\n\t\t\t\tcase OP_MULF: EmitString( \"0F 59 C1\" ); break;\t// mulps xmm0, xmm1\n\t\t\t\tcase OP_DIVF: EmitString( \"0F 5E C1\" ); break;\t// divps xmm0, xmm1\n\t\t\t}\n\t\t} else {\n\t\t\tEmitString( \"C7 45 00\" );\t// mov dword ptr [ebp], 0x12345678\n\t\t\tEmit4( v );\n\t\t\tEmitString( \"D9 45 00\" );\t// fld dword ptr [ebp]\n\t\t\tEmitFCalcPop( op1 );\t\t// fmulp/fdivp/faddp/fsubp\n\t\t}\n\t\tEmitCommand( LAST_COMMAND_STORE_FLOAT_EDI );\n\t\tip +=1;\n\t\treturn true;\n\n\tcase OP_LSH:\n\t\tv = ci->value;\n\t\tif ( v < 0 || v > 31 )\n\t\t\tbreak;\n\t\tEmitMovEAXEDI( vm );\n\t\tEmitString( \"C1 E0\" );\t// shl eax, 0x12\n\t\tEmit1( v );\n\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );\n\t\tip += 1; // OP_LSH\n\t\treturn true;\n\n\tcase OP_RSHI:\n\t\tv = ci->value;\n\t\tif ( v < 0 || v > 31 )\n\t\t\tbreak;\n\t\tEmitMovEAXEDI( vm );\n\t\tEmitString( \"C1 F8\" );\t// sar eax, 0x12\n\t\tEmit1( v );\n\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );\n\t\tip += 1;\n\t\treturn true;\n\n\tcase OP_RSHU:\n\t\tv = ci->value;\n\t\tif ( v < 0 || v > 31 )\n\t\t\tbreak;\n\t\tEmitMovEAXEDI( vm );\n\t\tEmitString( \"C1 E8\" );\t// shr eax, 0x12\n\t\tEmit1( v );\n\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );\n\t\tip += 1;\n\t\treturn true;\n\n\tcase OP_BAND:\n\t\tv = ci->value;\n\t\tEmitMovEAXEDI( vm );\n\t\tif ( ISU8( v ) ) {\n\t\t\tEmitString( \"83 E0\" ); // and eax, 0x7F\n\t\t\tEmit1( v );\n\t\t} else {\n\t\t\tEmitString( \"25\" ); // and eax, 0x12345678\n\t\t\tEmit4( v );\n\t\t}\n\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );\n\t\tip += 1;\n\t\treturn true;\n\n\tcase OP_BOR:\n\t\tv = ci->value;\n\t\tEmitMovEAXEDI( vm );\n\t\tif ( ISU8( v ) ) {\n\t\t\tEmitString( \"83 C8\" ); // or eax, 0x7F\n\t\t\tEmit1( v );\n\t\t} else {\n\t\t\tEmitString( \"0D\" );    // or eax, 0x12345678\n\t\t\tEmit4( v );\n\t\t}\n\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );\n\t\tip += 1;\n\t\treturn true;\n\n\tcase OP_BXOR:\n\t\tv = ci->value;\n\t\tEmitMovEAXEDI( vm );\n\t\tif ( ISU8( v ) ) {\n\t\t\tEmitString( \"83 F0\" );\t// xor eax, 0x7F\n\t\t\tEmit1( v );\n\t\t} else {\n\t\t\tEmitString( \"35\" );\t\t// xor eax, 0x12345678\n\t\t\tEmit4( v );\n\t\t}\n\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );\n\t\tip += 1;\n\t\treturn true;\n\n\tcase OP_JUMP:\n\t\tEmitJump( vm, ni, ni->op, ci->value );\n\t\tip += 1; // OP_JUMP\n\t\treturn true;\n\n\tcase OP_CALL:\n#ifdef VM_LOG_SYSCALLS\n\t\t// [dataBase + programStack + 0] = ip;\n\t\tEmitString( \"C7 45 00\" );\t// mov dword ptr [ebp], 0x12345678\n\t\tEmit4( ip );\n#endif\n\t\tv = ci->value;\n\t\t// try to inline some syscalls\n\t\tif ( HasSSEFP() && v == ~g_sqrt ) {\n\t\t\t// inline SSE implementation of sin/cos is too problematic...\n\t\t\tEmitString( \"F3 0F 10 45 08\" );\t\t// movss xmm0, dword ptr [ebp + 8]\n\t\t\tEmitAddEDI4( vm );\n\t\t\tEmitString( \"F3 0F 51 C0\" );\t\t// sqrtss xmm0, xmm0\n\t\t\tEmitCommand( LAST_COMMAND_STORE_FLOAT_EDI );\n\t\t\tip += 1;\n\t\t\treturn true;\n\t\t} else if ( v == ~g_sin || v == ~g_cos || v == ~g_sqrt ) {\n\t\t\tEmitString( \"D9 45 08\" );\t\t// fld dword ptr [ebp + 8]\n\t\t\tswitch ( v ) {\n\t\t\t\tcase ~g_sqrt: EmitString( \"D9 FA\" ); break; // fsqrt\n\t\t\t\tcase ~g_sin: EmitString( \"D9 FE\" ); break;  // fsin\n\t\t\t\tcase ~g_cos: EmitString( \"D9 FF\" ); break;  // fcos\n\t\t\t}\n\t\t\tEmitAddEDI4( vm );\t\t\t\t\t\t\t\t// add edi, 4\n\t\t\tEmitCommand( LAST_COMMAND_STORE_FLOAT_EDI_X87 );// fstp dword ptr[edi]\n\t\t\tip += 1;\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( v < 0 ) // syscall\n\t\t{\n\t\t\tEmitString( \"B8\" );\t\t// mov eax, 0x12345678\n\t\t\tEmit4( ~v );\n\t\t\tEmitCallOffset( FUNC_SYSC );\n\t\t\tLastCommand = LAST_COMMAND_MOV_EAX_EDI_CALL;\n\t\t\tip += 1; // OP_CALL\n\t\t\treturn true;\n\t\t}\n\t\tEmitString( \"55\" );\t// push ebp\n\t\tEmitString( \"56\" );\t// push rsi\n\t\tEmitString( \"53\" );\t// push rbx\n\t\tEmitCallAddr( vm, v ); // call +addr\n\t\tEmitString( \"5B\" );\t// pop rbx\n\t\tEmitString( \"5E\" );\t// pop rsi\n\t\tEmitString( \"5D\" );\t// pop ebp\n\t\tip += 1; // OP_CALL\n\t\treturn true;\n\n\tcase OP_EQF:\n\tcase OP_NEF:\n\tcase OP_LTF:\n\tcase OP_LEF:\n\tcase OP_GTF:\n\tcase OP_GEF:\n\t\tif ( !HasFCOM() )\n\t\t\treturn false;\n\t\tEmitLoadFloatEDI( vm );\n\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\n\t\tv = ci->value;\n\t\tif ( HasSSEFP() ) {\n\t\t\tif ( v == 0 ) {\n\t\t\t\tEmitString( \"0F 57 C9\" );\t\t\t// xorps xmm1, xmm1\n\t\t\t} else {\n\t\t\t\tEmitString( \"C7 45 00\" );\t\t\t// mov dword ptr [ebp], v\n\t\t\t\tEmit4( v );\n\t\t\t\tEmitString( \"F3 0F 10 4D 00\" );\t\t// movss xmm1, dword ptr [ebp]\n\t\t\t}\n\t\t\tEmitString( \"0F 2F C1\" );\t\t\t\t// comiss xmm0, xmm1\n\t\t\tEmitJump( vm, ni, ni->op, ni->value );\n\t\t} else {\n\t\t\tif ( v == 0 ) {\n\t\t\t\tEmitString( \"D9 EE\" );\t\t// fldz\n\t\t\t} else {\n\t\t\t\tEmitString( \"C7 45 00\" );\t// mov [ebp], 0x12345678\n\t\t\t\tEmit4( v );\n\t\t\t\tEmitString( \"D9 45 00\" );\t// fld dword ptr [ebp]\n\t\t\t}\n\t\t\tEmitString( \"DF E9\" );\t\t// fucomip\n\t\t\tEmitString( \"DD D8\" );\t\t// fstp st(0)\n\t\t\tEmitJump( vm, ni, CommuteFloatOp( ni->op ), ni->value );\n\t\t}\n\t\tip +=1;\n\t\treturn true;\n\n\tcase OP_EQ:\n\tcase OP_NE:\n\tcase OP_GEI:\n\tcase OP_GTI:\n\tcase OP_GTU:\n\tcase OP_GEU:\n\tcase OP_LTU:\n\tcase OP_LEU:\n\tcase OP_LEI:\n\tcase OP_LTI:\n\t\tEmitMovEAXEDI( vm );\n\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\n\t\tv = ci->value;\n\t\tif ( v == 0 && ( op1 == OP_EQ || op1 == OP_NE ) ) {\n\t\t\tEmitString( \"85 C0\" );       // test eax, eax\n\t\t} else {\n\t\t\tif ( ISS8( v ) ) {\n\t\t\t\tEmitString( \"83 F8\" );   // cmp eax, 0x7F\n\t\t\t\tEmit1( v );\n\t\t\t} else {\n\t\t\t\tEmitString( \"3D\" );      // cmp eax, 0xFFFFFFFF\n\t\t\t\tEmit4( v );\n\t\t\t}\n\t\t}\n\t\tEmitJump( vm, ni, ni->op, ni->value );\n\t\tip += 1; \n\t\treturn true;\n\n\tdefault:\n\t\tbreak;\n\t}\n\n\treturn false;\n}\n\n/*\n=================\nVM_FindMOps\n\nSearch for known macro-op sequences\n=================\n*/\nstatic void VM_FindMOps( instruction_t *buf, int instructionCount )\n{\n\tint n, v, op0;\n\tinstruction_t *i;\n\t\n\ti = buf;\n\tn = 0;\n\n\twhile ( n < instructionCount )\n\t{\n\t\top0 = i->op;\n\t\tif ( op0 == OP_LOCAL ) {\n\t\t\t// OP_LOCAL + OP_LOCAL + OP_LOAD4 + OP_CONST + OP_XXX + OP_STORE4\n\t\t\tif ( (i+1)->op == OP_LOCAL && i->value == (i+1)->value && (i+2)->op == OP_LOAD4 && (i+3)->op == OP_CONST && (i+4)->op != OP_UNDEF && (i+5)->op == OP_STORE4 ) {\n\t\t\t\tv = (i+4)->op;\n\t\t\t\tif ( v == OP_ADD ) {\n\t\t\t\t\ti->op = MOP_ADD4;\n\t\t\t\t\ti += 6; n += 6;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif ( v == OP_SUB ) {\n\t\t\t\t\ti->op = MOP_SUB4;\n\t\t\t\t\ti += 6; n += 6;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif ( v == OP_BAND ) {\n\t\t\t\t\ti->op = MOP_BAND4;\n\t\t\t\t\ti += 6; n += 6;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif ( v == OP_BOR ) {\n\t\t\t\t\ti->op = MOP_BOR4;\n\t\t\t\t\ti += 6; n += 6;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// skip useless sequences\n\t\t\tif ( (i+1)->op == OP_LOCAL && (i+0)->value == (i+1)->value && (i+2)->op == OP_LOAD4 && (i+3)->op == OP_STORE4 ) {\n\t\t\t\ti->op = MOP_IGNORE4;\n\t\t\t\ti += 4; n += 4;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else if ( op0 == OP_CONST && (i+1)->op == OP_CALL && (i+2)->op == OP_POP && i >= buf+6 && (i-1)->op == OP_ARG && !i->jused ) {\n\t\t\t// some void function( arg1, arg2, arg3 )\n\t\t\tif ( i->value == ~g_strlcpy ) {\n\t\t\t\ti->op = MOP_NCPY;\n\t\t\t\ti += 3; n += 3;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\ti++;\n\t\tn++;\n\t}\n}\n\n\n/*\n=================\nEmitMOPs\n=================\n*/\nstatic qbool EmitMOPs( vm_t *vm, int op ) \n{\n\tint v, n;\n\tswitch ( op ) \n\t{\n\t\t//[local] += CONST\n\t\tcase MOP_ADD4:\n\t\t\tn = inst[ip+2].value;\n\t\t\tv = ci->value; // local variable address\n\t\t\tif ( ISS8( n ) ) {\n\t\t\t\tif ( ISS8( v ) ) {\n\t\t\t\t\tEmitString( \"83 45\" );\t// add dword ptr [ebp + 0x7F], 0x12\n\t\t\t\t\tEmit1( v );\n\t\t\t\t\tEmit1( n );\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"83 85\" );\t// add dword ptr [ebp + 0x12345678], 0x12\n\t\t\t\t\tEmit4( v );\n\t\t\t\t\tEmit1( n );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif ( ISS8( v ) ) {\n\t\t\t\t\tEmitString( \"81 45\" );\t// add dword ptr [ebp + 0x7F], 0x12345678\n\t\t\t\t\tEmit1( v );\n\t\t\t\t\tEmit4( n );\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"81 85\" );\t// add dword ptr [ebp + 0x12345678], 0x12345678\n\t\t\t\t\tEmit4( v );\n\t\t\t\t\tEmit4( n );\n\t\t\t\t}\n\t\t\t}\n\t\t\tip += 5;\n\t\t\treturn true;\n\n\t\t//[local] -= CONST\n\t\tcase MOP_SUB4:\n\t\t\tn = inst[ip+2].value;\n\t\t\tv = ci->value; // local variable address\n\t\t\tif ( ISS8( n ) ) {\n\t\t\t\tif ( ISS8( v ) ) {\n\t\t\t\t\tEmitString( \"83 6D\" );\t// sub dword ptr [ebp + 0x7F], 0x12\n\t\t\t\t\tEmit1( v );\n\t\t\t\t\tEmit1( n );\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"83 AD\" );\t// sub dword ptr [ebp + 0x12345678], 0x12\n\t\t\t\t\tEmit4( v );\n\t\t\t\t\tEmit1( n );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif ( ISS8( v ) ) {\n\t\t\t\t\tEmitString( \"81 6D\" );\t// sub dword ptr [ebp + 0x7F], 0x12345678\n\t\t\t\t\tEmit1( v );\n\t\t\t\t\tEmit4( n );\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"81 AD\" );\t// sub dword ptr[esi+0x12345678], 0x12345678\n\t\t\t\t\tEmit4( v );\n\t\t\t\t\tEmit4( n );\n\t\t\t\t}\n\t\t\t}\n\t\t\tip += 5;\n\t\t\treturn true;\n\n\t\t//[local] &= CONST\n\t\tcase MOP_BAND4:\n\t\t\tn = inst[ip+2].value;\n\t\t\tv = ci->value; // local variable address\n\t\t\tif ( ISS8( n ) ) {\n\t\t\t\tif ( ISS8( v ) ) {\n\t\t\t\t\tEmitString( \"83 65\" );\t// and dword ptr [ebp + 0x7F], 0x12\n\t\t\t\t\tEmit1( v );\n\t\t\t\t\tEmit1( n );\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"83 A5\" );\t// and dword ptr [ebp + 0x12345678], 0x12\n\t\t\t\t\tEmit4( v );\n\t\t\t\t\tEmit1( n );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif ( ISS8( v ) ) {\n\t\t\t\t\tEmitString( \"81 65\" );\t// and dword ptr [ebp + 0x7F], 0x12345678\n\t\t\t\t\tEmit1( v );\n\t\t\t\t\tEmit4( n );\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"81 A5\" );\t// and dword ptr [ebp + 0x12345678], 0x12345678\n\t\t\t\t\tEmit4( v );\n\t\t\t\t\tEmit4( n );\n\t\t\t\t}\n\t\t\t}\n\t\t\tip += 5;\n\t\t\treturn true;\n\n\t\t//[local] |= CONST\n\t\tcase MOP_BOR4:\n\t\t\tn = inst[ip+2].value;\n\t\t\tv = ci->value; // local variable address\n\t\t\tif ( ISS8( n ) ) {\n\t\t\t\tif ( ISS8( v ) ) {\n\t\t\t\t\tEmitString( \"83 4D\" );\t// or dword ptr [ebp + 0x7F], 0x12\n\t\t\t\t\tEmit1( v );\n\t\t\t\t\tEmit1( n );\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"83 8D\" );\t// or dword ptr [ebp + 0x12345678], 0x12\n\t\t\t\t\tEmit4( v );\n\t\t\t\t\tEmit1( n );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif ( ISS8( v ) ) {\n\t\t\t\t\tEmitString( \"81 4D\" );\t// or dword ptr [ebp + 0x7F], 0x12345678\n\t\t\t\t\tEmit1( v );\n\t\t\t\t\tEmit4( n );\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"81 8D\" );\t// or dword ptr [ebp + 0x12345678], 0x12345678\n\t\t\t\t\tEmit4( v );\n\t\t\t\t\tEmit4( n );\n\t\t\t\t}\n\t\t\t}\n\t\t\tip += 5;\n\t\t\treturn true;\n\n\t\t// [local] = [local]\n\t\tcase MOP_IGNORE4:\n\t\t\tip += 3;\n\t\t\treturn true;\n\n\t\t// const + call + pop\n\t\tcase MOP_NCPY:\n\t\t\tEmitCallOffset( FUNC_NCPY );\n\t\t\tip += 2;\n\t\t\treturn true;\n\n\t};\n\treturn false;\n}\n\n/*\n=================\nVM_Compile\n=================\n*/\nqbool VM_Compile( vm_t *vm, vmHeader_t *header ) {\n\tconst char *errMsg;\n\tint     instructionCount;\n\tint\t\tproc_base;\n\tint\t\tproc_len;\n\tint\t\ti, n, v;\n\tqbool wantres;\n\n    Sys_GetProcessorId(NULL);\n\tinst = (instruction_t*)Q_malloc( (header->instructionCount + 8 ) * sizeof( instruction_t ) );\n\tinstructionOffsets = (int*)Q_malloc( header->instructionCount * sizeof( int ) );\n\n\terrMsg = VM_LoadInstructions( (byte *) header + header->codeOffset, header->codeLength, header->instructionCount, inst );\n\tif ( !errMsg ) {\n\t\terrMsg = VM_CheckInstructions( inst, vm->instructionCount, vm->jumpTableTargets, vm->numJumpTableTargets, vm->exactDataLength );\n\t}\n\tif ( errMsg ) {\n\t\tVM_FreeBuffers();\n\t\tCon_Printf( \"VM_CompileX86 error: %s\\n\", errMsg );\n\t\treturn false;\n\t}\n\t\n\t//VM_ReplaceInstructions( vm, inst );\n\n\tVM_FindMOps( inst, vm->instructionCount );\n\n\tcode = NULL; // we will allocate memory later, after last defined pass\n\tinstructionPointers = NULL;\n\n\tmemset( funcOffset, 0, sizeof( funcOffset ) );\n\n\tinstructionCount = header->instructionCount;\n\n\tfor( pass = 0; pass < NUM_PASSES; pass++ ) \n\t{\n__compile:\n\tpop1 = OP_UNDEF;\n\tlastConst = 0;\n\n\t// translate all instructions\n\tip = 0;\n\tcompiledOfs = 0;\n\tLastCommand = LAST_COMMAND_NONE;\n\n\tproc_base = -1;\n\tproc_len = 0;\n\t\n#if idx64\n\tEmitString( \"53\" );\t\t\t\t// push rbx\n\tEmitString( \"56\" );\t\t\t\t// push rsi\n\tEmitString( \"57\" );\t\t\t\t// push rdi\n\tEmitString( \"55\" );\t\t\t\t// push rbp\n\tEmitString( \"41 54\" );\t\t\t// push r12\n\tEmitString( \"41 55\" );\t\t\t// push r13\n\tEmitString( \"41 56\" );\t\t\t// push r14\n\tEmitString( \"41 57\" );\t\t\t// push r15\n\n\tEmitRexString( \"BB\" );\t\t\t// mov rbx, vm->dataBase\n\tEmitPtr( vm->dataBase );\n\n\tEmitString( \"49 B8\" );\t\t\t// mov r8, vm->instructionPointers\n\tEmitPtr( instructionPointers );\n\n\tEmitString( \"49 C7 C1\" );\t\t// mov r9, vm->dataMask\n\tEmit4( vm->dataMask );\n\n\tEmitString( \"49 BC\" );\t\t\t// mov r12, vm->systemCall\n\tEmitPtr( &vm->systemCall );\n\n\tEmitString( \"49 C7 C5\" );\t\t// mov r13, vm->stackBottom\n\tEmit4( vm->stackBottom );\n\n\tEmitRexString( \"B8\" );\t\t\t// mov rax, &vm->programStack\n\tEmitPtr( &vm->programStack );\n\tEmitString( \"8B 30\" );\t\t\t// mov esi, [rax]\n\n\tEmitRexString( \"B8\" );\t\t\t// mov rax, &vm->opStack\n\tEmitPtr( &vm->opStack );\n\tEmitRexString( \"8B 38\" );\t\t// mov rdi, [rax]\n\n\tEmitRexString( \"B8\" );\t\t\t// mov rax, &vm->opStackTop\n\tEmitPtr( &vm->opStackTop );\n\tEmitString( \"4C 8B 30\" );\t\t// mov r14, [rax]\n\n#else\n\tEmitString( \"60\" );\t\t\t\t// pushad\n\n\tEmitRexString( \"BB\" );\t\t\t// mov ebx, vm->dataBase\n\tEmitPtr( vm->dataBase );\n\n\tEmitString( \"8B 35\" );\t\t\t// mov esi, [vm->programStack]\n\tEmitPtr( &vm->programStack );\n\t\n\tEmitString( \"8B 3D\" );\t\t\t// mov edi, [vm->opStack]\n\tEmitPtr( &vm->opStack );\n#endif\n\n\tEmitCallOffset( FUNC_ENTR );\n\n#if idx64\n\n#ifdef DEBUG_VM\n\tEmitRexString( \"B8\" );\t\t\t// mov rax, &vm->programStack\n\tEmitPtr( &vm->programStack );\n\tEmitString( \"89 30\" );\t\t\t// mov [rax], esi\n#endif\n\n\tEmitRexString( \"B8\" );\t\t\t// mov rax, &vm->opStack\n\tEmitPtr( &vm->opStack );\n\tEmitRexString( \"89 38\" );\t\t// mov [rax], rdi\n\n\tEmitString( \"41 5F\" );\t\t\t// pop r15\n\tEmitString( \"41 5E\" );\t\t\t// pop r14\n\tEmitString( \"41 5D\" );\t\t\t// pop r13\n\tEmitString( \"41 5C\" );\t\t\t// pop r12\n\tEmitString( \"5D\" );\t\t\t\t// pop rbp\n\tEmitString( \"5F\" );\t\t\t\t// pop rdi\n\tEmitString( \"5E\" );\t\t\t\t// pop rsi\n\tEmitString( \"5B\" );\t\t\t\t// pop rbx\n#else\n\n#ifdef DEBUG_VM\n\tEmitString( \"89 35\" );\t\t\t// [vm->programStack], esi\n\tEmitPtr( &vm->programStack );\n#endif\n\n\tEmitString( \"89 3D\" );\t\t\t// [vm->opStack], edi\n\tEmitPtr( &vm->opStack );\n\n\tEmitString( \"61\" );\t\t\t\t// popad\n#endif\n\t\n\tEmitString( \"C3\" );\t\t\t\t// ret\n\t\n\tEmitAlign( 4 );\n\n\t // main function entry offset\n\tfuncOffset[FUNC_ENTR] = compiledOfs;\n\t\n\twhile ( ip < instructionCount )\n\t{\n\t\tinstructionOffsets[ ip ] = compiledOfs;\n\n\t\tci = &inst[ ip ];\n\t\tni = &inst[ ip + 1 ];\n\t\tip++;\n\n\t\tif ( ci->jused ) {\n\t\t\tLastCommand = LAST_COMMAND_NONE;\n\t\t\tpop1 = OP_UNDEF;\n\t\t}\n\t\n\t\tswitch ( ci->op ) {\n\n\t\tcase OP_UNDEF:\n\t\tcase OP_IGNORE:\n\t\t\tbreak;\n\n\t\tcase OP_BREAK:\n\t\t\tEmitString( \"CC\" );\t\t// int 3\n\t\t\tbreak;\n\n\t\tcase OP_ENTER:\n\t\t\tv = ci->value;\n\t\t\tif ( ISU8( v ) ) {\n\t\t\t\tEmitString( \"83 EE\" );\t\t// sub\tesi, 0x12\n\t\t\t\tEmit1( v );\n\t\t\t} else {\n\t\t\t\tEmitString( \"81 EE\" );\t\t// sub\tesi, 0x12345678\n\t\t\t\tEmit4( v );\n\t\t\t}\n\n\t\t\t// locate endproc\n\t\t\tfor ( n = -1, i = ip + 1; i < instructionCount; i++ ) {\n\t\t\t\tif ( inst[ i ].op == OP_PUSH && inst[ i + 1 ].op == OP_LEAVE ) {\n\t\t\t\t\tn = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// should never happen because equal check in VM_LoadInstructions() but anyway\n\t\t\tif ( n == -1 ) {\n\t\t\t\tVM_FreeBuffers();\n\t\t\t\tCon_Printf( \"VM_CompileX86 error: %s\\n\", \"missing proc end\" );\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tproc_base = ip + 1;\n\t\t\tproc_len = n - proc_base + 1 ;\n\n\t\t\t// programStack overflow check\n\t\t\tif ( (int)vm_rtChecks.value & 1 ) {\n#if idx64\n\t\t\t\tEmitString( \"4C 39 EE\" );\t\t// cmp\trsi, r13\n#else\n\t\t\t\tEmitString( \"81 FE\" );\t\t\t// cmp\tesi, vm->stackBottom\n\t\t\t\tEmit4( vm->stackBottom );\n#endif\n\t\t\t\tEmitString( \"0F 82\" );\t\t\t// jb +funcOffset[FUNC_PSOF]\n\t\t\t\tn = funcOffset[FUNC_PSOF] - compiledOfs;\n\t\t\t\tEmit4( n - 6 );\n\t\t\t}\n\n\t\t\t// opStack overflow check\n\t\t\tif ( (int)vm_rtChecks.value & 2 ) {\n\t\t\t\tif ( ISU8( ci->opStack ) ) {\n\t\t\t\t\tEmitRexString( \"8D 47\" );\t\t// lea eax, [edi+0x7F]\n\t\t\t\t\tEmit1( ci->opStack );\n\t\t\t\t} else {\n\t\t\t\t\tEmitRexString( \"8D 87\" );\t\t// lea eax, [edi+0x12345678]\n\t\t\t\t\tEmit4( ci->opStack );\n\t\t\t\t}\n#if idx64\n\t\t\t\tEmitString( \"4C 39 F0\" );\t\t// cmp rax, r14\n#else\n\t\t\t\tEmitString( \"3B 05\" );\t\t\t// cmp eax, [&vm->opStackTop]\n\t\t\t\tEmitPtr( &vm->opStackTop );\n#endif\n\t\t\t\tEmitString( \"0F 87\" );\t\t\t// ja +funcOffset[FUNC_OSOF]\n\t\t\t\tn = funcOffset[FUNC_OSOF] - compiledOfs;\n\t\t\t\tEmit4( n - 6 );\n\t\t\t}\n\n\t\t\tEmitRexString( \"8D 2C 33\" );\t\t// lea ebp, [ebx+esi]\n\t\t\tbreak;\n\n\t\tcase OP_CONST:\n\t\t\t\n\t\t\t// we can safely perform optimizations only in case if \n\t\t\t// we are 100% sure that next instruction is not a jump label\n\t\t\tif ( !ni->jused && ConstOptimize( vm ) )\n\t\t\t\tbreak;\n\n\t\t\tEmitAddEDI4( vm );\t\t\t\t\t// add edi, 4\n\t\t\tEmitString( \"C7 07\" );\t\t\t\t// mov dword ptr [edi], 0x12345678\n\t\t\tlastConst = ci->value;\n\t\t\tEmit4( lastConst );\n\t\t\tLastCommand = LAST_COMMAND_MOV_EDI_CONST;\n\t\t\tbreak;\n\n\t\tcase OP_LOCAL:\n\t\t\t// optimization: merge OP_LOCAL + OP_LOAD4\n\t\t\tif ( ni->op == OP_LOAD4 ) {\n\t\t\t\tEmitAddEDI4( vm );\t\t\t\t// add edi, 4\n\t\t\t\tv = ci->value;\n\t\t\t\tif ( ISU8( v ) ) {\n\t\t\t\t\tEmitString( \"8B 45\" );\t\t// mov eax, dword ptr [ebp + 0x7F]\n\t\t\t\t\tEmit1( v );\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"8B 85\" );\t\t// mov eax, dword ptr [ebp + 0x12345678]\n\t\t\t\t\tEmit4( v );\n\t\t\t\t}\n\t\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax\n\t\t\t\tip++;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// optimization: merge OP_LOCAL + OP_LOAD2\n\t\t\tif ( ni->op == OP_LOAD2 ) {\n\t\t\t\tEmitAddEDI4( vm );\t\t\t\t// add edi, 4\n\t\t\t\tv = ci->value;\n\t\t\t\tif ( ISU8( v ) ) {\n\t\t\t\t\tEmitString( \"0F B7 45\" );\t// movzx eax, word ptr [ebp + 0x7F]\n\t\t\t\t\tEmit1( v );\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"0F B7 85\" );\t// movzx eax, word ptr [ebp + 0x12345678]\n\t\t\t\t\tEmit4( v );\n\t\t\t\t} \n\t\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax\n\t\t\t\tip++;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// optimization: merge OP_LOCAL + OP_LOAD1\n\t\t\tif ( ni->op == OP_LOAD1 ) {\n\t\t\t\tEmitAddEDI4( vm );\t\t\t\t// add edi, 4\n\t\t\t\tv = ci->value;\n\t\t\t\tif ( ISU8( v ) ) {\n\t\t\t\t\tEmitString( \"0F B6 45\" );\t// movzx eax, byte ptr [ebp + 0x7F]\n\t\t\t\t\tEmit1( v );\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"0F B6 85\" );\t// movzx eax, byte ptr [ebp + 0x12345678]\n\t\t\t\t\tEmit4( v );\n\t\t\t\t}\n\t\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX ); // mov dword ptr [edi], eax\n\t\t\t\tip++;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// TODO: i = j + k;\n\t\t\t// TODO: i = j - k;\n\n\t\t\tEmitAddEDI4( vm );\t\t\t\t\t// add edi, 4\n\t\t\tv = ci->value;\n\t\t\tif ( ISU8( v ) ) {\n\t\t\t\tEmitString( \"8D 46\" );\t\t\t// lea eax, [esi + 0x7F]\n\t\t\t\tEmit1( v );\n\t\t\t} else {\n\t\t\t\tEmitString( \"8D 86\" );\t\t\t// lea eax, [esi + 0x12345678]\n\t\t\t\tEmit4( v );\n\t\t\t}\n\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\tbreak;\n\n\t\tcase OP_ARG:\n\t\t\tif ( LastCommand == LAST_COMMAND_STORE_FLOAT_EDI_SSE ) {\n\t\t\t\tREWIND(4);\n\t\t\t\tv = ci->value;\n\t\t\t\tif ( ISU8( v ) ) {\n\t\t\t\t\tEmitString( \"F3 0F 11 45\" ); // movss dword ptr [ebp + 0x7F], xmm0\n\t\t\t\t\tEmit1( v );\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"F3 0F 11 85\" ); // movss dword ptr [ebp + 0x12345678], xmm0\n\t\t\t\t\tEmit4( v );\n\t\t\t\t}\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t// mov\teax, dword ptr [edi]\n\t\t\tif ( (ci+1)->op == MOP_NCPY && !(ci+1)->jused ) {\n\t\t\t\t// we will read counter from eax\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tv = ci->value;\n\t\t\tif ( ISU8( v ) ) {\n\t\t\t\tEmitString( \"89 45\" );\t\t\t\t// mov\tdword ptr [ebp + 0x7F], eax\n\t\t\t\tEmit1( v );\n\t\t\t} else {\n\t\t\t\tEmitString( \"89 85\" );\t\t\t\t// mov\tdword ptr [ebp + 0x12345678], eax\n\t\t\t\tEmit4( v );\n\t\t\t}\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\tbreak;\n\n\t\tcase OP_CALL:\n#ifdef VM_LOG_SYSCALLS\n\t\t\t// [dataBase + programStack + 0] = ip-1;\n\t\t\tEmitString( \"C7 45 00\" );\t\t\t\t// mov dword ptr [ebp], 0x12345678\n\t\t\tEmit4( ip-1 );\n#endif\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t// mov eax, dword ptr [edi]\n\t\t\tEmitCallOffset( FUNC_CALL );\t\t\t// call +FUNC_CALL\n\t\t\tbreak;\n\n\t\tcase OP_PUSH:\n\t\t\tEmitAddEDI4( vm );\t\t\t\t\t\t// add edi, 4\n\t\t\tbreak;\n\n\t\tcase OP_POP:\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\tbreak;\n\n\t\tcase OP_LEAVE:\n#ifdef DEBUG_VM\n\t\t\tv = ci->value;\n\t\t\tif ( ISU8( v ) ) {\n\t\t\t\tEmitString( \"83 C6\" );\t\t// add\tesi, 0x12\n\t\t\t\tEmit1( v );\n\t\t\t} else {\n\t\t\t\tEmitString( \"81 C6\" );\t\t// add\tesi, 0x12345678\n\t\t\t\tEmit4( v );\n\t\t\t}\n#endif\n\t\t\tEmitString( \"C3\" );\t\t\t\t\t\t\t// ret\n\t\t\tbreak;\n\n\t\tcase OP_LOAD4:\n\t\t\tif ( LastCommand == LAST_COMMAND_MOV_EDI_EAX ) {\n\t\t\t\tREWIND( 2 );\n\t\t\t\tEmitCheckReg( vm, REG_EAX, 4 );\t\t\t// range check eax\n\t\t\t\tEmitString( \"8B 04 03\" );\t\t\t\t// mov\teax, dword ptr [ebx + eax]\n\t\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tEmitMovECXEDI( vm );\t\t\t\t\t\t// mov ecx, dword ptr [edi]\t\t\n\t\t\tEmitCheckReg( vm, REG_ECX, 4 );\t\t\t\t// range check ecx\n\t\t\tEmitString( \"8B 04 0B\" );\t\t\t\t\t// mov\teax, dword ptr [ebx + ecx]\n\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );\t// mov dword ptr [edi], eax\n\t\t\tbreak;\n\n\t\tcase OP_LOAD2:\n\t\t\tif ( LastCommand == LAST_COMMAND_MOV_EDI_EAX ) {\n\t\t\t\tREWIND( 2 );\n\t\t\t\tEmitCheckReg( vm, REG_EAX, 2 );\t\t\t// range check eax\n\t\t\t\tif ( ni->op == OP_SEX16 ) {\n\t\t\t\t\tEmitString( \"0F BF 04 03\" );\t\t// movsx eax, word ptr [ebx + eax]\n\t\t\t\t\tip++;\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"0F B7 04 03\" );\t\t// movzx eax, word ptr [ebx + eax]\n\t\t\t\t}\n\t\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tEmitMovECXEDI( vm );\t\t\t\t\t\t// mov ecx, dword ptr [edi]\n\t\t\tEmitCheckReg( vm, REG_ECX, 2 );\t\t\t\t// range check ecx\n\t\t\tif ( ni->op == OP_SEX16 ) {\n\t\t\t\tEmitString( \"0F BF 04 0B\" );\t\t\t// movsx eax, word ptr [ebx + ecx]\n\t\t\t\tip++;\n\t\t\t} else {\n\t\t\t\tEmitString( \"0F B7 04 0B\" );\t\t\t// movzx eax, word ptr [ebx + ecx]\n\t\t\t}\n\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );\t// mov dword ptr [edi], eax\n\t\t\tbreak;\n\n\t\tcase OP_LOAD1:\n\t\t\tif ( LastCommand == LAST_COMMAND_MOV_EDI_EAX ) {\n\t\t\t\tREWIND( 2 );\n\t\t\t\tEmitCheckReg( vm, REG_EAX, 1 );\t\t\t// range check eax\n\t\t\t\tif ( ni->op == OP_SEX8 ) {\n\t\t\t\t\tEmitString( \"0F BE 04 03\" );\t\t// movsx eax, byte ptr [ebx + eax]\n\t\t\t\t\tip++;\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"0F B6 04 03\" );\t\t// movzx eax, byte ptr [ebx + eax]\n\t\t\t\t}\n\t\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tEmitMovECXEDI( vm );\t\t\t\t\t\t// mov ecx, dword ptr [edi]\n\t\t\tEmitCheckReg( vm, REG_ECX, 1 );\t\t\t\t// range check ecx\n\t\t\tif ( ni->op == OP_SEX8 ) {\n\t\t\t\tEmitString( \"0F BE 04 0B\" );\t\t\t// movsx eax, byte ptr [ebx + ecx]\n\t\t\t\tip++;\n\t\t\t} else {\n\t\t\t\tEmitString( \"0F B6 04 0B\" );\t\t\t// movzx eax, byte ptr [ebx + ecx]\n\t\t\t}\n\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );\t// mov dword ptr [edi], eax\n\t\t\tbreak;\n\n\t\tcase OP_STORE4:\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t\t// mov eax, dword ptr [edi]\n\t\t\tEmitString( \"8B 4F FC\" );\t\t\t\t\t// mov ecx, dword ptr [edi-4]\n\t\t\tEmitCheckReg( vm, REG_ECX, 4 );\t\t\t\t// range check\n\t\t\tEmitString( \"89 04 0B\" );\t\t\t\t\t// mov dword ptr [ebx + ecx], eax\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_8 );\t\t// sub edi, 8\n\t\t\tbreak;\n\n\t\tcase OP_STORE2:\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t\t// mov eax, dword ptr [edi]\t\n\t\t\tEmitString( \"8B 4F FC\" );\t\t\t\t\t// mov ecx, dword ptr [edi-4]\n\t\t\tEmitCheckReg( vm, REG_ECX, 2 );\t\t\t\t// range check\n\t\t\tEmitString( \"66 89 04 0B\" );\t\t\t\t// mov word ptr [ebx + ecx], ax\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_8 );\t\t// sub edi, 8\n\t\t\tbreak;\n\n\t\tcase OP_STORE1:\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t\t// mov eax, dword ptr [edi]\t\n\t\t\tEmitString( \"8B 4F FC\" );\t\t\t\t\t// mov ecx, dword ptr [edi-4]\n\t\t\tEmitCheckReg( vm, REG_ECX, 1 );\t\t\t\t// range check\n\t\t\tEmitString( \"88 04 0B\" );\t\t\t\t\t// mov byte ptr [ebx + ecx], al\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_8 );\t\t// sub edi, 8\n\t\t\tbreak;\n\n\t\tcase OP_EQ:\n\t\tcase OP_NE:\n\t\tcase OP_LTI:\n\t\tcase OP_LEI:\n\t\tcase OP_GTI:\n\t\tcase OP_GEI:\n\t\tcase OP_LTU:\n\t\tcase OP_LEU:\n\t\tcase OP_GTU:\n\t\tcase OP_GEU:\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t// mov eax, dword ptr [edi]\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_8 );\t// sub edi, 8\n\t\t\tEmitString( \"39 47 04\" );\t\t\t\t// cmp dword ptr [edi+4], eax\n\t\t\tEmitJump( vm, ci, ci->op, ci->value );\n\t\t\tbreak;\n\n\t\tcase OP_EQF:\n\t\tcase OP_NEF:\n\t\tcase OP_LTF:\n\t\tcase OP_LEF:\n\t\tcase OP_GTF:\n\t\tcase OP_GEF:\n\t\t\tif ( HasFCOM() ) {\n\t\t\t\tEmitLoadFloatEDI( vm );\n\t\t\t\tif ( HasSSEFP() ) {\n\t\t\t\t\tEmitString( \"F3 0F 10 4F FC\" );\t\t\t// movss xmm1, dword ptr [edi-4]\n\t\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_8 );\t// sub edi, 8\n\t\t\t\t\tEmitString( \"0F 2F C8\" );\t\t\t\t// comiss xmm1, xmm0\n\t\t\t\t} else {\n\t\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_8 );\t// sub edi, 8\n\t\t\t\t\t//EmitString( \"D9 47 08\" );\t\t\t\t// fld dword ptr [edi+8]\n\t\t\t\t\tEmitString( \"D9 47 04\" );\t\t\t\t// fld dword ptr [edi+4]\n\t\t\t\t\tEmitString( \"DF E9\" );\t\t\t\t\t// fucomip\n\t\t\t\t\tEmitString( \"DD D8\" );\t\t\t\t\t// fstp st(0)\n\t\t\t\t}\n\t\t\t\tEmitJump( vm, ci, ci->op, ci->value );\n\t\t\t} else {\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_8 );\t// sub edi, 8\n\t\t\t\tEmitString( \"D9 47 04\" );\t\t\t\t// fld dword ptr [edi+4]\n\t\t\t\tEmitString( \"D8 5F 08\" );\t\t\t\t// fcomp dword ptr [edi+8]\n\t\t\t\tEmitString( \"DF E0\" );\t\t\t\t\t// fnstsw ax\n\t\t\t\tEmitFloatJump( vm, ci, ci->op, ci->value );\n\t\t\t}\n\t\t\tpop1 = OP_UNDEF;\n\t\t\tbreak;\t\t\t\n\n\t\tcase OP_NEGI:\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t// mov eax, dword ptr [edi]\n\t\t\tEmitString( \"F7 D8\" );\t\t\t\t\t// neg eax\n\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\tbreak;\n\n\t\tcase OP_ADD:\n\t\t\twantres = ( ops[ ni->op ].stack <= 0 );\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t\t// mov eax, dword ptr [edi]\n\t\t\tif ( wantres ) {\n\t\t\t\tEmitString( \"03 47 FC\" );\t\t\t\t// add eax, dword ptr [edi-4]\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\t} else {\n\t\t\t\tEmitString( \"01 47 FC\" );\t\t\t\t// add dword ptr [edi-4],eax\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OP_SUB:\n\t\t\twantres = ( ops[ ni->op ].stack <= 0 );\n\t\t\tif ( wantres ) {\n\t\t\t\tEmitMovECXEDI( vm );\t\t\t\t\t// mov ecx,dword ptr [edi]\n\t\t\t\tEmitString( \"8B 47 FC\" );\t\t\t\t// mov eax,dword ptr [edi-4]\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t\tEmitString( \"29 C8\" );\t\t\t\t\t// sub eax, ecx\n\t\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\t} else {\n\t\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t// mov eax, dword ptr [edi]\n\t\t\t\tEmitString( \"29 47 FC\" );\t\t\t\t// sub dword ptr [edi-4],eax\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OP_DIVI:\n\t\t\twantres = ( ops[ ni->op ].stack <= 0 );\n\t\t\tEmitMovECXEDI( vm );\t\t\t\t\t// mov ecx,dword ptr [edi]\n\t\t\tEmitString( \"8B 47 FC\" );\t\t\t\t// mov eax,dword ptr [edi-4]\n\t\t\tif ( wantres ) {\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t\tEmitString( \"99\" );\t\t\t\t\t\t// cdq\n\t\t\t\tEmitString( \"F7 F9\" );\t\t\t\t\t// idiv ecx\n\t\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\t} else{\n\t\t\t\tEmitString( \"99\" );\t\t\t\t\t\t// cdq\n\t\t\t\tEmitString( \"F7 F9\" );\t\t\t\t\t// idiv ecx\n\t\t\t\tEmitString( \"89 47 FC\" );\t\t\t\t// mov dword ptr [edi-4],eax\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OP_DIVU:\n\t\t\tEmitString( \"8B 47 FC\" );\t\t\t\t// mov eax,dword ptr [edi-4]\n\t\t\tEmitString( \"33 D2\" );\t\t\t\t\t// xor edx, edx\n\t\t\tEmitString( \"F7 37\" );\t\t\t\t\t// div dword ptr [edi]\n\t\t\tEmitString( \"89 47 FC\" );\t\t\t\t// mov dword ptr [edi-4],eax\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\tbreak;\n\n\t\tcase OP_MODI:\n\t\t\tEmitString( \"8B 47 FC\" );\t\t\t\t// mov eax,dword ptr [edi-4]\n\t\t\tEmitString( \"99\" );\t\t\t\t\t\t// cdq\n\t\t\tEmitString( \"F7 3F\" );\t\t\t\t\t// idiv dword ptr [edi]\n\t\t\tEmitString( \"89 57 FC\" );\t\t\t\t// mov dword ptr [edi-4],edx\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\tbreak;\n\n\t\tcase OP_MODU:\n\t\t\tEmitString( \"8B 47 FC\" );\t\t\t\t// mov eax,dword ptr [edi-4]\n\t\t\tEmitString( \"33 D2\" );\t\t\t\t\t// xor edx, edx\n\t\t\tEmitString( \"F7 37\" );\t\t\t\t\t// div dword ptr [edi]\n\t\t\tEmitString( \"89 57 FC\" );\t\t\t\t// mov dword ptr [edi-4],edx\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\tbreak;\n\n\t\tcase OP_MULI:\n\t\t\twantres = ( ops[ ni->op ].stack <= 0 );\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t// mov eax, dword ptr [edi]\n\t\t\tEmitString( \"F7 6F FC\" );\t\t\t\t// imul eax, dword ptr [edi-4]\n\t\t\tif ( wantres ) {\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\t} else {\n\t\t\t\tEmitString( \"89 47 FC\" );\t\t\t\t// mov dword ptr [edi-4],eax\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OP_MULU:\n\t\t\tEmitString( \"8B 47 FC\" );\t\t\t\t// mov eax,dword ptr [edi-4]\n\t\t\tEmitString( \"F7 27\" );\t\t\t\t\t// mul dword ptr [edi]\n\t\t\tEmitString( \"89 47 FC\" );\t\t\t\t// mov dword ptr [edi-4],eax\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\tbreak;\n\n\t\tcase OP_BAND:\n\t\t\twantres = ( ops[ ni->op ].stack <= 0 );\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t\t// mov eax, dword ptr [edi]\n\t\t\tif ( wantres ) {\n\t\t\t\tEmitString( \"23 47 FC\" );\t\t\t\t// and eax, dword ptr [edi-4]\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\t} else {\n\t\t\t\tEmitString( \"21 47 FC\" );\t\t\t\t// and dword ptr [edi-4],eax\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OP_BOR:\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t// mov eax, dword ptr [edi]\n\t\t\tEmitString( \"09 47 FC\" );\t\t\t\t// or dword ptr [edi-4],eax\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\tbreak;\n\n\t\tcase OP_BXOR:\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t// mov eax, dword ptr [edi]\n\t\t\tEmitString( \"31 47 FC\" );\t\t\t\t// xor dword ptr [edi-4],eax\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\tbreak;\n\n\t\tcase OP_BCOM:\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t// mov eax, dword ptr [edi]\n\t\t\tEmitString( \"F7 D0\" );\t\t\t\t\t// not eax\n\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\tbreak;\n\n\t\tcase OP_LSH:\n\t\t\tEmitMovECXEDI( vm );\t\t\t\t\t// mov ecx, dword ptr [edi]\n\t\t\tEmitString( \"D3 67 FC\" );\t\t\t\t// shl dword ptr [edi-4], cl\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\tbreak;\n\n\t\tcase OP_RSHI:\n\t\t\tEmitMovECXEDI( vm );\t\t\t\t\t// mov ecx, dword ptr [edi]\n\t\t\tEmitString( \"D3 7F FC\" );\t\t\t\t// sar dword ptr [edi-4], cl\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\tbreak;\n\n\t\tcase OP_RSHU:\n\t\t\tEmitMovECXEDI( vm );\t\t\t\t\t// mov ecx, dword ptr [edi]\n\t\t\tEmitString( \"D3 6F FC\" );\t\t\t\t// shr dword ptr [edi-4], cl\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\tbreak;\n\n\t\tcase OP_NEGF:\n\t\t\t//if ( !ci->fpu )\n\t\t\tEmitLoadFloatEDI( vm );\t\t\t\t\t// fld dword ptr [edi] | movss xmm0, dword ptr [edi]\n\t\t\tif ( HasSSEFP() ) {\n\t\t\t\tEmitString( \"0F 57 C9\" );\t\t\t// xorps xmm1, xmm1\n\t\t\t\tEmitString( \"0F 5C C8\" );\t\t\t// subps xmm1, xmm0\n\t\t\t\tEmitString( \"0F 28 C1\" );\t\t\t// movaps xmm0, xmm1\n\t\t\t} else {\n\t\t\t\tEmitString( \"D9 E0\" );\t\t\t\t// fchs\n\t\t\t}\n\t\t\t//if ( !ci->fpu || ci->store )\n\t\t\tEmitCommand( LAST_COMMAND_STORE_FLOAT_EDI ); // fstp dword ptr [edi]\n\t\t\tbreak;\n\n\t\tcase OP_ADDF:\n\t\tcase OP_SUBF:\n\t\tcase OP_DIVF:\n\t\tcase OP_MULF:\n\t\t\twantres = ( ops[ ni->op ].stack <= 0 );\n\t\t\tif ( HasSSEFP() ) {\n\t\t\t\tif ( LastCommand == LAST_COMMAND_STORE_FLOAT_EDI_SSE ) {\n\t\t\t\t\tREWIND(4);\n\t\t\t\t\tEmitString( \"0F 28 C8\" );\t\t// movaps xmm1, xmm0\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"F3 0F 10 0F\" );\t// movss xmm1, dword ptr [edi]\n\t\t\t\t}\n\t\t\t\tEmitString( \"F3 0F 10 47 FC\" );\t\t// movss xmm0, dword ptr [edi-4]\n\t\t\t\tif ( wantres ) {\n\t\t\t\t\tif ( ni->op == OP_STORE4 ) {\n\t\t\t\t\t\t EmitString( \"8B 47 F8\" );\t// mov eax, dword ptr [edi-8]\n\t\t\t\t\t\t EmitCheckReg( vm, REG_EAX, 4 );\n\t\t\t\t\t} else if ( ni->op == OP_ARG ) {\n\t\t\t\t\t\t//\n\t\t\t\t\t} else {\n\t\t\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tswitch( ci->op ) {\n\t\t\t\t\tcase OP_ADDF: EmitString( \"0F 58 C1\" ); break;\t// addps xmm0, xmm1\n\t\t\t\t\tcase OP_SUBF: EmitString( \"0F 5C C1\" ); break;\t// subps xmm0, xmm1\n\t\t\t\t\tcase OP_MULF: EmitString( \"0F 59 C1\" ); break;\t// mulps xmm0, xmm1\n\t\t\t\t\tcase OP_DIVF: EmitString( \"0F 5E C1\" ); break;\t// divps xmm0, xmm1\n\t\t\t\t}\n\t\t\t\tif ( wantres ) {\n\t\t\t\t\tif ( ni->op == OP_STORE4 ) {\n\t\t\t\t\t\tEmitString( \"F3 0F 11 04 03\" );\t\t// movss dword ptr [ebx + eax], xmm0\n\t\t\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_12 );\n\t\t\t\t\t\tpop1 = OP_UNDEF;\n\t\t\t\t\t\tip++; // OP_STORE4\n\t\t\t\t\t} else if ( ni->op == OP_ARG ) {\n\t\t\t\t\t\tv = ni->value;\n\t\t\t\t\t\tif ( ISU8( v ) ) {\n\t\t\t\t\t\t\tEmitString( \"F3 0F 11 45\" ); // movss dword ptr [ebp + 0x7F], xmm0\n\t\t\t\t\t\t\tEmit1( v );\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tEmitString( \"F3 0F 11 85\" ); // movss dword ptr [ebp + 0x12345678], xmm0\n\t\t\t\t\t\t\tEmit4( v );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_8 );\n\t\t\t\t\t\tpop1 = OP_UNDEF;\n\t\t\t\t\t\tip++; // OP_ARG\n\t\t\t\t\t} else {\n\t\t\t\t\t\tEmitCommand( LAST_COMMAND_STORE_FLOAT_EDI );\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"F3 0F 11 47 FC\" );\t\t\t// movss dword ptr [edi-4], xmm0\n\t\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tEmitString( \"D9 47 FC\" );\t\t\t// fld dword ptr [edi-4]\n\t\t\t\tEmitFCalcEDI( ci->op );\t\t\t\t// fadd|fsub|fmul|fdiv dword ptr [edi]\n\t\t\t\tEmitString( \"D9 5F FC\" );\t\t\t// fstp dword ptr [edi-4]\n\t\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OP_CVIF:\n\t\t\tif ( HasSSEFP() ) {\n\t\t\t\tif ( LastCommand == LAST_COMMAND_MOV_EDI_EAX ) {\n\t\t\t\t\tREWIND(2);\n\t\t\t\t\tEmitString( \"F3 0F 2A C0\" );\t// cvtsi2ss xmm0, eax\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"F3 0F 2A 07\" );\t// cvtsi2ss xmm0, dword ptr [edi]\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tEmitString( \"DB 07\" );\t\t\t\t// fild dword ptr [edi]\n\t\t\t}\n\t\t\t//if ( !ci->fpu ) \n\t\t\tEmitCommand( LAST_COMMAND_STORE_FLOAT_EDI );\n\t\t\tbreak;\n\n\t\tcase OP_CVFI:\n\t\t\tif ( HasSSEFP() ) {\n\t\t\t\t// assume that rounding mode in MXCSR is correctly set in 64-bit environment\n\t\t\t\tEmitLoadFloatEDI_SSE( vm );\t\t\t\t// movss xmm0, dword ptr [edi]\n\t\t\t\tEmitString( \"F3 0F 2C C0\" );\t\t\t// cvttss2si eax, xmm0\n\t\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\t} else {\n\t\t\t\tEmitLoadFloatEDI_X87( vm );\t\t\t// fld dword ptr [edi]\n\t\t\t\t// call the library conversion function\n\t\t\t\tEmitCallOffset( FUNC_FTOL );\t// call +FUNC_FTOL\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OP_SEX8:\n\t\t\tEmitString( \"0F BE 07\" );\t\t\t\t// movsx eax, byte ptr [edi]\n\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\tbreak;\n\n\t\tcase OP_SEX16:\n\t\t\tEmitString( \"0F BF 07\" );\t\t\t\t// movsx eax, word ptr [edi]\n\t\t\tEmitCommand( LAST_COMMAND_MOV_EDI_EAX );// mov dword ptr [edi], eax\n\t\t\tbreak;\n\n\t\tcase OP_BLOCK_COPY:\n\t\t\tEmitString( \"B9\" );\t\t\t\t\t\t// mov ecx, 0x12345678\n\t\t\tEmit4( ci->value >> 2 );\n\t\t\tEmitCallOffset( FUNC_BCPY );\n\t\t\tbreak;\n\n\t\tcase OP_JUMP:\n\t\t\tEmitMovEAXEDI( vm );\t\t\t\t\t// mov eax, dword ptr [edi]\n\t\t\tEmitCommand( LAST_COMMAND_SUB_DI_4 );\t// sub edi, 4\n\n\t\t\t// jump target range check\n\t\t\tif ( (int)vm_rtChecks.value & 4 ) {\n\t\t\t\tif ( proc_base != -1 ) {\n\t\t\t\t\t// allow jump within local function scope only\n\t\t\t\t\tEmitString( \"89 C2\" );\t\t\t// mov edx, eax\n\t\t\t\t\tif ( ISU8( proc_base ) ) {\n\t\t\t\t\t\tEmitString( \"83 EA\" );\t\t// sub edx, 0x7F\n\t\t\t\t\t\tEmit1( proc_base );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tEmitString( \"81 EA\" );\t\t// sub edx, 0x12345678\n\t\t\t\t\t\tEmit4( proc_base );\n\t\t\t\t\t}\n\t\t\t\t\tif ( ISU8( proc_len ) ) {\n\t\t\t\t\t\tEmitString( \"83 FA\" );\t\t// cmp edx, 0x7F\n\t\t\t\t\t\tEmit1( proc_len );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tEmitString( \"81 FA\" );\t\t// cmp edx, 0x12345678\n\t\t\t\t\t\tEmit4( proc_len );\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tEmitString( \"3D\" );\t\t\t\t// cmp eax, 0x12345678\n\t\t\t\t\tEmit4( vm->instructionCount );\n\t\t\t\t}\n\t\t\t\tEmitString( \"0F 83\" );\t\t\t\t// jae +funcOffset[FUNC_BADJ]\n\t\t\t\tn = funcOffset[FUNC_BADJ] - compiledOfs;\n\t\t\t\tEmit4( n - 6 );\n\t\t\t}\n#if idx64\n\t\t\tEmitString( \"41 FF 24 C0\" );\t\t\t// jmp dword ptr [r8 + rax*8]\n#else\n\t\t\tEmitString( \"FF 24 85\" );\t\t\t\t// jmp dword ptr [instructionPointers + eax * 4]\n\t\t\tEmitPtr( instructionPointers );\n#endif\n\t\t\tbreak;\n\n\t\tcase MOP_IGNORE4:\n\t\tcase MOP_ADD4:\n\t\tcase MOP_SUB4:\n\t\tcase MOP_BAND4:\n\t\tcase MOP_BOR4:\n\t\tcase MOP_NCPY:\n\t\t\tif ( !EmitMOPs( vm, ci->op ) )\n\t\t\t\tSV_Error( \"VM_CompileX86: bad opcode %02X\", ci->op );\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tSV_Error( \"VM_CompileX86: bad opcode %02X\", ci->op );\n\t\t\tVM_FreeBuffers();\n\t\t\treturn false;\n\t\t}\n\n\t\tpop1 = (opcode_t)ci->op;\n\t} // while( ip < header->instructionCount )\n\n\t\t// ****************\n\t\t// system functions\n\t\t// ****************\n\t\tEmitAlign( 8 );\n\t\tfuncOffset[FUNC_CALL] = compiledOfs;\n\t\tEmitCallFunc( vm );\n\n\t\tEmitAlign( 8 );\n\t\tfuncOffset[FUNC_FTOL] = compiledOfs;\n\t\tEmitFTOLFunc( vm );\n\n\t\tEmitAlign( 8 );\n\t\tfuncOffset[FUNC_BCPY] = compiledOfs;\n\t\tEmitBCPYFunc( vm );\n\n\t\tEmitAlign( 8 );\n\t\tfuncOffset[FUNC_NCPY] = compiledOfs;\n\t\tEmitNCPYFunc( vm );\n\n\t\t// ***************\n\t\t// error functions\n\t\t// ***************\n\n\t\t// bad jump\n\t\tEmitAlign( 8 );\n\t\tfuncOffset[FUNC_BADJ] = compiledOfs;\n\t\tEmitBADJFunc( vm );\n\n\t\t// error jump\n\t\tEmitAlign( 8 );\n\t\tfuncOffset[FUNC_ERRJ] = compiledOfs;\n\t\tEmitERRJFunc( vm );\n\n\t\t// programStack overflow\n\t\tEmitAlign( 8 );\n\t\tfuncOffset[FUNC_PSOF] = compiledOfs;\n\t\tEmitPSOFFunc( vm );\n\n\t\t// opStack overflow\n\t\tEmitAlign( 8 );\n\t\tfuncOffset[FUNC_OSOF] = compiledOfs;\n\t\tEmitOSOFFunc( vm );\n\n\t\t// read/write access violation\n\t\tEmitAlign( 8 );\n\t\tfuncOffset[FUNC_DATA] = compiledOfs;\n\t\tEmitDATAFunc( vm );\n\n\t\tEmitAlign( sizeof( intptr_t ) ); // for instructionPointers\n\n\t} // for( pass = 0; pass < n; pass++ )\n\n\tn = header->instructionCount * sizeof( intptr_t );\n\n\tif ( code == NULL ) {\n\t\tcode = (byte*)VM_Alloc_Compiled( vm, PAD(compiledOfs,8), n );\n\t\tif ( code == NULL ) {\n\t\t\treturn false;\n\t\t}\n\t\tinstructionPointers = (intptr_t*)(byte*)(code + PAD(compiledOfs,8));\n\t\t//vm->instructionPointers = instructionPointers; // for debug purposes?\n\t\tpass = NUM_PASSES-1; // repeat last pass\n\t\tgoto __compile;\n\t}\n\n\t// offset all the instruction pointers for the new location\n\tfor ( i = 0 ; i < header->instructionCount ; i++ ) {\n\t\tif ( !inst[i].jused ) {\n\t\t\tinstructionPointers[ i ] = (intptr_t)badJumpPtr;\n\t\t\tcontinue;\n\t\t}\n\t\tinstructionPointers[ i ] = (intptr_t)vm->codeBase.ptr + instructionOffsets[ i ];\n\t}\n\n\tVM_FreeBuffers();\n\n#ifdef VM_X86_MMAP\n\tif ( mprotect( vm->codeBase.ptr, vm->codeSize, PROT_READ|PROT_EXEC ) ) {\n\t\tVM_Destroy_Compiled( vm );\n\t\tCon_Printf( \"VM_CompileX86: mprotect failed\\n\" );\n\t\treturn false;\n\t}\n#elif _WIN32\n\t{\n\t\tDWORD oldProtect = 0;\n\t\t\n\t\t// remove write permissions.\n\t\tif ( !VirtualProtect( vm->codeBase.ptr, vm->codeSize, PAGE_EXECUTE_READ, &oldProtect ) ) {\n\t\t\tVM_Destroy_Compiled( vm );\n\t\t\tCon_Printf( \"VM_CompileX86: VirtualProtect failed\\n\" );\n\t\t\treturn false;\n\t\t}\n\t}\n#endif\n\n\tvm->destroy = VM_Destroy_Compiled;\n\n\tCon_Printf( \"VM file %s compiled to %i bytes of code\\n\", vm->name, compiledOfs );\n\n\treturn true;\n}\n/*\n=================\nVM_Alloc_Compiled\n=================\n*/\nstatic void *VM_Alloc_Compiled( vm_t *vm, int codeLength, int tableLength )\n{\n\tvoid\t*ptr;\n\tint\t\tlength;\n\n\tlength = codeLength + tableLength;\n#ifdef VM_X86_MMAP\n\tptr = mmap( NULL, length, PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0 );\n\tif ( ptr == MAP_FAILED ) {\n\t\tSV_Error( \"VM_CompileX86: mmap failed\" );\n\t\treturn NULL;\n\t}\n#elif _WIN32\n\t// allocate memory with EXECUTE permissions under windows.\n\tptr = VirtualAlloc( NULL, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE );\n\tif ( !ptr ) {\n\t\tSV_Error( \"VM_CompileX86: VirtualAlloc failed\" );\n\t\treturn NULL;\n\t}\n#else\n\tptr = malloc( length );\n\tif ( !ptr ) {\n\t\tSV_Error( \"VM_CompileX86: malloc failed\" );\n\t\treturn NULL;\n\t}\n#endif\n\tvm->codeBase.ptr = (byte*)ptr;\n\tvm->codeLength = codeLength;\n\tvm->codeSize = length;\n\n\treturn vm->codeBase.ptr;\n}\n\n\n/*\n==============\nVM_Destroy_Compiled\n==============\n*/\nstatic void VM_Destroy_Compiled( vm_t* vm )\n{\n#ifdef VM_X86_MMAP\n\tmunmap( vm->codeBase.ptr, vm->codeSize );\n#elif _WIN32\n\tVirtualFree( vm->codeBase.ptr, 0, MEM_RELEASE );\n#else\n\tfree( vm->codeBase.ptr );\n#endif\n\tvm->codeBase.ptr = NULL;\n}\n\n/*\n==============\nVM_CallCompiled\n\nThis function is called directly by the generated code\n==============\n*/\nint\tVM_CallCompiled( vm_t *vm, int nargs, int *args ) \n{\n\tint\t\topStack[MAX_OPSTACK_SIZE];\n\tunsigned int stackOnEntry;\n\tint\t\t*image;\n\tint\t\t*oldOpTop;\n\tint\t\ti;\n\n\t// we might be called recursively, so this might not be the very top\n\tstackOnEntry = vm->programStack;\n\toldOpTop = vm->opStackTop;\n\n\tvm->programStack -= (MAX_VMMAIN_CALL_ARGS+2)*4;\n\n\t// set up the stack frame \n\timage = (int*)( vm->dataBase + vm->programStack );\n\tfor ( i = 0; i < nargs; i++ ) {\n\t\timage[ i + 2 ] = args[ i ];\n\t}\n\n\timage[1] =  0;\t// return stack\n\timage[0] = -1;\t// will terminate loop on return\n\n\topStack[1] = 0;\n\n\tvm->opStack = opStack;\n\tvm->opStackTop = opStack + ARRAY_LEN( opStack ) - 1;\n\n\tvm->codeBase.func(); // go into generated code\n\n\tif ( vm->opStack != &opStack[1] ) {\n\t\tSV_Error( \"opStack corrupted in compiled code\" );\n\t}\n\n#ifdef DEBUG_VM\n\tif ( vm->programStack != stackOnEntry - CALL_PSTACK ) {\n\t\tSV_Error( \"programStack corrupted in compiled code\" );\n\t}\n#endif\n\n\tvm->programStack = stackOnEntry;\n\tvm->opStackTop = oldOpTop;\n\n\treturn vm->opStack[0];\n}\n#else\nint\tVM_CallCompiled( vm_t *vm, int nargs, int *args ) { return 0;}\nqbool VM_Compile( vm_t *vm, vmHeader_t *header ) { return false; }\n#endif\n#endif\t\t\t\t/* USE_PR2 */\n\n"
  },
  {
    "path": "src/vx_camera.c",
    "content": "/*\nCopyright (C) 2011 VULTUREIIC\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n//VULTUREIIC\n//Like so many other things, the camera features never got finished. I had in mind all kinds of neat things too\n\n\n#include \"quakedef.h\"\n#include \"vx_stuff.h\"\n\n\nvec3_t\tcamera_pos; //Where the camera is\nvec3_t\tcamera_angles; //Where the camera is looking\nvec3_t\tcamera_pos_finish; //Where we want the camera to stop\nvec3_t\tcamera_angles_finish; //Where we want the camera looking when we stop\nfloat\tcamera_speed; //Speed that the camera moves\n\ncameramode_t cameratype;\n\nvoid CameraRandomPoint (vec3_t org)\n{\n\t/* ?TONIK? WTF is cl.simorg */\n\tvec3_t stop, normal, dest, forward, right, up, vec;\n\n\tdest[0] = org[0] + (rand() % 500 - 250);\n\tdest[1] = org[1] + (rand() % 500 - 250);\n\tdest[2] = org[2] + (rand() % 100 + 50);\n\n\tCL_TraceLine (org, dest, stop, normal);\n\tVectorSubtract (org, stop, vec);\n\tAngleVectors (vec, forward, right, up);\n\tif (!VectorCompare (dest, stop)) {\n\t\tdest[0] = stop[0] + forward[0] * 15 + normal[0] * 15;\n\t\tdest[1] = stop[1] + forward[1] * 15 + normal[1] * 15;\n\t\tdest[2] = stop[2] + forward[2] * 15 + normal[2] * 15;\n\t}\n\n\tVectorCopy (dest, camera_pos);\n}\n\nvoid CameraUpdate (qbool dead)\n{\n\tvec3_t dest, destangles;\n\tif ((cls.demoplayback || cl.spectator) && cl_camera_tpp.integer == 1)\n\t\tcameratype = C_CHASECAM;\n\telse if ((cls.demoplayback || cl.spectator) && cl_camera_tpp.integer == 2)\n\t{\n\t\tif (cameratype == C_NORMAL)\n\t\t\tCameraRandomPoint (cl.simorg);\n\t\tcameratype = C_EXTERNAL;\n\t}\n\telse if (dead && (cls.demoplayback || cl.spectator) && amf_camera_death.value)\n\t{\n\t\tif (cameratype == C_NORMAL)\n\t\t\tCameraRandomPoint (cl.simorg);\n\t\tcameratype = C_EXTERNAL;\n\t}\n\telse\n\t\tcameratype = C_NORMAL;\n\tif (cameratype == C_NORMAL)\n\t{\n\t\tVectorCopy (cl.simorg, camera_pos);\n\t\tVectorCopy (cl.simangles, camera_angles);\n\t\treturn;\n\t}\n\n\t//CHASECAM\n\tif (cameratype == C_CHASECAM)\n\t{\n\t\tif (!dead)\n\t\t{\n\t\t\tfloat\tdist = amf_camera_chase_dist.value;\n\t\t\tfloat\theight = amf_camera_chase_height.value;\n\t\t\tint i;\n\t\t\tvec3_t\tforward, up, right, normal, impact;\n\t\t\t\n\t\t\tAngleVectors (cl.viewangles, forward, right, up);\n\t\t\tfor (i=0;i<3;i++)\n\t\t\t\tdest[i] = r_refdef.vieworg[i] + forward[i] * dist;\n\t\t\tdest[2] = dest[2] + height;\n\t\t\tCL_TraceLine (r_refdef.vieworg, dest, impact, normal);\n\t\t\tif (!VectorCompare(dest, impact))\n\t\t\t{\n\t\t\t\tdest[0] = impact[0] + forward[0] * 8 + normal[0] * 4;\n\t\t\t\tdest[1] = impact[1] + forward[1] * 8 + normal[1] * 4;\n\t\t\t\tdest[2] = impact[2] + forward[2] * 8 + normal[2] * 4;\n\t\t\t}\n\t\t\tVectorCopy(cl.simangles, destangles);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcameratype = C_EXTERNAL;\n\t\t\t//cameratype = C_NORMAL;\n\t\t\treturn;\n\t\t}\n\t}\n\telse if (cameratype == C_EXTERNAL)\n\t{\n\t\tvec3_t normal, impact, vec;\n\t\tCL_TraceLine (camera_pos, cl.simorg, impact, normal);\n\t\tif (!VectorCompare(cl.simorg, impact))\n\t\t\tCameraRandomPoint (cl.simorg);\n\t\tVectorSubtract(cl.simorg, camera_pos, vec);\n\t\tvectoangles(vec, destangles);\n\t\tdestangles[0] = -destangles[0];\n\t\tVectorCopy(camera_pos, dest);\n\t}\n\n\tVectorCopy(dest, r_refdef.vieworg);\n\tVectorCopy(destangles, r_refdef.viewangles);\n\tVectorCopy (dest, camera_pos);\n\tVectorCopy (destangles, camera_angles);\n}\n"
  },
  {
    "path": "src/vx_camera.h",
    "content": "/*\nCopyright (C) 2011 VultureIIC\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n//vx_camera.h for QWAMF\n//VultureIIC\n\nvoid Start_DeathCam(void);\nvoid Update_DeathCam(void);\nvoid End_DeathCam(void);\nextern cvar_t r_deathcam;\nextern cvar_t chase_back;\nextern cvar_t chase_up;\nextern cvar_t chase_active;\nextern cvar_t r_externcam;\nextern qbool chasecam_allowed;\n\nextern int externcam;\nextern vec3_t death_org;\n\n\n//Screen shaking\nextern vec3_t shakemag[1];\nextern cvar_t r_screenshaking;\n\nvoid ApplyRadialForce(vec3_t org, float power);\nvoid Update_ScreenShake (void);\nvoid Reset_ScreenShake (void);\nvoid Init_ScreenShake (void);\n"
  },
  {
    "path": "src/vx_coronas.c",
    "content": "/*\nCopyright (C) 2011 VULTUREIIC\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"vx_stuff.h\"\n#include \"glm_texture_arrays.h\"\n#include \"r_sprite3d.h\"\n#include \"r_state.h\"\n\n//fixme: move to header\nextern float bubblecolor[NUM_DLIGHTTYPES][4];\nstatic void CoronaStats(int change);\n\ntypedef struct corona_s\n{\n\tvec3_t origin;\n\tfloat scale;\n\tfloat growth;\n\tfloat die;\n\tvec3_t color;\n\tfloat alpha;\n\tfloat fade;\n\tcoronatype_t type;\n\tqbool sighted;\n\tqbool los; //to prevent having to trace a line twice\n\tint texture;\n\tstruct corona_s* next;\n\n\tint static_entity_id;\n\tint client_entity_id;\n} corona_t;\n\n//Tei: original whas 256, upped so whas low (?!) for some games\n#define MAX_CORONAS 300\n\nstatic corona_t r_corona[MAX_CORONAS];\nstatic corona_t* r_corona_by_tex[CORONATEX_COUNT];\n\n#define CORONA_SCALE 130\n#define CORONA_ALPHA 1\n\nvoid R_UpdateCoronas(void)\n{\n\tint i;\n\tcorona_t *c;\n\tfloat frametime = cls.frametime;\n\n\tmemset(r_corona_by_tex, 0, sizeof(r_corona_by_tex));\n\n\tCoronaStats(-CoronaCount);\n\tfor (i = 0; i < MAX_CORONAS; i++) {\n\t\tc = &r_corona[i];\n\n\t\tif (c->type == C_FREE) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (c->type == C_EXPLODE) {\n\t\t\tif (c->die < cl.time && c->texture != CORONATEX_EXPLOSIONFLASH7) {\n\t\t\t\tc->texture++;\n\t\t\t\tc->die = cl.time + 0.03;\n\t\t\t}\n\t\t}\n\n\t\t// First, check to see if it's time to die.\n\t\tif (!amf_coronas.integer || c->die < cl.time || c->scale <= 0 || c->alpha <= 0) {\n\t\t\t// Free this corona up.\n\t\t\tc->scale = 0;\n\t\t\tc->alpha = 0;\n\t\t\tc->type = C_FREE;\n\t\t\tc->sighted = false;\n\t\t\tc->client_entity_id = 0; // so can be reused\n\t\t\tc->static_entity_id = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\tCoronaStats(1);\n\t\tc->scale += c->growth * frametime;\n\t\tc->alpha += c->fade * frametime;\n\n\t\tc->next = r_corona_by_tex[c->texture];\n\t\tr_corona_by_tex[c->texture] = c;\n\n\t\tc->los = true;\n\t\tif (c->static_entity_id > 0) {\n\t\t\tc->los = (cl_static_entities[c->static_entity_id - 1].visframe >= r_framecount - 1);\n\t\t}\n\t\telse if (c->client_entity_id > 0) {\n\t\t\tc->los = (cl_entities[c->client_entity_id - 1].sequence == cl.validsequence);\n\t\t}\n\t\tif (c->los) {\n\t\t\tvec3_t impact = { 0,0,0 }, normal, temp;\n\n\t\t\tVectorCopy(c->origin, temp);\n\t\t\tif (c->type == C_FIRE) {\n\t\t\t\ttemp[2] += 6;\n\t\t\t}\n\t\t\tCL_TraceLine(r_refdef.vieworg, temp, impact, normal);\n\t\t\tc->los = VectorCompare(impact, temp);\n\t\t}\n\t\tif (!c->los) {\n\t\t\t//Can't see it, so make it fade out(faster)\n\t\t\tc->scale = 0;\n\t\t\tc->alpha = 0;\n\t\t}\n\t\telse {\n\t\t\tif (c->type == C_FIRE) {\n\t\t\t\tc->fade = 1.5;\n\t\t\t\tc->growth = 1000;\n\t\t\t\tif (c->scale > 150) {\n\t\t\t\t\tc->scale = 150 + rand() % 15; //flicker when at full radius\n\t\t\t\t}\n\t\t\t\tc->alpha = min(c->alpha, 0.2f);\n\t\t\t}\n\t\t\tc->sighted = true;\n\t\t}\n\t}\n}\n\n//R_DrawCoronas\nvoid R_DrawCoronas(void)\n{\n\tvec3_t dist, up, right;\n\tfloat fdist, scale, alpha;\n\tcorona_t *c;\n\tcorona_texture_id tex;\n\n\tif (!amf_coronas.integer && !CoronaCount) {\n\t\treturn;\n\t}\n\n\tif (!ISPAUSED) {\n\t\tR_UpdateCoronas();\n\t}\n\n\tVectorScale(vup, 1, up);//1.5\n\tVectorScale(vright, 1, right);\n\n\tfor (tex = CORONATEX_STANDARD; tex < CORONATEX_COUNT; ++tex) {\n\t\tsprite3d_batch_id batch_id = SPRITE3D_CORONATEX_STANDARD + (tex - CORONATEX_STANDARD);\n\t\tcorona_texture_t* texture = &corona_textures[tex];\n\t\tbyte color[4];\n\n\t\tif (!r_corona_by_tex[tex]) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tR_Sprite3DInitialiseBatch(batch_id, r_state_coronas, R_UseModernOpenGL() ? texture->array_tex : texture->texnum, texture->array_index, r_primitive_triangle_strip);\n\n\t\tfor (c = r_corona_by_tex[tex]; c; c = c->next) {\n\t\t\tr_sprite3d_vert_t* vert;\n\t\t\tif (c->type == C_FREE) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!c->los) {\n\t\t\t\t// can't see it and\n\t\t\t\tif (!c->sighted) {\n\t\t\t\t\t// haven't seen it before\n\t\t\t\t\tcontinue; // don't draw it\n\t\t\t\t}\n\t\t\t}\n\t\t\t// else it will be fading out, so that's 'kay\n\n\t\t\tVectorSubtract(r_refdef.vieworg, c->origin, dist);\n\t\t\tfdist = VectorLength(dist);\n\n\t\t\tfdist = max(fdist, 24);\n\t\t\tscale = (1 - 1 / fdist) * c->scale;\n\t\t\talpha = c->alpha;\n\n\t\t\tcolor[0] = (c->color[0] * alpha) * 255;\n\t\t\tcolor[1] = (c->color[1] * alpha) * 255;\n\t\t\tcolor[2] = (c->color[2] * alpha) * 255;\n\t\t\tcolor[3] = 0;\n\n\t\t\tvert = R_Sprite3DAddEntry(batch_id, 4);\n\t\t\tif (!vert) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tR_Sprite3DSetVert(\n\t\t\t\tvert++,\n\t\t\t\t// Left/Up\n\t\t\t\tc->origin[0] + up[0] * (scale / 2) + (right[0] * (scale / 2)*-1),\n\t\t\t\tc->origin[1] + up[1] * (scale / 2) + (right[1] * (scale / 2)*-1),\n\t\t\t\tc->origin[2] + up[2] * (scale / 2) + (right[2] * (scale / 2)*-1),\n\t\t\t\t0, 0, color, texture->array_index\n\t\t\t);\n\t\t\tR_Sprite3DSetVert(\n\t\t\t\tvert++,\n\t\t\t\t// Right/Up\n\t\t\t\tc->origin[0] + right[0] * (scale / 2) + up[0] * (scale / 2),\n\t\t\t\tc->origin[1] + right[1] * (scale / 2) + up[1] * (scale / 2),\n\t\t\t\tc->origin[2] + right[2] * (scale / 2) + up[2] * (scale / 2),\n\t\t\t\ttexture->array_scale_s, 0, color, texture->array_index\n\t\t\t);\n\t\t\tR_Sprite3DSetVert(\n\t\t\t\tvert++,\n\t\t\t\t// Left/Down\n\t\t\t\tc->origin[0] + (right[0] * (scale / 2)*-1) + (up[0] * (scale / 2)*-1),\n\t\t\t\tc->origin[1] + (right[1] * (scale / 2)*-1) + (up[1] * (scale / 2)*-1),\n\t\t\t\tc->origin[2] + (right[2] * (scale / 2)*-1) + (up[2] * (scale / 2)*-1),\n\t\t\t\t0, texture->array_scale_t, color, texture->array_index\n\t\t\t);\n\t\t\tR_Sprite3DSetVert(\n\t\t\t\tvert++,\n\t\t\t\t// Right/Down\n\t\t\t\tc->origin[0] + right[0] * (scale / 2) + (up[0] * (scale / 2)*-1),\n\t\t\t\tc->origin[1] + right[1] * (scale / 2) + (up[1] * (scale / 2)*-1),\n\t\t\t\tc->origin[2] + right[2] * (scale / 2) + (up[2] * (scale / 2)*-1),\n\t\t\t\ttexture->array_scale_s, texture->array_scale_t, color, texture->array_index\n\t\t\t);\n\n\t\t\t// It's sort of cheap, but lets draw a few more here to make the effect more obvious\n\t\t\tif (c->type == C_FLASH || c->type == C_BLUEFLASH) {\n\t\t\t\tint a;\n\n\t\t\t\tfor (a = 0; a < 5; a++) {\n\t\t\t\t\tvert = R_Sprite3DAddEntry(batch_id, 4);\n\t\t\t\t\tif (!vert) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tR_Sprite3DSetVert(\n\t\t\t\t\t\tvert++,\n\t\t\t\t\t\t// Left/Up\n\t\t\t\t\t\tc->origin[0] + up[0] * (scale / 30) + (right[0] * (scale)*-1),\n\t\t\t\t\t\tc->origin[1] + up[1] * (scale / 30) + (right[1] * (scale)*-1),\n\t\t\t\t\t\tc->origin[2] + up[2] * (scale / 30) + (right[2] * (scale)*-1),\n\t\t\t\t\t\t0, 0, color, texture->array_index\n\t\t\t\t\t);\n\t\t\t\t\tR_Sprite3DSetVert(\n\t\t\t\t\t\tvert++,\n\t\t\t\t\t\t// Right/Up\n\t\t\t\t\t\tc->origin[0] + right[0] * (scale)+up[0] * (scale / 30),\n\t\t\t\t\t\tc->origin[1] + right[1] * (scale)+up[1] * (scale / 30),\n\t\t\t\t\t\tc->origin[2] + right[2] * (scale)+up[2] * (scale / 30),\n\t\t\t\t\t\ttexture->array_scale_s, 0, color, texture->array_index\n\t\t\t\t\t);\n\t\t\t\t\tR_Sprite3DSetVert(\n\t\t\t\t\t\tvert++,\n\t\t\t\t\t\t// Left/Down\n\t\t\t\t\t\tc->origin[0] + (right[0] * (scale)*-1) + (up[0] * (scale / 30)*-1),\n\t\t\t\t\t\tc->origin[1] + (right[1] * (scale)*-1) + (up[1] * (scale / 30)*-1),\n\t\t\t\t\t\tc->origin[2] + (right[2] * (scale)*-1) + (up[2] * (scale / 30)*-1),\n\t\t\t\t\t\t0, texture->array_scale_t, color, texture->array_index\n\t\t\t\t\t);\n\t\t\t\t\tR_Sprite3DSetVert(\n\t\t\t\t\t\tvert++,\n\t\t\t\t\t\t// Right/Down\n\t\t\t\t\t\tc->origin[0] + right[0] * (scale)+(up[0] * (scale / 30)*-1),\n\t\t\t\t\t\tc->origin[1] + right[1] * (scale)+(up[1] * (scale / 30)*-1),\n\t\t\t\t\t\tc->origin[2] + right[2] * (scale)+(up[2] * (scale / 30)*-1),\n\t\t\t\t\t\ttexture->array_scale_s, texture->array_scale_t, color, texture->array_index\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void R_CoronasNewImpl(coronatype_t type, vec3_t origin, int entity_id)\n{\n\tcorona_t *c = NULL;\n\tint i;\n\tqbool corona_found = false;\n\tint corona_id = 0;\n\tcustomlight_t cst_lt = { 0 };\n\n\tif (ISPAUSED || !amf_coronas.integer) {\n\t\treturn;\n\t}\n\n\tif (entity_id) {\n\t\tentity_id = bound(0, entity_id, CL_MAX_EDICTS);\n\n\t\tcorona_id = cl_entities[entity_id - 1].corona_id;\n\t\tif (corona_id >= 0 && corona_id < MAX_CORONAS) {\n\t\t\tif (r_corona[corona_id].client_entity_id == entity_id) {\n\t\t\t\tc = &r_corona[corona_id];\n\t\t\t\tcorona_found = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!c) {\n\t\tc = r_corona;\n\t\tfor (i = 0; i < MAX_CORONAS; i++, c++) {\n\t\t\tif (c->type == C_FREE) {\n\t\t\t\tmemset(c, 0, sizeof(*c));\n\t\t\t\tcorona_found = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!corona_found) {\n\t\t//Tei: last attempt to get a valid corona to \"canibalize\"\n\t\tc = r_corona;\n\t\tfor (i = 0; i < MAX_CORONAS; i++, c++) {\n\t\t\t//Search a fire corona that is about to die soon\n\t\t\tif ((c->type == C_FIRE) &&\n\t\t\t\t(c->die < (cl.time + 0.1f) || c->scale <= 0.1f || c->alpha <= 0.1f)\n\t\t\t\t) {\n\t\t\t\tmemset(c, 0, sizeof(*c));\n\t\t\t\tcorona_found = true;\n\t\t\t\t// succesfully canibalize a fire corona that whas about to die.\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\t//If can't canibalize a corona, It exit silently\n\t\t//This is the worst case scenario, and will never happend\n\t\treturn;\n\t}\n\n\tc->sighted = false;\n\tVectorCopy(origin, c->origin);\n\tc->type = type;\n\tc->los = false;\n\tc->texture = CORONATEX_STANDARD;\n\tc->client_entity_id = entity_id;\n\tif (entity_id) {\n\t\tcl_entities[entity_id - 1].corona_id = c - r_corona;\n\t}\n\n\tif (type == C_FLASH || type == C_BLUEFLASH) {\n\t\tif (type == C_BLUEFLASH) {\n\t\t\tVectorCopy(bubblecolor[lt_blue], c->color);\n\t\t}\n\t\telse {\n\t\t\tdlightColorEx(r_explosionlightcolor.value, r_explosionlightcolor.string, lt_explosion, false, &cst_lt);\n\t\t\tif (cst_lt.type == lt_custom) {\n\t\t\t\tVectorCopy(cst_lt.color, c->color);\n\t\t\t\tVectorScale(c->color, (1.0 / 255), c->color); // cast byte to float\n\t\t\t}\n\t\t\telse {\n\t\t\t\tVectorCopy(bubblecolor[cst_lt.type], c->color);\n\t\t\t}\n\t\t\tVectorMA(c->color, 1.5, c->color, c->color);\n\t\t}\n\t\tc->scale = 600;\n\t\tc->die = cl.time + 0.2;\n\t\tc->alpha = 0.25;\n\t\tc->fade = 0;\n\t\tc->growth = -3000;\n\t}\n\telse if (type == C_SMALLFLASH) {\n\t\tc->color[0] = 1;\n\t\tc->color[1] = 0.8;\n\t\tc->color[2] = 0.3;\n\t\tc->scale = 150;\n\t\tc->die = cl.time + 0.1;\n\t\tc->alpha = 0.66;\n\t\tc->fade = 0;\n\t\tc->growth = -2000 + (rand() % 500) - 250;\n\t}\n\telse if (type == C_LIGHTNING) {\n\t\tVectorCopy(bubblecolor[lt_blue], c->color);\n\t\tc->scale = 80;\n\t\tc->die = cl.time + 0.01;\n\t\tc->alpha = 0.33;\n\t\tc->fade = 0;\n\t\tc->growth = 0;\n\t}\n\telse if (type == C_SMALLLIGHTNING) {\n\t\tVectorCopy(bubblecolor[lt_blue], c->color);\n\t\tc->scale = 40;\n\t\tc->die = cl.time + 0.01;\n\t\tc->alpha = 0.33;\n\t\tc->fade = 0;\n\t\tc->growth = 0;\n\t}\n\telse if (type == C_ROCKETLIGHT) {\n\t\tdlightColorEx(r_rocketlightcolor.value, r_rocketlightcolor.string, lt_rocket, false, &cst_lt);\n\t\tc->alpha = 1;\n\t\tif (cst_lt.type == lt_custom) {\n\t\t\tVectorCopy(cst_lt.color, c->color);\n\t\t\tVectorScale(c->color, (1.0 / 255), c->color); // cast byte to float\n\t\t\tc->alpha = cst_lt.alpha * (1.0 / 255);\n\t\t}\n\t\telse {\n\t\t\tVectorCopy(bubblecolor[cst_lt.type], c->color);\n\t\t}\n\t\tc->scale = 60;\n\t\tc->die = cl.time + 0.01;\n\t\tc->fade = 0;\n\t\tc->growth = 0;\n\t}\n\telse if (type == C_GREENLIGHT) {\n\t\tc->color[0] = 0;\n\t\tc->color[1] = 1;\n\t\tc->color[2] = 0;\n\t\tc->scale = 20;\n\t\tc->die = cl.time + 0.01;\n\t\tc->alpha = 0.5;\n\t\tc->fade = 0;\n\t\tc->growth = 0;\n\t}\n\telse if (type == C_REDLIGHT) {\n\t\tc->color[0] = 1;\n\t\tc->color[1] = 0;\n\t\tc->color[2] = 0;\n\t\tc->scale = 20;\n\t\tc->die = cl.time + 0.01;\n\t\tc->alpha = 0.5;\n\t\tc->fade = 0;\n\t\tc->growth = 0;\n\t}\n\telse if (type == C_BLUESPARK) {\n\t\tVectorCopy(bubblecolor[lt_blue], c->color);\n\t\tc->scale = 30;\n\t\tc->die = cl.time + 0.75;\n\t\tc->alpha = 0.5;\n\t\tc->fade = -1;\n\t\tc->growth = -60;\n\t}\n\telse if (type == C_GUNFLASH) {\n\t\tvec3_t normal, impact, vec;\n\t\tc->color[0] = c->color[1] = c->color[2] = 1;\n\t\tc->texture = CORONATEX_GUNFLASH;\n\t\tc->scale = 50;\n\t\tc->die = cl.time + 0.1;\n\t\tc->alpha = 0.66;\n\t\tc->fade = 0;\n\t\tc->growth = -500;\n\t\t//A lot of the time the message is being sent just inside of a wall or something\n\t\t//I want to move it out of the wall so we can see it\n\t\t//Mainly for the hwguy\n\t\t//Sigh, if only they knew \"omg see gunshots thru wall hax\"\n\t\tCL_TraceLine(r_refdef.vieworg, origin, impact, normal);\n\t\tif (!VectorCompare(origin, impact)) {\n\t\t\tVectorSubtract(r_refdef.vieworg, origin, vec);\n\t\t\tVectorNormalize(vec);\n\t\t\tVectorMA(origin, 2, vec, c->origin);\n\t\t}\n\t}\n\telse if (type == C_EXPLODE) {\n\t\tc->color[0] = c->color[1] = c->color[2] = 1;\n\t\tc->scale = 200;\n\t\tc->die = cl.time + 0.03;\n\t\tc->alpha = 1;\n\t\tc->growth = 0;\n\t\tc->fade = 0;\n\t\tc->texture = CORONATEX_EXPLOSIONFLASH1;\n\t}\n\telse if (type == C_WHITELIGHT) {\n\t\tc->color[0] = 1;\n\t\tc->color[1] = 1;\n\t\tc->color[2] = 1;\n\t\tc->scale = 40;\n\t\tc->die = cl.time + 1;\n\t\tc->alpha = 0.5;\n\t\tc->fade = -1;\n\t\tc->growth = -200;\n\t}\n\telse if (type == C_WIZLIGHT) {\n\t\tc->color[0] = 0;\n\t\tc->color[1] = 0.5;\n\t\tc->color[2] = 0;\n\t\tc->scale = 60;\n\t\tc->die = cl.time + 0.01;\n\t\tc->alpha = 1;\n\t\tc->fade = 0;\n\t\tc->growth = 0;\n\t}\n\telse if (type == C_KNIGHTLIGHT) {\n\t\tc->color[0] = 1;\n\t\tc->color[1] = 0.3;\n\t\tc->color[2] = 0;\n\t\tc->scale = 60;\n\t\tc->die = cl.time + 0.01;\n\t\tc->alpha = 1;\n\t\tc->fade = 0;\n\t\tc->growth = 0;\n\t}\n\telse if (type == C_VORELIGHT) {\n\t\tc->color[0] = 0.3;\n\t\tc->color[1] = 0;\n\t\tc->color[2] = 1;\n\t\tc->scale = 60;\n\t\tc->die = cl.time + 0.01;\n\t\tc->alpha = 1;\n\t\tc->fade = 0;\n\t\tc->growth = 0;\n\t}\n}\n\nvoid R_CoronasNew(coronatype_t type, vec3_t origin)\n{\n\tR_CoronasNewImpl(type, origin, 0);\n}\n\nvoid R_CoronasEntityNew(coronatype_t type, centity_t* cent)\n{\n\tR_CoronasNewImpl(type, cent->lerp_origin, (cent - cl_entities) + 1);\n}\n\nvoid InitCoronas(void)\n{\n\tcorona_t *c;\n\tint i;\n\n\t//VULT STATS\n\tCoronaCount = 0;\n\tCoronaCountHigh = 0;\n\tfor (i = 0; i < MAX_CORONAS; i++) {\n\t\tc = &r_corona[i];\n\t\tc->type = C_FREE;\n\t\tc->los = false;\n\t\tc->sighted = false;\n\t}\n}\n\n//NewStaticLightCorona\n//Throws down a permanent light at origin, and wont put another on top of it\n//This needs fixing so it wont be called so often\nvoid NewStaticLightCorona(coronatype_t type, vec3_t origin, int entity_id)\n{\n\tcorona_t* c;\n\tcorona_t* e = NULL;\n\tint\t      i;\n\tqbool     breakage = true;\n\n\tif (entity_id) {\n\t\tint corona_id = cl_static_entities[entity_id - 1].corona_id;\n\n\t\tif (corona_id >= 0 && corona_id < MAX_CORONAS) {\n\t\t\tif (r_corona[corona_id].static_entity_id == entity_id) {\n\t\t\t\te = &r_corona[corona_id];\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!e) {\n\t\tc = r_corona;\n\t\tfor (i = 0; i < MAX_CORONAS; i++, c++) {\n\t\t\tif (!e && c->type == C_FREE) {\n\t\t\t\te = c;\n\t\t\t\tbreakage = false;\n\t\t\t}\n\t\t\telse if (entity_id && entity_id == c->static_entity_id) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (c->type == C_FIRE && VectorCompare(c->origin, origin)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tif (breakage) {\n\t\t\t//no free coronas\n\t\t\treturn;\n\t\t}\n\t}\n\n\tmemset(e, 0, sizeof(*e));\n\n\te->sighted = false;\n\tVectorCopy(origin, e->origin);\n\te->type = type;\n\te->los = false;\n\te->texture = CORONATEX_STANDARD;\n\te->static_entity_id = entity_id;\n\tif (entity_id) {\n\t\tcl_static_entities[entity_id - 1].corona_id = e - r_corona;\n\t}\n\n\tif (type == C_FIRE) {\n\t\te->color[0] = 0.5;\n\t\te->color[1] = 0.2;\n\t\te->color[2] = 0.05;\n\t\t//e->scale = 0.1;\n\t\te->scale = 150 + rand() % 15;\n\t\te->die = cl.time + 800;\n\t\te->alpha = 0.2f;\n\t\te->fade = 0.5;\n\t\te->growth = 800;\n\t}\n}\n\nstatic void CoronaStats(int change)\n{\n\tif (CoronaCount > CoronaCountHigh) {\n\t\tCoronaCountHigh = CoronaCount;\n\t}\n\tCoronaCount += change;\n}\n\nvoid VX_FlagTexturesForArray(texture_flag_t* texture_flags)\n{\n\tcorona_texture_id tex;\n\n\tfor (tex = CORONATEX_STANDARD; tex < CORONATEX_COUNT; ++tex) {\n\t\tif (R_TextureReferenceIsValid(corona_textures[tex].texnum)) {\n\t\t\ttexture_flags[corona_textures[tex].texnum.index].flags |= (1 << TEXTURETYPES_SPRITES);\n\t\t}\n\t}\n}\n\nvoid VX_ImportTextureArrayReferences(texture_flag_t* texture_flags)\n{\n\tcorona_texture_id tex;\n\n\tfor (tex = CORONATEX_STANDARD; tex < CORONATEX_COUNT; ++tex) {\n\t\tif (R_TextureReferenceIsValid(corona_textures[tex].texnum)) {\n\t\t\ttexture_array_ref_t* array_ref = &texture_flags[corona_textures[tex].texnum.index].array_ref[TEXTURETYPES_SPRITES];\n\n\t\t\tcorona_textures[tex].array_tex = array_ref->ref;\n\t\t\tcorona_textures[tex].array_index = array_ref->index;\n\t\t\tcorona_textures[tex].array_scale_s = array_ref->scale_s;\n\t\t\tcorona_textures[tex].array_scale_t = array_ref->scale_t;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/vx_stuff.c",
    "content": "/*\nCopyright (C) 2011 VULTUREIIC\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"hud.h\"\n#include \"hud_common.h\"\n#include \"vx_stuff.h\"\n#include \"sbar.h\"\n#include \"fonts.h\"\n#include \"r_texture.h\"\n#include \"qmb_particles.h\"\n\ncorona_texture_t corona_textures[CORONATEX_COUNT];\n\ncvar_t\t\ttei_lavafire = { \"gl_surface_lava\", \"0\" };\ncvar_t\t\ttei_slime = { \"gl_surface_slime\", \"0\" };\n\ncvar_t\t\tamf_coronas = { \"gl_coronas\", \"0\" }; // 1\ncvar_t\t\tamf_coronas_tele = { \"gl_coronas_tele\", \"0\" };\ncvar_t\t\tamf_weather_rain = { \"gl_weather_rain\", \"0\" };\ncvar_t\t\tamf_weather_rain_fast = { \"gl_weather_rain_fast\", \"0\" };\ncvar_t\t\tamf_extratrails = { \"gl_extratrails\", \"0\" }; // 1\ncvar_t\t\tamf_detpacklights = { \"gl_detpacklights\", \"0\" }; // 1\ncvar_t\t\tamf_buildingsparks = { \"gl_buildingsparks\", \"0\" }; // 1\ncvar_t\t\tamf_cutf_tesla_effect = { \"gl_cutf_tesla_effect\", \"0\" }; // 1\ncvar_t\t\tamf_underwater_trails = { \"gl_turb_trails\", \"0\" }; // 1\ncvar_t      amf_underwater_fire = { \"gl_turb_fire\", \"1\" };\ncvar_t      amf_underwater_effects = { \"gl_turb_effects\", \"1\" };\n\ncvar_t\t\tamf_hidenails = { \"cl_hidenails\", \"0\" };\ncvar_t\t\tamf_hiderockets = { \"cl_hiderockets\", \"0\" };\n\ncvar_t\t\tamf_stat_loss = { \"r_damagestats\", \"0\" };\ncvar_t\t\tamf_lightning = { \"gl_lightning\", \"0\" }; // 1\n\ncvar_t\t\tamf_lightning_size = { \"gl_lightning_size\", \"3\" };\ncvar_t\t\tamf_lightning_sparks = { \"gl_lightning_sparks\", \"0\" }; // 0.4\ncvar_t\t\tamf_lightning_sparks_size = { \"gl_lightning_sparks_size\", \"300\", CVAR_RULESET_MAX | CVAR_RULESET_MIN, NULL, 300, 300, 1 };\ncvar_t\t\tamf_lightning_color = { \"gl_lightning_color\", \"120 140 255\", CVAR_COLOR };\ncvar_t\t\tamf_lighting_vertex = { \"gl_lighting_vertex\", \"0\" }; // 1\ncvar_t\t\tamf_lighting_colour = { \"gl_lighting_color\", \"0\" }; // 1\n\ncvar_t\t\tamf_inferno_trail = { \"gl_inferno_trail\", \"2\" };\ncvar_t\t\tamf_inferno_speed = { \"gl_inferno_speed\", \"1000\" };\n\ncvar_t\t\tcl_camera_tpp = { \"cl_camera_tpp\", \"0\" };\ncvar_t\t\tamf_camera_chase_dist = { \"cl_camera_tpp_distance\", \"-56\" };\ncvar_t\t\tamf_camera_chase_height = { \"cl_camera_tpp_height\", \"24\" };\ncvar_t\t\tamf_camera_death = { \"cl_camera_death\", \"0\" }; // 1\ncvar_t\t\tamf_nailtrail_water = { \"gl_nailtrail_turb\", \"0\" };\ncvar_t\t\tamf_nailtrail_plasma = { \"gl_nailtrail_plasma\", \"0\" };\ncvar_t\t\tamf_nailtrail = { \"gl_nailtrail\", \"0\" }; // 1\n\ncvar_t\t\tamf_part_gunshot = { \"gl_particle_gunshots\", \"0\" };\ncvar_t\t\tamf_part_gunshot_type = { \"gl_particle_gunshots_type\", \"1\" };\ncvar_t\t\tamf_part_spikes = { \"gl_particle_spikes\", \"0\" }; // 0.1\ncvar_t\t\tamf_part_spikes_type = { \"gl_particle_spikes_type\", \"1\" };\ncvar_t\t\tamf_part_shockwaves = { \"gl_particle_shockwaves\", \"0\" }; // 1\ncvar_t\t\tamf_part_2dshockwaves = { \"gl_particle_shockwaves_flat\", \"0\" };\ncvar_t\t\tamf_part_blood = { \"gl_particle_blood\", \"0\" }; // 1\ncvar_t\t\tamf_part_blood_color = { \"gl_particle_blood_color\", \"1\" };\ncvar_t\t\tamf_part_blood_type = { \"gl_particle_blood_type\", \"1\" };\ncvar_t\t\tamf_part_explosion = { \"gl_particle_explosions\", \"0\" };\ncvar_t\t\tamf_part_blobexplosion = { \"gl_particle_blobs\", \"0\" }; // 0.1\ncvar_t\t\tamf_part_gibtrails = { \"gl_particle_gibtrails\", \"0\" }; // 1\ncvar_t\t\tamf_part_muzzleflash = { \"gl_particle_muzzleflash\", \"0\" };\ncvar_t\t\tamf_part_deatheffect = { \"gl_particle_deatheffect\", \"0\" }; // 1\ncvar_t\t\tamf_part_teleport = { \"gl_particle_telesplash\", \"0\" };\ncvar_t\t\tamf_part_fire = { \"gl_particle_fire\", \"0\" }; // 1\ncvar_t\t\tamf_part_firecolor = { \"gl_particle_firecolor\", \"\" }; // 1\ncvar_t\t\tamf_part_sparks = { \"gl_particle_sparks\", \"0\" }; // 1\ncvar_t\t\tamf_part_traillen = { \"gl_particle_trail_length\", \"1\" };\ncvar_t\t\tamf_part_trailtime = { \"gl_particle_trail_time\",   \"1\" };\ncvar_t\t\tamf_part_trailwidth = { \"gl_particle_trail_width\",  \"3\" };\ncvar_t\t\tamf_part_traildetail = { \"gl_particle_trail_detail\", \"1\" };\ncvar_t\t\tamf_part_trailtype = { \"gl_particle_trail_type\",   \"1\" };\n\nstatic int vxdamagepov;\nstatic int vxdamagecount;\nstatic int vxdamagecount_oldhealth;\nstatic int vxdamagecount_time;\nstatic int vxdamagecountarmour;\nstatic int vxdamagecountarmour_oldhealth;\nstatic int vxdamagecountarmour_time;\n\nvoid Amf_Reset_DamageStats(void)\n{\n\tvxdamagepov = cl.spectator ? Cam_TrackNum() : -1;\n\tvxdamagecount = 0;\n\tvxdamagecount_time = 0;\n\tvxdamagecount_oldhealth = 0;\n\tvxdamagecountarmour = 0;\n\tvxdamagecountarmour_time = 0;\n\tvxdamagecountarmour_oldhealth = 0;\n}\n\nchar *TP_ConvertToWhiteText(char *s)\n{\n\tstatic char\tbuf[4096];\n\tchar\t*out, *p;\n\tbuf[0] = 0;\n\tout = buf;\n\n\tfor (p = s; *p; p++) {\n\t\t*out++ = *p - (*p & 128);\n\t}\n\t*out = 0;\n\treturn buf;\n}\n\n///////////////////////////////////////\n//Model Checking\ntypedef struct\n{\n\tchar *number;\n\tchar *name;\n\tchar *description;\n\tqbool notify;\n} checkmodel_t;\n\ncheckmodel_t tp_checkmodels[] =\n{\n\t{\"6967\", \"Eyes Model\", \"\", false},\n\t{\"8986\", \"Player Model with External Skins\", \"Possible external fullbright skins.\", true},\n\t{\"20572\", \"Dox TF PAK\", \".\", false},\n\t{\"37516\", \"Unknown non-cheating model\", \"I was told it wasn't a cheat model by Kay\", false},\n\t{\"13845\", \"TF 2.8 Standard\", \"\", false},\n\t{\"33168\", \"Q1 Standard\", \"\", false},\n\t{\"10560\", \"Ugly Quake Guy\", \"\", false},\n\t{\"44621\", \"Women's TF PAK\", \"\", false},\n\t{\"64351\", \"Razer PAK\", \"Glowing/Spiked plus other cheats.\", true},\n\t{\"17542\", \"Glow13 and Razer PAK\", \"Glowing/Spiked plus other cheats.\", true},\n\t{\"43672\", \"Modified/Original Glow\", \"This model is a cheat.\", true},\n\t{\"52774\", \"Thugguns PAK\", \"Spiked Player Model.\", true},\n\t{\"54273\", \"YourRom3ro's PAK\", \"Spiked Player Model.\", true},\n\t{\"47919\", \"DMPAK\", \"Spiked/Fullbright Player Model and loud water\", true},\n\t{\"60647\", \"Navy Seals PAK\", \"No Information (Considered cheat by TF Anti-Cheat\", true},\n\t{\"36870\", \"Clan Llama Force\", \"Fullbright Player Model\", true},\n\t{\"58759\", \"Clan LF / Acid Angel\", \"Fullbright Player Model plus other cheats\", true},\n\t{\"36870\", \"WBMod\", \"Hacked/Glowing Models\", true},\n\t{\"64524\", \"Fullbright\", \"Fullbright Player Model missing the head\", true}\n};\n\n#define NUMCHECKMODELS (sizeof(tp_checkmodels) / sizeof(tp_checkmodels[0]))\n\ncheckmodel_t *TP_GetModels(char *s)\n{\n\tunsigned int i;\n\tchar *f = TP_ConvertToWhiteText(s);\n\tcheckmodel_t *model = tp_checkmodels;\n\n\tfor (i = 0, model = tp_checkmodels; i < NUMCHECKMODELS; i++, model++) {\n\t\tif (strstr(f, model->number)) {\n\t\t\treturn model;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n////////////////////////////////\n/// test stuff\nvoid Draw_AlphaWindow(int x1, int y1, int x2, int y2, int col, float alpha)\n{\n\tint dist;\n\n\tdist = x2 - x1;\n\tDraw_Fill(x1, y1, dist + 1, 1, 2);      //Border - Top\n\tDraw_Fill(x1, y2, dist, 1, 1);          //Border - Bottom\n\tdist = y2 - y1;\n\tDraw_Fill(x1, y1, 1, dist, 2);          //Border - Left\n\tDraw_Fill(x2, y1, 1, dist + 1, 1);      //Border - Right\n\n\tDraw_AlphaFill(x1, y1, x2 - x1, y2 - y1, col, alpha);\n}\n\nvoid Draw_AMFStatLoss(int stat, hud_t* hud)\n{\n\tstatic int *vxdmgcnt, *vxdmgcnt_t, *vxdmgcnt_o;\n\tstatic int x;\n\tstatic cvar_t* scale[2] = { NULL, NULL };\n\tstatic cvar_t* style[2];\n\tstatic cvar_t* digits[2];\n\tstatic cvar_t* align[2];\n\tstatic cvar_t* duration[2];\n\tstatic cvar_t* proportional[2];\n\tint index = (stat == STAT_HEALTH ? 0 : 1);\n\tfloat effect_duration;\n\n\tif (cl.spectator && Cam_TrackNum() != vxdamagepov) {\n\t\tAmf_Reset_DamageStats();\n\t}\n\n\tif (stat == STAT_HEALTH) {\n\t\tvxdmgcnt = &vxdamagecount;\n\t\tvxdmgcnt_t = &vxdamagecount_time;\n\t\tvxdmgcnt_o = &vxdamagecount_oldhealth;\n\t\tx = 136;\n\t}\n\telse {\n\t\tvxdmgcnt = &vxdamagecountarmour;\n\t\tvxdmgcnt_t = &vxdamagecountarmour_time;\n\t\tvxdmgcnt_o = &vxdamagecountarmour_oldhealth;\n\t\tx = 24;\n\t}\n\n\tif (scale[index] == NULL && hud) {\n\t\t// first time called\n\t\tscale[index] = HUD_FindVar(hud, \"scale\");\n\t\tstyle[index] = HUD_FindVar(hud, \"style\");\n\t\tdigits[index] = HUD_FindVar(hud, \"digits\");\n\t\talign[index] = HUD_FindVar(hud, \"align\");\n\t\tduration[index] = HUD_FindVar(hud, \"duration\");\n\t\tproportional[index] = HUD_FindVar(hud, \"proportional\");\n\t}\n\teffect_duration = hud && duration[index] ? duration[index]->value : amf_stat_loss.value;\n\n\t//VULT STAT LOSS\n\t//Pretty self explanitory, I just thought it would be a nice feature to go with my \"what the hell is going on?\" theme\n\t//and obscure even more of the screen\n\tif (cl.stats[stat] < (*vxdmgcnt_o - 1)) {\n\t\tif (*vxdmgcnt_t > cl.time) {\n\t\t\t//add to damage\n\t\t\t*vxdmgcnt = *vxdmgcnt + (*vxdmgcnt_o - cl.stats[stat]);\n\t\t}\n\t\telse {\n\t\t\t*vxdmgcnt = *vxdmgcnt_o - cl.stats[stat];\n\t\t}\n\t\t*vxdmgcnt_t = cl.time + 2 * effect_duration;\n\t}\n\t*vxdmgcnt_o = cl.stats[stat];\n\n\tif (*vxdmgcnt_t > cl.time) {\n\t\tfloat alpha = min(1, (*vxdmgcnt_t - cl.time));\n\t\tfloat old_alpha = Draw_MultiplyOverallAlpha(alpha);\n\t\tif (hud) {\n\t\t\tSCR_HUD_DrawNum(hud, abs(*vxdmgcnt), 1, scale[index]->value, style[index]->value, digits[index]->integer, align[index]->string, proportional[index]->integer);\n\t\t}\n\t\telse {\n\t\t\tSbar_DrawNum(x, -24, abs(*vxdmgcnt), 3, (*vxdmgcnt) > 0);\n\t\t}\n\t\tDraw_SetOverallAlpha(old_alpha);\n\t}\n}\n\nvoid InitVXStuff(void)\n{\n\tint flags = TEX_COMPLAIN | TEX_MIPMAP | TEX_ALPHA | TEX_NOSCALE;\n\tint i;\n\n\tif (!qmb_initialized) {\n\t\t// FIXME: hm, in case of vid_restart, what we must do if before vid_restart qmb_initialized was true?\n\t\treturn;\n\t}\n\n\tfor (i = 0; i < CORONATEX_COUNT; ++i) {\n\t\tcorona_textures[i].array_scale_s = 1;\n\t\tcorona_textures[i].array_scale_t = 1;\n\t\tcorona_textures[i].array_index = 0;\n\t\tR_TextureReferenceInvalidate(corona_textures[i].array_tex);\n\t\tR_TextureReferenceInvalidate(corona_textures[i].texnum);\n\t}\n\n\tcorona_textures[CORONATEX_STANDARD].texnum = R_LoadTextureImage(\"textures/flash\", NULL, 0, 0, flags);\n\tcorona_textures[CORONATEX_GUNFLASH].texnum = R_LoadTextureImage(\"textures/gunflash\", NULL, 0, 0, flags);\n\tcorona_textures[CORONATEX_EXPLOSIONFLASH1].texnum = R_LoadTextureImage(\"textures/explosionflash1\", NULL, 0, 0, flags);\n\tcorona_textures[CORONATEX_EXPLOSIONFLASH2].texnum = R_LoadTextureImage(\"textures/explosionflash2\", NULL, 0, 0, flags);\n\tcorona_textures[CORONATEX_EXPLOSIONFLASH3].texnum = R_LoadTextureImage(\"textures/explosionflash3\", NULL, 0, 0, flags);\n\tcorona_textures[CORONATEX_EXPLOSIONFLASH4].texnum = R_LoadTextureImage(\"textures/explosionflash4\", NULL, 0, 0, flags);\n\tcorona_textures[CORONATEX_EXPLOSIONFLASH5].texnum = R_LoadTextureImage(\"textures/explosionflash5\", NULL, 0, 0, flags);\n\tcorona_textures[CORONATEX_EXPLOSIONFLASH6].texnum = R_LoadTextureImage(\"textures/explosionflash6\", NULL, 0, 0, flags);\n\tcorona_textures[CORONATEX_EXPLOSIONFLASH7].texnum = R_LoadTextureImage(\"textures/explosionflash7\", NULL, 0, 0, flags);\n\n\tInitCoronas(); // safe re-init\n\n\tInit_VLights(); // safe re-init imo\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SBAR);\n\tCvar_Register(&amf_stat_loss);\n\tCvar_ResetCurrentGroup();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_SPECTATOR);\n\tCvar_Register(&cl_camera_tpp);\n\tCvar_Register(&amf_camera_chase_dist);\n\tCvar_Register(&amf_camera_chase_height);\n\tCvar_Register(&amf_camera_death);\n\tCvar_ResetCurrentGroup();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_EYECANDY);\n\tCvar_Register(&amf_hiderockets);\n\tCvar_Register(&amf_hidenails);\n\tCvar_ResetCurrentGroup();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_LIGHTING);\n\tCvar_Register(&amf_coronas);\n\tCvar_Register(&amf_coronas_tele);\n\tCvar_Register(&amf_lighting_vertex);\n\tCvar_Register(&amf_lighting_colour);\n\tCvar_ResetCurrentGroup();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_PARTICLES);\n\tCvar_Register(&amf_part_blood);\n\tCvar_Register(&amf_part_blood_color);\n\tCvar_Register(&amf_part_blood_type);\n\tCvar_Register(&amf_part_muzzleflash);\n\tCvar_Register(&amf_part_fire);\n\tCvar_Register(&amf_part_firecolor);\n\tCvar_Register(&amf_part_deatheffect);\n\tCvar_Register(&amf_part_gunshot);\n\tCvar_Register(&amf_part_gunshot_type);\n\tCvar_Register(&amf_part_spikes);\n\tCvar_Register(&amf_part_spikes_type);\n\tCvar_Register(&amf_part_explosion);\n\tCvar_Register(&amf_part_blobexplosion);\n\tCvar_Register(&amf_part_teleport);\n\tCvar_Register(&amf_part_sparks);\n\tCvar_Register(&amf_part_2dshockwaves);\n\tCvar_Register(&amf_part_shockwaves);\n\tCvar_Register(&amf_part_gibtrails);\n\tCvar_Register(&amf_part_traillen);\n\tCvar_Register(&amf_part_trailtime);\n\tCvar_Register(&amf_part_traildetail);\n\tCvar_Register(&amf_part_trailwidth);\n\tCvar_Register(&amf_part_trailtype);\n\tCvar_ResetCurrentGroup();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_TURB);\n\tCvar_Register(&tei_lavafire);\n\tCvar_Register(&tei_slime);\n\tCvar_Register(&amf_weather_rain);\n\tCvar_Register(&amf_weather_rain_fast);\n\tCvar_Register(&amf_underwater_trails);\n\tCvar_Register(&amf_underwater_fire);\n\tCvar_Register(&amf_underwater_effects);\n\tCvar_ResetCurrentGroup();\n\n\tCvar_SetCurrentGroup(CVAR_GROUP_OPENGL);\n\tCvar_Register(&amf_nailtrail);\n\tCvar_Register(&amf_nailtrail_plasma);\n\tCvar_Register(&amf_nailtrail_water);\n\tCvar_Register(&amf_extratrails);\n\tCvar_Register(&amf_detpacklights);\n\tCvar_Register(&amf_buildingsparks);\n\tCvar_Register(&amf_lightning);\n\tCvar_Register(&amf_lightning_color);\n\tCvar_Register(&amf_lightning_size);\n\tCvar_Register(&amf_lightning_sparks);\n\tCvar_Register(&amf_lightning_sparks_size);\n\tCvar_Register(&amf_inferno_trail);\n\tCvar_Register(&amf_inferno_speed);\n\tCvar_Register(&amf_cutf_tesla_effect);\n\tCvar_ResetCurrentGroup();\n}\n\n\n"
  },
  {
    "path": "src/vx_stuff.h",
    "content": "/*\nCopyright (C) 2011 VultureIIC\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n// VultureIIC\n#ifndef __VX_STUFF__H__\n#define __VX_STUFF__H__\n\n#include \"hud.h\"\n\n// For coronas\ntypedef enum\n{\n\tC_FLASH,\t\t\t// Explosion type - Basically just fades out\n\tC_SMALLFLASH,\t\t// Muzzleflash\n\tC_BLUEFLASH,\t\t// Blob expl\n\tC_FREE,\t\t\t\t// Unused\n\tC_LIGHTNING,\t\t// Used on lightning beams\n\tC_SMALLLIGHTNING,\t// Used on lightning beams\n\tC_FIRE,\t\t\t\t// Used in torches\n\tC_ROCKETLIGHT,\t\t// A rockets glow...\n\tC_GREENLIGHT,\t\t// Detpack\n\tC_REDLIGHT,\t\t\t// Detpack about to blow\n\tC_BLUESPARK,\t\t// Gibbed building spark\n\tC_GUNFLASH,\t\t\t// Shotgun hit effect\n\tC_EXPLODE,\t\t\t// Animated explosion\n\tC_WHITELIGHT,\t\t// Gunshot #2 effect\n\tC_WIZLIGHT,\t\t\t// CREATURE TRACER LIGHTS\n\tC_KNIGHTLIGHT,\n\tC_VORELIGHT,\n} coronatype_t;\n\nvoid R_CoronasNew(coronatype_t type, vec3_t origin);\nvoid R_CoronasEntityNew(coronatype_t type, centity_t* cent);\nvoid R_DrawCoronas(void);\nvoid InitCoronas(void);\nvoid InitVXStuff(void);\nvoid NewStaticLightCorona(coronatype_t type, vec3_t origin, int entity_id);\n\ntypedef enum {\n\tCORONATEX_STANDARD,\n\tCORONATEX_GUNFLASH,\n\tCORONATEX_EXPLOSIONFLASH1,\n\tCORONATEX_EXPLOSIONFLASH2,\n\tCORONATEX_EXPLOSIONFLASH3,\n\tCORONATEX_EXPLOSIONFLASH4,\n\tCORONATEX_EXPLOSIONFLASH5,\n\tCORONATEX_EXPLOSIONFLASH6,\n\tCORONATEX_EXPLOSIONFLASH7,\n\n\tCORONATEX_COUNT\n} corona_texture_id;\n\ntypedef struct corona_texture_s {\n\ttexture_ref texnum;\n\n\ttexture_ref array_tex;\n\tint         array_index;\n\tfloat       array_scale_s;\n\tfloat       array_scale_t;\n} corona_texture_t;\n\nextern corona_texture_t corona_textures[CORONATEX_COUNT];\n\n\nfloat CL_TraceLine(vec3_t start, vec3_t end, vec3_t impact, vec3_t normal);\nvoid WeatherEffect(void);\nvoid SparkGen(vec3_t org, byte col[3], float count, float size, float life);\nextern cvar_t tei_lavafire;\nextern cvar_t tei_slime;\n\nextern cvar_t amf_coronas;\nextern cvar_t amf_coronas_tele;\nextern cvar_t amf_weather_rain;\nextern cvar_t amf_weather_rain_fast;\nextern cvar_t amf_nailtrail;\nextern cvar_t amf_hidenails;\nextern cvar_t amf_extratrails;\nextern cvar_t amf_detpacklights;\nextern cvar_t amf_buildingsparks;\nextern cvar_t amf_lightning;\nextern cvar_t amf_lightning_color;\nextern cvar_t amf_lightning_size;\nextern cvar_t amf_lightning_sparks;\nextern cvar_t amf_lightning_sparks_size;\nextern cvar_t cl_camera_tpp;\nextern cvar_t amf_camera_chase_dist;\nextern cvar_t amf_camera_chase_height;\nextern cvar_t amf_camera_death;\nextern cvar_t amf_stat_loss;\nextern cvar_t amf_part_gunshot;\nextern cvar_t amf_part_spikes;\nextern cvar_t amf_part_explosion;\nextern cvar_t amf_part_blobexplosion;\nextern cvar_t amf_part_teleport;\nextern cvar_t amf_part_blood;\nextern cvar_t amf_part_traillen;\nextern cvar_t amf_underwater_trails;\nextern cvar_t amf_part_sparks;\nextern cvar_t amf_hiderockets;\nextern cvar_t amf_nailtrail_plasma;\nextern cvar_t amf_part_muzzleflash;\nextern cvar_t amf_inferno_trail;\nextern cvar_t amf_inferno_speed;\nextern cvar_t amf_part_2dshockwaves;\nextern cvar_t amf_part_shockwaves;\nextern cvar_t amf_part_trailtime;\nextern cvar_t amf_part_gunshot_type;\nextern cvar_t amf_part_spikes_type;\nextern cvar_t amf_part_blood_color;\nextern cvar_t amf_part_blood_type;\nextern cvar_t amf_part_gibtrails;\nextern cvar_t amf_lighting_vertex;\nextern cvar_t amf_lighting_colour;\nextern cvar_t amf_part_fire;\nextern cvar_t amf_part_firecolor;\nextern cvar_t amf_tracker_frags;\nextern cvar_t amf_tracker_flags;\nextern cvar_t amf_tracker_streaks;\nextern cvar_t amf_cutf_tesla_effect;\nextern cvar_t amf_nailtrail_water;\nextern cvar_t amf_part_deatheffect;\nextern cvar_t amf_part_traildetail;\nextern cvar_t amf_part_trailwidth;\nextern cvar_t amf_part_trailtype;\n\nextern int ParticleCount, ParticleCountHigh, CoronaCount, CoronaCountHigh;\n\nvoid ParticleAlphaTrail(centity_t* cent, float size, float life);\nvoid ParticleNailTrail(centity_t* client_ent, float size, float life);\n\ntypedef enum\n{\n\tC_NORMAL, //Normal camera\n\tC_CHASECAM, //Chase cam is on\n\tC_EXTERNAL, //Looking at the player for whatever reason\n} cameramode_t;\n\nextern cameramode_t cameratype;\nvoid CameraUpdate(qbool dead);\nvoid VXGunshot(vec3_t org, float count);\nvoid VXTeleport(vec3_t org);\nvoid VXBlobExplosion(vec3_t org);\nvoid VXExplosion(vec3_t org);\nvoid VXBlood(vec3_t org, float count);\nvoid FuelRodGunTrail(centity_t* cent);\nvoid FireballTrail(centity_t* cent, byte col[3], float size, float life);\nvoid FireballTrailWave(centity_t* cent, byte col[3], float size, float life, vec3_t angle);\n\nvoid DrawMuzzleflash(vec3_t start, vec3_t angle, vec3_t vel);\nvoid VXNailhit(vec3_t org, float count);\nvoid FuelRodExplosion(vec3_t org);\nvoid Init_VLights(void);\nvoid ParticleFire(vec3_t org);\nvoid ParticleTorchFire(entity_t* ent);\nvoid VX_TeslaCharge(vec3_t org);\n\nvoid VX_LightningBeam(vec3_t start, vec3_t end);\nvoid VX_DeathEffect(vec3_t org);\nvoid VX_GibEffect(vec3_t org);\nvoid VX_DetpackExplosion(vec3_t org);\nvoid VX_LightningTrail(vec3_t start, vec3_t end);\n\nvoid Amf_Reset_DamageStats(void);\nvoid Draw_AMFStatLoss(int stat, hud_t* hud);\n\nint VX_OwnFragTextLen(float scale, qbool proportional);\ndouble VX_OwnFragTime(void);\nconst char * VX_OwnFragText(void);\nqbool VX_TrackerIsEnemy(int player);\n\n#endif // __VX_STUFF__H__\n"
  },
  {
    "path": "src/vx_tracker.c",
    "content": "/*\nCopyright (C) 2011 VULTUREIIC\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n/**\n\t\\file\n\n\t\\brief\n\tFrags Tracker Screen Element\n\n\t\\author\n\tVULTUREIIC\n**/\n\n#include \"quakedef.h\"\n#include \"utils.h\"\n#include \"vx_tracker.h\"\n#include \"gl_model.h\"\n#include \"fonts.h\"\n#include \"hud.h\"\n#include \"r_texture.h\"\n\n#define MAX_IMAGENAME 32\n\n// This can't be increased without changing private use image reservations (PRIVATE_USE_IMAGES_TRACKERIMAGES_BASE etc)\n#define MAX_IMAGES_PER_WEAPON 4\n#define TEXTFLAG_WEAPON 1\n\ntypedef struct weapon_label_s {\n\tchar label[64];\n\tint starts[4];\n\tbyte colors[4][4];\n\tint count;\n} weapon_label_t;\n\nstatic int weapon_images[MAX_WEAPON_CLASSES];\nstatic weapon_label_t weapon_labels[MAX_WEAPON_CLASSES];\n\n// hard-coded values, remember tracker color codes were 0-9, not 0-F\nstatic const byte color_white[4] = { 255, 255, 255, 255 };\nstatic const byte color960[4] = { 255, 170, 0, 255 };      // 'you died', suicides etc\nstatic const byte color940[4] = { 255, 113, 0, 255 };      // streaks\nstatic const byte color900[4] = { 255,   0, 0, 255 };      // <x> killed you\nstatic const byte color380[4] = {  77, 227, 0, 255 };      // teamkills\n\n//static void VX_TrackerAddText(char *msg, tracktype_t tt);\n\nstatic void VX_TrackerAddSegmented4(const char* lhs_text, const byte* lhs_color, const char* center_text, const byte* center_color, const char* rhs_text, const byte* rhs_color, const char* extra_text, const byte* extra_color);\n#define VX_TrackerAddSegmented(lhs_text, lhs_color, center_text, center_color, rhs_text, rhs_color) VX_TrackerAddSegmented4(lhs_text, lhs_color, center_text, center_color, rhs_text, rhs_color, \"\", NULL)\nstatic void VX_TrackerAddWeaponImageSplit(const char* lhs_text, const byte* lhs_color, int weapon, const char* rhs_text, const byte* rhs_color);\nstatic void VX_TrackerAddWeaponTextSplit(char* lhs_text, int weapon, const byte* color, char* rhs_text);\n\n//STREAKS\ntypedef struct {\n\tint frags;\n\tchar *spreestring;\n\tchar *name; //internal name\n\tchar *wavfilename;\n} killing_streak_t;\n\nkilling_streak_t tp_streak[] = {\n\t{ 100, \"teh chet\",           \"0wnhack\",     \"client/streakx6.wav\" },\n\t{ 50,  \"the master now\",     \"master\",      \"client/streakx5.wav\" },\n\t{ 20,  \"godlike\",            \"godlike\",     \"client/streakx4.wav\" },\n\t{ 15,  \"unstoppable\",        \"unstoppable\", \"client/streakx3.wav\" },\n\t{ 10,  \"on a rampage\",       \"rampage\",     \"client/streakx2.wav\" },\n\t{ 5,   \"on a killing spree\", \"spree\",       \"client/streakx1.wav\" },\n};\n\n#define NUMSTREAK (sizeof(tp_streak) / sizeof(tp_streak[0]))\n\nextern cvar_t\t\tcl_useimagesinfraglog;\n\nstatic int active_track = 0;\nstatic int max_active_tracks = 0;\n\nstatic void VXSCR_DrawTrackerString(float x_pos, float y_pos, float width, int name_width, qbool proportional, float scale, float image_scale, qbool align_right);\nstatic void OnChange_TrackerNameWidth(cvar_t* var, char* value, qbool* cancel);\n\ncvar_t r_tracker                                   = {\"r_tracker\", \"1\"};\ncvar_t amf_tracker_flags                           = {\"r_tracker_flags\", \"0\"};\ncvar_t amf_tracker_frags                           = {\"r_tracker_frags\", \"1\"};\ncvar_t amf_tracker_streaks                         = {\"r_tracker_streaks\", \"0\"};\ncvar_t amf_tracker_time                            = {\"r_tracker_time\", \"4\"};\ncvar_t amf_tracker_messages                        = {\"r_tracker_messages\", \"20\"};\nstatic cvar_t amf_tracker_colorfix\t\t\t\t         = {\"r_tracker_colorfix\", \"0\"};\nstatic cvar_t amf_tracker_pickups                  = {\"r_tracker_pickups\", \"0\"};\ncvar_t amf_tracker_align_right                     = {\"r_tracker_align_right\", \"1\"};\ncvar_t amf_tracker_scale                           = {\"r_tracker_scale\", \"1\"};\nstatic cvar_t amf_tracker_inconsole                = {\"r_tracker_inconsole\", \"0\"};\nstatic cvar_t amf_tracker_inconsole_colored_weapon = {\"r_tracker_inconsole_colored_weapon\", \"0\"};\nstatic cvar_t amf_tracker_x                        = {\"r_tracker_x\", \"0\"};\nstatic cvar_t amf_tracker_y                        = {\"r_tracker_y\", \"0\"};\nstatic cvar_t amf_tracker_frame_color              = {\"r_tracker_frame_color\", \"0 0 0 0\", CVAR_COLOR};\nstatic cvar_t amf_tracker_images_scale             = {\"r_tracker_images_scale\", \"1\"};\nstatic cvar_t amf_tracker_color_good               = {\"r_tracker_color_good\",     \"090\", CVAR_TRACKERCOLOR }; // good news\nstatic cvar_t amf_tracker_color_bad                = {\"r_tracker_color_bad\",      \"900\", CVAR_TRACKERCOLOR }; // bad news\nstatic cvar_t amf_tracker_color_tkgood             = {\"r_tracker_color_tkgood\",   \"990\", CVAR_TRACKERCOLOR }; // team kill, not on ur team\nstatic cvar_t amf_tracker_color_tkbad              = {\"r_tracker_color_tkbad\",    \"009\", CVAR_TRACKERCOLOR }; // team kill, on ur team\nstatic cvar_t amf_tracker_color_myfrag             = {\"r_tracker_color_myfrag\",   \"090\", CVAR_TRACKERCOLOR }; // use this color for frag which u done\nstatic cvar_t amf_tracker_color_fragonme           = {\"r_tracker_color_fragonme\", \"900\", CVAR_TRACKERCOLOR }; // use this color when u frag someone\nstatic cvar_t amf_tracker_color_suicide            = {\"r_tracker_color_suicide\",  \"900\", CVAR_TRACKERCOLOR }; // use this color when u suicides\nstatic cvar_t amf_tracker_string_suicides          = {\"r_tracker_string_suicides\", \" (suicides)\"};\nstatic cvar_t amf_tracker_string_died              = {\"r_tracker_string_died\",     \" (died)\"};\nstatic cvar_t amf_tracker_string_teammate          = {\"r_tracker_string_teammate\", \"teammate\"};\nstatic cvar_t amf_tracker_string_enemy             = {\"r_tracker_string_enemy\",    \"enemy\"};\nstatic cvar_t amf_tracker_string_inconsole_prefix  = {\"r_tracker_string_inconsole_prefix\", \"\"};\nstatic cvar_t amf_tracker_name_width               = {\"r_tracker_name_width\",      \"0\", 0, OnChange_TrackerNameWidth};\nstatic cvar_t amf_tracker_own_frag_prefix          = {\"r_tracker_own_frag_prefix\", \"You fragged \"};\nstatic cvar_t amf_tracker_positive_enemy_suicide   = {\"r_tracker_positive_enemy_suicide\", \"0\"};\t// Medar wanted it to be customizable\nstatic cvar_t amf_tracker_positive_enemy_vs_enemy  = {\"r_tracker_positive_enemy_vs_enemy\", \"0\"};\nstatic cvar_t amf_tracker_proportional             = {\"r_tracker_proportional\", \"0\"};\nstatic cvar_t amf_tracker_weapon_first             = {\"r_tracker_weapon_first\", \"0\"};\nstatic cvar_t amf_tracker_row_spacing              = {\"r_tracker_row_spacing\", \"0\"};\n\n#define MAX_TRACKERMESSAGES 30\n#define MAX_TRACKER_MSG_LEN 500\n#define MAX_IMAGES_PER_LINE 2\n#define MAX_SEGMENTS_PER_LINE 6\n\ntypedef struct \n{\n\tfloat die;\n\n\t// If set, delete subsequent message at same time as this one\n\tqbool linked;\n\n\t// Pre-parse now, don't do this every frame\n\tchar text[MAX_SEGMENTS_PER_LINE][64];\n\tint text_flags[MAX_SEGMENTS_PER_LINE];\n\tbyte colors[MAX_SEGMENTS_PER_LINE][4];\n\tmpic_t* images[MAX_SEGMENTS_PER_LINE];\n\ttext_alignment_t alignment[MAX_SEGMENTS_PER_LINE];\n\tint segments;\n\n\t// Kept this as it's used for positioning...\n\tint printable_characters;\n\tint image_characters;\n\tqbool pad;\n} trackmsg_t;\n\nstatic trackmsg_t trackermsg[MAX_TRACKERMESSAGES];\n\nstatic void VX_PreProcessMessage(trackmsg_t* msg);\n\nstatic qbool VX_FilterDeaths(void)\n{\n\textern cvar_t amf_tracker_frags;\n\n\treturn !amf_tracker_frags.integer;\n}\n\nstatic qbool VX_FilterStreaks(void)\n{\n\treturn !amf_tracker_streaks.integer;\n}\n\nstatic qbool VX_FilterFlags(void)\n{\n\treturn !amf_tracker_flags.integer;\n}\n\nstatic qbool VX_FilterPickups(void)\n{\n\treturn !amf_tracker_pickups.integer;\n}\n\nstatic struct {\n\tdouble time;\n\tchar text[MAX_SCOREBOARDNAME+20];\n} ownfragtext;\n\nvoid InitTracker(void)\n{\n\tCvar_SetCurrentGroup(CVAR_GROUP_SCREEN);\n\n\tCvar_Register(&r_tracker);\n\tCvar_Register(&amf_tracker_frags);\n\tCvar_Register(&amf_tracker_flags);\n\tCvar_Register(&amf_tracker_streaks);\n\tCvar_Register(&amf_tracker_messages);\n\tCvar_Register(&amf_tracker_colorfix);\n\tCvar_Register(&amf_tracker_inconsole);\n\tCvar_Register(&amf_tracker_inconsole_colored_weapon);\n\tCvar_Register(&amf_tracker_time);\n\tCvar_Register(&amf_tracker_align_right);\n\tCvar_Register(&amf_tracker_x);\n\tCvar_Register(&amf_tracker_y);\n\tCvar_Register(&amf_tracker_frame_color);\n\tCvar_Register(&amf_tracker_scale);\n\tCvar_Register(&amf_tracker_images_scale);\n\tCvar_Register(&amf_tracker_pickups);\n\n\tCvar_Register(&amf_tracker_color_good);\n\tCvar_Register(&amf_tracker_color_bad);\n\tCvar_Register(&amf_tracker_color_tkgood);\n\tCvar_Register(&amf_tracker_color_tkbad);\n\tCvar_Register(&amf_tracker_color_myfrag);\n\tCvar_Register(&amf_tracker_color_fragonme);\n\tCvar_Register(&amf_tracker_color_suicide);\n\n\tCvar_Register(&amf_tracker_string_suicides);\n\tCvar_Register(&amf_tracker_string_died);\n\tCvar_Register(&amf_tracker_string_teammate);\n\tCvar_Register(&amf_tracker_string_enemy);\n\tCvar_Register(&amf_tracker_string_inconsole_prefix);\n\n\tCvar_Register(&amf_tracker_name_width);\n\tCvar_Register(&amf_tracker_own_frag_prefix);\n\tCvar_Register(&amf_tracker_positive_enemy_suicide);\n\tCvar_Register(&amf_tracker_positive_enemy_vs_enemy);\n\tCvar_Register(&amf_tracker_proportional);\n\tCvar_Register(&amf_tracker_weapon_first);\n\tCvar_Register(&amf_tracker_row_spacing);\n}\n\nvoid VX_TrackerClear(void)\n{\n\tint i;\n\n\t// this block VX_TrackerAddText() untill first VX_TrackerThink()\n\t//   which mean we connected and may start process it right\n\tactive_track = max_active_tracks = 0;\n\n\tmemset(trackermsg, 0, sizeof(trackermsg[0]));\n\tfor (i = 0; i < MAX_TRACKERMESSAGES; i++) {\n\t\ttrackermsg[i].die = -1;\n\t}\n\tmemset(&ownfragtext, 0, sizeof(ownfragtext));\n}\n\n//When a message fades away, this moves all the other messages up a slot\nvoid VX_TrackerThink(void)\n{\n\tint i;\n\n\tif (r_tracker.integer) {\n\t\tVXSCR_DrawTrackerString(amf_tracker_x.value, vid.height * 0.2 / bound(0.1, amf_tracker_scale.value, 10) + amf_tracker_y.value, vid.width, amf_tracker_name_width.integer, amf_tracker_proportional.integer, amf_tracker_scale.value, amf_tracker_images_scale.value, amf_tracker_align_right.integer);\n\t}\n\n\tif (ISPAUSED) {\n\t\treturn;\n\t}\n\n\tactive_track = 0; // 0 slots active\n\tmax_active_tracks = bound(0, amf_tracker_messages.value, MAX_TRACKERMESSAGES);\n\n\tfor (i = 0; i < max_active_tracks; i++) {\n\t\tif (trackermsg[i].die < r_refdef2.time) {\n\t\t\t// inactive\n\t\t\tcontinue;\n\t\t}\n\n\t\tactive_track = i+1; // i+1 slots active\n\n\t\tif (!i) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (trackermsg[i - 1].die < r_refdef2.time && trackermsg[i].die >= r_refdef2.time) {\n\t\t\t// free slot above\n\t\t\tmemcpy(&trackermsg[i - 1], &trackermsg[i], sizeof(trackermsg[0]));\n\t\t\tmemset(&trackermsg[i], 0, sizeof(trackermsg[0]));\n\t\t\ttrackermsg[i].die = -1;\n\n\t\t\tactive_track = i; // i slots active\n\t\t\tcontinue;\n\t\t}\n\t}\n}\n\nstatic qbool VX_TrackerStringPrint(const char* text)\n{\n\tswitch (amf_tracker_inconsole.integer) {\n\t\tcase 1:\n\t\t\tCom_Printf(\"%s\\n\", text);\n\t\t\treturn false;\n\t\tcase 2:\n\t\t\tCom_Printf(\"%s\\n\", text);\n\t\t\treturn true;\n\t\tcase 3:\n\t\t{\n\t\t\tint flags = Print_flags[Print_current];\n\t\t\tPrint_flags[Print_current] |= PR_NONOTIFY;\n\t\t\tCom_Printf(\"%s\\n\", text);\n\t\t\tPrint_flags[Print_current] = flags;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nstatic qbool VX_TrackerStringPrintSegments(const char* text1, const char* text2, const char* text3, const char* text4)\n{\n\tint i;\n\n\tif (!amf_tracker_inconsole.integer) {\n\t\treturn true;\n\t}\n\n\tfor (i = 0; i < 4; ++i) {\n\t\tif (text1 == NULL || !text1[0]) {\n\t\t\ttext1 = text2;\n\t\t\ttext2 = text3;\n\t\t\ttext3 = text4;\n\t\t\ttext4 = NULL;\n\t\t}\n\t}\n\n\tswitch (amf_tracker_inconsole.integer) {\n\t\tcase 1:\n\t\t\tCom_Printf(\"%s%s%s%s%s%s%s%s\\n\", amf_tracker_string_inconsole_prefix.string, text1 ? text1 : \"\", text1 && text2 ? \" \" : \"\", text2 ? text2 : \"\", text2 && text3 ? \" \" : \"\", text3 ? text3 : \"\", text3 && text4 ? \" \" : \"\", text4 ? text4 : \"\");\n\t\t\treturn false;\n\t\tcase 2:\n\t\t\tCom_Printf(\"%s%s%s%s%s%s%s%s\\n\", amf_tracker_string_inconsole_prefix.string, text1 ? text1 : \"\", text1 && text2 ? \" \" : \"\", text2 ? text2 : \"\", text2 && text3 ? \" \" : \"\", text3 ? text3 : \"\", text3 && text4 ? \" \" : \"\", text4 ? text4 : \"\");\n\t\t\treturn true;\n\t\tcase 3:\n\t\t{\n\t\t\tint flags = Print_flags[Print_current];\n\t\t\tPrint_flags[Print_current] |= PR_NONOTIFY;\n\t\t\tCom_Printf(\"%s%s%s%s%s%s%s%s\\n\", amf_tracker_string_inconsole_prefix.string, text1 ? text1 : \"\", text1 && text2 ? \" \" : \"\", text2 ? text2 : \"\", text2 && text3 ? \" \" : \"\", text3 ? text3 : \"\", text3 && text4 ? \" \" : \"\", text4 ? text4 : \"\");\n\t\t\tPrint_flags[Print_current] = flags;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nstatic qbool VX_TrackerStringPrintSegmentsWithImage(const char* text1, const byte* text1_color, int weapon, const char* text3, const byte* text3_color)\n{\n\twchar imagetext[MAX_IMAGES_PER_WEAPON + 1] = { 0 };\n\tint offset = weapon * MAX_IMAGES_PER_WEAPON;\n\tint base = (PRIVATE_USE_TRACKERIMAGES_CHARSET << 8) + offset;\n\tint flags = Print_flags[Print_current];\n\tqbool suppress_from_tracker = amf_tracker_inconsole.integer == 1;\n\tqbool suppress_from_notify = amf_tracker_inconsole.integer == 3;\n\n\tif (!amf_tracker_inconsole.integer) {\n\t\treturn true;\n\t}\n\n\tif (R_TextureReferenceIsValid(char_textures[PRIVATE_USE_TRACKERIMAGES_CHARSET].glyphs[offset].texnum)) {\n\t\tint i;\n\t\tfor (i = 0; i < weapon_images[weapon]; ++i) {\n\t\t\timagetext[i] = base + i;\n\t\t}\n\t}\n\telse {\n\t\treturn VX_TrackerStringPrintSegments(text1, GetWeaponName(weapon), text3, NULL);\n\t}\n\n\tPrint_flags[Print_current] |= (PR_NORESET | (suppress_from_notify ? PR_NONOTIFY : 0));\n\n\tCom_Printf(\"%s\", amf_tracker_string_inconsole_prefix.string);\n\n\tif (text1 && text1[0]) {\n\t\tif (text1_color) {\n\t\t\tCom_Printf(\"&c%x%x%x%s&r \", text1_color[0] / 16, text1_color[1] / 16, text1_color[2] / 16, text1);\n\t\t}\n\t\telse {\n\t\t\tCom_Printf(\"%s \", text1);\n\t\t}\n\t}\n\tCon_PrintW(imagetext);\n\tif (text3 && text3[0]) {\n\t\tif (text1 && text1[0]) {\n\t\t\tif (text3_color) {\n\t\t\t\tCom_Printf(\" &c%x%x%x%s&r\\n\", text3_color[0] / 16, text3_color[1] / 16, text3_color[2] / 16, text3);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCom_Printf(\" %s\\n\", text3);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (text3_color) {\n\t\t\t\tCom_Printf(\" &c%x%x%x%s&r\\n\", text3_color[0] / 16, text3_color[1] / 16, text3_color[2] / 16, text3);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tCom_Printf(\"%s\\n\", text3);\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\tCom_Printf(\"\\n\");\n\t}\n\n\tPrint_flags[Print_current] = flags;\n\treturn !suppress_from_tracker;\n}\n\nstatic trackmsg_t* VX_NewTrackerMsg(void)\n{\n\tif (!max_active_tracks) {\n\t\treturn NULL;\n\t}\n\n\t// free space by removing the oldest one\n\tif (active_track >= max_active_tracks) {\n\t\tint i;\n\t\tint remove = 1;\n\n\t\tfor (i = 0; i < max_active_tracks; ++i) {\n\t\t\tif (!trackermsg[i].linked) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t++remove;\n\t\t}\n\n\t\tfor (i = remove; i < max_active_tracks; i++) {\n\t\t\tmemcpy(&trackermsg[i - remove], &trackermsg[i], sizeof(trackermsg[0]));\n\t\t}\n\t\tactive_track = max_active_tracks - remove;\n\t\tmemset(&trackermsg[active_track], 0, sizeof(trackermsg[active_track]) * remove);\n\t}\n\telse {\n\t\tmemset(&trackermsg[active_track], 0, sizeof(trackermsg[active_track]));\n\t}\n\ttrackermsg[active_track].die = r_refdef2.time + max(0, amf_tracker_time.value);\n\treturn &trackermsg[active_track++];\n}\n\nstatic void VX_TrackerAddFlaggedTextSegment(trackmsg_t* msg, const char* text, const byte* color, int flags)\n{\n\tif (text && text[0] && color && msg->segments < sizeof(msg->colors) / sizeof(msg->colors[0])) {\n\t\tmemcpy(msg->colors[msg->segments], color, sizeof(msg->colors[msg->segments]));\n\t\tstrlcpy(msg->text[msg->segments], text, sizeof(msg->text[msg->segments]));\n\t\tmsg->text_flags[msg->segments] = flags;\n\t\t++msg->segments;\n\t}\n}\n\nstatic void VX_TrackerAddTextSegment(trackmsg_t* msg, const char* text, const byte* color)\n{\n\tVX_TrackerAddFlaggedTextSegment(msg, text, color, 0);\n}\n\nstatic void VX_TrackerAddImageSegment(trackmsg_t* msg, mpic_t* pic)\n{\n\tif (pic) {\n\t\tmsg->text[msg->segments][0] = '\\0';\n\t\tmsg->images[msg->segments] = pic;\n\t\t++msg->segments;\n\t}\n}\n\nstatic void VX_TrackerAddSimpleText(const char* text, const byte* color)\n{\n\ttrackmsg_t* msg;\n\n\tif (!text || !text[0] || CL_Demo_SkipMessage(true)) {\n\t\treturn;\n\t}\n\n\tif (!VX_TrackerStringPrint(text)) {\n\t\treturn;\n\t}\n\n\tmsg = VX_NewTrackerMsg();\n\tif (msg) {\n\t\tVX_TrackerAddTextSegment(msg, text, color);\n\t\tVX_PreProcessMessage(msg);\n\t}\n}\n\nstatic void VX_TrackerAddSegmented4(const char* lhs_text, const byte* lhs_color, const char* center_text, const byte* center_color, const char* rhs_text, const byte* rhs_color, const char* extra_text, const byte* extra_color)\n{\n\ttrackmsg_t* msg;\n\n\tif (!lhs_text || !lhs_text[0] || CL_Demo_SkipMessage(true)) {\n\t\treturn;\n\t}\n\n\tif (!VX_TrackerStringPrintSegments(lhs_text, center_text, rhs_text, extra_text)) {\n\t\treturn;\n\t}\n\n\tmsg = VX_NewTrackerMsg();\n\tif (msg) {\n\t\tVX_TrackerAddTextSegment(msg, lhs_text, lhs_color);\n\t\tVX_TrackerAddTextSegment(msg, center_text, center_color);\n\t\tVX_TrackerAddTextSegment(msg, rhs_text, rhs_color);\n\t\tVX_TrackerAddTextSegment(msg, extra_text, extra_color);\n\t\tVX_PreProcessMessage(msg);\n\t}\n}\n\nstatic void VX_TrackerAddWeaponImageSplit(const char* lhs_text, const byte* lhs_color, int weapon, const char* rhs_text, const byte* rhs_color)\n{\n\ttrackmsg_t* msg;\n\tint i;\n\n\twhile (lhs_text && isspace((byte)lhs_text[0] & 127)) {\n\t\t++lhs_text;\n\t}\n\n\twhile (rhs_text && isspace((byte)rhs_text[0] & 127)) {\n\t\t++rhs_text;\n\t}\n\n\tif (((!lhs_text || !lhs_text[0]) && (!rhs_text || !rhs_text[0])) || CL_Demo_SkipMessage(true)) {\n\t\treturn;\n\t}\n\n\tif (!VX_TrackerStringPrintSegmentsWithImage(lhs_text, lhs_color, weapon, rhs_text, rhs_color)) {\n\t\treturn;\n\t}\n\n\tmsg = VX_NewTrackerMsg();\n\tif (msg) {\n\t\tmsg->pad = lhs_text && rhs_text && lhs_text[0] && rhs_text[0];\n\n\t\tif (!amf_tracker_weapon_first.integer)\n\t\t\tVX_TrackerAddTextSegment(msg, lhs_text, lhs_color);\n\n\t\tfor (i = 0; i < weapon_images[weapon]; ++i) {\n\t\t\tmpic_t* pic = &char_textures[PRIVATE_USE_TRACKERIMAGES_CHARSET].glyphs[weapon * MAX_IMAGES_PER_WEAPON + i];\n\n\t\t\tif (R_TextureReferenceIsValid(pic->texnum)) {\n\t\t\t\tVX_TrackerAddImageSegment(msg, pic);\n\t\t\t}\n\t\t}\n\n\t\tif (amf_tracker_weapon_first.integer)\n\t\t\tVX_TrackerAddTextSegment(msg, lhs_text, lhs_color);\n\n\t\tVX_TrackerAddTextSegment(msg, rhs_text, rhs_color);\n\t\tVX_PreProcessMessage(msg);\n\t}\n}\n\nstatic void VX_TrackerAddWeaponTextSplit(char* lhs_text, int weapon, const byte* color, char* rhs_text)\n{\n\ttrackmsg_t* msg;\n\tint i;\n\tchar *weapon_text;\n\n\tif (((!lhs_text || !lhs_text[0]) && (!rhs_text || !rhs_text[0])) || CL_Demo_SkipMessage(true)) {\n\t\treturn;\n\t}\n\n\tweapon_text = amf_tracker_inconsole_colored_weapon.integer == 0\n\t\t? GetWeaponName(weapon)\n\t\t: GetColoredWeaponName(weapon, color);\n\n\tif (!VX_TrackerStringPrintSegments(\n\t\tamf_tracker_weapon_first.integer ? weapon_text : lhs_text,\n\t\tamf_tracker_weapon_first.integer ? lhs_text : weapon_text,\n\t\trhs_text, NULL)) {\n\t\treturn;\n\t}\n\n\tmsg = VX_NewTrackerMsg();\n\tif (msg) {\n\t\tmsg->pad = lhs_text && rhs_text && lhs_text[0] && rhs_text[0];\n\n\t\tif (!amf_tracker_weapon_first.integer)\n\t\t\tVX_TrackerAddTextSegment(\n\t\t\t\tmsg,\n\t\t\t\tamf_tracker_colorfix.integer ? Q_normalizetext(lhs_text) : lhs_text,\n\t\t\t\tamf_tracker_colorfix.integer ? color : color_white);\n\n\t\tfor (i = 0; i < weapon_labels[weapon].count; ++i) {\n\t\t\tif (weapon_labels[weapon].colors[i][3]) {\n\t\t\t\tVX_TrackerAddFlaggedTextSegment(msg, weapon_labels[weapon].label + weapon_labels[weapon].starts[i], weapon_labels[weapon].colors[i], TEXTFLAG_WEAPON);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tVX_TrackerAddFlaggedTextSegment(msg, weapon_labels[weapon].label + weapon_labels[weapon].starts[i], color, TEXTFLAG_WEAPON);\n\t\t\t}\n\t\t}\n\n\t\tif (amf_tracker_weapon_first.integer)\n\t\t\tVX_TrackerAddTextSegment(\n\t\t\t\tmsg,\n\t\t\t\tamf_tracker_colorfix.integer ? Q_normalizetext(lhs_text) : lhs_text,\n\t\t\t\tamf_tracker_colorfix.integer ? color : color_white);\n\n\t\tVX_TrackerAddTextSegment(\n\t\t\tmsg,\n\t\t\tamf_tracker_colorfix.integer ? Q_normalizetext(rhs_text) : rhs_text,\n\t\t\tamf_tracker_colorfix.integer ? color : color_white);\n\t\tVX_PreProcessMessage(msg);\n\t}\n}\n\nstatic void VX_TrackerLinkStrings(void)\n{\n\tif (active_track > 1) {\n\t\ttrackermsg[active_track - 2].linked = true;\n\t}\n}\n\nstatic char* VX_Name(int player, char* buffer, int max_length)\n{\n\tstrlcpy(buffer, cl.players[player].shortname, max_length);\n\n\treturn buffer;\n}\n\n// Own Frags Text\nstatic void VX_OwnFragNew(const char *victim)\n{\n\townfragtext.time = r_refdef2.time;\n\n\tsnprintf(ownfragtext.text, sizeof(ownfragtext.text), \"%s%s\", amf_tracker_own_frag_prefix.string, victim);\n}\n\nint VX_OwnFragTextLen(float scale, qbool proportional)\n{\n\treturn Draw_StringLengthColors(ownfragtext.text, -1, scale, proportional);\n}\n\ndouble VX_OwnFragTime(void)\n{\n\treturn r_refdef2.time - ownfragtext.time;\n}\n\nconst char * VX_OwnFragText(void)\n{\n\treturn ownfragtext.text;\n}\n\n// return true if player enemy comparing to u, handle spectator mode\nqbool VX_TrackerIsEnemy(int player)\n{\n\tint selfnum;\n\n\tif (!cl.teamplay) // non teamplay mode, so player is enemy if not u \n\t\treturn !(cl.playernum == player || (player == Cam_TrackNum() && cl.spectator));\n\n\t// ok, below is teamplay\n\n\tif (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator))\n\t\treturn false;\n\n\tselfnum = (cl.spectator ? Cam_TrackNum() : cl.playernum);\n\n\tif (selfnum == -1)\n\t\treturn true; // well, seems u r spec, but tracking noone\n\n\treturn strncmp(cl.players[player].team, cl.players[selfnum].team, sizeof(cl.players[0].team)-1);\n}\n\n// is this player you, or you track him in case of spec\nstatic qbool its_you(int player)\n{\n\treturn (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator));\n}\n\nstatic byte* SuicideColor(int player)\n{\n\tif (its_you(player)) {\n\t\treturn amf_tracker_color_suicide.color;\n\t}\n\n\t// with images color_bad == enemy color\n\t// without images color bad == bad frag for us\n\tif (cl_useimagesinfraglog.integer) {\n\t\tif (amf_tracker_positive_enemy_suicide.integer) {\n\t\t\treturn (VX_TrackerIsEnemy(player) ? amf_tracker_color_good.color : amf_tracker_color_bad.color);\n\t\t}\n\t\telse {\n\t\t\treturn (VX_TrackerIsEnemy(player) ? amf_tracker_color_bad.color : amf_tracker_color_good.color);\n\t\t}\n\t}\n\telse {\n\t\treturn (VX_TrackerIsEnemy(player) ? amf_tracker_color_good.color : amf_tracker_color_bad.color);\n\t}\n}\n\nstatic byte* XvsYFullColor(int killed, int killer)\n{\n\tif (its_you(killed)) {\n\t\treturn amf_tracker_color_fragonme.color;\n\t}\n\n\tif (its_you(killer)) {\n\t\treturn amf_tracker_color_myfrag.color;\n\t}\n\n\tif(VX_TrackerIsEnemy(killer) && VX_TrackerIsEnemy(killed)) {\n\t\treturn (amf_tracker_positive_enemy_vs_enemy.integer) ? amf_tracker_color_good.color : amf_tracker_color_bad.color;\n\t}\n\n\treturn (VX_TrackerIsEnemy(killed) ? amf_tracker_color_good.color : amf_tracker_color_bad.color);\n}\n\nstatic byte* OddFragFullColor(int killer)\n{\n\tif (its_you(killer)) {\n\t\treturn amf_tracker_color_myfrag.color;\n\t}\n\n\treturn (!VX_TrackerIsEnemy(killer) ? amf_tracker_color_good.color : amf_tracker_color_bad.color);\n}\n\nstatic byte* EnemyFullColor(void)\n{\n\treturn amf_tracker_color_bad.color;\n}\n\nstatic byte* TeamKillColor(int player)\n{\n\treturn (VX_TrackerIsEnemy(player) ? amf_tracker_color_tkgood.color : amf_tracker_color_tkbad.color);\n}\n\nvoid VX_TrackerDeath(int player, int weapon, int count)\n{\n\tif (VX_FilterDeaths()) {\n\t\treturn;\n\t}\n\n\tif (amf_tracker_frags.integer == 2) {\n\t\tchar player_name[MAX_SCOREBOARDNAME];\n\n\t\tVX_Name(player, player_name, MAX_SCOREBOARDNAME);\n\n\t\tif (cl_useimagesinfraglog.integer) {\n\t\t\t//snprintf(outstring, sizeof(outstring), \"&c%s%s&r %s&c%s%s&r\", SuiColor(player), VX_Name(player), GetWeaponName(weapon), SuiColor(player), amf_tracker_string_died.string);\n\t\t\tQ_normalizetext(player_name);\n\t\t\tVX_TrackerAddWeaponImageSplit(player_name, SuicideColor(player), weapon, amf_tracker_string_died.string, SuicideColor(player));\n\t\t}\n\t\telse {\n\t\t\t//snprintf(outstring, sizeof(outstring), \"&r%s &c%s%s&r%s\", VX_Name(player), SuiColor(player), GetWeaponName(weapon), amf_tracker_string_died.string);\n\t\t\tVX_TrackerAddWeaponTextSplit(player_name, weapon, SuicideColor(player), amf_tracker_string_died.string);\n\t\t}\n\t}\n\telse if (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator)) {\n\t\tchar outstring[MAX_TRACKER_MSG_LEN];\n\n\t\t//snprintf(outstring, sizeof(outstring), \"&c960You died&r\\n%s deaths: %i\", GetWeaponName(weapon), count);\n\t\tVX_TrackerAddSimpleText(\"You died\", color960);\n\t\tif (cl_useimagesinfraglog.integer) {\n\t\t\tsnprintf(outstring, sizeof(outstring), \"deaths: %i\", count);\n\t\t\tVX_TrackerAddWeaponImageSplit(NULL, NULL, weapon, outstring, color_white);\n\t\t}\n\t\telse {\n\t\t\tsnprintf(outstring, sizeof(outstring), \"%s deaths: %i\", GetWeaponName(weapon), count);\n\t\t\tVX_TrackerAddSimpleText(outstring, color_white);\n\t\t}\n\t\tVX_TrackerLinkStrings();\n\t}\n}\n\nvoid VX_TrackerSuicide(int player, int weapon, int count)\n{\n\tif (VX_FilterDeaths()) {\n\t\treturn;\n\t}\n\n\tif (amf_tracker_frags.integer == 2) {\n\t\tchar player_name[MAX_SCOREBOARDNAME];\n\n\t\tVX_Name(player, player_name, MAX_SCOREBOARDNAME);\n\n\t\tif (cl_useimagesinfraglog.integer) {\n\t\t\t//snprintf(outstring, sizeof(outstring), \"&c%s%s&r %s&c%s%s&r\", SuiColor(player), VX_Name(player), GetWeaponName(weapon), SuiColor(player), amf_tracker_string_suicides.string);\n\t\t\tQ_normalizetext(player_name);\n\n\t\t\tVX_TrackerAddWeaponImageSplit(player_name, SuicideColor(player), weapon, amf_tracker_string_suicides.string, SuicideColor(player));\n\t\t}\n\t\telse {\n\t\t\t//snprintf(outstring, sizeof(outstring), \"&r%s &c%s%s&r%s\", VX_Name(player), SuiColor(player), GetWeaponName(weapon), amf_tracker_string_suicides.string);\n\t\t\tVX_TrackerAddWeaponTextSplit(player_name, weapon, SuicideColor(player), amf_tracker_string_suicides.string);\n\t\t}\n\t}\n\telse if (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator)) {\n\t\tchar outstring[MAX_TRACKER_MSG_LEN];\n\n\t\t//snprintf(outstring, sizeof(outstring), \"&c960You killed yourself&r\\n%s suicides: %i\", GetWeaponName(weapon), count);\n\t\tVX_TrackerAddSimpleText(\"You killed yourself\", color960);\n\t\tif (cl_useimagesinfraglog.integer) {\n\t\t\tsnprintf(outstring, sizeof(outstring), \"suicides: %i\", count);\n\n\t\t\tVX_TrackerAddWeaponImageSplit(NULL, NULL, weapon, amf_tracker_string_suicides.string, SuicideColor(player));\n\t\t}\n\t\telse {\n\t\t\tsnprintf(outstring, sizeof(outstring), \"%s suicides: %i\", GetWeaponName(weapon), count);\n\t\t\tVX_TrackerAddSimpleText(outstring, color_white);\n\t\t}\n\t\tVX_TrackerLinkStrings();\n\t}\n}\n\nvoid VX_TrackerFragXvsY(int player, int killer, int weapon, int player_wcount, int killer_wcount)\n{\n\tint colorfix = amf_tracker_colorfix.integer;\n\tchar outstring[20];\n\tchar player_name[MAX_SCOREBOARDNAME];\n\tchar killer_name[MAX_SCOREBOARDNAME];\n\n\tif (VX_FilterDeaths()) {\n\t\treturn;\n\t}\n\n\tif (amf_tracker_frags.integer == 2) {\n\t\tVX_Name(player, player_name, MAX_SCOREBOARDNAME);\n\t\tVX_Name(killer, killer_name, MAX_SCOREBOARDNAME);\n\n\t\tif (cl_useimagesinfraglog.integer) {\n\t\t\t//snprintf(outstring, sizeof(outstring), \"&c%s%s&r %s &c%s%s&r\", XvsYColor(player, killer), VX_Name(killer), GetWeaponName(weapon), XvsYColor(killer, player), VX_Name(player));\n\t\t\tQ_normalizetext(player_name);\n\t\t\tQ_normalizetext(killer_name);\n\n\t\t\tVX_TrackerAddWeaponImageSplit(killer_name, XvsYFullColor(player, killer), weapon, player_name, colorfix ? XvsYFullColor(player, killer) : XvsYFullColor(killer, player));\n\t\t}\n\t\telse {\n\t\t\tVX_TrackerAddWeaponTextSplit(killer_name, weapon, XvsYFullColor(player, killer), player_name);\n\t\t}\n\t}\n\telse if (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator)) {\n\t\tVX_Name(killer, killer_name, MAX_SCOREBOARDNAME);\n\n\t\t//snprintf(outstring, sizeof(outstring), \"&r%s &c900killed you&r\\n%s deaths: %i\", cl.players[killer].name, GetWeaponName(weapon), player_wcount);\n\t\tVX_TrackerAddSegmented(killer_name, color_white, \" \", color_white, \"killed you\", color900);\n\n\t\tif (cl_useimagesinfraglog.integer) {\n\t\t\tsnprintf(outstring, sizeof(outstring), \" deaths: %i\", player_wcount);\n\t\t\tVX_TrackerAddWeaponImageSplit(NULL, NULL, weapon, outstring, color_white);\n\t\t}\n\t\telse {\n\t\t\tsnprintf(outstring, sizeof(outstring), \"%s deaths: %i\", GetWeaponName(weapon), player_wcount);\n\t\t\tVX_TrackerAddSimpleText(outstring, color_white);\n\t\t}\n\t\tVX_TrackerLinkStrings();\n\t}\n\telse if (cl.playernum == killer || (killer == Cam_TrackNum() && cl.spectator)) {\n\t\tVX_Name(player, player_name, MAX_SCOREBOARDNAME);\n\n\t\t//snprintf(outstring, sizeof(outstring), \"&c900You killed &r%s\\n%s kills: %i\", cl.players[player].name, GetWeaponName(weapon), killer_wcount);\n\t\tVX_TrackerAddSegmented(\"You killed \", color900, \" \", color_white, player_name, color_white);\n\t\tif (cl_useimagesinfraglog.integer) {\n\t\t\tsnprintf(outstring, sizeof(outstring), \" kills: %i\", killer_wcount);\n\t\t\tVX_TrackerAddWeaponImageSplit(NULL, NULL, weapon, outstring, color_white);\n\t\t}\n\t\telse {\n\t\t\tsnprintf(outstring, sizeof(outstring), \"%s kills: %i\", GetWeaponName(weapon), killer_wcount);\n\t\t\tVX_TrackerAddSimpleText(outstring, color_white);\n\t\t}\n\t\tVX_TrackerLinkStrings();\n\t}\n\n\tif (cl.playernum == killer || (killer == Cam_TrackNum() && cl.spectator)) {\n\t\tVX_OwnFragNew(cl.players[player].name);\n\t}\n}\n\nvoid VX_TrackerOddFrag(int player, int weapon, int wcount)\n{\n\tchar outstring[20];\n\tchar player_name[MAX_SCOREBOARDNAME];\n\n\tif (VX_FilterDeaths()) {\n\t\treturn;\n\t}\n\n\tif (amf_tracker_frags.integer == 2) {\n\t\tVX_Name(player, player_name, MAX_SCOREBOARDNAME);\n\n\t\tif (cl_useimagesinfraglog.integer) {\n\t\t\t//snprintf(outstring, sizeof(outstring), \"&c%s%s&r %s &c%s%s&r\", OddFragColor(player), VX_Name(player), GetWeaponName(weapon), EnemyColor(), amf_tracker_string_enemy.string);\n\n\t\t\tQ_normalizetext(player_name);\n\n\t\t\tVX_TrackerAddWeaponImageSplit(player_name, OddFragFullColor(player), weapon, amf_tracker_string_enemy.string, EnemyFullColor());\n\t\t}\n\t\telse {\n\t\t\t//snprintf(outstring, sizeof(outstring), \"&r%s &c%s%s&r %s\", VX_Name(player), OddFragColor(player), GetWeaponName(weapon), amf_tracker_string_enemy.string);\n\t\t\tVX_TrackerAddWeaponTextSplit(player_name, weapon, OddFragFullColor(player), amf_tracker_string_enemy.string);\n\t\t}\n\t}\n\telse if (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator)) {\n\t\t//snprintf(outstring, sizeof(outstring), \"&c900You killed&r an enemy\\n%s kills: %i\", GetWeaponName(weapon), wcount);\n\t\tVX_TrackerAddSegmented(\"You killed\", color900, \" an enemy\", color_white, \"\", NULL);\n\n\t\tif (cl_useimagesinfraglog.integer) {\n\t\t\tsnprintf(outstring, sizeof(outstring), \" kills: %i\", wcount);\n\t\t\tVX_TrackerAddWeaponImageSplit(NULL, NULL, weapon, outstring, color_white);\n\t\t}\n\t\telse {\n\t\t\tsnprintf(outstring, sizeof(outstring), \"%s kills: %i\", GetWeaponName(weapon), wcount);\n\t\t\tVX_TrackerAddSimpleText(outstring, color_white);\n\t\t}\n\t\tVX_TrackerLinkStrings();\n\t}\n}\n\nvoid VX_TrackerTK_XvsY(int player, int killer, int weapon, int p_count, int p_icount, int k_count, int k_icount)\n{\n\tchar outstring[20];\n\tchar player_name[MAX_SCOREBOARDNAME];\n\tchar killer_name[MAX_SCOREBOARDNAME];\n\n\tif (VX_FilterDeaths()) {\n\t\treturn;\n\t}\n\n\tif (amf_tracker_frags.integer == 2) {\n\t\tbyte* color = TeamKillColor(player);\n\n\t\tVX_Name(player, player_name, MAX_SCOREBOARDNAME);\n\t\tVX_Name(killer, killer_name, MAX_SCOREBOARDNAME);\n\n\t\tif (cl_useimagesinfraglog.integer) {\n\t\t\t//snprintf(outstring, sizeof(outstring), \"&c%s%s&r %s &c%s%s&r\", TKColor(player), VX_Name(killer), GetWeaponName(weapon), TKColor(player), VX_Name(player));\n\t\t\tQ_normalizetext(player_name);\n\t\t\tQ_normalizetext(killer_name);\n\n\t\t\tVX_TrackerAddWeaponImageSplit(killer_name, color, weapon, player_name, color);\n\t\t}\n\t\telse {\n\t\t\t//snprintf(outstring, sizeof(outstring), \"&r%s &c%s%s&r %s\", VX_Name(killer), TKColor(player), GetWeaponName(weapon), VX_Name(player));\n\t\t\tVX_TrackerAddWeaponTextSplit(killer_name, weapon, color, player_name);\n\t\t}\n\t}\n\telse if (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator)) {\n\t\tVX_Name(killer, killer_name, MAX_SCOREBOARDNAME);\n\n\t\t//snprintf(outstring, sizeof(outstring), \"&c380Teammate&r %s &c900killed you\\nTimes: %i\\nTotal Teamkills: %i\", cl.players[killer].name, p_icount, p_count);\n\t\tVX_TrackerAddSegmented(\"Teammate \", color380, killer_name, color_white, \" killed you\", color900);\n\t\t//snprintf(outstring, sizeof(outstring), \"&c380Teammate&r %s &c900killed you\\nTimes: %i\\nTotal Teamkills: %i\", cl.players[killer].name, p_icount, p_count);\n\t\tsnprintf(outstring, sizeof(outstring), \"Times: %i\", p_icount);\n\t\tVX_TrackerAddSimpleText(outstring, color_white);\n\t\tVX_TrackerLinkStrings();\n\t\tsnprintf(outstring, sizeof(outstring), \"Total Teamkills: %i\", p_count);\n\t\tVX_TrackerAddSimpleText(outstring, color_white);\n\t\tVX_TrackerLinkStrings();\n\t}\n\telse if (cl.playernum == killer || (killer == Cam_TrackNum() && cl.spectator)) {\n\t\tVX_Name(player, player_name, MAX_SCOREBOARDNAME);\n\n\t\t//snprintf(outstring, sizeof(outstring), \"&c900You killed &c380teammate&r %s\\nTimes: %i\\nTotal Teamkills: %i\", cl.players[player].name, k_icount, k_count);\n\t\tVX_TrackerAddSegmented(\"You killed \", color900, \"teammate \", color380, player_name, color_white);\n\t\tsnprintf(outstring, sizeof(outstring), \"Times: %i\", k_icount);\n\t\tVX_TrackerAddSimpleText(outstring, color_white);\n\t\tVX_TrackerLinkStrings();\n\t\tsnprintf(outstring, sizeof(outstring), \"Total Teamkills: %i\", k_count);\n\t\tVX_TrackerAddSimpleText(outstring, color_white);\n\t\tVX_TrackerLinkStrings();\n\t}\n}\n\nvoid VX_TrackerOddTeamkill(int player, int weapon, int count)\n{\n\tchar outstring[20];\n\tchar player_name[MAX_SCOREBOARDNAME];\n\n\tif (VX_FilterDeaths()) {\n\t\treturn;\n\t}\n\n\tif (amf_tracker_frags.integer == 2) {\n\t\tVX_Name(player, player_name, MAX_SCOREBOARDNAME);\n\n\t\tif (cl_useimagesinfraglog.integer) {\n\t\t\t//snprintf(outstring, sizeof(outstring), \"&c%s%s&r %s &c%s%s&r\", TKColor(player), VX_Name(player), GetWeaponName(weapon), TKColor(player), amf_tracker_string_teammate.string);\n\t\t\tQ_normalizetext(player_name);\n\n\t\t\tVX_TrackerAddWeaponImageSplit(player_name, TeamKillColor(player), weapon, amf_tracker_string_teammate.string, TeamKillColor(player));\n\t\t}\n\t\telse {\n\t\t\t//snprintf(outstring, sizeof(outstring), \"&r%s &c%s%s&r %s\", VX_Name(player), TKColor(player), GetWeaponName(weapon), amf_tracker_string_teammate.string);\n\t\t\tVX_TrackerAddWeaponTextSplit(player_name, weapon, TeamKillColor(player), amf_tracker_string_teammate.string);\n\t\t}\n\t}\n\telse if (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator)) {\n\t\t//snprintf(outstring, sizeof(outstring), \"&c900You killed &c380a teammate&r\\nTotal Teamkills: %i\", count);\n\t\tVX_TrackerAddSegmented(\"You killed \", color900, \"a teammate\", color380, \"\", NULL);\n\t\tsnprintf(outstring, sizeof(outstring), \"Total Teamkills: %i\", count);\n\t\tVX_TrackerAddSimpleText(outstring, color_white);\n\t\tVX_TrackerLinkStrings();\n\t}\n}\n\nvoid VX_TrackerOddTeamkilled(int player, int weapon)\n{\n\tchar player_name[MAX_SCOREBOARDNAME];\n\n\tif (VX_FilterDeaths()) {\n\t\treturn;\n\t}\n\n\tVX_Name(player, player_name, sizeof(player_name));\n\n\tif (amf_tracker_frags.integer == 2) {\n\t\tif (cl_useimagesinfraglog.integer) {\n\t\t\tVX_TrackerAddWeaponImageSplit(amf_tracker_string_teammate.string, TeamKillColor(player), weapon, player_name, TeamKillColor(player));\n\t\t}\n\t\telse {\n\t\t\tVX_TrackerAddWeaponTextSplit(amf_tracker_string_teammate.string, weapon, TeamKillColor(player), player_name);\n\t\t}\n\t}\n\telse if (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator)) {\n\t\tVX_TrackerAddSegmented(\"Teammate \", color380, \"killed you\", color900, \"\", NULL);\n\t}\n}\n\nvoid VX_TrackerFlagTouch(int count)\n{\n\tchar outstring[MAX_TRACKER_MSG_LEN];\n\n\tif (VX_FilterFlags()) {\n\t\treturn;\n\t}\n\n\tVX_TrackerAddSimpleText(\"You've taken the flag\", color960);\n\tsnprintf(outstring, sizeof(outstring), \"Flags taken: %i\", count);\n\tVX_TrackerAddSimpleText(outstring, color_white);\n\tVX_TrackerLinkStrings();\n}\n\nvoid VX_TrackerFlagDrop(int count)\n{\n\tchar outstring[MAX_TRACKER_MSG_LEN];\n\n\tif (VX_FilterFlags()) {\n\t\treturn;\n\t}\n\n\tVX_TrackerAddSimpleText(\"You've dropped the flag\", color960);\n\tsnprintf(outstring, sizeof(outstring), \"Flags dropped: %i\", count);\n\tVX_TrackerAddSimpleText(outstring, color_white);\n\tVX_TrackerLinkStrings();\n}\n\nvoid VX_TrackerFlagCapture(int count)\n{\n\tchar outstring[MAX_TRACKER_MSG_LEN];\n\n\tif (VX_FilterFlags()) {\n\t\treturn;\n\t}\n\n\tVX_TrackerAddSimpleText(\"You've captured the flag\", color960);\n\tsnprintf(outstring, sizeof(outstring), \"Flags captured: %i\", count);\n\tVX_TrackerAddSimpleText(outstring, color_white);\n\tVX_TrackerLinkStrings();\n}\n\nkilling_streak_t *VX_GetStreak (int frags)\t\n{\n\tunsigned int i;\n\tkilling_streak_t *streak = tp_streak;\n\n\tfor (i = 0, streak = tp_streak; i < NUMSTREAK; i++, streak++) {\n\t\tif (frags >= streak->frags) {\n\t\t\treturn streak;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nvoid VX_TrackerStreak (int player, int count)\n{\n\tchar outstring[MAX_TRACKER_MSG_LEN]=\"\";\n\tkilling_streak_t *streak = VX_GetStreak(count);\n\n\tif (VX_FilterStreaks()) {\n\t\treturn;\n\t}\n\n\tif (!streak || streak->frags != count) {\n\t\treturn;\n\t}\n\n\tif (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator)) {\n\t\tsnprintf(outstring, sizeof(outstring), \"You are %s (%i kills)\", streak->spreestring, count);\n\n\t\tVX_TrackerAddSimpleText(outstring, color940);\n\t}\n\telse {\n\t\tsnprintf(outstring, sizeof(outstring), \"is %s (%i kills)\", streak->spreestring, count);\n\n\t\tVX_TrackerAddSegmented(cl.players[player].name, color_white, outstring, color940, \"\", NULL);\n\t}\n}\n\nvoid VX_TrackerStreakEnd(int player, int killer, int count)\n{\n\tchar outstring[MAX_TRACKER_MSG_LEN]=\"\";\n\tkilling_streak_t *streak = VX_GetStreak(count);\n\n\tif (!streak) {\n\t\treturn;\n\t}\n\n\tif (VX_FilterStreaks()) {\n\t\treturn;\n\t}\n\n\tif (player == killer) {\n\t\t// streak ends due to suicide\n\t\tgender_id gender = cl.players[player].gender;\n\n\t\tif (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator)) {\n\t\t\tsnprintf(outstring, sizeof(outstring), \"You were looking good until you killed yourself (%i kills)\", count);\n\t\t\tVX_TrackerAddSimpleText(outstring, color940);\n\t\t}\n\t\telse if (gender == gender_male) {\n\t\t\tsnprintf(outstring, sizeof(outstring), \" was looking good until he killed himself (%i kills)\", count);\n\t\t\tVX_TrackerAddSegmented(cl.players[player].name, color_white, outstring, color940, \"\", NULL);\n\t\t}\n\t\telse if (gender == gender_female) {\n\t\t\tsnprintf(outstring, sizeof(outstring), \" was looking good until she killed herself (%i kills)\", count);\n\t\t\tVX_TrackerAddSegmented(cl.players[player].name, color_white, outstring, color940, \"\", NULL);\n\t\t}\n\t\telse if (gender == gender_neutral) {\n\t\t\tsnprintf(outstring, sizeof(outstring), \" was looking good until it killed itself (%i kills)\", count);\n\t\t\tVX_TrackerAddSegmented(cl.players[player].name, color_white, outstring, color940, \"\", NULL);\n\t\t}\n\t\telse {\n\t\t\tsnprintf(outstring, sizeof(outstring), \" was looking good, then committed suicide (%i kills)\", count);\n\t\t\tVX_TrackerAddSegmented(cl.players[player].name, color_white, outstring, color940, \"\", NULL);\n\t\t}\n\t}\n\telse {\n\t\t// non suicide\n\t\tif (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator)) {\n\t\t\tsnprintf(outstring, sizeof(outstring), \" (%i kills)\", count);\n\t\t\tVX_TrackerAddSegmented(\"Your streak was ended by \", color940, cl.players[killer].name, color_white, outstring, color940);\n\t\t}\n\t\telse if (cl.playernum == killer || (killer == Cam_TrackNum() && cl.spectator)) {\n\t\t\tsnprintf(outstring, sizeof(outstring), \"'s streak was ended by you (%i kills)\", count);\n\t\t\tVX_TrackerAddSegmented(cl.players[player].name, color_white, outstring, color940, \"\", NULL);\n\t\t}\n\t\telse {\n\t\t\tsnprintf(outstring, sizeof(outstring), \" (%i kills)\", count);\n\t\t\tVX_TrackerAddSegmented4(cl.players[player].name, color_white, \"'s streak was ended by \", color940, cl.players[killer].name, color_white, outstring, color940);\n\t\t}\n\t}\n}\n\nvoid VX_TrackerStreakEndOddTeamkilled(int player, int count)\n{\n\tchar outstring[MAX_TRACKER_MSG_LEN]=\"\";\n\tkilling_streak_t *streak = VX_GetStreak(count);\n\n\tif (!streak) {\n\t\treturn;\n\t}\n\n\tif (VX_FilterStreaks()) {\n\t\treturn;\n\t}\n\n\tif (cl.playernum == player || (player == Cam_TrackNum() && cl.spectator)) {\n\t\tsnprintf(outstring, sizeof(outstring), \"Your streak was ended by teammate (%i kills)\", count);\n\n\t\tVX_TrackerAddSimpleText(outstring, color940);\n\t}\n\telse {\n\t\tsnprintf(outstring, sizeof(outstring), \"'s streak was ended by teammate (%i kills)\", count);\n\n\t\tVX_TrackerAddSegmented(cl.players[player].name, color_white, outstring, color940, \"\", NULL);\n\t}\n}\n\nvoid VXSCR_MeasureTracker(float* width, float* height, float scale, qbool proportional, int name_width)\n{\n\tint i;\n\tint padded_width = 8 * bound(name_width, 0, MAX_SCOREBOARDNAME - 1) * scale;\n\n\t*width = *height = 0;\n\n\tif (!active_track) {\n\t\treturn;\n\t}\n\n\t// Draw the max allowed trackers allowed at the same time\n\t// the latest ones are always shown.\n\tfor (i = 0; i < max_active_tracks; i++) {\n\t\tint printable_chars;\n\t\tfloat x = 0;\n\t\tint s;\n\n\t\t// Time expired for this tracker, don't draw it.\n\t\tif (trackermsg[i].die < r_refdef2.time) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tprintable_chars = trackermsg[i].printable_characters + trackermsg[i].image_characters;\n\t\tif (printable_chars <= 0) {\n\t\t\tbreak;\n\t\t}\n\n\t\t// Place the tracker.\n\t\tx = FontFixedWidth(1, scale, false, proportional);\n\n\t\t// Draw the segments.\n\t\tfor (s = 0; s < trackermsg[i].segments; ++s) {\n\t\t\tmpic_t* pic = trackermsg[i].images[s];\n\n\t\t\tif (pic) {\n\t\t\t\tx += 8 * 2 * scale;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (trackermsg[i].pad && padded_width && !trackermsg[i].text_flags[s]) {\n\t\t\t\t\tx += padded_width;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tx += Draw_StringLength(trackermsg[i].text[s], -1, scale, proportional);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tx += 8 * scale;\n\t\t}\n\n\t\t*width = max(*width, x);\n\t\t*height += 8 * scale;\t// Next line.\n\t}\n}\n\n// We need a seperate function, since our messages are in colour... and transparent\nstatic void VXSCR_DrawTrackerString(float x_pos, float y_pos, float width, int name_width, qbool proportional, float scale, float image_scale, qbool align_right)\n{\n\tint\t\tx, y;\n\tint\t\ti, printable_chars, s;\n\tfloat\talpha = 1, width_one_char;\n\tint     padded_width = 8 * bound(name_width, 0, MAX_SCOREBOARDNAME - 1) * scale;\n\n\tscale = bound(0.1, scale, 10);\n\timage_scale = bound(0.1, image_scale, 10);\n\tif (!active_track) {\n\t\treturn;\n\t}\n\n\twidth_one_char = FontFixedWidth(1, scale, false, proportional);\n\n\t// Draw the max allowed trackers allowed at the same time\n\t// the latest ones are always shown.\n\ty = y_pos;\n\tfor (i = 0; i < max_active_tracks; i++) {\n\t\tint initial_position;\n\n\t\t// Time expired for this tracker, don't draw it.\n\t\tif (trackermsg[i].die < r_refdef2.time) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Fade the text as it gets older.\n\t\talpha = min(1, (trackermsg[i].die - r_refdef2.time) / 2);\n\n\t\tprintable_chars = trackermsg[i].printable_characters + trackermsg[i].image_characters;\n\t\tif (printable_chars <= 0) {\n\t\t\tbreak;\n\t\t}\n\n\t\t// Place the tracker.\n\t\tif (!proportional) {\n\t\t\tx = x_pos + (align_right ? width - (printable_chars + 1) * width_one_char : width_one_char);\n\t\t}\n\t\telse {\n\t\t\tx = x_pos + width_one_char;\n\t\t}\n\n\t\t// Draw the segments.\n\t\tinitial_position = Draw_ImagePosition();\n\t\tfor (s = 0; s < trackermsg[i].segments; ++s) {\n\t\t\tmpic_t* pic = trackermsg[i].images[s];\n\n\t\t\tif (pic) {\n\t\t\t\t// Draw pic\n\t\t\t\tDraw_FitPicAlpha(\n\t\t\t\t\t(float)x - 0.5 * 8 * 2 * (image_scale - 1) * scale,\n\t\t\t\t\t(float)y - 0.5 * 8 * (image_scale - 1) * scale,\n\t\t\t\t\timage_scale * 8 * 2 * scale,\n\t\t\t\t\timage_scale * 8 * scale, pic, alpha\n\t\t\t\t);\n\n\t\t\t\tx += 8 * 2 * scale;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// Draw text\n\t\t\t\tclrinfo_t clr;\n\n\t\t\t\tclr.c = RGBAVECT_TO_COLOR_PREMULT_SPECIFIC(trackermsg[i].colors[s], alpha);\n\t\t\t\tclr.i = 0;\n\n\t\t\t\tif (trackermsg[i].pad && padded_width && !trackermsg[i].text_flags[s]) {\n\t\t\t\t\tDraw_SColoredStringAligned(x, y, trackermsg[i].text[s], &clr, 1, scale, alpha, proportional, trackermsg[i].alignment[s], x + padded_width);\n\t\t\t\t\tx += padded_width;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tx += Draw_SColoredAlphaString(x, y, trackermsg[i].text[s], &clr, 1, 0, scale, alpha, proportional);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tx += 8 * scale;\n\t\t}\n\t\tif (proportional && align_right) {\n\t\t\t// shift into correct location, now we know where it ended\n\t\t\tint new_draw_position = Draw_ImagePosition();\n\n\t\t\tDraw_AdjustImages(initial_position, new_draw_position, width - (x - x_pos));\n\t\t}\n\n\t\ty += (8 + amf_tracker_row_spacing.value) * scale;\t// Next line with configurable spacing.\n\t}\n}\n\nstatic void VX_PreProcessMessage(trackmsg_t* msg)\n{\n\tint s;\n\tint padded_chars = bound(amf_tracker_name_width.integer, 0, MAX_SCOREBOARDNAME - 1);\n\n\tmsg->printable_characters = msg->image_characters = 0;\n\tfor (s = 0; s < msg->segments; ++s) {\n\t\tif (msg->images[s]) {\n\t\t\tmsg->image_characters += 2;\n\t\t}\n\t\telse  {\n\t\t\tint length = 0;\n\n\t\t\tif (msg->pad && !msg->text_flags[s] && padded_chars) {\n\t\t\t\tlength = padded_chars;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlength = strlen(msg->text[s]);\n\t\t\t}\n\t\t\tmsg->printable_characters += length;\n\t\t}\n\t\t++msg->printable_characters;\n\t}\n\n\t--msg->printable_characters;\n}\n\nstatic void OnChange_TrackerNameWidth(cvar_t* var, char* value, qbool* cancel)\n{\n\tint i;\n\n\tCvar_SetIgnoreCallback(var, value);\n\n\tfor (i = 0; i < max_active_tracks; ++i) {\n\t\tif (trackermsg[i].die < cl.time) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tVX_PreProcessMessage(&trackermsg[i]);\n\t}\n}\n\nvoid VX_TrackerInit(void)\n{\n\tint i;\n\tchar fullpath[MAX_PATH];\n\n\tmemset(weapon_images, 0, sizeof(weapon_images));\n\tmemset(weapon_labels, 0, sizeof(weapon_labels));\n\n\tchar_textures[PRIVATE_USE_TRACKERIMAGES_CHARSET].custom_scale_x = 2;\n\tchar_textures[PRIVATE_USE_TRACKERIMAGES_CHARSET].custom_scale_y = 1;\n\n\t// Pre-cache weapon-class images\n\tfor (i = 0; i < MAX_WEAPON_CLASSES; ++i) {\n\t\tconst char* image = GetWeaponImageName(i);\n\t\tconst char* text = GetWeaponTextName(i);\n\n\t\tif (image && image[0]) {\n\t\t\tconst char* start = image;\n\t\t\tint l = 0;\n\n\t\t\twhile (start[l] && start[l] != '\\n') {\n\t\t\t\t// Image escape.\n\t\t\t\tif (start[l] == '\\\\') {\n\t\t\t\t\t// We found opening slash, get image name now.\n\t\t\t\t\tint from, to;\n\n\t\t\t\t\tfrom = to = ++l;\n\n\t\t\t\t\tfor (; start[l]; l++) {\n\t\t\t\t\t\tif (start[l] == '\\n')\n\t\t\t\t\t\t\tbreak; // Something bad, we didn't find a closing slash.\n\n\t\t\t\t\t\tif (start[l] == '\\\\')\n\t\t\t\t\t\t\tbreak; // Found a closing slash.\n\n\t\t\t\t\t\tto = l + 1;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (to > from) {\n\t\t\t\t\t\tchar imagename[128];\n\n\t\t\t\t\t\t// We got potential image name, treat image as two printable characters.\n\t\t\t\t\t\tif (to - from < sizeof(imagename)) {\n\t\t\t\t\t\t\tint base = (i * MAX_IMAGES_PER_WEAPON);\n\t\t\t\t\t\t\tmpic_t* texture;\n\n\t\t\t\t\t\t\tstrncpy(imagename, start + from, to - from);\n\t\t\t\t\t\t\timagename[to - from] = '\\0';\n\n\t\t\t\t\t\t\tsnprintf(fullpath, sizeof(fullpath), \"textures/tracker/%s\", imagename);\n\n\t\t\t\t\t\t\ttexture = R_LoadPicImage(fullpath, NULL, 0, 0, TEX_ALPHA);\n\t\t\t\t\t\t\tif (texture) {\n\t\t\t\t\t\t\t\tmpic_t* pic = &char_textures[PRIVATE_USE_TRACKERIMAGES_CHARSET].glyphs[base + weapon_images[i]];\n\n\t\t\t\t\t\t\t\t*pic = *texture;\n\t\t\t\t\t\t\t\tpic->width = 16;\n\t\t\t\t\t\t\t\tpic->height = 8;\n\t\t\t\t\t\t\t\t++weapon_images[i];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (start[l] == '\\\\') {\n\t\t\t\t\t\tl++; // Advance.\n\t\t\t\t\t}\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tl++; // Increment count of any chars in string untill end or new line.\n\t\t\t}\n\t\t}\n\n\t\tif (text && text[0]) {\n\t\t\tint j, pos = 0, count = 0;\n\t\t\tint len;\n\n\t\t\tstrlcpy(weapon_labels[i].label, text, sizeof(weapon_labels[i].label));\n\n\t\t\tlen = strlen(weapon_labels[i].label);\n\t\t\tfor (j = 0; j < len; ++j) {\n\t\t\t\tif (text[j] == '&' && text[j + 1] == 'c' && text[j + 2] && text[j + 3] && text[j + 4]) {\n\t\t\t\t\tweapon_labels[i].label[j] = '\\0';\n\n\t\t\t\t\tif (count) {\n\t\t\t\t\t\t++weapon_labels[i].count;\n\t\t\t\t\t\tif (weapon_labels[i].count >= sizeof(weapon_labels[i].starts) / sizeof(weapon_labels[i].starts[0])) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tweapon_labels[i].colors[weapon_labels[i].count][0] = HexToInt(text[j + 2]) * 16;\n\t\t\t\t\tweapon_labels[i].colors[weapon_labels[i].count][1] = HexToInt(text[j + 3]) * 16;\n\t\t\t\t\tweapon_labels[i].colors[weapon_labels[i].count][2] = HexToInt(text[j + 4]) * 16;\n\t\t\t\t\tweapon_labels[i].colors[weapon_labels[i].count][3] = 255;\n\t\t\t\t\tcount = 0;\n\t\t\t\t\tj += 4;\n\t\t\t\t}\n\t\t\t\telse if (text[j] == '&' && text[j + 1] == 'r') {\n\t\t\t\t\tweapon_labels[i].label[pos++] = '\\0';\n\n\t\t\t\t\tif (count) {\n\t\t\t\t\t\t++weapon_labels[i].count;\n\t\t\t\t\t\tif (weapon_labels[i].count >= sizeof(weapon_labels[i].starts) / sizeof(weapon_labels[i].starts[0])) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tmemset(weapon_labels[i].colors[0], 0, sizeof(weapon_labels[i].colors[0]));\n\t\t\t\t\tcount = 0;\n\t\t\t\t\tj += 1;\n\t\t\t\t}\n\t\t\t\telse if (text[j] == ' ') {\n\t\t\t\t\tweapon_labels[i].label[pos++] = '\\0';\n\n\t\t\t\t\tif (count) {\n\t\t\t\t\t\t++weapon_labels[i].count;\n\t\t\t\t\t\tif (weapon_labels[i].count >= sizeof(weapon_labels[i].starts) / sizeof(weapon_labels[i].starts[0])) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tmemset(weapon_labels[i].colors[0], 0, sizeof(weapon_labels[i].colors[0]));\n\t\t\t\t\tcount = 0;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (!count) {\n\t\t\t\t\t\tweapon_labels[i].starts[weapon_labels[i].count] = pos;\n\t\t\t\t\t}\n\t\t\t\t\tweapon_labels[i].label[pos++] = text[j];\n\t\t\t\t\t++count;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!weapon_labels[i].count && count) {\n\t\t\t\tweapon_labels[i].count = 1;\n\t\t\t}\n\t\t}\n\t}\n\n\tCachePics_MarkAtlasDirty();\n}\n\nvoid SCR_HUD_DrawTracker(hud_t* hud)\n{\n\tint x = 0, y = 0;\n\tfloat width = 0, height = 0;\n\n\tstatic cvar_t\n\t\t*hud_tracker_scale = NULL,\n\t\t*hud_tracker_proportional,\n\t\t*hud_tracker_name_width,\n\t\t*hud_tracker_image_scale,\n\t\t*hud_tracker_align_right;\n\n\tif (!hud_tracker_scale) {\n\t\thud_tracker_scale = HUD_FindVar(hud, \"scale\");\n\t\thud_tracker_proportional = HUD_FindVar(hud, \"proportional\");\n\t\thud_tracker_name_width = HUD_FindVar(hud, \"name_width\");\n\t\thud_tracker_align_right = HUD_FindVar(hud, \"align_right\");\n\t\thud_tracker_image_scale = HUD_FindVar(hud, \"image_scale\");\n\t}\n\n\tVXSCR_MeasureTracker(&width, &height, hud_tracker_scale->value, hud_tracker_proportional->integer, hud_tracker_name_width->integer);\n\n\tif (height > 0 && width > 0 && HUD_PrepareDraw(hud, ceil(width), ceil(height), &x, &y)) {\n\t\tVXSCR_DrawTrackerString(x, y, ceil(width), hud_tracker_name_width->integer, hud_tracker_proportional->integer, hud_tracker_scale->value, hud_tracker_image_scale->value, hud_tracker_align_right->integer);\n\t}\n}\n\n// This needs improved...\nvoid VX_TrackerPickupText(const char* line)\n{\n\tif (VX_FilterPickups()) {\n\t\treturn;\n\t}\n\n\tVX_TrackerAddSimpleText(line, color_white);\n}\n"
  },
  {
    "path": "src/vx_tracker.h",
    "content": "/*\nCopyright (C) 2011 VultureIIC\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef __VX_TRACKER_H__\n#define __VX_TRACKER_H__\n\nextern cvar_t amf_tracker_messages;\nextern cvar_t amf_tracker_time;\nextern cvar_t amf_tracker_align_right;\nextern cvar_t amf_tracker_scale;\n\nvoid VX_TrackerDeath(int player, int weapon, int count);\nvoid VX_TrackerSuicide(int player, int weapon, int count);\nvoid VX_TrackerFragXvsY(int player, int killer, int weapon, int player_wcount, int killer_wcount);\nvoid VX_TrackerOddFrag(int player, int weapon, int wcount);\n\nvoid VX_TrackerTK_XvsY(int player, int killer, int weapon, int p_count, int p_icount, int k_count, int k_icount);\nvoid VX_TrackerOddTeamkill(int player, int weapon, int count);\nvoid VX_TrackerOddTeamkilled(int player, int weapon);\n\nvoid VX_TrackerFlagTouch(int count);\nvoid VX_TrackerFlagDrop(int count);\nvoid VX_TrackerFlagCapture(int count);\n\nchar* GetWeaponName(int num);\nchar* GetColoredWeaponName(int num, const byte *color);\nconst char* GetWeaponImageName(int num);\nconst char* GetWeaponTextName(int num);\nvoid VX_TrackerThink(void);\nvoid VX_TrackerInit(void);\nvoid VX_TrackerClear(void);\nvoid VX_TrackerStreak(int player, int count);\nvoid VX_TrackerStreakEnd(int player, int killer, int count);\nvoid VX_TrackerStreakEndOddTeamkilled(int player, int count);\n\n// This needs improved...\nvoid VX_TrackerPickupText(const char* line);\n\n#define MAX_WEAPON_CLASSES\t\t64\n#define MAX_FRAG_DEFINITIONS\t512\n#define MAX_FRAGMSG_LENGTH\t\t256\n#define MAX_TRACKER_IMAGES      128\n\n#endif // __VX_TRACKER_H__\n"
  },
  {
    "path": "src/vx_vertexlights.c",
    "content": "/*\nCopyright (C) 2011 VULTUREIIC\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// RIOT - Vertex lighting for models\n#include \"quakedef.h\"\n#include \"vx_vertexlights.h\"\n#include \"gl_local.h\"\n\n//#define DEG2RAD(a) (a * M_PI / 180.0)\n\n// Meag: these were set in R_AliasSetupLighting(), but only used in VLight_ResetAnormTable(),\n//       which is only called during startup...\n#define VLIGHT_PITCH 45.0f\n#define VLIGHT_YAW   45.0f\n#define VLIGHT_HIGHCUT (240.0f / 255.0f)\n#define VLIGHT_LOWCUT (40.0f / 255.0f)\n#define VLIGHT_RANGE (VLIGHT_HIGHCUT - VLIGHT_LOWCUT)\n\nstatic byte anorm_pitch[162];\nstatic byte anorm_yaw[162];\n\nstatic float vlighttable[256][256];\n\n#ifdef _WIN32\n#ifndef fabsf\n#define fabsf(x) (float)fabs((double)x)\n#endif\n#ifndef fmodf\n#define fmodf(x, y) (float)fmod((double)x, (double)y)\n#endif\n#endif\n\nstatic float VLight_GetLightValueByAngles(float pitchofs, float yawofs, float apitch, float ayaw)\n{\n\tint pitch, yaw;\n\tfloat retval[4];\n\tfloat weight[2], diff[2];\n\n\tpitchofs = fabsf(pitchofs + (apitch * 256) / 360);\n\tyawofs = fabsf(yawofs) + (ayaw * 256) / 360;\n\n\tpitch = (unsigned int)pitchofs;\n\tyaw = (unsigned int)yawofs;\n\n\tweight[0] = pitchofs - pitch;\n\tweight[1] = yawofs - yaw;\n\n\tretval[0] = vlighttable[pitch % 256][yaw % 256];\n\tretval[1] = vlighttable[(pitch + 1) % 256][yaw % 256];\n\tretval[2] = vlighttable[pitch % 256][(yaw + 1) % 256];\n\tretval[3] = vlighttable[(pitch + 1) % 256][(yaw + 1) % 256];\n\n\tdiff[0] = retval[0] + (retval[1] - retval[0]) * weight[0];\n\tdiff[1] = retval[2] + (retval[3] - retval[2]) * weight[0];\n\n\tretval[0] = (diff[0] + (diff[1] - diff[0]) * weight[1]);\n\tretval[0] = max(retval[0], cl.minlight / 255.0f);\n\treturn bound(0, retval[0], 2);\n}\n\nstatic float VLight_GetLightValue(int index, float apitch, float ayaw)\n{\n\treturn VLight_GetLightValueByAngles(anorm_pitch[index], anorm_yaw[index], apitch, ayaw);\n}\n\nfloat VLight_LerpLightByAngles(float pitchofs1, float yawofs1, float pitchofs2, float yawofs2, float ilerp, float apitch, float ayaw)\n{\n\tfloat lightval1, lightval2;\n\n\tlightval1 = VLight_GetLightValueByAngles(pitchofs1, yawofs1, apitch, ayaw);\n\tif (pitchofs1 == pitchofs2 && yawofs1 == yawofs2) {\n\t\treturn lightval1;\n\t}\n\n\tlightval2 = VLight_GetLightValueByAngles(pitchofs2, yawofs2, apitch, ayaw);\n\treturn (lightval2 * ilerp) + (lightval1 * (1 - ilerp));\n}\n\nfloat VLight_LerpLight(int index1, int index2, float ilerp, float apitch, float ayaw)\n{\n\tfloat lightval1, lightval2;\n\n\tlightval1 = VLight_GetLightValue(index1, apitch, ayaw);\n\tif (index1 == index2) {\n\t\treturn lightval1;\n\t}\n\n\tlightval2 = VLight_GetLightValue(index2, apitch, ayaw);\n\treturn (lightval2 * ilerp) + (lightval1 * (1 - ilerp));\n}\n\nstatic void VLight_ResetAnormTable(void)\n{\n\tint i,j;\n\tfloat angle;\n\tfloat sp, sy, cp, cy;\n\tfloat precut;\n\tvec3_t normal;\n\tvec3_t lightvec;\n\n\t// Define the light vector here\n\tangle\t= DEG2RAD(VLIGHT_PITCH);\n\tsy\t\t= sin(angle);\n\tcy\t\t= cos(angle);\n\tangle\t= DEG2RAD(-VLIGHT_YAW);\n\tsp\t\t= sin(angle);\n\tcp\t\t= cos(angle);\n\tVectorSet(lightvec, cp * cy, cp * sy, -sp);\n\n\t// First thing that needs to be done is the conversion of the\n\t// anorm table into a pitch/yaw table\n\tfor (i = 0; i < NUMVERTEXNORMALS; ++i) {\n\t\textern float r_avertexnormals[NUMVERTEXNORMALS][3];\n\t\tfloat ang[3];\n\n\t\tvectoangles(r_avertexnormals[i], ang);\n\t\tanorm_pitch[i] = ang[0] * 256.0f / 360.0f;\n\t\tanorm_yaw[i] = ang[1] * 256.0f / 360.0f;\n\t}\n\n\t// Next, a light value table must be constructed for pitch/yaw offsets\n\tfor (i = 0; i < 256; i++) {\n\t\tangle = DEG2RAD(i * 360.0f / 256.0f);\n\t\tsy = sin(angle);\n\t\tcy = cos(angle);\n\n\t\tfor (j = 0; j < 256; j++) {\n\t\t\tangle = DEG2RAD(j * 360.0f / 256.0f);\n\t\t\tsp = sin(angle);\n\t\t\tcp = cos(angle);\n\n\t\t\tVectorSet(normal, cp * cy, cp * sy, -sp);\n\n\t\t\t// rescale [-1, 1] => [0, 1]\n\t\t\tprecut = (DotProduct(normal, lightvec) + 1) * 0.5f;\n\t\t\t// rescale within low/high cut range\n\t\t\tprecut = (precut * VLIGHT_RANGE + VLIGHT_LOWCUT);\n\t\t\t// rescale back to [0, 2]\n\t\t\tvlighttable[i][j] = precut * 2;\n\t\t}\n\t}\n}\n\nvoid Init_VLights(void)\n{\n\tVLight_ResetAnormTable();\n}\n"
  },
  {
    "path": "src/vx_vertexlights.h",
    "content": "/*\nCopyright (C) 2011 VultureIIC\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\nfloat VLight_LerpLight(int index1, int index2, float ilerp, float apitch, float ayaw);\nfloat VLight_LerpLightByAngles(float pitchofs1, float yawofs1, float pitchofs2, float yawofs2, float ilerp, float apitch, float ayaw);\n\nvoid Init_VLights(void);\n"
  },
  {
    "path": "src/wad.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n\t$Id: wad.c,v 1.20 2007-10-04 14:56:54 dkure Exp $\n*/\n// wad.c\n\n#include \"quakedef.h\"\n#include \"gl_model.h\"\n#include \"wad.h\"\n#include \"crc.h\"\n\ntypedef struct {\n\tint\t\t\tfilepos;\n\tint\t\t\tdisksize;\n\tint\t\t\tsize;\t\t\t\t\t// uncompressed\n\tchar\t\ttype;\n\tchar\t\tcompression;\n\tchar\t\tpad1, pad2;\n\tchar\t\tname[16];\t\t\t\t// must be null terminated\n} lumpinfo_t;\n\ntypedef struct {\n\tchar\t\tidentification[4];\t\t// should be WAD2 or 2DAW\n\tint\t\t\tnumlumps;\n\tint\t\t\tinfotableofs;\n} wadinfo_t;\n\nstatic int\t\t\twad_numlumps;\nstatic int\t\t\twad_filesize;\nstatic lumpinfo_t\t*wad_lumps;\nstatic byte\t\t\t*wad_base = NULL;\n\nstatic void W_InsertOcranaLeds(byte *data);\n\nvoid SwapPic(qpic_t *pic)\n{\n\tpic->width = LittleLong(pic->width);\n\tpic->height = LittleLong(pic->height);\n}\n\n/*\nLowercases name and pads with spaces and a terminating 0 to the length of lumpinfo_t->name.\nUsed so lumpname lookups can proceed rapidly by comparing 4 chars at a time\nSpace padding is so names can be printed nicely in tables. Can safely be performed in place.\n*/\nstatic void W_CleanupName(const char *in, char *out)\n{\n\tint i, c;\n\n\tfor (i = 0; i < 16; i++) {\n\t\tc = in[i];\n\t\tif (!c)\n\t\t\tbreak;\n\n\t\tif (c >= 'A' && c <= 'Z')\n\t\t\tc += ('a' - 'A');\n\t\tout[i] = c;\n\t}\n\n\tfor (; i < 16; i++) {\n\t\tout[i] = 0;\n\t}\n}\n\nvoid W_FreeWadFile(void)\n{\n\tQ_free(wad_base);\n\twad_base = NULL;\n\twad_lumps = NULL;\n\twad_numlumps = 0;\n\twad_filesize = 0;\n}\n\nvoid W_LoadWadFile(const char *filename)\n{\n\tlumpinfo_t *lump_p;\n\twadinfo_t *header;\n\tunsigned i;\n\tint infotableofs;\n\n\t// only one .wad can be loaded at a time\n\tW_FreeWadFile();\n\n\twad_base = FS_LoadHeapFile(filename, &wad_filesize);\n\n\tif (!wad_base) {\n\t\tif (!strcmp(filename, \"gfx.wad\"))\n\t\t\tSys_Error(\"Couldn't load gfx.wad.\\n\"\n\t\t\t\t\"This usually happens when you don't have original Quake 1 files in id1 subdirectory.\\n\"\n\t\t\t\t\"Ensure you have pak0.pak and pak1.pak in subdirectory id1.\");\n\t\telse\n\t\t\tSys_Error(\"W_LoadWadFile: couldn't load %s\", filename);\n\t}\n\theader = (wadinfo_t *)wad_base;\n\n\tif (memcmp(header->identification, \"WAD2\", 4))\n\t\tSys_Error(\"Wad file %s doesn't have WAD2 id\\n\", filename);\n\n\twad_numlumps = LittleLong(header->numlumps);\n\tinfotableofs = LittleLong(header->infotableofs);\n\twad_lumps = (lumpinfo_t *)(wad_base + infotableofs);\n\n\tif (infotableofs + wad_numlumps * sizeof(lump_t) > wad_filesize)\n\t\tSys_Error(\"Wad lump table exceeds file size\");\n\n\tfor (i = 0, lump_p = wad_lumps; i < wad_numlumps; i++, lump_p++) {\n\t\tlump_p->filepos = LittleLong(lump_p->filepos);\n\t\tlump_p->size = LittleLong(lump_p->size);\n\n\t\tW_CleanupName(lump_p->name, lump_p->name);\n\n\t\tif (lump_p->filepos < sizeof(wadinfo_t) || lump_p->filepos + LittleLong(lump_p->disksize) > wad_filesize)\n\t\t\tSys_Error(\"Wad lump %s exceeds file size\", lump_p->name);\n\n\t\tif (lump_p->type == TYP_QPIC)\n\t\t\tSwapPic((qpic_t *)(wad_base + lump_p->filepos));\n\t}\n}\n\nlumpinfo_t *W_GetLumpinfo(const char *name)\n{\n\tint i;\n\tlumpinfo_t\t*lump_p;\n\tchar clean[16];\n\n\tW_CleanupName(name, clean);\n\tfor (lump_p = wad_lumps, i = 0; i < wad_numlumps; i++, lump_p++) {\n\t\tif (!strcmp(clean, lump_p->name)) {\n\t\t\treturn lump_p;\n\t\t}\n\t}\n\n\tSys_Error(\"W_GetLumpinfo: %s not found\", name);\n\treturn NULL;\n}\n\nvoid* W_GetLumpName(const char *name)\n{\n\tlumpinfo_t* lump = W_GetLumpinfo(name);\n\n\t// Make sure we have a lump. \n\tif (!lump) {\n\t\treturn NULL;\n\t}\n\n\t// If we got the conchars lump, check if the CRC is the same\n\t// as in the original gfx.wad, and if so, insert leds into it.\n\tif (!strcmp(name, \"conchars\")) {\n\t\tif (lump->filepos + lump->size < wad_filesize\n\t\t\t&& CRC_Block(wad_base + lump->filepos, lump->size) == 798) {\n\t\t\tW_InsertOcranaLeds(wad_base + lump->filepos);\n\t\t}\n\t}\n\n\treturn (void *)(wad_base + lump->filepos);\n}\n\nvoid* W_GetLumpNum(int num)\n{\n\tlumpinfo_t *lump;\n\n\tif (num < 0 || num > wad_numlumps) {\n\t\tSys_Error(\"W_GetLumpNum: bad number: %i\", num);\n\t}\n\n\tlump = wad_lumps + num;\n\n\treturn (void *)(wad_base + lump->filepos);\n}\n\nstatic byte ocrana_leds[4][8][8] = {\n\t{\n\t\t// green\n\t\t{0x00,0x38,0x3b,0x3b,0x3b,0x3b,0x35,0x00},\n\t\t{0x38,0x3b,0x3d,0x3f,0x3f,0x3d,0x38,0x35},\n\t\t{0x3b,0x3d,0xfe,0x3f,0x3f,0x3f,0x3b,0x35},\n\t\t{0x3b,0x3f,0x3f,0x3f,0x3f,0x3f,0x3b,0x35},\n\t\t{0x3b,0x3f,0x3f,0x3f,0x3f,0x3d,0x3b,0x35},\n\t\t{0x3b,0x3d,0x3f,0x3f,0x3d,0x3b,0x38,0x35},\n\t\t{0x35,0x38,0x3b,0x3b,0x3b,0x38,0x35,0x35},\n\t\t{0x00,0x35,0x35,0x35,0x35,0x35,0x35,0x00}\n\t},\n\t{\n\t\t// red\n\t\t{0x00,0xf8,0xf9,0xf9,0xf9,0xf9,0x4c,0x00},\n\t\t{0xf8,0xf9,0xfa,0xfb,0xfb,0xfa,0xf8,0x4c},\n\t\t{0xf9,0xfa,0xfe,0xfb,0xfb,0xfb,0xf9,0x4c},\n\t\t{0xf9,0xfb,0xfb,0xfb,0xfb,0xfb,0xf9,0x4c},\n\t\t{0xf9,0xfb,0xfb,0xfb,0xfb,0xfa,0xf9,0x4c},\n\t\t{0xf9,0xfa,0xfb,0xfb,0xfa,0xf9,0xf8,0x4c},\n\t\t{0x4c,0xf8,0xf9,0xf9,0xf9,0xf8,0x4c,0x4c},\n\t\t{0x00,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x00}\n\t},\n\t{\n\t\t// yellow\n\t\t{0x00,0xc8,0xc5,0xc5,0xc5,0xc5,0xcb,0x00},\n\t\t{0xc8,0xc5,0xc2,0x6f,0x6f,0xc2,0xc8,0xcb},\n\t\t{0xc5,0xc2,0xfe,0x6f,0x6f,0x6f,0xc5,0xcb},\n\t\t{0xc5,0x6f,0x6f,0x6f,0x6f,0x6f,0xc5,0xcb},\n\t\t{0xc5,0x6f,0x6f,0x6f,0x6f,0xc2,0xc5,0xcb},\n\t\t{0xc5,0xc2,0x6f,0x6f,0xc2,0xc5,0xc8,0xcb},\n\t\t{0xcb,0xc8,0xc5,0xc5,0xc5,0xc8,0xcb,0xcb},\n\t\t{0x00,0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,0x00}\n\t},\n\t{\n\t\t// blue\n\t\t{0x00,0xd8,0xd5,0xd5,0xd5,0xd5,0xdc,0x00},\n\t\t{0xd8,0xd5,0xd2,0xd0,0xd0,0xd2,0xd8,0xdc},\n\t\t{0xd5,0xd2,0xfe,0xd0,0xd0,0xd0,0xd5,0xdc},\n\t\t{0xd5,0xd0,0xd0,0xd0,0xd0,0xd0,0xd5,0xdc},\n\t\t{0xd5,0xd0,0xd0,0xd0,0xd0,0xd2,0xd5,0xdc},\n\t\t{0xd5,0xd2,0xd0,0xd0,0xd2,0xd5,0xd8,0xdc},\n\t\t{0xdc,0xd8,0xd5,0xd5,0xd5,0xd8,0xdc,0xdc},\n\t\t{0x00,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0x00}\n\t}\n};\n\nstatic void W_InsertOcranaLeds(byte *data)\n{\n\tbyte *leddata;\n\tint i, x, y;\n\n\tfor (i = 0; i < 4; i++) {\n\t\tleddata = data + (0x80 >> 4 << 10) + (0x06 << 3) + (i << 3);\n\n\t\tfor (y = 0; y < 8; y++) {\n\t\t\tfor (x = 0; x < 8; x++)\n\t\t\t\t*leddata++ = ocrana_leds[i][y][x];\n\t\t\tleddata += 128 - 8;\n\t\t}\n\t}\n}\n\n/*\n=============================================================================\nWAD3 Texture Loading for BSP 3.0 Support\n=============================================================================\n*/\n\n#define TEXWAD_MAXIMAGES 16384\ntypedef struct {\n\tchar name[MAX_QPATH];\n\tvfsfile_t *file;\n\tint position;\n\tint size;\n} texwadlump_t;\n\nstatic texwadlump_t texwadlump[TEXWAD_MAXIMAGES];\nstatic int wad3_numlumps = 0;\n\nvoid WAD3_LoadWadFile(const char *filename)\n{\n\tlumpinfo_t *lumps, *lump_p;\n\twadinfo_t header;\n\tint i, j, infotableofs, numlumps, lowmark;\n\tvfsfile_t *file;\n\tvfserrno_t err;\n\n\tif (wad3_numlumps == TEXWAD_MAXIMAGES)\n\t\treturn;\n\n\tif (!(file = FS_OpenVFS(va(\"textures/halflife/%s\", filename), \"rb\", FS_ANY)))\n\t\tif (!(file = FS_OpenVFS(va(\"textures/wad3/%s\", filename), \"rb\", FS_ANY)))\n\t\t\tif (!(file = FS_OpenVFS(va(\"textures/%s\", filename), \"rb\", FS_ANY)))\n\t\t\t\tif (!(file = FS_OpenVFS(filename, \"rb\", FS_ANY))) {\n\t\t\t\t\tCom_Printf(\"WAD3_LoadWadFile: couldn't load halflife wad \\\"%s\\\"\", filename);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\tif (VFS_READ(file, &header, sizeof(wadinfo_t), &err) != sizeof(wadinfo_t)) {\n\t\tCom_Printf(\"WAD3_LoadWadFile: unable to read wad header\\n\");\n\t\treturn;\n\t}\n\n\tif (memcmp(header.identification, \"WAD3\", 4)) {\n\t\tCom_Printf(\"WAD3_LoadWadFile: Wad file %s doesn't have WAD3 id\\n\", filename);\n\t\treturn;\n\t}\n\n\tnumlumps = LittleLong(header.numlumps);\n\tif (numlumps < 1 || numlumps > TEXWAD_MAXIMAGES) {\n\t\tCom_Printf(\"WAD3_LoadWadFile: invalid number of lumps (%i)\\n\", numlumps);\n\t\treturn;\n\t}\n\n\tinfotableofs = LittleLong(header.infotableofs);\n\tif (VFS_SEEK(file, infotableofs, SEEK_SET)) {\n\t\tCom_Printf(\"WAD3_LoadWadFile: unable to seek to lump table\\n\");\n\t\treturn;\n\t}\n\n\tlowmark = Hunk_LowMark();\n\tif (!(lumps = Hunk_AllocName(sizeof(lumpinfo_t) * numlumps, filename))) {\n\t\tCom_Printf(\"WAD3_LoadWadFile: unable to allocate temporary memory for lump table\\n\");\n\t\treturn;\n\t}\n\n\tif (((size_t)VFS_READ(file, lumps, sizeof(lumpinfo_t)*numlumps, &err)) != sizeof(lumpinfo_t) * numlumps) {\n\t\tCom_Printf(\"WAD3_LoadWadFile: unable to read lump table\\n\");\n\t\tHunk_FreeToLowMark(lowmark);\n\t\treturn;\n\t}\n\n\tfor (i = 0, lump_p = lumps; i < numlumps; i++, lump_p++) {\n\t\tW_CleanupName(lump_p->name, lump_p->name);\n\t\tfor (j = 0; j < wad3_numlumps; j++) {\n\t\t\tif (!strcmp(lump_p->name, texwadlump[j].name))\n\t\t\t\tgoto skip_duplicate;\n\t\t}\n\t\tstrlcpy(texwadlump[j].name, lump_p->name, sizeof(texwadlump[j].name));\n\t\ttexwadlump[j].file = file;\n\t\ttexwadlump[j].position = LittleLong(lump_p->filepos);\n\t\ttexwadlump[j].size = LittleLong(lump_p->disksize);\n\t\twad3_numlumps++;\n\t\tif (wad3_numlumps == TEXWAD_MAXIMAGES) {\n\t\t\tbreak;\n\t\t}\nskip_duplicate:\n\t\t;\n\t}\n\n\tHunk_FreeToLowMark(lowmark);\n\t//leaves the file open\n}\n\n//converts paletted to rgba\nbyte *ConvertWad3ToRGBA(int width, int height, byte *in, qbool alpha)\n{\n\tbyte *data, *pal;\n\tint i, p, image_size;\n\n\timage_size = width * height;\n\tdata = Q_malloc(image_size * 4);\n\n\tpal = (byte *)in + ((image_size * 85) / 64) + 2;\n\n\tfor (i = 0; i < image_size; i++) {\n\t\tp = *in++;\n\t\tif (p == 255 && alpha) {\n\t\t\t((int *)data)[i] = 0;\n\t\t}\n\t\telse {\n\t\t\tp *= 3;\n\t\t\tdata[i * 4 + 0] = pal[p];\n\t\t\tdata[i * 4 + 1] = pal[p + 1];\n\t\t\tdata[i * 4 + 2] = pal[p + 2];\n\t\t\tdata[i * 4 + 3] = 255;\n\t\t}\n\t}\n\treturn data;\n}\n\nbyte *WAD3_LoadTexture(texture_t *tx)\n{\n\tint i, j = 0;\n\tmiptex_t *mt;\n\tbyte *data;\n\tvfsfile_t *file;\n\tvfserrno_t err;\n\n\tif (tx->offsets[0])\n\t\treturn ConvertWad3ToRGBA(tx->width, tx->height, (byte *)(tx + 1), (tx->name[0] == '{'));\n\n\tfor (i = 0; i < wad3_numlumps; i++) {\n\t\tif (strcasecmp(tx->name, texwadlump[i].name))\n\t\t\tcontinue;\n\n\t\tfile = texwadlump[i].file;\n\t\tif (VFS_SEEK(file, texwadlump[i].position, SEEK_SET)) {\n\t\t\tCom_Printf(\"WAD3_LoadTexture: corrupt WAD3 file\\n\");\n\t\t\treturn NULL;\n\t\t}\n\t\tmt = Q_malloc(texwadlump[i].size);\n\t\tif (VFS_READ(file, mt, texwadlump[i].size, &err) < texwadlump[i].size) {\n\t\t\tCom_Printf(\"WAD3_LoadTexture: corrupt WAD3 file\\n\");\n\t\t\tQ_free(mt);\n\t\t\treturn NULL;\n\t\t}\n\t\tmt->width = LittleLong(mt->width);\n\t\tmt->height = LittleLong(mt->height);\n\t\tif (mt->width != tx->width || mt->height != tx->height) {\n\t\t\tQ_free(mt);\n\t\t\treturn NULL;\n\t\t}\n\t\tfor (j = 0;j < MIPLEVELS;j++)\n\t\t\tmt->offsets[j] = LittleLong(mt->offsets[j]);\n\t\tdata = ConvertWad3ToRGBA(mt->width, mt->height, (byte *)(mt + 1), (tx->name[0] == '{'));\n\t\tQ_free(mt);\n\t\treturn data;\n\t}\n\treturn NULL;\n}\n"
  },
  {
    "path": "src/wad.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// wad.h\n\n#ifndef _WAD_H\n#define _WAD_H\n\n//#include \"bspfile.h\"\n\n//===============\n//   TYPES\n//===============\n\n#define\tCMP_NONE\t\t0\n#define\tCMP_LZSS\t\t1\n\n#define\tTYP_NONE\t\t0\n#define\tTYP_LABEL\t\t1\n\n#define\tTYP_LUMPY\t\t64\t\t\t\t// 64 + grab command number\n#define\tTYP_PALETTE\t\t64\n#define\tTYP_QTEX\t\t65\n#define\tTYP_QPIC\t\t66\n#define\tTYP_SOUND\t\t67\n#define\tTYP_MIPTEX\t\t68\n\ntypedef struct {\n\tint\t\t\twidth, height;\n\tbyte\t\tdata[4];\t\t\t// variably sized\n} qpic_t;\n\nvoid W_LoadWadFile(const char *filename);\nvoid* W_GetLumpName(const char *name);\nvoid W_FreeWadFile(void);\n\nvoid SwapPic(qpic_t *pic);\n\nvoid WAD3_LoadWadFile(const char *filename);\nbyte* WAD3_LoadTexture(texture_t *tx);\n\n#endif // _WAD_H\n"
  },
  {
    "path": "src/winquake.rc",
    "content": "#include <windows.h>\n#include \"version.h\"\n\n1 VERSIONINFO\n{\n\tBLOCK \"StringFileInfo\"\n\t{\n\t\tBLOCK \"040904B0\"\n\t\t{\n\t\t\tVALUE \"ProductName\", \"ezQuake\"\n\t\t\tVALUE \"ProductVersion\", VERSION_NUMBER \" Build \" VERSION\n\t\t\tVALUE \"FileDescription\", \"ezQuake v\" VERSION_NUMBER \" - QuakeWorld(r) Today\"\n\t\t\tVALUE \"FileVersion\", VERSION_NUMBER\n\t\t\tVALUE \"CompanyName\", \"The ezQuake Developers\"\n\t\t\tVALUE \"OriginalFileName\", \"ezquake.exe\"\n\t\t\tVALUE \"InternalName\", \"ezquake\"\n\t\t}\n\t}\n\n\tBLOCK \"VarFileInfo\"\n\t{\n\t\tVALUE \"Translation\",  0x0409, 0x04B0\n\t}\n}\n\n1 ICON \"ezquake.ico\"\nCREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST \"ezquake.exe.manifest\"\n"
  },
  {
    "path": "src/xsd.c",
    "content": "/*\nCopyright (C) 2011 VULTUREIIC\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n// $Id: xsd.c,v 1.14 2007-10-29 12:48:12 cokeman1982 Exp $\n\n#include \"quakedef.h\"\n#include \"expat.h\"\n#include \"xsd.h\"\n\ntypedef xml_t * (*XSD_DocumentLoadType)(vfsfile_t *v, int len);\ntypedef void (*XSD_DocumentFreeType)(xml_t *);\ntypedef xml_document_t * (*XSD_DocumentConvertType)(xml_t *);\n\ntypedef struct xsd_mapping_s\n{\n    char *document_type;\n    XSD_DocumentLoadType load_function;\n    XSD_DocumentFreeType free_function;\n    XSD_DocumentConvertType convert_function;\n}\nxsd_mapping_t;\n\nxsd_mapping_t xsd_mappings[] = {\n    //{\"variable\", XSD_Variable_LoadFromHandle, XSD_Variable_Free, XSD_Variable_Convert},\n    //{\"command\", XSD_Command_LoadFromHandle, XSD_Command_Free, NULL},\n    {\"document\", XSD_Document_LoadFromHandle, XSD_Document_Free, NULL},\n    {NULL, NULL, NULL}\n};\n\n// get value attribute from the array\nconst char *XSD_GetAttribute(const char **atts, const char *name)\n{\n    while (*atts)\n    {\n        if (!strcmp(*atts, name))\n            return *(atts+1);\n\n        atts += 2;\n    }\n\n    return NULL;\n}\n\n// add text (strcat) with realocation\nchar *XSD_AddText(char *dst, const char *src, int src_len)\n{\n    char *buf;\n\tsize_t len;\n\n    if (dst == NULL)\n    {\n        buf = (char *) Q_malloc(src_len + 1);\n        memcpy(buf, src, src_len);\n        buf[src_len] = 0;\n    }\n    else\n    {\n\t\tlen = 1 + src_len + strlen(dst);\n        buf = (char *) Q_malloc(len);\n        strlcpy (buf, dst, len);\n        memcpy(buf+strlen(buf), src, src_len);\n        buf[src_len+strlen(dst)] = 0;\n        Q_free(dst);\n    }\n\n    return buf;\n}\n\n// test if character is valid XML space\nint XSD_IsSpace(char c)\n{\n    if (c == ' '  ||  c == '\\t'  ||  c == '\\n'  ||  c == '\\r')\n        return 1;\n    return 0;\n}\n\n// strip spaces multiple spaces from in-between words\nchar *XSD_StripSpaces (char *str)\n{\n    char *buf, *ret;\n    unsigned int p = 0, q = 0;\n\n    if (str == NULL)\n        return str;\n\n    buf = (char *) Q_malloc(strlen(str)+1);\n    for (p=0; p < strlen(str); p++)\n    {\n        if (XSD_IsSpace(str[p]))\n        {\n            if (q == 0  ||  XSD_IsSpace(buf[q-1]))\n                ;\n            else\n                buf[q++] = ' ';\n        }\n        else\n            buf[q++] = str[p];\n    }\n\n    // strip spaces from the end\n    while (q > 0  &&  XSD_IsSpace(buf[q-1]))\n        q--;\n    buf[q] = 0;\n\n    ret = (char *) Q_strdup(buf);\n    Q_free(buf);\n    Q_free(str);\n    return ret;\n}\n\n// check if we are somewhere inside given element\nint XSD_IsIn(char *path, char *subPath)\n{\n    size_t sublen = strlen(subPath);\n\n    // if path is shorter than subpath\n    if (strlen(path) < sublen)\n        return 0;\n\n    // if it matches\n    if (!strncmp(path, subPath, sublen)  &&\n        (path[sublen] == 0  ||  path[sublen] == '/'))\n    {\n        return 1;\n    }\n\n    // no match\n    return 0;\n}\n\n// initialize parser stack\nvoid XSD_InitStack(xml_parser_stack_t *stack)\n{\n    memset(stack, 0, sizeof(xml_parser_stack_t));\n}\n\n// restore parser handlers from previous one in stack\nvoid XSD_RestoreStack(xml_parser_stack_t *stack)\n{\n    XML_SetStartElementHandler(stack->parser, stack->oldStartHandler);\n    XML_SetEndElementHandler(stack->parser, stack->oldEndHandler);\n    XML_SetCharacterDataHandler(stack->parser, stack->oldCharacterDataHandler);\n    XML_SetUserData(stack->parser, stack->oldUserData);\n}\n\n// call when element starts\nvoid XSD_OnStartElement(xml_parser_stack_t *stack, const XML_Char *name, const XML_Char **atts)\n{\n    strlcat(stack->path, \"/\", sizeof (stack->path));\n    strlcat(stack->path, name, sizeof (stack->path));\n}\n\n// call when element ends\nvoid XSD_OnEndElement(xml_parser_stack_t *stack, const XML_Char *name)\n{\n    char *t = strrchr(stack->path, '/');\n    *t = 0;\n}\n\nstatic void XSD_DetectType_OnStartElement(void *userData, const XML_Char *name, const XML_Char **atts)\n{\n    char *type = (char *) userData;\n\n    if (type[0] == 0)\n        strcpy(type, name);\n}\n\n// load document, auto-recognizing its type, returns NULL in case of failure\nxml_t * XSD_LoadDocument(char *filename)\n{\n    xml_t *ret = NULL;\n    int i;\n\tvfsfile_t *f;\n    XML_Parser parser = NULL;\n    int len;\n\tint filelen;\n    char buf[XML_READ_BUFSIZE];\n    char document_type[1024];\n\t\n\t// FIXME: D-Kure, does FS_ANY handle both the above cases\n\tif (!(f = FS_OpenVFS(filename, \"rb\", FS_ANY))) {\n\t\treturn NULL;\n\t}\n\tfilelen = VFS_GETLEN(f);\n\n    // initialize XML parser\n    parser = XML_ParserCreate(NULL);\n\tif (parser == NULL) {\n\t\tCom_Printf(\"could not open2\\n\");\n        goto error;\n\t}\n    XML_SetStartElementHandler(parser, XSD_DetectType_OnStartElement);\n    XML_SetUserData(parser, document_type);\n\n    document_type[0] = 0;\n\n    while (document_type[0] == 0  &&  (len = VFS_READ(f, buf, XML_READ_BUFSIZE, NULL)) > 0)\n    {\n\t\tif (XML_Parse(parser, buf, len, 0) != XML_STATUS_OK) {\n\t\t\tCom_Printf(\"could not open3\\n\");\n            goto error;\n\t\t}\n    }\n\tif (document_type[0] == 0) {\n\t\tCom_Printf(\"could not open4\\n\");\n        goto error;\n\t}\n\n    // now we know what document type it is...\n\n    // parser is no more needed\n    XML_ParserFree(parser);\n    parser = NULL;\n\n    // fseek to the beginning of the file\n\tVFS_SEEK(f, 0, SEEK_SET);\n\n    // execute loading parser\n    i = 0;\n    while (xsd_mappings[i].document_type != NULL)\n    {\n        if (!strcmp(xsd_mappings[i].document_type, document_type))\n        {\n\t\t\t//???\n            ret = xsd_mappings[i].load_function(f, filelen);\n            break;\n        }\n        i++;\n    }\n\n    if (ret)\n    {\n\t\tVFS_CLOSE(f);\n        return ret;\n    }\n\nerror:\n    if (f)\n\t\tVFS_CLOSE(f);\n\n    if (parser)\n        XML_ParserFree(parser);\n    return NULL;\n}\n\n// load document and convert it to xml_document_t\nxml_document_t * XSD_LoadDocumentWithXsl(char *filename)\n{\n    xml_t *doc;\n    xml_document_t *document;\n\n    doc = XSD_LoadDocument(filename);\n\n    if (doc == NULL)\n        return NULL;\n\n    if (!strcmp(doc->document_type, \"document\"))\n        return (xml_document_t *) doc; // no conversion\n\n    // convert\n    document = XSD_XslConvert(doc);\n    XSD_FreeDocument(doc);\n    return document;\n\n}\n\n// free document loaded with XSD_LoadDocument\nvoid XSD_FreeDocument(xml_t *document)\n{\n    int i = 0;\n    while (xsd_mappings[i].document_type != NULL)\n    {\n        if (!strcmp(xsd_mappings[i].document_type, document->document_type))\n        {\n            xsd_mappings[i].free_function(document);\n            return;\n        }\n        i++;\n    }\n}\n\n// convert any known xml document to \"document\" type\nxml_document_t * XSD_XslConvert(xml_t *doc)\n{\n    int i = 0;\n    while (xsd_mappings[i].document_type != NULL)\n    {\n        if (!strcmp(xsd_mappings[i].document_type, doc->document_type))\n        {\n            if (xsd_mappings[i].convert_function)\n                return (xml_document_t *)xsd_mappings[i].convert_function(doc);\n            else\n                return NULL;\n        }\n        i++;\n    }\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/xsd.h",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n#ifndef __XSD_H__\n#define __XSD_H__\n\n\n#define XML_READ_BUFSIZE 4096\n\ntypedef struct xml_s\n{\n    char *document_type;\n\n    // ----------------\n\n    // rest goes here...\n}\nxml_t;\n\n\n// include different document types handlers\n#include \"xsd_document.h\"\n\n\n#define PARSER_STACK_PATH_SIZE  1024    // temporary\n\ntypedef struct xml_parser_stack_s\n{\n    XML_Parser parser;\n    xml_t *document;\n\n    char path[PARSER_STACK_PATH_SIZE];\n\n    // previous parser handlers\n    // allows for switching parser functions during parsing\n    XML_StartElementHandler oldStartHandler;\n    XML_EndElementHandler oldEndHandler;\n    XML_CharacterDataHandler oldCharacterDataHandler;\n    void *oldUserData;\n\n    // parser specific data\n    void *parser_specific;\n}\nxml_parser_stack_t;\n\n\n// get value attribute from the array\nconst char *XSD_GetAttribute(const char **atts, const char *name);\n\n// add text (strcat) with realocation\nchar *XSD_AddText(char *dst, const char *src, int src_len);\n\n// test if character is valid XML space\nint XSD_IsSpace(char);\n\n// strip spaces multiple spaces from in-between words\nchar *XSD_StripSpaces(char *);\n\n// check if we are somewhere inside given element\nint XSD_IsIn(char *path, char *subPath);\n\n// initialize parser stack\nvoid XSD_InitStack(xml_parser_stack_t *stack);\n\n// restore parser handlers from previous one in stack\nvoid XSD_RestoreStack(xml_parser_stack_t *stack);\n\n// call when element starts\nvoid XSD_OnStartElement(xml_parser_stack_t *stack, const XML_Char *name, const XML_Char **atts);\n\n// call when element ends\nvoid XSD_OnEndElement(xml_parser_stack_t *stack, const XML_Char *name);\n\n\n\n\n// load document, auto-recognizing its type, returns NULL in case of failure\nxml_t * XSD_LoadDocument(char *filename);\n\n// load document and convert it to xml_document_t\nxml_document_t * XSD_LoadDocumentWithXsl(char *filename);\n\n// free document loaded with XSD_LoadDocument\nvoid XSD_FreeDocument(xml_t *);\n\n// convert any known xml document to \"document\" type\nxml_document_t * XSD_XslConvert(xml_t *doc);\n\n\n\n#endif // __XSD_H__\n"
  },
  {
    "path": "src/xsd_document.c",
    "content": "/*\nCopyright (C) 2011 ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n// $Id: xsd_document.c,v 1.9 2007-10-04 14:56:54 dkure Exp $\n\n#include \"quakedef.h\"\n#include \"expat.h\"\n#include \"xsd.h\"\n#include \"xsd_document.h\"\n\n\n// general parser\nstatic void OnStartElement(void *userData, const XML_Char *name, const XML_Char **atts);\nstatic void OnEndElement(void *userData, const XML_Char *name);\nstatic void OnCharacterData(void *userData, const XML_Char *s, int len);\n\n// BLOCKS parser\nstatic void OnStartElement_Blocks(void *userData, const XML_Char *name, const XML_Char **atts);\nstatic void OnEndElement_Blocks(void *userData, const XML_Char *name);\nstatic void OnCharacterData_Blocks(void *userData, const XML_Char *s, int len);\n\n\n// init xml_document_t struct\nstatic void XSD_Document_Init(xml_document_t *document)\n{\n    memset(document, 0, sizeof(xml_document_t));\n}\n\n// create new document\nxml_document_t * XSD_Document_New(void)\n{\n    xml_document_t *doc = (xml_document_t *) Q_malloc(sizeof(xml_document_t));\n    XSD_Document_Init(doc);\n    doc->document_type = Q_strdup(\"document\");\n    return doc;\n}\n\nstatic void XSD_Free_Tags(document_tag_t *t);\n\n// free document_tag_p_t\nstatic void XSD_Free_Tag_P(document_tag_p_t *p)\n{\n    XSD_Free_Tags(p->tags);\n    Q_free(p);\n}\n\n// free document_tag_section_t\nstatic void XSD_Free_Tag_Section(document_tag_section_t *s)\n{\n    if (s->id)\n        Q_free(s->id);\n    if (s->title)\n        Q_free(s->title);\n    XSD_Free_Tags(s->tags);\n    Q_free(s);\n}\n\n// free document_tag_em_t\nstatic void XSD_Free_Tag_Em(document_tag_em_t *i)\n{\n    XSD_Free_Tags(i->tags);\n    Q_free(i);\n}\n\n// free document_tag_color_t\nstatic void XSD_Free_Tag_Color(document_tag_color_t *c)\n{\n    XSD_Free_Tags(c->tags);\n    Q_free(c);\n}\n\n// free document_tag_a_t\nstatic void XSD_Free_Tag_A(document_tag_a_t *a)\n{\n    if (a->href)\n        Q_free(a->href);\n    XSD_Free_Tags(a->tags);\n    Q_free(a);\n}\n\n// free document_tag_img_t\nstatic void XSD_Free_Tag_Img(document_tag_img_t *i)\n{\n    XSD_Free_Tags(i->tags);\n    Q_free(i);\n}\n\n// free document_tag_br_t\nstatic void XSD_Free_Tag_Br(document_tag_br_t *br)\n{\n    Q_free(br);\n}\n\n// free document_tag_sp_t\nstatic void XSD_Free_Tag_Sp(document_tag_sp_t *sp)\n{\n    Q_free(sp);\n}\n\n// free document_tag_hr_t\nstatic void XSD_Free_Tag_Hr(document_tag_hr_t *hr)\n{\n    Q_free(hr);\n}\n\n// free document_tag_pre_t\nstatic void XSD_Free_Tag_Pre(document_tag_pre_t *pre)\n{\n    if (pre->text)\n        Q_free(pre->text);\n    if (pre->alt)\n        Q_free(pre->alt);\n    Q_free(pre);\n}\n\n// free document_tag_h_t\nstatic void XSD_Free_Tag_H(document_tag_h_t *h)\n{\n    XSD_Free_Tags(h->tags);\n    Q_free(h);\n}\n\n// free document_tag_list_t\nstatic void XSD_Free_Tag_List(document_tag_list_t *l)\n{\n    XSD_Free_Tags((document_tag_t *)l->items);\n    Q_free(l);\n}\n\n// free document_tag_li_t\nstatic void XSD_Free_Tag_Li(document_tag_li_t *l)\n{\n    XSD_Free_Tags(l->tags);\n    Q_free(l);\n}\n\n// free document_tag_list_t\nstatic void XSD_Free_Tag_Dict(document_tag_dict_t *l)\n{\n    XSD_Free_Tags((document_tag_t *)l->items);\n    Q_free(l);\n}\n\n// free document_tag_di_t\nstatic void XSD_Free_Tag_Di(document_tag_di_t *l)\n{\n    XSD_Free_Tags(l->name);\n    XSD_Free_Tags(l->description);\n    Q_free(l);\n}\n\n// free document_tag_text_t tag\nstatic void XSD_Free_Tag_Text(document_tag_text_t *t)\n{\n    if (t->text)\n        Q_free(t->text);\n    Q_free(t);\n}\n\n// free tag chain\nstatic void XSD_Free_Tags(document_tag_t *t)\n{\n    while (t)\n    {\n        document_tag_t *next = t->next;\n\n        switch (t->type)\n        {\n        case tag_text:\n            XSD_Free_Tag_Text((document_tag_text_t *) t);\n            break;\n\n        case tag_section:\n            XSD_Free_Tag_Section((document_tag_section_t *) t);\n            break;\n\n        case tag_p:\n            XSD_Free_Tag_P((document_tag_p_t *) t);\n            break;\n\n        case tag_hr:\n            XSD_Free_Tag_Hr((document_tag_hr_t *) t);\n            break;\n\n        case tag_pre:\n            XSD_Free_Tag_Pre((document_tag_pre_t *) t);\n            break;\n\n        case tag_h:\n            XSD_Free_Tag_H((document_tag_h_t *) t);\n            break;\n\n        case tag_list:\n            XSD_Free_Tag_List((document_tag_list_t *) t);\n            break;\n\n        case tag_li:\n            XSD_Free_Tag_Li((document_tag_li_t *) t);\n            break;\n\n        case tag_dict:\n            XSD_Free_Tag_Dict((document_tag_dict_t *) t);\n            break;\n\n        case tag_di:\n            XSD_Free_Tag_Di((document_tag_di_t *) t);\n            break;\n\n        case tag_em:\n            XSD_Free_Tag_Em((document_tag_em_t *) t);\n            break;\n\n        case tag_color:\n            XSD_Free_Tag_Color((document_tag_color_t *) t);\n            break;\n\n        case tag_a:\n            XSD_Free_Tag_A((document_tag_a_t *) t);\n            break;\n\n        case tag_img:\n            XSD_Free_Tag_Img((document_tag_img_t *) t);\n            break;\n\n        case tag_br:\n            XSD_Free_Tag_Br((document_tag_br_t *) t);\n            break;\n\n        case tag_sp:\n            XSD_Free_Tag_Sp((document_tag_sp_t *) t);\n            break;\n\n        default:\n            assert(0);\n        }\n\n        t = next;\n    }\n}\n\n// free xml_document_t struct\nvoid XSD_Document_Free(xml_t *doc)\n{\n    xml_document_t *document = (xml_document_t *) doc;\n\n    if (document->title)\n        Q_free(document->title);\n\n    XSD_Free_Tags(document->content);\n\n    if (document->document_type)\n        Q_free(document->document_type);\n\n    // delete document\n    Q_free(document);\n}\n\n// find last tag in chain\nstatic document_tag_t *GetLast(document_tag_t *tag)\n{\n    if (tag == NULL)\n        return NULL;\n\n    while (tag->next)\n        tag = tag->next;\n    return tag;\n}\n\n// ----------------------------------------------------------------------------\n//\n// BLOCKS parser\n//\nstatic void OnStartElement_Blocks(void *userData, const XML_Char *name, const XML_Char **atts)\n{\n    xml_parser_stack_t *stack = (xml_parser_stack_t *) userData;\n    xml_document_t *document = (xml_document_t *) stack->document;\n    document_parser_specific_t *specific = (document_parser_specific_t *) stack->parser_specific;\n\n    if (stack->path[0] == 0)\n    {\n        if (!strcmp(name, \"p\"))\n        {\n            const char *attr;\n\n            document_tag_p_t *p = (document_tag_p_t *) Q_malloc(sizeof(document_tag_p_t));\n            memset(p, 0, sizeof(document_tag_p_t));\n            p->type = tag_p;\n\n            // check indentation\n            attr = XSD_GetAttribute(atts, \"indent\");\n            if (attr == NULL  ||  !strcmp(attr, \"false\"))\n                p->indent = false;\n            else\n                p->indent = true;\n\n            // check alignment\n            attr = XSD_GetAttribute(atts, \"align\");\n            p->align = align_left;\n            if (attr != NULL)\n            {\n                if (!strcmp(attr, \"right\"))\n                    p->align = align_right;\n                else  if (!strcmp(attr, \"center\"))\n                    p->align = align_center;\n            }\n\n            // add to list\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) p;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) p;\n\n            // and now parse this element with new parser\n            // create sub-parser\n            {\n                xml_parser_stack_t *newstack;\n                document_parser_specific_t *specific;\n\n                newstack = (xml_parser_stack_t *) Q_malloc(sizeof(xml_parser_stack_t));\n                specific = (document_parser_specific_t *) Q_malloc(sizeof(document_parser_specific_t));\n\n                specific->tag = &p->tags;\n\n                // prepare user data\n                XSD_InitStack(newstack);\n                newstack->parser_specific = specific;\n                newstack->parser = stack->parser;\n                newstack->document = (xml_t *) document;\n                newstack->oldUserData = userData;\n                newstack->oldStartHandler = OnStartElement_Blocks;\n                newstack->oldEndHandler = OnEndElement_Blocks;\n                newstack->oldCharacterDataHandler = OnCharacterData_Blocks;\n\n                XML_SetUserData(stack->parser, newstack);\n                XML_SetStartElementHandler(stack->parser, OnStartElement_Blocks);\n                XML_SetEndElementHandler(stack->parser, OnEndElement_Blocks);\n                XML_SetCharacterDataHandler(stack->parser, OnCharacterData_Blocks);\n\n                return;\n            }\n        }\n        \n        if (!strcmp(name, \"section\"))\n        {\n            const char *attr;\n\n            document_tag_section_t *p = (document_tag_section_t *) Q_malloc(sizeof(document_tag_section_t));\n            memset(p, 0, sizeof(document_tag_section_t));\n            p->type = tag_section;\n\n            // check indentation\n            attr = XSD_GetAttribute(atts, \"indent\");\n            if (attr == NULL  ||  !strcmp(attr, \"false\"))\n                p->indent = false;\n            else\n                p->indent = true;\n\n            // check id\n            attr = XSD_GetAttribute(atts, \"id\");\n            if (attr)\n                p->id = Q_strdup(attr);\n            else\n                p->id = Q_strdup(va(\"%___%d\", specific->section_num++));\n\n            // check title\n            attr = XSD_GetAttribute(atts, \"title\");\n            if (attr)\n                p->title = Q_strdup(attr);\n            else\n                p->title = Q_strdup(\"<unnamed>\");\n\n            // add to list\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) p;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) p;\n\n            // and now parse this element with new parser\n            // create sub-parser\n            {\n                xml_parser_stack_t *newstack;\n                document_parser_specific_t *specific;\n\n                newstack = (xml_parser_stack_t *) Q_malloc(sizeof(xml_parser_stack_t));\n                specific = (document_parser_specific_t *) Q_malloc(sizeof(document_parser_specific_t));\n\n                specific->tag = &p->tags;\n\n                // prepare user data\n                XSD_InitStack(newstack);\n                newstack->parser_specific = specific;\n                newstack->parser = stack->parser;\n                newstack->document = (xml_t *) document;\n                newstack->oldUserData = userData;\n                newstack->oldStartHandler = OnStartElement_Blocks;\n                newstack->oldEndHandler = OnEndElement_Blocks;\n                newstack->oldCharacterDataHandler = OnCharacterData_Blocks;\n\n                XML_SetUserData(stack->parser, newstack);\n                XML_SetStartElementHandler(stack->parser, OnStartElement_Blocks);\n                XML_SetEndElementHandler(stack->parser, OnEndElement_Blocks);\n                XML_SetCharacterDataHandler(stack->parser, OnCharacterData_Blocks);\n\n                return;\n            }\n        }\n        \n        if (!strcmp(name, \"list\"))\n        {\n            const char *attr;\n\n            // create new list and add to tag list\n            document_tag_list_t *list = (document_tag_list_t *) Q_malloc(sizeof(document_tag_list_t));\n            memset(list, 0, sizeof(document_tag_list_t));\n            list->type = tag_list;\n\n            // check bullet type\n            list->bullet = list_bullet_none;\n            attr = XSD_GetAttribute(atts, \"bullet\");\n            if (attr == NULL  ||  !strcmp(attr, \"dot\"))\n                list->bullet = list_bullet_dot;\n            else if (!strcmp(attr, \"none\"))\n                list->bullet = list_bullet_none;\n            else if (!strcmp(attr, \"number\"))\n                list->bullet = list_bullet_number;\n            else if (!strcmp(attr, \"letter\"))\n                list->bullet = list_bullet_letter;\n            else if (!strcmp(attr, \"big-letter\"))\n                list->bullet = list_bullet_bigletter;\n\n            // check separator type\n            attr = XSD_GetAttribute(atts, \"separator\");\n            list->separator = list_separator_none;\n            if (attr == NULL  ||  !strcmp(attr, \"none\"))\n                list->separator = list_separator_none;\n            else if (!strcmp(attr, \"dot\"))\n                list->separator = list_separator_dot;\n            else if (!strcmp(attr, \"par\"))\n                list->separator = list_separator_par;\n\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) list;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) list;\n        }\n\n        if (!strcmp(name, \"dict\"))\n        {\n            const char *attr;\n\n            // create new dict and add to tag list\n            document_tag_dict_t *dict = (document_tag_dict_t *) Q_malloc(sizeof(document_tag_dict_t));\n            memset(dict, 0, sizeof(document_tag_dict_t));\n            dict->type = tag_dict;\n\n            // check bullet type\n            dict->bullet = list_bullet_none;\n            attr = XSD_GetAttribute(atts, \"bullet\");\n            if (attr == NULL  ||  !strcmp(attr, \"none\"))\n                dict->bullet = list_bullet_none;\n            else if (!strcmp(attr, \"dot\"))\n                dict->bullet = list_bullet_dot;\n            else if (!strcmp(attr, \"number\"))\n                dict->bullet = list_bullet_number;\n            else if (!strcmp(attr, \"letter\"))\n                dict->bullet = list_bullet_letter;\n            else if (!strcmp(attr, \"big-letter\"))\n                dict->bullet = list_bullet_bigletter;\n\n            // check separator type\n            attr = XSD_GetAttribute(atts, \"separator\");\n            dict->separator = list_separator_none;\n            if (attr == NULL  ||  !strcmp(attr, \"none\"))\n                dict->separator = list_separator_none;\n            else if (!strcmp(attr, \"dot\"))\n                dict->separator = list_separator_dot;\n            else if (!strcmp(attr, \"par\"))\n                dict->separator = list_separator_par;\n\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) dict;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) dict;\n        }\n\n        if (!strcmp(name, \"hr\"))\n        {\n            document_tag_hr_t *hr = (document_tag_hr_t *) Q_malloc(sizeof(document_tag_hr_t));\n            memset(hr, 0, sizeof(document_tag_hr_t));\n            hr->type = tag_hr;\n\n            // add to list\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) hr;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) hr;\n        }\n\n        if (!strcmp(name, \"pre\"))\n        {\n            const char *attr;\n\n            document_tag_pre_t *pre = (document_tag_pre_t *) Q_malloc(sizeof(document_tag_pre_t));\n            memset(pre, 0, sizeof(document_tag_pre_t));\n            pre->type = tag_pre;\n\n            // check alt\n            attr = XSD_GetAttribute(atts, \"alt\");\n            if (attr != NULL)\n                pre->alt = Q_strdup(attr);\n\n            // add to list\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) pre;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) pre;\n        }\n\n        if (!strcmp(name, \"h\"))\n        {\n            const char *attr;\n\n            document_tag_h_t *h = (document_tag_h_t *) Q_malloc(sizeof(document_tag_h_t));\n            memset(h, 0, sizeof(document_tag_h_t));\n            h->type = tag_h;\n\n            // check alignment\n            attr = XSD_GetAttribute(atts, \"align\");\n            h->align = align_left;\n            if (attr != NULL)\n            {\n                if (!strcmp(attr, \"right\"))\n                    h->align = align_right;\n                else  if (!strcmp(attr, \"center\"))\n                    h->align = align_center;\n            }\n\n            // add to list\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) h;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) h;\n\n            // and now parse this element with new parser\n            // create sub-parser\n            {\n                xml_parser_stack_t *newstack;\n                document_parser_specific_t *specific;\n\n                newstack = (xml_parser_stack_t *) Q_malloc(sizeof(xml_parser_stack_t));\n                specific = (document_parser_specific_t *) Q_malloc(sizeof(document_parser_specific_t));\n\n                specific->tag = &h->tags;\n\n                // prepare user data\n                XSD_InitStack(newstack);\n                newstack->parser_specific = specific;\n                newstack->parser = stack->parser;\n                newstack->document = (xml_t *) document;\n                newstack->oldUserData = userData;\n                newstack->oldStartHandler = OnStartElement_Blocks;\n                newstack->oldEndHandler = OnEndElement_Blocks;\n                newstack->oldCharacterDataHandler = OnCharacterData_Blocks;\n\n                XML_SetUserData(stack->parser, newstack);\n                XML_SetStartElementHandler(stack->parser, OnStartElement_Blocks);\n                XML_SetEndElementHandler(stack->parser, OnEndElement_Blocks);\n                XML_SetCharacterDataHandler(stack->parser, OnCharacterData_Blocks);\n\n                return;\n            }\n        }\n\n        if (!strcmp(name, \"em\"))\n        {\n            document_tag_em_t *i = (document_tag_em_t *) Q_malloc(sizeof(document_tag_em_t));\n            memset(i, 0, sizeof(document_tag_em_t));\n            i->type = tag_em;\n\n            // add to list\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) i;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) i;\n\n            // and now parse this element with new parser\n            // create sub-parser\n            {\n                xml_parser_stack_t *newstack;\n                document_parser_specific_t *specific;\n\n                newstack = (xml_parser_stack_t *) Q_malloc(sizeof(xml_parser_stack_t));\n                specific = (document_parser_specific_t *) Q_malloc(sizeof(document_parser_specific_t));\n\n                specific->tag = &i->tags;\n\n                // prepare user data\n                XSD_InitStack(newstack);\n                newstack->parser_specific = specific;\n                newstack->parser = stack->parser;\n                newstack->document = (xml_t *) document;\n                newstack->oldUserData = userData;\n                newstack->oldStartHandler = OnStartElement_Blocks;\n                newstack->oldEndHandler = OnEndElement_Blocks;\n                newstack->oldCharacterDataHandler = OnCharacterData_Blocks;\n\n                XML_SetUserData(stack->parser, newstack);\n                XML_SetStartElementHandler(stack->parser, OnStartElement_Blocks);\n                XML_SetEndElementHandler(stack->parser, OnEndElement_Blocks);\n                XML_SetCharacterDataHandler(stack->parser, OnCharacterData_Blocks);\n\n                return;\n            }\n        }\n\n        if (!strcmp(name, \"color\"))\n        {\n            document_tag_color_t *i = (document_tag_color_t *) Q_malloc(sizeof(document_tag_color_t));\n            memset(i, 0, sizeof(document_tag_color_t));\n            i->type = tag_color;\n\n            // add to list\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) i;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) i;\n\n            // and now parse this element with new parser\n            // create sub-parser\n            {\n                xml_parser_stack_t *newstack;\n                document_parser_specific_t *specific;\n\n                newstack = (xml_parser_stack_t *) Q_malloc(sizeof(xml_parser_stack_t));\n                specific = (document_parser_specific_t *) Q_malloc(sizeof(document_parser_specific_t));\n\n                specific->tag = &i->tags;\n\n                // prepare user data\n                XSD_InitStack(newstack);\n                newstack->parser_specific = specific;\n                newstack->parser = stack->parser;\n                newstack->document = (xml_t *) document;\n                newstack->oldUserData = userData;\n                newstack->oldStartHandler = OnStartElement_Blocks;\n                newstack->oldEndHandler = OnEndElement_Blocks;\n                newstack->oldCharacterDataHandler = OnCharacterData_Blocks;\n\n                XML_SetUserData(stack->parser, newstack);\n                XML_SetStartElementHandler(stack->parser, OnStartElement_Blocks);\n                XML_SetEndElementHandler(stack->parser, OnEndElement_Blocks);\n                XML_SetCharacterDataHandler(stack->parser, OnCharacterData_Blocks);\n\n                return;\n            }\n        }\n\n        if (!strcmp(name, \"a\"))\n        {\n            const char *attr;\n\n            document_tag_a_t *i = (document_tag_a_t *) Q_malloc(sizeof(document_tag_a_t));\n            memset(i, 0, sizeof(document_tag_a_t));\n            i->type = tag_a;\n\n            // check href\n            attr = XSD_GetAttribute(atts, \"href\");\n            if (attr)\n                i->href = Q_strdup(attr);\n\n            // add to list\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) i;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) i;\n\n            // and now parse this element with new parser\n            // create sub-parser\n            {\n                xml_parser_stack_t *newstack;\n                document_parser_specific_t *specific;\n\n                newstack = (xml_parser_stack_t *) Q_malloc(sizeof(xml_parser_stack_t));\n                specific = (document_parser_specific_t *) Q_malloc(sizeof(document_parser_specific_t));\n\n                specific->tag = &i->tags;\n\n                // prepare user data\n                XSD_InitStack(newstack);\n                newstack->parser_specific = specific;\n                newstack->parser = stack->parser;\n                newstack->document = (xml_t *) document;\n                newstack->oldUserData = userData;\n                newstack->oldStartHandler = OnStartElement_Blocks;\n                newstack->oldEndHandler = OnEndElement_Blocks;\n                newstack->oldCharacterDataHandler = OnCharacterData_Blocks;\n\n                XML_SetUserData(stack->parser, newstack);\n                XML_SetStartElementHandler(stack->parser, OnStartElement_Blocks);\n                XML_SetEndElementHandler(stack->parser, OnEndElement_Blocks);\n                XML_SetCharacterDataHandler(stack->parser, OnCharacterData_Blocks);\n\n                return;\n            }\n        }\n\n        if (!strcmp(name, \"img\"))\n        {\n            document_tag_img_t *i = (document_tag_img_t *) Q_malloc(sizeof(document_tag_img_t));\n            memset(i, 0, sizeof(document_tag_img_t));\n            i->type = tag_img;\n\n            // add to list\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) i;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) i;\n\n            // and now parse this element with new parser\n            // create sub-parser\n            {\n                xml_parser_stack_t *newstack;\n                document_parser_specific_t *specific;\n\n                newstack = (xml_parser_stack_t *) Q_malloc(sizeof(xml_parser_stack_t));\n                specific = (document_parser_specific_t *) Q_malloc(sizeof(document_parser_specific_t));\n\n                specific->tag = &i->tags;\n\n                // prepare user data\n                XSD_InitStack(newstack);\n                newstack->parser_specific = specific;\n                newstack->parser = stack->parser;\n                newstack->document = (xml_t *) document;\n                newstack->oldUserData = userData;\n                newstack->oldStartHandler = OnStartElement_Blocks;\n                newstack->oldEndHandler = OnEndElement_Blocks;\n                newstack->oldCharacterDataHandler = OnCharacterData_Blocks;\n\n                XML_SetUserData(stack->parser, newstack);\n                XML_SetStartElementHandler(stack->parser, OnStartElement_Blocks);\n                XML_SetEndElementHandler(stack->parser, OnEndElement_Blocks);\n                XML_SetCharacterDataHandler(stack->parser, OnCharacterData_Blocks);\n\n                return;\n            }\n        }\n\n        if (!strcmp(name, \"br\"))\n        {\n            document_tag_br_t *i = (document_tag_br_t *) Q_malloc(sizeof(document_tag_br_t));\n            memset(i, 0, sizeof(document_tag_br_t));\n            i->type = tag_br;\n\n            // add to list\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) i;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) i;\n        }\n\n        if (!strcmp(name, \"sp\"))\n        {\n            document_tag_sp_t *i = (document_tag_sp_t *) Q_malloc(sizeof(document_tag_sp_t));\n            memset(i, 0, sizeof(document_tag_sp_t));\n            i->type = tag_sp;\n\n            // add to list\n            if (specific->tag[0] == NULL)\n                specific->tag[0] = (document_tag_t *) i;\n            else\n                GetLast(specific->tag[0])->next = (document_tag_t *) i;\n        }\n    }\n\n    if (!strcmp(stack->path, \"/list\")  &&  !strcmp(name, \"li\"))\n    {\n        xml_parser_stack_t *newstack;\n        document_tag_list_t *list;\n        document_tag_li_t *item;\n\n        // find last elemend (LIST)\n        list = (document_tag_list_t *) GetLast(specific->tag[0]);\n        assert(list->type == tag_list);\n\n        // create new LI and add to list\n        item = (document_tag_li_t *) Q_malloc(sizeof(document_tag_li_t));\n        memset(item, 0, sizeof(document_tag_li_t));\n        item->type = tag_li;\n        if (list->items == NULL)\n            list->items = item;\n        else\n            GetLast((document_tag_t *)list->items)->next = (document_tag_t *)item;\n\n        // start new parser for LI\n        {\n            document_parser_specific_t *specific;\n            newstack = (xml_parser_stack_t *) Q_malloc(sizeof(xml_parser_stack_t));\n            specific = (document_parser_specific_t *) Q_malloc(sizeof(document_parser_specific_t));\n\n            specific->tag = &item->tags;\n\n            // prepare user data\n            XSD_InitStack(newstack);\n            newstack->parser_specific = specific;\n            newstack->parser = stack->parser;\n            newstack->document = (xml_t *) document;\n            newstack->oldUserData = userData;\n            newstack->oldStartHandler = OnStartElement_Blocks;\n            newstack->oldEndHandler = OnEndElement_Blocks;\n            newstack->oldCharacterDataHandler = OnCharacterData_Blocks;\n\n            XML_SetUserData(stack->parser, newstack);\n            XML_SetStartElementHandler(stack->parser, OnStartElement_Blocks);\n            XML_SetEndElementHandler(stack->parser, OnEndElement_Blocks);\n            XML_SetCharacterDataHandler(stack->parser, OnCharacterData_Blocks);\n\n            return;\n        }\n    }\n\n    if (!strcmp(stack->path, \"/dict\")  &&  !strcmp(name, \"di\"))\n    {\n        document_tag_dict_t *dict;\n        document_tag_di_t *item;\n\n        // find last elemend (DICT)\n        dict = (document_tag_dict_t *) GetLast(specific->tag[0]);\n        assert(dict->type == tag_dict);\n\n        // create new DI and add to dict\n        item = (document_tag_di_t *) Q_malloc(sizeof(document_tag_di_t));\n        memset(item, 0, sizeof(document_tag_di_t));\n        item->type = tag_di;\n        if (dict->items == NULL)\n            dict->items = item;\n        else\n            GetLast((document_tag_t *)dict->items)->next = (document_tag_t *)item;\n    }\n\n    if (!strcmp(stack->path, \"/dict/di\")  &&  !strcmp(name, \"name\"))\n    {\n        xml_parser_stack_t *newstack;\n        document_tag_dict_t *dict;\n        document_tag_di_t *item;\n\n        // find last elemend (DICT)\n        dict = (document_tag_dict_t *) GetLast(specific->tag[0]);\n        assert(dict->type == tag_dict);\n\n        // find last element (DI)\n        item = (document_tag_di_t *) GetLast((document_tag_t *)dict->items);\n        assert(item->type == tag_di);\n\n        // start new parser for DI/name\n        {\n            document_parser_specific_t *specific;\n            newstack = (xml_parser_stack_t *) Q_malloc(sizeof(xml_parser_stack_t));\n            specific = (document_parser_specific_t *) Q_malloc(sizeof(document_parser_specific_t));\n\n            specific->tag = &item->name;\n\n            // prepare user data\n            XSD_InitStack(newstack);\n            newstack->parser_specific = specific;\n            newstack->parser = stack->parser;\n            newstack->document = (xml_t *) document;\n            newstack->oldUserData = userData;\n            newstack->oldStartHandler = OnStartElement_Blocks;\n            newstack->oldEndHandler = OnEndElement_Blocks;\n            newstack->oldCharacterDataHandler = OnCharacterData_Blocks;\n\n            XML_SetUserData(stack->parser, newstack);\n            XML_SetStartElementHandler(stack->parser, OnStartElement_Blocks);\n            XML_SetEndElementHandler(stack->parser, OnEndElement_Blocks);\n            XML_SetCharacterDataHandler(stack->parser, OnCharacterData_Blocks);\n\n            return;\n        }\n    }\n\n    if (!strcmp(stack->path, \"/dict/di\")  &&  !strcmp(name, \"description\"))\n    {\n        xml_parser_stack_t *newstack;\n        document_tag_dict_t *dict;\n        document_tag_di_t *item;\n\n        // find last elemend (DICT)\n        dict = (document_tag_dict_t *) GetLast(specific->tag[0]);\n        assert(dict->type == tag_dict);\n\n        // find last element (DI)\n        item = (document_tag_di_t *) GetLast((document_tag_t *)dict->items);\n        assert(item->type == tag_di);\n\n        // start new parser for DI/name\n        {\n            document_parser_specific_t *specific;\n            newstack = (xml_parser_stack_t *) Q_malloc(sizeof(xml_parser_stack_t));\n            specific = (document_parser_specific_t *) Q_malloc(sizeof(document_parser_specific_t));\n\n            specific->tag = &item->description;\n\n            // prepare user data\n            XSD_InitStack(newstack);\n            newstack->parser_specific = specific;\n            newstack->parser = stack->parser;\n            newstack->document = (xml_t *) document;\n            newstack->oldUserData = userData;\n            newstack->oldStartHandler = OnStartElement_Blocks;\n            newstack->oldEndHandler = OnEndElement_Blocks;\n            newstack->oldCharacterDataHandler = OnCharacterData_Blocks;\n\n            XML_SetUserData(stack->parser, newstack);\n            XML_SetStartElementHandler(stack->parser, OnStartElement_Blocks);\n            XML_SetEndElementHandler(stack->parser, OnEndElement_Blocks);\n            XML_SetCharacterDataHandler(stack->parser, OnCharacterData_Blocks);\n\n            return;\n        }\n    }\n\n    XSD_OnStartElement(stack, name, atts);\n}\n\nstatic void OnEndElement_Blocks(void *userData, const XML_Char *name)\n{\n    xml_parser_stack_t *stack = (xml_parser_stack_t *) userData;\n//    xml_document_t *document = (xml_document_t *) stack->document;\n//    document_parser_specific_t *specific = (document_parser_specific_t *) stack->parser_specific;\n\n    // if element ends and our stack is empty, it means we\n    // should return to parent stack & parser\n    if (stack->path[0] == 0)\n    {\n        XSD_RestoreStack(stack);\n        Q_free(stack->parser_specific);\n        Q_free(stack);\n        return;\n    }\n\n    XSD_OnEndElement(stack, name);\n}\n\nstatic void OnCharacterData_Blocks(void *userData, const XML_Char *s, int len)\n{\n    xml_parser_stack_t *stack = (xml_parser_stack_t *) userData;\n//    xml_document_t *document = (xml_document_t *) stack->document;\n    document_parser_specific_t *specific = (document_parser_specific_t *) stack->parser_specific;\n\n    if (stack->path[0] == 0)\n    {\n        document_tag_text_t *text;\n        document_tag_t *last = GetLast(specific->tag[0]);\n\n        if (last == NULL)\n        {\n            // create new text tag and start chain with it\n            text = (document_tag_text_t *) Q_malloc(sizeof(document_tag_text_t));\n            memset(text, 0, sizeof(document_tag_text_t));\n            text->type = tag_text;\n            specific->tag[0] = (document_tag_t *) text;\n        }\n        else if (last->type == tag_text)\n        {\n            // append to this tag\n            text = (document_tag_text_t *) last;\n        }\n        else\n        {\n            // create new text tag and add it to the chain\n            text = (document_tag_text_t *) Q_malloc(sizeof(document_tag_text_t));\n            memset(text, 0, sizeof(document_tag_text_t));\n            text->type = tag_text;\n            last->next = (document_tag_t *) text;\n        }\n\n        text->text = XSD_AddText(text->text, s, len);\n    }\n\n    if (!strcmp(stack->path, \"/pre\"))\n    {\n        document_tag_pre_t *pre;\n\n        // find last element (pre)\n        pre = (document_tag_pre_t *) GetLast(specific->tag[0]);\n        assert(pre->type = tag_pre);\n\n        // and add text to it\n        pre->text = XSD_AddText(pre->text, s, len);\n    }\n}\n\n\n// ----------------------------------------------------------------------------\n//\n// general parser\n//\n\nstatic void OnStartElement(void *userData, const XML_Char *name, const XML_Char **atts)\n{\n    xml_parser_stack_t *stack = (xml_parser_stack_t *) userData;\n    xml_document_t *document = (xml_document_t *) stack->document;\n//    document_parser_specific_t *specific = (document_parser_specific_t *) stack->parser_specific;\n\n    if (stack->path[0] == 0)\n        document->document_type = Q_strdup(name);\n\n    if (!strcmp(stack->path, \"/document/head/authors\")  &&  !strcmp(name, \"author\"))\n    {\n        xml_document_author_t *author = (xml_document_author_t *) Q_malloc(sizeof(xml_document_author_t));\n        memset(author, 0, sizeof(xml_document_author_t));\n\n        if (document->authors == NULL)\n            document->authors = author;\n        else\n        {\n            xml_document_author_t *last = (xml_document_author_t *)\n                GetLast((document_tag_t *)document->authors);\n            last->next = author;\n        }\n    }\n\n    if (XSD_IsIn(stack->path, \"/document\"))\n    {\n        if (!strcmp(name, \"body\"))\n        {\n            // create sub-parser\n            xml_parser_stack_t *newstack;\n            document_parser_specific_t *specific;\n\n            newstack = (xml_parser_stack_t *) Q_malloc(sizeof(xml_parser_stack_t));\n            specific = (document_parser_specific_t *) Q_malloc(sizeof(document_parser_specific_t));\n\n            specific->tag = &document->content;\n\n            // prepare user data\n            XSD_InitStack(newstack);\n            newstack->parser_specific = specific;\n            newstack->parser = stack->parser;\n            newstack->document = (xml_t *) document;\n            newstack->oldUserData = userData;\n            newstack->oldStartHandler = OnStartElement;\n            newstack->oldEndHandler = OnEndElement;\n            newstack->oldCharacterDataHandler = OnCharacterData;\n\n            XML_SetUserData(stack->parser, newstack);\n            XML_SetStartElementHandler(stack->parser, OnStartElement_Blocks);\n            XML_SetEndElementHandler(stack->parser, OnEndElement_Blocks);\n            XML_SetCharacterDataHandler(stack->parser, OnCharacterData_Blocks);\n\n            return;\n        }\n    }\n\n    XSD_OnStartElement(stack, name, atts);\n}\n\nstatic void OnEndElement(void *userData, const XML_Char *name)\n{\n    xml_parser_stack_t *stack = (xml_parser_stack_t *) userData;\n    xml_document_t *document = (xml_document_t *) stack->document;\n\n    // strip spaces from elements already loaded\n    if (!strcmp(stack->path, \"/document/title\"))\n        document->title = XSD_StripSpaces(document->title);\n\n    XSD_OnEndElement(stack, name);\n}\n\nstatic void OnCharacterData(void *userData, const XML_Char *s, int len)\n{\n    xml_parser_stack_t *stack = (xml_parser_stack_t *) userData;\n    xml_document_t *document = (xml_document_t *) stack->document;\n\n    if (!strcmp(stack->path, \"/document/head/title\"))\n        document->title = XSD_AddText(document->title, s, len);\n\n    if (!strcmp(stack->path, \"/document/head/date\"))\n        document->date = XSD_AddText(document->date, s, len);\n\n    if (!strcmp(stack->path, \"/document/head/authors/author\"))\n    {\n        xml_document_author_t *a = (xml_document_author_t *)\n            GetLast((document_tag_t *)document->authors);\n\n        if (!strcmp(stack->path, \"/document/head/authors/name\"))\n            a->name = XSD_AddText(a->name, s, len);\n        if (!strcmp(stack->path, \"/document/head/authors/email\"))\n            a->name = XSD_AddText(a->email, s, len);\n        if (!strcmp(stack->path, \"/document/head/authors/im\"))\n            a->name = XSD_AddText(a->im, s, len);\n    }\n}\n\n// read document content from file, return 0 if error\nxml_t * XSD_Document_LoadFromHandle(vfsfile_t *v, int filelen) {\n\tvfserrno_t err;\n    xml_document_t *document;\n    XML_Parser parser = NULL;\n    int len;\n\tint pos = 0;\n    char buf[XML_READ_BUFSIZE];\n    xml_parser_stack_t parser_stack;\n\n    // create blank document\n    document = (xml_document_t *) Q_malloc(sizeof(xml_document_t));\n    XSD_Document_Init(document);\n\n    // initialize XML parser\n    parser = XML_ParserCreate(NULL);\n    if (parser == NULL)\n        goto error;\n    XML_SetStartElementHandler(parser, OnStartElement);\n    XML_SetEndElementHandler(parser, OnEndElement);\n    XML_SetCharacterDataHandler(parser, OnCharacterData);\n\n    // prepare user data\n    XSD_InitStack(&parser_stack);\n    parser_stack.document = (xml_t *) document;\n    parser_stack.parser = parser;\n    XML_SetUserData(parser, &parser_stack);\n\n    while ((len = VFS_READ(v, buf, min(XML_READ_BUFSIZE, filelen-pos), &err)) > 0)\n    {\n        if (XML_Parse(parser, buf, len, 0) != XML_STATUS_OK)\n            goto error;\n\n\t\tpos += len;\n    }\n    if (XML_Parse(parser, NULL, 0, 1) != XML_STATUS_OK)\n        goto error;\n\n    XML_ParserFree(parser);\n\n    return (xml_t *) document;\n\nerror:\n\n    if (parser)\n        XML_ParserFree(parser);\n    XSD_Document_Free((xml_t *)document);\n\n    return NULL;\n}\n\n// read document content from file, return 0 if error\nxml_document_t * XSD_Document_Load(char *filename)\n{\n    xml_document_t *document;\n\tvfsfile_t *f;\n\n\tif (!(f = FS_OpenVFS(filename, \"rb\", FS_ANY))) {\n\t\treturn NULL;\n\t}\n    document = (xml_document_t *) XSD_Document_LoadFromHandle(f, VFS_GETLEN(f));\n\tVFS_CLOSE(f);\n    return document;\n}\n"
  },
  {
    "path": "src/xsd_document.h",
    "content": "/*\nCopyright (C) 2011 azazello and ezQuake team\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n#ifndef __XSD_DOCUMENT_H__\n#define __XSD_DOCUMENT_H__\n\n#include \"xsd.h\"\n\ntypedef enum\n{\n    // plain text\n    tag_text,\n\n    // block elements\n    tag_section,\n    tag_p,\n    tag_list,\n    tag_dict,\n    tag_hr,\n    tag_h,\n    tag_pre,\n\n    // inline elements\n    tag_em,\n    tag_a,\n    tag_img,\n    tag_color,\n    tag_br,\n    tag_sp,\n\n    // other\n    tag_li,\n    tag_di\n}\ndocument_tag_type_t;\n\ntypedef enum\n{\n    align_left,\n    align_center,\n    align_right\n}\ndocument_align_t;\n\n\n// This is a base for all tag structures. Every tag structure\n// should inherit fields defined here. This one is abstract.\ntypedef struct document_tag_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n\n    // rest of data follow\n    // ...\n}\ndocument_tag_t;\n\n\ntypedef struct document_tag_text_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    char *text;\n}\ndocument_tag_text_t;\n\n\ntypedef struct document_tag_p_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    document_tag_t *tags;\n    qbool indent;\n    document_align_t align;\n}\ndocument_tag_p_t;\n\n\ntypedef struct document_tag_section_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    document_tag_t *tags;\n    qbool indent;\n    char *title;\n    char *id;\n}\ndocument_tag_section_t;\n\n\ntypedef struct document_tag_hr_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n}\ndocument_tag_hr_t;\n\n\ntypedef struct document_tag_h_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    document_tag_t *tags;\n    document_align_t align;\n}\ndocument_tag_h_t;\n\n\ntypedef struct document_tag_em_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    document_tag_t *tags;\n}\ndocument_tag_em_t;\n\n\ntypedef struct document_tag_color_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    document_tag_t *tags;\n}\ndocument_tag_color_t;\n\n\ntypedef struct document_tag_a_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    document_tag_t *tags;\n    char *href;\n}\ndocument_tag_a_t;\n\n\ntypedef struct document_tag_img_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    document_tag_t *tags;\n}\ndocument_tag_img_t;\n\n\ntypedef struct document_tag_br_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n}\ndocument_tag_br_t;\n\n\ntypedef struct document_tag_pre_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    char *text;\n    char *alt;\n}\ndocument_tag_pre_t;\n\n\ntypedef struct document_tag_sp_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n}\ndocument_tag_sp_t;\n\n\ntypedef enum\n{\n    list_bullet_none,\n    list_bullet_dot,\n    list_bullet_number,\n    list_bullet_letter,\n    list_bullet_bigletter\n}\nlist_bullet_t;\n\ntypedef enum\n{\n    list_separator_none,\n    list_separator_dot,\n    list_separator_par\n}\nlist_separator_t;\n\ntypedef struct document_tag_li_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    document_tag_t *tags;\n}\ndocument_tag_li_t;\n\ntypedef struct document_tag_list_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    document_tag_li_t *items;\n    list_bullet_t bullet;\n    list_separator_t separator;\n}\ndocument_tag_list_t;\n\n\ntypedef struct document_tag_di_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    document_tag_t *name;\n    document_tag_t *description;\n}\ndocument_tag_di_t;\n\ntypedef struct document_tag_dict_s\n{\n    document_tag_type_t type;\n    struct document_tag_s *next;\n    // --------------------\n    document_tag_di_t *items;\n    list_bullet_t bullet;\n    list_separator_t separator;\n}\ndocument_tag_dict_t;\n\n\ntypedef struct xml_document_author_s\n{\n    document_tag_type_t type;   // nothing for this type\n    struct xml_document_author_s *next;\n    // --------------------\n\n    char *name;\n    char *email;\n    char *im;\n}\nxml_document_author_t;\n\ntypedef struct xml_document_s\n{\n    char *document_type;\n\n    // head\n    char *title;\n    char *date;\n    xml_document_author_t *authors;\n\n    // body\n    document_tag_t *content;\n}\nxml_document_t;\n\n\n// document parser specific data\ntypedef struct document_parser_specific_s\n{\n    document_tag_t **tag;\n    int section_num;\n}\ndocument_parser_specific_t;\n\n\n// create new document\nxml_document_t * XSD_Document_New(void);\n\n// free xml_document_t struct\nvoid XSD_Document_Free(xml_t *);\n\n// read document content from file, return NULL if error\nxml_t * XSD_Document_LoadFromHandle(vfsfile_t *v, int filelen);\n\n// read document content from file, return NULL if error\nxml_document_t * XSD_Document_Load(char *filename);\n\n#endif // __XSD_DOCUMENT_H__\n"
  },
  {
    "path": "src/zone.c",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// zone.c - memory management\n\n#ifdef SERVERONLY\n#include \"qwsvdef.h\"\n\nstatic void Cache_FreeLow(int new_low_hunk);\nstatic void Cache_FreeHigh(int new_high_hunk);\n#else\n#include \"common.h\"\n#include \"gl_model.h\"\n\n#define Cache_FreeLow(...)\n#define Cache_FreeHigh(...)\n#define Cache_Init(...)\n#endif\n\n//============================================================================\n\n#define HUNK_SENTINEL 0x1df001ed\n\ntypedef struct {\n\tint\t\tsentinel;\n\tint\t\tsize; // including sizeof(hunk_t), -1 = not allocated\n\tchar\tname[8];\n} hunk_t;\n\nbyte\t*hunk_base;\nint\t\thunk_size;\n\nint\t\thunk_low_used;\nint\t\thunk_high_used;\n\nqbool\thunk_tempactive;\nint\t\thunk_tempmark;\n\n/*\n==============\nHunk_Check\n\nRun consistency and sentinel trashing checks\n==============\n*/\nvoid Hunk_Check(void)\n{\n\thunk_t *h;\n\n\tfor (h = (hunk_t *)hunk_base; (byte *)h != hunk_base + hunk_low_used;) {\n\t\tif (h->sentinel != HUNK_SENTINEL) {\n\t\t\tSys_Error(\"Hunk_Check: trashed sentinel\");\n\t\t}\n\t\tif (h->size < 16 || h->size + (byte *)h - hunk_base > hunk_size) {\n\t\t\tSys_Error(\"Hunk_Check: bad size\");\n\t\t}\n\t\th = (hunk_t *)((byte *)h + h->size);\n\t}\n}\n\n/*\n==============\nHunk_Print\n\nIf \"all\" is specified, every single allocation is printed.\nOtherwise, allocations with the same name will be totaled up before printing.\n==============\n*/\nvoid Hunk_Print(qbool all)\n{\n\thunk_t  *h, *next, *endlow, *starthigh, *endhigh;\n\tint     sum;\n\tint     totalblocks;\n\tchar    name[9];\n\n\tname[8] = 0;\n\tsum = 0;\n\ttotalblocks = 0;\n\n\th = (hunk_t *)hunk_base;\n\tendlow = (hunk_t *)(hunk_base + hunk_low_used);\n\tstarthigh = (hunk_t *)(hunk_base + hunk_size - hunk_high_used);\n\tendhigh = (hunk_t *)(hunk_base + hunk_size);\n\n\tCon_Printf(\"          :%8i total hunk size\\n\", hunk_size);\n\tCon_Printf(\"-------------------------\\n\");\n\n\twhile (1) {\n\t\t// skip to the high hunk if done with low hunk\n\t\tif (h == endlow) {\n\t\t\tCon_Printf(\"-------------------------\\n\");\n\t\t\tCon_Printf(\"        :%8ikb REMAINING\\n\", (hunk_size - hunk_low_used - hunk_high_used) / 1024);\n\t\t\tCon_Printf(\"-------------------------\\n\");\n\t\t\th = starthigh;\n\t\t}\n\n\t\t// if totally done, break\n\t\tif (h == endhigh) {\n\t\t\tbreak;\n\t\t}\n\n\t\t// run consistency checks\n\t\tif (h->sentinel != HUNK_SENTINEL) {\n\t\t\tSys_Error(\"Hunk_Print: trashed sentinel\");\n\t\t}\n\t\tif (h->size < 16 || h->size + (byte *)h - hunk_base > hunk_size) {\n\t\t\tSys_Error(\"Hunk_Print: bad size\");\n\t\t}\n\n\t\tnext = (hunk_t *)((byte *)h + h->size);\n\t\ttotalblocks++;\n\t\tsum += h->size;\n\n\t\t// print the single block\n\t\tmemcpy(name, h->name, 8);\n\t\tif (all) {\n\t\t\tCon_Printf(\"%8p :%8i %8s\\n\", h, h->size, name);\n\t\t}\n\n\t\t// print the total\n\t\tif (next == endlow || next == endhigh || strncmp(h->name, next->name, 8)) {\n\t\t\tif (!all) {\n\t\t\t\tCon_Printf(\"          :%8ikb %8s (TOTAL)\\n\", sum / 1024, name);\n\t\t\t}\n\t\t\tsum = 0;\n\t\t}\n\n\t\th = next;\n\t}\n\n\tCon_Printf(\"-------------------------\\n\");\n\tCon_Printf(\"%8i total blocks\\n\", totalblocks);\n\tCon_Printf(\"High used %i, low used %i\\n\", hunk_high_used, hunk_low_used);\n}\n\nvoid Hunk_Print_f(void)\n{\n\tqbool all = Cmd_Argc() != 1;\n\n\tHunk_Print(all);\n}\n\n/*\n===================\nHunk_AllocName\n===================\n*/\nvoid *Hunk_AllocName(int size, const char *name)\n{\n\thunk_t *h;\n\n#ifdef PARANOID\n\tHunk_Check();\n#endif\n\n\tif (size < 0) {\n\t\tSys_Error(\"Hunk_AllocName: bad size: %i\", size);\n\t}\n\n\tsize = sizeof(hunk_t) + ((size + 15) & ~15);\n\n\tif (hunk_size - hunk_low_used - hunk_high_used < size) {\n\t\tif ((int)developer.value)\n\t\t{\n\t\t\tHunk_Print(true);\n\t\t}\n#ifdef SERVERONLY\n\t\tSys_Error(\"Hunk_AllocName: Not enough RAM allocated. Try starting using \\\"-mem 64\\\" (or more) on the command line.\");\n#else\n\t\tSys_Error(\"Hunk_AllocName: Not enough RAM allocated. Try starting using \\\"-mem 128\\\" (or more) on the command line.\");\n#endif\n\t}\n\n\th = (hunk_t *)(hunk_base + hunk_low_used);\n\thunk_low_used += size;\n\n\tCache_FreeLow(hunk_low_used);\n\n\tmemset(h, 0, size);\n\n\th->size = size;\n\th->sentinel = HUNK_SENTINEL;\n\tstrlcpy(h->name, name, sizeof(h->name));\n\n\treturn (void *)(h + 1);\n}\n\nint\tHunk_LowMark(void)\n{\n\treturn hunk_low_used;\n}\n\nvoid Hunk_FreeToLowMark(int mark)\n{\n\tif (mark < 0 || mark > hunk_low_used) {\n\t\tSys_Error(\"Hunk_FreeToLowMark: bad mark %i, hunk_low_used = %i\", mark, hunk_low_used);\n\t}\n\tmemset(hunk_base + mark, 0, hunk_low_used - mark);\n\thunk_low_used = mark;\n}\n\nstatic int Hunk_HighMark(void)\n{\n\treturn hunk_high_used;\n}\n\nstatic void Hunk_FreeToHighMark(int mark)\n{\n\tif (mark < 0 || mark > hunk_high_used) {\n\t\tSys_Error(\"Hunk_FreeToHighMark: bad mark %i\", mark);\n\t}\n\tmemset(hunk_base + hunk_size - hunk_high_used, 0, hunk_high_used - mark);\n\thunk_high_used = mark;\n}\n\n/*\n===================\nHunk_HighAllocName\n===================\n*/\nstatic void *Hunk_HighAllocName(int size, char *name)\n{\n\thunk_t *h;\n\n\tif (size < 0) {\n\t\tSys_Error(\"Hunk_HighAllocName: bad size: %i\", size);\n\t}\n\n#ifdef PARANOID\n\tHunk_Check();\n#endif\n\n\tsize = sizeof(hunk_t) + ((size + 15)&~15);\n\n\tif (hunk_size - hunk_low_used - hunk_high_used < size) {\n#ifdef SERVERONLY\n\t\tSys_Error(\"Hunk_HighAllocName: Not enough RAM allocated. Try starting using \\\"-mem 64\\\" (or more) on the command line.\");\n#else\n\t\tSys_Error(\"Hunk_HighAllocName: Not enough RAM allocated. Try starting using \\\"-mem 128\\\" (or more) on the command line.\");\n#endif\n\t}\n\n\thunk_high_used += size;\n\tCache_FreeHigh(hunk_high_used);\n\n\th = (hunk_t *)(hunk_base + hunk_size - hunk_high_used);\n\n\tmemset(h, 0, size);\n\th->size = size;\n\th->sentinel = HUNK_SENTINEL;\n\tstrlcpy(h->name, name, sizeof(h->name));\n\n\treturn (void *)(h + 1);\n}\n\n/*\n=================\nHunk_TempFlush\n\nFree the temporary memory zone to baseline.\n=================\n*/\nvoid Hunk_TempFlush(void)\n{\n\tif (hunk_tempactive) {\n\t\tHunk_FreeToHighMark(hunk_tempmark);\n\t\thunk_tempactive = false;\n\t}\n\n\thunk_tempmark = Hunk_HighMark();\n}\n\n/*\n=================\nHunk_TempAlloc\n\nReturn space from the top of the hunk\n=================\n*/\nvoid *Hunk_TempAlloc(int size)\n{\n\tvoid *buf;\n\n\tHunk_TempFlush();\n\n\tbuf = Hunk_HighAllocName(size, \"temp\");\n\n\thunk_tempactive = true;\n\n\treturn buf;\n}\n\n/*\n=================\nHunk_TempAllocMore\n\nReturn space after any previous temporary allocation without clearing first.\n=================\n*/\nvoid *Hunk_TempAllocMore(int size)\n{\n\tif (!hunk_tempactive)\n\t\treturn Hunk_TempAlloc(size);\n\n\treturn Hunk_HighAllocName(size, \"temp+\");\n}\n\n#ifdef SERVERONLY\n/*\n===============================================================================\n\nCACHE MEMORY\n\n===============================================================================\n*/\n\ntypedef struct cache_system_s {\n\tint                     size; // including this header\n\tcache_user_t*           user;\n\tchar                    name[16];\n\tstruct cache_system_s   *prev, *next;\n\tstruct cache_system_s   *lru_prev, *lru_next; // for LRU flushing\n} cache_system_t;\n\ncache_system_t* Cache_TryAlloc(int size, qbool nobottom);\n\ncache_system_t cache_head;\n\n/*\n===========\nCache_Move\n===========\n*/\nvoid Cache_Move(cache_system_t* c)\n{\n\tcache_system_t* new_block;\n\n\t// we are clearing up space at the bottom, so only allocate it late\n\tnew_block = Cache_TryAlloc(c->size, true);\n\tif (new_block) {\n\t\tmemcpy(new_block + 1, c + 1, c->size - sizeof(cache_system_t));\n\t\tnew_block->user = c->user;\n\t\tmemcpy(new_block->name, c->name, sizeof(new_block->name));\n\t\tCache_Free(c->user);\n\t\tnew_block->user->data = (void*)(new_block + 1);\n\t}\n\telse {\n\t\tCache_Free(c->user); // tough luck...\n\t}\n}\n\n/*\n============\nCache_FreeLow\n\nThrow things out until the hunk can be expanded to the given point\n============\n*/\nvoid Cache_FreeLow(int new_low_hunk)\n{\n\tcache_system_t* c;\n\n\twhile (1) {\n\t\tc = cache_head.next;\n\t\tif (c == &cache_head) {\n\t\t\treturn; // nothing in cache at all\n\t\t}\n\t\tif ((byte*)c >= hunk_base + new_low_hunk) {\n\t\t\treturn; // there is space to grow the hunk\n\t\t}\n\t\tCache_Move(c); // reclaim the space\n\t}\n}\n\n/*\n============\nCache_FreeHigh\n\nThrow things out until the hunk can be expanded to the given point\n============\n*/\nvoid Cache_FreeHigh(int new_high_hunk)\n{\n\tcache_system_t *c, *prev;\n\n\tprev = NULL;\n\twhile (1) {\n\t\tc = cache_head.prev;\n\t\tif (c == &cache_head) {\n\t\t\treturn; // nothing in cache at all\n\t\t}\n\t\tif ((byte*)c + c->size <= hunk_base + hunk_size - new_high_hunk) {\n\t\t\treturn; // there is space to grow the hunk\n\t\t}\n\t\tif (c == prev) {\n\t\t\tCache_Free(c->user); // didn't move out of the way\n\t\t}\n\t\telse {\n\t\t\tCache_Move(c); // try to move it\n\t\t\tprev = c;\n\t\t}\n\t}\n}\n\nvoid Cache_UnlinkLRU(cache_system_t* cs)\n{\n\tif (!cs->lru_next || !cs->lru_prev) {\n\t\tSys_Error(\"Cache_UnlinkLRU: NULL link\");\n\t}\n\n\tcs->lru_next->lru_prev = cs->lru_prev;\n\tcs->lru_prev->lru_next = cs->lru_next;\n\n\tcs->lru_prev = cs->lru_next = NULL;\n}\n\nvoid Cache_MakeLRU(cache_system_t* cs)\n{\n\tif (cs->lru_next || cs->lru_prev) {\n\t\tSys_Error(\"Cache_MakeLRU: active link\");\n\t}\n\n\tcache_head.lru_next->lru_prev = cs;\n\tcs->lru_next = cache_head.lru_next;\n\tcs->lru_prev = &cache_head;\n\tcache_head.lru_next = cs;\n}\n\n/*\n============\nCache_TryAlloc\n\nLooks for a free block of memory between the high and low hunk marks\nSize should already include the header and padding\n============\n*/\ncache_system_t* Cache_TryAlloc(int size, qbool nobottom)\n{\n\tcache_system_t *cs, *new_block;\n\n\t// is the cache completely empty?\n\tif (!nobottom && cache_head.prev == &cache_head) {\n\t\tif (hunk_size - hunk_high_used - hunk_low_used < size) {\n\t\t\tSys_Error(\"Cache_TryAlloc: %i is greater than free hunk\", size);\n\t\t}\n\n\t\tnew_block = (cache_system_t*)(hunk_base + hunk_low_used);\n\t\tmemset(new_block, 0, sizeof(*new_block));\n\t\tnew_block->size = size;\n\n\t\tcache_head.prev = cache_head.next = new_block;\n\t\tnew_block->prev = new_block->next = &cache_head;\n\n\t\tCache_MakeLRU(new_block);\n\t\treturn new_block;\n\t}\n\n\t// search from the bottom up for space\n\tnew_block = (cache_system_t*)(hunk_base + hunk_low_used);\n\tcs = cache_head.next;\n\n\tdo {\n\t\tif (!nobottom || cs != cache_head.next) {\n\t\t\tif ((byte*)cs - (byte*)new_block >= size) {\n\t\t\t\t// found space\n\t\t\t\tmemset(new_block, 0, sizeof(*new_block));\n\t\t\t\tnew_block->size = size;\n\n\t\t\t\tnew_block->next = cs;\n\t\t\t\tnew_block->prev = cs->prev;\n\t\t\t\tcs->prev->next = new_block;\n\t\t\t\tcs->prev = new_block;\n\n\t\t\t\tCache_MakeLRU(new_block);\n\n\t\t\t\treturn new_block;\n\t\t\t}\n\t\t}\n\n\t\t// continue looking\n\t\tnew_block = (cache_system_t*)((byte*)cs + cs->size);\n\t\tcs = cs->next;\n\t} while (cs != &cache_head);\n\n\t// try to allocate one at the very end\n\tif (hunk_base + hunk_size - hunk_high_used - (byte*)new_block >= size) {\n\t\tmemset(new_block, 0, sizeof(*new_block));\n\t\tnew_block->size = size;\n\n\t\tnew_block->next = &cache_head;\n\t\tnew_block->prev = cache_head.prev;\n\t\tcache_head.prev->next = new_block;\n\t\tcache_head.prev = new_block;\n\n\t\tCache_MakeLRU(new_block);\n\n\t\treturn new_block;\n\t}\n\n\treturn NULL; // couldn't allocate\n}\n\n/*\n============\nCache_Flush\n\nThrow everything out, so new data will be demand cached\n============\n*/\nvoid Cache_Flush(void)\n{\n\twhile (cache_head.next != &cache_head) {\n\t\tCache_Free(cache_head.next->user); // reclaim the space\n\t}\n#ifndef SERVERONLY\n\tMod_ClearSimpleTextures();\n#endif\n}\n\n/*\n============\nCache_Print\n\n============\n*/\nvoid Cache_Print(void)\n{\n\tcache_system_t* cd;\n\n\tfor (cd = cache_head.next; cd != &cache_head; cd = cd->next) {\n\t\tCon_Printf(\"%5.1f kB : %s\\n\", (cd->size / (float)(1024)), cd->name);\n\t}\n}\n\n/*\n============\nCache_Report\n\n============\n*/\nvoid Cache_Report(void)\n{\n\tCon_Printf(\"%4.1f of %4.1f megabyte data cache free\\n\",\n\t\t(float)(hunk_size - hunk_high_used - hunk_low_used) / (1024 * 1024),\n\t\t(float)hunk_size / (1024 * 1024));\n}\n\n/*\n============\nCache_Init\n\n============\n*/\nvoid Cache_Init(void)\n{\n\tcache_head.next = cache_head.prev = &cache_head;\n\tcache_head.lru_next = cache_head.lru_prev = &cache_head;\n\n#ifndef WITH_DP_MEM\n\t// If DP mem is used then we can't add commands untill Cmd_Init() executed.\n\tCache_Init_Commands();\n#endif\n}\n\nvoid Cache_Init_Commands(void)\n{\n\tCmd_AddCommand(\"flush\", Cache_Flush);\n\tCmd_AddCommand(\"cache_print\", Cache_Print);\n\tCmd_AddCommand(\"cache_report\", Cache_Report);\n}\n\n#ifndef WITH_DP_MEM\n/*\n==============\nCache_Free\n\nFrees the memory and removes it from the LRU list\n==============\n*/\nvoid Cache_Free(cache_user_t* c)\n{\n\tcache_system_t* cs;\n\n\tif (!c->data) {\n\t\tSys_Error(\"Cache_Free: not allocated\");\n\t}\n\n\tcs = ((cache_system_t*)c->data) - 1;\n\n\tcs->prev->next = cs->next;\n\tcs->next->prev = cs->prev;\n\tcs->next = cs->prev = NULL;\n\n\tc->data = NULL;\n\n\tCache_UnlinkLRU(cs);\n}\n\n/*\n==============\nCache_Check\n==============\n*/\nvoid* Cache_Check(cache_user_t* c)\n{\n\tcache_system_t* cs;\n\n\tif (!c->data) {\n\t\treturn NULL;\n\t}\n\n\tcs = ((cache_system_t*)c->data) - 1;\n\n\t// move to head of LRU\n\tCache_UnlinkLRU(cs);\n\tCache_MakeLRU(cs);\n\n\treturn c->data;\n}\n\n/*\n==============\nCache_Alloc\n==============\n*/\nvoid* Cache_Alloc(cache_user_t* c, int size, const char* name)\n{\n\tcache_system_t* cs;\n\n\tif (c->data) {\n\t\tSys_Error(\"Cache_Alloc: already allocated\");\n\t}\n\n\tif (size <= 0) {\n\t\tSys_Error(\"Cache_Alloc: size %i\", size);\n\t}\n\n\tsize = (size + sizeof(cache_system_t) + 15) & ~15;\n\n\t// find memory for it\n\twhile (1) {\n\t\tcs = Cache_TryAlloc(size, false);\n\t\tif (cs) {\n\t\t\tstrlcpy(cs->name, name, sizeof(cs->name));\n\t\t\tc->data = (void*)(cs + 1);\n\t\t\tcs->user = c;\n\t\t\tbreak;\n\t\t}\n\n\t\t// free the least recently used cahedat\n\t\tif (cache_head.lru_prev == &cache_head) {\n\t\t\tSys_Error(\"Cache_Alloc: out of memory\");\n\t\t}\n\n\t\t// not enough memory at all\n\t\tCache_Free(cache_head.lru_prev->user);\n\t}\n\n\treturn Cache_Check(c);\n}\n#endif // !WITH_DP_MEM\n#endif // SERVERONLY\n\n//============================================================================\n\n/*\n========================\nMemory_Init\n========================\n*/\nvoid Memory_Init(void *buf, int size)\n{\n\thunk_base = (byte *)buf;\n\thunk_size = size;\n\thunk_low_used = 0;\n\thunk_high_used = 0;\n\n\tCache_Init();\n\n\tCmd_AddCommand(\"hunk_print\", Hunk_Print_f);\n}\n"
  },
  {
    "path": "src/zone.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n#ifndef __ZONE_H__\n#define __ZONE_H__\n\n/*\n memory allocation\n\n\nH_??? The hunk manages the entire memory block given to quake.  It must be\ncontiguous.  Memory can be allocated from either the low or high end in a\nstack fashion.  The only way memory is released is by resetting one of the\npointers.\n\nHunk allocations should be given a name, so the Hunk_Print () function\ncan display usage.\n\nHunk allocations are guaranteed to be 16 byte aligned.\n\nThe video buffers are allocated high to avoid leaving a hole underneath\nserver allocations when changing to a higher video mode.\n\n\nCache_??? Cache memory is for objects that can be dynamically loaded and\ncan usefully stay persistent between levels.  The size of the cache\nfluctuates from level to level.\n\nTo allocate a cachable object\n\n\nTemp_??? Temp memory is used for file loading and surface caching.  The size\nof the cache memory is adjusted so that there is a minimum of 512k remaining\nfor temp memory.\n\n\n------ Top of Memory -------\n\nhigh hunk allocations\n\n<--- high hunk reset point held by vid\n\nvideo buffer\n\nz buffer\n\nsurface cache\n\n<--- high hunk used\n\ncachable memory\n\n<--- low hunk used\n\nclient and server low hunk allocations\n\n<-- low hunk reset point held by host\n\nstartup hunk allocations\n\n----- Bottom of Memory -----\n\n\n\n*/\n\nvoid Memory_Init (void *buf, int size);\n\nvoid *Hunk_AllocName (int size, const char *name);\n\nint\tHunk_LowMark (void);\nvoid Hunk_FreeToLowMark (int mark);\n\nvoid Hunk_TempFlush(void);\nvoid *Hunk_TempAlloc (int size);\nvoid *Hunk_TempAllocMore(int size);\n\nvoid Hunk_Check (void);\n\n#ifdef SERVERONLY\ntypedef struct cache_user_s\n{\n\tvoid *data;\n} cache_user_t;\n\nvoid Cache_Flush (void);\n\nvoid *Cache_Check (cache_user_t *c);\n// returns the cached data, and moves to the head of the LRU list\n// if present, otherwise returns NULL\n\nvoid Cache_Free(cache_user_t *c);\n\nvoid *Cache_Alloc (cache_user_t *c, int size, const char *name);\n// Returns NULL if all purgeable data was tossed and there still\n// wasn't enough room.\n\nvoid Cache_Report (void);\nvoid Cache_Init_Commands (void);\n#endif\n\n#endif /* !__ZONE_H__ */\n"
  },
  {
    "path": "vcpkg.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json\",\n  \"name\": \"ezquake\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": [\n    { \"name\": \"pkgconf\", \"default-features\": false, \"platform\": \"osx\", \"host\": true },\n    { \"name\": \"zlib\", \"default-features\": false },\n    { \"name\": \"pcre2\", \"default-features\": false },\n    { \"name\": \"minizip\", \"default-features\": false },\n    { \"name\": \"expat\", \"default-features\": false },\n    { \"name\": \"jansson\", \"default-features\": false },\n    { \"name\": \"curl\", \"default-features\": false, \"features\": [\"ssl\"], \"platform\": \"windows\" },\n    { \"name\": \"curl\", \"default-features\": false, \"features\": [\"mbedtls\"], \"platform\": \"linux, osx\" },\n    { \"name\": \"libpng\", \"default-features\": false },\n    { \"name\": \"libjpeg-turbo\", \"default-features\": false },\n    { \"name\": \"freetype\", \"default-features\": false, \"features\": [\"zlib\"] },\n    { \"name\": \"speex\", \"default-features\": false },\n    { \"name\": \"speexdsp\", \"default-features\": false },\n    { \"name\": \"libsndfile\", \"default-features\": false, \"features\": [\"external-libs\"] },\n    { \"name\": \"alsa\", \"default-features\": false, \"platform\": \"linux\" },\n    { \"name\": \"sdl2\", \"default-features\": false, \"platform\": \"windows, osx\" },\n    { \"name\": \"sdl2\", \"default-features\": false, \"features\": [\"wayland\", \"x11\", \"alsa\"], \"platform\": \"linux\" }\n  ]\n}\n"
  }
]